From 6118fca000eaf094b80787d5290bef8e56af2d02 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 27 Sep 2024 22:29:23 +0100 Subject: [PATCH 001/567] readme: update group links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e62fb8c8b3..c2df084477 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ You must: Messages not following these rules will be deleted, the right to send messages may be revoked, and the access to the new members to the group may be temporarily restricted, to prevent re-joining under a different name - our imperfect group moderation does not have a better solution at the moment. -You can join an English-speaking users group if you want to ask any questions: [#SimpleX users group](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2Fos8FftfoV8zjb2T89fUEjJtF7y64p5av%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAQqMgh0fw2lPhjn3PDIEfAKA_E0-gf8Hr8zzhYnDivRs%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22lBPiveK2mjfUH43SN77R0w%3D%3D%22%7D) +You can join an English-speaking users group if you want to ask any questions: [#SimpleX users group](https://simplex.chat/contact#/?v=2-4&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2Fos8FftfoV8zjb2T89fUEjJtF7y64p5av%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAQqMgh0fw2lPhjn3PDIEfAKA_E0-gf8Hr8zzhYnDivRs%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22lBPiveK2mjfUH43SN77R0w%3D%3D%22%7D) There is also a group [#simplex-devs](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FvYCRjIflKNMGYlfTkuHe4B40qSlQ0439%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAHNdcqNbzXZhyMoSBjT2R0-Eb1EPaLyUg3KZjn-kmM1w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22PD20tcXjw7IpkkMCfR6HLA%3D%3D%22%7D) for developers who build on SimpleX platform: @@ -83,7 +83,7 @@ There is also a group [#simplex-devs](https://simplex.chat/contact#/?v=1-4&smp=s There are groups in other languages, that we have the apps interface translated into. These groups are for testing, and asking questions to other SimpleX Chat users: -[\#SimpleX-DE](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FkIEl7OQzcp-J6aDmjdlQbRJwqkcZE7XR%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAR16PCu02MobRmKAsjzhDWMZcWP9hS8l5AUZi-Gs8z18%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22puYPMCQt11yPUvgmI5jCiw%3D%3D%22%7D) (German-speaking), [\#SimpleX-ES](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FaJ8O1O8A8GbeoaHTo_V8dcefaCl7ouPb%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEA034qWTA3sWcTsi6aWhNf9BA34vKVCFaEBdP2R66z6Ao%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22wiZ1v_wNjLPlT-nCSB-bRA%3D%3D%22%7D) (Spanish-speaking), [\#SimpleX-FR](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FvIHQDxTor53nwnWWTy5cHNwQQAdWN5Hw%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAPdgK1eBnETmgiqEQufbUkydKBJafoRx4iRrtrC2NAGc%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%221FyUryBPza-1ZFFE80Ekbg%3D%3D%22%7D) (French-speaking), [\#SimpleX-RU](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FXZyt3hJmWsycpN7Dqve_wbrAqb6myk1R%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAMFVIoytozTEa_QXOgoZFq_oe0IwZBYKvW50trSFXzXo%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22xz05ngjA3pNIxLZ32a8Vxg%3D%3D%22%7D) (Russian-speaking), [\#SimpleX-IT](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2F0weR-ZgDUl7ruOtI_8TZwEsnJP6UiImA%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAq4PSThO9Fvb5ydF48wB0yNbpzCbuQJCW3vZ9BGUfcxk%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22e-iceLA0SctC62eARgYDWg%3D%3D%22%7D) (Italian-speaking). +[\#SimpleX-DE](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FmfiivxDKWFuowXrQOp11jsY8TuP__rBL%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAiz3pKNwvKudckFYMUfgoT0s96B0jfZ7ALHAu7rtE9HQ%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22jZeJpXGrRXQJU_-MSJ_v2A%3D%3D%22%7D) (German-speaking), [\#SimpleX-ES](https://simplex.chat/contact#/?v=2-4&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FJ5ES83pJimY2BRklS8fvy_iQwIU37xra%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEA0F0STP6UqN_12_k2cjjTrIjFgBGeWhOAmbY1qlk3pnM%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22VmUU0fqmYdCRmVCyvStvHA%3D%3D%22%7D) (Spanish-speaking), [\#SimpleX-FR](https://simplex.chat/contact#/?v=2-7&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FxCHBE_6PBRMqNEpm4UQDHXb9cz-mN7dd%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAetqlcM7zTCRw-iatnwCrvpJSto7lq5Yv6AsBMWv7GSM%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22foO5Xw4hhjOa_x7zET7otw%3D%3D%22%7D) (French-speaking), [\#SimpleX-RU](https://simplex.chat/contact#/?v=2-4&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FVXQTB0J2lLjYkgjWByhl6-1qmb5fgZHh%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAI6JaEWezfSwvcoTEkk6au-gkjrXR2ew2OqZYMYBvayk%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22ORH9OEe8Duissh-hslfeVg%3D%3D%22%7D) (Russian-speaking), [\#SimpleX-IT](https://simplex.chat/contact#/?v=2-7&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FqpHu0psOUdYfc11yQCzSyq5JhijrBzZT%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEACZ_7fbwlM45wl6cGif8cY47oPQ_AMdP0ATqOYLA6zHY%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%229uRQRTir3ealdcSfB0zsrw%3D%3D%22%7D) (Italian-speaking). You can join either by opening these links in the app or by opening them in a desktop browser and scanning the QR code. From fabbe0285dad176faf4c4a5a49bca81ce0a0f11e Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 9 Oct 2024 14:37:21 +0700 Subject: [PATCH 002/567] ui: rely on different value in stats when checking calls media (#5007) * ui: rely on different value in stats when checking calls media * int64 --- apps/ios/Shared/Views/Call/WebRTCClient.swift | 9 ++++----- .../src/commonMain/resources/assets/www/call.js | 11 +++++------ packages/simplex-chat-webrtc/src/call.ts | 11 +++++------ 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/apps/ios/Shared/Views/Call/WebRTCClient.swift b/apps/ios/Shared/Views/Call/WebRTCClient.swift index 389e5d0503..db7910836e 100644 --- a/apps/ios/Shared/Views/Call/WebRTCClient.swift +++ b/apps/ios/Shared/Views/Call/WebRTCClient.swift @@ -306,8 +306,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg func setupMuteUnmuteListener(_ transceiver: RTCRtpTransceiver, _ track: RTCMediaStreamTrack) { // logger.log("Setting up mute/unmute listener in the call without encryption for mid = \(transceiver.mid)") Task { - // for some reason even for disabled tracks one packet arrives (seeing this on screenVideo track) - var lastPacketsReceived = 1 + var lastBytesReceived: Int64 = 0 // muted initially var mutedSeconds = 4 while let call = self.activeCall, transceiver.receiver.track?.readyState == .live { @@ -315,8 +314,8 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg let stat = stats.statistics.values.first(where: { stat in stat.type == "inbound-rtp"}) if let stat { //logger.debug("Stat \(stat.debugDescription)") - let packets = stat.values["packetsReceived"] as! Int - if packets <= lastPacketsReceived { + let bytes = stat.values["bytesReceived"] as! Int64 + if bytes <= lastBytesReceived { mutedSeconds += 1 if mutedSeconds == 3 { await MainActor.run { @@ -329,7 +328,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg self.onMediaMuteUnmute(transceiver.mid, false) } } - lastPacketsReceived = packets + lastBytesReceived = bytes mutedSeconds = 0 } } diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/www/call.js b/apps/multiplatform/common/src/commonMain/resources/assets/www/call.js index 0e58050fcf..32f014e622 100644 --- a/apps/multiplatform/common/src/commonMain/resources/assets/www/call.js +++ b/apps/multiplatform/common/src/commonMain/resources/assets/www/call.js @@ -955,8 +955,7 @@ const processCommand = (function () { function setupMuteUnmuteListener(transceiver, track) { // console.log("Setting up mute/unmute listener in the call without encryption for mid = ", transceiver.mid) let inboundStatsId = ""; - // for some reason even for disabled tracks one packet arrives (seeing this on screenVideo track) - let lastPacketsReceived = 1; + let lastBytesReceived = 0; // muted initially let mutedSeconds = 4; let statsInterval = setInterval(async () => { @@ -970,9 +969,9 @@ const processCommand = (function () { }); } if (inboundStatsId) { - // even though MSDN site says `packetsReceived` is available in WebView 80+, in reality it's available even in 69 - const packets = (_a = stats.get(inboundStatsId)) === null || _a === void 0 ? void 0 : _a.packetsReceived; - if (packets <= lastPacketsReceived) { + // even though MSDN site says `bytesReceived` is available in WebView 80+, in reality it's available even in 69 + const bytes = (_a = stats.get(inboundStatsId)) === null || _a === void 0 ? void 0 : _a.bytesReceived; + if (bytes <= lastBytesReceived) { mutedSeconds++; if (mutedSeconds == 3) { onMediaMuteUnmute(transceiver.mid, true); @@ -982,7 +981,7 @@ const processCommand = (function () { if (mutedSeconds >= 3) { onMediaMuteUnmute(transceiver.mid, false); } - lastPacketsReceived = packets; + lastBytesReceived = bytes; mutedSeconds = 0; } } diff --git a/packages/simplex-chat-webrtc/src/call.ts b/packages/simplex-chat-webrtc/src/call.ts index a961dbe442..eda535cfa7 100644 --- a/packages/simplex-chat-webrtc/src/call.ts +++ b/packages/simplex-chat-webrtc/src/call.ts @@ -1272,8 +1272,7 @@ const processCommand = (function () { function setupMuteUnmuteListener(transceiver: RTCRtpTransceiver, track: MediaStreamTrack) { // console.log("Setting up mute/unmute listener in the call without encryption for mid = ", transceiver.mid) let inboundStatsId = "" - // for some reason even for disabled tracks one packet arrives (seeing this on screenVideo track) - let lastPacketsReceived = 1 + let lastBytesReceived = 0 // muted initially let mutedSeconds = 4 let statsInterval = setInterval(async () => { @@ -1286,9 +1285,9 @@ const processCommand = (function () { }) } if (inboundStatsId) { - // even though MSDN site says `packetsReceived` is available in WebView 80+, in reality it's available even in 69 - const packets = (stats as any).get(inboundStatsId)?.packetsReceived - if (packets <= lastPacketsReceived) { + // even though MSDN site says `bytesReceived` is available in WebView 80+, in reality it's available even in 69 + const bytes = (stats as any).get(inboundStatsId)?.bytesReceived + if (bytes <= lastBytesReceived) { mutedSeconds++ if (mutedSeconds == 3) { onMediaMuteUnmute(transceiver.mid, true) @@ -1297,7 +1296,7 @@ const processCommand = (function () { if (mutedSeconds >= 3) { onMediaMuteUnmute(transceiver.mid, false) } - lastPacketsReceived = packets + lastBytesReceived = bytes mutedSeconds = 0 } } From 12423f4afad4868ce9fc7f116ad8cd78155158aa Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 9 Oct 2024 15:15:58 +0400 Subject: [PATCH 003/567] core: test db indexes (#4999) --- src/Simplex/Chat/Migrations/chat_lint.sql | 2 ++ tests/SchemaDump.hs | 35 +++++++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 src/Simplex/Chat/Migrations/chat_lint.sql diff --git a/src/Simplex/Chat/Migrations/chat_lint.sql b/src/Simplex/Chat/Migrations/chat_lint.sql new file mode 100644 index 0000000000..86047070fb --- /dev/null +++ b/src/Simplex/Chat/Migrations/chat_lint.sql @@ -0,0 +1,2 @@ +CREATE INDEX 'chat_items_group_id' ON 'chat_items'('group_id'); --> groups(group_id) +CREATE INDEX 'connections_group_member_id' ON 'connections'('group_member_id'); --> group_members(group_member_id) diff --git a/tests/SchemaDump.hs b/tests/SchemaDump.hs index b6bc91e48b..23f36713b4 100644 --- a/tests/SchemaDump.hs +++ b/tests/SchemaDump.hs @@ -24,12 +24,29 @@ testDB = "tests/tmp/test_chat.db" appSchema :: FilePath appSchema = "src/Simplex/Chat/Migrations/chat_schema.sql" +-- Some indexes found by `.lint fkey-indexes` are not added to schema, explanation: +-- +-- - CREATE INDEX 'chat_items_group_id' ON 'chat_items'('group_id'); --> groups(group_id) +-- +-- Covering index is used instead. See for example: +-- EXPLAIN QUERY PLAN DELETE FROM groups; +-- (uses idx_chat_items_groups_item_status) +-- +-- - CREATE INDEX 'connections_group_member_id' ON 'connections'('group_member_id'); --> group_members(group_member_id) +-- +-- Covering index is used instead. See for example: +-- EXPLAIN QUERY PLAN DELETE FROM group_members; +-- (uses idx_connections_group_member) +appLint :: FilePath +appLint = "src/Simplex/Chat/Migrations/chat_lint.sql" + testSchema :: FilePath testSchema = "tests/tmp/test_agent_schema.sql" schemaDumpTest :: Spec schemaDumpTest = do it "verify and overwrite schema dump" testVerifySchemaDump + it "verify .lint fkey-indexes" testVerifyLintFKeyIndexes it "verify schema down migrations" testSchemaMigrations testVerifySchemaDump :: IO () @@ -40,6 +57,14 @@ testVerifySchemaDump = withTmpFiles $ do getSchema testDB appSchema `shouldReturn` savedSchema removeFile testDB +testVerifyLintFKeyIndexes :: IO () +testVerifyLintFKeyIndexes = withTmpFiles $ do + savedLint <- ifM (doesFileExist appLint) (readFile appLint) (pure "") + savedLint `deepseq` pure () + void $ createChatStore testDB "" False MCError + getLintFKeyIndexes testDB "tests/tmp/chat_lint.sql" `shouldReturn` savedLint + removeFile testDB + testSchemaMigrations :: IO () testSchemaMigrations = withTmpFiles $ do let noDownMigrations = dropWhileEnd (\Migration {down} -> isJust down) Store.migrations @@ -81,7 +106,13 @@ skipComparisonForDownMigrations = ] getSchema :: FilePath -> FilePath -> IO String -getSchema dpPath schemaPath = do - void $ readCreateProcess (shell $ "sqlite3 " <> dpPath <> " '.schema --indent' > " <> schemaPath) "" +getSchema dbPath schemaPath = do + void $ readCreateProcess (shell $ "sqlite3 " <> dbPath <> " '.schema --indent' > " <> schemaPath) "" sch <- readFile schemaPath sch `deepseq` pure sch + +getLintFKeyIndexes :: FilePath -> FilePath -> IO String +getLintFKeyIndexes dbPath lintPath = do + void $ readCreateProcess (shell $ "sqlite3 " <> dbPath <> " '.lint fkey-indexes' > " <> lintPath) "" + lint <- readFile lintPath + lint `deepseq` pure lint From ac5f0bc7bb4d1e707d2fe1a8cf2b70e60e909b41 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 9 Oct 2024 12:31:51 +0100 Subject: [PATCH 004/567] ui: allow deleting and moderating up to 200 messages (#5010) --- .../Chat/SelectableChatItemToolbars.swift | 18 ++++++++------- .../views/chat/SelectableChatItemToolbars.kt | 23 +++++++++++-------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/SelectableChatItemToolbars.swift b/apps/ios/Shared/Views/Chat/SelectableChatItemToolbars.swift index 746c423b8f..7b185d8211 100644 --- a/apps/ios/Shared/Views/Chat/SelectableChatItemToolbars.swift +++ b/apps/ios/Shared/Views/Chat/SelectableChatItemToolbars.swift @@ -41,7 +41,8 @@ struct SelectedItemsBottomToolbar: View { @State var forwardEnabled: Bool = false - @State var allButtonsDisabled = false + @State var deleteCountProhibited = false + @State var forwardCountProhibited = false var body: some View { VStack(spacing: 0) { @@ -55,9 +56,9 @@ struct SelectedItemsBottomToolbar: View { .resizable() .scaledToFit() .frame(width: 20, height: 20, alignment: .center) - .foregroundColor(!deleteEnabled || allButtonsDisabled ? theme.colors.secondary: .red) + .foregroundColor(!deleteEnabled || deleteCountProhibited ? theme.colors.secondary: .red) } - .disabled(!deleteEnabled || allButtonsDisabled) + .disabled(!deleteEnabled || deleteCountProhibited) Spacer() Button { @@ -67,9 +68,9 @@ struct SelectedItemsBottomToolbar: View { .resizable() .scaledToFit() .frame(width: 20, height: 20, alignment: .center) - .foregroundColor(!moderateEnabled || allButtonsDisabled ? theme.colors.secondary : .red) + .foregroundColor(!moderateEnabled || deleteCountProhibited ? theme.colors.secondary : .red) } - .disabled(!moderateEnabled || allButtonsDisabled) + .disabled(!moderateEnabled || deleteCountProhibited) .opacity(canModerate ? 1 : 0) Spacer() @@ -80,9 +81,9 @@ struct SelectedItemsBottomToolbar: View { .resizable() .scaledToFit() .frame(width: 20, height: 20, alignment: .center) - .foregroundColor(!forwardEnabled || allButtonsDisabled ? theme.colors.secondary : theme.colors.primary) + .foregroundColor(!forwardEnabled || forwardCountProhibited ? theme.colors.secondary : theme.colors.primary) } - .disabled(!forwardEnabled || allButtonsDisabled) + .disabled(!forwardEnabled || forwardCountProhibited) } .frame(maxHeight: .infinity) .padding([.leading, .trailing], 12) @@ -105,7 +106,8 @@ struct SelectedItemsBottomToolbar: View { private func recheckItems(_ chatInfo: ChatInfo, _ chatItems: [ChatItem], _ selectedItems: Set?) { let count = selectedItems?.count ?? 0 - allButtonsDisabled = count == 0 || count > 20 + deleteCountProhibited = count == 0 || count > 200 + forwardCountProhibited = count == 0 || count > 20 canModerate = possibleToModerate(chatInfo) if let selected = selectedItems { let me: Bool diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt index d12e7ac090..5cf9ebb6c7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt @@ -59,7 +59,8 @@ fun SelectedItemsBottomToolbar( val canModerate = remember { mutableStateOf(false) } val moderateEnabled = remember { mutableStateOf(false) } val forwardEnabled = remember { mutableStateOf(false) } - val allButtonsDisabled = remember { mutableStateOf(false) } + val deleteCountProhibited = remember { mutableStateOf(false) } + val forwardCountProhibited = remember { mutableStateOf(false) } Box { // It's hard to measure exact height of ComposeView with different fontSizes. Better to depend on actual ComposeView, even empty ComposeView(chatModel = chatModel, Chat.sampleData, remember { mutableStateOf(ComposeState(useLinkPreviews = false)) }, remember { mutableStateOf(null) }, {}) @@ -75,36 +76,36 @@ fun SelectedItemsBottomToolbar( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - IconButton({ deleteItems(deleteForEveryoneEnabled.value) }, enabled = deleteEnabled.value && !allButtonsDisabled.value) { + IconButton({ deleteItems(deleteForEveryoneEnabled.value) }, enabled = deleteEnabled.value && !deleteCountProhibited.value) { Icon( painterResource(MR.images.ic_delete), null, Modifier.size(22.dp), - tint = if (!deleteEnabled.value || allButtonsDisabled.value) MaterialTheme.colors.secondary else MaterialTheme.colors.error + tint = if (!deleteEnabled.value || deleteCountProhibited.value) MaterialTheme.colors.secondary else MaterialTheme.colors.error ) } - IconButton({ moderateItems() }, Modifier.alpha(if (canModerate.value) 1f else 0f), enabled = moderateEnabled.value && !allButtonsDisabled.value) { + IconButton({ moderateItems() }, Modifier.alpha(if (canModerate.value) 1f else 0f), enabled = moderateEnabled.value && !deleteCountProhibited.value) { Icon( painterResource(MR.images.ic_flag), null, Modifier.size(22.dp), - tint = if (!moderateEnabled.value || allButtonsDisabled.value) MaterialTheme.colors.secondary else MaterialTheme.colors.error + tint = if (!moderateEnabled.value || deleteCountProhibited.value) MaterialTheme.colors.secondary else MaterialTheme.colors.error ) } - IconButton({ forwardItems() }, enabled = forwardEnabled.value && !allButtonsDisabled.value) { + IconButton({ forwardItems() }, enabled = forwardEnabled.value && !forwardCountProhibited.value) { Icon( painterResource(MR.images.ic_forward), null, Modifier.size(22.dp), - tint = if (!forwardEnabled.value || allButtonsDisabled.value) MaterialTheme.colors.secondary else MaterialTheme.colors.primary + tint = if (!forwardEnabled.value || forwardCountProhibited.value) MaterialTheme.colors.secondary else MaterialTheme.colors.primary ) } } } LaunchedEffect(chatInfo, chatItems, selectedChatItems.value) { - recheckItems(chatInfo, chatItems, selectedChatItems, deleteEnabled, deleteForEveryoneEnabled, canModerate, moderateEnabled, forwardEnabled, allButtonsDisabled) + recheckItems(chatInfo, chatItems, selectedChatItems, deleteEnabled, deleteForEveryoneEnabled, canModerate, moderateEnabled, forwardEnabled, deleteCountProhibited, forwardCountProhibited) } } @@ -116,10 +117,12 @@ private fun recheckItems(chatInfo: ChatInfo, canModerate: MutableState, moderateEnabled: MutableState, forwardEnabled: MutableState, - allButtonsDisabled: MutableState + deleteCountProhibited: MutableState, + forwardCountProhibited: MutableState ) { val count = selectedChatItems.value?.size ?: 0 - allButtonsDisabled.value = count == 0 || count > 20 + deleteCountProhibited.value = count == 0 || count > 200 + forwardCountProhibited.value = count == 0 || count > 20 canModerate.value = possibleToModerate(chatInfo) val selected = selectedChatItems.value ?: return var rDeleteEnabled = true From 03865b4a182835c5d1e73e3e9f6470fc25cf6754 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 9 Oct 2024 15:56:05 +0100 Subject: [PATCH 005/567] ios: v6.1 whats new (#5005) * ios: v6.1 whats new * update * android, desktop --- .../Views/Onboarding/WhatsNewView.swift | 33 +++++++++++++++++ .../common/views/onboarding/WhatsNewView.kt | 36 +++++++++++++++++-- .../commonMain/resources/MR/base/strings.xml | 10 ++++++ .../resources/MR/images/ic_calendar.svg | 1 + 4 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_calendar.svg diff --git a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift index ed3adcfe7d..2ae4aa8c2b 100644 --- a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift +++ b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift @@ -467,6 +467,39 @@ private let versionDescriptions: [VersionDescription] = [ ) ] ), + VersionDescription( + version: "v6.1", + post: URL(string: "https://simplex.chat/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html"), + features: [ + FeatureDescription( + icon: "checkmark.shield", + title: "Better security ✅", + description: "SimpleX protocols reviewed by Trail of Bits." + ), + FeatureDescription( + icon: "video", + title: "Better calls", + description: "Switch audio and video during the call." + ), + FeatureDescription( + icon: "bolt", + title: "Better notifications", + description: "Improved delivery, reduced traffic usage.\nMore improvements are coming soon!" + ), + FeatureDescription( + icon: nil, + title: "Better user experience", + description: nil, + subfeatures: [ + ("link", "Switch chat profile for 1-time invitations."), + ("message", "Customizable message shape."), + ("calendar", "Better message dates."), + ("arrowshape.turn.up.right", "Forward up to 20 messages at once."), + ("flag", "Delete or moderate up to 200 messages.") + ] + ), + ] + ), ] private let lastVersion = versionDescriptions.last!.version diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt index be9acedd89..703f3b8915 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt @@ -53,7 +53,8 @@ fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) { maxLines = 2, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.h4, - fontWeight = FontWeight.Medium + fontWeight = FontWeight.Medium, + modifier = Modifier.padding(bottom = 6.dp) ) if (link != null) { linkButton(link) @@ -64,7 +65,7 @@ fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), - modifier = Modifier.padding(bottom = 4.dp) + modifier = Modifier.padding(bottom = 6.dp) ) { Icon(painterResource(si), stringResource(sd), tint = MaterialTheme.colors.secondary) Text(generalGetString(sd), fontSize = 15.sp) @@ -648,7 +649,36 @@ private val versionDescriptions: List = listOf( show = appPlatform.isDesktop ), ), - ) + ), + VersionDescription( + version = "v6.1", + post = "https://simplex.chat/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html", + features = listOf( + FeatureDescription( + icon = MR.images.ic_verified_user, + titleId = MR.strings.v6_1_better_security, + descrId = MR.strings.v6_1_better_security_descr, + link = "https://simplex.chat/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html" + ), + FeatureDescription( + icon = MR.images.ic_videocam, + titleId = MR.strings.v6_1_better_calls, + descrId = MR.strings.v6_1_better_calls_descr + ), + FeatureDescription( + icon = null, + titleId = MR.strings.v6_1_better_user_experience, + descrId = null, + subfeatures = listOf( + MR.images.ic_link to MR.strings.v6_1_switch_chat_profile_descr, + MR.images.ic_chat to MR.strings.v6_1_customizable_message_descr, + MR.images.ic_calendar to MR.strings.v6_1_message_dates_descr, + MR.images.ic_forward to MR.strings.v6_1_forward_many_messages_descr, + MR.images.ic_delete to MR.strings.v6_1_delete_many_messages_descr + ) + ), + ), + ), ) private val lastVersion = versionDescriptions.last().version diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 2f6c601558..bb755f2b58 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -2041,6 +2041,16 @@ Download new versions from GitHub. Control your network Connection and servers status. + Better security ✅ + SimpleX protocols reviewed by Trail of Bits. + Better calls + Switch audio and video during the call. + Better user experience + Switch chat profile for 1-time invitations. + Customizable message shape. + Better message dates. + Forward up to 20 messages at once. + Delete or moderate up to 200 messages. seconds diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_calendar.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_calendar.svg new file mode 100644 index 0000000000..bac344b0c8 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_calendar.svg @@ -0,0 +1 @@ + \ No newline at end of file From 0c69f6553a3501f1f26bf17814aa1a0e689d898b Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Thu, 10 Oct 2024 12:57:15 +0300 Subject: [PATCH 006/567] ios: fix group member sheet load animation (#5008) * ios: fix group member sheet load animation * improve diff * rename * revert moving sections * re-indent --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Model/SimpleXAPI.swift | 12 +- .../Chat/ChatItem/CIRcvDecryptionError.swift | 2 +- .../Chat/Group/GroupMemberInfoView.swift | 183 +++++++++--------- 3 files changed, 106 insertions(+), 91 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 74cee396c7..17f5936b6b 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -585,12 +585,18 @@ func apiContactInfo(_ contactId: Int64) async throws -> (ConnectionStats?, Profi throw r } -func apiGroupMemberInfo(_ groupId: Int64, _ groupMemberId: Int64) throws -> (GroupMember, ConnectionStats?) { +func apiGroupMemberInfoSync(_ groupId: Int64, _ groupMemberId: Int64) throws -> (GroupMember, ConnectionStats?) { let r = chatSendCmdSync(.apiGroupMemberInfo(groupId: groupId, groupMemberId: groupMemberId)) if case let .groupMemberInfo(_, _, member, connStats_) = r { return (member, connStats_) } throw r } +func apiGroupMemberInfo(_ groupId: Int64, _ groupMemberId: Int64) async throws -> (GroupMember, ConnectionStats?) { + let r = await chatSendCmd(.apiGroupMemberInfo(groupId: groupId, groupMemberId: groupMemberId)) + if case let .groupMemberInfo(_, _, member, connStats_) = r { return (member, connStats_) } + throw r +} + func apiContactQueueInfo(_ contactId: Int64) async throws -> (RcvMsgInfo?, ServerQueueInfo) { let r = await chatSendCmd(.apiContactQueueInfo(contactId: contactId)) if case let .queueInfo(_, rcvMsgInfo, queueInfo) = r { return (rcvMsgInfo, queueInfo) } @@ -645,8 +651,8 @@ func apiGetContactCode(_ contactId: Int64) async throws -> (Contact, String) { throw r } -func apiGetGroupMemberCode(_ groupId: Int64, _ groupMemberId: Int64) throws -> (GroupMember, String) { - let r = chatSendCmdSync(.apiGetGroupMemberCode(groupId: groupId, groupMemberId: groupMemberId)) +func apiGetGroupMemberCode(_ groupId: Int64, _ groupMemberId: Int64) async throws -> (GroupMember, String) { + let r = await chatSendCmd(.apiGetGroupMemberCode(groupId: groupId, groupMemberId: groupMemberId)) if case let .groupMemberCode(_, _, member, connectionCode) = r { return (member, connectionCode) } throw r } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift index c76ffe8c05..693641b1d3 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift @@ -48,7 +48,7 @@ struct CIRcvDecryptionError: View { if case let .group(groupInfo) = chat.chatInfo, case let .groupRcv(groupMember) = chatItem.chatDir { do { - let (member, stats) = try apiGroupMemberInfo(groupInfo.apiId, groupMember.groupMemberId) + let (member, stats) = try apiGroupMemberInfoSync(groupInfo.apiId, groupMember.groupMemberId) if let s = stats { m.updateGroupMemberConnectionStats(groupInfo, member, s) } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index ddf3b8e4b9..fd72b5b515 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -18,6 +18,7 @@ struct GroupMemberInfoView: View { var navigation: Bool = false @State private var connectionStats: ConnectionStats? = nil @State private var connectionCode: String? = nil + @State private var connectionLoaded: Bool = false @State private var newRole: GroupMemberRole = .member @State private var alert: GroupMemberInfoViewAlert? @State private var sheet: PlanAndConnectActionSheet? @@ -94,129 +95,137 @@ struct GroupMemberInfoView: View { .listRowSeparator(.hidden) .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) - if member.memberActive { - Section { - if let code = connectionCode { verifyCodeButton(code) } - if let connStats = connectionStats, - connStats.ratchetSyncAllowed { - synchronizeConnectionButton() - } - // } else if developerTools { - // synchronizeConnectionButtonForce() - // } - } - } + if connectionLoaded { - if let contactLink = member.contactLink { - Section { - SimpleXLinkQRCode(uri: contactLink) - Button { - showShareSheet(items: [simplexChatLink(contactLink)]) - } label: { - Label("Share address", systemImage: "square.and.arrow.up") + if member.memberActive { + Section { + if let code = connectionCode { verifyCodeButton(code) } + if let connStats = connectionStats, + connStats.ratchetSyncAllowed { + synchronizeConnectionButton() + } + // } else if developerTools { + // synchronizeConnectionButtonForce() + // } } - if let contactId = member.memberContactId { - if knownDirectChat(contactId) == nil && !groupInfo.fullGroupPreferences.directMessages.on(for: groupInfo.membership) { + } + + if let contactLink = member.contactLink { + Section { + SimpleXLinkQRCode(uri: contactLink) + Button { + showShareSheet(items: [simplexChatLink(contactLink)]) + } label: { + Label("Share address", systemImage: "square.and.arrow.up") + } + if let contactId = member.memberContactId { + if knownDirectChat(contactId) == nil && !groupInfo.fullGroupPreferences.directMessages.on(for: groupInfo.membership) { + connectViaAddressButton(contactLink) + } + } else { connectViaAddressButton(contactLink) } - } else { - connectViaAddressButton(contactLink) + } header: { + Text("Address") + .foregroundColor(theme.colors.secondary) + } footer: { + Text("You can share this address with your contacts to let them connect with **\(member.displayName)**.") + .foregroundColor(theme.colors.secondary) } - } header: { - Text("Address") - .foregroundColor(theme.colors.secondary) - } footer: { - Text("You can share this address with your contacts to let them connect with **\(member.displayName)**.") - .foregroundColor(theme.colors.secondary) } - } - Section(header: Text("Member").foregroundColor(theme.colors.secondary)) { - infoRow("Group", groupInfo.displayName) + Section(header: Text("Member").foregroundColor(theme.colors.secondary)) { + infoRow("Group", groupInfo.displayName) - if let roles = member.canChangeRoleTo(groupInfo: groupInfo) { - Picker("Change role", selection: $newRole) { - ForEach(roles) { role in - Text(role.text) + if let roles = member.canChangeRoleTo(groupInfo: groupInfo) { + Picker("Change role", selection: $newRole) { + ForEach(roles) { role in + Text(role.text) + } } + .frame(height: 36) + } else { + infoRow("Role", member.memberRole.text) } - .frame(height: 36) - } else { - infoRow("Role", member.memberRole.text) } - } - if let connStats = connectionStats { - Section(header: Text("Servers").foregroundColor(theme.colors.secondary)) { - // TODO network connection status - Button("Change receiving address") { - alert = .switchAddressAlert - } - .disabled( - connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil } - || connStats.ratchetSyncSendProhibited - ) - if connStats.rcvQueuesInfo.contains(where: { $0.rcvSwitchStatus != nil }) { - Button("Abort changing address") { - alert = .abortSwitchAddressAlert + if let connStats = connectionStats { + Section(header: Text("Servers").foregroundColor(theme.colors.secondary)) { + // TODO network connection status + Button("Change receiving address") { + alert = .switchAddressAlert } .disabled( - connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil && !$0.canAbortSwitch } + connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil } || connStats.ratchetSyncSendProhibited ) + if connStats.rcvQueuesInfo.contains(where: { $0.rcvSwitchStatus != nil }) { + Button("Abort changing address") { + alert = .abortSwitchAddressAlert + } + .disabled( + connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil && !$0.canAbortSwitch } + || connStats.ratchetSyncSendProhibited + ) + } + smpServers("Receiving via", connStats.rcvQueuesInfo.map { $0.rcvServer }, theme.colors.secondary) + smpServers("Sending via", connStats.sndQueuesInfo.map { $0.sndServer }, theme.colors.secondary) } - smpServers("Receiving via", connStats.rcvQueuesInfo.map { $0.rcvServer }, theme.colors.secondary) - smpServers("Sending via", connStats.sndQueuesInfo.map { $0.sndServer }, theme.colors.secondary) } - } - if groupInfo.membership.memberRole >= .admin { - adminDestructiveSection(member) - } else { - nonAdminBlockSection(member) - } + if groupInfo.membership.memberRole >= .admin { + adminDestructiveSection(member) + } else { + nonAdminBlockSection(member) + } - if developerTools { - Section(header: Text("For console").foregroundColor(theme.colors.secondary)) { - infoRow("Local name", member.localDisplayName) - infoRow("Database ID", "\(member.groupMemberId)") - if let conn = member.activeConn { - let connLevelDesc = conn.connLevel == 0 ? NSLocalizedString("direct", comment: "connection level description") : String.localizedStringWithFormat(NSLocalizedString("indirect (%d)", comment: "connection level description"), conn.connLevel) - infoRow("Connection", connLevelDesc) - } - Button ("Debug delivery") { - Task { - do { - let info = queueInfoText(try await apiGroupMemberQueueInfo(groupInfo.apiId, member.groupMemberId)) - await MainActor.run { alert = .queueInfo(info: info) } - } catch let e { - logger.error("apiContactQueueInfo error: \(responseError(e))") - let a = getErrorAlert(e, "Error") - await MainActor.run { alert = .error(title: a.title, error: a.message) } + if developerTools { + Section(header: Text("For console").foregroundColor(theme.colors.secondary)) { + infoRow("Local name", member.localDisplayName) + infoRow("Database ID", "\(member.groupMemberId)") + if let conn = member.activeConn { + let connLevelDesc = conn.connLevel == 0 ? NSLocalizedString("direct", comment: "connection level description") : String.localizedStringWithFormat(NSLocalizedString("indirect (%d)", comment: "connection level description"), conn.connLevel) + infoRow("Connection", connLevelDesc) + } + Button ("Debug delivery") { + Task { + do { + let info = queueInfoText(try await apiGroupMemberQueueInfo(groupInfo.apiId, member.groupMemberId)) + await MainActor.run { alert = .queueInfo(info: info) } + } catch let e { + logger.error("apiContactQueueInfo error: \(responseError(e))") + let a = getErrorAlert(e, "Error") + await MainActor.run { alert = .error(title: a.title, error: a.message) } + } } } } } + } } .navigationBarHidden(true) - .onAppear { + .task { if #unavailable(iOS 16) { // this condition prevents re-setting picker if !justOpened { return } } justOpened = false - DispatchQueue.main.async { - newRole = member.memberRole - do { - let (_, stats) = try apiGroupMemberInfo(groupInfo.apiId, member.groupMemberId) - let (mem, code) = member.memberActive ? try apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId) : (member, nil) + newRole = member.memberRole + do { + let (_, stats) = try await apiGroupMemberInfo(groupInfo.apiId, member.groupMemberId) + let (mem, code) = member.memberActive ? try await apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId) : (member, nil) + await MainActor.run { _ = chatModel.upsertGroupMember(groupInfo, mem) connectionStats = stats connectionCode = code - } catch let error { - logger.error("apiGroupMemberInfo or apiGetGroupMemberCode error: \(responseError(error))") + connectionLoaded = true } + } catch let error { + await MainActor.run { + connectionLoaded = true + } + logger.error("apiGroupMemberInfo or apiGetGroupMemberCode error: \(responseError(error))") } } .onChange(of: newRole) { newRole in From 4020cb074f82bec619c65462fd9bd30254be54b8 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Thu, 10 Oct 2024 11:01:15 +0100 Subject: [PATCH 007/567] ios: export localizations --- .../bg.xcloc/Localized Contents/bg.xliff | 49 +++++++++++++++ .../cs.xcloc/Localized Contents/cs.xliff | 49 +++++++++++++++ .../de.xcloc/Localized Contents/de.xliff | 49 +++++++++++++++ .../en.xcloc/Localized Contents/en.xliff | 62 +++++++++++++++++++ .../es.xcloc/Localized Contents/es.xliff | 49 +++++++++++++++ .../fi.xcloc/Localized Contents/fi.xliff | 49 +++++++++++++++ .../fr.xcloc/Localized Contents/fr.xliff | 49 +++++++++++++++ .../hu.xcloc/Localized Contents/hu.xliff | 49 +++++++++++++++ .../it.xcloc/Localized Contents/it.xliff | 49 +++++++++++++++ .../ja.xcloc/Localized Contents/ja.xliff | 49 +++++++++++++++ .../nl.xcloc/Localized Contents/nl.xliff | 49 +++++++++++++++ .../pl.xcloc/Localized Contents/pl.xliff | 49 +++++++++++++++ .../ru.xcloc/Localized Contents/ru.xliff | 49 +++++++++++++++ .../th.xcloc/Localized Contents/th.xliff | 49 +++++++++++++++ .../tr.xcloc/Localized Contents/tr.xliff | 49 +++++++++++++++ .../uk.xcloc/Localized Contents/uk.xliff | 49 +++++++++++++++ .../Localized Contents/zh-Hans.xliff | 49 +++++++++++++++ 17 files changed, 846 insertions(+) diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index ac1f6a3767..1a40820dce 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -1059,11 +1059,19 @@ Лош хеш на съобщението No comment provided by engineer. + + Better calls + No comment provided by engineer. + Better groups По-добри групи No comment provided by engineer. + + Better message dates. + No comment provided by engineer. + Better messages По-добри съобщения @@ -1073,6 +1081,18 @@ Better networking No comment provided by engineer. + + Better notifications + No comment provided by engineer. + + + Better security ✅ + No comment provided by engineer. + + + Better user experience + No comment provided by engineer. + Black No comment provided by engineer. @@ -1853,6 +1873,10 @@ This is your own one-time link! Персонализирано време No comment provided by engineer. + + Customizable message shape. + No comment provided by engineer. + Customize theme No comment provided by engineer. @@ -2142,6 +2166,10 @@ This is your own one-time link! Изтрий старата база данни? No comment provided by engineer. + + Delete or moderate up to 200 messages. + No comment provided by engineer. + Delete pending connection? Изтрий предстоящата връзка? @@ -3224,6 +3252,10 @@ This is your own one-time link! Forward messages without files? alert message + + Forward up to 20 messages at once. + No comment provided by engineer. + Forwarded Препратено @@ -3601,6 +3633,11 @@ Error: %2$@ Импортиране на архив No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + No comment provided by engineer. + Improved message delivery Подобрена доставка на съобщения @@ -6188,6 +6225,10 @@ Enable in *Network & servers* settings. Еднократна покана за SimpleX simplex link type + + SimpleX protocols reviewed by Trail of Bits. + No comment provided by engineer. + Simplified incognito mode Опростен режим инкогнито @@ -6352,6 +6393,14 @@ Enable in *Network & servers* settings. Подкрепете SimpleX Chat No comment provided by engineer. + + Switch audio and video during the call. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + No comment provided by engineer. + System Системен diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index 7a01af7338..af56b6631f 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -1028,10 +1028,18 @@ Špatný hash zprávy No comment provided by engineer. + + Better calls + No comment provided by engineer. + Better groups No comment provided by engineer. + + Better message dates. + No comment provided by engineer. + Better messages Lepší zprávy @@ -1041,6 +1049,18 @@ Better networking No comment provided by engineer. + + Better notifications + No comment provided by engineer. + + + Better security ✅ + No comment provided by engineer. + + + Better user experience + No comment provided by engineer. + Black No comment provided by engineer. @@ -1783,6 +1803,10 @@ This is your own one-time link! Vlastní čas No comment provided by engineer. + + Customizable message shape. + No comment provided by engineer. + Customize theme No comment provided by engineer. @@ -2069,6 +2093,10 @@ This is your own one-time link! Smazat starou databázi? No comment provided by engineer. + + Delete or moderate up to 200 messages. + No comment provided by engineer. + Delete pending connection? Smazat čekající připojení? @@ -3116,6 +3144,10 @@ This is your own one-time link! Forward messages without files? alert message + + Forward up to 20 messages at once. + No comment provided by engineer. + Forwarded No comment provided by engineer. @@ -3482,6 +3514,11 @@ Error: %2$@ Importing archive No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + No comment provided by engineer. + Improved message delivery No comment provided by engineer. @@ -5983,6 +6020,10 @@ Enable in *Network & servers* settings. Jednorázová pozvánka SimpleX simplex link type + + SimpleX protocols reviewed by Trail of Bits. + No comment provided by engineer. + Simplified incognito mode Zjednodušený inkognito režim @@ -6143,6 +6184,14 @@ Enable in *Network & servers* settings. Podpořte SimpleX Chat No comment provided by engineer. + + Switch audio and video during the call. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + No comment provided by engineer. + System Systém diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index faccbfc29c..b49e6a75a5 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -1084,11 +1084,19 @@ Ungültiger Nachrichten-Hash No comment provided by engineer. + + Better calls + No comment provided by engineer. + Better groups Bessere Gruppen No comment provided by engineer. + + Better message dates. + No comment provided by engineer. + Better messages Verbesserungen bei Nachrichten @@ -1099,6 +1107,18 @@ Kontrollieren Sie Ihr Netzwerk No comment provided by engineer. + + Better notifications + No comment provided by engineer. + + + Better security ✅ + No comment provided by engineer. + + + Better user experience + No comment provided by engineer. + Black Schwarz @@ -1919,6 +1939,10 @@ Das ist Ihr eigener Einmal-Link! Zeit anpassen No comment provided by engineer. + + Customizable message shape. + No comment provided by engineer. + Customize theme Design anpassen @@ -2213,6 +2237,10 @@ Das ist Ihr eigener Einmal-Link! Alte Datenbank löschen? No comment provided by engineer. + + Delete or moderate up to 200 messages. + No comment provided by engineer. + Delete pending connection? Ausstehende Verbindung löschen? @@ -3336,6 +3364,10 @@ Das ist Ihr eigener Einmal-Link! Nachrichten ohne Dateien weiterleiten? alert message + + Forward up to 20 messages at once. + No comment provided by engineer. + Forwarded Weitergeleitet @@ -3725,6 +3757,11 @@ Fehler: %2$@ Archiv wird importiert No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + No comment provided by engineer. + Improved message delivery Verbesserte Zustellung von Nachrichten @@ -6418,6 +6455,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. SimpleX-Einmal-Einladung simplex link type + + SimpleX protocols reviewed by Trail of Bits. + No comment provided by engineer. + Simplified incognito mode Vereinfachter Inkognito-Modus @@ -6593,6 +6634,14 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Unterstützung von SimpleX Chat No comment provided by engineer. + + Switch audio and video during the call. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + No comment provided by engineer. + System System diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 73b6fad88b..321b430a3f 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -1085,11 +1085,21 @@ Bad message hash No comment provided by engineer. + + Better calls + Better calls + No comment provided by engineer. + Better groups Better groups No comment provided by engineer. + + Better message dates. + Better message dates. + No comment provided by engineer. + Better messages Better messages @@ -1100,6 +1110,21 @@ Better networking No comment provided by engineer. + + Better notifications + Better notifications + No comment provided by engineer. + + + Better security ✅ + Better security ✅ + No comment provided by engineer. + + + Better user experience + Better user experience + No comment provided by engineer. + Black Black @@ -1920,6 +1945,11 @@ This is your own one-time link! Custom time No comment provided by engineer. + + Customizable message shape. + Customizable message shape. + No comment provided by engineer. + Customize theme Customize theme @@ -2214,6 +2244,11 @@ This is your own one-time link! Delete old database? No comment provided by engineer. + + Delete or moderate up to 200 messages. + Delete or moderate up to 200 messages. + No comment provided by engineer. + Delete pending connection? Delete pending connection? @@ -3337,6 +3372,11 @@ This is your own one-time link! Forward messages without files? alert message + + Forward up to 20 messages at once. + Forward up to 20 messages at once. + No comment provided by engineer. + Forwarded Forwarded @@ -3726,6 +3766,13 @@ Error: %2$@ Importing archive No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + Improved delivery, reduced traffic usage. +More improvements are coming soon! + No comment provided by engineer. + Improved message delivery Improved message delivery @@ -6424,6 +6471,11 @@ Enable in *Network & servers* settings. SimpleX one-time invitation simplex link type + + SimpleX protocols reviewed by Trail of Bits. + SimpleX protocols reviewed by Trail of Bits. + No comment provided by engineer. + Simplified incognito mode Simplified incognito mode @@ -6599,6 +6651,16 @@ Enable in *Network & servers* settings. Support SimpleX Chat No comment provided by engineer. + + Switch audio and video during the call. + Switch audio and video during the call. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + Switch chat profile for 1-time invitations. + No comment provided by engineer. + System System diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 8f4832f6ab..82817d8136 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -1084,11 +1084,19 @@ Hash de mensaje incorrecto No comment provided by engineer. + + Better calls + No comment provided by engineer. + Better groups Grupos mejorados No comment provided by engineer. + + Better message dates. + No comment provided by engineer. + Better messages Mensajes mejorados @@ -1099,6 +1107,18 @@ Uso de red mejorado No comment provided by engineer. + + Better notifications + No comment provided by engineer. + + + Better security ✅ + No comment provided by engineer. + + + Better user experience + No comment provided by engineer. + Black Negro @@ -1919,6 +1939,10 @@ This is your own one-time link! Tiempo personalizado No comment provided by engineer. + + Customizable message shape. + No comment provided by engineer. + Customize theme Personalizar tema @@ -2213,6 +2237,10 @@ This is your own one-time link! ¿Eliminar base de datos antigua? No comment provided by engineer. + + Delete or moderate up to 200 messages. + No comment provided by engineer. + Delete pending connection? ¿Eliminar conexión pendiente? @@ -3336,6 +3364,10 @@ This is your own one-time link! ¿Reenviar mensajes sin los archivos? alert message + + Forward up to 20 messages at once. + No comment provided by engineer. + Forwarded Reenviado @@ -3725,6 +3757,11 @@ Error: %2$@ Importando archivo No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + No comment provided by engineer. + Improved message delivery Entrega de mensajes mejorada @@ -6418,6 +6455,10 @@ Actívalo en ajustes de *Servidores y Redes*. Invitación SimpleX de un uso simplex link type + + SimpleX protocols reviewed by Trail of Bits. + No comment provided by engineer. + Simplified incognito mode Modo incógnito simplificado @@ -6593,6 +6634,14 @@ Actívalo en ajustes de *Servidores y Redes*. Soporte SimpleX Chat No comment provided by engineer. + + Switch audio and video during the call. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + No comment provided by engineer. + System Sistema diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index 8c7e7a982a..4b384842b6 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -1022,10 +1022,18 @@ Virheellinen viestin tarkiste No comment provided by engineer. + + Better calls + No comment provided by engineer. + Better groups No comment provided by engineer. + + Better message dates. + No comment provided by engineer. + Better messages Parempia viestejä @@ -1035,6 +1043,18 @@ Better networking No comment provided by engineer. + + Better notifications + No comment provided by engineer. + + + Better security ✅ + No comment provided by engineer. + + + Better user experience + No comment provided by engineer. + Black No comment provided by engineer. @@ -1776,6 +1796,10 @@ This is your own one-time link! Mukautettu aika No comment provided by engineer. + + Customizable message shape. + No comment provided by engineer. + Customize theme No comment provided by engineer. @@ -2062,6 +2086,10 @@ This is your own one-time link! Poista vanha tietokanta? No comment provided by engineer. + + Delete or moderate up to 200 messages. + No comment provided by engineer. + Delete pending connection? Poistetaanko odottava yhteys? @@ -3106,6 +3134,10 @@ This is your own one-time link! Forward messages without files? alert message + + Forward up to 20 messages at once. + No comment provided by engineer. + Forwarded No comment provided by engineer. @@ -3472,6 +3504,11 @@ Error: %2$@ Importing archive No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + No comment provided by engineer. + Improved message delivery No comment provided by engineer. @@ -5970,6 +6007,10 @@ Enable in *Network & servers* settings. SimpleX-kertakutsu simplex link type + + SimpleX protocols reviewed by Trail of Bits. + No comment provided by engineer. + Simplified incognito mode No comment provided by engineer. @@ -6129,6 +6170,14 @@ Enable in *Network & servers* settings. SimpleX Chat tuki No comment provided by engineer. + + Switch audio and video during the call. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + No comment provided by engineer. + System Järjestelmä diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 77c08bd4f7..b672bebc8c 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -1077,11 +1077,19 @@ Mauvais hash de message No comment provided by engineer. + + Better calls + No comment provided by engineer. + Better groups Des groupes plus performants No comment provided by engineer. + + Better message dates. + No comment provided by engineer. + Better messages Meilleurs messages @@ -1092,6 +1100,18 @@ Meilleure gestion de réseau No comment provided by engineer. + + Better notifications + No comment provided by engineer. + + + Better security ✅ + No comment provided by engineer. + + + Better user experience + No comment provided by engineer. + Black Noir @@ -1910,6 +1930,10 @@ Il s'agit de votre propre lien unique ! Délai personnalisé No comment provided by engineer. + + Customizable message shape. + No comment provided by engineer. + Customize theme Personnaliser le thème @@ -2204,6 +2228,10 @@ Il s'agit de votre propre lien unique ! Supprimer l'ancienne base de données ? No comment provided by engineer. + + Delete or moderate up to 200 messages. + No comment provided by engineer. + Delete pending connection? Supprimer la connexion en attente ? @@ -3316,6 +3344,10 @@ Il s'agit de votre propre lien unique ! Forward messages without files? alert message + + Forward up to 20 messages at once. + No comment provided by engineer. + Forwarded Transféré @@ -3703,6 +3735,11 @@ Erreur : %2$@ Importation de l'archive No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + No comment provided by engineer. + Improved message delivery Amélioration de la transmission des messages @@ -6381,6 +6418,10 @@ Activez-le dans les paramètres *Réseau et serveurs*. Invitation unique SimpleX simplex link type + + SimpleX protocols reviewed by Trail of Bits. + No comment provided by engineer. + Simplified incognito mode Mode incognito simplifié @@ -6555,6 +6596,14 @@ Activez-le dans les paramètres *Réseau et serveurs*. Supporter SimpleX Chat No comment provided by engineer. + + Switch audio and video during the call. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + No comment provided by engineer. + System Système diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index eff07e079c..82df5fb87f 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -1084,11 +1084,19 @@ Hibás az üzenet hasító értéke No comment provided by engineer. + + Better calls + No comment provided by engineer. + Better groups Javított csoportok No comment provided by engineer. + + Better message dates. + No comment provided by engineer. + Better messages Jobb üzenetek @@ -1099,6 +1107,18 @@ Jobb hálózatkezelés No comment provided by engineer. + + Better notifications + No comment provided by engineer. + + + Better security ✅ + No comment provided by engineer. + + + Better user experience + No comment provided by engineer. + Black Fekete @@ -1919,6 +1939,10 @@ Ez az Ön egyszer használható hivatkozása! Személyreszabott idő No comment provided by engineer. + + Customizable message shape. + No comment provided by engineer. + Customize theme Téma személyre szabása @@ -2213,6 +2237,10 @@ Ez az Ön egyszer használható hivatkozása! Régi adatbázis törlése? No comment provided by engineer. + + Delete or moderate up to 200 messages. + No comment provided by engineer. + Delete pending connection? Függőben lévő ismerőskérelem törlése? @@ -3336,6 +3364,10 @@ Ez az Ön egyszer használható hivatkozása! Üzenetek továbbítása fájlok nélkül? alert message + + Forward up to 20 messages at once. + No comment provided by engineer. + Forwarded Továbbított @@ -3725,6 +3757,11 @@ Hiba: %2$@ Archívum importálása No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + No comment provided by engineer. + Improved message delivery Továbbfejlesztett üzenetkézbesítés @@ -6418,6 +6455,10 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Egyszer használható SimpleX-meghívó simplex link type + + SimpleX protocols reviewed by Trail of Bits. + No comment provided by engineer. + Simplified incognito mode Egyszerűsített inkognító mód @@ -6593,6 +6634,14 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. SimpleX Chat támogatása No comment provided by engineer. + + Switch audio and video during the call. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + No comment provided by engineer. + System Rendszer diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index b993244ef0..99c83d04a4 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -1084,11 +1084,19 @@ Hash del messaggio errato No comment provided by engineer. + + Better calls + No comment provided by engineer. + Better groups Gruppi migliorati No comment provided by engineer. + + Better message dates. + No comment provided by engineer. + Better messages Messaggi migliorati @@ -1099,6 +1107,18 @@ Rete migliorata No comment provided by engineer. + + Better notifications + No comment provided by engineer. + + + Better security ✅ + No comment provided by engineer. + + + Better user experience + No comment provided by engineer. + Black Nero @@ -1919,6 +1939,10 @@ Questo è il tuo link una tantum! Tempo personalizzato No comment provided by engineer. + + Customizable message shape. + No comment provided by engineer. + Customize theme Personalizza il tema @@ -2213,6 +2237,10 @@ Questo è il tuo link una tantum! Eliminare il database vecchio? No comment provided by engineer. + + Delete or moderate up to 200 messages. + No comment provided by engineer. + Delete pending connection? Eliminare la connessione in attesa? @@ -3336,6 +3364,10 @@ Questo è il tuo link una tantum! Inoltrare i messaggi senza file? alert message + + Forward up to 20 messages at once. + No comment provided by engineer. + Forwarded Inoltrato @@ -3725,6 +3757,11 @@ Errore: %2$@ Importazione archivio No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + No comment provided by engineer. + Improved message delivery Consegna dei messaggi migliorata @@ -6418,6 +6455,10 @@ Attivalo nelle impostazioni *Rete e server*. Invito SimpleX una tantum simplex link type + + SimpleX protocols reviewed by Trail of Bits. + No comment provided by engineer. + Simplified incognito mode Modalità incognito semplificata @@ -6593,6 +6634,14 @@ Attivalo nelle impostazioni *Rete e server*. Supporta SimpleX Chat No comment provided by engineer. + + Switch audio and video during the call. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + No comment provided by engineer. + System Sistema diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index eb869ec182..eb8d43fb11 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -1045,10 +1045,18 @@ メッセージのハッシュ値問題 No comment provided by engineer. + + Better calls + No comment provided by engineer. + Better groups No comment provided by engineer. + + Better message dates. + No comment provided by engineer. + Better messages より良いメッセージ @@ -1058,6 +1066,18 @@ Better networking No comment provided by engineer. + + Better notifications + No comment provided by engineer. + + + Better security ✅ + No comment provided by engineer. + + + Better user experience + No comment provided by engineer. + Black No comment provided by engineer. @@ -1800,6 +1820,10 @@ This is your own one-time link! カスタム時間 No comment provided by engineer. + + Customizable message shape. + No comment provided by engineer. + Customize theme No comment provided by engineer. @@ -2086,6 +2110,10 @@ This is your own one-time link! 古いデータベースを削除しますか? No comment provided by engineer. + + Delete or moderate up to 200 messages. + No comment provided by engineer. + Delete pending connection? 接続待ちの接続を削除しますか? @@ -3131,6 +3159,10 @@ This is your own one-time link! Forward messages without files? alert message + + Forward up to 20 messages at once. + No comment provided by engineer. + Forwarded No comment provided by engineer. @@ -3497,6 +3529,11 @@ Error: %2$@ Importing archive No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + No comment provided by engineer. + Improved message delivery No comment provided by engineer. @@ -5988,6 +6025,10 @@ Enable in *Network & servers* settings. SimpleX使い捨て招待リンク simplex link type + + SimpleX protocols reviewed by Trail of Bits. + No comment provided by engineer. + Simplified incognito mode シークレットモードの簡素化 @@ -6148,6 +6189,14 @@ Enable in *Network & servers* settings. Simplex Chatを支援 No comment provided by engineer. + + Switch audio and video during the call. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + No comment provided by engineer. + System システム diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 3a9eb65ea3..fdb0615964 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -1084,11 +1084,19 @@ Onjuiste bericht hash No comment provided by engineer. + + Better calls + No comment provided by engineer. + Better groups Betere groepen No comment provided by engineer. + + Better message dates. + No comment provided by engineer. + Better messages Betere berichten @@ -1099,6 +1107,18 @@ Beter netwerk No comment provided by engineer. + + Better notifications + No comment provided by engineer. + + + Better security ✅ + No comment provided by engineer. + + + Better user experience + No comment provided by engineer. + Black Zwart @@ -1919,6 +1939,10 @@ Dit is uw eigen eenmalige link! Aangepaste tijd No comment provided by engineer. + + Customizable message shape. + No comment provided by engineer. + Customize theme Thema aanpassen @@ -2213,6 +2237,10 @@ Dit is uw eigen eenmalige link! Oude database verwijderen? No comment provided by engineer. + + Delete or moderate up to 200 messages. + No comment provided by engineer. + Delete pending connection? Wachtende verbinding verwijderen? @@ -3336,6 +3364,10 @@ Dit is uw eigen eenmalige link! Berichten doorsturen zonder bestanden? alert message + + Forward up to 20 messages at once. + No comment provided by engineer. + Forwarded Doorgestuurd @@ -3725,6 +3757,11 @@ Fout: %2$@ Archief importeren No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + No comment provided by engineer. + Improved message delivery Verbeterde berichtbezorging @@ -6418,6 +6455,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Eenmalige SimpleX uitnodiging simplex link type + + SimpleX protocols reviewed by Trail of Bits. + No comment provided by engineer. + Simplified incognito mode Vereenvoudigde incognitomodus @@ -6593,6 +6634,14 @@ Schakel dit in in *Netwerk en servers*-instellingen. Ondersteuning van SimpleX Chat No comment provided by engineer. + + Switch audio and video during the call. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + No comment provided by engineer. + System Systeem diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 3decaf241d..036f50dc17 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -1077,11 +1077,19 @@ Zły hash wiadomości No comment provided by engineer. + + Better calls + No comment provided by engineer. + Better groups Lepsze grupy No comment provided by engineer. + + Better message dates. + No comment provided by engineer. + Better messages Lepsze wiadomości @@ -1092,6 +1100,18 @@ Lepsze sieciowanie No comment provided by engineer. + + Better notifications + No comment provided by engineer. + + + Better security ✅ + No comment provided by engineer. + + + Better user experience + No comment provided by engineer. + Black Czarny @@ -1910,6 +1930,10 @@ To jest twój jednorazowy link! Niestandardowy czas No comment provided by engineer. + + Customizable message shape. + No comment provided by engineer. + Customize theme Dostosuj motyw @@ -2204,6 +2228,10 @@ To jest twój jednorazowy link! Usunąć starą bazę danych? No comment provided by engineer. + + Delete or moderate up to 200 messages. + No comment provided by engineer. + Delete pending connection? Usunąć oczekujące połączenie? @@ -3316,6 +3344,10 @@ To jest twój jednorazowy link! Forward messages without files? alert message + + Forward up to 20 messages at once. + No comment provided by engineer. + Forwarded Przekazane dalej @@ -3703,6 +3735,11 @@ Błąd: %2$@ Importowanie archiwum No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + No comment provided by engineer. + Improved message delivery Ulepszona dostawa wiadomości @@ -6381,6 +6418,10 @@ Włącz w ustawianiach *Sieć i serwery* . Zaproszenie jednorazowe SimpleX simplex link type + + SimpleX protocols reviewed by Trail of Bits. + No comment provided by engineer. + Simplified incognito mode Uproszczony tryb incognito @@ -6555,6 +6596,14 @@ Włącz w ustawianiach *Sieć i serwery* . Wspieraj SimpleX Chat No comment provided by engineer. + + Switch audio and video during the call. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + No comment provided by engineer. + System System diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index e457bf1c7c..a1163fb35d 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -1077,11 +1077,19 @@ Ошибка хэш сообщения No comment provided by engineer. + + Better calls + No comment provided by engineer. + Better groups Улучшенные группы No comment provided by engineer. + + Better message dates. + No comment provided by engineer. + Better messages Улучшенные сообщения @@ -1092,6 +1100,18 @@ Улучшенные сетевые функции No comment provided by engineer. + + Better notifications + No comment provided by engineer. + + + Better security ✅ + No comment provided by engineer. + + + Better user experience + No comment provided by engineer. + Black Черная @@ -1910,6 +1930,10 @@ This is your own one-time link! Пользовательское время No comment provided by engineer. + + Customizable message shape. + No comment provided by engineer. + Customize theme Настроить тему @@ -2204,6 +2228,10 @@ This is your own one-time link! Удалить предыдущую версию данных? No comment provided by engineer. + + Delete or moderate up to 200 messages. + No comment provided by engineer. + Delete pending connection? Удалить ожидаемое соединение? @@ -3316,6 +3344,10 @@ This is your own one-time link! Forward messages without files? alert message + + Forward up to 20 messages at once. + No comment provided by engineer. + Forwarded Переслано @@ -3703,6 +3735,11 @@ Error: %2$@ Импорт архива No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + No comment provided by engineer. + Improved message delivery Улучшенная доставка сообщений @@ -6381,6 +6418,10 @@ Enable in *Network & servers* settings. SimpleX одноразовая ссылка simplex link type + + SimpleX protocols reviewed by Trail of Bits. + No comment provided by engineer. + Simplified incognito mode Упрощенный режим Инкогнито @@ -6555,6 +6596,14 @@ Enable in *Network & servers* settings. Поддержать SimpleX Chat No comment provided by engineer. + + Switch audio and video during the call. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + No comment provided by engineer. + System Системная diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index 779063407f..37ade821f0 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -1014,10 +1014,18 @@ แฮชข้อความไม่ดี No comment provided by engineer. + + Better calls + No comment provided by engineer. + Better groups No comment provided by engineer. + + Better message dates. + No comment provided by engineer. + Better messages ข้อความที่ดีขึ้น @@ -1027,6 +1035,18 @@ Better networking No comment provided by engineer. + + Better notifications + No comment provided by engineer. + + + Better security ✅ + No comment provided by engineer. + + + Better user experience + No comment provided by engineer. + Black No comment provided by engineer. @@ -1765,6 +1785,10 @@ This is your own one-time link! เวลาที่กําหนดเอง No comment provided by engineer. + + Customizable message shape. + No comment provided by engineer. + Customize theme No comment provided by engineer. @@ -2051,6 +2075,10 @@ This is your own one-time link! ลบฐานข้อมูลเก่า? No comment provided by engineer. + + Delete or moderate up to 200 messages. + No comment provided by engineer. + Delete pending connection? ลบการเชื่อมต่อที่รอดำเนินการหรือไม่? @@ -3091,6 +3119,10 @@ This is your own one-time link! Forward messages without files? alert message + + Forward up to 20 messages at once. + No comment provided by engineer. + Forwarded No comment provided by engineer. @@ -3457,6 +3489,11 @@ Error: %2$@ Importing archive No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + No comment provided by engineer. + Improved message delivery No comment provided by engineer. @@ -5944,6 +5981,10 @@ Enable in *Network & servers* settings. คำเชิญ SimpleX แบบครั้งเดียว simplex link type + + SimpleX protocols reviewed by Trail of Bits. + No comment provided by engineer. + Simplified incognito mode No comment provided by engineer. @@ -6102,6 +6143,14 @@ Enable in *Network & servers* settings. สนับสนุน SimpleX แชท No comment provided by engineer. + + Switch audio and video during the call. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + No comment provided by engineer. + System ระบบ diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index 33f7c048dc..99f4ab0345 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -1061,11 +1061,19 @@ Kötü mesaj karması No comment provided by engineer. + + Better calls + No comment provided by engineer. + Better groups Daha iyi gruplar No comment provided by engineer. + + Better message dates. + No comment provided by engineer. + Better messages Daha iyi mesajlar @@ -1075,6 +1083,18 @@ Better networking No comment provided by engineer. + + Better notifications + No comment provided by engineer. + + + Better security ✅ + No comment provided by engineer. + + + Better user experience + No comment provided by engineer. + Black No comment provided by engineer. @@ -1857,6 +1877,10 @@ Bu senin kendi tek kullanımlık bağlantın! Özel saat No comment provided by engineer. + + Customizable message shape. + No comment provided by engineer. + Customize theme No comment provided by engineer. @@ -2147,6 +2171,10 @@ Bu senin kendi tek kullanımlık bağlantın! Eski veritabanı silinsin mi? No comment provided by engineer. + + Delete or moderate up to 200 messages. + No comment provided by engineer. + Delete pending connection? Bekleyen bağlantı silinsin mi? @@ -3233,6 +3261,10 @@ Bu senin kendi tek kullanımlık bağlantın! Forward messages without files? alert message + + Forward up to 20 messages at once. + No comment provided by engineer. + Forwarded İletildi @@ -3614,6 +3646,11 @@ Hata: %2$@ Arşiv içe aktarılıyor No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + No comment provided by engineer. + Improved message delivery İyileştirilmiş mesaj iletimi @@ -6217,6 +6254,10 @@ Enable in *Network & servers* settings. SimpleX tek kullanımlık davet simplex link type + + SimpleX protocols reviewed by Trail of Bits. + No comment provided by engineer. + Simplified incognito mode Basitleştirilmiş gizli mod @@ -6381,6 +6422,14 @@ Enable in *Network & servers* settings. SimpleX Chat'e destek ol No comment provided by engineer. + + Switch audio and video during the call. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + No comment provided by engineer. + System Sistem diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 8b1b7ee053..ce37b43c23 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -1077,11 +1077,19 @@ Поганий хеш повідомлення No comment provided by engineer. + + Better calls + No comment provided by engineer. + Better groups Кращі групи No comment provided by engineer. + + Better message dates. + No comment provided by engineer. + Better messages Кращі повідомлення @@ -1092,6 +1100,18 @@ Краща мережа No comment provided by engineer. + + Better notifications + No comment provided by engineer. + + + Better security ✅ + No comment provided by engineer. + + + Better user experience + No comment provided by engineer. + Black Чорний @@ -1910,6 +1930,10 @@ This is your own one-time link! Індивідуальний час No comment provided by engineer. + + Customizable message shape. + No comment provided by engineer. + Customize theme Налаштувати тему @@ -2204,6 +2228,10 @@ This is your own one-time link! Видалити стару базу даних? No comment provided by engineer. + + Delete or moderate up to 200 messages. + No comment provided by engineer. + Delete pending connection? Видалити очікуване з'єднання? @@ -3316,6 +3344,10 @@ This is your own one-time link! Forward messages without files? alert message + + Forward up to 20 messages at once. + No comment provided by engineer. + Forwarded Переслано @@ -3703,6 +3735,11 @@ Error: %2$@ Імпорт архіву No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + No comment provided by engineer. + Improved message delivery Покращена доставка повідомлень @@ -6381,6 +6418,10 @@ Enable in *Network & servers* settings. Одноразове запрошення SimpleX simplex link type + + SimpleX protocols reviewed by Trail of Bits. + No comment provided by engineer. + Simplified incognito mode Спрощений режим інкогніто @@ -6555,6 +6596,14 @@ Enable in *Network & servers* settings. Підтримка чату SimpleX No comment provided by engineer. + + Switch audio and video during the call. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + No comment provided by engineer. + System Система diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index 58f1f4fe0e..91893dd939 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -1077,11 +1077,19 @@ 错误消息散列 No comment provided by engineer. + + Better calls + No comment provided by engineer. + Better groups 更佳的群组 No comment provided by engineer. + + Better message dates. + No comment provided by engineer. + Better messages 更好的消息 @@ -1092,6 +1100,18 @@ 更好的网络 No comment provided by engineer. + + Better notifications + No comment provided by engineer. + + + Better security ✅ + No comment provided by engineer. + + + Better user experience + No comment provided by engineer. + Black 黑色 @@ -1910,6 +1930,10 @@ This is your own one-time link! 自定义时间 No comment provided by engineer. + + Customizable message shape. + No comment provided by engineer. + Customize theme 自定义主题 @@ -2204,6 +2228,10 @@ This is your own one-time link! 删除旧数据库吗? No comment provided by engineer. + + Delete or moderate up to 200 messages. + No comment provided by engineer. + Delete pending connection? 删除待定连接? @@ -3316,6 +3344,10 @@ This is your own one-time link! Forward messages without files? alert message + + Forward up to 20 messages at once. + No comment provided by engineer. + Forwarded 已转发 @@ -3703,6 +3735,11 @@ Error: %2$@ 正在导入存档 No comment provided by engineer. + + Improved delivery, reduced traffic usage. +More improvements are coming soon! + No comment provided by engineer. + Improved message delivery 改进了消息传递 @@ -6381,6 +6418,10 @@ Enable in *Network & servers* settings. SimpleX 一次性邀请 simplex link type + + SimpleX protocols reviewed by Trail of Bits. + No comment provided by engineer. + Simplified incognito mode 简化的隐身模式 @@ -6555,6 +6596,14 @@ Enable in *Network & servers* settings. 支持 SimpleX Chat No comment provided by engineer. + + Switch audio and video during the call. + No comment provided by engineer. + + + Switch chat profile for 1-time invitations. + No comment provided by engineer. + System 系统 From b1ef442f1e55801d1b155ef2dfbd4d92e0f214fc Mon Sep 17 00:00:00 2001 From: Diogo Date: Thu, 10 Oct 2024 11:02:05 +0100 Subject: [PATCH 008/567] android, desktop: fix profile switching failure handling (#5014) --- .../common/views/newchat/NewChatView.kt | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index 3ac8cdee64..8acddc2aa6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -321,17 +321,19 @@ fun ActiveProfilePicker( } } - controller.changeActiveUser_( - rhId = user.remoteHostId, - toUserId = user.userId, - viewPwd = if (user.hidden) searchTextOrPassword.value else null - ) - - if (chatModel.currentUser.value?.userId != user.userId) { - AlertManager.shared.showAlertMsg(generalGetString( - MR.strings.switching_profile_error_title), - String.format(generalGetString(MR.strings.switching_profile_error_message), user.chatViewName) + if ((contactConnection != null && updatedConn != null) || contactConnection == null) { + controller.changeActiveUser_( + rhId = user.remoteHostId, + toUserId = user.userId, + viewPwd = if (user.hidden) searchTextOrPassword.value else null ) + + if (chatModel.currentUser.value?.userId != user.userId) { + AlertManager.shared.showAlertMsg(generalGetString( + MR.strings.switching_profile_error_title), + String.format(generalGetString(MR.strings.switching_profile_error_message), user.chatViewName) + ) + } } if (updatedConn != null) { From 21b1904b0ef68ffdc145ca11c839d10ef6f7522b Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 10 Oct 2024 11:30:51 +0100 Subject: [PATCH 009/567] ui: translations (#5015) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (Italian) Currently translated at 100.0% (2079 of 2079 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (1831 of 1831 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2079 of 2079 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2079 of 2079 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1831 of 1831 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2079 of 2079 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Polish) Currently translated at 99.2% (2064 of 2079 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pl/ * Translated using Weblate (Turkish) Currently translated at 100.0% (1831 of 1831 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/tr/ * Translated using Weblate (Turkish) Currently translated at 100.0% (2079 of 2079 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/tr/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2079 of 2079 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1831 of 1831 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Polish) Currently translated at 99.7% (2083 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pl/ * Translated using Weblate (Hungarian) Currently translated at 99.5% (2079 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1831 of 1831 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Dutch) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (1831 of 1831 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/ * Translated using Weblate (Polish) Currently translated at 99.9% (2088 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pl/ * Translated using Weblate (Polish) Currently translated at 100.0% (1831 of 1831 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/pl/ * Translated using Weblate (Turkish) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/tr/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1831 of 1831 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Vietnamese) Currently translated at 43.0% (899 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Indonesian) Currently translated at 12.5% (263 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Vietnamese) Currently translated at 43.1% (901 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (German) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (German) Currently translated at 100.0% (1831 of 1831 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (Italian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1831 of 1831 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Arabic) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Greek) Currently translated at 11.6% (243 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/el/ * Translated using Weblate (Greek) Currently translated at 1.2% (22 of 1831 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/el/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1831 of 1831 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Russian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (Russian) Currently translated at 99.9% (1830 of 1831 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ru/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1831 of 1831 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * process localizations --------- Co-authored-by: Random Co-authored-by: 大王叫我来巡山 Co-authored-by: No name Co-authored-by: Bezruchenko Simon Co-authored-by: B.O.S.S Co-authored-by: Abdullah Koyuncu Co-authored-by: summoner001 Co-authored-by: M1K4 Co-authored-by: tuananh-ng <158744840+tuananh-ng@users.noreply.github.com> Co-authored-by: billy appetie Co-authored-by: Ghost of Sparta Co-authored-by: mlanp Co-authored-by: jonnysemon Co-authored-by: diodepon Co-authored-by: J R --- .../de.xcloc/Localized Contents/de.xliff | 8 + .../el.xcloc/Localized Contents/el.xliff | 2 +- .../es.xcloc/Localized Contents/es.xliff | 26 +- .../hu.xcloc/Localized Contents/hu.xliff | 108 +-- .../it.xcloc/Localized Contents/it.xliff | 8 + .../nl.xcloc/Localized Contents/nl.xliff | 9 + .../pl.xcloc/Localized Contents/pl.xliff | 54 ++ .../ru.xcloc/Localized Contents/ru.xliff | 53 ++ .../tr.xcloc/Localized Contents/tr.xliff | 316 ++++++- .../SimpleX SE/hu.lproj/Localizable.strings | 2 +- .../ios/SimpleX SE/tr.lproj/InfoPlist.strings | 14 +- .../SimpleX SE/tr.lproj/Localizable.strings | 116 ++- apps/ios/de.lproj/Localizable.strings | 24 + apps/ios/es.lproj/Localizable.strings | 42 +- apps/ios/hu.lproj/Localizable.strings | 122 +-- apps/ios/it.lproj/Localizable.strings | 24 + apps/ios/nl.lproj/Localizable.strings | 27 + apps/ios/pl.lproj/Localizable.strings | 156 ++++ apps/ios/ru.lproj/Localizable.strings | 153 ++++ apps/ios/tr.lproj/Localizable.strings | 807 +++++++++++++++++- .../commonMain/resources/MR/ar/strings.xml | 17 + .../commonMain/resources/MR/de/strings.xml | 17 + .../commonMain/resources/MR/el/strings.xml | 16 + .../commonMain/resources/MR/es/strings.xml | 43 +- .../commonMain/resources/MR/hu/strings.xml | 112 +-- .../commonMain/resources/MR/in/strings.xml | 1 + .../commonMain/resources/MR/it/strings.xml | 17 + .../commonMain/resources/MR/nl/strings.xml | 18 + .../commonMain/resources/MR/pl/strings.xml | 42 + .../commonMain/resources/MR/ru/strings.xml | 61 +- .../commonMain/resources/MR/tr/strings.xml | 111 ++- .../commonMain/resources/MR/uk/strings.xml | 7 + .../commonMain/resources/MR/vi/strings.xml | 20 + .../resources/MR/zh-rCN/strings.xml | 17 + 34 files changed, 2357 insertions(+), 213 deletions(-) diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index b49e6a75a5..23150926f2 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -947,6 +947,7 @@ App session + App-Sitzung No comment provided by engineer. @@ -4549,10 +4550,12 @@ Das ist Ihr Link für die Gruppe %@! New SOCKS credentials will be used every time you start the app. + Jedes Mal wenn Sie die App starten, werden neue SOCKS-Anmeldeinformationen genutzt No comment provided by engineer. New SOCKS credentials will be used for each server. + Für jeden Server werden neue SOCKS-Anmeldeinformationen genutzt No comment provided by engineer. @@ -4677,10 +4680,12 @@ Das ist Ihr Link für die Gruppe %@! No permission to record speech + Keine Genehmigung für Sprach-Aufnahmen No comment provided by engineer. No permission to record video + Keine Genehmigung für Video-Aufnahmen No comment provided by engineer. @@ -6153,6 +6158,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Server + Server No comment provided by engineer. @@ -7008,10 +7014,12 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt To record speech please grant permission to use Microphone. + Bitte erteilen Sie für Sprach-Aufnahmen die Genehmigung das Mikrofon zu nutzen. No comment provided by engineer. To record video please grant permission to use Camera. + Bitte erteilen Sie für Video-Aufnahmen die Genehmigung die Kamera zu nutzen. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff b/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff index b8432a33b6..799c61b448 100644 --- a/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff +++ b/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff @@ -4200,7 +4200,7 @@ SimpleX servers cannot see your profile. ## In reply to - ## Ως απαντηση σε + ## Ως απάντηση σε copied message info diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 82817d8136..6816181c21 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -947,6 +947,7 @@ App session + Sesión de aplicación No comment provided by engineer. @@ -1562,7 +1563,7 @@ Confirm that you remember database passphrase to migrate it. - Para migrar confirma que recuerdas la frase de contraseña de la base de datos. + Para migrar la base de datos confirma que recuerdas la frase de contraseña. No comment provided by engineer. @@ -4164,7 +4165,7 @@ This is your link for group %@! Local profile data only - Sólo datos del perfil local + Eliminar sólo el perfil No comment provided by engineer. @@ -4549,10 +4550,12 @@ This is your link for group %@! New SOCKS credentials will be used every time you start the app. + Se usarán credenciales SOCKS nuevas cada vez que inicies la aplicación. No comment provided by engineer. New SOCKS credentials will be used for each server. + Se usarán credenciales SOCKS nuevas por cada servidor. No comment provided by engineer. @@ -4677,10 +4680,12 @@ This is your link for group %@! No permission to record speech + Sin permiso para grabación de voz No comment provided by engineer. No permission to record video + Sin permiso para grabación de vídeo No comment provided by engineer. @@ -5134,7 +5139,7 @@ Error: %@ Possibly, certificate fingerprint in server address is incorrect - Posiblemente la huella digital del certificado en la dirección del servidor es incorrecta + Posiblemente la huella del certificado en la dirección del servidor es incorrecta server test error @@ -5204,7 +5209,7 @@ Error: %@ Profile and server connections - Datos del perfil y conexiones + Eliminar perfil y conexiones No comment provided by engineer. @@ -6153,6 +6158,7 @@ Actívalo en ajustes de *Servidores y Redes*. Server + Servidor No comment provided by engineer. @@ -6182,7 +6188,7 @@ Actívalo en ajustes de *Servidores y Redes*. Server test failed! - ¡Error en prueba del servidor! + ¡Prueba no superada! No comment provided by engineer. @@ -6729,7 +6735,7 @@ Actívalo en ajustes de *Servidores y Redes*. Test failed at step %@. - La prueba ha fallado en el paso %@. + Prueba no superada en el paso %@. server test failure @@ -6744,7 +6750,7 @@ Actívalo en ajustes de *Servidores y Redes*. Tests failed! - ¡Pruebas fallidas! + ¡Pruebas no superadas! No comment provided by engineer. @@ -7008,10 +7014,12 @@ Se te pedirá que completes la autenticación antes de activar esta función. To record speech please grant permission to use Microphone. + Para grabación de voz, por favor concede el permiso para usar el micrófono. No comment provided by engineer. To record video please grant permission to use Camera. + Para grabación de vídeo, por favor concede el permiso para usar la cámara. No comment provided by engineer. @@ -8774,7 +8782,7 @@ Repeat connection request? removed profile picture - imagen de perfil eliminada + ha eliminado la imagen del perfil profile update event chat item @@ -8838,7 +8846,7 @@ last received msg: %2$@ set new profile picture - nueva imagen de perfil + tiene nueva imagen del perfil profile update event chat item diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 82df5fb87f..61cf467bd0 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -354,27 +354,27 @@ **Add contact**: to create a new invitation link, or connect via a link you received. - **Ismerős hozzáadása**: új meghívó-hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz. + **Ismerős hozzáadása:** új meghívó-hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz. No comment provided by engineer. **Add new contact**: to create your one-time QR Code or link for your contact. - **Új ismerős hozzáadása**: egyszer használható QR-kód vagy hivatkozás létrehozása az ismerőse számára. + **Új ismerős hozzáadása:** egyszer használható QR-kód vagy hivatkozás létrehozása az ismerőse számára. No comment provided by engineer. **Create group**: to create a new group. - **Csoport létrehozása**: új csoport létrehozásához. + **Csoport létrehozása:** új csoport létrehozásához. No comment provided by engineer. **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. - **Privátabb**: 20 percenként ellenőrzi az új üzeneteket. Az eszköztoken megosztásra kerül a SimpleX Chat-kiszolgálóval, de az nem, hogy hány ismerőse vagy üzenete van. + **Privátabb:** 20 percenként ellenőrzi az új üzeneteket. Az eszköztoken megosztásra kerül a SimpleX Chat-kiszolgálóval, de az nem, hogy hány ismerőse vagy üzenete van. No comment provided by engineer. **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). - **Legprivátabb**: ne használja a SimpleX Chat értesítési kiszolgálót, rendszeresen ellenőrizze az üzeneteket a háttérben (attól függően, hogy milyen gyakran használja az alkalmazást). + **Legprivátabb:** ne használja a SimpleX Chat értesítési kiszolgálót, rendszeresen ellenőrizze az üzeneteket a háttérben (attól függően, hogy milyen gyakran használja az alkalmazást). No comment provided by engineer. @@ -389,7 +389,7 @@ **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. - **Javasolt**: az eszköztoken és az értesítések elküldésre kerülnek a SimpleX Chat értesítési kiszolgálóra, kivéve az üzenet tartalma, mérete vagy az, hogy kitől származik. + **Megjegyzés:** az eszköztoken és az értesítések elküldésre kerülnek a SimpleX Chat értesítési kiszolgálóra, kivéve az üzenet tartalma, mérete vagy az, hogy kitől származik. No comment provided by engineer. @@ -606,7 +606,7 @@ Accept incognito - Fogadás inkognítóban + Fogadás inkognitóban accept contact request via notification swipe action @@ -947,6 +947,7 @@ App session + Alkalmazás munkamenete No comment provided by engineer. @@ -1811,7 +1812,7 @@ Ez az Ön egyszer használható hivatkozása! Corner - Sarkos + Sarok No comment provided by engineer. @@ -2258,7 +2259,7 @@ Ez az Ön egyszer használható hivatkozása! Delete up to 20 messages at once. - Legfeljebb 20 üzenet törlése egyszerre. + Legfeljebb 20 üzenet egyszerre való törlése. No comment provided by engineer. @@ -2834,7 +2835,7 @@ Ez az Ön egyszer használható hivatkozása! Error changing to incognito! - Hiba az inkognitó-profilra való váltáskor! + Hiba az inkognitóprofilra való váltáskor! No comment provided by engineer. @@ -3069,7 +3070,7 @@ Ez az Ön egyszer használható hivatkozása! Error switching profile! - Hiba a profil váltásakor! + Hiba a profilváltásakor! alertTitle @@ -3104,7 +3105,7 @@ Ez az Ön egyszer használható hivatkozása! Error verifying passphrase: - Hiba a jelmondat ellenőrzésekor: + Hiba a jelmondat hitelesítésekor: No comment provided by engineer. @@ -3194,7 +3195,7 @@ Ez az Ön egyszer használható hivatkozása! Favorite - Csillag + Kedvenc swipe action @@ -3281,7 +3282,7 @@ Ez az Ön egyszer használható hivatkozása! Filter unread and favorite chats. - Olvasatlan és csillagozott csevegésekre való szűrés. + Olvasatlan és kedvenc csevegésekre való szűrés. No comment provided by engineer. @@ -3489,7 +3490,7 @@ Hiba: %2$@ Group image - Csoportkép + Csoport profilképe No comment provided by engineer. @@ -3609,12 +3610,12 @@ Hiba: %2$@ Hidden profile password - Rejtett profil jelszó + Rejtett profiljelszó No comment provided by engineer. Hide - Elrejt + Elrejtés chat item action @@ -3629,7 +3630,7 @@ Hiba: %2$@ Hide: - Elrejt: + Elrejtés: No comment provided by engineer. @@ -3799,7 +3800,7 @@ More improvements are coming soon! Incognito groups - Inkognitó csoportok + Inkognitócsoportok No comment provided by engineer. @@ -4074,12 +4075,12 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! KeyChain error - Kulcstartó hiba + Kulcstartóhiba No comment provided by engineer. Keychain error - Kulcstartó hiba + Kulcstartóhiba No comment provided by engineer. @@ -4549,10 +4550,12 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! New SOCKS credentials will be used every time you start the app. + Minden alkalommal, amikor elindítja az alkalmazást, új SOCKS-hitelesítő-adatokat fog használni. No comment provided by engineer. New SOCKS credentials will be used for each server. + Minden egyes kiszolgálóhoz új SOCKS-hitelesítő-adatok legyenek használva. No comment provided by engineer. @@ -4677,10 +4680,12 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! No permission to record speech + Nincs jogosultság megadva a beszéd rögzítéséhez No comment provided by engineer. No permission to record video + Nincs jogosultság megadva a videó rögzítéséhez No comment provided by engineer. @@ -4760,7 +4765,7 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! Onion hosts will be **required** for connection. Requires compatible VPN. - Az onion-kiszolgálók **szükségesek** a kapcsolódáshoz. + Onion-kiszolgálók **szükségesek** a kapcsolódáshoz. Kompatibilis VPN szükséges. No comment provided by engineer. @@ -5179,12 +5184,12 @@ Hiba: %@ Private message routing - Privát üzenet útválasztás + Privát üzenet-útválasztás No comment provided by engineer. Private message routing 🚀 - Privát üzenet útválasztás 🚀 + Privát üzenet-útválasztás 🚀 No comment provided by engineer. @@ -5199,7 +5204,7 @@ Hiba: %@ Private routing error - Privát útválasztási hiba + Privát útválasztáshiba No comment provided by engineer. @@ -6153,6 +6158,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Server + Kiszolgáló No comment provided by engineer. @@ -6461,7 +6467,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Simplified incognito mode - Egyszerűsített inkognító mód + Egyszerűsített inkognitómód No comment provided by engineer. @@ -6679,7 +6685,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Tail - Nyúlványos + Farok No comment provided by engineer. @@ -6714,7 +6720,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Tap to paste link - Koppintson a hivatkozás beillesztéséhez + Koppintson ide a hivatkozás beillesztéséhez No comment provided by engineer. @@ -7008,10 +7014,12 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll To record speech please grant permission to use Microphone. + A beszéd rögzítéséhez adjon engedélyt a Mikrofon használatára. No comment provided by engineer. To record video please grant permission to use Camera. + A videó rögzítéséhez adjon engedélyt a Kamera használatára. No comment provided by engineer. @@ -7031,7 +7039,7 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll To verify end-to-end encryption with your contact compare (or scan) the code on your devices. - A végpontok közötti titkosítás ellenőrzéséhez hasonlítsa össze (vagy olvassa be a QR-kódot) az ismerőse eszközén lévő kóddal. + A végpontok közötti titkosítás hitelesítéséhez hasonlítsa össze (vagy olvassa be a QR-kódot) az ismerőse eszközén lévő kóddal. No comment provided by engineer. @@ -7041,7 +7049,7 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll Toggle incognito when connecting. - Inkognitómód kapcsolódáskor. + Inkognitómód használata kapcsolódáskor. No comment provided by engineer. @@ -7126,7 +7134,7 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll Unfav. - Csillagozás megszüntetése + Kedvenc megszüntetése swipe action @@ -7318,7 +7326,7 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc Use new incognito profile - Az új inkognító profil használata + Új inkognitóprofil használata No comment provided by engineer. @@ -7328,7 +7336,7 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc Use private routing with unknown servers when IP address is not protected. - Privát útválasztás használata ismeretlen kiszolgálókkal, ha az IP-cím nem védett. + Használjon privát útválasztást ismeretlen kiszolgálókkal, ha az IP-cím nem védett. No comment provided by engineer. @@ -7368,37 +7376,37 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc Verify code with desktop - Kód ellenőrzése a számítógépen + Kód hitelesítése a számítógépen No comment provided by engineer. Verify connection - Kapcsolat ellenőrzése + Kapcsolat hitelesítése No comment provided by engineer. Verify connection security - Kapcsolat biztonságának ellenőrzése + Biztonságos kapcsolat hitelesítése No comment provided by engineer. Verify connections - Kapcsolatok ellenőrzése + Kapcsolatok hitelesítése No comment provided by engineer. Verify database passphrase - Az adatbázis jelmondatának ellenőrzése + Az adatbázis jelmondatának hitelesítése No comment provided by engineer. Verify passphrase - Jelmondat ellenőrzése + Jelmondat hitelesítése No comment provided by engineer. Verify security code - Biztonsági kód ellenőrzése + Biztonsági kód hitelesítése No comment provided by engineer. @@ -7553,7 +7561,7 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc When you share an incognito profile with somebody, this profile will be used for the groups they invite you to. - Inkognitó-profil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott. + Inkognitóprofil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott. No comment provided by engineer. @@ -7715,7 +7723,7 @@ Csatlakozáskérés megismétlése? You can enable later via Settings - Később engedélyezheti a Beállításokban + Később engedélyezheti a „Beállításokban” No comment provided by engineer. @@ -7922,12 +7930,12 @@ Kapcsolatkérés megismétlése? You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile - Egy olyan ismerőst próbál meghívni, akivel inkognító profilt osztott meg abban a csoportban, amelyben saját fő profilja van használatban + Egy olyan ismerősét próbálja meghívni, akivel inkognitóprofilt osztott meg abban a csoportban, amelyben a saját fő profilja van használatban No comment provided by engineer. You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed - Inkognító profilt használ ehhez a csoporthoz - fő profilja megosztásának elkerülése érdekében a meghívók küldése le van tiltva + Inkognitóprofilt használ ehhez a csoporthoz - fő profilja megosztásának elkerülése érdekében a meghívók küldése le van tiltva No comment provided by engineer. @@ -8222,7 +8230,7 @@ Kapcsolatkérés megismétlése? changed your role to %@ - megváltoztatta a szerepkörét erre: %@ + megváltoztatta az Ön szerepkörét erre: %@ rcv group event chat item @@ -8522,7 +8530,7 @@ Kapcsolatkérés megismétlése? incognito via contact address link - inkognitó a kapcsolattartási hivatkozáson keresztül + inkognitó a kapcsolattartási címhivatkozáson keresztül chat list item description @@ -9003,12 +9011,12 @@ utoljára fogadott üzenet: %2$@ you changed role for yourself to %@ - saját szerepkör megváltoztatva erre: %@ + saját szerepköre megváltozott erre: %@ snd group event chat item you changed role of %1$@ to %2$@ - %1$@ szerepkörét megváltoztatta erre: %@ + Ön megváltoztatta %1$@ szerepkörét erre: %@ snd group event chat item @@ -9018,7 +9026,7 @@ utoljára fogadott üzenet: %2$@ you removed %@ - eltávolította őt: %@ + Ön eltávolította őt: %@ snd group event chat item @@ -9231,7 +9239,7 @@ utoljára fogadott üzenet: %2$@ Keychain error - Kulcstartó hiba + Kulcstartóhiba No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 99c83d04a4..46c2a63aff 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -947,6 +947,7 @@ App session + Sessione dell'app No comment provided by engineer. @@ -4549,10 +4550,12 @@ Questo è il tuo link per il gruppo %@! New SOCKS credentials will be used every time you start the app. + Le nuove credenziali SOCKS verranno usate ogni volta che avvii l'app. No comment provided by engineer. New SOCKS credentials will be used for each server. + Le nuove credenziali SOCKS verranno usate per ogni server. No comment provided by engineer. @@ -4677,10 +4680,12 @@ Questo è il tuo link per il gruppo %@! No permission to record speech + Nessuna autorizzazione per registrare l'audio No comment provided by engineer. No permission to record video + Nessuna autorizzazione per registrare il video No comment provided by engineer. @@ -6153,6 +6158,7 @@ Attivalo nelle impostazioni *Rete e server*. Server + Server No comment provided by engineer. @@ -7008,10 +7014,12 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio To record speech please grant permission to use Microphone. + Per registrare l'audio, concedi l'autorizzazione di usare il microfono. No comment provided by engineer. To record video please grant permission to use Camera. + Per registrare il video, concedi l'autorizzazione di usare la fotocamera. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index fdb0615964..47030eb058 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -947,6 +947,7 @@ App session + Appsessie No comment provided by engineer. @@ -4549,10 +4550,12 @@ Dit is jouw link voor groep %@! New SOCKS credentials will be used every time you start the app. + Elke keer dat u de app start, worden er nieuwe SOCKS-inloggegevens gebruikt. No comment provided by engineer. New SOCKS credentials will be used for each server. + Voor elke server worden nieuwe SOCKS-inloggegevens gebruikt. No comment provided by engineer. @@ -4677,10 +4680,12 @@ Dit is jouw link voor groep %@! No permission to record speech + Geen toestemming om spraak op te nemen No comment provided by engineer. No permission to record video + Geen toestemming om video op te nemen No comment provided by engineer. @@ -6153,6 +6158,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Server + Server No comment provided by engineer. @@ -6679,6 +6685,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Tail + Staart No comment provided by engineer. @@ -7007,10 +7014,12 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc To record speech please grant permission to use Microphone. + Geef toestemming om de microfoon te gebruiken om spraak op te nemen. No comment provided by engineer. To record video please grant permission to use Camera. + Om video op te nemen, dient u toestemming te geven om de camera te gebruiken. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 036f50dc17..ee3ef5b12e 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -139,6 +139,7 @@ %1$@, %2$@ + %1$@, %2$@ format for date separator in chat @@ -163,18 +164,22 @@ %d file(s) are still being downloaded. + %d plik(ów) jest dalej pobieranych. forward confirmation reason %d file(s) failed to download. + %d plik(ów) nie udało się pobrać. forward confirmation reason %d file(s) were deleted. + %d plik(ów) zostało usuniętych. forward confirmation reason %d file(s) were not downloaded. + %d plik(ów) nie zostało pobranych. forward confirmation reason @@ -184,6 +189,7 @@ %d messages not forwarded + %d wiadomości nie przekazanych alert title @@ -941,6 +947,7 @@ App session + Sesja aplikacji No comment provided by engineer. @@ -1050,6 +1057,7 @@ Auto-accept settings + Ustawienia automatycznej akceptacji alert title @@ -1395,6 +1403,7 @@ Chat preferences were changed. + Preferencje czatu zostały zmienione. alert message @@ -1803,6 +1812,7 @@ To jest twój jednorazowy link! Corner + Róg No comment provided by engineer. @@ -2484,6 +2494,7 @@ To jest twój jednorazowy link! Do not use credentials with proxy. + Nie używaj danych logowania do proxy. No comment provided by engineer. @@ -2529,6 +2540,7 @@ To jest twój jednorazowy link! Download files + Pobierz pliki alert action @@ -2808,6 +2820,7 @@ To jest twój jednorazowy link! Error changing connection profile + Błąd zmiany połączenia profilu No comment provided by engineer. @@ -2822,6 +2835,7 @@ To jest twój jednorazowy link! Error changing to incognito! + Błąd zmiany na incognito! No comment provided by engineer. @@ -2946,6 +2960,7 @@ To jest twój jednorazowy link! Error migrating settings + Błąd migracji ustawień No comment provided by engineer. @@ -3050,6 +3065,7 @@ To jest twój jednorazowy link! Error switching profile + Błąd zmiany profilu No comment provided by engineer. @@ -3190,6 +3206,8 @@ To jest twój jednorazowy link! File errors: %@ + Błędy pliku: +%@ alert message @@ -3329,6 +3347,7 @@ To jest twój jednorazowy link! Forward %d message(s)? + Przekazać %d wiadomość(i)? alert title @@ -3338,10 +3357,12 @@ To jest twój jednorazowy link! Forward messages + Przekaż wiadomości alert action Forward messages without files? + Przekazać wiadomości bez plików? alert message @@ -3360,6 +3381,7 @@ To jest twój jednorazowy link! Forwarding %lld messages + Przekazywanie %lld wiadomości No comment provided by engineer. @@ -3658,6 +3680,7 @@ Błąd: %2$@ IP address + Adres IP No comment provided by engineer. @@ -4312,6 +4335,7 @@ To jest twój link do grupy %@! Message shape + Kształt wiadomości No comment provided by engineer. @@ -4366,6 +4390,7 @@ To jest twój link do grupy %@! Messages were deleted after you selected them. + Wiadomości zostały usunięte po wybraniu ich. alert message @@ -4525,10 +4550,12 @@ To jest twój link do grupy %@! New SOCKS credentials will be used every time you start the app. + Nowe poświadczenia SOCKS będą używane przy każdym uruchomieniu aplikacji. No comment provided by engineer. New SOCKS credentials will be used for each server. + Dla każdego serwera zostaną użyte nowe poświadczenia SOCKS. No comment provided by engineer. @@ -4653,10 +4680,12 @@ To jest twój link do grupy %@! No permission to record speech + Brak zezwoleń do nagrania rozmowy No comment provided by engineer. No permission to record video + Brak zezwoleń do nagrania wideo No comment provided by engineer. @@ -4681,6 +4710,7 @@ To jest twój link do grupy %@! Nothing to forward! + Nic do przekazania! alert title @@ -4909,6 +4939,8 @@ Wymaga włączenia VPN. Other file errors: %@ + Inne błędy pliku: +%@ alert message @@ -4948,6 +4980,7 @@ Wymaga włączenia VPN. Password + Hasło No comment provided by engineer. @@ -5101,6 +5134,7 @@ Błąd: %@ Port + Port No comment provided by engineer. @@ -5292,6 +5326,7 @@ Włącz w ustawianiach *Sieć i serwery* . Proxy requires password + Proxy wymaga hasła No comment provided by engineer. @@ -5517,6 +5552,7 @@ Włącz w ustawianiach *Sieć i serwery* . Remove archive? + Usunąć archiwum? No comment provided by engineer. @@ -5701,6 +5737,7 @@ Włącz w ustawianiach *Sieć i serwery* . SOCKS proxy + Proxy SOCKS No comment provided by engineer. @@ -5791,6 +5828,7 @@ Włącz w ustawianiach *Sieć i serwery* . Save your profile? + Zapisać Twój profil? alert title @@ -5815,6 +5853,7 @@ Włącz w ustawianiach *Sieć i serwery* . Saving %lld messages + Zapisywanie %lld wiadomości No comment provided by engineer. @@ -5899,6 +5938,7 @@ Włącz w ustawianiach *Sieć i serwery* . Select chat profile + Wybierz profil czatu No comment provided by engineer. @@ -6118,6 +6158,7 @@ Włącz w ustawianiach *Sieć i serwery* . Server + Serwer No comment provided by engineer. @@ -6242,6 +6283,7 @@ Włącz w ustawianiach *Sieć i serwery* . Settings were changed. + Ustawienia zostały zmienione. alert message @@ -6281,6 +6323,7 @@ Włącz w ustawianiach *Sieć i serwery* . Share profile + Udostępnij profil No comment provided by engineer. @@ -6454,6 +6497,7 @@ Włącz w ustawianiach *Sieć i serwery* . Some app settings were not migrated. + Niektóre ustawienia aplikacji nie zostały zmigrowane. No comment provided by engineer. @@ -6641,6 +6685,7 @@ Włącz w ustawianiach *Sieć i serwery* . Tail + Ogon No comment provided by engineer. @@ -6837,6 +6882,7 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom The uploaded database archive will be permanently removed from the servers. + Przesłane archiwum bazy danych zostanie trwale usunięte z serwerów. No comment provided by engineer. @@ -6968,10 +7014,12 @@ Przed włączeniem tej funkcji zostanie wyświetlony monit uwierzytelniania. To record speech please grant permission to use Microphone. + Aby nagrać rozmowę, proszę zezwolić na użycie Mikrofonu. No comment provided by engineer. To record video please grant permission to use Camera. + Aby nagrać wideo, proszę zezwolić na użycie Aparatu. No comment provided by engineer. @@ -7243,6 +7291,7 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Use SOCKS proxy + Użyj proxy SOCKS No comment provided by engineer. @@ -7317,6 +7366,7 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Username + Nazwa użytkownika No comment provided by engineer. @@ -7930,6 +7980,7 @@ Powtórzyć prośbę połączenia? Your chat preferences + Twoje preferencje czatu alert title @@ -7939,6 +7990,7 @@ Powtórzyć prośbę połączenia? Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Twoje połączenie zostało przeniesione do %@, ale podczas przekierowywania do profilu wystąpił nieoczekiwany błąd. No comment provided by engineer. @@ -7958,6 +8010,7 @@ Powtórzyć prośbę połączenia? Your credentials may be sent unencrypted. + Twoje poświadczenia mogą zostać wysłane niezaszyfrowane. No comment provided by engineer. @@ -7997,6 +8050,7 @@ Powtórzyć prośbę połączenia? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + Twój profil został zmieniony. Jeśli go zapiszesz, zaktualizowany profil zostanie wysłany do wszystkich kontaktów. alert message diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index a1163fb35d..c7f5cb26ed 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -163,18 +163,22 @@ %d file(s) are still being downloaded. + %d файл(ов) загружаются. forward confirmation reason %d file(s) failed to download. + %d файл(ов) не удалось загрузить. forward confirmation reason %d file(s) were deleted. + %d файлов было удалено. forward confirmation reason %d file(s) were not downloaded. + %d файлов не было загружено. forward confirmation reason @@ -184,6 +188,7 @@ %d messages not forwarded + %d сообщений не переслано alert title @@ -941,6 +946,7 @@ App session + Сессия приложения No comment provided by engineer. @@ -1050,6 +1056,7 @@ Auto-accept settings + Настройки автоприема alert title @@ -1395,6 +1402,7 @@ Chat preferences were changed. + Настройки чата были изменены. alert message @@ -1803,6 +1811,7 @@ This is your own one-time link! Corner + Угол No comment provided by engineer. @@ -2484,6 +2493,7 @@ This is your own one-time link! Do not use credentials with proxy. + Не использовать учетные данные с прокси. No comment provided by engineer. @@ -2529,6 +2539,7 @@ This is your own one-time link! Download files + Загрузить файлы alert action @@ -2808,6 +2819,7 @@ This is your own one-time link! Error changing connection profile + Ошибка при изменении профиля соединения No comment provided by engineer. @@ -2822,6 +2834,7 @@ This is your own one-time link! Error changing to incognito! + Ошибка при смене на Инкогнито! No comment provided by engineer. @@ -2946,6 +2959,7 @@ This is your own one-time link! Error migrating settings + Ошибка миграции настроек No comment provided by engineer. @@ -3050,6 +3064,7 @@ This is your own one-time link! Error switching profile + Ошибка переключения профиля No comment provided by engineer. @@ -3190,6 +3205,8 @@ This is your own one-time link! File errors: %@ + Ошибки файлов: +%@ alert message @@ -3329,6 +3346,7 @@ This is your own one-time link! Forward %d message(s)? + Переслать %d сообщение(й)? alert title @@ -3338,10 +3356,12 @@ This is your own one-time link! Forward messages + Переслать сообщения alert action Forward messages without files? + Переслать сообщения без файлов? alert message @@ -3360,6 +3380,7 @@ This is your own one-time link! Forwarding %lld messages + Пересылка %lld сообщений No comment provided by engineer. @@ -3658,6 +3679,7 @@ Error: %2$@ IP address + IP адрес No comment provided by engineer. @@ -4312,6 +4334,7 @@ This is your link for group %@! Message shape + Форма сообщений No comment provided by engineer. @@ -4366,6 +4389,7 @@ This is your link for group %@! Messages were deleted after you selected them. + Сообщения были удалены после того, как вы их выбрали. alert message @@ -4525,10 +4549,12 @@ This is your link for group %@! New SOCKS credentials will be used every time you start the app. + Новые учетные данные SOCKS будут использоваться при каждом запуске приложения. No comment provided by engineer. New SOCKS credentials will be used for each server. + Новые учетные данные SOCKS будут использоваться для каждого сервера. No comment provided by engineer. @@ -4653,10 +4679,12 @@ This is your link for group %@! No permission to record speech + Нет разрешения на запись речи No comment provided by engineer. No permission to record video + Нет разрешения на запись видео No comment provided by engineer. @@ -4681,6 +4709,7 @@ This is your link for group %@! Nothing to forward! + Нет сообщений, которые можно переслать! alert title @@ -4909,6 +4938,8 @@ Requires compatible VPN. Other file errors: %@ + Другие ошибки файлов: +%@ alert message @@ -4948,6 +4979,7 @@ Requires compatible VPN. Password + Пароль No comment provided by engineer. @@ -5101,6 +5133,7 @@ Error: %@ Port + Порт No comment provided by engineer. @@ -5292,6 +5325,7 @@ Enable in *Network & servers* settings. Proxy requires password + Прокси требует пароль No comment provided by engineer. @@ -5517,6 +5551,7 @@ Enable in *Network & servers* settings. Remove archive? + Удалить архив? No comment provided by engineer. @@ -5701,6 +5736,7 @@ Enable in *Network & servers* settings. SOCKS proxy + SOCKS прокси No comment provided by engineer. @@ -5791,6 +5827,7 @@ Enable in *Network & servers* settings. Save your profile? + Сохранить ваш профиль? alert title @@ -5815,6 +5852,7 @@ Enable in *Network & servers* settings. Saving %lld messages + Сохранение %lld сообщений No comment provided by engineer. @@ -5899,6 +5937,7 @@ Enable in *Network & servers* settings. Select chat profile + Выберите профиль чата No comment provided by engineer. @@ -6118,6 +6157,7 @@ Enable in *Network & servers* settings. Server + Сервер No comment provided by engineer. @@ -6242,6 +6282,7 @@ Enable in *Network & servers* settings. Settings were changed. + Настройки были изменены. alert message @@ -6281,6 +6322,7 @@ Enable in *Network & servers* settings. Share profile + Поделиться профилем No comment provided by engineer. @@ -6454,6 +6496,7 @@ Enable in *Network & servers* settings. Some app settings were not migrated. + Некоторые настройки приложения не были перенесены. No comment provided by engineer. @@ -6641,6 +6684,7 @@ Enable in *Network & servers* settings. Tail + Хвост No comment provided by engineer. @@ -6837,6 +6881,7 @@ It can happen because of some bug or when the connection is compromised. The uploaded database archive will be permanently removed from the servers. + Загруженный архив базы данных будет навсегда удален с серверов. No comment provided by engineer. @@ -6968,10 +7013,12 @@ You will be prompted to complete authentication before this feature is enabled.< To record speech please grant permission to use Microphone. + Для записи речи, пожалуйста, дайте разрешение на использование микрофона. No comment provided by engineer. To record video please grant permission to use Camera. + Для записи видео, пожалуйста, дайте разрешение на использование камеры. No comment provided by engineer. @@ -7243,6 +7290,7 @@ To connect, please ask your contact to create another connection link and check Use SOCKS proxy + Использовать SOCKS прокси No comment provided by engineer. @@ -7317,6 +7365,7 @@ To connect, please ask your contact to create another connection link and check Username + Имя пользователя No comment provided by engineer. @@ -7930,6 +7979,7 @@ Repeat connection request? Your chat preferences + Ваши настройки чата alert title @@ -7939,6 +7989,7 @@ Repeat connection request? Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Соединение было перемещено на %@, но при смене профиля произошла неожиданная ошибка. No comment provided by engineer. @@ -7958,6 +8009,7 @@ Repeat connection request? Your credentials may be sent unencrypted. + Ваши учетные данные могут быть отправлены в незашифрованном виде. No comment provided by engineer. @@ -7997,6 +8049,7 @@ Repeat connection request? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + Ваш профиль был изменен. Если вы сохраните его, обновленный профиль будет отправлен всем вашим контактам. alert message diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index 99f4ab0345..3bc063ecc3 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -139,6 +139,7 @@ %1$@, %2$@ + %1$@,%2$@ format for date separator in chat @@ -163,18 +164,22 @@ %d file(s) are still being downloaded. + %d dosyası(ları) hala indiriliyor. forward confirmation reason %d file(s) failed to download. + %d dosyası(ları) indirilemedi. forward confirmation reason %d file(s) were deleted. + %d dosyası(ları) silindi. forward confirmation reason %d file(s) were not downloaded. + %d dosyası(ları) indirilmedi. forward confirmation reason @@ -184,6 +189,7 @@ %d messages not forwarded + %d mesajı iletilmeyedi alert title @@ -578,6 +584,7 @@ Accent + Ana renk No comment provided by engineer. @@ -605,14 +612,17 @@ Acknowledged + Onaylandı No comment provided by engineer. Acknowledgement errors + Onay hataları No comment provided by engineer. Active connections + Aktif bağlantılar No comment provided by engineer. @@ -657,14 +667,17 @@ Additional accent + Ek ana renk No comment provided by engineer. Additional accent 2 + Ek vurgu 2 No comment provided by engineer. Additional secondary + Ek ikincil renk No comment provided by engineer. @@ -694,6 +707,7 @@ Advanced settings + Gelişmiş ayarlar No comment provided by engineer. @@ -713,6 +727,7 @@ All data is private to your device. + Tüm veriler cihazınıza özeldir. No comment provided by engineer. @@ -737,6 +752,7 @@ All profiles + Tüm Profiller profile dropdown @@ -766,6 +782,7 @@ Allow calls? + Aramalara izin verilsin mi ? No comment provided by engineer. @@ -805,6 +822,7 @@ Allow sharing + Paylaşıma izin ver No comment provided by engineer. @@ -929,6 +947,7 @@ App session + Uygulama oturumu No comment provided by engineer. @@ -953,6 +972,7 @@ Apply to + Şuna uygula No comment provided by engineer. @@ -962,10 +982,12 @@ Archive contacts to chat later. + Daha sonra görüşmek için kişileri arşivleyin. No comment provided by engineer. Archived contacts + Arşivli kişiler No comment provided by engineer. @@ -1035,6 +1057,7 @@ Auto-accept settings + Ayarları otomatik olarak kabul et alert title @@ -1044,6 +1067,7 @@ Background + Arka plan No comment provided by engineer. @@ -1081,6 +1105,7 @@ Better networking + Daha iyi ağ oluşturma No comment provided by engineer. @@ -1097,6 +1122,7 @@ Black + Siyah No comment provided by engineer. @@ -1136,10 +1162,12 @@ Blur for better privacy. + Daha iyi gizlilik için bulanıklaştır. No comment provided by engineer. Blur media + Medyayı bulanıklaştır No comment provided by engineer. @@ -1189,6 +1217,7 @@ Calls prohibited! + Aramalara izin verilmiyor! No comment provided by engineer. @@ -1198,10 +1227,12 @@ Can't call contact + Kişi aranamıyor No comment provided by engineer. Can't call member + Üye aranamaz No comment provided by engineer. @@ -1216,6 +1247,7 @@ Can't message member + Üyeye mesaj gönderilemiyor No comment provided by engineer. @@ -1235,6 +1267,7 @@ Cannot forward message + Mesaj iletilemiyor No comment provided by engineer. @@ -1310,6 +1343,7 @@ Chat colors + Sohbet renkleri No comment provided by engineer. @@ -1329,6 +1363,7 @@ Chat database exported + Veritabanı dışa aktarıldı No comment provided by engineer. @@ -1353,6 +1388,7 @@ Chat list + Sohbet listesi No comment provided by engineer. @@ -1367,6 +1403,7 @@ Chat preferences were changed. + Sohbet tercihleri değiştirildi. alert message @@ -1376,6 +1413,7 @@ Chat theme + Sohbet teması No comment provided by engineer. @@ -1410,14 +1448,17 @@ Chunks deleted + Parçalar silindi No comment provided by engineer. Chunks downloaded + Parçalar indirildi No comment provided by engineer. Chunks uploaded + Parçalar yüklendi No comment provided by engineer. @@ -1447,10 +1488,12 @@ Color chats with the new themes. + Yeni temalarla renkli sohbetler. No comment provided by engineer. Color mode + Renk modu No comment provided by engineer. @@ -1465,6 +1508,7 @@ Completed + Tamamlandı No comment provided by engineer. @@ -1474,6 +1518,7 @@ Configured %@ servers + Yapılandırılmış %@ sunucuları No comment provided by engineer. @@ -1488,6 +1533,7 @@ Confirm contact deletion? + Kişiyi silmek istediğinizden emin misiniz ? No comment provided by engineer. @@ -1547,6 +1593,7 @@ Connect to your friends faster. + Arkadaşlarınıza daha hızlı bağlanın. No comment provided by engineer. @@ -1590,6 +1637,7 @@ Bu senin kendi tek kullanımlık bağlantın! Connected + Bağlandı No comment provided by engineer. @@ -1599,15 +1647,17 @@ Bu senin kendi tek kullanımlık bağlantın! Connected servers + Bağlı sunucular No comment provided by engineer. Connected to desktop - Bilgisayara bağlanıldı + Masaüstüne bağlandı No comment provided by engineer. Connecting + Bağlanıyor No comment provided by engineer. @@ -1622,6 +1672,7 @@ Bu senin kendi tek kullanımlık bağlantın! Connecting to contact, please wait or check later! + Kişiye bağlanılıyor, lütfen bekleyin ya da daha sonra kontrol edin! No comment provided by engineer. @@ -1636,6 +1687,7 @@ Bu senin kendi tek kullanımlık bağlantın! Connection and servers status. + Bağlantı ve sunucuların durumu. No comment provided by engineer. @@ -1650,6 +1702,7 @@ Bu senin kendi tek kullanımlık bağlantın! Connection notifications + Bağlantı bildirimleri No comment provided by engineer. @@ -1669,10 +1722,12 @@ Bu senin kendi tek kullanımlık bağlantın! Connection with desktop stopped + Masaüstü ile bağlantı durduruldu No comment provided by engineer. Connections + Bağlantılar No comment provided by engineer. @@ -1687,6 +1742,7 @@ Bu senin kendi tek kullanımlık bağlantın! Contact deleted! + Kişiler silindi! No comment provided by engineer. @@ -1701,6 +1757,7 @@ Bu senin kendi tek kullanımlık bağlantın! Contact is deleted. + Kişi silindi. No comment provided by engineer. @@ -1715,6 +1772,7 @@ Bu senin kendi tek kullanımlık bağlantın! Contact will be deleted - this cannot be undone! + Kişiler silinecek - bu geri alınamaz ! No comment provided by engineer. @@ -1734,6 +1792,7 @@ Bu senin kendi tek kullanımlık bağlantın! Conversation deleted! + Sohbet silindi! No comment provided by engineer. @@ -1743,6 +1802,7 @@ Bu senin kendi tek kullanımlık bağlantın! Copy error + Kopyalama hatası No comment provided by engineer. @@ -1752,6 +1812,7 @@ Bu senin kendi tek kullanımlık bağlantın! Corner + Köşeleri yuvarlama No comment provided by engineer. @@ -1826,6 +1887,7 @@ Bu senin kendi tek kullanımlık bağlantın! Created + Yaratıldı No comment provided by engineer. @@ -1865,6 +1927,7 @@ Bu senin kendi tek kullanımlık bağlantın! Current profile + Aktif profil No comment provided by engineer. @@ -1883,6 +1946,7 @@ Bu senin kendi tek kullanımlık bağlantın! Customize theme + Renk temalarını kişiselleştir No comment provided by engineer. @@ -1892,6 +1956,7 @@ Bu senin kendi tek kullanımlık bağlantın! Dark mode colors + Karanlık mod renkleri No comment provided by engineer. @@ -2015,6 +2080,7 @@ Bu senin kendi tek kullanımlık bağlantın! Delete %lld messages of members? + Üyelerin %lld mesajları silinsin mi? No comment provided by engineer. @@ -2079,6 +2145,7 @@ Bu senin kendi tek kullanımlık bağlantın! Delete contact? + Kişiyi sil? No comment provided by engineer. @@ -2192,6 +2259,7 @@ Bu senin kendi tek kullanımlık bağlantın! Delete up to 20 messages at once. + Tek seferde en fazla 20 mesaj silin. No comment provided by engineer. @@ -2201,10 +2269,12 @@ Bu senin kendi tek kullanımlık bağlantın! Delete without notification + Bildirim göndermeden sil No comment provided by engineer. Deleted + Silindi No comment provided by engineer. @@ -2219,6 +2289,7 @@ Bu senin kendi tek kullanımlık bağlantın! Deletion errors + Silme hatası No comment provided by engineer. @@ -2258,6 +2329,7 @@ Bu senin kendi tek kullanımlık bağlantın! Destination server address of %@ is incompatible with forwarding server %@ settings. + Hedef sunucu adresi %@, yönlendirme sunucusu %@ ayarlarıyla uyumlu değil. No comment provided by engineer. @@ -2267,14 +2339,17 @@ Bu senin kendi tek kullanımlık bağlantın! Destination server version of %@ is incompatible with forwarding server %@. + Hedef sunucu %@ sürümü, yönlendirme sunucusu %@ ile uyumlu değil. No comment provided by engineer. Detailed statistics + Detaylı istatistikler No comment provided by engineer. Details + Detaylar No comment provided by engineer. @@ -2284,6 +2359,7 @@ Bu senin kendi tek kullanımlık bağlantın! Developer options + Geliştirici seçenekleri No comment provided by engineer. @@ -2338,6 +2414,7 @@ Bu senin kendi tek kullanımlık bağlantın! Disabled + Devre dışı No comment provided by engineer. @@ -2417,6 +2494,7 @@ Bu senin kendi tek kullanımlık bağlantın! Do not use credentials with proxy. + Kimlik bilgilerini proxy ile kullanmayın. No comment provided by engineer. @@ -2447,6 +2525,7 @@ Bu senin kendi tek kullanımlık bağlantın! Download errors + İndirme hataları No comment provided by engineer. @@ -2461,14 +2540,17 @@ Bu senin kendi tek kullanımlık bağlantın! Download files + Dosyaları indirin alert action Downloaded + İndirildi No comment provided by engineer. Downloaded files + Dosyalar İndirildi No comment provided by engineer. @@ -2573,6 +2655,7 @@ Bu senin kendi tek kullanımlık bağlantın! Enabled + Etkin No comment provided by engineer. @@ -2737,6 +2820,7 @@ Bu senin kendi tek kullanımlık bağlantın! Error changing connection profile + Bağlantı profili değiştirilirken hata oluştu No comment provided by engineer. @@ -2751,10 +2835,12 @@ Bu senin kendi tek kullanımlık bağlantın! Error changing to incognito! + Gizli moduna geçerken hata oluştu! No comment provided by engineer. Error connecting to forwarding server %@. Please try later. + Yönlendirme sunucusu %@'ya bağlanırken hata oluştu. Lütfen daha sonra deneyin. No comment provided by engineer. @@ -2854,6 +2940,7 @@ Bu senin kendi tek kullanımlık bağlantın! Error exporting theme: %@ + Tema dışa aktarılırken hata oluştu: %@ No comment provided by engineer. @@ -2873,6 +2960,7 @@ Bu senin kendi tek kullanımlık bağlantın! Error migrating settings + Ayarlar taşınırken hata oluştu No comment provided by engineer. @@ -2887,10 +2975,12 @@ Bu senin kendi tek kullanımlık bağlantın! Error reconnecting server + Hata, sunucuya yeniden bağlanılıyor No comment provided by engineer. Error reconnecting servers + Hata sunuculara yeniden bağlanılıyor No comment provided by engineer. @@ -2900,6 +2990,7 @@ Bu senin kendi tek kullanımlık bağlantın! Error resetting statistics + Hata istatistikler sıfırlanıyor No comment provided by engineer. @@ -2974,6 +3065,7 @@ Bu senin kendi tek kullanımlık bağlantın! Error switching profile + Profil değiştirme sırasında hata oluştu No comment provided by engineer. @@ -3038,6 +3130,7 @@ Bu senin kendi tek kullanımlık bağlantın! Errors + Hatalar No comment provided by engineer. @@ -3067,6 +3160,7 @@ Bu senin kendi tek kullanımlık bağlantın! Export theme + Temayı dışa aktar No comment provided by engineer. @@ -3106,27 +3200,34 @@ Bu senin kendi tek kullanımlık bağlantın! File error + Dosya hatası No comment provided by engineer. File errors: %@ + Dosya hataları: +%@ alert message File not found - most likely file was deleted or cancelled. + Dosya bulunamadı - muhtemelen dosya silindi veya göderim iptal edildi. file error text File server error: %@ + Dosya sunucusu hatası: %@ file error text File status + Dosya durumu No comment provided by engineer. File status: %@ + Dosya durumu: %@ copied message info @@ -3246,6 +3347,7 @@ Bu senin kendi tek kullanımlık bağlantın! Forward %d message(s)? + %d mesaj(lar)ı iletilsin mi? alert title @@ -3255,10 +3357,12 @@ Bu senin kendi tek kullanımlık bağlantın! Forward messages + İletileri ilet alert action Forward messages without files? + Mesajlar dosyalar olmadan iletilsin mi ? alert message @@ -3277,18 +3381,22 @@ Bu senin kendi tek kullanımlık bağlantın! Forwarding %lld messages + %lld mesajlarını ilet No comment provided by engineer. Forwarding server %@ failed to connect to destination server %@. Please try later. + Yönlendirme sunucusu %@, hedef sunucu %@'ya bağlanamadı. Lütfen daha sonra deneyin. No comment provided by engineer. Forwarding server address is incompatible with network settings: %@. + Yönlendirme sunucusu adresi ağ ayarlarıyla uyumsuz: %@. No comment provided by engineer. Forwarding server version is incompatible with network settings: %@. + Yönlendirme sunucusu sürümü ağ ayarlarıyla uyumsuz: %@. No comment provided by engineer. @@ -3347,10 +3455,12 @@ Hata: %2$@ Good afternoon! + İyi öğlenler! message preview Good morning! + Günaydın! message preview @@ -3570,6 +3680,7 @@ Hata: %2$@ IP address + IP adresi No comment provided by engineer. @@ -3639,6 +3750,7 @@ Hata: %2$@ Import theme + Temayı içe aktar No comment provided by engineer. @@ -3770,6 +3882,7 @@ More improvements are coming soon! Interface colors + Arayüz renkleri No comment provided by engineer. @@ -3875,6 +3988,7 @@ More improvements are coming soon! It protects your IP address and connections. + IP adresinizi ve bağlantılarınızı korur. No comment provided by engineer. @@ -3941,6 +4055,7 @@ Bu senin grup için bağlantın %@! Keep conversation + Sohbeti sakla No comment provided by engineer. @@ -4120,10 +4235,12 @@ Bu senin grup için bağlantın %@! Media & file servers + Medya ve dosya sunucuları No comment provided by engineer. Medium + Orta blur media @@ -4133,6 +4250,7 @@ Bu senin grup için bağlantın %@! Member inactive + Üye inaktif item status text @@ -4152,6 +4270,7 @@ Bu senin grup için bağlantın %@! Menus + Menüler No comment provided by engineer. @@ -4176,10 +4295,12 @@ Bu senin grup için bağlantın %@! Message forwarded + Mesaj iletildi item status text Message may be delivered later if member becomes active. + Kullanıcı aktif olursa mesaj iletilebilir. item status description @@ -4204,14 +4325,17 @@ Bu senin grup için bağlantın %@! Message reception + Mesaj alındısı No comment provided by engineer. Message servers + Mesaj sunucuları No comment provided by engineer. Message shape + Mesaj şekli No comment provided by engineer. @@ -4221,10 +4345,12 @@ Bu senin grup için bağlantın %@! Message status + Mesaj durumu No comment provided by engineer. Message status: %@ + Mesaj durumu: %@ copied message info @@ -4254,14 +4380,17 @@ Bu senin grup için bağlantın %@! Messages received + Mesajlar alındı No comment provided by engineer. Messages sent + Mesajlar gönderildi No comment provided by engineer. Messages were deleted after you selected them. + Mesajlar siz seçtikten sonra silindi. alert message @@ -4421,10 +4550,12 @@ Bu senin grup için bağlantın %@! New SOCKS credentials will be used every time you start the app. + Uygulamayı her başlattığınızda yeni SOCKS kimlik bilgileri kullanılacaktır. No comment provided by engineer. New SOCKS credentials will be used for each server. + Her sunucu için yeni SOCKS kimlik bilgileri kullanılacaktır. No comment provided by engineer. @@ -4434,6 +4565,7 @@ Bu senin grup için bağlantın %@! New chat experience 🎉 + Yeni bir sohbet deneyimi 🎉 No comment provided by engineer. @@ -4468,6 +4600,7 @@ Bu senin grup için bağlantın %@! New media options + Yeni medya seçenekleri No comment provided by engineer. @@ -4517,6 +4650,7 @@ Bu senin grup için bağlantın %@! No direct connection yet, message is forwarded by admin. + Henüz direkt bağlantı yok mesaj admin tarafından yönlendirildi. item status description @@ -4536,6 +4670,7 @@ Bu senin grup için bağlantın %@! No info, try to reload + Bilgi yok, yenilemeyi deneyin No comment provided by engineer. @@ -4545,10 +4680,12 @@ Bu senin grup için bağlantın %@! No permission to record speech + Konuşma kaydetme izni yok No comment provided by engineer. No permission to record video + Video kaydı için izin yok No comment provided by engineer. @@ -4568,10 +4705,12 @@ Bu senin grup için bağlantın %@! Nothing selected + Hiçbir şey seçilmedi No comment provided by engineer. Nothing to forward! + Yönlendirilecek bir şey yok! alert title @@ -4649,6 +4788,7 @@ VPN'nin etkinleştirilmesi gerekir. Only delete conversation + Sadece sohbeti sil No comment provided by engineer. @@ -4748,6 +4888,7 @@ VPN'nin etkinleştirilmesi gerekir. Open server settings + Sunucu ayarlarını aç No comment provided by engineer. @@ -4792,11 +4933,14 @@ VPN'nin etkinleştirilmesi gerekir. Other %@ servers + Diğer %@ sunucuları No comment provided by engineer. Other file errors: %@ + Diğer dosya hataları: +%@ alert message @@ -4836,6 +4980,7 @@ VPN'nin etkinleştirilmesi gerekir. Password + Şifre No comment provided by engineer. @@ -4870,6 +5015,7 @@ VPN'nin etkinleştirilmesi gerekir. Pending + Bekleniyor No comment provided by engineer. @@ -4894,10 +5040,12 @@ VPN'nin etkinleştirilmesi gerekir. Play from the chat list. + Sohbet listesinden oynat. No comment provided by engineer. Please ask your contact to enable calls. + Lütfen kişinizden çağrılara izin vermesini isteyin. No comment provided by engineer. @@ -4908,6 +5056,8 @@ VPN'nin etkinleştirilmesi gerekir. Please check that mobile and desktop are connected to the same local network, and that desktop firewall allows the connection. Please share any other issues with the developers. + Lütfen telefonun ve bilgisayarın aynı lokal ağa bağlı olduğundan ve bilgisayar güvenlik duvarının bağlantıya izin verdiğinden emin olun. +Lütfen diğer herhangi bir sorunu geliştiricilerle paylaşın. No comment provided by engineer. @@ -4984,6 +5134,7 @@ Hata: %@ Port + Port No comment provided by engineer. @@ -5013,6 +5164,7 @@ Hata: %@ Previously connected servers + Önceden bağlanılmış sunucular No comment provided by engineer. @@ -5052,6 +5204,7 @@ Hata: %@ Private routing error + Gizli yönlendirme hatası No comment provided by engineer. @@ -5076,6 +5229,7 @@ Hata: %@ Profile theme + Profil teması No comment provided by engineer. @@ -5162,14 +5316,17 @@ Enable in *Network & servers* settings. Proxied + Proxyli No comment provided by engineer. Proxied servers + Proxy sunucuları No comment provided by engineer. Proxy requires password + Proxy şifre gerektirir No comment provided by engineer. @@ -5194,6 +5351,7 @@ Enable in *Network & servers* settings. Reachable chat toolbar + Erişilebilir sohbet araç çubuğu No comment provided by engineer. @@ -5238,11 +5396,12 @@ Enable in *Network & servers* settings. Receipts are disabled - Gönderildi bilgisi devre dışı bırakıldı + Alıcılar devre dışı bırakıldı No comment provided by engineer. Receive errors + Alım sırasında hata No comment provided by engineer. @@ -5267,14 +5426,17 @@ Enable in *Network & servers* settings. Received messages + Alınan mesajlar No comment provided by engineer. Received reply + Alınan cevap No comment provided by engineer. Received total + Toplam alınan No comment provided by engineer. @@ -5309,6 +5471,7 @@ Enable in *Network & servers* settings. Reconnect + Yeniden bağlan No comment provided by engineer. @@ -5318,18 +5481,22 @@ Enable in *Network & servers* settings. Reconnect all servers + Tüm sunuculara yeniden bağlan No comment provided by engineer. Reconnect all servers? + Tüm sunuculara yeniden bağlansın mı? No comment provided by engineer. Reconnect server to force message delivery. It uses additional traffic. + Mesajı göndermeye zorlamak için sunucuya yeniden bağlan. Bu ekstra internet kullanır. No comment provided by engineer. Reconnect server? + Sunucuya yeniden bağlansın mı ? No comment provided by engineer. @@ -5385,10 +5552,12 @@ Enable in *Network & servers* settings. Remove archive? + Arşiv kaldırılsın mı ? No comment provided by engineer. Remove image + Resmi kaldır No comment provided by engineer. @@ -5463,14 +5632,17 @@ Enable in *Network & servers* settings. Reset all hints + Tüm ip uçlarını sıfırla No comment provided by engineer. Reset all statistics + Tüm istatistikleri sıfırla No comment provided by engineer. Reset all statistics? + Tüm istatistikler sıfırlansın mı ? No comment provided by engineer. @@ -5480,6 +5652,7 @@ Enable in *Network & servers* settings. Reset to app theme + Uygulama temasına sıfırla No comment provided by engineer. @@ -5489,6 +5662,7 @@ Enable in *Network & servers* settings. Reset to user theme + Kullanıcı temasına sıfırla No comment provided by engineer. @@ -5558,10 +5732,12 @@ Enable in *Network & servers* settings. SMP server + SMP sunucusu No comment provided by engineer. SOCKS proxy + SOCKS vekili No comment provided by engineer. @@ -5597,6 +5773,7 @@ Enable in *Network & servers* settings. Save and reconnect + Kayıt et ve yeniden bağlan No comment provided by engineer. @@ -5651,6 +5828,7 @@ Enable in *Network & servers* settings. Save your profile? + Profiliniz kaydedilsin mi? alert title @@ -5675,14 +5853,17 @@ Enable in *Network & servers* settings. Saving %lld messages + %lld mesajlarını kaydet No comment provided by engineer. Scale + Ölçeklendir No comment provided by engineer. Scan / Paste link + Tara / Bağlantı yapıştır No comment provided by engineer. @@ -5727,6 +5908,7 @@ Enable in *Network & servers* settings. Secondary + İkincil renk No comment provided by engineer. @@ -5736,6 +5918,7 @@ Enable in *Network & servers* settings. Secured + Güvenli No comment provided by engineer. @@ -5755,14 +5938,17 @@ Enable in *Network & servers* settings. Select chat profile + Sohbet profili seç No comment provided by engineer. Selected %lld + Seçilen %lld No comment provided by engineer. Selected chat preferences prohibit this message. + Seçilen sohbet tercihleri bu mesajı yasakladı. No comment provided by engineer. @@ -5812,6 +5998,7 @@ Enable in *Network & servers* settings. Send errors + Gönderme hataları No comment provided by engineer. @@ -5826,6 +6013,7 @@ Enable in *Network & servers* settings. Send message to enable calls. + Çağrıları aktif etmek için mesaj gönder. No comment provided by engineer. @@ -5885,7 +6073,7 @@ Enable in *Network & servers* settings. Sending delivery receipts will be enabled for all contacts. - Gönderildi bilgisi tüm kişiler için etkinleştirilecektir. + Tüm kişiler için iletim bilgisi gönderme özelliği etkinleştirilecek. No comment provided by engineer. @@ -5930,6 +6118,7 @@ Enable in *Network & servers* settings. Sent directly + Direkt gönderildi No comment provided by engineer. @@ -5944,6 +6133,7 @@ Enable in *Network & servers* settings. Sent messages + Gönderilen mesajlar No comment provided by engineer. @@ -5953,22 +6143,27 @@ Enable in *Network & servers* settings. Sent reply + Gönderilen cevap No comment provided by engineer. Sent total + Gönderilen tüm mesajların toplamı No comment provided by engineer. Sent via proxy + Bir proxy aracılığıyla gönderildi No comment provided by engineer. Server + Sunucu No comment provided by engineer. Server address + Sunucu adresi No comment provided by engineer. @@ -5978,6 +6173,7 @@ Enable in *Network & servers* settings. Server address is incompatible with network settings: %@. + Sunucu adresi ağ ayarlarıyla uyumsuz: %@. No comment provided by engineer. @@ -5997,6 +6193,7 @@ Enable in *Network & servers* settings. Server type + Sunucu tipi No comment provided by engineer. @@ -6006,6 +6203,7 @@ Enable in *Network & servers* settings. Server version is incompatible with your app: %@. + Sunucu sürümü uygulamanızla uyumlu değil: %@. No comment provided by engineer. @@ -6015,10 +6213,12 @@ Enable in *Network & servers* settings. Servers info + Sunucu bilgileri No comment provided by engineer. Servers statistics will be reset - this cannot be undone! + Sunucu istatistikleri sıfırlanacaktır - bu geri alınamaz! No comment provided by engineer. @@ -6038,6 +6238,7 @@ Enable in *Network & servers* settings. Set default theme + Varsayılan temaya ayarla No comment provided by engineer. @@ -6082,6 +6283,7 @@ Enable in *Network & servers* settings. Settings were changed. + Ayarlar değiştirildi. alert message @@ -6111,6 +6313,7 @@ Enable in *Network & servers* settings. Share from other apps. + Diğer uygulamalardan paylaşın. No comment provided by engineer. @@ -6120,6 +6323,7 @@ Enable in *Network & servers* settings. Share profile + Profil paylaş No comment provided by engineer. @@ -6129,6 +6333,7 @@ Enable in *Network & servers* settings. Share to SimpleX + SimpleX ile paylaş No comment provided by engineer. @@ -6163,6 +6368,7 @@ Enable in *Network & servers* settings. Show percentage + Yüzdeyi göster No comment provided by engineer. @@ -6182,6 +6388,7 @@ Enable in *Network & servers* settings. SimpleX + SimpleX No comment provided by engineer. @@ -6265,6 +6472,7 @@ Enable in *Network & servers* settings. Size + Boyut No comment provided by engineer. @@ -6284,14 +6492,17 @@ Enable in *Network & servers* settings. Soft + Yumuşak blur media Some app settings were not migrated. + Bazı uygulama ayarları taşınamadı. No comment provided by engineer. Some file(s) were not exported: + Bazı dosya(lar) dışa aktarılmadı: No comment provided by engineer. @@ -6301,6 +6512,7 @@ Enable in *Network & servers* settings. Some non-fatal errors occurred during import: + İçe aktarma sırasında bazı önemli olmayan hatalar oluştu: No comment provided by engineer. @@ -6330,10 +6542,12 @@ Enable in *Network & servers* settings. Starting from %@. + %@'dan başlayarak. No comment provided by engineer. Statistics + İstatistikler No comment provided by engineer. @@ -6398,6 +6612,7 @@ Enable in *Network & servers* settings. Strong + Güçlü blur media @@ -6407,14 +6622,17 @@ Enable in *Network & servers* settings. Subscribed + Abone olundu No comment provided by engineer. Subscription errors + Abone olurken hata No comment provided by engineer. Subscriptions ignored + Abonelikler göz ardı edildi No comment provided by engineer. @@ -6442,6 +6660,7 @@ Enable in *Network & servers* settings. TCP connection + TCP bağlantısı No comment provided by engineer. @@ -6466,6 +6685,7 @@ Enable in *Network & servers* settings. Tail + Konuşma balonu No comment provided by engineer. @@ -6510,6 +6730,7 @@ Enable in *Network & servers* settings. Temporary file error + Geçici dosya hatası No comment provided by engineer. @@ -6616,10 +6837,12 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. The messages will be deleted for all members. + Mesajlar tüm üyeler için silinecektir. No comment provided by engineer. The messages will be marked as moderated for all members. + Mesajlar tüm üyeler için moderasyonlu olarak işaretlenecektir. No comment provided by engineer. @@ -6659,10 +6882,12 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. The uploaded database archive will be permanently removed from the servers. + Yüklenen veritabanı arşivi sunuculardan kalıcı olarak kaldırılacaktır. No comment provided by engineer. Themes + Temalar No comment provided by engineer. @@ -6732,6 +6957,7 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. This link was used with another mobile device, please create a new link on the desktop. + Bu bağlantı başka bir mobil cihazda kullanıldı, lütfen masaüstünde yeni bir bağlantı oluşturun. No comment provided by engineer. @@ -6741,6 +6967,7 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Title + Başlık No comment provided by engineer. @@ -6787,10 +7014,12 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec To record speech please grant permission to use Microphone. + Konuşmayı kaydetmek için lütfen Mikrofon kullanma izni verin. No comment provided by engineer. To record video please grant permission to use Camera. + Video kaydetmek için lütfen Kamera kullanım izni verin. No comment provided by engineer. @@ -6815,6 +7044,7 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec Toggle chat list: + Sohbet listesini değiştir: No comment provided by engineer. @@ -6824,10 +7054,12 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec Toolbar opacity + Araç çubuğu opaklığı No comment provided by engineer. Total + Toplam No comment provided by engineer. @@ -6837,6 +7069,7 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec Transport sessions + Taşıma oturumları No comment provided by engineer. @@ -7008,6 +7241,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Update settings? + Ayarları güncelleyelim mi? No comment provided by engineer. @@ -7022,6 +7256,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Upload errors + Yükleme hataları No comment provided by engineer. @@ -7036,10 +7271,12 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Uploaded + Yüklendi No comment provided by engineer. Uploaded files + Yüklenen dosyalar No comment provided by engineer. @@ -7054,6 +7291,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Use SOCKS proxy + SOCKS vekili kullan No comment provided by engineer. @@ -7118,14 +7356,17 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Use the app with one hand. + Uygulamayı tek elle kullan. No comment provided by engineer. User selection + Kullanıcı seçimi No comment provided by engineer. Username + Kullanıcı Adı No comment provided by engineer. @@ -7260,10 +7501,12 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Wallpaper accent + Duvar kağıdı vurgusu No comment provided by engineer. Wallpaper background + Duvar kağıdı arkaplanı No comment provided by engineer. @@ -7373,6 +7616,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Wrong key or unknown file chunk address - most likely file is deleted. + Yanlış anahtar veya bilinmeyen dosya yığın adresi - büyük olasılıkla dosya silinmiştir. file error text @@ -7382,6 +7626,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste XFTP server + XFTP sunucusu No comment provided by engineer. @@ -7458,6 +7703,7 @@ Katılma isteği tekrarlansın mı? You are not connected to these servers. Private routing is used to deliver messages to them. + Bu sunuculara bağlı değilsiniz. Mesajları onlara iletmek için özel yönlendirme kullanılır. No comment provided by engineer. @@ -7467,6 +7713,7 @@ Katılma isteği tekrarlansın mı? You can change it in Appearance settings. + Görünüm ayarlarından değiştirebilirsiniz. No comment provided by engineer. @@ -7506,6 +7753,7 @@ Katılma isteği tekrarlansın mı? You can send messages to %@ from Archived contacts. + Arşivlenen kişilerden %@'ya mesaj gönderebilirsiniz. No comment provided by engineer. @@ -7535,6 +7783,7 @@ Katılma isteği tekrarlansın mı? You can still view conversation with %@ in the list of chats. + Sohbet listesinde %@ ile konuşmayı görüntülemeye devam edebilirsiniz. No comment provided by engineer. @@ -7601,10 +7850,12 @@ Bağlantı isteği tekrarlansın mı? You may migrate the exported database. + Dışa aktarılan veritabanını taşıyabilirsiniz. No comment provided by engineer. You may save the exported archive. + Dışa aktarılan arşivi kaydedebilirsiniz. No comment provided by engineer. @@ -7614,6 +7865,7 @@ Bağlantı isteği tekrarlansın mı? You need to allow your contact to call to be able to call them. + Kendiniz arayabilmeniz için önce irtibat kişinizin sizi aramasına izin vermelisiniz. No comment provided by engineer. @@ -7728,6 +7980,7 @@ Bağlantı isteği tekrarlansın mı? Your chat preferences + Sohbet tercihleriniz alert title @@ -7737,6 +7990,7 @@ Bağlantı isteği tekrarlansın mı? Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Bağlantınız %@ adresine taşındı ancak sizi profile yönlendirirken beklenmedik bir hata oluştu. No comment provided by engineer. @@ -7756,6 +8010,7 @@ Bağlantı isteği tekrarlansın mı? Your credentials may be sent unencrypted. + Kimlik bilgileriniz şifrelenmeden gönderilebilir. No comment provided by engineer. @@ -7795,6 +8050,7 @@ Bağlantı isteği tekrarlansın mı? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + Profiliniz değiştirildi. Kaydederseniz, güncellenmiş profil tüm kişilerinize gönderilecektir. alert message @@ -7894,6 +8150,7 @@ Bağlantı isteği tekrarlansın mı? attempts + denemeler No comment provided by engineer. @@ -7938,6 +8195,7 @@ Bağlantı isteği tekrarlansın mı? call + Ara No comment provided by engineer. @@ -8092,6 +8350,7 @@ Bağlantı isteği tekrarlansın mı? decryption errors + Şifre çözme hataları No comment provided by engineer. @@ -8146,6 +8405,7 @@ Bağlantı isteği tekrarlansın mı? duplicates + Kopyalar No comment provided by engineer. @@ -8230,6 +8490,7 @@ Bağlantı isteği tekrarlansın mı? expired + Süresi dolmuş No comment provided by engineer. @@ -8264,6 +8525,7 @@ Bağlantı isteği tekrarlansın mı? inactive + inaktif No comment provided by engineer. @@ -8308,6 +8570,7 @@ Bağlantı isteği tekrarlansın mı? invite + davet No comment provided by engineer. @@ -8367,6 +8630,7 @@ Bağlantı isteği tekrarlansın mı? message + mesaj No comment provided by engineer. @@ -8401,6 +8665,7 @@ Bağlantı isteği tekrarlansın mı? mute + Sessiz No comment provided by engineer. @@ -8457,10 +8722,12 @@ Bağlantı isteği tekrarlansın mı? other + diğer No comment provided by engineer. other errors + diğer hatalar No comment provided by engineer. @@ -8535,6 +8802,7 @@ Bağlantı isteği tekrarlansın mı? search + ara No comment provided by engineer. @@ -8623,6 +8891,7 @@ son alınan msj: %2$@ unmute + susturmayı kaldır No comment provided by engineer. @@ -8672,6 +8941,7 @@ son alınan msj: %2$@ video + video No comment provided by engineer. @@ -8852,14 +9122,17 @@ son alınan msj: %2$@ SimpleX SE + SimpleX SE Bundle display name SimpleX SE + SimpleX SE Bundle name Copyright © 2024 SimpleX Chat. All rights reserved. + Telif Hakkı © 2024 SimpleX Chat. Tüm hakları saklıdır. Copyright (human-readable) @@ -8871,150 +9144,187 @@ son alınan msj: %2$@ %@ + %@ No comment provided by engineer. App is locked! + Uygulama kilitlendi! No comment provided by engineer. Cancel + İptal et No comment provided by engineer. Cannot access keychain to save database password + Veritabanı şifresini kaydetmek için Anahtar Zinciri'ne erişilemiyor No comment provided by engineer. Cannot forward message + Mesaj iletilemiyor No comment provided by engineer. Comment + Yorum No comment provided by engineer. Currently maximum supported file size is %@. + Şu anki maksimum desteklenen dosya boyutu %@ kadardır. No comment provided by engineer. Database downgrade required + Veritabanı sürüm düşürme gerekli No comment provided by engineer. Database encrypted! + Veritabanı şifrelendi! No comment provided by engineer. Database error + Veritabanı hatası No comment provided by engineer. Database passphrase is different from saved in the keychain. + Veritabanı parolası Anahtar Zinciri'nde kayıtlı olandan farklıdır. No comment provided by engineer. Database passphrase is required to open chat. + Konuşmayı açmak için veri tabanı parolası gerekli. No comment provided by engineer. Database upgrade required + Veritabanı yükseltmesi gerekli No comment provided by engineer. Error preparing file + Dosya hazırlanırken hata oluştu No comment provided by engineer. Error preparing message + Mesaj hazırlanırken hata oluştu No comment provided by engineer. Error: %@ + Hata: %@ No comment provided by engineer. File error + Dosya hatası No comment provided by engineer. Incompatible database version + Uyumsuz veritabanı sürümü No comment provided by engineer. Invalid migration confirmation + Geçerli olmayan taşıma onayı No comment provided by engineer. Keychain error + Anahtarlık hatası No comment provided by engineer. Large file! + Büyük dosya! No comment provided by engineer. No active profile + Aktif profil yok No comment provided by engineer. Ok + Tamam No comment provided by engineer. Open the app to downgrade the database. + Veritabanının sürümünü düşürmek için uygulamayı açın. No comment provided by engineer. Open the app to upgrade the database. + Veritabanını güncellemek için uygulamayı açın. No comment provided by engineer. Passphrase + Parola No comment provided by engineer. Please create a profile in the SimpleX app + Lütfen SimpleX uygulamasında bir profil oluşturun No comment provided by engineer. Selected chat preferences prohibit this message. + Seçilen sohbet tercihleri bu mesajı yasakladı. No comment provided by engineer. Sending a message takes longer than expected. + Mesaj göndermek beklenenden daha uzun sürüyor. No comment provided by engineer. Sending message… + Mesaj gönderiliyor… No comment provided by engineer. Share + Paylaş No comment provided by engineer. Slow network? + Ağ yavaş mı? No comment provided by engineer. Unknown database error: %@ + Bilinmeyen veritabanı hatası: %@ No comment provided by engineer. Unsupported format + Desteklenmeyen format No comment provided by engineer. Wait + Bekleyin No comment provided by engineer. Wrong database passphrase + Yanlış veritabanı parolası No comment provided by engineer. You can allow sharing in Privacy & Security / SimpleX Lock settings. + Gizlilik ve Güvenlik / SimpleX Lock ayarlarından paylaşıma izin verebilirsiniz. No comment provided by engineer. diff --git a/apps/ios/SimpleX SE/hu.lproj/Localizable.strings b/apps/ios/SimpleX SE/hu.lproj/Localizable.strings index fccde46b65..94f18db853 100644 --- a/apps/ios/SimpleX SE/hu.lproj/Localizable.strings +++ b/apps/ios/SimpleX SE/hu.lproj/Localizable.strings @@ -56,7 +56,7 @@ "Invalid migration confirmation" = "Érvénytelen átköltöztetési visszaigazolás"; /* No comment provided by engineer. */ -"Keychain error" = "Kulcstartó hiba"; +"Keychain error" = "Kulcstartóhiba"; /* No comment provided by engineer. */ "Large file!" = "Nagy fájl!"; diff --git a/apps/ios/SimpleX SE/tr.lproj/InfoPlist.strings b/apps/ios/SimpleX SE/tr.lproj/InfoPlist.strings index 388ac01f7f..cf1ca31f53 100644 --- a/apps/ios/SimpleX SE/tr.lproj/InfoPlist.strings +++ b/apps/ios/SimpleX SE/tr.lproj/InfoPlist.strings @@ -1,7 +1,9 @@ -/* - InfoPlist.strings - SimpleX +/* Bundle display name */ +"CFBundleDisplayName" = "SimpleX SE"; + +/* Bundle name */ +"CFBundleName" = "SimpleX SE"; + +/* Copyright (human-readable) */ +"NSHumanReadableCopyright" = "Telif Hakkı © 2024 SimpleX Chat. Tüm hakları saklıdır."; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/SimpleX SE/tr.lproj/Localizable.strings b/apps/ios/SimpleX SE/tr.lproj/Localizable.strings index 5ef592ec70..baef71c127 100644 --- a/apps/ios/SimpleX SE/tr.lproj/Localizable.strings +++ b/apps/ios/SimpleX SE/tr.lproj/Localizable.strings @@ -1,7 +1,111 @@ -/* - Localizable.strings - SimpleX +/* No comment provided by engineer. */ +"%@" = "%@"; + +/* No comment provided by engineer. */ +"App is locked!" = "Uygulama kilitlendi!"; + +/* No comment provided by engineer. */ +"Cancel" = "İptal et"; + +/* No comment provided by engineer. */ +"Cannot access keychain to save database password" = "Veritabanı şifresini kaydetmek için Anahtar Zinciri'ne erişilemiyor"; + +/* No comment provided by engineer. */ +"Cannot forward message" = "Mesaj iletilemiyor"; + +/* No comment provided by engineer. */ +"Comment" = "Yorum"; + +/* No comment provided by engineer. */ +"Currently maximum supported file size is %@." = "Şu anki maksimum desteklenen dosya boyutu %@ kadardır."; + +/* No comment provided by engineer. */ +"Database downgrade required" = "Veritabanı sürüm düşürme gerekli"; + +/* No comment provided by engineer. */ +"Database encrypted!" = "Veritabanı şifrelendi!"; + +/* No comment provided by engineer. */ +"Database error" = "Veritabanı hatası"; + +/* No comment provided by engineer. */ +"Database passphrase is different from saved in the keychain." = "Veritabanı parolası Anahtar Zinciri'nde kayıtlı olandan farklıdır."; + +/* No comment provided by engineer. */ +"Database passphrase is required to open chat." = "Konuşmayı açmak için veri tabanı parolası gerekli."; + +/* No comment provided by engineer. */ +"Database upgrade required" = "Veritabanı yükseltmesi gerekli"; + +/* No comment provided by engineer. */ +"Error preparing file" = "Dosya hazırlanırken hata oluştu"; + +/* No comment provided by engineer. */ +"Error preparing message" = "Mesaj hazırlanırken hata oluştu"; + +/* No comment provided by engineer. */ +"Error: %@" = "Hata: %@"; + +/* No comment provided by engineer. */ +"File error" = "Dosya hatası"; + +/* No comment provided by engineer. */ +"Incompatible database version" = "Uyumsuz veritabanı sürümü"; + +/* No comment provided by engineer. */ +"Invalid migration confirmation" = "Geçerli olmayan taşıma onayı"; + +/* No comment provided by engineer. */ +"Keychain error" = "Anahtarlık hatası"; + +/* No comment provided by engineer. */ +"Large file!" = "Büyük dosya!"; + +/* No comment provided by engineer. */ +"No active profile" = "Aktif profil yok"; + +/* No comment provided by engineer. */ +"Ok" = "Tamam"; + +/* No comment provided by engineer. */ +"Open the app to downgrade the database." = "Veritabanının sürümünü düşürmek için uygulamayı açın."; + +/* No comment provided by engineer. */ +"Open the app to upgrade the database." = "Veritabanını güncellemek için uygulamayı açın."; + +/* No comment provided by engineer. */ +"Passphrase" = "Parola"; + +/* No comment provided by engineer. */ +"Please create a profile in the SimpleX app" = "Lütfen SimpleX uygulamasında bir profil oluşturun"; + +/* No comment provided by engineer. */ +"Selected chat preferences prohibit this message." = "Seçilen sohbet tercihleri bu mesajı yasakladı."; + +/* No comment provided by engineer. */ +"Sending a message takes longer than expected." = "Mesaj göndermek beklenenden daha uzun sürüyor."; + +/* No comment provided by engineer. */ +"Sending message…" = "Mesaj gönderiliyor…"; + +/* No comment provided by engineer. */ +"Share" = "Paylaş"; + +/* No comment provided by engineer. */ +"Slow network?" = "Ağ yavaş mı?"; + +/* No comment provided by engineer. */ +"Unknown database error: %@" = "Bilinmeyen veritabanı hatası: %@"; + +/* No comment provided by engineer. */ +"Unsupported format" = "Desteklenmeyen format"; + +/* No comment provided by engineer. */ +"Wait" = "Bekleyin"; + +/* No comment provided by engineer. */ +"Wrong database passphrase" = "Yanlış veritabanı parolası"; + +/* No comment provided by engineer. */ +"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "Gizlilik ve Güvenlik / SimpleX Lock ayarlarından paylaşıma izin verebilirsiniz."; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 6c078ae6ef..df9d096064 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -595,6 +595,9 @@ /* No comment provided by engineer. */ "App passcode is replaced with self-destruct passcode." = "App-Zugangscode wurde durch den Selbstzerstörungs-Zugangscode ersetzt."; +/* No comment provided by engineer. */ +"App session" = "App-Sitzung"; + /* No comment provided by engineer. */ "App version" = "App Version"; @@ -3070,6 +3073,12 @@ /* No comment provided by engineer. */ "New passphrase…" = "Neues Passwort…"; +/* No comment provided by engineer. */ +"New SOCKS credentials will be used every time you start the app." = "Jedes Mal wenn Sie die App starten, werden neue SOCKS-Anmeldeinformationen genutzt"; + +/* No comment provided by engineer. */ +"New SOCKS credentials will be used for each server." = "Für jeden Server werden neue SOCKS-Anmeldeinformationen genutzt"; + /* pref value */ "no" = "Nein"; @@ -3112,6 +3121,12 @@ /* No comment provided by engineer. */ "No network connection" = "Keine Netzwerkverbindung"; +/* No comment provided by engineer. */ +"No permission to record speech" = "Keine Genehmigung für Sprach-Aufnahmen"; + +/* No comment provided by engineer. */ +"No permission to record video" = "Keine Genehmigung für Video-Aufnahmen"; + /* No comment provided by engineer. */ "No permission to record voice message" = "Keine Berechtigung für das Aufnehmen von Sprachnachrichten"; @@ -4064,6 +4079,9 @@ /* No comment provided by engineer. */ "Sent via proxy" = "Über einen Proxy gesendet"; +/* No comment provided by engineer. */ +"Server" = "Server"; + /* No comment provided by engineer. */ "Server address" = "Server-Adresse"; @@ -4592,6 +4610,12 @@ /* No comment provided by engineer. */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "Zum Schutz Ihrer IP-Adresse, wird für die Nachrichten-Auslieferung privates Routing über Ihre konfigurierten SMP-Server genutzt."; +/* No comment provided by engineer. */ +"To record speech please grant permission to use Microphone." = "Bitte erteilen Sie für Sprach-Aufnahmen die Genehmigung das Mikrofon zu nutzen."; + +/* No comment provided by engineer. */ +"To record video please grant permission to use Camera." = "Bitte erteilen Sie für Video-Aufnahmen die Genehmigung die Kamera zu nutzen."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Bitte erlauben Sie die Nutzung des Mikrofons, um Sprachnachrichten aufnehmen zu können."; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 81284f9441..198fe3bd70 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -595,6 +595,9 @@ /* No comment provided by engineer. */ "App passcode is replaced with self-destruct passcode." = "El código de acceso será reemplazado por código de autodestrucción."; +/* No comment provided by engineer. */ +"App session" = "Sesión de aplicación"; + /* No comment provided by engineer. */ "App version" = "Versión de la aplicación"; @@ -1014,7 +1017,7 @@ "Confirm password" = "Confirmar contraseña"; /* No comment provided by engineer. */ -"Confirm that you remember database passphrase to migrate it." = "Para migrar confirma que recuerdas la frase de contraseña de la base de datos."; +"Confirm that you remember database passphrase to migrate it." = "Para migrar la base de datos confirma que recuerdas la frase de contraseña."; /* No comment provided by engineer. */ "Confirm upload" = "Confirmar subida"; @@ -2762,7 +2765,7 @@ "Local name" = "Nombre local"; /* No comment provided by engineer. */ -"Local profile data only" = "Sólo datos del perfil local"; +"Local profile data only" = "Eliminar sólo el perfil"; /* No comment provided by engineer. */ "Lock after" = "Bloquear en"; @@ -3070,6 +3073,12 @@ /* No comment provided by engineer. */ "New passphrase…" = "Contraseña nueva…"; +/* No comment provided by engineer. */ +"New SOCKS credentials will be used every time you start the app." = "Se usarán credenciales SOCKS nuevas cada vez que inicies la aplicación."; + +/* No comment provided by engineer. */ +"New SOCKS credentials will be used for each server." = "Se usarán credenciales SOCKS nuevas por cada servidor."; + /* pref value */ "no" = "no"; @@ -3112,6 +3121,12 @@ /* No comment provided by engineer. */ "No network connection" = "Sin conexión de red"; +/* No comment provided by engineer. */ +"No permission to record speech" = "Sin permiso para grabación de voz"; + +/* No comment provided by engineer. */ +"No permission to record video" = "Sin permiso para grabación de vídeo"; + /* No comment provided by engineer. */ "No permission to record voice message" = "Sin permiso para grabar mensajes de voz"; @@ -3409,7 +3424,7 @@ "Port" = "Puerto"; /* server test error */ -"Possibly, certificate fingerprint in server address is incorrect" = "Posiblemente la huella digital del certificado en la dirección del servidor es incorrecta"; +"Possibly, certificate fingerprint in server address is incorrect" = "Posiblemente la huella del certificado en la dirección del servidor es incorrecta"; /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Conserva el último borrador del mensaje con los datos adjuntos."; @@ -3451,7 +3466,7 @@ "Private routing error" = "Error de enrutamiento privado"; /* No comment provided by engineer. */ -"Profile and server connections" = "Datos del perfil y conexiones"; +"Profile and server connections" = "Eliminar perfil y conexiones"; /* No comment provided by engineer. */ "Profile image" = "Imagen del perfil"; @@ -3692,7 +3707,7 @@ "removed contact address" = "dirección de contacto eliminada"; /* profile update event chat item */ -"removed profile picture" = "imagen de perfil eliminada"; +"removed profile picture" = "ha eliminado la imagen del perfil"; /* rcv group event chat item */ "removed you" = "te ha expulsado"; @@ -4064,6 +4079,9 @@ /* No comment provided by engineer. */ "Sent via proxy" = "Mediante proxy"; +/* No comment provided by engineer. */ +"Server" = "Servidor"; + /* No comment provided by engineer. */ "Server address" = "Dirección del servidor"; @@ -4083,7 +4101,7 @@ "Server requires authorization to upload, check password" = "El servidor requiere autorización para subir, comprueba la contraseña"; /* No comment provided by engineer. */ -"Server test failed!" = "¡Error en prueba del servidor!"; +"Server test failed!" = "¡Prueba no superada!"; /* No comment provided by engineer. */ "Server type" = "Tipo de servidor"; @@ -4125,7 +4143,7 @@ "set new contact address" = "nueva dirección de contacto"; /* profile update event chat item */ -"set new profile picture" = "nueva imagen de perfil"; +"set new profile picture" = "tiene nueva imagen del perfil"; /* No comment provided by engineer. */ "Set passcode" = "Código autodestrucción"; @@ -4425,7 +4443,7 @@ "Temporary file error" = "Error en archivo temporal"; /* server test failure */ -"Test failed at step %@." = "La prueba ha fallado en el paso %@."; +"Test failed at step %@." = "Prueba no superada en el paso %@."; /* No comment provided by engineer. */ "Test server" = "Probar servidor"; @@ -4434,7 +4452,7 @@ "Test servers" = "Probar servidores"; /* No comment provided by engineer. */ -"Tests failed!" = "¡Pruebas fallidas!"; +"Tests failed!" = "¡Pruebas no superadas!"; /* No comment provided by engineer. */ "Thank you for installing SimpleX Chat!" = "¡Gracias por instalar SimpleX Chat!"; @@ -4592,6 +4610,12 @@ /* No comment provided by engineer. */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "Para proteger tu dirección IP, el enrutamiento privado usa tu lista de servidores SMP para enviar mensajes."; +/* No comment provided by engineer. */ +"To record speech please grant permission to use Microphone." = "Para grabación de voz, por favor concede el permiso para usar el micrófono."; + +/* No comment provided by engineer. */ +"To record video please grant permission to use Camera." = "Para grabación de vídeo, por favor concede el permiso para usar la cámara."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Para grabar el mensaje de voz concede permiso para usar el micrófono."; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index 0f0acb5c80..ab598c2957 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -65,13 +65,13 @@ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Csillagozás a GitHubon](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Ismerős hozzáadása**: új meghívó-hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz."; +"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Ismerős hozzáadása:** új meghívó-hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz."; /* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Új ismerős hozzáadása**: egyszer használható QR-kód vagy hivatkozás létrehozása az ismerőse számára."; +"**Add new contact**: to create your one-time QR Code for your contact." = "**Új ismerős hozzáadása:** egyszer használható QR-kód vagy hivatkozás létrehozása az ismerőse számára."; /* No comment provided by engineer. */ -"**Create group**: to create a new group." = "**Csoport létrehozása**: új csoport létrehozásához."; +"**Create group**: to create a new group." = "**Csoport létrehozása:** új csoport létrehozásához."; /* No comment provided by engineer. */ "**e2e encrypted** audio call" = "**e2e titkosított** hanghívás"; @@ -80,10 +80,10 @@ "**e2e encrypted** video call" = "**e2e titkosított** videóhívás"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**Privátabb**: 20 percenként ellenőrzi az új üzeneteket. Az eszköztoken megosztásra kerül a SimpleX Chat-kiszolgálóval, de az nem, hogy hány ismerőse vagy üzenete van."; +"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**Privátabb:** 20 percenként ellenőrzi az új üzeneteket. Az eszköztoken megosztásra kerül a SimpleX Chat-kiszolgálóval, de az nem, hogy hány ismerőse vagy üzenete van."; /* No comment provided by engineer. */ -"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Legprivátabb**: ne használja a SimpleX Chat értesítési kiszolgálót, rendszeresen ellenőrizze az üzeneteket a háttérben (attól függően, hogy milyen gyakran használja az alkalmazást)."; +"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Legprivátabb:** ne használja a SimpleX Chat értesítési kiszolgálót, rendszeresen ellenőrizze az üzeneteket a háttérben (attól függően, hogy milyen gyakran használja az alkalmazást)."; /* No comment provided by engineer. */ "**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Megjegyzés:** ha két eszközön is ugyanazt az adatbázist használja, akkor biztonsági védelemként megszakítja az ismerőseitől érkező üzenetek visszafejtését."; @@ -92,7 +92,7 @@ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Megjegyzés:** NEM tudja visszaállítani vagy megváltoztatni jelmondatát, ha elveszíti azt."; /* No comment provided by engineer. */ -"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Javasolt**: az eszköztoken és az értesítések elküldésre kerülnek a SimpleX Chat értesítési kiszolgálóra, kivéve az üzenet tartalma, mérete vagy az, hogy kitől származik."; +"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Megjegyzés:** az eszköztoken és az értesítések elküldésre kerülnek a SimpleX Chat értesítési kiszolgálóra, kivéve az üzenet tartalma, mérete vagy az, hogy kitől származik."; /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Figyelmeztetés:** Az azonnali push-értesítésekhez a kulcstartóban tárolt jelmondat megadása szükséges."; @@ -368,7 +368,7 @@ /* accept contact request via notification swipe action */ -"Accept incognito" = "Fogadás inkognítóban"; +"Accept incognito" = "Fogadás inkognitóban"; /* call status */ "accepted call" = "elfogadott hívás"; @@ -595,6 +595,9 @@ /* No comment provided by engineer. */ "App passcode is replaced with self-destruct passcode." = "Az alkalmazás jelkód helyettesítésre kerül egy önmegsemmisítő jelkóddal."; +/* No comment provided by engineer. */ +"App session" = "Alkalmazás munkamenete"; + /* No comment provided by engineer. */ "App version" = "Alkalmazás verzió"; @@ -864,7 +867,7 @@ "changed role of %@ to %@" = "%1$@ szerepkörét megváltoztatta erre: %2$@"; /* rcv group event chat item */ -"changed your role to %@" = "megváltoztatta a szerepkörét erre: %@"; +"changed your role to %@" = "megváltoztatta az Ön szerepkörét erre: %@"; /* chat item text */ "changing address for %@…" = "cím megváltoztatása nála: %@…"; @@ -1206,7 +1209,7 @@ "Core version: v%@" = "Alapverziószám: v%@"; /* No comment provided by engineer. */ -"Corner" = "Sarkos"; +"Corner" = "Sarok"; /* No comment provided by engineer. */ "Correct name to %@?" = "Név javítása erre: %@?"; @@ -1489,7 +1492,7 @@ "Delete queue" = "Sorbaállítás törlése"; /* No comment provided by engineer. */ -"Delete up to 20 messages at once." = "Legfeljebb 20 üzenet törlése egyszerre."; +"Delete up to 20 messages at once." = "Legfeljebb 20 üzenet egyszerre való törlése."; /* No comment provided by engineer. */ "Delete user profile?" = "Felhasználói profil törlése?"; @@ -1904,7 +1907,7 @@ "Error changing setting" = "Hiba a beállítás megváltoztatásakor"; /* No comment provided by engineer. */ -"Error changing to incognito!" = "Hiba az inkognitó-profilra való váltáskor!"; +"Error changing to incognito!" = "Hiba az inkognitóprofilra való váltáskor!"; /* No comment provided by engineer. */ "Error connecting to forwarding server %@. Please try later." = "Hiba a(z) %@ továbbító kiszolgálóhoz való kapcsolódáskor. Próbálja meg később."; @@ -2045,7 +2048,7 @@ "Error switching profile" = "Hiba a profilváltáskor"; /* alertTitle */ -"Error switching profile!" = "Hiba a profil váltásakor!"; +"Error switching profile!" = "Hiba a profilváltásakor!"; /* No comment provided by engineer. */ "Error synchronizing connection" = "Hiba a kapcsolat szinkronizálásakor"; @@ -2066,7 +2069,7 @@ "Error uploading the archive" = "Hiba az archívum feltöltésekor"; /* No comment provided by engineer. */ -"Error verifying passphrase:" = "Hiba a jelmondat ellenőrzésekor:"; +"Error verifying passphrase:" = "Hiba a jelmondat hitelesítésekor:"; /* No comment provided by engineer. */ "Error: " = "Hiba: "; @@ -2126,7 +2129,7 @@ "Faster joining and more reliable messages." = "Gyorsabb csatlakozás és megbízhatóbb üzenetkézbesítés."; /* swipe action */ -"Favorite" = "Csillag"; +"Favorite" = "Kedvenc"; /* No comment provided by engineer. */ "File error" = "Fájlhiba"; @@ -2177,7 +2180,7 @@ "Files and media prohibited!" = "A fájlok- és a médiatartalmak küldése le van tiltva!"; /* No comment provided by engineer. */ -"Filter unread and favorite chats." = "Olvasatlan és csillagozott csevegésekre való szűrés."; +"Filter unread and favorite chats." = "Olvasatlan és kedvenc csevegésekre való szűrés."; /* No comment provided by engineer. */ "Finalize migration" = "Átköltöztetés véglegesítése"; @@ -2303,7 +2306,7 @@ "Group full name (optional)" = "Csoport teljes neve (nem kötelező)"; /* No comment provided by engineer. */ -"Group image" = "Csoportkép"; +"Group image" = "Csoport profilképe"; /* No comment provided by engineer. */ "Group invitation" = "Csoportmeghívó"; @@ -2378,10 +2381,10 @@ "Hidden chat profiles" = "Rejtett csevegési profilok"; /* No comment provided by engineer. */ -"Hidden profile password" = "Rejtett profil jelszó"; +"Hidden profile password" = "Rejtett profiljelszó"; /* chat item action */ -"Hide" = "Elrejt"; +"Hide" = "Elrejtés"; /* No comment provided by engineer. */ "Hide app screen in the recent apps." = "Alkalmazás képernyőjének elrejtése a gyakran használt alkalmazások között."; @@ -2390,7 +2393,7 @@ "Hide profile" = "Profil elrejtése"; /* No comment provided by engineer. */ -"Hide:" = "Elrejt:"; +"Hide:" = "Elrejtés:"; /* No comment provided by engineer. */ "History" = "Előzmények"; @@ -2492,7 +2495,7 @@ "Incognito" = "Inkognitó"; /* No comment provided by engineer. */ -"Incognito groups" = "Inkognitó csoportok"; +"Incognito groups" = "Inkognitócsoportok"; /* No comment provided by engineer. */ "Incognito mode" = "Inkognitómód"; @@ -2501,7 +2504,7 @@ "Incognito mode protects your privacy by using a new random profile for each contact." = "Az inkognitómód védi személyes adatait azáltal, hogy minden ismerőshöz új véletlenszerű profilt használ."; /* chat list item description */ -"incognito via contact address link" = "inkognitó a kapcsolattartási hivatkozáson keresztül"; +"incognito via contact address link" = "inkognitó a kapcsolattartási címhivatkozáson keresztül"; /* chat list item description */ "incognito via group link" = "inkognitó a csoporthivatkozáson keresztül"; @@ -2705,10 +2708,10 @@ "Keep your connections" = "Kapcsolatok megtartása"; /* No comment provided by engineer. */ -"Keychain error" = "Kulcstartó hiba"; +"Keychain error" = "Kulcstartóhiba"; /* No comment provided by engineer. */ -"KeyChain error" = "Kulcstartó hiba"; +"KeyChain error" = "Kulcstartóhiba"; /* No comment provided by engineer. */ "Large file!" = "Nagy fájl!"; @@ -3070,6 +3073,12 @@ /* No comment provided by engineer. */ "New passphrase…" = "Új jelmondat…"; +/* No comment provided by engineer. */ +"New SOCKS credentials will be used every time you start the app." = "Minden alkalommal, amikor elindítja az alkalmazást, új SOCKS-hitelesítő-adatokat fog használni."; + +/* No comment provided by engineer. */ +"New SOCKS credentials will be used for each server." = "Minden egyes kiszolgálóhoz új SOCKS-hitelesítő-adatok legyenek használva."; + /* pref value */ "no" = "nem"; @@ -3112,6 +3121,12 @@ /* No comment provided by engineer. */ "No network connection" = "Nincs hálózati kapcsolat"; +/* No comment provided by engineer. */ +"No permission to record speech" = "Nincs jogosultság megadva a beszéd rögzítéséhez"; + +/* No comment provided by engineer. */ +"No permission to record video" = "Nincs jogosultság megadva a videó rögzítéséhez"; + /* No comment provided by engineer. */ "No permission to record voice message" = "Nincs engedély a hangüzenet rögzítésére"; @@ -3175,7 +3190,7 @@ "One-time invitation link" = "Egyszer használható meghívó-hivatkozás"; /* No comment provided by engineer. */ -"Onion hosts will be **required** for connection.\nRequires compatible VPN." = "Az onion-kiszolgálók **szükségesek** a kapcsolódáshoz.\nKompatibilis VPN szükséges."; +"Onion hosts will be **required** for connection.\nRequires compatible VPN." = "Onion-kiszolgálók **szükségesek** a kapcsolódáshoz.\nKompatibilis VPN szükséges."; /* No comment provided by engineer. */ "Onion hosts will be used when available.\nRequires compatible VPN." = "Onion-kiszolgálók használata, ha azok rendelkezésre állnak.\nVPN engedélyezése szükséges."; @@ -3436,10 +3451,10 @@ "Private filenames" = "Privát fájlnevek"; /* No comment provided by engineer. */ -"Private message routing" = "Privát üzenet útválasztás"; +"Private message routing" = "Privát üzenet-útválasztás"; /* No comment provided by engineer. */ -"Private message routing 🚀" = "Privát üzenet útválasztás 🚀"; +"Private message routing 🚀" = "Privát üzenet-útválasztás 🚀"; /* name of notes to self */ "Private notes" = "Privát jegyzetek"; @@ -3448,7 +3463,7 @@ "Private routing" = "Privát útválasztás"; /* No comment provided by engineer. */ -"Private routing error" = "Privát útválasztási hiba"; +"Private routing error" = "Privát útválasztáshiba"; /* No comment provided by engineer. */ "Profile and server connections" = "Profil és kiszolgálókapcsolatok"; @@ -4064,6 +4079,9 @@ /* No comment provided by engineer. */ "Sent via proxy" = "Proxyn keresztül küldve"; +/* No comment provided by engineer. */ +"Server" = "Kiszolgáló"; + /* No comment provided by engineer. */ "Server address" = "Kiszolgáló címe"; @@ -4254,7 +4272,7 @@ "SimpleX one-time invitation" = "Egyszer használható SimpleX-meghívó"; /* No comment provided by engineer. */ -"Simplified incognito mode" = "Egyszerűsített inkognító mód"; +"Simplified incognito mode" = "Egyszerűsített inkognitómód"; /* No comment provided by engineer. */ "Size" = "Méret"; @@ -4380,7 +4398,7 @@ "System authentication" = "Rendszerhitelesítés"; /* No comment provided by engineer. */ -"Tail" = "Nyúlványos"; +"Tail" = "Farok"; /* No comment provided by engineer. */ "Take picture" = "Kép készítése"; @@ -4401,7 +4419,7 @@ "Tap to join incognito" = "Koppintson az inkognitóban való csatlakozáshoz"; /* No comment provided by engineer. */ -"Tap to paste link" = "Koppintson a hivatkozás beillesztéséhez"; +"Tap to paste link" = "Koppintson ide a hivatkozás beillesztéséhez"; /* No comment provided by engineer. */ "Tap to scan" = "Koppintson a beolvasáshoz"; @@ -4592,6 +4610,12 @@ /* No comment provided by engineer. */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "Az IP-cím védelmének érdekében a privát útválasztás az SMP-kiszolgálókat használja az üzenetek kézbesítéséhez."; +/* No comment provided by engineer. */ +"To record speech please grant permission to use Microphone." = "A beszéd rögzítéséhez adjon engedélyt a Mikrofon használatára."; + +/* No comment provided by engineer. */ +"To record video please grant permission to use Camera." = "A videó rögzítéséhez adjon engedélyt a Kamera használatára."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Hangüzenet rögzítéséhez adjon engedélyt a mikrofon használathoz."; @@ -4602,13 +4626,13 @@ "To support instant push notifications the chat database has to be migrated." = "Az azonnali push-értesítések támogatásához a csevegési adatbázis átköltöztetése szükséges."; /* No comment provided by engineer. */ -"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "A végpontok közötti titkosítás ellenőrzéséhez hasonlítsa össze (vagy olvassa be a QR-kódot) az ismerőse eszközén lévő kóddal."; +"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "A végpontok közötti titkosítás hitelesítéséhez hasonlítsa össze (vagy olvassa be a QR-kódot) az ismerőse eszközén lévő kóddal."; /* No comment provided by engineer. */ "Toggle chat list:" = "Csevegőlista átváltása:"; /* No comment provided by engineer. */ -"Toggle incognito when connecting." = "Inkognitómód kapcsolódáskor."; +"Toggle incognito when connecting." = "Inkognitómód használata kapcsolódáskor."; /* No comment provided by engineer. */ "Toolbar opacity" = "Eszköztár átlátszatlansága"; @@ -4662,7 +4686,7 @@ "Unexpected migration state" = "Váratlan átköltöztetési állapot"; /* swipe action */ -"Unfav." = "Csillagozás megszüntetése"; +"Unfav." = "Kedvenc megszüntetése"; /* No comment provided by engineer. */ "Unhide" = "Felfedés"; @@ -4791,13 +4815,13 @@ "Use iOS call interface" = "Az iOS hívófelület használata"; /* No comment provided by engineer. */ -"Use new incognito profile" = "Az új inkognító profil használata"; +"Use new incognito profile" = "Új inkognitóprofil használata"; /* No comment provided by engineer. */ "Use only local notifications?" = "Csak helyi értesítések használata?"; /* No comment provided by engineer. */ -"Use private routing with unknown servers when IP address is not protected." = "Privát útválasztás használata ismeretlen kiszolgálókkal, ha az IP-cím nem védett."; +"Use private routing with unknown servers when IP address is not protected." = "Használjon privát útválasztást ismeretlen kiszolgálókkal, ha az IP-cím nem védett."; /* No comment provided by engineer. */ "Use private routing with unknown servers." = "Használjon privát útválasztást ismeretlen kiszolgálókkal."; @@ -4833,25 +4857,25 @@ "v%@ (%@)" = "v%@ (%@)"; /* No comment provided by engineer. */ -"Verify code with desktop" = "Kód ellenőrzése a számítógépen"; +"Verify code with desktop" = "Kód hitelesítése a számítógépen"; /* No comment provided by engineer. */ -"Verify connection" = "Kapcsolat ellenőrzése"; +"Verify connection" = "Kapcsolat hitelesítése"; /* No comment provided by engineer. */ -"Verify connection security" = "Kapcsolat biztonságának ellenőrzése"; +"Verify connection security" = "Biztonságos kapcsolat hitelesítése"; /* No comment provided by engineer. */ -"Verify connections" = "Kapcsolatok ellenőrzése"; +"Verify connections" = "Kapcsolatok hitelesítése"; /* No comment provided by engineer. */ -"Verify database passphrase" = "Az adatbázis jelmondatának ellenőrzése"; +"Verify database passphrase" = "Az adatbázis jelmondatának hitelesítése"; /* No comment provided by engineer. */ -"Verify passphrase" = "Jelmondat ellenőrzése"; +"Verify passphrase" = "Jelmondat hitelesítése"; /* No comment provided by engineer. */ -"Verify security code" = "Biztonsági kód ellenőrzése"; +"Verify security code" = "Biztonsági kód hitelesítése"; /* No comment provided by engineer. */ "Via browser" = "Böngészőn keresztül"; @@ -4977,7 +5001,7 @@ "When people request to connect, you can accept or reject it." = "Amikor az emberek kapcsolatot kérnek, Ön elfogadhatja vagy elutasíthatja azokat."; /* No comment provided by engineer. */ -"When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Inkognitó-profil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott."; +"When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Inkognitóprofil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott."; /* No comment provided by engineer. */ "WiFi" = "Wi-Fi"; @@ -5088,7 +5112,7 @@ "You can create it later" = "Létrehozás később"; /* No comment provided by engineer. */ -"You can enable later via Settings" = "Később engedélyezheti a Beállításokban"; +"You can enable later via Settings" = "Később engedélyezheti a „Beállításokban”"; /* No comment provided by engineer. */ "You can enable them later via app Privacy & Security settings." = "Később engedélyezheti őket az alkalmazás „Adatvédelem és biztonság” menüben."; @@ -5145,10 +5169,10 @@ "you changed address for %@" = "cím megváltoztatva nála: %@"; /* snd group event chat item */ -"you changed role for yourself to %@" = "saját szerepkör megváltoztatva erre: %@"; +"you changed role for yourself to %@" = "saját szerepköre megváltozott erre: %@"; /* snd group event chat item */ -"you changed role of %@ to %@" = "%1$@ szerepkörét megváltoztatta erre: %@"; +"you changed role of %@ to %@" = "Ön megváltoztatta %1$@ szerepkörét erre: %@"; /* No comment provided by engineer. */ "You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "Ön szabályozhatja, hogy mely kiszogál(ók)ón keresztül **kapja** az üzeneteket, az ismerősöket - az üzenetküldéshez használt kiszolgálókon."; @@ -5196,7 +5220,7 @@ "You rejected group invitation" = "Csoportmeghívó elutasítva"; /* snd group event chat item */ -"you removed %@" = "eltávolította őt: %@"; +"you removed %@" = "Ön eltávolította őt: %@"; /* No comment provided by engineer. */ "You sent group invitation" = "Csoportmeghívó elküldve"; @@ -5241,10 +5265,10 @@ "you: " = "Ön: "; /* No comment provided by engineer. */ -"You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile" = "Egy olyan ismerőst próbál meghívni, akivel inkognító profilt osztott meg abban a csoportban, amelyben saját fő profilja van használatban"; +"You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile" = "Egy olyan ismerősét próbálja meghívni, akivel inkognitóprofilt osztott meg abban a csoportban, amelyben a saját fő profilja van használatban"; /* No comment provided by engineer. */ -"You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Inkognító profilt használ ehhez a csoporthoz - fő profilja megosztásának elkerülése érdekében a meghívók küldése le van tiltva"; +"You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Inkognitóprofilt használ ehhez a csoporthoz - fő profilja megosztásának elkerülése érdekében a meghívók küldése le van tiltva"; /* No comment provided by engineer. */ "Your %@ servers" = "%@ nevű profiljához tartozó kiszolgálók"; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index 9f29876dd3..d780372b3a 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -595,6 +595,9 @@ /* No comment provided by engineer. */ "App passcode is replaced with self-destruct passcode." = "Il codice di accesso dell'app viene sostituito da un codice di autodistruzione."; +/* No comment provided by engineer. */ +"App session" = "Sessione dell'app"; + /* No comment provided by engineer. */ "App version" = "Versione dell'app"; @@ -3070,6 +3073,12 @@ /* No comment provided by engineer. */ "New passphrase…" = "Nuova password…"; +/* No comment provided by engineer. */ +"New SOCKS credentials will be used every time you start the app." = "Le nuove credenziali SOCKS verranno usate ogni volta che avvii l'app."; + +/* No comment provided by engineer. */ +"New SOCKS credentials will be used for each server." = "Le nuove credenziali SOCKS verranno usate per ogni server."; + /* pref value */ "no" = "no"; @@ -3112,6 +3121,12 @@ /* No comment provided by engineer. */ "No network connection" = "Nessuna connessione di rete"; +/* No comment provided by engineer. */ +"No permission to record speech" = "Nessuna autorizzazione per registrare l'audio"; + +/* No comment provided by engineer. */ +"No permission to record video" = "Nessuna autorizzazione per registrare il video"; + /* No comment provided by engineer. */ "No permission to record voice message" = "Nessuna autorizzazione per registrare messaggi vocali"; @@ -4064,6 +4079,9 @@ /* No comment provided by engineer. */ "Sent via proxy" = "Inviato via proxy"; +/* No comment provided by engineer. */ +"Server" = "Server"; + /* No comment provided by engineer. */ "Server address" = "Indirizzo server"; @@ -4592,6 +4610,12 @@ /* No comment provided by engineer. */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "Per proteggere il tuo indirizzo IP, l'instradamento privato usa i tuoi server SMP per consegnare i messaggi."; +/* No comment provided by engineer. */ +"To record speech please grant permission to use Microphone." = "Per registrare l'audio, concedi l'autorizzazione di usare il microfono."; + +/* No comment provided by engineer. */ +"To record video please grant permission to use Camera." = "Per registrare il video, concedi l'autorizzazione di usare la fotocamera."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Per registrare un messaggio vocale, concedi l'autorizzazione all'uso del microfono."; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index 14c146d70c..e5e9d00682 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -595,6 +595,9 @@ /* No comment provided by engineer. */ "App passcode is replaced with self-destruct passcode." = "De app-toegangscode wordt vervangen door een zelfvernietigings wachtwoord."; +/* No comment provided by engineer. */ +"App session" = "Appsessie"; + /* No comment provided by engineer. */ "App version" = "App versie"; @@ -3070,6 +3073,12 @@ /* No comment provided by engineer. */ "New passphrase…" = "Nieuw wachtwoord…"; +/* No comment provided by engineer. */ +"New SOCKS credentials will be used every time you start the app." = "Elke keer dat u de app start, worden er nieuwe SOCKS-inloggegevens gebruikt."; + +/* No comment provided by engineer. */ +"New SOCKS credentials will be used for each server." = "Voor elke server worden nieuwe SOCKS-inloggegevens gebruikt."; + /* pref value */ "no" = "Nee"; @@ -3112,6 +3121,12 @@ /* No comment provided by engineer. */ "No network connection" = "Geen netwerkverbinding"; +/* No comment provided by engineer. */ +"No permission to record speech" = "Geen toestemming om spraak op te nemen"; + +/* No comment provided by engineer. */ +"No permission to record video" = "Geen toestemming om video op te nemen"; + /* No comment provided by engineer. */ "No permission to record voice message" = "Geen toestemming om spraakbericht op te nemen"; @@ -4064,6 +4079,9 @@ /* No comment provided by engineer. */ "Sent via proxy" = "Verzonden via proxy"; +/* No comment provided by engineer. */ +"Server" = "Server"; + /* No comment provided by engineer. */ "Server address" = "Server adres"; @@ -4379,6 +4397,9 @@ /* No comment provided by engineer. */ "System authentication" = "Systeem authenticatie"; +/* No comment provided by engineer. */ +"Tail" = "Staart"; + /* No comment provided by engineer. */ "Take picture" = "Foto nemen"; @@ -4589,6 +4610,12 @@ /* No comment provided by engineer. */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "Om uw IP-adres te beschermen, gebruikt privéroutering uw SMP-servers om berichten te bezorgen."; +/* No comment provided by engineer. */ +"To record speech please grant permission to use Microphone." = "Geef toestemming om de microfoon te gebruiken om spraak op te nemen."; + +/* No comment provided by engineer. */ +"To record video please grant permission to use Camera." = "Om video op te nemen, dient u toestemming te geven om de camera te gebruiken."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Geef toestemming om de microfoon te gebruiken om een spraakbericht op te nemen."; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index 246f841897..b8883ac092 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -160,6 +160,9 @@ /* notification title */ "%@ wants to connect!" = "%@ chce się połączyć!"; +/* format for date separator in chat */ +"%@, %@" = "%1$@, %2$@"; + /* No comment provided by engineer. */ "%@, %@ and %lld members" = "%@, %@ i %lld członków"; @@ -172,9 +175,24 @@ /* time interval */ "%d days" = "%d dni"; +/* forward confirmation reason */ +"%d file(s) are still being downloaded." = "%d plik(ów) jest dalej pobieranych."; + +/* forward confirmation reason */ +"%d file(s) failed to download." = "%d plik(ów) nie udało się pobrać."; + +/* forward confirmation reason */ +"%d file(s) were deleted." = "%d plik(ów) zostało usuniętych."; + +/* forward confirmation reason */ +"%d file(s) were not downloaded." = "%d plik(ów) nie zostało pobranych."; + /* time interval */ "%d hours" = "%d godzin"; +/* alert title */ +"%d messages not forwarded" = "%d wiadomości nie przekazanych"; + /* time interval */ "%d min" = "%d min"; @@ -577,6 +595,9 @@ /* No comment provided by engineer. */ "App passcode is replaced with self-destruct passcode." = "Pin aplikacji został zastąpiony pinem samozniszczenia."; +/* No comment provided by engineer. */ +"App session" = "Sesja aplikacji"; + /* No comment provided by engineer. */ "App version" = "Wersja aplikacji"; @@ -649,6 +670,9 @@ /* No comment provided by engineer. */ "Auto-accept images" = "Automatyczne akceptowanie obrazów"; +/* alert title */ +"Auto-accept settings" = "Ustawienia automatycznej akceptacji"; + /* No comment provided by engineer. */ "Back" = "Wstecz"; @@ -890,6 +914,9 @@ /* No comment provided by engineer. */ "Chat preferences" = "Preferencje czatu"; +/* alert message */ +"Chat preferences were changed." = "Preferencje czatu zostały zmienione."; + /* No comment provided by engineer. */ "Chat profile" = "Profil użytkownika"; @@ -1181,6 +1208,9 @@ /* No comment provided by engineer. */ "Core version: v%@" = "Wersja rdzenia: v%@"; +/* No comment provided by engineer. */ +"Corner" = "Róg"; + /* No comment provided by engineer. */ "Correct name to %@?" = "Poprawić imię na %@?"; @@ -1614,6 +1644,9 @@ /* No comment provided by engineer. */ "Do NOT send messages directly, even if your or destination server does not support private routing." = "NIE wysyłaj wiadomości bezpośrednio, nawet jeśli serwer docelowy nie obsługuje prywatnego trasowania."; +/* No comment provided by engineer. */ +"Do not use credentials with proxy." = "Nie używaj danych logowania do proxy."; + /* No comment provided by engineer. */ "Do NOT use private routing." = "NIE używaj prywatnego trasowania."; @@ -1645,6 +1678,9 @@ /* server test step */ "Download file" = "Pobierz plik"; +/* alert action */ +"Download files" = "Pobierz pliki"; + /* No comment provided by engineer. */ "Downloaded" = "Pobrane"; @@ -1861,12 +1897,18 @@ /* No comment provided by engineer. */ "Error changing address" = "Błąd zmiany adresu"; +/* No comment provided by engineer. */ +"Error changing connection profile" = "Błąd zmiany połączenia profilu"; + /* No comment provided by engineer. */ "Error changing role" = "Błąd zmiany roli"; /* No comment provided by engineer. */ "Error changing setting" = "Błąd zmiany ustawienia"; +/* No comment provided by engineer. */ +"Error changing to incognito!" = "Błąd zmiany na incognito!"; + /* No comment provided by engineer. */ "Error connecting to forwarding server %@. Please try later." = "Błąd połączenia z serwerem przekierowania %@. Spróbuj ponownie później."; @@ -1939,6 +1981,9 @@ /* No comment provided by engineer. */ "Error loading %@ servers" = "Błąd ładowania %@ serwerów"; +/* No comment provided by engineer. */ +"Error migrating settings" = "Błąd migracji ustawień"; + /* No comment provided by engineer. */ "Error opening chat" = "Błąd otwierania czatu"; @@ -1999,6 +2044,9 @@ /* No comment provided by engineer. */ "Error stopping chat" = "Błąd zatrzymania czatu"; +/* No comment provided by engineer. */ +"Error switching profile" = "Błąd zmiany profilu"; + /* alertTitle */ "Error switching profile!" = "Błąd przełączania profilu!"; @@ -2086,6 +2134,9 @@ /* No comment provided by engineer. */ "File error" = "Błąd pliku"; +/* alert message */ +"File errors:\n%@" = "Błędy pliku:\n%@"; + /* file error text */ "File not found - most likely file was deleted or cancelled." = "Nie odnaleziono pliku - najprawdopodobniej plik został usunięty lub anulowany."; @@ -2167,9 +2218,18 @@ /* chat item action */ "Forward" = "Przekaż dalej"; +/* alert title */ +"Forward %d message(s)?" = "Przekazać %d wiadomość(i)?"; + /* No comment provided by engineer. */ "Forward and save messages" = "Przesyłaj dalej i zapisuj wiadomości"; +/* alert action */ +"Forward messages" = "Przekaż wiadomości"; + +/* alert message */ +"Forward messages without files?" = "Przekazać wiadomości bez plików?"; + /* No comment provided by engineer. */ "forwarded" = "przekazane dalej"; @@ -2179,6 +2239,9 @@ /* No comment provided by engineer. */ "Forwarded from" = "Przekazane dalej od"; +/* No comment provided by engineer. */ +"Forwarding %lld messages" = "Przekazywanie %lld wiadomości"; + /* No comment provided by engineer. */ "Forwarding server %@ failed to connect to destination server %@. Please try later." = "Serwer przekazujący %@ nie mógł połączyć się z serwerem docelowym %@. Spróbuj ponownie później."; @@ -2566,6 +2629,9 @@ /* No comment provided by engineer. */ "iOS Keychain will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving push notifications." = "iOS Keychain będzie używany do bezpiecznego przechowywania hasła po ponownym uruchomieniu aplikacji lub zmianie hasła - pozwoli to na otrzymywanie powiadomień push."; +/* No comment provided by engineer. */ +"IP address" = "Adres IP"; + /* No comment provided by engineer. */ "Irreversible message deletion" = "Nieodwracalne usuwanie wiadomości"; @@ -2818,6 +2884,9 @@ /* No comment provided by engineer. */ "Message servers" = "Serwery wiadomości"; +/* No comment provided by engineer. */ +"Message shape" = "Kształt wiadomości"; + /* No comment provided by engineer. */ "Message source remains private." = "Źródło wiadomości pozostaje prywatne."; @@ -2848,6 +2917,9 @@ /* No comment provided by engineer. */ "Messages sent" = "Wysłane wiadomości"; +/* alert message */ +"Messages were deleted after you selected them." = "Wiadomości zostały usunięte po wybraniu ich."; + /* No comment provided by engineer. */ "Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Wiadomości, pliki i połączenia są chronione przez **szyfrowanie end-to-end** z doskonałym utajnianiem z wyprzedzeniem i odzyskiem po złamaniu."; @@ -3001,6 +3073,12 @@ /* No comment provided by engineer. */ "New passphrase…" = "Nowe hasło…"; +/* No comment provided by engineer. */ +"New SOCKS credentials will be used every time you start the app." = "Nowe poświadczenia SOCKS będą używane przy każdym uruchomieniu aplikacji."; + +/* No comment provided by engineer. */ +"New SOCKS credentials will be used for each server." = "Dla każdego serwera zostaną użyte nowe poświadczenia SOCKS."; + /* pref value */ "no" = "nie"; @@ -3043,6 +3121,12 @@ /* No comment provided by engineer. */ "No network connection" = "Brak połączenia z siecią"; +/* No comment provided by engineer. */ +"No permission to record speech" = "Brak zezwoleń do nagrania rozmowy"; + +/* No comment provided by engineer. */ +"No permission to record video" = "Brak zezwoleń do nagrania wideo"; + /* No comment provided by engineer. */ "No permission to record voice message" = "Brak uprawnień do nagrywania wiadomości głosowej"; @@ -3058,6 +3142,9 @@ /* No comment provided by engineer. */ "Nothing selected" = "Nic nie jest zaznaczone"; +/* alert title */ +"Nothing to forward!" = "Nic do przekazania!"; + /* No comment provided by engineer. */ "Notifications" = "Powiadomienia"; @@ -3210,6 +3297,9 @@ /* No comment provided by engineer. */ "other errors" = "inne błędy"; +/* alert message */ +"Other file errors:\n%@" = "Inne błędy pliku:\n%@"; + /* member role */ "owner" = "właściciel"; @@ -3231,6 +3321,9 @@ /* No comment provided by engineer. */ "Passcode set!" = "Pin ustawiony!"; +/* No comment provided by engineer. */ +"Password" = "Hasło"; + /* No comment provided by engineer. */ "Password to show" = "Hasło do wyświetlenia"; @@ -3327,6 +3420,9 @@ /* No comment provided by engineer. */ "Polish interface" = "Polski interfejs"; +/* No comment provided by engineer. */ +"Port" = "Port"; + /* server test error */ "Possibly, certificate fingerprint in server address is incorrect" = "Możliwe, że odcisk palca certyfikatu w adresie serwera jest nieprawidłowy"; @@ -3438,6 +3534,9 @@ /* No comment provided by engineer. */ "Proxied servers" = "Serwery trasowane przez proxy"; +/* No comment provided by engineer. */ +"Proxy requires password" = "Proxy wymaga hasła"; + /* No comment provided by engineer. */ "Push notifications" = "Powiadomienia push"; @@ -3583,6 +3682,9 @@ /* No comment provided by engineer. */ "Remove" = "Usuń"; +/* No comment provided by engineer. */ +"Remove archive?" = "Usunąć archiwum?"; + /* No comment provided by engineer. */ "Remove image" = "Usuń obraz"; @@ -3755,6 +3857,9 @@ /* No comment provided by engineer. */ "Save welcome message?" = "Zapisać wiadomość powitalną?"; +/* alert title */ +"Save your profile?" = "Zapisać Twój profil?"; + /* No comment provided by engineer. */ "saved" = "zapisane"; @@ -3773,6 +3878,9 @@ /* No comment provided by engineer. */ "Saved WebRTC ICE servers will be removed" = "Zapisane serwery WebRTC ICE zostaną usunięte"; +/* No comment provided by engineer. */ +"Saving %lld messages" = "Zapisywanie %lld wiadomości"; + /* No comment provided by engineer. */ "Scale" = "Skaluj"; @@ -3836,6 +3944,9 @@ /* chat item action */ "Select" = "Wybierz"; +/* No comment provided by engineer. */ +"Select chat profile" = "Wybierz profil czatu"; + /* No comment provided by engineer. */ "Selected %lld" = "Zaznaczono %lld"; @@ -3968,6 +4079,9 @@ /* No comment provided by engineer. */ "Sent via proxy" = "Wysłano przez proxy"; +/* No comment provided by engineer. */ +"Server" = "Serwer"; + /* No comment provided by engineer. */ "Server address" = "Adres serwera"; @@ -4049,6 +4163,9 @@ /* No comment provided by engineer. */ "Settings" = "Ustawienia"; +/* alert message */ +"Settings were changed." = "Ustawienia zostały zmienione."; + /* No comment provided by engineer. */ "Shape profile images" = "Kształtuj obrazy profilowe"; @@ -4070,6 +4187,9 @@ /* No comment provided by engineer. */ "Share link" = "Udostępnij link"; +/* No comment provided by engineer. */ +"Share profile" = "Udostępnij profil"; + /* No comment provided by engineer. */ "Share this 1-time invite link" = "Udostępnij ten jednorazowy link"; @@ -4169,9 +4289,15 @@ /* No comment provided by engineer. */ "SMP server" = "Serwer SMP"; +/* No comment provided by engineer. */ +"SOCKS proxy" = "Proxy SOCKS"; + /* blur media */ "Soft" = "Łagodny"; +/* No comment provided by engineer. */ +"Some app settings were not migrated." = "Niektóre ustawienia aplikacji nie zostały zmigrowane."; + /* No comment provided by engineer. */ "Some file(s) were not exported:" = "Niektóre plik(i) nie zostały wyeksportowane:"; @@ -4271,6 +4397,9 @@ /* No comment provided by engineer. */ "System authentication" = "Uwierzytelnianie systemu"; +/* No comment provided by engineer. */ +"Tail" = "Ogon"; + /* No comment provided by engineer. */ "Take picture" = "Zrób zdjęcie"; @@ -4400,6 +4529,9 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Tekst, który wkleiłeś nie jest linkiem SimpleX."; +/* No comment provided by engineer. */ +"The uploaded database archive will be permanently removed from the servers." = "Przesłane archiwum bazy danych zostanie trwale usunięte z serwerów."; + /* No comment provided by engineer. */ "Themes" = "Motywy"; @@ -4478,6 +4610,12 @@ /* No comment provided by engineer. */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "Aby chronić Twój adres IP, prywatne trasowanie używa Twoich serwerów SMP, aby dostarczyć wiadomości."; +/* No comment provided by engineer. */ +"To record speech please grant permission to use Microphone." = "Aby nagrać rozmowę, proszę zezwolić na użycie Mikrofonu."; + +/* No comment provided by engineer. */ +"To record video please grant permission to use Camera." = "Aby nagrać wideo, proszę zezwolić na użycie Aparatu."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Aby nagrać wiadomość głosową należy udzielić zgody na użycie Mikrofonu."; @@ -4694,6 +4832,9 @@ /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Użyć serwerów SimpleX Chat?"; +/* No comment provided by engineer. */ +"Use SOCKS proxy" = "Użyj proxy SOCKS"; + /* No comment provided by engineer. */ "Use the app while in the call." = "Używaj aplikacji podczas połączenia."; @@ -4703,6 +4844,9 @@ /* No comment provided by engineer. */ "User selection" = "Wybór użytkownika"; +/* No comment provided by engineer. */ +"Username" = "Nazwa użytkownika"; + /* No comment provided by engineer. */ "Using SimpleX Chat servers." = "Używanie serwerów SimpleX Chat."; @@ -5138,9 +5282,15 @@ /* No comment provided by engineer. */ "Your chat database is not encrypted - set passphrase to encrypt it." = "Baza danych czatu nie jest szyfrowana - ustaw hasło, aby ją zaszyfrować."; +/* alert title */ +"Your chat preferences" = "Twoje preferencje czatu"; + /* No comment provided by engineer. */ "Your chat profiles" = "Twoje profile czatu"; +/* No comment provided by engineer. */ +"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Twoje połączenie zostało przeniesione do %@, ale podczas przekierowywania do profilu wystąpił nieoczekiwany błąd."; + /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "Twój kontakt wysłał plik, który jest większy niż obecnie obsługiwany maksymalny rozmiar (%@)."; @@ -5150,6 +5300,9 @@ /* No comment provided by engineer. */ "Your contacts will remain connected." = "Twoje kontakty pozostaną połączone."; +/* No comment provided by engineer. */ +"Your credentials may be sent unencrypted." = "Twoje poświadczenia mogą zostać wysłane niezaszyfrowane."; + /* No comment provided by engineer. */ "Your current chat database will be DELETED and REPLACED with the imported one." = "Twoja obecna baza danych czatu zostanie usunięta i zastąpiona zaimportowaną."; @@ -5174,6 +5327,9 @@ /* No comment provided by engineer. */ "Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Twój profil jest przechowywany na urządzeniu i udostępniany tylko Twoim kontaktom. Serwery SimpleX nie mogą zobaczyć Twojego profilu."; +/* alert message */ +"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Twój profil został zmieniony. Jeśli go zapiszesz, zaktualizowany profil zostanie wysłany do wszystkich kontaktów."; + /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Twój profil, kontakty i dostarczone wiadomości są przechowywane na Twoim urządzeniu."; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index a5648ced4b..4392c20699 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -172,9 +172,24 @@ /* time interval */ "%d days" = "%d дней"; +/* forward confirmation reason */ +"%d file(s) are still being downloaded." = "%d файл(ов) загружаются."; + +/* forward confirmation reason */ +"%d file(s) failed to download." = "%d файл(ов) не удалось загрузить."; + +/* forward confirmation reason */ +"%d file(s) were deleted." = "%d файлов было удалено."; + +/* forward confirmation reason */ +"%d file(s) were not downloaded." = "%d файлов не было загружено."; + /* time interval */ "%d hours" = "%d ч."; +/* alert title */ +"%d messages not forwarded" = "%d сообщений не переслано"; + /* time interval */ "%d min" = "%d мин"; @@ -577,6 +592,9 @@ /* No comment provided by engineer. */ "App passcode is replaced with self-destruct passcode." = "Код доступа в приложение будет заменен кодом самоуничтожения."; +/* No comment provided by engineer. */ +"App session" = "Сессия приложения"; + /* No comment provided by engineer. */ "App version" = "Версия приложения"; @@ -649,6 +667,9 @@ /* No comment provided by engineer. */ "Auto-accept images" = "Автоприем изображений"; +/* alert title */ +"Auto-accept settings" = "Настройки автоприема"; + /* No comment provided by engineer. */ "Back" = "Назад"; @@ -890,6 +911,9 @@ /* No comment provided by engineer. */ "Chat preferences" = "Предпочтения"; +/* alert message */ +"Chat preferences were changed." = "Настройки чата были изменены."; + /* No comment provided by engineer. */ "Chat profile" = "Профиль чата"; @@ -1181,6 +1205,9 @@ /* No comment provided by engineer. */ "Core version: v%@" = "Версия ядра: v%@"; +/* No comment provided by engineer. */ +"Corner" = "Угол"; + /* No comment provided by engineer. */ "Correct name to %@?" = "Исправить имя на %@?"; @@ -1614,6 +1641,9 @@ /* No comment provided by engineer. */ "Do NOT send messages directly, even if your or destination server does not support private routing." = "Не отправлять сообщения напрямую, даже если сервер получателя не поддерживает конфиденциальную доставку."; +/* No comment provided by engineer. */ +"Do not use credentials with proxy." = "Не использовать учетные данные с прокси."; + /* No comment provided by engineer. */ "Do NOT use private routing." = "Не использовать конфиденциальную доставку."; @@ -1645,6 +1675,9 @@ /* server test step */ "Download file" = "Загрузка файла"; +/* alert action */ +"Download files" = "Загрузить файлы"; + /* No comment provided by engineer. */ "Downloaded" = "Принято"; @@ -1861,12 +1894,18 @@ /* No comment provided by engineer. */ "Error changing address" = "Ошибка при изменении адреса"; +/* No comment provided by engineer. */ +"Error changing connection profile" = "Ошибка при изменении профиля соединения"; + /* No comment provided by engineer. */ "Error changing role" = "Ошибка при изменении роли"; /* No comment provided by engineer. */ "Error changing setting" = "Ошибка при изменении настройки"; +/* No comment provided by engineer. */ +"Error changing to incognito!" = "Ошибка при смене на Инкогнито!"; + /* No comment provided by engineer. */ "Error connecting to forwarding server %@. Please try later." = "Ошибка подключения к пересылающему серверу %@. Попробуйте позже."; @@ -1939,6 +1978,9 @@ /* No comment provided by engineer. */ "Error loading %@ servers" = "Ошибка загрузки %@ серверов"; +/* No comment provided by engineer. */ +"Error migrating settings" = "Ошибка миграции настроек"; + /* No comment provided by engineer. */ "Error opening chat" = "Ошибка доступа к чату"; @@ -1999,6 +2041,9 @@ /* No comment provided by engineer. */ "Error stopping chat" = "Ошибка при остановке чата"; +/* No comment provided by engineer. */ +"Error switching profile" = "Ошибка переключения профиля"; + /* alertTitle */ "Error switching profile!" = "Ошибка выбора профиля!"; @@ -2086,6 +2131,9 @@ /* No comment provided by engineer. */ "File error" = "Ошибка файла"; +/* alert message */ +"File errors:\n%@" = "Ошибки файлов:\n%@"; + /* file error text */ "File not found - most likely file was deleted or cancelled." = "Файл не найден - скорее всего, файл был удален или отменен."; @@ -2167,9 +2215,18 @@ /* chat item action */ "Forward" = "Переслать"; +/* alert title */ +"Forward %d message(s)?" = "Переслать %d сообщение(й)?"; + /* No comment provided by engineer. */ "Forward and save messages" = "Переслать и сохранить сообщение"; +/* alert action */ +"Forward messages" = "Переслать сообщения"; + +/* alert message */ +"Forward messages without files?" = "Переслать сообщения без файлов?"; + /* No comment provided by engineer. */ "forwarded" = "переслано"; @@ -2179,6 +2236,9 @@ /* No comment provided by engineer. */ "Forwarded from" = "Переслано из"; +/* No comment provided by engineer. */ +"Forwarding %lld messages" = "Пересылка %lld сообщений"; + /* No comment provided by engineer. */ "Forwarding server %@ failed to connect to destination server %@. Please try later." = "Пересылающий сервер %@ не смог подключиться к серверу назначения %@. Попробуйте позже."; @@ -2566,6 +2626,9 @@ /* No comment provided by engineer. */ "iOS Keychain will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving push notifications." = "Пароль базы данных будет безопасно сохранен в iOS Keychain после запуска чата или изменения пароля - это позволит получать мгновенные уведомления."; +/* No comment provided by engineer. */ +"IP address" = "IP адрес"; + /* No comment provided by engineer. */ "Irreversible message deletion" = "Окончательное удаление сообщений"; @@ -2818,6 +2881,9 @@ /* No comment provided by engineer. */ "Message servers" = "Серверы сообщений"; +/* No comment provided by engineer. */ +"Message shape" = "Форма сообщений"; + /* No comment provided by engineer. */ "Message source remains private." = "Источник сообщения остаётся конфиденциальным."; @@ -2848,6 +2914,9 @@ /* No comment provided by engineer. */ "Messages sent" = "Сообщений отправлено"; +/* alert message */ +"Messages were deleted after you selected them." = "Сообщения были удалены после того, как вы их выбрали."; + /* No comment provided by engineer. */ "Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Сообщения, файлы и звонки защищены **end-to-end шифрованием** с прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома."; @@ -3001,6 +3070,12 @@ /* No comment provided by engineer. */ "New passphrase…" = "Новый пароль…"; +/* No comment provided by engineer. */ +"New SOCKS credentials will be used every time you start the app." = "Новые учетные данные SOCKS будут использоваться при каждом запуске приложения."; + +/* No comment provided by engineer. */ +"New SOCKS credentials will be used for each server." = "Новые учетные данные SOCKS будут использоваться для каждого сервера."; + /* pref value */ "no" = "нет"; @@ -3043,6 +3118,12 @@ /* No comment provided by engineer. */ "No network connection" = "Нет интернет-соединения"; +/* No comment provided by engineer. */ +"No permission to record speech" = "Нет разрешения на запись речи"; + +/* No comment provided by engineer. */ +"No permission to record video" = "Нет разрешения на запись видео"; + /* No comment provided by engineer. */ "No permission to record voice message" = "Нет разрешения для записи голосового сообщения"; @@ -3058,6 +3139,9 @@ /* No comment provided by engineer. */ "Nothing selected" = "Ничего не выбрано"; +/* alert title */ +"Nothing to forward!" = "Нет сообщений, которые можно переслать!"; + /* No comment provided by engineer. */ "Notifications" = "Уведомления"; @@ -3210,6 +3294,9 @@ /* No comment provided by engineer. */ "other errors" = "другие ошибки"; +/* alert message */ +"Other file errors:\n%@" = "Другие ошибки файлов:\n%@"; + /* member role */ "owner" = "владелец"; @@ -3231,6 +3318,9 @@ /* No comment provided by engineer. */ "Passcode set!" = "Код доступа установлен!"; +/* No comment provided by engineer. */ +"Password" = "Пароль"; + /* No comment provided by engineer. */ "Password to show" = "Пароль чтобы раскрыть"; @@ -3327,6 +3417,9 @@ /* No comment provided by engineer. */ "Polish interface" = "Польский интерфейс"; +/* No comment provided by engineer. */ +"Port" = "Порт"; + /* server test error */ "Possibly, certificate fingerprint in server address is incorrect" = "Возможно, хэш сертификата в адресе сервера неверный"; @@ -3438,6 +3531,9 @@ /* No comment provided by engineer. */ "Proxied servers" = "Проксированные серверы"; +/* No comment provided by engineer. */ +"Proxy requires password" = "Прокси требует пароль"; + /* No comment provided by engineer. */ "Push notifications" = "Доставка уведомлений"; @@ -3583,6 +3679,9 @@ /* No comment provided by engineer. */ "Remove" = "Удалить"; +/* No comment provided by engineer. */ +"Remove archive?" = "Удалить архив?"; + /* No comment provided by engineer. */ "Remove image" = "Удалить изображение"; @@ -3755,6 +3854,9 @@ /* No comment provided by engineer. */ "Save welcome message?" = "Сохранить приветственное сообщение?"; +/* alert title */ +"Save your profile?" = "Сохранить ваш профиль?"; + /* No comment provided by engineer. */ "saved" = "сохранено"; @@ -3773,6 +3875,9 @@ /* No comment provided by engineer. */ "Saved WebRTC ICE servers will be removed" = "Сохраненные WebRTC ICE серверы будут удалены"; +/* No comment provided by engineer. */ +"Saving %lld messages" = "Сохранение %lld сообщений"; + /* No comment provided by engineer. */ "Scale" = "Масштаб"; @@ -3836,6 +3941,9 @@ /* chat item action */ "Select" = "Выбрать"; +/* No comment provided by engineer. */ +"Select chat profile" = "Выберите профиль чата"; + /* No comment provided by engineer. */ "Selected %lld" = "Выбрано %lld"; @@ -3968,6 +4076,9 @@ /* No comment provided by engineer. */ "Sent via proxy" = "Отправлено через прокси"; +/* No comment provided by engineer. */ +"Server" = "Сервер"; + /* No comment provided by engineer. */ "Server address" = "Адрес сервера"; @@ -4049,6 +4160,9 @@ /* No comment provided by engineer. */ "Settings" = "Настройки"; +/* alert message */ +"Settings were changed." = "Настройки были изменены."; + /* No comment provided by engineer. */ "Shape profile images" = "Форма картинок профилей"; @@ -4070,6 +4184,9 @@ /* No comment provided by engineer. */ "Share link" = "Поделиться ссылкой"; +/* No comment provided by engineer. */ +"Share profile" = "Поделиться профилем"; + /* No comment provided by engineer. */ "Share this 1-time invite link" = "Поделиться одноразовой ссылкой-приглашением"; @@ -4169,9 +4286,15 @@ /* No comment provided by engineer. */ "SMP server" = "SMP сервер"; +/* No comment provided by engineer. */ +"SOCKS proxy" = "SOCKS прокси"; + /* blur media */ "Soft" = "Слабое"; +/* No comment provided by engineer. */ +"Some app settings were not migrated." = "Некоторые настройки приложения не были перенесены."; + /* No comment provided by engineer. */ "Some file(s) were not exported:" = "Некоторые файл(ы) не были экспортированы:"; @@ -4271,6 +4394,9 @@ /* No comment provided by engineer. */ "System authentication" = "Системная аутентификация"; +/* No comment provided by engineer. */ +"Tail" = "Хвост"; + /* No comment provided by engineer. */ "Take picture" = "Сделать фото"; @@ -4400,6 +4526,9 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Вставленный текст не является SimpleX-ссылкой."; +/* No comment provided by engineer. */ +"The uploaded database archive will be permanently removed from the servers." = "Загруженный архив базы данных будет навсегда удален с серверов."; + /* No comment provided by engineer. */ "Themes" = "Темы"; @@ -4478,6 +4607,12 @@ /* No comment provided by engineer. */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "Чтобы защитить ваш IP адрес, приложение использует Ваши SMP серверы для конфиденциальной доставки сообщений."; +/* No comment provided by engineer. */ +"To record speech please grant permission to use Microphone." = "Для записи речи, пожалуйста, дайте разрешение на использование микрофона."; + +/* No comment provided by engineer. */ +"To record video please grant permission to use Camera." = "Для записи видео, пожалуйста, дайте разрешение на использование камеры."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Для записи голосового сообщения, пожалуйста разрешите доступ к микрофону."; @@ -4694,6 +4829,9 @@ /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Использовать серверы предосталенные SimpleX Chat?"; +/* No comment provided by engineer. */ +"Use SOCKS proxy" = "Использовать SOCKS прокси"; + /* No comment provided by engineer. */ "Use the app while in the call." = "Используйте приложение во время звонка."; @@ -4703,6 +4841,9 @@ /* No comment provided by engineer. */ "User selection" = "Выбор пользователя"; +/* No comment provided by engineer. */ +"Username" = "Имя пользователя"; + /* No comment provided by engineer. */ "Using SimpleX Chat servers." = "Используются серверы, предоставленные SimpleX Chat."; @@ -5138,9 +5279,15 @@ /* No comment provided by engineer. */ "Your chat database is not encrypted - set passphrase to encrypt it." = "База данных НЕ зашифрована. Установите пароль, чтобы защитить Ваши данные."; +/* alert title */ +"Your chat preferences" = "Ваши настройки чата"; + /* No comment provided by engineer. */ "Your chat profiles" = "Ваши профили чата"; +/* No comment provided by engineer. */ +"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Соединение было перемещено на %@, но при смене профиля произошла неожиданная ошибка."; + /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "Ваш контакт отправил файл, размер которого превышает максимальный размер (%@)."; @@ -5150,6 +5297,9 @@ /* No comment provided by engineer. */ "Your contacts will remain connected." = "Ваши контакты сохранятся."; +/* No comment provided by engineer. */ +"Your credentials may be sent unencrypted." = "Ваши учетные данные могут быть отправлены в незашифрованном виде."; + /* No comment provided by engineer. */ "Your current chat database will be DELETED and REPLACED with the imported one." = "Текущие данные Вашего чата будет УДАЛЕНЫ и ЗАМЕНЕНЫ импортированными."; @@ -5174,6 +5324,9 @@ /* No comment provided by engineer. */ "Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Ваш профиль хранится на Вашем устройстве и отправляется только Вашим контактам. SimpleX серверы не могут получить доступ к Вашему профилю."; +/* alert message */ +"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Ваш профиль был изменен. Если вы сохраните его, обновленный профиль будет отправлен всем вашим контактам."; + /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Ваш профиль, контакты и доставленные сообщения хранятся на Вашем устройстве."; diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index 3359a8d616..c35d25f1b5 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -160,6 +160,9 @@ /* notification title */ "%@ wants to connect!" = "%@ bağlanmak istiyor!"; +/* format for date separator in chat */ +"%@, %@" = "%1$@,%2$@"; + /* No comment provided by engineer. */ "%@, %@ and %lld members" = "%@, %@ ve %lld üyeleri"; @@ -172,9 +175,24 @@ /* time interval */ "%d days" = "%d gün"; +/* forward confirmation reason */ +"%d file(s) are still being downloaded." = "%d dosyası(ları) hala indiriliyor."; + +/* forward confirmation reason */ +"%d file(s) failed to download." = "%d dosyası(ları) indirilemedi."; + +/* forward confirmation reason */ +"%d file(s) were deleted." = "%d dosyası(ları) silindi."; + +/* forward confirmation reason */ +"%d file(s) were not downloaded." = "%d dosyası(ları) indirilmedi."; + /* time interval */ "%d hours" = "%d saat"; +/* alert title */ +"%d messages not forwarded" = "%d mesajı iletilmeyedi"; + /* time interval */ "%d min" = "%d dakika"; @@ -334,6 +352,9 @@ /* No comment provided by engineer. */ "above, then choose:" = "yukarı çıkın, ardından seçin:"; +/* No comment provided by engineer. */ +"Accent" = "Ana renk"; + /* accept contact request via notification accept incoming call via notification swipe action */ @@ -352,6 +373,15 @@ /* call status */ "accepted call" = "kabul edilen arama"; +/* No comment provided by engineer. */ +"Acknowledged" = "Onaylandı"; + +/* No comment provided by engineer. */ +"Acknowledgement errors" = "Onay hataları"; + +/* No comment provided by engineer. */ +"Active connections" = "Aktif bağlantılar"; + /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Kişilerinizin başkalarıyla paylaşabilmesi için profilinize adres ekleyin. Profil güncellemesi kişilerinize gönderilecek."; @@ -376,6 +406,15 @@ /* No comment provided by engineer. */ "Add welcome message" = "Karşılama mesajı ekleyin"; +/* No comment provided by engineer. */ +"Additional accent" = "Ek ana renk"; + +/* No comment provided by engineer. */ +"Additional accent 2" = "Ek vurgu 2"; + +/* No comment provided by engineer. */ +"Additional secondary" = "Ek ikincil renk"; + /* No comment provided by engineer. */ "Address" = "Adres"; @@ -397,6 +436,9 @@ /* No comment provided by engineer. */ "Advanced network settings" = "Gelişmiş ağ ayarları"; +/* No comment provided by engineer. */ +"Advanced settings" = "Gelişmiş ayarlar"; + /* chat item text */ "agreeing encryption for %@…" = "%@ için şifreleme kabul ediliyor…"; @@ -412,6 +454,9 @@ /* No comment provided by engineer. */ "All data is erased when it is entered." = "Kullanıldığında bütün veriler silinir."; +/* No comment provided by engineer. */ +"All data is private to your device." = "Tüm veriler cihazınıza özeldir."; + /* No comment provided by engineer. */ "All group members will remain connected." = "Tüm grup üyeleri bağlı kalacaktır."; @@ -427,6 +472,9 @@ /* No comment provided by engineer. */ "All new messages from %@ will be hidden!" = "%@ 'den gelen bütün yeni mesajlar saklı olacak!"; +/* profile dropdown */ +"All profiles" = "Tüm Profiller"; + /* No comment provided by engineer. */ "All your contacts will remain connected." = "Konuştuğun kişilerin tümü bağlı kalacaktır."; @@ -442,6 +490,9 @@ /* No comment provided by engineer. */ "Allow calls only if your contact allows them." = "Yalnızca irtibat kişiniz izin veriyorsa aramalara izin verin."; +/* No comment provided by engineer. */ +"Allow calls?" = "Aramalara izin verilsin mi ?"; + /* No comment provided by engineer. */ "Allow disappearing messages only if your contact allows it to you." = "Eğer kişide izin verirse kaybolan mesajlara izin ver."; @@ -463,6 +514,9 @@ /* No comment provided by engineer. */ "Allow sending disappearing messages." = "Kendiliğinden yok olan mesajlar göndermeye izin ver."; +/* No comment provided by engineer. */ +"Allow sharing" = "Paylaşıma izin ver"; + /* No comment provided by engineer. */ "Allow to irreversibly delete sent messages. (24 hours)" = "Gönderilen mesajların kalıcı olarak silinmesine izin ver. (24 saat içinde)"; @@ -541,6 +595,9 @@ /* No comment provided by engineer. */ "App passcode is replaced with self-destruct passcode." = "Uygulama parolası kendi kendini imha eden parolayla değiştirildi."; +/* No comment provided by engineer. */ +"App session" = "Uygulama oturumu"; + /* No comment provided by engineer. */ "App version" = "Uygulama sürümü"; @@ -553,15 +610,27 @@ /* No comment provided by engineer. */ "Apply" = "Uygula"; +/* No comment provided by engineer. */ +"Apply to" = "Şuna uygula"; + /* No comment provided by engineer. */ "Archive and upload" = "Arşivle ve yükle"; +/* No comment provided by engineer. */ +"Archive contacts to chat later." = "Daha sonra görüşmek için kişileri arşivleyin."; + +/* No comment provided by engineer. */ +"Archived contacts" = "Arşivli kişiler"; + /* No comment provided by engineer. */ "Archiving database" = "Veritabanı arşivleniyor"; /* No comment provided by engineer. */ "Attach" = "Ekle"; +/* No comment provided by engineer. */ +"attempts" = "denemeler"; + /* No comment provided by engineer. */ "Audio & video calls" = "Sesli & görüntülü aramalar"; @@ -601,9 +670,15 @@ /* No comment provided by engineer. */ "Auto-accept images" = "Fotoğrafları otomatik kabul et"; +/* alert title */ +"Auto-accept settings" = "Ayarları otomatik olarak kabul et"; + /* No comment provided by engineer. */ "Back" = "Geri"; +/* No comment provided by engineer. */ +"Background" = "Arka plan"; + /* No comment provided by engineer. */ "Bad desktop address" = "Kötü bilgisayar adresi"; @@ -625,6 +700,12 @@ /* No comment provided by engineer. */ "Better messages" = "Daha iyi mesajlar"; +/* No comment provided by engineer. */ +"Better networking" = "Daha iyi ağ oluşturma"; + +/* No comment provided by engineer. */ +"Black" = "Siyah"; + /* No comment provided by engineer. */ "Block" = "Engelle"; @@ -655,6 +736,12 @@ /* No comment provided by engineer. */ "Blocked by admin" = "Yönetici tarafından engellendi"; +/* No comment provided by engineer. */ +"Blur for better privacy." = "Daha iyi gizlilik için bulanıklaştır."; + +/* No comment provided by engineer. */ +"Blur media" = "Medyayı bulanıklaştır"; + /* No comment provided by engineer. */ "bold" = "kalın"; @@ -679,6 +766,9 @@ /* 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)." = "Sohbet profiline göre (varsayılan) veya [bağlantıya göre](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; +/* No comment provided by engineer. */ +"call" = "Ara"; + /* No comment provided by engineer. */ "Call already ended!" = "Arama çoktan bitti!"; @@ -694,15 +784,27 @@ /* No comment provided by engineer. */ "Calls" = "Aramalar"; +/* No comment provided by engineer. */ +"Calls prohibited!" = "Aramalara izin verilmiyor!"; + /* No comment provided by engineer. */ "Camera not available" = "Kamera mevcut değil"; +/* No comment provided by engineer. */ +"Can't call contact" = "Kişi aranamıyor"; + +/* No comment provided by engineer. */ +"Can't call member" = "Üye aranamaz"; + /* No comment provided by engineer. */ "Can't invite contact!" = "Kişi davet edilemiyor!"; /* No comment provided by engineer. */ "Can't invite contacts!" = "Kişiler davet edilemiyor!"; +/* No comment provided by engineer. */ +"Can't message member" = "Üyeye mesaj gönderilemiyor"; + /* alert button */ "Cancel" = "İptal et"; @@ -715,6 +817,9 @@ /* No comment provided by engineer. */ "Cannot access keychain to save database password" = "Veritabanı şifresini kaydetmek için Anahtar Zinciri'ne erişilemiyor"; +/* No comment provided by engineer. */ +"Cannot forward message" = "Mesaj iletilemiyor"; + /* alert title */ "Cannot receive file" = "Dosya alınamıyor"; @@ -773,6 +878,9 @@ /* No comment provided by engineer. */ "Chat archive" = "Sohbet arşivi"; +/* No comment provided by engineer. */ +"Chat colors" = "Sohbet renkleri"; + /* No comment provided by engineer. */ "Chat console" = "Sohbet konsolu"; @@ -782,6 +890,9 @@ /* No comment provided by engineer. */ "Chat database deleted" = "Sohbet veritabanı silindi"; +/* No comment provided by engineer. */ +"Chat database exported" = "Veritabanı dışa aktarıldı"; + /* No comment provided by engineer. */ "Chat database imported" = "Sohbet veritabanı içe aktarıldı"; @@ -794,15 +905,24 @@ /* No comment provided by engineer. */ "Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Sohbet durduruldu. Bu veritabanını zaten başka bir cihazda kullandıysanız, sohbete başlamadan önce onu geri aktarmalısınız."; +/* No comment provided by engineer. */ +"Chat list" = "Sohbet listesi"; + /* No comment provided by engineer. */ "Chat migrated!" = "Sohbet taşındı!"; /* No comment provided by engineer. */ "Chat preferences" = "Sohbet tercihleri"; +/* alert message */ +"Chat preferences were changed." = "Sohbet tercihleri değiştirildi."; + /* No comment provided by engineer. */ "Chat profile" = "Kullanıcı profili"; +/* No comment provided by engineer. */ +"Chat theme" = "Sohbet teması"; + /* No comment provided by engineer. */ "Chats" = "Sohbetler"; @@ -821,6 +941,15 @@ /* No comment provided by engineer. */ "Choose from library" = "Kütüphaneden seç"; +/* No comment provided by engineer. */ +"Chunks deleted" = "Parçalar silindi"; + +/* No comment provided by engineer. */ +"Chunks downloaded" = "Parçalar indirildi"; + +/* No comment provided by engineer. */ +"Chunks uploaded" = "Parçalar yüklendi"; + /* swipe action */ "Clear" = "Temizle"; @@ -836,6 +965,12 @@ /* No comment provided by engineer. */ "Clear verification" = "Doğrulamayı temizle"; +/* No comment provided by engineer. */ +"Color chats with the new themes." = "Yeni temalarla renkli sohbetler."; + +/* No comment provided by engineer. */ +"Color mode" = "Renk modu"; + /* No comment provided by engineer. */ "colored" = "renklendirilmiş"; @@ -848,12 +983,21 @@ /* No comment provided by engineer. */ "complete" = "tamamlandı"; +/* No comment provided by engineer. */ +"Completed" = "Tamamlandı"; + /* No comment provided by engineer. */ "Configure ICE servers" = "ICE sunucularını ayarla"; +/* No comment provided by engineer. */ +"Configured %@ servers" = "Yapılandırılmış %@ sunucuları"; + /* No comment provided by engineer. */ "Confirm" = "Onayla"; +/* No comment provided by engineer. */ +"Confirm contact deletion?" = "Kişiyi silmek istediğinizden emin misiniz ?"; + /* No comment provided by engineer. */ "Confirm database upgrades" = "Veritabanı geliştirmelerini onayla"; @@ -893,6 +1037,9 @@ /* No comment provided by engineer. */ "connect to SimpleX Chat developers." = "SimpleX Chat geliştiricilerine bağlan."; +/* No comment provided by engineer. */ +"Connect to your friends faster." = "Arkadaşlarınıza daha hızlı bağlanın."; + /* No comment provided by engineer. */ "Connect to yourself?" = "Kendine mi bağlanacaksın?"; @@ -917,6 +1064,9 @@ /* No comment provided by engineer. */ "connected" = "bağlanıldı"; +/* No comment provided by engineer. */ +"Connected" = "Bağlandı"; + /* No comment provided by engineer. */ "Connected desktop" = "Bilgisayara bağlandı"; @@ -924,11 +1074,17 @@ "connected directly" = "doğrudan bağlandı"; /* No comment provided by engineer. */ -"Connected to desktop" = "Bilgisayara bağlanıldı"; +"Connected servers" = "Bağlı sunucular"; + +/* No comment provided by engineer. */ +"Connected to desktop" = "Masaüstüne bağlandı"; /* No comment provided by engineer. */ "connecting" = "bağlanılıyor"; +/* No comment provided by engineer. */ +"Connecting" = "Bağlanıyor"; + /* No comment provided by engineer. */ "connecting (accepted)" = "bağlanılıyor (onaylandı)"; @@ -950,6 +1106,9 @@ /* No comment provided by engineer. */ "Connecting server… (error: %@)" = "Sunucuya bağlanıyor…(hata:%@)"; +/* No comment provided by engineer. */ +"Connecting to contact, please wait or check later!" = "Kişiye bağlanılıyor, lütfen bekleyin ya da daha sonra kontrol edin!"; + /* No comment provided by engineer. */ "Connecting to desktop" = "Bilgisayara bağlanıyor"; @@ -959,6 +1118,9 @@ /* No comment provided by engineer. */ "Connection" = "Bağlantı"; +/* No comment provided by engineer. */ +"Connection and servers status." = "Bağlantı ve sunucuların durumu."; + /* No comment provided by engineer. */ "Connection error" = "Bağlantı hatası"; @@ -968,6 +1130,9 @@ /* chat list item title (it should not be shown */ "connection established" = "bağlantı kuruldu"; +/* No comment provided by engineer. */ +"Connection notifications" = "Bağlantı bildirimleri"; + /* No comment provided by engineer. */ "Connection request sent!" = "Bağlantı daveti gönderildi!"; @@ -977,9 +1142,15 @@ /* No comment provided by engineer. */ "Connection timeout" = "Bağlantı süresi geçmiş"; +/* No comment provided by engineer. */ +"Connection with desktop stopped" = "Masaüstü ile bağlantı durduruldu"; + /* connection information */ "connection:%@" = "bağlantı:%@"; +/* No comment provided by engineer. */ +"Connections" = "Bağlantılar"; + /* profile update event chat item */ "contact %@ changed to %@" = "%1$@ kişisi %2$@ olarak değişti"; @@ -989,6 +1160,9 @@ /* No comment provided by engineer. */ "Contact already exists" = "Kişi zaten mevcut"; +/* No comment provided by engineer. */ +"Contact deleted!" = "Kişiler silindi!"; + /* No comment provided by engineer. */ "contact has e2e encryption" = "kişi uçtan uca şifrelemeye sahiptir"; @@ -1001,12 +1175,18 @@ /* notification */ "Contact is connected" = "Kişi bağlandı"; +/* No comment provided by engineer. */ +"Contact is deleted." = "Kişi silindi."; + /* No comment provided by engineer. */ "Contact name" = "Kişi adı"; /* No comment provided by engineer. */ "Contact preferences" = "Kişi tercihleri"; +/* No comment provided by engineer. */ +"Contact will be deleted - this cannot be undone!" = "Kişiler silinecek - bu geri alınamaz !"; + /* No comment provided by engineer. */ "Contacts" = "Kişiler"; @@ -1016,12 +1196,21 @@ /* No comment provided by engineer. */ "Continue" = "Devam et"; +/* No comment provided by engineer. */ +"Conversation deleted!" = "Sohbet silindi!"; + /* No comment provided by engineer. */ "Copy" = "Kopyala"; +/* No comment provided by engineer. */ +"Copy error" = "Kopyalama hatası"; + /* No comment provided by engineer. */ "Core version: v%@" = "Çekirdek sürümü: v%@"; +/* No comment provided by engineer. */ +"Corner" = "Köşeleri yuvarlama"; + /* No comment provided by engineer. */ "Correct name to %@?" = "İsim %@ olarak düzeltilsin mi?"; @@ -1064,6 +1253,9 @@ /* No comment provided by engineer. */ "Create your profile" = "Profilini oluştur"; +/* No comment provided by engineer. */ +"Created" = "Yaratıldı"; + /* No comment provided by engineer. */ "Created at" = "Şurada oluşturuldu"; @@ -1088,6 +1280,9 @@ /* No comment provided by engineer. */ "Current passphrase…" = "Şu anki parola…"; +/* No comment provided by engineer. */ +"Current profile" = "Aktif profil"; + /* No comment provided by engineer. */ "Currently maximum supported file size is %@." = "Şu anki maksimum desteklenen dosya boyutu %@ kadardır."; @@ -1097,9 +1292,15 @@ /* No comment provided by engineer. */ "Custom time" = "Özel saat"; +/* No comment provided by engineer. */ +"Customize theme" = "Renk temalarını kişiselleştir"; + /* No comment provided by engineer. */ "Dark" = "Karanlık"; +/* No comment provided by engineer. */ +"Dark mode colors" = "Karanlık mod renkleri"; + /* No comment provided by engineer. */ "Database downgrade" = "Veritabanı sürüm düşürme"; @@ -1169,6 +1370,9 @@ /* message decrypt error item */ "Decryption error" = "Şifre çözme hatası"; +/* No comment provided by engineer. */ +"decryption errors" = "Şifre çözme hataları"; + /* pref value */ "default (%@)" = "varsayılan (%@)"; @@ -1182,6 +1386,9 @@ swipe action */ "Delete" = "Sil"; +/* No comment provided by engineer. */ +"Delete %lld messages of members?" = "Üyelerin %lld mesajları silinsin mi?"; + /* No comment provided by engineer. */ "Delete %lld messages?" = "%lld mesaj silinsin mi?"; @@ -1218,6 +1425,9 @@ /* No comment provided by engineer. */ "Delete contact" = "Kişiyi sil"; +/* No comment provided by engineer. */ +"Delete contact?" = "Kişiyi sil?"; + /* No comment provided by engineer. */ "Delete database" = "Veritabanını sil"; @@ -1281,12 +1491,21 @@ /* server test step */ "Delete queue" = "Sırayı sil"; +/* No comment provided by engineer. */ +"Delete up to 20 messages at once." = "Tek seferde en fazla 20 mesaj silin."; + /* No comment provided by engineer. */ "Delete user profile?" = "Kullanıcı profili silinsin mi?"; +/* No comment provided by engineer. */ +"Delete without notification" = "Bildirim göndermeden sil"; + /* deleted chat item */ "deleted" = "silindi"; +/* No comment provided by engineer. */ +"Deleted" = "Silindi"; + /* No comment provided by engineer. */ "Deleted at" = "de silindi"; @@ -1299,6 +1518,9 @@ /* rcv group event chat item */ "deleted group" = "silinmiş grup"; +/* No comment provided by engineer. */ +"Deletion errors" = "Silme hatası"; + /* No comment provided by engineer. */ "Delivery" = "Teslimat"; @@ -1320,12 +1542,27 @@ /* No comment provided by engineer. */ "Desktop devices" = "Bilgisayar cihazları"; +/* No comment provided by engineer. */ +"Destination server address of %@ is incompatible with forwarding server %@ settings." = "Hedef sunucu adresi %@, yönlendirme sunucusu %@ ayarlarıyla uyumlu değil."; + /* snd error text */ "Destination server error: %@" = "Hedef sunucu hatası: %@"; +/* No comment provided by engineer. */ +"Destination server version of %@ is incompatible with forwarding server %@." = "Hedef sunucu %@ sürümü, yönlendirme sunucusu %@ ile uyumlu değil."; + +/* No comment provided by engineer. */ +"Detailed statistics" = "Detaylı istatistikler"; + +/* No comment provided by engineer. */ +"Details" = "Detaylar"; + /* No comment provided by engineer. */ "Develop" = "Geliştir"; +/* No comment provided by engineer. */ +"Developer options" = "Geliştirici seçenekleri"; + /* No comment provided by engineer. */ "Developer tools" = "Geliştirici araçları"; @@ -1365,6 +1602,9 @@ /* No comment provided by engineer. */ "disabled" = "devre dışı"; +/* No comment provided by engineer. */ +"Disabled" = "Devre dışı"; + /* No comment provided by engineer. */ "Disappearing message" = "Kaybolan mesaj"; @@ -1404,6 +1644,9 @@ /* No comment provided by engineer. */ "Do NOT send messages directly, even if your or destination server does not support private routing." = "Sizin veya hedef sunucunun özel yönlendirmeyi desteklememesi durumunda bile mesajları doğrudan GÖNDERMEYİN."; +/* No comment provided by engineer. */ +"Do not use credentials with proxy." = "Kimlik bilgilerini proxy ile kullanmayın."; + /* No comment provided by engineer. */ "Do NOT use private routing." = "Gizli yönlendirmeyi KULLANMA."; @@ -1426,12 +1669,24 @@ chat item action */ "Download" = "İndir"; +/* No comment provided by engineer. */ +"Download errors" = "İndirme hataları"; + /* No comment provided by engineer. */ "Download failed" = "Yükleme başarısız oldu"; /* server test step */ "Download file" = "Dosya indir"; +/* alert action */ +"Download files" = "Dosyaları indirin"; + +/* No comment provided by engineer. */ +"Downloaded" = "İndirildi"; + +/* No comment provided by engineer. */ +"Downloaded files" = "Dosyalar İndirildi"; + /* No comment provided by engineer. */ "Downloading archive" = "Arşiv indiriliyor"; @@ -1444,6 +1699,9 @@ /* integrity error chat item */ "duplicate message" = "yinelenen mesaj"; +/* No comment provided by engineer. */ +"duplicates" = "Kopyalar"; + /* No comment provided by engineer. */ "Duration" = "Süre"; @@ -1501,6 +1759,9 @@ /* enabled status */ "enabled" = "etkin"; +/* No comment provided by engineer. */ +"Enabled" = "Etkin"; + /* No comment provided by engineer. */ "Enabled for" = "Şunlar için etkinleştirildi"; @@ -1636,12 +1897,21 @@ /* No comment provided by engineer. */ "Error changing address" = "Adres değiştirilirken hata oluştu"; +/* No comment provided by engineer. */ +"Error changing connection profile" = "Bağlantı profili değiştirilirken hata oluştu"; + /* No comment provided by engineer. */ "Error changing role" = "Rol değiştirilirken hata oluştu"; /* No comment provided by engineer. */ "Error changing setting" = "Ayar değiştirilirken hata oluştu"; +/* No comment provided by engineer. */ +"Error changing to incognito!" = "Gizli moduna geçerken hata oluştu!"; + +/* No comment provided by engineer. */ +"Error connecting to forwarding server %@. Please try later." = "Yönlendirme sunucusu %@'ya bağlanırken hata oluştu. Lütfen daha sonra deneyin."; + /* No comment provided by engineer. */ "Error creating address" = "Adres oluşturulurken hata oluştu"; @@ -1699,6 +1969,9 @@ /* No comment provided by engineer. */ "Error exporting chat database" = "Sohbet veritabanı dışa aktarılırken hata oluştu"; +/* No comment provided by engineer. */ +"Error exporting theme: %@" = "Tema dışa aktarılırken hata oluştu: %@"; + /* No comment provided by engineer. */ "Error importing chat database" = "Sohbet veritabanı içe aktarılırken hata oluştu"; @@ -1708,15 +1981,27 @@ /* No comment provided by engineer. */ "Error loading %@ servers" = "%@ sunucuları yüklenirken hata oluştu"; +/* No comment provided by engineer. */ +"Error migrating settings" = "Ayarlar taşınırken hata oluştu"; + /* No comment provided by engineer. */ "Error opening chat" = "Sohbeti açarken sorun oluştu"; /* alert title */ "Error receiving file" = "Dosya alınırken sorun oluştu"; +/* No comment provided by engineer. */ +"Error reconnecting server" = "Hata, sunucuya yeniden bağlanılıyor"; + +/* No comment provided by engineer. */ +"Error reconnecting servers" = "Hata sunuculara yeniden bağlanılıyor"; + /* No comment provided by engineer. */ "Error removing member" = "Kişiyi silerken sorun oluştu"; +/* No comment provided by engineer. */ +"Error resetting statistics" = "Hata istatistikler sıfırlanıyor"; + /* No comment provided by engineer. */ "Error saving %@ servers" = "%@ sunucuları kaydedilirken sorun oluştu"; @@ -1759,6 +2044,9 @@ /* No comment provided by engineer. */ "Error stopping chat" = "Sohbet durdurulurken hata oluştu"; +/* No comment provided by engineer. */ +"Error switching profile" = "Profil değiştirme sırasında hata oluştu"; + /* alertTitle */ "Error switching profile!" = "Profil değiştirilirken hata oluştu!"; @@ -1795,6 +2083,9 @@ /* No comment provided by engineer. */ "Error: URL is invalid" = "Hata: URL geçersiz"; +/* No comment provided by engineer. */ +"Errors" = "Hatalar"; + /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Konuşma sırasında devre dışı bırakılsa bile."; @@ -1807,12 +2098,18 @@ /* chat item action */ "Expand" = "Genişlet"; +/* No comment provided by engineer. */ +"expired" = "Süresi dolmuş"; + /* No comment provided by engineer. */ "Export database" = "Veritabanını dışarı aktar"; /* No comment provided by engineer. */ "Export error:" = "Dışarı çıkarma hatası:"; +/* No comment provided by engineer. */ +"Export theme" = "Temayı dışa aktar"; + /* No comment provided by engineer. */ "Exported database archive." = "Dışarı çıkarılmış veritabanı arşivi."; @@ -1834,6 +2131,24 @@ /* swipe action */ "Favorite" = "Favori"; +/* No comment provided by engineer. */ +"File error" = "Dosya hatası"; + +/* alert message */ +"File errors:\n%@" = "Dosya hataları:\n%@"; + +/* file error text */ +"File not found - most likely file was deleted or cancelled." = "Dosya bulunamadı - muhtemelen dosya silindi veya göderim iptal edildi."; + +/* file error text */ +"File server error: %@" = "Dosya sunucusu hatası: %@"; + +/* No comment provided by engineer. */ +"File status" = "Dosya durumu"; + +/* copied message info */ +"File status: %@" = "Dosya durumu: %@"; + /* No comment provided by engineer. */ "File will be deleted from servers." = "Dosya sunuculardan silinecek."; @@ -1903,9 +2218,18 @@ /* chat item action */ "Forward" = "İlet"; +/* alert title */ +"Forward %d message(s)?" = "%d mesaj(lar)ı iletilsin mi?"; + /* No comment provided by engineer. */ "Forward and save messages" = "Mesajları ilet ve kaydet"; +/* alert action */ +"Forward messages" = "İletileri ilet"; + +/* alert message */ +"Forward messages without files?" = "Mesajlar dosyalar olmadan iletilsin mi ?"; + /* No comment provided by engineer. */ "forwarded" = "iletildi"; @@ -1915,6 +2239,18 @@ /* No comment provided by engineer. */ "Forwarded from" = "Şuradan iletildi"; +/* No comment provided by engineer. */ +"Forwarding %lld messages" = "%lld mesajlarını ilet"; + +/* No comment provided by engineer. */ +"Forwarding server %@ failed to connect to destination server %@. Please try later." = "Yönlendirme sunucusu %@, hedef sunucu %@'ya bağlanamadı. Lütfen daha sonra deneyin."; + +/* No comment provided by engineer. */ +"Forwarding server address is incompatible with network settings: %@." = "Yönlendirme sunucusu adresi ağ ayarlarıyla uyumsuz: %@."; + +/* No comment provided by engineer. */ +"Forwarding server version is incompatible with network settings: %@." = "Yönlendirme sunucusu sürümü ağ ayarlarıyla uyumsuz: %@."; + /* snd error text */ "Forwarding server: %@\nDestination server error: %@" = "Yönlendirme sunucusu: %1$@\nHedef sunucu hatası: %2$@"; @@ -1945,6 +2281,12 @@ /* No comment provided by engineer. */ "GIFs and stickers" = "GİFler ve çıkartmalar"; +/* message preview */ +"Good afternoon!" = "İyi öğlenler!"; + +/* message preview */ +"Good morning!" = "Günaydın!"; + /* No comment provided by engineer. */ "Group" = "Grup"; @@ -2122,6 +2464,9 @@ /* No comment provided by engineer. */ "Import failed" = "İçe aktarma başarısız oldu"; +/* No comment provided by engineer. */ +"Import theme" = "Temayı içe aktar"; + /* No comment provided by engineer. */ "Importing archive" = "Arşiv içe aktarılıyor"; @@ -2143,6 +2488,9 @@ /* No comment provided by engineer. */ "In-call sounds" = "Arama içi sesler"; +/* No comment provided by engineer. */ +"inactive" = "inaktif"; + /* No comment provided by engineer. */ "Incognito" = "Gizli"; @@ -2206,6 +2554,9 @@ /* No comment provided by engineer. */ "Interface" = "Arayüz"; +/* No comment provided by engineer. */ +"Interface colors" = "Arayüz renkleri"; + /* invalid chat data */ "invalid chat" = "geçersi̇z sohbet"; @@ -2248,6 +2599,9 @@ /* group name */ "invitation to group %@" = "%@ grubuna davet"; +/* No comment provided by engineer. */ +"invite" = "davet"; + /* No comment provided by engineer. */ "Invite friends" = "Arkadaşları davet et"; @@ -2275,6 +2629,9 @@ /* No comment provided by engineer. */ "iOS Keychain will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving push notifications." = "iOS Anahtar Zinciri, uygulamayı yeniden başlattıktan veya parolayı değiştirdikten sonra parolayı güvenli bir şekilde saklamak için kullanılacaktır - anlık bildirimlerin alınmasına izin verecektir."; +/* No comment provided by engineer. */ +"IP address" = "IP adresi"; + /* No comment provided by engineer. */ "Irreversible message deletion" = "Geri dönülemeyen mesaj silimi"; @@ -2293,6 +2650,9 @@ /* No comment provided by engineer. */ "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." = "Şu durumlarda ortaya çıkabilir:\n1. Mesajların gönderici istemcide 2 gün sonra veya sunucuda 30 gün sonra süresi dolmuştur.\n2. Siz veya kişi eski veritabanı yedeği kullandığı için mesaj şifre çözme işlemi başarısız olmuştur.\n3. Bağlantı tehlikeye girmiştir."; +/* No comment provided by engineer. */ +"It protects your IP address and connections." = "IP adresinizi ve bağlantılarınızı korur."; + /* 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 (%@)." = "Bu bağlantı üzerinden zaten bağlanmışsınız gibi görünüyor. Eğer durum böyle değilse, bir hata oluştu (%@)."; @@ -2335,6 +2695,9 @@ /* No comment provided by engineer. */ "Keep" = "Tut"; +/* No comment provided by engineer. */ +"Keep conversation" = "Sohbeti sakla"; + /* No comment provided by engineer. */ "Keep the app open to use it from desktop" = "Bilgisayardan kullanmak için uygulamayı açık tut"; @@ -2446,6 +2809,12 @@ /* No comment provided by engineer. */ "Max 30 seconds, received instantly." = "Maksimum 30 saniye, anında alındı."; +/* No comment provided by engineer. */ +"Media & file servers" = "Medya ve dosya sunucuları"; + +/* blur media */ +"Medium" = "Orta"; + /* member role */ "member" = "üye"; @@ -2458,6 +2827,9 @@ /* rcv group event chat item */ "member connected" = "bağlanıldı"; +/* item status text */ +"Member inactive" = "Üye inaktif"; + /* No comment provided by engineer. */ "Member role will be changed to \"%@\". All group members will be notified." = "Üye rolü \"%@\" olarak değiştirilecektir. Ve tüm grup üyeleri bilgilendirilecektir."; @@ -2467,6 +2839,12 @@ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Üye gruptan çıkarılacaktır - bu geri alınamaz!"; +/* No comment provided by engineer. */ +"Menus" = "Menüler"; + +/* No comment provided by engineer. */ +"message" = "mesaj"; + /* item status text */ "Message delivery error" = "Mesaj gönderim hatası"; @@ -2479,6 +2857,12 @@ /* No comment provided by engineer. */ "Message draft" = "Mesaj taslağı"; +/* item status text */ +"Message forwarded" = "Mesaj iletildi"; + +/* item status description */ +"Message may be delivered later if member becomes active." = "Kullanıcı aktif olursa mesaj iletilebilir."; + /* No comment provided by engineer. */ "Message queue info" = "Mesaj kuyruğu bilgisi"; @@ -2494,9 +2878,24 @@ /* notification */ "message received" = "mesaj alındı"; +/* No comment provided by engineer. */ +"Message reception" = "Mesaj alındısı"; + +/* No comment provided by engineer. */ +"Message servers" = "Mesaj sunucuları"; + +/* No comment provided by engineer. */ +"Message shape" = "Mesaj şekli"; + /* No comment provided by engineer. */ "Message source remains private." = "Mesaj kaynağı gizli kalır."; +/* No comment provided by engineer. */ +"Message status" = "Mesaj durumu"; + +/* copied message info */ +"Message status: %@" = "Mesaj durumu: %@"; + /* No comment provided by engineer. */ "Message text" = "Mesaj yazısı"; @@ -2512,6 +2911,15 @@ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "%@ den gelen mesajlar gösterilecektir!"; +/* No comment provided by engineer. */ +"Messages received" = "Mesajlar alındı"; + +/* No comment provided by engineer. */ +"Messages sent" = "Mesajlar gönderildi"; + +/* alert message */ +"Messages were deleted after you selected them." = "Mesajlar siz seçtikten sonra silindi."; + /* No comment provided by engineer. */ "Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Mesajlar, dosyalar ve aramalar **uçtan uca şifreleme** ile mükemmel ileri gizlilik, inkar ve izinsiz giriş kurtarma ile korunur."; @@ -2590,6 +2998,9 @@ /* No comment provided by engineer. */ "Multiple chat profiles" = "Çoklu sohbet profili"; +/* No comment provided by engineer. */ +"mute" = "Sessiz"; + /* swipe action */ "Mute" = "Sustur"; @@ -2623,6 +3034,9 @@ /* No comment provided by engineer. */ "New chat" = "Yeni sohbet"; +/* No comment provided by engineer. */ +"New chat experience 🎉" = "Yeni bir sohbet deneyimi 🎉"; + /* notification */ "New contact request" = "Yeni bağlantı isteği"; @@ -2641,6 +3055,9 @@ /* No comment provided by engineer. */ "New in %@" = "%@ da yeni"; +/* No comment provided by engineer. */ +"New media options" = "Yeni medya seçenekleri"; + /* No comment provided by engineer. */ "New member role" = "Yeni üye rolü"; @@ -2656,6 +3073,12 @@ /* No comment provided by engineer. */ "New passphrase…" = "Yeni parola…"; +/* No comment provided by engineer. */ +"New SOCKS credentials will be used every time you start the app." = "Uygulamayı her başlattığınızda yeni SOCKS kimlik bilgileri kullanılacaktır."; + +/* No comment provided by engineer. */ +"New SOCKS credentials will be used for each server." = "Her sunucu için yeni SOCKS kimlik bilgileri kullanılacaktır."; + /* pref value */ "no" = "hayır"; @@ -2677,6 +3100,9 @@ /* No comment provided by engineer. */ "No device token!" = "Cihaz tokeni yok!"; +/* item status description */ +"No direct connection yet, message is forwarded by admin." = "Henüz direkt bağlantı yok mesaj admin tarafından yönlendirildi."; + /* No comment provided by engineer. */ "no e2e encryption" = "uçtan uca şifreleme yok"; @@ -2689,9 +3115,18 @@ /* No comment provided by engineer. */ "No history" = "Geçmiş yok"; +/* No comment provided by engineer. */ +"No info, try to reload" = "Bilgi yok, yenilemeyi deneyin"; + /* No comment provided by engineer. */ "No network connection" = "Ağ bağlantısı yok"; +/* No comment provided by engineer. */ +"No permission to record speech" = "Konuşma kaydetme izni yok"; + +/* No comment provided by engineer. */ +"No permission to record video" = "Video kaydı için izin yok"; + /* No comment provided by engineer. */ "No permission to record voice message" = "Sesli mesaj kaydetmek için izin yok"; @@ -2704,6 +3139,12 @@ /* No comment provided by engineer. */ "Not compatible!" = "Uyumlu değil!"; +/* No comment provided by engineer. */ +"Nothing selected" = "Hiçbir şey seçilmedi"; + +/* alert title */ +"Nothing to forward!" = "Yönlendirilecek bir şey yok!"; + /* No comment provided by engineer. */ "Notifications" = "Bildirimler"; @@ -2760,6 +3201,9 @@ /* No comment provided by engineer. */ "Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Yalnızca istemci cihazlar kullanıcı profillerini, kişileri, grupları ve **2 katmanlı uçtan uca şifreleme** ile gönderilen mesajları depolar."; +/* No comment provided by engineer. */ +"Only delete conversation" = "Sadece sohbeti sil"; + /* No comment provided by engineer. */ "Only group owners can change group preferences." = "Grup tercihlerini yalnızca grup sahipleri değiştirebilir."; @@ -2814,6 +3258,9 @@ /* authentication reason */ "Open migration to another device" = "Başka bir cihaza açık geçiş"; +/* No comment provided by engineer. */ +"Open server settings" = "Sunucu ayarlarını aç"; + /* No comment provided by engineer. */ "Open Settings" = "Ayarları aç"; @@ -2838,9 +3285,21 @@ /* No comment provided by engineer. */ "Or show this code" = "Veya bu kodu göster"; +/* No comment provided by engineer. */ +"other" = "diğer"; + /* No comment provided by engineer. */ "Other" = "Diğer"; +/* No comment provided by engineer. */ +"Other %@ servers" = "Diğer %@ sunucuları"; + +/* No comment provided by engineer. */ +"other errors" = "diğer hatalar"; + +/* alert message */ +"Other file errors:\n%@" = "Diğer dosya hataları:\n%@"; + /* member role */ "owner" = "sahip"; @@ -2862,6 +3321,9 @@ /* No comment provided by engineer. */ "Passcode set!" = "Şifre ayarlandı!"; +/* No comment provided by engineer. */ +"Password" = "Şifre"; + /* No comment provided by engineer. */ "Password to show" = "Gösterilecek şifre"; @@ -2883,6 +3345,9 @@ /* No comment provided by engineer. */ "peer-to-peer" = "eşler arası"; +/* No comment provided by engineer. */ +"Pending" = "Bekleniyor"; + /* No comment provided by engineer. */ "People can connect to you only via the links you share." = "İnsanlar size yalnızca paylaştığınız bağlantılar üzerinden ulaşabilir."; @@ -2901,9 +3366,18 @@ /* No comment provided by engineer. */ "PING interval" = "PING aralığı"; +/* No comment provided by engineer. */ +"Play from the chat list." = "Sohbet listesinden oynat."; + +/* No comment provided by engineer. */ +"Please ask your contact to enable calls." = "Lütfen kişinizden çağrılara izin vermesini isteyin."; + /* No comment provided by engineer. */ "Please ask your contact to enable sending voice messages." = "Lütfen konuştuğunuz kişiden sesli mesaj göndermeyi etkinleştirmesini isteyin."; +/* No comment provided by engineer. */ +"Please check that mobile and desktop are connected to the same local network, and that desktop firewall allows the connection.\nPlease share any other issues with the developers." = "Lütfen telefonun ve bilgisayarın aynı lokal ağa bağlı olduğundan ve bilgisayar güvenlik duvarının bağlantıya izin verdiğinden emin olun.\nLütfen diğer herhangi bir sorunu geliştiricilerle paylaşın."; + /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "Lütfen doğru bağlantıyı kullandığınızı kontrol edin veya kişiden size başka bir bağlantı göndermesini isteyin."; @@ -2946,6 +3420,9 @@ /* No comment provided by engineer. */ "Polish interface" = "Lehçe arayüz"; +/* No comment provided by engineer. */ +"Port" = "Port"; + /* server test error */ "Possibly, certificate fingerprint in server address is incorrect" = "Muhtemelen, sunucu adresindeki parmakizi sertifikası doğru değil"; @@ -2961,6 +3438,9 @@ /* No comment provided by engineer. */ "Preview" = "Ön izleme"; +/* No comment provided by engineer. */ +"Previously connected servers" = "Önceden bağlanılmış sunucular"; + /* No comment provided by engineer. */ "Privacy & security" = "Gizlilik & güvenlik"; @@ -2982,6 +3462,9 @@ /* No comment provided by engineer. */ "Private routing" = "Gizli yönlendirme"; +/* No comment provided by engineer. */ +"Private routing error" = "Gizli yönlendirme hatası"; + /* No comment provided by engineer. */ "Profile and server connections" = "Profil ve sunucu bağlantıları"; @@ -2994,6 +3477,9 @@ /* No comment provided by engineer. */ "Profile password" = "Profil parolası"; +/* No comment provided by engineer. */ +"Profile theme" = "Profil teması"; + /* No comment provided by engineer. */ "Profile update will be sent to your contacts." = "Profil güncellemesi kişilerinize gönderilecektir."; @@ -3042,6 +3528,15 @@ /* No comment provided by engineer. */ "Protocol timeout per KB" = "KB başına protokol zaman aşımı"; +/* No comment provided by engineer. */ +"Proxied" = "Proxyli"; + +/* No comment provided by engineer. */ +"Proxied servers" = "Proxy sunucuları"; + +/* No comment provided by engineer. */ +"Proxy requires password" = "Proxy şifre gerektirir"; + /* No comment provided by engineer. */ "Push notifications" = "Anında bildirimler"; @@ -3057,6 +3552,9 @@ /* No comment provided by engineer. */ "Rate the app" = "Uygulamayı değerlendir"; +/* No comment provided by engineer. */ +"Reachable chat toolbar" = "Erişilebilir sohbet araç çubuğu"; + /* chat item menu */ "React…" = "Tepki ver…"; @@ -3082,7 +3580,10 @@ "Read more in our GitHub repository." = "Daha fazlasını GitHub depomuzdan oku."; /* No comment provided by engineer. */ -"Receipts are disabled" = "Gönderildi bilgisi devre dışı bırakıldı"; +"Receipts are disabled" = "Alıcılar devre dışı bırakıldı"; + +/* No comment provided by engineer. */ +"Receive errors" = "Alım sırasında hata"; /* No comment provided by engineer. */ "received answer…" = "alınan cevap…"; @@ -3102,6 +3603,15 @@ /* message info title */ "Received message" = "Mesaj alındı"; +/* No comment provided by engineer. */ +"Received messages" = "Alınan mesajlar"; + +/* No comment provided by engineer. */ +"Received reply" = "Alınan cevap"; + +/* No comment provided by engineer. */ +"Received total" = "Toplam alınan"; + /* No comment provided by engineer. */ "Receiving address will be changed to a different server. Address change will complete after sender comes online." = "Alıcı adresi farklı bir sunucuya değiştirilecektir. Gönderici çevrimiçi olduktan sonra adres değişikliği tamamlanacaktır."; @@ -3120,9 +3630,24 @@ /* No comment provided by engineer. */ "Recipients see updates as you type them." = "Alıcılar yazdığına göre güncellemeleri görecektir."; +/* No comment provided by engineer. */ +"Reconnect" = "Yeniden bağlan"; + /* No comment provided by engineer. */ "Reconnect all connected servers to force message delivery. It uses additional traffic." = "Mesaj teslimini zorlamak için bağlı tüm sunucuları yeniden bağlayın. Ek trafik kullanır."; +/* No comment provided by engineer. */ +"Reconnect all servers" = "Tüm sunuculara yeniden bağlan"; + +/* No comment provided by engineer. */ +"Reconnect all servers?" = "Tüm sunuculara yeniden bağlansın mı?"; + +/* No comment provided by engineer. */ +"Reconnect server to force message delivery. It uses additional traffic." = "Mesajı göndermeye zorlamak için sunucuya yeniden bağlan. Bu ekstra internet kullanır."; + +/* No comment provided by engineer. */ +"Reconnect server?" = "Sunucuya yeniden bağlansın mı ?"; + /* No comment provided by engineer. */ "Reconnect servers?" = "Sunuculara yeniden bağlanılsın mı?"; @@ -3157,6 +3682,12 @@ /* No comment provided by engineer. */ "Remove" = "Sil"; +/* No comment provided by engineer. */ +"Remove archive?" = "Arşiv kaldırılsın mı ?"; + +/* No comment provided by engineer. */ +"Remove image" = "Resmi kaldır"; + /* No comment provided by engineer. */ "Remove member" = "Kişiyi sil"; @@ -3214,12 +3745,27 @@ /* No comment provided by engineer. */ "Reset" = "Sıfırla"; +/* No comment provided by engineer. */ +"Reset all hints" = "Tüm ip uçlarını sıfırla"; + +/* No comment provided by engineer. */ +"Reset all statistics" = "Tüm istatistikleri sıfırla"; + +/* No comment provided by engineer. */ +"Reset all statistics?" = "Tüm istatistikler sıfırlansın mı ?"; + /* No comment provided by engineer. */ "Reset colors" = "Renkleri sıfırla"; +/* No comment provided by engineer. */ +"Reset to app theme" = "Uygulama temasına sıfırla"; + /* No comment provided by engineer. */ "Reset to defaults" = "Varsayılanlara sıfırla"; +/* No comment provided by engineer. */ +"Reset to user theme" = "Kullanıcı temasına sıfırla"; + /* No comment provided by engineer. */ "Restart the app to create a new chat profile" = "Yeni bir sohbet profili oluşturmak için uygulamayı yeniden başlatın"; @@ -3278,6 +3824,9 @@ /* No comment provided by engineer. */ "Save and notify group members" = "Kaydet ve grup üyelerine bildir"; +/* No comment provided by engineer. */ +"Save and reconnect" = "Kayıt et ve yeniden bağlan"; + /* No comment provided by engineer. */ "Save and update group profile" = "Kaydet ve grup profilini güncelle"; @@ -3308,6 +3857,9 @@ /* No comment provided by engineer. */ "Save welcome message?" = "Hoşgeldin mesajı kaydedilsin mi?"; +/* alert title */ +"Save your profile?" = "Profiliniz kaydedilsin mi?"; + /* No comment provided by engineer. */ "saved" = "kaydedildi"; @@ -3326,6 +3878,15 @@ /* No comment provided by engineer. */ "Saved WebRTC ICE servers will be removed" = "Kaydedilmiş WebRTC ICE sunucuları silinecek"; +/* No comment provided by engineer. */ +"Saving %lld messages" = "%lld mesajlarını kaydet"; + +/* No comment provided by engineer. */ +"Scale" = "Ölçeklendir"; + +/* No comment provided by engineer. */ +"Scan / Paste link" = "Tara / Bağlantı yapıştır"; + /* No comment provided by engineer. */ "Scan code" = "Kod okut"; @@ -3341,6 +3902,9 @@ /* No comment provided by engineer. */ "Scan server QR code" = "Sunucu QR kodu okut"; +/* No comment provided by engineer. */ +"search" = "ara"; + /* No comment provided by engineer. */ "Search" = "Ara"; @@ -3353,6 +3917,9 @@ /* network option */ "sec" = "sn"; +/* No comment provided by engineer. */ +"Secondary" = "İkincil renk"; + /* time unit */ "seconds" = "saniye"; @@ -3362,6 +3929,9 @@ /* server test step */ "Secure queue" = "Sırayı koru"; +/* No comment provided by engineer. */ +"Secured" = "Güvenli"; + /* No comment provided by engineer. */ "Security assessment" = "Güvenlik değerlendirmesi"; @@ -3374,6 +3944,15 @@ /* chat item action */ "Select" = "Seç"; +/* No comment provided by engineer. */ +"Select chat profile" = "Sohbet profili seç"; + +/* No comment provided by engineer. */ +"Selected %lld" = "Seçilen %lld"; + +/* No comment provided by engineer. */ +"Selected chat preferences prohibit this message." = "Seçilen sohbet tercihleri bu mesajı yasakladı."; + /* No comment provided by engineer. */ "Self-destruct" = "Kendi kendini imha"; @@ -3404,12 +3983,18 @@ /* No comment provided by engineer. */ "Send disappearing message" = "Kaybolan bir mesaj gönder"; +/* No comment provided by engineer. */ +"Send errors" = "Gönderme hataları"; + /* No comment provided by engineer. */ "Send link previews" = "Bağlantı ön gösterimleri gönder"; /* No comment provided by engineer. */ "Send live message" = "Canlı mesaj gönder"; +/* No comment provided by engineer. */ +"Send message to enable calls." = "Çağrıları aktif etmek için mesaj gönder."; + /* No comment provided by engineer. */ "Send messages directly when IP address is protected and your or destination server does not support private routing." = "IP adresi korumalı olduğunda ve sizin veya hedef sunucunun özel yönlendirmeyi desteklemediği durumlarda mesajları doğrudan gönderin."; @@ -3444,7 +4029,7 @@ "Sending delivery receipts will be enabled for all contacts in all visible chat profiles." = "Görüldü bilgisi, tüm görünür sohbet profillerindeki tüm kişiler için etkinleştirilecektir."; /* No comment provided by engineer. */ -"Sending delivery receipts will be enabled for all contacts." = "Gönderildi bilgisi tüm kişiler için etkinleştirilecektir."; +"Sending delivery receipts will be enabled for all contacts." = "Tüm kişiler için iletim bilgisi gönderme özelliği etkinleştirilecek."; /* No comment provided by engineer. */ "Sending file will be stopped." = "Dosya gönderimi durdurulacaktır."; @@ -3470,15 +4055,39 @@ /* copied message info */ "Sent at: %@" = "Şuradan gönderildi: %@"; +/* No comment provided by engineer. */ +"Sent directly" = "Direkt gönderildi"; + /* notification */ "Sent file event" = "Dosya etkinliği gönderildi"; /* message info title */ "Sent message" = "Mesaj gönderildi"; +/* No comment provided by engineer. */ +"Sent messages" = "Gönderilen mesajlar"; + /* No comment provided by engineer. */ "Sent messages will be deleted after set time." = "Gönderilen mesajlar ayarlanan süreden sonra silinecektir."; +/* No comment provided by engineer. */ +"Sent reply" = "Gönderilen cevap"; + +/* No comment provided by engineer. */ +"Sent total" = "Gönderilen tüm mesajların toplamı"; + +/* No comment provided by engineer. */ +"Sent via proxy" = "Bir proxy aracılığıyla gönderildi"; + +/* No comment provided by engineer. */ +"Server" = "Sunucu"; + +/* No comment provided by engineer. */ +"Server address" = "Sunucu adresi"; + +/* No comment provided by engineer. */ +"Server address is incompatible with network settings: %@." = "Sunucu adresi ağ ayarlarıyla uyumsuz: %@."; + /* srv error text. */ "Server address is incompatible with network settings." = "Sunucu adresi ağ ayarlarıyla uyumlu değil."; @@ -3494,12 +4103,24 @@ /* No comment provided by engineer. */ "Server test failed!" = "Sunucu testinde hata oluştu!"; +/* No comment provided by engineer. */ +"Server type" = "Sunucu tipi"; + /* srv error text */ "Server version is incompatible with network settings." = "Sunucu sürümü ağ ayarlarıyla uyumlu değil."; +/* No comment provided by engineer. */ +"Server version is incompatible with your app: %@." = "Sunucu sürümü uygulamanızla uyumlu değil: %@."; + /* No comment provided by engineer. */ "Servers" = "Sunucular"; +/* No comment provided by engineer. */ +"Servers info" = "Sunucu bilgileri"; + +/* No comment provided by engineer. */ +"Servers statistics will be reset - this cannot be undone!" = "Sunucu istatistikleri sıfırlanacaktır - bu geri alınamaz!"; + /* No comment provided by engineer. */ "Session code" = "Oturum kodu"; @@ -3509,6 +4130,9 @@ /* No comment provided by engineer. */ "Set contact name…" = "Kişi adı gir…"; +/* No comment provided by engineer. */ +"Set default theme" = "Varsayılan temaya ayarla"; + /* No comment provided by engineer. */ "Set group preferences" = "Grup tercihlerini ayarla"; @@ -3539,6 +4163,9 @@ /* No comment provided by engineer. */ "Settings" = "Ayarlar"; +/* alert message */ +"Settings were changed." = "Ayarlar değiştirildi."; + /* No comment provided by engineer. */ "Shape profile images" = "Profil resimlerini şekillendir"; @@ -3554,12 +4181,21 @@ /* No comment provided by engineer. */ "Share address with contacts?" = "Kişilerle adres paylaşılsın mı?"; +/* No comment provided by engineer. */ +"Share from other apps." = "Diğer uygulamalardan paylaşın."; + /* No comment provided by engineer. */ "Share link" = "Bağlantıyı paylaş"; +/* No comment provided by engineer. */ +"Share profile" = "Profil paylaş"; + /* No comment provided by engineer. */ "Share this 1-time invite link" = "Bu tek kullanımlık bağlantı davetini paylaş"; +/* No comment provided by engineer. */ +"Share to SimpleX" = "SimpleX ile paylaş"; + /* No comment provided by engineer. */ "Share with contacts" = "Kişilerle paylaş"; @@ -3578,6 +4214,9 @@ /* No comment provided by engineer. */ "Show message status" = "Mesaj durumunu göster"; +/* No comment provided by engineer. */ +"Show percentage" = "Yüzdeyi göster"; + /* No comment provided by engineer. */ "Show preview" = "Ön gösterimi göser"; @@ -3587,6 +4226,9 @@ /* No comment provided by engineer. */ "Show:" = "Göster:"; +/* No comment provided by engineer. */ +"SimpleX" = "SimpleX"; + /* No comment provided by engineer. */ "SimpleX address" = "SimpleX adresi"; @@ -3632,6 +4274,9 @@ /* No comment provided by engineer. */ "Simplified incognito mode" = "Basitleştirilmiş gizli mod"; +/* No comment provided by engineer. */ +"Size" = "Boyut"; + /* No comment provided by engineer. */ "Skip" = "Atla"; @@ -3641,9 +4286,27 @@ /* No comment provided by engineer. */ "Small groups (max 20)" = "Küçük gruplar (en fazla 20 kişi)"; +/* No comment provided by engineer. */ +"SMP server" = "SMP sunucusu"; + +/* No comment provided by engineer. */ +"SOCKS proxy" = "SOCKS vekili"; + +/* blur media */ +"Soft" = "Yumuşak"; + +/* No comment provided by engineer. */ +"Some app settings were not migrated." = "Bazı uygulama ayarları taşınamadı."; + +/* No comment provided by engineer. */ +"Some file(s) were not exported:" = "Bazı dosya(lar) dışa aktarılmadı:"; + /* No comment provided by engineer. */ "Some non-fatal errors occurred during import - you may see Chat console for more details." = "İçe aktarma sırasında bazı ölümcül olmayan hatalar oluştu - daha fazla ayrıntı için Sohbet konsoluna bakabilirsiniz."; +/* No comment provided by engineer. */ +"Some non-fatal errors occurred during import:" = "İçe aktarma sırasında bazı önemli olmayan hatalar oluştu:"; + /* notification title */ "Somebody" = "Biri"; @@ -3662,9 +4325,15 @@ /* No comment provided by engineer. */ "Start migration" = "Geçişi başlat"; +/* No comment provided by engineer. */ +"Starting from %@." = "%@'dan başlayarak."; + /* No comment provided by engineer. */ "starting…" = "başlatılıyor…"; +/* No comment provided by engineer. */ +"Statistics" = "İstatistikler"; + /* No comment provided by engineer. */ "Stop" = "Dur"; @@ -3704,9 +4373,21 @@ /* No comment provided by engineer. */ "strike" = "çizik"; +/* blur media */ +"Strong" = "Güçlü"; + /* No comment provided by engineer. */ "Submit" = "Gönder"; +/* No comment provided by engineer. */ +"Subscribed" = "Abone olundu"; + +/* No comment provided by engineer. */ +"Subscription errors" = "Abone olurken hata"; + +/* No comment provided by engineer. */ +"Subscriptions ignored" = "Abonelikler göz ardı edildi"; + /* No comment provided by engineer. */ "Support SimpleX Chat" = "SimpleX Chat'e destek ol"; @@ -3716,6 +4397,9 @@ /* No comment provided by engineer. */ "System authentication" = "Sistem yetkilendirilmesi"; +/* No comment provided by engineer. */ +"Tail" = "Konuşma balonu"; + /* No comment provided by engineer. */ "Take picture" = "Fotoğraf çek"; @@ -3740,6 +4424,9 @@ /* No comment provided by engineer. */ "Tap to scan" = "Taramak için tıkla"; +/* No comment provided by engineer. */ +"TCP connection" = "TCP bağlantısı"; + /* No comment provided by engineer. */ "TCP connection timeout" = "TCP bağlantı zaman aşımı"; @@ -3752,6 +4439,9 @@ /* No comment provided by engineer. */ "TCP_KEEPINTVL" = "TCP_TVLDEKAL"; +/* No comment provided by engineer. */ +"Temporary file error" = "Geçici dosya hatası"; + /* server test failure */ "Test failed at step %@." = "Test %@ adımında başarısız oldu."; @@ -3812,6 +4502,12 @@ /* No comment provided by engineer. */ "The message will be marked as moderated for all members." = "Mesaj tüm üyeler için yönetilmiş olarak işaretlenecektir."; +/* No comment provided by engineer. */ +"The messages will be deleted for all members." = "Mesajlar tüm üyeler için silinecektir."; + +/* No comment provided by engineer. */ +"The messages will be marked as moderated for all members." = "Mesajlar tüm üyeler için moderasyonlu olarak işaretlenecektir."; + /* No comment provided by engineer. */ "The next generation of private messaging" = "Gizli mesajlaşmanın yeni nesli"; @@ -3833,6 +4529,12 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Yapıştırdığın metin bir SimpleX bağlantısı değildir."; +/* No comment provided by engineer. */ +"The uploaded database archive will be permanently removed from the servers." = "Yüklenen veritabanı arşivi sunuculardan kalıcı olarak kaldırılacaktır."; + +/* No comment provided by engineer. */ +"Themes" = "Temalar"; + /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Bu ayarlar mevcut profiliniz **%@** içindir."; @@ -3875,9 +4577,15 @@ /* No comment provided by engineer. */ "This is your own SimpleX address!" = "Bu senin kendi SimpleX adresin!"; +/* No comment provided by engineer. */ +"This link was used with another mobile device, please create a new link on the desktop." = "Bu bağlantı başka bir mobil cihazda kullanıldı, lütfen masaüstünde yeni bir bağlantı oluşturun."; + /* No comment provided by engineer. */ "This setting applies to messages in your current chat profile **%@**." = "Bu ayar, geçerli sohbet profiliniz **%@** deki mesajlara uygulanır."; +/* No comment provided by engineer. */ +"Title" = "Başlık"; + /* No comment provided by engineer. */ "To ask any questions and to receive updates:" = "Soru sormak ve güncellemeleri almak için:"; @@ -3902,6 +4610,12 @@ /* No comment provided by engineer. */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "IP adresinizi korumak için,gizli yönlendirme mesajları iletmek için SMP sunucularınızı kullanır."; +/* No comment provided by engineer. */ +"To record speech please grant permission to use Microphone." = "Konuşmayı kaydetmek için lütfen Mikrofon kullanma izni verin."; + +/* No comment provided by engineer. */ +"To record video please grant permission to use Camera." = "Video kaydetmek için lütfen Kamera kullanım izni verin."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Sesli mesaj kaydetmek için lütfen Mikrofon kullanım izni verin."; @@ -3914,12 +4628,24 @@ /* No comment provided by engineer. */ "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Kişinizle uçtan uca şifrelemeyi doğrulamak için cihazlarınızdaki kodu karşılaştırın (veya tarayın)."; +/* No comment provided by engineer. */ +"Toggle chat list:" = "Sohbet listesini değiştir:"; + /* No comment provided by engineer. */ "Toggle incognito when connecting." = "Bağlanırken gizli moda geçiş yap."; +/* No comment provided by engineer. */ +"Toolbar opacity" = "Araç çubuğu opaklığı"; + +/* No comment provided by engineer. */ +"Total" = "Toplam"; + /* No comment provided by engineer. */ "Transport isolation" = "Taşıma izolasyonu"; +/* No comment provided by engineer. */ +"Transport sessions" = "Taşıma oturumları"; + /* No comment provided by engineer. */ "Trying to connect to the server used to receive messages from this contact (error: %@)." = "Bu kişiden mesaj almak için kullanılan sunucuya bağlanılmaya çalışılıyor (hata: %@)."; @@ -4013,6 +4739,9 @@ /* authentication reason */ "Unlock app" = "Uygulama kilidini aç"; +/* No comment provided by engineer. */ +"unmute" = "susturmayı kaldır"; + /* swipe action */ "Unmute" = "Susturmayı kaldır"; @@ -4034,6 +4763,9 @@ /* No comment provided by engineer. */ "Update network settings?" = "Bağlantı ayarları güncellensin mi?"; +/* No comment provided by engineer. */ +"Update settings?" = "Ayarları güncelleyelim mi?"; + /* rcv group event chat item */ "updated group profile" = "grup profili güncellendi"; @@ -4046,12 +4778,21 @@ /* No comment provided by engineer. */ "Upgrade and open chat" = "Yükselt ve sohbeti aç"; +/* No comment provided by engineer. */ +"Upload errors" = "Yükleme hataları"; + /* No comment provided by engineer. */ "Upload failed" = "Yükleme başarısız"; /* server test step */ "Upload file" = "Dosya yükle"; +/* No comment provided by engineer. */ +"Uploaded" = "Yüklendi"; + +/* No comment provided by engineer. */ +"Uploaded files" = "Yüklenen dosyalar"; + /* No comment provided by engineer. */ "Uploading archive" = "Arşiv yükleme"; @@ -4091,9 +4832,21 @@ /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "SimpleX Chat sunucuları kullanılsın mı?"; +/* No comment provided by engineer. */ +"Use SOCKS proxy" = "SOCKS vekili kullan"; + /* No comment provided by engineer. */ "Use the app while in the call." = "Görüşme sırasında uygulamayı kullanın."; +/* No comment provided by engineer. */ +"Use the app with one hand." = "Uygulamayı tek elle kullan."; + +/* No comment provided by engineer. */ +"User selection" = "Kullanıcı seçimi"; + +/* No comment provided by engineer. */ +"Username" = "Kullanıcı Adı"; + /* No comment provided by engineer. */ "Using SimpleX Chat servers." = "SimpleX Chat sunucuları kullanılıyor."; @@ -4142,6 +4895,9 @@ /* No comment provided by engineer. */ "Via secure quantum resistant protocol." = "Güvenli kuantum dirençli protokol ile."; +/* No comment provided by engineer. */ +"video" = "video"; + /* No comment provided by engineer. */ "Video call" = "Görüntülü arama"; @@ -4199,6 +4955,12 @@ /* No comment provided by engineer. */ "Waiting for video" = "Video bekleniyor"; +/* No comment provided by engineer. */ +"Wallpaper accent" = "Duvar kağıdı vurgusu"; + +/* No comment provided by engineer. */ +"Wallpaper background" = "Duvar kağıdı arkaplanı"; + /* No comment provided by engineer. */ "wants to connect to you!" = "bağlanmak istiyor!"; @@ -4271,9 +5033,15 @@ /* snd error text */ "Wrong key or unknown connection - most likely this connection is deleted." = "Yanlış anahtar veya bilinmeyen bağlantı - büyük olasılıkla bu bağlantı silinmiştir."; +/* file error text */ +"Wrong key or unknown file chunk address - most likely file is deleted." = "Yanlış anahtar veya bilinmeyen dosya yığın adresi - büyük olasılıkla dosya silinmiştir."; + /* No comment provided by engineer. */ "Wrong passphrase!" = "Yanlış parola!"; +/* No comment provided by engineer. */ +"XFTP server" = "XFTP sunucusu"; + /* pref value */ "yes" = "evet"; @@ -4325,6 +5093,9 @@ /* No comment provided by engineer. */ "You are invited to group" = "Gruba davet edildiniz"; +/* No comment provided by engineer. */ +"You are not connected to these servers. Private routing is used to deliver messages to them." = "Bu sunuculara bağlı değilsiniz. Mesajları onlara iletmek için özel yönlendirme kullanılır."; + /* No comment provided by engineer. */ "you are observer" = "gözlemcisiniz"; @@ -4334,6 +5105,9 @@ /* No comment provided by engineer. */ "You can accept calls from lock screen, without device and app authentication." = "Cihaz ve uygulama kimlik doğrulaması olmadan kilit ekranından çağrı kabul edebilirsiniz."; +/* No comment provided by engineer. */ +"You can change it in Appearance settings." = "Görünüm ayarlarından değiştirebilirsiniz."; + /* No comment provided by engineer. */ "You can create it later" = "Daha sonra oluşturabilirsiniz"; @@ -4355,6 +5129,9 @@ /* notification body */ "You can now chat with %@" = "Artık %@ adresine mesaj gönderebilirsin"; +/* No comment provided by engineer. */ +"You can send messages to %@ from Archived contacts." = "Arşivlenen kişilerden %@'ya mesaj gönderebilirsiniz."; + /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Kilit ekranı bildirim önizlemesini ayarlar üzerinden ayarlayabilirsiniz."; @@ -4370,6 +5147,9 @@ /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "Sohbeti uygulamada Ayarlar / Veritabanı üzerinden veya uygulamayı yeniden başlatarak başlatabilirsiniz"; +/* No comment provided by engineer. */ +"You can still view conversation with %@ in the list of chats." = "Sohbet listesinde %@ ile konuşmayı görüntülemeye devam edebilirsiniz."; + /* No comment provided by engineer. */ "You can turn on SimpleX Lock via Settings." = "SimpleX Kilidini Ayarlar üzerinden açabilirsiniz."; @@ -4421,9 +5201,18 @@ /* snd group event chat item */ "you left" = "terk ettiniz"; +/* No comment provided by engineer. */ +"You may migrate the exported database." = "Dışa aktarılan veritabanını taşıyabilirsiniz."; + +/* No comment provided by engineer. */ +"You may save the exported archive." = "Dışa aktarılan arşivi kaydedebilirsiniz."; + /* No comment provided by engineer. */ "You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts." = "Sohbet veritabanınızın en son sürümünü SADECE bir cihazda kullanmalısınız, aksi takdirde bazı kişilerden daha fazla mesaj alamayabilirsiniz."; +/* No comment provided by engineer. */ +"You need to allow your contact to call to be able to call them." = "Kendiniz arayabilmeniz için önce irtibat kişinizin sizi aramasına izin vermelisiniz."; + /* No comment provided by engineer. */ "You need to allow your contact to send voice messages to be able to send them." = "Sesli mesaj gönderebilmeniz için kişinizin de sesli mesaj göndermesine izin vermeniz gerekir."; @@ -4493,9 +5282,15 @@ /* No comment provided by engineer. */ "Your chat database is not encrypted - set passphrase to encrypt it." = "Sohbet veritabanınız şifrelenmemiş - şifrelemek için parola ayarlayın."; +/* alert title */ +"Your chat preferences" = "Sohbet tercihleriniz"; + /* No comment provided by engineer. */ "Your chat profiles" = "Sohbet profillerin"; +/* No comment provided by engineer. */ +"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Bağlantınız %@ adresine taşındı ancak sizi profile yönlendirirken beklenmedik bir hata oluştu."; + /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "Kişiniz şu anda desteklenen maksimum boyuttan (%@) daha büyük bir dosya gönderdi."; @@ -4505,6 +5300,9 @@ /* No comment provided by engineer. */ "Your contacts will remain connected." = "Kişileriniz bağlı kalacaktır."; +/* No comment provided by engineer. */ +"Your credentials may be sent unencrypted." = "Kimlik bilgileriniz şifrelenmeden gönderilebilir."; + /* No comment provided by engineer. */ "Your current chat database will be DELETED and REPLACED with the imported one." = "Mevcut sohbet veritabanınız SİLİNECEK ve içe aktarılan veritabanıyla DEĞİŞTİRİLECEKTİR."; @@ -4529,6 +5327,9 @@ /* No comment provided by engineer. */ "Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Profiliniz cihazınızda saklanır ve sadece kişilerinizle paylaşılır. SimpleX sunucuları profilinizi göremez."; +/* alert message */ +"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Profiliniz değiştirildi. Kaydederseniz, güncellenmiş profil tüm kişilerinize gönderilecektir."; + /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Profiliniz, kişileriniz ve gönderilmiş mesajlar cihazınızda saklanır."; diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index e0c50c6cd5..147d79002b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -2108,4 +2108,21 @@ شكل الرسالة ذيل ركن + جلسة التطبيق + الخادم + سيتم استخدام بيانات اعتماد SOCKS الجديدة في كل مرة تبدأ فيها تشغيل التطبيق. + سيتم استخدام بيانات اعتماد SOCKS الجديدة لكل خادم. + انقر فوق زر المعلومات الموجود بالقرب من حقل العنوان للسماح باستخدام الميكروفون. + افتح إعدادات Safari / مواقع الويب / الميكروفون، ثم اختر السماح لـ localhost. + لإجراء مكالمات، اسمح باستخدام الميكروفون. أنهِ المكالمة وحاول الاتصال مرة أخرى. + تجربة مستخدم أفضل + شكل الرسالة قابل للتخصيص. + تبديل الصوت والفيديو أثناء المكالمة. + حذف أو إشراف ما يصل إلى 200 رسالة. + حوّل ما يصل إلى 20 رسالة آن واحد. + مكالمات أفضل + تواريخ أفضل للرسائل. + أمان أفضل ✅ + بروتوكولات SimpleX تمت مراجعتها بواسطة Trail of Bits. + تبديل ملف تعريف الدردشة لدعوات لمرة واحدة. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index f99dd4fa60..912f3fb84e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -2192,4 +2192,21 @@ Form der Nachricht Sprechblase Abrundung Ecken + App-Sitzung + Für jeden Server werden neue SOCKS-Anmeldeinformationen genutzt + Server + Klicken Sie auf die Info-Schaltfläche neben dem Adressfeld, um die Verwendung des Mikrofons zu erlauben. + Um Anrufe durchzuführen, erlauben Sie die Nutzung Ihres Mikrofons. Beenden Sie den Anruf und versuchen Sie es erneut. + Öffnen Sie die Safari-Einstellungen / Webseiten / Mikrofon und wählen Sie dann \"Für Localhost erlauben\". + Verbesserte Anrufe + Anpassbares Format des Nachrichtenfelds + Bis zu 200 Nachrichten löschen oder moderieren + Bis zu 20 Nachrichten auf einmal weiterleiten + Die SimpleX-Protokolle wurden von Trail of Bits überprüft. + Während des Anrufs zwischen Audio und Video wechseln + Das Chat-Profil für Einmal-Einladungen wechseln + Jedes Mal wenn Sie die App starten, werden neue SOCKS-Anmeldeinformationen genutzt + Verbesserte Sicherheit ✅ + Verbesserte Nachrichten-Datumsinformation + Verbesserte Nutzer-Erfahrung \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml index baa85f07f4..cb4185ccb7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml @@ -227,4 +227,20 @@ διαγράφτηκε με συντονιστή %s φραγμένος + Σχετικά με τη διεύθυνση SimpleX + συμφωνία κρυπτογράφησης… + Όλες οι χρωματικές λειτουργίες + Η αλλαγή διεύθυνσης θα ακυρωθεί. Θα χρησιμοποιηθεί η παλιά διεύθυνση παραλαβής. + Ενεργές συνδέσεις + Προχωρημένες ρυθμίσεις + Πρόσθετη προφορά + Προσθήκη επαφής + Διακοπή αλλαγής διεύθυνσης + Προχωρημένες ρυθμίσεις + Οι διαχειριστές μπορούν να αποκλείσουν ένα μέλος για όλους. + Αναγνωρισμένο + παραπάνω, λοιπόν: + Προσθέστε τη διεύθυνση στο προφίλ σας, έτσι ώστε οι επαφές σας να μπορούν να τη μοιραστούν με άλλα άτομα. Το ενημέρωμένο προφίλ θα σταλεί στις επαφές σας. + διαχειριστές + Λάθη αναγνώρισης \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index 8156bb938c..4caa2dc86b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -366,7 +366,7 @@ en modo incógnito mediante enlace de dirección del contacto ¡Error al crear perfil! No se pudo cargar el chat - No se pudieron cargar los chats + Fallo en la carga de chats Enlace completo Error al eliminar contacto Error al unirte al grupo @@ -562,7 +562,7 @@ ha salido Salir del grupo Sólo los propietarios pueden modificar las preferencias del grupo. - Sólo datos del perfil + Eliminar sólo el perfil no k marcado eliminado @@ -579,7 +579,7 @@ Establecer una conexión privada Comprueba tu conexión de red con %1$s e inténtalo de nuevo. El remitente puede haber eliminado la solicitud de conexión. - Posiblemente la huella digital del certificado en la dirección del servidor es incorrecta + Posiblemente la huella del certificado en la dirección del servidor es incorrecta Responder Guardar contraseña en Keystore Error al restaurar base de datos @@ -668,7 +668,7 @@ Recibiendo vía Timeout protocolo seg - Datos del perfil y conexiones + Eliminar perfil y conexiones No se permiten mensajes temporales. Sólo tú puedes enviar mensajes de voz. Sólo tu contacto puede enviar mensajes de voz. @@ -820,7 +820,7 @@ Intentando conectar con el servidor para recibir mensajes de este contacto. formato de mensaje desconocido Intentando conectar con el servidor para recibir mensajes de este contacto (error: %1$s). - Prueba fallida en el paso %s. + Prueba no superada en el paso %s. Pulsa para iniciar chat nuevo Compartir mensaje… Compartir medios… @@ -832,8 +832,8 @@ Cambiar servidor de recepción Totalmente descentralizado. Visible sólo para los miembros. Para conectarte mediante enlace - ¡Error en prueba del servidor! - Algunos servidores no superaron la prueba: + ¡Prueba no superada! + Algunos servidores no han superado la prueba: Usar servidor Usar para conexiones nuevas Sistema @@ -1627,9 +1627,9 @@ Miembro pasado %1$s el miembro %1$s ha cambiado a %2$s dirección de contacto eliminada - imagen de perfil eliminada + ha eliminado la imagen del perfil nueva dirección de contacto - nueva imagen de perfil + tiene nueva imagen del perfil Llamada Llamada finalizada Videollamada @@ -1686,7 +1686,7 @@ Finalizar migración Atención: el archivo será eliminado.]]> Comprueba tu conexión a internet y vuelve a intentarlo - Para migrar confirma que recuerdas la frase de contraseña de la base de datos. + Para migrar la base de datos confirma que recuerdas la frase de contraseña. Error al verificar la frase de contraseña: Recuerda: usar la misma base de datos en dos dispositivos hará que falle el descifrado de mensajes como protección de seguridad.]]> Migrar desde otro dispositivo y escanea el código QR.]]> @@ -2107,8 +2107,25 @@ Comparte perfil Tu conexión ha sido trasladada a %s pero ha ocurrido un error inesperado al redirigirte al perfil. Sonido silenciado - Error al inicializar WebView. Asegúrese de que tiene WebView instalado y que su arquitectura soportada es arm64.\nError: %s - Formato de mensaje - Esquina + Error al iniciar WebView. Asegúrate de tener WebView instalado y que sea compatible con la arquitectura amr64.\nError: %s + Forma del mensaje + Esquinas Cola + Se usarán credenciales SOCKS nuevas cada vez que inicies la aplicación. + Sesión de aplicación + Abre la configuración de Safari / Sitios Web / Micrófono y a continuación selecciona Permitir para localhost. + Pulsa el botón info del campo dirección para permitir el uso del micrófono. + Para hacer llamadas, permite el uso del micrófono. Cuelga e intenta llamar de nuevo. + Se usarán credenciales SOCKS nuevas por cada servidor. + Servidor + Llamadas mejoradas + Mensajes mejor datados. + Experiencia de usuario mejorada + Forma personalizable de los mensajes. + Desplazamiento de hasta 20 mensajes. + Protocolos de SimpleX auditados por Trail of Bits. + Intercambia audio y video durante la llamada. + Seguridad mejorada ✅ + Borrar o moderar hasta 200 mensajes. + Cambia el perfil de chat para invitaciones de un solo uso. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index 63b898c0e5..b08cf57136 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -25,7 +25,7 @@ Elfogadás Elfogadás gombra fent, majd: - Elfogadás inkognítóban + Elfogadás inkognitóban Kapcsolatkérés elfogadása? Elfogadás Elfogadás @@ -70,7 +70,7 @@ " \nElérhető a v5.1-ben" Mindkét fél véglegesen törölheti az elküldött üzeneteket. (24 óra) - Javított csoportok + Továbbfejlesztett csoportok Minden üzenet törlésre kerül - ez a művelet nem vonható vissza! Az üzenetek CSAK az Ön számára törlődnek. Hívás befejeződött HÍVÁSOK @@ -138,7 +138,7 @@ Az elküldött üzenetek végleges törlése engedélyezve van az ismerősei számára. (24 óra) Mégse Az alkalmazás csak akkor tud értesítéseket fogadni, amikor meg van nyitva. A háttérszolgáltatás nem indul el - Jobb üzenetek + Továbbfejlesztett üzenetek A cím módosítása megszakad. A régi fogadási cím kerül felhasználásra. Engedélyezés Hibás számítógép cím @@ -338,7 +338,7 @@ Kép törlése Fájl létrehozása Tikos csoport létrehozása - Kiürítés + Elvetés Ismerős törlése? Kiürítés Cím létrehozása, hogy az emberek kapcsolatba léphessenek Önnel. @@ -495,7 +495,7 @@ %d üzenet megjelölve törlésre titkosítás újra egyeztetése engedélyezve Önmegsemmisítés engedélyezése - Olvasatlan és csillagozott csevegésekre való szűrés. + Olvasatlan és kedvenc csevegésekre való szűrés. A csevegések betöltése sikertelen A csoport már létezik! Francia kezelőfelület @@ -512,12 +512,12 @@ A csoport inaktív Gyors és nem kell várni, amíg a feladó online lesz! Hiba a csoporthoz való csatlakozáskor - Csillag + Kedvenc Csoport moderáció Fájl Csoporthivatkozás titkosítás-újraegyeztetés szükséges ehhez: %s - Hiba a profil váltásakor! + Hiba a profilváltáskor! Kísérleti funkciók Engedélyezés (felülírások megtartásával) Adja meg a helyes jelmondatot. @@ -624,7 +624,7 @@ Rejtett csevegési profilok Fájlok és médiatartalmak A kép mentve a „Galériába” - Elrejt + Elrejtés Azonnal A fájlok- és a médiatartalmak küldése le van tiltva! Profil elrejtése @@ -636,13 +636,13 @@ Nem kompatibilis adatbázis-verzió Hogyan működik a SimpleX Nem kompatibilis verzió - Elrejt + Elrejtés Bejövő videóhívás Téves jelkód Azonnali - Inkognitó csoportok + Inkognitócsoportok Hogyan - Elrejt + Elrejtés Kép Fejlesztett adatvédelem és biztonság Mellőzés @@ -657,7 +657,7 @@ Alkalmazás képernyőjének elrejtése a gyakran használt alkalmazások között. Javított kiszolgáló konfiguráció Előzmények - Rejtett profil jelszó + Rejtett profiljelszó Adatbázis importálása Importálás Azonnali értesítések @@ -668,7 +668,7 @@ Kép A fájlok- és a médiatartalmak le vannak tiltva ebben a csoportban. Hogyan működik - Elrejt: + Elrejtés: Hiba az ismerőssel történő kapcsolat létrehozásában ICE-kiszolgálók (soronként egy) beolvashatja a QR-kódot a videohívásban, vagy az ismerőse megoszthat egy meghívó-hivatkozást.]]> @@ -715,7 +715,7 @@ Kapcsolatok megtartása Tagok meghívása Üzenetreakciók - Egyszerre csak egy eszköz működhet + Egyszerre csak 1 eszköz működhet Csatlakozik a csoportjához? Nagy fájl! Helyi név @@ -773,9 +773,8 @@ elhagyta a csoportot Az üzenetek végleges törlése le van tiltva ebben a csevegésben. Max 40 másodperc, azonnal fogadható. - inkognitó a kapcsolattartási cím-hivatkozáson keresztül - Az onion-kiszolgálók szükségesek a kapcsolódáshoz. -\nMegjegyzés: .onion cím nélkül nem fog tudni kapcsolódni a kiszolgálókhoz. + inkognitó a kapcsolattartási címhivatkozáson keresztül + Onion-kiszolgálók szükségesek a kapcsolódáshoz.\nMegjegyzés: .onion cím nélkül nem fog tudni kapcsolódni a kiszolgálókhoz. Olasz kezelőfelület Nincsenek háttérhívások Üzenetek @@ -871,12 +870,10 @@ Meghívás a csoportba Zárolás miután Bejövő hanghívás - Kulcstartó hiba + Kulcstartóhiba Csatlakozik a csoporthoz? Az inkognitómód védi személyes adatait azáltal, hogy minden ismerőshöz új véletlenszerű profilt használ. - - stabilabb üzenetkézbesítés. -\n- valamivel jobb csoportok. -\n- és még sok más! + - stabilabb üzenetkézbesítés.\n- picit továbbfejlesztett csoportok.\n- és még sok más! Üzenetreakciók Nincs társított hordozható eszköz Hálózat állapota @@ -945,7 +942,7 @@ Válasz Ez az Ön egyszer használható hivatkozása! SimpleX Chat hívások - Új inkognító profil használata + Új inkognitóprofil használata Frissítse az alkalmazást, és lépjen kapcsolatba a fejlesztőkkel. SimpleX Hivatkozás előnézete @@ -1057,7 +1054,7 @@ Üdvözlőüzenet mp A profilfrissítés elküldésre került az ismerősök számára. - Egyszerűsített inkognító mód + Egyszerűsített inkognitómód Üdvözlőüzenet mentése? Új csevegési fiók létrehozásához indítsa újra az alkalmazást. Engedély megtagadva! @@ -1137,7 +1134,7 @@ Várakozás a képre Hangüzenetek Biztosan eltávolítja? - Biztonsági kód ellenőrzése + Biztonsági kód hitelesítése eltávolította Önt SimpleX-cím Megjelenítés: @@ -1198,7 +1195,7 @@ Alkalmazás képernyőjének védelme QR-kód megjelenítése videóhívás - Csillagozás megszüntetése + Kedvenc megszüntetése Kézbesítési jelentések küldése SimpleX-cím Koppintson a @@ -1218,7 +1215,7 @@ Mentés közvetítő-kiszolgálón keresztül Megosztás megállítása - eltávolította őt: %1$s + Ön eltávolította őt: %1$s Jelmondat mentése és a csevegés megnyitása Beállítások mentése? Nincsenek felhasználói azonosítók. @@ -1259,7 +1256,7 @@ SimpleX-zár bekapcsolva közvetlen üzenet küldése Beolvasás hordozható eszközről - Kapcsolatok ellenőrzése + Kapcsolatok hitelesítése Üzenet megosztása… másodperc A SimpleX-zár nincs bekapcsolva! @@ -1268,7 +1265,7 @@ Csevegési adatbázis eltávolította őt: %1$s Sikertelen kiszolgáló teszt! - Kapcsolat ellenőrzése + Kapcsolat hitelesítése Tudjon meg többet A fájl küldője visszavonta az átvitelt. Csevegési szolgáltatás megállítása? @@ -1295,7 +1292,7 @@ \nkövetkező generációja Hálózati beállítások megváltoztatása? Várakozás a hordozható eszköz társítására: - Kapcsolat biztonságának ellenőrzése + Biztonságos kapcsolat hitelesítése fájlok küldése egyelőre még nem támogatott cím megváltoztatva nála: %s fájlok fogadása egyelőre még nem támogatott @@ -1348,13 +1345,13 @@ A videó akkor érkezik meg, amikor a küldője befejezte annak feltöltését. egyszer használható hivatkozást osztott meg inkognitóban Már kapcsolódott ahhoz a kiszolgálóhoz, amely az adott ismerősétől érkező üzenetek fogadására szolgál. - Később engedélyezheti a Beállításokban + Később engedélyezheti a „Beállításokban” Akkor lesz kapcsolódva a csoporthoz, amikor a csoport tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később! különböző átköltöztetés az alkalmazásban/adatbázisban: %s / %s %1$s.]]> Profil felfedése Ez nem egy érvényes kapcsolattartási hivatkozás! - A végpontok közötti titkosítás ellenőrzéséhez hasonlítsa össze (vagy olvassa be a QR-kódot) az ismerőse eszközén lévő kóddal. + A végpontok közötti titkosítás hitelesítéséhez hasonlítsa össze (vagy olvassa be a QR-kódot) az ismerőse eszközén lévő kóddal. A csevegési adatbázis legfrissebb verzióját CSAK egy eszközön kell használnia, ellenkező esetben előfordulhat, hogy az üzeneteket nem fogja megkapni valamennyi ismerősétől. Ez a beállítás a jelenlegi csevegési profilban lévő üzenetekre érvényes Meghívást kapott a csoportba. Csatlakozzon, hogy kapcsolatba léphessen a csoport tagjaival. @@ -1370,7 +1367,7 @@ Egy olyan ismerősét próbálja meghívni, akivel inkognitó-profilt osztott meg abban a csoportban, amelyben a saját fő profilja van használatban %1$s nevű csoporthoz.]]> Amikor az alkalmazás fut - Inkognító profilt használ ehhez a csoporthoz - fő profilja megosztásának elkerülése érdekében a meghívók küldése le van tiltva + Inkognitóprofilt használ ehhez a csoporthoz - fő profilja megosztásának elkerülése érdekében a meghívók küldése le van tiltva Kapcsolat izolációs mód Akkor lesz kapcsolódva, ha a kapcsolatkérése elfogadásra kerül, várjon, vagy ellenőrizze később! A hangüzenetek küldése le van tiltva ebben a csoportban. @@ -1387,7 +1384,7 @@ Megoszthatja a címét egy hivatkozásként vagy egy QR-kódként – így bárki kapcsolódhat Önhöz. Létrehozás később A profilja az eszközén van tárolva és csak az ismerőseivel kerül megosztásra. A SimpleX-kiszolgálók nem láthatják a profilját. - %s szerepkörét megváltoztatta erre: %s + Ön megváltoztatta %s szerepkörét erre: %s Csoportmeghívó elutasítva Az adatvédelem érdekében, a más csevegési platformokon megszokott felhasználói azonosítók helyett, a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, minden egyes ismerőshöz egy-egy különbözőt. (a megosztáshoz az ismerősével) @@ -1417,27 +1414,27 @@ Nem veszíti el az ismerőseit, ha később törli a címét. A beállítások frissítése a kiszolgálókhoz való újra kapcsolódással jár. kapcsolatba akar lépni Önnel! - saját szerepköre erre változott: %s + saját szerepköre megváltozott erre: %s A csevegési szolgáltatás elindítható a „Beállítások / Adatbázis” menüben vagy az alkalmazás újraindításával. - Kód ellenőrzése a hordozható eszközön + Kód hitelesítése a hordozható eszközön Csatlakozott ehhez a csoporthoz. Kapcsolódás a meghívó csoporttaghoz. a SimpleX Chat fejlesztőivel, ahol bármiről kérdezhet és értesülhet az újdonságokról.]]> Nem kötelező üdvözlőüzenettel. Ismeretlen adatbázishiba: %s Elrejtheti vagy lenémíthatja a felhasználó profiljait - koppintson (vagy számítógép-alkalmazásban kattintson) hosszan a profilra a felugró menühöz. - Inkognító mód kapcsolódáskor. + Inkognitómód használata kapcsolódáskor. Megoszthat egy hivatkozást vagy QR-kódot - így bárki csatlakozhat a csoporthoz. Ha a csoport később törlésre kerül, akkor nem fogja elveszíteni annak tagjait. Csatlakozott ehhez a csoporthoz %1$s csoporthoz!]]> A hangüzenetek küldése le van tiltva ebben a csevegésben. Ön irányítja csevegését! - Kód ellenőrzése a számítógépen + Kód hitelesítése a számítógépen Az időzóna védelme érdekében a kép-/hangfájlok UTC-t használnak. A kapcsolatkérés elküldésre kerül ezen csoporttag számára. Inkognitó-profil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott. Már küldött egy kapcsolatkérést ezen a címen keresztül! Megoszthatja ezt a SimpleX-címet az ismerőseivel, hogy kapcsolatba léphessenek vele: %s. - Amikor az emberek kapcsolatot kérnek, ön elfogadhatja vagy elutasíthatja azokat. + Amikor az emberek kapcsolatot kérnek, Ön elfogadhatja vagy elutasíthatja azokat. Megjelenítendő üzenet beállítása az új tagok számára! Köszönet a felhasználóknak - hozzájárulás a Weblate-en! A kézbesítési jelentés küldése minden ismerőse számára engedélyezésre kerül. @@ -1523,7 +1520,7 @@ Alkalmazás jelkód Ismerős hozzáadása Koppintson a beolvasáshoz - Koppintson a hivatkozás beillesztéséhez + Koppintson ide a hivatkozás beillesztéséhez Ismerős hozzáadása: új meghívó-hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz.]]> Csoport létrehozása: új csoport létrehozásához.]]> A csevegés leállt. Ha már használta ezt az adatbázist egy másik eszközön, úgy visszaállítás szükséges a csevegés megkezdése előtt. @@ -1649,14 +1646,14 @@ Hiba a beállítások mentésekor Hiba az archívum letöltésekor Hiba az archívum feltöltésekor - Hiba a jelmondat ellenőrzésekor: + Hiba a jelmondat hitelesítésekor: Az exportált fájl nem létezik A fájl törlésre került, vagy érvénytelen hivatkozás %s letöltve Archívum importálása Feltöltés előkészítése - Az adatbázis jelmondatának ellenőrzése - Jelmondat ellenőrzése + Az adatbázis jelmondatának hitelesítése + Jelmondat hitelesítése Jelmondat beállítása Kép a képben hívások Biztonságosabb csoportok @@ -1756,7 +1753,7 @@ Profilképek Profilkép alakzat Négyzet, kör vagy bármi a kettő között. - Célkiszolgáló hiba: %1$s + Célkiszolgáló-hiba: %1$s Továbbító kiszolgáló: %1$s \nHiba: %2$s Hálózati problémák - az üzenet többszöri elküldési kísérlet után lejárt. @@ -1781,13 +1778,13 @@ Privát útválasztás Használjon privát útválasztást ismeretlen kiszolgálókkal. Mindig használjon privát útválasztást. - Üzenet útválasztási mód + Üzenet-útválasztási mód Közvetlen üzenetküldés, ha az IP-cím védett és az Ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. Közvetlen üzenetküldés, ha az Ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. Az IP-cím védelmének érdekében a privát útválasztás az SMP-kiszolgálókat használja az üzenetek kézbesítéséhez. - Üzenet útválasztási tartalék - PRIVÁT ÜZENET ÚTVÁLASZTÁS - Privát útválasztás használata ismeretlen kiszolgálókkal, ha az IP-cím nem védett. + Üzenet-útválasztási tartalék + PRIVÁT ÜZENET-ÚTVÁLASZTÁS + Használjon privát útválasztást ismeretlen kiszolgálókkal, ha az IP-cím nem védett. Ne küldjön üzeneteket közvetlenül, még akkor sem, ha az Ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. Tor vagy VPN nélkül az IP-címe látható lesz a fájlkiszolgálók számára. FÁJLOK @@ -1834,7 +1831,7 @@ Alkalmazás témájának visszaállítása Tegye egyedivé a csevegéseit! Új csevegési témák - Privát üzenet útválasztás 🚀 + Privát üzenet-útválasztás 🚀 Fájlok biztonságos fogadása Csökkentett akkumulátor-használattal. Hiba a WebView előkészítésekor. Frissítse rendszerét az új verzióra. Lépjen kapcsolatba a fejlesztőkkel. @@ -2033,7 +2030,7 @@ Folytatás Ellenőrízze a hálózatát Média- és fájlkiszolgálók - Legfeljebb 20 üzenet törlése egyszerre. + Legfeljebb 20 üzenet egyszerre való törlése. Védi az IP-címét és a kapcsolatait. Könnyen elérhető eszköztár Üzenetkiszolgálók @@ -2099,11 +2096,24 @@ Hang elnémítva Hiba a WebView előkészítésekor. Győződjön meg arról, hogy a WebView telepítve van-e, és támogatja-e az arm64 architektúrát. \nHiba: %s - Sarkos + Sarok Üzenetbuborék alakja - Nyúlványos + Farok Kiszolgáló Minden alkalommal, amikor elindítja az alkalmazást, új SOCKS-hitelesítő-adatokat fog használni. Alkalmazás munkamenete Minden egyes kiszolgálóhoz új SOCKS-hitelesítő-adatok legyenek használva. + Kattintson a címmező melletti info gombra a mikrofon használatának engedélyezéséhez. + Nyissa meg a Safari Beállítások / Weboldalak / Mikrofon menüt, majd válassza a helyi kiszolgálók engedélyezése lehetőséget. + Hívások kezdeményezéséhez engedélyezze a mikrofon használatát. Fejezze be a hívást, és próbálja meg a hívást újra. + Továbbfejlesztett hívásélmény + Továbbfejlesztett üzenetdátumok. + Továbbfejlesztett felhasználói élmény + Testreszabható üzenetbuborékok. + Legfeljebb 200 üzenet egyszerre való törlése, vagy moderálása. + Legfeljebb 20 üzenet egyszerre való továbbítása. + Hang/Videó váltása hívás közben. + Csevegési profilváltás az egyszer használható meghívókhoz. + Továbbfejlesztett biztonság ✅ + A SimpleX Chat biztonsága a Trail of Bits által lett újraauditálva. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml index 9f6fa799b7..9c256b4a4b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml @@ -262,4 +262,5 @@ Hanya kontak kamu yang dapat melakukan panggilan. Izinkan untuk mengirim file dan media. Semua kontak kamu akan tetap terhubung. + %1$d berkas telah dihapus. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index 20fa81df8b..c1b6a0808c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -2111,4 +2111,21 @@ Angolo Forma del messaggio Coda + Sessione dell\'app + Le nuove credenziali SOCKS verranno usate ogni volta che avvii l\'app. + Le nuove credenziali SOCKS verranno usate per ogni server. + Server + Apri le impostazioni di Safari / Siti web / Microfono, quindi scegli Consenti per localhost. + Clicca il pulsante info vicino al campo indirizzo per consentire l\'uso del microfono. + Per effettuare chiamate, consenti di usare il microfono. Termina la chiamata e cerca di richiamare. + Chiamate migliorate + Date dei messaggi migliorate. + Sicurezza migliorata ✅ + Esperienza utente migliorata + Forma dei messaggi personalizzabile. + Protocolli di SimpleX esaminati da Trail of Bits. + Cambia tra audio e video durante la chiamata. + Cambia profilo di chat per inviti una tantum. + Elimina o modera fino a 200 messaggi. + Inoltra fino a 20 messaggi alla volta. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index 5ea2fe0383..5b603bd84d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -2107,4 +2107,22 @@ Fout bij initialiseren van WebView. Zorg ervoor dat WebView geïnstalleerd is en de ondersteunde architectuur is arm64.\nFout: %s Hoek Berichtvorm + Appsessie + Elke keer dat u de app start, worden er nieuwe SOCKS-inloggegevens gebruikt. + Voor elke server worden nieuwe SOCKS-inloggegevens gebruikt. + Server + Staart + Klik op de infoknop naast het adresveld om het gebruik van de microfoon toe te staan. + Open Safari Instellingen / Websites / Microfoon en kies Toestaan voor localhost. + Als u wilt bellen, geeft u toestemming om uw microfoon te gebruiken. Beëindig het gesprek en probeer opnieuw te bellen. + Betere beveiliging ✅ + SimpleX-protocollen beoordeeld door Trail of Bits. + Betere datums voor berichten. + Betere gebruikerservaring + Aanpasbare berichtvorm. + Wissel tussen audio en video tijdens het gesprek. + Wijzig chatprofiel voor eenmalige uitnodigingen. + Maximaal 200 berichten verwijderen of modereren. + Stuur maximaal 20 berichten tegelijk door. + Betere gesprekken \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml index b686920309..8056422e5f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml @@ -2084,4 +2084,46 @@ %1$d plik(ów/i) nie udało się pobrać. Błąd zmiany profilu BAZA CZATU + %1$d błędów plików:\n%2$s + %1$d innych błędów plików. + Wiadomości zostały usunięte po wybraniu ich. + Nic do przekazania! + Pobierz + Zapisywanie %1$s wiadomości + Uwierzytelnianie proxy + Hasło + Błąd inicjalizacji WebView. Upewnij się, że WebView jest zainstalowany, a jego obsługiwana architektura to arm64.\nBłąd: %s + Wiadomości zostaną usunięte - nie można tego cofnąć! + Usunąć archiwum? + Róg + Kształt wiadomości + Sesja aplikacji + Nowe poświadczenia SOCKS będą używane przy każdym uruchomieniu aplikacji. + Dla każdego serwera zostaną użyte nowe poświadczenia SOCKS. + Kliknij przycisk informacji przy polu adresu, aby zezwolić na korzystanie z mikrofonu. + Otwórz Safari Ustawienia / Strony internetowe / Mikrofon, a następnie wybierz opcję Zezwalaj dla localhost. + Użyj różnych poświadczeń proxy dla każdego połączenia. + Użyj różnych poświadczeń proxy dla każdego profilu. + Użyj losowych poświadczeń + Nazwa użytkownika + Twoje poświadczenia mogą zostać wysłane niezaszyfrowane. + Dźwięk wyciszony + Wybierz profil czatu + Udostępnij profil + Twoje połączenie zostało przeniesione do %s, ale podczas przekierowania do profilu wystąpił nieoczekiwany błąd. + Tryb systemu + Przesłane archiwum bazy danych zostanie trwale usunięte z serwerów. + Serwer + Ogon + Aby wykonywać połączenia, zezwól na korzystanie z mikrofonu. Zakończ połączenie i spróbuj zadzwonić ponownie. + Lepsze bezpieczeństwo ✅ + Lepsze daty wiadomości. + Możliwość dostosowania kształtu wiadomości. + Lepsze połączenia + Lepsze doświadczenie użytkownika + Protokoły SimpleX sprawdzone przez Trail of Bits. + Przełączanie audio i wideo podczas połączenia. + Usuń lub moderuj do 200 wiadomości. + Przekazywanie do 20 wiadomości jednocześnie. + Przełącz profil czatu dla zaproszeń jednorazowych. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml index 2e9725913a..fe7a226aeb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -445,7 +445,7 @@ Эта строка не является ссылкой-приглашением! Открыть в приложении.]]> - входящий звонок… + звонок… пропущенный звонок отклоненный звонок принятый звонок @@ -2150,4 +2150,63 @@ Открыть из списка чатов. Сбросить все подсказки. Вы можете изменить это в настройках Интерфейса. + Пересылка %1$s сообщений + Сохранение %1$s сообщений + Убедитесь, что конфигурация прокси правильная. + Аутентификация прокси + Использовать случайные учетные данные + Режим системы + Ошибка пересылки сообщений + %1$d ошибок файлов:\n%2$s + %1$d других ошибок файлов. + Переслать %1$s сообщение(й)? + Переслать сообщения без файлов? + Сообщения были удалены после того, как вы их выбрали. + Нет сообщений, которые можно переслать! + %1$d файл(ов) загружаются. + %1$d файл(ов) не удалось загрузить. + %1$d файлов было удалено. + %1$d файлов не было загружено. + Загрузить + %1$s сообщений не переслано + Переслать сообщения… + Проверьте правильность ссылки SimpleX. + Неверная ссылка + БАЗА ДАННЫХ + Ошибка инициализации WebView. Убедитесь, что у вас установлен WebView и его поддерживаемая архитектура – arm64.\nОшибка: %s + Звук отключен + Сообщения будут удалены — это нельзя отменить! + Ошибка переключения профиля + Выберите профиль чата + Поделиться профилем + Соединение было перемещено на %s, но при смене профиля произошла неожиданная ошибка. + Угол + Сессия приложения + Новые учетные данные SOCKS будут использоваться при каждом запуске приложения. + Новые учетные данные SOCKS будут использоваться для каждого сервера. + Сервер + Форма сообщений + Хвост + Нажмите кнопку информации рядом с адресной строкой, чтобы разрешить микрофон. + Откройте Настройки Safari / Веб-сайты / Микрофон, затем выберите Разрешить для localhost. + Улучшенные звонки + Улучшенные даты сообщений. + Улучшенная безопасность ✅ + Улучшенный интерфейс + Настраиваемая форма сообщений. + Удаляйте или модерируйте до 200 сообщений. + Пересылайте до 20 сообщений за раз. + Переключайте звук и видео во время звонка. + Переключайте профиль чата для одноразовых приглашений. + Анализ безопасности протоколов SimpleX от Trail of Bits. + Чтобы совершать звонки, разрешите использовать микрофон. Завершите вызов и попробуйте позвонить снова. + Не использовать учетные данные с прокси. + Ошибка сохранения прокси + Пароль + Использовать разные учетные данные прокси для каждого соединения. + Использовать разные учетные данные прокси для каждого профиля. + Имя пользователя + Ваши учетные данные могут быть отправлены в незашифрованном виде. + Удалить архив? + Загруженный архив базы данных будет навсегда удален с серверов. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml index cb64695783..30afe21e50 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml @@ -22,7 +22,7 @@ Görünüm Ayarlarınız Sessize al - Sessizden çıkar + Susturmayı kaldır İptal Adres değişikliğini iptal et\? 30 saniye @@ -94,7 +94,7 @@ Tercihleri kaydet\? Profil parolasını kaydet Profil sadece konuştuğun kişilerle paylaşılır. - Gizli iletişimin gelecek kuşağı + Gizli iletişimin\ngelecek kuşağı Ses kapalı Doğrulama iptal edildi Yeniden başlat @@ -757,7 +757,7 @@ aramaya bağlanılıyor… Gönderildi bilgisi kapalı! Birkaç şey daha - Daha fazla pil kullanır! Arka plan hizmeti her zaman çalışır - mesajlar gelir gelmez bildirim gönderilir.]]> + Daha fazla pil kullanır! Uygulama her zaman arka planda çalışır - bildirimler anında gösterilir.]]> Çok yakında! Kişi ve tüm mesajlar silinecektir - bu geri alınamaz! Kişi henüz bağlanmadı! @@ -914,7 +914,7 @@ TAMAM Daha fazla Tek seferlik davet bağlantısı - Ağ ayarları + Gelişmiş ayarlar Siz uygulamayı yeniden başlatana kadar bildirimler çalışmayacaktır Kapalı Yeni görünen ad: @@ -989,7 +989,7 @@ Zaman dilimi, görsel/ses korumak için UTC kullan. Özel dosya adları Yeni bir sohbet başlatmak için - İnsanlar size sadece paylaştığınız bağlantılar üzerinden ulaşabilir. + Kimin bağlanabileceğine siz karar verirsiniz. Gizlilik yeniden tanımlanıyor GitHub repomuzda daha fazlasını okuyun. Periyodik @@ -1109,7 +1109,7 @@ Kişiyi ve mesajı göster Daha iyi gruplar Videonun kodu çözülemiyor. Lütfen farklı bir video deneyin veya geliştiricilerle iletişime geçin. - İçe aktarma sırasında bir takım hatlar oluştu - daha fazla detay için sohbet konsoluna bakabilirsiniz. + İçe aktarma sırasında bazı önemli olmayan hatalar oluştu: Geliştirici seçeneklerini göster %s bağlandı Onaylarsanız, mesajlaşma sunucuları IP adresinizi ve sağlayıcınızı - hangi sunuculara bağlandığınızı - görebilecektir. @@ -1777,7 +1777,7 @@ Sunucu sürümü ağ ayarlarıyla uyumlu değil. Yanlış anahtar veya bilinmeyen bağlantı - büyük olasılıkla bu bağlantı silinmiştir. Gizli yönlendirme - Bilinmeyen yönlendiriciler + Bilinmeyen sunucular Her zaman gizli yönlendirmeyi kullan. Gizli yönlendirmeyi KULLANMA. Mesaj yönlendirme modu @@ -1887,7 +1887,7 @@ Sohbet profili seç XFTP sunucuları yapılandırıldı Medya ve dosya sunucuları - Proxy ile bilgeleri kullanma + Kimlik bilgilerini proxy ile kullanmayın. Proxy kayıt edilirken hata oluştu. Proxy konfigürasyonunun doğru olduğundan emin olun. Şifre @@ -1919,7 +1919,7 @@ İndirme hataları Mesaj durumu Geçersiz link - Lütfen SimpleX linki doğru mu kontrol edin. + Lütfen SimpleX bağlantısının doğru olup olmadığını kontrol edin. Varış sunucusu ardesi (%1$s) yönlendirme sunucusu (%2$s) ile uyumsuz. Varış sunucusu sürümü (%1$s) yönlendirme sunucusu (%2$s) ile uyumsuz. Mesaj iletildi @@ -1941,7 +1941,7 @@ Yeni medya seçenekleri Daha iyi gizlilik için bulanıklaştır. Arkadaşlarınıza daha hızlı bağlanın - Aynı anda yirmiye kadar mesaj silin. + Tek seferde en fazla 20 mesaj silin. Sohbet listesinden oynat. Arşiv kaldırılsın mı ? Bağlandı @@ -1975,7 +1975,7 @@ Veritabanı dışa aktarıldı Parçalar silindi Parçalar indirildi - Sunucuayı bağlandı + Bağlı sunucular Kişiye bağlanılıyor, lütfen bekleyin ya da daha sonra kontrol edin. Kopyalama hatası Bildirim göndermeden sil @@ -2035,4 +2035,93 @@ Her sunucu için yeni SOCKS kimlik bilgileri kullanılacaktır. İndir %s (%s) Mesaj şekli + Yüzdeyi göster + Güncelleme indirme işlemi iptal edildi + Abone olurken hata + Mesajlar tüm üyeler için silinecektir. + Mesajlar tüm üyeler için moderasyonlu olarak işaretlenecektir. + Yanlış anahtar veya bilinmeyen dosya yığın adresi - büyük olasılıkla dosya silinmiştir. + Ayarlar + Profil paylaş + Bağlantınız %s\'ye taşındı ancak sizi profile yönlendirirken beklenmedik bir hata oluştu. + Proxy kimlik doğrulaması + Rastgele kimlik bilgileri kullan + Her bağlantı için farklı proxy kimlik bilgileri kullan. + Her profil için farklı proxy kimlik bilgileri kullan. + Kimlik bilgileriniz şifrelenmeden gönderilebilir. + Kullanıcı Adı + Bu sürümü atlayın + Yeni sürümlerden haberdar olmak için Kararlı veya Beta sürümleri için periyodik kontrolü açın. + Yumuşak + Bazı dosya(lar) dışa aktarılmadı + Zoom + Uygulamayı otomatik olarak yükselt + Yüklenen dosyalar + Boyut + Abone olundu + Abonelikler göz ardı edildi + Bağlantılarınız + Ses kapatıldı + Yüklendi + Sunucu istatistikleri sıfırlanacaktır - bu geri alınamaz! + Erişilebilir sohbet araç çubuğu + Görünüm ayarlarından değiştirebilirsiniz. + Sohbet listesini değiştir: + Sistem modu + Erişilebilir sohbet araç çubuğu + İçin bilgi gösteriliyor + İstatistikler + %s\'den başlayarak.\nTüm veriler cihazınıza özeldir. + Bir proxy aracılığıyla gönderildi + Sunucu adresi + Yükleme hataları + TCP bağlantısı + SOCKS proxy + Proxy sunucuları + Geçici dosya hatası + Video + Arşivlenen kişilerden %1$s\'e mesaj gönderebilirsiniz. + Sohbetler listesinde %1$s ile yapılan konuşmayı hala görüntüleyebilirsiniz. + XFTP sunucusu + Alım sırasında hata + SMP sunucusu + Sunucu adresi ağ ayarlarıyla uyumsuz: %1$s. + Sunucu sürümü uygulamanızla uyumlu değil: %1$s. + Kendiniz arayabilmeniz için önce irtibat kişinizin sizi aramasına izin vermelisiniz. + %s\'den başlayarak. + Onay hataları + Güvenli + Uygulama oturumu + Sunucu + Stabil + Güncelleme mevcut: %s + Bu bağlantı başka bir mobil cihazda kullanıldı, lütfen masaüstünde yeni bir bağlantı oluşturun. + Mikrofon kullanımına izin vermek için adres alanının yanındaki bilgi düğmesine tıklayın. + Safari Ayarları / Web Siteleri / Mikrofon\'u açın, ardından localhost için İzin Ver\'i seçin. + Arama yapmak için mikrofonunuzu kullanmanıza izin verin. Aramayı sonlandırın ve tekrar aramayı deneyin. + Konuşma balonu + Güçlü + Uygulamayı tek elle kullan. + Sunucu bilgileri + Toplam + Bu sunuculara bağlı değilsiniz. Mesajları onlara iletmek için özel yönlendirme kullanılır. + Aktif bağlantılar + Köşeleri yuvarlama + Yüklenen veritabanı arşivi sunuculardan kalıcı olarak kaldırılacaktır. + Proxyli + Taşıma oturumları + Dışa aktarılan veritabanını taşıyabilirsiniz. + Dışa aktarılan arşivi kaydedebilirsiniz. + Gönderilen tüm mesajların toplamı + Gönderilen mesajlar + Daha iyi mesaj tarihleri. + Özelleştirilebilir mesaj şekli. + Aynı anda en fazla 20 mesaj iletin. + Görüşme sırasında ses ve görüntüyü değiştirin. + Sohbet profilini 1 kerelik davetler için değiştirin. + Daha iyi aramalar + Daha iyi kullanıcı deneyimi + 200\'e kadar mesajı silin veya düzenleyin. + Daha iyi güvenlik ✅ + SimpleX protokolleri Trail of Bits tarafından incelenmiştir. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index 58627f2f28..fd9492c8d7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -2108,4 +2108,11 @@ Хвіст Куточок Форма повідомлення + Сесія додатку + Нові облікові дані SOCKS будуть використовуватись щоразу, коли ви запускаєте додаток. + Нові облікові дані SOCKS будуть використовуватись для кожного сервера. + Сервер + Натисніть кнопку інформації поруч із полем адреси, щоб дозволити використання мікрофона. + Відкрийте Налаштування Safari / Сайти / Мікрофон, а потім виберіть \"Дозволити для localhost\". + Щоб здійснювати дзвінки, дозволіть використовувати ваш мікрофон. Завершіть дзвінок і спробуйте зателефонувати знову. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml index 392250c470..d464ce6bba 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml @@ -884,4 +884,24 @@ Lịch sử Không tìm thấy nhóm! Hồ sơ trò chuyện ẩn + Cách sử dụng + Cách thức hoạt động + Cách thức SimpleX hoạt động + Cách làm + Lỗi khởi động WebView. Hãy đảm bảo bạn đã cài đặt WebView và kiến trúc hỗ trợ của nó là arm64.\nLỗi: %s + giờ + Lưu trữ + Phiên làm việc trên ứng dụng + Cách sử dụng markdown + Nhấn nút thông tin gần trường địa chỉ để cho phép sử dụng microphone. + Trải nghiệm cuộc gọi tốt hơn + Bảo mật hơn ✅ + Trải nghiệm người dùng tuyệt vời hơn + Hình dạng tin nhắn có thể tùy chỉnh được. + Mô tả thời gian tin nhắn tốt hơn. + Xóa hay kiểm duyệt tối đa 200 tin nhắn. + Góc + Chuyển tiếp tối đa 20 tin nhắn cùng một lúc. + Cách sử dụng máy chủ của bạn + Giao diện Hungary và Thổ Nhĩ Kỳ \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index 1f84bb5eca..eefd310fd0 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -2109,4 +2109,21 @@ 拐角 尾部 初始化 WebView 出错。确保你安装了 WebView 且其支持的架构为 arm64。\n错误:%s + 应用会话 + 每次启动应用都会使用新的 SOCKS5 凭据。 + 服务器 + 打开 Safari 设置/网站/麦克风,接着在 localhost 选择“允许”。 + 要进行通话,请允许使用设备麦克风。结束通话并尝试再次呼叫。 + 单击地址附近的\"信息\"按钮允许使用麦克风。 + 每个服务器都会使用新的 SOCKS5 凭据。 + 更好的消息日期。 + 更佳的安全性✅ + 更佳的使用体验 + 可自定义消息形状。 + 一次性转发最多20条消息。 + Trail of Bits 审核了 SimpleX 协议。 + 通话期间切换音频和视频。 + 对一次性邀请切换聊天配置文件。 + 更佳的通话 + 允许自行删除或管理员移除最多200条消息。 \ No newline at end of file From df53ae9d4ff76dc54dbf4104665e3b71522d6d0d Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Thu, 10 Oct 2024 18:11:01 +0700 Subject: [PATCH 010/567] ios: fix remaining bugs in calls (#5013) --- apps/ios/Shared/Views/Call/ActiveCallView.swift | 12 +++++------- apps/ios/Shared/Views/Call/CallController.swift | 8 +++----- apps/ios/Shared/Views/Call/CallViewRenderers.swift | 10 ++++++++-- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/apps/ios/Shared/Views/Call/ActiveCallView.swift b/apps/ios/Shared/Views/Call/ActiveCallView.swift index 1f9f0739b6..2f76f1f046 100644 --- a/apps/ios/Shared/Views/Call/ActiveCallView.swift +++ b/apps/ios/Shared/Views/Call/ActiveCallView.swift @@ -401,15 +401,13 @@ struct ActiveCallOverlay: View { private func endCallButton() -> some View { let cc = CallController.shared - return callButton("phone.down.fill", padding: 10) { + return callButton("phone.down.fill", .red, padding: 10) { if let uuid = call.callUUID { cc.endCall(callUUID: uuid) } else { cc.endCall(call: call) {} } } - .background(.red) - .clipShape(.circle) } private func toggleMicButton() -> some View { @@ -480,9 +478,7 @@ struct ActiveCallOverlay: View { } @ViewBuilder private func controlButton(_ call: Call, _ imageName: String, padding: CGFloat, _ perform: @escaping () -> Void) -> some View { - callButton(imageName, padding: padding, perform) - .background(call.peerMediaSources.hasVideo ? Color.black.opacity(0.2) : Color.white.opacity(0.2)) - .clipShape(.circle) + callButton(imageName, call.peerMediaSources.hasVideo ? Color.black.opacity(0.2) : Color.white.opacity(0.2), padding: padding, perform) } @ViewBuilder private func audioDevicePickerButton() -> some View { @@ -495,7 +491,7 @@ struct ActiveCallOverlay: View { .clipShape(.circle) } - private func callButton(_ imageName: String, padding: CGFloat, _ perform: @escaping () -> Void) -> some View { + private func callButton(_ imageName: String, _ background: Color, padding: CGFloat, _ perform: @escaping () -> Void) -> some View { Button { perform() } label: { @@ -504,8 +500,10 @@ struct ActiveCallOverlay: View { .scaledToFit() .padding(padding) .frame(width: 60, height: 60) + .background(background) } .foregroundColor(whiteColorWithAlpha) + .clipShape(.circle) } private var whiteColorWithAlpha: Color { diff --git a/apps/ios/Shared/Views/Call/CallController.swift b/apps/ios/Shared/Views/Call/CallController.swift index bf0f1045a4..1f28180e87 100644 --- a/apps/ios/Shared/Views/Call/CallController.swift +++ b/apps/ios/Shared/Views/Call/CallController.swift @@ -357,11 +357,9 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse self.provider.reportCall(with: uuid, updated: update) } } else if callManager.startOutgoingCall(callUUID: callUUID) { - if callManager.startOutgoingCall(callUUID: callUUID) { - logger.debug("CallController.startCall: call started") - } else { - logger.error("CallController.startCall: no active call") - } + logger.debug("CallController.startCall: call started") + } else { + logger.error("CallController.startCall: no active call") } } diff --git a/apps/ios/Shared/Views/Call/CallViewRenderers.swift b/apps/ios/Shared/Views/Call/CallViewRenderers.swift index fbfeb99bf5..e779093a24 100644 --- a/apps/ios/Shared/Views/Call/CallViewRenderers.swift +++ b/apps/ios/Shared/Views/Call/CallViewRenderers.swift @@ -21,16 +21,22 @@ struct CallViewRemote: UIViewRepresentable { let remoteCameraRenderer = RTCMTLVideoView(frame: view.frame) remoteCameraRenderer.videoContentMode = contentMode remoteCameraRenderer.tag = 0 + + let screenVideo = call.peerMediaSources.screenVideo let remoteScreenRenderer = RTCMTLVideoView(frame: view.frame) remoteScreenRenderer.videoContentMode = contentMode remoteScreenRenderer.tag = 1 - remoteScreenRenderer.alpha = call.peerMediaSources.screenVideo ? 1 : 0 + remoteScreenRenderer.alpha = screenVideo ? 1 : 0 context.coordinator.cameraRenderer = remoteCameraRenderer context.coordinator.screenRenderer = remoteScreenRenderer client.addRemoteCameraRenderer(remoteCameraRenderer) client.addRemoteScreenRenderer(remoteScreenRenderer) - addSubviewAndResize(remoteCameraRenderer, remoteScreenRenderer, into: view) + if screenVideo { + addSubviewAndResize(remoteScreenRenderer, remoteCameraRenderer, into: view) + } else { + addSubviewAndResize(remoteCameraRenderer, remoteScreenRenderer, into: view) + } if AVPictureInPictureController.isPictureInPictureSupported() { makeViewWithRTCRenderer(remoteCameraRenderer, remoteScreenRenderer, view, context) From cebb4aa93b14f39608fa10c01cf7b5adeda738e4 Mon Sep 17 00:00:00 2001 From: Diogo Date: Thu, 10 Oct 2024 18:55:37 +0100 Subject: [PATCH 011/567] ios: fix ocassional error on getSubsTotal (#5021) --- apps/ios/Shared/Views/ChatList/ChatListView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 4edc8a45f1..a1b40aadbe 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -437,7 +437,7 @@ struct SubsStatusIndicator: View { private func startTask() { task = Task { while !Task.isCancelled { - if AppChatState.shared.value == .active { + if AppChatState.shared.value == .active, ChatModel.shared.chatRunning == true { do { let (subs, hasSess) = try await getAgentSubsTotal() await MainActor.run { From baa585357f55937a869ee0273e51bf093d62eb86 Mon Sep 17 00:00:00 2001 From: Diogo Date: Thu, 10 Oct 2024 19:01:31 +0100 Subject: [PATCH 012/567] multiplatform: disable chat buttons on user picker when chat is stopped (#5017) * ios: disable chat buttons on user picker when chat is stopped * small change * disable use from desktop on android when chat stopped --- .../ios/Shared/Views/ChatList/UserPicker.swift | 18 ++++++++++++------ .../common/views/chatlist/UserPicker.kt | 3 ++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/apps/ios/Shared/Views/ChatList/UserPicker.swift b/apps/ios/Shared/Views/ChatList/UserPicker.swift index b7818bfac4..cfcfe851f3 100644 --- a/apps/ios/Shared/Views/ChatList/UserPicker.swift +++ b/apps/ios/Shared/Views/ChatList/UserPicker.swift @@ -33,6 +33,7 @@ struct UserPicker: View { .sorted(using: KeyPathComparator(\.user.activeOrder, order: .reverse)) let sectionWidth = max(frameWidth - sectionHorizontalPadding * 2, 0) let currentUserWidth = max(frameWidth - sectionHorizontalPadding - rowPadding * 2 - 14 - imageSize, 0) + let stopped = m.chatRunning != true VStack(spacing: sectionSpacing) { if let user = m.currentUser { StickyScrollView(resetScroll: $resetScroll) { @@ -46,10 +47,14 @@ struct UserPicker: View { .frame(width: otherUsers.isEmpty ? sectionWidth : currentUserWidth, alignment: .leading) .modifier(ListRow { activeSheet = .currentProfile }) .clipShape(sectionShape) + .disabled(stopped) + .opacity(stopped ? 0.4 : 1) ForEach(otherUsers) { u in userView(u, size: imageSize) .frame(maxWidth: sectionWidth * 0.618) .fixedSize() + .disabled(stopped) + .opacity(stopped ? 0.4 : 1) } } .padding(.horizontal, sectionHorizontalPadding) @@ -60,10 +65,10 @@ struct UserPicker: View { .onPreferenceChange(DetermineWidth.Key.self) { frameWidth = $0 } } VStack(spacing: 0) { - openSheetOnTap("qrcode", title: m.userAddress == nil ? "Create SimpleX address" : "Your SimpleX address", sheet: .address) - openSheetOnTap("switch.2", title: "Chat preferences", sheet: .chatPreferences) - openSheetOnTap("person.crop.rectangle.stack", title: "Your chat profiles", sheet: .chatProfiles) - openSheetOnTap("desktopcomputer", title: "Use from desktop", sheet: .useFromDesktop) + openSheetOnTap("qrcode", title: m.userAddress == nil ? "Create SimpleX address" : "Your SimpleX address", sheet: .address, disabled: stopped) + openSheetOnTap("switch.2", title: "Chat preferences", sheet: .chatPreferences, disabled: stopped) + openSheetOnTap("person.crop.rectangle.stack", title: "Your chat profiles", sheet: .chatProfiles, disabled: stopped) + openSheetOnTap("desktopcomputer", title: "Use from desktop", sheet: .useFromDesktop, disabled: stopped) ZStack(alignment: .trailing) { openSheetOnTap("gearshape", title: "Settings", sheet: .settings, showDivider: false) Image(systemName: colorScheme == .light ? "sun.max" : "moon.fill") @@ -149,15 +154,16 @@ struct UserPicker: View { .clipShape(sectionShape) } - private func openSheetOnTap(_ icon: String, title: LocalizedStringKey, sheet: UserPickerSheet, showDivider: Bool = true) -> some View { + private func openSheetOnTap(_ icon: String, title: LocalizedStringKey, sheet: UserPickerSheet, showDivider: Bool = true, disabled: Bool = false) -> some View { ZStack(alignment: .bottom) { settingsRow(icon, color: theme.colors.secondary) { - Text(title).foregroundColor(.primary) + Text(title).foregroundColor(.primary).opacity(disabled ? 0.4 : 1) } .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, rowPadding) .padding(.vertical, rowVerticalPadding) .modifier(ListRow { activeSheet = sheet }) + .disabled(disabled) if showDivider { Divider().padding(.leading, 52) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt index 8546dc4fb3..4a3bce7752 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt @@ -341,7 +341,8 @@ private fun GlobalSettingsSection( ModalManager.start.showCustomModal { close -> ConnectDesktopView(close) } - } + }, + disabled = stopped ) } else { UserPickerOptionRow( From e9a99dfb3c9703da5c3a98c1aab103a8f66c08b5 Mon Sep 17 00:00:00 2001 From: Diogo Date: Thu, 10 Oct 2024 19:06:25 +0100 Subject: [PATCH 013/567] ios: fix empty qr code reader when swapping to connect via link (#5016) --- apps/ios/Shared/Views/NewChat/NewChatView.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift index 63f2e789db..5ff680d5f3 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatView.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -97,6 +97,11 @@ struct NewChatView: View { } .pickerStyle(.segmented) .padding() + .onChange(of: $selection.wrappedValue) { opt in + if opt == NewChatOption.connect { + showQRCodeScanner = true + } + } VStack { // it seems there's a bug in iOS 15 if several views in switch (or if-else) statement have different transitions From 0d8c179861bfde461725e3f493c40c310dfde84d Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 11 Oct 2024 01:08:03 +0700 Subject: [PATCH 014/567] ios: fix not showing link creation and add group members pages (#5020) --- apps/ios/Shared/Views/Chat/ChatView.swift | 11 ++++++++--- apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift | 5 +++-- apps/ios/Shared/Views/NewChat/NewChatView.swift | 8 ++++---- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 7f6b61c1ea..3e056fa581 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -137,6 +137,14 @@ struct ChatView: View { } } } + // it should be presented on top level in order to prevent a bug in SwiftUI on iOS 16 related to .focused() modifier in AddGroupMembersView's search field + .appSheet(isPresented: $showAddMembersSheet) { + Group { + if case let .group(groupInfo) = cInfo { + AddGroupMembersView(chat: chat, groupInfo: groupInfo) + } + } + } .sheet(isPresented: Binding( get: { !forwardedChatItems.isEmpty }, set: { isPresented in @@ -304,9 +312,6 @@ struct ChatView: View { } } else { addMembersButton() - .appSheet(isPresented: $showAddMembersSheet) { - AddGroupMembersView(chat: chat, groupInfo: groupInfo) - } } } Menu { diff --git a/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift b/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift index 051b1158ec..3ca3e0e4d8 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift @@ -14,7 +14,8 @@ enum ContactType: Int { } struct NewChatMenuButton: View { - @EnvironmentObject var chatModel: ChatModel + // do not use chatModel here because it prevents showing AddGroupMembersView after group creation and QR code after link creation on iOS 16 +// @EnvironmentObject var chatModel: ChatModel @State private var showNewChatSheet = false @State private var alert: SomeAlert? = nil @State private var pendingConnection: PendingContactConnection? = nil @@ -32,7 +33,7 @@ struct NewChatMenuButton: View { NewChatSheet(pendingConnection: $pendingConnection) .environment(\EnvironmentValues.refresh as! WritableKeyPath, nil) .onDisappear { - alert = cleanupPendingConnection(chatModel: chatModel, contactConnection: pendingConnection) + alert = cleanupPendingConnection(contactConnection: pendingConnection) pendingConnection = nil } } diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift index 5ff680d5f3..4ca33e674d 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatView.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -45,10 +45,10 @@ enum NewChatOption: Identifiable { var id: Self { self } } -func cleanupPendingConnection(chatModel: ChatModel, contactConnection: PendingContactConnection?) -> SomeAlert? { +func cleanupPendingConnection(contactConnection: PendingContactConnection?) -> SomeAlert? { var alert: SomeAlert? = nil - if !(chatModel.showingInvitation?.connChatUsed ?? true), + if !(ChatModel.shared.showingInvitation?.connChatUsed ?? true), let conn = contactConnection { alert = SomeAlert( alert: Alert( @@ -68,7 +68,7 @@ func cleanupPendingConnection(chatModel: ChatModel, contactConnection: PendingCo ) } - chatModel.showingInvitation = nil + ChatModel.shared.showingInvitation = nil return alert } @@ -157,7 +157,7 @@ struct NewChatView: View { } .onDisappear { if !choosingProfile { - parentAlert = cleanupPendingConnection(chatModel: m, contactConnection: contactConnection) + parentAlert = cleanupPendingConnection(contactConnection: contactConnection) contactConnection = nil } } From 75bacb7923a68b65a81c0eb9d9e5fe9fb25201a2 Mon Sep 17 00:00:00 2001 From: Yaroslav Pavlov Date: Thu, 10 Oct 2024 19:10:11 +0100 Subject: [PATCH 015/567] desktop: fix typescript sdk ability to send / receive messages (#4970) * typescript sdk: fix send messages * typescript sdk: fix send messages naming --- .../typescript/examples/squaring-bot.js | 17 +++++++++-------- .../simplex-chat-client/typescript/package.json | 2 +- .../typescript/src/client.ts | 10 +++++----- .../typescript/src/command.ts | 4 ++-- .../typescript/src/response.ts | 10 +++++----- 5 files changed, 22 insertions(+), 21 deletions(-) diff --git a/packages/simplex-chat-client/typescript/examples/squaring-bot.js b/packages/simplex-chat-client/typescript/examples/squaring-bot.js index 5a96dfb205..9651436ffc 100644 --- a/packages/simplex-chat-client/typescript/examples/squaring-bot.js +++ b/packages/simplex-chat-client/typescript/examples/squaring-bot.js @@ -35,15 +35,16 @@ async function run() { ) continue } - case "newChatItem": { + case "newChatItems": { // calculates the square of the number and sends the reply - const {chatInfo} = resp.chatItem - if (chatInfo.type !== ChatInfoType.Direct) continue - const msg = ciContentText(resp.chatItem.chatItem.content) - if (msg) { - const n = +msg - const reply = typeof n === "number" && !isNaN(n) ? `${n} * ${n} = ${n * n}` : `this is not a number` - await chat.apiSendTextMessage(ChatType.Direct, chatInfo.contact.contactId, reply) + for (const {chatInfo, chatItem} of resp.chatItems) { + if (chatInfo.type !== ChatInfoType.Direct) continue + const msg = ciContentText(chatItem.content) + if (msg) { + const n = +msg + const reply = typeof n === "number" && !isNaN(n) ? `${n} * ${n} = ${n * n}` : `this is not a number` + await chat.apiSendTextMessage(ChatType.Direct, chatInfo.contact.contactId, reply) + } } } } diff --git a/packages/simplex-chat-client/typescript/package.json b/packages/simplex-chat-client/typescript/package.json index c8aa6b4f1e..bb2fdda702 100644 --- a/packages/simplex-chat-client/typescript/package.json +++ b/packages/simplex-chat-client/typescript/package.json @@ -1,6 +1,6 @@ { "name": "simplex-chat", - "version": "0.2.0", + "version": "0.2.1", "description": "SimpleX Chat client", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/simplex-chat-client/typescript/src/client.ts b/packages/simplex-chat-client/typescript/src/client.ts index eb3e39f00a..3600147ba7 100644 --- a/packages/simplex-chat-client/typescript/src/client.ts +++ b/packages/simplex-chat-client/typescript/src/client.ts @@ -160,14 +160,14 @@ export class ChatClient { throw new ChatCommandError("error loading chat", r) } - async apiSendMessage(chatType: ChatType, chatId: number, message: CC.ComposedMessage): Promise { - const r = await this.sendChatCommand({type: "apiSendMessage", chatType, chatId, message}) - if (r.type === "newChatItem") return r.chatItem + async apiSendMessages(chatType: ChatType, chatId: number, messages: CC.ComposedMessage[]): Promise { + const r = await this.sendChatCommand({type: "apiSendMessage", chatType, chatId, messages}) + if (r.type === "newChatItems") return r.chatItems throw new ChatCommandError("unexpected response", r) } - apiSendTextMessage(chatType: ChatType, chatId: number, text: string): Promise { - return this.apiSendMessage(chatType, chatId, {msgContent: {type: "text", text}}) + async apiSendTextMessage(chatType: ChatType, chatId: number, text: string): Promise { + return this.apiSendMessages(chatType, chatId, [{msgContent: {type: "text", text}}]) } async apiUpdateChatItem(chatType: ChatType, chatId: number, chatItemId: CC.ChatItemId, msgContent: CC.MsgContent): Promise { diff --git a/packages/simplex-chat-client/typescript/src/command.ts b/packages/simplex-chat-client/typescript/src/command.ts index bd17a55926..e512e06672 100644 --- a/packages/simplex-chat-client/typescript/src/command.ts +++ b/packages/simplex-chat-client/typescript/src/command.ts @@ -277,7 +277,7 @@ export interface APISendMessage extends IChatCommand { type: "apiSendMessage" chatType: ChatType chatId: number - message: ComposedMessage + messages: ComposedMessage[] } export interface ComposedMessage { @@ -709,7 +709,7 @@ export function cmdString(cmd: ChatCommand): string { case "apiGetChat": return `/_get chat ${cmd.chatType}${cmd.chatId}${paginationStr(cmd.pagination)}` case "apiSendMessage": - return `/_send ${cmd.chatType}${cmd.chatId} json ${JSON.stringify(cmd.message)}` + return `/_send ${cmd.chatType}${cmd.chatId} json ${JSON.stringify(cmd.messages)}` case "apiUpdateChatItem": return `/_update item ${cmd.chatType}${cmd.chatId} ${cmd.chatItemId} json ${JSON.stringify(cmd.msgContent)}` case "apiDeleteChatItem": diff --git a/packages/simplex-chat-client/typescript/src/response.ts b/packages/simplex-chat-client/typescript/src/response.ts index b50b2e2943..2e92e335df 100644 --- a/packages/simplex-chat-client/typescript/src/response.ts +++ b/packages/simplex-chat-client/typescript/src/response.ts @@ -12,7 +12,7 @@ export type ChatResponse = | CRUserProtoServers | CRContactInfo | CRGroupMemberInfo - | CRNewChatItem + | CRNewChatItems | CRChatItemStatusUpdated | CRChatItemUpdated | CRChatItemDeleted @@ -109,7 +109,7 @@ type ChatResponseTag = | "userProtoServers" | "contactInfo" | "groupMemberInfo" - | "newChatItem" + | "newChatItems" | "chatItemStatusUpdated" | "chatItemUpdated" | "chatItemDeleted" @@ -255,10 +255,10 @@ export interface CRGroupMemberInfo extends CR { connectionStats_?: ConnectionStats } -export interface CRNewChatItem extends CR { - type: "newChatItem" +export interface CRNewChatItems extends CR { + type: "newChatItems" user: User - chatItem: AChatItem + chatItems: AChatItem[] } export interface CRChatItemStatusUpdated extends CR { From ec014d721ef82b54e92de57033f8ec2255a3f813 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Thu, 10 Oct 2024 19:15:09 +0100 Subject: [PATCH 016/567] sdk: fix test --- .../typescript/tests/client.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/simplex-chat-client/typescript/tests/client.test.ts b/packages/simplex-chat-client/typescript/tests/client.test.ts index 2c44406c3d..eef57558f2 100644 --- a/packages/simplex-chat-client/typescript/tests/client.test.ts +++ b/packages/simplex-chat-client/typescript/tests/client.test.ts @@ -26,8 +26,8 @@ describe.skip("ChatClient (expects SimpleX Chat server with a user, without cont assert(r2.type === "contactConnected") const contact1 = (r1 as CR.CRContactConnected).contact // const contact2 = (r2 as C.CRContactConnected).contact - const r3 = await c.apiSendTextMessage(CC.ChatType.CTDirect, contact1.contactId, "hello") - assert(r3.chatItem.content.type === "sndMsgContent" && r3.chatItem.content.msgContent.text === "hello") + const r3 = await c.apiSendTextMessage(CC.ChatType.Direct, contact1.contactId, "hello") + assert(r3[0].chatItem.content.type === "sndMsgContent" && r3[0].chatItem.content.msgContent.text === "hello") const r4 = await c.msgQ.dequeue() assert(isItemSent(r4) || isNewRcvItem(r4)) await c.disconnect() @@ -38,9 +38,9 @@ describe.skip("ChatClient (expects SimpleX Chat server with a user, without cont function isNewRcvItem(r: CR.ChatResponse): boolean { return ( - r.type === "newChatItem" && - r.chatItem.chatItem.content.type === "rcvMsgContent" && - r.chatItem.chatItem.content.msgContent.text === "hello" + r.type === "newChatItems" && + r.chatItems[0].chatItem.content.type === "rcvMsgContent" && + r.chatItems[0].chatItem.content.msgContent.text === "hello" ) } }, 20000) From e3528d3ffea451d05561f196b4baded27e4255b4 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 11 Oct 2024 20:36:57 +0700 Subject: [PATCH 017/567] android: re-apply custom language when webview appears (#5022) * android: re-apply custom language when webview appears There is a bug on Android related to including WebView. App language changes to system language regardless of what was set before in context's configuration. Re-set needed to be done twice: after creating of WebView and after removing it from a view * add link to bug --------- Co-authored-by: Evgeny --- .../chat/simplex/app/views/call/CallActivity.kt | 10 +++++++--- .../kotlin/chat/simplex/common/helpers/Locale.kt | 3 +-- .../simplex/common/views/call/CallView.android.kt | 15 ++++++++++++++- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt index e7503733ac..a5a1726757 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt @@ -24,8 +24,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.platform.* import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -37,7 +36,9 @@ import chat.simplex.app.R import chat.simplex.app.TAG import chat.simplex.app.model.NtfManager import chat.simplex.app.model.NtfManager.AcceptCallAction +import chat.simplex.common.helpers.applyAppLocale import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.platform.chatModel import chat.simplex.common.ui.theme.* @@ -49,6 +50,7 @@ import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.launch import kotlinx.datetime.Clock import java.lang.ref.WeakReference +import java.util.* import chat.simplex.common.platform.chatModel as m class CallActivity: ComponentActivity(), ServiceConnection { @@ -56,6 +58,7 @@ class CallActivity: ComponentActivity(), ServiceConnection { var boundService: CallService? = null override fun onCreate(savedInstanceState: Bundle?) { + applyAppLocale(appPrefs.appLanguage) super.onCreate(savedInstanceState) callActivity = WeakReference(this) when (intent?.action) { @@ -80,6 +83,7 @@ class CallActivity: ComponentActivity(), ServiceConnection { override fun onDestroy() { super.onDestroy() + (mainActivity.get() ?: this).applyAppLocale(appPrefs.appLanguage) if (isOnLockScreenNow()) { lockAfterIncomingCall() } @@ -233,7 +237,7 @@ fun CallActivityView() { } SimpleXTheme { var prevCall by remember { mutableStateOf(call) } - KeyChangeEffect(m.activeCall.value) { + KeyChangeEffect(m.activeCall.value, remember { appPrefs.appLanguage.state }.value) { if (m.activeCall.value != null) { prevCall = m.activeCall.value activity.boundService?.updateNotification() diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/helpers/Locale.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/helpers/Locale.kt index b9d7d27ba9..c289715886 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/helpers/Locale.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/helpers/Locale.kt @@ -31,8 +31,7 @@ private fun Activity.applyLocale(locale: Locale) { Locale.setDefault(locale) val appConf = Configuration(androidAppContext.resources.configuration).apply { setLocale(locale) } val activityConf = Configuration(resources.configuration).apply { setLocale(locale) } - @Suppress("DEPRECATION") - androidAppContext.resources.updateConfiguration(appConf, resources.displayMetrics) + androidAppContext = androidAppContext.createConfigurationContext(appConf) @Suppress("DEPRECATION") resources.updateConfiguration(activityConf, resources.displayMetrics) } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt index 37bf8d1330..b35f47e36e 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt @@ -41,8 +41,10 @@ import androidx.core.content.ContextCompat import androidx.lifecycle.* import androidx.webkit.WebViewAssetLoader import androidx.webkit.WebViewClientCompat +import chat.simplex.common.helpers.applyAppLocale import chat.simplex.common.helpers.showAllowPermissionInSettingsAlert import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* @@ -702,9 +704,10 @@ fun WebRTCView(callCommand: SnapshotStateList, onResponse: (WVAPIM Box(Modifier.fillMaxSize()) { AndroidView( - factory = { AndroidViewContext -> + factory = { try { (staticWebView ?: WebView(androidAppContext)).apply { + reapplyLocale() layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, @@ -775,6 +778,16 @@ private fun updateActiveCall(initial: Call, transform: (Call) -> Call) { } } +/* +* Creating WebView automatically drops user's custom app locale to default system locale. +* Preventing it by re-applying custom locale +* https://issuetracker.google.com/issues/109833940 +* */ +private fun reapplyLocale() { + mainActivity.get()?.applyAppLocale(appPrefs.appLanguage) + callActivity.get()?.applyAppLocale(appPrefs.appLanguage) +} + private class LocalContentWebViewClient(val webView: MutableState, private val assetLoader: WebViewAssetLoader) : WebViewClientCompat() { override fun shouldInterceptRequest( view: WebView, From 2127c7dccebc4873aca1186117e1a85438c1a848 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 11 Oct 2024 20:37:36 +0700 Subject: [PATCH 018/567] android, desktop: don't stop audio track on Android in calls (#5024) * android, desktop: don't stop audio track on Android in calls There is a problem related to managing selected audio output device in call. When microphone is disabled, WebView turns speaker on without need. No way to prevent it was found yet. This is temporary workaround that makes everything work except it makes microphone icon visible in status bar (microphone is not used actually in that moment) * enabled=false --- .../src/commonMain/resources/assets/www/call.js | 13 ++++++++++--- packages/simplex-chat-webrtc/src/call.ts | 13 +++++++++---- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/www/call.js b/apps/multiplatform/common/src/commonMain/resources/assets/www/call.js index 32f014e622..4dae487d03 100644 --- a/apps/multiplatform/common/src/commonMain/resources/assets/www/call.js +++ b/apps/multiplatform/common/src/commonMain/resources/assets/www/call.js @@ -63,6 +63,7 @@ const allowSendScreenAudio = false; // When one side of a call sends candidates tot fast (until local & remote descriptions are set), that candidates // will be stored here and then set when the call will be ready to process them let afterCallInitializedCandidates = []; +const stopTrackOnAndroid = false; const processCommand = (function () { const defaultIceServers = [ { urls: ["stuns:stun.simplex.im:443"] }, @@ -844,7 +845,10 @@ const processCommand = (function () { // doing it vice versa gives an error like "too many cameras were open" on some Android devices or webViews // which means the second camera will never be opened for (const t of source == CallMediaSource.Mic ? call.localStream.getAudioTracks() : call.localStream.getVideoTracks()) { - t.stop(); + if (isDesktop || source != CallMediaSource.Mic || stopTrackOnAndroid) + t.stop(); + else + t.enabled = false; call.localStream.removeTrack(t); } let localStream; @@ -894,7 +898,7 @@ const processCommand = (function () { if (!localStream || !oldCamera || !videos) return; if (!inactiveCallMediaSources.mic) { - localStream.getAudioTracks().forEach((elem) => elem.stop()); + localStream.getAudioTracks().forEach((elem) => (isDesktop || stopTrackOnAndroid ? elem.stop() : (elem.enabled = false))); localStream.getAudioTracks().forEach((elem) => localStream.removeTrack(elem)); } if (!inactiveCallMediaSources.camera || oldCamera != newCamera) { @@ -1157,7 +1161,10 @@ const processCommand = (function () { transceiver.sender.replaceTrack(t); } else { - t.stop(); + if (isDesktop || t.kind == CallMediaType.Video || stopTrackOnAndroid) + t.stop(); + else + t.enabled = false; s.removeTrack(t); transceiver.sender.replaceTrack(null); } diff --git a/packages/simplex-chat-webrtc/src/call.ts b/packages/simplex-chat-webrtc/src/call.ts index eda535cfa7..693ad6bbe5 100644 --- a/packages/simplex-chat-webrtc/src/call.ts +++ b/packages/simplex-chat-webrtc/src/call.ts @@ -246,7 +246,7 @@ const callCrypto = callCryptoFunction() declare var RTCRtpScriptTransform: { prototype: RTCRtpScriptTransform - new (worker: Worker, options?: any): RTCRtpScriptTransform + new (worker: Worker, options?: any, transfer?: any[] | undefined): RTCRtpScriptTransform } enum TransformOperation { @@ -316,6 +316,8 @@ const allowSendScreenAudio = false // will be stored here and then set when the call will be ready to process them let afterCallInitializedCandidates: RTCIceCandidateInit[] = [] +const stopTrackOnAndroid = false + const processCommand = (function () { type RTCRtpSenderWithEncryption = RTCRtpSender & { createEncodedStreams: () => TransformStream @@ -1141,7 +1143,8 @@ const processCommand = (function () { // doing it vice versa gives an error like "too many cameras were open" on some Android devices or webViews // which means the second camera will never be opened for (const t of source == CallMediaSource.Mic ? call.localStream.getAudioTracks() : call.localStream.getVideoTracks()) { - t.stop() + if (isDesktop || source != CallMediaSource.Mic || stopTrackOnAndroid) t.stop() + else t.enabled = false call.localStream.removeTrack(t) } let localStream: MediaStream @@ -1200,7 +1203,7 @@ const processCommand = (function () { if (!localStream || !oldCamera || !videos) return if (!inactiveCallMediaSources.mic) { - localStream.getAudioTracks().forEach((elem) => elem.stop()) + localStream.getAudioTracks().forEach((elem) => (isDesktop || stopTrackOnAndroid ? elem.stop() : (elem.enabled = false))) localStream.getAudioTracks().forEach((elem) => localStream.removeTrack(elem)) } if (!inactiveCallMediaSources.camera || oldCamera != newCamera) { @@ -1474,7 +1477,9 @@ const processCommand = (function () { if (enable) { transceiver.sender.replaceTrack(t) } else { - t.stop() + if (isDesktop || t.kind == CallMediaType.Video || stopTrackOnAndroid) t.stop() + else t.enabled = false + s.removeTrack(t) transceiver.sender.replaceTrack(null) } From 9a87f344b56430edb3c565c101ca214e48ee2180 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 11 Oct 2024 18:37:38 +0400 Subject: [PATCH 019/567] core: do not regenerate key when accepting connection to avoid invalidating invitation link on bad networks (#5018) * core: prepare conn (plan) * update * group join * comment * comment * wip * Revert "wip" This reverts commit 0849f433772e2681a6dc2f0051b83d2b5d0a8f31. * accept * save contact_id, reuse contact * refactor * simplexmq * set contactUsed * support retrying join * exclude prepared connections from API responses * avoid race with events * avoid race better * fix UI * update library * tmp * update * display error details on ios cmd prohibited * underscore instead of empty * Update apps/ios/Shared/Model/SimpleXAPI.swift Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> * test * update simplexmq --------- Co-authored-by: Evgeny Poberezkin Co-authored-by: Diogo --- apps/ios/Shared/Model/SimpleXAPI.swift | 2 +- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++--- apps/ios/SimpleXChat/APITypes.swift | 2 +- apps/ios/SimpleXChat/ChatTypes.swift | 2 + .../chat/simplex/common/model/ChatModel.kt | 2 + cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 1 + src/Simplex/Chat.hs | 97 +++++++++---- src/Simplex/Chat/Controller.hs | 4 + .../M20241010_contact_requests_contact_id.hs | 22 +++ src/Simplex/Chat/Migrations/chat_schema.sql | 2 + src/Simplex/Chat/Protocol.hs | 11 ++ src/Simplex/Chat/Store/Direct.hs | 81 +++++++---- src/Simplex/Chat/Store/Messages.hs | 5 +- src/Simplex/Chat/Store/Migrations.hs | 4 +- src/Simplex/Chat/Store/Shared.hs | 6 +- src/Simplex/Chat/Types.hs | 5 + tests/ChatClient.hs | 4 +- tests/ChatTests/Direct.hs | 133 +++++++++++++++++- tests/ChatTests/Profiles.hs | 65 +++++++++ 21 files changed, 401 insertions(+), 91 deletions(-) create mode 100644 src/Simplex/Chat/Migrations/M20241010_contact_requests_contact_id.hs diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 17f5936b6b..a0a32156c4 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -462,7 +462,7 @@ func apiGetNtfToken() -> (DeviceToken?, NtfTknStatus?, NotificationsMode, String let r = chatSendCmdSync(.apiGetNtfToken) switch r { case let .ntfToken(token, status, ntfMode, ntfServer): return (token, status, ntfMode, ntfServer) - case .chatCmdError(_, .errorAgent(.CMD(.PROHIBITED))): return (nil, nil, .off, nil) + case .chatCmdError(_, .errorAgent(.CMD(.PROHIBITED, _))): return (nil, nil, .off, nil) default: logger.debug("apiGetNtfToken response: \(String(describing: r))") return (nil, nil, .off, nil) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 3e2e908d32..7b9bbb726d 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -144,11 +144,6 @@ 5CFE0922282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; }; 640417CD2B29B8C200CCB412 /* NewChatMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640417CB2B29B8C200CCB412 /* NewChatMenuButton.swift */; }; 640417CE2B29B8C200CCB412 /* NewChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640417CC2B29B8C200CCB412 /* NewChatView.swift */; }; - 640548A32CB56735005DE1E4 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6405489E2CB56735005DE1E4 /* libgmpxx.a */; }; - 640548A42CB56735005DE1E4 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6405489F2CB56735005DE1E4 /* libgmp.a */; }; - 640548A52CB56735005DE1E4 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 640548A02CB56735005DE1E4 /* libffi.a */; }; - 640548A62CB56735005DE1E4 /* libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 640548A12CB56735005DE1E4 /* libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv-ghc9.6.3.a */; }; - 640548A72CB56735005DE1E4 /* libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 640548A22CB56735005DE1E4 /* libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv.a */; }; 6407BA83295DA85D0082BA18 /* CIInvalidJSONView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */; }; 6419EC562AB8BC8B004A607A /* ContextInvitingContactMemberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */; }; 6419EC582AB97507004A607A /* CIMemberCreatedContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */; }; @@ -159,6 +154,11 @@ 6442E0BE2880182D00CEC0F9 /* GroupChatInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6442E0BD2880182D00CEC0F9 /* GroupChatInfoView.swift */; }; 64466DC829FC2B3B00E3D48D /* CreateSimpleXAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */; }; 64466DCC29FFE3E800E3D48D /* MailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64466DCB29FFE3E800E3D48D /* MailView.swift */; }; + 644844672CB932A5004A1BC6 /* libHSsimplex-chat-6.1.0.7-4dlRUyozvfTCAHYlr9Ja1T-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644844622CB932A4004A1BC6 /* libHSsimplex-chat-6.1.0.7-4dlRUyozvfTCAHYlr9Ja1T-ghc9.6.3.a */; }; + 644844682CB932A5004A1BC6 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644844632CB932A5004A1BC6 /* libffi.a */; }; + 644844692CB932A5004A1BC6 /* libHSsimplex-chat-6.1.0.7-4dlRUyozvfTCAHYlr9Ja1T.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644844642CB932A5004A1BC6 /* libHSsimplex-chat-6.1.0.7-4dlRUyozvfTCAHYlr9Ja1T.a */; }; + 6448446A2CB932A5004A1BC6 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644844652CB932A5004A1BC6 /* libgmpxx.a */; }; + 6448446B2CB932A5004A1BC6 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644844662CB932A5004A1BC6 /* libgmp.a */; }; 6448BBB628FA9D56000D2AB9 /* GroupLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */; }; 644EFFDE292BCD9D00525D5B /* ComposeVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */; }; 644EFFE0292CFD7F00525D5B /* CIVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFDF292CFD7F00525D5B /* CIVoiceView.swift */; }; @@ -486,11 +486,6 @@ 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ZoomableScrollView.swift; path = Shared/Views/ZoomableScrollView.swift; sourceTree = SOURCE_ROOT; }; 640417CB2B29B8C200CCB412 /* NewChatMenuButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewChatMenuButton.swift; sourceTree = ""; }; 640417CC2B29B8C200CCB412 /* NewChatView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewChatView.swift; sourceTree = ""; }; - 6405489E2CB56735005DE1E4 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - 6405489F2CB56735005DE1E4 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - 640548A02CB56735005DE1E4 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 640548A12CB56735005DE1E4 /* libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv-ghc9.6.3.a"; sourceTree = ""; }; - 640548A22CB56735005DE1E4 /* libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv.a"; sourceTree = ""; }; 6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIInvalidJSONView.swift; sourceTree = ""; }; 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextInvitingContactMemberView.swift; sourceTree = ""; }; 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMemberCreatedContactView.swift; sourceTree = ""; }; @@ -501,6 +496,11 @@ 6442E0BD2880182D00CEC0F9 /* GroupChatInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupChatInfoView.swift; sourceTree = ""; }; 64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSimpleXAddress.swift; sourceTree = ""; }; 64466DCB29FFE3E800E3D48D /* MailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailView.swift; sourceTree = ""; }; + 644844622CB932A4004A1BC6 /* libHSsimplex-chat-6.1.0.7-4dlRUyozvfTCAHYlr9Ja1T-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.7-4dlRUyozvfTCAHYlr9Ja1T-ghc9.6.3.a"; sourceTree = ""; }; + 644844632CB932A5004A1BC6 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + 644844642CB932A5004A1BC6 /* libHSsimplex-chat-6.1.0.7-4dlRUyozvfTCAHYlr9Ja1T.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.7-4dlRUyozvfTCAHYlr9Ja1T.a"; sourceTree = ""; }; + 644844652CB932A5004A1BC6 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + 644844662CB932A5004A1BC6 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupLinkView.swift; sourceTree = ""; }; 644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeVoiceView.swift; sourceTree = ""; }; 644EFFDF292CFD7F00525D5B /* CIVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIVoiceView.swift; sourceTree = ""; }; @@ -655,14 +655,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 640548A32CB56735005DE1E4 /* libgmpxx.a in Frameworks */, - 640548A72CB56735005DE1E4 /* libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv.a in Frameworks */, - 640548A52CB56735005DE1E4 /* libffi.a in Frameworks */, - 640548A42CB56735005DE1E4 /* libgmp.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - 640548A62CB56735005DE1E4 /* libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv-ghc9.6.3.a in Frameworks */, + 644844692CB932A5004A1BC6 /* libHSsimplex-chat-6.1.0.7-4dlRUyozvfTCAHYlr9Ja1T.a in Frameworks */, + 6448446B2CB932A5004A1BC6 /* libgmp.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, + 644844682CB932A5004A1BC6 /* libffi.a in Frameworks */, + 644844672CB932A5004A1BC6 /* libHSsimplex-chat-6.1.0.7-4dlRUyozvfTCAHYlr9Ja1T-ghc9.6.3.a in Frameworks */, + 6448446A2CB932A5004A1BC6 /* libgmpxx.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -739,11 +739,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - 640548A02CB56735005DE1E4 /* libffi.a */, - 6405489F2CB56735005DE1E4 /* libgmp.a */, - 6405489E2CB56735005DE1E4 /* libgmpxx.a */, - 640548A12CB56735005DE1E4 /* libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv-ghc9.6.3.a */, - 640548A22CB56735005DE1E4 /* libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv.a */, + 644844632CB932A5004A1BC6 /* libffi.a */, + 644844662CB932A5004A1BC6 /* libgmp.a */, + 644844652CB932A5004A1BC6 /* libgmpxx.a */, + 644844622CB932A4004A1BC6 /* libHSsimplex-chat-6.1.0.7-4dlRUyozvfTCAHYlr9Ja1T-ghc9.6.3.a */, + 644844642CB932A5004A1BC6 /* libHSsimplex-chat-6.1.0.7-4dlRUyozvfTCAHYlr9Ja1T.a */, ); path = Libraries; sourceTree = ""; diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index bff150f58f..fae6d2293f 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -2041,7 +2041,7 @@ public enum SQLiteError: Decodable, Hashable { } public enum AgentErrorType: Decodable, Hashable { - case CMD(cmdErr: CommandErrorType) + case CMD(cmdErr: CommandErrorType, errContext: String) case CONN(connErr: ConnectionErrorType) case SMP(serverAddress: String, smpErr: ProtocolErrorType) case NTF(ntfErr: ProtocolErrorType) diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 7b81057e0b..45dab17cf2 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -1852,6 +1852,7 @@ public struct PendingContactConnection: Decodable, NamedChat, Hashable { public enum ConnStatus: String, Decodable, Hashable { case new = "new" + case prepared = "prepared" case joined = "joined" case requested = "requested" case accepted = "accepted" @@ -1863,6 +1864,7 @@ public enum ConnStatus: String, Decodable, Hashable { get { switch self { case .new: return true + case .prepared: return false case .joined: return false case .requested: return true case .accepted: return true diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 682a472060..6bc565097f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -1889,6 +1889,7 @@ class PendingContactConnection( @Serializable enum class ConnStatus { @SerialName("new") New, + @SerialName("prepared") Prepared, @SerialName("joined") Joined, @SerialName("requested") Requested, @SerialName("accepted") Accepted, @@ -1898,6 +1899,7 @@ enum class ConnStatus { val initiated: Boolean? get() = when (this) { New -> true + Prepared -> false Joined -> false Requested -> true Accepted -> true diff --git a/cabal.project b/cabal.project index da46056668..515ad0312b 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: b8971a31bcb82fffabcb792c9afd6bc4a96ec649 + tag: 887044283079b91f5d1ce84372a7e1a5c31c379b source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 314cb070f1..658f2fb648 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."b8971a31bcb82fffabcb792c9afd6bc4a96ec649" = "1p6m390ngcsp7i7vy0m0zxh167gkbciavva9a00l6pxwzaz9qmpi"; + "https://github.com/simplex-chat/simplexmq.git"."887044283079b91f5d1ce84372a7e1a5c31c379b" = "1lqlcyph9pn8ibwydi8m8bfcrswgs0dcsd867ldwr4xqnlkmd7qd"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index c637789ad2..5c3a313869 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -149,6 +149,7 @@ library Simplex.Chat.Migrations.M20240827_calls_uuid Simplex.Chat.Migrations.M20240920_user_order Simplex.Chat.Migrations.M20241008_indexes + Simplex.Chat.Migrations.M20241010_contact_requests_contact_id Simplex.Chat.Mobile Simplex.Chat.Mobile.File Simplex.Chat.Mobile.Shared diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 55c70d6a2e..5f586e8dad 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1299,12 +1299,18 @@ processChatCommand' vr = \case APIAcceptContact incognito connReqId -> withUser $ \_ -> do (user@User {userId}, cReq@UserContactRequest {userContactLinkId}) <- withFastStore $ \db -> getContactRequest' db connReqId withUserContactLock "acceptContact" userContactLinkId $ do + (ct, conn@Connection {connId}, sqSecured) <- acceptContactRequest user cReq incognito ucl <- withFastStore $ \db -> getUserContactLinkById db userId userContactLinkId let contactUsed = (\(_, groupId_, _) -> isNothing groupId_) ucl - -- [incognito] generate profile to send, create connection with incognito profile - incognitoProfile <- if incognito then Just . NewIncognito <$> liftIO generateRandomProfile else pure Nothing - ct <- acceptContactRequest user cReq incognitoProfile contactUsed - pure $ CRAcceptingContactRequest user ct + ct' <- withStore' $ \db -> do + deleteContactRequestRec db user cReq + updateContactAccepted db user ct contactUsed + conn' <- + if sqSecured + then conn {connStatus = ConnSndReady} <$ updateConnectionStatusFromTo db connId ConnNew ConnSndReady + else pure conn + pure ct {contactUsed, activeConn = Just conn'} + pure $ CRAcceptingContactRequest user ct' APIRejectContact connReqId -> withUser $ \user -> do cReq@UserContactRequest {userContactLinkId, agentContactConnId = AgentConnId connId, agentInvitationId = AgentInvId invId} <- withFastStore $ \db -> @@ -1763,7 +1769,7 @@ processChatCommand' vr = \case pure conn' APIConnectPlan userId cReqUri -> withUserId userId $ \user -> CRConnectionPlan user <$> connectPlan user cReqUri - APIConnect userId incognito (Just (ACR SCMInvitation cReq)) -> withUserId userId $ \user -> withInvitationLock "connect" (strEncode cReq) . procCmd $ do + APIConnect userId incognito (Just (ACR SCMInvitation cReq@(CRInvitationUri crData e2e))) -> withUserId userId $ \user -> withInvitationLock "connect" (strEncode cReq) . procCmd $ do subMode <- chatReadVar subscriptionMode -- [incognito] generate profile to send incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing @@ -1774,10 +1780,27 @@ processChatCommand' vr = \case Just (agentV, pqSup') -> do let chatV = agentToChatVersion agentV dm <- encodeConnInfoPQ pqSup' chatV $ XInfo profileToSend - connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq pqSup' - conn@PendingContactConnection {pccConnId} <- withFastStore' $ \db -> createDirectConnection db user connId cReq ConnJoined (incognitoProfile $> profileToSend) subMode chatV pqSup' - joinPreparedAgentConnection user pccConnId connId cReq dm pqSup' subMode - pure $ CRSentConfirmation user conn + withFastStore' (\db -> getConnectionEntityByConnReq db vr user cReqs) >>= \case + Nothing -> joinNewConn chatV dm + Just (RcvDirectMsgConnection conn@Connection {connId, connStatus, contactConnInitiated} Nothing) + | connStatus == ConnNew && contactConnInitiated -> joinNewConn chatV dm -- own connection link + | connStatus == ConnPrepared -> do -- retrying join after error + pcc <- withFastStore $ \db -> getPendingContactConnection db userId connId + joinPreparedConn (aConnId conn) pcc dm + Just ent -> throwChatError $ CECommandError $ "connection exists: " <> show (connEntityInfo ent) + where + joinNewConn chatV dm = do + connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq pqSup' + pcc <- withFastStore' $ \db -> createDirectConnection db user connId cReq ConnPrepared (incognitoProfile $> profileToSend) subMode chatV pqSup' + joinPreparedConn connId pcc dm + joinPreparedConn connId pcc@PendingContactConnection {pccConnId} dm = do + void $ withAgent $ \a -> joinConnection a (aUserId user) connId True cReq dm pqSup' subMode + withFastStore' $ \db -> updateConnectionStatusFromTo db pccConnId ConnPrepared ConnJoined + pure $ CRSentConfirmation user pcc {pccConnStatus = ConnJoined} + cReqs = + ( CRInvitationUri crData {crScheme = SSSimplex} e2e, + CRInvitationUri crData {crScheme = simplexChat} e2e + ) APIConnect userId incognito (Just (ACR SCMContact cReq)) -> withUserId userId $ \user -> connectViaContact user incognito cReq APIConnect _ _ Nothing -> throwChatError CEInvalidConnReq Connect incognito aCReqUri@(Just cReqUri) -> withUser $ \user@User {userId} -> do @@ -2029,20 +2052,21 @@ processChatCommand' vr = \case Just Connection {peerChatVRange} -> do subMode <- chatReadVar subscriptionMode dm <- encodeConnInfo $ XGrpAcpt membershipMemId - agentConnId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True connRequest PQSupportOff - let chatV = vr `peerConnChatVersion` peerChatVRange - cId <- withFastStore' $ \db -> do - Connection {connId = cId} <- createMemberConnection db userId fromMember agentConnId chatV peerChatVRange subMode + agentConnId <- case memberConn fromMember of + Nothing -> do + agentConnId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True connRequest PQSupportOff + let chatV = vr `peerConnChatVersion` peerChatVRange + void $ withFastStore' $ \db -> createMemberConnection db userId fromMember agentConnId chatV peerChatVRange subMode + pure agentConnId + Just conn -> pure $ aConnId conn + withFastStore' $ \db -> do updateGroupMemberStatus db userId fromMember GSMemAccepted updateGroupMemberStatus db userId membership GSMemAccepted - pure cId - void (withAgent $ \a -> joinConnection a (aUserId user) (Just agentConnId) True connRequest dm PQSupportOff subMode) + void (withAgent $ \a -> joinConnection a (aUserId user) agentConnId True connRequest dm PQSupportOff subMode) `catchChatError` \e -> do withFastStore' $ \db -> do - deleteConnectionRecord db user cId updateGroupMemberStatus db userId fromMember GSMemInvited updateGroupMemberStatus db userId membership GSMemInvited - withAgent $ \a -> deleteConnectionAsync a False agentConnId throwError e updateCIGroupInvitationStatus user g CIGISAccepted `catchChatError` (toView . CRChatError (Just user)) pure $ CRUserAcceptedGroupSent user g {membership = membership {memberStatus = GSMemAccepted}} Nothing @@ -2615,7 +2639,7 @@ processChatCommand' vr = \case joinPreparedAgentConnection user pccConnId connId cReq dm pqSup subMode joinPreparedAgentConnection :: User -> Int64 -> ConnId -> ConnectionRequestUri m -> ByteString -> PQSupport -> SubscriptionMode -> CM () joinPreparedAgentConnection user pccConnId connId cReq connInfo pqSup subMode = do - void (withAgent $ \a -> joinConnection a (aUserId user) (Just connId) True cReq connInfo pqSup subMode) + void (withAgent $ \a -> joinConnection a (aUserId user) connId True cReq connInfo pqSup subMode) `catchChatError` \e -> do withFastStore' $ \db -> deleteConnectionRecord db user pccConnId withAgent $ \a -> deleteConnectionAsync a False connId @@ -2857,6 +2881,8 @@ processChatCommand' vr = \case connectPlan user (ACR SCMInvitation (CRInvitationUri crData e2e)) = do withFastStore' (\db -> getConnectionEntityByConnReq db vr user cReqSchemas) >>= \case Nothing -> pure $ CPInvitationLink ILPOk + Just (RcvDirectMsgConnection Connection {connStatus = ConnPrepared} Nothing) -> + pure $ CPInvitationLink ILPOk Just (RcvDirectMsgConnection conn ct_) -> do let Connection {connStatus, contactConnInitiated} = conn if @@ -3664,29 +3690,41 @@ getRcvFilePath fileId fPath_ fn keepHandle = case fPath_ of liftIO $ B.hPut h "" >> hFlush h | otherwise = liftIO $ B.writeFile fPath "" -acceptContactRequest :: User -> UserContactRequest -> Maybe IncognitoProfile -> Bool -> CM Contact -acceptContactRequest user UserContactRequest {agentInvitationId = AgentInvId invId, cReqChatVRange, localDisplayName = cName, profileId, profile = cp, userContactLinkId, xContactId, pqSupport} incognitoProfile contactUsed = do +acceptContactRequest :: User -> UserContactRequest -> IncognitoEnabled -> CM (Contact, Connection, SndQueueSecured) +acceptContactRequest user@User {userId} UserContactRequest {agentInvitationId = AgentInvId invId, contactId_, cReqChatVRange, localDisplayName = cName, profileId, profile = cp, userContactLinkId, xContactId, pqSupport} incognito = do subMode <- chatReadVar subscriptionMode let pqSup = PQSupportOn - vr <- chatVersionRange - let profileToSend = profileToSendOnAccept user incognitoProfile False - chatV = vr `peerConnChatVersion` cReqChatVRange pqSup' = pqSup `CR.pqSupportAnd` pqSupport + vr <- chatVersionRange + let chatV = vr `peerConnChatVersion` cReqChatVRange + (ct, conn, incognitoProfile) <- case contactId_ of + Nothing -> do + incognitoProfile <- if incognito then Just . NewIncognito <$> liftIO generateRandomProfile else pure Nothing + connId <- withAgent $ \a -> prepareConnectionToAccept a True invId pqSup' + (ct, conn) <- withStore' $ \db -> createAcceptedContact db user connId chatV cReqChatVRange cName profileId cp userContactLinkId xContactId incognitoProfile subMode pqSup' False + pure (ct, conn, incognitoProfile) + Just contactId -> do + ct <- withFastStore $ \db -> getContact db vr user contactId + case contactConn ct of + Nothing -> throwChatError $ CECommandError "contact has no connection" + Just conn@Connection {customUserProfileId} -> do + incognitoProfile <- forM customUserProfileId $ \pId -> withFastStore $ \db -> getProfileById db userId pId + pure (ct, conn, ExistingIncognito <$> incognitoProfile) + let profileToSend = profileToSendOnAccept user incognitoProfile False dm <- encodeConnInfoPQ pqSup' chatV $ XInfo profileToSend - (acId, sqSecured) <- withAgent $ \a -> acceptContact a True invId dm pqSup' subMode - let connStatus = if sqSecured then ConnSndReady else ConnNew - withStore' $ \db -> createAcceptedContact db user acId connStatus chatV cReqChatVRange cName profileId cp userContactLinkId xContactId incognitoProfile subMode pqSup' contactUsed + (ct,conn,) <$> withAgent (\a -> acceptContact a (aConnId conn) True invId dm pqSup' subMode) acceptContactRequestAsync :: User -> UserContactRequest -> Maybe IncognitoProfile -> Bool -> PQSupport -> CM Contact -acceptContactRequestAsync user UserContactRequest {agentInvitationId = AgentInvId invId, cReqChatVRange, localDisplayName = cName, profileId, profile = p, userContactLinkId, xContactId} incognitoProfile contactUsed pqSup = do +acceptContactRequestAsync user cReq@UserContactRequest {agentInvitationId = AgentInvId invId, cReqChatVRange, localDisplayName = cName, profileId, profile = p, userContactLinkId, xContactId} incognitoProfile contactUsed pqSup = do subMode <- chatReadVar subscriptionMode let profileToSend = profileToSendOnAccept user incognitoProfile False vr <- chatVersionRange let chatV = vr `peerConnChatVersion` cReqChatVRange (cmdId, acId) <- agentAcceptContactAsync user True invId (XInfo profileToSend) subMode pqSup chatV withStore' $ \db -> do - ct@Contact {activeConn} <- createAcceptedContact db user acId ConnNew chatV cReqChatVRange cName profileId p userContactLinkId xContactId incognitoProfile subMode pqSup contactUsed - forM_ activeConn $ \Connection {connId} -> setCommandConnId db user cmdId connId + (ct, Connection {connId}) <- createAcceptedContact db user acId chatV cReqChatVRange cName profileId p userContactLinkId xContactId incognitoProfile subMode pqSup contactUsed + deleteContactRequestRec db user cReq + setCommandConnId db user cmdId connId pure ct acceptGroupJoinRequestAsync :: User -> GroupInfo -> UserContactRequest -> GroupMemberRole -> Maybe IncognitoProfile -> CM GroupMember @@ -4551,6 +4589,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = lift $ setContactNetworkStatus ct' NSConnected toView $ CRContactConnected user ct' (fmap fromLocalProfile incognitoProfile) when (directOrUsed ct') $ do + unless (contactUsed ct') $ withFastStore' $ \db -> updateContactUsed db user ct' createInternalChatItem user (CDDirectRcv ct') (CIRcvDirectE2EEInfo $ E2EInfo pqEnc) Nothing createFeatureEnabledItems ct' when (contactConnInitiated conn') $ do diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index e13566aa7b..700dec9d2e 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -1416,6 +1416,10 @@ catchStoreError :: ExceptT StoreError IO a -> (StoreError -> ExceptT StoreError catchStoreError = catchAllErrors mkStoreError {-# INLINE catchStoreError #-} +tryStoreError' :: ExceptT StoreError IO a -> IO (Either StoreError a) +tryStoreError' = tryAllErrors' mkStoreError +{-# INLINE tryStoreError' #-} + mkStoreError :: SomeException -> StoreError mkStoreError = SEInternalError . show {-# INLINE mkStoreError #-} diff --git a/src/Simplex/Chat/Migrations/M20241010_contact_requests_contact_id.hs b/src/Simplex/Chat/Migrations/M20241010_contact_requests_contact_id.hs new file mode 100644 index 0000000000..24e7f3a98e --- /dev/null +++ b/src/Simplex/Chat/Migrations/M20241010_contact_requests_contact_id.hs @@ -0,0 +1,22 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Migrations.M20241010_contact_requests_contact_id where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20241010_contact_requests_contact_id :: Query +m20241010_contact_requests_contact_id = + [sql| +ALTER TABLE contact_requests ADD COLUMN contact_id INTEGER REFERENCES contacts ON DELETE CASCADE; + +CREATE INDEX idx_contact_requests_contact_id ON contact_requests(contact_id); +|] + +down_m20241010_contact_requests_contact_id :: Query +down_m20241010_contact_requests_contact_id = + [sql| +DROP INDEX idx_contact_requests_contact_id; + +ALTER TABLE contact_requests DROP COLUMN contact_id; +|] diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index ad13fb5db9..2619a5c4e5 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -327,6 +327,7 @@ CREATE TABLE contact_requests( peer_chat_min_version INTEGER NOT NULL DEFAULT 1, peer_chat_max_version INTEGER NOT NULL DEFAULT 1, pq_support INTEGER NOT NULL DEFAULT 0, + contact_id INTEGER REFERENCES contacts ON DELETE CASCADE, FOREIGN KEY(user_id, local_display_name) REFERENCES display_names(user_id, local_display_name) ON UPDATE CASCADE @@ -888,3 +889,4 @@ CREATE INDEX idx_chat_items_fwd_from_chat_item_id ON chat_items( CREATE INDEX idx_received_probes_group_member_id on received_probes( group_member_id ); +CREATE INDEX idx_contact_requests_contact_id ON contact_requests(contact_id); diff --git a/src/Simplex/Chat/Protocol.hs b/src/Simplex/Chat/Protocol.hs index f827568fa7..ea39293b9f 100644 --- a/src/Simplex/Chat/Protocol.hs +++ b/src/Simplex/Chat/Protocol.hs @@ -125,6 +125,17 @@ data ConnectionEntity $(JQ.deriveJSON (sumTypeJSON fstToLower) ''ConnectionEntity) +connEntityInfo :: ConnectionEntity -> String +connEntityInfo = \case + RcvDirectMsgConnection c ct_ -> ctInfo ct_ <> ", status: " <> show (connStatus c) + RcvGroupMsgConnection c g m -> mInfo g m <> ", status: " <> show (connStatus c) + SndFileConnection c _ft -> "snd file, status: " <> show (connStatus c) + RcvFileConnection c _ft -> "rcv file, status: " <> show (connStatus c) + UserContactConnection c _uc -> "user address, status: " <> show (connStatus c) + where + ctInfo = maybe "connection" $ \Contact {contactId} -> "contact " <> show contactId + mInfo GroupInfo {groupId} GroupMember {groupMemberId} = "group " <> show groupId <> ", member " <> show groupMemberId + updateEntityConnStatus :: ConnectionEntity -> ConnStatus -> ConnectionEntity updateEntityConnStatus connEntity connStatus = case connEntity of RcvDirectMsgConnection c ct_ -> RcvDirectMsgConnection (st c) ((\ct -> (ct :: Contact) {activeConn = Just $ st c}) <$> ct_) diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs index c0f007b6ac..4d33fa113d 100644 --- a/src/Simplex/Chat/Store/Direct.hs +++ b/src/Simplex/Chat/Store/Direct.hs @@ -62,6 +62,8 @@ module Simplex.Chat.Store.Direct getContactRequestIdByName, deleteContactRequest, createAcceptedContact, + deleteContactRequestRec, + updateContactAccepted, getUserByContactRequestId, getPendingContactConnections, updatePCCUser, @@ -69,6 +71,7 @@ module Simplex.Chat.Store.Direct getConnectionById, getConnectionsContacts, updateConnectionStatus, + updateConnectionStatusFromTo, updateContactSettings, setConnConnReqInv, resetContactConnInitiated, @@ -655,7 +658,7 @@ createOrUpdateContactRequest db vr user@User {userId} userContactLinkId invId (V db [sql| SELECT - cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.user_contact_link_id, + cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.user_contact_link_id, c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, cr.pq_support, p.preferences, cr.created_at, cr.updated_at, cr.peer_chat_min_version, cr.peer_chat_max_version FROM contact_requests cr @@ -724,7 +727,7 @@ getContactRequest db User {userId} contactRequestId = db [sql| SELECT - cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.user_contact_link_id, + cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.user_contact_link_id, c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, cr.pq_support, p.preferences, cr.created_at, cr.updated_at, cr.peer_chat_min_version, cr.peer_chat_max_version FROM contact_requests cr @@ -766,9 +769,8 @@ deleteContactRequest db User {userId} contactRequestId = do (userId, userId, contactRequestId, userId) DB.execute db "DELETE FROM contact_requests WHERE user_id = ? AND contact_request_id = ?" (userId, contactRequestId) -createAcceptedContact :: DB.Connection -> User -> ConnId -> ConnStatus -> VersionChat -> VersionRangeChat -> ContactName -> ProfileId -> Profile -> Int64 -> Maybe XContactId -> Maybe IncognitoProfile -> SubscriptionMode -> PQSupport -> Bool -> IO Contact -createAcceptedContact db user@User {userId, profile = LocalProfile {preferences}} agentConnId connStatus connChatVersion cReqChatVRange localDisplayName profileId profile userContactLinkId xContactId incognitoProfile subMode pqSup contactUsed = do - DB.execute db "DELETE FROM contact_requests WHERE user_id = ? AND local_display_name = ?" (userId, localDisplayName) +createAcceptedContact :: DB.Connection -> User -> ConnId -> VersionChat -> VersionRangeChat -> ContactName -> ProfileId -> Profile -> Int64 -> Maybe XContactId -> Maybe IncognitoProfile -> SubscriptionMode -> PQSupport -> Bool -> IO (Contact, Connection) +createAcceptedContact db user@User {userId, profile = LocalProfile {preferences}} agentConnId connChatVersion cReqChatVRange localDisplayName profileId profile userContactLinkId xContactId incognitoProfile subMode pqSup contactUsed = do createdAt <- getCurrentTime customUserProfileId <- forM incognitoProfile $ \case NewIncognito p -> createIncognitoProfile_ db userId createdAt p @@ -779,29 +781,42 @@ createAcceptedContact db user@User {userId, profile = LocalProfile {preferences} "INSERT INTO contacts (user_id, local_display_name, contact_profile_id, enable_ntfs, user_preferences, created_at, updated_at, chat_ts, xcontact_id, contact_used) VALUES (?,?,?,?,?,?,?,?,?,?)" (userId, localDisplayName, profileId, True, userPreferences, createdAt, createdAt, createdAt, xContactId, contactUsed) contactId <- insertedRowId db - conn <- createConnection_ db userId ConnContact (Just contactId) agentConnId connStatus connChatVersion cReqChatVRange Nothing (Just userContactLinkId) customUserProfileId 0 createdAt subMode pqSup + DB.execute db "UPDATE contact_requests SET contact_id = ? WHERE user_id = ? AND local_display_name = ?" (contactId, userId, localDisplayName) + conn <- createConnection_ db userId ConnContact (Just contactId) agentConnId ConnNew connChatVersion cReqChatVRange Nothing (Just userContactLinkId) customUserProfileId 0 createdAt subMode pqSup let mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito conn - pure $ - Contact - { contactId, - localDisplayName, - profile = toLocalProfile profileId profile "", - activeConn = Just conn, - viaGroup = Nothing, - contactUsed, - contactStatus = CSActive, - chatSettings = defaultChatSettings, - userPreferences, - mergedPreferences, - createdAt, - updatedAt = createdAt, - chatTs = Just createdAt, - contactGroupMemberId = Nothing, - contactGrpInvSent = False, - uiThemes = Nothing, - chatDeleted = False, - customData = Nothing - } + ct = + Contact + { contactId, + localDisplayName, + profile = toLocalProfile profileId profile "", + activeConn = Just conn, + viaGroup = Nothing, + contactUsed, + contactStatus = CSActive, + chatSettings = defaultChatSettings, + userPreferences, + mergedPreferences, + createdAt, + updatedAt = createdAt, + chatTs = Just createdAt, + contactGroupMemberId = Nothing, + contactGrpInvSent = False, + uiThemes = Nothing, + chatDeleted = False, + customData = Nothing + } + pure (ct, conn) + +deleteContactRequestRec :: DB.Connection -> User -> UserContactRequest -> IO () +deleteContactRequestRec db User {userId} UserContactRequest {contactRequestId} = + DB.execute db "DELETE FROM contact_requests WHERE user_id = ? AND contact_request_id = ?" (userId, contactRequestId) + +updateContactAccepted :: DB.Connection -> User -> Contact -> Bool -> IO () +updateContactAccepted db User {userId} Contact {contactId} contactUsed = + DB.execute + db + "UPDATE contacts SET contact_used = ? WHERE user_id = ? AND contact_id = ?" + (contactUsed, userId, contactId) getContactIdByName :: DB.Connection -> User -> ContactName -> ExceptT StoreError IO Int64 getContactIdByName db User {userId} cName = @@ -927,7 +942,17 @@ getConnectionsContacts db agentConnIds = do toContactRef (contactId, connId, acId, localDisplayName) = ContactRef {contactId, connId, agentConnId = AgentConnId acId, localDisplayName} updateConnectionStatus :: DB.Connection -> Connection -> ConnStatus -> IO () -updateConnectionStatus db Connection {connId} connStatus = do +updateConnectionStatus db Connection {connId} = updateConnectionStatus_ db connId +{-# INLINE updateConnectionStatus #-} + +updateConnectionStatusFromTo :: DB.Connection -> Int64 -> ConnStatus -> ConnStatus -> IO () +updateConnectionStatusFromTo db connId fromStatus toStatus = do + maybeFirstRow fromOnly (DB.query db "SELECT conn_status FROM connections WHERE connection_id = ?" (Only connId)) >>= \case + Just status | status == fromStatus -> updateConnectionStatus_ db connId toStatus + _ -> pure () + +updateConnectionStatus_ :: DB.Connection -> Int64 -> ConnStatus -> IO () +updateConnectionStatus_ db connId connStatus = do currentTs <- getCurrentTime if connStatus == ConnReady then DB.execute db "UPDATE connections SET conn_status = ?, updated_at = ?, conn_req_inv = NULL WHERE connection_id = ?" (connStatus, currentTs, connId) diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index 562a865276..ad77e6c3f1 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -882,7 +882,7 @@ getContactRequestChatPreviews_ db User {userId} pagination clq = case clq of db ( [sql| SELECT - cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.user_contact_link_id, + cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.user_contact_link_id, c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, cr.pq_support, p.preferences, cr.created_at, cr.updated_at as ts, cr.peer_chat_min_version, cr.peer_chat_max_version @@ -930,6 +930,7 @@ getContactConnectionChatPreviews_ db User {userId} pagination clq = case clq of FROM connections WHERE user_id = :user_id AND conn_type = :conn_contact + AND conn_status != :conn_status AND contact_id IS NULL AND conn_level = 0 AND via_contact IS NULL @@ -938,7 +939,7 @@ getContactConnectionChatPreviews_ db User {userId} pagination clq = case clq of |] <> pagQuery ) - ([":user_id" := userId, ":conn_contact" := ConnContact, ":search" := search] <> pagParams) + ([":user_id" := userId, ":conn_contact" := ConnContact, ":conn_status" := ConnPrepared, ":search" := search] <> pagParams) toPreview :: (Int64, ConnId, ConnStatus, Maybe ByteString, Maybe Int64, Maybe GroupLinkId, Maybe Int64, Maybe ConnReqInvitation, LocalAlias, UTCTime, UTCTime) -> AChatPreviewData toPreview connRow = let conn@PendingContactConnection {updatedAt} = toPendingContactConnection connRow diff --git a/src/Simplex/Chat/Store/Migrations.hs b/src/Simplex/Chat/Store/Migrations.hs index a546b1851a..e2d12e78d7 100644 --- a/src/Simplex/Chat/Store/Migrations.hs +++ b/src/Simplex/Chat/Store/Migrations.hs @@ -113,6 +113,7 @@ import Simplex.Chat.Migrations.M20240528_quota_err_counter import Simplex.Chat.Migrations.M20240827_calls_uuid import Simplex.Chat.Migrations.M20240920_user_order import Simplex.Chat.Migrations.M20241008_indexes +import Simplex.Chat.Migrations.M20241010_contact_requests_contact_id import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -225,7 +226,8 @@ schemaMigrations = ("20240528_quota_err_counter", m20240528_quota_err_counter, Just down_m20240528_quota_err_counter), ("20240827_calls_uuid", m20240827_calls_uuid, Just down_m20240827_calls_uuid), ("20240920_user_order", m20240920_user_order, Just down_m20240920_user_order), - ("20241008_indexes", m20241008_indexes, Just down_m20241008_indexes) + ("20241008_indexes", m20241008_indexes, Just down_m20241008_indexes), + ("20241010_contact_requests_contact_id", m20241010_contact_requests_contact_id, Just down_m20241010_contact_requests_contact_id) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index ba41cc47be..f9a8685ec8 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -411,13 +411,13 @@ getProfileById db userId profileId = toProfile :: (ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences) -> LocalProfile toProfile (displayName, fullName, image, contactLink, localAlias, preferences) = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} -type ContactRequestRow = (Int64, ContactName, AgentInvId, Int64, AgentConnId, Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact) :. (Maybe XContactId, PQSupport, Maybe Preferences, UTCTime, UTCTime, VersionChat, VersionChat) +type ContactRequestRow = (Int64, ContactName, AgentInvId, Maybe ContactId, Int64, AgentConnId, Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact) :. (Maybe XContactId, PQSupport, Maybe Preferences, UTCTime, UTCTime, VersionChat, VersionChat) toContactRequest :: ContactRequestRow -> UserContactRequest -toContactRequest ((contactRequestId, localDisplayName, agentInvitationId, userContactLinkId, agentContactConnId, profileId, displayName, fullName, image, contactLink) :. (xContactId, pqSupport, preferences, createdAt, updatedAt, minVer, maxVer)) = do +toContactRequest ((contactRequestId, localDisplayName, agentInvitationId, contactId_, userContactLinkId, agentContactConnId, profileId, displayName, fullName, image, contactLink) :. (xContactId, pqSupport, preferences, createdAt, updatedAt, minVer, maxVer)) = do let profile = Profile {displayName, fullName, image, contactLink, preferences} cReqChatVRange = fromMaybe (versionToRange maxVer) $ safeVersionRange minVer maxVer - in UserContactRequest {contactRequestId, agentInvitationId, userContactLinkId, agentContactConnId, cReqChatVRange, localDisplayName, profileId, profile, xContactId, pqSupport, createdAt, updatedAt} + in UserContactRequest {contactRequestId, agentInvitationId, contactId_, userContactLinkId, agentContactConnId, cReqChatVRange, localDisplayName, profileId, profile, xContactId, pqSupport, createdAt, updatedAt} userQuery :: Query userQuery = diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index 71fa1d98b9..36bf9edb52 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -297,6 +297,7 @@ userContactGroupId UserContact {groupId} = groupId data UserContactRequest = UserContactRequest { contactRequestId :: Int64, agentInvitationId :: AgentInvId, + contactId_ :: Maybe ContactId, userContactLinkId :: Int64, agentContactConnId :: AgentConnId, -- connection id of user contact cReqChatVRange :: VersionRangeChat, @@ -1371,6 +1372,8 @@ aConnId' PendingContactConnection {pccAgentConnId = AgentConnId cId} = cId data ConnStatus = -- | connection is created by initiating party with agent NEW command (createConnection) ConnNew + | -- | connection is prepared, to avoid changing keys on invitation links when retrying. + ConnPrepared | -- | connection is joined by joining party with agent JOIN command (joinConnection) ConnJoined | -- | initiating party received CONF notification (to be renamed to REQ) @@ -1399,6 +1402,7 @@ instance ToJSON ConnStatus where instance TextEncoding ConnStatus where textDecode = \case "new" -> Just ConnNew + "prepared" -> Just ConnPrepared "joined" -> Just ConnJoined "requested" -> Just ConnRequested "accepted" -> Just ConnAccepted @@ -1408,6 +1412,7 @@ instance TextEncoding ConnStatus where _ -> Nothing textEncode = \case ConnNew -> "new" + ConnPrepared -> "prepared" ConnJoined -> "joined" ConnRequested -> "requested" ConnAccepted -> "accepted" diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index 2ce04dbaca..36bdf92dbf 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -463,7 +463,7 @@ smpServerCfg = withSmpServer :: IO () -> IO () withSmpServer = withSmpServer' smpServerCfg -withSmpServer' :: ServerConfig -> IO () -> IO () +withSmpServer' :: ServerConfig -> IO a -> IO a withSmpServer' cfg = serverBracket (\started -> runSMPServerBlocking started cfg Nothing) xftpTestPort :: ServiceName @@ -515,7 +515,7 @@ withXFTPServer' cfg = runXFTPServerBlocking started cfg Nothing ) -serverBracket :: (TMVar Bool -> IO ()) -> IO () -> IO () +serverBracket :: (TMVar Bool -> IO ()) -> IO a -> IO a serverBracket server f = do started <- newEmptyTMVarIO bracket diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index c47cf975a1..8971e8d22d 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -1,9 +1,12 @@ {-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE PostfixOperators #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} +{-# OPTIONS_GHC -Wno-ambiguous-fields #-} module ChatTests.Direct where @@ -22,14 +25,18 @@ import Database.SQLite.Simple (Only (..)) import Simplex.Chat.AppSettings (defaultAppSettings) import qualified Simplex.Chat.AppSettings as AS import Simplex.Chat.Call -import Simplex.Chat.Controller (ChatConfig (..)) +import Simplex.Chat.Controller (ChatConfig (..), DefaultAgentServers (..)) import Simplex.Chat.Messages (ChatItemId) -import Simplex.Chat.Options (ChatOpts (..)) +import Simplex.Chat.Options import Simplex.Chat.Protocol (supportedChatVRange) import Simplex.Chat.Store (agentStoreFile, chatStoreFile) import Simplex.Chat.Types (VersionRangeChat, authErrDisableCount, sameVerificationCode, verificationCode, pattern VersionChat) +import Simplex.Messaging.Agent.Env.SQLite +import Simplex.Messaging.Agent.RetryInterval import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Crypto as C +import Simplex.Messaging.Server.Env.STM hiding (subscriptions) +import Simplex.Messaging.Transport import Simplex.Messaging.Util (safeDecodeUtf8) import Simplex.Messaging.Version import System.Directory (copyFile, doesDirectoryExist, doesFileExist) @@ -40,6 +47,8 @@ chatDirectTests :: SpecWith FilePath chatDirectTests = do describe "direct messages" $ do describe "add contact and send/receive messages" testAddContact + it "retry connecting via the same link" testRetryConnecting + xit'' "retry connecting via the same link with client timeout" testRetryConnectingClientTimeout it "mark multiple messages as read" testMarkReadDirect it "clear chat with contact" testContactClear it "deleting contact deletes profile" testDeleteContactDeletesProfile @@ -215,6 +224,126 @@ testAddContact = versionTestMatrix2 runTestAddContact then chatFeatures else (0, e2eeInfoNoPQStr) : tail chatFeatures +testRetryConnecting :: HasCallStack => FilePath -> IO () +testRetryConnecting tmp = testChatCfgOpts2 cfg' opts' aliceProfile bobProfile test tmp + where + test alice bob = do + inv <- withSmpServer' serverCfg' $ do + alice ##> "/_connect 1" + getInvitation alice + alice <## "server disconnected localhost ()" + bob ##> ("/_connect plan 1 " <> inv) + bob <## "invitation link: ok to connect" + bob ##> ("/_connect 1 " <> inv) + bob <##. "smp agent error: BROKER" + withSmpServer' serverCfg' $ do + alice <## "server connected localhost ()" + bob ##> ("/_connect plan 1 " <> inv) + bob <## "invitation link: ok to connect" + bob ##> ("/_connect 1 " <> inv) + bob <## "confirmation sent!" + concurrently_ + (bob <## "alice (Alice): contact is connected") + (alice <## "bob (Bob): contact is connected") + alice #> "@bob message 1" + bob <# "alice> message 1" + bob #> "@alice message 2" + alice <# "bob> message 2" + bob <## "server disconnected localhost (@alice)" + alice <## "server disconnected localhost (@bob)" + serverCfg' = + smpServerCfg + { transports = [("7003", transport @TLS, False)], + msgQueueQuota = 2, + storeLogFile = Just $ tmp <> "/smp-server-store.log", + storeMsgsFile = Just $ tmp <> "/smp-server-messages.log" + } + fastRetryInterval = defaultReconnectInterval {initialInterval = 50000} -- same as in agent tests + cfg' = + testCfg + { agentConfig = + testAgentCfg + { quotaExceededTimeout = 1, + messageRetryInterval = RetryInterval2 {riFast = fastRetryInterval, riSlow = fastRetryInterval} + } + } + opts' = + testOpts + { coreOptions = + testCoreOpts + { smpServers = ["smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7003"] + } + } + +testRetryConnectingClientTimeout :: HasCallStack => FilePath -> IO () +testRetryConnectingClientTimeout tmp = do + inv <- withSmpServer' serverCfg' $ do + withNewTestChatCfgOpts tmp cfg' opts' "alice" aliceProfile $ \alice -> do + alice ##> "/_connect 1" + inv <- getInvitation alice + + withNewTestChatCfgOpts tmp cfgZeroTimeout opts' "bob" bobProfile $ \bob -> do + bob ##> ("/_connect plan 1 " <> inv) + bob <## "invitation link: ok to connect" + bob ##> ("/_connect 1 " <> inv) + bob <## "smp agent error: BROKER {brokerAddress = \"smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=@localhost:7003\", brokerErr = TIMEOUT}" + + pure inv + + logFile <- readFile $ tmp <> "/smp-server-store.log" + logFile `shouldContain` "SECURE" + + withSmpServer' serverCfg' $ do + withTestChatCfgOpts tmp cfg' opts' "alice" $ \alice -> do + withTestChatCfgOpts tmp cfg' opts' "bob" $ \bob -> do + bob ##> ("/_connect plan 1 " <> inv) + bob <## "invitation link: ok to connect" + bob ##> ("/_connect 1 " <> inv) + bob <## "confirmation sent!" + + concurrently_ + (bob <## "alice (Alice): contact is connected") + (alice <## "bob (Bob): contact is connected") + alice #> "@bob message 1" + bob <# "alice> message 1" + bob #> "@alice message 2" + alice <# "bob> message 2" + where + serverCfg' = + smpServerCfg + { transports = [("7003", transport @TLS, False)], + msgQueueQuota = 2, + storeLogFile = Just $ tmp <> "/smp-server-store.log", + storeMsgsFile = Just $ tmp <> "/smp-server-messages.log" + } + fastRetryInterval = defaultReconnectInterval {initialInterval = 50000} -- same as in agent tests + cfg' = + testCfg + { agentConfig = + testAgentCfg + { quotaExceededTimeout = 1, + messageRetryInterval = RetryInterval2 {riFast = fastRetryInterval, riSlow = fastRetryInterval} + } + } + cfgZeroTimeout = + (testCfg :: ChatConfig) + { agentConfig = + testAgentCfg + { quotaExceededTimeout = 1, + messageRetryInterval = RetryInterval2 {riFast = fastRetryInterval, riSlow = fastRetryInterval} + }, + defaultServers = + let def@DefaultAgentServers {netCfg} = defaultServers testCfg + in def {netCfg = (netCfg :: NetworkConfig) {tcpTimeout = 10}} + } + opts' = + testOpts + { coreOptions = + testCoreOpts + { smpServers = ["smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7003"] + } + } + testMarkReadDirect :: HasCallStack => FilePath -> IO () testMarkReadDirect = testChat2 aliceProfile bobProfile $ \alice bob -> do connectUsers alice bob diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index 003fba7cfe..06ed9aa5bc 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -14,10 +14,14 @@ import Control.Monad.Except import qualified Data.Attoparsec.ByteString.Char8 as A import qualified Data.ByteString.Char8 as B import qualified Data.Text as T +import Simplex.Chat.Controller (ChatConfig (..)) +import Simplex.Chat.Options import Simplex.Chat.Store.Shared (createContact) import Simplex.Chat.Types (ConnStatus (..), Profile (..)) import Simplex.Chat.Types.Shared (GroupMemberRole (..)) import Simplex.Chat.Types.UITheme +import Simplex.Messaging.Agent.Env.SQLite +import Simplex.Messaging.Agent.RetryInterval import Simplex.Messaging.Encoding.String (StrEncoding (..)) import Simplex.Messaging.Server.Env.STM hiding (subscriptions) import Simplex.Messaging.Transport @@ -33,6 +37,7 @@ chatProfileTests = do it "use multiword profile names" testMultiWordProfileNames describe "user contact link" $ do it "create and connect via contact link" testUserContactLink + it "retry accepting connection via contact link" testRetryAcceptingViaContactLink it "add contact link to profile" testProfileLink it "auto accept contact requests" testUserContactLinkAutoAccept it "deduplicate contact requests" testDeduplicateContactRequests @@ -253,6 +258,66 @@ testUserContactLink = alice @@@ [("@cath", lastChatFeature), ("@bob", "hey")] alice <##> cath +testRetryAcceptingViaContactLink :: HasCallStack => FilePath -> IO () +testRetryAcceptingViaContactLink tmp = testChatCfgOpts2 cfg' opts' aliceProfile bobProfile test tmp + where + test alice bob = do + cLink <- withSmpServer' serverCfg' $ do + alice ##> "/ad" + getContactLink alice True + alice <## "server disconnected localhost ()" + bob ##> ("/_connect plan 1 " <> cLink) + bob <## "contact address: ok to connect" + bob ##> ("/_connect 1 " <> cLink) + bob <##. "smp agent error: BROKER" + withSmpServer' serverCfg' $ do + alice <## "server connected localhost ()" + bob ##> ("/_connect plan 1 " <> cLink) + bob <## "contact address: ok to connect" + bob ##> ("/_connect 1 " <> cLink) + alice <#? bob + alice <## "server disconnected localhost ()" + bob <## "server disconnected localhost ()" + alice ##> "/ac bob" + alice <##. "smp agent error: BROKER" + withSmpServer' serverCfg' $ do + alice <## "server connected localhost ()" + bob <## "server connected localhost ()" + alice ##> "/ac bob" + alice <## "bob (Bob): accepting contact request, you can send messages to contact" + concurrently_ + (bob <## "alice (Alice): contact is connected") + (alice <## "bob (Bob): contact is connected") + alice #> "@bob message 1" + bob <# "alice> message 1" + bob #> "@alice message 2" + alice <# "bob> message 2" + alice <## "server disconnected localhost (@bob)" + bob <## "server disconnected localhost (@alice)" + serverCfg' = + smpServerCfg + { transports = [("7003", transport @TLS, False)], + msgQueueQuota = 2, + storeLogFile = Just $ tmp <> "/smp-server-store.log", + storeMsgsFile = Just $ tmp <> "/smp-server-messages.log" + } + fastRetryInterval = defaultReconnectInterval {initialInterval = 50000} -- same as in agent tests + cfg' = + testCfg + { agentConfig = + testAgentCfg + { quotaExceededTimeout = 1, + messageRetryInterval = RetryInterval2 {riFast = fastRetryInterval, riSlow = fastRetryInterval} + } + } + opts' = + testOpts + { coreOptions = + testCoreOpts + { smpServers = ["smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7003"] + } + } + testProfileLink :: HasCallStack => FilePath -> IO () testProfileLink = testChat3 aliceProfile bobProfile cathProfile $ From dbe4504f05b49b65df1c05927a72bd1f11ca7a7c Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 11 Oct 2024 15:40:20 +0100 Subject: [PATCH 020/567] core: 6.1.0.8 (simplexmq: 6.1.0.7) --- cabal.project | 2 +- package.yaml | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- src/Simplex/Chat/Remote.hs | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cabal.project b/cabal.project index 515ad0312b..2df43da784 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 887044283079b91f5d1ce84372a7e1a5c31c379b + tag: c41bfe831d6d3fdf068f7419cbfed6afa46cb5b5 source-repository-package type: git diff --git a/package.yaml b/package.yaml index c7f844e077..4cd245590f 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.1.0.7 +version: 6.1.0.8 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 658f2fb648..487cbfa9dd 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."887044283079b91f5d1ce84372a7e1a5c31c379b" = "1lqlcyph9pn8ibwydi8m8bfcrswgs0dcsd867ldwr4xqnlkmd7qd"; + "https://github.com/simplex-chat/simplexmq.git"."c41bfe831d6d3fdf068f7419cbfed6afa46cb5b5" = "1awqy4srdgcwmjf7q3s9w75w6wp38qk65fza2k3q1a1s2lj6h8w1"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 5c3a313869..ca67d2c7f3 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.1.0.7 +version: 6.1.0.8 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index ff77ac0546..88539b55e3 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -73,11 +73,11 @@ import UnliftIO.Directory (copyFile, createDirectoryIfMissing, doesDirectoryExis -- when acting as host minRemoteCtrlVersion :: AppVersion -minRemoteCtrlVersion = AppVersion [6, 1, 0, 4] +minRemoteCtrlVersion = AppVersion [6, 1, 0, 8] -- when acting as controller minRemoteHostVersion :: AppVersion -minRemoteHostVersion = AppVersion [6, 1, 0, 4] +minRemoteHostVersion = AppVersion [6, 1, 0, 8] currentAppVersion :: AppVersion currentAppVersion = AppVersion SC.version From 7ff6ef09fefef6c815788bb2b72fb0c393b02564 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 11 Oct 2024 22:54:15 +0700 Subject: [PATCH 021/567] android: hide mic icon when mic is disabled (#5025) --- .../chat/simplex/common/views/call/CallView.android.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt index b35f47e36e..3fc5620222 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt @@ -194,7 +194,11 @@ actual fun ActiveCallView() { updateActiveCall(call) { val sources = it.localMediaSources when (cmd.source) { - CallMediaSource.Mic -> it.copy(localMediaSources = sources.copy(mic = cmd.enable)) + CallMediaSource.Mic -> { + val am = androidAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager + am.isMicrophoneMute = !cmd.enable + it.copy(localMediaSources = sources.copy(mic = cmd.enable)) + } CallMediaSource.Camera -> it.copy(localMediaSources = sources.copy(camera = cmd.enable)) CallMediaSource.ScreenAudio -> it.copy(localMediaSources = sources.copy(screenAudio = cmd.enable)) CallMediaSource.ScreenVideo -> it.copy(localMediaSources = sources.copy(screenVideo = cmd.enable)) From 83f42704ea58a8dd88f74cc1f79674b18ffec124 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 11 Oct 2024 17:08:32 +0100 Subject: [PATCH 022/567] ui: translations (#5026) * Translated using Weblate (Italian) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Turkish) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/tr/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (German) Currently translated at 99.7% (1839 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (German) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (Dutch) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/ * Translated using Weblate (Japanese) Currently translated at 65.1% (1201 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ja/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Russian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (Russian) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ru/ * process localizations --------- Co-authored-by: Random Co-authored-by: No name Co-authored-by: Abdullah Koyuncu Co-authored-by: Ghost of Sparta Co-authored-by: summoner001 Co-authored-by: mlanp Co-authored-by: M1K4 Co-authored-by: acevif --- .../de.xcloc/Localized Contents/de.xliff | 13 ++++ .../es.xcloc/Localized Contents/es.xliff | 13 ++++ .../hu.xcloc/Localized Contents/hu.xliff | 61 +++++++++------ .../it.xcloc/Localized Contents/it.xliff | 13 ++++ .../ja.xcloc/Localized Contents/ja.xliff | 2 + .../nl.xcloc/Localized Contents/nl.xliff | 13 ++++ .../ru.xcloc/Localized Contents/ru.xliff | 13 ++++ .../tr.xcloc/Localized Contents/tr.xliff | 13 ++++ apps/ios/de.lproj/Localizable.strings | 36 +++++++++ apps/ios/es.lproj/Localizable.strings | 36 +++++++++ apps/ios/hu.lproj/Localizable.strings | 78 ++++++++++++++----- .../hu.lproj/SimpleX--iOS--InfoPlist.strings | 6 +- apps/ios/it.lproj/Localizable.strings | 36 +++++++++ apps/ios/ja.lproj/Localizable.strings | 6 ++ apps/ios/nl.lproj/Localizable.strings | 36 +++++++++ apps/ios/ru.lproj/Localizable.strings | 39 ++++++++++ apps/ios/tr.lproj/Localizable.strings | 36 +++++++++ .../commonMain/resources/MR/es/strings.xml | 2 +- .../commonMain/resources/MR/hu/strings.xml | 41 +++++----- .../commonMain/resources/MR/ru/strings.xml | 2 +- 20 files changed, 424 insertions(+), 71 deletions(-) diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 23150926f2..7ab1e3a588 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -1087,6 +1087,7 @@ Better calls + Verbesserte Anrufe No comment provided by engineer. @@ -1096,6 +1097,7 @@ Better message dates. + Verbesserte Nachrichten-Datumsinformation No comment provided by engineer. @@ -1110,14 +1112,17 @@ Better notifications + Verbesserte Benachrichtigungen No comment provided by engineer. Better security ✅ + Verbesserte Sicherheit ✅ No comment provided by engineer. Better user experience + Verbesserte Nutzer-Erfahrung No comment provided by engineer. @@ -1942,6 +1947,7 @@ Das ist Ihr eigener Einmal-Link! Customizable message shape. + Anpassbares Format des Nachrichtenfelds No comment provided by engineer. @@ -2240,6 +2246,7 @@ Das ist Ihr eigener Einmal-Link! Delete or moderate up to 200 messages. + Bis zu 200 Nachrichten löschen oder moderieren No comment provided by engineer. @@ -3367,6 +3374,7 @@ Das ist Ihr eigener Einmal-Link! Forward up to 20 messages at once. + Bis zu 20 Nachrichten auf einmal weiterleiten No comment provided by engineer. @@ -3761,6 +3769,8 @@ Fehler: %2$@ Improved delivery, reduced traffic usage. More improvements are coming soon! + Verbesserte Nachrichten-Auslieferung und verringerter Datenverbrauch. +Weitere Verbesserungen sind bald verfügbar! No comment provided by engineer. @@ -6463,6 +6473,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. SimpleX protocols reviewed by Trail of Bits. + Die SimpleX-Protokolle wurden von Trail of Bits überprüft. No comment provided by engineer. @@ -6642,10 +6653,12 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Switch audio and video during the call. + Während des Anrufs zwischen Audio und Video wechseln No comment provided by engineer. Switch chat profile for 1-time invitations. + Das Chat-Profil für Einmal-Einladungen wechseln No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 6816181c21..617a693c7d 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -1087,6 +1087,7 @@ Better calls + Llamadas mejoradas No comment provided by engineer. @@ -1096,6 +1097,7 @@ Better message dates. + Sistema de fechas mejorado. No comment provided by engineer. @@ -1110,14 +1112,17 @@ Better notifications + Notificaciones mejoradas No comment provided by engineer. Better security ✅ + Seguridad mejorada ✅ No comment provided by engineer. Better user experience + Experiencia de usuario mejorada No comment provided by engineer. @@ -1942,6 +1947,7 @@ This is your own one-time link! Customizable message shape. + Forma personalizable de los mensajes. No comment provided by engineer. @@ -2240,6 +2246,7 @@ This is your own one-time link! Delete or moderate up to 200 messages. + Borrar o moderar hasta 200 mensajes. No comment provided by engineer. @@ -3367,6 +3374,7 @@ This is your own one-time link! Forward up to 20 messages at once. + Desplazamiento de hasta 20 mensajes. No comment provided by engineer. @@ -3761,6 +3769,8 @@ Error: %2$@ Improved delivery, reduced traffic usage. More improvements are coming soon! + Reducción del tráfico y entrega mejorada. +¡Pronto habrá nuevas mejoras! No comment provided by engineer. @@ -6463,6 +6473,7 @@ Actívalo en ajustes de *Servidores y Redes*. SimpleX protocols reviewed by Trail of Bits. + Protocolos de SimpleX auditados por Trail of Bits. No comment provided by engineer. @@ -6642,10 +6653,12 @@ Actívalo en ajustes de *Servidores y Redes*. Switch audio and video during the call. + Intercambia audio y video durante la llamada. No comment provided by engineer. Switch chat profile for 1-time invitations. + Cambia el perfil de chat para invitaciones de un solo uso. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 61cf467bd0..16eb6e8288 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -762,7 +762,7 @@ All your contacts will remain connected. Profile update will be sent to your contacts. - Az ismerőseivel kapcsolatban marad. A profil változtatások frissítésre kerülnek az ismerősöknél. + Az ismerőseivel kapcsolatban marad. A profil-változtatások frissítésre kerülnek az ismerősöknél. No comment provided by engineer. @@ -1087,6 +1087,7 @@ Better calls + Továbbfejlesztett hívásélmény No comment provided by engineer. @@ -1096,6 +1097,7 @@ Better message dates. + Továbbfejlesztett üzenetdátumok. No comment provided by engineer. @@ -1110,14 +1112,17 @@ Better notifications + Továbbfejlesztett értesítések No comment provided by engineer. Better security ✅ + Továbbfejlesztett biztonság ✅ No comment provided by engineer. Better user experience + Továbbfejlesztett felhasználói élmény No comment provided by engineer. @@ -1408,7 +1413,7 @@ Chat profile - Felhasználói profil + Csevegési profil No comment provided by engineer. @@ -1942,6 +1947,7 @@ Ez az Ön egyszer használható hivatkozása! Customizable message shape. + Testreszabható üzenetbuborékok. No comment provided by engineer. @@ -2240,6 +2246,7 @@ Ez az Ön egyszer használható hivatkozása! Delete or moderate up to 200 messages. + Legfeljebb 200 üzenet egyszerre való törlése, vagy moderálása. No comment provided by engineer. @@ -2910,7 +2917,7 @@ Ez az Ön egyszer használható hivatkozása! Error deleting user profile - Hiba a felhasználói profil törlésekor + Hiba a felhasználó-profil törlésekor No comment provided by engineer. @@ -3025,7 +3032,7 @@ Ez az Ön egyszer használható hivatkozása! Error saving user password - Hiba a felhasználó jelszavának mentésekor + Hiba a felhasználói jelszó mentésekor No comment provided by engineer. @@ -3095,7 +3102,7 @@ Ez az Ön egyszer használható hivatkozása! Error updating user privacy - Hiba a felhasználói beállítások frissítésekor + Hiba a felhasználói adatvédelem frissítésekor No comment provided by engineer. @@ -3367,6 +3374,7 @@ Ez az Ön egyszer használható hivatkozása! Forward up to 20 messages at once. + Legfeljebb 20 üzenet egyszerre való továbbítása. No comment provided by engineer. @@ -3615,7 +3623,7 @@ Hiba: %2$@ Hide - Elrejtés + Összecsukás chat item action @@ -3700,7 +3708,7 @@ Hiba: %2$@ If you need to use the chat now tap **Do it later** below (you will be offered to migrate the database when you restart the app). - Ha most kell használnia a csevegést, koppintson alább az **Befejezés később** lehetőségre (az alkalmazás újraindításakor felajánlásra kerül az adatbázis átköltöztetése). + Ha most kell használnia a csevegést, koppintson alább a **Befejezés később** lehetőségre (az alkalmazás újraindításakor felajánlásra kerül az adatbázis átköltöztetése). No comment provided by engineer. @@ -3761,6 +3769,8 @@ Hiba: %2$@ Improved delivery, reduced traffic usage. More improvements are coming soon! + Továbbfejlesztett kézbesítés, csökkentett adatforgalom-használat. +További fejlesztések hamarosan! No comment provided by engineer. @@ -4205,7 +4215,7 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Sokan kérdezték: *ha a SimpleX Chatnek nincsenek felhasználói azonosítói, akkor hogyan tud üzeneteket kézbesíteni?* + Sokan kérdezték: *ha a SimpleX Chatnek nincsenek felhasználó-azonosítói, akkor hogyan tud üzeneteket kézbesíteni?* No comment provided by engineer. @@ -4345,12 +4355,12 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! Message status - Üzenetállapot + Üzenet állapota No comment provided by engineer. Message status: %@ - Üzenetállapot: %@ + Üzenet állapota: %@ copied message info @@ -4783,7 +4793,7 @@ VPN engedélyezése szükséges. Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. - Csak az eszközök alkalmazásai tárolják a felhasználói profilokat, névjegyeket, csoportokat és a **2 rétegű végpontok közötti titkosítással** küldött üzeneteket. + Csak az eszközök alkalmazásai tárolják a felhasználó-profilokat, névjegyeket, csoportokat és a **2 rétegű végpontok közötti titkosítással** küldött üzeneteket. No comment provided by engineer. @@ -4893,7 +4903,7 @@ VPN engedélyezése szükséges. Open user profiles - Felhasználói profilok megnyitása + Felhasználó-profilok megnyitása authentication reason @@ -5808,7 +5818,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Save profile password - Felhasználói fiók jelszavának mentése + Profiljelszó mentése No comment provided by engineer. @@ -6463,6 +6473,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. SimpleX protocols reviewed by Trail of Bits. + A SimpleX Chat biztonsága a Trail of Bits által lett újraauditálva. No comment provided by engineer. @@ -6642,10 +6653,12 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Switch audio and video during the call. + Hang/Videó váltása hívás közben. No comment provided by engineer. Switch chat profile for 1-time invitations. + Csevegési profilváltás az egyszer használható meghívókhoz. No comment provided by engineer. @@ -6700,7 +6713,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Tap to Connect - Koppintson a kapcsolódáshoz + Koppintson ide a kapcsolódáshoz No comment provided by engineer. @@ -6710,12 +6723,12 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Tap to join - Koppintson a csatlakozáshoz + Koppintson ide a csatlakozáshoz No comment provided by engineer. Tap to join incognito - Koppintson az inkognitóban való csatlakozáshoz + Koppintson ide az inkognitóban való csatlakozáshoz No comment provided by engineer. @@ -6725,7 +6738,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Tap to scan - Koppintson a beolvasáshoz + Koppintson ide a QR-kód beolvasáshoz No comment provided by engineer. @@ -6770,7 +6783,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. The 1st platform without any user identifiers – private by design. - Az első csevegési rendszer bármiféle felhasználó azonosító nélkül - privátra lett tervezre. + Az első csevegési rendszer bármiféle felhasználó-azonosító nélkül - privátra lett tervezre. No comment provided by engineer. @@ -6992,7 +7005,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Az adatvédelem érdekében, a más csevegési platformokon megszokott felhasználói azonosítók helyett, a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, minden egyes ismerőshöz egy-egy különbözőt. + Az adatvédelem érdekében, a más csevegési platformokon megszokott felhasználó-azonosítók helyett, a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, minden egyes ismerőshöz egy-egy különbözőt. No comment provided by engineer. @@ -7728,7 +7741,7 @@ Csatlakozáskérés megismétlése? You can enable them later via app Privacy & Security settings. - Később engedélyezheti őket az alkalmazás „Adatvédelem és biztonság” menüben. + Később engedélyezheti őket az alkalmazás „Adatvédelem és biztonság” menüjében. No comment provided by engineer. @@ -7738,7 +7751,7 @@ Csatlakozáskérés megismétlése? You can hide or mute a user profile - swipe it to the right. - Elrejtheti vagy lenémíthatja a felhasználó profiljait - csúsztassa jobbra a profilt. + Elrejtheti vagy lenémíthatja a felhasználó -profiljait - csúsztassa jobbra a profilt. No comment provided by engineer. @@ -9068,7 +9081,7 @@ utoljára fogadott üzenet: %2$@ SimpleX needs camera access to scan QR codes to connect to other users and for video calls. - A SimpleX-nek kamera-hozzáférésre van szüksége a QR-kódok beolvasásához, hogy kapcsolódhasson más felhasználókhoz és videohívásokhoz. + A SimpleXnek kamera-hozzáférésre van szüksége a QR-kódok beolvasásához, hogy kapcsolódhasson más felhasználókhoz és videohívásokhoz. Privacy - Camera Usage Description @@ -9083,12 +9096,12 @@ utoljára fogadott üzenet: %2$@ SimpleX needs microphone access for audio and video calls, and to record voice messages. - A SimpleX-nek mikrofon-hozzáférésre van szüksége hang- és videohívásokhoz, valamint hangüzenetek rögzítéséhez. + A SimpleXnek mikrofon-hozzáférésre van szüksége hang- és videohívásokhoz, valamint hangüzenetek rögzítéséhez. Privacy - Microphone Usage Description SimpleX needs access to Photo Library for saving captured and received media - A SimpleX-nek hozzáférésre van szüksége a Galériához a rögzített és fogadott média mentéséhez + A SimpleXnek galéria-hozzáférésre van szüksége a rögzített és fogadott média mentéséhez Privacy - Photo Library Additions Usage Description diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 46c2a63aff..488d51d225 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -1087,6 +1087,7 @@ Better calls + Chiamate migliorate No comment provided by engineer. @@ -1096,6 +1097,7 @@ Better message dates. + Date dei messaggi migliorate. No comment provided by engineer. @@ -1110,14 +1112,17 @@ Better notifications + Notifiche migliorate No comment provided by engineer. Better security ✅ + Sicurezza migliorata ✅ No comment provided by engineer. Better user experience + Esperienza utente migliorata No comment provided by engineer. @@ -1942,6 +1947,7 @@ Questo è il tuo link una tantum! Customizable message shape. + Forma dei messaggi personalizzabile. No comment provided by engineer. @@ -2240,6 +2246,7 @@ Questo è il tuo link una tantum! Delete or moderate up to 200 messages. + Elimina o modera fino a 200 messaggi. No comment provided by engineer. @@ -3367,6 +3374,7 @@ Questo è il tuo link una tantum! Forward up to 20 messages at once. + Inoltra fino a 20 messaggi alla volta. No comment provided by engineer. @@ -3761,6 +3769,8 @@ Errore: %2$@ Improved delivery, reduced traffic usage. More improvements are coming soon! + Consegna migliorata, utilizzo di traffico ridotto. +Altri miglioramenti sono in arrivo! No comment provided by engineer. @@ -6463,6 +6473,7 @@ Attivalo nelle impostazioni *Rete e server*. SimpleX protocols reviewed by Trail of Bits. + Protocolli di SimpleX esaminati da Trail of Bits. No comment provided by engineer. @@ -6642,10 +6653,12 @@ Attivalo nelle impostazioni *Rete e server*. Switch audio and video during the call. + Cambia tra audio e video durante la chiamata. No comment provided by engineer. Switch chat profile for 1-time invitations. + Cambia profilo di chat per inviti una tantum. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index eb8d43fb11..4fa8144d91 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -4121,10 +4121,12 @@ This is your link for group %@! Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. + メッセージ、ファイル、通話は、前方秘匿性、否認可能性および侵入復元性を備えた**エンドツーエンドの暗号化**によって保護されます。 No comment provided by engineer. Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. + メッセージ、ファイル、通話は、前方秘匿性、否認可能性および侵入復元性を備えた**耐量子E2E暗号化**によって保護されます。 No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 47030eb058..0bc47c81ea 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -1087,6 +1087,7 @@ Better calls + Betere gesprekken No comment provided by engineer. @@ -1096,6 +1097,7 @@ Better message dates. + Betere datums voor berichten. No comment provided by engineer. @@ -1110,14 +1112,17 @@ Better notifications + Betere meldingen No comment provided by engineer. Better security ✅ + Betere beveiliging ✅ No comment provided by engineer. Better user experience + Betere gebruikerservaring No comment provided by engineer. @@ -1942,6 +1947,7 @@ Dit is uw eigen eenmalige link! Customizable message shape. + Aanpasbare berichtvorm. No comment provided by engineer. @@ -2240,6 +2246,7 @@ Dit is uw eigen eenmalige link! Delete or moderate up to 200 messages. + Maximaal 200 berichten verwijderen of modereren. No comment provided by engineer. @@ -3367,6 +3374,7 @@ Dit is uw eigen eenmalige link! Forward up to 20 messages at once. + Stuur maximaal 20 berichten tegelijk door. No comment provided by engineer. @@ -3761,6 +3769,8 @@ Fout: %2$@ Improved delivery, reduced traffic usage. More improvements are coming soon! + Verbeterde levering, minder data gebruik. +Binnenkort meer verbeteringen! No comment provided by engineer. @@ -6463,6 +6473,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. SimpleX protocols reviewed by Trail of Bits. + SimpleX-protocollen beoordeeld door Trail of Bits. No comment provided by engineer. @@ -6642,10 +6653,12 @@ Schakel dit in in *Netwerk en servers*-instellingen. Switch audio and video during the call. + Wissel tussen audio en video tijdens het gesprek. No comment provided by engineer. Switch chat profile for 1-time invitations. + Wijzig chatprofiel voor eenmalige uitnodigingen. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index c7f5cb26ed..ced93b4c12 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -139,6 +139,7 @@ %1$@, %2$@ + %1$@, %2$@ format for date separator in chat @@ -1086,6 +1087,7 @@ Better calls + Улучшенные звонки No comment provided by engineer. @@ -1095,6 +1097,7 @@ Better message dates. + Улучшенные даты сообщений. No comment provided by engineer. @@ -1109,14 +1112,17 @@ Better notifications + Улучшенные уведомления No comment provided by engineer. Better security ✅ + Улучшенная безопасность ✅ No comment provided by engineer. Better user experience + Улучшенный интерфейс No comment provided by engineer. @@ -1941,6 +1947,7 @@ This is your own one-time link! Customizable message shape. + Настраиваемая форма сообщений. No comment provided by engineer. @@ -2239,6 +2246,7 @@ This is your own one-time link! Delete or moderate up to 200 messages. + Удаляйте или модерируйте до 200 сообщений. No comment provided by engineer. @@ -3366,6 +3374,7 @@ This is your own one-time link! Forward up to 20 messages at once. + Пересылайте до 20 сообщений за раз. No comment provided by engineer. @@ -3760,6 +3769,7 @@ Error: %2$@ Improved delivery, reduced traffic usage. More improvements are coming soon! + Улучшенная доставка, меньше трафик. No comment provided by engineer. @@ -6462,6 +6472,7 @@ Enable in *Network & servers* settings. SimpleX protocols reviewed by Trail of Bits. + Аудит SimpleX протоколов от Trail of Bits. No comment provided by engineer. @@ -6641,10 +6652,12 @@ Enable in *Network & servers* settings. Switch audio and video during the call. + Переключайте звук и видео во время звонка. No comment provided by engineer. Switch chat profile for 1-time invitations. + Переключайте профиль чата для одноразовых приглашений. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index 3bc063ecc3..b911eb1220 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -1087,6 +1087,7 @@ Better calls + Daha iyi aramalar No comment provided by engineer. @@ -1096,6 +1097,7 @@ Better message dates. + Daha iyi mesaj tarihleri. No comment provided by engineer. @@ -1110,14 +1112,17 @@ Better notifications + Daha iyi bildirimler No comment provided by engineer. Better security ✅ + Daha iyi güvenlik ✅ No comment provided by engineer. Better user experience + Daha iyi kullanıcı deneyimi No comment provided by engineer. @@ -1942,6 +1947,7 @@ Bu senin kendi tek kullanımlık bağlantın! Customizable message shape. + Özelleştirilebilir mesaj şekli. No comment provided by engineer. @@ -2240,6 +2246,7 @@ Bu senin kendi tek kullanımlık bağlantın! Delete or moderate up to 200 messages. + 200'e kadar mesajı silin veya düzenleyin. No comment provided by engineer. @@ -3367,6 +3374,7 @@ Bu senin kendi tek kullanımlık bağlantın! Forward up to 20 messages at once. + Aynı anda en fazla 20 mesaj iletin. No comment provided by engineer. @@ -3761,6 +3769,8 @@ Hata: %2$@ Improved delivery, reduced traffic usage. More improvements are coming soon! + İyileştirilmiş teslimat, azaltılmış trafik kullanımı. +Daha fazla iyileştirme yakında geliyor! No comment provided by engineer. @@ -6463,6 +6473,7 @@ Enable in *Network & servers* settings. SimpleX protocols reviewed by Trail of Bits. + SimpleX protokolleri Trail of Bits tarafından incelenmiştir. No comment provided by engineer. @@ -6642,10 +6653,12 @@ Enable in *Network & servers* settings. Switch audio and video during the call. + Görüşme sırasında ses ve görüntüyü değiştirin. No comment provided by engineer. Switch chat profile for 1-time invitations. + Sohbet profilini 1 kerelik davetler için değiştirin. No comment provided by engineer. diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index df9d096064..7334314c3e 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -694,15 +694,30 @@ /* No comment provided by engineer. */ "Bad message ID" = "Falsche Nachrichten-ID"; +/* No comment provided by engineer. */ +"Better calls" = "Verbesserte Anrufe"; + /* No comment provided by engineer. */ "Better groups" = "Bessere Gruppen"; +/* No comment provided by engineer. */ +"Better message dates." = "Verbesserte Nachrichten-Datumsinformation"; + /* No comment provided by engineer. */ "Better messages" = "Verbesserungen bei Nachrichten"; /* No comment provided by engineer. */ "Better networking" = "Kontrollieren Sie Ihr Netzwerk"; +/* No comment provided by engineer. */ +"Better notifications" = "Verbesserte Benachrichtigungen"; + +/* No comment provided by engineer. */ +"Better security ✅" = "Verbesserte Sicherheit ✅"; + +/* No comment provided by engineer. */ +"Better user experience" = "Verbesserte Nutzer-Erfahrung"; + /* No comment provided by engineer. */ "Black" = "Schwarz"; @@ -1292,6 +1307,9 @@ /* No comment provided by engineer. */ "Custom time" = "Zeit anpassen"; +/* No comment provided by engineer. */ +"Customizable message shape." = "Anpassbares Format des Nachrichtenfelds"; + /* No comment provided by engineer. */ "Customize theme" = "Design anpassen"; @@ -1482,6 +1500,9 @@ /* No comment provided by engineer. */ "Delete old database?" = "Alte Datenbank löschen?"; +/* No comment provided by engineer. */ +"Delete or moderate up to 200 messages." = "Bis zu 200 Nachrichten löschen oder moderieren"; + /* No comment provided by engineer. */ "Delete pending connection?" = "Ausstehende Verbindung löschen?"; @@ -2230,6 +2251,9 @@ /* alert message */ "Forward messages without files?" = "Nachrichten ohne Dateien weiterleiten?"; +/* No comment provided by engineer. */ +"Forward up to 20 messages at once." = "Bis zu 20 Nachrichten auf einmal weiterleiten"; + /* No comment provided by engineer. */ "forwarded" = "weitergeleitet"; @@ -2470,6 +2494,9 @@ /* No comment provided by engineer. */ "Importing archive" = "Archiv wird importiert"; +/* No comment provided by engineer. */ +"Improved delivery, reduced traffic usage.\nMore improvements are coming soon!" = "Verbesserte Nachrichten-Auslieferung und verringerter Datenverbrauch.\nWeitere Verbesserungen sind bald verfügbar!"; + /* No comment provided by engineer. */ "Improved message delivery" = "Verbesserte Zustellung von Nachrichten"; @@ -4271,6 +4298,9 @@ /* simplex link type */ "SimpleX one-time invitation" = "SimpleX-Einmal-Einladung"; +/* No comment provided by engineer. */ +"SimpleX protocols reviewed by Trail of Bits." = "Die SimpleX-Protokolle wurden von Trail of Bits überprüft."; + /* No comment provided by engineer. */ "Simplified incognito mode" = "Vereinfachter Inkognito-Modus"; @@ -4391,6 +4421,12 @@ /* No comment provided by engineer. */ "Support SimpleX Chat" = "Unterstützung von SimpleX Chat"; +/* No comment provided by engineer. */ +"Switch audio and video during the call." = "Während des Anrufs zwischen Audio und Video wechseln"; + +/* No comment provided by engineer. */ +"Switch chat profile for 1-time invitations." = "Das Chat-Profil für Einmal-Einladungen wechseln"; + /* No comment provided by engineer. */ "System" = "System"; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 198fe3bd70..196be9aba2 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -694,15 +694,30 @@ /* No comment provided by engineer. */ "Bad message ID" = "ID de mensaje incorrecto"; +/* No comment provided by engineer. */ +"Better calls" = "Llamadas mejoradas"; + /* No comment provided by engineer. */ "Better groups" = "Grupos mejorados"; +/* No comment provided by engineer. */ +"Better message dates." = "Sistema de fechas mejorado."; + /* No comment provided by engineer. */ "Better messages" = "Mensajes mejorados"; /* No comment provided by engineer. */ "Better networking" = "Uso de red mejorado"; +/* No comment provided by engineer. */ +"Better notifications" = "Notificaciones mejoradas"; + +/* No comment provided by engineer. */ +"Better security ✅" = "Seguridad mejorada ✅"; + +/* No comment provided by engineer. */ +"Better user experience" = "Experiencia de usuario mejorada"; + /* No comment provided by engineer. */ "Black" = "Negro"; @@ -1292,6 +1307,9 @@ /* No comment provided by engineer. */ "Custom time" = "Tiempo personalizado"; +/* No comment provided by engineer. */ +"Customizable message shape." = "Forma personalizable de los mensajes."; + /* No comment provided by engineer. */ "Customize theme" = "Personalizar tema"; @@ -1482,6 +1500,9 @@ /* No comment provided by engineer. */ "Delete old database?" = "¿Eliminar base de datos antigua?"; +/* No comment provided by engineer. */ +"Delete or moderate up to 200 messages." = "Borrar o moderar hasta 200 mensajes."; + /* No comment provided by engineer. */ "Delete pending connection?" = "¿Eliminar conexión pendiente?"; @@ -2230,6 +2251,9 @@ /* alert message */ "Forward messages without files?" = "¿Reenviar mensajes sin los archivos?"; +/* No comment provided by engineer. */ +"Forward up to 20 messages at once." = "Desplazamiento de hasta 20 mensajes."; + /* No comment provided by engineer. */ "forwarded" = "reenviado"; @@ -2470,6 +2494,9 @@ /* No comment provided by engineer. */ "Importing archive" = "Importando archivo"; +/* No comment provided by engineer. */ +"Improved delivery, reduced traffic usage.\nMore improvements are coming soon!" = "Reducción del tráfico y entrega mejorada.\n¡Pronto habrá nuevas mejoras!"; + /* No comment provided by engineer. */ "Improved message delivery" = "Entrega de mensajes mejorada"; @@ -4271,6 +4298,9 @@ /* simplex link type */ "SimpleX one-time invitation" = "Invitación SimpleX de un uso"; +/* No comment provided by engineer. */ +"SimpleX protocols reviewed by Trail of Bits." = "Protocolos de SimpleX auditados por Trail of Bits."; + /* No comment provided by engineer. */ "Simplified incognito mode" = "Modo incógnito simplificado"; @@ -4391,6 +4421,12 @@ /* No comment provided by engineer. */ "Support SimpleX Chat" = "Soporte SimpleX Chat"; +/* No comment provided by engineer. */ +"Switch audio and video during the call." = "Intercambia audio y video durante la llamada."; + +/* No comment provided by engineer. */ +"Switch chat profile for 1-time invitations." = "Cambia el perfil de chat para invitaciones de un solo uso."; + /* No comment provided by engineer. */ "System" = "Sistema"; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index ab598c2957..f2fc640d13 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -479,7 +479,7 @@ "All your contacts will remain connected." = "Minden ismerősével kapcsolatban marad."; /* No comment provided by engineer. */ -"All your contacts will remain connected. Profile update will be sent to your contacts." = "Az ismerőseivel kapcsolatban marad. A profil változtatások frissítésre kerülnek az ismerősöknél."; +"All your contacts will remain connected. Profile update will be sent to your contacts." = "Az ismerőseivel kapcsolatban marad. A profil-változtatások frissítésre kerülnek az ismerősöknél."; /* No comment provided by engineer. */ "All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Minden ismerőse, a beszélgetései és a fájljai biztonságosan titkosításra kerülnek, melyek részletekben feltöltődnek a beállított XFTP-közvetítő-kiszolgálóra."; @@ -694,15 +694,30 @@ /* No comment provided by engineer. */ "Bad message ID" = "Téves üzenet ID"; +/* No comment provided by engineer. */ +"Better calls" = "Továbbfejlesztett hívásélmény"; + /* No comment provided by engineer. */ "Better groups" = "Javított csoportok"; +/* No comment provided by engineer. */ +"Better message dates." = "Továbbfejlesztett üzenetdátumok."; + /* No comment provided by engineer. */ "Better messages" = "Jobb üzenetek"; /* No comment provided by engineer. */ "Better networking" = "Jobb hálózatkezelés"; +/* No comment provided by engineer. */ +"Better notifications" = "Továbbfejlesztett értesítések"; + +/* No comment provided by engineer. */ +"Better security ✅" = "Továbbfejlesztett biztonság ✅"; + +/* No comment provided by engineer. */ +"Better user experience" = "Továbbfejlesztett felhasználói élmény"; + /* No comment provided by engineer. */ "Black" = "Fekete"; @@ -918,7 +933,7 @@ "Chat preferences were changed." = "A csevegési beállítások megváltoztak."; /* No comment provided by engineer. */ -"Chat profile" = "Felhasználói profil"; +"Chat profile" = "Csevegési profil"; /* No comment provided by engineer. */ "Chat theme" = "Csevegés témája"; @@ -1292,6 +1307,9 @@ /* No comment provided by engineer. */ "Custom time" = "Személyreszabott idő"; +/* No comment provided by engineer. */ +"Customizable message shape." = "Testreszabható üzenetbuborékok."; + /* No comment provided by engineer. */ "Customize theme" = "Téma személyre szabása"; @@ -1482,6 +1500,9 @@ /* No comment provided by engineer. */ "Delete old database?" = "Régi adatbázis törlése?"; +/* No comment provided by engineer. */ +"Delete or moderate up to 200 messages." = "Legfeljebb 200 üzenet egyszerre való törlése, vagy moderálása."; + /* No comment provided by engineer. */ "Delete pending connection?" = "Függőben lévő ismerőskérelem törlése?"; @@ -1952,7 +1973,7 @@ "Error deleting token" = "Hiba a token törlésekor"; /* No comment provided by engineer. */ -"Error deleting user profile" = "Hiba a felhasználói profil törlésekor"; +"Error deleting user profile" = "Hiba a felhasználó-profil törlésekor"; /* No comment provided by engineer. */ "Error downloading the archive" = "Hiba az archívum letöltésekor"; @@ -2021,7 +2042,7 @@ "Error saving settings" = "Hiba a beállítások mentésekor"; /* No comment provided by engineer. */ -"Error saving user password" = "Hiba a felhasználó jelszavának mentésekor"; +"Error saving user password" = "Hiba a felhasználói jelszó mentésekor"; /* No comment provided by engineer. */ "Error scanning code: %@" = "Hiba a kód beolvasásakor: %@"; @@ -2063,7 +2084,7 @@ "Error updating settings" = "Hiba történt a beállítások frissítésekor"; /* No comment provided by engineer. */ -"Error updating user privacy" = "Hiba a felhasználói beállítások frissítésekor"; +"Error updating user privacy" = "Hiba a felhasználói adatvédelem frissítésekor"; /* No comment provided by engineer. */ "Error uploading the archive" = "Hiba az archívum feltöltésekor"; @@ -2230,6 +2251,9 @@ /* alert message */ "Forward messages without files?" = "Üzenetek továbbítása fájlok nélkül?"; +/* No comment provided by engineer. */ +"Forward up to 20 messages at once." = "Legfeljebb 20 üzenet egyszerre való továbbítása."; + /* No comment provided by engineer. */ "forwarded" = "továbbított"; @@ -2384,7 +2408,7 @@ "Hidden profile password" = "Rejtett profiljelszó"; /* chat item action */ -"Hide" = "Elrejtés"; +"Hide" = "Összecsukás"; /* No comment provided by engineer. */ "Hide app screen in the recent apps." = "Alkalmazás képernyőjének elrejtése a gyakran használt alkalmazások között."; @@ -2435,7 +2459,7 @@ "If you enter your self-destruct passcode while opening the app:" = "Ha az alkalmazás megnyitásakor megadja az önmegsemmisítő jelkódot:"; /* No comment provided by engineer. */ -"If you need to use the chat now tap **Do it later** below (you will be offered to migrate the database when you restart the app)." = "Ha most kell használnia a csevegést, koppintson alább az **Befejezés később** lehetőségre (az alkalmazás újraindításakor felajánlásra kerül az adatbázis átköltöztetése)."; +"If you need to use the chat now tap **Do it later** below (you will be offered to migrate the database when you restart the app)." = "Ha most kell használnia a csevegést, koppintson alább a **Befejezés később** lehetőségre (az alkalmazás újraindításakor felajánlásra kerül az adatbázis átköltöztetése)."; /* No comment provided by engineer. */ "Ignore" = "Mellőzés"; @@ -2470,6 +2494,9 @@ /* No comment provided by engineer. */ "Importing archive" = "Archívum importálása"; +/* No comment provided by engineer. */ +"Improved delivery, reduced traffic usage.\nMore improvements are coming soon!" = "Továbbfejlesztett kézbesítés, csökkentett adatforgalom-használat.\nTovábbi fejlesztések hamarosan!"; + /* No comment provided by engineer. */ "Improved message delivery" = "Továbbfejlesztett üzenetkézbesítés"; @@ -2789,7 +2816,7 @@ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Győződjön meg arról, hogy a WebRTC ICE-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva."; /* No comment provided by engineer. */ -"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Sokan kérdezték: *ha a SimpleX Chatnek nincsenek felhasználói azonosítói, akkor hogyan tud üzeneteket kézbesíteni?*"; +"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Sokan kérdezték: *ha a SimpleX Chatnek nincsenek felhasználó-azonosítói, akkor hogyan tud üzeneteket kézbesíteni?*"; /* No comment provided by engineer. */ "Mark deleted for everyone" = "Jelölje meg mindenki számára töröltként"; @@ -2891,10 +2918,10 @@ "Message source remains private." = "Az üzenet forrása titokban marad."; /* No comment provided by engineer. */ -"Message status" = "Üzenetállapot"; +"Message status" = "Üzenet állapota"; /* copied message info */ -"Message status: %@" = "Üzenetállapot: %@"; +"Message status: %@" = "Üzenet állapota: %@"; /* No comment provided by engineer. */ "Message text" = "Név és üzenet"; @@ -3199,7 +3226,7 @@ "Onion hosts will not be used." = "Onion-kiszolgálók nem lesznek használva."; /* No comment provided by engineer. */ -"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Csak az eszközök alkalmazásai tárolják a felhasználói profilokat, névjegyeket, csoportokat és a **2 rétegű végpontok közötti titkosítással** küldött üzeneteket."; +"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Csak az eszközök alkalmazásai tárolják a felhasználó-profilokat, névjegyeket, csoportokat és a **2 rétegű végpontok közötti titkosítással** küldött üzeneteket."; /* No comment provided by engineer. */ "Only delete conversation" = "Csak a beszélgetés törlése"; @@ -3265,7 +3292,7 @@ "Open Settings" = "Beállítások megnyitása"; /* authentication reason */ -"Open user profiles" = "Felhasználói profilok megnyitása"; +"Open user profiles" = "Felhasználó-profilok megnyitása"; /* No comment provided by engineer. */ "Open-source protocol and code – anybody can run the servers." = "Nyílt forráskódú protokoll és forráskód – bárki üzemeltethet kiszolgálókat."; @@ -3846,7 +3873,7 @@ "Save preferences?" = "Beállítások mentése?"; /* No comment provided by engineer. */ -"Save profile password" = "Felhasználói fiók jelszavának mentése"; +"Save profile password" = "Profiljelszó mentése"; /* No comment provided by engineer. */ "Save servers" = "Kiszolgálók mentése"; @@ -4271,6 +4298,9 @@ /* simplex link type */ "SimpleX one-time invitation" = "Egyszer használható SimpleX-meghívó"; +/* No comment provided by engineer. */ +"SimpleX protocols reviewed by Trail of Bits." = "A SimpleX Chat biztonsága a Trail of Bits által lett újraauditálva."; + /* No comment provided by engineer. */ "Simplified incognito mode" = "Egyszerűsített inkognitómód"; @@ -4391,6 +4421,12 @@ /* No comment provided by engineer. */ "Support SimpleX Chat" = "SimpleX Chat támogatása"; +/* No comment provided by engineer. */ +"Switch audio and video during the call." = "Hang/Videó váltása hívás közben."; + +/* No comment provided by engineer. */ +"Switch chat profile for 1-time invitations." = "Csevegési profilváltás az egyszer használható meghívókhoz."; + /* No comment provided by engineer. */ "System" = "Rendszer"; @@ -4410,19 +4446,19 @@ "Tap to activate profile." = "A profil aktiválásához koppintson az ikonra."; /* No comment provided by engineer. */ -"Tap to Connect" = "Koppintson a kapcsolódáshoz"; +"Tap to Connect" = "Koppintson ide a kapcsolódáshoz"; /* No comment provided by engineer. */ -"Tap to join" = "Koppintson a csatlakozáshoz"; +"Tap to join" = "Koppintson ide a csatlakozáshoz"; /* No comment provided by engineer. */ -"Tap to join incognito" = "Koppintson az inkognitóban való csatlakozáshoz"; +"Tap to join incognito" = "Koppintson ide az inkognitóban való csatlakozáshoz"; /* No comment provided by engineer. */ "Tap to paste link" = "Koppintson ide a hivatkozás beillesztéséhez"; /* No comment provided by engineer. */ -"Tap to scan" = "Koppintson a beolvasáshoz"; +"Tap to scan" = "Koppintson ide a QR-kód beolvasáshoz"; /* No comment provided by engineer. */ "TCP connection" = "TCP kapcsolat"; @@ -4464,7 +4500,7 @@ "Thanks to the users – contribute via Weblate!" = "Köszönet a felhasználóknak - hozzájárulás a Weblate-en!"; /* No comment provided by engineer. */ -"The 1st platform without any user identifiers – private by design." = "Az első csevegési rendszer bármiféle felhasználó azonosító nélkül - privátra lett tervezre."; +"The 1st platform without any user identifiers – private by design." = "Az első csevegési rendszer bármiféle felhasználó-azonosító nélkül - privátra lett tervezre."; /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Az alkalmazás értesíteni fogja, amikor üzeneteket vagy kapcsolatkéréseket kap – beállítások megnyitása az engedélyezéshez."; @@ -4599,7 +4635,7 @@ "To make a new connection" = "Új kapcsolat létrehozásához"; /* No comment provided by engineer. */ -"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "Az adatvédelem érdekében, a más csevegési platformokon megszokott felhasználói azonosítók helyett, a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, minden egyes ismerőshöz egy-egy különbözőt."; +"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "Az adatvédelem érdekében, a más csevegési platformokon megszokott felhasználó-azonosítók helyett, a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, minden egyes ismerőshöz egy-egy különbözőt."; /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Az időzóna védelme érdekében a kép-/hangfájlok UTC-t használnak."; @@ -5115,13 +5151,13 @@ "You can enable later via Settings" = "Később engedélyezheti a „Beállításokban”"; /* No comment provided by engineer. */ -"You can enable them later via app Privacy & Security settings." = "Később engedélyezheti őket az alkalmazás „Adatvédelem és biztonság” menüben."; +"You can enable them later via app Privacy & Security settings." = "Később engedélyezheti őket az alkalmazás „Adatvédelem és biztonság” menüjében."; /* No comment provided by engineer. */ "You can give another try." = "Megpróbálhatja még egyszer."; /* No comment provided by engineer. */ -"You can hide or mute a user profile - swipe it to the right." = "Elrejtheti vagy lenémíthatja a felhasználó profiljait - csúsztassa jobbra a profilt."; +"You can hide or mute a user profile - swipe it to the right." = "Elrejtheti vagy lenémíthatja a felhasználó -profiljait - csúsztassa jobbra a profilt."; /* No comment provided by engineer. */ "You can make it visible to your SimpleX contacts via Settings." = "Láthatóvá teheti a SimpleXbeli ismerősei számára a „Beállításokban”."; diff --git a/apps/ios/hu.lproj/SimpleX--iOS--InfoPlist.strings b/apps/ios/hu.lproj/SimpleX--iOS--InfoPlist.strings index 7b75cfcea3..434f906b4e 100644 --- a/apps/ios/hu.lproj/SimpleX--iOS--InfoPlist.strings +++ b/apps/ios/hu.lproj/SimpleX--iOS--InfoPlist.strings @@ -2,7 +2,7 @@ "CFBundleName" = "SimpleX"; /* Privacy - Camera Usage Description */ -"NSCameraUsageDescription" = "A SimpleX-nek kamera-hozzáférésre van szüksége a QR-kódok beolvasásához, hogy kapcsolódhasson más felhasználókhoz és videohívásokhoz."; +"NSCameraUsageDescription" = "A SimpleXnek kamera-hozzáférésre van szüksége a QR-kódok beolvasásához, hogy kapcsolódhasson más felhasználókhoz és videohívásokhoz."; /* Privacy - Face ID Usage Description */ "NSFaceIDUsageDescription" = "A SimpleX Face ID-t használ a helyi hitelesítéshez"; @@ -11,8 +11,8 @@ "NSLocalNetworkUsageDescription" = "A SimpleX helyi hálózati hozzáférést használ, hogy lehetővé tegye a felhasználói csevegőprofil használatát számítógépen keresztül ugyanazon a hálózaton."; /* Privacy - Microphone Usage Description */ -"NSMicrophoneUsageDescription" = "A SimpleX-nek mikrofon-hozzáférésre van szüksége hang- és videohívásokhoz, valamint hangüzenetek rögzítéséhez."; +"NSMicrophoneUsageDescription" = "A SimpleXnek mikrofon-hozzáférésre van szüksége hang- és videohívásokhoz, valamint hangüzenetek rögzítéséhez."; /* Privacy - Photo Library Additions Usage Description */ -"NSPhotoLibraryAddUsageDescription" = "A SimpleX-nek hozzáférésre van szüksége a Galériához a rögzített és fogadott média mentéséhez"; +"NSPhotoLibraryAddUsageDescription" = "A SimpleXnek galéria-hozzáférésre van szüksége a rögzített és fogadott média mentéséhez"; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index d780372b3a..308ff5d18e 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -694,15 +694,30 @@ /* No comment provided by engineer. */ "Bad message ID" = "ID del messaggio errato"; +/* No comment provided by engineer. */ +"Better calls" = "Chiamate migliorate"; + /* No comment provided by engineer. */ "Better groups" = "Gruppi migliorati"; +/* No comment provided by engineer. */ +"Better message dates." = "Date dei messaggi migliorate."; + /* No comment provided by engineer. */ "Better messages" = "Messaggi migliorati"; /* No comment provided by engineer. */ "Better networking" = "Rete migliorata"; +/* No comment provided by engineer. */ +"Better notifications" = "Notifiche migliorate"; + +/* No comment provided by engineer. */ +"Better security ✅" = "Sicurezza migliorata ✅"; + +/* No comment provided by engineer. */ +"Better user experience" = "Esperienza utente migliorata"; + /* No comment provided by engineer. */ "Black" = "Nero"; @@ -1292,6 +1307,9 @@ /* No comment provided by engineer. */ "Custom time" = "Tempo personalizzato"; +/* No comment provided by engineer. */ +"Customizable message shape." = "Forma dei messaggi personalizzabile."; + /* No comment provided by engineer. */ "Customize theme" = "Personalizza il tema"; @@ -1482,6 +1500,9 @@ /* No comment provided by engineer. */ "Delete old database?" = "Eliminare il database vecchio?"; +/* No comment provided by engineer. */ +"Delete or moderate up to 200 messages." = "Elimina o modera fino a 200 messaggi."; + /* No comment provided by engineer. */ "Delete pending connection?" = "Eliminare la connessione in attesa?"; @@ -2230,6 +2251,9 @@ /* alert message */ "Forward messages without files?" = "Inoltrare i messaggi senza file?"; +/* No comment provided by engineer. */ +"Forward up to 20 messages at once." = "Inoltra fino a 20 messaggi alla volta."; + /* No comment provided by engineer. */ "forwarded" = "inoltrato"; @@ -2470,6 +2494,9 @@ /* No comment provided by engineer. */ "Importing archive" = "Importazione archivio"; +/* No comment provided by engineer. */ +"Improved delivery, reduced traffic usage.\nMore improvements are coming soon!" = "Consegna migliorata, utilizzo di traffico ridotto.\nAltri miglioramenti sono in arrivo!"; + /* No comment provided by engineer. */ "Improved message delivery" = "Consegna dei messaggi migliorata"; @@ -4271,6 +4298,9 @@ /* simplex link type */ "SimpleX one-time invitation" = "Invito SimpleX una tantum"; +/* No comment provided by engineer. */ +"SimpleX protocols reviewed by Trail of Bits." = "Protocolli di SimpleX esaminati da Trail of Bits."; + /* No comment provided by engineer. */ "Simplified incognito mode" = "Modalità incognito semplificata"; @@ -4391,6 +4421,12 @@ /* No comment provided by engineer. */ "Support SimpleX Chat" = "Supporta SimpleX Chat"; +/* No comment provided by engineer. */ +"Switch audio and video during the call." = "Cambia tra audio e video durante la chiamata."; + +/* No comment provided by engineer. */ +"Switch chat profile for 1-time invitations." = "Cambia profilo di chat per inviti una tantum."; + /* No comment provided by engineer. */ "System" = "Sistema"; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index 03344b8521..20c4819d87 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -2067,6 +2067,12 @@ /* No comment provided by engineer. */ "Messages & files" = "メッセージ & ファイル"; +/* No comment provided by engineer. */ +"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "メッセージ、ファイル、通話は、前方秘匿性、否認可能性および侵入復元性を備えた**エンドツーエンドの暗号化**によって保護されます。"; + +/* No comment provided by engineer. */ +"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "メッセージ、ファイル、通話は、前方秘匿性、否認可能性および侵入復元性を備えた**耐量子E2E暗号化**によって保護されます。"; + /* No comment provided by engineer. */ "Migrating database archive…" = "データベースのアーカイブを移行しています…"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index e5e9d00682..d1b74886f1 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -694,15 +694,30 @@ /* No comment provided by engineer. */ "Bad message ID" = "Onjuiste bericht-ID"; +/* No comment provided by engineer. */ +"Better calls" = "Betere gesprekken"; + /* No comment provided by engineer. */ "Better groups" = "Betere groepen"; +/* No comment provided by engineer. */ +"Better message dates." = "Betere datums voor berichten."; + /* No comment provided by engineer. */ "Better messages" = "Betere berichten"; /* No comment provided by engineer. */ "Better networking" = "Beter netwerk"; +/* No comment provided by engineer. */ +"Better notifications" = "Betere meldingen"; + +/* No comment provided by engineer. */ +"Better security ✅" = "Betere beveiliging ✅"; + +/* No comment provided by engineer. */ +"Better user experience" = "Betere gebruikerservaring"; + /* No comment provided by engineer. */ "Black" = "Zwart"; @@ -1292,6 +1307,9 @@ /* No comment provided by engineer. */ "Custom time" = "Aangepaste tijd"; +/* No comment provided by engineer. */ +"Customizable message shape." = "Aanpasbare berichtvorm."; + /* No comment provided by engineer. */ "Customize theme" = "Thema aanpassen"; @@ -1482,6 +1500,9 @@ /* No comment provided by engineer. */ "Delete old database?" = "Oude database verwijderen?"; +/* No comment provided by engineer. */ +"Delete or moderate up to 200 messages." = "Maximaal 200 berichten verwijderen of modereren."; + /* No comment provided by engineer. */ "Delete pending connection?" = "Wachtende verbinding verwijderen?"; @@ -2230,6 +2251,9 @@ /* alert message */ "Forward messages without files?" = "Berichten doorsturen zonder bestanden?"; +/* No comment provided by engineer. */ +"Forward up to 20 messages at once." = "Stuur maximaal 20 berichten tegelijk door."; + /* No comment provided by engineer. */ "forwarded" = "doorgestuurd"; @@ -2470,6 +2494,9 @@ /* No comment provided by engineer. */ "Importing archive" = "Archief importeren"; +/* No comment provided by engineer. */ +"Improved delivery, reduced traffic usage.\nMore improvements are coming soon!" = "Verbeterde levering, minder data gebruik.\nBinnenkort meer verbeteringen!"; + /* No comment provided by engineer. */ "Improved message delivery" = "Verbeterde berichtbezorging"; @@ -4271,6 +4298,9 @@ /* simplex link type */ "SimpleX one-time invitation" = "Eenmalige SimpleX uitnodiging"; +/* No comment provided by engineer. */ +"SimpleX protocols reviewed by Trail of Bits." = "SimpleX-protocollen beoordeeld door Trail of Bits."; + /* No comment provided by engineer. */ "Simplified incognito mode" = "Vereenvoudigde incognitomodus"; @@ -4391,6 +4421,12 @@ /* No comment provided by engineer. */ "Support SimpleX Chat" = "Ondersteuning van SimpleX Chat"; +/* No comment provided by engineer. */ +"Switch audio and video during the call." = "Wissel tussen audio en video tijdens het gesprek."; + +/* No comment provided by engineer. */ +"Switch chat profile for 1-time invitations." = "Wijzig chatprofiel voor eenmalige uitnodigingen."; + /* No comment provided by engineer. */ "System" = "Systeem"; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 4392c20699..63b7285a45 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -160,6 +160,9 @@ /* notification title */ "%@ wants to connect!" = "%@ хочет соединиться!"; +/* format for date separator in chat */ +"%@, %@" = "%1$@, %2$@"; + /* No comment provided by engineer. */ "%@, %@ and %lld members" = "%@, %@ и %lld членов группы"; @@ -691,15 +694,30 @@ /* No comment provided by engineer. */ "Bad message ID" = "Ошибка ID сообщения"; +/* No comment provided by engineer. */ +"Better calls" = "Улучшенные звонки"; + /* No comment provided by engineer. */ "Better groups" = "Улучшенные группы"; +/* No comment provided by engineer. */ +"Better message dates." = "Улучшенные даты сообщений."; + /* No comment provided by engineer. */ "Better messages" = "Улучшенные сообщения"; /* No comment provided by engineer. */ "Better networking" = "Улучшенные сетевые функции"; +/* No comment provided by engineer. */ +"Better notifications" = "Улучшенные уведомления"; + +/* No comment provided by engineer. */ +"Better security ✅" = "Улучшенная безопасность ✅"; + +/* No comment provided by engineer. */ +"Better user experience" = "Улучшенный интерфейс"; + /* No comment provided by engineer. */ "Black" = "Черная"; @@ -1289,6 +1307,9 @@ /* No comment provided by engineer. */ "Custom time" = "Пользовательское время"; +/* No comment provided by engineer. */ +"Customizable message shape." = "Настраиваемая форма сообщений."; + /* No comment provided by engineer. */ "Customize theme" = "Настроить тему"; @@ -1479,6 +1500,9 @@ /* No comment provided by engineer. */ "Delete old database?" = "Удалить предыдущую версию данных?"; +/* No comment provided by engineer. */ +"Delete or moderate up to 200 messages." = "Удаляйте или модерируйте до 200 сообщений."; + /* No comment provided by engineer. */ "Delete pending connection?" = "Удалить ожидаемое соединение?"; @@ -2227,6 +2251,9 @@ /* alert message */ "Forward messages without files?" = "Переслать сообщения без файлов?"; +/* No comment provided by engineer. */ +"Forward up to 20 messages at once." = "Пересылайте до 20 сообщений за раз."; + /* No comment provided by engineer. */ "forwarded" = "переслано"; @@ -2467,6 +2494,9 @@ /* No comment provided by engineer. */ "Importing archive" = "Импорт архива"; +/* No comment provided by engineer. */ +"Improved delivery, reduced traffic usage.\nMore improvements are coming soon!" = "Улучшенная доставка, меньше трафик."; + /* No comment provided by engineer. */ "Improved message delivery" = "Улучшенная доставка сообщений"; @@ -4268,6 +4298,9 @@ /* simplex link type */ "SimpleX one-time invitation" = "SimpleX одноразовая ссылка"; +/* No comment provided by engineer. */ +"SimpleX protocols reviewed by Trail of Bits." = "Аудит SimpleX протоколов от Trail of Bits."; + /* No comment provided by engineer. */ "Simplified incognito mode" = "Упрощенный режим Инкогнито"; @@ -4388,6 +4421,12 @@ /* No comment provided by engineer. */ "Support SimpleX Chat" = "Поддержать SimpleX Chat"; +/* No comment provided by engineer. */ +"Switch audio and video during the call." = "Переключайте звук и видео во время звонка."; + +/* No comment provided by engineer. */ +"Switch chat profile for 1-time invitations." = "Переключайте профиль чата для одноразовых приглашений."; + /* No comment provided by engineer. */ "System" = "Системная"; diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index c35d25f1b5..63ca78bccf 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -694,15 +694,30 @@ /* No comment provided by engineer. */ "Bad message ID" = "Kötü mesaj kimliği"; +/* No comment provided by engineer. */ +"Better calls" = "Daha iyi aramalar"; + /* No comment provided by engineer. */ "Better groups" = "Daha iyi gruplar"; +/* No comment provided by engineer. */ +"Better message dates." = "Daha iyi mesaj tarihleri."; + /* No comment provided by engineer. */ "Better messages" = "Daha iyi mesajlar"; /* No comment provided by engineer. */ "Better networking" = "Daha iyi ağ oluşturma"; +/* No comment provided by engineer. */ +"Better notifications" = "Daha iyi bildirimler"; + +/* No comment provided by engineer. */ +"Better security ✅" = "Daha iyi güvenlik ✅"; + +/* No comment provided by engineer. */ +"Better user experience" = "Daha iyi kullanıcı deneyimi"; + /* No comment provided by engineer. */ "Black" = "Siyah"; @@ -1292,6 +1307,9 @@ /* No comment provided by engineer. */ "Custom time" = "Özel saat"; +/* No comment provided by engineer. */ +"Customizable message shape." = "Özelleştirilebilir mesaj şekli."; + /* No comment provided by engineer. */ "Customize theme" = "Renk temalarını kişiselleştir"; @@ -1482,6 +1500,9 @@ /* No comment provided by engineer. */ "Delete old database?" = "Eski veritabanı silinsin mi?"; +/* No comment provided by engineer. */ +"Delete or moderate up to 200 messages." = "200'e kadar mesajı silin veya düzenleyin."; + /* No comment provided by engineer. */ "Delete pending connection?" = "Bekleyen bağlantı silinsin mi?"; @@ -2230,6 +2251,9 @@ /* alert message */ "Forward messages without files?" = "Mesajlar dosyalar olmadan iletilsin mi ?"; +/* No comment provided by engineer. */ +"Forward up to 20 messages at once." = "Aynı anda en fazla 20 mesaj iletin."; + /* No comment provided by engineer. */ "forwarded" = "iletildi"; @@ -2470,6 +2494,9 @@ /* No comment provided by engineer. */ "Importing archive" = "Arşiv içe aktarılıyor"; +/* No comment provided by engineer. */ +"Improved delivery, reduced traffic usage.\nMore improvements are coming soon!" = "İyileştirilmiş teslimat, azaltılmış trafik kullanımı.\nDaha fazla iyileştirme yakında geliyor!"; + /* No comment provided by engineer. */ "Improved message delivery" = "İyileştirilmiş mesaj iletimi"; @@ -4271,6 +4298,9 @@ /* simplex link type */ "SimpleX one-time invitation" = "SimpleX tek kullanımlık davet"; +/* No comment provided by engineer. */ +"SimpleX protocols reviewed by Trail of Bits." = "SimpleX protokolleri Trail of Bits tarafından incelenmiştir."; + /* No comment provided by engineer. */ "Simplified incognito mode" = "Basitleştirilmiş gizli mod"; @@ -4391,6 +4421,12 @@ /* No comment provided by engineer. */ "Support SimpleX Chat" = "SimpleX Chat'e destek ol"; +/* No comment provided by engineer. */ +"Switch audio and video during the call." = "Görüşme sırasında ses ve görüntüyü değiştirin."; + +/* No comment provided by engineer. */ +"Switch chat profile for 1-time invitations." = "Sohbet profilini 1 kerelik davetler için değiştirin."; + /* No comment provided by engineer. */ "System" = "Sistem"; diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index 4caa2dc86b..31d161f3db 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -2119,7 +2119,7 @@ Se usarán credenciales SOCKS nuevas por cada servidor. Servidor Llamadas mejoradas - Mensajes mejor datados. + Sistema de fechas mejorado. Experiencia de usuario mejorada Forma personalizable de los mensajes. Desplazamiento de hasta 20 mensajes. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index b08cf57136..7482b4ea06 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -49,7 +49,7 @@ Megjegyzés: az üzenet- és fájlközvetítő-kiszolgálók SOCKS proxyn keresztül kapcsolódnak. A hívások és a hivatkozások előnézetének elküldése közvetlen kapcsolatot használnak.]]> Alkalmazásadatok biztonsági mentése Az adatbázis előkészítése sikertelen - Az ismerőseivel kapcsolatban marad. A profil változtatások frissítésre kerülnek az ismerősöknél. + Az ismerőseivel kapcsolatban marad. A profil-változtatások frissítésre kerülnek az ismerősöknél. A csevegési profillal (alapértelmezett), vagy a kapcsolattal (BÉTA). Egy új véletlenszerű profil lesz megosztva. A hangüzenetek küldése csak abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. @@ -504,7 +504,7 @@ Hiba a csevegés elindításakor A csoport profilja a tagok eszközein tárolódik, nem a kiszolgálókon. Jelmondat megadása… - Hiba a felhasználói beállítások frissítésekor + Hiba a felhasználói adatvédelem frissítésekor Titkosít Csoport nem található! Hiba az SMP-kiszolgálók mentésekor @@ -542,7 +542,7 @@ Fájlok és médiatartalmak KONZOLHOZ Sikertelen titkosítás-újraegyeztetés. - Hiba a felhasználói profil törlésekor + Hiba a felhasználó-profil törlésekor Csoporttag általi javítás nem támogatott Üdvözlőüzenet megadása… Titkosított adatbázis @@ -642,7 +642,7 @@ Azonnali Inkognitócsoportok Hogyan - Elrejtés + Összecsukás Kép Fejlesztett adatvédelem és biztonság Mellőzés @@ -804,7 +804,7 @@ Egy hordozható eszköz társítása Értesítési szolgáltatás Csak a csoporttulajdonosok engedélyezhetik a hangüzenetek küldését. - 2 rétegű végpontok közötti titkosítással küldött üzeneteket.]]> + 2 rétegű végpontok közötti titkosítással küldött üzeneteket.]]> Érvénytelen átköltöztetési visszaigazolás Csak a csoporttulajdonosok módosíthatják a csoportbeállításokat. Nincsenek előzmények @@ -822,7 +822,7 @@ ajánlott %s Csoport elhagyása Minden %s által írt üzenet megjelenik! - Ha a SimpleX Chatnek nincs felhasználói azonosítója, hogyan lehet mégis üzeneteket küldeni?]]> + Ha a SimpleX Chatnek nincs felhasználó-azonosítója, hogyan lehet mégis üzeneteket küldeni?]]> Ez akkor fordulhat elő, ha:\n1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak.\n2. Az üzenet visszafejtése sikertelen volt, mert Ön, vagy az ismerőse régebbi adatbázis biztonsági mentést használt.\n3. A kapcsolat sérült. megfigyelő inkognitó a csoporthivatkozáson keresztül @@ -979,7 +979,7 @@ Elutasítás Ismerős nevének és az üzenet tartalmának megjelenítése BEÁLLÍTÁSOK - Felhasználói fiók jelszavának mentése + Profiljelszó mentése Fájlküldés megállítása? Számítógép leválasztása? A hangüzenetek le vannak tilva! @@ -1065,7 +1065,7 @@ Privát értesítések Meghívta egy ismerősét %s nincs hitelesítve - Koppintson a kapcsolódáshoz + Koppintson ide a kapcsolódáshoz Ennek az eszköznek a neve Jelenlegi profil Fájl feltöltése @@ -1218,7 +1218,7 @@ Ön eltávolította őt: %1$s Jelmondat mentése és a csevegés megnyitása Beállítások mentése? - Nincsenek felhasználói azonosítók. + Nincsenek felhasználó-azonosítók. A közvetlen üzenetek küldése a tagok között le van tiltva. SOCKS proxy használata? Hangszóró kikapcsolva @@ -1286,7 +1286,7 @@ \nEz a művelet nem vonható vissza - profiljai, ismerősei, csevegési üzenetei és fájljai véglegesen törölve lesznek. Ötletek és javaslatok Figyelmeztetés: néhány adat elveszhet! - Koppintson az új csevegés indításához + Koppintson ide az új csevegés indításához Várakozás a számítógépre… A privát üzenetküldés \nkövetkező generációja @@ -1311,7 +1311,7 @@ A jelszó nem található a Keystore-ban, ezért kézzel szükséges megadni. Ez akkor történhetett meg, ha visszaállította az alkalmazás adatait egy biztonságimentési eszközzel. Ha nem így történt, akkor lépjen kapcsolatba a fejlesztőkkel. Az ismerősei továbbra is kapcsolódva maradnak. A kiszolgálónak engedélyre van szüksége a várólisták létrehozásához, ellenőrizze jelszavát - Az adatbázis nem működik megfelelően. Koppintson további információért + Az adatbázis nem működik megfelelően. Koppintson ide a további információkért A fájl küldése leállt. Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott ismerősétől érkező üzenetek fogadására szolgál. Nem sikerült hitelesíteni; próbálja meg újra. @@ -1386,7 +1386,7 @@ A profilja az eszközén van tárolva és csak az ismerőseivel kerül megosztásra. A SimpleX-kiszolgálók nem láthatják a profilját. Ön megváltoztatta %s szerepkörét erre: %s Csoportmeghívó elutasítva - Az adatvédelem érdekében, a más csevegési platformokon megszokott felhasználói azonosítók helyett, a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, minden egyes ismerőshöz egy-egy különbözőt. + Az adatvédelem érdekében, a más csevegési platformokon megszokott felhasználó-azonosítók helyett, a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, minden egyes ismerőshöz egy-egy különbözőt. (a megosztáshoz az ismerősével) Csoportmeghívó elküldve Kapcsolat izolációs mód frissítése? @@ -1421,7 +1421,7 @@ a SimpleX Chat fejlesztőivel, ahol bármiről kérdezhet és értesülhet az újdonságokról.]]> Nem kötelező üdvözlőüzenettel. Ismeretlen adatbázishiba: %s - Elrejtheti vagy lenémíthatja a felhasználó profiljait - koppintson (vagy számítógép-alkalmazásban kattintson) hosszan a profilra a felugró menühöz. + Elrejtheti vagy lenémíthatja a felhasználó-profiljait - koppintson (vagy számítógép-alkalmazásban kattintson) hosszan a profilra a felugró menühöz. Inkognitómód használata kapcsolódáskor. Megoszthat egy hivatkozást vagy QR-kódot - így bárki csatlakozhat a csoporthoz. Ha a csoport később törlésre kerül, akkor nem fogja elveszíteni annak tagjait. Csatlakozott ehhez a csoporthoz @@ -1443,7 +1443,7 @@ Ez a művelet nem vonható vissza - a kiválasztottnál korábban küldött és fogadott üzenetek törlésre kerülnek. Ez több percet is igénybe vehet. A profilja csak az ismerőseivel kerül megosztásra. Néhány kiszolgáló megbukott a teszten: - Koppintson a csatlakozáshoz + Koppintson ide a csatlakozáshoz Ez a művelet nem vonható vissza - az összes fogadott és küldött fájl a médiatartalmakkal együtt törlésre kerülnek. Az alacsony felbontású képek viszont megmaradnak. A kézbesítési jelentések engedélyezve vannak %d ismerősnél Küldés ezen keresztül: @@ -1457,7 +1457,7 @@ Az ismerőse, akivel megosztotta ezt a hivatkozást, NEM fog tudni kapcsolódni! A véletlenszerű jelmondat egyszerű szövegként van tárolva a beállításokban. \nEz később megváltoztatható. - Koppintson az inkognitóban való kapcsolódáshoz + Koppintson ide az inkognitóban való kapcsolódáshoz Jelmondat beállítása az exportáláshoz A kézbesítési jelentések le vannak tiltva %d csoportban Néhány nem végzetes hiba történt az importáláskor: @@ -1519,7 +1519,7 @@ Látható előzmények Alkalmazás jelkód Ismerős hozzáadása - Koppintson a beolvasáshoz + Koppintson ide a QR-kód beolvasásához Koppintson ide a hivatkozás beillesztéséhez Ismerős hozzáadása: új meghívó-hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz.]]> Csoport létrehozása: új csoport létrehozásához.]]> @@ -1844,8 +1844,8 @@ \nutoljára kézbesített üzenet: %2$s Hibás kulcs vagy ismeretlen fájltöredék cím - valószínűleg a fájl törlődött. Ideiglenesfájl-hiba - Üzenetállapot - Üzenetállapot: %s + Üzenet állapota + Üzenet állapota: %s Fájlhiba A fájl nem található - valószínűleg a fájlt törölték vagy visszavonták. Fájlkiszolgáló-hiba: %1$s @@ -1874,8 +1874,7 @@ Kapcsolódás Hibák Függőben - Ekkortól kezdve: %s. -\nMinden adat biztonságban van az eszközén. + Statisztikagyűjtés kezdete: %s.\nMinden adat biztonságban van az eszközén. Elküldött üzenetek Proxyzott kiszolgálók Újrakapcsolódás a kiszolgálókhoz? @@ -1899,7 +1898,7 @@ Összes elküldött Proxyn keresztül küldve SMP-kiszolgáló - Ekkortól kezdve: %s. + Statisztikagyűjtés kezdete: %s. Feltöltve XFTP-kiszolgáló Proxyzott diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml index fe7a226aeb..aa9856eb9f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -2198,7 +2198,7 @@ Пересылайте до 20 сообщений за раз. Переключайте звук и видео во время звонка. Переключайте профиль чата для одноразовых приглашений. - Анализ безопасности протоколов SimpleX от Trail of Bits. + Аудит SimpleX протоколов от Trail of Bits. Чтобы совершать звонки, разрешите использовать микрофон. Завершите вызов и попробуйте позвонить снова. Не использовать учетные данные с прокси. Ошибка сохранения прокси From 2c3c97f5cc4bfa3a407600cbb4e60963fbe07415 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 11 Oct 2024 18:46:45 +0100 Subject: [PATCH 023/567] 6.1-beta.5: ios 243, android 246, desktop 72 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 60 +++++++++++----------- apps/multiplatform/gradle.properties | 8 +-- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 7b9bbb726d..56b09206b5 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -154,11 +154,6 @@ 6442E0BE2880182D00CEC0F9 /* GroupChatInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6442E0BD2880182D00CEC0F9 /* GroupChatInfoView.swift */; }; 64466DC829FC2B3B00E3D48D /* CreateSimpleXAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */; }; 64466DCC29FFE3E800E3D48D /* MailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64466DCB29FFE3E800E3D48D /* MailView.swift */; }; - 644844672CB932A5004A1BC6 /* libHSsimplex-chat-6.1.0.7-4dlRUyozvfTCAHYlr9Ja1T-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644844622CB932A4004A1BC6 /* libHSsimplex-chat-6.1.0.7-4dlRUyozvfTCAHYlr9Ja1T-ghc9.6.3.a */; }; - 644844682CB932A5004A1BC6 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644844632CB932A5004A1BC6 /* libffi.a */; }; - 644844692CB932A5004A1BC6 /* libHSsimplex-chat-6.1.0.7-4dlRUyozvfTCAHYlr9Ja1T.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644844642CB932A5004A1BC6 /* libHSsimplex-chat-6.1.0.7-4dlRUyozvfTCAHYlr9Ja1T.a */; }; - 6448446A2CB932A5004A1BC6 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644844652CB932A5004A1BC6 /* libgmpxx.a */; }; - 6448446B2CB932A5004A1BC6 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644844662CB932A5004A1BC6 /* libgmp.a */; }; 6448BBB628FA9D56000D2AB9 /* GroupLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */; }; 644EFFDE292BCD9D00525D5B /* ComposeVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */; }; 644EFFE0292CFD7F00525D5B /* CIVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFDF292CFD7F00525D5B /* CIVoiceView.swift */; }; @@ -228,6 +223,11 @@ E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; E5DCF9982C5906FF007928CC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9962C5906FF007928CC /* InfoPlist.strings */; }; + E5E997BF2CB9867B00D7A2FA /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997BA2CB9867B00D7A2FA /* libgmpxx.a */; }; + E5E997C02CB9867B00D7A2FA /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997BB2CB9867B00D7A2FA /* libffi.a */; }; + E5E997C12CB9867B00D7A2FA /* libHSsimplex-chat-6.1.0.8-CCpM4Xk6yH6LfY0I6dYuyA-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997BC2CB9867B00D7A2FA /* libHSsimplex-chat-6.1.0.8-CCpM4Xk6yH6LfY0I6dYuyA-ghc9.6.3.a */; }; + E5E997C22CB9867B00D7A2FA /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997BD2CB9867B00D7A2FA /* libgmp.a */; }; + E5E997C32CB9867B00D7A2FA /* libHSsimplex-chat-6.1.0.8-CCpM4Xk6yH6LfY0I6dYuyA.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997BE2CB9867B00D7A2FA /* libHSsimplex-chat-6.1.0.8-CCpM4Xk6yH6LfY0I6dYuyA.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -496,11 +496,6 @@ 6442E0BD2880182D00CEC0F9 /* GroupChatInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupChatInfoView.swift; sourceTree = ""; }; 64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSimpleXAddress.swift; sourceTree = ""; }; 64466DCB29FFE3E800E3D48D /* MailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailView.swift; sourceTree = ""; }; - 644844622CB932A4004A1BC6 /* libHSsimplex-chat-6.1.0.7-4dlRUyozvfTCAHYlr9Ja1T-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.7-4dlRUyozvfTCAHYlr9Ja1T-ghc9.6.3.a"; sourceTree = ""; }; - 644844632CB932A5004A1BC6 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 644844642CB932A5004A1BC6 /* libHSsimplex-chat-6.1.0.7-4dlRUyozvfTCAHYlr9Ja1T.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.7-4dlRUyozvfTCAHYlr9Ja1T.a"; sourceTree = ""; }; - 644844652CB932A5004A1BC6 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - 644844662CB932A5004A1BC6 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupLinkView.swift; sourceTree = ""; }; 644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeVoiceView.swift; sourceTree = ""; }; 644EFFDF292CFD7F00525D5B /* CIVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIVoiceView.swift; sourceTree = ""; }; @@ -617,6 +612,11 @@ E5DCF9A62C590731007928CC /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/InfoPlist.strings; sourceTree = ""; }; E5DCF9A72C590732007928CC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoPlist.strings; sourceTree = ""; }; E5DCF9A82C590732007928CC /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = ""; }; + E5E997BA2CB9867B00D7A2FA /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + E5E997BB2CB9867B00D7A2FA /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E5E997BC2CB9867B00D7A2FA /* libHSsimplex-chat-6.1.0.8-CCpM4Xk6yH6LfY0I6dYuyA-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.8-CCpM4Xk6yH6LfY0I6dYuyA-ghc9.6.3.a"; sourceTree = ""; }; + E5E997BD2CB9867B00D7A2FA /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + E5E997BE2CB9867B00D7A2FA /* libHSsimplex-chat-6.1.0.8-CCpM4Xk6yH6LfY0I6dYuyA.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.8-CCpM4Xk6yH6LfY0I6dYuyA.a"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -655,14 +655,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E5E997C12CB9867B00D7A2FA /* libHSsimplex-chat-6.1.0.8-CCpM4Xk6yH6LfY0I6dYuyA-ghc9.6.3.a in Frameworks */, + E5E997C22CB9867B00D7A2FA /* libgmp.a in Frameworks */, + E5E997C32CB9867B00D7A2FA /* libHSsimplex-chat-6.1.0.8-CCpM4Xk6yH6LfY0I6dYuyA.a in Frameworks */, + E5E997BF2CB9867B00D7A2FA /* libgmpxx.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - 644844692CB932A5004A1BC6 /* libHSsimplex-chat-6.1.0.7-4dlRUyozvfTCAHYlr9Ja1T.a in Frameworks */, - 6448446B2CB932A5004A1BC6 /* libgmp.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, + E5E997C02CB9867B00D7A2FA /* libffi.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - 644844682CB932A5004A1BC6 /* libffi.a in Frameworks */, - 644844672CB932A5004A1BC6 /* libHSsimplex-chat-6.1.0.7-4dlRUyozvfTCAHYlr9Ja1T-ghc9.6.3.a in Frameworks */, - 6448446A2CB932A5004A1BC6 /* libgmpxx.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -739,11 +739,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - 644844632CB932A5004A1BC6 /* libffi.a */, - 644844662CB932A5004A1BC6 /* libgmp.a */, - 644844652CB932A5004A1BC6 /* libgmpxx.a */, - 644844622CB932A4004A1BC6 /* libHSsimplex-chat-6.1.0.7-4dlRUyozvfTCAHYlr9Ja1T-ghc9.6.3.a */, - 644844642CB932A5004A1BC6 /* libHSsimplex-chat-6.1.0.7-4dlRUyozvfTCAHYlr9Ja1T.a */, + E5E997BB2CB9867B00D7A2FA /* libffi.a */, + E5E997BD2CB9867B00D7A2FA /* libgmp.a */, + E5E997BA2CB9867B00D7A2FA /* libgmpxx.a */, + E5E997BC2CB9867B00D7A2FA /* libHSsimplex-chat-6.1.0.8-CCpM4Xk6yH6LfY0I6dYuyA-ghc9.6.3.a */, + E5E997BE2CB9867B00D7A2FA /* libHSsimplex-chat-6.1.0.8-CCpM4Xk6yH6LfY0I6dYuyA.a */, ); path = Libraries; sourceTree = ""; @@ -1899,7 +1899,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 242; + CURRENT_PROJECT_VERSION = 243; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1948,7 +1948,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 242; + CURRENT_PROJECT_VERSION = 243; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1989,7 +1989,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 242; + CURRENT_PROJECT_VERSION = 243; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2009,7 +2009,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 242; + CURRENT_PROJECT_VERSION = 243; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2034,7 +2034,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 242; + CURRENT_PROJECT_VERSION = 243; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2071,7 +2071,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 242; + CURRENT_PROJECT_VERSION = 243; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2108,7 +2108,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 242; + CURRENT_PROJECT_VERSION = 243; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2159,7 +2159,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 242; + CURRENT_PROJECT_VERSION = 243; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2210,7 +2210,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 242; + CURRENT_PROJECT_VERSION = 243; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2244,7 +2244,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 242; + CURRENT_PROJECT_VERSION = 243; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 23ffb621ab..b939e0ba55 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -26,11 +26,11 @@ android.enableJetifier=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.1-beta.4 -android.version_code=245 +android.version_name=6.1-beta.5 +android.version_code=246 -desktop.version_name=6.1-beta.4 -desktop.version_code=71 +desktop.version_name=6.1-beta.5 +desktop.version_code=72 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From aa2eafdacba0c4893e488c7ad138eafe5dbbf0b0 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 11 Oct 2024 20:46:08 +0100 Subject: [PATCH 024/567] blog: v6.1 announcement placeholder (#5004) * blog: v6.1 announcement placeholder * draft --- ...rity-review-better-calls-user-experience.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md diff --git a/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md b/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md new file mode 100644 index 0000000000..42ff739106 --- /dev/null +++ b/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md @@ -0,0 +1,18 @@ +--- +layout: layouts/article.html +title: "SimpleX network: cryptographic design review by Trail of Bits, v6.1 released with better calls and user experience." +date: 2024-10-14 +# image: images/20240814-reachable.png +# previewBody: blog_previews/20240814.html +permalink: "/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html" +draft: true + +--- + +# SimpleX network: security review of protocols design by Trail of Bits, v6.1 released with better calls and user experience. + +**Published:** Oct 14, 2024 + +This is the placeholder for the security review and release announcement. + +Come back on Monday afternoon! From e76dc33cf07569cf01a866cc9c39320f5f8ad43c Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 11 Oct 2024 23:47:54 +0400 Subject: [PATCH 025/567] core: associate new contact with all corresponding members on member contact re-creation (e.g. after it was merged to many members and then deleted) (#5028) --- src/Simplex/Chat/Store/Groups.hs | 10 ++--- tests/ChatTests/Groups.hs | 68 ++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 5 deletions(-) diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 55847114ca..142c702f77 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -1933,8 +1933,8 @@ createMemberContact contactId <- insertedRowId db DB.execute db - "UPDATE group_members SET contact_id = ?, updated_at = ? WHERE group_member_id = ?" - (contactId, currentTs, groupMemberId) + "UPDATE group_members SET contact_id = ?, updated_at = ? WHERE contact_profile_id = ?" + (contactId, currentTs, memberContactProfileId) DB.execute db [sql| @@ -2003,7 +2003,7 @@ createMemberContactInvited user@User {userId, profile = LocalProfile {preferences}} connIds gInfo - m@GroupMember {groupMemberId, localDisplayName = memberLDN, memberProfile, memberContactProfileId} + m@GroupMember {localDisplayName = memberLDN, memberProfile, memberContactProfileId} mConn subMode = do currentTs <- liftIO getCurrentTime @@ -2031,8 +2031,8 @@ createMemberContactInvited contactId <- insertedRowId db DB.execute db - "UPDATE group_members SET contact_id = ?, updated_at = ? WHERE group_member_id = ?" - (contactId, currentTs, groupMemberId) + "UPDATE group_members SET contact_id = ?, updated_at = ? WHERE contact_profile_id = ?" + (contactId, currentTs, memberContactProfileId) pure contactId updateMemberContactInvited :: DB.Connection -> User -> (CommandId, ConnId) -> GroupInfo -> Connection -> Contact -> SubscriptionMode -> ExceptT StoreError IO Contact diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index 1d12625cdc..f1a36c8722 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -130,6 +130,7 @@ chatGroupTests = do it "invited member replaces member contact reference if it already exists" testMemberContactInvitedConnectionReplaced it "share incognito profile" testMemberContactIncognito it "sends and updates profile when creating contact" testMemberContactProfileUpdate + it "re-create member contact after deletion, many groups" testRecreateMemberContactManyGroups describe "group message forwarding" $ do it "forward messages between invitee and introduced (x.msg.new)" testGroupMsgForward it "deduplicate forwarded messages" testGroupMsgForwardDeduplicate @@ -4537,6 +4538,73 @@ testMemberContactProfileUpdate = alice <# "#team kate> hello there" bob <# "#team kate> hello there" -- updated profile +testRecreateMemberContactManyGroups :: HasCallStack => FilePath -> IO () +testRecreateMemberContactManyGroups = + testChat2 aliceProfile bobProfile $ + \alice bob -> do + connectUsers alice bob + createGroup2' "team" alice bob False + createGroup2' "club" alice bob False + + -- alice can message bob via team and via club + alice ##> "@#team bob 1" + alice <# "@bob 1" + bob <# "alice> 1" + + bob ##> "@#team alice 2" + bob <# "@alice 2" + alice <# "bob> 2" + + alice ##> "@#club bob 3" + alice <# "@bob 3" + bob <# "alice> 3" + + bob ##> "@#club alice 4" + bob <# "@alice 4" + alice <# "bob> 4" + + -- alice deletes contact with bob + alice ##> "/d bob" + alice <## "bob: contact is deleted" + bob <## "alice (Alice) deleted contact with you" + + bob ##> "/d alice" + bob <## "alice: contact is deleted" + + -- alice creates member contact with bob + alice ##> "@#team bob hi" + alice + <### [ "member #team bob does not have direct connection, creating", + "contact for member #team bob is created", + "sent invitation to connect directly to member #team bob", + WithTime "@bob hi" + ] + bob + <### [ "#team alice is creating direct contact alice with you", + WithTime "alice> hi" + ] + bob <## "alice (Alice): you can send messages to contact" + concurrently_ + (alice <## "bob (Bob): contact is connected") + (bob <## "alice (Alice): contact is connected") + + -- alice can message bob via team and via club + alice ##> "@#team bob 1" + alice <# "@bob 1" + bob <# "alice> 1" + + bob ##> "@#team alice 2" + bob <# "@alice 2" + alice <# "bob> 2" + + alice ##> "@#club bob 3" + alice <# "@bob 3" + bob <# "alice> 3" + + bob ##> "@#club alice 4" + bob <# "@alice 4" + alice <# "bob> 4" + testGroupMsgForward :: HasCallStack => FilePath -> IO () testGroupMsgForward = testChat3 aliceProfile bobProfile cathProfile $ From 26986686ca1c7b1b39e928976f88555b750cab85 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 12 Oct 2024 09:06:05 +0100 Subject: [PATCH 026/567] ios: fix link previews to be enabled by default --- apps/ios/SimpleXChat/AppGroup.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ios/SimpleXChat/AppGroup.swift b/apps/ios/SimpleXChat/AppGroup.swift index afbcff2bf3..5ae3c9b901 100644 --- a/apps/ios/SimpleXChat/AppGroup.swift +++ b/apps/ios/SimpleXChat/AppGroup.swift @@ -85,7 +85,7 @@ public func registerGroupDefaults() { GROUP_DEFAULT_INITIAL_RANDOM_DB_PASSPHRASE: false, GROUP_DEFAULT_APP_LOCAL_AUTH_ENABLED: true, GROUP_DEFAULT_ALLOW_SHARE_EXTENSION: false, - GROUP_DEFAULT_PRIVACY_LINK_PREVIEWS: false, + GROUP_DEFAULT_PRIVACY_LINK_PREVIEWS: true, GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES: true, GROUP_DEFAULT_PRIVACY_TRANSFER_IMAGES_INLINE: false, GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES: true, From 7ab6e44a6ec55b30d8bbb9c669934d05497bf159 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 12 Oct 2024 10:33:45 +0100 Subject: [PATCH 027/567] directory service: list pending groups (#5029) * directory service: list pending groups * user commands to remove a group from directory and to set initial member role (TODO tests) * tests --- .../src/Directory/Events.hs | 17 ++++- .../src/Directory/Service.hs | 73 +++++++++++++------ .../src/Directory/Store.hs | 38 ++++++++-- tests/Bots/DirectoryTests.hs | 52 +++++++++++++ 4 files changed, 150 insertions(+), 30 deletions(-) diff --git a/apps/simplex-directory-service/src/Directory/Events.hs b/apps/simplex-directory-service/src/Directory/Events.hs index 64e6acf1d8..3119815d7b 100644 --- a/apps/simplex-directory-service/src/Directory/Events.hs +++ b/apps/simplex-directory-service/src/Directory/Events.hs @@ -106,11 +106,13 @@ data DirectoryCmdTag (r :: DirectoryRole) where DCConfirmDuplicateGroup_ :: DirectoryCmdTag 'DRUser DCListUserGroups_ :: DirectoryCmdTag 'DRUser DCDeleteGroup_ :: DirectoryCmdTag 'DRUser + DCSetRole_ :: DirectoryCmdTag 'DRUser DCApproveGroup_ :: DirectoryCmdTag 'DRSuperUser DCRejectGroup_ :: DirectoryCmdTag 'DRSuperUser DCSuspendGroup_ :: DirectoryCmdTag 'DRSuperUser DCResumeGroup_ :: DirectoryCmdTag 'DRSuperUser DCListLastGroups_ :: DirectoryCmdTag 'DRSuperUser + DCListPendingGroups_ :: DirectoryCmdTag 'DRSuperUser DCExecuteCommand_ :: DirectoryCmdTag 'DRSuperUser deriving instance Show (DirectoryCmdTag r) @@ -127,11 +129,13 @@ data DirectoryCmd (r :: DirectoryRole) where DCConfirmDuplicateGroup :: UserGroupRegId -> GroupName -> DirectoryCmd 'DRUser DCListUserGroups :: DirectoryCmd 'DRUser DCDeleteGroup :: UserGroupRegId -> GroupName -> DirectoryCmd 'DRUser + DCSetRole :: GroupId -> GroupName -> GroupMemberRole -> DirectoryCmd 'DRUser DCApproveGroup :: {groupId :: GroupId, displayName :: GroupName, groupApprovalId :: GroupApprovalId} -> DirectoryCmd 'DRSuperUser DCRejectGroup :: GroupId -> GroupName -> DirectoryCmd 'DRSuperUser DCSuspendGroup :: GroupId -> GroupName -> DirectoryCmd 'DRSuperUser DCResumeGroup :: GroupId -> GroupName -> DirectoryCmd 'DRSuperUser DCListLastGroups :: Int -> DirectoryCmd 'DRSuperUser + DCListPendingGroups :: Int -> DirectoryCmd 'DRSuperUser DCExecuteCommand :: String -> DirectoryCmd 'DRSuperUser DCUnknownCommand :: DirectoryCmd 'DRUser DCCommandError :: DirectoryCmdTag r -> DirectoryCmd r @@ -163,11 +167,13 @@ directoryCmdP = "list" -> u DCListUserGroups_ "ls" -> u DCListUserGroups_ "delete" -> u DCDeleteGroup_ + "role" -> u DCSetRole_ "approve" -> su DCApproveGroup_ "reject" -> su DCRejectGroup_ "suspend" -> su DCSuspendGroup_ "resume" -> su DCResumeGroup_ "last" -> su DCListLastGroups_ + "pending" -> su DCListPendingGroups_ "exec" -> su DCExecuteCommand_ "x" -> su DCExecuteCommand_ _ -> fail "bad command tag" @@ -184,14 +190,19 @@ directoryCmdP = DCConfirmDuplicateGroup_ -> gc DCConfirmDuplicateGroup DCListUserGroups_ -> pure DCListUserGroups DCDeleteGroup_ -> gc DCDeleteGroup + DCSetRole_ -> do + (groupId, displayName) <- gc (,) + memberRole <- A.space *> ("member" $> GRMember <|> "observer" $> GRObserver) + pure $ DCSetRole groupId displayName memberRole DCApproveGroup_ -> do (groupId, displayName) <- gc (,) groupApprovalId <- A.space *> A.decimal - pure $ DCApproveGroup {groupId, displayName, groupApprovalId} + pure DCApproveGroup {groupId, displayName, groupApprovalId} DCRejectGroup_ -> gc DCRejectGroup DCSuspendGroup_ -> gc DCSuspendGroup DCResumeGroup_ -> gc DCResumeGroup DCListLastGroups_ -> DCListLastGroups <$> (A.space *> A.decimal <|> pure 10) + DCListPendingGroups_ -> DCListPendingGroups <$> (A.space *> A.decimal <|> pure 10) DCExecuteCommand_ -> DCExecuteCommand . T.unpack <$> (A.space *> A.takeText) where gc f = f <$> (A.space *> A.decimal <* A.char ':') <*> displayNameP @@ -214,13 +225,15 @@ directoryCmdTag = \case DCRecentGroups -> "new" DCSubmitGroup _ -> "submit" DCConfirmDuplicateGroup {} -> "confirm" - DCListUserGroups -> "list" + DCListUserGroups -> "list" DCDeleteGroup {} -> "delete" DCApproveGroup {} -> "approve" + DCSetRole {} -> "role" DCRejectGroup {} -> "reject" DCSuspendGroup {} -> "suspend" DCResumeGroup {} -> "resume" DCListLastGroups _ -> "last" + DCListPendingGroups _ -> "pending" DCExecuteCommand _ -> "exec" DCUnknownCommand -> "unknown" DCCommandError _ -> "error" diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs index 2b12427638..ba03642a28 100644 --- a/apps/simplex-directory-service/src/Directory/Service.hs +++ b/apps/simplex-directory-service/src/Directory/Service.hs @@ -23,6 +23,7 @@ import Data.Set (Set) import qualified Data.Set as S import Data.Text (Text) import qualified Data.Text as T +import Data.Text.Encoding (decodeLatin1) import Data.Time.Clock (diffUTCTime, getCurrentTime) import Data.Time.LocalTime (getCurrentTimeZone) import Directory.Events @@ -447,30 +448,43 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi DCRecentGroups -> withFoundListedGroups Nothing $ sendAllGroups takeRecent "the most recent" STRecent DCSubmitGroup _link -> pure () DCConfirmDuplicateGroup ugrId gName -> - atomically (getUserGroupReg st (contactId' ct) ugrId) >>= \case - Nothing -> sendReply $ "Group ID " <> show ugrId <> " not found" - Just GroupReg {dbGroupId, groupRegStatus} -> do - getGroup cc dbGroupId >>= \case - Nothing -> sendReply $ "Group ID " <> show ugrId <> " not found" - Just g@GroupInfo {groupProfile = GroupProfile {displayName}} - | displayName == gName -> - readTVarIO groupRegStatus >>= \case - GRSPendingConfirmation -> do - getDuplicateGroup g >>= \case - Nothing -> sendMessage cc ct "Error: getDuplicateGroup. Please notify the developers." - Just DGReserved -> sendMessage cc ct $ groupAlreadyListed g - _ -> processInvitation ct g - _ -> sendReply $ "Error: the group ID " <> show ugrId <> " (" <> T.unpack displayName <> ") is not pending confirmation." - | otherwise -> sendReply $ "Group ID " <> show ugrId <> " has the display name " <> T.unpack displayName + withUserGroupReg ugrId gName $ \gr g@GroupInfo {groupProfile = GroupProfile {displayName}} -> + readTVarIO (groupRegStatus gr) >>= \case + GRSPendingConfirmation -> + getDuplicateGroup g >>= \case + Nothing -> sendMessage cc ct "Error: getDuplicateGroup. Please notify the developers." + Just DGReserved -> sendMessage cc ct $ groupAlreadyListed g + _ -> processInvitation ct g + _ -> sendReply $ "Error: the group ID " <> show ugrId <> " (" <> T.unpack displayName <> ") is not pending confirmation." DCListUserGroups -> atomically (getUserGroupRegs st $ contactId' ct) >>= \grs -> do sendReply $ show (length grs) <> " registered group(s)" void . forkIO $ forM_ (reverse grs) $ \gr@GroupReg {userGroupRegId} -> sendGroupInfo ct gr userGroupRegId Nothing - DCDeleteGroup _ugrId _gName -> pure () + DCDeleteGroup ugrId gName -> + withUserGroupReg ugrId gName $ \gr GroupInfo {groupProfile = GroupProfile {displayName}} -> do + delGroupReg st gr + sendReply $ T.unpack $ "Your group " <> displayName <> " is deleted from the directory" + DCSetRole ugrId gName mRole -> + withUserGroupReg ugrId gName $ \_gr GroupInfo {groupId, groupProfile = GroupProfile {displayName}} -> do + gLink_ <- setGroupLinkRole cc groupId mRole + sendReply $ T.unpack $ case gLink_ of + Nothing -> "Error: the initial member role for the group " <> displayName <> " was NOT upgated" + Just gLink -> + ("The initial member role for the group " <> displayName <> " is set to *" <> decodeLatin1 (strEncode mRole) <> "*\n\n") + <> ("*Please note*: it applies only to members joining via this link: " <> safeDecodeUtf8 (strEncode $ simplexChatContact gLink)) DCUnknownCommand -> sendReply "Unknown command" DCCommandError tag -> sendReply $ "Command error: " <> show tag where + withUserGroupReg ugrId gName action = + atomically (getUserGroupReg st (contactId' ct) ugrId) >>= \case + Nothing -> sendReply $ "Group ID " <> show ugrId <> " not found" + Just gr@GroupReg {dbGroupId} -> do + getGroup cc dbGroupId >>= \case + Nothing -> sendReply $ "Group ID " <> show ugrId <> " not found" + Just g@GroupInfo {groupProfile = GroupProfile {displayName}} + | displayName == gName -> action gr g + | otherwise -> sendReply $ "Group ID " <> show ugrId <> " has the display name " <> T.unpack displayName sendReply = sendComposedMessage cc ct (Just ciId) . textMsgContent withFoundListedGroups s_ action = getGroups_ s_ >>= \case @@ -576,13 +590,8 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi notifyOwner gr $ "The group " <> userGroupReference' gr gName <> " is listed in the directory again!" sendReply "Group listing resumed!" _ -> sendReply $ "The group " <> groupRef <> " is not suspended, can't be resumed." - DCListLastGroups count -> - readTVarIO (groupRegs st) >>= \grs -> do - sendReply $ show (length grs) <> " registered group(s)" <> (if length grs > count then ", showing the last " <> show count else "") - void . forkIO $ forM_ (reverse $ take count grs) $ \gr@GroupReg {dbGroupId, dbContactId} -> do - ct_ <- getContact cc dbContactId - let ownerStr = "Owner: " <> maybe "getContact error" localDisplayName' ct_ - sendGroupInfo ct gr dbGroupId $ Just ownerStr + DCListLastGroups count -> listGroups count False + DCListPendingGroups count -> listGroups count True DCExecuteCommand cmdStr -> sendChatCmdStr cc cmdStr >>= \r -> do ts <- getCurrentTime @@ -593,6 +602,17 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi where superUser = KnownContact {contactId = contactId' ct, localDisplayName = localDisplayName' ct} sendReply = sendComposedMessage cc ct (Just ciId) . textMsgContent + listGroups count pending = + readTVarIO (groupRegs st) >>= \groups -> do + grs <- + if pending + then filterM (fmap pendingApproval . readTVarIO . groupRegStatus) groups + else pure groups + sendReply $ show (length grs) <> " registered group(s)" <> (if length grs > count then ", showing the last " <> show count else "") + void . forkIO $ forM_ (reverse $ take count grs) $ \gr@GroupReg {dbGroupId, dbContactId} -> do + ct_ <- getContact cc dbContactId + let ownerStr = "Owner: " <> maybe "getContact error" localDisplayName' ct_ + sendGroupInfo ct gr dbGroupId $ Just ownerStr getGroupAndReg :: GroupId -> GroupName -> IO (Maybe (GroupInfo, GroupReg)) getGroupAndReg gId gName = @@ -641,5 +661,12 @@ getGroupAndSummary cc gId = resp <$> sendChatCmd cc (APIGroupInfo gId) CRGroupInfo {groupInfo, groupSummary} -> Just (groupInfo, groupSummary) _ -> Nothing +setGroupLinkRole :: ChatController -> GroupId -> GroupMemberRole -> IO (Maybe ConnReqContact) +setGroupLinkRole cc gId mRole = resp <$> sendChatCmd cc (APIGroupLinkMemberRole gId mRole) + where + resp = \case + CRGroupLink _ _ gLink _ -> Just gLink + _ -> Nothing + unexpectedError :: String -> String unexpectedError err = "Unexpected error: " <> err <> ", please notify the developers." diff --git a/apps/simplex-directory-service/src/Directory/Store.hs b/apps/simplex-directory-service/src/Directory/Store.hs index c810102e08..cecb253e8d 100644 --- a/apps/simplex-directory-service/src/Directory/Store.hs +++ b/apps/simplex-directory-service/src/Directory/Store.hs @@ -12,6 +12,7 @@ module Directory.Store GroupApprovalId, restoreDirectoryStore, addGroupReg, + delGroupReg, setGroupStatus, setGroupRegOwner, getGroupReg, @@ -19,6 +20,7 @@ module Directory.Store getUserGroupRegs, filterListedGroups, groupRegStatusText, + pendingApproval, ) where @@ -79,6 +81,11 @@ data GroupRegStatus | GRSSuspendedBadRoles | GRSRemoved +pendingApproval :: GroupRegStatus -> Bool +pendingApproval = \case + GRSPendingApproval _ -> True + _ -> False + data DirectoryStatus = DSListed | DSReserved | DSRegistered groupRegStatusText :: GroupRegStatus -> Text @@ -118,6 +125,12 @@ addGroupReg st ct GroupInfo {groupId} grStatus = do | dbContactId == ctId && userGroupRegId > mx = userGroupRegId | otherwise = mx +delGroupReg :: DirectoryStore -> GroupReg -> IO () +delGroupReg st GroupReg {dbGroupId = gId} = do + logGDelete st gId + atomically $ unlistGroup st gId + atomically $ modifyTVar' (groupRegs st) $ filter ((gId ==) . dbGroupId) + setGroupStatus :: DirectoryStore -> GroupReg -> GroupRegStatus -> IO () setGroupStatus st gr grStatus = do logGUpdateStatus st (dbGroupId gr) grStatus @@ -167,10 +180,15 @@ unlistGroup st gId = do data DirectoryLogRecord = GRCreate GroupRegData + | GRDelete GroupId | GRUpdateStatus GroupId GroupRegStatus | GRUpdateOwner GroupId GroupMemberId -data DLRTag = GRCreate_ | GRUpdateStatus_ | GRUpdateOwner_ +data DLRTag + = GRCreate_ + | GRDelete_ + | GRUpdateStatus_ + | GRUpdateOwner_ logDLR :: DirectoryStore -> DirectoryLogRecord -> IO () logDLR st r = forM_ (directoryLogFile st) $ \h -> B.hPutStrLn h (strEncode r) @@ -178,6 +196,9 @@ logDLR st r = forM_ (directoryLogFile st) $ \h -> B.hPutStrLn h (strEncode r) logGCreate :: DirectoryStore -> GroupRegData -> IO () logGCreate st = logDLR st . GRCreate +logGDelete :: DirectoryStore -> GroupId -> IO () +logGDelete st = logDLR st . GRDelete + logGUpdateStatus :: DirectoryStore -> GroupId -> GroupRegStatus -> IO () logGUpdateStatus st = logDLR st .: GRUpdateStatus @@ -187,11 +208,13 @@ logGUpdateOwner st = logDLR st .: GRUpdateOwner instance StrEncoding DLRTag where strEncode = \case GRCreate_ -> "GCREATE" + GRDelete_ -> "GDELETE" GRUpdateStatus_ -> "GSTATUS" GRUpdateOwner_ -> "GOWNER" strP = A.takeTill (== ' ') >>= \case "GCREATE" -> pure GRCreate_ + "GDELETE" -> pure GRDelete_ "GSTATUS" -> pure GRUpdateStatus_ "GOWNER" -> pure GRUpdateOwner_ _ -> fail "invalid DLRTag" @@ -199,13 +222,15 @@ instance StrEncoding DLRTag where instance StrEncoding DirectoryLogRecord where strEncode = \case GRCreate gr -> strEncode (GRCreate_, gr) + GRDelete gId -> strEncode (GRDelete_, gId) GRUpdateStatus gId grStatus -> strEncode (GRUpdateStatus_, gId, grStatus) GRUpdateOwner gId grOwnerId -> strEncode (GRUpdateOwner_, gId, grOwnerId) strP = - strP >>= \case - GRCreate_ -> GRCreate <$> (A.space *> strP) - GRUpdateStatus_ -> GRUpdateStatus <$> (A.space *> A.decimal) <*> (A.space *> strP) - GRUpdateOwner_ -> GRUpdateOwner <$> (A.space *> A.decimal) <*> (A.space *> A.decimal) + strP_ >>= \case + GRCreate_ -> GRCreate <$> strP + GRDelete_ -> GRDelete <$> strP + GRUpdateStatus_ -> GRUpdateStatus <$> A.decimal <*> _strP + GRUpdateOwner_ -> GRUpdateOwner <$> A.decimal <* A.space <*> A.decimal instance StrEncoding GroupRegData where strEncode GroupRegData {dbGroupId_, userGroupRegId_, dbContactId_, dbOwnerMemberId_, groupRegStatus_} = @@ -314,6 +339,9 @@ readDirectoryData f = putStrLn $ "Warning: duplicate group with ID " <> show gId <> ", group replaced." pure $ M.insert gId gr m + GRDelete gId -> case M.lookup gId m of + Just _ -> pure $ M.delete gId m + Nothing -> m <$ putStrLn ("Warning: no group with ID " <> show gId <> ", deletion ignored.") GRUpdateStatus gId groupRegStatus_ -> case M.lookup gId m of Just gr -> pure $ M.insert gId gr {groupRegStatus_} m Nothing -> m <$ putStrLn ("Warning: no group with ID " <> show gId <> ", status update ignored.") diff --git a/tests/Bots/DirectoryTests.hs b/tests/Bots/DirectoryTests.hs index 7cd775c5ee..3a3e9f889f 100644 --- a/tests/Bots/DirectoryTests.hs +++ b/tests/Bots/DirectoryTests.hs @@ -28,6 +28,8 @@ directoryServiceTests :: SpecWith FilePath directoryServiceTests = do it "should register group" testDirectoryService it "should suspend and resume group" testSuspendResume + it "should delete group registration" testDeleteGroup + it "should change initial member role" testSetRole it "should join found group via link" testJoinGroup it "should support group names with spaces" testGroupNameWithSpaces it "should return more groups in search, all and recent groups" testSearchGroups @@ -139,6 +141,15 @@ testDirectoryService tmp = bob <# "SimpleX-Directory> Thank you! The group link for ID 1 (PSA) is added to the welcome message." bob <## "You will be notified once the group is added to the directory - it may take up to 24 hours." approvalRequested superUser welcomeWithLink' (1 :: Int) + superUser #> "@SimpleX-Directory /pending" + superUser <# "SimpleX-Directory> > /pending" + superUser <## " 1 registered group(s)" + superUser <# "SimpleX-Directory> 1. PSA (Privacy, Security & Anonymity)" + superUser <## "Welcome message:" + superUser <##. "Welcome! Link to join the group PSA: " + superUser <## "Owner: bob" + superUser <## "2 members" + superUser <## "Status: pending admin approval" superUser #> "@SimpleX-Directory /approve 1:PSA 1" superUser <# "SimpleX-Directory> > /approve 1:PSA 1" superUser <## " Group approved!" @@ -197,6 +208,47 @@ testSuspendResume tmp = bob <# "SimpleX-Directory> The group ID 1 (privacy) is listed in the directory again!" groupFound bob "privacy" +testDeleteGroup :: HasCallStack => FilePath -> IO () +testDeleteGroup tmp = + withDirectoryService tmp $ \superUser dsLink -> + withNewTestChat tmp "bob" bobProfile $ \bob -> do + bob `connectVia` dsLink + registerGroup superUser bob "privacy" "Privacy" + groupFound bob "privacy" + bob #> "@SimpleX-Directory /delete 1:privacy" + bob <# "SimpleX-Directory> > /delete 1:privacy" + bob <## " Your group privacy is deleted from the directory" + groupNotFound bob "privacy" + +testSetRole :: HasCallStack => FilePath -> IO () +testSetRole tmp = + withDirectoryService tmp $ \superUser dsLink -> + withNewTestChat tmp "bob" bobProfile $ \bob -> + withNewTestChat tmp "cath" cathProfile $ \cath -> do + bob `connectVia` dsLink + registerGroup superUser bob "privacy" "Privacy" + groupFound bob "privacy" + bob #> "@SimpleX-Directory /role 1:privacy observer" + bob <# "SimpleX-Directory> > /role 1:privacy observer" + bob <## " The initial member role for the group privacy is set to observer" + bob <## "" + note <- getTermLine bob + let groupLink = dropStrPrefix "Please note: it applies only to members joining via this link: " note + cath ##> ("/c " <> groupLink) + cath <## "connection request sent!" + cath <## "#privacy: joining the group..." + cath <## "#privacy: you joined the group" + cath <#. "#privacy SimpleX-Directory> Link to join the group privacy: https://simplex.chat/" + cath <## "#privacy: member bob (Bob) is connected" + bob <## "#privacy: SimpleX-Directory added cath (Catherine) to the group (connecting...)" + bob <## "#privacy: new member cath is connected" + bob ##> "/ms #privacy" + bob <## "bob (Bob): owner, you, created group" + bob <## "SimpleX-Directory: admin, invited, connected" + bob <## "cath (Catherine): observer, connected" + cath ##> "#privacy hello" + cath <## "#privacy: you don't have permission to send messages" + testJoinGroup :: HasCallStack => FilePath -> IO () testJoinGroup tmp = withDirectoryServiceCfg tmp testCfgGroupLinkViaContact $ \superUser dsLink -> From 88fdc1ef75eaf1d7077655864b380b2a5353d33b Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 12 Oct 2024 10:56:24 +0100 Subject: [PATCH 028/567] core: 6.1.0.9 --- package.yaml | 2 +- simplex-chat.cabal | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.yaml b/package.yaml index 4cd245590f..d98b470be9 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.1.0.8 +version: 6.1.0.9 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/simplex-chat.cabal b/simplex-chat.cabal index ca67d2c7f3..6913ddc14d 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.1.0.8 +version: 6.1.0.9 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From fa95e4e9ad4c82adaf8a89a30d55d8619455c755 Mon Sep 17 00:00:00 2001 From: Diogo Date: Sat, 12 Oct 2024 10:59:51 +0100 Subject: [PATCH 029/567] ios: dont show tails for moderated and blocked items unless revealed (#5030) * ios: stop showing tails for non revealed moderated or blocked items * simplify --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Views/Chat/ChatView.swift | 2 +- apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 3e056fa581..b3943eb066 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -1187,7 +1187,7 @@ struct ChatView: View { allowMenu: $allowMenu ) .environment(\.showTimestamp, itemSeparation.timestamp) - .modifier(ChatItemClipped(ci, tailVisible: itemSeparation.largeGap)) + .modifier(ChatItemClipped(ci, tailVisible: itemSeparation.largeGap && (ci.meta.itemDeleted == nil || revealed))) .contextMenu { menu(ci, range, live: composeState.liveMessage != nil) } .accessibilityLabel("") if ci.content.msgContent != nil && (ci.meta.itemDeleted == nil || revealed) && ci.reactions.count > 0 { diff --git a/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift b/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift index e1e0911e4d..9aa6ac86cf 100644 --- a/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift +++ b/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift @@ -36,12 +36,7 @@ struct ChatItemClipped: ViewModifier { .sndMsgContent, .rcvMsgContent, .rcvDecryptionError, - .sndDeleted, - .rcvDeleted, .rcvIntegrityError, - .sndModerated, - .rcvModerated, - .rcvBlocked, .invalidJSON: let tail = if let mc = ci.content.msgContent, mc.isImageOrVideo && mc.text.isEmpty { false From d2b4b7bed6861dc1f6a6bbbc68d657206e86bc4d Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 12 Oct 2024 11:36:49 +0100 Subject: [PATCH 030/567] ui: translations (#5031) * Translated using Weblate (Spanish) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Dutch) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/ * Translated using Weblate (Czech) Currently translated at 96.1% (2008 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * process localizations --------- Co-authored-by: No name Co-authored-by: M1K4 Co-authored-by: zenobit Co-authored-by: summoner001 --- .../es.xcloc/Localized Contents/es.xliff | 2 +- .../hu.xcloc/Localized Contents/hu.xliff | 34 +-- .../nl.xcloc/Localized Contents/nl.xliff | 2 +- apps/ios/es.lproj/Localizable.strings | 2 +- apps/ios/hu.lproj/Localizable.strings | 34 +-- apps/ios/nl.lproj/Localizable.strings | 2 +- .../commonMain/resources/MR/cs/strings.xml | 202 +++++++++++++++++- .../commonMain/resources/MR/es/strings.xml | 2 +- .../commonMain/resources/MR/hu/strings.xml | 42 ++-- .../commonMain/resources/MR/nl/strings.xml | 2 +- 10 files changed, 256 insertions(+), 68 deletions(-) diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 617a693c7d..21cd9919db 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -2246,7 +2246,7 @@ This is your own one-time link! Delete or moderate up to 200 messages. - Borrar o moderar hasta 200 mensajes. + Borra o modera hasta 200 mensajes a la vez. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 16eb6e8288..0c8c1635a5 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -1438,7 +1438,7 @@ Choose _Migrate from another device_ on the new device and scan QR code. - Válassza az _Átköltöztetés egy másik eszközről opciót az új eszközén és olvassa be a QR-kódot. + Válassza az _Átköltöztetés egy másik eszközről_ opciót az új eszközén és olvassa be a QR-kódot. No comment provided by engineer. @@ -1722,7 +1722,7 @@ Ez az Ön egyszer használható hivatkozása! Connection timeout - Kapcsolat időtúllépés + Időtúllépés kapcsolódáskor No comment provided by engineer. @@ -1977,7 +1977,7 @@ Ez az Ön egyszer használható hivatkozása! Database IDs and Transport isolation option. - Adatbázis-azonosítók és átviteli izolációs beállítások. + Adatbázis-azonosítók és átvitel-izolációs beállítások. No comment provided by engineer. @@ -2356,7 +2356,7 @@ Ez az Ön egyszer használható hivatkozása! Details - Részletek + További részletek No comment provided by engineer. @@ -2391,7 +2391,7 @@ Ez az Ön egyszer használható hivatkozása! Different names, avatars and transport isolation. - Különböző nevek, avatarok és átviteli izoláció. + Különböző nevek, profilképek és átvitel-izoláció. No comment provided by engineer. @@ -4355,12 +4355,12 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! Message status - Üzenet állapota + Üzenetállapot No comment provided by engineer. Message status: %@ - Üzenet állapota: %@ + Üzenetállapot: %@ copied message info @@ -4955,12 +4955,12 @@ VPN engedélyezése szükséges. PING count - PING számláló + PING-ek száma No comment provided by engineer. PING interval - PING időköze + Időtartam a PING-ek között No comment provided by engineer. @@ -5316,12 +5316,12 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Protocol timeout - Protokoll időtúllépés + Protokoll időtúllépése No comment provided by engineer. Protocol timeout per KB - Protokoll időkorlát KB-onként + Protokoll időtúllépése KB-onként No comment provided by engineer. @@ -5446,7 +5446,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Received total - Összes fogadott + Összes fogadott üzenet No comment provided by engineer. @@ -6158,7 +6158,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Sent total - Összes elküldött + Összes elküldött üzenet No comment provided by engineer. @@ -6373,7 +6373,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Show message status - Üzenet állapot megjelenítése + Üzenetállapot megjelenítése No comment provided by engineer. @@ -6678,7 +6678,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. TCP connection timeout - TCP kapcsolat időtúllépés + TCP kapcsolat időtúllépése No comment provided by engineer. @@ -7072,12 +7072,12 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll Total - Összesen + Összes kapcsolat No comment provided by engineer. Transport isolation - Kapcsolat izolációs mód + Átvitel-izoláció módja No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 0bc47c81ea..ce6faeccca 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -6653,7 +6653,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Switch audio and video during the call. - Wissel tussen audio en video tijdens het gesprek. + Wisselen tussen audio en video tijdens het gesprek. No comment provided by engineer. diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 196be9aba2..70c29f49e0 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -1501,7 +1501,7 @@ "Delete old database?" = "¿Eliminar base de datos antigua?"; /* No comment provided by engineer. */ -"Delete or moderate up to 200 messages." = "Borrar o moderar hasta 200 mensajes."; +"Delete or moderate up to 200 messages." = "Borra o modera hasta 200 mensajes a la vez."; /* No comment provided by engineer. */ "Delete pending connection?" = "¿Eliminar conexión pendiente?"; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index f2fc640d13..c707f72bf6 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -948,7 +948,7 @@ "Chinese and Spanish interface" = "Kínai és spanyol kezelőfelület"; /* No comment provided by engineer. */ -"Choose _Migrate from another device_ on the new device and scan QR code." = "Válassza az _Átköltöztetés egy másik eszközről opciót az új eszközén és olvassa be a QR-kódot."; +"Choose _Migrate from another device_ on the new device and scan QR code." = "Válassza az _Átköltöztetés egy másik eszközről_ opciót az új eszközén és olvassa be a QR-kódot."; /* No comment provided by engineer. */ "Choose file" = "Fájl kiválasztása"; @@ -1155,7 +1155,7 @@ "Connection terminated" = "Kapcsolat megszakítva"; /* No comment provided by engineer. */ -"Connection timeout" = "Kapcsolat időtúllépés"; +"Connection timeout" = "Időtúllépés kapcsolódáskor"; /* No comment provided by engineer. */ "Connection with desktop stopped" = "A kapcsolat a számítógéppel megszakadt"; @@ -1341,7 +1341,7 @@ "Database ID: %d" = "Adatbázis-azonosító: %d"; /* No comment provided by engineer. */ -"Database IDs and Transport isolation option." = "Adatbázis-azonosítók és átviteli izolációs beállítások."; +"Database IDs and Transport isolation option." = "Adatbázis-azonosítók és átvitel-izolációs beállítások."; /* No comment provided by engineer. */ "Database is encrypted using a random passphrase, you can change it." = "Az adatbázis egy véletlenszerű jelmondattal van titkosítva, ami megváltoztatható."; @@ -1576,7 +1576,7 @@ "Detailed statistics" = "Részletes statisztikák"; /* No comment provided by engineer. */ -"Details" = "Részletek"; +"Details" = "További részletek"; /* No comment provided by engineer. */ "Develop" = "Fejlesztés"; @@ -1600,7 +1600,7 @@ "different migration in the app/database: %@ / %@" = "különböző átköltöztetések az alkalmazásban/adatbázisban: %@ / %@"; /* No comment provided by engineer. */ -"Different names, avatars and transport isolation." = "Különböző nevek, avatarok és átviteli izoláció."; +"Different names, avatars and transport isolation." = "Különböző nevek, profilképek és átvitel-izoláció."; /* connection level description */ "direct" = "közvetlen"; @@ -2918,10 +2918,10 @@ "Message source remains private." = "Az üzenet forrása titokban marad."; /* No comment provided by engineer. */ -"Message status" = "Üzenet állapota"; +"Message status" = "Üzenetállapot"; /* copied message info */ -"Message status: %@" = "Üzenet állapota: %@"; +"Message status: %@" = "Üzenetállapot: %@"; /* No comment provided by engineer. */ "Message text" = "Név és üzenet"; @@ -3388,10 +3388,10 @@ "Picture-in-picture calls" = "Kép a képben hívások"; /* No comment provided by engineer. */ -"PING count" = "PING számláló"; +"PING count" = "PING-ek száma"; /* No comment provided by engineer. */ -"PING interval" = "PING időköze"; +"PING interval" = "Időtartam a PING-ek között"; /* No comment provided by engineer. */ "Play from the chat list." = "Lejátszás a csevegési listából."; @@ -3550,10 +3550,10 @@ "Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Védje IP-címét az ismerősei által kiválasztott üzenet-közvetítő-kiszolgálókkal szemben.\nEngedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben."; /* No comment provided by engineer. */ -"Protocol timeout" = "Protokoll időtúllépés"; +"Protocol timeout" = "Protokoll időtúllépése"; /* No comment provided by engineer. */ -"Protocol timeout per KB" = "Protokoll időkorlát KB-onként"; +"Protocol timeout per KB" = "Protokoll időtúllépése KB-onként"; /* No comment provided by engineer. */ "Proxied" = "Proxyzott"; @@ -3637,7 +3637,7 @@ "Received reply" = "Fogadott válaszüzenet-buborék színe"; /* No comment provided by engineer. */ -"Received total" = "Összes fogadott"; +"Received total" = "Összes fogadott üzenet"; /* No comment provided by engineer. */ "Receiving address will be changed to a different server. Address change will complete after sender comes online." = "A fogadó cím egy másik kiszolgálóra változik. A címváltoztatás a feladó online állapotba kerülése után fejeződik be."; @@ -4101,7 +4101,7 @@ "Sent reply" = "Válaszüzenet-buborék színe"; /* No comment provided by engineer. */ -"Sent total" = "Összes elküldött"; +"Sent total" = "Összes elküldött üzenet"; /* No comment provided by engineer. */ "Sent via proxy" = "Proxyn keresztül küldve"; @@ -4239,7 +4239,7 @@ "Show last messages" = "Szobák utolsó üzeneteinek megjelenítése a listanézetben"; /* No comment provided by engineer. */ -"Show message status" = "Üzenet állapot megjelenítése"; +"Show message status" = "Üzenetállapot megjelenítése"; /* No comment provided by engineer. */ "Show percentage" = "Százalék megjelenítése"; @@ -4464,7 +4464,7 @@ "TCP connection" = "TCP kapcsolat"; /* No comment provided by engineer. */ -"TCP connection timeout" = "TCP kapcsolat időtúllépés"; +"TCP connection timeout" = "TCP kapcsolat időtúllépése"; /* No comment provided by engineer. */ "TCP_KEEPCNT" = "TCP_KEEPCNT"; @@ -4674,10 +4674,10 @@ "Toolbar opacity" = "Eszköztár átlátszatlansága"; /* No comment provided by engineer. */ -"Total" = "Összesen"; +"Total" = "Összes kapcsolat"; /* No comment provided by engineer. */ -"Transport isolation" = "Kapcsolat izolációs mód"; +"Transport isolation" = "Átvitel-izoláció módja"; /* No comment provided by engineer. */ "Transport sessions" = "Munkamenetek átvitele"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index d1b74886f1..b9caba8463 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -4422,7 +4422,7 @@ "Support SimpleX Chat" = "Ondersteuning van SimpleX Chat"; /* No comment provided by engineer. */ -"Switch audio and video during the call." = "Wissel tussen audio en video tijdens het gesprek."; +"Switch audio and video during the call." = "Wisselen tussen audio en video tijdens het gesprek."; /* No comment provided by engineer. */ "Switch chat profile for 1-time invitations." = "Wijzig chatprofiel voor eenmalige uitnodigingen."; diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml index 595545213a..59c531f3d3 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml @@ -667,8 +667,8 @@ obdržel potvrzení… připojování… Nové vymezení soukromí - 1. platforma bez jakýchkoliv uživatelských identifikátorů – soukromá již od návrhu. - Odolná vůči spamu a zneužití + Bez uživatelských identifikátorů + Odolná vůči spamu K ochraně soukromí, místo uživatelských ID užívaných všemi ostatními platformami, SimpleX používá identifikátory pro fronty zpráv, zvlášť pro každý z vašich kontaktů. když SimpleX nemá žádný identifikátor uživatelů, jak může doručovat zprávy\?]]> přijímat zprávy, vaše kontakty – servery, které používáte k zasílání zpráv.]]> @@ -679,8 +679,8 @@ Když aplikace běží Okamžité Nejlepší pro baterii. Budete přijímat oznámení pouze když aplikace běží (žádná služba na pozadí).]]> - Dobré pro baterii. Služba na pozadí bude kontrolovat každých 10 minut. Můžete zmeškat hovory nebo naléhavé zprávy.]]> - Využívá více baterie! Služba na pozadí je spuštěna vždy - oznámení se zobrazí, jakmile jsou zprávy k dispozici.]]> + Dobré pro baterii. Apka bude kontrolovat zprávy každých 10 minut. Můžete zmeškat volání nebo naléhavé zprávy.]]> + Využívá více baterie! Apka stále běží na pozadí - oznámení se zobrazí okamžitě.]]> Vložte přijatý odkaz Příchozí videohovor Příchozí zvukový hovor @@ -1582,7 +1582,7 @@ člen %1$s změněn na %2$s blokováno Blokováno adminem - Vytvořeno v: %s + Vytvořen v: %s Zpráva příliš velká %s z důvodu: %s]]> Spojení zastaveno @@ -1817,8 +1817,7 @@ Bezpečné přijímání souborů Nové motivy chatu Soukromé směrování zpráv 🚀 - Chraňte vaši IP adresu před relé zpráv, které jste si vybrali. -\nPovolit v nastavení *Síť & servery*. + Chraňte vaši IP adresu před relé zpráv vašich kontaktů.\nPovolte v nastavení *Síť & servery*. Vylepšené doručování zpráv Perské UI Prosím zkontrolujte, že mobil a desktop jsou připojeny ke stejné místní síti, a že stolní firewall umožňuje připojení. @@ -1862,4 +1861,193 @@ Nelze zavolat člena skupiny Archivované kontakty Archivujte kontakty pro pozdější chatování. + Adresa předávacího serveru je nekompatibilní s nastavením sítě: %1$s. + Verze předávacího serveru je nekompatibilní s nastavením sítě: %1$s. + Cílová adresa serveru %1$s je nekompatibilní s nastavením přeposílajícího serveru %2$s. + Chyba připojení k přeposílajícímu serveru %1$s. Prosím, zkuste to později. + Předávacímu serveru %1$s se nepodařilo připojit k cílovému serveru %2$s. Prosím, zkuste to později. + Vybrané nastavení chatu zakazuje tuto zprávu. + Jiné SMP servery + Nastavené SMP servery + Probíhá + Části nahrány + %1$d chuba souboru(ů):\n%2$s + %1$d jiná chyba souboru(ů). + Chyba přeposílaní zpráv + Adresa serveru není kompatibilní s nastavením sítě. + Předat %1$s zpráv(u)? + Nic k předání! + Předat zprávy bez souborů? + %1$d soubor(y) se nepodařilo stáhnout. + %1$d soubor(y) nestažen(y). + %1$d soubor(y) smazán(y). + %1$s zprávy nepředány + Stáhnout + Předávám %1$s zpráv + Předat zprávy… + Uložit %1$s zpráv + Nepoužívat autorizaci s proxy. + Chyba ukládání proxy + Ujistěte se, že nastavení proxy je správné. + Heslo + Proxy autentizace + Instalovat aktualizace + Otevřít umístění souboru + Stahování aktualizace, nezavírejte aplikaci + Prosím restartujte aplikaci. + Instalovány úspěšně + Připomenout později + Zkontrolovat aktualizace + Vypnuto + CHAT DATABÁZE + vypnut + info fronty serveru: %1$s\n\nposlední obdržená zpráva: %2$s + Uložit a připojit znovu + Hrajte ze seznamu chatů. + Přijatých zprávy + Znovu připojit servery? + Kompletní + Zabezpečeno + Prosím zkontrolujte, že SimpleX odkaz je správný. + Chybný odkaz + Verze cílového serveru %1$s je nekompatibilní s nastavením přeposílajícího serveru %2$s. + Zpráva předána + Udržujte konverzaci + Jen smazat konverzaci + Potvrdit smazání kontaktu? + Kontakt bude smazán - nelze vrátit! + Konverzace odstraněna! + Vložit odkaz + Chat databáze exportována + Členu skupiny nelze odeslat zprávu + Požádejte váš kontakt ať povolí volání. + Odeslaných odpovědí + Škálovat + Přizpůsobit + Rozmazání pro lepší soukromí. + Připojte se k vašim přátelům rychleji. + Smazat až 20 zpráv najednou. + Zvětšit velikost písma. + Stav připojení a serverů. + Kontrolujte svou síť + Chyby + Připojen + Připojování + Připojené servery + Dříve připojené servery + Potvrzeno + duplikáty + Smazán + Otevřít nastavení serveru + Nový zážitek z chatu 🎉 + Nové možnosti médií + Nová zpráva + Skenovat / Vložit odkaz + Žádné filtrované kontakty + Chyba inicializace WebView. Ujistěte se, že máte nainstalován WebView podporující architekturu arm64.\nChyba: %s + Chrání vaši IP adresu a připojení. + Zprávy byly odstraněny poté, co jste je vybrali. + Nové přihlašovací údaje SOCKS budou použity pokaždé, když zapnete aplikaci. + Nové přihlašovací údaje SOCKS budou použity pro každý server. + Znovu připojte všechny připojené servery pro vynucení doručení. Využívá další provoz. + Resetovat všechny tipy + %1$d soubor(y) stále stahuji. + Lepší datování zpráv. + Lepší zabezpečení ✅ + Části odstraněny + připojení + Aktuální profil + Chyba znovu připojení serveru + Chyba při opětovném připojování serverů + Znovu připojte server pro vynucení doručení. Využívá další provoz. + Chyba + Zprávy budou smazány - nelze vrátit! + Smazat bez upozornění + hledat + Chyba přepínání profilu + Vyberte chat profil + zpráva + otevřít + Kontakt smazán! + neaktivní + Detaily + Resetovat všechny statistiky + Prosím zkuste později. + Chyba soukromého směrování + Adresa serveru není kompatibilní s nastavením sítě: %1$s. + Člen neaktivní + Zpráva může být doručena později až bude člen aktivní. + Zatím bez přímého spojení, zpráva je předána adminem. + Připojování ke kontaktu, počkejte nebo se podívejte později! + Kontakt odstraněn. + Chyby mazání + Podrobné statistiky + Části staženy + Server + Odesílat zprávy přímo, když je IP adresa chráněna a váš nebo cílový server nepodporuje soukromé směrování. + Odeslat zprávy přímo, když váš nebo cílový server nepodporuje soukromé směrování. + Zkontrolovat aktualizace + Vypnout + Vypnut + Stáhnout %s (%s) + Pozvat + Vytvořit + Roh + Tvar zpráv + Pokračovat + Klikněte na info tlačítko blízko pole adresy, pro použití mikrofonu. + Otevřete nastavení Safari / Webové stránky / mikrofon, vyberte možnost Povolit pro localhost. + Velikost písma + Stáhnout nové verze z GitHubu. + Dosažitelný panel nástrojů chatu + Odebrat archiv? + Soubory + Odeslaných zpráv + Staženo + Znovu připojit server? + chyba dešifrování + Lepší volání + Větší přívětivost + Přizpůsobitelný tvar zpráv. + Smazat nebo moderovat až 200 zpráv. + Předat až 20 zpráv najednou. + Žádné info, zkuste načíst znovu + Informace o serverech + Stažené soubory + Chyby stahování + Chyba resetování statistik + Přijaté zprávy + Přijato celkem + Chyb přijmutí + Připojte znovu všechny servery + Reset + Resetovat všechny statistiky? + Odeslané zprávy + Odeslaných celkem + Adresa serveru + Dosažitelný panel nástrojů chatu + Chyba potvrzení + Připojení + Vytvořen + prošlý + jiné + jiné chyby + Znovu připojit + Chyby odesílání + Odesláno přímo + Odeslaných přes proxy + Odstranit %d zpráv členů? + Zprávy budou označeny pro smazání. Příjemci budou moci tyto zprávy odhalit. + Vybrat + Zpráva + Nic nevybráno + Vybrány %d + Střední + Příjem zpráv + Nastavené XFTP servery + Servery médií a souborů + Servery zpráv + Jiné FXTP servery + Pozvat + Pošlete zprávu pro povolení volání. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index 31d161f3db..5ce86cd374 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -2126,6 +2126,6 @@ Protocolos de SimpleX auditados por Trail of Bits. Intercambia audio y video durante la llamada. Seguridad mejorada ✅ - Borrar o moderar hasta 200 mensajes. + Borra o modera hasta 200 mensajes a la vez. Cambia el perfil de chat para invitaciones de un solo uso. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index 7482b4ea06..5edf8e786f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -219,7 +219,7 @@ Kapcsolódás a számítógéphez Kapcsolat Név helyesbítése erre: %s? - Kapcsolat időtúllépés + Időtúllépés kapcsolódáskor Kapcsolódás %1$s által? Létrehozás Ismerős beállításai @@ -357,7 +357,7 @@ Adatbázis titkosítási jelmondat frissül és eltárolásra kerül a beállításokban. Adatbázis-azonosító Adatbázis-azonosító: %d - Adatbázis-azonosítók és átviteli izolációs beállítások. + Adatbázis-azonosítók és átvitel-izolációs beállítások. Az adatbázis-titkosítási jelmondat megváltoztatásra és mentésre kerül a Keystore-ban. Az adatbázis titkosításra kerül és a jelmondat eltárolásra a beállításokban. Kiszolgáló törlése @@ -677,7 +677,7 @@ mutassa meg a QR-kódot a videohívásban, vagy ossza meg a hivatkozást.]]> Megerősítés esetén az üzenetküldő-kiszolgálók látni fogják az IP-címét és a szolgáltatóját – azt, hogy mely kiszolgálókhoz kapcsolódik. A kép akkor érkezik meg, amikor a küldője befejezte annak feltöltését. - QR kód beolvasásával.]]> + QR-kód beolvasásával.]]> A kapott SimpleX Chat-meghívó-hivatkozását megnyithatja a böngészőjében: Ha az alkalmazás megnyitásakor megadja az önmegsemmisítő jelkódot: Megtalált számítógép @@ -686,7 +686,7 @@ Csevegési profil létrehozása Levélszemét elleni védelem Hordozható eszközök leválasztása - Különböző nevek, avatarok és átviteli izoláció. + Különböző nevek, profilképek és átvitel-izoláció. Elutasítás esetén a feladó NEM kap értesítést. Szerepkörválasztás bővítése A kép akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később! @@ -801,7 +801,7 @@ Több csevegőprofil törlésre jelölve Némítás - Egy hordozható eszköz társítása + Hordozható eszköz társítása Értesítési szolgáltatás Csak a csoporttulajdonosok engedélyezhetik a hangüzenetek küldését. 2 rétegű végpontok közötti titkosítással küldött üzeneteket.]]> @@ -893,13 +893,13 @@ Új csevegés kezdése Bárki üzemeltethet kiszolgálókat. Megnyitás - Protokoll időtúllépés + Protokoll időtúllépése titkos Értesítés előnézete várakozás a visszaigazolásra… Fájl megállítása a csoporthivatkozáson keresztül - PING időköze + Időtartam a PING-ek között Eltűnő üzenet küldése Önmegsemmisítési jelkód Mentés és a csoportprofil frissítése @@ -984,7 +984,7 @@ Számítógép leválasztása? A hangüzenetek le vannak tilva! Közvetlen üzenet küldése a kapcsolódáshoz - PING számláló + PING-ek száma Fejlesztői beállítások megjelenítése %s kapcsolódott Rendszer @@ -1124,7 +1124,7 @@ Társítás számítógéppel PROFIL port %d - Kapcsolódás hivatkozáson keresztül + Kapcsolódás egy hivatkozáson keresztül Cím megosztása A kiszolgáló QR-kódjának beolvasása Megállítás @@ -1279,7 +1279,7 @@ SimpleX Chat-kiszolgálók használata? Csevegési profil felfedése Videók és fájlok 1Gb méretig - TCP kapcsolat időtúllépés + TCP kapcsolat időtúllépése A(z) %1$s nevű profiljának SimpleX-címe megosztásra fog kerülni. Ön már kapcsolódva van ehhez: %1$s. Jelenlegi csevegési adatbázis TÖRLÉSRE és FELCSERÉLÉSRE kerül az importált által! @@ -1368,7 +1368,7 @@ %1$s nevű csoporthoz.]]> Amikor az alkalmazás fut Inkognitóprofilt használ ehhez a csoporthoz - fő profilja megosztásának elkerülése érdekében a meghívók küldése le van tiltva - Kapcsolat izolációs mód + Átvitel-izoláció Akkor lesz kapcsolódva, ha a kapcsolatkérése elfogadásra kerül, várjon, vagy ellenőrizze később! A hangüzenetek küldése le van tiltva ebben a csoportban. Alkalmazás akkumulátor-használata / Korlátlan módot az alkalmazás beállításaiban.]]> @@ -1389,8 +1389,8 @@ Az adatvédelem érdekében, a más csevegési platformokon megszokott felhasználó-azonosítók helyett, a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, minden egyes ismerőshöz egy-egy különbözőt. (a megosztáshoz az ismerősével) Csoportmeghívó elküldve - Kapcsolat izolációs mód frissítése? - Kapcsolat izolációs mód + Átvitel-izoláció módjának frissítése? + Átvitel-izoláció Ettől a csoporttól nem fog értesítéseket kapni. A csevegési előzmények megmaradnak. A csevegési adatbázis nem titkosított - állítson be egy jelmondatot annak védelméhez. Közvetlen internet kapcsolat használata? @@ -1438,7 +1438,7 @@ Megjelenítendő üzenet beállítása az új tagok számára! Köszönet a felhasználóknak - hozzájárulás a Weblate-en! A kézbesítési jelentés küldése minden ismerőse számára engedélyezésre kerül. - Protokoll időkorlát KB-onként + Protokoll időtúllépése KB-onként Az adatbázis-jelmondat megváltoztatására tett kísérlet nem fejeződött be. Ez a művelet nem vonható vissza - a kiválasztottnál korábban küldött és fogadott üzenetek törlésre kerülnek. Ez több percet is igénybe vehet. A profilja csak az ismerőseivel kerül megosztásra. @@ -1768,7 +1768,7 @@ Soha Ismeretlen kiszolgálók Ha az IP-cím rejtett - Üzenet állapot megjelenítése + Üzenetállapot megjelenítése Visszafejlesztés engedélyezése Mindig Nem @@ -1844,8 +1844,8 @@ \nutoljára kézbesített üzenet: %2$s Hibás kulcs vagy ismeretlen fájltöredék cím - valószínűleg a fájl törlődött. Ideiglenesfájl-hiba - Üzenet állapota - Üzenet állapota: %s + Üzenetállapot + Üzenetállapot: %s Fájlhiba A fájl nem található - valószínűleg a fájlt törölték vagy visszavonták. Fájlkiszolgáló-hiba: %1$s @@ -1890,12 +1890,12 @@ Letöltve lejárt egyéb - Összes fogadott + Összes fogadott üzenet Üzenetfogadási hibák Újrakapcsolás Üzenetküldési hibák Közvetlenül küldött - Összes elküldött + Összes elküldött üzenet Proxyn keresztül küldve SMP-kiszolgáló Statisztikagyűjtés kezdete: %s. @@ -1926,7 +1926,7 @@ Konfigurált XFTP-kiszolgálók Kapcsolódva Jelenlegi profil - Részletek + További részletek visszafejtési hibák Törölve Fogadott üzenetek @@ -1943,7 +1943,7 @@ A kiszolgálóhoz való újrakapcsolódás az üzenetkézbesítési jelentések kikényszerítéséhez. Ez további adatforgalmat használ. Elküldött üzenetek Munkamenetek átvitele - Összesen + Összes kapcsolat Statisztikák Információk megjelenítése ehhez: A kiszolgáló verziója nem kompatibilis az alkalmazással: %1$s. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index 5b603bd84d..22112a8376 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -2120,7 +2120,7 @@ Betere datums voor berichten. Betere gebruikerservaring Aanpasbare berichtvorm. - Wissel tussen audio en video tijdens het gesprek. + Wisselen tussen audio en video tijdens het gesprek. Wijzig chatprofiel voor eenmalige uitnodigingen. Maximaal 200 berichten verwijderen of modereren. Stuur maximaal 20 berichten tegelijk door. From 601b4cd619fe577ec11b1cb87cbb6f913a0dacca Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 12 Oct 2024 12:08:16 +0100 Subject: [PATCH 031/567] 6.1: ios 244, android 247, desktop 73 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 60 +++++++++++----------- apps/multiplatform/gradle.properties | 8 +-- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 56b09206b5..2903388fb9 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -223,11 +223,11 @@ E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; E5DCF9982C5906FF007928CC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9962C5906FF007928CC /* InfoPlist.strings */; }; - E5E997BF2CB9867B00D7A2FA /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997BA2CB9867B00D7A2FA /* libgmpxx.a */; }; - E5E997C02CB9867B00D7A2FA /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997BB2CB9867B00D7A2FA /* libffi.a */; }; - E5E997C12CB9867B00D7A2FA /* libHSsimplex-chat-6.1.0.8-CCpM4Xk6yH6LfY0I6dYuyA-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997BC2CB9867B00D7A2FA /* libHSsimplex-chat-6.1.0.8-CCpM4Xk6yH6LfY0I6dYuyA-ghc9.6.3.a */; }; - E5E997C22CB9867B00D7A2FA /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997BD2CB9867B00D7A2FA /* libgmp.a */; }; - E5E997C32CB9867B00D7A2FA /* libHSsimplex-chat-6.1.0.8-CCpM4Xk6yH6LfY0I6dYuyA.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997BE2CB9867B00D7A2FA /* libHSsimplex-chat-6.1.0.8-CCpM4Xk6yH6LfY0I6dYuyA.a */; }; + E5E997C92CBA891A00D7A2FA /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997C42CBA891A00D7A2FA /* libgmpxx.a */; }; + E5E997CA2CBA891A00D7A2FA /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997C52CBA891A00D7A2FA /* libgmp.a */; }; + E5E997CB2CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997C62CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv-ghc9.6.3.a */; }; + E5E997CC2CBA891A00D7A2FA /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997C72CBA891A00D7A2FA /* libffi.a */; }; + E5E997CD2CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997C82CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -612,11 +612,11 @@ E5DCF9A62C590731007928CC /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/InfoPlist.strings; sourceTree = ""; }; E5DCF9A72C590732007928CC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoPlist.strings; sourceTree = ""; }; E5DCF9A82C590732007928CC /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = ""; }; - E5E997BA2CB9867B00D7A2FA /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E5E997BB2CB9867B00D7A2FA /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - E5E997BC2CB9867B00D7A2FA /* libHSsimplex-chat-6.1.0.8-CCpM4Xk6yH6LfY0I6dYuyA-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.8-CCpM4Xk6yH6LfY0I6dYuyA-ghc9.6.3.a"; sourceTree = ""; }; - E5E997BD2CB9867B00D7A2FA /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - E5E997BE2CB9867B00D7A2FA /* libHSsimplex-chat-6.1.0.8-CCpM4Xk6yH6LfY0I6dYuyA.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.8-CCpM4Xk6yH6LfY0I6dYuyA.a"; sourceTree = ""; }; + E5E997C42CBA891A00D7A2FA /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + E5E997C52CBA891A00D7A2FA /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + E5E997C62CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv-ghc9.6.3.a"; sourceTree = ""; }; + E5E997C72CBA891A00D7A2FA /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E5E997C82CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv.a"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -655,14 +655,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E5E997C12CB9867B00D7A2FA /* libHSsimplex-chat-6.1.0.8-CCpM4Xk6yH6LfY0I6dYuyA-ghc9.6.3.a in Frameworks */, - E5E997C22CB9867B00D7A2FA /* libgmp.a in Frameworks */, - E5E997C32CB9867B00D7A2FA /* libHSsimplex-chat-6.1.0.8-CCpM4Xk6yH6LfY0I6dYuyA.a in Frameworks */, - E5E997BF2CB9867B00D7A2FA /* libgmpxx.a in Frameworks */, + E5E997C92CBA891A00D7A2FA /* libgmpxx.a in Frameworks */, + E5E997CB2CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv-ghc9.6.3.a in Frameworks */, + E5E997CA2CBA891A00D7A2FA /* libgmp.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, + E5E997CC2CBA891A00D7A2FA /* libffi.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - E5E997C02CB9867B00D7A2FA /* libffi.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, + E5E997CD2CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -739,11 +739,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E5E997BB2CB9867B00D7A2FA /* libffi.a */, - E5E997BD2CB9867B00D7A2FA /* libgmp.a */, - E5E997BA2CB9867B00D7A2FA /* libgmpxx.a */, - E5E997BC2CB9867B00D7A2FA /* libHSsimplex-chat-6.1.0.8-CCpM4Xk6yH6LfY0I6dYuyA-ghc9.6.3.a */, - E5E997BE2CB9867B00D7A2FA /* libHSsimplex-chat-6.1.0.8-CCpM4Xk6yH6LfY0I6dYuyA.a */, + E5E997C72CBA891A00D7A2FA /* libffi.a */, + E5E997C52CBA891A00D7A2FA /* libgmp.a */, + E5E997C42CBA891A00D7A2FA /* libgmpxx.a */, + E5E997C62CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv-ghc9.6.3.a */, + E5E997C82CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv.a */, ); path = Libraries; sourceTree = ""; @@ -1899,7 +1899,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 243; + CURRENT_PROJECT_VERSION = 244; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1948,7 +1948,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 243; + CURRENT_PROJECT_VERSION = 244; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1989,7 +1989,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 243; + CURRENT_PROJECT_VERSION = 244; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2009,7 +2009,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 243; + CURRENT_PROJECT_VERSION = 244; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2034,7 +2034,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 243; + CURRENT_PROJECT_VERSION = 244; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2071,7 +2071,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 243; + CURRENT_PROJECT_VERSION = 244; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2108,7 +2108,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 243; + CURRENT_PROJECT_VERSION = 244; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2159,7 +2159,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 243; + CURRENT_PROJECT_VERSION = 244; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2210,7 +2210,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 243; + CURRENT_PROJECT_VERSION = 244; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2244,7 +2244,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 243; + CURRENT_PROJECT_VERSION = 244; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index b939e0ba55..907b0a94ae 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -26,11 +26,11 @@ android.enableJetifier=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.1-beta.5 -android.version_code=246 +android.version_name=6.1 +android.version_code=247 -desktop.version_name=6.1-beta.5 -desktop.version_code=72 +desktop.version_name=6.1 +desktop.version_code=73 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From 13912a4af950409f9a137ba9987a50512a041e1a Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Sun, 13 Oct 2024 08:43:47 +0000 Subject: [PATCH 032/567] docs/smp-server: update to latest changes (#4960) * docs/smp-server: update to latest changes * update --------- Co-authored-by: Evgeny Poberezkin --- docs/SERVER.md | 476 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 414 insertions(+), 62 deletions(-) diff --git a/docs/SERVER.md b/docs/SERVER.md index c2cb486375..ce6c466573 100644 --- a/docs/SERVER.md +++ b/docs/SERVER.md @@ -1,13 +1,16 @@ --- title: Hosting your own SMP Server -revision: 03.06.2024 +revision: 12.10.2024 --- -| Updated 28.05.2024 | Languages: EN, [FR](/docs/lang/fr/SERVER.md), [CZ](/docs/lang/cs/SERVER.md), [PL](/docs/lang/pl/SERVER.md) | +# Hosting your own SMP Server + +| Updated 12.10.2024 | Languages: EN, [FR](/docs/lang/fr/SERVER.md), [CZ](/docs/lang/cs/SERVER.md), [PL](/docs/lang/pl/SERVER.md) | ### Table of Contents -- [Hosting your own SMP server](#hosting-your-own-smp-server) +- [Quick start](#quick-start) +- [Detailed guide](#detailed-guide) - [Overview](#overview) - [Installation](#installation) - [Configuration](#configuration) @@ -29,17 +32,218 @@ revision: 03.06.2024 - [Updating your SMP server](#updating-your-smp-server) - [Configuring the app to use the server](#configuring-the-app-to-use-the-server) -# Hosting your own SMP Server +## Quick start -## Overview +To create SMP server, you'll need: + +- VPS or any other server. +- Your server domain, with A and AAAA records specifying server IPv4 and IPv6 addresses (`smp1.example.com`) +- A basic Linux knowledge. + +*Please note*: while you can run an SMP server without a domain name, in the near future client applications will start using server domain name in the invitation links (instead of `simplex.chat` domain they use now). In case a server does not have domain name and server pages (see below), the clients will be generaing the links with `simplex:` scheme that cannot be opened in the browsers. + +1. Install server with [Installation script](https://github.com/simplex-chat/simplexmq#using-installation-script). + +2. Adjust firewall: + + ```sh + ufw allow 80/tcp &&\ + ufw allow 443/tcp &&\ + ufw allow 5223/tcp + ``` + +3. Init server: + + Replace `smp1.example.com` with your actual server domain. + + ```sh + su smp -c 'smp-server init --yes \ + --store-log \ + --no-password \ + --control-port \ + --socks-proxy \ + --source-code \ + --fqdn=smp1.example.com + ``` + +4. Install tor: + + ```sh + CODENAME="$(lsb_release -c | awk '{print $2}')" + + echo "deb [signed-by=/usr/share/keyrings/tor-archive-keyring.gpg] https://deb.torproject.org/torproject.org ${CODENAME} main + deb-src [signed-by=/usr/share/keyrings/tor-archive-keyring.gpg] https://deb.torproject.org/torproject.org ${CODENAME} main" > /etc/apt/sources.list.d/tor.list &&\ + curl --proto '=https' --tlsv1.2 -sSf https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc | gpg --dearmor | tee /usr/share/keyrings/tor-archive-keyring.gpg >/dev/null &&\ + apt update && apt install -y tor deb.torproject.org-keyring + ``` + +5. Configure tor: + + ```sh + tor-instance-create tor2 &&\ + mkdir /var/lib/tor/simplex-smp/ &&\ + chown debian-tor:debian-tor /var/lib/tor/simplex-smp/ &&\ + chmod 700 /var/lib/tor/simplex-smp/ + ``` + + ```sh + vim /etc/tor/torrc + ``` + + Paste the following: + + ```sh + # Enable log (otherwise, tor doesn't seem to deploy onion address) + Log notice file /var/log/tor/notices.log + # Enable single hop routing (2 options below are dependencies of the third) - It will reduce the latency at the cost of lower anonimity of the server - as SMP-server onion address is used in the clients together with public address, this is ok. If you deploy SMP-server with onion-only address, keep standard configuration. + SOCKSPort 0 + HiddenServiceNonAnonymousMode 1 + HiddenServiceSingleHopMode 1 + # smp-server hidden service host directory and port mappings + HiddenServiceDir /var/lib/tor/simplex-smp/ + HiddenServicePort 5223 localhost:5223 + HiddenServicePort 443 localhost:443 + ``` + + ```sh + vim /etc/tor/instances/tor2/torrc + ``` + + Paste the following: + + ```sh + # Log tor to systemd daemon + Log notice syslog + # Listen to local 9050 port for socks proxy + SocksPort 9050 + ``` + +6. Start tor: + + ```sh + systemctl enable tor &&\ + systemctl start tor &&\ + systemctl restart tor &&\ + systemctl enable --now tor@tor2 + ``` + +7. Install Caddy: + + ```sh + sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl &&\ + curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg &&\ + curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list &&\ + sudo apt update && sudo apt install caddy + ``` + +8. Configure Caddy: + + ```sh + vim /etc/caddy/Caddyfile + ``` + + Replace `smp1.example.com` with your actual server domain. Paste the following: + + ``` + http://smp1.example.com { + redir https://smp1.example.com{uri} permanent + } + + smp1.example.com:8443 { + tls { + key_type rsa4096 + } + } + ``` + + ```sh + vim /usr/local/bin/simplex-servers-certs + ``` + + Replace `smp1.example.com` with your actual server domain. Paste the following: + + ```sh + #!/usr/bin/env sh + set -eu + + user='smp' + group="$user" + + domain='smp1.example.com' + folder_in="/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/${domain}" + folder_out='/etc/opt/simplex' + key_name='web.key' + cert_name='web.crt' + + # Copy certifiacte from Caddy directory to smp-server directory + cp "${folder_in}/${domain}.crt" "${folder_out}/${cert_name}" + # Assign correct permissions + chown "$user":"$group" "${folder_out}/${cert_name}" + + # Copy certifiacte key from Caddy directory to smp-server directory + cp "${folder_in}/${domain}.key" "${folder_out}/${key_name}" + # Assign correct permissions + chown "$user":"$group" "${folder_out}/${key_name}" + ``` + + ```sh + chmod +x /usr/local/bin/simplex-servers-certs + ``` + + ```sh + sudo crontab -e + ``` + + Paste the following: + + ```sh + # Every week on 00:20 sunday + 20 0 * * 0 /usr/local/bin/simplex-servers-certs + ``` + +9. Enable and start Caddy service: + + Wait until "good to go" has been printed. + + ```sh + systemctl enable --now caddy &&\ + sleep 10 &&\ + /usr/local/bin/simplex-servers-certs &&\ + echo 'good to go' + ``` + +10. Enable and start smp-server: + + ```sh + systemctl enable --now smp-server.service + ``` + +11. Print your address: + + ```sh + smp="$(journalctl --output cat -q _SYSTEMD_INVOCATION_ID="$(systemctl show -p InvocationID --value smp-server)" | grep -m1 'Server address:' | awk '{print $NF}' | sed 's/:443.*//')" + tor="$(cat /var/lib/tor/simplex-smp/hostname)" + + echo "$smp,$tor" + ``` + +## Detailed guide + +### Overview SMP server is the relay server used to pass messages in SimpleX network. SimpleX Chat apps have preset servers (for mobile apps these are smp11, smp12 and smp14.simplex.im), but you can easily change app configuration to use other servers. SimpleX clients only determine which server is used to receive the messages, separately for each contact (or group connection with a group member), and these servers are only temporary, as the delivery address can change. +To create SMP server, you'll need: + +1. VPS or any other server. +2. Your own domain, pointed at the server (`smp.example.com`) +3. A basic Linux knowledge. + _Please note_: when you change the servers in the app configuration, it only affects which servers will be used for the new contacts, the existing contacts will not automatically move to the new servers, but you can move them manually using ["Change receiving address"](../blog/20221108-simplex-chat-v4.2-security-audit-new-website.md#change-your-delivery-address-beta) button in contact/member information pages – it will be automated in the future. -## Installation +### Installation 1. First, install `smp-server`: @@ -82,8 +286,10 @@ Manual installation requires some preliminary actions: ```sh # For Ubuntu sudo ufw allow 5223/tcp + sudo ufw allow 443/tcp + sudo ufw allow 80/tcp # For Fedora - sudo firewall-cmd --permanent --add-port=5223/tcp && \ + sudo firewall-cmd --permanent --add-port=5223/tcp --add-port=443/tcp --add-port=80/tcp && \ sudo firewall-cmd --reload ``` @@ -102,6 +308,7 @@ Manual installation requires some preliminary actions: LimitNOFILE=65535 KillSignal=SIGINT TimeoutStopSec=infinity + AmbientCapabilities=CAP_NET_BIND_SERVICE [Install] WantedBy=multi-user.target @@ -109,7 +316,7 @@ Manual installation requires some preliminary actions: And execute `sudo systemctl daemon-reload`. -## Configuration +### Configuration To see which options are available, execute `smp-server` without flags: @@ -129,7 +336,7 @@ You can get further help by executing `sudo su smp -c "smp-server -h"` After that, we need to configure `smp-server`: -### Interactively +#### Interactively Execute the following command: @@ -159,7 +366,7 @@ These statistics include daily counts of created, secured and deleted queues, se Enter your domain or ip address that your smp-server is running on - it will be included in server certificates and also printed as part of server address. -### Via command line options +#### Via command line options Execute the following command: @@ -223,7 +430,7 @@ Server address: smp://d5fcsc7hhtPpexYUbI2XPxDbyU2d3WsVmROimcL90ss=:V8ONoJ6ICwnrZ The server address above should be used in your client configuration, and if you added server password it should only be shared with the other people who you want to allow using your server to receive the messages (all your contacts will be able to send messages - it does not require a password). If you passed IP address or hostnames during the initialisation, they will be printed as part of server address, otherwise replace `` with the actual server hostnames. -## Further configuration +### Further configuration All generated configuration, along with a description for each parameter, is available inside configuration file in `/etc/opt/simplex/smp-server.ini` for further customization. Depending on the smp-server version, the configuration file looks something like this: @@ -245,26 +452,26 @@ source_code: https://github.com/simplex-chat/simplexmq # condition_amendments: link # Server location and operator. -server_country: -operator: -operator_country: -website: +# server_country: ISO-3166 2-letter code +# operator: entity (organization or person name) +# operator_country: ISO-3166 2-letter code +# website: # Administrative contacts. -#admin_simplex: SimpleX address -admin_email: +# admin_simplex: SimpleX address +# admin_email: # admin_pgp: # admin_pgp_fingerprint: # Contacts for complaints and feedback. # complaints_simplex: SimpleX address -complaints_email: +# complaints_email: # complaints_pgp: # complaints_pgp_fingerprint: # Hosting provider. -hosting: -hosting_country: +# hosting: entity (organization or person name) +# hosting_country: ISO-3166 2-letter code [STORE_LOG] # The server uses STM memory for persistence, @@ -278,6 +485,7 @@ enable: on # they are preserved in the .bak file until the next restart. restore_messages: on expire_messages_days: 21 +expire_ntfs_hours: 24 # Log daily server statistics to CSV file log_stats: on @@ -294,11 +502,17 @@ new_queues: on # with the users who you want to allow creating messaging queues on your server. # create_password: password to create new queues (any printable ASCII characters without whitespace, '@', ':' and '/') +# control_port_admin_password: +# control_port_user_password: + [TRANSPORT] -# host is only used to print server address on start -host: -port: 5223 +# Host is only used to print server address on start. +# You can specify multiple server ports. +host: +port: 5223,443 log_tls_errors: off + +# Use `websockets: 443` to run websockets server in addition to plain TLS. websockets: off # control_port: 5224 @@ -310,7 +524,7 @@ websockets: off # required_host_mode: off # The domain suffixes of the relays you operate (space-separated) to count as separate proxy statistics. -# own_server_domains: +# own_server_domains: # SOCKS proxy port for forwarding messages to destination servers. # You may need a separate instance of SOCKS proxy for incoming single-hop requests. @@ -326,7 +540,7 @@ websockets: off [INACTIVE_CLIENTS] # TTL and interval to check inactive clients disconnect: off -# ttl: 43200 +# ttl: 21600 # check_interval: 3600 [WEB] @@ -336,18 +550,18 @@ static_path: /var/opt/simplex/www # Run an embedded server on this port # Onion sites can use any port and register it in the hidden service config. # Running on a port 80 may require setting process capabilities. -# http: 8000 +#http: 8000 # You can run an embedded TLS web server too if you provide port and cert and key files. # Not required for running relay on onion address. -# https: 443 -# cert: /etc/opt/simplex/web.cert -# key: /etc/opt/simplex/web.key +https: 443 +cert: /etc/opt/simplex/web.crt +key: /etc/opt/simplex/web.key ``` -## Server security +### Server security -### Initialization +#### Initialization Although it's convenient to initialize smp-server configuration directly on the server, operators **ARE ADVISED** to initialize smp-server fully offline to protect your SMP server CA private key. @@ -367,7 +581,7 @@ Follow the steps to quickly initialize the server offline: rsync -hzasP $HOME/simplex/smp/config/ @:/etc/opt/simplex/ ``` -### Private keys +#### Private keys Connection to the smp server occurs via a TLS connection. During the TLS handshake, the client verifies smp-server CA and server certificates by comparing its fingerprint with the one included in server address. If server TLS credential is compromised, this key can be used to sign a new one, keeping the same server identity and established connections. In order to protect your smp-server from bad actors, operators **ARE ADVISED** to move CA private key to a safe place. That could be: @@ -392,7 +606,7 @@ Follow the steps to secure your CA keys: rm /etc/opt/simplex/ca.key ``` -### Online certificate rotation +#### Online certificate rotation Operators of smp servers **ARE ADVISED** to rotate online certificate regularly (e.g., every 3 months). In order to do this, follow the steps: @@ -468,9 +682,9 @@ Operators of smp servers **ARE ADVISED** to rotate online certificate regularly 10. Done! -## Tor: installation and configuration +### Tor: installation and configuration -### Installation for onion address +#### Installation for onion address SMP-server can also be deployed to be available via [Tor](https://www.torproject.org) network. Run the following commands as `root` user. @@ -526,6 +740,7 @@ SMP-server can also be deployed to be available via [Tor](https://www.torproject # smp-server hidden service host directory and port mappings HiddenServiceDir /var/lib/tor/simplex-smp/ HiddenServicePort 5223 localhost:5223 + HiddenServicePort 443 localhost:443 ``` - Create directories: @@ -550,7 +765,7 @@ SMP-server can also be deployed to be available via [Tor](https://www.torproject cat /var/lib/tor/simplex-smp/hostname ``` -### SOCKS port for SMP PROXY +#### SOCKS port for SMP PROXY SMP-server versions starting from `v5.8.0-beta.0` can be configured to PROXY smp servers available exclusively through [Tor](https://www.torproject.org) network to be accessible to the clients that do not use Tor. Run the following commands as `root` user. @@ -597,9 +812,11 @@ SMP-server versions starting from `v5.8.0-beta.0` can be configured to PROXY smp ... ``` -## Server information page +### Server information page -SMP-server versions starting from `v5.8.0` can be configured to serve Web page with server information that can include admin info, server info, provider info, etc. Run the following commands as `root` user. +SMP server **SHOULD** be configured to serve Web page with server information that can include admin info, server info, provider info, etc. It will also serve connection links, generated using the mobile/desktop apps. Run the following commands as `root` user. + +_Please note:_ this configuration is supported since `v6.1.0-beta.2`. 1. Add the following to your smp-server configuration (please modify fields in [INFORMATION] section to include relevant information): @@ -608,8 +825,19 @@ SMP-server versions starting from `v5.8.0` can be configured to serve Web page w ``` ```ini + [TRANSPORT] + # host is only used to print server address on start + host: + port: 443,5223 + websockets: off + log_tls_errors: off + control_port: 5224 + [WEB] + https: 443 static_path: /var/opt/simplex/www + cert: /etc/opt/simplex/web.crt + key: /etc/opt/simplex/web.key [INFORMATION] # AGPLv3 license requires that you make any source code modifications @@ -678,16 +906,23 @@ SMP-server versions starting from `v5.8.0` can be configured to serve Web page w [Full Caddy instllation instructions](https://caddyserver.com/docs/install) -3. Replace Caddy configuration with the following (don't forget to replace ``): +3. Replace Caddy configuration with the following: + + Please replace `YOUR_DOMAIN` with your actual domain (smp.example.com). ```sh vim /etc/caddy/Caddyfile ``` - ```caddy - { - root * /var/opt/simplex/www - file_server + ``` + http://YOUR_DOMAIN { + redir https://YOUR_DOMAIN{uri} permanent + } + + YOUR_DOMAIN:8443 { + tls { + key_type rsa4096 + } } ``` @@ -697,17 +932,75 @@ SMP-server versions starting from `v5.8.0` can be configured to serve Web page w systemctl enable --now caddy ``` -5. Upgrade your smp-server to latest version - [Updating your smp server](#updating-your-smp-server) +5. Create script to copy certificates to your smp directory: -6. Access the webpage you've deployed from your browser. You should see the smp-server information that you've provided in your ini file. + Please replace `YOUR_DOMAIN` with your actual domain (smp.example.com). -## Documentation + ```sh + vim /usr/local/bin/simplex-servers-certs + ``` + + ```sh + #!/usr/bin/env sh + set -eu + + user='smp' + group="$user" + + domain='HOST' + folder_in="/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/${domain}" + folder_out='/etc/opt/simplex' + key_name='web.key' + cert_name='web.crt' + + # Copy certifiacte from Caddy directory to smp-server directory + cp "${folder_in}/${domain}.crt" "${folder_out}/${cert_name}" + # Assign correct permissions + chown "$user":"$group" "${folder_out}/${cert_name}" + + # Copy certifiacte key from Caddy directory to smp-server directory + cp "${folder_in}/${domain}.key" "${folder_out}/${key_name}" + # Assign correct permissions + chown "$user":"$group" "${folder_out}/${key_name}" + ``` + +6. Make the script executable and execute it: + + ```sh + chmod +x /usr/local/bin/simplex-servers-certs && /usr/local/bin/simplex-servers-certs + ``` + +7. Check if certificates were copied: + + ```sh + ls -haltr /etc/opt/simplex/web* + ``` + +8. Create cronjob to copy certificates to smp directory in timely manner: + + ```sh + sudo crontab -e + ``` + + ```sh + # Every week on 00:20 sunday + 20 0 * * 0 /usr/local/bin/simplex-servers-certs + ``` + +9. Then: + + - If you're running at least `v6.1.0-beta.2`, [restart the server](#systemd-commands). + - If you're running below `v6.1.0-beta.2`, [upgrade the server](#updating-your-smp-server). + +10. Access the webpage you've deployed from your browser (`https://smp.example.org`). You should see the smp-server information that you've provided in your ini file. + +### Documentation All necessary files for `smp-server` are located in `/etc/opt/simplex/` folder. Stored messages, connections, statistics and server log are located in `/var/opt/simplex/` folder. -### SMP server address +#### SMP server address SMP server address has the following format: @@ -727,7 +1020,7 @@ smp://[:]@[,] Your configured hostname(s) of `smp-server`. You can check your configured hosts in `/etc/opt/simplex/smp-server.ini`, under `[TRANSPORT]` section in `host:` field. -### Systemd commands +#### Systemd commands To start `smp-server` on host boot, run: @@ -786,16 +1079,18 @@ Nov 23 19:23:21 5588ab759e80 smp-server[30878]: not expiring inactive clients Nov 23 19:23:21 5588ab759e80 smp-server[30878]: creating new queues requires password ``` -### Monitoring +#### Monitoring You can enable `smp-server` statistics for `Grafana` dashboard by setting value `on` in `/etc/opt/simplex/smp-server.ini`, under `[STORE_LOG]` section in `log_stats:` field. Logs will be stored as `csv` file in `/var/opt/simplex/smp-server-stats.daily.log`. Fields for the `csv` file are: ```sh -fromTime,qCreated,qSecured,qDeleted,msgSent,msgRecv,dayMsgQueues,weekMsgQueues,monthMsgQueues,msgSentNtf,msgRecvNtf,dayCountNtf,weekCountNtf,monthCountNtf,qCount,msgCount,msgExpired,qDeletedNew,qDeletedSecured,pRelays_pRequests,pRelays_pSuccesses,pRelays_pErrorsConnect,pRelays_pErrorsCompat,pRelays_pErrorsOther,pRelaysOwn_pRequests,pRelaysOwn_pSuccesses,pRelaysOwn_pErrorsConnect,pRelaysOwn_pErrorsCompat,pRelaysOwn_pErrorsOther,pMsgFwds_pRequests,pMsgFwds_pSuccesses,pMsgFwds_pErrorsConnect,pMsgFwds_pErrorsCompat,pMsgFwds_pErrorsOther,pMsgFwdsOwn_pRequests,pMsgFwdsOwn_pSuccesses,pMsgFwdsOwn_pErrorsConnect,pMsgFwdsOwn_pErrorsCompat,pMsgFwdsOwn_pErrorsOther,pMsgFwdsRecv,qSub,qSubAuth,qSubDuplicate,qSubProhibited,msgSentAuth,msgSentQuota,msgSentLarge +fromTime,qCreated,qSecured,qDeleted,msgSent,msgRecv,dayMsgQueues,weekMsgQueues,monthMsgQueues,msgSentNtf,msgRecvNtf,dayCountNtf,weekCountNtf,monthCountNtf,qCount,msgCount,msgExpired,qDeletedNew,qDeletedSecured,pRelays_pRequests,pRelays_pSuccesses,pRelays_pErrorsConnect,pRelays_pErrorsCompat,pRelays_pErrorsOther,pRelaysOwn_pRequests,pRelaysOwn_pSuccesses,pRelaysOwn_pErrorsConnect,pRelaysOwn_pErrorsCompat,pRelaysOwn_pErrorsOther,pMsgFwds_pRequests,pMsgFwds_pSuccesses,pMsgFwds_pErrorsConnect,pMsgFwds_pErrorsCompat,pMsgFwds_pErrorsOther,pMsgFwdsOwn_pRequests,pMsgFwdsOwn_pSuccesses,pMsgFwdsOwn_pErrorsConnect,pMsgFwdsOwn_pErrorsCompat,pMsgFwdsOwn_pErrorsOther,pMsgFwdsRecv,qSub,qSubAuth,qSubDuplicate,qSubProhibited,msgSentAuth,msgSentQuota,msgSentLarge,msgNtfs,msgNtfNoSub,msgNtfLost,qSubNoMsg,msgRecvGet,msgGet,msgGetNoMsg,msgGetAuth,msgGetDuplicate,msgGetProhibited,psSubDaily,psSubWeekly,psSubMonthly,qCount2,ntfCreated,ntfDeleted,ntfSub,ntfSubAuth,ntfSubDuplicate,ntfCount,qDeletedAllB,qSubAllB,qSubEnd,qSubEndB,ntfDeletedB,ntfSubB,msgNtfsB,msgNtfExpired ``` +#### Fields description + | Field number | Field name | Field Description | | ------------- | ---------------------------- | -------------------------- | | 1 | `fromTime` | Date of statistics | @@ -856,6 +1151,34 @@ fromTime,qCreated,qSecured,qDeleted,msgSent,msgRecv,dayMsgQueues,weekMsgQueues,m | 45 | `msgSentAuth` | Authentication errors | | 46 | `msgSentQuota` | Quota errors | | 47 | `msgSentLarge` | Large message errors | +| 48 | `msgNtfs` | XXXXXXXXXXXXXXXXXXXX | +| 49 | `msgNtfNoSub` | XXXXXXXXXXXXXXXXXXXX | +| 50 | `msgNtfLost` | XXXXXXXXXXXXXXXXXXXX | +| 51 | `qSubNoMsg` | Removed, always 0 | +| 52 | `msgRecvGet` | XXXXXXXXXXXXXXXXX | +| 53 | `msgGet` | XXXXXXXXXXXXXXXXX | +| 54 | `msgGetNoMsg` | XXXXXXXXXXXXXXXXX | +| 55 | `msgGetAuth` | XXXXXXXXXXXXXXXXX | +| 56 | `msgGetDuplicate` | XXXXXXXXXXXXXXXXX | +| 57 | `msgGetProhibited` | XXXXXXXXXXXXXXXXX | +| 58 | `psSub_dayCount` | Removed, always 0 | +| 59 | `psSub_weekCount` | Removed, always 0 | +| 60 | `psSub_monthCount` | Removed, always 0 | +| 61 | `qCount` | XXXXXXXXXXXXXXXXX | +| 62 | `ntfCreated` | XXXXXXXXXXXXXXXXX | +| 63 | `ntfDeleted` | XXXXXXXXXXXXXXXXX | +| 64 | `ntfSub` | XXXXXXXXXXXXXXXXX | +| 65 | `ntfSubAuth` | XXXXXXXXXXXXXXXXX | +| 66 | `ntfSubDuplicate` | XXXXXXXXXXXXXXXXX | +| 67 | `ntfCount` | XXXXXXXXXXXXXXXXX | +| 68 | `qDeletedAllB` | XXXXXXXXXXXXXXXXX | +| 69 | `qSubAllB` | XXXXXXXXXXXXXXXXX | +| 70 | `qSubEnd` | XXXXXXXXXXXXXXXXX | +| 71 | `qSubEndB` | XXXXXXXXXXXXXXXXX | +| 72 | `ntfDeletedB` | XXXXXXXXXXXXXXXXX | +| 73 | `ntfSubB` | XXXXXXXXXXXXXXXXX | +| 74 | `msgNtfsB` | XXXXXXXXXXXXXXXXX | +| 75 | `msgNtfExpired` | XXXXXXXXXXXXXXXXX | To import `csv` to `Grafana` one should: @@ -863,83 +1186,112 @@ To import `csv` to `Grafana` one should: 2. Allow local mode by appending following: - ```sh - [plugin.marcusolsson-csv-datasource] - allow_local_mode = true - ``` + ```sh + [plugin.marcusolsson-csv-datasource] + allow_local_mode = true + ``` - ... to `/etc/grafana/grafana.ini` + ... to `/etc/grafana/grafana.ini` 3. Add a CSV data source: - - In the side menu, click the Configuration tab (cog icon) - - Click Add data source in the top-right corner of the Data Sources tab - - Enter "CSV" in the search box to find the CSV data source - - Click the search result that says "CSV" - - In URL, enter a file that points to CSV content + - In the side menu, click the Configuration tab (cog icon) + - Click Add data source in the top-right corner of the Data Sources tab + - Enter "CSV" in the search box to find the CSV data source + - Click the search result that says "CSV" + - In URL, enter a file that points to CSV content 4. You're done! You should be able to create your own dashboard with statistics. For further documentation, see: [CSV Data Source for Grafana - Documentation](https://grafana.github.io/grafana-csv-datasource/) -## Updating your SMP server +### Updating your SMP server To update your smp-server to latest version, choose your installation method and follow the steps: - Manual deployment + 1. Stop the server: + ```sh sudo systemctl stop smp-server ``` + 2. Update the binary: + ```sh curl -L https://github.com/simplex-chat/simplexmq/releases/latest/download/smp-server-ubuntu-20_04-x86-64 -o /usr/local/bin/smp-server && chmod +x /usr/local/bin/smp-server ``` + 3. Start the server: + ```sh sudo systemctl start smp-server ``` - [Offical installation script](https://github.com/simplex-chat/simplexmq#using-installation-script) + 1. Execute the followin command: + ```sh sudo simplex-servers-update ``` + + To install specific version, run: + + ```sh + export VER= &&\ + sudo -E simplex-servers-update + ``` + 2. Done! - [Docker container](https://github.com/simplex-chat/simplexmq#using-docker) + 1. Stop and remove the container: + ```sh docker rm $(docker stop $(docker ps -a -q --filter ancestor=simplexchat/smp-server --format="\{\{.ID\}\}")) ``` + 2. Pull latest image: + ```sh docker pull simplexchat/smp-server:latest ``` + 3. Start new container: + ```sh docker run -d \ -p 5223:5223 \ + -p 443:443 \ -v $HOME/simplex/smp/config:/etc/opt/simplex:z \ -v $HOME/simplex/smp/logs:/var/opt/simplex:z \ simplexchat/smp-server:latest ``` - [Linode Marketplace](https://www.linode.com/marketplace/apps/simplex-chat/simplex-chat/) + 1. Pull latest images: + ```sh docker-compose --project-directory /etc/docker/compose/simplex pull ``` + 2. Restart the containers: + ```sh docker-compose --project-directory /etc/docker/compose/simplex up -d --remove-orphans ``` + 3. Remove obsolete images: + ```sh docker image prune ``` -## Configuring the app to use the server +### Configuring the app to use the server To configure the app to use your messaging server copy it's full address, including password, and add it to the app. You have an option to use your server together with preset servers or without them - you can remove or disable them. From f8f5c3c6be52004c532c089d3d87cbdc9c90f5ce Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sun, 13 Oct 2024 22:01:14 +0100 Subject: [PATCH 033/567] docs: correction --- docs/protocol/simplex-chat.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/protocol/simplex-chat.md b/docs/protocol/simplex-chat.md index 4b5f87821b..c74465f6fe 100644 --- a/docs/protocol/simplex-chat.md +++ b/docs/protocol/simplex-chat.md @@ -226,7 +226,7 @@ While introduced members establish connection inside group, inviting member forw ### Member roles -Currently members can have one of three roles - `owner`, `admin`, `member` and `observer`. The user that created the group is self-assigned owner role, the new members are assigned role by the member who adds them - only `owner` and `admin` members can add new members; only `owner` members can add members with `owner` role. `Observer` members only receive messages and aren't allowed to send messages. +Currently members can have one of four roles - `owner`, `admin`, `member` and `observer`. The user that created the group is self-assigned owner role, the new members are assigned role by the member who adds them - only `owner` and `admin` members can add new members; only `owner` members can add members with `owner` role. `Observer` members only receive messages and aren't allowed to send messages. ### Messages to manage groups and add members From 0af718f03f0a6ff35e9046934b626be3a5026612 Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Mon, 14 Oct 2024 09:12:00 +0000 Subject: [PATCH 034/567] flatpak: update metainfo (#5039) --- .../flatpak/chat.simplex.simplex.metainfo.xml | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/scripts/flatpak/chat.simplex.simplex.metainfo.xml b/scripts/flatpak/chat.simplex.simplex.metainfo.xml index 2d48dc4dc3..c3c7954836 100644 --- a/scripts/flatpak/chat.simplex.simplex.metainfo.xml +++ b/scripts/flatpak/chat.simplex.simplex.metainfo.xml @@ -38,6 +38,30 @@ + + https://simplex.chat/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html + +

New in v6.1:

+

Better security:

+
    +
  • SimpleX protocols reviewed by Trail of Bits.
  • +
  • security improvements (don't worry, there is nothing critical there).
  • +
+

Better calls:

+
    +
  • you can switch audio and video during the call.
  • +
  • share the screen from desktop app.
  • +
+

Better user experience:

+
    +
  • switch chat profile for 1-time invitations.
  • +
  • customizable message shape.
  • +
  • better message dates.
  • +
  • forward up to 20 messages at once.
  • +
  • delete or moderate up to 200 messages.
  • +
+
+
https://simplex.chat/blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.html From 11a44dc1fd461a93079f897048b46998db55da5c Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 14 Oct 2024 13:18:48 +0100 Subject: [PATCH 035/567] blog: v6.1 and security review announcement (#5040) * blog: v6.1 and security review announcement * update, images * readme * update review links on home page * links to review --- PRIVACY.md | 6 +- README.md | 14 +- ...ex-chat-v4.2-security-audit-new-website.md | 2 +- ...ity-review-better-calls-user-experience.md | 184 +++++++++++++++++- blog/README.md | 13 +- blog/images/20241014-calls.png | Bin 0 -> 292892 bytes blog/images/20241014-forward.png | Bin 0 -> 595757 bytes blog/images/20241014-messages.png | Bin 0 -> 246543 bytes blog/images/20241014-profiles1.png | Bin 0 -> 373107 bytes blog/images/20241014-profiles2.png | Bin 0 -> 368997 bytes docs/SECURITY.md | 4 +- ..._Review_2024_Summary_Report_12_08_2024.pdf | Bin 0 -> 920033 bytes docs/TRANSPARENCY.md | 5 +- website/langs/ar.json | 5 +- website/langs/cs.json | 5 +- website/langs/de.json | 5 +- website/langs/en.json | 10 +- website/langs/es.json | 5 +- website/langs/fi.json | 5 +- website/langs/fr.json | 5 +- website/langs/he.json | 5 +- website/langs/hu.json | 5 +- website/langs/it.json | 5 +- website/langs/ja.json | 5 +- website/langs/nl.json | 5 +- website/langs/pl.json | 5 +- website/langs/pt_BR.json | 5 +- website/langs/ru.json | 6 +- website/langs/uk.json | 5 +- website/langs/zh_Hans.json | 5 +- .../src/_includes/blog_previews/20241014.html | 12 ++ .../overlay_content/hero/card_3.html | 2 +- 32 files changed, 259 insertions(+), 74 deletions(-) create mode 100644 blog/images/20241014-calls.png create mode 100644 blog/images/20241014-forward.png create mode 100644 blog/images/20241014-messages.png create mode 100644 blog/images/20241014-profiles1.png create mode 100644 blog/images/20241014-profiles2.png create mode 100644 docs/SimpleX_Design_Review_2024_Summary_Report_12_08_2024.pdf create mode 100644 website/src/_includes/blog_previews/20241014.html diff --git a/PRIVACY.md b/PRIVACY.md index 66dff0e807..669a0bf4be 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -25,7 +25,9 @@ While SimpleX Chat Ltd is not a communication service provider, and provide publ We see users and data sovereignty, and device and provider portability as critically important properties for any communication system. -SimpleX Chat security assessment was done in October 2022 by [Trail of Bits](https://www.trailofbits.com/about), and most fixes were released in v4.2 – see [the announcement](/blog/20221108-simplex-chat-v4.2-security-audit-new-website.md). +The implementation security assessment of SimpleX cryptography and networking was done in October 2022 by [Trail of Bits](https://www.trailofbits.com/about), and most fixes were released in v4.2 – see [the announcement](/blog/20221108-simplex-chat-v4.2-security-audit-new-website.md). + +The cryptographic review of SimpleX protocols design was done in July 2024 by Trail of Bits – see [the announcement](/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md). ### Your information @@ -172,4 +174,4 @@ You accept the Conditions of Use of Software and Infrastructure ("Conditions") b **Ending these conditions**. You may end these Conditions with SimpleX Chat Ltd at any time by deleting our Applications from your devices and discontinuing use of our Infrastructure. The provisions related to Licenses, Disclaimers, Limitation of Liability, Resolving dispute, Availability, Changes to the conditions, Enforcing the conditions, and Ending these conditions will survive termination of your relationship with SimpleX Chat Ltd. -Updated April 24, 2024 +Updated October 14, 2024 diff --git a/README.md b/README.md index c2df084477..ff4ae8c657 100644 --- a/README.md +++ b/README.md @@ -233,14 +233,12 @@ You can use SimpleX with your own servers and still communicate with people usin Recent and important updates: +[Oct 14, 2024. SimpleX network: security review of protocols design by Trail of Bits, v6.1 released with better calls and user experience.](./blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md) + [Aug 14, 2024. SimpleX network: the investment from Jack Dorsey and Asymmetric, v6.0 released with the new user experience and private message routing](./blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.md) [Jun 4, 2024. SimpleX network: private message routing, v5.8 released with IP address protection and chat themes](./blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md) -[Apr 26, 2024. SimpleX network: legally binding transparency, v5.7 released with better calls and messages.](./blog/20240426-simplex-legally-binding-transparency-v5-7-better-user-experience.md) - -[Mar 23, 2024. SimpleX network: real privacy and stable profits, non-profits for protocols, v5.6 released with quantum resistant e2e encryption and simple profile migration.](./blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.md) - [Mar 14, 2024. SimpleX Chat v5.6 beta: adding quantum resistance to Signal double ratchet algorithm.](./blog/20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md) [Jan 24, 2024. SimpleX Chat: free infrastructure from Linode, v5.5 released with private notes, group history and a simpler UX to connect.](./blog/20240124-simplex-chat-infrastructure-costs-v5-5-simplex-ux-private-notes-group-history.md) @@ -249,10 +247,6 @@ Recent and important updates: [Sep 25, 2023. SimpleX Chat v5.3 released: desktop app, local file encryption, improved groups and directory service](./blog/20230925-simplex-chat-v5-3-desktop-app-local-file-encryption-directory-service.md). -[Jul 22, 2023. SimpleX Chat: v5.2 released with message delivery receipts](./blog/20230722-simplex-chat-v5-2-message-delivery-receipts.md). - -[May 23, 2023. SimpleX Chat: v5.1 released with message reactions and self-destruct passcode](./blog/20230523-simplex-chat-v5-1-message-reactions-self-destruct-passcode.md). - [Apr 22, 2023. SimpleX Chat: vision and funding, v5.0 released with videos and files up to 1gb](./blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.md). [Mar 1, 2023. SimpleX File Transfer Protocol – send large files efficiently, privately and securely, soon to be integrated into SimpleX Chat apps.](./blog/20230301-simplex-file-transfer-protocol.md). @@ -409,7 +403,9 @@ Please also join [#simplex-devs](https://simplex.chat/contact#/?v=1-2&smp=smp%3A [SimpleX protocols and security model](https://github.com/simplex-chat/simplexmq/blob/master/protocol/overview-tjr.md) was reviewed, and had many breaking changes and improvements in v1.0.0. -The security audit was performed in October 2022 by [Trail of Bits](https://www.trailofbits.com/about), and most fixes were released in v4.2.0 – see [the announcement](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md). +The implementation security assessment of SimpleX cryptography and networking was done in October 2022 by [Trail of Bits](https://www.trailofbits.com/about) – see [the announcement](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md). + +The cryptographic review of SimpleX protocols was done in July 2024 by Trail of Bits – see [the announcement](./blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md). SimpleX Chat is still a relatively early stage platform (the mobile apps were released in March 2022), so you may discover some bugs and missing features. We would really appreciate if you let us know anything that needs to be fixed or improved. diff --git a/blog/20221108-simplex-chat-v4.2-security-audit-new-website.md b/blog/20221108-simplex-chat-v4.2-security-audit-new-website.md index 515f06c66b..51dba8818c 100644 --- a/blog/20221108-simplex-chat-v4.2-security-audit-new-website.md +++ b/blog/20221108-simplex-chat-v4.2-security-audit-new-website.md @@ -27,7 +27,7 @@ In the course of the audit, Trail of Bits assessed the maturity of the [simplexm Explained below is our understanding of the issues, as well as fixes implemented by the SimpleX Chat team after project completion. The full security review is available via [Trail of Bits publications](https://github.com/trailofbits/publications/blob/master/reviews/SimpleXChat.pdf). -We are hugely thankful to Trail of Bits and their engineers for the work they did, helping us identify these issues and supporting the ongoing efforts to make Simple Chat more secure. +We are hugely thankful to Trail of Bits and their engineers for the work they did, helping us identify these issues and supporting the ongoing efforts to make SimpleX Chat more secure. ### Medium severity issues diff --git a/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md b/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md index 42ff739106..feb2a85036 100644 --- a/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md +++ b/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md @@ -2,10 +2,9 @@ layout: layouts/article.html title: "SimpleX network: cryptographic design review by Trail of Bits, v6.1 released with better calls and user experience." date: 2024-10-14 -# image: images/20240814-reachable.png -# previewBody: blog_previews/20240814.html +image: images/20221108-trail-of-bits.jpg +previewBody: blog_previews/20241014.html permalink: "/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html" -draft: true --- @@ -13,6 +12,181 @@ draft: true **Published:** Oct 14, 2024 -This is the placeholder for the security review and release announcement. +[New security audit](#simplex-cryptographic-design-review-by-trail-of-bits): +- [review findings](#review-findings-our-comments-and-improvements), our comments and improvements. +- [next](#next-security-audit-in-2025): security audit in early 2025. -Come back on Monday afternoon! +[What's new in v6.1](#whats-new-in-v61): +- [better calls](#better-calls). +- [better iOS notifications](#better-ios-notifications). +- [better user experience](#better-user-experience). + +## SimpleX cryptographic design review by Trail of Bits + + + +It's been almost two years since Trail of Bits did the first security assessment of SimpleX Chat. + +Since then SimpleX Chat grew a lot, both in the number of users and in its functionality. We added XFTP — a protocol for sending files, — and XRCP — the protocol for using a mobile app profile from a desktop app. Messaging protocols also evolved a lot, adding private message routing and quantum resistant encryption. + +Trail of Bits reviewed the design of protocols used in SimpleX network and applications in July 2024. Even though there are no critical issues, we made some security improvements based on this report. + +[Trail of Bits](https://www.trailofbits.com/about) is a US based security and technology consultancy whose clients include big tech companies, governmental agencies and major blockchain projects. Its engineers reviewed the cryptographic design of the protocols used in SimpleX network and applications over a week: +- SimpleX Messaging Protocol (SMP), including a formal verification of currently used message queue negotiation protocol, +- the SMP agent protocol, +- the push notification system, +- the file transfer protocol (XFTP), +- the remote control protocol (XRCP), +- and the chat protocol. + +There are 3 medium and 1 low severity findings, all of which require a high difficulty attack to exploit — the attacker would need to have a privileged access to the system, may need to know complex technical details, or must discover other weaknesses to exploit them. Additionally, there are 3 informational findings. + +3 of these issues are improved in v6.1, and the remaining issues are accepted. Below we are commenting on these findings in detail, and also on the released improvements. + +The full cryptographic design review is available [here](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/SimpleX_Design_Review_2024_Summary_Report_12_08_2024.pdf). + +We are very thankful to Trail of Bits and their engineers for their work identifying these issues and helping us make SimpleX Chat more secure. + +### Review findings, our comments and improvements + +#### Protocols specifications (informational) + +The review finding #1 is that the protocols specification is informal. We addressed [reported](https://github.com/simplex-chat/simplexmq/commit/7b6c86c6c1093cdae5ad2ee566655828076bc25c) [inconsistencies](https://github.com/simplex-chat/simplex-chat/commit/1cb3c25478db0f2a42c943f7469f5f9f75752a27), and we accept that we need to improve specification beyond verbose descriptions and ABNF syntax specification, and add algebraic notations and sequence diagrams. Having said that, the current specification correctly describes the implemented protocol, without any contradictions. + +#### User-correlating attacks via introduced latency or via GET command of messaging protocol (medium and low severity) + +These two findings #7 and #2 of the report relate to the attacks confirming that two known users communicate via observing their internet traffic. + +The first attack is possible for a party that can introduce the latency in the network traffic. This attacker has to control some network node that passes the traffic of the sender — for example, it could be the sender's ISP, VPN provider, Tor entry node operator, the operator of the forwarding SMP server or a server hosting provider, etc. Such attacker can correlate delays in sender's traffic and the suspected recipient's traffic to confirm that they communicate. + +The second attack relates to GET command used by iOS clients receiving notifications — depending on whether the server has the message, there will be a different number of packets sent, allowing the observer to determine if there was the message. While this comment is correct, in practice iOS clients only send GET commands when they receive notification, which also happens only when there is a message on the server, so in absolute majority of cases the number of packets will be the same. + +These are not new findings — this type of attacks is covered in [threat model](https://github.com/simplex-chat/simplexmq/blob/stable/protocol/overview-tjr.md#a-passive-adversary-able-to-monitor-a-set-of-senders-and-recipients): _a passive adversary able to monitor a set of senders and recipients **can** perform traffic correlation attacks against senders and recipients and correlate senders and recipients within the monitored set, frustrated by the number of users on the servers_. + +As threat model states, this attack is more likely to be successful with the less busy servers, and also for the users with few connections. + +The recommendation of the review is to add optional randomized latency to message delivery that would reduce the opportunities for traffic correlation attacks — we consider adding it in the future. + +#### A compromised transport protocol allows more efficient correlation attacks (medium severity) + +The finding #3 is about the incorrect statement in threat model for SMP and XFTP protocols: _a passive adversary, able to monitor a set of senders and recipients, **cannot**, even in case of a compromised transport protocol perform traffic correlation attacks with any increase in efficiency over a non-compromised transport protocol_. + +For protocols prior to v6.1 it is only partially correct, as responses to the commands that create a messaging queue or a file chunk include the identifiers both for senders and for the recipients, so if any observers were to compromise transport protocol (TLS) and record these identifiers, then they were able to correlate message senders with the recipients (and file recipients with the file senders). + +The solution to make this correlation impossible even in case of compromised TLS is to encrypt these identifiers, as proposed in the review, or, better, encrypt the whole transmission inside TLS. + +However unlikely is TLS being compromised, we added additional [transport encryption layer in SMP protocol](https://github.com/simplex-chat/simplexmq/pull/1317/files), where it can be more important, and we are going to add the same layer of encryption in XFTP protocol later, where we [amended the threat model](https://github.com/simplex-chat/simplexmq/commit/7b6c86c6c1093cdae5ad2ee566655828076bc25c). + +#### XRCP protocol recommendations (informational) + +XRCP protocol is used for connecting desktop and mobile. There are two findings in the review: + +- SHA256 was used as a KDF in XRCP (#4). +- there was no forward secrecy or break-in recovery between sessions (#5). + +SHA256 is now [replaced with SHA3-256](https://github.com/simplex-chat/simplexmq/pull/1302/files), as was [recommended](https://www.ietf.org/archive/id/draft-josefsson-ntruprime-hybrid-01.html) by the internet draft about hybrid key agreement that XRCP uses. + +Even though XRCP sessions are short lived, and usually the connection happens over local network, we added forward secrecy to XRCP sessions [here](https://github.com/simplex-chat/simplexmq/pull/1328/files) and [here](https://github.com/simplex-chat/simplex-chat/pull/4926/files) — each request from desktop app to mobile app is now encrypted with a new key derived from chain ratchets. This improves security of this connection. + +We believe that it is unnecessary to have in-session break-in recovery in XRCP protocol, as there is break-in recovery between the sessions. + +#### Device compromise can be hidden in some scenarios (medium) + +The finding #6 in the report is about an attacker who was not only able to break into the device and get a copy of the database, which would be mitigated by break-in recovery in [double ratchet protocol](../docs/GLOSSARY.md#double-ratchet-algorithm), but also was able to modify the state of the app database and to substitute the addresses and cryptographic keys of the messaging queues used with some contact with other message queues that the attacker controls. + +Even though this is a very hard attack, if successful, it would allow the attacker intercepting all messages with this contact. + +Effectively, it is a [man-in-the-middle attack](../docs/GLOSSARY.md#man-in-the-middle-attack), where an intermediary is inserted via the app database modification. Such attack can be mitigated by periodic verification of security codes. Although, the attacker who was able to modify the state of the device, could have also modified the app itself, making it show the same security code as the compromised contact has, thus avoiding detection. + +We accept that such an attack is possible, and we don't believe there is any viable defense against the attacker who can modify the device state. We may consider adding the measures to validate the database integrity, but they may be ineffective in case the app and/or operating system are compromised. + +### Next: security audit in 2025 + +We are planning the implementation security assessment with Trail of Bits in the beginning of 2025. It will be a twice bigger assessment than we did in 2022 — it will cover both the core of the app and the handling of cryptographic secrets in the mobile applications. + +## What's new in v6.1 + +This release has many user experience and stability improvements. + +### Better calls + + + +This release improves reliability and usability of the calls. Now you can enable the camera and share the screen from the desktop app even if the call started as a voice call. We've also fixed several issues that prevented calls from connecting. + +This is a substantial change, and some issues may have been introduced - please report them. + +We will be further improving the calls interface in the app in the next versions. + +### Better iOS notifications + +iOS notifications were added [more than 2 years ago](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md), based on this [system design](./20220404-simplex-chat-instant-notifications.md). Until recently we made almost no improvements to them. As the number of iOS users is growing, their reliability is insufficient. In addition to that, once we started the work on improving them, we have found several important issues, one of which was introduced recently, when we improved the speed of creating new connections. + +This release fixes many important issues with iOS notifications delivery in iOS app, improves app performance and reduces traffic required to manage notifications. + +We also fixed several notification server issues, made change that almost completely prevents losing notifications when notification servers are restarted, and added real-time monitoring to diagnose any issues with iOS notifications delivery. + +This work is not over – iOS notifications in a decentralized network are complex and require more work. We will be further improving both client apps and servers to make their delivery stable. + + +### Better user experience + +#### New conversation layout and customizable messages + + + +Messages are now grouped when they are sent sequentially, with less than 60 seconds between them. We also made message shapes configurable, and separated the messages in different days. When you scroll conversation quickly, there will be a floating date indication, allowing to find messages faster. + +#### Improved switching between user profiles + + + +Another improvement relates to switching between chat profiles. Previously, when you added multiple chat profiles to the app, there were two problems: +- you had to tap twice to get to some important functions in the app, +- anybody who could see your screen, could also see all your chat profiles. + +We changed this design by making important functions available after tapping profile image once, and by only showing the previously used profile image to switch to it quickly, while switching to other profiles now requires scrolling to them or opening *Your chat profiles* screen. + +You also can switch chat profile when creating a one-time invitation link. + +#### Faster deletion, moderation and forwarding of messages + + + +You now can forward multiple messages at once - up to 20. If you are forwarding messages with files or media, and they were not received, the app will offer you to download them, and it will also allow forwarding messages without files. These messages will be "packed" into the smallest number of sent messages as possible. If there are no images and messages are not too large, it will be just one sent message containing all forwarded messages. + +The previous version allowed deleting and moderating multiple messages. As most users now upgraded the app, we increased the maximum number of messages that can be deleted or moderated to 200 messages - in most cases all these deletions will be packed into one sent message. + +## SimpleX network + +Some links to answer the most common questions: + +[How can SimpleX deliver messages without user identifiers](./20220511-simplex-chat-v2-images-files.md#the-first-messaging-platform-without-user-identifiers). + +[What are the risks to have identifiers assigned to the users](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md#why-having-users-identifiers-is-bad-for-the-users). + +[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-technical-details-and-limitations). + +[Frequently asked questions](../docs/FAQ.md). + +Please also see our [website](https://simplex.chat). + +## Please support us with your donations + +Huge *thank you* to everybody who donated to SimpleX Chat! + +Prioritizing users privacy and security, and also raising the investment, would have been impossible without your support and donations. + +Also, funding the work to transition the protocols to non-profit governance model would not have been possible without the donations we received from the users. + +Our pledge to our users is that SimpleX protocols are and will remain open, and in public domain, so anybody can build the future implementations of the clients and the servers. We are building SimpleX platform based on the same principles as email and web, but much more private and secure. + +Your donations help us raise more funds — any amount, even the price of the cup of coffee, makes a big difference for us. + +See [this section](https://github.com/simplex-chat/simplex-chat/tree/master#help-us-with-donations) for the ways to donate. + +Thank you, + +Evgeny + +SimpleX Chat founder diff --git a/blog/README.md b/blog/README.md index 03afc15f8f..c8de7e83f9 100644 --- a/blog/README.md +++ b/blog/README.md @@ -1,8 +1,19 @@ # Blog +Oct 14, 2024 [SimpleX network: security review of protocols design by Trail of Bits, v6.1 released with better calls and user experience.](./20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md) + +New security audit: Trail of Bits reviewed the cryptographic design of protocols used in SimpleX network and apps. + +What's new in v6.1: +- Better calls: switch audio and video during the call. +- Better iOS notifications: improved delivery, reduced traffic usage. +- Better user experience: switch chat profiles, customizable message shapes, forward up to 20 messages. + +--- + Aug 14, 2024 [SimpleX network: the investment from Jack Dorsey and Asymmetric, v6.0 released with the new user experience and private message routing](./20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.md) -[SimpleX Chat: vision and funding 2.0](#simplex-chat-vision-and-funding-20): past, present, future. +SimpleX Chat: vision and funding 2.0: past, present, future. Announcing the investment from Jack Dorsey and Asymmetric. diff --git a/blog/images/20241014-calls.png b/blog/images/20241014-calls.png new file mode 100644 index 0000000000000000000000000000000000000000..c9d8bb18a7ae1063037e2bc2ab0b96cbfdae8500 GIT binary patch literal 292892 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

RZ8c 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

fPm3TZkPU8IL2yE@8@hKl?v1TRxchx8d0U=w;jU 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

QJ>KEo~8(%2=$W&dqCBLe>%zcl4RB+&n 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 literal 0 HcmV?d00001 diff --git a/blog/images/20241014-forward.png b/blog/images/20241014-forward.png new file mode 100644 index 0000000000000000000000000000000000000000..c094bf80a9d6368ae24c7161ea4c3e8b2c0fd63f GIT binary patch literal 595757 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�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

%+kuWvz*55*QeW$7YP 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�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�7ySP7zJ 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 z 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

BRK6 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%0DcuCmg6A`5(n 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�Df~sn%I(D8W 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 literal 0 HcmV?d00001 diff --git a/blog/images/20241014-messages.png b/blog/images/20241014-messages.png new file mode 100644 index 0000000000000000000000000000000000000000..afc79938990e99b8d8557d0138bd4d0a8c591a76 GIT binary patch literal 246543 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

%N*qJ_5S1OnQ!=|IYD#g9LYBD z6qOSs?a&gyDk?TUG%PwwM<4PF zO-Z8_9f`jl_0t}dSmOLdsUva;K#&(~FJLw#y&cbhSq{oEs~A{ zFSS3cwv^;gtC^PHgh<a2$pM;k* z+nkN6jdjp(oqB)^@GI)&wxAa&BB;FXkz)7bydF6cXttPNltlVe=^ypdyjeQr`vt-u zm-KAefH-+E0<9~*f&jQ#H_;ms@VO#2nz^jLCJ20*+*p=Wf&-`mV$nk00}u|NJfs`IIAnpROUfH1GMC`C=MG3f}+&5lvi7JX_`0gXuflkEwbgYM9K zUl+~9`zdz)*{VSK{KX4y&)zwYf14xpaX%z57ipr)Dl60&jXqr0X*QC=%dN?~4|2C( z&;_;ReDXfue$+*Z2IT;CO5OJ5tm%iwPfV_Nc57#WX;r2dH_9uvAT@(tdtT-jFQ*fz zVkc8RAs1$D)LCHHCJVedT7ju9!~)3q3@No3r0`Zo)&Q8R*QbH)KB|9pzOD*$A0*dN%k>eh-pVQ^g-ed)xA3}X=XAoC)?$u1>jyPpD}5* zm+BVV50`Sl0f(%LBl@u=7U&b1 zH-D|aP{sw*Yfdrt*aEu^qC(nA#6v3HZKag5*G^@l?N#7=5o!6uhC;zkYy{PR7H$L9 zG?B==NHA|{@yoG!DM4QxXuRgDTFpa1xuzeD7rO7WB4`$cI#n7Jq*vy0q598xJbC!= zVe!T1pBL)@^VJJ6fAj-QNW3|9sw{g?NbfTje4E0|`g77V zSIBQ6LDs2tJBf?n{OR@M($ggAJR4yFB}PFzM!=jLGKJt9HR9W)t<6Rn${b4sB;!huJ?v)-iTf6tKv%Jva=HFGw#PpTK%GwI z%pG7nnp4?Q9P_HzUO|8M_0TcDd-yXJ5W1ICqBTh_5a?ohT1 zBx<3?^PurzY&u=5-i(m3xwNwx2OhaVP&G*YDf{;8fOX^Zb-qG)-{kbshe**kSO8sJ zd>i#6zQjPKUlkgp^N{o(H8zTCogd*hE^oR-$Cs1DBj>^_tQuM*J<|j`^7S+QLQpP{ z&LDakVvQf5mwqiT1jb6Ny#&aUx(x7*C$K(^ZFutUpvDr52GeW!70S95(xy7*VW`Wk z#!$Hp<>DE^_vJA9Wv9Qo=uOrHci0TNu*3$^DOgcXPdYYWv3l%RkjQ%qsm{DE@%s_L z{664&1C+*4<)4uTJm;S(EX&xXcbX7fU%(7vn=tBWJEk_; z`%H>{$#8K(3_!hwHoiq)!3?3lJ>Y1+wN<>Z^a^So_d?y|TKzpH;&njhUVzuN`bzGM#DoZ->DAlEz353(ZM3y?K z;0z}sf$TlL3<%1VlDmj8Z_DI{RQX}qE2yA&rA0sm)q!pL`6Kda6xEh$JgdRmwdI>g z#qR*nKS7P)5@mP=8UP-E^sDvn7MsN%h3e1IX?r+z;hURIS&BR=cpg7QIDv9T1$@T+ zvvgYkEjS;?mCGxL&fs32WHl1g%RCw<%^b$GviiztL3vKoatZETDD1G{v!44+IENbR zK1npe@pYurcLB?{0q07y)dLrw=caaCra3Re3a&R$SMh!s5IzXS zgHli#R8*n^mX=$YHiLApa^K6ZpVxc8WTorwST9m0>WaEfho!uqvEdQeR>zeHS}$_Zn44fYDk3Bro)rc45|UhY*s|bAt2mt zeuK2?eM(aeU@I^eaBG8HlZS_Za;>bWnxba3^(Ej8U<()(gu21eNyY87QWxh`NPoC!&T<`B(0G3O-u%kSlaxh_EWdvSP>(rDFjs-&x0$S#Y6i`ADR zR5_-x9yLA%2t1502ZmX%zf6$eY&j^;Zt^^glSVypR8a0>#tpBYNV?2#7YA8or_Dyt z4tdKj`FIgN`o%zCFJCr@qg$)K$fx;t1K8i;;G-+`x8TL4VtwJ2;#H0Y{$l&j$+rpr z)So|Lw8&)JW(A1HIYFoMsKE3{8$T}6Gxjb6YRM7=PXnYazfK@7@SQfDlu82SrYUb# zUzvucoL0VDKc*KF{1Watp*iH})80*9>GVyc>jY z3GiE4yabrN0jRwlVfw@VjA`g42QdJ-*4|x&_IpMB(sv;)>y`HcMVQ*^%Y0tG!Ayaq zxd{NnzPWeA;6N~3oVdE0`~+2NZ&5ZD7fxMS`X1xfks-O1$@q_`Y69Ap0mWBQvPgEj zPmZmG8VMA(&$j^e&Un}A?*d}k@OAJLfZ;m8_7%#~0FG9W?%rf_{TZYK*V%Bnh{RXR zFZ*l&wFIW+=U2hC7D)`_Ce3?|4*<*G4^VC>^}$v1xKO)}r2cb2tor7FA!8xUR<&ii zdX|c0iRBvL{F{S6g_etKTxEk4{^omg@fU;Ae)PP`ZA(_C|bhP#tHT=gaWl1D#fTw%JMUB-fo1j|3YvA?lysWvO%i zeI$Wr_=j{0&IQ$8<5O2c_B9 z846d~eYu6y`yMJMb-KqglI%KYx>7PIE?F%Gr3Me)T^_1}qQqM;pL^7Clm>1e2so^1 zAD6~+_pVgHzAB6SN{sOJ;?i<)`}VEkqmREV{>$ImDw++)D2BKx-Kl{2uspTn*eAYR zgG@MGGRwZh(wO_rIWXtI)8+tjP9&!_bgh*tH_2{^gM#uos!=f@=3fh^}t8P+Vx=tX#Df;qlHg>8{9vfMa z0M{A4bQrPK$-7Ls7ni_MKn%^sjP z{c$WH_Ba?w>G2hKtj_i!(&c-A+KbSV%y>>S*CchAMXaM-tiD2e9~Q4I|1z{BvyI6b zi~Q++EdW$+wY~%>zYDOi9@_Qo6y@x<%vq2)VyT!e`_l*)k&^@1+#iwueWSLhEK}km6 z1Jx~9k2XyBp#^=cM89l$zU#j40esVB`oiY8T zy>eAdG<^oebMHpaaX@wemy+= zwAgBV9gBsn_UG|_al!hL!lP|1A`bp36P@4L4$HtU`+FBWW-BCF{bE$ua=d5ZS$YM+ z(|};(DAArK?r~+$UmoOZ0O;caKcDaH^XiB9th&&=6gOn&`wY;t-~4_&2kf?ed|T@0 zrT(ln9o=`{dAIYczxL~G1Sjo(_iz7DFKVMG{@@S(z}rNQUNF52X+h9hBG}}D&c2`F zys{*{U(o6Ep2wVX;CyqS-+vx2r|*t?Uyd;4mKN)mY^#TK+HeNO$o zoaZc`0d#IcMQ6Nr@vWd?{IZwn=<*?e_^`6$=NRP)qPmdO4hxrFkN_2vxeS6xlIY z;C*ZVj{*FzqBH%gkylDDnOIa6!~=O5nmkskaLj z0lWNMM%CqzQ&EklQx|=ouL!2Gi@C?U2Y(fkbZNM)u6gZkfcz3xalSNZt~4=6dQaWfhro3d%F9ax=4q3B*-( z$yXXFa>)68JPyD>nb~!LdF_+I=)`TcL~dR#I{*Bi|MP|4`uo3C{X4($Ym5KrAN|+G zkAC>W&vLv;@6(BMy18U2cy0{g4beTdrtS;FMc8f$kA$C;;(7Qv2hJ@A@*v+a4e_gz~@=JhH4%tCxTwv#cIJXb}GJv|kROzz^6S>LJ&5fpSdlU2uj5h(znw=EH z_pPN!s}R)oG)8+dc!U#fS4mkwZE0H2f2r6;f8C9ps)(d;DQ!2cYU)v4?iHlf50Tir zD0zGJKabN+hub;NbeU(e%dVuGiX_)&NW?ppb}VEB9pryjYRdGKKT>6RgR z#qV<9Zk+jT&Vg?&2l|t=(pon+Duq_hIdq)?NN()&qhzQP00PvZ7C0)w4vQn)ql1+@ z8-Tupsl&E9)tKvxv;rJ4oCqW$?h=+&R+BAo`)qX0A^j5|-$H6FeHBdhB}X_V=~^&3 zGlywFt*bP*&~<;0TqXg9lM-b;0310{bOF$q$~A-+lO{Vscn5mbJ$8`R3SqPKTDk;^ z>OTuoTeU-^-Z!ww@Qdi2|136CzO?)|0qEZ?KHvNQ0K8iPA`jTWSPPYqZPL7rRQy-? zy%cH~s;Id6Q|UQd9YeKar*${<&;_7gyZqM1xgcM)j}{w8r9mmP0NWYqIOmc;Ybkrs zA;4FPpX`BGQ8N+X{zc@I1w1(|f&6PLzf4>@GS7A;y;T~ti6 z>GLehl*h%h%wwMRx#WO-JB`H_uQdAbSnBufR`a0x#v8BwZy$a1$v^!k|Ky+4fA@EP zm(Ci_NTr}%-xF@9o>e2q620bUKM2sybIkV>lXU{s^Vf3@JntM(YE4HGG;5o?#l}jX z1!vgx3osCy7?BiLJcg9;>OX_Dxf2Jlih?&uBsCMI$yH1lItH}%IJ9LSa3{zVz@uO7 zg$Wg2w^BNXn|$3pKtES-&0#NCT&XT{6gl~MP*1q%x5zpH<=XnLE@PRcYunq!f@&>p z0zFID+P}vJ&2`oh>LQ=gB#7;jN;`&d(Wy?A-)Z?d`Zr^Q!n{(OZEm6z&o0w!+*E|)`nrHWqmR^wJ!Cs{|@ zu5}XCI9^8L?!iUsQmdQ3iS+ymn=RdNDj0Tyq?v3^#0SWjwsLNc!f2m3zgAlo1gKtZn zi%842;UkM%XqFfE8}Ap_mwyX&oN94v^L_mOhCTKKeh{GN2t6(^H;w>T$+Mtbskw2V z6-jijOx=epEq|72oP_s-CKO6E+Vq=fJt-fD;2?Sg_mL$NZnB{E%P^ zcozV^q06l*g;MRf>|6G-q-qc&DDmTjOF(*C={g%QTYKMx^%T=~01?b;o7>YX6VNw3 z^R`oR1waSbx3E^?QS@95L9o(zER`rFG>=#~Aa&O~VmeSra**t@kT9<*(!-P{{xfyt zLAm5l?c&>s7tky(&c?xCt+BA+Sg5sK`mPo=zwBv!q00jCmN>&p#| z+SzUb5(UZk0Lc$Hol;5p&BY&v1XpnGVL_YdQybni52-tW?ln|0?y~vukPV=h>u;dr zeK#z=NQNpaFJf^-@GGEC!*-I`C)0r&Avs?{J!BPgiUQsqe@x&pR|!BZkXO21(mw!xXW$Cq$Vk?nP5-GnHv%-!Is zj=f;qF9!Onr~d$z71dSbrKS*-_^Z~l10Svf{M9Gd#(EuT`vx?rukYeynXBGei7yTU zbPpGDQ>|YYl&q(IIXKZh#*n^;MVJ&EDkZnyDvi$IT)_S;Iq7rp;N6A8v!pUo=68&L z&l2OFE-g04J-WpPRd!kLc2Pi?gEBUG2sd{&i6r_wG zJd-JT1hE+1$GFe`%sKGw;sD(k^^(@!?U0gVVx*{Q@dUfz35469alOgIPGjE!h{ua= zhXQCV03bliST00I7pb*sCat|Ygl~oA64hKl*(Me-1w;KLcs30|I12*pk_)o6_Z=H8 zF@Xr7Os+`MYiq?Kw!sb8O8_w6+Pj4m8)^Aj>0%51S-gZaU#l;{hc175W$KbNi<|6m zvBI?wUkozeW|8>GX$U;EL9SGJ3xKE^iFU*V=mJxp*VP4Q;uOFOC~qJ9wD@HAUj?wd ziuu243pEu?`oRClqog4H;@$hN?f z>K$$x6-au}k!m7>;NzX6JO@w##5WqB7ME84W5DiesvgX z=`2l+ee14WB=Ss`H1Sl>VCN1MQd^k-Q>2U*Sj3cK>=zPU> z+3#tdJ1yq~&pV(B9Si5K#{qkII385HOU@;^jN;NOd#9I2emFAc|Jy84ZqThoXGq>=fHX6fSouRy?vn>Qtc+7yn+OFqNRVAydo=|J?{0csz#Z08puq(|NaZ~dlQ=Q3IJf{t)gKQV;pL87&XTgIPSf1} zlr&gWXu7aZLOJB=p8?42<~B5;Uc&;x#Yxy9N2;9`GBX*vx^!9y)ELN9a2{$by%G%L zeedN=9|XOEL=O~F4MZukCIfdn_X04gmQlfiw$&w|eslHLnN+tzP2&#gEZQH}sRr}v z%HQU16<~k8xOMQyp&q09$_lC;ZjAK6qjccVK>$&KvM~kX`a9{kAW+pqeRIKf0?6{} zIGzZU+mvGk$^6?$_1RpBop6EvJ@m3SD_<9Ht^OyZkieT?1yX9-Zm%M(z7_iPHy3{) z;-m(4Tz;v{W-bim3F}4#EIUk7D2ar0zCO;^!p{eJiVF4-R|st>5t1EYq ztb2N;VA#Wiwy|YyV{ucZDjTY|_|8p}>St^JTWPz;&#T%J8z~pCK0+A5bCO~o!^Z&T z%K+uKVo`7#%P~s$m4Yj2-T>hG&h~2gOvyPxX}w z5&t?1oP}03z9Lwbi=1BS;!8CYQ?9ZRR^9XqwQH1Y1wW7tP96Ii{l)JF(!JCA8bDqL zyuSkQ;WS+Sv4kNsUzLa@?MG+9NapAt+mp@dG#et7Mhnh$8Jr6=Ghk0|p8Q{F$cshOiEGoy6DD}R5u)to4PVs{4w=pwPjYW46BxF^4!1hr0>C_BoE53&jY>-E;9gN*dmHIC>Mk+J*bmhm=MWY* zfV%M%U3bA~NYYUoQKHX}E@ccs07UiJ+k_X$*DkyYSQa33JM_%U0MJ2Q#`Kf4JpB$` zcxY*}Kp5WvBM(qVKm|dSo%EGpRBU;Z)P@4fpH=ZjWzT@Aq}J0u1(|}$q(0(Mx)2*P zJ;hVNC|FiX>;g-m?E6ONg^*DDbs>R8o={0rg+wX%A-dIz=wGjJ7?CNiR?)NOBuy6q zg3&Z!Q{SZ7L`PkVBnddPh@(9S(gm~XU3(yp`3`sLi(?is)KPvIn=6$*zlK#9Rb1Tg z3C;~?x&q<_B=|c>%gtkzcq#286UgwadcH|gZ+umBwAp?et1p85m8Hw1eJ6C#(^rJ6 za5qVs9dEA7106<~i1^?uisxYb)r46}e*k%Fo4ZQvwUi~1kP45#OUI zmQ32Ho5bmy0P|#<+w3_kNJ+Um;?Sc>#Q?pijz}{;0o41{0bh`=djhD|TN3?e-H(|u z0Ed}o88EGfCSxeyGXxx(WO?OyuAq61#oNY zTq&^tQz^KT_C}LaGLaOoAsJUSBT0Y-$kwTUb6x^}{2;+J%uA~*+gN>3vOd~UN{}3B zU1DRY?j9;8E+|qS;RStCEf8h{GHp`JDN40d`Enhku;cyhgY&D{XO~V@TU_9{(epxa zBNi%tHBbe}6Z>pi{!6yV6R!pD>eS0KRc*p1d{;WYgWmn4*2l$O^RQUgCVVU`&bluQ zl-@a4a4raTGpJxxNwC+G2FHWaa%p=SPCmF$F@F~+S;gmR(es3OAh(rr3(i$XdDu`L zh30|(Nv%@#mlim!iV#)ybPO8+06+jqL_t(GRk*kLZM>&ci%*DalVsE9qC;Wd4RXle zgMR(clRljam`le(P+KCXOnuM0RHAd5Tv{g)M1@+1CKt1aGluO_ ze@?Mx^VhL~i9tXKVj@khcm|BJ(Y1c*-JmZ_Iv$&2rVjwyItUdUecap{=?)u%$S>@| zQ%)Btu_2ba>TmWrHp3acOwxJ}`Dsaa{s$;d&rPIZdtrlHU2sh&dN`24bG!9TsJ3Wb zML?>VLhDYw^9G<-C*ajSwI!#WohILqKr((|2ic;(C5uzk*LCz)Ff$B@vu@e^SzO=_*UhCsEUpPUFu)f-P@MPrg|8 ztnQOIcCP>0|Mn<;oo1naIn9|df?NLJIimEo*aXb`Jz6y<(J8y;*TgxyWR%9J&m1T>(wJV-L`!v$>U|07~Y7YW#rQTlBe}ILR zgBB`TJa1C=@}f)7o}API@`5g{u`Gr9&4b1Vl>5cv2Dai43+%J=d#Q~%QvRKp6KpC4 z7I@}#bER5s>@fz4^loiei zD3$K+A&C}ff4%=7u+;LaY<67Z`6o!%t`^sq-s2?8i^U=fkjwSgQKi|A@cV6#n&;?x z-nn7ZMS);eDSEODqn^9qb$pk>t{YZ8=;uCm#kGqr@Rv^0_Y_iTFHIcdXD~FRGAz>* zu+)mx1va*_sE~H`%3YMi92OynALF&L`Np@Ee0txi64kt9BSi7JBM1m-sz*B@W0?xGGnI zGr8S%`@Vwc#IZEalwzLh@r0S`__Uog62T5{iM;gJqC|>;E zbHo8Ag$@&8YZnU+FT4@b*|rnLQ+ISYB&Qw@bhv@;F`GRrs@C)dn5Rx41PGcJ(E)!u zo;@@vK>Fc5W*z`W*}-ScC~7*fPPtT1ar0^wTkEwIq`*kpo7*49M%zlQtDXa(bk6P$ zt>x4Pp5;{?J@hw==H^Ge{Q*?wK)AE~DTKu;3((KgCLc9E3)|mH!Ig>&WIf>KKBfn? z%`FhWz#%#x1Ih)V>QaBX_lKDD`%XyG+s!th@|TOdsJ(o)`~Mc-Tl@RP%E~1+Wd5}H zaQj~n{|8uZ!3H{jbZOyIvB)Oni^Yp!z2yL#3b=Oc7&hIrSQ_O`9-Df z8hE)wd>baKtN2bJTXuqD!+RI-=J2MKf$yw< ztNDA`vBr^>JKkhaF3rX)_avW!myv)a! zB^1;fY#Lq1qRa9LDlp@ud#=KCnJllo2A#YP|M*HV_;&i=13i1PgrfTCTJ(F-&MfV&y7vGp<1aZWyELt+9r9DIZonka9myyWuq z$D*DpOIDNVrP~jy`N-P(cToSq?)AZgViEiBoetZnRFR2s?JUQyWY1eGCJ(U3?dHsz zOTQQra6#Zcy5X7w6rg*G=HkLy@erNwgU*BE<)vSeixI|?EUVZ&*A!x-*ehP)gwT(6 zejoFNe}>hPABTk$0rzceoF5<|_h2JIvX)eo2CKX7@%C<#eAN6TEV(4r6Zv$S@;jAv z*j)KyEF84{q6GT_((Gg_-G$g`(wDyDTuM7FGD_`&DE@LItZGD_QtG^Qb@9Eh=yHhO z{|!`t>hOE7a~DY^ATnG6=9Xzo4@3#gTb>l{nG%3;%+{$JkI#0Ce*=E$`-4rngT?|H*KJpxiQLTBWbNknnf@0?SzPnap+z^>IP#*2Or_11!vE?6h z#YR*XHx+=ny83OT;T`Din`Z4GS!W84L$FCY}1F??J<@P-{8d=3ttZ3xu~$SiL5>X{4Fm1b)#rq;&JdDurE;Meg*dhD(A zX>L);`ImeD7U}-GNa{RQ6hL;SpnQ;&G+QNK)QBjr8$ItgK8!B}E*@?l{ADbPJn%@T zy2}pMZ&u;0dh5}2%E_d}#`~KAsiCv9+kPO3_HqCzgWj_LNrQ{=GPs>JK+iy58U*?$ z(IV*f(n(WUA}*a?rjw=S8T7k}Q@)(!%-<{XXPh`ErTHAazX0o&0oJOusLsOnpt+kF z2NE+K@w_r?(^oQob>H)Hfi62MN0pw=2|^xUJ&li*YPDn+(0sY}CKB`?GL}|Qvv?Sr zHkVOx5ilzuS1m**XxG%AQgDI33MlG}Uud&9q|uStbxHhD}&qf+lG{CpJFY%Za1ufDqn zBgILq@F%rAXFAl?_?ofALT8)Jq>s&glVqF$rEOJknyras!0!7p$kgAk8N`p1mtnmG zzM}wo87PmFrs)WRO|wkju()MVZa$;(vaA`13(zgAI`-LbSlZbygL~t6okUZPcTVp1 zn7V(sQae2-1$!2XAzxvtx3U}T_n|&S7oL?HHX}`xw$kv&oL=3AlHUb@?*>h)jr?YQ zpL1Z&fhWQNz^syBH&0fvZ~pjj^F*i^or#mPqhP3+FiId9S0BE$$EH=(OOljdu|zi< z;J|~3DvOuXj3g-64I7h4x3zbdsK`aJQWI)3NZVU12x_aZk`MOT2jlVE@vX|n+4}ms zMXl+=CS@`EzpAj*0p(wk1sfFsty*Dm(`S{#oP4(eP>&Nc38A`$cFR{#H`#984hgT| zS#YiSM1i}`jiHA~f!oz4lQ*{L*;t!^w&45o&`H-{Gm55Xo)@bx7i){?>!YrbR888X ziE0sa4NgeY^Pv-x=qrm!YA<8YjJ3xSnLxR!DxQ$4ZoZ46ulN6m&8e3QPY-nwp_Jc+ zj=W9>EiHz0{n(^WdYL7~f>*(=P9Q3H&EPWn1pWd}!(_0Y{Ug#Ce@xG75TNID^E+ue zE^Pt1aSUTxCxx|a_AA3?^_Anu&1vS1uioA0DqW+T@1#GM#Wao!KPsK)*uM*{PAoxp zv(L#7?m+E30URtbZ_dejd#mHzMk(_rNJ?>PtN95^!$ z&^HCRoi=9gIPRWV^~|z5UBX?u1jWlptI-#4ZhyuEFDQ4&as*-Yu?&F63L6&3a)59p z=57|PA%U-BttIvYIEjxKl)A$%_ygY6FOf~of2cqu&UC+7P#SpKLIASQCq(-TUbIY4$I&J*fT*gDdiIx43DUe| z4YeLYxhh1`=CZ-N_TTF+QfLo*s1cotC(e~wwZp;-V)tp&-gUm$32gNf6x&t>p3^!3 ztl(d;>jJ>^Ojjwjc?*PH0GQ`6C-@zQj!d)R&0`!;p7YBz8qfIBHj17cCog6UZyBU< z?v8N4Lx>K!M%3()u=5{@10jgW{SYq!xctlWLU1#F%$BRP{OMgH&>;)Ty2N{$nvb1< zU8?r0px>a=XE~2K=fIo;({g}Ls1#bD8^_w02ApY=JnMMcRM$$1haRaP&uKvnAZggr zGE1nTZ~)RcP96KjOYg;4+IXaenys+r;#Y+59G1ut9y#KTHvq?&7@_+B;dpG3)H_sP zQU62Ar zmG&wj_ms`699J(u*L2|~cFdaq)V1o1r_1YdQU zl>U>S8RU~+4pdq3l+ycbxOIlop1jXr?foPy)F>@?0pkHr7qMsVv*E%_Cr?wkm=q~< z2Im4%L9qaN4?Ff5kP0@lv^<09sWL1@68s9zz0BX=(vuelIe+uc&iB%xlhz}BBYF4! zo|b0m$za|*bH2R{1|FnG-bmAv1n0+h>H#93-cNhh21DnUdkd(=Jv};}x194hF5S7R z(l!I6|i2>2?3r{AtdCIR~By z2LR?Aq9ZsDVE#n%JY^V@_hJISPB(u9qYszDGX^xkYszcS#t(TA5ao^{w#4hF$*I5)j;ERM7aTZw&sgo}c zl$QGiLy+!9T)!^J?<7MTFV`cXGO+co6g#W2l)4BtV}+Pybh5JO{1; z3&rD7yU}1XIlsqo&qwLTDan*!Mlc{SZ%$HPDu_NMn$F32KV?$LZ=5*Gjk8q;e0IUIyp~f=7FcxDEkc`tn>lfclAY&G+9g>F9ZNY7F+6vwc@H`~w5icdwZq^jA zYDZkDHiyXvXcQz~2S_UkKLk8G)h;6K)k27e;VgHprZ}G@zzfjlpdiC1Am0h;uwYW_ zBGPF5YxQ^7So%7#pp!gSQF&1+KQ-t)Qle2mpo;o@I?gW?ZKUDaX}<|~7DJ!^O6@Ix z%a`zM9e|E)d=|17u;G4y*-1BtuGe2Kb}*OdB1hZqs^iSe2`tM}?5xU>RY$T!cxD-v z63P;1H-!t(2c_sP4g~1-ALo0+k8;v0FwOwJj1S6j4Cgh<884UFa3_Im!%MeubQzqV zq@I&PkN#_!y)wO~J9zmn#+K2^Jgqm51xm>^t$1y*6Y4CU7J8D?%FC`r<^HX8-8vl{ z2ahyq_&WIhwEQ~VsAyZYfev+0+oRs5A@Ml;%AW`Hn!7m%<{Wr(98k*K!GekEA$>E1 zPcHxIa!P7A^^0#8Ev(A4_P!}PNW2#S*bFG$!DW(^8I8^y6ecOF8z{E`9{!AQFYvp?2Hh`0-Kdi{8=sWfr01NQKVh;bCd8I0l z`FNLJ)ADX!<^1%sT=|~MIe0fW{mOTrP1mxx0LbY?pl9Qc6z^Fb6jPh!n|yrm_sWQ> zk!pfQm^%lmb$RZ9zTZx#AURO0OzFCAE8rJT{9ovMY16dO7{9O)$Wn^Y~Wu)4B^{p zye0>mVb<^{S%O~m%+)y$2|Rk_ZeY~fi&A_ycxGVlz3*ZW@Bci5r#d?CPa2fxVyJ3l z(|41r>O#^XpZ)r9|L{XN(cnwMyQsB%6yRL7pSzs6s@cgJ>OfnlzMQEO@ChIVdu1Rw zn5pmc zsd=@MPWrUHcT4pcWwB9|;uwC9s`AjHQDGm8e6BnKl8>xTIcSSQ)KB;78jIqEsnV7ezstffKlY#aggD3?LG@BJ!Z*iqqgd5q&_xLnpz>FaO2tlE+n z6`4=v{L5(=C-Xa(d)U34zj2h7A5spRZ24U|?WgptN{T=`A3UUi4*f4c`kyeeWfh7N9=~XWqG70@h*9_hmlh@~GQBEKc_8Bcfrcj`ov3mftI<;XOa+ zn3<1*_xyg;UHVMh1J6qRb5!sr?Jp>|l_~w!S?rTq-2v5T7*$_(XhQq^{x$-0`Q?Zd zcWy`aQ^vb^<%{maY;Lxs=^Yikzqem(Zfq30yB>9If8+C@J5)=A3f2jluEEX_vF3l~ z9GG+9iE+Tql>SqtCHmuR2bD7|2jfboxgR#nwL#u#aVn+ORbq5G)10SDT-B33Y=Unb zd|JG@{GS2zt}@AOhNN0)xJT2UM0*-Nk7gh6HB>OL+JQ8yR;&PL7eo5(AwlX%tLk#0 za)XGez6JNH!>EpNxA8%!$>`Sy{YUe7^1oEiIY9NtO{qojO3D6k`V66)w(W9Y5DOly ztZ2jhQth?iU43zdx=C}H3ma8^?!hm0@;!voM>6LzI)K97NFmRsJ%<)fi z&pgWa)1+nGS<-e|T1Ldrt<;|4vp{{hI$DjzFb$f_B27v#UgJ7M73QRIiU8-%3B+wG znTKN8=xQJYQyz&xEf2fkGt zP{jd>vtx?D{JC-xT>+TyL4Pw@mtj03O)9a)FUO=PIwti8dLk_TdiB1VtEUiIKWAs z5_Wo!J4F$OM)BaJkCFN>hNNG$8?B@y>o6dNo|EDWA5EAu};M;M!cJzDXO!9#0X8=%v=%jO@aRlYwMF*;B2y6$_5vU8o zb9jNTpjUSsIJa#1N}-Pe{_JH+Ng-<(O+KB-rU@o zn_Ut#>tTVZN~btz zZiO^>g~Nh;FECW{ox)Dia1y9=)8=(mQUb`PO`&dNT&Z5e9{FbhYK5>zog2XFVH(jz zg3n1JJ!-T*8vAU1Iak!eM!RY(*XvrXsR7bI3JWOL7r)od)^Hr8gj9VS6(Iq%r=qHk zGC2?XB~iV{&9R4k*-&p^^&tJ!>o;v(hc6IL!h@f(BgDH%IAIWNGzR8P9%{#I{%X@pFMpaI<5XgySV zrbhJrwz}@f0t^Z;BIWXtIljQ(X+D>aP?1G2nk3RhTIK~w0ueTc;MRi#f z4FH2ue=eoy#nM&$EYIu)$Z2^a;MNKBw1l!)Tty1I7y8T>QHjtLpZ3XN}L^?r!VO|g*gYl^&Ak`azZ2$^Xif&2A{JI%HY>N z*kGPU&u!>HanyX*6sZU;-?>Rt9rSFC zMZXnHEPk7v{PWV;Ec;_;^0hgHOejK}{tvBvGWUBuak*o~VX^V(VezLw{p;e**SCuG zOZ|=$)tE~{7D+EB74F+z#gp-S9{StLf!uj!*jpGq0>Fl;!HbSA&zFPl*1_FaR4i0A z9|&L(m~)b8`w*MbEIQ&Vz%x=RRcy56;U>(3b_Ek;TY$??;^a=H<@#F*eG`h$2;pZy zhCtTI%#D>*4!CloYLZ5$%tEb)_NlG)Jn04eg@;;AG3-|BYe4xA*rd76FTVWnPg9T0 z7FKCgK>@sJM$1+vFE!-xh1t;U_k8P`jIDIhmJfv4ay z1LaY`*LWGkdpF#uu+o-+dj|CSCuSA>gGGI5qXUI8w3>)5u`3;h7+;!N$hp1fB?CYq$EYk$OjjqhE{&QB- zmQy%xzU%%9uhagS7?}U|Z~u1jdkLL)RxeyE{=q-|uZr(~|NEW4_=~?>_#ghq|EZ|a z$%pCTg(^!7&a*ARX&Eq&Kj*+~IAFKL!bL0XwMc1B+g*_$a=N8XTir8rOmuOKeRUBj zbFzy)v$W4TAwW=z1BK9IL5<~lDL3F7Ku`bJ5E^#E`$K3c;1himG}U|KN zBvj)aBY6l zeOwubr88-G^T~^yOk2L^w4@``Hj0-nK1?UmZFtM;m7RRZUj)4Sg&^mZ!#t(CwXy?w zA^D!NxfplN;+Bg1l#26IvmMpHQ&sode}}d*#Yix+Y!d_XQc9Jtzxt~9FaF-&YrXx> zJH^t)ey}+WPtP2cB1?D;}UO1zK-u!WVPQlOmZ{qF(8wfe-zPP}KD-U*B2wn3QsyEA( z%K=mcqi!@zYAH{j?%DFi1qvo3TMv=I-7j8T`f*6jJwOL%jcDTCZs<5c~qlrQ% z!?dhnqbid115P#7Lduo;3rSMVd!0bMfizv-sRonOVa|qs(&VPoE!1f)ExuNCDm%sf z)>lyQBXZ8_Ib(1xcR4|xU{Qc404#&Pax(V&-1p0KYdOrg&*o=Z-UXY2@^RCYKEq0D znWmHSHm^}-J1OoY?*-C%p<|w2((7F(&C>3b)olDyARlrV!wJg0beTuP=+9u?Jn~}2 z{GS1LND=QJI_`8huK+f5S?~;y_8gMHNEGcOcie6f$~>iQMVJ{fdSYPy3%~FS9VONEyL{zpaqH__QrtN_JX}Z{K%u-ueE4<8gy*n!hLUXYR`@ee zWkbWxJ)bWQSf|!)sR@r!0H75XL)=_h6qo6&rHP>&+ezX`tMNCJJ0|~{Tc1Sy`o(vO z7RSxE_U=q^cn;~gAyP2Acbg;YF^PwS+-LKxEUJ(DJUKq8?tTNo=o}xBmRp8YRx3*B z^JMik0S}_iCQChq?i9Ptd#GC|?XRNRatSd0kdkOUKl`#e-iac+9SwirhaP9 zGEskk+GRGX3c}S7Us}9~TF=*^cYnF|I(?-cz_*4p-0&_|!p=U?_gfp-8GjvKAw7@H zn8J3YC|-h%Q;)0;GWDeE(R%o1+* zXW_+QO(nnZ4Yjtx_k(GT|9fI!{yV?(J2A@t{_p?(zrT6&=70Zx{$Kz1Z<5^x*L&A4 z*MGoW>W(ypq{fn7*mq)%BhYzpYm_v0FQ13P%*)W@fOTr!j_sn=Yl4uYy$`o&<4E^7 zDjsyq)dfa&UUj1RJimZCN$5jIJTgKRBZ2b$qJHVSfN^xd=}Jn^)eE0U^@Mx`<;|^6 zp`%$;S6@ybefR4G(rc(qoJl7vGt7dW)|m#9&;u4I`?rhw#qab?B$kJm-a$#~m>(T_ zPD=psQ#s-<#VeQa0;gq$8lbTz|7GEq79%rDO9J2lc4p zp=5scV0Bzt6aQ4zIbZ{<9L$pKOSM-6oIh-Qf;!K;VX{y_zSFuB8$E9>{V3F51iwqD zz6iu!>?Y}Wn=oGjK(0p{-$Q@jxT?N*0;(HBQ<=x5b9lJ2^j_MP+&2pk9@=4naWV3n zEFW7CJOR`#2pk25XVMpkl*jy5{Ms~ylSXOPJxc&xP@E;)f^mVe_hC-|d>O~MS<0>S zJOlP|KzTR)7!QnVx~Zx2ruQ(6itx)J(scy;j!$8tw-W>Na_YBl-~ONZ?Nz+@-go|+Z@&EKcX@QA zInpyiS`O{epYz>F=fGX0Hk#e>>KqhjUW6_OtW)cDe3w3FRqAU+bNkccaFHX}Tj;`S zE4&BHNsS54{-BylrFH?J>9vY9`&LNI1?7Tp0X2FXk-a7Y7ivtXkxm*M9;Yk$E3I$s z-wTzR3h4%z1X@7mA@fm!%0l(6TpXzj9;XouM;}04?FK#aVA{<=|l@fuvw#QYA zF|AOC@lLzDQhz&i+0A=e=Y(q>esNe_pr4Y%KK9e^H$OxYNn4AyP;QT7O~wQpIHYH#0t#qYhKTSlOKX*C*1eOQUwmP zbEg94kU{k`PSQR0clOaQ$Agt)e8RUVxikc@{bpBw&VJA3oqD-I<4?!c_N(Z*EX$ne z;6NU~)YG$Wd;M-K{_vq5TiO*4@2R0a(m_g?4)HN^SEaxS9bM>la|bCl;2kR?o!z9< zxF{$O0IrlB$zZkHJ=L8)mJrewkfW;7I=Dxki$x77c?FY*VM?&EjTHZ3QCoYH4vIuc z(0^=tydLEQ+k6EGwGp4Wv>`5d&KpZ3D*4neyh?f2_*v@wqtboMzpL7_`4M>lxz=7I zM6R8|z{x+U(3RhjuY(QoPwO5}d%P3{cXdGBdi7$_sx*rA`YT+oqRz4jSY6>DAWbN4 z08n3zLv6-O^`ua$den;5+6%>En|!+)O||#!LLsTPjN;`1pG_% z%D)2@O0-$5basmiwHw728$-4C-lAI|kQp-Y}B z>9%Zn(P5dg^n2X$<+>s71*PV+gwf;F(M0WmswLZYrWqlRrp7N4IdH1LneH;xv<-`z z_|JiP;>%QjqYpZ!4!GDqHj|ngNSOM1%x5wWT6KW)h!qQn^wcdxpXl6vef?MgBhK^r zXNA7l=yrzZvjlUhhjG9R+*V?;_l>T!h_HUA}hmQ@VKEfXTV>o6MKv}5b zkZyhbJ=nk`I>65q&o32LlwiT(C_5J`>e$~wl?Q3D3zDf_qJ3geWq#dCSAAm#$+%On z08g;I)4UV<)}f~y0Y}#IRFv0HV`&z9NRQtPl@S50lH_!-kb_pgMNXB$*pbqr^a7yP zI(l^YNi0s3oZmkDDO6NK60SX7-l1K_ zLTB8zZTj%Sc3i&7*$#Uaa5{mOrMknyorRxts*7#j0#AW!c``qXJjYE_nxs!@b%5e= zBX+}C4khQNAvl-MrZuUvI*k)FOwy+e%N3F1&!jmY32%>X0K%4RE+pzcc%7;(KAt{@ zNbcM9r^~}UjvNutlo;9#HZ$Hho6W@8D0FQ)i|ssh-ZHu6=LmW({egb7jOQb9S#>(P)7rd5pOYcG0bSg{%#<2Q$9|{1x@geco zirR(OSQv0reSlc=OCv`I>3qtoI|;=wUCwKq=cMo6*m$2JsamrZz_;=6X8`pFMSbn9 zP?K3e{YF)s+J#pLkNWoAMMf{^ zSPv{(I3?6Wi5|~utOO#EY)k|4OER!vBXrXM|s9p#@50ILxtK33oIUoIg zQhMXPcUo?RZn*~=?X?~iZm@LI<(-4S2&*UpN=h|eax)5L8}?Mv74*V4IBaRReJ^zE zwe`Ny`~tB460M?M++_iwT92DPyYLUNss3G_<>8p^!e9|J#R}T&1CRBC0jjf_ zgwow7DK)p@XHYI^om84UP>kMB)3qD|>wNjUJ6`p|9ruuVA@8OE#Xl^sOWANQ>wX&G(VWDuw+UacIsW?~{tj@#&5KR%vmKO_P^HuX}|rG;X$3I(@(K zVVs^R=uCMEqy^&nc`z;WaS`Hm5>Lu;Ps@v(-zm~b!`BJ*Sf;uA z?AV!`!I0Mzd*zmxTct(HlJmjD4ya63pM9!%+c|4v^@GiQ(XcKJN%7%&dEW(LgB znhm`s3DpfYwrIGNEx%8pp6|al`m)6QJeBSFu9@kn&vlq3=97)p-7ebi!_yNIlRX2y z12+=${%~L?s)pK1bNdq{(|~exx$Rk%ML_HxX6H~XsiD3hK=&DhjB3q`Q_@2I=#aPe zZ-o@Rx^e?j7|~rUKu3N3?bO+o><7(>g8{+-G#&KY8=HU4eGA;Z+|_$W(uvf)eejU$ zKD53P8#}dTJOF!L-O&}@LbM1AD3Vm@ZD0!qs!@&D)uyJlmPEzdg(=cQj z)CIlO0F^D&SQ_p90OJenBT!XBz2$xM%nhF;=i}!wns)fYnCt;ZZko*YzSnD5=)pf* zLdl@{*8ZO{f!9K%B&lamzk~c7o!QVgrlSgv)>vAY8g!$kR!vmnF>N<#Zntisn)2tx zYUPF4plX;4)tm8LZ(oSTi+bp82z8Nad^fXd5ru}$Z}vBko|`h|q2OH$FR2Wt>xfFs z{j#wTUpx-^+VEHKal3ekjp*$|RJU5Fixuw-E&Fg!>NN%w=yF1gNiq8kj}gAc5flq* z1?CyN9uwtMugphK?%ljk1Ijba-UZ~ASr>drkG;RXH=O$H%gf_S!gHFvgr<0yWFast zd1+9woBxxz6uirmNg@sjYxe4r@3G-?4XZ59VO4}PakRrgcyoM!)fP|od~tccIdp(M zy`Qmg@>i*fdR1KV>qVH+!_0uWgQNs^x*e`Ze3#Jh#xu$?_;N;tc^b)hRISq0aObF8 zPlK1wM4sI`42ij)fhKh{^dov`lg96bPlO08(~~{NCqAWPhOxQ*F+lus50o?O@w$#m z$(w-NFN(vB59n+xmh0<;c?}HBL{}bNdik92K$hB9?*Nwjr;uh^luBpwYvoZ&FzQ z$8sz2-e`V?RE{qO(6CH-qx`XmGi8DGA>lSTeCQD%m{T>;!+&}CmpJ8<(@~-59`w7Y zaPcBgHkQ(L;|Tg)Aubv zh)p(zzgpCLeKW^7`QDR|3PN?`0z#$a0&+pC*I7x;1^(H-x3o6Nv=x? zAT*@PsD0GdUgwp=v6H;-89+3VcT5ql8crP5RHEY(J`Vr@RHgk)#i+`B6jo!jCPN`i;PG_J*~YWPL6^YX zGy|ARPj=bQFl?X&6pxo~j(_w%P2I{jkCfLbsTLp$SQ9|4FhM>H@a*QsCK7a2CJZ?n zXT2yjzrjg>e@(vI0g#m}Us-$;aQ=RP;WZ@G>!^$zAPv@P%Uw2D-e99+GFd3?1ND0X zaRP27%?}zM$Hr4_jq6t~m-9oKBiaGFmr=P8uD*anmvqVFX%Ski-2)WSenk4`~Mz^{*B_*<-Z+al$!5R zht*D6C`iZn&V?HE2|}aILgy&(Sx(e20&_uYmMROHXXT_Nn-28Zyi8vZJ*ktvEO9q2 z(=feZfP64*0efB;$fHsD%a6>rGA{?ydqTf0W{;i;@{Cq$+!;8NQ7z>Kq~xlpC_Nvh zrsBxDgH&D6uHExJE@z3LWe6RnYwwlaQO<6dVPxw!DxGe$=kit-w)M)FDm}`R%H4Z& z>M}YD%+5*Ou-Y<*+=+bj_jzy>WCae+f%$o;%%f$nPOaN$tfSiW(J1jNNyHrbX5`9J zVERyV2b;}UMyX%^ev(!{24K?=p`<*y<2vOyGz#`nL0JSW?S0czMd<@vhiuDR>eUQb z+A$Ax6c18z04tB^y=;@ew$;@G4~cecT%80Al5cl&>_SzbWO;Roo{*wD_P7yus55+t z_90;e{!MJd7l6daW~U+H%U*){vXH_}pmjDhYA#R}lS4Lts(w*J%}1NwEVqt?FQ=8B zQ~4gJdUzRJ4Ad|s=!Q!zl`OFU@ZRiPh>e#U9CjoqUPOYweRvy*y;A2zK>G(Ffu07O zo99;Z7V+Ohl8yd2OE3M(&h+(5pgqy<##rr~dm^Y(eI@wXD8Gt%MRo8Gk&G|0xRRe) zC1#t$h%`MZ&FZRaZ#?lkw}W!7qnADr-oqet@?V3#7h>`9+R86eXB-~Z z1^=IS(xN7s&IDP7t`@^8*cod2HXJDQs(Sp`d!1cIv2H#1GU|4CjPPG|9xwK8{ z1|{yMWPaquioc(hgVwTCbzCwkj_I`6Svsw~X-lv`(#C>v-I{jrJT8 zT%f_Yk$Slv_dsG0qQu&Rh^k1L(`L(~VCQHG5-=+wy3%a46#1oejgX}2RAzaB z(@TkPL;ZUJc;HhrUmu#N#5cK6$H0#I4z|7{EhO}UUu4rr zh}S}*zmC0d)i*Q?s1!M+Urw{1)2JsU|Hez@8H}s6?nabZbrGdeZUhxzFJm6@-oej` zTl;@pe6{~a^uuoud~61AzJz{!SZRTK73$*V(k~R(>hJXU<|4z5t%B@93H>NKZEs5H zchOPT5{wH1>q6BULAdHGE)4c~f3M+&T@C`{pQ<)j7T*Z{^U-M^eG}sYsK)a4BybKe zUgJ7JuHaQEu~O-1@8d=u63+O8pj;Dz;{xe&k}epsv|AcYXVSC;*_KUr5?{>2%QDD| z952OwVt;$}3B424WE9UB(H2fyj}w$uDY>o=`ctZI@609@<3;I@Alxt>xVOnLdY}Cj zw^5V1wV$qUcz!^&?^107e$z6}xpJjG+N-PZv$QyGGcNNCfq9uWC!qmX_Q>UVf{k&2 znwk8xNNbFfTQtmWk!F(aJdvl%0qfE_wQkGJb{wigiwo(-b(Xcdn|=mD(Hd^WFG5aQE=9gwzpc9Ga!abg|M;JUTwkFAI;z z?=Cv`J8^=ln^M(#Z-!*OrR5|3`F|3Z#s^SNdG0GepOlz@IZLyZP76q=IO7ElW` zGLZIpQkQ8lO(oZE-ke5)Z91kQ;67<_Af4u6I6=APn1q*;mhqVI0=6WWnS^)r6vr^) z+Do%5KWD#L-j$ZST+fnkDIS%lQu0g8gV&cjsJC^B7r3r1lYo?3002M$NklohWqqTbUmjP3GM{)5;Qe*un&tzjo7##@91JIjD z_HUo~Xt4N5{CTM`%i*V-^?Ijxf2&)revwDBY5wM!=YVzli23flwKRA+_>4POg-)?s z-Dl|>ybq8XVqK)pAwKGECouU08yp~Mfy0Ap*egGL@Fz%-UyM^uVNLv2)kT$*X`OoP z%&v?N03LuD1w1(^fVB$(PdE(|j%A6xQx9JlG|?EFUj^;G!N&@9GCv4==`J|Z7l>hb zGT~D0g*pdDDbcqfL^|W}-icTJ_Hw!kEh+#%Ho2m{lBB!TFaOG8W-7d4mFC_L)e`lo zwVdJxN2R-_BLHn;T5^l880xYs_4e|l&1AVmt|r}UH%RU-+>4E$s+nklP;jgjlx@JL z(&F8PyGW=xK&panI_3y975L!rqu7-Cc!i|09EBCuTpJaZ%N$ewdH`xSmI`#;1S(KB zyo-a7)B}jWyYgEA>ZRBKt7?z>_nK5}p!U;5xBMb>sH&oh%lq5^NAb$iF9Q~^+0SA_ zXZcclS)dHBg^%TN(NX5SVTU=tC@85{I)5Kij4i%o2*5WmXX#fA`K+WqeKGN~|2}0| zg{JWrD>>fM~H+qUgv66Xj~rHpQ@*ZEuNvsP7Z0)WLAXW?3G1{9PF z&c|V~G)Rh)b1%7jfhK1^wi!viV87XB!#BTGeX-ve$NTf_EQ5s@9K&;&ET4QBmybE# zN%u6eofK-U_m%kEkt;%C{oix(?4PL4p7-O`ga1ro2KCtt!`*-#A4wBp!p)eK1tO}1*CLJw#{1;h+Zx-)~YYTlUghe zlt5oYO+;R0knX0@>x$V3M zRnc~jCOz=W_6CjAOB}+-+cLKX0-yX-0Gy@7f?|<@0DW912+ly(w5A2+0_jtBnyqqi(Dpop`Z6B;G!qO2N&UyX9x(6&pY+p?)cDhf$N6N1^NNt7M9*8`i(PM zUpS}jlb7p5vJQ|wjrxftl*Nv7b(-D+s-N~at8ig2k4{gZ0?s@Om^8FHOP*RjnW38< zFsEXy-2TcU5o!a}&%1-y$phF7OLvAzSsu%KZKYHEj4#{=jB~4u1*x28nDIR6Gvh$6 zJL}RqwQi$LljB(3jzF5i6No7lt4uT6Jytq_A?6B^)>c-KNRK?4{;`O&^H6wTevuBzkK-KcYSdR712Bc7A6^q9;78!xZ$sAKzGm5^q!U+gyTBmI7e`pY)} z^h*I^mloDS`u+969|17m0=zGwzQW!G(($BCb-o4uN~i_q>Rt<;)i)R9CmnBjAe3nF z`TCEF_R^2KYA7x+`pogYz}Lmca;WN9%66n>`T}i1@V$c%BHhC_vpGImh;ftbDw{d) zqqr&!O5krD{BiLbmQmL0FJnvmqX;WNKV*Ss9?7ze)>}4Xu`#mViJ6^YUHG_&aii<2 zE58!pe69L&EC?)*{M1Rib*36iiv?+|=2we;d2^aW?^Z87bNSHN-7Pv7sXyDOC}tXG zTvb|(pTV)}BTBzda>fw^3x-bv%8jqo-Sk`(NRM<)ysnODdQ+@cfNSosDz5hb=dedY9SX;7g>X?CO1pBN}B3~E1hpM zW-e8dTFPnj$;DRvC7Qu$6P#tCPnDLv+}i3o3uoD=cSk|r<+A4>r@hY(nDhBweW|Rr z5|mp(($5tY|>>a3l4F!7RYYwv3c2x#N9PEaxZ z7>RRTlXAEKz&=Rd6b_KANkb1F3hOKHOHV=~; z$>rLcL6?&E*H(Tl(p*`%R@9c3q46H&@kG;Qc<>;k+%8bA)YF0^=PV_I&dF5Wddum2 zWclQcR$b()(*7+LH0s>bK!O*l<5>HGav!YQN6*!KyB_0isnvaYs-supI|b z4<=|F0qH(cgfcikX?_(1=R8j%SuU%>NXIl~uuRgP!L;;DA}!B6vCJ8qXUVyJHS6Q& za5KK!cC5<|3xdidUo5(G)ZkeKI(ONT+33_T{n_EmSkmjhhu-#z!l7u(|D%twyPdJ zYL%5A1o|nz2fT;WP>?>v|I{UU!U;Ln;@d@Y?+#M!PZBNi z#LcACLlxcg);=m4(664kssz?eq5)XQX{2rrHGlQdHMbbx9-tnJ4(@&xXk%YI77Qa* zPWm+Er`%?$(Ba9EEx<>uwgdppZ*s_S?ulk6fK@h)E}`=-K;Hn!dZhg_DlTq<)ZCy_ z?wuxwHlZgi_*9Qwl@N8d588_W<6BI~kC2wX!Kd

a&NR=d??{03GNU%l!bR9j3> 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 literal 0 HcmV?d00001 diff --git a/blog/images/20241014-profiles1.png b/blog/images/20241014-profiles1.png new file mode 100644 index 0000000000000000000000000000000000000000..5fb44e3086207582d774d031b6c3f5686090c715 GIT binary patch literal 373107 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 +`jRHpy`l$0!kLiliGK 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

)*mJfr^3_%V359|e{832w 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

*mz3lv@X{-b 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 z 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 Qvpg9%C;VHr@R*Ur@aHZa|1` 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 BbzT4f literal 0 HcmV?d00001 diff --git a/docs/SECURITY.md b/docs/SECURITY.md index 0885e31725..00c47a614d 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -8,7 +8,9 @@ revision: 23.04.2024 While great care is taken to ensure the highest level of security and privacy in SimpleX network servers and clients, all software can have flaws, and we believe it is a critical part of an organization's social responsibility to minimize the impact of these flaws through continual vulnerability discovery efforts, defense in depth design, and prompt remediation and notification. -The security assessment of SimpleX cryptography and networking was done by Trail of Bits in [November 2022](../blog/20221108-simplex-chat-v4.2-security-audit-new-website.md). +The implementation security assessment of SimpleX cryptography and networking was done by Trail of Bits in [November 2022](../blog/20221108-simplex-chat-v4.2-security-audit-new-website.md). + +The cryptographic review of SimpleX protocols design was done by Trail of Bits in [July 2024](../blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md). We are planning design review of SimpleX protocols in July 2024 and implementation review in December 2024/January 2025. diff --git a/docs/SimpleX_Design_Review_2024_Summary_Report_12_08_2024.pdf b/docs/SimpleX_Design_Review_2024_Summary_Report_12_08_2024.pdf new file mode 100644 index 0000000000000000000000000000000000000000..aec222046c77153ae685d6df3cc93d0be4162cf1 GIT binary patch literal 920033 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 z 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تصدير قاعدة بيانات الدردشة وإعادة تثبيت التطبيق.", "comparison-section-list-point-4a": "مُرحلات SimpleX لا يمكنها أن تتنازل عن تعمية بين الطرفين. تحقق من رمز الأمان للتخفيف من الهجوم على القناة خارج النطاق", "hero-overlay-3-title": "التقييم الأمني", - "hero-overlay-card-3-p-2": "قامت Trail of Bits بمراجعة مكونات التشفير والشبكات الخاصة بمنصة SimpleX في نوفمبر 2022.", - "hero-overlay-card-3-p-3": "اقرأ المزيد في الإعلان.", + "hero-overlay-card-3-p-2": "قامت Trail of Bits بمراجعة مكونات التشفير والشبكات الخاصة بمنصة SimpleX في نوفمبر 2022. اقرأ المزيد في الإعلان.", "jobs": "انضم للفريق", "hero-overlay-3-textlink": "التقييم الأمني", "hero-overlay-card-3-p-1": "Trail of Bits هي شركة رائدة في مجال الاستشارات الأمنية والتكنولوجية، ومن بين عملائها شركات التكنولوجيا الكبرى والوكالات الحكومية ومشاريع blockchain الكبرى.", @@ -256,4 +255,4 @@ "docs-dropdown-10": "الشفافية", "docs-dropdown-11": "الأسئلة الأكثر شيوعًا", "docs-dropdown-12": "الأمان" -} +} \ No newline at end of file diff --git a/website/langs/cs.json b/website/langs/cs.json index 8429b5d8ad..d10b14a85c 100644 --- a/website/langs/cs.json +++ b/website/langs/cs.json @@ -250,10 +250,9 @@ "stable-versions-built-by-f-droid-org": "Stabilní verze vytvořené F-Droid.org", "releases-to-this-repo-are-done-1-2-days-later": "Vydání v tomto repozitáři se provádí o několik dní později", "jobs": "Připojit k týmu", - "hero-overlay-card-3-p-2": "Trail of Bits přezkoumala kryptografii a síťové komponenty SimpleX platformy v listopadu 2022.", - "hero-overlay-card-3-p-3": "Přečtěte si více v ohlášení.", + "hero-overlay-card-3-p-2": "Trail of Bits přezkoumala kryptografii a síťové komponenty SimpleX platformy v listopadu 2022. Přečtěte si více v ohlášení.", "docs-dropdown-9": "Ke stažení", "docs-dropdown-10": "Transparentnost", "docs-dropdown-11": "FAQ (často kladené dotazy)", "docs-dropdown-12": "Bezpečnost" -} +} \ No newline at end of file diff --git a/website/langs/de.json b/website/langs/de.json index c8d58d0343..c57d059fb3 100644 --- a/website/langs/de.json +++ b/website/langs/de.json @@ -245,8 +245,7 @@ "f-droid-page-simplex-chat-repo-section-text": "Um es Ihrem F-Droid-Client hinzuzufügen, scannen Sie den QR-Code oder nutzen Sie diese URL:", "comparison-section-list-point-4a": "SimpleX-Relais können die E2E-Verschlüsselung nicht kompromittieren. Überprüfen Sie den Sicherheitscode, um einen möglichen Angriff auf den Out-of-Band-Kanal zu entschärfen", "hero-overlay-3-title": "Sicherheits-Gutachten", - "hero-overlay-card-3-p-2": "Trail of Bits untersuchte im November 2022 die kryptografischen und Netzwerk-Komponenten der SimpleX-Plattform.", - "hero-overlay-card-3-p-3": "Lesen Sie mehr dazu in der Ankündigung.", + "hero-overlay-card-3-p-2": "Trail of Bits untersuchte im November 2022 die kryptografischen und Netzwerk-Komponenten der SimpleX-Plattform. Lesen Sie mehr dazu in der Ankündigung.", "jobs": "Treten Sie dem Team bei", "hero-overlay-3-textlink": "Sicherheits-Gutachten", "hero-overlay-card-3-p-1": "Trail of Bits ist eine führende Security- und Technologie-Unternehmensberatung, deren Kunden aus den Bereichen Big-Tech, Regierungsbehörden und großen Blockchain-Projekten stammen.", @@ -256,4 +255,4 @@ "docs-dropdown-10": "Transparent", "docs-dropdown-11": "FAQ", "docs-dropdown-12": "Sicherheit" -} +} \ No newline at end of file diff --git a/website/langs/en.json b/website/langs/en.json index 89bd17f5d4..a9b31f2a6e 100644 --- a/website/langs/en.json +++ b/website/langs/en.json @@ -30,12 +30,12 @@ "hero-p-1": "Other apps have user IDs: Signal, Matrix, Session, Briar, Jami, Cwtch, etc.
SimpleX does not, not even random numbers.
This radically improves your privacy.", "hero-overlay-1-textlink": "Why user IDs are bad for privacy?", "hero-overlay-2-textlink": "How does SimpleX work?", - "hero-overlay-3-textlink": "Security assessment", + "hero-overlay-3-textlink": "Security assessments", "hero-2-header": "Make a private connection", "hero-2-header-desc": "The video shows how you connect to your friend via their 1-time QR-code, in person or via a video link. You can also connect by sharing an invitation link.", "hero-overlay-1-title": "How does SimpleX work?", "hero-overlay-2-title": "Why user IDs are bad for privacy?", - "hero-overlay-3-title": "Security assessment", + "hero-overlay-3-title": "Security assessments", "feature-1-title": "E2E-encrypted messages with markdown and editing", "feature-2-title": "E2E-encrypted
images, videos and files", "feature-3-title": "E2E-encrypted decentralized groups — only users know they exist", @@ -102,8 +102,8 @@ "hero-overlay-card-2-p-3": "Even with the most private apps that use Tor v3 services, if you talk to two different contacts via the same profile they can prove that they are connected to the same person.", "hero-overlay-card-2-p-4": "SimpleX protects against these attacks by not having any user IDs in its design. And, if you use Incognito mode, you will have a different display name for each contact, avoiding any shared data between them.", "hero-overlay-card-3-p-1": "Trail of Bits is a leading security and technology consultancy whose clients include big tech, governmental agencies and major blockchain projects.", - "hero-overlay-card-3-p-2": "Trail of Bits reviewed SimpleX platform cryptography and networking components in November 2022.", - "hero-overlay-card-3-p-3": "Read more in the announcement.", + "hero-overlay-card-3-p-2": "Trail of Bits reviewed SimpleX network cryptography and networking components in November 2022. Read more.", + "hero-overlay-card-3-p-3": "Trail of Bits reviewed cryptographic design of SimpleX network protocols in July 2024. Read more.", "simplex-network-overlay-card-1-p-1": "P2P messaging protocols and apps have various problems that make them less reliable than SimpleX, more complex to analyse, and vulnerable to several types of attack.", "simplex-network-overlay-card-1-li-1": "P2P networks rely on some variant of DHT to route messages. DHT designs have to balance delivery guarantee and latency. SimpleX has both better delivery guarantee and lower latency than P2P, because the message can be redundantly passed via several servers in parallel, using the servers chosen by the recipient. In P2P networks the message is passed through O(log N) nodes sequentially, using nodes chosen by the algorithm.", "simplex-network-overlay-card-1-li-2": "SimpleX design, unlike most P2P networks, has no global user identifiers of any kind, even temporary, and only uses temporary pairwise identifiers, providing better anonymity and metadata protection.", @@ -256,4 +256,4 @@ "jobs": "Join team", "please-enable-javascript": "Please enable JavaScript to see the QR code.", "please-use-link-in-mobile-app": "Please use the link in the mobile app" -} +} \ No newline at end of file diff --git a/website/langs/es.json b/website/langs/es.json index 989d89557c..8f4ff0912e 100644 --- a/website/langs/es.json +++ b/website/langs/es.json @@ -245,8 +245,7 @@ "releases-to-this-repo-are-done-1-2-days-later": "Las versiones aparecen varios días más tarde en este repositorio", "comparison-section-list-point-4a": "Los servidores de retransmisión no pueden comprometer la encriptación e2e. Para evitar posibles ataques, verifique el código de seguridad mediante un canal alternativo", "hero-overlay-3-title": "Evaluación de la seguridad", - "hero-overlay-card-3-p-2": "Trail of Bits revisó la criptografía y los componentes de red de la plataforma SimpleX en noviembre de 2022.", - "hero-overlay-card-3-p-3": "Más información en la noticia.", + "hero-overlay-card-3-p-2": "Trail of Bits revisó la criptografía y los componentes de red de la plataforma SimpleX en noviembre de 2022. Más información.", "jobs": "Únete al equipo", "hero-overlay-3-textlink": "Evaluación de la seguridad", "hero-overlay-card-3-p-1": "Trail of Bits es una consultora de seguridad y tecnología líder cuyos clientes incluyen grandes tecnológicas, agencias gubernamentales e importantes proyectos de blockchain.", @@ -256,4 +255,4 @@ "docs-dropdown-10": "Transparencia", "docs-dropdown-11": "FAQ", "docs-dropdown-12": "Seguridad" -} +} \ No newline at end of file diff --git a/website/langs/fi.json b/website/langs/fi.json index 0b82902fdc..13ef20bfa5 100644 --- a/website/langs/fi.json +++ b/website/langs/fi.json @@ -249,8 +249,7 @@ "hero-overlay-3-title": "Turvallisuuden arviointi", "hero-overlay-card-3-p-1": "Trail of Bits on johtava turvallisuus- ja teknologiakonsultointiyritys, jonka asiakkaita ovat muun muassa suuret teknologiayritykset, valtion virastot ja suuret lohkoketjuprojektit.", "hero-overlay-3-textlink": "Turvallisuusarviointi", - "hero-overlay-card-3-p-2": "Trail of Bits tarkasteli SimpleX-alustan salaus- ja verkkokomponentteja marraskuussa 2022.", - "hero-overlay-card-3-p-3": "Lue lisää ilmoituksesta.", + "hero-overlay-card-3-p-2": "Trail of Bits tarkasteli SimpleX-alustan salaus- ja verkkokomponentteja marraskuussa 2022. Lue lisää ilmoituksesta.", "please-enable-javascript": "Ota JavaScript käyttöön nähdäksesi QR-koodin.", "please-use-link-in-mobile-app": "Käytä mobiilisovelluksessa olevaa linkkiä" -} +} \ No newline at end of file diff --git a/website/langs/fr.json b/website/langs/fr.json index 678753d1e6..f907757388 100644 --- a/website/langs/fr.json +++ b/website/langs/fr.json @@ -246,8 +246,7 @@ "stable-versions-built-by-f-droid-org": "Versions stables créées par F-Droid.org", "comparison-section-list-point-4a": "Les relais SimpleX ne peuvent pas compromettre le chiffrement e2e. Vérifier le code de sécurité pour limiter les attaques sur le canal hors bande", "hero-overlay-3-title": "Évaluation de sécurité", - "hero-overlay-card-3-p-2": "Trail of Bits a examiné les composants cryptographiques et réseau de la plateforme SimpleX en novembre 2022.", - "hero-overlay-card-3-p-3": "En savoir plus sur l'annonce.", + "hero-overlay-card-3-p-2": "Trail of Bits a examiné les composants cryptographiques et réseau de la plateforme SimpleX en novembre 2022. En savoir plus.", "jobs": "Rejoignez notre équipe", "hero-overlay-3-textlink": "Évaluation de sécurité", "hero-overlay-card-3-p-1": "Trail of Bits est un cabinet leader dans le secteur de la sécurité et des technologies qui compte parmi ses clients des grandes entreprises de la tech, des agences gouvernementales et d'importants projets de blockchain.", @@ -257,4 +256,4 @@ "docs-dropdown-10": "Transparence", "docs-dropdown-12": "Sécurité", "docs-dropdown-11": "FAQ" -} +} \ No newline at end of file diff --git a/website/langs/he.json b/website/langs/he.json index 72411c8cd1..9cf7ce194d 100644 --- a/website/langs/he.json +++ b/website/langs/he.json @@ -87,12 +87,11 @@ "simplex-unique-1-title": "יש לך פרטיות מלאה", "simplex-private-card-10-point-1": "SimpleX משתמש בכתובות ואישורים אנונימיים זמניים בזוגות עבור כל איש קשר של משתמש או חבר בקבוצה.", "privacy-matters-overlay-card-1-p-1": "הרבה חברות גדולות משתמשות במידע עם מי אתה בקשר כדי להעריך את ההכנסה שלך, למכור לך מוצרים שאתה לא באמת צריך ולקבוע את המחירים.", - "hero-overlay-card-3-p-2": "Trail of Bits סקרה את רכיבי ההצפנה והרשת של פלטפורמת SimpleX בנובמבר 2022.", + "hero-overlay-card-3-p-2": "Trail of Bits סקרה את רכיבי ההצפנה והרשת של פלטפורמת SimpleX בנובמבר 2022. קרא עוד ב הודעה.", "hero-overlay-card-2-p-4": "SimpleX מגן מפני התקפות אלה בכך שאין בעיצובו מזהי משתמש. ואם אתה משתמש במצב זהות נסתרת, יהיה לך שם תצוגה שונה לכל איש קשר, תוך הימנעות מכל מידע משותף ביניהם.", "hero-overlay-card-2-p-1": "כאשר למשתמשים יש זהויות מתמשכות, גם אם זה רק מספר אקראי, כמו מזהה הפעלה, קיים סיכון שהספק או התוקף יוכלו לראות כיצד המשתמשים מחוברים וכמה הודעות הם שולחים.", "hero-overlay-card-1-p-5": "רק מכשירי לקוח מאחסנים פרופילי משתמשים, אנשי קשר וקבוצות; ההודעות נשלחות עם הצפנה דו-שכבתית מקצה לקצה.", "privacy-matters-overlay-card-1-p-2": "קמעונאים מקוונים יודעים שאנשים עם הכנסה נמוכה יותר נוטים יותר לבצע רכישות דחופות, ולכן הם עשויים לגבות מחירים גבוהים יותר או להסיר הנחות.", - "hero-overlay-card-3-p-3": "קרא עוד בהודעה.", "hero-overlay-card-1-p-2": "כדי להעביר הודעות, במקום מזהי משתמש המשמשים את כל הפלטפורמות האחרות, SimpleX משתמש במזהים אנונימיים זמניים זוגיים של תורי הודעות, נפרדים עבור כל אחד מהחיבורים שלך — אין מזהים לטווח ארוך.", "hero-overlay-card-2-p-2": "לאחר מכן הם יוכלו לקשר מידע זה עם הרשתות החברתיות הציבוריות הקיימות, ולקבוע כמה זהויות אמיתיות.", "privacy-matters-overlay-card-1-p-3": "חלק מחברות פיננסיות וביטוח משתמשות בגרפים חברתיים כדי לקבוע שיעורי ריבית ופרמיות. לעתים קרובות זה גורם לאנשים עם הכנסה נמוכה יותר לשלם יותר — זה ידוע בתור 'פרמיית עוני'.", @@ -256,4 +255,4 @@ "docs-dropdown-10": "שקיפות", "docs-dropdown-11": "שאלות ותשובות", "docs-dropdown-12": "אבטחה" -} +} \ No newline at end of file diff --git a/website/langs/hu.json b/website/langs/hu.json index d6e99cee1b..f4a40a02bb 100644 --- a/website/langs/hu.json +++ b/website/langs/hu.json @@ -95,7 +95,7 @@ "hero-overlay-card-2-p-3": "Még a Tor v3 szolgáltatásokat használó, legprivátabb alkalmazások esetében is, ha két különböző kapcsolattartóval beszél ugyanazon a profilon keresztül, bizonyítani tudják, hogy ugyanahhoz a személyhez kapcsolódnak.", "hero-overlay-card-2-p-4": "A SimpleX úgy védekezik ezen támadások ellen, hogy nem tartalmaz felhasználói azonosítókat. Ha pedig használja az inkognitó módot, akkor minden egyes létrejött kapcsolatban más-más felhasználó név jelenik meg, így elkerülhető a közöttük lévő összefüggések bizonyítása.", "hero-overlay-card-3-p-1": "Trail of Bits egy vezető biztonsági és technológiai tanácsadó cég, amelynek ügyfelei közé tartoznak a nagy technológiai cégek, kormányzati ügynökségek és jelentős blokklánc projektek.", - "hero-overlay-card-3-p-2": "A Trail of Bits 2022 novemberében áttekintette a SimpleX-platform kriptográfiai és hálózati komponenseit.", + "hero-overlay-card-3-p-2": "A Trail of Bits 2022 novemberében áttekintette a SimpleX-platform kriptográfiai és hálózati komponenseit. További információk.", "simplex-network-overlay-card-1-li-1": "A P2P-hálózatok az üzenetek továbbítására a DHT valamelyik változatát használják. A DHT kialakításakor egyensúlyt kell teremteni a kézbesítési garancia és a késleltetés között. A SimpleX jobb kézbesítési garanciával és alacsonyabb késleltetéssel rendelkezik, mint a P2P, mivel az üzenet redundánsan, a címzett által kiválasztott kiszolgálók segítségével több kiszolgálón keresztül párhuzamosan továbbítható. A P2P-hálózatokban az üzenet O(log N) csomóponton halad át szekvenciálisan, az algoritmus által kiválasztott csomópontok segítségével.", "simplex-network-overlay-card-1-li-2": "A SimpleX kialakítása a legtöbb P2P-hálózattól eltérően nem rendelkezik semmiféle globális felhasználói azonosítóval, még ideiglenesen sem, és csak ideiglenes páros azonosítókat használ, ami jobb névtelenséget és metaadatvédelmet biztosít.", "simplex-network-overlay-card-1-li-3": "A P2P nem oldja meg a MITM-támadás problémát, és a legtöbb létező implementáció nem használ sávon kívüli üzeneteket a kezdeti kulcscseréhez. A SimpleX a kezdeti kulcscseréhez sávon kívüli üzeneteket, vagy bizonyos esetekben már meglévő biztonságos és megbízható kapcsolatokat használ.", @@ -229,7 +229,6 @@ "simplex-private-card-5-point-2": "A kiszolgálók és a hálózatot megfigyelők számára a különböző méretű üzenetek egyformának tűnnek.", "privacy-matters-1-title": "Hirdetés és árdiszkrimináció", "hero-overlay-card-1-p-3": "Ön határozza meg, hogy melyik kiszolgáló(ka)t használja az üzenetek fogadására, a kapcsolatokhoz — azokat a kiszolgálókat, amelyeket az üzenetek küldésére használ. Minden beszélgetés két különböző kiszolgálót használ.", - "hero-overlay-card-3-p-3": "További információk a közleményben.", "simplex-network-overlay-card-1-p-1": "A P2P üzenetküldő protokollok és alkalmazások számos problémával küzdenek, amelyek miatt kevésbé megbízhatóak, mint a SimpleX, bonyolultabb az elemzésük és többféle támadással szemben sebezhetőek.", "chat-bot-example": "Chat bot példa", "simplex-private-3-title": "Biztonságos, hitelesített
TLS adatátvitel", @@ -256,4 +255,4 @@ "simplex-chat-via-f-droid": "SimpleX Chat az F-Droidon keresztül", "simplex-chat-repo": "SimpleX Chat tároló", "stable-and-beta-versions-built-by-developers": "A fejlesztők által készített stabil és béta verziók" -} +} \ No newline at end of file diff --git a/website/langs/it.json b/website/langs/it.json index ffbb28903a..b593c395d8 100644 --- a/website/langs/it.json +++ b/website/langs/it.json @@ -245,8 +245,7 @@ "f-droid-page-simplex-chat-repo-section-text": "Per aggiungerlo al tuo client F-Droid scansiona il codice QR o usa questo URL:", "comparison-section-list-point-4a": "I relay di SimpleX non possono compromettere la crittografia e2e. Verifica il codice di sicurezza per mitigare gli attacchi sul canale fuori banda", "hero-overlay-3-title": "Valutazione della sicurezza", - "hero-overlay-card-3-p-2": "Trail of Bits ha revisionato i componenti di crittografia e di rete della piattaforma SimpleX nel novembre 2022.", - "hero-overlay-card-3-p-3": "Maggiori informazioni nell'annuncio.", + "hero-overlay-card-3-p-2": "Trail of Bits ha revisionato i componenti di crittografia e di rete della piattaforma SimpleX nel novembre 2022. Maggiori informazioni.", "jobs": "Unisciti al team", "hero-overlay-3-textlink": "Valutazione della sicurezza", "hero-overlay-card-3-p-1": "Trail of Bits è leader nella consulenza di sicurezza e tecnologia, i cui clienti includono grandi aziende, agenzie governative e importanti progetti di blockchain.", @@ -256,4 +255,4 @@ "docs-dropdown-10": "Trasparenza", "docs-dropdown-12": "Sicurezza", "docs-dropdown-11": "Domande frequenti" -} +} \ No newline at end of file diff --git a/website/langs/ja.json b/website/langs/ja.json index 48aaa41493..4adf8705da 100644 --- a/website/langs/ja.json +++ b/website/langs/ja.json @@ -245,15 +245,14 @@ "f-droid-page-f-droid-org-repo-section-text": "SimpleX Chat と F-Droid.org リポジトリは、異なるキーを使用してビルドに署名します。 切り替えるには、チャット データベースをエクスポートし、アプリを再インストールしてください。", "simplex-private-5-title": "何レイヤーもの
コンテンツパディング", "hero-overlay-card-3-p-1": "Trail of Bitsは、大手ハイテク企業、政府機関、主要なブロックチェーン・プロジェクトなどを顧客に持つ、セキュリティとテクノロジーの大手コンサルタント会社です。", - "hero-overlay-card-3-p-3": "詳しくは お知らせをご覧ください。", "jobs": "チームに参加する", "hero-overlay-3-textlink": "セキュリティ監査", "hero-overlay-3-title": "セキュリティ監査", - "hero-overlay-card-3-p-2": "Trail of Bitsは2022年11月にSimpleXプラットフォームの暗号とネットワークのコンポーネントを検証しました。", + "hero-overlay-card-3-p-2": "Trail of Bitsは2022年11月にSimpleXプラットフォームの暗号とネットワークのコンポーネントを検証しました。詳しくは お知らせをご覧ください。", "docs-dropdown-9": "ダウンロード", "please-enable-javascript": "QRコードを表示するためにJavaScriptを有効にしてください。", "please-use-link-in-mobile-app": "このリンクをモバイルアプリで使用してください", "docs-dropdown-10": "透明性", "docs-dropdown-11": "よくある質問", "docs-dropdown-12": "セキュリティ" -} +} \ No newline at end of file diff --git a/website/langs/nl.json b/website/langs/nl.json index 1d6dc4c2df..02be8cbe41 100644 --- a/website/langs/nl.json +++ b/website/langs/nl.json @@ -245,8 +245,7 @@ "docs-dropdown-8": "SimpleX Directory Service", "comparison-section-list-point-4a": "SimpleX relais kunnen de e2e-versleuteling niet in gevaar brengen. Controleer de beveiligingscode om aanvallen op out-of-band kanalen te beperken", "hero-overlay-3-title": "Beveiligings beoordeling", - "hero-overlay-card-3-p-2": "Trail of Bits heeft in november 2022 de cryptografie en netwerkcomponenten van het SimpleX-platform beoordeeld.", - "hero-overlay-card-3-p-3": "Lees meer in de aankondiging.", + "hero-overlay-card-3-p-2": "Trail of Bits heeft in november 2022 de cryptografie en netwerkcomponenten van het SimpleX-platform beoordeeld. Lees meer in de aankondiging.", "jobs": "Sluit je aan bij het team", "hero-overlay-3-textlink": "Beveiligings beoordeling", "hero-overlay-card-3-p-1": "Trail of Bits is een toonaangevend beveiligings- en technologieadviesbureau met klanten onder meer grote technologiebedrijven, overheidsinstanties en grote blockchain-projecten.", @@ -256,4 +255,4 @@ "docs-dropdown-10": "Transparantie", "docs-dropdown-11": "FAQ", "docs-dropdown-12": "Beveiliging" -} +} \ No newline at end of file diff --git a/website/langs/pl.json b/website/langs/pl.json index e976be8295..c25ab4cd05 100644 --- a/website/langs/pl.json +++ b/website/langs/pl.json @@ -245,8 +245,7 @@ "releases-to-this-repo-are-done-1-2-days-later": "Wydania na tym repo są kilka dni później", "comparison-section-list-point-4a": "Przekaźniki SimpleX nie mogą skompromitować szyfrowania e2e. Zweryfikuj kody bezpieczeństwa aby złagodzić atak na kanał pozapasmowy", "hero-overlay-3-title": "Ocena bezpieczeństwa", - "hero-overlay-card-3-p-2": "Trail of Bits przejrzał komponenty kryptograficzne i sieciowe platformy SimpleX w listopadzie 2022.", - "hero-overlay-card-3-p-3": "Przeczytaj więcej w ogłoszeniach.", + "hero-overlay-card-3-p-2": "Trail of Bits przejrzał komponenty kryptograficzne i sieciowe platformy SimpleX w listopadzie 2022. Przeczytaj więcej w ogłoszeniach.", "jobs": "Dołącz do zespołu", "hero-overlay-3-textlink": "Ocena bezpieczeństwa", "hero-overlay-card-3-p-1": "Trail of Bits jest wiodącą firmą konsultingową w zakresie bezpieczeństwa i technologii, której klientami są duże firmy technologiczne, agencje rządowe i główne projekty blockchain.", @@ -256,4 +255,4 @@ "docs-dropdown-10": "Przezroczystość", "docs-dropdown-12": "Bezpieczeństwo", "docs-dropdown-11": "Często zadawane pytania" -} +} \ No newline at end of file diff --git a/website/langs/pt_BR.json b/website/langs/pt_BR.json index 77c48e1938..12fd10d10c 100644 --- a/website/langs/pt_BR.json +++ b/website/langs/pt_BR.json @@ -246,8 +246,7 @@ "hero-overlay-3-textlink": "Avaliação Segura", "hero-overlay-3-title": "Avaliação Segura", "hero-overlay-card-3-p-1": "Trail of Bits é uma consultoria líder em segurança e tecnologia cujos clientes incluem grandes empresas de tecnologia, agências governamentais e grandes projetos de blockchain.", - "hero-overlay-card-3-p-2": "Trail of Bits analisou a criptografia da plataforma SimpleX e os componentes de rede em novembro de 2022.", - "hero-overlay-card-3-p-3": "Leia mais em o anúncio.", + "hero-overlay-card-3-p-2": "Trail of Bits analisou a criptografia da plataforma SimpleX e os componentes de rede em novembro de 2022. Leia mais em o anúncio.", "f-droid-page-f-droid-org-repo-section-text": "Os repositórios SimpleX Chat e F-Droid.org assinam compilações com chaves diferentes. Para mudar, exporte o banco de dados de bate-papo e reinstale o aplicativo.", "please-enable-javascript": "Por favor habilite o JavaScript para ver o QR code.", "jobs": "Junte-se à equipe", @@ -256,4 +255,4 @@ "docs-dropdown-11": "FAQ", "docs-dropdown-10": "Transparência", "docs-dropdown-12": "Segurança" -} +} \ No newline at end of file diff --git a/website/langs/ru.json b/website/langs/ru.json index 827d0c0544..3c71bc24eb 100644 --- a/website/langs/ru.json +++ b/website/langs/ru.json @@ -37,7 +37,8 @@ "simplex-explained-tab-1-text": "1. Как это видят пользователи", "tap-to-close": "Нажмите, чтобы закрыть", "simplex-unique-card-4-p-2": "Вы можете использовать SimpleX со своими собственными серверами или с серверами, предоставленными нами — и при этом подключаться к любому пользователю SimpleX.", - "hero-overlay-card-3-p-2": "В ноябре 2022 года Trail of Bits провела обзор криптографии и сетевых компонентов SimpleX.", + "hero-overlay-card-3-p-2": "В ноябре 2022 года Trail of Bits провела обзор криптографии и сетевых компонентов SimpleX. Дополнительная информация.", + "hero-overlay-card-3-p-3": "В июле 2024 года Trail of Bits провела обзор криптографического дизайна протоколов SimpleX. Дополнительная информация.", "feature-1-title": "Сообщения зашифрованные E2E-шифрованием
с поддержкой markdown и редактированием", "comparison-point-4-text": "Единая или Централизованная сеть", "guide-dropdown-9": "Установление соединений", @@ -77,7 +78,6 @@ "simplex-unique-2-overlay-1-title": "Лучшая защита от спама и злоупотреблений", "simplex-private-6-title": "Внеполосный
Обмен ключами", "join-us-on-GitHub": "Присоединяйтесь к нам на GitHub", - "hero-overlay-card-3-p-3": "Подробнее читайте в анонсе.", "comparison-section-header": "Сравнение с другими протоколами", "invitation-hero-header": "Вы получили одноразовую ссылку для подключения в SimpleX Chat", "no-secure": "Нет - безопасно", @@ -256,4 +256,4 @@ "docs-dropdown-10": "Прозрачность", "docs-dropdown-12": "Безопасность", "docs-dropdown-11": "Часто задаваемые вопросы" -} +} \ No newline at end of file diff --git a/website/langs/uk.json b/website/langs/uk.json index cad05772da..de18cbda85 100644 --- a/website/langs/uk.json +++ b/website/langs/uk.json @@ -244,8 +244,7 @@ "stable-and-beta-versions-built-by-developers": "Стабільні та бета-версії, побудовані розробниками", "f-droid-page-f-droid-org-repo-section-text": "SimpleX Chat та репозитарії F-Droid.org підписують збірки різними ключами. Щоб переключитися, будь ласка, експортуйте базу даних чату та перевстановіть додаток.", "hero-overlay-3-title": "Оцінка безпеки", - "hero-overlay-card-3-p-2": "Trail of Bits переглянувало криптографію та компоненти мережі платформи SimpleX у листопаді 2022 року.", - "hero-overlay-card-3-p-3": "Читайте більше в оголошенні.", + "hero-overlay-card-3-p-2": "Trail of Bits переглянувало криптографію та компоненти мережі платформи SimpleX у листопаді 2022 року. Читайте більше в оголошенні.", "jobs": "Приєднатися до команди", "hero-overlay-3-textlink": "Оцінка безпеки", "hero-overlay-card-3-p-1": "Trail of Bits є провідною консалтинговою фірмою з безпеки та технологій, клієнтами якої є великі технологічні компанії, урядові агенції та великі проекти у сфері блокчейну.", @@ -256,4 +255,4 @@ "docs-dropdown-11": "ПОШИРЕНІ ЗАПИТАННЯ", "docs-dropdown-10": "Прозорість", "docs-dropdown-12": "Безпека" -} +} \ No newline at end of file diff --git a/website/langs/zh_Hans.json b/website/langs/zh_Hans.json index 03da9db140..7e44bf75e2 100644 --- a/website/langs/zh_Hans.json +++ b/website/langs/zh_Hans.json @@ -245,8 +245,7 @@ "docs-dropdown-8": "SimpleX 目录服务", "f-droid-page-f-droid-org-repo-section-text": "SimpleX Chat 和 F-Droid.org 存储库使用不同的密钥对构建进行签名。 如需切换,请导出聊天数据库并重新安装应用。", "hero-overlay-3-title": "安全性评估", - "hero-overlay-card-3-p-2": "2022年11月份,Trail of Bits 审核了 SimpleX 平台的密码学和网络部件。", - "hero-overlay-card-3-p-3": "更多内容见 该公告。", + "hero-overlay-card-3-p-2": "2022年11月份,Trail of Bits 审核了 SimpleX 平台的密码学和网络部件。更多内容见 该公告。", "jobs": "加入团队", "hero-overlay-3-textlink": "安全性评估", "hero-overlay-card-3-p-1": "Trail of Bits 是一家领先的安全和技术咨询企业,其客户包括大型科技公司、政府机构和重要的区块链项目。", @@ -256,4 +255,4 @@ "docs-dropdown-10": "透明度", "docs-dropdown-11": "常问问题", "docs-dropdown-12": "安全性" -} +} \ No newline at end of file diff --git a/website/src/_includes/blog_previews/20241014.html b/website/src/_includes/blog_previews/20241014.html new file mode 100644 index 0000000000..2683910e73 --- /dev/null +++ b/website/src/_includes/blog_previews/20241014.html @@ -0,0 +1,12 @@ +

New security audit!

+ +

Trail of Bits reviewed the cryptographic design of protocols used in SimpleX network and apps. +

+ +

v6.1 is released:

+ +
    +
  • Better calls: switch audio and video during the call.
  • +
  • Better iOS notifications: improved delivery, reduced traffic usage.
  • +
  • Better user experience: switch chat profiles, customizable message shapes, forward up to 20 messages.
  • +
\ No newline at end of file diff --git a/website/src/_includes/overlay_content/hero/card_3.html b/website/src/_includes/overlay_content/hero/card_3.html index 0e564c378a..1eddc0bc96 100644 --- a/website/src/_includes/overlay_content/hero/card_3.html +++ b/website/src/_includes/overlay_content/hero/card_3.html @@ -6,4 +6,4 @@

{{ "hero-overlay-card-3-p-3" | i18n({}, lang ) | safe }} -

+

\ No newline at end of file From b7131e16f2ef9a8f3bddfdcfa041959b91bdc7a4 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 14 Oct 2024 13:27:04 +0100 Subject: [PATCH 036/567] docs: fix links --- docs/SECURITY.md | 2 +- docs/TRANSPARENCY.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/SECURITY.md b/docs/SECURITY.md index 00c47a614d..72db650c35 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -12,7 +12,7 @@ The implementation security assessment of SimpleX cryptography and networking wa The cryptographic review of SimpleX protocols design was done by Trail of Bits in [July 2024](../blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md). -We are planning design review of SimpleX protocols in July 2024 and implementation review in December 2024/January 2025. +We are planning implementation security assessment in early 2025. ## Reporting security issues diff --git a/docs/TRANSPARENCY.md b/docs/TRANSPARENCY.md index 9637d2e85f..3ce7ed056d 100644 --- a/docs/TRANSPARENCY.md +++ b/docs/TRANSPARENCY.md @@ -24,8 +24,8 @@ Our objective is to consistently ensure that no user data and absolute minimum o - [Encryption Primitives Used](https://github.com/simplex-chat/simplexmq/blob/stable/protocol/overview-tjr.md#encryption-primitives-used) - [Threat model](https://github.com/simplex-chat/simplexmq/blob/stable/protocol/overview-tjr.md#threat-model) - Security assessments: - - Trail of Bits, SimpleX cryptography and networking, [October 2022](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md). - - Trail of Bits, the cryptographic review of SimpleX protocols design, [July 2024](./blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md). + - Trail of Bits, SimpleX cryptography and networking, [October 2022](../blog/20221108-simplex-chat-v4.2-security-audit-new-website.md). + - Trail of Bits, the cryptographic review of SimpleX protocols design, [July 2024](../blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md). Have a more specific question? Reach out to us via [SimpleX Chat](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion) or via email [chat@simplex.chat](mailto:chat@simplex.chat). From de94892fe74825e87800b4d46d0089a87badda4a Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Tue, 15 Oct 2024 10:58:54 +0300 Subject: [PATCH 037/567] ios: replace revealed bindings with constant value (#5027) --- .../Chat/ChatItem/CIChatFeatureView.swift | 7 ++- .../Views/Chat/ChatItem/CIFileView.swift | 21 ++++--- .../Views/Chat/ChatItem/CIVoiceView.swift | 8 +-- .../Chat/ChatItem/FramedCIVoiceView.swift | 11 ++-- .../Views/Chat/ChatItem/FramedItemView.swift | 61 ++++++++++--------- .../Chat/ChatItem/MarkedDeletedItemView.swift | 7 ++- apps/ios/Shared/Views/Chat/ChatItemView.swift | 59 +++++++++--------- apps/ios/Shared/Views/Chat/ChatView.swift | 2 +- .../UserSettings/AppearanceSettings.swift | 4 +- 9 files changed, 96 insertions(+), 84 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIChatFeatureView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIChatFeatureView.swift index 27d8d9c2de..02be8af73b 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIChatFeatureView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIChatFeatureView.swift @@ -11,11 +11,11 @@ import SimpleXChat struct CIChatFeatureView: View { @EnvironmentObject var m: ChatModel + @Environment(\.revealed) var revealed: Bool @ObservedObject var im = ItemsModel.shared @ObservedObject var chat: Chat @EnvironmentObject var theme: AppTheme var chatItem: ChatItem - @Binding var revealed: Bool var feature: Feature var icon: String? = nil var iconColor: Color @@ -106,6 +106,9 @@ struct CIChatFeatureView: View { struct CIChatFeatureView_Previews: PreviewProvider { static var previews: some View { let enabled = FeatureEnabled(forUser: false, forContact: false) - CIChatFeatureView(chat: Chat.sampleData, chatItem: ChatItem.getChatFeatureSample(.fullDelete, enabled), revealed: Binding.constant(true), feature: ChatFeature.fullDelete, iconColor: enabled.iconColor(.secondary)) + CIChatFeatureView( + chat: Chat.sampleData, + chatItem: ChatItem.getChatFeatureSample(.fullDelete, enabled), feature: ChatFeature.fullDelete, iconColor: enabled.iconColor(.secondary) + ).environment(\.revealed, true) } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift index fcb330c321..f5ab7f3a4b 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift @@ -285,17 +285,18 @@ struct CIFileView_Previews: PreviewProvider { file: nil ) Group { - 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)) + ChatItemView(chat: Chat.sampleData, chatItem: sentFile) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample()) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileName: "some_long_file_name_here", fileStatus: .rcvInvitation)) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvAccepted)) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10))) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvCancelled)) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileSize: 1_000_000_000, fileStatus: .rcvInvitation)) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(text: "Hello there", fileStatus: .rcvInvitation)) + 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)) + ChatItemView(chat: Chat.sampleData, chatItem: fileChatItemWtFile) } + .environment(\.revealed, false) .previewLayout(.fixed(width: 360, height: 360)) } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift index 45a20f03bd..acecaaae4f 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift @@ -510,10 +510,10 @@ struct CIVoiceView_Previews: PreviewProvider { duration: 30, allowMenu: Binding.constant(true) ) - ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage, revealed: Binding.constant(false), allowMenu: .constant(true)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(), revealed: Binding.constant(false), allowMenu: .constant(true)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), revealed: Binding.constant(false), allowMenu: .constant(true)) - ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWtFile, revealed: Binding.constant(false), allowMenu: .constant(true)) + ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage, allowMenu: .constant(true)) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(), allowMenu: .constant(true)) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), allowMenu: .constant(true)) + ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWtFile, allowMenu: .constant(true)) } .previewLayout(.fixed(width: 360, height: 360)) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift index 64a7f29a25..47a2cbb6cb 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift @@ -92,12 +92,13 @@ struct FramedCIVoiceView_Previews: PreviewProvider { file: CIFile.getSample(fileStatus: .sndComplete) ) Group { - 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)) + ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there")) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there", fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10))) + 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.")) + ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWithQuote) } + .environment(\.revealed, false) .previewLayout(.fixed(width: 360, height: 360)) } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift index 5f2930951f..9b71e6c4a4 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift @@ -16,7 +16,6 @@ struct FramedItemView: View { @ObservedObject var chat: Chat var chatItem: ChatItem var preview: UIImage? - @Binding var revealed: Bool var maxWidth: CGFloat = .infinity @State var msgWidth: CGFloat = 0 var imgWidth: CGFloat? = nil @@ -59,7 +58,7 @@ struct FramedItemView: View { framedItemHeader(icon: "arrowshape.turn.up.forward", caption: Text(itemForwarded.text(chat.chatInfo.chatType)).italic(), pad: true) } - ChatItemContentView(chat: chat, chatItem: chatItem, revealed: $revealed, msgContentView: framedMsgContentView) + ChatItemContentView(chat: chat, chatItem: chatItem, msgContentView: framedMsgContentView) .padding(chatItem.content.msgContent != nil ? 0 : 4) .overlay(DetermineWidth()) } @@ -371,14 +370,14 @@ func chatItemFrameContextColor(_ ci: ChatItem, _ theme: AppTheme) -> Color { struct FramedItemView_Previews: PreviewProvider { static var previews: some View { Group{ - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -"), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line "), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat"), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat"), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd)), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv)), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv)), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -"), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line "), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat"), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat"), allowMenu: Binding.constant(true)) } .previewLayout(.fixed(width: 360, height: 200)) } @@ -387,17 +386,18 @@ struct FramedItemView_Previews: PreviewProvider { struct FramedItemView_Edited_Previews: PreviewProvider { static var previews: some View { Group { - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat", .rcvRead, itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi there hello hello hello ther hello hello", chatDir: .directSnd, image: "data:image/jpg;base64,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"), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello there this is a long text", quotedItem: CIQuote.getSample(1, .now, "hi there", chatDir: .directSnd, image: "data:image/jpg;base64,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"), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemEdited: true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), itemEdited: true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemEdited: true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemEdited: true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, itemEdited: true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, itemEdited: true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, itemEdited: true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat", .rcvRead, itemEdited: true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi there hello hello hello ther hello hello", chatDir: .directSnd, image: "data:image/jpg;base64,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"), itemEdited: true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello there this is a long text", quotedItem: CIQuote.getSample(1, .now, "hi there", chatDir: .directSnd, image: "data:image/jpg;base64,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"), itemEdited: true), allowMenu: Binding.constant(true)) } + .environment(\.revealed, false) .previewLayout(.fixed(width: 360, height: 200)) } } @@ -405,17 +405,18 @@ struct FramedItemView_Edited_Previews: PreviewProvider { struct FramedItemView_Deleted_Previews: PreviewProvider { static var previews: some View { Group { - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi there hello hello hello ther hello hello", chatDir: .directSnd, image: "data:image/jpg;base64,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"), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello there this is a long text", quotedItem: CIQuote.getSample(1, .now, "hi there", chatDir: .directSnd, image: "data:image/jpg;base64,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"), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), itemDeleted: .deleted(deletedTs: .now)), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi there hello hello hello ther hello hello", chatDir: .directSnd, image: "data:image/jpg;base64,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"), itemDeleted: .deleted(deletedTs: .now)), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello there this is a long text", quotedItem: CIQuote.getSample(1, .now, "hi there", chatDir: .directSnd, image: "data:image/jpg;base64,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"), itemDeleted: .deleted(deletedTs: .now)), allowMenu: Binding.constant(true)) } + .environment(\.revealed, false) .previewLayout(.fixed(width: 360, height: 200)) } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift index afd817357c..6631206987 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift @@ -12,9 +12,9 @@ import SimpleXChat struct MarkedDeletedItemView: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme + @Environment(\.revealed) var revealed: Bool @ObservedObject var chat: Chat var chatItem: ChatItem - @Binding var revealed: Bool var body: some View { (Text(mergedMarkedDeletedText).italic() + Text(" ") + chatItem.timestampText) @@ -79,7 +79,10 @@ struct MarkedDeletedItemView: View { struct MarkedDeletedItemView_Previews: PreviewProvider { static var previews: some View { Group { - MarkedDeletedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true)) + MarkedDeletedItemView( + chat: Chat.sampleData, + chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)) + ).environment(\.revealed, true) } .previewLayout(.fixed(width: 360, height: 200)) } diff --git a/apps/ios/Shared/Views/Chat/ChatItemView.swift b/apps/ios/Shared/Views/Chat/ChatItemView.swift index bf09d15ff1..418c2d7c34 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemView.swift @@ -14,19 +14,28 @@ extension EnvironmentValues { static let defaultValue: Bool = true } + struct Revealed: EnvironmentKey { + static let defaultValue: Bool = true + } + var showTimestamp: Bool { get { self[ShowTimestamp.self] } set { self[ShowTimestamp.self] = newValue } } + + var revealed: Bool { + get { self[Revealed.self] } + set { self[Revealed.self] = newValue } + } } struct ChatItemView: View { @ObservedObject var chat: Chat @EnvironmentObject var theme: AppTheme @Environment(\.showTimestamp) var showTimestamp: Bool + @Environment(\.revealed) var revealed: Bool var chatItem: ChatItem var maxWidth: CGFloat = .infinity - @Binding var revealed: Bool @Binding var allowMenu: Bool init( @@ -34,27 +43,25 @@ struct ChatItemView: View { chatItem: ChatItem, showMember: Bool = false, maxWidth: CGFloat = .infinity, - revealed: Binding, allowMenu: Binding = .constant(false) ) { self.chat = chat self.chatItem = chatItem self.maxWidth = maxWidth - _revealed = revealed _allowMenu = allowMenu } var body: some View { let ci = chatItem if chatItem.meta.itemDeleted != nil && (!revealed || chatItem.isDeletedContent) { - MarkedDeletedItemView(chat: chat, chatItem: chatItem, revealed: $revealed) + MarkedDeletedItemView(chat: chat, chatItem: chatItem) } else if ci.quotedItem == nil && ci.meta.itemForwarded == nil && ci.meta.itemDeleted == nil && !ci.meta.isLive { if let mc = ci.content.msgContent, mc.isText && isShortEmoji(ci.content.text) { EmojiItemView(chat: chat, chatItem: ci) } else if ci.content.text.isEmpty, case let .voice(_, duration) = ci.content.msgContent { CIVoiceView(chat: chat, chatItem: ci, recordingFile: ci.file, duration: duration, allowMenu: $allowMenu) } else if ci.content.msgContent == nil { - ChatItemContentView(chat: chat, chatItem: chatItem, revealed: $revealed, msgContentView: { Text(ci.text) }) // msgContent is unreachable branch in this case + ChatItemContentView(chat: chat, chatItem: chatItem, msgContentView: { Text(ci.text) }) // msgContent is unreachable branch in this case } else { framedItemView() } @@ -84,7 +91,6 @@ struct ChatItemView: View { chat: chat, chatItem: chatItem, preview: preview, - revealed: $revealed, maxWidth: maxWidth, imgWidth: adjustedMaxWidth, videoWidth: adjustedMaxWidth, @@ -96,9 +102,9 @@ struct ChatItemView: View { struct ChatItemContentView: View { @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var theme: AppTheme + @Environment(\.revealed) var revealed: Bool @ObservedObject var chat: Chat var chatItem: ChatItem - @Binding var revealed: Bool var msgContentView: () -> Content @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false @@ -130,7 +136,7 @@ struct ChatItemContentView: View { case let .rcvChatPreference(feature, allowed, param): CIFeaturePreferenceView(chat: chat, chatItem: chatItem, feature: feature, allowed: allowed, param: param) case let .sndChatPreference(feature, _, _): - CIChatFeatureView(chat: chat, chatItem: chatItem, revealed: $revealed, feature: feature, icon: feature.icon, iconColor: theme.colors.secondary) + CIChatFeatureView(chat: chat, chatItem: chatItem, feature: feature, icon: feature.icon, iconColor: theme.colors.secondary) case let .rcvGroupFeature(feature, preference, _, role): chatFeatureView(feature, preference.enabled(role, for: chat.chatInfo.groupInfo?.membership).iconColor(theme.colors.secondary)) case let .sndGroupFeature(feature, preference, _, role): chatFeatureView(feature, preference.enabled(role, for: chat.chatInfo.groupInfo?.membership).iconColor(theme.colors.secondary)) case let .rcvChatFeatureRejected(feature): chatFeatureView(feature, .red) @@ -177,7 +183,7 @@ struct ChatItemContentView: View { } private func chatFeatureView(_ feature: Feature, _ iconColor: Color) -> some View { - CIChatFeatureView(chat: chat, chatItem: chatItem, revealed: $revealed, feature: feature, iconColor: iconColor) + CIChatFeatureView(chat: chat, chatItem: chatItem, feature: feature, iconColor: iconColor) } private var mergedGroupEventText: Text? { @@ -238,16 +244,17 @@ func chatEventText(_ ci: ChatItem, _ secondaryColor: Color) -> Text { struct ChatItemView_Previews: PreviewProvider { static var previews: some View { Group{ - 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)) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello")) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too")) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂")) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂")) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂🙂")) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getDeletedContentSample()) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now))) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂", .sndSent(sndProgress: .complete), itemLive: true)).environment(\.revealed, true) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemLive: true)).environment(\.revealed, true) } + .environment(\.revealed, false) .previewLayout(.fixed(width: 360, height: 70)) .environmentObject(Chat.sampleData) } @@ -265,8 +272,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider { content: .rcvIntegrityError(msgError: .msgSkipped(fromMsgId: 1, toMsgId: 2)), quotedItem: nil, file: nil - ), - revealed: Binding.constant(true) + ) ) ChatItemView( chat: Chat.sampleData, @@ -276,8 +282,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider { content: .rcvDecryptionError(msgDecryptError: .ratchetHeader, msgCount: 2), quotedItem: nil, file: nil - ), - revealed: Binding.constant(true) + ) ) ChatItemView( chat: Chat.sampleData, @@ -287,8 +292,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider { content: .rcvGroupInvitation(groupInvitation: CIGroupInvitation.getSample(status: .pending), memberRole: .admin), quotedItem: nil, file: nil - ), - revealed: Binding.constant(true) + ) ) ChatItemView( chat: Chat.sampleData, @@ -298,8 +302,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider { content: .rcvGroupEvent(rcvGroupEvent: .memberAdded(groupMemberId: 1, profile: Profile.sampleData)), quotedItem: nil, file: nil - ), - revealed: Binding.constant(true) + ) ) ChatItemView( chat: Chat.sampleData, @@ -309,10 +312,10 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider { content: ciFeatureContent, quotedItem: nil, file: nil - ), - revealed: Binding.constant(true) + ) ) } + .environment(\.revealed, true) .previewLayout(.fixed(width: 360, height: 70)) .environmentObject(Chat.sampleData) } diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index b3943eb066..1acf08035c 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -1183,9 +1183,9 @@ struct ChatView: View { chat: chat, chatItem: ci, maxWidth: maxWidth, - revealed: .constant(revealed), allowMenu: $allowMenu ) + .environment(\.revealed, revealed) .environment(\.showTimestamp, itemSeparation.timestamp) .modifier(ChatItemClipped(ci, tailVisible: itemSeparation.largeGap && (ci.meta.itemDeleted == nil || revealed))) .contextMenu { menu(ci, range, live: composeState.liveMessage != nil) } diff --git a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift index ab3388bfce..50f0a3a7b6 100644 --- a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift @@ -367,13 +367,13 @@ struct ChatThemePreview: View { let alice = ChatItem.getSample(1, CIDirection.directRcv, Date.now, NSLocalizedString("Good afternoon!", comment: "message preview")) let bob = ChatItem.getSample(2, CIDirection.directSnd, Date.now, NSLocalizedString("Good morning!", comment: "message preview"), quotedItem: CIQuote.getSample(alice.id, alice.meta.itemTs, alice.content.text, chatDir: alice.chatDir)) HStack { - ChatItemView(chat: Chat.sampleData, chatItem: alice, revealed: Binding.constant(false)) + ChatItemView(chat: Chat.sampleData, chatItem: alice) .modifier(ChatItemClipped(alice, tailVisible: true)) Spacer() } HStack { Spacer() - ChatItemView(chat: Chat.sampleData, chatItem: bob, revealed: Binding.constant(false)) + ChatItemView(chat: Chat.sampleData, chatItem: bob) .modifier(ChatItemClipped(bob, tailVisible: true)) .frame(alignment: .trailing) } From b5d8c652494302f95b5fa930a4d90add0a5d4ae5 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:01:06 +0400 Subject: [PATCH 038/567] ui: quota error description (#5037) --- apps/ios/Shared/Model/SimpleXAPI.swift | 6 ++++++ .../kotlin/chat/simplex/common/model/SimpleXAPI.kt | 9 +++++++++ .../common/src/commonMain/resources/MR/base/strings.xml | 2 ++ 3 files changed, 17 insertions(+) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index a0a32156c4..9297aa7898 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -743,6 +743,12 @@ func apiConnect_(incognito: Bool, connReq: String) async -> ((ConnReqType, Pendi message: "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." ) return (nil, alert) + case .chatCmdError(_, .errorAgent(.SMP(_, .QUOTA))): + let alert = mkAlert( + title: "Undelivered messages", + message: "The connection reached the limit of undelivered messages, your contact may be offline." + ) + return (nil, alert) case let .chatCmdError(_, .errorAgent(.INTERNAL(internalErr))): if internalErr == "SEUniqueID" { let alert = mkAlert( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index eec7a0f30d..9f0ed746a4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -1226,6 +1226,15 @@ object ChatController { ) return null } + r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent + && r.chatError.agentError is AgentErrorType.SMP + && r.chatError.agentError.smpErr is SMPErrorType.QUOTA -> { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.connection_error_quota), + generalGetString(MR.strings.connection_error_quota_desc) + ) + return null + } else -> { if (!(networkErrorAlert(r))) { apiErrorAlert("apiConnect", generalGetString(MR.strings.connection_error), r) diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index bb755f2b58..fd6988d5e8 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -144,6 +144,8 @@ Please check that you used the correct link or ask your contact to send you another one. Connection error (AUTH) 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. + Undelivered messages + The connection reached the limit of undelivered messages, your contact may be offline. Error accepting contact request Sender may have deleted the connection request. Error deleting contact From 515a0ddfdd135815415fbb32c1e543da7b3f8697 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 16 Oct 2024 19:25:47 +0100 Subject: [PATCH 039/567] blog: wired's attack on privacy (#5063) * blog: wired misleading attack on privacy of communications * image * update * title * update * update * preview --- ...ity-review-better-calls-user-experience.md | 2 +- blog/20241016-wired-attack-on-privacy.md | 39 ++++++++++++++++++ blog/images/20241016-wired-privacy.jpg | Bin 0 -> 43148 bytes .../src/_includes/blog_previews/20241016.html | 4 ++ 4 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 blog/20241016-wired-attack-on-privacy.md create mode 100644 blog/images/20241016-wired-privacy.jpg create mode 100644 website/src/_includes/blog_previews/20241016.html diff --git a/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md b/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md index feb2a85036..cefe8560f5 100644 --- a/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md +++ b/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md @@ -23,7 +23,7 @@ permalink: "/blog/20241014-simplex-network-v6-1-security-review-better-calls-use ## SimpleX cryptographic design review by Trail of Bits - + It's been almost two years since Trail of Bits did the first security assessment of SimpleX Chat. diff --git a/blog/20241016-wired-attack-on-privacy.md b/blog/20241016-wired-attack-on-privacy.md new file mode 100644 index 0000000000..3bc69c8176 --- /dev/null +++ b/blog/20241016-wired-attack-on-privacy.md @@ -0,0 +1,39 @@ +--- +layout: layouts/article.html +title: "Wired’s Attack on Privacy" +date: 2024-10-16 +previewBody: blog_previews/20241016.html +image: images/20241016-wired-privacy.jpg +imageWide: true +permalink: "/blog/20241016-wired-attack-on-privacy.html" +--- + +# Wired’s Attack on Privacy + + + +The [Wired article](https://www.wired.com/story/neo-nazis-flee-telegram-encrypted-app-simplex/) by David Gilbert focusing on neo-Nazis moving to SimpleX Chat following the Telegram's changes in privacy policy is biased and misleading. By cherry-picking information from [the report](https://www.isdglobal.org/digital_dispatches/neo-nazi-accelerationists-seek-new-digital-refuge-amid-looming-telegram-crackdown/) by the Institute for Strategic Dialogue (ISD), Wired fails to mention that SimpleX network design prioritizes privacy in order to protect human rights defenders, journalists, and everyday users who value their privacy — many people feel safer using SimpleX than non-private apps, being protected from strangers contacting them. + +Yes, privacy-focused SimpleX network offers encryption and anonymity — that’s the point. To paint this as problematic solely because of who may use such apps misses the broader, critical context. + +SimpleX’s true strength lies in protection of [users' metadata](./20240416-dangers-of-metadata-in-messengers.md), which can reveal sensitive information about who is communicating, when, and how often. SimpleX protocols are designed to minimize metadata collection. For countless people, especially vulnerable groups, these features can be life-saving. Wired article ignores these essential protections, and overlooks the positive aspects of having such a unique design, as noted in the publication which [they link to](https://www.maargentino.com/is-telegrams-privacy-shift-driving-extremists-toward-simplex/): + +> *“SimpleX also has a significant advantage when it comes to protecting metadata — the information that can reveal who you’re talking to, when, and how often. SimpleX is designed with privacy at its core, minimizing the amount of metadata collected and ensuring that any temporary data necessary for functionality is not retained or linked to identifiable users.”* + +Both publications referenced by Wired also explore how SimpleX design actually hinders extremist groups from spreading propaganda or building large networks. SimpleX design restricts message visibility and file retention, making it far from ideal for those looking to coordinate large networks. Yet these important qualities are ignored by Wired in favor of fear-mongering about encryption — an argument we've seen before when apps like Signal [faced similar treatment](https://foreignpolicy.com/2021/03/13/telegram-signal-apps-right-wing-extremism-islamic-state-terrorism-violence-europol-encrypted/). Ironically, Wired just a month earlier encouraged its readers to [adopt encrypted messaging apps](https://www.wired.com/story/gadget-lab-podcast-657/), making its current stance even more contradictory. + +The vilification of apps that offer critically important privacy, anonymity, and encryption must stop. That a small share of users may abuse these tools doesn’t justify broad criticism. Additionally, the lobbying for client-side scanning, which Wired’s article seems to indirectly endorse, is not only dangerous but goes against fundamental principles of free speech and personal security. We strongly oppose the use of private communications for any kind of monitoring, including AI training, which would undermine the very trust encryption is designed to build. + +It’s alarming to see Wired not only criticize SimpleX for its strong privacy protections but also subtly blame the European Court of Human Rights for [upholding basic human rights](https://www.theregister.com/2024/02/15/echr_backdoor_encryption/) by rejecting laws that would force encrypted apps to scan and hand over private messages before encryption. Wired writes: + +> *…European Court of Human Rights decision in February of this year ruled that forcing encrypted messaging apps to provide a backdoor to law enforcement was illegal. This decision undermined the EU’s controversial proposal that would potentially force encrypted messaging apps to scan all user content for identifiers of child sexual abuse material.* + +This commentary is both inappropriate and misguided — it plays into the hands of anti-privacy lobbyists attempting to criminalize access to private communications. Framing privacy and anonymity as tools for criminals ignores the reality that these protections are essential for millions of legitimate users, from activists to journalists, to ordinary citizens. Client-side scanning can't have any meaningful effect on reducing CSAM distribution, instead resulting in increase of crime and abuse when criminals get access to this data. + +We need to correct this narrative. The real danger lies not in protecting communication, but in failing to do so. Privacy apps like SimpleX are crucial, not just for those resisting mass surveillance, but for everyone who values the right to communicate without fear of their conversations being monitored or misused. This is a right we must defend and incorporate into law, [as we wrote before](./20240704-future-of-privacy-enforcing-privacy-standards.md). + +Wired could have stood on the right side of this battle and helped normalize the demand for privacy, genuinely protecting people from criminals and from the exploitation of the increasingly AI-enabled mass surveillance. Instead they chose the path of spreading fear and uncertainty of encrypted messaging and tools that enable privacy and anonymity. + +Spreading misinformation about privacy and security undermines trust in the tools that protect us, making it easier to justify more invasive surveillance measures that chip away at our civil liberties. + +Wired did not respond to our request for comment. diff --git a/blog/images/20241016-wired-privacy.jpg b/blog/images/20241016-wired-privacy.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a97e9ba79cc2ccc6980e40ea2f4d075800c0ea3c GIT binary patch literal 43148 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%0D*g%BKy56d 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 Wired article by David Gilbert focusing on neo-Nazis moving to SimpleX Chat following the Telegram's changes in + privacy policy is biased and misleading. By cherry-picking information from the report by the Institute for + Strategic Dialogue (ISD), Wired fails to mention that SimpleX network design prioritizes privacy in order to protect + human rights defenders, journalists, and everyday users who value their privacy.

\ No newline at end of file From d57abfcc93a787fc52e6e332cc01ee9bb573d4ef Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Wed, 16 Oct 2024 21:48:13 +0300 Subject: [PATCH 040/567] ios: fix theme import file picker (#5048) * ios: fix theme import file picker * minor --- .../Views/Helpers/ThemeModeEditor.swift | 64 +++++++------- .../UserSettings/AppearanceSettings.swift | 84 +++++++++++-------- 2 files changed, 83 insertions(+), 65 deletions(-) diff --git a/apps/ios/Shared/Views/Helpers/ThemeModeEditor.swift b/apps/ios/Shared/Views/Helpers/ThemeModeEditor.swift index ae94b4685c..9d5ae2e289 100644 --- a/apps/ios/Shared/Views/Helpers/ThemeModeEditor.swift +++ b/apps/ios/Shared/Views/Helpers/ThemeModeEditor.swift @@ -16,6 +16,7 @@ struct UserWallpaperEditor: View { @State var themeModeOverride: ThemeModeOverride @State var applyToMode: DefaultThemeMode? @State var showMore: Bool = false + @State var showFileImporter: Bool = false @Binding var globalThemeUsed: Bool var save: (DefaultThemeMode?, ThemeModeOverride?) async -> Void @@ -125,24 +126,27 @@ struct UserWallpaperEditor: View { CustomizeThemeColorsSection(editColor: { name in editColor(name, theme) }) - ImportExportThemeSection(perChat: nil, perUser: ChatModel.shared.currentUser?.uiThemes) { imported in - let importedFromString = imported.wallpaper?.importFromString() - let importedType = importedFromString?.toAppWallpaper().type - let currentTheme = ThemeManager.currentColors(nil, nil, nil, themeOverridesDefault.get()) - let type: WallpaperType? = if importedType?.sameType(currentTheme.wallpaper.type) == true { nil } else { importedType } - let colors = ThemeManager.currentThemeOverridesForExport(type, nil, nil).colors - let res = ThemeModeOverride(mode: imported.base.mode, colors: imported.colors, wallpaper: importedFromString).removeSameColors(imported.base, colorsToCompare: colors) - Task { - await MainActor.run { - themeModeOverride = res - } - await save(applyToMode, res) - } - } + ImportExportThemeSection(showFileImporter: $showFileImporter, perChat: nil, perUser: ChatModel.shared.currentUser?.uiThemes) } else { AdvancedSettingsButton(theme.colors.primary) { showMore = true } } } + .modifier( + ThemeImporter(isPresented: $showFileImporter) { imported in + let importedFromString = imported.wallpaper?.importFromString() + let importedType = importedFromString?.toAppWallpaper().type + let currentTheme = ThemeManager.currentColors(nil, nil, nil, themeOverridesDefault.get()) + let type: WallpaperType? = if importedType?.sameType(currentTheme.wallpaper.type) == true { nil } else { importedType } + let colors = ThemeManager.currentThemeOverridesForExport(type, nil, nil).colors + let res = ThemeModeOverride(mode: imported.base.mode, colors: imported.colors, wallpaper: importedFromString).removeSameColors(imported.base, colorsToCompare: colors) + Task { + await MainActor.run { + themeModeOverride = res + } + await save(applyToMode, res) + } + } + ) } private func onTypeCopyFromSameTheme(_ type: WallpaperType?) -> Bool { @@ -216,6 +220,7 @@ struct ChatWallpaperEditor: View { @State var themeModeOverride: ThemeModeOverride @State var applyToMode: DefaultThemeMode? @State var showMore: Bool = false + @State var showFileImporter: Bool = false @Binding var globalThemeUsed: Bool var save: (DefaultThemeMode?, ThemeModeOverride?) async -> Void @@ -328,24 +333,27 @@ struct ChatWallpaperEditor: View { CustomizeThemeColorsSection(editColor: editColor) - ImportExportThemeSection(perChat: themeModeOverride, perUser: ChatModel.shared.currentUser?.uiThemes) { imported in - let importedFromString = imported.wallpaper?.importFromString() - let importedType = importedFromString?.toAppWallpaper().type - let currentTheme = ThemeManager.currentColors(nil, nil, ChatModel.shared.currentUser?.uiThemes, themeOverridesDefault.get()) - let type: WallpaperType? = if importedType?.sameType(currentTheme.wallpaper.type) == true { nil } else { importedType } - let colors = ThemeManager.currentThemeOverridesForExport(type, nil, ChatModel.shared.currentUser?.uiThemes).colors - let res = ThemeModeOverride(mode: imported.base.mode, colors: imported.colors, wallpaper: importedFromString).removeSameColors(imported.base, colorsToCompare: colors) - Task { - await MainActor.run { - themeModeOverride = res - } - await save(applyToMode, res) - } - } + ImportExportThemeSection(showFileImporter: $showFileImporter, perChat: themeModeOverride, perUser: ChatModel.shared.currentUser?.uiThemes) } else { AdvancedSettingsButton(theme.colors.primary) { showMore = true } } } + .modifier( + ThemeImporter(isPresented: $showFileImporter) { imported in + let importedFromString = imported.wallpaper?.importFromString() + let importedType = importedFromString?.toAppWallpaper().type + let currentTheme = ThemeManager.currentColors(nil, nil, ChatModel.shared.currentUser?.uiThemes, themeOverridesDefault.get()) + let type: WallpaperType? = if importedType?.sameType(currentTheme.wallpaper.type) == true { nil } else { importedType } + let colors = ThemeManager.currentThemeOverridesForExport(type, nil, ChatModel.shared.currentUser?.uiThemes).colors + let res = ThemeModeOverride(mode: imported.base.mode, colors: imported.colors, wallpaper: importedFromString).removeSameColors(imported.base, colorsToCompare: colors) + Task { + await MainActor.run { + themeModeOverride = res + } + await save(applyToMode, res) + } + } + ) } private func onTypeCopyFromSameTheme(_ type: WallpaperType?) -> Bool { diff --git a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift index 50f0a3a7b6..4c61d592ac 100644 --- a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift @@ -583,11 +583,14 @@ struct CustomizeThemeView: View { } } - ImportExportThemeSection(perChat: nil, perUser: nil, save: { theme in + ImportExportThemeSection(showFileImporter: $showFileImporter, perChat: nil, perUser: nil) + } + .modifier( + ThemeImporter(isPresented: $showFileImporter) { theme in ThemeManager.saveAndApplyThemeOverrides(theme) saveThemeToDatabase(nil) - }) - } + } + ) /// When changing app theme, user overrides are hidden. User overrides will be returned back after closing Appearance screen, see ThemeDestinationPicker() .interactiveDismissDisabled(true) } @@ -595,10 +598,9 @@ struct CustomizeThemeView: View { struct ImportExportThemeSection: View { @EnvironmentObject var theme: AppTheme + @Binding var showFileImporter: Bool var perChat: ThemeModeOverride? var perUser: ThemeModeOverrides? - var save: (ThemeOverrides) -> Void - @State private var showFileImporter = false var body: some View { Section { @@ -626,39 +628,47 @@ struct ImportExportThemeSection: View { } label: { Text("Import theme").foregroundColor(theme.colors.primary) } - .fileImporter( - isPresented: $showFileImporter, - allowedContentTypes: [.data/*.plainText*/], - allowsMultipleSelection: false - ) { result in - if case let .success(files) = result, let fileURL = files.first { - do { - var fileSize: Int? = nil - if fileURL.startAccessingSecurityScopedResource() { - let resourceValues = try fileURL.resourceValues(forKeys: [.fileSizeKey]) - fileSize = resourceValues.fileSize - } - if let fileSize = fileSize, - // Same as Android/desktop - fileSize <= 5_500_000 { - if let string = try? String(contentsOf: fileURL, encoding: .utf8), let theme: ThemeOverrides = decodeYAML("themeId: \(UUID().uuidString)\n" + string) { - save(theme) - logger.error("Saved theme from file") - } else { - logger.error("Error decoding theme file") - } - fileURL.stopAccessingSecurityScopedResource() - } else { - fileURL.stopAccessingSecurityScopedResource() - let prettyMaxFileSize = ByteCountFormatter.string(fromByteCount: 5_500_000, countStyle: .binary) - AlertManager.shared.showAlertMsg( - title: "Large file!", - message: "Currently maximum supported file size is \(prettyMaxFileSize)." - ) - } - } catch { - logger.error("Appearance fileImporter error \(error.localizedDescription)") + } + } +} + +struct ThemeImporter: ViewModifier { + @Binding var isPresented: Bool + var save: (ThemeOverrides) -> Void + + func body(content: Content) -> some View { + content.fileImporter( + isPresented: $isPresented, + allowedContentTypes: [.data/*.plainText*/], + allowsMultipleSelection: false + ) { result in + if case let .success(files) = result, let fileURL = files.first { + do { + var fileSize: Int? = nil + if fileURL.startAccessingSecurityScopedResource() { + let resourceValues = try fileURL.resourceValues(forKeys: [.fileSizeKey]) + fileSize = resourceValues.fileSize } + if let fileSize = fileSize, + // Same as Android/desktop + fileSize <= 5_500_000 { + if let string = try? String(contentsOf: fileURL, encoding: .utf8), let theme: ThemeOverrides = decodeYAML("themeId: \(UUID().uuidString)\n" + string) { + save(theme) + logger.error("Saved theme from file") + } else { + logger.error("Error decoding theme file") + } + fileURL.stopAccessingSecurityScopedResource() + } else { + fileURL.stopAccessingSecurityScopedResource() + let prettyMaxFileSize = ByteCountFormatter.string(fromByteCount: 5_500_000, countStyle: .binary) + AlertManager.shared.showAlertMsg( + title: "Large file!", + message: "Currently maximum supported file size is \(prettyMaxFileSize)." + ) + } + } catch { + logger.error("Appearance fileImporter error \(error.localizedDescription)") } } } From c54fae01369a781742d4e69620e3d0a8b88127b7 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Wed, 16 Oct 2024 21:55:59 +0300 Subject: [PATCH 041/567] ios: fix sheets dismissing during biometric authentication (#5062) * ios: fix sheets dismissing during biometric authentication * remove AppSheet * Revert "remove AppSheet" This reverts commit 3aa1688cbd9cb931ff14c522301ee69f843cafb0. * remove local auth request on sheet dismissal * revert biometricAuth --- apps/ios/Shared/ContentView.swift | 4 +++- apps/ios/Shared/Views/ChatList/ChatListView.swift | 8 +++++--- apps/ios/Shared/Views/Helpers/AppSheet.swift | 12 +++++++----- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift index 6d600f33ff..e85f6a664a 100644 --- a/apps/ios/Shared/ContentView.swift +++ b/apps/ios/Shared/ContentView.swift @@ -13,6 +13,7 @@ struct ContentView: View { @EnvironmentObject var chatModel: ChatModel @ObservedObject var alertManager = AlertManager.shared @ObservedObject var callController = CallController.shared + @ObservedObject var appSheetState = AppSheetState.shared @Environment(\.colorScheme) var colorScheme @EnvironmentObject var theme: AppTheme @EnvironmentObject var sceneDelegate: SceneDelegate @@ -250,7 +251,8 @@ struct ContentView: View { private func mainView() -> some View { ZStack(alignment: .top) { - ChatListView(activeUserPickerSheet: $chatListUserPickerSheet).privacySensitive(protectScreen) + ChatListView(activeUserPickerSheet: $chatListUserPickerSheet) + .redacted(reason: appSheetState.redactionReasons(protectScreen)) .onAppear { requestNtfAuthorization() // Local Authentication notice is to be shown on next start after onboarding is complete diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index a1b40aadbe..7b24995f62 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -121,9 +121,11 @@ struct ChatListView: View { UserPicker(userPickerShown: $userPickerShown, activeSheet: $activeUserPickerSheet) } ) - .sheet(item: $activeUserPickerSheet) { - UserPickerSheetView(sheet: $0) - } + .appSheet( + item: $activeUserPickerSheet, + onDismiss: { chatModel.laRequest = nil }, + content: { UserPickerSheetView(sheet: $0) } + ) .onChange(of: activeUserPickerSheet) { if $0 != nil { DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { diff --git a/apps/ios/Shared/Views/Helpers/AppSheet.swift b/apps/ios/Shared/Views/Helpers/AppSheet.swift index 0ade1c0d8e..1e334367e8 100644 --- a/apps/ios/Shared/Views/Helpers/AppSheet.swift +++ b/apps/ios/Shared/Views/Helpers/AppSheet.swift @@ -11,6 +11,12 @@ import SwiftUI class AppSheetState: ObservableObject { static let shared = AppSheetState() @Published var scenePhaseActive: Bool = false + + func redactionReasons(_ protectScreen: Bool) -> RedactionReasons { + !protectScreen || scenePhaseActive + ? RedactionReasons() + : RedactionReasons.placeholder + } } private struct PrivacySensitive: ViewModifier { @@ -19,11 +25,7 @@ private struct PrivacySensitive: ViewModifier { @ObservedObject var appSheetState: AppSheetState = AppSheetState.shared func body(content: Content) -> some View { - if !protectScreen { - content - } else { - content.privacySensitive(!appSheetState.scenePhaseActive).redacted(reason: .privacy) - } + content.redacted(reason: appSheetState.redactionReasons(protectScreen)) } } From a160acef12c67b7d93a140361903dfef064e6807 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Fri, 18 Oct 2024 12:04:53 +0300 Subject: [PATCH 042/567] ios: fix navigation title redaction after biometric authentication (#5065) --- apps/ios/Shared/Views/TerminalView.swift | 7 ++++++- apps/ios/Shared/Views/UserSettings/SettingsView.swift | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/ios/Shared/Views/TerminalView.swift b/apps/ios/Shared/Views/TerminalView.swift index 36c05ed43d..23e1f783f7 100644 --- a/apps/ios/Shared/Views/TerminalView.swift +++ b/apps/ios/Shared/Views/TerminalView.swift @@ -105,7 +105,12 @@ struct TerminalView: View { } } .navigationViewStyle(.stack) - .navigationTitle("Chat console") + .toolbar { + // Redaction broken for `.navigationTitle` - using a toolbar item instead. + ToolbarItem(placement: .principal) { + Text("Chat console").font(.headline) + } + } .modifier(ThemedBackground()) } diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index a7938b86c3..12a982e76b 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -331,7 +331,12 @@ struct SettingsView: View { chatDatabaseRow() NavigationLink { MigrateFromDevice(showProgressOnSettings: $showProgress) - .navigationTitle("Migrate device") + .toolbar { + // Redaction broken for `.navigationTitle` - using a toolbar item instead. + ToolbarItem(placement: .principal) { + Text("Migrate device").font(.headline) + } + } .modifier(ThemedBackground(grouped: true)) .navigationBarTitleDisplayMode(.large) } label: { From 391304370597894aa4b675df098dc82d07175159 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Fri, 18 Oct 2024 12:07:18 +0300 Subject: [PATCH 043/567] ios: fix chat not loading if initial page has too many merged items (#5066) Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Views/Chat/ReverseList.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ios/Shared/Views/Chat/ReverseList.swift b/apps/ios/Shared/Views/Chat/ReverseList.swift index 2e09909c5e..e33adcef58 100644 --- a/apps/ios/Shared/Views/Chat/ReverseList.swift +++ b/apps/ios/Shared/Views/Chat/ReverseList.swift @@ -78,7 +78,7 @@ struct ReverseList: UIViewControllerRepresentable { self.dataSource = UITableViewDiffableDataSource( tableView: tableView ) { (tableView, indexPath, item) -> UITableViewCell? in - if indexPath.item > self.itemCount - 8, self.itemCount > 8 { + if indexPath.item > self.itemCount - 8 { self.representer.loadPage() } let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath) From 7cde2cf6c2798dd63e2e7b2228533c7d64b3a1b5 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Fri, 18 Oct 2024 14:35:27 +0300 Subject: [PATCH 044/567] ios: optimise ComposeView rendering (#5042) * ios: replace revealed bindings with constant value * ios: optimise ComposeView rendering * rename --------- Co-authored-by: Evgeny Poberezkin --- .../Chat/ComposeMessage/ComposeView.swift | 4 +- .../Chat/ComposeMessage/SendMessageView.swift | 2 +- .../Shared/Views/Helpers/UserDefault.swift | 62 +++++++++++++++++++ apps/ios/SimpleX.xcodeproj/project.pbxproj | 4 ++ 4 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 apps/ios/Shared/Views/Helpers/UserDefault.swift diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index 971d088fe2..19e2b528f1 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -287,8 +287,8 @@ struct ComposeView: View { // this is a workaround to fire an explicit event in certain cases @State private var stopPlayback: Bool = false - @AppStorage(DEFAULT_PRIVACY_SAVE_LAST_DRAFT) private var saveLastDraft = true - @AppStorage(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial + @UserDefault(DEFAULT_PRIVACY_SAVE_LAST_DRAFT) private var saveLastDraft = true + @UserDefault(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial var body: some View { VStack(spacing: 0) { diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift index 50ec8f28c1..8880023e02 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift @@ -40,7 +40,7 @@ struct SendMessageView: View { @State private var showCustomTimePicker = false @State private var selectedDisappearingMessageTime: Int? = customDisappearingMessageTimeDefault.get() @State private var progressByTimeout = false - @AppStorage(DEFAULT_LIVE_MESSAGE_ALERT_SHOWN) private var liveMessageAlertShown = false + @UserDefault(DEFAULT_LIVE_MESSAGE_ALERT_SHOWN) private var liveMessageAlertShown = false var body: some View { ZStack { diff --git a/apps/ios/Shared/Views/Helpers/UserDefault.swift b/apps/ios/Shared/Views/Helpers/UserDefault.swift new file mode 100644 index 0000000000..5f18465d20 --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/UserDefault.swift @@ -0,0 +1,62 @@ +// +// UserDefault.swift +// SimpleX (iOS) +// +// Created by user on 14/10/2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import Combine + +@propertyWrapper +public struct UserDefault: DynamicProperty { + @StateObject private var observer = UserDefaultObserver() + let initialValue: Value + let key: String + let store: UserDefaults + + public init( + wrappedValue: Value, + _ key: String, + store: UserDefaults = .standard + ) { + self.initialValue = wrappedValue + self.key = key + self.store = store + } + + public var wrappedValue: Value { + get { + // Observer can only be accessed after the property wrapper is installed in view (runtime exception) + observer.subscribe(to: key) + return store.object(forKey: key) as? Value ?? initialValue + } + nonmutating set { + store.set(newValue, forKey: key) + } + } +} + +private class UserDefaultObserver: ObservableObject { + private var subscribed = false + + func subscribe(to key: String) { + if !subscribed { + NotificationCenter.default.addObserver( + self, + selector: #selector(userDefaultsDidChange), + name: UserDefaults.didChangeNotification, + object: nil + ) + subscribed = true + } + } + + @objc + private func userDefaultsDidChange(_ notification: Notification) { + Task { @MainActor in objectWillChange.send() } + } + + deinit { NotificationCenter.default.removeObserver(self) } +} diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 2903388fb9..6307bb1d16 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -204,6 +204,7 @@ CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = CE38A29B2C3FCD72005ED185 /* SwiftyGif */; }; CE75480A2C622630009579B7 /* SwipeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7548092C622630009579B7 /* SwipeLabel.swift */; }; CE984D4B2C36C5D500E3AEFF /* ChatItemClipShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */; }; + CEA6E91C2CBD21B0002B5DB4 /* UserDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA6E91B2CBD21B0002B5DB4 /* UserDefault.swift */; }; CEDB245B2C9CD71800FBC5F6 /* StickyScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDB245A2C9CD71800FBC5F6 /* StickyScrollView.swift */; }; CEDE70222C48FD9500233B1F /* SEChatState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDE70212C48FD9500233B1F /* SEChatState.swift */; }; CEE723AA2C3BD3D70009AE93 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE723A92C3BD3D70009AE93 /* ShareViewController.swift */; }; @@ -545,6 +546,7 @@ CE3097FA2C4C0C9F00180898 /* ErrorAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorAlert.swift; sourceTree = ""; }; CE7548092C622630009579B7 /* SwipeLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeLabel.swift; sourceTree = ""; }; CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemClipShape.swift; sourceTree = ""; }; + CEA6E91B2CBD21B0002B5DB4 /* UserDefault.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefault.swift; sourceTree = ""; }; CEDB245A2C9CD71800FBC5F6 /* StickyScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyScrollView.swift; sourceTree = ""; }; CEDE70212C48FD9500233B1F /* SEChatState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SEChatState.swift; sourceTree = ""; }; CEE723A72C3BD3D70009AE93 /* SimpleX SE.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "SimpleX SE.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -802,6 +804,7 @@ CE176F1F2C87014C00145DBC /* InvertedForegroundStyle.swift */, CEDB245A2C9CD71800FBC5F6 /* StickyScrollView.swift */, CEFB2EDE2CA1BCC7004B1ECE /* SheetRepresentable.swift */, + CEA6E91B2CBD21B0002B5DB4 /* UserDefault.swift */, ); path = Helpers; sourceTree = ""; @@ -1482,6 +1485,7 @@ 5CFA59D12864782E00863A68 /* ChatArchiveView.swift in Sources */, 649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */, 5CADE79C292131E900072E13 /* ContactPreferencesView.swift in Sources */, + CEA6E91C2CBD21B0002B5DB4 /* UserDefault.swift in Sources */, 5CB346E52868AA7F001FD2EF /* SuspendChat.swift in Sources */, 5C9C2DA52894777E00CC63B1 /* GroupProfileView.swift in Sources */, 5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */, From f3cd167502af8391a0a83959b3db1db5fd229443 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 18 Oct 2024 13:06:47 +0100 Subject: [PATCH 045/567] core: ntf server --- src/Simplex/Chat.hs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 5f586e8dad..c875f9afa3 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -203,8 +203,9 @@ _defaultSMPServers = _defaultNtfServers :: [NtfServer] _defaultNtfServers = - [ "ntf://KmpZNNXiVZJx_G2T7jRUmDFxWXM3OAnunz3uLT0tqAA=@ntf3.simplex.im,pxculznuryunjdvtvh6s6szmanyadumpbmvevgdpe4wk5c65unyt4yid.onion", - "ntf://CJ5o7X6fCxj2FFYRU2KuCo70y4jSqz7td2HYhLnXWbU=@ntf4.simplex.im,wtvuhdj26jwprmomnyfu5wfuq2hjkzfcc72u44vi6gdhrwxldt6xauad.onion" + [ "ntf://FB-Uop7RTaZZEG0ZLD2CIaTjsPh-Fw0zFAnb7QyA8Ks=@ntf2.simplex.im,5ex3mupcazy3zlky64ab27phjhijpemsiby33qzq3pliejipbtx5xgad.onion" + -- "ntf://KmpZNNXiVZJx_G2T7jRUmDFxWXM3OAnunz3uLT0tqAA=@ntf3.simplex.im,pxculznuryunjdvtvh6s6szmanyadumpbmvevgdpe4wk5c65unyt4yid.onion", + -- "ntf://CJ5o7X6fCxj2FFYRU2KuCo70y4jSqz7td2HYhLnXWbU=@ntf4.simplex.im,wtvuhdj26jwprmomnyfu5wfuq2hjkzfcc72u44vi6gdhrwxldt6xauad.onion" ] maxImageSize :: Integer From 9175897acff478a5111019c6dc474d534c201298 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 18 Oct 2024 14:17:04 +0100 Subject: [PATCH 046/567] core, ui: add SMP STORE error (#5071) * core, ui: add SMP STORE error * update library --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 16 ++++++++-------- apps/ios/SimpleXChat/APITypes.swift | 1 + .../chat/simplex/common/model/SimpleXAPI.kt | 2 ++ cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 6307bb1d16..602c337560 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -220,15 +220,15 @@ D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; + E56F461C2CC290A700F1559D /* libHSsimplex-chat-6.1.0.9-7TvTSM1A8tfDn1HsJMdPKs-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E56F461A2CC290A700F1559D /* libHSsimplex-chat-6.1.0.9-7TvTSM1A8tfDn1HsJMdPKs-ghc9.6.3.a */; }; + E56F461D2CC290A700F1559D /* libHSsimplex-chat-6.1.0.9-7TvTSM1A8tfDn1HsJMdPKs.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E56F461B2CC290A700F1559D /* libHSsimplex-chat-6.1.0.9-7TvTSM1A8tfDn1HsJMdPKs.a */; }; E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; }; E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; E5DCF9982C5906FF007928CC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9962C5906FF007928CC /* InfoPlist.strings */; }; E5E997C92CBA891A00D7A2FA /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997C42CBA891A00D7A2FA /* libgmpxx.a */; }; E5E997CA2CBA891A00D7A2FA /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997C52CBA891A00D7A2FA /* libgmp.a */; }; - E5E997CB2CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997C62CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv-ghc9.6.3.a */; }; E5E997CC2CBA891A00D7A2FA /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997C72CBA891A00D7A2FA /* libffi.a */; }; - E5E997CD2CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997C82CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -562,6 +562,8 @@ D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = ""; }; + E56F461A2CC290A700F1559D /* libHSsimplex-chat-6.1.0.9-7TvTSM1A8tfDn1HsJMdPKs-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.9-7TvTSM1A8tfDn1HsJMdPKs-ghc9.6.3.a"; sourceTree = ""; }; + E56F461B2CC290A700F1559D /* libHSsimplex-chat-6.1.0.9-7TvTSM1A8tfDn1HsJMdPKs.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.9-7TvTSM1A8tfDn1HsJMdPKs.a"; sourceTree = ""; }; E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; @@ -616,9 +618,7 @@ E5DCF9A82C590732007928CC /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = ""; }; E5E997C42CBA891A00D7A2FA /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; E5E997C52CBA891A00D7A2FA /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - E5E997C62CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv-ghc9.6.3.a"; sourceTree = ""; }; E5E997C72CBA891A00D7A2FA /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - E5E997C82CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv.a"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -657,14 +657,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E56F461C2CC290A700F1559D /* libHSsimplex-chat-6.1.0.9-7TvTSM1A8tfDn1HsJMdPKs-ghc9.6.3.a in Frameworks */, E5E997C92CBA891A00D7A2FA /* libgmpxx.a in Frameworks */, - E5E997CB2CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv-ghc9.6.3.a in Frameworks */, E5E997CA2CBA891A00D7A2FA /* libgmp.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, E5E997CC2CBA891A00D7A2FA /* libffi.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, + E56F461D2CC290A700F1559D /* libHSsimplex-chat-6.1.0.9-7TvTSM1A8tfDn1HsJMdPKs.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - E5E997CD2CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -744,8 +744,8 @@ E5E997C72CBA891A00D7A2FA /* libffi.a */, E5E997C52CBA891A00D7A2FA /* libgmp.a */, E5E997C42CBA891A00D7A2FA /* libgmpxx.a */, - E5E997C62CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv-ghc9.6.3.a */, - E5E997C82CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv.a */, + E56F461A2CC290A700F1559D /* libHSsimplex-chat-6.1.0.9-7TvTSM1A8tfDn1HsJMdPKs-ghc9.6.3.a */, + E56F461B2CC290A700F1559D /* libHSsimplex-chat-6.1.0.9-7TvTSM1A8tfDn1HsJMdPKs.a */, ); path = Libraries; sourceTree = ""; diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index fae6d2293f..ea567427fe 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -2088,6 +2088,7 @@ public enum ProtocolErrorType: Decodable, Hashable { case AUTH case CRYPTO case QUOTA + case STORE(storeErr: String) case NO_MSG case LARGE_MSG case EXPIRED diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 9f0ed746a4..29d45a3b9b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -6054,6 +6054,7 @@ sealed class SMPErrorType { is AUTH -> "AUTH" is CRYPTO -> "CRYPTO" is QUOTA -> "QUOTA" + is STORE -> "STORE ${storeErr}" is NO_MSG -> "NO_MSG" is LARGE_MSG -> "LARGE_MSG" is EXPIRED -> "EXPIRED" @@ -6066,6 +6067,7 @@ sealed class SMPErrorType { @Serializable @SerialName("AUTH") class AUTH: SMPErrorType() @Serializable @SerialName("CRYPTO") class CRYPTO: SMPErrorType() @Serializable @SerialName("QUOTA") class QUOTA: SMPErrorType() + @Serializable @SerialName("STORE") class STORE(val storeErr: String): SMPErrorType() @Serializable @SerialName("NO_MSG") class NO_MSG: SMPErrorType() @Serializable @SerialName("LARGE_MSG") class LARGE_MSG: SMPErrorType() @Serializable @SerialName("EXPIRED") class EXPIRED: SMPErrorType() diff --git a/cabal.project b/cabal.project index 2df43da784..4fc8a72d45 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: c41bfe831d6d3fdf068f7419cbfed6afa46cb5b5 + tag: 967afaf802d7ea98480eaf280bfc6f35d4d43f05 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 487cbfa9dd..d68731083f 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."c41bfe831d6d3fdf068f7419cbfed6afa46cb5b5" = "1awqy4srdgcwmjf7q3s9w75w6wp38qk65fza2k3q1a1s2lj6h8w1"; + "https://github.com/simplex-chat/simplexmq.git"."967afaf802d7ea98480eaf280bfc6f35d4d43f05" = "0k8m07hxfgn8h8pqrfchqd8490fvv1jf8slw8qjp0vxdpxa84n3i"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; From 28383edb83d61c9e5ad62f0352dee39aeba48bff Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 18 Oct 2024 14:21:17 +0100 Subject: [PATCH 047/567] core: 6.1.1.0 (simplexmq: 6.1.1.0) --- package.yaml | 2 +- simplex-chat.cabal | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.yaml b/package.yaml index d98b470be9..94dc13ad2e 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.1.0.9 +version: 6.1.1.0 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 6913ddc14d..96d16f5004 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.1.0.9 +version: 6.1.1.0 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From 2ffabd1ef8a6eea257a727e40f1ab025098905c2 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 18 Oct 2024 18:07:38 +0400 Subject: [PATCH 048/567] ios: fix changing user via notification (#5069) --- apps/ios/Shared/Model/ChatModel.swift | 1 + apps/ios/Shared/Model/NtfManager.swift | 25 +++++++++++++++++++------ apps/ios/Shared/SimpleXApp.swift | 16 +++++++++++----- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 8525a141e3..e6970d4728 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -147,6 +147,7 @@ final class ChatModel: ObservableObject { @Published var chatDbEncrypted: Bool? @Published var chatDbStatus: DBMigrationResult? @Published var ctrlInitInProgress: Bool = false + @Published var notificationResponse: UNNotificationResponse? // local authentication @Published var contentViewAccessAuthenticated: Bool = false @Published var laRequest: LocalAuthRequest? diff --git a/apps/ios/Shared/Model/NtfManager.swift b/apps/ios/Shared/Model/NtfManager.swift index 95063845f1..18fabc5a32 100644 --- a/apps/ios/Shared/Model/NtfManager.swift +++ b/apps/ios/Shared/Model/NtfManager.swift @@ -29,17 +29,33 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject { private var granted = false private var prevNtfTime: Dictionary = [:] + override init() { + super.init() + UNUserNotificationCenter.current().delegate = self + } + // Handle notification when app is in background func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler handler: () -> Void) { logger.debug("NtfManager.userNotificationCenter: didReceive") - let content = response.notification.request.content + if appStateGroupDefault.get() == .active { + processNotificationResponse(response) + } else { + logger.debug("NtfManager.userNotificationCenter: remember response in model") + ChatModel.shared.notificationResponse = response + } + handler() + } + + func processNotificationResponse(_ ntfResponse: UNNotificationResponse) { let chatModel = ChatModel.shared - let action = response.actionIdentifier - logger.debug("NtfManager.userNotificationCenter: didReceive: action \(action), categoryIdentifier \(content.categoryIdentifier)") + let content = ntfResponse.notification.request.content + let action = ntfResponse.actionIdentifier + logger.debug("NtfManager.processNotificationResponse: didReceive: action \(action), categoryIdentifier \(content.categoryIdentifier)") if let userId = content.userInfo["userId"] as? Int64, userId != chatModel.currentUser?.userId { + logger.debug("NtfManager.processNotificationResponse changeActiveUser") changeActiveUser(userId, viewPwd: nil) } if content.categoryIdentifier == ntfCategoryContactRequest && (action == ntfActionAcceptContact || action == ntfActionAcceptContactIncognito), @@ -61,7 +77,6 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject { ItemsModel.shared.loadOpenChat(chatId) } } - handler() } private func ntfCallAction(_ content: UNNotificationContent, _ action: String) -> (ChatId, NtfCallAction)? { @@ -76,7 +91,6 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject { return nil } - // Handle notification when the app is in foreground func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, @@ -210,7 +224,6 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject { } } } - center.delegate = self } func notifyContactRequest(_ user: any UserLike, _ contactRequest: UserContactRequest) { diff --git a/apps/ios/Shared/SimpleXApp.swift b/apps/ios/Shared/SimpleXApp.swift index 7f2c3b5866..0dd54782b4 100644 --- a/apps/ios/Shared/SimpleXApp.swift +++ b/apps/ios/Shared/SimpleXApp.swift @@ -82,11 +82,17 @@ struct SimpleXApp: App { if appState != .stopped { startChatAndActivate { - if appState.inactive && chatModel.chatRunning == true { - Task { - await updateChats() - if !chatModel.showCallView && !CallController.shared.hasActiveCalls() { - await updateCallInvitations() + if chatModel.chatRunning == true { + if let ntfResponse = chatModel.notificationResponse { + chatModel.notificationResponse = nil + NtfManager.shared.processNotificationResponse(ntfResponse) + } + if appState.inactive { + Task { + await updateChats() + if !chatModel.showCallView && !CallController.shared.hasActiveCalls() { + await updateCallInvitations() + } } } } From edf99fcd1d7d38d2501d19608b94c084cf00f2ac Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 18 Oct 2024 18:37:14 +0100 Subject: [PATCH 049/567] 6.1.1: ios 245, android 249, desktop 74 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 56 +++++++++++----------- apps/multiplatform/gradle.properties | 8 ++-- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 602c337560..a21d47c16c 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -220,8 +220,8 @@ D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; - E56F461C2CC290A700F1559D /* libHSsimplex-chat-6.1.0.9-7TvTSM1A8tfDn1HsJMdPKs-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E56F461A2CC290A700F1559D /* libHSsimplex-chat-6.1.0.9-7TvTSM1A8tfDn1HsJMdPKs-ghc9.6.3.a */; }; - E56F461D2CC290A700F1559D /* libHSsimplex-chat-6.1.0.9-7TvTSM1A8tfDn1HsJMdPKs.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E56F461B2CC290A700F1559D /* libHSsimplex-chat-6.1.0.9-7TvTSM1A8tfDn1HsJMdPKs.a */; }; + E56F46202CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E56F461E2CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a */; }; + E56F46212CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E56F461F2CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a */; }; E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; }; E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; @@ -562,8 +562,8 @@ D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = ""; }; - E56F461A2CC290A700F1559D /* libHSsimplex-chat-6.1.0.9-7TvTSM1A8tfDn1HsJMdPKs-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.9-7TvTSM1A8tfDn1HsJMdPKs-ghc9.6.3.a"; sourceTree = ""; }; - E56F461B2CC290A700F1559D /* libHSsimplex-chat-6.1.0.9-7TvTSM1A8tfDn1HsJMdPKs.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.9-7TvTSM1A8tfDn1HsJMdPKs.a"; sourceTree = ""; }; + E56F461E2CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a"; sourceTree = ""; }; + E56F461F2CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a"; sourceTree = ""; }; E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; @@ -657,14 +657,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E56F461C2CC290A700F1559D /* libHSsimplex-chat-6.1.0.9-7TvTSM1A8tfDn1HsJMdPKs-ghc9.6.3.a in Frameworks */, E5E997C92CBA891A00D7A2FA /* libgmpxx.a in Frameworks */, E5E997CA2CBA891A00D7A2FA /* libgmp.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, E5E997CC2CBA891A00D7A2FA /* libffi.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - E56F461D2CC290A700F1559D /* libHSsimplex-chat-6.1.0.9-7TvTSM1A8tfDn1HsJMdPKs.a in Frameworks */, + E56F46202CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, + E56F46212CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -744,8 +744,8 @@ E5E997C72CBA891A00D7A2FA /* libffi.a */, E5E997C52CBA891A00D7A2FA /* libgmp.a */, E5E997C42CBA891A00D7A2FA /* libgmpxx.a */, - E56F461A2CC290A700F1559D /* libHSsimplex-chat-6.1.0.9-7TvTSM1A8tfDn1HsJMdPKs-ghc9.6.3.a */, - E56F461B2CC290A700F1559D /* libHSsimplex-chat-6.1.0.9-7TvTSM1A8tfDn1HsJMdPKs.a */, + E56F461F2CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a */, + E56F461E2CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a */, ); path = Libraries; sourceTree = ""; @@ -1903,7 +1903,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1928,7 +1928,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES_THIN; - MARKETING_VERSION = 6.1; + MARKETING_VERSION = 6.1.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1952,7 +1952,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1977,7 +1977,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.1; + MARKETING_VERSION = 6.1.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1993,11 +1993,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.1; + MARKETING_VERSION = 6.1.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2013,11 +2013,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.1; + MARKETING_VERSION = 6.1.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2038,7 +2038,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2053,7 +2053,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.1; + MARKETING_VERSION = 6.1.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2075,7 +2075,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2090,7 +2090,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.1; + MARKETING_VERSION = 6.1.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2112,7 +2112,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2138,7 +2138,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.1; + MARKETING_VERSION = 6.1.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2163,7 +2163,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2189,7 +2189,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.1; + MARKETING_VERSION = 6.1.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2214,7 +2214,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2229,7 +2229,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.1; + MARKETING_VERSION = 6.1.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2248,7 +2248,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2263,7 +2263,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.1; + MARKETING_VERSION = 6.1.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 907b0a94ae..32bd7579c8 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -26,11 +26,11 @@ android.enableJetifier=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.1 -android.version_code=247 +android.version_name=6.1.1 +android.version_code=249 -desktop.version_name=6.1 -desktop.version_code=73 +desktop.version_name=6.1.1 +desktop.version_code=74 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From 78510b6fd3d61c23da9ca0ffa1072c9073128bec Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 25 Oct 2024 20:09:59 +0400 Subject: [PATCH 050/567] core, ios: get messages for multiple last notifications; separately get notification connections before requesting messages (to avoid acknowledgement races in case of parralel nse threads); coordinate nse threads (#5084) * core, ios: get messages for multiple last notifications (#5047) * ios: refactor notification service (#5086) * core, ios: separately get notification connections before requesting messages; coordinate nse threads (#5085) --- apps/ios/Shared/Model/NtfManager.swift | 14 +- .../ios/SimpleX NSE/NotificationService.swift | 496 +++++++++++++----- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +- apps/ios/SimpleXChat/APITypes.swift | 24 +- apps/ios/SimpleXChat/ChatTypes.swift | 15 +- apps/ios/SimpleXChat/Notifications.swift | 48 +- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat.hs | 56 +- src/Simplex/Chat/Controller.hs | 19 +- src/Simplex/Chat/View.hs | 4 +- 11 files changed, 487 insertions(+), 233 deletions(-) diff --git a/apps/ios/Shared/Model/NtfManager.swift b/apps/ios/Shared/Model/NtfManager.swift index 18fabc5a32..b2fa6a0200 100644 --- a/apps/ios/Shared/Model/NtfManager.swift +++ b/apps/ios/Shared/Model/NtfManager.swift @@ -199,6 +199,12 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject { actions: [], intentIdentifiers: [], hiddenPreviewsBodyPlaceholder: NSLocalizedString("SimpleX encrypted message or connection event", comment: "notification") + ), + UNNotificationCategory( + identifier: ntfCategoryManyEvents, + actions: [], + intentIdentifiers: [], + hiddenPreviewsBodyPlaceholder: NSLocalizedString("New events", comment: "notification") ) ]) } @@ -228,24 +234,24 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject { func notifyContactRequest(_ user: any UserLike, _ contactRequest: UserContactRequest) { logger.debug("NtfManager.notifyContactRequest") - addNotification(createContactRequestNtf(user, contactRequest)) + addNotification(createContactRequestNtf(user, contactRequest, 0)) } func notifyContactConnected(_ user: any UserLike, _ contact: Contact) { logger.debug("NtfManager.notifyContactConnected") - addNotification(createContactConnectedNtf(user, contact)) + addNotification(createContactConnectedNtf(user, contact, 0)) } func notifyMessageReceived(_ user: any UserLike, _ cInfo: ChatInfo, _ cItem: ChatItem) { logger.debug("NtfManager.notifyMessageReceived") if cInfo.ntfsEnabled { - addNotification(createMessageReceivedNtf(user, cInfo, cItem)) + addNotification(createMessageReceivedNtf(user, cInfo, cItem, 0)) } } func notifyCallInvitation(_ invitation: RcvCallInvitation) { logger.debug("NtfManager.notifyCallInvitation") - addNotification(createCallInvitationNtf(invitation)) + addNotification(createCallInvitationNtf(invitation, 0)) } func setNtfBadgeCount(_ count: Int) { diff --git a/apps/ios/SimpleX NSE/NotificationService.swift b/apps/ios/SimpleX NSE/NotificationService.swift index 99bedb891f..ce80adf38f 100644 --- a/apps/ios/SimpleX NSE/NotificationService.swift +++ b/apps/ios/SimpleX NSE/NotificationService.swift @@ -26,14 +26,52 @@ enum NSENotification { case nse(UNMutableNotificationContent) case callkit(RcvCallInvitation) case empty - case msgInfo(NtfMsgAckInfo) +} - var isCallInvitation: Bool { +public enum NSENotificationData { + case connectionEvent(_ user: User, _ connEntity: ConnectionEntity) + case contactConnected(_ user: any UserLike, _ contact: Contact) + case contactRequest(_ user: any UserLike, _ contactRequest: UserContactRequest) + case messageReceived(_ user: any UserLike, _ cInfo: ChatInfo, _ cItem: ChatItem) + case callInvitation(_ invitation: RcvCallInvitation) + case msgInfo(NtfMsgAckInfo) + case noNtf + + var callInvitation: RcvCallInvitation? { switch self { - case let .nse(ntf): ntf.categoryIdentifier == ntfCategoryCallInvitation - case .callkit: true - case .empty: false - case .msgInfo: false + case let .callInvitation(invitation): invitation + default: nil + } + } + + func notificationContent(_ badgeCount: Int) -> UNMutableNotificationContent { + return switch self { + case let .connectionEvent(user, connEntity): createConnectionEventNtf(user, connEntity, badgeCount) + case let .contactConnected(user, contact): createContactConnectedNtf(user, contact, badgeCount) + case let .contactRequest(user, contactRequest): createContactRequestNtf(user, contactRequest, badgeCount) + case let .messageReceived(user, cInfo, cItem): createMessageReceivedNtf(user, cInfo, cItem, badgeCount) + case let .callInvitation(invitation): createCallInvitationNtf(invitation, badgeCount) + case .msgInfo: UNMutableNotificationContent() + case .noNtf: UNMutableNotificationContent() + } + } + + var notificationEvent: NSENotificationData? { + return switch self { + case .connectionEvent: self + case .contactConnected: self + case .contactRequest: self + case .messageReceived: self + case .callInvitation: self + case .msgInfo: nil + case .noNtf: nil + } + } + + var newMsgData: (any UserLike, ChatInfo)? { + return switch self { + case let .messageReceived(user, cInfo, _): (user, cInfo) + default: nil } } } @@ -43,9 +81,10 @@ enum NSENotification { // or when background notification is received. class NSEThreads { static let shared = NSEThreads() - private static let queue = DispatchQueue(label: "chat.simplex.app.SimpleX-NSE.notification-threads.lock") + static let queue = DispatchQueue(label: "chat.simplex.app.SimpleX-NSE.notification-threads.lock") private var allThreads: Set = [] - private var activeThreads: [(UUID, NotificationService)] = [] + var activeThreads: [(UUID, NotificationService)] = [] + var droppedNotifications: [(ChatId, NSENotificationData)] = [] func newThread() -> UUID { NSEThreads.queue.sync { @@ -64,22 +103,19 @@ class NSEThreads { } } - func processNotification(_ id: ChatId, _ ntf: NSENotification) async -> Void { - var waitTime: Int64 = 5_000_000000 - while waitTime > 0 { - if let (_, nse) = rcvEntityThread(id), - nse.shouldProcessNtf && nse.processReceivedNtf(ntf) { - break - } else { - try? await Task.sleep(nanoseconds: 10_000000) - waitTime -= 10_000000 - } + func processNotification(_ id: ChatId, _ ntf: NSENotificationData) async -> Void { + if let (_, nse) = rcvEntityThread(id), + nse.expectedMessages[id]?.shouldProcessNtf ?? false { + nse.processReceivedNtf(id, ntf, signalReady: true) } } private func rcvEntityThread(_ id: ChatId) -> (UUID, NotificationService)? { NSEThreads.queue.sync { - activeThreads.first(where: { (_, nse) in nse.receiveEntityId == id }) + // this selects the earliest thread that: + // 1) has this connection in nse.expectedMessages + // 2) has not completed processing messages for this connection (not ready) + activeThreads.first(where: { (_, nse) in nse.expectedMessages[id]?.ready == false }) } } @@ -106,31 +142,38 @@ class NSEThreads { } } +struct ExpectedMessage { + var ntfConn: UserNtfConn + var receiveConnId: String? + var expectedMsgId: String? + var allowedGetNextAttempts: Int + var msgBestAttemptNtf: NSENotificationData? + var ready: Bool + var shouldProcessNtf: Bool + var startedProcessingNewMsgs: Bool + var semaphore: DispatchSemaphore +} + // Notification service extension creates a new instance of the class and calls didReceive for each notification. // Each didReceive is called in its own thread, but multiple calls can be made in one process, and, empirically, there is never // more than one process of notification service extension exists at a time. // Soon after notification service delivers the last notification it is either suspended or terminated. class NotificationService: UNNotificationServiceExtension { var contentHandler: ((UNNotificationContent) -> Void)? - var bestAttemptNtf: NSENotification? + // served as notification if no message attempts (msgBestAttemptNtf) could be produced + var serviceBestAttemptNtf: NSENotification? var badgeCount: Int = 0 // thread is added to allThreads here - if thread did not start chat, // chat does not need to be suspended but NSE state still needs to be set to "suspended". var threadId: UUID? = NSEThreads.shared.newThread() - var notificationInfo: NtfMessages? - var receiveEntityId: String? - var receiveConnId: String? - var expectedMessage: String? - var allowedGetNextAttempts: Int = 3 - // return true if the message is taken - it prevents sending it to another NotificationService instance for processing - var shouldProcessNtf = false + var expectedMessages: Dictionary = [:] // key is receiveEntityId var appSubscriber: AppSubscriber? var returnedSuspension = false override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { logger.debug("DEBUGGING: NotificationService.didReceive") let ntf = if let ntf_ = request.content.mutableCopy() as? UNMutableNotificationContent { ntf_ } else { UNMutableNotificationContent() } - setBestAttemptNtf(ntf) + setServiceBestAttemptNtf(ntf) self.contentHandler = contentHandler registerGroupDefaults() let appState = appStateGroupDefault.get() @@ -138,13 +181,11 @@ class NotificationService: UNNotificationServiceExtension { switch appState { case .stopped: setBadgeCount() - setBestAttemptNtf(createAppStoppedNtf()) + setServiceBestAttemptNtf(createAppStoppedNtf(badgeCount)) deliverBestAttemptNtf() case .suspended: - setBadgeCount() receiveNtfMessages(request, contentHandler) case .suspending: - setBadgeCount() Task { let state: AppState = await withCheckedContinuation { cont in appSubscriber = appStateSubscriber { s in @@ -171,8 +212,9 @@ class NotificationService: UNNotificationServiceExtension { deliverBestAttemptNtf() } } - default: - deliverBestAttemptNtf() + case .active: contentHandler(UNMutableNotificationContent()) + case .activating: contentHandler(UNMutableNotificationContent()) + case .bgRefresh: contentHandler(UNMutableNotificationContent()) } } @@ -192,78 +234,165 @@ class NotificationService: UNNotificationServiceExtension { if let t = threadId { NSEThreads.shared.startThread(t, self) } let dbStatus = startChat() if case .ok = dbStatus, - let ntfInfo = apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo) { - logger.debug("NotificationService: receiveNtfMessages: apiGetNtfMessage \(String(describing: ntfInfo.receivedMsg_ == nil ? 0 : 1))") - if let connEntity = ntfInfo.connEntity_ { - setBestAttemptNtf( - ntfInfo.ntfsEnabled - ? .nse(createConnectionEventNtf(ntfInfo.user, connEntity)) - : .empty - ) - if let id = connEntity.id, ntfInfo.expectedMsg_ != nil { - notificationInfo = ntfInfo - receiveEntityId = id - receiveConnId = connEntity.conn.agentConnId - let expectedMsgId = ntfInfo.expectedMsg_?.msgId - let receivedMsgId = ntfInfo.receivedMsg_?.msgId - logger.debug("NotificationService: receiveNtfMessages: expectedMsgId = \(expectedMsgId ?? "nil", privacy: .private), receivedMsgId = \(receivedMsgId ?? "nil", privacy: .private)") - expectedMessage = expectedMsgId - shouldProcessNtf = true - return + let ntfConns = apiGetNtfConns(nonce: nonce, encNtfInfo: encNtfInfo) { + logger.debug("NotificationService: receiveNtfMessages: apiGetNtfConns ntfConns count = \(ntfConns.count)") + + for ntfConn in ntfConns { + addExpectedMessage(ntfConn: ntfConn) + } + + let connIdsToGet = expectedMessages.compactMap { (id, _) in + let started = NSEThreads.queue.sync { + let canStart = checkCanStart(id) + if let t = threadId { logger.debug("NotificationService thread \(t, privacy: .private): receiveNtfMessages: can start: \(canStart)") } + if canStart { + processDroppedNotifications(id) + expectedMessages[id]?.startedProcessingNewMsgs = true + expectedMessages[id]?.shouldProcessNtf = true + } + return canStart + } + if started { + return expectedMessages[id]?.receiveConnId + } else { + if let t = threadId { logger.debug("NotificationService thread \(t, privacy: .private): receiveNtfMessages: entity \(id, privacy: .private) waiting on semaphore") } + expectedMessages[id]?.semaphore.wait() + if let t = threadId { logger.debug("NotificationService thread \(t, privacy: .private): receiveNtfMessages: entity \(id, privacy: .private) proceeding after semaphore") } + Task { + NSEThreads.queue.sync { + processDroppedNotifications(id) + expectedMessages[id]?.startedProcessingNewMsgs = true + expectedMessages[id]?.shouldProcessNtf = true + } + if let connId = expectedMessages[id]?.receiveConnId { + let _ = getConnNtfMessage(connId: connId) + } + } + return nil } } + + if !connIdsToGet.isEmpty { + if let r = apiGetConnNtfMessages(connIds: connIdsToGet) { + logger.debug("NotificationService: receiveNtfMessages: apiGetConnNtfMessages count = \(r.count)") + } + return + } } else if let dbStatus = dbStatus { - setBestAttemptNtf(createErrorNtf(dbStatus)) + setServiceBestAttemptNtf(createErrorNtf(dbStatus, badgeCount)) } } deliverBestAttemptNtf() } + func addExpectedMessage(ntfConn: UserNtfConn) { + if let connEntity = ntfConn.connEntity_, + let receiveEntityId = connEntity.id, ntfConn.expectedMsg_ != nil { + let expectedMsgId = ntfConn.expectedMsg_?.msgId + logger.debug("NotificationService: addExpectedMessage: expectedMsgId = \(expectedMsgId ?? "nil", privacy: .private)") + expectedMessages[receiveEntityId] = ExpectedMessage( + ntfConn: ntfConn, + receiveConnId: connEntity.conn.agentConnId, + expectedMsgId: expectedMsgId, + allowedGetNextAttempts: 3, + msgBestAttemptNtf: ntfConn.defaultBestAttemptNtf, + ready: false, + shouldProcessNtf: false, + startedProcessingNewMsgs: false, + semaphore: DispatchSemaphore(value: 0) + ) + } + } + + func checkCanStart(_ entityId: String) -> Bool { + return !NSEThreads.shared.activeThreads.contains(where: { + (tId, nse) in tId != threadId && nse.expectedMessages.contains(where: { $0.key == entityId }) + }) + } + + func processDroppedNotifications(_ entityId: String) { + if !NSEThreads.shared.droppedNotifications.isEmpty { + let messagesToProcess = NSEThreads.shared.droppedNotifications.filter { (eId, _) in eId == entityId } + NSEThreads.shared.droppedNotifications.removeAll(where: { (eId, _) in eId == entityId }) + for (index, (_, ntf)) in messagesToProcess.enumerated() { + if let t = threadId { logger.debug("NotificationService thread \(t, privacy: .private): entity \(entityId, privacy: .private): processing dropped notification \(index, privacy: .private)") } + processReceivedNtf(entityId, ntf, signalReady: false) + } + } + } + override func serviceExtensionTimeWillExpire() { logger.debug("DEBUGGING: NotificationService.serviceExtensionTimeWillExpire") deliverBestAttemptNtf(urgent: true) } - func processReceivedNtf(_ ntf: NSENotification) -> Bool { - guard let ntfInfo = notificationInfo, let expectedMsgTs = ntfInfo.expectedMsg_?.msgTs else { return false } - if !ntfInfo.user.showNotifications { - self.setBestAttemptNtf(.empty) + var expectingMoreMessages: Bool { + !expectedMessages.allSatisfy { $0.value.ready } + } + + func processReceivedNtf(_ id: ChatId, _ ntf: NSENotificationData, signalReady: Bool) { + guard let expectedMessage = expectedMessages[id] else { + return + } + guard let expectedMsgTs = expectedMessage.ntfConn.expectedMsg_?.msgTs else { + NSEThreads.shared.droppedNotifications.append((id, ntf)) + if signalReady { entityReady(id) } + return } if case let .msgInfo(info) = ntf { - if info.msgId == expectedMessage { - expectedMessage = nil + if info.msgId == expectedMessage.expectedMsgId { logger.debug("NotificationService processNtf: msgInfo msgId = \(info.msgId, privacy: .private): expected") + expectedMessages[id]?.expectedMsgId = nil + if signalReady { entityReady(id) } self.deliverBestAttemptNtf() - return true } else if let msgTs = info.msgTs_, msgTs > expectedMsgTs { logger.debug("NotificationService processNtf: msgInfo msgId = \(info.msgId, privacy: .private): unexpected msgInfo, let other instance to process it, stopping this one") + NSEThreads.shared.droppedNotifications.append((id, ntf)) + if signalReady { entityReady(id) } self.deliverBestAttemptNtf() - return false - } else if allowedGetNextAttempts > 0, let receiveConnId = receiveConnId { + } else if (expectedMessages[id]?.allowedGetNextAttempts ?? 0) > 0, let receiveConnId = expectedMessages[id]?.receiveConnId { logger.debug("NotificationService processNtf: msgInfo msgId = \(info.msgId, privacy: .private): unexpected msgInfo, get next message") - allowedGetNextAttempts -= 1 - if let receivedMsg = apiGetConnNtfMessage(connId: receiveConnId) { - logger.debug("NotificationService processNtf, on apiGetConnNtfMessage: msgInfo msgId = \(info.msgId, privacy: .private), receivedMsg msgId = \(receivedMsg.msgId, privacy: .private)") - return true + expectedMessages[id]?.allowedGetNextAttempts -= 1 + if let receivedMsg = getConnNtfMessage(connId: receiveConnId) { + logger.debug("NotificationService processNtf, on getConnNtfMessage: msgInfo msgId = \(info.msgId, privacy: .private), receivedMsg msgId = \(receivedMsg.msgId, privacy: .private)") } else { - logger.debug("NotificationService processNtf, on apiGetConnNtfMessage: msgInfo msgId = \(info.msgId, privacy: .private): no next message, deliver best attempt") + logger.debug("NotificationService processNtf, on getConnNtfMessage: msgInfo msgId = \(info.msgId, privacy: .private): no next message, deliver best attempt") + NSEThreads.shared.droppedNotifications.append((id, ntf)) + if signalReady { entityReady(id) } self.deliverBestAttemptNtf() - return false } } else { logger.debug("NotificationService processNtf: msgInfo msgId = \(info.msgId, privacy: .private): unknown message, let other instance to process it") + NSEThreads.shared.droppedNotifications.append((id, ntf)) + if signalReady { entityReady(id) } self.deliverBestAttemptNtf() - return false } - } else if ntfInfo.user.showNotifications { + } else if expectedMessage.ntfConn.user.showNotifications { logger.debug("NotificationService processNtf: setting best attempt") - self.setBestAttemptNtf(ntf) - if ntf.isCallInvitation { - self.deliverBestAttemptNtf() + if ntf.notificationEvent != nil { + setBadgeCount() } - return true + let prevBestAttempt = expectedMessages[id]?.msgBestAttemptNtf + if prevBestAttempt?.callInvitation != nil { + if ntf.callInvitation != nil { // replace with newer call + expectedMessages[id]?.msgBestAttemptNtf = ntf + } // otherwise keep call as best attempt + } else { + expectedMessages[id]?.msgBestAttemptNtf = ntf + } + } else { + NSEThreads.shared.droppedNotifications.append((id, ntf)) + if signalReady { entityReady(id) } + } + } + + func entityReady(_ entityId: ChatId) { + if let t = threadId { logger.debug("NotificationService thread \(t, privacy: .private): entityReady: entity \(entityId, privacy: .private)") } + expectedMessages[entityId]?.ready = true + if let (tNext, nse) = NSEThreads.shared.activeThreads.first(where: { (_, nse) in nse.expectedMessages[entityId]?.startedProcessingNewMsgs == false }) { + if let t = threadId { logger.debug("NotificationService thread \(t, privacy: .private): entityReady: signal next thread \(tNext, privacy: .private) for entity \(entityId, privacy: .private)") } + nse.expectedMessages[entityId]?.semaphore.signal() } - return false } func setBadgeCount() { @@ -271,37 +400,32 @@ class NotificationService: UNNotificationServiceExtension { ntfBadgeCountGroupDefault.set(badgeCount) } - func setBestAttemptNtf(_ ntf: UNMutableNotificationContent) { - setBestAttemptNtf(.nse(ntf)) - } - - func setBestAttemptNtf(_ ntf: NSENotification) { - logger.debug("NotificationService.setBestAttemptNtf") - if case let .nse(notification) = ntf { - notification.badge = badgeCount as NSNumber - bestAttemptNtf = .nse(notification) - } else { - bestAttemptNtf = ntf - } + func setServiceBestAttemptNtf(_ ntf: UNMutableNotificationContent) { + logger.debug("NotificationService.setServiceBestAttemptNtf") + serviceBestAttemptNtf = .nse(ntf) } private func deliverBestAttemptNtf(urgent: Bool = false) { - logger.debug("NotificationService.deliverBestAttemptNtf") - // stop processing other messages - shouldProcessNtf = false + if (urgent || !expectingMoreMessages) { + logger.debug("NotificationService.deliverBestAttemptNtf") + // stop processing other messages + for (key, _) in expectedMessages { + expectedMessages[key]?.shouldProcessNtf = false + } - let suspend: Bool - if let t = threadId { - threadId = nil - suspend = NSEThreads.shared.endThread(t) && NSEThreads.shared.noThreads - } else { - suspend = false + let suspend: Bool + if let t = threadId { + threadId = nil + suspend = NSEThreads.shared.endThread(t) && NSEThreads.shared.noThreads + } else { + suspend = false + } + deliverCallkitOrNotification(urgent: urgent, suspend: suspend) } - deliverCallkitOrNotification(urgent: urgent, suspend: suspend) } private func deliverCallkitOrNotification(urgent: Bool, suspend: Bool = false) { - if case .callkit = bestAttemptNtf { + if useCallKit() && expectedMessages.contains(where: { $0.value.msgBestAttemptNtf?.callInvitation != nil }) { logger.debug("NotificationService.deliverCallkitOrNotification: will suspend, callkit") if urgent { // suspending NSE even though there may be other notifications @@ -339,19 +463,13 @@ class NotificationService: UNNotificationServiceExtension { } private func deliverNotification() { - if let handler = contentHandler, let ntf = bestAttemptNtf { + if let handler = contentHandler, let ntf = prepareNotification() { contentHandler = nil - bestAttemptNtf = nil - let deliver: (UNMutableNotificationContent?) -> Void = { ntf in - let useNtf = if let ntf = ntf { - appStateGroupDefault.get().running ? UNMutableNotificationContent() : ntf - } else { - UNMutableNotificationContent() - } - handler(useNtf) - } + serviceBestAttemptNtf = nil switch ntf { - case let .nse(content): deliver(content) + case let .nse(content): + content.badge = badgeCount as NSNumber + handler(content) case let .callkit(invitation): logger.debug("NotificationService reportNewIncomingVoIPPushPayload for \(invitation.contact.id)") CXProvider.reportNewIncomingVoIPPushPayload([ @@ -362,13 +480,85 @@ class NotificationService: UNNotificationServiceExtension { "callTs": invitation.callTs.timeIntervalSince1970 ]) { error in logger.debug("reportNewIncomingVoIPPushPayload result: \(error)") - deliver(error == nil ? nil : createCallInvitationNtf(invitation)) + handler(error == nil ? UNMutableNotificationContent() : createCallInvitationNtf(invitation, self.badgeCount)) } - case .empty: deliver(nil) // used to mute notifications that did not unsubscribe yet - case .msgInfo: deliver(nil) // unreachable, the best attempt is never set to msgInfo + case .empty: + handler(UNMutableNotificationContent()) // used to mute notifications that did not unsubscribe yet } } } + + private func prepareNotification() -> NSENotification? { + if expectedMessages.isEmpty { + return serviceBestAttemptNtf + } else if let callNtfKV = expectedMessages.first(where: { $0.value.msgBestAttemptNtf?.callInvitation != nil }), + let callInv = callNtfKV.value.msgBestAttemptNtf?.callInvitation, + let callNtf = callNtfKV.value.msgBestAttemptNtf { + return useCallKit() ? .callkit(callInv) : .nse(callNtf.notificationContent(badgeCount)) + } else { + let ntfEvents = expectedMessages.compactMap { $0.value.msgBestAttemptNtf?.notificationEvent } + if ntfEvents.isEmpty { + return .empty + } else if let ntfEvent = ntfEvents.count == 1 ? ntfEvents.first : nil { + return .nse(ntfEvent.notificationContent(badgeCount)) + } else { + return .nse(createJointNtf(ntfEvents)) + } + } + } + + private func createJointNtf(_ ntfEvents: [NSENotificationData]) -> UNMutableNotificationContent { + let previewMode = ntfPreviewModeGroupDefault.get() + let newMsgsData: [(any UserLike, ChatInfo)] = ntfEvents.compactMap { $0.newMsgData } + if !newMsgsData.isEmpty, let userId = newMsgsData.first?.0.userId { + let newMsgsChats: [ChatInfo] = newMsgsData.map { $0.1 } + let uniqueChatsNames = uniqueNewMsgsChatsNames(newMsgsChats) + var body: String + if previewMode == .hidden { + body = String.localizedStringWithFormat(NSLocalizedString("New messages in %d chats", comment: "notification body"), uniqueChatsNames.count) + } else { + body = String.localizedStringWithFormat(NSLocalizedString("From: %@", comment: "notification body"), newMsgsChatsNamesStr(uniqueChatsNames)) + } + return createNotification( + categoryIdentifier: ntfCategoryManyEvents, + title: NSLocalizedString("New messages", comment: "notification"), + body: body, + userInfo: ["userId": userId], + badgeCount: badgeCount + ) + } else { + return createNotification( + categoryIdentifier: ntfCategoryManyEvents, + title: NSLocalizedString("New events", comment: "notification"), + body: String.localizedStringWithFormat(NSLocalizedString("%d new events", comment: "notification body"), ntfEvents.count), + badgeCount: badgeCount + ) + } + } + + private func uniqueNewMsgsChatsNames(_ newMsgsChats: [ChatInfo]) -> [String] { + var seenChatIds = Set() + var uniqueChatsNames: [String] = [] + for chat in newMsgsChats { + if !seenChatIds.contains(chat.id) { + seenChatIds.insert(chat.id) + uniqueChatsNames.append(chat.chatViewName) + } + } + return uniqueChatsNames + } + + private func newMsgsChatsNamesStr(_ names: [String]) -> String { + return switch names.count { + case 1: names[0] + case 2: "\(names[0]) and \(names[1])" + case 3: "\(names[0] + ", " + names[1]) and \(names[2])" + default: + names.count > 3 + ? "\(names[0]), \(names[1]) and \(names.count - 2) other chats" + : "" + } + } } // nseStateGroupDefault must not be used in NSE directly, only via this singleton @@ -582,28 +772,25 @@ func chatRecvMsg() async -> ChatResponse? { private let isInChina = SKStorefront().countryCode == "CHN" private func useCallKit() -> Bool { !isInChina && callKitEnabledGroupDefault.get() } -func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotification)? { +func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotificationData)? { logger.debug("NotificationService receivedMsgNtf: \(res.responseType)") switch res { case let .contactConnected(user, contact, _): - return (contact.id, .nse(createContactConnectedNtf(user, contact))) + return (contact.id, .contactConnected(user, contact)) // case let .contactConnecting(contact): // TODO profile update case let .receivedContactRequest(user, contactRequest): - return (UserContact(contactRequest: contactRequest).id, .nse(createContactRequestNtf(user, contactRequest))) + return (UserContact(contactRequest: contactRequest).id, .contactRequest(user, contactRequest)) case let .newChatItems(user, chatItems): // Received items are created one at a time if let chatItem = chatItems.first { let cInfo = chatItem.chatInfo var cItem = chatItem.chatItem - if !cInfo.ntfsEnabled { - ntfBadgeCountGroupDefault.set(max(0, ntfBadgeCountGroupDefault.get() - 1)) - } if let file = cItem.autoReceiveFile() { cItem = autoReceiveFile(file) ?? cItem } - let ntf: NSENotification = cInfo.ntfsEnabled ? .nse(createMessageReceivedNtf(user, cInfo, cItem)) : .empty - return cItem.showNotification ? (chatItem.chatId, ntf) : nil + let ntf: NSENotificationData = (cInfo.ntfsEnabled && cItem.showNotification) ? .messageReceived(user, cInfo, cItem) : .noNtf + return (chatItem.chatId, ntf) } else { return nil } @@ -620,10 +807,7 @@ func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotification)? { return nil case let .callInvitation(invitation): // Do not post it without CallKit support, iOS will stop launching the app without showing CallKit - return ( - invitation.contact.id, - useCallKit() ? .callkit(invitation) : .nse(createCallInvitationNtf(invitation)) - ) + return (invitation.contact.id, .callInvitation(invitation)) case let .ntfMessage(_, connEntity, ntfMessage): return if let id = connEntity.id { (id, .msgInfo(ntfMessage)) } else { nil } case .chatSuspended: @@ -704,15 +888,15 @@ func apiSetEncryptLocalFiles(_ enable: Bool) throws { throw r } -func apiGetNtfMessage(nonce: String, encNtfInfo: String) -> NtfMessages? { +func apiGetNtfConns(nonce: String, encNtfInfo: String) -> [UserNtfConn]? { guard apiGetActiveUser() != nil else { logger.debug("no active user") return nil } - let r = sendSimpleXCmd(.apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo)) - if case let .ntfMessages(user, connEntity_, expectedMsg_, receivedMsg_) = r, let user = user { - logger.debug("apiGetNtfMessage response ntfMessages: \(receivedMsg_ == nil ? 0 : 1)") - return NtfMessages(user: user, connEntity_: connEntity_, expectedMsg_: expectedMsg_, receivedMsg_: receivedMsg_) + let r = sendSimpleXCmd(.apiGetNtfConns(nonce: nonce, encNtfInfo: encNtfInfo)) + if case let .ntfConns(ntfConns) = r { + logger.debug("apiGetNtfConns response ntfConns: \(ntfConns.count)") + return ntfConns.compactMap { toUserNtfConn($0) } } else if case let .chatCmdError(_, error) = r { logger.debug("apiGetNtfMessage error response: \(String.init(describing: error))") } else { @@ -721,17 +905,33 @@ func apiGetNtfMessage(nonce: String, encNtfInfo: String) -> NtfMessages? { return nil } -func apiGetConnNtfMessage(connId: String) -> NtfMsgInfo? { +func toUserNtfConn(_ ntfConn: NtfConn) -> UserNtfConn? { + if let user = ntfConn.user_ { + return UserNtfConn(user: user, connEntity_: ntfConn.connEntity_, expectedMsg_: ntfConn.expectedMsg_) + } else { + return nil + } +} + +func apiGetConnNtfMessages(connIds: [String]) -> [NtfMsgInfo?]? { guard apiGetActiveUser() != nil else { logger.debug("no active user") return nil } - let r = sendSimpleXCmd(.apiGetConnNtfMessage(connId: connId)) - if case let .connNtfMessage(receivedMsg_) = r { - logger.debug("apiGetConnNtfMessage response receivedMsg_: \(receivedMsg_ == nil ? 0 : 1)") - return receivedMsg_ + let r = sendSimpleXCmd(.apiGetConnNtfMessages(connIds: connIds)) + if case let .connNtfMessages(receivedMsgs) = r { + logger.debug("apiGetConnNtfMessages response receivedMsgs: \(receivedMsgs.count)") + return receivedMsgs + } + logger.debug("apiGetConnNtfMessages error: \(responseError(r))") + return nil +} + +func getConnNtfMessage(connId: String) -> NtfMsgInfo? { + let r_ = apiGetConnNtfMessages(connIds: [connId]) + if let r = r_, let receivedMsg = r.count == 1 ? r.first : nil { + return receivedMsg } - logger.debug("apiGetConnNtfMessage error: \(responseError(r))") return nil } @@ -769,13 +969,33 @@ func setNetworkConfig(_ cfg: NetCfg) throws { throw r } -struct NtfMessages { +struct UserNtfConn { var user: User var connEntity_: ConnectionEntity? var expectedMsg_: NtfMsgInfo? - var receivedMsg_: NtfMsgInfo? - var ntfsEnabled: Bool { - user.showNotifications && (connEntity_?.ntfsEnabled ?? false) + var defaultBestAttemptNtf: NSENotificationData { + return if !user.showNotifications { + .noNtf + } else if let connEntity = connEntity_ { + switch connEntity { + case let .rcvDirectMsgConnection(_, contact): + contact?.chatSettings.enableNtfs == .all + ? .connectionEvent(user, connEntity) + : .noNtf + case let .rcvGroupMsgConnection(_, groupInfo, _): + groupInfo.chatSettings.enableNtfs == .all + ? .connectionEvent(user, connEntity) + : .noNtf + case .sndFileConnection: .noNtf + case .rcvFileConnection: .noNtf + case let .userContactConnection(_, userContact): + userContact.groupId == nil + ? .connectionEvent(user, connEntity) + : .noNtf + } + } else { + .noNtf + } } } diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index a21d47c16c..2b1160061c 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -148,6 +148,11 @@ 6419EC562AB8BC8B004A607A /* ContextInvitingContactMemberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */; }; 6419EC582AB97507004A607A /* CIMemberCreatedContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */; }; 6432857C2925443C00FBE5C8 /* GroupPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */; }; + 643B3B452CCBEB080083A2CF /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B402CCBEB080083A2CF /* libgmpxx.a */; }; + 643B3B462CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a */; }; + 643B3B472CCBEB080083A2CF /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B422CCBEB080083A2CF /* libffi.a */; }; + 643B3B482CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a */; }; + 643B3B492CCBEB080083A2CF /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B442CCBEB080083A2CF /* libgmp.a */; }; 6440CA00288857A10062C672 /* CIEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440C9FF288857A10062C672 /* CIEventView.swift */; }; 6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */; }; 6442E0BA287F169300CEC0F9 /* AddGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6442E0B9287F169300CEC0F9 /* AddGroupView.swift */; }; @@ -220,15 +225,10 @@ D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; - E56F46202CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E56F461E2CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a */; }; - E56F46212CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E56F461F2CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a */; }; E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; }; E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; E5DCF9982C5906FF007928CC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9962C5906FF007928CC /* InfoPlist.strings */; }; - E5E997C92CBA891A00D7A2FA /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997C42CBA891A00D7A2FA /* libgmpxx.a */; }; - E5E997CA2CBA891A00D7A2FA /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997C52CBA891A00D7A2FA /* libgmp.a */; }; - E5E997CC2CBA891A00D7A2FA /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997C72CBA891A00D7A2FA /* libffi.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -491,6 +491,11 @@ 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextInvitingContactMemberView.swift; sourceTree = ""; }; 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMemberCreatedContactView.swift; sourceTree = ""; }; 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupPreferencesView.swift; sourceTree = ""; }; + 643B3B402CCBEB080083A2CF /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgmpxx.a; path = Libraries/libgmpxx.a; sourceTree = ""; }; + 643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a"; path = "Libraries/libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a"; sourceTree = ""; }; + 643B3B422CCBEB080083A2CF /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libffi.a; path = Libraries/libffi.a; sourceTree = ""; }; + 643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a"; path = "Libraries/libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a"; sourceTree = ""; }; + 643B3B442CCBEB080083A2CF /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgmp.a; path = Libraries/libgmp.a; sourceTree = ""; }; 6440C9FF288857A10062C672 /* CIEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIEventView.swift; sourceTree = ""; }; 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupMembersView.swift; sourceTree = ""; }; 6442E0B9287F169300CEC0F9 /* AddGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupView.swift; sourceTree = ""; }; @@ -562,8 +567,6 @@ D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = ""; }; - E56F461E2CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a"; sourceTree = ""; }; - E56F461F2CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a"; sourceTree = ""; }; E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; @@ -616,9 +619,6 @@ E5DCF9A62C590731007928CC /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/InfoPlist.strings; sourceTree = ""; }; E5DCF9A72C590732007928CC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoPlist.strings; sourceTree = ""; }; E5DCF9A82C590732007928CC /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = ""; }; - E5E997C42CBA891A00D7A2FA /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E5E997C52CBA891A00D7A2FA /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - E5E997C72CBA891A00D7A2FA /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -657,14 +657,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E5E997C92CBA891A00D7A2FA /* libgmpxx.a in Frameworks */, - E5E997CA2CBA891A00D7A2FA /* libgmp.a in Frameworks */, + 643B3B452CCBEB080083A2CF /* libgmpxx.a in Frameworks */, + 643B3B472CCBEB080083A2CF /* libffi.a in Frameworks */, + 643B3B492CCBEB080083A2CF /* libgmp.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - E5E997CC2CBA891A00D7A2FA /* libffi.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - E56F46202CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - E56F46212CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a in Frameworks */, + 643B3B482CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a in Frameworks */, + 643B3B462CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -741,11 +741,6 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E5E997C72CBA891A00D7A2FA /* libffi.a */, - E5E997C52CBA891A00D7A2FA /* libgmp.a */, - E5E997C42CBA891A00D7A2FA /* libgmpxx.a */, - E56F461F2CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a */, - E56F461E2CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a */, ); path = Libraries; sourceTree = ""; @@ -817,6 +812,11 @@ 5CC2C0FA2809BF11000C35E3 /* Localizable.strings */, 5C422A7C27A9A6FA0097A1E1 /* SimpleX (iOS).entitlements */, 5C764E5C279C70B7000C6508 /* Libraries */, + 643B3B422CCBEB080083A2CF /* libffi.a */, + 643B3B442CCBEB080083A2CF /* libgmp.a */, + 643B3B402CCBEB080083A2CF /* libgmpxx.a */, + 643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a */, + 643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a */, 5CA059C2279559F40002BEB4 /* Shared */, 5CDCAD462818589900503DA2 /* SimpleX NSE */, CEE723A82C3BD3D70009AE93 /* SimpleX SE */, diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index ea567427fe..3c9b91fa0b 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -55,8 +55,8 @@ public enum ChatCommand { case apiRegisterToken(token: DeviceToken, notificationMode: NotificationsMode) case apiVerifyToken(token: DeviceToken, nonce: String, code: String) case apiDeleteToken(token: DeviceToken) - case apiGetNtfMessage(nonce: String, encNtfInfo: String) - case apiGetConnNtfMessage(connId: String) + case apiGetNtfConns(nonce: String, encNtfInfo: String) + case apiGetConnNtfMessages(connIds: [String]) case apiNewGroup(userId: Int64, incognito: Bool, groupProfile: GroupProfile) case apiAddMember(groupId: Int64, contactId: Int64, memberRole: GroupMemberRole) case apiJoinGroup(groupId: Int64) @@ -214,8 +214,8 @@ public enum ChatCommand { case let .apiRegisterToken(token, notificationMode): return "/_ntf register \(token.cmdString) \(notificationMode.rawValue)" 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 .apiGetConnNtfMessage(connId): return "/_ntf conn message \(connId)" + case let .apiGetNtfConns(nonce, encNtfInfo): return "/_ntf conns \(nonce) \(encNtfInfo)" + case let .apiGetConnNtfMessages(connIds): return "/_ntf conn messages \(connIds.joined(separator: ","))" 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)" @@ -369,8 +369,8 @@ public enum ChatCommand { case .apiRegisterToken: return "apiRegisterToken" case .apiVerifyToken: return "apiVerifyToken" case .apiDeleteToken: return "apiDeleteToken" - case .apiGetNtfMessage: return "apiGetNtfMessage" - case .apiGetConnNtfMessage: return "apiGetConnNtfMessage" + case .apiGetNtfConns: return "apiGetNtfConns" + case .apiGetConnNtfMessages: return "apiGetConnNtfMessages" case .apiNewGroup: return "apiNewGroup" case .apiAddMember: return "apiAddMember" case .apiJoinGroup: return "apiJoinGroup" @@ -682,8 +682,8 @@ public enum ChatResponse: Decodable, Error { case callInvitations(callInvitations: [RcvCallInvitation]) case ntfTokenStatus(status: NtfTknStatus) case ntfToken(token: DeviceToken, status: NtfTknStatus, ntfMode: NotificationsMode, ntfServer: String) - case ntfMessages(user_: User?, connEntity_: ConnectionEntity?, expectedMsg_: NtfMsgInfo?, receivedMsg_: NtfMsgInfo?) - case connNtfMessage(receivedMsg_: NtfMsgInfo?) + case ntfConns(ntfConns: [NtfConn]) + case connNtfMessages(receivedMsgs: [NtfMsgInfo?]) case ntfMessage(user: UserRef, connEntity: ConnectionEntity, ntfMessage: NtfMsgAckInfo) case contactConnectionDeleted(user: UserRef, connection: PendingContactConnection) case contactDisabled(user: UserRef, contact: Contact) @@ -851,8 +851,8 @@ public enum ChatResponse: Decodable, Error { case .callInvitations: return "callInvitations" case .ntfTokenStatus: return "ntfTokenStatus" case .ntfToken: return "ntfToken" - case .ntfMessages: return "ntfMessages" - case .connNtfMessage: return "connNtfMessage" + case .ntfConns: return "ntfConns" + case .connNtfMessages: return "connNtfMessages" case .ntfMessage: return "ntfMessage" case .contactConnectionDeleted: return "contactConnectionDeleted" case .contactDisabled: return "contactDisabled" @@ -1029,8 +1029,8 @@ public enum ChatResponse: Decodable, Error { case let .callInvitations(invs): return String(describing: invs) case let .ntfTokenStatus(status): return String(describing: status) case let .ntfToken(token, status, ntfMode, ntfServer): return "token: \(token)\nstatus: \(status.rawValue)\nntfMode: \(ntfMode.rawValue)\nntfServer: \(ntfServer)" - case let .ntfMessages(u, connEntity, expectedMsg_, receivedMsg_): return withUser(u, "connEntity: \(String(describing: connEntity))\nexpectedMsg_: \(String(describing: expectedMsg_))\nreceivedMsg_: \(String(describing: receivedMsg_))") - case let .connNtfMessage(receivedMsg_): return "receivedMsg_: \(String(describing: receivedMsg_))" + case let .ntfConns(ntfConns): return String(describing: ntfConns) + case let .connNtfMessages(receivedMsgs): return "receivedMsgs: \(String(describing: receivedMsgs))" case let .ntfMessage(u, connEntity, ntfMessage): return withUser(u, "connEntity: \(String(describing: connEntity))\nntfMessage: \(String(describing: ntfMessage))") case let .contactConnectionDeleted(u, connection): return withUser(u, String(describing: connection)) case let .contactDisabled(u, contact): return withUser(u, String(describing: contact)) diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 45dab17cf2..1bd5673f01 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2270,16 +2270,13 @@ public enum ConnectionEntity: Decodable, Hashable { case let .userContactConnection(entityConnection, _): entityConnection } } +} + +public struct NtfConn: Decodable, Hashable { + public var user_: User? + public var connEntity_: ConnectionEntity? + public var expectedMsg_: NtfMsgInfo? - public var ntfsEnabled: Bool { - switch self { - case let .rcvDirectMsgConnection(_, contact): return contact?.chatSettings.enableNtfs == .all - case let .rcvGroupMsgConnection(_, groupInfo, _): return groupInfo.chatSettings.enableNtfs == .all - case .sndFileConnection: return false - case .rcvFileConnection: return false - case let .userContactConnection(_, userContact): return userContact.groupId == nil - } - } } public struct NtfMsgInfo: Decodable, Hashable { diff --git a/apps/ios/SimpleXChat/Notifications.swift b/apps/ios/SimpleXChat/Notifications.swift index 65bb06a7e8..a922e3a816 100644 --- a/apps/ios/SimpleXChat/Notifications.swift +++ b/apps/ios/SimpleXChat/Notifications.swift @@ -15,13 +15,14 @@ public let ntfCategoryContactConnected = "NTF_CAT_CONTACT_CONNECTED" public let ntfCategoryMessageReceived = "NTF_CAT_MESSAGE_RECEIVED" public let ntfCategoryCallInvitation = "NTF_CAT_CALL_INVITATION" public let ntfCategoryConnectionEvent = "NTF_CAT_CONNECTION_EVENT" +public let ntfCategoryManyEvents = "NTF_CAT_MANY_EVENTS" public let ntfCategoryCheckMessage = "NTF_CAT_CHECK_MESSAGE" public let appNotificationId = "chat.simplex.app.notification" let contactHidden = NSLocalizedString("Contact hidden:", comment: "notification") -public func createContactRequestNtf(_ user: any UserLike, _ contactRequest: UserContactRequest) -> UNMutableNotificationContent { +public func createContactRequestNtf(_ user: any UserLike, _ contactRequest: UserContactRequest, _ badgeCount: Int) -> UNMutableNotificationContent { let hideContent = ntfPreviewModeGroupDefault.get() == .hidden return createNotification( categoryIdentifier: ntfCategoryContactRequest, @@ -34,11 +35,12 @@ public func createContactRequestNtf(_ user: any UserLike, _ contactRequest: User hideContent ? NSLocalizedString("this contact", comment: "notification title") : contactRequest.chatViewName ), targetContentIdentifier: nil, - userInfo: ["chatId": contactRequest.id, "contactRequestId": contactRequest.apiId, "userId": user.userId] + userInfo: ["chatId": contactRequest.id, "contactRequestId": contactRequest.apiId, "userId": user.userId], + badgeCount: badgeCount ) } -public func createContactConnectedNtf(_ user: any UserLike, _ contact: Contact) -> UNMutableNotificationContent { +public func createContactConnectedNtf(_ user: any UserLike, _ contact: Contact, _ badgeCount: Int) -> UNMutableNotificationContent { let hideContent = ntfPreviewModeGroupDefault.get() == .hidden return createNotification( categoryIdentifier: ntfCategoryContactConnected, @@ -51,12 +53,13 @@ public func createContactConnectedNtf(_ user: any UserLike, _ contact: Contact) hideContent ? NSLocalizedString("this contact", comment: "notification title") : contact.chatViewName ), targetContentIdentifier: contact.id, - userInfo: ["userId": user.userId] + userInfo: ["userId": user.userId], // userInfo: ["chatId": contact.id, "contactId": contact.apiId] + badgeCount: badgeCount ) } -public func createMessageReceivedNtf(_ user: any UserLike, _ cInfo: ChatInfo, _ cItem: ChatItem) -> UNMutableNotificationContent { +public func createMessageReceivedNtf(_ user: any UserLike, _ cInfo: ChatInfo, _ cItem: ChatItem, _ badgeCount: Int) -> UNMutableNotificationContent { let previewMode = ntfPreviewModeGroupDefault.get() var title: String if case let .group(groupInfo) = cInfo, case let .groupRcv(groupMember) = cItem.chatDir { @@ -69,12 +72,13 @@ public func createMessageReceivedNtf(_ user: any UserLike, _ cInfo: ChatInfo, _ title: title, body: previewMode == .message ? hideSecrets(cItem) : NSLocalizedString("new message", comment: "notification"), targetContentIdentifier: cInfo.id, - userInfo: ["userId": user.userId] + userInfo: ["userId": user.userId], // userInfo: ["chatId": cInfo.id, "chatItemId": cItem.id] + badgeCount: badgeCount ) } -public func createCallInvitationNtf(_ invitation: RcvCallInvitation) -> UNMutableNotificationContent { +public func createCallInvitationNtf(_ invitation: RcvCallInvitation, _ badgeCount: Int) -> UNMutableNotificationContent { let text = invitation.callType.media == .video ? NSLocalizedString("Incoming video call", comment: "notification") : NSLocalizedString("Incoming audio call", comment: "notification") @@ -84,11 +88,12 @@ public func createCallInvitationNtf(_ invitation: RcvCallInvitation) -> UNMutabl title: hideContent ? contactHidden : "\(invitation.contact.chatViewName):", body: text, targetContentIdentifier: nil, - userInfo: ["chatId": invitation.contact.id, "userId": invitation.user.userId] + userInfo: ["chatId": invitation.contact.id, "userId": invitation.user.userId], + badgeCount: badgeCount ) } -public func createConnectionEventNtf(_ user: User, _ connEntity: ConnectionEntity) -> UNMutableNotificationContent { +public func createConnectionEventNtf(_ user: User, _ connEntity: ConnectionEntity, _ badgeCount: Int) -> UNMutableNotificationContent { let hideContent = ntfPreviewModeGroupDefault.get() == .hidden var title: String var body: String? = nil @@ -118,11 +123,12 @@ public func createConnectionEventNtf(_ user: User, _ connEntity: ConnectionEntit title: title, body: body, targetContentIdentifier: targetContentIdentifier, - userInfo: ["userId": user.userId] + userInfo: ["userId": user.userId], + badgeCount: badgeCount ) } -public func createErrorNtf(_ dbStatus: DBMigrationResult) -> UNMutableNotificationContent { +public func createErrorNtf(_ dbStatus: DBMigrationResult, _ badgeCount: Int) -> UNMutableNotificationContent { var title: String switch dbStatus { case .errorNotADatabase: @@ -142,14 +148,16 @@ public func createErrorNtf(_ dbStatus: DBMigrationResult) -> UNMutableNotificati } return createNotification( categoryIdentifier: ntfCategoryConnectionEvent, - title: title + title: title, + badgeCount: badgeCount ) } -public func createAppStoppedNtf() -> UNMutableNotificationContent { +public func createAppStoppedNtf(_ badgeCount: Int) -> UNMutableNotificationContent { return createNotification( categoryIdentifier: ntfCategoryConnectionEvent, - title: NSLocalizedString("Encrypted message: app is stopped", comment: "notification") + title: NSLocalizedString("Encrypted message: app is stopped", comment: "notification"), + badgeCount: badgeCount ) } @@ -159,8 +167,15 @@ private func groupMsgNtfTitle(_ groupInfo: GroupInfo, _ groupMember: GroupMember : "#\(groupInfo.displayName) \(groupMember.chatViewName):" } -public func createNotification(categoryIdentifier: String, title: String, subtitle: String? = nil, body: String? = nil, - targetContentIdentifier: String? = nil, userInfo: [AnyHashable : Any] = [:]) -> UNMutableNotificationContent { +public func createNotification( + categoryIdentifier: String, + title: String, + subtitle: String? = nil, + body: String? = nil, + targetContentIdentifier: String? = nil, + userInfo: [AnyHashable : Any] = [:], + badgeCount: Int +) -> UNMutableNotificationContent { let content = UNMutableNotificationContent() content.categoryIdentifier = categoryIdentifier content.title = title @@ -170,6 +185,7 @@ public func createNotification(categoryIdentifier: String, title: String, subtit content.userInfo = userInfo // TODO move logic of adding sound here, so it applies to background notifications too content.sound = .default + content.badge = badgeCount as NSNumber // content.interruptionLevel = .active // content.relevanceScore = 0.5 // 0-1 return content diff --git a/cabal.project b/cabal.project index 4fc8a72d45..e98f8122d0 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 967afaf802d7ea98480eaf280bfc6f35d4d43f05 + tag: a8471eed5be93e7c3741aa4742b24193c9a2d6f5 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index d68731083f..8f53d078dc 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."967afaf802d7ea98480eaf280bfc6f35d4d43f05" = "0k8m07hxfgn8h8pqrfchqd8490fvv1jf8slw8qjp0vxdpxa84n3i"; + "https://github.com/simplex-chat/simplexmq.git"."a8471eed5be93e7c3741aa4742b24193c9a2d6f5" = "093i40api0dp7rvw6f1f3pww3q5iv6mvbj577nlxp3qqcbvyh6fs"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index c875f9afa3..885d4303c8 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -204,8 +204,8 @@ _defaultSMPServers = _defaultNtfServers :: [NtfServer] _defaultNtfServers = [ "ntf://FB-Uop7RTaZZEG0ZLD2CIaTjsPh-Fw0zFAnb7QyA8Ks=@ntf2.simplex.im,5ex3mupcazy3zlky64ab27phjhijpemsiby33qzq3pliejipbtx5xgad.onion" - -- "ntf://KmpZNNXiVZJx_G2T7jRUmDFxWXM3OAnunz3uLT0tqAA=@ntf3.simplex.im,pxculznuryunjdvtvh6s6szmanyadumpbmvevgdpe4wk5c65unyt4yid.onion", - -- "ntf://CJ5o7X6fCxj2FFYRU2KuCo70y4jSqz7td2HYhLnXWbU=@ntf4.simplex.im,wtvuhdj26jwprmomnyfu5wfuq2hjkzfcc72u44vi6gdhrwxldt6xauad.onion" + -- "ntf://KmpZNNXiVZJx_G2T7jRUmDFxWXM3OAnunz3uLT0tqAA=@ntf3.simplex.im,pxculznuryunjdvtvh6s6szmanyadumpbmvevgdpe4wk5c65unyt4yid.onion", + -- "ntf://CJ5o7X6fCxj2FFYRU2KuCo70y4jSqz7td2HYhLnXWbU=@ntf4.simplex.im,wtvuhdj26jwprmomnyfu5wfuq2hjkzfcc72u44vi6gdhrwxldt6xauad.onion" ] maxImageSize :: Integer @@ -1457,26 +1457,31 @@ processChatCommand' vr = \case CRNtfTokenStatus <$> withAgent (\a -> registerNtfToken a token mode) APIVerifyToken token nonce code -> withUser $ \_ -> withAgent (\a -> verifyNtfToken a token nonce code) >> ok_ APIDeleteToken token -> withUser $ \_ -> withAgent (`deleteNtfToken` token) >> ok_ - APIGetNtfMessage nonce encNtfInfo -> withUser $ \_ -> do - (NotificationInfo {ntfConnId, ntfMsgMeta = nMsgMeta}, msg) <- withAgent $ \a -> getNotificationMessage a nonce encNtfInfo - let agentConnId = AgentConnId ntfConnId - user_ <- withStore' (`getUserByAConnId` agentConnId) - connEntity_ <- - pure user_ $>>= \user -> - withStore (\db -> Just <$> getConnectionEntity db vr user agentConnId) `catchChatError` (\e -> toView (CRChatError (Just user) e) $> Nothing) - pure - CRNtfMessages - { user_, - connEntity_, - -- Decrypted ntf meta of the expected message (the one notification was sent for) - expectedMsg_ = expectedMsgInfo <$> nMsgMeta, - -- Info of the first message retrieved by agent using GET - -- (may differ from the expected message due to, for example, coalescing or loss of notifications) - receivedMsg_ = receivedMsgInfo <$> msg - } - ApiGetConnNtfMessage (AgentConnId connId) -> withUser $ \_ -> do - msg <- withAgent $ \a -> getConnectionMessage a connId - pure $ CRConnNtfMessage (receivedMsgInfo <$> msg) + APIGetNtfConns nonce encNtfInfo -> withUser $ \user -> do + ntfInfos <- withAgent $ \a -> getNotificationConns a nonce encNtfInfo + (errs, ntfMsgs) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (getMsgConn db) (L.toList ntfInfos)) + unless (null errs) $ toView $ CRChatErrors (Just user) errs + pure $ CRNtfConns ntfMsgs + where + getMsgConn :: DB.Connection -> NotificationInfo -> IO NtfConn + getMsgConn db NotificationInfo {ntfConnId, ntfMsgMeta = nMsgMeta} = do + let agentConnId = AgentConnId ntfConnId + user_ <- getUserByAConnId db agentConnId + connEntity_ <- + pure user_ $>>= \user -> + eitherToMaybe <$> runExceptT (getConnectionEntity db vr user agentConnId) + pure $ + NtfConn + { user_, + connEntity_, + -- Decrypted ntf meta of the expected message (the one notification was sent for) + expectedMsg_ = expectedMsgInfo <$> nMsgMeta + } + ApiGetConnNtfMessages connIds -> withUser $ \_ -> do + let acIds = L.map (\(AgentConnId acId) -> acId) connIds + msgs <- lift $ withAgent' $ \a -> getConnectionMessages a acIds + let ntfMsgs = L.map (\msg -> receivedMsgInfo <$> msg) msgs + pure $ CRConnNtfMessages ntfMsgs APIGetUserProtoServers userId (AProtocolType p) -> withUserId userId $ \user -> withServerProtocol p $ do cfg@ChatConfig {defaultServers} <- asks config servers <- withFastStore' (`getProtocolServers` user) @@ -1785,7 +1790,8 @@ processChatCommand' vr = \case Nothing -> joinNewConn chatV dm Just (RcvDirectMsgConnection conn@Connection {connId, connStatus, contactConnInitiated} Nothing) | connStatus == ConnNew && contactConnInitiated -> joinNewConn chatV dm -- own connection link - | connStatus == ConnPrepared -> do -- retrying join after error + | connStatus == ConnPrepared -> do + -- retrying join after error pcc <- withFastStore $ \db -> getPendingContactConnection db userId connId joinPreparedConn (aConnId conn) pcc dm Just ent -> throwChatError $ CECommandError $ "connection exists: " <> show (connEntityInfo ent) @@ -8061,8 +8067,8 @@ chatCommandP = "/_ntf register " *> (APIRegisterToken <$> strP_ <*> strP), "/_ntf verify " *> (APIVerifyToken <$> strP <* A.space <*> strP <* A.space <*> strP), "/_ntf delete " *> (APIDeleteToken <$> strP), - "/_ntf message " *> (APIGetNtfMessage <$> strP <* A.space <*> strP), - "/_ntf conn message " *> (ApiGetConnNtfMessage <$> strP), + "/_ntf conns " *> (APIGetNtfConns <$> strP <* A.space <*> strP), + "/_ntf conn messages " *> (ApiGetConnNtfMessages <$> strP), "/_add #" *> (APIAddMember <$> A.decimal <* A.space <*> A.decimal <*> memberRole), "/_join #" *> (APIJoinGroup <$> A.decimal), "/_member role #" *> (APIMemberRole <$> A.decimal <* A.space <*> A.decimal <*> memberRole), diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 700dec9d2e..b39b4d7456 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -330,8 +330,8 @@ data ChatCommand | APIRegisterToken DeviceToken NotificationsMode | APIVerifyToken DeviceToken C.CbNonce ByteString | APIDeleteToken DeviceToken - | APIGetNtfMessage {nonce :: C.CbNonce, encNtfInfo :: ByteString} - | ApiGetConnNtfMessage {connId :: AgentConnId} + | APIGetNtfConns {nonce :: C.CbNonce, encNtfInfo :: ByteString} + | ApiGetConnNtfMessages {connIds :: NonEmpty AgentConnId} | APIAddMember GroupId ContactId GroupMemberRole | APIJoinGroup GroupId | APIMemberRole GroupId GroupMemberId GroupMemberRole @@ -745,8 +745,8 @@ data ChatResponse | CRUserContactLinkSubError {chatError :: ChatError} -- TODO delete | CRNtfTokenStatus {status :: NtfTknStatus} | CRNtfToken {token :: DeviceToken, status :: NtfTknStatus, ntfMode :: NotificationsMode, ntfServer :: NtfServer} - | CRNtfMessages {user_ :: Maybe User, connEntity_ :: Maybe ConnectionEntity, expectedMsg_ :: Maybe NtfMsgInfo, receivedMsg_ :: Maybe NtfMsgInfo} - | CRConnNtfMessage {receivedMsg_ :: Maybe NtfMsgInfo} + | CRNtfConns {ntfConns :: [NtfConn]} + | CRConnNtfMessages {receivedMsgs :: NonEmpty (Maybe NtfMsgInfo)} | CRNtfMessage {user :: User, connEntity :: ConnectionEntity, ntfMessage :: NtfMsgAckInfo} | CRContactConnectionDeleted {user :: User, connection :: PendingContactConnection} | CRRemoteHostList {remoteHosts :: [RemoteHostInfo]} @@ -1010,7 +1010,7 @@ defaultSimpleNetCfg = smpWebPort = False, tcpTimeout_ = Nothing, logTLSErrors = False - } + } data ContactSubStatus = ContactSubStatus { contact :: Contact, @@ -1063,6 +1063,13 @@ instance FromJSON ComposedMessage where parseJSON invalid = JT.prependFailure "bad ComposedMessage, " (JT.typeMismatch "Object" invalid) +data NtfConn = NtfConn + { user_ :: Maybe User, + connEntity_ :: Maybe ConnectionEntity, + expectedMsg_ :: Maybe NtfMsgInfo + } + deriving (Show) + data NtfMsgInfo = NtfMsgInfo {msgId :: Text, msgTs :: UTCTime} deriving (Show) @@ -1535,6 +1542,8 @@ $(JQ.deriveJSON defaultJSON ''UserProfileUpdateSummary) $(JQ.deriveJSON defaultJSON ''NtfMsgInfo) +$(JQ.deriveJSON defaultJSON ''NtfConn) + $(JQ.deriveJSON defaultJSON ''NtfMsgAckInfo) $(JQ.deriveJSON defaultJSON ''SwitchProgress) diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 5ecde0a99c..ade36476c7 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -325,8 +325,8 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRContactConnectionDeleted u PendingContactConnection {pccConnId} -> ttyUser u ["connection :" <> sShow pccConnId <> " deleted"] CRNtfTokenStatus status -> ["device token status: " <> plain (smpEncode status)] CRNtfToken _ status mode srv -> ["device token status: " <> plain (smpEncode status) <> ", notifications mode: " <> plain (strEncode mode) <> ", server: " <> sShow srv] - CRNtfMessages {} -> [] - CRConnNtfMessage {} -> [] + CRNtfConns {} -> [] + CRConnNtfMessages {} -> [] CRNtfMessage {} -> [] CRCurrentRemoteHost rhi_ -> [ maybe From 37b78edb91f60a9f78d727c5e78a49c9a4885f41 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Mon, 28 Oct 2024 18:18:26 +0400 Subject: [PATCH 051/567] ios: move Network and servers settings modules to folder (#5110) --- .../AdvancedNetworkSettings.swift | 0 .../NetworkAndServers.swift | 0 .../ProtocolServerView.swift | 0 .../ProtocolServersView.swift | 0 .../ScanProtocolServer.swift | 0 apps/ios/SimpleX.xcodeproj/project.pbxproj | 18 +++++++++++++----- 6 files changed, 13 insertions(+), 5 deletions(-) rename apps/ios/Shared/Views/UserSettings/{ => NetworkAndServers}/AdvancedNetworkSettings.swift (100%) rename apps/ios/Shared/Views/UserSettings/{ => NetworkAndServers}/NetworkAndServers.swift (100%) rename apps/ios/Shared/Views/UserSettings/{ => NetworkAndServers}/ProtocolServerView.swift (100%) rename apps/ios/Shared/Views/UserSettings/{ => NetworkAndServers}/ProtocolServersView.swift (100%) rename apps/ios/Shared/Views/UserSettings/{ => NetworkAndServers}/ScanProtocolServer.swift (100%) diff --git a/apps/ios/Shared/Views/UserSettings/AdvancedNetworkSettings.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift similarity index 100% rename from apps/ios/Shared/Views/UserSettings/AdvancedNetworkSettings.swift rename to apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift similarity index 100% rename from apps/ios/Shared/Views/UserSettings/NetworkAndServers.swift rename to apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift diff --git a/apps/ios/Shared/Views/UserSettings/ProtocolServerView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift similarity index 100% rename from apps/ios/Shared/Views/UserSettings/ProtocolServerView.swift rename to apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift diff --git a/apps/ios/Shared/Views/UserSettings/ProtocolServersView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift similarity index 100% rename from apps/ios/Shared/Views/UserSettings/ProtocolServersView.swift rename to apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift diff --git a/apps/ios/Shared/Views/UserSettings/ScanProtocolServer.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ScanProtocolServer.swift similarity index 100% rename from apps/ios/Shared/Views/UserSettings/ScanProtocolServer.swift rename to apps/ios/Shared/Views/UserSettings/NetworkAndServers/ScanProtocolServer.swift diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 2b1160061c..7aaa439adb 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -912,10 +912,9 @@ 5CB924DF27A8678B00ACCCDD /* UserSettings */ = { isa = PBXGroup; children = ( + 643B3B4C2CCFD34B0083A2CF /* NetworkAndServers */, 5CB924D627A8563F00ACCCDD /* SettingsView.swift */, 5CB346E62868D76D001FD2EF /* NotificationsView.swift */, - 5C9C2DA6289957AE00CC63B1 /* AdvancedNetworkSettings.swift */, - 5C9C2DA82899DA6F00CC63B1 /* NetworkAndServers.swift */, 5CADE79929211BB900072E13 /* PreferencesView.swift */, 5C5DB70D289ABDD200730FFF /* AppearanceSettings.swift */, 5C05DF522840AA1D00C683F9 /* CallSettings.swift */, @@ -923,9 +922,6 @@ 5CC036DF29C488D500C0EF20 /* HiddenProfileView.swift */, 5C577F7C27C83AA10006112D /* MarkdownHelp.swift */, 5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */, - 5C93292E29239A170090FFF9 /* ProtocolServersView.swift */, - 5C93293029239BED0090FFF9 /* ProtocolServerView.swift */, - 5C9329402929248A0090FFF9 /* ScanProtocolServer.swift */, 5CB2084E28DA4B4800D024EC /* RTCServers.swift */, 64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */, 18415845648CA4F5A8BCA272 /* UserProfilesView.swift */, @@ -1056,6 +1052,18 @@ path = Database; sourceTree = ""; }; + 643B3B4C2CCFD34B0083A2CF /* NetworkAndServers */ = { + isa = PBXGroup; + children = ( + 5C9329402929248A0090FFF9 /* ScanProtocolServer.swift */, + 5C93293029239BED0090FFF9 /* ProtocolServerView.swift */, + 5C93292E29239A170090FFF9 /* ProtocolServersView.swift */, + 5C9C2DA6289957AE00CC63B1 /* AdvancedNetworkSettings.swift */, + 5C9C2DA82899DA6F00CC63B1 /* NetworkAndServers.swift */, + ); + path = NetworkAndServers; + sourceTree = ""; + }; 6440CA01288AEC770062C672 /* Group */ = { isa = PBXGroup; children = ( From 24090fe3500ed596c0d8610b91e59c8037a4a7d1 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 1 Nov 2024 00:11:26 +0700 Subject: [PATCH 052/567] android, desktop: update to Compose 1.7.0 (#5038) * docs: correction * android, desktop: update to Compose 1.7.0 - support image drag-and-drop from other applications right to a chat (with and without transparent pixels - will be png or jpg) * stable * workaround --------- Co-authored-by: Evgeny Poberezkin --- .../common/platform/Modifier.android.kt | 2 +- .../platform/PlatformTextField.android.kt | 11 ++- .../common/views/call/CallView.android.kt | 4 +- .../views/chatlist/ChatListView.android.kt | 7 +- .../helpers/WorkaroundFocusSearchLayout.kt | 41 +++++++++++ .../chat/simplex/common/platform/Modifier.kt | 2 +- .../chat/simplex/common/views/WelcomeView.kt | 1 + .../simplex/common/views/chat/ChatView.kt | 9 +-- .../simplex/common/views/chat/SendMsgView.kt | 7 +- .../views/helpers/DefaultBasicTextField.kt | 3 - .../helpers/ExposedDropDownSettingRow.kt | 4 +- .../common/views/helpers/TextEditor.kt | 1 + .../common/views/newchat/NewChatView.kt | 1 + .../simplex/common/platform/Images.desktop.kt | 32 +++++++++ .../common/platform/Modifier.desktop.kt | 72 ++++++++++++++++--- .../platform/PlatformTextField.desktop.kt | 4 +- .../chatlist/ChatListNavLinkView.desktop.kt | 18 ++--- .../common/views/helpers/Utils.desktop.kt | 4 +- apps/multiplatform/gradle.properties | 4 +- 19 files changed, 176 insertions(+), 51 deletions(-) create mode 100644 apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/WorkaroundFocusSearchLayout.kt diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Modifier.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Modifier.android.kt index 2ff2a3e021..2aa66bc69b 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Modifier.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Modifier.android.kt @@ -21,7 +21,7 @@ actual fun ProvideWindowInsets( actual fun Modifier.desktopOnExternalDrag( enabled: Boolean, onFiles: (List) -> Unit, - onImage: (Painter) -> Unit, + onImage: (File) -> Unit, onText: (String) -> Unit ): Modifier = this diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt index 0b17a3aadf..5365db6a4c 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.core.graphics.drawable.DrawableCompat +import androidx.core.view.children import androidx.core.view.inputmethod.EditorInfoCompat import androidx.core.view.inputmethod.InputConnectionCompat import androidx.core.widget.doAfterTextChanged @@ -94,8 +95,8 @@ actual fun PlatformTextField( } val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl - AndroidView(modifier = Modifier, factory = { - val editText = @SuppressLint("AppCompatCustomView") object: EditText(it) { + AndroidView(modifier = Modifier, factory = { context -> + val editText = @SuppressLint("AppCompatCustomView") object: EditText(context) { override fun setOnReceiveContentListener( mimeTypes: Array?, listener: OnReceiveContentListener? @@ -148,8 +149,12 @@ actual fun PlatformTextField( } } editText.doAfterTextChanged { text -> if (composeState.value.preview is ComposePreview.VoicePreview && text.toString() != "") editText.setText("") } - editText + val workaround = WorkaroundFocusSearchLayout(context) + workaround.addView(editText) + workaround.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + workaround }) { + val it = it.children.first() as EditText it.setTextColor(textColor.toArgb()) it.setHintTextColor(hintColor.toArgb()) it.hint = placeholder diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt index 3fc5620222..f3a9be6132 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt @@ -22,7 +22,6 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* -import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.snapshots.SnapshotStateList @@ -411,6 +410,7 @@ private fun ControlButton(icon: Painter, iconText: StringResource, enabled: Bool @Composable private fun ControlButtonWrap(enabled: Boolean = true, action: () -> Unit, background: Color = controlButtonsBackground(), size: Dp, content: @Composable () -> Unit) { + val ripple = remember { ripple(bounded = false, radius = size / 2, color = background.lighter(0.1f)) } Box( Modifier .background(background, CircleShape) @@ -419,7 +419,7 @@ private fun ControlButtonWrap(enabled: Boolean = true, action: () -> Unit, backg onClick = action, role = Role.Button, interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(bounded = false, radius = size / 2, color = background.lighter(0.1f)), + indication = ripple, enabled = enabled ), contentAlignment = Alignment.Center diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.android.kt index e0fd81f7b6..4681a5a64d 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.android.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.* -import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -40,8 +39,8 @@ actual fun ActiveCallInteractiveArea(call: Call) { val onClick = { platform.androidStartCallActivity(false) } Box(Modifier.offset(y = CALL_TOP_OFFSET).height(CALL_INTERACTIVE_AREA_HEIGHT)) { val source = remember { MutableInteractionSource() } - val indication = rememberRipple(bounded = true, 3000.dp) - Box(Modifier.height(CALL_TOP_GREEN_LINE_HEIGHT).clickable(onClick = onClick, indication = indication, interactionSource = source)) { + val ripple = remember { ripple(bounded = true, 3000.dp) } + Box(Modifier.height(CALL_TOP_GREEN_LINE_HEIGHT).clickable(onClick = onClick, indication = ripple, interactionSource = source)) { GreenLine(call) } Box( @@ -50,7 +49,7 @@ actual fun ActiveCallInteractiveArea(call: Call) { .size(CALL_BOTTOM_ICON_HEIGHT) .background(SimplexGreen, CircleShape) .clip(CircleShape) - .clickable(onClick = onClick, indication = indication, interactionSource = source) + .clickable(onClick = onClick, indication = ripple, interactionSource = source) .align(Alignment.BottomCenter), contentAlignment = Alignment.Center ) { diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/WorkaroundFocusSearchLayout.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/WorkaroundFocusSearchLayout.kt new file mode 100644 index 0000000000..d111b99385 --- /dev/null +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/WorkaroundFocusSearchLayout.kt @@ -0,0 +1,41 @@ +package chat.simplex.common.views.helpers + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.widget.FrameLayout + +/** + * A workaround for the ANR issue on Compose 1.7.x. + * https://issuetracker.google.com/issues/369354336 + * Code from: + * https://issuetracker.google.com/issues/369354336#comment8 +*/ +class WorkaroundFocusSearchLayout : FrameLayout { + + constructor( + context: Context, + ) : super(context) + + constructor( + context: Context, + attrs: AttributeSet?, + ) : super(context, attrs) + + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int, + ) : super(context, attrs, defStyleAttr) + + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int, + defStyleRes: Int, + ) : super(context, attrs, defStyleAttr, defStyleRes) + + override fun focusSearch(focused: View?, direction: Int): View? { + return null + } +} \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Modifier.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Modifier.kt index 6683ea7d33..5281fcb1ad 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Modifier.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Modifier.kt @@ -27,7 +27,7 @@ expect fun ProvideWindowInsets( expect fun Modifier.desktopOnExternalDrag( enabled: Boolean = true, onFiles: (List) -> Unit = {}, - onImage: (Painter) -> Unit = {}, + onImage: (File) -> Unit = {}, onText: (String) -> Unit = {} ): Modifier diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt index f5dcc6b54a..0d5350dbe0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt @@ -289,6 +289,7 @@ fun ProfileNameField(name: MutableState, placeholder: String = "", isVal enabled = true, isError = false, interactionSource = remember { MutableInteractionSource() }, + colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.Unspecified) ) } ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index d89782148a..570f763e99 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -642,14 +642,7 @@ fun ChatLayout( .desktopOnExternalDrag( enabled = remember(attachmentDisabled.value, chatInfo.value?.userCanSend) { mutableStateOf(!attachmentDisabled.value && chatInfo.value?.userCanSend == true) }.value, onFiles = { paths -> composeState.onFilesAttached(paths.map { it.toURI() }) }, - onImage = { - // TODO: file is not saved anywhere?! - val tmpFile = File.createTempFile("image", ".bmp", tmpDir) - tmpFile.deleteOnExit() - chatModel.filesToDelete.add(tmpFile) - val uri = tmpFile.toURI() - CoroutineScope(Dispatchers.IO).launch { composeState.processPickedMedia(listOf(uri), null) } - }, + onImage = { file -> CoroutineScope(Dispatchers.IO).launch { composeState.processPickedMedia(listOf(file.toURI()), null) } }, onText = { // Need to parse HTML in order to correctly display the content //composeState.value = composeState.value.copy(message = composeState.value.message + it) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt index 162e753b18..76c4fc4a62 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt @@ -7,7 +7,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.* import androidx.compose.material.* -import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.* @@ -423,6 +422,7 @@ private fun SendMsgButton( onLongClick: (() -> Unit)? = null ) { val interactionSource = remember { MutableInteractionSource() } + val ripple = remember { ripple(bounded = false, radius = 24.dp) } Box( modifier = Modifier.requiredSize(36.dp) .combinedClickable( @@ -431,7 +431,7 @@ private fun SendMsgButton( enabled = enabled, role = Role.Button, interactionSource = interactionSource, - indication = rememberRipple(bounded = false, radius = 24.dp) + indication = ripple ) .onRightClick { onLongClick?.invoke() }, contentAlignment = Alignment.Center @@ -454,6 +454,7 @@ private fun SendMsgButton( @Composable private fun StartLiveMessageButton(enabled: Boolean, onClick: () -> Unit) { val interactionSource = remember { MutableInteractionSource() } + val ripple = remember { ripple(bounded = false, radius = 24.dp) } Box( modifier = Modifier.requiredSize(36.dp) .clickable( @@ -461,7 +462,7 @@ private fun StartLiveMessageButton(enabled: Boolean, onClick: () -> Unit) { enabled = enabled, role = Role.Button, interactionSource = interactionSource, - indication = rememberRipple(bounded = false, radius = 24.dp) + indication = ripple ), contentAlignment = Alignment.Center ) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultBasicTextField.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultBasicTextField.kt index a6f0d2c9b6..b0366cceb3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultBasicTextField.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultBasicTextField.kt @@ -3,7 +3,6 @@ package chat.simplex.common.views.helpers import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.defaultMinSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.shape.ZeroCornerSize import androidx.compose.foundation.text.* import androidx.compose.material.* @@ -22,13 +21,11 @@ import androidx.compose.ui.text.input.* import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* import chat.simplex.common.views.database.PassphraseStrength -import chat.simplex.common.views.database.validKey import chat.simplex.res.MR import kotlinx.coroutines.delay import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch -@OptIn(ExperimentalComposeUiApi::class) @Composable fun DefaultBasicTextField( modifier: Modifier, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt index 8349841973..7ed91adbd9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.* -import androidx.compose.material.ripple.rememberRipple import dev.icerock.moko.resources.compose.painterResource import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -107,6 +106,7 @@ fun ExposedDropDownSettingWithIcon( expanded.value = !expanded.value && enabled.value } ) { + val ripple = remember { ripple(bounded = false, radius = boxSize / 2, color = background.lighter(0.1f)) } Box( Modifier .background(background, CircleShape) @@ -115,7 +115,7 @@ fun ExposedDropDownSettingWithIcon( onClick = {}, role = Role.Button, interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(bounded = false, radius = boxSize / 2, color = background.lighter(0.1f)), + indication = ripple, enabled = enabled.value ), contentAlignment = Alignment.Center diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/TextEditor.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/TextEditor.kt index 45accccc59..ab7e562697 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/TextEditor.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/TextEditor.kt @@ -87,6 +87,7 @@ fun TextEditor( enabled = true, isError = false, interactionSource = remember { MutableInteractionSource() }, + colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.Unspecified) ) } ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index 8acddc2aa6..5298e11e75 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -616,6 +616,7 @@ fun LinkTextView(link: String, share: Boolean) { enabled = false, isError = false, interactionSource = remember { MutableInteractionSource() }, + colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.Unspecified) ) }) } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt index e65adea70e..0f53adaf0b 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt @@ -206,3 +206,35 @@ fun BufferedImage.flip(vertically: Boolean, horizontally: Boolean): BufferedImag } return AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR).filter(this, null) } + +fun BufferedImage.saveInTmpFile(): File? { + val formats = arrayOf("jpg", "png") + for (format in formats) { + val tmpFile = File.createTempFile("image", ".$format", tmpDir) + try { + // May fail on JPG, using PNG as an alternative + val success = ImageIO.write(this, format, tmpFile) + if (success) { + tmpFile.deleteOnExit() + chatModel.filesToDelete.add(tmpFile) + return tmpFile + } else { + tmpFile.delete() + } + } catch (e: Exception) { + Log.e(TAG, e.stackTraceToString()) + tmpFile.delete() + return null + } + } + return null +} + +fun BufferedImage.hasAlpha(): Boolean { + for (x in 0 until width) { + for (y in 0 until height) { + if (getRGB(x, y) == 0) return true + } + } + return false +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Modifier.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Modifier.desktop.kt index 97f8bc129a..150885cbc8 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Modifier.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Modifier.desktop.kt @@ -1,10 +1,17 @@ package chat.simplex.common.platform import androidx.compose.foundation.contextMenuOpenDetector +import androidx.compose.foundation.draganddrop.dragAndDropTarget import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.* -import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.draganddrop.* +import androidx.compose.ui.draganddrop.DragData import androidx.compose.ui.input.pointer.* +import java.awt.Image +import java.awt.datatransfer.DataFlavor +import java.awt.datatransfer.Transferable +import java.awt.image.BufferedImage import java.io.File import java.net.URI @@ -23,16 +30,61 @@ actual fun ProvideWindowInsets( actual fun Modifier.desktopOnExternalDrag( enabled: Boolean, onFiles: (List) -> Unit, - onImage: (Painter) -> Unit, + onImage: (File) -> Unit, onText: (String) -> Unit -): Modifier = -onExternalDrag(enabled) { - when(val data = it.dragData) { - // data.readFiles() returns filePath in URI format (where spaces replaces with %20). But it's an error-prone idea to work later - // with such format when everywhere we use absolutePath in File() format - is DragData.FilesList -> onFiles(data.readFiles().map { URI.create(it).toFile() }) - is DragData.Image -> onImage(data.readImage()) - is DragData.Text -> onText(data.readText()) +): Modifier { + val callback = remember { + object : DragAndDropTarget { + override fun onDrop(event: DragAndDropEvent): Boolean { + when (val data = event.dragData()) { + // data.readFiles() returns filePath in URI format (where spaces replaces with %20). But it's an error-prone idea to work later + // with such format when everywhere we use absolutePath in File() format + is DragData.FilesList -> { + val files = data.readFiles() + // When dragging and dropping an image from browser, it comes to FilesList section but no files inside + if (files.isNotEmpty()) { + onFiles(files.map { URI.create(it).toFile() }) + } else { + try { + val transferable = event.awtTransferable + if (transferable.isDataFlavorSupported(DataFlavor.imageFlavor)) { + onImage(DragDataImageImpl(transferable).bufferedImage().saveInTmpFile() ?: return false) + } else { + return false + } + } catch (e: Exception) { + Log.e(TAG, e.stackTraceToString()) + return false + } + } + } + is DragData.Image -> onImage(DragDataImageImpl(event.awtTransferable).bufferedImage().saveInTmpFile() ?: return false) + is DragData.Text -> onText(data.readText()) + } + return true + } + } + } + return dragAndDropTarget(shouldStartDragAndDrop = { true }, target = callback) +} + +// Copied from AwtDragData and modified +private class DragDataImageImpl(private val transferable: Transferable) { + fun bufferedImage(): BufferedImage = (transferable.getTransferData(DataFlavor.imageFlavor) as Image).bufferedImage() + private fun Image.bufferedImage(): BufferedImage { + if (this is BufferedImage && hasAlpha()) { + // Such image cannot be drawn as JPG, only PNG + return this + } + // Creating non-transparent image which can be drawn as JPG + val bufferedImage = BufferedImage(getWidth(null), getHeight(null), BufferedImage.TYPE_INT_RGB) + val g2 = bufferedImage.createGraphics() + try { + g2.drawImage(this, 0, 0, null) + } finally { + g2.dispose() + } + return bufferedImage } } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt index 5b0db7c94a..e37e99f3e9 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt @@ -10,6 +10,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.input.key.* import androidx.compose.ui.platform.* @@ -109,7 +110,7 @@ actual fun PlatformTextField( maxLines = 16, keyboardOptions = KeyboardOptions.Default.copy( capitalization = KeyboardCapitalization.Sentences, - autoCorrect = true + autoCorrectEnabled = true ), modifier = Modifier .padding(vertical = 4.dp) @@ -193,6 +194,7 @@ actual fun PlatformTextField( interactionSource = remember { MutableInteractionSource() }, contentPadding = PaddingValues(), visualTransformation = VisualTransformation.None, + colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.Unspecified) ) Spacer(Modifier.height(10.dp)) } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.desktop.kt index 189f1842dd..9789fa3d1a 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.desktop.kt @@ -10,20 +10,20 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.drawscope.ContentDrawScope +import androidx.compose.ui.node.DelegatableNode +import androidx.compose.ui.node.DrawModifierNode import androidx.compose.ui.unit.dp import chat.simplex.common.platform.onRightClick import chat.simplex.common.views.helpers.* -object NoIndication : Indication { - private object NoIndicationInstance : IndicationInstance { - override fun ContentDrawScope.drawIndication() { - drawContent() - } - } - @Composable - override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance { - return NoIndicationInstance +object NoIndication : IndicationNodeFactory { + // Should be as a class, not an object. Otherwise, crash + private class NoIndicationInstance : Modifier.Node(), DrawModifierNode { + override fun ContentDrawScope.draw() { drawContent() } } + override fun create(interactionSource: InteractionSource): DelegatableNode = NoIndicationInstance() + override fun hashCode(): Int = -1 + override fun equals(other: Any?) = other === this } @Composable diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt index 641ddb8744..d541a5780e 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt @@ -156,7 +156,9 @@ actual fun getFileSize(uri: URI): Long? = uri.toFile().length() actual fun getBitmapFromUri(uri: URI, withAlertOnException: Boolean): ImageBitmap? = try { - ImageIO.read(uri.inputStream()).toComposeImageBitmap() + uri.inputStream().use { + ImageIO.read(it).toComposeImageBitmap() + } } catch (e: Exception) { Log.e(TAG, "Error while decoding drawable: ${e.stackTraceToString()}") if (withAlertOnException) showImageDecodingException() diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 32bd7579c8..d795257a76 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -21,8 +21,6 @@ kotlin.code.style=official # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library android.nonTransitiveRClass=true -# Automatically convert third-party libraries to use AndroidX -android.enableJetifier=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 @@ -34,4 +32,4 @@ desktop.version_code=74 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 -compose.version=1.6.1 +compose.version=1.7.0 From 4162bccc468a011ec06be99aab8fee1753f75132 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 1 Nov 2024 00:26:17 +0700 Subject: [PATCH 053/567] multiplatform: edge to edge design (#5051) * multiplatform: insets * more features and better performance * calls and removed unused code * changes * removed logs * status and nav bar colors * chatList and newChatSheet search fields * overhaul * search fields, devtools, chatlist, newchatsheet, onehand on desktop, scrollbars * android, desktop: update to Compose 1.7.0 - support image drag-and-drop from other applications right to a chat (with and without transparent pixels - will be png or jpg) * stable * workaround * changes * ideal adapting height layout * dropdownmenu, userpicker, onehandui, call layout, columns * rename bars properties and strings * faster update and better layout * gallery in landscape with cutout * better cutout * 1% step on slider * app bar moves to bottom in one hand ui * default alpha * changes * userpicker colors * changes * blur * fix wrong drawing area in chatview * fix * fixed differently * changes * changes * android fix * Revert "android fix" This reverts commit 7d417afd9b011045b68921546c6218a0f97912aa. * changes * changes * blur * swap * no logs * fix build * old Android support * fix position of menu * disable blur on Android 12 * call button padding * useless code * fix padding in group info view * rename * rename * newline * one more fix * changes --------- Co-authored-by: Evgeny Poberezkin --- .../java/chat/simplex/app/MainActivity.kt | 14 +- .../main/java/chat/simplex/app/SimplexApp.kt | 92 +-- apps/multiplatform/common/build.gradle.kts | 1 - .../common/platform/Modifier.android.kt | 12 - .../platform/PlatformTextField.android.kt | 10 +- .../common/platform/Resources.android.kt | 10 +- .../platform/ScrollableColumn.android.kt | 159 ++++- .../simplex/common/platform/UI.android.kt | 30 +- .../common/views/call/CallView.android.kt | 19 +- .../views/chat/item/CIImageView.android.kt | 9 - .../views/chatlist/ChatListView.android.kt | 27 +- .../views/chatlist/UserPicker.android.kt | 133 ++-- .../views/helpers/GetImageView.android.kt | 2 + .../views/usersettings/Appearance.android.kt | 14 +- .../usersettings/SettingsView.android.kt | 3 +- .../kotlin/chat/simplex/common/App.kt | 119 ++-- .../chat/simplex/common/model/ChatModel.kt | 3 + .../chat/simplex/common/model/SimpleXAPI.kt | 9 +- .../chat/simplex/common/platform/Modifier.kt | 9 - .../chat/simplex/common/platform/Platform.kt | 5 +- .../common/platform/ScrollableColumn.kt | 34 + .../chat/simplex/common/ui/theme/Theme.kt | 36 +- .../simplex/common/ui/theme/ThemeManager.kt | 7 +- .../chat/simplex/common/views/TerminalView.kt | 147 ++--- .../chat/simplex/common/views/WelcomeView.kt | 95 ++- .../views/call/IncomingCallAlertView.kt | 2 +- .../simplex/common/views/chat/ChatInfoView.kt | 5 +- .../common/views/chat/ChatItemInfoView.kt | 8 +- .../simplex/common/views/chat/ChatView.kt | 419 ++++++------- .../simplex/common/views/chat/ComposeView.kt | 6 +- .../common/views/chat/ContactPreferences.kt | 5 +- .../simplex/common/views/chat/ScanCodeView.kt | 7 +- .../views/chat/SelectableChatItemToolbars.kt | 12 +- .../simplex/common/views/chat/SendMsgView.kt | 6 +- .../common/views/chat/VerifyCodeView.kt | 6 +- .../views/chat/group/AddGroupMembersView.kt | 5 +- .../views/chat/group/GroupChatInfoView.kt | 15 +- .../common/views/chat/group/GroupLinkView.kt | 4 +- .../views/chat/group/GroupMemberInfoView.kt | 5 +- .../views/chat/group/GroupPreferences.kt | 4 +- .../views/chat/group/GroupProfileView.kt | 8 +- .../views/chat/group/WelcomeMessageView.kt | 4 +- .../common/views/chat/item/FramedItemView.kt | 67 ++ .../views/chat/item/ImageFullScreenView.kt | 13 +- .../common/views/chatlist/ChatListView.kt | 393 +++++++----- .../views/chatlist/ServersSummaryView.kt | 20 +- .../common/views/chatlist/ShareListView.kt | 122 ++-- .../common/views/chatlist/UserPicker.kt | 22 +- .../common/views/database/ChatArchiveView.kt | 4 +- .../views/database/DatabaseEncryptionView.kt | 2 +- .../views/database/DatabaseErrorView.kt | 5 +- .../common/views/database/DatabaseView.kt | 4 +- .../common/views/helpers/AppBarTitle.kt | 71 +++ .../common/views/helpers/BlurModifier.kt | 139 +++++ .../common/views/helpers/ChatWallpaper.kt | 93 +-- .../views/helpers/ChooseAttachmentView.kt | 2 + .../common/views/helpers/CloseSheetBar.kt | 181 ------ .../common/views/helpers/CollapsingAppBar.kt | 56 +- .../views/helpers/DefaultDropdownMenu.kt | 3 +- .../common/views/helpers/DefaultTopAppBar.kt | 243 ++++++-- .../simplex/common/views/helpers/ModalView.kt | 60 +- .../common/views/helpers/SearchTextField.kt | 25 +- .../common/views/helpers/TextEditor.kt | 1 - .../common/views/helpers/ThemeModeEditor.kt | 10 +- .../views/migration/MigrateFromDevice.kt | 4 +- .../common/views/migration/MigrateToDevice.kt | 4 +- .../views/newchat/AddContactLearnMore.kt | 4 +- .../common/views/newchat/AddGroupView.kt | 16 +- .../newchat/ContactConnectionInfoView.kt | 6 +- .../common/views/newchat/NewChatSheet.kt | 583 ++++++++++-------- .../common/views/newchat/NewChatView.kt | 19 +- .../views/onboarding/CreateSimpleXAddress.kt | 8 +- .../common/views/onboarding/HowItWorks.kt | 6 +- .../views/onboarding/LinkAMobileView.kt | 55 +- .../views/onboarding/SetNotificationsMode.kt | 11 +- .../onboarding/SetupDatabasePassphrase.kt | 7 +- .../common/views/onboarding/SimpleXInfo.kt | 19 +- .../common/views/onboarding/WhatsNewView.kt | 3 +- .../common/views/remote/ConnectDesktopView.kt | 8 +- .../common/views/remote/ConnectMobileView.kt | 12 +- .../usersettings/AdvancedNetworkSettings.kt | 13 +- .../common/views/usersettings/Appearance.kt | 143 ++++- .../common/views/usersettings/CallSettings.kt | 2 +- .../views/usersettings/DeveloperView.kt | 10 +- .../common/views/usersettings/HelpView.kt | 6 +- .../views/usersettings/HiddenProfileView.kt | 5 +- .../views/usersettings/NetworkAndServers.kt | 11 +- .../usersettings/NotificationsSettingsView.kt | 12 +- .../common/views/usersettings/Preferences.kt | 4 +- .../views/usersettings/PrivacySettings.kt | 8 +- .../views/usersettings/ProtocolServerView.kt | 5 +- .../views/usersettings/ProtocolServersView.kt | 5 +- .../views/usersettings/ScanProtocolServer.kt | 6 +- .../usersettings/SetDeliveryReceiptsView.kt | 5 +- .../common/views/usersettings/SettingsView.kt | 20 +- .../usersettings/UserAddressLearnMore.kt | 6 +- .../views/usersettings/UserProfileView.kt | 6 +- .../views/usersettings/UserProfilesView.kt | 10 +- .../views/usersettings/VersionInfoView.kt | 4 +- .../commonMain/resources/MR/base/strings.xml | 3 + .../kotlin/chat/simplex/common/DesktopApp.kt | 15 +- .../common/platform/Modifier.desktop.kt | 11 - .../platform/PlatformTextField.desktop.kt | 40 +- .../platform/ScrollableColumn.desktop.kt | 210 ++++++- .../views/chatlist/ChatListView.desktop.kt | 179 ++++-- .../views/chatlist/UserPicker.desktop.kt | 10 +- .../views/usersettings/Appearance.desktop.kt | 13 +- .../usersettings/SettingsView.desktop.kt | 3 +- 108 files changed, 2651 insertions(+), 1935 deletions(-) create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AppBarTitle.kt create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/BlurModifier.kt delete mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt index f29c0c3387..2d2829f1f2 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt @@ -4,8 +4,10 @@ import android.content.Context import android.content.Intent import android.net.Uri import android.os.* +import android.view.View import android.view.WindowManager import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge import androidx.compose.ui.platform.ClipboardManager import androidx.fragment.app.FragmentActivity import chat.simplex.app.model.NtfManager @@ -13,7 +15,6 @@ import chat.simplex.app.model.NtfManager.getUserIdFromIntent import chat.simplex.common.* import chat.simplex.common.helpers.* import chat.simplex.common.model.* -import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chatlist.* import chat.simplex.common.views.helpers.* @@ -24,13 +25,21 @@ import kotlinx.coroutines.* import java.lang.ref.WeakReference class MainActivity: FragmentActivity() { + companion object { + const val OLD_ANDROID_UI_FLAGS = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + } override fun onCreate(savedInstanceState: Bundle?) { mainActivity = WeakReference(this) platform.androidSetNightModeIfSupported() val c = CurrentColors.value.colors - platform.androidSetStatusAndNavBarColors(c.isLight, c.background, !appPrefs.oneHandUI.get(), appPrefs.oneHandUI.get()) + platform.androidSetStatusAndNavigationBarAppearance(c.isLight, c.isLight) applyAppLocale(ChatModel.controller.appPrefs.appLanguage) + // This flag makes status bar and navigation bar fully transparent. But on API level < 30 it breaks insets entirely + // https://issuetracker.google.com/issues/236862874 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + window.setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS) + } super.onCreate(savedInstanceState) // testJson() // When call ended and orientation changes, it re-process old intent, it's unneeded. @@ -47,6 +56,7 @@ class MainActivity: FragmentActivity() { WindowManager.LayoutParams.FLAG_SECURE ) } + enableEdgeToEdge() setContent { AppScreen() } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt index 40e8ffa9bc..13f9b888b9 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt @@ -7,6 +7,7 @@ import chat.simplex.common.platform.Log import android.content.Intent import android.content.pm.ActivityInfo import android.os.* +import android.view.View import androidx.compose.animation.core.* import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -16,6 +17,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.core.view.ViewCompat import androidx.lifecycle.* import androidx.work.* +import chat.simplex.app.MainActivity.Companion.OLD_ANDROID_UI_FLAGS import chat.simplex.app.model.NtfManager import chat.simplex.app.model.NtfManager.AcceptCallAction import chat.simplex.app.views.call.CallActivity @@ -26,7 +28,6 @@ import chat.simplex.common.model.ChatModel.withChats import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.call.* -import chat.simplex.common.views.chatlist.statusBarColorAfterCall import chat.simplex.common.views.helpers.* import chat.simplex.common.views.onboarding.OnboardingStage import com.jakewharton.processphoenix.ProcessPhoenix @@ -274,79 +275,32 @@ class SimplexApp: Application(), LifecycleEventObserver { uiModeManager.setApplicationNightMode(mode) } - override fun androidSetDrawerStatusAndNavBarColor( - isLight: Boolean, - drawerShadingColor: Color, - toolbarOnTop: Boolean, - navBarColor: Color, - ) { - val window = mainActivity.get()?.window ?: return - - @Suppress("DEPRECATION") - val windowInsetController = ViewCompat.getWindowInsetsController(window.decorView) - // Blend status bar color to the animated color - val colors = CurrentColors.value.colors - val baseBackgroundColor = if (toolbarOnTop) colors.background.mixWith(colors.onBackground, 0.97f) else colors.background - var statusBar = baseBackgroundColor.mixWith(drawerShadingColor.copy(1f), 1 - drawerShadingColor.alpha).toArgb() - var statusBarLight = isLight - - // SimplexGreen while in call - if (window.statusBarColor == SimplexGreen.toArgb()) { - statusBarColorAfterCall.intValue = statusBar - statusBar = SimplexGreen.toArgb() - statusBarLight = false - } - window.statusBarColor = statusBar - val navBar = navBarColor.toArgb() - if (windowInsetController?.isAppearanceLightStatusBars != statusBarLight) { - windowInsetController?.isAppearanceLightStatusBars = statusBarLight - } - if (window.navigationBarColor != navBar) { - window.navigationBarColor = navBar - } - if (windowInsetController?.isAppearanceLightNavigationBars != isLight) { - windowInsetController?.isAppearanceLightNavigationBars = isLight - } - } - - override fun androidSetStatusAndNavBarColors(isLight: Boolean, backgroundColor: Color, hasTop: Boolean, hasBottom: Boolean) { + override fun androidSetStatusAndNavigationBarAppearance(isLightStatusBar: Boolean, isLightNavBar: Boolean, blackNavBar: Boolean, themeBackgroundColor: Color) { val window = mainActivity.get()?.window ?: return @Suppress("DEPRECATION") + val statusLight = isLightStatusBar && chatModel.activeCall.value == null + val navBarLight = isLightNavBar || windowOrientation() == WindowOrientation.LANDSCAPE val windowInsetController = ViewCompat.getWindowInsetsController(window.decorView) - - var statusBar = (if (hasTop && appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete) { - backgroundColor.mixWith(CurrentColors.value.colors.onBackground, 0.97f) - } else { - if (CurrentColors.value.base == DefaultTheme.SIMPLEX) { - backgroundColor.lighter(0.4f) + if (windowInsetController?.isAppearanceLightStatusBars != statusLight) { + windowInsetController?.isAppearanceLightStatusBars = statusLight + } + window.navigationBarColor = Color.Transparent.toArgb() + if (windowInsetController?.isAppearanceLightNavigationBars != navBarLight) { + windowInsetController?.isAppearanceLightNavigationBars = navBarLight + } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + window.decorView.systemUiVisibility = if (statusLight && navBarLight) { + View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR or OLD_ANDROID_UI_FLAGS + } else if (statusLight) { + View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR or OLD_ANDROID_UI_FLAGS + } else if (navBarLight) { + View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR or OLD_ANDROID_UI_FLAGS } else { - backgroundColor + OLD_ANDROID_UI_FLAGS } - }).toArgb() - var statusBarLight = isLight - - // SimplexGreen while in call - if (window.statusBarColor == SimplexGreen.toArgb()) { - statusBarColorAfterCall.intValue = statusBar - statusBar = SimplexGreen.toArgb() - statusBarLight = false - } - val navBar = (if (hasBottom && appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete) { - backgroundColor.mixWith(CurrentColors.value.colors.onBackground, 0.97f) + window.navigationBarColor = if (blackNavBar) Color.Black.toArgb() else themeBackgroundColor.toArgb() } else { - backgroundColor - }).toArgb() - if (window.statusBarColor != statusBar) { - window.statusBarColor = statusBar - } - if (windowInsetController?.isAppearanceLightStatusBars != statusBarLight) { - windowInsetController?.isAppearanceLightStatusBars = statusBarLight - } - if (window.navigationBarColor != navBar) { - window.navigationBarColor = navBar - } - if (windowInsetController?.isAppearanceLightNavigationBars != isLight) { - windowInsetController?.isAppearanceLightNavigationBars = isLight + window.navigationBarColor = Color.Transparent.toArgb() } } @@ -401,6 +355,8 @@ class SimplexApp: Application(), LifecycleEventObserver { } return true } + + override val androidApiLevel: Int get() = Build.VERSION.SDK_INT } } } diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts index 1aaa061daa..0e45c66efd 100644 --- a/apps/multiplatform/common/build.gradle.kts +++ b/apps/multiplatform/common/build.gradle.kts @@ -64,7 +64,6 @@ kotlin { implementation("androidx.activity:activity-compose:1.9.1") val workVersion = "2.9.1" implementation("androidx.work:work-runtime-ktx:$workVersion") - implementation("com.google.accompanist:accompanist-insets:0.30.1") // Video support implementation("com.google.android.exoplayer:exoplayer:2.19.1") diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Modifier.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Modifier.android.kt index 2aa66bc69b..5d07aae088 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Modifier.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Modifier.android.kt @@ -3,20 +3,8 @@ package chat.simplex.common.platform import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.painter.Painter -import com.google.accompanist.insets.navigationBarsWithImePadding import java.io.File -actual fun Modifier.navigationBarsWithImePadding(): Modifier = navigationBarsWithImePadding() - -@Composable -actual fun ProvideWindowInsets( - consumeWindowInsets: Boolean, - windowInsetsAnimationsEnabled: Boolean, - content: @Composable () -> Unit -) { - com.google.accompanist.insets.ProvideWindowInsets(content = content) -} - @Composable actual fun Modifier.desktopOnExternalDrag( enabled: Boolean, diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt index 5365db6a4c..7b820aa67e 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt @@ -6,8 +6,7 @@ import android.graphics.drawable.ColorDrawable import android.os.Build import android.text.InputType import android.util.Log -import android.view.OnReceiveContentListener -import android.view.ViewGroup +import android.view.* import android.view.inputmethod.* import android.widget.EditText import android.widget.TextView @@ -141,6 +140,13 @@ actual fun PlatformTextField( Log.e(TAG, e.stackTraceToString()) } } + editText.onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus -> + // shows keyboard when user had search field on ChatView focused before clicking on this text field + // it still produce weird animation of closing/opening keyboard but the solution is to replace this Android EditText with Compose BasicTextField + if (hasFocus) { + showKeyboard = true + } + } editText.doOnTextChanged { text, _, _, _ -> if (!composeState.value.inProgress) { onMessageChange(text.toString()) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Resources.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Resources.android.kt index 73c920b940..d4b77274ba 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Resources.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Resources.android.kt @@ -6,11 +6,11 @@ import android.content.Context import android.content.SharedPreferences import android.content.res.Configuration import android.text.BidiFormatter +import androidx.compose.foundation.layout.* import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asImageBitmap -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.* import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight @@ -50,7 +50,11 @@ actual fun windowOrientation(): WindowOrientation = when (mainActivity.get()?.re } @Composable -actual fun windowWidth(): Dp = LocalConfiguration.current.screenWidthDp.dp +actual fun windowWidth(): Dp { + val direction = LocalLayoutDirection.current + val cutout = WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal).asPaddingValues() + return LocalConfiguration.current.screenWidthDp.dp - cutout.calculateStartPadding(direction) - cutout.calculateEndPadding(direction) +} @Composable actual fun windowHeight(): Dp = LocalConfiguration.current.screenHeightDp.dp diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/ScrollableColumn.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/ScrollableColumn.android.kt index 6851970b81..d70177ffb9 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/ScrollableColumn.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/ScrollableColumn.android.kt @@ -7,10 +7,12 @@ import androidx.compose.foundation.lazy.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import chat.simplex.common.model.ChatController.appPrefs +import chat.simplex.common.ui.theme.DEFAULT_PADDING +import chat.simplex.common.views.chatlist.NavigationBarBackground import chat.simplex.common.views.helpers.* import kotlinx.coroutines.flow.filter import kotlin.math.absoluteValue @@ -25,25 +27,74 @@ actual fun LazyColumnWithScrollBar( horizontalAlignment: Alignment.Horizontal, flingBehavior: FlingBehavior, userScrollEnabled: Boolean, + additionalBarOffset: State?, + fillMaxSize: Boolean, content: LazyListScope.() -> Unit ) { - val state = state ?: LocalAppBarHandler.current?.listState ?: rememberLazyListState() - val connection = LocalAppBarHandler.current?.connection + val handler = LocalAppBarHandler.current + require(handler != null) { "Using LazyColumnWithScrollBar and without AppBarHandler is an error. Use LazyColumnWithScrollBarNoAppBar instead" } + + val state = state ?: handler.listState + val connection = handler.connection LaunchedEffect(Unit) { - snapshotFlow { state.firstVisibleItemScrollOffset } - .filter { state.firstVisibleItemIndex == 0 } - .collect { scrollPosition -> - val offset = connection?.appBarOffset - if (offset != null && (offset + scrollPosition).absoluteValue > 1) { - connection.appBarOffset = -scrollPosition.toFloat() -// Log.d(TAG, "Scrolling position changed from $offset to ${connection.appBarOffset}") + if (reverseLayout) { + snapshotFlow { state.layoutInfo.visibleItemsInfo.lastOrNull()?.offset ?: 0 } + .collect { scrollPosition -> + connection.appBarOffset = if (state.layoutInfo.visibleItemsInfo.lastOrNull()?.index == state.layoutInfo.totalItemsCount - 1) { + state.layoutInfo.viewportEndOffset - scrollPosition.toFloat() - state.layoutInfo.afterContentPadding + } else { + // show always when last item is not visible + -1000f + } + //Log.d(TAG, "Scrolling position changed from $offset to ${connection.appBarOffset}") } - } + } else { + snapshotFlow { state.firstVisibleItemScrollOffset } + .filter { state.firstVisibleItemIndex == 0 } + .collect { scrollPosition -> + val offset = connection.appBarOffset + if ((offset + scrollPosition + state.layoutInfo.afterContentPadding).absoluteValue > 1) { + connection.appBarOffset = -scrollPosition.toFloat() + //Log.d(TAG, "Scrolling position changed from $offset to ${connection.appBarOffset}") + } + } + } } - if (connection != null) { - LazyColumn(modifier.nestedScroll(connection), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content) - } else { - LazyColumn(modifier, state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content) + LazyColumn( + if (fillMaxSize) { + Modifier.fillMaxSize().copyViewToAppBar(remember { appPrefs.appearanceBarsBlurRadius.state }.value, LocalAppBarHandler.current?.graphicsLayer).then(modifier).nestedScroll(connection) + } else { + Modifier.copyViewToAppBar(remember { appPrefs.appearanceBarsBlurRadius.state }.value, LocalAppBarHandler.current?.graphicsLayer).then(modifier).nestedScroll(connection) + }, + state, + contentPadding, + reverseLayout, + verticalArrangement, + horizontalAlignment, + flingBehavior, + userScrollEnabled + ) { + content() + } +} + + +@Composable +actual fun LazyColumnWithScrollBarNoAppBar( + modifier: Modifier, + state: LazyListState?, + contentPadding: PaddingValues, + reverseLayout: Boolean, + verticalArrangement: Arrangement.Vertical, + horizontalAlignment: Alignment.Horizontal, + flingBehavior: FlingBehavior, + userScrollEnabled: Boolean, + additionalBarOffset: State?, + content: LazyListScope.() -> Unit +) { + val state = state ?: rememberLazyListState() + LazyColumn(modifier, state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled) { + content() } } @@ -54,32 +105,80 @@ actual fun ColumnWithScrollBar( horizontalAlignment: Alignment.Horizontal, state: ScrollState?, maxIntrinsicSize: Boolean, + fillMaxSize: Boolean, content: @Composable() (ColumnScope.() -> Unit) ) { - val state = state ?: LocalAppBarHandler.current?.scrollState ?: rememberScrollState() - val connection = LocalAppBarHandler.current?.connection + val handler = LocalAppBarHandler.current + require(handler != null) { "Using ColumnWithScrollBar and without AppBarHandler is an error. Use ColumnWithScrollBarNoAppBar instead" } + + val modifier = if (fillMaxSize) Modifier.fillMaxSize().then(modifier).imePadding() else modifier.imePadding() + val state = state ?: handler.scrollState + val connection = handler.connection LaunchedEffect(Unit) { snapshotFlow { state.value } .collect { scrollPosition -> - val offset = connection?.appBarOffset - if (offset != null && (offset + scrollPosition).absoluteValue > 1) { + val offset = connection.appBarOffset + if ((offset + scrollPosition).absoluteValue > 1) { connection.appBarOffset = -scrollPosition.toFloat() // Log.d(TAG, "Scrolling position changed from $offset to ${connection.appBarOffset}") } } } - if (connection != null) { - Column( - if (maxIntrinsicSize) { - modifier.nestedScroll(connection).verticalScroll(state).height(IntrinsicSize.Max) - } else { - modifier.nestedScroll(connection).verticalScroll(state) - }, verticalArrangement, horizontalAlignment, content) - } else { - Column(if (maxIntrinsicSize) { + val oneHandUI = remember { appPrefs.oneHandUI.state } + Box(Modifier.fillMaxHeight()) { + Column( + if (maxIntrinsicSize) { + Modifier.copyViewToAppBar(remember { appPrefs.appearanceBarsBlurRadius.state }.value, LocalAppBarHandler.current?.graphicsLayer).then(modifier).nestedScroll(connection).verticalScroll(state).height(IntrinsicSize.Max) + } else { + Modifier.copyViewToAppBar(remember { appPrefs.appearanceBarsBlurRadius.state }.value, LocalAppBarHandler.current?.graphicsLayer).then(modifier).nestedScroll(connection).verticalScroll(state) + }, verticalArrangement, horizontalAlignment + ) { + if (oneHandUI.value) { + Spacer(Modifier.padding(top = DEFAULT_PADDING + 5.dp).windowInsetsTopHeight(WindowInsets.statusBars)) + content() + Spacer(Modifier.navigationBarsPadding().padding(bottom = AppBarHeight * fontSizeSqrtMultiplier)) + } else { + Spacer(Modifier.statusBarsPadding().padding(top = AppBarHeight * fontSizeSqrtMultiplier)) + content() + Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.systemBars)) + } + } + if (!oneHandUI.value) { + NavigationBarBackground(false, false) + } + } +} + +@Composable +actual fun ColumnWithScrollBarNoAppBar( + modifier: Modifier, + verticalArrangement: Arrangement.Vertical, + horizontalAlignment: Alignment.Horizontal, + state: ScrollState?, + maxIntrinsicSize: Boolean, + content: @Composable() (ColumnScope.() -> Unit) +) { + val modifier = modifier.imePadding() + val state = state ?: rememberScrollState() + val oneHandUI = remember { appPrefs.oneHandUI.state } + Box(Modifier.fillMaxHeight()) { + Column( + if (maxIntrinsicSize) { modifier.verticalScroll(state).height(IntrinsicSize.Max) } else { modifier.verticalScroll(state) - }, verticalArrangement, horizontalAlignment, content) + }, verticalArrangement, horizontalAlignment + ) { + if (oneHandUI.value) { + Spacer(Modifier.windowInsetsTopHeight(WindowInsets.systemBars)) + content() + } else { + content() + Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.systemBars)) + } + } + if (!oneHandUI.value) { + NavigationBarBackground(false, false) + } } } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/UI.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/UI.android.kt index c90946c95b..7ab6bf525f 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/UI.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/UI.android.kt @@ -3,17 +3,18 @@ package chat.simplex.common.platform import android.app.Activity import android.content.Context import android.content.pm.ActivityInfo -import android.graphics.Rect import android.os.* import android.view.* import android.view.inputmethod.InputMethodManager import android.widget.Toast import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.ime import androidx.compose.runtime.* +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalView import chat.simplex.common.AppScreen import chat.simplex.common.model.clear -import chat.simplex.common.ui.theme.SimpleXTheme import chat.simplex.common.views.helpers.* import androidx.compose.ui.platform.LocalContext as LocalContext1 import chat.simplex.res.MR @@ -43,28 +44,13 @@ actual fun LocalMultiplatformView(): Any? = LocalView.current @Composable actual fun getKeyboardState(): State { - val keyboardState = remember { mutableStateOf(KeyboardState.Closed) } - val view = LocalView.current - DisposableEffect(view) { - val onGlobalListener = ViewTreeObserver.OnGlobalLayoutListener { - val rect = Rect() - view.getWindowVisibleDisplayFrame(rect) - val screenHeight = view.rootView.height - val keypadHeight = screenHeight - rect.bottom - keyboardState.value = if (keypadHeight > screenHeight * 0.15) { - KeyboardState.Opened - } else { - KeyboardState.Closed - } - } - view.viewTreeObserver.addOnGlobalLayoutListener(onGlobalListener) - - onDispose { - view.viewTreeObserver.removeOnGlobalLayoutListener(onGlobalListener) + val density = LocalDensity.current + val ime = WindowInsets.ime + return remember { + derivedStateOf { + if (ime.getBottom(density) == 0) KeyboardState.Closed else KeyboardState.Opened } } - - return keyboardState } actual fun hideKeyboard(view: Any?, clearFocus: Boolean) { diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt index f3a9be6132..601b907902 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt @@ -47,6 +47,7 @@ import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.onboarding.OnboardingStage import chat.simplex.res.MR import com.google.accompanist.permissions.* import dev.icerock.moko.resources.StringResource @@ -328,11 +329,14 @@ private fun ActiveCallOverlayLayout( flipCamera: () -> Unit ) { Column { - CloseSheetBar({ chatModel.activeCallViewIsCollapsed.value = true }, true, tintColor = Color(0xFFFFFFD8)) { - if (call.hasVideo) { - Text(call.contact.chatViewName, Modifier.fillMaxWidth().padding(end = DEFAULT_PADDING), color = Color(0xFFFFFFD8), style = MaterialTheme.typography.h2, overflow = TextOverflow.Ellipsis, maxLines = 1) - } - } + CallAppBar( + title = { + if (call.hasVideo) { + Text(call.contact.chatViewName, Modifier.offset(x = (-4).dp).padding(end = DEFAULT_PADDING), color = Color(0xFFFFFFD8), style = MaterialTheme.typography.h2, overflow = TextOverflow.Ellipsis, maxLines = 1) + } + }, + onBack = { chatModel.activeCallViewIsCollapsed.value = true } + ) Column(Modifier.padding(horizontal = DEFAULT_PADDING)) { @Composable fun SelectSoundDevice(size: Dp) { @@ -590,8 +594,9 @@ fun CallPermissionsView(pipActive: Boolean, hasVideo: Boolean, cancel: () -> Uni } } } else { - ModalView(background = Color.Black, showClose = false, close = {}) { - ColumnWithScrollBar(Modifier.fillMaxSize()) { + ModalView(background = Color.Black, showAppBar = false, close = {}) { + Column { + Spacer(Modifier.height(AppBarHeight * fontSizeSqrtMultiplier)) AppBarTitle(stringResource(MR.strings.permissions_required)) Spacer(Modifier.weight(1f)) val onClick = { diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.android.kt index 05a9430ff1..ae5b8043ed 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.android.kt @@ -8,7 +8,6 @@ import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalContext import chat.simplex.common.model.CIFile -import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.CurrentColors import chat.simplex.common.views.helpers.ModalManager @@ -39,14 +38,6 @@ actual fun SimpleAndAnimatedImageView( if (getLoadedFilePath(file) != null) { ModalManager.fullscreen.showCustomModal(animated = false) { close -> ImageFullScreenView(imageProvider, close) - if (smallView) { - DisposableEffect(Unit) { - onDispose { - val c = CurrentColors.value.colors - platform.androidSetStatusAndNavBarColors(c.isLight, c.background, !appPrefs.oneHandUI.get(), appPrefs.oneHandUI.get()) - } - } - } } } } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.android.kt index 4681a5a64d..7db39b7d3e 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.android.kt @@ -1,6 +1,5 @@ package chat.simplex.common.views.chatlist -import android.app.Activity import androidx.compose.foundation.* import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* @@ -11,21 +10,17 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.* import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp +import androidx.compose.ui.unit.* import chat.simplex.common.ANDROID_CALL_TOP_PADDING import chat.simplex.common.model.durationText import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.call.* -import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.datetime.Clock private val CALL_INTERACTIVE_AREA_HEIGHT = 74.dp @@ -37,11 +32,12 @@ private val CALL_BOTTOM_ICON_HEIGHT = CALL_INTERACTIVE_AREA_HEIGHT + CALL_BOTTOM @Composable actual fun ActiveCallInteractiveArea(call: Call) { val onClick = { platform.androidStartCallActivity(false) } - Box(Modifier.offset(y = CALL_TOP_OFFSET).height(CALL_INTERACTIVE_AREA_HEIGHT)) { + val statusBar = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + Box(Modifier.offset(y = CALL_TOP_OFFSET).height(CALL_INTERACTIVE_AREA_HEIGHT + statusBar)) { val source = remember { MutableInteractionSource() } val ripple = remember { ripple(bounded = true, 3000.dp) } - Box(Modifier.height(CALL_TOP_GREEN_LINE_HEIGHT).clickable(onClick = onClick, indication = ripple, interactionSource = source)) { - GreenLine(call) + Box(Modifier.height(CALL_TOP_GREEN_LINE_HEIGHT + statusBar).clickable(onClick = onClick, indication = ripple, interactionSource = source)) { + GreenLine(statusBar, call) } Box( Modifier @@ -62,16 +58,13 @@ actual fun ActiveCallInteractiveArea(call: Call) { } } -// Temporary solution for storing a color that needs to be applied after call ends -var statusBarColorAfterCall = mutableIntStateOf(CurrentColors.value.colors.background.toArgb()) - @Composable -private fun GreenLine(call: Call) { +private fun GreenLine(statusBarHeight: Dp, call: Call) { Row( Modifier .fillMaxSize() .background(SimplexGreen) - .padding(top = -CALL_TOP_OFFSET) + .padding(top = -CALL_TOP_OFFSET + statusBarHeight) .padding(horizontal = DEFAULT_PADDING), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center @@ -80,12 +73,10 @@ private fun GreenLine(call: Call) { Spacer(Modifier.weight(1f)) CallDuration(call) } - val window = (LocalContext.current as Activity).window DisposableEffect(Unit) { - statusBarColorAfterCall.intValue = window.statusBarColor - window.statusBarColor = SimplexGreen.toArgb() + platform.androidSetStatusAndNavigationBarAppearance(false, CurrentColors.value.colors.isLight) onDispose { - window.statusBarColor = statusBarColorAfterCall.intValue + platform.androidSetStatusAndNavigationBarAppearance(CurrentColors.value.colors.isLight, CurrentColors.value.colors.isLight) } } } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt index b68756c669..54e3061d25 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt @@ -19,13 +19,11 @@ import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* -import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.User import chat.simplex.common.model.UserInfo import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* -import chat.simplex.common.views.onboarding.OnboardingStage import kotlinx.coroutines.CancellationException import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow @@ -36,6 +34,7 @@ private val USER_PICKER_ROW_PADDING = 16.dp @Composable actual fun UserPickerUsersSection( users: List, + iconColor: Color, stopped: Boolean, onUserClicked: (user: User) -> Unit, ) { @@ -140,87 +139,73 @@ actual fun PlatformUserPicker(modifier: Modifier, pickerState: MutableStateFlow< } else { Modifier } - Box( - Modifier - .fillMaxSize() - .then(clickableModifier) - .drawBehind { - val pos = when { - dismissState.progress.from == DismissValue.Default && dismissState.progress.to == DismissValue.Default -> 1f - dismissState.progress.from == DismissValue.DismissedToEnd && dismissState.progress.to == DismissValue.DismissedToEnd -> 0f - dismissState.progress.to == DismissValue.Default -> dismissState.progress.fraction - else -> 1 - dismissState.progress.fraction - } - val colors = CurrentColors.value.colors - val resultingColor = if (colors.isLight) colors.onSurface.copy(alpha = ScrimOpacity) else Color.Black.copy(0.64f) - val adjustedAlpha = resultingColor.alpha * calculateFraction(pos = pos) - val shadingColor = resultingColor.copy(alpha = adjustedAlpha) - - if (pickerState.value.isVisible()) { - platform.androidSetDrawerStatusAndNavBarColor( - isLight = colors.isLight, - drawerShadingColor = shadingColor, - toolbarOnTop = !appPrefs.oneHandUI.get(), - navBarColor = colors.background.mixWith(colors.onBackground, 1 - userPickerAlpha()) - ) - } else if (ModalManager.start.modalCount.value == 0) { - platform.androidSetDrawerStatusAndNavBarColor( - isLight = colors.isLight, - drawerShadingColor = shadingColor, - toolbarOnTop = !appPrefs.oneHandUI.get(), - navBarColor = (if (appPrefs.oneHandUI.get() && appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete) { - colors.background.mixWith(CurrentColors.value.colors.onBackground, 0.97f) - } else { - colors.background - }) - ) - } - drawRect( - if (pos != 0f) resultingColor else Color.Transparent, - alpha = calculateFraction(pos = pos) - ) - } - .graphicsLayer { - if (heightValue == 0) { - alpha = 0f - } - translationY = dismissState.offset.value - }, - contentAlignment = Alignment.BottomCenter - ) { + Box { Box( - Modifier.onSizeChanged { height.intValue = it.height } - ) { - KeyChangeEffect(pickerIsVisible) { - if (pickerState.value.isVisible()) { - try { - dismissState.animateTo(DismissValue.Default, userPickerAnimSpec()) - } catch (e: CancellationException) { - Log.e(TAG, "Cancelled animateTo: ${e.stackTraceToString()}") - pickerState.value = AnimatedViewState.GONE + Modifier + .fillMaxSize() + .then(clickableModifier) + .drawBehind { + val pos = calculatePosition(dismissState) + val colors = CurrentColors.value.colors + val resultingColor = if (colors.isLight) colors.onSurface.copy(alpha = ScrimOpacity) else Color.Black.copy(0.64f) + drawRect( + if (pos != 0f) resultingColor else Color.Transparent, + alpha = calculateFraction(pos = pos) + ) + } + .graphicsLayer { + if (heightValue == 0) { + alpha = 0f } - } else { - try { - dismissState.animateTo(DismissValue.DismissedToEnd, userPickerAnimSpec()) - } catch (e: CancellationException) { - Log.e(TAG, "Cancelled animateTo2: ${e.stackTraceToString()}") - pickerState.value = AnimatedViewState.VISIBLE + translationY = dismissState.offset.value + }, + contentAlignment = Alignment.BottomCenter + ) { + Box( + Modifier.onSizeChanged { height.intValue = it.height } + ) { + KeyChangeEffect(pickerIsVisible) { + if (pickerState.value.isVisible()) { + try { + dismissState.animateTo(DismissValue.Default, userPickerAnimSpec()) + } catch (e: CancellationException) { + Log.e(TAG, "Cancelled animateTo: ${e.stackTraceToString()}") + pickerState.value = AnimatedViewState.GONE + } + } else { + try { + dismissState.animateTo(DismissValue.DismissedToEnd, userPickerAnimSpec()) + } catch (e: CancellationException) { + Log.e(TAG, "Cancelled animateTo2: ${e.stackTraceToString()}") + pickerState.value = AnimatedViewState.VISIBLE + } } } - } - val draggableModifier = if (height.intValue != 0) - Modifier.draggableBottomDrawerModifier( - state = dismissState, - swipeDistance = height.intValue.toFloat(), - ) - else Modifier - Box(draggableModifier.then(modifier)) { - content() + val draggableModifier = if (height.intValue != 0) + Modifier.draggableBottomDrawerModifier( + state = dismissState, + swipeDistance = height.intValue.toFloat(), + ) + else Modifier + Box(draggableModifier.then(modifier).navigationBarsPadding()) { + content() + } } } + NavigationBarBackground( + modifier = Modifier.graphicsLayer { alpha = if (calculatePosition(dismissState) > 0.1f) 1f else 0f }, + color = MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, alpha = 1 - userPickerAlpha()) + ) } } +private fun calculatePosition(dismissState: DismissState): Float = when { + dismissState.progress.from == DismissValue.Default && dismissState.progress.to == DismissValue.Default -> 1f + dismissState.progress.from == DismissValue.DismissedToEnd && dismissState.progress.to == DismissValue.DismissedToEnd -> 0f + dismissState.progress.to == DismissValue.Default -> dismissState.progress.fraction + else -> 1 - dismissState.progress.fraction +} + private fun Modifier.draggableBottomDrawerModifier( state: DismissState, swipeDistance: Float, diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/GetImageView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/GetImageView.android.kt index 98d1f8fb19..1c7ba1dcf0 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/GetImageView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/GetImageView.android.kt @@ -171,6 +171,8 @@ actual fun GetImageBottomSheet( modifier = Modifier .fillMaxWidth() .wrapContentHeight() + .imePadding() + .navigationBarsPadding() .onFocusChanged { focusState -> if (!focusState.hasFocus) hideBottomSheet() } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt index 418174a8e9..e5450e8e49 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt @@ -2,6 +2,7 @@ package chat.simplex.common.views.usersettings import SectionBottomSpacer import SectionDividerSpaced +import SectionSpacer import SectionView import android.app.Activity import android.content.ComponentName @@ -31,6 +32,7 @@ import chat.simplex.common.model.ChatModel import chat.simplex.common.platform.* import chat.simplex.common.helpers.APPLICATION_ID import chat.simplex.common.helpers.saveAppLocale +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.res.MR import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.compose.painterResource @@ -75,9 +77,7 @@ fun AppearanceScope.AppearanceLayout( systemDarkTheme: SharedPreference, changeIcon: (AppIcon) -> Unit, ) { - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - ) { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.appearance_settings)) SectionView(stringResource(MR.strings.settings_section_title_interface), contentPadding = PaddingValues()) { val context = LocalContext.current @@ -106,15 +106,15 @@ fun AppearanceScope.AppearanceLayout( } // } - SettingsPreferenceItem(icon = null, stringResource(MR.strings.one_hand_ui), ChatModel.controller.appPrefs.oneHandUI) { - val c = CurrentColors.value.colors - platform.androidSetStatusAndNavBarColors(c.isLight, c.background, false, false) - } + SettingsPreferenceItem(icon = null, stringResource(MR.strings.one_hand_ui), ChatModel.controller.appPrefs.oneHandUI) } SectionDividerSpaced() ThemesSection(systemDarkTheme) + SectionDividerSpaced() + AppToolbarsSection() + SectionDividerSpaced() MessageShapeSection() diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.android.kt index 96b4a43e1a..04b59732dd 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.android.kt @@ -13,14 +13,13 @@ import dev.icerock.moko.resources.compose.stringResource @Composable actual fun SettingsSectionApp( showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), - showCustomModal: (@Composable ModalData.(ChatModel, () -> Unit) -> Unit) -> (() -> Unit), showVersion: () -> Unit, withAuth: (title: String, desc: String, block: () -> Unit) -> Unit ) { SectionView(stringResource(MR.strings.settings_section_title_app)) { SettingsActionItem(painterResource(MR.images.ic_restart_alt), stringResource(MR.strings.settings_restart_app), ::restartApp) SettingsActionItem(painterResource(MR.images.ic_power_settings_new), stringResource(MR.strings.settings_shutdown), { shutdownAppAlert(::shutdownApp) }) - SettingsActionItem(painterResource(MR.images.ic_code), stringResource(MR.strings.settings_developer_tools), showSettingsModal { DeveloperView(it, showCustomModal, withAuth) }) + SettingsActionItem(painterResource(MR.images.ic_code), stringResource(MR.strings.settings_developer_tools), showSettingsModal { DeveloperView(withAuth) }) AppVersionItem(showVersion) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index b95aed45d2..ee38cb80fe 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -11,10 +11,13 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clipToBounds -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.draw.* +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.* +import androidx.compose.ui.graphics.drawscope.clipRect import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.dp import chat.simplex.common.views.usersettings.SetDeliveryReceiptsView import chat.simplex.common.model.* @@ -39,14 +42,39 @@ import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.* import kotlinx.coroutines.flow.* +import kotlin.math.absoluteValue @Composable fun AppScreen() { AppBarHandler.appBarMaxHeightPx = with(LocalDensity.current) { AppBarHeight.roundToPx() } SimpleXTheme { - ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { - Surface(color = MaterialTheme.colors.background, contentColor = LocalContentColor.current) { - MainScreen() + Surface(color = MaterialTheme.colors.background, contentColor = LocalContentColor.current) { + // This padding applies to landscape view only taking care of navigation bar and holes in screen in status bar area + // (because nav bar and holes located on vertical sides of screen in landscape view) + val direction = LocalLayoutDirection.current + val safePadding = WindowInsets.safeDrawing.asPaddingValues() + val cutout = WindowInsets.displayCutout.asPaddingValues() + val cutoutStart = cutout.calculateStartPadding(direction) + val cutoutEnd = cutout.calculateEndPadding(direction) + val cutoutMax = maxOf(cutoutStart, cutoutEnd) + val paddingStartUntouched = safePadding.calculateStartPadding(direction) + val paddingStart = paddingStartUntouched - cutoutStart + val paddingEndUntouched = safePadding.calculateEndPadding(direction) + val paddingEnd = paddingEndUntouched - cutoutEnd + // Such a strange layout is needed because the main content should be covered by solid color in order to hide overflow + // of some elements that may have negative offset (so, can't use Row {}). + // To check: go to developer settings of Android, choose Display cutout -> Punch hole, and rotate the phone to landscape, open any chat + Box { + val fullscreenGallery = remember { chatModel.fullscreenGalleryVisible } + Box(Modifier.padding(start = paddingStart + cutoutMax, end = paddingEnd + cutoutMax).consumeWindowInsets(PaddingValues(start = paddingStartUntouched, end = paddingEndUntouched))) { + Box(Modifier.drawBehind { + if (fullscreenGallery.value) { + drawRect(Color.Black, topLeft = Offset(-(paddingStart + cutoutMax).toPx(), 0f), Size(size.width + (paddingStart + cutoutMax).toPx() + (paddingEnd + cutoutMax).toPx(), size.height)) + } + }) { + MainScreen() + } + } } } } @@ -138,7 +166,9 @@ fun MainScreen() { } SetupClipboardListener() if (appPlatform.isAndroid) { - AndroidScreen(userPickerState) + AndroidWrapInCallLayout { + AndroidScreen(userPickerState) + } } else { DesktopScreen(userPickerState) } @@ -170,7 +200,9 @@ fun MainScreen() { } } if (appPlatform.isAndroid) { - ModalManager.fullscreen.showInView() + AndroidWrapInCallLayout { + ModalManager.fullscreen.showInView() + } SwitchingUsersView() } @@ -237,19 +269,39 @@ fun MainScreen() { val ANDROID_CALL_TOP_PADDING = 40.dp +@Composable +fun AndroidWrapInCallLayout(content: @Composable () -> Unit) { + val call = remember { chatModel.activeCall}.value + val showCallArea = call != null && call.callState != CallState.WaitCapabilities && call.callState != CallState.InvitationAccepted + Box { + Box(Modifier.padding(top = if (showCallArea) ANDROID_CALL_TOP_PADDING else 0.dp)) { + content() + } + if (call != null && showCallArea) { + ActiveCallInteractiveArea(call) + } + } +} + @Composable fun AndroidScreen(userPickerState: MutableStateFlow) { BoxWithConstraints { - val call = remember { chatModel.activeCall} .value - val showCallArea = call != null && call.callState != CallState.WaitCapabilities && call.callState != CallState.InvitationAccepted val currentChatId = remember { mutableStateOf(chatModel.chatId.value) } val offset = remember { Animatable(if (chatModel.chatId.value == null) 0f else maxWidth.value) } + val cutout = WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal).asPaddingValues() + val direction = LocalLayoutDirection.current + val hasCutout = cutout.calculateStartPadding(direction) + cutout.calculateEndPadding(direction) > 0.dp Box( Modifier + // clipping only for devices with cutout currently visible on sides. It prevents showing chat list with open chat view + // In order cases it's not needed to use clip + .then(if (hasCutout) Modifier.clip(RectangleShape) else Modifier) .graphicsLayer { - translationX = -offset.value.dp.toPx() + // minOf thing is needed for devices with holes in screen while the user on ChatView rotates his phone from portrait to landscape + // because in this case (at least in emulator) maxWidth changes in two steps: big first, smaller on next frame. + // But offset is remembered already, so this is a better way than dropping a value of offset + translationX = -minOf(offset.value.dp, maxWidth).toPx() } - .padding(top = if (showCallArea) ANDROID_CALL_TOP_PADDING else 0.dp) ) { StartPartOfScreen(userPickerState) } @@ -271,51 +323,40 @@ fun AndroidScreen(userPickerState: MutableStateFlow) { snapshotFlow { chatModel.chatId.value } .distinctUntilChanged() .collect { - if (it == null) { - platform.androidSetStatusAndNavBarColors(CurrentColors.value.colors.isLight, CurrentColors.value.colors.background, !appPrefs.oneHandUI.get(), appPrefs.oneHandUI.get()) - onComposed(null) - } + if (it == null) onComposed(null) currentChatId.value = it } } } - LaunchedEffect(Unit) { - snapshotFlow { ModalManager.center.modalCount.value > 0 } - .filter { chatModel.chatId.value == null } - .collect { modalBackground -> - if (chatModel.newChatSheetVisible.value) { - platform.androidSetStatusAndNavBarColors(CurrentColors.value.colors.isLight, CurrentColors.value.colors.background, false, appPrefs.oneHandUI.get()) - } else if (modalBackground) { - platform.androidSetStatusAndNavBarColors(CurrentColors.value.colors.isLight, CurrentColors.value.colors.background, false, false) - } else { - platform.androidSetStatusAndNavBarColors(CurrentColors.value.colors.isLight, CurrentColors.value.colors.background, !appPrefs.oneHandUI.get(), appPrefs.oneHandUI.get()) - } - } - } Box(Modifier - .graphicsLayer { translationX = maxWidth.toPx() - offset.value.dp.toPx() } - .padding(top = if (showCallArea) ANDROID_CALL_TOP_PADDING else 0.dp) + .then(if (hasCutout) Modifier.clip(RectangleShape) else Modifier) + .graphicsLayer { translationX = maxWidth.toPx() - minOf(offset.value.dp, maxWidth).toPx() } ) Box2@{ currentChatId.value?.let { ChatView(currentChatId, onComposed) } } - if (call != null && showCallArea) { - ActiveCallInteractiveArea(call) - } } } @Composable fun StartPartOfScreen(userPickerState: MutableStateFlow) { if (chatModel.setDeliveryReceipts.value) { - SetDeliveryReceiptsView(chatModel) + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { + SetDeliveryReceiptsView(chatModel) + } } else { val stopped = chatModel.chatRunning.value == false - if (chatModel.sharedContent.value == null) - ChatListView(chatModel, userPickerState, AppLock::setPerformLA, stopped) - else - ShareListView(chatModel, stopped) + if (chatModel.sharedContent.value == null) { + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { + ChatListView(chatModel, userPickerState, AppLock::setPerformLA, stopped) + } + } else { + // LALAL initial load of view doesn't show blur. Focusing text field shows it + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler(keyboardCoversBar = false)) { + ShareListView(chatModel, stopped) + } + } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 6bc565097f..422eb1e77f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -90,6 +90,9 @@ object ChatModel { // Needed to check for bottom nav bar and to apply or not navigation bar color on Android val newChatSheetVisible = mutableStateOf(false) + // Needed to apply black color to left/right cutout area on Android + val fullscreenGalleryVisible = mutableStateOf(false) + // preferences val notificationPreviewMode by lazy { mutableStateOf( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 29d45a3b9b..fab85fa679 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -118,6 +118,9 @@ class AppPreferences { val privacyEncryptLocalFiles = mkBoolPreference(SHARED_PREFS_PRIVACY_ENCRYPT_LOCAL_FILES, true) val privacyAskToApproveRelays = mkBoolPreference(SHARED_PREFS_PRIVACY_ASK_TO_APPROVE_RELAYS, true) val privacyMediaBlurRadius = mkIntPreference(SHARED_PREFS_PRIVACY_MEDIA_BLUR_RADIUS, 0) + // Blur broken on Android 12, see https://github.com/chrisbanes/haze/issues/77. And not available before 12 + val deviceSupportsBlur = appPlatform.isDesktop || (platform.androidApiLevel ?: 0) >= 32 + val appearanceBarsBlurRadius = mkIntPreference(SHARED_PREFS_APPEARANCE_BARS_BLUR_RADIUS, if (deviceSupportsBlur) 50 else 0) val experimentalCalls = mkBoolPreference(SHARED_PREFS_EXPERIMENTAL_CALLS, false) val showUnreadAndFavorites = mkBoolPreference(SHARED_PREFS_SHOW_UNREAD_AND_FAVORITES, false) val chatArchiveName = mkStrPreference(SHARED_PREFS_CHAT_ARCHIVE_NAME, null) @@ -223,6 +226,8 @@ class AppPreferences { val chatItemTail = mkBoolPreference(SHARED_PREFS_CHAT_ITEM_TAIL, true) val fontScale = mkFloatPreference(SHARED_PREFS_FONT_SCALE, 1f) val densityScale = mkFloatPreference(SHARED_PREFS_DENSITY_SCALE, 1f) + val inAppBarsDefaultAlpha = if (deviceSupportsBlur) 0.875f else 0.975f + val inAppBarsAlpha = mkFloatPreference(SHARED_PREFS_IN_APP_BARS_ALPHA, inAppBarsDefaultAlpha) val whatsNewVersion = mkStrPreference(SHARED_PREFS_WHATS_NEW_VERSION, null) val lastMigratedVersionCode = mkIntPreference(SHARED_PREFS_LAST_MIGRATED_VERSION_CODE, 0) @@ -244,7 +249,7 @@ class AppPreferences { val iosCallKitEnabled = mkBoolPreference(SHARED_PREFS_IOS_CALL_KIT_ENABLED, true) val iosCallKitCallsInRecents = mkBoolPreference(SHARED_PREFS_IOS_CALL_KIT_CALLS_IN_RECENTS, false) - val oneHandUI = mkBoolPreference(SHARED_PREFS_ONE_HAND_UI, appPlatform.isAndroid) + val oneHandUI = mkBoolPreference(SHARED_PREFS_ONE_HAND_UI, true) val hintPreferences: List, Boolean>> = listOf( laNoticeShown to false, @@ -362,6 +367,7 @@ class AppPreferences { private const val SHARED_PREFS_PRIVACY_ENCRYPT_LOCAL_FILES = "PrivacyEncryptLocalFiles" private const val SHARED_PREFS_PRIVACY_ASK_TO_APPROVE_RELAYS = "PrivacyAskToApproveRelays" private const val SHARED_PREFS_PRIVACY_MEDIA_BLUR_RADIUS = "PrivacyMediaBlurRadius" + private const val SHARED_PREFS_APPEARANCE_BARS_BLUR_RADIUS = "AppearanceBarsBlurRadius" const val SHARED_PREFS_PRIVACY_FULL_BACKUP = "FullBackup" private const val SHARED_PREFS_EXPERIMENTAL_CALLS = "ExperimentalCalls" private const val SHARED_PREFS_SHOW_UNREAD_AND_FAVORITES = "ShowUnreadAndFavorites" @@ -428,6 +434,7 @@ class AppPreferences { private const val SHARED_PREFS_CHAT_ITEM_TAIL = "ChatItemTail" private const val SHARED_PREFS_FONT_SCALE = "FontScale" private const val SHARED_PREFS_DENSITY_SCALE = "DensityScale" + private const val SHARED_PREFS_IN_APP_BARS_ALPHA = "InAppBarsAlpha" private const val SHARED_PREFS_WHATS_NEW_VERSION = "WhatsNewVersion" private const val SHARED_PREFS_LAST_MIGRATED_VERSION_CODE = "LastMigratedVersionCode" private const val SHARED_PREFS_CUSTOM_DISAPPEARING_MESSAGE_TIME = "CustomDisappearingMessageTime" diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Modifier.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Modifier.kt index 5281fcb1ad..be7022ca80 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Modifier.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Modifier.kt @@ -14,15 +14,6 @@ import kotlinx.coroutines.* import kotlinx.coroutines.flow.filter import java.io.File -expect fun Modifier.navigationBarsWithImePadding(): Modifier - -@Composable -expect fun ProvideWindowInsets( - consumeWindowInsets: Boolean = true, - windowInsetsAnimationsEnabled: Boolean = true, - content: @Composable () -> Unit -) - @Composable expect fun Modifier.desktopOnExternalDrag( enabled: Boolean = true, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt index 5dfa5aa200..23ab450cb6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt @@ -8,6 +8,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import chat.simplex.common.model.ChatId import chat.simplex.common.model.NotificationsMode +import chat.simplex.common.ui.theme.CurrentColors import kotlinx.coroutines.Job interface PlatformInterface { @@ -20,12 +21,12 @@ interface PlatformInterface { fun androidChatInitializedAndStarted() {} fun androidIsBackgroundCallAllowed(): Boolean = true fun androidSetNightModeIfSupported() {} - fun androidSetStatusAndNavBarColors(isLight: Boolean, backgroundColor: Color, hasTop: Boolean, hasBottom: Boolean) {} - fun androidSetDrawerStatusAndNavBarColor(isLight: Boolean, drawerShadingColor: Color, toolbarOnTop: Boolean, navBarColor: Color) {} + fun androidSetStatusAndNavigationBarAppearance(isLightStatusBar: Boolean, isLightNavBar: Boolean, blackNavBar: Boolean = false, themeBackgroundColor: Color = CurrentColors.value.colors.background) {} fun androidStartCallActivity(acceptCall: Boolean, remoteHostId: Long? = null, chatId: ChatId? = null) {} fun androidPictureInPictureAllowed(): Boolean = true fun androidCallEnded() {} fun androidRestartNetworkObserver() {} + val androidApiLevel: Int? get() = null @Composable fun androidLockPortraitOrientation() {} suspend fun androidAskToAllowBackgroundCalls(): Boolean = true @Composable fun desktopShowAppUpdateNotice() {} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/ScrollableColumn.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/ScrollableColumn.kt index 532bfddfcf..b0be547a31 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/ScrollableColumn.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/ScrollableColumn.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.lazy.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @Composable @@ -21,11 +22,44 @@ expect fun LazyColumnWithScrollBar( horizontalAlignment: Alignment.Horizontal = Alignment.Start, flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(), userScrollEnabled: Boolean = true, + additionalBarOffset: State? = null, + // by default, this function will include .fillMaxSize() without you doing anything. If you don't need it, pass `false` here + // maxSize (at least maxHeight) is needed for blur on appBars to work correctly + fillMaxSize: Boolean = true, + content: LazyListScope.() -> Unit +) + +@Composable +expect fun LazyColumnWithScrollBarNoAppBar( + modifier: Modifier = Modifier, + state: LazyListState? = null, + contentPadding: PaddingValues = PaddingValues(0.dp), + reverseLayout: Boolean = false, + verticalArrangement: Arrangement.Vertical = + if (!reverseLayout) Arrangement.Top else Arrangement.Bottom, + horizontalAlignment: Alignment.Horizontal = Alignment.Start, + flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(), + userScrollEnabled: Boolean = true, + additionalBarOffset: State? = null, content: LazyListScope.() -> Unit ) @Composable expect fun ColumnWithScrollBar( + modifier: Modifier = Modifier, + verticalArrangement: Arrangement.Vertical = Arrangement.Top, + horizontalAlignment: Alignment.Horizontal = Alignment.Start, + state: ScrollState? = null, + // set true when you want to show something in the center with respected .fillMaxSize() + maxIntrinsicSize: Boolean = false, + // by default, this function will include .fillMaxSize() without you doing anything. If you don't need it, pass `false` here + // maxSize (at least maxHeight) is needed for blur on appBars to work correctly + fillMaxSize: Boolean = true, + content: @Composable ColumnScope.() -> Unit +) + +@Composable +expect fun ColumnWithScrollBarNoAppBar( modifier: Modifier = Modifier, verticalArrangement: Arrangement.Vertical = Arrangement.Top, horizontalAlignment: Alignment.Horizontal = Alignment.Start, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt index 595c22e3e2..80542ced02 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt @@ -1,14 +1,14 @@ package chat.simplex.common.ui.theme -import androidx.compose.foundation.background import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.* +import androidx.compose.ui.graphics.layer.GraphicsLayer import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.* import chat.simplex.common.model.ChatController import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* @@ -587,21 +587,27 @@ data class ThemeModeOverride ( } } -fun Modifier.themedBackground(baseTheme: DefaultTheme = CurrentColors.value.base, shape: Shape = RectangleShape): Modifier { - return if (baseTheme == DefaultTheme.SIMPLEX) { - this.background(brush = Brush.linearGradient( - listOf( - CurrentColors.value.colors.background.darker(0.4f), - CurrentColors.value.colors.background.lighter(0.4f) - ), - Offset(0f, Float.POSITIVE_INFINITY), - Offset(Float.POSITIVE_INFINITY, 0f) - ), shape = shape) - } else { - this.background(color = CurrentColors.value.colors.background, shape = shape) +fun Modifier.themedBackground(baseTheme: DefaultTheme = CurrentColors.value.base, bgLayerSize: MutableState?, bgLayer: GraphicsLayer?/*, shape: Shape = RectangleShape*/): Modifier { + return drawBehind { + copyBackgroundToAppBar(bgLayerSize, bgLayer) { + if (baseTheme == DefaultTheme.SIMPLEX) { + drawRect(brush = themedBackgroundBrush()) + } else { + drawRect(CurrentColors.value.colors.background) + } + } } } +fun themedBackgroundBrush(): Brush = Brush.linearGradient( + listOf( + CurrentColors.value.colors.background.darker(0.4f), + CurrentColors.value.colors.background.lighter(0.4f) + ), + Offset(0f, Float.POSITIVE_INFINITY), + Offset(Float.POSITIVE_INFINITY, 0f) +) + val DEFAULT_PADDING = 20.dp val DEFAULT_SPACE_AFTER_ICON = 4.dp val DEFAULT_PADDING_HALF = DEFAULT_PADDING / 2 diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/ThemeManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/ThemeManager.kt index 7f19f58949..a5293b6a24 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/ThemeManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/ThemeManager.kt @@ -1,7 +1,6 @@ package chat.simplex.common.ui.theme import androidx.compose.material.Colors -import androidx.compose.material.MaterialTheme import androidx.compose.runtime.MutableState import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb @@ -107,7 +106,7 @@ object ThemeManager { CurrentColors.value = currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get()) platform.androidSetNightModeIfSupported() val c = CurrentColors.value.colors - platform.androidSetStatusAndNavBarColors(c.isLight, c.background, !ChatController.appPrefs.oneHandUI.get(), ChatController.appPrefs.oneHandUI.get()) + platform.androidSetStatusAndNavigationBarAppearance(c.isLight, c.isLight) } fun changeDarkTheme(theme: String) { @@ -125,10 +124,6 @@ object ThemeManager { themeIds[nonSystemThemeName] = prevValue.themeId appPrefs.currentThemeIds.set(themeIds) CurrentColors.value = currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get()) - if (name == ThemeColor.BACKGROUND) { - val c = CurrentColors.value.colors - platform.androidSetStatusAndNavBarColors(c.isLight, c.background, false, false) - } } fun applyThemeColor(name: ThemeColor, color: Color? = null, pref: MutableState) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt index d89803f8e4..d4eb416081 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt @@ -7,40 +7,34 @@ import androidx.compose.foundation.lazy.* import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.* import androidx.compose.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.layoutId import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp +import androidx.compose.ui.unit.* import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chat.* import chat.simplex.common.views.helpers.* import chat.simplex.common.model.ChatModel import chat.simplex.common.platform.* -import kotlinx.coroutines.flow.collect +import chat.simplex.common.views.chat.item.CONSOLE_COMPOSE_LAYOUT_ID +import chat.simplex.common.views.chat.item.AdaptingBottomPaddingLayout +import chat.simplex.common.views.chatlist.NavigationBarBackground import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch @Composable -fun TerminalView(floating: Boolean = false, close: () -> Unit) { +fun TerminalView(floating: Boolean = false) { val composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = false)) } - val close = { - close() - if (appPlatform.isDesktop) { - ModalManager.center.closeModals() - } - } - BackHandler(onBack = { - close() - }) TerminalLayout( composeState, floating, sendCommand = { sendCommand(chatModel, composeState) }, - close ) } @@ -69,7 +63,6 @@ fun TerminalLayout( composeState: MutableState, floating: Boolean, sendCommand: () -> Unit, - close: () -> Unit ) { val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground) val textStyle = remember { mutableStateOf(smallFont) } @@ -77,65 +70,63 @@ fun TerminalLayout( fun onMessageChange(s: String) { composeState.value = composeState.value.copy(message = s) } - - ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { - Scaffold( - topBar = { CloseSheetBar(close) }, - bottomBar = { - Column { - Divider() - Box(Modifier.padding(horizontal = 8.dp)) { - SendMsgView( - composeState = composeState, - showVoiceRecordIcon = false, - recState = remember { mutableStateOf(RecordingState.NotStarted) }, - isDirectChat = false, - liveMessageAlertShown = SharedPreference(get = { false }, set = {}), - sendMsgEnabled = true, - sendButtonEnabled = true, - nextSendGrpInv = false, - needToAllowVoiceToContact = false, - allowedVoiceByPrefs = false, - userIsObserver = false, - userCanSend = true, - allowVoiceToContact = {}, - placeholder = "", - sendMessage = { sendCommand() }, - sendLiveMessage = null, - updateLiveMessage = null, - editPrevMessage = {}, - onMessageChange = ::onMessageChange, - onFilesPasted = {}, - textStyle = textStyle - ) - } - } - }, - contentColor = LocalContentColor.current, - modifier = Modifier.navigationBarsWithImePadding() - ) { contentPadding -> - Surface( - modifier = Modifier - .padding(contentPadding) - .fillMaxWidth(), - color = MaterialTheme.colors.background, - contentColor = LocalContentColor.current + val oneHandUI = remember { appPrefs.oneHandUI.state } + Box(Modifier.fillMaxSize()) { + val composeViewHeight = remember { mutableStateOf(0.dp) } + AdaptingBottomPaddingLayout(Modifier, CONSOLE_COMPOSE_LAYOUT_ID, composeViewHeight) { + TerminalLog(floating, composeViewHeight) + Column( + Modifier + .layoutId(CONSOLE_COMPOSE_LAYOUT_ID) + .align(Alignment.BottomCenter) + .navigationBarsPadding() + .consumeWindowInsets(PaddingValues(bottom = if (oneHandUI.value) AppBarHeight * fontSizeSqrtMultiplier else 0.dp)) + .imePadding() + .padding(bottom = if (oneHandUI.value) AppBarHeight * fontSizeSqrtMultiplier else 0.dp) + .background(MaterialTheme.colors.background) ) { - TerminalLog(floating) + Divider() + Box(Modifier.padding(horizontal = 8.dp)) { + SendMsgView( + composeState = composeState, + showVoiceRecordIcon = false, + recState = remember { mutableStateOf(RecordingState.NotStarted) }, + isDirectChat = false, + liveMessageAlertShown = SharedPreference(get = { false }, set = {}), + sendMsgEnabled = true, + sendButtonEnabled = true, + nextSendGrpInv = false, + needToAllowVoiceToContact = false, + allowedVoiceByPrefs = false, + userIsObserver = false, + userCanSend = true, + allowVoiceToContact = {}, + placeholder = "", + sendMessage = { sendCommand() }, + sendLiveMessage = null, + updateLiveMessage = null, + editPrevMessage = {}, + onMessageChange = ::onMessageChange, + onFilesPasted = {}, + textStyle = textStyle + ) + } } } + if (!oneHandUI.value) { + NavigationBarBackground(true, oneHandUI.value) + } } } @Composable -fun TerminalLog(floating: Boolean) { +fun TerminalLog(floating: Boolean, composeViewHeight: State) { val reversedTerminalItems by remember { derivedStateOf { chatModel.terminalItems.value.asReversed() } } - val clipboard = LocalClipboardManager.current val listState = LocalAppBarHandler.current?.listState ?: rememberLazyListState() LaunchedEffect(Unit) { - var autoScrollToBottom = true + var autoScrollToBottom = listState.firstVisibleItemIndex <= 1 launch { snapshotFlow { listState.layoutInfo.totalItemsCount } .filter { autoScrollToBottom } @@ -150,12 +141,21 @@ fun TerminalLog(floating: Boolean) { launch { snapshotFlow { listState.firstVisibleItemIndex } .collect { - autoScrollToBottom = listState.firstVisibleItemIndex == 0 + autoScrollToBottom = it == 0 } } } - LazyColumnWithScrollBar(reverseLayout = true, state = listState) { + LazyColumnWithScrollBar ( + reverseLayout = true, + contentPadding = PaddingValues( + top = topPaddingToContent(), + bottom = composeViewHeight.value + ), + state = listState, + additionalBarOffset = composeViewHeight + ) { items(reversedTerminalItems, key = { item -> item.id to item.createdAtNanos }) { item -> + val clipboard = LocalClipboardManager.current val rhId = item.remoteHostId val rhIdStr = if (rhId == null) "" else "$rhId " Text( @@ -172,13 +172,15 @@ fun TerminalLog(floating: Boolean) { ModalManager.start } modalPlace.showModal(endButtons = { ShareButton { clipboard.shareText(item.details) } }) { - SelectionContainer(modifier = Modifier.verticalScroll(rememberScrollState())) { - val details = item.details - .let { - if (it.length < 100_000) it - else it.substring(0, 100_000) - } - Text(details, modifier = Modifier.heightIn(max = 50_000.dp).padding(horizontal = DEFAULT_PADDING).padding(bottom = DEFAULT_PADDING)) + ColumnWithScrollBar { + SelectionContainer { + val details = item.details + .let { + if (it.length < 100_000) it + else it.substring(0, 100_000) + } + Text(details, modifier = Modifier.heightIn(max = 50_000.dp).padding(horizontal = DEFAULT_PADDING).padding(bottom = DEFAULT_PADDING)) + } } } }.padding(horizontal = 8.dp, vertical = 4.dp) @@ -208,8 +210,7 @@ fun PreviewTerminalLayout() { TerminalLayout( composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = false)) }, sendCommand = {}, - floating = false, - close = {} + floating = false ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt index 0d5350dbe0..e1e3dcb56b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt @@ -40,8 +40,6 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) { val scrollState = rememberScrollState() val keyboardState by getKeyboardState() var savedKeyboardState by remember { mutableStateOf(keyboardState) } - - ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { Box( modifier = Modifier .fillMaxSize() @@ -50,11 +48,9 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) { val displayName = rememberSaveable { mutableStateOf("") } val focusRequester = remember { FocusRequester() } - ColumnWithScrollBar( - modifier = Modifier.fillMaxSize() - ) { + ColumnWithScrollBar { Column(Modifier.padding(horizontal = DEFAULT_PADDING)) { - AppBarTitle(stringResource(MR.strings.create_profile), bottomPadding = DEFAULT_PADDING) + AppBarTitle(stringResource(MR.strings.create_profile), withPadding = false, bottomPadding = DEFAULT_PADDING) Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { Text( stringResource(MR.strings.display_name), @@ -102,7 +98,6 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) { } } } - } } @Composable @@ -111,59 +106,42 @@ fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) { val scrollState = rememberScrollState() val keyboardState by getKeyboardState() var savedKeyboardState by remember { mutableStateOf(keyboardState) } - val handler = remember { AppBarHandler() } - CompositionLocalProvider( - LocalAppBarHandler provides handler - ) { - ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { - Column( - modifier = Modifier - .fillMaxSize() - .themedBackground(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - CloseSheetBar(close = { - if (chatModel.users.none { !it.user.hidden }) { - appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) - } else { - close() + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { + ModalView({ + if (chatModel.users.none { !it.user.hidden }) { + appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) + } else { + close() + } + }) { + ColumnWithScrollBar { + val displayName = rememberSaveable { mutableStateOf("") } + val focusRequester = remember { FocusRequester() } + Column(if (appPlatform.isAndroid) Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING) else Modifier.widthIn(max = 600.dp).fillMaxHeight().padding(horizontal = DEFAULT_PADDING).align(Alignment.CenterHorizontally)) { + Box(Modifier.align(Alignment.CenterHorizontally)) { + AppBarTitle(stringResource(MR.strings.create_profile), bottomPadding = DEFAULT_PADDING, withPadding = false) } - }) - BackHandler(onBack = { - appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) - }) + ProfileNameField(displayName, stringResource(MR.strings.display_name), { it.trim() == mkValidName(it) }, focusRequester) + Spacer(Modifier.height(DEFAULT_PADDING)) + ReadableText(MR.strings.your_profile_is_stored_on_your_device, TextAlign.Start, padding = PaddingValues(), style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary)) + ReadableText(MR.strings.profile_is_only_shared_with_your_contacts, TextAlign.Start, style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary)) + } + Spacer(Modifier.fillMaxHeight().weight(1f)) + Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + OnboardingActionButton( + if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp), + labelId = MR.strings.create_profile_button, + onboarding = null, + enabled = canCreateProfile(displayName.value), + onclick = { createProfileOnboarding(chat.simplex.common.platform.chatModel, displayName.value, close) } + ) + // Reserve space + TextButtonBelowOnboardingButton("", null) + } - ColumnWithScrollBar( - modifier = Modifier.fillMaxSize() - ) { - val displayName = rememberSaveable { mutableStateOf("") } - val focusRequester = remember { FocusRequester() } - Column(if (appPlatform.isAndroid) Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING) else Modifier.widthIn(max = 600.dp).fillMaxHeight().padding(horizontal = DEFAULT_PADDING).align(Alignment.CenterHorizontally)) { - Box(Modifier.align(Alignment.CenterHorizontally)) { - AppBarTitle(stringResource(MR.strings.create_profile), bottomPadding = DEFAULT_PADDING, withPadding = false) - } - ProfileNameField(displayName, stringResource(MR.strings.display_name), { it.trim() == mkValidName(it) }, focusRequester) - Spacer(Modifier.height(DEFAULT_PADDING)) - ReadableText(MR.strings.your_profile_is_stored_on_your_device, TextAlign.Start, padding = PaddingValues(), style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary)) - ReadableText(MR.strings.profile_is_only_shared_with_your_contacts, TextAlign.Start, style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary)) - } - Spacer(Modifier.fillMaxHeight().weight(1f)) - Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { - OnboardingActionButton( - if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp), - labelId = MR.strings.create_profile_button, - onboarding = null, - enabled = canCreateProfile(displayName.value), - onclick = { createProfileOnboarding(chat.simplex.common.platform.chatModel, displayName.value, close) } - ) - // Reserve space - TextButtonBelowOnboardingButton("", null) - } - - LaunchedEffect(Unit) { - delay(300) - focusRequester.requestFocus() - } + LaunchedEffect(Unit) { + delay(300) + focusRequester.requestFocus() } } LaunchedEffect(Unit) { @@ -255,7 +233,6 @@ fun ProfileNameField(name: MutableState, placeholder: String = "", isVal val modifier = Modifier .fillMaxWidth() .heightIn(min = 50.dp) - .navigationBarsWithImePadding() .onFocusChanged { focused = it.isFocused } Column( Modifier diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/IncomingCallAlertView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/IncomingCallAlertView.kt index 32681234fa..4d8c1fae46 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/IncomingCallAlertView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/IncomingCallAlertView.kt @@ -53,7 +53,7 @@ fun IncomingCallAlertLayout( acceptCall: () -> Unit ) { val color = if (isInDarkTheme()) MaterialTheme.colors.surface else IncomingCallLight - Column(Modifier.fillMaxWidth().background(color).padding(top = DEFAULT_PADDING, bottom = DEFAULT_PADDING, start = DEFAULT_PADDING, end = 8.dp)) { + Column(Modifier.fillMaxWidth().background(color).statusBarsPadding().padding(top = DEFAULT_PADDING, bottom = DEFAULT_PADDING, start = DEFAULT_PADDING, end = 8.dp)) { IncomingCallInfo(invitation, chatModel) Spacer(Modifier.height(8.dp)) Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt index 9149b039ef..df13368900 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt @@ -529,10 +529,7 @@ fun ChatInfoLayout( KeyChangeEffect(chat.id) { scope.launch { scrollState.scrollTo(0) } } - ColumnWithScrollBar( - Modifier - .fillMaxWidth() - ) { + ColumnWithScrollBar { Row( Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt index bdbfdb89c3..30bbe72a72 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt @@ -276,7 +276,7 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools @Composable fun HistoryTab() { - ColumnWithScrollBar(Modifier.fillMaxWidth()) { + ColumnWithScrollBar { Details() SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true) val versions = ciInfo.itemVersions @@ -300,7 +300,7 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools @Composable fun QuoteTab(qi: CIQuote) { - ColumnWithScrollBar(Modifier.fillMaxWidth()) { + ColumnWithScrollBar { Details() SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true) SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { @@ -313,7 +313,7 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools @Composable fun ForwardedFromTab(forwardedFromItem: AChatItem) { - ColumnWithScrollBar(Modifier.fillMaxWidth()) { + ColumnWithScrollBar { Details() SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true) SectionView { @@ -375,7 +375,7 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools @Composable fun DeliveryTab(memberDeliveryStatuses: List) { - ColumnWithScrollBar(Modifier.fillMaxWidth()) { + ColumnWithScrollBar { Details() SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true) val mss = membersStatuses(chatModel, memberDeliveryStatuses) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 570f763e99..2c43a81f7d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -12,10 +12,11 @@ import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.* -import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.draw.* import androidx.compose.ui.graphics.* +import androidx.compose.ui.graphics.layer.GraphicsLayer import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.* import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource @@ -108,6 +109,7 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - } } val clipboard = LocalClipboardManager.current + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler(chatInfo.id, keyboardCoversBar = false)) { when (chatInfo) { is ChatInfo.Direct, is ChatInfo.Group, is ChatInfo.Local -> { val perChatTheme = remember(chatInfo, CurrentColors.value.base) { if (chatInfo is ChatInfo.Direct) chatInfo.contact.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight) else if (chatInfo is ChatInfo.Group) chatInfo.groupInfo.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight) else null } @@ -523,28 +525,10 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - showViaProxy = chatModel.controller.appPrefs.showSentViaProxy.get(), showSearch = showSearch ) - if (appPlatform.isAndroid) { - val backgroundColor = MaterialTheme.colors.background - val backgroundColorState = rememberUpdatedState(backgroundColor) - LaunchedEffect(Unit) { - snapshotFlow { ModalManager.center.modalCount.value > 0 } - .collect { modalBackground -> - if (modalBackground) { - platform.androidSetStatusAndNavBarColors(CurrentColors.value.colors.isLight, CurrentColors.value.colors.background, false, false) - } else { - platform.androidSetStatusAndNavBarColors(CurrentColors.value.colors.isLight, backgroundColorState.value, true, false) - } - } - } - } } } is ChatInfo.ContactConnection -> { val close = { chatModel.chatId.value = null } - val handler = remember { AppBarHandler() } - CompositionLocalProvider( - LocalAppBarHandler provides handler - ) { ModalView(close, showClose = appPlatform.isAndroid, content = { ContactConnectionInfoView(chatModel, chatRh, chatInfo.contactConnection.connReqInv, chatInfo.contactConnection, false, close) }) @@ -553,14 +537,9 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - ModalManager.end.closeModals() chatModel.chatItems.clear() } - } } is ChatInfo.InvalidJSON -> { val close = { chatModel.chatId.value = null } - val handler = remember { AppBarHandler() } - CompositionLocalProvider( - LocalAppBarHandler provides handler - ) { ModalView(close, showClose = appPlatform.isAndroid, endButtons = { ShareButton { clipboard.shareText(chatInfo.json) } }, content = { InvalidJSONView(chatInfo.json) }) @@ -569,10 +548,10 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - ModalManager.end.closeModals() chatModel.chatItems.clear() } - } } else -> {} } + } } } @@ -649,67 +628,60 @@ fun ChatLayout( }, ) ) { - ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { - ModalBottomSheetLayout( - scrimColor = Color.Black.copy(alpha = 0.12F), - modifier = Modifier.navigationBarsWithImePadding(), - sheetElevation = 0.dp, - sheetContent = { - ChooseAttachmentView( - attachmentOption, - hide = { scope.launch { attachmentBottomSheetState.hide() } } - ) - }, - sheetState = attachmentBottomSheetState, - sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp) - ) { - val floatingButton: MutableState<@Composable () -> Unit> = remember { mutableStateOf({}) } - val setFloatingButton = { button: @Composable () -> Unit -> - floatingButton.value = button - } - - Scaffold( - topBar = { - if (selectedChatItems.value == null) { - val chatInfo = chatInfo.value - if (chatInfo != null) { - ChatInfoToolbar(chatInfo, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch) - } - } else { - SelectedItemsTopToolbar(selectedChatItems) - } - }, - bottomBar = composeView, - modifier = Modifier.navigationBarsWithImePadding(), - floatingActionButton = { floatingButton.value() }, - contentColor = LocalContentColor.current, - backgroundColor = Color.Unspecified - ) { contentPadding -> - val wallpaperImage = MaterialTheme.wallpaper.type.image - val wallpaperType = MaterialTheme.wallpaper.type - val backgroundColor = MaterialTheme.wallpaper.background ?: wallpaperType.defaultBackgroundColor(CurrentColors.value.base, MaterialTheme.colors.background) - val tintColor = MaterialTheme.wallpaper.tint ?: wallpaperType.defaultTintColor(CurrentColors.value.base) - BoxWithConstraints(Modifier - .fillMaxSize() - .background(MaterialTheme.colors.background) - .then(if (wallpaperImage != null) - Modifier.drawWithCache { chatViewBackground(wallpaperImage, wallpaperType, backgroundColor, tintColor) } - else - Modifier) - .padding(contentPadding) - ) { - val remoteHostId = remember { remoteHostId }.value - val chatInfo = remember { chatInfo }.value - if (chatInfo != null) { + ModalBottomSheetLayout( + scrimColor = Color.Black.copy(alpha = 0.12F), + sheetElevation = 0.dp, + sheetContent = { + ChooseAttachmentView( + attachmentOption, + hide = { scope.launch { attachmentBottomSheetState.hide() } } + ) + }, + sheetState = attachmentBottomSheetState, + sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp) + ) { + val composeViewHeight = remember { mutableStateOf(0.dp) } + Box(Modifier.fillMaxSize().chatViewBackgroundModifier(MaterialTheme.colors, MaterialTheme.wallpaper, LocalAppBarHandler.current?.backgroundGraphicsLayerSize, LocalAppBarHandler.current?.backgroundGraphicsLayer)) { + val remoteHostId = remember { remoteHostId }.value + val chatInfo = remember { chatInfo }.value + AdaptingBottomPaddingLayout(Modifier, CHAT_COMPOSE_LAYOUT_ID, composeViewHeight) { + if (chatInfo != null) { + Box(Modifier.fillMaxSize()) { ChatItemsList( - remoteHostId, chatInfo, unreadCount, composeState, searchValue, + remoteHostId, chatInfo, unreadCount, composeState, composeViewHeight, searchValue, useLinkPreviews, linkMode, selectedChatItems, showMemberInfo, loadPrevMessages, deleteMessage, deleteMessages, receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat, forwardItem, updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember, - setReaction, showItemDetails, markRead, setFloatingButton, onComposed, developerTools, showViaProxy, + setReaction, showItemDetails, markRead, remember { { onComposed(it) } }, developerTools, showViaProxy, ) } } + val oneHandUI = remember { appPrefs.oneHandUI.state } + Box( + Modifier + .layoutId(CHAT_COMPOSE_LAYOUT_ID) + .align(Alignment.BottomCenter) + .imePadding() + .navigationBarsPadding() + .then(if (oneHandUI.value) Modifier.padding(bottom = AppBarHeight * fontSizeSqrtMultiplier) else Modifier) + ) { + composeView() + } + } + val oneHandUI = remember { appPrefs.oneHandUI.state } + if (oneHandUI.value) { + StatusBarBackground() + } else { + NavigationBarBackground(true, oneHandUI.value, noAlpha = true) + } + Box(if (oneHandUI.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) { + if (selectedChatItems.value == null) { + if (chatInfo != null) { + ChatInfoToolbar(chatInfo, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch) + } + } else { + SelectedItemsTopToolbar(selectedChatItems) + } } } } @@ -717,7 +689,7 @@ fun ChatLayout( } @Composable -fun ChatInfoToolbar( +fun BoxScope.ChatInfoToolbar( chatInfo: ChatInfo, back: () -> Unit, info: () -> Unit, @@ -869,21 +841,33 @@ fun ChatInfoToolbar( } } } - - DefaultTopAppBar( + val oneHandUI = remember { appPrefs.oneHandUI.state } + DefaultAppBar( navigationButton = { if (appPlatform.isAndroid || showSearch.value) { NavigationButtonBack(onBackClicked) } }, title = { ChatInfoToolbarTitle(chatInfo) }, onTitleClick = if (chatInfo is ChatInfo.Local) null else info, showSearch = showSearch.value, + onTop = !oneHandUI.value, onSearchValueChanged = onSearchValueChanged, - buttons = barButtons + buttons = { barButtons.forEach { it() } } ) - - Divider(Modifier.padding(top = AppBarHeight * fontSizeSqrtMultiplier)) - - Box(Modifier.fillMaxWidth().wrapContentSize(Alignment.TopEnd).offset(y = AppBarHeight * fontSizeSqrtMultiplier)) { - DefaultDropdownMenu(showMenu) { - menuItems.forEach { it() } + Box(Modifier.fillMaxWidth().wrapContentSize(Alignment.TopEnd)) { + val density = LocalDensity.current + val width = remember { mutableStateOf(250.dp) } + val height = remember { mutableStateOf(0.dp) } + DefaultDropdownMenu( + showMenu, + modifier = Modifier.onSizeChanged { with(density) { + width.value = it.width.toDp().coerceAtLeast(250.dp) + if (oneHandUI.value && (appPlatform.isDesktop || (platform.androidApiLevel ?: 0) >= 30)) height.value = it.height.toDp() + } }, + offset = DpOffset(-width.value, if (oneHandUI.value) -height.value else AppBarHeight) + ) { + if (oneHandUI.value) { + menuItems.asReversed().forEach { it() } + } else { + menuItems.forEach { it() } + } } } } @@ -927,11 +911,12 @@ private fun ContactVerifiedShield() { } @Composable -fun BoxWithConstraintsScope.ChatItemsList( +fun BoxScope.ChatItemsList( remoteHostId: Long?, chatInfo: ChatInfo, unreadCount: State, composeState: MutableState, + composeViewHeight: State, searchValue: State, useLinkPreviews: Boolean, linkMode: SimplexLinkMode, @@ -956,7 +941,6 @@ fun BoxWithConstraintsScope.ChatItemsList( setReaction: (ChatInfo, ChatItem, Boolean, MsgReaction) -> Unit, showItemDetails: (ChatInfo, ChatItem) -> Unit, markRead: (CC.ItemRange, unreadCountAfter: Int?) -> Unit, - setFloatingButton: (@Composable () -> Unit) -> Unit, onComposed: suspend (chatId: String) -> Unit, developerTools: Boolean, showViaProxy: Boolean @@ -980,13 +964,18 @@ fun BoxWithConstraintsScope.ChatItemsList( PreloadItems(chatInfo.id, listState, ChatPagination.UNTIL_PRELOAD_COUNT, loadPrevMessages) Spacer(Modifier.size(8.dp)) - val reversedChatItems by remember { derivedStateOf { chatModel.chatItems.asReversed() } } - val maxHeightRounded = with(LocalDensity.current) { maxHeight.roundToPx() } - val scrollToItem: (Long) -> Unit = { itemId: Long -> - val index = reversedChatItems.indexOfFirst { it.id == itemId } - if (index != -1) { - scope.launch { listState.animateScrollToItem(kotlin.math.min(reversedChatItems.lastIndex, index + 1), -maxHeightRounded) } - } + val reversedChatItems = remember { derivedStateOf { chatModel.chatItems.asReversed() } } + val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent().roundToPx() }) + val maxHeight = remember { derivedStateOf { listState.layoutInfo.viewportEndOffset - topPaddingToContentPx.value } } + val scrollToItem: State<(Long) -> Unit> = remember { + mutableStateOf( + { itemId: Long -> + val index = reversedChatItems.value.indexOfFirst { it.id == itemId } + if (index != -1) { + scope.launch { listState.animateScrollToItem(kotlin.math.min(reversedChatItems.value.lastIndex, index + 1), -maxHeight.value) } + } + } + ) } // TODO: Having this block on desktop makes ChatItemsList() to recompose twice on chatModel.chatId update instead of once LaunchedEffect(chatInfo.id) { @@ -1004,8 +993,18 @@ fun BoxWithConstraintsScope.ChatItemsList( VideoPlayerHolder.releaseAll() } ) - LazyColumnWithScrollBar(Modifier.align(Alignment.BottomCenter), state = listState, reverseLayout = true) { - itemsIndexed(reversedChatItems, key = { _, item -> item.id to item.meta.createdAt.toEpochMilliseconds() }) { i, cItem -> + LazyColumnWithScrollBar( + Modifier.align(Alignment.BottomCenter), + state = listState, + reverseLayout = true, + contentPadding = PaddingValues( + top = topPaddingToContent(), + bottom = composeViewHeight.value + ), + additionalBarOffset = composeViewHeight + ) { + itemsIndexed(reversedChatItems.value, key = { _, item -> item.id to item.meta.createdAt.toEpochMilliseconds() }) { i, cItem -> + val itemScope = rememberCoroutineScope() CompositionLocalProvider( // Makes horizontal and vertical scrolling to coexist nicely. // With default touchSlop when you scroll LazyColumn, you can unintentionally open reply view @@ -1013,10 +1012,10 @@ fun BoxWithConstraintsScope.ChatItemsList( ) { val provider = { providerForGallery(i, chatModel.chatItems.value, cItem.id) { indexInReversed -> - scope.launch { + itemScope.launch { listState.scrollToItem( - kotlin.math.min(reversedChatItems.lastIndex, indexInReversed + 1), - -maxHeightRounded + kotlin.math.min(reversedChatItems.value.lastIndex, indexInReversed + 1), + -maxHeight.value ) } } @@ -1029,7 +1028,7 @@ fun BoxWithConstraintsScope.ChatItemsList( tryOrShowError("${cItem.id}ChatItem", error = { CIBrokenComposableView(if (cItem.chatDir.sent) Alignment.CenterEnd else Alignment.CenterStart) }) { - ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, range = range, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp) + ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, range = range, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem.value, setReaction = setReaction, showItemDetails = showItemDetails, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp) } } @@ -1037,7 +1036,7 @@ fun BoxWithConstraintsScope.ChatItemsList( fun ChatItemView(cItem: ChatItem, range: IntRange?, prevItem: ChatItem?, itemSeparation: ItemSeparation, previousItemSeparation: ItemSeparation?) { val dismissState = rememberDismissState(initialValue = DismissValue.Default) { if (it == DismissValue.DismissedToStart) { - scope.launch { + itemScope.launch { if ((cItem.content is CIContent.SndMsgContent || cItem.content is CIContent.RcvMsgContent) && chatInfo !is ChatInfo.Local) { if (composeState.value.editing) { composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem), useLinkPreviews = useLinkPreviews) @@ -1234,16 +1233,17 @@ fun BoxWithConstraintsScope.ChatItemsList( } val range = chatViewItemsRange(currIndex, prevHidden) + val reversed = reversedChatItems.value if (revealed.value && range != null) { - reversedChatItems.subList(range.first, range.last + 1).forEachIndexed { index, ci -> - val prev = if (index + range.first == prevHidden) prevItem else reversedChatItems[index + range.first + 1] + reversed.subList(range.first, range.last + 1).forEachIndexed { index, ci -> + val prev = if (index + range.first == prevHidden) prevItem else reversed[index + range.first + 1] ChatItemView(ci, null, prev, itemSeparation, previousItemSeparation) } } else { ChatItemView(cItem, range, prevItem, itemSeparation, previousItemSeparation) } - if (i == reversedChatItems.lastIndex) { + if (i == reversed.lastIndex) { DateSeparator(cItem.meta.itemTs) } } @@ -1251,7 +1251,7 @@ fun BoxWithConstraintsScope.ChatItemsList( if (cItem.isRcvNew && chatInfo.id == ChatModel.chatId.value) { LaunchedEffect(cItem.id) { - scope.launch { + itemScope.launch { delay(600) markRead(CC.ItemRange(cItem.id, cItem.id), null) } @@ -1260,10 +1260,10 @@ fun BoxWithConstraintsScope.ChatItemsList( } } } - FloatingButtons(chatModel.chatItems, unreadCount, remoteHostId, chatInfo, searchValue, markRead, setFloatingButton, listState) + FloatingButtons(chatModel.chatItems, unreadCount, composeViewHeight, remoteHostId, chatInfo, searchValue, markRead, listState) FloatingDate( - Modifier.padding(top = 10.dp).align(Alignment.TopCenter), + Modifier.padding(top = 10.dp + topPaddingToContent()).align(Alignment.TopCenter), listState, ) @@ -1318,87 +1318,65 @@ private fun ScrollToBottom(chatId: ChatId, listState: LazyListState, chatItems: } @Composable -fun BoxWithConstraintsScope.FloatingButtons( +fun BoxScope.FloatingButtons( chatItems: State>, unreadCount: State, + composeViewHeight: State, remoteHostId: Long?, chatInfo: ChatInfo, searchValue: State, markRead: (CC.ItemRange, unreadCountAfter: Int?) -> Unit, - setFloatingButton: (@Composable () -> Unit) -> Unit, listState: LazyListState ) { val scope = rememberCoroutineScope() - var firstVisibleIndex by remember { mutableStateOf(listState.firstVisibleItemIndex) } - var lastIndexOfVisibleItems by remember { mutableStateOf(listState.layoutInfo.visibleItemsInfo.lastIndex) } - var firstItemIsVisible by remember { mutableStateOf(firstVisibleIndex == 0) } - - LaunchedEffect(listState) { - snapshotFlow { listState.firstVisibleItemIndex } - .distinctUntilChanged() - .collect { - firstVisibleIndex = it - firstItemIsVisible = firstVisibleIndex == 0 - } - } - - LaunchedEffect(listState) { - // When both snapshotFlows located in one LaunchedEffect second block will never be called because coroutine is paused on first block - // so separate them into two LaunchedEffects - snapshotFlow { listState.layoutInfo.visibleItemsInfo.lastIndex } - .distinctUntilChanged() - .collect { - lastIndexOfVisibleItems = it - } - } - val bottomUnreadCount by remember { + val maxHeight = remember { derivedStateOf { listState.layoutInfo.viewportSize.height } } + val bottomUnreadCount = remember { derivedStateOf { if (unreadCount.value == 0) return@derivedStateOf 0 val items = chatItems.value - val from = items.lastIndex - firstVisibleIndex - lastIndexOfVisibleItems + val from = items.lastIndex - listState.firstVisibleItemIndex - listState.layoutInfo.visibleItemsInfo.lastIndex if (items.size <= from || from < 0) return@derivedStateOf 0 items.subList(from, items.size).count { it.isRcvNew } } } - val firstVisibleOffset = (-with(LocalDensity.current) { maxHeight.roundToPx() } * 0.8).toInt() - LaunchedEffect(bottomUnreadCount, firstItemIsVisible) { - val showButtonWithCounter = bottomUnreadCount > 0 && !firstItemIsVisible && searchValue.value.isEmpty() - val showButtonWithArrow = !showButtonWithCounter && !firstItemIsVisible - setFloatingButton( - bottomEndFloatingButton( - bottomUnreadCount, - showButtonWithCounter, - showButtonWithArrow, - onClickArrowDown = { - scope.launch { listState.animateScrollToItem(0) } - }, - onClickCounter = { - scope.launch { listState.animateScrollToItem(kotlin.math.max(0, bottomUnreadCount - 1), firstVisibleOffset) } - } - )) - } + val showBottomButtonWithCounter = remember { derivedStateOf { bottomUnreadCount.value > 0 && listState.firstVisibleItemIndex != 0 && searchValue.value.isEmpty() } } + val showBottomButtonWithArrow = remember { derivedStateOf { !showBottomButtonWithCounter.value && listState.firstVisibleItemIndex != 0 } } + BottomEndFloatingButton( + bottomUnreadCount, + showBottomButtonWithCounter, + showBottomButtonWithArrow, + composeViewHeight, + onClickArrowDown = { + scope.launch { listState.animateScrollToItem(0) } + }, + onClickCounter = { + val firstVisibleOffset = (-maxHeight.value * 0.8).toInt() + scope.launch { listState.animateScrollToItem(kotlin.math.max(0, bottomUnreadCount.value - 1), firstVisibleOffset) } + } + ) // Don't show top FAB if is in search if (searchValue.value.isNotEmpty()) return val fabSize = 56.dp - val topUnreadCount by remember { - derivedStateOf { unreadCount.value - bottomUnreadCount } - } - val showButtonWithCounter = topUnreadCount > 0 - val height = with(LocalDensity.current) { maxHeight.toPx() } + val topUnreadCount = remember { derivedStateOf { unreadCount.value - bottomUnreadCount.value } } val showDropDown = remember { mutableStateOf(false) } TopEndFloatingButton( - Modifier.padding(end = DEFAULT_PADDING, top = 24.dp).align(Alignment.TopEnd), + Modifier.padding(end = DEFAULT_PADDING, top = 24.dp + topPaddingToContent()).align(Alignment.TopEnd), topUnreadCount, - showButtonWithCounter, - onClick = { scope.launch { listState.animateScrollBy(height) } }, + onClick = { scope.launch { listState.animateScrollBy(maxHeight.value.toFloat()) } }, onLongClick = { showDropDown.value = true } ) - Box { - DefaultDropdownMenu(showDropDown, offset = DpOffset(this@FloatingButtons.maxWidth - DEFAULT_PADDING, 24.dp + fabSize)) { + Box(Modifier.fillMaxWidth().wrapContentSize(Alignment.TopEnd)) { + val density = LocalDensity.current + val width = remember { mutableStateOf(250.dp) } + DefaultDropdownMenu( + showDropDown, + modifier = Modifier.onSizeChanged { with(density) { width.value = it.width.toDp().coerceAtLeast(250.dp) } }, + offset = DpOffset(-DEFAULT_PADDING - width.value, 24.dp + fabSize + topPaddingToContent()) + ) { ItemAction( generalGetString(MR.strings.mark_read), painterResource(MR.images.ic_check), @@ -1406,7 +1384,7 @@ fun BoxWithConstraintsScope.FloatingButtons( val minUnreadItemId = chatModel.chats.value.firstOrNull { it.remoteHostId == remoteHostId && it.id == chatInfo.id }?.chatStats?.minUnreadItemId ?: return@ItemAction markRead( CC.ItemRange(minUnreadItemId, chatItems.value[chatItems.value.size - listState.layoutInfo.visibleItemsInfo.lastIndex - 1].id - 1), - bottomUnreadCount + bottomUnreadCount.value ) showDropDown.value = false }) @@ -1468,12 +1446,11 @@ fun MemberImage(member: GroupMember) { @Composable private fun TopEndFloatingButton( modifier: Modifier = Modifier, - unreadCount: Int, - showButtonWithCounter: Boolean, + unreadCount: State, onClick: () -> Unit, onLongClick: () -> Unit ) = when { - showButtonWithCounter -> { + unreadCount.value > 0 -> { val interactionSource = interactionSourceWithDetection(onClick, onLongClick) FloatingActionButton( {}, // no action here @@ -1483,7 +1460,7 @@ private fun TopEndFloatingButton( interactionSource = interactionSource, ) { Text( - unreadCountStr(unreadCount), + unreadCountStr(unreadCount.value), color = MaterialTheme.colors.primary, fontSize = 14.sp, ) @@ -1493,6 +1470,16 @@ private fun TopEndFloatingButton( } } +@Composable +fun topPaddingToContent(): Dp { + val oneHandUI = remember { appPrefs.oneHandUI.state } + return if (oneHandUI.value) { + WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + } else { + AppBarHeight * fontSizeSqrtMultiplier + WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + } +} + @Composable private fun FloatingDate( modifier: Modifier, @@ -1502,8 +1489,9 @@ private fun FloatingDate( var isNearBottom by remember { mutableStateOf(true) } val lastVisibleItemDate = remember { derivedStateOf { - if (listState.layoutInfo.visibleItemsInfo.lastIndex >= 0 && listState.firstVisibleItemIndex >= 0) { - val lastVisibleChatItemIndex = chatModel.chatItems.value.lastIndex - listState.firstVisibleItemIndex - listState.layoutInfo.visibleItemsInfo.lastIndex + if (listState.layoutInfo.visibleItemsInfo.lastIndex >= 0) { + val lastFullyVisibleOffset = listState.layoutInfo.viewportEndOffset + val lastVisibleChatItemIndex = chatModel.chatItems.value.lastIndex - (listState.layoutInfo.visibleItemsInfo.lastOrNull { item -> item.offset + item.size <= lastFullyVisibleOffset && item.size > 0 }?.index ?: 0) val item = chatModel.chatItems.value.getOrNull(lastVisibleChatItemIndex) val timeZone = TimeZone.currentSystemDefault() item?.meta?.itemTs?.toLocalDateTime(timeZone)?.date?.atStartOfDayIn(timeZone) @@ -1690,48 +1678,44 @@ fun openGroupLink(groupInfo: GroupInfo, rhId: Long?, view: Any? = null, close: ( } } -private fun bottomEndFloatingButton( - unreadCount: Int, - showButtonWithCounter: Boolean, - showButtonWithArrow: Boolean, +@Composable +private fun BoxScope.BottomEndFloatingButton( + unreadCount: State, + showButtonWithCounter: State, + showButtonWithArrow: State, + composeViewHeight: State, onClickArrowDown: () -> Unit, onClickCounter: () -> Unit -): @Composable () -> Unit = when { - showButtonWithCounter -> { - { - FloatingActionButton( - onClick = onClickCounter, - elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp), - modifier = Modifier.size(48.dp), - backgroundColor = MaterialTheme.colors.secondaryVariant, - ) { - Text( - unreadCountStr(unreadCount), - color = MaterialTheme.colors.primary, - fontSize = 14.sp, - ) - } +) = when { + showButtonWithCounter.value -> { + FloatingActionButton( + onClick = onClickCounter, + elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp), + modifier = Modifier.padding(end = DEFAULT_PADDING, bottom = DEFAULT_PADDING + composeViewHeight.value).align(Alignment.BottomEnd).size(48.dp), + backgroundColor = MaterialTheme.colors.secondaryVariant, + ) { + Text( + unreadCountStr(unreadCount.value), + color = MaterialTheme.colors.primary, + fontSize = 14.sp, + ) } } - showButtonWithArrow -> { - { - FloatingActionButton( - onClick = onClickArrowDown, - elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp), - modifier = Modifier.size(48.dp), - backgroundColor = MaterialTheme.colors.secondaryVariant, - ) { - Icon( - painter = painterResource(MR.images.ic_keyboard_arrow_down), - contentDescription = null, - tint = MaterialTheme.colors.primary - ) - } + showButtonWithArrow.value -> { + FloatingActionButton( + onClick = onClickArrowDown, + elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp), + modifier = Modifier.padding(end = DEFAULT_PADDING, bottom = DEFAULT_PADDING + composeViewHeight.value).align(Alignment.BottomEnd).size(48.dp), + backgroundColor = MaterialTheme.colors.secondaryVariant, + ) { + Icon( + painter = painterResource(MR.images.ic_keyboard_arrow_down), + contentDescription = null, + tint = MaterialTheme.colors.primary + ) } } - else -> { - {} - } + else -> {} } @Composable @@ -1858,6 +1842,25 @@ private fun memberNames(member: GroupMember, prevMember: GroupMember?, memCount: } } +fun Modifier.chatViewBackgroundModifier( + colors: Colors, + wallpaper: AppWallpaper, + backgroundGraphicsLayerSize: MutableState?, + backgroundGraphicsLayer: GraphicsLayer? +): Modifier { + val wallpaperImage = wallpaper.type.image + val wallpaperType = wallpaper.type + val backgroundColor = wallpaper.background ?: wallpaperType.defaultBackgroundColor(CurrentColors.value.base, colors.background) + val tintColor = wallpaper.tint ?: wallpaperType.defaultTintColor(CurrentColors.value.base) + + return this + .then(if (wallpaperImage != null) + Modifier.drawWithCache { chatViewBackground(wallpaperImage, wallpaperType, backgroundColor, tintColor, backgroundGraphicsLayerSize, backgroundGraphicsLayer) } + else + Modifier.drawWithCache { onDrawBehind { copyBackgroundToAppBar(backgroundGraphicsLayerSize, backgroundGraphicsLayer) { drawRect(backgroundColor) } } } + ) +} + fun chatViewItemsRange(currIndex: Int?, prevHidden: Int?): IntRange? = if (currIndex != null && prevHidden != null && prevHidden > currIndex) { currIndex..prevHidden diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index cad18af9bb..fd1d3ab92d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -13,12 +13,14 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.text.font.FontStyle import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.model.ChatModel.filesToDelete import chat.simplex.common.model.ChatModel.withChats @@ -896,7 +898,7 @@ fun ComposeView( } } } - Column(Modifier.background(MaterialTheme.colors.background)) { + Box(Modifier.background(MaterialTheme.colors.background)) { Divider() Row(Modifier.padding(end = 8.dp), verticalAlignment = Alignment.Bottom) { val isGroupAndProhibitedFiles = chat.chatInfo is ChatInfo.Group && !chat.chatInfo.groupInfo.fullGroupPreferences.files.on(chat.chatInfo.groupInfo.membership) @@ -918,7 +920,7 @@ fun ComposeView( && !nextSendGrpInv.value IconButton( attachmentClicked, - Modifier.padding(bottom = if (appPlatform.isAndroid) 2.sp.toDp() else 5.sp.toDp() * fontSizeSqrtMultiplier), + Modifier.padding(start = 3.dp, end = 1.dp, bottom = if (appPlatform.isAndroid) 2.sp.toDp() else 5.sp.toDp() * fontSizeSqrtMultiplier), enabled = attachmentEnabled ) { Icon( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt index 725367e150..b1e9bf750e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt @@ -81,10 +81,7 @@ private fun ContactPreferencesLayout( reset: () -> Unit, savePrefs: () -> Unit, ) { - ColumnWithScrollBar( - Modifier - .fillMaxWidth(), - ) { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.contact_preferences)) val timedMessages: MutableState = remember(featuresAllowed) { mutableStateOf(featuresAllowed.timedMessagesAllowed) } val onTTLUpdated = { ttl: Int? -> diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ScanCodeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ScanCodeView.kt index 73017c3d42..a12a75b747 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ScanCodeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ScanCodeView.kt @@ -1,9 +1,11 @@ package chat.simplex.common.views.chat +import SectionBottomSpacer import androidx.compose.foundation.layout.* import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.common.ui.theme.DEFAULT_PADDING import chat.simplex.common.views.helpers.* import chat.simplex.common.views.newchat.QRCodeScanner @@ -12,9 +14,7 @@ import dev.icerock.moko.resources.compose.stringResource @Composable fun ScanCodeView(verifyCode: (String?, cb: (Boolean) -> Unit) -> Unit, close: () -> Unit) { - Column( - Modifier.fillMaxSize() - ) { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.scan_code)) QRCodeScanner { text -> verifyCode(text) { @@ -28,5 +28,6 @@ fun ScanCodeView(verifyCode: (String?, cb: (Boolean) -> Unit) -> Unit, close: () } } Text(stringResource(MR.strings.scan_code_from_contacts_app), Modifier.padding(horizontal = DEFAULT_PADDING)) + SectionBottomSpacer() } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt index 5cf9ebb6c7..838398c503 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.BackHandler import chat.simplex.common.platform.chatModel import chat.simplex.common.views.helpers.* @@ -20,11 +21,12 @@ import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource @Composable -fun SelectedItemsTopToolbar(selectedChatItems: MutableState?>) { +fun BoxScope.SelectedItemsTopToolbar(selectedChatItems: MutableState?>) { val onBackClicked = { selectedChatItems.value = null } BackHandler(onBack = onBackClicked) val count = selectedChatItems.value?.size ?: 0 - DefaultTopAppBar( + val oneHandUI = remember { appPrefs.oneHandUI.state } + DefaultAppBar( navigationButton = { NavigationButtonClose(onButtonClicked = onBackClicked) }, title = { Text( @@ -39,10 +41,9 @@ fun SelectedItemsTopToolbar(selectedChatItems: MutableState?>) { ) }, onTitleClick = null, - showSearch = false, + onTop = !oneHandUI.value, onSearchValueChanged = {}, ) - Divider(Modifier.padding(top = AppBarHeight * fontSizeSqrtMultiplier)) } @Composable @@ -68,6 +69,8 @@ fun SelectedItemsBottomToolbar( Modifier .matchParentSize() .background(MaterialTheme.colors.background) + .padding(horizontal = 2.dp) + .height(AppBarHeight * fontSizeSqrtMultiplier) .pointerInput(Unit) { detectGesture { true @@ -103,6 +106,7 @@ fun SelectedItemsBottomToolbar( ) } } + Divider(Modifier.align(Alignment.TopStart)) } LaunchedEffect(chatInfo, chatItems, selectedChatItems.value) { recheckItems(chatInfo, chatItems, selectedChatItems, deleteEnabled, deleteForEveryoneEnabled, canModerate, moderateEnabled, forwardEnabled, deleteCountProhibited, forwardCountProhibited) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt index 76c4fc4a62..d912f8e030 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.* import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.* @@ -60,7 +61,8 @@ fun SendMsgView( ) { val showCustomDisappearingMessageDialog = remember { mutableStateOf(false) } - Box(Modifier.padding(vertical = if (appPlatform.isAndroid) 8.dp else 6.dp)) { + val padding = if (appPlatform.isAndroid) PaddingValues(vertical = 8.dp) else PaddingValues(top = 3.dp, bottom = 4.dp) + Box(Modifier.padding(padding)) { val cs = composeState.value var progressByTimeout by rememberSaveable { mutableStateOf(false) } LaunchedEffect(composeState.value.inProgress) { @@ -146,7 +148,7 @@ fun SendMsgView( && (cs.preview !is ComposePreview.VoicePreview || !stopRecOnNextClick.value) && cs.contextItem is ComposeContextItem.NoContextItem ) { - Spacer(Modifier.width(10.dp)) + Spacer(Modifier.width(12.dp)) StartLiveMessageButton(userCanSend) { if (composeState.value.preview is ComposePreview.NoPreview) { startLiveMessage(scope, sendLiveMessage, updateLiveMessage, sendButtonSize, sendButtonAlpha, composeState, liveMessageAlertShown) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt index 5bd707ab66..69087ecd60 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt @@ -56,11 +56,7 @@ private fun VerifyCodeLayout( connectionVerified: Boolean, verifyCode: (String?, cb: (Boolean) -> Unit) -> Unit, ) { - ColumnWithScrollBar( - Modifier - .fillMaxSize() - .padding(horizontal = DEFAULT_PADDING) - ) { + ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_PADDING)) { AppBarTitle(stringResource(MR.strings.security_code), withPadding = false) val splitCode = splitToParts(connectionCode, 24) Row(Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING_HALF), horizontalArrangement = Arrangement.Center) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt index 18a3a0d14d..b351f56c29 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt @@ -130,10 +130,7 @@ fun AddGroupMembersLayout( } } - ColumnWithScrollBar( - Modifier - .fillMaxWidth(), - ) { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.button_add_members)) profileText() Spacer(Modifier.size(DEFAULT_PADDING)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index a14d227074..76f2866950 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatModel.withChats import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* @@ -283,9 +284,14 @@ fun ModalData.GroupChatInfoLayout( if (s.isEmpty()) members else members.filter { m -> m.anyNameContains(s) } } } + Box { + val oneHandUI = remember { appPrefs.oneHandUI.state } LazyColumnWithScrollBar( - Modifier - .fillMaxWidth(), + contentPadding = if (oneHandUI.value) { + PaddingValues(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + DEFAULT_PADDING + 5.dp, bottom = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()) + } else { + PaddingValues(top = topPaddingToContent()) + }, state = listState ) { item { @@ -397,6 +403,11 @@ fun ModalData.GroupChatInfoLayout( } } SectionBottomSpacer() + Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) + } + } + if (!oneHandUI.value) { + NavigationBarBackground(oneHandUI.value, oneHandUI.value) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt index 5291520566..956ee575de 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt @@ -119,9 +119,7 @@ fun GroupLinkLayout( ) } - ColumnWithScrollBar( - Modifier, - ) { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.group_link)) Text( stringResource(MR.strings.you_can_share_group_link_anybody_will_be_able_to_connect), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt index 9981d70a52..a03cff2bb0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt @@ -313,10 +313,7 @@ fun GroupMemberInfoLayout( } } - ColumnWithScrollBar( - Modifier - .fillMaxWidth(), - ) { + ColumnWithScrollBar { Row( Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt index b7d66dd4f6..128dfe2d97 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt @@ -82,9 +82,7 @@ private fun GroupPreferencesLayout( reset: () -> Unit, savePrefs: () -> Unit, ) { - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - ) { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.group_preferences)) val timedMessages = remember(preferences) { mutableStateOf(preferences.timedMessages.enable) } val onTTLUpdated = { ttl: Int? -> diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt index 6375ef1a20..e81722f3f0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt @@ -82,10 +82,9 @@ fun GroupProfileLayout( }, close) } } - ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { ModalBottomSheetLayout( scrimColor = Color.Black.copy(alpha = 0.12F), - modifier = Modifier.navigationBarsWithImePadding(), + modifier = Modifier.imePadding(), sheetContent = { GetImageBottomSheet( chosenImage, @@ -98,9 +97,7 @@ fun GroupProfileLayout( sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp) ) { ModalView(close = closeWithAlert) { - ColumnWithScrollBar( - Modifier - ) { + ColumnWithScrollBar { Column( Modifier.fillMaxWidth() .padding(horizontal = DEFAULT_PADDING) @@ -177,7 +174,6 @@ fun GroupProfileLayout( } } } - } } private fun canUpdateProfile(displayName: String, groupProfile: GroupProfile): Boolean = diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt index b6312e4d82..7f0af360e7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt @@ -95,9 +95,7 @@ private fun GroupWelcomeLayout( linkMode: SimplexLinkMode, save: () -> Unit, ) { - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - ) { + ColumnWithScrollBar { val editMode = remember { mutableStateOf(true) } AppBarTitle(stringResource(MR.strings.group_welcome_title)) val wt = rememberSaveable { welcomeText } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt index f346402957..f0480a5c50 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt @@ -10,6 +10,7 @@ import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.layout.* +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.UriHandler import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource @@ -320,6 +321,8 @@ fun CIMarkdownText( const val CHAT_IMAGE_LAYOUT_ID = "chatImage" const val CHAT_BUBBLE_LAYOUT_ID = "chatBubble" +const val CHAT_COMPOSE_LAYOUT_ID = "chatCompose" +const val CONSOLE_COMPOSE_LAYOUT_ID = "consoleCompose" /** * Equal to [androidx.compose.ui.unit.Constraints.MaxFocusMask], which is 0x3FFFF - 1 * Other values make a crash `java.lang.IllegalArgumentException: Can't represent a width of 123456 and height of 9909 in Constraints` @@ -398,6 +401,70 @@ fun DependentLayout( } } } + +// The purpose of this layout is to make measuring of bottom compose view and adapt top lazy column to its size in the same frame (not on the next frame as you would expect). +// So, steps are: +// - measuring the layout: measured height of compose view before this step is 0, it's added to content padding of lazy column (so it's == 0) +// - measured the layout: measured height of compose view now is correct, but it's not yet applied to lazy column content padding (so it's == 0) and lazy column is placed higher than compose view in view with respect to compose view's height +// - on next frame measured height is correct and content padding is the same, lazy column placed to occupy all parent view's size +// - every added/removed line in compose view goes through the same process. +@Composable +fun AdaptingBottomPaddingLayout( + modifier: Modifier = Modifier, + mainLayoutId: String, + expectedHeight: MutableState, + content: @Composable () -> Unit +) { + val expected = with(LocalDensity.current) { expectedHeight.value.roundToPx() } + Layout( + content = content, + modifier = modifier + ) { measureable, constraints -> + require(measureable.size <= 2) { "Should be exactly one or two elements in this layout, you have ${measureable.size}" } + val mainPlaceable = measureable.firstOrNull { it.layoutId == mainLayoutId }!!.measure(constraints) + val placeables: List = measureable.map { + if (it.layoutId == mainLayoutId) + mainPlaceable + else + it.measure(constraints.copy(maxHeight = if (expected != mainPlaceable.measuredHeight) constraints.maxHeight - mainPlaceable.measuredHeight + expected else constraints.maxHeight)) } + expectedHeight.value = mainPlaceable.measuredHeight.toDp() + layout(constraints.maxWidth, constraints.maxHeight) { + var y = 0 + placeables.forEach { + if (it !== mainPlaceable) { + it.place(0, y) + y += it.measuredHeight + } else { + it.place(0, constraints.maxHeight - mainPlaceable.measuredHeight) + y += it.measuredHeight + } + } + } + } +} + +@Composable +fun CenteredRowLayout( + modifier: Modifier = Modifier, + content: @Composable () -> Unit +) { + Layout( + content = content, + modifier = modifier + ) { measureable, constraints -> + require(measureable.size == 3) { "Should be exactly three elements in this layout, you have ${measureable.size}" } + val first = measureable[0].measure(constraints.copy(minWidth = 0, minHeight = 0)) + val third = measureable[2].measure(constraints.copy(minWidth = first.measuredWidth, minHeight = 0)) + val second = measureable[1].measure(constraints.copy(minWidth = 0, minHeight = 0, maxWidth = (constraints.maxWidth - first.measuredWidth - third.measuredWidth).coerceAtLeast(0))) + // Limit width for every other element to width of important element and height for a sum of all elements. + layout(constraints.maxWidth, constraints.maxHeight) { + first.place(0, ((constraints.maxHeight - first.measuredHeight) / 2).coerceAtLeast(0)) + second.place((constraints.maxWidth - second.measuredWidth) / 2, ((constraints.maxHeight - second.measuredHeight) / 2).coerceAtLeast(0)) + third.place(constraints.maxWidth - third.measuredWidth, ((constraints.maxHeight - third.measuredHeight) / 2).coerceAtLeast(0)) + } + } +} + /* class EditedProvider: PreviewParameterProvider { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.kt index ab3918549d..70d6fa4aa8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.kt @@ -12,7 +12,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.* import androidx.compose.ui.input.pointer.* import androidx.compose.ui.layout.onGloballyPositioned -import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.CryptoFile import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.CurrentColors @@ -58,9 +57,17 @@ fun ImageFullScreenView(imageProvider: () -> ImageGalleryProvider, close: () -> val playersToRelease = rememberSaveable { mutableSetOf() } DisposableEffectOnGone( always = { - platform.androidSetStatusAndNavBarColors(CurrentColors.value.colors.isLight, Color.Black, false, false) + platform.androidSetStatusAndNavigationBarAppearance(false, false, blackNavBar = true) + chatModel.fullscreenGalleryVisible.value = true }, - whenGone = { playersToRelease.forEach { VideoPlayerHolder.release(it, true, true) } } + whenDispose = { + val c = CurrentColors.value.colors + platform.androidSetStatusAndNavigationBarAppearance(c.isLight, c.isLight) + chatModel.fullscreenGalleryVisible.value = false + }, + whenGone = { + playersToRelease.forEach { VideoPlayerHolder.release(it, true, true) } + } ) @Composable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 50949b0b16..586bca87d0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -10,8 +10,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier +import androidx.compose.ui.* import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.* import androidx.compose.ui.graphics.* @@ -34,6 +33,7 @@ import chat.simplex.common.views.onboarding.shouldShowWhatsNew import chat.simplex.common.platform.* import chat.simplex.common.views.call.Call import chat.simplex.common.views.chat.item.CIFileViewScope +import chat.simplex.common.views.chat.topPaddingToContent import chat.simplex.common.views.newchat.* import chat.simplex.common.views.usersettings.* import chat.simplex.res.MR @@ -41,7 +41,6 @@ import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.serialization.json.Json -import java.net.URI import kotlin.time.Duration.Companion.seconds private fun showNewChatSheet(oneHandUI: State) { @@ -55,7 +54,7 @@ private fun showNewChatSheet(oneHandUI: State) { chatModel.newChatSheetVisible.value = false close() } - ModalView(close, closeOnTop = !oneHandUI.value) { + ModalView(close, showAppBar = !oneHandUI.value) { if (appPlatform.isAndroid) { BackHandler { close() @@ -122,11 +121,7 @@ fun ToggleChatListCard() { SharedPreferenceToggle( appPrefs.oneHandUI, - enabled = true, - onChange = { - val c = CurrentColors.value.colors - platform.androidSetStatusAndNavBarColors(c.isLight, c.background, !appPrefs.oneHandUI.get(), appPrefs.oneHandUI.get()) - } + enabled = true ) } } @@ -154,74 +149,36 @@ fun ChatListView(chatModel: ChatModel, userPickerState: MutableStateFlow, listState: LazyListState) { + if (!chatModel.desktopNoUserNoRemote) { + ChatList(searchText = searchText, listState) + } + if (chatModel.chats.value.isEmpty() && !chatModel.switchingUsersAndHosts.value && !chatModel.desktopNoUserNoRemote) { + Text( + stringResource( + if (chatModel.chatRunning.value == null) MR.strings.loading_chats else MR.strings.you_have_no_chats + ), Modifier.align(Alignment.Center), color = MaterialTheme.colors.secondary + ) + } +} + +@Composable +private fun BoxScope.NewChatSheetFloatingButton(oneHandUI: State, stopped: Boolean) { + FloatingActionButton( + onClick = { + if (!stopped) { + showNewChatSheet(oneHandUI) + } + }, + Modifier + .navigationBarsPadding() + .padding(end = DEFAULT_PADDING, bottom = DEFAULT_PADDING) + .align(Alignment.BottomEnd) + .size(AppBarHeight * fontSizeSqrtMultiplier), + elevation = FloatingActionButtonDefaults.elevation( + defaultElevation = 0.dp, + pressedElevation = 0.dp, + hoveredElevation = 0.dp, + focusedElevation = 0.dp, + ), + backgroundColor = if (!stopped) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, + contentColor = Color.White + ) { + Icon(painterResource(MR.images.ic_edit_filled), stringResource(MR.strings.add_contact_or_create_group), Modifier.size(22.dp * fontSizeSqrtMultiplier)) + } +} + @Composable private fun ConnectButton(text: String, onClick: () -> Unit) { Button( @@ -256,7 +253,7 @@ private fun ConnectButton(text: String, onClick: () -> Unit) { } @Composable -private fun ChatListToolbar(userPickerState: MutableStateFlow, stopped: Boolean, setPerformLA: (Boolean) -> Unit) { +private fun ChatListToolbar(userPickerState: MutableStateFlow, listState: LazyListState, stopped: Boolean, setPerformLA: (Boolean) -> Unit) { val serversSummary: MutableState = remember { mutableStateOf(null) } val barButtons = arrayListOf<@Composable RowScope.() -> Unit>() val updatingProgress = remember { chatModel.updatingProgress }.value @@ -265,6 +262,18 @@ private fun ChatListToolbar(userPickerState: MutableStateFlow if (oneHandUI.value) { val sp16 = with(LocalDensity.current) { 16.sp.toDp() } + if (appPlatform.isDesktop && oneHandUI.value) { + val call = remember { chatModel.activeCall } + if (call.value != null) { + barButtons.add { + val c = call.value + if (c != null) { + ActiveCallInteractiveArea(c) + Spacer(Modifier.width(5.dp)) + } + } + } + } if (!stopped) { barButtons.add { IconButton( @@ -323,7 +332,9 @@ private fun ChatListToolbar(userPickerState: MutableStateFlow } } val clipboard = LocalClipboardManager.current - DefaultTopAppBar( + val scope = rememberCoroutineScope() + val canScrollToZero = remember { derivedStateOf { listState.firstVisibleItemIndex != 0 || listState.firstVisibleItemScrollOffset != 0 } } + DefaultAppBar( navigationButton = { if (chatModel.users.isEmpty() && !chatModel.desktopNoUserNoRemote) { NavigationButtonMenu { @@ -351,15 +362,14 @@ private fun ChatListToolbar(userPickerState: MutableStateFlow SubscriptionStatusIndicator( click = { ModalManager.start.closeModals() + val summary = serversSummary.value ModalManager.start.showModalCloseable( endButtons = { - val summary = serversSummary.value if (summary != null) { ShareButton { val json = Json { prettyPrint = true } - val text = json.encodeToString(PresentedServersSummary.serializer(), summary) clipboard.shareText(text) } @@ -370,10 +380,10 @@ private fun ChatListToolbar(userPickerState: MutableStateFlow ) } }, - onTitleClick = null, - showSearch = false, + onTitleClick = if (canScrollToZero.value) { { scrollToBottom(scope, listState) } } else null, + onTop = !oneHandUI.value, onSearchValueChanged = {}, - buttons = barButtons + buttons = { barButtons.forEach { it() } } ) } @@ -491,74 +501,78 @@ fun connectIfOpenedViaUri(rhId: Long?, uri: String, chatModel: ChatModel) { @Composable private fun ChatListSearchBar(listState: LazyListState, searchText: MutableState, searchShowingSimplexLink: MutableState, searchChatFilteredBySimplexLink: MutableState) { - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { - val focusRequester = remember { FocusRequester() } - var focused by remember { mutableStateOf(false) } - Icon( - painterResource(MR.images.ic_search), - contentDescription = null, - Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING_HALF).size(22.dp * fontSizeSqrtMultiplier), - tint = MaterialTheme.colors.secondary - ) - SearchTextField( - Modifier.weight(1f).onFocusChanged { focused = it.hasFocus }.focusRequester(focusRequester), - placeholder = stringResource(MR.strings.search_or_paste_simplex_link), - alwaysVisible = true, - searchText = searchText, - enabled = !remember { searchShowingSimplexLink }.value, - trailingContent = null, - ) { - searchText.value = searchText.value.copy(it) - } - val hasText = remember { derivedStateOf { searchText.value.text.isNotEmpty() } } - if (hasText.value) { - val hideSearchOnBack: () -> Unit = { searchText.value = TextFieldValue() } - BackHandler(onBack = hideSearchOnBack) - KeyChangeEffect(chatModel.currentRemoteHost.value) { - hideSearchOnBack() + Box { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { + val focusRequester = remember { FocusRequester() } + var focused by remember { mutableStateOf(false) } + Icon( + painterResource(MR.images.ic_search), + contentDescription = null, + Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING_HALF).size(22.dp * fontSizeSqrtMultiplier), + tint = MaterialTheme.colors.secondary + ) + SearchTextField( + Modifier.weight(1f).onFocusChanged { focused = it.hasFocus }.focusRequester(focusRequester), + placeholder = stringResource(MR.strings.search_or_paste_simplex_link), + alwaysVisible = true, + searchText = searchText, + enabled = !remember { searchShowingSimplexLink }.value, + trailingContent = null, + ) { + searchText.value = searchText.value.copy(it) } - } else { - val padding = if (appPlatform.isDesktop) 0.dp else 7.dp - if (chatModel.chats.value.isNotEmpty()) { - ToggleFilterEnabledButton() - } - Spacer(Modifier.width(padding)) - } - val focusManager = LocalFocusManager.current - val keyboardState = getKeyboardState() - LaunchedEffect(keyboardState.value) { - if (keyboardState.value == KeyboardState.Closed && focused) { - focusManager.clearFocus() - } - } - val view = LocalMultiplatformView() - LaunchedEffect(Unit) { - snapshotFlow { searchText.value.text } - .distinctUntilChanged() - .collect { - val link = strHasSingleSimplexLink(it.trim()) - if (link != null) { - // if SimpleX link is pasted, show connection dialogue - hideKeyboard(view) - if (link.format is Format.SimplexLink) { - val linkText = link.simplexLinkText(link.format.linkType, link.format.smpHosts) - searchText.value = searchText.value.copy(linkText, selection = TextRange.Zero) - } - searchShowingSimplexLink.value = true - searchChatFilteredBySimplexLink.value = null - connect(link.text, searchChatFilteredBySimplexLink) { searchText.value = TextFieldValue() } - } else if (!searchShowingSimplexLink.value || it.isEmpty()) { - if (it.isNotEmpty()) { - // if some other text is pasted, enter search mode - focusRequester.requestFocus() - } else if (listState.layoutInfo.totalItemsCount > 0) { - listState.scrollToItem(0) - } - searchShowingSimplexLink.value = false - searchChatFilteredBySimplexLink.value = null - } + val hasText = remember { derivedStateOf { searchText.value.text.isNotEmpty() } } + if (hasText.value) { + val hideSearchOnBack: () -> Unit = { searchText.value = TextFieldValue() } + BackHandler(onBack = hideSearchOnBack) + KeyChangeEffect(chatModel.currentRemoteHost.value) { + hideSearchOnBack() } + } else { + val padding = if (appPlatform.isDesktop) 0.dp else 7.dp + if (chatModel.chats.value.isNotEmpty()) { + ToggleFilterEnabledButton() + } + Spacer(Modifier.width(padding)) + } + val focusManager = LocalFocusManager.current + val keyboardState = getKeyboardState() + LaunchedEffect(keyboardState.value) { + if (keyboardState.value == KeyboardState.Closed && focused) { + focusManager.clearFocus() + } + } + val view = LocalMultiplatformView() + LaunchedEffect(Unit) { + snapshotFlow { searchText.value.text } + .distinctUntilChanged() + .collect { + val link = strHasSingleSimplexLink(it.trim()) + if (link != null) { + // if SimpleX link is pasted, show connection dialogue + hideKeyboard(view) + if (link.format is Format.SimplexLink) { + val linkText = link.simplexLinkText(link.format.linkType, link.format.smpHosts) + searchText.value = searchText.value.copy(linkText, selection = TextRange.Zero) + } + searchShowingSimplexLink.value = true + searchChatFilteredBySimplexLink.value = null + connect(link.text, searchChatFilteredBySimplexLink) { searchText.value = TextFieldValue() } + } else if (!searchShowingSimplexLink.value || it.isEmpty()) { + if (it.isNotEmpty()) { + // if some other text is pasted, enter search mode + focusRequester.requestFocus() + } else if (listState.layoutInfo.totalItemsCount > 0) { + listState.scrollToItem(0) + } + searchShowingSimplexLink.value = false + searchChatFilteredBySimplexLink.value = null + } + } + } } + val oneHandUI = remember { appPrefs.oneHandUI.state } + Divider(Modifier.align(if (oneHandUI.value) Alignment.TopStart else Alignment.BottomStart)) } } @@ -590,8 +604,37 @@ enum class ScrollDirection { } @Composable -private fun ChatList(chatModel: ChatModel, searchText: MutableState) { - val listState = rememberLazyListState(lazyListState.first, lazyListState.second) +fun BoxScope.StatusBarBackground() { + if (appPlatform.isAndroid) { + val finalColor = MaterialTheme.colors.background.copy(0.88f) + Box(Modifier.fillMaxWidth().windowInsetsTopHeight(WindowInsets.statusBars).background(finalColor)) + } +} + +@Composable +fun BoxScope.NavigationBarBackground(appBarOnBottom: Boolean = false, mixedColor: Boolean, noAlpha: Boolean = false) { + if (appPlatform.isAndroid) { + val barPadding = WindowInsets.navigationBars.asPaddingValues() + val paddingBottom = barPadding.calculateBottomPadding() + val color = if (mixedColor) MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.97f) else MaterialTheme.colors.background + val finalColor = color.copy(if (noAlpha) 1f else if (appBarOnBottom) remember { appPrefs.inAppBarsAlpha.state }.value else 0.6f) + Box(Modifier.align(Alignment.BottomStart).height(paddingBottom).fillMaxWidth().background(finalColor)) + } +} + +@Composable +fun BoxScope.NavigationBarBackground(modifier: Modifier, color: Color = MaterialTheme.colors.background) { + val keyboardState = getKeyboardState() + if (appPlatform.isAndroid && keyboardState.value == KeyboardState.Closed) { + val barPadding = WindowInsets.navigationBars.asPaddingValues() + val paddingBottom = barPadding.calculateBottomPadding() + val finalColor = color.copy(0.6f) + Box(modifier.align(Alignment.BottomStart).height(paddingBottom).fillMaxWidth().background(finalColor)) + } +} + +@Composable +private fun BoxScope.ChatList(searchText: MutableState, listState: LazyListState) { var scrollDirection by remember { mutableStateOf(ScrollDirection.Idle) } var previousIndex by remember { mutableStateOf(0) } var previousScrollOffset by remember { mutableStateOf(0) } @@ -628,40 +671,45 @@ private fun ChatList(chatModel: ChatModel, searchText: MutableState(null) } val chats = filteredChats(showUnreadAndFavorites, searchShowingSimplexLink, searchChatFilteredBySimplexLink, searchText.value.text, allChats.value.toList()) + val topPaddingToContent = topPaddingToContent() + val blankSpaceSize = if (oneHandUI.value) WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + AppBarHeight * fontSizeSqrtMultiplier else topPaddingToContent LazyColumnWithScrollBar( - Modifier.fillMaxSize(), + if (!oneHandUI.value) Modifier.imePadding() else Modifier, listState, reverseLayout = oneHandUI.value ) { + item { Spacer(Modifier.height(blankSpaceSize)) } stickyHeader { Column( Modifier + .zIndex(1f) .offset { - val y = if (searchText.value.text.isEmpty()) { - val offsetMultiplier = if (oneHandUI.value) 1 else -1 - if ( - (oneHandUI.value && scrollDirection == ScrollDirection.Up) || - (appPlatform.isAndroid && keyboardState == KeyboardState.Opened) - ) { - 0 - } else if (listState.firstVisibleItemIndex == 0) offsetMultiplier * listState.firstVisibleItemScrollOffset else offsetMultiplier * 1000 + val offsetMultiplier = if (oneHandUI.value) 1 else -1 + val y = if (searchText.value.text.isNotEmpty() || (appPlatform.isAndroid && keyboardState == KeyboardState.Opened) || scrollDirection == ScrollDirection.Up) { + if (listState.firstVisibleItemIndex == 0) -offsetMultiplier * listState.firstVisibleItemScrollOffset + else -offsetMultiplier * blankSpaceSize.roundToPx() } else { - 0 + when (listState.firstVisibleItemIndex) { + 0 -> 0 + 1 -> offsetMultiplier * listState.firstVisibleItemScrollOffset + else -> offsetMultiplier * 1000 + } } IntOffset(0, y) } - .background(MaterialTheme.colors.background), + .background(MaterialTheme.colors.background) ) { if (oneHandUI.value) { - Divider() - } - ChatListSearchBar(listState, searchText, searchShowingSimplexLink, searchChatFilteredBySimplexLink) - if (!oneHandUI.value) { - Divider() + Column(Modifier.consumeWindowInsets(WindowInsets.navigationBars).consumeWindowInsets(PaddingValues(bottom = AppBarHeight))) { + ChatListSearchBar(listState, searchText, searchShowingSimplexLink, searchChatFilteredBySimplexLink) + Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime)) + } + } else { + ChatListSearchBar(listState, searchText, searchShowingSimplexLink, searchChatFilteredBySimplexLink) } } } - if (appPlatform.isAndroid && !oneHandUICardShown.value && chats.count() > 1) { + if (!oneHandUICardShown.value && chats.size > 1) { item { ToggleChatListCard() } @@ -672,17 +720,30 @@ private fun ChatList(chatModel: ChatModel, searchText: MutableState= 3) appPrefs.oneHandUICardShown.set(true) + } + } } fun filteredChats( @@ -727,3 +788,7 @@ private fun filtered(chat: Chat): Boolean = (chat.chatInfo.chatSettings?.favorite ?: false) || chat.chatStats.unreadChat || (chat.chatInfo.ntfsEnabled && chat.chatStats.unreadCount > 0) + +fun scrollToBottom(scope: CoroutineScope, listState: LazyListState) { + scope.launch { try { listState.animateScrollToItem(0) } catch (e: Exception) { Log.e(TAG, e.stackTraceToString()) } } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt index 6219252b54..4e3ee2340c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt @@ -620,9 +620,7 @@ fun ModalData.SMPServerSummaryView( ModalView( close = close ) { - ColumnWithScrollBar( - Modifier.fillMaxSize(), - ) { + ColumnWithScrollBar { val bottomPadding = DEFAULT_PADDING AppBarTitle( stringResource(MR.strings.smp_server), @@ -645,9 +643,7 @@ fun ModalData.DetailedXFTPStatsView( ModalView( close = close ) { - ColumnWithScrollBar( - Modifier.fillMaxSize(), - ) { + ColumnWithScrollBar { Box(contentAlignment = Alignment.Center) { val bottomPadding = DEFAULT_PADDING AppBarTitle( @@ -671,9 +667,7 @@ fun ModalData.DetailedSMPStatsView( ModalView( close = close ) { - ColumnWithScrollBar( - Modifier.fillMaxSize(), - ) { + ColumnWithScrollBar { Box(contentAlignment = Alignment.Center) { val bottomPadding = DEFAULT_PADDING AppBarTitle( @@ -697,9 +691,7 @@ fun ModalData.XFTPServerSummaryView( ModalView( close = close ) { - ColumnWithScrollBar( - Modifier.fillMaxSize(), - ) { + ColumnWithScrollBar { Box(contentAlignment = Alignment.Center) { val bottomPadding = DEFAULT_PADDING AppBarTitle( @@ -715,9 +707,7 @@ fun ModalData.XFTPServerSummaryView( @Composable fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableState) { - ColumnWithScrollBar( - Modifier.fillMaxSize(), - ) { + ColumnWithScrollBar { var showUserSelection by remember { mutableStateOf(false) } val selectedUserCategory = remember { stateGetOrPut("selectedUserCategory") { PresentedUserCategory.ALL_USERS } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt index 769a0b83f6..9ca2c1e2cd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt @@ -11,10 +11,13 @@ import androidx.compose.ui.graphics.Color import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.views.helpers.* import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.themedBackground +import chat.simplex.common.views.chat.topPaddingToContent import chat.simplex.common.views.newchat.ActiveProfilePicker import chat.simplex.res.MR @@ -22,26 +25,7 @@ import chat.simplex.res.MR fun ShareListView(chatModel: ChatModel, stopped: Boolean) { var searchInList by rememberSaveable { mutableStateOf("") } val oneHandUI = remember { appPrefs.oneHandUI.state } - - Scaffold( - contentColor = LocalContentColor.current, - topBar = { - if (!oneHandUI.value) { - Column { - ShareListToolbar(chatModel, stopped) { searchInList = it.trim() } - Divider() - } - } - }, - bottomBar = { - if (oneHandUI.value) { - Column { - Divider() - ShareListToolbar(chatModel, stopped) { searchInList = it.trim() } - } - } - } - ) { + Box(Modifier.fillMaxSize().themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer)) { val sharedContent = chatModel.sharedContent.value var isMediaOrFileAttachment = false var isVoice = false @@ -69,22 +53,24 @@ fun ShareListView(chatModel: ChatModel, stopped: Boolean) { } null -> {} } - Box(Modifier.padding(it)) { - Column( - modifier = Modifier.fillMaxSize() - ) { - if (chatModel.chats.value.isNotEmpty()) { - ShareList( - chatModel, - search = searchInList, - isMediaOrFileAttachment = isMediaOrFileAttachment, - isVoice = isVoice, - hasSimplexLink = hasSimplexLink, - ) - } else { - EmptyList() - } - } + if (chatModel.chats.value.isNotEmpty()) { + ShareList( + chatModel, + search = searchInList, + isMediaOrFileAttachment = isMediaOrFileAttachment, + isVoice = isVoice, + hasSimplexLink = hasSimplexLink, + ) + } else { + EmptyList() + } + if (oneHandUI.value) { + StatusBarBackground() + } else { + NavigationBarBackground(oneHandUI.value, true) + } + Box(Modifier.align(if (oneHandUI.value) Alignment.BottomStart else Alignment.TopStart)) { + ShareListToolbar(chatModel, stopped) { searchInList = it.trim() } } } } @@ -108,7 +94,6 @@ private fun ShareListToolbar(chatModel: ChatModel, stopped: Boolean, onSearchVal if (showSearch) { BackHandler(onBack = hideSearchOnBack) } - val barButtons = arrayListOf<@Composable RowScope.() -> Unit>() val users by remember { derivedStateOf { chatModel.users.filter { u -> u.user.activeUser || !u.user.hidden } } } val navButton: @Composable RowScope.() -> Unit = { when { @@ -118,13 +103,13 @@ private fun ShareListToolbar(chatModel: ChatModel, stopped: Boolean, onSearchVal .filter { u -> !u.user.activeUser && !u.user.hidden } .all { u -> u.unreadCount == 0 } UserProfileButton(chatModel.currentUser.value?.profile?.image, allRead) { - ModalManager.start.showCustomModal { close -> + ModalManager.start.showCustomModal(keyboardCoversBar = false) { close -> val search = rememberSaveable { mutableStateOf("") } ModalView( { close() }, - endButtons = { - SearchTextField(Modifier.fillMaxWidth(), placeholder = stringResource(MR.strings.search_verb), alwaysVisible = true) { search.value = it } - }, + showSearch = true, + searchAlwaysVisible = true, + onSearchValueChanged = { search.value = it }, content = { ActiveProfilePicker( search = search, @@ -148,31 +133,8 @@ private fun ShareListToolbar(chatModel: ChatModel, stopped: Boolean, onSearchVal }) } } - if (chatModel.chats.value.size >= 8) { - barButtons.add { - IconButton({ showSearch = true }) { - Icon(painterResource(MR.images.ic_search_500), stringResource(MR.strings.search_verb), tint = MaterialTheme.colors.primary) - } - } - } - if (stopped) { - barButtons.add { - IconButton(onClick = { - AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.chat_is_stopped_indication), - generalGetString(MR.strings.you_can_start_chat_via_setting_or_by_restarting_the_app) - ) - }) { - Icon( - painterResource(MR.images.ic_report_filled), - generalGetString(MR.strings.chat_is_stopped_indication), - tint = Color.Red, - ) - } - } - } - DefaultTopAppBar( + DefaultAppBar( navigationButton = navButton, title = { Row(verticalAlignment = Alignment.CenterVertically) { @@ -191,8 +153,29 @@ private fun ShareListToolbar(chatModel: ChatModel, stopped: Boolean, onSearchVal }, onTitleClick = null, showSearch = showSearch, + onTop = !remember { appPrefs.oneHandUI.state }.value, onSearchValueChanged = onSearchValueChanged, - buttons = barButtons + buttons = { + if (chatModel.chats.value.size >= 8) { + IconButton({ showSearch = true }) { + Icon(painterResource(MR.images.ic_search_500), stringResource(MR.strings.search_verb), tint = MaterialTheme.colors.primary) + } + } + if (stopped) { + IconButton(onClick = { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.chat_is_stopped_indication), + generalGetString(MR.strings.you_can_start_chat_via_setting_or_by_restarting_the_app) + ) + }) { + Icon( + painterResource(MR.images.ic_report_filled), + generalGetString(MR.strings.chat_is_stopped_indication), + tint = Color.Red, + ) + } + } + } ) } @@ -211,8 +194,13 @@ private fun ShareList( filteredChats(false, mutableStateOf(false), mutableStateOf(null), search, sorted) } } + val topPaddingToContent = topPaddingToContent() LazyColumnWithScrollBar( - modifier = Modifier.fillMaxSize(), + modifier = Modifier.then(if (oneHandUI.value) Modifier.consumeWindowInsets(WindowInsets.navigationBars.only(WindowInsetsSides.Vertical)) else Modifier).imePadding(), + contentPadding = PaddingValues( + top = if (oneHandUI.value) WindowInsets.statusBars.asPaddingValues().calculateTopPadding() else topPaddingToContent, + bottom = if (oneHandUI.value) WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + AppBarHeight * fontSizeSqrtMultiplier else 0.dp + ), reverseLayout = oneHandUI.value ) { items(chats) { chat -> diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt index 4a3bce7752..2709c7760b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatController.stopRemoteHostAndReloadHosts import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.ui.theme.* @@ -137,12 +138,16 @@ fun UserPicker( } } + val oneHandUI = remember { appPrefs.oneHandUI.state } + val iconColor = MaterialTheme.colors.secondaryVariant + val background = if (appPlatform.isAndroid) MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, alpha = 1 - userPickerAlpha()) else MaterialTheme.colors.surface PlatformUserPicker( modifier = Modifier .height(IntrinsicSize.Min) .fillMaxWidth() - .then(if (newChat.isVisible()) Modifier.shadow(8.dp, clip = true) else Modifier) - .background(if (appPlatform.isAndroid) MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, alpha = 1 - userPickerAlpha()) else MaterialTheme.colors.surface) + .then(if (newChat.isVisible()) Modifier.shadow(8.dp, clip = true, ambientColor = background) else Modifier) + .padding(top = if (appPlatform.isDesktop && oneHandUI.value) 7.dp else 0.dp) + .background(background) .padding(bottom = USER_PICKER_SECTION_SPACING - DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL), pickerState = userPickerState ) { @@ -198,12 +203,13 @@ fun UserPicker( UserPickerUsersSection( users = users, onUserClicked = onUserClicked, + iconColor = iconColor, stopped = stopped ) } } else if (currentUser != null) { SectionItemView({ onUserClicked(currentUser) }, 80.dp, padding = PaddingValues(start = 16.dp, end = DEFAULT_PADDING), disabled = stopped) { - ProfilePreview(currentUser.profile, stopped = stopped) + ProfilePreview(currentUser.profile, iconColor = iconColor, stopped = stopped) } } } @@ -234,6 +240,7 @@ fun UserPicker( Column(modifier = Modifier.padding(vertical = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL)) { UserPickerUsersSection( users = inactiveUsers, + iconColor = iconColor, onUserClicked = onUserClicked, stopped = stopped ) @@ -265,13 +272,15 @@ fun UserPicker( generalGetString(MR.strings.auth_open_chat_profiles), generalGetString(MR.strings.auth_log_in_using_credential) ) { - ModalManager.start.showCustomModal { close -> + ModalManager.start.showCustomModal(keyboardCoversBar = false) { close -> val search = rememberSaveable { mutableStateOf("") } val profileHidden = rememberSaveable { mutableStateOf(false) } ModalView( { close() }, - endButtons = { - SearchTextField(Modifier.fillMaxWidth(), placeholder = stringResource(MR.strings.search_verb), alwaysVisible = true) { search.value = it } + showSearch = true, + searchAlwaysVisible = true, + onSearchValueChanged = { + search.value = it }, content = { UserProfilesView(chatModel, search, profileHidden) }) } @@ -519,6 +528,7 @@ private fun DevicePickerRow( @Composable expect fun UserPickerUsersSection( users: List, + iconColor: Color, stopped: Boolean, onUserClicked: (user: User) -> Unit, ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt index 6846d1c735..96acea5446 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt @@ -46,9 +46,7 @@ fun ChatArchiveLayout( saveArchive: () -> Unit, deleteArchiveAlert: () -> Unit ) { - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - ) { + ColumnWithScrollBar { AppBarTitle(title) SectionView(stringResource(MR.strings.chat_archive_section)) { SettingsActionItem( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt index b73a0ca0bc..654d250274 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt @@ -203,7 +203,7 @@ fun DatabaseEncryptionLayout( Layout() } } else { - ColumnWithScrollBar(Modifier.fillMaxWidth(), maxIntrinsicSize = true) { + ColumnWithScrollBar(maxIntrinsicSize = true) { Layout() } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt index 333c73e195..9264ca69af 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt @@ -77,10 +77,7 @@ fun DatabaseErrorView( Text(String.format(generalGetString(MR.strings.database_migrations), ms.joinToString(", "))) } - ColumnWithScrollBar( - Modifier.fillMaxSize(), - verticalArrangement = Arrangement.Center, - ) { + ColumnWithScrollBarNoAppBar(Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center) { val buttonEnabled = validKey(dbKey.value) && !progressIndicator.value when (val status = chatDbStatus.value) { is DBMigrationResult.ErrorNotADatabase -> diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index b287847ace..d36bd255e3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -156,9 +156,7 @@ fun DatabaseLayout( val stopped = !runChat val operationsDisabled = (!stopped || progressIndicator) && !chatModel.desktopNoUserNoRemote - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - ) { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.your_chat_database)) if (!chatModel.desktopNoUserNoRemote) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AppBarTitle.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AppBarTitle.kt new file mode 100644 index 0000000000..195ec020e5 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AppBarTitle.kt @@ -0,0 +1,71 @@ +package chat.simplex.common.views.helpers + +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.graphics.* +import androidx.compose.ui.unit.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chatlist.* +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import kotlin.math.absoluteValue + +@Composable +fun AppBarTitle(title: String, hostDevice: Pair? = null, withPadding: Boolean = true, bottomPadding: Dp = DEFAULT_PADDING * 1.5f + 8.dp) { + val handler = LocalAppBarHandler.current + val connection = handler?.connection + LaunchedEffect(title) { + handler?.title?.value = title + } + val theme = CurrentColors.collectAsState() + val titleColor = MaterialTheme.appColors.title + val brush = if (theme.value.base == DefaultTheme.SIMPLEX) + Brush.linearGradient(listOf(titleColor.darker(0.2f), titleColor.lighter(0.35f)), Offset(0f, Float.POSITIVE_INFINITY), Offset(Float.POSITIVE_INFINITY, 0f)) + else // color is not updated when changing themes if I pass null here + Brush.linearGradient(listOf(titleColor, titleColor), Offset(0f, Float.POSITIVE_INFINITY), Offset(Float.POSITIVE_INFINITY, 0f)) + Column { + Text( + title, + Modifier + .padding(start = if (withPadding) DEFAULT_PADDING else 0.dp, top = DEFAULT_PADDING_HALF, end = if (withPadding) DEFAULT_PADDING else 0.dp,) + .graphicsLayer { + alpha = bottomTitleAlpha(connection) + }, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.h1.copy(brush = brush), + color = MaterialTheme.colors.primaryVariant, + textAlign = TextAlign.Start + ) + if (hostDevice != null) { + Box(Modifier.padding(start = if (withPadding) DEFAULT_PADDING else 0.dp, end = if (withPadding) DEFAULT_PADDING else 0.dp).graphicsLayer { + alpha = bottomTitleAlpha(connection) + }) { + HostDeviceTitle(hostDevice) + } + } + Spacer(Modifier.height(bottomPadding)) + } +} + +private fun bottomTitleAlpha(connection: CollapsingAppBarNestedScrollConnection?) = + if ((connection?.appBarOffset ?: 0f).absoluteValue < AppBarHandler.appBarMaxHeightPx / 3) 1f + else ((AppBarHandler.appBarMaxHeightPx) + (connection?.appBarOffset ?: 0f) / 1.5f).coerceAtLeast(0f) / AppBarHandler.appBarMaxHeightPx + +@Composable +private fun HostDeviceTitle(hostDevice: Pair, extraPadding: Boolean = false) { + Row(Modifier.fillMaxWidth().padding(top = 5.dp, bottom = if (extraPadding) DEFAULT_PADDING * 2 else DEFAULT_PADDING_HALF), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Start) { + DevicePill( + active = true, + onClick = {}, + actionButtonVisible = false, + icon = painterResource(if (hostDevice.first == null) MR.images.ic_desktop else MR.images.ic_smartphone_300), + text = hostDevice.second + ) + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/BlurModifier.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/BlurModifier.kt new file mode 100644 index 0000000000..096b6c55ac --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/BlurModifier.kt @@ -0,0 +1,139 @@ +package chat.simplex.common.views.helpers + +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.runtime.State +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.* +import androidx.compose.ui.graphics.drawscope.clipRect +import androidx.compose.ui.graphics.drawscope.translate +import androidx.compose.ui.graphics.layer.GraphicsLayer +import androidx.compose.ui.graphics.layer.drawLayer +import androidx.compose.ui.unit.* +import chat.simplex.common.platform.appPlatform +import chat.simplex.common.ui.theme.CurrentColors + +fun Modifier.blurredBackgroundModifier( + keyboardInset: WindowInsets, + handler: AppBarHandler?, + blurRadius: State, + prefAlpha: State, + keyboardCoversBar: Boolean, + onTop: Boolean, + density: Density +): Modifier { + val graphicsLayer = handler?.graphicsLayer + val backgroundGraphicsLayer = handler?.backgroundGraphicsLayer + val backgroundGraphicsLayerSize = handler?.backgroundGraphicsLayerSize + if (handler == null || graphicsLayer == null || backgroundGraphicsLayer == null || blurRadius.value == 0 || prefAlpha.value == 1f || backgroundGraphicsLayerSize === null) + return this + + return if (appPlatform.isAndroid) { + this.androidBlurredModifier(keyboardInset, blurRadius.value, keyboardCoversBar, onTop, graphicsLayer, backgroundGraphicsLayer, backgroundGraphicsLayerSize, density) + } else { + this.desktopBlurredModifier(keyboardInset, blurRadius, keyboardCoversBar, onTop, graphicsLayer, backgroundGraphicsLayer, backgroundGraphicsLayerSize, density) + } +} + +// this is more performant version than for Android but can't be used on desktop because on first frame it shows transparent view +// which is very noticeable on desktop and unnoticeable on Android +private fun Modifier.androidBlurredModifier( + keyboardInset: WindowInsets, + blurRadius: Int, + keyboardCoversBar: Boolean, + onTop: Boolean, + graphicsLayer: GraphicsLayer, + backgroundGraphicsLayer: GraphicsLayer, + backgroundGraphicsLayerSize: State, + density: Density +): Modifier = this + .graphicsLayer { + renderEffect = if (blurRadius > 0) BlurEffect(blurRadius.dp.toPx(), blurRadius.dp.toPx()) else null + clip = blurRadius > 0 + } + .graphicsLayer { + if (!onTop) { + val bgSize = when { + backgroundGraphicsLayerSize.value.height == 0 && backgroundGraphicsLayer.size.height != 0 -> backgroundGraphicsLayer.size.height + backgroundGraphicsLayerSize.value.height == 0 -> graphicsLayer.size.height + else -> backgroundGraphicsLayerSize.value.height + } + val keyboardHeightCovered = if (!keyboardCoversBar) keyboardInset.getBottom(density) else 0 + translationY = -bgSize + size.height + keyboardHeightCovered + } + } + .drawBehind { + drawRect(Color.Black) + if (onTop) { + clipRect { + if (backgroundGraphicsLayer.size != IntSize.Zero) { + drawLayer(backgroundGraphicsLayer) + } else { + drawRect(CurrentColors.value.colors.background, size = Size(graphicsLayer.size.width.toFloat(), graphicsLayer.size.height.toFloat())) + } + drawLayer(graphicsLayer) + } + } else { + if (backgroundGraphicsLayer.size != IntSize.Zero) { + drawLayer(backgroundGraphicsLayer) + } else { + drawRect(CurrentColors.value.colors.background, size = Size(graphicsLayer.size.width.toFloat(), graphicsLayer.size.height.toFloat())) + } + drawLayer(graphicsLayer) + } + } + .graphicsLayer { + if (!onTop) { + val bgSize = when { + backgroundGraphicsLayerSize.value.height == 0 && backgroundGraphicsLayer.size.height != 0 -> backgroundGraphicsLayer.size.height + backgroundGraphicsLayerSize.value.height == 0 -> graphicsLayer.size.height + else -> backgroundGraphicsLayerSize.value.height + } + val keyboardHeightCovered = if (!keyboardCoversBar) keyboardInset.getBottom(density) else 0 + translationY -= -bgSize + size.height + keyboardHeightCovered + } + } + +private fun Modifier.desktopBlurredModifier( + keyboardInset: WindowInsets, + blurRadius: State, + keyboardCoversBar: Boolean, + onTop: Boolean, + graphicsLayer: GraphicsLayer, + backgroundGraphicsLayer: GraphicsLayer, + backgroundGraphicsLayerSize: State, + density: Density +): Modifier = this + .graphicsLayer { + renderEffect = if (blurRadius.value > 0) BlurEffect(blurRadius.value.dp.toPx(), blurRadius.value.dp.toPx()) else null + clip = blurRadius.value > 0 + } + .drawBehind { + drawRect(Color.Black) + if (onTop) { + clipRect { + if (backgroundGraphicsLayer.size != IntSize.Zero) { + drawLayer(backgroundGraphicsLayer) + } else { + drawRect(CurrentColors.value.colors.background, size = Size(graphicsLayer.size.width.toFloat(), graphicsLayer.size.height.toFloat())) + } + drawLayer(graphicsLayer) + } + } else { + val bgSize = when { + backgroundGraphicsLayerSize.value.height == 0 && backgroundGraphicsLayer.size.height != 0 -> backgroundGraphicsLayer.size.height + backgroundGraphicsLayerSize.value.height == 0 -> graphicsLayer.size.height + else -> backgroundGraphicsLayerSize.value.height + } + val keyboardHeightCovered = if (!keyboardCoversBar) keyboardInset.getBottom(density) else 0 + translate(top = -bgSize + size.height + keyboardHeightCovered) { + if (backgroundGraphicsLayer.size != IntSize.Zero) { + drawLayer(backgroundGraphicsLayer) + } else { + drawRect(CurrentColors.value.colors.background, size = Size(graphicsLayer.size.width.toFloat(), graphicsLayer.size.height.toFloat())) + } + drawLayer(graphicsLayer) + } + } + } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatWallpaper.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatWallpaper.kt index 2941b748c7..c1a76d7bf8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatWallpaper.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatWallpaper.kt @@ -1,11 +1,13 @@ package chat.simplex.common.views.helpers +import androidx.compose.runtime.* import androidx.compose.ui.draw.CacheDrawScope import androidx.compose.ui.draw.DrawResult import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.* import androidx.compose.ui.graphics.drawscope.* +import androidx.compose.ui.graphics.layer.GraphicsLayer import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.* import chat.simplex.common.model.ChatController.appPrefs @@ -381,7 +383,14 @@ private fun drawToBitmap(image: ImageBitmap, imageScale: Float, tint: Color, siz return bitmap } -fun CacheDrawScope.chatViewBackground(image: ImageBitmap, imageType: WallpaperType, background: Color, tint: Color): DrawResult { +fun CacheDrawScope.chatViewBackground( + image: ImageBitmap, + imageType: WallpaperType, + background: Color, + tint: Color, + graphicsLayerSize: MutableState? = null, + backgroundGraphicsLayer: GraphicsLayer? = null +): DrawResult { val imageScale = if (imageType is WallpaperType.Preset) { (imageType.scale ?: 1f) * imageType.predefinedImageScale } else if (imageType is WallpaperType.Image && imageType.scaleType == WallpaperScaleType.REPEAT) { @@ -396,53 +405,55 @@ fun CacheDrawScope.chatViewBackground(image: ImageBitmap, imageType: WallpaperTy } return onDrawBehind { - val quality = if (appPlatform.isAndroid) FilterQuality.High else FilterQuality.Low - drawRect(background) - when (imageType) { - is WallpaperType.Preset -> drawImage(image) - is WallpaperType.Image -> when (val scaleType = imageType.scaleType ?: WallpaperScaleType.FILL) { - WallpaperScaleType.REPEAT -> drawImage(image) - WallpaperScaleType.FILL, WallpaperScaleType.FIT -> { - clipRect { - val scale = scaleType.contentScale.computeScaleFactor(Size(image.width.toFloat(), image.height.toFloat()), Size(size.width, size.height)) - val scaledWidth = (image.width * scale.scaleX).roundToInt() - val scaledHeight = (image.height * scale.scaleY).roundToInt() - // Large image will cause freeze - if (image.width > 4320 || image.height > 4320) return@clipRect + copyBackgroundToAppBar(graphicsLayerSize, backgroundGraphicsLayer) { + val quality = if (appPlatform.isAndroid) FilterQuality.High else FilterQuality.Low + drawRect(background) + when (imageType) { + is WallpaperType.Preset -> drawImage(image) + is WallpaperType.Image -> when (val scaleType = imageType.scaleType ?: WallpaperScaleType.FILL) { + WallpaperScaleType.REPEAT -> drawImage(image) + WallpaperScaleType.FILL, WallpaperScaleType.FIT -> { + clipRect { + val scale = scaleType.contentScale.computeScaleFactor(Size(image.width.toFloat(), image.height.toFloat()), Size(size.width, size.height)) + val scaledWidth = (image.width * scale.scaleX).roundToInt() + val scaledHeight = (image.height * scale.scaleY).roundToInt() + // Large image will cause freeze + if (image.width > 4320 || image.height > 4320) return@clipRect - drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality) - if (scaleType == WallpaperScaleType.FIT) { - if (scaledWidth < size.width) { - // has black lines at left and right sides - var x = (size.width - scaledWidth) / 2 - while (x > 0) { - drawImage(image, dstOffset = IntOffset(x = (x - scaledWidth).roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality) - x -= scaledWidth - } - x = size.width - (size.width - scaledWidth) / 2 - while (x < size.width) { - drawImage(image, dstOffset = IntOffset(x = x.roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality) - x += scaledWidth - } - } else { - // has black lines at top and bottom sides - var y = (size.height - scaledHeight) / 2 - while (y > 0) { - drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = (y - scaledHeight).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality) - y -= scaledHeight - } - y = size.height - (size.height - scaledHeight) / 2 - while (y < size.height) { - drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = y.roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality) - y += scaledHeight + drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality) + if (scaleType == WallpaperScaleType.FIT) { + if (scaledWidth < size.width) { + // has black lines at left and right sides + var x = (size.width - scaledWidth) / 2 + while (x > 0) { + drawImage(image, dstOffset = IntOffset(x = (x - scaledWidth).roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality) + x -= scaledWidth + } + x = size.width - (size.width - scaledWidth) / 2 + while (x < size.width) { + drawImage(image, dstOffset = IntOffset(x = x.roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality) + x += scaledWidth + } + } else { + // has black lines at top and bottom sides + var y = (size.height - scaledHeight) / 2 + while (y > 0) { + drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = (y - scaledHeight).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality) + y -= scaledHeight + } + y = size.height - (size.height - scaledHeight) / 2 + while (y < size.height) { + drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = y.roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality) + y += scaledHeight + } } } } + drawRect(tint) } - drawRect(tint) } + is WallpaperType.Empty -> {} } - is WallpaperType.Empty -> {} } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.kt index aa3c4560ea..33cf7c2263 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChooseAttachmentView.kt @@ -19,6 +19,8 @@ fun ChooseAttachmentView(attachmentOption: MutableState, hide Box( modifier = Modifier .fillMaxWidth() + .navigationBarsPadding() + .imePadding() .wrapContentHeight() .onFocusChanged { focusState -> if (!focusState.hasFocus) hide() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt deleted file mode 100644 index 104c05309c..0000000000 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt +++ /dev/null @@ -1,181 +0,0 @@ -package chat.simplex.common.views.helpers - -import androidx.compose.foundation.layout.* -import androidx.compose.material.* -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.foundation.background -import androidx.compose.ui.draw.* -import androidx.compose.ui.graphics.* -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.* -import chat.simplex.common.platform.appPlatform -import chat.simplex.common.ui.theme.* -import chat.simplex.common.views.chatlist.DevicePill -import chat.simplex.res.MR -import dev.icerock.moko.resources.compose.painterResource -import kotlin.math.absoluteValue - -@Composable -fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, tintColor: Color = if (close != null) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, arrangement: Arrangement.Vertical = Arrangement.Top, closeBarTitle: String? = null, barPaddingValues: PaddingValues = PaddingValues(horizontal = AppBarHorizontalPadding), endButtons: @Composable RowScope.() -> Unit = {}) { - var rowModifier = Modifier - .fillMaxWidth() - .height(AppBarHeight * fontSizeSqrtMultiplier) - val themeBackgroundMix = MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.97f) - if (!closeBarTitle.isNullOrEmpty()) { - rowModifier = rowModifier.background(themeBackgroundMix) - } - val handler = LocalAppBarHandler.current - val connection = LocalAppBarHandler.current?.connection - val title = remember(handler?.title?.value) { handler?.title ?: mutableStateOf("") } - - Column( - verticalArrangement = arrangement, - modifier = Modifier - .fillMaxWidth() - .heightIn(min = AppBarHeight * fontSizeSqrtMultiplier) - .drawWithCache { - val backgroundColor = if (appPlatform.isDesktop && connection != null) themeBackgroundMix.copy(alpha = topTitleAlpha(connection)) else Color.Transparent - onDrawBehind { - if (appPlatform.isDesktop) { - drawRect(backgroundColor) - } - } - } - ) { - Row( - modifier = Modifier.padding(barPaddingValues), - content = { - Row( - rowModifier, - verticalAlignment = Alignment.CenterVertically - ) { - if (showClose) { - NavigationButtonBack(tintColor = tintColor, onButtonClicked = close) - } else { - Spacer(Modifier) - } - if (!closeBarTitle.isNullOrEmpty()) { - Row( - Modifier.weight(1f), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - closeBarTitle, - fontWeight = FontWeight.SemiBold, - maxLines = 1 - ) - } - } else if (title.value.isNotEmpty() && connection != null) { - Row( - Modifier - .padding(start = if (showClose) 0.dp else DEFAULT_PADDING_HALF) - .weight(1f) // hides the title if something wants full width (eg, search field in chat profiles screen) - .graphicsLayer { - alpha = topTitleAlpha((connection)) - } - .padding(start = 4.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - title.value, - fontWeight = FontWeight.SemiBold, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } - } else { - Spacer(Modifier.weight(1f)) - } - Row { - endButtons() - } - } - } - ) - if (closeBarTitle.isNullOrEmpty() && title.value.isNotEmpty() && connection != null) { - Divider( - Modifier - .graphicsLayer { - alpha = topTitleAlpha(connection) - } - ) - } - } -} - -@Composable -fun AppBarTitle(title: String, hostDevice: Pair? = null, withPadding: Boolean = true, bottomPadding: Dp = DEFAULT_PADDING * 1.5f + 8.dp) { - val handler = LocalAppBarHandler.current - val connection = handler?.connection - LaunchedEffect(title) { - handler?.title?.value = title - } - val theme = CurrentColors.collectAsState() - val titleColor = MaterialTheme.appColors.title - val brush = if (theme.value.base == DefaultTheme.SIMPLEX) - Brush.linearGradient(listOf(titleColor.darker(0.2f), titleColor.lighter(0.35f)), Offset(0f, Float.POSITIVE_INFINITY), Offset(Float.POSITIVE_INFINITY, 0f)) - else // color is not updated when changing themes if I pass null here - Brush.linearGradient(listOf(titleColor, titleColor), Offset(0f, Float.POSITIVE_INFINITY), Offset(Float.POSITIVE_INFINITY, 0f)) - Column { - Text( - title, - Modifier - .padding(start = if (withPadding) DEFAULT_PADDING else 0.dp, top = DEFAULT_PADDING_HALF, end = if (withPadding) DEFAULT_PADDING else 0.dp,) - .graphicsLayer { - alpha = bottomTitleAlpha(connection) - }, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.h1.copy(brush = brush), - color = MaterialTheme.colors.primaryVariant, - textAlign = TextAlign.Start - ) - if (hostDevice != null) { - Box(Modifier.padding(start = if (withPadding) DEFAULT_PADDING else 0.dp, end = if (withPadding) DEFAULT_PADDING else 0.dp).graphicsLayer { - alpha = bottomTitleAlpha(connection) - }) { - HostDeviceTitle(hostDevice) - } - } - Spacer(Modifier.height(bottomPadding)) - } -} - -private fun topTitleAlpha(connection: CollapsingAppBarNestedScrollConnection) = - if (connection.appBarOffset.absoluteValue < AppBarHandler.appBarMaxHeightPx / 3) 0f - else ((-connection.appBarOffset * 1.5f) / (AppBarHandler.appBarMaxHeightPx)).coerceIn(0f, 1f) - -private fun bottomTitleAlpha(connection: CollapsingAppBarNestedScrollConnection?) = - if ((connection?.appBarOffset ?: 0f).absoluteValue < AppBarHandler.appBarMaxHeightPx / 3) 1f - else ((AppBarHandler.appBarMaxHeightPx) + (connection?.appBarOffset ?: 0f) / 1.5f).coerceAtLeast(0f) / AppBarHandler.appBarMaxHeightPx - -@Composable -private fun HostDeviceTitle(hostDevice: Pair, extraPadding: Boolean = false) { - Row(Modifier.fillMaxWidth().padding(top = 5.dp, bottom = if (extraPadding) DEFAULT_PADDING * 2 else DEFAULT_PADDING_HALF), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Start) { - DevicePill( - active = true, - onClick = {}, - actionButtonVisible = false, - icon = painterResource(if (hostDevice.first == null) MR.images.ic_desktop else MR.images.ic_smartphone_300), - text = hostDevice.second - ) - } -} - -@Preview/*( - uiMode = Configuration.UI_MODE_NIGHT_YES, - showBackground = true, - name = "Dark Mode" -)*/ -@Composable -fun PreviewCloseSheetBar() { - SimpleXTheme { - CloseSheetBar(close = {}) - } -} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CollapsingAppBar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CollapsingAppBar.kt index 4410f7ada5..50942169b3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CollapsingAppBar.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CollapsingAppBar.kt @@ -3,15 +3,67 @@ package chat.simplex.common.views.helpers import androidx.compose.foundation.ScrollState import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.* import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.layer.GraphicsLayer +import androidx.compose.ui.graphics.layer.drawLayer +import androidx.compose.ui.graphics.rememberGraphicsLayer import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource -import androidx.compose.ui.unit.Velocity +import androidx.compose.ui.unit.IntSize +import chat.simplex.common.model.ChatController.appPrefs val LocalAppBarHandler: ProvidableCompositionLocal = staticCompositionLocalOf { null } +@Composable +fun rememberAppBarHandler(key1: Any? = null, key2: Any? = null, keyboardCoversBar: Boolean = true): AppBarHandler { + val graphicsLayer = rememberGraphicsLayer() + val backgroundGraphicsLayer = rememberGraphicsLayer() + return remember(key1, key2) { AppBarHandler(graphicsLayer, backgroundGraphicsLayer, keyboardCoversBar) } +} + +@Composable +fun adjustAppBarHandler(handler: AppBarHandler): AppBarHandler { + val graphicsLayer = rememberGraphicsLayer() + val backgroundGraphicsLayer = rememberGraphicsLayer() + if (handler.graphicsLayer == null || handler.graphicsLayer?.isReleased == true || handler.backgroundGraphicsLayer?.isReleased == true) { + handler.graphicsLayer = graphicsLayer + handler.backgroundGraphicsLayer = backgroundGraphicsLayer + } + return handler +} + +fun Modifier.copyViewToAppBar(blurRadius: Int, graphicsLayer: GraphicsLayer?): Modifier { + return if (blurRadius > 0 && graphicsLayer != null) { + this.drawWithContent { + graphicsLayer.record { + this@drawWithContent.drawContent() + } + drawLayer(graphicsLayer) + } + } else this +} + +fun DrawScope.copyBackgroundToAppBar(graphicsLayerSize: MutableState?, backgroundGraphicsLayer: GraphicsLayer?, scope: DrawScope.() -> Unit) { + val blurRadius = appPrefs.appearanceBarsBlurRadius.get() + if (blurRadius > 0 && graphicsLayerSize != null && backgroundGraphicsLayer != null) { + graphicsLayerSize.value = backgroundGraphicsLayer.size + backgroundGraphicsLayer.record { + scope() + } + drawLayer(backgroundGraphicsLayer) + } else { + scope() + } +} + @Stable class AppBarHandler( + var graphicsLayer: GraphicsLayer?, + var backgroundGraphicsLayer: GraphicsLayer?, + val keyboardCoversBar: Boolean = true, listState: LazyListState = LazyListState(0, 0), scrollState: ScrollState = ScrollState(initial = 0) ) { @@ -24,6 +76,8 @@ class AppBarHandler( val connection = CollapsingAppBarNestedScrollConnection() + val backgroundGraphicsLayerSize: MutableState = mutableStateOf(IntSize.Zero) + companion object { var appBarMaxHeightPx: Int = 0 } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultDropdownMenu.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultDropdownMenu.kt index 267fc86462..1f00af2809 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultDropdownMenu.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultDropdownMenu.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.unit.dp @Composable fun DefaultDropdownMenu( showMenu: MutableState, + modifier: Modifier = Modifier, offset: DpOffset = DpOffset(0.dp, 0.dp), dropdownMenuItems: (@Composable () -> Unit)? ) { @@ -23,7 +24,7 @@ fun DefaultDropdownMenu( DropdownMenu( expanded = showMenu.value, onDismissRequest = { showMenu.value = false }, - modifier = Modifier + modifier = modifier .widthIn(min = 250.dp) .background(MaterialTheme.colors.surface) .padding(vertical = 4.dp), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt index 28e9a997ae..cf0c5f7e96 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt @@ -3,44 +3,120 @@ package chat.simplex.common.views.helpers import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.* -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.* import androidx.compose.ui.graphics.* -import androidx.compose.ui.unit.Dp +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.* import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.unit.dp +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chat.item.CenteredRowLayout import chat.simplex.res.MR +import kotlin.math.absoluteValue @Composable -fun DefaultTopAppBar( +fun DefaultAppBar( navigationButton: (@Composable RowScope.() -> Unit)? = null, - title: (@Composable () -> Unit)?, + title: (@Composable () -> Unit)? = null, + fixedTitleText: String? = null, onTitleClick: (() -> Unit)? = null, - showSearch: Boolean, - onSearchValueChanged: (String) -> Unit, - buttons: List<@Composable RowScope.() -> Unit> = emptyList(), + onTop: Boolean, + showSearch: Boolean = false, + searchAlwaysVisible: Boolean = false, + onSearchValueChanged: (String) -> Unit = {}, + buttons: @Composable RowScope.() -> Unit = {}, ) { // If I just disable clickable modifier when don't need it, it will stop passing clicks to search. Replacing the whole modifier val modifier = if (!showSearch) { Modifier.clickable(enabled = onTitleClick != null, onClick = onTitleClick ?: { }) - } else Modifier + } else Modifier.imePadding() - TopAppBar( - modifier = modifier, - title = { - if (!showSearch) { - title?.invoke() - } else { - SearchTextField(Modifier.fillMaxWidth(), alwaysVisible = false, onValueChange = onSearchValueChanged) + val themeBackgroundMix = MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.97f) + val prefAlpha = remember { appPrefs.inAppBarsAlpha.state } + val handler = LocalAppBarHandler.current + val connection = LocalAppBarHandler.current?.connection + val titleText = remember(handler?.title?.value, fixedTitleText) { + if (fixedTitleText != null) { + mutableStateOf(fixedTitleText) + } else { + handler?.title ?: mutableStateOf("") + } + } + val keyboardInset = WindowInsets.ime + Box(modifier) { + val density = LocalDensity.current + val blurRadius = remember { appPrefs.appearanceBarsBlurRadius.state } + Box(Modifier + .matchParentSize() + .blurredBackgroundModifier(keyboardInset, handler, blurRadius, prefAlpha, handler?.keyboardCoversBar == true, onTop, density) + .drawWithCache { + // store it as a variable, don't put it inside if without holding it here. Compiler don't see it changes otherwise + val alpha = prefAlpha.value + val backgroundColor = if (title != null || fixedTitleText != null || connection == null || !onTop) { + themeBackgroundMix.copy(alpha) + } else { + themeBackgroundMix.copy(topTitleAlpha(false, connection)) + } + onDrawBehind { + drawRect(backgroundColor) + } } - }, - backgroundColor = MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.97f), - navigationIcon = navigationButton, - buttons = if (!showSearch) buttons else emptyList(), - centered = !showSearch, + ) + Box( + Modifier + .fillMaxWidth() + .then(if (!onTop) Modifier.navigationBarsPadding() else Modifier) + .heightIn(min = AppBarHeight * fontSizeSqrtMultiplier) + ) { + AppBar( + title = { + if (showSearch) { + SearchTextField(Modifier.fillMaxWidth(), alwaysVisible = searchAlwaysVisible, reducedCloseButtonPadding = 12.dp, onValueChange = onSearchValueChanged) + } else if (title != null) { + title() + } else if (titleText.value.isNotEmpty() && connection != null) { + Row( + Modifier + .graphicsLayer { + alpha = if (fixedTitleText != null) 1f else topTitleAlpha(true, connection) + } + ) { + Text( + titleText.value, + fontWeight = FontWeight.SemiBold, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } + }, + navigationIcon = navigationButton, + buttons = if (!showSearch) buttons else {{}}, + centered = !showSearch && (title != null || !onTop), + onTop = onTop, + ) + AppBarDivider(onTop, title != null || fixedTitleText != null, connection) + } + } +} + + +@Composable +fun CallAppBar( + title: @Composable () -> Unit, + onBack: () -> Unit +) { + AppBar( + title, + navigationIcon = { NavigationButtonBack(tintColor = Color(0xFFFFFFD8), onButtonClicked = onBack) }, + centered = false, + onTop = true ) } @@ -83,58 +159,107 @@ fun NavigationButtonMenu(onButtonClicked: () -> Unit) { } @Composable -private fun TopAppBar( +private fun BoxScope.AppBarDivider(onTop: Boolean, fixedAlpha: Boolean, connection: CollapsingAppBarNestedScrollConnection?) { + if (connection != null) { + Divider( + Modifier + .align(if (onTop) Alignment.BottomStart else Alignment.TopStart) + .graphicsLayer { + alpha = if (!onTop || fixedAlpha) 1f else topTitleAlpha(false, connection, 1f) + } + ) + } else { + Divider(Modifier.align(if (onTop) Alignment.BottomStart else Alignment.TopStart)) + } +} + +@Composable +private fun AppBar( title: @Composable () -> Unit, modifier: Modifier = Modifier, navigationIcon: @Composable (RowScope.() -> Unit)? = null, - buttons: List<@Composable RowScope.() -> Unit> = emptyList(), - backgroundColor: Color = MaterialTheme.colors.primarySurface, + buttons: @Composable RowScope.() -> Unit = {}, centered: Boolean, + onTop: Boolean, ) { - Box( - modifier - .fillMaxWidth() - .height(AppBarHeight * fontSizeSqrtMultiplier) - .background(backgroundColor) - .padding(horizontal = 4.dp), - contentAlignment = Alignment.CenterStart, + val adjustedModifier = modifier + .then(if (onTop) Modifier.statusBarsPadding() else Modifier) + .height(AppBarHeight * fontSizeSqrtMultiplier) + .fillMaxWidth() + .padding(horizontal = AppBarHorizontalPadding) + if (centered) { + AppBarCenterAligned(adjustedModifier, title, navigationIcon, buttons) + } else { + AppBarStartAligned(adjustedModifier, title, navigationIcon, buttons) + } +} + +@Composable +private fun AppBarStartAligned( + modifier: Modifier, + title: @Composable () -> Unit, + navigationIcon: @Composable (RowScope.() -> Unit)? = null, + buttons: @Composable RowScope.() -> Unit +) { + Row( + modifier, + verticalAlignment = Alignment.CenterVertically ) { if (navigationIcon != null) { - Row( - Modifier - .fillMaxHeight() - .width(TitleInsetWithIcon - AppBarHorizontalPadding), - verticalAlignment = Alignment.CenterVertically, - content = navigationIcon - ) + navigationIcon() + Spacer(Modifier.width(AppBarHorizontalPadding)) + } else { + Spacer(Modifier.width(DEFAULT_PADDING)) + } + Row(Modifier + .weight(1f) + .padding(end = DEFAULT_PADDING_HALF) + ) { + title() } Row( - Modifier - .fillMaxHeight() - .fillMaxWidth(), horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically, ) { - buttons.forEach { it() } - } - val startPadding = if (navigationIcon != null) TitleInsetWithIcon else TitleInsetWithoutIcon - val endPadding = (buttons.size * 50f).dp - Box( - Modifier - .fillMaxWidth() - .padding( - start = if (centered) kotlin.math.max(startPadding.value, endPadding.value).dp else startPadding, - end = if (centered) kotlin.math.max(startPadding.value, endPadding.value).dp else endPadding - ), - contentAlignment = Alignment.Center - ) { - title() + buttons() } } } +@Composable +private fun AppBarCenterAligned( + modifier: Modifier, + title: @Composable () -> Unit, + navigationIcon: @Composable (RowScope.() -> Unit)? = null, + buttons: @Composable RowScope.() -> Unit, +) { + CenteredRowLayout(modifier) { + if (navigationIcon != null) { + Row( + Modifier.padding(end = AppBarHorizontalPadding), + verticalAlignment = Alignment.CenterVertically, + content = navigationIcon + ) + } else { + Spacer(Modifier) + } + Row( + Modifier.padding(end = DEFAULT_PADDING_HALF) + ) { + title() + } + Row( + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + ) { + buttons() + } + } +} + +private fun topTitleAlpha(text: Boolean, connection: CollapsingAppBarNestedScrollConnection, alpha: Float = appPrefs.inAppBarsAlpha.get()) = + if (connection.appBarOffset.absoluteValue < AppBarHandler.appBarMaxHeightPx / 3) 0f + else ((-connection.appBarOffset * 1.5f) / (AppBarHandler.appBarMaxHeightPx)).coerceIn(0f, if (text) 1f else alpha) + val AppBarHeight = 56.dp -val AppBarHorizontalPadding = 4.dp -val BottomAppBarHeight = 60.dp -private val TitleInsetWithoutIcon = DEFAULT_PADDING - AppBarHorizontalPadding -val TitleInsetWithIcon = 72.dp +val AppBarHorizontalPadding = 2.dp diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt index 4c35e72701..c181f74e99 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt @@ -6,12 +6,14 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatModel import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chatlist.StatusBarBackground import kotlinx.coroutines.flow.MutableStateFlow import java.util.concurrent.atomic.AtomicBoolean import kotlin.math.min @@ -21,24 +23,40 @@ import kotlin.math.sqrt fun ModalView( close: () -> Unit, showClose: Boolean = true, + showAppBar: Boolean = true, enableClose: Boolean = true, - background: Color = MaterialTheme.colors.background, + background: Color = Color.Unspecified, modifier: Modifier = Modifier, - closeOnTop: Boolean = true, + showSearch: Boolean = false, + searchAlwaysVisible: Boolean = false, + onSearchValueChanged: (String) -> Unit = {}, endButtons: @Composable RowScope.() -> Unit = {}, - content: @Composable () -> Unit, + content: @Composable BoxScope.() -> Unit, ) { - if (showClose) { + if (showClose && showAppBar) { BackHandler(enabled = enableClose, onBack = close) } + val oneHandUI = remember { appPrefs.oneHandUI.state } Surface(Modifier.fillMaxSize(), contentColor = LocalContentColor.current) { - Column(if (background != MaterialTheme.colors.background) Modifier.background(background) else Modifier.themedBackground()) { - if (closeOnTop) { - CloseSheetBar(if (enableClose) close else null, showClose, endButtons = endButtons) - } + Box(if (background != Color.Unspecified) Modifier.background(background) else Modifier.themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer)) { Box(modifier = modifier) { content() } + if (showAppBar) { + if (oneHandUI.value) { + StatusBarBackground() + } + Box(Modifier.align(if (oneHandUI.value) Alignment.BottomStart else Alignment.TopStart)) { + DefaultAppBar( + navigationButton = if (showClose) {{ NavigationButtonBack(onButtonClicked = if (enableClose) close else null) }} else null, + onTop = !oneHandUI.value, + showSearch = showSearch, + searchAlwaysVisible = searchAlwaysVisible, + onSearchValueChanged = onSearchValueChanged, + buttons = endButtons + ) + } + } } } } @@ -47,7 +65,7 @@ enum class ModalPlacement { START, CENTER, END, FULLSCREEN } -class ModalData() { +class ModalData(val keyboardCoversBar: Boolean = true) { private val state = mutableMapOf>() fun stateGetOrPut (key: String, default: () -> T): MutableState = state.getOrPut(key) { mutableStateOf(default() as Any) } as MutableState @@ -55,7 +73,7 @@ class ModalData() { fun stateGetOrPutNullable (key: String, default: () -> T?): MutableState = state.getOrPut(key) { mutableStateOf(default() as Any?) } as MutableState - val appBarHandler = AppBarHandler() + val appBarHandler = AppBarHandler(null, null, keyboardCoversBar = keyboardCoversBar) } class ModalManager(private val placement: ModalPlacement? = null) { @@ -69,23 +87,21 @@ class ModalManager(private val placement: ModalPlacement? = null) { private var passcodeView: MutableStateFlow<(@Composable (close: () -> Unit) -> Unit)?> = MutableStateFlow(null) private var onTimePasscodeView: MutableStateFlow<(@Composable (close: () -> Unit) -> Unit)?> = MutableStateFlow(null) - fun showModal(settings: Boolean = false, showClose: Boolean = true, closeOnTop: Boolean = true, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable ModalData.() -> Unit) { - val data = ModalData() + fun showModal(settings: Boolean = false, showClose: Boolean = true, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable ModalData.() -> Unit) { showCustomModal { close -> - ModalView(close, showClose = showClose, closeOnTop = closeOnTop, endButtons = endButtons, content = { data.content() }) + ModalView(close, showClose = showClose, endButtons = endButtons, content = { content() }) } } - fun showModalCloseable(settings: Boolean = false, showClose: Boolean = true, closeOnTop: Boolean = true, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable ModalData.(close: () -> Unit) -> Unit) { - val data = ModalData() + fun showModalCloseable(settings: Boolean = false, showClose: Boolean = true, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable ModalData.(close: () -> Unit) -> Unit) { showCustomModal { close -> - ModalView(close, showClose = showClose, endButtons = endButtons, closeOnTop = closeOnTop, content = { data.content(close) }) + ModalView(close, showClose = showClose, endButtons = endButtons, content = { content(close) }) } } - fun showCustomModal(animated: Boolean = true, modal: @Composable ModalData.(close: () -> Unit) -> Unit) { + fun showCustomModal(animated: Boolean = true, keyboardCoversBar: Boolean = true, modal: @Composable ModalData.(close: () -> Unit) -> Unit) { Log.d(TAG, "ModalManager.showCustomModal") - val data = ModalData() + val data = ModalData(keyboardCoversBar = keyboardCoversBar) // Means, animation is in progress or not started yet. Do not wait until animation finishes, just remove all from screen. // This is useful when invoking close() and ShowCustomModal one after another without delay. Otherwise, screen will hold prev view if (toRemove.isNotEmpty()) { @@ -146,9 +162,7 @@ class ModalManager(private val placement: ModalPlacement? = null) { // Without animation if (modalCount.value > 0 && modalViews.lastOrNull()?.first == false) { modalViews.lastOrNull()?.let { - CompositionLocalProvider( - LocalAppBarHandler provides it.second.appBarHandler - ) { + CompositionLocalProvider(LocalAppBarHandler provides adjustAppBarHandler(it.second.appBarHandler)) { it.third(it.second, ::closeModal) } } @@ -164,9 +178,7 @@ class ModalManager(private val placement: ModalPlacement? = null) { } ) { modalViews.getOrNull(it - 1)?.let { - CompositionLocalProvider( - LocalAppBarHandler provides it.second.appBarHandler - ) { + CompositionLocalProvider(LocalAppBarHandler provides adjustAppBarHandler(it.second.appBarHandler)) { it.third(it.second, ::closeModal) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt index 60dceab4ad..7124f34ac0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SearchTextField.kt @@ -2,7 +2,7 @@ package chat.simplex.common.views.helpers import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.ZeroCornerSize import androidx.compose.foundation.text.* import androidx.compose.material.* @@ -18,12 +18,9 @@ import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalSoftwareKeyboardController import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.* import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp +import androidx.compose.ui.unit.* import chat.simplex.common.platform.* import chat.simplex.res.MR import kotlinx.coroutines.delay @@ -38,6 +35,7 @@ fun SearchTextField( placeholder: String = stringResource(MR.strings.search_verb), enabled: Boolean = true, trailingContent: @Composable (() -> Unit)? = null, + reducedCloseButtonPadding: Dp = 0.dp, onValueChange: (String) -> Unit ) { val focusRequester = remember { FocusRequester() } @@ -81,15 +79,20 @@ fun SearchTextField( ) val shape = MaterialTheme.shapes.small.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize) val interactionSource = remember { MutableInteractionSource() } + val textStyle = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground) + // sizing is done differently on Android and desktop in order to have the same height of search and compose view on desktop + // see PlatformTextField.desktop + SendMsgView + val padding = if (appPlatform.isAndroid) PaddingValues() else PaddingValues(top = 3.dp, bottom = 4.dp) BasicTextField( value = searchText.value, modifier = modifier .background(colors.backgroundColor(enabled).value, shape) .indicatorLine(enabled, false, interactionSource, colors) .focusRequester(focusRequester) + .padding(padding) .defaultMinSize( minWidth = TextFieldDefaults.MinWidth, - minHeight = TextFieldDefaults.MinHeight + minHeight = if (appPlatform.isAndroid) TextFieldDefaults.MinHeight else 0.dp ), onValueChange = { searchText.value = it @@ -100,18 +103,14 @@ fun SearchTextField( visualTransformation = VisualTransformation.None, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), singleLine = true, - textStyle = TextStyle( - color = MaterialTheme.colors.onBackground, - fontWeight = FontWeight.Normal, - fontSize = 15.sp - ), + textStyle = textStyle, interactionSource = interactionSource, decorationBox = @Composable { innerTextField -> TextFieldDefaults.TextFieldDecorationBox( value = searchText.value.text, innerTextField = innerTextField, placeholder = { - Text(placeholder, maxLines = 1, overflow = TextOverflow.Ellipsis) + Text(placeholder, style = textStyle.copy(color = MaterialTheme.colors.secondary), maxLines = 1, overflow = TextOverflow.Ellipsis) }, trailingIcon = if (searchText.value.text.isNotEmpty()) {{ IconButton({ @@ -121,7 +120,7 @@ fun SearchTextField( } searchText.value = TextFieldValue(""); onValueChange("") - }) { + }, Modifier.offset(x = reducedCloseButtonPadding)) { Icon(painterResource(MR.images.ic_close), stringResource(MR.strings.icon_descr_close_button), tint = MaterialTheme.colors.primary,) } }} else trailingContent, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/TextEditor.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/TextEditor.kt index ab7e562697..da16e2b7e7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/TextEditor.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/TextEditor.kt @@ -57,7 +57,6 @@ fun TextEditor( ) { val textFieldModifier = modifier .fillMaxWidth() - .navigationBarsWithImePadding() .onFocusChanged { focused = it.isFocused } .padding(10.dp) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ThemeModeEditor.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ThemeModeEditor.kt index a77290d90f..d7cdf0e2e3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ThemeModeEditor.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ThemeModeEditor.kt @@ -32,10 +32,7 @@ fun ModalData.UserWallpaperEditor( globalThemeUsed: MutableState, save: suspend (applyToMode: DefaultThemeMode?, ThemeModeOverride?) -> Unit ) { - ColumnWithScrollBar( - Modifier - .fillMaxSize() - ) { + ColumnWithScrollBar { val applyToMode = remember { stateGetOrPutNullable("applyToMode") { applyToMode } } var showMore by remember { stateGetOrPut("showMore") { false } } val themeModeOverride = remember { stateGetOrPut("themeModeOverride") { theme } } @@ -231,10 +228,7 @@ fun ModalData.ChatWallpaperEditor( globalThemeUsed: MutableState, save: suspend (applyToMode: DefaultThemeMode?, ThemeModeOverride?) -> Unit ) { - ColumnWithScrollBar( - Modifier - .fillMaxSize() - ) { + ColumnWithScrollBar { val applyToMode = remember { stateGetOrPutNullable("applyToMode") { applyToMode } } var showMore by remember { stateGetOrPut("showMore") { false } } val themeModeOverride = remember { stateGetOrPut("themeModeOverride") { theme } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt index 4cc7899cc8..e2ee8878c8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt @@ -149,9 +149,7 @@ private fun MigrateFromDeviceLayout( ) { val tempDatabaseFile = rememberSaveable { mutableStateOf(fileForTemporaryDatabase()) } - ColumnWithScrollBar( - Modifier.fillMaxSize(), maxIntrinsicSize = true - ) { + ColumnWithScrollBar(maxIntrinsicSize = true) { AppBarTitle(stringResource(MR.strings.migrate_from_device_title)) SectionByState(migrationState, tempDatabaseFile.value, chatReceiver) SectionBottomSpacer() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt index 415f5cdd57..90f8593c4a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt @@ -162,9 +162,7 @@ private fun ModalData.MigrateToDeviceLayout( close: () -> Unit, ) { val tempDatabaseFile = rememberSaveable { mutableStateOf(fileForTemporaryDatabase()) } - ColumnWithScrollBar( - Modifier.fillMaxSize(), maxIntrinsicSize = true - ) { + ColumnWithScrollBar(maxIntrinsicSize = true) { AppBarTitle(stringResource(MR.strings.migrate_to_device_title)) SectionByState(migrationState, tempDatabaseFile.value, chatReceiver, close) SectionBottomSpacer() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactLearnMore.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactLearnMore.kt index da59050a3a..077abd1b98 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactLearnMore.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactLearnMore.kt @@ -15,9 +15,7 @@ import chat.simplex.res.MR @Composable fun AddContactLearnMore(close: () -> Unit) { - ColumnWithScrollBar( - Modifier.padding(horizontal = DEFAULT_PADDING), - ) { + ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_PADDING)) { AppBarTitle(stringResource(MR.strings.one_time_link), withPadding = false) ReadableText(MR.strings.scan_qr_to_connect_to_contact) ReadableText(MR.strings.if_you_cant_meet_in_person) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt index c430a62340..e1d3d6541a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt @@ -84,10 +84,9 @@ fun AddGroupLayout( val focusRequester = remember { FocusRequester() } val incognito = remember { mutableStateOf(incognitoPref.get()) } - ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { ModalBottomSheetLayout( scrimColor = Color.Black.copy(alpha = 0.12F), - modifier = Modifier.navigationBarsWithImePadding(), + modifier = Modifier.imePadding(), sheetContent = { GetImageBottomSheet( chosenImage, @@ -100,11 +99,7 @@ fun AddGroupLayout( sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp) ) { ModalView(close = close) { - ColumnWithScrollBar( - Modifier - .fillMaxSize() - .padding(horizontal = DEFAULT_PADDING) - ) { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.create_secret_group_title), hostDevice(rhId)) Box( Modifier @@ -122,7 +117,7 @@ fun AddGroupLayout( } } } - Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Row(Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { Text( stringResource(MR.strings.group_display_name_field), fontSize = 16.sp @@ -134,7 +129,9 @@ fun AddGroupLayout( } } } - ProfileNameField(displayName, "", { isValidDisplayName(it.trim()) }, focusRequester) + Box(Modifier.padding(horizontal = DEFAULT_PADDING)) { + ProfileNameField(displayName, "", { isValidDisplayName(it.trim()) }, focusRequester) + } Spacer(Modifier.height(8.dp)) SettingsActionItem( @@ -170,7 +167,6 @@ fun AddGroupLayout( } } } - } } fun canCreateProfile(displayName: String): Boolean = displayName.trim().isNotEmpty() && isValidDisplayName(displayName.trim()) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt index 64ff7e4f40..1623f8510d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt @@ -89,7 +89,7 @@ private fun ContactConnectionInfoLayout( SettingsActionItemWithContent( icon = painterResource(MR.images.ic_theater_comedy_filled), text = null, - click = { ModalManager.start.showModal { IncognitoView() } }, + click = { ModalManager.end.showModal { IncognitoView() } }, iconColor = Indigo, extraPadding = false ) { @@ -105,9 +105,7 @@ private fun ContactConnectionInfoLayout( } } - ColumnWithScrollBar( - Modifier, - ) { + ColumnWithScrollBar { AppBarTitle( stringResource( if (contactConnection.initiated) MR.strings.you_invited_a_contact diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt index 1a3ea10806..02996381f8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt @@ -1,9 +1,7 @@ package chat.simplex.common.views.newchat -import SectionDivider import SectionDividerSpaced import SectionItemView -import SectionSpacer import SectionView import TextIconSpaced import androidx.compose.desktop.ui.tooling.preview.Preview @@ -14,8 +12,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier +import androidx.compose.ui.* import androidx.compose.ui.focus.* import androidx.compose.ui.graphics.* import androidx.compose.ui.graphics.painter.Painter @@ -32,56 +29,43 @@ import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* -import chat.simplex.common.views.chatlist.ScrollDirection +import chat.simplex.common.views.chat.topPaddingToContent +import chat.simplex.common.views.chatlist.* import chat.simplex.common.views.contacts.* import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter -import java.net.URI @Composable fun ModalData.NewChatSheet(rh: RemoteHostInfo?, close: () -> Unit) { val oneHandUI = remember { appPrefs.oneHandUI.state } - val keyboardState by getKeyboardState() - val showToolbarInOneHandUI = remember { derivedStateOf { keyboardState == KeyboardState.Closed && oneHandUI.value } } - Scaffold( - bottomBar = { - if (showToolbarInOneHandUI.value) { - Column { - Divider() - CloseSheetBar( - close = close, - showClose = true, - endButtons = { Spacer(Modifier.minimumInteractiveComponentSize()) }, - arrangement = Arrangement.Bottom, - closeBarTitle = generalGetString(MR.strings.new_message), - barPaddingValues = PaddingValues(horizontal = 0.dp) - ) - } - } + Box { + val closeAll = { ModalManager.start.closeModals() } + + Column(modifier = Modifier.fillMaxSize()) { + NewChatSheetLayout( + addContact = { + ModalManager.start.showModalCloseable(endButtons = { AddContactLearnMoreButton() }) { _ -> NewChatView(chatModel.currentRemoteHost.value, NewChatOption.INVITE, close = closeAll) } + }, + scanPaste = { + ModalManager.start.showModalCloseable(endButtons = { AddContactLearnMoreButton() }) { _ -> NewChatView(chatModel.currentRemoteHost.value, NewChatOption.CONNECT, showQRCodeScanner = appPlatform.isAndroid, close = closeAll) } + }, + createGroup = { + ModalManager.start.showCustomModal { close -> AddGroupView(chatModel, chatModel.currentRemoteHost.value, close, closeAll) } + }, + rh = rh, + close = close + ) } - ) { - Column( - modifier = Modifier.fillMaxSize().padding(it) - ) { - val closeAll = { ModalManager.start.closeModals() } - - Column(modifier = Modifier.fillMaxSize()) { - NewChatSheetLayout( - addContact = { - ModalManager.start.showModalCloseable(endButtons = { AddContactLearnMoreButton() }) { _ -> NewChatView(chatModel.currentRemoteHost.value, NewChatOption.INVITE, close = closeAll ) } - }, - scanPaste = { - ModalManager.start.showModalCloseable(endButtons = { AddContactLearnMoreButton() }) { _ -> NewChatView(chatModel.currentRemoteHost.value, NewChatOption.CONNECT, showQRCodeScanner = appPlatform.isAndroid, close = closeAll) } - }, - createGroup = { - ModalManager.start.showCustomModal { close -> AddGroupView(chatModel, chatModel.currentRemoteHost.value, close, closeAll) } - }, - rh = rh, - close = close + if (oneHandUI.value) { + Column(Modifier.align(Alignment.BottomCenter)) { + DefaultAppBar( + navigationButton = { NavigationButtonBack(onButtonClicked = close) }, + fixedTitleText = generalGetString(MR.strings.new_message), + onTop = false, ) } } @@ -187,168 +171,258 @@ private fun ModalData.NewChatSheetLayout( derivedStateOf { filterContactTypes(chatModel.chats.value, deletedContactTypes) } } - LazyColumnWithScrollBar( - Modifier.fillMaxSize(), - listState, - reverseLayout = oneHandUI.value - ) { - if (!oneHandUI.value) { - item { - Box(contentAlignment = Alignment.Center) { - val bottomPadding = DEFAULT_PADDING - AppBarTitle( - stringResource(MR.strings.new_message), - hostDevice(rh?.remoteHostId), - bottomPadding = bottomPadding + val actionButtonsOriginal = listOf( + Triple( + painterResource(MR.images.ic_add_link), + stringResource(MR.strings.add_contact_tab), + addContact, + ), + Triple( + painterResource(MR.images.ic_qr_code), + if (appPlatform.isAndroid) stringResource(MR.strings.scan_paste_link) else stringResource(MR.strings.paste_link), + scanPaste, + ), + Triple( + painterResource(MR.images.ic_group), + stringResource(MR.strings.create_group_button), + createGroup, + ) + ) + + @Composable + fun DeletedChatsItem(actionButtons: List Unit>>) { + if (searchText.value.text.isEmpty()) { + Spacer(Modifier.padding(bottom = 27.dp)) + } + + if (searchText.value.text.isEmpty()) { + Row { + SectionView { + actionButtons.map { + NewChatButton( + icon = it.first, + text = it.second, + click = it.third, + ) + } + } + } + if (deletedChats.isNotEmpty()) { + SectionDividerSpaced(maxBottomPadding = false) + SectionView { + SectionItemView( + click = { + ModalManager.start.showCustomModal { closeDeletedChats -> + ModalView( + close = closeDeletedChats, + showAppBar = !oneHandUI.value, + ) { + if (oneHandUI.value) { + BackHandler(onBack = closeDeletedChats) + } + DeletedContactsView(rh = rh, closeDeletedChats = closeDeletedChats, close = { + ModalManager.start.closeModals() + }) + } + } + } + ) { + Icon( + painterResource(MR.images.ic_inventory_2), + contentDescription = stringResource(MR.strings.deleted_chats), + tint = MaterialTheme.colors.secondary, + ) + TextIconSpaced(false) + Text(text = stringResource(MR.strings.deleted_chats), color = MaterialTheme.colors.onBackground) + } + } + } + } + } + + @Composable + fun NoFilteredContactsItem() { + if (filteredContactChats.isEmpty() && allChats.isNotEmpty()) { + Column(sectionModifier.fillMaxSize().padding(DEFAULT_PADDING)) { + Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { + Text( + generalGetString(MR.strings.no_filtered_contacts), + color = MaterialTheme.colors.secondary ) } } } - stickyHeader { - Column( - Modifier - .offset { - val y = if (searchText.value.text.isEmpty()) { - val offsetMultiplier = if (oneHandUI.value) 1 else -1 + } - if ( - (oneHandUI.value && scrollDirection == ScrollDirection.Up) || - (appPlatform.isAndroid && keyboardState == KeyboardState.Opened) - ) { - 0 - } else if (oneHandUI.value && listState.firstVisibleItemIndex == 0) { - listState.firstVisibleItemScrollOffset - } else if (!oneHandUI.value && listState.firstVisibleItemIndex == 0) { - 0 - } else if (!oneHandUI.value && listState.firstVisibleItemIndex == 1) { - -listState.firstVisibleItemScrollOffset + @Composable + fun OneHandLazyColumn() { + val blankSpaceSize = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + AppBarHeight * fontSizeSqrtMultiplier + LazyColumnWithScrollBar( + state = listState, + reverseLayout = oneHandUI.value + ) { + item { Spacer(Modifier.height(blankSpaceSize)) } + stickyHeader { + val scrolledSomething by remember { derivedStateOf { listState.firstVisibleItemScrollOffset > 0 || listState.firstVisibleItemIndex > 0 } } + Column( + Modifier + .zIndex(1f) + .offset { + val y = if (searchText.value.text.isNotEmpty() || (appPlatform.isAndroid && keyboardState == KeyboardState.Opened)) { + if (listState.firstVisibleItemIndex == 0) -minOf(listState.firstVisibleItemScrollOffset, blankSpaceSize.roundToPx()) + else -blankSpaceSize.roundToPx() } else { - offsetMultiplier * 1000 - } - } else { - 0 - } - IntOffset(0, y) - } - .background(MaterialTheme.colors.background) - ) { - Divider() - ContactsSearchBar( - listState = listState, - searchText = searchText, - searchShowingSimplexLink = searchShowingSimplexLink, - searchChatFilteredBySimplexLink = searchChatFilteredBySimplexLink, - close = close, - ) - if (!oneHandUI.value) { - Divider() - } - } - } - item { - if (searchText.value.text.isEmpty()) { - Spacer(Modifier.padding(bottom = 27.dp)) - } - - val actionButtonsOriginal = listOf( - Triple( - painterResource(MR.images.ic_add_link), - stringResource(MR.strings.add_contact_tab), - addContact, - ), - Triple( - painterResource(MR.images.ic_qr_code), - if (appPlatform.isAndroid) stringResource(MR.strings.scan_paste_link) else stringResource(MR.strings.paste_link), - scanPaste, - ), - Triple( - painterResource(MR.images.ic_group), - stringResource(MR.strings.create_group_button), - createGroup, - ) - ) - - val actionButtons by remember(oneHandUI.value) { - derivedStateOf { - if (oneHandUI.value) actionButtonsOriginal.asReversed() else actionButtonsOriginal - } - } - - if (searchText.value.text.isEmpty()) { - Row { - SectionView { - actionButtons.map { - NewChatButton( - icon = it.first, - text = it.second, - click = it.third, - ) - } - } - } - if (deletedChats.isNotEmpty()) { - SectionDividerSpaced(maxBottomPadding = false) - SectionView { - SectionItemView( - click = { - ModalManager.start.showCustomModal { closeDeletedChats -> - ModalView( - close = closeDeletedChats, - closeOnTop = !oneHandUI.value, - ) { - DeletedContactsView(rh = rh, closeDeletedChats = closeDeletedChats, close = { - ModalManager.start.closeModals() - }) - } + when (listState.firstVisibleItemIndex) { + 0 -> 0 + 1 -> listState.firstVisibleItemScrollOffset + else -> 1000 } } - ) { - Icon( - painterResource(MR.images.ic_inventory_2), - contentDescription = stringResource(MR.strings.deleted_chats), - tint = MaterialTheme.colors.secondary, - ) - TextIconSpaced(false) - Text(text = stringResource(MR.strings.deleted_chats), color = MaterialTheme.colors.onBackground) + IntOffset(0, y) } + // show background when something is scrolled because otherwise the bar is transparent. + // not using background always because of gradient in SimpleX theme + .background( + if (scrolledSomething && (keyboardState == KeyboardState.Opened || searchText.value.text.isNotEmpty())) { + MaterialTheme.colors.background + } else { + Color.Unspecified + } + ) + ) { + Divider() + Column(Modifier.consumeWindowInsets(WindowInsets.navigationBars).consumeWindowInsets(PaddingValues(bottom = AppBarHeight * fontSizeSqrtMultiplier))) { + ContactsSearchBar( + listState = listState, + searchText = searchText, + searchShowingSimplexLink = searchShowingSimplexLink, + searchChatFilteredBySimplexLink = searchChatFilteredBySimplexLink, + close = close, + ) + Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime)) } } } - } - - item { - if (filteredContactChats.isNotEmpty() && searchText.value.text.isEmpty()) { - if (!oneHandUI.value) { - SectionDividerSpaced() - SectionView(stringResource(MR.strings.contact_list_header_title).uppercase(), headerBottomPadding = DEFAULT_PADDING_HALF) {} - } else { + item { + DeletedChatsItem(actionButtonsOriginal.asReversed()) + } + item { + if (filteredContactChats.isNotEmpty() && searchText.value.text.isEmpty()) { SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = false) SectionView(stringResource(MR.strings.contact_list_header_title).uppercase(), headerBottomPadding = DEFAULT_PADDING_HALF) {} Spacer(Modifier.height(DEFAULT_PADDING_HALF)) } } - } - - item { - if (filteredContactChats.isEmpty() && allChats.isNotEmpty()) { - Column(sectionModifier.fillMaxSize().padding(DEFAULT_PADDING)) { - Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { - Text( - generalGetString(MR.strings.no_filtered_contacts), - color = MaterialTheme.colors.secondary - ) + item { + NoFilteredContactsItem() + } + itemsIndexed(filteredContactChats) { index, chat -> + val nextChatSelected = remember(chat.id, filteredContactChats) { + derivedStateOf { + chatModel.chatId.value != null && filteredContactChats.getOrNull(index + 1)?.id == chatModel.chatId.value } } + ContactListNavLinkView(chat, nextChatSelected, showDeletedChatIcon = true) } - } - - itemsIndexed(filteredContactChats) { index, chat -> - val nextChatSelected = remember(chat.id, filteredContactChats) { - derivedStateOf { - chatModel.chatId.value != null && filteredContactChats.getOrNull(index + 1)?.id == chatModel.chatId.value + if (appPlatform.isAndroid) { + item { + Spacer(Modifier.windowInsetsTopHeight(WindowInsets.statusBars)) } } - ContactListNavLinkView(chat, nextChatSelected, showDeletedChatIcon = true) + } + } + + @Composable + fun NonOneHandLazyColumn() { + val blankSpaceSize = topPaddingToContent() + LazyColumnWithScrollBar( + Modifier.imePadding(), + state = listState, + reverseLayout = false + ) { + item { + Box(Modifier.padding(top = blankSpaceSize)) { + AppBarTitle( + stringResource(MR.strings.new_message), + hostDevice(rh?.remoteHostId), + bottomPadding = DEFAULT_PADDING + ) + } + } + stickyHeader { + val scrolledSomething by remember { derivedStateOf { listState.firstVisibleItemScrollOffset > 0 || listState.firstVisibleItemIndex > 0 } } + Column( + Modifier + .zIndex(1f) + .offset { + val y = if (searchText.value.text.isNotEmpty() || (appPlatform.isAndroid && keyboardState == KeyboardState.Opened)) { + if (listState.firstVisibleItemIndex == 0) (listState.firstVisibleItemScrollOffset - (listState.layoutInfo.visibleItemsInfo[0].size - blankSpaceSize.roundToPx())).coerceAtLeast(0) + else blankSpaceSize.roundToPx() + } else { + when (listState.firstVisibleItemIndex) { + 0 -> 0 + 1 -> -listState.firstVisibleItemScrollOffset + else -> -1000 + } + } + IntOffset(0, y) + } + // show background when something is scrolled because otherwise the bar is transparent. + // not using background always because of gradient in SimpleX theme + .background( + if (scrolledSomething && (keyboardState == KeyboardState.Opened || searchText.value.text.isNotEmpty())) { + MaterialTheme.colors.background + } else { + Color.Unspecified + } + ) + ) { + Divider() + ContactsSearchBar( + listState = listState, + searchText = searchText, + searchShowingSimplexLink = searchShowingSimplexLink, + searchChatFilteredBySimplexLink = searchChatFilteredBySimplexLink, + close = close, + ) + Divider() + } + } + item { + DeletedChatsItem(actionButtonsOriginal) + } + item { + if (filteredContactChats.isNotEmpty() && searchText.value.text.isEmpty()) { + SectionDividerSpaced() + SectionView(stringResource(MR.strings.contact_list_header_title).uppercase(), headerBottomPadding = DEFAULT_PADDING_HALF) {} + } + } + item { + NoFilteredContactsItem() + } + itemsIndexed(filteredContactChats) { index, chat -> + val nextChatSelected = remember(chat.id, filteredContactChats) { + derivedStateOf { + chatModel.chatId.value != null && filteredContactChats.getOrNull(index + 1)?.id == chatModel.chatId.value + } + } + ContactListNavLinkView(chat, nextChatSelected, showDeletedChatIcon = true) + } + if (appPlatform.isAndroid) { + item { + Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) + } + } + } + } + + Box { + if (oneHandUI.value) { + OneHandLazyColumn() + StatusBarBackground() + } else { + NonOneHandLazyColumn() + NavigationBarBackground(oneHandUI.value, true) } } } @@ -554,26 +628,7 @@ private fun contactTypesSearchTargets(baseContactTypes: List, searc @Composable private fun ModalData.DeletedContactsView(rh: RemoteHostInfo?, closeDeletedChats: () -> Unit, close: () -> Unit) { val oneHandUI = remember { appPrefs.oneHandUI.state } - val keyboardState by getKeyboardState() - val showToolbarInOneHandUI = remember { derivedStateOf { keyboardState == KeyboardState.Closed && oneHandUI.value } } - - Scaffold( - bottomBar = { - if (showToolbarInOneHandUI.value) { - Column { - Divider() - CloseSheetBar( - close = closeDeletedChats, - showClose = true, - endButtons = { Spacer(Modifier.minimumInteractiveComponentSize()) }, - arrangement = Arrangement.Bottom, - closeBarTitle = generalGetString(MR.strings.deleted_chats), - barPaddingValues = PaddingValues(horizontal = 0.dp) - ) - } - } - } - ) { contentPadding -> + Box { val listState = remember { appBarHandler.listState } val searchText = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) } val searchShowingSimplexLink = remember { mutableStateOf(false) } @@ -590,57 +645,93 @@ private fun ModalData.DeletedContactsView(rh: RemoteHostInfo?, closeDeletedChats contactChats = allChats ) - LazyColumnWithScrollBar( - Modifier.fillMaxSize(), - contentPadding = contentPadding, - reverseLayout = oneHandUI.value, - ) { - item { - if (!oneHandUI.value) { - Box(contentAlignment = Alignment.Center) { - val bottomPadding = DEFAULT_PADDING - AppBarTitle( - stringResource(MR.strings.deleted_chats), - hostDevice(rh?.remoteHostId), - bottomPadding = bottomPadding - ) - } - } - } - item { - if (!oneHandUI.value) { - Divider() - } - ContactsSearchBar( - listState = listState, - searchText = searchText, - searchShowingSimplexLink = searchShowingSimplexLink, - searchChatFilteredBySimplexLink = searchChatFilteredBySimplexLink, - close = close, - ) - Divider() - } - - item { - if (filteredContactChats.isEmpty() && allChats.isNotEmpty()) { - Column(Modifier.fillMaxSize().padding(DEFAULT_PADDING)) { - Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { - Text( - generalGetString(MR.strings.no_filtered_contacts), - color = MaterialTheme.colors.secondary, + Box { + val topPaddingToContent = topPaddingToContent() + LazyColumnWithScrollBar( + if (!oneHandUI.value) Modifier.imePadding() else Modifier, + contentPadding = PaddingValues( + top = if (!oneHandUI.value) topPaddingToContent else 0.dp, + bottom = if (oneHandUI.value) WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + AppBarHeight * fontSizeSqrtMultiplier else 0.dp + ), + reverseLayout = oneHandUI.value, + ) { + item { + if (!oneHandUI.value) { + Box(contentAlignment = Alignment.Center) { + val bottomPadding = DEFAULT_PADDING + AppBarTitle( + stringResource(MR.strings.deleted_chats), + hostDevice(rh?.remoteHostId), + bottomPadding = bottomPadding ) } } } - } + item { + if (!oneHandUI.value) { + Divider() + ContactsSearchBar( + listState = listState, + searchText = searchText, + searchShowingSimplexLink = searchShowingSimplexLink, + searchChatFilteredBySimplexLink = searchChatFilteredBySimplexLink, + close = close, + ) + } else { + Column(Modifier.consumeWindowInsets(WindowInsets.navigationBars).consumeWindowInsets(PaddingValues(bottom = AppBarHeight))) { + ContactsSearchBar( + listState = listState, + searchText = searchText, + searchShowingSimplexLink = searchShowingSimplexLink, + searchChatFilteredBySimplexLink = searchChatFilteredBySimplexLink, + close = close, + ) + Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime)) + } + } + Divider() + } - itemsIndexed(filteredContactChats) { index, chat -> - val nextChatSelected = remember(chat.id, filteredContactChats) { - derivedStateOf { - chatModel.chatId.value != null && filteredContactChats.getOrNull(index + 1)?.id == chatModel.chatId.value + item { + if (filteredContactChats.isEmpty() && allChats.isNotEmpty()) { + Column(Modifier.fillMaxSize().padding(DEFAULT_PADDING)) { + Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { + Text( + generalGetString(MR.strings.no_filtered_contacts), + color = MaterialTheme.colors.secondary, + ) + } + } } } - ContactListNavLinkView(chat, nextChatSelected, showDeletedChatIcon = false) + + itemsIndexed(filteredContactChats) { index, chat -> + val nextChatSelected = remember(chat.id, filteredContactChats) { + derivedStateOf { + chatModel.chatId.value != null && filteredContactChats.getOrNull(index + 1)?.id == chatModel.chatId.value + } + } + ContactListNavLinkView(chat, nextChatSelected, showDeletedChatIcon = false) + } + if (appPlatform.isAndroid) { + item { + Spacer(if (oneHandUI.value) Modifier.windowInsetsTopHeight(WindowInsets.statusBars) else Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) + } + } + } + if (oneHandUI.value) { + StatusBarBackground() + } else { + NavigationBarBackground(oneHandUI.value, true) + } + } + if (oneHandUI.value) { + Column(Modifier.align(Alignment.BottomCenter)) { + DefaultAppBar( + navigationButton = { NavigationButtonBack(onButtonClicked = closeDeletedChats) }, + fixedTitleText = generalGetString(MR.strings.deleted_chats), + onTop = false, + ) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index 5298e11e75..61403e07a4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -29,10 +29,12 @@ import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.sp import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.model.ChatModel.withChats import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chat.topPaddingToContent import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.* import chat.simplex.res.MR @@ -398,8 +400,12 @@ fun ActiveProfilePicker( .fillMaxSize() .alpha(if (progressByTimeout) 0.6f else 1f) ) { - LazyColumnWithScrollBar(userScrollEnabled = !switchingProfile.value) { + LazyColumnWithScrollBar(Modifier.padding(top = topPaddingToContent()), userScrollEnabled = !switchingProfile.value) { item { + val oneHandUI = remember { appPrefs.oneHandUI.state } + if (oneHandUI.value) { + Spacer(Modifier.padding(top = DEFAULT_PADDING + 5.dp)) + } AppBarTitle(stringResource(MR.strings.select_chat_profile), hostDevice(rhId), bottomPadding = DEFAULT_PADDING) } val activeProfile = filteredProfiles.firstOrNull { it.activeUser } @@ -434,6 +440,9 @@ fun ActiveProfilePicker( ProfilePickerUserOption(p) } } + item { + Spacer(Modifier.imePadding().padding(bottom = DEFAULT_BOTTOM_PADDING)) + } } } if (progressByTimeout) { @@ -472,13 +481,13 @@ private fun InviteView(rhId: Long?, connReqInvitation: String, contactConnection end = 16.dp ), click = { - ModalManager.start.showCustomModal { close -> + ModalManager.start.showCustomModal(keyboardCoversBar = false) { close -> val search = rememberSaveable { mutableStateOf("") } ModalView( { close() }, - endButtons = { - SearchTextField(Modifier.fillMaxWidth(), placeholder = stringResource(MR.strings.search_verb), alwaysVisible = true) { search.value = it } - }, + showSearch = true, + searchAlwaysVisible = true, + onSearchValueChanged = { search.value = it }, content = { ActiveProfilePicker( search = search, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt index 20a7ada3aa..28ad0fdb7b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt @@ -76,15 +76,9 @@ private fun CreateSimpleXAddressLayout( createAddress: () -> Unit, nextStep: () -> Unit, ) { - val handler = remember { AppBarHandler() } - CompositionLocalProvider( - LocalAppBarHandler provides handler - ) { + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { ModalView({}, showClose = false) { ColumnWithScrollBar( - Modifier - .fillMaxSize() - .themedBackground(), horizontalAlignment = Alignment.CenterHorizontally, ) { AppBarTitle(stringResource(MR.strings.simplex_address)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt index 9c7e2bdce7..98e8ec971d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt @@ -23,11 +23,7 @@ import dev.icerock.moko.resources.StringResource @Composable fun HowItWorks(user: User?, onboardingStage: SharedPreference? = null) { - ColumnWithScrollBar( - Modifier - .fillMaxWidth() - .padding(DEFAULT_PADDING), - ) { + ColumnWithScrollBar(Modifier.padding(DEFAULT_PADDING)) { AppBarTitle(stringResource(MR.strings.how_simplex_works), withPadding = false) ReadableText(MR.strings.many_people_asked_how_can_it_deliver) ReadableText(MR.strings.to_protect_privacy_simplex_has_ids_for_queues) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/LinkAMobileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/LinkAMobileView.kt index f0e34218d1..9e48f4b2bd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/LinkAMobileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/LinkAMobileView.kt @@ -7,21 +7,16 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatModel -import chat.simplex.common.platform.BackHandler import chat.simplex.common.platform.chatModel import chat.simplex.common.ui.theme.DEFAULT_PADDING -import chat.simplex.common.ui.theme.themedBackground import chat.simplex.common.views.helpers.* import chat.simplex.common.views.remote.AddingMobileDevice import chat.simplex.common.views.remote.DeviceNameField import chat.simplex.common.views.usersettings.PreferenceToggle import chat.simplex.res.MR -import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource @Composable @@ -59,34 +54,32 @@ private fun LinkAMobileLayout( staleQrCode: MutableState, updateDeviceName: (String) -> Unit, ) { - Column(Modifier.themedBackground()) { - CloseSheetBar(close = { - appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) - }) - BackHandler(onBack = { - appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) - }) - AppBarTitle(stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles)) - Row(Modifier.weight(1f).padding(horizontal = DEFAULT_PADDING * 2), verticalAlignment = Alignment.CenterVertically) { - Column( - Modifier.weight(0.3f), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - SectionView(generalGetString(MR.strings.this_device_name).uppercase()) { - DeviceNameField(deviceName.value ?: "") { updateDeviceName(it) } - SectionTextFooter(generalGetString(MR.strings.this_device_name_shared_with_mobile)) - PreferenceToggle(stringResource(MR.strings.multicast_discoverable_via_local_network), checked = remember { ChatModel.controller.appPrefs.offerRemoteMulticast.state }.value) { - ChatModel.controller.appPrefs.offerRemoteMulticast.set(it) + ModalView({ appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) }) { + Column(Modifier.fillMaxSize().padding(top = AppBarHeight * fontSizeSqrtMultiplier)) { + Box(Modifier.align(Alignment.CenterHorizontally)) { + AppBarTitle(stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles)) + } + Row(Modifier.weight(1f).padding(horizontal = DEFAULT_PADDING * 2), verticalAlignment = Alignment.CenterVertically) { + Column( + Modifier.weight(0.3f), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + SectionView(generalGetString(MR.strings.this_device_name).uppercase()) { + DeviceNameField(deviceName.value ?: "") { updateDeviceName(it) } + SectionTextFooter(generalGetString(MR.strings.this_device_name_shared_with_mobile)) + PreferenceToggle(stringResource(MR.strings.multicast_discoverable_via_local_network), checked = remember { ChatModel.controller.appPrefs.offerRemoteMulticast.state }.value) { + ChatModel.controller.appPrefs.offerRemoteMulticast.set(it) + } } } - } - Box(Modifier.weight(0.7f)) { - AddingMobileDevice(false, staleQrCode, connecting) { - // currentRemoteHost will be set instantly but remoteHosts may be delayed - if (chatModel.remoteHosts.isEmpty() && chatModel.currentRemoteHost.value == null) { - chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) - } else { - chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.OnboardingComplete) + Box(Modifier.weight(0.7f)) { + AddingMobileDevice(false, staleQrCode, connecting) { + // currentRemoteHost will be set instantly but remoteHosts may be delayed + if (chatModel.remoteHosts.isEmpty() && chatModel.currentRemoteHost.value == null) { + chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) + } else { + chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.OnboardingComplete) + } } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt index 1903b3cf81..e480d4330b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt @@ -25,16 +25,9 @@ import chat.simplex.res.MR @Composable fun SetNotificationsMode(m: ChatModel) { - val handler = remember { AppBarHandler() } - CompositionLocalProvider( - LocalAppBarHandler provides handler - ) { + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { ModalView({}, showClose = false) { - ColumnWithScrollBar( - modifier = Modifier - .fillMaxSize() - .themedBackground() - ) { + ColumnWithScrollBar(Modifier.themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer)) { Box(Modifier.align(Alignment.CenterHorizontally)) { AppBarTitle(stringResource(MR.strings.onboarding_notifications_mode_title)) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt index 858ca68af3..d0a3e601d2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt @@ -104,13 +104,10 @@ private fun SetupDatabasePassphraseLayout( onConfirmEncrypt: () -> Unit, nextStep: () -> Unit, ) { - val handler = remember { AppBarHandler() } - CompositionLocalProvider( - LocalAppBarHandler provides handler - ) { + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { ModalView({}, showClose = false) { ColumnWithScrollBar( - Modifier.fillMaxSize().themedBackground().padding(bottom = DEFAULT_PADDING * 2), + Modifier.themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer).padding(bottom = DEFAULT_PADDING * 2), horizontalAlignment = Alignment.CenterHorizontally, ) { AppBarTitle(stringResource(MR.strings.setup_database_passphrase)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt index c176950902..e43404cb07 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt @@ -31,15 +31,17 @@ import dev.icerock.moko.resources.StringResource @Composable fun SimpleXInfo(chatModel: ChatModel, onboarding: Boolean = true) { if (onboarding) { - ModalView({}, showClose = false, endButtons = { - IconButton({ ModalManager.fullscreen.showModal { HowItWorks(chatModel.currentUser.value, null) }}) { - Icon(painterResource(MR.images.ic_info), null, Modifier.size(28.dp), tint = MaterialTheme.colors.primary) + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { + ModalView({}, showClose = false, endButtons = { + IconButton({ ModalManager.fullscreen.showModal { HowItWorks(chatModel.currentUser.value, null) } }) { + Icon(painterResource(MR.images.ic_info), null, Modifier.size(28.dp), tint = MaterialTheme.colors.primary) + } + }) { + SimpleXInfoLayout( + user = chatModel.currentUser.value, + onboardingStage = chatModel.controller.appPrefs.onboardingStage + ) } - }) { - SimpleXInfoLayout( - user = chatModel.currentUser.value, - onboardingStage = chatModel.controller.appPrefs.onboardingStage - ) } } else { SimpleXInfoLayout( @@ -56,7 +58,6 @@ fun SimpleXInfoLayout( ) { ColumnWithScrollBar( Modifier - .fillMaxSize() .padding(horizontal = DEFAULT_PADDING), horizontalAlignment = Alignment.CenterHorizontally ) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt index 703f3b8915..bdbef3b654 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt @@ -119,11 +119,10 @@ fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) { ModalView(close = close) { ColumnWithScrollBar( Modifier - .fillMaxSize() .padding(horizontal = DEFAULT_PADDING), verticalArrangement = Arrangement.spacedBy(DEFAULT_PADDING.times(0.75f)) ) { - AppBarTitle(String.format(generalGetString(MR.strings.new_in_version), v.version), bottomPadding = DEFAULT_PADDING) + AppBarTitle(String.format(generalGetString(MR.strings.new_in_version), v.version), withPadding = false, bottomPadding = DEFAULT_PADDING) v.features.forEach { feature -> if (feature.show) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt index eb7fd7b6b5..c3eed3118e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt @@ -74,9 +74,7 @@ private fun ConnectDesktopLayout(deviceName: String, close: () -> Unit) { val sessionAddress = remember { mutableStateOf("") } val remoteCtrls = remember { mutableStateListOf() } val session = remember { chatModel.remoteCtrlSession }.value - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - ) { + ColumnWithScrollBar { val discovery = if (session == null) null else session.sessionState is UIRemoteCtrlSessionState.Searching if (discovery == true || (discovery == null && !showConnectScreen.value)) { SearchingDesktop(deviceName, remoteCtrls) @@ -408,9 +406,7 @@ private fun DesktopAddressView(sessionAddress: MutableState) { @Composable private fun LinkedDesktopsView(remoteCtrls: SnapshotStateList) { - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - ) { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.linked_desktops)) SectionView(stringResource(MR.strings.desktop_devices).uppercase()) { remoteCtrls.forEach { rc -> diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt index 92503f273e..e727b94781 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt @@ -89,7 +89,7 @@ fun ConnectMobileLayout( connectDesktop: () -> Unit, deleteHost: (RemoteHostInfo) -> Unit, ) { - ColumnWithScrollBar(Modifier.fillMaxWidth()) { + ColumnWithScrollBar { AppBarTitle(stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles)) SectionView(generalGetString(MR.strings.this_device_name).uppercase()) { DeviceNameField(deviceName.value ?: "") { updateDeviceName(it) } @@ -176,7 +176,15 @@ private fun ConnectMobileViewLayout( refreshQrCode: () -> Unit = {}, UnderQrLayout: @Composable () -> Unit = {}, ) { - ColumnWithScrollBar(Modifier.fillMaxWidth()) { + @Composable + fun ScrollableLayout(content: @Composable ColumnScope.() -> Unit) { + if (LocalAppBarHandler.current != null) { + ColumnWithScrollBar(content = content) + } else { + ColumnWithScrollBarNoAppBar(content = content) + } + } + ScrollableLayout { if (title != null) { AppBarTitle(title) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt index 35e0a3c6d8..5757b5d1f4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt @@ -202,10 +202,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U ) { val secondsLabel = stringResource(MR.strings.network_option_seconds_label) - ColumnWithScrollBar( - Modifier - .fillMaxWidth(), - ) { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.network_settings_title)) if (currentRemoteHost == null) { @@ -328,9 +325,7 @@ private fun SMPProxyModePicker( icon = painterResource(MR.images.ic_settings_ethernet), onSelected = { showModal { - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - ) { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.network_smp_proxy_mode_private_routing)) SectionViewSelectableCards(null, smpProxyMode, values, updateSMPProxyMode) } @@ -365,9 +360,7 @@ private fun SMPProxyFallbackPicker( enabled = enabled, onSelected = { showModal { - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - ) { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.network_smp_proxy_fallback_allow_downgrade)) SectionViewSelectableCards(null, smpProxyFallback, values, updateSMPProxyFallback) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt index f2cd26803b..b4fead6692 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt @@ -4,9 +4,11 @@ import SectionBottomSpacer import SectionDividerSpaced import SectionItemView import SectionItemViewSpaceBetween +import SectionItemViewWithoutMinPadding import SectionSpacer import SectionView import androidx.compose.foundation.* +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.grid.* import androidx.compose.foundation.shape.CircleShape @@ -39,6 +41,7 @@ import chat.simplex.common.views.chat.item.msgTailWidthDp import chat.simplex.res.MR import com.godaddy.android.colorpicker.ClassicColorPicker import com.godaddy.android.colorpicker.HsvColor +import dev.icerock.moko.resources.StringResource import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.datetime.Clock @@ -86,27 +89,114 @@ object AppearanceScope { } @Composable - fun MessageShapeSection() { - SectionView(stringResource(MR.strings.settings_section_title_message_shape).uppercase(), contentPadding = PaddingValues()) { - Row(modifier = Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING + 4.dp ) ,verticalAlignment = Alignment.CenterVertically) { - Text(stringResource(MR.strings.settings_message_shape_corner), color = colors.onBackground) - Spacer(Modifier.width(10.dp)) - Slider( - remember { appPreferences.chatItemRoundness.state }.value, - valueRange = 0f..1f, - steps = 20, - onValueChange = { - val diff = it % 0.05f - appPreferences.chatItemRoundness.set(it + (if (diff >= 0.025f) -diff + 0.05f else -diff)) - saveThemeToDatabase(null) - }, - colors = SliderDefaults.colors( - activeTickColor = Color.Transparent, - inactiveTickColor = Color.Transparent, + fun AppToolbarsSection() { + BoxWithConstraints { + SectionView(stringResource(MR.strings.appearance_app_toolbars).uppercase()) { + SectionItemViewWithoutMinPadding { + Box(Modifier.weight(1f)) { + Text( + stringResource(MR.strings.appearance_in_app_bars_alpha), + Modifier.clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) { + appPrefs.inAppBarsAlpha.set(appPrefs.inAppBarsDefaultAlpha) + }, + maxLines = 1 + ) + } + Spacer(Modifier.padding(end = 10.dp)) + Slider( + (1 - remember { appPrefs.inAppBarsAlpha.state }.value).coerceIn(0f, 0.5f), + onValueChange = { + val diff = it % 0.025f + appPrefs.inAppBarsAlpha.set(1f - (String.format(Locale.US, "%.3f", it + (if (diff >= 0.0125f) -diff + 0.025f else -diff)).toFloatOrNull() ?: 1f)) + }, + Modifier.widthIn(max = (this@BoxWithConstraints.maxWidth - DEFAULT_PADDING * 2) * 0.618f), + valueRange = 0f..0.5f, + steps = 21, + colors = SliderDefaults.colors( + activeTickColor = Color.Transparent, + inactiveTickColor = Color.Transparent, + ) ) - ) + } + // In Android in OneHandUI there is a problem with setting initial value of blur if it was 0 before entering the screen. + // So doing in two steps works ok + fun saveBlur(value: Int) { + val oneHandUI = appPrefs.oneHandUI.get() + val pref = appPrefs.appearanceBarsBlurRadius + if (appPlatform.isAndroid && oneHandUI && pref.get() == 0) { + pref.set(if (value > 2) value - 1 else value + 1) + withApi { + delay(50) + pref.set(value) + } + } else { + pref.set(value) + } + } + val blur = remember { appPrefs.appearanceBarsBlurRadius.state } + if (appPrefs.deviceSupportsBlur || blur.value > 0) { + SectionItemViewWithoutMinPadding { + Box(Modifier.weight(1f)) { + Text( + stringResource(MR.strings.appearance_bars_blur_radius), + Modifier.clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) { + saveBlur(50) + }, + maxLines = 1 + ) + } + Spacer(Modifier.padding(end = 10.dp)) + Slider( + blur.value.toFloat() / 100f, + onValueChange = { + val diff = it % 0.05f + saveBlur(((String.format(Locale.US, "%.2f", it + (if (diff >= 0.025f) -diff + 0.05f else -diff)).toFloatOrNull() ?: 1f) * 100).toInt()) + }, + Modifier.widthIn(max = (this@BoxWithConstraints.maxWidth - DEFAULT_PADDING * 2) * 0.618f), + valueRange = 0f..1f, + steps = 21, + colors = SliderDefaults.colors( + activeTickColor = Color.Transparent, + inactiveTickColor = Color.Transparent, + ) + ) + } + } + } + } + } + + @Composable + fun MessageShapeSection() { + BoxWithConstraints { + SectionView(stringResource(MR.strings.settings_section_title_message_shape).uppercase()) { + SectionItemViewWithoutMinPadding { + Text(stringResource(MR.strings.settings_message_shape_corner), Modifier.weight(1f)) + Spacer(Modifier.width(10.dp)) + Slider( + remember { appPreferences.chatItemRoundness.state }.value, + onValueChange = { + val diff = it % 0.05f + appPreferences.chatItemRoundness.set(it + (if (diff >= 0.025f) -diff + 0.05f else -diff)) + saveThemeToDatabase(null) + }, + Modifier.widthIn(max = (this@BoxWithConstraints.maxWidth - DEFAULT_PADDING * 2) * 0.618f), + valueRange = 0f..1f, + steps = 20, + colors = SliderDefaults.colors( + activeTickColor = Color.Transparent, + inactiveTickColor = Color.Transparent, + ) + ) + } + SettingsPreferenceItem(icon = null, stringResource(MR.strings.settings_message_shape_tail), appPreferences.chatItemTail) } - SettingsPreferenceItem(icon = null, stringResource(MR.strings.settings_message_shape_tail), appPreferences.chatItemTail) } } @@ -115,7 +205,7 @@ object AppearanceScope { val localFontScale = remember { mutableStateOf(appPrefs.fontScale.get()) } SectionView(stringResource(MR.strings.appearance_font_size).uppercase(), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { Row(Modifier.padding(top = 10.dp), verticalAlignment = Alignment.CenterVertically) { - Box(Modifier.size(60.dp) + Box(Modifier.size(50.dp) .background(MaterialTheme.colors.surface, RoundedCornerShape(percent = 22)) .clip(RoundedCornerShape(percent = 22)) .clickable { @@ -129,7 +219,7 @@ object AppearanceScope { Text("Aa", color = if (localFontScale.value == 1f) MaterialTheme.colors.primary else MaterialTheme.colors.onBackground) } } - Spacer(Modifier.width(10.dp)) + Spacer(Modifier.width(15.dp)) // Text("${(localFontScale.value * 100).roundToInt()}%", Modifier.width(70.dp), textAlign = TextAlign.Center, fontSize = 12.sp) if (appPlatform.isAndroid) { Slider( @@ -185,7 +275,7 @@ object AppearanceScope { Column(Modifier .drawWithCache { if (wallpaperImage != null && wallpaperType != null && backgroundColor != null && tintColor != null) { - chatViewBackground(wallpaperImage, wallpaperType, backgroundColor, tintColor) + chatViewBackground(wallpaperImage, wallpaperType, backgroundColor, tintColor, null, null) } else { onDrawBehind { drawRect(themeBackgroundColor) @@ -514,9 +604,7 @@ object AppearanceScope { @Composable fun CustomizeThemeView(onChooseType: (WallpaperType?) -> Unit) { - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - ) { + ColumnWithScrollBar { val currentTheme by CurrentColors.collectAsState() AppBarTitle(stringResource(MR.strings.customize_theme_title)) @@ -909,10 +997,7 @@ object AppearanceScope { currentColors: () -> ThemeManager.ActiveTheme, onColorChange: (Color?) -> Unit, ) { - ColumnWithScrollBar( - Modifier - .fillMaxWidth() - ) { + ColumnWithScrollBar(Modifier.imePadding()) { AppBarTitle(name.text) val supportedLiveChange = name in listOf(ThemeColor.SECONDARY, ThemeColor.BACKGROUND, ThemeColor.SURFACE, ThemeColor.RECEIVED_MESSAGE, ThemeColor.SENT_MESSAGE, ThemeColor.SENT_QUOTE, ThemeColor.WALLPAPER_BACKGROUND, ThemeColor.WALLPAPER_TINT) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/CallSettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/CallSettings.kt index 468a192f09..cb36e4ae1a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/CallSettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/CallSettings.kt @@ -36,7 +36,7 @@ fun CallSettingsLayout( callOnLockScreen: SharedPreference, editIceServers: () -> Unit, ) { - ColumnWithScrollBar(Modifier.fillMaxWidth()) { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.your_calls)) val lockCallState = remember { mutableStateOf(callOnLockScreen.get()) } SectionView(stringResource(MR.strings.settings_section_title_settings)) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt index 2123d98f41..87770e9ffd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt @@ -22,12 +22,10 @@ import chat.simplex.common.views.helpers.* import chat.simplex.res.MR @Composable -fun DeveloperView( - m: ChatModel, - showCustomModal: (@Composable ModalData.(ChatModel, () -> Unit) -> Unit) -> (() -> Unit), - withAuth: (title: String, desc: String, block: () -> Unit) -> Unit +fun DeveloperView(withAuth: (title: String, desc: String, block: () -> Unit) -> Unit ) { - ColumnWithScrollBar(Modifier.fillMaxWidth()) { + val m = chatModel + ColumnWithScrollBar { val uriHandler = LocalUriHandler.current AppBarTitle(stringResource(MR.strings.settings_developer_tools)) val developerTools = m.controller.appPrefs.developerTools @@ -35,7 +33,7 @@ fun DeveloperView( val unchangedHints = mutableStateOf(unchangedHintPreferences()) SectionView { InstallTerminalAppItem(uriHandler) - ChatConsoleItem { withAuth(generalGetString(MR.strings.auth_open_chat_console), generalGetString(MR.strings.auth_log_in_using_credential), showCustomModal { it, close -> TerminalView(false, close) }) } + ChatConsoleItem { withAuth(generalGetString(MR.strings.auth_open_chat_console), generalGetString(MR.strings.auth_log_in_using_credential)) { ModalManager.start.showModalCloseable { TerminalView(false) } } } ResetHintsItem(unchangedHints) SettingsPreferenceItem(painterResource(MR.images.ic_code), stringResource(MR.strings.show_developer_options), developerTools) SectionTextFooter( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HelpView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HelpView.kt index c2bf69bc0e..aaaef31583 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HelpView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HelpView.kt @@ -21,11 +21,7 @@ fun HelpView(userDisplayName: String) { @Composable fun HelpLayout(userDisplayName: String) { - ColumnWithScrollBar( - Modifier - .fillMaxWidth() - .padding(horizontal = DEFAULT_PADDING), - ){ + ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_PADDING)){ AppBarTitle(String.format(stringResource(MR.strings.personal_welcome), userDisplayName), withPadding = false) ChatHelpView() } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt index e5116f9149..55bd796a3b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt @@ -56,10 +56,7 @@ private fun HiddenProfileLayout( user: User, saveProfilePassword: (String) -> Unit ) { - ColumnWithScrollBar( - Modifier - .fillMaxWidth(), - ) { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.hide_profile)) SectionView(contentPadding = PaddingValues(start = 8.dp, end = DEFAULT_PADDING)) { UserProfileRow(user) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt index dc3def3884..2c4870b121 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt @@ -109,7 +109,7 @@ fun NetworkAndServersView() { toggleSocksProxy: (Boolean) -> Unit, ) { val m = chatModel - ColumnWithScrollBar(Modifier.fillMaxWidth()) { + ColumnWithScrollBar { val showModal = { it: @Composable ModalData.() -> Unit -> ModalManager.start.showModal(content = it) } val showCustomModal = { it: @Composable (close: () -> Unit) -> Unit -> ModalManager.start.showCustomModal { close -> it(close) }} @@ -304,10 +304,7 @@ fun SocksProxySettings( } }, ) { - ColumnWithScrollBar( - Modifier - .fillMaxWidth() - ) { + ColumnWithScrollBar { AppBarTitle(generalGetString(MR.strings.network_socks_proxy_settings)) SectionView(stringResource(MR.strings.network_socks_proxy).uppercase()) { Column(Modifier.padding(horizontal = DEFAULT_PADDING)) { @@ -479,9 +476,7 @@ fun SessionModePicker( icon = painterResource(MR.images.ic_safety_divider), onSelected = { showModal { - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - ) { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.network_session_mode_transport_isolation)) SectionViewSelectable(null, sessionMode, values, updateSessionMode) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt index 515d73a426..60bde83c17 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt @@ -56,9 +56,7 @@ fun NotificationsSettingsLayout( val modes = remember { notificationModes() } val previewModes = remember { notificationPreviewModes() } - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - ) { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.notifications)) SectionView(null) { if (appPlatform == AppPlatform.ANDROID) { @@ -90,9 +88,7 @@ fun NotificationsModeView( onNotificationsModeSelected: (NotificationsMode) -> Unit, ) { val modes = remember { notificationModes() } - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - ) { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.settings_notifications_mode_title).lowercase().capitalize(Locale.current)) SectionViewSelectable(null, notificationsMode, modes, onNotificationsModeSelected) } @@ -104,9 +100,7 @@ fun NotificationPreviewView( onNotificationPreviewModeSelected: (NotificationPreviewMode) -> Unit, ) { val previewModes = remember { notificationPreviewModes() } - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - ) { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.settings_notification_preview_title)) SectionViewSelectable(null, notificationPreviewMode, previewModes, onNotificationPreviewModeSelected) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt index 96a0bdcda3..bc27773ca6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt @@ -66,9 +66,7 @@ private fun PreferencesLayout( reset: () -> Unit, savePrefs: () -> Unit, ) { - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - ) { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.your_preferences)) val timedMessages = remember(preferences) { mutableStateOf(preferences.timedMessages.allow) } TimedMessagesFeatureSection(timedMessages) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt index abf318390f..9ec2d29843 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt @@ -55,9 +55,7 @@ fun PrivacySettingsView( showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), setPerformLA: (Boolean) -> Unit ) { - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - ) { + ColumnWithScrollBar { val simplexLinkMode = chatModel.controller.appPrefs.simplexLinkMode AppBarTitle(stringResource(MR.strings.your_privacy)) PrivacyDeviceSection(showSettingsModal, setPerformLA) @@ -514,9 +512,7 @@ fun SimplexLockView( } } - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - ) { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.chat_lock)) SectionView { EnableLock(remember { appPrefs.performLA.state }) { performLAToggle -> diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt index 3a1a1cb8f3..be566e6c5a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt @@ -75,10 +75,7 @@ private fun ProtocolServerLayout( onUpdate: (ServerCfg) -> Unit, onDelete: () -> Unit, ) { - ColumnWithScrollBar( - Modifier - .fillMaxWidth() - ) { + ColumnWithScrollBar { AppBarTitle(stringResource(if (server.preset) MR.strings.smp_servers_preset_server else MR.strings.smp_servers_your_server)) if (server.preset) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt index 5d5f1d039a..f5e3cda2c7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt @@ -192,10 +192,7 @@ private fun ProtocolServersLayout( saveSMPServers: () -> Unit, showServer: (ServerCfg) -> Unit, ) { - ColumnWithScrollBar( - Modifier - .fillMaxWidth() - ) { + ColumnWithScrollBar { AppBarTitle(stringResource(if (serverProtocol == ServerProtocol.SMP) MR.strings.your_SMP_servers else MR.strings.your_XFTP_servers)) val configuredServers = servers.filter { it.preset || it.enabled } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt index 7c2c578d6a..966f44cac7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt @@ -7,6 +7,7 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp import chat.simplex.common.model.ServerAddress.Companion.parseServerAddress import chat.simplex.common.model.ServerCfg +import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.common.ui.theme.DEFAULT_PADDING import chat.simplex.common.views.helpers.* import chat.simplex.common.views.newchat.QRCodeScanner @@ -17,10 +18,7 @@ expect fun ScanProtocolServer(rhId: Long?, onNext: (ServerCfg) -> Unit) @Composable fun ScanProtocolServerLayout(rhId: Long?, onNext: (ServerCfg) -> Unit) { - Column( - Modifier - .fillMaxSize() - ) { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.smp_servers_scan_qr)) QRCodeScanner { text -> val res = parseServerAddress(text) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt index 0229e7da2a..ef4acdeac6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt @@ -74,10 +74,7 @@ private fun SetDeliveryReceiptsLayout( userCount: Int, ) { Box(Modifier.padding(top = DEFAULT_PADDING)) { - ColumnWithScrollBar( - Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - ) { + ColumnWithScrollBar(horizontalAlignment = Alignment.CenterHorizontally) { AppBarTitle(stringResource(MR.strings.delivery_receipts_title)) Spacer(Modifier.weight(1f)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt index bb4a0b61b0..78c5e3b212 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt @@ -39,7 +39,6 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, close: ( val user = chatModel.currentUser.value val stopped = chatModel.chatRunning.value == false SettingsLayout( - profile = user?.profile, stopped, chatModel.chatDbEncrypted.value == true, remember { chatModel.controller.appPrefs.storeDBPassphrase.state }.value, @@ -53,9 +52,9 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, close: ( val search = rememberSaveable { mutableStateOf("") } ModalView( { close() }, - endButtons = { - SearchTextField(Modifier.fillMaxWidth(), placeholder = stringResource(MR.strings.search_verb), alwaysVisible = true) { search.value = it } - }, + showSearch = true, + searchAlwaysVisible = true, + onSearchValueChanged = { search.value = it }, content = { modalView(chatModel, search) }) } }, @@ -80,7 +79,6 @@ val simplexTeamUri = @Composable fun SettingsLayout( - profile: LocalProfile?, stopped: Boolean, encrypted: Boolean, passphraseSaved: Boolean, @@ -94,18 +92,12 @@ fun SettingsLayout( showVersion: () -> Unit, withAuth: (title: String, desc: String, block: () -> Unit) -> Unit, ) { - val scope = rememberCoroutineScope() val view = LocalMultiplatformView() LaunchedEffect(Unit) { hideKeyboard(view) } - val theme = CurrentColors.collectAsState() val uriHandler = LocalUriHandler.current - ColumnWithScrollBar( - Modifier - .fillMaxSize() - .themedBackground(theme.value.base) - ) { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.your_settings)) SectionView(stringResource(MR.strings.settings_section_title_settings)) { @@ -142,7 +134,7 @@ fun SettingsLayout( } SectionDividerSpaced() - SettingsSectionApp(showSettingsModal, showCustomModal, showVersion, withAuth) + SettingsSectionApp(showSettingsModal, showVersion, withAuth) SectionBottomSpacer() } } @@ -150,7 +142,6 @@ fun SettingsLayout( @Composable expect fun SettingsSectionApp( showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), - showCustomModal: (@Composable ModalData.(ChatModel, () -> Unit) -> Unit) -> (() -> Unit), showVersion: () -> Unit, withAuth: (title: String, desc: String, block: () -> Unit) -> Unit ) @@ -488,7 +479,6 @@ private fun runAuth(title: String, desc: String, onFinish: (success: Boolean) -> fun PreviewSettingsLayout() { SimpleXTheme { SettingsLayout( - profile = LocalProfile.sampleData, stopped = false, encrypted = false, passphraseSaved = false, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressLearnMore.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressLearnMore.kt index 1ac0cd7ecd..6d6b72d2d1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressLearnMore.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressLearnMore.kt @@ -13,11 +13,7 @@ import chat.simplex.res.MR @Composable fun UserAddressLearnMore() { - ColumnWithScrollBar( - Modifier - .fillMaxHeight() - .padding(horizontal = DEFAULT_PADDING) - ) { + ColumnWithScrollBar(Modifier .padding(horizontal = DEFAULT_PADDING)) { AppBarTitle(stringResource(MR.strings.simplex_address), withPadding = false) ReadableText(MR.strings.you_can_share_your_address) ReadableText(MR.strings.you_wont_lose_your_contacts_if_delete_address) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt index 10acaffe1a..90122bd29d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt @@ -71,10 +71,8 @@ fun UserProfileLayout( val keyboardState by getKeyboardState() var savedKeyboardState by remember { mutableStateOf(keyboardState) } val focusRequester = remember { FocusRequester() } - ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { ModalBottomSheetLayout( scrimColor = Color.Black.copy(alpha = 0.12F), - modifier = Modifier.navigationBarsWithImePadding(), sheetContent = { GetImageBottomSheet( chosenImage, @@ -90,7 +88,6 @@ fun UserProfileLayout( displayName.value == profile.displayName && fullName.value == profile.fullName && profile.image == profileImage.value - val closeWithAlert = { if (dataUnchanged || !canSaveProfile(displayName.value, profile)) { close() @@ -103,7 +100,7 @@ fun UserProfileLayout( Modifier .padding(horizontal = DEFAULT_PADDING), ) { - AppBarTitle(stringResource(MR.strings.your_current_profile)) + AppBarTitle(stringResource(MR.strings.your_current_profile), withPadding = false) ReadableText(generalGetString(MR.strings.your_profile_is_stored_on_device_and_shared_only_with_contacts_simplex_cannot_see_it), TextAlign.Center) Column( Modifier @@ -170,7 +167,6 @@ fun UserProfileLayout( } } } - } } @Composable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt index dcf8351166..fa9e709d4b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt @@ -151,10 +151,7 @@ private fun UserProfilesLayout( unmuteUser: (User) -> Unit, showHiddenProfile: (User) -> Unit, ) { - ColumnWithScrollBar( - Modifier - .fillMaxWidth() - ) { + ColumnWithScrollBar { if (profileHidden.value) { SectionView { SettingsActionItem(painterResource(MR.images.ic_lock_open_right), stringResource(MR.strings.enter_password_to_show), click = { @@ -252,10 +249,7 @@ enum class UserProfileAction { @Composable private fun ProfileActionView(action: UserProfileAction, user: User, doAction: (String) -> Unit) { - ColumnWithScrollBar( - Modifier - .fillMaxWidth() - ) { + ColumnWithScrollBar { val actionPassword = rememberSaveable { mutableStateOf("") } val passwordValid by remember { derivedStateOf { actionPassword.value == actionPassword.value.trim() } } val actionEnabled by remember { derivedStateOf { actionPassword.value != "" && passwordValid && correctPassword(user, actionPassword.value) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/VersionInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/VersionInfoView.kt index 06a4762210..52addd146b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/VersionInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/VersionInfoView.kt @@ -1,6 +1,5 @@ package chat.simplex.common.views.usersettings -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -8,6 +7,7 @@ import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.stringResource import chat.simplex.common.BuildConfigCommon import chat.simplex.common.model.CoreVersionInfo +import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.common.platform.appPlatform import chat.simplex.common.ui.theme.DEFAULT_PADDING import chat.simplex.common.views.helpers.AppBarTitle @@ -15,7 +15,7 @@ import chat.simplex.res.MR @Composable fun VersionInfoView(info: CoreVersionInfo) { - Column( + ColumnWithScrollBar( Modifier.padding(horizontal = DEFAULT_PADDING), ) { AppBarTitle(stringResource(MR.strings.app_version_title), withPadding = false) diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index fd6988d5e8..1ab7e3aed2 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1755,6 +1755,9 @@ Remove image Font size Zoom + App toolbars + Transparency + Blur System mode diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt index 1fb739946c..25d85a6b7d 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt @@ -198,12 +198,17 @@ private fun ApplicationScope.AppWindow(closedByError: MutableState) { val cWindowState = rememberWindowState(placement = WindowPlacement.Floating, width = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier, height = 768.dp) Window(state = cWindowState, onCloseRequest = { hiddenUntilRestart = true }, title = stringResource(MR.strings.chat_console)) { + val data = remember { ModalData() } SimpleXTheme { - TerminalView(true) { hiddenUntilRestart = true } - ModalManager.floatingTerminal.showInView() - DisposableEffect(Unit) { - onDispose { - ModalManager.floatingTerminal.closeModals() + CompositionLocalProvider(LocalAppBarHandler provides data.appBarHandler) { + ModalView({ hiddenUntilRestart = true }) { + TerminalView(true) + } + ModalManager.floatingTerminal.showInView() + DisposableEffect(Unit) { + onDispose { + ModalManager.floatingTerminal.closeModals() + } } } } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Modifier.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Modifier.desktop.kt index 150885cbc8..b090e301d5 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Modifier.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Modifier.desktop.kt @@ -15,17 +15,6 @@ import java.awt.image.BufferedImage import java.io.File import java.net.URI -actual fun Modifier.navigationBarsWithImePadding(): Modifier = this - -@Composable -actual fun ProvideWindowInsets( - consumeWindowInsets: Boolean, - windowInsetsAnimationsEnabled: Boolean, - content: @Composable () -> Unit -) { - content() -} - @Composable actual fun Modifier.desktopOnExternalDrag( enabled: Boolean, diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt index e37e99f3e9..e7bcf4802a 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt @@ -1,10 +1,12 @@ package chat.simplex.common.platform +import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.* +import androidx.compose.material.TextFieldDefaults.textFieldWithLabelPadding import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -113,7 +115,9 @@ actual fun PlatformTextField( autoCorrectEnabled = true ), modifier = Modifier - .padding(vertical = 4.dp) + .padding(start = startPadding, end = endPadding) + .offset(y = (-5).dp) + .fillMaxWidth() .focusRequester(focusRequester) .onPreviewKeyEvent { if ((it.key == Key.Enter || it.key == Key.NumPadEnter) && it.type == KeyEventType.KeyDown) { @@ -177,30 +181,24 @@ actual fun PlatformTextField( }, cursorBrush = SolidColor(MaterialTheme.colors.secondary), decorationBox = { innerTextField -> - Row(verticalAlignment = Alignment.Bottom) { CompositionLocalProvider( LocalLayoutDirection provides if (isRtlByCharacters) LayoutDirection.Rtl else LocalLayoutDirection.current ) { - Column(Modifier.weight(1f).padding(start = startPadding, end = endPadding)) { - Spacer(Modifier.height(8.dp)) - TextFieldDefaults.TextFieldDecorationBox( - value = textFieldValue.text, - innerTextField = innerTextField, - placeholder = { Text(placeholder, style = textStyle.value.copy(color = MaterialTheme.colors.secondary)) }, - singleLine = false, - enabled = true, - isError = false, - trailingIcon = null, - interactionSource = remember { MutableInteractionSource() }, - contentPadding = PaddingValues(), - visualTransformation = VisualTransformation.None, - colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.Unspecified) - ) - Spacer(Modifier.height(10.dp)) - } + TextFieldDefaults.TextFieldDecorationBox( + value = textFieldValue.text, + innerTextField = innerTextField, + placeholder = { Text(placeholder, style = textStyle.value.copy(color = MaterialTheme.colors.secondary)) }, + singleLine = false, + enabled = true, + isError = false, + trailingIcon = null, + interactionSource = remember { MutableInteractionSource() }, + contentPadding = textFieldWithLabelPadding(start = 0.dp, end = 0.dp), + visualTransformation = VisualTransformation.None, + colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.Unspecified) + ) } - } - }, + } ) showDeleteTextButton.value = cs.message.split("\n").size >= 4 && !cs.inProgress if (composeState.value.preview is ComposePreview.VoicePreview) { diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt index e6b26f9290..a294f1cc60 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt @@ -15,11 +15,14 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.pointer.* +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import chat.simplex.common.model.ChatController.appPrefs +import chat.simplex.common.ui.theme.DEFAULT_PADDING import chat.simplex.common.views.helpers.* import kotlinx.coroutines.* import kotlinx.coroutines.flow.filter -import kotlin.math.absoluteValue +import kotlin.math.* @Composable actual fun LazyColumnWithScrollBar( @@ -31,6 +34,78 @@ actual fun LazyColumnWithScrollBar( horizontalAlignment: Alignment.Horizontal, flingBehavior: FlingBehavior, userScrollEnabled: Boolean, + additionalBarOffset: State?, + fillMaxSize: Boolean, + content: LazyListScope.() -> Unit +) { + val handler = LocalAppBarHandler.current + require(handler != null) { "Using LazyColumnWithScrollBar and without AppBarHandler is an error. Use LazyColumnWithScrollBarNoAppBar instead" } + + val scope = rememberCoroutineScope() + val scrollBarAlpha = remember { Animatable(0f) } + val scrollJob: MutableState = remember { mutableStateOf(Job()) } + val scrollModifier = remember { + Modifier + .pointerInput(Unit) { + detectCursorMove { + scope.launch { + scrollBarAlpha.animateTo(1f) + } + scrollJob.value.cancel() + scrollJob.value = scope.launch { + delay(1000L) + scrollBarAlpha.animateTo(0f) + } + } + } + } + val state = state ?: handler.listState + val connection = handler.connection + // When scroll bar is dragging, there is no scroll event in nested scroll modifier. So, listen for changes on lazy column state + // (only first visible row is useful because LazyColumn doesn't have absolute scroll position, only relative to row) + val scrollBarDraggingState = remember { mutableStateOf(false) } + LaunchedEffect(Unit) { + if (reverseLayout) { + snapshotFlow { state.layoutInfo.visibleItemsInfo.lastOrNull()?.offset ?: 0 } + .collect { scrollPosition -> + connection.appBarOffset = if (state.layoutInfo.visibleItemsInfo.lastOrNull()?.index == state.layoutInfo.totalItemsCount - 1) { + state.layoutInfo.viewportEndOffset - scrollPosition.toFloat() - state.layoutInfo.afterContentPadding + } else { + // show always when last item is not visible + -1000f + } + //Log.d(TAG, "Scrolling position changed from $offset to ${connection.appBarOffset}") + } + } else { + snapshotFlow { state.firstVisibleItemScrollOffset } + .filter { state.firstVisibleItemIndex == 0 } + .collect { scrollPosition -> + val offset = connection.appBarOffset + if ((offset + scrollPosition + state.layoutInfo.afterContentPadding).absoluteValue > 1 || scrollBarDraggingState.value) { + connection.appBarOffset = -scrollPosition.toFloat() + //Log.d(TAG, "Scrolling position changed from $offset to ${connection.appBarOffset}") + } + } + } + } + val modifier = if (fillMaxSize) Modifier.fillMaxSize().then(modifier) else modifier + Box(Modifier.copyViewToAppBar(remember { appPrefs.appearanceBarsBlurRadius.state }.value, LocalAppBarHandler.current?.graphicsLayer).nestedScroll(connection)) { + LazyColumn(modifier.then(scrollModifier), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content) + ScrollBar(reverseLayout, state, scrollBarAlpha, scrollJob, scrollBarDraggingState, additionalBarOffset) + } +} + +@Composable +actual fun LazyColumnWithScrollBarNoAppBar( + modifier: Modifier, + state: LazyListState?, + contentPadding: PaddingValues, + reverseLayout: Boolean, + verticalArrangement: Arrangement.Vertical, + horizontalAlignment: Alignment.Horizontal, + flingBehavior: FlingBehavior, + userScrollEnabled: Boolean, + additionalBarOffset: State?, content: LazyListScope.() -> Unit ) { val scope = rememberCoroutineScope() @@ -51,32 +126,110 @@ actual fun LazyColumnWithScrollBar( } } } - val state = state ?: LocalAppBarHandler.current?.listState ?: rememberLazyListState() - val connection = LocalAppBarHandler.current?.connection + val state = state ?: rememberLazyListState() // When scroll bar is dragging, there is no scroll event in nested scroll modifier. So, listen for changes on lazy column state // (only first visible row is useful because LazyColumn doesn't have absolute scroll position, only relative to row) val scrollBarDraggingState = remember { mutableStateOf(false) } - LaunchedEffect(Unit) { - snapshotFlow { state.firstVisibleItemScrollOffset } - .filter { state.firstVisibleItemIndex == 0 } - .collect { scrollPosition -> - val offset = connection?.appBarOffset - if (offset != null && ((offset + scrollPosition).absoluteValue > 1 || scrollBarDraggingState.value)) { - connection.appBarOffset = -scrollPosition.toFloat() -// Log.d(TAG, "Scrolling position changed from $offset to ${connection.appBarOffset}") - } - } - } - Box(if (connection != null) Modifier.nestedScroll(connection) else Modifier) { + Box { LazyColumn(modifier.then(scrollModifier), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content) - Box(Modifier.fillMaxSize(), contentAlignment = Alignment.CenterEnd) { - DesktopScrollBar(rememberScrollbarAdapter(state), Modifier.fillMaxHeight(), scrollBarAlpha, scrollJob, reverseLayout, scrollBarDraggingState) - } + ScrollBar(reverseLayout, state, scrollBarAlpha, scrollJob, scrollBarDraggingState, additionalBarOffset) + } +} + +@Composable +private fun ScrollBar( + reverseLayout: Boolean, + state: LazyListState, + scrollBarAlpha: Animatable, + scrollJob: MutableState, + scrollBarDraggingState: MutableState, + additionalBarHeight: State? +) { + val oneHandUI = remember { appPrefs.oneHandUI.state } + val padding = if (additionalBarHeight != null) { + PaddingValues(top = if (oneHandUI.value) 0.dp else AppBarHeight * fontSizeSqrtMultiplier, bottom = additionalBarHeight.value) + } else if (reverseLayout) { + PaddingValues(bottom = AppBarHeight * fontSizeSqrtMultiplier) + } else { + PaddingValues(top = if (oneHandUI.value) 0.dp else AppBarHeight * fontSizeSqrtMultiplier) + } + Box(Modifier.fillMaxSize().padding(padding), contentAlignment = Alignment.CenterEnd) { + DesktopScrollBar(rememberScrollbarAdapter(state), Modifier.fillMaxHeight(), scrollBarAlpha, scrollJob, reverseLayout, scrollBarDraggingState) } } @Composable actual fun ColumnWithScrollBar( + modifier: Modifier, + verticalArrangement: Arrangement.Vertical, + horizontalAlignment: Alignment.Horizontal, + state: ScrollState?, + maxIntrinsicSize: Boolean, + fillMaxSize: Boolean, + content: @Composable() (ColumnScope.() -> Unit) +) { + val handler = LocalAppBarHandler.current + require(handler != null) { "Using ColumnWithScrollBar and without AppBarHandler is an error. Use ColumnWithScrollBarNoAppBar instead" } + + val scope = rememberCoroutineScope() + val scrollBarAlpha = remember { Animatable(0f) } + val scrollJob: MutableState = remember { mutableStateOf(Job()) } + val scrollModifier = remember { + Modifier + .pointerInput(Unit) { + detectCursorMove { + scope.launch { + scrollBarAlpha.animateTo(1f) + } + scrollJob.value.cancel() + scrollJob.value = scope.launch { + delay(1000L) + scrollBarAlpha.animateTo(0f) + } + } + } + } + val state = state ?: handler.scrollState + val connection = handler.connection + // When scroll bar is dragging, there is no scroll event in nested scroll modifier. So, listen for changes on column state + // (exact scroll position is available but in Int, not Float) + val scrollBarDraggingState = remember { mutableStateOf(false) } + LaunchedEffect(Unit) { + snapshotFlow { state.value } + .collect { scrollPosition -> + val offset = connection.appBarOffset + if ((offset + scrollPosition).absoluteValue > 1 || scrollBarDraggingState.value) { + connection.appBarOffset = -scrollPosition.toFloat() +// Log.d(TAG, "Scrolling position changed from $offset to ${connection.appBarOffset}") + } + } + } + val modifier = if (fillMaxSize) Modifier.fillMaxSize().then(modifier) else modifier + Box(Modifier.nestedScroll(connection)) { + val oneHandUI = remember { appPrefs.oneHandUI.state } + val padding = if (oneHandUI.value) PaddingValues(bottom = AppBarHeight * fontSizeSqrtMultiplier) else PaddingValues(top = AppBarHeight * fontSizeSqrtMultiplier) + Column( + if (maxIntrinsicSize) { + modifier.copyViewToAppBar(remember { appPrefs.appearanceBarsBlurRadius.state }.value, LocalAppBarHandler.current?.graphicsLayer).verticalScroll(state).height(IntrinsicSize.Max).then(scrollModifier) + } else { + modifier.then(scrollModifier).copyViewToAppBar(remember { appPrefs.appearanceBarsBlurRadius.state }.value, LocalAppBarHandler.current?.graphicsLayer).verticalScroll(state) + }, + verticalArrangement, horizontalAlignment + ) { + Spacer(if (oneHandUI.value) Modifier.padding(top = DEFAULT_PADDING + 5.dp) else Modifier.padding(padding)) + content() + if (oneHandUI.value) { + Spacer(Modifier.padding(padding)) + } + } + Box(Modifier.fillMaxSize().padding(padding), contentAlignment = Alignment.CenterEnd) { + DesktopScrollBar(rememberScrollbarAdapter(state), Modifier.fillMaxHeight(), scrollBarAlpha, scrollJob, false, scrollBarDraggingState) + } + } +} + +@Composable +actual fun ColumnWithScrollBarNoAppBar( modifier: Modifier, verticalArrangement: Arrangement.Vertical, horizontalAlignment: Alignment.Horizontal, @@ -102,29 +255,20 @@ actual fun ColumnWithScrollBar( } } } - val state = state ?: LocalAppBarHandler.current?.scrollState ?: rememberScrollState() - val connection = LocalAppBarHandler.current?.connection + val state = state ?: rememberScrollState() // When scroll bar is dragging, there is no scroll event in nested scroll modifier. So, listen for changes on column state // (exact scroll position is available but in Int, not Float) val scrollBarDraggingState = remember { mutableStateOf(false) } - LaunchedEffect(Unit) { - snapshotFlow { state.value } - .collect { scrollPosition -> - val offset = connection?.appBarOffset - if (offset != null && ((offset + scrollPosition).absoluteValue > 1 || scrollBarDraggingState.value)) { - connection.appBarOffset = -scrollPosition.toFloat() -// Log.d(TAG, "Scrolling position changed from $offset to ${connection.appBarOffset}") - } - } - } - Box(if (connection != null) Modifier.nestedScroll(connection) else Modifier) { + Box { Column( if (maxIntrinsicSize) { modifier.verticalScroll(state).height(IntrinsicSize.Max).then(scrollModifier) } else { - modifier.verticalScroll(state).then(scrollModifier) + modifier.then(scrollModifier).verticalScroll(state) }, - verticalArrangement, horizontalAlignment, content) + verticalArrangement, horizontalAlignment) { + content() + } Box(Modifier.fillMaxSize(), contentAlignment = Alignment.CenterEnd) { DesktopScrollBar(rememberScrollbarAdapter(state), Modifier.fillMaxHeight(), scrollBarAlpha, scrollJob, false, scrollBarDraggingState) } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt index bf2e118cd1..a1df7091d6 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt @@ -1,86 +1,155 @@ package chat.simplex.common.views.chatlist import androidx.compose.foundation.* +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.Icon -import androidx.compose.material.MaterialTheme +import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.call.Call -import chat.simplex.common.views.call.CallMediaType import chat.simplex.common.views.chat.item.ItemAction import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import kotlinx.coroutines.flow.MutableStateFlow @Composable actual fun ActiveCallInteractiveArea(call: Call) { val showMenu = remember { mutableStateOf(false) } - CompositionLocalProvider( - LocalIndication provides NoIndication + val oneHandUI = remember { appPrefs.oneHandUI.state } + if (oneHandUI.value) { + ActiveCallInteractiveAreaOneHand(call, showMenu) + } else { + CompositionLocalProvider( + LocalIndication provides NoIndication + ) { + ActiveCallInteractiveAreaNonOneHand(call, showMenu) + } + } +} + +@Composable +private fun ActiveCallInteractiveAreaOneHand(call: Call, showMenu: MutableState) { + Box( + Modifier + .minimumInteractiveComponentSize() + .combinedClickable(onClick = { + val chat = chatModel.getChat(call.contact.id) + if (chat != null) { + withBGApi { + openChat(chat.remoteHostId, chat.chatInfo, chatModel) + } + } + }, + onLongClick = { showMenu.value = true }, + role = Role.Button, + interactionSource = remember { MutableInteractionSource() }, + indication = remember { ripple(bounded = false, radius = 24.dp) } + ) + .onRightClick { showMenu.value = true }, + contentAlignment = Alignment.Center + ) { + ProfileImage( + image = call.contact.profile.image, + size = 37.dp * fontSizeSqrtMultiplier, + color = MaterialTheme.colors.secondaryVariant.mixWith(MaterialTheme.colors.onBackground, 0.97f) + ) + Box( + Modifier.offset(x = 1.dp, y = (-1).dp).background(SimplexGreen, CircleShape).padding(3.dp) + .align(Alignment.TopEnd) + ) { + if (call.hasVideo) { + Icon( + painterResource(MR.images.ic_videocam_filled), + stringResource(MR.strings.icon_descr_video_call), + Modifier.size(12.dp), + tint = Color.White + ) + } else { + Icon( + painterResource(MR.images.ic_call_filled), + stringResource(MR.strings.icon_descr_audio_call), + Modifier.size(12.dp), + tint = Color.White + ) + } + } + DefaultDropdownMenu(showMenu) { + ItemAction( + stringResource(MR.strings.icon_descr_hang_up), + painterResource(MR.images.ic_call_end_filled), + color = MaterialTheme.colors.error, + onClick = { + withBGApi { chatModel.callManager.endCall(call) } + showMenu.value = false + }) + } + } +} + +@Composable +private fun ActiveCallInteractiveAreaNonOneHand(call: Call, showMenu: MutableState) { + Box( + Modifier + .fillMaxSize(), + contentAlignment = Alignment.BottomEnd ) { Box( Modifier - .fillMaxSize(), - contentAlignment = Alignment.BottomEnd - ) { - Box( - Modifier - .padding(end = 15.dp, bottom = 92.dp) - .size(67.dp) - .combinedClickable(onClick = { - val chat = chatModel.getChat(call.contact.id) - if (chat != null) { - withBGApi { - openChat(chat.remoteHostId, chat.chatInfo, chatModel) - } + .padding(end = 15.dp, bottom = 92.dp) + .size(67.dp) + .combinedClickable(onClick = { + val chat = chatModel.getChat(call.contact.id) + if (chat != null) { + withBGApi { + openChat(chat.remoteHostId, chat.chatInfo, chatModel) } - }, - onLongClick = { showMenu.value = true }) - .onRightClick { showMenu.value = true }, - contentAlignment = Alignment.Center - ) { - Box(Modifier.background(MaterialTheme.colors.background, CircleShape)) { - ProfileImageForActiveCall(size = 56.dp, image = call.contact.profile.image) - } - Box( - Modifier.padding().background(SimplexGreen, CircleShape).padding(4.dp) - .align(Alignment.TopEnd) - ) { - if (call.hasVideo) { - Icon( - painterResource(MR.images.ic_videocam_filled), - stringResource(MR.strings.icon_descr_video_call), - Modifier.size(18.dp), - tint = Color.White - ) - } else { - Icon( - painterResource(MR.images.ic_call_filled), - stringResource(MR.strings.icon_descr_audio_call), - Modifier.size(18.dp), - tint = Color.White - ) } + }, + onLongClick = { showMenu.value = true }) + .onRightClick { showMenu.value = true }, + contentAlignment = Alignment.Center + ) { + Box(Modifier.background(MaterialTheme.colors.background, CircleShape)) { + ProfileImageForActiveCall(size = 56.dp, image = call.contact.profile.image) + } + Box( + Modifier.padding().background(SimplexGreen, CircleShape).padding(4.dp) + .align(Alignment.TopEnd) + ) { + if (call.hasVideo) { + Icon( + painterResource(MR.images.ic_videocam_filled), + stringResource(MR.strings.icon_descr_video_call), + Modifier.size(18.dp), + tint = Color.White + ) + } else { + Icon( + painterResource(MR.images.ic_call_filled), + stringResource(MR.strings.icon_descr_audio_call), + Modifier.size(18.dp), + tint = Color.White + ) } - DefaultDropdownMenu(showMenu) { - ItemAction( - stringResource(MR.strings.icon_descr_hang_up), - painterResource(MR.images.ic_call_end_filled), - color = MaterialTheme.colors.error, - onClick = { - withBGApi { chatModel.callManager.endCall(call) } - showMenu.value = false - }) - } + } + DefaultDropdownMenu(showMenu) { + ItemAction( + stringResource(MR.strings.icon_descr_hang_up), + painterResource(MR.images.ic_call_end_filled), + color = MaterialTheme.colors.error, + onClick = { + withBGApi { chatModel.callManager.endCall(call) } + showMenu.value = false + }) } } } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt index d9b53b9485..3855835ab6 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt @@ -10,11 +10,13 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.User import chat.simplex.common.model.UserInfo import chat.simplex.common.platform.* @@ -25,6 +27,7 @@ import kotlinx.coroutines.flow.MutableStateFlow @Composable actual fun UserPickerUsersSection( users: List, + iconColor: Color, stopped: Boolean, onUserClicked: (user: User) -> Unit, ) { @@ -37,7 +40,7 @@ actual fun UserPickerUsersSection( .padding(horizontal = horizontalPadding) .height((55.dp + 16.sp.toDp()) * rowsToDisplay + (if (rowsToDisplay > 1) DEFAULT_PADDING else 0.dp)) ) { - ColumnWithScrollBar( + ColumnWithScrollBarNoAppBar( verticalArrangement = Arrangement.spacedBy(DEFAULT_PADDING) ) { val spaceBetween = (((DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier) - (horizontalPadding)) - (65.dp * 5)) / 5 @@ -57,7 +60,7 @@ actual fun UserPickerUsersSection( ) { val user = u.user Box { - ProfileImage(size = 55.dp, image = user.profile.image, color = MaterialTheme.colors.secondaryVariant) + ProfileImage(size = 55.dp, image = user.profile.image, color = iconColor) if (u.unreadCount > 0 && !user.activeUser) { unreadBadge(u.unreadCount, user.showNtfs, true) @@ -95,7 +98,8 @@ actual fun PlatformUserPicker(modifier: Modifier, pickerState: MutableStateFlow< .clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = { pickerState.value = AnimatedViewState.HIDING }), contentAlignment = Alignment.TopStart ) { - ColumnWithScrollBar(modifier) { + val oneHandUI = remember { appPrefs.oneHandUI.state } + ColumnWithScrollBarNoAppBar(modifier.align(if (oneHandUI.value) Alignment.BottomCenter else Alignment.TopCenter)) { content() } } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt index 244504f4c7..91ff8831ce 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt @@ -2,6 +2,7 @@ package chat.simplex.common.views.usersettings import SectionBottomSpacer import SectionDividerSpaced +import SectionSpacer import SectionView import androidx.compose.foundation.* import androidx.compose.foundation.layout.* @@ -39,9 +40,7 @@ fun AppearanceScope.AppearanceLayout( languagePref: SharedPreference, systemDarkTheme: SharedPreference, ) { - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - ) { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.appearance_settings)) SectionView(stringResource(MR.strings.settings_section_title_language), contentPadding = PaddingValues()) { val state = rememberSaveable { mutableStateOf(languagePref.get() ?: "system") } @@ -58,10 +57,14 @@ fun AppearanceScope.AppearanceLayout( } } } + SettingsPreferenceItem(icon = null, stringResource(MR.strings.one_hand_ui), ChatModel.controller.appPrefs.oneHandUI) } SectionDividerSpaced() ThemesSection(systemDarkTheme) + SectionDividerSpaced() + AppToolbarsSection() + SectionDividerSpaced() MessageShapeSection() @@ -83,7 +86,7 @@ fun DensityScaleSection() { val localDensityScale = remember { mutableStateOf(appPrefs.densityScale.get()) } SectionView(stringResource(MR.strings.appearance_zoom).uppercase(), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { Row(Modifier.padding(top = 10.dp), verticalAlignment = Alignment.CenterVertically) { - Box(Modifier.size(60.dp) + Box(Modifier.size(50.dp) .background(MaterialTheme.colors.surface, RoundedCornerShape(percent = 22)) .clip(RoundedCornerShape(percent = 22)) .clickable { @@ -101,7 +104,7 @@ fun DensityScaleSection() { ) } } - Spacer(Modifier.width(10.dp)) + Spacer(Modifier.width(15.dp)) Slider( localDensityScale.value, valueRange = 1f..2f, diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.desktop.kt index ee8ae93de5..5b4a044df3 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.desktop.kt @@ -18,12 +18,11 @@ import dev.icerock.moko.resources.compose.stringResource @Composable actual fun SettingsSectionApp( showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), - showCustomModal: (@Composable ModalData.(ChatModel, () -> Unit) -> Unit) -> (() -> Unit), showVersion: () -> Unit, withAuth: (title: String, desc: String, block: () -> Unit) -> Unit ) { SectionView(stringResource(MR.strings.settings_section_title_app)) { - SettingsActionItem(painterResource(MR.images.ic_code), stringResource(MR.strings.settings_developer_tools), showSettingsModal { DeveloperView(it, showCustomModal, withAuth) }) + SettingsActionItem(painterResource(MR.images.ic_code), stringResource(MR.strings.settings_developer_tools), showSettingsModal { DeveloperView(withAuth) }) val selectedChannel = remember { appPrefs.appUpdateChannel.state } val values = AppUpdatesChannel.entries.map { it to it.text } ExposedDropDownSettingRow(stringResource(MR.strings.app_check_for_updates), values, selectedChannel) { From 3c8c9d8b524482903a819861f032d896e43b8024 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 2 Nov 2024 13:43:45 +0000 Subject: [PATCH 054/567] website: update jobs page --- docs/JOIN_TEAM.md | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/docs/JOIN_TEAM.md b/docs/JOIN_TEAM.md index 26502a05af..c72a75cfec 100644 --- a/docs/JOIN_TEAM.md +++ b/docs/JOIN_TEAM.md @@ -8,25 +8,23 @@ layout: layouts/jobs.html SimpleX Chat Ltd is a seed stage startup with a lot of user growth in 2022-2023, and a lot of exciting technical and product problems to solve to grow faster. -We currently have 4 full-time people in the team - all engineers, including the founder. - -We want to add up to 3 people to the team. +We currently have 6 full-time people in the team. +We want to add 2 people to the team. ## Who we are looking for -### Product/UI designer +### Web designer & developer for a website contract -You will be designing the user experience and the interface of both the app and the website in collaboration with the team. +You will work with the founder and a product marketing expert to convert the stories we want to tell our current and prospective users into interactive experiences. -The current focus of the app is privacy and security, but we hope to have the design that would support the feeling of psychological safety, enabling people to achieve the results in the smallest amount of time. +You are an expert in creating interactive web experiences: +- 15+ years of web development and design experience. +- Passionate about communications, privacy and data ownership. +- Competent using PhotoShop, 3D modelling, etc. +- Competent in Web tech, including JavaScript, animations, etc. -You are an experienced and innovative product designer with: -- 8+ years of user experience and visual design. -- Expertise in typography and high sensitivity to colors. -- Exceptional precision and attention to details. -- Strong opinions (weakly held). -- A strong empathy. +We will NOT consider agencies or groups – it must be one person working on the project. ### Application Haskell engineer @@ -34,13 +32,12 @@ You will work with the Haskell core of the client applications and with the netw You are an expert in language models, databases and Haskell: - expert knowledge of SQL. -- Haskell exception handling, concurrency, STM, type systems. -- 8y+ of software engineering experience in complex projects, +- Haskell strictness, exceptions, [concurrency](https://simonmar.github.io/pages/pcph.html), STM, [type systems](https://thinkingwithtypes.com). +- 15y+ of software engineering experience in complex projects. - deep understanding of the common programming principles: - data structures, bits and bytes, text encoding. - - software design and algorithms. - - concurrency. - - networking. + - [functional software design](https://mitp-content-server.mit.edu/books/content/sectbyfn/books_pres_0/6515/sicp.zip/index.html) and algorithms. + - protocols and networking. ## About you @@ -48,6 +45,7 @@ You are an expert in language models, databases and Haskell: - already use SimpleX Chat to communicate with friends/family or participate in public SimpleX Chat groups. - passionate about privacy, security and communications. - interested to make contributions to SimpleX Chat open-source project in your free time before we hire you, as an extended test. + - you founded (and probably failed) at least one startup, or spent more time working for yourself than being employed. - **Exceptionally pragmatic, very fast and customer-focussed**: - care about the customers (aka users) and about the product we build much more than about the code quality, technology stack, etc. From ceb17b23b42dddae68f81598602942612fc4ee23 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 2 Nov 2024 15:28:41 +0000 Subject: [PATCH 055/567] bumped haskell.nix (#5134) Co-authored-by: Moritz Angermann --- flake.lock | 111 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 82 insertions(+), 29 deletions(-) diff --git a/flake.lock b/flake.lock index a11e01683e..eac7357cd8 100644 --- a/flake.lock +++ b/flake.lock @@ -156,11 +156,11 @@ "ghc98X": { "flake": false, "locked": { - "lastModified": 1696643148, - "narHash": "sha256-E02DfgISH7EvvNAu0BHiPvl1E5FGMDi0pWdNZtIBC9I=", + "lastModified": 1715066704, + "narHash": "sha256-F0EVR8x/fcpj1st+hz96Wdsz5uwVIOziGKAwRxLOYJw=", "ref": "ghc-9.8", - "rev": "443e870d977b1ab6fc05f47a9a17bc49296adbd6", - "revCount": 61642, + "rev": "78a253543d466ac511a1664a3e6aff032ca684d5", + "revCount": 61757, "submodules": true, "type": "git", "url": "https://gitlab.haskell.org/ghc/ghc" @@ -175,11 +175,11 @@ "ghc99": { "flake": false, "locked": { - "lastModified": 1697054644, - "narHash": "sha256-kKarOuXUaAH3QWv7ASx+gGFMHaHKe0pK5Zu37ky2AL4=", + "lastModified": 1726585445, + "narHash": "sha256-IdwQBex4boY6s0Plj5+ixf36rfYSUyMdTWrztKvZH30=", "ref": "refs/heads/master", - "rev": "f383a242c76f90bcca8a4d7ee001dcb49c172a9a", - "revCount": 62040, + "rev": "7fd9e5e29ab54eb406880077463e8552e2ddd39a", + "revCount": 67238, "submodules": true, "type": "git", "url": "https://gitlab.haskell.org/ghc/ghc" @@ -225,6 +225,8 @@ "hls-2.2": "hls-2.2", "hls-2.3": "hls-2.3", "hls-2.4": "hls-2.4", + "hls-2.5": "hls-2.5", + "hls-2.6": "hls-2.6", "hpc-coveralls": "hpc-coveralls", "hydra": "hydra", "iserv-proxy": "iserv-proxy", @@ -238,16 +240,17 @@ "nixpkgs-2205": "nixpkgs-2205", "nixpkgs-2211": "nixpkgs-2211", "nixpkgs-2305": "nixpkgs-2305", + "nixpkgs-2311": "nixpkgs-2311", "nixpkgs-unstable": "nixpkgs-unstable", "old-ghc-nix": "old-ghc-nix", "stackage": "stackage" }, "locked": { - "lastModified": 1701163700, - "narHash": "sha256-sOrewUS3LnzV09nGr7+3R6Q6zsgU4smJc61QsHq+4DE=", + "lastModified": 1705833500, + "narHash": "sha256-rUIr6JNbCedt1g4gVYVvE9t0oFU6FUspCA0DS5cA8Bg=", "owner": "input-output-hk", "repo": "haskell.nix", - "rev": "2808bfe3e62e9eb4ee8974cd623a00e1611f302b", + "rev": "d0c35e75cbbc6858770af42ac32b0b85495fbd71", "type": "github" }, "original": { @@ -328,16 +331,50 @@ "hls-2.4": { "flake": false, "locked": { - "lastModified": 1696939266, - "narHash": "sha256-VOMf5+kyOeOmfXTHlv4LNFJuDGa7G3pDnOxtzYR40IU=", + "lastModified": 1699862708, + "narHash": "sha256-YHXSkdz53zd0fYGIYOgLt6HrA0eaRJi9mXVqDgmvrjk=", "owner": "haskell", "repo": "haskell-language-server", - "rev": "362fdd1293efb4b82410b676ab1273479f6d17ee", + "rev": "54507ef7e85fa8e9d0eb9a669832a3287ffccd57", "type": "github" }, "original": { "owner": "haskell", - "ref": "2.4.0.0", + "ref": "2.4.0.1", + "repo": "haskell-language-server", + "type": "github" + } + }, + "hls-2.5": { + "flake": false, + "locked": { + "lastModified": 1701080174, + "narHash": "sha256-fyiR9TaHGJIIR0UmcCb73Xv9TJq3ht2ioxQ2mT7kVdc=", + "owner": "haskell", + "repo": "haskell-language-server", + "rev": "27f8c3d3892e38edaef5bea3870161815c4d014c", + "type": "github" + }, + "original": { + "owner": "haskell", + "ref": "2.5.0.0", + "repo": "haskell-language-server", + "type": "github" + } + }, + "hls-2.6": { + "flake": false, + "locked": { + "lastModified": 1705325287, + "narHash": "sha256-+P87oLdlPyMw8Mgoul7HMWdEvWP/fNlo8jyNtwME8E8=", + "owner": "haskell", + "repo": "haskell-language-server", + "rev": "6e0b342fa0327e628610f2711f8c3e4eaaa08b1e", + "type": "github" + }, + "original": { + "owner": "haskell", + "ref": "2.6.0.0", "repo": "haskell-language-server", "type": "github" } @@ -384,11 +421,11 @@ "iserv-proxy": { "flake": false, "locked": { - "lastModified": 1691634696, - "narHash": "sha256-MZH2NznKC/gbgBu8NgIibtSUZeJ00HTLJ0PlWKCBHb0=", + "lastModified": 1707968597, + "narHash": "sha256-C53NqToxl+n9s1pQ0iLtiH6P5vX3rM+NW/mFt4Ykpsk=", "ref": "hkm/remote-iserv", - "rev": "43a979272d9addc29fbffc2e8542c5d96e993d73", - "revCount": 14, + "rev": "1b7f8aeb37bbc7c00f04e44d9379aa15a4409e8b", + "revCount": 18, "type": "git", "url": "https://gitlab.haskell.org/hamishmack/iserv-proxy.git" }, @@ -552,11 +589,11 @@ }, "nixpkgs-2305": { "locked": { - "lastModified": 1695416179, - "narHash": "sha256-610o1+pwbSu+QuF3GE0NU5xQdTHM3t9wyYhB9l94Cd8=", + "lastModified": 1705033721, + "narHash": "sha256-K5eJHmL1/kev6WuqyqqbS1cdNnSidIZ3jeqJ7GbrYnQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "715d72e967ec1dd5ecc71290ee072bcaf5181ed6", + "rev": "a1982c92d8980a0114372973cbdfe0a307f1bdea", "type": "github" }, "original": { @@ -566,6 +603,22 @@ "type": "github" } }, + "nixpkgs-2311": { + "locked": { + "lastModified": 1719957072, + "narHash": "sha256-gvFhEf5nszouwLAkT9nWsDzocUTqLWHuL++dvNjMp9I=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "7144d6241f02d171d25fba3edeaf15e0f2592105", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-23.11-darwin", + "repo": "nixpkgs", + "type": "github" + } + }, "nixpkgs-lib": { "locked": { "dir": "lib", @@ -602,17 +655,17 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1695318763, - "narHash": "sha256-FHVPDRP2AfvsxAdc+AsgFJevMz5VBmnZglFUMlxBkcY=", + "lastModified": 1694822471, + "narHash": "sha256-6fSDCj++lZVMZlyqOe9SIOL8tYSBz1bI8acwovRwoX8=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "e12483116b3b51a185a33a272bf351e357ba9a99", + "rev": "47585496bcb13fb72e4a90daeea2f434e2501998", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixpkgs-unstable", "repo": "nixpkgs", + "rev": "47585496bcb13fb72e4a90daeea2f434e2501998", "type": "github" } }, @@ -664,11 +717,11 @@ "stackage": { "flake": false, "locked": { - "lastModified": 1699834215, - "narHash": "sha256-g/JKy0BCvJaxPuYDl3QVc4OY8cFEomgG+hW/eEV470M=", + "lastModified": 1726532152, + "narHash": "sha256-LRXbVY3M2S8uQWdwd2zZrsnVPEvt2GxaHGoy8EFFdJA=", "owner": "input-output-hk", "repo": "stackage.nix", - "rev": "47aacd04abcce6bad57f43cbbbd133538380248e", + "rev": "c77b3530cebad603812cb111c6f64968c2d2337d", "type": "github" }, "original": { From 165143a1112308c035ac00ed669b96b60599aa1c Mon Sep 17 00:00:00 2001 From: Alexander Bondarenko <486682+dpwiz@users.noreply.github.com> Date: Sat, 2 Nov 2024 19:51:11 +0200 Subject: [PATCH 056/567] Use simplexmq with client_library flag (#5133) * Use simplexmq with client_library flag * fix server config for mq master * simplexmq --------- Co-authored-by: Evgeny Poberezkin --- Dockerfile | 2 +- cabal.project | 2 +- flake.nix | 7 +++++++ scripts/desktop/build-lib-linux.sh | 2 +- scripts/desktop/build-lib-mac.sh | 2 +- scripts/desktop/build-lib-windows.sh | 2 +- scripts/nix/sha256map.nix | 2 +- tests/ChatClient.hs | 4 ++++ 8 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6c60195f97..7b9641777a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ RUN cp ./scripts/cabal.project.local.linux ./cabal.project.local # Compile simplex-chat RUN cabal update -RUN cabal build exe:simplex-chat +RUN cabal build exe:simplex-chat --constraint 'simplexmq +client_library' # Strip the binary from debug symbols to reduce size RUN bin=$(find /project/dist-newstyle -name "simplex-chat" -type f -executable) && \ diff --git a/cabal.project b/cabal.project index e98f8122d0..c9b8b11722 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: a8471eed5be93e7c3741aa4742b24193c9a2d6f5 + tag: ffecf200d4874dfa34f6d15b269964c0115a54ca source-repository-package type: git diff --git a/flake.nix b/flake.nix index e8ff779a87..1a1043c5f2 100644 --- a/flake.nix +++ b/flake.nix @@ -198,6 +198,7 @@ packages.direct-sqlcipher.components.library.libs = pkgs.lib.mkForce [ pkgs.pkgsCross.mingwW64.openssl ]; + packages.simplexmq.flags.client_library = true; packages.simplexmq.components.library.libs = pkgs.lib.mkForce [ pkgs.pkgsCross.mingwW64.openssl ]; @@ -335,6 +336,7 @@ packages.direct-sqlcipher.patches = [ ./scripts/nix/direct-sqlcipher-android-log.patch ]; + packages.simplexmq.flags.client_library = true; packages.simplexmq.components.library.libs = pkgs.lib.mkForce [ (android32Pkgs.openssl.override { static = true; enableKTLS = false; }) ]; @@ -443,6 +445,7 @@ packages.direct-sqlcipher.patches = [ ./scripts/nix/direct-sqlcipher-android-log.patch ]; + packages.simplexmq.flags.client_library = true; packages.simplexmq.components.library.libs = pkgs.lib.mkForce [ (androidPkgs.openssl.override { static = true; }) ]; @@ -547,6 +550,7 @@ packages.simplexmq.flags.swift = true; packages.direct-sqlcipher.flags.commoncrypto = true; packages.entropy.flags.DoNotGetEntropy = true; + packages.simplexmq.flags.client_library = true; packages.simplexmq.components.library.libs = pkgs.lib.mkForce [ # TODO: have a cross override for iOS, that sets this. ((pkgs.openssl.override { static = true; }).overrideDerivation (old: { CFLAGS = "-mcpu=apple-a7 -march=armv8-a+norcpc" ;})) @@ -561,6 +565,7 @@ extra-modules = [{ packages.direct-sqlcipher.flags.commoncrypto = true; packages.entropy.flags.DoNotGetEntropy = true; + packages.simplexmq.flags.client_library = true; packages.simplexmq.components.library.libs = pkgs.lib.mkForce [ ((pkgs.openssl.override { static = true; }).overrideDerivation (old: { CFLAGS = "-mcpu=apple-a7 -march=armv8-a+norcpc" ;})) ]; @@ -578,6 +583,7 @@ packages.simplexmq.flags.swift = true; packages.direct-sqlcipher.flags.commoncrypto = true; packages.entropy.flags.DoNotGetEntropy = true; + packages.simplexmq.flags.client_library = true; packages.simplexmq.components.library.libs = pkgs.lib.mkForce [ (pkgs.openssl.override { static = true; }) ]; @@ -591,6 +597,7 @@ extra-modules = [{ packages.direct-sqlcipher.flags.commoncrypto = true; packages.entropy.flags.DoNotGetEntropy = true; + packages.simplexmq.flags.client_library = true; packages.simplexmq.components.library.libs = pkgs.lib.mkForce [ (pkgs.openssl.override { static = true; }) ]; diff --git a/scripts/desktop/build-lib-linux.sh b/scripts/desktop/build-lib-linux.sh index da645c6e86..80ae9fa82e 100755 --- a/scripts/desktop/build-lib-linux.sh +++ b/scripts/desktop/build-lib-linux.sh @@ -25,7 +25,7 @@ for elem in "${exports[@]}"; do count=$(grep -R "$elem$" libsimplex.dll.def | wc for elem in "${exports[@]}"; do count=$(grep -R "\"$elem\"" flake.nix | wc -l); if [ $count -ne 2 ]; then echo Wrong exports in flake.nix. Add \"$elem\" in two places of the file; exit 1; fi ; done rm -rf $BUILD_DIR -cabal build lib:simplex-chat --ghc-options='-optl-Wl,-rpath,$ORIGIN -flink-rts -threaded' +cabal build lib:simplex-chat --ghc-options='-optl-Wl,-rpath,$ORIGIN -flink-rts -threaded' --constraint 'simplexmq +client_library' cd $BUILD_DIR/build #patchelf --add-needed libHSrts_thr-ghc${GHC_VERSION}.so libHSsimplex-chat-*-inplace-ghc${GHC_VERSION}.so #patchelf --add-rpath '$ORIGIN' libHSsimplex-chat-*-inplace-ghc${GHC_VERSION}.so diff --git a/scripts/desktop/build-lib-mac.sh b/scripts/desktop/build-lib-mac.sh index 2b0fd5376f..9d7d5031a0 100755 --- a/scripts/desktop/build-lib-mac.sh +++ b/scripts/desktop/build-lib-mac.sh @@ -24,7 +24,7 @@ for elem in "${exports[@]}"; do count=$(grep -R "$elem$" libsimplex.dll.def | wc for elem in "${exports[@]}"; do count=$(grep -R "\"$elem\"" flake.nix | wc -l); if [ $count -ne 2 ]; then echo Wrong exports in flake.nix. Add \"$elem\" in two places of the file; exit 1; fi ; done rm -rf $BUILD_DIR -cabal build lib:simplex-chat lib:simplex-chat --ghc-options="-optl-Wl,-rpath,@loader_path -optl-Wl,-L$GHC_LIBS_DIR/$ARCH-osx-ghc-$GHC_VERSION -optl-lHSrts_thr-ghc$GHC_VERSION -optl-lffi" +cabal build lib:simplex-chat lib:simplex-chat --ghc-options="-optl-Wl,-rpath,@loader_path -optl-Wl,-L$GHC_LIBS_DIR/$ARCH-osx-ghc-$GHC_VERSION -optl-lHSrts_thr-ghc$GHC_VERSION -optl-lffi" --constraint 'simplexmq +client_library' cd $BUILD_DIR/build mkdir deps 2> /dev/null || true diff --git a/scripts/desktop/build-lib-windows.sh b/scripts/desktop/build-lib-windows.sh index 72de53854f..0e96a42e86 100755 --- a/scripts/desktop/build-lib-windows.sh +++ b/scripts/desktop/build-lib-windows.sh @@ -51,7 +51,7 @@ echo " ghc-options: -shared -threaded -optl-L$openssl_windows_style_path -opt # Very important! Without it the build fails on linking step since the linker can't find exported symbols. # It looks like GHC bug because with such random path the build ends successfully sed -i "s/ld.lld.exe/abracadabra.exe/" `ghc --print-libdir`/settings -cabal build lib:simplex-chat +cabal build lib:simplex-chat --constraint 'simplexmq +client_library' rm -rf apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/ rm -rf apps/multiplatform/desktop/build/cmake diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 8f53d078dc..8de91675e3 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."a8471eed5be93e7c3741aa4742b24193c9a2d6f5" = "093i40api0dp7rvw6f1f3pww3q5iv6mvbj577nlxp3qqcbvyh6fs"; + "https://github.com/simplex-chat/simplexmq.git"."ffecf200d4874dfa34f6d15b269964c0115a54ca" = "0kb8hq37fc5g198wq7dswnlwjzk67q8rrzil2dii5lc6xfr47jbs"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index 36bdf92dbf..75b85d7a5f 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -51,6 +51,7 @@ import qualified Simplex.Messaging.Crypto.Ratchet as CR import Simplex.Messaging.Protocol (srvHostnamesSMPClientVersion) import Simplex.Messaging.Server (runSMPServerBlocking) import Simplex.Messaging.Server.Env.STM +import Simplex.Messaging.Server.MsgStore.Types (AMSType (..), SMSType (..)) import Simplex.Messaging.Transport import Simplex.Messaging.Transport.Server (ServerCredentials (..), defaultTransportServerConfig) import Simplex.Messaging.Version @@ -424,6 +425,9 @@ smpServerCfg = tbqSize = 1, -- serverTbqSize = 1, msgQueueQuota = 16, + msgStoreType = AMSType SMSMemory, + maxJournalMsgCount = 1000, + maxJournalStateLines = 1000, queueIdBytes = 12, msgIdBytes = 6, storeLogFile = Nothing, From 7a741e7ac4ce945cf8bda920153169b6c2d8c051 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 2 Nov 2024 20:03:27 +0000 Subject: [PATCH 057/567] ios: update core library --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 2b1160061c..cd146d4292 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -149,9 +149,9 @@ 6419EC582AB97507004A607A /* CIMemberCreatedContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */; }; 6432857C2925443C00FBE5C8 /* GroupPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */; }; 643B3B452CCBEB080083A2CF /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B402CCBEB080083A2CF /* libgmpxx.a */; }; - 643B3B462CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a */; }; + 643B3B462CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5-ghc9.6.3.a */; }; 643B3B472CCBEB080083A2CF /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B422CCBEB080083A2CF /* libffi.a */; }; - 643B3B482CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a */; }; + 643B3B482CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5.a */; }; 643B3B492CCBEB080083A2CF /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B442CCBEB080083A2CF /* libgmp.a */; }; 6440CA00288857A10062C672 /* CIEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440C9FF288857A10062C672 /* CIEventView.swift */; }; 6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */; }; @@ -492,9 +492,9 @@ 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMemberCreatedContactView.swift; sourceTree = ""; }; 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupPreferencesView.swift; sourceTree = ""; }; 643B3B402CCBEB080083A2CF /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgmpxx.a; path = Libraries/libgmpxx.a; sourceTree = ""; }; - 643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a"; path = "Libraries/libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a"; sourceTree = ""; }; + 643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5-ghc9.6.3.a"; path = "Libraries/libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5-ghc9.6.3.a"; sourceTree = ""; }; 643B3B422CCBEB080083A2CF /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libffi.a; path = Libraries/libffi.a; sourceTree = ""; }; - 643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a"; path = "Libraries/libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a"; sourceTree = ""; }; + 643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5.a"; path = "Libraries/libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5.a"; sourceTree = ""; }; 643B3B442CCBEB080083A2CF /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgmp.a; path = Libraries/libgmp.a; sourceTree = ""; }; 6440C9FF288857A10062C672 /* CIEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIEventView.swift; sourceTree = ""; }; 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupMembersView.swift; sourceTree = ""; }; @@ -663,8 +663,8 @@ 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - 643B3B482CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a in Frameworks */, - 643B3B462CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a in Frameworks */, + 643B3B482CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5.a in Frameworks */, + 643B3B462CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5-ghc9.6.3.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -815,8 +815,8 @@ 643B3B422CCBEB080083A2CF /* libffi.a */, 643B3B442CCBEB080083A2CF /* libgmp.a */, 643B3B402CCBEB080083A2CF /* libgmpxx.a */, - 643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a */, - 643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a */, + 643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5-ghc9.6.3.a */, + 643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5.a */, 5CA059C2279559F40002BEB4 /* Shared */, 5CDCAD462818589900503DA2 /* SimpleX NSE */, CEE723A82C3BD3D70009AE93 /* SimpleX SE */, From 97df069730e2d63b3eb7b644b127a7e3cc7b03ed Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 4 Nov 2024 13:28:57 +0000 Subject: [PATCH 058/567] core: add support for server operators (#4961) * core: add support for server operators * migration * update schema and queries, rfc * add usage conditions tables * core: server operators new apis draft * update * conditions * update * add get conditions api * add get conditions API * WIP * compiles * fix schema * core: ui logic in types (#5139) * update --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --- cabal.project | 2 +- docs/rfcs/2024-10-27-server-operators.md | 24 ++++ package.yaml | 1 + scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 10 ++ src/Simplex/Chat.hs | 50 +++++++- src/Simplex/Chat/Controller.hs | 25 +++- .../Migrations/M20241027_server_operators.hs | 70 +++++++++++ src/Simplex/Chat/Migrations/chat_schema.sql | 39 +++++++ src/Simplex/Chat/Operators.hs | 110 ++++++++++++++++++ src/Simplex/Chat/Operators/Conditions.hs | 19 +++ src/Simplex/Chat/Store/Migrations.hs | 4 +- src/Simplex/Chat/Store/Profiles.hs | 77 ++++++++++-- src/Simplex/Chat/Terminal.hs | 8 +- src/Simplex/Chat/View.hs | 24 ++-- tests/ChatClient.hs | 7 +- tests/RandomServers.hs | 4 +- 17 files changed, 440 insertions(+), 36 deletions(-) create mode 100644 docs/rfcs/2024-10-27-server-operators.md create mode 100644 src/Simplex/Chat/Migrations/M20241027_server_operators.hs create mode 100644 src/Simplex/Chat/Operators.hs create mode 100644 src/Simplex/Chat/Operators/Conditions.hs diff --git a/cabal.project b/cabal.project index c9b8b11722..61ce04a569 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: ffecf200d4874dfa34f6d15b269964c0115a54ca + tag: ff05a465ee15ac7ae2c14a9fb703a18564950631 source-repository-package type: git diff --git a/docs/rfcs/2024-10-27-server-operators.md b/docs/rfcs/2024-10-27-server-operators.md new file mode 100644 index 0000000000..5456d28f08 --- /dev/null +++ b/docs/rfcs/2024-10-27-server-operators.md @@ -0,0 +1,24 @@ +# Server operators + +## Problem + +All preconfigured servers operated by a single company create a risk that user connections can be analysed by aggregating transport information from these servers. + +The solution is to have more than one operator servers pre-configured in the app. + +For operators to be protected from any violations of rights of other users or third parties by the users who use servers of these operators, the users have to explicitely accept conditions of use with the operator, in the same way they accept conditions of use with SimpleX Chat Ltd by downloading the app. + +## Solution + +Allow to assign operators to servers, both with preconfigured operators and servers, and with user-defined operators. Agent added support for server roles, chat app could: +- allow assigning server roles only on the operator level. +- only on server level. +- on both, with server roles overriding operator roles (that would require a different type for server for chat app). + +For simplicity of both UX and logic it is probably better to allow assigning roles only on operators' level, and servers without set operators can be used for both roles. + +For agreements, it is sufficient to record the signatures of these agreements on users' devices, together with the copy of signed agreement (or its hash and version) in a separate table. The terms themselves could be: +- included in the app - either in code or in migration. +- referenced with a stable link to a particular commit. + +The first solution seems better, as it avoids any third party dependency, and the agreement size is relatively small (~31kb), to reduce size we can store it compressed. diff --git a/package.yaml b/package.yaml index 94dc13ad2e..2fc50a3532 100644 --- a/package.yaml +++ b/package.yaml @@ -29,6 +29,7 @@ dependencies: - email-validate == 2.3.* - exceptions == 0.10.* - filepath == 1.4.* + - file-embed == 0.0.15.* - http-types == 0.12.* - http2 >= 4.2.2 && < 4.3 - memory == 0.18.* diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 8de91675e3..3e0f103641 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."ffecf200d4874dfa34f6d15b269964c0115a54ca" = "0kb8hq37fc5g198wq7dswnlwjzk67q8rrzil2dii5lc6xfr47jbs"; + "https://github.com/simplex-chat/simplexmq.git"."ff05a465ee15ac7ae2c14a9fb703a18564950631" = "1gv4nwqzbqkj7y3ffkiwkr4qwv52vdzppsds5vsfqaayl14rzmgp"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 96d16f5004..c7d603457c 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -150,10 +150,13 @@ library Simplex.Chat.Migrations.M20240920_user_order Simplex.Chat.Migrations.M20241008_indexes Simplex.Chat.Migrations.M20241010_contact_requests_contact_id + Simplex.Chat.Migrations.M20241027_server_operators Simplex.Chat.Mobile Simplex.Chat.Mobile.File Simplex.Chat.Mobile.Shared Simplex.Chat.Mobile.WebRTC + Simplex.Chat.Operators + Simplex.Chat.Operators.Conditions Simplex.Chat.Options Simplex.Chat.ProfileGenerator Simplex.Chat.Protocol @@ -213,6 +216,7 @@ library , directory ==1.3.* , email-validate ==2.3.* , exceptions ==0.10.* + , file-embed ==0.0.15.* , filepath ==1.4.* , http-types ==0.12.* , http2 >=4.2.2 && <4.3 @@ -276,6 +280,7 @@ executable simplex-bot , directory ==1.3.* , email-validate ==2.3.* , exceptions ==0.10.* + , file-embed ==0.0.15.* , filepath ==1.4.* , http-types ==0.12.* , http2 >=4.2.2 && <4.3 @@ -340,6 +345,7 @@ executable simplex-bot-advanced , directory ==1.3.* , email-validate ==2.3.* , exceptions ==0.10.* + , file-embed ==0.0.15.* , filepath ==1.4.* , http-types ==0.12.* , http2 >=4.2.2 && <4.3 @@ -407,6 +413,7 @@ executable simplex-broadcast-bot , directory ==1.3.* , email-validate ==2.3.* , exceptions ==0.10.* + , file-embed ==0.0.15.* , filepath ==1.4.* , http-types ==0.12.* , http2 >=4.2.2 && <4.3 @@ -472,6 +479,7 @@ executable simplex-chat , directory ==1.3.* , email-validate ==2.3.* , exceptions ==0.10.* + , file-embed ==0.0.15.* , filepath ==1.4.* , http-types ==0.12.* , http2 >=4.2.2 && <4.3 @@ -543,6 +551,7 @@ executable simplex-directory-service , directory ==1.3.* , email-validate ==2.3.* , exceptions ==0.10.* + , file-embed ==0.0.15.* , filepath ==1.4.* , http-types ==0.12.* , http2 >=4.2.2 && <4.3 @@ -642,6 +651,7 @@ test-suite simplex-chat-test , directory ==1.3.* , email-validate ==2.3.* , exceptions ==0.10.* + , file-embed ==0.0.15.* , filepath ==1.4.* , generic-random ==1.5.* , http-types ==0.12.* diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 885d4303c8..380f6c5d24 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -67,6 +67,7 @@ import Simplex.Chat.Messages import Simplex.Chat.Messages.Batch (MsgBatch (..), batchMessages) import Simplex.Chat.Messages.CIContent import Simplex.Chat.Messages.CIContent.Events +import Simplex.Chat.Operators import Simplex.Chat.Options import Simplex.Chat.ProfileGenerator (generateRandomProfile) import Simplex.Chat.Protocol @@ -97,7 +98,7 @@ import qualified Simplex.FileTransfer.Transport as XFTP import Simplex.FileTransfer.Types (FileErrorType (..), RcvFileId, SndFileId) import Simplex.Messaging.Agent as Agent import Simplex.Messaging.Agent.Client (SubInfo (..), agentClientStore, getAgentQueuesInfo, getAgentWorkersDetails, getAgentWorkersSummary, getFastNetworkConfig, ipAddressProtected, withLockMap) -import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), InitialAgentServers (..), ServerCfg (..), createAgentStore, defaultAgentConfig, enabledServerCfg, presetServerCfg) +import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), InitialAgentServers (..), OperatorId, ServerCfg (..), allRoles, createAgentStore, defaultAgentConfig, enabledServerCfg, presetServerCfg) import Simplex.Messaging.Agent.Lock (withLock) import Simplex.Messaging.Agent.Protocol import qualified Simplex.Messaging.Agent.Protocol as AP (AgentErrorType (..)) @@ -152,7 +153,7 @@ defaultChatConfig = { smp = _defaultSMPServers, useSMP = 4, ntf = _defaultNtfServers, - xftp = L.map (presetServerCfg True) defaultXFTPServers, + xftp = L.map (presetServerCfg True allRoles operatorSimpleXChat) defaultXFTPServers, useXFTP = L.length defaultXFTPServers, netCfg = defaultNetworkConfig }, @@ -181,7 +182,7 @@ _defaultSMPServers :: NonEmpty (ServerCfg 'PSMP) _defaultSMPServers = L.fromList $ map - (presetServerCfg True) + (presetServerCfg True allRoles operatorSimpleXChat) [ "smp://0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU=@smp8.simplex.im,beccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion", "smp://SkIkI6EPd2D63F4xFKfHk7I1UGZVNn6k1QWZ5rcyr6w=@smp9.simplex.im,jssqzccmrcws6bhmn77vgmhfjmhwlyr3u7puw4erkyoosywgl67slqqd.onion", "smp://6iIcWT_dF2zN_w5xzZEY7HI2Prbh3ldP07YTyDexPjE=@smp10.simplex.im,rb2pbttocvnbrngnwziclp2f4ckjq65kebafws6g4hy22cdaiv5dwjqd.onion", @@ -195,12 +196,15 @@ _defaultSMPServers = "smp://N_McQS3F9TGoh4ER0QstUf55kGnNSd-wXfNPZ7HukcM=@smp19.simplex.im,i53bbtoqhlc365k6kxzwdp5w3cdt433s7bwh3y32rcbml2vztiyyz5id.onion" ] <> map - (presetServerCfg False) + (presetServerCfg False allRoles operatorSimpleXChat) [ "smp://u2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU=@smp4.simplex.im,o5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion", "smp://hpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg=@smp5.simplex.im,jjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion", "smp://PQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo=@smp6.simplex.im,bylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion" ] +operatorSimpleXChat :: Maybe OperatorId +operatorSimpleXChat = Just 1 + _defaultNtfServers :: [NtfServer] _defaultNtfServers = [ "ntf://FB-Uop7RTaZZEG0ZLD2CIaTjsPh-Fw0zFAnb7QyA8Ks=@ntf2.simplex.im,5ex3mupcazy3zlky64ab27phjhijpemsiby33qzq3pliejipbtx5xgad.onion" @@ -1484,8 +1488,11 @@ processChatCommand' vr = \case pure $ CRConnNtfMessages ntfMsgs APIGetUserProtoServers userId (AProtocolType p) -> withUserId userId $ \user -> withServerProtocol p $ do cfg@ChatConfig {defaultServers} <- asks config - servers <- withFastStore' (`getProtocolServers` user) - pure $ CRUserProtoServers user $ AUPS $ UserProtoServers p (useServers cfg p servers) (cfgServers p defaultServers) + srvs <- withFastStore' (`getProtocolServers` user) + ts <- liftIO getCurrentTime + operators <- withFastStore' $ \db -> getServerOperators db ts + let servers = AUPS $ UserProtoServers p (useServers cfg p srvs) (cfgServers p defaultServers) + pure $ CRUserProtoServers {user, servers, operators} GetUserProtoServers aProtocol -> withUser $ \User {userId} -> processChatCommand $ APIGetUserProtoServers userId aProtocol APISetUserProtoServers userId (APSC p (ProtoServersConfig servers)) @@ -1501,6 +1508,37 @@ processChatCommand' vr = \case lift $ CRServerTestResult user srv <$> withAgent' (\a -> testProtocolServer a (aUserId user) server) TestProtoServer srv -> withUser $ \User {userId} -> processChatCommand $ APITestProtoServer userId srv + APIGetServerOperators -> pure $ chatCmdError Nothing "not supported" + APISetServerOperators _operators -> pure $ chatCmdError Nothing "not supported" + APIGetUserServers userId -> withUserId userId $ \user -> + pure $ chatCmdError (Just user) "not supported" + APISetUserServers userId _userServers -> withUserId userId $ \user -> + pure $ chatCmdError (Just user) "not supported" + APIValidateServers _userServers -> + -- response is CRUserServersValidation + pure $ chatCmdError Nothing "not supported" + APIGetUsageConditions -> do + -- TODO + -- get current conditions + -- get latest accepted conditions (from operators) + ts <- liftIO getCurrentTime + let usageConditions = + UsageConditions + { conditionsId = 1, + conditionsCommit = "abc", + notifiedAt = Nothing, + createdAt = ts + } + pure + CRUsageConditions + { usageConditions = usageConditions, + conditionsText = usageConditionsText, + acceptedConditions = Nothing + } + APISetConditionsNotified _conditionsId -> do + pure $ chatCmdError Nothing "not supported" + APIAcceptConditions _conditionsId _opIds -> + pure $ chatCmdError Nothing "not supported" APISetChatItemTTL userId newTTL_ -> withUserId userId $ \user -> checkStoreNotChanged $ withChatLock "setChatItemTTL" $ do diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index b39b4d7456..bd2cee3e50 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -57,6 +57,7 @@ import Simplex.Chat.Call import Simplex.Chat.Markdown (MarkdownList) import Simplex.Chat.Messages import Simplex.Chat.Messages.CIContent +import Simplex.Chat.Operators import Simplex.Chat.Protocol import Simplex.Chat.Remote.AppVersion import Simplex.Chat.Remote.Types @@ -70,7 +71,7 @@ import Simplex.Chat.Util (liftIOEither) import Simplex.FileTransfer.Description (FileDescriptionURI) import Simplex.Messaging.Agent (AgentClient, SubscriptionsInfo) import Simplex.Messaging.Agent.Client (AgentLocks, AgentQueuesInfo (..), AgentWorkersDetails (..), AgentWorkersSummary (..), ProtocolTestFailure, SMPServerSubs, ServerQueueInfo, UserNetworkInfo) -import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, NetworkConfig, ServerCfg) +import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, NetworkConfig, OperatorId, ServerCfg) import Simplex.Messaging.Agent.Lock import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation, SQLiteStore, UpMigration, withTransaction, withTransactionPriority) @@ -352,6 +353,14 @@ data ChatCommand | SetUserProtoServers AProtoServersConfig | APITestProtoServer UserId AProtoServerWithAuth | TestProtoServer AProtoServerWithAuth + | APIGetServerOperators + | APISetServerOperators (NonEmpty (OperatorId, Bool)) + | APIGetUserServers UserId + | APISetUserServers UserId (NonEmpty UserServers) + | APIValidateServers (NonEmpty UserServers) -- response is CRUserServersValidation + | APIGetUsageConditions + | APISetConditionsNotified Int64 + | APIAcceptConditions Int64 (NonEmpty OperatorId) | APISetChatItemTTL UserId (Maybe Int64) | SetChatItemTTL (Maybe Int64) | APIGetChatItemTTL UserId @@ -577,8 +586,12 @@ data ChatResponse | CRChatItemInfo {user :: User, chatItem :: AChatItem, chatItemInfo :: ChatItemInfo} | CRChatItemId User (Maybe ChatItemId) | CRApiParsedMarkdown {formattedText :: Maybe MarkdownList} - | CRUserProtoServers {user :: User, servers :: AUserProtoServers} + | CRUserProtoServers {user :: User, servers :: AUserProtoServers, operators :: [ServerOperator]} | CRServerTestResult {user :: User, testServer :: AProtoServerWithAuth, testFailure :: Maybe ProtocolTestFailure} + | CRServerOperators {operators :: [ServerOperator], conditionsAction :: UsageConditionsAction} + | CRUserServers {userServers :: [UserServers]} + | CRUserServersValidation {serverErrors :: [UserServersError]} + | CRUsageConditions {usageConditions :: UsageConditions, conditionsText :: Text, acceptedConditions :: Maybe UsageConditions} | CRChatItemTTL {user :: User, chatItemTTL :: Maybe Int64} | CRNetworkConfig {networkConfig :: NetworkConfig} | CRContactInfo {user :: User, contact :: Contact, connectionStats_ :: Maybe ConnectionStats, customUserProfile :: Maybe Profile} @@ -948,6 +961,12 @@ data AProtoServersConfig = forall p. ProtocolTypeI p => APSC (SProtocolType p) ( deriving instance Show AProtoServersConfig +data UserServersError + = USEStorageMissing + | USEProxyMissing + | USEDuplicate {server :: AProtoServerWithAuth} + deriving (Show) + data UserProtoServers p = UserProtoServers { serverProtocol :: SProtocolType p, protoServers :: NonEmpty (ServerCfg p), @@ -1526,6 +1545,8 @@ $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "DB") ''DatabaseError) $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "Chat") ''ChatError) +$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "USE") ''UserServersError) + $(JQ.deriveJSON defaultJSON ''AppFilePathsConfig) $(JQ.deriveJSON defaultJSON ''ContactSubStatus) diff --git a/src/Simplex/Chat/Migrations/M20241027_server_operators.hs b/src/Simplex/Chat/Migrations/M20241027_server_operators.hs new file mode 100644 index 0000000000..bc9f40bddf --- /dev/null +++ b/src/Simplex/Chat/Migrations/M20241027_server_operators.hs @@ -0,0 +1,70 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Migrations.M20241027_server_operators where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20241027_server_operators :: Query +m20241027_server_operators = + [sql| +CREATE TABLE server_operators ( + server_operator_id INTEGER PRIMARY KEY AUTOINCREMENT, + server_operator_tag TEXT, + trade_name TEXT NOT NULL, + legal_name TEXT, + server_domains TEXT, + enabled INTEGER NOT NULL DEFAULT 1, + role_storage INTEGER NOT NULL DEFAULT 1, + role_proxy INTEGER NOT NULL DEFAULT 1, + accepted_conditions_commit TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +ALTER TABLE protocol_servers ADD COLUMN server_operator_id INTEGER REFERENCES server_operators ON DELETE SET NULL; + +CREATE TABLE usage_conditions ( + usage_conditions_id INTEGER PRIMARY KEY AUTOINCREMENT, + conditions_commit TEXT NOT NULL UNIQUE, + notified_at TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE TABLE operator_usage_conditions ( + operator_usage_conditions_id INTEGER PRIMARY KEY AUTOINCREMENT, + server_operator_id INTEGER REFERENCES server_operators (server_operator_id) ON DELETE SET NULL ON UPDATE CASCADE, + server_operator_tag TEXT, + conditions_commit TEXT NOT NULL, + accepted_at TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE INDEX idx_protocol_servers_server_operator_id ON protocol_servers(server_operator_id); +CREATE INDEX idx_operator_usage_conditions_server_operator_id ON operator_usage_conditions(server_operator_id); +CREATE UNIQUE INDEX idx_operator_usage_conditions_conditions_commit ON operator_usage_conditions(server_operator_id, conditions_commit); + +INSERT INTO server_operators + (server_operator_id, server_operator_tag, trade_name, legal_name, server_domains, enabled) + VALUES (1, 'simplex', 'SimpleX Chat', 'SimpleX Chat Ltd', 'simplex.im', 1); +INSERT INTO server_operators + (server_operator_id, server_operator_tag, trade_name, legal_name, server_domains, enabled) + VALUES (2, 'xyz', 'XYZ', 'XYZ Ltd', 'xyz.com', 0); + +-- UPDATE protocol_servers SET server_operator_id = 1 WHERE host LIKE "%.simplex.im" OR host LIKE "%.simplex.im,%"; +|] + +down_m20241027_server_operators :: Query +down_m20241027_server_operators = + [sql| +DROP INDEX idx_operator_usage_conditions_conditions_commit; +DROP INDEX idx_operator_usage_conditions_server_operator_id; +DROP INDEX idx_protocol_servers_server_operator_id; + +ALTER TABLE protocol_servers DROP COLUMN server_operator_id; + +DROP TABLE operator_usage_conditions; +DROP TABLE usage_conditions; +DROP TABLE server_operators; +|] diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index 2619a5c4e5..07c363eda9 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -450,6 +450,7 @@ CREATE TABLE IF NOT EXISTS "protocol_servers"( created_at TEXT NOT NULL DEFAULT(datetime('now')), updated_at TEXT NOT NULL DEFAULT(datetime('now')), protocol TEXT NOT NULL DEFAULT 'smp', + server_operator_id INTEGER REFERENCES server_operators ON DELETE SET NULL, UNIQUE(user_id, host, port) ); CREATE TABLE xftp_file_descriptions( @@ -589,6 +590,34 @@ CREATE TABLE note_folders( unread_chat INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE app_settings(app_settings TEXT NOT NULL); +CREATE TABLE server_operators( + server_operator_id INTEGER PRIMARY KEY AUTOINCREMENT, + server_operator_tag TEXT, + trade_name TEXT NOT NULL, + legal_name TEXT, + server_domains TEXT, + enabled INTEGER NOT NULL DEFAULT 1, + role_storage INTEGER NOT NULL DEFAULT 1, + role_proxy INTEGER NOT NULL DEFAULT 1, + accepted_conditions_commit TEXT, + created_at TEXT NOT NULL DEFAULT(datetime('now')), + updated_at TEXT NOT NULL DEFAULT(datetime('now')) +); +CREATE TABLE usage_conditions( + usage_conditions_id INTEGER PRIMARY KEY AUTOINCREMENT, + conditions_commit TEXT NOT NULL UNIQUE, + notified_at TEXT, + created_at TEXT NOT NULL DEFAULT(datetime('now')), + updated_at TEXT NOT NULL DEFAULT(datetime('now')) +); +CREATE TABLE operator_usage_conditions( + operator_usage_conditions_id INTEGER PRIMARY KEY AUTOINCREMENT, + server_operator_id INTEGER REFERENCES server_operators(server_operator_id) ON DELETE SET NULL ON UPDATE CASCADE, + server_operator_tag TEXT, + conditions_commit TEXT NOT NULL, + accepted_at TEXT, + created_at TEXT NOT NULL DEFAULT(datetime('now')) +); CREATE INDEX contact_profiles_index ON contact_profiles( display_name, full_name @@ -890,3 +919,13 @@ CREATE INDEX idx_received_probes_group_member_id on received_probes( group_member_id ); CREATE INDEX idx_contact_requests_contact_id ON contact_requests(contact_id); +CREATE INDEX idx_protocol_servers_server_operator_id ON protocol_servers( + server_operator_id +); +CREATE INDEX idx_operator_usage_conditions_server_operator_id ON operator_usage_conditions( + server_operator_id +); +CREATE UNIQUE INDEX idx_operator_usage_conditions_conditions_commit ON operator_usage_conditions( + server_operator_id, + conditions_commit +); diff --git a/src/Simplex/Chat/Operators.hs b/src/Simplex/Chat/Operators.hs new file mode 100644 index 0000000000..9a2dac0b1b --- /dev/null +++ b/src/Simplex/Chat/Operators.hs @@ -0,0 +1,110 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TemplateHaskell #-} + +module Simplex.Chat.Operators where + +import Data.Aeson (FromJSON (..), ToJSON (..)) +import qualified Data.Aeson as J +import qualified Data.Aeson.Encoding as JE +import qualified Data.Aeson.TH as JQ +import Data.FileEmbed +import Data.Int (Int64) +import Data.List.NonEmpty (NonEmpty) +import Data.Text (Text) +import Data.Time.Clock (UTCTime) +import Database.SQLite.Simple.FromField (FromField (..)) +import Database.SQLite.Simple.ToField (ToField (..)) +import Language.Haskell.TH.Syntax (lift) +import Simplex.Chat.Operators.Conditions +import Simplex.Chat.Types.Util (textParseJSON) +import Simplex.Messaging.Agent.Env.SQLite (OperatorId, ServerRoles) +import Simplex.Messaging.Encoding.String +import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, fromTextField_, sumTypeJSON) +import Simplex.Messaging.Protocol (ProtoServerWithAuth, ProtocolType (..)) +import Simplex.Messaging.Util (safeDecodeUtf8) + +usageConditionsCommit :: Text +usageConditionsCommit = "165143a1112308c035ac00ed669b96b60599aa1c" + +usageConditionsText :: Text +usageConditionsText = + $( let s = $(embedFile =<< makeRelativeToProject "PRIVACY.md") + in [|stripFrontMatter (safeDecodeUtf8 $(lift s))|] + ) + +data OperatorTag = OTSimplex | OTXyz + deriving (Show) + +instance FromField OperatorTag where fromField = fromTextField_ textDecode + +instance ToField OperatorTag where toField = toField . textEncode + +instance FromJSON OperatorTag where + parseJSON = textParseJSON "OperatorTag" + +instance ToJSON OperatorTag where + toJSON = J.String . textEncode + toEncoding = JE.text . textEncode + +instance TextEncoding OperatorTag where + textDecode = \case + "simplex" -> Just OTSimplex + "xyz" -> Just OTXyz + _ -> Nothing + textEncode = \case + OTSimplex -> "simplex" + OTXyz -> "xyz" + +data UsageConditions = UsageConditions + { conditionsId :: Int64, + conditionsCommit :: Text, + notifiedAt :: Maybe UTCTime, + createdAt :: UTCTime + } + deriving (Show) + +data UsageConditionsAction + = UCAReview {operators :: [ServerOperator], deadline :: Maybe UTCTime, showNotice :: Bool} + | UCAAccepted {operators :: [ServerOperator]} + deriving (Show) + +-- TODO UI logic +usageConditionsAction :: UsageConditionsAction +usageConditionsAction = UCAAccepted [] + +data ConditionsAcceptance + = CAAccepted {acceptedAt :: UTCTime} + | CARequired {deadline :: Maybe UTCTime} + deriving (Show) + +data ServerOperator = ServerOperator + { operatorId :: OperatorId, + operatorTag :: Maybe OperatorTag, + tradeName :: Text, + legalName :: Maybe Text, + serverDomains :: [Text], + acceptedConditions :: ConditionsAcceptance, + enabled :: Bool, + roles :: ServerRoles + } + deriving (Show) + +data UserServers = UserServers + { operator :: ServerOperator, + smpServers :: NonEmpty (ProtoServerWithAuth 'PSMP), + xftpServers :: NonEmpty (ProtoServerWithAuth 'PXFTP) + } + deriving (Show) + +$(JQ.deriveJSON defaultJSON ''UsageConditions) + +$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CA") ''ConditionsAcceptance) + +$(JQ.deriveJSON defaultJSON ''ServerOperator) + +$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "UCA") ''UsageConditionsAction) + +$(JQ.deriveJSON defaultJSON ''UserServers) diff --git a/src/Simplex/Chat/Operators/Conditions.hs b/src/Simplex/Chat/Operators/Conditions.hs new file mode 100644 index 0000000000..55cf8b658d --- /dev/null +++ b/src/Simplex/Chat/Operators/Conditions.hs @@ -0,0 +1,19 @@ +{-# LANGUAGE OverloadedStrings #-} + +module Simplex.Chat.Operators.Conditions where + +import Data.Char (isSpace) +import Data.Text (Text) +import qualified Data.Text as T + +stripFrontMatter :: Text -> Text +stripFrontMatter = + T.unlines + . dropWhile ("# " `T.isPrefixOf`) -- strip title + . dropWhile (T.all isSpace) + . dropWhile fm + . (\ls -> let ls' = dropWhile (not . fm) ls in if null ls' then ls else ls') + . dropWhile fm + . T.lines + where + fm = ("---" `T.isPrefixOf`) diff --git a/src/Simplex/Chat/Store/Migrations.hs b/src/Simplex/Chat/Store/Migrations.hs index e2d12e78d7..e33f2336cc 100644 --- a/src/Simplex/Chat/Store/Migrations.hs +++ b/src/Simplex/Chat/Store/Migrations.hs @@ -114,6 +114,7 @@ import Simplex.Chat.Migrations.M20240827_calls_uuid import Simplex.Chat.Migrations.M20240920_user_order import Simplex.Chat.Migrations.M20241008_indexes import Simplex.Chat.Migrations.M20241010_contact_requests_contact_id +import Simplex.Chat.Migrations.M20241027_server_operators import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -227,7 +228,8 @@ schemaMigrations = ("20240827_calls_uuid", m20240827_calls_uuid, Just down_m20240827_calls_uuid), ("20240920_user_order", m20240920_user_order, Just down_m20240920_user_order), ("20241008_indexes", m20241008_indexes, Just down_m20241008_indexes), - ("20241010_contact_requests_contact_id", m20241010_contact_requests_contact_id, Just down_m20241010_contact_requests_contact_id) + ("20241010_contact_requests_contact_id", m20241010_contact_requests_contact_id, Just down_m20241010_contact_requests_contact_id), + ("20241027_server_operators", m20241027_server_operators, Just down_m20241027_server_operators) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index fb9774a54e..fe2cc737fb 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -47,7 +47,9 @@ module Simplex.Chat.Store.Profiles getContactWithoutConnViaAddress, updateUserAddressAutoAccept, getProtocolServers, + -- overwriteOperatorsAndServers, overwriteProtocolServers, + getServerOperators, createCall, deleteCalls, getCalls, @@ -76,6 +78,7 @@ import Database.SQLite.Simple (NamedParam (..), Only (..), (:.) (..)) import Database.SQLite.Simple.QQ (sql) import Simplex.Chat.Call import Simplex.Chat.Messages +import Simplex.Chat.Operators import Simplex.Chat.Protocol import Simplex.Chat.Store.Direct import Simplex.Chat.Store.Shared @@ -83,7 +86,7 @@ import Simplex.Chat.Types import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Shared import Simplex.Chat.Types.UITheme -import Simplex.Messaging.Agent.Env.SQLite (ServerCfg (..)) +import Simplex.Messaging.Agent.Env.SQLite (OperatorId, ServerCfg (..), ServerRoles (..)) import Simplex.Messaging.Agent.Protocol (ACorrId, ConnId, UserId) import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow) import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB @@ -521,20 +524,25 @@ getProtocolServers db User {userId} = <$> DB.query db [sql| - SELECT host, port, key_hash, basic_auth, preset, tested, enabled - FROM protocol_servers - WHERE user_id = ? AND protocol = ?; + SELECT s.host, s.port, s.key_hash, s.basic_auth, s.server_operator_id, s.preset, s.tested, s.enabled, o.role_storage, o.role_proxy + FROM protocol_servers s + LEFT JOIN server_operators o USING (server_operator_id) + WHERE s.user_id = ? AND s.protocol = ? |] (userId, decodeLatin1 $ strEncode protocol) where protocol = protocolTypeI @p - toServerCfg :: (NonEmpty TransportHost, String, C.KeyHash, Maybe Text, Bool, Maybe Bool, Bool) -> ServerCfg p - toServerCfg (host, port, keyHash, auth_, preset, tested, enabled) = + toServerCfg :: (NonEmpty TransportHost, String, C.KeyHash, Maybe Text, Maybe OperatorId, Bool, Maybe Bool, Bool, Maybe Bool, Maybe Bool) -> ServerCfg p + toServerCfg (host, port, keyHash, auth_, operator, preset, tested, enabled, storage_, proxy_) = let server = ProtoServerWithAuth (ProtocolServer protocol host port keyHash) (BasicAuth . encodeUtf8 <$> auth_) - in ServerCfg {server, preset, tested, enabled} + roles = ServerRoles {storage = fromMaybe True storage_, proxy = fromMaybe True proxy_} + in ServerCfg {server, operator, preset, tested, enabled, roles} -overwriteProtocolServers :: forall p. ProtocolTypeI p => DB.Connection -> User -> [ServerCfg p] -> ExceptT StoreError IO () +-- overwriteOperatorsAndServers :: forall p. ProtocolTypeI p => DB.Connection -> User -> Maybe [ServerOperator] -> [ServerCfg p] -> ExceptT StoreError IO [ServerCfg p] +-- overwriteOperatorsAndServers db user@User {userId} operators_ servers = do +overwriteProtocolServers :: forall p. ProtocolTypeI p => DB.Connection -> User -> [ServerCfg p] -> ExceptT StoreError IO () overwriteProtocolServers db User {userId} servers = + -- liftIO $ mapM_ (updateServerOperators_ db) operators_ checkConstraint SEUniqueID . ExceptT $ do currentTs <- getCurrentTime DB.execute db "DELETE FROM protocol_servers WHERE user_id = ? AND protocol = ? " (userId, protocol) @@ -549,9 +557,62 @@ overwriteProtocolServers db User {userId} servers = |] ((protocol, host, port, keyHash, safeDecodeUtf8 . unBasicAuth <$> auth_) :. (preset, tested, enabled, userId, currentTs, currentTs)) pure $ Right () + -- Right <$> getProtocolServers db user where protocol = decodeLatin1 $ strEncode $ protocolTypeI @p +getServerOperators :: DB.Connection -> UTCTime -> IO [ServerOperator] +getServerOperators db ts = + map toOperator + <$> DB.query_ + db + [sql| + SELECT server_operator_id, server_operator_tag, trade_name, legal_name, server_domains, enabled, role_storage, role_proxy + FROM server_operators; + |] + where + -- TODO get conditions state + toOperator (operatorId, operatorTag, tradeName, legalName, domains, enabled, storage, proxy) = + let roles = ServerRoles {storage, proxy} + in ServerOperator {operatorId, operatorTag, tradeName, legalName, serverDomains = [domains], acceptedConditions = CAAccepted ts, enabled, roles} + +-- updateServerOperators_ :: DB.Connection -> [ServerOperator] -> IO [ServerOperator] +-- updateServerOperators_ db operators = do +-- DB.execute_ db "DELETE FROM server_operators WHERE preset = 0" +-- let (existing, new) = partition (isJust . operatorId) operators +-- existing' <- mapM (\op -> upsertExisting op $> op) existing +-- new' <- mapM insertNew new +-- pure $ existing' <> new' +-- where +-- upsertExisting ServerOperator {operatorId, name, preset, enabled, roles = ServerRoles {storage, proxy}} +-- | preset = +-- DB.execute +-- db +-- [sql| +-- UPDATE server_operators +-- SET enabled = ?, role_storage = ?, role_proxy = ? +-- WHERE server_operator_id = ? +-- |] +-- (enabled, storage, proxy, operatorId) +-- | otherwise = +-- DB.execute +-- db +-- [sql| +-- INSERT INTO server_operators (server_operator_id, name, preset, enabled, role_storage, role_proxy) +-- VALUES (?,?,?,?,?,?) +-- |] +-- (operatorId, name, preset, enabled, storage, proxy) +-- insertNew op@ServerOperator {name, preset, enabled, roles = ServerRoles {storage, proxy}} = do +-- DB.execute +-- db +-- [sql| +-- INSERT INTO server_operators (name, preset, enabled, role_storage, role_proxy) +-- VALUES (?,?,?,?,?) +-- |] +-- (name, preset, enabled, storage, proxy) +-- opId <- insertedRowId db +-- pure op {operatorId = Just opId} + createCall :: DB.Connection -> User -> Call -> UTCTime -> IO () createCall db user@User {userId} Call {contactId, callId, callUUID, chatItemId, callState} callTs = do currentTs <- getCurrentTime diff --git a/src/Simplex/Chat/Terminal.hs b/src/Simplex/Chat/Terminal.hs index 5cc695db04..e38a34d45f 100644 --- a/src/Simplex/Chat/Terminal.hs +++ b/src/Simplex/Chat/Terminal.hs @@ -13,7 +13,7 @@ import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) import Database.SQLite.Simple (SQLError (..)) import qualified Database.SQLite.Simple as DB -import Simplex.Chat (defaultChatConfig) +import Simplex.Chat (defaultChatConfig, operatorSimpleXChat) import Simplex.Chat.Controller import Simplex.Chat.Core import Simplex.Chat.Help (chatWelcome) @@ -21,7 +21,7 @@ import Simplex.Chat.Options import Simplex.Chat.Terminal.Input import Simplex.Chat.Terminal.Output import Simplex.FileTransfer.Client.Presets (defaultXFTPServers) -import Simplex.Messaging.Agent.Env.SQLite (presetServerCfg) +import Simplex.Messaging.Agent.Env.SQLite (allRoles, presetServerCfg) import Simplex.Messaging.Client (NetworkConfig (..), SMPProxyFallback (..), SMPProxyMode (..), defaultNetworkConfig) import Simplex.Messaging.Util (raceAny_) import System.IO (hFlush, hSetEcho, stdin, stdout) @@ -34,14 +34,14 @@ terminalChatConfig = { smp = L.fromList $ map - (presetServerCfg True) + (presetServerCfg True allRoles operatorSimpleXChat) [ "smp://u2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU=@smp4.simplex.im,o5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion", "smp://hpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg=@smp5.simplex.im,jjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion", "smp://PQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo=@smp6.simplex.im,bylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion" ], useSMP = 3, ntf = ["ntf://FB-Uop7RTaZZEG0ZLD2CIaTjsPh-Fw0zFAnb7QyA8Ks=@ntf2.simplex.im,ntg7jdjy2i3qbib3sykiho3enekwiaqg3icctliqhtqcg6jmoh6cxiad.onion"], - xftp = L.map (presetServerCfg True) defaultXFTPServers, + xftp = L.map (presetServerCfg True allRoles operatorSimpleXChat) defaultXFTPServers, useXFTP = L.length defaultXFTPServers, netCfg = defaultNetworkConfig diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index ade36476c7..c53e5a2749 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -19,7 +19,7 @@ import qualified Data.ByteString.Lazy.Char8 as LB import Data.Char (isSpace, toUpper) import Data.Function (on) import Data.Int (Int64) -import Data.List (groupBy, intercalate, intersperse, partition, sortOn) +import Data.List (foldl', groupBy, intercalate, intersperse, partition, sortOn) import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.List.NonEmpty as L import Data.Map.Strict (Map) @@ -42,6 +42,7 @@ import Simplex.Chat.Help import Simplex.Chat.Markdown import Simplex.Chat.Messages hiding (NewChatItem (..)) import Simplex.Chat.Messages.CIContent +import Simplex.Chat.Operators import Simplex.Chat.Protocol import Simplex.Chat.Remote.AppVersion (AppVersion (..), pattern AppVersionRange) import Simplex.Chat.Remote.Types @@ -95,8 +96,12 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRChats chats -> viewChats ts tz chats CRApiChat u chat -> ttyUser u $ if testView then testViewChat chat else [viewJSON chat] CRApiParsedMarkdown ft -> [viewJSON ft] - CRUserProtoServers u userServers -> ttyUser u $ viewUserServers userServers testView + CRUserProtoServers u userServers operators -> ttyUser u $ viewUserServers userServers operators testView CRServerTestResult u srv testFailure -> ttyUser u $ viewServerTestResult srv testFailure + CRServerOperators {} -> [] + CRUserServers {} -> [] + CRUserServersValidation _ -> [] + CRUsageConditions {} -> [] CRChatItemTTL u ttl -> ttyUser u $ viewChatItemTTL ttl CRNetworkConfig cfg -> viewNetworkConfig cfg CRContactInfo u ct cStats customUserProfile -> ttyUser u $ viewContactInfo ct cStats customUserProfile @@ -1209,8 +1214,8 @@ viewUserPrivacy User {userId} User {userId = userId', localDisplayName = n', sho "profile is " <> if isJust viewPwdHash then "hidden" else "visible" ] -viewUserServers :: AUserProtoServers -> Bool -> [StyledString] -viewUserServers (AUPS UserProtoServers {serverProtocol = p, protoServers, presetServers}) testView = +viewUserServers :: AUserProtoServers -> [ServerOperator] -> Bool -> [StyledString] +viewUserServers (AUPS UserProtoServers {serverProtocol = p, protoServers, presetServers}) operators testView = customServers <> if testView then [] @@ -1228,8 +1233,8 @@ viewUserServers (AUPS UserProtoServers {serverProtocol = p, protoServers, preset pName = protocolName p customServers = if null protoServers - then ("no " <> pName <> " servers saved, using presets: ") : viewServers presetServers - else viewServers protoServers + then ("no " <> pName <> " servers saved, using presets: ") : viewServers operators presetServers + else viewServers operators protoServers protocolName :: ProtocolTypeI p => SProtocolType p -> StyledString protocolName = plain . map toUpper . T.unpack . decodeLatin1 . strEncode @@ -1326,8 +1331,11 @@ viewConnectionStats ConnectionStats {rcvQueuesInfo, sndQueuesInfo} = ["receiving messages via: " <> viewRcvQueuesInfo rcvQueuesInfo | not $ null rcvQueuesInfo] <> ["sending messages via: " <> viewSndQueuesInfo sndQueuesInfo | not $ null sndQueuesInfo] -viewServers :: ProtocolTypeI p => NonEmpty (ServerCfg p) -> [StyledString] -viewServers = map (plain . B.unpack . strEncode . (\ServerCfg {server} -> server)) . L.toList +viewServers :: ProtocolTypeI p => [ServerOperator] -> NonEmpty (ServerCfg p) -> [StyledString] +viewServers operators = map (plain . (\ServerCfg {server, operator} -> B.unpack (strEncode server) <> viewOperator operator)) . L.toList + where + ops :: Map (Maybe Int64) Text = foldl' (\m ServerOperator {operatorId, tradeName} -> M.insert (Just operatorId) tradeName m) M.empty operators + viewOperator = maybe "" $ \op -> " (operator " <> maybe (show op) T.unpack (M.lookup (Just op) ops) <> ")" viewRcvQueuesInfo :: [RcvQueueInfo] -> StyledString viewRcvQueuesInfo = plain . intercalate ", " . map showQueueInfo diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index 75b85d7a5f..d435af186e 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -423,11 +423,10 @@ smpServerCfg = ServerConfig { transports = [(serverPort, transport @TLS, False)], tbqSize = 1, - -- serverTbqSize = 1, - msgQueueQuota = 16, msgStoreType = AMSType SMSMemory, - maxJournalMsgCount = 1000, - maxJournalStateLines = 1000, + msgQueueQuota = 16, + maxJournalMsgCount = 24, + maxJournalStateLines = 4, queueIdBytes = 12, msgIdBytes = 6, storeLogFile = Nothing, diff --git a/tests/RandomServers.hs b/tests/RandomServers.hs index 0c6baa71bb..e0b1939c9e 100644 --- a/tests/RandomServers.hs +++ b/tests/RandomServers.hs @@ -9,7 +9,7 @@ import Control.Monad (replicateM) import qualified Data.List.NonEmpty as L import Simplex.Chat (cfgServers, cfgServersToUse, defaultChatConfig, randomServers) import Simplex.Chat.Controller (ChatConfig (..)) -import Simplex.Messaging.Agent.Env.SQLite (ServerCfg (..)) +import Simplex.Messaging.Agent.Env.SQLite (ServerCfg (..), ServerRoles (..)) import Simplex.Messaging.Protocol (ProtoServerWithAuth (..), SProtocolType (..), UserProtocol) import Test.Hspec @@ -18,6 +18,8 @@ randomServersTests = describe "choosig random servers" $ do it "should choose 4 random SMP servers and keep the rest disabled" testRandomSMPServers it "should keep all 6 XFTP servers" testRandomXFTPServers +deriving instance Eq ServerRoles + deriving instance Eq (ServerCfg p) testRandomSMPServers :: IO () From bdaec30fa084e4c18964035d432abc42a55288a7 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Mon, 4 Nov 2024 21:11:03 +0400 Subject: [PATCH 059/567] core: getServerOperators, getUserServers, getUsageConditions apis wip (#5141) --- src/Simplex/Chat.hs | 31 +++++++------ src/Simplex/Chat/Controller.hs | 2 +- src/Simplex/Chat/Operators.hs | 38 ++++++++++++---- src/Simplex/Chat/Store/Profiles.hs | 73 ++++++++++++++++++++++++------ src/Simplex/Chat/Store/Shared.hs | 1 + 5 files changed, 107 insertions(+), 38 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 380f6c5d24..bd165ea5e6 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1489,8 +1489,7 @@ processChatCommand' vr = \case APIGetUserProtoServers userId (AProtocolType p) -> withUserId userId $ \user -> withServerProtocol p $ do cfg@ChatConfig {defaultServers} <- asks config srvs <- withFastStore' (`getProtocolServers` user) - ts <- liftIO getCurrentTime - operators <- withFastStore' $ \db -> getServerOperators db ts + operators <- withFastStore $ \db -> getServerOperators db let servers = AUPS $ UserProtoServers p (useServers cfg p srvs) (cfgServers p defaultServers) pure $ CRUserProtoServers {user, servers, operators} GetUserProtoServers aProtocol -> withUser $ \User {userId} -> @@ -1508,27 +1507,31 @@ processChatCommand' vr = \case lift $ CRServerTestResult user srv <$> withAgent' (\a -> testProtocolServer a (aUserId user) server) TestProtoServer srv -> withUser $ \User {userId} -> processChatCommand $ APITestProtoServer userId srv - APIGetServerOperators -> pure $ chatCmdError Nothing "not supported" + APIGetServerOperators -> do + operators <- withFastStore $ \db -> getServerOperators db + let conditionsAction = usageConditionsAction operators + pure $ CRServerOperators operators conditionsAction APISetServerOperators _operators -> pure $ chatCmdError Nothing "not supported" - APIGetUserServers userId -> withUserId userId $ \user -> - pure $ chatCmdError (Just user) "not supported" + APIGetUserServers userId -> withUserId userId $ \user -> do + (operators, smpServers, xftpServers) <- withFastStore $ \db -> do + operators <- getServerOperators db + smpServers <- liftIO $ getServers db user SPSMP + xftpServers <- liftIO $ getServers db user SPXFTP + pure (operators, smpServers, xftpServers) + let userServers = groupByOperator operators smpServers xftpServers + pure $ CRUserServers user userServers + where + getServers :: (ProtocolTypeI p) => DB.Connection -> User -> SProtocolType p -> IO [ServerCfg p] + getServers db user _p = getProtocolServers db user APISetUserServers userId _userServers -> withUserId userId $ \user -> pure $ chatCmdError (Just user) "not supported" APIValidateServers _userServers -> -- response is CRUserServersValidation pure $ chatCmdError Nothing "not supported" APIGetUsageConditions -> do + usageConditions <- withFastStore $ \db -> getCurrentUsageConditions db -- TODO - -- get current conditions -- get latest accepted conditions (from operators) - ts <- liftIO getCurrentTime - let usageConditions = - UsageConditions - { conditionsId = 1, - conditionsCommit = "abc", - notifiedAt = Nothing, - createdAt = ts - } pure CRUsageConditions { usageConditions = usageConditions, diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index bd2cee3e50..2cb8e0cd42 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -589,7 +589,7 @@ data ChatResponse | CRUserProtoServers {user :: User, servers :: AUserProtoServers, operators :: [ServerOperator]} | CRServerTestResult {user :: User, testServer :: AProtoServerWithAuth, testFailure :: Maybe ProtocolTestFailure} | CRServerOperators {operators :: [ServerOperator], conditionsAction :: UsageConditionsAction} - | CRUserServers {userServers :: [UserServers]} + | CRUserServers {user :: User, userServers :: [UserServers]} | CRUserServersValidation {serverErrors :: [UserServersError]} | CRUsageConditions {usageConditions :: UsageConditions, conditionsText :: Text, acceptedConditions :: Maybe UsageConditions} | CRChatItemTTL {user :: User, chatItemTTL :: Maybe Int64} diff --git a/src/Simplex/Chat/Operators.hs b/src/Simplex/Chat/Operators.hs index 9a2dac0b1b..ff110e2ada 100644 --- a/src/Simplex/Chat/Operators.hs +++ b/src/Simplex/Chat/Operators.hs @@ -1,6 +1,7 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE LambdaCase #-} +{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} @@ -12,7 +13,9 @@ import qualified Data.Aeson.Encoding as JE import qualified Data.Aeson.TH as JQ import Data.FileEmbed import Data.Int (Int64) -import Data.List.NonEmpty (NonEmpty) +import Data.Map.Strict (Map) +import qualified Data.Map.Strict as M +import Data.Maybe (fromMaybe) import Data.Text (Text) import Data.Time.Clock (UTCTime) import Database.SQLite.Simple.FromField (FromField (..)) @@ -20,10 +23,10 @@ import Database.SQLite.Simple.ToField (ToField (..)) import Language.Haskell.TH.Syntax (lift) import Simplex.Chat.Operators.Conditions import Simplex.Chat.Types.Util (textParseJSON) -import Simplex.Messaging.Agent.Env.SQLite (OperatorId, ServerRoles) +import Simplex.Messaging.Agent.Env.SQLite (OperatorId, ServerCfg (..), ServerRoles) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, fromTextField_, sumTypeJSON) -import Simplex.Messaging.Protocol (ProtoServerWithAuth, ProtocolType (..)) +import Simplex.Messaging.Protocol (ProtocolType (..)) import Simplex.Messaging.Util (safeDecodeUtf8) usageConditionsCommit :: Text @@ -72,8 +75,8 @@ data UsageConditionsAction deriving (Show) -- TODO UI logic -usageConditionsAction :: UsageConditionsAction -usageConditionsAction = UCAAccepted [] +usageConditionsAction :: [ServerOperator] -> UsageConditionsAction +usageConditionsAction _operators = UCAAccepted [] data ConditionsAcceptance = CAAccepted {acceptedAt :: UTCTime} @@ -93,12 +96,31 @@ data ServerOperator = ServerOperator deriving (Show) data UserServers = UserServers - { operator :: ServerOperator, - smpServers :: NonEmpty (ProtoServerWithAuth 'PSMP), - xftpServers :: NonEmpty (ProtoServerWithAuth 'PXFTP) + { operator :: Maybe ServerOperator, + smpServers :: [ServerCfg 'PSMP], + xftpServers :: [ServerCfg 'PXFTP] } deriving (Show) +groupByOperator :: [ServerOperator] -> [ServerCfg 'PSMP] -> [ServerCfg 'PXFTP] -> [UserServers] +groupByOperator srvOperators smpSrvs xftpSrvs = + map createOperatorServers (M.toList combinedMap) + where + srvOperatorId :: ServerCfg p -> Maybe Int64 + srvOperatorId ServerCfg {operator} = operator + operatorMap :: Map (Maybe Int64) (Maybe ServerOperator) + operatorMap = M.fromList [(Just (operatorId op), Just op) | op <- srvOperators] `M.union` M.singleton Nothing Nothing + initialMap :: Map (Maybe Int64) ([ServerCfg 'PSMP], [ServerCfg 'PXFTP]) + initialMap = M.fromList [(key, ([], [])) | key <- M.keys operatorMap] + smpsMap = foldr (\server acc -> M.adjust (\(smps, xftps) -> (server : smps, xftps)) (srvOperatorId server) acc) initialMap smpSrvs + combinedMap = foldr (\server acc -> M.adjust (\(smps, xftps) -> (smps, server : xftps)) (srvOperatorId server) acc) smpsMap xftpSrvs + createOperatorServers (key, (groupedSmps, groupedXftps)) = + UserServers + { operator = fromMaybe Nothing (M.lookup key operatorMap), + smpServers = groupedSmps, + xftpServers = groupedXftps + } + $(JQ.deriveJSON defaultJSON ''UsageConditions) $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CA") ''ConditionsAcceptance) diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index fe2cc737fb..d6627505f3 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -50,6 +50,7 @@ module Simplex.Chat.Store.Profiles -- overwriteOperatorsAndServers, overwriteProtocolServers, getServerOperators, + getCurrentUsageConditions, createCall, deleteCalls, getCalls, @@ -73,7 +74,8 @@ import qualified Data.List.NonEmpty as L import Data.Maybe (fromMaybe) import Data.Text (Text) import Data.Text.Encoding (decodeLatin1, encodeUtf8) -import Data.Time.Clock (UTCTime (..), getCurrentTime) +import Data.Time (addUTCTime) +import Data.Time.Clock (UTCTime (..), getCurrentTime, nominalDay) import Database.SQLite.Simple (NamedParam (..), Only (..), (:.) (..)) import Database.SQLite.Simple.QQ (sql) import Simplex.Chat.Call @@ -540,7 +542,7 @@ getProtocolServers db User {userId} = -- overwriteOperatorsAndServers :: forall p. ProtocolTypeI p => DB.Connection -> User -> Maybe [ServerOperator] -> [ServerCfg p] -> ExceptT StoreError IO [ServerCfg p] -- overwriteOperatorsAndServers db user@User {userId} operators_ servers = do -overwriteProtocolServers :: forall p. ProtocolTypeI p => DB.Connection -> User -> [ServerCfg p] -> ExceptT StoreError IO () +overwriteProtocolServers :: forall p. ProtocolTypeI p => DB.Connection -> User -> [ServerCfg p] -> ExceptT StoreError IO () overwriteProtocolServers db User {userId} servers = -- liftIO $ mapM_ (updateServerOperators_ db) operators_ checkConstraint SEUniqueID . ExceptT $ do @@ -556,25 +558,66 @@ overwriteProtocolServers db User {userId} servers = VALUES (?,?,?,?,?,?,?,?,?,?,?) |] ((protocol, host, port, keyHash, safeDecodeUtf8 . unBasicAuth <$> auth_) :. (preset, tested, enabled, userId, currentTs, currentTs)) - pure $ Right () -- Right <$> getProtocolServers db user + pure $ Right () where protocol = decodeLatin1 $ strEncode $ protocolTypeI @p -getServerOperators :: DB.Connection -> UTCTime -> IO [ServerOperator] -getServerOperators db ts = - map toOperator - <$> DB.query_ - db - [sql| - SELECT server_operator_id, server_operator_tag, trade_name, legal_name, server_domains, enabled, role_storage, role_proxy - FROM server_operators; +getServerOperators :: DB.Connection -> ExceptT StoreError IO [ServerOperator] +getServerOperators db = do + conditions <- getCurrentUsageConditions db + liftIO $ + map (toOperator conditions) + <$> DB.query_ + db + [sql| + SELECT + so.server_operator_id, so.server_operator_tag, so.trade_name, so.legal_name, + so.server_domains, so.enabled, so.role_storage, so.role_proxy, + LastOperatorConditions.conditions_commit, LastOperatorConditions.accepted_at + FROM server_operators so + LEFT JOIN ( + SELECT server_operator_id, conditions_commit, accepted_at, MAX(operator_usage_conditions_id) + FROM operator_usage_conditions + GROUP BY server_operator_id + ) LastOperatorConditions ON LastOperatorConditions.server_operator_id = so.server_operator_id |] where - -- TODO get conditions state - toOperator (operatorId, operatorTag, tradeName, legalName, domains, enabled, storage, proxy) = - let roles = ServerRoles {storage, proxy} - in ServerOperator {operatorId, operatorTag, tradeName, legalName, serverDomains = [domains], acceptedConditions = CAAccepted ts, enabled, roles} + toOperator :: + UsageConditions -> + ( (OperatorId, Maybe OperatorTag, Text, Maybe Text, Text, Bool, Bool, Bool) + :. (Maybe Text, Maybe UTCTime) + ) -> + ServerOperator + toOperator + UsageConditions {conditionsCommit, createdAt} + ( (operatorId, operatorTag, tradeName, legalName, domains, enabled, storage, proxy) + :. (operatorConditionsCommit_, acceptedAt_) + ) = + let roles = ServerRoles {storage, proxy} + acceptedConditions = case (operatorConditionsCommit_, acceptedAt_) of + (Nothing, _) -> CARequired Nothing + (Just operatorConditionsCommit, Just acceptedAt) + | conditionsCommit == operatorConditionsCommit -> CAAccepted acceptedAt + _ -> CARequired (Just $ conditionsDeadline createdAt) + in ServerOperator {operatorId, operatorTag, tradeName, legalName, serverDomains = [domains], acceptedConditions, enabled, roles} + conditionsDeadline :: UTCTime -> UTCTime + conditionsDeadline = addUTCTime (31 * nominalDay) + +getCurrentUsageConditions :: DB.Connection -> ExceptT StoreError IO UsageConditions +getCurrentUsageConditions db = + ExceptT . firstRow toUsageConditions SEUsageConditionsNotFound $ + DB.query_ + db + [sql| + SELECT usage_conditions_id, conditions_commit, notified_at, created_at + FROM usage_conditions + ORDER BY usage_conditions_id DESC LIMIT 1 + |] + +toUsageConditions :: (Int64, Text, Maybe UTCTime, UTCTime) -> UsageConditions +toUsageConditions (conditionsId, conditionsCommit, notifiedAt, createdAt) = + UsageConditions {conditionsId, conditionsCommit, notifiedAt, createdAt} -- updateServerOperators_ :: DB.Connection -> [ServerOperator] -> IO [ServerOperator] -- updateServerOperators_ db operators = do diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index f9a8685ec8..083079e2ea 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -127,6 +127,7 @@ data StoreError | SERemoteCtrlNotFound {remoteCtrlId :: RemoteCtrlId} | SERemoteCtrlDuplicateCA | SEProhibitedDeleteUser {userId :: UserId, contactId :: ContactId} + | SEUsageConditionsNotFound deriving (Show, Exception) $(J.deriveJSON (sumTypeJSON $ dropPrefix "SE") ''StoreError) From 3b0205b25f5a3377d0b5c6162f21d6cc82b4565a Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 5 Nov 2024 14:15:20 +0400 Subject: [PATCH 060/567] core: setServerOperators, getUsageConditions api wip (#5145) --- src/Simplex/Chat.hs | 16 ++-- src/Simplex/Chat/Controller.hs | 2 +- .../Migrations/M20241027_server_operators.hs | 10 +- src/Simplex/Chat/Migrations/chat_schema.sql | 2 +- src/Simplex/Chat/Operators.hs | 15 ++- src/Simplex/Chat/Store/Profiles.hs | 91 ++++++++++++++++--- 6 files changed, 105 insertions(+), 31 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index bd165ea5e6..b083134e2c 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1511,7 +1511,10 @@ processChatCommand' vr = \case operators <- withFastStore $ \db -> getServerOperators db let conditionsAction = usageConditionsAction operators pure $ CRServerOperators operators conditionsAction - APISetServerOperators _operators -> pure $ chatCmdError Nothing "not supported" + APISetServerOperators operatorsEnabled -> do + operators <- withFastStore $ \db -> setServerOperators db operatorsEnabled + let conditionsAction = usageConditionsAction operators + pure $ CRServerOperators operators conditionsAction APIGetUserServers userId -> withUserId userId $ \user -> do (operators, smpServers, xftpServers) <- withFastStore $ \db -> do operators <- getServerOperators db @@ -1529,14 +1532,15 @@ processChatCommand' vr = \case -- response is CRUserServersValidation pure $ chatCmdError Nothing "not supported" APIGetUsageConditions -> do - usageConditions <- withFastStore $ \db -> getCurrentUsageConditions db - -- TODO - -- get latest accepted conditions (from operators) + (usageConditions, acceptedConditions) <- withFastStore $ \db -> do + usageConditions <- getCurrentUsageConditions db + acceptedConditions <- getLatestAcceptedConditions db + pure (usageConditions, acceptedConditions) pure CRUsageConditions - { usageConditions = usageConditions, + { usageConditions, conditionsText = usageConditionsText, - acceptedConditions = Nothing + acceptedConditions } APISetConditionsNotified _conditionsId -> do pure $ chatCmdError Nothing "not supported" diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 2cb8e0cd42..81e7a9980b 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -354,7 +354,7 @@ data ChatCommand | APITestProtoServer UserId AProtoServerWithAuth | TestProtoServer AProtoServerWithAuth | APIGetServerOperators - | APISetServerOperators (NonEmpty (OperatorId, Bool)) + | APISetServerOperators (NonEmpty OperatorEnabled) | APIGetUserServers UserId | APISetUserServers UserId (NonEmpty UserServers) | APIValidateServers (NonEmpty UserServers) -- response is CRUserServersValidation diff --git a/src/Simplex/Chat/Migrations/M20241027_server_operators.hs b/src/Simplex/Chat/Migrations/M20241027_server_operators.hs index bc9f40bddf..fc0ca21e54 100644 --- a/src/Simplex/Chat/Migrations/M20241027_server_operators.hs +++ b/src/Simplex/Chat/Migrations/M20241027_server_operators.hs @@ -11,13 +11,13 @@ m20241027_server_operators = CREATE TABLE server_operators ( server_operator_id INTEGER PRIMARY KEY AUTOINCREMENT, server_operator_tag TEXT, + app_vendor INTEGER NOT NULL, trade_name TEXT NOT NULL, legal_name TEXT, server_domains TEXT, enabled INTEGER NOT NULL DEFAULT 1, role_storage INTEGER NOT NULL DEFAULT 1, role_proxy INTEGER NOT NULL DEFAULT 1, - accepted_conditions_commit TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now')) ); @@ -46,11 +46,11 @@ CREATE INDEX idx_operator_usage_conditions_server_operator_id ON operator_usage_ CREATE UNIQUE INDEX idx_operator_usage_conditions_conditions_commit ON operator_usage_conditions(server_operator_id, conditions_commit); INSERT INTO server_operators - (server_operator_id, server_operator_tag, trade_name, legal_name, server_domains, enabled) - VALUES (1, 'simplex', 'SimpleX Chat', 'SimpleX Chat Ltd', 'simplex.im', 1); + (server_operator_id, server_operator_tag, app_vendor, trade_name, legal_name, server_domains, enabled) + VALUES (1, 'simplex', 1, 'SimpleX Chat', 'SimpleX Chat Ltd', 'simplex.im', 1); INSERT INTO server_operators - (server_operator_id, server_operator_tag, trade_name, legal_name, server_domains, enabled) - VALUES (2, 'xyz', 'XYZ', 'XYZ Ltd', 'xyz.com', 0); + (server_operator_id, server_operator_tag, app_vendor, trade_name, legal_name, server_domains, enabled) + VALUES (2, 'xyz', 0, 'XYZ', 'XYZ Ltd', 'xyz.com', 0); -- UPDATE protocol_servers SET server_operator_id = 1 WHERE host LIKE "%.simplex.im" OR host LIKE "%.simplex.im,%"; |] diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index 07c363eda9..1541f36b60 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -593,13 +593,13 @@ CREATE TABLE app_settings(app_settings TEXT NOT NULL); CREATE TABLE server_operators( server_operator_id INTEGER PRIMARY KEY AUTOINCREMENT, server_operator_tag TEXT, + app_vendor INTEGER NOT NULL, trade_name TEXT NOT NULL, legal_name TEXT, server_domains TEXT, enabled INTEGER NOT NULL DEFAULT 1, role_storage INTEGER NOT NULL DEFAULT 1, role_proxy INTEGER NOT NULL DEFAULT 1, - accepted_conditions_commit TEXT, created_at TEXT NOT NULL DEFAULT(datetime('now')), updated_at TEXT NOT NULL DEFAULT(datetime('now')) ); diff --git a/src/Simplex/Chat/Operators.hs b/src/Simplex/Chat/Operators.hs index ff110e2ada..6fc5663085 100644 --- a/src/Simplex/Chat/Operators.hs +++ b/src/Simplex/Chat/Operators.hs @@ -79,7 +79,7 @@ usageConditionsAction :: [ServerOperator] -> UsageConditionsAction usageConditionsAction _operators = UCAAccepted [] data ConditionsAcceptance - = CAAccepted {acceptedAt :: UTCTime} + = CAAccepted {acceptedAt :: Maybe UTCTime} | CARequired {deadline :: Maybe UTCTime} deriving (Show) @@ -89,7 +89,14 @@ data ServerOperator = ServerOperator tradeName :: Text, legalName :: Maybe Text, serverDomains :: [Text], - acceptedConditions :: ConditionsAcceptance, + conditionsAcceptance :: ConditionsAcceptance, + enabled :: Bool, + roles :: ServerRoles + } + deriving (Show) + +data OperatorEnabled = OperatorEnabled + { operatorId :: OperatorId, enabled :: Bool, roles :: ServerRoles } @@ -106,10 +113,10 @@ groupByOperator :: [ServerOperator] -> [ServerCfg 'PSMP] -> [ServerCfg 'PXFTP] - groupByOperator srvOperators smpSrvs xftpSrvs = map createOperatorServers (M.toList combinedMap) where - srvOperatorId :: ServerCfg p -> Maybe Int64 srvOperatorId ServerCfg {operator} = operator + opId ServerOperator {operatorId} = operatorId operatorMap :: Map (Maybe Int64) (Maybe ServerOperator) - operatorMap = M.fromList [(Just (operatorId op), Just op) | op <- srvOperators] `M.union` M.singleton Nothing Nothing + operatorMap = M.fromList [(Just (opId op), Just op) | op <- srvOperators] `M.union` M.singleton Nothing Nothing initialMap :: Map (Maybe Int64) ([ServerCfg 'PSMP], [ServerCfg 'PXFTP]) initialMap = M.fromList [(key, ([], [])) | key <- M.keys operatorMap] smpsMap = foldr (\server acc -> M.adjust (\(smps, xftps) -> (server : smps, xftps)) (srvOperatorId server) acc) initialMap smpSrvs diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index d6627505f3..259d08d9ad 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -50,7 +50,9 @@ module Simplex.Chat.Store.Profiles -- overwriteOperatorsAndServers, overwriteProtocolServers, getServerOperators, + setServerOperators, getCurrentUsageConditions, + getLatestAcceptedConditions, createCall, deleteCalls, getCalls, @@ -72,7 +74,7 @@ import Data.Int (Int64) import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as L import Data.Maybe (fromMaybe) -import Data.Text (Text) +import Data.Text (Text, splitOn) import Data.Text.Encoding (decodeLatin1, encodeUtf8) import Data.Time (addUTCTime) import Data.Time.Clock (UTCTime (..), getCurrentTime, nominalDay) @@ -565,44 +567,80 @@ overwriteProtocolServers db User {userId} servers = getServerOperators :: DB.Connection -> ExceptT StoreError IO [ServerOperator] getServerOperators db = do - conditions <- getCurrentUsageConditions db + now <- liftIO getCurrentTime + currentConditions <- getCurrentUsageConditions db + latestAcceptedConditions <- getLatestAcceptedConditions db liftIO $ - map (toOperator conditions) + map (toOperator now currentConditions latestAcceptedConditions) <$> DB.query_ db [sql| SELECT so.server_operator_id, so.server_operator_tag, so.trade_name, so.legal_name, so.server_domains, so.enabled, so.role_storage, so.role_proxy, - LastOperatorConditions.conditions_commit, LastOperatorConditions.accepted_at + AcceptedConditions.conditions_commit, AcceptedConditions.accepted_at FROM server_operators so LEFT JOIN ( SELECT server_operator_id, conditions_commit, accepted_at, MAX(operator_usage_conditions_id) FROM operator_usage_conditions GROUP BY server_operator_id - ) LastOperatorConditions ON LastOperatorConditions.server_operator_id = so.server_operator_id + ) AcceptedConditions ON AcceptedConditions.server_operator_id = so.server_operator_id |] where toOperator :: + UTCTime -> UsageConditions -> + Maybe UsageConditions -> ( (OperatorId, Maybe OperatorTag, Text, Maybe Text, Text, Bool, Bool, Bool) :. (Maybe Text, Maybe UTCTime) ) -> ServerOperator toOperator - UsageConditions {conditionsCommit, createdAt} + now + UsageConditions {conditionsCommit = currentCommit, createdAt, notifiedAt} + latestAcceptedConditions_ ( (operatorId, operatorTag, tradeName, legalName, domains, enabled, storage, proxy) - :. (operatorConditionsCommit_, acceptedAt_) + :. (operatorCommit_, acceptedAt_) ) = let roles = ServerRoles {storage, proxy} - acceptedConditions = case (operatorConditionsCommit_, acceptedAt_) of + serverDomains = splitOn "," domains + conditionsAcceptance = case (latestAcceptedConditions_, operatorCommit_) of + -- no conditions were ever accepted for any operator(s) + -- (shouldn't happen as there should always be record for SimpleX Chat) (Nothing, _) -> CARequired Nothing - (Just operatorConditionsCommit, Just acceptedAt) - | conditionsCommit == operatorConditionsCommit -> CAAccepted acceptedAt - _ -> CARequired (Just $ conditionsDeadline createdAt) - in ServerOperator {operatorId, operatorTag, tradeName, legalName, serverDomains = [domains], acceptedConditions, enabled, roles} - conditionsDeadline :: UTCTime -> UTCTime - conditionsDeadline = addUTCTime (31 * nominalDay) + -- no conditions were ever accepted for this operator + (_, Nothing) -> CARequired Nothing + (Just UsageConditions {conditionsCommit = latestAcceptedCommit}, Just operatorCommit) + | latestAcceptedCommit == currentCommit -> + if operatorCommit == latestAcceptedCommit + then -- current conditions were accepted for operator + CAAccepted acceptedAt_ + else -- current conditions were NOT accepted for operator, but were accepted for other operator(s) + CARequired Nothing + | otherwise -> + if operatorCommit == latestAcceptedCommit + then -- new conditions available, latest accepted conditions were accepted for operator + conditionsRequiredOrDeadline createdAt (fromMaybe now notifiedAt) + else -- new conditions available, latest accepted conditions were NOT accepted for operator (were accepted for other operator(s)) + CARequired Nothing + in ServerOperator {operatorId, operatorTag, tradeName, legalName, serverDomains, conditionsAcceptance, enabled, roles} + conditionsRequiredOrDeadline :: UTCTime -> UTCTime -> ConditionsAcceptance + conditionsRequiredOrDeadline createdAt notifiedAtOrNow = + if notifiedAtOrNow < addUTCTime (14 * nominalDay) createdAt + then CARequired (Just $ conditionsDeadline notifiedAtOrNow) + else CARequired Nothing + where + conditionsDeadline :: UTCTime -> UTCTime + conditionsDeadline = addUTCTime (31 * nominalDay) + +setServerOperators :: DB.Connection -> NonEmpty OperatorEnabled -> ExceptT StoreError IO [ServerOperator] +setServerOperators db operatorsEnabled = do + liftIO $ forM_ operatorsEnabled $ \OperatorEnabled {operatorId, enabled, roles = ServerRoles {storage, proxy}} -> + DB.execute + db + "UPDATE server_operators SET enabled = ?, role_storage = ?, role_proxy = ? WHERE server_operator_id = ?" + (enabled, storage, proxy, operatorId) + getServerOperators db getCurrentUsageConditions :: DB.Connection -> ExceptT StoreError IO UsageConditions getCurrentUsageConditions db = @@ -619,6 +657,31 @@ toUsageConditions :: (Int64, Text, Maybe UTCTime, UTCTime) -> UsageConditions toUsageConditions (conditionsId, conditionsCommit, notifiedAt, createdAt) = UsageConditions {conditionsId, conditionsCommit, notifiedAt, createdAt} +getLatestAcceptedConditions :: DB.Connection -> ExceptT StoreError IO (Maybe UsageConditions) +getLatestAcceptedConditions db = do + (latestAcceptedCommit_ :: Maybe Text) <- + liftIO $ + maybeFirstRow fromOnly $ + DB.query_ + db + [sql| + SELECT conditions_commit + FROM operator_usage_conditions + WHERE conditions_accepted = 1 + ORDER BY accepted_at DESC + LIMIT 1 + |] + forM latestAcceptedCommit_ $ \latestAcceptedCommit -> + ExceptT . firstRow toUsageConditions SEUsageConditionsNotFound $ + DB.query + db + [sql| + SELECT usage_conditions_id, conditions_commit, notified_at, created_at + FROM usage_conditions + WHERE conditions_commit = ? + |] + (Only latestAcceptedCommit) + -- updateServerOperators_ :: DB.Connection -> [ServerOperator] -> IO [ServerOperator] -- updateServerOperators_ db operators = do -- DB.execute_ db "DELETE FROM server_operators WHERE preset = 0" From 2da89c2cf1d155e228979c6460c5dddb0cca73c3 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 5 Nov 2024 21:40:33 +0400 Subject: [PATCH 061/567] core: setConditionsNotified, acceptConditions, setUserServers, validateServers apis wip (#5147) --- src/Simplex/Chat.hs | 39 +++++---- src/Simplex/Chat/Controller.hs | 14 +--- src/Simplex/Chat/Operators.hs | 65 +++++++++++++-- src/Simplex/Chat/Store/Profiles.hs | 127 ++++++++++++++++++++++------- 4 files changed, 181 insertions(+), 64 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index b083134e2c..69b78ba9d4 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1489,7 +1489,7 @@ processChatCommand' vr = \case APIGetUserProtoServers userId (AProtocolType p) -> withUserId userId $ \user -> withServerProtocol p $ do cfg@ChatConfig {defaultServers} <- asks config srvs <- withFastStore' (`getProtocolServers` user) - operators <- withFastStore $ \db -> getServerOperators db + (operators, _) <- withFastStore $ \db -> getServerOperators db let servers = AUPS $ UserProtoServers p (useServers cfg p srvs) (cfgServers p defaultServers) pure $ CRUserProtoServers {user, servers, operators} GetUserProtoServers aProtocol -> withUser $ \User {userId} -> @@ -1508,44 +1508,51 @@ processChatCommand' vr = \case TestProtoServer srv -> withUser $ \User {userId} -> processChatCommand $ APITestProtoServer userId srv APIGetServerOperators -> do - operators <- withFastStore $ \db -> getServerOperators db - let conditionsAction = usageConditionsAction operators + (operators, conditionsAction) <- withFastStore $ \db -> getServerOperators db pure $ CRServerOperators operators conditionsAction APISetServerOperators operatorsEnabled -> do - operators <- withFastStore $ \db -> setServerOperators db operatorsEnabled - let conditionsAction = usageConditionsAction operators + (operators, conditionsAction) <- withFastStore $ \db -> setServerOperators db operatorsEnabled pure $ CRServerOperators operators conditionsAction APIGetUserServers userId -> withUserId userId $ \user -> do (operators, smpServers, xftpServers) <- withFastStore $ \db -> do - operators <- getServerOperators db + (operators, _) <- getServerOperators db smpServers <- liftIO $ getServers db user SPSMP xftpServers <- liftIO $ getServers db user SPXFTP pure (operators, smpServers, xftpServers) let userServers = groupByOperator operators smpServers xftpServers pure $ CRUserServers user userServers where - getServers :: (ProtocolTypeI p) => DB.Connection -> User -> SProtocolType p -> IO [ServerCfg p] + getServers :: ProtocolTypeI p => DB.Connection -> User -> SProtocolType p -> IO [ServerCfg p] getServers db user _p = getProtocolServers db user - APISetUserServers userId _userServers -> withUserId userId $ \user -> - pure $ chatCmdError (Just user) "not supported" - APIValidateServers _userServers -> - -- response is CRUserServersValidation - pure $ chatCmdError Nothing "not supported" + APISetUserServers userId userServers -> withUserId userId $ \user -> do + let errors = validateUserServers userServers + unless (null errors) $ throwChatError (CECommandError $ "user servers validation error(s): " <> show errors) + withFastStore $ \db -> setUserServers db user userServers + -- TODO set protocol servers for agent + ok_ + APIValidateServers userServers -> do + let errors = validateUserServers userServers + pure $ CRUserServersValidation errors APIGetUsageConditions -> do (usageConditions, acceptedConditions) <- withFastStore $ \db -> do usageConditions <- getCurrentUsageConditions db acceptedConditions <- getLatestAcceptedConditions db pure (usageConditions, acceptedConditions) + -- TODO if db commit is different from source commit, conditionsText should be nothing in response pure CRUsageConditions { usageConditions, conditionsText = usageConditionsText, acceptedConditions } - APISetConditionsNotified _conditionsId -> do - pure $ chatCmdError Nothing "not supported" - APIAcceptConditions _conditionsId _opIds -> - pure $ chatCmdError Nothing "not supported" + APISetConditionsNotified conditionsId -> do + currentTs <- liftIO getCurrentTime + withFastStore' $ \db -> setConditionsNotified db conditionsId currentTs + ok_ + APIAcceptConditions conditionsId operators -> do + currentTs <- liftIO getCurrentTime + (operators', conditionsAction) <- withFastStore $ \db -> acceptConditions db conditionsId operators currentTs + pure $ CRServerOperators operators' conditionsAction APISetChatItemTTL userId newTTL_ -> withUserId userId $ \user -> checkStoreNotChanged $ withChatLock "setChatItemTTL" $ do diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 81e7a9980b..cbfa0969d4 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -71,7 +71,7 @@ import Simplex.Chat.Util (liftIOEither) import Simplex.FileTransfer.Description (FileDescriptionURI) import Simplex.Messaging.Agent (AgentClient, SubscriptionsInfo) import Simplex.Messaging.Agent.Client (AgentLocks, AgentQueuesInfo (..), AgentWorkersDetails (..), AgentWorkersSummary (..), ProtocolTestFailure, SMPServerSubs, ServerQueueInfo, UserNetworkInfo) -import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, NetworkConfig, OperatorId, ServerCfg) +import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, NetworkConfig, ServerCfg) import Simplex.Messaging.Agent.Lock import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation, SQLiteStore, UpMigration, withTransaction, withTransactionPriority) @@ -360,7 +360,7 @@ data ChatCommand | APIValidateServers (NonEmpty UserServers) -- response is CRUserServersValidation | APIGetUsageConditions | APISetConditionsNotified Int64 - | APIAcceptConditions Int64 (NonEmpty OperatorId) + | APIAcceptConditions Int64 (NonEmpty ServerOperator) | APISetChatItemTTL UserId (Maybe Int64) | SetChatItemTTL (Maybe Int64) | APIGetChatItemTTL UserId @@ -588,7 +588,7 @@ data ChatResponse | CRApiParsedMarkdown {formattedText :: Maybe MarkdownList} | CRUserProtoServers {user :: User, servers :: AUserProtoServers, operators :: [ServerOperator]} | CRServerTestResult {user :: User, testServer :: AProtoServerWithAuth, testFailure :: Maybe ProtocolTestFailure} - | CRServerOperators {operators :: [ServerOperator], conditionsAction :: UsageConditionsAction} + | CRServerOperators {operators :: [ServerOperator], conditionsAction :: Maybe UsageConditionsAction} | CRUserServers {user :: User, userServers :: [UserServers]} | CRUserServersValidation {serverErrors :: [UserServersError]} | CRUsageConditions {usageConditions :: UsageConditions, conditionsText :: Text, acceptedConditions :: Maybe UsageConditions} @@ -961,12 +961,6 @@ data AProtoServersConfig = forall p. ProtocolTypeI p => APSC (SProtocolType p) ( deriving instance Show AProtoServersConfig -data UserServersError - = USEStorageMissing - | USEProxyMissing - | USEDuplicate {server :: AProtoServerWithAuth} - deriving (Show) - data UserProtoServers p = UserProtoServers { serverProtocol :: SProtocolType p, protoServers :: NonEmpty (ServerCfg p), @@ -1545,8 +1539,6 @@ $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "DB") ''DatabaseError) $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "Chat") ''ChatError) -$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "USE") ''UserServersError) - $(JQ.deriveJSON defaultJSON ''AppFilePathsConfig) $(JQ.deriveJSON defaultJSON ''ContactSubStatus) diff --git a/src/Simplex/Chat/Operators.hs b/src/Simplex/Chat/Operators.hs index 6fc5663085..5e32807ddc 100644 --- a/src/Simplex/Chat/Operators.hs +++ b/src/Simplex/Chat/Operators.hs @@ -13,20 +13,22 @@ import qualified Data.Aeson.Encoding as JE import qualified Data.Aeson.TH as JQ import Data.FileEmbed import Data.Int (Int64) +import Data.List.NonEmpty (NonEmpty) import Data.Map.Strict (Map) import qualified Data.Map.Strict as M -import Data.Maybe (fromMaybe) +import Data.Maybe (fromMaybe, isNothing) import Data.Text (Text) -import Data.Time.Clock (UTCTime) +import Data.Time (addUTCTime) +import Data.Time.Clock (UTCTime, nominalDay) import Database.SQLite.Simple.FromField (FromField (..)) import Database.SQLite.Simple.ToField (ToField (..)) import Language.Haskell.TH.Syntax (lift) import Simplex.Chat.Operators.Conditions import Simplex.Chat.Types.Util (textParseJSON) -import Simplex.Messaging.Agent.Env.SQLite (OperatorId, ServerCfg (..), ServerRoles) +import Simplex.Messaging.Agent.Env.SQLite (OperatorId, ServerCfg (..), ServerRoles (..)) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, fromTextField_, sumTypeJSON) -import Simplex.Messaging.Protocol (ProtocolType (..)) +import Simplex.Messaging.Protocol (AProtoServerWithAuth, ProtocolType (..)) import Simplex.Messaging.Util (safeDecodeUtf8) usageConditionsCommit :: Text @@ -74,9 +76,30 @@ data UsageConditionsAction | UCAAccepted {operators :: [ServerOperator]} deriving (Show) --- TODO UI logic -usageConditionsAction :: [ServerOperator] -> UsageConditionsAction -usageConditionsAction _operators = UCAAccepted [] +usageConditionsAction :: [ServerOperator] -> UsageConditions -> UTCTime -> Maybe UsageConditionsAction +usageConditionsAction operators UsageConditions {createdAt, notifiedAt} now = do + let enabledOperators = filter (\ServerOperator {enabled} -> enabled) operators + if null enabledOperators + then Nothing + else + if all conditionsAccepted enabledOperators + then + let acceptedForOperators = filter conditionsAccepted operators + in Just $ UCAAccepted acceptedForOperators + else + let acceptForOperators = filter (not . conditionsAccepted) enabledOperators + deadline = conditionsRequiredOrDeadline createdAt (fromMaybe now notifiedAt) + showNotice = isNothing notifiedAt + in Just $ UCAReview acceptForOperators deadline showNotice + +conditionsRequiredOrDeadline :: UTCTime -> UTCTime -> Maybe UTCTime +conditionsRequiredOrDeadline createdAt notifiedAtOrNow = + if notifiedAtOrNow < addUTCTime (14 * nominalDay) createdAt + then Just $ conditionsDeadline notifiedAtOrNow + else Nothing -- required + where + conditionsDeadline :: UTCTime -> UTCTime + conditionsDeadline = addUTCTime (31 * nominalDay) data ConditionsAcceptance = CAAccepted {acceptedAt :: Maybe UTCTime} @@ -95,6 +118,11 @@ data ServerOperator = ServerOperator } deriving (Show) +conditionsAccepted :: ServerOperator -> Bool +conditionsAccepted ServerOperator {conditionsAcceptance} = case conditionsAcceptance of + CAAccepted {} -> True + _ -> False + data OperatorEnabled = OperatorEnabled { operatorId :: OperatorId, enabled :: Bool, @@ -128,6 +156,27 @@ groupByOperator srvOperators smpSrvs xftpSrvs = xftpServers = groupedXftps } +data UserServersError + = USEStorageMissing + | USEProxyMissing + | USEDuplicate {server :: AProtoServerWithAuth} + deriving (Show) + +validateUserServers :: NonEmpty UserServers -> [UserServersError] +validateUserServers userServers = + let storageMissing_ = if any (canUseForRole storage) userServers then [] else [USEStorageMissing] + proxyMissing_ = if any (canUseForRole proxy) userServers then [] else [USEProxyMissing] + -- TODO duplicate errors + -- allSMPServers = + -- map (\ServerCfg {server} -> server) $ + -- concatMap (\UserServers {smpServers} -> smpServers) userServers + in storageMissing_ <> proxyMissing_ -- <> duplicateErrors + where + canUseForRole :: (ServerRoles -> Bool) -> UserServers -> Bool + canUseForRole roleSel UserServers {operator, smpServers, xftpServers} = case operator of + Just ServerOperator {roles} -> roleSel roles + Nothing -> not (null smpServers) && not (null xftpServers) + $(JQ.deriveJSON defaultJSON ''UsageConditions) $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CA") ''ConditionsAcceptance) @@ -137,3 +186,5 @@ $(JQ.deriveJSON defaultJSON ''ServerOperator) $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "UCA") ''UsageConditionsAction) $(JQ.deriveJSON defaultJSON ''UserServers) + +$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "USE") ''UserServersError) diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index 259d08d9ad..f4f574c3d7 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -53,6 +53,9 @@ module Simplex.Chat.Store.Profiles setServerOperators, getCurrentUsageConditions, getLatestAcceptedConditions, + setConditionsNotified, + acceptConditions, + setUserServers, createCall, deleteCalls, getCalls, @@ -76,8 +79,7 @@ import qualified Data.List.NonEmpty as L import Data.Maybe (fromMaybe) import Data.Text (Text, splitOn) import Data.Text.Encoding (decodeLatin1, encodeUtf8) -import Data.Time (addUTCTime) -import Data.Time.Clock (UTCTime (..), getCurrentTime, nominalDay) +import Data.Time.Clock (UTCTime (..), getCurrentTime) import Database.SQLite.Simple (NamedParam (..), Only (..), (:.) (..)) import Database.SQLite.Simple.QQ (sql) import Simplex.Chat.Call @@ -542,6 +544,7 @@ getProtocolServers db User {userId} = roles = ServerRoles {storage = fromMaybe True storage_, proxy = fromMaybe True proxy_} in ServerCfg {server, operator, preset, tested, enabled, roles} +-- TODO remove -- overwriteOperatorsAndServers :: forall p. ProtocolTypeI p => DB.Connection -> User -> Maybe [ServerOperator] -> [ServerCfg p] -> ExceptT StoreError IO [ServerCfg p] -- overwriteOperatorsAndServers db user@User {userId} operators_ servers = do overwriteProtocolServers :: forall p. ProtocolTypeI p => DB.Connection -> User -> [ServerCfg p] -> ExceptT StoreError IO () @@ -565,27 +568,29 @@ overwriteProtocolServers db User {userId} servers = where protocol = decodeLatin1 $ strEncode $ protocolTypeI @p -getServerOperators :: DB.Connection -> ExceptT StoreError IO [ServerOperator] +getServerOperators :: DB.Connection -> ExceptT StoreError IO ([ServerOperator], Maybe UsageConditionsAction) getServerOperators db = do now <- liftIO getCurrentTime currentConditions <- getCurrentUsageConditions db latestAcceptedConditions <- getLatestAcceptedConditions db - liftIO $ - map (toOperator now currentConditions latestAcceptedConditions) - <$> DB.query_ - db - [sql| - SELECT - so.server_operator_id, so.server_operator_tag, so.trade_name, so.legal_name, - so.server_domains, so.enabled, so.role_storage, so.role_proxy, - AcceptedConditions.conditions_commit, AcceptedConditions.accepted_at - FROM server_operators so - LEFT JOIN ( - SELECT server_operator_id, conditions_commit, accepted_at, MAX(operator_usage_conditions_id) - FROM operator_usage_conditions - GROUP BY server_operator_id - ) AcceptedConditions ON AcceptedConditions.server_operator_id = so.server_operator_id - |] + operators <- + liftIO $ + map (toOperator now currentConditions latestAcceptedConditions) + <$> DB.query_ + db + [sql| + SELECT + so.server_operator_id, so.server_operator_tag, so.trade_name, so.legal_name, + so.server_domains, so.enabled, so.role_storage, so.role_proxy, + AcceptedConditions.conditions_commit, AcceptedConditions.accepted_at + FROM server_operators so + LEFT JOIN ( + SELECT server_operator_id, conditions_commit, accepted_at, MAX(operator_usage_conditions_id) + FROM operator_usage_conditions + GROUP BY server_operator_id + ) AcceptedConditions ON AcceptedConditions.server_operator_id = so.server_operator_id + |] + pure (operators, usageConditionsAction operators currentConditions now) where toOperator :: UTCTime -> @@ -620,20 +625,12 @@ getServerOperators db = do | otherwise -> if operatorCommit == latestAcceptedCommit then -- new conditions available, latest accepted conditions were accepted for operator - conditionsRequiredOrDeadline createdAt (fromMaybe now notifiedAt) + CARequired $ conditionsRequiredOrDeadline createdAt (fromMaybe now notifiedAt) else -- new conditions available, latest accepted conditions were NOT accepted for operator (were accepted for other operator(s)) CARequired Nothing in ServerOperator {operatorId, operatorTag, tradeName, legalName, serverDomains, conditionsAcceptance, enabled, roles} - conditionsRequiredOrDeadline :: UTCTime -> UTCTime -> ConditionsAcceptance - conditionsRequiredOrDeadline createdAt notifiedAtOrNow = - if notifiedAtOrNow < addUTCTime (14 * nominalDay) createdAt - then CARequired (Just $ conditionsDeadline notifiedAtOrNow) - else CARequired Nothing - where - conditionsDeadline :: UTCTime -> UTCTime - conditionsDeadline = addUTCTime (31 * nominalDay) -setServerOperators :: DB.Connection -> NonEmpty OperatorEnabled -> ExceptT StoreError IO [ServerOperator] +setServerOperators :: DB.Connection -> NonEmpty OperatorEnabled -> ExceptT StoreError IO ([ServerOperator], Maybe UsageConditionsAction) setServerOperators db operatorsEnabled = do liftIO $ forM_ operatorsEnabled $ \OperatorEnabled {operatorId, enabled, roles = ServerRoles {storage, proxy}} -> DB.execute @@ -667,7 +664,6 @@ getLatestAcceptedConditions db = do [sql| SELECT conditions_commit FROM operator_usage_conditions - WHERE conditions_accepted = 1 ORDER BY accepted_at DESC LIMIT 1 |] @@ -682,6 +678,77 @@ getLatestAcceptedConditions db = do |] (Only latestAcceptedCommit) +setConditionsNotified :: DB.Connection -> Int64 -> UTCTime -> IO () +setConditionsNotified db conditionsId notifiedAt = + DB.execute db "UPDATE usage_conditions SET notified_at = ? WHERE usage_conditions_id = ?" (notifiedAt, conditionsId) + +acceptConditions :: DB.Connection -> Int64 -> NonEmpty ServerOperator -> UTCTime -> ExceptT StoreError IO ([ServerOperator], Maybe UsageConditionsAction) +acceptConditions db conditionsId operators acceptedAt = do + UsageConditions {conditionsCommit} <- getUsageConditionsById_ db conditionsId + liftIO $ forM_ operators $ \ServerOperator {operatorId, operatorTag} -> + DB.execute + db + [sql| + INSERT INTO operator_usage_conditions + (server_operator_id, server_operator_tag, conditions_commit, accepted_at) + VALUES (?,?,?,?) + |] + (operatorId, operatorTag, conditionsCommit, acceptedAt) + getServerOperators db + +getUsageConditionsById_ :: DB.Connection -> Int64 -> ExceptT StoreError IO UsageConditions +getUsageConditionsById_ db conditionsId = + ExceptT . firstRow toUsageConditions SEUsageConditionsNotFound $ + DB.query + db + [sql| + SELECT usage_conditions_id, conditions_commit, notified_at, created_at + FROM usage_conditions + WHERE usage_conditions_id = ? + |] + (Only conditionsId) + +setUserServers :: DB.Connection -> User -> NonEmpty UserServers -> ExceptT StoreError IO () +setUserServers db User {userId} userServers = do + currentTs <- liftIO getCurrentTime + forM_ userServers $ do + \UserServers {operator, smpServers, xftpServers} -> do + forM_ operator $ \op -> liftIO $ updateOperator currentTs op + overwriteServers currentTs operator smpServers + overwriteServers currentTs operator xftpServers + where + updateOperator :: UTCTime -> ServerOperator -> IO () + updateOperator currentTs ServerOperator {operatorId, enabled, roles = ServerRoles {storage, proxy}} = + DB.execute + db + [sql| + UPDATE server_operators + SET enabled = ?, role_storage = ?, role_proxy = ?, updated_at = ? + WHERE server_operator_id = ? + |] + (enabled, storage, proxy, operatorId, currentTs) + overwriteServers :: forall p. ProtocolTypeI p => UTCTime -> Maybe ServerOperator -> [ServerCfg p] -> ExceptT StoreError IO () + overwriteServers currentTs serverOperator servers = + checkConstraint SEUniqueID . ExceptT $ do + case serverOperator of + Nothing -> + DB.execute db "DELETE FROM protocol_servers WHERE user_id = ? AND server_operator_id IS NULL AND protocol = ?" (userId, protocol) + Just ServerOperator {operatorId} -> + DB.execute db "DELETE FROM protocol_servers WHERE user_id = ? AND server_operator_id = ? AND protocol = ?" (userId, operatorId, protocol) + forM_ servers $ \ServerCfg {server, operator, preset, tested, enabled} -> do + let ProtoServerWithAuth ProtocolServer {host, port, keyHash} auth_ = server + DB.execute + db + [sql| + INSERT INTO protocol_servers + (protocol, host, port, key_hash, basic_auth, operator, preset, tested, enabled, user_id, created_at, updated_at) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?) + |] + ((protocol, host, port, keyHash, safeDecodeUtf8 . unBasicAuth <$> auth_, operator) :. (preset, tested, enabled, userId, currentTs, currentTs)) + pure $ Right () + where + protocol = decodeLatin1 $ strEncode $ protocolTypeI @p + -- updateServerOperators_ :: DB.Connection -> [ServerOperator] -> IO [ServerOperator] -- updateServerOperators_ db operators = do -- DB.execute_ db "DELETE FROM server_operators WHERE preset = 0" From 8396e70e7b82b111f2d2e156d10a92dea4883319 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 6 Nov 2024 16:13:08 +0400 Subject: [PATCH 062/567] core: validate servers - find servers with duplicate hosts (#5150) --- src/Simplex/Chat/Operators.hs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/Simplex/Chat/Operators.hs b/src/Simplex/Chat/Operators.hs index 5e32807ddc..cedc3ca6d1 100644 --- a/src/Simplex/Chat/Operators.hs +++ b/src/Simplex/Chat/Operators.hs @@ -14,6 +14,7 @@ import qualified Data.Aeson.TH as JQ import Data.FileEmbed import Data.Int (Int64) import Data.List.NonEmpty (NonEmpty) +import qualified Data.List.NonEmpty as L import Data.Map.Strict (Map) import qualified Data.Map.Strict as M import Data.Maybe (fromMaybe, isNothing) @@ -28,7 +29,7 @@ import Simplex.Chat.Types.Util (textParseJSON) import Simplex.Messaging.Agent.Env.SQLite (OperatorId, ServerCfg (..), ServerRoles (..)) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, fromTextField_, sumTypeJSON) -import Simplex.Messaging.Protocol (AProtoServerWithAuth, ProtocolType (..)) +import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), ProtoServerWithAuth (..), ProtocolServer (..), ProtocolType (..), SProtocolType (..)) import Simplex.Messaging.Util (safeDecodeUtf8) usageConditionsCommit :: Text @@ -159,23 +160,34 @@ groupByOperator srvOperators smpSrvs xftpSrvs = data UserServersError = USEStorageMissing | USEProxyMissing - | USEDuplicate {server :: AProtoServerWithAuth} + | USEDuplicateSMP {server :: AProtoServerWithAuth} + | USEDuplicateXFTP {server :: AProtoServerWithAuth} deriving (Show) validateUserServers :: NonEmpty UserServers -> [UserServersError] validateUserServers userServers = let storageMissing_ = if any (canUseForRole storage) userServers then [] else [USEStorageMissing] proxyMissing_ = if any (canUseForRole proxy) userServers then [] else [USEProxyMissing] - -- TODO duplicate errors - -- allSMPServers = - -- map (\ServerCfg {server} -> server) $ - -- concatMap (\UserServers {smpServers} -> smpServers) userServers - in storageMissing_ <> proxyMissing_ -- <> duplicateErrors + + allSMPServers = map (\ServerCfg {server} -> server) $ concatMap (\UserServers {smpServers} -> smpServers) userServers + duplicateSMPServers = findDuplicatesByHost allSMPServers + duplicateSMPErrors = map (USEDuplicateSMP . AProtoServerWithAuth SPSMP) duplicateSMPServers + + allXFTPServers = map (\ServerCfg {server} -> server) $ concatMap (\UserServers {xftpServers} -> xftpServers) userServers + duplicateXFTPServers = findDuplicatesByHost allXFTPServers + duplicateXFTPErrors = map (USEDuplicateXFTP . AProtoServerWithAuth SPXFTP) duplicateXFTPServers + in storageMissing_ <> proxyMissing_ <> duplicateSMPErrors <> duplicateXFTPErrors where canUseForRole :: (ServerRoles -> Bool) -> UserServers -> Bool canUseForRole roleSel UserServers {operator, smpServers, xftpServers} = case operator of Just ServerOperator {roles} -> roleSel roles Nothing -> not (null smpServers) && not (null xftpServers) + findDuplicatesByHost :: [ProtoServerWithAuth p] -> [ProtoServerWithAuth p] + findDuplicatesByHost servers = + let allHosts = concatMap (L.toList . host . protoServer) servers + hostCounts = M.fromListWith (+) [(host, 1 :: Int) | host <- allHosts] + duplicateHosts = M.keys $ M.filter (> 1) hostCounts + in filter (\srv -> any (`elem` duplicateHosts) (L.toList $ host . protoServer $ srv)) servers $(JQ.deriveJSON defaultJSON ''UsageConditions) From ef0f21a11c082c9f3b9b5bc77d2d70c6bd9ee5ce Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 8 Nov 2024 14:45:00 +0400 Subject: [PATCH 063/567] core: operator apis commands (#5155) --- src/Simplex/Chat.hs | 8 ++++++++ src/Simplex/Chat/Operators.hs | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 69b78ba9d4..dad3a6f813 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -8140,6 +8140,14 @@ chatCommandP = "/_servers " *> (APIGetUserProtoServers <$> A.decimal <* A.space <*> strP), "/smp" $> GetUserProtoServers (AProtocolType SPSMP), "/xftp" $> GetUserProtoServers (AProtocolType SPXFTP), + "/_operators" $> APIGetServerOperators, + "/_operators " *> (APISetServerOperators <$> jsonP), + "/_user_servers " *> (APIGetUserServers <$> A.decimal), + "/_user_servers " *> (APISetUserServers <$> A.decimal <* A.space <*> jsonP), + "/_validate_servers " *> (APIValidateServers <$> jsonP), + "/_conditions" $> APIGetUsageConditions, + "/_conditions_notified " *> (APISetConditionsNotified <$> A.decimal), + "/_accept_conditions " *> (APIAcceptConditions <$> A.decimal <* A.space <*> jsonP), "/_ttl " *> (APISetChatItemTTL <$> A.decimal <* A.space <*> ciTTLDecimal), "/ttl " *> (SetChatItemTTL <$> ciTTL), "/_ttl " *> (APIGetChatItemTTL <$> A.decimal), diff --git a/src/Simplex/Chat/Operators.hs b/src/Simplex/Chat/Operators.hs index cedc3ca6d1..b3f92caaf9 100644 --- a/src/Simplex/Chat/Operators.hs +++ b/src/Simplex/Chat/Operators.hs @@ -195,6 +195,8 @@ $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CA") ''ConditionsAcceptance) $(JQ.deriveJSON defaultJSON ''ServerOperator) +$(JQ.deriveJSON defaultJSON ''OperatorEnabled) + $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "UCA") ''UsageConditionsAction) $(JQ.deriveJSON defaultJSON ''UserServers) From 2d588949b1f397d683dbca195ca3330983fef0e4 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 10 Nov 2024 15:21:33 +0000 Subject: [PATCH 064/567] directory service: additional commands (#5159) * directory service: additional commands * notify superusers * 48 hours * replace T.elem --- apps/simplex-bot-advanced/Main.hs | 10 +- .../src/Broadcast/Bot.hs | 5 +- .../src/Broadcast/Options.hs | 9 +- .../src/Directory/Events.hs | 57 ++-- .../src/Directory/Options.hs | 14 +- .../src/Directory/Service.hs | 269 +++++++++++------- src/Simplex/Chat/Bot.hs | 16 +- src/Simplex/Chat/Bot/KnownContacts.hs | 4 +- tests/Bots/DirectoryTests.hs | 39 ++- 9 files changed, 259 insertions(+), 164 deletions(-) diff --git a/apps/simplex-bot-advanced/Main.hs b/apps/simplex-bot-advanced/Main.hs index 4733dafb79..cedbd4fe34 100644 --- a/apps/simplex-bot-advanced/Main.hs +++ b/apps/simplex-bot-advanced/Main.hs @@ -9,6 +9,7 @@ module Main where import Control.Concurrent.Async import Control.Concurrent.STM import Control.Monad +import Data.Text (Text) import qualified Data.Text as T import Simplex.Chat.Bot import Simplex.Chat.Controller @@ -18,6 +19,7 @@ import Simplex.Chat.Messages.CIContent import Simplex.Chat.Options import Simplex.Chat.Terminal (terminalChatConfig) import Simplex.Chat.Types +import Simplex.Messaging.Util (tshow) import System.Directory (getAppUserDataDirectory) import Text.Read @@ -34,7 +36,7 @@ welcomeGetOpts = do putStrLn $ "db: " <> dbFilePrefix <> "_chat.db, " <> dbFilePrefix <> "_agent.db" pure opts -welcomeMessage :: String +welcomeMessage :: Text welcomeMessage = "Hello! I am a simple squaring bot.\nIf you send me a number, I will calculate its square" mySquaringBot :: User -> ChatController -> IO () @@ -47,10 +49,10 @@ mySquaringBot _user cc = do contactConnected contact sendMessage cc contact welcomeMessage CRNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content = mc@CIRcvMsgContent {}}) : _} -> do - let msg = T.unpack $ ciContentToText mc - number_ = readMaybe msg :: Maybe Integer + let msg = ciContentToText mc + number_ = readMaybe (T.unpack msg) :: Maybe Integer sendMessage cc contact $ case number_ of - Just n -> msg <> " * " <> msg <> " = " <> show (n * n) + Just n -> msg <> " * " <> msg <> " = " <> tshow (n * n) _ -> "\"" <> msg <> "\" is not a number" _ -> pure () where diff --git a/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs b/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs index da021ee0b5..c526d64886 100644 --- a/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs +++ b/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs @@ -21,6 +21,7 @@ import Simplex.Chat.Messages.CIContent import Simplex.Chat.Options import Simplex.Chat.Protocol (MsgContent (..)) import Simplex.Chat.Types +import Simplex.Messaging.Util (tshow) import System.Directory (getAppUserDataDirectory) welcomeGetOpts :: IO BroadcastBotOpts @@ -48,14 +49,14 @@ broadcastBot BroadcastBotOpts {publishers, welcomeMessage, prohibitedMessage} _u CRContactsList _ cts -> void . forkIO $ do let cts' = filter broadcastTo cts forM_ cts' $ \ct' -> sendComposedMessage cc ct' Nothing mc - sendReply $ "Forwarded to " <> show (length cts') <> " contact(s)" + sendReply $ "Forwarded to " <> tshow (length cts') <> " contact(s)" r -> putStrLn $ "Error getting contacts list: " <> show r else sendReply "!1 Message is not supported!" | otherwise -> do sendReply prohibitedMessage deleteMessage cc ct $ chatItemId' ci where - sendReply = sendComposedMessage cc ct (Just $ chatItemId' ci) . textMsgContent + sendReply = sendComposedMessage cc ct (Just $ chatItemId' ci) . MCText publisher = KnownContact {contactId = contactId' ct, localDisplayName = localDisplayName' ct} allowContent = \case MCText _ -> True diff --git a/apps/simplex-broadcast-bot/src/Broadcast/Options.hs b/apps/simplex-broadcast-bot/src/Broadcast/Options.hs index 57986874aa..5bc4ffef25 100644 --- a/apps/simplex-broadcast-bot/src/Broadcast/Options.hs +++ b/apps/simplex-broadcast-bot/src/Broadcast/Options.hs @@ -7,6 +7,7 @@ module Broadcast.Options where import Data.Maybe (fromMaybe) +import Data.Text (Text) import Options.Applicative import Simplex.Chat.Bot.KnownContacts import Simplex.Chat.Controller (updateStr, versionNumber, versionString) @@ -15,14 +16,14 @@ import Simplex.Chat.Options (ChatCmdLog (..), ChatOpts (..), CoreChatOpts, coreC data BroadcastBotOpts = BroadcastBotOpts { coreOptions :: CoreChatOpts, publishers :: [KnownContact], - welcomeMessage :: String, - prohibitedMessage :: String + welcomeMessage :: Text, + prohibitedMessage :: Text } -defaultWelcomeMessage :: [KnownContact] -> String +defaultWelcomeMessage :: [KnownContact] -> Text defaultWelcomeMessage ps = "Hello! I am a broadcast bot.\nI broadcast messages to all connected users from " <> knownContactNames ps <> "." -defaultProhibitedMessage :: [KnownContact] -> String +defaultProhibitedMessage :: [KnownContact] -> Text defaultProhibitedMessage ps = "Sorry, only these users can broadcast messages: " <> knownContactNames ps <> ". Your message is deleted." broadcastBotOpts :: FilePath -> FilePath -> Parser BroadcastBotOpts diff --git a/apps/simplex-directory-service/src/Directory/Events.hs b/apps/simplex-directory-service/src/Directory/Events.hs index 3119815d7b..ce165a1344 100644 --- a/apps/simplex-directory-service/src/Directory/Events.hs +++ b/apps/simplex-directory-service/src/Directory/Events.hs @@ -89,10 +89,11 @@ crDirectoryEvent = \case CRChatErrors {chatErrors} -> Just $ DELogChatResponse $ "chat errors: " <> T.intercalate ", " (map tshow chatErrors) _ -> Nothing -data DirectoryRole = DRUser | DRSuperUser +data DirectoryRole = DRUser | DRAdmin | DRSuperUser data SDirectoryRole (r :: DirectoryRole) where SDRUser :: SDirectoryRole 'DRUser + SDRAdmin :: SDirectoryRole 'DRAdmin SDRSuperUser :: SDirectoryRole 'DRSuperUser deriving instance Show (SDirectoryRole r) @@ -107,12 +108,14 @@ data DirectoryCmdTag (r :: DirectoryRole) where DCListUserGroups_ :: DirectoryCmdTag 'DRUser DCDeleteGroup_ :: DirectoryCmdTag 'DRUser DCSetRole_ :: DirectoryCmdTag 'DRUser - DCApproveGroup_ :: DirectoryCmdTag 'DRSuperUser - DCRejectGroup_ :: DirectoryCmdTag 'DRSuperUser - DCSuspendGroup_ :: DirectoryCmdTag 'DRSuperUser - DCResumeGroup_ :: DirectoryCmdTag 'DRSuperUser - DCListLastGroups_ :: DirectoryCmdTag 'DRSuperUser - DCListPendingGroups_ :: DirectoryCmdTag 'DRSuperUser + DCApproveGroup_ :: DirectoryCmdTag 'DRAdmin + DCRejectGroup_ :: DirectoryCmdTag 'DRAdmin + DCSuspendGroup_ :: DirectoryCmdTag 'DRAdmin + DCResumeGroup_ :: DirectoryCmdTag 'DRAdmin + DCListLastGroups_ :: DirectoryCmdTag 'DRAdmin + DCListPendingGroups_ :: DirectoryCmdTag 'DRAdmin + DCShowGroupLink_ :: DirectoryCmdTag 'DRAdmin + DCSendToGroupOwner_ :: DirectoryCmdTag 'DRAdmin DCExecuteCommand_ :: DirectoryCmdTag 'DRSuperUser deriving instance Show (DirectoryCmdTag r) @@ -130,12 +133,14 @@ data DirectoryCmd (r :: DirectoryRole) where DCListUserGroups :: DirectoryCmd 'DRUser DCDeleteGroup :: UserGroupRegId -> GroupName -> DirectoryCmd 'DRUser DCSetRole :: GroupId -> GroupName -> GroupMemberRole -> DirectoryCmd 'DRUser - DCApproveGroup :: {groupId :: GroupId, displayName :: GroupName, groupApprovalId :: GroupApprovalId} -> DirectoryCmd 'DRSuperUser - DCRejectGroup :: GroupId -> GroupName -> DirectoryCmd 'DRSuperUser - DCSuspendGroup :: GroupId -> GroupName -> DirectoryCmd 'DRSuperUser - DCResumeGroup :: GroupId -> GroupName -> DirectoryCmd 'DRSuperUser - DCListLastGroups :: Int -> DirectoryCmd 'DRSuperUser - DCListPendingGroups :: Int -> DirectoryCmd 'DRSuperUser + DCApproveGroup :: {groupId :: GroupId, displayName :: GroupName, groupApprovalId :: GroupApprovalId} -> DirectoryCmd 'DRAdmin + DCRejectGroup :: GroupId -> GroupName -> DirectoryCmd 'DRAdmin + DCSuspendGroup :: GroupId -> GroupName -> DirectoryCmd 'DRAdmin + DCResumeGroup :: GroupId -> GroupName -> DirectoryCmd 'DRAdmin + DCListLastGroups :: Int -> DirectoryCmd 'DRAdmin + DCListPendingGroups :: Int -> DirectoryCmd 'DRAdmin + DCShowGroupLink :: GroupId -> GroupName -> DirectoryCmd 'DRAdmin + DCSendToGroupOwner :: GroupId -> GroupName -> Text -> DirectoryCmd 'DRAdmin DCExecuteCommand :: String -> DirectoryCmd 'DRSuperUser DCUnknownCommand :: DirectoryCmd 'DRUser DCCommandError :: DirectoryCmdTag r -> DirectoryCmd r @@ -168,17 +173,20 @@ directoryCmdP = "ls" -> u DCListUserGroups_ "delete" -> u DCDeleteGroup_ "role" -> u DCSetRole_ - "approve" -> su DCApproveGroup_ - "reject" -> su DCRejectGroup_ - "suspend" -> su DCSuspendGroup_ - "resume" -> su DCResumeGroup_ - "last" -> su DCListLastGroups_ - "pending" -> su DCListPendingGroups_ + "approve" -> au DCApproveGroup_ + "reject" -> au DCRejectGroup_ + "suspend" -> au DCSuspendGroup_ + "resume" -> au DCResumeGroup_ + "last" -> au DCListLastGroups_ + "pending" -> au DCListPendingGroups_ + "link" -> au DCShowGroupLink_ + "owner" -> au DCSendToGroupOwner_ "exec" -> su DCExecuteCommand_ "x" -> su DCExecuteCommand_ _ -> fail "bad command tag" where u = pure . ADCT SDRUser + au = pure . ADCT SDRAdmin su = pure . ADCT SDRSuperUser cmdP :: DirectoryCmdTag r -> Parser (DirectoryCmd r) cmdP = \case @@ -203,6 +211,11 @@ directoryCmdP = DCResumeGroup_ -> gc DCResumeGroup DCListLastGroups_ -> DCListLastGroups <$> (A.space *> A.decimal <|> pure 10) DCListPendingGroups_ -> DCListPendingGroups <$> (A.space *> A.decimal <|> pure 10) + DCShowGroupLink_ -> gc DCShowGroupLink + DCSendToGroupOwner_ -> do + (groupId, displayName) <- gc (,) + msg <- A.space *> A.takeText + pure $ DCSendToGroupOwner groupId displayName msg DCExecuteCommand_ -> DCExecuteCommand . T.unpack <$> (A.space *> A.takeText) where gc f = f <$> (A.space *> A.decimal <* A.char ':') <*> displayNameP @@ -213,8 +226,8 @@ directoryCmdP = quoted c = A.char c *> takeNameTill (== c) <* A.char c refChar c = c > ' ' && c /= '#' && c /= '@' -viewName :: String -> String -viewName n = if ' ' `elem` n then "'" <> n <> "'" else n +viewName :: Text -> Text +viewName n = if any (== ' ') (T.unpack n) then "'" <> n <> "'" else n directoryCmdTag :: DirectoryCmd r -> Text directoryCmdTag = \case @@ -234,6 +247,8 @@ directoryCmdTag = \case DCResumeGroup {} -> "resume" DCListLastGroups _ -> "last" DCListPendingGroups _ -> "pending" + DCShowGroupLink {} -> "link" + DCSendToGroupOwner {} -> "owner" DCExecuteCommand _ -> "exec" DCUnknownCommand -> "unknown" DCCommandError _ -> "error" diff --git a/apps/simplex-directory-service/src/Directory/Options.hs b/apps/simplex-directory-service/src/Directory/Options.hs index 0d64064d7d..7f02a580e6 100644 --- a/apps/simplex-directory-service/src/Directory/Options.hs +++ b/apps/simplex-directory-service/src/Directory/Options.hs @@ -11,6 +11,7 @@ module Directory.Options ) where +import qualified Data.Text as T import Options.Applicative import Simplex.Chat.Bot.KnownContacts import Simplex.Chat.Controller (updateStr, versionNumber, versionString) @@ -18,9 +19,10 @@ import Simplex.Chat.Options (ChatOpts (..), ChatCmdLog (..), CoreChatOpts, coreC data DirectoryOpts = DirectoryOpts { coreOptions :: CoreChatOpts, + adminUsers :: [KnownContact], superUsers :: [KnownContact], directoryLog :: Maybe FilePath, - serviceName :: String, + serviceName :: T.Text, searchResults :: Int, testing :: Bool } @@ -28,6 +30,13 @@ data DirectoryOpts = DirectoryOpts directoryOpts :: FilePath -> FilePath -> Parser DirectoryOpts directoryOpts appDir defaultDbFileName = do coreOptions <- coreChatOptsP appDir defaultDbFileName + adminUsers <- + option + parseKnownContacts + ( long "admin-users" + <> metavar "ADMIN_USERS" + <> help "Comma-separated list of admin-users in the format CONTACT_ID:DISPLAY_NAME who will be allowed to manage the directory" + ) superUsers <- option parseKnownContacts @@ -52,9 +61,10 @@ directoryOpts appDir defaultDbFileName = do pure DirectoryOpts { coreOptions, + adminUsers, superUsers, directoryLog, - serviceName, + serviceName = T.pack serviceName, searchResults = 10, testing = False } diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs index ba03642a28..c1012f2a0a 100644 --- a/apps/simplex-directory-service/src/Directory/Service.hs +++ b/apps/simplex-directory-service/src/Directory/Service.hs @@ -17,13 +17,11 @@ import Control.Concurrent.Async import Control.Concurrent.STM import Control.Logger.Simple import Control.Monad -import qualified Data.ByteString.Char8 as B import Data.Maybe (fromMaybe, maybeToList) import Data.Set (Set) import qualified Data.Set as S import Data.Text (Text) import qualified Data.Text as T -import Data.Text.Encoding (decodeLatin1) import Data.Time.Clock (diffUTCTime, getCurrentTime) import Data.Time.LocalTime (getCurrentTimeZone) import Directory.Events @@ -37,6 +35,7 @@ import Simplex.Chat.Core import Simplex.Chat.Messages import Simplex.Chat.Options import Simplex.Chat.Protocol (MsgContent (..)) +import Simplex.Chat.Store.Shared (StoreError (..)) import Simplex.Chat.Types import Simplex.Chat.Types.Shared import Simplex.Chat.View (serializeChatResponse, simplexChatContact, viewContactName, viewGroupName) @@ -79,7 +78,7 @@ welcomeGetOpts = do pure opts directoryService :: DirectoryStore -> DirectoryOpts -> User -> ChatController -> IO () -directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testing} user@User {userId} cc = do +directoryService st DirectoryOpts {adminUsers, superUsers, serviceName, searchResults, testing} user@User {userId} cc = do initializeBotAddress' (not testing) cc env <- newServiceState race_ (forever $ void getLine) . forever $ do @@ -102,6 +101,7 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi logInfo $ "command received " <> directoryCmdTag cmd case sUser of SDRUser -> deUserCommand env ct ciId cmd + SDRAdmin -> deAdminCommand ct ciId cmd SDRSuperUser -> deSuperUserCommand ct ciId cmd DELogChatResponse r -> logInfo r where @@ -118,9 +118,9 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi userGroupReference gr GroupInfo {groupProfile = GroupProfile {displayName}} = userGroupReference' gr displayName userGroupReference' GroupReg {userGroupRegId} displayName = groupReference' userGroupRegId displayName groupReference GroupInfo {groupId, groupProfile = GroupProfile {displayName}} = groupReference' groupId displayName - groupReference' groupId displayName = "ID " <> show groupId <> " (" <> T.unpack displayName <> ")" + groupReference' groupId displayName = "ID " <> tshow groupId <> " (" <> displayName <> ")" groupAlreadyListed GroupInfo {groupProfile = GroupProfile {displayName, fullName}} = - T.unpack $ "The group " <> displayName <> " (" <> fullName <> ") is already listed in the directory, please choose another name." + "The group " <> displayName <> " (" <> fullName <> ") is already listed in the directory, please choose another name." getGroups :: Text -> IO (Maybe [(GroupInfo, GroupSummary)]) getGroups = getGroups_ . Just @@ -151,7 +151,7 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi processInvitation ct g@GroupInfo {groupId, groupProfile = GroupProfile {displayName}} = do void $ addGroupReg st ct g GRSProposed r <- sendChatCmd cc $ APIJoinGroup groupId - sendMessage cc ct $ T.unpack $ case r of + sendMessage cc ct $ case r of CRUserAcceptedGroupSent {} -> "Joining the group " <> displayName <> "…" _ -> "Error joining group " <> displayName <> ", please re-send the invitation!" @@ -179,10 +179,10 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi where askConfirmation = do ugrId <- addGroupReg st ct g GRSPendingConfirmation - sendMessage cc ct $ T.unpack $ "The group " <> displayName <> " (" <> fullName <> ") is already submitted to the directory.\nTo confirm the registration, please send:" - sendMessage cc ct $ "/confirm " <> show ugrId <> ":" <> viewName (T.unpack displayName) + sendMessage cc ct $ "The group " <> displayName <> " (" <> fullName <> ") is already submitted to the directory.\nTo confirm the registration, please send:" + sendMessage cc ct $ "/confirm " <> tshow ugrId <> ":" <> viewName displayName - badRolesMsg :: GroupRolesStatus -> Maybe String + badRolesMsg :: GroupRolesStatus -> Maybe Text badRolesMsg = \case GRSOk -> Nothing GRSServiceNotAdmin -> Just "You must grant directory service *admin* role to register the group" @@ -218,7 +218,7 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi when (ctId `isOwner` gr) $ do setGroupRegOwner st gr owner let GroupInfo {groupId, groupProfile = GroupProfile {displayName}} = g - notifyOwner gr $ T.unpack $ "Joined the group " <> displayName <> ", creating the link…" + notifyOwner gr $ "Joined the group " <> displayName <> ", creating the link…" sendChatCmd cc (APICreateGroupLink groupId GRMember) >>= \case CRGroupLinkCreated {connReqContact} -> do setGroupStatus st gr GRSPendingUpdate @@ -227,7 +227,7 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi "Created the public link to join the group via this directory service that is always online.\n\n\ \Please add it to the group welcome message.\n\ \For example, add:" - notifyOwner gr $ "Link to join the group " <> T.unpack displayName <> ": " <> B.unpack (strEncode $ simplexChatContact connReqContact) + notifyOwner gr $ "Link to join the group " <> displayName <> ": " <> strEncodeTxt (simplexChatContact connReqContact) CRChatCmdError _ (ChatError e) -> case e of CEGroupUserRole {} -> notifyOwner gr "Failed creating group link, as service is no longer an admin." CEGroupMemberUserRemoved -> notifyOwner gr "Failed creating group link, as service is removed from the group." @@ -256,7 +256,7 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi GPHasServiceLink -> when (ctId `isOwner` gr) $ groupLinkAdded gr GPServiceLinkError -> do when (ctId `isOwner` gr) $ notifyOwner gr $ "Error: " <> serviceName <> " has no group link for " <> userGroupRef <> ". Please report the error to the developers." - logError $ "Error: no group link for " <> T.pack userGroupRef + logError $ "Error: no group link for " <> userGroupRef GRSPendingApproval n -> processProfileChange gr $ n + 1 GRSActive -> processProfileChange gr 1 GRSSuspended -> processProfileChange gr 1 @@ -277,7 +277,7 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi _ -> do let gaId = 1 setGroupStatus st gr $ GRSPendingApproval gaId - notifyOwner gr $ "Thank you! The group link for " <> userGroupReference gr toGroup <> " is added to the welcome message.\nYou will be notified once the group is added to the directory - it may take up to 24 hours." + notifyOwner gr $ "Thank you! The group link for " <> userGroupReference gr toGroup <> " is added to the welcome message.\nYou will be notified once the group is added to the directory - it may take up to 48 hours." checkRolesSendToApprove gr gaId processProfileChange gr n' = do setGroupStatus st gr GRSPendingUpdate @@ -299,13 +299,13 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi notifyOwner gr $ "The group " <> userGroupRef <> " is updated!\nIt is hidden from the directory until approved." notifySuperUsers $ "The group " <> groupRef <> " is updated." checkRolesSendToApprove gr n' - GPServiceLinkError -> logError $ "Error: no group link for " <> T.pack groupRef <> " pending approval." + GPServiceLinkError -> logError $ "Error: no group link for " <> groupRef <> " pending approval." groupProfileUpdate = profileUpdate <$> sendChatCmd cc (APIGetGroupLink groupId) where profileUpdate = \case CRGroupLink {connReqContact} -> - let groupLink1 = safeDecodeUtf8 $ strEncode connReqContact - groupLink2 = safeDecodeUtf8 $ strEncode $ simplexChatContact connReqContact + let groupLink1 = strEncodeTxt connReqContact + groupLink2 = strEncodeTxt $ simplexChatContact connReqContact hadLinkBefore = groupLink1 `isInfix` description p || groupLink2 `isInfix` description p hasLinkNow = groupLink1 `isInfix` description p' || groupLink2 `isInfix` description p' in if @@ -331,7 +331,7 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi msg = maybe (MCText text) (\image -> MCImage {text, image}) image' withSuperUsers $ \cId -> do sendComposedMessage' cc cId Nothing msg - sendMessage' cc cId $ "/approve " <> show dbGroupId <> ":" <> viewName (T.unpack displayName) <> " " <> show gaId + sendMessage' cc cId $ "/approve " <> tshow dbGroupId <> ":" <> viewName displayName <> " " <> tshow gaId deContactRoleChanged :: GroupInfo -> ContactId -> GroupMemberRole -> IO () deContactRoleChanged g@GroupInfo {membership = GroupMember {memberRole = serviceRole}} ctId contactRole = do @@ -356,7 +356,7 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi where rStatus = groupRolesStatus contactRole serviceRole groupRef = groupReference g - ctRole = "*" <> B.unpack (strEncode contactRole) <> "*" + ctRole = "*" <> strEncodeTxt contactRole <> "*" suCtRole = "(user role is set to " <> ctRole <> ")." deServiceRoleChanged :: GroupInfo -> GroupMemberRole -> IO () @@ -382,7 +382,7 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi _ -> pure () where groupRef = groupReference g - srvRole = "*" <> B.unpack (strEncode serviceRole) <> "*" + srvRole = "*" <> strEncodeTxt serviceRole <> "*" suSrvRole = "(" <> serviceName <> " role is changed to " <> srvRole <> ")." whenContactIsOwner gr action = getGroupMember gr @@ -426,7 +426,7 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi <> serviceName <> " bot will create a public group link for the new members to join even when you are offline.\n\ \3. You will then need to add this link to the group welcome message.\n\ - \4. Once the link is added, service admins will approve the group (it can take up to 24 hours), and everybody will be able to find it in directory.\n\n\ + \4. Once the link is added, service admins will approve the group (it can take up to 48 hours), and everybody will be able to find it in directory.\n\n\ \Start from inviting the bot to your group as admin - it will guide you through the process" DCSearchGroup s -> withFoundListedGroups (Just s) $ sendSearchResults s DCSearchNext -> @@ -448,44 +448,47 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi DCRecentGroups -> withFoundListedGroups Nothing $ sendAllGroups takeRecent "the most recent" STRecent DCSubmitGroup _link -> pure () DCConfirmDuplicateGroup ugrId gName -> - withUserGroupReg ugrId gName $ \gr g@GroupInfo {groupProfile = GroupProfile {displayName}} -> + withUserGroupReg ugrId gName $ \g@GroupInfo {groupProfile = GroupProfile {displayName}} gr -> readTVarIO (groupRegStatus gr) >>= \case GRSPendingConfirmation -> getDuplicateGroup g >>= \case Nothing -> sendMessage cc ct "Error: getDuplicateGroup. Please notify the developers." Just DGReserved -> sendMessage cc ct $ groupAlreadyListed g _ -> processInvitation ct g - _ -> sendReply $ "Error: the group ID " <> show ugrId <> " (" <> T.unpack displayName <> ") is not pending confirmation." + _ -> sendReply $ "Error: the group ID " <> tshow ugrId <> " (" <> displayName <> ") is not pending confirmation." DCListUserGroups -> atomically (getUserGroupRegs st $ contactId' ct) >>= \grs -> do - sendReply $ show (length grs) <> " registered group(s)" + sendReply $ tshow (length grs) <> " registered group(s)" void . forkIO $ forM_ (reverse grs) $ \gr@GroupReg {userGroupRegId} -> sendGroupInfo ct gr userGroupRegId Nothing DCDeleteGroup ugrId gName -> - withUserGroupReg ugrId gName $ \gr GroupInfo {groupProfile = GroupProfile {displayName}} -> do + withUserGroupReg ugrId gName $ \GroupInfo {groupProfile = GroupProfile {displayName}} gr -> do delGroupReg st gr - sendReply $ T.unpack $ "Your group " <> displayName <> " is deleted from the directory" - DCSetRole ugrId gName mRole -> - withUserGroupReg ugrId gName $ \_gr GroupInfo {groupId, groupProfile = GroupProfile {displayName}} -> do - gLink_ <- setGroupLinkRole cc groupId mRole - sendReply $ T.unpack $ case gLink_ of - Nothing -> "Error: the initial member role for the group " <> displayName <> " was NOT upgated" - Just gLink -> - ("The initial member role for the group " <> displayName <> " is set to *" <> decodeLatin1 (strEncode mRole) <> "*\n\n") - <> ("*Please note*: it applies only to members joining via this link: " <> safeDecodeUtf8 (strEncode $ simplexChatContact gLink)) + sendReply $ "Your group " <> displayName <> " is deleted from the directory" + DCSetRole gId gName mRole -> + (if isAdmin then withGroupAndReg sendReply else withUserGroupReg) gId gName $ + \GroupInfo {groupId, groupProfile = GroupProfile {displayName}} _gr -> do + gLink_ <- setGroupLinkRole cc groupId mRole + sendReply $ case gLink_ of + Nothing -> "Error: the initial member role for the group " <> displayName <> " was NOT upgated" + Just gLink -> + ("The initial member role for the group " <> displayName <> " is set to *" <> strEncodeTxt mRole <> "*\n\n") + <> ("*Please note*: it applies only to members joining via this link: " <> strEncodeTxt (simplexChatContact gLink)) DCUnknownCommand -> sendReply "Unknown command" - DCCommandError tag -> sendReply $ "Command error: " <> show tag + DCCommandError tag -> sendReply $ "Command error: " <> tshow tag where + knownCt = knownContact ct + isAdmin = knownCt `elem` adminUsers || knownCt `elem` superUsers withUserGroupReg ugrId gName action = atomically (getUserGroupReg st (contactId' ct) ugrId) >>= \case - Nothing -> sendReply $ "Group ID " <> show ugrId <> " not found" + Nothing -> sendReply $ "Group ID " <> tshow ugrId <> " not found" Just gr@GroupReg {dbGroupId} -> do getGroup cc dbGroupId >>= \case - Nothing -> sendReply $ "Group ID " <> show ugrId <> " not found" + Nothing -> sendReply $ "Group ID " <> tshow ugrId <> " not found" Just g@GroupInfo {groupProfile = GroupProfile {displayName}} - | displayName == gName -> action gr g - | otherwise -> sendReply $ "Group ID " <> show ugrId <> " has the display name " <> T.unpack displayName - sendReply = sendComposedMessage cc ct (Just ciId) . textMsgContent + | displayName == gName -> action g gr + | otherwise -> sendReply $ "Group ID " <> tshow ugrId <> " has the display name " <> displayName + sendReply = mkSendReply ct ciId withFoundListedGroups s_ action = getGroups_ s_ >>= \case Just groups -> atomically (filterListedGroups st groups) >>= action @@ -495,8 +498,8 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi gs -> do let gs' = takeTop searchResults gs moreGroups = length gs - length gs' - more = if moreGroups > 0 then ", sending top " <> show (length gs') else "" - sendReply $ "Found " <> show (length gs) <> " group(s)" <> more <> "." + more = if moreGroups > 0 then ", sending top " <> tshow (length gs') else "" + sendReply $ "Found " <> tshow (length gs) <> " group(s)" <> more <> "." updateSearchRequest (STSearch s) $ groupIds gs' sendFoundGroups gs' moreGroups sendAllGroups takeFirst sortName searchType = \case @@ -504,8 +507,8 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi gs -> do let gs' = takeFirst searchResults gs moreGroups = length gs - length gs' - more = if moreGroups > 0 then ", sending " <> sortName <> " " <> show (length gs') else "" - sendReply $ show (length gs) <> " group(s) listed" <> more <> "." + more = if moreGroups > 0 then ", sending " <> sortName <> " " <> tshow (length gs') else "" + sendReply $ tshow (length gs) <> " group(s) listed" <> more <> "." updateSearchRequest searchType $ groupIds gs' sendFoundGroups gs' moreGroups sendNextSearchResults takeFirst SearchRequest {searchType, sentGroups} = \case @@ -516,7 +519,7 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi let gs' = takeFirst searchResults $ filterNotSent sentGroups gs sentGroups' = sentGroups <> groupIds gs' moreGroups = length gs - S.size sentGroups' - sendReply $ "Sending " <> show (length gs') <> " more group(s)." + sendReply $ "Sending " <> tshow (length gs') <> " more group(s)." updateSearchRequest searchType sentGroups' sendFoundGroups gs' moreGroups updateSearchRequest :: SearchType -> Set GroupId -> IO () @@ -527,9 +530,10 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi sendFoundGroups gs moreGroups = void . forkIO $ do forM_ gs $ - \(GroupInfo {groupProfile = p@GroupProfile {image = image_}}, GroupSummary {currentMembers}) -> do + \(GroupInfo {groupId, groupProfile = p@GroupProfile {image = image_}}, GroupSummary {currentMembers}) -> do let membersStr = "_" <> tshow currentMembers <> " members_" - text = groupInfoText p <> "\n" <> membersStr + showId = if isAdmin then tshow groupId <> ". " else "" + text = showId <> groupInfoText p <> "\n" <> membersStr msg = maybe (MCText text) (\image -> MCImage {text, image}) image_ sendComposedMessage cc ct Nothing msg when (moreGroups > 0) $ @@ -537,92 +541,134 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi MCText $ "Send */next* or just *.* for " <> tshow moreGroups <> " more result(s)." - deSuperUserCommand :: Contact -> ChatItemId -> DirectoryCmd 'DRSuperUser -> IO () - deSuperUserCommand ct ciId cmd - | superUser `elem` superUsers = case cmd of + deAdminCommand :: Contact -> ChatItemId -> DirectoryCmd 'DRAdmin -> IO () + deAdminCommand ct ciId cmd + | knownCt `elem` adminUsers || knownCt `elem` superUsers = case cmd of DCApproveGroup {groupId, displayName = n, groupApprovalId} -> - getGroupAndReg groupId n >>= \case - Nothing -> sendReply $ "The group " <> groupRef <> " not found (getGroupAndReg)." - Just (g, gr) -> - readTVarIO (groupRegStatus gr) >>= \case - GRSPendingApproval gaId - | gaId == groupApprovalId -> do - getDuplicateGroup g >>= \case - Nothing -> sendReply "Error: getDuplicateGroup. Please notify the developers." - Just DGReserved -> sendReply $ "The group " <> groupRef <> " is already listed in the directory." - _ -> do - getGroupRolesStatus g gr >>= \case - Just GRSOk -> do - setGroupStatus st gr GRSActive - sendReply "Group approved!" - notifyOwner gr $ "The group " <> userGroupReference' gr n <> " is approved and listed in directory!\nPlease note: if you change the group profile it will be hidden from directory until it is re-approved." - Just GRSServiceNotAdmin -> replyNotApproved serviceNotAdmin - Just GRSContactNotOwner -> replyNotApproved "user is not an owner." - Just GRSBadRoles -> replyNotApproved $ "user is not an owner, " <> serviceNotAdmin - Nothing -> sendReply "Error: getGroupRolesStatus. Please notify the developers." - where - replyNotApproved reason = sendReply $ "Group is not approved: " <> reason - serviceNotAdmin = serviceName <> " is not an admin." - | otherwise -> sendReply "Incorrect approval code" - _ -> sendReply $ "Error: the group " <> groupRef <> " is not pending approval." + withGroupAndReg sendReply groupId n $ \g gr -> + readTVarIO (groupRegStatus gr) >>= \case + GRSPendingApproval gaId + | gaId == groupApprovalId -> do + getDuplicateGroup g >>= \case + Nothing -> sendReply "Error: getDuplicateGroup. Please notify the developers." + Just DGReserved -> sendReply $ "The group " <> groupRef <> " is already listed in the directory." + _ -> do + getGroupRolesStatus g gr >>= \case + Just GRSOk -> do + setGroupStatus st gr GRSActive + let approved = "The group " <> userGroupReference' gr n <> " is approved" + notifyOwner gr $ approved <> " and listed in directory!\nPlease note: if you change the group profile it will be hidden from directory until it is re-approved." + sendReply "Group approved!" + notifyOtherSuperUsers $ approved <> " by " <> viewName (localDisplayName' ct) + Just GRSServiceNotAdmin -> replyNotApproved serviceNotAdmin + Just GRSContactNotOwner -> replyNotApproved "user is not an owner." + Just GRSBadRoles -> replyNotApproved $ "user is not an owner, " <> serviceNotAdmin + Nothing -> sendReply "Error: getGroupRolesStatus. Please notify the developers." + where + replyNotApproved reason = sendReply $ "Group is not approved: " <> reason + serviceNotAdmin = serviceName <> " is not an admin." + | otherwise -> sendReply "Incorrect approval code" + _ -> sendReply $ "Error: the group " <> groupRef <> " is not pending approval." where groupRef = groupReference' groupId n DCRejectGroup _gaId _gName -> pure () DCSuspendGroup groupId gName -> do let groupRef = groupReference' groupId gName - getGroupAndReg groupId gName >>= \case - Nothing -> sendReply $ "The group " <> groupRef <> " not found (getGroupAndReg)." - Just (_, gr) -> - readTVarIO (groupRegStatus gr) >>= \case - GRSActive -> do - setGroupStatus st gr GRSSuspended - notifyOwner gr $ "The group " <> userGroupReference' gr gName <> " is suspended and hidden from directory. Please contact the administrators." - sendReply "Group suspended!" - _ -> sendReply $ "The group " <> groupRef <> " is not active, can't be suspended." + withGroupAndReg sendReply groupId gName $ \_ gr -> + readTVarIO (groupRegStatus gr) >>= \case + GRSActive -> do + setGroupStatus st gr GRSSuspended + let suspended = "The group " <> userGroupReference' gr gName <> " is suspended" + notifyOwner gr $ suspended <> " and hidden from directory. Please contact the administrators." + sendReply "Group suspended!" + notifyOtherSuperUsers $ suspended <> " by " <> viewName (localDisplayName' ct) + _ -> sendReply $ "The group " <> groupRef <> " is not active, can't be suspended." DCResumeGroup groupId gName -> do let groupRef = groupReference' groupId gName - getGroupAndReg groupId gName >>= \case - Nothing -> sendReply $ "The group " <> groupRef <> " not found (getGroupAndReg)." - Just (_, gr) -> - readTVarIO (groupRegStatus gr) >>= \case - GRSSuspended -> do - setGroupStatus st gr GRSActive - notifyOwner gr $ "The group " <> userGroupReference' gr gName <> " is listed in the directory again!" - sendReply "Group listing resumed!" - _ -> sendReply $ "The group " <> groupRef <> " is not suspended, can't be resumed." + withGroupAndReg sendReply groupId gName $ \_ gr -> + readTVarIO (groupRegStatus gr) >>= \case + GRSSuspended -> do + setGroupStatus st gr GRSActive + let groupStr = "The group " <> userGroupReference' gr gName + notifyOwner gr $ groupStr <> " is listed in the directory again!" + sendReply "Group listing resumed!" + notifyOtherSuperUsers $ groupStr <> " listing resumed by " <> viewName (localDisplayName' ct) + _ -> sendReply $ "The group " <> groupRef <> " is not suspended, can't be resumed." DCListLastGroups count -> listGroups count False DCListPendingGroups count -> listGroups count True - DCExecuteCommand cmdStr -> - sendChatCmdStr cc cmdStr >>= \r -> do - ts <- getCurrentTime - tz <- getCurrentTimeZone - sendReply $ serializeChatResponse (Nothing, Just user) ts tz Nothing r - DCCommandError tag -> sendReply $ "Command error: " <> show tag + DCShowGroupLink groupId gName -> do + let groupRef = groupReference' groupId gName + withGroupAndReg sendReply groupId gName $ \_ _ -> + sendChatCmd cc (APIGetGroupLink groupId) >>= \case + CRGroupLink {connReqContact, memberRole} -> + sendReply $ T.unlines + [ "The link to join the group " <> groupRef <> ":", + strEncodeTxt $ simplexChatContact connReqContact, + "New member role: " <> strEncodeTxt memberRole + ] + CRChatCmdError _ (ChatErrorStore (SEGroupLinkNotFound _)) -> + sendReply $ "The group " <> groupRef <> " has no public link." + r -> do + ts <- getCurrentTime + tz <- getCurrentTimeZone + let resp = T.pack $ serializeChatResponse (Nothing, Just user) ts tz Nothing r + sendReply $ "Unexpected error:\n" <> resp + DCSendToGroupOwner groupId gName msg -> do + let groupRef = groupReference' groupId gName + withGroupAndReg sendReply groupId gName $ \_ gr@GroupReg {dbContactId} -> do + notifyOwner gr msg + owner_ <- getContact cc dbContactId + let ownerInfo = "the owner of the group " <> groupRef + ownerName ct' = "@" <> viewName (localDisplayName' ct') <> ", " + sendReply $ "Forwarded to " <> maybe "" ownerName owner_ <> ownerInfo + DCCommandError tag -> sendReply $ "Command error: " <> tshow tag | otherwise = sendReply "You are not allowed to use this command" where - superUser = KnownContact {contactId = contactId' ct, localDisplayName = localDisplayName' ct} - sendReply = sendComposedMessage cc ct (Just ciId) . textMsgContent + knownCt = knownContact ct + sendReply = mkSendReply ct ciId + notifyOtherSuperUsers s = withSuperUsers $ \ctId -> unless (ctId == contactId' ct) $ sendMessage' cc ctId s listGroups count pending = readTVarIO (groupRegs st) >>= \groups -> do grs <- if pending then filterM (fmap pendingApproval . readTVarIO . groupRegStatus) groups else pure groups - sendReply $ show (length grs) <> " registered group(s)" <> (if length grs > count then ", showing the last " <> show count else "") + sendReply $ tshow (length grs) <> " registered group(s)" <> (if length grs > count then ", showing the last " <> tshow count else "") void . forkIO $ forM_ (reverse $ take count grs) $ \gr@GroupReg {dbGroupId, dbContactId} -> do ct_ <- getContact cc dbContactId let ownerStr = "Owner: " <> maybe "getContact error" localDisplayName' ct_ sendGroupInfo ct gr dbGroupId $ Just ownerStr - getGroupAndReg :: GroupId -> GroupName -> IO (Maybe (GroupInfo, GroupReg)) - getGroupAndReg gId gName = - getGroup cc gId - $>>= \g@GroupInfo {groupProfile = GroupProfile {displayName}} -> - if displayName == gName - then - atomically (getGroupReg st gId) - $>>= \gr -> pure $ Just (g, gr) - else pure Nothing + deSuperUserCommand :: Contact -> ChatItemId -> DirectoryCmd 'DRSuperUser -> IO () + deSuperUserCommand ct ciId cmd + | knownContact ct `elem` superUsers = case cmd of + DCExecuteCommand cmdStr -> + sendChatCmdStr cc cmdStr >>= \r -> do + ts <- getCurrentTime + tz <- getCurrentTimeZone + sendReply $ T.pack $ serializeChatResponse (Nothing, Just user) ts tz Nothing r + DCCommandError tag -> sendReply $ "Command error: " <> tshow tag + | otherwise = sendReply "You are not allowed to use this command" + where + sendReply = mkSendReply ct ciId + + knownContact :: Contact -> KnownContact + knownContact ct = KnownContact {contactId = contactId' ct, localDisplayName = localDisplayName' ct} + + mkSendReply :: Contact -> ChatItemId -> Text -> IO () + mkSendReply ct ciId = sendComposedMessage cc ct (Just ciId) . MCText + + withGroupAndReg :: (Text -> IO ()) -> GroupId -> GroupName -> (GroupInfo -> GroupReg -> IO ()) -> IO () + withGroupAndReg sendReply gId gName action = + getGroup cc gId >>= \case + Nothing -> sendReply $ "Group ID " <> tshow gId <> " not found (getGroup)" + Just g@GroupInfo {groupProfile = GroupProfile {displayName}} + | displayName == gName -> + atomically (getGroupReg st gId) >>= \case + Nothing -> sendReply $ "Registration for group ID " <> tshow gId <> " not found (getGroupReg)" + Just gr -> action g gr + | otherwise -> + sendReply $ "Group ID " <> tshow gId <> " has the display name " <> displayName sendGroupInfo :: Contact -> GroupReg -> GroupId -> Maybe Text -> IO () sendGroupInfo ct gr@GroupReg {dbGroupId} useGroupId ownerStr_ = do @@ -668,5 +714,8 @@ setGroupLinkRole cc gId mRole = resp <$> sendChatCmd cc (APIGroupLinkMemberRole CRGroupLink _ _ gLink _ -> Just gLink _ -> Nothing -unexpectedError :: String -> String +unexpectedError :: Text -> Text unexpectedError err = "Unexpected error: " <> err <> ", please notify the developers." + +strEncodeTxt :: StrEncoding a => a -> Text +strEncodeTxt = safeDecodeUtf8 . strEncode diff --git a/src/Simplex/Chat/Bot.hs b/src/Simplex/Chat/Bot.hs index 66479c0ee6..8c0978a98f 100644 --- a/src/Simplex/Chat/Bot.hs +++ b/src/Simplex/Chat/Bot.hs @@ -12,6 +12,7 @@ import Control.Concurrent.STM import Control.Monad import qualified Data.ByteString.Char8 as B import Data.List.NonEmpty (NonEmpty (..)) +import Data.Text (Text) import qualified Data.Text as T import Simplex.Chat.Controller import Simplex.Chat.Core @@ -31,10 +32,10 @@ chatBotRepl welcome answer _user cc = do case resp of CRContactConnected _ contact _ -> do contactConnected contact - void $ sendMessage cc contact welcome + void $ sendMessage cc contact $ T.pack welcome CRNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content = mc@CIRcvMsgContent {}}) : _} -> do let msg = T.unpack $ ciContentToText mc - void $ sendMessage cc contact =<< answer contact msg + void $ sendMessage cc contact . T.pack =<< answer contact msg _ -> pure () where contactConnected Contact {localDisplayName} = putStrLn $ T.unpack localDisplayName <> " connected" @@ -57,11 +58,11 @@ initializeBotAddress' logAddress cc = do when logAddress $ putStrLn $ "Bot's contact address is: " <> B.unpack (strEncode uri) void $ sendChatCmd cc $ AddressAutoAccept $ Just AutoAccept {acceptIncognito = False, autoReply = Nothing} -sendMessage :: ChatController -> Contact -> String -> IO () -sendMessage cc ct = sendComposedMessage cc ct Nothing . textMsgContent +sendMessage :: ChatController -> Contact -> Text -> IO () +sendMessage cc ct = sendComposedMessage cc ct Nothing . MCText -sendMessage' :: ChatController -> ContactId -> String -> IO () -sendMessage' cc ctId = sendComposedMessage' cc ctId Nothing . textMsgContent +sendMessage' :: ChatController -> ContactId -> Text -> IO () +sendMessage' cc ctId = sendComposedMessage' cc ctId Nothing . MCText sendComposedMessage :: ChatController -> Contact -> Maybe ChatItemId -> MsgContent -> IO () sendComposedMessage cc = sendComposedMessage' cc . contactId' @@ -83,9 +84,6 @@ deleteMessage cc ct chatItemId = do contactRef :: Contact -> ChatRef contactRef = ChatRef CTDirect . contactId' -textMsgContent :: String -> MsgContent -textMsgContent = MCText . T.pack - printLog :: ChatController -> ChatLogLevel -> String -> IO () printLog cc level s | logLevel (config cc) <= level = putStrLn s diff --git a/src/Simplex/Chat/Bot/KnownContacts.hs b/src/Simplex/Chat/Bot/KnownContacts.hs index 1ea44d49be..4555bb9fee 100644 --- a/src/Simplex/Chat/Bot/KnownContacts.hs +++ b/src/Simplex/Chat/Bot/KnownContacts.hs @@ -18,8 +18,8 @@ data KnownContact = KnownContact } deriving (Eq) -knownContactNames :: [KnownContact] -> String -knownContactNames = T.unpack . T.intercalate ", " . map (("@" <>) . localDisplayName) +knownContactNames :: [KnownContact] -> Text +knownContactNames = T.intercalate ", " . map (("@" <>) . localDisplayName) parseKnownContacts :: ReadM [KnownContact] parseKnownContacts = eitherReader $ parseAll knownContactsP . encodeUtf8 . T.pack diff --git a/tests/Bots/DirectoryTests.hs b/tests/Bots/DirectoryTests.hs index 3a3e9f889f..c50bb8b02d 100644 --- a/tests/Bots/DirectoryTests.hs +++ b/tests/Bots/DirectoryTests.hs @@ -10,7 +10,8 @@ import ChatTests.Utils import Control.Concurrent (forkIO, killThread, threadDelay) import Control.Exception (finally) import Control.Monad (forM_) -import Directory.Events (viewName) +import qualified Data.Text as T +import qualified Directory.Events as DE import Directory.Options import Directory.Service import Directory.Store @@ -27,7 +28,7 @@ import Test.Hspec hiding (it) directoryServiceTests :: SpecWith FilePath directoryServiceTests = do it "should register group" testDirectoryService - it "should suspend and resume group" testSuspendResume + it "should suspend and resume group, send message to owner" testSuspendResume it "should delete group registration" testDeleteGroup it "should change initial member role" testSetRole it "should join found group via link" testJoinGroup @@ -67,6 +68,7 @@ mkDirectoryOpts :: FilePath -> [KnownContact] -> DirectoryOpts mkDirectoryOpts tmp superUsers = DirectoryOpts { coreOptions = testCoreOpts {dbFilePrefix = tmp serviceDbPrefix}, + adminUsers = [], superUsers, directoryLog = Just $ tmp "directory_service.log", serviceName = "SimpleX-Directory", @@ -77,6 +79,9 @@ mkDirectoryOpts tmp superUsers = serviceDbPrefix :: FilePath serviceDbPrefix = "directory_service" +viewName :: String -> String +viewName = T.unpack . DE.viewName . T.pack + testDirectoryService :: HasCallStack => FilePath -> IO () testDirectoryService tmp = withDirectoryService tmp $ \superUser dsLink -> @@ -111,7 +116,7 @@ testDirectoryService tmp = -- putStrLn "*** update profile so that it has link" updateGroupProfile bob welcomeWithLink bob <# "SimpleX-Directory> Thank you! The group link for ID 1 (PSA) is added to the welcome message." - bob <## "You will be notified once the group is added to the directory - it may take up to 24 hours." + bob <## "You will be notified once the group is added to the directory - it may take up to 48 hours." approvalRequested superUser welcomeWithLink (1 :: Int) -- putStrLn "*** update profile so that it still has link" let welcomeWithLink' = "Welcome! " <> welcomeWithLink @@ -139,7 +144,7 @@ testDirectoryService tmp = -- putStrLn "*** update profile so that it has link again" updateGroupProfile bob welcomeWithLink' bob <# "SimpleX-Directory> Thank you! The group link for ID 1 (PSA) is added to the welcome message." - bob <## "You will be notified once the group is added to the directory - it may take up to 24 hours." + bob <## "You will be notified once the group is added to the directory - it may take up to 48 hours." approvalRequested superUser welcomeWithLink' (1 :: Int) superUser #> "@SimpleX-Directory /pending" superUser <# "SimpleX-Directory> > /pending" @@ -207,6 +212,17 @@ testSuspendResume tmp = superUser <## " Group listing resumed!" bob <# "SimpleX-Directory> The group ID 1 (privacy) is listed in the directory again!" groupFound bob "privacy" + superUser #> "@SimpleX-Directory privacy" + groupFoundN_ (Just 1) 2 superUser "privacy" + superUser #> "@SimpleX-Directory /link 1:privacy" + superUser <# "SimpleX-Directory> > /link 1:privacy" + superUser <## " The link to join the group ID 1 (privacy):" + superUser <##. "https://simplex.chat/contact" + superUser <## "New member role: member" + superUser #> "@SimpleX-Directory /owner 1:privacy hello there" + superUser <# "SimpleX-Directory> > /owner 1:privacy hello there" + superUser <## " Forwarded to @bob, the owner of the group ID 1 (privacy)" + bob <# "SimpleX-Directory> hello there" testDeleteGroup :: HasCallStack => FilePath -> IO () testDeleteGroup tmp = @@ -650,7 +666,7 @@ testRegOwnerRemovedLink tmp = bob <## "description changed to:" bob <## welcomeWithLink bob <# "SimpleX-Directory> Thank you! The group link for ID 1 (privacy) is added to the welcome message." - bob <## "You will be notified once the group is added to the directory - it may take up to 24 hours." + bob <## "You will be notified once the group is added to the directory - it may take up to 48 hours." cath <## "bob updated group #privacy:" cath <## "description changed to:" cath <## welcomeWithLink @@ -692,7 +708,7 @@ testAnotherOwnerRemovedLink tmp = bob <## "description changed to:" bob <## (welcomeWithLink <> " - welcome!") bob <# "SimpleX-Directory> Thank you! The group link for ID 1 (privacy) is added to the welcome message." - bob <## "You will be notified once the group is added to the directory - it may take up to 24 hours." + bob <## "You will be notified once the group is added to the directory - it may take up to 48 hours." cath <## "bob updated group #privacy:" cath <## "description changed to:" cath <## (welcomeWithLink <> " - welcome!") @@ -774,7 +790,7 @@ testDuplicateProhibitWhenUpdated tmp = cath ##> "/gp privacy security Security" cath <## "changed to #security (Security)" cath <# "SimpleX-Directory> Thank you! The group link for ID 2 (security) is added to the welcome message." - cath <## "You will be notified once the group is added to the directory - it may take up to 24 hours." + cath <## "You will be notified once the group is added to the directory - it may take up to 48 hours." notifySuperUser superUser cath "security" "Security" welcomeWithLink' 2 approveRegistration superUser cath "security" 2 groupFound bob "security" @@ -1035,7 +1051,7 @@ updateProfileWithLink u n welcomeWithLink ugId = do u <## "description changed to:" u <## welcomeWithLink u <# ("SimpleX-Directory> Thank you! The group link for ID " <> show ugId <> " (" <> n <> ") is added to the welcome message.") - u <## "You will be notified once the group is added to the directory - it may take up to 24 hours." + u <## "You will be notified once the group is added to the directory - it may take up to 48 hours." notifySuperUser :: TestCC -> TestCC -> String -> String -> String -> Int -> IO () notifySuperUser su u n fn welcomeWithLink gId = do @@ -1112,10 +1128,13 @@ groupFoundN count u name = do groupFoundN' count u name groupFoundN' :: Int -> TestCC -> String -> IO () -groupFoundN' count u name = do +groupFoundN' = groupFoundN_ Nothing + +groupFoundN_ :: Maybe Int -> Int -> TestCC -> String -> IO () +groupFoundN_ shownId_ count u name = do u <# ("SimpleX-Directory> > " <> name) u <## " Found 1 group(s)." - u <#. ("SimpleX-Directory> " <> name) + u <#. ("SimpleX-Directory> " <> maybe "" (\gId -> show gId <> ". ") shownId_ <> name) u <## "Welcome message:" u <##. "Link to join the group " u <## (show count <> " members") From 8af54539f66b94d554ee90d8995c5619f399e993 Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Tue, 12 Nov 2024 10:37:12 +0000 Subject: [PATCH 065/567] docs: add control port section (#5164) * docs: add control port section * docs: apply suggestions --- docs/SERVER.md | 81 +++++++++++++++++++++++++++++++++++++++++++-- docs/XFTP-SERVER.md | 69 +++++++++++++++++++++++++++++++++++++- 2 files changed, 146 insertions(+), 4 deletions(-) diff --git a/docs/SERVER.md b/docs/SERVER.md index ce6c466573..9c3f2f619e 100644 --- a/docs/SERVER.md +++ b/docs/SERVER.md @@ -28,7 +28,8 @@ revision: 12.10.2024 - [Documentation](#documentation) - [SMP server address](#smp-server-address) - [Systemd commands](#systemd-commands) - - [Monitoring](#monitoring) + - [Control port](#control-port) + - [Daily statistics](#daily-statistics) - [Updating your SMP server](#updating-your-smp-server) - [Configuring the app to use the server](#configuring-the-app-to-use-the-server) @@ -1079,7 +1080,81 @@ Nov 23 19:23:21 5588ab759e80 smp-server[30878]: not expiring inactive clients Nov 23 19:23:21 5588ab759e80 smp-server[30878]: creating new queues requires password ``` -#### Monitoring +#### Control port + +Enabling control port in the configuration allows administrator to see information about the smp-server in real-time. Additionally, it allows to delete queues for content moderation and see the debug info about the clients, sockets, etc. Enabling the control port requires setting the `admin` and `user` passwords. + +1. Generate two passwords for each user: + + ```sh + tr -dc A-Za-z0-9 + control_port_user_password: + + [TRANSPORT] + control_port: 5224 + ``` + +3. Restart the server: + + ```sh + systemctl restart smp-server + ``` + +To access the control port, use: + +```sh +nc 127.0.0.1 5224 +``` + +or: + +```sh +telnet 127.0.0.1 5224 +``` + +Upon connecting, the control port should print: + +```sh +SMP server control port +'help' for supported commands +``` + +To authenticate, type the following and hit enter. Change the `my_generated_password` with the `user` or `admin` password from the configuration: + +```sh +auth my_generated_password +``` + +Here's the full list of commands, their descriptions and who can access them. + +| Command | Description | Requires `admin` role | +| ---------------- | ------------------------------------------------------------------------------- | -------------------------- | +| `stats` | Real-time statistics. Fields described in [Daily statistics](#daily-statistics) | - | +| `stats-rts` | GHC/Haskell statistics. Can be enabled with `+RTS -T -RTS` option | - | +| `clients` | Clients information. Useful for debugging. | yes | +| `sockets` | General sockets information. | - | +| `socket-threads` | Thread infomation per socket. Useful for debugging. | yes | +| `threads` | Threads information. Useful for debugging. | yes | +| `server-info` | Aggregated server infomation. | - | +| `delete` | Delete known queue. Useful for content moderation. | - | +| `save` | Save queues/messages from memory. | yes | +| `help` | Help menu. | - | +| `quit` | Exit the control port. | - | + +#### Daily statistics You can enable `smp-server` statistics for `Grafana` dashboard by setting value `on` in `/etc/opt/simplex/smp-server.ini`, under `[STORE_LOG]` section in `log_stats:` field. @@ -1089,7 +1164,7 @@ Logs will be stored as `csv` file in `/var/opt/simplex/smp-server-stats.daily.lo fromTime,qCreated,qSecured,qDeleted,msgSent,msgRecv,dayMsgQueues,weekMsgQueues,monthMsgQueues,msgSentNtf,msgRecvNtf,dayCountNtf,weekCountNtf,monthCountNtf,qCount,msgCount,msgExpired,qDeletedNew,qDeletedSecured,pRelays_pRequests,pRelays_pSuccesses,pRelays_pErrorsConnect,pRelays_pErrorsCompat,pRelays_pErrorsOther,pRelaysOwn_pRequests,pRelaysOwn_pSuccesses,pRelaysOwn_pErrorsConnect,pRelaysOwn_pErrorsCompat,pRelaysOwn_pErrorsOther,pMsgFwds_pRequests,pMsgFwds_pSuccesses,pMsgFwds_pErrorsConnect,pMsgFwds_pErrorsCompat,pMsgFwds_pErrorsOther,pMsgFwdsOwn_pRequests,pMsgFwdsOwn_pSuccesses,pMsgFwdsOwn_pErrorsConnect,pMsgFwdsOwn_pErrorsCompat,pMsgFwdsOwn_pErrorsOther,pMsgFwdsRecv,qSub,qSubAuth,qSubDuplicate,qSubProhibited,msgSentAuth,msgSentQuota,msgSentLarge,msgNtfs,msgNtfNoSub,msgNtfLost,qSubNoMsg,msgRecvGet,msgGet,msgGetNoMsg,msgGetAuth,msgGetDuplicate,msgGetProhibited,psSubDaily,psSubWeekly,psSubMonthly,qCount2,ntfCreated,ntfDeleted,ntfSub,ntfSubAuth,ntfSubDuplicate,ntfCount,qDeletedAllB,qSubAllB,qSubEnd,qSubEndB,ntfDeletedB,ntfSubB,msgNtfsB,msgNtfExpired ``` -#### Fields description +**Fields description** | Field number | Field name | Field Description | | ------------- | ---------------------------- | -------------------------- | diff --git a/docs/XFTP-SERVER.md b/docs/XFTP-SERVER.md index a2eb9816e5..88428a0dc3 100644 --- a/docs/XFTP-SERVER.md +++ b/docs/XFTP-SERVER.md @@ -361,7 +361,74 @@ Feb 27 19:21:11 localhost xftp-server[2350]: Listening on port 443... Feb 27 19:21:11 localhost xftp-server[2350]: [INFO 2023-02-27 19:21:11 +0000 src/Simplex/FileTransfer/Server/Env.hs:85] Total / available storage: 64424509440 / 64424509440 ```` -### Monitoring +### Control port + +Enabling control port in the configuration allows administrator to see information about the smp-server in real-time. Additionally, it allows to delete file chunks for content moderation and see the debug info about the clients, sockets, etc. Enabling the control port requires setting the `admin` and `user` passwords. + +1. Generate two passwords for each user: + + ```sh + tr -dc A-Za-z0-9 + control_port_user_password: + + [TRANSPORT] + control_port: 5224 + ``` + +3. Restart the server: + + ```sh + systemctl restart xftp-server + ``` + +To access the control port, use: + +```sh +nc 127.0.0.1 5224 +``` + +or: + +```sh +telnet 127.0.0.1 5224 +``` + +Upon connecting, the control port should print: + +```sh +XFTP server control port +'help' for supported commands +``` + +To authenticate, type the following and hit enter. Change the `my_generated_password` with the `user` or `admin` password from the configuration: + +```sh +auth my_generated_password +``` + +Here's the full list of commands, their descriptions and who can access them. + +| Command | Description | Requires `admin` role | +| ---------------- | ------------------------------------------------------------------------------- | -------------------------- | +| `stats-rts` | GHC/Haskell statistics. Can be enabled with `+RTS -T -RTS` option | - | +| `delete` | Delete known file chunk. Useful for content moderation. | - | +| `help` | Help menu. | - | +| `quit` | Exit the control port. | - | + +### Daily statistics You can enable `xftp-server` statistics for `Grafana` dashboard by setting value `on` in `/etc/opt/simplex-xftp/file-server.ini`, under `[STORE_LOG]` section in `log_stats:` field. From 15bac88ec99ba09ebc116ee5282bf593608c6218 Mon Sep 17 00:00:00 2001 From: Diogo Date: Wed, 13 Nov 2024 09:27:49 +0000 Subject: [PATCH 066/567] desktop, android: user profiles move auth to change actions, show unread counts (#5171) * auth only on change actions for profiles * show notification count in profiles view * auth to hidde profile * save authorized * refactor and icon fix * keep key --- .../common/views/chatlist/UserPicker.kt | 89 +++++++------ .../views/usersettings/UserProfilesView.kt | 119 ++++++++++-------- .../commonMain/resources/MR/base/strings.xml | 2 +- 3 files changed, 121 insertions(+), 89 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt index 2709c7760b..185ec3925f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt @@ -268,22 +268,32 @@ fun UserPicker( painterResource(MR.images.ic_manage_accounts), stringResource(MR.strings.your_chat_profiles), { - doWithAuth( - generalGetString(MR.strings.auth_open_chat_profiles), - generalGetString(MR.strings.auth_log_in_using_credential) - ) { - ModalManager.start.showCustomModal(keyboardCoversBar = false) { close -> - val search = rememberSaveable { mutableStateOf("") } - val profileHidden = rememberSaveable { mutableStateOf(false) } - ModalView( - { close() }, - showSearch = true, - searchAlwaysVisible = true, - onSearchValueChanged = { - search.value = it - }, - content = { UserProfilesView(chatModel, search, profileHidden) }) - } + ModalManager.start.showCustomModal(keyboardCoversBar = false) { close -> + val search = rememberSaveable { mutableStateOf("") } + val profileHidden = rememberSaveable { mutableStateOf(false) } + val authorized = remember { stateGetOrPut("authorized") { false } } + ModalView( + { close() }, + showSearch = true, + searchAlwaysVisible = true, + onSearchValueChanged = { + search.value = it + }, + content = { + UserProfilesView(chatModel, search, profileHidden) { block -> + if (authorized.value) { + block() + } else { + doWithAuth( + generalGetString(MR.strings.auth_open_chat_profiles), + generalGetString(MR.strings.auth_log_in_using_credential) + ) { + authorized.value = true + block() + } + } + } + }) } }, disabled = stopped @@ -412,26 +422,35 @@ fun UserProfilePickerItem( UserProfileRow(u, enabled) if (u.activeUser) { Icon(painterResource(MR.images.ic_done_filled), null, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground) - } else if (u.hidden) { - Icon(painterResource(MR.images.ic_lock), null, Modifier.size(20.dp), tint = MaterialTheme.colors.secondary) - } else if (unreadCount > 0) { - Box( - contentAlignment = Alignment.Center - ) { - Text( - unreadCountStr(unreadCount), - color = Color.White, - fontSize = 10.sp, - modifier = Modifier - .background(MaterialTheme.colors.primaryVariant, shape = CircleShape) - .padding(2.dp) - .badgeLayout() - ) - } - } else if (!u.showNtfs) { - Icon(painterResource(MR.images.ic_notifications_off), null, Modifier.size(20.dp), tint = MaterialTheme.colors.secondary) } else { - Box(Modifier.size(20.dp)) + Row(verticalAlignment = Alignment.CenterVertically) { + if (unreadCount > 0) { + Box( + contentAlignment = Alignment.Center, + ) { + Text( + unreadCountStr(unreadCount), + color = Color.White, + fontSize = 10.sp, + modifier = Modifier + .background(if (u.showNtfs) MaterialTheme.colors.primaryVariant else MaterialTheme.colors.secondary, shape = CircleShape) + .padding(2.dp) + .badgeLayout() + ) + } + + if (u.hidden) { + Spacer(Modifier.width(8.dp)) + Icon(painterResource(MR.images.ic_lock), null, Modifier.size(20.dp), tint = MaterialTheme.colors.secondary) + } + } else if (u.hidden) { + Icon(painterResource(MR.images.ic_lock), null, Modifier.size(20.dp), tint = MaterialTheme.colors.secondary) + } else if (!u.showNtfs) { + Icon(painterResource(MR.images.ic_notifications_off), null, Modifier.size(20.dp), tint = MaterialTheme.colors.secondary) + } else { + Box(Modifier.size(20.dp)) + } + } } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt index fa9e709d4b..ad732cd699 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt @@ -36,7 +36,7 @@ import dev.icerock.moko.resources.StringResource import kotlinx.coroutines.* @Composable -fun UserProfilesView(m: ChatModel, search: MutableState, profileHidden: MutableState) { +fun UserProfilesView(m: ChatModel, search: MutableState, profileHidden: MutableState, withAuth: (block: () -> Unit) -> Unit) { val searchTextOrPassword = rememberSaveable { search } val users by remember { derivedStateOf { m.users.map { it.user } } } val filteredUsers by remember { derivedStateOf { filteredUsers(m, searchTextOrPassword.value) } } @@ -48,8 +48,10 @@ fun UserProfilesView(m: ChatModel, search: MutableState, profileHidden: showHiddenProfilesNotice = m.controller.appPrefs.showHiddenProfilesNotice, visibleUsersCount = visibleUsersCount(m), addUser = { - ModalManager.center.showModalCloseable { close -> - CreateProfile(m, close) + withAuth { + ModalManager.center.showModalCloseable { close -> + CreateProfile(m, close) + } } }, activateUser = { user -> @@ -64,68 +66,78 @@ fun UserProfilesView(m: ChatModel, search: MutableState, profileHidden: } }, removeUser = { user -> - val text = buildAnnotatedString { - append(generalGetString(MR.strings.users_delete_all_chats_deleted) + "\n\n" + generalGetString(MR.strings.users_delete_profile_for) + " ") - withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { - append(user.displayName) + withAuth { + val text = buildAnnotatedString { + append(generalGetString(MR.strings.users_delete_all_chats_deleted) + "\n\n" + generalGetString(MR.strings.users_delete_profile_for) + " ") + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + append(user.displayName) + } + append(":") } - append(":") - } - AlertManager.shared.showAlertDialogButtonsColumn( - title = generalGetString(MR.strings.users_delete_question), - text = text, - buttons = { - Column { - SectionItemView({ - AlertManager.shared.hideAlert() - removeUser(m, user, users, true, searchTextOrPassword.value.trim()) - }) { - Text(stringResource(MR.strings.users_delete_with_connections), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = Color.Red) - } - SectionItemView({ - AlertManager.shared.hideAlert() - removeUser(m, user, users, false, searchTextOrPassword.value.trim()) - } - ) { - Text(stringResource(MR.strings.users_delete_data_only), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = Color.Red) + AlertManager.shared.showAlertDialogButtonsColumn( + title = generalGetString(MR.strings.users_delete_question), + text = text, + buttons = { + Column { + SectionItemView({ + AlertManager.shared.hideAlert() + removeUser(m, user, users, true, searchTextOrPassword.value.trim()) + }) { + Text(stringResource(MR.strings.users_delete_with_connections), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = Color.Red) + } + SectionItemView({ + AlertManager.shared.hideAlert() + removeUser(m, user, users, false, searchTextOrPassword.value.trim()) + } + ) { + Text(stringResource(MR.strings.users_delete_data_only), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = Color.Red) + } } } - } - ) + ) + } }, unhideUser = { user -> - if (passwordEntryRequired(user, searchTextOrPassword.value)) { - ModalManager.start.showModalCloseable(true) { close -> - ProfileActionView(UserProfileAction.UNHIDE, user) { pwd -> - withBGApi { - setUserPrivacy(m) { m.controller.apiUnhideUser(user, pwd) } - close() + withAuth { + if (passwordEntryRequired(user, searchTextOrPassword.value)) { + ModalManager.start.showModalCloseable(true) { close -> + ProfileActionView(UserProfileAction.UNHIDE, user) { pwd -> + withBGApi { + setUserPrivacy(m) { m.controller.apiUnhideUser(user, pwd) } + close() + } } } + } else { + withBGApi { setUserPrivacy(m) { m.controller.apiUnhideUser(user, searchTextOrPassword.value.trim()) } } } - } else { - withBGApi { setUserPrivacy(m) { m.controller.apiUnhideUser(user, searchTextOrPassword.value.trim()) } } } }, muteUser = { user -> - withBGApi { - setUserPrivacy(m, onSuccess = { - if (m.controller.appPrefs.showMuteProfileAlert.get()) showMuteProfileAlert(m.controller.appPrefs.showMuteProfileAlert) - }) { m.controller.apiMuteUser(user) } + withAuth { + withBGApi { + setUserPrivacy(m, onSuccess = { + if (m.controller.appPrefs.showMuteProfileAlert.get()) showMuteProfileAlert(m.controller.appPrefs.showMuteProfileAlert) + }) { m.controller.apiMuteUser(user) } + } } }, unmuteUser = { user -> - withBGApi { setUserPrivacy(m) { m.controller.apiUnmuteUser(user) } } + withAuth { + withBGApi { setUserPrivacy(m) { m.controller.apiUnmuteUser(user) } } + } }, showHiddenProfile = { user -> - ModalManager.start.showModalCloseable(true) { close -> - HiddenProfileView(m, user) { - profileHidden.value = true - withBGApi { - delay(10_000) - profileHidden.value = false + withAuth { + ModalManager.start.showModalCloseable(true) { close -> + HiddenProfileView(m, user) { + profileHidden.value = true + withBGApi { + delay(10_000) + profileHidden.value = false + } + close() } - close() } } } @@ -138,7 +150,7 @@ fun UserProfilesView(m: ChatModel, search: MutableState, profileHidden: @Composable private fun UserProfilesLayout( users: List, - filteredUsers: List, + filteredUsers: List, searchTextOrPassword: MutableState, profileHidden: MutableState, visibleUsersCount: Int, @@ -195,7 +207,7 @@ private fun UserProfilesLayout( @Composable private fun UserView( - user: User, + userInfo: UserInfo, visibleUsersCount: Int, activateUser: (User) -> Unit, removeUser: (User) -> Unit, @@ -205,7 +217,8 @@ private fun UserView( showHiddenProfile: (User) -> Unit, ) { val showMenu = remember { mutableStateOf(false) } - UserProfilePickerItem(user, onLongClick = { showMenu.value = true }) { + val user = userInfo.user + UserProfilePickerItem(user, onLongClick = { showMenu.value = true }, unreadCount = userInfo.unreadCount) { activateUser(user) } Box(Modifier.padding(horizontal = DEFAULT_PADDING)) { @@ -290,7 +303,7 @@ private fun ProfileActionView(action: UserProfileAction, user: User, doAction: ( } } -fun filteredUsers(m: ChatModel, searchTextOrPassword: String): List { +fun filteredUsers(m: ChatModel, searchTextOrPassword: String): List { val s = searchTextOrPassword.trim() val lower = s.lowercase() return m.users.filter { u -> @@ -299,7 +312,7 @@ fun filteredUsers(m: ChatModel, searchTextOrPassword: String): List { } else { correctPassword(u.user, s) } - }.map { it.user } + } } private fun visibleUsersCount(m: ChatModel): Int = m.users.filter { u -> !u.user.hidden }.size diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 1ab7e3aed2..0ada4d3095 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -267,7 +267,7 @@ Device authentication is disabled. Turning off SimpleX Lock. Stop chat Open chat console - Open chat profiles + Change chat profiles Open migration screen SimpleX Lock not enabled! You can turn on SimpleX Lock via Settings. From 60c37f0d1d03be234e0f40f97044daa48ae30f99 Mon Sep 17 00:00:00 2001 From: Diogo Date: Wed, 13 Nov 2024 11:41:39 +0000 Subject: [PATCH 067/567] ios: user profiles move auth to change actions, show unread counts (#5170) * ios: user profiles move auth to change actions, show unread count per profile * simpler approach and add profile protection * not show muted icon * refactor * not needed * fix * simpler fix * deadline --------- Co-authored-by: Evgeny Poberezkin --- .../Shared/Views/ChatList/UserPicker.swift | 18 ++- .../Views/UserSettings/UserProfilesView.swift | 128 ++++++++++++------ 2 files changed, 98 insertions(+), 48 deletions(-) diff --git a/apps/ios/Shared/Views/ChatList/UserPicker.swift b/apps/ios/Shared/Views/ChatList/UserPicker.swift index cfcfe851f3..dbe10ad997 100644 --- a/apps/ios/Shared/Views/ChatList/UserPicker.swift +++ b/apps/ios/Shared/Views/ChatList/UserPicker.swift @@ -124,7 +124,7 @@ struct UserPicker: View { ZStack(alignment: .topTrailing) { ProfileImage(imageStr: u.user.image, size: size, color: Color(uiColor: .tertiarySystemGroupedBackground)) if (u.unreadCount > 0) { - unreadBadge(u).offset(x: 4, y: -4) + UnreadBadge(userInfo: u).offset(x: 4, y: -4) } } .padding(.trailing, 6) @@ -169,15 +169,21 @@ struct UserPicker: View { } } } - - private func unreadBadge(_ u: UserInfo) -> some View { +} + +struct UnreadBadge: View { + var userInfo: UserInfo + @EnvironmentObject var theme: AppTheme + @Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize + + var body: some View { let size = dynamicSize(userFont).chatInfoSize - return unreadCountText(u.unreadCount) - .font(userFont <= .xxxLarge ? .caption : .caption2) + unreadCountText(userInfo.unreadCount) + .font(userFont <= .xxxLarge ? .caption : .caption2) .foregroundColor(.white) .padding(.horizontal, dynamicSize(userFont).unreadPadding) .frame(minWidth: size, minHeight: size) - .background(u.user.showNtfs ? theme.colors.primary : theme.colors.secondary) + .background(userInfo.user.showNtfs ? theme.colors.primary : theme.colors.secondary) .cornerRadius(dynamicSize(userFont).unreadCorner) } } diff --git a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift index 330ce56e0b..c3dce183bb 100644 --- a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift @@ -21,6 +21,7 @@ struct UserProfilesView: View { @State private var profileHidden = false @State private var profileAction: UserProfileAction? @State private var actionPassword = "" + @State private var navigateToProfileCreate = false var trimmedSearchTextOrPassword: String { searchTextOrPassword.trimmingCharacters(in: .whitespaces)} @@ -55,17 +56,6 @@ struct UserProfilesView: View { } var body: some View { - if authorized { - userProfilesView() - } else { - Button(action: runAuth) { Label("Unlock", systemImage: "lock") } - .onAppear(perform: runAuth) - } - } - - private func runAuth() { authorize(NSLocalizedString("Open user profiles", comment: "authentication reason"), $authorized) } - - private func userProfilesView() -> some View { List { if profileHidden { Button { @@ -77,12 +67,14 @@ struct UserProfilesView: View { Section { let users = filteredUsers() let v = ForEach(users) { u in - userView(u.user) + userView(u) } if #available(iOS 16, *) { v.onDelete { indexSet in if let i = indexSet.first { - confirmDeleteUser(users[i].user) + withAuth { + confirmDeleteUser(users[i].user) + } } } } else { @@ -90,12 +82,22 @@ struct UserProfilesView: View { } if trimmedSearchTextOrPassword == "" { - NavigationLink { - CreateProfile() - } label: { + NavigationLink( + destination: CreateProfile(), + isActive: $navigateToProfileCreate + ) { Label("Add profile", systemImage: "plus") + .frame(maxWidth: .infinity, alignment: .leading) + .frame(height: 38) + .padding(.leading, 16).padding(.vertical, 8).padding(.trailing, 32) + .contentShape(Rectangle()) + .onTapGesture { + withAuth { + self.navigateToProfileCreate = true + } + } + .padding(.leading, -16).padding(.vertical, -8).padding(.trailing, -32) } - .frame(height: 38) } } footer: { Text("Tap to activate profile.") @@ -189,7 +191,25 @@ struct UserProfilesView: View { private var visibleUsersCount: Int { m.users.filter({ u in !u.user.hidden }).count } - + + private func withAuth(_ action: @escaping () -> Void) { + if authorized { + action() + } else { + authenticate( + reason: NSLocalizedString("Change user profiles", comment: "authentication reason") + ) { laResult in + switch laResult { + case .success, .unavailable: + authorized = true + AppSheetState.shared.scenePhaseActive = true + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: action) + case .failed: authorized = false + } + } + } + } + private func correctPassword(_ user: User, _ pwd: String) -> Bool { if let ph = user.viewPwdHash { return pwd != "" && chatPasswordHash(pwd, ph.salt) == ph.hash @@ -213,8 +233,10 @@ struct UserProfilesView: View { passwordField settingsRow("trash", color: theme.colors.secondary) { Button("Delete chat profile", role: .destructive) { - profileAction = nil - Task { await removeUser(user, delSMPQueues, viewPwd: actionPassword) } + withAuth { + profileAction = nil + Task { await removeUser(user, delSMPQueues, viewPwd: actionPassword) } + } } .disabled(!actionEnabled(user)) } @@ -231,8 +253,10 @@ struct UserProfilesView: View { passwordField settingsRow("lock.open", color: theme.colors.secondary) { Button("Unhide chat profile") { - profileAction = nil - setUserPrivacy(user) { try await apiUnhideUser(user.userId, viewPwd: actionPassword) } + withAuth{ + profileAction = nil + setUserPrivacy(user) { try await apiUnhideUser(user.userId, viewPwd: actionPassword) } + } } .disabled(!actionEnabled(user)) } @@ -255,11 +279,13 @@ struct UserProfilesView: View { private func deleteModeButton(_ title: LocalizedStringKey, _ delSMPQueues: Bool) -> some View { Button(title, role: .destructive) { - if let user = userToDelete { - if passwordEntryRequired(user) { - profileAction = .deleteUser(user: user, delSMPQueues: delSMPQueues) - } else { - alert = .deleteUser(user: user, delSMPQueues: delSMPQueues) + withAuth { + if let user = userToDelete { + if passwordEntryRequired(user) { + profileAction = .deleteUser(user: user, delSMPQueues: delSMPQueues) + } else { + alert = .deleteUser(user: user, delSMPQueues: delSMPQueues) + } } } } @@ -301,7 +327,8 @@ struct UserProfilesView: View { } } - @ViewBuilder private func userView(_ user: User) -> some View { + @ViewBuilder private func userView(_ userInfo: UserInfo) -> some View { + let user = userInfo.user let v = Button { Task { do { @@ -319,12 +346,19 @@ struct UserProfilesView: View { Spacer() if user.activeUser { Image(systemName: "checkmark").foregroundColor(theme.colors.onBackground) - } else if user.hidden { - Image(systemName: "lock").foregroundColor(theme.colors.secondary) - } else if !user.showNtfs { - Image(systemName: "speaker.slash").foregroundColor(theme.colors.secondary) } else { - Image(systemName: "checkmark").foregroundColor(.clear) + if userInfo.unreadCount > 0 { + UnreadBadge(userInfo: userInfo) + } + if user.hidden { + Image(systemName: "lock").foregroundColor(theme.colors.secondary) + } else if userInfo.unreadCount == 0 { + if !user.showNtfs { + Image(systemName: "speaker.slash").foregroundColor(theme.colors.secondary) + } else { + Image(systemName: "checkmark").foregroundColor(.clear) + } + } } } } @@ -332,30 +366,38 @@ struct UserProfilesView: View { .swipeActions(edge: .leading, allowsFullSwipe: true) { if user.hidden { Button("Unhide") { - if passwordEntryRequired(user) { - profileAction = .unhideUser(user: user) - } else { - setUserPrivacy(user) { try await apiUnhideUser(user.userId, viewPwd: trimmedSearchTextOrPassword) } + withAuth { + if passwordEntryRequired(user) { + profileAction = .unhideUser(user: user) + } else { + setUserPrivacy(user) { try await apiUnhideUser(user.userId, viewPwd: trimmedSearchTextOrPassword) } + } } } .tint(.green) } else { if visibleUsersCount > 1 { Button("Hide") { - selectedUser = user + withAuth { + selectedUser = user + } } .tint(.gray) } Group { if user.showNtfs { Button("Mute") { - setUserPrivacy(user, successAlert: showMuteProfileAlert ? .muteProfileAlert : nil) { - try await apiMuteUser(user.userId) + withAuth { + setUserPrivacy(user, successAlert: showMuteProfileAlert ? .muteProfileAlert : nil) { + try await apiMuteUser(user.userId) + } } } } else { Button("Unmute") { - setUserPrivacy(user) { try await apiUnmuteUser(user.userId) } + withAuth { + setUserPrivacy(user) { try await apiUnmuteUser(user.userId) } + } } } } @@ -367,7 +409,9 @@ struct UserProfilesView: View { } else { v.swipeActions(edge: .trailing, allowsFullSwipe: true) { Button("Delete", role: .destructive) { - confirmDeleteUser(user) + withAuth { + confirmDeleteUser(user) + } } } } From 4d82209a3ac51689265a654a49715336b0d0409b Mon Sep 17 00:00:00 2001 From: Diogo Date: Thu, 14 Nov 2024 08:34:25 +0000 Subject: [PATCH 068/567] core: pagination API to load items around defined or the earliest unread item (#5100) * core: auto increment chat item ids (#5088) * core: auto increment chat item ids * file name * down name * update schema * ignore down migration on schema dump test * fix testDirectMessageDelete test * fix testNotes test * core: initial api support for items around a given item (#5092) * core: initial api support for items around a given item * implementation and tests for local messages * pass entities down * unused * getAllChatItems implementation and tests * pagination for getting chat and tests * remove unused import * group implementation and tests * refactor * order by created at for local and direct chats * core: initial landing api for chat and gaps (#5104) * initial work on initial param for loading chat * support for initial * controller parse * fixed sqls * refactor names * fix ChatLandingSection serialized type * total accuracy on landing section * descriptive view message * foldr * refactor to make landingSection reusable * refactor: use foldr everywhere * propagate search * Revert "propagate search" This reverts commit 01611fd7197c135639db2a869d96d7621ba093ee. * throw when search is sent for initial * gap size wip (needs testing) * final * remove order by * remove index --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> * core: fix initial api latest chat items ordering (#5151) * core: fix one item missing from latest in initial and wrong check (#5153) * core: fix one item missing from latest in initial and wrong check * final fixes and tests * clearer tests * core: remove gaps and make sure page size is always the same (#5163) * remove gaps * consistent pagination size * proper fix and around fix too * optimize * refactor * core: simplify pagination * core: first unread queries (#5174) * core: pagination nav info (#5175) * core: pagination nav info * wip * rework * rework * group, local * fix * rename * fix tests * just --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Co-authored-by: Evgeny Poberezkin --- .../src/Directory/Service.hs | 4 +- simplex-chat.cabal | 1 + src/Simplex/Chat.hs | 14 +- src/Simplex/Chat/Controller.hs | 4 +- src/Simplex/Chat/Messages.hs | 18 +- .../M20241023_chat_item_autoincrement_id.hs | 34 + src/Simplex/Chat/Migrations/chat_schema.sql | 4 +- src/Simplex/Chat/Store/Messages.hs | 682 +++++++++++++----- src/Simplex/Chat/Store/Migrations.hs | 4 +- src/Simplex/Chat/View.hs | 2 +- tests/ChatTests/Direct.hs | 58 +- tests/ChatTests/Groups.hs | 34 + tests/ChatTests/Local.hs | 6 +- tests/SchemaDump.hs | 4 +- 14 files changed, 665 insertions(+), 204 deletions(-) create mode 100644 src/Simplex/Chat/Migrations/M20241023_chat_item_autoincrement_id.hs diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs index c1012f2a0a..2c18d4df27 100644 --- a/apps/simplex-directory-service/src/Directory/Service.hs +++ b/apps/simplex-directory-service/src/Directory/Service.hs @@ -630,7 +630,7 @@ directoryService st DirectoryOpts {adminUsers, superUsers, serviceName, searchRe listGroups count pending = readTVarIO (groupRegs st) >>= \groups -> do grs <- - if pending + if pending then filterM (fmap pendingApproval . readTVarIO . groupRegStatus) groups else pure groups sendReply $ tshow (length grs) <> " registered group(s)" <> (if length grs > count then ", showing the last " <> tshow count else "") @@ -689,7 +689,7 @@ getContact cc ctId = resp <$> sendChatCmd cc (APIGetChat (ChatRef CTDirect ctId) where resp :: ChatResponse -> Maybe Contact resp = \case - CRApiChat _ (AChat SCTDirect Chat {chatInfo = DirectChat ct}) -> Just ct + CRApiChat _ (AChat SCTDirect Chat {chatInfo = DirectChat ct}) _ -> Just ct _ -> Nothing getGroup :: ChatController -> GroupId -> IO (Maybe GroupInfo) diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 96d16f5004..fb7f32faa5 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -150,6 +150,7 @@ library Simplex.Chat.Migrations.M20240920_user_order Simplex.Chat.Migrations.M20241008_indexes Simplex.Chat.Migrations.M20241010_contact_requests_contact_id + Simplex.Chat.Migrations.M20241023_chat_item_autoincrement_id Simplex.Chat.Mobile Simplex.Chat.Mobile.File Simplex.Chat.Mobile.Shared diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 885d4303c8..b74531b9e1 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -735,14 +735,14 @@ processChatCommand' vr = \case APIGetChat (ChatRef cType cId) pagination search -> withUser $ \user -> case cType of -- TODO optimize queries calculating ChatStats, currently they're disabled CTDirect -> do - directChat <- withFastStore (\db -> getDirectChat db vr user cId pagination search) - pure $ CRApiChat user (AChat SCTDirect directChat) + (directChat, navInfo) <- withFastStore (\db -> getDirectChat db vr user cId pagination search) + pure $ CRApiChat user (AChat SCTDirect directChat) navInfo CTGroup -> do - groupChat <- withFastStore (\db -> getGroupChat db vr user cId pagination search) - pure $ CRApiChat user (AChat SCTGroup groupChat) + (groupChat, navInfo) <- withFastStore (\db -> getGroupChat db vr user cId pagination search) + pure $ CRApiChat user (AChat SCTGroup groupChat) navInfo CTLocal -> do - localChat <- withFastStore (\db -> getLocalChat db user cId pagination search) - pure $ CRApiChat user (AChat SCTLocal localChat) + (localChat, navInfo) <- withFastStore (\db -> getLocalChat db user cId pagination search) + pure $ CRApiChat user (AChat SCTLocal localChat) navInfo CTContactRequest -> pure $ chatCmdError (Just user) "not implemented" CTContactConnection -> pure $ chatCmdError (Just user) "not supported" APIGetChatItems pagination search -> withUser $ \user -> do @@ -8301,6 +8301,8 @@ chatCommandP = (CPLast <$ "count=" <*> A.decimal) <|> (CPAfter <$ "after=" <*> A.decimal <* A.space <* "count=" <*> A.decimal) <|> (CPBefore <$ "before=" <*> A.decimal <* A.space <* "count=" <*> A.decimal) + <|> (CPAround <$ "around=" <*> A.decimal <* A.space <* "count=" <*> A.decimal) + <|> (CPInitial <$ "initial=" <*> A.decimal) paginationByTimeP = (PTLast <$ "count=" <*> A.decimal) <|> (PTAfter <$ "after=" <*> strP <* A.space <* "count=" <*> A.decimal) diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index b39b4d7456..4be6086acb 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -572,7 +572,7 @@ data ChatResponse | CRChatSuspended | CRApiChats {user :: User, chats :: [AChat]} | CRChats {chats :: [AChat]} - | CRApiChat {user :: User, chat :: AChat} + | CRApiChat {user :: User, chat :: AChat, navInfo :: Maybe NavigationInfo} | CRChatItems {user :: User, chatName_ :: Maybe ChatName, chatItems :: [AChatItem]} | CRChatItemInfo {user :: User, chatItem :: AChatItem, chatItemInfo :: ChatItemInfo} | CRChatItemId User (Maybe ChatItemId) @@ -839,6 +839,8 @@ data ChatPagination = CPLast Int | CPAfter ChatItemId Int | CPBefore ChatItemId Int + | CPAround ChatItemId Int + | CPInitial Int deriving (Show) data PaginationByTime diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index 50e68e5bf4..0e3575b64c 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -227,8 +227,8 @@ data CChatItem c = forall d. MsgDirectionI d => CChatItem (SMsgDirection d) (Cha deriving instance Show (CChatItem c) -cchatItemId :: CChatItem c -> ChatItemId -cchatItemId (CChatItem _ ci) = chatItemId' ci +cChatItemId :: CChatItem c -> ChatItemId +cChatItemId (CChatItem _ ci) = chatItemId' ci chatItemId' :: ChatItem c d -> ChatItemId chatItemId' ChatItem {meta = CIMeta {itemId}} = itemId @@ -239,6 +239,12 @@ chatItemTs (CChatItem _ ci) = chatItemTs' ci chatItemTs' :: ChatItem c d -> UTCTime chatItemTs' ChatItem {meta = CIMeta {itemTs}} = itemTs +ciCreatedAt :: CChatItem c -> UTCTime +ciCreatedAt (CChatItem _ ci) = ciCreatedAt' ci + +ciCreatedAt' :: ChatItem c d -> UTCTime +ciCreatedAt' ChatItem {meta = CIMeta {createdAt}} = createdAt + chatItemTimed :: ChatItem c d -> Maybe CITimed chatItemTimed ChatItem {meta = CIMeta {itemTimed}} = itemTimed @@ -318,6 +324,12 @@ data ChatStats = ChatStats } deriving (Show) +data NavigationInfo = NavigationInfo + { afterUnread :: Int, + afterTotal :: Int + } + deriving (Show) + -- | type to show a mix of messages from multiple chats data AChatItem = forall c d. (ChatTypeI c, MsgDirectionI d) => AChatItem (SChatType c) (SMsgDirection d) (ChatInfo c) (ChatItem c d) @@ -1408,6 +1420,8 @@ $(JQ.deriveJSON defaultJSON ''ChatItemInfo) $(JQ.deriveJSON defaultJSON ''ChatStats) +$(JQ.deriveJSON defaultJSON ''NavigationInfo) + instance ChatTypeI c => ToJSON (Chat c) where toJSON = $(JQ.mkToJSON defaultJSON ''Chat) toEncoding = $(JQ.mkToEncoding defaultJSON ''Chat) diff --git a/src/Simplex/Chat/Migrations/M20241023_chat_item_autoincrement_id.hs b/src/Simplex/Chat/Migrations/M20241023_chat_item_autoincrement_id.hs new file mode 100644 index 0000000000..7f1e272026 --- /dev/null +++ b/src/Simplex/Chat/Migrations/M20241023_chat_item_autoincrement_id.hs @@ -0,0 +1,34 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Migrations.M20241023_chat_item_autoincrement_id where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20241023_chat_item_autoincrement_id :: Query +m20241023_chat_item_autoincrement_id = + [sql| +INSERT INTO sqlite_sequence (name, seq) +SELECT 'chat_items', MAX(ROWID) FROM chat_items; + +PRAGMA writable_schema=1; + +UPDATE sqlite_master SET sql = replace(sql, 'INTEGER PRIMARY KEY', 'INTEGER PRIMARY KEY AUTOINCREMENT') +WHERE name = 'chat_items' AND type = 'table'; + +PRAGMA writable_schema=0; +|] + +down_m20241023_chat_item_autoincrement_id :: Query +down_m20241023_chat_item_autoincrement_id = + [sql| +DELETE FROM sqlite_sequence WHERE name = 'chat_items'; + +PRAGMA writable_schema=1; + +UPDATE sqlite_master +SET sql = replace(sql, 'INTEGER PRIMARY KEY AUTOINCREMENT', 'INTEGER PRIMARY KEY') +WHERE name = 'chat_items' AND type = 'table'; + +PRAGMA writable_schema=0; +|] diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index 2619a5c4e5..f16ca6b870 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -360,7 +360,7 @@ CREATE TABLE pending_group_messages( updated_at TEXT NOT NULL DEFAULT(datetime('now')) ); CREATE TABLE chat_items( - chat_item_id INTEGER PRIMARY KEY, + chat_item_id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE, contact_id INTEGER REFERENCES contacts ON DELETE CASCADE, group_id INTEGER REFERENCES groups ON DELETE CASCADE, @@ -399,6 +399,7 @@ CREATE TABLE chat_items( fwd_from_chat_item_id INTEGER REFERENCES chat_items ON DELETE SET NULL, via_proxy INTEGER ); +CREATE TABLE sqlite_sequence(name,seq); CREATE TABLE chat_item_messages( chat_item_id INTEGER NOT NULL REFERENCES chat_items ON DELETE CASCADE, message_id INTEGER NOT NULL UNIQUE REFERENCES messages ON DELETE CASCADE, @@ -429,7 +430,6 @@ CREATE TABLE commands( created_at TEXT NOT NULL DEFAULT(datetime('now')), updated_at TEXT NOT NULL DEFAULT(datetime('now')) ); -CREATE TABLE sqlite_sequence(name,seq); CREATE TABLE settings( settings_id INTEGER PRIMARY KEY, chat_item_ttl INTEGER, diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index ad77e6c3f1..ab8a52a98a 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -3,6 +3,7 @@ {-# LANGUAGE GADTs #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} @@ -947,37 +948,41 @@ getContactConnectionChatPreviews_ db User {userId} pagination clq = case clq of aChat = AChat SCTContactConnection $ Chat (ContactConnection conn) [] stats in ACPD SCTContactConnection $ ContactConnectionPD updatedAt aChat -getDirectChat :: DB.Connection -> VersionRangeChat -> User -> Int64 -> ChatPagination -> Maybe String -> ExceptT StoreError IO (Chat 'CTDirect) +getDirectChat :: DB.Connection -> VersionRangeChat -> User -> Int64 -> ChatPagination -> Maybe String -> ExceptT StoreError IO (Chat 'CTDirect, Maybe NavigationInfo) getDirectChat db vr user contactId pagination search_ = do let search = fromMaybe "" search_ ct <- getContact db vr user contactId - liftIO $ case pagination of - CPLast count -> getDirectChatLast_ db user ct count search - CPAfter afterId count -> getDirectChatAfter_ db user ct afterId count search - CPBefore beforeId count -> getDirectChatBefore_ db user ct beforeId count search + case pagination of + CPLast count -> liftIO $ (,Nothing) <$> getDirectChatLast_ db user ct count search + CPAfter afterId count -> (,Nothing) <$> getDirectChatAfter_ db user ct afterId count search + CPBefore beforeId count -> (,Nothing) <$> getDirectChatBefore_ db user ct beforeId count search + CPAround aroundId count -> getDirectChatAround_ db user ct aroundId count search + CPInitial count -> do + unless (null search) $ throwError $ SEInternalError "initial chat pagination doesn't support search" + getDirectChatInitial_ db user ct count -- the last items in reverse order (the last item in the conversation is the first in the returned list) getDirectChatLast_ :: DB.Connection -> User -> Contact -> Int -> String -> IO (Chat 'CTDirect) -getDirectChatLast_ db user@User {userId} ct@Contact {contactId} count search = do +getDirectChatLast_ db user ct count search = do let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False} - chatItemIds <- getDirectChatItemIdsLast_ - currentTs <- getCurrentTime - chatItems <- mapM (safeGetDirectItem db user ct currentTs) chatItemIds - pure $ Chat (DirectChat ct) (reverse chatItems) stats - where - getDirectChatItemIdsLast_ :: IO [ChatItemId] - getDirectChatItemIdsLast_ = - map fromOnly - <$> DB.query - db - [sql| - SELECT chat_item_id - FROM chat_items - WHERE user_id = ? AND contact_id = ? AND item_text LIKE '%' || ? || '%' - ORDER BY created_at DESC, chat_item_id DESC - LIMIT ? - |] - (userId, contactId, search, count) + ciIds <- getDirectChatItemIdsLast_ db user ct count search + ts <- getCurrentTime + cis <- mapM (safeGetDirectItem db user ct ts) ciIds + pure $ Chat (DirectChat ct) (reverse cis) stats + +getDirectChatItemIdsLast_ :: DB.Connection -> User -> Contact -> Int -> String -> IO [ChatItemId] +getDirectChatItemIdsLast_ db User {userId} Contact {contactId} count search = + map fromOnly + <$> DB.query + db + [sql| + SELECT chat_item_id + FROM chat_items + WHERE user_id = ? AND contact_id = ? AND item_text LIKE '%' || ? || '%' + ORDER BY created_at DESC, chat_item_id DESC + LIMIT ? + |] + (userId, contactId, search, count) safeGetDirectItem :: DB.Connection -> User -> Contact -> UTCTime -> ChatItemId -> IO (CChatItem 'CTDirect) safeGetDirectItem db user ct currentTs itemId = @@ -1021,82 +1026,181 @@ getDirectChatItemLast db user@User {userId} contactId = do (userId, contactId) getDirectChatItem db user contactId chatItemId -getDirectChatAfter_ :: DB.Connection -> User -> Contact -> ChatItemId -> Int -> String -> IO (Chat 'CTDirect) -getDirectChatAfter_ db user@User {userId} ct@Contact {contactId} afterChatItemId count search = do +getDirectChatAfter_ :: DB.Connection -> User -> Contact -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTDirect) +getDirectChatAfter_ db user ct@Contact {contactId} afterId count search = do let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False} - chatItemIds <- getDirectChatItemIdsAfter_ - currentTs <- getCurrentTime - chatItems <- mapM (safeGetDirectItem db user ct currentTs) chatItemIds - pure $ Chat (DirectChat ct) chatItems stats + afterCI <- getDirectChatItem db user contactId afterId + ciIds <- liftIO $ getDirectCIsAfter_ db user ct afterCI count search + ts <- liftIO getCurrentTime + cis <- liftIO $ mapM (safeGetDirectItem db user ct ts) ciIds + pure $ Chat (DirectChat ct) cis stats + +getDirectCIsAfter_ :: DB.Connection -> User -> Contact -> CChatItem 'CTDirect -> Int -> String -> IO [ChatItemId] +getDirectCIsAfter_ db User {userId} Contact {contactId} afterCI count search = + map fromOnly + <$> DB.query + db + [sql| + SELECT chat_item_id + FROM chat_items + WHERE user_id = ? AND contact_id = ? AND item_text LIKE '%' || ? || '%' + AND (created_at > ? OR (created_at = ? AND chat_item_id > ?)) + ORDER BY created_at ASC, chat_item_id ASC + LIMIT ? + |] + (userId, contactId, search, ciCreatedAt afterCI, ciCreatedAt afterCI, cChatItemId afterCI, count) + +getDirectChatBefore_ :: DB.Connection -> User -> Contact -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTDirect) +getDirectChatBefore_ db user ct@Contact {contactId} beforeId count search = do + let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False} + beforeCI <- getDirectChatItem db user contactId beforeId + ciIds <- liftIO $ getDirectCIsBefore_ db user ct beforeCI count search + ts <- liftIO getCurrentTime + cis <- liftIO $ mapM (safeGetDirectItem db user ct ts) ciIds + pure $ Chat (DirectChat ct) (reverse cis) stats + +getDirectCIsBefore_ :: DB.Connection -> User -> Contact -> CChatItem 'CTDirect -> Int -> String -> IO [ChatItemId] +getDirectCIsBefore_ db User {userId} Contact {contactId} beforeCI count search = + map fromOnly + <$> DB.query + db + [sql| + SELECT chat_item_id + FROM chat_items + WHERE user_id = ? AND contact_id = ? AND item_text LIKE '%' || ? || '%' + AND (created_at < ? OR (created_at = ? AND chat_item_id < ?)) + ORDER BY created_at DESC, chat_item_id DESC + LIMIT ? + |] + (userId, contactId, search, ciCreatedAt beforeCI, ciCreatedAt beforeCI, cChatItemId beforeCI, count) + +getDirectChatAround_ :: DB.Connection -> User -> Contact -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTDirect, Maybe NavigationInfo) +getDirectChatAround_ db user ct aroundId count search = do + stats <- liftIO $ getContactStats_ db user ct + getDirectChatAround' db user ct aroundId count search stats + +getDirectChatAround' :: DB.Connection -> User -> Contact -> ChatItemId -> Int -> String -> ChatStats -> ExceptT StoreError IO (Chat 'CTDirect, Maybe NavigationInfo) +getDirectChatAround' db user ct@Contact {contactId} aroundId count search stats = do + aroundCI <- getDirectChatItem db user contactId aroundId + beforeIds <- liftIO $ getDirectCIsBefore_ db user ct aroundCI count search + afterIds <- liftIO $ getDirectCIsAfter_ db user ct aroundCI count search + ts <- liftIO getCurrentTime + beforeCIs <- liftIO $ mapM (safeGetDirectItem db user ct ts) beforeIds + afterCIs <- liftIO $ mapM (safeGetDirectItem db user ct ts) afterIds + let cis = reverse beforeCIs <> [aroundCI] <> afterCIs + navInfo <- liftIO $ getNavInfo cis + pure (Chat (DirectChat ct) cis stats, Just navInfo) where - getDirectChatItemIdsAfter_ :: IO [ChatItemId] - getDirectChatItemIdsAfter_ = - map fromOnly + getNavInfo cis_ = case cis_ of + [] -> pure $ NavigationInfo 0 0 + cis -> getContactNavInfo_ db user ct (last cis) + +getDirectChatInitial_ :: DB.Connection -> User -> Contact -> Int -> ExceptT StoreError IO (Chat 'CTDirect, Maybe NavigationInfo) +getDirectChatInitial_ db user ct count = do + liftIO (getContactMinUnreadId_ db user ct) >>= \case + Just minUnreadItemId -> do + unreadCount <- liftIO $ getContactUnreadCount_ db user ct + let stats = ChatStats {unreadCount, minUnreadItemId, unreadChat = False} + getDirectChatAround' db user ct minUnreadItemId count "" stats + Nothing -> liftIO $ (,Just $ NavigationInfo 0 0) <$> getDirectChatLast_ db user ct count "" + +getContactStats_ :: DB.Connection -> User -> Contact -> IO ChatStats +getContactStats_ db user ct = do + minUnreadItemId <- fromMaybe 0 <$> getContactMinUnreadId_ db user ct + unreadCount <- getContactUnreadCount_ db user ct + pure ChatStats {unreadCount, minUnreadItemId, unreadChat = False} + +getContactMinUnreadId_ :: DB.Connection -> User -> Contact -> IO (Maybe ChatItemId) +getContactMinUnreadId_ db User {userId} Contact {contactId} = + fmap join . maybeFirstRow fromOnly $ + DB.query + db + [sql| + SELECT chat_item_id + FROM chat_items + WHERE user_id = ? AND contact_id = ? AND item_status = ? + ORDER BY created_at ASC, chat_item_id ASC + LIMIT 1 + |] + (userId, contactId, CISRcvNew) + +getContactUnreadCount_ :: DB.Connection -> User -> Contact -> IO Int +getContactUnreadCount_ db User {userId} Contact {contactId} = + fromOnly . head + <$> DB.query + db + [sql| + SELECT COUNT(1) + FROM chat_items + WHERE user_id = ? AND contact_id = ? AND item_status = ? + |] + (userId, contactId, CISRcvNew) + +getContactNavInfo_ :: DB.Connection -> User -> Contact -> CChatItem 'CTDirect -> IO NavigationInfo +getContactNavInfo_ db User {userId} Contact {contactId} afterCI = do + afterUnread <- getAfterUnreadCount + afterTotal <- getAfterTotalCount + pure NavigationInfo {afterUnread, afterTotal} + where + getAfterUnreadCount :: IO Int + getAfterUnreadCount = + fromOnly . head <$> DB.query db [sql| - SELECT chat_item_id + SELECT COUNT(1) FROM chat_items - WHERE user_id = ? AND contact_id = ? AND item_text LIKE '%' || ? || '%' - AND chat_item_id > ? - ORDER BY created_at ASC, chat_item_id ASC - LIMIT ? + WHERE user_id = ? AND contact_id = ? AND item_status = ? + AND (created_at > ? OR (created_at = ? AND chat_item_id > ?)) |] - (userId, contactId, search, afterChatItemId, count) - -getDirectChatBefore_ :: DB.Connection -> User -> Contact -> ChatItemId -> Int -> String -> IO (Chat 'CTDirect) -getDirectChatBefore_ db user@User {userId} ct@Contact {contactId} beforeChatItemId count search = do - let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False} - chatItemIds <- getDirectChatItemsIdsBefore_ - currentTs <- getCurrentTime - chatItems <- mapM (safeGetDirectItem db user ct currentTs) chatItemIds - pure $ Chat (DirectChat ct) (reverse chatItems) stats - where - getDirectChatItemsIdsBefore_ :: IO [ChatItemId] - getDirectChatItemsIdsBefore_ = - map fromOnly + (userId, contactId, CISRcvNew, ciCreatedAt afterCI, ciCreatedAt afterCI, cChatItemId afterCI) + getAfterTotalCount :: IO Int + getAfterTotalCount = + fromOnly . head <$> DB.query db [sql| - SELECT chat_item_id + SELECT COUNT(1) FROM chat_items - WHERE user_id = ? AND contact_id = ? AND item_text LIKE '%' || ? || '%' - AND chat_item_id < ? - ORDER BY created_at DESC, chat_item_id DESC - LIMIT ? + WHERE user_id = ? AND contact_id = ? + AND (created_at > ? OR (created_at = ? AND chat_item_id > ?)) |] - (userId, contactId, search, beforeChatItemId, count) + (userId, contactId, ciCreatedAt afterCI, ciCreatedAt afterCI, cChatItemId afterCI) -getGroupChat :: DB.Connection -> VersionRangeChat -> User -> Int64 -> ChatPagination -> Maybe String -> ExceptT StoreError IO (Chat 'CTGroup) +getGroupChat :: DB.Connection -> VersionRangeChat -> User -> Int64 -> ChatPagination -> Maybe String -> ExceptT StoreError IO (Chat 'CTGroup, Maybe NavigationInfo) getGroupChat db vr user groupId pagination search_ = do let search = fromMaybe "" search_ g <- getGroupInfo db vr user groupId case pagination of - CPLast count -> liftIO $ getGroupChatLast_ db user g count search - CPAfter afterId count -> getGroupChatAfter_ db user g afterId count search - CPBefore beforeId count -> getGroupChatBefore_ db user g beforeId count search + CPLast count -> liftIO $ (,Nothing) <$> getGroupChatLast_ db user g count search + CPAfter afterId count -> (,Nothing) <$> getGroupChatAfter_ db user g afterId count search + CPBefore beforeId count -> (,Nothing) <$> getGroupChatBefore_ db user g beforeId count search + CPAround aroundId count -> getGroupChatAround_ db user g aroundId count search + CPInitial count -> do + unless (null search) $ throwError $ SEInternalError "initial chat pagination doesn't support search" + getGroupChatInitial_ db user g count getGroupChatLast_ :: DB.Connection -> User -> GroupInfo -> Int -> String -> IO (Chat 'CTGroup) -getGroupChatLast_ db user@User {userId} g@GroupInfo {groupId} count search = do +getGroupChatLast_ db user g count search = do let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False} - chatItemIds <- getGroupChatItemIdsLast_ - currentTs <- getCurrentTime - chatItems <- mapM (safeGetGroupItem db user g currentTs) chatItemIds - pure $ Chat (GroupChat g) (reverse chatItems) stats - where - getGroupChatItemIdsLast_ :: IO [ChatItemId] - getGroupChatItemIdsLast_ = - map fromOnly - <$> DB.query - db - [sql| - SELECT chat_item_id - FROM chat_items - WHERE user_id = ? AND group_id = ? AND item_text LIKE '%' || ? || '%' - ORDER BY item_ts DESC, chat_item_id DESC - LIMIT ? - |] - (userId, groupId, search, count) + ciIds <- getGroupChatItemIdsLast_ db user g count search + ts <- getCurrentTime + cis <- mapM (safeGetGroupItem db user g ts) ciIds + pure $ Chat (GroupChat g) (reverse cis) stats + +getGroupChatItemIdsLast_ :: DB.Connection -> User -> GroupInfo -> Int -> String -> IO [ChatItemId] +getGroupChatItemIdsLast_ db User {userId} GroupInfo {groupId} count search = + map fromOnly + <$> DB.query + db + [sql| + SELECT chat_item_id + FROM chat_items + WHERE user_id = ? AND group_id = ? AND item_text LIKE '%' || ? || '%' + ORDER BY item_ts DESC, chat_item_id DESC + LIMIT ? + |] + (userId, groupId, search, count) safeGetGroupItem :: DB.Connection -> User -> GroupInfo -> UTCTime -> ChatItemId -> IO (CChatItem 'CTGroup) safeGetGroupItem db user g currentTs itemId = @@ -1141,83 +1245,180 @@ getGroupMemberChatItemLast db user@User {userId} groupId groupMemberId = do getGroupChatItem db user groupId chatItemId getGroupChatAfter_ :: DB.Connection -> User -> GroupInfo -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTGroup) -getGroupChatAfter_ db user@User {userId} g@GroupInfo {groupId} afterChatItemId count search = do +getGroupChatAfter_ db user g@GroupInfo {groupId} afterId count search = do let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False} - afterChatItem <- getGroupChatItem db user groupId afterChatItemId - chatItemIds <- liftIO $ getGroupChatItemIdsAfter_ (chatItemTs afterChatItem) - currentTs <- liftIO getCurrentTime - chatItems <- liftIO $ mapM (safeGetGroupItem db user g currentTs) chatItemIds - pure $ Chat (GroupChat g) chatItems stats - where - getGroupChatItemIdsAfter_ :: UTCTime -> IO [ChatItemId] - getGroupChatItemIdsAfter_ afterChatItemTs = - map fromOnly - <$> DB.query - db - [sql| - SELECT chat_item_id - FROM chat_items - WHERE user_id = ? AND group_id = ? AND item_text LIKE '%' || ? || '%' - AND (item_ts > ? OR (item_ts = ? AND chat_item_id > ?)) - ORDER BY item_ts ASC, chat_item_id ASC - LIMIT ? - |] - (userId, groupId, search, afterChatItemTs, afterChatItemTs, afterChatItemId, count) + afterCI <- getGroupChatItem db user groupId afterId + ciIds <- liftIO $ getGroupCIsAfter_ db user g afterCI count search + ts <- liftIO getCurrentTime + cis <- liftIO $ mapM (safeGetGroupItem db user g ts) ciIds + pure $ Chat (GroupChat g) cis stats + +getGroupCIsAfter_ :: DB.Connection -> User -> GroupInfo -> CChatItem 'CTGroup -> Int -> String -> IO [ChatItemId] +getGroupCIsAfter_ db User {userId} GroupInfo {groupId} afterCI count search = + map fromOnly + <$> DB.query + db + [sql| + SELECT chat_item_id + FROM chat_items + WHERE user_id = ? AND group_id = ? AND item_text LIKE '%' || ? || '%' + AND (item_ts > ? OR (item_ts = ? AND chat_item_id > ?)) + ORDER BY item_ts ASC, chat_item_id ASC + LIMIT ? + |] + (userId, groupId, search, chatItemTs afterCI, chatItemTs afterCI, cChatItemId afterCI, count) getGroupChatBefore_ :: DB.Connection -> User -> GroupInfo -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTGroup) -getGroupChatBefore_ db user@User {userId} g@GroupInfo {groupId} beforeChatItemId count search = do +getGroupChatBefore_ db user g@GroupInfo {groupId} beforeId count search = do let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False} - beforeChatItem <- getGroupChatItem db user groupId beforeChatItemId - chatItemIds <- liftIO $ getGroupChatItemIdsBefore_ (chatItemTs beforeChatItem) - currentTs <- liftIO getCurrentTime - chatItems <- liftIO $ mapM (safeGetGroupItem db user g currentTs) chatItemIds - pure $ Chat (GroupChat g) (reverse chatItems) stats + beforeCI <- getGroupChatItem db user groupId beforeId + ciIds <- liftIO $ getGroupCIsBefore_ db user g beforeCI count search + ts <- liftIO getCurrentTime + cis <- liftIO $ mapM (safeGetGroupItem db user g ts) ciIds + pure $ Chat (GroupChat g) (reverse cis) stats + +getGroupCIsBefore_ :: DB.Connection -> User -> GroupInfo -> CChatItem 'CTGroup -> Int -> String -> IO [ChatItemId] +getGroupCIsBefore_ db User {userId} GroupInfo {groupId} beforeCI count search = + map fromOnly + <$> DB.query + db + [sql| + SELECT chat_item_id + FROM chat_items + WHERE user_id = ? AND group_id = ? AND item_text LIKE '%' || ? || '%' + AND (item_ts < ? OR (item_ts = ? AND chat_item_id < ?)) + ORDER BY item_ts DESC, chat_item_id DESC + LIMIT ? + |] + (userId, groupId, search, chatItemTs beforeCI, chatItemTs beforeCI, cChatItemId beforeCI, count) + +getGroupChatAround_ :: DB.Connection -> User -> GroupInfo -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTGroup, Maybe NavigationInfo) +getGroupChatAround_ db user g aroundId count search = do + stats <- liftIO $ getGroupStats_ db user g + getGroupChatAround' db user g aroundId count search stats + +getGroupChatAround' :: DB.Connection -> User -> GroupInfo -> ChatItemId -> Int -> String -> ChatStats -> ExceptT StoreError IO (Chat 'CTGroup, Maybe NavigationInfo) +getGroupChatAround' db user g@GroupInfo {groupId} aroundId count search stats = do + aroundCI <- getGroupChatItem db user groupId aroundId + beforeIds <- liftIO $ getGroupCIsBefore_ db user g aroundCI count search + afterIds <- liftIO $ getGroupCIsAfter_ db user g aroundCI count search + ts <- liftIO getCurrentTime + beforeCIs <- liftIO $ mapM (safeGetGroupItem db user g ts) beforeIds + afterCIs <- liftIO $ mapM (safeGetGroupItem db user g ts) afterIds + let cis = reverse beforeCIs <> [aroundCI] <> afterCIs + navInfo <- liftIO $ getNavInfo cis + pure (Chat (GroupChat g) cis stats, Just navInfo) where - getGroupChatItemIdsBefore_ :: UTCTime -> IO [ChatItemId] - getGroupChatItemIdsBefore_ beforeChatItemTs = - map fromOnly + getNavInfo cis_ = case cis_ of + [] -> pure $ NavigationInfo 0 0 + cis -> getGroupNavInfo_ db user g (last cis) + +getGroupChatInitial_ :: DB.Connection -> User -> GroupInfo -> Int -> ExceptT StoreError IO (Chat 'CTGroup, Maybe NavigationInfo) +getGroupChatInitial_ db user g count = + liftIO (getGroupMinUnreadId_ db user g) >>= \case + Just minUnreadItemId -> do + unreadCount <- liftIO $ getGroupUnreadCount_ db user g + let stats = ChatStats {unreadCount, minUnreadItemId, unreadChat = False} + getGroupChatAround' db user g minUnreadItemId count "" stats + Nothing -> liftIO $ (,Just $ NavigationInfo 0 0) <$> getGroupChatLast_ db user g count "" + +getGroupStats_ :: DB.Connection -> User -> GroupInfo -> IO ChatStats +getGroupStats_ db user g = do + minUnreadItemId <- fromMaybe 0 <$> getGroupMinUnreadId_ db user g + unreadCount <- getGroupUnreadCount_ db user g + pure ChatStats {unreadCount, minUnreadItemId, unreadChat = False} + +getGroupMinUnreadId_ :: DB.Connection -> User -> GroupInfo -> IO (Maybe ChatItemId) +getGroupMinUnreadId_ db User {userId} GroupInfo {groupId} = + fmap join . maybeFirstRow fromOnly $ + DB.query + db + [sql| + SELECT chat_item_id + FROM chat_items + WHERE user_id = ? AND group_id = ? AND item_status = ? + ORDER BY item_ts ASC, chat_item_id ASC + LIMIT 1 + |] + (userId, groupId, CISRcvNew) + +getGroupUnreadCount_ :: DB.Connection -> User -> GroupInfo -> IO Int +getGroupUnreadCount_ db User {userId} GroupInfo {groupId} = + fromOnly . head + <$> DB.query + db + [sql| + SELECT COUNT(1) + FROM chat_items + WHERE user_id = ? AND group_id = ? AND item_status = ? + |] + (userId, groupId, CISRcvNew) + +getGroupNavInfo_ :: DB.Connection -> User -> GroupInfo -> CChatItem 'CTGroup -> IO NavigationInfo +getGroupNavInfo_ db User {userId} GroupInfo {groupId} afterCI = do + afterUnread <- getAfterUnreadCount + afterTotal <- getAfterTotalCount + pure NavigationInfo {afterUnread, afterTotal} + where + getAfterUnreadCount :: IO Int + getAfterUnreadCount = + fromOnly . head <$> DB.query db [sql| - SELECT chat_item_id + SELECT COUNT(1) FROM chat_items - WHERE user_id = ? AND group_id = ? AND item_text LIKE '%' || ? || '%' - AND (item_ts < ? OR (item_ts = ? AND chat_item_id < ?)) - ORDER BY item_ts DESC, chat_item_id DESC - LIMIT ? + WHERE user_id = ? AND group_id = ? AND item_status = ? + AND (item_ts > ? OR (item_ts = ? AND chat_item_id > ?)) |] - (userId, groupId, search, beforeChatItemTs, beforeChatItemTs, beforeChatItemId, count) + (userId, groupId, CISRcvNew, chatItemTs afterCI, chatItemTs afterCI, cChatItemId afterCI) + getAfterTotalCount :: IO Int + getAfterTotalCount = + fromOnly . head + <$> DB.query + db + [sql| + SELECT COUNT(1) + FROM chat_items + WHERE user_id = ? AND group_id = ? + AND (item_ts > ? OR (item_ts = ? AND chat_item_id > ?)) + |] + (userId, groupId, chatItemTs afterCI, chatItemTs afterCI, cChatItemId afterCI) -getLocalChat :: DB.Connection -> User -> Int64 -> ChatPagination -> Maybe String -> ExceptT StoreError IO (Chat 'CTLocal) +getLocalChat :: DB.Connection -> User -> Int64 -> ChatPagination -> Maybe String -> ExceptT StoreError IO (Chat 'CTLocal, Maybe NavigationInfo) getLocalChat db user folderId pagination search_ = do let search = fromMaybe "" search_ nf <- getNoteFolder db user folderId - liftIO $ case pagination of - CPLast count -> getLocalChatLast_ db user nf count search - CPAfter afterId count -> getLocalChatAfter_ db user nf afterId count search - CPBefore beforeId count -> getLocalChatBefore_ db user nf beforeId count search + case pagination of + CPLast count -> liftIO $ (,Nothing) <$> getLocalChatLast_ db user nf count search + CPAfter afterId count -> (,Nothing) <$> getLocalChatAfter_ db user nf afterId count search + CPBefore beforeId count -> (,Nothing) <$> getLocalChatBefore_ db user nf beforeId count search + CPAround aroundId count -> getLocalChatAround_ db user nf aroundId count search + CPInitial count -> do + unless (null search) $ throwError $ SEInternalError "initial chat pagination doesn't support search" + getLocalChatInitial_ db user nf count getLocalChatLast_ :: DB.Connection -> User -> NoteFolder -> Int -> String -> IO (Chat 'CTLocal) -getLocalChatLast_ db user@User {userId} nf@NoteFolder {noteFolderId} count search = do +getLocalChatLast_ db user nf count search = do let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False} - chatItemIds <- getLocalChatItemIdsLast_ - currentTs <- getCurrentTime - chatItems <- mapM (safeGetLocalItem db user nf currentTs) chatItemIds - pure $ Chat (LocalChat nf) (reverse chatItems) stats - where - getLocalChatItemIdsLast_ :: IO [ChatItemId] - getLocalChatItemIdsLast_ = - map fromOnly - <$> DB.query - db - [sql| - SELECT chat_item_id - FROM chat_items - WHERE user_id = ? AND note_folder_id = ? AND item_text LIKE '%' || ? || '%' - ORDER BY created_at DESC, chat_item_id DESC - LIMIT ? - |] - (userId, noteFolderId, search, count) + ciIds <- getLocalChatItemIdsLast_ db user nf count search + ts <- getCurrentTime + cis <- mapM (safeGetLocalItem db user nf ts) ciIds + pure $ Chat (LocalChat nf) (reverse cis) stats + +getLocalChatItemIdsLast_ :: DB.Connection -> User -> NoteFolder -> Int -> String -> IO [ChatItemId] +getLocalChatItemIdsLast_ db User {userId} NoteFolder {noteFolderId} count search = + map fromOnly + <$> DB.query + db + [sql| + SELECT chat_item_id + FROM chat_items + WHERE user_id = ? AND note_folder_id = ? AND item_text LIKE '%' || ? || '%' + ORDER BY created_at DESC, chat_item_id DESC + LIMIT ? + |] + (userId, noteFolderId, search, count) safeGetLocalItem :: DB.Connection -> User -> NoteFolder -> UTCTime -> ChatItemId -> IO (CChatItem 'CTLocal) safeGetLocalItem db user NoteFolder {noteFolderId} currentTs itemId = @@ -1245,51 +1446,146 @@ safeToLocalItem currentTs itemId = \case file = Nothing } -getLocalChatAfter_ :: DB.Connection -> User -> NoteFolder -> ChatItemId -> Int -> String -> IO (Chat 'CTLocal) -getLocalChatAfter_ db user@User {userId} nf@NoteFolder {noteFolderId} afterChatItemId count search = do +getLocalChatAfter_ :: DB.Connection -> User -> NoteFolder -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTLocal) +getLocalChatAfter_ db user nf@NoteFolder {noteFolderId} afterId count search = do let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False} - chatItemIds <- getLocalChatItemIdsAfter_ - currentTs <- getCurrentTime - chatItems <- mapM (safeGetLocalItem db user nf currentTs) chatItemIds - pure $ Chat (LocalChat nf) chatItems stats - where - getLocalChatItemIdsAfter_ :: IO [ChatItemId] - getLocalChatItemIdsAfter_ = - map fromOnly - <$> DB.query - db - [sql| - SELECT chat_item_id - FROM chat_items - WHERE user_id = ? AND note_folder_id = ? AND item_text LIKE '%' || ? || '%' - AND chat_item_id > ? - ORDER BY created_at ASC, chat_item_id ASC - LIMIT ? - |] - (userId, noteFolderId, search, afterChatItemId, count) + afterCI <- getLocalChatItem db user noteFolderId afterId + ciIds <- liftIO $ getLocalCIsAfter_ db user nf afterCI count search + ts <- liftIO getCurrentTime + cis <- liftIO $ mapM (safeGetLocalItem db user nf ts) ciIds + pure $ Chat (LocalChat nf) cis stats -getLocalChatBefore_ :: DB.Connection -> User -> NoteFolder -> ChatItemId -> Int -> String -> IO (Chat 'CTLocal) -getLocalChatBefore_ db user@User {userId} nf@NoteFolder {noteFolderId} beforeChatItemId count search = do +getLocalCIsAfter_ :: DB.Connection -> User -> NoteFolder -> CChatItem 'CTLocal -> Int -> String -> IO [ChatItemId] +getLocalCIsAfter_ db User {userId} NoteFolder {noteFolderId} afterCI count search = + map fromOnly + <$> DB.query + db + [sql| + SELECT chat_item_id + FROM chat_items + WHERE user_id = ? AND note_folder_id = ? AND item_text LIKE '%' || ? || '%' + AND (created_at > ? OR (created_at = ? AND chat_item_id > ?)) + ORDER BY created_at ASC, chat_item_id ASC + LIMIT ? + |] + (userId, noteFolderId, search, ciCreatedAt afterCI, ciCreatedAt afterCI, cChatItemId afterCI, count) + +getLocalChatBefore_ :: DB.Connection -> User -> NoteFolder -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTLocal) +getLocalChatBefore_ db user nf@NoteFolder {noteFolderId} beforeId count search = do let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False} - chatItemIds <- getLocalChatItemIdsBefore_ - currentTs <- getCurrentTime - chatItems <- mapM (safeGetLocalItem db user nf currentTs) chatItemIds - pure $ Chat (LocalChat nf) (reverse chatItems) stats + beforeCI <- getLocalChatItem db user noteFolderId beforeId + ciIds <- liftIO $ getLocalCIsBefore_ db user nf beforeCI count search + ts <- liftIO getCurrentTime + cis <- liftIO $ mapM (safeGetLocalItem db user nf ts) ciIds + pure $ Chat (LocalChat nf) (reverse cis) stats + +getLocalCIsBefore_ :: DB.Connection -> User -> NoteFolder -> CChatItem 'CTLocal -> Int -> String -> IO [ChatItemId] +getLocalCIsBefore_ db User {userId} NoteFolder {noteFolderId} beforeCI count search = + map fromOnly + <$> DB.query + db + [sql| + SELECT chat_item_id + FROM chat_items + WHERE user_id = ? AND note_folder_id = ? AND item_text LIKE '%' || ? || '%' + AND (created_at < ? OR (created_at = ? AND chat_item_id < ?)) + ORDER BY created_at DESC, chat_item_id DESC + LIMIT ? + |] + (userId, noteFolderId, search, ciCreatedAt beforeCI, ciCreatedAt beforeCI, cChatItemId beforeCI, count) + +getLocalChatAround_ :: DB.Connection -> User -> NoteFolder -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTLocal, Maybe NavigationInfo) +getLocalChatAround_ db user nf aroundId count search = do + stats <- liftIO $ getLocalStats_ db user nf + getLocalChatAround' db user nf aroundId count search stats + +getLocalChatAround' :: DB.Connection -> User -> NoteFolder -> ChatItemId -> Int -> String -> ChatStats -> ExceptT StoreError IO (Chat 'CTLocal, Maybe NavigationInfo) +getLocalChatAround' db user nf@NoteFolder {noteFolderId} aroundId count search stats = do + aroundCI <- getLocalChatItem db user noteFolderId aroundId + beforeIds <- liftIO $ getLocalCIsBefore_ db user nf aroundCI count search + afterIds <- liftIO $ getLocalCIsAfter_ db user nf aroundCI count search + ts <- liftIO getCurrentTime + beforeCIs <- liftIO $ mapM (safeGetLocalItem db user nf ts) beforeIds + afterCIs <- liftIO $ mapM (safeGetLocalItem db user nf ts) afterIds + let cis = reverse beforeCIs <> [aroundCI] <> afterCIs + navInfo <- liftIO $ getNavInfo cis + pure (Chat (LocalChat nf) cis stats, Just navInfo) where - getLocalChatItemIdsBefore_ :: IO [ChatItemId] - getLocalChatItemIdsBefore_ = - map fromOnly + getNavInfo cis_ = case cis_ of + [] -> pure $ NavigationInfo 0 0 + cis -> getLocalNavInfo_ db user nf (last cis) + +getLocalChatInitial_ :: DB.Connection -> User -> NoteFolder -> Int -> ExceptT StoreError IO (Chat 'CTLocal, Maybe NavigationInfo) +getLocalChatInitial_ db user nf count = do + liftIO (getLocalMinUnreadId_ db user nf) >>= \case + Just minUnreadItemId -> do + unreadCount <- liftIO $ getLocalUnreadCount_ db user nf + let stats = ChatStats {unreadCount, minUnreadItemId, unreadChat = False} + getLocalChatAround' db user nf minUnreadItemId count "" stats + Nothing -> liftIO $ (,Just $ NavigationInfo 0 0) <$> getLocalChatLast_ db user nf count "" + +getLocalStats_ :: DB.Connection -> User -> NoteFolder -> IO ChatStats +getLocalStats_ db user nf = do + minUnreadItemId <- fromMaybe 0 <$> getLocalMinUnreadId_ db user nf + unreadCount <- getLocalUnreadCount_ db user nf + pure ChatStats {unreadCount, minUnreadItemId, unreadChat = False} + +getLocalMinUnreadId_ :: DB.Connection -> User -> NoteFolder -> IO (Maybe ChatItemId) +getLocalMinUnreadId_ db User {userId} NoteFolder {noteFolderId} = + fmap join . maybeFirstRow fromOnly $ + DB.query + db + [sql| + SELECT chat_item_id + FROM chat_items + WHERE user_id = ? AND note_folder_id = ? AND item_status = ? + ORDER BY created_at ASC, chat_item_id ASC + LIMIT 1 + |] + (userId, noteFolderId, CISRcvNew) + +getLocalUnreadCount_ :: DB.Connection -> User -> NoteFolder -> IO Int +getLocalUnreadCount_ db User {userId} NoteFolder {noteFolderId} = + fromOnly . head + <$> DB.query + db + [sql| + SELECT COUNT(1) + FROM chat_items + WHERE user_id = ? AND note_folder_id = ? AND item_status = ? + |] + (userId, noteFolderId, CISRcvNew) + +getLocalNavInfo_ :: DB.Connection -> User -> NoteFolder -> CChatItem 'CTLocal -> IO NavigationInfo +getLocalNavInfo_ db User {userId} NoteFolder {noteFolderId} afterCI = do + afterUnread <- getAfterUnreadCount + afterTotal <- getAfterTotalCount + pure NavigationInfo {afterUnread, afterTotal} + where + getAfterUnreadCount :: IO Int + getAfterUnreadCount = + fromOnly . head <$> DB.query db [sql| - SELECT chat_item_id + SELECT COUNT(1) FROM chat_items - WHERE user_id = ? AND note_folder_id = ? AND item_text LIKE '%' || ? || '%' - AND chat_item_id < ? - ORDER BY created_at DESC, chat_item_id DESC - LIMIT ? + WHERE user_id = ? AND note_folder_id = ? AND item_status = ? + AND (created_at > ? OR (created_at = ? AND chat_item_id > ?)) |] - (userId, noteFolderId, search, beforeChatItemId, count) + (userId, noteFolderId, CISRcvNew, ciCreatedAt afterCI, ciCreatedAt afterCI, cChatItemId afterCI) + getAfterTotalCount :: IO Int + getAfterTotalCount = + fromOnly . head + <$> DB.query + db + [sql| + SELECT COUNT(1) + FROM chat_items + WHERE user_id = ? AND note_folder_id = ? + AND (created_at > ? OR (created_at = ? AND chat_item_id > ?)) + |] + (userId, noteFolderId, ciCreatedAt afterCI, ciCreatedAt afterCI, cChatItemId afterCI) toChatItemRef :: (ChatItemId, Maybe Int64, Maybe Int64, Maybe Int64) -> Either StoreError (ChatRef, ChatItemId) toChatItemRef = \case @@ -1581,6 +1877,12 @@ getAllChatItems db vr user@User {userId} pagination search_ = do CPLast count -> liftIO $ getAllChatItemsLast_ count CPAfter afterId count -> liftIO . getAllChatItemsAfter_ afterId count . aChatItemTs =<< getAChatItem_ afterId CPBefore beforeId count -> liftIO . getAllChatItemsBefore_ beforeId count . aChatItemTs =<< getAChatItem_ beforeId + CPAround aroundId count -> liftIO . getAllChatItemsAround_ aroundId count . aChatItemTs =<< getAChatItem_ aroundId + CPInitial count -> do + unless (null search) $ throwError $ SEInternalError "initial chat pagination doesn't support search" + liftIO getFirstUnreadItemId_ >>= \case + Just itemId -> liftIO . getAllChatItemsAround_ itemId count . aChatItemTs =<< getAChatItem_ itemId + Nothing -> liftIO $ getAllChatItemsLast_ count mapM (uncurry (getAChatItem db vr user)) itemRefs where search = fromMaybe "" search_ @@ -1624,6 +1926,30 @@ getAllChatItems db vr user@User {userId} pagination search_ = do LIMIT ? |] (userId, search, beforeTs, beforeTs, beforeId, count) + getChatItem chatId = + DB.query + db + [sql| + SELECT chat_item_id, contact_id, group_id, note_folder_id + FROM chat_items + WHERE chat_item_id = ? + |] + (Only chatId) + getAllChatItemsAround_ aroundId count aroundTs = do + itemsBefore <- getAllChatItemsBefore_ aroundId count aroundTs + item <- getChatItem aroundId + itemsAfter <- getAllChatItemsAfter_ aroundId count aroundTs + pure $ itemsBefore <> item <> itemsAfter + getFirstUnreadItemId_ = + fmap join . maybeFirstRow fromOnly $ + DB.query + db + [sql| + SELECT MIN(chat_item_id) + FROM chat_items + WHERE user_id = ? AND item_status = ? + |] + (userId, CISRcvNew) getChatItemIdsByAgentMsgId :: DB.Connection -> Int64 -> AgentMsgId -> IO [ChatItemId] getChatItemIdsByAgentMsgId db connId msgId = @@ -2631,9 +2957,9 @@ getGroupSndStatusCounts db itemId = getGroupHistoryItems :: DB.Connection -> User -> GroupInfo -> Int -> IO [Either StoreError (CChatItem 'CTGroup)] getGroupHistoryItems db user@User {userId} GroupInfo {groupId} count = do - chatItemIds <- getLastItemIds_ + ciIds <- getLastItemIds_ -- use getGroupCIWithReactions to read reactions data - reverse <$> mapM (runExceptT . getGroupChatItem db user groupId) chatItemIds + reverse <$> mapM (runExceptT . getGroupChatItem db user groupId) ciIds where getLastItemIds_ :: IO [ChatItemId] getLastItemIds_ = diff --git a/src/Simplex/Chat/Store/Migrations.hs b/src/Simplex/Chat/Store/Migrations.hs index e2d12e78d7..2444078a33 100644 --- a/src/Simplex/Chat/Store/Migrations.hs +++ b/src/Simplex/Chat/Store/Migrations.hs @@ -114,6 +114,7 @@ import Simplex.Chat.Migrations.M20240827_calls_uuid import Simplex.Chat.Migrations.M20240920_user_order import Simplex.Chat.Migrations.M20241008_indexes import Simplex.Chat.Migrations.M20241010_contact_requests_contact_id +import Simplex.Chat.Migrations.M20241023_chat_item_autoincrement_id import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -227,7 +228,8 @@ schemaMigrations = ("20240827_calls_uuid", m20240827_calls_uuid, Just down_m20240827_calls_uuid), ("20240920_user_order", m20240920_user_order, Just down_m20240920_user_order), ("20241008_indexes", m20241008_indexes, Just down_m20241008_indexes), - ("20241010_contact_requests_contact_id", m20241010_contact_requests_contact_id, Just down_m20241010_contact_requests_contact_id) + ("20241010_contact_requests_contact_id", m20241010_contact_requests_contact_id, Just down_m20241010_contact_requests_contact_id), + ("20241023_chat_item_autoincrement_id", m20241023_chat_item_autoincrement_id, Just down_m20241023_chat_item_autoincrement_id) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index ade36476c7..8ae74e8961 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -93,7 +93,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRChatSuspended -> ["chat suspended"] CRApiChats u chats -> ttyUser u $ if testView then testViewChats chats else [viewJSON chats] CRChats chats -> viewChats ts tz chats - CRApiChat u chat -> ttyUser u $ if testView then testViewChat chat else [viewJSON chat] + CRApiChat u chat _ -> ttyUser u $ if testView then testViewChat chat else [viewJSON chat] CRApiParsedMarkdown ft -> [viewJSON ft] CRUserProtoServers u userServers -> ttyUser u $ viewUserServers userServers testView CRServerTestResult u srv testFailure -> ttyUser u $ viewServerTestResult srv testFailure diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index 8971e8d22d..8756657e59 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -66,6 +66,7 @@ chatDirectTests = do it "repeat AUTH errors disable contact" testRepeatAuthErrorsDisableContact it "should send multiline message" testMultilineMessage it "send large message" testLargeMessage + it "initial chat pagination" testChatPaginationInitial describe "batch send messages" $ do it "send multiple messages api" testSendMulti it "send multiple timed messages" testSendMultiTimed @@ -123,7 +124,7 @@ chatDirectTests = do it "chat items only expire for users who configured expiration" testEnableCIExpirationOnlyForOneUser it "disabling chat item expiration doesn't disable it for other users" testDisableCIExpirationOnlyForOneUser it "both users have configured timed messages with contacts, messages expire, restart" testUsersTimedMessages - it "user profile privacy: hide profiles and notificaitons" testUserPrivacy + it "user profile privacy: hide profiles and notifications" testUserPrivacy describe "settings" $ do it "set chat item expiration TTL" testSetChatItemTTL it "save/get app settings" testAppSettings @@ -210,6 +211,7 @@ testAddContact = versionTestMatrix2 runTestAddContact -- pagination alice #$> ("/_get chat @2 after=" <> itemId 1 <> " count=100", chat, [(0, "hello there"), (0, "how are you?")]) alice #$> ("/_get chat @2 before=" <> itemId 2 <> " count=100", chat, features <> [(1, "hello there 🙂")]) + alice #$> ("/_get chat @2 around=" <> itemId 2 <> " count=2", chat, [(0, "Audio/video calls: enabled"), (1, "hello there 🙂"), (0, "hello there"), (0, "how are you?")]) -- search alice #$> ("/_get chat @2 count=100 search=ello ther", chat, [(1, "hello there 🙂"), (0, "hello there")]) -- read messages @@ -360,6 +362,36 @@ testMarkReadDirect = testChat2 aliceProfile bobProfile $ \alice bob -> do let itemIds = intercalate "," $ map show [i - 3 .. i] bob #$> ("/_read chat items @2 " <> itemIds, id, "ok") +testChatPaginationInitial :: HasCallStack => FilePath -> IO () +testChatPaginationInitial = testChatOpts2 opts aliceProfile bobProfile $ \alice bob -> do + connectUsers alice bob + -- Wait, otherwise ids are going to be wrong. + threadDelay 1000000 + + -- Send messages from alice to bob + forM_ ([1 .. 10] :: [Int]) $ \n -> alice #> ("@bob " <> show n) + + -- Bob receives the messages. + forM_ ([1 .. 10] :: [Int]) $ \n -> bob <# ("alice> " <> show n) + + -- All messages are unread for bob, should return area around unread + bob #$> ("/_get chat @2 initial=2", chat, [(0, "Voice messages: enabled"), (0, "Audio/video calls: enabled"), (0, "1"), (0, "2"), (0, "3")]) + + -- Read next 2 items + let itemIds = intercalate "," $ map itemId [1 .. 2] + bob #$> ("/_read chat items @2 " <> itemIds, id, "ok") + bob #$> ("/_get chat @2 initial=2", chat, [(0, "1"), (0, "2"), (0, "3"), (0, "4"), (0, "5")]) + + -- Read all items + bob #$> ("/_read chat @2", id, "ok") + bob #$> ("/_get chat @2 initial=3", chat, [(0, "8"), (0, "9"), (0, "10")]) + bob #$> ("/_get chat @2 initial=5", chat, [(0, "6"), (0, "7"), (0, "8"), (0, "9"), (0, "10")]) + where + opts = + testOpts + { markRead = False + } + testDuplicateContactsSeparate :: HasCallStack => FilePath -> IO () testDuplicateContactsSeparate = testChat2 aliceProfile bobProfile $ @@ -791,7 +823,7 @@ testDirectMessageDelete = alice @@@ [("@bob", lastChatFeature)] alice #$> ("/_get chat @2 count=100", chat, chatFeatures) - -- alice: msg id 1 + -- alice: msg id 3 bob ##> ("/_update item @2 " <> itemId 2 <> " text hey alice") bob <# "@alice [edited] > hello 🙂" bob <## " hey alice" @@ -806,12 +838,12 @@ testDirectMessageDelete = alice @@@ [("@bob", "hey alice [marked deleted]")] alice #$> ("/_get chat @2 count=100", chat, chatFeatures <> [(0, "hey alice [marked deleted]")]) - -- alice: deletes msg id 1 that was broadcast deleted by bob - alice #$> ("/_delete item @2 " <> itemId 1 <> " internal", id, "message deleted") + -- alice: deletes msg id 3 that was broadcast deleted by bob + alice #$> ("/_delete item @2 " <> itemId 3 <> " internal", id, "message deleted") alice @@@ [("@bob", lastChatFeature)] alice #$> ("/_get chat @2 count=100", chat, chatFeatures) - -- alice: msg id 1, bob: msg id 3 (quoting message alice deleted locally) + -- alice: msg id 4, bob: msg id 3 (quoting message alice deleted locally) bob `send` "> @alice (hello 🙂) do you receive my messages?" bob <# "@alice > hello 🙂" bob <## " do you receive my messages?" @@ -819,14 +851,14 @@ testDirectMessageDelete = alice <## " do you receive my messages?" alice @@@ [("@bob", "do you receive my messages?")] alice #$> ("/_get chat @2 count=100", chat', chatFeatures' <> [((0, "do you receive my messages?"), Just (1, "hello 🙂"))]) - alice #$> ("/_delete item @2 " <> itemId 1 <> " broadcast", id, "cannot delete this item") + alice #$> ("/_delete item @2 " <> itemId 4 <> " broadcast", id, "cannot delete this item") - -- alice: msg id 2, bob: msg id 4 + -- alice: msg id 5, bob: msg id 4 bob #> "@alice how are you?" alice <# "bob> how are you?" - -- alice: deletes msg id 2 - alice #$> ("/_delete item @2 " <> itemId 2 <> " internal", id, "message deleted") + -- alice: deletes msg id 5 + alice #$> ("/_delete item @2 " <> itemId 5 <> " internal", id, "message deleted") -- bob: marks deleted msg id 4 (that alice deleted locally) bob #$> ("/_delete item @2 " <> itemId 4 <> " broadcast", id, "message marked deleted") @@ -2340,6 +2372,14 @@ testUserPrivacy = "bob> Voice messages: enabled", "bob> Audio/video calls: enabled" ] + alice ##> "/_get items around=11 count=2" + alice + <##? [ "bob> Full deletion: off", + "bob> Message reactions: enabled", + "bob> Voice messages: enabled", + "bob> Audio/video calls: enabled", + "@bob hello" + ] alice ##> "/_get items after=12 count=10" alice <##? [ "@bob hello", diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index f1a36c8722..a7de42128c 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -36,6 +36,7 @@ chatGroupTests = do describe "chat groups" $ do describe "add contacts, create group and send/receive messages" testGroupMatrix it "mark multiple messages as read" testMarkReadGroup + it "initial chat pagination" testChatPaginationInitial it "v1: add contacts, create group and send/receive messages" testGroup it "v1: add contacts, create group and send/receive messages, check messages" testGroupCheckMessages it "send large message" testGroupLargeMessage @@ -344,6 +345,7 @@ testGroupShared alice bob cath checkMessages directConnections = do -- so we take into account group event items as well as sent group invitations in direct chats alice #$> ("/_get chat #1 after=" <> msgItem1 <> " count=100", chat, [(0, "hi there"), (0, "hey team")]) alice #$> ("/_get chat #1 before=" <> msgItem2 <> " count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (0, "connected"), (1, "hello"), (0, "hi there")]) + alice #$> ("/_get chat #1 around=" <> msgItem1 <> " count=2", chat, [(0, "connected"), (0, "connected"), (1, "hello"), (0, "hi there"), (0, "hey team")]) alice #$> ("/_get chat #1 count=100 search=team", chat, [(0, "hey team")]) bob @@@ [("@cath", "hey"), ("#team", "hey team"), ("@alice", "received invitation to join group team as admin")] bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "added cath (Catherine)"), (0, "connected"), (0, "hello"), (1, "hi there"), (0, "hey team")]) @@ -374,6 +376,38 @@ testMarkReadGroup = testChat2 aliceProfile bobProfile $ \alice bob -> do let itemIds = intercalate "," $ map show [i - 3 .. i] bob #$> ("/_read chat items #1 " <> itemIds, id, "ok") +testChatPaginationInitial :: HasCallStack => FilePath -> IO () +testChatPaginationInitial = testChatOpts2 opts aliceProfile bobProfile $ \alice bob -> do + createGroup2 "team" alice bob + -- Wait, otherwise ids are going to be wrong. + threadDelay 1000000 + lastEventId <- (read :: String -> Int) <$> lastItemId bob + let groupItemId n = show $ lastEventId + n + + -- Send messages from alice to bob + forM_ ([1 .. 10] :: [Int]) $ \n -> alice #> ("#team " <> show n) + + -- Bob receives the messages. + forM_ ([1 .. 10] :: [Int]) $ \n -> bob <# ("#team alice> " <> show n) + + -- All messages are unread for bob, should return area around unread + bob #$> ("/_get chat #1 initial=2", chat, [(0, "Recent history: on"), (0, "connected"), (0, "1"), (0, "2"), (0, "3")]) + + -- Read next 2 items + let itemIds = intercalate "," $ map groupItemId [1 .. 2] + bob #$> ("/_read chat items #1 " <> itemIds, id, "ok") + bob #$> ("/_get chat #1 initial=2", chat, [(0, "1"), (0, "2"), (0, "3"), (0, "4"), (0, "5")]) + + -- Read all items + bob #$> ("/_read chat #1", id, "ok") + bob #$> ("/_get chat #1 initial=3", chat, [(0, "8"), (0, "9"), (0, "10")]) + bob #$> ("/_get chat #1 initial=5", chat, [(0, "6"), (0, "7"), (0, "8"), (0, "9"), (0, "10")]) + where + opts = + testOpts + { markRead = False + } + testGroupLargeMessage :: HasCallStack => FilePath -> IO () testGroupLargeMessage = testChat2 aliceProfile bobProfile $ diff --git a/tests/ChatTests/Local.hs b/tests/ChatTests/Local.hs index da9c043648..40df02252d 100644 --- a/tests/ChatTests/Local.hs +++ b/tests/ChatTests/Local.hs @@ -51,7 +51,7 @@ testNotes tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do alice ##> "/chats" alice /* "ahoy!" - alice ##> "/_update item *1 1 text Greetings." + alice ##> "/_update item *1 2 text Greetings." alice ##> "/tail *" alice <# "* Greetings." @@ -102,6 +102,10 @@ testChatPagination tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do alice #$> ("/_get chat *1 count=100", chat, [(1, "hello world"), (1, "memento mori"), (1, "knock-knock"), (1, "who's there?")]) alice #$> ("/_get chat *1 count=1", chat, [(1, "who's there?")]) + alice #$> ("/_get chat *1 around=2 count=1", chat, [(1, "hello world"), (1, "memento mori"), (1, "knock-knock")]) + alice #$> ("/_get chat *1 around=2 count=3", chat, [(1, "hello world"), (1, "memento mori"), (1, "knock-knock"), (1, "who's there?")]) + alice #$> ("/_get chat *1 around=3 count=10", chat, [(1, "hello world"), (1, "memento mori"), (1, "knock-knock"), (1, "who's there?")]) + alice #$> ("/_get chat *1 around=4 count=1", chat, [(1, "knock-knock"), (1, "who's there?")]) alice #$> ("/_get chat *1 after=2 count=10", chat, [(1, "knock-knock"), (1, "who's there?")]) alice #$> ("/_get chat *1 after=2 count=2", chat, [(1, "knock-knock"), (1, "who's there?")]) alice #$> ("/_get chat *1 after=1 count=2", chat, [(1, "memento mori"), (1, "knock-knock")]) diff --git a/tests/SchemaDump.hs b/tests/SchemaDump.hs index 23f36713b4..4e63a31001 100644 --- a/tests/SchemaDump.hs +++ b/tests/SchemaDump.hs @@ -102,7 +102,9 @@ skipComparisonForDownMigrations = -- table and indexes move down to the end of the file "20231215_recreate_msg_deliveries", -- on down migration idx_msg_deliveries_agent_ack_cmd_id index moves down to the end of the file - "20240313_drop_agent_ack_cmd_id" + "20240313_drop_agent_ack_cmd_id", + -- on down migration chat_item_autoincrement_id makes sequence table creation move down on the file + "20241023_chat_item_autoincrement_id" ] getSchema :: FilePath -> FilePath -> IO String From a5061f3147165a05979d6ace33960aced2d6ac03 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 14 Nov 2024 11:59:44 +0000 Subject: [PATCH 069/567] docs: update privacy policy and conditions of use (#5129) * docs: update privacy policy and conditions of use * update * note * update date --- PRIVACY.md | 164 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 106 insertions(+), 58 deletions(-) diff --git a/PRIVACY.md b/PRIVACY.md index 669a0bf4be..7c4bfbf660 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -3,27 +3,49 @@ layout: layouts/privacy.html permalink: /privacy/index.html --- -# SimpleX Chat Privacy Policy and Conditions of Use +# SimpleX Chat Operators Privacy Policy and Conditions of Use -SimpleX Chat is the first communication network based on a new protocol stack that builds on the same ideas of complete openness and decentralization as email and web, with the focus on providing security and privacy of communications, and without compromising on usability. +## Summary -SimpleX Chat communication protocol is the first protocol that has no user profile IDs of any kind, not even random numbers, cryptographic keys or hashes that identify the users. SimpleX Chat apps allow their users to send messages and files via relay server infrastructure. Relay server owners and providers do not have any access to your messages, thanks to double-ratchet end-to-end encryption algorithm (also known as Signal algorithm - do not confuse with Signal protocols or platform) and additional encryption layers, and they also have no access to your profile and contacts - as they do not provide any user accounts. +[Introduction](#introduction) and [General principles](#general-principles) cover SimpleX Chat network design, the network operators, and the principles of privacy and security provided by SimpleX network. + +[Privacy policy](#privacy-policy) covers: +- data stored only on your device - [your profiles](#user-profiles), delivered [messages and files](#messages-and-files). You can transfer this information to another device, and you are responsible for its preservation - if you delete the app it will be lost. +- [private message delivery](#private-message-delivery) that protects your IP address and connection graph from the destination servers. +- [undelivered messages and files](#storage-of-messages-and-files-on-the-servers) stored on the servers. +- [how users connect](#connections-with-other-users) without any user profile identifiers. +- [iOS push notifications](#ios-push-notifications) privacy limitations. +- [user support](#user-support), [SimpleX directory](#simplex-directory) and [any other data](#another-information-stored-on-the-servers) that may be stored on the servers. +- [preset server operators](#preset-server-operators) and the [information they may share](#information-preset-server-operators-may-share). +- [source code license](#source-code-license) and [updates to this document](#updates). + +[Conditions of Use](#conditions-of-use-of-software-and-infrastructure) are the conditions you need to accept to use SimpleX Chat applications and the relay servers of preset operators. Their purpose is to protect the users and preset server operators. + +*Please note*: this summary and any links in this document are provided for information only - they are not a part of the Privacy Policy and Conditions of Use. + +## Introduction + +SimpleX Chat (also referred to as SimpleX) is the first communication network based on a new protocol stack that builds on the same ideas of complete openness and decentralization as email and web, with the focus on providing security and privacy of communications, and without compromising on usability. + +SimpleX messaging protocol is the first protocol that has no user profile IDs of any kind, not even random numbers, cryptographic keys or hashes that identify the users. SimpleX apps allow their users to send messages and files via relay server infrastructure. Relay server owners and operators do not have any access to your messages, thanks to double-ratchet end-to-end encryption algorithm (also known as Signal algorithm - do not confuse with Signal protocols or platform) and additional encryption layers, and they also have no access to your profile and contacts - as they do not host user accounts. Double ratchet algorithm has such important properties as [forward secrecy](/docs/GLOSSARY.md#forward-secrecy), sender [repudiation](/docs/GLOSSARY.md#) and break-in recovery (also known as [post-compromise security](/docs/GLOSSARY.md#post-compromise-security)). -If you believe that any part of this document is not aligned with our mission or values, please raise it with us via [email](mailto:chat@simplex.chat) or [chat](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion). +If you believe that any part of this document is not aligned with SimpleX network mission or values, please raise it via [email](mailto:chat@simplex.chat) or [chat](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion). ## Privacy Policy -SimpleX Chat Ltd uses the best industry practices for security and encryption to provide client and server software for secure [end-to-end encrypted](/docs/GLOSSARY.md#end-to-end-encryption) messaging via private connections. This encryption cannot be compromised by the relays servers, even if they are modified or compromised, via [man-in-the-middle attack](/docs/GLOSSARY.md#man-in-the-middle-attack), unlike most other communication platforms, services and networks. +### General principles -SimpleX Chat software is built on top of SimpleX messaging and application protocols, based on a new message routing protocol allowing to establish private connections without having any kind of addresses or other identifiers assigned to its users - it does not use emails, phone numbers, usernames, identity keys or any other user profile identifiers to pass messages between the user applications. +SimpleX network software uses the best industry practices for security and encryption to provide client and server software for secure [end-to-end encrypted](/docs/GLOSSARY.md#end-to-end-encryption) messaging via private connections. This encryption is protected from being compromised by the relays servers, even if they are modified or compromised, via [man-in-the-middle attack](/docs/GLOSSARY.md#man-in-the-middle-attack). -SimpleX Chat software is similar in its design approach to email clients and browsers - it allows you to have full control of your data and freely choose the relay server providers, in the same way you choose which website or email provider to use, or use your own relay servers, simply by changing the configuration of the client software. The only current restriction to that is Apple push notifications - at the moment they can only be delivered via the preset servers that we operate, as explained below. We are exploring the solutions to deliver push notifications to iOS devices via other providers or users' own servers. +SimpleX software is built on top of SimpleX messaging and application protocols, based on a new message routing protocol allowing to establish private connections without having identifiers assigned to its users - it does not use emails, phone numbers, usernames, identity keys or any other user profile identifiers to pass messages between the user applications. -While SimpleX Chat Ltd is not a communication service provider, and provide public preset relays "as is", as experimental, without any guarantees of availability or data retention, we are committed to maintain a high level of availability, reliability and security of these preset relays. We will be adding alternative preset infrastructure providers to the software in the future, and you will continue to be able to use any other providers or your own servers. +SimpleX software is similar in its design approach to email clients and browsers - it allows you to have full control of your data and freely choose the relay server operators, in the same way you choose which website or email provider to use, or use your own relay servers, simply by changing the configuration of the client software. The only current restriction to that is Apple push notifications - at the moment they can only be delivered via the servers operated by SimpleX Chat Ltd, as explained below. We are exploring the solutions to deliver push notifications to iOS devices via other providers or users' own servers. -We see users and data sovereignty, and device and provider portability as critically important properties for any communication system. +SimpleX network operators are not communication service provider, and provide public relays "as is", as experimental, without any guarantees of availability or data retention. The operators of the relay servers preset in the app ("Preset Server Operators"), including SimpleX Chat Ltd, are committed to maintain a high level of availability, reliability and security. SimpleX client apps can have multiple preset relay server operators that you can opt-in or opt-out of using. You are and will continue to be able to use any other operators or your own servers. + +SimpleX network design is based on the principles of users and data sovereignty, and device and operator portability. The implementation security assessment of SimpleX cryptography and networking was done in October 2022 by [Trail of Bits](https://www.trailofbits.com/about), and most fixes were released in v4.2 – see [the announcement](/blog/20221108-simplex-chat-v4.2-security-audit-new-website.md). @@ -33,35 +55,41 @@ The cryptographic review of SimpleX protocols design was done in July 2024 by Tr #### User profiles -Servers used by SimpleX Chat apps do not create, store or identify user profiles. The profiles you can create in the app are local to your device, and can be removed at any time via the app. +Servers used by SimpleX Chat apps do not create, store or identify user chat profiles. The profiles you can create in the app are local to your device, and can be removed at any time via the app. -When you create the local profile, no records are created on any of the relay servers, and infrastructure providers, whether SimpleX Chat Ltd or any other, have no access to any part of your information, and even to the fact that you created a profile - it is a local record stored only on your device. That means that if you delete the app, and have no backup, you will permanently lose all your data and the private connections you created with other software users. +When you create the local profile, no records are created on any of the relay servers, and infrastructure operators, whether preset in the app or any other, have no access to any part of your information, and even to the fact that you created a profile - it is a local record stored only on your device. That means that if you delete the app, and have no backup, you will permanently lose all your data and the private connections you created with other software users. You can transfer the profile to another device by creating a backup of the app data and restoring it on the new device, but you cannot use more than one device with the copy of the same profile at the same time - it will disrupt any active conversations on either or both devices, as a security property of end-to-end encryption. #### Messages and Files -SimpleX relay servers cannot decrypt or otherwise access the content or even the size of your messages and files you send or receive. Each message is padded to a fixed size of 16kb. Each file is sent in chunks of 64kb, 256kb, 1mb or 8mb via all or some of the configured file relay servers. Both messages and files are sent end-to-end encrypted, and the servers do not have technical means to compromise this encryption, because part of the [key exchange](/docs/GLOSSARY.md#key-exchange) happens out-of-band. +SimpleX relay servers cannot decrypt or otherwise access the content or even the size of your messages and files you send or receive. Each message is padded to a fixed size of 16kb. Each file is sent in chunks of 64kb, 256kb, 1mb or 4mb via all or some of the configured file relay servers. Both messages and files are sent end-to-end encrypted, and the servers do not have technical means to compromise this encryption, because part of the [key exchange](/docs/GLOSSARY.md#key-exchange) happens out-of-band. Your message history is stored only on your own device and the devices of your contacts. While the recipients' devices are offline, messaging relay servers temporarily store end-to-end encrypted messages – you can configure which relay servers are used to receive the messages from the new contacts, and you can manually change them for the existing contacts too. -You do not have control over which servers are used to send messages to your contacts - they are chosen by them. To send messages your client needs to connect to these servers, therefore the servers chosen by your contacts can observe your IP address. You can use VPN or some overlay network (e.g., Tor) to hide your IP address from the servers chosen by your contacts. In the near future we will add the layer in the messaging protocol that will route sent message via the relays chosen by you as well. +#### Private message delivery -The messages are permanently removed from the used relay servers as soon as they are delivered, as long as these servers used unmodified published code. Undelivered messages are deleted after the time that is configured in the messaging servers you use (21 days for preset messaging servers). +You do not have control over which servers are used to send messages to your contacts - these servers are chosen by your contacts. To send messages your client by default uses configured servers to forward messages to the destination servers, thus protecting your IP address from the servers chosen by your contacts. + +In case you use preset servers of more than one operator, the app will prefer to use a server of an operator different from the operator of the destination server to forward messages, preventing destination server to correlate messages as belonging to one client. + +You can additionally use VPN or some overlay network (e.g., Tor) to hide your IP address from the servers chosen by you. + +*Please note*: the clients allow changing configuration to connect to the destination servers directly. It is not recommended - if you make such change, your IP address will be visible to the destination servers. + +#### Storage of messages and files on the servers + +The messages are removed from the relay servers as soon as all messages of the file they were stored in are delivered and saving new messages switches to another file, as long as these servers use unmodified published code. Undelivered messages are also marked as delivered after the time that is configured in the messaging servers you use (21 days for preset messaging servers). The files are stored on file relay servers for the time configured in the relay servers you use (48 hours for preset file servers). -If a messaging servers are restarted, the encrypted message can be stored in a backup file until it is overwritten by the next restart (usually within 1 week for preset relay servers). - -As this software is fully open-source and provided under AGPLv3 license, all infrastructure providers and owners, and the developers of the client and server applications who use the SimpleX Chat source code, are required to publish any changes to this software under the same AGPLv3 license - including any modifications to the provided servers. - -In addition to the AGPLv3 license terms, SimpleX Chat Ltd is committed to the software users that the preset relays that we provide via the apps will always be compiled from the [published open-source code](https://github.com/simplex-chat/simplexmq), without any modifications. +The encrypted messages can be stored for some time after they are delivered or expired (because servers use append-only logs for message storage). This time varies, and may be longer in connections with fewer messages, but it is usually limited to 1 month, including any backup storage. #### Connections with other users -When you create a connection with another user, two messaging queues (you can think about them as mailboxes) are created on messaging relay servers (chosen by you and your contact each), that can be the preset servers or the servers that you and your contact configured in the app. SimpleX messaging protocol uses separate queues for direct and response messages, and the apps prefer to create these queues on two different relay servers for increased privacy, in case you have more than one relay server configured in the app, which is the default. +When you create a connection with another user, two messaging queues (you can think about them as mailboxes) are created on messaging relay servers (chosen by you and your contact each), that can be the preset servers or the servers that you and your contact configured in the app. SimpleX messaging protocol uses separate queues for direct and response messages, and the apps prefer to create these queues on two different relay servers, or, if available, the relays of two different operators, for increased privacy, in case you have more than one relay server configured in the app, which is the default. -SimpleX relay servers do not store information about which queues are linked to your profile on the device, and they do not collect any information that would allow infrastructure owners and providers to establish that these queues are related to your device or your profile - the access to each queue is authorized by two anonymous unique cryptographic keys, different for each queue, and separate for sender and recipient of the messages. +Preset and unmodified SimpleX relay servers do not store information about which queues are linked to your profile on the device, and they do not collect any information that would allow infrastructure owners and operators to establish that these queues are related to your device or your profile - the access to each queue is authorized by two anonymous unique cryptographic keys, different for each queue, and separate for sender and recipient of the messages. #### Connection links privacy @@ -77,6 +105,8 @@ You can always safely replace the initial part of the link `https://simplex.chat #### iOS Push Notifications +This section applies only to the notification servers operated by SimpleX Chat Ltd. + When you choose to use instant push notifications in SimpleX iOS app, because the design of push notifications requires storing the device token on notification server, the notifications server can observe how many messaging queues your device has notifications enabled for, and approximately how many messages are sent to each queue. Preset notification server cannot observe the actual addresses of these queues, as a separate address is used to subscribe to the notifications. It also cannot observe who sends messages to you. Apple push notifications servers can only observe how many notifications are sent to you, but not from how many contacts, or from which messaging relays, as notifications are delivered to your device end-to-end encrypted by one of the preset notification servers - these notifications only contain end-to-end encrypted metadata, not even encrypted message content, and they look completely random to Apple push notification servers. @@ -85,93 +115,111 @@ You can read more about the design of iOS push notifications [here](./blog/20220 #### Another information stored on the servers -Additional technical information can be stored on our servers, including randomly generated authentication tokens, keys, push tokens, and other material that is necessary to transmit messages. SimpleX Chat design limits this additional technical information to the minimum required to operate the software and servers. To prevent server overloading or attacks, the servers can temporarily store data that can link to particular users or devices, including IP addresses, geographic location, or information related to the transport sessions. This information is not stored for the absolute majority of the app users, even for those who use the servers very actively. +Additional technical information can be stored on the network servers, including randomly generated authentication tokens, keys, push tokens, and other material that is necessary to transmit messages. SimpleX network design limits this additional technical information to the minimum required to operate the software and servers. To prevent server overloading or attacks, the servers can temporarily store data that can link to particular users or devices, including IP addresses, geographic location, or information related to the transport sessions. This information is not stored for the absolute majority of the app users, even for those who use the servers very actively. #### SimpleX Directory +This section applies only to the experimental group directory operated by SimpleX Chat Ltd. + [SimpleX Directory](/docs/DIRECTORY.md) stores: your search requests, the messages and the members profiles in the registered groups. You can connect to SimpleX Directory via [this address](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion). #### User Support -If you contact SimpleX Chat Ltd, any personal data you share with us is kept only for the purposes of researching the issue and contacting you about your case. We recommend contacting support [via chat](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion) when it is possible, and avoid sharing any personal information. +The app includes support contact operated by SimpleX Chat Ltd. If you contact support, any personal data you share is kept only for the purposes of researching the issue and contacting you about your case. We recommend contacting support [via chat](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion) when it is possible, and avoid sharing any personal information. -### Information we may share +### Preset Server Operators -SimpleX Chat Ltd operates preset relay servers using third parties. While we do not have access and cannot share any user data, these third parties may access the encrypted user messages (but NOT the actual unencrypted message content or size) as it is stored or transmitted via our servers. Hosting providers can also store IP addresses and other transport information as part of their logs. +Preset server operators will not share the information on their servers with each other, other than aggregate usage statistics. -We use a third party for email services - if you ask for support via email, your and SimpleX Chat Ltd email providers may access these emails according to their privacy policies and terms. When the request is sensitive, we recommend contacting us via SimpleX Chat or using encrypted email using PGP key published at [openpgp.org](https://keys.openpgp.org/search?q=chat%40simplex.chat). +Preset server operators will not provide general access to their servers or the data on their servers to each other. -The cases when SimpleX Chat Ltd may share the data temporarily stored on the servers: +Preset server operators will provide non-administrative access to control port of preset servers to SimpleX Chat Ltd, for the purposes of removing identified illegal content. This control port access only allows deleting known links and files, and access to aggregate statistics, but does NOT allow enumerating any information on the servers. + +### Information Preset Server Operators May Share + +The preset server operators use third parties. While they do not have access and cannot share any user data, these third parties may access the encrypted user messages (but NOT the actual unencrypted message content or size) as it is stored or transmitted via the servers. Hosting and network providers can also store IP addresses and other transport information as part of their logs. + +SimpleX Chat Ltd uses a third party for email services - if you ask for support via email, your and SimpleX Chat Ltd email providers may access these emails according to their privacy policies and terms. When the request is sensitive, please contact us via SimpleX Chat apps or using encrypted email using PGP key published at [openpgp.org](https://keys.openpgp.org/search?q=chat%40simplex.chat). + +The cases when the preset server operators may share the data temporarily stored on the servers: - To meet any applicable law, or enforceable governmental request or court order. - To enforce applicable terms, including investigation of potential violations. - To detect, prevent, or otherwise address fraud, security, or technical issues. -- To protect against harm to the rights, property, or safety of software users, SimpleX Chat Ltd, or the public as required or permitted by law. +- To protect against harm to the rights, property, or safety of software users, operators of preset servers, or the public as required or permitted by law. -At the time of updating this document, we have never provided or have been requested the access to the preset relay servers or any information from the servers by any third parties. If we are ever requested to provide such access or information, we will follow the due legal process to limit any information shared with the third parties to the minimally required by law. +At the time of updating this document, the preset server operators have never provided or have been requested the access to the preset relay servers or any information from the servers by any third parties. If the preset server operators are ever requested to provide such access or information, they will follow the due legal process to limit any information shared with the third parties to the minimally required by law. -We will publish information we are legally allowed to share about such requests in the [Transparency reports](./docs/TRANSPARENCY.md). +Preset server operators will publish information they are legally allowed to share about such requests in the [Transparency reports](./docs/TRANSPARENCY.md). + +### Source code license + +As this software is fully open-source and provided under AGPLv3 license, all infrastructure owners and operators, and the developers of the client and server applications who use the SimpleX Chat source code, are required to publish any changes to this software under the same AGPLv3 license - including any modifications to the servers. + +In addition to the AGPLv3 license terms, the preset relay server operators are committed to the software users that these servers will always be compiled from the [published open-source code](https://github.com/simplex-chat/simplexmq), without any modifications. ### Updates -We will update this Privacy Policy as needed so that it is current, accurate, and as clear as possible. Your continued use of our software applications and preset relays infrastructure confirms your acceptance of our updated Privacy Policy. +This Privacy Policy applies to SimpleX Chat Ltd and all other preset server operators you use in the app. -Please also read our Conditions of Use of Software and Infrastructure below. +This Privacy Policy may be updated as needed so that it is current, accurate, and as clear as possible. When it is updated, you will have to review and accept the changed policy within 30 days of such changes to continue using preset relay servers. Even if you fail to accept the changed policy, your continued use of SimpleX Chat software applications and preset relay servers confirms your acceptance of the updated Privacy Policy. -If you have questions about our Privacy Policy please contact us via [email](mailto:chat@simplex.chat) or [chat](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion). +Please also read The Conditions of Use of Software and Infrastructure below. + +If you have questions about this Privacy Policy please contact SimpleX Chat Ltd via [email](mailto:chat@simplex.chat) or [chat](https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23%2F%3Fv%3D1%26dh%3DMCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion). ## Conditions of Use of Software and Infrastructure -You accept the Conditions of Use of Software and Infrastructure ("Conditions") by installing or using any of our software or using any of our server infrastructure (collectively referred to as "Applications"), whether preset in the software or not. +You accept the Conditions of Use of Software and Infrastructure ("Conditions") by installing or using any of SimpleX Chat software or using any of server infrastructure (collectively referred to as "Applications") operated by the Preset Server Operators, including SimpleX Chat Ltd, whether these servers are preset in the software or not. -**Minimal age**. You must be at least 13 years old to use our Applications. The minimum age to use our Applications without parental approval may be higher in your country. +**Minimal age**. You must be at least 13 years old to use SimpleX Chat Applications. The minimum age to use SimpleX Applications without parental approval may be higher in your country. -**Infrastructure**. Our Infrastructure includes preset messaging and file relay servers, and iOS push notification servers provided by SimpleX Chat Ltd for public use. Our infrastructure does not have any modifications from the [published open-source code](https://github.com/simplex-chat/simplexmq) available under AGPLv3 license. Any infrastructure provider, whether commercial or not, is required by the Affero clause (named after Affero Inc. company that pioneered the community-based Q&A sites in early 2000s) to publish any modifications under the same license. The statements in relation to Infrastructure and relay servers anywhere in this document assume no modifications to the published code, even in the cases when it is not explicitly stated. +**Infrastructure**. Infrastructure of the preset server operators includes messaging and file relay servers. SimpleX Chat Ltd also provides iOS push notification servers for public use. This infrastructure does not have any modifications from the [published open-source code](https://github.com/simplex-chat/simplexmq) available under AGPLv3 license. Any infrastructure provider, whether commercial or not, is required by the Affero clause (named after Affero Inc. company that pioneered the community-based Q&A sites in early 2000s) to publish any modifications under the same license. The statements in relation to Infrastructure and relay servers anywhere in this document assume no modifications to the published code, even in the cases when it is not explicitly stated. -**Client applications**. Our client application Software (referred to as "app" or "apps") also has no modifications compared with published open-source code, and any developers of the alternative client apps based on our code are required to publish any modifications under the same AGPLv3 license. Client applications should not include any tracking or analytics code, and do not share any information with SimpleX Chat Ltd or any other third parties. If you ever discover any tracking or analytics code, please report it to us, so we can remove it. +**Client applications**. SimpleX Chat client application Software (referred to as "app" or "apps") also has no modifications compared with published open-source code, and any developers of the alternative client apps based on SimpleX Chat code are required to publish any modifications under the same AGPLv3 license. Client applications should not include any tracking or analytics code, and do not share any tracking information with SimpleX Chat Ltd, preset server operators or any other third parties. If you ever discover any tracking or analytics code, please report it to SimpleX Chat Ltd, so it can be removed. -**Accessing the infrastructure**. For the efficiency of the network access, the client Software by default accesses all queues your app creates on any relay server within one user profile via the same network (TCP/IP) connection. At the cost of additional traffic this configuration can be changed to use different transport session for each connection. Relay servers do not collect information about which queues were created or accessed via the same connection, so the relay servers cannot establish which queues belong to the same user profile. Whoever might observe your network traffic would know which relay servers you use, and how much data you send, but not to whom it is sent - the data that leaves the servers is always different from the data they receive - there are no identifiers or ciphertext in common, even inside TLS encryption layer. Please refer to our [technical design document](https://github.com/simplex-chat/simplexmq/blob/master/protocol/overview-tjr.md) for more information about our privacy model and known security and privacy risks. +**Accessing the infrastructure**. For the efficiency of the network access, the client Software by default accesses all queues your app creates on any relay server within one user profile via the same network (TCP/IP) connection. At the cost of additional traffic this configuration can be changed to use different transport session for each connection. Relay servers do not collect information about which queues were created or accessed via the same connection, so the relay servers cannot establish which queues belong to the same user profile. Whoever might observe your network traffic would know which relay servers you use, and how much data you send, but not to whom it is sent - the data that leaves the servers is always different from the data they receive - there are no identifiers or ciphertext in common, even inside TLS encryption layer. Please refer to the [technical design document](https://github.com/simplex-chat/simplexmq/blob/master/protocol/overview-tjr.md) for more information about the privacy model and known security and privacy risks. -**Privacy of user data**. Servers do not retain any data we transmit for any longer than necessary to deliver the messages between apps. SimpleX Chat Ltd collects aggregate statistics across all its servers, as supported by published code and can be enabled by any infrastructure provider, but not any statistics per-user, or per geographic location, or per IP address, or per transport session. We do not have information about how many people use SimpleX Chat applications, we only know an approximate number of app installations and the aggregate traffic through the preset servers. In any case, we do not and will not sell or in any way monetize user data. Our future business model assumes charging for some optional Software features instead, in a transparent and fair way. +**Privacy of user data**. Servers do not retain any data you transmit for any longer than necessary to deliver the messages between apps. Preset server operators collect aggregate statistics across all their servers, as supported by published code and can be enabled by any infrastructure operator, but not any statistics per-user, or per geographic location, or per IP address, or per transport session. SimpleX Chat Ltd does not have information about how many people use SimpleX Chat applications, it only knows an approximate number of app installations and the aggregate traffic through the preset servers. In any case, preset server operators do not and will not sell or in any way monetize user data. The future business model assumes charging for some optional Software features instead, in a transparent and fair way. -**Operating our Infrastructure**. For the purpose of using our Software, if you continue using preset servers, you agree that your end-to-end encrypted messages are transferred via the preset servers in any countries where we have or use facilities and service providers or partners. The information about geographic location of the servers will be made available in the apps in the near future. +**Operating Infrastructure**. For the purpose of using SimpleX Chat Software, if you continue using preset servers, you agree that your end-to-end encrypted messages are transferred via the preset servers in any countries where preset server operators have or use facilities and service providers or partners. The information about geographic location and hosting providers of the preset messaging servers is available on server pages. -**Software**. You agree to downloading and installing updates to our Applications when they are available; they would only be automatic if you configure your devices in this way. +**Software**. You agree to downloading and installing updates to SimpleX Chat Applications when they are available; they would only be automatic if you configure your devices in this way. -**Traffic and device costs**. You are solely responsible for the traffic and device costs that you incur while using our Applications, and any associated taxes. +**Traffic and device costs**. You are solely responsible for the traffic and device costs that you incur while using SimpleX Chat Applications, and any associated taxes. -**Legal usage**. You agree to use our Applications only for legal purposes. You will not use (or assist others in using) our Applications in ways that: 1) violate or infringe the rights of Software users, SimpleX Chat Ltd, or others, including privacy, publicity, intellectual property, or other proprietary rights; 2) involve sending illegal communications, e.g. spam. While we cannot access content or identify messages or groups, in some cases the links to the illegal communications available via our Applications can be shared publicly on social media or websites. We reserve the right to remove such links from the preset servers and disrupt the conversations that send illegal content via our servers, whether they were reported by the users or discovered by our team. +**Legal usage**. You agree to use SimpleX Chat Applications only for legal purposes. You will not use (or assist others in using) the Applications in ways that: 1) violate or infringe the rights of Software users, SimpleX Chat Ltd, other preset server operators, or others, including privacy, publicity, intellectual property, or other proprietary rights; 2) involve sending illegal communications, e.g. spam. While server operators cannot access content or identify messages or groups, in some cases the links to the illegal communications can be shared publicly on social media or websites. Preset server operators reserve the right to remove such links from the preset servers and disrupt the conversations that send illegal content via their servers, whether they were reported by the users or discovered by the operators themselves. -**Damage to SimpleX Chat Ltd**. You must not (or assist others to) access, use, modify, distribute, transfer, or exploit our Applications in unauthorized manners, or in ways that harm Software users, SimpleX Chat Ltd, our Infrastructure, or any other systems. For example, you must not 1) access our Infrastructure or systems without authorization, in any way other than by using the Software; 2) disrupt the integrity or performance of our Infrastructure; 3) collect information about our users in any manner; or 4) sell, rent, or charge for our Infrastructure. This does not prohibit you from providing your own Infrastructure to others, whether free or for a fee, as long as you do not violate these Conditions and AGPLv3 license, including the requirement to publish any modifications of the relay server software. +**Damage to SimpleX Chat Ltd and Preset Server Operators**. You must not (or assist others to) access, use, modify, distribute, transfer, or exploit SimpleX Chat Applications in unauthorized manners, or in ways that harm Software users, SimpleX Chat Ltd, other preset server operators, their Infrastructure, or any other systems. For example, you must not 1) access preset operators' Infrastructure or systems without authorization, in any way other than by using the Software; 2) disrupt the integrity or performance of preset operators' Infrastructure; 3) collect information about the users in any manner; or 4) sell, rent, or charge for preset operators' Infrastructure. This does not prohibit you from providing your own Infrastructure to others, whether free or for a fee, as long as you do not violate these Conditions and AGPLv3 license, including the requirement to publish any modifications of the relay server software. -**Keeping your data secure**. SimpleX Chat is the first communication software that aims to be 100% private by design - server software neither has the ability to access your messages, nor it has information about who you communicate with. That means that you are solely responsible for keeping your device, your user profile and any data safe and secure. If you lose your phone or remove the Software from the device, you will not be able to recover the lost data, unless you made a back up. To protect the data you need to make regular backups, as using old backups may disrupt your communication with some of the contacts. +**Keeping your data secure**. SimpleX Chat is the first communication software that aims to be 100% private by design - server software neither has the ability to access your messages, nor it has information about who you communicate with. That means that you are solely responsible for keeping your device, your user profile and any data safe and secure. If you lose your phone or remove the Software from the device, you will not be able to recover the lost data, unless you made a back up. To protect the data you need to make regular backups, as using old backups may disrupt your communication with some of the contacts. SimpleX Chat Ltd and other preset server operators are not responsible for any data loss. **Storing the messages on the device**. The messages are stored in the encrypted database on your device. Whether and how database passphrase is stored is determined by the configuration of the Software you use. The databases created prior to 2023 or in CLI (terminal) app may remain unencrypted, and it will be indicated in the app interface. In this case, if you make a backup of the data and store it unencrypted, the backup provider may be able to access the messages. Please note, that the desktop apps can be configured to store the database passphrase in the configuration file in plaintext, and unless you set the passphrase when first running the app, a random passphrase will be used and stored on the device. You can remove it from the device via the app settings. **Storing the files on the device**. The files currently sent and received in the apps by default (except CLI app) are stored on your device encrypted using unique keys, different for each file, that are stored in the database. Once the message that the file was attached to is removed, even if the copy of the encrypted file is retained, it should be impossible to recover the key allowing to decrypt the file. This local file encryption may affect app performance, and it can be disabled via the app settings. This change will only affect the new files. If you later re-enable the encryption, it will also affect only the new files. If you make a backup of the app data and store it unencrypted, the backup provider will be able to access any unencrypted files. In any case, irrespective of the storage setting, the files are always sent by all apps end-to-end encrypted. -**No Access to Emergency Services**. Our Applications do not provide access to emergency service providers like the police, fire department, hospitals, or other public safety organizations. Make sure you can contact emergency service providers through a mobile, fixed-line telephone, or other service. +**No Access to Emergency Services**. SimpleX Chat Applications do not provide access to emergency service providers like the police, fire department, hospitals, or other public safety organizations. Make sure you can contact emergency service providers through a mobile, fixed-line telephone, or other service. -**Third-party services**. Our Applications may allow you to access, use, or interact with our or third-party websites, apps, content, and other products and services. When you use third-party services, their terms and privacy policies govern your use of those services. +**Third-party services**. SimpleX Chat Applications may allow you to access, use, or interact with the websites of SimpleX Chat Ltd, preset server operators or other third-party websites, apps, content, and other products and services. When you use third-party services, their terms and privacy policies govern your use of those services. -**Your Rights**. You own the messages and the information you transmit through our Applications. Your recipients are able to retain the messages they receive from you; there is no technical ability to delete data from their devices. While there are various app features that allow deleting messages from the recipients' devices, such as _disappearing messages_ and _full message deletion_, their functioning on your recipients' devices cannot be guaranteed or enforced, as the device may be offline or have a modified version of the Software. At the same time, repudiation property of the end-to-end encryption algorithm allows you to plausibly deny having sent the message, like you can deny what you said in a private face-to-face conversation, as the recipient cannot provide any proof to the third parties, by design. +**Your Rights**. You own the messages and the information you transmit through SimpleX Applications. Your recipients are able to retain the messages they receive from you; there is no technical ability to delete data from their devices. While there are various app features that allow deleting messages from the recipients' devices, such as _disappearing messages_ and _full message deletion_, their functioning on your recipients' devices cannot be guaranteed or enforced, as the device may be offline or have a modified version of the Software. At the same time, repudiation property of the end-to-end encryption algorithm allows you to plausibly deny having sent the message, like you can deny what you said in a private face-to-face conversation, as the recipient cannot provide any proof to the third parties, by design. -**License**. SimpleX Chat Ltd grants you a limited, revocable, non-exclusive, and non-transferable license to use our Applications in accordance with these Conditions. The source-code of Applications is available and can be used under [AGPL v3 license](https://github.com/simplex-chat/simplex-chat/blob/stable/LICENSE). +**License**. SimpleX Chat Ltd grants you a limited, revocable, non-exclusive, and non-transferable license to use SimpleX Chat Applications in accordance with these Conditions. The source-code of Applications is available and can be used under [AGPL v3 license](https://github.com/simplex-chat/simplex-chat/blob/stable/LICENSE). -**SimpleX Chat Ltd Rights**. We own all copyrights, trademarks, domains, logos, trade secrets, and other intellectual property rights associated with our Applications. You may not use our copyrights, trademarks, domains, logos, and other intellectual property rights unless you have our written permission, and unless under an open-source license distributed together with the source code. To report copyright, trademark, or other intellectual property infringement, please contact chat@simplex.chat. +**SimpleX Chat Ltd Rights**. SimpleX Chat Ltd (and, where applicable, preset server operators) owns all copyrights, trademarks, domains, logos, trade secrets, and other intellectual property rights associated with the Applications. You may not use SimpleX Chat Ltd copyrights, trademarks, domains, logos, and other intellectual property rights unless you have SimpleX Chat Ltd written permission, and unless under an open-source license distributed together with the source code. To report copyright, trademark, or other intellectual property infringement, please contact chat@simplex.chat. -**Disclaimers**. YOU USE OUR APPLICATIONS AT YOUR OWN RISK AND SUBJECT TO THE FOLLOWING DISCLAIMERS. WE PROVIDE OUR APPLICATIONS ON AN “AS IS” BASIS WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, NON-INFRINGEMENT, AND FREEDOM FROM COMPUTER VIRUS OR OTHER HARMFUL CODE. SIMPLEX CHAT LTD DOES NOT WARRANT THAT ANY INFORMATION PROVIDED BY US IS ACCURATE, COMPLETE, OR USEFUL, THAT OUR APPLICATIONS WILL BE OPERATIONAL, ERROR-FREE, SECURE, OR SAFE, OR THAT OUR APPLICATIONS WILL FUNCTION WITHOUT DISRUPTIONS, DELAYS, OR IMPERFECTIONS. WE DO NOT CONTROL, AND ARE NOT RESPONSIBLE FOR, CONTROLLING HOW OR WHEN OUR USERS USE OUR APPLICATIONS. WE ARE NOT RESPONSIBLE FOR THE ACTIONS OR INFORMATION (INCLUDING CONTENT) OF OUR USERS OR OTHER THIRD PARTIES. YOU RELEASE US, AFFILIATES, DIRECTORS, OFFICERS, EMPLOYEES, PARTNERS, AND AGENTS ("SIMPLEX PARTIES") FROM ANY CLAIM, COMPLAINT, CAUSE OF ACTION, CONTROVERSY, OR DISPUTE (TOGETHER, "CLAIM") AND DAMAGES, KNOWN AND UNKNOWN, RELATING TO, ARISING OUT OF, OR IN ANY WAY CONNECTED WITH ANY SUCH CLAIM YOU HAVE AGAINST ANY THIRD PARTIES. +**Disclaimers**. YOU USE SIMPLEX APPLICATIONS AT YOUR OWN RISK AND SUBJECT TO THE FOLLOWING DISCLAIMERS. SIMPLEX CHAT LTD PROVIDES APPLICATIONS ON AN “AS IS” BASIS WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, NON-INFRINGEMENT, AND FREEDOM FROM COMPUTER VIRUS OR OTHER HARMFUL CODE. SIMPLEX CHAT LTD DOES NOT WARRANT THAT ANY INFORMATION PROVIDED BY THEM IS ACCURATE, COMPLETE, OR USEFUL, THAT THEIR APPLICATIONS WILL BE OPERATIONAL, ERROR-FREE, SECURE, OR SAFE, OR THAT THEIR APPLICATIONS WILL FUNCTION WITHOUT DISRUPTIONS, DELAYS, OR IMPERFECTIONS. SIMPLEX CHAT LTD AND OTHER PRESET OPERATORS DO NOT CONTROL, AND ARE NOT RESPONSIBLE FOR, CONTROLLING HOW OR WHEN THE USERS USE APPLICATIONS. SIMPLEX CHAT LTD AND OTHER PRESET OPERATORS ARE NOT RESPONSIBLE FOR THE ACTIONS OR INFORMATION (INCLUDING CONTENT) OF THEIR USERS OR OTHER THIRD PARTIES. YOU RELEASE SIMPLEX CHAT LTD, OTHER PRESET OPERATORS, AFFILIATES, DIRECTORS, OFFICERS, EMPLOYEES, PARTNERS, AND AGENTS ("SIMPLEX PARTIES") FROM ANY CLAIM, COMPLAINT, CAUSE OF ACTION, CONTROVERSY, OR DISPUTE (TOGETHER, "CLAIM") AND DAMAGES, KNOWN AND UNKNOWN, RELATING TO, ARISING OUT OF, OR IN ANY WAY CONNECTED WITH ANY SUCH CLAIM YOU HAVE AGAINST ANY THIRD PARTIES. -**Limitation of liability**. THE SIMPLEX PARTIES WILL NOT BE LIABLE TO YOU FOR ANY LOST PROFITS OR CONSEQUENTIAL, SPECIAL, PUNITIVE, INDIRECT, OR INCIDENTAL DAMAGES RELATING TO, ARISING OUT OF, OR IN ANY WAY IN CONNECTION WITH OUR CONDITIONS, US, OR OUR APPLICATIONS, EVEN IF THE SIMPLEX PARTIES HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. OUR AGGREGATE LIABILITY RELATING TO, ARISING OUT OF, OR IN ANY WAY IN CONNECTION WITH OUR CONDITIONS, US, OR OUR APPLICATIONS WILL NOT EXCEED ONE DOLLAR ($1). THE FOREGOING DISCLAIMER OF CERTAIN DAMAGES AND LIMITATION OF LIABILITY WILL APPLY TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW. THE LAWS OF SOME JURISDICTIONS MAY NOT ALLOW THE EXCLUSION OR LIMITATION OF CERTAIN DAMAGES, SO SOME OR ALL OF THE EXCLUSIONS AND LIMITATIONS SET FORTH ABOVE MAY NOT APPLY TO YOU. NOTWITHSTANDING ANYTHING TO THE CONTRARY IN OUR CONDITIONS, IN SUCH CASES, THE LIABILITY OF THE SIMPLEX PARTIES WILL BE LIMITED TO THE EXTENT PERMITTED BY APPLICABLE LAW. +**Limitation of liability**. THE SIMPLEX PARTIES WILL NOT BE LIABLE TO YOU FOR ANY LOST PROFITS OR CONSEQUENTIAL, SPECIAL, PUNITIVE, INDIRECT, OR INCIDENTAL DAMAGES RELATING TO, ARISING OUT OF, OR IN ANY WAY IN CONNECTION WITH OUR CONDITIONS, US, OR SIMPLEX APPLICATIONS, EVEN IF THE SIMPLEX PARTIES HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. THE AGGREGATE LIABILITY OF THE SIMPLEX PARTIES RELATING TO, ARISING OUT OF, OR IN ANY WAY IN CONNECTION WITH THESE CONDITIONS, THE SIMPLEX PARTIES, OR THE APPLICATIONS WILL NOT EXCEED ONE DOLLAR ($1). THE FOREGOING DISCLAIMER OF CERTAIN DAMAGES AND LIMITATION OF LIABILITY WILL APPLY TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW. THE LAWS OF SOME JURISDICTIONS MAY NOT ALLOW THE EXCLUSION OR LIMITATION OF CERTAIN DAMAGES, SO SOME OR ALL OF THE EXCLUSIONS AND LIMITATIONS SET FORTH ABOVE MAY NOT APPLY TO YOU. NOTWITHSTANDING ANYTHING TO THE CONTRARY IN THE CONDITIONS, IN SUCH CASES, THE LIABILITY OF THE SIMPLEX PARTIES WILL BE LIMITED TO THE EXTENT PERMITTED BY APPLICABLE LAW. -**Availability**. Our Applications may be disrupted, including for maintenance, upgrades, or network or equipment failures. We may discontinue some or all of our Applications, including certain features and the support for certain devices and platforms, at any time. +**Availability**. The Applications may be disrupted, including for maintenance, upgrades, or network or equipment failures. SimpleX Chat Ltd may discontinue some or all of their Applications, including certain features and the support for certain devices and platforms, at any time. Preset server operators may discontinue providing the servers, at any time. -**Resolving disputes**. You agree to resolve any Claim you have with us relating to or arising from our Conditions, us, or our Applications in the courts of England and Wales. You also agree to submit to the personal jurisdiction of such courts for the purpose of resolving all such disputes. The laws of England govern our Conditions, as well as any disputes, whether in court or arbitration, which might arise between SimpleX Chat Ltd and you, without regard to conflict of law provisions. +**Resolving disputes**. You agree to resolve any Claim you have with SimpleX Chat Ltd and/or preset server operators relating to or arising from these Conditions, them, or the Applications in the courts of England and Wales. You also agree to submit to the personal jurisdiction of such courts for the purpose of resolving all such disputes. The laws of England govern these Conditions, as well as any disputes, whether in court or arbitration, which might arise between SimpleX Chat Ltd (or preset server operators) and you, without regard to conflict of law provisions. -**Changes to the conditions**. SimpleX Chat Ltd may update the Conditions from time to time. Your continued use of our Applications confirms your acceptance of our updated Conditions and supersedes any prior Conditions. You will comply with all applicable export control and trade sanctions laws. Our Conditions cover the entire agreement between you and SimpleX Chat Ltd regarding our Applications. If you do not agree with our Conditions, you should stop using our Applications. +**Changes to the conditions**. SimpleX Chat Ltd may update the Conditions from time to time. The updated conditions have to be accepted within 30 days. Even if you fail to accept updated conditions, your continued use of SimpleX Chat Applications confirms your acceptance of the updated Conditions and supersedes any prior Conditions. You will comply with all applicable export control and trade sanctions laws. These Conditions cover the entire agreement between you and SimpleX Chat Ltd, and any preset server operators where applicable, regarding SimpleX Chat Applications. If you do not agree with these Conditions, you should stop using the Applications. -**Enforcing the conditions**. If we fail to enforce any of our Conditions, that does not mean we waive the right to enforce them. If any provision of the Conditions is deemed unlawful, void, or unenforceable, that provision shall be deemed severable from our Conditions and shall not affect the enforceability of the remaining provisions. Our Applications are not intended for distribution to or use in any country where such distribution or use would violate local law or would subject us to any regulations in another country. We reserve the right to limit our Applications in any country. If you have specific questions about these Conditions, please contact us at chat@simplex.chat. +**Enforcing the conditions**. If SimpleX Chat Ltd or preset server operators fail to enforce any of these Conditions, that does not mean they waive the right to enforce them. If any provision of the Conditions is deemed unlawful, void, or unenforceable, that provision shall be deemed severable from the Conditions and shall not affect the enforceability of the remaining provisions. The Applications are not intended for distribution to or use in any country where such distribution or use would violate local law or would subject SimpleX Chat Ltd to any regulations in another country. SimpleX Chat Ltd reserve the right to limit the access to the Applications in any country. Preset operators reserve the right to limit access to their servers in any country. If you have specific questions about these Conditions, please contact SimpleX Chat Ltd at chat@simplex.chat. -**Ending these conditions**. You may end these Conditions with SimpleX Chat Ltd at any time by deleting our Applications from your devices and discontinuing use of our Infrastructure. The provisions related to Licenses, Disclaimers, Limitation of Liability, Resolving dispute, Availability, Changes to the conditions, Enforcing the conditions, and Ending these conditions will survive termination of your relationship with SimpleX Chat Ltd. +**Ending these conditions**. You may end these Conditions with SimpleX Chat Ltd and preset server operators at any time by deleting the Applications from your devices and discontinuing use of the Infrastructure of SimpleX Chat Ltd and preset server operators. The provisions related to Licenses, Disclaimers, Limitation of Liability, Resolving dispute, Availability, Changes to the conditions, Enforcing the conditions, and Ending these conditions will survive termination of your relationship with SimpleX Chat Ltd and/or preset server operators. -Updated October 14, 2024 +Updated November 14, 2024 From e45a96935c452946266aa41d62868d70945d948b Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Thu, 14 Nov 2024 12:16:51 +0000 Subject: [PATCH 070/567] ci: update website build --- .github/workflows/web.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml index 7fc66308f8..6839d48aeb 100644 --- a/.github/workflows/web.yml +++ b/.github/workflows/web.yml @@ -10,6 +10,7 @@ on: - blog/** - docs/** - .github/workflows/web.yml + - PRIVACY.md jobs: build: From d42cab8e227db171ac4292e2523454fb925529f2 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 14 Nov 2024 17:43:34 +0000 Subject: [PATCH 071/567] core: preset operators and servers (#5142) * core: preset servers and operators (WIP) * usageConditionsToAdd * simplify * WIP * database entity IDs * preset operators and servers (compiles) * update (most tests pass) * remove imports * fix * update * make preset servers lists potentially empty in some operators, as long as the combined list is not empty * CLI API in progress, validateUserServers * make servers of disabled operators "unknown", consider only enabled servers when switching profile links * exclude disabled operators when receiving files * fix TH in ghc 8.10.7 * add type for ghc 8.10.7 * pattern match for ghc 8.10.7 * ghc 8.10.7 fix attempt * remove additional pattern, update servers * do not strip title from conditions * remove space --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --- cabal.project | 2 +- package.yaml | 3 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 21 +- src/Simplex/Chat.hs | 449 +++++++++------- src/Simplex/Chat/Controller.hs | 96 ++-- .../Migrations/M20241027_server_operators.hs | 18 +- src/Simplex/Chat/Migrations/chat_schema.sql | 9 +- src/Simplex/Chat/Operators.hs | 396 ++++++++++++--- src/Simplex/Chat/Operators/Conditions.hs | 2 +- src/Simplex/Chat/Stats.hs | 3 +- src/Simplex/Chat/Store/Profiles.hs | 478 ++++++++++-------- src/Simplex/Chat/Store/Shared.hs | 1 + src/Simplex/Chat/Terminal.hs | 37 +- src/Simplex/Chat/Terminal/Main.hs | 4 +- src/Simplex/Chat/View.hs | 115 +++-- tests/ChatClient.hs | 19 +- tests/ChatTests/Direct.hs | 77 ++- tests/ChatTests/Groups.hs | 2 + tests/ChatTests/Profiles.hs | 12 +- tests/RandomServers.hs | 51 +- 21 files changed, 1148 insertions(+), 649 deletions(-) diff --git a/cabal.project b/cabal.project index 61ce04a569..74f944c37d 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: ff05a465ee15ac7ae2c14a9fb703a18564950631 + tag: 93f30c8edf9243ad2291dd6427d87328e282560a source-repository-package type: git diff --git a/package.yaml b/package.yaml index 2fc50a3532..4a95d52044 100644 --- a/package.yaml +++ b/package.yaml @@ -39,6 +39,7 @@ dependencies: - optparse-applicative >= 0.15 && < 0.17 - random >= 1.1 && < 1.3 - record-hasfield == 1.0.* + - scientific ==0.3.7.* - simple-logger == 0.1.* - simplexmq >= 5.0 - socks == 0.6.* @@ -73,7 +74,7 @@ when: - bytestring == 0.10.* - process >= 1.6 && < 1.6.18 - template-haskell == 2.16.* - - text >= 1.2.3.0 && < 1.3 + - text >= 1.2.4.0 && < 1.3 library: source-dirs: src diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 3e0f103641..bd2602c1b6 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."ff05a465ee15ac7ae2c14a9fb703a18564950631" = "1gv4nwqzbqkj7y3ffkiwkr4qwv52vdzppsds5vsfqaayl14rzmgp"; + "https://github.com/simplex-chat/simplexmq.git"."93f30c8edf9243ad2291dd6427d87328e282560a" = "1zf0sp9dy6kz4zvyz6mdgmhydps7khcq84n30irp983w1xh7gzs7"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 47987cd697..d3ea814011 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -228,6 +228,7 @@ library , optparse-applicative >=0.15 && <0.17 , random >=1.1 && <1.3 , record-hasfield ==1.0.* + , scientific ==0.3.7.* , simple-logger ==0.1.* , simplexmq >=5.0 , socks ==0.6.* @@ -254,7 +255,7 @@ library bytestring ==0.10.* , process >=1.6 && <1.6.18 , template-haskell ==2.16.* - , text >=1.2.3.0 && <1.3 + , text >=1.2.4.0 && <1.3 executable simplex-bot main-is: Main.hs @@ -292,6 +293,7 @@ executable simplex-bot , optparse-applicative >=0.15 && <0.17 , random >=1.1 && <1.3 , record-hasfield ==1.0.* + , scientific ==0.3.7.* , simple-logger ==0.1.* , simplex-chat , simplexmq >=5.0 @@ -319,7 +321,7 @@ executable simplex-bot bytestring ==0.10.* , process >=1.6 && <1.6.18 , template-haskell ==2.16.* - , text >=1.2.3.0 && <1.3 + , text >=1.2.4.0 && <1.3 executable simplex-bot-advanced main-is: Main.hs @@ -357,6 +359,7 @@ executable simplex-bot-advanced , optparse-applicative >=0.15 && <0.17 , random >=1.1 && <1.3 , record-hasfield ==1.0.* + , scientific ==0.3.7.* , simple-logger ==0.1.* , simplex-chat , simplexmq >=5.0 @@ -384,7 +387,7 @@ executable simplex-bot-advanced bytestring ==0.10.* , process >=1.6 && <1.6.18 , template-haskell ==2.16.* - , text >=1.2.3.0 && <1.3 + , text >=1.2.4.0 && <1.3 executable simplex-broadcast-bot main-is: Main.hs @@ -425,6 +428,7 @@ executable simplex-broadcast-bot , optparse-applicative >=0.15 && <0.17 , random >=1.1 && <1.3 , record-hasfield ==1.0.* + , scientific ==0.3.7.* , simple-logger ==0.1.* , simplex-chat , simplexmq >=5.0 @@ -452,7 +456,7 @@ executable simplex-broadcast-bot bytestring ==0.10.* , process >=1.6 && <1.6.18 , template-haskell ==2.16.* - , text >=1.2.3.0 && <1.3 + , text >=1.2.4.0 && <1.3 executable simplex-chat main-is: Main.hs @@ -491,6 +495,7 @@ executable simplex-chat , optparse-applicative >=0.15 && <0.17 , random >=1.1 && <1.3 , record-hasfield ==1.0.* + , scientific ==0.3.7.* , simple-logger ==0.1.* , simplex-chat , simplexmq >=5.0 @@ -519,7 +524,7 @@ executable simplex-chat bytestring ==0.10.* , process >=1.6 && <1.6.18 , template-haskell ==2.16.* - , text >=1.2.3.0 && <1.3 + , text >=1.2.4.0 && <1.3 executable simplex-directory-service main-is: Main.hs @@ -563,6 +568,7 @@ executable simplex-directory-service , optparse-applicative >=0.15 && <0.17 , random >=1.1 && <1.3 , record-hasfield ==1.0.* + , scientific ==0.3.7.* , simple-logger ==0.1.* , simplex-chat , simplexmq >=5.0 @@ -590,7 +596,7 @@ executable simplex-directory-service bytestring ==0.10.* , process >=1.6 && <1.6.18 , template-haskell ==2.16.* - , text >=1.2.3.0 && <1.3 + , text >=1.2.4.0 && <1.3 test-suite simplex-chat-test type: exitcode-stdio-1.0 @@ -664,6 +670,7 @@ test-suite simplex-chat-test , optparse-applicative >=0.15 && <0.17 , random >=1.1 && <1.3 , record-hasfield ==1.0.* + , scientific ==0.3.7.* , silently ==1.2.* , simple-logger ==0.1.* , simplex-chat @@ -692,7 +699,7 @@ test-suite simplex-chat-test bytestring ==0.10.* , process >=1.6 && <1.6.18 , template-haskell ==2.16.* - , text >=1.2.3.0 && <1.3 + , text >=1.2.4.0 && <1.3 if impl(ghc >= 9.6.2) build-depends: hspec ==2.11.* diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index c517aa52d5..86b6a5e51b 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -6,6 +6,7 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RankNTypes #-} @@ -43,7 +44,7 @@ import Data.Functor (($>)) import Data.Functor.Identity import Data.Int (Int64) import Data.List (find, foldl', isSuffixOf, mapAccumL, partition, sortOn, zipWith4) -import Data.List.NonEmpty (NonEmpty (..), nonEmpty, toList, (<|)) +import Data.List.NonEmpty (NonEmpty (..), (<|)) import qualified Data.List.NonEmpty as L import Data.Map.Strict (Map) import qualified Data.Map.Strict as M @@ -54,6 +55,7 @@ import qualified Data.Text as T import Data.Text.Encoding (decodeLatin1, encodeUtf8) import Data.Time (NominalDiffTime, addUTCTime, defaultTimeLocale, formatTime) import Data.Time.Clock (UTCTime, diffUTCTime, getCurrentTime, nominalDay, nominalDiffTimeToSeconds) +import Data.Type.Equality import qualified Data.UUID as UUID import qualified Data.UUID.V4 as V4 import Data.Word (Word32) @@ -98,7 +100,7 @@ import qualified Simplex.FileTransfer.Transport as XFTP import Simplex.FileTransfer.Types (FileErrorType (..), RcvFileId, SndFileId) import Simplex.Messaging.Agent as Agent import Simplex.Messaging.Agent.Client (SubInfo (..), agentClientStore, getAgentQueuesInfo, getAgentWorkersDetails, getAgentWorkersSummary, getFastNetworkConfig, ipAddressProtected, withLockMap) -import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), InitialAgentServers (..), OperatorId, ServerCfg (..), allRoles, createAgentStore, defaultAgentConfig, enabledServerCfg, presetServerCfg) +import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), InitialAgentServers (..), ServerCfg (..), ServerRoles (..), allRoles, createAgentStore, defaultAgentConfig) import Simplex.Messaging.Agent.Lock (withLock) import Simplex.Messaging.Agent.Protocol import qualified Simplex.Messaging.Agent.Protocol as AP (AgentErrorType (..)) @@ -138,6 +140,32 @@ import qualified UnliftIO.Exception as E import UnliftIO.IO (hClose, hSeek, hTell, openFile) import UnliftIO.STM +operatorSimpleXChat :: NewServerOperator +operatorSimpleXChat = + ServerOperator + { operatorId = DBNewEntity, + operatorTag = Just OTSimplex, + tradeName = "SimpleX Chat", + legalName = Just "SimpleX Chat Ltd", + serverDomains = ["simplex.im"], + conditionsAcceptance = CARequired Nothing, + enabled = True, + roles = allRoles + } + +operatorFlux :: NewServerOperator +operatorFlux = + ServerOperator + { operatorId = DBNewEntity, + operatorTag = Just OTFlux, + tradeName = "Flux", + legalName = Just "InFlux Technologies Limited", + serverDomains = ["simplexonflux.com"], + conditionsAcceptance = CARequired Nothing, + enabled = False, + roles = ServerRoles {storage = False, proxy = True} + } + defaultChatConfig :: ChatConfig defaultChatConfig = ChatConfig @@ -148,13 +176,25 @@ defaultChatConfig = }, chatVRange = supportedChatVRange, confirmMigrations = MCConsole, - defaultServers = - DefaultAgentServers - { smp = _defaultSMPServers, - useSMP = 4, + presetServers = + PresetServers + { operators = + [ PresetOperator + { operator = Just operatorSimpleXChat, + smp = simplexChatSMPServers, + useSMP = 4, + xftp = map (presetServer True) $ L.toList defaultXFTPServers, + useXFTP = 3 + }, + PresetOperator + { operator = Just operatorFlux, + smp = fluxSMPServers, + useSMP = 3, + xftp = fluxXFTPServers, + useXFTP = 3 + } + ], ntf = _defaultNtfServers, - xftp = L.map (presetServerCfg True allRoles operatorSimpleXChat) defaultXFTPServers, - useXFTP = L.length defaultXFTPServers, netCfg = defaultNetworkConfig }, tbqSize = 1024, @@ -178,32 +218,52 @@ defaultChatConfig = chatHooks = defaultChatHooks } -_defaultSMPServers :: NonEmpty (ServerCfg 'PSMP) -_defaultSMPServers = - L.fromList $ - map - (presetServerCfg True allRoles operatorSimpleXChat) - [ "smp://0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU=@smp8.simplex.im,beccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion", - "smp://SkIkI6EPd2D63F4xFKfHk7I1UGZVNn6k1QWZ5rcyr6w=@smp9.simplex.im,jssqzccmrcws6bhmn77vgmhfjmhwlyr3u7puw4erkyoosywgl67slqqd.onion", - "smp://6iIcWT_dF2zN_w5xzZEY7HI2Prbh3ldP07YTyDexPjE=@smp10.simplex.im,rb2pbttocvnbrngnwziclp2f4ckjq65kebafws6g4hy22cdaiv5dwjqd.onion", - "smp://1OwYGt-yqOfe2IyVHhxz3ohqo3aCCMjtB-8wn4X_aoY=@smp11.simplex.im,6ioorbm6i3yxmuoezrhjk6f6qgkc4syabh7m3so74xunb5nzr4pwgfqd.onion", - "smp://UkMFNAXLXeAAe0beCa4w6X_zp18PwxSaSjY17BKUGXQ=@smp12.simplex.im,ie42b5weq7zdkghocs3mgxdjeuycheeqqmksntj57rmejagmg4eor5yd.onion", - "smp://enEkec4hlR3UtKx2NMpOUK_K4ZuDxjWBO1d9Y4YXVaA=@smp14.simplex.im,aspkyu2sopsnizbyfabtsicikr2s4r3ti35jogbcekhm3fsoeyjvgrid.onion", - "smp://h--vW7ZSkXPeOUpfxlFGgauQmXNFOzGoizak7Ult7cw=@smp15.simplex.im,oauu4bgijybyhczbnxtlggo6hiubahmeutaqineuyy23aojpih3dajad.onion", - "smp://hejn2gVIqNU6xjtGM3OwQeuk8ZEbDXVJXAlnSBJBWUA=@smp16.simplex.im,p3ktngodzi6qrf7w64mmde3syuzrv57y55hxabqcq3l5p6oi7yzze6qd.onion", - "smp://ZKe4uxF4Z_aLJJOEsC-Y6hSkXgQS5-oc442JQGkyP8M=@smp17.simplex.im,ogtwfxyi3h2h5weftjjpjmxclhb5ugufa5rcyrmg7j4xlch7qsr5nuqd.onion", - "smp://PtsqghzQKU83kYTlQ1VKg996dW4Cw4x_bvpKmiv8uns=@smp18.simplex.im,lyqpnwbs2zqfr45jqkncwpywpbtq7jrhxnib5qddtr6npjyezuwd3nqd.onion", - "smp://N_McQS3F9TGoh4ER0QstUf55kGnNSd-wXfNPZ7HukcM=@smp19.simplex.im,i53bbtoqhlc365k6kxzwdp5w3cdt433s7bwh3y32rcbml2vztiyyz5id.onion" +simplexChatSMPServers :: [NewUserServer 'PSMP] +simplexChatSMPServers = + map + (presetServer True) + [ "smp://0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU=@smp8.simplex.im,beccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion", + "smp://SkIkI6EPd2D63F4xFKfHk7I1UGZVNn6k1QWZ5rcyr6w=@smp9.simplex.im,jssqzccmrcws6bhmn77vgmhfjmhwlyr3u7puw4erkyoosywgl67slqqd.onion", + "smp://6iIcWT_dF2zN_w5xzZEY7HI2Prbh3ldP07YTyDexPjE=@smp10.simplex.im,rb2pbttocvnbrngnwziclp2f4ckjq65kebafws6g4hy22cdaiv5dwjqd.onion", + "smp://1OwYGt-yqOfe2IyVHhxz3ohqo3aCCMjtB-8wn4X_aoY=@smp11.simplex.im,6ioorbm6i3yxmuoezrhjk6f6qgkc4syabh7m3so74xunb5nzr4pwgfqd.onion", + "smp://UkMFNAXLXeAAe0beCa4w6X_zp18PwxSaSjY17BKUGXQ=@smp12.simplex.im,ie42b5weq7zdkghocs3mgxdjeuycheeqqmksntj57rmejagmg4eor5yd.onion", + "smp://enEkec4hlR3UtKx2NMpOUK_K4ZuDxjWBO1d9Y4YXVaA=@smp14.simplex.im,aspkyu2sopsnizbyfabtsicikr2s4r3ti35jogbcekhm3fsoeyjvgrid.onion", + "smp://h--vW7ZSkXPeOUpfxlFGgauQmXNFOzGoizak7Ult7cw=@smp15.simplex.im,oauu4bgijybyhczbnxtlggo6hiubahmeutaqineuyy23aojpih3dajad.onion", + "smp://hejn2gVIqNU6xjtGM3OwQeuk8ZEbDXVJXAlnSBJBWUA=@smp16.simplex.im,p3ktngodzi6qrf7w64mmde3syuzrv57y55hxabqcq3l5p6oi7yzze6qd.onion", + "smp://ZKe4uxF4Z_aLJJOEsC-Y6hSkXgQS5-oc442JQGkyP8M=@smp17.simplex.im,ogtwfxyi3h2h5weftjjpjmxclhb5ugufa5rcyrmg7j4xlch7qsr5nuqd.onion", + "smp://PtsqghzQKU83kYTlQ1VKg996dW4Cw4x_bvpKmiv8uns=@smp18.simplex.im,lyqpnwbs2zqfr45jqkncwpywpbtq7jrhxnib5qddtr6npjyezuwd3nqd.onion", + "smp://N_McQS3F9TGoh4ER0QstUf55kGnNSd-wXfNPZ7HukcM=@smp19.simplex.im,i53bbtoqhlc365k6kxzwdp5w3cdt433s7bwh3y32rcbml2vztiyyz5id.onion" + ] + <> map + (presetServer False) + [ "smp://u2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU=@smp4.simplex.im,o5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion", + "smp://hpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg=@smp5.simplex.im,jjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion", + "smp://PQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo=@smp6.simplex.im,bylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion" ] - <> map - (presetServerCfg False allRoles operatorSimpleXChat) - [ "smp://u2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU=@smp4.simplex.im,o5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion", - "smp://hpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg=@smp5.simplex.im,jjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion", - "smp://PQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo=@smp6.simplex.im,bylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion" - ] -operatorSimpleXChat :: Maybe OperatorId -operatorSimpleXChat = Just 1 +fluxSMPServers :: [NewUserServer 'PSMP] +fluxSMPServers = + map + (presetServer True) + [ "smp://xQW_ufMkGE20UrTlBl8QqceG1tbuylXhr9VOLPyRJmw=@smp1.simplexonflux.com,qb4yoanyl4p7o33yrknv4rs6qo7ugeb2tu2zo66sbebezs4cpyosarid.onion", + "smp://LDnWZVlAUInmjmdpQQoIo6FUinRXGe0q3zi5okXDE4s=@smp2.simplexonflux.com,yiqtuh3q4x7hgovkomafsod52wvfjucdljqbbipg5sdssnklgongxbqd.onion", + "smp://1jne379u7IDJSxAvXbWb_JgoE7iabcslX0LBF22Rej0=@smp3.simplexonflux.com,a5lm4k7ufei66cdck6fy63r4lmkqy3dekmmb7jkfdm5ivi6kfaojshad.onion", + "smp://xmAmqj75I9mWrUihLUlI0ZuNLXlIwFIlHRq5Pb6cHAU=@smp4.simplexonflux.com,qpcz2axyy66u26hfdd2e23uohcf3y6c36mn7dcuilcgnwjasnrvnxjqd.onion", + "smp://rWvBYyTamuRCBYb_KAn-nsejg879ndhiTg5Sq3k0xWA=@smp5.simplexonflux.com,4ao347qwiuluyd45xunmii4skjigzuuox53hpdsgbwxqafd4yrticead.onion", + "smp://PN7-uqLBToqlf1NxHEaiL35lV2vBpXq8Nj8BW11bU48=@smp6.simplexonflux.com,hury6ot3ymebbr2535mlp7gcxzrjpc6oujhtfxcfh2m4fal4xw5fq6qd.onion" + ] + +fluxXFTPServers :: [NewUserServer 'PXFTP] +fluxXFTPServers = + map + (presetServer True) + [ "xftp://92Sctlc09vHl_nAqF2min88zKyjdYJ9mgxRCJns5K2U=@xftp1.simplexonflux.com,apl3pumq3emwqtrztykyyoomdx4dg6ysql5zek2bi3rgznz7ai3odkid.onion", + "xftp://YBXy4f5zU1CEhnbbCzVWTNVNsaETcAGmYqGNxHntiE8=@xftp2.simplexonflux.com,c5jjecisncnngysah3cz2mppediutfelco4asx65mi75d44njvua3xid.onion", + "xftp://ARQO74ZSvv2OrulRF3CdgwPz_AMy27r0phtLSq5b664=@xftp3.simplexonflux.com,dc4mohiubvbnsdfqqn7xhlhpqs5u4tjzp7xpz6v6corwvzvqjtaqqiqd.onion", + "xftp://ub2jmAa9U0uQCy90O-fSUNaYCj6sdhl49Jh3VpNXP58=@xftp4.simplexonflux.com,4qq5pzier3i4yhpuhcrhfbl6j25udc4czoyascrj4yswhodhfwev3nyd.onion", + "xftp://Rh19D5e4Eez37DEE9hAlXDB3gZa1BdFYJTPgJWPO9OI=@xftp5.simplexonflux.com,q7itltdn32hjmgcqwhow4tay5ijetng3ur32bolssw32fvc5jrwvozad.onion", + "xftp://0AznwoyfX8Od9T_acp1QeeKtxUi676IBIiQjXVwbdyU=@xftp6.simplexonflux.com,upvzf23ou6nrmaf3qgnhd6cn3d74tvivlmz3p7wdfwq6fhthjrjiiqid.onion" + ] _defaultNtfServers :: [NtfServer] _defaultNtfServers = @@ -240,16 +300,19 @@ newChatController :: ChatDatabase -> Maybe User -> ChatConfig -> ChatOpts -> Boo newChatController ChatDatabase {chatStore, agentStore} user - cfg@ChatConfig {agentConfig = aCfg, defaultServers, inlineFiles, deviceNameForRemote, confirmMigrations} + cfg@ChatConfig {agentConfig = aCfg, presetServers, inlineFiles, deviceNameForRemote, confirmMigrations} ChatOpts {coreOptions = CoreChatOpts {smpServers, xftpServers, simpleNetCfg, logLevel, logConnections, logServerHosts, logFile, tbqSize, highlyAvailable, yesToUpMigrations}, deviceName, optFilesFolder, optTempDirectory, showReactions, allowInstantFiles, autoAcceptFileSize} backgroundMode = do let inlineFiles' = if allowInstantFiles || autoAcceptFileSize > 0 then inlineFiles else inlineFiles {sendChunks = 0, receiveInstant = False} confirmMigrations' = if confirmMigrations == MCConsole && yesToUpMigrations then MCYesUp else confirmMigrations - config = cfg {logLevel, showReactions, tbqSize, subscriptionEvents = logConnections, hostEvents = logServerHosts, defaultServers = configServers, inlineFiles = inlineFiles', autoAcceptFileSize, highlyAvailable, confirmMigrations = confirmMigrations'} + config = cfg {logLevel, showReactions, tbqSize, subscriptionEvents = logConnections, hostEvents = logServerHosts, presetServers = presetServers', inlineFiles = inlineFiles', autoAcceptFileSize, highlyAvailable, confirmMigrations = confirmMigrations'} firstTime = dbNew chatStore currentUser <- newTVarIO user + randomSMP <- randomPresetServers SPSMP presetServers' + randomXFTP <- randomPresetServers SPXFTP presetServers' + let randomServers = RandomServers {smpServers = randomSMP, xftpServers = randomXFTP} currentRemoteHost <- newTVarIO Nothing - servers <- agentServers config + servers <- withTransaction chatStore $ \db -> agentServers db config randomServers smpAgent <- getSMPAgentClient aCfg {tbqSize} servers agentStore backgroundMode agentAsync <- newTVarIO Nothing random <- liftIO C.newRandom @@ -285,6 +348,7 @@ newChatController ChatController { firstTime, currentUser, + randomServers, currentRemoteHost, smpAgent, agentAsync, @@ -322,28 +386,41 @@ newChatController contactMergeEnabled } where - configServers :: DefaultAgentServers - configServers = - let DefaultAgentServers {smp = defSmp, xftp = defXftp, netCfg} = defaultServers - smp' = maybe defSmp (L.map enabledServerCfg) (nonEmpty smpServers) - xftp' = maybe defXftp (L.map enabledServerCfg) (nonEmpty xftpServers) - in defaultServers {smp = smp', xftp = xftp', netCfg = updateNetworkConfig netCfg simpleNetCfg} - agentServers :: ChatConfig -> IO InitialAgentServers - agentServers config@ChatConfig {defaultServers = defServers@DefaultAgentServers {ntf, netCfg}} = do - users <- withTransaction chatStore getUsers - smp' <- getUserServers users SPSMP - xftp' <- getUserServers users SPXFTP + presetServers' :: PresetServers + presetServers' = presetServers {operators = operators', netCfg = netCfg'} + where + PresetServers {operators, netCfg} = presetServers + netCfg' = updateNetworkConfig netCfg simpleNetCfg + operators' = case (smpServers, xftpServers) of + ([], []) -> operators + (smpSrvs, []) -> L.map disableSMP operators <> [custom smpSrvs []] + ([], xftpSrvs) -> L.map disableXFTP operators <> [custom [] xftpSrvs] + (smpSrvs, xftpSrvs) -> [custom smpSrvs xftpSrvs] + disableSMP op@PresetOperator {smp} = (op :: PresetOperator) {smp = map disableSrv smp} + disableXFTP op@PresetOperator {xftp} = (op :: PresetOperator) {xftp = map disableSrv xftp} + disableSrv :: forall p. NewUserServer p -> NewUserServer p + disableSrv srv = (srv :: NewUserServer p) {enabled = False} + custom smpSrvs xftpSrvs = + PresetOperator + { operator = Nothing, + smp = map newUserServer smpSrvs, + useSMP = 0, + xftp = map newUserServer xftpSrvs, + useXFTP = 0 + } + agentServers :: DB.Connection -> ChatConfig -> RandomServers -> IO InitialAgentServers + agentServers db ChatConfig {presetServers = PresetServers {operators = presetOps, ntf, netCfg}} rs = do + users <- getUsers db + opDomains <- operatorDomains <$> getUpdateServerOperators db presetOps (null users) + smp' <- getServers SPSMP users opDomains + xftp' <- getServers SPXFTP users opDomains pure InitialAgentServers {smp = smp', xftp = xftp', ntf, netCfg} where - getUserServers :: forall p. (ProtocolTypeI p, UserProtocol p) => [User] -> SProtocolType p -> IO (Map UserId (NonEmpty (ServerCfg p))) - getUserServers users protocol = case users of - [] -> pure $ M.fromList [(1, cfgServers protocol defServers)] - _ -> M.fromList <$> initialServers - where - initialServers :: IO [(UserId, NonEmpty (ServerCfg p))] - initialServers = mapM (\u -> (aUserId u,) <$> userServers u) users - userServers :: User -> IO (NonEmpty (ServerCfg p)) - userServers user' = useServers config protocol <$> withTransaction chatStore (`getProtocolServers` user') + getServers :: forall p. (ProtocolTypeI p, UserProtocol p) => SProtocolType p -> [User] -> [(Text, ServerOperator)] -> IO (Map UserId (NonEmpty (ServerCfg p))) + getServers p users opDomains = do + let rs' = rndServers p rs + fmap M.fromList $ forM users $ \u -> + (aUserId u,) . agentServerCfgs opDomains rs' <$> getUpdateUserServers db p presetOps rs' u updateNetworkConfig :: NetworkConfig -> SimpleNetCfg -> NetworkConfig updateNetworkConfig cfg SimpleNetCfg {socksProxy, socksMode, hostMode, requiredHostMode, smpProxyMode_, smpProxyFallback_, smpWebPort, tcpTimeout_, logTLSErrors} = @@ -386,33 +463,37 @@ withFileLock :: String -> Int64 -> CM a -> CM a withFileLock name = withEntityLock name . CLFile {-# INLINE withFileLock #-} -useServers :: UserProtocol p => ChatConfig -> SProtocolType p -> [ServerCfg p] -> NonEmpty (ServerCfg p) -useServers ChatConfig {defaultServers} p = fromMaybe (cfgServers p defaultServers) . nonEmpty +serverCfg :: ProtoServerWithAuth p -> ServerCfg p +serverCfg server = ServerCfg {server, operator = Nothing, enabled = True, roles = allRoles} -randomServers :: forall p. UserProtocol p => SProtocolType p -> ChatConfig -> IO (NonEmpty (ServerCfg p), [ServerCfg p]) -randomServers p ChatConfig {defaultServers} = do - let srvs = cfgServers p defaultServers - (enbldSrvs, dsbldSrvs) = L.partition (\ServerCfg {enabled} -> enabled) srvs - toUse = cfgServersToUse p defaultServers - if length enbldSrvs <= toUse - then pure (srvs, []) - else do - (enbldSrvs', srvsToDisable) <- splitAt toUse <$> shuffle enbldSrvs - let dsbldSrvs' = map (\srv -> (srv :: ServerCfg p) {enabled = False}) srvsToDisable - srvs' = sortOn server' $ enbldSrvs' <> dsbldSrvs' <> dsbldSrvs - pure (fromMaybe srvs $ L.nonEmpty srvs', srvs') +useServers :: forall p. UserProtocol p => SProtocolType p -> RandomServers -> [UserServer p] -> NonEmpty (NewUserServer p) +useServers p rs servers = case L.nonEmpty servers of + Nothing -> rndServers p rs + Just srvs -> L.map (\srv -> (srv :: UserServer p) {serverId = DBNewEntity}) srvs + +rndServers :: UserProtocol p => SProtocolType p -> RandomServers -> NonEmpty (NewUserServer p) +rndServers p RandomServers {smpServers, xftpServers} = case p of + SPSMP -> smpServers + SPXFTP -> xftpServers + +randomPresetServers :: forall p. UserProtocol p => SProtocolType p -> PresetServers -> IO (NonEmpty (NewUserServer p)) +randomPresetServers p PresetServers {operators} = toJust . L.nonEmpty . concat =<< mapM opSrvs operators where - server' ServerCfg {server = ProtoServerWithAuth srv _} = srv - -cfgServers :: UserProtocol p => SProtocolType p -> DefaultAgentServers -> NonEmpty (ServerCfg p) -cfgServers p DefaultAgentServers {smp, xftp} = case p of - SPSMP -> smp - SPXFTP -> xftp - -cfgServersToUse :: UserProtocol p => SProtocolType p -> DefaultAgentServers -> Int -cfgServersToUse p DefaultAgentServers {useSMP, useXFTP} = case p of - SPSMP -> useSMP - SPXFTP -> useXFTP + toJust = \case + Just a -> pure a + Nothing -> E.throwIO $ userError "no preset servers" + opSrvs :: PresetOperator -> IO [NewUserServer p] + opSrvs op = do + let srvs = operatorServers p op + toUse = operatorServersToUse p op + (enbldSrvs, dsbldSrvs) = partition (\UserServer {enabled} -> enabled) srvs + if toUse <= 0 || toUse >= length enbldSrvs + then pure srvs + else do + (enbldSrvs', srvsToDisable) <- splitAt toUse <$> shuffle enbldSrvs + let dsbldSrvs' = map (\srv -> (srv :: NewUserServer p) {enabled = False}) srvsToDisable + pure $ sortOn server' $ enbldSrvs' <> dsbldSrvs' <> dsbldSrvs + server' UserServer {server = ProtoServerWithAuth srv _} = srv -- enableSndFiles has no effect when mainApp is True startChatController :: Bool -> Bool -> CM' (Async ()) @@ -556,19 +637,24 @@ processChatCommand' vr = \case forM_ profile $ \Profile {displayName} -> checkValidName displayName p@Profile {displayName} <- liftIO $ maybe generateRandomProfile pure profile u <- asks currentUser - (smp, smpServers) <- chooseServers SPSMP - (xftp, xftpServers) <- chooseServers SPXFTP + smpServers <- chooseServers SPSMP + xftpServers <- chooseServers SPXFTP users <- withFastStore' getUsers forM_ users $ \User {localDisplayName = n, activeUser, viewPwdHash} -> when (n == displayName) . throwChatError $ if activeUser || isNothing viewPwdHash then CEUserExists displayName else CEInvalidDisplayName {displayName, validName = ""} + opDomains <- operatorDomains . fst <$> withFastStore getServerOperators + rs <- asks randomServers + let smp = agentServerCfgs opDomains (rndServers SPSMP rs) smpServers + xftp = agentServerCfgs opDomains (rndServers SPXFTP rs) xftpServers auId <- withAgent (\a -> createUser a smp xftp) ts <- liftIO $ getCurrentTime >>= if pastTimestamp then coupleDaysAgo else pure user <- withFastStore $ \db -> createUserRecordAt db (AgentUserId auId) p True ts createPresetContactCards user `catchChatError` \_ -> pure () - withFastStore $ \db -> createNoteFolder db user - storeServers user smpServers - storeServers user xftpServers + withFastStore $ \db -> do + createNoteFolder db user + liftIO $ mapM_ (insertProtocolServer db SPSMP user ts) $ useServers SPSMP rs smpServers + liftIO $ mapM_ (insertProtocolServer db SPXFTP user ts) $ useServers SPXFTP rs xftpServers atomically . writeTVar u $ Just user pure $ CRActiveUser user where @@ -577,18 +663,10 @@ processChatCommand' vr = \case withFastStore $ \db -> do createContact db user simplexStatusContactProfile createContact db user simplexTeamContactProfile - chooseServers :: (ProtocolTypeI p, UserProtocol p) => SProtocolType p -> CM (NonEmpty (ServerCfg p), [ServerCfg p]) - chooseServers protocol = - asks currentUser >>= readTVarIO >>= \case - Nothing -> asks config >>= liftIO . randomServers protocol - Just user -> chosenServers =<< withFastStore' (`getProtocolServers` user) - where - chosenServers servers = do - cfg <- asks config - pure (useServers cfg protocol servers, servers) - storeServers user servers = - unless (null servers) . withFastStore $ - \db -> overwriteProtocolServers db user servers + chooseServers :: forall p. ProtocolTypeI p => SProtocolType p -> CM [UserServer p] + chooseServers p = do + srvs <- chatReadVar currentUser >>= mapM (\user -> withFastStore' $ \db -> getProtocolServers db p user) + pure $ fromMaybe [] srvs coupleDaysAgo t = (`addUTCTime` t) . fromInteger . negate . (+ (2 * day)) <$> randomRIO (0, day) day = 86400 ListUsers -> CRUsersList <$> withFastStore' getUsersInfo @@ -1486,57 +1564,67 @@ processChatCommand' vr = \case msgs <- lift $ withAgent' $ \a -> getConnectionMessages a acIds let ntfMsgs = L.map (\msg -> receivedMsgInfo <$> msg) msgs pure $ CRConnNtfMessages ntfMsgs - APIGetUserProtoServers userId (AProtocolType p) -> withUserId userId $ \user -> withServerProtocol p $ do - cfg@ChatConfig {defaultServers} <- asks config - srvs <- withFastStore' (`getProtocolServers` user) - (operators, _) <- withFastStore $ \db -> getServerOperators db - let servers = AUPS $ UserProtoServers p (useServers cfg p srvs) (cfgServers p defaultServers) - pure $ CRUserProtoServers {user, servers, operators} - GetUserProtoServers aProtocol -> withUser $ \User {userId} -> - processChatCommand $ APIGetUserProtoServers userId aProtocol - APISetUserProtoServers userId (APSC p (ProtoServersConfig servers)) - | null servers || any (\ServerCfg {enabled} -> enabled) servers -> withUserId userId $ \user -> withServerProtocol p $ do - withFastStore $ \db -> overwriteProtocolServers db user servers - cfg <- asks config - lift $ withAgent' $ \a -> setProtocolServers a (aUserId user) $ useServers cfg p servers - ok user - | otherwise -> withUserId userId $ \user -> pure $ chatCmdError (Just user) "all servers are disabled" - SetUserProtoServers serversConfig -> withUser $ \User {userId} -> - processChatCommand $ APISetUserProtoServers userId serversConfig + GetUserProtoServers (AProtocolType p) -> withUser $ \user -> withServerProtocol p $ do + srvs <- withFastStore (`getUserServers` user) + CRUserServers user <$> liftIO (groupedServers srvs p) + where + groupedServers :: UserProtocol p => ([ServerOperator], [UserServer 'PSMP], [UserServer 'PXFTP]) -> SProtocolType p -> IO [UserOperatorServers] + groupedServers (operators, smpServers, xftpServers) = \case + SPSMP -> groupByOperator (operators, smpServers, []) + SPXFTP -> groupByOperator (operators, [], xftpServers) + SetUserProtoServers (AProtocolType (p :: SProtocolType p)) srvs -> withUser $ \user@User {userId} -> withServerProtocol p $ do + srvs' <- mapM aUserServer srvs + userServers_ <- liftIO . groupByOperator =<< withFastStore (`getUserServers` user) + case L.nonEmpty userServers_ of + Nothing -> throwChatError $ CECommandError "no servers" + Just userServers -> case srvs of + [] -> throwChatError $ CECommandError "no servers" + _ -> processChatCommand $ APISetUserServers userId $ L.map (updatedSrvs p) userServers + where + -- disable preset and replace custom servers (groupByOperator always adds custom) + updatedSrvs :: UserProtocol p => SProtocolType p -> UserOperatorServers -> UpdatedUserOperatorServers + updatedSrvs p' UserOperatorServers {operator, smpServers, xftpServers} = case p' of + SPSMP -> u (updateSrvs smpServers, map (AUS SDBStored) xftpServers) + SPXFTP -> u (map (AUS SDBStored) smpServers, updateSrvs xftpServers) + where + u = uncurry $ UpdatedUserOperatorServers operator + updateSrvs :: [UserServer p] -> [AUserServer p] + updateSrvs pSrvs = map disableSrv pSrvs <> maybe srvs' (const []) operator + disableSrv srv@UserServer {preset} = + AUS SDBStored $ if preset then srv {enabled = False} else srv {deleted = True} + where + aUserServer :: AProtoServerWithAuth -> CM (AUserServer p) + aUserServer (AProtoServerWithAuth p' srv) = case testEquality p p' of + Just Refl -> pure $ AUS SDBNew $ newUserServer srv + Nothing -> throwChatError $ CECommandError $ "incorrect server protocol: " <> B.unpack (strEncode srv) APITestProtoServer userId srv@(AProtoServerWithAuth _ server) -> withUserId userId $ \user -> lift $ CRServerTestResult user srv <$> withAgent' (\a -> testProtocolServer a (aUserId user) server) TestProtoServer srv -> withUser $ \User {userId} -> processChatCommand $ APITestProtoServer userId srv - APIGetServerOperators -> do - (operators, conditionsAction) <- withFastStore $ \db -> getServerOperators db - pure $ CRServerOperators operators conditionsAction - APISetServerOperators operatorsEnabled -> do - (operators, conditionsAction) <- withFastStore $ \db -> setServerOperators db operatorsEnabled - pure $ CRServerOperators operators conditionsAction - APIGetUserServers userId -> withUserId userId $ \user -> do - (operators, smpServers, xftpServers) <- withFastStore $ \db -> do - (operators, _) <- getServerOperators db - smpServers <- liftIO $ getServers db user SPSMP - xftpServers <- liftIO $ getServers db user SPXFTP - pure (operators, smpServers, xftpServers) - let userServers = groupByOperator operators smpServers xftpServers - pure $ CRUserServers user userServers - where - getServers :: ProtocolTypeI p => DB.Connection -> User -> SProtocolType p -> IO [ServerCfg p] - getServers db user _p = getProtocolServers db user + APIGetServerOperators -> uncurry CRServerOperators <$> withFastStore getServerOperators + APISetServerOperators operatorsEnabled -> withFastStore $ \db -> do + liftIO $ setServerOperators db operatorsEnabled + uncurry CRServerOperators <$> getServerOperators db + APIGetUserServers userId -> withUserId userId $ \user -> withFastStore $ \db -> + CRUserServers user <$> (liftIO . groupByOperator =<< getUserServers db user) APISetUserServers userId userServers -> withUserId userId $ \user -> do let errors = validateUserServers userServers unless (null errors) $ throwChatError (CECommandError $ "user servers validation error(s): " <> show errors) - withFastStore $ \db -> setUserServers db user userServers - -- TODO set protocol servers for agent + (operators, smpServers, xftpServers) <- withFastStore $ \db -> do + setUserServers db user userServers + getUserServers db user + let opDomains = operatorDomains operators + rs <- asks randomServers + lift $ withAgent' $ \a -> do + let auId = aUserId user + setProtocolServers a auId $ agentServerCfgs opDomains (rndServers SPSMP rs) smpServers + setProtocolServers a auId $ agentServerCfgs opDomains (rndServers SPXFTP rs) xftpServers ok_ - APIValidateServers userServers -> do - let errors = validateUserServers userServers - pure $ CRUserServersValidation errors + APIValidateServers userServers -> pure $ CRUserServersValidation $ validateUserServers userServers APIGetUsageConditions -> do (usageConditions, acceptedConditions) <- withFastStore $ \db -> do usageConditions <- getCurrentUsageConditions db - acceptedConditions <- getLatestAcceptedConditions db + acceptedConditions <- liftIO $ getLatestAcceptedConditions db pure (usageConditions, acceptedConditions) -- TODO if db commit is different from source commit, conditionsText should be nothing in response pure @@ -1545,14 +1633,14 @@ processChatCommand' vr = \case conditionsText = usageConditionsText, acceptedConditions } - APISetConditionsNotified conditionsId -> do + APISetConditionsNotified condId -> do currentTs <- liftIO getCurrentTime - withFastStore' $ \db -> setConditionsNotified db conditionsId currentTs + withFastStore' $ \db -> setConditionsNotified db condId currentTs ok_ - APIAcceptConditions conditionsId operators -> do + APIAcceptConditions condId opIds -> withFastStore $ \db -> do currentTs <- liftIO getCurrentTime - (operators', conditionsAction) <- withFastStore $ \db -> acceptConditions db conditionsId operators currentTs - pure $ CRServerOperators operators' conditionsAction + acceptConditions db condId opIds currentTs + uncurry CRServerOperators <$> getServerOperators db APISetChatItemTTL userId newTTL_ -> withUserId userId $ \user -> checkStoreNotChanged $ withChatLock "setChatItemTTL" $ do @@ -1805,8 +1893,9 @@ processChatCommand' vr = \case canKeepLink (CRInvitationUri crData _) newUser = do let ConnReqUriData {crSmpQueues = q :| _} = crData SMPQueueUri {queueAddress = SMPQueueAddress {smpServer}} = q - cfg <- asks config - newUserServers <- L.map (\ServerCfg {server} -> protoServer server) . useServers cfg SPSMP <$> withFastStore' (`getProtocolServers` newUser) + newUserServers <- + map protoServer' . filter (\ServerCfg {enabled} -> enabled) + <$> getKnownAgentServers SPSMP newUser pure $ smpServer `elem` newUserServers updateConnRecord user@User {userId} conn@PendingContactConnection {customUserProfileId} newUser = do withAgent $ \a -> changeConnectionUser a (aUserId user) (aConnId' conn) (aUserId newUser) @@ -2140,7 +2229,7 @@ processChatCommand' vr = \case where changeMemberRole user gInfo members m gEvent = do let GroupMember {memberId = mId, memberRole = mRole, memberStatus = mStatus, memberContactId, localDisplayName = cName} = m - assertUserGroupRole gInfo $ maximum [GRAdmin, mRole, memRole] + assertUserGroupRole gInfo $ maximum ([GRAdmin, mRole, memRole] :: [GroupMemberRole]) withGroupLock "memberRole" groupId . procCmd $ do unless (mRole == memRole) $ do withFastStore' $ \db -> updateGroupMemberRole db user m memRole @@ -2538,14 +2627,15 @@ processChatCommand' vr = \case pure $ CRAgentSubsTotal user subsTotal hasSession GetAgentServersSummary userId -> withUserId userId $ \user -> do agentServersSummary <- lift $ withAgent' getAgentServersSummary - cfg <- asks config - (users, smpServers, xftpServers) <- - withStore' $ \db -> (,,) <$> getUsers db <*> getServers db cfg user SPSMP <*> getServers db cfg user SPXFTP - let presentedServersSummary = toPresentedServersSummary agentServersSummary users user smpServers xftpServers _defaultNtfServers - pure $ CRAgentServersSummary user presentedServersSummary + withStore' $ \db -> do + users <- getUsers db + smpServers <- getServers db user SPSMP + xftpServers <- getServers db user SPXFTP + let presentedServersSummary = toPresentedServersSummary agentServersSummary users user smpServers xftpServers _defaultNtfServers + pure $ CRAgentServersSummary user presentedServersSummary where - getServers :: (ProtocolTypeI p, UserProtocol p) => DB.Connection -> ChatConfig -> User -> SProtocolType p -> IO (NonEmpty (ProtocolServer p)) - getServers db cfg user p = L.map (\ServerCfg {server} -> protoServer server) . useServers cfg p <$> getProtocolServers db user + getServers :: ProtocolTypeI p => DB.Connection -> User -> SProtocolType p -> IO [ProtocolServer p] + getServers db user p = map (\UserServer {server} -> protoServer server) <$> getProtocolServers db p user ResetAgentServersStats -> withAgent resetAgentServersStats >> ok_ GetAgentWorkers -> lift $ CRAgentWorkersSummary <$> withAgent' getAgentWorkersSummary GetAgentWorkersDetails -> lift $ CRAgentWorkersDetails <$> withAgent' getAgentWorkersDetails @@ -3663,8 +3753,7 @@ receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} S.toList $ S.fromList $ concatMap (\FD.FileChunk {replicas} -> map (\FD.FileChunkReplica {server} -> server) replicas) chunks getUnknownSrvs :: [XFTPServer] -> CM [XFTPServer] getUnknownSrvs srvs = do - cfg <- asks config - knownSrvs <- L.map (\ServerCfg {server} -> protoServer server) . useServers cfg SPXFTP <$> withStore' (`getProtocolServers` user) + knownSrvs <- map protoServer' <$> getKnownAgentServers SPXFTP user pure $ filter (`notElem` knownSrvs) srvs ipProtectedForSrvs :: [XFTPServer] -> CM Bool ipProtectedForSrvs srvs = do @@ -3678,6 +3767,17 @@ receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} toView $ CRChatItemUpdated user aci throwChatError $ CEFileNotApproved fileId unknownSrvs +getKnownAgentServers :: (ProtocolTypeI p, UserProtocol p) => SProtocolType p -> User -> CM [ServerCfg p] +getKnownAgentServers p user = do + rs <- asks randomServers + withStore $ \db -> do + opDomains <- operatorDomains . fst <$> getServerOperators db + srvs <- liftIO $ getProtocolServers db p user + pure $ L.toList $ agentServerCfgs opDomains (rndServers p rs) srvs + +protoServer' :: ServerCfg p -> ProtocolServer p +protoServer' ServerCfg {server} = protoServer server + getNetworkConfig :: CM' NetworkConfig getNetworkConfig = withAgent' $ liftIO . getFastNetworkConfig @@ -3876,7 +3976,7 @@ subscribeUserConnections vr onlyNeeded agentBatchSubscribe user = do (sftConns, sfts) <- getSndFileTransferConns (rftConns, rfts) <- getRcvFileTransferConns (pcConns, pcs) <- getPendingContactConns - let conns = concat [ctConns, ucConns, mConns, sftConns, rftConns, pcConns] + let conns = concat ([ctConns, ucConns, mConns, sftConns, rftConns, pcConns] :: [[ConnId]]) pure (conns, cts, ucs, gs, ms, sfts, rfts, pcs) -- subscribe using batched commands rs <- withAgent $ \a -> agentBatchSubscribe a conns @@ -4684,7 +4784,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = ctItem = AChatItem SCTDirect SMDSnd (DirectChat ct) SWITCH qd phase cStats -> do toView $ CRContactSwitch user ct (SwitchProgress qd phase cStats) - when (phase `elem` [SPStarted, SPCompleted]) $ case qd of + when (phase == SPStarted || phase == SPCompleted) $ case qd of QDRcv -> createInternalChatItem user (CDDirectSnd ct) (CISndConnEvent $ SCESwitchQueue phase Nothing) Nothing QDSnd -> createInternalChatItem user (CDDirectRcv ct) (CIRcvConnEvent $ RCESwitchQueue phase) Nothing RSYNC rss cryptoErr_ cStats -> @@ -4969,7 +5069,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = (Just fileDescrText, Just msgId) -> do partSize <- asks $ xftpDescrPartSize . config let parts = splitFileDescr partSize fileDescrText - pure . toList $ L.map (XMsgFileDescr msgId) parts + pure . L.toList $ L.map (XMsgFileDescr msgId) parts _ -> pure [] let fileDescrChatMsgs = map (ChatMessage senderVRange Nothing) fileDescrEvents GroupMember {memberId} = sender @@ -5095,7 +5195,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = when continued $ sendPendingGroupMessages user m conn SWITCH qd phase cStats -> do toView $ CRGroupMemberSwitch user gInfo m (SwitchProgress qd phase cStats) - when (phase `elem` [SPStarted, SPCompleted]) $ case qd of + when (phase == SPStarted || phase == SPCompleted) $ case qd of QDRcv -> createInternalChatItem user (CDGroupSnd gInfo) (CISndConnEvent . SCESwitchQueue phase . Just $ groupMemberRef m) Nothing QDSnd -> createInternalChatItem user (CDGroupRcv gInfo m) (CIRcvConnEvent $ RCESwitchQueue phase) Nothing RSYNC rss cryptoErr_ cStats -> @@ -6659,15 +6759,17 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = messageWarning "x.grp.mem.con: neither member is invitee" where inviteeXGrpMemCon :: GroupMemberIntro -> CM () - inviteeXGrpMemCon GroupMemberIntro {introId, introStatus} - | introStatus == GMIntroReConnected = updateStatus introId GMIntroConnected - | introStatus `elem` [GMIntroToConnected, GMIntroConnected] = pure () - | otherwise = updateStatus introId GMIntroToConnected + inviteeXGrpMemCon GroupMemberIntro {introId, introStatus} = case introStatus of + GMIntroReConnected -> updateStatus introId GMIntroConnected + GMIntroToConnected -> pure () + GMIntroConnected -> pure () + _ -> updateStatus introId GMIntroToConnected forwardMemberXGrpMemCon :: GroupMemberIntro -> CM () - forwardMemberXGrpMemCon GroupMemberIntro {introId, introStatus} - | introStatus == GMIntroToConnected = updateStatus introId GMIntroConnected - | introStatus `elem` [GMIntroReConnected, GMIntroConnected] = pure () - | otherwise = updateStatus introId GMIntroReConnected + forwardMemberXGrpMemCon GroupMemberIntro {introId, introStatus} = case introStatus of + GMIntroToConnected -> updateStatus introId GMIntroConnected + GMIntroReConnected -> pure () + GMIntroConnected -> pure () + _ -> updateStatus introId GMIntroReConnected updateStatus introId status = withStore' $ \db -> updateIntroStatus db introId status xGrpMemDel :: GroupInfo -> GroupMember -> MemberId -> RcvMessage -> UTCTime -> CM () @@ -8132,22 +8234,18 @@ chatCommandP = "/smp test " *> (TestProtoServer . AProtoServerWithAuth SPSMP <$> strP), "/xftp test " *> (TestProtoServer . AProtoServerWithAuth SPXFTP <$> strP), "/ntf test " *> (TestProtoServer . AProtoServerWithAuth SPNTF <$> strP), - "/_servers " *> (APISetUserProtoServers <$> A.decimal <* A.space <*> srvCfgP), - "/smp " *> (SetUserProtoServers . APSC SPSMP . ProtoServersConfig . map enabledServerCfg <$> protocolServersP), - "/smp default" $> SetUserProtoServers (APSC SPSMP $ ProtoServersConfig []), - "/xftp " *> (SetUserProtoServers . APSC SPXFTP . ProtoServersConfig . map enabledServerCfg <$> protocolServersP), - "/xftp default" $> SetUserProtoServers (APSC SPXFTP $ ProtoServersConfig []), - "/_servers " *> (APIGetUserProtoServers <$> A.decimal <* A.space <*> strP), + "/smp " *> (SetUserProtoServers (AProtocolType SPSMP) . map (AProtoServerWithAuth SPSMP) <$> protocolServersP), + "/xftp " *> (SetUserProtoServers (AProtocolType SPXFTP) . map (AProtoServerWithAuth SPXFTP) <$> protocolServersP), "/smp" $> GetUserProtoServers (AProtocolType SPSMP), "/xftp" $> GetUserProtoServers (AProtocolType SPXFTP), "/_operators" $> APIGetServerOperators, "/_operators " *> (APISetServerOperators <$> jsonP), - "/_user_servers " *> (APIGetUserServers <$> A.decimal), - "/_user_servers " *> (APISetUserServers <$> A.decimal <* A.space <*> jsonP), + "/_servers " *> (APIGetUserServers <$> A.decimal), + "/_servers " *> (APISetUserServers <$> A.decimal <* A.space <*> jsonP), "/_validate_servers " *> (APIValidateServers <$> jsonP), "/_conditions" $> APIGetUsageConditions, "/_conditions_notified " *> (APISetConditionsNotified <$> A.decimal), - "/_accept_conditions " *> (APIAcceptConditions <$> A.decimal <* A.space <*> jsonP), + "/_accept_conditions " *> (APIAcceptConditions <$> A.decimal <*> _strP), "/_ttl " *> (APISetChatItemTTL <$> A.decimal <* A.space <*> ciTTLDecimal), "/ttl " *> (SetChatItemTTL <$> ciTTL), "/_ttl " *> (APIGetChatItemTTL <$> A.decimal), @@ -8491,7 +8589,6 @@ chatCommandP = onOffP (Just <$> (AutoAccept <$> (" incognito=" *> onOffP <|> pure False) <*> optional (A.space *> msgContentP))) (pure Nothing) - srvCfgP = strP >>= \case AProtocolType p -> APSC p <$> (A.space *> jsonP) rcCtrlAddressP = RCCtrlAddress <$> ("addr=" *> strP) <*> (" iface=" *> (jsonP <|> text1P)) text1P = safeDecodeUtf8 <$> A.takeTill (== ' ') char_ = optional . A.char diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 8406e214c9..3c2b8045d7 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -35,7 +35,6 @@ import qualified Data.ByteArray as BA import Data.ByteString.Char8 (ByteString) import qualified Data.ByteString.Char8 as B import Data.Char (ord) -import Data.Constraint (Dict (..)) import Data.Int (Int64) import Data.List.NonEmpty (NonEmpty) import Data.Map.Strict (Map) @@ -71,7 +70,7 @@ import Simplex.Chat.Util (liftIOEither) import Simplex.FileTransfer.Description (FileDescriptionURI) import Simplex.Messaging.Agent (AgentClient, SubscriptionsInfo) import Simplex.Messaging.Agent.Client (AgentLocks, AgentQueuesInfo (..), AgentWorkersDetails (..), AgentWorkersSummary (..), ProtocolTestFailure, SMPServerSubs, ServerQueueInfo, UserNetworkInfo) -import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, NetworkConfig, ServerCfg) +import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, NetworkConfig) import Simplex.Messaging.Agent.Lock import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation, SQLiteStore, UpMigration, withTransaction, withTransactionPriority) @@ -85,7 +84,7 @@ import Simplex.Messaging.Crypto.Ratchet (PQEncryption) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Notifications.Protocol (DeviceToken (..), NtfTknStatus) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, parseAll, parseString, sumTypeJSON) -import Simplex.Messaging.Protocol (AProtoServerWithAuth, AProtocolType (..), CorrId, MsgId, NMsgMeta (..), NtfServer, ProtocolType (..), ProtocolTypeI, QueueId, SMPMsgMeta (..), SProtocolType, SubscriptionMode (..), UserProtocol, XFTPServer, userProtocol) +import Simplex.Messaging.Protocol (AProtoServerWithAuth, AProtocolType (..), CorrId, MsgId, NMsgMeta (..), NtfServer, ProtocolType (..), QueueId, SMPMsgMeta (..), SubscriptionMode (..), XFTPServer) import Simplex.Messaging.TMap (TMap) import Simplex.Messaging.Transport (TLS, simplexMQVersion) import Simplex.Messaging.Transport.Client (SocksProxyWithAuth, TransportHost) @@ -133,7 +132,7 @@ data ChatConfig = ChatConfig { agentConfig :: AgentConfig, chatVRange :: VersionRangeChat, confirmMigrations :: MigrationConfirmation, - defaultServers :: DefaultAgentServers, + presetServers :: PresetServers, tbqSize :: Natural, fileChunkSize :: Integer, xftpDescrPartSize :: Int, @@ -155,6 +154,12 @@ data ChatConfig = ChatConfig chatHooks :: ChatHooks } +data RandomServers = RandomServers + { smpServers :: NonEmpty (NewUserServer 'PSMP), + xftpServers :: NonEmpty (NewUserServer 'PXFTP) + } + deriving (Show) + -- The hooks can be used to extend or customize chat core in mobile or CLI clients. data ChatHooks = ChatHooks { -- preCmdHook can be used to process or modify the commands before they are processed. @@ -173,12 +178,9 @@ defaultChatHooks = eventHook = \_ -> pure } -data DefaultAgentServers = DefaultAgentServers - { smp :: NonEmpty (ServerCfg 'PSMP), - useSMP :: Int, +data PresetServers = PresetServers + { operators :: NonEmpty PresetOperator, ntf :: [NtfServer], - xftp :: NonEmpty (ServerCfg 'PXFTP), - useXFTP :: Int, netCfg :: NetworkConfig } @@ -204,6 +206,7 @@ data ChatDatabase = ChatDatabase {chatStore :: SQLiteStore, agentStore :: SQLite data ChatController = ChatController { currentUser :: TVar (Maybe User), + randomServers :: RandomServers, currentRemoteHost :: TVar (Maybe RemoteHostId), firstTime :: Bool, smpAgent :: AgentClient, @@ -347,20 +350,18 @@ data ChatCommand | APIGetGroupLink GroupId | APICreateMemberContact GroupId GroupMemberId | APISendMemberContactInvitation {contactId :: ContactId, msgContent_ :: Maybe MsgContent} - | APIGetUserProtoServers UserId AProtocolType | GetUserProtoServers AProtocolType - | APISetUserProtoServers UserId AProtoServersConfig - | SetUserProtoServers AProtoServersConfig + | SetUserProtoServers AProtocolType [AProtoServerWithAuth] | APITestProtoServer UserId AProtoServerWithAuth | TestProtoServer AProtoServerWithAuth | APIGetServerOperators - | APISetServerOperators (NonEmpty OperatorEnabled) + | APISetServerOperators (NonEmpty ServerOperator) | APIGetUserServers UserId - | APISetUserServers UserId (NonEmpty UserServers) - | APIValidateServers (NonEmpty UserServers) -- response is CRUserServersValidation + | APISetUserServers UserId (NonEmpty UpdatedUserOperatorServers) + | APIValidateServers (NonEmpty UpdatedUserOperatorServers) -- response is CRUserServersValidation | APIGetUsageConditions | APISetConditionsNotified Int64 - | APIAcceptConditions Int64 (NonEmpty ServerOperator) + | APIAcceptConditions Int64 (NonEmpty Int64) | APISetChatItemTTL UserId (Maybe Int64) | SetChatItemTTL (Maybe Int64) | APIGetChatItemTTL UserId @@ -586,10 +587,9 @@ data ChatResponse | CRChatItemInfo {user :: User, chatItem :: AChatItem, chatItemInfo :: ChatItemInfo} | CRChatItemId User (Maybe ChatItemId) | CRApiParsedMarkdown {formattedText :: Maybe MarkdownList} - | CRUserProtoServers {user :: User, servers :: AUserProtoServers, operators :: [ServerOperator]} | CRServerTestResult {user :: User, testServer :: AProtoServerWithAuth, testFailure :: Maybe ProtocolTestFailure} | CRServerOperators {operators :: [ServerOperator], conditionsAction :: Maybe UsageConditionsAction} - | CRUserServers {user :: User, userServers :: [UserServers]} + | CRUserServers {user :: User, userServers :: [UserOperatorServers]} | CRUserServersValidation {serverErrors :: [UserServersError]} | CRUsageConditions {usageConditions :: UsageConditions, conditionsText :: Text, acceptedConditions :: Maybe UsageConditions} | CRChatItemTTL {user :: User, chatItemTTL :: Maybe Int64} @@ -956,23 +956,23 @@ instance ToJSON AgentQueueId where toJSON = strToJSON toEncoding = strToJEncoding -data ProtoServersConfig p = ProtoServersConfig {servers :: [ServerCfg p]} - deriving (Show) +-- data ProtoServersConfig p = ProtoServersConfig {servers :: [ServerCfg p]} +-- deriving (Show) -data AProtoServersConfig = forall p. ProtocolTypeI p => APSC (SProtocolType p) (ProtoServersConfig p) +-- data AProtoServersConfig = forall p. ProtocolTypeI p => APSC (SProtocolType p) (ProtoServersConfig p) -deriving instance Show AProtoServersConfig +-- deriving instance Show AProtoServersConfig -data UserProtoServers p = UserProtoServers - { serverProtocol :: SProtocolType p, - protoServers :: NonEmpty (ServerCfg p), - presetServers :: NonEmpty (ServerCfg p) - } - deriving (Show) +-- data UserProtoServers p = UserProtoServers +-- { serverProtocol :: SProtocolType p, +-- protoServers :: NonEmpty (ServerCfg p), +-- presetServers :: NonEmpty (ServerCfg p) +-- } +-- deriving (Show) -data AUserProtoServers = forall p. (ProtocolTypeI p, UserProtocol p) => AUPS (UserProtoServers p) +-- data AUserProtoServers = forall p. (ProtocolTypeI p, UserProtocol p) => AUPS (UserProtoServers p) -deriving instance Show AUserProtoServers +-- deriving instance Show AUserProtoServers data ArchiveConfig = ArchiveConfig {archivePath :: FilePath, disableCompression :: Maybe Bool, parentTempDirectory :: Maybe FilePath} deriving (Show) @@ -1575,28 +1575,28 @@ $(JQ.deriveJSON defaultJSON ''CoreVersionInfo) $(JQ.deriveJSON defaultJSON ''SlowSQLQuery) -instance ProtocolTypeI p => FromJSON (ProtoServersConfig p) where - parseJSON = $(JQ.mkParseJSON defaultJSON ''ProtoServersConfig) +-- instance ProtocolTypeI p => FromJSON (ProtoServersConfig p) where +-- parseJSON = $(JQ.mkParseJSON defaultJSON ''ProtoServersConfig) -instance ProtocolTypeI p => FromJSON (UserProtoServers p) where - parseJSON = $(JQ.mkParseJSON defaultJSON ''UserProtoServers) +-- instance ProtocolTypeI p => FromJSON (UserProtoServers p) where +-- parseJSON = $(JQ.mkParseJSON defaultJSON ''UserProtoServers) -instance ProtocolTypeI p => ToJSON (UserProtoServers p) where - toJSON = $(JQ.mkToJSON defaultJSON ''UserProtoServers) - toEncoding = $(JQ.mkToEncoding defaultJSON ''UserProtoServers) +-- instance ProtocolTypeI p => ToJSON (UserProtoServers p) where +-- toJSON = $(JQ.mkToJSON defaultJSON ''UserProtoServers) +-- toEncoding = $(JQ.mkToEncoding defaultJSON ''UserProtoServers) -instance FromJSON AUserProtoServers where - parseJSON v = J.withObject "AUserProtoServers" parse v - where - parse o = do - AProtocolType (p :: SProtocolType p) <- o .: "serverProtocol" - case userProtocol p of - Just Dict -> AUPS <$> J.parseJSON @(UserProtoServers p) v - Nothing -> fail $ "AUserProtoServers: unsupported protocol " <> show p +-- instance FromJSON AUserProtoServers where +-- parseJSON v = J.withObject "AUserProtoServers" parse v +-- where +-- parse o = do +-- AProtocolType (p :: SProtocolType p) <- o .: "serverProtocol" +-- case userProtocol p of +-- Just Dict -> AUPS <$> J.parseJSON @(UserProtoServers p) v +-- Nothing -> fail $ "AUserProtoServers: unsupported protocol " <> show p -instance ToJSON AUserProtoServers where - toJSON (AUPS s) = $(JQ.mkToJSON defaultJSON ''UserProtoServers) s - toEncoding (AUPS s) = $(JQ.mkToEncoding defaultJSON ''UserProtoServers) s +-- instance ToJSON AUserProtoServers where +-- toJSON (AUPS s) = $(JQ.mkToJSON defaultJSON ''UserProtoServers) s +-- toEncoding (AUPS s) = $(JQ.mkToEncoding defaultJSON ''UserProtoServers) s $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "RCS") ''RemoteCtrlSessionState) diff --git a/src/Simplex/Chat/Migrations/M20241027_server_operators.hs b/src/Simplex/Chat/Migrations/M20241027_server_operators.hs index fc0ca21e54..d84cc5aa73 100644 --- a/src/Simplex/Chat/Migrations/M20241027_server_operators.hs +++ b/src/Simplex/Chat/Migrations/M20241027_server_operators.hs @@ -11,7 +11,6 @@ m20241027_server_operators = CREATE TABLE server_operators ( server_operator_id INTEGER PRIMARY KEY AUTOINCREMENT, server_operator_tag TEXT, - app_vendor INTEGER NOT NULL, trade_name TEXT NOT NULL, legal_name TEXT, server_domains TEXT, @@ -22,8 +21,6 @@ CREATE TABLE server_operators ( updated_at TEXT NOT NULL DEFAULT (datetime('now')) ); -ALTER TABLE protocol_servers ADD COLUMN server_operator_id INTEGER REFERENCES server_operators ON DELETE SET NULL; - CREATE TABLE usage_conditions ( usage_conditions_id INTEGER PRIMARY KEY AUTOINCREMENT, conditions_commit TEXT NOT NULL UNIQUE, @@ -41,18 +38,8 @@ CREATE TABLE operator_usage_conditions ( created_at TEXT NOT NULL DEFAULT (datetime('now')) ); -CREATE INDEX idx_protocol_servers_server_operator_id ON protocol_servers(server_operator_id); CREATE INDEX idx_operator_usage_conditions_server_operator_id ON operator_usage_conditions(server_operator_id); -CREATE UNIQUE INDEX idx_operator_usage_conditions_conditions_commit ON operator_usage_conditions(server_operator_id, conditions_commit); - -INSERT INTO server_operators - (server_operator_id, server_operator_tag, app_vendor, trade_name, legal_name, server_domains, enabled) - VALUES (1, 'simplex', 1, 'SimpleX Chat', 'SimpleX Chat Ltd', 'simplex.im', 1); -INSERT INTO server_operators - (server_operator_id, server_operator_tag, app_vendor, trade_name, legal_name, server_domains, enabled) - VALUES (2, 'xyz', 0, 'XYZ', 'XYZ Ltd', 'xyz.com', 0); - --- UPDATE protocol_servers SET server_operator_id = 1 WHERE host LIKE "%.simplex.im" OR host LIKE "%.simplex.im,%"; +CREATE UNIQUE INDEX idx_operator_usage_conditions_conditions_commit ON operator_usage_conditions(conditions_commit, server_operator_id); |] down_m20241027_server_operators :: Query @@ -60,9 +47,6 @@ down_m20241027_server_operators = [sql| DROP INDEX idx_operator_usage_conditions_conditions_commit; DROP INDEX idx_operator_usage_conditions_server_operator_id; -DROP INDEX idx_protocol_servers_server_operator_id; - -ALTER TABLE protocol_servers DROP COLUMN server_operator_id; DROP TABLE operator_usage_conditions; DROP TABLE usage_conditions; diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index 0eb7f66913..c037a60770 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -450,7 +450,6 @@ CREATE TABLE IF NOT EXISTS "protocol_servers"( created_at TEXT NOT NULL DEFAULT(datetime('now')), updated_at TEXT NOT NULL DEFAULT(datetime('now')), protocol TEXT NOT NULL DEFAULT 'smp', - server_operator_id INTEGER REFERENCES server_operators ON DELETE SET NULL, UNIQUE(user_id, host, port) ); CREATE TABLE xftp_file_descriptions( @@ -593,7 +592,6 @@ CREATE TABLE app_settings(app_settings TEXT NOT NULL); CREATE TABLE server_operators( server_operator_id INTEGER PRIMARY KEY AUTOINCREMENT, server_operator_tag TEXT, - app_vendor INTEGER NOT NULL, trade_name TEXT NOT NULL, legal_name TEXT, server_domains TEXT, @@ -919,13 +917,10 @@ CREATE INDEX idx_received_probes_group_member_id on received_probes( group_member_id ); CREATE INDEX idx_contact_requests_contact_id ON contact_requests(contact_id); -CREATE INDEX idx_protocol_servers_server_operator_id ON protocol_servers( - server_operator_id -); CREATE INDEX idx_operator_usage_conditions_server_operator_id ON operator_usage_conditions( server_operator_id ); CREATE UNIQUE INDEX idx_operator_usage_conditions_conditions_commit ON operator_usage_conditions( - server_operator_id, - conditions_commit + conditions_commit, + server_operator_id ); diff --git a/src/Simplex/Chat/Operators.hs b/src/Simplex/Chat/Operators.hs index b3f92caaf9..55de357090 100644 --- a/src/Simplex/Chat/Operators.hs +++ b/src/Simplex/Chat/Operators.hs @@ -1,24 +1,42 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE KindSignatures #-} {-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeApplications #-} +{-# OPTIONS_GHC -fno-warn-ambiguous-fields #-} module Simplex.Chat.Operators where +import Control.Applicative ((<|>)) import Data.Aeson (FromJSON (..), ToJSON (..)) import qualified Data.Aeson as J import qualified Data.Aeson.Encoding as JE import qualified Data.Aeson.TH as JQ import Data.FileEmbed +import Data.Foldable (foldMap') +import Data.IORef import Data.Int (Int64) +import Data.List (find, foldl') import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as L import Data.Map.Strict (Map) import qualified Data.Map.Strict as M -import Data.Maybe (fromMaybe, isNothing) +import Data.Maybe (fromMaybe, isNothing, mapMaybe) +import Data.Scientific (floatingOrInteger) +import Data.Set (Set) +import qualified Data.Set as S import Data.Text (Text) +import qualified Data.Text as T import Data.Time (addUTCTime) import Data.Time.Clock (UTCTime, nominalDay) import Database.SQLite.Simple.FromField (FromField (..)) @@ -26,23 +44,51 @@ import Database.SQLite.Simple.ToField (ToField (..)) import Language.Haskell.TH.Syntax (lift) import Simplex.Chat.Operators.Conditions import Simplex.Chat.Types.Util (textParseJSON) -import Simplex.Messaging.Agent.Env.SQLite (OperatorId, ServerCfg (..), ServerRoles (..)) +import Simplex.Messaging.Agent.Env.SQLite (ServerCfg (..), ServerRoles (..), allRoles) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, fromTextField_, sumTypeJSON) -import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), ProtoServerWithAuth (..), ProtocolServer (..), ProtocolType (..), SProtocolType (..)) -import Simplex.Messaging.Util (safeDecodeUtf8) +import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), ProtoServerWithAuth (..), ProtocolServer (..), ProtocolType (..), ProtocolTypeI, SProtocolType (..), UserProtocol) +import Simplex.Messaging.Transport.Client (TransportHost (..)) +import Simplex.Messaging.Util (atomicModifyIORef'_, safeDecodeUtf8) usageConditionsCommit :: Text -usageConditionsCommit = "165143a1112308c035ac00ed669b96b60599aa1c" +usageConditionsCommit = "a5061f3147165a05979d6ace33960aced2d6ac03" + +previousConditionsCommit :: Text +previousConditionsCommit = "11a44dc1fd461a93079f897048b46998db55da5c" usageConditionsText :: Text usageConditionsText = $( let s = $(embedFile =<< makeRelativeToProject "PRIVACY.md") - in [|stripFrontMatter (safeDecodeUtf8 $(lift s))|] + in [|stripFrontMatter $(lift (safeDecodeUtf8 s))|] ) -data OperatorTag = OTSimplex | OTXyz - deriving (Show) +data DBStored = DBStored | DBNew + +data SDBStored (s :: DBStored) where + SDBStored :: SDBStored 'DBStored + SDBNew :: SDBStored 'DBNew + +deriving instance Show (SDBStored s) + +class DBStoredI s where sdbStored :: SDBStored s + +instance DBStoredI 'DBStored where sdbStored = SDBStored + +instance DBStoredI 'DBNew where sdbStored = SDBNew + +data DBEntityId' (s :: DBStored) where + DBEntityId :: Int64 -> DBEntityId' 'DBStored + DBNewEntity :: DBEntityId' 'DBNew + +deriving instance Show (DBEntityId' s) + +type DBEntityId = DBEntityId' 'DBStored + +type DBNewEntity = DBEntityId' 'DBNew + +data OperatorTag = OTSimplex | OTFlux + deriving (Eq, Ord, Show) instance FromField OperatorTag where fromField = fromTextField_ textDecode @@ -58,11 +104,17 @@ instance ToJSON OperatorTag where instance TextEncoding OperatorTag where textDecode = \case "simplex" -> Just OTSimplex - "xyz" -> Just OTXyz + "flux" -> Just OTFlux _ -> Nothing textEncode = \case OTSimplex -> "simplex" - OTXyz -> "xyz" + OTFlux -> "flux" + +-- this and other types only define instances of serialization for known DB IDs only, +-- entities without IDs cannot be serialized to JSON +instance FromField DBEntityId where fromField f = DBEntityId <$> fromField f + +instance ToField DBEntityId where toField (DBEntityId i) = toField i data UsageConditions = UsageConditions { conditionsId :: Int64, @@ -80,18 +132,16 @@ data UsageConditionsAction usageConditionsAction :: [ServerOperator] -> UsageConditions -> UTCTime -> Maybe UsageConditionsAction usageConditionsAction operators UsageConditions {createdAt, notifiedAt} now = do let enabledOperators = filter (\ServerOperator {enabled} -> enabled) operators - if null enabledOperators - then Nothing - else - if all conditionsAccepted enabledOperators - then - let acceptedForOperators = filter conditionsAccepted operators - in Just $ UCAAccepted acceptedForOperators - else - let acceptForOperators = filter (not . conditionsAccepted) enabledOperators - deadline = conditionsRequiredOrDeadline createdAt (fromMaybe now notifiedAt) - showNotice = isNothing notifiedAt - in Just $ UCAReview acceptForOperators deadline showNotice + if + | null enabledOperators -> Nothing + | all conditionsAccepted enabledOperators -> + let acceptedForOperators = filter conditionsAccepted operators + in Just $ UCAAccepted acceptedForOperators + | otherwise -> + let acceptForOperators = filter (not . conditionsAccepted) enabledOperators + deadline = conditionsRequiredOrDeadline createdAt (fromMaybe now notifiedAt) + showNotice = isNothing notifiedAt + in Just $ UCAReview acceptForOperators deadline showNotice conditionsRequiredOrDeadline :: UTCTime -> UTCTime -> Maybe UTCTime conditionsRequiredOrDeadline createdAt notifiedAtOrNow = @@ -107,8 +157,16 @@ data ConditionsAcceptance | CARequired {deadline :: Maybe UTCTime} deriving (Show) -data ServerOperator = ServerOperator - { operatorId :: OperatorId, +type ServerOperator = ServerOperator' 'DBStored + +type NewServerOperator = ServerOperator' 'DBNew + +data AServerOperator = forall s. ASO (SDBStored s) (ServerOperator' s) + +deriving instance Show AServerOperator + +data ServerOperator' s = ServerOperator + { operatorId :: DBEntityId' s, operatorTag :: Maybe OperatorTag, tradeName :: Text, legalName :: Maybe Text, @@ -124,81 +182,257 @@ conditionsAccepted ServerOperator {conditionsAcceptance} = case conditionsAccept CAAccepted {} -> True _ -> False -data OperatorEnabled = OperatorEnabled - { operatorId :: OperatorId, - enabled :: Bool, - roles :: ServerRoles - } - deriving (Show) - -data UserServers = UserServers +data UserOperatorServers = UserOperatorServers { operator :: Maybe ServerOperator, - smpServers :: [ServerCfg 'PSMP], - xftpServers :: [ServerCfg 'PXFTP] + smpServers :: [UserServer 'PSMP], + xftpServers :: [UserServer 'PXFTP] } deriving (Show) -groupByOperator :: [ServerOperator] -> [ServerCfg 'PSMP] -> [ServerCfg 'PXFTP] -> [UserServers] -groupByOperator srvOperators smpSrvs xftpSrvs = - map createOperatorServers (M.toList combinedMap) +data UpdatedUserOperatorServers = UpdatedUserOperatorServers + { operator :: Maybe ServerOperator, + smpServers :: [AUserServer 'PSMP], + xftpServers :: [AUserServer 'PXFTP] + } + deriving (Show) + +updatedServers :: UserProtocol p => UpdatedUserOperatorServers -> SProtocolType p -> [AUserServer p] +updatedServers UpdatedUserOperatorServers {smpServers, xftpServers} = \case + SPSMP -> smpServers + SPXFTP -> xftpServers + +type UserServer p = UserServer' 'DBStored p + +type NewUserServer p = UserServer' 'DBNew p + +data AUserServer p = forall s. AUS (SDBStored s) (UserServer' s p) + +deriving instance Show (AUserServer p) + +data UserServer' s p = UserServer + { serverId :: DBEntityId' s, + server :: ProtoServerWithAuth p, + preset :: Bool, + tested :: Maybe Bool, + enabled :: Bool, + deleted :: Bool + } + deriving (Show) + +data PresetOperator = PresetOperator + { operator :: Maybe NewServerOperator, + smp :: [NewUserServer 'PSMP], + useSMP :: Int, + xftp :: [NewUserServer 'PXFTP], + useXFTP :: Int + } + +operatorServers :: UserProtocol p => SProtocolType p -> PresetOperator -> [NewUserServer p] +operatorServers p PresetOperator {smp, xftp} = case p of + SPSMP -> smp + SPXFTP -> xftp + +operatorServersToUse :: UserProtocol p => SProtocolType p -> PresetOperator -> Int +operatorServersToUse p PresetOperator {useSMP, useXFTP} = case p of + SPSMP -> useSMP + SPXFTP -> useXFTP + +presetServer :: Bool -> ProtoServerWithAuth p -> NewUserServer p +presetServer = newUserServer_ True + +newUserServer :: ProtoServerWithAuth p -> NewUserServer p +newUserServer = newUserServer_ False True + +newUserServer_ :: Bool -> Bool -> ProtoServerWithAuth p -> NewUserServer p +newUserServer_ preset enabled server = + UserServer {serverId = DBNewEntity, server, preset, tested = Nothing, enabled, deleted = False} + +-- This function should be used inside DB transaction to update conditions in the database +-- it evaluates to (conditions to mark as accepted to SimpleX operator, current conditions, and conditions to add) +usageConditionsToAdd :: Bool -> UTCTime -> [UsageConditions] -> (Maybe UsageConditions, UsageConditions, [UsageConditions]) +usageConditionsToAdd = usageConditionsToAdd' previousConditionsCommit usageConditionsCommit + +-- This function is used in unit tests +usageConditionsToAdd' :: Text -> Text -> Bool -> UTCTime -> [UsageConditions] -> (Maybe UsageConditions, UsageConditions, [UsageConditions]) +usageConditionsToAdd' prevCommit sourceCommit newUser createdAt = \case + [] + | newUser -> (Just sourceCond, sourceCond, [sourceCond]) + | otherwise -> (Just prevCond, sourceCond, [prevCond, sourceCond]) + where + prevCond = conditions 1 prevCommit + sourceCond = conditions 2 sourceCommit + conds + | hasSourceCond -> (Nothing, last conds, []) + | otherwise -> (Nothing, sourceCond, [sourceCond]) + where + hasSourceCond = any ((sourceCommit ==) . conditionsCommit) conds + sourceCond = conditions cId sourceCommit + cId = maximum (map conditionsId conds) + 1 where - srvOperatorId ServerCfg {operator} = operator - opId ServerOperator {operatorId} = operatorId - operatorMap :: Map (Maybe Int64) (Maybe ServerOperator) - operatorMap = M.fromList [(Just (opId op), Just op) | op <- srvOperators] `M.union` M.singleton Nothing Nothing - initialMap :: Map (Maybe Int64) ([ServerCfg 'PSMP], [ServerCfg 'PXFTP]) - initialMap = M.fromList [(key, ([], [])) | key <- M.keys operatorMap] - smpsMap = foldr (\server acc -> M.adjust (\(smps, xftps) -> (server : smps, xftps)) (srvOperatorId server) acc) initialMap smpSrvs - combinedMap = foldr (\server acc -> M.adjust (\(smps, xftps) -> (smps, server : xftps)) (srvOperatorId server) acc) smpsMap xftpSrvs - createOperatorServers (key, (groupedSmps, groupedXftps)) = - UserServers - { operator = fromMaybe Nothing (M.lookup key operatorMap), - smpServers = groupedSmps, - xftpServers = groupedXftps - } + conditions cId commit = UsageConditions {conditionsId = cId, conditionsCommit = commit, notifiedAt = Nothing, createdAt} + +-- This function should be used inside DB transaction to update operators. +-- It allows to add/remove/update preset operators in the database preserving enabled and roles settings, +-- and preserves custom operators without tags for forward compatibility. +updatedServerOperators :: NonEmpty PresetOperator -> [ServerOperator] -> [AServerOperator] +updatedServerOperators presetOps storedOps = + foldr addPreset [] presetOps + <> map (ASO SDBStored) (filter (isNothing . operatorTag) storedOps) + where + -- TODO remove domains of preset operators from custom + addPreset PresetOperator {operator} = case operator of + Nothing -> id + Just presetOp -> (storedOp' :) + where + storedOp' = case find ((operatorTag presetOp ==) . operatorTag) storedOps of + Just ServerOperator {operatorId, conditionsAcceptance, enabled, roles} -> + ASO SDBStored presetOp {operatorId, conditionsAcceptance, enabled, roles} + Nothing -> ASO SDBNew presetOp + +-- This function should be used inside DB transaction to update servers. +updatedUserServers :: forall p. UserProtocol p => SProtocolType p -> NonEmpty PresetOperator -> NonEmpty (NewUserServer p) -> [UserServer p] -> NonEmpty (AUserServer p) +updatedUserServers _ _ randomSrvs [] = L.map (AUS SDBNew) randomSrvs +updatedUserServers p presetOps randomSrvs srvs = + fromMaybe (L.map (AUS SDBNew) randomSrvs) (L.nonEmpty updatedSrvs) + where + updatedSrvs = map userServer presetSrvs <> map (AUS SDBStored) (filter customServer srvs) + storedSrvs :: Map (ProtoServerWithAuth p) (UserServer p) + storedSrvs = foldl' (\ss srv@UserServer {server} -> M.insert server srv ss) M.empty srvs + customServer :: UserServer p -> Bool + customServer srv = not (preset srv) && all (`S.notMember` presetHosts) (srvHost srv) + presetSrvs :: [NewUserServer p] + presetSrvs = concatMap (operatorServers p) presetOps + presetHosts :: Set TransportHost + presetHosts = foldMap' (S.fromList . L.toList . srvHost) presetSrvs + userServer :: NewUserServer p -> AUserServer p + userServer srv@UserServer {server} = maybe (AUS SDBNew srv) (AUS SDBStored) (M.lookup server storedSrvs) + +srvHost :: UserServer' s p -> NonEmpty TransportHost +srvHost UserServer {server = ProtoServerWithAuth srv _} = host srv + +agentServerCfgs :: [(Text, ServerOperator)] -> NonEmpty (NewUserServer p) -> [UserServer' s p] -> NonEmpty (ServerCfg p) +agentServerCfgs opDomains randomSrvs = + fromMaybe fallbackSrvs . L.nonEmpty . mapMaybe enabledOpAgentServer + where + fallbackSrvs = L.map (snd . agentServer) randomSrvs + enabledOpAgentServer srv = + let (opEnabled, srvCfg) = agentServer srv + in if opEnabled then Just srvCfg else Nothing + agentServer :: UserServer' s p -> (Bool, ServerCfg p) + agentServer srv@UserServer {server, enabled} = + case find (\(d, _) -> any (matchingHost d) (srvHost srv)) opDomains of + Just (_, ServerOperator {operatorId = DBEntityId opId, enabled = opEnabled, roles}) -> + (opEnabled, ServerCfg {server, enabled, operator = Just opId, roles}) + Nothing -> + (True, ServerCfg {server, enabled, operator = Nothing, roles = allRoles}) + +matchingHost :: Text -> TransportHost -> Bool +matchingHost d = \case + THDomainName h -> d `T.isSuffixOf` T.pack h + _ -> False + +operatorDomains :: [ServerOperator] -> [(Text, ServerOperator)] +operatorDomains = foldr (\op ds -> foldr (\d -> ((d, op) :)) ds (serverDomains op)) [] + +groupByOperator :: ([ServerOperator], [UserServer 'PSMP], [UserServer 'PXFTP]) -> IO [UserOperatorServers] +groupByOperator (ops, smpSrvs, xftpSrvs) = do + ss <- mapM (\op -> (serverDomains op,) <$> newIORef (UserOperatorServers (Just op) [] [])) ops + custom <- newIORef $ UserOperatorServers Nothing [] [] + mapM_ (addServer ss custom addSMP) (reverse smpSrvs) + mapM_ (addServer ss custom addXFTP) (reverse xftpSrvs) + opSrvs <- mapM (readIORef . snd) ss + customSrvs <- readIORef custom + pure $ opSrvs <> [customSrvs] + where + addServer :: [([Text], IORef UserOperatorServers)] -> IORef UserOperatorServers -> (UserServer p -> UserOperatorServers -> UserOperatorServers) -> UserServer p -> IO () + addServer ss custom add srv = + let v = maybe custom snd $ find (\(ds, _) -> any (\d -> any (matchingHost d) (srvHost srv)) ds) ss + in atomicModifyIORef'_ v $ add srv + addSMP srv s@UserOperatorServers {smpServers} = (s :: UserOperatorServers) {smpServers = srv : smpServers} + addXFTP srv s@UserOperatorServers {xftpServers} = (s :: UserOperatorServers) {xftpServers = srv : xftpServers} data UserServersError - = USEStorageMissing - | USEProxyMissing - | USEDuplicateSMP {server :: AProtoServerWithAuth} - | USEDuplicateXFTP {server :: AProtoServerWithAuth} + = USEStorageMissing {protocol :: AProtocolType} + | USEProxyMissing {protocol :: AProtocolType} + | USEDuplicateServer {protocol :: AProtocolType, duplicateServer :: AProtoServerWithAuth, duplicateHost :: TransportHost} deriving (Show) -validateUserServers :: NonEmpty UserServers -> [UserServersError] -validateUserServers userServers = - let storageMissing_ = if any (canUseForRole storage) userServers then [] else [USEStorageMissing] - proxyMissing_ = if any (canUseForRole proxy) userServers then [] else [USEProxyMissing] - - allSMPServers = map (\ServerCfg {server} -> server) $ concatMap (\UserServers {smpServers} -> smpServers) userServers - duplicateSMPServers = findDuplicatesByHost allSMPServers - duplicateSMPErrors = map (USEDuplicateSMP . AProtoServerWithAuth SPSMP) duplicateSMPServers - - allXFTPServers = map (\ServerCfg {server} -> server) $ concatMap (\UserServers {xftpServers} -> xftpServers) userServers - duplicateXFTPServers = findDuplicatesByHost allXFTPServers - duplicateXFTPErrors = map (USEDuplicateXFTP . AProtoServerWithAuth SPXFTP) duplicateXFTPServers - in storageMissing_ <> proxyMissing_ <> duplicateSMPErrors <> duplicateXFTPErrors +validateUserServers :: NonEmpty UpdatedUserOperatorServers -> [UserServersError] +validateUserServers uss = + missingRolesErr SPSMP storage USEStorageMissing + <> missingRolesErr SPSMP proxy USEProxyMissing + <> missingRolesErr SPXFTP storage USEStorageMissing + <> duplicatServerErrs SPSMP + <> duplicatServerErrs SPXFTP where - canUseForRole :: (ServerRoles -> Bool) -> UserServers -> Bool - canUseForRole roleSel UserServers {operator, smpServers, xftpServers} = case operator of - Just ServerOperator {roles} -> roleSel roles - Nothing -> not (null smpServers) && not (null xftpServers) - findDuplicatesByHost :: [ProtoServerWithAuth p] -> [ProtoServerWithAuth p] - findDuplicatesByHost servers = - let allHosts = concatMap (L.toList . host . protoServer) servers - hostCounts = M.fromListWith (+) [(host, 1 :: Int) | host <- allHosts] - duplicateHosts = M.keys $ M.filter (> 1) hostCounts - in filter (\srv -> any (`elem` duplicateHosts) (L.toList $ host . protoServer $ srv)) servers + missingRolesErr :: (ProtocolTypeI p, UserProtocol p) => SProtocolType p -> (ServerRoles -> Bool) -> (AProtocolType -> UserServersError) -> [UserServersError] + missingRolesErr p roleSel err = [err (AProtocolType p) | not hasRole] + where + hasRole = + any (\(AUS _ UserServer {deleted, enabled}) -> enabled && not deleted) $ + concatMap (`updatedServers` p) $ filter roleEnabled (L.toList uss) + roleEnabled UpdatedUserOperatorServers {operator} = + maybe True (\ServerOperator {enabled, roles} -> enabled && roleSel roles) operator + duplicatServerErrs :: (ProtocolTypeI p, UserProtocol p) => SProtocolType p -> [UserServersError] + duplicatServerErrs p = mapMaybe duplicateErr_ srvs + where + srvs = + filter (\(AUS _ UserServer {deleted}) -> not deleted) $ + concatMap (`updatedServers` p) (L.toList uss) + duplicateErr_ (AUS _ srv@UserServer {server}) = + USEDuplicateServer (AProtocolType p) (AProtoServerWithAuth p server) + <$> find (`S.member` duplicateHosts) (srvHost srv) + duplicateHosts = snd $ foldl' addHost (S.empty, S.empty) allHosts + allHosts = concatMap (\(AUS _ srv) -> L.toList $ srvHost srv) srvs + addHost (hs, dups) h + | h `S.member` hs = (hs, S.insert h dups) + | otherwise = (S.insert h hs, dups) + +instance ToJSON (DBEntityId' s) where + toEncoding = \case + DBEntityId i -> toEncoding i + DBNewEntity -> JE.null_ + toJSON = \case + DBEntityId i -> toJSON i + DBNewEntity -> J.Null + +instance DBStoredI s => FromJSON (DBEntityId' s) where + parseJSON v = case (v, sdbStored @s) of + (J.Null, SDBNew) -> pure DBNewEntity + (J.Number n, SDBStored) -> case floatingOrInteger n of + Left (_ :: Double) -> fail "bad DBEntityId" + Right i -> pure $ DBEntityId (fromInteger i) + _ -> fail "bad DBEntityId" + omittedField = case sdbStored @s of + SDBStored -> Nothing + SDBNew -> Just DBNewEntity $(JQ.deriveJSON defaultJSON ''UsageConditions) $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CA") ''ConditionsAcceptance) -$(JQ.deriveJSON defaultJSON ''ServerOperator) +instance ToJSON (ServerOperator' s) where + toEncoding = $(JQ.mkToEncoding defaultJSON ''ServerOperator') + toJSON = $(JQ.mkToJSON defaultJSON ''ServerOperator') -$(JQ.deriveJSON defaultJSON ''OperatorEnabled) +instance DBStoredI s => FromJSON (ServerOperator' s) where + parseJSON = $(JQ.mkParseJSON defaultJSON ''ServerOperator') $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "UCA") ''UsageConditionsAction) -$(JQ.deriveJSON defaultJSON ''UserServers) +instance ProtocolTypeI p => ToJSON (UserServer' s p) where + toEncoding = $(JQ.mkToEncoding defaultJSON ''UserServer') + toJSON = $(JQ.mkToJSON defaultJSON ''UserServer') + +instance (DBStoredI s, ProtocolTypeI p) => FromJSON (UserServer' s p) where + parseJSON = $(JQ.mkParseJSON defaultJSON ''UserServer') + +instance ProtocolTypeI p => FromJSON (AUserServer p) where + parseJSON v = (AUS SDBStored <$> parseJSON v) <|> (AUS SDBNew <$> parseJSON v) + +$(JQ.deriveJSON defaultJSON ''UserOperatorServers) + +instance FromJSON UpdatedUserOperatorServers where + parseJSON = $(JQ.mkParseJSON defaultJSON ''UpdatedUserOperatorServers) $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "USE") ''UserServersError) diff --git a/src/Simplex/Chat/Operators/Conditions.hs b/src/Simplex/Chat/Operators/Conditions.hs index 55cf8b658d..a314c1901a 100644 --- a/src/Simplex/Chat/Operators/Conditions.hs +++ b/src/Simplex/Chat/Operators/Conditions.hs @@ -9,7 +9,7 @@ import qualified Data.Text as T stripFrontMatter :: Text -> Text stripFrontMatter = T.unlines - . dropWhile ("# " `T.isPrefixOf`) -- strip title + -- . dropWhile ("# " `T.isPrefixOf`) -- strip title . dropWhile (T.all isSpace) . dropWhile fm . (\ls -> let ls' = dropWhile (not . fm) ls in if null ls' then ls else ls') diff --git a/src/Simplex/Chat/Stats.hs b/src/Simplex/Chat/Stats.hs index 6dd5c79ab1..21ad25b311 100644 --- a/src/Simplex/Chat/Stats.hs +++ b/src/Simplex/Chat/Stats.hs @@ -7,7 +7,6 @@ module Simplex.Chat.Stats where import qualified Data.Aeson.TH as J import Data.List (partition) -import Data.List.NonEmpty (NonEmpty) import Data.Map.Strict (Map) import qualified Data.Map.Strict as M import Data.Maybe (fromMaybe, isJust) @@ -131,7 +130,7 @@ data NtfServerSummary = NtfServerSummary -- - users are passed to exclude hidden users from totalServersSummary; -- - if currentUser is hidden, it should be accounted in totalServersSummary; -- - known is set only in user level summaries based on passed userSMPSrvs and userXFTPSrvs -toPresentedServersSummary :: AgentServersSummary -> [User] -> User -> NonEmpty SMPServer -> NonEmpty XFTPServer -> [NtfServer] -> PresentedServersSummary +toPresentedServersSummary :: AgentServersSummary -> [User] -> User -> [SMPServer] -> [XFTPServer] -> [NtfServer] -> PresentedServersSummary toPresentedServersSummary agentSummary users currentUser userSMPSrvs userXFTPSrvs userNtfSrvs = do let (userSMPSrvsSumms, allSMPSrvsSumms) = accSMPSrvsSummaries (userSMPCurr, userSMPPrev, userSMPProx) = smpSummsIntoCategories userSMPSrvsSumms diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index f4f574c3d7..39bd4bb985 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -1,5 +1,8 @@ +{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} @@ -47,9 +50,13 @@ module Simplex.Chat.Store.Profiles getContactWithoutConnViaAddress, updateUserAddressAutoAccept, getProtocolServers, + getUpdateUserServers, -- overwriteOperatorsAndServers, overwriteProtocolServers, + insertProtocolServer, + getUpdateServerOperators, getServerOperators, + getUserServers, setServerOperators, getCurrentUsageConditions, getLatestAcceptedConditions, @@ -77,10 +84,11 @@ import Data.Int (Int64) import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as L import Data.Maybe (fromMaybe) -import Data.Text (Text, splitOn) +import Data.Text (Text) +import qualified Data.Text as T import Data.Text.Encoding (decodeLatin1, encodeUtf8) import Data.Time.Clock (UTCTime (..), getCurrentTime) -import Database.SQLite.Simple (NamedParam (..), Only (..), (:.) (..)) +import Database.SQLite.Simple (NamedParam (..), Only (..), Query, (:.) (..)) import Database.SQLite.Simple.QQ (sql) import Simplex.Chat.Call import Simplex.Chat.Messages @@ -92,7 +100,7 @@ import Simplex.Chat.Types import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Shared import Simplex.Chat.Types.UITheme -import Simplex.Messaging.Agent.Env.SQLite (OperatorId, ServerCfg (..), ServerRoles (..)) +import Simplex.Messaging.Agent.Env.SQLite (ServerRoles (..)) import Simplex.Messaging.Agent.Protocol (ACorrId, ConnId, UserId) import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow) import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB @@ -100,7 +108,7 @@ import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto.Ratchet as CR import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON) -import Simplex.Messaging.Protocol (BasicAuth (..), ProtoServerWithAuth (..), ProtocolServer (..), ProtocolTypeI (..), SubscriptionMode) +import Simplex.Messaging.Protocol (BasicAuth (..), ProtoServerWithAuth (..), ProtocolServer (..), ProtocolType (..), ProtocolTypeI (..), SProtocolType (..), SubscriptionMode, UserProtocol) import Simplex.Messaging.Transport.Client (TransportHost) import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8) @@ -524,177 +532,282 @@ updateUserAddressAutoAccept db user@User {userId} autoAccept = do Just AutoAccept {acceptIncognito, autoReply} -> (True, acceptIncognito, autoReply) _ -> (False, False, Nothing) -getProtocolServers :: forall p. ProtocolTypeI p => DB.Connection -> User -> IO [ServerCfg p] -getProtocolServers db User {userId} = - map toServerCfg +getUpdateUserServers :: forall p. (ProtocolTypeI p, UserProtocol p) => DB.Connection -> SProtocolType p -> NonEmpty PresetOperator -> NonEmpty (NewUserServer p) -> User -> IO [UserServer p] +getUpdateUserServers db p presetOps randomSrvs user = do + ts <- getCurrentTime + srvs <- getProtocolServers db p user + let srvs' = L.toList $ updatedUserServers p presetOps randomSrvs srvs + mapM (upsertServer ts) srvs' + where + upsertServer :: UTCTime -> AUserServer p -> IO (UserServer p) + upsertServer ts (AUS _ s@UserServer {serverId}) = case serverId of + DBNewEntity -> insertProtocolServer db p user ts s + DBEntityId _ -> updateProtocolServer db p ts s $> s + +getProtocolServers :: forall p. ProtocolTypeI p => DB.Connection -> SProtocolType p -> User -> IO [UserServer p] +getProtocolServers db p User {userId} = + map toUserServer <$> DB.query db [sql| - SELECT s.host, s.port, s.key_hash, s.basic_auth, s.server_operator_id, s.preset, s.tested, s.enabled, o.role_storage, o.role_proxy - FROM protocol_servers s - LEFT JOIN server_operators o USING (server_operator_id) - WHERE s.user_id = ? AND s.protocol = ? + SELECT smp_server_id, host, port, key_hash, basic_auth, preset, tested, enabled + FROM protocol_servers + WHERE user_id = ? AND protocol = ? |] - (userId, decodeLatin1 $ strEncode protocol) + (userId, decodeLatin1 $ strEncode p) where - protocol = protocolTypeI @p - toServerCfg :: (NonEmpty TransportHost, String, C.KeyHash, Maybe Text, Maybe OperatorId, Bool, Maybe Bool, Bool, Maybe Bool, Maybe Bool) -> ServerCfg p - toServerCfg (host, port, keyHash, auth_, operator, preset, tested, enabled, storage_, proxy_) = - let server = ProtoServerWithAuth (ProtocolServer protocol host port keyHash) (BasicAuth . encodeUtf8 <$> auth_) - roles = ServerRoles {storage = fromMaybe True storage_, proxy = fromMaybe True proxy_} - in ServerCfg {server, operator, preset, tested, enabled, roles} + toUserServer :: (DBEntityId, NonEmpty TransportHost, String, C.KeyHash, Maybe Text, Bool, Maybe Bool, Bool) -> UserServer p + toUserServer (serverId, host, port, keyHash, auth_, preset, tested, enabled) = + let server = ProtoServerWithAuth (ProtocolServer p host port keyHash) (BasicAuth . encodeUtf8 <$> auth_) + in UserServer {serverId, server, preset, tested, enabled, deleted = False} -- TODO remove -- overwriteOperatorsAndServers :: forall p. ProtocolTypeI p => DB.Connection -> User -> Maybe [ServerOperator] -> [ServerCfg p] -> ExceptT StoreError IO [ServerCfg p] -- overwriteOperatorsAndServers db user@User {userId} operators_ servers = do -overwriteProtocolServers :: forall p. ProtocolTypeI p => DB.Connection -> User -> [ServerCfg p] -> ExceptT StoreError IO () -overwriteProtocolServers db User {userId} servers = +overwriteProtocolServers :: ProtocolTypeI p => DB.Connection -> SProtocolType p -> User -> [UserServer p] -> ExceptT StoreError IO () +overwriteProtocolServers db p User {userId} servers = -- liftIO $ mapM_ (updateServerOperators_ db) operators_ checkConstraint SEUniqueID . ExceptT $ do currentTs <- getCurrentTime - DB.execute db "DELETE FROM protocol_servers WHERE user_id = ? AND protocol = ? " (userId, protocol) - forM_ servers $ \ServerCfg {server, preset, tested, enabled} -> do - let ProtoServerWithAuth ProtocolServer {host, port, keyHash} auth_ = server + DB.execute db "DELETE FROM protocol_servers WHERE user_id = ? AND protocol = ? " (userId, decodeLatin1 $ strEncode p) + forM_ servers $ \UserServer {serverId, server, preset, tested, enabled} -> do DB.execute db [sql| INSERT INTO protocol_servers - (protocol, host, port, key_hash, basic_auth, preset, tested, enabled, user_id, created_at, updated_at) - VALUES (?,?,?,?,?,?,?,?,?,?,?) + (server_id, protocol, host, port, key_hash, basic_auth, preset, tested, enabled, user_id, created_at, updated_at) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?) |] - ((protocol, host, port, keyHash, safeDecodeUtf8 . unBasicAuth <$> auth_) :. (preset, tested, enabled, userId, currentTs, currentTs)) - -- Right <$> getProtocolServers db user + (Only serverId :. serverColumns p server :. (preset, tested, enabled, userId, currentTs, currentTs)) pure $ Right () - where - protocol = decodeLatin1 $ strEncode $ protocolTypeI @p + +insertProtocolServer :: forall p. ProtocolTypeI p => DB.Connection -> SProtocolType p -> User -> UTCTime -> NewUserServer p -> IO (UserServer p) +insertProtocolServer db p User {userId} ts srv@UserServer {server, preset, tested, enabled} = do + DB.execute + db + [sql| + INSERT INTO protocol_servers + (protocol, host, port, key_hash, basic_auth, preset, tested, enabled, user_id, created_at, updated_at) + VALUES (?,?,?,?,?,?,?,?,?,?,?) + |] + (serverColumns p server :. (preset, tested, enabled, userId, ts, ts)) + sId <- insertedRowId db + pure (srv :: NewUserServer p) {serverId = DBEntityId sId} + +updateProtocolServer :: ProtocolTypeI p => DB.Connection -> SProtocolType p -> UTCTime -> UserServer p -> IO () +updateProtocolServer db p ts UserServer {serverId, server, preset, tested, enabled} = + DB.execute + db + [sql| + UPDATE protocol_servers + SET protocol = ?, host = ?, port = ?, key_hash = ?, basic_auth = ?, + preset = ?, tested = ?, enabled = ?, updated_at = ? + WHERE smp_server_id = ? + |] + (serverColumns p server :. (preset, tested, enabled, ts, serverId)) + +serverColumns :: ProtocolTypeI p => SProtocolType p -> ProtoServerWithAuth p -> (Text, NonEmpty TransportHost, String, C.KeyHash, Maybe Text) +serverColumns p (ProtoServerWithAuth ProtocolServer {host, port, keyHash} auth_) = + let protocol = decodeLatin1 $ strEncode p + auth = safeDecodeUtf8 . unBasicAuth <$> auth_ + in (protocol, host, port, keyHash, auth) getServerOperators :: DB.Connection -> ExceptT StoreError IO ([ServerOperator], Maybe UsageConditionsAction) getServerOperators db = do - now <- liftIO getCurrentTime - currentConditions <- getCurrentUsageConditions db - latestAcceptedConditions <- getLatestAcceptedConditions db - operators <- - liftIO $ - map (toOperator now currentConditions latestAcceptedConditions) - <$> DB.query_ - db - [sql| - SELECT - so.server_operator_id, so.server_operator_tag, so.trade_name, so.legal_name, - so.server_domains, so.enabled, so.role_storage, so.role_proxy, - AcceptedConditions.conditions_commit, AcceptedConditions.accepted_at - FROM server_operators so - LEFT JOIN ( - SELECT server_operator_id, conditions_commit, accepted_at, MAX(operator_usage_conditions_id) - FROM operator_usage_conditions - GROUP BY server_operator_id - ) AcceptedConditions ON AcceptedConditions.server_operator_id = so.server_operator_id - |] - pure (operators, usageConditionsAction operators currentConditions now) - where - toOperator :: - UTCTime -> - UsageConditions -> - Maybe UsageConditions -> - ( (OperatorId, Maybe OperatorTag, Text, Maybe Text, Text, Bool, Bool, Bool) - :. (Maybe Text, Maybe UTCTime) - ) -> - ServerOperator - toOperator - now - UsageConditions {conditionsCommit = currentCommit, createdAt, notifiedAt} - latestAcceptedConditions_ - ( (operatorId, operatorTag, tradeName, legalName, domains, enabled, storage, proxy) - :. (operatorCommit_, acceptedAt_) - ) = - let roles = ServerRoles {storage, proxy} - serverDomains = splitOn "," domains - conditionsAcceptance = case (latestAcceptedConditions_, operatorCommit_) of - -- no conditions were ever accepted for any operator(s) - -- (shouldn't happen as there should always be record for SimpleX Chat) - (Nothing, _) -> CARequired Nothing - -- no conditions were ever accepted for this operator - (_, Nothing) -> CARequired Nothing - (Just UsageConditions {conditionsCommit = latestAcceptedCommit}, Just operatorCommit) - | latestAcceptedCommit == currentCommit -> - if operatorCommit == latestAcceptedCommit - then -- current conditions were accepted for operator - CAAccepted acceptedAt_ - else -- current conditions were NOT accepted for operator, but were accepted for other operator(s) - CARequired Nothing - | otherwise -> - if operatorCommit == latestAcceptedCommit - then -- new conditions available, latest accepted conditions were accepted for operator - CARequired $ conditionsRequiredOrDeadline createdAt (fromMaybe now notifiedAt) - else -- new conditions available, latest accepted conditions were NOT accepted for operator (were accepted for other operator(s)) - CARequired Nothing - in ServerOperator {operatorId, operatorTag, tradeName, legalName, serverDomains, conditionsAcceptance, enabled, roles} + currentConds <- getCurrentUsageConditions db + liftIO $ do + now <- getCurrentTime + latestAcceptedConds_ <- getLatestAcceptedConditions db + let getConds op = (\ca -> op {conditionsAcceptance = ca}) <$> getOperatorConditions_ db op currentConds latestAcceptedConds_ now + operators <- mapM getConds =<< getServerOperators_ db + pure (operators, usageConditionsAction operators currentConds now) -setServerOperators :: DB.Connection -> NonEmpty OperatorEnabled -> ExceptT StoreError IO ([ServerOperator], Maybe UsageConditionsAction) -setServerOperators db operatorsEnabled = do - liftIO $ forM_ operatorsEnabled $ \OperatorEnabled {operatorId, enabled, roles = ServerRoles {storage, proxy}} -> - DB.execute - db - "UPDATE server_operators SET enabled = ?, role_storage = ?, role_proxy = ? WHERE server_operator_id = ?" - (enabled, storage, proxy, operatorId) - getServerOperators db +getUserServers :: DB.Connection -> User -> ExceptT StoreError IO ([ServerOperator], [UserServer 'PSMP], [UserServer 'PXFTP]) +getUserServers db user = + (,,) + <$> (fst <$> getServerOperators db) + <*> liftIO (getProtocolServers db SPSMP user) + <*> liftIO (getProtocolServers db SPXFTP user) + +setServerOperators :: DB.Connection -> NonEmpty ServerOperator -> IO () +setServerOperators db ops = do + currentTs <- getCurrentTime + mapM_ (updateServerOperator db currentTs) ops + +updateServerOperator :: DB.Connection -> UTCTime -> ServerOperator -> IO () +updateServerOperator db currentTs ServerOperator {operatorId, enabled, roles = ServerRoles {storage, proxy}} = + DB.execute + db + [sql| + UPDATE server_operators + SET enabled = ?, role_storage = ?, role_proxy = ?, updated_at = ? + WHERE server_operator_id = ? + |] + (enabled, storage, proxy, operatorId, currentTs) + +getUpdateServerOperators :: DB.Connection -> NonEmpty PresetOperator -> Bool -> IO [ServerOperator] +getUpdateServerOperators db presetOps newUser = do + conds <- map toUsageConditions <$> DB.query_ db usageCondsQuery + now <- getCurrentTime + let (acceptForSimplex_, currentConds, condsToAdd) = usageConditionsToAdd newUser now conds + mapM_ insertConditions condsToAdd + latestAcceptedConds_ <- getLatestAcceptedConditions db + ops <- updatedServerOperators presetOps <$> getServerOperators_ db + forM ops $ \(ASO _ op) -> + case operatorId op of + DBNewEntity -> do + op' <- insertOperator op + case (operatorTag op', acceptForSimplex_) of + (Just OTSimplex, Just cond) -> autoAcceptConditions op' cond + _ -> pure op' + DBEntityId _ -> do + updateOperator op + getOperatorConditions_ db op currentConds latestAcceptedConds_ now >>= \case + CARequired Nothing | operatorTag op == Just OTSimplex -> autoAcceptConditions op currentConds + CARequired (Just ts) | ts < now -> autoAcceptConditions op currentConds + ca -> pure op {conditionsAcceptance = ca} + where + insertConditions UsageConditions {conditionsId, conditionsCommit, notifiedAt, createdAt} = + DB.execute + db + [sql| + INSERT INTO usage_conditions + (usage_conditions_id, conditions_commit, notified_at, created_at) + VALUES (?,?,?,?) + |] + (conditionsId, conditionsCommit, notifiedAt, createdAt) + updateOperator :: ServerOperator -> IO () + updateOperator ServerOperator {operatorId, tradeName, legalName, serverDomains, enabled, roles = ServerRoles {storage, proxy}} = + DB.execute + db + [sql| + UPDATE server_operators + SET trade_name = ?, legal_name = ?, server_domains = ?, enabled = ?, role_storage = ?, role_proxy = ? + WHERE server_operator_id = ? + |] + (tradeName, legalName, T.intercalate "," serverDomains, enabled, storage, proxy, operatorId) + insertOperator :: NewServerOperator -> IO ServerOperator + insertOperator op@ServerOperator {operatorTag, tradeName, legalName, serverDomains, enabled, roles = ServerRoles {storage, proxy}} = do + DB.execute + db + [sql| + INSERT INTO server_operators + (server_operator_tag, trade_name, legal_name, server_domains, enabled, role_storage, role_proxy) + VALUES (?,?,?,?,?,?,?) + |] + (operatorTag, tradeName, legalName, T.intercalate "," serverDomains, enabled, storage, proxy) + opId <- insertedRowId db + pure op {operatorId = DBEntityId opId} + autoAcceptConditions op UsageConditions {conditionsCommit} = + acceptConditions_ db op conditionsCommit Nothing + $> op {conditionsAcceptance = CAAccepted Nothing} + +serverOperatorQuery :: Query +serverOperatorQuery = + [sql| + SELECT server_operator_id, server_operator_tag, trade_name, legal_name, + server_domains, enabled, role_storage, role_proxy + FROM server_operators + |] + +getServerOperators_ :: DB.Connection -> IO [ServerOperator] +getServerOperators_ db = map toServerOperator <$> DB.query_ db serverOperatorQuery + +toServerOperator :: (DBEntityId, Maybe OperatorTag, Text, Maybe Text, Text, Bool, Bool, Bool) -> ServerOperator +toServerOperator (operatorId, operatorTag, tradeName, legalName, domains, enabled, storage, proxy) = + ServerOperator + { operatorId, + operatorTag, + tradeName, + legalName, + serverDomains = T.splitOn "," domains, + conditionsAcceptance = CARequired Nothing, + enabled, + roles = ServerRoles {storage, proxy} + } + +getOperatorConditions_ :: DB.Connection -> ServerOperator -> UsageConditions -> Maybe UsageConditions -> UTCTime -> IO ConditionsAcceptance +getOperatorConditions_ db ServerOperator {operatorId} UsageConditions {conditionsCommit = currentCommit, createdAt, notifiedAt} latestAcceptedConds_ now = do + case latestAcceptedConds_ of + Nothing -> pure $ CARequired Nothing -- no conditions accepted by any operator + Just UsageConditions {conditionsCommit = latestAcceptedCommit} -> do + operatorAcceptedConds_ <- + maybeFirstRow id $ + DB.query + db + [sql| + SELECT conditions_commit, accepted_at + FROM operator_usage_conditions + WHERE server_operator_id = ? + ORDER BY operator_usage_conditions_id DESC + LIMIT 1 + |] + (Only operatorId) + pure $ case operatorAcceptedConds_ of + Just (operatorCommit, acceptedAt_) + | operatorCommit /= latestAcceptedCommit -> CARequired Nothing -- TODO should we consider this operator disabled? + | currentCommit /= latestAcceptedCommit -> CARequired $ conditionsRequiredOrDeadline createdAt (fromMaybe now notifiedAt) + | otherwise -> CAAccepted acceptedAt_ + _ -> CARequired Nothing -- no conditions were accepted for this operator getCurrentUsageConditions :: DB.Connection -> ExceptT StoreError IO UsageConditions getCurrentUsageConditions db = ExceptT . firstRow toUsageConditions SEUsageConditionsNotFound $ - DB.query_ - db - [sql| - SELECT usage_conditions_id, conditions_commit, notified_at, created_at - FROM usage_conditions - ORDER BY usage_conditions_id DESC LIMIT 1 - |] + DB.query_ db (usageCondsQuery <> " DESC LIMIT 1") + +usageCondsQuery :: Query +usageCondsQuery = + [sql| + SELECT usage_conditions_id, conditions_commit, notified_at, created_at + FROM usage_conditions + ORDER BY usage_conditions_id + |] toUsageConditions :: (Int64, Text, Maybe UTCTime, UTCTime) -> UsageConditions toUsageConditions (conditionsId, conditionsCommit, notifiedAt, createdAt) = UsageConditions {conditionsId, conditionsCommit, notifiedAt, createdAt} -getLatestAcceptedConditions :: DB.Connection -> ExceptT StoreError IO (Maybe UsageConditions) -getLatestAcceptedConditions db = do - (latestAcceptedCommit_ :: Maybe Text) <- - liftIO $ - maybeFirstRow fromOnly $ - DB.query_ - db - [sql| +getLatestAcceptedConditions :: DB.Connection -> IO (Maybe UsageConditions) +getLatestAcceptedConditions db = + maybeFirstRow toUsageConditions $ + DB.query_ + db + [sql| + SELECT usage_conditions_id, conditions_commit, notified_at, created_at + FROM usage_conditions + WHERE conditions_commit = ( SELECT conditions_commit FROM operator_usage_conditions ORDER BY accepted_at DESC LIMIT 1 - |] - forM latestAcceptedCommit_ $ \latestAcceptedCommit -> - ExceptT . firstRow toUsageConditions SEUsageConditionsNotFound $ - DB.query - db - [sql| - SELECT usage_conditions_id, conditions_commit, notified_at, created_at - FROM usage_conditions - WHERE conditions_commit = ? - |] - (Only latestAcceptedCommit) + ) + |] setConditionsNotified :: DB.Connection -> Int64 -> UTCTime -> IO () -setConditionsNotified db conditionsId notifiedAt = - DB.execute db "UPDATE usage_conditions SET notified_at = ? WHERE usage_conditions_id = ?" (notifiedAt, conditionsId) +setConditionsNotified db condId notifiedAt = + DB.execute db "UPDATE usage_conditions SET notified_at = ? WHERE usage_conditions_id = ?" (notifiedAt, condId) -acceptConditions :: DB.Connection -> Int64 -> NonEmpty ServerOperator -> UTCTime -> ExceptT StoreError IO ([ServerOperator], Maybe UsageConditionsAction) -acceptConditions db conditionsId operators acceptedAt = do - UsageConditions {conditionsCommit} <- getUsageConditionsById_ db conditionsId - liftIO $ forM_ operators $ \ServerOperator {operatorId, operatorTag} -> - DB.execute - db - [sql| - INSERT INTO operator_usage_conditions - (server_operator_id, server_operator_tag, conditions_commit, accepted_at) - VALUES (?,?,?,?) - |] - (operatorId, operatorTag, conditionsCommit, acceptedAt) - getServerOperators db +acceptConditions :: DB.Connection -> Int64 -> NonEmpty Int64 -> UTCTime -> ExceptT StoreError IO () +acceptConditions db condId opIds acceptedAt = do + UsageConditions {conditionsCommit} <- getUsageConditionsById_ db condId + operators <- mapM getServerOperator_ opIds + let ts = Just acceptedAt + liftIO $ forM_ operators $ \op -> acceptConditions_ db op conditionsCommit ts + where + getServerOperator_ opId = + ExceptT $ firstRow toServerOperator (SEOperatorNotFound opId) $ + DB.query db (serverOperatorQuery <> " WHERE operator_id = ?") (Only opId) + +acceptConditions_ :: DB.Connection -> ServerOperator -> Text -> Maybe UTCTime -> IO () +acceptConditions_ db ServerOperator {operatorId, operatorTag} conditionsCommit acceptedAt = + DB.execute + db + [sql| + INSERT INTO operator_usage_conditions + (server_operator_id, server_operator_tag, conditions_commit, accepted_at) + VALUES (?,?,?,?) + |] + (operatorId, operatorTag, conditionsCommit, acceptedAt) getUsageConditionsById_ :: DB.Connection -> Int64 -> ExceptT StoreError IO UsageConditions getUsageConditionsById_ db conditionsId = @@ -708,83 +821,22 @@ getUsageConditionsById_ db conditionsId = |] (Only conditionsId) -setUserServers :: DB.Connection -> User -> NonEmpty UserServers -> ExceptT StoreError IO () -setUserServers db User {userId} userServers = do - currentTs <- liftIO getCurrentTime - forM_ userServers $ do - \UserServers {operator, smpServers, xftpServers} -> do - forM_ operator $ \op -> liftIO $ updateOperator currentTs op - overwriteServers currentTs operator smpServers - overwriteServers currentTs operator xftpServers +setUserServers :: DB.Connection -> User -> NonEmpty UpdatedUserOperatorServers -> ExceptT StoreError IO () +setUserServers db user@User {userId} userServers = checkConstraint SEUniqueID $ liftIO $ do + ts <- getCurrentTime + forM_ userServers $ \UpdatedUserOperatorServers {operator, smpServers, xftpServers} -> do + mapM_ (updateServerOperator db ts) operator + mapM_ (upsertOrDelete SPSMP ts) smpServers + mapM_ (upsertOrDelete SPXFTP ts) xftpServers where - updateOperator :: UTCTime -> ServerOperator -> IO () - updateOperator currentTs ServerOperator {operatorId, enabled, roles = ServerRoles {storage, proxy}} = - DB.execute - db - [sql| - UPDATE server_operators - SET enabled = ?, role_storage = ?, role_proxy = ?, updated_at = ? - WHERE server_operator_id = ? - |] - (enabled, storage, proxy, operatorId, currentTs) - overwriteServers :: forall p. ProtocolTypeI p => UTCTime -> Maybe ServerOperator -> [ServerCfg p] -> ExceptT StoreError IO () - overwriteServers currentTs serverOperator servers = - checkConstraint SEUniqueID . ExceptT $ do - case serverOperator of - Nothing -> - DB.execute db "DELETE FROM protocol_servers WHERE user_id = ? AND server_operator_id IS NULL AND protocol = ?" (userId, protocol) - Just ServerOperator {operatorId} -> - DB.execute db "DELETE FROM protocol_servers WHERE user_id = ? AND server_operator_id = ? AND protocol = ?" (userId, operatorId, protocol) - forM_ servers $ \ServerCfg {server, operator, preset, tested, enabled} -> do - let ProtoServerWithAuth ProtocolServer {host, port, keyHash} auth_ = server - DB.execute - db - [sql| - INSERT INTO protocol_servers - (protocol, host, port, key_hash, basic_auth, operator, preset, tested, enabled, user_id, created_at, updated_at) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?) - |] - ((protocol, host, port, keyHash, safeDecodeUtf8 . unBasicAuth <$> auth_, operator) :. (preset, tested, enabled, userId, currentTs, currentTs)) - pure $ Right () - where - protocol = decodeLatin1 $ strEncode $ protocolTypeI @p - --- updateServerOperators_ :: DB.Connection -> [ServerOperator] -> IO [ServerOperator] --- updateServerOperators_ db operators = do --- DB.execute_ db "DELETE FROM server_operators WHERE preset = 0" --- let (existing, new) = partition (isJust . operatorId) operators --- existing' <- mapM (\op -> upsertExisting op $> op) existing --- new' <- mapM insertNew new --- pure $ existing' <> new' --- where --- upsertExisting ServerOperator {operatorId, name, preset, enabled, roles = ServerRoles {storage, proxy}} --- | preset = --- DB.execute --- db --- [sql| --- UPDATE server_operators --- SET enabled = ?, role_storage = ?, role_proxy = ? --- WHERE server_operator_id = ? --- |] --- (enabled, storage, proxy, operatorId) --- | otherwise = --- DB.execute --- db --- [sql| --- INSERT INTO server_operators (server_operator_id, name, preset, enabled, role_storage, role_proxy) --- VALUES (?,?,?,?,?,?) --- |] --- (operatorId, name, preset, enabled, storage, proxy) --- insertNew op@ServerOperator {name, preset, enabled, roles = ServerRoles {storage, proxy}} = do --- DB.execute --- db --- [sql| --- INSERT INTO server_operators (name, preset, enabled, role_storage, role_proxy) --- VALUES (?,?,?,?,?) --- |] --- (name, preset, enabled, storage, proxy) --- opId <- insertedRowId db --- pure op {operatorId = Just opId} + upsertOrDelete :: ProtocolTypeI p => SProtocolType p -> UTCTime -> AUserServer p -> IO () + upsertOrDelete p ts (AUS _ s@UserServer {serverId, deleted}) = case serverId of + DBNewEntity + | deleted -> pure () + | otherwise -> void $ insertProtocolServer db p user ts s + DBEntityId srvId + | deleted -> DB.execute db "DELETE FROM protocol_servers WHERE user_id = ? AND smp_server_id = ? AND preset = ?" (userId, srvId, False) + | otherwise -> updateProtocolServer db p ts s createCall :: DB.Connection -> User -> Call -> UTCTime -> IO () createCall db user@User {userId} Call {contactId, callId, callUUID, chatItemId, callState} callTs = do diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index 083079e2ea..fcd9896917 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -127,6 +127,7 @@ data StoreError | SERemoteCtrlNotFound {remoteCtrlId :: RemoteCtrlId} | SERemoteCtrlDuplicateCA | SEProhibitedDeleteUser {userId :: UserId, contactId :: ContactId} + | SEOperatorNotFound {serverOperatorId :: Int64} | SEUsageConditionsNotFound deriving (Show, Exception) diff --git a/src/Simplex/Chat/Terminal.hs b/src/Simplex/Chat/Terminal.hs index e38a34d45f..aa6babfcbd 100644 --- a/src/Simplex/Chat/Terminal.hs +++ b/src/Simplex/Chat/Terminal.hs @@ -1,6 +1,7 @@ {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} module Simplex.Chat.Terminal where @@ -13,15 +14,15 @@ import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) import Database.SQLite.Simple (SQLError (..)) import qualified Database.SQLite.Simple as DB -import Simplex.Chat (defaultChatConfig, operatorSimpleXChat) +import Simplex.Chat (_defaultNtfServers, defaultChatConfig, operatorSimpleXChat) import Simplex.Chat.Controller import Simplex.Chat.Core import Simplex.Chat.Help (chatWelcome) +import Simplex.Chat.Operators import Simplex.Chat.Options import Simplex.Chat.Terminal.Input import Simplex.Chat.Terminal.Output import Simplex.FileTransfer.Client.Presets (defaultXFTPServers) -import Simplex.Messaging.Agent.Env.SQLite (allRoles, presetServerCfg) import Simplex.Messaging.Client (NetworkConfig (..), SMPProxyFallback (..), SMPProxyMode (..), defaultNetworkConfig) import Simplex.Messaging.Util (raceAny_) import System.IO (hFlush, hSetEcho, stdin, stdout) @@ -29,20 +30,24 @@ import System.IO (hFlush, hSetEcho, stdin, stdout) terminalChatConfig :: ChatConfig terminalChatConfig = defaultChatConfig - { defaultServers = - DefaultAgentServers - { smp = - L.fromList $ - map - (presetServerCfg True allRoles operatorSimpleXChat) - [ "smp://u2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU=@smp4.simplex.im,o5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion", - "smp://hpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg=@smp5.simplex.im,jjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion", - "smp://PQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo=@smp6.simplex.im,bylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion" - ], - useSMP = 3, - ntf = ["ntf://FB-Uop7RTaZZEG0ZLD2CIaTjsPh-Fw0zFAnb7QyA8Ks=@ntf2.simplex.im,ntg7jdjy2i3qbib3sykiho3enekwiaqg3icctliqhtqcg6jmoh6cxiad.onion"], - xftp = L.map (presetServerCfg True allRoles operatorSimpleXChat) defaultXFTPServers, - useXFTP = L.length defaultXFTPServers, + { presetServers = + PresetServers + { operators = + [ PresetOperator + { operator = Just operatorSimpleXChat, + smp = + map + (presetServer True) + [ "smp://u2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU=@smp4.simplex.im,o5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion", + "smp://hpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg=@smp5.simplex.im,jjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion", + "smp://PQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo=@smp6.simplex.im,bylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion" + ], + useSMP = 3, + xftp = map (presetServer True) $ L.toList defaultXFTPServers, + useXFTP = 3 + } + ], + ntf = _defaultNtfServers, netCfg = defaultNetworkConfig { smpProxyMode = SPMUnknown, diff --git a/src/Simplex/Chat/Terminal/Main.hs b/src/Simplex/Chat/Terminal/Main.hs index 64703a3a92..b0eb4dac88 100644 --- a/src/Simplex/Chat/Terminal/Main.hs +++ b/src/Simplex/Chat/Terminal/Main.hs @@ -10,7 +10,7 @@ import Data.Maybe (fromMaybe) import Data.Time.Clock (getCurrentTime) import Data.Time.LocalTime (getCurrentTimeZone) import Network.Socket -import Simplex.Chat.Controller (ChatConfig (..), ChatController (..), ChatResponse (..), DefaultAgentServers (DefaultAgentServers, netCfg), SimpleNetCfg (..), currentRemoteHost, versionNumber, versionString) +import Simplex.Chat.Controller (ChatConfig (..), ChatController (..), ChatResponse (..), PresetServers (..), SimpleNetCfg (..), currentRemoteHost, versionNumber, versionString) import Simplex.Chat.Core import Simplex.Chat.Options import Simplex.Chat.Terminal @@ -56,7 +56,7 @@ simplexChatCLI' cfg opts@ChatOpts {chatCmd, chatCmdLog, chatCmdDelay, chatServer putStrLn $ serializeChatResponse (rh, Just user) ts tz rh r welcome :: ChatConfig -> ChatOpts -> IO () -welcome ChatConfig {defaultServers = DefaultAgentServers {netCfg}} ChatOpts {coreOptions = CoreChatOpts {dbFilePrefix, simpleNetCfg = SimpleNetCfg {socksProxy, socksMode, smpProxyMode_, smpProxyFallback_}}} = +welcome ChatConfig {presetServers = PresetServers {netCfg}} ChatOpts {coreOptions = CoreChatOpts {dbFilePrefix, simpleNetCfg = SimpleNetCfg {socksProxy, socksMode, smpProxyMode_, smpProxyFallback_}}} = mapM_ putStrLn [ versionString versionNumber, diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 1e6986ee03..2f289afe4b 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -19,12 +19,13 @@ import qualified Data.ByteString.Lazy.Char8 as LB import Data.Char (isSpace, toUpper) import Data.Function (on) import Data.Int (Int64) -import Data.List (foldl', groupBy, intercalate, intersperse, partition, sortOn) +import Data.List (groupBy, intercalate, intersperse, partition, sortOn) import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.List.NonEmpty as L import Data.Map.Strict (Map) import qualified Data.Map.Strict as M import Data.Maybe (fromMaybe, isJust, isNothing, mapMaybe) +import Data.String import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (decodeLatin1) @@ -54,7 +55,7 @@ import Simplex.Chat.Types.Shared import Simplex.Chat.Types.UITheme import qualified Simplex.FileTransfer.Transport as XFTP import Simplex.Messaging.Agent.Client (ProtocolTestFailure (..), ProtocolTestStep (..), SubscriptionsInfo (..)) -import Simplex.Messaging.Agent.Env.SQLite (NetworkConfig (..), ServerCfg (..)) +import Simplex.Messaging.Agent.Env.SQLite (NetworkConfig (..), ServerRoles (..)) import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..)) import Simplex.Messaging.Client (SMPProxyFallback, SMPProxyMode (..), SocksMode (..)) @@ -96,10 +97,9 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRChats chats -> viewChats ts tz chats CRApiChat u chat _ -> ttyUser u $ if testView then testViewChat chat else [viewJSON chat] CRApiParsedMarkdown ft -> [viewJSON ft] - CRUserProtoServers u userServers operators -> ttyUser u $ viewUserServers userServers operators testView CRServerTestResult u srv testFailure -> ttyUser u $ viewServerTestResult srv testFailure - CRServerOperators {} -> [] - CRUserServers {} -> [] + CRServerOperators ops ca -> viewServerOperators ops ca + CRUserServers u uss -> ttyUser u $ concatMap viewUserServers uss <> (if testView then [] else serversUserHelp) CRUserServersValidation _ -> [] CRUsageConditions {} -> [] CRChatItemTTL u ttl -> ttyUser u $ viewChatItemTTL ttl @@ -1214,27 +1214,31 @@ viewUserPrivacy User {userId} User {userId = userId', localDisplayName = n', sho "profile is " <> if isJust viewPwdHash then "hidden" else "visible" ] -viewUserServers :: AUserProtoServers -> [ServerOperator] -> Bool -> [StyledString] -viewUserServers (AUPS UserProtoServers {serverProtocol = p, protoServers, presetServers}) operators testView = - customServers - <> if testView - then [] - else - [ "", - "use " <> highlight (srvCmd <> " test ") <> " to test " <> pName <> " server connection", - "use " <> highlight (srvCmd <> " ") <> " to configure " <> pName <> " servers", - "use " <> highlight (srvCmd <> " default") <> " to remove configured " <> pName <> " servers and use presets" - ] - <> case p of - SPSMP -> ["(chat option " <> highlight' "-s" <> " (" <> highlight' "--server" <> ") has precedence over saved SMP servers for chat session)"] - SPXFTP -> ["(chat option " <> highlight' "-xftp-servers" <> " has precedence over saved XFTP servers for chat session)"] +viewUserServers :: UserOperatorServers -> [StyledString] +viewUserServers (UserOperatorServers _ [] []) = [] +viewUserServers UserOperatorServers {operator, smpServers, xftpServers} = + [plain $ maybe "Your servers" shortViewOperator operator] + <> viewServers SPSMP smpServers + <> viewServers SPXFTP xftpServers where - srvCmd = "/" <> strEncode p - pName = protocolName p - customServers = - if null protoServers - then ("no " <> pName <> " servers saved, using presets: ") : viewServers operators presetServers - else viewServers operators protoServers + viewServers :: ProtocolTypeI p => SProtocolType p -> [UserServer p] -> [StyledString] + viewServers _ [] = [] + viewServers p srvs = [" " <> protocolName p <> " servers"] <> map (plain . (" " <> ) . viewServer) srvs + where + viewServer UserServer {server, preset, tested, enabled} = safeDecodeUtf8 (strEncode server) <> serverInfo + where + serverInfo = if null serverInfo_ then "" else parens $ T.intercalate ", " serverInfo_ + serverInfo_ = ["preset" | preset] <> testedInfo <> ["disabled" | not enabled] + testedInfo = maybe [] (\t -> ["test: " <> if t then "passed" else "failed"]) tested + +serversUserHelp :: [StyledString] +serversUserHelp = + [ "", + "use " <> highlight' "/smp test " <> " to test SMP server connection", + "use " <> highlight' "/smp " <> " to configure SMP servers", + "or the same commands starting from /xftp for XFTP servers", + "chat options " <> highlight' "-s" <> " (" <> highlight' "--server" <> ") and " <> highlight' "--xftp-servers" <> " have precedence over preset servers for new user profiles" + ] protocolName :: ProtocolTypeI p => SProtocolType p -> StyledString protocolName = plain . map toUpper . T.unpack . decodeLatin1 . strEncode @@ -1255,6 +1259,53 @@ viewServerTestResult (AProtoServerWithAuth p _) = \case where pName = protocolName p +viewServerOperators :: [ServerOperator] -> Maybe UsageConditionsAction -> [StyledString] +viewServerOperators ops ca = map (plain . viewOperator) ops <> maybe [] viewConditionsAction ca + +viewOperator :: ServerOperator' s -> Text +viewOperator op@ServerOperator {tradeName, legalName, serverDomains, conditionsAcceptance} = + viewOpIdTag op + <> tradeName + <> maybe "" parens legalName + <> (", domains: " <> T.intercalate ", " serverDomains) + <> (", conditions: " <> viewOpConditions conditionsAcceptance) + <> (", " <> viewOpEnabled op) + +shortViewOperator :: ServerOperator -> Text +shortViewOperator op@ServerOperator {operatorId = DBEntityId opId, tradeName} = + tshow opId <> ". " <> tradeName <> parens (viewOpEnabled op) + +viewOpIdTag :: ServerOperator' s -> Text +viewOpIdTag ServerOperator {operatorId, operatorTag} = case operatorId of + DBEntityId i -> tshow i <> " - " <> tag + DBNewEntity -> tag + where + tag = maybe "" textEncode operatorTag <> ". " + +viewOpConditions :: ConditionsAcceptance -> Text +viewOpConditions = \case + CAAccepted ts -> viewCond "accepted" ts + CARequired ts -> viewCond "required" ts + where + viewCond w ts = w <> maybe "" (parens . tshow) ts + +viewOpEnabled :: ServerOperator' s -> Text +viewOpEnabled ServerOperator {enabled, roles = ServerRoles {storage, proxy}} + | enabled && storage && proxy = "enabled" + | enabled && storage = "enabled storage" + | enabled && proxy = "enabled proxy" + | otherwise = "disabled" + +viewConditionsAction :: UsageConditionsAction -> [StyledString] +viewConditionsAction = \case + UCAReview {operators, deadline, showNotice} | showNotice -> case deadline of + Just ts -> [plain $ "New conditions will be accepted at " <> tshow ts <> " for " <> ops] + Nothing -> [plain $ "New conditions have to be accepted for " <> ops] + where + ops = T.intercalate ", " $ map legalName_ operators + legalName_ ServerOperator {tradeName, legalName} = fromMaybe tradeName legalName + _ -> [] + viewChatItemTTL :: Maybe Int64 -> [StyledString] viewChatItemTTL = \case Nothing -> ["old messages are not being deleted"] @@ -1331,11 +1382,11 @@ viewConnectionStats ConnectionStats {rcvQueuesInfo, sndQueuesInfo} = ["receiving messages via: " <> viewRcvQueuesInfo rcvQueuesInfo | not $ null rcvQueuesInfo] <> ["sending messages via: " <> viewSndQueuesInfo sndQueuesInfo | not $ null sndQueuesInfo] -viewServers :: ProtocolTypeI p => [ServerOperator] -> NonEmpty (ServerCfg p) -> [StyledString] -viewServers operators = map (plain . (\ServerCfg {server, operator} -> B.unpack (strEncode server) <> viewOperator operator)) . L.toList - where - ops :: Map (Maybe Int64) Text = foldl' (\m ServerOperator {operatorId, tradeName} -> M.insert (Just operatorId) tradeName m) M.empty operators - viewOperator = maybe "" $ \op -> " (operator " <> maybe (show op) T.unpack (M.lookup (Just op) ops) <> ")" +-- viewServers :: ProtocolTypeI p => [ServerOperator] -> NonEmpty (ServerCfg p) -> [StyledString] +-- viewServers operators = map (plain . (\ServerCfg {server, operator} -> B.unpack (strEncode server) <> viewOperator operator)) . L.toList +-- where +-- ops :: Map (Maybe DBEntityId) Text = foldl' (\m ServerOperator {operatorId, tradeName} -> M.insert (Just operatorId) tradeName m) M.empty operators +-- viewOperator = maybe "" $ \op -> " (operator " <> maybe (show op) T.unpack (M.lookup (Just op) ops) <> ")" viewRcvQueuesInfo :: [RcvQueueInfo] -> StyledString viewRcvQueuesInfo = plain . intercalate ", " . map showQueueInfo @@ -1934,7 +1985,9 @@ viewVersionInfo logLevel CoreVersionInfo {version, simplexmqVersion, simplexmqCo then [versionString version, updateStr, "simplexmq: " <> simplexmqVersion <> parens simplexmqCommit] else [versionString version, updateStr] where - parens s = " (" <> s <> ")" + +parens :: (IsString a, Semigroup a) => a -> a +parens s = " (" <> s <> ")" viewRemoteHosts :: [RemoteHostInfo] -> [StyledString] viewRemoteHosts = \case diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index d435af186e..b3d8166f9f 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -25,9 +25,10 @@ import Data.Maybe (isNothing) import qualified Data.Text as T import Network.Socket import Simplex.Chat -import Simplex.Chat.Controller (ChatCommand (..), ChatConfig (..), ChatController (..), ChatDatabase (..), ChatLogLevel (..), defaultSimpleNetCfg) +import Simplex.Chat.Controller (ChatCommand (..), ChatConfig (..), ChatController (..), ChatDatabase (..), ChatLogLevel (..), PresetServers (..), defaultSimpleNetCfg) import Simplex.Chat.Core import Simplex.Chat.Options +import Simplex.Chat.Operators (PresetOperator (..), presetServer) import Simplex.Chat.Protocol (currentChatVersion, pqEncryptionCompressionVersion) import Simplex.Chat.Store import Simplex.Chat.Store.Profiles @@ -94,8 +95,8 @@ testCoreOpts = { dbFilePrefix = "./simplex_v1", dbKey = "", -- dbKey = "this is a pass-phrase to encrypt the database", - smpServers = ["smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001"], - xftpServers = ["xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002"], + smpServers = [], + xftpServers = [], simpleNetCfg = defaultSimpleNetCfg, logLevel = CLLImportant, logConnections = False, @@ -149,6 +150,18 @@ testCfg :: ChatConfig testCfg = defaultChatConfig { agentConfig = testAgentCfg, + presetServers = + (presetServers defaultChatConfig) + { operators = + [ PresetOperator + { operator = Nothing, + smp = map (presetServer True) ["smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001"], + useSMP = 1, + xftp = map (presetServer True) ["xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002"], + useXFTP = 1 + } + ] + }, showReceipts = False, testView = True, tbqSize = 16 diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index 8756657e59..bd2a267c3a 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -25,7 +25,7 @@ import Database.SQLite.Simple (Only (..)) import Simplex.Chat.AppSettings (defaultAppSettings) import qualified Simplex.Chat.AppSettings as AS import Simplex.Chat.Call -import Simplex.Chat.Controller (ChatConfig (..), DefaultAgentServers (..)) +import Simplex.Chat.Controller (ChatConfig (..), PresetServers (..)) import Simplex.Chat.Messages (ChatItemId) import Simplex.Chat.Options import Simplex.Chat.Protocol (supportedChatVRange) @@ -334,8 +334,8 @@ testRetryConnectingClientTimeout tmp = do { quotaExceededTimeout = 1, messageRetryInterval = RetryInterval2 {riFast = fastRetryInterval, riSlow = fastRetryInterval} }, - defaultServers = - let def@DefaultAgentServers {netCfg} = defaultServers testCfg + presetServers = + let def@PresetServers {netCfg} = presetServers testCfg in def {netCfg = (netCfg :: NetworkConfig) {tcpTimeout = 10}} } opts' = @@ -1141,17 +1141,32 @@ testGetSetSMPServers :: HasCallStack => FilePath -> IO () testGetSetSMPServers = testChat2 aliceProfile bobProfile $ \alice _ -> do - alice #$> ("/_servers 1 smp", id, "smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001") + alice ##> "/_servers 1" + alice <## "Your servers" + alice <## " SMP servers" + alice <## " smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001 (preset)" + alice <## " XFTP servers" + alice <## " xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002 (preset)" alice #$> ("/smp smp://1234-w==@smp1.example.im", id, "ok") - alice #$> ("/smp", id, "smp://1234-w==@smp1.example.im") + alice ##> "/smp" + alice <## "Your servers" + alice <## " SMP servers" + alice <## " smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001 (preset, disabled)" + alice <## " smp://1234-w==@smp1.example.im" alice #$> ("/smp smp://1234-w==:password@smp1.example.im", id, "ok") - alice #$> ("/smp", id, "smp://1234-w==:password@smp1.example.im") + -- alice #$> ("/smp", id, "smp://1234-w==:password@smp1.example.im") + alice ##> "/smp" + alice <## "Your servers" + alice <## " SMP servers" + alice <## " smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001 (preset, disabled)" + alice <## " smp://1234-w==:password@smp1.example.im" alice #$> ("/smp smp://2345-w==@smp2.example.im smp://3456-w==@smp3.example.im:5224", id, "ok") alice ##> "/smp" - alice <## "smp://2345-w==@smp2.example.im" - alice <## "smp://3456-w==@smp3.example.im:5224" - alice #$> ("/smp default", id, "ok") - alice #$> ("/smp", id, "smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001") + alice <## "Your servers" + alice <## " SMP servers" + alice <## " smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001 (preset, disabled)" + alice <## " smp://2345-w==@smp2.example.im" + alice <## " smp://3456-w==@smp3.example.im:5224" testTestSMPServerConnection :: HasCallStack => FilePath -> IO () testTestSMPServerConnection = @@ -1172,17 +1187,31 @@ testGetSetXFTPServers :: HasCallStack => FilePath -> IO () testGetSetXFTPServers = testChat2 aliceProfile bobProfile $ \alice _ -> withXFTPServer $ do - alice #$> ("/_servers 1 xftp", id, "xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002") + alice ##> "/_servers 1" + alice <## "Your servers" + alice <## " SMP servers" + alice <## " smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001 (preset)" + alice <## " XFTP servers" + alice <## " xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002 (preset)" alice #$> ("/xftp xftp://1234-w==@xftp1.example.im", id, "ok") - alice #$> ("/xftp", id, "xftp://1234-w==@xftp1.example.im") + alice ##> "/xftp" + alice <## "Your servers" + alice <## " XFTP servers" + alice <## " xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002 (preset, disabled)" + alice <## " xftp://1234-w==@xftp1.example.im" alice #$> ("/xftp xftp://1234-w==:password@xftp1.example.im", id, "ok") - alice #$> ("/xftp", id, "xftp://1234-w==:password@xftp1.example.im") + alice ##> "/xftp" + alice <## "Your servers" + alice <## " XFTP servers" + alice <## " xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002 (preset, disabled)" + alice <## " xftp://1234-w==:password@xftp1.example.im" alice #$> ("/xftp xftp://2345-w==@xftp2.example.im xftp://3456-w==@xftp3.example.im:5224", id, "ok") alice ##> "/xftp" - alice <## "xftp://2345-w==@xftp2.example.im" - alice <## "xftp://3456-w==@xftp3.example.im:5224" - alice #$> ("/xftp default", id, "ok") - alice #$> ("/xftp", id, "xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002") + alice <## "Your servers" + alice <## " XFTP servers" + alice <## " xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002 (preset, disabled)" + alice <## " xftp://2345-w==@xftp2.example.im" + alice <## " xftp://3456-w==@xftp3.example.im:5224" testTestXFTPServer :: HasCallStack => FilePath -> IO () testTestXFTPServer = @@ -1800,11 +1829,17 @@ testCreateUserSameServers = where checkCustomServers alice = do alice ##> "/smp" - alice <## "smp://2345-w==@smp2.example.im" - alice <## "smp://3456-w==@smp3.example.im:5224" + alice <## "Your servers" + alice <## " SMP servers" + alice <## " smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001 (preset, disabled)" + alice <## " smp://2345-w==@smp2.example.im" + alice <## " smp://3456-w==@smp3.example.im:5224" alice ##> "/xftp" - alice <## "xftp://2345-w==@xftp2.example.im" - alice <## "xftp://3456-w==@xftp3.example.im:5224" + alice <## "Your servers" + alice <## " XFTP servers" + alice <## " xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002 (preset, disabled)" + alice <## " xftp://2345-w==@xftp2.example.im" + alice <## " xftp://3456-w==@xftp3.example.im:5224" testDeleteUser :: HasCallStack => FilePath -> IO () testDeleteUser = diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index a7de42128c..a51f42114b 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -1,8 +1,10 @@ +{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PostfixOperators #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} +{-# OPTIONS_GHC -fno-warn-ambiguous-fields #-} module ChatTests.Groups where diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index 06ed9aa5bc..1d390e1236 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -2,6 +2,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PostfixOperators #-} {-# LANGUAGE TypeApplications #-} +{-# OPTIONS_GHC -fno-warn-ambiguous-fields #-} module ChatTests.Profiles where @@ -1733,7 +1734,16 @@ testChangePCCUserDiffSrv tmp = do -- Create new user with different servers alice ##> "/create user alisa" showActiveUser alice "alisa" - alice #$> ("/smp smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7003", id, "ok") + alice ##> "/smp" + alice <## "Your servers" + alice <## " SMP servers" + alice <## " smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001 (preset)" + alice #$> ("/smp smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@127.0.0.1:7003", id, "ok") + alice ##> "/smp" + alice <## "Your servers" + alice <## " SMP servers" + alice <## " smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001 (preset, disabled)" + alice <## " smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@127.0.0.1:7003" alice ##> "/user alice" showActiveUser alice "alice (Alice)" -- Change connection to newly created user and use the newly created connection diff --git a/tests/RandomServers.hs b/tests/RandomServers.hs index e0b1939c9e..8b0b94dbd5 100644 --- a/tests/RandomServers.hs +++ b/tests/RandomServers.hs @@ -1,53 +1,64 @@ +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE GADTs #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} {-# OPTIONS_GHC -Wno-orphans #-} +{-# OPTIONS_GHC -fno-warn-ambiguous-fields #-} module RandomServers where import Control.Monad (replicateM) +import Data.Foldable (foldMap') +import Data.List (sortOn) +import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as L -import Simplex.Chat (cfgServers, cfgServersToUse, defaultChatConfig, randomServers) -import Simplex.Chat.Controller (ChatConfig (..)) -import Simplex.Messaging.Agent.Env.SQLite (ServerCfg (..), ServerRoles (..)) +import Data.Monoid (Sum (..)) +import Simplex.Chat (defaultChatConfig, randomPresetServers) +import Simplex.Chat.Controller (ChatConfig (..), PresetServers (..)) +import Simplex.Chat.Operators (DBEntityId' (..), NewUserServer, UserServer' (..), operatorServers, operatorServersToUse) +import Simplex.Messaging.Agent.Env.SQLite (ServerRoles (..)) import Simplex.Messaging.Protocol (ProtoServerWithAuth (..), SProtocolType (..), UserProtocol) import Test.Hspec randomServersTests :: Spec randomServersTests = describe "choosig random servers" $ do - it "should choose 4 random SMP servers and keep the rest disabled" testRandomSMPServers - it "should keep all 6 XFTP servers" testRandomXFTPServers + it "should choose 4 + 3 random SMP servers and keep the rest disabled" testRandomSMPServers + it "should choose 3 + 3 random XFTP servers and keep the rest disabled" testRandomXFTPServers deriving instance Eq ServerRoles -deriving instance Eq (ServerCfg p) +deriving instance Eq (DBEntityId' s) + +deriving instance Eq (UserServer' s p) testRandomSMPServers :: IO () testRandomSMPServers = do [srvs1, srvs2, srvs3] <- replicateM 3 $ - checkEnabled SPSMP 4 False =<< randomServers SPSMP defaultChatConfig + checkEnabled SPSMP 7 False =<< randomPresetServers SPSMP (presetServers defaultChatConfig) (srvs1 == srvs2 && srvs2 == srvs3) `shouldBe` False -- && to avoid rare failures testRandomXFTPServers :: IO () testRandomXFTPServers = do [srvs1, srvs2, srvs3] <- replicateM 3 $ - checkEnabled SPXFTP 6 True =<< randomServers SPXFTP defaultChatConfig - (srvs1 == srvs2 && srvs2 == srvs3) `shouldBe` True + checkEnabled SPXFTP 6 False =<< randomPresetServers SPXFTP (presetServers defaultChatConfig) + (srvs1 == srvs2 && srvs2 == srvs3) `shouldBe` False -- && to avoid rare failures -checkEnabled :: UserProtocol p => SProtocolType p -> Int -> Bool -> (L.NonEmpty (ServerCfg p), [ServerCfg p]) -> IO [ServerCfg p] -checkEnabled p n allUsed (srvs, _) = do - let def = defaultServers defaultChatConfig - cfgSrvs = L.sortWith server' $ cfgServers p def - toUse = cfgServersToUse p def - srvs == cfgSrvs `shouldBe` allUsed - L.map enable srvs `shouldBe` L.map enable cfgSrvs - let enbldSrvs = L.filter (\ServerCfg {enabled} -> enabled) srvs +checkEnabled :: UserProtocol p => SProtocolType p -> Int -> Bool -> NonEmpty (NewUserServer p) -> IO [NewUserServer p] +checkEnabled p n allUsed srvs = do + let srvs' = sortOn server' $ L.toList srvs + PresetServers {operators = presetOps} = presetServers defaultChatConfig + presetSrvs = sortOn server' $ concatMap (operatorServers p) presetOps + Sum toUse = foldMap' (Sum . operatorServersToUse p) presetOps + srvs' == presetSrvs `shouldBe` allUsed + map enable srvs' `shouldBe` map enable presetSrvs + let enbldSrvs = filter (\UserServer {enabled} -> enabled) srvs' toUse `shouldBe` n length enbldSrvs `shouldBe` n pure enbldSrvs where - server' ServerCfg {server = ProtoServerWithAuth srv _} = srv - enable :: forall p. ServerCfg p -> ServerCfg p - enable srv = (srv :: ServerCfg p) {enabled = False} + server' UserServer {server = ProtoServerWithAuth srv _} = srv + enable :: forall p. NewUserServer p -> NewUserServer p + enable srv = (srv :: NewUserServer p) {enabled = False} From 1fbf21d3953bea03ff05d827fe46dca05845bc90 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 15 Nov 2024 07:15:04 +0000 Subject: [PATCH 072/567] core: validate servers of all user profiles (#5180) * core: validate servers of all user profiles * validate all servers * fix parsing, test --- simplex-chat.cabal | 1 + src/Simplex/Chat.hs | 12 ++- src/Simplex/Chat/Controller.hs | 4 +- src/Simplex/Chat/Operators.hs | 130 ++++++++++++++++++++++++--------- src/Simplex/Chat/View.hs | 2 +- tests/OperatorTests.hs | 92 +++++++++++++++++++++++ tests/RandomServers.hs | 4 +- tests/Test.hs | 2 + 8 files changed, 207 insertions(+), 40 deletions(-) create mode 100644 tests/OperatorTests.hs diff --git a/simplex-chat.cabal b/simplex-chat.cabal index d3ea814011..8d1a298af4 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -618,6 +618,7 @@ test-suite simplex-chat-test MarkdownTests MessageBatching MobileTests + OperatorTests ProtocolTests RandomServers RemoteTests diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 86b6a5e51b..05f99656bb 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1608,7 +1608,7 @@ processChatCommand' vr = \case APIGetUserServers userId -> withUserId userId $ \user -> withFastStore $ \db -> CRUserServers user <$> (liftIO . groupByOperator =<< getUserServers db user) APISetUserServers userId userServers -> withUserId userId $ \user -> do - let errors = validateUserServers userServers + errors <- validateAllUsersServers userId $ L.toList userServers unless (null errors) $ throwChatError (CECommandError $ "user servers validation error(s): " <> show errors) (operators, smpServers, xftpServers) <- withFastStore $ \db -> do setUserServers db user userServers @@ -1620,7 +1620,8 @@ processChatCommand' vr = \case setProtocolServers a auId $ agentServerCfgs opDomains (rndServers SPSMP rs) smpServers setProtocolServers a auId $ agentServerCfgs opDomains (rndServers SPXFTP rs) xftpServers ok_ - APIValidateServers userServers -> pure $ CRUserServersValidation $ validateUserServers userServers + APIValidateServers userId userServers -> withUserId userId $ \user -> + CRUserServersValidation user <$> validateAllUsersServers userId userServers APIGetUsageConditions -> do (usageConditions, acceptedConditions) <- withFastStore $ \db -> do usageConditions <- getCurrentUsageConditions db @@ -2926,6 +2927,11 @@ processChatCommand' vr = \case withServerProtocol p action = case userProtocol p of Just Dict -> action _ -> throwChatError $ CEServerProtocol $ AProtocolType p + validateAllUsersServers :: UserServersClass u => Int64 -> [u] -> CM [UserServersError] + validateAllUsersServers currUserId userServers = withFastStore $ \db -> do + users' <- filter (\User {userId} -> userId /= currUserId) <$> liftIO (getUsers db) + others <- mapM (\user -> liftIO . fmap (user,) . groupByOperator =<< getUserServers db user) users' + pure $ validateUserServers userServers others forwardFile :: ChatName -> FileTransferId -> (ChatName -> CryptoFile -> ChatCommand) -> CM ChatResponse forwardFile chatName fileId sendCommand = withUser $ \user -> do withStore (\db -> getFileTransfer db user fileId) >>= \case @@ -8242,7 +8248,7 @@ chatCommandP = "/_operators " *> (APISetServerOperators <$> jsonP), "/_servers " *> (APIGetUserServers <$> A.decimal), "/_servers " *> (APISetUserServers <$> A.decimal <* A.space <*> jsonP), - "/_validate_servers " *> (APIValidateServers <$> jsonP), + "/_validate_servers " *> (APIValidateServers <$> A.decimal <* A.space <*> jsonP), "/_conditions" $> APIGetUsageConditions, "/_conditions_notified " *> (APISetConditionsNotified <$> A.decimal), "/_accept_conditions " *> (APIAcceptConditions <$> A.decimal <*> _strP), diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 3c2b8045d7..27acf8990b 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -358,7 +358,7 @@ data ChatCommand | APISetServerOperators (NonEmpty ServerOperator) | APIGetUserServers UserId | APISetUserServers UserId (NonEmpty UpdatedUserOperatorServers) - | APIValidateServers (NonEmpty UpdatedUserOperatorServers) -- response is CRUserServersValidation + | APIValidateServers UserId [ValidatedUserOperatorServers] -- response is CRUserServersValidation | APIGetUsageConditions | APISetConditionsNotified Int64 | APIAcceptConditions Int64 (NonEmpty Int64) @@ -590,7 +590,7 @@ data ChatResponse | CRServerTestResult {user :: User, testServer :: AProtoServerWithAuth, testFailure :: Maybe ProtocolTestFailure} | CRServerOperators {operators :: [ServerOperator], conditionsAction :: Maybe UsageConditionsAction} | CRUserServers {user :: User, userServers :: [UserOperatorServers]} - | CRUserServersValidation {serverErrors :: [UserServersError]} + | CRUserServersValidation {user :: User, serverErrors :: [UserServersError]} | CRUsageConditions {usageConditions :: UsageConditions, conditionsText :: Text, acceptedConditions :: Maybe UsageConditions} | CRChatItemTTL {user :: User, chatItemTTL :: Maybe Int64} | CRNetworkConfig {networkConfig :: NetworkConfig} diff --git a/src/Simplex/Chat/Operators.hs b/src/Simplex/Chat/Operators.hs index 55de357090..6bf1a75da4 100644 --- a/src/Simplex/Chat/Operators.hs +++ b/src/Simplex/Chat/Operators.hs @@ -1,5 +1,6 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE KindSignatures #-} @@ -13,6 +14,7 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilyDependencies #-} {-# OPTIONS_GHC -fno-warn-ambiguous-fields #-} module Simplex.Chat.Operators where @@ -22,10 +24,12 @@ import Data.Aeson (FromJSON (..), ToJSON (..)) import qualified Data.Aeson as J import qualified Data.Aeson.Encoding as JE import qualified Data.Aeson.TH as JQ +import Data.Either (partitionEithers) import Data.FileEmbed import Data.Foldable (foldMap') import Data.IORef import Data.Int (Int64) +import Data.Kind import Data.List (find, foldl') import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as L @@ -43,11 +47,12 @@ import Database.SQLite.Simple.FromField (FromField (..)) import Database.SQLite.Simple.ToField (ToField (..)) import Language.Haskell.TH.Syntax (lift) import Simplex.Chat.Operators.Conditions +import Simplex.Chat.Types (User) import Simplex.Chat.Types.Util (textParseJSON) import Simplex.Messaging.Agent.Env.SQLite (ServerCfg (..), ServerRoles (..), allRoles) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, fromTextField_, sumTypeJSON) -import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), ProtoServerWithAuth (..), ProtocolServer (..), ProtocolType (..), ProtocolTypeI, SProtocolType (..), UserProtocol) +import Simplex.Messaging.Protocol (AProtocolType (..), ProtoServerWithAuth (..), ProtocolServer (..), ProtocolType (..), ProtocolTypeI, SProtocolType (..), UserProtocol) import Simplex.Messaging.Transport.Client (TransportHost (..)) import Simplex.Messaging.Util (atomicModifyIORef'_, safeDecodeUtf8) @@ -196,10 +201,56 @@ data UpdatedUserOperatorServers = UpdatedUserOperatorServers } deriving (Show) -updatedServers :: UserProtocol p => UpdatedUserOperatorServers -> SProtocolType p -> [AUserServer p] -updatedServers UpdatedUserOperatorServers {smpServers, xftpServers} = \case - SPSMP -> smpServers - SPXFTP -> xftpServers +data ValidatedUserOperatorServers = ValidatedUserOperatorServers + { operator :: Maybe ServerOperator, + smpServers :: [AValidatedServer 'PSMP], + xftpServers :: [AValidatedServer 'PXFTP] + } + deriving (Show) + +data AValidatedServer p = forall s. AVS (SDBStored s) (ValidatedServer s p) + +deriving instance Show (AValidatedServer p) + +type ValidatedServer s p = UserServer_ s ValidatedProtoServer p + +data ValidatedProtoServer p = ValidatedProtoServer {unVPS :: Either Text (ProtoServerWithAuth p)} + deriving (Show) + +class UserServersClass u where + type AServer u = (s :: ProtocolType -> Type) | s -> u + operator' :: u -> Maybe ServerOperator + partitionValid :: [AServer u p] -> ([Text], [AUserServer p]) + servers' :: UserProtocol p => u -> SProtocolType p -> [AServer u p] + +instance UserServersClass UserOperatorServers where + type AServer UserOperatorServers = UserServer_ 'DBStored ProtoServerWithAuth + operator' UserOperatorServers {operator} = operator + partitionValid ss = ([], map (AUS SDBStored) ss) + servers' UserOperatorServers {smpServers, xftpServers} = \case + SPSMP -> smpServers + SPXFTP -> xftpServers + +instance UserServersClass UpdatedUserOperatorServers where + type AServer UpdatedUserOperatorServers = AUserServer + operator' UpdatedUserOperatorServers {operator} = operator + partitionValid = ([],) + servers' UpdatedUserOperatorServers {smpServers, xftpServers} = \case + SPSMP -> smpServers + SPXFTP -> xftpServers + +instance UserServersClass ValidatedUserOperatorServers where + type AServer ValidatedUserOperatorServers = AValidatedServer + operator' ValidatedUserOperatorServers {operator} = operator + partitionValid = partitionEithers . map serverOrErr + where + serverOrErr :: AValidatedServer p -> Either Text (AUserServer p) + serverOrErr (AVS s srv@UserServer {server = server'}) = (\server -> AUS s srv {server}) <$> unVPS server' + servers' ValidatedUserOperatorServers {smpServers, xftpServers} = \case + SPSMP -> smpServers + SPXFTP -> xftpServers + +type UserServer' s p = UserServer_ s ProtoServerWithAuth p type UserServer p = UserServer' 'DBStored p @@ -209,9 +260,9 @@ data AUserServer p = forall s. AUS (SDBStored s) (UserServer' s p) deriving instance Show (AUserServer p) -data UserServer' s p = UserServer +data UserServer_ s (srv :: ProtocolType -> Type) (p :: ProtocolType) = UserServer { serverId :: DBEntityId' s, - server :: ProtoServerWithAuth p, + server :: srv p, preset :: Bool, tested :: Maybe Bool, enabled :: Bool, @@ -352,35 +403,36 @@ groupByOperator (ops, smpSrvs, xftpSrvs) = do addXFTP srv s@UserOperatorServers {xftpServers} = (s :: UserOperatorServers) {xftpServers = srv : xftpServers} data UserServersError - = USEStorageMissing {protocol :: AProtocolType} - | USEProxyMissing {protocol :: AProtocolType} - | USEDuplicateServer {protocol :: AProtocolType, duplicateServer :: AProtoServerWithAuth, duplicateHost :: TransportHost} + = USENoServers {protocol :: AProtocolType, user :: Maybe User} + | USEStorageMissing {protocol :: AProtocolType, user :: Maybe User} + | USEProxyMissing {protocol :: AProtocolType, user :: Maybe User} + | USEInvalidServer {protocol :: AProtocolType, invalidServer :: Text} + | USEDuplicateServer {protocol :: AProtocolType, duplicateServer :: Text, duplicateHost :: TransportHost} deriving (Show) -validateUserServers :: NonEmpty UpdatedUserOperatorServers -> [UserServersError] -validateUserServers uss = - missingRolesErr SPSMP storage USEStorageMissing - <> missingRolesErr SPSMP proxy USEProxyMissing - <> missingRolesErr SPXFTP storage USEStorageMissing - <> duplicatServerErrs SPSMP - <> duplicatServerErrs SPXFTP +validateUserServers :: UserServersClass u' => [u'] -> [(User, [UserOperatorServers])] -> [UserServersError] +validateUserServers curr others = currUserErrs <> concatMap otherUserErrs others where - missingRolesErr :: (ProtocolTypeI p, UserProtocol p) => SProtocolType p -> (ServerRoles -> Bool) -> (AProtocolType -> UserServersError) -> [UserServersError] - missingRolesErr p roleSel err = [err (AProtocolType p) | not hasRole] + currUserErrs = noServersErrs SPSMP Nothing curr <> noServersErrs SPXFTP Nothing curr <> serverErrs SPSMP curr <> serverErrs SPXFTP curr + otherUserErrs (user, uss) = noServersErrs SPSMP (Just user) uss <> noServersErrs SPXFTP (Just user) uss + noServersErrs :: (UserServersClass u, ProtocolTypeI p, UserProtocol p) => SProtocolType p -> Maybe User -> [u] -> [UserServersError] + noServersErrs p user uss + | noServers opEnabled = [USENoServers p' user] + | otherwise = [USEStorageMissing p' user | noServers (hasRole storage)] <> [USEProxyMissing p' user | noServers (hasRole proxy)] where - hasRole = - any (\(AUS _ UserServer {deleted, enabled}) -> enabled && not deleted) $ - concatMap (`updatedServers` p) $ filter roleEnabled (L.toList uss) - roleEnabled UpdatedUserOperatorServers {operator} = - maybe True (\ServerOperator {enabled, roles} -> enabled && roleSel roles) operator - duplicatServerErrs :: (ProtocolTypeI p, UserProtocol p) => SProtocolType p -> [UserServersError] - duplicatServerErrs p = mapMaybe duplicateErr_ srvs + p' = AProtocolType p + noServers cond = not $ any srvEnabled $ snd $ partitionValid $ concatMap (`servers'` p) $ filter cond uss + opEnabled = maybe True (\ServerOperator {enabled} -> enabled) . operator' + hasRole roleSel = maybe True (\ServerOperator {enabled, roles} -> enabled && roleSel roles) . operator' + srvEnabled (AUS _ UserServer {deleted, enabled}) = enabled && not deleted + serverErrs :: (UserServersClass u, ProtocolTypeI p, UserProtocol p) => SProtocolType p -> [u] -> [UserServersError] + serverErrs p uss = map (USEInvalidServer p') invalidSrvs <> mapMaybe duplicateErr_ srvs where - srvs = - filter (\(AUS _ UserServer {deleted}) -> not deleted) $ - concatMap (`updatedServers` p) (L.toList uss) + p' = AProtocolType p + (invalidSrvs, userSrvs) = partitionValid $ concatMap (`servers'` p) uss + srvs = filter (\(AUS _ UserServer {deleted}) -> not deleted) userSrvs duplicateErr_ (AUS _ srv@UserServer {server}) = - USEDuplicateServer (AProtocolType p) (AProtoServerWithAuth p server) + USEDuplicateServer p' (safeDecodeUtf8 $ strEncode server) <$> find (`S.member` duplicateHosts) (srvHost srv) duplicateHosts = snd $ foldl' addHost (S.empty, S.empty) allHosts allHosts = concatMap (\(AUS _ srv) -> L.toList $ srvHost srv) srvs @@ -421,18 +473,30 @@ instance DBStoredI s => FromJSON (ServerOperator' s) where $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "UCA") ''UsageConditionsAction) instance ProtocolTypeI p => ToJSON (UserServer' s p) where - toEncoding = $(JQ.mkToEncoding defaultJSON ''UserServer') - toJSON = $(JQ.mkToJSON defaultJSON ''UserServer') + toEncoding = $(JQ.mkToEncoding defaultJSON ''UserServer_) + toJSON = $(JQ.mkToJSON defaultJSON ''UserServer_) instance (DBStoredI s, ProtocolTypeI p) => FromJSON (UserServer' s p) where - parseJSON = $(JQ.mkParseJSON defaultJSON ''UserServer') + parseJSON = $(JQ.mkParseJSON defaultJSON ''UserServer_) instance ProtocolTypeI p => FromJSON (AUserServer p) where parseJSON v = (AUS SDBStored <$> parseJSON v) <|> (AUS SDBNew <$> parseJSON v) +instance ProtocolTypeI p => FromJSON (ValidatedProtoServer p) where + parseJSON v = ValidatedProtoServer <$> ((Right <$> parseJSON v) <|> (Left <$> parseJSON v)) + +instance (DBStoredI s, ProtocolTypeI p) => FromJSON (ValidatedServer s p) where + parseJSON = $(JQ.mkParseJSON defaultJSON ''UserServer_) + +instance ProtocolTypeI p => FromJSON (AValidatedServer p) where + parseJSON v = (AVS SDBStored <$> parseJSON v) <|> (AVS SDBNew <$> parseJSON v) + $(JQ.deriveJSON defaultJSON ''UserOperatorServers) instance FromJSON UpdatedUserOperatorServers where parseJSON = $(JQ.mkParseJSON defaultJSON ''UpdatedUserOperatorServers) +instance FromJSON ValidatedUserOperatorServers where + parseJSON = $(JQ.mkParseJSON defaultJSON ''ValidatedUserOperatorServers) + $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "USE") ''UserServersError) diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 2f289afe4b..317fd58a8e 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -100,7 +100,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRServerTestResult u srv testFailure -> ttyUser u $ viewServerTestResult srv testFailure CRServerOperators ops ca -> viewServerOperators ops ca CRUserServers u uss -> ttyUser u $ concatMap viewUserServers uss <> (if testView then [] else serversUserHelp) - CRUserServersValidation _ -> [] + CRUserServersValidation {} -> [] CRUsageConditions {} -> [] CRChatItemTTL u ttl -> ttyUser u $ viewChatItemTTL ttl CRNetworkConfig cfg -> viewNetworkConfig cfg diff --git a/tests/OperatorTests.hs b/tests/OperatorTests.hs new file mode 100644 index 0000000000..1b867a3e1d --- /dev/null +++ b/tests/OperatorTests.hs @@ -0,0 +1,92 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE TypeApplications #-} +{-# OPTIONS_GHC -Wno-orphans #-} +{-# OPTIONS_GHC -fno-warn-ambiguous-fields #-} + +module OperatorTests (operatorTests) where + +import qualified Data.List.NonEmpty as L +import Simplex.Chat +import Simplex.Chat.Operators +import Simplex.Chat.Types +import Simplex.FileTransfer.Client.Presets (defaultXFTPServers) +import Simplex.Messaging.Agent.Env.SQLite (ServerRoles (..), allRoles) +import Simplex.Messaging.Protocol +import Test.Hspec + +operatorTests :: Spec +operatorTests = describe "managing server operators" $ do + validateServers + +validateServers :: Spec +validateServers = describe "validate user servers" $ do + it "should pass valid user servers" $ validateUserServers [valid] [] `shouldBe` [] + it "should fail without servers" $ do + validateUserServers [invalidNoServers] [] `shouldBe` [USENoServers aSMP Nothing] + validateUserServers [invalidDisabled] [] `shouldBe` [USENoServers aSMP Nothing] + validateUserServers [invalidDisabledOp] [] `shouldBe` [USENoServers aSMP Nothing, USENoServers aXFTP Nothing] + it "should fail without servers with storage role" $ do + validateUserServers [invalidNoStorage] [] `shouldBe` [USEStorageMissing aSMP Nothing, USEStorageMissing aXFTP Nothing] + it "should fail with duplicate host" $ do + validateUserServers [invalidDuplicate] [] `shouldBe` + [ USEDuplicateServer aSMP "smp://0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU=@smp8.simplex.im,beccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion" "smp8.simplex.im", + USEDuplicateServer aSMP "smp://abcd@smp8.simplex.im" "smp8.simplex.im" + ] + it "should fail with invalid host" $ do + validateUserServers [invalidHost] [] `shouldBe` [USENoServers aXFTP Nothing, USEInvalidServer aSMP "smp:abcd@smp8.simplex.im"] + where + aSMP = AProtocolType SPSMP + aXFTP = AProtocolType SPXFTP + +deriving instance Eq User + +deriving instance Eq UserServersError + +valid :: UpdatedUserOperatorServers +valid = + UpdatedUserOperatorServers + { operator = Just operatorSimpleXChat {operatorId = DBEntityId 1}, + smpServers = map (AUS SDBNew) simplexChatSMPServers, + xftpServers = map (AUS SDBNew . presetServer True) $ L.toList defaultXFTPServers + } + +invalidNoServers :: UpdatedUserOperatorServers +invalidNoServers = (valid :: UpdatedUserOperatorServers) {smpServers = []} + +invalidDisabled :: UpdatedUserOperatorServers +invalidDisabled = + (valid :: UpdatedUserOperatorServers) + { smpServers = map (AUS SDBNew . (\srv -> (srv :: NewUserServer 'PSMP) {enabled = False})) simplexChatSMPServers + } + +invalidDisabledOp :: UpdatedUserOperatorServers +invalidDisabledOp = + (valid :: UpdatedUserOperatorServers) + { operator = Just operatorSimpleXChat {operatorId = DBEntityId 1, enabled = False} + } + +invalidNoStorage :: UpdatedUserOperatorServers +invalidNoStorage = + (valid :: UpdatedUserOperatorServers) + { operator = Just operatorSimpleXChat {operatorId = DBEntityId 1, roles = allRoles {storage = False}} + } + +invalidDuplicate :: UpdatedUserOperatorServers +invalidDuplicate = + (valid :: UpdatedUserOperatorServers) + { smpServers = map (AUS SDBNew) $ simplexChatSMPServers <> [presetServer True "smp://abcd@smp8.simplex.im"] + } + +invalidHost :: ValidatedUserOperatorServers +invalidHost = + ValidatedUserOperatorServers + { operator = Just operatorSimpleXChat {operatorId = DBEntityId 1}, + smpServers = [validatedServer (Left "smp:abcd@smp8.simplex.im"), validatedServer (Right "smp://abcd@smp8.simplex.im")], + xftpServers = [] + } + where + validatedServer srv = + AVS SDBNew (presetServer @'PSMP True "smp://abcd@smp8.simplex.im") {server = ValidatedProtoServer srv} diff --git a/tests/RandomServers.hs b/tests/RandomServers.hs index 8b0b94dbd5..048a2b5e5a 100644 --- a/tests/RandomServers.hs +++ b/tests/RandomServers.hs @@ -1,8 +1,10 @@ {-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE TypeSynonymInstances #-} {-# OPTIONS_GHC -Wno-orphans #-} {-# OPTIONS_GHC -fno-warn-ambiguous-fields #-} @@ -16,7 +18,7 @@ import qualified Data.List.NonEmpty as L import Data.Monoid (Sum (..)) import Simplex.Chat (defaultChatConfig, randomPresetServers) import Simplex.Chat.Controller (ChatConfig (..), PresetServers (..)) -import Simplex.Chat.Operators (DBEntityId' (..), NewUserServer, UserServer' (..), operatorServers, operatorServersToUse) +import Simplex.Chat.Operators import Simplex.Messaging.Agent.Env.SQLite (ServerRoles (..)) import Simplex.Messaging.Protocol (ProtoServerWithAuth (..), SProtocolType (..), UserProtocol) import Test.Hspec diff --git a/tests/Test.hs b/tests/Test.hs index 3d59b840dd..079c583a6e 100644 --- a/tests/Test.hs +++ b/tests/Test.hs @@ -10,6 +10,7 @@ import MarkdownTests import MessageBatching import MobileTests import ProtocolTests +import OperatorTests import RandomServers import RemoteTests import SchemaDump @@ -31,6 +32,7 @@ main = do around tmpBracket $ describe "WebRTC encryption" webRTCTests describe "Valid names" validNameTests describe "Message batching" batchingTests + describe "Operators" operatorTests describe "Random servers" randomServersTests around testBracket $ do describe "Mobile API Tests" mobileTests From ff8e29c0eb202792058d5ed391c152d3558ec07c Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 15 Nov 2024 11:20:32 +0400 Subject: [PATCH 073/567] core: fix accept conditions query (#5187) --- src/Simplex/Chat/Store/Profiles.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index 39bd4bb985..87b5d2fd64 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -796,7 +796,7 @@ acceptConditions db condId opIds acceptedAt = do where getServerOperator_ opId = ExceptT $ firstRow toServerOperator (SEOperatorNotFound opId) $ - DB.query db (serverOperatorQuery <> " WHERE operator_id = ?") (Only opId) + DB.query db (serverOperatorQuery <> " WHERE server_operator_id = ?") (Only opId) acceptConditions_ :: DB.Connection -> ServerOperator -> Text -> Maybe UTCTime -> IO () acceptConditions_ db ServerOperator {operatorId, operatorTag} conditionsCommit acceptedAt = From feb687d3b8bae376691f01807305d76504cfbe73 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 15 Nov 2024 12:08:15 +0000 Subject: [PATCH 074/567] core: different roles for different protocols (#5185) * core: different roles for different protocols * include current conditions in responses * fix * fix test * fix --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --- src/Simplex/Chat.hs | 28 ++++++----- src/Simplex/Chat/Controller.hs | 2 +- .../Migrations/M20241027_server_operators.hs | 6 ++- src/Simplex/Chat/Migrations/chat_schema.sql | 6 ++- src/Simplex/Chat/Operators.hs | 31 ++++++++---- src/Simplex/Chat/Store/Profiles.hs | 44 +++++++++-------- src/Simplex/Chat/View.hs | 48 ++++++++++++------- tests/OperatorTests.hs | 4 +- 8 files changed, 104 insertions(+), 65 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 05f99656bb..95bb405bae 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -150,7 +150,8 @@ operatorSimpleXChat = serverDomains = ["simplex.im"], conditionsAcceptance = CARequired Nothing, enabled = True, - roles = allRoles + smpRoles = allRoles, + xftpRoles = allRoles } operatorFlux :: NewServerOperator @@ -163,7 +164,8 @@ operatorFlux = serverDomains = ["simplexonflux.com"], conditionsAcceptance = CARequired Nothing, enabled = False, - roles = ServerRoles {storage = False, proxy = True} + smpRoles = ServerRoles {storage = False, proxy = True}, + xftpRoles = allRoles } defaultChatConfig :: ChatConfig @@ -420,7 +422,7 @@ newChatController getServers p users opDomains = do let rs' = rndServers p rs fmap M.fromList $ forM users $ \u -> - (aUserId u,) . agentServerCfgs opDomains rs' <$> getUpdateUserServers db p presetOps rs' u + (aUserId u,) . agentServerCfgs p opDomains rs' <$> getUpdateUserServers db p presetOps rs' u updateNetworkConfig :: NetworkConfig -> SimpleNetCfg -> NetworkConfig updateNetworkConfig cfg SimpleNetCfg {socksProxy, socksMode, hostMode, requiredHostMode, smpProxyMode_, smpProxyFallback_, smpWebPort, tcpTimeout_, logTLSErrors} = @@ -643,10 +645,10 @@ processChatCommand' vr = \case forM_ users $ \User {localDisplayName = n, activeUser, viewPwdHash} -> when (n == displayName) . throwChatError $ if activeUser || isNothing viewPwdHash then CEUserExists displayName else CEInvalidDisplayName {displayName, validName = ""} - opDomains <- operatorDomains . fst <$> withFastStore getServerOperators + opDomains <- operatorDomains . serverOperators <$> withFastStore getServerOperators rs <- asks randomServers - let smp = agentServerCfgs opDomains (rndServers SPSMP rs) smpServers - xftp = agentServerCfgs opDomains (rndServers SPXFTP rs) xftpServers + let smp = agentServerCfgs SPSMP opDomains (rndServers SPSMP rs) smpServers + xftp = agentServerCfgs SPXFTP opDomains (rndServers SPXFTP rs) xftpServers auId <- withAgent (\a -> createUser a smp xftp) ts <- liftIO $ getCurrentTime >>= if pastTimestamp then coupleDaysAgo else pure user <- withFastStore $ \db -> createUserRecordAt db (AgentUserId auId) p True ts @@ -1601,10 +1603,10 @@ processChatCommand' vr = \case lift $ CRServerTestResult user srv <$> withAgent' (\a -> testProtocolServer a (aUserId user) server) TestProtoServer srv -> withUser $ \User {userId} -> processChatCommand $ APITestProtoServer userId srv - APIGetServerOperators -> uncurry CRServerOperators <$> withFastStore getServerOperators + APIGetServerOperators -> CRServerOperatorConditions <$> withFastStore getServerOperators APISetServerOperators operatorsEnabled -> withFastStore $ \db -> do liftIO $ setServerOperators db operatorsEnabled - uncurry CRServerOperators <$> getServerOperators db + CRServerOperatorConditions <$> getServerOperators db APIGetUserServers userId -> withUserId userId $ \user -> withFastStore $ \db -> CRUserServers user <$> (liftIO . groupByOperator =<< getUserServers db user) APISetUserServers userId userServers -> withUserId userId $ \user -> do @@ -1617,8 +1619,8 @@ processChatCommand' vr = \case rs <- asks randomServers lift $ withAgent' $ \a -> do let auId = aUserId user - setProtocolServers a auId $ agentServerCfgs opDomains (rndServers SPSMP rs) smpServers - setProtocolServers a auId $ agentServerCfgs opDomains (rndServers SPXFTP rs) xftpServers + setProtocolServers a auId $ agentServerCfgs SPSMP opDomains (rndServers SPSMP rs) smpServers + setProtocolServers a auId $ agentServerCfgs SPXFTP opDomains (rndServers SPXFTP rs) xftpServers ok_ APIValidateServers userId userServers -> withUserId userId $ \user -> CRUserServersValidation user <$> validateAllUsersServers userId userServers @@ -1641,7 +1643,7 @@ processChatCommand' vr = \case APIAcceptConditions condId opIds -> withFastStore $ \db -> do currentTs <- liftIO getCurrentTime acceptConditions db condId opIds currentTs - uncurry CRServerOperators <$> getServerOperators db + CRServerOperatorConditions <$> getServerOperators db APISetChatItemTTL userId newTTL_ -> withUserId userId $ \user -> checkStoreNotChanged $ withChatLock "setChatItemTTL" $ do @@ -3777,9 +3779,9 @@ getKnownAgentServers :: (ProtocolTypeI p, UserProtocol p) => SProtocolType p -> getKnownAgentServers p user = do rs <- asks randomServers withStore $ \db -> do - opDomains <- operatorDomains . fst <$> getServerOperators db + opDomains <- operatorDomains . serverOperators <$> getServerOperators db srvs <- liftIO $ getProtocolServers db p user - pure $ L.toList $ agentServerCfgs opDomains (rndServers p rs) srvs + pure $ L.toList $ agentServerCfgs p opDomains (rndServers p rs) srvs protoServer' :: ServerCfg p -> ProtocolServer p protoServer' ServerCfg {server} = protoServer server diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 27acf8990b..7fb811255f 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -588,7 +588,7 @@ data ChatResponse | CRChatItemId User (Maybe ChatItemId) | CRApiParsedMarkdown {formattedText :: Maybe MarkdownList} | CRServerTestResult {user :: User, testServer :: AProtoServerWithAuth, testFailure :: Maybe ProtocolTestFailure} - | CRServerOperators {operators :: [ServerOperator], conditionsAction :: Maybe UsageConditionsAction} + | CRServerOperatorConditions {conditions :: ServerOperatorConditions} | CRUserServers {user :: User, userServers :: [UserOperatorServers]} | CRUserServersValidation {user :: User, serverErrors :: [UserServersError]} | CRUsageConditions {usageConditions :: UsageConditions, conditionsText :: Text, acceptedConditions :: Maybe UsageConditions} diff --git a/src/Simplex/Chat/Migrations/M20241027_server_operators.hs b/src/Simplex/Chat/Migrations/M20241027_server_operators.hs index d84cc5aa73..c4b40c4706 100644 --- a/src/Simplex/Chat/Migrations/M20241027_server_operators.hs +++ b/src/Simplex/Chat/Migrations/M20241027_server_operators.hs @@ -15,8 +15,10 @@ CREATE TABLE server_operators ( legal_name TEXT, server_domains TEXT, enabled INTEGER NOT NULL DEFAULT 1, - role_storage INTEGER NOT NULL DEFAULT 1, - role_proxy INTEGER NOT NULL DEFAULT 1, + smp_role_storage INTEGER NOT NULL DEFAULT 1, + smp_role_proxy INTEGER NOT NULL DEFAULT 1, + xftp_role_storage INTEGER NOT NULL DEFAULT 1, + xftp_role_proxy INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now')) ); diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index c037a60770..0dc68034e7 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -596,8 +596,10 @@ CREATE TABLE server_operators( legal_name TEXT, server_domains TEXT, enabled INTEGER NOT NULL DEFAULT 1, - role_storage INTEGER NOT NULL DEFAULT 1, - role_proxy INTEGER NOT NULL DEFAULT 1, + smp_role_storage INTEGER NOT NULL DEFAULT 1, + smp_role_proxy INTEGER NOT NULL DEFAULT 1, + xftp_role_storage INTEGER NOT NULL DEFAULT 1, + xftp_role_proxy INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL DEFAULT(datetime('now')), updated_at TEXT NOT NULL DEFAULT(datetime('now')) ); diff --git a/src/Simplex/Chat/Operators.hs b/src/Simplex/Chat/Operators.hs index 6bf1a75da4..c3d9a8823b 100644 --- a/src/Simplex/Chat/Operators.hs +++ b/src/Simplex/Chat/Operators.hs @@ -134,6 +134,13 @@ data UsageConditionsAction | UCAAccepted {operators :: [ServerOperator]} deriving (Show) +data ServerOperatorConditions = ServerOperatorConditions + { serverOperators :: [ServerOperator], + currentConditions :: UsageConditions, + conditionsAction :: Maybe UsageConditionsAction + } + deriving (Show) + usageConditionsAction :: [ServerOperator] -> UsageConditions -> UTCTime -> Maybe UsageConditionsAction usageConditionsAction operators UsageConditions {createdAt, notifiedAt} now = do let enabledOperators = filter (\ServerOperator {enabled} -> enabled) operators @@ -178,10 +185,16 @@ data ServerOperator' s = ServerOperator serverDomains :: [Text], conditionsAcceptance :: ConditionsAcceptance, enabled :: Bool, - roles :: ServerRoles + smpRoles :: ServerRoles, + xftpRoles :: ServerRoles } deriving (Show) +operatorRoles :: UserProtocol p => SProtocolType p -> ServerOperator -> ServerRoles +operatorRoles p op = case p of + SPSMP -> smpRoles op + SPXFTP -> xftpRoles op + conditionsAccepted :: ServerOperator -> Bool conditionsAccepted ServerOperator {conditionsAcceptance} = case conditionsAcceptance of CAAccepted {} -> True @@ -336,8 +349,8 @@ updatedServerOperators presetOps storedOps = Just presetOp -> (storedOp' :) where storedOp' = case find ((operatorTag presetOp ==) . operatorTag) storedOps of - Just ServerOperator {operatorId, conditionsAcceptance, enabled, roles} -> - ASO SDBStored presetOp {operatorId, conditionsAcceptance, enabled, roles} + Just ServerOperator {operatorId, conditionsAcceptance, enabled, smpRoles, xftpRoles} -> + ASO SDBStored presetOp {operatorId, conditionsAcceptance, enabled, smpRoles, xftpRoles} Nothing -> ASO SDBNew presetOp -- This function should be used inside DB transaction to update servers. @@ -361,8 +374,8 @@ updatedUserServers p presetOps randomSrvs srvs = srvHost :: UserServer' s p -> NonEmpty TransportHost srvHost UserServer {server = ProtoServerWithAuth srv _} = host srv -agentServerCfgs :: [(Text, ServerOperator)] -> NonEmpty (NewUserServer p) -> [UserServer' s p] -> NonEmpty (ServerCfg p) -agentServerCfgs opDomains randomSrvs = +agentServerCfgs :: UserProtocol p => SProtocolType p -> [(Text, ServerOperator)] -> NonEmpty (NewUserServer p) -> [UserServer' s p] -> NonEmpty (ServerCfg p) +agentServerCfgs p opDomains randomSrvs = fromMaybe fallbackSrvs . L.nonEmpty . mapMaybe enabledOpAgentServer where fallbackSrvs = L.map (snd . agentServer) randomSrvs @@ -372,8 +385,8 @@ agentServerCfgs opDomains randomSrvs = agentServer :: UserServer' s p -> (Bool, ServerCfg p) agentServer srv@UserServer {server, enabled} = case find (\(d, _) -> any (matchingHost d) (srvHost srv)) opDomains of - Just (_, ServerOperator {operatorId = DBEntityId opId, enabled = opEnabled, roles}) -> - (opEnabled, ServerCfg {server, enabled, operator = Just opId, roles}) + Just (_, op@ServerOperator {operatorId = DBEntityId opId, enabled = opEnabled}) -> + (opEnabled, ServerCfg {server, enabled, operator = Just opId, roles = operatorRoles p op}) Nothing -> (True, ServerCfg {server, enabled, operator = Nothing, roles = allRoles}) @@ -423,7 +436,7 @@ validateUserServers curr others = currUserErrs <> concatMap otherUserErrs others p' = AProtocolType p noServers cond = not $ any srvEnabled $ snd $ partitionValid $ concatMap (`servers'` p) $ filter cond uss opEnabled = maybe True (\ServerOperator {enabled} -> enabled) . operator' - hasRole roleSel = maybe True (\ServerOperator {enabled, roles} -> enabled && roleSel roles) . operator' + hasRole roleSel = maybe True (\op@ServerOperator {enabled} -> enabled && roleSel (operatorRoles p op)) . operator' srvEnabled (AUS _ UserServer {deleted, enabled}) = enabled && not deleted serverErrs :: (UserServersClass u, ProtocolTypeI p, UserProtocol p) => SProtocolType p -> [u] -> [UserServersError] serverErrs p uss = map (USEInvalidServer p') invalidSrvs <> mapMaybe duplicateErr_ srvs @@ -472,6 +485,8 @@ instance DBStoredI s => FromJSON (ServerOperator' s) where $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "UCA") ''UsageConditionsAction) +$(JQ.deriveJSON defaultJSON ''ServerOperatorConditions) + instance ProtocolTypeI p => ToJSON (UserServer' s p) where toEncoding = $(JQ.mkToEncoding defaultJSON ''UserServer_) toJSON = $(JQ.mkToJSON defaultJSON ''UserServer_) diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index 87b5d2fd64..daf9a78fca 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -612,20 +612,21 @@ serverColumns p (ProtoServerWithAuth ProtocolServer {host, port, keyHash} auth_) auth = safeDecodeUtf8 . unBasicAuth <$> auth_ in (protocol, host, port, keyHash, auth) -getServerOperators :: DB.Connection -> ExceptT StoreError IO ([ServerOperator], Maybe UsageConditionsAction) +getServerOperators :: DB.Connection -> ExceptT StoreError IO ServerOperatorConditions getServerOperators db = do - currentConds <- getCurrentUsageConditions db + currentConditions <- getCurrentUsageConditions db liftIO $ do now <- getCurrentTime latestAcceptedConds_ <- getLatestAcceptedConditions db - let getConds op = (\ca -> op {conditionsAcceptance = ca}) <$> getOperatorConditions_ db op currentConds latestAcceptedConds_ now - operators <- mapM getConds =<< getServerOperators_ db - pure (operators, usageConditionsAction operators currentConds now) + let getConds op = (\ca -> op {conditionsAcceptance = ca}) <$> getOperatorConditions_ db op currentConditions latestAcceptedConds_ now + ops <- mapM getConds =<< getServerOperators_ db + let conditionsAction = usageConditionsAction ops currentConditions now + pure ServerOperatorConditions {serverOperators = ops, currentConditions, conditionsAction} getUserServers :: DB.Connection -> User -> ExceptT StoreError IO ([ServerOperator], [UserServer 'PSMP], [UserServer 'PXFTP]) getUserServers db user = (,,) - <$> (fst <$> getServerOperators db) + <$> (serverOperators <$> getServerOperators db) <*> liftIO (getProtocolServers db SPSMP user) <*> liftIO (getProtocolServers db SPXFTP user) @@ -635,15 +636,15 @@ setServerOperators db ops = do mapM_ (updateServerOperator db currentTs) ops updateServerOperator :: DB.Connection -> UTCTime -> ServerOperator -> IO () -updateServerOperator db currentTs ServerOperator {operatorId, enabled, roles = ServerRoles {storage, proxy}} = +updateServerOperator db currentTs ServerOperator {operatorId, enabled, smpRoles, xftpRoles} = DB.execute db [sql| UPDATE server_operators - SET enabled = ?, role_storage = ?, role_proxy = ?, updated_at = ? + SET enabled = ?, smp_role_storage = ?, smp_role_proxy = ?, xftp_role_storage = ?, xftp_role_proxy = ?, updated_at = ? WHERE server_operator_id = ? |] - (enabled, storage, proxy, operatorId, currentTs) + (enabled, storage smpRoles, proxy smpRoles, storage xftpRoles, proxy xftpRoles, currentTs, operatorId) getUpdateServerOperators :: DB.Connection -> NonEmpty PresetOperator -> Bool -> IO [ServerOperator] getUpdateServerOperators db presetOps newUser = do @@ -677,25 +678,25 @@ getUpdateServerOperators db presetOps newUser = do |] (conditionsId, conditionsCommit, notifiedAt, createdAt) updateOperator :: ServerOperator -> IO () - updateOperator ServerOperator {operatorId, tradeName, legalName, serverDomains, enabled, roles = ServerRoles {storage, proxy}} = + updateOperator ServerOperator {operatorId, tradeName, legalName, serverDomains, enabled, smpRoles, xftpRoles} = DB.execute db [sql| UPDATE server_operators - SET trade_name = ?, legal_name = ?, server_domains = ?, enabled = ?, role_storage = ?, role_proxy = ? + SET trade_name = ?, legal_name = ?, server_domains = ?, enabled = ?, smp_role_storage = ?, smp_role_proxy = ?, xftp_role_storage = ?, xftp_role_proxy = ? WHERE server_operator_id = ? |] - (tradeName, legalName, T.intercalate "," serverDomains, enabled, storage, proxy, operatorId) + (tradeName, legalName, T.intercalate "," serverDomains, enabled, storage smpRoles, proxy smpRoles, storage xftpRoles, proxy xftpRoles, operatorId) insertOperator :: NewServerOperator -> IO ServerOperator - insertOperator op@ServerOperator {operatorTag, tradeName, legalName, serverDomains, enabled, roles = ServerRoles {storage, proxy}} = do + insertOperator op@ServerOperator {operatorTag, tradeName, legalName, serverDomains, enabled, smpRoles, xftpRoles} = do DB.execute db [sql| INSERT INTO server_operators - (server_operator_tag, trade_name, legal_name, server_domains, enabled, role_storage, role_proxy) - VALUES (?,?,?,?,?,?,?) + (server_operator_tag, trade_name, legal_name, server_domains, enabled, smp_role_storage, smp_role_proxy, xftp_role_storage, xftp_role_proxy) + VALUES (?,?,?,?,?,?,?,?,?) |] - (operatorTag, tradeName, legalName, T.intercalate "," serverDomains, enabled, storage, proxy) + (operatorTag, tradeName, legalName, T.intercalate "," serverDomains, enabled, storage smpRoles, proxy smpRoles, storage xftpRoles, proxy xftpRoles) opId <- insertedRowId db pure op {operatorId = DBEntityId opId} autoAcceptConditions op UsageConditions {conditionsCommit} = @@ -706,15 +707,15 @@ serverOperatorQuery :: Query serverOperatorQuery = [sql| SELECT server_operator_id, server_operator_tag, trade_name, legal_name, - server_domains, enabled, role_storage, role_proxy + server_domains, enabled, smp_role_storage, smp_role_proxy, xftp_role_storage, xftp_role_proxy FROM server_operators |] getServerOperators_ :: DB.Connection -> IO [ServerOperator] getServerOperators_ db = map toServerOperator <$> DB.query_ db serverOperatorQuery -toServerOperator :: (DBEntityId, Maybe OperatorTag, Text, Maybe Text, Text, Bool, Bool, Bool) -> ServerOperator -toServerOperator (operatorId, operatorTag, tradeName, legalName, domains, enabled, storage, proxy) = +toServerOperator :: (DBEntityId, Maybe OperatorTag, Text, Maybe Text, Text, Bool) :. (Bool, Bool) :. (Bool, Bool) -> ServerOperator +toServerOperator ((operatorId, operatorTag, tradeName, legalName, domains, enabled) :. smpRoles' :. xftpRoles') = ServerOperator { operatorId, operatorTag, @@ -723,8 +724,11 @@ toServerOperator (operatorId, operatorTag, tradeName, legalName, domains, enable serverDomains = T.splitOn "," domains, conditionsAcceptance = CARequired Nothing, enabled, - roles = ServerRoles {storage, proxy} + smpRoles = serverRoles smpRoles', + xftpRoles = serverRoles xftpRoles' } + where + serverRoles (storage, proxy) = ServerRoles {storage, proxy} getOperatorConditions_ :: DB.Connection -> ServerOperator -> UsageConditions -> Maybe UsageConditions -> UTCTime -> IO ConditionsAcceptance getOperatorConditions_ db ServerOperator {operatorId} UsageConditions {conditionsCommit = currentCommit, createdAt, notifiedAt} latestAcceptedConds_ now = do diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 317fd58a8e..e4c0fd5606 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -65,7 +65,7 @@ import qualified Simplex.Messaging.Crypto.Ratchet as CR import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (dropPrefix, taggedObjectJSON) -import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType, ProtocolServer (..), ProtocolTypeI, SProtocolType (..)) +import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType, ProtocolServer (..), ProtocolTypeI, SProtocolType (..), UserProtocol) import qualified Simplex.Messaging.Protocol as SMP import Simplex.Messaging.Transport.Client (TransportHost (..)) import Simplex.Messaging.Util (safeDecodeUtf8, tshow) @@ -98,7 +98,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRApiChat u chat _ -> ttyUser u $ if testView then testViewChat chat else [viewJSON chat] CRApiParsedMarkdown ft -> [viewJSON ft] CRServerTestResult u srv testFailure -> ttyUser u $ viewServerTestResult srv testFailure - CRServerOperators ops ca -> viewServerOperators ops ca + CRServerOperatorConditions (ServerOperatorConditions ops _ ca) -> viewServerOperators ops ca CRUserServers u uss -> ttyUser u $ concatMap viewUserServers uss <> (if testView then [] else serversUserHelp) CRUserServersValidation {} -> [] CRUsageConditions {} -> [] @@ -1221,15 +1221,27 @@ viewUserServers UserOperatorServers {operator, smpServers, xftpServers} = <> viewServers SPSMP smpServers <> viewServers SPXFTP xftpServers where - viewServers :: ProtocolTypeI p => SProtocolType p -> [UserServer p] -> [StyledString] + viewServers :: (ProtocolTypeI p, UserProtocol p) => SProtocolType p -> [UserServer p] -> [StyledString] viewServers _ [] = [] - viewServers p srvs = [" " <> protocolName p <> " servers"] <> map (plain . (" " <> ) . viewServer) srvs + viewServers p srvs + | maybe True (\ServerOperator {enabled} -> enabled) operator = + [" " <> protocolName p <> " servers" <> maybe "" ((" " <>) . viewRoles) operator] + <> map (plain . (" " <> ) . viewServer) srvs + | otherwise = [] where viewServer UserServer {server, preset, tested, enabled} = safeDecodeUtf8 (strEncode server) <> serverInfo where serverInfo = if null serverInfo_ then "" else parens $ T.intercalate ", " serverInfo_ serverInfo_ = ["preset" | preset] <> testedInfo <> ["disabled" | not enabled] testedInfo = maybe [] (\t -> ["test: " <> if t then "passed" else "failed"]) tested + viewRoles op@ServerOperator {enabled} + | not enabled = "disabled" + | storage rs && proxy rs = "enabled" + | storage rs = "enabled storage" + | proxy rs = "enabled proxy" + | otherwise = "disabled (servers known)" + where + rs = operatorRoles p op serversUserHelp :: [StyledString] serversUserHelp = @@ -1272,8 +1284,8 @@ viewOperator op@ServerOperator {tradeName, legalName, serverDomains, conditionsA <> (", " <> viewOpEnabled op) shortViewOperator :: ServerOperator -> Text -shortViewOperator op@ServerOperator {operatorId = DBEntityId opId, tradeName} = - tshow opId <> ". " <> tradeName <> parens (viewOpEnabled op) +shortViewOperator ServerOperator {operatorId = DBEntityId opId, tradeName, enabled} = + tshow opId <> ". " <> tradeName <> parens (if enabled then "enabled" else "disabled") viewOpIdTag :: ServerOperator' s -> Text viewOpIdTag ServerOperator {operatorId, operatorTag} = case operatorId of @@ -1290,11 +1302,19 @@ viewOpConditions = \case viewCond w ts = w <> maybe "" (parens . tshow) ts viewOpEnabled :: ServerOperator' s -> Text -viewOpEnabled ServerOperator {enabled, roles = ServerRoles {storage, proxy}} - | enabled && storage && proxy = "enabled" - | enabled && storage = "enabled storage" - | enabled && proxy = "enabled proxy" - | otherwise = "disabled" +viewOpEnabled ServerOperator {enabled, smpRoles, xftpRoles} + | not enabled = "disabled" + | no smpRoles && no xftpRoles = "disabled (servers known)" + | both smpRoles && both xftpRoles = "enabled" + | otherwise = "SMP " <> viewRoles smpRoles <> ", XFTP" <> viewRoles xftpRoles + where + no rs = not $ storage rs || proxy rs + both rs = storage rs && proxy rs + viewRoles rs + | both rs = "enabled" + | storage rs = "enabled storage" + | proxy rs = "enabled proxy" + | otherwise = "disabled (servers known)" viewConditionsAction :: UsageConditionsAction -> [StyledString] viewConditionsAction = \case @@ -1382,12 +1402,6 @@ viewConnectionStats ConnectionStats {rcvQueuesInfo, sndQueuesInfo} = ["receiving messages via: " <> viewRcvQueuesInfo rcvQueuesInfo | not $ null rcvQueuesInfo] <> ["sending messages via: " <> viewSndQueuesInfo sndQueuesInfo | not $ null sndQueuesInfo] --- viewServers :: ProtocolTypeI p => [ServerOperator] -> NonEmpty (ServerCfg p) -> [StyledString] --- viewServers operators = map (plain . (\ServerCfg {server, operator} -> B.unpack (strEncode server) <> viewOperator operator)) . L.toList --- where --- ops :: Map (Maybe DBEntityId) Text = foldl' (\m ServerOperator {operatorId, tradeName} -> M.insert (Just operatorId) tradeName m) M.empty operators --- viewOperator = maybe "" $ \op -> " (operator " <> maybe (show op) T.unpack (M.lookup (Just op) ops) <> ")" - viewRcvQueuesInfo :: [RcvQueueInfo] -> StyledString viewRcvQueuesInfo = plain . intercalate ", " . map showQueueInfo where diff --git a/tests/OperatorTests.hs b/tests/OperatorTests.hs index 1b867a3e1d..4966bfbb97 100644 --- a/tests/OperatorTests.hs +++ b/tests/OperatorTests.hs @@ -29,7 +29,7 @@ validateServers = describe "validate user servers" $ do validateUserServers [invalidDisabled] [] `shouldBe` [USENoServers aSMP Nothing] validateUserServers [invalidDisabledOp] [] `shouldBe` [USENoServers aSMP Nothing, USENoServers aXFTP Nothing] it "should fail without servers with storage role" $ do - validateUserServers [invalidNoStorage] [] `shouldBe` [USEStorageMissing aSMP Nothing, USEStorageMissing aXFTP Nothing] + validateUserServers [invalidNoStorage] [] `shouldBe` [USEStorageMissing aSMP Nothing] it "should fail with duplicate host" $ do validateUserServers [invalidDuplicate] [] `shouldBe` [ USEDuplicateServer aSMP "smp://0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU=@smp8.simplex.im,beccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion" "smp8.simplex.im", @@ -71,7 +71,7 @@ invalidDisabledOp = invalidNoStorage :: UpdatedUserOperatorServers invalidNoStorage = (valid :: UpdatedUserOperatorServers) - { operator = Just operatorSimpleXChat {operatorId = DBEntityId 1, roles = allRoles {storage = False}} + { operator = Just operatorSimpleXChat {operatorId = DBEntityId 1, smpRoles = allRoles {storage = False}} } invalidDuplicate :: UpdatedUserOperatorServers From b605ebfd2adf3b426d0abc1a4fe00bd54740b48b Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 15 Nov 2024 12:14:53 +0000 Subject: [PATCH 075/567] core: remove comments --- src/Simplex/Chat/Controller.hs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 7fb811255f..c085dcf470 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -956,24 +956,6 @@ instance ToJSON AgentQueueId where toJSON = strToJSON toEncoding = strToJEncoding --- data ProtoServersConfig p = ProtoServersConfig {servers :: [ServerCfg p]} --- deriving (Show) - --- data AProtoServersConfig = forall p. ProtocolTypeI p => APSC (SProtocolType p) (ProtoServersConfig p) - --- deriving instance Show AProtoServersConfig - --- data UserProtoServers p = UserProtoServers --- { serverProtocol :: SProtocolType p, --- protoServers :: NonEmpty (ServerCfg p), --- presetServers :: NonEmpty (ServerCfg p) --- } --- deriving (Show) - --- data AUserProtoServers = forall p. (ProtocolTypeI p, UserProtocol p) => AUPS (UserProtoServers p) - --- deriving instance Show AUserProtoServers - data ArchiveConfig = ArchiveConfig {archivePath :: FilePath, disableCompression :: Maybe Bool, parentTempDirectory :: Maybe FilePath} deriving (Show) From 6843269cff36b3b167f4c74e8bb7c4ef4d4468ef Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sun, 17 Nov 2024 10:25:03 +0000 Subject: [PATCH 076/567] core: 6.2.0.0 (simplexmq: 6.2.0.3) --- cabal.project | 2 +- package.yaml | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- src/Simplex/Chat/Remote.hs | 4 ++-- tests/ChatClient.hs | 4 +++- 6 files changed, 9 insertions(+), 7 deletions(-) diff --git a/cabal.project b/cabal.project index c9b8b11722..7cc11fc583 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: ffecf200d4874dfa34f6d15b269964c0115a54ca + tag: a64c1aa2c41938c5e18cc49d08075f14e5d25f0d source-repository-package type: git diff --git a/package.yaml b/package.yaml index 94dc13ad2e..7bfa560a26 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.1.1.0 +version: 6.2.0.0 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 8de91675e3..b63bafcd96 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."ffecf200d4874dfa34f6d15b269964c0115a54ca" = "0kb8hq37fc5g198wq7dswnlwjzk67q8rrzil2dii5lc6xfr47jbs"; + "https://github.com/simplex-chat/simplexmq.git"."a64c1aa2c41938c5e18cc49d08075f14e5d25f0d" = "1kf86vrh5zfrqyczfjcj3d2nagmqb0rwhhdc10fw5n8jcgmdw6rp"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index fb7f32faa5..4a5c3b6970 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.1.1.0 +version: 6.2.0.0 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index 88539b55e3..320a35815d 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -73,11 +73,11 @@ import UnliftIO.Directory (copyFile, createDirectoryIfMissing, doesDirectoryExis -- when acting as host minRemoteCtrlVersion :: AppVersion -minRemoteCtrlVersion = AppVersion [6, 1, 0, 8] +minRemoteCtrlVersion = AppVersion [6, 2, 0, 0] -- when acting as controller minRemoteHostVersion :: AppVersion -minRemoteHostVersion = AppVersion [6, 1, 0, 8] +minRemoteHostVersion = AppVersion [6, 2, 0, 0] currentAppVersion :: AppVersion currentAppVersion = AppVersion SC.version diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index 75b85d7a5f..73f3e350df 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -439,6 +439,8 @@ smpServerCfg = controlPortUserAuth = Nothing, controlPortAdminAuth = Nothing, messageExpiration = Just defaultMessageExpiration, + expireMessagesOnStart = False, + idleQueueInterval = defaultIdleQueueInterval, notificationExpiration = defaultNtfExpiration, inactiveClientExpiration = Just defaultInactiveClientExpiration, smpCredentials = @@ -524,7 +526,7 @@ serverBracket server f = do started <- newEmptyTMVarIO bracket (forkIOWithUnmask ($ server started)) - (\t -> killThread t >> waitFor started "stop") + (\t -> killThread t >> waitFor started "stop" >> threadDelay 100000) (\_ -> waitFor started "start" >> f) where waitFor started s = From e645dd99e7cd41ef2b3efdc2c13106cb09cd4554 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sun, 17 Nov 2024 22:37:18 +0000 Subject: [PATCH 077/567] 6.2-beta.0: ios 246, android 251, desktop 75 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 56 +++++++++++----------- apps/multiplatform/gradle.properties | 8 ++-- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index cd146d4292..4061ef0f9c 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -149,9 +149,9 @@ 6419EC582AB97507004A607A /* CIMemberCreatedContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */; }; 6432857C2925443C00FBE5C8 /* GroupPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */; }; 643B3B452CCBEB080083A2CF /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B402CCBEB080083A2CF /* libgmpxx.a */; }; - 643B3B462CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5-ghc9.6.3.a */; }; + 643B3B462CCBEB080083A2CF /* libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs-ghc9.6.3.a */; }; 643B3B472CCBEB080083A2CF /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B422CCBEB080083A2CF /* libffi.a */; }; - 643B3B482CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5.a */; }; + 643B3B482CCBEB080083A2CF /* libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs.a */; }; 643B3B492CCBEB080083A2CF /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B442CCBEB080083A2CF /* libgmp.a */; }; 6440CA00288857A10062C672 /* CIEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440C9FF288857A10062C672 /* CIEventView.swift */; }; 6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */; }; @@ -492,9 +492,9 @@ 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMemberCreatedContactView.swift; sourceTree = ""; }; 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupPreferencesView.swift; sourceTree = ""; }; 643B3B402CCBEB080083A2CF /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgmpxx.a; path = Libraries/libgmpxx.a; sourceTree = ""; }; - 643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5-ghc9.6.3.a"; path = "Libraries/libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5-ghc9.6.3.a"; sourceTree = ""; }; + 643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs-ghc9.6.3.a"; path = "Libraries/libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs-ghc9.6.3.a"; sourceTree = ""; }; 643B3B422CCBEB080083A2CF /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libffi.a; path = Libraries/libffi.a; sourceTree = ""; }; - 643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5.a"; path = "Libraries/libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5.a"; sourceTree = ""; }; + 643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs.a"; path = "Libraries/libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs.a"; sourceTree = ""; }; 643B3B442CCBEB080083A2CF /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgmp.a; path = Libraries/libgmp.a; sourceTree = ""; }; 6440C9FF288857A10062C672 /* CIEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIEventView.swift; sourceTree = ""; }; 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupMembersView.swift; sourceTree = ""; }; @@ -663,8 +663,8 @@ 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - 643B3B482CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5.a in Frameworks */, - 643B3B462CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5-ghc9.6.3.a in Frameworks */, + 643B3B482CCBEB080083A2CF /* libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs.a in Frameworks */, + 643B3B462CCBEB080083A2CF /* libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs-ghc9.6.3.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -815,8 +815,8 @@ 643B3B422CCBEB080083A2CF /* libffi.a */, 643B3B442CCBEB080083A2CF /* libgmp.a */, 643B3B402CCBEB080083A2CF /* libgmpxx.a */, - 643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5-ghc9.6.3.a */, - 643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.1.1.0-CTfGB7l09cqEHVIdvhrnH5.a */, + 643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs-ghc9.6.3.a */, + 643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs.a */, 5CA059C2279559F40002BEB4 /* Shared */, 5CDCAD462818589900503DA2 /* SimpleX NSE */, CEE723A82C3BD3D70009AE93 /* SimpleX SE */, @@ -1903,7 +1903,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 245; + CURRENT_PROJECT_VERSION = 246; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1928,7 +1928,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES_THIN; - MARKETING_VERSION = 6.1.1; + MARKETING_VERSION = 6.2; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1952,7 +1952,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 245; + CURRENT_PROJECT_VERSION = 246; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1977,7 +1977,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.1.1; + MARKETING_VERSION = 6.2; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1993,11 +1993,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 245; + CURRENT_PROJECT_VERSION = 246; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.1.1; + MARKETING_VERSION = 6.2; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2013,11 +2013,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 245; + CURRENT_PROJECT_VERSION = 246; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.1.1; + MARKETING_VERSION = 6.2; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2038,7 +2038,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 245; + CURRENT_PROJECT_VERSION = 246; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2053,7 +2053,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.1.1; + MARKETING_VERSION = 6.2; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2075,7 +2075,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 245; + CURRENT_PROJECT_VERSION = 246; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2090,7 +2090,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.1.1; + MARKETING_VERSION = 6.2; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2112,7 +2112,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 245; + CURRENT_PROJECT_VERSION = 246; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2138,7 +2138,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.1.1; + MARKETING_VERSION = 6.2; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2163,7 +2163,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 245; + CURRENT_PROJECT_VERSION = 246; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2189,7 +2189,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.1.1; + MARKETING_VERSION = 6.2; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2214,7 +2214,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 245; + CURRENT_PROJECT_VERSION = 246; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2229,7 +2229,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.1.1; + MARKETING_VERSION = 6.2; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2248,7 +2248,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 245; + CURRENT_PROJECT_VERSION = 246; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2263,7 +2263,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.1.1; + MARKETING_VERSION = 6.2; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index d795257a76..e105e61dde 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -24,11 +24,11 @@ android.nonTransitiveRClass=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.1.1 -android.version_code=249 +android.version_name=6.2-beta.0 +android.version_code=251 -desktop.version_name=6.1.1 -desktop.version_code=74 +desktop.version_name=6.2-beta.0 +desktop.version_code=75 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From d1ae3ba2d39fdd2aa9c67a9fa6ab5de7ddf955aa Mon Sep 17 00:00:00 2001 From: Diogo Date: Mon, 18 Nov 2024 15:36:54 +0000 Subject: [PATCH 078/567] desktop, android: add address card to chat list and remove address from onboarding (#5177) * desktop, android: add address card to chat list * add create address button to address learn more view * envelope size to match avatars * refactor * no color for info icon * envelope padding * remove address from onboarding * show create in address card info * backwards compatibility for address onboarding step * paddings between cards * paddings * toolbar -> chats -> cards * dont hide address card * update string --------- Co-authored-by: Evgeny Poberezkin --- .../kotlin/chat/simplex/common/App.kt | 3 +- .../chat/simplex/common/model/SimpleXAPI.kt | 3 + .../chat/simplex/common/platform/Core.kt | 7 +- .../chat/simplex/common/views/WelcomeView.kt | 6 +- .../common/views/chatlist/ChatListView.kt | 200 +++++++++++++----- .../views/onboarding/CreateSimpleXAddress.kt | 197 ----------------- .../views/onboarding/SetNotificationsMode.kt | 14 ++ .../onboarding/SetupDatabasePassphrase.kt | 2 +- .../usersettings/UserAddressLearnMore.kt | 27 ++- .../views/usersettings/UserAddressView.kt | 49 +++-- .../commonMain/resources/MR/base/strings.xml | 1 + 11 files changed, 233 insertions(+), 276 deletions(-) delete mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index ee38cb80fe..7af1d574ad 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -194,7 +194,8 @@ fun MainScreen() { OnboardingStage.Step2_CreateProfile -> CreateFirstProfile(chatModel) {} OnboardingStage.LinkAMobile -> LinkAMobile() OnboardingStage.Step2_5_SetupDatabasePassphrase -> SetupDatabasePassphrase(chatModel) - OnboardingStage.Step3_CreateSimpleXAddress -> CreateSimpleXAddress(chatModel, null) + // Ensure backwards compatibility with old onboarding stage for address creation, otherwise notification setup would be skipped + OnboardingStage.Step3_CreateSimpleXAddress -> SetNotificationsMode(chatModel) OnboardingStage.Step4_SetNotificationsMode -> SetNotificationsMode(chatModel) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index fab85fa679..5674920914 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -179,6 +179,7 @@ class AppPreferences { val liveMessageAlertShown = mkBoolPreference(SHARED_PREFS_LIVE_MESSAGE_ALERT_SHOWN, false) val showHiddenProfilesNotice = mkBoolPreference(SHARED_PREFS_SHOW_HIDDEN_PROFILES_NOTICE, true) val oneHandUICardShown = mkBoolPreference(SHARED_PREFS_ONE_HAND_UI_CARD_SHOWN, false) + val addressCreationCardShown = mkBoolPreference(SHARED_PREFS_ADDRESS_CREATION_CARD_SHOWN, false) val showMuteProfileAlert = mkBoolPreference(SHARED_PREFS_SHOW_MUTE_PROFILE_ALERT, true) val appLanguage = mkStrPreference(SHARED_PREFS_APP_LANGUAGE, null) val appUpdateChannel = mkEnumPreference(SHARED_PREFS_APP_UPDATE_CHANNEL, AppUpdatesChannel.DISABLED) { AppUpdatesChannel.entries.firstOrNull { it.name == this } } @@ -254,6 +255,7 @@ class AppPreferences { val hintPreferences: List, Boolean>> = listOf( laNoticeShown to false, oneHandUICardShown to false, + addressCreationCardShown to false, liveMessageAlertShown to false, showHiddenProfilesNotice to true, showMuteProfileAlert to true, @@ -408,6 +410,7 @@ class AppPreferences { private const val SHARED_PREFS_LIVE_MESSAGE_ALERT_SHOWN = "LiveMessageAlertShown" private const val SHARED_PREFS_SHOW_HIDDEN_PROFILES_NOTICE = "ShowHiddenProfilesNotice" private const val SHARED_PREFS_ONE_HAND_UI_CARD_SHOWN = "OneHandUICardShown" + private const val SHARED_PREFS_ADDRESS_CREATION_CARD_SHOWN = "AddressCreationCardShown" private const val SHARED_PREFS_SHOW_MUTE_PROFILE_ALERT = "ShowMuteProfileAlert" private const val SHARED_PREFS_STORE_DB_PASSPHRASE = "StoreDBPassphrase" private const val SHARED_PREFS_INITIAL_RANDOM_DB_PASSPHRASE = "InitialRandomDBPassphrase" diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt index 57b93d4d6e..79132b5eb1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt @@ -137,8 +137,13 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat } } else if (startChat().await()) { val savedOnboardingStage = appPreferences.onboardingStage.get() + val next = if (appPlatform.isAndroid) { + OnboardingStage.Step4_SetNotificationsMode + } else { + OnboardingStage.OnboardingComplete + } val newStage = if (listOf(OnboardingStage.Step1_SimpleXInfo, OnboardingStage.Step2_CreateProfile).contains(savedOnboardingStage) && chatModel.users.size == 1) { - OnboardingStage.Step3_CreateSimpleXAddress + next } else { savedOnboardingStage } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt index e1e3dcb56b..17658d23e8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt @@ -165,7 +165,7 @@ fun createProfileInNoProfileSetup(displayName: String, close: () -> Unit) { if (!chatModel.connectedToRemote()) { chatModel.localUserCreated.value = true } - controller.appPrefs.onboardingStage.set(OnboardingStage.Step3_CreateSimpleXAddress) + controller.appPrefs.onboardingStage.set(OnboardingStage.Step4_SetNotificationsMode) controller.startChat(user) controller.switchUIRemoteHost(null) close() @@ -181,7 +181,7 @@ fun createProfileInProfiles(chatModel: ChatModel, displayName: String, close: () chatModel.currentUser.value = user if (chatModel.users.isEmpty()) { chatModel.controller.startChat(user) - chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step3_CreateSimpleXAddress) + chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step4_SetNotificationsMode) } else { val users = chatModel.controller.listUsers(rhId) chatModel.users.clear() @@ -204,7 +204,7 @@ fun createProfileOnboarding(chatModel: ChatModel, displayName: String, close: () onboardingStage.set(if (appPlatform.isDesktop && chatModel.controller.appPrefs.initialRandomDBPassphrase.get() && !chatModel.desktopOnboardingRandomPassword.value) { OnboardingStage.Step2_5_SetupDatabasePassphrase } else { - OnboardingStage.Step3_CreateSimpleXAddress + OnboardingStage.Step4_SetNotificationsMode }) } else { // the next two lines are only needed for failure case when because of the database error the app gets stuck on on-boarding screen, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 586bca87d0..9661a305cc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -34,7 +34,9 @@ import chat.simplex.common.platform.* import chat.simplex.common.views.call.Call import chat.simplex.common.views.chat.item.CIFileViewScope import chat.simplex.common.views.chat.topPaddingToContent +import chat.simplex.common.views.mkValidName import chat.simplex.common.views.newchat.* +import chat.simplex.common.views.showInvalidNameAlert import chat.simplex.common.views.usersettings.* import chat.simplex.res.MR import kotlinx.coroutines.* @@ -72,58 +74,39 @@ private fun showNewChatSheet(oneHandUI: State) { @Composable fun ToggleChatListCard() { - Column( - modifier = Modifier - .padding(16.dp) - .clip(RoundedCornerShape(18.dp)) + ChatListCard( + close = { + appPrefs.oneHandUICardShown.set(true) + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.one_hand_ui), + text = generalGetString(MR.strings.one_hand_ui_change_instruction), + ) + } ) { - Box( + Column( modifier = Modifier - .background(MaterialTheme.appColors.sentMessage) + .padding(horizontal = DEFAULT_PADDING) + .padding(top = DEFAULT_PADDING) ) { - Box( - modifier = Modifier.fillMaxWidth().matchParentSize().padding(5.dp), - contentAlignment = Alignment.TopEnd + Row( + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() ) { - IconButton( - onClick = { - appPrefs.oneHandUICardShown.set(true) - AlertManager.shared.showAlertMsg( - title = generalGetString(MR.strings.one_hand_ui), - text = generalGetString(MR.strings.one_hand_ui_change_instruction), - ) - } - ) { - Icon( - painterResource(MR.images.ic_close), stringResource(MR.strings.back), tint = MaterialTheme.colors.secondary - ) - } + Text(stringResource(MR.strings.one_hand_ui_card_title), style = MaterialTheme.typography.h3) } - Column( - modifier = Modifier - .padding(horizontal = DEFAULT_PADDING) - .padding(top = DEFAULT_PADDING) + Row( + Modifier.fillMaxWidth().padding(top = 6.dp, bottom = 12.dp), + verticalAlignment = Alignment.CenterVertically ) { - Row( - horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(MR.strings.one_hand_ui_card_title), style = MaterialTheme.typography.h3) - } - Row( - Modifier.fillMaxWidth().padding(top = 6.dp, bottom = 12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text(stringResource(MR.strings.one_hand_ui), Modifier.weight(10f), style = MaterialTheme.typography.body1) + Text(stringResource(MR.strings.one_hand_ui), Modifier.weight(10f), style = MaterialTheme.typography.body1) - Spacer(Modifier.fillMaxWidth().weight(1f)) + Spacer(Modifier.fillMaxWidth().weight(1f)) - SharedPreferenceToggle( - appPrefs.oneHandUI, - enabled = true - ) - } + SharedPreferenceToggle( + appPrefs.oneHandUI, + enabled = true + ) } } } @@ -196,6 +179,94 @@ fun ChatListView(chatModel: ChatModel, userPickerState: MutableStateFlow Unit, + onCardClick: (() -> Unit)? = null, + content: @Composable BoxScope.() -> Unit +) { + Column( + modifier = Modifier.clip(RoundedCornerShape(18.dp)) + ) { + Box( + modifier = Modifier + .background(MaterialTheme.appColors.sentMessage) + .clickable { + onCardClick?.invoke() + } + ) { + Box( + modifier = Modifier.fillMaxWidth().matchParentSize().padding(5.dp), + contentAlignment = Alignment.TopEnd + ) { + IconButton( + onClick = { + close() + } + ) { + Icon( + painterResource(MR.images.ic_close), stringResource(MR.strings.back), tint = MaterialTheme.colors.secondary + ) + } + } + content() + } + } +} + +@Composable +private fun AddressCreationCard() { + ChatListCard( + close = { + appPrefs.addressCreationCardShown.set(true) + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.simplex_address), + text = generalGetString(MR.strings.address_creation_instruction), + ) + }, + onCardClick = { + ModalManager.start.showModal { + UserAddressLearnMore(showCreateAddressButton = true) + } + } + ) { + Box(modifier = Modifier.matchParentSize().padding(end = (DEFAULT_PADDING_HALF + 2.dp) * fontSizeSqrtMultiplier, bottom = 2.dp), contentAlignment = Alignment.BottomEnd) { + TextButton( + onClick = { + ModalManager.start.showModalCloseable { close -> + UserAddressView(chatModel = chatModel, shareViaProfile = false, autoCreateAddress = true, close = close) + } + }, + ) { + Text(stringResource(MR.strings.create_address_button), style = MaterialTheme.typography.body1) + } + } + Row( + Modifier + .fillMaxWidth() + .padding(DEFAULT_PADDING), + verticalAlignment = Alignment.CenterVertically + ) { + Box(Modifier.padding(vertical = 4.dp)) { + Box(Modifier.background(MaterialTheme.colors.primary, CircleShape).padding(12.dp)) { + ProfileImage(size = 37.dp, null, icon = MR.images.ic_mail_filled, color = Color.White, backgroundColor = Color.Red) + } + } + Column(modifier = Modifier.padding(start = DEFAULT_PADDING)) { + Text(stringResource(MR.strings.your_simplex_contact_address), style = MaterialTheme.typography.h3) + Spacer(Modifier.fillMaxWidth().padding(DEFAULT_PADDING_HALF)) + Row(verticalAlignment = Alignment.CenterVertically) { + Text(stringResource(MR.strings.how_to_use_simplex_chat), Modifier.padding(end = DEFAULT_SPACE_AFTER_ICON), style = MaterialTheme.typography.body1) + Icon( + painterResource(MR.images.ic_info), + null, + ) + } + } + } + } +} + @Composable private fun BoxScope.ChatListWithLoadingScreen(searchText: MutableState, listState: LazyListState) { if (!chatModel.desktopNoUserNoRemote) { @@ -641,6 +712,7 @@ private fun BoxScope.ChatList(searchText: MutableState, listStat val keyboardState by getKeyboardState() val oneHandUI = remember { appPrefs.oneHandUI.state } val oneHandUICardShown = remember { appPrefs.oneHandUICardShown.state } + val addressCreationCardShown = remember { appPrefs.addressCreationCardShown.state } LaunchedEffect(listState.firstVisibleItemIndex, listState.firstVisibleItemScrollOffset) { val currentIndex = listState.firstVisibleItemIndex @@ -709,20 +781,15 @@ private fun BoxScope.ChatList(searchText: MutableState, listStat } } } - if (!oneHandUICardShown.value && chats.size > 1) { - item { - ToggleChatListCard() - } - } itemsIndexed(chats, key = { _, chat -> chat.remoteHostId to chat.id }) { index, chat -> val nextChatSelected = remember(chat.id, chats) { derivedStateOf { chatModel.chatId.value != null && chats.getOrNull(index + 1)?.id == chatModel.chatId.value } } ChatListNavLinkView(chat, nextChatSelected) } - if (!oneHandUICardShown.value && chats.size <= 1) { + if (!oneHandUICardShown.value || !addressCreationCardShown.value) { item { - ToggleChatListCard() + ChatListFeatureCards() } } if (appPlatform.isAndroid) { @@ -741,7 +808,36 @@ private fun BoxScope.ChatList(searchText: MutableState, listStat } if (!oneHandUICardShown.value) { LaunchedEffect(chats.size) { - if (chats.size >= 3) appPrefs.oneHandUICardShown.set(true) + if (chats.size >= 3) { + appPrefs.oneHandUICardShown.set(true) + } + } + } + + if (!addressCreationCardShown.value) { + LaunchedEffect(chatModel.userAddress.value) { + if (chatModel.userAddress.value != null) { + appPrefs.addressCreationCardShown.set(true) + } + } + } +} + +@Composable +private fun ChatListFeatureCards() { + val oneHandUI = remember { appPrefs.oneHandUI.state } + val oneHandUICardShown = remember { appPrefs.oneHandUICardShown.state } + val addressCreationCardShown = remember { appPrefs.addressCreationCardShown.state } + + Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { + if (!oneHandUICardShown.value && !oneHandUI.value) { + ToggleChatListCard() + } + if (!addressCreationCardShown.value) { + AddressCreationCard() + } + if (!oneHandUICardShown.value && oneHandUI.value) { + ToggleChatListCard() } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt deleted file mode 100644 index 28ad0fdb7b..0000000000 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt +++ /dev/null @@ -1,197 +0,0 @@ -package chat.simplex.common.views.onboarding - -import androidx.compose.foundation.* -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.* -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalClipboardManager -import androidx.compose.ui.platform.LocalUriHandler -import androidx.compose.ui.text.font.FontWeight -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import chat.simplex.common.model.* -import chat.simplex.common.platform.* -import chat.simplex.common.ui.theme.* -import chat.simplex.common.views.helpers.* -import chat.simplex.common.views.newchat.SimpleXLinkQRCode -import chat.simplex.common.views.newchat.simplexChatLink -import chat.simplex.res.MR - -@Composable -fun CreateSimpleXAddress(m: ChatModel, rhId: Long?) { - var progressIndicator by remember { mutableStateOf(false) } - val userAddress = remember { m.userAddress } - val clipboard = LocalClipboardManager.current - val uriHandler = LocalUriHandler.current - - LaunchedEffect(Unit) { - prepareChatBeforeAddressCreation(rhId) - } - - CreateSimpleXAddressLayout( - userAddress.value, - share = { address: String -> clipboard.shareText(address) }, - sendEmail = { address -> - uriHandler.sendEmail( - generalGetString(MR.strings.email_invite_subject), - generalGetString(MR.strings.email_invite_body).format(simplexChatLink(address.connReqContact)) - ) - }, - createAddress = { - withBGApi { - progressIndicator = true - val connReqContact = m.controller.apiCreateUserAddress(rhId) - if (connReqContact != null) { - m.userAddress.value = UserContactLinkRec(connReqContact) - progressIndicator = false - } - } - }, - nextStep = { - val next = if (appPlatform.isAndroid) { - OnboardingStage.Step4_SetNotificationsMode - } else { - OnboardingStage.OnboardingComplete - } - m.controller.appPrefs.onboardingStage.set(next) - }, - ) - - if (progressIndicator) { - ProgressIndicator() - } -} - -@Composable -private fun CreateSimpleXAddressLayout( - userAddress: UserContactLinkRec?, - share: (String) -> Unit, - sendEmail: (UserContactLinkRec) -> Unit, - createAddress: () -> Unit, - nextStep: () -> Unit, -) { - CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { - ModalView({}, showClose = false) { - ColumnWithScrollBar( - horizontalAlignment = Alignment.CenterHorizontally, - ) { - AppBarTitle(stringResource(MR.strings.simplex_address)) - - Spacer(Modifier.weight(1f)) - - if (userAddress != null) { - SimpleXLinkQRCode(userAddress.connReqContact) - Spacer(Modifier.height(DEFAULT_PADDING_HALF)) - Row { - ShareAddressButton { share(simplexChatLink(userAddress.connReqContact)) } - Spacer(Modifier.width(DEFAULT_PADDING * 2)) - ShareViaEmailButton { sendEmail(userAddress) } - } - Spacer(Modifier.height(DEFAULT_PADDING)) - Spacer(Modifier.weight(1f)) - Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { - OnboardingActionButton( - modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp), - labelId = MR.strings.continue_to_next_step, - onboarding = null, - onclick = nextStep - ) - // Reserve space - TextButtonBelowOnboardingButton("", null) - } - } else { - Button(createAddress, Modifier, shape = CircleShape, contentPadding = PaddingValues()) { - Icon(painterResource(MR.images.ic_mail_filled), null, Modifier.size(100.dp).background(MaterialTheme.colors.primary, CircleShape).padding(25.dp), tint = Color.White) - } - Spacer(Modifier.height(DEFAULT_PADDING)) - Spacer(Modifier.weight(1f)) - Text(stringResource(MR.strings.create_simplex_address), style = MaterialTheme.typography.h3, fontWeight = FontWeight.Bold) - TextBelowButton(stringResource(MR.strings.you_can_make_address_visible_via_settings)) - Spacer(Modifier.height(DEFAULT_PADDING)) - Spacer(Modifier.weight(1f)) - - Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { - OnboardingActionButton( - modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp), - labelId = MR.strings.create_address_button, - onboarding = null, - onclick = createAddress - ) - TextButtonBelowOnboardingButton(stringResource(MR.strings.dont_create_address), nextStep) - } - } - } - } - } -} - -@Composable -fun ShareAddressButton(onClick: () -> Unit) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - IconButton(onClick, Modifier.padding(bottom = DEFAULT_PADDING_HALF).border(1.dp, MaterialTheme.colors.secondary.copy(0.1f), CircleShape)) { - Icon( - painterResource(MR.images.ic_share_filled), generalGetString(MR.strings.share_verb), tint = MaterialTheme.colors.primary, - modifier = Modifier.size(50.dp).padding(DEFAULT_PADDING_HALF) - ) - } - Text(stringResource(MR.strings.share_verb)) - } -} - -@Composable -fun ShareViaEmailButton(onClick: () -> Unit) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - IconButton(onClick, Modifier.padding(bottom = DEFAULT_PADDING_HALF).border(1.dp, MaterialTheme.colors.secondary.copy(0.1f), CircleShape)) { - Icon( - painterResource(MR.images.ic_mail), generalGetString(MR.strings.share_verb), tint = MaterialTheme.colors.primary, - modifier = Modifier.size(50.dp).padding(DEFAULT_PADDING_HALF) - ) - } - Text(stringResource(MR.strings.invite_friends_short)) - } -} - -@Composable -private fun TextBelowButton(text: String) { - Text( - text, - Modifier - .fillMaxWidth() - .padding(horizontal = DEFAULT_PADDING * 3, vertical = DEFAULT_PADDING_HALF), - style = MaterialTheme.typography.subtitle1, - color = MaterialTheme.colors.secondary, - textAlign = TextAlign.Center, - ) -} - -@Composable -private fun ProgressIndicator() { - Box( - Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator( - Modifier - .padding(horizontal = 2.dp) - .size(30.dp), - color = MaterialTheme.colors.secondary, - strokeWidth = 3.dp - ) - } -} - -private fun prepareChatBeforeAddressCreation(rhId: Long?) { - // No visible users but may have hidden. In this case chat should be started anyway because it's stopped on this stage with hidden users - if (chatModel.users.any { u -> !u.user.hidden }) return - withBGApi { - val user = chatModel.controller.apiGetActiveUser(rhId) ?: return@withBGApi - chatModel.currentUser.value = user - chatModel.controller.startChat(user) - } -} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt index e480d4330b..d6d5753b6c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt @@ -25,6 +25,10 @@ import chat.simplex.res.MR @Composable fun SetNotificationsMode(m: ChatModel) { + LaunchedEffect(Unit) { + prepareChatBeforeNotificationsSetup(m) + } + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { ModalView({}, showClose = false) { ColumnWithScrollBar(Modifier.themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer)) { @@ -94,3 +98,13 @@ fun SelectableCard(currentValue: State, newValue: T, title: String, descr } Spacer(Modifier.height(14.dp)) } + +private fun prepareChatBeforeNotificationsSetup(chatModel: ChatModel) { + // No visible users but may have hidden. In this case chat should be started anyway because it's stopped on this stage with hidden users + if (chatModel.users.any { u -> !u.user.hidden }) return + withBGApi { + val user = chatModel.controller.apiGetActiveUser(null) ?: return@withBGApi + chatModel.currentUser.value = user + chatModel.controller.startChat(user) + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt index d0a3e601d2..4ad2675e83 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt @@ -36,7 +36,7 @@ fun SetupDatabasePassphrase(m: ChatModel) { val confirmNewKey = rememberSaveable { mutableStateOf("") } fun nextStep() { if (appPlatform.isAndroid || chatModel.currentUser.value != null) { - m.controller.appPrefs.onboardingStage.set(OnboardingStage.Step3_CreateSimpleXAddress) + m.controller.appPrefs.onboardingStage.set(OnboardingStage.Step4_SetNotificationsMode) } else { m.controller.appPrefs.onboardingStage.set(OnboardingStage.LinkAMobile) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressLearnMore.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressLearnMore.kt index 6d6b72d2d1..efc161ac65 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressLearnMore.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressLearnMore.kt @@ -1,9 +1,16 @@ package chat.simplex.common.views.usersettings import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.* import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp import chat.simplex.common.platform.ColumnWithScrollBar +import chat.simplex.common.platform.chatModel import chat.simplex.common.ui.theme.DEFAULT_PADDING import dev.icerock.moko.resources.compose.stringResource import chat.simplex.common.views.helpers.* @@ -12,12 +19,30 @@ import chat.simplex.common.views.onboarding.ReadableTextWithLink import chat.simplex.res.MR @Composable -fun UserAddressLearnMore() { +fun UserAddressLearnMore(showCreateAddressButton: Boolean = false) { ColumnWithScrollBar(Modifier .padding(horizontal = DEFAULT_PADDING)) { AppBarTitle(stringResource(MR.strings.simplex_address), withPadding = false) ReadableText(MR.strings.you_can_share_your_address) ReadableText(MR.strings.you_wont_lose_your_contacts_if_delete_address) ReadableText(MR.strings.you_can_accept_or_reject_connection) ReadableTextWithLink(MR.strings.read_more_in_user_guide_with_link, "https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address") + + if (showCreateAddressButton) { + Spacer(Modifier.weight(1f)) + Column(Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING * 2), horizontalAlignment = Alignment.CenterHorizontally) { + Button( + onClick = { + ModalManager.start.showModalCloseable { close -> + UserAddressView(chatModel = chatModel, shareViaProfile = false, autoCreateAddress = true, close = close) + } + }, + shape = CircleShape, + contentPadding = PaddingValues(horizontal = DEFAULT_PADDING * 2, vertical = DEFAULT_PADDING), + colors = ButtonDefaults.buttonColors(MaterialTheme.colors.primary, disabledBackgroundColor = MaterialTheme.colors.secondary) + ) { + Text(stringResource(MR.strings.create_simplex_address), style = MaterialTheme.typography.h2, color = Color.White, fontSize = 18.sp, fontWeight = FontWeight.Medium) + } + } + } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt index b357272e16..aa9ba70b02 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt @@ -33,6 +33,7 @@ fun UserAddressView( chatModel: ChatModel, viaCreateLinkView: Boolean = false, shareViaProfile: Boolean = false, + autoCreateAddress: Boolean = false, close: () -> Unit ) { // TODO close when remote host changes @@ -58,6 +59,33 @@ fun UserAddressView( } } } + + fun createAddress() { + withBGApi { + progressIndicator = true + val connReqContact = chatModel.controller.apiCreateUserAddress(user?.value?.remoteHostId) + if (connReqContact != null) { + chatModel.userAddress.value = UserContactLinkRec(connReqContact) + + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.share_address_with_contacts_question), + text = generalGetString(MR.strings.add_address_to_your_profile), + confirmText = generalGetString(MR.strings.share_verb), + onConfirm = { + setProfileAddress(true) + shareViaProfile.value = true + } + ) + } + progressIndicator = false + } + } + + LaunchedEffect(autoCreateAddress) { + if (autoCreateAddress) { + createAddress() + } + } val userAddress = remember { chatModel.userAddress } val clipboard = LocalClipboardManager.current val uriHandler = LocalUriHandler.current @@ -67,26 +95,7 @@ fun UserAddressView( userAddress = userAddress.value, shareViaProfile, onCloseHandler, - createAddress = { - withBGApi { - progressIndicator = true - val connReqContact = chatModel.controller.apiCreateUserAddress(user?.value?.remoteHostId) - if (connReqContact != null) { - chatModel.userAddress.value = UserContactLinkRec(connReqContact) - - AlertManager.shared.showAlertDialog( - title = generalGetString(MR.strings.share_address_with_contacts_question), - text = generalGetString(MR.strings.add_address_to_your_profile), - confirmText = generalGetString(MR.strings.share_verb), - onConfirm = { - setProfileAddress(true) - shareViaProfile.value = true - } - ) - } - progressIndicator = false - } - }, + createAddress = { createAddress() }, learnMore = { ModalManager.start.showModal { UserAddressLearnMore() diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 0ada4d3095..673100bd8d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -378,6 +378,7 @@ Tap to Connect Connect with %1$s? Search or paste SimpleX link + Tap Create SimpleX address in the menu to create it later. No selected chat From 619985730ec5027ee4109eb44a80d03bc48a28d0 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 18 Nov 2024 18:44:28 +0000 Subject: [PATCH 079/567] core: use random servers for each operator (#5192) * core: use random servers for each operator (WIP, compiles with undefined stub) * compiles * fix some, break some * tests pass * cleanup * delays in tests * enable random servers test * remove new preset servers in down migration * fix migration * test --- src/Simplex/Chat.hs | 216 ++++++++++-------- src/Simplex/Chat/Controller.hs | 12 +- .../Migrations/M20241027_server_operators.hs | 2 + src/Simplex/Chat/Operators.hs | 137 ++++++----- src/Simplex/Chat/Store/Profiles.hs | 76 ++---- tests/ChatClient.hs | 19 +- tests/ChatTests/Direct.hs | 17 +- tests/ChatTests/Groups.hs | 5 - tests/ChatTests/Profiles.hs | 4 +- tests/OperatorTests.hs | 59 ++++- tests/RandomServers.hs | 20 +- 11 files changed, 319 insertions(+), 248 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 95bb405bae..819832a1ed 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -3,6 +3,7 @@ {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-} +{-# LANGUAGE KindSignatures #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE NamedFieldPuns #-} @@ -178,6 +179,8 @@ defaultChatConfig = }, chatVRange = supportedChatVRange, confirmMigrations = MCConsole, + -- this property should NOT use operator = Nothing + -- non-operator servers can be passed via options presetServers = PresetServers { operators = @@ -310,11 +313,15 @@ newChatController config = cfg {logLevel, showReactions, tbqSize, subscriptionEvents = logConnections, hostEvents = logServerHosts, presetServers = presetServers', inlineFiles = inlineFiles', autoAcceptFileSize, highlyAvailable, confirmMigrations = confirmMigrations'} firstTime = dbNew chatStore currentUser <- newTVarIO user - randomSMP <- randomPresetServers SPSMP presetServers' - randomXFTP <- randomPresetServers SPXFTP presetServers' - let randomServers = RandomServers {smpServers = randomSMP, xftpServers = randomXFTP} + randomPresetServers <- chooseRandomServers presetServers' + let rndSrvs = L.toList randomPresetServers + operatorWithId (i, op) = (\o -> o {operatorId = DBEntityId i}) <$> pOperator op + opDomains = operatorDomains $ mapMaybe operatorWithId $ zip [1..] rndSrvs + agentSMP <- randomServerCfgs "agent SMP servers" SPSMP opDomains rndSrvs + agentXFTP <- randomServerCfgs "agent XFTP servers" SPXFTP opDomains rndSrvs + let randomAgentServers = RandomAgentServers {smpServers = agentSMP, xftpServers = agentXFTP} currentRemoteHost <- newTVarIO Nothing - servers <- withTransaction chatStore $ \db -> agentServers db config randomServers + servers <- withTransaction chatStore $ \db -> agentServers db config randomPresetServers randomAgentServers smpAgent <- getSMPAgentClient aCfg {tbqSize} servers agentStore backgroundMode agentAsync <- newTVarIO Nothing random <- liftIO C.newRandom @@ -350,7 +357,8 @@ newChatController ChatController { firstTime, currentUser, - randomServers, + randomPresetServers, + randomAgentServers, currentRemoteHost, smpAgent, agentAsync, @@ -410,19 +418,26 @@ newChatController xftp = map newUserServer xftpSrvs, useXFTP = 0 } - agentServers :: DB.Connection -> ChatConfig -> RandomServers -> IO InitialAgentServers - agentServers db ChatConfig {presetServers = PresetServers {operators = presetOps, ntf, netCfg}} rs = do + randomServerCfgs :: UserProtocol p => String -> SProtocolType p -> [(Text, ServerOperator)] -> [PresetOperator] -> IO (NonEmpty (ServerCfg p)) + randomServerCfgs name p opDomains rndSrvs = + toJustOrError name $ L.nonEmpty $ agentServerCfgs p opDomains $ concatMap (pServers p) rndSrvs + agentServers :: DB.Connection -> ChatConfig -> NonEmpty PresetOperator -> RandomAgentServers -> IO InitialAgentServers + agentServers db ChatConfig {presetServers = PresetServers {ntf, netCfg}} presetOps as = do users <- getUsers db - opDomains <- operatorDomains <$> getUpdateServerOperators db presetOps (null users) - smp' <- getServers SPSMP users opDomains - xftp' <- getServers SPXFTP users opDomains - pure InitialAgentServers {smp = smp', xftp = xftp', ntf, netCfg} + ops <- getUpdateServerOperators db presetOps (null users) + let opDomains = operatorDomains $ mapMaybe snd ops + (smp', xftp') <- unzip <$> mapM (getServers ops opDomains) users + pure InitialAgentServers {smp = M.fromList smp', xftp = M.fromList xftp', ntf, netCfg} where - getServers :: forall p. (ProtocolTypeI p, UserProtocol p) => SProtocolType p -> [User] -> [(Text, ServerOperator)] -> IO (Map UserId (NonEmpty (ServerCfg p))) - getServers p users opDomains = do - let rs' = rndServers p rs - fmap M.fromList $ forM users $ \u -> - (aUserId u,) . agentServerCfgs p opDomains rs' <$> getUpdateUserServers db p presetOps rs' u + getServers :: [(Maybe PresetOperator, Maybe ServerOperator)] -> [(Text, ServerOperator)] -> User -> IO ((UserId, NonEmpty (ServerCfg 'PSMP)), (UserId, NonEmpty (ServerCfg 'PXFTP))) + getServers ops opDomains user' = do + smpSrvs <- getProtocolServers db SPSMP user' + xftpSrvs <- getProtocolServers db SPXFTP user' + uss <- groupByOperator' (ops, smpSrvs, xftpSrvs) + ts <- getCurrentTime + uss' <- mapM (setUserServers' db user' ts . updatedUserServers) uss + let auId = aUserId user' + pure $ bimap (auId,) (auId,) $ useServers as opDomains uss' updateNetworkConfig :: NetworkConfig -> SimpleNetCfg -> NetworkConfig updateNetworkConfig cfg SimpleNetCfg {socksProxy, socksMode, hostMode, requiredHostMode, smpProxyMode_, smpProxyFallback_, smpWebPort, tcpTimeout_, logTLSErrors} = @@ -465,28 +480,31 @@ withFileLock :: String -> Int64 -> CM a -> CM a withFileLock name = withEntityLock name . CLFile {-# INLINE withFileLock #-} -serverCfg :: ProtoServerWithAuth p -> ServerCfg p -serverCfg server = ServerCfg {server, operator = Nothing, enabled = True, roles = allRoles} +useServers :: Foldable f => RandomAgentServers -> [(Text, ServerOperator)] -> f UserOperatorServers -> (NonEmpty (ServerCfg 'PSMP), NonEmpty (ServerCfg 'PXFTP)) +useServers as opDomains uss = + let smp' = useServerCfgs SPSMP as opDomains $ concatMap (servers' SPSMP) uss + xftp' = useServerCfgs SPXFTP as opDomains $ concatMap (servers' SPXFTP) uss + in (smp', xftp') -useServers :: forall p. UserProtocol p => SProtocolType p -> RandomServers -> [UserServer p] -> NonEmpty (NewUserServer p) -useServers p rs servers = case L.nonEmpty servers of - Nothing -> rndServers p rs - Just srvs -> L.map (\srv -> (srv :: UserServer p) {serverId = DBNewEntity}) srvs - -rndServers :: UserProtocol p => SProtocolType p -> RandomServers -> NonEmpty (NewUserServer p) -rndServers p RandomServers {smpServers, xftpServers} = case p of - SPSMP -> smpServers - SPXFTP -> xftpServers - -randomPresetServers :: forall p. UserProtocol p => SProtocolType p -> PresetServers -> IO (NonEmpty (NewUserServer p)) -randomPresetServers p PresetServers {operators} = toJust . L.nonEmpty . concat =<< mapM opSrvs operators +useServerCfgs :: forall p. UserProtocol p => SProtocolType p -> RandomAgentServers -> [(Text, ServerOperator)] -> [UserServer p] -> NonEmpty (ServerCfg p) +useServerCfgs p RandomAgentServers {smpServers, xftpServers} opDomains = + fromMaybe (rndAgentServers p) . L.nonEmpty . agentServerCfgs p opDomains where - toJust = \case - Just a -> pure a - Nothing -> E.throwIO $ userError "no preset servers" - opSrvs :: PresetOperator -> IO [NewUserServer p] - opSrvs op = do - let srvs = operatorServers p op + rndAgentServers :: SProtocolType p -> NonEmpty (ServerCfg p) + rndAgentServers = \case + SPSMP -> smpServers + SPXFTP -> xftpServers + +chooseRandomServers :: PresetServers -> IO (NonEmpty PresetOperator) +chooseRandomServers PresetServers {operators} = + forM operators $ \op -> do + smp' <- opSrvs SPSMP op + xftp' <- opSrvs SPXFTP op + pure (op :: PresetOperator) {smp = smp', xftp = xftp'} + where + opSrvs :: forall p. UserProtocol p => SProtocolType p -> PresetOperator -> IO [NewUserServer p] + opSrvs p op = do + let srvs = pServers p op toUse = operatorServersToUse p op (enbldSrvs, dsbldSrvs) = partition (\UserServer {enabled} -> enabled) srvs if toUse <= 0 || toUse >= length enbldSrvs @@ -497,6 +515,13 @@ randomPresetServers p PresetServers {operators} = toJust . L.nonEmpty . concat = pure $ sortOn server' $ enbldSrvs' <> dsbldSrvs' <> dsbldSrvs server' UserServer {server = ProtoServerWithAuth srv _} = srv +toJustOrError :: String -> Maybe a -> IO a +toJustOrError name = \case + Just a -> pure a + Nothing -> do + putStrLn $ name <> ": expected Just, exiting" + E.throwIO $ userError name + -- enableSndFiles has no effect when mainApp is True startChatController :: Bool -> Bool -> CM' (Async ()) startChatController mainApp enableSndFiles = do @@ -525,7 +550,7 @@ startChatController mainApp enableSndFiles = do startXFTP startWorkers = do tmp <- readTVarIO =<< asks tempDirectory runExceptT (withAgent $ \a -> startWorkers a tmp) >>= \case - Left e -> liftIO $ print $ "Error starting XFTP workers: " <> show e + Left e -> liftIO $ putStrLn $ "Error starting XFTP workers: " <> show e Right _ -> pure () startCleanupManager = do cleanupAsync <- asks cleanupManagerAsync @@ -639,36 +664,43 @@ processChatCommand' vr = \case forM_ profile $ \Profile {displayName} -> checkValidName displayName p@Profile {displayName} <- liftIO $ maybe generateRandomProfile pure profile u <- asks currentUser - smpServers <- chooseServers SPSMP - xftpServers <- chooseServers SPXFTP users <- withFastStore' getUsers forM_ users $ \User {localDisplayName = n, activeUser, viewPwdHash} -> when (n == displayName) . throwChatError $ if activeUser || isNothing viewPwdHash then CEUserExists displayName else CEInvalidDisplayName {displayName, validName = ""} - opDomains <- operatorDomains . serverOperators <$> withFastStore getServerOperators - rs <- asks randomServers - let smp = agentServerCfgs SPSMP opDomains (rndServers SPSMP rs) smpServers - xftp = agentServerCfgs SPXFTP opDomains (rndServers SPXFTP rs) xftpServers - auId <- withAgent (\a -> createUser a smp xftp) + (uss, (smp', xftp')) <- chooseServers =<< readTVarIO u + auId <- withAgent $ \a -> createUser a smp' xftp' ts <- liftIO $ getCurrentTime >>= if pastTimestamp then coupleDaysAgo else pure - user <- withFastStore $ \db -> createUserRecordAt db (AgentUserId auId) p True ts - createPresetContactCards user `catchChatError` \_ -> pure () - withFastStore $ \db -> do + user <- withFastStore $ \db -> do + user <- createUserRecordAt db (AgentUserId auId) p True ts + mapM_ (setUserServers db user ts) uss + createPresetContactCards db user `catchStoreError` \_ -> pure () createNoteFolder db user - liftIO $ mapM_ (insertProtocolServer db SPSMP user ts) $ useServers SPSMP rs smpServers - liftIO $ mapM_ (insertProtocolServer db SPXFTP user ts) $ useServers SPXFTP rs xftpServers + pure user atomically . writeTVar u $ Just user pure $ CRActiveUser user where - createPresetContactCards :: User -> CM () - createPresetContactCards user = - withFastStore $ \db -> do - createContact db user simplexStatusContactProfile - createContact db user simplexTeamContactProfile - chooseServers :: forall p. ProtocolTypeI p => SProtocolType p -> CM [UserServer p] - chooseServers p = do - srvs <- chatReadVar currentUser >>= mapM (\user -> withFastStore' $ \db -> getProtocolServers db p user) - pure $ fromMaybe [] srvs + createPresetContactCards :: DB.Connection -> User -> ExceptT StoreError IO () + createPresetContactCards db user = do + createContact db user simplexStatusContactProfile + createContact db user simplexTeamContactProfile + chooseServers :: Maybe User -> CM ([UpdatedUserOperatorServers], (NonEmpty (ServerCfg 'PSMP), NonEmpty (ServerCfg 'PXFTP))) + chooseServers user_ = do + as <- asks randomAgentServers + mapM (withFastStore . flip getUserServers >=> liftIO . groupByOperator) user_ >>= \case + Just uss -> do + let opDomains = operatorDomains $ mapMaybe operator' uss + uss' = map copyServers uss + pure $ (uss',) $ useServers as opDomains uss + Nothing -> do + ps <- asks randomPresetServers + uss <- presetUserServers <$> withFastStore' (\db -> getUpdateServerOperators db ps True) + let RandomAgentServers {smpServers = smp', xftpServers = xftp'} = as + pure (uss, (smp', xftp')) + copyServers :: UserOperatorServers -> UpdatedUserOperatorServers + copyServers UserOperatorServers {operator, smpServers, xftpServers} = + let new srv = AUS SDBNew srv {serverId = DBNewEntity} + in UpdatedUserOperatorServers {operator, smpServers = map new smpServers, xftpServers = map new xftpServers} coupleDaysAgo t = (`addUTCTime` t) . fromInteger . negate . (+ (2 * day)) <$> randomRIO (0, day) day = 86400 ListUsers -> CRUsersList <$> withFastStore' getUsersInfo @@ -1568,32 +1600,16 @@ processChatCommand' vr = \case pure $ CRConnNtfMessages ntfMsgs GetUserProtoServers (AProtocolType p) -> withUser $ \user -> withServerProtocol p $ do srvs <- withFastStore (`getUserServers` user) - CRUserServers user <$> liftIO (groupedServers srvs p) - where - groupedServers :: UserProtocol p => ([ServerOperator], [UserServer 'PSMP], [UserServer 'PXFTP]) -> SProtocolType p -> IO [UserOperatorServers] - groupedServers (operators, smpServers, xftpServers) = \case - SPSMP -> groupByOperator (operators, smpServers, []) - SPXFTP -> groupByOperator (operators, [], xftpServers) + liftIO $ CRUserServers user <$> groupByOperator (protocolServers p srvs) SetUserProtoServers (AProtocolType (p :: SProtocolType p)) srvs -> withUser $ \user@User {userId} -> withServerProtocol p $ do - srvs' <- mapM aUserServer srvs userServers_ <- liftIO . groupByOperator =<< withFastStore (`getUserServers` user) case L.nonEmpty userServers_ of Nothing -> throwChatError $ CECommandError "no servers" Just userServers -> case srvs of [] -> throwChatError $ CECommandError "no servers" - _ -> processChatCommand $ APISetUserServers userId $ L.map (updatedSrvs p) userServers - where - -- disable preset and replace custom servers (groupByOperator always adds custom) - updatedSrvs :: UserProtocol p => SProtocolType p -> UserOperatorServers -> UpdatedUserOperatorServers - updatedSrvs p' UserOperatorServers {operator, smpServers, xftpServers} = case p' of - SPSMP -> u (updateSrvs smpServers, map (AUS SDBStored) xftpServers) - SPXFTP -> u (map (AUS SDBStored) smpServers, updateSrvs xftpServers) - where - u = uncurry $ UpdatedUserOperatorServers operator - updateSrvs :: [UserServer p] -> [AUserServer p] - updateSrvs pSrvs = map disableSrv pSrvs <> maybe srvs' (const []) operator - disableSrv srv@UserServer {preset} = - AUS SDBStored $ if preset then srv {enabled = False} else srv {deleted = True} + _ -> do + srvs' <- mapM aUserServer srvs + processChatCommand $ APISetUserServers userId $ L.map (updatedServers p srvs') userServers where aUserServer :: AProtoServerWithAuth -> CM (AUserServer p) aUserServer (AProtoServerWithAuth p' srv) = case testEquality p p' of @@ -1607,20 +1623,21 @@ processChatCommand' vr = \case APISetServerOperators operatorsEnabled -> withFastStore $ \db -> do liftIO $ setServerOperators db operatorsEnabled CRServerOperatorConditions <$> getServerOperators db - APIGetUserServers userId -> withUserId userId $ \user -> withFastStore $ \db -> + APIGetUserServers userId -> withUserId userId $ \user -> withFastStore $ \db -> do CRUserServers user <$> (liftIO . groupByOperator =<< getUserServers db user) APISetUserServers userId userServers -> withUserId userId $ \user -> do errors <- validateAllUsersServers userId $ L.toList userServers unless (null errors) $ throwChatError (CECommandError $ "user servers validation error(s): " <> show errors) - (operators, smpServers, xftpServers) <- withFastStore $ \db -> do - setUserServers db user userServers - getUserServers db user - let opDomains = operatorDomains operators - rs <- asks randomServers + uss <- withFastStore $ \db -> do + ts <- liftIO getCurrentTime + mapM (setUserServers db user ts) userServers + as <- asks randomAgentServers lift $ withAgent' $ \a -> do let auId = aUserId user - setProtocolServers a auId $ agentServerCfgs SPSMP opDomains (rndServers SPSMP rs) smpServers - setProtocolServers a auId $ agentServerCfgs SPXFTP opDomains (rndServers SPXFTP rs) xftpServers + opDomains = operatorDomains $ mapMaybe operator' $ L.toList uss + (smp', xftp') = useServers as opDomains uss + setProtocolServers a auId smp' + setProtocolServers a auId xftp' ok_ APIValidateServers userId userServers -> withUserId userId $ \user -> CRUserServersValidation user <$> validateAllUsersServers userId userServers @@ -1897,7 +1914,7 @@ processChatCommand' vr = \case let ConnReqUriData {crSmpQueues = q :| _} = crData SMPQueueUri {queueAddress = SMPQueueAddress {smpServer}} = q newUserServers <- - map protoServer' . filter (\ServerCfg {enabled} -> enabled) + map protoServer' . L.filter (\ServerCfg {enabled} -> enabled) <$> getKnownAgentServers SPSMP newUser pure $ smpServer `elem` newUserServers updateConnRecord user@User {userId} conn@PendingContactConnection {customUserProfileId} newUser = do @@ -3375,6 +3392,23 @@ processChatCommand' vr = \case msgInfo <- withFastStore' (`getLastRcvMsgInfo` connId) CRQueueInfo user msgInfo <$> withAgent (`getConnectionQueueInfo` acId) +protocolServers :: UserProtocol p => SProtocolType p -> ([Maybe ServerOperator], [UserServer 'PSMP], [UserServer 'PXFTP]) -> ([Maybe ServerOperator], [UserServer 'PSMP], [UserServer 'PXFTP]) +protocolServers p (operators, smpServers, xftpServers) = case p of + SPSMP -> (operators, smpServers, []) + SPXFTP -> (operators, [], xftpServers) + +-- disable preset and replace custom servers (groupByOperator always adds custom) +updatedServers :: forall p. UserProtocol p => SProtocolType p -> [AUserServer p] -> UserOperatorServers -> UpdatedUserOperatorServers +updatedServers p' srvs UserOperatorServers {operator, smpServers, xftpServers} = case p' of + SPSMP -> u (updateSrvs smpServers, map (AUS SDBStored) xftpServers) + SPXFTP -> u (map (AUS SDBStored) smpServers, updateSrvs xftpServers) + where + u = uncurry $ UpdatedUserOperatorServers operator + updateSrvs :: [UserServer p] -> [AUserServer p] + updateSrvs pSrvs = map disableSrv pSrvs <> maybe srvs (const []) operator + disableSrv srv@UserServer {preset} = + AUS SDBStored $ if preset then srv {enabled = False} else srv {deleted = True} + type ComposeMessageReq = (ComposedMessage, Maybe CIForwardedFrom) contactCITimed :: Contact -> CM (Maybe CITimed) @@ -3761,7 +3795,7 @@ receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} S.toList $ S.fromList $ concatMap (\FD.FileChunk {replicas} -> map (\FD.FileChunkReplica {server} -> server) replicas) chunks getUnknownSrvs :: [XFTPServer] -> CM [XFTPServer] getUnknownSrvs srvs = do - knownSrvs <- map protoServer' <$> getKnownAgentServers SPXFTP user + knownSrvs <- L.map protoServer' <$> getKnownAgentServers SPXFTP user pure $ filter (`notElem` knownSrvs) srvs ipProtectedForSrvs :: [XFTPServer] -> CM Bool ipProtectedForSrvs srvs = do @@ -3775,13 +3809,13 @@ receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} toView $ CRChatItemUpdated user aci throwChatError $ CEFileNotApproved fileId unknownSrvs -getKnownAgentServers :: (ProtocolTypeI p, UserProtocol p) => SProtocolType p -> User -> CM [ServerCfg p] +getKnownAgentServers :: (ProtocolTypeI p, UserProtocol p) => SProtocolType p -> User -> CM (NonEmpty (ServerCfg p)) getKnownAgentServers p user = do - rs <- asks randomServers + as <- asks randomAgentServers withStore $ \db -> do opDomains <- operatorDomains . serverOperators <$> getServerOperators db srvs <- liftIO $ getProtocolServers db p user - pure $ L.toList $ agentServerCfgs p opDomains (rndServers p rs) srvs + pure $ useServerCfgs p as opDomains srvs protoServer' :: ServerCfg p -> ProtocolServer p protoServer' ServerCfg {server} = protoServer server diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index c085dcf470..b6229e07ba 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -70,7 +70,7 @@ import Simplex.Chat.Util (liftIOEither) import Simplex.FileTransfer.Description (FileDescriptionURI) import Simplex.Messaging.Agent (AgentClient, SubscriptionsInfo) import Simplex.Messaging.Agent.Client (AgentLocks, AgentQueuesInfo (..), AgentWorkersDetails (..), AgentWorkersSummary (..), ProtocolTestFailure, SMPServerSubs, ServerQueueInfo, UserNetworkInfo) -import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, NetworkConfig) +import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, NetworkConfig, ServerCfg) import Simplex.Messaging.Agent.Lock import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation, SQLiteStore, UpMigration, withTransaction, withTransactionPriority) @@ -154,9 +154,9 @@ data ChatConfig = ChatConfig chatHooks :: ChatHooks } -data RandomServers = RandomServers - { smpServers :: NonEmpty (NewUserServer 'PSMP), - xftpServers :: NonEmpty (NewUserServer 'PXFTP) +data RandomAgentServers = RandomAgentServers + { smpServers :: NonEmpty (ServerCfg 'PSMP), + xftpServers :: NonEmpty (ServerCfg 'PXFTP) } deriving (Show) @@ -183,6 +183,7 @@ data PresetServers = PresetServers ntf :: [NtfServer], netCfg :: NetworkConfig } + deriving (Show) data InlineFilesConfig = InlineFilesConfig { offerChunks :: Integer, @@ -206,7 +207,8 @@ data ChatDatabase = ChatDatabase {chatStore :: SQLiteStore, agentStore :: SQLite data ChatController = ChatController { currentUser :: TVar (Maybe User), - randomServers :: RandomServers, + randomPresetServers :: NonEmpty PresetOperator, + randomAgentServers :: RandomAgentServers, currentRemoteHost :: TVar (Maybe RemoteHostId), firstTime :: Bool, smpAgent :: AgentClient, diff --git a/src/Simplex/Chat/Migrations/M20241027_server_operators.hs b/src/Simplex/Chat/Migrations/M20241027_server_operators.hs index c4b40c4706..1316e3c006 100644 --- a/src/Simplex/Chat/Migrations/M20241027_server_operators.hs +++ b/src/Simplex/Chat/Migrations/M20241027_server_operators.hs @@ -53,4 +53,6 @@ DROP INDEX idx_operator_usage_conditions_server_operator_id; DROP TABLE operator_usage_conditions; DROP TABLE usage_conditions; DROP TABLE server_operators; + +DELETE FROM protocol_servers WHERE host LIKE "%.simplexonflux.com,%"; |] diff --git a/src/Simplex/Chat/Operators.hs b/src/Simplex/Chat/Operators.hs index c3d9a8823b..f7a07682f9 100644 --- a/src/Simplex/Chat/Operators.hs +++ b/src/Simplex/Chat/Operators.hs @@ -27,6 +27,7 @@ import qualified Data.Aeson.TH as JQ import Data.Either (partitionEithers) import Data.FileEmbed import Data.Foldable (foldMap') +import Data.Functor.Identity import Data.IORef import Data.Int (Int64) import Data.Kind @@ -234,13 +235,13 @@ class UserServersClass u where type AServer u = (s :: ProtocolType -> Type) | s -> u operator' :: u -> Maybe ServerOperator partitionValid :: [AServer u p] -> ([Text], [AUserServer p]) - servers' :: UserProtocol p => u -> SProtocolType p -> [AServer u p] + servers' :: UserProtocol p => SProtocolType p -> u -> [AServer u p] instance UserServersClass UserOperatorServers where type AServer UserOperatorServers = UserServer_ 'DBStored ProtoServerWithAuth operator' UserOperatorServers {operator} = operator partitionValid ss = ([], map (AUS SDBStored) ss) - servers' UserOperatorServers {smpServers, xftpServers} = \case + servers' p UserOperatorServers {smpServers, xftpServers} = case p of SPSMP -> smpServers SPXFTP -> xftpServers @@ -248,7 +249,7 @@ instance UserServersClass UpdatedUserOperatorServers where type AServer UpdatedUserOperatorServers = AUserServer operator' UpdatedUserOperatorServers {operator} = operator partitionValid = ([],) - servers' UpdatedUserOperatorServers {smpServers, xftpServers} = \case + servers' p UpdatedUserOperatorServers {smpServers, xftpServers} = case p of SPSMP -> smpServers SPXFTP -> xftpServers @@ -259,7 +260,7 @@ instance UserServersClass ValidatedUserOperatorServers where where serverOrErr :: AValidatedServer p -> Either Text (AUserServer p) serverOrErr (AVS s srv@UserServer {server = server'}) = (\server -> AUS s srv {server}) <$> unVPS server' - servers' ValidatedUserOperatorServers {smpServers, xftpServers} = \case + servers' p ValidatedUserOperatorServers {smpServers, xftpServers} = case p of SPSMP -> smpServers SPXFTP -> xftpServers @@ -290,9 +291,13 @@ data PresetOperator = PresetOperator xftp :: [NewUserServer 'PXFTP], useXFTP :: Int } + deriving (Show) -operatorServers :: UserProtocol p => SProtocolType p -> PresetOperator -> [NewUserServer p] -operatorServers p PresetOperator {smp, xftp} = case p of +pOperator :: PresetOperator -> Maybe NewServerOperator +pOperator PresetOperator {operator} = operator + +pServers :: UserProtocol p => SProtocolType p -> PresetOperator -> [NewUserServer p] +pServers p PresetOperator {smp, xftp} = case p of SPSMP -> smp SPXFTP -> xftp @@ -335,83 +340,113 @@ usageConditionsToAdd' prevCommit sourceCommit newUser createdAt = \case where conditions cId commit = UsageConditions {conditionsId = cId, conditionsCommit = commit, notifiedAt = Nothing, createdAt} +presetUserServers :: [(Maybe PresetOperator, Maybe ServerOperator)] -> [UpdatedUserOperatorServers] +presetUserServers = mapMaybe $ \(presetOp_, op) -> mkUS op <$> presetOp_ + where + mkUS op PresetOperator {smp, xftp} = + UpdatedUserOperatorServers op (map (AUS SDBNew) smp) (map (AUS SDBNew) xftp) + -- This function should be used inside DB transaction to update operators. -- It allows to add/remove/update preset operators in the database preserving enabled and roles settings, -- and preserves custom operators without tags for forward compatibility. -updatedServerOperators :: NonEmpty PresetOperator -> [ServerOperator] -> [AServerOperator] +updatedServerOperators :: NonEmpty PresetOperator -> [ServerOperator] -> [(Maybe PresetOperator, Maybe AServerOperator)] updatedServerOperators presetOps storedOps = foldr addPreset [] presetOps - <> map (ASO SDBStored) (filter (isNothing . operatorTag) storedOps) + <> map (\op -> (Nothing, Just $ ASO SDBStored op)) (filter (isNothing . operatorTag) storedOps) where -- TODO remove domains of preset operators from custom - addPreset PresetOperator {operator} = case operator of - Nothing -> id - Just presetOp -> (storedOp' :) - where - storedOp' = case find ((operatorTag presetOp ==) . operatorTag) storedOps of - Just ServerOperator {operatorId, conditionsAcceptance, enabled, smpRoles, xftpRoles} -> - ASO SDBStored presetOp {operatorId, conditionsAcceptance, enabled, smpRoles, xftpRoles} - Nothing -> ASO SDBNew presetOp + addPreset op = ((Just op, storedOp' <$> pOperator op) :) + where + storedOp' presetOp = case find ((operatorTag presetOp ==) . operatorTag) storedOps of + Just ServerOperator {operatorId, conditionsAcceptance, enabled, smpRoles, xftpRoles} -> + ASO SDBStored presetOp {operatorId, conditionsAcceptance, enabled, smpRoles, xftpRoles} + Nothing -> ASO SDBNew presetOp -- This function should be used inside DB transaction to update servers. -updatedUserServers :: forall p. UserProtocol p => SProtocolType p -> NonEmpty PresetOperator -> NonEmpty (NewUserServer p) -> [UserServer p] -> NonEmpty (AUserServer p) -updatedUserServers _ _ randomSrvs [] = L.map (AUS SDBNew) randomSrvs -updatedUserServers p presetOps randomSrvs srvs = - fromMaybe (L.map (AUS SDBNew) randomSrvs) (L.nonEmpty updatedSrvs) +updatedUserServers :: (Maybe PresetOperator, UserOperatorServers) -> UpdatedUserOperatorServers +updatedUserServers (presetOp_, UserOperatorServers {operator, smpServers, xftpServers}) = + UpdatedUserOperatorServers {operator, smpServers = smp', xftpServers = xftp'} where - updatedSrvs = map userServer presetSrvs <> map (AUS SDBStored) (filter customServer srvs) - storedSrvs :: Map (ProtoServerWithAuth p) (UserServer p) - storedSrvs = foldl' (\ss srv@UserServer {server} -> M.insert server srv ss) M.empty srvs - customServer :: UserServer p -> Bool - customServer srv = not (preset srv) && all (`S.notMember` presetHosts) (srvHost srv) - presetSrvs :: [NewUserServer p] - presetSrvs = concatMap (operatorServers p) presetOps - presetHosts :: Set TransportHost - presetHosts = foldMap' (S.fromList . L.toList . srvHost) presetSrvs - userServer :: NewUserServer p -> AUserServer p - userServer srv@UserServer {server} = maybe (AUS SDBNew srv) (AUS SDBStored) (M.lookup server storedSrvs) + stored = map (AUS SDBStored) + (smp', xftp') = case presetOp_ of + Nothing -> (stored smpServers, stored xftpServers) + Just presetOp -> (updated SPSMP smpServers, updated SPXFTP xftpServers) + where + updated :: forall p. UserProtocol p => SProtocolType p -> [UserServer p] -> [AUserServer p] + updated p srvs = map userServer presetSrvs <> stored (filter customServer srvs) + where + storedSrvs :: Map (ProtoServerWithAuth p) (UserServer p) + storedSrvs = foldl' (\ss srv@UserServer {server} -> M.insert server srv ss) M.empty srvs + customServer :: UserServer p -> Bool + customServer srv = not (preset srv) && all (`S.notMember` presetHosts) (srvHost srv) + presetSrvs :: [NewUserServer p] + presetSrvs = pServers p presetOp + presetHosts :: Set TransportHost + presetHosts = foldMap' (S.fromList . L.toList . srvHost) presetSrvs + userServer :: NewUserServer p -> AUserServer p + userServer srv@UserServer {server} = maybe (AUS SDBNew srv) (AUS SDBStored) (M.lookup server storedSrvs) srvHost :: UserServer' s p -> NonEmpty TransportHost srvHost UserServer {server = ProtoServerWithAuth srv _} = host srv -agentServerCfgs :: UserProtocol p => SProtocolType p -> [(Text, ServerOperator)] -> NonEmpty (NewUserServer p) -> [UserServer' s p] -> NonEmpty (ServerCfg p) -agentServerCfgs p opDomains randomSrvs = - fromMaybe fallbackSrvs . L.nonEmpty . mapMaybe enabledOpAgentServer +agentServerCfgs :: UserProtocol p => SProtocolType p -> [(Text, ServerOperator)] -> [UserServer' s p] -> [ServerCfg p] +agentServerCfgs p opDomains = mapMaybe agentServer where - fallbackSrvs = L.map (snd . agentServer) randomSrvs - enabledOpAgentServer srv = - let (opEnabled, srvCfg) = agentServer srv - in if opEnabled then Just srvCfg else Nothing - agentServer :: UserServer' s p -> (Bool, ServerCfg p) + agentServer :: UserServer' s p -> Maybe (ServerCfg p) agentServer srv@UserServer {server, enabled} = case find (\(d, _) -> any (matchingHost d) (srvHost srv)) opDomains of - Just (_, op@ServerOperator {operatorId = DBEntityId opId, enabled = opEnabled}) -> - (opEnabled, ServerCfg {server, enabled, operator = Just opId, roles = operatorRoles p op}) + Just (_, op@ServerOperator {operatorId = DBEntityId opId, enabled = opEnabled}) + | opEnabled -> Just ServerCfg {server, enabled, operator = Just opId, roles = operatorRoles p op} + | otherwise -> Nothing Nothing -> - (True, ServerCfg {server, enabled, operator = Nothing, roles = allRoles}) + Just ServerCfg {server, enabled, operator = Nothing, roles = allRoles} matchingHost :: Text -> TransportHost -> Bool matchingHost d = \case THDomainName h -> d `T.isSuffixOf` T.pack h _ -> False -operatorDomains :: [ServerOperator] -> [(Text, ServerOperator)] +operatorDomains :: [ServerOperator' s] -> [(Text, ServerOperator' s)] operatorDomains = foldr (\op ds -> foldr (\d -> ((d, op) :)) ds (serverDomains op)) [] -groupByOperator :: ([ServerOperator], [UserServer 'PSMP], [UserServer 'PXFTP]) -> IO [UserOperatorServers] -groupByOperator (ops, smpSrvs, xftpSrvs) = do - ss <- mapM (\op -> (serverDomains op,) <$> newIORef (UserOperatorServers (Just op) [] [])) ops - custom <- newIORef $ UserOperatorServers Nothing [] [] +class Box b where + box :: a -> b a + unbox :: b a -> a + +instance Box Identity where + box = Identity + unbox = runIdentity + +instance Box ((,) (Maybe a)) where + box = (Nothing,) + unbox = snd + +groupByOperator :: ([Maybe ServerOperator], [UserServer 'PSMP], [UserServer 'PXFTP]) -> IO [UserOperatorServers] +groupByOperator (ops, smpSrvs, xftpSrvs) = map runIdentity <$> groupByOperator_ (map Identity ops, smpSrvs, xftpSrvs) + +-- For the initial app start this function relies on tuple being Functor/Box +-- to preserve the information about operator being DBNew or DBStored +groupByOperator' :: ([(Maybe PresetOperator, Maybe ServerOperator)], [UserServer 'PSMP], [UserServer 'PXFTP]) -> IO [(Maybe PresetOperator, UserOperatorServers)] +groupByOperator' = groupByOperator_ +{-# INLINE groupByOperator' #-} + +groupByOperator_ :: forall f. (Box f, Traversable f) => ([f (Maybe ServerOperator)], [UserServer 'PSMP], [UserServer 'PXFTP]) -> IO [f UserOperatorServers] +groupByOperator_ (ops, smpSrvs, xftpSrvs) = do + let ops' = mapMaybe sequence ops + customOp_ = find (isNothing . unbox) ops + ss <- mapM ((\op -> (serverDomains (unbox op),) <$> newIORef (mkUS . Just <$> op))) ops' + custom <- newIORef $ maybe (box $ mkUS Nothing) (mkUS <$>) customOp_ mapM_ (addServer ss custom addSMP) (reverse smpSrvs) mapM_ (addServer ss custom addXFTP) (reverse xftpSrvs) opSrvs <- mapM (readIORef . snd) ss customSrvs <- readIORef custom pure $ opSrvs <> [customSrvs] where - addServer :: [([Text], IORef UserOperatorServers)] -> IORef UserOperatorServers -> (UserServer p -> UserOperatorServers -> UserOperatorServers) -> UserServer p -> IO () + mkUS op = UserOperatorServers op [] [] + addServer :: [([Text], IORef (f UserOperatorServers))] -> IORef (f UserOperatorServers) -> (UserServer p -> UserOperatorServers -> UserOperatorServers) -> UserServer p -> IO () addServer ss custom add srv = let v = maybe custom snd $ find (\(ds, _) -> any (\d -> any (matchingHost d) (srvHost srv)) ds) ss - in atomicModifyIORef'_ v $ add srv + in atomicModifyIORef'_ v (add srv <$>) addSMP srv s@UserOperatorServers {smpServers} = (s :: UserOperatorServers) {smpServers = srv : smpServers} addXFTP srv s@UserOperatorServers {xftpServers} = (s :: UserOperatorServers) {xftpServers = srv : xftpServers} @@ -434,7 +469,7 @@ validateUserServers curr others = currUserErrs <> concatMap otherUserErrs others | otherwise = [USEStorageMissing p' user | noServers (hasRole storage)] <> [USEProxyMissing p' user | noServers (hasRole proxy)] where p' = AProtocolType p - noServers cond = not $ any srvEnabled $ snd $ partitionValid $ concatMap (`servers'` p) $ filter cond uss + noServers cond = not $ any srvEnabled $ snd $ partitionValid $ concatMap (servers' p) $ filter cond uss opEnabled = maybe True (\ServerOperator {enabled} -> enabled) . operator' hasRole roleSel = maybe True (\op@ServerOperator {enabled} -> enabled && roleSel (operatorRoles p op)) . operator' srvEnabled (AUS _ UserServer {deleted, enabled}) = enabled && not deleted @@ -442,7 +477,7 @@ validateUserServers curr others = currUserErrs <> concatMap otherUserErrs others serverErrs p uss = map (USEInvalidServer p') invalidSrvs <> mapMaybe duplicateErr_ srvs where p' = AProtocolType p - (invalidSrvs, userSrvs) = partitionValid $ concatMap (`servers'` p) uss + (invalidSrvs, userSrvs) = partitionValid $ concatMap (servers' p) uss srvs = filter (\(AUS _ UserServer {deleted}) -> not deleted) userSrvs duplicateErr_ (AUS _ srv@UserServer {server}) = USEDuplicateServer p' (safeDecodeUtf8 $ strEncode server) diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index daf9a78fca..ec657fd6f7 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -50,9 +50,6 @@ module Simplex.Chat.Store.Profiles getContactWithoutConnViaAddress, updateUserAddressAutoAccept, getProtocolServers, - getUpdateUserServers, - -- overwriteOperatorsAndServers, - overwriteProtocolServers, insertProtocolServer, getUpdateServerOperators, getServerOperators, @@ -63,6 +60,7 @@ module Simplex.Chat.Store.Profiles setConditionsNotified, acceptConditions, setUserServers, + setUserServers', createCall, deleteCalls, getCalls, @@ -83,7 +81,7 @@ import Data.Functor (($>)) import Data.Int (Int64) import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as L -import Data.Maybe (fromMaybe) +import Data.Maybe (catMaybes, fromMaybe) import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (decodeLatin1, encodeUtf8) @@ -108,7 +106,7 @@ import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto.Ratchet as CR import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON) -import Simplex.Messaging.Protocol (BasicAuth (..), ProtoServerWithAuth (..), ProtocolServer (..), ProtocolType (..), ProtocolTypeI (..), SProtocolType (..), SubscriptionMode, UserProtocol) +import Simplex.Messaging.Protocol (BasicAuth (..), ProtoServerWithAuth (..), ProtocolServer (..), ProtocolType (..), ProtocolTypeI (..), SProtocolType (..), SubscriptionMode) import Simplex.Messaging.Transport.Client (TransportHost) import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8) @@ -532,18 +530,6 @@ updateUserAddressAutoAccept db user@User {userId} autoAccept = do Just AutoAccept {acceptIncognito, autoReply} -> (True, acceptIncognito, autoReply) _ -> (False, False, Nothing) -getUpdateUserServers :: forall p. (ProtocolTypeI p, UserProtocol p) => DB.Connection -> SProtocolType p -> NonEmpty PresetOperator -> NonEmpty (NewUserServer p) -> User -> IO [UserServer p] -getUpdateUserServers db p presetOps randomSrvs user = do - ts <- getCurrentTime - srvs <- getProtocolServers db p user - let srvs' = L.toList $ updatedUserServers p presetOps randomSrvs srvs - mapM (upsertServer ts) srvs' - where - upsertServer :: UTCTime -> AUserServer p -> IO (UserServer p) - upsertServer ts (AUS _ s@UserServer {serverId}) = case serverId of - DBNewEntity -> insertProtocolServer db p user ts s - DBEntityId _ -> updateProtocolServer db p ts s $> s - getProtocolServers :: forall p. ProtocolTypeI p => DB.Connection -> SProtocolType p -> User -> IO [UserServer p] getProtocolServers db p User {userId} = map toUserServer @@ -561,26 +547,6 @@ getProtocolServers db p User {userId} = let server = ProtoServerWithAuth (ProtocolServer p host port keyHash) (BasicAuth . encodeUtf8 <$> auth_) in UserServer {serverId, server, preset, tested, enabled, deleted = False} --- TODO remove --- overwriteOperatorsAndServers :: forall p. ProtocolTypeI p => DB.Connection -> User -> Maybe [ServerOperator] -> [ServerCfg p] -> ExceptT StoreError IO [ServerCfg p] --- overwriteOperatorsAndServers db user@User {userId} operators_ servers = do -overwriteProtocolServers :: ProtocolTypeI p => DB.Connection -> SProtocolType p -> User -> [UserServer p] -> ExceptT StoreError IO () -overwriteProtocolServers db p User {userId} servers = - -- liftIO $ mapM_ (updateServerOperators_ db) operators_ - checkConstraint SEUniqueID . ExceptT $ do - currentTs <- getCurrentTime - DB.execute db "DELETE FROM protocol_servers WHERE user_id = ? AND protocol = ? " (userId, decodeLatin1 $ strEncode p) - forM_ servers $ \UserServer {serverId, server, preset, tested, enabled} -> do - DB.execute - db - [sql| - INSERT INTO protocol_servers - (server_id, protocol, host, port, key_hash, basic_auth, preset, tested, enabled, user_id, created_at, updated_at) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?) - |] - (Only serverId :. serverColumns p server :. (preset, tested, enabled, userId, currentTs, currentTs)) - pure $ Right () - insertProtocolServer :: forall p. ProtocolTypeI p => DB.Connection -> SProtocolType p -> User -> UTCTime -> NewUserServer p -> IO (UserServer p) insertProtocolServer db p User {userId} ts srv@UserServer {server, preset, tested, enabled} = do DB.execute @@ -623,10 +589,10 @@ getServerOperators db = do let conditionsAction = usageConditionsAction ops currentConditions now pure ServerOperatorConditions {serverOperators = ops, currentConditions, conditionsAction} -getUserServers :: DB.Connection -> User -> ExceptT StoreError IO ([ServerOperator], [UserServer 'PSMP], [UserServer 'PXFTP]) +getUserServers :: DB.Connection -> User -> ExceptT StoreError IO ([Maybe ServerOperator], [UserServer 'PSMP], [UserServer 'PXFTP]) getUserServers db user = (,,) - <$> (serverOperators <$> getServerOperators db) + <$> (map Just . serverOperators <$> getServerOperators db) <*> liftIO (getProtocolServers db SPSMP user) <*> liftIO (getProtocolServers db SPXFTP user) @@ -646,7 +612,7 @@ updateServerOperator db currentTs ServerOperator {operatorId, enabled, smpRoles, |] (enabled, storage smpRoles, proxy smpRoles, storage xftpRoles, proxy xftpRoles, currentTs, operatorId) -getUpdateServerOperators :: DB.Connection -> NonEmpty PresetOperator -> Bool -> IO [ServerOperator] +getUpdateServerOperators :: DB.Connection -> NonEmpty PresetOperator -> Bool -> IO [(Maybe PresetOperator, Maybe ServerOperator)] getUpdateServerOperators db presetOps newUser = do conds <- map toUsageConditions <$> DB.query_ db usageCondsQuery now <- getCurrentTime @@ -654,7 +620,7 @@ getUpdateServerOperators db presetOps newUser = do mapM_ insertConditions condsToAdd latestAcceptedConds_ <- getLatestAcceptedConditions db ops <- updatedServerOperators presetOps <$> getServerOperators_ db - forM ops $ \(ASO _ op) -> + forM ops $ traverse $ mapM $ \(ASO _ op) -> -- traverse for tuple, mapM for Maybe case operatorId op of DBNewEntity -> do op' <- insertOperator op @@ -825,22 +791,24 @@ getUsageConditionsById_ db conditionsId = |] (Only conditionsId) -setUserServers :: DB.Connection -> User -> NonEmpty UpdatedUserOperatorServers -> ExceptT StoreError IO () -setUserServers db user@User {userId} userServers = checkConstraint SEUniqueID $ liftIO $ do - ts <- getCurrentTime - forM_ userServers $ \UpdatedUserOperatorServers {operator, smpServers, xftpServers} -> do - mapM_ (updateServerOperator db ts) operator - mapM_ (upsertOrDelete SPSMP ts) smpServers - mapM_ (upsertOrDelete SPXFTP ts) xftpServers +setUserServers :: DB.Connection -> User -> UTCTime -> UpdatedUserOperatorServers -> ExceptT StoreError IO UserOperatorServers +setUserServers db user ts = checkConstraint SEUniqueID . liftIO . setUserServers' db user ts + +setUserServers' :: DB.Connection -> User -> UTCTime -> UpdatedUserOperatorServers -> IO UserOperatorServers +setUserServers' db user@User {userId} ts UpdatedUserOperatorServers {operator, smpServers, xftpServers} = do + mapM_ (updateServerOperator db ts) operator + smpSrvs' <- catMaybes <$> mapM (upsertOrDelete SPSMP) smpServers + xftpSrvs' <- catMaybes <$> mapM (upsertOrDelete SPXFTP) xftpServers + pure UserOperatorServers {operator, smpServers = smpSrvs', xftpServers = xftpSrvs'} where - upsertOrDelete :: ProtocolTypeI p => SProtocolType p -> UTCTime -> AUserServer p -> IO () - upsertOrDelete p ts (AUS _ s@UserServer {serverId, deleted}) = case serverId of + upsertOrDelete :: ProtocolTypeI p => SProtocolType p -> AUserServer p -> IO (Maybe (UserServer p)) + upsertOrDelete p (AUS _ s@UserServer {serverId, deleted}) = case serverId of DBNewEntity - | deleted -> pure () - | otherwise -> void $ insertProtocolServer db p user ts s + | deleted -> pure Nothing + | otherwise -> Just <$> insertProtocolServer db p user ts s DBEntityId srvId - | deleted -> DB.execute db "DELETE FROM protocol_servers WHERE user_id = ? AND smp_server_id = ? AND preset = ?" (userId, srvId, False) - | otherwise -> updateProtocolServer db p ts s + | deleted -> Nothing <$ DB.execute db "DELETE FROM protocol_servers WHERE user_id = ? AND smp_server_id = ? AND preset = ?" (userId, srvId, False) + | otherwise -> Just s <$ updateProtocolServer db p ts s createCall :: DB.Connection -> User -> Call -> UTCTime -> IO () createCall db user@User {userId} Call {contactId, callId, callUUID, chatItemId, callState} callTs = do diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index c2cc44d164..7bf7804472 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -25,10 +25,9 @@ import Data.Maybe (isNothing) import qualified Data.Text as T import Network.Socket import Simplex.Chat -import Simplex.Chat.Controller (ChatCommand (..), ChatConfig (..), ChatController (..), ChatDatabase (..), ChatLogLevel (..), PresetServers (..), defaultSimpleNetCfg) +import Simplex.Chat.Controller (ChatCommand (..), ChatConfig (..), ChatController (..), ChatDatabase (..), ChatLogLevel (..), defaultSimpleNetCfg) import Simplex.Chat.Core import Simplex.Chat.Options -import Simplex.Chat.Operators (PresetOperator (..), presetServer) import Simplex.Chat.Protocol (currentChatVersion, pqEncryptionCompressionVersion) import Simplex.Chat.Store import Simplex.Chat.Store.Profiles @@ -95,8 +94,8 @@ testCoreOpts = { dbFilePrefix = "./simplex_v1", dbKey = "", -- dbKey = "this is a pass-phrase to encrypt the database", - smpServers = [], - xftpServers = [], + smpServers = ["smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001"], + xftpServers = ["xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002"], simpleNetCfg = defaultSimpleNetCfg, logLevel = CLLImportant, logConnections = False, @@ -150,18 +149,6 @@ testCfg :: ChatConfig testCfg = defaultChatConfig { agentConfig = testAgentCfg, - presetServers = - (presetServers defaultChatConfig) - { operators = - [ PresetOperator - { operator = Nothing, - smp = map (presetServer True) ["smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001"], - useSMP = 1, - xftp = map (presetServer True) ["xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002"], - useXFTP = 1 - } - ] - }, showReceipts = False, testView = True, tbqSize = 16 diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index bd2a267c3a..6bbf72171e 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -240,6 +240,7 @@ testRetryConnecting tmp = testChatCfgOpts2 cfg' opts' aliceProfile bobProfile te bob <##. "smp agent error: BROKER" withSmpServer' serverCfg' $ do alice <## "server connected localhost ()" + threadDelay 250000 bob ##> ("/_connect plan 1 " <> inv) bob <## "invitation link: ok to connect" bob ##> ("/_connect 1 " <> inv) @@ -1144,27 +1145,24 @@ testGetSetSMPServers = alice ##> "/_servers 1" alice <## "Your servers" alice <## " SMP servers" - alice <## " smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001 (preset)" + alice <## " smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001" alice <## " XFTP servers" - alice <## " xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002 (preset)" + alice <## " xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002" alice #$> ("/smp smp://1234-w==@smp1.example.im", id, "ok") alice ##> "/smp" alice <## "Your servers" alice <## " SMP servers" - alice <## " smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001 (preset, disabled)" alice <## " smp://1234-w==@smp1.example.im" alice #$> ("/smp smp://1234-w==:password@smp1.example.im", id, "ok") -- alice #$> ("/smp", id, "smp://1234-w==:password@smp1.example.im") alice ##> "/smp" alice <## "Your servers" alice <## " SMP servers" - alice <## " smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001 (preset, disabled)" alice <## " smp://1234-w==:password@smp1.example.im" alice #$> ("/smp smp://2345-w==@smp2.example.im smp://3456-w==@smp3.example.im:5224", id, "ok") alice ##> "/smp" alice <## "Your servers" alice <## " SMP servers" - alice <## " smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001 (preset, disabled)" alice <## " smp://2345-w==@smp2.example.im" alice <## " smp://3456-w==@smp3.example.im:5224" @@ -1190,26 +1188,23 @@ testGetSetXFTPServers = alice ##> "/_servers 1" alice <## "Your servers" alice <## " SMP servers" - alice <## " smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001 (preset)" + alice <## " smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001" alice <## " XFTP servers" - alice <## " xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002 (preset)" + alice <## " xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002" alice #$> ("/xftp xftp://1234-w==@xftp1.example.im", id, "ok") alice ##> "/xftp" alice <## "Your servers" alice <## " XFTP servers" - alice <## " xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002 (preset, disabled)" alice <## " xftp://1234-w==@xftp1.example.im" alice #$> ("/xftp xftp://1234-w==:password@xftp1.example.im", id, "ok") alice ##> "/xftp" alice <## "Your servers" alice <## " XFTP servers" - alice <## " xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002 (preset, disabled)" alice <## " xftp://1234-w==:password@xftp1.example.im" alice #$> ("/xftp xftp://2345-w==@xftp2.example.im xftp://3456-w==@xftp3.example.im:5224", id, "ok") alice ##> "/xftp" alice <## "Your servers" alice <## " XFTP servers" - alice <## " xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002 (preset, disabled)" alice <## " xftp://2345-w==@xftp2.example.im" alice <## " xftp://3456-w==@xftp3.example.im:5224" @@ -1831,13 +1826,11 @@ testCreateUserSameServers = alice ##> "/smp" alice <## "Your servers" alice <## " SMP servers" - alice <## " smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001 (preset, disabled)" alice <## " smp://2345-w==@smp2.example.im" alice <## " smp://3456-w==@smp3.example.im:5224" alice ##> "/xftp" alice <## "Your servers" alice <## " XFTP servers" - alice <## " xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002 (preset, disabled)" alice <## " xftp://2345-w==@xftp2.example.im" alice <## " xftp://3456-w==@xftp3.example.im:5224" diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index a51f42114b..bdd3b53829 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -1988,7 +1988,6 @@ testGroupAsync tmp = do (bob <## "#team: you joined the group") alice #> "#team hello bob" bob <# "#team alice> hello bob" - print (1 :: Integer) withTestChat tmp "alice" $ \alice -> do withNewTestChat tmp "cath" cathProfile $ \cath -> do alice <## "1 contacts connected (use /cs for the list)" @@ -2008,7 +2007,6 @@ testGroupAsync tmp = do ] alice #> "#team hello cath" cath <# "#team alice> hello cath" - print (2 :: Integer) withTestChat tmp "bob" $ \bob -> do withTestChat tmp "cath" $ \cath -> do concurrentlyN_ @@ -2024,7 +2022,6 @@ testGroupAsync tmp = do cath <## "#team: member bob (Bob) is connected" ] threadDelay 500000 - print (3 :: Integer) withTestChat tmp "bob" $ \bob -> do withNewTestChat tmp "dan" danProfile $ \dan -> do bob <## "2 contacts connected (use /cs for the list)" @@ -2044,7 +2041,6 @@ testGroupAsync tmp = do ] threadDelay 1000000 threadDelay 1000000 - print (4 :: Integer) withTestChat tmp "alice" $ \alice -> do withTestChat tmp "cath" $ \cath -> do withTestChat tmp "dan" $ \dan -> do @@ -2066,7 +2062,6 @@ testGroupAsync tmp = do dan <## "#team: member cath (Catherine) is connected" ] threadDelay 1000000 - print (5 :: Integer) withTestChat tmp "alice" $ \alice -> do withTestChat tmp "bob" $ \bob -> do withTestChat tmp "cath" $ \cath -> do diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index 1d390e1236..3ff8808541 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -273,6 +273,7 @@ testRetryAcceptingViaContactLink tmp = testChatCfgOpts2 cfg' opts' aliceProfile bob <##. "smp agent error: BROKER" withSmpServer' serverCfg' $ do alice <## "server connected localhost ()" + threadDelay 250000 bob ##> ("/_connect plan 1 " <> cLink) bob <## "contact address: ok to connect" bob ##> ("/_connect 1 " <> cLink) @@ -1737,12 +1738,11 @@ testChangePCCUserDiffSrv tmp = do alice ##> "/smp" alice <## "Your servers" alice <## " SMP servers" - alice <## " smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001 (preset)" + alice <## " smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001" alice #$> ("/smp smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@127.0.0.1:7003", id, "ok") alice ##> "/smp" alice <## "Your servers" alice <## " SMP servers" - alice <## " smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001 (preset, disabled)" alice <## " smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@127.0.0.1:7003" alice ##> "/user alice" showActiveUser alice "alice (Alice)" diff --git a/tests/OperatorTests.hs b/tests/OperatorTests.hs index 4966bfbb97..03cea56133 100644 --- a/tests/OperatorTests.hs +++ b/tests/OperatorTests.hs @@ -1,6 +1,12 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeApplications #-} {-# OPTIONS_GHC -Wno-orphans #-} @@ -8,8 +14,10 @@ module OperatorTests (operatorTests) where +import Data.Bifunctor (second) import qualified Data.List.NonEmpty as L import Simplex.Chat +import Simplex.Chat.Controller (ChatConfig (..), PresetServers (..)) import Simplex.Chat.Operators import Simplex.Chat.Types import Simplex.FileTransfer.Client.Presets (defaultXFTPServers) @@ -19,10 +27,11 @@ import Test.Hspec operatorTests :: Spec operatorTests = describe "managing server operators" $ do - validateServers + validateServersTest + updatedServersTest -validateServers :: Spec -validateServers = describe "validate user servers" $ do +validateServersTest :: Spec +validateServersTest = describe "validate user servers" $ do it "should pass valid user servers" $ validateUserServers [valid] [] `shouldBe` [] it "should fail without servers" $ do validateUserServers [invalidNoServers] [] `shouldBe` [USENoServers aSMP Nothing] @@ -41,6 +50,50 @@ validateServers = describe "validate user servers" $ do aSMP = AProtocolType SPSMP aXFTP = AProtocolType SPXFTP +updatedServersTest :: Spec +updatedServersTest = describe "validate user servers" $ do + it "adding preset operators on first start" $ do + let ops' :: [(Maybe PresetOperator, Maybe AServerOperator)] = + updatedServerOperators operators [] + length ops' `shouldBe` 2 + all addedPreset ops' `shouldBe` True + let ops'' :: [(Maybe PresetOperator, Maybe ServerOperator)] = + saveOps ops' -- mock getUpdateServerOperators + uss <- groupByOperator' (ops'', [], []) -- no stored servers + length uss `shouldBe` 3 + [op1, op2, op3] <- pure $ map updatedUserServers uss + [p1, p2] <- pure operators -- presets + sameServers p1 op1 + sameServers p2 op2 + null (servers' SPSMP op3) `shouldBe` True + null (servers' SPXFTP op3) `shouldBe` True + it "adding preset operators and assiging servers to operator for existing users" $ do + let ops' = updatedServerOperators operators [] + ops'' = saveOps ops' + uss <- + groupByOperator' + ( ops'', + saveSrvs $ take 3 simplexChatSMPServers <> [newUserServer "smp://abcd@smp.example.im"], + saveSrvs $ map (presetServer True) $ L.take 3 defaultXFTPServers + ) + [op1, op2, op3] <- pure $ map updatedUserServers uss + [p1, p2] <- pure operators -- presets + sameServers p1 op1 + sameServers p2 op2 + map srvHost' (servers' SPSMP op3) `shouldBe` [["smp.example.im"]] + null (servers' SPXFTP op3) `shouldBe` True + where + addedPreset = \case + (Just PresetOperator {operator = Just op}, Just (ASO SDBNew op')) -> operatorTag op == operatorTag op' + _ -> False + saveOps = zipWith (\i -> second ((\(ASO _ op) -> op {operatorId = DBEntityId i}) <$>)) [1..] + saveSrvs = zipWith (\i srv -> srv {serverId = DBEntityId i}) [1..] + sameServers preset op = do + map srvHost (pServers SPSMP preset) `shouldBe` map srvHost' (servers' SPSMP op) + map srvHost (pServers SPXFTP preset) `shouldBe` map srvHost' (servers' SPXFTP op) + srvHost' (AUS _ s) = srvHost s + PresetServers {operators} = presetServers defaultChatConfig + deriving instance Eq User deriving instance Eq UserServersError diff --git a/tests/RandomServers.hs b/tests/RandomServers.hs index 048a2b5e5a..d0d74724d0 100644 --- a/tests/RandomServers.hs +++ b/tests/RandomServers.hs @@ -14,9 +14,8 @@ import Control.Monad (replicateM) import Data.Foldable (foldMap') import Data.List (sortOn) import Data.List.NonEmpty (NonEmpty) -import qualified Data.List.NonEmpty as L import Data.Monoid (Sum (..)) -import Simplex.Chat (defaultChatConfig, randomPresetServers) +import Simplex.Chat (defaultChatConfig, chooseRandomServers) import Simplex.Chat.Controller (ChatConfig (..), PresetServers (..)) import Simplex.Chat.Operators import Simplex.Messaging.Agent.Env.SQLite (ServerRoles (..)) @@ -38,22 +37,25 @@ testRandomSMPServers :: IO () testRandomSMPServers = do [srvs1, srvs2, srvs3] <- replicateM 3 $ - checkEnabled SPSMP 7 False =<< randomPresetServers SPSMP (presetServers defaultChatConfig) + checkEnabled SPSMP 7 False =<< chooseRandomServers (presetServers defaultChatConfig) (srvs1 == srvs2 && srvs2 == srvs3) `shouldBe` False -- && to avoid rare failures testRandomXFTPServers :: IO () testRandomXFTPServers = do [srvs1, srvs2, srvs3] <- replicateM 3 $ - checkEnabled SPXFTP 6 False =<< randomPresetServers SPXFTP (presetServers defaultChatConfig) + checkEnabled SPXFTP 6 False =<< chooseRandomServers (presetServers defaultChatConfig) (srvs1 == srvs2 && srvs2 == srvs3) `shouldBe` False -- && to avoid rare failures -checkEnabled :: UserProtocol p => SProtocolType p -> Int -> Bool -> NonEmpty (NewUserServer p) -> IO [NewUserServer p] -checkEnabled p n allUsed srvs = do - let srvs' = sortOn server' $ L.toList srvs - PresetServers {operators = presetOps} = presetServers defaultChatConfig - presetSrvs = sortOn server' $ concatMap (operatorServers p) presetOps +checkEnabled :: UserProtocol p => SProtocolType p -> Int -> Bool -> NonEmpty (PresetOperator) -> IO [NewUserServer p] +checkEnabled p n allUsed presetOps' = do + let PresetServers {operators = presetOps} = presetServers defaultChatConfig + presetSrvs = sortOn server' $ concatMap (pServers p) presetOps + srvs' = sortOn server' $ concatMap (pServers p) presetOps' Sum toUse = foldMap' (Sum . operatorServersToUse p) presetOps + Sum toUse' = foldMap' (Sum . operatorServersToUse p) presetOps' + length presetOps `shouldBe` length presetOps' + toUse `shouldBe` toUse' srvs' == presetSrvs `shouldBe` allUsed map enable srvs' `shouldBe` map enable presetSrvs let enbldSrvs = filter (\UserServer {enabled} -> enabled) srvs' From fcae5e992582780dcf053b1353d2c615f5e15a1d Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 19 Nov 2024 00:22:35 +0400 Subject: [PATCH 080/567] core: fix validation of operator servers for non current users (#5205) * core: fix validation of operator servers for non current users * style * refactor --------- Co-authored-by: Evgeny Poberezkin --- src/Simplex/Chat.hs | 13 +++++++++++-- src/Simplex/Chat/Operators.hs | 2 ++ tests/RandomServers.hs | 2 -- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 819832a1ed..11cd8e33ad 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1611,7 +1611,7 @@ processChatCommand' vr = \case srvs' <- mapM aUserServer srvs processChatCommand $ APISetUserServers userId $ L.map (updatedServers p srvs') userServers where - aUserServer :: AProtoServerWithAuth -> CM (AUserServer p) + aUserServer :: AProtoServerWithAuth -> CM (AUserServer p) aUserServer (AProtoServerWithAuth p' srv) = case testEquality p p' of Just Refl -> pure $ AUS SDBNew $ newUserServer srv Nothing -> throwChatError $ CECommandError $ "incorrect server protocol: " <> B.unpack (strEncode srv) @@ -2949,8 +2949,17 @@ processChatCommand' vr = \case validateAllUsersServers :: UserServersClass u => Int64 -> [u] -> CM [UserServersError] validateAllUsersServers currUserId userServers = withFastStore $ \db -> do users' <- filter (\User {userId} -> userId /= currUserId) <$> liftIO (getUsers db) - others <- mapM (\user -> liftIO . fmap (user,) . groupByOperator =<< getUserServers db user) users' + others <- mapM (getUserOperatorServers db) users' pure $ validateUserServers userServers others + where + getUserOperatorServers :: DB.Connection -> User -> ExceptT StoreError IO (User, [UserOperatorServers]) + getUserOperatorServers db user = do + uss <- liftIO . groupByOperator =<< getUserServers db user + pure (user, map updatedUserServers uss) + updatedUserServers uss = uss {operator = updatedOp <$> operator' uss} :: UserOperatorServers + updatedOp op = fromMaybe op $ find matchingOp $ mapMaybe operator' userServers + where + matchingOp op' = operatorId op' == operatorId op forwardFile :: ChatName -> FileTransferId -> (ChatName -> CryptoFile -> ChatCommand) -> CM ChatResponse forwardFile chatName fileId sendCommand = withUser $ \user -> do withStore (\db -> getFileTransfer db user fileId) >>= \case diff --git a/src/Simplex/Chat/Operators.hs b/src/Simplex/Chat/Operators.hs index f7a07682f9..1f9b79b56b 100644 --- a/src/Simplex/Chat/Operators.hs +++ b/src/Simplex/Chat/Operators.hs @@ -89,6 +89,8 @@ data DBEntityId' (s :: DBStored) where deriving instance Show (DBEntityId' s) +deriving instance Eq (DBEntityId' s) + type DBEntityId = DBEntityId' 'DBStored type DBNewEntity = DBEntityId' 'DBNew diff --git a/tests/RandomServers.hs b/tests/RandomServers.hs index d0d74724d0..9b83be26c4 100644 --- a/tests/RandomServers.hs +++ b/tests/RandomServers.hs @@ -29,8 +29,6 @@ randomServersTests = describe "choosig random servers" $ do deriving instance Eq ServerRoles -deriving instance Eq (DBEntityId' s) - deriving instance Eq (UserServer' s p) testRandomSMPServers :: IO () From 70a29512b76fd9cd6f04c790d4ae13f348e68eda Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 19 Nov 2024 15:37:00 +0400 Subject: [PATCH 081/567] ios: server operators ui (#5114) * wip * refactor, fix bindings * wip * wip * fixes * wip * information map, logos * global conditions hack * restructure * restructure * texts * text * restructure * wip * restructure * rename * wip * conditions for all * comment * onboarding wip * onboarding wip * fix paddings * fix paddings * wip * fix padding * onboarding wip * nav link instead of sheet * pretty button * large titles * notifications mode button style * reenable demo operator * Revert "reenable demo operator" This reverts commit 42111eb333bd5482100567c2f9855756d364caf3. * padding * reenable demo operator * refactor (removes additional model api) * style * bold * bold * light/dark * fix button * comment * wip * remove preset * new types * api types * apis * smp and xftp servers in single view * test operator servers, refactor * save in main view * better progress * better in progress * remove shadow * update * apis * conditions view wip * load text * remove custom servers button from onboarding, open already conditions in nav link * allow to continue with simplex on onboarding * footer * existing users notice * fix to not show nothing on no action * disable notice * review later * disable notice * wip * wip * wip * wip * optional tag * fix * fix tags * fix * wip * remove coding keys * fix onboarding * rename * rework model wip * wip * wip * wip * fix * wip * wip * delete * simplify * wip * fix delete * ios: server operators ui wip * refactor * edited * save servers on dismiss/back * ios: add address card and remove address from onboarding (#5181) * ios: add address card and remove address from onboarding * allow for address creation in info when open via card * conditions interactions wip * conditions interactions wip * fix * wip * wip * wip * wip * rename * wip * fix * remove operator binding * fix set enabled * rename * cleanup * text * fix info view dark mode * update lib * ios: operators & servers validation * fix * ios: align onboarding style * ios: align onboarding style * ios: operators info (#5207) * ios: operators info * update * update texts * texts --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --------- Co-authored-by: Diogo Co-authored-by: Evgeny Poberezkin --- .../flux_logo-light.imageset/Contents.json | 21 + .../Flux_logo_blue_white.png | Bin 0 -> 33847 bytes .../flux_logo.imageset/Contents.json | 21 + .../flux_logo.imageset/Flux_logo_blue.png | Bin 0 -> 34876 bytes .../flux_logo_symbol.imageset/Contents.json | 21 + .../Flux_symbol_blue-white.png | Bin 0 -> 17248 bytes apps/ios/Shared/ContentView.swift | 29 +- apps/ios/Shared/Model/ChatModel.swift | 2 + apps/ios/Shared/Model/SimpleXAPI.swift | 74 ++- .../Shared/Views/ChatList/ChatListView.swift | 27 +- .../Views/ChatList/ServersSummaryView.swift | 51 +- .../Onboarding/AddressCreationCard.swift | 116 ++++ .../Onboarding/ChooseServerOperators.swift | 344 +++++++++++ .../Views/Onboarding/CreateProfile.swift | 86 ++- .../Shared/Views/Onboarding/HowItWorks.swift | 21 +- .../Views/Onboarding/OnboardingView.swift | 6 +- .../Onboarding/SetNotificationsMode.swift | 63 +- .../Shared/Views/Onboarding/SimpleXInfo.swift | 174 +++--- .../Views/Onboarding/WhatsNewView.swift | 433 +++++++------ .../NetworkAndServers/NetworkAndServers.swift | 363 ++++++++++- .../NetworkAndServers/NewServerView.swift | 156 +++++ .../NetworkAndServers/OperatorView.swift | 569 ++++++++++++++++++ .../ProtocolServerView.swift | 57 +- .../ProtocolServersView.swift | 472 +++++++-------- .../ScanProtocolServer.swift | 24 +- .../Views/UserSettings/SettingsView.swift | 27 +- .../UserSettings/UserAddressLearnMore.swift | 46 +- .../Views/UserSettings/UserAddressView.swift | 42 +- apps/ios/SimpleX.xcodeproj/project.pbxproj | 66 +- apps/ios/SimpleXChat/APITypes.swift | 443 ++++++++++++-- 30 files changed, 3014 insertions(+), 740 deletions(-) create mode 100644 apps/ios/Shared/Assets.xcassets/flux_logo-light.imageset/Contents.json create mode 100644 apps/ios/Shared/Assets.xcassets/flux_logo-light.imageset/Flux_logo_blue_white.png create mode 100644 apps/ios/Shared/Assets.xcassets/flux_logo.imageset/Contents.json create mode 100644 apps/ios/Shared/Assets.xcassets/flux_logo.imageset/Flux_logo_blue.png create mode 100644 apps/ios/Shared/Assets.xcassets/flux_logo_symbol.imageset/Contents.json create mode 100644 apps/ios/Shared/Assets.xcassets/flux_logo_symbol.imageset/Flux_symbol_blue-white.png create mode 100644 apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift create mode 100644 apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift create mode 100644 apps/ios/Shared/Views/UserSettings/NetworkAndServers/NewServerView.swift create mode 100644 apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift diff --git a/apps/ios/Shared/Assets.xcassets/flux_logo-light.imageset/Contents.json b/apps/ios/Shared/Assets.xcassets/flux_logo-light.imageset/Contents.json new file mode 100644 index 0000000000..d3a15f9a33 --- /dev/null +++ b/apps/ios/Shared/Assets.xcassets/flux_logo-light.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Flux_logo_blue_white.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apps/ios/Shared/Assets.xcassets/flux_logo-light.imageset/Flux_logo_blue_white.png b/apps/ios/Shared/Assets.xcassets/flux_logo-light.imageset/Flux_logo_blue_white.png new file mode 100644 index 0000000000000000000000000000000000000000..e1d6dda4fedd57195f0ee425a0a28a579f16b266 GIT binary patch literal 33847 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�>Y9E5G~yEbTaSm_@QckJ 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

PL9g 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 F{C_Q?zn%a9 literal 0 HcmV?d00001 diff --git a/apps/ios/Shared/Assets.xcassets/flux_logo_symbol.imageset/Contents.json b/apps/ios/Shared/Assets.xcassets/flux_logo_symbol.imageset/Contents.json new file mode 100644 index 0000000000..16686bdf80 --- /dev/null +++ b/apps/ios/Shared/Assets.xcassets/flux_logo_symbol.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Flux_symbol_blue-white.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apps/ios/Shared/Assets.xcassets/flux_logo_symbol.imageset/Flux_symbol_blue-white.png b/apps/ios/Shared/Assets.xcassets/flux_logo_symbol.imageset/Flux_symbol_blue-white.png new file mode 100644 index 0000000000000000000000000000000000000000..0793b0ee85dfff83a4e180ebc0cc70230a452c28 GIT binary patch literal 17248 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 E0Eh^ Void> = [:] diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 9297aa7898..c61ad412c0 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -500,18 +500,6 @@ func apiDeleteToken(token: DeviceToken) async throws { try await sendCommandOkResp(.apiDeleteToken(token: token)) } -func getUserProtoServers(_ serverProtocol: ServerProtocol) throws -> UserProtoServers { - let userId = try currentUserId("getUserProtoServers") - let r = chatSendCmdSync(.apiGetUserProtoServers(userId: userId, serverProtocol: serverProtocol)) - if case let .userProtoServers(_, servers) = r { return servers } - throw r -} - -func setUserProtoServers(_ serverProtocol: ServerProtocol, servers: [ServerCfg]) async throws { - let userId = try currentUserId("setUserProtoServers") - try await sendCommandOkResp(.apiSetUserProtoServers(userId: userId, serverProtocol: serverProtocol, servers: servers)) -} - func testProtoServer(server: String) async throws -> Result<(), ProtocolTestFailure> { let userId = try currentUserId("testProtoServer") let r = await chatSendCmd(.apiTestProtoServer(userId: userId, server: server)) @@ -524,6 +512,65 @@ func testProtoServer(server: String) async throws -> Result<(), ProtocolTestFail throw r } +func getServerOperators() throws -> ServerOperatorConditions { + let r = chatSendCmdSync(.apiGetServerOperators) + if case let .serverOperatorConditions(conditions) = r { return conditions } + logger.error("getServerOperators error: \(String(describing: r))") + throw r +} + +func setServerOperators(operators: [ServerOperator]) async throws -> ServerOperatorConditions { + let r = await chatSendCmd(.apiSetServerOperators(operators: operators)) + if case let .serverOperatorConditions(conditions) = r { return conditions } + logger.error("setServerOperators error: \(String(describing: r))") + throw r +} + +func getUserServers() async throws -> [UserOperatorServers] { + let userId = try currentUserId("getUserServers") + let r = await chatSendCmd(.apiGetUserServers(userId: userId)) + if case let .userServers(_, userServers) = r { return userServers } + logger.error("getUserServers error: \(String(describing: r))") + throw r +} + +func setUserServers(userServers: [UserOperatorServers]) async throws { + let userId = try currentUserId("setUserServers") + let r = await chatSendCmd(.apiSetUserServers(userId: userId, userServers: userServers)) + if case .cmdOk = r { return } + logger.error("setUserServers error: \(String(describing: r))") + throw r +} + +func validateServers(userServers: [UserOperatorServers]) async throws -> [UserServersError] { + let userId = try currentUserId("validateServers") + let r = await chatSendCmd(.apiValidateServers(userId: userId, userServers: userServers)) + if case let .userServersValidation(_, serverErrors) = r { return serverErrors } + logger.error("validateServers error: \(String(describing: r))") + throw r +} + +func getUsageConditions() async throws -> (UsageConditions, String?, UsageConditions?) { + let r = await chatSendCmd(.apiGetUsageConditions) + if case let .usageConditions(usageConditions, conditionsText, acceptedConditions) = r { return (usageConditions, conditionsText, acceptedConditions) } + logger.error("getUsageConditions error: \(String(describing: r))") + throw r +} + +func setConditionsNotified(conditionsId: Int64) async throws { + let r = await chatSendCmd(.apiSetConditionsNotified(conditionsId: conditionsId)) + if case .cmdOk = r { return } + logger.error("setConditionsNotified error: \(String(describing: r))") + throw r +} + +func acceptConditions(conditionsId: Int64, operatorIds: [Int64]) async throws -> ServerOperatorConditions { + let r = await chatSendCmd(.apiAcceptConditions(conditionsId: conditionsId, operatorIds: operatorIds)) + if case let .serverOperatorConditions(conditions) = r { return conditions } + logger.error("acceptConditions error: \(String(describing: r))") + throw r +} + func getChatItemTTL() throws -> ChatItemTTL { let userId = try currentUserId("getChatItemTTL") return try chatItemTTLResponse(chatSendCmdSync(.apiGetChatItemTTL(userId: userId))) @@ -1558,6 +1605,7 @@ func initializeChat(start: Bool, confirmStart: Bool = false, dbKey: String? = ni try apiSetEncryptLocalFiles(privacyEncryptLocalFilesGroupDefault.get()) m.chatInitialized = true m.currentUser = try apiGetActiveUser() + m.conditions = try getServerOperators() if m.currentUser == nil { onboardingStageDefault.set(.step1_SimpleXInfo) privacyDeliveryReceiptsSet.set(true) @@ -1624,7 +1672,7 @@ func startChat(refreshInvitations: Bool = true) throws { withAnimation { let savedOnboardingStage = onboardingStageDefault.get() m.onboardingStage = [.step1_SimpleXInfo, .step2_CreateProfile].contains(savedOnboardingStage) && m.users.count == 1 - ? .step3_CreateSimpleXAddress + ? .step3_ChooseServerOperators : savedOnboardingStage if m.onboardingStage == .onboardingComplete && !privacyDeliveryReceiptsSet.get() { m.setDeliveryReceipts = true diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 7b24995f62..8e7aec581b 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -36,6 +36,10 @@ struct UserPickerSheetView: View { @EnvironmentObject var chatModel: ChatModel @State private var loaded = false + @State private var currUserServers: [UserOperatorServers] = [] + @State private var userServers: [UserOperatorServers] = [] + @State private var serverErrors: [UserServersError] = [] + var body: some View { NavigationView { ZStack { @@ -56,7 +60,11 @@ struct UserPickerSheetView: View { case .useFromDesktop: ConnectDesktopView() case .settings: - SettingsView() + SettingsView( + currUserServers: $currUserServers, + userServers: $userServers, + serverErrors: $serverErrors + ) } } Color.clear // Required for list background to be rendered during loading @@ -76,6 +84,16 @@ struct UserPickerSheetView: View { { loaded = true } ) } + .onDisappear { + if serversCanBeSaved(currUserServers, userServers, serverErrors) { + showAlert( + title: NSLocalizedString("Save servers?", comment: "alert title"), + buttonTitle: NSLocalizedString("Save", comment: "alert button"), + buttonAction: { saveServers($currUserServers, $userServers) }, + cancelButton: true + ) + } + } } } @@ -94,6 +112,7 @@ struct ChatListView: View { @AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false @AppStorage(GROUP_DEFAULT_ONE_HAND_UI, store: groupDefaults) private var oneHandUI = true @AppStorage(DEFAULT_ONE_HAND_UI_CARD_SHOWN) private var oneHandUICardShown = false + @AppStorage(DEFAULT_ADDRESS_CREATION_CARD_SHOWN) private var addressCreationCardShown = false @AppStorage(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial var body: some View { @@ -282,6 +301,12 @@ struct ChatListView: View { .listRowSeparator(.hidden) .listRowBackground(Color.clear) } + if !addressCreationCardShown { + AddressCreationCard() + .scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center) + .listRowSeparator(.hidden) + .listRowBackground(Color.clear) + } if #available(iOS 16.0, *) { ForEach(cs, id: \.viewId) { chat in ChatListNavLink(chat: chat) diff --git a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift index 22ea78f27b..a13a159a45 100644 --- a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift +++ b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift @@ -20,6 +20,10 @@ struct ServersSummaryView: View { @State private var timer: Timer? = nil @State private var alert: SomeAlert? + @State private var currUserServers: [UserOperatorServers] = [] + @State private var userServers: [UserOperatorServers] = [] + @State private var serverErrors: [UserServersError] = [] + @AppStorage(DEFAULT_SHOW_SUBSCRIPTION_PERCENTAGE) private var showSubscriptionPercentage = false enum PresentedUserCategory { @@ -53,6 +57,15 @@ struct ServersSummaryView: View { } .onDisappear { stopTimer() + + if serversCanBeSaved(currUserServers, userServers, serverErrors) { + showAlert( + title: NSLocalizedString("Save servers?", comment: "alert title"), + buttonTitle: NSLocalizedString("Save", comment: "alert button"), + buttonAction: { saveServers($currUserServers, $userServers) }, + cancelButton: true + ) + } } .alert(item: $alert) { $0.alert } } @@ -275,7 +288,10 @@ struct ServersSummaryView: View { NavigationLink(tag: srvSumm.id, selection: $selectedSMPServer) { SMPServerSummaryView( summary: srvSumm, - statsStartedAt: statsStartedAt + statsStartedAt: statsStartedAt, + currUserServers: $currUserServers, + userServers: $userServers, + serverErrors: $serverErrors ) .navigationBarTitle("SMP server") .navigationBarTitleDisplayMode(.large) @@ -344,7 +360,10 @@ struct ServersSummaryView: View { NavigationLink(tag: srvSumm.id, selection: $selectedXFTPServer) { XFTPServerSummaryView( summary: srvSumm, - statsStartedAt: statsStartedAt + statsStartedAt: statsStartedAt, + currUserServers: $currUserServers, + userServers: $userServers, + serverErrors: $serverErrors ) .navigationBarTitle("XFTP server") .navigationBarTitleDisplayMode(.large) @@ -486,6 +505,10 @@ struct SMPServerSummaryView: View { @AppStorage(DEFAULT_SHOW_SUBSCRIPTION_PERCENTAGE) private var showSubscriptionPercentage = false + @Binding var currUserServers: [UserOperatorServers] + @Binding var userServers: [UserOperatorServers] + @Binding var serverErrors: [UserServersError] + var body: some View { List { Section("Server address") { @@ -493,9 +516,13 @@ struct SMPServerSummaryView: View { .textSelection(.enabled) if summary.known == true { NavigationLink { - ProtocolServersView(serverProtocol: .smp) - .navigationTitle("Your SMP servers") - .modifier(ThemedBackground(grouped: true)) + NetworkAndServers( + currUserServers: $currUserServers, + userServers: $userServers, + serverErrors: $serverErrors + ) + .navigationTitle("Network & servers") + .modifier(ThemedBackground(grouped: true)) } label: { Text("Open server settings") } @@ -674,6 +701,10 @@ struct XFTPServerSummaryView: View { var summary: XFTPServerSummary var statsStartedAt: Date + @Binding var currUserServers: [UserOperatorServers] + @Binding var userServers: [UserOperatorServers] + @Binding var serverErrors: [UserServersError] + var body: some View { List { Section("Server address") { @@ -681,9 +712,13 @@ struct XFTPServerSummaryView: View { .textSelection(.enabled) if summary.known == true { NavigationLink { - ProtocolServersView(serverProtocol: .xftp) - .navigationTitle("Your XFTP servers") - .modifier(ThemedBackground(grouped: true)) + NetworkAndServers( + currUserServers: $currUserServers, + userServers: $userServers, + serverErrors: $serverErrors + ) + .navigationTitle("Network & servers") + .modifier(ThemedBackground(grouped: true)) } label: { Text("Open server settings") } diff --git a/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift b/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift new file mode 100644 index 0000000000..e9a8fedaf9 --- /dev/null +++ b/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift @@ -0,0 +1,116 @@ +// +// AddressCreationCard.swift +// SimpleX (iOS) +// +// Created by Diogo Cunha on 13/11/2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +struct AddressCreationCard: View { + @EnvironmentObject var theme: AppTheme + @EnvironmentObject private var chatModel: ChatModel + @Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize + @AppStorage(DEFAULT_ADDRESS_CREATION_CARD_SHOWN) private var addressCreationCardShown = false + @State private var showAddressCreationAlert = false + @State private var showAddressSheet = false + @State private var showAddressInfoSheet = false + + var body: some View { + let addressExists = chatModel.userAddress != nil + let chats = chatModel.chats.filter { chat in + !chat.chatInfo.chatDeleted && chatContactType(chat: chat) != ContactType.card + } + ZStack(alignment: .topTrailing) { + HStack(alignment: .top, spacing: 16) { + let envelopeSize = dynamicSize(userFont).profileImageSize + Image(systemName: "envelope.circle.fill") + .resizable() + .frame(width: envelopeSize, height: envelopeSize) + .foregroundColor(.accentColor) + VStack(alignment: .leading) { + Text("Your SimpleX address") + .font(.title3) + Spacer() + HStack(alignment: .center) { + Text("How to use it") + VStack { + Image(systemName: "info.circle") + .foregroundColor(theme.colors.secondary) + } + } + } + } + .frame(maxWidth: .infinity, alignment: .leading) + VStack(alignment: .trailing) { + Image(systemName: "multiply") + .foregroundColor(theme.colors.secondary) + .onTapGesture { + showAddressCreationAlert = true + } + Spacer() + Text("Create") + .foregroundColor(.accentColor) + .onTapGesture { + showAddressSheet = true + } + } + } + .onTapGesture { + showAddressInfoSheet = true + } + .padding() + .background(theme.appColors.sentMessage) + .cornerRadius(12) + .frame(height: dynamicSize(userFont).rowHeight) + .padding(.vertical, 12) + .alert(isPresented: $showAddressCreationAlert) { + Alert( + title: Text("SimpleX address"), + message: Text("You can create it in user picker."), + dismissButton: .default(Text("Ok")) { + withAnimation { + addressCreationCardShown = true + } + } + ) + } + .sheet(isPresented: $showAddressSheet) { + NavigationView { + UserAddressView(autoCreate: true) + .navigationTitle("SimpleX address") + .navigationBarTitleDisplayMode(.large) + .modifier(ThemedBackground(grouped: true)) + } + } + .sheet(isPresented: $showAddressInfoSheet) { + NavigationView { + UserAddressLearnMore(showCreateAddressButton: true) + .navigationTitle("SimpleX address") + .navigationBarTitleDisplayMode(.large) + .modifier(ThemedBackground(grouped: true)) + } + } + .onChange(of: addressExists) { exists in + if exists, !addressCreationCardShown { + addressCreationCardShown = true + } + } + .onChange(of: chats.count) { size in + if size >= 3, !addressCreationCardShown { + addressCreationCardShown = true + } + } + .onAppear { + if addressExists, !addressCreationCardShown { + addressCreationCardShown = true + } + } + } +} + +#Preview { + AddressCreationCard() +} diff --git a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift new file mode 100644 index 0000000000..248c1b34c4 --- /dev/null +++ b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift @@ -0,0 +1,344 @@ +// +// ChooseServerOperators.swift +// SimpleX (iOS) +// +// Created by spaced4ndy on 31.10.2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +struct OnboardingButtonStyle: ButtonStyle { + @EnvironmentObject var theme: AppTheme + var isDisabled: Bool = false + + func makeBody(configuration: Configuration) -> some View { + configuration.label + .font(.system(size: 17, weight: .semibold)) + .padding() + .frame(maxWidth: .infinity) + .background( + isDisabled + ? ( + theme.colors.isLight + ? .gray.opacity(0.17) + : .gray.opacity(0.27) + ) + : theme.colors.primary + ) + .foregroundColor( + isDisabled + ? ( + theme.colors.isLight + ? .gray.opacity(0.4) + : .white.opacity(0.2) + ) + : .white + ) + .cornerRadius(16) + .scaleEffect(configuration.isPressed ? 0.95 : 1.0) + } +} + +struct ChooseServerOperators: View { + @Environment(\.dismiss) var dismiss: DismissAction + @Environment(\.colorScheme) var colorScheme: ColorScheme + @EnvironmentObject var theme: AppTheme + var onboarding: Bool + @State private var showInfoSheet = false + @State private var serverOperators: [ServerOperator] = [] + @State private var selectedOperatorIds = Set() + @State private var reviewConditionsNavLinkActive = false + @State private var justOpened = true + + var selectedOperators: [ServerOperator] { serverOperators.filter { selectedOperatorIds.contains($0.operatorId) } } + + var body: some View { + NavigationView { + GeometryReader { g in + ScrollView { + VStack(alignment: .leading, spacing: 20) { + Text("Choose operators") + .font(.largeTitle) + .bold() + + infoText() + + Spacer() + + ForEach(serverOperators) { srvOperator in + operatorCheckView(srvOperator) + } + + Spacer() + + let reviewForOperators = selectedOperators.filter { !$0.conditionsAcceptance.conditionsAccepted } + let canReviewLater = reviewForOperators.allSatisfy { $0.conditionsAcceptance.usageAllowed } + + VStack(spacing: 8) { + if !reviewForOperators.isEmpty { + reviewConditionsButton() + } else { + continueButton() + } + if onboarding { + Text("You can disable operators and configure your servers in Network & servers settings.") + .multilineTextAlignment(.center) + .font(.footnote) + .padding(.horizontal, 32) + } + } + .padding(.bottom) + + if !onboarding && !reviewForOperators.isEmpty { + VStack(spacing: 8) { + reviewLaterButton() + ( + Text("Conditions will be accepted for enabled operators after 30 days.") + + Text(" ") + + Text("You can configure operators in Network & servers settings.") + ) + .multilineTextAlignment(.center) + .font(.footnote) + .padding(.horizontal, 32) + } + .disabled(!canReviewLater) + .padding(.bottom) + } + } + .frame(minHeight: g.size.height) + } + .onAppear { + if justOpened { + serverOperators = ChatModel.shared.conditions.serverOperators + selectedOperatorIds = Set(serverOperators.filter { $0.enabled }.map { $0.operatorId }) + justOpened = false + } + } + .sheet(isPresented: $showInfoSheet) { + ChooseServerOperatorsInfoView() + } + } + .frame(maxHeight: .infinity) + .padding() + } + } + + private func infoText() -> some View { + HStack(spacing: 12) { + Image(systemName: "info.circle") + .resizable() + .scaledToFit() + .frame(width: 20, height: 20) + .foregroundColor(theme.colors.primary) + .onTapGesture { + showInfoSheet = true + } + + Text("Select operators, whose servers you will be using.") + } + } + + @ViewBuilder private func operatorCheckView(_ serverOperator: ServerOperator) -> some View { + let checked = selectedOperatorIds.contains(serverOperator.operatorId) + let icon = checked ? "checkmark.circle.fill" : "circle" + let iconColor = checked ? theme.colors.primary : Color(uiColor: .tertiaryLabel).asAnotherColorFromSecondary(theme) + HStack(spacing: 10) { + Image(serverOperator.largeLogo(colorScheme)) + .resizable() + .scaledToFit() + .frame(height: 48) + Spacer() + Image(systemName: icon) + .resizable() + .scaledToFit() + .frame(width: 26, height: 26) + .foregroundColor(iconColor) + } + .background(Color(.systemBackground)) + .padding() + .clipShape(RoundedRectangle(cornerRadius: 18)) + .overlay( + RoundedRectangle(cornerRadius: 18) + .stroke(Color(uiColor: .secondarySystemFill), lineWidth: 2) + ) + .padding(.horizontal, 2) + .onTapGesture { + if checked { + selectedOperatorIds.remove(serverOperator.operatorId) + } else { + selectedOperatorIds.insert(serverOperator.operatorId) + } + } + } + + private func reviewConditionsButton() -> some View { + ZStack { + Button { + reviewConditionsNavLinkActive = true + } label: { + Text("Review conditions") + } + .buttonStyle(OnboardingButtonStyle(isDisabled: selectedOperatorIds.isEmpty)) + .disabled(selectedOperatorIds.isEmpty) + + NavigationLink(isActive: $reviewConditionsNavLinkActive) { + reviewConditionsDestinationView() + } label: { + EmptyView() + } + .frame(width: 1, height: 1) + .hidden() + } + } + + private func continueButton() -> some View { + Button { + continueToNextStep() + } label: { + Text("Continue") + } + .buttonStyle(OnboardingButtonStyle(isDisabled: selectedOperatorIds.isEmpty)) + .disabled(selectedOperatorIds.isEmpty) + } + + private func reviewLaterButton() -> some View { + Button { + continueToNextStep() + } label: { + Text("Review later") + } + .buttonStyle(.borderless) + } + + private func continueToNextStep() { + if onboarding { + withAnimation { + onboardingStageDefault.set(.step4_SetNotificationsMode) + ChatModel.shared.onboardingStage = .step4_SetNotificationsMode + } + } else { + dismiss() + } + } + + private func reviewConditionsDestinationView() -> some View { + reviewConditionsView() + .navigationTitle("Conditions of use") + .navigationBarTitleDisplayMode(.large) + .modifier(ThemedBackground(grouped: true)) + } + + @ViewBuilder private func reviewConditionsView() -> some View { + let operatorsWithConditionsAccepted = ChatModel.shared.conditions.serverOperators.filter { $0.conditionsAcceptance.conditionsAccepted } + let acceptForOperators = selectedOperators.filter { !$0.conditionsAcceptance.conditionsAccepted } + VStack(alignment: .leading, spacing: 20) { + if !operatorsWithConditionsAccepted.isEmpty { + Text("Conditions are already accepted for following operator(s): **\(operatorsWithConditionsAccepted.map { $0.legalName_ }.joined(separator: ", "))**.") + Text("Same conditions will apply to operator(s): **\(acceptForOperators.map { $0.legalName_ }.joined(separator: ", "))**.") + } else { + Text("Conditions will be accepted for operator(s): **\(acceptForOperators.map { $0.legalName_ }.joined(separator: ", "))**.") + } + ConditionsTextView() + acceptConditionsButton() + .padding(.bottom) + .padding(.bottom) + } + .padding(.horizontal) + .frame(maxHeight: .infinity) + } + + private func acceptConditionsButton() -> some View { + Button { + Task { + do { + let conditionsId = ChatModel.shared.conditions.currentConditions.conditionsId + let acceptForOperators = selectedOperators.filter { !$0.conditionsAcceptance.conditionsAccepted } + let operatorIds = acceptForOperators.map { $0.operatorId } + let r = try await acceptConditions(conditionsId: conditionsId, operatorIds: operatorIds) + await MainActor.run { + ChatModel.shared.conditions = r + } + if let enabledOperators = enabledOperators(r.serverOperators) { + let r2 = try await setServerOperators(operators: enabledOperators) + await MainActor.run { + ChatModel.shared.conditions = r2 + continueToNextStep() + } + } else { + await MainActor.run { + continueToNextStep() + } + } + } catch let error { + await MainActor.run { + showAlert( + NSLocalizedString("Error accepting conditions", comment: "alert title"), + message: responseError(error) + ) + } + } + } + } label: { + Text("Accept conditions") + } + .buttonStyle(OnboardingButtonStyle()) + } + + private func enabledOperators(_ operators: [ServerOperator]) -> [ServerOperator]? { + var ops = operators + if !ops.isEmpty { + for i in 0.. some View { - HStack { - Button { - hideKeyboard() - withAnimation { - m.onboardingStage = .step1_SimpleXInfo - } - } label: { - HStack { - Image(systemName: "lessthan") - Text("About SimpleX") - } - } - - Spacer() - - Button { - createProfile(displayName, showAlert: showAlert, dismiss: dismiss) - } label: { - HStack { - Text("Create") - Image(systemName: "greaterthan") - } - } - .disabled(!canCreateProfile(displayName)) + func createProfileButton() -> some View { + Button { + createProfile(displayName, showAlert: showAlert, dismiss: dismiss) + } label: { + Text("Create profile") } + .buttonStyle(OnboardingButtonStyle(isDisabled: !canCreateProfile(displayName))) + .disabled(!canCreateProfile(displayName)) } private func showAlert(_ alert: UserProfileAlert) { @@ -176,8 +162,8 @@ private func createProfile(_ displayName: String, showAlert: (UserProfileAlert) if m.users.isEmpty || m.users.allSatisfy({ $0.user.hidden }) { try startChat() withAnimation { - onboardingStageDefault.set(.step3_CreateSimpleXAddress) - m.onboardingStage = .step3_CreateSimpleXAddress + onboardingStageDefault.set(.step3_ChooseServerOperators) + m.onboardingStage = .step3_ChooseServerOperators } } else { onboardingStageDefault.set(.onboardingComplete) diff --git a/apps/ios/Shared/Views/Onboarding/HowItWorks.swift b/apps/ios/Shared/Views/Onboarding/HowItWorks.swift index c1975765d2..f11dbbe7a8 100644 --- a/apps/ios/Shared/Views/Onboarding/HowItWorks.swift +++ b/apps/ios/Shared/Views/Onboarding/HowItWorks.swift @@ -9,8 +9,10 @@ import SwiftUI struct HowItWorks: View { + @Environment(\.dismiss) var dismiss: DismissAction @EnvironmentObject var m: ChatModel var onboarding: Bool + @Binding var createProfileNavLinkActive: Bool var body: some View { VStack(alignment: .leading) { @@ -37,8 +39,8 @@ struct HowItWorks: View { Spacer() if onboarding { - OnboardingActionButton() - .padding(.bottom, 8) + createFirstProfileButton() + .padding(.bottom) } } .lineLimit(10) @@ -46,10 +48,23 @@ struct HowItWorks: View { .frame(maxHeight: .infinity, alignment: .top) .modifier(ThemedBackground()) } + + private func createFirstProfileButton() -> some View { + Button { + dismiss() + createProfileNavLinkActive = true + } label: { + Text("Create your profile") + } + .buttonStyle(OnboardingButtonStyle(isDisabled: false)) + } } struct HowItWorks_Previews: PreviewProvider { static var previews: some View { - HowItWorks(onboarding: true) + HowItWorks( + onboarding: true, + createProfileNavLinkActive: Binding.constant(false) + ) } } diff --git a/apps/ios/Shared/Views/Onboarding/OnboardingView.swift b/apps/ios/Shared/Views/Onboarding/OnboardingView.swift index 438491b5f1..de3dce21bb 100644 --- a/apps/ios/Shared/Views/Onboarding/OnboardingView.swift +++ b/apps/ios/Shared/Views/Onboarding/OnboardingView.swift @@ -16,6 +16,7 @@ struct OnboardingView: View { case .step1_SimpleXInfo: SimpleXInfo(onboarding: true) case .step2_CreateProfile: CreateFirstProfile() case .step3_CreateSimpleXAddress: CreateSimpleXAddress() + case .step3_ChooseServerOperators: ChooseServerOperators(onboarding: true) case .step4_SetNotificationsMode: SetNotificationsMode() case .onboardingComplete: EmptyView() } @@ -24,8 +25,9 @@ struct OnboardingView: View { enum OnboardingStage: String, Identifiable { case step1_SimpleXInfo - case step2_CreateProfile - case step3_CreateSimpleXAddress + case step2_CreateProfile // deprecated + case step3_CreateSimpleXAddress // deprecated + case step3_ChooseServerOperators case step4_SetNotificationsMode case onboardingComplete diff --git a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift index 7681a42a77..03ee9c67e0 100644 --- a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift +++ b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift @@ -15,41 +15,44 @@ struct SetNotificationsMode: View { @State private var showAlert: NotificationAlert? var body: some View { - ScrollView { - VStack(alignment: .leading, spacing: 16) { - Text("Push notifications") - .font(.largeTitle) - .bold() - .frame(maxWidth: .infinity) - - Text("Send notifications:") - ForEach(NotificationsMode.values) { mode in - NtfModeSelector(mode: mode, selection: $notificationMode) - } - - Spacer() - - Button { - if let token = m.deviceToken { - setNotificationsMode(token, notificationMode) - } else { - AlertManager.shared.showAlertMsg(title: "No device token!") + GeometryReader { g in + ScrollView { + VStack(alignment: .leading, spacing: 16) { + Text("Push notifications") + .font(.largeTitle) + .bold() + .frame(maxWidth: .infinity) + + Text("Send notifications:") + ForEach(NotificationsMode.values) { mode in + NtfModeSelector(mode: mode, selection: $notificationMode) } - onboardingStageDefault.set(.onboardingComplete) - m.onboardingStage = .onboardingComplete - } label: { - if case .off = notificationMode { - Text("Use chat") - } else { - Text("Enable notifications") + + Spacer() + + Button { + if let token = m.deviceToken { + setNotificationsMode(token, notificationMode) + } else { + AlertManager.shared.showAlertMsg(title: "No device token!") + } + onboardingStageDefault.set(.onboardingComplete) + m.onboardingStage = .onboardingComplete + } label: { + if case .off = notificationMode { + Text("Use chat") + } else { + Text("Enable notifications") + } } + .buttonStyle(OnboardingButtonStyle()) + .padding(.bottom) } - .font(.title) - .frame(maxWidth: .infinity) + .padding() + .frame(minHeight: g.size.height) } - .padding() - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom) } + .frame(maxHeight: .infinity) } private func setNotificationsMode(_ token: DeviceToken, _ mode: NotificationsMode) { diff --git a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift index ee5a618e68..2e077e9d95 100644 --- a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift +++ b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift @@ -13,81 +13,85 @@ struct SimpleXInfo: View { @EnvironmentObject var m: ChatModel @Environment(\.colorScheme) var colorScheme: ColorScheme @State private var showHowItWorks = false + @State private var createProfileNavLinkActive = false var onboarding: Bool var body: some View { - GeometryReader { g in - ScrollView { - VStack(alignment: .leading) { - Image(colorScheme == .light ? "logo" : "logo-light") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: g.size.width * 0.67) - .padding(.bottom, 8) - .frame(maxWidth: .infinity, minHeight: 48, alignment: .top) + NavigationView { + GeometryReader { g in + ScrollView { + VStack(alignment: .leading, spacing: 20) { + Image(colorScheme == .light ? "logo" : "logo-light") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: g.size.width * 0.67) + .padding(.bottom, 8) + .frame(maxWidth: .infinity, minHeight: 48, alignment: .top) - VStack(alignment: .leading) { - Text("The next generation of private messaging") - .font(.title2) - .padding(.bottom, 30) - .padding(.horizontal, 40) - .frame(maxWidth: .infinity) - .multilineTextAlignment(.center) - infoRow("privacy", "Privacy redefined", - "The 1st platform without any user identifiers – private by design.", width: 48) - infoRow("shield", "Immune to spam and abuse", - "People can connect to you only via the links you share.", width: 46) - infoRow(colorScheme == .light ? "decentralized" : "decentralized-light", "Decentralized", - "Open-source protocol and code – anybody can run the servers.", width: 44) - } + VStack(alignment: .leading) { + Text("The next generation of private messaging") + .font(.title2) + .padding(.bottom, 30) + .padding(.horizontal, 40) + .frame(maxWidth: .infinity) + .multilineTextAlignment(.center) + infoRow("privacy", "Privacy redefined", + "The 1st platform without any user identifiers – private by design.", width: 48) + infoRow("shield", "Immune to spam and abuse", + "People can connect to you only via the links you share.", width: 46) + infoRow(colorScheme == .light ? "decentralized" : "decentralized-light", "Decentralized", + "Open-source protocol and code – anybody can run the servers.", width: 44) + } - Spacer() - if onboarding { - OnboardingActionButton() Spacer() + if onboarding { + onboardingActionButton() + + Button { + m.migrationState = .pasteOrScanLink + } label: { + Label("Migrate from another device", systemImage: "tray.and.arrow.down") + .font(.subheadline) + } + .frame(maxWidth: .infinity) + } + Button { - m.migrationState = .pasteOrScanLink + showHowItWorks = true } label: { - Label("Migrate from another device", systemImage: "tray.and.arrow.down") + Label("How it works", systemImage: "info.circle") .font(.subheadline) } - .padding(.bottom, 8) .frame(maxWidth: .infinity) + .padding(.bottom) } - - Button { - showHowItWorks = true - } label: { - Label("How it works", systemImage: "info.circle") - .font(.subheadline) - } - .padding(.bottom, 8) - .frame(maxWidth: .infinity) - + .frame(minHeight: g.size.height) } - .frame(minHeight: g.size.height) - } - .sheet(isPresented: Binding( - get: { m.migrationState != nil }, - set: { _ in - m.migrationState = nil - MigrationToDeviceState.save(nil) } - )) { - NavigationView { - VStack(alignment: .leading) { - MigrateToDevice(migrationState: $m.migrationState) + .sheet(isPresented: Binding( + get: { m.migrationState != nil }, + set: { _ in + m.migrationState = nil + MigrationToDeviceState.save(nil) } + )) { + NavigationView { + VStack(alignment: .leading) { + MigrateToDevice(migrationState: $m.migrationState) + } + .navigationTitle("Migrate here") + .modifier(ThemedBackground(grouped: true)) } - .navigationTitle("Migrate here") - .modifier(ThemedBackground(grouped: true)) + } + .sheet(isPresented: $showHowItWorks) { + HowItWorks( + onboarding: onboarding, + createProfileNavLinkActive: $createProfileNavLinkActive + ) } } - .sheet(isPresented: $showHowItWorks) { - HowItWorks(onboarding: onboarding) - } + .frame(maxHeight: .infinity) + .padding() } - .frame(maxHeight: .infinity) - .padding() } private func infoRow(_ image: String, _ title: LocalizedStringKey, _ text: LocalizedStringKey, width: CGFloat) -> some View { @@ -108,49 +112,51 @@ struct SimpleXInfo: View { .padding(.bottom, 20) .padding(.trailing, 6) } -} -struct OnboardingActionButton: View { - @EnvironmentObject var m: ChatModel - @Environment(\.colorScheme) var colorScheme - - var body: some View { + @ViewBuilder private func onboardingActionButton() -> some View { if m.currentUser == nil { - actionButton("Create your profile", onboarding: .step2_CreateProfile) + createFirstProfileButton() } else { - actionButton("Make a private connection", onboarding: .onboardingComplete) + userExistsFallbackButton() } } - private func actionButton(_ label: LocalizedStringKey, onboarding: OnboardingStage) -> some View { - Button { - withAnimation { - onboardingStageDefault.set(onboarding) - m.onboardingStage = onboarding + private func createFirstProfileButton() -> some View { + ZStack { + Button { + createProfileNavLinkActive = true + } label: { + Text("Create your profile") } - } label: { - HStack { - Text(label).font(.title2) - Image(systemName: "greaterthan") + .buttonStyle(OnboardingButtonStyle(isDisabled: false)) + + NavigationLink(isActive: $createProfileNavLinkActive) { + createProfileDestinationView() + } label: { + EmptyView() } + .frame(width: 1, height: 1) + .hidden() } - .frame(maxWidth: .infinity) - .padding(.bottom) } - private func actionButton(_ label: LocalizedStringKey, action: @escaping () -> Void) -> some View { + private func createProfileDestinationView() -> some View { + CreateFirstProfile() + .navigationTitle("Create your profile") + .navigationBarTitleDisplayMode(.large) + .modifier(ThemedBackground(grouped: true)) + } + + private func userExistsFallbackButton() -> some View { Button { withAnimation { - action() + onboardingStageDefault.set(.onboardingComplete) + m.onboardingStage = .onboardingComplete } } label: { - HStack { - Text(label).font(.title2) - Image(systemName: "greaterthan") - } + Text("Make a private connection") } - .frame(maxWidth: .infinity) - .padding(.bottom) + .buttonStyle(OnboardingButtonStyle(isDisabled: false)) } } diff --git a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift index 2ae4aa8c2b..1d1ec5b64c 100644 --- a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift +++ b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift @@ -7,190 +7,209 @@ // import SwiftUI +import SimpleXChat private struct VersionDescription { var version: String var post: URL? - var features: [FeatureDescription] + var features: [Feature] } -private struct FeatureDescription { - var icon: String? - var title: LocalizedStringKey - var description: LocalizedStringKey? +private enum Feature: Identifiable { + case feature(Description) + case view(FeatureView) + + var id: LocalizedStringKey { + switch self { + case let .feature(d): d.title + case let .view(v): v.title + } + } +} + +private struct Description { + let icon: String? + let title: LocalizedStringKey + let description: LocalizedStringKey? var subfeatures: [(icon: String, description: LocalizedStringKey)] = [] } +private struct FeatureView { + let icon: String? + let title: LocalizedStringKey + let view: () -> any View +} + private let versionDescriptions: [VersionDescription] = [ VersionDescription( version: "v4.2", post: URL(string: "https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html"), features: [ - FeatureDescription( + .feature(Description( icon: "checkmark.shield", title: "Security assessment", description: "SimpleX Chat security was audited by Trail of Bits." - ), - FeatureDescription( + )), + .feature(Description( icon: "person.2", title: "Group links", description: "Admins can create the links to join groups." - ), - FeatureDescription( + )), + .feature(Description( icon: "checkmark", title: "Auto-accept contact requests", description: "With optional welcome message." - ), + )), ] ), VersionDescription( version: "v4.3", post: URL(string: "https://simplex.chat/blog/20221206-simplex-chat-v4.3-voice-messages.html"), features: [ - FeatureDescription( + .feature(Description( icon: "mic", title: "Voice messages", description: "Max 30 seconds, received instantly." - ), - FeatureDescription( + )), + .feature(Description( icon: "trash.slash", title: "Irreversible message deletion", description: "Your contacts can allow full message deletion." - ), - FeatureDescription( + )), + .feature(Description( icon: "externaldrive.connected.to.line.below", title: "Improved server configuration", description: "Add servers by scanning QR codes." - ), - FeatureDescription( + )), + .feature(Description( icon: "eye.slash", title: "Improved privacy and security", description: "Hide app screen in the recent apps." - ), + )), ] ), VersionDescription( version: "v4.4", post: URL(string: "https://simplex.chat/blog/20230103-simplex-chat-v4.4-disappearing-messages.html"), features: [ - FeatureDescription( + .feature(Description( icon: "stopwatch", title: "Disappearing messages", description: "Sent messages will be deleted after set time." - ), - FeatureDescription( + )), + .feature(Description( icon: "ellipsis.circle", title: "Live messages", description: "Recipients see updates as you type them." - ), - FeatureDescription( + )), + .feature(Description( icon: "checkmark.shield", title: "Verify connection security", description: "Compare security codes with your contacts." - ), - FeatureDescription( + )), + .feature(Description( icon: "camera", title: "GIFs and stickers", description: "Send them from gallery or custom keyboards." - ), - FeatureDescription( + )), + .feature(Description( icon: "character", title: "French interface", description: "Thanks to the users – contribute via Weblate!" - ) + )), ] ), VersionDescription( version: "v4.5", post: URL(string: "https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html"), features: [ - FeatureDescription( + .feature(Description( icon: "person.crop.rectangle.stack", title: "Multiple chat profiles", description: "Different names, avatars and transport isolation." - ), - FeatureDescription( + )), + .feature(Description( icon: "rectangle.and.pencil.and.ellipsis", title: "Message draft", description: "Preserve the last message draft, with attachments." - ), - FeatureDescription( + )), + .feature(Description( icon: "network.badge.shield.half.filled", title: "Transport isolation", description: "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." - ), - FeatureDescription( + )), + .feature(Description( icon: "lock.doc", title: "Private filenames", description: "To protect timezone, image/voice files use UTC." - ), - FeatureDescription( + )), + .feature(Description( icon: "battery.25", title: "Reduced battery usage", description: "More improvements are coming soon!" - ), - FeatureDescription( + )), + .feature(Description( icon: "character", title: "Italian interface", description: "Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" - ) + )), ] ), VersionDescription( version: "v4.6", post: URL(string: "https://simplex.chat/blog/20230328-simplex-chat-v4-6-hidden-profiles.html"), features: [ - FeatureDescription( + .feature(Description( icon: "lock", title: "Hidden chat profiles", description: "Protect your chat profiles with a password!" - ), - FeatureDescription( + )), + .feature(Description( icon: "phone.arrow.up.right", title: "Audio and video calls", description: "Fully re-implemented - work in background!" - ), - FeatureDescription( + )), + .feature(Description( icon: "flag", title: "Group moderation", description: "Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" - ), - FeatureDescription( + )), + .feature(Description( icon: "plus.message", title: "Group welcome message", description: "Set the message shown to new members!" - ), - FeatureDescription( + )), + .feature(Description( icon: "battery.50", title: "Further reduced battery usage", description: "More improvements are coming soon!" - ), - FeatureDescription( + )), + .feature(Description( icon: "character", title: "Chinese and Spanish interface", description: "Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" - ), + )), ] ), VersionDescription( version: "v5.0", post: URL(string: "https://simplex.chat/blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.html"), features: [ - FeatureDescription( + .feature(Description( icon: "arrow.up.doc", title: "Videos and files up to 1gb", description: "Fast and no wait until the sender is online!" - ), - FeatureDescription( + )), + .feature(Description( icon: "lock", title: "App passcode", description: "Set it instead of system authentication." - ), - FeatureDescription( + )), + .feature(Description( icon: "character", title: "Polish interface", description: "Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" - ), + )), ] ), // Also @@ -200,240 +219,240 @@ private let versionDescriptions: [VersionDescription] = [ version: "v5.1", post: URL(string: "https://simplex.chat/blog/20230523-simplex-chat-v5-1-message-reactions-self-destruct-passcode.html"), features: [ - FeatureDescription( + .feature(Description( icon: "face.smiling", title: "Message reactions", description: "Finally, we have them! 🚀" - ), - FeatureDescription( + )), + .feature(Description( icon: "arrow.up.message", title: "Better messages", description: "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." - ), - FeatureDescription( + )), + .feature(Description( icon: "lock", title: "Self-destruct passcode", description: "All data is erased when it is entered." - ), - FeatureDescription( + )), + .feature(Description( icon: "character", title: "Japanese interface", description: "Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" - ), + )), ] ), VersionDescription( version: "v5.2", post: URL(string: "https://simplex.chat/blog/20230722-simplex-chat-v5-2-message-delivery-receipts.html"), features: [ - FeatureDescription( + .feature(Description( icon: "checkmark", title: "Message delivery receipts!", description: "The second tick we missed! ✅" - ), - FeatureDescription( + )), + .feature(Description( icon: "star", title: "Find chats faster", description: "Filter unread and favorite chats." - ), - FeatureDescription( + )), + .feature(Description( icon: "exclamationmark.arrow.triangle.2.circlepath", title: "Keep your connections", description: "Fix encryption after restoring backups." - ), - FeatureDescription( + )), + .feature(Description( icon: "stopwatch", title: "Make one message disappear", description: "Even when disabled in the conversation." - ), - FeatureDescription( + )), + .feature(Description( icon: "gift", title: "A few more things", description: "- more stable message delivery.\n- a bit better groups.\n- and more!" - ), + )), ] ), VersionDescription( version: "v5.3", post: URL(string: "https://simplex.chat/blog/20230925-simplex-chat-v5-3-desktop-app-local-file-encryption-directory-service.html"), features: [ - FeatureDescription( + .feature(Description( icon: "desktopcomputer", title: "New desktop app!", description: "Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" - ), - FeatureDescription( + )), + .feature(Description( icon: "lock", title: "Encrypt stored files & media", description: "App encrypts new local files (except videos)." - ), - FeatureDescription( + )), + .feature(Description( icon: "magnifyingglass", title: "Discover and join groups", description: "- 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." - ), - FeatureDescription( + )), + .feature(Description( icon: "theatermasks", title: "Simplified incognito mode", description: "Toggle incognito when connecting." - ), - FeatureDescription( + )), + .feature(Description( icon: "character", title: "\(4) new interface languages", description: "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" - ), + )), ] ), 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( + .feature(Description( icon: "desktopcomputer", title: "Link mobile and desktop apps! 🔗", description: "Via secure quantum resistant protocol." - ), - FeatureDescription( + )), + .feature(Description( icon: "person.2", title: "Better groups", description: "Faster joining and more reliable messages." - ), - FeatureDescription( + )), + .feature(Description( icon: "theatermasks", title: "Incognito groups", description: "Create a group using a random profile." - ), - FeatureDescription( + )), + .feature(Description( icon: "hand.raised", title: "Block group members", description: "To hide unwanted messages." - ), - FeatureDescription( + )), + .feature(Description( icon: "gift", title: "A few more things", description: "- optionally notify deleted contacts.\n- profile names with spaces.\n- and more!" - ), + )), ] ), VersionDescription( version: "v5.5", post: URL(string: "https://simplex.chat/blog/20240124-simplex-chat-infrastructure-costs-v5-5-simplex-ux-private-notes-group-history.html"), features: [ - FeatureDescription( + .feature(Description( icon: "folder", title: "Private notes", description: "With encrypted files and media." - ), - FeatureDescription( + )), + .feature(Description( icon: "link", title: "Paste link to connect!", description: "Search bar accepts invitation links." - ), - FeatureDescription( + )), + .feature(Description( icon: "bubble.left.and.bubble.right", title: "Join group conversations", description: "Recent history and improved [directory bot](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion)." - ), - FeatureDescription( + )), + .feature(Description( icon: "battery.50", title: "Improved message delivery", description: "With reduced battery usage." - ), - FeatureDescription( + )), + .feature(Description( icon: "character", title: "Turkish interface", description: "Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" - ), + )), ] ), VersionDescription( version: "v5.6", post: URL(string: "https://simplex.chat/blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.html"), features: [ - FeatureDescription( + .feature(Description( icon: "key", title: "Quantum resistant encryption", description: "Enable in direct chats (BETA)!" - ), - FeatureDescription( + )), + .feature(Description( icon: "tray.and.arrow.up", title: "App data migration", description: "Migrate to another device via QR code." - ), - FeatureDescription( + )), + .feature(Description( icon: "phone", title: "Picture-in-picture calls", description: "Use the app while in the call." - ), - FeatureDescription( + )), + .feature(Description( icon: "hand.raised", title: "Safer groups", description: "Admins can block a member for all." - ), - FeatureDescription( + )), + .feature(Description( icon: "character", title: "Hungarian interface", description: "Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" - ), + )), ] ), VersionDescription( version: "v5.7", post: URL(string: "https://simplex.chat/blog/20240426-simplex-legally-binding-transparency-v5-7-better-user-experience.html"), features: [ - FeatureDescription( + .feature(Description( icon: "key", title: "Quantum resistant encryption", description: "Will be enabled in direct chats!" - ), - FeatureDescription( + )), + .feature(Description( icon: "arrowshape.turn.up.forward", title: "Forward and save messages", description: "Message source remains private." - ), - FeatureDescription( + )), + .feature(Description( icon: "music.note", title: "In-call sounds", description: "When connecting audio and video calls." - ), - FeatureDescription( + )), + .feature(Description( icon: "person.crop.square", title: "Shape profile images", description: "Square, circle, or anything in between." - ), - FeatureDescription( + )), + .feature(Description( icon: "antenna.radiowaves.left.and.right", title: "Network management", description: "More reliable network connection." - ) + )), ] ), VersionDescription( version: "v5.8", post: URL(string: "https://simplex.chat/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.html"), features: [ - FeatureDescription( + .feature(Description( icon: "arrow.forward", title: "Private message routing 🚀", description: "Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." - ), - FeatureDescription( + )), + .feature(Description( icon: "network.badge.shield.half.filled", title: "Safely receive files", description: "Confirm files from unknown servers." - ), - FeatureDescription( + )), + .feature(Description( icon: "battery.50", title: "Improved message delivery", description: "With reduced battery usage." - ) + )), ] ), VersionDescription( version: "v6.0", post: URL(string: "https://simplex.chat/blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.html"), features: [ - FeatureDescription( + .feature(Description( icon: nil, title: "New chat experience 🎉", description: nil, @@ -444,8 +463,8 @@ private let versionDescriptions: [VersionDescription] = [ ("platter.filled.bottom.and.arrow.down.iphone", "Use the app with one hand."), ("paintpalette", "Color chats with the new themes."), ] - ), - FeatureDescription( + )), + .feature(Description( icon: nil, title: "New media options", description: nil, @@ -454,39 +473,39 @@ private let versionDescriptions: [VersionDescription] = [ ("play.circle", "Play from the chat list."), ("circle.filled.pattern.diagonalline.rectangle", "Blur for better privacy.") ] - ), - FeatureDescription( + )), + .feature(Description( icon: "arrow.forward", title: "Private message routing 🚀", description: "It protects your IP address and connections." - ), - FeatureDescription( + )), + .feature(Description( icon: "network", title: "Better networking", description: "Connection and servers status." - ) + )), ] ), VersionDescription( version: "v6.1", post: URL(string: "https://simplex.chat/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html"), features: [ - FeatureDescription( + .feature(Description( icon: "checkmark.shield", title: "Better security ✅", description: "SimpleX protocols reviewed by Trail of Bits." - ), - FeatureDescription( + )), + .feature(Description( icon: "video", title: "Better calls", description: "Switch audio and video during the call." - ), - FeatureDescription( + )), + .feature(Description( icon: "bolt", title: "Better notifications", description: "Improved delivery, reduced traffic usage.\nMore improvements are coming soon!" - ), - FeatureDescription( + )), + .feature(Description( icon: nil, title: "Better user experience", description: nil, @@ -497,9 +516,25 @@ private let versionDescriptions: [VersionDescription] = [ ("arrowshape.turn.up.right", "Forward up to 20 messages at once."), ("flag", "Delete or moderate up to 200 messages.") ] - ), + )), ] ), + VersionDescription( + version: "v6.2 (beta.1)", + post: URL(string: "https://simplex.chat/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html"), + features: [ + .view(FeatureView( + icon: nil, + title: "Network decentralization", + view: newOperatorsView + )), + .feature(Description( + icon: "text.quote", + title: "Improved chat navigation", + description: "- Open chat on the first unread message.\n- Jump to quoted messages." + )), + ] + ) ] private let lastVersion = versionDescriptions.last!.version @@ -514,14 +549,56 @@ func shouldShowWhatsNew() -> Bool { return v != lastVersion } +fileprivate func newOperatorsView() -> some View { + VStack(alignment: .leading) { + Image((operatorsInfo[.flux] ?? ServerOperator.dummyOperatorInfo).largeLogo) + .resizable() + .scaledToFit() + .frame(height: 48) + Text("The second preset operator in the app!") + .multilineTextAlignment(.leading) + .lineLimit(10) + HStack { + Button("Enable Flux") { + + } + Text("for better metadata privacy.") + } + } +} + struct WhatsNewView: View { @Environment(\.dismiss) var dismiss: DismissAction @EnvironmentObject var theme: AppTheme @State var currentVersion = versionDescriptions.count - 1 @State var currentVersionNav = versionDescriptions.count - 1 var viaSettings = false + @State var showWhatsNew: Bool + var showOperatorsNotice: Bool var body: some View { + viewBody() + .task { + if showOperatorsNotice { + do { + let conditionsId = ChatModel.shared.conditions.currentConditions.conditionsId + try await setConditionsNotified(conditionsId: conditionsId) + } catch let error { + logger.error("WhatsNewView setConditionsNotified error: \(responseError(error))") + } + } + } + } + + @ViewBuilder private func viewBody() -> some View { + if showWhatsNew { + whatsNewView() + } else if showOperatorsNotice { + ChooseServerOperators(onboarding: false) + } + } + + private func whatsNewView() -> some View { VStack { TabView(selection: $currentVersion) { ForEach(Array(versionDescriptions.enumerated()), id: \.0) { (i, v) in @@ -532,9 +609,11 @@ struct WhatsNewView: View { .foregroundColor(theme.colors.secondary) .frame(maxWidth: .infinity) .padding(.vertical) - ForEach(v.features, id: \.title) { f in - featureDescription(f) - .padding(.bottom, 8) + ForEach(v.features) { f in + switch f { + case let .feature(d): featureDescription(d).padding(.bottom, 8) + case let .view(v): AnyView(v.view()).padding(.bottom, 8) + } } if let post = v.post { Link(destination: post) { @@ -546,11 +625,21 @@ struct WhatsNewView: View { } if !viaSettings { Spacer() - Button("Ok") { - dismiss() + + if showOperatorsNotice { + Button("View updated conditions") { + showWhatsNew = false + } + .font(.title3) + .frame(maxWidth: .infinity, alignment: .center) + } else { + Button("Ok") { + dismiss() + } + .font(.title3) + .frame(maxWidth: .infinity, alignment: .center) } - .font(.title3) - .frame(maxWidth: .infinity, alignment: .center) + Spacer() } } @@ -568,20 +657,24 @@ struct WhatsNewView: View { currentVersionNav = currentVersion } } - - private func featureDescription(_ f: FeatureDescription) -> some View { - VStack(alignment: .leading, spacing: 4) { - if let icon = f.icon { - HStack(alignment: .center, spacing: 4) { - Image(systemName: icon) - .symbolRenderingMode(.monochrome) - .foregroundColor(theme.colors.secondary) - .frame(minWidth: 30, alignment: .center) - Text(f.title).font(.title3).bold() - } - } else { - Text(f.title).font(.title3).bold() + + @ViewBuilder private func featureHeader(_ icon: String?, _ title: LocalizedStringKey) -> some View { + if let icon { + HStack(alignment: .center, spacing: 4) { + Image(systemName: icon) + .symbolRenderingMode(.monochrome) + .foregroundColor(theme.colors.secondary) + .frame(minWidth: 30, alignment: .center) + Text(title).font(.title3).bold() } + } else { + Text(title).font(.title3).bold() + } + } + + private func featureDescription(_ f: Description) -> some View { + VStack(alignment: .leading, spacing: 4) { + featureHeader(f.icon, f.title) if let d = f.description { Text(d) .multilineTextAlignment(.leading) @@ -636,6 +729,6 @@ struct WhatsNewView: View { struct NewFeaturesView_Previews: PreviewProvider { static var previews: some View { - WhatsNewView() + WhatsNewView(showWhatsNew: true, showOperatorsNotice: false) } } diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift index 155a3956be..2247e3d8d5 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift @@ -19,28 +19,80 @@ private enum NetworkAlert: Identifiable { } } +private enum NetworkAndServersSheet: Identifiable { + case showConditions(conditionsAction: UsageConditionsAction) + + var id: String { + switch self { + case .showConditions: return "showConditions" + } + } +} + struct NetworkAndServers: View { + @Environment(\.dismiss) var dismiss: DismissAction @EnvironmentObject var m: ChatModel + @Environment(\.colorScheme) var colorScheme: ColorScheme @EnvironmentObject var theme: AppTheme + @Binding var currUserServers: [UserOperatorServers] + @Binding var userServers: [UserOperatorServers] + @Binding var serverErrors: [UserServersError] + @State private var sheetItem: NetworkAndServersSheet? = nil + @State private var justOpened = true + @State private var showSaveDialog = false var body: some View { VStack { List { + let conditionsAction = m.conditions.conditionsAction + let anyOperatorEnabled = userServers.contains(where: { $0.operator?.enabled ?? false }) Section { - NavigationLink { - ProtocolServersView(serverProtocol: .smp) - .navigationTitle("Your SMP servers") - .modifier(ThemedBackground(grouped: true)) - } label: { - Text("Message servers") + ForEach(userServers.enumerated().map { $0 }, id: \.element.id) { idx, userOperatorServers in + if let serverOperator = userOperatorServers.operator { + serverOperatorView(idx, serverOperator) + } else { + EmptyView() + } } - NavigationLink { - ProtocolServersView(serverProtocol: .xftp) - .navigationTitle("Your XFTP servers") + if let conditionsAction = conditionsAction, anyOperatorEnabled { + conditionsButton(conditionsAction) + } + } header: { + Text("Preset servers") + .foregroundColor(theme.colors.secondary) + } footer: { + switch conditionsAction { + case let .review(_, deadline, _): + if let deadline = deadline, anyOperatorEnabled { + Text("Conditions will be considered accepted on: \(conditionsTimestamp(deadline)).") + .foregroundColor(theme.colors.secondary) + } + default: + EmptyView() + } + } + + Section { + if let idx = userServers.firstIndex(where: { $0.operator == nil }) { + NavigationLink { + YourServersView( + userServers: $userServers, + serverErrors: $serverErrors, + operatorIndex: idx + ) + .navigationTitle("Your servers") .modifier(ThemedBackground(grouped: true)) - } label: { - Text("Media & file servers") + } label: { + HStack { + Text("Your servers") + + if userServers[idx] != currUserServers[idx] { + Spacer() + unsavedChangesIndicator() + } + } + } } NavigationLink { @@ -55,6 +107,17 @@ struct NetworkAndServers: View { .foregroundColor(theme.colors.secondary) } + Section { + Button("Save servers", action: { saveServers($currUserServers, $userServers) }) + .disabled(!serversCanBeSaved(currUserServers, userServers, serverErrors)) + } footer: { + if let errStr = globalServersError(serverErrors) { + ServersErrorView(errStr: errStr) + } else if !serverErrors.isEmpty { + ServersErrorView(errStr: NSLocalizedString("Errors in servers configuration.", comment: "servers error")) + } + } + Section(header: Text("Calls").foregroundColor(theme.colors.secondary)) { NavigationLink { RTCServers() @@ -74,11 +137,287 @@ struct NetworkAndServers: View { } } } + .task { + // this condition is needed to prevent re-setting the servers when exiting single server view + if justOpened { + do { + currUserServers = try await getUserServers() + userServers = currUserServers + serverErrors = [] + } catch let error { + await MainActor.run { + showAlert( + NSLocalizedString("Error loading servers", comment: "alert title"), + message: responseError(error) + ) + } + } + justOpened = false + } + } + .modifier(BackButton(disabled: Binding.constant(false)) { + if serversCanBeSaved(currUserServers, userServers, serverErrors) { + showSaveDialog = true + } else { + dismiss() + } + }) + .confirmationDialog("Save servers?", isPresented: $showSaveDialog, titleVisibility: .visible) { + Button("Save") { + saveServers($currUserServers, $userServers) + dismiss() + } + Button("Exit without saving") { dismiss() } + } + .sheet(item: $sheetItem) { item in + switch item { + case let .showConditions(conditionsAction): + UsageConditionsView( + conditionsAction: conditionsAction, + currUserServers: $currUserServers, + userServers: $userServers + ) + .modifier(ThemedBackground(grouped: true)) + } + } + } + + private func serverOperatorView(_ operatorIndex: Int, _ serverOperator: ServerOperator) -> some View { + NavigationLink() { + OperatorView( + currUserServers: $currUserServers, + userServers: $userServers, + serverErrors: $serverErrors, + operatorIndex: operatorIndex, + useOperator: serverOperator.enabled + ) + .navigationBarTitle("\(serverOperator.tradeName) servers") + .modifier(ThemedBackground(grouped: true)) + .navigationBarTitleDisplayMode(.large) + } label: { + HStack { + Image(serverOperator.logo(colorScheme)) + .resizable() + .scaledToFit() + .grayscale(serverOperator.enabled ? 0.0 : 1.0) + .frame(width: 24, height: 24) + Text(serverOperator.tradeName) + .foregroundColor(serverOperator.enabled ? theme.colors.onBackground : theme.colors.secondary) + + if userServers[operatorIndex] != currUserServers[operatorIndex] { + Spacer() + unsavedChangesIndicator() + } + } + } + } + + private func unsavedChangesIndicator() -> some View { + Image(systemName: "pencil") + .foregroundColor(theme.colors.secondary) + .symbolRenderingMode(.monochrome) + .frame(maxWidth: 24, maxHeight: 24, alignment: .center) + } + + private func conditionsButton(_ conditionsAction: UsageConditionsAction) -> some View { + Button { + sheetItem = .showConditions(conditionsAction: conditionsAction) + } label: { + switch conditionsAction { + case .review: + Text("Review conditions") + case .accepted: + Text("Accepted conditions") + } + } + } +} + +struct UsageConditionsView: View { + @Environment(\.dismiss) var dismiss: DismissAction + @EnvironmentObject var theme: AppTheme + var conditionsAction: UsageConditionsAction + @Binding var currUserServers: [UserOperatorServers] + @Binding var userServers: [UserOperatorServers] + + var body: some View { + VStack(alignment: .leading, spacing: 20) { + Text("Conditions of use") + .font(.largeTitle) + .bold() + .padding(.top) + .padding(.top) + + switch conditionsAction { + + case let .review(operators, _, _): + Text("Conditions will be accepted for the operator(s): **\(operators.map { $0.legalName_ }.joined(separator: ", "))**.") + ConditionsTextView() + acceptConditionsButton(operators.map { $0.operatorId }) + .padding(.bottom) + .padding(.bottom) + + case let .accepted(operators): + Text("Conditions are accepted for the operator(s): **\(operators.map { $0.legalName_ }.joined(separator: ", "))**.") + ConditionsTextView() + .padding(.bottom) + .padding(.bottom) + } + } + .padding(.horizontal) + .frame(maxHeight: .infinity) + } + + private func acceptConditionsButton(_ operatorIds: [Int64]) -> some View { + Button { + acceptForOperators(operatorIds) + } label: { + Text("Accept conditions") + } + .buttonStyle(OnboardingButtonStyle()) + } + + func acceptForOperators(_ operatorIds: [Int64]) { + Task { + do { + let conditionsId = ChatModel.shared.conditions.currentConditions.conditionsId + let r = try await acceptConditions(conditionsId: conditionsId, operatorIds: operatorIds) + await MainActor.run { + ChatModel.shared.conditions = r + updateOperatorsConditionsAcceptance($currUserServers, r.serverOperators) + updateOperatorsConditionsAcceptance($userServers, r.serverOperators) + dismiss() + } + } catch let error { + await MainActor.run { + showAlert( + NSLocalizedString("Error accepting conditions", comment: "alert title"), + message: responseError(error) + ) + } + } + } + } +} + +func validateServers_(_ userServers: Binding<[UserOperatorServers]>, _ serverErrors: Binding<[UserServersError]>) { + let userServersToValidate = userServers.wrappedValue + Task { + do { + let errs = try await validateServers(userServers: userServersToValidate) + await MainActor.run { + serverErrors.wrappedValue = errs + } + } catch let error { + logger.error("validateServers error: \(responseError(error))") + } + } +} + +func serversCanBeSaved( + _ currUserServers: [UserOperatorServers], + _ userServers: [UserOperatorServers], + _ serverErrors: [UserServersError] +) -> Bool { + return userServers != currUserServers && serverErrors.isEmpty +} + +struct ServersErrorView: View { + @EnvironmentObject var theme: AppTheme + var errStr: String + + var body: some View { + HStack { + Image(systemName: "exclamationmark.circle") + .foregroundColor(.red) + Text(errStr) + .foregroundColor(theme.colors.secondary) + } + } +} + +func globalServersError(_ serverErrors: [UserServersError]) -> String? { + for err in serverErrors { + if let errStr = err.globalError { + return errStr + } + } + return nil +} + +func globalSMPServersError(_ serverErrors: [UserServersError]) -> String? { + for err in serverErrors { + if let errStr = err.globalSMPError { + return errStr + } + } + return nil +} + +func globalXFTPServersError(_ serverErrors: [UserServersError]) -> String? { + for err in serverErrors { + if let errStr = err.globalXFTPError { + return errStr + } + } + return nil +} + +func findDuplicateHosts(_ serverErrors: [UserServersError]) -> Set { + let duplicateHostsList = serverErrors.compactMap { err in + if case let .duplicateServer(_, _, duplicateHost) = err { + return duplicateHost + } else { + return nil + } + } + return Set(duplicateHostsList) +} + +func saveServers(_ currUserServers: Binding<[UserOperatorServers]>, _ userServers: Binding<[UserOperatorServers]>) { + let userServersToSave = userServers.wrappedValue + Task { + do { + try await setUserServers(userServers: userServersToSave) + // Get updated servers to learn new server ids (otherwise it messes up delete of newly added and saved servers) + do { + let updatedServers = try await getUserServers() + await MainActor.run { + currUserServers.wrappedValue = updatedServers + userServers.wrappedValue = updatedServers + } + } catch let error { + logger.error("saveServers getUserServers error: \(responseError(error))") + await MainActor.run { + currUserServers.wrappedValue = userServersToSave + } + } + } catch let error { + logger.error("saveServers setUserServers error: \(responseError(error))") + await MainActor.run { + showAlert( + NSLocalizedString("Error saving servers", comment: "alert title"), + message: responseError(error) + ) + } + } + } +} + +func updateOperatorsConditionsAcceptance(_ usvs: Binding<[UserOperatorServers]>, _ updatedOperators: [ServerOperator]) { + for i in 0.. some View { + VStack { + let serverAddress = parseServerAddress(serverToEdit.server) + let valid = serverAddress?.valid == true + List { + Section { + TextEditor(text: $serverToEdit.server) + .multilineTextAlignment(.leading) + .autocorrectionDisabled(true) + .autocapitalization(.none) + .allowsTightening(true) + .lineLimit(10) + .frame(height: 144) + .padding(-6) + } header: { + HStack { + Text("Your server address") + .foregroundColor(theme.colors.secondary) + if !valid { + Spacer() + Image(systemName: "exclamationmark.circle").foregroundColor(.red) + } + } + } + useServerSection(valid) + if valid { + Section(header: Text("Add to another device").foregroundColor(theme.colors.secondary)) { + MutableQRCode(uri: $serverToEdit.server) + .listRowInsets(EdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 12)) + } + } + } + } + } + + private func useServerSection(_ valid: Bool) -> some View { + Section(header: Text("Use server").foregroundColor(theme.colors.secondary)) { + HStack { + Button("Test server") { + testing = true + serverToEdit.tested = nil + Task { + if let f = await testServerConnection(server: $serverToEdit) { + showTestFailure = true + testFailure = f + } + await MainActor.run { testing = false } + } + } + .disabled(!valid || testing) + Spacer() + showTestStatus(server: serverToEdit) + } + Toggle("Use for new connections", isOn: $serverToEdit.enabled) + } + } +} + +func serverProtocolAndOperator(_ server: UserServer, _ userServers: [UserOperatorServers]) -> (ServerProtocol, ServerOperator?)? { + if let serverAddress = parseServerAddress(server.server) { + let serverProtocol = serverAddress.serverProtocol + let hostnames = serverAddress.hostnames + let matchingOperator = userServers.compactMap { $0.operator }.first { op in + op.serverDomains.contains { domain in + hostnames.contains { hostname in + hostname.hasSuffix(domain) + } + } + } + return (serverProtocol, matchingOperator) + } else { + return nil + } +} + +func addServer( + _ server: UserServer, + _ userServers: Binding<[UserOperatorServers]>, + _ serverErrors: Binding<[UserServersError]>, + _ dismiss: DismissAction +) { + if let (serverProtocol, matchingOperator) = serverProtocolAndOperator(server, userServers.wrappedValue) { + if let i = userServers.wrappedValue.firstIndex(where: { $0.operator?.operatorId == matchingOperator?.operatorId }) { + switch serverProtocol { + case .smp: userServers[i].wrappedValue.smpServers.append(server) + case .xftp: userServers[i].wrappedValue.xftpServers.append(server) + } + validateServers_(userServers, serverErrors) + dismiss() + if let op = matchingOperator { + showAlert( + NSLocalizedString("Operator server", comment: "alert title"), + message: String.localizedStringWithFormat(NSLocalizedString("Server added to operator %@.", comment: "alert message"), op.tradeName) + ) + } + } else { // Shouldn't happen + dismiss() + showAlert(NSLocalizedString("Error adding server", comment: "alert title")) + } + } else { + dismiss() + if server.server.trimmingCharacters(in: .whitespaces) != "" { + showAlert( + NSLocalizedString("Invalid server address!", comment: "alert title"), + message: NSLocalizedString("Check server address and try again.", comment: "alert title") + ) + } + } +} + +#Preview { + NewServerView( + userServers: Binding.constant([UserOperatorServers.sampleDataNilOperator]), + serverErrors: Binding.constant([]) + ) +} diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift new file mode 100644 index 0000000000..ef02e94e3f --- /dev/null +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift @@ -0,0 +1,569 @@ +// +// OperatorView.swift +// SimpleX (iOS) +// +// Created by spaced4ndy on 28.10.2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +struct OperatorView: View { + @Environment(\.dismiss) var dismiss: DismissAction + @Environment(\.colorScheme) var colorScheme: ColorScheme + @EnvironmentObject var theme: AppTheme + @Environment(\.editMode) private var editMode + @Binding var currUserServers: [UserOperatorServers] + @Binding var userServers: [UserOperatorServers] + @Binding var serverErrors: [UserServersError] + var operatorIndex: Int + @State var useOperator: Bool + @State private var useOperatorToggleReset: Bool = false + @State private var showConditionsSheet: Bool = false + @State private var selectedServer: String? = nil + @State private var testing = false + + var body: some View { + operatorView() + .opacity(testing ? 0.4 : 1) + .overlay { + if testing { + ProgressView() + .scaleEffect(2) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + } + .allowsHitTesting(!testing) + } + + @ViewBuilder private func operatorView() -> some View { + let duplicateHosts = findDuplicateHosts(serverErrors) + VStack { + List { + Section { + infoViewLink() + useOperatorToggle() + } header: { + Text("Operator") + .foregroundColor(theme.colors.secondary) + } footer: { + if let errStr = globalServersError(serverErrors) { + ServersErrorView(errStr: errStr) + } else { + switch (userServers[operatorIndex].operator_.conditionsAcceptance) { + case let .accepted(acceptedAt): + if let acceptedAt = acceptedAt { + Text("Conditions accepted on: \(conditionsTimestamp(acceptedAt)).") + .foregroundColor(theme.colors.secondary) + } + case let .required(deadline): + if userServers[operatorIndex].operator_.enabled, let deadline = deadline { + Text("Conditions will be accepted on: \(conditionsTimestamp(deadline)).") + .foregroundColor(theme.colors.secondary) + } + } + } + } + + if userServers[operatorIndex].operator_.enabled { + if !userServers[operatorIndex].smpServers.filter({ !$0.deleted }).isEmpty { + Section { + Toggle("To receive", isOn: $userServers[operatorIndex].operator_.smpRoles.storage) + .onChange(of: userServers[operatorIndex].operator_.smpRoles.storage) { _ in + validateServers_($userServers, $serverErrors) + } + Toggle("For private routing", isOn: $userServers[operatorIndex].operator_.smpRoles.proxy) + .onChange(of: userServers[operatorIndex].operator_.smpRoles.proxy) { _ in + validateServers_($userServers, $serverErrors) + } + } header: { + Text("Use for messages") + .foregroundColor(theme.colors.secondary) + } footer: { + if let errStr = globalSMPServersError(serverErrors) { + ServersErrorView(errStr: errStr) + } + } + } + + // Preset servers can't be deleted + if !userServers[operatorIndex].smpServers.filter({ $0.preset }).isEmpty { + Section { + ForEach($userServers[operatorIndex].smpServers) { srv in + if srv.wrappedValue.preset { + ProtocolServerViewLink( + userServers: $userServers, + serverErrors: $serverErrors, + duplicateHosts: duplicateHosts, + server: srv, + serverProtocol: .smp, + backLabel: "\(userServers[operatorIndex].operator_.tradeName) servers", + selectedServer: $selectedServer + ) + } else { + EmptyView() + } + } + } header: { + Text("Message servers") + .foregroundColor(theme.colors.secondary) + } footer: { + if let errStr = globalSMPServersError(serverErrors) { + ServersErrorView(errStr: errStr) + } else { + Text("The servers for new connections of your current chat profile **\(ChatModel.shared.currentUser?.displayName ?? "")**.") + .foregroundColor(theme.colors.secondary) + .lineLimit(10) + } + } + } + + if !userServers[operatorIndex].smpServers.filter({ !$0.preset && !$0.deleted }).isEmpty { + Section { + ForEach($userServers[operatorIndex].smpServers) { srv in + if !srv.wrappedValue.preset && !srv.wrappedValue.deleted { + ProtocolServerViewLink( + userServers: $userServers, + serverErrors: $serverErrors, + duplicateHosts: duplicateHosts, + server: srv, + serverProtocol: .smp, + backLabel: "\(userServers[operatorIndex].operator_.tradeName) servers", + selectedServer: $selectedServer + ) + } else { + EmptyView() + } + } + .onDelete { indexSet in + deleteSMPServer($userServers, operatorIndex, indexSet) + validateServers_($userServers, $serverErrors) + } + } header: { + Text("Added message servers") + .foregroundColor(theme.colors.secondary) + } + } + + if !userServers[operatorIndex].xftpServers.filter({ !$0.deleted }).isEmpty { + Section { + Toggle("To send", isOn: $userServers[operatorIndex].operator_.xftpRoles.storage) + .onChange(of: userServers[operatorIndex].operator_.xftpRoles.storage) { _ in + validateServers_($userServers, $serverErrors) + } + } header: { + Text("Use for files") + .foregroundColor(theme.colors.secondary) + } footer: { + if let errStr = globalXFTPServersError(serverErrors) { + ServersErrorView(errStr: errStr) + } + } + } + + // Preset servers can't be deleted + if !userServers[operatorIndex].xftpServers.filter({ $0.preset }).isEmpty { + Section { + ForEach($userServers[operatorIndex].xftpServers) { srv in + if srv.wrappedValue.preset { + ProtocolServerViewLink( + userServers: $userServers, + serverErrors: $serverErrors, + duplicateHosts: duplicateHosts, + server: srv, + serverProtocol: .xftp, + backLabel: "\(userServers[operatorIndex].operator_.tradeName) servers", + selectedServer: $selectedServer + ) + } else { + EmptyView() + } + } + } header: { + Text("Media & file servers") + .foregroundColor(theme.colors.secondary) + } footer: { + if let errStr = globalXFTPServersError(serverErrors) { + ServersErrorView(errStr: errStr) + } else { + Text("The servers for new files of your current chat profile **\(ChatModel.shared.currentUser?.displayName ?? "")**.") + .foregroundColor(theme.colors.secondary) + .lineLimit(10) + } + } + } + + if !userServers[operatorIndex].xftpServers.filter({ !$0.preset && !$0.deleted }).isEmpty { + Section { + ForEach($userServers[operatorIndex].xftpServers) { srv in + if !srv.wrappedValue.preset && !srv.wrappedValue.deleted { + ProtocolServerViewLink( + userServers: $userServers, + serverErrors: $serverErrors, + duplicateHosts: duplicateHosts, + server: srv, + serverProtocol: .xftp, + backLabel: "\(userServers[operatorIndex].operator_.tradeName) servers", + selectedServer: $selectedServer + ) + } else { + EmptyView() + } + } + .onDelete { indexSet in + deleteXFTPServer($userServers, operatorIndex, indexSet) + validateServers_($userServers, $serverErrors) + } + } header: { + Text("Added media & file servers") + .foregroundColor(theme.colors.secondary) + } + } + + Section { + TestServersButton( + smpServers: $userServers[operatorIndex].smpServers, + xftpServers: $userServers[operatorIndex].xftpServers, + testing: $testing + ) + } + } + } + } + .toolbar { + if ( + !userServers[operatorIndex].smpServers.filter({ !$0.preset && !$0.deleted }).isEmpty || + !userServers[operatorIndex].xftpServers.filter({ !$0.preset && !$0.deleted }).isEmpty + ) { + EditButton() + } + } + .sheet(isPresented: $showConditionsSheet, onDismiss: onUseToggleSheetDismissed) { + SingleOperatorUsageConditionsView( + currUserServers: $currUserServers, + userServers: $userServers, + serverErrors: $serverErrors, + operatorIndex: operatorIndex + ) + .modifier(ThemedBackground(grouped: true)) + } + } + + private func infoViewLink() -> some View { + NavigationLink() { + OperatorInfoView(serverOperator: userServers[operatorIndex].operator_) + .navigationBarTitle("Network operator") + .modifier(ThemedBackground(grouped: true)) + .navigationBarTitleDisplayMode(.large) + } label: { + HStack { + Image(userServers[operatorIndex].operator_.logo(colorScheme)) + .resizable() + .scaledToFit() + .grayscale(userServers[operatorIndex].operator_.enabled ? 0.0 : 1.0) + .frame(width: 24, height: 24) + Text(userServers[operatorIndex].operator_.tradeName) + } + } + } + + private func useOperatorToggle() -> some View { + Toggle("Use servers", isOn: $useOperator) + .onChange(of: useOperator) { useOperatorToggle in + if useOperatorToggleReset { + useOperatorToggleReset = false + } else if useOperatorToggle { + switch userServers[operatorIndex].operator_.conditionsAcceptance { + case .accepted: + userServers[operatorIndex].operator_.enabled = true + validateServers_($userServers, $serverErrors) + case let .required(deadline): + if deadline == nil { + showConditionsSheet = true + } else { + userServers[operatorIndex].operator_.enabled = true + validateServers_($userServers, $serverErrors) + } + } + } else { + userServers[operatorIndex].operator_.enabled = false + validateServers_($userServers, $serverErrors) + } + } + } + + private func onUseToggleSheetDismissed() { + if useOperator && !userServers[operatorIndex].operator_.conditionsAcceptance.usageAllowed { + useOperatorToggleReset = true + useOperator = false + } + } +} + +func conditionsTimestamp(_ date: Date) -> String { + let localDateFormatter = DateFormatter() + localDateFormatter.dateStyle = .medium + localDateFormatter.timeStyle = .none + return localDateFormatter.string(from: date) +} + +struct OperatorInfoView: View { + @EnvironmentObject var theme: AppTheme + @Environment(\.colorScheme) var colorScheme: ColorScheme + var serverOperator: ServerOperator + + var body: some View { + VStack { + List { + Section { + VStack(alignment: .leading) { + Image(serverOperator.largeLogo(colorScheme)) + .resizable() + .scaledToFit() + .frame(height: 48) + if let legalName = serverOperator.legalName { + Text(legalName) + } + } + } + Section { + VStack(alignment: .leading, spacing: 12) { + ForEach(serverOperator.info.description, id: \.self) { d in + Text(d) + } + } + } + Section { + Link("\(serverOperator.info.website)", destination: URL(string: serverOperator.info.website)!) + } + } + } + } +} + +struct ConditionsTextView: View { + @State private var conditionsData: (UsageConditions, String?, UsageConditions?)? + @State private var failedToLoad: Bool = false + + let defaultConditionsLink = "https://github.com/simplex-chat/simplex-chat/blob/stable/PRIVACY.md" + + var body: some View { + viewBody() + .frame(maxWidth: .infinity, maxHeight: .infinity) + .task { + do { + conditionsData = try await getUsageConditions() + } catch let error { + logger.error("ConditionsTextView getUsageConditions error: \(responseError(error))") + failedToLoad = true + } + } + } + + // TODO Markdown & diff rendering + @ViewBuilder private func viewBody() -> some View { + if let (usageConditions, conditionsText, acceptedConditions) = conditionsData { + if let conditionsText = conditionsText { + ScrollView { + Text(conditionsText.trimmingCharacters(in: .whitespacesAndNewlines)) + .padding() + } + .background( + RoundedRectangle(cornerRadius: 12, style: .continuous) + .fill(Color(uiColor: .secondarySystemGroupedBackground)) + ) + } else { + let conditionsLink = "https://github.com/simplex-chat/simplex-chat/blob/\(usageConditions.conditionsCommit)/PRIVACY.md" + conditionsLinkView(conditionsLink) + } + } else if failedToLoad { + conditionsLinkView(defaultConditionsLink) + } else { + ProgressView() + .scaleEffect(2) + } + } + + private func conditionsLinkView(_ conditionsLink: String) -> some View { + VStack(alignment: .leading, spacing: 20) { + Text("Current conditions text couldn't be loaded, you can review conditions via this link:") + Link(destination: URL(string: conditionsLink)!) { + Text(conditionsLink) + .multilineTextAlignment(.leading) + } + } + } +} + +struct SingleOperatorUsageConditionsView: View { + @Environment(\.dismiss) var dismiss: DismissAction + @EnvironmentObject var theme: AppTheme + @Binding var currUserServers: [UserOperatorServers] + @Binding var userServers: [UserOperatorServers] + @Binding var serverErrors: [UserServersError] + var operatorIndex: Int + @State private var usageConditionsNavLinkActive: Bool = false + + var body: some View { + viewBody() + } + + @ViewBuilder private func viewBody() -> some View { + let operatorsWithConditionsAccepted = ChatModel.shared.conditions.serverOperators.filter { $0.conditionsAcceptance.conditionsAccepted } + if case .accepted = userServers[operatorIndex].operator_.conditionsAcceptance { + + // In current UI implementation this branch doesn't get shown - as conditions can't be opened from inside operator once accepted + VStack(alignment: .leading, spacing: 20) { + Group { + viewHeader() + ConditionsTextView() + .padding(.bottom) + .padding(.bottom) + } + .padding(.horizontal) + } + .frame(maxHeight: .infinity) + + } else if !operatorsWithConditionsAccepted.isEmpty { + + NavigationView { + VStack(alignment: .leading, spacing: 20) { + Group { + viewHeader() + Text("Conditions are already accepted for following operator(s): **\(operatorsWithConditionsAccepted.map { $0.legalName_ }.joined(separator: ", "))**.") + Text("Same conditions will apply to operator **\(userServers[operatorIndex].operator_.legalName_)**.") + conditionsAppliedToOtherOperatorsText() + usageConditionsNavLinkButton() + + Spacer() + + acceptConditionsButton() + .padding(.bottom) + .padding(.bottom) + } + .padding(.horizontal) + } + .frame(maxHeight: .infinity) + } + + } else { + + VStack(alignment: .leading, spacing: 20) { + Group { + viewHeader() + Text("To use the servers of **\(userServers[operatorIndex].operator_.legalName_)**, accept conditions of use.") + conditionsAppliedToOtherOperatorsText() + ConditionsTextView() + acceptConditionsButton() + .padding(.bottom) + .padding(.bottom) + } + .padding(.horizontal) + } + .frame(maxHeight: .infinity) + + } + } + + private func viewHeader() -> some View { + Text("Use servers of \(userServers[operatorIndex].operator_.tradeName)") + .font(.largeTitle) + .bold() + .padding(.top) + .padding(.top) + } + + @ViewBuilder private func conditionsAppliedToOtherOperatorsText() -> some View { + let otherOperatorsToApply = ChatModel.shared.conditions.serverOperators.filter { + $0.enabled && + !$0.conditionsAcceptance.conditionsAccepted && + $0.operatorId != userServers[operatorIndex].operator_.operatorId + } + if !otherOperatorsToApply.isEmpty { + Text("These conditions will also apply for: **\(otherOperatorsToApply.map { $0.legalName_ }.joined(separator: ", "))**.") + } + } + + @ViewBuilder private func acceptConditionsButton() -> some View { + let operatorIds = ChatModel.shared.conditions.serverOperators + .filter { + $0.operatorId == userServers[operatorIndex].operator_.operatorId || // Opened operator + ($0.enabled && !$0.conditionsAcceptance.conditionsAccepted) // Other enabled operators with conditions not accepted + } + .map { $0.operatorId } + Button { + acceptForOperators(operatorIds, operatorIndex) + } label: { + Text("Accept conditions") + } + .buttonStyle(OnboardingButtonStyle()) + } + + func acceptForOperators(_ operatorIds: [Int64], _ operatorIndexToEnable: Int) { + Task { + do { + let conditionsId = ChatModel.shared.conditions.currentConditions.conditionsId + let r = try await acceptConditions(conditionsId: conditionsId, operatorIds: operatorIds) + await MainActor.run { + ChatModel.shared.conditions = r + updateOperatorsConditionsAcceptance($currUserServers, r.serverOperators) + updateOperatorsConditionsAcceptance($userServers, r.serverOperators) + userServers[operatorIndexToEnable].operator?.enabled = true + validateServers_($userServers, $serverErrors) + dismiss() + } + } catch let error { + await MainActor.run { + showAlert( + NSLocalizedString("Error accepting conditions", comment: "alert title"), + message: responseError(error) + ) + } + } + } + } + + private func usageConditionsNavLinkButton() -> some View { + ZStack { + Button { + usageConditionsNavLinkActive = true + } label: { + Text("View conditions") + } + + NavigationLink(isActive: $usageConditionsNavLinkActive) { + usageConditionsDestinationView() + } label: { + EmptyView() + } + .frame(width: 1, height: 1) + .hidden() + } + } + + private func usageConditionsDestinationView() -> some View { + VStack(spacing: 20) { + ConditionsTextView() + .padding(.top) + + acceptConditionsButton() + .padding(.bottom) + .padding(.bottom) + } + .padding(.horizontal) + .navigationTitle("Conditions of use") + .navigationBarTitleDisplayMode(.large) + .modifier(ThemedBackground(grouped: true)) + } +} + +#Preview { + OperatorView( + currUserServers: Binding.constant([UserOperatorServers.sampleData1, UserOperatorServers.sampleDataNilOperator]), + userServers: Binding.constant([UserOperatorServers.sampleData1, UserOperatorServers.sampleDataNilOperator]), + serverErrors: Binding.constant([]), + operatorIndex: 1, + useOperator: ServerOperator.sampleData1.enabled + ) +} diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift index da29dfac29..13d01874ed 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift @@ -12,15 +12,15 @@ import SimpleXChat struct ProtocolServerView: View { @Environment(\.dismiss) var dismiss: DismissAction @EnvironmentObject var theme: AppTheme - let serverProtocol: ServerProtocol - @Binding var server: ServerCfg - @State var serverToEdit: ServerCfg + @Binding var userServers: [UserOperatorServers] + @Binding var serverErrors: [UserServersError] + @Binding var server: UserServer + @State var serverToEdit: UserServer + var backLabel: LocalizedStringKey @State private var showTestFailure = false @State private var testing = false @State private var testFailure: ProtocolTestFailure? - var proto: String { serverProtocol.rawValue.uppercased() } - var body: some View { ZStack { if server.preset { @@ -32,9 +32,33 @@ struct ProtocolServerView: View { ProgressView().scaleEffect(2) } } - .modifier(BackButton(label: "Your \(proto) servers", disabled: Binding.constant(false)) { - server = serverToEdit - dismiss() + .modifier(BackButton(label: backLabel, disabled: Binding.constant(false)) { + if let (serverToEditProtocol, serverToEditOperator) = serverProtocolAndOperator(serverToEdit, userServers), + let (serverProtocol, serverOperator) = serverProtocolAndOperator(server, userServers) { + if serverToEditProtocol != serverProtocol { + dismiss() + showAlert( + NSLocalizedString("Error updating server", comment: "alert title"), + message: NSLocalizedString("Server protocol changed.", comment: "alert title") + ) + } else if serverToEditOperator != serverOperator { + dismiss() + showAlert( + NSLocalizedString("Error updating server", comment: "alert title"), + message: NSLocalizedString("Server operator changed.", comment: "alert title") + ) + } else { + server = serverToEdit + validateServers_($userServers, $serverErrors) + dismiss() + } + } else { + dismiss() + showAlert( + NSLocalizedString("Invalid server address!", comment: "alert title"), + message: NSLocalizedString("Check server address and try again.", comment: "alert title") + ) + } }) .alert(isPresented: $showTestFailure) { Alert( @@ -62,7 +86,7 @@ struct ProtocolServerView: View { private func customServer() -> some View { VStack { let serverAddress = parseServerAddress(serverToEdit.server) - let valid = serverAddress?.valid == true && serverAddress?.serverProtocol == serverProtocol + let valid = serverAddress?.valid == true List { Section { TextEditor(text: $serverToEdit.server) @@ -112,10 +136,7 @@ struct ProtocolServerView: View { Spacer() showTestStatus(server: serverToEdit) } - let useForNewDisabled = serverToEdit.tested != true && !serverToEdit.preset Toggle("Use for new connections", isOn: $serverToEdit.enabled) - .disabled(useForNewDisabled) - .foregroundColor(useForNewDisabled ? theme.colors.secondary : theme.colors.onBackground) } } } @@ -142,7 +163,7 @@ struct BackButton: ViewModifier { } } -@ViewBuilder func showTestStatus(server: ServerCfg) -> some View { +@ViewBuilder func showTestStatus(server: UserServer) -> some View { switch server.tested { case .some(true): Image(systemName: "checkmark") @@ -155,7 +176,7 @@ struct BackButton: ViewModifier { } } -func testServerConnection(server: Binding) async -> ProtocolTestFailure? { +func testServerConnection(server: Binding) async -> ProtocolTestFailure? { do { let r = try await testProtoServer(server: server.wrappedValue.server) switch r { @@ -178,9 +199,11 @@ func testServerConnection(server: Binding) async -> ProtocolTestFailu struct ProtocolServerView_Previews: PreviewProvider { static var previews: some View { ProtocolServerView( - serverProtocol: .smp, - server: Binding.constant(ServerCfg.sampleData.custom), - serverToEdit: ServerCfg.sampleData.custom + userServers: Binding.constant([UserOperatorServers.sampleDataNilOperator]), + serverErrors: Binding.constant([]), + server: Binding.constant(UserServer.sampleData.custom), + serverToEdit: UserServer.sampleData.custom, + backLabel: "Your SMP servers" ) } } diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift index 0fb37d5c49..ed3c5c773c 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift @@ -11,238 +11,166 @@ import SimpleXChat private let howToUrl = URL(string: "https://simplex.chat/docs/server.html")! -struct ProtocolServersView: View { +struct YourServersView: View { @Environment(\.dismiss) var dismiss: DismissAction @EnvironmentObject private var m: ChatModel @EnvironmentObject var theme: AppTheme @Environment(\.editMode) private var editMode - let serverProtocol: ServerProtocol - @State private var currServers: [ServerCfg] = [] - @State private var presetServers: [ServerCfg] = [] - @State private var configuredServers: [ServerCfg] = [] - @State private var otherServers: [ServerCfg] = [] + @Binding var userServers: [UserOperatorServers] + @Binding var serverErrors: [UserServersError] + var operatorIndex: Int @State private var selectedServer: String? = nil @State private var showAddServer = false + @State private var newServerNavLinkActive = false @State private var showScanProtoServer = false - @State private var justOpened = true @State private var testing = false - @State private var alert: ServerAlert? = nil - @State private var showSaveDialog = false - - var proto: String { serverProtocol.rawValue.uppercased() } var body: some View { - ZStack { - protocolServersView() - if testing { - ProgressView().scaleEffect(2) + yourServersView() + .opacity(testing ? 0.4 : 1) + .overlay { + if testing { + ProgressView() + .scaleEffect(2) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } } - } + .allowsHitTesting(!testing) } - enum ServerAlert: Identifiable { - case testsFailed(failures: [String: ProtocolTestFailure]) - case error(title: LocalizedStringKey, error: LocalizedStringKey = "") - - var id: String { - switch self { - case .testsFailed: return "testsFailed" - case let .error(title, _): return "error \(title)" - } - } - } - - private func protocolServersView() -> some View { + @ViewBuilder private func yourServersView() -> some View { + let duplicateHosts = findDuplicateHosts(serverErrors) List { - if !configuredServers.isEmpty { + if !userServers[operatorIndex].smpServers.filter({ !$0.deleted }).isEmpty { Section { - ForEach($configuredServers) { srv in - protocolServerView(srv) - } - .onMove { indexSet, offset in - configuredServers.move(fromOffsets: indexSet, toOffset: offset) + ForEach($userServers[operatorIndex].smpServers) { srv in + if !srv.wrappedValue.deleted { + ProtocolServerViewLink( + userServers: $userServers, + serverErrors: $serverErrors, + duplicateHosts: duplicateHosts, + server: srv, + serverProtocol: .smp, + backLabel: "Your servers", + selectedServer: $selectedServer + ) + } else { + EmptyView() + } } .onDelete { indexSet in - configuredServers.remove(atOffsets: indexSet) + deleteSMPServer($userServers, operatorIndex, indexSet) + validateServers_($userServers, $serverErrors) } } header: { - Text("Configured \(proto) servers") + Text("Message servers") .foregroundColor(theme.colors.secondary) } footer: { - Text("The servers for new connections of your current chat profile **\(m.currentUser?.displayName ?? "")**.") - .foregroundColor(theme.colors.secondary) - .lineLimit(10) + if let errStr = globalSMPServersError(serverErrors) { + ServersErrorView(errStr: errStr) + } else { + Text("The servers for new connections of your current chat profile **\(m.currentUser?.displayName ?? "")**.") + .foregroundColor(theme.colors.secondary) + .lineLimit(10) + } } } - if !otherServers.isEmpty { + if !userServers[operatorIndex].xftpServers.filter({ !$0.deleted }).isEmpty { Section { - ForEach($otherServers) { srv in - protocolServerView(srv) - } - .onMove { indexSet, offset in - otherServers.move(fromOffsets: indexSet, toOffset: offset) + ForEach($userServers[operatorIndex].xftpServers) { srv in + if !srv.wrappedValue.deleted { + ProtocolServerViewLink( + userServers: $userServers, + serverErrors: $serverErrors, + duplicateHosts: duplicateHosts, + server: srv, + serverProtocol: .xftp, + backLabel: "Your servers", + selectedServer: $selectedServer + ) + } else { + EmptyView() + } } .onDelete { indexSet in - otherServers.remove(atOffsets: indexSet) + deleteXFTPServer($userServers, operatorIndex, indexSet) + validateServers_($userServers, $serverErrors) } } header: { - Text("Other \(proto) servers") + Text("Media & file servers") .foregroundColor(theme.colors.secondary) + } footer: { + if let errStr = globalXFTPServersError(serverErrors) { + ServersErrorView(errStr: errStr) + } else { + Text("The servers for new files of your current chat profile **\(m.currentUser?.displayName ?? "")**.") + .foregroundColor(theme.colors.secondary) + .lineLimit(10) + } } } Section { - Button("Add server") { - showAddServer = true + ZStack { + Button("Add server") { + showAddServer = true + } + + NavigationLink(isActive: $newServerNavLinkActive) { + newServerDestinationView() + } label: { + EmptyView() + } + .frame(width: 1, height: 1) + .hidden() + } + } footer: { + if let errStr = globalServersError(serverErrors) { + ServersErrorView(errStr: errStr) } } Section { - Button("Reset") { partitionServers(currServers) } - .disabled(Set(allServers) == Set(currServers) || testing) - Button("Test servers", action: testServers) - .disabled(testing || allServersDisabled) - Button("Save servers", action: saveServers) - .disabled(saveDisabled) + TestServersButton( + smpServers: $userServers[operatorIndex].smpServers, + xftpServers: $userServers[operatorIndex].xftpServers, + testing: $testing + ) howToButton() } } - .toolbar { EditButton() } - .confirmationDialog("Add server", isPresented: $showAddServer, titleVisibility: .hidden) { - Button("Enter server manually") { - otherServers.append(ServerCfg.empty) - selectedServer = allServers.last?.id + .toolbar { + if ( + !userServers[operatorIndex].smpServers.filter({ !$0.deleted }).isEmpty || + !userServers[operatorIndex].xftpServers.filter({ !$0.deleted }).isEmpty + ) { + EditButton() } + } + .confirmationDialog("Add server", isPresented: $showAddServer, titleVisibility: .hidden) { + Button("Enter server manually") { newServerNavLinkActive = true } Button("Scan server QR code") { showScanProtoServer = true } - Button("Add preset servers", action: addAllPresets) - .disabled(hasAllPresets()) } .sheet(isPresented: $showScanProtoServer) { - ScanProtocolServer(servers: $otherServers) - .modifier(ThemedBackground(grouped: true)) - } - .modifier(BackButton(disabled: Binding.constant(false)) { - if saveDisabled { - dismiss() - justOpened = false - } else { - showSaveDialog = true - } - }) - .confirmationDialog("Save servers?", isPresented: $showSaveDialog, titleVisibility: .visible) { - Button("Save") { - saveServers() - dismiss() - justOpened = false - } - Button("Exit without saving") { dismiss() } - } - .alert(item: $alert) { a in - switch a { - case let .testsFailed(fs): - let msg = fs.map { (srv, f) in - "\(srv): \(f.localizedDescription)" - }.joined(separator: "\n") - return Alert( - title: Text("Tests failed!"), - message: Text("Some servers failed the test:\n" + msg) - ) - case .error: - return Alert( - title: Text("Error") - ) - } - } - .onAppear { - // this condition is needed to prevent re-setting the servers when exiting single server view - if justOpened { - do { - let r = try getUserProtoServers(serverProtocol) - currServers = r.protoServers - presetServers = r.presetServers - partitionServers(currServers) - } catch let error { - alert = .error( - title: "Error loading \(proto) servers", - error: "Error: \(responseError(error))" - ) - } - justOpened = false - } else { - partitionServers(allServers) - } - } - } - - private func partitionServers(_ servers: [ServerCfg]) { - configuredServers = servers.filter { $0.preset || $0.enabled } - otherServers = servers.filter { !($0.preset || $0.enabled) } - } - - private var allServers: [ServerCfg] { - configuredServers + otherServers - } - - private var saveDisabled: Bool { - allServers.isEmpty || - Set(allServers) == Set(currServers) || - testing || - !allServers.allSatisfy { srv in - if let address = parseServerAddress(srv.server) { - return uniqueAddress(srv, address) - } - return false - } || - allServersDisabled - } - - private var allServersDisabled: Bool { - allServers.allSatisfy { !$0.enabled } - } - - private func protocolServerView(_ server: Binding) -> some View { - let srv = server.wrappedValue - return NavigationLink(tag: srv.id, selection: $selectedServer) { - ProtocolServerView( - serverProtocol: serverProtocol, - server: server, - serverToEdit: srv + ScanProtocolServer( + userServers: $userServers, + serverErrors: $serverErrors ) - .navigationBarTitle(srv.preset ? "Preset server" : "Your server") .modifier(ThemedBackground(grouped: true)) - .navigationBarTitleDisplayMode(.large) - } label: { - let address = parseServerAddress(srv.server) - HStack { - Group { - if let address = address { - if !address.valid || address.serverProtocol != serverProtocol { - invalidServer() - } else if !uniqueAddress(srv, address) { - Image(systemName: "exclamationmark.circle").foregroundColor(.red) - } else if !srv.enabled { - Image(systemName: "slash.circle").foregroundColor(theme.colors.secondary) - } else { - showTestStatus(server: srv) - } - } else { - invalidServer() - } - } - .frame(width: 16, alignment: .center) - .padding(.trailing, 4) - - let v = Text(address?.hostnames.first ?? srv.server).lineLimit(1) - if srv.enabled { - v - } else { - v.foregroundColor(theme.colors.secondary) - } - } } } + private func newServerDestinationView() -> some View { + NewServerView( + userServers: $userServers, + serverErrors: $serverErrors + ) + .navigationTitle("New server") + .navigationBarTitleDisplayMode(.large) + .modifier(ThemedBackground(grouped: true)) + } + func howToButton() -> some View { Button { DispatchQueue.main.async { @@ -255,33 +183,114 @@ struct ProtocolServersView: View { } } } +} + +struct ProtocolServerViewLink: View { + @EnvironmentObject var theme: AppTheme + @Binding var userServers: [UserOperatorServers] + @Binding var serverErrors: [UserServersError] + var duplicateHosts: Set + @Binding var server: UserServer + var serverProtocol: ServerProtocol + var backLabel: LocalizedStringKey + @Binding var selectedServer: String? + + var body: some View { + let proto = serverProtocol.rawValue.uppercased() + + NavigationLink(tag: server.id, selection: $selectedServer) { + ProtocolServerView( + userServers: $userServers, + serverErrors: $serverErrors, + server: $server, + serverToEdit: server, + backLabel: backLabel + ) + .navigationBarTitle("\(proto) server") + .modifier(ThemedBackground(grouped: true)) + .navigationBarTitleDisplayMode(.large) + } label: { + let address = parseServerAddress(server.server) + HStack { + Group { + if let address = address { + if !address.valid || address.serverProtocol != serverProtocol { + invalidServer() + } else if address.hostnames.contains(where: duplicateHosts.contains) { + Image(systemName: "exclamationmark.circle").foregroundColor(.red) + } else if !server.enabled { + Image(systemName: "slash.circle").foregroundColor(theme.colors.secondary) + } else { + showTestStatus(server: server) + } + } else { + invalidServer() + } + } + .frame(width: 16, alignment: .center) + .padding(.trailing, 4) + + let v = Text(address?.hostnames.first ?? server.server).lineLimit(1) + if server.enabled { + v + } else { + v.foregroundColor(theme.colors.secondary) + } + } + } + } private func invalidServer() -> some View { Image(systemName: "exclamationmark.circle").foregroundColor(.red) } +} - private func uniqueAddress(_ s: ServerCfg, _ address: ServerAddress) -> Bool { - allServers.allSatisfy { srv in - address.hostnames.allSatisfy { host in - srv.id == s.id || !srv.server.contains(host) - } +func deleteSMPServer( + _ userServers: Binding<[UserOperatorServers]>, + _ operatorServersIndex: Int, + _ serverIndexSet: IndexSet +) { + if let idx = serverIndexSet.first { + let server = userServers[operatorServersIndex].wrappedValue.smpServers[idx] + if server.serverId == nil { + userServers[operatorServersIndex].wrappedValue.smpServers.remove(at: idx) + } else { + var updatedServer = server + updatedServer.deleted = true + userServers[operatorServersIndex].wrappedValue.smpServers[idx] = updatedServer } } +} - private func hasAllPresets() -> Bool { - presetServers.allSatisfy { hasPreset($0) } - } - - private func addAllPresets() { - for srv in presetServers { - if !hasPreset(srv) { - configuredServers.append(srv) - } +func deleteXFTPServer( + _ userServers: Binding<[UserOperatorServers]>, + _ operatorServersIndex: Int, + _ serverIndexSet: IndexSet +) { + if let idx = serverIndexSet.first { + let server = userServers[operatorServersIndex].wrappedValue.xftpServers[idx] + if server.serverId == nil { + userServers[operatorServersIndex].wrappedValue.xftpServers.remove(at: idx) + } else { + var updatedServer = server + updatedServer.deleted = true + userServers[operatorServersIndex].wrappedValue.xftpServers[idx] = updatedServer } } +} - private func hasPreset(_ srv: ServerCfg) -> Bool { - allServers.contains(where: { $0.server == srv.server }) +struct TestServersButton: View { + @Binding var smpServers: [UserServer] + @Binding var xftpServers: [UserServer] + @Binding var testing: Bool + + var body: some View { + Button("Test servers", action: testServers) + .disabled(testing || allServersDisabled) + } + + private var allServersDisabled: Bool { + smpServers.allSatisfy { !$0.enabled } && xftpServers.allSatisfy { !$0.enabled } } private func testServers() { @@ -292,68 +301,59 @@ struct ProtocolServersView: View { await MainActor.run { testing = false if !fs.isEmpty { - alert = .testsFailed(failures: fs) + let msg = fs.map { (srv, f) in + "\(srv): \(f.localizedDescription)" + }.joined(separator: "\n") + showAlert( + NSLocalizedString("Tests failed!", comment: "alert title"), + message: String.localizedStringWithFormat(NSLocalizedString("Some servers failed the test:\n%@", comment: "alert message"), msg) + ) } } } } private func resetTestStatus() { - for i in 0.. [String: ProtocolTestFailure] { var fs: [String: ProtocolTestFailure] = [:] - for i in 0..) { switch resp { case let .success(r): - if parseServerAddress(r.string) != nil { - servers.append(ServerCfg(server: r.string, preset: false, tested: nil, enabled: false)) - dismiss() - } else { - showAddressError = true - } + var server: UserServer = .empty + server.server = r.string + addServer(server, $userServers, $serverErrors, dismiss) case let .failure(e): logger.error("ScanProtocolServer.processQRCode QR code error: \(e.localizedDescription)") dismiss() @@ -54,6 +45,9 @@ struct ScanProtocolServer: View { struct ScanProtocolServer_Previews: PreviewProvider { static var previews: some View { - ScanProtocolServer(servers: Binding.constant([])) + ScanProtocolServer( + userServers: Binding.constant([UserOperatorServers.sampleDataNilOperator]), + serverErrors: Binding.constant([]) + ) } } diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index 12a982e76b..e73697e42a 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -50,6 +50,7 @@ let DEFAULT_PROFILE_IMAGE_CORNER_RADIUS = "profileImageCornerRadius" let DEFAULT_CHAT_ITEM_ROUNDNESS = "chatItemRoundness" let DEFAULT_CHAT_ITEM_TAIL = "chatItemTail" let DEFAULT_ONE_HAND_UI_CARD_SHOWN = "oneHandUICardShown" +let DEFAULT_ADDRESS_CREATION_CARD_SHOWN = "addressCreationCardShown" let DEFAULT_TOOLBAR_MATERIAL = "toolbarMaterial" let DEFAULT_CONNECT_VIA_LINK_TAB = "connectViaLinkTab" let DEFAULT_LIVE_MESSAGE_ALERT_SHOWN = "liveMessageAlertShown" @@ -107,6 +108,7 @@ let appDefaults: [String: Any] = [ DEFAULT_CHAT_ITEM_ROUNDNESS: defaultChatItemRoundness, DEFAULT_CHAT_ITEM_TAIL: true, DEFAULT_ONE_HAND_UI_CARD_SHOWN: false, + DEFAULT_ADDRESS_CREATION_CARD_SHOWN: false, DEFAULT_TOOLBAR_MATERIAL: ToolbarMaterial.defaultMaterial, DEFAULT_CONNECT_VIA_LINK_TAB: ConnectViaLinkTab.scan.rawValue, DEFAULT_LIVE_MESSAGE_ALERT_SHOWN: false, @@ -135,6 +137,7 @@ let appDefaults: [String: Any] = [ let hintDefaults = [ DEFAULT_LA_NOTICE_SHOWN, DEFAULT_ONE_HAND_UI_CARD_SHOWN, + DEFAULT_ADDRESS_CREATION_CARD_SHOWN, DEFAULT_LIVE_MESSAGE_ALERT_SHOWN, DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE, DEFAULT_SHOW_MUTE_PROFILE_ALERT, @@ -263,6 +266,10 @@ struct SettingsView: View { @EnvironmentObject var theme: AppTheme @State private var showProgress: Bool = false + @Binding var currUserServers: [UserOperatorServers] + @Binding var userServers: [UserOperatorServers] + @Binding var serverErrors: [UserServersError] + var body: some View { ZStack { settingsView() @@ -289,9 +296,13 @@ struct SettingsView: View { .disabled(chatModel.chatRunning != true) NavigationLink { - NetworkAndServers() - .navigationTitle("Network & servers") - .modifier(ThemedBackground(grouped: true)) + NetworkAndServers( + currUserServers: $currUserServers, + userServers: $userServers, + serverErrors: $serverErrors + ) + .navigationTitle("Network & servers") + .modifier(ThemedBackground(grouped: true)) } label: { settingsRow("externaldrive.connected.to.line.below", color: theme.colors.secondary) { Text("Network & servers") } } @@ -356,7 +367,7 @@ struct SettingsView: View { } } NavigationLink { - WhatsNewView(viaSettings: true) + WhatsNewView(viaSettings: true, showWhatsNew: true, showOperatorsNotice: false) .modifier(ThemedBackground()) .navigationBarTitleDisplayMode(.inline) } label: { @@ -525,7 +536,11 @@ struct SettingsView_Previews: PreviewProvider { static var previews: some View { let chatModel = ChatModel() chatModel.currentUser = User.sampleData - return SettingsView() - .environmentObject(chatModel) + return SettingsView( + currUserServers: Binding.constant([UserOperatorServers.sampleData1, UserOperatorServers.sampleDataNilOperator]), + userServers: Binding.constant([UserOperatorServers.sampleData1, UserOperatorServers.sampleDataNilOperator]), + serverErrors: Binding.constant([]) + ) + .environmentObject(chatModel) } } diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift b/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift index 15f6a1c7d7..d4bc0959c9 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift @@ -9,15 +9,47 @@ import SwiftUI struct UserAddressLearnMore: View { + @State var showCreateAddressButton = false + @State private var createAddressLinkActive = false + var body: some View { - List { - VStack(alignment: .leading, spacing: 18) { - Text("You can share your address as a link or QR code - anybody can connect to you.") - Text("You won't lose your contacts if you later delete your address.") - Text("When people request to connect, you can accept or reject it.") - Text("Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address).") + VStack { + List { + VStack(alignment: .leading, spacing: 18) { + Text("You can share your address as a link or QR code - anybody can connect to you.") + Text("You won't lose your contacts if you later delete your address.") + Text("When people request to connect, you can accept or reject it.") + Text("Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address).") + } + .listRowBackground(Color.clear) } - .listRowBackground(Color.clear) + .frame(maxHeight: .infinity) + + if showCreateAddressButton { + addressCreationButton() + .padding() + } + } + } + + private func addressCreationButton() -> some View { + ZStack { + Button { + createAddressLinkActive = true + } label: { + Text("Create SimpleX address") + } + .buttonStyle(OnboardingButtonStyle()) + + NavigationLink(isActive: $createAddressLinkActive) { + UserAddressView(autoCreate: true) + .navigationTitle("SimpleX address") + .navigationBarTitleDisplayMode(.large) + } label: { + EmptyView() + } + .frame(width: 1, height: 1) + .hidden() } } } diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift index 2469dc59db..cbc3e9b79e 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift @@ -15,6 +15,7 @@ struct UserAddressView: View { @EnvironmentObject private var chatModel: ChatModel @EnvironmentObject var theme: AppTheme @State var shareViaProfile = false + @State var autoCreate = false @State private var aas = AutoAcceptState() @State private var savedAAS = AutoAcceptState() @State private var ignoreShareViaProfileChange = false @@ -67,6 +68,11 @@ struct UserAddressView: View { } } } + .onAppear { + if chatModel.userAddress == nil, autoCreate { + createAddress() + } + } } @Namespace private var bottomID @@ -212,26 +218,30 @@ struct UserAddressView: View { private func createAddressButton() -> some View { Button { - progressIndicator = true - Task { - do { - let connReqContact = try await apiCreateUserAddress() - DispatchQueue.main.async { - chatModel.userAddress = UserContactLink(connReqContact: connReqContact) - alert = .shareOnCreate - progressIndicator = false - } - } catch let error { - logger.error("UserAddressView apiCreateUserAddress: \(responseError(error))") - let a = getErrorAlert(error, "Error creating address") - alert = .error(title: a.title, error: a.message) - await MainActor.run { progressIndicator = false } - } - } + createAddress() } label: { Label("Create SimpleX address", systemImage: "qrcode") } } + + private func createAddress() { + progressIndicator = true + Task { + do { + let connReqContact = try await apiCreateUserAddress() + DispatchQueue.main.async { + chatModel.userAddress = UserContactLink(connReqContact: connReqContact) + alert = .shareOnCreate + progressIndicator = false + } + } catch let error { + logger.error("UserAddressView apiCreateUserAddress: \(responseError(error))") + let a = getErrorAlert(error, "Error creating address") + alert = .error(title: a.title, error: a.message) + await MainActor.run { progressIndicator = false } + } + } + } private func deleteAddressButton() -> some View { Button(role: .destructive) { diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 8a63cd3309..8dc195e17f 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -144,20 +144,22 @@ 5CFE0922282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; }; 640417CD2B29B8C200CCB412 /* NewChatMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640417CB2B29B8C200CCB412 /* NewChatMenuButton.swift */; }; 640417CE2B29B8C200CCB412 /* NewChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640417CC2B29B8C200CCB412 /* NewChatView.swift */; }; + 640743612CD360E600158442 /* ChooseServerOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640743602CD360E600158442 /* ChooseServerOperators.swift */; }; 6407BA83295DA85D0082BA18 /* CIInvalidJSONView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */; }; 6419EC562AB8BC8B004A607A /* ContextInvitingContactMemberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */; }; 6419EC582AB97507004A607A /* CIMemberCreatedContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */; }; + 642BA82D2CE50495005E9412 /* NewServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 642BA82C2CE50495005E9412 /* NewServerView.swift */; }; + 642BA8332CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH.a */; }; + 642BA8342CEB3D4B005E9412 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA82F2CEB3D4B005E9412 /* libffi.a */; }; + 642BA8352CEB3D4B005E9412 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA8302CEB3D4B005E9412 /* libgmp.a */; }; + 642BA8362CEB3D4B005E9412 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA8312CEB3D4B005E9412 /* libgmpxx.a */; }; + 642BA8372CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH-ghc9.6.3.a */; }; 6432857C2925443C00FBE5C8 /* GroupPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */; }; - 643B3B452CCBEB080083A2CF /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B402CCBEB080083A2CF /* libgmpxx.a */; }; - 643B3B462CCBEB080083A2CF /* libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs-ghc9.6.3.a */; }; - 643B3B472CCBEB080083A2CF /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B422CCBEB080083A2CF /* libffi.a */; }; - 643B3B482CCBEB080083A2CF /* libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs.a */; }; - 643B3B492CCBEB080083A2CF /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 643B3B442CCBEB080083A2CF /* libgmp.a */; }; + 643B3B4E2CCFD6400083A2CF /* OperatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 643B3B4D2CCFD6400083A2CF /* OperatorView.swift */; }; 6440CA00288857A10062C672 /* CIEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440C9FF288857A10062C672 /* CIEventView.swift */; }; 6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */; }; 6442E0BA287F169300CEC0F9 /* AddGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6442E0B9287F169300CEC0F9 /* AddGroupView.swift */; }; 6442E0BE2880182D00CEC0F9 /* GroupChatInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6442E0BD2880182D00CEC0F9 /* GroupChatInfoView.swift */; }; - 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 */; }; 644EFFDE292BCD9D00525D5B /* ComposeVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */; }; @@ -200,7 +202,9 @@ 8CC4ED902BD7B8530078AEE8 /* CallAudioDeviceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC4ED8F2BD7B8530078AEE8 /* CallAudioDeviceManager.swift */; }; 8CC956EE2BC0041000412A11 /* NetworkObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC956ED2BC0041000412A11 /* NetworkObserver.swift */; }; 8CE848A32C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CE848A22C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift */; }; + B73EFE532CE5FA3500C778EA /* CreateSimpleXAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = B73EFE522CE5FA3500C778EA /* CreateSimpleXAddress.swift */; }; B76E6C312C5C41D900EC11AA /* ContactListNavLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = B76E6C302C5C41D900EC11AA /* ContactListNavLink.swift */; }; + B79ADAFF2CE4EF930083DFFD /* AddressCreationCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = B79ADAFE2CE4EF930083DFFD /* AddressCreationCard.swift */; }; CE176F202C87014C00145DBC /* InvertedForegroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE176F1F2C87014C00145DBC /* InvertedForegroundStyle.swift */; }; CE1EB0E42C459A660099D896 /* ShareAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1EB0E32C459A660099D896 /* ShareAPI.swift */; }; CE2AD9CE2C452A4D00E844E3 /* ChatUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2AD9CD2C452A4D00E844E3 /* ChatUtils.swift */; }; @@ -436,7 +440,7 @@ 5CB634AC29E46CF70066AD6B /* LocalAuthView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAuthView.swift; sourceTree = ""; }; 5CB634AE29E4BB7D0066AD6B /* SetAppPasscodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetAppPasscodeView.swift; sourceTree = ""; }; 5CB634B029E5EFEA0066AD6B /* PasscodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasscodeView.swift; sourceTree = ""; }; - 5CB924D627A8563F00ACCCDD /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + 5CB924D627A8563F00ACCCDD /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; wrapsLines = 0; }; 5CB924E027A867BA00ACCCDD /* UserProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfile.swift; sourceTree = ""; }; 5CB9250C27A9432000ACCCDD /* ChatListNavLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListNavLink.swift; sourceTree = ""; }; 5CBD285529565CAE00EC2CF4 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; @@ -487,20 +491,22 @@ 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ZoomableScrollView.swift; path = Shared/Views/ZoomableScrollView.swift; sourceTree = SOURCE_ROOT; }; 640417CB2B29B8C200CCB412 /* NewChatMenuButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewChatMenuButton.swift; sourceTree = ""; }; 640417CC2B29B8C200CCB412 /* NewChatView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewChatView.swift; sourceTree = ""; }; + 640743602CD360E600158442 /* ChooseServerOperators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChooseServerOperators.swift; sourceTree = ""; }; 6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIInvalidJSONView.swift; sourceTree = ""; }; 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextInvitingContactMemberView.swift; sourceTree = ""; }; 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMemberCreatedContactView.swift; sourceTree = ""; }; + 642BA82C2CE50495005E9412 /* NewServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewServerView.swift; sourceTree = ""; }; + 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH.a"; sourceTree = ""; }; + 642BA82F2CEB3D4B005E9412 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + 642BA8302CEB3D4B005E9412 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + 642BA8312CEB3D4B005E9412 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH-ghc9.6.3.a"; sourceTree = ""; }; 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupPreferencesView.swift; sourceTree = ""; }; - 643B3B402CCBEB080083A2CF /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgmpxx.a; path = Libraries/libgmpxx.a; sourceTree = ""; }; - 643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs-ghc9.6.3.a"; path = "Libraries/libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs-ghc9.6.3.a"; sourceTree = ""; }; - 643B3B422CCBEB080083A2CF /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libffi.a; path = Libraries/libffi.a; sourceTree = ""; }; - 643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs.a"; path = "Libraries/libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs.a"; sourceTree = ""; }; - 643B3B442CCBEB080083A2CF /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgmp.a; path = Libraries/libgmp.a; sourceTree = ""; }; + 643B3B4D2CCFD6400083A2CF /* OperatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperatorView.swift; sourceTree = ""; }; 6440C9FF288857A10062C672 /* CIEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIEventView.swift; sourceTree = ""; }; 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupMembersView.swift; sourceTree = ""; }; 6442E0B9287F169300CEC0F9 /* AddGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupView.swift; sourceTree = ""; }; 6442E0BD2880182D00CEC0F9 /* GroupChatInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupChatInfoView.swift; sourceTree = ""; }; - 64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSimpleXAddress.swift; sourceTree = ""; }; 64466DCB29FFE3E800E3D48D /* MailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailView.swift; sourceTree = ""; }; 6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupLinkView.swift; sourceTree = ""; }; 644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeVoiceView.swift; sourceTree = ""; }; @@ -544,7 +550,9 @@ 8CC4ED8F2BD7B8530078AEE8 /* CallAudioDeviceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallAudioDeviceManager.swift; sourceTree = ""; }; 8CC956ED2BC0041000412A11 /* NetworkObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkObserver.swift; sourceTree = ""; }; 8CE848A22C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableChatItemToolbars.swift; sourceTree = ""; }; + B73EFE522CE5FA3500C778EA /* CreateSimpleXAddress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateSimpleXAddress.swift; sourceTree = ""; }; B76E6C302C5C41D900EC11AA /* ContactListNavLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactListNavLink.swift; sourceTree = ""; }; + B79ADAFE2CE4EF930083DFFD /* AddressCreationCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressCreationCard.swift; sourceTree = ""; }; CE176F1F2C87014C00145DBC /* InvertedForegroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvertedForegroundStyle.swift; sourceTree = ""; }; CE1EB0E32C459A660099D896 /* ShareAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareAPI.swift; sourceTree = ""; }; CE2AD9CD2C452A4D00E844E3 /* ChatUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatUtils.swift; sourceTree = ""; }; @@ -657,14 +665,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 643B3B452CCBEB080083A2CF /* libgmpxx.a in Frameworks */, - 643B3B472CCBEB080083A2CF /* libffi.a in Frameworks */, - 643B3B492CCBEB080083A2CF /* libgmp.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - 643B3B482CCBEB080083A2CF /* libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs.a in Frameworks */, - 643B3B462CCBEB080083A2CF /* libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs-ghc9.6.3.a in Frameworks */, + 642BA8342CEB3D4B005E9412 /* libffi.a in Frameworks */, + 642BA8352CEB3D4B005E9412 /* libgmp.a in Frameworks */, + 642BA8372CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH-ghc9.6.3.a in Frameworks */, + 642BA8332CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH.a in Frameworks */, + 642BA8362CEB3D4B005E9412 /* libgmpxx.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -741,6 +749,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( + 642BA82F2CEB3D4B005E9412 /* libffi.a */, + 642BA8302CEB3D4B005E9412 /* libgmp.a */, + 642BA8312CEB3D4B005E9412 /* libgmpxx.a */, + 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH-ghc9.6.3.a */, + 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH.a */, ); path = Libraries; sourceTree = ""; @@ -812,11 +825,6 @@ 5CC2C0FA2809BF11000C35E3 /* Localizable.strings */, 5C422A7C27A9A6FA0097A1E1 /* SimpleX (iOS).entitlements */, 5C764E5C279C70B7000C6508 /* Libraries */, - 643B3B422CCBEB080083A2CF /* libffi.a */, - 643B3B442CCBEB080083A2CF /* libgmp.a */, - 643B3B402CCBEB080083A2CF /* libgmpxx.a */, - 643B3B412CCBEB080083A2CF /* libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs-ghc9.6.3.a */, - 643B3B432CCBEB080083A2CF /* libHSsimplex-chat-6.2.0.0-ICCEDdz3q5b2XylcUTCFFs.a */, 5CA059C2279559F40002BEB4 /* Shared */, 5CDCAD462818589900503DA2 /* SimpleX NSE */, CEE723A82C3BD3D70009AE93 /* SimpleX SE */, @@ -875,13 +883,15 @@ 5CB0BA8C282711BC00B3292C /* Onboarding */ = { isa = PBXGroup; children = ( + B73EFE522CE5FA3500C778EA /* CreateSimpleXAddress.swift */, 5CB0BA8D2827126500B3292C /* OnboardingView.swift */, 5CB0BA8F282713D900B3292C /* SimpleXInfo.swift */, 5CB0BA992827FD8800B3292C /* HowItWorks.swift */, 5CB0BA91282713FD00B3292C /* CreateProfile.swift */, - 64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */, 5C9A5BDA2871E05400A5B906 /* SetNotificationsMode.swift */, 5CBD285B29575B8E00EC2CF4 /* WhatsNewView.swift */, + 640743602CD360E600158442 /* ChooseServerOperators.swift */, + B79ADAFE2CE4EF930083DFFD /* AddressCreationCard.swift */, ); path = Onboarding; sourceTree = ""; @@ -1056,8 +1066,10 @@ isa = PBXGroup; children = ( 5C9329402929248A0090FFF9 /* ScanProtocolServer.swift */, + 642BA82C2CE50495005E9412 /* NewServerView.swift */, 5C93293029239BED0090FFF9 /* ProtocolServerView.swift */, 5C93292E29239A170090FFF9 /* ProtocolServersView.swift */, + 643B3B4D2CCFD6400083A2CF /* OperatorView.swift */, 5C9C2DA6289957AE00CC63B1 /* AdvancedNetworkSettings.swift */, 5C9C2DA82899DA6F00CC63B1 /* NetworkAndServers.swift */, ); @@ -1383,10 +1395,12 @@ 64C06EB52A0A4A7C00792D4D /* ChatItemInfoView.swift in Sources */, 640417CE2B29B8C200CCB412 /* NewChatView.swift in Sources */, 6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */, + 640743612CD360E600158442 /* ChooseServerOperators.swift in Sources */, 5C3F1D58284363C400EC8A82 /* PrivacySettings.swift in Sources */, 5C55A923283CEDE600C4E99E /* SoundPlayer.swift in Sources */, 5C93292F29239A170090FFF9 /* ProtocolServersView.swift in Sources */, 5CB924D727A8563F00ACCCDD /* SettingsView.swift in Sources */, + B79ADAFF2CE4EF930083DFFD /* AddressCreationCard.swift in Sources */, 5CEACCE327DE9246000BD591 /* ComposeView.swift in Sources */, E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */, 5C65DAF929D0CC20003CEE45 /* DeveloperView.swift in Sources */, @@ -1413,12 +1427,12 @@ 644EFFE2292D089800525D5B /* FramedCIVoiceView.swift in Sources */, 5C4B3B0A285FB130003915F2 /* DatabaseView.swift in Sources */, 5CB2084F28DA4B4800D024EC /* RTCServers.swift in Sources */, + B73EFE532CE5FA3500C778EA /* CreateSimpleXAddress.swift in Sources */, 5CB634AF29E4BB7D0066AD6B /* SetAppPasscodeView.swift in Sources */, 5C10D88828EED12E00E58BF0 /* ContactConnectionInfo.swift in Sources */, 5CBE6C12294487F7002D9531 /* VerifyCodeView.swift in Sources */, 3CDBCF4227FAE51000354CDD /* ComposeLinkView.swift in Sources */, 648679AB2BC96A74006456E7 /* ChatItemForwardingView.swift in Sources */, - 64466DC829FC2B3B00E3D48D /* CreateSimpleXAddress.swift in Sources */, 3CDBCF4827FF621E00354CDD /* CILinkView.swift in Sources */, 5C7505A827B6D34800BE3227 /* ChatInfoToolbar.swift in Sources */, B76E6C312C5C41D900EC11AA /* ContactListNavLink.swift in Sources */, @@ -1536,7 +1550,9 @@ 5CB634AD29E46CF70066AD6B /* LocalAuthView.swift in Sources */, 18415FEFE153C5920BFB7828 /* GroupWelcomeView.swift in Sources */, 18415F9A2D551F9757DA4654 /* CIVideoView.swift in Sources */, + 642BA82D2CE50495005E9412 /* NewServerView.swift in Sources */, 184158C131FDB829D8A117EA /* VideoPlayerView.swift in Sources */, + 643B3B4E2CCFD6400083A2CF /* OperatorView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 3c9b91fa0b..5470059e92 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -72,9 +72,15 @@ public enum ChatCommand { case apiGetGroupLink(groupId: Int64) case apiCreateMemberContact(groupId: Int64, groupMemberId: Int64) case apiSendMemberContactInvitation(contactId: Int64, msg: MsgContent) - case apiGetUserProtoServers(userId: Int64, serverProtocol: ServerProtocol) - case apiSetUserProtoServers(userId: Int64, serverProtocol: ServerProtocol, servers: [ServerCfg]) case apiTestProtoServer(userId: Int64, server: String) + case apiGetServerOperators + case apiSetServerOperators(operators: [ServerOperator]) + case apiGetUserServers(userId: Int64) + case apiSetUserServers(userId: Int64, userServers: [UserOperatorServers]) + case apiValidateServers(userId: Int64, userServers: [UserOperatorServers]) + case apiGetUsageConditions + case apiSetConditionsNotified(conditionsId: Int64) + case apiAcceptConditions(conditionsId: Int64, operatorIds: [Int64]) case apiSetChatItemTTL(userId: Int64, seconds: Int64?) case apiGetChatItemTTL(userId: Int64) case apiSetNetworkConfig(networkConfig: NetCfg) @@ -231,9 +237,15 @@ public enum ChatCommand { case let .apiGetGroupLink(groupId): return "/_get link #\(groupId)" case let .apiCreateMemberContact(groupId, groupMemberId): return "/_create member contact #\(groupId) \(groupMemberId)" case let .apiSendMemberContactInvitation(contactId, mc): return "/_invite member contact @\(contactId) \(mc.cmdString)" - case let .apiGetUserProtoServers(userId, serverProtocol): return "/_servers \(userId) \(serverProtocol)" - case let .apiSetUserProtoServers(userId, serverProtocol, servers): return "/_servers \(userId) \(serverProtocol) \(protoServersStr(servers))" case let .apiTestProtoServer(userId, server): return "/_server test \(userId) \(server)" + case .apiGetServerOperators: return "/_operators" + case let .apiSetServerOperators(operators): return "/_operators \(encodeJSON(operators))" + case let .apiGetUserServers(userId): return "/_servers \(userId)" + case let .apiSetUserServers(userId, userServers): return "/_servers \(userId) \(encodeJSON(userServers))" + case let .apiValidateServers(userId, userServers): return "/_validate_servers \(userId) \(encodeJSON(userServers))" + case .apiGetUsageConditions: return "/_conditions" + case let .apiSetConditionsNotified(conditionsId): return "/_conditions_notified \(conditionsId)" + case let .apiAcceptConditions(conditionsId, operatorIds): return "/_accept_conditions \(conditionsId) \(joinedIds(operatorIds))" case let .apiSetChatItemTTL(userId, seconds): return "/_ttl \(userId) \(chatItemTTLStr(seconds: seconds))" case let .apiGetChatItemTTL(userId): return "/_ttl \(userId)" case let .apiSetNetworkConfig(networkConfig): return "/_network \(encodeJSON(networkConfig))" @@ -386,9 +398,15 @@ public enum ChatCommand { case .apiGetGroupLink: return "apiGetGroupLink" case .apiCreateMemberContact: return "apiCreateMemberContact" case .apiSendMemberContactInvitation: return "apiSendMemberContactInvitation" - case .apiGetUserProtoServers: return "apiGetUserProtoServers" - case .apiSetUserProtoServers: return "apiSetUserProtoServers" case .apiTestProtoServer: return "apiTestProtoServer" + case .apiGetServerOperators: return "apiGetServerOperators" + case .apiSetServerOperators: return "apiSetServerOperators" + case .apiGetUserServers: return "apiGetUserServers" + case .apiSetUserServers: return "apiSetUserServers" + case .apiValidateServers: return "apiValidateServers" + case .apiGetUsageConditions: return "apiGetUsageConditions" + case .apiSetConditionsNotified: return "apiSetConditionsNotified" + case .apiAcceptConditions: return "apiAcceptConditions" case .apiSetChatItemTTL: return "apiSetChatItemTTL" case .apiGetChatItemTTL: return "apiGetChatItemTTL" case .apiSetNetworkConfig: return "apiSetNetworkConfig" @@ -475,10 +493,6 @@ public enum ChatCommand { func joinedIds(_ ids: [Int64]) -> String { ids.map { "\($0)" }.joined(separator: ",") } - - func protoServersStr(_ servers: [ServerCfg]) -> String { - encodeJSON(ProtoServersConfig(servers: servers)) - } func chatItemTTLStr(seconds: Int64?) -> String { if let seconds = seconds { @@ -548,8 +562,11 @@ public enum ChatResponse: Decodable, Error { case apiChats(user: UserRef, chats: [ChatData]) case apiChat(user: UserRef, chat: ChatData) case chatItemInfo(user: UserRef, chatItem: AChatItem, chatItemInfo: ChatItemInfo) - case userProtoServers(user: UserRef, servers: UserProtoServers) case serverTestResult(user: UserRef, testServer: String, testFailure: ProtocolTestFailure?) + case serverOperatorConditions(conditions: ServerOperatorConditions) + case userServers(user: UserRef, userServers: [UserOperatorServers]) + case userServersValidation(user: UserRef, serverErrors: [UserServersError]) + case usageConditions(usageConditions: UsageConditions, conditionsText: String, acceptedConditions: UsageConditions?) case chatItemTTL(user: UserRef, chatItemTTL: Int64?) case networkConfig(networkConfig: NetCfg) case contactInfo(user: UserRef, contact: Contact, connectionStats_: ConnectionStats?, customUserProfile: Profile?) @@ -721,8 +738,11 @@ public enum ChatResponse: Decodable, Error { case .apiChats: return "apiChats" case .apiChat: return "apiChat" case .chatItemInfo: return "chatItemInfo" - case .userProtoServers: return "userProtoServers" case .serverTestResult: return "serverTestResult" + case .serverOperatorConditions: return "serverOperators" + case .userServers: return "userServers" + case .userServersValidation: return "userServersValidation" + case .usageConditions: return "usageConditions" case .chatItemTTL: return "chatItemTTL" case .networkConfig: return "networkConfig" case .contactInfo: return "contactInfo" @@ -890,8 +910,11 @@ public enum ChatResponse: Decodable, Error { case let .apiChats(u, chats): return withUser(u, String(describing: chats)) case let .apiChat(u, chat): return withUser(u, String(describing: chat)) case let .chatItemInfo(u, chatItem, chatItemInfo): return withUser(u, "chatItem: \(String(describing: chatItem))\nchatItemInfo: \(String(describing: chatItemInfo))") - case let .userProtoServers(u, servers): return withUser(u, "servers: \(String(describing: servers))") case let .serverTestResult(u, server, testFailure): return withUser(u, "server: \(server)\nresult: \(String(describing: testFailure))") + case let .serverOperatorConditions(conditions): return "conditions: \(String(describing: conditions))" + case let .userServers(u, userServers): return withUser(u, "userServers: \(String(describing: userServers))") + case let .userServersValidation(u, serverErrors): return withUser(u, "serverErrors: \(String(describing: serverErrors))") + case let .usageConditions(usageConditions, _, acceptedConditions): return "usageConditions: \(String(describing: usageConditions))\nacceptedConditions: \(String(describing: acceptedConditions))" 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))") @@ -1175,86 +1198,426 @@ public struct DBEncryptionConfig: Codable { public var newKey: String } -struct SMPServersConfig: Encodable { - var smpServers: [ServerCfg] -} - public enum ServerProtocol: String, Decodable { case smp case xftp } -public struct ProtoServersConfig: Codable { - public var servers: [ServerCfg] +public enum OperatorTag: String, Codable { + case simplex = "simplex" + case flux = "flux" + case xyz = "xyz" + case demo = "demo" } -public struct UserProtoServers: Decodable { - public var serverProtocol: ServerProtocol - public var protoServers: [ServerCfg] - public var presetServers: [ServerCfg] +public struct ServerOperatorInfo: Decodable { + public var description: [String] + public var website: String + public var logo: String + public var largeLogo: String + public var logoDarkMode: String + public var largeLogoDarkMode: String } -public struct ServerCfg: Identifiable, Equatable, Codable, Hashable { +public let operatorsInfo: Dictionary = [ + .simplex: ServerOperatorInfo( + description: ["SimpleX Chat preset servers"], + website: "https://simplex.chat", + logo: "decentralized", + largeLogo: "logo", + logoDarkMode: "decentralized-light", + largeLogoDarkMode: "logo-light" + ), + .flux: ServerOperatorInfo( + description: [ + "Flux is the largest decentralized cloud infrastructure, leveraging a global network of user-operated computational nodes.", + "Flux offers a powerful, scalable, and affordable platform designed to support individuals, businesses, and cutting-edge technologies like AI. With high uptime and worldwide distribution, Flux ensures reliable, accessible cloud computing for all." + ], + website: "https://runonflux.com", + logo: "flux_logo_symbol", + largeLogo: "flux_logo", + logoDarkMode: "flux_logo_symbol", + largeLogoDarkMode: "flux_logo-light" + ), + .xyz: ServerOperatorInfo( + description: ["XYZ servers"], + website: "XYZ website", + logo: "shield", + largeLogo: "logo", + logoDarkMode: "shield", + largeLogoDarkMode: "logo-light" + ), + .demo: ServerOperatorInfo( + description: ["Demo operator"], + website: "Demo website", + logo: "decentralized", + largeLogo: "logo", + logoDarkMode: "decentralized-light", + largeLogoDarkMode: "logo-light" + ) +] + +public struct UsageConditions: Decodable { + public var conditionsId: Int64 + public var conditionsCommit: String + public var notifiedAt: Date? + public var createdAt: Date + + public static var sampleData = UsageConditions( + conditionsId: 1, + conditionsCommit: "11a44dc1fd461a93079f897048b46998db55da5c", + notifiedAt: nil, + createdAt: Date.now + ) +} + +public enum UsageConditionsAction: Decodable { + case review(operators: [ServerOperator], deadline: Date?, showNotice: Bool) + case accepted(operators: [ServerOperator]) + + public var showNotice: Bool { + switch self { + case let .review(_, _, showNotice): showNotice + case .accepted: false + } + } +} + +public struct ServerOperatorConditions: Decodable { + public var serverOperators: [ServerOperator] + public var currentConditions: UsageConditions + public var conditionsAction: UsageConditionsAction? + + public static var empty = ServerOperatorConditions( + serverOperators: [], + currentConditions: UsageConditions(conditionsId: 0, conditionsCommit: "empty", notifiedAt: nil, createdAt: .now), + conditionsAction: nil + ) +} + +public enum ConditionsAcceptance: Equatable, Codable, Hashable { + case accepted(acceptedAt: Date?) + // If deadline is present, it means there's a grace period to review and accept conditions during which user can continue to use the operator. + // No deadline indicates it's required to accept conditions for the operator to start using it. + case required(deadline: Date?) + + public var conditionsAccepted: Bool { + switch self { + case .accepted: true + case .required: false + } + } + + public var usageAllowed: Bool { + switch self { + case .accepted: true + case let .required(deadline): deadline != nil + } + } +} + +public struct ServerOperator: Identifiable, Equatable, Codable { + public var operatorId: Int64 + public var operatorTag: OperatorTag? + public var tradeName: String + public var legalName: String? + public var serverDomains: [String] + public var conditionsAcceptance: ConditionsAcceptance + public var enabled: Bool + public var smpRoles: ServerRoles + public var xftpRoles: ServerRoles + + public var id: Int64 { operatorId } + + public static func == (l: ServerOperator, r: ServerOperator) -> Bool { + l.operatorId == r.operatorId && l.operatorTag == r.operatorTag && l.tradeName == r.tradeName && l.legalName == r.legalName && + l.serverDomains == r.serverDomains && l.conditionsAcceptance == r.conditionsAcceptance && l.enabled == r.enabled && + l.smpRoles == r.smpRoles && l.xftpRoles == r.xftpRoles + } + + public var legalName_: String { + legalName ?? tradeName + } + + public var info: ServerOperatorInfo { + return if let operatorTag = operatorTag { + operatorsInfo[operatorTag] ?? ServerOperator.dummyOperatorInfo + } else { + ServerOperator.dummyOperatorInfo + } + } + + public static let dummyOperatorInfo = ServerOperatorInfo( + description: ["Default"], + website: "Default", + logo: "decentralized", + largeLogo: "logo", + logoDarkMode: "decentralized-light", + largeLogoDarkMode: "logo-light" + ) + + public func logo(_ colorScheme: ColorScheme) -> String { + colorScheme == .light ? info.logo : info.logoDarkMode + } + + public func largeLogo(_ colorScheme: ColorScheme) -> String { + colorScheme == .light ? info.largeLogo : info.largeLogoDarkMode + } + + public static var sampleData1 = ServerOperator( + operatorId: 1, + operatorTag: .simplex, + tradeName: "SimpleX Chat", + legalName: "SimpleX Chat Ltd", + serverDomains: ["simplex.im"], + conditionsAcceptance: .accepted(acceptedAt: nil), + enabled: true, + smpRoles: ServerRoles(storage: true, proxy: true), + xftpRoles: ServerRoles(storage: true, proxy: true) + ) + + public static var sampleData2 = ServerOperator( + operatorId: 2, + operatorTag: .xyz, + tradeName: "XYZ", + legalName: nil, + serverDomains: ["xyz.com"], + conditionsAcceptance: .required(deadline: nil), + enabled: false, + smpRoles: ServerRoles(storage: false, proxy: true), + xftpRoles: ServerRoles(storage: false, proxy: true) + ) + + public static var sampleData3 = ServerOperator( + operatorId: 3, + operatorTag: .demo, + tradeName: "Demo", + legalName: nil, + serverDomains: ["demo.com"], + conditionsAcceptance: .required(deadline: nil), + enabled: false, + smpRoles: ServerRoles(storage: true, proxy: false), + xftpRoles: ServerRoles(storage: true, proxy: false) + ) +} + +public struct ServerRoles: Equatable, Codable { + public var storage: Bool + public var proxy: Bool +} + +public struct UserOperatorServers: Identifiable, Equatable, Codable { + public var `operator`: ServerOperator? + public var smpServers: [UserServer] + public var xftpServers: [UserServer] + + public var id: String { + if let op = self.operator { + "\(op.operatorId)" + } else { + "nil operator" + } + } + + public var operator_: ServerOperator { + get { + self.operator ?? ServerOperator( + operatorId: 0, + operatorTag: nil, + tradeName: "", + legalName: "", + serverDomains: [], + conditionsAcceptance: .accepted(acceptedAt: nil), + enabled: false, + smpRoles: ServerRoles(storage: true, proxy: true), + xftpRoles: ServerRoles(storage: true, proxy: true) + ) + } + set { `operator` = newValue } + } + + public static var sampleData1 = UserOperatorServers( + operator: ServerOperator.sampleData1, + smpServers: [UserServer.sampleData.preset], + xftpServers: [UserServer.sampleData.xftpPreset] + ) + + public static var sampleDataNilOperator = UserOperatorServers( + operator: nil, + smpServers: [UserServer.sampleData.preset], + xftpServers: [UserServer.sampleData.xftpPreset] + ) +} + +public enum UserServersError: Decodable { + case noServers(protocol: ServerProtocol, user: UserRef?) + case storageMissing(protocol: ServerProtocol, user: UserRef?) + case proxyMissing(protocol: ServerProtocol, user: UserRef?) + case invalidServer(protocol: ServerProtocol, invalidServer: String) + case duplicateServer(protocol: ServerProtocol, duplicateServer: String, duplicateHost: String) + + public var globalError: String? { + switch self { + case let .noServers(`protocol`, _): + switch `protocol` { + case .smp: return globalSMPError + case .xftp: return globalXFTPError + } + case let .storageMissing(`protocol`, _): + switch `protocol` { + case .smp: return globalSMPError + case .xftp: return globalXFTPError + } + case let .proxyMissing(`protocol`, _): + switch `protocol` { + case .smp: return globalSMPError + case .xftp: return globalXFTPError + } + default: return nil + } + } + + public var globalSMPError: String? { + switch self { + case let .noServers(.smp, user): + let text = NSLocalizedString("No message servers.", comment: "servers error") + if let user = user { + return userStr(user) + " " + text + } else { + return text + } + case let .storageMissing(.smp, user): + let text = NSLocalizedString("No servers to receive messages.", comment: "servers error") + if let user = user { + return userStr(user) + " " + text + } else { + return text + } + case let .proxyMissing(.smp, user): + let text = NSLocalizedString("No servers for private message routing.", comment: "servers error") + if let user = user { + return userStr(user) + " " + text + } else { + return text + } + default: + return nil + } + } + + public var globalXFTPError: String? { + switch self { + case let .noServers(.xftp, user): + let text = NSLocalizedString("No media & file servers.", comment: "servers error") + if let user = user { + return userStr(user) + " " + text + } else { + return text + } + case let .storageMissing(.xftp, user): + let text = NSLocalizedString("No servers to send files.", comment: "servers error") + if let user = user { + return userStr(user) + " " + text + } else { + return text + } + case let .proxyMissing(.xftp, user): + let text = NSLocalizedString("No servers to receive files.", comment: "servers error") + if let user = user { + return userStr(user) + " " + text + } else { + return text + } + default: + return nil + } + } + + private func userStr(_ user: UserRef) -> String { + String.localizedStringWithFormat(NSLocalizedString("For chat profile %@:", comment: "servers error"), user.localDisplayName) + } +} + +public struct UserServer: Identifiable, Equatable, Codable, Hashable { + public var serverId: Int64? public var server: String public var preset: Bool public var tested: Bool? public var enabled: Bool + public var deleted: Bool var createdAt = Date() -// public var sendEnabled: Bool // can we potentially want to prevent sending on the servers we use to receive? -// Even if we don't see the use case, it's probably better to allow it in the model -// In any case, "trusted/known" servers are out of scope of this change - public init(server: String, preset: Bool, tested: Bool?, enabled: Bool) { + public init(serverId: Int64?, server: String, preset: Bool, tested: Bool?, enabled: Bool, deleted: Bool) { + self.serverId = serverId self.server = server self.preset = preset self.tested = tested self.enabled = enabled + self.deleted = deleted } - public static func == (l: ServerCfg, r: ServerCfg) -> Bool { - l.server == r.server && l.preset == r.preset && l.tested == r.tested && l.enabled == r.enabled + public static func == (l: UserServer, r: UserServer) -> Bool { + l.serverId == r.serverId && l.server == r.server && l.preset == r.preset && l.tested == r.tested && + l.enabled == r.enabled && l.deleted == r.deleted } public var id: String { "\(server) \(createdAt)" } - public static var empty = ServerCfg(server: "", preset: false, tested: nil, enabled: false) + public static var empty = UserServer(serverId: nil, server: "", preset: false, tested: nil, enabled: false, deleted: false) public var isEmpty: Bool { server.trimmingCharacters(in: .whitespaces) == "" } public struct SampleData { - public var preset: ServerCfg - public var custom: ServerCfg - public var untested: ServerCfg + public var preset: UserServer + public var custom: UserServer + public var untested: UserServer + public var xftpPreset: UserServer } public static var sampleData = SampleData( - preset: ServerCfg( + preset: UserServer( + serverId: 1, server: "smp://abcd@smp8.simplex.im", preset: true, tested: true, - enabled: true + enabled: true, + deleted: false ), - custom: ServerCfg( + custom: UserServer( + serverId: 2, server: "smp://abcd@smp9.simplex.im", preset: false, tested: false, - enabled: false + enabled: false, + deleted: false ), - untested: ServerCfg( + untested: UserServer( + serverId: 3, server: "smp://abcd@smp10.simplex.im", preset: false, tested: nil, - enabled: true + enabled: true, + deleted: false + ), + xftpPreset: UserServer( + serverId: 4, + server: "xftp://abcd@xftp8.simplex.im", + preset: true, + tested: true, + enabled: true, + deleted: false ) ) enum CodingKeys: CodingKey { + case serverId case server case preset case tested case enabled + case deleted } } From 4b9c618ae36b7751217d59cbfb65352c7289c8d1 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 19 Nov 2024 14:10:33 +0000 Subject: [PATCH 082/567] core: remove a separate type to validate servers with invalid addresses (they are prevented by the UI) (#5211) --- src/Simplex/Chat/Controller.hs | 2 +- src/Simplex/Chat/Operators.hs | 67 +++++++--------------------------- tests/OperatorTests.hs | 13 ------- 3 files changed, 15 insertions(+), 67 deletions(-) diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index b6229e07ba..e44ea2ac18 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -360,7 +360,7 @@ data ChatCommand | APISetServerOperators (NonEmpty ServerOperator) | APIGetUserServers UserId | APISetUserServers UserId (NonEmpty UpdatedUserOperatorServers) - | APIValidateServers UserId [ValidatedUserOperatorServers] -- response is CRUserServersValidation + | APIValidateServers UserId [UpdatedUserOperatorServers] -- response is CRUserServersValidation | APIGetUsageConditions | APISetConditionsNotified Int64 | APIAcceptConditions Int64 (NonEmpty Int64) diff --git a/src/Simplex/Chat/Operators.hs b/src/Simplex/Chat/Operators.hs index 1f9b79b56b..ebe1da8176 100644 --- a/src/Simplex/Chat/Operators.hs +++ b/src/Simplex/Chat/Operators.hs @@ -24,7 +24,6 @@ import Data.Aeson (FromJSON (..), ToJSON (..)) import qualified Data.Aeson as J import qualified Data.Aeson.Encoding as JE import qualified Data.Aeson.TH as JQ -import Data.Either (partitionEithers) import Data.FileEmbed import Data.Foldable (foldMap') import Data.Functor.Identity @@ -217,32 +216,19 @@ data UpdatedUserOperatorServers = UpdatedUserOperatorServers } deriving (Show) -data ValidatedUserOperatorServers = ValidatedUserOperatorServers - { operator :: Maybe ServerOperator, - smpServers :: [AValidatedServer 'PSMP], - xftpServers :: [AValidatedServer 'PXFTP] - } - deriving (Show) - -data AValidatedServer p = forall s. AVS (SDBStored s) (ValidatedServer s p) - -deriving instance Show (AValidatedServer p) - -type ValidatedServer s p = UserServer_ s ValidatedProtoServer p - data ValidatedProtoServer p = ValidatedProtoServer {unVPS :: Either Text (ProtoServerWithAuth p)} deriving (Show) class UserServersClass u where type AServer u = (s :: ProtocolType -> Type) | s -> u operator' :: u -> Maybe ServerOperator - partitionValid :: [AServer u p] -> ([Text], [AUserServer p]) + aUserServer' :: AServer u p -> AUserServer p servers' :: UserProtocol p => SProtocolType p -> u -> [AServer u p] instance UserServersClass UserOperatorServers where - type AServer UserOperatorServers = UserServer_ 'DBStored ProtoServerWithAuth + type AServer UserOperatorServers = UserServer' 'DBStored operator' UserOperatorServers {operator} = operator - partitionValid ss = ([], map (AUS SDBStored) ss) + aUserServer' = AUS SDBStored servers' p UserOperatorServers {smpServers, xftpServers} = case p of SPSMP -> smpServers SPXFTP -> xftpServers @@ -250,24 +236,11 @@ instance UserServersClass UserOperatorServers where instance UserServersClass UpdatedUserOperatorServers where type AServer UpdatedUserOperatorServers = AUserServer operator' UpdatedUserOperatorServers {operator} = operator - partitionValid = ([],) + aUserServer' = id servers' p UpdatedUserOperatorServers {smpServers, xftpServers} = case p of SPSMP -> smpServers SPXFTP -> xftpServers -instance UserServersClass ValidatedUserOperatorServers where - type AServer ValidatedUserOperatorServers = AValidatedServer - operator' ValidatedUserOperatorServers {operator} = operator - partitionValid = partitionEithers . map serverOrErr - where - serverOrErr :: AValidatedServer p -> Either Text (AUserServer p) - serverOrErr (AVS s srv@UserServer {server = server'}) = (\server -> AUS s srv {server}) <$> unVPS server' - servers' p ValidatedUserOperatorServers {smpServers, xftpServers} = case p of - SPSMP -> smpServers - SPXFTP -> xftpServers - -type UserServer' s p = UserServer_ s ProtoServerWithAuth p - type UserServer p = UserServer' 'DBStored p type NewUserServer p = UserServer' 'DBNew p @@ -276,9 +249,9 @@ data AUserServer p = forall s. AUS (SDBStored s) (UserServer' s p) deriving instance Show (AUserServer p) -data UserServer_ s (srv :: ProtocolType -> Type) (p :: ProtocolType) = UserServer +data UserServer' s (p :: ProtocolType) = UserServer { serverId :: DBEntityId' s, - server :: srv p, + server :: ProtoServerWithAuth p, preset :: Bool, tested :: Maybe Bool, enabled :: Bool, @@ -456,7 +429,6 @@ data UserServersError = USENoServers {protocol :: AProtocolType, user :: Maybe User} | USEStorageMissing {protocol :: AProtocolType, user :: Maybe User} | USEProxyMissing {protocol :: AProtocolType, user :: Maybe User} - | USEInvalidServer {protocol :: AProtocolType, invalidServer :: Text} | USEDuplicateServer {protocol :: AProtocolType, duplicateServer :: Text, duplicateHost :: TransportHost} deriving (Show) @@ -471,16 +443,15 @@ validateUserServers curr others = currUserErrs <> concatMap otherUserErrs others | otherwise = [USEStorageMissing p' user | noServers (hasRole storage)] <> [USEProxyMissing p' user | noServers (hasRole proxy)] where p' = AProtocolType p - noServers cond = not $ any srvEnabled $ snd $ partitionValid $ concatMap (servers' p) $ filter cond uss + noServers cond = not $ any srvEnabled $ userServers p $ filter cond uss opEnabled = maybe True (\ServerOperator {enabled} -> enabled) . operator' hasRole roleSel = maybe True (\op@ServerOperator {enabled} -> enabled && roleSel (operatorRoles p op)) . operator' srvEnabled (AUS _ UserServer {deleted, enabled}) = enabled && not deleted serverErrs :: (UserServersClass u, ProtocolTypeI p, UserProtocol p) => SProtocolType p -> [u] -> [UserServersError] - serverErrs p uss = map (USEInvalidServer p') invalidSrvs <> mapMaybe duplicateErr_ srvs + serverErrs p uss = mapMaybe duplicateErr_ srvs where p' = AProtocolType p - (invalidSrvs, userSrvs) = partitionValid $ concatMap (servers' p) uss - srvs = filter (\(AUS _ UserServer {deleted}) -> not deleted) userSrvs + srvs = filter (\(AUS _ UserServer {deleted}) -> not deleted) $ userServers p uss duplicateErr_ (AUS _ srv@UserServer {server}) = USEDuplicateServer p' (safeDecodeUtf8 $ strEncode server) <$> find (`S.member` duplicateHosts) (srvHost srv) @@ -489,6 +460,8 @@ validateUserServers curr others = currUserErrs <> concatMap otherUserErrs others addHost (hs, dups) h | h `S.member` hs = (hs, S.insert h dups) | otherwise = (S.insert h hs, dups) + userServers :: (UserServersClass u, UserProtocol p) => SProtocolType p -> [u] -> [AUserServer p] + userServers p = map aUserServer' . concatMap (servers' p) instance ToJSON (DBEntityId' s) where toEncoding = \case @@ -525,30 +498,18 @@ $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "UCA") ''UsageConditionsAction) $(JQ.deriveJSON defaultJSON ''ServerOperatorConditions) instance ProtocolTypeI p => ToJSON (UserServer' s p) where - toEncoding = $(JQ.mkToEncoding defaultJSON ''UserServer_) - toJSON = $(JQ.mkToJSON defaultJSON ''UserServer_) + toEncoding = $(JQ.mkToEncoding defaultJSON ''UserServer') + toJSON = $(JQ.mkToJSON defaultJSON ''UserServer') instance (DBStoredI s, ProtocolTypeI p) => FromJSON (UserServer' s p) where - parseJSON = $(JQ.mkParseJSON defaultJSON ''UserServer_) + parseJSON = $(JQ.mkParseJSON defaultJSON ''UserServer') instance ProtocolTypeI p => FromJSON (AUserServer p) where parseJSON v = (AUS SDBStored <$> parseJSON v) <|> (AUS SDBNew <$> parseJSON v) -instance ProtocolTypeI p => FromJSON (ValidatedProtoServer p) where - parseJSON v = ValidatedProtoServer <$> ((Right <$> parseJSON v) <|> (Left <$> parseJSON v)) - -instance (DBStoredI s, ProtocolTypeI p) => FromJSON (ValidatedServer s p) where - parseJSON = $(JQ.mkParseJSON defaultJSON ''UserServer_) - -instance ProtocolTypeI p => FromJSON (AValidatedServer p) where - parseJSON v = (AVS SDBStored <$> parseJSON v) <|> (AVS SDBNew <$> parseJSON v) - $(JQ.deriveJSON defaultJSON ''UserOperatorServers) instance FromJSON UpdatedUserOperatorServers where parseJSON = $(JQ.mkParseJSON defaultJSON ''UpdatedUserOperatorServers) -instance FromJSON ValidatedUserOperatorServers where - parseJSON = $(JQ.mkParseJSON defaultJSON ''ValidatedUserOperatorServers) - $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "USE") ''UserServersError) diff --git a/tests/OperatorTests.hs b/tests/OperatorTests.hs index 03cea56133..0a00d7b83c 100644 --- a/tests/OperatorTests.hs +++ b/tests/OperatorTests.hs @@ -44,8 +44,6 @@ validateServersTest = describe "validate user servers" $ do [ USEDuplicateServer aSMP "smp://0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU=@smp8.simplex.im,beccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion" "smp8.simplex.im", USEDuplicateServer aSMP "smp://abcd@smp8.simplex.im" "smp8.simplex.im" ] - it "should fail with invalid host" $ do - validateUserServers [invalidHost] [] `shouldBe` [USENoServers aXFTP Nothing, USEInvalidServer aSMP "smp:abcd@smp8.simplex.im"] where aSMP = AProtocolType SPSMP aXFTP = AProtocolType SPXFTP @@ -132,14 +130,3 @@ invalidDuplicate = (valid :: UpdatedUserOperatorServers) { smpServers = map (AUS SDBNew) $ simplexChatSMPServers <> [presetServer True "smp://abcd@smp8.simplex.im"] } - -invalidHost :: ValidatedUserOperatorServers -invalidHost = - ValidatedUserOperatorServers - { operator = Just operatorSimpleXChat {operatorId = DBEntityId 1}, - smpServers = [validatedServer (Left "smp:abcd@smp8.simplex.im"), validatedServer (Right "smp://abcd@smp8.simplex.im")], - xftpServers = [] - } - where - validatedServer srv = - AVS SDBNew (presetServer @'PSMP True "smp://abcd@smp8.simplex.im") {server = ValidatedProtoServer srv} From 181f72fa1f735904232bf26b21a7d222f6acdfab Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 19 Nov 2024 15:26:41 +0000 Subject: [PATCH 083/567] ios: texts about operators (#5213) * ios: texts about operators * remove comment * button for conditions --- .../Onboarding/ChooseServerOperators.swift | 24 ++++++++++++------- .../Views/Onboarding/CreateProfile.swift | 6 +++-- .../Shared/Views/Onboarding/SimpleXInfo.swift | 1 - .../NetworkAndServers/OperatorView.swift | 13 ++++------ apps/ios/SimpleXChat/APITypes.swift | 5 +++- 5 files changed, 29 insertions(+), 20 deletions(-) diff --git a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift index 248c1b34c4..4b886ad9be 100644 --- a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift +++ b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift @@ -70,6 +70,11 @@ struct ChooseServerOperators: View { ForEach(serverOperators) { srvOperator in operatorCheckView(srvOperator) } + Text("You can configure servers via settings.") + .font(.footnote) + .multilineTextAlignment(.center) + .frame(maxWidth: .infinity, alignment: .center) +// .padding(.horizontal, 32) Spacer() @@ -83,10 +88,12 @@ struct ChooseServerOperators: View { continueButton() } if onboarding { - Text("You can disable operators and configure your servers in Network & servers settings.") - .multilineTextAlignment(.center) - .font(.footnote) - .padding(.horizontal, 32) + Button("Conditions of use") { + // TODO open accepted conditions + } + .font(.callout) + .foregroundColor(reviewForOperators.isEmpty ? .accentColor : .clear) + .padding(.top) } } .padding(.bottom) @@ -136,7 +143,7 @@ struct ChooseServerOperators: View { showInfoSheet = true } - Text("Select operators, whose servers you will be using.") + Text("Select network operators to use.") } } @@ -320,14 +327,15 @@ struct ChooseServerOperators: View { struct ChooseServerOperatorsInfoView: View { var body: some View { VStack(alignment: .leading) { - Text("Why choose multiple operators") + Text("Network operators") .font(.largeTitle) + .bold() .padding(.vertical) ScrollView { VStack(alignment: .leading) { Group { - Text("Selecting multiple operators improves protection of your communication graph.") - Text("TODO Better explanation") + Text("When more than one network operator is enabled, the app will use the servers of different operators for each conversation.") + Text("For example, if you receive messages via SimpleX Chat server, the app will use one of Flux servers for private routing.") } .padding(.bottom) } diff --git a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift index 9d1f9f4709..b9f569e96d 100644 --- a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift +++ b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift @@ -90,8 +90,10 @@ struct CreateFirstProfile: View { var body: some View { VStack(alignment: .leading, spacing: 20) { Text("Your profile, contacts and delivered messages are stored on your device.") + .font(.callout) .foregroundColor(theme.colors.secondary) Text("The profile is only shared with your contacts.") + .font(.callout) .foregroundColor(theme.colors.secondary) HStack { @@ -114,8 +116,8 @@ struct CreateFirstProfile: View { .padding(.horizontal) .padding(.vertical, 10) .background( - RoundedRectangle(cornerRadius: 16, style: .continuous) - .fill(Color(uiColor: .secondarySystemGroupedBackground)) + RoundedRectangle(cornerRadius: 10, style: .continuous) + .fill(Color(uiColor: .tertiarySystemFill)) ) } .padding(.top) diff --git a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift index 2e077e9d95..ea3627871e 100644 --- a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift +++ b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift @@ -144,7 +144,6 @@ struct SimpleXInfo: View { CreateFirstProfile() .navigationTitle("Create your profile") .navigationBarTitleDisplayMode(.large) - .modifier(ThemedBackground(grouped: true)) } private func userExistsFallbackButton() -> some View { diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift index ef02e94e3f..63586e2121 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift @@ -257,14 +257,11 @@ struct OperatorView: View { .modifier(ThemedBackground(grouped: true)) .navigationBarTitleDisplayMode(.large) } label: { - HStack { - Image(userServers[operatorIndex].operator_.logo(colorScheme)) - .resizable() - .scaledToFit() - .grayscale(userServers[operatorIndex].operator_.enabled ? 0.0 : 1.0) - .frame(width: 24, height: 24) - Text(userServers[operatorIndex].operator_.tradeName) - } + Image(userServers[operatorIndex].operator_.largeLogo(colorScheme)) + .resizable() + .scaledToFit() + .grayscale(userServers[operatorIndex].operator_.enabled ? 0.0 : 1.0) + .frame(height: 40) } } diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 5470059e92..016a8213c3 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -1221,7 +1221,10 @@ public struct ServerOperatorInfo: Decodable { public let operatorsInfo: Dictionary = [ .simplex: ServerOperatorInfo( - description: ["SimpleX Chat preset servers"], + description: [ + "SimpleX Chat is the first communication network that has no user profile IDs of any kind, not even random numbers or keys that identify the users.", + "SimpleX Chat Ltd develops the communication software for SimpleX network." + ], website: "https://simplex.chat", logo: "decentralized", largeLogo: "logo", From 58c92ed004b01937ef6c233a6243c1db57669320 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 19 Nov 2024 20:48:51 +0400 Subject: [PATCH 084/567] ios: rework existing users notice, condition views (#5214) --- apps/ios/Shared/ContentView.swift | 26 +++-- .../Onboarding/ChooseServerOperators.swift | 68 ++++++++++++-- .../Shared/Views/Onboarding/HowItWorks.swift | 1 + .../Views/Onboarding/WhatsNewView.swift | 94 +++++++++++-------- .../NetworkAndServers/NetworkAndServers.swift | 35 ++++--- .../Views/UserSettings/SettingsView.swift | 2 +- 6 files changed, 159 insertions(+), 67 deletions(-) diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift index 62de4bc1c6..ac699d4a2c 100644 --- a/apps/ios/Shared/ContentView.swift +++ b/apps/ios/Shared/ContentView.swift @@ -10,11 +10,13 @@ import Intents import SimpleXChat private enum NoticesSheet: Identifiable { - case notices(showWhatsNew: Bool, showOperatorsNotice: Bool) + case whatsNew(updatedConditions: Bool) + case updatedConditions var id: String { switch self { - case .notices: return "notices" + case .whatsNew: return "whatsNew" + case .updatedConditions: return "updatedConditions" } } } @@ -274,10 +276,12 @@ struct ContentView: View { DispatchQueue.main.asyncAfter(deadline: .now() + 1) { if !noticesShown { let showWhatsNew = shouldShowWhatsNew() - let showOperatorsNotice = chatModel.conditions.conditionsAction?.showNotice ?? false - noticesShown = showWhatsNew || showOperatorsNotice - if noticesShown { - noticesSheetItem = .notices(showWhatsNew: showWhatsNew, showOperatorsNotice: showOperatorsNotice) + let showUpdatedConditions = chatModel.conditions.conditionsAction?.showNotice ?? false + noticesShown = showWhatsNew || showUpdatedConditions + if showWhatsNew { + noticesSheetItem = .whatsNew(updatedConditions: showUpdatedConditions) + } else if showUpdatedConditions { + noticesSheetItem = .updatedConditions } } } @@ -288,8 +292,14 @@ struct ContentView: View { .onChange(of: chatModel.appOpenUrl) { _ in connectViaUrl() } .sheet(item: $noticesSheetItem) { item in switch item { - case let .notices(showWhatsNew, showOperatorsNotice): - WhatsNewView(showWhatsNew: showWhatsNew, showOperatorsNotice: showOperatorsNotice) + case let .whatsNew(updatedConditions): + WhatsNewView(updatedConditions: updatedConditions) + case .updatedConditions: + UsageConditionsView( + currUserServers: Binding.constant([]), + userServers: Binding.constant([]) + ) + .modifier(ThemedBackground(grouped: true)) } } if chatModel.setDeliveryReceipts { diff --git a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift index 4b886ad9be..09e0060c22 100644 --- a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift +++ b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift @@ -41,15 +41,27 @@ struct OnboardingButtonStyle: ButtonStyle { } } +private enum ChooseServerOperatorsSheet: Identifiable { + case showInfo + case showConditions + + var id: String { + switch self { + case .showInfo: return "showInfo" + case .showConditions: return "showConditions" + } + } +} + struct ChooseServerOperators: View { @Environment(\.dismiss) var dismiss: DismissAction @Environment(\.colorScheme) var colorScheme: ColorScheme @EnvironmentObject var theme: AppTheme var onboarding: Bool - @State private var showInfoSheet = false @State private var serverOperators: [ServerOperator] = [] @State private var selectedOperatorIds = Set() @State private var reviewConditionsNavLinkActive = false + @State private var sheetItem: ChooseServerOperatorsSheet? = nil @State private var justOpened = true var selectedOperators: [ServerOperator] { serverOperators.filter { selectedOperatorIds.contains($0.operatorId) } } @@ -74,25 +86,34 @@ struct ChooseServerOperators: View { .font(.footnote) .multilineTextAlignment(.center) .frame(maxWidth: .infinity, alignment: .center) -// .padding(.horizontal, 32) + .padding(.horizontal, 32) Spacer() let reviewForOperators = selectedOperators.filter { !$0.conditionsAcceptance.conditionsAccepted } let canReviewLater = reviewForOperators.allSatisfy { $0.conditionsAcceptance.usageAllowed } + let currEnabledOperatorIds = Set(serverOperators.filter { $0.enabled }.map { $0.operatorId }) VStack(spacing: 8) { if !reviewForOperators.isEmpty { reviewConditionsButton() + } else if selectedOperatorIds != currEnabledOperatorIds && !selectedOperatorIds.isEmpty { + setOperatorsButton() } else { continueButton() } if onboarding { - Button("Conditions of use") { - // TODO open accepted conditions + Group { + if reviewForOperators.isEmpty { + Button("Conditions of use") { + sheetItem = .showConditions + } + } else { + Text("Conditions of use") + .foregroundColor(.clear) + } } .font(.callout) - .foregroundColor(reviewForOperators.isEmpty ? .accentColor : .clear) .padding(.top) } } @@ -123,8 +144,17 @@ struct ChooseServerOperators: View { justOpened = false } } - .sheet(isPresented: $showInfoSheet) { - ChooseServerOperatorsInfoView() + .sheet(item: $sheetItem) { item in + switch item { + case .showInfo: + ChooseServerOperatorsInfoView() + case .showConditions: + UsageConditionsView( + currUserServers: Binding.constant([]), + userServers: Binding.constant([]) + ) + .modifier(ThemedBackground(grouped: true)) + } } } .frame(maxHeight: .infinity) @@ -140,7 +170,7 @@ struct ChooseServerOperators: View { .frame(width: 20, height: 20) .foregroundColor(theme.colors.primary) .onTapGesture { - showInfoSheet = true + sheetItem = .showInfo } Text("Select network operators to use.") @@ -200,6 +230,28 @@ struct ChooseServerOperators: View { } } + private func setOperatorsButton() -> some View { + Button { + Task { + if let enabledOperators = enabledOperators(serverOperators) { + let r = try await setServerOperators(operators: enabledOperators) + await MainActor.run { + ChatModel.shared.conditions = r + continueToNextStep() + } + } else { + await MainActor.run { + continueToNextStep() + } + } + } + } label: { + Text("Update") + } + .buttonStyle(OnboardingButtonStyle(isDisabled: selectedOperatorIds.isEmpty)) + .disabled(selectedOperatorIds.isEmpty) + } + private func continueButton() -> some View { Button { continueToNextStep() diff --git a/apps/ios/Shared/Views/Onboarding/HowItWorks.swift b/apps/ios/Shared/Views/Onboarding/HowItWorks.swift index f11dbbe7a8..9a0ee4ddeb 100644 --- a/apps/ios/Shared/Views/Onboarding/HowItWorks.swift +++ b/apps/ios/Shared/Views/Onboarding/HowItWorks.swift @@ -18,6 +18,7 @@ struct HowItWorks: View { VStack(alignment: .leading) { Text("How SimpleX works") .font(.largeTitle) + .bold() .padding(.vertical) ScrollView { VStack(alignment: .leading) { diff --git a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift index 1d1ec5b64c..c078fb23b1 100644 --- a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift +++ b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift @@ -526,7 +526,7 @@ private let versionDescriptions: [VersionDescription] = [ .view(FeatureView( icon: nil, title: "Network decentralization", - view: newOperatorsView + view: { NewOperatorsView() } )), .feature(Description( icon: "text.quote", @@ -549,20 +549,37 @@ func shouldShowWhatsNew() -> Bool { return v != lastVersion } -fileprivate func newOperatorsView() -> some View { - VStack(alignment: .leading) { - Image((operatorsInfo[.flux] ?? ServerOperator.dummyOperatorInfo).largeLogo) - .resizable() - .scaledToFit() - .frame(height: 48) - Text("The second preset operator in the app!") - .multilineTextAlignment(.leading) - .lineLimit(10) - HStack { - Button("Enable Flux") { - +fileprivate struct NewOperatorsView: View { + @State private var showOperatorsSheet = false + + var body: some View { + VStack(alignment: .leading) { + Image((operatorsInfo[.flux] ?? ServerOperator.dummyOperatorInfo).largeLogo) + .resizable() + .scaledToFit() + .frame(height: 48) + Text("The second preset operator in the app!") + .multilineTextAlignment(.leading) + .lineLimit(10) + HStack { + Button("Enable Flux") { + showOperatorsSheet = true + } + Text("for better metadata privacy.") } - Text("for better metadata privacy.") + } + .sheet(isPresented: $showOperatorsSheet) { + ChooseServerOperators(onboarding: false) + } + } +} + +private enum WhatsNewViewSheet: Identifiable { + case showConditions + + var id: String { + switch self { + case .showConditions: return "showConditions" } } } @@ -573,13 +590,13 @@ struct WhatsNewView: View { @State var currentVersion = versionDescriptions.count - 1 @State var currentVersionNav = versionDescriptions.count - 1 var viaSettings = false - @State var showWhatsNew: Bool - var showOperatorsNotice: Bool + var updatedConditions: Bool + @State private var sheetItem: WhatsNewViewSheet? = nil var body: some View { - viewBody() + whatsNewView() .task { - if showOperatorsNotice { + if updatedConditions { do { let conditionsId = ChatModel.shared.conditions.currentConditions.conditionsId try await setConditionsNotified(conditionsId: conditionsId) @@ -588,14 +605,16 @@ struct WhatsNewView: View { } } } - } - - @ViewBuilder private func viewBody() -> some View { - if showWhatsNew { - whatsNewView() - } else if showOperatorsNotice { - ChooseServerOperators(onboarding: false) - } + .sheet(item: $sheetItem) { item in + switch item { + case .showConditions: + UsageConditionsView( + currUserServers: Binding.constant([]), + userServers: Binding.constant([]) + ) + .modifier(ThemedBackground(grouped: true)) + } + } } private func whatsNewView() -> some View { @@ -623,22 +642,19 @@ struct WhatsNewView: View { } } } + if updatedConditions { + Button("View updated conditions") { + sheetItem = .showConditions + } + } if !viaSettings { Spacer() - if showOperatorsNotice { - Button("View updated conditions") { - showWhatsNew = false - } - .font(.title3) - .frame(maxWidth: .infinity, alignment: .center) - } else { - Button("Ok") { - dismiss() - } - .font(.title3) - .frame(maxWidth: .infinity, alignment: .center) + Button("Ok") { + dismiss() } + .font(.title3) + .frame(maxWidth: .infinity, alignment: .center) Spacer() } @@ -729,6 +745,6 @@ struct WhatsNewView: View { struct NewFeaturesView_Previews: PreviewProvider { static var previews: some View { - WhatsNewView(showWhatsNew: true, showOperatorsNotice: false) + WhatsNewView(updatedConditions: false) } } diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift index 2247e3d8d5..c668ad3858 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift @@ -20,7 +20,7 @@ private enum NetworkAlert: Identifiable { } private enum NetworkAndServersSheet: Identifiable { - case showConditions(conditionsAction: UsageConditionsAction) + case showConditions var id: String { switch self { @@ -65,7 +65,7 @@ struct NetworkAndServers: View { switch conditionsAction { case let .review(_, deadline, _): if let deadline = deadline, anyOperatorEnabled { - Text("Conditions will be considered accepted on: \(conditionsTimestamp(deadline)).") + Text("Conditions will be accepted on: \(conditionsTimestamp(deadline)).") .foregroundColor(theme.colors.secondary) } default: @@ -171,9 +171,8 @@ struct NetworkAndServers: View { } .sheet(item: $sheetItem) { item in switch item { - case let .showConditions(conditionsAction): + case .showConditions: UsageConditionsView( - conditionsAction: conditionsAction, currUserServers: $currUserServers, userServers: $userServers ) @@ -221,7 +220,7 @@ struct NetworkAndServers: View { private func conditionsButton(_ conditionsAction: UsageConditionsAction) -> some View { Button { - sheetItem = .showConditions(conditionsAction: conditionsAction) + sheetItem = .showConditions } label: { switch conditionsAction { case .review: @@ -236,7 +235,6 @@ struct NetworkAndServers: View { struct UsageConditionsView: View { @Environment(\.dismiss) var dismiss: DismissAction @EnvironmentObject var theme: AppTheme - var conditionsAction: UsageConditionsAction @Binding var currUserServers: [UserOperatorServers] @Binding var userServers: [UserOperatorServers] @@ -248,14 +246,29 @@ struct UsageConditionsView: View { .padding(.top) .padding(.top) - switch conditionsAction { + switch ChatModel.shared.conditions.conditionsAction { - case let .review(operators, _, _): + case .none: + ConditionsTextView() + .padding(.bottom) + .padding(.bottom) + + case let .review(operators, deadline, _): Text("Conditions will be accepted for the operator(s): **\(operators.map { $0.legalName_ }.joined(separator: ", "))**.") ConditionsTextView() - acceptConditionsButton(operators.map { $0.operatorId }) - .padding(.bottom) - .padding(.bottom) + VStack(spacing: 8) { + acceptConditionsButton(operators.map { $0.operatorId }) + if let deadline = deadline { + Text("Conditions will be automatically accepted for enabled operators on: \(conditionsTimestamp(deadline)).") + .foregroundColor(theme.colors.secondary) + .font(.footnote) + .multilineTextAlignment(.center) + .frame(maxWidth: .infinity, alignment: .center) + .padding(.horizontal, 32) + } + } + .padding(.bottom) + .padding(.bottom) case let .accepted(operators): Text("Conditions are accepted for the operator(s): **\(operators.map { $0.legalName_ }.joined(separator: ", "))**.") diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index e73697e42a..f2a1a56d01 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -367,7 +367,7 @@ struct SettingsView: View { } } NavigationLink { - WhatsNewView(viaSettings: true, showWhatsNew: true, showOperatorsNotice: false) + WhatsNewView(viaSettings: true, updatedConditions: false) .modifier(ThemedBackground()) .navigationBarTitleDisplayMode(.inline) } label: { From 4e37efdc4a68ce817f00015d66a2cc1f99007fcf Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 20 Nov 2024 07:23:25 +0000 Subject: [PATCH 085/567] core: update agent servers (#5215) --- src/Simplex/Chat.hs | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 11cd8e33ad..0daf9fa394 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1620,9 +1620,26 @@ processChatCommand' vr = \case TestProtoServer srv -> withUser $ \User {userId} -> processChatCommand $ APITestProtoServer userId srv APIGetServerOperators -> CRServerOperatorConditions <$> withFastStore getServerOperators - APISetServerOperators operatorsEnabled -> withFastStore $ \db -> do - liftIO $ setServerOperators db operatorsEnabled - CRServerOperatorConditions <$> getServerOperators db + APISetServerOperators operators -> do + as <- asks randomAgentServers + (opsConds, srvs) <- withFastStore $ \db -> do + liftIO $ setServerOperators db operators + opsConds <- getServerOperators db + let ops = serverOperators opsConds + ops' = map Just ops <> [Nothing] + opDomains = operatorDomains ops + liftIO $ fmap (opsConds,) . mapM (getServers db as ops' opDomains) =<< getUsers db + lift $ withAgent' $ \a -> forM_ srvs $ \(auId, (smp', xftp')) -> do + setProtocolServers a auId smp' + setProtocolServers a auId xftp' + pure $ CRServerOperatorConditions opsConds + where + getServers :: DB.Connection -> RandomAgentServers -> [Maybe ServerOperator] -> [(Text, ServerOperator)] -> User -> IO (UserId, (NonEmpty (ServerCfg 'PSMP), NonEmpty (ServerCfg 'PXFTP))) + getServers db as ops opDomains user = do + smpSrvs <- getProtocolServers db SPSMP user + xftpSrvs <- getProtocolServers db SPXFTP user + uss <- groupByOperator (ops, smpSrvs, xftpSrvs) + pure $ (aUserId user,) $ useServers as opDomains uss APIGetUserServers userId -> withUserId userId $ \user -> withFastStore $ \db -> do CRUserServers user <$> (liftIO . groupByOperator =<< getUserServers db user) APISetUserServers userId userServers -> withUserId userId $ \user -> do @@ -2955,8 +2972,8 @@ processChatCommand' vr = \case getUserOperatorServers :: DB.Connection -> User -> ExceptT StoreError IO (User, [UserOperatorServers]) getUserOperatorServers db user = do uss <- liftIO . groupByOperator =<< getUserServers db user - pure (user, map updatedUserServers uss) - updatedUserServers uss = uss {operator = updatedOp <$> operator' uss} :: UserOperatorServers + pure (user, map updatedUserSrvs uss) + updatedUserSrvs uss = uss {operator = updatedOp <$> operator' uss} :: UserOperatorServers updatedOp op = fromMaybe op $ find matchingOp $ mapMaybe operator' userServers where matchingOp op' = operatorId op' == operatorId op From e5534c0402e606ad9aa1c9e39566690fcaf4d9e1 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 20 Nov 2024 14:28:36 +0400 Subject: [PATCH 086/567] ios: improve onboarding animations (#5216) --- apps/ios/Shared/Model/SimpleXAPI.swift | 18 +- .../Onboarding/ChooseServerOperators.swift | 299 ++++++++++-------- .../Views/Onboarding/CreateProfile.swift | 147 ++++++--- .../Views/Onboarding/OnboardingView.swift | 31 +- .../Onboarding/SetNotificationsMode.swift | 7 +- .../Shared/Views/Onboarding/SimpleXInfo.swift | 141 ++++----- .../Views/Onboarding/WhatsNewView.swift | 5 +- 7 files changed, 359 insertions(+), 289 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index c61ad412c0..13b11568d8 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1650,7 +1650,7 @@ private func chatInitialized(start: Bool, refreshInvitations: Bool) throws { } } -func startChat(refreshInvitations: Bool = true) throws { +func startChat(refreshInvitations: Bool = true, onboarding: Bool = false) throws { logger.debug("startChat") let m = ChatModel.shared try setNetworkConfig(getNetCfg()) @@ -1669,13 +1669,15 @@ func startChat(refreshInvitations: Bool = true) throws { if let token = m.deviceToken { registerToken(token: token) } - withAnimation { - let savedOnboardingStage = onboardingStageDefault.get() - m.onboardingStage = [.step1_SimpleXInfo, .step2_CreateProfile].contains(savedOnboardingStage) && m.users.count == 1 - ? .step3_ChooseServerOperators - : savedOnboardingStage - if m.onboardingStage == .onboardingComplete && !privacyDeliveryReceiptsSet.get() { - m.setDeliveryReceipts = true + if !onboarding { + withAnimation { + let savedOnboardingStage = onboardingStageDefault.get() + m.onboardingStage = [.step1_SimpleXInfo, .step2_CreateProfile].contains(savedOnboardingStage) && m.users.count == 1 + ? .step3_ChooseServerOperators + : savedOnboardingStage + if m.onboardingStage == .onboardingComplete && !privacyDeliveryReceiptsSet.get() { + m.setDeliveryReceipts = true + } } } } diff --git a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift index 09e0060c22..45c7a94bae 100644 --- a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift +++ b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift @@ -62,104 +62,105 @@ struct ChooseServerOperators: View { @State private var selectedOperatorIds = Set() @State private var reviewConditionsNavLinkActive = false @State private var sheetItem: ChooseServerOperatorsSheet? = nil + @State private var notificationsModeNavLinkActive = false @State private var justOpened = true var selectedOperators: [ServerOperator] { serverOperators.filter { selectedOperatorIds.contains($0.operatorId) } } var body: some View { - NavigationView { - GeometryReader { g in - ScrollView { - VStack(alignment: .leading, spacing: 20) { + GeometryReader { g in + ScrollView { + VStack(alignment: .leading, spacing: 20) { + if !onboarding { Text("Choose operators") .font(.largeTitle) .bold() + } - infoText() - - Spacer() - - ForEach(serverOperators) { srvOperator in - operatorCheckView(srvOperator) + infoText() + + Spacer() + + ForEach(serverOperators) { srvOperator in + operatorCheckView(srvOperator) + } + Text("You can configure servers via settings.") + .font(.footnote) + .multilineTextAlignment(.center) + .frame(maxWidth: .infinity, alignment: .center) + .padding(.horizontal, 32) + + Spacer() + + let reviewForOperators = selectedOperators.filter { !$0.conditionsAcceptance.conditionsAccepted } + let canReviewLater = reviewForOperators.allSatisfy { $0.conditionsAcceptance.usageAllowed } + let currEnabledOperatorIds = Set(serverOperators.filter { $0.enabled }.map { $0.operatorId }) + + VStack(spacing: 8) { + if !reviewForOperators.isEmpty { + reviewConditionsButton() + } else if selectedOperatorIds != currEnabledOperatorIds && !selectedOperatorIds.isEmpty { + setOperatorsButton() + } else { + continueButton() } - Text("You can configure servers via settings.") - .font(.footnote) - .multilineTextAlignment(.center) - .frame(maxWidth: .infinity, alignment: .center) - .padding(.horizontal, 32) - - Spacer() - - let reviewForOperators = selectedOperators.filter { !$0.conditionsAcceptance.conditionsAccepted } - let canReviewLater = reviewForOperators.allSatisfy { $0.conditionsAcceptance.usageAllowed } - let currEnabledOperatorIds = Set(serverOperators.filter { $0.enabled }.map { $0.operatorId }) - - VStack(spacing: 8) { - if !reviewForOperators.isEmpty { - reviewConditionsButton() - } else if selectedOperatorIds != currEnabledOperatorIds && !selectedOperatorIds.isEmpty { - setOperatorsButton() - } else { - continueButton() - } - if onboarding { - Group { - if reviewForOperators.isEmpty { - Button("Conditions of use") { - sheetItem = .showConditions - } - } else { - Text("Conditions of use") - .foregroundColor(.clear) + if onboarding { + Group { + if reviewForOperators.isEmpty { + Button("Conditions of use") { + sheetItem = .showConditions } + } else { + Text("Conditions of use") + .foregroundColor(.clear) } - .font(.callout) - .padding(.top) } + .font(.callout) + .padding(.top) } + } + .padding(.bottom) + + if !onboarding && !reviewForOperators.isEmpty { + VStack(spacing: 8) { + reviewLaterButton() + ( + Text("Conditions will be accepted for enabled operators after 30 days.") + + Text(" ") + + Text("You can configure operators in Network & servers settings.") + ) + .multilineTextAlignment(.center) + .font(.footnote) + .padding(.horizontal, 32) + } + .disabled(!canReviewLater) .padding(.bottom) - - if !onboarding && !reviewForOperators.isEmpty { - VStack(spacing: 8) { - reviewLaterButton() - ( - Text("Conditions will be accepted for enabled operators after 30 days.") - + Text(" ") - + Text("You can configure operators in Network & servers settings.") - ) - .multilineTextAlignment(.center) - .font(.footnote) - .padding(.horizontal, 32) - } - .disabled(!canReviewLater) - .padding(.bottom) - } - } - .frame(minHeight: g.size.height) - } - .onAppear { - if justOpened { - serverOperators = ChatModel.shared.conditions.serverOperators - selectedOperatorIds = Set(serverOperators.filter { $0.enabled }.map { $0.operatorId }) - justOpened = false } } - .sheet(item: $sheetItem) { item in - switch item { - case .showInfo: - ChooseServerOperatorsInfoView() - case .showConditions: - UsageConditionsView( - currUserServers: Binding.constant([]), - userServers: Binding.constant([]) - ) - .modifier(ThemedBackground(grouped: true)) - } + .frame(minHeight: g.size.height) + } + .onAppear { + if justOpened { + serverOperators = ChatModel.shared.conditions.serverOperators + selectedOperatorIds = Set(serverOperators.filter { $0.enabled }.map { $0.operatorId }) + justOpened = false + } + } + .sheet(item: $sheetItem) { item in + switch item { + case .showInfo: + ChooseServerOperatorsInfoView() + case .showConditions: + UsageConditionsView( + currUserServers: Binding.constant([]), + userServers: Binding.constant([]) + ) + .modifier(ThemedBackground(grouped: true)) } } - .frame(maxHeight: .infinity) - .padding() } + .frame(maxHeight: .infinity) + .padding() } private func infoText() -> some View { @@ -193,7 +194,7 @@ struct ChooseServerOperators: View { .frame(width: 26, height: 26) .foregroundColor(iconColor) } - .background(Color(.systemBackground)) + .background(theme.colors.background) .padding() .clipShape(RoundedRectangle(cornerRadius: 18)) .overlay( @@ -231,57 +232,83 @@ struct ChooseServerOperators: View { } private func setOperatorsButton() -> some View { - Button { - Task { - if let enabledOperators = enabledOperators(serverOperators) { - let r = try await setServerOperators(operators: enabledOperators) - await MainActor.run { - ChatModel.shared.conditions = r - continueToNextStep() - } - } else { - await MainActor.run { - continueToNextStep() + notificationsModeNavLinkButton { + Button { + Task { + if let enabledOperators = enabledOperators(serverOperators) { + let r = try await setServerOperators(operators: enabledOperators) + await MainActor.run { + ChatModel.shared.conditions = r + continueToNextStep() + } + } else { + await MainActor.run { + continueToNextStep() + } } } + } label: { + Text("Update") } - } label: { - Text("Update") + .buttonStyle(OnboardingButtonStyle(isDisabled: selectedOperatorIds.isEmpty)) + .disabled(selectedOperatorIds.isEmpty) } - .buttonStyle(OnboardingButtonStyle(isDisabled: selectedOperatorIds.isEmpty)) - .disabled(selectedOperatorIds.isEmpty) } private func continueButton() -> some View { - Button { - continueToNextStep() - } label: { - Text("Continue") + notificationsModeNavLinkButton { + Button { + continueToNextStep() + } label: { + Text("Continue") + } + .buttonStyle(OnboardingButtonStyle(isDisabled: selectedOperatorIds.isEmpty)) + .disabled(selectedOperatorIds.isEmpty) } - .buttonStyle(OnboardingButtonStyle(isDisabled: selectedOperatorIds.isEmpty)) - .disabled(selectedOperatorIds.isEmpty) } private func reviewLaterButton() -> some View { - Button { - continueToNextStep() - } label: { - Text("Review later") + notificationsModeNavLinkButton { + Button { + continueToNextStep() + } label: { + Text("Review later") + } + .buttonStyle(.borderless) } - .buttonStyle(.borderless) } private func continueToNextStep() { if onboarding { - withAnimation { - onboardingStageDefault.set(.step4_SetNotificationsMode) - ChatModel.shared.onboardingStage = .step4_SetNotificationsMode - } + onboardingStageDefault.set(.step4_SetNotificationsMode) + notificationsModeNavLinkActive = true } else { dismiss() } } + func notificationsModeNavLinkButton(_ button: @escaping (() -> some View)) -> some View { + ZStack { + button() + + NavigationLink(isActive: $notificationsModeNavLinkActive) { + notificationsModeDestinationView() + } label: { + EmptyView() + } + .frame(width: 1, height: 1) + .hidden() + } + } + + private func notificationsModeDestinationView() -> some View { + SetNotificationsMode() + .navigationTitle("Push notifications") + .navigationBarTitleDisplayMode(.large) + .navigationBarBackButtonHidden(true) + .modifier(ThemedBackground(grouped: false)) + } + private func reviewConditionsDestinationView() -> some View { reviewConditionsView() .navigationTitle("Conditions of use") @@ -309,40 +336,42 @@ struct ChooseServerOperators: View { } private func acceptConditionsButton() -> some View { - Button { - Task { - do { - let conditionsId = ChatModel.shared.conditions.currentConditions.conditionsId - let acceptForOperators = selectedOperators.filter { !$0.conditionsAcceptance.conditionsAccepted } - let operatorIds = acceptForOperators.map { $0.operatorId } - let r = try await acceptConditions(conditionsId: conditionsId, operatorIds: operatorIds) - await MainActor.run { - ChatModel.shared.conditions = r - } - if let enabledOperators = enabledOperators(r.serverOperators) { - let r2 = try await setServerOperators(operators: enabledOperators) + notificationsModeNavLinkButton { + Button { + Task { + do { + let conditionsId = ChatModel.shared.conditions.currentConditions.conditionsId + let acceptForOperators = selectedOperators.filter { !$0.conditionsAcceptance.conditionsAccepted } + let operatorIds = acceptForOperators.map { $0.operatorId } + let r = try await acceptConditions(conditionsId: conditionsId, operatorIds: operatorIds) await MainActor.run { - ChatModel.shared.conditions = r2 - continueToNextStep() + ChatModel.shared.conditions = r } - } else { + if let enabledOperators = enabledOperators(r.serverOperators) { + let r2 = try await setServerOperators(operators: enabledOperators) + await MainActor.run { + ChatModel.shared.conditions = r2 + continueToNextStep() + } + } else { + await MainActor.run { + continueToNextStep() + } + } + } catch let error { await MainActor.run { - continueToNextStep() + showAlert( + NSLocalizedString("Error accepting conditions", comment: "alert title"), + message: responseError(error) + ) } } - } catch let error { - await MainActor.run { - showAlert( - NSLocalizedString("Error accepting conditions", comment: "alert title"), - message: responseError(error) - ) - } } + } label: { + Text("Accept conditions") } - } label: { - Text("Accept conditions") + .buttonStyle(OnboardingButtonStyle()) } - .buttonStyle(OnboardingButtonStyle()) } private func enabledOperators(_ operators: [ServerOperator]) -> [ServerOperator]? { diff --git a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift index b9f569e96d..30e7d4dc83 100644 --- a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift +++ b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift @@ -38,7 +38,7 @@ struct CreateProfile: View { TextField("Enter your name…", text: $displayName) .focused($focusDisplayName) Button { - createProfile(displayName, showAlert: { alert = $0 }, dismiss: dismiss) + createProfile() } label: { Label("Create profile", systemImage: "checkmark") } @@ -78,6 +78,35 @@ struct CreateProfile: View { } } } + + private func createProfile() { + hideKeyboard() + let profile = Profile( + displayName: displayName.trimmingCharacters(in: .whitespaces), + fullName: "" + ) + let m = ChatModel.shared + do { + AppChatState.shared.set(.active) + m.currentUser = try apiCreateActiveUser(profile) + // .isEmpty check is redundant here, but it makes it clearer what is going on + if m.users.isEmpty || m.users.allSatisfy({ $0.user.hidden }) { + try startChat() + withAnimation { + onboardingStageDefault.set(.step3_ChooseServerOperators) + m.onboardingStage = .step3_ChooseServerOperators + } + } else { + onboardingStageDefault.set(.onboardingComplete) + m.onboardingStage = .onboardingComplete + dismiss() + m.users = try listUsers() + try getUserChatData() + } + } catch let error { + showCreateProfileAlert(showAlert: { alert = $0 }, error) + } + } } struct CreateFirstProfile: View { @@ -86,6 +115,7 @@ struct CreateFirstProfile: View { @Environment(\.dismiss) var dismiss @State private var displayName: String = "" @FocusState private var focusDisplayName + @State private var nextStepNavLinkActive = false var body: some View { VStack(alignment: .leading, spacing: 20) { @@ -136,69 +166,84 @@ struct CreateFirstProfile: View { } func createProfileButton() -> some View { - Button { - createProfile(displayName, showAlert: showAlert, dismiss: dismiss) - } label: { - Text("Create profile") + ZStack { + Button { + createProfile() + } label: { + Text("Create profile") + } + .buttonStyle(OnboardingButtonStyle(isDisabled: !canCreateProfile(displayName))) + .disabled(!canCreateProfile(displayName)) + + NavigationLink(isActive: $nextStepNavLinkActive) { + nextStepDestinationView() + } label: { + EmptyView() + } + .frame(width: 1, height: 1) + .hidden() } - .buttonStyle(OnboardingButtonStyle(isDisabled: !canCreateProfile(displayName))) - .disabled(!canCreateProfile(displayName)) } private func showAlert(_ alert: UserProfileAlert) { AlertManager.shared.showAlert(userProfileAlert(alert, $displayName)) } + + private func nextStepDestinationView() -> some View { + ChooseServerOperators(onboarding: true) + .navigationTitle("Choose operators") + .navigationBarTitleDisplayMode(.large) + .navigationBarBackButtonHidden(true) + .modifier(ThemedBackground(grouped: false)) + } + + private func createProfile() { + hideKeyboard() + let profile = Profile( + displayName: displayName.trimmingCharacters(in: .whitespaces), + fullName: "" + ) + let m = ChatModel.shared + do { + AppChatState.shared.set(.active) + m.currentUser = try apiCreateActiveUser(profile) + try startChat(onboarding: true) + onboardingStageDefault.set(.step3_ChooseServerOperators) + nextStepNavLinkActive = true + } catch let error { + showCreateProfileAlert(showAlert: showAlert, error) + } + } } -private func createProfile(_ displayName: String, showAlert: (UserProfileAlert) -> Void, dismiss: DismissAction) { - hideKeyboard() - let profile = Profile( - displayName: displayName.trimmingCharacters(in: .whitespaces), - fullName: "" - ) +private func showCreateProfileAlert( + showAlert: (UserProfileAlert) -> Void, + _ error: Error +) { let m = ChatModel.shared - do { - AppChatState.shared.set(.active) - m.currentUser = try apiCreateActiveUser(profile) - // .isEmpty check is redundant here, but it makes it clearer what is going on - if m.users.isEmpty || m.users.allSatisfy({ $0.user.hidden }) { - try startChat() - withAnimation { - onboardingStageDefault.set(.step3_ChooseServerOperators) - m.onboardingStage = .step3_ChooseServerOperators - } + switch error as? ChatResponse { + case .chatCmdError(_, .errorStore(.duplicateName)), + .chatCmdError(_, .error(.userExists)): + if m.currentUser == nil { + AlertManager.shared.showAlert(duplicateUserAlert) } else { - onboardingStageDefault.set(.onboardingComplete) - m.onboardingStage = .onboardingComplete - dismiss() - m.users = try listUsers() - try getUserChatData() + showAlert(.duplicateUserError) } - } catch let error { - switch error as? ChatResponse { - case .chatCmdError(_, .errorStore(.duplicateName)), - .chatCmdError(_, .error(.userExists)): - if m.currentUser == nil { - AlertManager.shared.showAlert(duplicateUserAlert) - } else { - showAlert(.duplicateUserError) - } - case .chatCmdError(_, .error(.invalidDisplayName)): - if m.currentUser == nil { - AlertManager.shared.showAlert(invalidDisplayNameAlert) - } else { - showAlert(.invalidDisplayNameError) - } - default: - let err: LocalizedStringKey = "Error: \(responseError(error))" - if m.currentUser == nil { - AlertManager.shared.showAlert(creatUserErrorAlert(err)) - } else { - showAlert(.createUserError(error: err)) - } + case .chatCmdError(_, .error(.invalidDisplayName)): + if m.currentUser == nil { + AlertManager.shared.showAlert(invalidDisplayNameAlert) + } else { + showAlert(.invalidDisplayNameError) + } + default: + let err: LocalizedStringKey = "Error: \(responseError(error))" + if m.currentUser == nil { + AlertManager.shared.showAlert(creatUserErrorAlert(err)) + } else { + showAlert(.createUserError(error: err)) } - logger.error("Failed to create user or start chat: \(responseError(error))") } + logger.error("Failed to create user or start chat: \(responseError(error))") } private func canCreateProfile(_ displayName: String) -> Bool { diff --git a/apps/ios/Shared/Views/Onboarding/OnboardingView.swift b/apps/ios/Shared/Views/Onboarding/OnboardingView.swift index de3dce21bb..172db25315 100644 --- a/apps/ios/Shared/Views/Onboarding/OnboardingView.swift +++ b/apps/ios/Shared/Views/Onboarding/OnboardingView.swift @@ -12,13 +12,30 @@ struct OnboardingView: View { var onboarding: OnboardingStage var body: some View { - switch onboarding { - case .step1_SimpleXInfo: SimpleXInfo(onboarding: true) - case .step2_CreateProfile: CreateFirstProfile() - case .step3_CreateSimpleXAddress: CreateSimpleXAddress() - case .step3_ChooseServerOperators: ChooseServerOperators(onboarding: true) - case .step4_SetNotificationsMode: SetNotificationsMode() - case .onboardingComplete: EmptyView() + NavigationView { + switch onboarding { + case .step1_SimpleXInfo: + SimpleXInfo(onboarding: true) + .modifier(ThemedBackground(grouped: false)) + case .step2_CreateProfile: // deprecated + CreateFirstProfile() + .modifier(ThemedBackground(grouped: false)) + case .step3_CreateSimpleXAddress: // deprecated + CreateSimpleXAddress() + case .step3_ChooseServerOperators: + ChooseServerOperators(onboarding: true) + .navigationTitle("Choose operators") + .navigationBarTitleDisplayMode(.large) + .navigationBarBackButtonHidden(true) + .modifier(ThemedBackground(grouped: false)) + case .step4_SetNotificationsMode: + SetNotificationsMode() + .navigationTitle("Push notifications") + .navigationBarTitleDisplayMode(.large) + .navigationBarBackButtonHidden(true) + .modifier(ThemedBackground(grouped: false)) + case .onboardingComplete: EmptyView() + } } } } diff --git a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift index 03ee9c67e0..91a755459a 100644 --- a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift +++ b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift @@ -17,12 +17,7 @@ struct SetNotificationsMode: View { var body: some View { GeometryReader { g in ScrollView { - VStack(alignment: .leading, spacing: 16) { - Text("Push notifications") - .font(.largeTitle) - .bold() - .frame(maxWidth: .infinity) - + VStack(alignment: .leading, spacing: 20) { Text("Send notifications:") ForEach(NotificationsMode.values) { mode in NtfModeSelector(mode: mode, selection: $notificationMode) diff --git a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift index ea3627871e..2229f47a49 100644 --- a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift +++ b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift @@ -17,81 +17,79 @@ struct SimpleXInfo: View { var onboarding: Bool var body: some View { - NavigationView { - GeometryReader { g in - ScrollView { - VStack(alignment: .leading, spacing: 20) { - Image(colorScheme == .light ? "logo" : "logo-light") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: g.size.width * 0.67) - .padding(.bottom, 8) - .frame(maxWidth: .infinity, minHeight: 48, alignment: .top) + GeometryReader { g in + ScrollView { + VStack(alignment: .leading, spacing: 20) { + Image(colorScheme == .light ? "logo" : "logo-light") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: g.size.width * 0.67) + .padding(.bottom, 8) + .frame(maxWidth: .infinity, minHeight: 48, alignment: .top) - VStack(alignment: .leading) { - Text("The next generation of private messaging") - .font(.title2) - .padding(.bottom, 30) - .padding(.horizontal, 40) - .frame(maxWidth: .infinity) - .multilineTextAlignment(.center) - infoRow("privacy", "Privacy redefined", - "The 1st platform without any user identifiers – private by design.", width: 48) - infoRow("shield", "Immune to spam and abuse", - "People can connect to you only via the links you share.", width: 46) - infoRow(colorScheme == .light ? "decentralized" : "decentralized-light", "Decentralized", - "Open-source protocol and code – anybody can run the servers.", width: 44) - } - - Spacer() - - if onboarding { - onboardingActionButton() - - Button { - m.migrationState = .pasteOrScanLink - } label: { - Label("Migrate from another device", systemImage: "tray.and.arrow.down") - .font(.subheadline) - } + VStack(alignment: .leading) { + Text("The next generation of private messaging") + .font(.title2) + .padding(.bottom, 30) + .padding(.horizontal, 40) .frame(maxWidth: .infinity) - } + .multilineTextAlignment(.center) + infoRow("privacy", "Privacy redefined", + "The 1st platform without any user identifiers – private by design.", width: 48) + infoRow("shield", "Immune to spam and abuse", + "People can connect to you only via the links you share.", width: 46) + infoRow(colorScheme == .light ? "decentralized" : "decentralized-light", "Decentralized", + "Open-source protocol and code – anybody can run the servers.", width: 44) + } + + Spacer() + + if onboarding { + createFirstProfileButton() Button { - showHowItWorks = true + m.migrationState = .pasteOrScanLink } label: { - Label("How it works", systemImage: "info.circle") + Label("Migrate from another device", systemImage: "tray.and.arrow.down") .font(.subheadline) } .frame(maxWidth: .infinity) - .padding(.bottom) } - .frame(minHeight: g.size.height) - } - .sheet(isPresented: Binding( - get: { m.migrationState != nil }, - set: { _ in - m.migrationState = nil - MigrationToDeviceState.save(nil) } - )) { - NavigationView { - VStack(alignment: .leading) { - MigrateToDevice(migrationState: $m.migrationState) - } - .navigationTitle("Migrate here") - .modifier(ThemedBackground(grouped: true)) + + Button { + showHowItWorks = true + } label: { + Label("How it works", systemImage: "info.circle") + .font(.subheadline) } + .frame(maxWidth: .infinity) + .padding(.bottom) } - .sheet(isPresented: $showHowItWorks) { - HowItWorks( - onboarding: onboarding, - createProfileNavLinkActive: $createProfileNavLinkActive - ) + .frame(minHeight: g.size.height) + } + .sheet(isPresented: Binding( + get: { m.migrationState != nil }, + set: { _ in + m.migrationState = nil + MigrationToDeviceState.save(nil) } + )) { + NavigationView { + VStack(alignment: .leading) { + MigrateToDevice(migrationState: $m.migrationState) + } + .navigationTitle("Migrate here") + .modifier(ThemedBackground(grouped: true)) } } - .frame(maxHeight: .infinity) - .padding() + .sheet(isPresented: $showHowItWorks) { + HowItWorks( + onboarding: onboarding, + createProfileNavLinkActive: $createProfileNavLinkActive + ) + } } + .frame(maxHeight: .infinity) + .padding() } private func infoRow(_ image: String, _ title: LocalizedStringKey, _ text: LocalizedStringKey, width: CGFloat) -> some View { @@ -113,14 +111,6 @@ struct SimpleXInfo: View { .padding(.trailing, 6) } - @ViewBuilder private func onboardingActionButton() -> some View { - if m.currentUser == nil { - createFirstProfileButton() - } else { - userExistsFallbackButton() - } - } - private func createFirstProfileButton() -> some View { ZStack { Button { @@ -144,18 +134,7 @@ struct SimpleXInfo: View { CreateFirstProfile() .navigationTitle("Create your profile") .navigationBarTitleDisplayMode(.large) - } - - private func userExistsFallbackButton() -> some View { - Button { - withAnimation { - onboardingStageDefault.set(.onboardingComplete) - m.onboardingStage = .onboardingComplete - } - } label: { - Text("Make a private connection") - } - .buttonStyle(OnboardingButtonStyle(isDisabled: false)) + .modifier(ThemedBackground(grouped: false)) } } diff --git a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift index c078fb23b1..82497a7922 100644 --- a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift +++ b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift @@ -569,7 +569,10 @@ fileprivate struct NewOperatorsView: View { } } .sheet(isPresented: $showOperatorsSheet) { - ChooseServerOperators(onboarding: false) + NavigationView { + ChooseServerOperators(onboarding: false) + .modifier(ThemedBackground(grouped: false)) + } } } } From 313acefb193e6256d44a6f0b735b292f0d8efe44 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 20 Nov 2024 17:18:24 +0400 Subject: [PATCH 087/567] ios: remove crashing accept button (#5217) --- apps/ios/Shared/ContentView.swift | 1 + .../Onboarding/ChooseServerOperators.swift | 2 +- .../Views/Onboarding/CreateProfile.swift | 2 +- .../Views/Onboarding/OnboardingView.swift | 8 ++++---- .../Shared/Views/Onboarding/SimpleXInfo.swift | 2 +- .../Shared/Views/Onboarding/WhatsNewView.swift | 2 +- .../NetworkAndServers/OperatorView.swift | 18 ++++++------------ 7 files changed, 15 insertions(+), 20 deletions(-) diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift index ac699d4a2c..c5a7a6f20b 100644 --- a/apps/ios/Shared/ContentView.swift +++ b/apps/ios/Shared/ContentView.swift @@ -294,6 +294,7 @@ struct ContentView: View { switch item { case let .whatsNew(updatedConditions): WhatsNewView(updatedConditions: updatedConditions) + .modifier(ThemedBackground()) case .updatedConditions: UsageConditionsView( currUserServers: Binding.constant([]), diff --git a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift index 45c7a94bae..471d27ea50 100644 --- a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift +++ b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift @@ -306,7 +306,7 @@ struct ChooseServerOperators: View { .navigationTitle("Push notifications") .navigationBarTitleDisplayMode(.large) .navigationBarBackButtonHidden(true) - .modifier(ThemedBackground(grouped: false)) + .modifier(ThemedBackground()) } private func reviewConditionsDestinationView() -> some View { diff --git a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift index 30e7d4dc83..c6760319b1 100644 --- a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift +++ b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift @@ -194,7 +194,7 @@ struct CreateFirstProfile: View { .navigationTitle("Choose operators") .navigationBarTitleDisplayMode(.large) .navigationBarBackButtonHidden(true) - .modifier(ThemedBackground(grouped: false)) + .modifier(ThemedBackground()) } private func createProfile() { diff --git a/apps/ios/Shared/Views/Onboarding/OnboardingView.swift b/apps/ios/Shared/Views/Onboarding/OnboardingView.swift index 172db25315..d004e0306f 100644 --- a/apps/ios/Shared/Views/Onboarding/OnboardingView.swift +++ b/apps/ios/Shared/Views/Onboarding/OnboardingView.swift @@ -16,10 +16,10 @@ struct OnboardingView: View { switch onboarding { case .step1_SimpleXInfo: SimpleXInfo(onboarding: true) - .modifier(ThemedBackground(grouped: false)) + .modifier(ThemedBackground()) case .step2_CreateProfile: // deprecated CreateFirstProfile() - .modifier(ThemedBackground(grouped: false)) + .modifier(ThemedBackground()) case .step3_CreateSimpleXAddress: // deprecated CreateSimpleXAddress() case .step3_ChooseServerOperators: @@ -27,13 +27,13 @@ struct OnboardingView: View { .navigationTitle("Choose operators") .navigationBarTitleDisplayMode(.large) .navigationBarBackButtonHidden(true) - .modifier(ThemedBackground(grouped: false)) + .modifier(ThemedBackground()) case .step4_SetNotificationsMode: SetNotificationsMode() .navigationTitle("Push notifications") .navigationBarTitleDisplayMode(.large) .navigationBarBackButtonHidden(true) - .modifier(ThemedBackground(grouped: false)) + .modifier(ThemedBackground()) case .onboardingComplete: EmptyView() } } diff --git a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift index 2229f47a49..2d90fb2fb2 100644 --- a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift +++ b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift @@ -134,7 +134,7 @@ struct SimpleXInfo: View { CreateFirstProfile() .navigationTitle("Create your profile") .navigationBarTitleDisplayMode(.large) - .modifier(ThemedBackground(grouped: false)) + .modifier(ThemedBackground()) } } diff --git a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift index 82497a7922..4208c4a068 100644 --- a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift +++ b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift @@ -571,7 +571,7 @@ fileprivate struct NewOperatorsView: View { .sheet(isPresented: $showOperatorsSheet) { NavigationView { ChooseServerOperators(onboarding: false) - .modifier(ThemedBackground(grouped: false)) + .modifier(ThemedBackground()) } } } diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift index 63586e2121..6cebfdcde6 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift @@ -540,18 +540,12 @@ struct SingleOperatorUsageConditionsView: View { } private func usageConditionsDestinationView() -> some View { - VStack(spacing: 20) { - ConditionsTextView() - .padding(.top) - - acceptConditionsButton() - .padding(.bottom) - .padding(.bottom) - } - .padding(.horizontal) - .navigationTitle("Conditions of use") - .navigationBarTitleDisplayMode(.large) - .modifier(ThemedBackground(grouped: true)) + ConditionsTextView() + .padding() + .padding(.bottom) + .navigationTitle("Conditions of use") + .navigationBarTitleDisplayMode(.large) + .modifier(ThemedBackground(grouped: true)) } } From 29b54ec5b296926c56882f3db5c2f12774efbfc8 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 20 Nov 2024 17:58:13 +0400 Subject: [PATCH 088/567] ios: rework saving settings (#5219) * ios: rework saving settings * fix * shorter names --------- Co-authored-by: Evgeny Poberezkin --- .../Shared/Views/ChatList/ChatListView.swift | 31 ++++++---- .../Views/ChatList/ServersSummaryView.swift | 57 +------------------ .../NetworkAndServers/NetworkAndServers.swift | 52 ++++++++--------- .../Views/UserSettings/SettingsView.swift | 22 ++----- 4 files changed, 50 insertions(+), 112 deletions(-) diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 8e7aec581b..6da17fb312 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -31,14 +31,22 @@ enum UserPickerSheet: Identifiable { } } +class SaveableSettings: ObservableObject { + @Published var servers: ServerSettings = ServerSettings(currUserServers: [], userServers: [], serverErrors: []) +} + +struct ServerSettings { + public var currUserServers: [UserOperatorServers] + public var userServers: [UserOperatorServers] + public var serverErrors: [UserServersError] +} + struct UserPickerSheetView: View { let sheet: UserPickerSheet @EnvironmentObject var chatModel: ChatModel - @State private var loaded = false + @StateObject private var ss = SaveableSettings() - @State private var currUserServers: [UserOperatorServers] = [] - @State private var userServers: [UserOperatorServers] = [] - @State private var serverErrors: [UserServersError] = [] + @State private var loaded = false var body: some View { NavigationView { @@ -60,11 +68,7 @@ struct UserPickerSheetView: View { case .useFromDesktop: ConnectDesktopView() case .settings: - SettingsView( - currUserServers: $currUserServers, - userServers: $userServers, - serverErrors: $serverErrors - ) + SettingsView() } } Color.clear // Required for list background to be rendered during loading @@ -85,15 +89,20 @@ struct UserPickerSheetView: View { ) } .onDisappear { - if serversCanBeSaved(currUserServers, userServers, serverErrors) { + if serversCanBeSaved( + ss.servers.currUserServers, + ss.servers.userServers, + ss.servers.serverErrors + ) { showAlert( title: NSLocalizedString("Save servers?", comment: "alert title"), buttonTitle: NSLocalizedString("Save", comment: "alert button"), - buttonAction: { saveServers($currUserServers, $userServers) }, + buttonAction: { saveServers($ss.servers.currUserServers, $ss.servers.userServers) }, cancelButton: true ) } } + .environmentObject(ss) } } diff --git a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift index a13a159a45..b87b84ebc0 100644 --- a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift +++ b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift @@ -20,10 +20,6 @@ struct ServersSummaryView: View { @State private var timer: Timer? = nil @State private var alert: SomeAlert? - @State private var currUserServers: [UserOperatorServers] = [] - @State private var userServers: [UserOperatorServers] = [] - @State private var serverErrors: [UserServersError] = [] - @AppStorage(DEFAULT_SHOW_SUBSCRIPTION_PERCENTAGE) private var showSubscriptionPercentage = false enum PresentedUserCategory { @@ -57,15 +53,6 @@ struct ServersSummaryView: View { } .onDisappear { stopTimer() - - if serversCanBeSaved(currUserServers, userServers, serverErrors) { - showAlert( - title: NSLocalizedString("Save servers?", comment: "alert title"), - buttonTitle: NSLocalizedString("Save", comment: "alert button"), - buttonAction: { saveServers($currUserServers, $userServers) }, - cancelButton: true - ) - } } .alert(item: $alert) { $0.alert } } @@ -288,10 +275,7 @@ struct ServersSummaryView: View { NavigationLink(tag: srvSumm.id, selection: $selectedSMPServer) { SMPServerSummaryView( summary: srvSumm, - statsStartedAt: statsStartedAt, - currUserServers: $currUserServers, - userServers: $userServers, - serverErrors: $serverErrors + statsStartedAt: statsStartedAt ) .navigationBarTitle("SMP server") .navigationBarTitleDisplayMode(.large) @@ -360,10 +344,7 @@ struct ServersSummaryView: View { NavigationLink(tag: srvSumm.id, selection: $selectedXFTPServer) { XFTPServerSummaryView( summary: srvSumm, - statsStartedAt: statsStartedAt, - currUserServers: $currUserServers, - userServers: $userServers, - serverErrors: $serverErrors + statsStartedAt: statsStartedAt ) .navigationBarTitle("XFTP server") .navigationBarTitleDisplayMode(.large) @@ -505,28 +486,11 @@ struct SMPServerSummaryView: View { @AppStorage(DEFAULT_SHOW_SUBSCRIPTION_PERCENTAGE) private var showSubscriptionPercentage = false - @Binding var currUserServers: [UserOperatorServers] - @Binding var userServers: [UserOperatorServers] - @Binding var serverErrors: [UserServersError] - var body: some View { List { Section("Server address") { Text(summary.smpServer) .textSelection(.enabled) - if summary.known == true { - NavigationLink { - NetworkAndServers( - currUserServers: $currUserServers, - userServers: $userServers, - serverErrors: $serverErrors - ) - .navigationTitle("Network & servers") - .modifier(ThemedBackground(grouped: true)) - } label: { - Text("Open server settings") - } - } } if let stats = summary.stats { @@ -701,28 +665,11 @@ struct XFTPServerSummaryView: View { var summary: XFTPServerSummary var statsStartedAt: Date - @Binding var currUserServers: [UserOperatorServers] - @Binding var userServers: [UserOperatorServers] - @Binding var serverErrors: [UserServersError] - var body: some View { List { Section("Server address") { Text(summary.xftpServer) .textSelection(.enabled) - if summary.known == true { - NavigationLink { - NetworkAndServers( - currUserServers: $currUserServers, - userServers: $userServers, - serverErrors: $serverErrors - ) - .navigationTitle("Network & servers") - .modifier(ThemedBackground(grouped: true)) - } label: { - Text("Open server settings") - } - } } if let stats = summary.stats { diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift index c668ad3858..8b07c9a519 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift @@ -34,9 +34,7 @@ struct NetworkAndServers: View { @EnvironmentObject var m: ChatModel @Environment(\.colorScheme) var colorScheme: ColorScheme @EnvironmentObject var theme: AppTheme - @Binding var currUserServers: [UserOperatorServers] - @Binding var userServers: [UserOperatorServers] - @Binding var serverErrors: [UserServersError] + @EnvironmentObject var ss: SaveableSettings @State private var sheetItem: NetworkAndServersSheet? = nil @State private var justOpened = true @State private var showSaveDialog = false @@ -45,9 +43,9 @@ struct NetworkAndServers: View { VStack { List { let conditionsAction = m.conditions.conditionsAction - let anyOperatorEnabled = userServers.contains(where: { $0.operator?.enabled ?? false }) + let anyOperatorEnabled = ss.servers.userServers.contains(where: { $0.operator?.enabled ?? false }) Section { - ForEach(userServers.enumerated().map { $0 }, id: \.element.id) { idx, userOperatorServers in + ForEach(ss.servers.userServers.enumerated().map { $0 }, id: \.element.id) { idx, userOperatorServers in if let serverOperator = userOperatorServers.operator { serverOperatorView(idx, serverOperator) } else { @@ -74,11 +72,11 @@ struct NetworkAndServers: View { } Section { - if let idx = userServers.firstIndex(where: { $0.operator == nil }) { + if let idx = ss.servers.userServers.firstIndex(where: { $0.operator == nil }) { NavigationLink { YourServersView( - userServers: $userServers, - serverErrors: $serverErrors, + userServers: $ss.servers.userServers, + serverErrors: $ss.servers.serverErrors, operatorIndex: idx ) .navigationTitle("Your servers") @@ -87,7 +85,7 @@ struct NetworkAndServers: View { HStack { Text("Your servers") - if userServers[idx] != currUserServers[idx] { + if ss.servers.userServers[idx] != ss.servers.currUserServers[idx] { Spacer() unsavedChangesIndicator() } @@ -108,12 +106,12 @@ struct NetworkAndServers: View { } Section { - Button("Save servers", action: { saveServers($currUserServers, $userServers) }) - .disabled(!serversCanBeSaved(currUserServers, userServers, serverErrors)) + Button("Save servers", action: { saveServers($ss.servers.currUserServers, $ss.servers.userServers) }) + .disabled(!serversCanBeSaved(ss.servers.currUserServers, ss.servers.userServers, ss.servers.serverErrors)) } footer: { - if let errStr = globalServersError(serverErrors) { + if let errStr = globalServersError(ss.servers.serverErrors) { ServersErrorView(errStr: errStr) - } else if !serverErrors.isEmpty { + } else if !ss.servers.serverErrors.isEmpty { ServersErrorView(errStr: NSLocalizedString("Errors in servers configuration.", comment: "servers error")) } } @@ -141,9 +139,9 @@ struct NetworkAndServers: View { // this condition is needed to prevent re-setting the servers when exiting single server view if justOpened { do { - currUserServers = try await getUserServers() - userServers = currUserServers - serverErrors = [] + ss.servers.currUserServers = try await getUserServers() + ss.servers.userServers = ss.servers.currUserServers + ss.servers.serverErrors = [] } catch let error { await MainActor.run { showAlert( @@ -156,7 +154,7 @@ struct NetworkAndServers: View { } } .modifier(BackButton(disabled: Binding.constant(false)) { - if serversCanBeSaved(currUserServers, userServers, serverErrors) { + if serversCanBeSaved(ss.servers.currUserServers, ss.servers.userServers, ss.servers.serverErrors) { showSaveDialog = true } else { dismiss() @@ -164,7 +162,7 @@ struct NetworkAndServers: View { }) .confirmationDialog("Save servers?", isPresented: $showSaveDialog, titleVisibility: .visible) { Button("Save") { - saveServers($currUserServers, $userServers) + saveServers($ss.servers.currUserServers, $ss.servers.userServers) dismiss() } Button("Exit without saving") { dismiss() } @@ -173,8 +171,8 @@ struct NetworkAndServers: View { switch item { case .showConditions: UsageConditionsView( - currUserServers: $currUserServers, - userServers: $userServers + currUserServers: $ss.servers.currUserServers, + userServers: $ss.servers.userServers ) .modifier(ThemedBackground(grouped: true)) } @@ -184,9 +182,9 @@ struct NetworkAndServers: View { private func serverOperatorView(_ operatorIndex: Int, _ serverOperator: ServerOperator) -> some View { NavigationLink() { OperatorView( - currUserServers: $currUserServers, - userServers: $userServers, - serverErrors: $serverErrors, + currUserServers: $ss.servers.currUserServers, + userServers: $ss.servers.userServers, + serverErrors: $ss.servers.serverErrors, operatorIndex: operatorIndex, useOperator: serverOperator.enabled ) @@ -203,7 +201,7 @@ struct NetworkAndServers: View { Text(serverOperator.tradeName) .foregroundColor(serverOperator.enabled ? theme.colors.onBackground : theme.colors.secondary) - if userServers[operatorIndex] != currUserServers[operatorIndex] { + if ss.servers.userServers[operatorIndex] != ss.servers.currUserServers[operatorIndex] { Spacer() unsavedChangesIndicator() } @@ -427,10 +425,6 @@ func updateOperatorsConditionsAcceptance(_ usvs: Binding<[UserOperatorServers]>, struct NetworkServersView_Previews: PreviewProvider { static var previews: some View { - NetworkAndServers( - currUserServers: Binding.constant([UserOperatorServers.sampleData1, UserOperatorServers.sampleDataNilOperator]), - userServers: Binding.constant([UserOperatorServers.sampleData1, UserOperatorServers.sampleDataNilOperator]), - serverErrors: Binding.constant([]) - ) + NetworkAndServers() } } diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index f2a1a56d01..95bf327f1b 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -266,10 +266,6 @@ struct SettingsView: View { @EnvironmentObject var theme: AppTheme @State private var showProgress: Bool = false - @Binding var currUserServers: [UserOperatorServers] - @Binding var userServers: [UserOperatorServers] - @Binding var serverErrors: [UserServersError] - var body: some View { ZStack { settingsView() @@ -296,13 +292,9 @@ struct SettingsView: View { .disabled(chatModel.chatRunning != true) NavigationLink { - NetworkAndServers( - currUserServers: $currUserServers, - userServers: $userServers, - serverErrors: $serverErrors - ) - .navigationTitle("Network & servers") - .modifier(ThemedBackground(grouped: true)) + NetworkAndServers() + .navigationTitle("Network & servers") + .modifier(ThemedBackground(grouped: true)) } label: { settingsRow("externaldrive.connected.to.line.below", color: theme.colors.secondary) { Text("Network & servers") } } @@ -536,11 +528,7 @@ struct SettingsView_Previews: PreviewProvider { static var previews: some View { let chatModel = ChatModel() chatModel.currentUser = User.sampleData - return SettingsView( - currUserServers: Binding.constant([UserOperatorServers.sampleData1, UserOperatorServers.sampleDataNilOperator]), - userServers: Binding.constant([UserOperatorServers.sampleData1, UserOperatorServers.sampleDataNilOperator]), - serverErrors: Binding.constant([]) - ) - .environmentObject(chatModel) + return SettingsView() + .environmentObject(chatModel) } } From f3cef7ce12ef76c4e2d9dd7329593ce6d5c86815 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 20 Nov 2024 18:23:51 +0400 Subject: [PATCH 089/567] ios: remove unused type --- apps/ios/SimpleXChat/APITypes.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 016a8213c3..8014600d47 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -1456,7 +1456,6 @@ public enum UserServersError: Decodable { case noServers(protocol: ServerProtocol, user: UserRef?) case storageMissing(protocol: ServerProtocol, user: UserRef?) case proxyMissing(protocol: ServerProtocol, user: UserRef?) - case invalidServer(protocol: ServerProtocol, invalidServer: String) case duplicateServer(protocol: ServerProtocol, duplicateServer: String, duplicateHost: String) public var globalError: String? { From 2b155db57d7885f64ea28f0742a8c8519ddc1dbc Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Thu, 21 Nov 2024 02:23:55 +0700 Subject: [PATCH 090/567] android, desktop: open chat on first unread, "scroll" to quoted items that were not loaded (#5140) * android, desktop: infinity scroll rework * group corrections * scroll to quote/unread/top/bottom * changes * changes * changes * changes * better * changes * fix chat closing on desktop * fix reading items counter, scrolling to newly appeared message, removed unneeded items loading, only partially visible items marked read * workaround of showing buttom with arrow down on new messages receiving * rename param * fix tests * comments and removed unused code * performance optimization * optimization for loading more items in small chat * fix loading prev items in loop * workaround to blinking button with counter * terminal scroll fix * different click events for floating buttons * refactor * change * WIP * refactor * refactor * renames * refactor * refactor * change * mark read problem fix * fix tests * fix auto scroll in some situations * fix scroll to quote when it's near the top loaded area * refactor * refactor * rename * rename * fix * alert when quoted message doesn't exist * refactor --------- Co-authored-by: Evgeny Poberezkin --- .../simplex/common/platform/UI.android.kt | 3 +- .../views/chat/item/CIVideoView.android.kt | 7 + .../chat/simplex/common/model/ChatModel.kt | 155 +++- .../chat/simplex/common/model/SimpleXAPI.kt | 25 +- .../simplex/common/platform/NtfManager.kt | 2 +- .../chat/simplex/common/views/TerminalView.kt | 14 +- .../common/views/chat/ChatItemInfoView.kt | 2 +- .../common/views/chat/ChatItemsLoader.kt | 301 +++++++ .../common/views/chat/ChatItemsMerger.kt | 379 +++++++++ .../simplex/common/views/chat/ChatView.kt | 738 ++++++++++++------ .../simplex/common/views/chat/ComposeView.kt | 4 +- .../views/chat/group/GroupMemberInfoView.kt | 16 +- .../views/chat/item/CIChatFeatureView.kt | 2 +- .../common/views/chat/item/CIVideoView.kt | 3 + .../common/views/chat/item/ChatItemView.kt | 49 +- .../common/views/chat/item/FramedItemView.kt | 16 +- .../views/chat/item/MarkedDeletedItemView.kt | 4 +- .../views/chatlist/ChatListNavLinkView.kt | 64 +- .../common/views/chatlist/ChatPreviewView.kt | 2 +- .../views/contacts/ContactListNavView.kt | 6 +- .../common/views/database/DatabaseView.kt | 2 +- .../common/views/newchat/AddGroupView.kt | 2 +- .../common/views/newchat/ConnectPlan.kt | 4 +- .../commonMain/resources/MR/base/strings.xml | 2 + .../chat/simplex/app/ChatItemsMergerTest.kt | 158 ++++ .../kotlin/chat/simplex/common/DesktopApp.kt | 2 +- .../views/chat/item/CIVideoView.desktop.kt | 4 + .../views/chatlist/ChatListView.desktop.kt | 4 +- 28 files changed, 1575 insertions(+), 395 deletions(-) create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsMerger.kt create mode 100644 apps/multiplatform/common/src/commonTest/kotlin/chat/simplex/app/ChatItemsMergerTest.kt diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/UI.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/UI.android.kt index 7ab6bf525f..ae5966b20f 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/UI.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/UI.android.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalView import chat.simplex.common.AppScreen import chat.simplex.common.model.clear +import chat.simplex.common.model.clearAndNotify import chat.simplex.common.views.helpers.* import androidx.compose.ui.platform.LocalContext as LocalContext1 import chat.simplex.res.MR @@ -75,7 +76,7 @@ actual class GlobalExceptionsHandler: Thread.UncaughtExceptionHandler { } else if (chatModel.chatId.value != null) { // Since no modals are open, the problem is probably in ChatView chatModel.chatId.value = null - chatModel.chatItems.clear() + chatModel.chatItems.clearAndNotify() } else { // ChatList, nothing to do. Maybe to show other view except ChatList } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/CIVideoView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/CIVideoView.android.kt index f2f3e27766..a8c084bbad 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/CIVideoView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/CIVideoView.android.kt @@ -44,3 +44,10 @@ actual fun LocalWindowWidth(): Dp { (rect.width() / density).dp } } + +@Composable +actual fun LocalWindowHeight(): Dp { + val view = LocalView.current + val density = LocalDensity.current + return with(density) { view.height.toDp() } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 422eb1e77f..ef777f151f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -8,10 +8,11 @@ import androidx.compose.ui.graphics.* import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.font.* import androidx.compose.ui.text.style.TextDecoration +import chat.simplex.common.model.ChatModel.chatItemsChangesListener import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.call.* -import chat.simplex.common.views.chat.ComposeState +import chat.simplex.common.views.chat.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.migration.MigrationToDeviceState import chat.simplex.common.views.migration.MigrationToState @@ -22,6 +23,7 @@ import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import kotlin.collections.removeAll as remAll import kotlinx.datetime.* import kotlinx.datetime.TimeZone import kotlinx.serialization.* @@ -35,6 +37,7 @@ import java.net.URI import java.time.format.DateTimeFormatter import java.time.format.FormatStyle import java.util.* +import kotlin.collections.ArrayList import kotlin.random.Random import kotlin.time.* @@ -64,7 +67,14 @@ object ChatModel { // current chat val chatId = mutableStateOf(null) + /** if you modify the items by adding/removing them, use helpers methods like [addAndNotify], [removeLastAndNotify], [removeAllAndNotify], [clearAndNotify] and so on. + * If some helper is missing, create it. Notify is needed to track state of items that we added manually (not via api call). See [apiLoadMessages]. + * If you use api call to get the items, use just [add] instead of [addAndNotify]. + * Never modify underlying list directly because it produces unexpected results in ChatView's LazyColumn (setting by index is ok) */ val chatItems = mutableStateOf(SnapshotStateList()) + // set listener here that will be notified on every add/delete of a chat item + var chatItemsChangesListener: ChatItemsChangesListener? = null + val chatState = ActiveChatState() // rhId, chatId val deletedChats = mutableStateOf>>(emptyList()) val chatItemStatuses = mutableMapOf() @@ -216,6 +226,15 @@ object ChatModel { popChatCollector.throttlePopChat(chat.remoteHostId, chat.id, currentPosition = 0) } + private suspend fun reorderChat(chat: Chat, toIndex: Int) { + val newChats = SnapshotStateList() + newChats.addAll(chats.value) + newChats.remove(chat) + newChats.add(index = toIndex, chat) + chats.replaceAll(newChats) + popChatCollector.throttlePopChat(chat.remoteHostId, chat.id, currentPosition = toIndex) + } + fun updateChatInfo(rhId: Long?, cInfo: ChatInfo) { val i = getChatIndex(rhId, cInfo.id) if (i >= 0) { @@ -317,7 +336,7 @@ object ChatModel { chat.chatStats ) if (appPlatform.isDesktop && cItem.chatDir.sent) { - addChat(chats.removeAt(i)) + reorderChat(chats[i], 0) } else { popChatCollector.throttlePopChat(chat.remoteHostId, chat.id, currentPosition = i) } @@ -330,9 +349,9 @@ object ChatModel { // Prevent situation when chat item already in the list received from backend if (chatItems.value.none { it.id == cItem.id }) { if (chatItems.value.lastOrNull()?.id == ChatItem.TEMP_LIVE_CHAT_ITEM_ID) { - chatItems.add(kotlin.math.max(0, chatItems.value.lastIndex), cItem) + chatItems.addAndNotify(kotlin.math.max(0, chatItems.value.lastIndex), cItem) } else { - chatItems.add(cItem) + chatItems.addAndNotify(cItem) } } } @@ -377,7 +396,7 @@ object ChatModel { } else { cItem } - chatItems.add(ci) + chatItems.addAndNotify(ci) true } } else { @@ -416,7 +435,7 @@ object ChatModel { } // remove from current chat if (chatId.value == cInfo.id) { - chatItems.removeAll { + chatItems.removeAllAndNotify { // We delete taking into account meta.createdAt to make sure we will not be in situation when two items with the same id will be deleted // (it can happen if already deleted chat item in backend still in the list and new one came with the same (re-used) chat item id) val remove = it.id == cItem.id && it.meta.createdAt == cItem.meta.createdAt @@ -436,7 +455,7 @@ object ChatModel { // clear current chat if (chatId.value == cInfo.id) { chatItemStatuses.clear() - chatItems.clear() + chatItems.clearAndNotify() } } @@ -607,14 +626,14 @@ object ChatModel { suspend fun addLiveDummy(chatInfo: ChatInfo): ChatItem { val cItem = ChatItem.liveDummy(chatInfo is ChatInfo.Direct) withContext(Dispatchers.Main) { - chatItems.add(cItem) + chatItems.addAndNotify(cItem) } return cItem } fun removeLiveDummy() { if (chatItems.value.lastOrNull()?.id == ChatItem.TEMP_LIVE_CHAT_ITEM_ID) { - chatItems.removeLast() + chatItems.removeLastAndNotify() } } @@ -622,11 +641,17 @@ object ChatModel { val cInfo = chatInfo var markedRead = 0 if (chatId.value == cInfo.id) { - var i = 0 val items = chatItems.value - while (i < items.size) { + var i = items.lastIndex + val itemIdsFromRange = if (range != null) { + (range.from .. range.to).toMutableSet() + } else { + mutableSetOf() + } + val markedReadIds = mutableSetOf() + while (i >= 0) { val item = items[i] - if (item.meta.itemStatus is CIStatus.RcvNew && (range == null || (range.from <= item.id && item.id <= range.to))) { + if (item.meta.itemStatus is CIStatus.RcvNew && (range == null || itemIdsFromRange.contains(item.id))) { val newItem = item.withStatus(CIStatus.RcvRead()) items[i] = newItem if (newItem.meta.itemLive != true && newItem.meta.itemTimed?.ttl != null) { @@ -634,10 +659,17 @@ object ChatModel { deleteAt = Clock.System.now() + newItem.meta.itemTimed.ttl.toDuration(DurationUnit.SECONDS))) ) } + markedReadIds.add(item.id) markedRead++ + if (range != null) { + itemIdsFromRange.remove(item.id) + // already set all needed items as read, can finish the loop + if (itemIdsFromRange.isEmpty()) break + } } - i += 1 + i-- } + chatItemsChangesListener?.read(if (range != null) markedReadIds else null, items) } return markedRead } @@ -684,17 +716,6 @@ object ChatModel { return count to ns } - // returns the index of the passed item and the next item (it has smaller index) - fun getNextChatItem(ci: ChatItem): Pair { - val i = getChatItemIndexOrNull(ci) - return if (i != null) { - val reversedChatItems = chatItems.asReversed() - i to if (i > 0) reversedChatItems[i - 1] else null - } else { - null to null - } - } - // 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 fun getPrevShownChatItem(ciIndex: Int?, ciCategory: CIMergeCategory?): Pair { @@ -738,7 +759,7 @@ object ChatModel { fun replaceConnReqView(id: String, withId: String) { if (id == showingInvitation.value?.connId) { showingInvitation.value = null - chatModel.chatItems.clear() + chatModel.chatItems.clearAndNotify() chatModel.chatId.value = withId ModalManager.start.closeModals() ModalManager.end.closeModals() @@ -748,7 +769,7 @@ object ChatModel { fun dismissConnReqView(id: String) { if (id == showingInvitation.value?.connId) { showingInvitation.value = null - chatModel.chatItems.clear() + chatModel.chatItems.clearAndNotify() chatModel.chatId.value = null // Close NewChatView ModalManager.start.closeModals() @@ -798,6 +819,15 @@ object ChatModel { fun connectedToRemote(): Boolean = currentRemoteHost.value != null || remoteCtrlSession.value?.active == true } +interface ChatItemsChangesListener { + // pass null itemIds if the whole chat now read + fun read(itemIds: Set?, newItems: List) + fun added(item: Pair, index: Int) + // itemId, index in old chatModel.chatItems (before the update), isRcvNew (is item unread or not) + fun removed(itemIds: List>, newItems: List) + fun cleared() +} + data class ShowingInvitation( val connId: String, val connReq: String, @@ -1293,6 +1323,12 @@ data class Contact( } } +@Serializable +data class NavigationInfo( + val afterUnread: Int = 0, + val afterTotal: Int = 0 +) + @Serializable enum class ContactStatus { @SerialName("active") Active, @@ -2279,12 +2315,24 @@ data class ChatItem ( } } -fun MutableState>.add(index: Int, elem: T) { - value = SnapshotStateList().apply { addAll(value); add(index, elem) } +fun MutableState>.add(index: Int, elem: Chat) { + value = SnapshotStateList().apply { addAll(value); add(index, elem) } } -fun MutableState>.add(elem: T) { - value = SnapshotStateList().apply { addAll(value); add(elem) } +fun MutableState>.addAndNotify(index: Int, elem: ChatItem) { + value = SnapshotStateList().apply { addAll(value); add(index, elem); chatItemsChangesListener?.added(elem.id to elem.isRcvNew, index) } +} + +fun MutableState>.add(elem: Chat) { + value = SnapshotStateList().apply { addAll(value); add(elem) } +} + +// For some reason, Kotlin version crashes if the list is empty +fun MutableList.removeAll(predicate: (T) -> Boolean): Boolean = if (isEmpty()) false else remAll(predicate) + +// Adds item to chatItems and notifies a listener about newly added item +fun MutableState>.addAndNotify(elem: ChatItem) { + value = SnapshotStateList().apply { addAll(value); add(elem); chatItemsChangesListener?.added(elem.id to elem.isRcvNew, lastIndex) } } fun MutableState>.addAll(index: Int, elems: List) { @@ -2295,28 +2343,59 @@ fun MutableState>.addAll(elems: List) { value = SnapshotStateList().apply { addAll(value); addAll(elems) } } -fun MutableState>.removeAll(block: (T) -> Boolean) { - value = SnapshotStateList().apply { addAll(value); removeAll(block) } +fun MutableState>.removeAll(block: (Chat) -> Boolean) { + value = SnapshotStateList().apply { addAll(value); removeAll(block) } } -fun MutableState>.removeAt(index: Int): T { - val new = SnapshotStateList() +// Removes item(s) from chatItems and notifies a listener about removed item(s) +fun MutableState>.removeAllAndNotify(block: (ChatItem) -> Boolean) { + val toRemove = ArrayList>() + value = SnapshotStateList().apply { + addAll(value) + var i = 0 + removeAll { + val remove = block(it) + if (remove) toRemove.add(Triple(it.id, i, it.isRcvNew)) + i++ + remove + } + } + if (toRemove.isNotEmpty()) { + chatItemsChangesListener?.removed(toRemove, value) + } +} + +fun MutableState>.removeAt(index: Int): Chat { + val new = SnapshotStateList() new.addAll(value) val res = new.removeAt(index) value = new return res } -fun MutableState>.removeLast() { - value = SnapshotStateList().apply { addAll(value); removeLast() } +fun MutableState>.removeLastAndNotify() { + val removed: Triple + value = SnapshotStateList().apply { + addAll(value) + val remIndex = lastIndex + val rem = removeLast() + removed = Triple(rem.id, remIndex, rem.isRcvNew) + } + chatItemsChangesListener?.removed(listOf(removed), value) } fun MutableState>.replaceAll(elems: List) { value = SnapshotStateList().apply { addAll(elems) } } -fun MutableState>.clear() { - value = SnapshotStateList() +fun MutableState>.clear() { + value = SnapshotStateList() +} + +// Removes all chatItems and notifies a listener about it +fun MutableState>.clearAndNotify() { + value = SnapshotStateList() + chatItemsChangesListener?.cleared() } fun State>.asReversed(): MutableList = value.asReversed() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 5674920914..7f7f8a6e58 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -22,6 +22,7 @@ import dev.icerock.moko.resources.compose.painterResource import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.call.* +import chat.simplex.common.views.chat.item.showQuotedItemDoesNotExistAlert import chat.simplex.common.views.migration.MigrationFileLinkData import chat.simplex.common.views.onboarding.OnboardingStage import chat.simplex.common.views.usersettings.* @@ -868,11 +869,15 @@ object ChatController { return emptyList() } - suspend fun apiGetChat(rh: Long?, type: ChatType, id: Long, pagination: ChatPagination = ChatPagination.Last(ChatPagination.INITIAL_COUNT), search: String = ""): Chat? { + suspend fun apiGetChat(rh: Long?, type: ChatType, id: Long, pagination: ChatPagination, search: String = ""): Pair? { val r = sendCmd(rh, CC.ApiGetChat(type, id, pagination, search)) - if (r is CR.ApiChat) return if (rh == null) r.chat else r.chat.copy(remoteHostId = rh) + if (r is CR.ApiChat) return if (rh == null) r.chat to r.navInfo else r.chat.copy(remoteHostId = rh) to r.navInfo Log.e(TAG, "apiGetChat bad response: ${r.responseType} ${r.details}") - AlertManager.shared.showAlertMsg(generalGetString(MR.strings.failed_to_parse_chat_title), generalGetString(MR.strings.contact_developers)) + if (pagination is ChatPagination.Around && r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore && r.chatError.storeError is StoreError.ChatItemNotFound) { + showQuotedItemDoesNotExistAlert() + } else { + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.failed_to_parse_chat_title), generalGetString(MR.strings.contact_developers)) + } return null } @@ -2861,7 +2866,7 @@ object ChatController { chatModel.users.addAll(users) chatModel.currentUser.value = user if (user == null) { - chatModel.chatItems.clear() + chatModel.chatItems.clearAndNotify() withChats { chats.clear() popChatCollector.clear() @@ -3423,7 +3428,7 @@ sealed class CC { is GetAgentServersSummary -> "getAgentServersSummary" } - class ItemRange(val from: Long, val to: Long) + data class ItemRange(val from: Long, val to: Long) fun chatItemTTLStr(seconds: Long?): String { if (seconds == null) return "none" @@ -3471,15 +3476,19 @@ sealed class ChatPagination { class Last(val count: Int): ChatPagination() class After(val chatItemId: Long, val count: Int): ChatPagination() class Before(val chatItemId: Long, val count: Int): ChatPagination() + class Around(val chatItemId: Long, val count: Int): ChatPagination() + class Initial(val count: Int): ChatPagination() val cmdString: String get() = when (this) { is Last -> "count=${this.count}" is After -> "after=${this.chatItemId} count=${this.count}" is Before -> "before=${this.chatItemId} count=${this.count}" + is Around -> "around=${this.chatItemId} count=${this.count}" + is Initial -> "initial=${this.count}" } companion object { - const val INITIAL_COUNT = 100 + val INITIAL_COUNT = if (appPlatform.isDesktop) 100 else 75 const val PRELOAD_COUNT = 100 const val UNTIL_PRELOAD_COUNT = 50 } @@ -4917,7 +4926,7 @@ sealed class CR { @Serializable @SerialName("chatRunning") class ChatRunning: CR() @Serializable @SerialName("chatStopped") class ChatStopped: CR() @Serializable @SerialName("apiChats") class ApiChats(val user: UserRef, val chats: List): CR() - @Serializable @SerialName("apiChat") class ApiChat(val user: UserRef, val chat: Chat): CR() + @Serializable @SerialName("apiChat") class ApiChat(val user: UserRef, val chat: Chat, val navInfo: NavigationInfo = NavigationInfo()): CR() @Serializable @SerialName("chatItemInfo") class ApiChatItemInfo(val user: UserRef, val chatItem: AChatItem, val chatItemInfo: ChatItemInfo): CR() @Serializable @SerialName("userProtoServers") class UserProtoServers(val user: UserRef, val servers: UserProtocolServers): CR() @Serializable @SerialName("serverTestResult") class ServerTestResult(val user: UserRef, val testServer: String, val testFailure: ProtocolTestFailure? = null): CR() @@ -5267,7 +5276,7 @@ sealed class CR { is ChatRunning -> noDetails() is ChatStopped -> noDetails() is ApiChats -> withUser(user, json.encodeToString(chats)) - is ApiChat -> withUser(user, json.encodeToString(chat)) + is ApiChat -> withUser(user, "chat: ${json.encodeToString(chat)}\nnavInfo: ${navInfo}") is ApiChatItemInfo -> withUser(user, "chatItem: ${json.encodeToString(chatItem)}\n${json.encodeToString(chatItemInfo)}") is UserProtoServers -> withUser(user, "servers: ${json.encodeToString(servers)}") is ServerTestResult -> withUser(user, "server: $testServer\nresult: ${json.encodeToString(testFailure)}") diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt index e7c653e1b9..1f1cb45d48 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt @@ -65,7 +65,7 @@ abstract class NtfManager { } val cInfo = chatModel.getChat(chatId)?.chatInfo chatModel.clearOverlays.value = true - if (cInfo != null && (cInfo is ChatInfo.Direct || cInfo is ChatInfo.Group)) openChat(null, cInfo, chatModel) + if (cInfo != null && (cInfo is ChatInfo.Direct || cInfo is ChatInfo.Group)) openChat(null, cInfo) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt index d4eb416081..b6eb4c8996 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.lazy.* import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.* import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.layoutId @@ -25,6 +26,7 @@ import chat.simplex.common.platform.* import chat.simplex.common.views.chat.item.CONSOLE_COMPOSE_LAYOUT_ID import chat.simplex.common.views.chat.item.AdaptingBottomPaddingLayout import chat.simplex.common.views.chatlist.NavigationBarBackground +import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch @@ -125,11 +127,11 @@ fun TerminalLog(floating: Boolean, composeViewHeight: State) { derivedStateOf { chatModel.terminalItems.value.asReversed() } } val listState = LocalAppBarHandler.current?.listState ?: rememberLazyListState() + var autoScrollToBottom = rememberSaveable { mutableStateOf(true) } LaunchedEffect(Unit) { - var autoScrollToBottom = listState.firstVisibleItemIndex <= 1 launch { snapshotFlow { listState.layoutInfo.totalItemsCount } - .filter { autoScrollToBottom } + .filter { autoScrollToBottom.value } .collect { try { listState.scrollToItem(0) @@ -138,10 +140,16 @@ fun TerminalLog(floating: Boolean, composeViewHeight: State) { } } } + var oldNumberOfElements = listState.layoutInfo.totalItemsCount launch { snapshotFlow { listState.firstVisibleItemIndex } + .drop(1) .collect { - autoScrollToBottom = it == 0 + if (oldNumberOfElements != listState.layoutInfo.totalItemsCount) { + oldNumberOfElements = listState.layoutInfo.totalItemsCount + return@collect + } + autoScrollToBottom.value = it == 0 } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt index 30bbe72a72..8b4f7f8ec9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt @@ -201,7 +201,7 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools SectionItemView( click = { withBGApi { - openChat(chatRh, forwardedFromItem.chatInfo, chatModel) + openChat(chatRh, forwardedFromItem.chatInfo) ModalManager.end.closeModals() } }, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt new file mode 100644 index 0000000000..22fba59004 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt @@ -0,0 +1,301 @@ +package chat.simplex.common.views.chat + +import androidx.compose.runtime.snapshots.SnapshotStateList +import chat.simplex.common.model.* +import chat.simplex.common.model.ChatModel.withChats +import chat.simplex.common.platform.chatModel +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.StateFlow +import kotlin.math.min + +const val TRIM_KEEP_COUNT = 200 + +suspend fun apiLoadMessages( + rhId: Long?, + chatType: ChatType, + apiId: Long, + pagination: ChatPagination, + chatState: ActiveChatState, + search: String = "", + visibleItemIndexesNonReversed: () -> IntRange = { 0 .. 0 } +) = coroutineScope { + val (chat, navInfo) = chatModel.controller.apiGetChat(rhId, chatType, apiId, pagination, search) ?: return@coroutineScope + // For .initial allow the chatItems to be empty as well as chatModel.chatId to not match this chat because these values become set after .initial finishes + if (((chatModel.chatId.value != chat.id || chat.chatItems.isEmpty()) && pagination !is ChatPagination.Initial && pagination !is ChatPagination.Last) + || !isActive) return@coroutineScope + + val (splits, unreadAfterItemId, totalAfter, unreadTotal, unreadAfter, unreadAfterNewestLoaded) = chatState + val oldItems = chatModel.chatItems.value + val newItems = SnapshotStateList() + when (pagination) { + is ChatPagination.Initial -> { + val newSplits = if (chat.chatItems.isNotEmpty() && navInfo.afterTotal > 0) listOf(chat.chatItems.last().id) else emptyList() + withChats { + if (chatModel.getChat(chat.id) == null) { + addChat(chat) + } + } + withContext(Dispatchers.Main) { + chatModel.chatItemStatuses.clear() + chatModel.chatItems.replaceAll(chat.chatItems) + chatModel.chatId.value = chat.chatInfo.id + splits.value = newSplits + if (chat.chatItems.isNotEmpty()) { + unreadAfterItemId.value = chat.chatItems.last().id + } + totalAfter.value = navInfo.afterTotal + unreadTotal.value = chat.chatStats.unreadCount + unreadAfter.value = navInfo.afterUnread + unreadAfterNewestLoaded.value = navInfo.afterUnread + } + } + is ChatPagination.Before -> { + newItems.addAll(oldItems) + val indexInCurrentItems: Int = oldItems.indexOfFirst { it.id == pagination.chatItemId } + if (indexInCurrentItems == -1) return@coroutineScope + val (newIds, _) = mapItemsToIds(chat.chatItems) + val wasSize = newItems.size + val (oldUnreadSplitIndex, newUnreadSplitIndex, trimmedIds, newSplits) = removeDuplicatesAndModifySplitsOnBeforePagination( + unreadAfterItemId, newItems, newIds, splits, visibleItemIndexesNonReversed + ) + val insertAt = (indexInCurrentItems - (wasSize - newItems.size) + trimmedIds.size).coerceAtLeast(0) + newItems.addAll(insertAt, chat.chatItems) + withContext(Dispatchers.Main) { + chatModel.chatItems.replaceAll(newItems) + splits.value = newSplits + chatState.moveUnreadAfterItem(oldUnreadSplitIndex, newUnreadSplitIndex, oldItems) + } + } + is ChatPagination.After -> { + newItems.addAll(oldItems) + val indexInCurrentItems: Int = oldItems.indexOfFirst { it.id == pagination.chatItemId } + if (indexInCurrentItems == -1) return@coroutineScope + + val mappedItems = mapItemsToIds(chat.chatItems) + val newIds = mappedItems.first + val (newSplits, unreadInLoaded) = removeDuplicatesAndModifySplitsOnAfterPagination( + mappedItems.second, pagination.chatItemId, newItems, newIds, chat, splits + ) + val indexToAdd = min(indexInCurrentItems + 1, newItems.size) + val indexToAddIsLast = indexToAdd == newItems.size + newItems.addAll(indexToAdd, chat.chatItems) + withContext(Dispatchers.Main) { + chatModel.chatItems.replaceAll(newItems) + splits.value = newSplits + chatState.moveUnreadAfterItem(splits.value.firstOrNull() ?: newItems.last().id, newItems) + // loading clear bottom area, updating number of unread items after the newest loaded item + if (indexToAddIsLast) { + unreadAfterNewestLoaded.value -= unreadInLoaded + } + } + } + is ChatPagination.Around -> { + newItems.addAll(oldItems) + val newSplits = removeDuplicatesAndUpperSplits(newItems, chat, splits, visibleItemIndexesNonReversed) + // currently, items will always be added on top, which is index 0 + newItems.addAll(0, chat.chatItems) + withContext(Dispatchers.Main) { + chatModel.chatItems.replaceAll(newItems) + splits.value = listOf(chat.chatItems.last().id) + newSplits + unreadAfterItemId.value = chat.chatItems.last().id + totalAfter.value = navInfo.afterTotal + unreadTotal.value = chat.chatStats.unreadCount + unreadAfter.value = navInfo.afterUnread + // no need to set it, count will be wrong + // unreadAfterNewestLoaded.value = navInfo.afterUnread + } + } + is ChatPagination.Last -> { + newItems.addAll(oldItems) + removeDuplicates(newItems, chat) + newItems.addAll(chat.chatItems) + withContext(Dispatchers.Main) { + chatModel.chatItems.replaceAll(newItems) + unreadAfterNewestLoaded.value = 0 + } + } + } +} + +private data class ModifiedSplits ( + val oldUnreadSplitIndex: Int, + val newUnreadSplitIndex: Int, + val trimmedIds: Set, + val newSplits: List, +) + +private fun removeDuplicatesAndModifySplitsOnBeforePagination( + unreadAfterItemId: StateFlow, + newItems: SnapshotStateList, + newIds: Set, + splits: StateFlow>, + visibleItemIndexesNonReversed: () -> IntRange +): ModifiedSplits { + var oldUnreadSplitIndex: Int = -1 + var newUnreadSplitIndex: Int = -1 + val visibleItemIndexes = visibleItemIndexesNonReversed() + var lastSplitIndexTrimmed = -1 + var allowedTrimming = true + var index = 0 + /** keep the newest [TRIM_KEEP_COUNT] items (bottom area) and oldest [TRIM_KEEP_COUNT] items, trim others */ + val trimRange = visibleItemIndexes.last + TRIM_KEEP_COUNT .. newItems.size - TRIM_KEEP_COUNT + val trimmedIds = mutableSetOf() + val prevItemTrimRange = visibleItemIndexes.last + TRIM_KEEP_COUNT + 1 .. newItems.size - TRIM_KEEP_COUNT + var newSplits = splits.value + + newItems.removeAll { + val invisibleItemToTrim = trimRange.contains(index) && allowedTrimming + val prevItemWasTrimmed = prevItemTrimRange.contains(index) && allowedTrimming + // may disable it after clearing the whole split range + if (splits.value.isNotEmpty() && it.id == splits.value.firstOrNull()) { + // trim only in one split range + allowedTrimming = false + } + val indexInSplits = splits.value.indexOf(it.id) + if (indexInSplits != -1) { + lastSplitIndexTrimmed = indexInSplits + } + if (invisibleItemToTrim) { + if (prevItemWasTrimmed) { + trimmedIds.add(it.id) + } else { + newUnreadSplitIndex = index + // prev item is not supposed to be trimmed, so exclude current one from trimming and set a split here instead. + // this allows to define splitRange of the oldest items and to start loading trimmed items when user scrolls in the opposite direction + if (lastSplitIndexTrimmed == -1) { + newSplits = listOf(it.id) + newSplits + } else { + val new = ArrayList(newSplits) + new[lastSplitIndexTrimmed] = it.id + newSplits = new + } + } + } + if (unreadAfterItemId.value == it.id) { + oldUnreadSplitIndex = index + } + index++ + (invisibleItemToTrim && prevItemWasTrimmed) || newIds.contains(it.id) + } + // will remove any splits that now becomes obsolete because items were merged + newSplits = newSplits.filterNot { split -> newIds.contains(split) || trimmedIds.contains(split) } + return ModifiedSplits(oldUnreadSplitIndex, newUnreadSplitIndex, trimmedIds, newSplits) +} + +private fun removeDuplicatesAndModifySplitsOnAfterPagination( + unreadInLoaded: Int, + paginationChatItemId: Long, + newItems: SnapshotStateList, + newIds: Set, + chat: Chat, + splits: StateFlow> +): Pair, Int> { + var unreadInLoaded = unreadInLoaded + var firstItemIdBelowAllSplits: Long? = null + val splitsToRemove = ArrayList() + val indexInSplitRanges = splits.value.indexOf(paginationChatItemId) + // Currently, it should always load from split range + val loadingFromSplitRange = indexInSplitRanges != -1 + val splitsToMerge = if (loadingFromSplitRange && indexInSplitRanges + 1 <= splits.value.size) ArrayList(splits.value.subList(indexInSplitRanges + 1, splits.value.size)) else ArrayList() + newItems.removeAll { + val duplicate = newIds.contains(it.id) + if (loadingFromSplitRange && duplicate) { + if (splitsToMerge.contains(it.id)) { + splitsToMerge.remove(it.id) + splitsToRemove.add(it.id) + } else if (firstItemIdBelowAllSplits == null && splitsToMerge.isEmpty()) { + // we passed all splits and found duplicated item below all of them, which means no splits anymore below the loaded items + firstItemIdBelowAllSplits = it.id + } + } + if (duplicate && it.isRcvNew) { + unreadInLoaded-- + } + duplicate + } + var newSplits: List = emptyList() + if (firstItemIdBelowAllSplits != null) { + // no splits anymore, all were merged with bottom items + newSplits = emptyList() + } else { + if (splitsToRemove.isNotEmpty()) { + val new = ArrayList(splits.value) + new.removeAll(splitsToRemove.toSet()) + newSplits = new + } + val enlargedSplit = splits.value.indexOf(paginationChatItemId) + if (enlargedSplit != -1) { + // move the split to the end of loaded items + val new = ArrayList(splits.value) + new[enlargedSplit] = chat.chatItems.last().id + newSplits = new + // Log.d(TAG, "Enlarged split range $newSplits") + } + } + return newSplits to unreadInLoaded +} + +private fun removeDuplicatesAndUpperSplits( + newItems: SnapshotStateList, + chat: Chat, + splits: StateFlow>, + visibleItemIndexesNonReversed: () -> IntRange +): List { + if (splits.value.isEmpty()) { + removeDuplicates(newItems, chat) + return splits.value + } + + val newSplits = splits.value.toMutableList() + val visibleItemIndexes = visibleItemIndexesNonReversed() + val (newIds, _) = mapItemsToIds(chat.chatItems) + val idsToTrim = ArrayList>() + idsToTrim.add(mutableSetOf()) + var index = 0 + newItems.removeAll { + val duplicate = newIds.contains(it.id) + if (!duplicate && visibleItemIndexes.first > index) { + idsToTrim.last().add(it.id) + } + if (visibleItemIndexes.first > index && splits.value.contains(it.id)) { + newSplits -= it.id + // closing previous range. All items in idsToTrim that ends with empty set should be deleted. + // Otherwise, the last set should be excluded from trimming because it is in currently visible split range + idsToTrim.add(mutableSetOf()) + } + + index++ + duplicate + } + if (idsToTrim.last().isNotEmpty()) { + // it has some elements to trim from currently visible range which means the items shouldn't be trimmed + // Otherwise, the last set would be empty + idsToTrim.removeLast() + } + val allItemsToDelete = idsToTrim.flatten() + if (allItemsToDelete.isNotEmpty()) { + newItems.removeAll { allItemsToDelete.contains(it.id) } + } + return newSplits +} + +// ids, number of unread items +private fun mapItemsToIds(items: List): Pair, Int> { + var unreadInLoaded = 0 + val ids = mutableSetOf() + var i = 0 + while (i < items.size) { + val item = items[i] + ids.add(item.id) + if (item.isRcvNew) { + unreadInLoaded++ + } + i++ + } + return ids to unreadInLoaded +} + +private fun removeDuplicates(newItems: SnapshotStateList, chat: Chat) { + val (newIds, _) = mapItemsToIds(chat.chatItems) + newItems.removeAll { newIds.contains(it.id) } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsMerger.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsMerger.kt new file mode 100644 index 0000000000..fda5c35e01 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsMerger.kt @@ -0,0 +1,379 @@ +package chat.simplex.common.views.chat + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.runtime.* +import chat.simplex.common.model.* +import chat.simplex.common.platform.chatModel +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.MutableStateFlow + +data class MergedItems ( + val items: List, + val splits: List, + // chat item id, index in list + val indexInParentItems: Map, +) { + companion object { + fun create(items: List, unreadCount: State, revealedItems: Set, chatState: ActiveChatState): MergedItems { + if (items.isEmpty()) return MergedItems(emptyList(), emptyList(), emptyMap()) + + val unreadAfterItemId = chatState.unreadAfterItemId + val itemSplits = chatState.splits.value + val mergedItems = ArrayList() + // Indexes of splits here will be related to reversedChatItems, not chatModel.chatItems + val splitRanges = ArrayList() + val indexInParentItems = mutableMapOf() + var index = 0 + var unclosedSplitIndex: Int? = null + var unclosedSplitIndexInParent: Int? = null + var visibleItemIndexInParent = -1 + var unreadBefore = unreadCount.value - chatState.unreadAfterNewestLoaded.value + var lastRevealedIdsInMergedItems: MutableList? = null + var lastRangeInReversedForMergedItems: MutableStateFlow? = null + var recent: MergedItem? = null + while (index < items.size) { + val item = items[index] + val prev = items.getOrNull(index - 1) + val next = items.getOrNull(index + 1) + val category = item.mergeCategory + val itemIsSplit = itemSplits.contains(item.id) + + if (item.id == unreadAfterItemId.value) { + unreadBefore = unreadCount.value - chatState.unreadAfter.value + } + if (item.isRcvNew) unreadBefore-- + + val revealed = item.mergeCategory == null || revealedItems.contains(item.id) + if (recent is MergedItem.Grouped && recent.mergeCategory == category && !revealedItems.contains(recent.items.first().item.id) && !itemIsSplit) { + val listItem = ListItem(item, prev, next, unreadBefore) + recent.items.add(listItem) + + if (item.isRcvNew) { + recent.unreadIds.add(item.id) + } + if (lastRevealedIdsInMergedItems != null && lastRangeInReversedForMergedItems != null) { + if (revealed) { + lastRevealedIdsInMergedItems += item.id + } + lastRangeInReversedForMergedItems.value = lastRangeInReversedForMergedItems.value.first..index + } + } else { + visibleItemIndexInParent++ + val listItem = ListItem(item, prev, next, unreadBefore) + recent = if (item.mergeCategory != null) { + if (item.mergeCategory != prev?.mergeCategory || lastRevealedIdsInMergedItems == null) { + lastRevealedIdsInMergedItems = if (revealedItems.contains(item.id)) mutableListOf(item.id) else mutableListOf() + } else if (revealed) { + lastRevealedIdsInMergedItems += item.id + } + lastRangeInReversedForMergedItems = MutableStateFlow(index .. index) + MergedItem.Grouped( + items = arrayListOf(listItem), + revealed = revealed, + revealedIdsWithinGroup = lastRevealedIdsInMergedItems, + rangeInReversed = lastRangeInReversedForMergedItems, + mergeCategory = item.mergeCategory, + startIndexInReversedItems = index, + unreadIds = if (item.isRcvNew) mutableSetOf(item.id) else mutableSetOf() + ) + } else { + lastRangeInReversedForMergedItems = null + MergedItem.Single( + item = listItem, + startIndexInReversedItems = index + ) + } + mergedItems.add(recent) + } + if (itemIsSplit) { + // found item that is considered as a split + if (unclosedSplitIndex != null && unclosedSplitIndexInParent != null) { + // it was at least second split in the list + splitRanges.add(SplitRange(unclosedSplitIndex until index, unclosedSplitIndexInParent until visibleItemIndexInParent)) + } + unclosedSplitIndex = index + unclosedSplitIndexInParent = visibleItemIndexInParent + } else if (index + 1 == items.size && unclosedSplitIndex != null && unclosedSplitIndexInParent != null) { + // just one split for the whole list, there will be no more, it's the end + splitRanges.add(SplitRange(unclosedSplitIndex .. index, unclosedSplitIndexInParent .. visibleItemIndexInParent)) + } + indexInParentItems[item.id] = visibleItemIndexInParent + index++ + } + return MergedItems( + mergedItems, + splitRanges, + indexInParentItems + ) + } + } +} + +sealed class MergedItem { + abstract val startIndexInReversedItems: Int + + // the item that is always single, cannot be grouped and always revealed + data class Single( + val item: ListItem, + override val startIndexInReversedItems: Int, + ): MergedItem() + + /** The item that can contain multiple items or just one depending on revealed state. When the whole group of merged items is revealed, + * there will be multiple [Grouped] items with revealed flag set to true. When the whole group is collapsed, it will be just one instance + * of [Grouped] item with all grouped items inside [items]. In other words, number of [MergedItem] will always be equal to number of + * visible rows in ChatView LazyColumn */ + @Stable + data class Grouped ( + val items: ArrayList, + val revealed: Boolean, + // it stores ids for all consecutive revealed items from the same group in order to hide them all on user's action + // it's the same list instance for all Grouped items within revealed group + /** @see reveal */ + val revealedIdsWithinGroup: MutableList, + val rangeInReversed: MutableStateFlow, + val mergeCategory: CIMergeCategory?, + val unreadIds: MutableSet, + override val startIndexInReversedItems: Int, + ): MergedItem() { + fun reveal(reveal: Boolean, revealedItems: MutableState>) { + val newRevealed = revealedItems.value.toMutableSet() + var i = 0 + if (reveal) { + while (i < items.size) { + newRevealed.add(items[i].item.id) + i++ + } + } else { + while (i < revealedIdsWithinGroup.size) { + newRevealed.remove(revealedIdsWithinGroup[i]) + i++ + } + revealedIdsWithinGroup.clear() + } + revealedItems.value = newRevealed + } + } + + fun hasUnread(): Boolean = when (this) { + is Single -> item.item.isRcvNew + is Grouped -> unreadIds.isNotEmpty() + } + + fun newest(): ListItem = when (this) { + is Single -> item + is Grouped -> items.first() + } + + fun oldest(): ListItem = when (this) { + is Single -> item + is Grouped -> items.last() + } + + fun lastIndexInReversed(): Int = when (this) { + is Single -> startIndexInReversedItems + is Grouped -> startIndexInReversedItems + items.lastIndex + } +} + +data class SplitRange( + /** range of indexes inside reversedChatItems where the first element is the split (it's index is [indexRangeInReversed.first]) + * so [0, 1, 2, -100-, 101] if the 3 is a split, SplitRange(indexRange = 3 .. 4) will be this SplitRange instance + * (3, 4 indexes of the splitRange with the split itself at index 3) + * */ + val indexRangeInReversed: IntRange, + /** range of indexes inside LazyColumn where the first element is the split (it's index is [indexRangeInParentItems.first]) */ + val indexRangeInParentItems: IntRange +) + +data class ListItem( + val item: ChatItem, + val prevItem: ChatItem?, + val nextItem: ChatItem?, + // how many unread items before (older than) this one (excluding this one) + val unreadBefore: Int +) + +data class ActiveChatState ( + val splits: MutableStateFlow> = MutableStateFlow(emptyList()), + val unreadAfterItemId: MutableStateFlow = MutableStateFlow(-1L), + // total items after unread after item (exclusive) + val totalAfter: MutableStateFlow = MutableStateFlow(0), + val unreadTotal: MutableStateFlow = MutableStateFlow(0), + // exclusive + val unreadAfter: MutableStateFlow = MutableStateFlow(0), + // exclusive + val unreadAfterNewestLoaded: MutableStateFlow = MutableStateFlow(0) +) { + fun moveUnreadAfterItem(toItemId: Long?, nonReversedItems: List) { + toItemId ?: return + val currentIndex = nonReversedItems.indexOfFirst { it.id == unreadAfterItemId.value } + val newIndex = nonReversedItems.indexOfFirst { it.id == toItemId } + if (currentIndex == -1 || newIndex == -1) return + unreadAfterItemId.value = toItemId + val unreadDiff = if (newIndex > currentIndex) { + -nonReversedItems.subList(currentIndex + 1, newIndex + 1).count { it.isRcvNew } + } else { + nonReversedItems.subList(newIndex + 1, currentIndex + 1).count { it.isRcvNew } + } + unreadAfter.value += unreadDiff + } + + fun moveUnreadAfterItem(fromIndex: Int, toIndex: Int, nonReversedItems: List) { + if (fromIndex == -1 || toIndex == -1) return + unreadAfterItemId.value = nonReversedItems[toIndex].id + val unreadDiff = if (toIndex > fromIndex) { + -nonReversedItems.subList(fromIndex + 1, toIndex + 1).count { it.isRcvNew } + } else { + nonReversedItems.subList(toIndex + 1, fromIndex + 1).count { it.isRcvNew } + } + unreadAfter.value += unreadDiff + } + + fun clear() { + splits.value = emptyList() + unreadAfterItemId.value = -1L + totalAfter.value = 0 + unreadTotal.value = 0 + unreadAfter.value = 0 + unreadAfterNewestLoaded.value = 0 + } +} + +fun visibleItemIndexesNonReversed(mergedItems: State, listState: LazyListState): IntRange { + val zero = 0 .. 0 + if (listState.layoutInfo.totalItemsCount == 0) return zero + val newest = mergedItems.value.items.getOrNull(listState.firstVisibleItemIndex)?.startIndexInReversedItems + val oldest = mergedItems.value.items.getOrNull(listState.layoutInfo.visibleItemsInfo.last().index)?.lastIndexInReversed() + if (newest == null || oldest == null) return zero + val size = chatModel.chatItems.value.size + val range = size - oldest .. size - newest + if (range.first < 0 || range.last < 0) return zero + + // visible items mapped to their underlying data structure which is chatModel.chatItems + return range +} + +fun recalculateChatStatePositions(chatState: ActiveChatState) = object: ChatItemsChangesListener { + override fun read(itemIds: Set?, newItems: List) { + val (_, unreadAfterItemId, _, unreadTotal, unreadAfter) = chatState + if (itemIds == null) { + // special case when the whole chat became read + unreadTotal.value = 0 + unreadAfter.value = 0 + return + } + var unreadAfterItemIndex: Int = -1 + // since it's more often that the newest items become read, it's logical to loop from the end of the list to finish it faster + var i = newItems.lastIndex + val ids = itemIds.toMutableSet() + // intermediate variables to prevent re-setting state value a lot of times without reason + var newUnreadTotal = unreadTotal.value + var newUnreadAfter = unreadAfter.value + while (i >= 0) { + val item = newItems[i] + if (item.id == unreadAfterItemId.value) { + unreadAfterItemIndex = i + } + if (ids.contains(item.id)) { + // was unread, now this item is read + if (unreadAfterItemIndex == -1) { + newUnreadAfter-- + } + newUnreadTotal-- + ids.remove(item.id) + if (ids.isEmpty()) break + } + i-- + } + unreadTotal.value = newUnreadTotal + unreadAfter.value = newUnreadAfter + } + override fun added(item: Pair, index: Int) { + if (item.second) { + chatState.unreadAfter.value++ + chatState.unreadTotal.value++ + } + } + override fun removed(itemIds: List>, newItems: List) { + val (splits, unreadAfterItemId, totalAfter, unreadTotal, unreadAfter) = chatState + val newSplits = ArrayList() + for (split in splits.value) { + val index = itemIds.indexOfFirst { it.first == split } + // deleted the item that was right before the split between items, find newer item so it will act like the split + if (index != -1) { + val newSplit = newItems.getOrNull(itemIds[index].second - itemIds.count { it.second <= index })?.id + // it the whole section is gone and splits overlap, don't add it at all + if (newSplit != null && !newSplits.contains(newSplit)) { + newSplits.add(newSplit) + } + } else { + newSplits.add(split) + } + } + splits.value = newSplits + + val index = itemIds.indexOfFirst { it.first == unreadAfterItemId.value } + // unread after item was removed + if (index != -1) { + var newUnreadAfterItemId = newItems.getOrNull(itemIds[index].second - itemIds.count { it.second <= index })?.id + val newUnreadAfterItemWasNull = newUnreadAfterItemId == null + if (newUnreadAfterItemId == null) { + // everything on top (including unread after item) were deleted, take top item as unread after id + newUnreadAfterItemId = newItems.firstOrNull()?.id + } + if (newUnreadAfterItemId != null) { + unreadAfterItemId.value = newUnreadAfterItemId + totalAfter.value -= itemIds.count { it.second > index } + unreadTotal.value -= itemIds.count { it.second <= index && it.third } + unreadAfter.value -= itemIds.count { it.second > index && it.third } + if (newUnreadAfterItemWasNull) { + // since the unread after item was moved one item after initial position, adjust counters accordingly + if (newItems.firstOrNull()?.isRcvNew == true) { + unreadTotal.value++ + unreadAfter.value-- + } + } + } else { + // all items were deleted, 0 items in chatItems + unreadAfterItemId.value = -1L + totalAfter.value = 0 + unreadTotal.value = 0 + unreadAfter.value = 0 + } + } else { + totalAfter.value -= itemIds.size + } + } + override fun cleared() { chatState.clear() } +} + +/** Helps in debugging */ +//@Composable +//fun BoxScope.ShowChatState() { +// Box(Modifier.align(Alignment.Center).size(200.dp).background(Color.Black)) { +// val s = chatModel.chatState +// Text( +// "itemId ${s.unreadAfterItemId.value} / ${chatModel.chatItems.value.firstOrNull { it.id == s.unreadAfterItemId.value }?.text}, \nunreadAfter ${s.unreadAfter.value}, afterNewest ${s.unreadAfterNewestLoaded.value}", +// color = Color.White +// ) +// } +//} +//// Returns items mapping for easy checking the structure +//fun MergedItems.mappingToString(): String = items.mapIndexed { index, g -> +// when (g) { +// is MergedItem.Single -> +// "\nstartIndexInParentItems $index, startIndexInReversedItems ${g.startIndexInReversedItems}, " + +// "revealed true, " + +// "mergeCategory null " + +// "\nunreadBefore ${g.item.unreadBefore}" +// +// is MergedItem.Grouped -> +// "\nstartIndexInParentItems $index, startIndexInReversedItems ${g.startIndexInReversedItems}, " + +// "revealed ${g.revealed}, " + +// "mergeCategory ${g.items[0].item.mergeCategory} " + +// g.items.mapIndexed { i, it -> +// "\nunreadBefore ${it.unreadBefore} ${Triple(index, g.startIndexInReversedItems + i, it.item.id)}" +// } +// } +//}.toString() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 2c43a81f7d..8dd3e42440 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -47,9 +47,9 @@ import kotlinx.coroutines.flow.* import kotlinx.datetime.* import java.io.File import java.net.URI -import kotlin.math.abs -import kotlin.math.sign +import kotlin.math.* +@Stable data class ItemSeparation(val timestamp: Boolean, val largeGap: Boolean, val date: Instant?) @Composable @@ -292,14 +292,11 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - } } }, - loadPrevMessages = { chatId -> + loadMessages = { chatId, pagination, chatState, visibleItemIndexes -> val c = chatModel.getChat(chatId) if (chatModel.chatId.value != chatId) return@ChatLayout - val firstId = chatModel.chatItems.value.firstOrNull()?.id - if (c != null && firstId != null) { - withBGApi { - apiLoadPrevMessages(c, chatModel, firstId, searchText.value) - } + if (c != null) { + apiLoadMessages(c.remoteHostId, c.chatInfo.chatType, c.chatInfo.apiId, pagination, chatState, searchText.value, visibleItemIndexes) } }, deleteMessage = { itemId, mode -> @@ -376,7 +373,7 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - }, openDirectChat = { contactId -> scope.launch { - openDirectChat(chatRh, contactId, chatModel) + openDirectChat(chatRh, contactId) } }, forwardItem = { cInfo, cItem -> @@ -516,7 +513,7 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - val c = chatModel.getChat(chatInfo.id) ?: return@ChatLayout if (chatModel.chatId.value != chatInfo.id) return@ChatLayout withBGApi { - apiFindMessages(c, chatModel, value) + apiFindMessages(c, value) searchText.value = value } }, @@ -535,7 +532,7 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - LaunchedEffect(chatInfo.id) { onComposed(chatInfo.id) ModalManager.end.closeModals() - chatModel.chatItems.clear() + chatModel.chatItems.clearAndNotify() } } is ChatInfo.InvalidJSON -> { @@ -546,7 +543,7 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - LaunchedEffect(chatInfo.id) { onComposed(chatInfo.id) ModalManager.end.closeModals() - chatModel.chatItems.clear() + chatModel.chatItems.clearAndNotify() } } else -> {} @@ -583,7 +580,7 @@ fun ChatLayout( back: () -> Unit, info: () -> Unit, showMemberInfo: (GroupInfo, GroupMember) -> Unit, - loadPrevMessages: (ChatId) -> Unit, + loadMessages: suspend (ChatId, ChatPagination, ActiveChatState, visibleItemIndexesNonReversed: () -> IntRange) -> Unit, deleteMessage: (Long, CIDeleteMode) -> Unit, deleteMessages: (List) -> Unit, receiveFile: (Long) -> Unit, @@ -649,7 +646,7 @@ fun ChatLayout( Box(Modifier.fillMaxSize()) { ChatItemsList( remoteHostId, chatInfo, unreadCount, composeState, composeViewHeight, searchValue, - useLinkPreviews, linkMode, selectedChatItems, showMemberInfo, loadPrevMessages, deleteMessage, deleteMessages, + useLinkPreviews, linkMode, selectedChatItems, showMemberInfo, loadMessages, deleteMessage, deleteMessages, receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat, forwardItem, updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember, setReaction, showItemDetails, markRead, remember { { onComposed(it) } }, developerTools, showViaProxy, @@ -922,7 +919,7 @@ fun BoxScope.ChatItemsList( linkMode: SimplexLinkMode, selectedChatItems: MutableState?>, showMemberInfo: (GroupInfo, GroupMember) -> Unit, - loadPrevMessages: (ChatId) -> Unit, + loadMessages: suspend (ChatId, ChatPagination, ActiveChatState, visibleItemIndexesNonReversed: () -> IntRange) -> Unit, deleteMessage: (Long, CIDeleteMode) -> Unit, deleteMessages: (List) -> Unit, receiveFile: (Long) -> Unit, @@ -945,65 +942,73 @@ fun BoxScope.ChatItemsList( developerTools: Boolean, showViaProxy: Boolean ) { - val listState = rememberLazyListState() - val scope = rememberCoroutineScope() - ScrollToBottom(chatInfo.id, listState, chatModel.chatItems) - var prevSearchEmptiness by rememberSaveable { mutableStateOf(searchValue.value.isEmpty()) } - // Scroll to bottom when search value changes from something to nothing and back - LaunchedEffect(searchValue.value.isEmpty()) { - // They are equal when orientation was changed, don't need to scroll. - // LaunchedEffect unaware of this event since it uses remember, not rememberSaveable - if (prevSearchEmptiness == searchValue.value.isEmpty()) return@LaunchedEffect - prevSearchEmptiness = searchValue.value.isEmpty() - - if (listState.firstVisibleItemIndex != 0) { - scope.launch { listState.scrollToItem(0) } + val searchValueIsEmpty = remember { derivedStateOf { searchValue.value.isEmpty() } } + val reversedChatItems = remember { derivedStateOf { chatModel.chatItems.asReversed() } } + val revealedItems = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(setOf()) } + val mergedItems = remember { derivedStateOf { MergedItems.create(reversedChatItems.value, unreadCount, revealedItems.value, chatModel.chatState) } } + val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent().roundToPx() }) + /** determines height based on window info and static height of two AppBars. It's needed because in the first graphic frame height of + * [composeViewHeight] is unknown, but we need to set scroll position for unread messages already so it will be correct before the first frame appears + * */ + val maxHeightForList = rememberUpdatedState( + with(LocalDensity.current) { LocalWindowHeight().roundToPx() - topPaddingToContentPx.value - (AppBarHeight * fontSizeSqrtMultiplier * 2).roundToPx() } + ) + val listState = rememberUpdatedState(rememberSaveable(chatInfo.id, searchValueIsEmpty.value, saver = LazyListState.Saver) { + val index = mergedItems.value.items.indexOfLast { it.hasUnread() } + if (index <= 0) { + LazyListState(0, 0) + } else { + LazyListState(index + 1, -maxHeightForList.value) + } + }) + val maxHeight = remember { derivedStateOf { listState.value.layoutInfo.viewportEndOffset - topPaddingToContentPx.value } } + val loadingMoreItems = remember { mutableStateOf(false) } + val ignoreLoadingRequests = remember(remoteHostId) { mutableSetOf() } + if (!loadingMoreItems.value) { + PreloadItems(chatInfo.id, if (searchValueIsEmpty.value) ignoreLoadingRequests else mutableSetOf(), mergedItems, listState, ChatPagination.UNTIL_PRELOAD_COUNT) { chatId, pagination -> + if (loadingMoreItems.value) return@PreloadItems false + try { + loadingMoreItems.value = true + loadMessages(chatId, pagination, chatModel.chatState) { + visibleItemIndexesNonReversed(mergedItems, listState.value) + } + } finally { + loadingMoreItems.value = false + } + true } } - PreloadItems(chatInfo.id, listState, ChatPagination.UNTIL_PRELOAD_COUNT, loadPrevMessages) + val chatInfoUpdated = rememberUpdatedState(chatInfo) + val scope = rememberCoroutineScope() + val scrollToItem: (Long) -> Unit = remember { scrollToItem(loadingMoreItems, chatInfoUpdated, maxHeight, scope, reversedChatItems, mergedItems, listState, loadMessages) } + + LoadLastItems(loadingMoreItems, remoteHostId, chatInfo) + SmallScrollOnNewMessage(listState, chatModel.chatItems) + val finishedInitialComposition = remember { mutableStateOf(false) } + NotifyChatListOnFinishingComposition(finishedInitialComposition, chatInfo, revealedItems, listState, onComposed) - Spacer(Modifier.size(8.dp)) - val reversedChatItems = remember { derivedStateOf { chatModel.chatItems.asReversed() } } - val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent().roundToPx() }) - val maxHeight = remember { derivedStateOf { listState.layoutInfo.viewportEndOffset - topPaddingToContentPx.value } } - val scrollToItem: State<(Long) -> Unit> = remember { - mutableStateOf( - { itemId: Long -> - val index = reversedChatItems.value.indexOfFirst { it.id == itemId } - if (index != -1) { - scope.launch { listState.animateScrollToItem(kotlin.math.min(reversedChatItems.value.lastIndex, index + 1), -maxHeight.value) } - } - } - ) - } - // TODO: Having this block on desktop makes ChatItemsList() to recompose twice on chatModel.chatId update instead of once - LaunchedEffect(chatInfo.id) { - var stopListening = false - snapshotFlow { listState.layoutInfo.visibleItemsInfo.lastIndex } - .distinctUntilChanged() - .filter { !stopListening } - .collect { - onComposed(chatInfo.id) - stopListening = true - } - } DisposableEffectOnGone( + always = { + chatModel.chatItemsChangesListener = recalculateChatStatePositions(chatModel.chatState) + }, whenGone = { VideoPlayerHolder.releaseAll() + chatModel.chatItemsChangesListener = null } ) - LazyColumnWithScrollBar( - Modifier.align(Alignment.BottomCenter), - state = listState, - reverseLayout = true, - contentPadding = PaddingValues( - top = topPaddingToContent(), - bottom = composeViewHeight.value - ), - additionalBarOffset = composeViewHeight - ) { - itemsIndexed(reversedChatItems.value, key = { _, item -> item.id to item.meta.createdAt.toEpochMilliseconds() }) { i, cItem -> + + @Composable + fun ChatViewListItem( + itemAtZeroIndexInWholeList: Boolean, + range: State, + showAvatar: Boolean, + cItem: ChatItem, + itemSeparation: ItemSeparation, + previousItemSeparationLargeGap: Boolean, + revealed: State, + reveal: (Boolean) -> Unit + ) { val itemScope = rememberCoroutineScope() CompositionLocalProvider( // Makes horizontal and vertical scrolling to coexist nicely. @@ -1011,29 +1016,27 @@ fun BoxScope.ChatItemsList( LocalViewConfiguration provides LocalViewConfiguration.current.bigTouchSlop() ) { val provider = { - providerForGallery(i, chatModel.chatItems.value, cItem.id) { indexInReversed -> + providerForGallery(chatModel.chatItems.value, cItem.id) { indexInReversed -> itemScope.launch { - listState.scrollToItem( - kotlin.math.min(reversedChatItems.value.lastIndex, indexInReversed + 1), + listState.value.scrollToItem( + min(reversedChatItems.value.lastIndex, indexInReversed + 1), -maxHeight.value ) } } } - val revealed = remember { mutableStateOf(false) } - @Composable - fun ChatItemViewShortHand(cItem: ChatItem, itemSeparation: ItemSeparation, range: IntRange?, fillMaxWidth: Boolean = true) { + fun ChatItemViewShortHand(cItem: ChatItem, itemSeparation: ItemSeparation, range: State, fillMaxWidth: Boolean = true) { tryOrShowError("${cItem.id}ChatItem", error = { CIBrokenComposableView(if (cItem.chatDir.sent) Alignment.CenterEnd else Alignment.CenterStart) }) { - ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, range = range, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem.value, setReaction = setReaction, showItemDetails = showItemDetails, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp) + ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, range = range, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, reveal = reveal, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp) } } @Composable - fun ChatItemView(cItem: ChatItem, range: IntRange?, prevItem: ChatItem?, itemSeparation: ItemSeparation, previousItemSeparation: ItemSeparation?) { + fun ChatItemView(cItem: ChatItem, range: State, itemSeparation: ItemSeparation, previousItemSeparationLargeGap: Boolean) { val dismissState = rememberDismissState(initialValue = DismissValue.Default) { if (it == DismissValue.DismissedToStart) { itemScope.launch { @@ -1060,12 +1063,12 @@ fun BoxScope.ChatItemsList( Box( modifier = modifier.padding( bottom = if (itemSeparation.largeGap) { - if (i == 0) { + if (itemAtZeroIndexInWholeList) { 8.dp } else { 4.dp } - } else 1.dp, top = if (previousItemSeparation?.largeGap == true) 4.dp else 1.dp + } else 1.dp, top = if (previousItemSeparationLargeGap) 4.dp else 1.dp ), contentAlignment = Alignment.CenterStart ) { @@ -1089,14 +1092,7 @@ fun BoxScope.ChatItemsList( val swipeableOrSelectionModifier = (if (selectionVisible) Modifier else swipeableModifier).graphicsLayer { translationX = selectionOffset.toPx() } if (chatInfo is ChatInfo.Group) { if (cItem.chatDir is CIDirection.GroupRcv) { - val member = cItem.chatDir.groupMember - val (prevMember, memCount) = - if (range != null) { - chatModel.getPrevHiddenMember(member, range) - } else { - null to 1 - } - if (prevItem == null || showMemberImage(member, prevItem) || prevMember != null) { + if (showAvatar) { Column( Modifier .padding(top = 8.dp) @@ -1107,8 +1103,16 @@ fun BoxScope.ChatItemsList( horizontalAlignment = Alignment.Start ) { @Composable - fun MemberNameAndRole() { + fun MemberNameAndRole(range: State) { Row(Modifier.padding(bottom = 2.dp).graphicsLayer { translationX = selectionOffset.toPx() }, horizontalArrangement = Arrangement.SpaceBetween) { + val member = cItem.chatDir.groupMember + val rangeValue = range.value + val (prevMember, memCount) = + if (rangeValue != null) { + chatModel.getPrevHiddenMember(member, rangeValue) + } else { + null to 1 + } Text( memberNames(member, prevMember, memCount), Modifier @@ -1143,6 +1147,7 @@ fun BoxScope.ChatItemsList( SelectedChatItem(Modifier, cItem.id, selectedChatItems) } Row(Modifier.graphicsLayer { translationX = selectionOffset.toPx() }) { + val member = cItem.chatDir.groupMember Box(Modifier.clickable { showMemberInfo(chatInfo.groupInfo, member) }) { MemberImage(member) } @@ -1154,7 +1159,7 @@ fun BoxScope.ChatItemsList( } if (cItem.content.showMemberName) { DependentLayout(Modifier, CHAT_BUBBLE_LAYOUT_ID) { - MemberNameAndRole() + MemberNameAndRole(range) Item() } } else { @@ -1217,58 +1222,68 @@ fun BoxScope.ChatItemsList( } } } - - val (currIndex, nextItem) = chatModel.getNextChatItem(cItem) - val ciCategory = cItem.mergeCategory - if (ciCategory != null && ciCategory == nextItem?.mergeCategory) { - // memberConnected events and deleted items are aggregated at the last chat item in a row, see ChatItemView - } else { - val (prevHidden, prevItem) = chatModel.getPrevShownChatItem(currIndex, ciCategory) - - val itemSeparation = getItemSeparation(cItem, nextItem) - val previousItemSeparation = if (prevItem != null) getItemSeparation(prevItem, cItem) else null - - if (itemSeparation.date != null) { - DateSeparator(itemSeparation.date) - } - - val range = chatViewItemsRange(currIndex, prevHidden) - val reversed = reversedChatItems.value - if (revealed.value && range != null) { - reversed.subList(range.first, range.last + 1).forEachIndexed { index, ci -> - val prev = if (index + range.first == prevHidden) prevItem else reversed[index + range.first + 1] - ChatItemView(ci, null, prev, itemSeparation, previousItemSeparation) - } - } else { - ChatItemView(cItem, range, prevItem, itemSeparation, previousItemSeparation) - } - - if (i == reversed.lastIndex) { - DateSeparator(cItem.meta.itemTs) - } + if (itemSeparation.date != null) { + DateSeparator(itemSeparation.date) } + ChatItemView(cItem, range, itemSeparation, previousItemSeparationLargeGap) + } + } + LazyColumnWithScrollBar( + Modifier.align(Alignment.BottomCenter), + state = listState.value, + reverseLayout = true, + contentPadding = PaddingValues( + top = topPaddingToContent(), + bottom = composeViewHeight.value + ), + additionalBarOffset = composeViewHeight + ) { + val mergedItemsValue = mergedItems.value + itemsIndexed(mergedItemsValue.items, key = { _, merged -> keyForItem(merged.newest().item) }) { index, merged -> + val isLastItem = index == mergedItemsValue.items.lastIndex + val last = if (isLastItem) reversedChatItems.value.lastOrNull() else null + val listItem = merged.newest() + val item = listItem.item + val range = if (merged is MergedItem.Grouped) { + merged.rangeInReversed.value + } else { + null + } + val showAvatar = if (merged is MergedItem.Grouped) shouldShowAvatar(item, listItem.nextItem) else true + val isRevealed = remember { derivedStateOf { revealedItems.value.contains(item.id) } } + val itemSeparation: ItemSeparation + val prevItemSeparationLargeGap: Boolean + if (merged is MergedItem.Single || isRevealed.value) { + val prev = listItem.prevItem + itemSeparation = getItemSeparation(item, prev) + val nextForGap = if ((item.mergeCategory != null && item.mergeCategory == prev?.mergeCategory) || isLastItem) null else listItem.nextItem + prevItemSeparationLargeGap = if (nextForGap == null) false else getItemSeparationLargeGap(nextForGap, item) + } else { + itemSeparation = getItemSeparation(item, null) + prevItemSeparationLargeGap = false + } + ChatViewListItem(index == 0, rememberUpdatedState(range), showAvatar, item, itemSeparation, prevItemSeparationLargeGap, isRevealed) { + if (merged is MergedItem.Grouped) merged.reveal(it, revealedItems) + } - - if (cItem.isRcvNew && chatInfo.id == ChatModel.chatId.value) { - LaunchedEffect(cItem.id) { - itemScope.launch { - delay(600) - markRead(CC.ItemRange(cItem.id, cItem.id), null) - } - } + if (last != null) { + // no using separate item(){} block in order to have total number of items in LazyColumn match number of merged items + DateSeparator(last.meta.itemTs) + } + if (item.isRcvNew) { + val (itemIdStart, itemIdEnd) = when (merged) { + is MergedItem.Single -> merged.item.item.id to merged.item.item.id + is MergedItem.Grouped -> merged.items.last().item.id to merged.items.first().item.id } + MarkItemsReadAfterDelay(keyForItem(item), itemIdStart, itemIdEnd, finishedInitialComposition, chatInfo.id, listState, markRead) } } } - FloatingButtons(chatModel.chatItems, unreadCount, composeViewHeight, remoteHostId, chatInfo, searchValue, markRead, listState) - - FloatingDate( - Modifier.padding(top = 10.dp + topPaddingToContent()).align(Alignment.TopCenter), - listState, - ) + FloatingButtons(loadingMoreItems, mergedItems, unreadCount, maxHeight, composeViewHeight, remoteHostId, chatInfo, searchValue, markRead, listState) + FloatingDate(Modifier.padding(top = 10.dp + topPaddingToContent()).align(Alignment.TopCenter), mergedItems, listState) LaunchedEffect(Unit) { - snapshotFlow { listState.isScrollInProgress } + snapshotFlow { listState.value.isScrollInProgress } .collect { chatViewScrollState.value = it } @@ -1276,33 +1291,43 @@ fun BoxScope.ChatItemsList( } @Composable -private fun ScrollToBottom(chatId: ChatId, listState: LazyListState, chatItems: State>) { - val scope = rememberCoroutineScope() - // Helps to scroll to bottom after moving from Group to Direct chat - // and prevents scrolling to bottom on orientation change - var shouldAutoScroll by rememberSaveable { mutableStateOf(true to chatId) } - LaunchedEffect(chatId, shouldAutoScroll) { - if ((shouldAutoScroll.first || shouldAutoScroll.second != chatId) && listState.firstVisibleItemIndex != 0) { - scope.launch { listState.scrollToItem(0) } +private fun LoadLastItems(loadingMoreItems: MutableState, remoteHostId: Long?, chatInfo: ChatInfo) { + LaunchedEffect(remoteHostId, chatInfo.id) { + try { + loadingMoreItems.value = true + if (chatModel.chatState.totalAfter.value <= 0) return@LaunchedEffect + delay(500) + withContext(Dispatchers.Default) { + apiLoadMessages(remoteHostId, chatInfo.chatType, chatInfo.apiId, ChatPagination.Last(ChatPagination.INITIAL_COUNT), chatModel.chatState) + } + } finally { + loadingMoreItems.value = false } - // Don't autoscroll next time until it will be needed - shouldAutoScroll = false to chatId } +} + +@Composable +private fun SmallScrollOnNewMessage(listState: State, chatItems: State>) { val scrollDistance = with(LocalDensity.current) { -39.dp.toPx() } - /* - * Since we use key with each item in LazyColumn, LazyColumn will not autoscroll to bottom item. We need to do it ourselves. - * When the first visible item (from bottom) is visible (even partially) we can autoscroll to 0 item. Or just scrollBy small distance otherwise - * */ LaunchedEffect(Unit) { - snapshotFlow { chatItems.value.lastOrNull()?.id } + var lastTotalItems = listState.value.layoutInfo.totalItemsCount + var lastItemId = chatItems.value.lastOrNull()?.id + snapshotFlow { listState.value.layoutInfo.totalItemsCount } .distinctUntilChanged() - .filter { listState.layoutInfo.visibleItemsInfo.firstOrNull()?.key != it } + .drop(1) .collect { + val diff = listState.value.layoutInfo.totalItemsCount - lastTotalItems + val sameLastItem = lastItemId == chatItems.value.lastOrNull()?.id + lastTotalItems = listState.value.layoutInfo.totalItemsCount + lastItemId = chatItems.value.lastOrNull()?.id + if (diff < 1 || diff > 2 || sameLastItem) { + return@collect + } try { - if (listState.firstVisibleItemIndex == 0 || (listState.firstVisibleItemIndex == 1 && listState.layoutInfo.totalItemsCount == chatItems.value.size)) { - if (appPlatform.isAndroid) listState.animateScrollToItem(0) else listState.scrollToItem(0) + if (listState.value.firstVisibleItemIndex == 0 || listState.value.firstVisibleItemIndex == 1) { + if (appPlatform.isAndroid) listState.value.animateScrollToItem(0) else listState.value.scrollToItem(0) } else { - if (appPlatform.isAndroid) listState.animateScrollBy(scrollDistance) else listState.scrollBy(scrollDistance) + if (appPlatform.isAndroid) listState.value.animateScrollBy(scrollDistance) else listState.value.scrollBy(scrollDistance) } } catch (e: CancellationException) { /** @@ -1317,55 +1342,89 @@ private fun ScrollToBottom(chatId: ChatId, listState: LazyListState, chatItems: } } +@Composable +private fun NotifyChatListOnFinishingComposition( + finishedInitialComposition: MutableState, + chatInfo: ChatInfo, + revealedItems: MutableState>, + listState: State, + onComposed: suspend (chatId: String) -> Unit +) { + LaunchedEffect(chatInfo.id) { + revealedItems.value = emptySet() + snapshotFlow { listState.value.layoutInfo.visibleItemsInfo.lastIndex } + .distinctUntilChanged() + .collect { + onComposed(chatInfo.id) + finishedInitialComposition.value = true + cancel() + } + } +} + @Composable fun BoxScope.FloatingButtons( - chatItems: State>, + loadingMoreItems: MutableState, + mergedItems: State, unreadCount: State, + maxHeight: State, composeViewHeight: State, remoteHostId: Long?, chatInfo: ChatInfo, searchValue: State, markRead: (CC.ItemRange, unreadCountAfter: Int?) -> Unit, - listState: LazyListState + listState: State ) { val scope = rememberCoroutineScope() - val maxHeight = remember { derivedStateOf { listState.layoutInfo.viewportSize.height } } + val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent().roundToPx() }) val bottomUnreadCount = remember { derivedStateOf { if (unreadCount.value == 0) return@derivedStateOf 0 - val items = chatItems.value - val from = items.lastIndex - listState.firstVisibleItemIndex - listState.layoutInfo.visibleItemsInfo.lastIndex - if (items.size <= from || from < 0) return@derivedStateOf 0 - - items.subList(from, items.size).count { it.isRcvNew } + val lastVisibleItem = oldestPartiallyVisibleListItemInListStateOrNull(topPaddingToContentPx, mergedItems, listState) ?: return@derivedStateOf -1 + unreadCount.value - lastVisibleItem.unreadBefore } } - val showBottomButtonWithCounter = remember { derivedStateOf { bottomUnreadCount.value > 0 && listState.firstVisibleItemIndex != 0 && searchValue.value.isEmpty() } } - val showBottomButtonWithArrow = remember { derivedStateOf { !showBottomButtonWithCounter.value && listState.firstVisibleItemIndex != 0 } } + val allowToShowBottomWithCounter = remember { mutableStateOf(true) } + val showBottomButtonWithCounter = remember { derivedStateOf { + val allow = allowToShowBottomWithCounter.value + val shouldShow = bottomUnreadCount.value > 0 && listState.value.firstVisibleItemIndex != 0 && searchValue.value.isEmpty() + // this tricky idea is to prevent showing button with arrow in the next frame after creating/receiving new message because the list will + // scroll to that message but before this happens, that button will show up and then will hide itself after scroll finishes. + // This workaround prevents it + allowToShowBottomWithCounter.value = shouldShow + shouldShow && allow + } } + val allowToShowBottomWithArrow = remember { mutableStateOf(true) } + val showBottomButtonWithArrow = remember { derivedStateOf { + val allow = allowToShowBottomWithArrow.value + val shouldShow = !showBottomButtonWithCounter.value && listState.value.firstVisibleItemIndex != 0 + allowToShowBottomWithArrow.value = shouldShow + shouldShow && allow + } } BottomEndFloatingButton( bottomUnreadCount, showBottomButtonWithCounter, showBottomButtonWithArrow, composeViewHeight, - onClickArrowDown = { - scope.launch { listState.animateScrollToItem(0) } - }, - onClickCounter = { - val firstVisibleOffset = (-maxHeight.value * 0.8).toInt() - scope.launch { listState.animateScrollToItem(kotlin.math.max(0, bottomUnreadCount.value - 1), firstVisibleOffset) } - } + onClick = { scope.launch { tryBlockAndSetLoadingMore(loadingMoreItems) { listState.value.animateScrollToItem(0) } } } ) // Don't show top FAB if is in search if (searchValue.value.isNotEmpty()) return val fabSize = 56.dp - val topUnreadCount = remember { derivedStateOf { unreadCount.value - bottomUnreadCount.value } } + val topUnreadCount = remember { derivedStateOf { if (bottomUnreadCount.value >= 0) (unreadCount.value - bottomUnreadCount.value).coerceAtLeast(0) else 0 } } val showDropDown = remember { mutableStateOf(false) } TopEndFloatingButton( Modifier.padding(end = DEFAULT_PADDING, top = 24.dp + topPaddingToContent()).align(Alignment.TopEnd), topUnreadCount, - onClick = { scope.launch { listState.animateScrollBy(maxHeight.value.toFloat()) } }, + onClick = { + val index = mergedItems.value.items.indexOfLast { it.hasUnread() } + if (index != -1) { + // scroll to the top unread item + scope.launch { tryBlockAndSetLoadingMore(loadingMoreItems) { listState.value.animateScrollToItem(index + 1, -maxHeight.value) } } + } + }, onLongClick = { showDropDown.value = true } ) @@ -1381,10 +1440,11 @@ fun BoxScope.FloatingButtons( generalGetString(MR.strings.mark_read), painterResource(MR.images.ic_check), onClick = { - val minUnreadItemId = chatModel.chats.value.firstOrNull { it.remoteHostId == remoteHostId && it.id == chatInfo.id }?.chatStats?.minUnreadItemId ?: return@ItemAction + val chat = chatModel.chats.value.firstOrNull { it.remoteHostId == remoteHostId && it.id == chatInfo.id } ?: return@ItemAction + val minUnreadItemId = chat.chatStats.minUnreadItemId markRead( - CC.ItemRange(minUnreadItemId, chatItems.value[chatItems.value.size - listState.layoutInfo.visibleItemsInfo.lastIndex - 1].id - 1), - bottomUnreadCount.value + CC.ItemRange(minUnreadItemId, chat.chatItems.lastOrNull()?.id ?: return@ItemAction), + 0 ) showDropDown.value = false }) @@ -1395,46 +1455,106 @@ fun BoxScope.FloatingButtons( @Composable fun PreloadItems( chatId: String, - listState: LazyListState, - remaining: Int = 10, - onLoadMore: (ChatId) -> Unit, + ignoreLoadingRequests: MutableSet, + mergedItems: State, + listState: State, + remaining: Int, + loadItems: suspend (ChatId, ChatPagination) -> Boolean, ) { // Prevent situation when initial load and load more happens one after another after selecting a chat with long scroll position from previous selection val allowLoad = remember { mutableStateOf(false) } val chatId = rememberUpdatedState(chatId) - val onLoadMore = rememberUpdatedState(onLoadMore) - LaunchedEffect(Unit) { - snapshotFlow { chatId.value } - .filterNotNull() - .collect { - allowLoad.value = listState.layoutInfo.totalItemsCount == listState.layoutInfo.visibleItemsInfo.size - delay(500) - allowLoad.value = true + val loadItems = rememberUpdatedState(loadItems) + val ignoreLoadingRequests = rememberUpdatedState(ignoreLoadingRequests) + PreloadItemsBefore(allowLoad, chatId, ignoreLoadingRequests, mergedItems, listState, remaining, loadItems) + PreloadItemsAfter(allowLoad, chatId, mergedItems, listState, remaining, loadItems) +} + +@Composable +private fun PreloadItemsBefore( + allowLoad: State, + chatId: State, + ignoreLoadingRequests: State>, + mergedItems: State, + listState: State, + remaining: Int, + loadItems: State Boolean>, +) { + KeyChangeEffect(allowLoad.value, chatId.value) { + snapshotFlow { listState.value.firstVisibleItemIndex } + .distinctUntilChanged() + .map { firstVisibleIndex -> + val splits = mergedItems.value.splits + val lastVisibleIndex = (listState.value.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0) + var lastIndexToLoadFrom: Int? = findLastIndexToLoadFromInSplits(firstVisibleIndex, lastVisibleIndex, remaining, splits) + val items = chatModel.chatItems.value + if (splits.isEmpty() && items.isNotEmpty() && lastVisibleIndex > mergedItems.value.items.size - remaining && items.size >= ChatPagination.INITIAL_COUNT) { + lastIndexToLoadFrom = items.lastIndex + } + if (allowLoad.value && lastIndexToLoadFrom != null) { + items.getOrNull(items.lastIndex - lastIndexToLoadFrom)?.id + } else { + null + } } - } - KeyChangeEffect(allowLoad.value) { - snapshotFlow { - val lInfo = listState.layoutInfo - val totalItemsNumber = lInfo.totalItemsCount - val lastVisibleItemIndex = (lInfo.visibleItemsInfo.lastOrNull()?.index ?: 0) + 1 - if (allowLoad.value && lastVisibleItemIndex > (totalItemsNumber - remaining) && totalItemsNumber >= ChatPagination.INITIAL_COUNT) - totalItemsNumber + ChatPagination.PRELOAD_COUNT - else - 0 - } - .filter { it > 0 } - .collect { - onLoadMore.value(chatId.value) + .filterNotNull() + .filter { !ignoreLoadingRequests.value.contains(it) } + .collect { loadFromItemId -> + withBGApi { + val sizeWas = chatModel.chatItems.value.size + val firstItemIdWas = chatModel.chatItems.value.firstOrNull()?.id + val triedToLoad = loadItems.value(chatId.value, ChatPagination.Before(loadFromItemId, ChatPagination.PRELOAD_COUNT)) + if (triedToLoad && sizeWas == chatModel.chatItems.value.size && firstItemIdWas == chatModel.chatItems.value.firstOrNull()?.id) { + ignoreLoadingRequests.value.add(loadFromItemId) + } + } } } } -private fun showMemberImage(member: GroupMember, prevItem: ChatItem?): Boolean = - when (val dir = prevItem?.chatDir) { - is CIDirection.GroupSnd -> true - is CIDirection.GroupRcv -> dir.groupMember.groupMemberId != member.groupMemberId - else -> false +@Composable +private fun PreloadItemsAfter( + allowLoad: MutableState, + chatId: State, + mergedItems: State, + listState: State, + remaining: Int, + loadItems: State Boolean>, +) { + LaunchedEffect(Unit) { + snapshotFlow { chatId.value } + .distinctUntilChanged() + .filterNotNull() + .collect { + allowLoad.value = listState.value.layoutInfo.totalItemsCount == listState.value.layoutInfo.visibleItemsInfo.size + delay(500) + allowLoad.value = true + } } + LaunchedEffect(chatId.value) { + launch { + snapshotFlow { listState.value.firstVisibleItemIndex } + .distinctUntilChanged() + .map { firstVisibleIndex -> + val items = chatModel.chatItems.value + val splits = mergedItems.value.splits + val split = splits.lastOrNull { it.indexRangeInParentItems.contains(firstVisibleIndex) } + // we're inside a splitRange (top --- [end of the splitRange --- we're here --- start of the splitRange] --- bottom) + if (split != null && split.indexRangeInParentItems.first + remaining > firstVisibleIndex) { + items.getOrNull(items.lastIndex - split.indexRangeInReversed.first)?.id + } else { + null + } + } + .filterNotNull() + .collect { loadFromItemId -> + withBGApi { + loadItems.value(chatId.value, ChatPagination.After(loadFromItemId, ChatPagination.PRELOAD_COUNT)) + } + } + } + } +} val MEMBER_IMAGE_SIZE: Dp = 37.dp @@ -1449,8 +1569,8 @@ private fun TopEndFloatingButton( unreadCount: State, onClick: () -> Unit, onLongClick: () -> Unit -) = when { - unreadCount.value > 0 -> { +) { + if (unreadCount.value > 0) { val interactionSource = interactionSourceWithDetection(onClick, onLongClick) FloatingActionButton( {}, // no action here @@ -1466,8 +1586,6 @@ private fun TopEndFloatingButton( ) } } - else -> { - } } @Composable @@ -1483,52 +1601,43 @@ fun topPaddingToContent(): Dp { @Composable private fun FloatingDate( modifier: Modifier, - listState: LazyListState, + mergedItems: State, + listState: State, ) { - var nearBottomIndex by remember { mutableStateOf(-1) } - var isNearBottom by remember { mutableStateOf(true) } + val isNearBottom = remember(chatModel.chatId) { mutableStateOf(listState.value.firstVisibleItemIndex == 0) } + val nearBottomIndex = remember(chatModel.chatId) { mutableStateOf(if (isNearBottom.value) -1 else 0) } + val showDate = remember(chatModel.chatId) { mutableStateOf(false) } + val density = LocalDensity.current.density + val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent().roundToPx() }) + val fontSizeSqrtMultiplier = fontSizeSqrtMultiplier val lastVisibleItemDate = remember { derivedStateOf { - if (listState.layoutInfo.visibleItemsInfo.lastIndex >= 0) { - val lastFullyVisibleOffset = listState.layoutInfo.viewportEndOffset - val lastVisibleChatItemIndex = chatModel.chatItems.value.lastIndex - (listState.layoutInfo.visibleItemsInfo.lastOrNull { item -> item.offset + item.size <= lastFullyVisibleOffset && item.size > 0 }?.index ?: 0) - val item = chatModel.chatItems.value.getOrNull(lastVisibleChatItemIndex) + if (listState.value.layoutInfo.visibleItemsInfo.lastIndex >= 0) { + val lastVisibleChatItem = lastFullyVisibleIemInListState(topPaddingToContentPx, density, fontSizeSqrtMultiplier, mergedItems, listState) val timeZone = TimeZone.currentSystemDefault() - item?.meta?.itemTs?.toLocalDateTime(timeZone)?.date?.atStartOfDayIn(timeZone) + lastVisibleChatItem?.meta?.itemTs?.toLocalDateTime(timeZone)?.date?.atStartOfDayIn(timeZone) } else { null } } } - val showDate = remember { mutableStateOf(false) } - LaunchedEffect(Unit) { - launch { - snapshotFlow { chatModel.chatId.value } - .distinctUntilChanged() - .collect { - showDate.value = false - isNearBottom = true - nearBottomIndex = -1 - } - } - } LaunchedEffect(Unit) { - snapshotFlow { listState.layoutInfo.visibleItemsInfo } + snapshotFlow { listState.value.layoutInfo.visibleItemsInfo } .collect { visibleItemsInfo -> if (visibleItemsInfo.find { it.index == 0 } != null) { var elapsedOffset = 0 for (it in visibleItemsInfo) { - if (elapsedOffset >= listState.layoutInfo.viewportSize.height / 2.5) { - nearBottomIndex = it.index + if (elapsedOffset >= listState.value.layoutInfo.viewportSize.height / 2.5) { + nearBottomIndex.value = it.index break; } elapsedOffset += it.size } } - isNearBottom = if (nearBottomIndex == -1) true else (visibleItemsInfo.firstOrNull()?.index ?: 0) <= nearBottomIndex + isNearBottom.value = if (nearBottomIndex.value == -1) true else (visibleItemsInfo.firstOrNull()?.index ?: 0) <= nearBottomIndex.value } } @@ -1536,7 +1645,7 @@ private fun FloatingDate( if (isVisible) { val now = Clock.System.now() val date = lastVisibleItemDate.value - if (!isNearBottom && !showDate.value && date != null && getTimestampDateText(date) != getTimestampDateText(now)) { + if (!isNearBottom.value && !showDate.value && date != null && getTimestampDateText(date) != getTimestampDateText(now)) { showDate.value = true } } else if (showDate.value) { @@ -1546,7 +1655,7 @@ private fun FloatingDate( LaunchedEffect(Unit) { var hideDateWhenNotScrolling: Job = Job() - snapshotFlow { listState.firstVisibleItemScrollOffset } + snapshotFlow { listState.value.firstVisibleItemScrollOffset } .collect { setDateVisibility(true) hideDateWhenNotScrolling.cancel() @@ -1654,6 +1763,93 @@ private fun DateSeparator(date: Instant) { ) } +@Composable +private fun MarkItemsReadAfterDelay( + itemKey: String, + itemIdStart: Long, + itemIdEnd: Long, + finishedInitialComposition: State, + chatId: ChatId, + listState: State, + markRead: (CC.ItemRange, unreadCountAfter: Int?) -> Unit +) { + // items can be "visible" in terms of LazyColumn but hidden behind compose view/appBar. So don't count such item as visible and not mark read + val itemIsPartiallyAboveCompose = remember { derivedStateOf { + val item = listState.value.layoutInfo.visibleItemsInfo.firstOrNull { it.key == itemKey } + if (item != null) { + item.offset >= 0 || -item.offset < item.size + } else { + false + } + } } + LaunchedEffect(itemIsPartiallyAboveCompose.value, itemIdStart, itemIdEnd, finishedInitialComposition.value, chatId) { + if (chatId != ChatModel.chatId.value || !itemIsPartiallyAboveCompose.value || !finishedInitialComposition.value) return@LaunchedEffect + + delay(600L) + markRead(CC.ItemRange(itemIdStart, itemIdEnd), null) + } +} + +private fun oldestPartiallyVisibleListItemInListStateOrNull(topPaddingToContentPx: State, mergedItems: State, listState: State): ListItem? { + val lastFullyVisibleOffset = listState.value.layoutInfo.viewportEndOffset - topPaddingToContentPx.value + return mergedItems.value.items.getOrNull((listState.value.layoutInfo.visibleItemsInfo.lastOrNull { item -> + item.offset <= lastFullyVisibleOffset + }?.index ?: listState.value.layoutInfo.visibleItemsInfo.lastOrNull()?.index) ?: -1)?.oldest() +} + +private fun lastFullyVisibleIemInListState(topPaddingToContentPx: State, density: Float, fontSizeSqrtMultiplier: Float, mergedItems: State, listState: State): ChatItem? { + val lastFullyVisibleOffsetMinusFloatingHeight = listState.value.layoutInfo.viewportEndOffset - topPaddingToContentPx.value - 50 * density * fontSizeSqrtMultiplier + return mergedItems.value.items.getOrNull( + (listState.value.layoutInfo.visibleItemsInfo.lastOrNull { item -> + item.offset <= lastFullyVisibleOffsetMinusFloatingHeight && item.size > 0 + } + ?.index + ?: listState.value.layoutInfo.visibleItemsInfo.lastOrNull()?.index) + ?: -1)?.newest()?.item +} + +private fun scrollToItem( + loadingMoreItems: MutableState, + chatInfo: State, + maxHeight: State, + scope: CoroutineScope, + reversedChatItems: State>, + mergedItems: State, + listState: State, + loadMessages: suspend (ChatId, ChatPagination, ActiveChatState, visibleItemIndexesNonReversed: () -> IntRange) -> Unit, +): (Long) -> Unit = { itemId: Long -> + withApi { + try { + var index = mergedItems.value.indexInParentItems[itemId] ?: -1 + // setting it to 'loading' even if the item is loaded because in rare cases when the resulting item is near the top, scrolling to + // it will trigger loading more items and will scroll to incorrect position (because of trimming) + loadingMoreItems.value = true + if (index == -1) { + val pagination = ChatPagination.Around(itemId, ChatPagination.PRELOAD_COUNT * 2) + val oldSize = reversedChatItems.value.size + withContext(Dispatchers.Default) { + loadMessages(chatInfo.value.id, pagination, chatModel.chatState) { + visibleItemIndexesNonReversed(mergedItems, listState.value) + } + } + var repeatsLeft = 50 + while (oldSize == reversedChatItems.value.size && repeatsLeft > 0) { + delay(20) + repeatsLeft-- + } + index = mergedItems.value.indexInParentItems[itemId] ?: -1 + } + if (index != -1) { + withContext(scope.coroutineContext) { + listState.value.animateScrollToItem(min(reversedChatItems.value.lastIndex, index + 1), -maxHeight.value) + } + } + } finally { + loadingMoreItems.value = false + } + } +} + val chatViewScrollState = MutableStateFlow(false) fun addGroupMembers(groupInfo: GroupInfo, rhId: Long?, view: Any? = null, close: (() -> Unit)? = null) { @@ -1684,12 +1880,11 @@ private fun BoxScope.BottomEndFloatingButton( showButtonWithCounter: State, showButtonWithArrow: State, composeViewHeight: State, - onClickArrowDown: () -> Unit, - onClickCounter: () -> Unit + onClick: () -> Unit ) = when { showButtonWithCounter.value -> { FloatingActionButton( - onClick = onClickCounter, + onClick = onClick, elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp), modifier = Modifier.padding(end = DEFAULT_PADDING, bottom = DEFAULT_PADDING + composeViewHeight.value).align(Alignment.BottomEnd).size(48.dp), backgroundColor = MaterialTheme.colors.secondaryVariant, @@ -1703,7 +1898,7 @@ private fun BoxScope.BottomEndFloatingButton( } showButtonWithArrow.value -> { FloatingActionButton( - onClick = onClickArrowDown, + onClick = onClick, elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp), modifier = Modifier.padding(end = DEFAULT_PADDING, bottom = DEFAULT_PADDING + composeViewHeight.value).align(Alignment.BottomEnd).size(48.dp), backgroundColor = MaterialTheme.colors.secondaryVariant, @@ -1861,6 +2056,26 @@ fun Modifier.chatViewBackgroundModifier( ) } +private fun findLastIndexToLoadFromInSplits(firstVisibleIndex: Int, lastVisibleIndex: Int, remaining: Int, splits: List): Int? { + for (split in splits) { + // before any split + if (split.indexRangeInParentItems.first > firstVisibleIndex) { + if (lastVisibleIndex > (split.indexRangeInParentItems.first - remaining)) { + return split.indexRangeInReversed.first - 1 + } + break + } + val containsInRange = split.indexRangeInParentItems.contains(firstVisibleIndex) + if (containsInRange) { + if (lastVisibleIndex > (split.indexRangeInParentItems.last - remaining)) { + return split.indexRangeInReversed.last + } + break + } + } + return null +} + fun chatViewItemsRange(currIndex: Int?, prevHidden: Int?): IntRange? = if (currIndex != null && prevHidden != null && prevHidden > currIndex) { currIndex..prevHidden @@ -1868,6 +2083,16 @@ fun chatViewItemsRange(currIndex: Int?, prevHidden: Int?): IntRange? = null } +private suspend fun tryBlockAndSetLoadingMore(loadingMoreItems: MutableState, block: suspend () -> Unit) { + try { + loadingMoreItems.value = true + block() + } catch (e: Exception) { + Log.e(TAG, e.stackTraceToString()) + } finally { + loadingMoreItems.value = false + } +} sealed class ProviderMedia { data class Image(val data: ByteArray, val image: ImageBitmap): ProviderMedia() @@ -1875,7 +2100,6 @@ sealed class ProviderMedia { } fun providerForGallery( - listStateIndex: Int, chatItems: List, cItemId: Long, scrollTo: (Int) -> Unit @@ -1943,15 +2167,18 @@ fun providerForGallery( override fun onDismiss(index: Int) { val internalIndex = initialIndex - index - val indexInChatItems = item(internalIndex, initialChatId)?.first ?: return + val item = item(internalIndex, initialChatId) + val indexInChatItems = item?.first ?: return val indexInReversed = chatItems.lastIndex - indexInChatItems // Do not scroll to active item, just to different items - if (indexInReversed == listStateIndex) return + if (item.second.id == cItemId) return scrollTo(indexInReversed) } } } +private fun keyForItem(item: ChatItem): String = (item.id to item.meta.createdAt.toEpochMilliseconds()).toString() + private fun ViewConfiguration.bigTouchSlop(slop: Float = 50f) = object: ViewConfiguration { override val longPressTimeoutMillis get() = @@ -2042,23 +2269,38 @@ private fun handleForwardConfirmation( ) } -private fun getItemSeparation(chatItem: ChatItem, nextItem: ChatItem?): ItemSeparation { - if (nextItem == null) { +private fun getItemSeparation(chatItem: ChatItem, prevItem: ChatItem?): ItemSeparation { + if (prevItem == null) { return ItemSeparation(timestamp = true, largeGap = true, date = null) } + val sameMemberAndDirection = if (prevItem.chatDir is GroupRcv && chatItem.chatDir is GroupRcv) { + chatItem.chatDir.groupMember.groupMemberId == prevItem.chatDir.groupMember.groupMemberId + } else chatItem.chatDir.sent == prevItem.chatDir.sent + val largeGap = !sameMemberAndDirection || (abs(prevItem.meta.createdAt.epochSeconds - chatItem.meta.createdAt.epochSeconds) >= 60) + + return ItemSeparation( + timestamp = largeGap || prevItem.meta.timestampText != chatItem.meta.timestampText, + largeGap = largeGap, + date = if (getTimestampDateText(chatItem.meta.itemTs) == getTimestampDateText(prevItem.meta.itemTs)) null else prevItem.meta.itemTs + ) +} + +private fun getItemSeparationLargeGap(chatItem: ChatItem, nextItem: ChatItem?): Boolean { + if (nextItem == null) { + return true + } + val sameMemberAndDirection = if (nextItem.chatDir is GroupRcv && chatItem.chatDir is GroupRcv) { chatItem.chatDir.groupMember.groupMemberId == nextItem.chatDir.groupMember.groupMemberId } else chatItem.chatDir.sent == nextItem.chatDir.sent - val largeGap = !sameMemberAndDirection || (abs(nextItem.meta.createdAt.epochSeconds - chatItem.meta.createdAt.epochSeconds) >= 60) - - return ItemSeparation( - timestamp = largeGap || nextItem.meta.timestampText != chatItem.meta.timestampText, - largeGap = largeGap, - date = if (getTimestampDateText(chatItem.meta.itemTs) == getTimestampDateText(nextItem.meta.itemTs)) null else nextItem.meta.itemTs - ) + return !sameMemberAndDirection || (abs(nextItem.meta.createdAt.epochSeconds - chatItem.meta.createdAt.epochSeconds) >= 60) } +private fun shouldShowAvatar(current: ChatItem, older: ChatItem?) = + current.chatDir is CIDirection.GroupRcv && (older == null || (older.chatDir !is CIDirection.GroupRcv || older.chatDir.groupMember.memberId != current.chatDir.groupMember.memberId)) + + @Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, @@ -2102,7 +2344,7 @@ fun PreviewChatLayout() { back = {}, info = {}, showMemberInfo = { _, _ -> }, - loadPrevMessages = {}, + loadMessages = { _, _, _, _ -> }, deleteMessage = { _, _ -> }, deleteMessages = { _ -> }, receiveFile = { _ -> }, @@ -2174,7 +2416,7 @@ fun PreviewGroupChatLayout() { back = {}, info = {}, showMemberInfo = { _, _ -> }, - loadPrevMessages = {}, + loadMessages = { _, _, _, _ -> }, deleteMessage = { _, _ -> }, deleteMessages = {}, receiveFile = { _ -> }, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index fd1d3ab92d..6cd46d49a6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -429,8 +429,8 @@ fun ComposeView( ttl = ttl ) - chatItems?.forEach { chatItem -> - withChats { + withChats { + chatItems?.forEach { chatItem -> addChatItem(rhId, chat.chatInfo, chatItem) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt index a03cff2bb0..a78dd36887 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt @@ -70,17 +70,9 @@ fun GroupMemberInfoView( getContactChat = { chatModel.getContactChat(it) }, openDirectChat = { withBGApi { - val c = chatModel.controller.apiGetChat(rhId, ChatType.Direct, it) - if (c != null) { - withChats { - if (chatModel.getContactChat(it) == null) { - addChat(c) - } - chatModel.chatItemStatuses.clear() - chatModel.chatItems.replaceAll(c.chatItems) - chatModel.chatId.value = c.id - closeAll() - } + apiLoadMessages(rhId, ChatType.Direct, it, ChatPagination.Initial(ChatPagination.INITIAL_COUNT), chatModel.chatState) + if (chatModel.getContactChat(it) != null) { + closeAll() } } }, @@ -92,7 +84,7 @@ fun GroupMemberInfoView( val memberChat = Chat(remoteHostId = rhId, ChatInfo.Direct(memberContact), chatItems = arrayListOf()) withChats { addChat(memberChat) - openLoadedChat(memberChat, chatModel) + openLoadedChat(memberChat) } closeAll() chatModel.setContactNetworkStatus(memberContact, NetworkStatus.Connected()) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIChatFeatureView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIChatFeatureView.kt index 19cc949543..9bb3cef1d7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIChatFeatureView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIChatFeatureView.kt @@ -21,7 +21,7 @@ fun CIChatFeatureView( feature: Feature, iconColor: Color, icon: Painter? = null, - revealed: MutableState, + revealed: State, showMenu: MutableState, ) { val merged = if (!revealed.value) mergedFeatures(chatItem, chatInfo) else emptyList() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVideoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVideoView.kt index ca93349092..9f7b5dc9c6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVideoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVideoView.kt @@ -431,6 +431,9 @@ fun VideoPreviewImageViewFullScreen(preview: ImageBitmap, onClick: () -> Unit, o @Composable expect fun LocalWindowWidth(): Dp +@Composable +expect fun LocalWindowHeight(): Dp + @Composable private fun progressIndicator() { CircularProgressIndicator( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index 3db9b55c5b..5096992c29 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -56,8 +56,8 @@ fun ChatItemView( imageProvider: (() -> ImageGalleryProvider)? = null, useLinkPreviews: Boolean, linkMode: SimplexLinkMode, - revealed: MutableState, - range: IntRange?, + revealed: State, + range: State, selectedChatItems: MutableState?>, fillMaxWidth: Boolean = true, selectChatItem: () -> Unit, @@ -79,6 +79,7 @@ fun ChatItemView( findModelMember: (String) -> GroupMember?, setReaction: (ChatInfo, ChatItem, Boolean, MsgReaction) -> Unit, showItemDetails: (ChatInfo, ChatItem) -> Unit, + reveal: (Boolean) -> Unit, developerTools: Boolean, showViaProxy: Boolean, showTimestamp: Boolean, @@ -91,7 +92,7 @@ fun ChatItemView( val showMenu = remember { mutableStateOf(false) } val fullDeleteAllowed = remember(cInfo) { cInfo.featureEnabled(ChatFeature.FullDelete) } val onLinkLongClick = { _: String -> showMenu.value = true } - val live = composeState.value.liveMessage != null + val live = remember { derivedStateOf { composeState.value.liveMessage != null } }.value Box( modifier = if (fillMaxWidth) Modifier.fillMaxWidth() else Modifier, @@ -275,7 +276,7 @@ fun ChatItemView( } ItemInfoAction(cInfo, cItem, showItemDetails, showMenu) if (revealed.value) { - HideItemAction(revealed, showMenu) + HideItemAction(revealed, showMenu, reveal) } if (cItem.meta.itemDeleted == null && cItem.file != null && cItem.file.cancelAction != null && !cItem.localNote) { CancelFileItemAction(cItem.file.fileId, showMenu, cancelFile = cancelFile, cancelAction = cItem.file.cancelAction) @@ -296,11 +297,11 @@ fun ChatItemView( cItem.meta.itemDeleted != null -> { DefaultDropdownMenu(showMenu) { if (revealed.value) { - HideItemAction(revealed, showMenu) + HideItemAction(revealed, showMenu, reveal) } else if (!cItem.isDeletedContent) { - RevealItemAction(revealed, showMenu) - } else if (range != null) { - ExpandItemAction(revealed, showMenu) + RevealItemAction(revealed, showMenu, reveal) + } else if (range.value != null) { + ExpandItemAction(revealed, showMenu, reveal) } ItemInfoAction(cInfo, cItem, showItemDetails, showMenu) DeleteItemAction(cItem, revealed, showMenu, questionText = deleteMessageQuestionText(), deleteMessage, deleteMessages) @@ -320,12 +321,12 @@ fun ChatItemView( } } } - cItem.mergeCategory != null && ((range?.count() ?: 0) > 1 || revealed.value) -> { + cItem.mergeCategory != null && ((range.value?.count() ?: 0) > 1 || revealed.value) -> { DefaultDropdownMenu(showMenu) { if (revealed.value) { - ShrinkItemAction(revealed, showMenu) + ShrinkItemAction(revealed, showMenu, reveal) } else { - ExpandItemAction(revealed, showMenu) + ExpandItemAction(revealed, showMenu, reveal) } DeleteItemAction(cItem, revealed, showMenu, questionText = deleteMessageQuestionText(), deleteMessage, deleteMessages) if (cItem.canBeDeletedForSelf) { @@ -350,7 +351,7 @@ fun ChatItemView( fun MarkedDeletedItemDropdownMenu() { DefaultDropdownMenu(showMenu) { if (!cItem.isDeletedContent) { - RevealItemAction(revealed, showMenu) + RevealItemAction(revealed, showMenu, reveal) } ItemInfoAction(cInfo, cItem, showItemDetails, showMenu) DeleteItemAction(cItem, revealed, showMenu, questionText = deleteMessageQuestionText(), deleteMessage, deleteMessages) @@ -623,7 +624,7 @@ fun ItemInfoAction( @Composable fun DeleteItemAction( cItem: ChatItem, - revealed: MutableState, + revealed: State, showMenu: MutableState, questionText: String, deleteMessage: (Long, CIDeleteMode) -> Unit, @@ -700,48 +701,48 @@ fun SelectItemAction( } @Composable -private fun RevealItemAction(revealed: MutableState, showMenu: MutableState) { +private fun RevealItemAction(revealed: State, showMenu: MutableState, reveal: (Boolean) -> Unit) { ItemAction( stringResource(MR.strings.reveal_verb), painterResource(MR.images.ic_visibility), onClick = { - revealed.value = true + reveal(true) showMenu.value = false } ) } @Composable -private fun HideItemAction(revealed: MutableState, showMenu: MutableState) { +private fun HideItemAction(revealed: State, showMenu: MutableState, reveal: (Boolean) -> Unit) { ItemAction( stringResource(MR.strings.hide_verb), painterResource(MR.images.ic_visibility_off), onClick = { - revealed.value = false + reveal(false) showMenu.value = false } ) } @Composable -private fun ExpandItemAction(revealed: MutableState, showMenu: MutableState) { +private fun ExpandItemAction(revealed: State, showMenu: MutableState, reveal: (Boolean) -> Unit) { ItemAction( stringResource(MR.strings.expand_verb), painterResource(MR.images.ic_expand_all), onClick = { - revealed.value = true + reveal(true) showMenu.value = false }, ) } @Composable -private fun ShrinkItemAction(revealed: MutableState, showMenu: MutableState) { +private fun ShrinkItemAction(revealed: State, showMenu: MutableState, reveal: (Boolean) -> Unit) { ItemAction( stringResource(MR.strings.hide_verb), painterResource(MR.images.ic_collapse_all), onClick = { - revealed.value = false + reveal(false) showMenu.value = false }, ) @@ -1063,7 +1064,7 @@ fun PreviewChatItemView( linkMode = SimplexLinkMode.DESCRIPTION, composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, revealed = remember { mutableStateOf(false) }, - range = 0..1, + range = remember { mutableStateOf(0..1) }, selectedChatItems = remember { mutableStateOf(setOf()) }, selectChatItem = {}, deleteMessage = { _, _ -> }, @@ -1084,6 +1085,7 @@ fun PreviewChatItemView( findModelMember = { null }, setReaction = { _, _, _, _ -> }, showItemDetails = { _, _ -> }, + reveal = {}, developerTools = false, showViaProxy = false, showTimestamp = true, @@ -1104,7 +1106,7 @@ fun PreviewChatItemViewDeletedContent() { linkMode = SimplexLinkMode.DESCRIPTION, composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, revealed = remember { mutableStateOf(false) }, - range = 0..1, + range = remember { mutableStateOf(0..1) }, selectedChatItems = remember { mutableStateOf(setOf()) }, selectChatItem = {}, deleteMessage = { _, _ -> }, @@ -1125,6 +1127,7 @@ fun PreviewChatItemViewDeletedContent() { findModelMember = { null }, setReaction = { _, _, _, _ -> }, showItemDetails = { _, _ -> }, + reveal = {}, developerTools = false, showViaProxy = false, preview = true, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt index f0480a5c50..cfaa3eced5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt @@ -129,7 +129,14 @@ fun FramedItemView( .fillMaxWidth() .combinedClickable( onLongClick = { showMenu.value = true }, - onClick = { scrollToItem(qi.itemId?: return@combinedClickable) } + onClick = { + val itemId = qi.itemId + if (itemId != null) { + scrollToItem(itemId) + } else { + showQuotedItemDoesNotExistAlert() + } + } ) .onRightClick { showMenu.value = true } ) { @@ -465,6 +472,13 @@ fun CenteredRowLayout( } } +fun showQuotedItemDoesNotExistAlert() { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.message_deleted_or_not_received_error_title), + text = generalGetString(MR.strings.message_deleted_or_not_received_error_desc) + ) +} + /* class EditedProvider: PreviewParameterProvider { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/MarkedDeletedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/MarkedDeletedItemView.kt index ea71895ce5..d2e19a37d6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/MarkedDeletedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/MarkedDeletedItemView.kt @@ -20,7 +20,7 @@ import dev.icerock.moko.resources.compose.stringResource import kotlinx.datetime.Clock @Composable -fun MarkedDeletedItemView(ci: ChatItem, timedMessagesTTL: Int?, revealed: MutableState, showViaProxy: Boolean, showTimestamp: Boolean) { +fun MarkedDeletedItemView(ci: ChatItem, timedMessagesTTL: Int?, revealed: State, showViaProxy: Boolean, showTimestamp: Boolean) { val sentColor = MaterialTheme.appColors.sentMessage val receivedColor = MaterialTheme.appColors.receivedMessage Surface( @@ -41,7 +41,7 @@ fun MarkedDeletedItemView(ci: ChatItem, timedMessagesTTL: Int?, revealed: Mutabl } @Composable -private fun MergedMarkedDeletedText(chatItem: ChatItem, revealed: MutableState) { +private fun MergedMarkedDeletedText(chatItem: ChatItem, revealed: State) { var i = getChatItemIndexOrNull(chatItem) val ciCategory = chatItem.mergeCategory val text = if (!revealed.value && ciCategory != null && i != null) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt index 3c7f1e781f..d071a9d4fd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt @@ -1,7 +1,6 @@ package chat.simplex.common.views.chatlist import SectionItemView -import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* @@ -14,6 +13,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.style.TextAlign @@ -33,6 +33,7 @@ import chat.simplex.common.views.newchat.* import chat.simplex.res.MR import kotlinx.coroutines.* import kotlinx.datetime.Clock +import kotlin.math.min @Composable fun ChatListNavLinkView(chat: Chat, nextChatSelected: State) { @@ -71,7 +72,7 @@ fun ChatListNavLinkView(chat: Chat, nextChatSelected: State) { ChatPreviewView(chat, showChatPreviews, chatModel.draft.value, chatModel.draftChatId.value, chatModel.currentUser.value?.profile?.displayName, contactNetworkStatus, disabled, linkMode, inProgress = false, progressByTimeout = false) } }, - click = { scope.launch { directChatAction(chat.remoteHostId, chat.chatInfo.contact, chatModel) } }, + click = { if (chatModel.chatId.value != chat.id) scope.launch { directChatAction(chat.remoteHostId, chat.chatInfo.contact, chatModel) } }, dropdownMenuItems = { tryOrShowError("${chat.id}ChatListNavLinkDropdown", error = {}) { ContactMenuItems(chat, chat.chatInfo.contact, chatModel, showMenu, showMarkRead) @@ -90,7 +91,7 @@ fun ChatListNavLinkView(chat: Chat, nextChatSelected: State) { ChatPreviewView(chat, showChatPreviews, chatModel.draft.value, chatModel.draftChatId.value, chatModel.currentUser.value?.profile?.displayName, null, disabled, linkMode, inProgress.value, progressByTimeout) } }, - click = { if (!inProgress.value) scope.launch { groupChatAction(chat.remoteHostId, chat.chatInfo.groupInfo, chatModel, inProgress) } }, + click = { if (!inProgress.value && chatModel.chatId.value != chat.id) scope.launch { groupChatAction(chat.remoteHostId, chat.chatInfo.groupInfo, chatModel, inProgress) } }, dropdownMenuItems = { tryOrShowError("${chat.id}ChatListNavLinkDropdown", error = {}) { GroupMenuItems(chat, chat.chatInfo.groupInfo, chatModel, showMenu, inProgress, showMarkRead) @@ -108,7 +109,7 @@ fun ChatListNavLinkView(chat: Chat, nextChatSelected: State) { ChatPreviewView(chat, showChatPreviews, chatModel.draft.value, chatModel.draftChatId.value, chatModel.currentUser.value?.profile?.displayName, null, disabled, linkMode, inProgress = false, progressByTimeout = false) } }, - click = { scope.launch { noteFolderChatAction(chat.remoteHostId, chat.chatInfo.noteFolder) } }, + click = { if (chatModel.chatId.value != chat.id) scope.launch { noteFolderChatAction(chat.remoteHostId, chat.chatInfo.noteFolder) } }, dropdownMenuItems = { tryOrShowError("${chat.id}ChatListNavLinkDropdown", error = {}) { NoteFolderMenuItems(chat, showMenu, showMarkRead) @@ -187,7 +188,7 @@ fun ErrorChatListItem() { suspend fun directChatAction(rhId: Long?, contact: Contact, chatModel: ChatModel) { when { contact.activeConn == null && contact.profile.contactLink != null && contact.active -> askCurrentOrIncognitoProfileConnectContactViaAddress(chatModel, rhId, contact, close = null, openChat = true) - else -> openChat(rhId, ChatInfo.Direct(contact), chatModel) + else -> openChat(rhId, ChatInfo.Direct(contact)) } } @@ -195,54 +196,31 @@ suspend fun groupChatAction(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatMo when (groupInfo.membership.memberStatus) { GroupMemberStatus.MemInvited -> acceptGroupInvitationAlertDialog(rhId, groupInfo, chatModel, inProgress) GroupMemberStatus.MemAccepted -> groupInvitationAcceptedAlert(rhId) - else -> openChat(rhId, ChatInfo.Group(groupInfo), chatModel) + else -> openChat(rhId, ChatInfo.Group(groupInfo)) } } -suspend fun noteFolderChatAction(rhId: Long?, noteFolder: NoteFolder) { - openChat(rhId, ChatInfo.Local(noteFolder), chatModel) -} +suspend fun noteFolderChatAction(rhId: Long?, noteFolder: NoteFolder) = openChat(rhId, ChatInfo.Local(noteFolder)) -suspend fun openDirectChat(rhId: Long?, contactId: Long, chatModel: ChatModel) = coroutineScope { - val chat = chatModel.controller.apiGetChat(rhId, ChatType.Direct, contactId) - if (chat != null && isActive) { - openLoadedChat(chat, chatModel) - } -} +suspend fun openDirectChat(rhId: Long?, contactId: Long) = openChat(rhId, ChatType.Direct, contactId) -suspend fun openGroupChat(rhId: Long?, groupId: Long, chatModel: ChatModel) = coroutineScope { - val chat = chatModel.controller.apiGetChat(rhId, ChatType.Group, groupId) - if (chat != null && isActive) { - openLoadedChat(chat, chatModel) - } -} +suspend fun openGroupChat(rhId: Long?, groupId: Long) = openChat(rhId, ChatType.Group, groupId) -suspend fun openChat(rhId: Long?, chatInfo: ChatInfo, chatModel: ChatModel) = coroutineScope { - val chat = chatModel.controller.apiGetChat(rhId, chatInfo.chatType, chatInfo.apiId) - if (chat != null && isActive) { - openLoadedChat(chat, chatModel) - } -} +suspend fun openChat(rhId: Long?, chatInfo: ChatInfo) = openChat(rhId, chatInfo.chatType, chatInfo.apiId) -fun openLoadedChat(chat: Chat, chatModel: ChatModel) { +private suspend fun openChat(rhId: Long?, chatType: ChatType, apiId: Long) = + apiLoadMessages(rhId, chatType, apiId, ChatPagination.Initial(ChatPagination.INITIAL_COUNT), chatModel.chatState) + +fun openLoadedChat(chat: Chat) { chatModel.chatItemStatuses.clear() chatModel.chatItems.replaceAll(chat.chatItems) chatModel.chatId.value = chat.chatInfo.id + chatModel.chatState.clear() } -suspend fun apiLoadPrevMessages(ch: Chat, chatModel: ChatModel, beforeChatItemId: Long, search: String) { - val chatInfo = ch.chatInfo - val pagination = ChatPagination.Before(beforeChatItemId, ChatPagination.PRELOAD_COUNT) - val chat = chatModel.controller.apiGetChat(ch.remoteHostId, chatInfo.chatType, chatInfo.apiId, pagination, search) ?: return - if (chatModel.chatId.value != chat.id) return - chatModel.chatItems.addAll(0, chat.chatItems) -} - -suspend fun apiFindMessages(ch: Chat, chatModel: ChatModel, search: String) { - val chatInfo = ch.chatInfo - val chat = chatModel.controller.apiGetChat(ch.remoteHostId, chatInfo.chatType, chatInfo.apiId, search = search) ?: return - if (chatModel.chatId.value != chat.id) return - chatModel.chatItems.replaceAll(chat.chatItems) +suspend fun apiFindMessages(ch: Chat, search: String) { + chatModel.chatItems.clearAndNotify() + apiLoadMessages(ch.remoteHostId, ch.chatInfo.chatType, ch.chatInfo.apiId, pagination = ChatPagination.Last(ChatPagination.INITIAL_COUNT), chatModel.chatState, search = search) } suspend fun setGroupMembers(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel) { @@ -724,7 +702,7 @@ fun askCurrentOrIncognitoProfileConnectContactViaAddress( close?.invoke() val ok = connectContactViaAddress(chatModel, rhId, contact.contactId, incognito = false) if (ok && openChat) { - openDirectChat(rhId, contact.contactId, chatModel) + openDirectChat(rhId, contact.contactId) } } }) { @@ -736,7 +714,7 @@ fun askCurrentOrIncognitoProfileConnectContactViaAddress( close?.invoke() val ok = connectContactViaAddress(chatModel, rhId, contact.contactId, incognito = true) if (ok && openChat) { - openDirectChat(rhId, contact.contactId, chatModel) + openDirectChat(rhId, contact.contactId) } } }) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt index d63e47bcdd..036768c6e7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt @@ -231,7 +231,7 @@ fun ChatPreviewView( fun chatItemContentPreview(chat: Chat, ci: ChatItem?) { val mc = ci?.content?.msgContent val provider by remember(chat.id, ci?.id, ci?.file?.fileStatus) { - mutableStateOf({ providerForGallery(0, chat.chatItems, ci?.id ?: 0) {} }) + mutableStateOf({ providerForGallery(chat.chatItems, ci?.id ?: 0) {} }) } val uriHandler = LocalUriHandler.current when (mc) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/contacts/ContactListNavView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/contacts/ContactListNavView.kt index 711fb1377d..0af8e7ca38 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/contacts/ContactListNavView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/contacts/ContactListNavView.kt @@ -21,7 +21,7 @@ fun onRequestAccepted(chat: Chat) { if (chatInfo is ChatInfo.Direct) { ModalManager.start.closeModals() if (chatInfo.contact.sndReady) { - openLoadedChat(chat, chatModel) + openLoadedChat(chat) } } } @@ -54,13 +54,13 @@ fun ContactListNavLinkView(chat: Chat, nextChatSelected: State, showDel when (contactType) { ContactType.RECENT -> { withApi { - openChat(rhId, chat.chatInfo, chatModel) + openChat(rhId, chat.chatInfo) ModalManager.start.closeModals() } } ContactType.CHAT_DELETED -> { withApi { - openChat(rhId, chat.chatInfo, chatModel) + openChat(rhId, chat.chatInfo) ModalManager.start.closeModals() } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index d36bd255e3..3dd7e673c4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -500,7 +500,7 @@ fun deleteChatDatabaseFilesAndState() { // Clear sensitive data on screen just in case ModalManager will fail to prevent hiding its modals while database encrypts itself chatModel.chatId.value = null - chatModel.chatItems.clear() + chatModel.chatItems.clearAndNotify() withLongRunningApi { withChats { chats.clear() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt index e1d3d6541a..6cecbe4979 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt @@ -44,7 +44,7 @@ fun AddGroupView(chatModel: ChatModel, rh: RemoteHostInfo?, close: () -> Unit, c if (groupInfo != null) { withChats { updateGroup(rhId = rhId, groupInfo) - chatModel.chatItems.clear() + chatModel.chatItems.clearAndNotify() chatModel.chatItemStatuses.clear() chatModel.chatId.value = groupInfo.id } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt index 6c18e47df3..7cd272c109 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt @@ -409,7 +409,7 @@ fun openKnownContact(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, co val c = chatModel.getContactChat(contact.contactId) if (c != null) { close?.invoke() - openDirectChat(rhId, contact.contactId, chatModel) + openDirectChat(rhId, contact.contactId) } } } @@ -490,7 +490,7 @@ fun openKnownGroup(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, grou val g = chatModel.getGroupChat(groupInfo.groupId) if (g != null) { close?.invoke() - openGroupChat(rhId, groupInfo.groupId, chatModel) + openGroupChat(rhId, groupInfo.groupId) } } } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 673100bd8d..7236b22563 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -276,6 +276,8 @@ Message delivery error Message delivery warning Most likely this contact has deleted the connection with you. + No message + This message was deleted or not received yet. Error: %1$s diff --git a/apps/multiplatform/common/src/commonTest/kotlin/chat/simplex/app/ChatItemsMergerTest.kt b/apps/multiplatform/common/src/commonTest/kotlin/chat/simplex/app/ChatItemsMergerTest.kt new file mode 100644 index 0000000000..18b17b25a9 --- /dev/null +++ b/apps/multiplatform/common/src/commonTest/kotlin/chat/simplex/app/ChatItemsMergerTest.kt @@ -0,0 +1,158 @@ +package chat.simplex.app + +import androidx.compose.runtime.mutableStateOf +import chat.simplex.common.model.* +import chat.simplex.common.views.chat.* +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.datetime.Clock +import kotlin.test.Test +import kotlin.test.assertEquals + +class ChatItemsMergerTest { + + @Test + fun testRecalculateSplitPositions() { + val oldItems = listOf(ChatItem.getSampleData(0), ChatItem.getSampleData(123L), ChatItem.getSampleData(124L), ChatItem.getSampleData(125L)) + + val splits1 = MutableStateFlow(listOf(123L)) + val chatState1 = ActiveChatState(splits = splits1) + val removed1 = listOf(oldItems[1]) + val newItems1 = oldItems - removed1 + val recalc1 = recalculateChatStatePositions(chatState1) + recalc1.removed(removed1.map { Triple(it.id, oldItems.indexOf(removed1[0]), it.isRcvNew) }, newItems1) + assertEquals(1, splits1.value.size) + assertEquals(124L, splits1.value.first()) + + val splits2 = MutableStateFlow(listOf(123L)) + val chatState2 = ActiveChatState(splits = splits2) + val removed2 = listOf(oldItems[1], oldItems[2]) + val newItems2 = oldItems - removed2 + val recalc2 = recalculateChatStatePositions(chatState2) + recalc2.removed(removed2.mapIndexed { index, it -> Triple(it.id, oldItems.indexOf(removed2[index]), it.isRcvNew) }, newItems2) + assertEquals(1, splits2.value.size) + assertEquals(125L, splits2.value.first()) + + val splits3 = MutableStateFlow(listOf(123L)) + val chatState3 = ActiveChatState(splits = splits3) + val removed3 = listOf(oldItems[1], oldItems[2], oldItems[3]) + val newItems3 = oldItems - removed3 + val recalc3 = recalculateChatStatePositions(chatState3) + recalc3.removed(removed3.mapIndexed { index, it -> Triple(it.id, oldItems.indexOf(removed3[index]), it.isRcvNew) }, newItems3) + assertEquals(0, splits3.value.size) + + val splits4 = MutableStateFlow(listOf(123L)) + val chatState4 = ActiveChatState(splits = splits4) + val recalc4 = recalculateChatStatePositions(chatState4) + recalc4.cleared() + assertEquals(0, splits4.value.size) + } + + @Test + fun testItemsMerging() { + val items = listOf( + ChatItem(CIDirection.DirectRcv(), CIMeta.getSample(100L, Clock.System.now(), text = ""), CIContent.SndGroupFeature(GroupFeature.Voice, GroupPreference(GroupFeatureEnabled.ON), memberRole_ = null), reactions = emptyList()), + ChatItem(CIDirection.DirectRcv(), CIMeta.getSample(99L, Clock.System.now(), text = ""), CIContent.SndGroupFeature(GroupFeature.FullDelete, GroupPreference(GroupFeatureEnabled.ON), memberRole_ = null), reactions = emptyList()), + ChatItem(CIDirection.DirectRcv(), CIMeta.getSample(98L, Clock.System.now(), text = "", itemDeleted = CIDeleted.Deleted(null)), CIContent.RcvDeleted(CIDeleteMode.cidmBroadcast), reactions = emptyList()), + ChatItem(CIDirection.DirectRcv(), CIMeta.getSample(97L, Clock.System.now(), text = "", itemDeleted = CIDeleted.Deleted(null)), CIContent.RcvDeleted(CIDeleteMode.cidmBroadcast), reactions = emptyList()), + ChatItem(CIDirection.DirectRcv(), CIMeta.getSample(96L, Clock.System.now(), text = ""), CIContent.RcvMsgContent(MsgContent.MCText("")), reactions = emptyList()), + ChatItem(CIDirection.DirectRcv(), CIMeta.getSample(95L, Clock.System.now(), text = ""), CIContent.RcvMsgContent(MsgContent.MCText("")), reactions = emptyList()), + ChatItem(CIDirection.DirectRcv(), CIMeta.getSample(94L, Clock.System.now(), text = ""), CIContent.RcvMsgContent(MsgContent.MCText("")), reactions = emptyList()), + ) + + val unreadCount = mutableStateOf(0) + val chatState = ActiveChatState() + val merged1 = MergedItems.create(items, unreadCount, emptySet(), chatState) + assertEquals( + listOf( + listOf(0, false, + listOf( + listOf(0, 100, CIMergeCategory.ChatFeature), + listOf(1, 99, CIMergeCategory.ChatFeature) + ) + ), + listOf(2, false, + listOf( + listOf(0, 98, CIMergeCategory.RcvItemDeleted), + listOf(1, 97, CIMergeCategory.RcvItemDeleted) + ) + ), + listOf(4, true, + listOf( + listOf(0, 96, null), + ) + ), + listOf(5, true, + listOf( + listOf(0, 95, null), + ) + ), + listOf(6, true, + listOf( + listOf(0, 94, null) + ) + ) + ).toList().toString(), + merged1.items.map { + listOf( + it.startIndexInReversedItems, + if (it is MergedItem.Grouped) it.revealed else true, + when (it) { + is MergedItem.Grouped -> it.items.mapIndexed { index, listItem -> + listOf(index, listItem.item.id, listItem.item.mergeCategory) + } + is MergedItem.Single -> listOf(listOf(0, it.item.item.id, it.item.item.mergeCategory)) + } + ) + }.toString() + ) + + val merged2 = MergedItems.create(items, unreadCount, setOf(98L, 97L), chatState) + assertEquals( + listOf( + listOf(0, false, + listOf( + listOf(0, 100, CIMergeCategory.ChatFeature), + listOf(1, 99, CIMergeCategory.ChatFeature) + ) + ), + listOf(2, true, + listOf( + listOf(0, 98, CIMergeCategory.RcvItemDeleted), + ) + ), + listOf(3, true, + listOf( + listOf(0, 97, CIMergeCategory.RcvItemDeleted) + ) + ), + listOf(4, true, + listOf( + listOf(0, 96, null), + ) + ), + listOf(5, true, + listOf( + listOf(0, 95, null), + ) + ), + listOf(6, true, + listOf( + listOf(0, 94, null) + ) + ) + ).toList().toString(), + merged2.items.map { + listOf( + it.startIndexInReversedItems, + if (it is MergedItem.Grouped) it.revealed else true, + when (it) { + is MergedItem.Grouped -> it.items.mapIndexed { index, listItem -> + listOf(index, listItem.item.id, listItem.item.mergeCategory) + } + is MergedItem.Single -> listOf(listOf(0, it.item.item.id, it.item.item.mergeCategory)) + } + ) + }.toString() + ) + } +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt index 25d85a6b7d..2702862e47 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt @@ -56,7 +56,7 @@ fun showApp() { } else { // The last possible cause that can be closed chatModel.chatId.value = null - chatModel.chatItems.clear() + chatModel.chatItems.clearAndNotify() } chatModel.activeCall.value?.let { withBGApi { diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/CIVideoView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/CIVideoView.desktop.kt index 2c063b5888..bdfbf6863f 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/CIVideoView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/CIVideoView.desktop.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalWindowInfo import androidx.compose.ui.unit.Dp import chat.simplex.common.platform.* import chat.simplex.common.simplexWindowState @@ -37,3 +38,6 @@ actual fun LocalWindowWidth(): Dp = with(LocalDensity.current) { simplexWindowState.windowState.size.width } } + +@Composable +actual fun LocalWindowHeight(): Dp = with(LocalDensity.current) { LocalWindowInfo.current.containerSize.height.toDp() } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt index a1df7091d6..3fa78bbbb5 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt @@ -45,7 +45,7 @@ private fun ActiveCallInteractiveAreaOneHand(call: Call, showMenu: MutableState< val chat = chatModel.getChat(call.contact.id) if (chat != null) { withBGApi { - openChat(chat.remoteHostId, chat.chatInfo, chatModel) + openChat(chat.remoteHostId, chat.chatInfo) } } }, @@ -110,7 +110,7 @@ private fun ActiveCallInteractiveAreaNonOneHand(call: Call, showMenu: MutableSta val chat = chatModel.getChat(call.contact.id) if (chat != null) { withBGApi { - openChat(chat.remoteHostId, chat.chatInfo, chatModel) + openChat(chat.remoteHostId, chat.chatInfo) } } }, From 522f99aadd20d6fe376b97678af85a74d76e5e43 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 20 Nov 2024 22:39:13 +0000 Subject: [PATCH 091/567] directory service: notify admins about group registration events (#5223) --- .../src/Directory/Service.hs | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs index 2c18d4df27..afcdb233e8 100644 --- a/apps/simplex-directory-service/src/Directory/Service.hs +++ b/apps/simplex-directory-service/src/Directory/Service.hs @@ -105,8 +105,11 @@ directoryService st DirectoryOpts {adminUsers, superUsers, serviceName, searchRe SDRSuperUser -> deSuperUserCommand ct ciId cmd DELogChatResponse r -> logInfo r where + withAdminUsers action = void . forkIO $ do + forM_ superUsers $ \KnownContact {contactId} -> action contactId + forM_ adminUsers $ \KnownContact {contactId} -> action contactId withSuperUsers action = void . forkIO $ forM_ superUsers $ \KnownContact {contactId} -> action contactId - notifySuperUsers s = withSuperUsers $ \contactId -> sendMessage' cc contactId s + notifyAdminUsers s = withAdminUsers $ \contactId -> sendMessage' cc contactId s notifyOwner GroupReg {dbContactId} = sendMessage' cc dbContactId ctId `isOwner` GroupReg {dbContactId} = ctId == dbContactId withGroupReg GroupInfo {groupId, localDisplayName} err action = do @@ -288,16 +291,16 @@ directoryService st DirectoryOpts {adminUsers, superUsers, serviceName, searchRe notifyOwner gr $ "The group profile is updated " <> userGroupRef <> ", but no link is added to the welcome message.\n\nThe group will remain hidden from the directory until the group link is added and the group is re-approved." GPServiceLinkRemoved -> do notifyOwner gr $ "The group link for " <> userGroupRef <> " is removed from the welcome message.\n\nThe group is hidden from the directory until the group link is added and the group is re-approved." - notifySuperUsers $ "The group link is removed from " <> groupRef <> ", de-listed." + notifyAdminUsers $ "The group link is removed from " <> groupRef <> ", de-listed." GPServiceLinkAdded -> do setGroupStatus st gr $ GRSPendingApproval n' notifyOwner gr $ "The group link is added to " <> userGroupRef <> "!\nIt is hidden from the directory until approved." - notifySuperUsers $ "The group link is added to " <> groupRef <> "." + notifyAdminUsers $ "The group link is added to " <> groupRef <> "." checkRolesSendToApprove gr n' GPHasServiceLink -> do setGroupStatus st gr $ GRSPendingApproval n' notifyOwner gr $ "The group " <> userGroupRef <> " is updated!\nIt is hidden from the directory until approved." - notifySuperUsers $ "The group " <> groupRef <> " is updated." + notifyAdminUsers $ "The group " <> groupRef <> " is updated." checkRolesSendToApprove gr n' GPServiceLinkError -> logError $ "Error: no group link for " <> groupRef <> " pending approval." groupProfileUpdate = profileUpdate <$> sendChatCmd cc (APIGetGroupLink groupId) @@ -329,7 +332,7 @@ directoryService st DirectoryOpts {adminUsers, superUsers, serviceName, searchRe maybe ("The group ID " <> tshow dbGroupId <> " submitted: ") (\c -> localDisplayName' c <> " submitted the group ID " <> tshow dbGroupId <> ": ") ct_ <> ("\n" <> groupInfoText p <> "\n" <> membersStr <> "\nTo approve send:") msg = maybe (MCText text) (\image -> MCImage {text, image}) image' - withSuperUsers $ \cId -> do + withAdminUsers $ \cId -> do sendComposedMessage' cc cId Nothing msg sendMessage' cc cId $ "/approve " <> tshow dbGroupId <> ":" <> viewName displayName <> " " <> tshow gaId @@ -344,14 +347,14 @@ directoryService st DirectoryOpts {adminUsers, superUsers, serviceName, searchRe GRSSuspendedBadRoles -> when (rStatus == GRSOk) $ do setGroupStatus st gr GRSActive notifyOwner gr $ uCtRole <> ".\n\nThe group is listed in the directory again." - notifySuperUsers $ "The group " <> groupRef <> " is listed " <> suCtRole + notifyAdminUsers $ "The group " <> groupRef <> " is listed " <> suCtRole GRSPendingApproval gaId -> when (rStatus == GRSOk) $ do sendToApprove g gr gaId notifyOwner gr $ uCtRole <> ".\n\nThe group is submitted for approval." GRSActive -> when (rStatus /= GRSOk) $ do setGroupStatus st gr GRSSuspendedBadRoles notifyOwner gr $ uCtRole <> ".\n\nThe group is no longer listed in the directory." - notifySuperUsers $ "The group " <> groupRef <> " is de-listed " <> suCtRole + notifyAdminUsers $ "The group " <> groupRef <> " is de-listed " <> suCtRole _ -> pure () where rStatus = groupRolesStatus contactRole serviceRole @@ -370,7 +373,7 @@ directoryService st DirectoryOpts {adminUsers, superUsers, serviceName, searchRe whenContactIsOwner gr $ do setGroupStatus st gr GRSActive notifyOwner gr $ uSrvRole <> ".\n\nThe group is listed in the directory again." - notifySuperUsers $ "The group " <> groupRef <> " is listed " <> suSrvRole + notifyAdminUsers $ "The group " <> groupRef <> " is listed " <> suSrvRole GRSPendingApproval gaId -> when (serviceRole == GRAdmin) $ whenContactIsOwner gr $ do sendToApprove g gr gaId @@ -378,7 +381,7 @@ directoryService st DirectoryOpts {adminUsers, superUsers, serviceName, searchRe GRSActive -> when (serviceRole /= GRAdmin) $ do setGroupStatus st gr GRSSuspendedBadRoles notifyOwner gr $ uSrvRole <> ".\n\nThe group is no longer listed in the directory." - notifySuperUsers $ "The group " <> groupRef <> " is de-listed " <> suSrvRole + notifyAdminUsers $ "The group " <> groupRef <> " is de-listed " <> suSrvRole _ -> pure () where groupRef = groupReference g @@ -395,7 +398,7 @@ directoryService st DirectoryOpts {adminUsers, superUsers, serviceName, searchRe when (ctId `isOwner` gr) $ do setGroupStatus st gr GRSRemoved notifyOwner gr $ "You are removed from the group " <> userGroupReference gr g <> ".\n\nThe group is no longer listed in the directory." - notifySuperUsers $ "The group " <> groupReference g <> " is de-listed (group owner is removed)." + notifyAdminUsers $ "The group " <> groupReference g <> " is de-listed (group owner is removed)." deContactLeftGroup :: ContactId -> GroupInfo -> IO () deContactLeftGroup ctId g = do @@ -404,7 +407,7 @@ directoryService st DirectoryOpts {adminUsers, superUsers, serviceName, searchRe when (ctId `isOwner` gr) $ do setGroupStatus st gr GRSRemoved notifyOwner gr $ "You left the group " <> userGroupReference gr g <> ".\n\nThe group is no longer listed in the directory." - notifySuperUsers $ "The group " <> groupReference g <> " is de-listed (group owner left)." + notifyAdminUsers $ "The group " <> groupReference g <> " is de-listed (group owner left)." deServiceRemovedFromGroup :: GroupInfo -> IO () deServiceRemovedFromGroup g = do @@ -412,7 +415,7 @@ directoryService st DirectoryOpts {adminUsers, superUsers, serviceName, searchRe withGroupReg g "service removed" $ \gr -> do setGroupStatus st gr GRSRemoved notifyOwner gr $ serviceName <> " is removed from the group " <> userGroupReference gr g <> ".\n\nThe group is no longer listed in the directory." - notifySuperUsers $ "The group " <> groupReference g <> " is de-listed (directory service is removed)." + notifyAdminUsers $ "The group " <> groupReference g <> " is de-listed (directory service is removed)." deUserCommand :: ServiceState -> Contact -> ChatItemId -> DirectoryCmd 'DRUser -> IO () deUserCommand env@ServiceState {searchRequests} ct ciId = \case From 61d7df89069ad9b4725d4b0f47c546fddc163b77 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Thu, 21 Nov 2024 16:54:35 +0000 Subject: [PATCH 092/567] ui: always use private routing by default --- apps/ios/SimpleXChat/APITypes.swift | 2 +- .../commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt | 2 +- src/Simplex/Chat/Options.hs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 8014600d47..51aa9108a1 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -1722,7 +1722,7 @@ public struct NetCfg: Codable, Equatable { public var hostMode: HostMode = .publicHost public var requiredHostMode = true public var sessionMode = TransportSessionMode.user - public var smpProxyMode: SMPProxyMode = .unknown + public var smpProxyMode: SMPProxyMode = .always public var smpProxyFallback: SMPProxyFallback = .allowProtected public var smpWebPort = false public var tcpConnectTimeout: Int // microseconds diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 7f7f8a6e58..580a663945 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -3683,7 +3683,7 @@ data class NetCfg( val hostMode: HostMode = HostMode.OnionViaSocks, val requiredHostMode: Boolean = false, val sessionMode: TransportSessionMode = TransportSessionMode.default, - val smpProxyMode: SMPProxyMode = SMPProxyMode.Unknown, + val smpProxyMode: SMPProxyMode = SMPProxyMode.Always, val smpProxyFallback: SMPProxyFallback = SMPProxyFallback.AllowProtected, val smpWebPort: Boolean = false, val tcpConnectTimeout: Long, // microseconds diff --git a/src/Simplex/Chat/Options.hs b/src/Simplex/Chat/Options.hs index 16ffe6e28f..f398831194 100644 --- a/src/Simplex/Chat/Options.hs +++ b/src/Simplex/Chat/Options.hs @@ -236,7 +236,7 @@ coreChatOptsP appDir defaultDbFileName = do ) yesToUpMigrations <- switch - ( long "--yes-migrate" + ( long "yes-migrate" <> short 'y' <> help "Automatically confirm \"up\" database migrations" ) From 78b3b12ec1cd02e440c954add8d320f2f6b954ad Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 21 Nov 2024 17:02:55 +0000 Subject: [PATCH 093/567] ios: button to open conditions and changes (#5225) --- .../Onboarding/ChooseServerOperators.swift | 3 ++ .../NetworkAndServers/NetworkAndServers.swift | 12 ++++--- .../NetworkAndServers/OperatorView.swift | 35 ++++++++++++++++--- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift index 471d27ea50..19d67bc62c 100644 --- a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift +++ b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift @@ -9,6 +9,8 @@ import SwiftUI import SimpleXChat +let conditionsURL = URL(string: "https://github.com/simplex-chat/simplex-chat/blob/stable/PRIVACY.md")! + struct OnboardingButtonStyle: ButtonStyle { @EnvironmentObject var theme: AppTheme var isDisabled: Bool = false @@ -313,6 +315,7 @@ struct ChooseServerOperators: View { reviewConditionsView() .navigationTitle("Conditions of use") .navigationBarTitleDisplayMode(.large) + .toolbar { ToolbarItem(placement: .navigationBarTrailing, content: conditionsLinkButton) } .modifier(ThemedBackground(grouped: true)) } diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift index 8b07c9a519..9b03b79353 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift @@ -238,11 +238,13 @@ struct UsageConditionsView: View { var body: some View { VStack(alignment: .leading, spacing: 20) { - Text("Conditions of use") - .font(.largeTitle) - .bold() - .padding(.top) - .padding(.top) + HStack { + Text("Conditions of use").font(.largeTitle).bold() + Spacer() + conditionsLinkButton() + } + .padding(.top) + .padding(.top) switch ChatModel.shared.conditions.conditionsAction { diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift index 6cebfdcde6..83152a001f 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift @@ -464,11 +464,13 @@ struct SingleOperatorUsageConditionsView: View { } private func viewHeader() -> some View { - Text("Use servers of \(userServers[operatorIndex].operator_.tradeName)") - .font(.largeTitle) - .bold() - .padding(.top) - .padding(.top) + HStack { + Text("Use \(userServers[operatorIndex].operator_.tradeName)").font(.largeTitle).bold() + Spacer() + conditionsLinkButton() + } + .padding(.top) + .padding(.top) } @ViewBuilder private func conditionsAppliedToOtherOperatorsText() -> some View { @@ -545,10 +547,33 @@ struct SingleOperatorUsageConditionsView: View { .padding(.bottom) .navigationTitle("Conditions of use") .navigationBarTitleDisplayMode(.large) + .toolbar { ToolbarItem(placement: .navigationBarTrailing, content: conditionsLinkButton) } .modifier(ThemedBackground(grouped: true)) } } +func conditionsLinkButton() -> some View { + let commit = ChatModel.shared.conditions.currentConditions.conditionsCommit + let mdUrl = URL(string: "https://github.com/simplex-chat/simplex-chat/blob/\(commit)/PRIVACY.md") ?? conditionsURL + return Menu { + Link(destination: mdUrl) { + Label("Open conditions", systemImage: "doc") + } + if let commitUrl = URL(string: "https://github.com/simplex-chat/simplex-chat/commit/\(commit)") { + Link(destination: commitUrl) { + Label("Open changes", systemImage: "ellipsis") + } + } + } label: { + Image(systemName: "arrow.up.right.circle") + .resizable() + .scaledToFit() + .frame(width: 20) + .padding(2) + .contentShape(Circle()) + } +} + #Preview { OperatorView( currUserServers: Binding.constant([UserOperatorServers.sampleData1, UserOperatorServers.sampleDataNilOperator]), From 1083a0727a4750ede208ee8fe0cdc4acd1deee4d Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Fri, 22 Nov 2024 08:31:58 +0000 Subject: [PATCH 094/567] flatpak: update metainfo (#5146) --- .../flatpak/chat.simplex.simplex.metainfo.xml | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/scripts/flatpak/chat.simplex.simplex.metainfo.xml b/scripts/flatpak/chat.simplex.simplex.metainfo.xml index c3c7954836..bc90e4e041 100644 --- a/scripts/flatpak/chat.simplex.simplex.metainfo.xml +++ b/scripts/flatpak/chat.simplex.simplex.metainfo.xml @@ -38,6 +38,34 @@ + + https://simplex.chat/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html + +

New in v6.1 - v6.1.1:

+
    +
  • Misc fixes
  • +
+

New in v6.1:

+

Better security:

+
    +
  • SimpleX protocols reviewed by Trail of Bits.
  • +
  • security improvements (don't worry, there is nothing critical there).
  • +
+

Better calls:

+
    +
  • you can switch audio and video during the call.
  • +
  • share the screen from desktop app.
  • +
+

Better user experience:

+
    +
  • switch chat profile for 1-time invitations.
  • +
  • customizable message shape.
  • +
  • better message dates.
  • +
  • forward up to 20 messages at once.
  • +
  • delete or moderate up to 200 messages.
  • +
+ + https://simplex.chat/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html From bab63d8f27118c3f3d59b979305c6ee5732e0065 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 22 Nov 2024 13:23:33 +0400 Subject: [PATCH 095/567] ios: fix repeatedly showing updated conditions --- apps/ios/Shared/ContentView.swift | 13 +++++++++++++ apps/ios/Shared/Views/Onboarding/WhatsNewView.swift | 10 ---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift index c5a7a6f20b..652258415e 100644 --- a/apps/ios/Shared/ContentView.swift +++ b/apps/ios/Shared/ContentView.swift @@ -295,12 +295,16 @@ struct ContentView: View { case let .whatsNew(updatedConditions): WhatsNewView(updatedConditions: updatedConditions) .modifier(ThemedBackground()) + .if(updatedConditions) { v in + v.task { await setConditionsNotified_() } + } case .updatedConditions: UsageConditionsView( currUserServers: Binding.constant([]), userServers: Binding.constant([]) ) .modifier(ThemedBackground(grouped: true)) + .task { await setConditionsNotified_() } } } if chatModel.setDeliveryReceipts { @@ -313,6 +317,15 @@ struct ContentView: View { .onContinueUserActivity("INStartVideoCallIntent", perform: processUserActivity) } + private func setConditionsNotified_() async { + do { + let conditionsId = ChatModel.shared.conditions.currentConditions.conditionsId + try await setConditionsNotified(conditionsId: conditionsId) + } catch let error { + logger.error("setConditionsNotified error: \(responseError(error))") + } + } + private func processUserActivity(_ activity: NSUserActivity) { let intent = activity.interaction?.intent if let intent = intent as? INStartCallIntent { diff --git a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift index 4208c4a068..c1c2cb8383 100644 --- a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift +++ b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift @@ -598,16 +598,6 @@ struct WhatsNewView: View { var body: some View { whatsNewView() - .task { - if updatedConditions { - do { - let conditionsId = ChatModel.shared.conditions.currentConditions.conditionsId - try await setConditionsNotified(conditionsId: conditionsId) - } catch let error { - logger.error("WhatsNewView setConditionsNotified error: \(responseError(error))") - } - } - } .sheet(item: $sheetItem) { item in switch item { case .showConditions: From 49d1b26bba44bf417b56bf6a0345bac98e1827ce Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 22 Nov 2024 10:38:00 +0000 Subject: [PATCH 096/567] core: tests for operators api, CLI command to update operators (#5226) --- src/Simplex/Chat.hs | 25 ++++++++++++++++++ src/Simplex/Chat/Controller.hs | 1 + src/Simplex/Chat/Operators.hs | 8 ++++++ src/Simplex/Chat/View.hs | 21 ++++++++++----- tests/ChatClient.hs | 10 +++++++ tests/ChatTests/Direct.hs | 48 ++++++++++++++++++++++++++++------ 6 files changed, 98 insertions(+), 15 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 0daf9fa394..5906da57de 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1640,6 +1640,16 @@ processChatCommand' vr = \case xftpSrvs <- getProtocolServers db SPXFTP user uss <- groupByOperator (ops, smpSrvs, xftpSrvs) pure $ (aUserId user,) $ useServers as opDomains uss + SetServerOperators operatorsRoles -> do + ops <- serverOperators <$> withFastStore getServerOperators + ops' <- mapM (updateOp ops) operatorsRoles + processChatCommand $ APISetServerOperators ops' + where + updateOp :: [ServerOperator] -> ServerOperatorRoles -> CM ServerOperator + updateOp ops r = + case find (\ServerOperator {operatorId = DBEntityId opId} -> operatorId' r == opId) ops of + Just op -> pure op {enabled = enabled' r, smpRoles = smpRoles' r, xftpRoles = xftpRoles' r} + Nothing -> throwError $ ChatErrorStore $ SEOperatorNotFound $ operatorId' r APIGetUserServers userId -> withUserId userId $ \user -> withFastStore $ \db -> do CRUserServers user <$> (liftIO . groupByOperator =<< getUserServers db user) APISetUserServers userId userServers -> withUserId userId $ \user -> do @@ -8308,6 +8318,7 @@ chatCommandP = "/xftp" $> GetUserProtoServers (AProtocolType SPXFTP), "/_operators" $> APIGetServerOperators, "/_operators " *> (APISetServerOperators <$> jsonP), + "/operators " *> (SetServerOperators . L.fromList <$> operatorRolesP `A.sepBy1` A.char ','), "/_servers " *> (APIGetUserServers <$> A.decimal), "/_servers " *> (APISetUserServers <$> A.decimal <* A.space <*> jsonP), "/_validate_servers " *> (APIValidateServers <$> A.decimal <* A.space <*> jsonP), @@ -8637,6 +8648,20 @@ chatCommandP = optional ("yes" *> A.space) *> (TMEEnableSetTTL <$> timedTTLP) <|> ("yes" $> TMEEnableKeepTTL) <|> ("no" $> TMEDisableKeepTTL) + operatorRolesP = do + operatorId' <- A.decimal + enabled' <- A.char ':' *> onOffP + smpRoles' <- (":smp=" *> srvRolesP) <|> pure allRoles + xftpRoles' <- (":xftp=" *> srvRolesP) <|> pure allRoles + pure ServerOperatorRoles {operatorId', enabled', smpRoles', xftpRoles'} + srvRolesP = srvRoles <$?> A.takeTill (\c -> c == ':' || c == ',') + where + srvRoles = \case + "off" -> Right $ ServerRoles False False + "proxy" -> Right ServerRoles {storage = False, proxy = True} + "storage" -> Right ServerRoles {storage = True, proxy = False} + "on" -> Right allRoles + _ -> Left "bad ServerRoles" netCfgP = do socksProxy <- "socks=" *> ("off" $> Nothing <|> "on" $> Just defaultSocksProxyWithAuth <|> Just <$> strP) socksMode <- " socks-mode=" *> strP <|> pure SMAlways diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index e44ea2ac18..23aa632478 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -358,6 +358,7 @@ data ChatCommand | TestProtoServer AProtoServerWithAuth | APIGetServerOperators | APISetServerOperators (NonEmpty ServerOperator) + | SetServerOperators (NonEmpty ServerOperatorRoles) | APIGetUserServers UserId | APISetUserServers UserId (NonEmpty UpdatedUserOperatorServers) | APIValidateServers UserId [UpdatedUserOperatorServers] -- response is CRUserServersValidation diff --git a/src/Simplex/Chat/Operators.hs b/src/Simplex/Chat/Operators.hs index ebe1da8176..e14e95211a 100644 --- a/src/Simplex/Chat/Operators.hs +++ b/src/Simplex/Chat/Operators.hs @@ -192,6 +192,14 @@ data ServerOperator' s = ServerOperator } deriving (Show) +data ServerOperatorRoles = ServerOperatorRoles + { operatorId' :: Int64, + enabled' :: Bool, + smpRoles' :: ServerRoles, + xftpRoles' :: ServerRoles + } + deriving (Show) + operatorRoles :: UserProtocol p => SProtocolType p -> ServerOperator -> ServerRoles operatorRoles p op = case p of SPSMP -> smpRoles op diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index e4c0fd5606..f9ec3f936c 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -101,7 +101,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRServerOperatorConditions (ServerOperatorConditions ops _ ca) -> viewServerOperators ops ca CRUserServers u uss -> ttyUser u $ concatMap viewUserServers uss <> (if testView then [] else serversUserHelp) CRUserServersValidation {} -> [] - CRUsageConditions {} -> [] + CRUsageConditions current _ accepted_ -> viewUsageConditions current accepted_ CRChatItemTTL u ttl -> ttyUser u $ viewChatItemTTL ttl CRNetworkConfig cfg -> viewNetworkConfig cfg CRContactInfo u ct cStats customUserProfile -> ttyUser u $ viewContactInfo ct cStats customUserProfile @@ -1280,8 +1280,8 @@ viewOperator op@ServerOperator {tradeName, legalName, serverDomains, conditionsA <> tradeName <> maybe "" parens legalName <> (", domains: " <> T.intercalate ", " serverDomains) + <> (", servers: " <> viewOpEnabled op) <> (", conditions: " <> viewOpConditions conditionsAcceptance) - <> (", " <> viewOpEnabled op) shortViewOperator :: ServerOperator -> Text shortViewOperator ServerOperator {operatorId = DBEntityId opId, tradeName, enabled} = @@ -1289,10 +1289,10 @@ shortViewOperator ServerOperator {operatorId = DBEntityId opId, tradeName, enabl viewOpIdTag :: ServerOperator' s -> Text viewOpIdTag ServerOperator {operatorId, operatorTag} = case operatorId of - DBEntityId i -> tshow i <> " - " <> tag + DBEntityId i -> tshow i <> tag DBNewEntity -> tag where - tag = maybe "" textEncode operatorTag <> ". " + tag = maybe "" (parens . textEncode) operatorTag <> ". " viewOpConditions :: ConditionsAcceptance -> Text viewOpConditions = \case @@ -1306,7 +1306,7 @@ viewOpEnabled ServerOperator {enabled, smpRoles, xftpRoles} | not enabled = "disabled" | no smpRoles && no xftpRoles = "disabled (servers known)" | both smpRoles && both xftpRoles = "enabled" - | otherwise = "SMP " <> viewRoles smpRoles <> ", XFTP" <> viewRoles xftpRoles + | otherwise = "SMP " <> viewRoles smpRoles <> ", XFTP " <> viewRoles xftpRoles where no rs = not $ storage rs || proxy rs both rs = storage rs && proxy rs @@ -1319,13 +1319,20 @@ viewOpEnabled ServerOperator {enabled, smpRoles, xftpRoles} viewConditionsAction :: UsageConditionsAction -> [StyledString] viewConditionsAction = \case UCAReview {operators, deadline, showNotice} | showNotice -> case deadline of - Just ts -> [plain $ "New conditions will be accepted at " <> tshow ts <> " for " <> ops] - Nothing -> [plain $ "New conditions have to be accepted for " <> ops] + Just ts -> [plain $ "The new conditions will be accepted for " <> ops <> " at " <> tshow ts] + Nothing -> [plain $ "The new conditions have to be accepted for " <> ops] where ops = T.intercalate ", " $ map legalName_ operators legalName_ ServerOperator {tradeName, legalName} = fromMaybe tradeName legalName _ -> [] +viewUsageConditions :: UsageConditions -> Maybe UsageConditions -> [StyledString] +viewUsageConditions current accepted_ = + [plain $ "Current conditions: " <> viewConds current <> maybe "" (\ac -> ", accepted conditions: " <> viewConds ac) accepted_] + where + viewConds UsageConditions {conditionsId, conditionsCommit, notifiedAt} = + tshow conditionsId <> maybe "" (const " (notified)") notifiedAt <> ". " <> conditionsCommit + viewChatItemTTL :: Maybe Int64 -> [StyledString] viewChatItemTTL = \case Nothing -> ["old messages are not being deleted"] diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index 7bf7804472..8b7e8fcd32 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -376,6 +376,16 @@ userName :: TestCC -> IO [Char] userName (TestCC ChatController {currentUser} _ _ _ _ _) = maybe "no current user" (\User {localDisplayName} -> T.unpack localDisplayName) <$> readTVarIO currentUser +testChat :: HasCallStack => Profile -> (HasCallStack => TestCC -> IO ()) -> FilePath -> IO () +testChat = testChatCfgOpts testCfg testOpts + +testChatCfgOpts :: HasCallStack => ChatConfig -> ChatOpts -> Profile -> (HasCallStack => TestCC -> IO ()) -> FilePath -> IO () +testChatCfgOpts cfg opts p test = testChatN cfg opts [p] test_ + where + test_ :: HasCallStack => [TestCC] -> IO () + test_ [tc] = test tc + test_ _ = error "expected 1 chat client" + testChat2 :: HasCallStack => Profile -> Profile -> (HasCallStack => TestCC -> TestCC -> IO ()) -> FilePath -> IO () testChat2 = testChatCfgOpts2 testCfg testOpts diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index 6bbf72171e..d305055d94 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -85,6 +85,8 @@ chatDirectTests = do describe "XFTP servers" $ do it "get and set XFTP servers" testGetSetXFTPServers it "test XFTP server connection" testTestXFTPServer + describe "operators and usage conditions" $ do + it "get and enable operators, accept conditions" testOperators describe "async connection handshake" $ do describe "connect when initiating client goes offline" $ do it "curr" $ testAsyncInitiatingOffline testCfg testCfg @@ -1140,8 +1142,8 @@ testSendMultiManyBatches = testGetSetSMPServers :: HasCallStack => FilePath -> IO () testGetSetSMPServers = - testChat2 aliceProfile bobProfile $ - \alice _ -> do + testChat aliceProfile $ + \alice -> do alice ##> "/_servers 1" alice <## "Your servers" alice <## " SMP servers" @@ -1168,8 +1170,8 @@ testGetSetSMPServers = testTestSMPServerConnection :: HasCallStack => FilePath -> IO () testTestSMPServerConnection = - testChat2 aliceProfile bobProfile $ - \alice _ -> do + testChat aliceProfile $ + \alice -> do alice ##> "/smp test smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=@localhost:7001" alice <## "SMP server test passed" -- to test with password: @@ -1183,8 +1185,8 @@ testTestSMPServerConnection = testGetSetXFTPServers :: HasCallStack => FilePath -> IO () testGetSetXFTPServers = - testChat2 aliceProfile bobProfile $ - \alice _ -> withXFTPServer $ do + testChat aliceProfile $ + \alice -> withXFTPServer $ do alice ##> "/_servers 1" alice <## "Your servers" alice <## " SMP servers" @@ -1210,8 +1212,8 @@ testGetSetXFTPServers = testTestXFTPServer :: HasCallStack => FilePath -> IO () testTestXFTPServer = - testChat2 aliceProfile bobProfile $ - \alice _ -> withXFTPServer $ do + testChat aliceProfile $ + \alice -> withXFTPServer $ do alice ##> "/xftp test xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=@localhost:7002" alice <## "XFTP server test passed" -- to test with password: @@ -1223,6 +1225,36 @@ testTestXFTPServer = alice <## "XFTP server test failed at Connect, error: BROKER {brokerAddress = \"xftp://LcJU@localhost:7002\", brokerErr = NETWORK}" alice <## "Possibly, certificate fingerprint in XFTP server address is incorrect" +testOperators :: HasCallStack => FilePath -> IO () +testOperators = + testChatCfgOpts testCfg opts' aliceProfile $ + \alice -> do + -- initial load + alice ##> "/_conditions" + alice <##. "Current conditions: 2." + alice ##> "/_operators" + alice <##. "1 (simplex). SimpleX Chat (SimpleX Chat Ltd), domains: simplex.im, servers: enabled, conditions: required (" + alice <## "2 (flux). Flux (InFlux Technologies Limited), domains: simplexonflux.com, servers: disabled, conditions: required" + alice <##. "The new conditions will be accepted for SimpleX Chat Ltd at " + -- set conditions notified + alice ##> "/_conditions_notified 2" + alice <## "ok" + alice ##> "/_operators" + alice <##. "1 (simplex). SimpleX Chat (SimpleX Chat Ltd), domains: simplex.im, servers: enabled, conditions: required (" + alice <## "2 (flux). Flux (InFlux Technologies Limited), domains: simplexonflux.com, servers: disabled, conditions: required" + alice ##> "/_conditions" + alice <##. "Current conditions: 2 (notified)." + -- accept conditions + alice ##> "/_accept_conditions 2 1,2" + alice <##. "1 (simplex). SimpleX Chat (SimpleX Chat Ltd), domains: simplex.im, servers: enabled, conditions: accepted (" + alice <##. "2 (flux). Flux (InFlux Technologies Limited), domains: simplexonflux.com, servers: disabled, conditions: accepted (" + -- update operators + alice ##> "/operators 2:on:smp=proxy" + alice <##. "1 (simplex). SimpleX Chat (SimpleX Chat Ltd), domains: simplex.im, servers: enabled, conditions: accepted (" + alice <##. "2 (flux). Flux (InFlux Technologies Limited), domains: simplexonflux.com, servers: SMP enabled proxy, XFTP enabled, conditions: accepted (" + where + opts' = testOpts {coreOptions = testCoreOpts {smpServers = [], xftpServers = []}} + testAsyncInitiatingOffline :: HasCallStack => ChatConfig -> ChatConfig -> FilePath -> IO () testAsyncInitiatingOffline aliceCfg bobCfg tmp = do inv <- withNewTestChatCfg tmp aliceCfg "alice" aliceProfile $ \alice -> do From ea9ee987cfb3e89618b8edc6d6e0a15da8e70c7f Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 22 Nov 2024 19:59:39 +0700 Subject: [PATCH 097/567] android, desktop: better message info screen (#5227) - changed tabBar style to leading icon - made tabBar the same size as AppBars - made background color as theme background --- .../common/views/chat/ChatItemInfoView.kt | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt index 8b4f7f8ec9..d6744a0a0d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.views.helpers.* import chat.simplex.common.ui.theme.* @@ -477,28 +478,33 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools selection.value = CIInfoTab.Delivery(ciInfo.memberDeliveryStatuses) } } - TabRow( - selectedTabIndex = availableTabs.indexOfFirst { it::class == selection.value::class }, - backgroundColor = Color.Transparent, - contentColor = MaterialTheme.colors.primary, - ) { - availableTabs.forEach { ciInfoTab -> - Tab( - selected = selection.value::class == ciInfoTab::class, - onClick = { - selection.value = ciInfoTab - }, - text = { Text(tabTitle(ciInfoTab), fontSize = 13.sp) }, - icon = { - Icon( - painterResource(tabIcon(ciInfoTab)), - tabTitle(ciInfoTab) - ) - }, - selectedContentColor = MaterialTheme.colors.primary, - unselectedContentColor = MaterialTheme.colors.secondary, - ) + val oneHandUI = remember { appPrefs.oneHandUI.state } + Box(Modifier.offset(x = 0.dp, y = if (oneHandUI.value) -AppBarHeight * fontSizeSqrtMultiplier else 0.dp)) { + TabRow( + selectedTabIndex = availableTabs.indexOfFirst { it::class == selection.value::class }, + Modifier.height(AppBarHeight * fontSizeSqrtMultiplier), + backgroundColor = MaterialTheme.colors.background, + contentColor = MaterialTheme.colors.primary, + ) { + availableTabs.forEach { ciInfoTab -> + LeadingIconTab( + selected = selection.value::class == ciInfoTab::class, + onClick = { + selection.value = ciInfoTab + }, + text = { Text(tabTitle(ciInfoTab), fontSize = 13.sp) }, + icon = { + Icon( + painterResource(tabIcon(ciInfoTab)), + tabTitle(ciInfoTab) + ) + }, + selectedContentColor = MaterialTheme.colors.primary, + unselectedContentColor = MaterialTheme.colors.secondary, + ) + } } + Divider() } } } else { From 396fa7f988bb12460dfe3f9217b8f0886d601029 Mon Sep 17 00:00:00 2001 From: Diogo Date: Fri, 22 Nov 2024 14:42:07 +0000 Subject: [PATCH 098/567] desktop, android: server operators (#5212) * api and types * whats new view * new package and movements * move network and servers to new package * network and servers view * wip * api update * build * conditions modal in settings * network and servers fns * save server fixes * more servers * move protocol servers view * message servers with validation * added message servers * use for files * fix error by server type * list xftp servers * android: add server view (#5221) * android add server wip * test servers button * fix save of custom servers * remove unused code * edit and view servers * fix * allow to enable untested * show all test errors in the end * android: custom servers view (#5224) * cleanup * validation footers * operator enabled validation * var -> val * reuse onboarding button * AppBarTitle without alpha * remove non scrollable title * change in AppBarTitle * changes in AppBar * bold strings + bordered text view * ChooseServerOperators * fix * new server view wip * fix * scan * rename * fix roles toggle texts * UsageConditionsView * aligned texts * more texts * replace hard coded logos with object ref * use snapshot state to recalculate errors * align views; fix accept * remove extra snapshots * fix ts * fix whatsnew * stage * animation on onboarding * refactor and fix * remember * fix start chat alert * show notice in chat list * refactor * fix validation * open conditions * whats new view updates * icon for navigation improvements * remove debug * simplify * fix * handle click when have unsaved changes * fix * Revert "fix" This reverts commit d49c3736415a9fe08464237e041c2d2b6fc665d3. * Revert "handle click when have unsaved changes" This reverts commit 39ca03f9c086b87b5b6571f93443ba16f2870d24. * fixed close of modals in whats new view * grouping * android: conditions view paddings (#5228) * revert padding * refresh operators on save * fixed modals in different views for desktop * ios: fix enabling operator model update * fix modals --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Co-authored-by: Avently <7953703+avently@users.noreply.github.com> --- .../NetworkAndServers/NetworkAndServers.swift | 2 + .../ScanProtocolServer.android.kt | 6 +- .../kotlin/chat/simplex/common/App.kt | 9 +- .../chat/simplex/common/model/ChatModel.kt | 9 + .../chat/simplex/common/model/SimpleXAPI.kt | 557 ++++++++++++-- .../chat/simplex/common/platform/Core.kt | 12 +- .../chat/simplex/common/views/WelcomeView.kt | 4 +- .../simplex/common/views/chat/ChatView.kt | 2 +- .../common/views/chatlist/ChatListView.kt | 26 +- .../views/chatlist/ServersSummaryView.kt | 19 +- .../common/views/helpers/AppBarTitle.kt | 19 +- .../common/views/helpers/CollapsingAppBar.kt | 1 + .../common/views/helpers/DefaultTopAppBar.kt | 3 +- .../common/views/migration/MigrateToDevice.kt | 1 + .../views/onboarding/ChooseServerOperators.kt | 354 +++++++++ .../common/views/onboarding/HowItWorks.kt | 4 +- .../common/views/onboarding/OnboardingView.kt | 1 + .../views/onboarding/SetNotificationsMode.kt | 8 +- .../onboarding/SetupDatabasePassphrase.kt | 3 +- .../common/views/onboarding/SimpleXInfo.kt | 5 +- .../common/views/onboarding/WhatsNewView.kt | 261 ++++--- .../common/views/remote/ConnectMobileView.kt | 1 + .../views/usersettings/ProtocolServersView.kt | 383 ---------- .../common/views/usersettings/SettingsView.kt | 7 +- .../AdvancedNetworkSettings.kt | 3 +- .../NetworkAndServers.kt | 540 ++++++++++++-- .../networkAndServers/NewServerView.kt | 144 ++++ .../networkAndServers/OperatorView.kt | 701 ++++++++++++++++++ .../ProtocolServerView.kt | 169 +++-- .../networkAndServers/ProtocolServersView.kt | 407 ++++++++++ .../ScanProtocolServer.kt | 14 +- .../commonMain/resources/MR/base/strings.xml | 84 +++ .../resources/MR/images/flux_logo@4x.png | Bin 0 -> 34876 bytes .../MR/images/flux_logo_light@4x.png | Bin 0 -> 33847 bytes .../MR/images/flux_logo_symbol@4x.png | Bin 0 -> 17248 bytes .../resources/MR/images/ic_outbound.svg | 1 + .../ScanProtocolServer.desktop.kt | 9 - .../ScanProtocolServer.desktop.kt | 9 + 38 files changed, 3032 insertions(+), 746 deletions(-) rename apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/{ => networkAndServers}/ScanProtocolServer.android.kt (69%) create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt delete mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt rename apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/{ => networkAndServers}/AdvancedNetworkSettings.kt (99%) rename apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/{ => networkAndServers}/NetworkAndServers.kt (52%) create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NewServerView.kt create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt rename apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/{ => networkAndServers}/ProtocolServerView.kt (51%) create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt rename apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/{ => networkAndServers}/ScanProtocolServer.kt (62%) create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/flux_logo@4x.png create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/flux_logo_light@4x.png create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/flux_logo_symbol@4x.png create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_outbound.svg delete mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.desktop.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ScanProtocolServer.desktop.kt diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift index 9b03b79353..8b6421b502 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift @@ -395,7 +395,9 @@ func saveServers(_ currUserServers: Binding<[UserOperatorServers]>, _ userServer // Get updated servers to learn new server ids (otherwise it messes up delete of newly added and saved servers) do { let updatedServers = try await getUserServers() + let updatedOperators = try await getServerOperators() await MainActor.run { + ChatModel.shared.conditions = updatedOperators currUserServers.wrappedValue = updatedServers userServers.wrappedValue = updatedServers } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ScanProtocolServer.android.kt similarity index 69% rename from apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.android.kt rename to apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ScanProtocolServer.android.kt index af5a27be11..8b5def7451 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ScanProtocolServer.android.kt @@ -1,13 +1,13 @@ -package chat.simplex.common.views.usersettings +package chat.simplex.common.views.usersettings.networkAndServers import android.Manifest import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import chat.simplex.common.model.ServerCfg +import chat.simplex.common.model.UserServer import com.google.accompanist.permissions.rememberPermissionState @Composable -actual fun ScanProtocolServer(rhId: Long?, onNext: (ServerCfg) -> Unit) { +actual fun ScanProtocolServer(rhId: Long?, onNext: (UserServer) -> Unit) { val cameraPermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA) LaunchedEffect(Unit) { cameraPermissionState.launchPermissionRequest() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index 7af1d574ad..b1ce003812 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -15,7 +15,6 @@ import androidx.compose.ui.draw.* import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.* -import androidx.compose.ui.graphics.drawscope.clipRect import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.dp @@ -42,7 +41,6 @@ import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.* import kotlinx.coroutines.flow.* -import kotlin.math.absoluteValue @Composable fun AppScreen() { @@ -194,6 +192,13 @@ fun MainScreen() { OnboardingStage.Step2_CreateProfile -> CreateFirstProfile(chatModel) {} OnboardingStage.LinkAMobile -> LinkAMobile() OnboardingStage.Step2_5_SetupDatabasePassphrase -> SetupDatabasePassphrase(chatModel) + OnboardingStage.Step3_ChooseServerOperators -> { + val modalData = remember { ModalData() } + modalData.ChooseServerOperators(true) + if (appPlatform.isDesktop) { + ModalManager.fullscreen.showInView() + } + } // Ensure backwards compatibility with old onboarding stage for address creation, otherwise notification setup would be skipped OnboardingStage.Step3_CreateSimpleXAddress -> SetNotificationsMode(chatModel) OnboardingStage.Step4_SetNotificationsMode -> SetNotificationsMode(chatModel) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index ef777f151f..e501ed5a91 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -145,6 +145,8 @@ object ChatModel { val clipboardHasText = mutableStateOf(false) val networkInfo = mutableStateOf(UserNetworkInfo(networkType = UserNetworkType.OTHER, online = true)) + val conditions = mutableStateOf(ServerOperatorConditionsDetail.empty) + val updatingProgress = mutableStateOf(null as Float?) var updatingRequest: Closeable? = null @@ -2567,6 +2569,13 @@ fun localTimestamp(t: Instant): String { return ts.toJavaLocalDateTime().format(dateFormatter) } +fun localDate(t: Instant): String { + val tz = TimeZone.currentSystemDefault() + val ts: LocalDateTime = t.toLocalDateTime(tz) + val dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM) + return ts.toJavaLocalDateTime().format(dateFormatter) +} + @Serializable sealed class CIStatus { @Serializable @SerialName("sndNew") class SndNew: CIStatus() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 580a663945..0cab7ce8e9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -26,10 +26,12 @@ import chat.simplex.common.views.chat.item.showQuotedItemDoesNotExistAlert import chat.simplex.common.views.migration.MigrationFileLinkData import chat.simplex.common.views.onboarding.OnboardingStage import chat.simplex.common.views.usersettings.* +import chat.simplex.common.views.usersettings.networkAndServers.serverHostname import com.charleskorn.kaml.Yaml import com.charleskorn.kaml.YamlConfiguration import chat.simplex.res.MR import com.russhwolf.settings.Settings +import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel @@ -963,36 +965,6 @@ object ChatController { return null } - suspend fun getUserProtoServers(rh: Long?, serverProtocol: ServerProtocol): UserProtocolServers? { - val userId = kotlin.runCatching { currentUserId("getUserProtoServers") }.getOrElse { return null } - val r = sendCmd(rh, CC.APIGetUserProtoServers(userId, serverProtocol)) - return if (r is CR.UserProtoServers) { if (rh == null) r.servers else r.servers.copy(protoServers = r.servers.protoServers.map { it.copy(remoteHostId = rh) }) } - else { - Log.e(TAG, "getUserProtoServers bad response: ${r.responseType} ${r.details}") - AlertManager.shared.showAlertMsg( - generalGetString(if (serverProtocol == ServerProtocol.SMP) MR.strings.error_loading_smp_servers else MR.strings.error_loading_xftp_servers), - "${r.responseType}: ${r.details}" - ) - null - } - } - - suspend fun setUserProtoServers(rh: Long?, serverProtocol: ServerProtocol, servers: List): Boolean { - val userId = kotlin.runCatching { currentUserId("setUserProtoServers") }.getOrElse { return false } - val r = sendCmd(rh, CC.APISetUserProtoServers(userId, serverProtocol, servers)) - return when (r) { - is CR.CmdOk -> true - else -> { - Log.e(TAG, "setUserProtoServers bad response: ${r.responseType} ${r.details}") - AlertManager.shared.showAlertMsg( - generalGetString(if (serverProtocol == ServerProtocol.SMP) MR.strings.error_saving_smp_servers else MR.strings.error_saving_xftp_servers), - generalGetString(if (serverProtocol == ServerProtocol.SMP) MR.strings.ensure_smp_server_address_are_correct_format_and_unique else MR.strings.ensure_xftp_server_address_are_correct_format_and_unique) - ) - false - } - } - } - suspend fun testProtoServer(rh: Long?, server: String): ProtocolTestFailure? { val userId = currentUserId("testProtoServer") val r = sendCmd(rh, CC.APITestProtoServer(userId, server)) @@ -1005,6 +977,106 @@ object ChatController { } } + suspend fun getServerOperators(rh: Long?): ServerOperatorConditionsDetail? { + val r = sendCmd(rh, CC.ApiGetServerOperators()) + + return when (r) { + is CR.ServerOperatorConditions -> r.conditions + else -> { + Log.e(TAG, "getServerOperators bad response: ${r.responseType} ${r.details}") + null + } + } + } + + suspend fun setServerOperators(rh: Long?, operators: List): ServerOperatorConditionsDetail? { + val r = sendCmd(rh, CC.ApiSetServerOperators(operators)) + return when (r) { + is CR.ServerOperatorConditions -> r.conditions + else -> { + Log.e(TAG, "setServerOperators bad response: ${r.responseType} ${r.details}") + null + } + } + } + + suspend fun getUserServers(rh: Long?): List? { + val userId = currentUserId("getUserServers") + val r = sendCmd(rh, CC.ApiGetUserServers(userId)) + return when (r) { + is CR.UserServers -> r.userServers + else -> { + Log.e(TAG, "getUserServers bad response: ${r.responseType} ${r.details}") + null + } + } + } + + suspend fun setUserServers(rh: Long?, userServers: List): Boolean { + val userId = currentUserId("setUserServers") + val r = sendCmd(rh, CC.ApiSetUserServers(userId, userServers)) + return when (r) { + is CR.CmdOk -> true + else -> { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.failed_to_save_servers), + "${r.responseType}: ${r.details}" + ) + Log.e(TAG, "setUserServers bad response: ${r.responseType} ${r.details}") + false + } + } + } + + suspend fun validateServers(rh: Long?, userServers: List): List? { + val userId = currentUserId("validateServers") + val r = sendCmd(rh, CC.ApiValidateServers(userId, userServers)) + return when (r) { + is CR.UserServersValidation -> r.serverErrors + else -> { + Log.e(TAG, "validateServers bad response: ${r.responseType} ${r.details}") + null + } + } + } + + suspend fun getUsageConditions(rh: Long?): Triple? { + val r = sendCmd(rh, CC.ApiGetUsageConditions()) + return when (r) { + is CR.UsageConditions -> Triple(r.usageConditions, r.conditionsText, r.acceptedConditions) + else -> { + Log.e(TAG, "getUsageConditions bad response: ${r.responseType} ${r.details}") + null + } + } + } + + suspend fun setConditionsNotified(rh: Long?, conditionsId: Long): Boolean { + val r = sendCmd(rh, CC.ApiSetConditionsNotified(conditionsId)) + return when (r) { + is CR.CmdOk -> true + else -> { + Log.e(TAG, "setConditionsNotified bad response: ${r.responseType} ${r.details}") + false + } + } + } + + suspend fun acceptConditions(rh: Long?, conditionsId: Long, operatorIds: List): ServerOperatorConditionsDetail? { + val r = sendCmd(rh, CC.ApiAcceptConditions(conditionsId, operatorIds)) + return when (r) { + is CR.ServerOperatorConditions -> r.conditions + else -> { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.error_accepting_operator_conditions), + "${r.responseType}: ${r.details}" + ) + Log.e(TAG, "acceptConditions bad response: ${r.responseType} ${r.details}") + null + } + } + } + suspend fun getChatItemTTL(rh: Long?): ChatItemTTL { val userId = currentUserId("getChatItemTTL") val r = sendCmd(rh, CC.APIGetChatItemTTL(userId)) @@ -3037,9 +3109,15 @@ sealed class CC { class APIGetGroupLink(val groupId: Long): CC() class APICreateMemberContact(val groupId: Long, val groupMemberId: Long): CC() class APISendMemberContactInvitation(val contactId: Long, val mc: MsgContent): CC() - class APIGetUserProtoServers(val userId: Long, val serverProtocol: ServerProtocol): CC() - class APISetUserProtoServers(val userId: Long, val serverProtocol: ServerProtocol, val servers: List): CC() class APITestProtoServer(val userId: Long, val server: String): CC() + class ApiGetServerOperators(): CC() + class ApiSetServerOperators(val operators: List): CC() + class ApiGetUserServers(val userId: Long): CC() + class ApiSetUserServers(val userId: Long, val userServers: List): CC() + class ApiValidateServers(val userId: Long, val userServers: List): CC() + class ApiGetUsageConditions(): CC() + class ApiSetConditionsNotified(val conditionsId: Long): CC() + class ApiAcceptConditions(val conditionsId: Long, val operatorIds: List): CC() class APISetChatItemTTL(val userId: Long, val seconds: Long?): CC() class APIGetChatItemTTL(val userId: Long): CC() class APISetNetworkConfig(val networkConfig: NetCfg): CC() @@ -3197,9 +3275,15 @@ sealed class CC { is APIGetGroupLink -> "/_get link #$groupId" is APICreateMemberContact -> "/_create member contact #$groupId $groupMemberId" is APISendMemberContactInvitation -> "/_invite member contact @$contactId ${mc.cmdString}" - is APIGetUserProtoServers -> "/_servers $userId ${serverProtocol.name.lowercase()}" - is APISetUserProtoServers -> "/_servers $userId ${serverProtocol.name.lowercase()} ${protoServersStr(servers)}" is APITestProtoServer -> "/_server test $userId $server" + is ApiGetServerOperators -> "/_operators" + is ApiSetServerOperators -> "/_operators ${json.encodeToString(operators)}" + is ApiGetUserServers -> "/_servers $userId" + is ApiSetUserServers -> "/_servers $userId ${json.encodeToString(userServers)}" + is ApiValidateServers -> "/_validate_servers $userId ${json.encodeToString(userServers)}" + is ApiGetUsageConditions -> "/_conditions" + is ApiSetConditionsNotified -> "/_conditions_notified ${conditionsId}" + is ApiAcceptConditions -> "/_accept_conditions ${conditionsId} ${operatorIds.joinToString(",")}" is APISetChatItemTTL -> "/_ttl $userId ${chatItemTTLStr(seconds)}" is APIGetChatItemTTL -> "/_ttl $userId" is APISetNetworkConfig -> "/_network ${json.encodeToString(networkConfig)}" @@ -3342,9 +3426,15 @@ sealed class CC { is APIGetGroupLink -> "apiGetGroupLink" is APICreateMemberContact -> "apiCreateMemberContact" is APISendMemberContactInvitation -> "apiSendMemberContactInvitation" - is APIGetUserProtoServers -> "apiGetUserProtoServers" - is APISetUserProtoServers -> "apiSetUserProtoServers" is APITestProtoServer -> "testProtoServer" + is ApiGetServerOperators -> "apiGetServerOperators" + is ApiSetServerOperators -> "apiSetServerOperators" + is ApiGetUserServers -> "apiGetUserServers" + is ApiSetUserServers -> "apiSetUserServers" + is ApiValidateServers -> "apiValidateServers" + is ApiGetUsageConditions -> "apiGetUsageConditions" + is ApiSetConditionsNotified -> "apiSetConditionsNotified" + is ApiAcceptConditions -> "apiAcceptConditions" is APISetChatItemTTL -> "apiSetChatItemTTL" is APIGetChatItemTTL -> "apiGetChatItemTTL" is APISetNetworkConfig -> "apiSetNetworkConfig" @@ -3459,8 +3549,6 @@ sealed class CC { companion object { fun chatRef(chatType: ChatType, id: Long) = "${chatType.type}${id}" - - fun protoServersStr(servers: List) = json.encodeToString(ProtoServersConfig(servers)) } } @@ -3510,24 +3598,350 @@ enum class ServerProtocol { } @Serializable -data class ProtoServersConfig( - val servers: List +enum class OperatorTag { + @SerialName("simplex") SimpleX, + @SerialName("flux") Flux, + @SerialName("xyz") XYZ, + @SerialName("demo") Demo +} + +data class ServerOperatorInfo( + val description: List, + val website: String, + val logo: ImageResource, + val largeLogo: ImageResource, + val logoDarkMode: ImageResource, + val largeLogoDarkMode: ImageResource +) +val operatorsInfo: Map = mapOf( + OperatorTag.SimpleX to ServerOperatorInfo( + description = listOf( + "SimpleX Chat is the first communication network that has no user profile IDs of any kind, not even random numbers or keys that identify the users.", + "SimpleX Chat Ltd develops the communication software for SimpleX network." + ), + website = "https://simplex.chat", + logo = MR.images.decentralized, + largeLogo = MR.images.logo, + logoDarkMode = MR.images.decentralized_light, + largeLogoDarkMode = MR.images.logo_light + ), + OperatorTag.Flux to ServerOperatorInfo( + description = listOf( + "Flux is the largest decentralized cloud infrastructure, leveraging a global network of user-operated computational nodes.", + "Flux offers a powerful, scalable, and affordable platform designed to support individuals, businesses, and cutting-edge technologies like AI. With high uptime and worldwide distribution, Flux ensures reliable, accessible cloud computing for all." + ), + website = "https://runonflux.com", + logo = MR.images.flux_logo_symbol, + largeLogo = MR.images.flux_logo, + logoDarkMode = MR.images.flux_logo_symbol, + largeLogoDarkMode = MR.images.flux_logo_light + ), + OperatorTag.XYZ to ServerOperatorInfo( + description = listOf("XYZ servers"), + website = "XYZ website", + logo = MR.images.shield, + largeLogo = MR.images.logo, + logoDarkMode = MR.images.shield, + largeLogoDarkMode = MR.images.logo_light + ), + OperatorTag.Demo to ServerOperatorInfo( + description = listOf("Demo operator"), + website = "Demo website", + logo = MR.images.decentralized, + largeLogo = MR.images.logo, + logoDarkMode = MR.images.decentralized_light, + largeLogoDarkMode = MR.images.logo_light + ) ) @Serializable -data class UserProtocolServers( - val serverProtocol: ServerProtocol, - val protoServers: List, - val presetServers: List, +data class UsageConditionsDetail( + val conditionsId: Long, + val conditionsCommit: String, + val notifiedAt: Instant?, + val createdAt: Instant +) { + companion object { + val sampleData = UsageConditionsDetail( + conditionsId = 1, + conditionsCommit = "11a44dc1fd461a93079f897048b46998db55da5c", + notifiedAt = null, + createdAt = Clock.System.now() + ) + } +} + +@Serializable +sealed class UsageConditionsAction { + @Serializable @SerialName("review") data class Review(val operators: List, val deadline: Instant?, val showNotice: Boolean) : UsageConditionsAction() + @Serializable @SerialName("accepted") data class Accepted(val operators: List) : UsageConditionsAction() + + val shouldShowNotice: Boolean + get() = when (this) { + is Review -> showNotice + else -> false + } +} + +@Serializable +data class ServerOperatorConditionsDetail( + val serverOperators: List, + val currentConditions: UsageConditionsDetail, + val conditionsAction: UsageConditionsAction? +) { + companion object { + val empty = ServerOperatorConditionsDetail( + serverOperators = emptyList(), + currentConditions = UsageConditionsDetail(conditionsId = 0, conditionsCommit = "empty", notifiedAt = null, createdAt = Clock.System.now()), + conditionsAction = null + ) + } +} + +@Serializable() +sealed class ConditionsAcceptance { + @Serializable @SerialName("accepted") data class Accepted(val acceptedAt: Instant?) : ConditionsAcceptance() + @Serializable @SerialName("required") data class Required(val deadline: Instant?) : ConditionsAcceptance() + + val conditionsAccepted: Boolean + get() = when (this) { + is Accepted -> true + is Required -> false + } + + val usageAllowed: Boolean + get() = when (this) { + is Accepted -> true + is Required -> this.deadline != null + } +} + +@Serializable +data class ServerOperator( + val operatorId: Long, + val operatorTag: OperatorTag?, + val tradeName: String, + val legalName: String?, + val serverDomains: List, + val conditionsAcceptance: ConditionsAcceptance, + val enabled: Boolean, + val smpRoles: ServerRoles, + val xftpRoles: ServerRoles, +) { + companion object { + val dummyOperatorInfo = ServerOperatorInfo( + description = listOf("Default"), + website = "Default", + logo = MR.images.decentralized, + largeLogo = MR.images.logo, + logoDarkMode = MR.images.decentralized_light, + largeLogoDarkMode = MR.images.logo_light + ) + + val sampleData1 = ServerOperator( + operatorId = 1, + operatorTag = OperatorTag.SimpleX, + tradeName = "SimpleX Chat", + legalName = "SimpleX Chat Ltd", + serverDomains = listOf("simplex.im"), + conditionsAcceptance = ConditionsAcceptance.Accepted(acceptedAt = null), + enabled = true, + smpRoles = ServerRoles(storage = true, proxy = true), + xftpRoles = ServerRoles(storage = true, proxy = true) + ) + + val sampleData2 = ServerOperator( + operatorId = 2, + operatorTag = OperatorTag.XYZ, + tradeName = "XYZ", + legalName = null, + serverDomains = listOf("xyz.com"), + conditionsAcceptance = ConditionsAcceptance.Required(deadline = null), + enabled = false, + smpRoles = ServerRoles(storage = false, proxy = true), + xftpRoles = ServerRoles(storage = false, proxy = true) + ) + + val sampleData3 = ServerOperator( + operatorId = 3, + operatorTag = OperatorTag.Demo, + tradeName = "Demo", + legalName = null, + serverDomains = listOf("demo.com"), + conditionsAcceptance = ConditionsAcceptance.Required(deadline = null), + enabled = false, + smpRoles = ServerRoles(storage = true, proxy = false), + xftpRoles = ServerRoles(storage = true, proxy = false) + ) + } + + val id: Long + get() = operatorId + + override fun equals(other: Any?): Boolean { + if (other !is ServerOperator) return false + return other.operatorId == this.operatorId && + other.operatorTag == this.operatorTag && + other.tradeName == this.tradeName && + other.legalName == this.legalName && + other.serverDomains == this.serverDomains && + other.conditionsAcceptance == this.conditionsAcceptance && + other.enabled == this.enabled && + other.smpRoles == this.smpRoles && + other.xftpRoles == this.xftpRoles + } + + override fun hashCode(): Int { + var result = operatorId.hashCode() + result = 31 * result + (operatorTag?.hashCode() ?: 0) + result = 31 * result + tradeName.hashCode() + result = 31 * result + (legalName?.hashCode() ?: 0) + result = 31 * result + serverDomains.hashCode() + result = 31 * result + conditionsAcceptance.hashCode() + result = 31 * result + enabled.hashCode() + result = 31 * result + smpRoles.hashCode() + result = 31 * result + xftpRoles.hashCode() + return result + } + + val legalName_: String + get() = legalName ?: tradeName + + val info: ServerOperatorInfo get() { + return if (this.operatorTag != null) { + operatorsInfo[this.operatorTag] ?: dummyOperatorInfo + } else { + dummyOperatorInfo + } + } + + val logo: ImageResource + @Composable + get() { + return if (isInDarkTheme()) info.logoDarkMode else info.logo + } + + val largeLogo: ImageResource + @Composable + get() { + return if (isInDarkTheme()) info.largeLogoDarkMode else info.largeLogo + } +} + +@Serializable +data class ServerRoles( + val storage: Boolean, + val proxy: Boolean ) @Serializable -data class ServerCfg( +data class UserOperatorServers( + val operator: ServerOperator?, + val smpServers: List, + val xftpServers: List +) { + val id: String + get() = operator?.operatorId?.toString() ?: "nil operator" + + val operator_: ServerOperator + get() = operator ?: ServerOperator( + operatorId = 0, + operatorTag = null, + tradeName = "", + legalName = null, + serverDomains = emptyList(), + conditionsAcceptance = ConditionsAcceptance.Accepted(null), + enabled = false, + smpRoles = ServerRoles(storage = true, proxy = true), + xftpRoles = ServerRoles(storage = true, proxy = true) + ) + + companion object { + val sampleData1 = UserOperatorServers( + operator = ServerOperator.sampleData1, + smpServers = listOf(UserServer.sampleData.preset), + xftpServers = listOf(UserServer.sampleData.xftpPreset) + ) + + val sampleDataNilOperator = UserOperatorServers( + operator = null, + smpServers = listOf(UserServer.sampleData.preset), + xftpServers = listOf(UserServer.sampleData.xftpPreset) + ) + } +} + +@Serializable +sealed class UserServersError { + @Serializable @SerialName("noServers") data class NoServers(val protocol: ServerProtocol, val user: UserRef?): UserServersError() + @Serializable @SerialName("storageMissing") data class StorageMissing(val protocol: ServerProtocol, val user: UserRef?): UserServersError() + @Serializable @SerialName("proxyMissing") data class ProxyMissing(val protocol: ServerProtocol, val user: UserRef?): UserServersError() + @Serializable @SerialName("duplicateServer") data class DuplicateServer(val protocol: ServerProtocol, val duplicateServer: String, val duplicateHost: String): UserServersError() + + val globalError: String? + get() = when (this.protocol_) { + ServerProtocol.SMP -> globalSMPError + ServerProtocol.XFTP -> globalXFTPError + } + + private val protocol_: ServerProtocol + get() = when (this) { + is NoServers -> this.protocol + is StorageMissing -> this.protocol + is ProxyMissing -> this.protocol + is DuplicateServer -> this.protocol + } + + val globalSMPError: String? + get() = if (this.protocol_ == ServerProtocol.SMP) { + when (this) { + is NoServers -> this.user?.let { "${userStr(it)} ${generalGetString(MR.strings.no_message_servers_configured)}" } + ?: generalGetString(MR.strings.no_message_servers_configured) + + is StorageMissing -> this.user?.let { "${userStr(it)} ${generalGetString(MR.strings.no_message_servers_configured_for_receiving)}" } + ?: generalGetString(MR.strings.no_message_servers_configured_for_receiving) + + is ProxyMissing -> this.user?.let { "${userStr(it)} ${generalGetString(MR.strings.no_message_servers_configured_for_private_routing)}" } + ?: generalGetString(MR.strings.no_message_servers_configured_for_private_routing) + + else -> null + } + } else { + null + } + + val globalXFTPError: String? + get() = if (this.protocol_ == ServerProtocol.XFTP) { + when (this) { + is NoServers -> this.user?.let { "${userStr(it)} ${generalGetString(MR.strings.no_media_servers_configured)}" } + ?: generalGetString(MR.strings.no_media_servers_configured) + + is StorageMissing -> this.user?.let { "${userStr(it)} ${generalGetString(MR.strings.no_media_servers_configured_for_sending)}" } + ?: generalGetString(MR.strings.no_media_servers_configured_for_sending) + + is ProxyMissing -> this.user?.let { "${userStr(it)} ${generalGetString(MR.strings.no_media_servers_configured_for_private_routing)}" } + ?: generalGetString(MR.strings.no_media_servers_configured_for_private_routing) + + else -> null + } + } else { + null + } + + private fun userStr(user: UserRef): String { + return String.format(generalGetString(MR.strings.for_chat_profile), user.localDisplayName) + } +} + +@Serializable +data class UserServer( val remoteHostId: Long?, + val serverId: Long?, val server: String, val preset: Boolean, val tested: Boolean? = null, - val enabled: Boolean + val enabled: Boolean, + val deleted: Boolean ) { @Transient private val createdAt: Date = Date() @@ -3541,35 +3955,51 @@ data class ServerCfg( get() = server.isBlank() companion object { - val empty = ServerCfg(remoteHostId = null, server = "", preset = false, tested = null, enabled = false) + val empty = UserServer(remoteHostId = null, serverId = null, server = "", preset = false, tested = null, enabled = false, deleted = false) class SampleData( - val preset: ServerCfg, - val custom: ServerCfg, - val untested: ServerCfg + val preset: UserServer, + val custom: UserServer, + val untested: UserServer, + val xftpPreset: UserServer ) val sampleData = SampleData( - preset = ServerCfg( + preset = UserServer( remoteHostId = null, + serverId = 1, server = "smp://abcd@smp8.simplex.im", preset = true, tested = true, - enabled = true + enabled = true, + deleted = false ), - custom = ServerCfg( + custom = UserServer( remoteHostId = null, + serverId = 2, server = "smp://abcd@smp9.simplex.im", preset = false, tested = false, - enabled = false + enabled = false, + deleted = false ), - untested = ServerCfg( + untested = UserServer( remoteHostId = null, + serverId = 3, server = "smp://abcd@smp10.simplex.im", preset = false, tested = null, - enabled = true + enabled = true, + deleted = false + ), + xftpPreset = UserServer( + remoteHostId = null, + serverId = 4, + server = "xftp://abcd@xftp8.simplex.im", + preset = true, + tested = true, + enabled = true, + deleted = false ) ) } @@ -4928,8 +5358,11 @@ sealed class CR { @Serializable @SerialName("apiChats") class ApiChats(val user: UserRef, val chats: List): CR() @Serializable @SerialName("apiChat") class ApiChat(val user: UserRef, val chat: Chat, val navInfo: NavigationInfo = NavigationInfo()): CR() @Serializable @SerialName("chatItemInfo") class ApiChatItemInfo(val user: UserRef, val chatItem: AChatItem, val chatItemInfo: ChatItemInfo): CR() - @Serializable @SerialName("userProtoServers") class UserProtoServers(val user: UserRef, val servers: UserProtocolServers): CR() @Serializable @SerialName("serverTestResult") class ServerTestResult(val user: UserRef, val testServer: String, val testFailure: ProtocolTestFailure? = null): CR() + @Serializable @SerialName("serverOperatorConditions") class ServerOperatorConditions(val conditions: ServerOperatorConditionsDetail): CR() + @Serializable @SerialName("userServers") class UserServers(val user: UserRef, val userServers: List): CR() + @Serializable @SerialName("userServersValidation") class UserServersValidation(val user: UserRef, val serverErrors: List): CR() + @Serializable @SerialName("usageConditions") class UsageConditions(val usageConditions: UsageConditionsDetail, val conditionsText: String?, val acceptedConditions: UsageConditionsDetail?): CR() @Serializable @SerialName("chatItemTTL") class ChatItemTTL(val user: UserRef, val chatItemTTL: Long? = null): CR() @Serializable @SerialName("networkConfig") class NetworkConfig(val networkConfig: NetCfg): CR() @Serializable @SerialName("contactInfo") class ContactInfo(val user: UserRef, val contact: Contact, val connectionStats_: ConnectionStats? = null, val customUserProfile: Profile? = null): CR() @@ -5108,8 +5541,11 @@ sealed class CR { is ApiChats -> "apiChats" is ApiChat -> "apiChat" is ApiChatItemInfo -> "chatItemInfo" - is UserProtoServers -> "userProtoServers" is ServerTestResult -> "serverTestResult" + is ServerOperatorConditions -> "serverOperatorConditions" + is UserServers -> "userServers" + is UserServersValidation -> "userServersValidation" + is UsageConditions -> "usageConditions" is ChatItemTTL -> "chatItemTTL" is NetworkConfig -> "networkConfig" is ContactInfo -> "contactInfo" @@ -5278,8 +5714,11 @@ sealed class CR { is ApiChats -> withUser(user, json.encodeToString(chats)) is ApiChat -> withUser(user, "chat: ${json.encodeToString(chat)}\nnavInfo: ${navInfo}") is ApiChatItemInfo -> withUser(user, "chatItem: ${json.encodeToString(chatItem)}\n${json.encodeToString(chatItemInfo)}") - is UserProtoServers -> withUser(user, "servers: ${json.encodeToString(servers)}") is ServerTestResult -> withUser(user, "server: $testServer\nresult: ${json.encodeToString(testFailure)}") + is ServerOperatorConditions -> "conditions: ${json.encodeToString(conditions)}" + is UserServers -> withUser(user, "userServers: ${json.encodeToString(userServers)}") + is UserServersValidation -> withUser(user, "serverErrors: ${json.encodeToString(serverErrors)}") + is UsageConditions -> "usageConditions: ${json.encodeToString(usageConditions)}\nnacceptedConditions: ${json.encodeToString(acceptedConditions)}" is ChatItemTTL -> withUser(user, json.encodeToString(chatItemTTL)) is NetworkConfig -> json.encodeToString(networkConfig) is ContactInfo -> withUser(user, "contact: ${json.encodeToString(contact)}\nconnectionStats: ${json.encodeToString(connectionStats_)}") diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt index 79132b5eb1..08ca72c6bd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt @@ -118,6 +118,7 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat if (appPreferences.encryptionStartedAt.get() != null) appPreferences.encryptionStartedAt.set(null) val user = chatController.apiGetActiveUser(null) chatModel.currentUser.value = user + chatModel.conditions.value = chatController.getServerOperators(null) ?: ServerOperatorConditionsDetail.empty if (user == null) { chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true) chatModel.currentUser.value = null @@ -137,13 +138,12 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat } } else if (startChat().await()) { val savedOnboardingStage = appPreferences.onboardingStage.get() - val next = if (appPlatform.isAndroid) { - OnboardingStage.Step4_SetNotificationsMode - } else { - OnboardingStage.OnboardingComplete - } val newStage = if (listOf(OnboardingStage.Step1_SimpleXInfo, OnboardingStage.Step2_CreateProfile).contains(savedOnboardingStage) && chatModel.users.size == 1) { - next + if (appPlatform.isAndroid) { + OnboardingStage.Step4_SetNotificationsMode + } else { + OnboardingStage.OnboardingComplete + } } else { savedOnboardingStage } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt index 17658d23e8..15d38c5490 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt @@ -165,7 +165,7 @@ fun createProfileInNoProfileSetup(displayName: String, close: () -> Unit) { if (!chatModel.connectedToRemote()) { chatModel.localUserCreated.value = true } - controller.appPrefs.onboardingStage.set(OnboardingStage.Step4_SetNotificationsMode) + controller.appPrefs.onboardingStage.set(OnboardingStage.Step3_ChooseServerOperators) controller.startChat(user) controller.switchUIRemoteHost(null) close() @@ -204,7 +204,7 @@ fun createProfileOnboarding(chatModel: ChatModel, displayName: String, close: () onboardingStage.set(if (appPlatform.isDesktop && chatModel.controller.appPrefs.initialRandomDBPassphrase.get() && !chatModel.desktopOnboardingRandomPassword.value) { OnboardingStage.Step2_5_SetupDatabasePassphrase } else { - OnboardingStage.Step4_SetNotificationsMode + OnboardingStage.Step3_ChooseServerOperators }) } else { // the next two lines are only needed for failure case when because of the database error the app gets stuck on on-boarding screen, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 8dd3e42440..e9e590ee95 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -1249,7 +1249,7 @@ fun BoxScope.ChatItemsList( } else { null } - val showAvatar = if (merged is MergedItem.Grouped) shouldShowAvatar(item, listItem.nextItem) else true + val showAvatar = shouldShowAvatar(item, listItem.nextItem) val isRevealed = remember { derivedStateOf { revealedItems.value.contains(item.id) } } val itemSeparation: ItemSeparation val prevItemSeparationLargeGap: Boolean diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 9661a305cc..20bb65ec7d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -25,19 +25,21 @@ import androidx.compose.ui.unit.* import chat.simplex.common.AppLock import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs +import chat.simplex.common.model.ChatController.setConditionsNotified import chat.simplex.common.model.ChatController.stopRemoteHostAndReloadHosts import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* -import chat.simplex.common.views.onboarding.WhatsNewView -import chat.simplex.common.views.onboarding.shouldShowWhatsNew import chat.simplex.common.platform.* import chat.simplex.common.views.call.Call import chat.simplex.common.views.chat.item.CIFileViewScope import chat.simplex.common.views.chat.topPaddingToContent import chat.simplex.common.views.mkValidName import chat.simplex.common.views.newchat.* +import chat.simplex.common.views.onboarding.* import chat.simplex.common.views.showInvalidNameAlert import chat.simplex.common.views.usersettings.* +import chat.simplex.common.views.usersettings.networkAndServers.ConditionsLinkButton +import chat.simplex.common.views.usersettings.networkAndServers.UsageConditionsView import chat.simplex.res.MR import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableStateFlow @@ -115,10 +117,26 @@ fun ToggleChatListCard() { @Composable fun ChatListView(chatModel: ChatModel, userPickerState: MutableStateFlow, setPerformLA: (Boolean) -> Unit, stopped: Boolean) { val oneHandUI = remember { appPrefs.oneHandUI.state } + val rhId = chatModel.remoteHostId() + LaunchedEffect(Unit) { - if (shouldShowWhatsNew(chatModel)) { + val showWhatsNew = shouldShowWhatsNew(chatModel) + val showUpdatedConditions = chatModel.conditions.value.conditionsAction?.shouldShowNotice ?: false + if (showWhatsNew) { delay(1000L) - ModalManager.center.showCustomModal { close -> WhatsNewView(close = close) } + ModalManager.center.showCustomModal { close -> WhatsNewView(close = close, updatedConditions = showUpdatedConditions) } + } else if (showUpdatedConditions) { + ModalManager.center.showModalCloseable(endButtons = { ConditionsLinkButton() }) { close -> + LaunchedEffect(Unit) { + val conditionsId = chatModel.conditions.value.currentConditions.conditionsId + try { + setConditionsNotified(rh = rhId, conditionsId = conditionsId) + } catch (e: Exception) { + Log.d(TAG, "UsageConditionsView setConditionsNotified error: ${e.message}") + } + } + UsageConditionsView(userServers = mutableStateOf(emptyList()), currUserServers = mutableStateOf(emptyList()), close = close, rhId = rhId) + } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt index 4e3ee2340c..acbc72ff48 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt @@ -48,7 +48,6 @@ import chat.simplex.common.model.localTimestamp import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* -import chat.simplex.common.views.usersettings.ProtocolServersView import chat.simplex.common.views.usersettings.SettingsPreferenceItem import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource @@ -540,15 +539,8 @@ fun XFTPServerSummaryLayout(summary: XFTPServerSummary, statsStartedAt: Instant, ) ) } - if (summary.known == true) { - SectionItemView(click = { - ModalManager.start.showCustomModal { close -> ProtocolServersView(chatModel, rhId = rh?.remoteHostId, ServerProtocol.XFTP, close) } - }) { - Text(generalGetString(MR.strings.open_server_settings_button)) - } - if (summary.stats != null || summary.sessions != null) { - SectionDividerSpaced() - } + if (summary.stats != null || summary.sessions != null) { + SectionDividerSpaced() } if (summary.stats != null) { @@ -579,12 +571,7 @@ fun SMPServerSummaryLayout(summary: SMPServerSummary, statsStartedAt: Instant, r ) ) } - if (summary.known == true) { - SectionItemView(click = { - ModalManager.start.showCustomModal { close -> ProtocolServersView(chatModel, rhId = rh?.remoteHostId, ServerProtocol.SMP, close) } - }) { - Text(generalGetString(MR.strings.open_server_settings_button)) - } + if (summary.stats != null || summary.subs != null || summary.sessions != null) { SectionDividerSpaced() } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AppBarTitle.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AppBarTitle.kt index 195ec020e5..afb557cc78 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AppBarTitle.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AppBarTitle.kt @@ -17,11 +17,21 @@ import dev.icerock.moko.resources.compose.painterResource import kotlin.math.absoluteValue @Composable -fun AppBarTitle(title: String, hostDevice: Pair? = null, withPadding: Boolean = true, bottomPadding: Dp = DEFAULT_PADDING * 1.5f + 8.dp) { +fun AppBarTitle( + title: String, + hostDevice: Pair? = null, + withPadding: Boolean = true, + bottomPadding: Dp = DEFAULT_PADDING * 1.5f + 8.dp, + enableAlphaChanges: Boolean = true +) { val handler = LocalAppBarHandler.current - val connection = handler?.connection + val connection = if (enableAlphaChanges) handler?.connection else null LaunchedEffect(title) { - handler?.title?.value = title + if (enableAlphaChanges) { + handler?.title?.value = title + } else { + handler?.connection?.scrollTrackingEnabled = false + } } val theme = CurrentColors.collectAsState() val titleColor = MaterialTheme.appColors.title @@ -54,7 +64,8 @@ fun AppBarTitle(title: String, hostDevice: Pair? = null, withPad } private fun bottomTitleAlpha(connection: CollapsingAppBarNestedScrollConnection?) = - if ((connection?.appBarOffset ?: 0f).absoluteValue < AppBarHandler.appBarMaxHeightPx / 3) 1f + if (connection?.scrollTrackingEnabled == false) 1f + else if ((connection?.appBarOffset ?: 0f).absoluteValue < AppBarHandler.appBarMaxHeightPx / 3) 1f else ((AppBarHandler.appBarMaxHeightPx) + (connection?.appBarOffset ?: 0f) / 1.5f).coerceAtLeast(0f) / AppBarHandler.appBarMaxHeightPx @Composable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CollapsingAppBar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CollapsingAppBar.kt index 50942169b3..ad6611b9d9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CollapsingAppBar.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CollapsingAppBar.kt @@ -84,6 +84,7 @@ class AppBarHandler( } class CollapsingAppBarNestedScrollConnection(): NestedScrollConnection { + var scrollTrackingEnabled = true var appBarOffset: Float by mutableFloatStateOf(0f) override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt index cf0c5f7e96..4bf20d2128 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt @@ -258,7 +258,8 @@ private fun AppBarCenterAligned( } private fun topTitleAlpha(text: Boolean, connection: CollapsingAppBarNestedScrollConnection, alpha: Float = appPrefs.inAppBarsAlpha.get()) = - if (connection.appBarOffset.absoluteValue < AppBarHandler.appBarMaxHeightPx / 3) 0f + if (!connection.scrollTrackingEnabled) 0f + else if (connection.appBarOffset.absoluteValue < AppBarHandler.appBarMaxHeightPx / 3) 0f else ((-connection.appBarOffset * 1.5f) / (AppBarHandler.appBarMaxHeightPx)).coerceIn(0f, if (text) 1f else alpha) val AppBarHeight = 56.dp diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt index 90f8593c4a..28ec77de70 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt @@ -26,6 +26,7 @@ import chat.simplex.common.views.helpers.DatabaseUtils.ksDatabasePassword import chat.simplex.common.views.newchat.QRCodeScanner import chat.simplex.common.views.onboarding.OnboardingStage import chat.simplex.common.views.usersettings.* +import chat.simplex.common.views.usersettings.networkAndServers.OnionRelatedLayout import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt new file mode 100644 index 0000000000..8b383e0146 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt @@ -0,0 +1,354 @@ +package chat.simplex.common.views.onboarding + +import SectionBottomSpacer +import SectionTextFooter +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.* +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import chat.simplex.common.model.ChatController.appPrefs +import chat.simplex.common.model.ServerOperator +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.usersettings.networkAndServers.* +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource + +@Composable +fun ModalData.ChooseServerOperators( + onboarding: Boolean, + close: (() -> Unit) = { ModalManager.fullscreen.closeModals() }, + modalManager: ModalManager = ModalManager.fullscreen +) { + LaunchedEffect(Unit) { + prepareChatBeforeFinishingOnboarding() + } + + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { + ModalView({}, showClose = false, endButtons = { + IconButton({ modalManager.showModal { ChooseServerOperatorsInfoView() } }) { + Icon(painterResource(MR.images.ic_info), null, Modifier.size(28.dp), tint = MaterialTheme.colors.primary) + } + }) { + val serverOperators = remember { derivedStateOf { chatModel.conditions.value.serverOperators } } + val selectedOperatorIds = remember { stateGetOrPut("selectedOperatorIds") { serverOperators.value.filter { it.enabled }.map { it.operatorId }.toSet() } } + val selectedOperators = remember { derivedStateOf { serverOperators.value.filter { selectedOperatorIds.value.contains(it.operatorId) } } } + + ColumnWithScrollBar( + Modifier + .themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer), + maxIntrinsicSize = true + ) { + Box(Modifier.align(Alignment.CenterHorizontally)) { + AppBarTitle(stringResource(MR.strings.onboarding_choose_server_operators)) + } + Column(( + if (appPlatform.isDesktop) Modifier.width(600.dp).align(Alignment.CenterHorizontally) else Modifier) + .padding(horizontal = DEFAULT_PADDING) + ) { + Text(stringResource(MR.strings.onboarding_select_network_operators_to_use)) + Spacer(Modifier.height(DEFAULT_PADDING)) + } + Spacer(Modifier.weight(1f)) + Column(( + if (appPlatform.isDesktop) Modifier.width(600.dp).align(Alignment.CenterHorizontally) else Modifier) + .fillMaxWidth() + .padding(horizontal = DEFAULT_PADDING) + ) { + serverOperators.value.forEachIndexed { index, srvOperator -> + OperatorCheckView(srvOperator, selectedOperatorIds) + if (index != serverOperators.value.lastIndex) { + Spacer(Modifier.height(DEFAULT_PADDING)) + } + } + Spacer(Modifier.height(DEFAULT_PADDING_HALF)) + + SectionTextFooter(annotatedStringResource(MR.strings.onboarding_network_operators_configure_via_settings), textAlign = TextAlign.Center) + } + Spacer(Modifier.weight(1f)) + + val reviewForOperators = selectedOperators.value.filter { !it.conditionsAcceptance.conditionsAccepted } + val canReviewLater = reviewForOperators.all { it.conditionsAcceptance.usageAllowed } + val currEnabledOperatorIds = serverOperators.value.filter { it.enabled }.map { it.operatorId }.toSet() + + Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + val enabled = selectedOperatorIds.value.isNotEmpty() + when { + reviewForOperators.isNotEmpty() -> ReviewConditionsButton(enabled, onboarding, selectedOperators, selectedOperatorIds, modalManager) + selectedOperatorIds.value != currEnabledOperatorIds && enabled -> SetOperatorsButton(true, onboarding, serverOperators, selectedOperatorIds, close) + else -> ContinueButton(enabled, onboarding, close) + } + if (onboarding && reviewForOperators.isEmpty()) { + TextButtonBelowOnboardingButton(stringResource(MR.strings.operator_conditions_of_use)) { + modalManager.showModalCloseable(endButtons = { ConditionsLinkButton() }) { close -> + UsageConditionsView( + currUserServers = remember { mutableStateOf(emptyList()) }, + userServers = remember { mutableStateOf(emptyList()) }, + close = close, + rhId = null + ) + } + } + } else if (onboarding || reviewForOperators.isEmpty()) { + // Reserve space + TextButtonBelowOnboardingButton("", null) + } + if (!onboarding && reviewForOperators.isNotEmpty()) { + ReviewLaterButton(canReviewLater, close) + SectionTextFooter( + annotatedStringResource(MR.strings.onboarding_network_operators_conditions_will_be_accepted) + + AnnotatedString(" ") + + annotatedStringResource(MR.strings.onboarding_network_operators_conditions_you_can_configure), + textAlign = TextAlign.Center + ) + SectionBottomSpacer() + } + } + } + } + } +} + +@Composable +private fun OperatorCheckView(serverOperator: ServerOperator, selectedOperatorIds: MutableState>) { + val checked = selectedOperatorIds.value.contains(serverOperator.operatorId) + TextButton({ + if (checked) { + selectedOperatorIds.value -= serverOperator.operatorId + } else { + selectedOperatorIds.value += serverOperator.operatorId + } + }, + border = BorderStroke(1.dp, color = if (checked) MaterialTheme.colors.primary else MaterialTheme.colors.secondary.copy(alpha = 0.5f)), + shape = RoundedCornerShape(18.dp) + ) { + Row(Modifier.padding(DEFAULT_PADDING_HALF), verticalAlignment = Alignment.CenterVertically) { + Image(painterResource(serverOperator.largeLogo), null, Modifier.height(48.dp)) + Spacer(Modifier.width(DEFAULT_PADDING_HALF).weight(1f)) + CircleCheckbox(checked) + } + } +} + +@Composable +private fun CircleCheckbox(checked: Boolean) { + if (checked) { + Box(contentAlignment = Alignment.Center) { + Icon( + painterResource(MR.images.ic_circle_filled), + null, + Modifier.size(26.dp), + tint = MaterialTheme.colors.primary + ) + Icon( + painterResource(MR.images.ic_check_filled), + null, + Modifier.size(20.dp), tint = MaterialTheme.colors.background + ) + } + } else { + Icon( + painterResource(MR.images.ic_circle), + null, + Modifier.size(26.dp), + tint = MaterialTheme.colors.secondary.copy(alpha = 0.5f) + ) + } +} + +@Composable +private fun ReviewConditionsButton( + enabled: Boolean, + onboarding: Boolean, + selectedOperators: State>, + selectedOperatorIds: State>, + modalManager: ModalManager +) { + OnboardingActionButton( + modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier, + labelId = MR.strings.operator_review_conditions, + onboarding = null, + enabled = enabled, + onclick = { + modalManager.showModalCloseable(endButtons = { ConditionsLinkButton() }) { close -> + ReviewConditionsView(onboarding, selectedOperators, selectedOperatorIds, close) + } + } + ) +} + +@Composable +private fun SetOperatorsButton(enabled: Boolean, onboarding: Boolean, serverOperators: State>, selectedOperatorIds: State>, close: () -> Unit) { + OnboardingActionButton( + modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier, + labelId = MR.strings.onboarding_network_operators_update, + onboarding = null, + enabled = enabled, + onclick = { + withBGApi { + val enabledOperators = enabledOperators(serverOperators.value, selectedOperatorIds.value) + if (enabledOperators != null) { + val r = chatController.setServerOperators(rh = chatModel.remoteHostId(), operators = enabledOperators) + if (r != null) { + chatModel.conditions.value = r + } + continueToNextStep(onboarding, close) + } + } + } + ) +} + +@Composable +private fun ContinueButton(enabled: Boolean, onboarding: Boolean, close: () -> Unit) { + OnboardingActionButton( + modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier, + labelId = MR.strings.onboarding_network_operators_continue, + onboarding = null, + enabled = enabled, + onclick = { + continueToNextStep(onboarding, close) + } + ) +} + +@Composable +private fun ReviewLaterButton(enabled: Boolean, close: () -> Unit) { + TextButtonBelowOnboardingButton( + stringResource(MR.strings.onboarding_network_operators_review_later), + onClick = if (!enabled) null else {{ continueToNextStep(false, close) }} + ) +} + +@Composable +private fun ReviewConditionsView( + onboarding: Boolean, + selectedOperators: State>, + selectedOperatorIds: State>, + close: () -> Unit +) { + // remembering both since we don't want to reload the view after the user accepts conditions + val operatorsWithConditionsAccepted = remember { chatModel.conditions.value.serverOperators.filter { it.conditionsAcceptance.conditionsAccepted } } + val acceptForOperators = remember { selectedOperators.value.filter { !it.conditionsAcceptance.conditionsAccepted } } + ColumnWithScrollBar(modifier = Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING)) { + AppBarTitle(stringResource(MR.strings.operator_conditions_of_use), withPadding = false, enableAlphaChanges = false) + if (operatorsWithConditionsAccepted.isNotEmpty()) { + ReadableText(MR.strings.operator_conditions_accepted_for_some, args = operatorsWithConditionsAccepted.joinToString(", ") { it.legalName_ }) + ReadableText(MR.strings.operator_same_conditions_will_apply_to_operators, args = acceptForOperators.joinToString(", ") { it.legalName_ }) + } else { + ReadableText(MR.strings.operator_conditions_will_be_accepted_for_some, args = acceptForOperators.joinToString(", ") { it.legalName_ }) + } + Column(modifier = Modifier.weight(1f).padding(top = DEFAULT_PADDING_HALF)) { + ConditionsTextView(chatModel.remoteHostId()) + } + Column(Modifier.padding(top = DEFAULT_PADDING).widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + AcceptConditionsButton(onboarding, selectedOperators, selectedOperatorIds, close) + // Reserve space + TextButtonBelowOnboardingButton("", null) + } + } +} + +@Composable +private fun AcceptConditionsButton( + onboarding: Boolean, + selectedOperators: State>, + selectedOperatorIds: State>, + close: () -> Unit +) { + fun continueOnAccept() { + if (appPlatform.isDesktop || !onboarding) { + if (onboarding) { close() } + continueToNextStep(onboarding, close) + } else { + continueToSetNotificationsAfterAccept() + } + } + OnboardingActionButton( + modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier, + labelId = MR.strings.accept_conditions, + onboarding = null, + onclick = { + withBGApi { + val conditionsId = chatModel.conditions.value.currentConditions.conditionsId + val acceptForOperators = selectedOperators.value.filter { !it.conditionsAcceptance.conditionsAccepted } + val operatorIds = acceptForOperators.map { it.operatorId } + val r = chatController.acceptConditions(chatModel.remoteHostId(), conditionsId = conditionsId, operatorIds = operatorIds) + if (r != null) { + chatModel.conditions.value = r + val enabledOperators = enabledOperators(r.serverOperators, selectedOperatorIds.value) + if (enabledOperators != null) { + val r2 = chatController.setServerOperators(rh = chatModel.remoteHostId(), operators = enabledOperators) + if (r2 != null) { + chatModel.conditions.value = r2 + continueOnAccept() + } + } else { + continueOnAccept() + } + } + } + } + ) +} + +private fun continueToNextStep(onboarding: Boolean, close: () -> Unit) { + if (onboarding) { + appPrefs.onboardingStage.set(if (appPlatform.isAndroid) OnboardingStage.Step4_SetNotificationsMode else OnboardingStage.OnboardingComplete) + } else { + close() + } +} + +private fun continueToSetNotificationsAfterAccept() { + appPrefs.onboardingStage.set(OnboardingStage.Step4_SetNotificationsMode) + ModalManager.fullscreen.showModalCloseable(showClose = false) { SetNotificationsMode(chatModel) } +} + +private fun enabledOperators(operators: List, selectedOperatorIds: Set): List? { + val ops = ArrayList(operators) + if (ops.isNotEmpty()) { + for (i in ops.indices) { + val op = ops[i] + ops[i] = op.copy(enabled = selectedOperatorIds.contains(op.operatorId)) + } + val haveSMPStorage = ops.any { it.enabled && it.smpRoles.storage } + val haveSMPProxy = ops.any { it.enabled && it.smpRoles.proxy } + val haveXFTPStorage = ops.any { it.enabled && it.xftpRoles.storage } + val haveXFTPProxy = ops.any { it.enabled && it.xftpRoles.proxy } + val firstEnabledIndex = ops.indexOfFirst { it.enabled } + if (haveSMPStorage && haveSMPProxy && haveXFTPStorage && haveXFTPProxy) { + return ops + } else if (firstEnabledIndex != -1) { + var op = ops[firstEnabledIndex] + if (!haveSMPStorage) op = op.copy(smpRoles = op.smpRoles.copy(storage = true)) + if (!haveSMPProxy) op = op.copy(smpRoles = op.smpRoles.copy(proxy = true)) + if (!haveXFTPStorage) op = op.copy(xftpRoles = op.xftpRoles.copy(storage = true)) + if (!haveXFTPProxy) op = op.copy(xftpRoles = op.xftpRoles.copy(proxy = true)) + ops[firstEnabledIndex] = op + return ops + } else { // Shouldn't happen - view doesn't let to proceed if no operators are enabled + return null + } + } else { + return null + } +} + +@Composable +private fun ChooseServerOperatorsInfoView() { + ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_PADDING)) { + AppBarTitle(stringResource(MR.strings.onboarding_network_operators), withPadding = false) + ReadableText(stringResource(MR.strings.onboarding_network_operators_app_will_use_different_operators)) + ReadableText(stringResource(MR.strings.onboarding_network_operators_app_will_use_for_routing)) + SectionBottomSpacer() + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt index 98e8ec971d..34b6209ffe 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt @@ -48,8 +48,8 @@ fun HowItWorks(user: User?, onboardingStage: SharedPreference? } @Composable -fun ReadableText(stringResId: StringResource, textAlign: TextAlign = TextAlign.Start, padding: PaddingValues = PaddingValues(bottom = 12.dp), style: TextStyle = LocalTextStyle.current) { - Text(annotatedStringResource(stringResId), modifier = Modifier.padding(padding), textAlign = textAlign, lineHeight = 22.sp, style = style) +fun ReadableText(stringResId: StringResource, textAlign: TextAlign = TextAlign.Start, padding: PaddingValues = PaddingValues(bottom = 12.dp), style: TextStyle = LocalTextStyle.current, args: Any? = null) { + Text(annotatedStringResource(stringResId, args), modifier = Modifier.padding(padding), textAlign = textAlign, lineHeight = 22.sp, style = style) } @Composable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingView.kt index d4c63248e5..510df13c3d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingView.kt @@ -5,6 +5,7 @@ enum class OnboardingStage { Step2_CreateProfile, LinkAMobile, Step2_5_SetupDatabasePassphrase, + Step3_ChooseServerOperators, Step3_CreateSimpleXAddress, Step4_SetNotificationsMode, OnboardingComplete diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt index d6d5753b6c..49c91813dc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt @@ -16,8 +16,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.ChatModel import chat.simplex.common.model.NotificationsMode -import chat.simplex.common.platform.ColumnWithScrollBar -import chat.simplex.common.platform.appPlatform +import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.changeNotificationsMode @@ -26,7 +25,7 @@ import chat.simplex.res.MR @Composable fun SetNotificationsMode(m: ChatModel) { LaunchedEffect(Unit) { - prepareChatBeforeNotificationsSetup(m) + prepareChatBeforeFinishingOnboarding() } CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { @@ -57,6 +56,7 @@ fun SetNotificationsMode(m: ChatModel) { onboarding = OnboardingStage.OnboardingComplete, onclick = { changeNotificationsMode(currentMode.value, m) + ModalManager.fullscreen.closeModals() } ) // Reserve space @@ -99,7 +99,7 @@ fun SelectableCard(currentValue: State, newValue: T, title: String, descr Spacer(Modifier.height(14.dp)) } -private fun prepareChatBeforeNotificationsSetup(chatModel: ChatModel) { +fun prepareChatBeforeFinishingOnboarding() { // No visible users but may have hidden. In this case chat should be started anyway because it's stopped on this stage with hidden users if (chatModel.users.any { u -> !u.user.hidden }) return withBGApi { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt index 4ad2675e83..f20cb38dad 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt @@ -17,7 +17,6 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import chat.simplex.common.model.* -import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.database.* @@ -36,7 +35,7 @@ fun SetupDatabasePassphrase(m: ChatModel) { val confirmNewKey = rememberSaveable { mutableStateOf("") } fun nextStep() { if (appPlatform.isAndroid || chatModel.currentUser.value != null) { - m.controller.appPrefs.onboardingStage.set(OnboardingStage.Step4_SetNotificationsMode) + m.controller.appPrefs.onboardingStage.set(OnboardingStage.Step3_ChooseServerOperators) } else { m.controller.appPrefs.onboardingStage.set(OnboardingStage.LinkAMobile) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt index e43404cb07..b133ae27d4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt @@ -164,14 +164,15 @@ fun OnboardingActionButton( @Composable fun TextButtonBelowOnboardingButton(text: String, onClick: (() -> Unit)?) { val state = getKeyboardState() + val enabled = onClick != null val topPadding by animateDpAsState(if (appPlatform.isAndroid && state.value == KeyboardState.Opened) 0.dp else DEFAULT_PADDING) val bottomPadding by animateDpAsState(if (appPlatform.isAndroid && state.value == KeyboardState.Opened) 0.dp else DEFAULT_PADDING * 2) if ((appPlatform.isAndroid && state.value == KeyboardState.Closed) || topPadding > 0.dp) { - TextButton({ onClick?.invoke() }, Modifier.padding(top = topPadding, bottom = bottomPadding).clip(CircleShape), enabled = onClick != null) { + TextButton({ onClick?.invoke() }, Modifier.padding(top = topPadding, bottom = bottomPadding).clip(CircleShape), enabled = enabled) { Text( text, Modifier.padding(start = DEFAULT_PADDING_HALF, end = DEFAULT_PADDING_HALF, bottom = 5.dp), - color = MaterialTheme.colors.primary, + color = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, fontWeight = FontWeight.Medium, textAlign = TextAlign.Center ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt index bdbef3b654..6cf945bcba 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt @@ -8,7 +8,6 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalUriHandler import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource @@ -17,17 +16,32 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.common.model.ChatModel +import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.setConditionsNotified +import chat.simplex.common.model.ServerOperator.Companion.dummyOperatorInfo import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.usersettings.networkAndServers.UsageConditionsView import chat.simplex.res.MR import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.StringResource @Composable -fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) { +fun ModalData.WhatsNewView(updatedConditions: Boolean = false, viaSettings: Boolean = false, close: () -> Unit) { val currentVersion = remember { mutableStateOf(versionDescriptions.lastIndex) } + val rhId = chatModel.remoteHostId() + + if (updatedConditions) { + LaunchedEffect(Unit) { + val conditionsId = chatModel.conditions.value.currentConditions.conditionsId + try { + setConditionsNotified(rh = rhId, conditionsId = conditionsId) + } catch (e: Exception) { + Log.d(TAG, "WhatsNewView setConditionsNotified error: ${e.message}") + } + } + } @Composable fun featureDescription(icon: ImageResource?, titleId: StringResource, descrId: StringResource?, link: String?, subfeatures: List>) { @@ -124,9 +138,18 @@ fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) { ) { AppBarTitle(String.format(generalGetString(MR.strings.new_in_version), v.version), withPadding = false, bottomPadding = DEFAULT_PADDING) + val modalManager = if (viaSettings) ModalManager.start else ModalManager.center + v.features.forEach { feature -> - if (feature.show) { - featureDescription(feature.icon, feature.titleId, feature.descrId, feature.link, feature.subfeatures) + when (feature) { + is VersionFeature.FeatureDescription -> { + if (feature.show) { + featureDescription(feature.icon, feature.titleId, feature.descrId, feature.link, feature.subfeatures) + } + } + is VersionFeature.FeatureView -> { + feature.view(modalManager) + } } } @@ -134,6 +157,18 @@ fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) { ReadMoreButton(v.post) } + if (updatedConditions) { + Text( + stringResource(MR.strings.view_updated_conditions), + color = MaterialTheme.colors.primary, + modifier = Modifier.clickable { + modalManager.showModalCloseable { + close -> UsageConditionsView(userServers = mutableStateOf(emptyList()), currUserServers = mutableStateOf(emptyList()), close = close, rhId = rhId) + } + } + ) + } + if (!viaSettings) { Spacer(Modifier.fillMaxHeight().weight(1f)) Box( @@ -141,7 +176,9 @@ fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) { ) { Text( generalGetString(MR.strings.ok), - modifier = Modifier.clickable(onClick = close), + modifier = Modifier.clickable(onClick = { + close() + }), style = MaterialTheme.typography.h3, color = MaterialTheme.colors.primary ) @@ -166,18 +203,26 @@ fun ReadMoreButton(url: String) { } } -private data class FeatureDescription( - val icon: ImageResource?, - val titleId: StringResource, - val descrId: StringResource?, - var subfeatures: List> = listOf(), - val link: String? = null, - val show: Boolean = true -) +private sealed class VersionFeature { + class FeatureDescription( + val icon: ImageResource?, + val titleId: StringResource, + val descrId: StringResource?, + var subfeatures: List> = listOf(), + val link: String? = null, + val show: Boolean = true + ): VersionFeature() + + class FeatureView( + val icon: ImageResource?, + val titleId: StringResource, + val view: @Composable (modalManager: ModalManager) -> Unit + ): VersionFeature() +} private data class VersionDescription( val version: String, - val features: List, + val features: List, val post: String? = null, ) @@ -186,18 +231,18 @@ private val versionDescriptions: List = listOf( version = "v4.2", post = "https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html", features = listOf( - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_verified_user, titleId = MR.strings.v4_2_security_assessment, descrId = MR.strings.v4_2_security_assessment_desc, link = "https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html" ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_group, titleId = MR.strings.v4_2_group_links, descrId = MR.strings.v4_2_group_links_desc ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_check, titleId = MR.strings.v4_2_auto_accept_contact_requests, descrId = MR.strings.v4_2_auto_accept_contact_requests_desc @@ -208,22 +253,22 @@ private val versionDescriptions: List = listOf( version = "v4.3", post = "https://simplex.chat/blog/20221206-simplex-chat-v4.3-voice-messages.html", features = listOf( - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_mic, titleId = MR.strings.v4_3_voice_messages, descrId = MR.strings.v4_3_voice_messages_desc ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_delete_forever, titleId = MR.strings.v4_3_irreversible_message_deletion, descrId = MR.strings.v4_3_irreversible_message_deletion_desc ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_wifi_tethering, titleId = MR.strings.v4_3_improved_server_configuration, descrId = MR.strings.v4_3_improved_server_configuration_desc ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_visibility_off, titleId = MR.strings.v4_3_improved_privacy_and_security, descrId = MR.strings.v4_3_improved_privacy_and_security_desc @@ -234,22 +279,22 @@ private val versionDescriptions: List = listOf( version = "v4.4", post = "https://simplex.chat/blog/20230103-simplex-chat-v4.4-disappearing-messages.html", features = listOf( - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_timer, titleId = MR.strings.v4_4_disappearing_messages, descrId = MR.strings.v4_4_disappearing_messages_desc ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_pending, titleId = MR.strings.v4_4_live_messages, descrId = MR.strings.v4_4_live_messages_desc ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_verified_user, titleId = MR.strings.v4_4_verify_connection_security, descrId = MR.strings.v4_4_verify_connection_security_desc ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_translate, titleId = MR.strings.v4_4_french_interface, descrId = MR.strings.v4_4_french_interface_descr @@ -260,33 +305,33 @@ private val versionDescriptions: List = listOf( version = "v4.5", post = "https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html", features = listOf( - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_manage_accounts, titleId = MR.strings.v4_5_multiple_chat_profiles, descrId = MR.strings.v4_5_multiple_chat_profiles_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_edit_note, titleId = MR.strings.v4_5_message_draft, descrId = MR.strings.v4_5_message_draft_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_safety_divider, titleId = MR.strings.v4_5_transport_isolation, descrId = MR.strings.v4_5_transport_isolation_descr, link = "https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation" ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_task, titleId = MR.strings.v4_5_private_filenames, descrId = MR.strings.v4_5_private_filenames_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_battery_2_bar, titleId = MR.strings.v4_5_reduced_battery_usage, descrId = MR.strings.v4_5_reduced_battery_usage_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_translate, titleId = MR.strings.v4_5_italian_interface, descrId = MR.strings.v4_5_italian_interface_descr, @@ -297,32 +342,32 @@ private val versionDescriptions: List = listOf( version = "v4.6", post = "https://simplex.chat/blog/20230328-simplex-chat-v4-6-hidden-profiles.html", features = listOf( - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_lock, titleId = MR.strings.v4_6_hidden_chat_profiles, descrId = MR.strings.v4_6_hidden_chat_profiles_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_flag, titleId = MR.strings.v4_6_group_moderation, descrId = MR.strings.v4_6_group_moderation_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_maps_ugc, titleId = MR.strings.v4_6_group_welcome_message, descrId = MR.strings.v4_6_group_welcome_message_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_call, titleId = MR.strings.v4_6_audio_video_calls, descrId = MR.strings.v4_6_audio_video_calls_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_battery_3_bar, titleId = MR.strings.v4_6_reduced_battery_usage, descrId = MR.strings.v4_6_reduced_battery_usage_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_translate, titleId = MR.strings.v4_6_chinese_spanish_interface, descrId = MR.strings.v4_6_chinese_spanish_interface_descr, @@ -333,17 +378,17 @@ private val versionDescriptions: List = listOf( version = "v5.0", post = "https://simplex.chat/blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.html", features = listOf( - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_upload_file, titleId = MR.strings.v5_0_large_files_support, descrId = MR.strings.v5_0_large_files_support_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_lock, titleId = MR.strings.v5_0_app_passcode, descrId = MR.strings.v5_0_app_passcode_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_translate, titleId = MR.strings.v5_0_polish_interface, descrId = MR.strings.v5_0_polish_interface_descr, @@ -354,27 +399,27 @@ private val versionDescriptions: List = listOf( version = "v5.1", post = "https://simplex.chat/blog/20230523-simplex-chat-v5-1-message-reactions-self-destruct-passcode.html", features = listOf( - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_add_reaction, titleId = MR.strings.v5_1_message_reactions, descrId = MR.strings.v5_1_message_reactions_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_chat, titleId = MR.strings.v5_1_better_messages, descrId = MR.strings.v5_1_better_messages_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_light_mode, titleId = MR.strings.v5_1_custom_themes, descrId = MR.strings.v5_1_custom_themes_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_lock, titleId = MR.strings.v5_1_self_destruct_passcode, descrId = MR.strings.v5_1_self_destruct_passcode_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_translate, titleId = MR.strings.v5_1_japanese_portuguese_interface, descrId = MR.strings.whats_new_thanks_to_users_contribute_weblate, @@ -385,27 +430,27 @@ private val versionDescriptions: List = listOf( version = "v5.2", post = "https://simplex.chat/blog/20230722-simplex-chat-v5-2-message-delivery-receipts.html", features = listOf( - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_check, titleId = MR.strings.v5_2_message_delivery_receipts, descrId = MR.strings.v5_2_message_delivery_receipts_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_star, titleId = MR.strings.v5_2_favourites_filter, descrId = MR.strings.v5_2_favourites_filter_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_sync_problem, titleId = MR.strings.v5_2_fix_encryption, descrId = MR.strings.v5_2_fix_encryption_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_timer, titleId = MR.strings.v5_2_disappear_one_message, descrId = MR.strings.v5_2_disappear_one_message_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_redeem, titleId = MR.strings.v5_2_more_things, descrId = MR.strings.v5_2_more_things_descr @@ -416,29 +461,29 @@ private val versionDescriptions: List = listOf( version = "v5.3", post = "https://simplex.chat/blog/20230925-simplex-chat-v5-3-desktop-app-local-file-encryption-directory-service.html", features = listOf( - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_desktop, titleId = MR.strings.v5_3_new_desktop_app, descrId = MR.strings.v5_3_new_desktop_app_descr, link = "https://simplex.chat/downloads/" ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_lock, titleId = MR.strings.v5_3_encrypt_local_files, descrId = MR.strings.v5_3_encrypt_local_files_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_search, titleId = MR.strings.v5_3_discover_join_groups, descrId = MR.strings.v5_3_discover_join_groups_descr, link = "simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion" ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_theater_comedy, titleId = MR.strings.v5_3_simpler_incognito_mode, descrId = MR.strings.v5_3_simpler_incognito_mode_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_translate, titleId = MR.strings.v5_3_new_interface_languages, descrId = MR.strings.v5_3_new_interface_languages_descr, @@ -449,27 +494,27 @@ private val versionDescriptions: List = listOf( version = "v5.4", post = "https://simplex.chat/blog/20231125-simplex-chat-v5-4-link-mobile-desktop-quantum-resistant-better-groups.html", features = listOf( - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_desktop, titleId = MR.strings.v5_4_link_mobile_desktop, descrId = MR.strings.v5_4_link_mobile_desktop_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_group, titleId = MR.strings.v5_4_better_groups, descrId = MR.strings.v5_4_better_groups_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_theater_comedy, titleId = MR.strings.v5_4_incognito_groups, descrId = MR.strings.v5_4_incognito_groups_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_back_hand, titleId = MR.strings.v5_4_block_group_members, descrId = MR.strings.v5_4_block_group_members_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_redeem, titleId = MR.strings.v5_2_more_things, descrId = MR.strings.v5_4_more_things_descr @@ -480,28 +525,28 @@ private val versionDescriptions: List = listOf( version = "v5.5", post = "https://simplex.chat/blog/20240124-simplex-chat-infrastructure-costs-v5-5-simplex-ux-private-notes-group-history.html", features = listOf( - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_folder_pen, titleId = MR.strings.v5_5_private_notes, descrId = MR.strings.v5_5_private_notes_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_link, titleId = MR.strings.v5_5_simpler_connect_ui, descrId = MR.strings.v5_5_simpler_connect_ui_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_forum, titleId = MR.strings.v5_5_join_group_conversation, descrId = MR.strings.v5_5_join_group_conversation_descr, link = "simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion" ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_battery_3_bar, titleId = MR.strings.v5_5_message_delivery, descrId = MR.strings.v5_5_message_delivery_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_translate, titleId = MR.strings.v5_5_new_interface_languages, descrId = MR.strings.whats_new_thanks_to_users_contribute_weblate, @@ -512,22 +557,22 @@ private val versionDescriptions: List = listOf( version = "v5.6", post = "https://simplex.chat/blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.html", features = listOf( - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_vpn_key_filled, titleId = MR.strings.v5_6_quantum_resistant_encryption, descrId = MR.strings.v5_6_quantum_resistant_encryption_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_ios_share, titleId = MR.strings.v5_6_app_data_migration, descrId = MR.strings.v5_6_app_data_migration_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_call, titleId = MR.strings.v5_6_picture_in_picture_calls, descrId = MR.strings.v5_6_picture_in_picture_calls_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_back_hand, titleId = MR.strings.v5_6_safer_groups, descrId = MR.strings.v5_6_safer_groups_descr @@ -538,32 +583,32 @@ private val versionDescriptions: List = listOf( version = "v5.7", post = "https://simplex.chat/blog/20240426-simplex-legally-binding-transparency-v5-7-better-user-experience.html", features = listOf( - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_vpn_key_filled, titleId = MR.strings.v5_6_quantum_resistant_encryption, descrId = MR.strings.v5_7_quantum_resistant_encryption_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_forward, titleId = MR.strings.v5_7_forward, descrId = MR.strings.v5_7_forward_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_music_note, titleId = MR.strings.v5_7_call_sounds, descrId = MR.strings.v5_7_call_sounds_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_account_box, titleId = MR.strings.v5_7_shape_profile_images, descrId = MR.strings.v5_7_shape_profile_images_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_wifi_tethering, titleId = MR.strings.v5_7_network, descrId = MR.strings.v5_7_network_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_translate, titleId = MR.strings.v5_7_new_interface_languages, descrId = MR.strings.whats_new_thanks_to_users_contribute_weblate, @@ -574,27 +619,27 @@ private val versionDescriptions: List = listOf( version = "v5.8", post = "https://simplex.chat/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.html", features = listOf( - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_settings_ethernet, titleId = MR.strings.v5_8_private_routing, descrId = MR.strings.v5_8_private_routing_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_palette, titleId = MR.strings.v5_8_chat_themes, descrId = MR.strings.v5_8_chat_themes_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_security, titleId = MR.strings.v5_8_safe_files, descrId = MR.strings.v5_8_safe_files_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_battery_3_bar, titleId = MR.strings.v5_8_message_delivery, descrId = MR.strings.v5_8_message_delivery_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_translate, titleId = MR.strings.v5_8_persian_ui, descrId = MR.strings.whats_new_thanks_to_users_contribute_weblate @@ -605,7 +650,7 @@ private val versionDescriptions: List = listOf( version = "v6.0", post = "https://simplex.chat/blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.html", features = listOf( - FeatureDescription( + VersionFeature.FeatureDescription( icon = null, titleId = MR.strings.v6_0_new_chat_experience, descrId = null, @@ -616,7 +661,7 @@ private val versionDescriptions: List = listOf( MR.images.ic_match_case to MR.strings.v6_0_increase_font_size ) ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = null, titleId = MR.strings.v6_0_new_media_options, descrId = null, @@ -625,23 +670,23 @@ private val versionDescriptions: List = listOf( MR.images.ic_blur_on to MR.strings.v6_0_privacy_blur, ) ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_toast, titleId = MR.strings.v6_0_reachable_chat_toolbar, descrId = MR.strings.v6_0_reachable_chat_toolbar_descr, show = appPlatform.isAndroid ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_settings_ethernet, titleId = MR.strings.v5_8_private_routing, descrId = MR.strings.v6_0_private_routing_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_wifi_tethering, titleId = MR.strings.v6_0_connection_servers_status, descrId = MR.strings.v6_0_connection_servers_status_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_upgrade, titleId = MR.strings.v6_0_upgrade_app, descrId = MR.strings.v6_0_upgrade_app_descr, @@ -653,18 +698,18 @@ private val versionDescriptions: List = listOf( version = "v6.1", post = "https://simplex.chat/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html", features = listOf( - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_verified_user, titleId = MR.strings.v6_1_better_security, descrId = MR.strings.v6_1_better_security_descr, link = "https://simplex.chat/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html" ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = MR.images.ic_videocam, titleId = MR.strings.v6_1_better_calls, descrId = MR.strings.v6_1_better_calls_descr ), - FeatureDescription( + VersionFeature.FeatureDescription( icon = null, titleId = MR.strings.v6_1_better_user_experience, descrId = null, @@ -678,6 +723,39 @@ private val versionDescriptions: List = listOf( ), ), ), + VersionDescription( + version = "v6.2 (beta.1)", + post = "https://simplex.chat/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html", + features = listOf( + VersionFeature.FeatureView( + icon = null, + titleId = MR.strings.v6_2_network_decentralization, + view = { modalManager -> + Column { + val src = (operatorsInfo[OperatorTag.Flux] ?: dummyOperatorInfo).largeLogo + Image(painterResource(src), null, modifier = Modifier.height(48.dp)) + Text(stringResource(MR.strings.v6_2_network_decentralization_descr), modifier = Modifier.padding(top = 8.dp)) + Row { + Text( + stringResource(MR.strings.v6_2_network_decentralization_enable_flux), + color = MaterialTheme.colors.primary, + modifier = Modifier.clickable { + modalManager.showModalCloseable { close -> ChooseServerOperators(onboarding = false, close, modalManager) } + } + ) + Text(" ") + Text(stringResource(MR.strings.v6_2_network_decentralization_enable_flux_reason)) + } + } + } + ), + VersionFeature.FeatureDescription( + icon = MR.images.ic_chat, + titleId = MR.strings.v6_2_improved_chat_navigation, + descrId = MR.strings.v6_2_improved_chat_navigation_descr + ), + ), + ) ) private val lastVersion = versionDescriptions.last().version @@ -700,7 +778,8 @@ fun shouldShowWhatsNew(m: ChatModel): Boolean { @Composable fun PreviewWhatsNewView() { SimpleXTheme { - WhatsNewView( + val data = remember { ModalData() } + data.WhatsNewView( viaSettings = true, close = {} ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt index e727b94781..1d01ab11ff 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt @@ -35,6 +35,7 @@ import chat.simplex.common.views.chatlist.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.newchat.QRCode import chat.simplex.common.views.usersettings.* +import chat.simplex.common.views.usersettings.networkAndServers.validPort import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt deleted file mode 100644 index f5e3cda2c7..0000000000 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt +++ /dev/null @@ -1,383 +0,0 @@ -package chat.simplex.common.views.usersettings - -import SectionBottomSpacer -import SectionDividerSpaced -import SectionItemView -import SectionTextFooter -import SectionView -import androidx.compose.foundation.* -import androidx.compose.foundation.layout.* -import androidx.compose.material.* -import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalUriHandler -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.text.* -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import chat.simplex.common.model.ServerAddress.Companion.parseServerAddress -import chat.simplex.common.views.helpers.* -import chat.simplex.common.model.* -import chat.simplex.common.platform.ColumnWithScrollBar -import chat.simplex.common.platform.appPlatform -import chat.simplex.res.MR - -@Composable -fun ModalData.ProtocolServersView(m: ChatModel, rhId: Long?, serverProtocol: ServerProtocol, close: () -> Unit) { - var presetServers by remember(rhId) { mutableStateOf(emptyList()) } - var servers by remember { stateGetOrPut("servers") { emptyList() } } - var serversAlreadyLoaded by remember { stateGetOrPut("serversAlreadyLoaded") { false } } - val currServers = remember(rhId) { mutableStateOf(servers) } - val testing = rememberSaveable(rhId) { mutableStateOf(false) } - val serversUnchanged = remember(servers) { derivedStateOf { servers == currServers.value || testing.value } } - val allServersDisabled = remember { derivedStateOf { servers.none { it.enabled } } } - val saveDisabled = remember(servers) { - derivedStateOf { - servers.isEmpty() || - servers == currServers.value || - testing.value || - servers.none { srv -> - val address = parseServerAddress(srv.server) - address != null && uniqueAddress(srv, address, servers) - } || - allServersDisabled.value - } - } - - KeyChangeEffect(rhId) { - servers = emptyList() - serversAlreadyLoaded = false - } - - LaunchedEffect(rhId) { - withApi { - val res = m.controller.getUserProtoServers(rhId, serverProtocol) - if (res != null) { - currServers.value = res.protoServers - presetServers = res.presetServers - if (servers.isEmpty() && !serversAlreadyLoaded) { - servers = currServers.value - serversAlreadyLoaded = true - } - } - } - } - val testServersJob = CancellableOnGoneJob() - fun showServer(server: ServerCfg) { - ModalManager.start.showModalCloseable(true) { close -> - var old by remember { mutableStateOf(server) } - val index = servers.indexOf(old) - ProtocolServerView( - m, - old, - serverProtocol, - onUpdate = { updated -> - val newServers = ArrayList(servers) - newServers.removeAt(index) - newServers.add(index, updated) - old = updated - servers = newServers - }, - onDelete = { - val newServers = ArrayList(servers) - newServers.removeAt(index) - servers = newServers - close() - }) - } - } - ModalView( - close = { - if (saveDisabled.value) close() - else showUnsavedChangesAlert({ saveServers(rhId, serverProtocol, currServers, servers, m, close) }, close) - }, - ) { - ProtocolServersLayout( - serverProtocol, - testing = testing.value, - servers = servers, - serversUnchanged = serversUnchanged.value, - saveDisabled = saveDisabled.value, - allServersDisabled = allServersDisabled.value, - m.currentUser.value, - addServer = { - AlertManager.shared.showAlertDialogButtonsColumn( - title = generalGetString(MR.strings.smp_servers_add), - buttons = { - Column { - SectionItemView({ - AlertManager.shared.hideAlert() - servers = servers + ServerCfg.empty - // No saving until something will be changed on the next screen to prevent blank servers on the list - showServer(servers.last()) - }) { - Text(stringResource(MR.strings.smp_servers_enter_manually), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) - } - if (appPlatform.isAndroid) { - SectionItemView({ - AlertManager.shared.hideAlert() - ModalManager.start.showModalCloseable { close -> - ScanProtocolServer(rhId) { - close() - servers = servers + it - } - } - } - ) { - Text(stringResource(MR.strings.smp_servers_scan_qr), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) - } - } - val hasAllPresets = hasAllPresets(presetServers, servers, m) - if (!hasAllPresets) { - SectionItemView({ - AlertManager.shared.hideAlert() - servers = (servers + addAllPresets(rhId, presetServers, servers, m)).sortedByDescending { it.preset } - }) { - Text(stringResource(MR.strings.smp_servers_preset_add), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) - } - } - } - } - ) - }, - testServers = { - testServersJob.value = withLongRunningApi { - testServers(testing, servers, m) { - servers = it - } - } - }, - resetServers = { - servers = currServers.value - }, - saveSMPServers = { - saveServers(rhId, serverProtocol, currServers, servers, m) - }, - showServer = ::showServer, - ) - - if (testing.value) { - Box( - Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator( - Modifier - .padding(horizontal = 2.dp) - .size(30.dp), - color = MaterialTheme.colors.secondary, - strokeWidth = 2.5.dp - ) - } - } - } -} - -@Composable -private fun ProtocolServersLayout( - serverProtocol: ServerProtocol, - testing: Boolean, - servers: List, - serversUnchanged: Boolean, - saveDisabled: Boolean, - allServersDisabled: Boolean, - currentUser: User?, - addServer: () -> Unit, - testServers: () -> Unit, - resetServers: () -> Unit, - saveSMPServers: () -> Unit, - showServer: (ServerCfg) -> Unit, -) { - ColumnWithScrollBar { - AppBarTitle(stringResource(if (serverProtocol == ServerProtocol.SMP) MR.strings.your_SMP_servers else MR.strings.your_XFTP_servers)) - - val configuredServers = servers.filter { it.preset || it.enabled } - val otherServers = servers.filter { !(it.preset || it.enabled) } - - if (configuredServers.isNotEmpty()) { - SectionView(stringResource(if (serverProtocol == ServerProtocol.SMP) MR.strings.smp_servers_configured else MR.strings.xftp_servers_configured).uppercase()) { - for (srv in configuredServers) { - SectionItemView({ showServer(srv) }, disabled = testing) { - ProtocolServerView(serverProtocol, srv, servers, testing) - } - } - } - SectionTextFooter( - remember(currentUser?.displayName) { - buildAnnotatedString { - append(generalGetString(MR.strings.smp_servers_per_user) + " ") - withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { - append(currentUser?.displayName ?: "") - } - append(".") - } - } - ) - SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false) - } - - if (otherServers.isNotEmpty()) { - SectionView(stringResource(if (serverProtocol == ServerProtocol.SMP) MR.strings.smp_servers_other else MR.strings.xftp_servers_other).uppercase()) { - for (srv in otherServers.filter { !(it.preset || it.enabled) }) { - SectionItemView({ showServer(srv) }, disabled = testing) { - ProtocolServerView(serverProtocol, srv, servers, testing) - } - } - } - } - - SectionView { - SettingsActionItem( - painterResource(MR.images.ic_add), - stringResource(MR.strings.smp_servers_add), - addServer, - disabled = testing, - textColor = if (testing) MaterialTheme.colors.secondary else MaterialTheme.colors.primary, - iconColor = if (testing) MaterialTheme.colors.secondary else MaterialTheme.colors.primary - ) - SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = false) - } - - SectionView { - SectionItemView(resetServers, disabled = serversUnchanged) { - Text(stringResource(MR.strings.reset_verb), color = if (!serversUnchanged) MaterialTheme.colors.onBackground else MaterialTheme.colors.secondary) - } - val testServersDisabled = testing || allServersDisabled - SectionItemView(testServers, disabled = testServersDisabled) { - Text(stringResource(MR.strings.smp_servers_test_servers), color = if (!testServersDisabled) MaterialTheme.colors.onBackground else MaterialTheme.colors.secondary) - } - SectionItemView(saveSMPServers, disabled = saveDisabled) { - Text(stringResource(MR.strings.smp_servers_save), color = if (!saveDisabled) MaterialTheme.colors.onBackground else MaterialTheme.colors.secondary) - } - } - SectionDividerSpaced(maxBottomPadding = false) - SectionView { - HowToButton() - } - SectionBottomSpacer() - } -} - -@Composable -private fun ProtocolServerView(serverProtocol: ServerProtocol, srv: ServerCfg, servers: List, disabled: Boolean) { - val address = parseServerAddress(srv.server) - when { - address == null || !address.valid || address.serverProtocol != serverProtocol || !uniqueAddress(srv, address, servers) -> InvalidServer() - !srv.enabled -> Icon(painterResource(MR.images.ic_do_not_disturb_on), null, tint = MaterialTheme.colors.secondary) - else -> ShowTestStatus(srv) - } - Spacer(Modifier.padding(horizontal = 4.dp)) - val text = address?.hostnames?.firstOrNull() ?: srv.server - if (srv.enabled) { - Text(text, color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.onBackground, maxLines = 1) - } else { - Text(text, maxLines = 1, color = MaterialTheme.colors.secondary) - } -} - -@Composable -private fun HowToButton() { - val uriHandler = LocalUriHandler.current - SettingsActionItem( - painterResource(MR.images.ic_open_in_new), - stringResource(MR.strings.how_to_use_your_servers), - { uriHandler.openUriCatching("https://simplex.chat/docs/server.html") }, - textColor = MaterialTheme.colors.primary, - iconColor = MaterialTheme.colors.primary - ) -} - -@Composable -fun InvalidServer() { - Icon(painterResource(MR.images.ic_error), null, tint = MaterialTheme.colors.error) -} - -private fun uniqueAddress(s: ServerCfg, address: ServerAddress, servers: List): Boolean = servers.all { srv -> - address.hostnames.all { host -> - srv.id == s.id || !srv.server.contains(host) - } -} - -private fun hasAllPresets(presetServers: List, servers: List, m: ChatModel): Boolean = - presetServers.all { hasPreset(it, servers) } ?: true - -private fun addAllPresets(rhId: Long?, presetServers: List, servers: List, m: ChatModel): List { - val toAdd = ArrayList() - for (srv in presetServers) { - if (!hasPreset(srv, servers)) { - toAdd.add(srv) - } - } - return toAdd -} - -private fun hasPreset(srv: ServerCfg, servers: List): Boolean = - servers.any { it.server == srv.server } - -private suspend fun testServers(testing: MutableState, servers: List, m: ChatModel, onUpdated: (List) -> Unit) { - val resetStatus = resetTestStatus(servers) - onUpdated(resetStatus) - testing.value = true - val fs = runServersTest(resetStatus, m) { onUpdated(it) } - testing.value = false - if (fs.isNotEmpty()) { - val msg = fs.map { it.key + ": " + it.value.localizedDescription }.joinToString("\n") - AlertManager.shared.showAlertMsg( - title = generalGetString(MR.strings.smp_servers_test_failed), - text = generalGetString(MR.strings.smp_servers_test_some_failed) + "\n" + msg - ) - } -} - -private fun resetTestStatus(servers: List): List { - val copy = ArrayList(servers) - for ((index, server) in servers.withIndex()) { - if (server.enabled) { - copy.removeAt(index) - copy.add(index, server.copy(tested = null)) - } - } - return copy -} - -private suspend fun runServersTest(servers: List, m: ChatModel, onUpdated: (List) -> Unit): Map { - val fs: MutableMap = mutableMapOf() - val updatedServers = ArrayList(servers) - for ((index, server) in servers.withIndex()) { - if (server.enabled) { - interruptIfCancelled() - val (updatedServer, f) = testServerConnection(server, m) - updatedServers.removeAt(index) - updatedServers.add(index, updatedServer) - // toList() is important. Otherwise, Compose will not redraw the screen after first update - onUpdated(updatedServers.toList()) - if (f != null) { - fs[serverHostname(updatedServer.server)] = f - } - } - } - return fs -} - -private fun saveServers(rhId: Long?, protocol: ServerProtocol, currServers: MutableState>, servers: List, m: ChatModel, afterSave: () -> Unit = {}) { - withBGApi { - if (m.controller.setUserProtoServers(rhId, protocol, servers)) { - currServers.value = servers - } - afterSave() - } -} - -private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) { - AlertManager.shared.showAlertDialogStacked( - title = generalGetString(MR.strings.smp_save_servers_question), - confirmText = generalGetString(MR.strings.save_verb), - dismissText = generalGetString(MR.strings.exit_without_saving), - onConfirm = save, - onDismiss = revert, - ) -} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt index 78c5e3b212..f3d22e0cdf 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt @@ -25,14 +25,13 @@ import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* -import chat.simplex.common.views.CreateProfile import chat.simplex.common.views.database.DatabaseView import chat.simplex.common.views.helpers.* import chat.simplex.common.views.migration.MigrateFromDeviceView import chat.simplex.common.views.onboarding.SimpleXInfo import chat.simplex.common.views.onboarding.WhatsNewView +import chat.simplex.common.views.usersettings.networkAndServers.NetworkAndServersView import chat.simplex.res.MR -import kotlinx.coroutines.* @Composable fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, close: () -> Unit) { @@ -102,7 +101,7 @@ fun SettingsLayout( SectionView(stringResource(MR.strings.settings_section_title_settings)) { SettingsActionItem(painterResource(if (notificationsMode.value == NotificationsMode.OFF) MR.images.ic_bolt_off else MR.images.ic_bolt), stringResource(MR.strings.notifications), showSettingsModal { NotificationsSettingsView(it) }, disabled = stopped) - SettingsActionItem(painterResource(MR.images.ic_wifi_tethering), stringResource(MR.strings.network_and_servers), showSettingsModal { NetworkAndServersView() }, disabled = stopped) + SettingsActionItem(painterResource(MR.images.ic_wifi_tethering), stringResource(MR.strings.network_and_servers), showCustomModal { _, close -> NetworkAndServersView(close) }, disabled = stopped) SettingsActionItem(painterResource(MR.images.ic_videocam), stringResource(MR.strings.settings_audio_video_calls), showSettingsModal { CallSettingsView(it, showModal) }, disabled = stopped) SettingsActionItem(painterResource(MR.images.ic_lock), stringResource(MR.strings.privacy_and_security), showSettingsModal { PrivacySettingsView(it, showSettingsModal, setPerformLA) }, disabled = stopped) SettingsActionItem(painterResource(MR.images.ic_light_mode), stringResource(MR.strings.appearance_settings), showSettingsModal { AppearanceView(it) }) @@ -118,7 +117,7 @@ fun SettingsLayout( SectionView(stringResource(MR.strings.settings_section_title_help)) { SettingsActionItem(painterResource(MR.images.ic_help), stringResource(MR.strings.how_to_use_simplex_chat), showModal { HelpView(userDisplayName ?: "") }, disabled = stopped) - SettingsActionItem(painterResource(MR.images.ic_add), stringResource(MR.strings.whats_new), showCustomModal { _, close -> WhatsNewView(viaSettings = true, close) }, disabled = stopped) + SettingsActionItem(painterResource(MR.images.ic_add), stringResource(MR.strings.whats_new), showCustomModal { _, close -> WhatsNewView(viaSettings = true, close = close) }, disabled = stopped) SettingsActionItem(painterResource(MR.images.ic_info), stringResource(MR.strings.about_simplex_chat), showModal { SimpleXInfo(it, onboarding = false) }) if (!chatModel.desktopNoUserNoRemote) { SettingsActionItem(painterResource(MR.images.ic_tag), stringResource(MR.strings.chat_with_the_founder), { uriHandler.openVerifiedSimplexUri(simplexTeamUri) }, textColor = MaterialTheme.colors.primary, disabled = stopped) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/AdvancedNetworkSettings.kt similarity index 99% rename from apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/AdvancedNetworkSettings.kt index 5757b5d1f4..838cac0172 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/AdvancedNetworkSettings.kt @@ -1,4 +1,4 @@ -package chat.simplex.common.views.usersettings +package chat.simplex.common.views.usersettings.networkAndServers import SectionBottomSpacer import SectionDividerSpaced @@ -26,6 +26,7 @@ import chat.simplex.common.views.helpers.* import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.common.platform.chatModel +import chat.simplex.common.views.usersettings.SettingsPreferenceItem import chat.simplex.res.MR import java.text.DecimalFormat diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt similarity index 52% rename from apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt index 2c4870b121..ef5b82a5d9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt @@ -1,6 +1,7 @@ -package chat.simplex.common.views.usersettings +package chat.simplex.common.views.usersettings.networkAndServers import SectionBottomSpacer +import SectionCustomFooter import SectionDividerSpaced import SectionItemView import SectionItemWithValue @@ -20,119 +21,245 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.* import androidx.compose.ui.text.input.* import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.ui.graphics.Color +import androidx.compose.foundation.Image +import androidx.compose.ui.graphics.* import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.* import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs +import chat.simplex.common.model.ChatController.getServerOperators +import chat.simplex.common.model.ChatController.getUserServers +import chat.simplex.common.model.ChatController.setUserServers import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.onboarding.OnboardingActionButton +import chat.simplex.common.views.onboarding.ReadableText +import chat.simplex.common.views.usersettings.* import chat.simplex.res.MR +import kotlinx.coroutines.launch @Composable -fun NetworkAndServersView() { +fun ModalData.NetworkAndServersView(close: () -> Unit) { val currentRemoteHost by remember { chatModel.currentRemoteHost } // It's not a state, just a one-time value. Shouldn't be used in any state-related situations val netCfg = remember { chatModel.controller.getNetCfg() } val networkUseSocksProxy: MutableState = remember { mutableStateOf(netCfg.useSocksProxy) } + val currUserServers = remember { stateGetOrPut("currUserServers") { emptyList() } } + val userServers = remember { stateGetOrPut("userServers") { emptyList() } } + val serverErrors = remember { stateGetOrPut("serverErrors") { emptyList() } } + val scope = rememberCoroutineScope() val proxyPort = remember { derivedStateOf { appPrefs.networkProxy.state.value.port } } - NetworkAndServersLayout( - currentRemoteHost = currentRemoteHost, - networkUseSocksProxy = networkUseSocksProxy, - onionHosts = remember { mutableStateOf(netCfg.onionHosts) }, - toggleSocksProxy = { enable -> - val def = NetCfg.defaults - val proxyDef = NetCfg.proxyDefaults - if (enable) { - AlertManager.shared.showAlertDialog( - title = generalGetString(MR.strings.network_enable_socks), - text = generalGetString(MR.strings.network_enable_socks_info).format(proxyPort.value), - confirmText = generalGetString(MR.strings.confirm_verb), - onConfirm = { - withBGApi { - var conf = controller.getNetCfg().withProxy(controller.appPrefs.networkProxy.get()) - if (conf.tcpConnectTimeout == def.tcpConnectTimeout) { - conf = conf.copy(tcpConnectTimeout = proxyDef.tcpConnectTimeout) - } - if (conf.tcpTimeout == def.tcpTimeout) { - conf = conf.copy(tcpTimeout = proxyDef.tcpTimeout) - } - if (conf.tcpTimeoutPerKb == def.tcpTimeoutPerKb) { - conf = conf.copy(tcpTimeoutPerKb = proxyDef.tcpTimeoutPerKb) - } - if (conf.rcvConcurrency == def.rcvConcurrency) { - conf = conf.copy(rcvConcurrency = proxyDef.rcvConcurrency) - } - chatModel.controller.apiSetNetworkConfig(conf) - chatModel.controller.setNetCfg(conf) - networkUseSocksProxy.value = true - } - } - ) + ModalView( + close = { + if (!serversCanBeSaved(currUserServers.value, userServers.value, serverErrors.value)) { + close() } else { - AlertManager.shared.showAlertDialog( - title = generalGetString(MR.strings.network_disable_socks), - text = generalGetString(MR.strings.network_disable_socks_info), - confirmText = generalGetString(MR.strings.confirm_verb), - onConfirm = { - withBGApi { - var conf = controller.getNetCfg().copy(socksProxy = null) - if (conf.tcpConnectTimeout == proxyDef.tcpConnectTimeout) { - conf = conf.copy(tcpConnectTimeout = def.tcpConnectTimeout) - } - if (conf.tcpTimeout == proxyDef.tcpTimeout) { - conf = conf.copy(tcpTimeout = def.tcpTimeout) - } - if (conf.tcpTimeoutPerKb == proxyDef.tcpTimeoutPerKb) { - conf = conf.copy(tcpTimeoutPerKb = def.tcpTimeoutPerKb) - } - if (conf.rcvConcurrency == proxyDef.rcvConcurrency) { - conf = conf.copy(rcvConcurrency = def.rcvConcurrency) - } - chatModel.controller.apiSetNetworkConfig(conf) - chatModel.controller.setNetCfg(conf) - networkUseSocksProxy.value = false - } - } + showUnsavedChangesAlert( + { scope.launch { saveServers(currentRemoteHost?.remoteHostId, currUserServers, userServers) }}, + close ) } } - ) + ) { + NetworkAndServersLayout( + currentRemoteHost = currentRemoteHost, + networkUseSocksProxy = networkUseSocksProxy, + onionHosts = remember { mutableStateOf(netCfg.onionHosts) }, + currUserServers = currUserServers, + userServers = userServers, + serverErrors = serverErrors, + toggleSocksProxy = { enable -> + val def = NetCfg.defaults + val proxyDef = NetCfg.proxyDefaults + if (enable) { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.network_enable_socks), + text = generalGetString(MR.strings.network_enable_socks_info).format(proxyPort.value), + confirmText = generalGetString(MR.strings.confirm_verb), + onConfirm = { + withBGApi { + var conf = controller.getNetCfg().withProxy(controller.appPrefs.networkProxy.get()) + if (conf.tcpConnectTimeout == def.tcpConnectTimeout) { + conf = conf.copy(tcpConnectTimeout = proxyDef.tcpConnectTimeout) + } + if (conf.tcpTimeout == def.tcpTimeout) { + conf = conf.copy(tcpTimeout = proxyDef.tcpTimeout) + } + if (conf.tcpTimeoutPerKb == def.tcpTimeoutPerKb) { + conf = conf.copy(tcpTimeoutPerKb = proxyDef.tcpTimeoutPerKb) + } + if (conf.rcvConcurrency == def.rcvConcurrency) { + conf = conf.copy(rcvConcurrency = proxyDef.rcvConcurrency) + } + chatModel.controller.apiSetNetworkConfig(conf) + chatModel.controller.setNetCfg(conf) + networkUseSocksProxy.value = true + } + } + ) + } else { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.network_disable_socks), + text = generalGetString(MR.strings.network_disable_socks_info), + confirmText = generalGetString(MR.strings.confirm_verb), + onConfirm = { + withBGApi { + var conf = controller.getNetCfg().copy(socksProxy = null) + if (conf.tcpConnectTimeout == proxyDef.tcpConnectTimeout) { + conf = conf.copy(tcpConnectTimeout = def.tcpConnectTimeout) + } + if (conf.tcpTimeout == proxyDef.tcpTimeout) { + conf = conf.copy(tcpTimeout = def.tcpTimeout) + } + if (conf.tcpTimeoutPerKb == proxyDef.tcpTimeoutPerKb) { + conf = conf.copy(tcpTimeoutPerKb = def.tcpTimeoutPerKb) + } + if (conf.rcvConcurrency == proxyDef.rcvConcurrency) { + conf = conf.copy(rcvConcurrency = def.rcvConcurrency) + } + chatModel.controller.apiSetNetworkConfig(conf) + chatModel.controller.setNetCfg(conf) + networkUseSocksProxy.value = false + } + } + ) + } + } + ) + } } @Composable fun NetworkAndServersLayout( currentRemoteHost: RemoteHostInfo?, networkUseSocksProxy: MutableState, onionHosts: MutableState, + currUserServers: MutableState>, + serverErrors: MutableState>, + userServers: MutableState>, toggleSocksProxy: (Boolean) -> Unit, ) { val m = chatModel + val conditionsAction = remember { m.conditions.value.conditionsAction } + val anyOperatorEnabled = remember { derivedStateOf { userServers.value.any { it.operator?.enabled == true } } } + val scope = rememberCoroutineScope() + + LaunchedEffect(Unit) { + if (currUserServers.value.isNotEmpty() || userServers.value.isNotEmpty()) { + return@LaunchedEffect + } + try { + val servers = getUserServers(rh = currentRemoteHost?.remoteHostId) + if (servers != null) { + currUserServers.value = servers + userServers.value = servers + } + } catch (e: Exception) { + Log.e(TAG, e.stackTraceToString()) + } + } + + @Composable + fun ConditionsButton(conditionsAction: UsageConditionsAction, rhId: Long?) { + SectionItemView( + click = { ModalManager.start.showModalCloseable(endButtons = { ConditionsLinkButton() }) { close -> UsageConditionsView(currUserServers, userServers, close, rhId) } }, + ) { + Text( + stringResource(if (conditionsAction is UsageConditionsAction.Review) MR.strings.operator_review_conditions else MR.strings.operator_conditions_accepted), + color = MaterialTheme.colors.primary + ) + } + } + ColumnWithScrollBar { - val showModal = { it: @Composable ModalData.() -> Unit -> ModalManager.start.showModal(content = it) } - val showCustomModal = { it: @Composable (close: () -> Unit) -> Unit -> ModalManager.start.showCustomModal { close -> it(close) }} + val showModal = { it: @Composable ModalData.() -> Unit -> ModalManager.start.showModal(content = it) } + val showCustomModal = { it: @Composable (close: () -> Unit) -> Unit -> ModalManager.start.showCustomModal { close -> it(close) } } AppBarTitle(stringResource(MR.strings.network_and_servers)) + // TODO: Review this and socks. if (!chatModel.desktopNoUserNoRemote) { - SectionView(generalGetString(MR.strings.settings_section_title_messages)) { - SettingsActionItem(painterResource(MR.images.ic_dns), stringResource(MR.strings.message_servers), { ModalManager.start.showCustomModal { close -> ProtocolServersView(m, m.remoteHostId, ServerProtocol.SMP, close) } }) + SectionView(generalGetString(MR.strings.network_preset_servers_title).uppercase()) { + userServers.value.forEachIndexed { index, srv -> + srv.operator?.let { ServerOperatorRow(index, it, currUserServers, userServers, serverErrors, currentRemoteHost?.remoteHostId) } + } + } + if (conditionsAction != null && anyOperatorEnabled.value) { + ConditionsButton(conditionsAction, rhId = currentRemoteHost?.remoteHostId) + } + val footerText = if (conditionsAction is UsageConditionsAction.Review && conditionsAction.deadline != null && anyOperatorEnabled.value) { + String.format(generalGetString(MR.strings.operator_conditions_will_be_accepted_on), localDate(conditionsAction.deadline)) + } else null - SettingsActionItem(painterResource(MR.images.ic_dns), stringResource(MR.strings.media_and_file_servers), { ModalManager.start.showCustomModal { close -> ProtocolServersView(m, m.remoteHostId, ServerProtocol.XFTP, close) } }) + if (footerText != null) { + SectionTextFooter(footerText) + } + SectionDividerSpaced() + } - if (currentRemoteHost == null) { - UseSocksProxySwitch(networkUseSocksProxy, toggleSocksProxy) - SettingsActionItem(painterResource(MR.images.ic_settings_ethernet), stringResource(MR.strings.network_socks_proxy_settings), { showCustomModal { SocksProxySettings(networkUseSocksProxy.value, appPrefs.networkProxy, onionHosts, sessionMode = appPrefs.networkSessionMode.get(), false, it) }}) - SettingsActionItem(painterResource(MR.images.ic_cable), stringResource(MR.strings.network_settings), { ModalManager.start.showCustomModal { AdvancedNetworkSettingsView(showModal, it) } }) - if (networkUseSocksProxy.value) { - SectionTextFooter(annotatedStringResource(MR.strings.socks_proxy_setting_limitations)) - SectionDividerSpaced(maxTopPadding = true) - } else { - SectionDividerSpaced() + SectionView(generalGetString(MR.strings.settings_section_title_messages)) { + val nullOperatorIndex = userServers.value.indexOfFirst { it.operator == null } + + if (nullOperatorIndex != -1) { + SectionItemView({ + ModalManager.start.showModal { + YourServersView( + userServers = userServers, + serverErrors = serverErrors, + operatorIndex = nullOperatorIndex, + rhId = currentRemoteHost?.remoteHostId + ) + } + }) { + Icon( + painterResource(MR.images.ic_dns), + stringResource(MR.strings.your_servers), + tint = MaterialTheme.colors.secondary + ) + TextIconSpaced() + Text(stringResource(MR.strings.your_servers), color = MaterialTheme.colors.onBackground) + + if (currUserServers.value.getOrNull(nullOperatorIndex) != userServers.value.getOrNull(nullOperatorIndex)) { + Spacer(Modifier.weight(1f)) + UnsavedChangesIndicator() } } } + + if (currentRemoteHost == null) { + UseSocksProxySwitch(networkUseSocksProxy, toggleSocksProxy) + SettingsActionItem(painterResource(MR.images.ic_settings_ethernet), stringResource(MR.strings.network_socks_proxy_settings), { showCustomModal { SocksProxySettings(networkUseSocksProxy.value, appPrefs.networkProxy, onionHosts, sessionMode = appPrefs.networkSessionMode.get(), false, it) } }) + SettingsActionItem(painterResource(MR.images.ic_cable), stringResource(MR.strings.network_settings), { ModalManager.start.showCustomModal { AdvancedNetworkSettingsView(showModal, it) } }) + if (networkUseSocksProxy.value) { + SectionTextFooter(annotatedStringResource(MR.strings.socks_proxy_setting_limitations)) + SectionDividerSpaced(maxTopPadding = true) + } else { + SectionDividerSpaced(maxBottomPadding = false) + } + } } + val saveDisabled = !serversCanBeSaved(currUserServers.value, userServers.value, serverErrors.value) + + SectionItemView( + { scope.launch { saveServers(rhId = currentRemoteHost?.remoteHostId, currUserServers, userServers) } }, + disabled = saveDisabled, + ) { + Text(stringResource(MR.strings.smp_servers_save), color = if (!saveDisabled) MaterialTheme.colors.onBackground else MaterialTheme.colors.secondary) + } + val serversErr = globalServersError(serverErrors.value) + if (serversErr != null) { + SectionCustomFooter { + ServersErrorFooter(serversErr) + } + } else if (serverErrors.value.isNotEmpty()) { + SectionCustomFooter { + ServersErrorFooter(generalGetString(MR.strings.errors_in_servers_configuration)) + } + } + + SectionDividerSpaced() SectionView(generalGetString(MR.strings.settings_section_title_calls)) { SettingsActionItem(painterResource(MR.images.ic_electrical_services), stringResource(MR.strings.webrtc_ice_servers), { ModalManager.start.showModal { RTCServersView(m) } }) @@ -504,6 +631,165 @@ fun showWrongProxyConfigAlert() { ) } +@Composable() +private fun ServerOperatorRow( + index: Int, + operator: ServerOperator, + currUserServers: MutableState>, + userServers: MutableState>, + serverErrors: MutableState>, + rhId: Long? +) { + SectionItemView( + { + ModalManager.start.showModalCloseable { close -> + OperatorView( + currUserServers, + userServers, + serverErrors, + index, + rhId + ) + } + } + ) { + Image( + painterResource(operator.logo), + operator.tradeName, + modifier = Modifier.size(24.dp), + colorFilter = if (operator.enabled) null else ColorFilter.colorMatrix(ColorMatrix().apply { + setToSaturation(0f) + }) + ) + TextIconSpaced() + Text(operator.tradeName, color = if (operator.enabled) MaterialTheme.colors.onBackground else MaterialTheme.colors.secondary) + + if (currUserServers.value.getOrNull(index) != userServers.value.getOrNull(index)) { + Spacer(Modifier.weight(1f)) + UnsavedChangesIndicator() + } + } +} + +@Composable +private fun UnsavedChangesIndicator() { + Icon( + painterResource(MR.images.ic_edit_filled), + stringResource(MR.strings.icon_descr_edited), + tint = MaterialTheme.colors.secondary, + modifier = Modifier.size(16.dp) + ) +} + +@Composable +fun UsageConditionsView( + currUserServers: MutableState>, + userServers: MutableState>, + close: () -> Unit, + rhId: Long? +) { + suspend fun acceptForOperators(rhId: Long?, operatorIds: List, close: () -> Unit) { + try { + val conditionsId = chatModel.conditions.value.currentConditions.conditionsId + val r = chatController.acceptConditions(rhId, conditionsId, operatorIds) ?: return + chatModel.conditions.value = r + updateOperatorsConditionsAcceptance(currUserServers, r.serverOperators) + updateOperatorsConditionsAcceptance(userServers, r.serverOperators) + close() + } catch (ex: Exception) { + Log.e(TAG, ex.stackTraceToString()) + } + } + + @Composable + fun AcceptConditionsButton(operatorIds: List, close: () -> Unit, bottomPadding: Dp = DEFAULT_PADDING * 2) { + val scope = rememberCoroutineScope() + Column(Modifier.fillMaxWidth().padding(bottom = bottomPadding), horizontalAlignment = Alignment.CenterHorizontally) { + OnboardingActionButton( + labelId = MR.strings.accept_conditions, + onboarding = null, + enabled = operatorIds.isNotEmpty(), + onclick = { + scope.launch { + acceptForOperators(rhId, operatorIds, close) + } + } + ) + } + } + + ColumnWithScrollBar(modifier = Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING)) { + AppBarTitle(stringResource(MR.strings.operator_conditions_of_use), enableAlphaChanges = false, withPadding = false) + when (val conditionsAction = chatModel.conditions.value.conditionsAction) { + is UsageConditionsAction.Review -> { + if (conditionsAction.operators.isNotEmpty()) { + ReadableText(MR.strings.operators_conditions_will_be_accepted_for, args = conditionsAction.operators.joinToString(", ") { it.legalName_ }) + } + Column(modifier = Modifier.weight(1f).padding(bottom = DEFAULT_PADDING, top = DEFAULT_PADDING_HALF)) { + ConditionsTextView(rhId) + } + AcceptConditionsButton(conditionsAction.operators.map { it.operatorId }, close, if (conditionsAction.deadline != null) DEFAULT_PADDING_HALF else DEFAULT_PADDING * 2) + if (conditionsAction.deadline != null) { + SectionTextFooter( + text = AnnotatedString(String.format(generalGetString(MR.strings.operator_conditions_accepted_for_enabled_operators_on), localDate(conditionsAction.deadline))), + textAlign = TextAlign.Center + ) + Spacer(Modifier.fillMaxWidth().height(DEFAULT_PADDING)) + } + } + + is UsageConditionsAction.Accepted -> { + if (conditionsAction.operators.isNotEmpty()) { + ReadableText(MR.strings.operators_conditions_accepted_for, args = conditionsAction.operators.joinToString(", ") { it.legalName_ }) + } + Column(modifier = Modifier.weight(1f).padding(bottom = DEFAULT_PADDING, top = DEFAULT_PADDING_HALF)) { + ConditionsTextView(rhId) + } + } + + else -> { + Column(modifier = Modifier.weight(1f).padding(bottom = DEFAULT_PADDING, top = DEFAULT_PADDING_HALF)) { + ConditionsTextView(rhId) + } + } + } + } +} + +@Composable +fun ServersErrorFooter(errStr: String) { + Row( + Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painterResource(MR.images.ic_error), + contentDescription = stringResource(MR.strings.server_error), + tint = Color.Red, + modifier = Modifier + .size(19.sp.toDp()) + .offset(x = 2.sp.toDp()) + ) + TextIconSpaced() + Text( + errStr, + color = MaterialTheme.colors.secondary, + lineHeight = 18.sp, + fontSize = 14.sp + ) + } +} + +private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) { + AlertManager.shared.showAlertDialogStacked( + title = generalGetString(MR.strings.smp_save_servers_question), + confirmText = generalGetString(MR.strings.save_verb), + dismissText = generalGetString(MR.strings.exit_without_saving), + onConfirm = save, + onDismiss = revert, + ) +} + fun showUpdateNetworkSettingsDialog( title: String, startsWith: String = "", @@ -521,6 +807,107 @@ fun showUpdateNetworkSettingsDialog( ) } +fun updateOperatorsConditionsAcceptance(usvs: MutableState>, updatedOperators: List) { + val modified = ArrayList(usvs.value) + for (i in modified.indices) { + val updatedOperator = updatedOperators.firstOrNull { it.operatorId == modified[i].operator?.operatorId } ?: continue + modified[i] = modified[i].copy(operator = modified[i].operator?.copy(conditionsAcceptance = updatedOperator.conditionsAcceptance)) + } + usvs.value = modified +} + +suspend fun validateServers_( + rhId: Long?, + userServersToValidate: List, + serverErrors: MutableState> +) { + try { + val errors = chatController.validateServers(rhId, userServersToValidate) ?: return + serverErrors.value = errors + } catch (ex: Exception) { + Log.e(TAG, ex.stackTraceToString()) + } +} + +fun serversCanBeSaved( + currUserServers: List, + userServers: List, + serverErrors: List +): Boolean { + return userServers != currUserServers && serverErrors.isEmpty() +} + +fun globalServersError(serverErrors: List): String? { + for (err in serverErrors) { + if (err.globalError != null) { + return err.globalError + } + } + return null +} + +fun globalSMPServersError(serverErrors: List): String? { + for (err in serverErrors) { + if (err.globalSMPError != null) { + return err.globalSMPError + } + } + return null +} + +fun globalXFTPServersError(serverErrors: List): String? { + for (err in serverErrors) { + if (err.globalXFTPError != null) { + return err.globalXFTPError + } + } + return null +} + +fun findDuplicateHosts(serverErrors: List): Set { + val duplicateHostsList = serverErrors.mapNotNull { err -> + if (err is UserServersError.DuplicateServer) { + err.duplicateHost + } else { + null + } + } + return duplicateHostsList.toSet() +} + +private suspend fun saveServers( + rhId: Long?, + currUserServers: MutableState>, + userServers: MutableState> +) { + val userServersToSave = userServers.value + try { + val set = setUserServers(rhId, userServersToSave) + + if (set) { + // Get updated servers to learn new server ids (otherwise it messes up delete of newly added and saved servers) + val updatedServers = getUserServers(rhId) + // Get updated operators to update model + val updatedOperators = getServerOperators(rhId) + + if (updatedOperators != null) { + chatModel.conditions.value = updatedOperators + } + + if (updatedServers != null ) { + currUserServers.value = updatedServers + userServers.value = updatedServers + } else { + currUserServers.value = userServersToSave + } + } else { + currUserServers.value = userServersToSave + } + } catch (ex: Exception) { + Log.e(TAG, ex.stackTraceToString()) + } +} + @Preview @Composable fun PreviewNetworkAndServersLayout() { @@ -530,6 +917,9 @@ fun PreviewNetworkAndServersLayout() { networkUseSocksProxy = remember { mutableStateOf(true) }, onionHosts = remember { mutableStateOf(OnionHosts.PREFER) }, toggleSocksProxy = {}, + currUserServers = remember { mutableStateOf(emptyList()) }, + userServers = remember { mutableStateOf(emptyList()) }, + serverErrors = remember { mutableStateOf(emptyList()) } ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NewServerView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NewServerView.kt new file mode 100644 index 0000000000..1ec2534ab1 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NewServerView.kt @@ -0,0 +1,144 @@ +package chat.simplex.common.views.usersettings.networkAndServers + +import SectionBottomSpacer +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.* +import dev.icerock.moko.resources.compose.stringResource +import chat.simplex.common.model.* +import chat.simplex.common.model.ServerAddress.Companion.parseServerAddress +import chat.simplex.common.views.helpers.* +import chat.simplex.common.platform.* +import chat.simplex.res.MR +import kotlinx.coroutines.* + +@Composable +fun ModalData.NewServerView( + userServers: MutableState>, + serverErrors: MutableState>, + rhId: Long?, + close: () -> Unit +) { + val testing = remember { mutableStateOf(false) } + val scope = rememberCoroutineScope() + val newServer = remember { mutableStateOf(UserServer.empty) } + + ModalView(close = { + addServer( + scope, + newServer.value, + userServers, + serverErrors, + rhId, + close = close + ) + }) { + Box { + NewServerLayout( + newServer, + testing.value, + testServer = { + testing.value = true + withLongRunningApi { + val res = testServerConnection(newServer.value, chatModel) + if (isActive) { + newServer.value = res.first + testing.value = false + } + } + }, + ) + + if (testing.value) { + DefaultProgressView(null) + } + } + } +} + +@Composable +private fun NewServerLayout( + server: MutableState, + testing: Boolean, + testServer: () -> Unit, +) { + ColumnWithScrollBar { + AppBarTitle(stringResource(MR.strings.smp_servers_new_server)) + CustomServer(server, testing, testServer, onDelete = null) + SectionBottomSpacer() + } +} + +fun serverProtocolAndOperator( + server: UserServer, + userServers: List +): Pair? { + val serverAddress = parseServerAddress(server.server) + return if (serverAddress != null) { + val serverProtocol = serverAddress.serverProtocol + val hostnames = serverAddress.hostnames + val matchingOperator = userServers.mapNotNull { it.operator }.firstOrNull { op -> + op.serverDomains.any { domain -> + hostnames.any { hostname -> + hostname.endsWith(domain) + } + } + } + Pair(serverProtocol, matchingOperator) + } else { + null + } +} + +fun addServer( + scope: CoroutineScope, + server: UserServer, + userServers: MutableState>, + serverErrors: MutableState>, + rhId: Long?, + close: () -> Unit +) { + val result = serverProtocolAndOperator(server, userServers.value) + if (result != null) { + val (serverProtocol, matchingOperator) = result + val operatorIndex = userServers.value.indexOfFirst { it.operator?.operatorId == matchingOperator?.operatorId } + if (operatorIndex != -1) { + // Create a mutable copy of the userServers list + val updatedUserServers = userServers.value.toMutableList() + val operatorServers = updatedUserServers[operatorIndex] + // Create a mutable copy of the smpServers or xftpServers and add the server + when (serverProtocol) { + ServerProtocol.SMP -> { + val updatedSMPServers = operatorServers.smpServers.toMutableList() + updatedSMPServers.add(server) + updatedUserServers[operatorIndex] = operatorServers.copy(smpServers = updatedSMPServers) + } + + ServerProtocol.XFTP -> { + val updatedXFTPServers = operatorServers.xftpServers.toMutableList() + updatedXFTPServers.add(server) + updatedUserServers[operatorIndex] = operatorServers.copy(xftpServers = updatedXFTPServers) + } + } + + userServers.value = updatedUserServers + close() + matchingOperator?.let { op -> + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.operator_server_alert_title), + text = String.format(generalGetString(MR.strings.server_added_to_operator__name), op.tradeName) + ) + } + } else { // Shouldn't happen + close() + AlertManager.shared.showAlertMsg(title = generalGetString(MR.strings.error_adding_server)) + } + } else { + close() + if (server.server.trim().isNotEmpty()) { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.smp_servers_invalid_address), + text = generalGetString(MR.strings.smp_servers_check_address) + ) + } + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt new file mode 100644 index 0000000000..cb02745511 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt @@ -0,0 +1,701 @@ +package chat.simplex.common.views.usersettings.networkAndServers + +import SectionBottomSpacer +import SectionCustomFooter +import SectionDividerSpaced +import SectionItemView +import SectionTextFooter +import SectionView +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.* +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.text.* +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs +import chat.simplex.common.model.ChatController.getUsageConditions +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chat.item.ItemAction +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.onboarding.* +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import java.net.URI + +@Composable +fun ModalData.OperatorView( + currUserServers: MutableState>, + userServers: MutableState>, + serverErrors: MutableState>, + operatorIndex: Int, + rhId: Long? +) { + val testing = remember { mutableStateOf(false) } + val operator = remember { userServers.value[operatorIndex].operator_ } + val currentUser = remember { chatModel.currentUser }.value + + LaunchedEffect(userServers) { + snapshotFlow { userServers.value } + .collect { updatedServers -> + validateServers_(rhId = rhId, userServersToValidate = updatedServers, serverErrors = serverErrors) + } + } + + Box { + ColumnWithScrollBar(Modifier.alpha(if (testing.value) 0.6f else 1f)) { + AppBarTitle(String.format(stringResource(MR.strings.operator_servers_title), operator.tradeName)) + OperatorViewLayout( + currUserServers, + userServers, + serverErrors, + operatorIndex, + navigateToProtocolView = { serverIndex, server, protocol -> + navigateToProtocolView(userServers, serverErrors, operatorIndex, rhId, serverIndex, server, protocol) + }, + currentUser, + rhId, + testing + ) + } + + if (testing.value) { + DefaultProgressView(null) + } + } +} + +fun navigateToProtocolView( + userServers: MutableState>, + serverErrors: MutableState>, + operatorIndex: Int, + rhId: Long?, + serverIndex: Int, + server: UserServer, + protocol: ServerProtocol +) { + ModalManager.start.showCustomModal { close -> + ProtocolServerView( + m = chatModel, + server = server, + serverProtocol = protocol, + userServers = userServers, + serverErrors = serverErrors, + onDelete = { + if (protocol == ServerProtocol.SMP) { + deleteSMPServer(userServers, operatorIndex, serverIndex) + } else { + deleteXFTPServer(userServers, operatorIndex, serverIndex) + } + close() + }, + onUpdate = { updatedServer -> + userServers.value = userServers.value.toMutableList().apply { + this[operatorIndex] = this[operatorIndex].copy( + smpServers = if (protocol == ServerProtocol.SMP) { + this[operatorIndex].smpServers.toMutableList().apply { + this[serverIndex] = updatedServer + } + } else this[operatorIndex].smpServers, + xftpServers = if (protocol == ServerProtocol.XFTP) { + this[operatorIndex].xftpServers.toMutableList().apply { + this[serverIndex] = updatedServer + } + } else this[operatorIndex].xftpServers + ) + } + }, + close = close, + rhId = rhId + ) + } +} + +@Composable +fun OperatorViewLayout( + currUserServers: MutableState>, + userServers: MutableState>, + serverErrors: MutableState>, + operatorIndex: Int, + navigateToProtocolView: (Int, UserServer, ServerProtocol) -> Unit, + currentUser: User?, + rhId: Long?, + testing: MutableState +) { + val operator by remember { derivedStateOf { userServers.value[operatorIndex].operator_ } } + val scope = rememberCoroutineScope() + val duplicateHosts = findDuplicateHosts(serverErrors.value) + + Column { + SectionView(generalGetString(MR.strings.operator).uppercase()) { + SectionItemView({ ModalManager.start.showModalCloseable { _ -> OperatorInfoView(operator) } }) { + Image(painterResource(operator.largeLogo), null, Modifier.height(48.dp)) + } + UseOperatorToggle( + scope = scope, + currUserServers = currUserServers, + userServers = userServers, + serverErrors = serverErrors, + operatorIndex = operatorIndex, + rhId = rhId + ) + } + val serversErr = globalServersError(serverErrors.value) + if (serversErr != null) { + SectionCustomFooter { + ServersErrorFooter(serversErr) + } + } else { + val footerText = when (val c = operator.conditionsAcceptance) { + is ConditionsAcceptance.Accepted -> if (c.acceptedAt != null) { + String.format(generalGetString(MR.strings.operator_conditions_accepted_on), localDate(c.acceptedAt)) + } else null + is ConditionsAcceptance.Required -> if (operator.enabled && c.deadline != null) { + String.format(generalGetString(MR.strings.operator_conditions_will_be_accepted_on), localDate(c.deadline)) + } else null + } + if (footerText != null) { + SectionTextFooter(footerText) + } + } + + if (operator.enabled) { + if (userServers.value[operatorIndex].smpServers.any { !it.deleted }) { + SectionDividerSpaced() + SectionView(generalGetString(MR.strings.operator_use_for_messages).uppercase()) { + SectionItemView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + Text( + stringResource(MR.strings.operator_use_for_messages_receiving), + Modifier.padding(end = 24.dp), + color = Color.Unspecified + ) + Spacer(Modifier.fillMaxWidth().weight(1f)) + DefaultSwitch( + checked = userServers.value[operatorIndex].operator_.smpRoles.storage, + onCheckedChange = { enabled -> + userServers.value = userServers.value.toMutableList().apply { + this[operatorIndex] = this[operatorIndex].copy( + operator = this[operatorIndex].operator?.copy( + smpRoles = this[operatorIndex].operator?.smpRoles?.copy(storage = enabled) ?: ServerRoles(storage = enabled, proxy = false) + ) + ) + } + } + ) + } + SectionItemView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + Text( + stringResource(MR.strings.operator_use_for_messages_private_routing), + Modifier.padding(end = 24.dp), + color = Color.Unspecified + ) + Spacer(Modifier.fillMaxWidth().weight(1f)) + DefaultSwitch( + checked = userServers.value[operatorIndex].operator_.smpRoles.proxy, + onCheckedChange = { enabled -> + userServers.value = userServers.value.toMutableList().apply { + this[operatorIndex] = this[operatorIndex].copy( + operator = this[operatorIndex].operator?.copy( + smpRoles = this[operatorIndex].operator?.smpRoles?.copy(proxy = enabled) ?: ServerRoles(storage = false, proxy = enabled) + ) + ) + } + } + ) + } + + } + val smpErr = globalSMPServersError(serverErrors.value) + if (smpErr != null) { + SectionCustomFooter { + ServersErrorFooter(smpErr) + } + } + } + + // Preset servers can't be deleted + if (userServers.value[operatorIndex].smpServers.any { it.preset }) { + SectionDividerSpaced() + SectionView(generalGetString(MR.strings.message_servers).uppercase()) { + userServers.value[operatorIndex].smpServers.forEachIndexed { i, server -> + if (!server.preset) return@forEachIndexed + SectionItemView({ navigateToProtocolView(i, server, ServerProtocol.SMP) }) { + ProtocolServerViewLink( + srv = server, + serverProtocol = ServerProtocol.SMP, + duplicateHosts = duplicateHosts + ) + } + } + } + val smpErr = globalSMPServersError(serverErrors.value) + if (smpErr != null) { + SectionCustomFooter { + ServersErrorFooter(smpErr) + } + } else { + SectionTextFooter( + remember(currentUser?.displayName) { + buildAnnotatedString { + append(generalGetString(MR.strings.smp_servers_per_user) + " ") + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + append(currentUser?.displayName ?: "") + } + append(".") + } + } + ) + } + } + + if (userServers.value[operatorIndex].smpServers.any { !it.preset && !it.deleted }) { + SectionDividerSpaced() + SectionView(generalGetString(MR.strings.operator_added_message_servers).uppercase()) { + userServers.value[operatorIndex].smpServers.forEachIndexed { i, server -> + if (server.deleted || server.preset) return@forEachIndexed + SectionItemView({ navigateToProtocolView(i, server, ServerProtocol.SMP) }) { + ProtocolServerViewLink( + srv = server, + serverProtocol = ServerProtocol.SMP, + duplicateHosts = duplicateHosts + ) + } + } + } + } + + if (userServers.value[operatorIndex].xftpServers.any { !it.deleted }) { + SectionDividerSpaced() + SectionView(generalGetString(MR.strings.operator_use_for_files).uppercase()) { + SectionItemView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + Text( + stringResource(MR.strings.operator_use_for_sending), + Modifier.padding(end = 24.dp), + color = Color.Unspecified + ) + Spacer(Modifier.fillMaxWidth().weight(1f)) + DefaultSwitch( + checked = userServers.value[operatorIndex].operator_.xftpRoles.storage, + onCheckedChange = { enabled -> + userServers.value = userServers.value.toMutableList().apply { + this[operatorIndex] = this[operatorIndex].copy( + operator = this[operatorIndex].operator?.copy( + xftpRoles = this[operatorIndex].operator?.xftpRoles?.copy(storage = enabled) ?: ServerRoles(storage = enabled, proxy = false) + ) + ) + } + } + ) + } + } + val xftpErr = globalXFTPServersError(serverErrors.value) + if (xftpErr != null) { + SectionCustomFooter { + ServersErrorFooter(xftpErr) + } + } + } + + // Preset servers can't be deleted + if (userServers.value[operatorIndex].xftpServers.any { it.preset }) { + SectionDividerSpaced() + SectionView(generalGetString(MR.strings.media_and_file_servers).uppercase()) { + userServers.value[operatorIndex].xftpServers.forEachIndexed { i, server -> + if (!server.preset) return@forEachIndexed + SectionItemView({ navigateToProtocolView(i, server, ServerProtocol.XFTP) }) { + ProtocolServerViewLink( + srv = server, + serverProtocol = ServerProtocol.XFTP, + duplicateHosts = duplicateHosts + ) + } + } + } + val xftpErr = globalXFTPServersError(serverErrors.value) + if (xftpErr != null) { + SectionCustomFooter { + ServersErrorFooter(xftpErr) + } + } else { + SectionTextFooter( + remember(currentUser?.displayName) { + buildAnnotatedString { + append(generalGetString(MR.strings.xftp_servers_per_user) + " ") + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + append(currentUser?.displayName ?: "") + } + append(".") + } + } + ) + } + } + + if (userServers.value[operatorIndex].xftpServers.any { !it.preset && !it.deleted}) { + SectionDividerSpaced() + SectionView(generalGetString(MR.strings.operator_added_xftp_servers).uppercase()) { + userServers.value[operatorIndex].xftpServers.forEachIndexed { i, server -> + if (server.deleted || server.preset) return@forEachIndexed + SectionItemView({ navigateToProtocolView(i, server, ServerProtocol.XFTP) }) { + ProtocolServerViewLink( + srv = server, + serverProtocol = ServerProtocol.XFTP, + duplicateHosts = duplicateHosts + ) + } + } + } + } + + SectionDividerSpaced() + SectionView { + TestServersButton( + testing = testing, + smpServers = userServers.value[operatorIndex].smpServers, + xftpServers = userServers.value[operatorIndex].xftpServers, + ) { p, l -> + when (p) { + ServerProtocol.XFTP -> userServers.value = userServers.value.toMutableList().apply { + this[operatorIndex] = this[operatorIndex].copy( + xftpServers = l + ) + } + + ServerProtocol.SMP -> userServers.value = userServers.value.toMutableList().apply { + this[operatorIndex] = this[operatorIndex].copy( + smpServers = l + ) + } + } + } + } + + SectionBottomSpacer() + } + } +} + +@Composable +private fun OperatorInfoView(serverOperator: ServerOperator) { + ColumnWithScrollBar { + AppBarTitle(stringResource(MR.strings.operator_info_title)) + + SectionView { + SectionItemView { + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { + Image(painterResource(serverOperator.largeLogo), null, Modifier.height(48.dp)) + if (serverOperator.legalName != null) { + Text(serverOperator.legalName) + } + } + } + } + + SectionDividerSpaced(maxBottomPadding = false) + + SectionView { + SectionItemView { + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { + serverOperator.info.description.forEach { d -> + Text(d) + } + } + } + } + + SectionDividerSpaced() + + SectionView(generalGetString(MR.strings.operator_website).uppercase()) { + SectionItemView { + val website = serverOperator.info.website + val uriHandler = LocalUriHandler.current + Text(website, color = MaterialTheme.colors.primary, modifier = Modifier.clickable { uriHandler.openUriCatching(website) }) + } + } + } +} + +@Composable +private fun UseOperatorToggle( + scope: CoroutineScope, + currUserServers: MutableState>, + userServers: MutableState>, + serverErrors: MutableState>, + operatorIndex: Int, + rhId: Long? +) { + SectionItemView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + Text( + stringResource(MR.strings.operator_use_operator_toggle_description), + Modifier.padding(end = 24.dp), + color = Color.Unspecified + ) + Spacer(Modifier.fillMaxWidth().weight(1f)) + DefaultSwitch( + checked = userServers.value[operatorIndex].operator?.enabled ?: false, + onCheckedChange = { enabled -> + val operator = userServers.value[operatorIndex].operator + if (enabled) { + when (val conditionsAcceptance = operator?.conditionsAcceptance) { + is ConditionsAcceptance.Accepted -> { + changeOperatorEnabled(userServers, operatorIndex, true) + } + + is ConditionsAcceptance.Required -> { + if (conditionsAcceptance.deadline == null) { + ModalManager.start.showModalCloseable(endButtons = { ConditionsLinkButton() }) { close -> + SingleOperatorUsageConditionsView( + currUserServers = currUserServers, + userServers = userServers, + serverErrors = serverErrors, + operatorIndex = operatorIndex, + rhId = rhId, + close = close + ) + } + } else { + changeOperatorEnabled(userServers, operatorIndex, true) + } + } + + else -> {} + } + } else { + changeOperatorEnabled(userServers, operatorIndex, false) + } + }, + ) + } +} + +@Composable +private fun SingleOperatorUsageConditionsView( + currUserServers: MutableState>, + userServers: MutableState>, + serverErrors: MutableState>, + operatorIndex: Int, + rhId: Long?, + close: () -> Unit +) { + val operatorsWithConditionsAccepted = remember { chatModel.conditions.value.serverOperators.filter { it.conditionsAcceptance.conditionsAccepted } } + val operator = remember { userServers.value[operatorIndex].operator_ } + val scope = rememberCoroutineScope() + + suspend fun acceptForOperators(rhId: Long?, operatorIds: List, operatorIndexToEnable: Int, close: () -> Unit) { + try { + val conditionsId = chatModel.conditions.value.currentConditions.conditionsId + val r = chatController.acceptConditions(rhId, conditionsId, operatorIds) ?: return + + chatModel.conditions.value = r + updateOperatorsConditionsAcceptance(currUserServers, r.serverOperators) + updateOperatorsConditionsAcceptance(userServers, r.serverOperators) + changeOperatorEnabled(userServers, operatorIndex, true) + close() + } catch (ex: Exception) { + Log.e(TAG, ex.stackTraceToString()) + } + } + + @Composable + fun AcceptConditionsButton(close: () -> Unit) { + // Opened operator or Other enabled operators with conditions not accepted + val operatorIds = chatModel.conditions.value.serverOperators + .filter { it.operatorId == operator.id || (it.enabled && !it.conditionsAcceptance.conditionsAccepted) } + .map { it.operatorId } + + Column(Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING * 2), horizontalAlignment = Alignment.CenterHorizontally) { + OnboardingActionButton( + labelId = MR.strings.accept_conditions, + onboarding = null, + enabled = operatorIds.isNotEmpty(), + onclick = { + scope.launch { + acceptForOperators(rhId, operatorIds, operatorIndex, close) + } + } + ) + } + } + + @Composable + fun UsageConditionsDestinationView(close: () -> Unit) { + ColumnWithScrollBar(modifier = Modifier.fillMaxSize()) { + AppBarTitle(stringResource(MR.strings.operator_conditions_of_use), enableAlphaChanges = false) + Column(modifier = Modifier.weight(1f).padding(end = DEFAULT_PADDING, start = DEFAULT_PADDING, bottom = DEFAULT_PADDING, top = DEFAULT_PADDING_HALF)) { + ConditionsTextView(rhId) + } + } + } + + @Composable + fun UsageConditionsNavLinkButton() { + Text( + stringResource(MR.strings.view_conditions), + color = MaterialTheme.colors.primary, + modifier = Modifier.padding(top = DEFAULT_PADDING_HALF).clickable { + ModalManager.start.showModalCloseable(endButtons = { ConditionsLinkButton() }) { close -> + UsageConditionsDestinationView(close) + } + } + ) + } + + ColumnWithScrollBar(modifier = Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING)) { + AppBarTitle(String.format(stringResource(MR.strings.use_servers_of_operator_x), operator.tradeName), enableAlphaChanges = false, withPadding = false) + if (operator.conditionsAcceptance is ConditionsAcceptance.Accepted) { + // In current UI implementation this branch doesn't get shown - as conditions can't be opened from inside operator once accepted + Column(modifier = Modifier.weight(1f).padding(end = DEFAULT_PADDING, start = DEFAULT_PADDING, bottom = DEFAULT_PADDING)) { + ConditionsTextView(rhId) + } + } else if (operatorsWithConditionsAccepted.isNotEmpty()) { + ReadableText( + MR.strings.operator_conditions_accepted_for_some, + args = operatorsWithConditionsAccepted.joinToString(", ") { it.legalName_ } + ) + ReadableText( + MR.strings.operator_same_conditions_will_be_applied, + args = operator.legalName_ + ) + ConditionsAppliedToOtherOperatorsText(userServers = userServers.value, operatorIndex = operatorIndex) + + UsageConditionsNavLinkButton() + Spacer(Modifier.fillMaxWidth().weight(1f)) + AcceptConditionsButton(close) + } else { + ReadableText( + MR.strings.operator_in_order_to_use_accept_conditions, + args = operator.legalName_ + ) + ConditionsAppliedToOtherOperatorsText(userServers = userServers.value, operatorIndex = operatorIndex) + Column(modifier = Modifier.weight(1f).padding(end = DEFAULT_PADDING, start = DEFAULT_PADDING, bottom = DEFAULT_PADDING)) { + ConditionsTextView(rhId) + } + AcceptConditionsButton(close) + } + } +} + +@Composable +fun ConditionsTextView( + rhId: Long? +) { + val conditionsData = remember { mutableStateOf?>(null) } + val failedToLoad = remember { mutableStateOf(false) } + val defaultConditionsLink = "https://github.com/simplex-chat/simplex-chat/blob/stable/PRIVACY.md" + val scope = rememberCoroutineScope() + + LaunchedEffect(Unit) { + scope.launch { + try { + val conditions = getUsageConditions(rh = rhId) + + if (conditions != null) { + conditionsData.value = conditions + } else { + failedToLoad.value = true + } + } catch (ex: Exception) { + failedToLoad.value = true + } + } + } + val conditions = conditionsData.value + + if (conditions != null) { + val (usageConditions, conditionsText, _) = conditions + + if (conditionsText != null) { + val scrollState = rememberScrollState() + Box( + modifier = Modifier + .fillMaxSize() + .border(border = BorderStroke(1.dp, CurrentColors.value.colors.secondary.copy(alpha = 0.6f)), shape = RoundedCornerShape(12.dp)) + .verticalScroll(scrollState) + .padding(8.dp) + ) { + Text( + text = conditionsText.trimIndent(), + modifier = Modifier.padding(8.dp) + ) + } + } else { + val conditionsLink = "https://github.com/simplex-chat/simplex-chat/blob/${usageConditions.conditionsCommit}/PRIVACY.md" + ConditionsLinkView(conditionsLink) + } + } else if (failedToLoad.value) { + ConditionsLinkView(defaultConditionsLink) + } else { + DefaultProgressView(null) + } +} + +@Composable +private fun ConditionsLinkView(conditionsLink: String) { + SectionItemView { + val uriHandler = LocalUriHandler.current + Text(stringResource(MR.strings.operator_conditions_failed_to_load), color = MaterialTheme.colors.onBackground) + Text(conditionsLink, color = MaterialTheme.colors.primary, modifier = Modifier.clickable { uriHandler.openUriCatching(conditionsLink) }) + } +} + +@Composable +private fun ConditionsAppliedToOtherOperatorsText(userServers: List, operatorIndex: Int) { + val otherOperatorsToApply = remember { + derivedStateOf { + chatModel.conditions.value.serverOperators.filter { + it.enabled && + !it.conditionsAcceptance.conditionsAccepted && + it.operatorId != userServers[operatorIndex].operator_.operatorId + } + } + } + + if (otherOperatorsToApply.value.isNotEmpty()) { + ReadableText(MR.strings.operator_conditions_will_be_applied) + } +} + +@Composable +fun ConditionsLinkButton() { + val showMenu = remember { mutableStateOf(false) } + val uriHandler = LocalUriHandler.current + val oneHandUI = remember { appPrefs.oneHandUI.state } + Column { + DefaultDropdownMenu(showMenu, offset = if (oneHandUI.value) DpOffset(0.dp, -AppBarHeight * fontSizeSqrtMultiplier * 3) else DpOffset.Zero) { + val commit = chatModel.conditions.value.currentConditions.conditionsCommit + ItemAction(stringResource(MR.strings.operator_open_conditions), painterResource(MR.images.ic_draft), onClick = { + val mdUrl = "https://github.com/simplex-chat/simplex-chat/blob/$commit/PRIVACY.md" + uriHandler.openUriCatching(mdUrl) + showMenu.value = false + }) + ItemAction(stringResource(MR.strings.operator_open_changes), painterResource(MR.images.ic_more_horiz), onClick = { + val commitUrl = "https://github.com/simplex-chat/simplex-chat/commit/$commit" + uriHandler.openUriCatching(commitUrl) + showMenu.value = false + }) + } + IconButton({ showMenu.value = true }) { + Icon(painterResource(MR.images.ic_outbound), null, tint = MaterialTheme.colors.primary) + } + } +} + +private fun changeOperatorEnabled(userServers: MutableState>, operatorIndex: Int, enabled: Boolean) { + userServers.value = userServers.value.toMutableList().apply { + this[operatorIndex] = this[operatorIndex].copy( + operator = this[operatorIndex].operator?.copy(enabled = enabled) + ) + } +} \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServerView.kt similarity index 51% rename from apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServerView.kt index be566e6c5a..bebc96a28c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServerView.kt @@ -1,16 +1,14 @@ -package chat.simplex.common.views.usersettings +package chat.simplex.common.views.usersettings.networkAndServers import SectionBottomSpacer import SectionDividerSpaced import SectionItemView import SectionItemViewSpaceBetween import SectionView -import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.* import androidx.compose.runtime.* -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import dev.icerock.moko.resources.compose.painterResource @@ -26,62 +24,103 @@ import chat.simplex.common.views.helpers.* import chat.simplex.common.views.newchat.QRCode import chat.simplex.common.model.ChatModel import chat.simplex.common.platform.* +import chat.simplex.common.views.usersettings.PreferenceToggle import chat.simplex.res.MR import kotlinx.coroutines.* import kotlinx.coroutines.flow.distinctUntilChanged @Composable -fun ProtocolServerView(m: ChatModel, server: ServerCfg, serverProtocol: ServerProtocol, onUpdate: (ServerCfg) -> Unit, onDelete: () -> Unit) { - var testing by remember { mutableStateOf(false) } - ProtocolServerLayout( - testing, - server, - serverProtocol, - testServer = { - testing = true - withLongRunningApi { - val res = testServerConnection(server, m) - if (isActive) { - onUpdate(res.first) - testing = false +fun ProtocolServerView( + m: ChatModel, + server: UserServer, + serverProtocol: ServerProtocol, + userServers: MutableState>, + serverErrors: MutableState>, + onDelete: () -> Unit, + onUpdate: (UserServer) -> Unit, + close: () -> Unit, + rhId: Long? +) { + val testing = remember { mutableStateOf(false) } + val scope = rememberCoroutineScope() + val draftServer = remember { mutableStateOf(server) } + + ModalView( + close = { + scope.launch { + val draftResult = serverProtocolAndOperator(draftServer.value, userServers.value) + val savedResult = serverProtocolAndOperator(server, userServers.value) + + if (draftResult != null && savedResult != null) { + val (serverToEditProtocol, serverToEditOperator) = draftResult + val (svProtocol, serverOperator) = savedResult + + if (serverToEditProtocol != svProtocol) { + close() + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.error_updating_server_title), + text = generalGetString(MR.strings.error_server_protocol_changed) + ) + } else if (serverToEditOperator != serverOperator) { + close() + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.error_updating_server_title), + text = generalGetString(MR.strings.error_server_operator_changed) + ) + } else { + onUpdate(draftServer.value) + close() + } + } else { + close() + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.smp_servers_invalid_address), + text = generalGetString(MR.strings.smp_servers_check_address) + ) } } - }, - onUpdate, - onDelete - ) - if (testing) { - Box( - Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator( - Modifier - .padding(horizontal = 2.dp) - .size(30.dp), - color = MaterialTheme.colors.secondary, - strokeWidth = 2.5.dp + } + ) { + Box { + ProtocolServerLayout( + draftServer, + serverProtocol, + testing.value, + testServer = { + testing.value = true + withLongRunningApi { + val res = testServerConnection(draftServer.value, m) + if (isActive) { + draftServer.value = res.first + testing.value = false + } + } + }, + onDelete ) + + if (testing.value) { + DefaultProgressView(null) + } } } } @Composable private fun ProtocolServerLayout( - testing: Boolean, - server: ServerCfg, + server: MutableState, serverProtocol: ServerProtocol, + testing: Boolean, testServer: () -> Unit, - onUpdate: (ServerCfg) -> Unit, onDelete: () -> Unit, ) { ColumnWithScrollBar { - AppBarTitle(stringResource(if (server.preset) MR.strings.smp_servers_preset_server else MR.strings.smp_servers_your_server)) + AppBarTitle(stringResource(if (serverProtocol == ServerProtocol.XFTP) MR.strings.xftp_server else MR.strings.smp_server)) - if (server.preset) { - PresetServer(testing, server, testServer, onUpdate, onDelete) + if (server.value.preset) { + PresetServer(server, testing, testServer) } else { - CustomServer(testing, server, serverProtocol, testServer, onUpdate, onDelete) + CustomServer(server, testing, testServer, onDelete) } SectionBottomSpacer() } @@ -89,16 +128,14 @@ private fun ProtocolServerLayout( @Composable private fun PresetServer( + server: MutableState, testing: Boolean, - server: ServerCfg, - testServer: () -> Unit, - onUpdate: (ServerCfg) -> Unit, - onDelete: () -> Unit, + testServer: () -> Unit ) { SectionView(stringResource(MR.strings.smp_servers_preset_address).uppercase()) { SelectionContainer { Text( - server.server, + server.value.server, Modifier.padding(start = DEFAULT_PADDING, top = 5.dp, end = DEFAULT_PADDING, bottom = 10.dp), style = TextStyle( fontFamily = FontFamily.Monospace, fontSize = 16.sp, @@ -108,23 +145,21 @@ private fun PresetServer( } } SectionDividerSpaced() - UseServerSection(true, testing, server, testServer, onUpdate, onDelete) + UseServerSection(server, true, testing, testServer) } @Composable -private fun CustomServer( +fun CustomServer( + server: MutableState, testing: Boolean, - server: ServerCfg, - serverProtocol: ServerProtocol, testServer: () -> Unit, - onUpdate: (ServerCfg) -> Unit, - onDelete: () -> Unit, + onDelete: (() -> Unit)?, ) { - val serverAddress = remember { mutableStateOf(server.server) } + val serverAddress = remember { mutableStateOf(server.value.server) } val valid = remember { derivedStateOf { with(parseServerAddress(serverAddress.value)) { - this?.valid == true && this.serverProtocol == serverProtocol + this?.valid == true } } } @@ -142,13 +177,14 @@ private fun CustomServer( snapshotFlow { serverAddress.value } .distinctUntilChanged() .collect { - testedPreviously[server.server] = server.tested - onUpdate(server.copy(server = it, tested = testedPreviously[serverAddress.value])) + testedPreviously[server.value.server] = server.value.tested + server.value = server.value.copy(server = it, tested = testedPreviously[serverAddress.value]) } } } SectionDividerSpaced(maxTopPadding = true) - UseServerSection(valid.value, testing, server, testServer, onUpdate, onDelete) + + UseServerSection(server, valid.value, testing, testServer, onDelete) if (valid.value) { SectionDividerSpaced() @@ -160,43 +196,44 @@ private fun CustomServer( @Composable private fun UseServerSection( + server: MutableState, valid: Boolean, testing: Boolean, - server: ServerCfg, testServer: () -> Unit, - onUpdate: (ServerCfg) -> Unit, - onDelete: () -> Unit, + onDelete: (() -> Unit)? = null, ) { SectionView(stringResource(MR.strings.smp_servers_use_server).uppercase()) { SectionItemViewSpaceBetween(testServer, disabled = !valid || testing) { Text(stringResource(MR.strings.smp_servers_test_server), color = if (valid && !testing) MaterialTheme.colors.onBackground else MaterialTheme.colors.secondary) - ShowTestStatus(server) + ShowTestStatus(server.value) } - val enabled = rememberUpdatedState(server.enabled) + val enabled = rememberUpdatedState(server.value.enabled) PreferenceToggle( stringResource(MR.strings.smp_servers_use_server_for_new_conn), - disabled = server.tested != true && !server.preset, + disabled = testing, checked = enabled.value ) { - onUpdate(server.copy(enabled = it)) + server.value = server.value.copy(enabled = it) } - - SectionItemView(onDelete, disabled = testing) { - Text(stringResource(MR.strings.smp_servers_delete_server), color = if (testing) MaterialTheme.colors.secondary else MaterialTheme.colors.error) + + if (onDelete != null) { + SectionItemView(onDelete, disabled = testing) { + Text(stringResource(MR.strings.smp_servers_delete_server), color = if (testing) MaterialTheme.colors.secondary else MaterialTheme.colors.error) + } } } } @Composable -fun ShowTestStatus(server: ServerCfg, modifier: Modifier = Modifier) = +fun ShowTestStatus(server: UserServer, modifier: Modifier = Modifier) = when (server.tested) { true -> Icon(painterResource(MR.images.ic_check), null, modifier, tint = SimplexGreen) false -> Icon(painterResource(MR.images.ic_close), null, modifier, tint = MaterialTheme.colors.error) else -> Icon(painterResource(MR.images.ic_check), null, modifier, tint = Color.Transparent) } -suspend fun testServerConnection(server: ServerCfg, m: ChatModel): Pair = +suspend fun testServerConnection(server: UserServer, m: ChatModel): Pair = try { val r = m.controller.testProtoServer(server.remoteHostId, server.server) server.copy(tested = r == null) to r diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt new file mode 100644 index 0000000000..63bf8b1dc4 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt @@ -0,0 +1,407 @@ +package chat.simplex.common.views.usersettings.networkAndServers + +import SectionBottomSpacer +import SectionCustomFooter +import SectionDividerSpaced +import SectionItemView +import SectionTextFooter +import SectionView +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalUriHandler +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import androidx.compose.ui.text.* +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import chat.simplex.common.model.ServerAddress.Companion.parseServerAddress +import chat.simplex.common.views.helpers.* +import chat.simplex.common.model.* +import chat.simplex.common.platform.* +import chat.simplex.common.views.usersettings.SettingsActionItem +import chat.simplex.res.MR +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +@Composable +fun ModalData.YourServersView( + userServers: MutableState>, + serverErrors: MutableState>, + operatorIndex: Int, + rhId: Long? +) { + val testing = remember { mutableStateOf(false) } + val currentUser = remember { chatModel.currentUser }.value + val scope = rememberCoroutineScope() + + LaunchedEffect(userServers) { + snapshotFlow { userServers.value } + .collect { updatedServers -> + validateServers_(rhId = rhId, userServersToValidate = updatedServers, serverErrors = serverErrors) + } + } + + Box { + ColumnWithScrollBar { + AppBarTitle(stringResource(MR.strings.your_servers)) + YourServersViewLayout( + scope, + userServers, + serverErrors, + operatorIndex, + navigateToProtocolView = { serverIndex, server, protocol -> + navigateToProtocolView(userServers, serverErrors, operatorIndex, rhId, serverIndex, server, protocol) + }, + currentUser, + rhId, + testing + ) + } + + if (testing.value) { + DefaultProgressView(null) + } + } +} + +@Composable +fun YourServersViewLayout( + scope: CoroutineScope, + userServers: MutableState>, + serverErrors: MutableState>, + operatorIndex: Int, + navigateToProtocolView: (Int, UserServer, ServerProtocol) -> Unit, + currentUser: User?, + rhId: Long?, + testing: MutableState +) { + val duplicateHosts = findDuplicateHosts(serverErrors.value) + + Column { + if (userServers.value[operatorIndex].smpServers.any { !it.deleted }) { + SectionView(generalGetString(MR.strings.message_servers).uppercase()) { + userServers.value[operatorIndex].smpServers.forEachIndexed { i, server -> + if (server.deleted) return@forEachIndexed + SectionItemView({ navigateToProtocolView(i, server, ServerProtocol.SMP) }) { + ProtocolServerViewLink( + srv = server, + serverProtocol = ServerProtocol.SMP, + duplicateHosts = duplicateHosts + ) + } + } + } + val smpErr = globalSMPServersError(serverErrors.value) + if (smpErr != null) { + SectionCustomFooter { + ServersErrorFooter(smpErr) + } + } else { + SectionTextFooter( + remember(currentUser?.displayName) { + buildAnnotatedString { + append(generalGetString(MR.strings.smp_servers_per_user) + " ") + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + append(currentUser?.displayName ?: "") + } + append(".") + } + } + ) + } + } + + if (userServers.value[operatorIndex].xftpServers.any { !it.deleted }) { + SectionDividerSpaced() + SectionView(generalGetString(MR.strings.media_and_file_servers).uppercase()) { + userServers.value[operatorIndex].xftpServers.forEachIndexed { i, server -> + if (server.deleted) return@forEachIndexed + SectionItemView({ navigateToProtocolView(i, server, ServerProtocol.XFTP) }) { + ProtocolServerViewLink( + srv = server, + serverProtocol = ServerProtocol.XFTP, + duplicateHosts = duplicateHosts + ) + } + } + } + val xftpErr = globalXFTPServersError(serverErrors.value) + if (xftpErr != null) { + SectionCustomFooter { + ServersErrorFooter(xftpErr) + } + } else { + SectionTextFooter( + remember(currentUser?.displayName) { + buildAnnotatedString { + append(generalGetString(MR.strings.xftp_servers_per_user) + " ") + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + append(currentUser?.displayName ?: "") + } + append(".") + } + } + ) + } + } + + if ( + userServers.value[operatorIndex].smpServers.any { !it.deleted } || + userServers.value[operatorIndex].xftpServers.any { !it.deleted } + ) { + SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = false) + } + + SectionView { + SettingsActionItem( + painterResource(MR.images.ic_add), + stringResource(MR.strings.smp_servers_add), + click = { showAddServerDialog(scope, userServers, serverErrors, rhId) }, + disabled = testing.value, + textColor = if (testing.value) MaterialTheme.colors.secondary else MaterialTheme.colors.primary, + iconColor = if (testing.value) MaterialTheme.colors.secondary else MaterialTheme.colors.primary + ) + } + val serversErr = globalServersError(serverErrors.value) + if (serversErr != null) { + SectionCustomFooter { + ServersErrorFooter(serversErr) + } + } + SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = false) + + SectionView { + TestServersButton( + testing = testing, + smpServers = userServers.value[operatorIndex].smpServers, + xftpServers = userServers.value[operatorIndex].xftpServers, + ) { p, l -> + when (p) { + ServerProtocol.XFTP -> userServers.value = userServers.value.toMutableList().apply { + this[operatorIndex] = this[operatorIndex].copy( + xftpServers = l + ) + } + + ServerProtocol.SMP -> userServers.value = userServers.value.toMutableList().apply { + this[operatorIndex] = this[operatorIndex].copy( + smpServers = l + ) + } + } + } + + HowToButton() + } + SectionBottomSpacer() + } +} + +@Composable +fun TestServersButton( + smpServers: List, + xftpServers: List, + testing: MutableState, + onUpdate: (ServerProtocol, List) -> Unit +) { + val scope = rememberCoroutineScope() + val disabled = derivedStateOf { (smpServers.none { it.enabled } && xftpServers.none { it.enabled }) || testing.value } + + SectionItemView( + { + scope.launch { + testServers(testing, smpServers, xftpServers, chatModel, onUpdate) + } + }, + disabled = disabled.value + ) { + Text(stringResource(MR.strings.smp_servers_test_servers), color = if (!disabled.value) MaterialTheme.colors.onBackground else MaterialTheme.colors.secondary) + } +} + +fun showAddServerDialog( + scope: CoroutineScope, + userServers: MutableState>, + serverErrors: MutableState>, + rhId: Long? +) { + AlertManager.shared.showAlertDialogButtonsColumn( + title = generalGetString(MR.strings.smp_servers_add), + buttons = { + Column { + SectionItemView({ + AlertManager.shared.hideAlert() + ModalManager.start.showCustomModal { close -> + NewServerView(userServers, serverErrors, rhId, close) + } + }) { + Text(stringResource(MR.strings.smp_servers_enter_manually), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + if (appPlatform.isAndroid) { + SectionItemView({ + AlertManager.shared.hideAlert() + ModalManager.start.showModalCloseable { close -> + ScanProtocolServer(rhId) { server -> + addServer( + scope, + server, + userServers, + serverErrors, + rhId, + close = close + ) + } + } + } + ) { + Text(stringResource(MR.strings.smp_servers_scan_qr), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + } + } + } + ) +} + +@Composable +fun ProtocolServerViewLink(serverProtocol: ServerProtocol, srv: UserServer, duplicateHosts: Set) { + val address = parseServerAddress(srv.server) + when { + address == null || !address.valid || address.serverProtocol != serverProtocol || address.hostnames.any { it in duplicateHosts } -> InvalidServer() + !srv.enabled -> Icon(painterResource(MR.images.ic_do_not_disturb_on), null, tint = MaterialTheme.colors.secondary) + else -> ShowTestStatus(srv) + } + Spacer(Modifier.padding(horizontal = 4.dp)) + val text = address?.hostnames?.firstOrNull() ?: srv.server + if (srv.enabled) { + Text(text, color = MaterialTheme.colors.onBackground, maxLines = 1) + } else { + Text(text, maxLines = 1, color = MaterialTheme.colors.secondary) + } +} + +@Composable +private fun HowToButton() { + val uriHandler = LocalUriHandler.current + SettingsActionItem( + painterResource(MR.images.ic_open_in_new), + stringResource(MR.strings.how_to_use_your_servers), + { uriHandler.openUriCatching("https://simplex.chat/docs/server.html") }, + textColor = MaterialTheme.colors.primary, + iconColor = MaterialTheme.colors.primary + ) +} + +@Composable +fun InvalidServer() { + Icon(painterResource(MR.images.ic_error), null, tint = MaterialTheme.colors.error) +} + +private suspend fun testServers( + testing: MutableState, + smpServers: List, + xftpServers: List, + m: ChatModel, + onUpdate: (ServerProtocol, List) -> Unit +) { + val smpResetStatus = resetTestStatus(smpServers) + onUpdate(ServerProtocol.SMP, smpResetStatus) + val xftpResetStatus = resetTestStatus(xftpServers) + onUpdate(ServerProtocol.XFTP, xftpResetStatus) + testing.value = true + val smpFailures = runServersTest(smpResetStatus, m) { onUpdate(ServerProtocol.SMP, it) } + val xftpFailures = runServersTest(xftpResetStatus, m) { onUpdate(ServerProtocol.XFTP, it) } + testing.value = false + val fs = smpFailures + xftpFailures + if (fs.isNotEmpty()) { + val msg = fs.map { it.key + ": " + it.value.localizedDescription }.joinToString("\n") + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.smp_servers_test_failed), + text = generalGetString(MR.strings.smp_servers_test_some_failed) + "\n" + msg + ) + } +} + +private fun resetTestStatus(servers: List): List { + val copy = ArrayList(servers) + for ((index, server) in servers.withIndex()) { + if (server.enabled) { + copy.removeAt(index) + copy.add(index, server.copy(tested = null)) + } + } + return copy +} + +private suspend fun runServersTest(servers: List, m: ChatModel, onUpdated: (List) -> Unit): Map { + val fs: MutableMap = mutableMapOf() + val updatedServers = ArrayList(servers) + for ((index, server) in servers.withIndex()) { + if (server.enabled) { + interruptIfCancelled() + val (updatedServer, f) = testServerConnection(server, m) + updatedServers.removeAt(index) + updatedServers.add(index, updatedServer) + // toList() is important. Otherwise, Compose will not redraw the screen after first update + onUpdated(updatedServers.toList()) + if (f != null) { + fs[serverHostname(updatedServer.server)] = f + } + } + } + return fs +} + +fun deleteXFTPServer( + userServers: MutableState>, + operatorServersIndex: Int, + serverIndex: Int +) { + val serverIsSaved = userServers.value[operatorServersIndex].xftpServers[serverIndex].serverId != null + + if (serverIsSaved) { + userServers.value = userServers.value.toMutableList().apply { + this[operatorServersIndex] = this[operatorServersIndex].copy( + xftpServers = this[operatorServersIndex].xftpServers.toMutableList().apply { + this[serverIndex] = this[serverIndex].copy(deleted = true) + } + ) + } + } else { + userServers.value = userServers.value.toMutableList().apply { + this[operatorServersIndex] = this[operatorServersIndex].copy( + xftpServers = this[operatorServersIndex].xftpServers.toMutableList().apply { + this.removeAt(serverIndex) + } + ) + } + } +} + +fun deleteSMPServer( + userServers: MutableState>, + operatorServersIndex: Int, + serverIndex: Int +) { + val serverIsSaved = userServers.value[operatorServersIndex].smpServers[serverIndex].serverId != null + + if (serverIsSaved) { + userServers.value = userServers.value.toMutableList().apply { + this[operatorServersIndex] = this[operatorServersIndex].copy( + smpServers = this[operatorServersIndex].smpServers.toMutableList().apply { + this[serverIndex] = this[serverIndex].copy(deleted = true) + } + ) + } + } else { + userServers.value = userServers.value.toMutableList().apply { + this[operatorServersIndex] = this[operatorServersIndex].copy( + smpServers = this[operatorServersIndex].smpServers.toMutableList().apply { + this.removeAt(serverIndex) + } + ) + } + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ScanProtocolServer.kt similarity index 62% rename from apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ScanProtocolServer.kt index 966f44cac7..56f16d4eb1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ScanProtocolServer.kt @@ -1,29 +1,25 @@ -package chat.simplex.common.views.usersettings +package chat.simplex.common.views.usersettings.networkAndServers -import androidx.compose.foundation.layout.* import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.unit.dp import chat.simplex.common.model.ServerAddress.Companion.parseServerAddress -import chat.simplex.common.model.ServerCfg +import chat.simplex.common.model.UserServer import chat.simplex.common.platform.ColumnWithScrollBar -import chat.simplex.common.ui.theme.DEFAULT_PADDING import chat.simplex.common.views.helpers.* import chat.simplex.common.views.newchat.QRCodeScanner import chat.simplex.res.MR @Composable -expect fun ScanProtocolServer(rhId: Long?, onNext: (ServerCfg) -> Unit) +expect fun ScanProtocolServer(rhId: Long?, onNext: (UserServer) -> Unit) @Composable -fun ScanProtocolServerLayout(rhId: Long?, onNext: (ServerCfg) -> Unit) { +fun ScanProtocolServerLayout(rhId: Long?, onNext: (UserServer) -> Unit) { ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.smp_servers_scan_qr)) QRCodeScanner { text -> val res = parseServerAddress(text) if (res != null) { - onNext(ServerCfg(remoteHostId = rhId, text, false, null, false)) + onNext(UserServer(remoteHostId = rhId, null, text, false, null, false, false)) } else { AlertManager.shared.showAlertMsg( title = generalGetString(MR.strings.smp_servers_invalid_address), diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 7236b22563..3c1ede1d23 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -109,6 +109,16 @@ Invalid display name! This display name is invalid. Please choose another name. Error switching profile! + Error saving servers + No message servers. + No servers to receive messages. + No servers for private message routing. + No media & file servers. + No servers to send files. + No servers to receive files. + For chat profile %s: + Errors in servers configuration. + Error accepting conditions Connection timeout @@ -750,6 +760,7 @@ Some servers failed the test: Scan server QR code Enter server manually + New server Preset server Your server Your server address @@ -1038,6 +1049,19 @@ Random passphrase is stored in settings as plaintext.\nYou can change it later. Use random passphrase + + Choose operators + Network operators + When more than one network operator is enabled, the app will use the servers of different operators for each conversation. + For example, if you receive messages via SimpleX Chat server, the app will use one of Flux servers for private routing. + Select network operators to use. + You can configure servers via settings. + Conditions will be accepted for enabled operators after 30 days. + You can configure operators in Network & servers settings. + Review later + Update + Continue + Incoming video call Incoming audio call @@ -1667,6 +1691,59 @@ Save group profile Error saving group profile + + Preset servers + Review conditions + Accepted conditions + Conditions will be automatically accepted for enabled operators on: %s. + Your servers + %s
.]]>
+ %s
.]]> + + + Operator + %s servers + Network operator + Website + Conditions accepted on: %s. + Conditions will be accepted on: %s. + Operator + Use servers + Use %s + Current conditions text couldn\'t be loaded, you can review conditions via this link: + %s
.]]> + %s
.]]> + %s
.]]> + %s
.]]> + %s
.]]> + %s
.]]> + %s
.]]> + %s

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 literal 0 HcmV?d00001 diff --git a/blog/images/20241014-profiles2.png b/blog/images/20241014-profiles2.png new file mode 100644 index 0000000000000000000000000000000000000000..91b18ca4af54f5957f4d53947381ca76f8068151 GIT binary patch literal 368997 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 z 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≈WJGE*CIX&^jrjKiOE!8>7=Lmwl87Wlr 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

.]]> + View conditions + Accept conditions + Conditions of use + %s
, accept conditions of use.]]> + Use for messages + To receive + For private routing + Added message servers + Use for files + To send + The servers for new files of your current chat profile + Added media & file servers + Open conditions + Open changes + + + Error updating server + Server protocol changed. + Server operator changed. + + + Operator server + Server added to operator %s. + Error adding server + TCP connection Reset to defaults @@ -2059,6 +2136,13 @@ Better message dates. Forward up to 20 messages at once. Delete or moderate up to 200 messages. + Network decentralization + The second preset operator in the app! + Enable flux + for better metadata privacy + Improved chat navigation + - Open chat on the first unread message.\n- Jump to quoted messages. + View updated conditions seconds diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/flux_logo@4x.png b/apps/multiplatform/common/src/commonMain/resources/MR/images/flux_logo@4x.png new file mode 100644 index 0000000000000000000000000000000000000000..87f1373d750bece6fbbd37fcdb4b115deaaeffbe GIT binary patch literal 34876 zcmaHT1yt1A7cSt?p|na$mw+HJNGc`W-5>%|Lxc1vpu`|0B`qKzASfj{A|WB7(lvx2 z-CggD-opQT?=IH5>)thUe&@IMK6`)r+uxa|8fpr}1Xl?#FffP}@5*XnU_hW47?>J( zIN<*n#e8M}|AM&RQPjZ$|M=tGdxC+%h@mKZTgS(2IUP6IxId*SC^LUJ1KHL<#*ecc zyMFf_cAP5zwd_z0EF2mB=ZwmV7w+6;Obb>tWW1K}>`oF+yoL%fYuFp)xq+iN~=y4b^+J)p$ z=3M`IxQAtWW~^I%A~;;di}B7YqMqUBD<3gI?rj&D5&<6kXBg-Iq@Uv7eYz#s(2gIK zcIbQ-ax$p)HCTnhA_tGH$ch2a?aW(EujXfNnojv$2%)ac?bRULkWiZ95o(^Mm1b35 zu7tclSH!@dRSbqRt6Ti2HL=mUS?d?tAsebf(I22wg-@&-MIJKW8+#{-@Xe)X*oLhi zFy6e$B`2g-v-80fWHrukw;g1W#yaf{b zm$dJ>OG6N#!07J&fjRPXEg{*_^&4?dZvXEBJmguZH&LBZgzk58-y1dZd}*P$CC6BI zR8SY^QK6%osUKS#BK>1*J3U)OSqnQp?oTsc0pI6%G^PF`oddEh=D?_&hemt3kU^oyqYA@g`>EIKqmA}a(SQ04M1lq^ z!3U!?e{z01w56!1IwK!59jA5vc)fGLD#gd935&0ITJrXffD~D~JpIM{>HqV5dGLJa z7fK95s*<_(Fk2zDOMUji^q6<^a7QgZF_39661JLzj8WUQJlZ<@{w4$Vhm*j6ZZb&Ng_-df)u>0e|1s|4{&nF33+Nx; zCtD}RBp{(iYzDiV7mmEET;#z=X<@Rxeuc=+(wO)x#Ml`a;aFzn}~pZ7F%<@#iaZL||YmXzmk?(njPx zl5UU5e#j-uMG*PHl;sT_X1`kKSZ1inTgE>AOvgW;n>7Yr84BGvv|GG{8OQ{;rc>$C z`5`a8qezTO?KX`KN-$npPC6gsS^gl>p!`RQ<+HAo5nYU%qPSmgVB`DCq7v2mi>a4M z11SVqm3jUrylg>u;S|)ZZ6E9y4g2CUW(p;TWh-XsV-eJcsh!Brt#*j#F#Tav%qCz| ze*WQx#?!Xu#i9Xjxph-%F5;IKcgZ>;gRu-X$G)bNG+H6YcO14xx?BI}nV8_2RV0de zlBu_}8Jj4O9L6$&m+^pB=a-!24Wti}BWNxl6C&M*a~{9M8a~Fj4#N30PUKkxTgKPce`ZvYfZj&rt9w z`u0as3ZE`eB|VHoKk-v}x!w6KVq#FxK1W~%A*}GpsSm=?A4=1#AU*dX4jN#qfT+V? z=Vr`oM@qwl>CQDB;UIybpTby0O%APDYkZ1PlXqDD$IAD?Hyc!J*WRY>zV2MSzyxjX zk-6u@Vi6ZylrLALrrIuZCEoXP+W9aFhM=>0--QNLRDKzARyCf6K9R*LE2jA+jd3Cf zuL}EB=RG+oNBH?~HG4xipprsg8ydIdRXvrL=&(e;d4Ibv!=k~0sT=UFLi@90Dy8#v z0gCGQKXQ0*3HWAJ%N1}$idHT@x!a-FNhFUI$dqO9g7U=(?Zu|Kf~q0HKQcl!2z7sG zlf_NnMvOt3mrd0Qor{Xi_*f$(kicYwdL!v2PbOyf|H;KJkWMq$GHH7C62@HTQ-N&hEx5%tFvzBtPhgln?z|tLd2IS-%J@s7+>(XV_v5Pi-s9Q?^>FK z=y=VfR9CT+m+~Lgl?}^=3#tR}%jd>Ew0@#fW6$_H6e6xDT}j@oyD+eRW>GbJDkSwk z9^(k9Ag4tZEm$IEKxv^Ra;CFR%tc(Fk;4jN>6?*d`C?#}2YH3sf` zud6&!5dS;Ulv|@CuFGjbM0`7n%8rlz@bnKz=N;IxyPlh)u5?DY1-xq>xp}u)9Ws&V zh*}?{VdvY`)?N<%<4frfe?I7)a*svB5F;9C{Zh+w;hePP^JoD}?^o zAN8g6O4O^&zHMy$duKe)Zu467kloD^$LBB-BZWHOw%Q!qgfy4_fhk@vqKuKG5%~x` zWG>csC}x}}NUaaqvYPcmKieS`R@vGAcnTT+?dBvn^JRyKr}tgL?}p;rMJZyjJd?uI z<_Du2F*YehDFZv^!*DE#a z!OWjVn|5M6vwlK{j0gCfsAe}zLv+ei*_o1tuf&!A zVXMyYV0LVUfSW?s8EdNj+dir(W{Kn7qofvL|KQ)_?i-b6dJY4EYU6=%T@{1Zp6!5I1aMWVf9$9PH0#q*}Zz&Bt+aUySU4tN+ydm``-4OgEL0t1W7 zON(V~R(dcU*R&JZ*8X89RuTdUX9-nzX+`$VO@!2t&pvvmqg-4m&I zdUUZM?QG~w@jO58*XA2orIfVU23(jHVb(0k9f=Sx2E#`#{x;D2y{6&$)xxzE@>D;F zFtO>oKDMymijOu_A;)Xe62Zhv``oh6qQQe1a{t>#2X$ohw{NEwH+QH*9-ghh;6X2E zVl$JcKi2&9au%V%+}H~IBNUWih27?1CF4-)=l?iWIe8UNS9XEaJ z_ww!#6IN)OYC!c_)_cz~tXCp~0?c)Z%^^qMr`x}^Ta}duNEO!kJJFN|S+LF>J3KNT zwKNUGd$kyMy}wR&;=R)cc^S^j18Dd}B7L907@$Oh*1Jf{M2f-KUv)K^8O4PpjbgRM zpH*>R5vZWSkD@V)R#vBpX5M8;@b=xBuxssIH4KwmbN=MVC*qTYu{N{5x-41r0^V{Y z$dg3E0e!8O3UA@!EM@+MX!sSHh2Xi8sqZ-$Ymg?zQOm+uCk|LOsD0Ap(;ri>ig=DW z?o;|~Gc3VoWVp*$Pe`gOKRqNO)#&dMeA*!t_CfHqCD?2jreM7jsPnILUZUP8MJ)Y3 z_1&Z#$HsZlLjCPpN4Qyhm|8a024QZTn53weXS8rab^6fk3*L>kxnC-bg}tY%*auLn zwgXwY@q6wRoqgO#6dm`4U?1ThXKT$0Q#=Fj1j8Q{Ai2>`QHwbD23fOk>t1*2i;IJ& zx0M*{(j4Mr$L}y!3}PJ5c9$pbr<}GaJ@odZ3G_T8ICi2hv*X7ZmI%>zT99r*V}e!A zr-=pt5?O|l4c3HUV2Af}0;{vOdj1a4IlKfD*yEf+DVcR$_v^socu}+_p~?l(ZGE^a ziw2@+MnmvSVcwQe?&@35?K%?+yzS|4xpm%O;1#x)UxdAJ@+63HDCcj6CL?S%0+agAcjhrDD&cX$jiZwU%@a zgSWJ|>=-(L<#CnHZ=Vc#!}8$+*(Rl%n3wHKh2(iDLr|+Hu0Nv6@m6kR>9B4m@1^S+ zZ+xpun))$EHQ3(2jk3 zWutc|2d(!{i`fhmY-@f^b!}$EPoI%pe1C%!vp*;=>9JH~?T0<=I{()cw7d+Lx{i|3 z4s8LJ`tK3iAN*o*Q%D=4pX%^5Hl0nwl=BIlV9ru$^r_IqL96DfZUwN2-~PCDajah} zkCY@R=bY(y=Ckq6{-Ze9;VueGb!{LF5eetQuvrs%W8w9zWC(#hSyr2ga?~;1V>(t~3`!9YGFm1Izfi3OBG?+F zfDsQX>1|T?68;Ku2+pAH^mJh^Y&wYKc60f+8Cl!R+5O2u;c5N7vgWTsPM_KC3mfCV zQsRlXyE}9>0qF<`2um6z908SJU=oWzHr<$mQ;@&(vXuFPg$R0mMXW?h_<{mSi*I|h zwCNc^e4z9~=Ec^+HP#sRpuv5Q;;ys<-#Q!t0g>=c?n2(?P^fL2TyU zJVHl&#QBIv;2|__`Vz|q+LaQ^3oD``kBC3)sndk^MsYM1Yfp?0g{09NJykfXB`IR# z6s*F>DthR^Mk+cf>)6JF0L`51am}bGNM(A4+UHLHWop`bgYRSODhl*cML7E1mm?ps z4Wy|3INT>ZYq%mvlVo)|nr3RwO*n?Y;G^|iaRofeik-nw0Xla}ke%OT8`^)zG>qYC z4k58CZ2YgPuqH+ADb&&hAec>N?=%_TiJnzwF9jlWmqrPrnw=2j9j4It3j3G3DNYAE zT=yl|U!q`r{02&8=rl-w>AazH(TO~j>e84)slR+xytJG&?2uGuqi06J%A(|KZI~lr ze4vVFHT5lyw`3)b@yLF9tp-AERlA>1;AzKowYhtx6Ch2kKlTC5ZFs+;=6O4i|FXju z6O%3zf`k*OeK}e{K)VwUR>Xw0ys=4zHlW!c}w9<6H zz%A^y`uR(tcG&bNd-O-t`%bGC*RujV$BER|74_2FR-Yfd^IUJJC>3L@hkX}f&PKs{ zI1KJG{PGg%c`uFrjdyC}uGDH^;jU^&GN>~|Hni<>--PTLvbZui)Y5}aSw7+Yc0O7nYV)AeVTb4vGT{m&tTHN8^4 zxaN8hqm8$I&n^4vj3c5xu6U86LUX-hd3Z_w-{C*R4b3S`VXFCRQXBQ+V#uiw=8Lm_ z(&WHdn2!F)twm5ujw%hY=}(W0#?mP@S`uq_$yyV7cUFB@r*EH(N*Z9rn)W1ib?D(e!C((`#^V+Dr3fRQmY~r~7A?=Wac6f9@E5v& zvLoT)u^C9hp1hYV{m3y zMC^UPClehS_s@q$6&^w&$7PQuINIfDhaFhk%>^P)Qo8uwcIM|9>cUMkQBFP^$!5M) zLqxT{^N!M|(<1HiM#^D{0}2KEHYKFy)z$7)DZoD#cZ0M19|Y-0N{h7H^d~)dR4Z_$ zmL2P)=mKm`$&sg% z5>)^ZI9+%9`iQ|6kHh1DjqawUqLsM0`wY=$%v(T061`X;U_+M?x^rbjy6@ivBrAV< z6AcG8W)`gb-ppfs*til>t>?IKINaIQb6s+&yJ6+kA~x7E)t=tZ-cAnav8cY3mmJd4 zv*^5;

cLJUc}gM`_%#u~JO@{)0uO?81!?0S1^z_z)9>FP{03p{d>l+l@MI)B1UE z5pUyqTT@C(UlWE)gSQzLpJX;msN|C#hvDTQnd zeaP8iCv3ZHN`mHx8<$&I+{w(_wW+S!9c$m2#!0@7_y*dKBc-@{m3S>!!U9TS zNEoEc?DOf8f)xrc|E`Qh>j>7C+-$yGGyA>ove@fX{?3Hv!;81p)57(qvYX1V%jWsL z@XQq3TX3&Ler^^6G(0MMod6vc@j2(>n8N_367e}x>)P;qccR`z?B2h{-^urp#U2B> z4=DfLjwTVaX=!&`F+yH1XM z&;sY}|_>`shNJ zPH{dGZkuO6vSru z3aWnMp8F7XgZ71RzPlDva$qMGycFgn2igBB!=i?-Z%LfLFNZ|5s*!$H}_@4oFDp7(jU#= zdNo&>7+J@@?s1D=G8NWMzMhW4pt?Ujr8xKL#-mB_I{QvJ%i+H;S5^XKlZrOm{i~n7 zZ99`>gXZy2TTBs>Qm}10>#1ieuJz*zwr6G)_)?jbL|c;+CIhq)PLg{Z5A}XzVMh~} zQPD&m`LRnQk>q!8w=G$0ow&Bk zQ-X2kL$4;xAs;?joTOI}W_!7E;dH#rW6kwDkalCH%Puc(W#aaHiV|#uQcQ|lvfvDC zW)Nq_)ZJTgvhSKMZF-GNS1)NB@H%y7UAxVpSCDiws?}tn12mup=rnPKlKSuk$e;(9 zxVeRFq$SizSw(4Am0P5r8#p5vWI%l;3b8Th1*eW* zN`Qc5D{4GB1U?gA_5vAgNTRW|QBW;<|H>(8BuvC*!mpTaj`qhbf&*^$|#$B z`n)jT&=*m>W{T)0o9K;ghh06m45NQXU;xSj#!cr9R&f5etn*O(y+hGgIG|oHIvc;u z58859ls#>33K}h{kpS*lYTGjCOx;bt5d_D%m4t2515`)Q8&$X>2>VR~4Y$Gl){vvz5n#!`uDn>Z{@}+=}>J}>7adm>*2`ypMVrbN5%BA|!LWTO0k3%cf z@jKTmW`^HS_9kbf#4b;!-MZR>Q@cduID!UbujOaHIeZyF9*I5_Qz|0sV!2Aj3w3Vf z34ymHS=(r1qsuymQHC)GbQ3Zc|AT^d{NmTWJy#5dca_)n=NMX)1KbiKA2455onT^` zr=ZvO5`FtFAlre3%YxawX5I@4;)YD|M$0#anI|W>2bHZAYutO}F$$;U zBqH5rPj{{#oO}fNvB*`|k(MOYVEz{37njj%6xQ11%kb@UOW$ru2O>bQ@$Bj#@n>9Y zP1wH06GtQ!Xp*WdmWA-J!+qPFyTL4JOQ&;MlH_ME;QQ3ae1y|q6`sE6UX63g*?t^*?sv7* z*b2i8m{|K@dKZ3>3zE)n+igoGYP?%$VtklJV{5CRT00ny1?7pyYqC%fS8q+uNzC3T z)W^X0D_WI|(ETZvNJqA~gb-Jr?0i)I-2{_li#*H9Pjo>l9Q7eyp>`D!YIQVys*d;i zEb9VDC&N3lVWyd>Tu9M&M6-zcWqbX*qtmm2fm@)#pUSYEe0x2A?}9xSU5sq~Wcbl7 z&=*ds7--s~Gxj`Sg8tf|1m)ypd$vI>{_r8|(-Yxs)fOpkc=>D()-V$0dMSl&`^ahif0 zh1|>!RsGR`^kKky-&uL_({;I4e>BwHFU{*{Fni9BWttbLnPZ7*IHU~Fky%F6oGEba zoUOvYdrNc!fmfuSPaZpSm_HRm+1TKH!Oe_q!eZfRu4JAZC_5^$rMvANk!ws3w_*Ng z-}_o|tswej07^3qj@8jB555!Cnw;G@sWami>$CZ=+&DkrHQs}~m=#eh@N`|J;!P6p zSA+PKo;r|nW?M*DN?QOZ-{e9l%VZTfN)C{_FIWY$L-ebzfC_Y zk_b@mD-8D8>MpTjT~EzX-b!MZiTY|jbGpkBtYx^fR%Kl-@q})yU24>w(kfO z7ui=u)%VF_w<2-6Fc^T~24%?@f+lmjGa-LhMCPVK2%vB@MkUQ;kLTNDEgI3hzBvDf@>gSb4t%n9ul z^s>ewKpYncY`i`oPNYn)9gNib(KJdUc1u^??Aa6X<>&`TrEb&1gl#(at6yH5ZWN*3 zHVus7)^Sy4BW=ujc1eN&F*d5r#j@^o?e1g1+a z`FCtg)L!U{*BLLiq@9DzI$BZnzG*~~t9!goGwmTM6RvM#(h7~n&Ag_wv*H`j@T;nMM0$1YD#`pExGmzg1>~)T=tHX>sbaU-^Tjl z*`sz@`x_9Boa~kg8PQ<3fA*pD;UT}6X-DCI1sgQoxt4GH%>W~jEX4?N$CbJp7qAtk zv^isrpxd?!va=B%I1|0*U;aGiq>KwFeWyE=DN!84oJJmhW~4Hkk%>Z`(2Tp~p*ZGh zM610gXiAcYYbYX<>W)CDKBe_VF`Bq7+C|annc6)u+tY-3a6^4AhDR4f!&~H&iGJ9l z(^dOF>54*xvI(S8X{rd$6xsdI>ARv$ejm2KKS?FHC(Hl+V0lh}3uekj-}r8bXq(WjKLtp)@;~LIF>L+U!TCLJ*rs*L5Bc@hcR~!>L)$>+SHr1 zWKGymdt7Y2DmpNa|FQRU0avrCB~i^vNkK{fT* zy74~Z8gO2bg>AfEe;`)jb>#VE&jlPb*P0hkvI)dDrXQM|p?;0oWR(WzlEoXP0>uM( z?)VXLyq70HtdefhBXVK}+A%qx%hsRnPN0E(VBp_A50}v9j68x?Y_51%x2!E+yU(k% zIuVUg5kwxM8kA>Ws#hGVn{61|Lj92@qxyY8ndi6^5_u%@aJDV;)wbu$7f168tZ6Xa z8G(Bf!^Zc}bogM*K??=#ZLLIR_@n?X}$};fr zKYIw!-Z%-MjSzmdnrR>PawYe^3(Mq0U3PK`XdElz2h`w?_FT3RN${4>Gx`Rv`8J!YSycx{Z90E zLL%`iAd6yaVrFV87b)_~DO}8{GJQZZQSa-b-3oY+bZk}o%{LK- zi6)TY_pZI=o&ZGbwEF-GQ7~YkY&=l?0J&?{u@VW-_b(@FnW!73#IAjwOzr z8bIXGBci4QepP2M4g8H9hNOOl`C*9{_bOaPSHF6P z4GfzO|9WxY>41*qCnEFaH0#AjdKUeMn_%>&@f^)Ra^H_?&B#46B)E<26J(ocv0T90 zseJ;^RhKoc^r*b zb5UWVJprEU6BdQKhz^MPe*z*oJ>qO_=2_vL!s7v{!2g*pdz2xq#xq<+*#aTcl@88_t{pVR5`wm^7me8(zzQ{fY3mh^q+q7@81FAxuDqvqHDiRm3a83w;x0 zOaPur7Vvuqro}A;dD8=6=+)j^Tn8Md?mRubb@BE0&(xTOH#2(HFJ-O$8T8?LutFN>4#=Q36d)YPuDVUT}`XHx3T5-+|a&UeDIZ*7vzxG z%xCcB#ZGeR4O8v^Yk1k_YcZ&rQd}BCsZ^2O(+m&*J^j4S99l;~O1q*X$-c?4Zox*M zu_#+=f{1C#+kk62bXY4~Y-cS%R8=JxKV;zt!?gXDnc`S-4Ck{a80Hcl{JfGj6k%&d zx*D}+eoA?zLD)vZbwR?|#4Y)%^>*)|&wwj8W}MG{xl;KbSJoEpZzvz^ZdXLO2(f=? z5>b5z!~!=N_wyFSN!nc|h6HAV^WJ)oF6&IhfZ9|Q1{~X-CJyN~r%26e~)2w36;uKLN6||b0#$w#J z{%~ftHNVD_de0g;b<`f|m;*SK%N?xT)gZ*nnkTi_sHvK#OlvbAm0 z_Ig`EwiciH>Z6uB2Ca>awrE*4=(0{;bou;TbJ$4x1^B`%RawnEeeN-WIz;3}v+-$3 zE1jcop+wcREYWm(m5TlAjc`>6#V`KXO1q|lPzI^##cbOmfsUD=n}1sc@l~ZY#(bcf zJw^9G$vtO^v+n2NranbP{&rm01%7>{P1bd5w1_1M@1A#E*pERd5WuQ?0rY2TK19t| zeS%-w7*G9X8cj{K8d|&Fg(YEmjlPuwGL+0E7%165h7xyd8zK>%ph*$x-2IWc?zv`H zRTYpYaI4{L@uRCn%RhifNsw~#bZnaWMZ}KrXx}=T-HBus|ErHE!_XQsCW^S;{4ubf zc*KzMb2q`ssH_k7jXH=z%eIb_Es&7k4NX-2Q6J`_x!`+PJXq~L{`SWF8LgkLho-`f zz5Hh`nNYF<{{BK3gckJK1bBAEPzN0aH(}4Es62W~)Z;sQb*`CMgErGs%F3q3Nq-Y} zFTc0?TnpmNKy@@2ZJ*^C!;l-yHyoeUH9qrOsVs>G^ zw%3)!kMHcw=M3$SWlH@e*qdvL7vq5X;M}#KK_3@(z>NOJcd&My<3I*nolF&Wrgp<@ z3FpPS6WoE)p8bUhVqH7E--LS!Kg_D9iXCcIm;`|iJ$-D}YpDgBT`jZoe@N^c-40rgP|inu_?i8=akqb)-%WjY zc0x<=Y^5h(`$k|U1|K8%?C!1N<4&o7WIPSd>`_YYX79q`{9FkbxmMStw@gl-fS%NfKX;CuDbE6ZB^d& zNU;o~M|BxS(N!dSO~^LrGcuN+-3mwhBcj~uN34?y~Lyc?F8M^dma{gn@#=_av znO9$yyRe3is>^xpb#+~OSeWKr%+&@PXnxOXlsn&L6RmXIA#p~8o4>u5ymIX&xP!vN z=+lQbt-&%m?FwDQ$8Qd>B>;_%M!mH&1H6_7cLa&WR_9?`ei-{L_ zlk-RNPR`HZE%&pC<_*z(fxUpI*47JIJWk&=O3UT z|7lwF(Np&$}ezE5?+H&eP=c4$5}Wbb|LU6FphCnz899=&cAo)$2-XSL5W_r#cl zdO-m{BE4FQ@RxEXqdg}W?QTl)_uJb%+%p#27*)`p0*o&cyTtwt8EIX<4i={Bj?~1k(ziyYimF?vqr?8-Y@8`ki)bkn z$FpW(v@zAO{t3diLVqpo_2t>1h)%-Q(u(y(3OmxQ(sjJm(L$qIb2d_?_hl7y+;Qlu zD^Io$Cd{furgZ}Ju-oJLO4>ayzV`UREzaym{1eR6%gP6MH?IRt_v!?L{^!e)U+6Eq zxcZiF>v+w9#&xBE`;C8zyHxBZYlpf}UQrU5)jD)$>x=mTg>5{WwQmYK>=rct4ZBW+ z5O!`M!&4OXYFPp$*KEB?{gg1@vkrU&TKmFBu@rXoh>X^ScNCsEw&UDa79ce4uqsqk zKr2i!c`$N!)~sw~v_J>WyeFTmtWFm_LqZodMoxQ7q0YF+HT>}f7}DdvO&X&zFAQgT zWd>3g@0LbuDMuo_h0@xBlou^V4aED0ilHf}a93??jA@}^{`po3o4)&Fi}sPp&p2$d z2)442j*F&RF-u)P6=9Bc3m_M(Hkt!lli-qNJVZXd2@UGQQI(*GIYrcC18o_3HUHs8~=l&O^caFQO zG~AMcIq#P|i^Cz_!}#U#aP&xR>gzXo=Dp_>f!*YG4;{z@B^|DS`Y|Wv}Bqw6vV*U=zFfK!8p{szt@B5HX@scfxt zLBheygjnk?!lpvP<1E7wVd#4|Ub=<>6wKKMo08IMTUvd2uRZ;cu}5ynN1~n6Ka7

PL9g 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 F{C_Q?zn%a9 literal 0 HcmV?d00001 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/flux_logo_light@4x.png b/apps/multiplatform/common/src/commonMain/resources/MR/images/flux_logo_light@4x.png new file mode 100644 index 0000000000000000000000000000000000000000..e1d6dda4fedd57195f0ee425a0a28a579f16b266 GIT binary patch literal 33847 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�>Y9E5G~yEbTaSm_@QckJ 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 E0Eh^ \ No newline at end of file diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.desktop.kt deleted file mode 100644 index 2d436dbbf0..0000000000 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.desktop.kt +++ /dev/null @@ -1,9 +0,0 @@ -package chat.simplex.common.views.usersettings - -import androidx.compose.runtime.Composable -import chat.simplex.common.model.ServerCfg - -@Composable -actual fun ScanProtocolServer(rhId: Long?, onNext: (ServerCfg) -> Unit) { - ScanProtocolServerLayout(rhId, onNext) -} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ScanProtocolServer.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ScanProtocolServer.desktop.kt new file mode 100644 index 0000000000..7d6f305a83 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ScanProtocolServer.desktop.kt @@ -0,0 +1,9 @@ +package chat.simplex.common.views.usersettings.networkAndServers + +import androidx.compose.runtime.Composable +import chat.simplex.common.model.UserServer + +@Composable +actual fun ScanProtocolServer(rhId: Long?, onNext: (UserServer) -> Unit) { + ScanProtocolServerLayout(rhId, onNext) +} From b5170684adf312b914d95a403099f92e228b8ea7 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 22 Nov 2024 19:21:21 +0400 Subject: [PATCH 099/567] android: fix single operator conditions paddings --- .../views/usersettings/networkAndServers/OperatorView.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt index cb02745511..df6122024f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt @@ -556,7 +556,7 @@ private fun SingleOperatorUsageConditionsView( AppBarTitle(String.format(stringResource(MR.strings.use_servers_of_operator_x), operator.tradeName), enableAlphaChanges = false, withPadding = false) if (operator.conditionsAcceptance is ConditionsAcceptance.Accepted) { // In current UI implementation this branch doesn't get shown - as conditions can't be opened from inside operator once accepted - Column(modifier = Modifier.weight(1f).padding(end = DEFAULT_PADDING, start = DEFAULT_PADDING, bottom = DEFAULT_PADDING)) { + Column(modifier = Modifier.weight(1f).padding(top = DEFAULT_PADDING_HALF, bottom = DEFAULT_PADDING)) { ConditionsTextView(rhId) } } else if (operatorsWithConditionsAccepted.isNotEmpty()) { @@ -579,7 +579,7 @@ private fun SingleOperatorUsageConditionsView( args = operator.legalName_ ) ConditionsAppliedToOtherOperatorsText(userServers = userServers.value, operatorIndex = operatorIndex) - Column(modifier = Modifier.weight(1f).padding(end = DEFAULT_PADDING, start = DEFAULT_PADDING, bottom = DEFAULT_PADDING)) { + Column(modifier = Modifier.weight(1f).padding(top = DEFAULT_PADDING_HALF, bottom = DEFAULT_PADDING)) { ConditionsTextView(rhId) } AcceptConditionsButton(close) From 2adfa0c18b877457c2f68b295ef88e292035a338 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 22 Nov 2024 19:33:49 +0400 Subject: [PATCH 100/567] android: information icon right of operator logo --- .../views/usersettings/networkAndServers/OperatorView.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt index df6122024f..121b6535c3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt @@ -141,7 +141,14 @@ fun OperatorViewLayout( Column { SectionView(generalGetString(MR.strings.operator).uppercase()) { SectionItemView({ ModalManager.start.showModalCloseable { _ -> OperatorInfoView(operator) } }) { - Image(painterResource(operator.largeLogo), null, Modifier.height(48.dp)) + Row( + Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Image(painterResource(operator.largeLogo), null, Modifier.height(48.dp)) + Spacer(Modifier.fillMaxWidth().weight(1f)) + Icon(painterResource(MR.images.ic_info), null, Modifier.size(28.dp), tint = MaterialTheme.colors.primary) + } } UseOperatorToggle( scope = scope, From bff2d7d3b6230d138e14f85e2381a66c5f2fd484 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 22 Nov 2024 22:34:43 +0700 Subject: [PATCH 101/567] android, desktop: highlight quoted messaged on click to scroll to it (#5229) --- .../simplex/common/views/chat/ChatView.kt | 25 ++++++++++++++++--- .../common/views/chat/item/ChatItemView.kt | 16 +++++++++++- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 8dd3e42440..2aee98acba 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -980,8 +980,9 @@ fun BoxScope.ChatItemsList( } val chatInfoUpdated = rememberUpdatedState(chatInfo) + val highlightedItems = remember { mutableStateOf(setOf()) } val scope = rememberCoroutineScope() - val scrollToItem: (Long) -> Unit = remember { scrollToItem(loadingMoreItems, chatInfoUpdated, maxHeight, scope, reversedChatItems, mergedItems, listState, loadMessages) } + val scrollToItem: (Long) -> Unit = remember { scrollToItem(loadingMoreItems, highlightedItems, chatInfoUpdated, maxHeight, scope, reversedChatItems, mergedItems, listState, loadMessages) } LoadLastItems(loadingMoreItems, remoteHostId, chatInfo) SmallScrollOnNewMessage(listState, chatModel.chatItems) @@ -1031,7 +1032,17 @@ fun BoxScope.ChatItemsList( tryOrShowError("${cItem.id}ChatItem", error = { CIBrokenComposableView(if (cItem.chatDir.sent) Alignment.CenterEnd else Alignment.CenterStart) }) { - ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, range = range, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, reveal = reveal, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp) + val highlighted = remember { derivedStateOf { highlightedItems.value.contains(cItem.id) } } + LaunchedEffect(Unit) { + snapshotFlow { highlighted.value } + .distinctUntilChanged() + .filter { it } + .collect { + delay(500) + highlightedItems.value = setOf() + } + } + ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, highlighted = highlighted, range = range, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, reveal = reveal, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp) } } @@ -1810,6 +1821,7 @@ private fun lastFullyVisibleIemInListState(topPaddingToContentPx: State, de private fun scrollToItem( loadingMoreItems: MutableState, + highlightedItems: MutableState>, chatInfo: State, maxHeight: State, scope: CoroutineScope, @@ -1840,8 +1852,13 @@ private fun scrollToItem( index = mergedItems.value.indexInParentItems[itemId] ?: -1 } if (index != -1) { - withContext(scope.coroutineContext) { - listState.value.animateScrollToItem(min(reversedChatItems.value.lastIndex, index + 1), -maxHeight.value) + if (listState.value.layoutInfo.visibleItemsInfo.any { it.index == index && it.offset + it.size <= maxHeight.value }) { + highlightedItems.value = setOf(itemId) + } else { + withContext(scope.coroutineContext) { + listState.value.animateScrollToItem(min(reversedChatItems.value.lastIndex, index + 1), -maxHeight.value) + highlightedItems.value = setOf(itemId) + } } } } finally { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index 5096992c29..f0c85736af 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -2,6 +2,8 @@ package chat.simplex.common.views.chat.item import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.* +import androidx.compose.foundation.interaction.HoverInteraction +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.* import androidx.compose.material.* @@ -57,6 +59,7 @@ fun ChatItemView( useLinkPreviews: Boolean, linkMode: SimplexLinkMode, revealed: State, + highlighted: State, range: State, selectedChatItems: MutableState?>, fillMaxWidth: Boolean = true, @@ -135,10 +138,19 @@ fun ChatItemView( } Column(horizontalAlignment = if (cItem.chatDir.sent) Alignment.End else Alignment.Start) { + val interactionSource = remember { MutableInteractionSource() } + val enterInteraction = remember { HoverInteraction.Enter() } + KeyChangeEffect(highlighted.value) { + if (highlighted.value) { + interactionSource.emit(enterInteraction) + } else { + interactionSource.emit(HoverInteraction.Exit(enterInteraction)) + } + } Column( Modifier .clipChatItem(cItem, itemSeparation.largeGap, revealed.value) - .combinedClickable(onLongClick = { showMenu.value = true }, onClick = onClick) + .combinedClickable(onLongClick = { showMenu.value = true }, onClick = onClick, interactionSource = interactionSource, indication = LocalIndication.current) .onRightClick { showMenu.value = true }, ) { @Composable @@ -1064,6 +1076,7 @@ fun PreviewChatItemView( linkMode = SimplexLinkMode.DESCRIPTION, composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, revealed = remember { mutableStateOf(false) }, + highlighted = remember { mutableStateOf(false) }, range = remember { mutableStateOf(0..1) }, selectedChatItems = remember { mutableStateOf(setOf()) }, selectChatItem = {}, @@ -1106,6 +1119,7 @@ fun PreviewChatItemViewDeletedContent() { linkMode = SimplexLinkMode.DESCRIPTION, composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, revealed = remember { mutableStateOf(false) }, + highlighted = remember { mutableStateOf(false) }, range = remember { mutableStateOf(0..1) }, selectedChatItems = remember { mutableStateOf(setOf()) }, selectChatItem = {}, From e47b16f3b4b876bc4ab88ba954fb3de1d633876a Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 22 Nov 2024 19:40:41 +0400 Subject: [PATCH 102/567] android: improve layout of operator logo --- .../views/usersettings/networkAndServers/OperatorView.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt index 121b6535c3..6f90836e89 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt @@ -147,11 +147,12 @@ fun OperatorViewLayout( ) { Image(painterResource(operator.largeLogo), null, Modifier.height(48.dp)) Spacer(Modifier.fillMaxWidth().weight(1f)) - Icon(painterResource(MR.images.ic_info), null, Modifier.size(28.dp), tint = MaterialTheme.colors.primary) + Box(Modifier.padding(horizontal = 2.dp)) { + Icon(painterResource(MR.images.ic_info), null, Modifier.size(28.dp), tint = MaterialTheme.colors.primary) + } } } UseOperatorToggle( - scope = scope, currUserServers = currUserServers, userServers = userServers, serverErrors = serverErrors, @@ -436,7 +437,6 @@ private fun OperatorInfoView(serverOperator: ServerOperator) { @Composable private fun UseOperatorToggle( - scope: CoroutineScope, currUserServers: MutableState>, userServers: MutableState>, serverErrors: MutableState>, From a6f5ba541b88f4479dd62761b5ebff40c6556e9f Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 22 Nov 2024 16:43:10 +0000 Subject: [PATCH 103/567] android, desktop: smaller info icon, corrections --- .../kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt | 2 +- .../common/views/usersettings/networkAndServers/OperatorView.kt | 2 +- .../common/src/commonMain/resources/MR/base/strings.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt index 6cf945bcba..e20a56c407 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt @@ -724,7 +724,7 @@ private val versionDescriptions: List = listOf( ), ), VersionDescription( - version = "v6.2 (beta.1)", + version = "v6.2-beta.1", post = "https://simplex.chat/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html", features = listOf( VersionFeature.FeatureView( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt index 6f90836e89..c61a9f5ef7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt @@ -148,7 +148,7 @@ fun OperatorViewLayout( Image(painterResource(operator.largeLogo), null, Modifier.height(48.dp)) Spacer(Modifier.fillMaxWidth().weight(1f)) Box(Modifier.padding(horizontal = 2.dp)) { - Icon(painterResource(MR.images.ic_info), null, Modifier.size(28.dp), tint = MaterialTheme.colors.primary) + Icon(painterResource(MR.images.ic_info), null, Modifier.size(24.dp), tint = MaterialTheme.colors.primaryVariant) } } } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 3c1ede1d23..8b379d8212 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -2139,7 +2139,7 @@ Network decentralization The second preset operator in the app! Enable flux - for better metadata privacy + for better metadata privacy. Improved chat navigation - Open chat on the first unread message.\n- Jump to quoted messages. View updated conditions From 76aedb4a15f8ab3dd62b47146ad6e85744369868 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 22 Nov 2024 17:21:05 +0000 Subject: [PATCH 104/567] core: update simplexmq --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cabal.project b/cabal.project index cfa9099517..793fc18952 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 5e6fa7fb94ca54e3d6d68aa83e403f1182197081 + tag: 97104988a307bd27b8bf5da7ed67455f3531d7ae source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index a92a2b5ca5..e3985379d0 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."5e6fa7fb94ca54e3d6d68aa83e403f1182197081" = "1jwrk60nw9h8f5zxbcj57ybqdnfmchq65c07xybifcycid0016l3"; + "https://github.com/simplex-chat/simplexmq.git"."97104988a307bd27b8bf5da7ed67455f3531d7ae" = "1xhk8cg4338d0cfjhdm2460p6nbvxfra80qnab2607nvy8wpddvl"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; From 9b71702ac8c07d985c1fe83b2c3e56e64944dc0f Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 22 Nov 2024 18:19:49 +0000 Subject: [PATCH 105/567] ios: move onboarding action cards, paddings (#5231) --- .../Shared/Views/ChatList/ChatListView.swift | 26 ++++++++++--------- .../Shared/Views/ChatList/OneHandUICard.swift | 1 - .../Onboarding/AddressCreationCard.swift | 1 - 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 6da17fb312..b18e9295b9 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -304,18 +304,6 @@ struct ChatListView: View { .padding(.top, oneHandUI ? 8 : 0) .id("searchBar") } - if !oneHandUICardShown { - OneHandUICard() - .scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center) - .listRowSeparator(.hidden) - .listRowBackground(Color.clear) - } - if !addressCreationCardShown { - AddressCreationCard() - .scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center) - .listRowSeparator(.hidden) - .listRowBackground(Color.clear) - } if #available(iOS 16.0, *) { ForEach(cs, id: \.viewId) { chat in ChatListNavLink(chat: chat) @@ -341,6 +329,20 @@ struct ChatListView: View { .disabled(chatModel.chatRunning != true || chatModel.deletedChats.contains(chat.chatInfo.id)) } } + if !oneHandUICardShown { + OneHandUICard() + .padding(.vertical, 6) + .scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center) + .listRowSeparator(.hidden) + .listRowBackground(Color.clear) + } + if !addressCreationCardShown { + AddressCreationCard() + .padding(.vertical, 6) + .scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center) + .listRowSeparator(.hidden) + .listRowBackground(Color.clear) + } } .listStyle(.plain) .onChange(of: chatModel.chatId) { currentChatId in diff --git a/apps/ios/Shared/Views/ChatList/OneHandUICard.swift b/apps/ios/Shared/Views/ChatList/OneHandUICard.swift index 636d165114..059f24cc82 100644 --- a/apps/ios/Shared/Views/ChatList/OneHandUICard.swift +++ b/apps/ios/Shared/Views/ChatList/OneHandUICard.swift @@ -32,7 +32,6 @@ struct OneHandUICard: View { .background(theme.appColors.sentMessage) .cornerRadius(12) .frame(height: dynamicSize(userFont).rowHeight) - .padding(.vertical, 12) .alert(isPresented: $showOneHandUIAlert) { Alert( title: Text("Reachable chat toolbar"), diff --git a/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift b/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift index e9a8fedaf9..eae64e4465 100644 --- a/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift +++ b/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift @@ -65,7 +65,6 @@ struct AddressCreationCard: View { .background(theme.appColors.sentMessage) .cornerRadius(12) .frame(height: dynamicSize(userFont).rowHeight) - .padding(.vertical, 12) .alert(isPresented: $showAddressCreationAlert) { Alert( title: Text("SimpleX address"), From 4f640c96d14e5d5005d8ba4694953e4c413c9ee9 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 22 Nov 2024 18:38:49 +0000 Subject: [PATCH 106/567] build: use openssl 3.0 (#5183) * build: use openssl 3.0 * docs * mac script --- .github/workflows/build.yml | 16 ++++++++-------- docs/CLI.md | 2 +- docs/CONTRIBUTING.md | 4 ++-- docs/lang/cs/CLI.md | 2 +- docs/lang/cs/CONTRIBUTING.md | 4 ++-- docs/lang/fr/CLI.md | 2 +- docs/lang/fr/CONTRIBUTING.md | 4 ++-- docs/lang/pl/CLI.md | 2 +- docs/lang/pl/CONTRIBUTING.md | 4 ++-- scripts/cabal.project.local.mac | 8 ++++---- scripts/desktop/build-lib-mac.sh | 14 +++++++------- scripts/desktop/build-lib-windows.sh | 4 ++-- scripts/desktop/prepare-openssl-windows.sh | 6 +++--- 13 files changed, 36 insertions(+), 36 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6ad4f12ef9..21fea3fe8b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -124,12 +124,12 @@ jobs: run: | echo "ignore-project: False" >> cabal.project.local echo "package simplexmq" >> cabal.project.local - echo " extra-include-dirs: /opt/homebrew/opt/openssl@1.1/include" >> cabal.project.local - echo " extra-lib-dirs: /opt/homebrew/opt/openssl@1.1/lib" >> cabal.project.local + echo " extra-include-dirs: /opt/homebrew/opt/openssl@3.0/include" >> cabal.project.local + echo " extra-lib-dirs: /opt/homebrew/opt/openssl@3.0/lib" >> cabal.project.local echo "" >> cabal.project.local echo "package direct-sqlcipher" >> cabal.project.local - echo " extra-include-dirs: /opt/homebrew/opt/openssl@1.1/include" >> cabal.project.local - echo " extra-lib-dirs: /opt/homebrew/opt/openssl@1.1/lib" >> cabal.project.local + echo " extra-include-dirs: /opt/homebrew/opt/openssl@3.0/include" >> cabal.project.local + echo " extra-lib-dirs: /opt/homebrew/opt/openssl@3.0/lib" >> cabal.project.local echo " flags: +openssl" >> cabal.project.local - name: Unix prepare cabal.project.local for Mac @@ -138,12 +138,12 @@ jobs: run: | echo "ignore-project: False" >> cabal.project.local echo "package simplexmq" >> cabal.project.local - echo " extra-include-dirs: /usr/local/opt/openssl@1.1/include" >> cabal.project.local - echo " extra-lib-dirs: /usr/local/opt/openssl@1.1/lib" >> cabal.project.local + echo " extra-include-dirs: /usr/local/opt/openssl@3.0/include" >> cabal.project.local + echo " extra-lib-dirs: /usr/local/opt/openssl@3.0/lib" >> cabal.project.local echo "" >> cabal.project.local echo "package direct-sqlcipher" >> cabal.project.local - echo " extra-include-dirs: /usr/local/opt/openssl@1.1/include" >> cabal.project.local - echo " extra-lib-dirs: /usr/local/opt/openssl@1.1/lib" >> cabal.project.local + echo " extra-include-dirs: /usr/local/opt/openssl@3.0/include" >> cabal.project.local + echo " extra-lib-dirs: /usr/local/opt/openssl@3.0/lib" >> cabal.project.local echo " flags: +openssl" >> cabal.project.local - name: Install AppImage dependencies diff --git a/docs/CLI.md b/docs/CLI.md index abc09b0e7c..6f56cf6cd3 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -134,7 +134,7 @@ cp scripts/cabal.project.local.linux cabal.project.local On Mac: ``` -brew install openssl@1.1 +brew install openssl@3.0 cp scripts/cabal.project.local.mac cabal.project.local ``` diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 493496fd3d..e7ce63ea54 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -21,9 +21,9 @@ cp scripts/cabal.project.local.mac cabal.project.local MacOS comes with LibreSSL as default, OpenSSL must be installed to compile SimpleX from source. -OpenSSL can be installed with `brew install openssl@1.1` +OpenSSL can be installed with `brew install openssl@3.0` -You will have to add `/opt/homebrew/opt/openssl@1.1/bin` to your PATH in order to have things working properly +You will have to add `/opt/homebrew/opt/openssl@3.0/bin` to your PATH in order to have things working properly ## Project branches diff --git a/docs/lang/cs/CLI.md b/docs/lang/cs/CLI.md index 338e48e57e..2477f6ea2f 100644 --- a/docs/lang/cs/CLI.md +++ b/docs/lang/cs/CLI.md @@ -117,7 +117,7 @@ git checkout stable apt-get update && apt-get install -y build-essential libgmp3-dev zlib1g-dev cp scripts/cabal.project.local.linux cabal.project.local # nebo na MacOS: -# brew install openssl@1.1 +# brew install openssl@3.0 # cp scripts/cabal.project.local.mac cabal.project.local # možná budete muset změnit cabal.project.local tak, aby ukazoval na skutečné umístění openssl cabal update diff --git a/docs/lang/cs/CONTRIBUTING.md b/docs/lang/cs/CONTRIBUTING.md index 17574bed4a..226b4d9343 100644 --- a/docs/lang/cs/CONTRIBUTING.md +++ b/docs/lang/cs/CONTRIBUTING.md @@ -20,6 +20,6 @@ cp scripts/cabal.project.local.mac cabal.project.local Systém MacOS je standardně dodáván s LibreSSL, pro kompilaci SimpleX ze zdrojových kódů je nutné nainstalovat OpenSSL. -OpenSSL lze nainstalovat pomocí `brew install openssl@1.1`. +OpenSSL lze nainstalovat pomocí `brew install openssl@3.0`. -Aby vše fungovalo správně, musíte do své cesty PATH přidat `/opt/homebrew/opt/openssl@1.1/bin`. +Aby vše fungovalo správně, musíte do své cesty PATH přidat `/opt/homebrew/opt/openssl@3.0/bin`. diff --git a/docs/lang/fr/CLI.md b/docs/lang/fr/CLI.md index e5093f20c0..58b84a0919 100644 --- a/docs/lang/fr/CLI.md +++ b/docs/lang/fr/CLI.md @@ -119,7 +119,7 @@ git checkout stable apt-get update && apt-get install -y build-essential libgmp3-dev zlib1g-dev cp scripts/cabal.project.local.linux cabal.project.local # ou sur MacOS: -# brew install openssl@1.1 +# brew install openssl@3.0 # cp scripts/cabal.project.local.mac cabal.project.local # vous devrez peut-être modifier cabal.project.local pour indiquer l'emplacement réel d'openssl cabal update diff --git a/docs/lang/fr/CONTRIBUTING.md b/docs/lang/fr/CONTRIBUTING.md index ea6dcb5ca3..1b83fc24ce 100644 --- a/docs/lang/fr/CONTRIBUTING.md +++ b/docs/lang/fr/CONTRIBUTING.md @@ -20,6 +20,6 @@ cp scripts/cabal.project.local.mac cabal.project.local LibreSSL est fourni par défaut sur MacOS, OpenSSL doit être installé pour compiler SimpleX à partir de la source. -OpenSSL peut être installé avec `brew install openssl@1.1` +OpenSSL peut être installé avec `brew install openssl@3.0` -Vous devez ajouter `/opt/homebrew/opt/openssl@1.1/bin` à votre PATH pour que tout fonctionne correctement. +Vous devez ajouter `/opt/homebrew/opt/openssl@3.0/bin` à votre PATH pour que tout fonctionne correctement. diff --git a/docs/lang/pl/CLI.md b/docs/lang/pl/CLI.md index 0a72b163bb..bc64b04415 100644 --- a/docs/lang/pl/CLI.md +++ b/docs/lang/pl/CLI.md @@ -133,7 +133,7 @@ cp scripts/cabal.project.local.linux cabal.project.local Na Macu: ``` -brew install openssl@1.1 +brew install openssl@3.0 cp scripts/cabal.project.local.mac cabal.project.local ``` diff --git a/docs/lang/pl/CONTRIBUTING.md b/docs/lang/pl/CONTRIBUTING.md index 4f62217479..5205e3c5a6 100644 --- a/docs/lang/pl/CONTRIBUTING.md +++ b/docs/lang/pl/CONTRIBUTING.md @@ -21,9 +21,9 @@ cp scripts/cabal.project.local.mac cabal.project.local MacOS ma domyślnie zainstalowany LibreSSL, OpenSSL musi być zainstalowany, aby skompilować SimpleX z kodu źródłowego. -OpenSSL można zainstalować za pomocą `brew install openssl@1.1` +OpenSSL można zainstalować za pomocą `brew install openssl@3.0` -Będziesz musiał dodać `/opt/homebrew/opt/openssl@1.1/bin` do swojego PATH, aby wszystko działało poprawnie +Będziesz musiał dodać `/opt/homebrew/opt/openssl@3.0/bin` do swojego PATH, aby wszystko działało poprawnie ## Branche projektu diff --git a/scripts/cabal.project.local.mac b/scripts/cabal.project.local.mac index dd62f1a391..6b4ff718b6 100644 --- a/scripts/cabal.project.local.mac +++ b/scripts/cabal.project.local.mac @@ -3,12 +3,12 @@ ignore-project: False -- amend to point to the actual openssl location package simplexmq - extra-include-dirs: /opt/homebrew/opt/openssl@1.1/include - extra-lib-dirs: /opt/homebrew/opt/openssl@1.1/lib + extra-include-dirs: /opt/homebrew/opt/openssl@3.0/include + extra-lib-dirs: /opt/homebrew/opt/openssl@3.0/lib package direct-sqlcipher - extra-include-dirs: /opt/homebrew/opt/openssl@1.1/include - extra-lib-dirs: /opt/homebrew/opt/openssl@1.1/lib + extra-include-dirs: /opt/homebrew/opt/openssl@3.0/include + extra-lib-dirs: /opt/homebrew/opt/openssl@3.0/lib flags: +openssl test-show-details: direct diff --git a/scripts/desktop/build-lib-mac.sh b/scripts/desktop/build-lib-mac.sh index 9d7d5031a0..070257ea5f 100755 --- a/scripts/desktop/build-lib-mac.sh +++ b/scripts/desktop/build-lib-mac.sh @@ -100,25 +100,25 @@ cp $BUILD_DIR/build/libHSsimplex-chat-*-inplace-ghc*.$LIB_EXT apps/multiplatform cd apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/ LIBCRYPTO_PATH=$(otool -l libHSdrct-*.$LIB_EXT | grep libcrypto | cut -d' ' -f11) -install_name_tool -change $LIBCRYPTO_PATH @rpath/libcrypto.1.1.$LIB_EXT libHSdrct-*.$LIB_EXT -cp $LIBCRYPTO_PATH libcrypto.1.1.$LIB_EXT -chmod 755 libcrypto.1.1.$LIB_EXT -install_name_tool -id "libcrypto.1.1.$LIB_EXT" libcrypto.1.1.$LIB_EXT +install_name_tool -change $LIBCRYPTO_PATH @rpath/libcrypto.3.0.$LIB_EXT libHSdrct-*.$LIB_EXT +cp $LIBCRYPTO_PATH libcrypto.3.0.$LIB_EXT +chmod 755 libcrypto.3.0.$LIB_EXT +install_name_tool -id "libcrypto.3.0.$LIB_EXT" libcrypto.3.0.$LIB_EXT install_name_tool -id "libffi.8.$LIB_EXT" libffi.$LIB_EXT LIBCRYPTO_PATH=$(otool -l $LIB | grep libcrypto | cut -d' ' -f11) if [ -n "$LIBCRYPTO_PATH" ]; then - install_name_tool -change $LIBCRYPTO_PATH @rpath/libcrypto.1.1.$LIB_EXT $LIB + install_name_tool -change $LIBCRYPTO_PATH @rpath/libcrypto.3.0.$LIB_EXT $LIB fi LIBCRYPTO_PATH=$(otool -l libHSsmplxmq*.$LIB_EXT | grep libcrypto | cut -d' ' -f11) if [ -n "$LIBCRYPTO_PATH" ]; then - install_name_tool -change $LIBCRYPTO_PATH @rpath/libcrypto.1.1.$LIB_EXT libHSsmplxmq*.$LIB_EXT + install_name_tool -change $LIBCRYPTO_PATH @rpath/libcrypto.3.0.$LIB_EXT libHSsmplxmq*.$LIB_EXT fi LIBCRYPTO_PATH=$(otool -l libHSsqlcphr-*.$LIB_EXT | grep libcrypto | cut -d' ' -f11) if [ -n "$LIBCRYPTO_PATH" ]; then - install_name_tool -change $LIBCRYPTO_PATH @rpath/libcrypto.1.1.$LIB_EXT libHSsqlcphr-*.$LIB_EXT + install_name_tool -change $LIBCRYPTO_PATH @rpath/libcrypto.3.0.$LIB_EXT libHSsqlcphr-*.$LIB_EXT fi for lib in $(find . -type f -name "*.$LIB_EXT"); do diff --git a/scripts/desktop/build-lib-windows.sh b/scripts/desktop/build-lib-windows.sh index 0e96a42e86..d27d71c08f 100755 --- a/scripts/desktop/build-lib-windows.sh +++ b/scripts/desktop/build-lib-windows.sh @@ -36,7 +36,7 @@ mkdir dist-newstyle 2>/dev/null || true 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') +openssl_windows_style_path=$(echo `pwd`/dist-newstyle/openssl-3.0.15 | sed 's#/\([a-zA-Z]\)#\1:#' | sed 's#/#\\#g') rm -rf $BUILD_DIR 2>/dev/null || true # Existence of this directory produces build error: cabal's bug rm -rf dist-newstyle/src/direct-sq* 2>/dev/null || true @@ -57,7 +57,7 @@ rm -rf apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/ rm -rf apps/multiplatform/desktop/build/cmake mkdir -p apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/ -cp dist-newstyle/openssl-1.1.1w/libcrypto-1_1-x64.dll apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/ +cp dist-newstyle/openssl-3.0.15/libcrypto-1_1-x64.dll apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/ cp libsimplex.dll apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/ scripts/desktop/prepare-vlc-windows.sh diff --git a/scripts/desktop/prepare-openssl-windows.sh b/scripts/desktop/prepare-openssl-windows.sh index d65d4b8e31..c579b8ef90 100644 --- a/scripts/desktop/prepare-openssl-windows.sh +++ b/scripts/desktop/prepare-openssl-windows.sh @@ -9,12 +9,12 @@ root_dir="$(dirname "$(dirname "$(readlink "$0")")")" cd $root_dir -if [ ! -f dist-newstyle/openssl-1.1.1w/libcrypto-1_1-x64.dll ]; then +if [ ! -f dist-newstyle/openssl-3.0.15/libcrypto-1_1-x64.dll ]; then mkdir dist-newstyle 2>/dev/null || true cd dist-newstyle - curl --tlsv1.2 https://www.openssl.org/source/openssl-1.1.1w.tar.gz -L -o openssl.tar.gz + curl --tlsv1.2 https://www.openssl.org/source/openssl-3.0.15.tar.gz -L -o openssl.tar.gz $WINDIR\\System32\\tar.exe -xvzf openssl.tar.gz - cd openssl-1.1.1w + cd openssl-3.0.15 ./Configure mingw64 make cd ../../ From bda84b08a1dcc62143c826d59ef371eebf76d014 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Sat, 23 Nov 2024 18:41:48 +0700 Subject: [PATCH 107/567] ci: fix mac & Windows build (#5232) * core: 6.2.0.1 (simplexmq 6.2.0.4) * action: fix mac build * fix Windows * version * revert version change --------- Co-authored-by: Evgeny Poberezkin --- .github/workflows/build.yml | 6 +++--- scripts/desktop/build-lib-windows.sh | 4 ++-- scripts/desktop/prepare-openssl-windows.sh | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 21fea3fe8b..c7ac10b9cf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -150,9 +150,9 @@ jobs: if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04' run: sudo apt install -y desktop-file-utils - - name: Install pkg-config for Mac + - name: Install openssl for Mac if: matrix.os == 'macos-latest' || matrix.os == 'macos-13' - run: brew install pkg-config + run: brew install openssl@3.0 - name: Unix prepare cabal.project.local for Ubuntu if: matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04' @@ -334,7 +334,7 @@ jobs: run: | export PATH=$PATH:/c/ghcup/bin:$(echo /c/tools/ghc-*/bin || echo) scripts/desktop/prepare-openssl-windows.sh - openssl_windows_style_path=$(echo `pwd`/dist-newstyle/openssl-1.1.1w | sed 's#/\([a-zA-Z]\)#\1:#' | sed 's#/#\\#g') + openssl_windows_style_path=$(echo `pwd`/dist-newstyle/openssl-3.0.15 | 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 diff --git a/scripts/desktop/build-lib-windows.sh b/scripts/desktop/build-lib-windows.sh index d27d71c08f..cbb886ccb3 100755 --- a/scripts/desktop/build-lib-windows.sh +++ b/scripts/desktop/build-lib-windows.sh @@ -47,7 +47,7 @@ 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 echo "package simplex-chat" >> cabal.project.local -echo " ghc-options: -shared -threaded -optl-L$openssl_windows_style_path -optl-lcrypto-1_1-x64 -o libsimplex.dll libsimplex.dll.def" >> cabal.project.local +echo " ghc-options: -shared -threaded -optl-L$openssl_windows_style_path -optl-lcrypto-3-x64 -o libsimplex.dll libsimplex.dll.def" >> cabal.project.local # Very important! Without it the build fails on linking step since the linker can't find exported symbols. # It looks like GHC bug because with such random path the build ends successfully sed -i "s/ld.lld.exe/abracadabra.exe/" `ghc --print-libdir`/settings @@ -57,7 +57,7 @@ rm -rf apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/ rm -rf apps/multiplatform/desktop/build/cmake mkdir -p apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/ -cp dist-newstyle/openssl-3.0.15/libcrypto-1_1-x64.dll apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/ +cp dist-newstyle/openssl-3.0.15/libcrypto-3-x64.dll apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/ cp libsimplex.dll apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/ scripts/desktop/prepare-vlc-windows.sh diff --git a/scripts/desktop/prepare-openssl-windows.sh b/scripts/desktop/prepare-openssl-windows.sh index c579b8ef90..ce50ee0a74 100644 --- a/scripts/desktop/prepare-openssl-windows.sh +++ b/scripts/desktop/prepare-openssl-windows.sh @@ -9,7 +9,7 @@ root_dir="$(dirname "$(dirname "$(readlink "$0")")")" cd $root_dir -if [ ! -f dist-newstyle/openssl-3.0.15/libcrypto-1_1-x64.dll ]; then +if [ ! -f dist-newstyle/openssl-3.0.15/libcrypto-3-x64.dll ]; then mkdir dist-newstyle 2>/dev/null || true cd dist-newstyle curl --tlsv1.2 https://www.openssl.org/source/openssl-3.0.15.tar.gz -L -o openssl.tar.gz From 7bcb514baf79e215b97be4734faadd42ef7f6df5 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 23 Nov 2024 11:43:52 +0000 Subject: [PATCH 108/567] core: 6.2.0.1 (simplexmq: 6.2.0.4) --- cabal.project | 2 +- package.yaml | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- src/Simplex/Chat/Remote.hs | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cabal.project b/cabal.project index 793fc18952..2246cfeb1d 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 97104988a307bd27b8bf5da7ed67455f3531d7ae + tag: 601620bdde612ebdd33da2637d99b15ff32170c9 source-repository-package type: git diff --git a/package.yaml b/package.yaml index bbfc624bf9..8e45db71e2 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.2.0.0 +version: 6.2.0.1 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index e3985379d0..72d2ddd59b 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."97104988a307bd27b8bf5da7ed67455f3531d7ae" = "1xhk8cg4338d0cfjhdm2460p6nbvxfra80qnab2607nvy8wpddvl"; + "https://github.com/simplex-chat/simplexmq.git"."601620bdde612ebdd33da2637d99b15ff32170c9" = "0lgiphb9sf5i29d378pah24mhf7m8df75jk6asvw8ns527g4amj1"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 9ff84d28d7..1a65b87d0b 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.2.0.0 +version: 6.2.0.1 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index 320a35815d..f818c8ea3a 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -73,11 +73,11 @@ import UnliftIO.Directory (copyFile, createDirectoryIfMissing, doesDirectoryExis -- when acting as host minRemoteCtrlVersion :: AppVersion -minRemoteCtrlVersion = AppVersion [6, 2, 0, 0] +minRemoteCtrlVersion = AppVersion [6, 2, 0, 1] -- when acting as controller minRemoteHostVersion :: AppVersion -minRemoteHostVersion = AppVersion [6, 2, 0, 0] +minRemoteHostVersion = AppVersion [6, 2, 0, 1] currentAppVersion :: AppVersion currentAppVersion = AppVersion SC.version From 30a24df9c03d327c8e36bfa15ac38baeb7158379 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 23 Nov 2024 14:37:44 +0000 Subject: [PATCH 109/567] ui: update whats new link (#5234) * ui: update whats new link * fix file name --- .../Views/Onboarding/WhatsNewView.swift | 8 ++--- .../common/views/onboarding/WhatsNewView.kt | 2 +- ...vacy-and-decentralization-for-all-users.md | 30 ++++++++++++++++++ blog/images/simplexonflux.png | Bin 0 -> 64801 bytes 4 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 blog/20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.md create mode 100644 blog/images/simplexonflux.png diff --git a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift index c1c2cb8383..0a3ef05029 100644 --- a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift +++ b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift @@ -521,7 +521,7 @@ private let versionDescriptions: [VersionDescription] = [ ), VersionDescription( version: "v6.2 (beta.1)", - post: URL(string: "https://simplex.chat/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html"), + post: URL(string: "https://simplex.chat/blog/20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.html"), features: [ .view(FeatureView( icon: nil, @@ -529,9 +529,9 @@ private let versionDescriptions: [VersionDescription] = [ view: { NewOperatorsView() } )), .feature(Description( - icon: "text.quote", - title: "Improved chat navigation", - description: "- Open chat on the first unread message.\n- Jump to quoted messages." + icon: "bolt", + title: "More reliable notifications", + description: "They are delivered even when Apple drops them." )), ] ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt index e20a56c407..8eb89931b3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt @@ -725,7 +725,7 @@ private val versionDescriptions: List = listOf( ), VersionDescription( version = "v6.2-beta.1", - post = "https://simplex.chat/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html", + post = "https://simplex.chat/blog/20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.html", features = listOf( VersionFeature.FeatureView( icon = null, diff --git a/blog/20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.md b/blog/20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.md new file mode 100644 index 0000000000..cb5db41c88 --- /dev/null +++ b/blog/20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.md @@ -0,0 +1,30 @@ +--- +layout: layouts/article.html +title: "Servers operated by Flux - true privacy and decentralization for all users" +date: 2024-11-25 +# previewBody: blog_previews/20241125.html +image: images/simplexonflux.png +imageWide: true +draft: true +permalink: "/blog/20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.html" +--- + +# Servers operated by Flux - true privacy and decentralization for all users + +**Will be published:** Nov 25, 2024 + +- [Welcome, Flux](#welcome-flux--the-new-servers-in-v62-beta1) - the new servers in v6.2-beta.1! +- What's the problem? +- Several operators improve connection privacy. +- SimpleX decentralization compared with Matrix, Session and Tor. +- What is next? + +## Welcome, Flux – the new servers in v6.2-beta.1! + + + +[Flux](https://runonflux.com) is a decentralized cloud infrastructure that consists of user-operated nodes. + +With v6.2 release all SimpleX Chat users can use pre-configured Flux servers to improve metadata privacy and decentralization. + +Come back on Monday November 25th to learn why it is important and how having several operators improves metadata privacy. diff --git a/blog/images/simplexonflux.png b/blog/images/simplexonflux.png new file mode 100644 index 0000000000000000000000000000000000000000..dad3f480f640513e8a92e6a51319be5fccb160e2 GIT binary patch literal 64801 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 literal 0 HcmV?d00001 From 909edac64fb9099c5abe5d0e8d8c736dfc80eceb Mon Sep 17 00:00:00 2001 From: Diogo Date: Sat, 23 Nov 2024 14:42:25 +0000 Subject: [PATCH 110/567] desktop: unsaved changes popup for network and servers when clicking middle lane (#5230) * Revert "Revert "handle click when have unsaved changes"" This reverts commit ba53cc63c6a860ca8802fe726c51bf172410a2e5. * fix in children view * unsaved changes for network and children * don't close all modals when pressing back * explicit param --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --- .../kotlin/chat/simplex/common/App.kt | 6 ++- .../chat/simplex/common/model/ChatModel.kt | 3 ++ .../networkAndServers/NetworkAndServers.kt | 39 +++++++++++++------ 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index b1ce003812..fc17c49c7e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -431,8 +431,10 @@ fun DesktopScreen(userPickerState: MutableStateFlow) { .fillMaxSize() .padding(start = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier) .clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = { - ModalManager.start.closeModals() - userPickerState.value = AnimatedViewState.HIDING + if (chatModel.centerPanelBackgroundClickHandler == null || chatModel.centerPanelBackgroundClickHandler?.invoke() == false) { + ModalManager.start.closeModals() + userPickerState.value = AnimatedViewState.HIDING + } }) ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index e501ed5a91..ca03d0ce72 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -167,6 +167,9 @@ object ChatModel { val processedCriticalError: ProcessedErrors = ProcessedErrors(60_000) val processedInternalError: ProcessedErrors = ProcessedErrors(20_000) + // return true if you handled the click + var centerPanelBackgroundClickHandler: (() -> Boolean)? = null + fun getUser(userId: Long): User? = if (currentUser.value?.userId == userId) { currentUser.value } else { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt index ef5b82a5d9..6b1dcec5d8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt @@ -39,10 +39,10 @@ import chat.simplex.common.views.onboarding.OnboardingActionButton import chat.simplex.common.views.onboarding.ReadableText import chat.simplex.common.views.usersettings.* import chat.simplex.res.MR -import kotlinx.coroutines.launch +import kotlinx.coroutines.* @Composable -fun ModalData.NetworkAndServersView(close: () -> Unit) { +fun ModalData.NetworkAndServersView(closeNetworkAndServers: () -> Unit) { val currentRemoteHost by remember { chatModel.currentRemoteHost } // It's not a state, just a one-time value. Shouldn't be used in any state-related situations val netCfg = remember { chatModel.controller.getNetCfg() } @@ -50,21 +50,36 @@ fun ModalData.NetworkAndServersView(close: () -> Unit) { val currUserServers = remember { stateGetOrPut("currUserServers") { emptyList() } } val userServers = remember { stateGetOrPut("userServers") { emptyList() } } val serverErrors = remember { stateGetOrPut("serverErrors") { emptyList() } } - val scope = rememberCoroutineScope() val proxyPort = remember { derivedStateOf { appPrefs.networkProxy.state.value.port } } - ModalView( - close = { - if (!serversCanBeSaved(currUserServers.value, userServers.value, serverErrors.value)) { + fun onClose(close: () -> Unit): Boolean = if (!serversCanBeSaved(currUserServers.value, userServers.value, serverErrors.value)) { + chatModel.centerPanelBackgroundClickHandler = null + close() + false + } else { + showUnsavedChangesAlert( + { + CoroutineScope(Dispatchers.Default).launch { + saveServers(currentRemoteHost?.remoteHostId, currUserServers, userServers) + chatModel.centerPanelBackgroundClickHandler = null + close() + } + }, + { + chatModel.centerPanelBackgroundClickHandler = null close() - } else { - showUnsavedChangesAlert( - { scope.launch { saveServers(currentRemoteHost?.remoteHostId, currUserServers, userServers) }}, - close - ) } + ) + true + } + + LaunchedEffect(Unit) { + // Enables unsaved changes alert on this view and all children views. + chatModel.centerPanelBackgroundClickHandler = { + onClose(close = { ModalManager.start.closeModals() }) } - ) { + } + ModalView(close = { onClose(closeNetworkAndServers) }) { NetworkAndServersLayout( currentRemoteHost = currentRemoteHost, networkUseSocksProxy = networkUseSocksProxy, From 6581e27524ed2f2ca5b76f6b7214fbf707bdeef8 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 23 Nov 2024 17:42:24 +0000 Subject: [PATCH 111/567] 6.2-beta.1: ios 247, android 252, desktop 76 --- .../Views/Onboarding/WhatsNewView.swift | 2 +- apps/ios/SimpleX.xcodeproj/project.pbxproj | 36 +++++++++---------- apps/multiplatform/gradle.properties | 8 ++--- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift index 0a3ef05029..92b2820681 100644 --- a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift +++ b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift @@ -531,7 +531,7 @@ private let versionDescriptions: [VersionDescription] = [ .feature(Description( icon: "bolt", title: "More reliable notifications", - description: "They are delivered even when Apple drops them." + description: "Delivered even when Apple drops them." )), ] ) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 8dc195e17f..4f03ced132 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -149,11 +149,11 @@ 6419EC562AB8BC8B004A607A /* ContextInvitingContactMemberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */; }; 6419EC582AB97507004A607A /* CIMemberCreatedContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */; }; 642BA82D2CE50495005E9412 /* NewServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 642BA82C2CE50495005E9412 /* NewServerView.swift */; }; - 642BA8332CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH.a */; }; + 642BA8332CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14.a */; }; 642BA8342CEB3D4B005E9412 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA82F2CEB3D4B005E9412 /* libffi.a */; }; 642BA8352CEB3D4B005E9412 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA8302CEB3D4B005E9412 /* libgmp.a */; }; 642BA8362CEB3D4B005E9412 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA8312CEB3D4B005E9412 /* libgmpxx.a */; }; - 642BA8372CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH-ghc9.6.3.a */; }; + 642BA8372CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14-ghc9.6.3.a */; }; 6432857C2925443C00FBE5C8 /* GroupPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */; }; 643B3B4E2CCFD6400083A2CF /* OperatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 643B3B4D2CCFD6400083A2CF /* OperatorView.swift */; }; 6440CA00288857A10062C672 /* CIEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440C9FF288857A10062C672 /* CIEventView.swift */; }; @@ -496,11 +496,11 @@ 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextInvitingContactMemberView.swift; sourceTree = ""; }; 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMemberCreatedContactView.swift; sourceTree = ""; }; 642BA82C2CE50495005E9412 /* NewServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewServerView.swift; sourceTree = ""; }; - 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH.a"; sourceTree = ""; }; + 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14.a"; sourceTree = ""; }; 642BA82F2CEB3D4B005E9412 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; 642BA8302CEB3D4B005E9412 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 642BA8312CEB3D4B005E9412 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH-ghc9.6.3.a"; sourceTree = ""; }; + 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14-ghc9.6.3.a"; sourceTree = ""; }; 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupPreferencesView.swift; sourceTree = ""; }; 643B3B4D2CCFD6400083A2CF /* OperatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperatorView.swift; sourceTree = ""; }; 6440C9FF288857A10062C672 /* CIEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIEventView.swift; sourceTree = ""; }; @@ -670,8 +670,8 @@ CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, 642BA8342CEB3D4B005E9412 /* libffi.a in Frameworks */, 642BA8352CEB3D4B005E9412 /* libgmp.a in Frameworks */, - 642BA8372CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH-ghc9.6.3.a in Frameworks */, - 642BA8332CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH.a in Frameworks */, + 642BA8372CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14-ghc9.6.3.a in Frameworks */, + 642BA8332CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14.a in Frameworks */, 642BA8362CEB3D4B005E9412 /* libgmpxx.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -752,8 +752,8 @@ 642BA82F2CEB3D4B005E9412 /* libffi.a */, 642BA8302CEB3D4B005E9412 /* libgmp.a */, 642BA8312CEB3D4B005E9412 /* libgmpxx.a */, - 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH-ghc9.6.3.a */, - 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.0-1FOg7s6V4oE9PrQV6sHPkH.a */, + 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14-ghc9.6.3.a */, + 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14.a */, ); path = Libraries; sourceTree = ""; @@ -1927,7 +1927,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 246; + CURRENT_PROJECT_VERSION = 247; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1976,7 +1976,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 246; + CURRENT_PROJECT_VERSION = 247; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2017,7 +2017,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 246; + CURRENT_PROJECT_VERSION = 247; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2037,7 +2037,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 246; + CURRENT_PROJECT_VERSION = 247; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2062,7 +2062,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 246; + CURRENT_PROJECT_VERSION = 247; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2099,7 +2099,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 246; + CURRENT_PROJECT_VERSION = 247; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2136,7 +2136,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 246; + CURRENT_PROJECT_VERSION = 247; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2187,7 +2187,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 246; + CURRENT_PROJECT_VERSION = 247; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2238,7 +2238,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 246; + CURRENT_PROJECT_VERSION = 247; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2272,7 +2272,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 246; + CURRENT_PROJECT_VERSION = 247; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index e105e61dde..b2d7875074 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -24,11 +24,11 @@ android.nonTransitiveRClass=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.2-beta.0 -android.version_code=251 +android.version_name=6.2-beta.1 +android.version_code=252 -desktop.version_name=6.2-beta.0 -desktop.version_code=75 +desktop.version_name=6.2-beta.1 +desktop.version_code=76 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From d40d690f86035eaf4a2f0c405c4585cf71d26ff5 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Sun, 24 Nov 2024 15:27:58 +0700 Subject: [PATCH 112/567] desktop (Windows): fix linking with openssl 3 (#5238) --- .../desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/multiplatform/desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt b/apps/multiplatform/desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt index f0679c0fa1..0e8a452e08 100644 --- a/apps/multiplatform/desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt +++ b/apps/multiplatform/desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt @@ -77,7 +77,7 @@ private fun initHaskell() { private fun windowsLoadRequiredLibs(libsTmpDir: File, vlcDir: File) { val mainLibs = arrayOf( - "libcrypto-1_1-x64.dll", + "libcrypto-3-x64.dll", "libsimplex.dll", "libapp-lib.dll" ) From 97b472fd9c43dfdf0f765d18f33794a96458e909 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 25 Nov 2024 09:24:12 +0000 Subject: [PATCH 113/567] blog: operators (#5240) * blog: network operators (draft) * update * update * ui: update whats new link * fix file name * update * update * update * update --- README.md | 2 + ...simplex-chat-v4.4-disappearing-messages.md | 2 +- ...04-simplex-chat-v4-5-user-chat-profiles.md | 2 +- ...20230301-simplex-file-transfer-protocol.md | 2 +- ...30328-simplex-chat-v4-6-hidden-profiles.md | 2 +- ...vision-funding-v5-videos-files-passcode.md | 2 +- ...essage-reactions-self-destruct-passcode.md | 2 +- ...lex-chat-v5-2-message-delivery-receipts.md | 2 +- ...local-file-encryption-directory-service.md | 2 +- ...desktop-quantum-resistant-better-groups.md | 2 +- ...-simplex-ux-private-notes-group-history.md | 2 +- ...istance-signal-double-ratchet-algorithm.md | 2 +- ...sistant-e2e-encryption-simple-migration.md | 2 +- ...ransparency-v5-7-better-user-experience.md | 2 +- ...5.8-private-message-routing-chat-themes.md | 2 +- ...-v6-private-routing-new-user-experience.md | 2 +- ...ity-review-better-calls-user-experience.md | 2 +- ...vacy-and-decentralization-for-all-users.md | 142 ++++++++++++++++-- blog/README.md | 10 ++ blog/images/20241125-operators-1.png | Bin 0 -> 146040 bytes blog/images/20241125-operators-2.png | Bin 0 -> 631873 bytes blog/images/20241125-operators-3.png | Bin 0 -> 78223 bytes .../src/_includes/blog_previews/20241125.html | 9 ++ website/src/css/blog.css | 56 +++++++ 24 files changed, 222 insertions(+), 29 deletions(-) create mode 100644 blog/images/20241125-operators-1.png create mode 100644 blog/images/20241125-operators-2.png create mode 100644 blog/images/20241125-operators-3.png create mode 100644 website/src/_includes/blog_previews/20241125.html diff --git a/README.md b/README.md index ff4ae8c657..52f753a5ab 100644 --- a/README.md +++ b/README.md @@ -233,6 +233,8 @@ You can use SimpleX with your own servers and still communicate with people usin Recent and important updates: +[Nov 25, 2025. Servers operated by Flux - true privacy and decentralization for all users](./20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.md) + [Oct 14, 2024. SimpleX network: security review of protocols design by Trail of Bits, v6.1 released with better calls and user experience.](./blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md) [Aug 14, 2024. SimpleX network: the investment from Jack Dorsey and Asymmetric, v6.0 released with the new user experience and private message routing](./blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.md) diff --git a/blog/20230103-simplex-chat-v4.4-disappearing-messages.md b/blog/20230103-simplex-chat-v4.4-disappearing-messages.md index 7c00df3228..ab9010535f 100644 --- a/blog/20230103-simplex-chat-v4.4-disappearing-messages.md +++ b/blog/20230103-simplex-chat-v4.4-disappearing-messages.md @@ -71,7 +71,7 @@ Some links to answer the most common questions: [What are the risks to have identifiers assigned to the users](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md#why-having-users-identifiers-is-bad-for-the-users). -[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-technical-details-and-limitations). +[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-and-security-technical-details-and-limitations). [How SimpleX is different from Session, Matrix, Signal, etc.](https://github.com/simplex-chat/simplex-chat/blob/stable/README.md#frequently-asked-questions). diff --git a/blog/20230204-simplex-chat-v4-5-user-chat-profiles.md b/blog/20230204-simplex-chat-v4-5-user-chat-profiles.md index 18817a18b6..0f20747ae8 100644 --- a/blog/20230204-simplex-chat-v4-5-user-chat-profiles.md +++ b/blog/20230204-simplex-chat-v4-5-user-chat-profiles.md @@ -97,7 +97,7 @@ Some links to answer the most common questions: [What are the risks to have identifiers assigned to the users](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md#why-having-users-identifiers-is-bad-for-the-users). -[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-technical-details-and-limitations). +[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-and-security-technical-details-and-limitations). [How SimpleX is different from Session, Matrix, Signal, etc.](https://github.com/simplex-chat/simplex-chat/blob/stable/README.md#frequently-asked-questions). diff --git a/blog/20230301-simplex-file-transfer-protocol.md b/blog/20230301-simplex-file-transfer-protocol.md index 0008dd6b9b..9219b8122c 100644 --- a/blog/20230301-simplex-file-transfer-protocol.md +++ b/blog/20230301-simplex-file-transfer-protocol.md @@ -139,7 +139,7 @@ Some links to answer the most common questions: [What are the risks to have identifiers assigned to the users](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md#why-having-users-identifiers-is-bad-for-the-users). -[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-technical-details-and-limitations). +[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-and-security-technical-details-and-limitations). [How SimpleX is different from Session, Matrix, Signal, etc.](https://github.com/simplex-chat/simplex-chat/blob/stable/README.md#frequently-asked-questions). diff --git a/blog/20230328-simplex-chat-v4-6-hidden-profiles.md b/blog/20230328-simplex-chat-v4-6-hidden-profiles.md index 4fe282b081..c369eb5792 100644 --- a/blog/20230328-simplex-chat-v4-6-hidden-profiles.md +++ b/blog/20230328-simplex-chat-v4-6-hidden-profiles.md @@ -104,7 +104,7 @@ Some links to answer the most common questions: [What are the risks to have identifiers assigned to the users](https://simplex.chat/#why-ids-bad-for-privacy). -[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-technical-details-and-limitations). +[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-and-security-technical-details-and-limitations). [How SimpleX is different from Session, Matrix, Signal, etc.](https://github.com/simplex-chat/simplex-chat/blob/stable/README.md#frequently-asked-questions). diff --git a/blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.md b/blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.md index 690292d14c..eb1288059a 100644 --- a/blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.md +++ b/blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.md @@ -108,7 +108,7 @@ Some links to answer the most common questions: [What are the risks to have identifiers assigned to the users](https://simplex.chat/#why-ids-bad-for-privacy). -[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-technical-details-and-limitations). +[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-and-security-technical-details-and-limitations). [How SimpleX is different from Session, Matrix, Signal, etc.](https://github.com/simplex-chat/simplex-chat/blob/stable/README.md#frequently-asked-questions). diff --git a/blog/20230523-simplex-chat-v5-1-message-reactions-self-destruct-passcode.md b/blog/20230523-simplex-chat-v5-1-message-reactions-self-destruct-passcode.md index 0cdbe2831f..0128a64b21 100644 --- a/blog/20230523-simplex-chat-v5-1-message-reactions-self-destruct-passcode.md +++ b/blog/20230523-simplex-chat-v5-1-message-reactions-self-destruct-passcode.md @@ -102,7 +102,7 @@ Some links to answer the most common questions: [What are the risks to have identifiers assigned to the users](https://simplex.chat/#why-ids-bad-for-privacy). -[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-technical-details-and-limitations). +[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-and-security-technical-details-and-limitations). [How SimpleX is different from Session, Matrix, Signal, etc.](https://github.com/simplex-chat/simplex-chat/blob/stable/README.md#frequently-asked-questions). diff --git a/blog/20230722-simplex-chat-v5-2-message-delivery-receipts.md b/blog/20230722-simplex-chat-v5-2-message-delivery-receipts.md index 759587821b..5818049858 100644 --- a/blog/20230722-simplex-chat-v5-2-message-delivery-receipts.md +++ b/blog/20230722-simplex-chat-v5-2-message-delivery-receipts.md @@ -160,7 +160,7 @@ Some links to answer the most common questions: [What are the risks to have identifiers assigned to the users](https://simplex.chat/#why-ids-bad-for-privacy). -[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-technical-details-and-limitations). +[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-and-security-technical-details-and-limitations). [How SimpleX is different from Session, Matrix, Signal, etc.](https://github.com/simplex-chat/simplex-chat/blob/stable/README.md#frequently-asked-questions). diff --git a/blog/20230925-simplex-chat-v5-3-desktop-app-local-file-encryption-directory-service.md b/blog/20230925-simplex-chat-v5-3-desktop-app-local-file-encryption-directory-service.md index 0222c25d77..3c3fb7b515 100644 --- a/blog/20230925-simplex-chat-v5-3-desktop-app-local-file-encryption-directory-service.md +++ b/blog/20230925-simplex-chat-v5-3-desktop-app-local-file-encryption-directory-service.md @@ -108,7 +108,7 @@ Some links to answer the most common questions: [What are the risks to have identifiers assigned to the users](https://simplex.chat/#why-ids-bad-for-privacy). -[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-technical-details-and-limitations). +[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-and-security-technical-details-and-limitations). [How SimpleX is different from Session, Matrix, Signal, etc.](https://github.com/simplex-chat/simplex-chat/blob/stable/README.md#frequently-asked-questions). diff --git a/blog/20231125-simplex-chat-v5-4-link-mobile-desktop-quantum-resistant-better-groups.md b/blog/20231125-simplex-chat-v5-4-link-mobile-desktop-quantum-resistant-better-groups.md index 7f50446bfa..539d719af4 100644 --- a/blog/20231125-simplex-chat-v5-4-link-mobile-desktop-quantum-resistant-better-groups.md +++ b/blog/20231125-simplex-chat-v5-4-link-mobile-desktop-quantum-resistant-better-groups.md @@ -133,7 +133,7 @@ Some links to answer the most common questions: [What are the risks to have identifiers assigned to the users](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md#why-having-users-identifiers-is-bad-for-the-users). -[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-technical-details-and-limitations). +[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-and-security-technical-details-and-limitations). [How SimpleX is different from Session, Matrix, Signal, etc.](https://github.com/simplex-chat/simplex-chat/blob/stable/README.md#frequently-asked-questions). diff --git a/blog/20240124-simplex-chat-infrastructure-costs-v5-5-simplex-ux-private-notes-group-history.md b/blog/20240124-simplex-chat-infrastructure-costs-v5-5-simplex-ux-private-notes-group-history.md index 43c502d8c4..f5539106b7 100644 --- a/blog/20240124-simplex-chat-infrastructure-costs-v5-5-simplex-ux-private-notes-group-history.md +++ b/blog/20240124-simplex-chat-infrastructure-costs-v5-5-simplex-ux-private-notes-group-history.md @@ -94,7 +94,7 @@ Some links to answer the most common questions: [What are the risks to have identifiers assigned to the users](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md#why-having-users-identifiers-is-bad-for-the-users). -[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-technical-details-and-limitations). +[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-and-security-technical-details-and-limitations). [How SimpleX is different from Session, Matrix, Signal, etc.](https://github.com/simplex-chat/simplex-chat/blob/stable/README.md#frequently-asked-questions). diff --git a/blog/20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md b/blog/20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md index 6d4c8b77a2..13a514c175 100644 --- a/blog/20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md +++ b/blog/20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md @@ -235,7 +235,7 @@ Some links to answer the most common questions: [What are the risks to have identifiers assigned to the users](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md#why-having-users-identifiers-is-bad-for-the-users). -[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-technical-details-and-limitations). +[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-and-security-technical-details-and-limitations). [How SimpleX is different from Session, Matrix, Signal, etc.](https://github.com/simplex-chat/simplex-chat/blob/stable/README.md#frequently-asked-questions). diff --git a/blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.md b/blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.md index c017b9d1cc..0980eb8896 100644 --- a/blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.md +++ b/blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.md @@ -132,7 +132,7 @@ Some links to answer the most common questions: [What are the risks to have identifiers assigned to the users](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md#why-having-users-identifiers-is-bad-for-the-users). -[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-technical-details-and-limitations). +[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-and-security-technical-details-and-limitations). [How SimpleX is different from Session, Matrix, Signal, etc.](https://github.com/simplex-chat/simplex-chat/blob/stable/README.md#frequently-asked-questions). diff --git a/blog/20240426-simplex-legally-binding-transparency-v5-7-better-user-experience.md b/blog/20240426-simplex-legally-binding-transparency-v5-7-better-user-experience.md index cb3e5b2d10..225c2637d7 100644 --- a/blog/20240426-simplex-legally-binding-transparency-v5-7-better-user-experience.md +++ b/blog/20240426-simplex-legally-binding-transparency-v5-7-better-user-experience.md @@ -90,7 +90,7 @@ Some links to answer the most common questions: [What are the risks to have identifiers assigned to the users](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md#why-having-users-identifiers-is-bad-for-the-users). -[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-technical-details-and-limitations). +[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-and-security-technical-details-and-limitations). [Frequently asked questions](../docs/FAQ.md). diff --git a/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md b/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md index 0519e78e7b..e06f7c2084 100644 --- a/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md +++ b/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md @@ -144,7 +144,7 @@ Some links to answer the most common questions: [What are the risks to have identifiers assigned to the users](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md#why-having-users-identifiers-is-bad-for-the-users). -[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-technical-details-and-limitations). +[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-and-security-technical-details-and-limitations). [Frequently asked questions](../docs/FAQ.md). diff --git a/blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.md b/blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.md index e81bf5516a..de9e33a87e 100644 --- a/blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.md +++ b/blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.md @@ -218,7 +218,7 @@ Some links to answer the most common questions: [What are the risks to have identifiers assigned to the users](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md#why-having-users-identifiers-is-bad-for-the-users). -[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-technical-details-and-limitations). +[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-and-security-technical-details-and-limitations). [Frequently asked questions](../docs/FAQ.md). diff --git a/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md b/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md index cefe8560f5..1bede1cc97 100644 --- a/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md +++ b/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md @@ -165,7 +165,7 @@ Some links to answer the most common questions: [What are the risks to have identifiers assigned to the users](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md#why-having-users-identifiers-is-bad-for-the-users). -[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-technical-details-and-limitations). +[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-and-security-technical-details-and-limitations). [Frequently asked questions](../docs/FAQ.md). diff --git a/blog/20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.md b/blog/20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.md index cb5db41c88..57c4f69981 100644 --- a/blog/20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.md +++ b/blog/20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.md @@ -2,29 +2,145 @@ layout: layouts/article.html title: "Servers operated by Flux - true privacy and decentralization for all users" date: 2024-11-25 -# previewBody: blog_previews/20241125.html +previewBody: blog_previews/20241125.html image: images/simplexonflux.png imageWide: true -draft: true permalink: "/blog/20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.html" --- -# Servers operated by Flux - true privacy and decentralization for all users +# Servers operated by Flux — true privacy and decentralization for all users -**Will be published:** Nov 25, 2024 +**Published:** Nov 25, 2024 -- [Welcome, Flux](#welcome-flux--the-new-servers-in-v62-beta1) - the new servers in v6.2-beta.1! -- What's the problem? -- Several operators improve connection privacy. -- SimpleX decentralization compared with Matrix, Session and Tor. -- What is next? +- [Welcome, Flux](#welcome-flux--the-new-servers-in-v62-beta1) — the new servers in v6.2-beta.1! +- [What's the problem](#whats-the-problem)? +- [Using two operators improves connection privacy](#using-two-operators-improves-connection-privacy). +- [SimpleX decentralization](#simplex-decentralization-compared-with-matrix-session-and-tor) compared with Matrix, Session and Tor. +- [What's next](#whats-next-for-simplex-network-decentralization) for SimpleX network decentralization? ## Welcome, Flux – the new servers in v6.2-beta.1! - + -[Flux](https://runonflux.com) is a decentralized cloud infrastructure that consists of user-operated nodes. +[Flux](https://runonflux.com) is a decentralized cloud infrastructure that consists of user-operated nodes [1]. With this beta release all SimpleX Chat users can use pre-configured Flux servers to improve metadata privacy and decentralization. -With v6.2 release all SimpleX Chat users can use pre-configured Flux servers to improve metadata privacy and decentralization. +We are very grateful to [Daniel Keller](https://x.com/dak_flux), CEO and co-founder of Flux, for supporting SimpleX network, and betting on our vision of extreme decentralization of communication. Flux investing their infrastructure in our vision is a game changer for us and our users. -Come back on Monday November 25th to learn why it is important and how having several operators improves metadata privacy. +Download new mobile and desktop SimpleX apps from [TestFlight](https://testflight.apple.com/join/DWuT2LQu) (iOS), [Play Store](https://play.google.com/store/apps/details?id=chat.simplex.app), our [F-Droid repo](https://simplex.chat/fdroid/) or [GitHub](https://github.com/simplex-chat/simplex-chat/releases/tag/v6.2.0-beta.1). + +Read on to learn why it is important and how using several operators improves metadata privacy. + +## What's the problem? + +SimpleX network is fully decentralized, without any central component or bootstrap nodes — you could use your own servers from day one. While there is no full list of SimpleX network servers, we see many hundreds of servers in public groups. + +But a large number of SimpleX app users use the servers pre-configured in the app. Even though the app randomly chooses 4 servers in each connection to improve privacy and security, prior to v6.2 for these users the servers were operated by the same company — ourselves. + +Our open-source code that we are [legally bound to use](./20240426-simplex-legally-binding-transparency-v5-7-better-user-experience.md#legally-binding-transparency) doesn't provide any metadata that could be used to learn who connects to whom. But the privacy of users' connections still depends on us honouring our promises and [privacy policy](../PRIVACY.md). Flux servers in the app improve that. + +## Using two operators improves connection privacy + + + +To ensure that the users' metadata from different servers cannot be combined to discover who talks to whom, the servers in each connection have to be operated by different independent organizations. + +Before this version the app was choosing servers randomly. Now, when both SimpleX Chat and Flux servers are enabled it will always choose servers of different operators in each connection to receive messages and for [private message routing](./20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md), increasing metadata privacy for all users. + +Flux servers are configured as opt-in, and the privacy policy and conditions of use that apply to Flux servers are the same as for SimpleX Chat servers, to make it simple for the users. + +To improve connection privacy by using Flux servers all you have to do is to enable Flux once the app offers it, or later, via Network & servers settings, and accept that the same conditions apply. + + + +By default, if both Flux and SimpleX servers are enabled in this version, you will be using SimpleX Chat servers to receive messages, Flux servers to forward messages to SimpleX Chat servers, and the servers of both to forward messages to unknown servers. We will enable Flux to receive messages by default a bit later, or you can change it now via settings. + +Any additional servers you add to app configuration are treated as belonging to another operator, so they will also be used to improve connection privacy, together with pre-configured servers, unless you disable them. + +## SimpleX decentralization compared with Matrix, Session and Tor + +SimpleX network decentralization model is different from other decentralized networks in several important aspects. + +| Communication network | SimpleX | Matrix | Session | Tor-based | +|:-----------------------------|:-------:|:------:|:-------:|:---------:| +| Full decentralization | ✅ | - | - | - | +| No user profile identity | ✅ | - | - | - | +| Connection privacy | ✅ | - | ✅ | ✅ | +| Server operator transparency | ✅ | ✅ | - | - | + +**Full decentralization** + +Fully decentralized networks do not have a central component, bootstrap nodes or any global shared state, like in cryptocurrency/blockchain-based communication networks. The presence of any central component or shared state introduces an attack vector that undermines privacy and security of the network. + +**No user profile identity** + +User profile identities, even if it is only a random number or a long-term key, undermine privacy of users connections, because in some cases they may allow network operators, observers and users to find out who talks to whom. + +Most communication networks rely on fixed user profile identities. It includes Matrix and communication networks with onion routing. + +SimpleX network design avoids the need for profile identities or keys, while still allowing optional long-term addresses for users and groups for convenience. It protects users from being discovered and approached by malicious parties, and many family users chose to use SimpleX with children because of it. + +**Connection privacy** + +SimpleX network has [private message routing](./20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md) (2-hop onion routing) — it prevents server operators from discovering who connects to whom via network traffic metadata. Onion routing used in Tor-based messengers and in Session also hides it. But because neither Tor nor Session users have knowledge about who operates servers, in some cases the clients may connect via the servers controlled by one entity, that may learn the IP addresses of both parties. + +Statistically, if traffic metadata from 2% of onion network servers is available to an attacker, and the client chooses servers randomly, after about 1750 of such choices the probability of choosing attacker's servers as both entry and exit nodes, and connection privacy being compromised becomes over 50% [2]. + +Matrix network does not provide connection privacy, as not only user identity exists, it is tied to a specific server that knows all user connections and a part of user's contacts connections. What is worse, Element — the most widely used Matrix app — offers the servers of only one organization to create an account, resulting in some degree of network centralization. + +**Server operator transparency** + +Operator transparency means that network users know who operates the servers they use. + +You may argue that when the operators are known, the servers data can be requested by the authorities. But such requests, in particular when multiple operators are used by all users, will follow a due legal process, and will not result in compromising the privacy of all users. + +With Tor and Session networks such legal process becomes impossible, and some users may see it as advantage. But nothing prevents the attackers, both criminal and corporate- or state-funded, to compromise the privacy of Tor or Session users by running many servers, or by purchasing traffic metadata from the existing server owners — there are no legal conditions that prohibit server owners of these networks to share or sell traffic data. + +Because of that, we see operator transparency in SimpleX network as a better trade-off for privacy of most users than operator anonymity provided by Session and Tor. You can see privacy of network participants as a zero sum game — for the end users to have it, server operators should be known. + +## What's next for SimpleX network decentralization + +SimpleX network is designed for extreme decentralization — not only users are distributed across network operators, as happens with federated networks, but each conversation will be relying on servers of 4-6 independent operators, and these operators will be regularly and automatically changed in the near future. + +We believe that the only viable commercial model is freemium — a small share of paying users, who have better service quality and additional features, sponsors free users. This model doesn't have downsides of exploitative "provide service, sell data" approaches, that technology monopolies practice, and it also doesn't have problems of cryptocurrency blockchains, that have shared and immutable state, and also have regulatory problems. + +To provide this extreme decentralization with freemium model we will create the system of payments allowing server operators to receive money for infrastructure certificates that will be used with any other participating network operators without compromising privacy of the paying users. You can read about this model [here](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/rfcs/2024-04-26-commercial-model.md). We will be writing more about it as this development progresses. + +## SimpleX network + +Some links to answer the most common questions: + +[How can SimpleX deliver messages without user identifiers](./20220511-simplex-chat-v2-images-files.md#the-first-messaging-platform-without-user-identifiers). + +[What are the risks to have identifiers assigned to the users](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md#why-having-users-identifiers-is-bad-for-the-users). + +[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-and-security-technical-details-and-limitations). + +[Frequently asked questions](../docs/FAQ.md). + +Please also see our [website](https://simplex.chat). + +## Please support us with your donations + +Huge *thank you* to everybody who donated to SimpleX Chat! + +Prioritizing users privacy and security, and also raising the investment, would have been impossible without your support and donations. + +Also, funding the work to transition the protocols to non-profit governance model would not have been possible without the donations we received from the users. + +Our pledge to our users is that SimpleX protocols are and will remain open, and in public domain, so anybody can build the future implementations of the clients and the servers. We are building SimpleX platform based on the same principles as email and web, but much more private and secure. + +Your donations help us raise more funds — any amount, even the price of the cup of coffee, makes a big difference for us. + +See [this section](https://github.com/simplex-chat/simplex-chat/#please-support-us-with-your-donations) for the ways to donate. + +Thank you, + +Evgeny + +SimpleX Chat founder + +[1] You can also to self-host your own SimpleX servers on [Flux decentralized cloud](https://home.runonflux.io/apps/marketplace?q=simplex). + +[2] The probability of connection being de-anonymized and the number of random server choices follow this equation: `(1 - s ^ 2) ^ n = 1 - p`, where `s` is the share of attacker-controlled servers in the network, `n` is the number of random choices of entry and exit nodes for the circuit, and `p` is the probability of both entry and exit nodes, and the connection privacy being compromised. Substituting `0.02` (2%) for `s`, `0.5` (50%) for `p`, and solving this equation for `n` we obtain that `1733` random circuits have 50% probability of privacy being compromised. + +Also see [this presentation about Tor](https://ritter.vg/p/tor-v1.6.pdf), specifically the approximate calculations on page 76, and also [Tor project post](https://blog.torproject.org/announcing-vanguards-add-onion-services/) about the changes that made attack on hidden service anonymity harder, but still viable in case the it is used for a long time. diff --git a/blog/README.md b/blog/README.md index c8de7e83f9..97ccffda9a 100644 --- a/blog/README.md +++ b/blog/README.md @@ -1,5 +1,15 @@ # Blog +Nov 25, 2025 [Servers operated by Flux - true privacy and decentralization for all users](./20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.md) + +- Welcome, Flux - the new servers in v6.2-beta.1! +- What's the problem? +- Using two operators improves connection privacy. +- SimpleX decentralization compared with Matrix, Session and Tor. +- What is next for SimpleX decentralization? + +--- + Oct 14, 2024 [SimpleX network: security review of protocols design by Trail of Bits, v6.1 released with better calls and user experience.](./20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md) New security audit: Trail of Bits reviewed the cryptographic design of protocols used in SimpleX network and apps. diff --git a/blog/images/20241125-operators-1.png b/blog/images/20241125-operators-1.png new file mode 100644 index 0000000000000000000000000000000000000000..a749fa0ed136c6c05b0c6dbcd79a7ed08b2b52d9 GIT binary patch literal 146040 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

2B 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

Dz+Ievo`Ixi{OQ0mj~>+7$6S z(}HO?hI2y-a{aI(U+W7B%Xpe%Ur0bJ_F)!~4gJ<&=3<=acpsZ8raSqIwAp#o>U z3Mk0XY~<}i1XY3KAkSBiG3%|#>bV_;kB^;S23XLq$q#u58s8+sUQOwueIA3fy~SVE z(J?^|5pfbh+9m(KKptflat>Cn!VNK$+N=$qat~x}lHWFfq_)BWrMCpRyJD5mojb(r zlA0iGB=+_sp0D0H1vKJ~spk4smpq*{aBx{dn_D#H|3nn zA&S1qM`!25>GwRv!z0!X2yC0NtovDqaf-`c9m{N&eF*s7GPK97n~G6yEi+Iy2=KhJ@>TSN(f&TQ@{Tq@vOwU&iH|+;{PPkO}!> zWr@{TZ-b-0>Leg6>h110;SNZ74!%V{Kv+ zRi7!CZg9AHlN(`VfXRFKk@;1V0J&e=kE?+nz4W*pdJ^lve;fm47z7oh!w(1zbB_Xzd}I$u03blJLAs3*Ju)tvuKI80J`iNVZZR z4L>64d~&6$cKf(;7a*r_KUm4YAn{JIuaVYLq zO3|Xlp?HgyVj*~Ox8PpfokDSn6Wrb1-QBIY!RIO>JtJ6SKP(v!U-&q*g>6-x{r64+fyG%OmAyE?`v@R_;kbJ+JV~EICOGfXQP7ed(iEW<}ha{ zNPG~FaT-wD*8k9e!-wd0_~&~*?RbKNVLm>PKeUm*Rs9g4j!)u+M2VH7tb^eOQPTLs zc*eR-N3pwl4k^E2*mNMhyRmw{vatPHZ*BxO{tkeyeA^Ex;Y#H4^jY{Jvtdo+4V_rLqrKKf9M9x%wCk=#i5-wKF zfUc&-6_&Uzzq&zh(M#eguuB=pZz06J zOf&npURMKx3knE)?WYo>5Fs%OxUdd8vJqfomG25J4y9d^#MRKzfdDT7<(WX=-)W4ciJo zv>sOxg;<}L>jR?JukRpPebzL9gRUgTVYz$~2R-|N_S2Sj&T@BaW?S0xZJN<`oYX9C zfV+L83rfHWV&FRKUUU`1BP7rc=2641&gv5R-6c?WO>6Rmivow~R)#dIz4XT^Xug{& zQE7DW;#PU6Q2|P*-LzKpb9d3M&_OzkYxD&t&&@?wN_Sx^1fMGKAw0cG1G<*_BJ^hA z^$@I}Np$f~jAsGkbWg}N*tO=0Fz9i&Q!2$n-c76toRS?)?;3+C(Y#m3s#vrQ^JDmIb;P| z*ceEK6W|k6OUq3b**cRJ^@y#r-ff;@R%jli6wH0BO^Y3!dXTr4{lG(a=Jt>7^ixKv zx-%_0BCFnvl|c1PX{2}8E2P6&&g7j^vq{IaKQryV1{%!B6d-2%7I?QO?`aCM zpTWL+;4MQTwc|6};XYfpKzSCHVywNrDt~mjo--mpf3Rth)Y)-eW(GArT0<0AlDOG} zpR_(Dm;OMH5t}7kTp`7SIpzASh-jF|M^Hiq);!NhH6p%?xQ#G6fq;e(0O(HzS2_W-+sxtdG1s%8D1Xli=W!1 z0QD~NTDoR6Xj7s=b_a>}b&d|Y8G|7%6?T6{va5`y*YRfL6iO#;CLbPiber8k{nMmZ z1G4giR0owV<@`~yr>>`bhNwe2aOcb4QWmsUvuO1N z=pcFX4y=Wc9x`-%Z-Z!xRFr=WYgqFq4n;;~ExYI@oYrKaiL&HDf3W zDf7vA-?h$M)aLIQpZG(WY&*#9e%AAh&Qs6hr2LE;oo_-H1b_YI$O0Xyu~b#i@fgSn zQN;FWs#r8YroE9W^hQ9Q%rfAb z&88=_BAKCV!SI-^#${txo4X_)->f z*^a;(*Wo@#t52)`Xbd&f1wyQA0$d4D=s<6%Mw<-@{Vd{!K1y(#2ar=YqprzV-eNWb z?Y?ULH!Ye^=7~;>>fzmbU7#Pl8%64Ccj}%Jc7{gFCEg0N2S`{-g_z4+fe&=Z%5|@U z4ZF>%qc14G+Fu~QZmq^J_=nKf<$fV;+s~T87R>@3caQx{c+)?{(2YWv8ufli4X)j- z&?ptxqCx#uCmKF8cM*25ewEN~ip&LV{#_2fSVZ%sQc7ZrQ6XRA++r5{WO24x7Y{8KevDiyPLR- zVd>(X)@@WB(qY7Jh{($Z<*_Y%fUIDl%G+N(Z*WyO6;|#631+>9Cb0~{(EJF+M;0z{ zrFJf@mwe(YrED={-ozA6%ne@7wBZ>JArRM01r23nrgUiFl>b($lrg?74Iyv;AZUMy z)mo83I1+FNuxk9ueitq-Hv6V$2jU!BHY)LOS~qWd>HZsC7l&{jV84NHVJB7OF_)e#B~WO31~{J z5w7rwRBobdHSIjnEZT{xd7GVFUVM9miQ zD*Y`Gkjqr~$ytT9-y^+Q{y0ozO&?*V;zD=K9qVDgzC8EluhaY2F8ZIn^w*B!g2qaz zP#Wm^=sQ@z!XRfF&k;K`!MemjaGiHV6N5V$CdlkJ8b0RgC?&%>Ljiw2nQA>WmF_16 zHg}}%d@tr$*dmtX9VQ`%0382V>pz=|SzC(nmpAaQ7Q=r%jIJPU22DDXn3{yBUmA2t zKTeI)L|9aOc^G~d(l{<@+`!sw@pH_nY6bXfyA&+)YJ2y!a5Ba;5rrG^6p6LbseX5?&!IyhsZAsZWoR>gK3_NI%RsoNSws6F32Axf3bJ#Euh`4T0@oZ z5%LBY;Z7_4u{W8fvgI_?(VRS(0VHg1)t$4$K){MOGJNv}#D6;)VTd1+Cn;2viz+p# z!OU!C4;c8m-#@Nxfa6@WnfVi4so%GF^ zgc8kDHk0`&vIPkJ*lr74cLaG!GbF{{19b%{^_-Q*Kz>cx%JcO*_W6G@L8G$wLx{4Y(p2VDWyb z)NE3$wyj`+5tS@)t<9LmtKi2i%{%>b;vu}`Y|5*3qI49q>23X@`8_84>Lvm#AOYemBjbdjYvILuZ0^jh}<(FA%{0`vfNpjgJdV$-6BF3wb z(9j?+E>#)9CQ~4uDJ@RB$W2H8Lo8r3>dpqY+bLtK{Xy$DLSPq4gG!05K(uYSTIz$EC93e%F0KU<-iz3w<@9u}B9Q~N${^eQwF=xnb&;DEN&}fua zh)<4BhO@Fgqo7n9NeUZP-IK1(ejpi+zAD+(Ys_Y7;PVOdR~;Yau1VInGA>`&{m5C*?84OUM6;Vj93NV&VnXU||>$EX**R6x+v?Lz+9v0FO^; zVkDBYm!E~iqv8rX4;tFTXxm!dbdOnQnqa=&LZw4np;qofv(A`Dvwf&%#be!l;Ubhu zU%?E#9K&`57NU+HAmGeB7a*p{sqm!`!lB=TPUIB&1a=qro5qIy$(uFZc ztWSgevF0rN0lx8yXjw1*yeg*krTm!kfPJGeVd2<(l385YJ`*T)z17GWK(&)hTUqdQ|GB7?RwM9ku4=k|EN%QKU~@wVLJXDyp)L1ZqS_9|eWx zcQYB((_|;54+Vwc(7j20UU!aAG6wZSSJ&iy`p_sZ*P#oCc zC)^UPU7>)t^MbA=(4tB2;0iY*mpluyW}4wpW5}d)_@xx`eV-|LiF`q?s*=iGkL-V2 z=>C812xCrYRbpJvh~=M*cR4^>S#Y)X?BJof$F$a_qaI-Car_dIA(430#kbDeNO*m@ zvJY2GOe{)L&WJPrso?uuI+=llKW_@EOG+2%TFWw#+6&~sUU2jB!j+1^E|%azhVaix zz!;9eN`OD8pAIBG?rw&7FXjM`*ne_k{=6!qp>p3#~h8j?Rp3$XHzT(wOMS zm_wBP{taOL$8(Z|Fo!Dj$UXoIT;M7}1jZT|L!q9`iCxaQEdGHD-_QZH{DMG*Y^I&< zb8phSf$?=pB<53!nM#TrThj|Ck!JJ+DRB6e$uxH|8bLGIbR@U4dxz|PZ29-rnc8v> z6Z|fR2qDq}UlG7qyOK9bu7U?M0ayFtxIgxiPGpR${EV{#Ty#eW(Q*9uo%Da7FR`We z62dJRTnd1+X-%dAszk4=fS{ZnzEN6Hf*BphMB;R!q%}zSHplxS31j>t)&Nz6c8P;3 zw3Ii#RD+s)JH4ut(gM!6OV#IRufwbiHN_Xc3BI)QZ$Ck~V0PCd>@@rrFw#_iF^DukX2Z@<5i>4?p=fr zk8Rc4tg-Z1OGAJN9odAtAmK=6*!}n{h?ef(ulz?^3M%{Dd%8zfA_kh7+2LT^ex>>% zISIV6PW=>a=9s{fl;)D6E6A;0bz#B1*MrHlHmlG$)i(5a5z|kiz za3Vcc47}yW08S+ScOl(@^{|J z^2k$|{g==AA_W1M06Q_~mS9tpUJZt&k_g?u9~%U>!g&J#hD9yie8~*1^vOQO6yXGI3{doYmYCm3vp!fNW0LA>pa^8lGA9?fE$5Nm7{`*$>PUvq+sdBG(HRh&5|D z21_c%7e#ELFF-*GZxBvijzPUiX0@Toue}eOojo-g%$$|?=w>zeMLOZonC7#LmXA;< zQM`5U3G*(56Nk3yWy+^kt2X7A)eD430R({6%;@kFVq0qkAOSObIItghvt*KqxXRBmw7zS=vqnZ0wlPjsX z(t0+z8#~l_7h9$;-wmz8G(H{fdFz0M@&<)v?LDB)y{LHrx7{XC!iI>#0!XkF#%4*us<$+FQEgj3b1c zECGa`Q7+l!mQCXQb+>yja4=GM%i&l%$lhE&cmH6BJ$c7GjNX;pJE{52{GrU&V2oju-PC8z)n5;O5_ji8o}1g4Wqi*sSG1mA1B`q3<3%=bCfe15 zBy4{bh&FZZd^|^x%!U1??`32qJ)zobMbm(97~4ceI_QcEK8S8MRQ95%_$}Gas`Q8A z)Vmm35_l3XQdvd9?Jr>hFAU4~OD4Y-3z|6qU4I*?@H7w0%`(4WHES2Hn_FW~}K7M42=3I#|*?dlteM_>p!?;L06Fx5x67g+L)8 zp}~!r4ec!CSvypR<^d8I4qr!^A?8#V!QrtC7H&;LF#g?HuTG$L`$tmc*|R?7@p5o9 zH$#}six$BWrg?h~e6<8p^4P89Y3DxsfpOMq#aqa};YkfXK+4QxEo zDt8H~*!K&%L18SnINt@eR9hnhz@1fCUvcr(?kY`fn2=lv&D)MObaMmd?*DXh|4*1= z2dTE9hHoZhpULP_5ft-TnmfX{eAHHaqzl0$D1j|Q6VZQ~@C*#~Z<`9(0hyuZWJK zoP?YSib##9MboM~I(uy#?ytE=NUx0Um__%AzT56aoYLuf`B9LfY(&dzzbAmO1 zg`WHAoGqmBYT;x75Lq29XtttASY{O#1XN+w275O+4pAgYLP&O`{^!;1!<+s|BlqG* zdUs1>Ta1OL6dE{OG+r51`AiLPNI*q4B}_Ba(!T@ zwR_cPEE{I($b(4W&}t%9o6ekhG#-9tz)L?5Lp@4tFQb}zDO&}Vi#ryEL3xfPvb30n zXK|YLg|!bm9ul!Qk*3fUb(WWpctTQ3F>JiiB^_Ub<9&c);c@Ap5l!3suZ#abU*2&; zt8OrU%$%DyD!VIMgecW#4N0sQMcGn+rW)-Xden`nRJ1aQrj5|$)f%`_(`Nlm5!-Wr z?LCuCAOb{?S9wnp+OL4eW8tyU_plb0)R@#R3f26ga%^k!#j)zCPT|usO`E`JZuj*+3IHb2-LwA`}9eRiM zsG?U<;(8TGh*fxy$tb9)N0_YwKQ~p)&n4BiSj3|PPX%xZWG3p?dXL$83riP69aQ5> zacZ-MDj&s9*!4=hs{HsPsm`0fAE~#Nc!r(G@0yxAC<` zODYYcb7-_#wpQ^pQN~hsBI&O5v4R`KoVp|k#!GXEH4Lqwe}@lMYj_h?lufZjDz*`! z@-A{a7vGP?AY|L!Lz#KIqDegm^;S8-QSkbjgrO?jc zg0I2+ULS&0hX_->;)$$~#TW>e&Gq4dwhPGinXZC1EmDcP4W2J{$R@7@ zLdWhNXi}yb^w673ab%oq@pq$ye1Aa>1IF9C3&F-D$j*o8~C zo2K?js4D?n-tQsZ+ZeM5$=T1T<#9Ueqs<`I8n`T`Hkw@430O><(=O3_$*#83guv<7 z_F~CWp<+Jd#L~wLL{9OpO(!RmNToKGhrWon_+iwk?6sEVI(IHhzOduQFh<%}sV(^X zc}X~DSM5odnd#j!2QF_@-+fS_D%Hzq16Y|}endbvSmjo~zzAZQsG>3m-prpe+=44m z_SyR{XYXImr$VUkz=fpzO*}}9y-Js1z1vW2)L>2-JRr)CF$T|&nCnI8gr`&fCJKTa zyRwOni#^>b8|bR^mE4O{Qg~i4VN&U=-iCV-c|Fs(L5l#CfVa%#Eu=WrF3yXp<~G$I z>nGO?0_{N4I5|3C*DcX_c*KzR-X@e9f$Ir{hhh;ODF#E+f2D{2tJjyVie2hbyjyNt zJZZC$%wfwA5v`=2aD*1tQjkznI=SM7?SX4M?R|2JguR|cv9dDA4Y z8DU*TDE;kLYsP?NF%tM>5P8q zoQ(1z6I3w5*Q3*O#}85$PrsfZvJCta>`gC+x1@Zhnk+A74pbZt+*N>h+;f+k7=HB0 zX(Y&}y6A>+mh3@)k0sJ0F!O%ta-DN6RqGW`?Sl7I7Tu(Sr%kJX;TS*1dJO_9fH8w2 zH$JZi8|sTF&^0~;`I774l+BZVPbHh>uF>NGca!6WBeLZ)&g^VE9E>n2P>bwGnuQ(8 zypWrq@GgJ@qE(o7zo9X#f?t}RX;^X#!h0o)PtGJN+92+ko^L!DKJEFkn#xoXRewd^a6hf2N`P>V-|=NvTk>JcmTS zo1AF_Xi+hvo?4U@B}Q7{n1e#|WQFwCDT^o5l;YvAbrFV!;+=m_T!y5FX0oDhb z&HBr{e1g+I%Xt2~ovy*`9p|Z72iORKx8G^}#M-H0xy8glo;hX-s4nc-&c&UwAa~w1 z$|!5E{|=27&Lh`KJ$FfMj!5k+-YPFi#{2hh0}`o{g)6roE~;&G#a`lIX#%(bRm`f5 zCw6PHd^UuqqdWn9QJ6$)hj>Pal^>?F@JL`q;Ue!h3 z;}J1j{XO8nUPShWa%`;vF-}}@q2qhajit2Ooh6brM;rm@8VtLm? z0wEoRJ{qsFpyNcW5qmDP&^JYZ5GSCRZME(9y(RA;`3q*|F&gUP;~47JJUynR_9)_* z;aZ85=(oxP!gb!WB#cbT(~le&lkoJ35D8*W7@9S=L>Mi#h7jCis824-RWi$syaX?1 zc$$8A6~@$ifATa^_Q@~kgA?i*9d*c`P$cJeNZ9r z_Nl^ruKYIS`a91HSil9x+tAGKauU(K3cw>(9QBDfOWeavEIH2Xo2Ph>K6~YqeaEsk zx&^Ne-EX*@qXP83XLw%lF{1OJ*FJRggQh0HhlM^#($7o_plmhd{{xaT|F2v5A78Q| zab&7*2W@4xeFSm#7iQH2tEJ8yrwUxTNoNN%Pw)fV`0*MII?F6|{cMdG^~!*@Yo>6v z?s4y|SB;j18@BdOYYLWK?%LU2+64i^&B9E{LBvLs7J=yR=8FP1joU_iIg(dQ>vQli z=VjeC+)WM8+FosYnah^WF5tH!oAY5}bKNRRc*^`__HT>1aiPp~V+U63n@DJ@A^!dG z7e1lW;hdOW4%jWR)L8G~l)tV#rCKx_nodF+ONpzUzG_&W_r)EACvN>%e!P#p##=8@ z`1*|ib|16|!a&{@R@?{XSE>y+u!r$+93~Y20gogNjPYz{A@YtFhDU>m4L!ytzQLi9 zk`>k+7-N5-Ua6GKjon`*6@D{I62gpA0P&s^v_u#Ydz-GtRSEnN)zEN~oMRD6*%b2I zg3@!>UxoTi#>FX*TjL6yL42q&bzz%#o)(}zuDgDo;Y4y1{X8NzU(qywJwYgqx!UR;P|BX_Ap*jarx?5Z`sp+_wLGe@DX{>8|eiA{A z@NZ6wa2oaM{!iF?Y@exQPzxb$WF!TfY(}%ej}O1Ui4;|8BP%$#<}7 z&Ii|T6LFf)K6&HlUH^AWn{I_;?B}7LGt5~3mW7;KbKjw4vhQN1pxX5Dk!dRLUvbV>ir$kS%h@snC=rFbmxV(r;JXHeFA$%MU4+G@pGc z*wr|gDI{NUDXf3$9?2Wi@6I0o(<%0+!J^z?e+yrmgW-Vgr+p@okgnd*$3ZNdR^4ua z;e=m7OG~dpzS}pY2}fvEYE~GA&6gWg%-87$Jf;4cTy686`W}Qc1&v6iX5;%41y_pT z-*`VE#*-ch*>UB)bUWDDLCb1QO0n6^jNJ89&_`;;_8Wlxq#hBsDZl?oNLor+&Ec8QlcSoGLD%0a> zNJZK;!-tI5J9~eu45SfC*{LlTl^y9c?b%9uI`j7N)p}|zhR}`hS&K9KA0DvEBx2_{avvZXN*q#|PPE?PI9@kPc{xAK&4-&w zC)1`oQZ&cj`pn3uaEDcx?a35T%R1)3Rsl$LR`sgdz#k?7j)91L?|hIWi@YXHhjJ2z zPNo6!u9nVs_xRqVcb6C1dns|p89x(66M&#P-t$8{098DSE(x|v*OY3ZlAM5Y<{&P^ zn&b0X!mE<8i74R;jg zNdq6VkEL-}Z)$0&*qRyf-<0|;3}Dogjl?*1U(^L-Ji;lUmRr}rdM&L7C6a|~@O)ELIqRJoF`c)T;ATnF z29H@c%$roEF|P2!u(Vuc0-8*f3%uD!1RE^$>K0gKbV-~i$1G!$AbsllM>Wfzy50-J zTw;6Gr3H|?r7y3GB=8;InC!0)Fe~X}2u%Luv);-T{Dq#z&$BC7VMjCi4(4DPSoj+; z;{e=z1#p;M3s7Qo%ozQb2QX@cOk&$h`QzIEL<$9;`78O=IJX(QRdAFn-ReQC0kL)0 ze!0QGHvW5(t@3$?4Y9SbEt}nD17dUiCsz1#B&gO`z2Dl}z@KUr&O41_C7d0-+^TcJ zvd;>fA;GHeg_rqJF6z~em|lB9bY$)vQ|hypsWx|mUx5uUH>5tRkp7?ETIs>D0CJa7 zixwQ4j*AOvm*Jb_eU6bB=fmO z+Bg&xX$Xac^}FzYODpXTz|}W2OeMb8XS0Gdv=3G&aEx?zNXfR&&}+VRzN0()`q}Ql zcrwkBPSFVK#4IJS;#NGv0Wf1S1~#T+BeAOWsiW4@cyL+ZfwS)1fGv$WM-*EhYTflB zB_Tezqtz}fF_>qJshN5-d@QDBefHZ?;`^;5PvYai(9<$6itlK8Z|%IFQ`X^jTh9H( z1~pPx9fl!+MfJ}`0W=?7Z8M#wO~`9(xdI}(%gA^4ASVNCBXKd{3<=WW26aHrx^1=o2(=vf78Xvg#(=|7vnYl{#dX+F~Y9|X^JTQ3S@_6eLnMi+FICu-k0Zuc3ZqT-6wd&XgJsw zc&sGOPJ{b|)#~Qi%id-C1dqRe+pL}Mk_f?$>5iCxn5@5$lJb9(dN=rQ!GsG$n9-bY zo^1jL2!)a1GPC%(3}a|0()S#+`j53!nq!JcYbtpOPu)pH(l!%1Or4w6T{0A7c3Foa znh<+l_5;XQ)DK<#zDQ4noQf23lR?{HC<&_xs^v#^y(0Xe!QdG!Y=q3{{&+cgpbDYZ zF5x}Rj?tLT_k-Lr7>kZ+K7<4hSr4;6d6XA;A4A!PPEx+LGoPaoFUM1q&?p4A#zY8# ztF0FB61?{^^dtC~(I@`D6u5g7)J0KNgdiX}m+$TQ73gT*V6ehE*ZEDV2VoFT%+}d@ z#aT7g@RV}J!?+MP+GFWyEgi!JQQ;P^@j4b$I};n?HUh#l`9umCQxt6KTxCfK=5WJ> zLo1Zj4oDV;&_H-I*c;2!R(mdnNxEdk=#Jp`>a?d32hlE#NG1$Q`=YJ%1}W*)fs zOu+Q*!PFGfl4;A9s4R*6K1q^{Wwm+^=Ii|dmg`T%m1%BgL4XREDnQGzY}LjisVSsU zeEwo^*)&iqn?S%N)W?a>{P}U?zG%BmZV%$7L{#VuqLXqnqi@_+=-sl#7#&O(Z~t&; z^+wExy$FYy{k;TfjNfQ%-hnilaJetu97|N0Y)DvujdFA6IV5V4!c*R}SV!m?hyX5YjWGF#?w|ru{#0StJJ25?05D1Sckvzd`E-i$) zjsx9sd}6T9yM7tiz|~Cpu)wOj-udCs#WYMYcSo`KX-#XMCjbbldN8y(IjWhOF4mZw zX41H{b6}}@{=K$+ZQMu}u{N(guoi*$T)Oe6LsT(Ka^@XT?gmDxBbRw#$M4mMY`}=t zrt5Wz;~@LDh3aC%j6KL&PLJg2ntDF|B;qlx+&?~McCa>#I(384xlR_~GEChs)Fj&> zjFKJWkMzPiv>>7peFtyxE*9QPlBIfr7pfgJx}ayIdMp|ELk=9&0wHsBy}mAbO9M!X}XeepPwNb zkmMQi`)AMfzJzAQ&bvD$vu4oU?d)6j`2R@;?WV!~u4((jnPlnVdrgWJMU^C<%q6SK zPOAC*#_NPn9sRT!VK1PfzlsX|XdTvQs7TEn&pp;drP|Cc_~U_TL!5I*ph?)LnboE} z_btOX5w62z>GXretPA`H0j?oUKWny`~3nR|A_V%O(vi=Ie20W}Oo zYr`5<3s+rTA(I^#f{8vTkdh~zAi+AHTQ;WDwA>-%3%mdp(h+`6)uu|PvRjxh#Va^( zK!u-KS{ie#pO#&dCy#_4rGdELYgZoTXJnoHs>cM3u0#RZpUFYFlLK!^eXeQeB_3~( zKwgb({^PzIjbwKDKw_3=8{ll51x`p!>{n2Jq{sVZDMSd{n2i(VF2D^JfJEa!Ly zVTwrSd4T76fdDpWm#Ezd;?MDUJ87<5Rq0y>?Ap6;$lr^n*zV`&`#}O0c-5&cXUG+K zG$uD4TLaJM+$hM)Z+)Fx4#U2+VTBlSyPtmkqS=LO)z9R7#Mx9^DO@}e;ySemQT`?? zc}`3MjZ6Ct5-AhQGolVoZ%1?Oiu>~ioFnLb%%5*jk{3L!ZRde^J`Vf!`J#pH^SkJh zx+fRE4$lLJ&zH4AygO^P9|nUoAt>CYHgM<3^+Z~r=Di%|P?v+Apwxl^_{_CbxJ7Z> zv*-5#VW#Y+4nKs*C!VLFQ@#YxatkQlFtu%lG}2>gp7>9Z_$j2-6UM;p+UAtgrO*F> zpx&9zoRN?GXe+nCoq(lE!~6G+?3%Bc+(nuhh5EajX0CND;f2ojhs=wbqQb1+n}~Rp zb?_tPy9)^58CLErR(_yho1yZBl$yd)FTuF>QQHjFP8Up^NRDxtO=(n;ZKK4r&U2cr z2OzpQjkY1;&tIxtSyF@wUL0uW2J1RP2tPe?A~AXQN;{Mqmdo@U!&>PCx^o04Zf3X$Kp>e#SDX|i=YE0iQWO60Y$`AjgpqDg1faLAqA(x zbgb*cMrT9BhDn>(YyF8vO$Z$ zYN6XPjLQ?&8i4l4qV zGp`2}zx9M)PqE+e*bZDjD-~-t_{=EzNFe!ef1|x}@A>Mv`n5w!QsJ)l?nqB2>W_$j1aSU@ zYo_fsqa+JNe|9-1AU(-yL)y`uH%7v8)8IZ*c(vq;?bttBuFz=O%OdBVC3-Dcr0zCb zhFd>~yxP{(1O>r_pxq>rzn4CUf{uo6#r2K$7@9=S%jU#!W!M*c-t#1fT_vzo zJxvfno_;s4G&+XR<|Vu9l6QkLtLi^yR1c{-hd)1C&llZQtaF*MBOk9riFJfrqTe^V z4isd~*&L-38{CoK@$Yw%6m(&$(ezI|DCOD=R2(-#?gY;rj8Qm1+jBVzoh zXH%TiF`UkW@k>@G#d&(JE+kgt;(Vi8!xHR-E=7FP)OSzXb-sF)_RxtG6y36mo5OqD zaDO~m0apZ6qVdoyR4G!35CWKwwX!TMbQ~pNTw436Cy_t@R?$aiEaZQZ?PEtoz~NW3 zen*w$XbN6W{pcxZXk$e3S72!qtJM)qKfk#>`?F<$GKpo{O4{gAAX$}eLpps4jx}Pb zrK*#~#e8{1UBP8Y!uZI$+$>g``mXvOJ-CXyRIU1Ihi%oqLT9ET7g2BmS%BF^r)W4r zq)oUNmk_g?f>Q2`lG&n59Bl6yEq7(|-_BO1 z%^_;X^9#&hXth2&K1X`UFEpUf1@(ro5U&4bOoTAYyBMAKnL-4_SL>$=Rg@F8694!q zr5$v{x=TaJTPwv~Wr!y&@@*cM9`|TUxoI~3yr0N-VQ>)0cS-ZDnyR#z+}(rT`%-P4 z4z}K`IyfCB`ud_><|{=g;nX*TxvMWCn!hUXv`cYuIgZKkppUqPWoM^L1s+Du%3m;F zNs@V}EzoJ{76=RJMz13^RX}fRw(f{BH8%Z{Wp7NE1$YG=kU2Y5tAC@fAEh$T-G&Bs zrsT*r#+E@dZ6!HaKo0iA$>@$^?zRGxzZ(CUY>-ZBs$0K3yz5r!OsGx3htL{HykYJ1 zWlv96Ad`#=snc%}2xVKvipvMB7W5F!Sun-(C@e-E%j5NPl*|1au!5`3ajymJ$RZ?Q zRI9EhbO)-@%JwDL<$Xw%5pwX~7c`G{2Cp`t;$8d(_4(j$CksLZLMKm0VOzL-_?K1Q ztyAo;6ylGT@{#MkL;soT@^ESzE7$-7BQWoyY0Zeqs8!3VOq;5{x|PHzf^eAbc;qGY zeAqatw_wvFuf#sW%b<)8!-_Vp0W91R6(C{7IKG|I+~$8(!$0V!yDU4sZCR=i%JaR3 zhRecDRp9xsA|Jvia-r$?@X>wG^FiJ{T>MI%5Mo4Xb4Sx6m>uu_*?RvL`e6ZejAX{) zZjf%JPtGpUsLc_;i*1dpv)tOZ0%r+`C%+BZ9(tFCn^2(ix$27$_(qCcQDxj*KxM{n zWl`Log^Q^%4dRLb$ZH5VjgKmlFORZI53o$27fHQhg#Z=8KZ(hgSxY4!{aUw&`k)PJ zccQW+Igh4wVXr+N0WEwpWdmpP@+%5u8jVs}Sr^_`;B!AxZYH?rgS)4)3Uv~)@wD5p zT>g4&c zRBObOjoRewJ_JsuO}T$>r9`KizB$DiM68aWG!!nyM2w*qhqpVFQOmQKZRU7UwukZ_n$2kiBdk%uIu(~K=<=a_0~-F}kU zJM+LSCJ1YRU1u!yREi5C+b}g|06Xq)phF6a+kM0OHDRUv&^i9%-N(xsTR{i~zuIb- zTD@g(b(l+C^+vd-Bru?+;3|$Md!@g%#kD*ysP5vshwJLQ#}m=`gGr#c_8-f3s`pRJ z_L|nZ6H7Aj#NEopDkF=Zi@TK{1s!GyLdF-Y(&zT*;hf%Ci0Dr}akXLx3)Xj7sj3$EU88stL`>UhAaJ#;V z!@`SSbC>1K&5}?J&YIkel6f}guTM8d`DR<=>LS=w7@sTg)Ze_$&!ieD)!p+T@#B6U?L1EtvNgDonx9BID65*)j%R%RG7caLKVg$F^&bwa`Fv0$?2Xb`$``lhW#(pVQ$z*boV_hKRqmv)qb&LrEM8BJn*3Ln2G>^_?aZTz@ghn&>y#^=wzOIiSVL#pCyybkU?h&iZpzs~tibK9w;1iKUy}9Y z^Xb8RVIm0S5^w>)aAg!4T2=-`D!`nSX&_>U819Xt3RFTHvx37EqrpNkfDp%*X7yRW z#u3+bM%)U@5cW5qnj!OJ`opBdOreB%_PzW0y;h~?QGboW=lM!#xMnKL+U2FkmZxj0 zLMw?hE4T9+(^msuSY(=BzgK0m`BL*bs6>*?UFK6`2o8UQ)n~GiWJog1sqW18CSZ{9 zl@lNO`6m*gqwm3f2#2OD>#3uBAOmjZvn=M8XYqX#P^#Qu7~3`G9lz-g1Wz16P`gsV ztzKEb_SgPG*!BfKKrDz9k9f%_HyJ@_5TBIK!IUJd5##CZ=__Gk-R#YAgAEx{^z2yb zj!r?}QdOAd(#}s$53Q?Ur$%VIi}yX*>)NiDabIfzP%R0^0?Grf^MgO=vB47RlD>MpRf7)0`YWQO z5Z$_V*Rb*Zr>x}-G08Ujo>aOPOz|k;uVJ;FX);xOKcPNfjteb&;FxQc^(WbqYi124 zNcGZUld{)x*0ZoL?6iRE#Ry3{0cH$&mba~c3n{9SNE`XF@Bjrq+hkY+C6oTXU7YR$AGVj zl=r*&J%-9Z2odc#XVs7DM~Cd{+%5cC<{<1fg6+&L2Rg$&DOr|))aM5jqx_0t9`2`5 zlDI~9N&{X2)v*Z3UD_TD;eGu_gwptXDq=jJT@?AC&j^V0V+DTS3T)!a zql=YeKF>!LR`!^!yjfiUS!|LE&6YRaCphMS-nIK5{Xf3GG9apMZJQJk2}Qb5LAtve zqy*^>0g0hu=#(5nkRG~oXpruZ?rtQ8?v8If?|GhczV{s7e;a=6HEXZC@9VzS<^{fNyICTeH-)BqGdsD3cOL@})99JW2fd9u zhL@*129q(*%i4*xwi3y~9-T2`s;ft832f1kHks7l>O#SPWS@{9?VmjfAE`gwe%)uG z4X8td_6FV0Xt`3nUVr8Ctx`zZu7JAi8Q1U)@0*%_t(kI*QOP0Q1fgfDf zhI=&uBqnxP3z2pM8qywlkUwe8K_%Bf7L2=za-a-6&tZaK=r&XKWeXU^`+ds?am)r_ zwOrF=sx$!ok*64WD)O%79OW@bjSt~LX-*DiLC8q9ew|n?*1vyf_H(2c7yVh>a~*`8 zb)aQk=Jg|YFA?E(KTYt>IjmG#Fe#U&Xfx%JkMaiBV&O1?+n^>tUnjRoVOaI-kE`fs zHu(z)euQc@nR|e{+oHONj&Gd`V-3z(if|m?ruJ3CvgN?3_2MG+LKQoVJIu9i)aE=E z8{8jLwy7$grG@fhtX4h5#6XjoW<_V}1i#c+t@t;3Wa{7tClo};K0{p*zzg@q1Hb5N z&81{R_?q{1iSj!1_pj||S#CVOuWnj-?y^2upSpuz%$CR9{e);X!8%thC!eP$_&Wzn z^5VQJfp9ayQEx49pkHs884UEjHdJwf(je{*B}?J8YJ* z_$Jn^#P&+L!YC(%*lbIb@}t&!hY9JziJ0~StqEeQn}P)DXm(_r<6`#zI1GU^*NF%o zl^8q>UdKh)eW9gZ$CYXIN&$BowvLMtB!>-cIzj{L>3g~1MDnD-R~Wgnp#$#T6t6cG zc2W=%rXlmkUP+N)st3;<7t)s#E#r={NJ%VYw3a0#>yMQ(@t2H+2kAS`lJ5Ij zp;5cqN)Xxplob3uLS|`CWX{XMXxw3V|5A+^ua#xtE8I)RD-%b`RBJC0dVi|bO!X=X zLv8CPTgur)0S#?kcDDFv!U*OEAVFfXaM z3Bv95+yX6oxkZ&R32(;wCT8|S8kHBF`x$VC<3UUf#otjW^(U-*@NJ^PBv}#K-CjP0&z6zRHefjeYt09 zHkB2nmTMnA?iwk+iTLKExE#BX()`Rk8kutd-2fdO#VdDqm;IA$xPqV3GfXJdUM5wm z`DcU{fy3DSuc#jkZ1tlxY;-1Hn2|7ezE~A9S*n%F1Qa2|3gK#?4I#yljReaXRe_D( ztU?@ylt9;?&H&i{!sIWT9CHA)a6g{wU!BB<%Y3)UMKO4LAnzF&DZnb_dAa;p3d@`q zIM2&G2VuO(f=*mD!-DR4eFsC#=Kfu2?@QkY4IB3CLd}w+Xz`SN9J21dR=L555MNWqQZJ)9hJYYNVLa+55b8>MsR8o#RIb z(Xl+O>gsy3>NzvFThy>iy#l`iEaGBS%lK6o7=x&M0mqryn{nL8l>uCLzVII7&F!a4 zSz_nJh$(8|B`%lg0Q^n`9Ym#)v040ZTfac9M5CbVxY}a98^~q@uah=qDq)ZWZ;C^k zN}ZU~9(i}v@#SUz#S|X({bKPdhyu#E|Ky6QH!nl9tb;G7f*#$~iWWRUq^5geKCW!3C>yuMf68bG38-`10K_Ud^3 zeJ>sAgao4VD~8={1BM62tGc3J9m!-yiT@_=xY>catqp zr+V01=h>ERm3q0F_3a^V&g8`FXGBab#MuOXvxbRC8AowCR$R`_mO&I2}b{B-lIAK%0r-Y~LRzS39c~3Usnon;%BlO*Q zIl~;AVRx#jU}?UJrfV}`EA8{?IefarqU2WkrK0WHta4-QAy7UGOebMGC?fMY0gJyx86VlyvXPp*`I4+E<=^qgsO#SUqo;8uY zAL#`eIP&M9n=j%RdQ|MhvKNJkWd}@R(M=J0;x4@g)@9W#{$CZ>;aJ>F^1S zhK7zq+u96p;PjLr!iid%6;v`Mta67!ND(P+$t;MJz(#`yw4HB16j&Yf))wgYVw+n$ z-H8Ku(A?`_;5@<}j&a0{vubrc8C6#+(A-CkvB1_93bC+3`x%B7gDDBg6+ao=OWVqL zeK(~Ppy3r?4rrx)=o$uNqj`iIrZn+n@mTg8r$i_1YWDZMl3c*OccT=9^z*SS4UWI` z_68G4Bv#vF;OzVVK1FW4oVTjHZM!{GKZrOg=piXRYQ*A_0zucQK_Z>+1nd8^H~=|AtR^f;0M4S4Gz z)!H&2p3L=ql5@)f7pME5lP|)aNSx4c$&2SN zsy+1Dp>&7&GRB0SzWB()lqB-{D}irTq<~soO*NP0=(lS9_FOCW2IWEgScjc!-OboikuSd-SxM>~uj*5YPC4a} z*UyKn&FFq6^`0F>|1J?Z@9pVJS?1H7WyDl2nDrkT&}MkOv4EHzg~xYr`-A8w@MNLe z(`LE3WMbcDzJ8MU**jmm6EqeoGT*YTx35eQg9XN(Mf*w;`L-Olb|{#z&Xp10`IC+m`J_P zqGEpGaJ_PPtS7Qo`O^&Bq0@hq{FrWc%FzMKAWrpN1XU*s@>1e%?q%O!_`-c#K}`j? z5v|$tVNT5RpbQK&Gu3k8GCtZy3XNDPj5*oMf@RMzV-27&SH}x z^ZQ-@zBE2<+4`FDfkC?_zh`qGq%xC?dl&9t;gm^wR!u)6pg$g$DdO~olzxaLx666D zsr*X_5&dud1{lb0gV-;$OQmH=6z6{&MMun>K<8xB6)>OX@$dvZ2^6>NW?hLnn=g z_*ngz=NK_edVCJi!djRB6wJ#*r}@-9yjo8Y-sAyScaFZ|LDKs$S&8dEG0@n&0|GRr z+z^ssn9-Ig*;7X8`#c|5A})ZfC41jJWSGS7Z}L%i~R2$;%)D?4jo6#nJ~?dm%I zHN#wY(rFyoO&NxnT~s7X0=wDL%kgu^OnLBP6!(Au`OH7tj&Owj;qM z@Y=e3Yz?Q~Z`q&HeuGhk1MHeiQUsJ2!xuc7`YQb9@;=kRSfe2z)1%Aa?Ra(e$ zAmu#Y7xMTio&%90b&Ap2d43N=%~bGSPe6BC5ivh*^rNLKMhe}0r6ssrIp9@ze=+J~ zlZldBZ7V1}T5Fi4@HhX!r^HYlMLg)8BzgVdSA0xlh2a?ZoUNd4nm$N62(G97gFm+L z?yIawdedTL*fm4?A$YuuF*?5@iynkQZPdbfm)1ehmtV^|yjSuX%wO%0gx?4eJU`RB zYsOEBLER$FDP27a7JwqbKc`_+5E7U?%S1+-fHk;fyQ{2$6tW717@L0}M0OJHuM)^wvdCfYrUG zW*59vz%%LoHd9TYM4<=cD(&%i&yG~>LNN@g;|O68KJ$0T;>mi|N9;%QM*YKLwPn29 z%ERqiSC1ADlrq~g)nIPu_96{0)twQyrLvFGa}oT@;W*tNb1H~EExPYTCH;XmZJA7j z;KsdsWLMGs1rhGFuSZ!>xI4`ntb}6y@kok*p74wdxFUj*Sc|c6>t(&fX3aePShxgL z9Zp0mNx-zG1_{ppL1y(TWi4b{T=-*tNJykvE7MpB-3Zzin!+3`1NYTweWUt|_)z#g zd}E!iJiQQ%ke((^-N;xD6@l_&{0HSI9Pgmt_k+q&vms>wv7?%#k~4`N``v2uPi7gv zMg69{78yUvdKr_72Z=er+73fJ&s)EeTjaJ{uI!{e_`q4cB=YU$=m>7CdhfKU8#O$< z8>s88M-m2e#HPuDpTkeSY3_a5`*yzn7G^xzlbn>UO!BDnBz}1IMniCODw9IiT+kVA$mnqAr>ayMgtaMCUsH8E7 z_gKBV_Tt12l!*s1HnGPBE)S+HI)ncZ%m*RbmXlwdEu0aNOOZH)xuU%*)zJ?R`i12! z#gU4MRbAPP>4&%pk2_nIt~6C`At)`>$3K76jXA_-*P@=ZJPO17E_v~JK75;qq+25I zSi6E@7od_w(7Yk(fcLF+wD0~Jukf`;inC<%bX}Grk0TpftL^DOkE?Tk>)Uv(x~~T- z7fhbu$c%dv5(M$)Fu_;`*+=auF0_t`19C7vEBOdLdp1MusY2Un_k86dDUdWgbleW; z!;F7rJQXvl`6{l>AvESBj^=Rdyd#IubW>0sZmmx>t-gCNIsgkHXg8(yQAhU{O2i6u zuSV#lV`?cTJ;NyUr-=idldU&(`EKxRrKG(UsWCs^3*vedp7#*agVksY+u=>ICd=L3 z#=GTjKJ}mP)VZG0Iy(*VpVA{ML72G1)(OngjGZS$<&Yv}<{Dq%ib;myn;$q7;chRD zwV%p@@Knp-f=(HKGJkl(#D8fe;jO1r)tk`OOUN}8{z4~*QZ!jCp@mtdypxBXpdp~; zGUaguy%n*-^Oyy;;k>j? zJ-t~LxFaPeuox0oj%9NeL> z{0EKg1jSL$e_P$Oq{cHK)fgvm8A7crZy+_RI;~Um43jiD6jK1M^7-y`>1Qm(uC{}t zOytu+6g!ts#zBUuV8L~Er*;`wCGN7>3aO4Lksy(x&9#cr^*bvD%t;Xkh$$Jxv32Qz5Eb!ij*X>=wtg+yy7>TX`uoEGX; zy$RZ4J^0ncBy(E2eOo>4UB9+ZD5S5C5t+s;l483iV=>x}TrmF)O5$;{;EO_1Y+!FS zSr}VDe-#nGF~>r-<62{wf$w12hQ^d#AMQYRRItHZTH0{jJ#Y<;>mquUT*Ub5?@lnU zFN<;ahZ$zPphJ8qC>IcOjA~Og|;6N4j)Ko`OW-WY!G}Z=(uX zKJ~kVLsLATI9^*UL;}&spHu$XJ4?7I%;V_K&twbf+y)Ez=opKd-R1`hL3WHf?BXy@{ejR`|uwp3UYHxf-C2vyIaa$ai<87ySwvtGk zG_OxO?ddUeY)2*B;%{6vyWBj9#baSb&QhAt-f)a@%09E|UK9tajJl30bj#({??Tg5 zRcS+YtJpMT7yt~z={+2E{SpPC^_Qm-#RDXUcsFL&^)wD!Tsyu6X=Z6M^X<(?YaqHk zlUu+V;2FQaPEg9>uvsIHFMqE$4E9r4IV4bfbeKDV4u<}Tel4mVjEh?uP8l0Nk-+*$ z?f7L`Hy;Intu4Wp(;oR-vw_}~!!5RPJj{*T_q_r@#iVR+Ou&&j z>VVOG9o^Xa_XmKV@MbMIFN{p|PPA$Lh5&$whcaG=f$*l&ewnHmoX2%#mq6?M28KQ0 z@4%pvIw5wxTCYC;k(V~N>pZcw_*so#94A37F&Ahgc)b1AZEN*pAm!ri`R?tE38t4x zSA4Ov_A6j6CyPHz*C8_T5d6=R9Yq|AP=_q6To|wX5CuQxF!!$2+c-wOs*y(Big7kg zN-qB`qEtYhx$ixBQv>(#DuHW@;F}3u9e)yUVlR!(a3Yxf&cpoWG>qV)H4hThiSIe3 z>K`0|5#*;L+w`oglhxCi7}UCjdWBzh{!p+6|6(-f`%RasIs=amkN?=bu}>c)1A&cz zn5X(D3`^+nZHc`d=4>Qz%1+H=p7%dQpRdisw2pH}E(%%vF=00zG5vT!LNgjrnp_m1 z#PaqZA*8SnwNGtLL$yH!CJE{p2f`H}{ZuaHBF-v33HuOrLqA1Xk5|JikBZ=ENx)=N zhS)_am;FR9K@e2P3uD@QWvKpZT>VY`ZgqNfX}k429$vG7Rs0&JmhU0v(4QGF>*ftk z4x$&eFa`55eFvrTJe{-lY#p2VQbE znOQk`-<5kwCh#!#H3p0kZ3<@%bKYejfH$;Rf@-Wdmchq1{11yA^W1|;V#!6G(AvOD z(SOov8or)szVBe>o66MNZ+ZNVTED|#QrL4jPoGgQM>2VGbH47r5k|l^@wLfr|G0XT}TmHZrhwInII>f(?taP1N;FcJih75MN;p9A(HpH^|#b zU_nVxd_CXb(GkeeJkwUE^q3ID9aLtC4-&dMQwwr$HB7t8KW;i__lQ4^XG%{Q9C__JU)`H0>`Vq?z#OFR3 zMNJQ5Xk;{CU}Uk)3L!Lm?-(Kx!-t~g9zajtk*+|%ZFrw)sV0!>j&247e4MN>nM>jPuV_0RYp`~NyB_&oIES6HA zF~UH3`@0*S>h8k-`^#@HeO`Y5MS5Qb9{C|+H(qh4tgfat`?InBWUYT{`0gGAXBCqM zLqc!*Bd$(CrcmBCRxi!)^t1)Q+5pN8)%*4XOu?)E)pfB^Z>0*9ZpZ`0dcr@$s)Zvc z47OaZk4YSj+W&2=+4n(Q_-ssViEWNKsYt02dvS7-fBasWU02CX{?W0O5O&LrKcCj@ z#+Va~`K=%DQaXM9gTa1zMI9-@jQs`4vmS0w3G;93+(MOj3YfX^8mtNG>Uti(b`qP& zu<<#q5_lyK-}D4MO~k2#0CCyx=Gp-|4aQuR&?)4I#D{-#nx|6H%9_|oi&`pTnrf*W zwdQDQPFvKlkdf8Hb9>F+NxDxt+8lCADV_Ad3WCjWmOvHgnctHqn0 z-E)&a&OG)`H}k%%6R)07iJ#9bbF{vq{sL_9W{b*1eY|`IM&#RrXsSl^4O9G-R5R#% z?-i}d7iIhqU9>F;l0MnlOq0G%CM~AZKb2G1Y|$;|ynYI>Ex@iE)=}>Aa+6Mf4&&~p zj7j(pt_ohU^f(Mn1_3$-I>y~0afdG0-8X`xD(~fYVv7bXIp#E|nHXp5<*U2MvJE1{ zgsSZU0e14Q$V(2;(-wgT2toSKkS01ydq%iX=RZ9t)(+k2#M0!qELFr-vjqOJ2s>}f zN^6|FPtKs{IHokON07LKxY7T&gr+~f%FhMdn2&s=TQ9yzDT$;UoWqvM3o1&WzK|D9 zLNf@)L4*F#Y!Vh0HZ3)kc6JC@u~v0;LW4Kl=9=+y8G2 z{>Mn1W5L!@%hep5RI*;Dde6i@#Mtz9?qlJIY6%j9Nmx6mQ@SCX4n{=_W0zW85mrRx znd-$J)mlLld+GC2!*Ob)WLawA-q64EK%`$Iy$+w6 z7SRpI<;|PyFt)=B6bl1cmW8^VHq01=hm0*4NDR_r&~3n3r=y98ac2IJ-{VC z^ty=L`Zr5Dyksa->M#T`ELsIKYa+_X0dc^En|-GhF((xMF_e*Gt8!)2YSBs zlTsVV+P}_Bwv0%!YD5%hr$wYx7oUfh2AhUd0DrGtl)NP|$a>0*!v5d(DqrgEqPn}7 zw_=U3&h=HJMD6*lPd(i_Wh^c@HOI~>f|DrwJ!P^bjHiEKE}<>v74wI6pG8knmSWEB zW4NjiV0Cj<0h-l6B;vn+tMvF+`EL@(xju;li`7v2@Cx28T9`6h z3&SB``M~}CIMa!gJ4v$cZ0AP!#=^!^x*(iGovzl)AJCgvsEzi!y2kQ?r^^*K5KKX{ z^x)m|nH*6LD3pDQ@hxP=w1f!f*ho#csFq;y!3;n)9KinfYyWM9e?0n1U?l`anAaC= zZdFf9YlrX3Nk{hIaq!%JQj^&R{hUa=9Z+Y!Y$b6k4VOKyv1C}h&q6LI?9%eU)H_!N zZ<0jLcqLUa)N}znHByXZYiE_7{l`xJTGC(~>-*-8o^Dd!ndYOGKBk?kVRPb+(I{sJ zXh@m47F$ztHWo#nuG_woTZ>EcKo?~FA?Oh1I{0Uh9^nbjZ0Ldt-QJyHg z-?-G~>!NQr2D;S(3tAU~xqtwWjRTn1L z)vy($jb)O;T8}d7@xrnZ>!bD(ZrjOQ!~u;@fao}{?#b$e&#;2+|2plzz7aVc0)Q}& zefr}qDVH|3nbW2-1)e!ySPsQi#IE6cCrz|X-{1qiY^$Ah1-X~gYcv4RcE9WeZRJT7 z^~t|W&tLERr%a8=rx&z6A_+F51sabm}Vqe0>*#_B-$DjRw68djz%q$_vxmURmuv^iCa{N)8 zYMk{Hwv=|Oa@Xr&Y-?y6JFR`=aYe&hGzsdJ_D4Ep$j^WNt|#mB)J2%?beIhBRwk)W zI1xB#C;|y`>H=tj{-p;0w#c8y5sB6+DNw`S+R4Z52sD)J_44A<^Yip!NC`59I%h%3 zbazt(oi+D%OJ z`vS24L(%s<14d76Ai>8BetHYx4{lW1^G4dq(Tl4|#U+!V*r~{V;5iROez{)aViAZj zBl7>N{lE6>ip5MaVaitI#ck;hmLk0M$^kZfDypV9>`%6(10NOk`+p@p*)6mIkj$4d zroa{@)uYFRTsu!w7({<3JrK`d&YS}PBe8RzuVCFneYEtD^8--anNr4yc}w=JjuxzAG7P(CqJQT#z@jQ zz!RMV0%R>H5Krp{ax9t$8}2Om#~exH*LK%nIh)q$zihLq;@b7HqYnh0kE<@(AQk@? z`}xlg`CFcRQaCoUdYrq2?wHWpQ6}0E=gZc8hS`Xfm?Fq^_)I!(MU-)ycbw`QZ;cAx z>lSYNT}AbrqPBz+$G4rio;Qca*q1VdPsxN&YvJ>=F*eyjPTWCblIk;{bA0;S+H>XS zhp`KMDEm`DzO7n%WCzz+_tuCEee$+b{`ax{hYRZLpTg*~-I`>O-i{ zlYPo6z#*```d(vkv%@VgZY6g&m2P@it?{`d>%l_%vjsR6$|mmtA71%pV3CL3%eRr4 zH2+qXE4!ZURp_n;nle*D(K%?%Nc-J#=6%WN5ZC#RM}wV;1(<@C67^|2M1)XqG0RyV zIJ5t9ZZ3b|>{A_09%o;VNgL;s=7yynU8AStd`%r=J-uREBY)=tB0G~^PsQtz4Z{OJ z20sSzYJ=CmCKUkD0TzfB`^Aid+SlymgMg%(D&Hqnn;Fg5ylSY1vtVe>G|M%K)WR;I z;Y^2Q@HOhTLhT^S7n+0~%zMXgh?1C}uw=h0cj%S{gy4}YpzQO{+&n|`0`=$}eR9Nn zYDZS^2w;iNXIfS?)pN zF9JIn30h$;zivjC_#VjZIl?<2YhMGb75Gh-op0BC$xi)IcjJ^a?cvEa_QhJocOi7C zD$A4BI(0%w;?Hud_^iX~6}mSPDfO0OU91fiWxrsX(ItRBArBD`c*;~0PNuxfqOq%g zonPcgnGI(7Kb)KV$Mi7~zD=Qm<}A&upSVqfZFT&x%PMp3BRAhi%MxQE)K#6VJTG~T z*1!-YpIl;a0540#w0Jr_#jYx*DFZbY*@*~B+TBI43x@SC^__X+UFmP@2{4~h8hk~A z*tv{w63|K(lA%ipy`_aihql5i99!;Orj$6 zXL09Yp(GwXz-&LBWQ#S@M4xN@N$a_eT4f$YB^qhNqcuOp$Osy@5HR1*Lz^e9i8)Rf=)H`ccwOU<8>c69H8_&z}kE<=X>JDdY45F70Od0B~0#( zFm90y*k5Cn1;@=vWI}-L!0e=+{q>WdWHK8SMb(RQ8$31IPP~ek4nV)qmQ@4C#=N@U zhqeddw8qKsjz`?ne%l*(G_R)`aQgx^RFiib)w=xcDE>1T{D(2~g`y5*awQ!Bvb6PM zAGB0|ikEn37etm|T3V)#5D~4;Smmv`No^es5p1Hn{9uh;+JF6Car)iQU3DiD>KeEj zmu8K2H^)5VF_)tJTJ);Wasuq9PH098NzR5i4-`=ZGVk=f<}8=i<7Mo*cx!H>UoY6d zM6lg-c=h(4A%k2N(_zt4Bhuie0X@}Hc&D67)P?T1vZ_tVt`A=)=f2Tg&3KJ$5B1Y( zX^n6?_CkKoI*SDY8Eh_*^VAUHG=mW+Dhw@-6j12RSC38L%=MiH!&ImVuMV&CeQZ#W z^RsQ=VkQ{+g*#5`b z!N-1>dqDg4Q1M_b-nk}hf7}4WX&q3WPf@3!wphrGE zxPDeal{arRL#ffg%LKpp(G0XMQJcDBSNskCsgv<(wM8hO!p~bzt?x4?%9*c{i@gLe z%^#3E)!_c(P+PF!t*Lye+r#-&cTaMU)wyYY9RbmP&Juyw0m;;h zG0@%`G;%4n8}k~wJJK_Ue^SiTWzuDVd$cw+2SbBnf>jLFnq1%FE&DT6@OG=(I6wS8 zVZPMcg!l@jzqg(dn2eo-S?b6UTO%iz=U+WZT~7nWKMzFUNkO@V#(=|qnw&q-GhF3% z%t#%okAo@7CQ&_3exxn!#P^>6r!Lo!_|oY@TLHkq`W3cYH+M1M zKCDuGafH)zlf_(ahXT$wskIv7B%MNm{GYpTjU%`1CNQ%O*-f~Vs4@rX-h2V?H*S9Z z2r)jl4kxRco!hU&9h7gFG^cd0pFVJv2Qc3Mb`5mprNjK`N(_SN5w(jZ!%&eCRxcMGizW0B#Dvxdqn*@xLQnosG7 z0T?UTsgn`CqR4|#{Mg0Lok*;Fwi6ayxNvGWF`X_A*(qLUgI_obp1xqCIaK+8ZOxre zZoiJHHfa>2i5DR8>D`6aOYQtQQQ90|jieznw_}3Mv0i}&{QYgCDn&h@>6~Spss%Xv&_!(j_5wumR4Q6_{+wP`u~@=0 zuPzRwU$PQX>YMqriX({JMGVe44yOmnjx(t0@kj)<|1@`o zdLTOl^(CLr`+&==oI+J|XZn6m+Bg?cl0}l`agc$x3OBodJ}GURRNpNk#`Ol@m9S0( zWtDN5TG(8>3AQ!x4@e1a>J>X_#`m3Z;|_Ajwt46tz43U1_~uKSs;&;{x8qTXD8qcT zf?tPIJzx4~8LW)y9R_C26<-GRhuP$s@Z}bl$$AIy7t5J znzQbDNukG9;WJVpYq$fNxVgGL64S?}shT>t3Urb3)V?|3t1+*+?j+zWZFlk|#|YU| z=fX^eAa*7SO%>bQViluawis~H8P{L0V2&fzC`gwSStqGR#pP)KVP4?>ku}}C)`+=Cv?$ech;^?M{r**~-ea2Iim|#x z-7X~n52#x?Hr_bOS}GUkS9gjMPj~?DcH!pGx*q#gyG*Id5dQPLQp554sin2!sR_}i zJR{c30+QUFbwjA_Yw60|j1OZX=xh%qub9rdxfzTQ0O^nJu}#8;HYz7!fv1iF z&X))4>o}LH=>`$eft)?{u+~yw*y%@SW&Te& zff_Pj&i(Sz=9wwZ_cL?x+0!dVsY&Xh^>cNPp5H5qzU&j1g!LziF!yf?D7NvpS7zDe zWrT3*GWXvi)7)-X9*x?T$n4fuSLE2nv6z|9v*Lt4nO*?I4N_tEbjOc9Y-oIBlGjaA z-Rd74B1%XlB2MP&VEs*(;GV(1V16Xydm%1HKXL5WRkmx-M@J3WR{)}}J9#ex3Z72v z%2ZJkoN0TGoKlPr{9<#=^`XmqomkXj?w(xUtNhw+3!ru7zQum`tp={cozei?#}c%% zo!HA~f|?s*@*mh5<}w8)ry)&Fk7G607dGMVpFZ}MWeuRPKrftHdyP+NNdPoYw079+WwTRr-Tuo>#HR%Ii%dFh)_fjU zL37jO)mUIIv{O4gqrxd~QA^7;=l)7BEw--+tAR^)2e9ccPkK1}Vl%=by!_i?T$|Fk z1(J(am`odP!Qc>+jyAH%Q-%r-iS<3RjBTol&_P8#`?zG?cmv>NY-INPV6fVOs-Ahh zsz`pH3j44!vpx_@;EOM?hC?D%Mo=(jdoAvjf z+JG@~{E^nOSFGAdv)vcyWeObXDXv+-IS#@>CSwPc$bM3HFLe>cr#9G1iQ37V89uIN z>eL^>5pQ>DJ}tNbN762Nojy|EX>k$?3-CE;8iXiDC;%Tya&glE^kN9kGviOI!oul>xNT+Ioam%q1MUY4&4~+T++U*ULRpTL})>4g$cj8CLd61@1E9 zwOusmMil%dKV-BuXD6h7?BrF4&v!Y5^~*fz(Xo1CeH;e^Ycy}zwG+*Y?3Os}D4fHc zXO+*;o%{CUl+-9-RS=K3Jl{CsrRSL>!RlSHC9yF8tEl3ibO)As7h+cH$C8`)>d{XI zd^G~T5*ESZ7bVVaiHx+>Q)60X+yY7Q%*_XiuV~IsLWTx+j8*@vlD&GMeWil!5>l;` ztza9HzGS{do4fd7{l==uQcnu}w1l-fQ;kT8C94hZjRVOpqV?p(xo%w)?06^mLUi3A ztcZ@;ZUy|@20-D8^Hem33dP-ATYkwS-r}Am{SkbJn*{ zz1d(1DZ49BebIqFtVas~$cQ=9L2b(L>@Di;)Q=zf@P#2keXSfg9pB$fKDQXE#f_(^ zjRj&e;{Qqu_I&VX#5I2M#Ro{Vx=s-S+G%=Q&a5TSgwXQ}HzL*v6!ht;NKLbY`D_W8A>xMUVif$z@KVopgc)yA#j zLf1FyIl@hQyU_Tmz*T@GvN^JgCv|J;Ei=ek18s6&XYNJ~Tys)Gy7sWa(cIj8Ft~P7 z$aIKKLQ`d;y#Hp6Gvi(4r4_P+FGsTT8(9PB65Y@Di@0%9H8~c!nL2p(xuDD^b*&7F zs$%5S6I*2t{xMhO{9bbkwQNVRv0U}Tya(ZwG1Vt3<=O|R`^Db|Wa?yl&gvmt ztf~Ur^|4L;Q$cpayx{BFVKwGyO7-U7-8E+nwN~ifXS+xhEQ<@c9hc?-#u|(c{^5zw zRG&O~&i4jMrf%1OIg@z6ob#MVafh4hWXRIyMDoS#gh9^OTv3&yQN4|2pZi=}=qW5F zIW9(m@o0T#3zm@?(M}ws8&Y@Hn+I5wwyogmmm#0$rp z&ek@~%I}ydH=SqAST$J_s$O|OW31eu`8Ag4$rig-{x=QtFDgS|f{ktkk~~;JUZ@zQ zoD~hRXyk4yu45PL?eXj})*$0--UxYGss}*AIZ+pTw$b-6kujWLwBB_ZANyHiolWM- z$wx9BG=|5kj(M8$_Q!B`E82vghF>wBh&bXT=PN#jalh-D8uDM~nx4uFbl`}|qF0hn z2?QLH8y0?{kU~Y8%Chmtws)^b=5LDoe@iv%@$$!itQ$91Eqx4KyKD;5i8D+k-LzJV z)pv31p;&Orx1g0gUJU1EkZn@IjaNrk6_S*W+|QNoS?`9L-)23Tj>7pSKX=?sRd$^A zrf#j4Ke7H@qkh%IzqTN;0Offu)1BW=1 z(A$vl$}<`5r-Z`b7VCQrkIJ$RQVN78yMC+JLzZlNMU|9h3zYCX%ySb~B?g^*$^xN{ z$R;p`;!!IUh1ABTA3W67JC0^TWS&lPdINpTlW=9{b+Vhy*TWD$4Q9M)X|O6O4G&-G zB^Ey!=}jZ24B@g5JlHW)-GE6OZ*r=-m)&cR0{l^VDb&{&0wuFv{~F6YyB1}lLJew=%2vD7>jHtTnz)MB@+Cdlhe z9aA&{tuj6I%+S5n=@`-7aVFN%G`*S;z?g@&& zSbLI&!=T8pnn3Ht`=bb8V;1+tj4=pCV}mP2>iWg*eplc7wmspbZ~6WJi02LavuKK| z`8XWpki1T;n%cSf{%A|+vF@r@N>Dl3ZKt4X*z1Drk#7+;Eug!w@{H>P2KS0jcR)EN zA$RY;cfr$N$2U}oj!RHy>{tjDEOxr(`DribW;jlLz8c++Wx0s&B^2oqe9Q%-Ye@U zMEmq0Ms|ah=_-+}zlH()DQ35sXr|UJ^l%&r0_pA<{rUZGkK$YIa$Nd8rz$sIbulq| z!L}Hw)0#zkp1+o#6=4xm`R_ly>=$aXT{l4&lh-lGC{p0!!=F1i4h<@QF@-=&eS92$ zshm!*%lQ*$YUhsE3XYvnxSmsZZ$Ml*Z)p`VqK1lIOhfkPk^pM){mRVK#~a6)?mk*@ zkt)hR^_-;fGy(TUJlLhx)43%DRh~to-`ZW8vE>T>gpp-A&+m5BTWBG`rrp7fB#3=% z(VHGJ?>n6vEk|l5*I%-PV%y8Gp;SS_wnmYF0q3{m<#!`unCGb*^ZRpkdVeb%If6ah z^$@W6bK7?q5w^pS*F~ke$vbkGVFWMDs7=)hRf20U&GYs0oJbNCEM0f!*W6w*MK!FY zZ>jZJlZ{UCYTGC07+4Fg!Z!v_Ap9J2a|&5FrVME~aO^Yzck;a!3)7G>QBSp|oY#mQ~I8!UXqc`J` zEnoJ!y_Fnw-84fEr)`xxK$X>9 zszHNaWd7LVT5dTFSxY(eFv}2n({kB#i1SB9K-2A$+It(@=3b!pM5k{*vE zz2%B}#SG|u1?SdgFg7)4_Q%f@X{ziU8Dq7CLd}yow+u=PSMG?zVpIhWzvl(j{Wuo* z{mS|MCm^$j4@0+nZ+MqoQQTCdBTu@pXU}~Xw8pwv5P&zIB(Ed+r)S-2N_hnPkX0w5NAzV7S?R=9q*oBSKiknd zyB*eEzL{D}_s1yglm1&(eM=NpDDmo~k2}a7Y=5)9Px3wn>7S);^8fZi!bB!PvL8d2 zcR2sdk)yM;#@IQRw^EvPexeaa zJ4eK~jBE|&>vmwr1@7@0uybnFpV~;796~`*9V9iZ`Y$d3QV-V>OI^#v&lpL?KlU6!mfYf zCM_y&?+7{=M^}pHe&_?NB07k+5#85Me!sv!q+4(x`jz6W0^P_=T+{WZEQOt}yg!`H zqI_t{u#B_k&ep7Y+Hu*R!c3VJLVZGe8qf}p6ljs+Sx;JM*e3}146T^-iLKw2Z8WP>9>E~#I zp&R4}{7+FYH)nU`OIMn0&@uzQYNr>P_pfQ{s#}4Fy0Kj%{aAZ-J?yQQysNWyq8~oa#K_e! zI~aO@F<&}8VwP+tc??zbuCG`$juZ&B)W4lm)k_om^BJ24FIyfxA4mETG5O12z6~+7 z<$k_}p<7@;xrN*l*4?6SzL_lje|&v)RFv)Zw(_Egs0ausNGm9*bR!`h0@4gA&Copz zh)6qxi1Yvwqr?o|AxcTZ2m?b&Go-}O@jaa5IqP@McX++U*4Z3H~5?GTG;)cxG^klQt4=6z!QKm6=eyJe_tQDn@k z9}{){aEMIPuYluO22|3LG(xDl`DVUH=RKl+BOK@M{>2@mw_|{Cj z)TwJ0t?9Wi{#mwqMvPufr%vQ{+M>8+`v8N_rmSSE)UDc&r2fDDa*fj{*x1fEJ7$@O zk^q*QfpY42L5Ra0y1wMlC;R3UblZl6h{4~*b=^HAies&C(pq2s=WCFPMoSYJodWxIbq_-CdB`(jv*`_mwx;b z^C~W4qULT$5w?fYkq?YC;4`{5r|?XsT^qjjVMgj~TPo`O$6K=bWYbQ3J`a)Dt}g=S zvZ~(JDqZO{pX(B6!%uHBHcUKcf8D#uVF;%G29V&pKie899|DwceIssVVL0d*Mtttl z^%Sc4q2_;d9)Q?w%Lk0#e<*n0d)&3P>2#&Z&%AJ;xLZ-N2A#hC^0=$%IEwmk%n{o; zV|vW(HxN~sTv+c&29c4;daFu8ZdX??+g8-$H6_*NY+ERCGF;VI;R9^u%JXl?T`Z=m zVGSImAU;EOx}cLf0f(#`h;K${>1{*xm9w@5bCSfPd?68d>-4 zJlGRIJLuzCca4E~B;SdFjL%+Qn?h#d2RLq?olKp9#(}L;kAf@?ElQoBrF3@B_G8ZU z>w$-Y+=7w=tw}z!d`1qQFbCso``Sne&6|>p=2ArGK~_`@bk)+*Xqh!+SQ`RGHSE$j zr0OEwnijV1CG#@bgH+m{)M zw1nK1<@q=9-cYebP8DNzo)Tm94X06MD=NaiCh^97cnFIF)>p*luOi2XX?0iP)3;oq z)`_D@>H>T6j{*N;cI`5;#@6+Oa6u(by(Wg^UqlVX52yW77)wqBt7_C_eUy&kVgtU@ zP8RzdqBBf%aNVNB(_wU8`Urh)ODK28UoV@))BU%8=o~Rcn4TSCS_M$PJu{Pgjot`- zQwY4n}*u1bKe3 zJCQU2n;w}8T;|q^b+4)jiyG_-Ew2W2S^ET5sPxHp%*S;!@B|Y`iD_8(jB>T8Srur9 zOajAt;9Kx1C-YH=1x@22RglwF++t>5W51f@$=K)Zms4k_dwcY52}hoM{_?w5BbfdX zv^hEu4Cd~5+hc^>@M`){h(^;QIb-p40w}){TCYd9M5$cY#&uEPCwt8Ycf?`-Gx`PJ z5TH=PGi)rAXy>x&GrjA$+NkL_Ei|LpH~O4Q7RY+8SGIj(fP>Q^Uh23L@dE6<=K1q= zeGtClXs4Nj{I3Vb=dmHf+Xbxdl1v-w>wSV&#$o-_t^i*pcFxNbDCBlyt6{Ap1 zE7Z^M*(|Oe*=EV-JJC^55*XT_55~i;(s5<6#A4q>tGpjczIt+_ljSgAug-L364OZ!Q+W`^h#wz?FBFWUQm6S!VLX4xh#+o)L4?P+Nn(7QoV$n;Q;IiP zj<^j$USG;p2iJZ(4uc8PH$#spJf@DF?f~2$N(tu`IC4ETra{ojo2%0;M)=x{)u2Lx z*}B`Q*KvGr zmVErdoh4O_aQ&|&QU9v(4@07ab&Nbb63+ zR?~0r(;~NI=Y7@bU-Z9U$2ob$$tPP;HL&H? z7@ruqEs9NzqD;pFFi22q)7eqekuw)=@!1*uaw<7i0oOR(1gff=SZfOqv;XJoZCm{p zGjd4&WI+6cZU{ST=ky>vQO%5ou6%lObqAwM^bEDJZ2tz%QDmM{^09}dYjbyewlD*` zVL?4!y*@=(@7f}<*LrCE_VR{UE47BYGM_bGIe7(l* z#4pCEGvUJ!+CWH8uuxhRu5Ai%8P3AaT-w;O7!9$tGr@qPF)7EW#3N~@XJ*`l<7CXR z4DglA5lZ_#KH59sV>{9` z&*ePo!rT=~u`HB^aLn#aYAq4VcK+^*auOyhc7JEb0qk?yrIBh$HW@GV3I}lI(J9#z zS74BAwbvCkb|J^1UZ{I^*LY%GsthfrZW2$kHvXMcD6L}x+e;T%n&la>xRcZ_o;ReM z@$|6psnOy<-L$xy5m;wr*Y_9Ua*33s{i2gSc*&->q0QT_C3rk1C}64rfPekN2* zw!Q*JS(_TgBGt5hm%3p69mCmvyU@btzMw|)4oMfGF`8Ln)IsC-mtO0mMvgYNd)sA z{7QQyI1XDIPCT;S3avYkuR?-)W;YfvmP^1bSiHYL7O$rB2LeEg5{z z+GH6#reLWPJ6CBF>sB8M_PWE}Psb&+mM;nQ70P_1UtrFuySWaReLdgMk}xd!xJw3B z6<<_^S~q;F?7TMiv5g~=Ux)zk68Z!<+3mF7Ykh0|Px7JxzYy$m65J>)-Rqy zp2Ux+hM!QNlhTH($h7*v5}CVz>Dd}-10LtWan+-l3~gVdmF6(8G(;Hx3{%r5s?B@r zP~_((_ttI&qnN~871t6iwclOW`#W480FbA4f`Wy&9;fDPxFntI3dlA#tuoxDNmNr_ z*&~ChSPlLqG=CWx_O7jY;=b+uraKrge&^F}3;+0z2+m2BKV|v8JM{afm2Q8&;W#T4 zbj^Wl#nL;ztp6_ybnN$l3C@hT$Nwtk4{9Xj_WYtk*&C5RWIV%`&Rn&@gy^_aWdnT&Ki!(y`@NBtFo%SU!KmAhUAk9%~V>{(P%cciPXGli0S+t*OE zJLu;}QFE$yu@_Z+jHe#tw248Bv`TQh({EPi0M&ulmYP#sV-?5QR)J)OVbnr_T=8Af z15Tr+^K{}QdFkUu<8O(73fFy)DvnzK%hjn)8!|bw7Pwi)}8H{n968Z9^ z1Z&rEah#$=l2jv}RAuzS|G+^Q@R_NXdM#aY#3YK<7q#{p44w{3t2DFk%60LJMvX|N z+ZuB0nDy*mW02tgqOzkd)=&0;Ja0CWD9CHVfAHxe(e|k~s~-Fc{w8Pc@a?p=xVzXf z^8UIvZ-4BQ!W;G`>wJK;Y{E)X3z@160CgbRc>sM`p*c{xcjC_wus3R0^)d)M`hJzkwr~70NG!6PvZN;jZiUJok*JD~DSFmuR^CP*D5%*MK z@Sn+hklnSV9f2t7`6*a{0}S)JpA=2aG*_3-ex(n*R}bS`3TDDz_im^0;qx6;ejCSz8lFkAjNW{c+v)Lfv%vIZS>}!TeE>mrekU&e z6v>cc(h9%JtFYU5+weKlWu9B)=pC-OM(H=(r$-y6$t$B*v16d==RAIzvoX}(ElQi; zs2lrJSER#Bs`e$1R%iPdHrkWUXQn-__o$uR;~MQ~5D5pH4b>|z-Nc^e+<{Q~)@6R?vCI!{ z>(G8;%5!Ct+~N{f^%@$?`Sx-Qm9m`Uzz4X3V!P^5+MbWy*&I-d${KWzD!>T5bsd;U z`=4KdUd^%ef_Y|dekXvO&DT4zRU0=UW>R=#xeN(?R-p zZc~hZd_l0vj=zLxM(}#qyMWzjvGwZCBk5MZXDIrV0wT-h=`<>n!@)!<>Y9${oMPlF z)3rDG3MPM?9JN!g&MZJ>Ce!)UOq7YkSnL` zcLAg;Y^Edz>WEi9OYbmma{AcUk+oX#hn&kHoJ-2d)|;_BwV9u)W z+Rjm$Np883>6XU7-yX(I&5IVXZ;Y0!!>y|yoJb#Ov7ZX_8tpWc%x zcvOC7u|X4o2EdPvV)Zn90svOXr!t%@DZu@b=HO?jId(#;M%^oJ72=T|>x zcU60VxL#I6$YBOrj(~`2`B}Q81fT`~c|()kSpFowca@(7X2XG`GQFK}z3J>U#A$?r zF_tz+*%UwMc)P7gwZtSZ)vzXdWH-ghlTbw))3Zk7y&#z7BT1i5^hJ!^OXFKmA`Sf& zBSMZXUmN=2uh^ccuJN{1)@Y$V{xv(GNQ$3$DP{#zzaTK_F0%XebiZB7WGQ!+78!sq zfH3J#PLSWTKA2|-cwy_#dNAivue#hk;q8SJ@dLGg4bP<`Vl)rzNbXLF>Q%wqTiL5_ zm&pHu8=)fJ1j7#!G-Fyy_vB(8>#(k7Tz`&|J5;+W5&PR(sLlXYS)zHk7E(SnGCbJl z)5do!^iNRj)BZy0Zqn{XPHZ$wbJIpL+HUGgc=~QS)+ivQ?@bm#9jsz#))z3ScXJ(z zPO8HD-g~6!tYV%UWB9wz7jquW=-;_4k-GRnTis5ag^gat!uJr|R1N1R)P4Rer2tpV zbjz2bO@|8cT|9Rd+tszeZMs*M?{H7Z38x8WYBk2$Gs zT+mFsK8H&iJoJ61#Bh7WiSTrq;j}s9#0vZL_bZ# z_v&qc=Ds2-01+lpg(~5KMYJ{CR`vmsp#C8u8h`1f@8)uI!F9i{Gv}bC%!-JEnTD!{ z%e^DS#wwQH>qF!e^eA#o%lrwltepOCKHr{aS_HlSJt8RNp+uAvvdTkL{czjL3p!H{ z#(HN+7AM2LiUTnKpJL!G>w|lALIJgiK?xxw*f6nD?MQv?$DgW(%D*nRY^@b{rtZ-w zw5~5y5H<)bl?~eG))~^4@%Nw;yE5J0cq#d29(A;Gr#pP^I6BUs!+aF=aU9m|7~;z<6G(qDBMGp8Ipt$mM+miB9R>S@HgESYl>)t(*B z3Bv|4Krj1Zcl{&a-0%H-F41cdfC5Jgb0+479bgTk7}uZg&=kwLq|e%Vv+T&32UXTT z8G%NY```U!Ch^jP!CtB-%@(+ZZ7bFZjc|Q?- zX++K~0n4m^zf<{0M9#GdT1#u6;gm0FX$EY{J2u9Ry0OXK%F~A`CvV(lBG~*cpMaeq zsni{_^d9*fQ_g$X3{?t?u=%h**@WuHhfsK5&Q>TpUPw^<8onL*fOzCHwGB&l`c2lzp z(jYa)03T}*mPa5)V!$~kPp5Vm)c4W0k_N8QZr*guMv>$X=&XFHMEs#lKpA;#z&-x% z0S?8A{=DJG1LF;pP^ebCN!yLFHFg=;ZUM4MHVqH~qR)3@g8M{Jb82g}mJj4?09&SBkC>(mOd znQ#_P+$@pQelv^E6wOuc>@e20>2nZ2jvhsZiBSmz_QUZd0xHB_rDXj`C1M_D(;GYW zeW8Rc&mhI4$nz%d<_Eh&wXvMpVpDAyAaePK8U23koKU#CVh{k(UDx}z%oR&ac!Tu! z9FHgOd2Ab0G}9{0l32ISFSr8Ws* zd3xW^k!Ws{gHF9eqANyO^&#h=jbWw0Fvg(|V4QV5MQIiQH04FYMryx7qNp|dEwEIo z>xr&vJ4718Che`b-%J|tVoAE+HEBNzYA248Ad8t0D#{4n1}u6)&D%4dKwU~c))5pR zLC!gc4D1#obEdOBOe}PMFtO32WTWv&o=Z{2OpxHPp>kuR5~>jC+1FpycCEK5@(4zq zmdQctF61-rJXOQl#jVloFJathI!B(l&F1}>cwRKEHFqM?BM3hq&?+P4w^8L&A@LZ1 zrXjauM?B7VKizU_dA)tdp8|{&R&?&%98R9RcYS= z@}9*zn+DzITv(K4v`Cw(f(3E?KTxlEXXuI%?SW)6Y2g0%)5RV~{MT^VuxG_&PgG?$dTKM;`_5rr3jlHU%>Xx8ZO8f8XkvvvLwix81?HBEcdFr~6*1YwM(A%~wfYClWb{ZS5)G%18yVEqp zsH_Z8Yro~^_-ZbNpWhHybuaWBT2>h2DSi!mtqo!vy>!31h^k}MKonHm;h*)xC)(97 zD|k?Zu<~BeIq}eXexS>EfT#1i9kfF{Po7n&9Q&2^DayHjbXvo2FrH?is=AfyIAk)zn->c~p zaA!D2xOwJk#wiC6&Y^s6b;*M{?(I0Y9^BIyU)Aj6JfQ}=b4IOBvyzS%N2Uq9o4!bG zb&=e?Tq%GdSOPnQMvYJm@5Sx|KG`8@C2JViYY2_iEbk%J(D}{hdEd0GgJLfy#kc#> zjG!Kq{pnB%GAGp>&iZtMNFs#%yIZS+x>6G(eWZ`tLDKHJ3Gk+5B4+?KC zR`z;?33(uv0P+de>0uRxm6El|o_#S>)_q>(M2$@i1lU)ICw?F%;3otv32YJn!DU?=^Roq^rii*VRh8ni z^PKfF!(Q3olW~jW;9H`f`~UKv{at7y!O`oL=P8Si&p3W%1J!F~-jx&Ao$9MRr(dgA zt$1jKxT3|kI!c5bEM{#ax06E}hb*U4F!k21YYwJkr8?aKMK5CiNFUr_&15;+QT>dZ zBEdT!015Lz^U2^Pg6#+Wl~U+ZLAsZM3F+Rpw+Q^~jpkLrRc_A{y&8#SKE63fxdkG7 zAt(qfIhX@6!oHaqC)1#@g09OE^v5|9(Ldg*SFjJN`&l%ENq?;C+*syB#{1|p8U1hRj&$IB|VZ`@E$-SBx zfEEqN%9gDCcJ6Rz;$r;gI)tRw-4g5W9PYMz-B=WE7m=4l;13uz>sGu4iW;0sJtr>R zZ+F#%d~3VEHJayqw6vd5QE{l}bMkI^r0_gY8J<@xbl=W6o?|<5-f2lVyzfM^iVVJ4brI9nOAbM3~c?IgPf=AFcVyo>%Q?`~i5 zJ<1TdbpQIjK_(Sge$k!}{pbei$ts$E#@ocqpWHAB(Q(@5^vf#XY(~0${y~^)7(`ygkqt z2#_I@5W@`f!@PI9)xAycf}K_yfa1UCs7jDw##2$E$$-E?7+FLU!26%g_C=kuSf<9m z2&f-5;)|RQ%4G5HLe7m0JYIa;3elIXWIh0qnI%1NjBgPCh6iU-1E6>3VP@a{Iox%s z3OPv|33k>a^kaxn$7sGSXIJ+mgSxU4_wGf&{Jf%&71AY}{RW7vDOme2}l z0U?wgyUEY$ufjj*pfv&vpFbI0+$19hf%!w<-X#uk?H{f6dmP>%vpT&((pgoOzUDaT zpryd&afPlq?kaS*t3ZOX+o~O|Xm_)V$KmWos*dJ;$ZE;y6n;M_2dn4o+hug@pcS~a zG%&gf46iy1$DsjouubTx2m04z4d2dika4DatEXUXqgO%Z;w8m9m^f~*2~M<9u}OG& z)s}=D;wiByU;v_7S$N%IGI43VciInAXR`U4NZf4BhzO{wP`KVu`^G7te(5<6jYy=* zQ3DsJrN)gDN3^)-cZDNG3^~!n=bsK*{gcBzIh7JLRaQ-H{EB}v-b1qfstnK2nky&p zEK4c$VzEBZf#mkk%&?#bwc>4EpkX;!GQS? zWW&V5Ns+T*4r%buMHxR`FD$}ocd3l+)Z5f#15CeaU*IhD&rEcD{fN)ba=F`sPT@hH znap(4Wp|+9r%LF$D|&Ju1y9_CrI3{xJO6>3E@<8pfoL}A`_R}v6xK-QxJLbsZ zKR0zF1yFU|I+Irs$4qgC&!3E#4`n<%-cv|hpOUSc&uLsfG|bj908Or9bOdB^ z5qESQoYVc(;qwt9+{Qp)zxye5^&EYR=9~EGNv`WriE8}F)7)R*fQ}mE@e;K(P`GQ1 zQ`S>1U|X2iDJTnw5uOR}wbYqW-yL(7>N9OuU)oPol7_=mny%2*Fm5_%0l*70e`A2b zk=P5!7+XyiP((KzS2|&D6@T217=)JO-UuaDL zn;rvxV?i1%(ByU?({4#vSL9-)bzX&KJQB3Ifs}OYZ3*AGXCSYueqhYZ^h0zWoKuw%_)ke(^VnOTm`qip2Lsr{*YSX=S4|5wXUUZROa;vf$b@I#@l|0T z!W!W28IYJJ*Q-+Z9m;2vp+~xbT^nV62NPvKB#p&anMfb)dIzGCX5(-|=zo+V5uT#9*=BzUBw#$H@94o24n~Sh z^fQld#Z|%Lf6$xJ{JW<0zk1xCEfBi(-{CmXIV>U(WQ!_N_2qC^Mpji+u_u19g#%^D z(>Sk?iINv-la0ThTiyg0`=Vf=8Ti-U+*qKO!Jk<1E zGGcbxVHW^7@afL2@nu~Z1#mY#%yPn}OHOB7o1a0U7)7)baUUhk-A4fwulu?!UzvDT-dvKDaZgw zN|Vt$c87@QS0&{ZD(%ovUTwQ{p<3f^N@WnH!Yx?>s;TZQ=AQDdr?p_egmHb)T=RE#hJ-M}WXL=JKo$AS)vp{mGe1FR&GOS1?2H$e;_`mRg~Ky|Cjk zHRD+-_o%MmoHAm7WhhU}hGV{-USJ(P=0{{p*Q#|jqv{^R+2D4^1^*aj&sdR+gvZTRnZkokbI$b&|rbTn_+X6&n< z5voM=WSC?Y|6Qs9$_%f$-gB;tm?G|}s3I z)kJ4AF9Hn;_U7`*sDwJ!w&h-^h&ciWG-@d=xjY8u9HS?*YVNCAZFViZI25ic#MLn6 z9$NK|E}(?~aaT+nr*!$P#OWYCy$4z2byUm z-suM1c9^bM9En|ohQ>?JCo4vGZpMjn%Jp}ixUDWVwiGL(QIRkKC~2juH7=;~BDZmg z@Q_SVKy4mfS_#j|y(%VQSvtzs&l8-g zLb9YN9>Z3!kRM51s5w>oq%3mD^$eY(CvnQ}o_F(~-v7V+(|M;x1@TWBhudInI`p!R z6YAG6CwF#=)9USXiW=ZDlj`614ExSEbV5Y=!3E?C6h(vQxMaYwZv1demn|_z=HQbtnCn3hBH$Qhhm#Q>vu^(x>JGjRW4&902x^OkV*Sd{( zRtqB3OG)tR2)q^;t!dx^o-EzoD)bKd2&cU=^n7&uy_kDy>_T=}K=j+2=^!NI2-3u4 zHpuUh*M?!NkLJqrFRE1V@b(QF&Pm}?96GOnr}JN(G=Gq;|MS2&gP-*aqze|D89gxW zkrv!_6VYS-KBB)E?$Zid7!Bc+=*IVJT%q_VE5AXeU&Rdb8fJW-dr?$J|k7@qTfBc_+G`Rg_?Mc*+ay90GI<;B;i86Ro{SOMuDbt+oN_^9| zQUF;dqbL6+A=}IqW2;pE1FXpzee{|75%*1;-HJTq6F_tP|G)hopZGfbz|5i=sgBbR zQxh1j?oB6tf64XWBU+X`JHe`qs+;2j!IDgZ_ZJV%cbQz#7H1g_<&}bd$(x@H#(dj` zn_5B8G5@;dzmd=_Rwl?ZPZ_=!{gT+eXb9G=q28b1U4!-p36|S#3p3a?l)lk?=xe2| zbamHc{&E&-)SRe7@hWM1#fXJ%7ALsGl*FdGa=7tQ0*H6Dz7+VPT~lSvpxfW57UexG zzBZu=739~iVNVnb)JTjIbhbqCBqJv%){LwrvdB*6MNaak{6o83pS z?2pME6$gj*qLy82?SuqoCt<#p`=4|;N-%dOJ4I9F0Ng2z$4RW-&_w12XucE@kj;g(-Tg^qzC z{lfinLao}j`E z$SS2LEE)t0>=W?tY!6=JRA94w*Ro);V;lCF{N zOp`wzJ$8#`W24OI^Up-FC}Q3C_qrxUZ`9Oy{U|&fS(dyKWj+#YP|I{4q{)a+rq$+L zxTs46ltF-UA5}I7I^<#PLgr<*o@`_y_X?o+trKW6nx3sIwh@IY$N74Ts|2p(LIQH5 zAVqbADm6_RNkhX< zQ+vC4)!J`X<`auLeZP#opQBctU?9)b#bPl2Wln%OsGwB&9bAo`$9Slp$85S+7)7I} z10^^7IUI1r-*%yT`RlLmi8;0U3ZJ`re+#QaGDZ@uRY1lSQw4&?Ehu^uyKM(u4E&PX z<0l_AhgiMnsWrXL^`^lBqB5rC!Sm6evK?LNumRJ$=n`BaNR5A8KIpmkO9@>|nJVZY zYQOs0(Q4X0U5lfn(&NSM8gE-{l^J&o)ac4arO3J_=%WJ4zjUU z+-6ZI23sFim2>4HUwa9l;}&u~Q?#q;$47?|ICvz7fu1&(=OmrOx?>!jACA-At{!r$ zF7~1RKg4yFehYqOrg`8Nr&BCE_sWKeGqhmO%G}&yb9@zOM&?#WRBrm%fimh3=`n#o zwa|qbFXIzZf|AL5h$3eWJ~h7Z2{o9mr?t)69TnrmYK{`|&r4b9JUm|Qc?LN^eToV{ zeF}A=tn}-ag8a|sELOirmi+=43f zlJW&3xOzM0a#4v{4%3B`hb7_{dct#Y4%I5N1g3k*FzjU66DYefz&R z>k{Edbu%)*nSMztDWsaQoAH~Q z{SRN)<;xp@m$ehX2Ji)n?MYf@Cu-+A92xx4qhuFaw%bdC6uA;9r?#%9h9Ia{MPh@A zc^adi5d(V^+H;bl9NFdKWyL-vBdY zOTItHCN;sy_&>Czzvi*IubJ_d4T{5_-x3gV6XAYU8=Ims+W!^-j()cN`#;* zD8s`j1Bx{VYO7RPU5Xs&ct@}ODPa1KgB7F<;_DT5*Y5Eqi%*x2Ps{7csX;*fdW>On zxvSaO?-SH=&ds{W{%NV2=SP{5rO;P!HQp%}gBvM&^)S8L-QOWxE)LU!t=s+DRui?4 zXMMv*X}pb-UP>XkJTH83yDU#&P9kclO0SAET6eF5?KL))4iTKe6==k!epL(1B^8e?H z4YIl&>xAuBPyanp^t1M!QXly4hkMUwPht{XMJ(Gp^qzP}y?BZ@8DHUUn)H1NyAq#( zwVlpamru>J@Je9-KQ-i4OC2zIbg@;86$;W1#=i&iPlT#3R~TrwsU#SWmfF61MT@Ua zdG+y4&8y9?y;*#%W70_RUX_Q&QD#whc)~jg&Pu6QECt@(qrBKQ$q*;0*LFy=-yG(c zl!gpdm#7zPCZHT=iEF&Fbc~bqL{*yWCH-5c(G%B*Crx(NWB543b$d}JfAmWc1Aoek9?*U+U7w|M~QigvGJ{BGQJD&}9|)zz&72obPNQeVa=TyMAeFcBChh~y0rgy(X}-!ewm z+)UP1$2@&CIdY-K51IlPpfTerkt+!0Z!PbsJ{?=O=+TB61iIc&Ooh|Gb&r^@pzts{ ztN8%5H6AJXergtwdPa?PtF8S@v-UTwBA^~ByH;_Fo4-?bKxQhHHaktOj|{!CJbHug zVxtcK`f29{3Uu|#xC*Ky+-V8C$~e_IQs48PiSvDr`|vYh_iiG+pQ1cBN=Kx*sjWEm zi^8i&!g}?f*0IHE@y#!jfIg-F;gEnkTO4r>kfHvfp0cp$=^Nm9K$OS>Lknd5GN+f@ z8Wh56$nH+I3O`!jPQi{J{xEtnrCE!Ed`hwZ@{W z=|_=ueiD^CCb#Thk0gZZbJqNzn9-eNJ&Zp!2!QCF;}+6?RQ5nWbyYr5_2 zt+2$m+?3*~7^-iqD1XDe+DcvrG(xiS;mwPl%rmn(vd^u*^CUUeJOQAVPtN$|@Q0S% zm6XDjLFwOktC|KYj-)XHLN5aLmfhu`fvGRoyjYWr(kd%U2Dc=A@uNe|8M`C@i7xzK zBbh`Ulx1jTdK7w#X2)4-UJxwsL8(gsBxDTwStzdds?Qg+3miAlSVdw+KR-Vo%3^ST zIH|B8iu$>o(cF&wb4Q`KnRv{wJT!X6+jy+<^wEWSvy4x;f?N%)#u2!WrBxorQ|VqZ z)#WaG)|RAf9w#N1ZQY)KydHcRiFXTjB(W$-Fsid| zTy=WJk8FUA4oy^7g20dd=1dd`N}TQ?+F=G`$$LI7)|FdIhF>jH=V|mN0^e?LA!mzFI0K>`3*f?KLR*iou_xy)L z{XcX}& zlkyj}V!qMgdE*1PXRxZ)7s2%Q^7jT;%*X1x8m4?##KGqN_kA;sY6O(GbtPkb!mmlS z0Kru@>BY8^`3X#(21}SeT&~voQLuEfmt(e+%X4C+cPPaDxtCGAy1nkr*H z@929JRI!3R;bd3-@B94E1VWjh%>Mm0;b>A6+QxE?UwtfTG9WB1Lk;IyLhWVQgDKoT zml3p76VJJ_#Tk@V-_8;WCSmULAc3`(es7Dp-@Z+9!gNDgYAr^eH1oou75LkaHV+Kn06vz>&>wOCa|w|K_QN5UP%t zR3ek39S*LCq{VTu3<#oQE5dxgTK|6YV*P>3S%wddN1c0CsRv6EY`HB&(hKU?1{**) z-Lu?DCK9}GixyZIq5MONi=LFybsnNc-Y*3w1b66T4Em%~kXNQY4|)~xR|z`+O5<)% zV&8YpKc=tmst_h%An67swqbm+YN^O8;nW^fsmMxb*HL%2v+2d7x^9OPCk4n-O;|8Sx4Q)3ReTR8W|CQX&)NVF?6Ae` z{q=a7!-OtTANk_j&3mR6QJP$DdFW29Pi{CX{mqj0kkT;%`drY7=}0HdyN-3jN=pL!jGqyL zr+FcWN%4$!V0!KUw(94xUHY|mwx6|4>)&D+eG8(1$@GjHq49FZkS}|F;3^tw((UW5 z)ozH=kIxpJL?PY)Bu`#g88RYmH zFnPAPPy2#ab#b*pABH69yHctW4Cwv_$0(9{W@wumChDtehs(=r(Dht@6I>)`LebNu z?+2dr^95=uOq`h`<^_ghc{{lEuFsUlpkce*6jZjA3AQUuMTPVbeoE(*0Yl|~`_ljK zEaudog%uKJi4TLuDzwnAf~%eMbUEsE-)jKnXGlJS|3}wX2F0N*TL*^#0YY#M5G1&} z1PLK{g1fuBPjH9e4#C}B2lv6<-F<+;`R3ev&Xce1tKtVWMHN$9dUvn2dbN|Q=<8|7 zcoWtJp=ld(F+K@yDI&@8Ts~Mjf4lK!g)>;ldifz2?f(v?9PrfUvG(mwvYyU|wTHx9 zTvS)7dZg*8?UBbLNngh%%$Jq-pb(ja`2He~^-7j%g;SqOsGTH*P90ZBechT0|9TA* zYuh+&ZQbd#@c$k6=0551758~pIK@&E%$Ke=*_t<)tMlzekzB?wTtzd@YLM(w7@Bwd zyZZr5!ENF)pREF-kG~XE4rJQI>x`M_Qwql-YI0It?zK8wRF33_pk|Bxp9hK#$_|7T z05sYzPV!lSIWvV{jtq5+O52_^Ka&GdGlTNI0({4Qr&-R`i@ju!n|$1qz?$T z>%U0Ay?(%9FkZ&lf^rIs={OAJI$Obklu2E`d;=$V;g2PR`v;rH}Fx zv>B4&<+@P#{u$6vn^Dd)K_3-h!@kQ+bTZ>h@Kfc4m!R0KmAPKMKDZfy;npc`S>b#F+9 zdc(CNcoK;$`T6md6HV{+<#{hoBK+hyf_ zQQg+_`DubjyB#{H+YpH3l>fON?fr~}vu0#T<45anx*A<8F0h@)77mi+!9Glau*NGC zQ}3q7I_-b;q9ZFirgLwvIMg!%+sZE54(r|t3ywcB`&O!!b5yoJ)$rPFh-70nSg%@S zx?L;6Z9es<*;WsT((CE}=Px7z8;E_7=M&O$S@SNnv`tI!gHJEyx=6}8vQe}Ggu9O+ zvHYiQ>X|WL?_HU81Dt_d_4utMER|mF&$_r1EPegY=<-G-+v4Ek>Zr2jxQ3dOl}Nqr zADDMgF)1i~T+mXbTq|Xfbb&UD!@Pz7(C@}>GOEVzc7O@Dyo)b6(3g1M1&37b?HID1 z#%04nBbQ!8i)xSYa?5S9DS?+#TG>43l;QM4f)MdPZZ!16cz+T88?jlf*pg}EtCnRBasor2c77vg~Yb*O)4bh~jJMJa(I_@t<3OJX|x4chwR&O<_SMP8t zzW10dIcWFuabcQC^?#R4kQ{w!cH&o(0};9gt7-E;%OqWAOE#hWzPC zPmjea#$217O~6FSP`Zv4F-I=buI&%o9f&5AuS1~+;_0}>G#|uDkN*BB_TJuIPA>Lo z+H|z8vfcAG8M5v@;8pGAPH{ZPs5wgJ^QZ~#LGm%^k0MG6*+zj*LU)EO1->jXJU%%- zsynVB?59b^6)KH)F2KWB8Cj_k1Z4kgX+@_-K72s*B~7VYtANPb7m55;Uf|(d_Zaq) zu>BO3BiOnzd@+GFP;1PLZvVFRj0-%@zn<#7*y?Jlbk^mM1QNq|S$l%oGRsX0*CqVW(>UiEG0uN@rnE5=Y0wll;ZvzK{kT;40UDw^0%i?HgN}5qjGmOV{-Lg|S zJzeUJ@*_2$hG=lCHLng0pwqaO@F%j6H^3*G!a_z|!@~}I|2=K}z!sh>bZEMAmrltU z6Ex0$F2D|rOz2xN@D5lC8SJWCTvXr95-Fw)B6%46@5ZcMG0B*G60cQwRB;49RqCn~ zj*ZYIdvYk@%a01^1-b`eR*?96sTjDbcdw)t43NW$A_cNAhi zr@^-CC$I_V=nmDOzAc%h`?$6*Yso&kklZ5vLE02`-%L(?f|q{_5~KJ1j&Kcbs4K7|5up-q+$TGWLmRwyXqpB}RjUXZ4*gG;KSu z`pkKvsT3kRV<;s@doTp4qDjw65*avc;=GCjr2deFl6bFI*jog}!jEZIb-sPEanul8 z+?|sUMv0GnLmlx%!~8XZ*xaV=9(Q_m)tU!Ovw_sm^S|cMhf)7-_!X4nJ{kY$>KLIbm-BJ|hZ#3V8>&behdm-$os}0JBp$GHqqp zUAQ`V(cS4>3l3#Xr45b#UX!|v14BP?ZRrpk_0T3Y|IAr8`DUZ8y7(Y zip*kC}rkZ#M`*?dMt>o|hmNt>*_eB8w z-&_7C8vEevjLYr8WxisV#ukXfC<3Zg;5grg%kCN}RgEhvFr3p=$)wI5Ez*jy*yV8W z(KLu;pL5FHo#d*FLoR95Lhu^wRD}ndRff@u6^uoUKLOStkzDS;1m}O}s!m|~`hNVk)ZE58R^2Ecp4E-cuVH#YBoO42s5X$6nHtzH4~d)*;}#aO%8bh$e1N%~h>>Md8v zAD~aO2MWyk=)%EduBX(;vg8OXkH0C1z*B2}!BRzEleSBAgk8 zU*~2H1X|(??2m6bX=Hz@ha%T!`;d};j*BPHgXR{!2oF3uSY3abePzM4RR;d`MLpeA z>ZKF89)uU-&Tj6t-L*(zs&C{U1$?0gHbq|eg~vW4N!vZ2fXm8_qHGX58E_API-ZU^ zxwj%TfHxZ$H34$lH*Ko}bzM_uTvZ(om}>X4s)%G!M|u@oPpG&@=9>e3 zSzcBkolZ2gBk%25)zUfpMI6@L;X_qMVR%39C_}EI`c{?{FhHkw{AqGgC`sej^_*wLG3{G&>wg0Xhut zJ+Bwc53>W&SvkpGhTl38mrY_@ZV%u7Fu*4=Yo?kFyHg})c{y0Fg-PQAEOfl@cg4Ny zRURWr+52U(SbNIecl}t!8%ezRPE?1f#a*Ur+Ys`{^KL@lQu1u(r#!2_aV#v60<>-9 z0*msRt(5fAs1urv6wwjM=ZN*FIRLnLulc0_n4t!VDoDokF683&Gv^pE-3M z`@Nyq4Uw%bXKbx~W2@Zj#H?b$=%I9@q(jTY#LHn&I}H#&V51MYwE`aA`! zWCf*RIYbi*nv@+vV~jRUMm%lnoK!h$@Y8aGHRthM8E5c2QVy0vKR$a9}DRZmK6zdF}ykIqlBdB8;Im@;} zmS)`l^zR(@Iqr=QSvoy1W~0G#9UH44=iUziwIsDxT!ZhuS-eQ1T-ap_PLeX-)PTDI zqsp`;@o+!=A#4VozJ}bNJ5niX`Yv@#r4|!r8Q_3sg4W9$hdT@h-gH!Lez?sG%kdr7Ypb5niGo~whBl1s_tc+`H5Iwa4dzsV)^4!?WY>|t?Ny{W9!hW zI_$b3oYCqfNDGmTjI_oui`Svs>#FPM)VNTsu1E=|!&SVE<1&fs%!e_^$)Yvs%eI)J zBIef1?fGk0W@U`>TxG{oY>3kk=qv(dG)&~8L)mIZK2_k;aAhWruDAJ`+bJjgZ}VhN zukCtOS;$PW!M4s4?UpvDYI$T+bQj0?vVqWUNQi;?J4l}6SjMNcaUGMumpTrg1&@^9 zcB=@e%ue#hSGg}LEh-VHR(sn~;c?;xZc+M$CZ&t=ek zvJbq=$5pjIIXq;g?|7PGxWAt6jU&WC`J`7Ak_p}op7BFv!o=3;nTk~kP$qIrCU(2; z!y9!;F&cD$LqcbpA87|J?awURwwqt%NhNC5I|O=VmKBBc_jgPej29t~V^{rnc)C&5 z4UO%NUpRGLCCS*o6o8CQhk9~7pyV{S$VDKPx31-OU6Z(iNAXQU(rKn zX0_&#&JSN|SeGU4zNlf)7kv6VRZM!*5iZIxV6Qps%8UaVk897kc#eyLUy}Hv*!~mH zI&kG`98N#dKv7AB=A?2@hxfTH!F>cXXUd6SDb`F3O*hYB8IDzrs?JmtPj~IK`EGR# zdCNcWKqFChp_(`SRu3PiE2%=6&@ouyn0i~Z28Ky%)6?M+a1bbHHx{#^jw4+TrcY}cN49h#K;h`1+< zOF1TscthQh*v5sPm1L`l?=i@PjMkDo2W?+Y0V$D0+<7=D{Aj07+y21H&xyG#EBu*& zTLN%M-L$quW^0a4de!Q7wxW_+%JUs%^aA7yV>Pk!AF|8)yYp8tV5Yv=p|Ic5wn><4 z$#oQpfr!9TO#qQanECe89Y<5f;M=9&WF1N;GE~byHE&+Wi7X;FEfpTiTNggPi_2yn zd)w;pfk0y{W_%Bw;%BW3UsbxF`yXFTuEdPa-uKUMZ0}V6^uVzAAU|e&DF68(I_!u` zECZlrU#l|bF*Wx&{L(w_jX3|jzO&<`O*3+a+U@~gS$ym307f!9jf|Z?`!l9n6-?3b zQ$wr0@nugs3_NWz&c!N=dWK*=bf8zuOPQefN96glFbQG6(*0Vy=jmHnNV~3e-&(6J zkfi*@b2lH4e)PkyRsi#~$43VN@J<{GHY}m)X-I?M^GqBL)OQBaxE=>0Q`T|KU3hcs z+e|bcNg!Bol>2h`K4jo-QkaRoF+$~TS&-?dIp{?w=tzIJpby_~BRrwd{2g^%&6@Q2 zV3?TbVl$vbP>L`FZ!H^p!1qj(!eVEBvHSpi25P7!9P}+cF5v z83Kmz^>5$b3nBi+QKnfQpY54p=;w(;b8)y-Pvxg_7UN@L7Xx)M{w_0cZVo343{cfe! z#c)`^Cn#;CqNzKbo~WfWpo_cavojRQG1*n6FC+K>iuNRUK#J}uT|gi@a_n<~7-50+ zb#dom(z-RdthvtH!7c{d(h+3IDZBvv4p{?WobpL!`&HNijgDd3FPCnrJ~Z{kH(;g> zaCwNWW*-|v@q9{0$jtiwdak{1Be&C zA%-_!!8WQixkQJ1RQMVek!lRpB@0~1hU%p=wOvradG1!tM|1@9L=;Nm0U0E6Yc^+<(&X=VXi1b2br`1vmj zfUndO7i+zqm_%~p8N%xkG41r^Q@KJ5hyzqzrr~%rVSR$AZBK>Ys>0Ul5jqZfFy6;m zgZG$Ol60Mwa~RS)IlkH2m6KvbvURGtJq*#D_RRU+FTK`oNeBtO-#^^hRE?sdAhKWOnw8O|Rk zqv=(`_lun(LQ*QgeI(o!XfKsJ5Be11Bx{et_lx7@$mRNHSf!xqu$RyQQrU&|z-GkW zSqF<~(wuQ93|4G4j0gF|RpWmVHD5HDJIZg{j)T)EKU%Ao&r2+R4E)U{x}kuCWjU2M zbc+Dy#bhT}EAk(TZ}a0-p=Q8MYq0xSD9a96@zf362nHcn-_z+}4qZ6;@dxSnTuOkU zL&%K#+Hs5x3_6_+2l~<|4d9e^`rZz5AY~(pZbYdzhHKw&kLD-oGBr#y%a_wI0@kXa zXRYm2lrJjV!>rGo)x6HW=YdDWE4ivgRK=%k?K4U>f3!b&lq{g@FC@6U z>SNRc%jK{6h?M>00V4#5=r+|^eR+fxRqcJ$^_SthpGcV+j;{!fZEO#kyrhKfIx-hI z%-X@SN#~fFXLrQ*B$*|IIosbdhdcL4&${gJqajoD}UC&e7ZrHTfDgFR?SJp4eJ49#I6{EkeY6x#_44?ve z{lMhLdfeg;X4E^(#cENw!N;4Mj4^R4zUyx|=mH86)vY3e+$Bk9FB5p$F_ESuVM!=| z&9+q#Wm3#EUAjLnT)%U%TTPQCPkj9rkT~QXE|o_lB+y)(zJ4pd^*-I|vcpV7uRHA2 z;$04`_~(_uS?jg7A)hakU<=8N-*$Zt!q3p{)aRbM?)j+qbXO=W9kT0_S}U>B-)~jn zFS7Oaz(y+(P*c{_4(WKL?}h&PiaQu2+qjmB98}flbvPxSoLQ7ywBOQAOtEH&A)HQ^ zXz3v+TGf>YdvYNk-=n8BM+|)7xtwTkcE|d^5Zh-AgqlEy+FkD*{1Zm& zPb11P6A~GujcO}Z@j%Qc|3`pXYIV$6BgodX_LPb5wZmHqjEETuZi=aCdCYo&yqdi5 zdOdFozs{$2*Ce9;WLDq%&Ve5Fv%qEhx843|Y6j-{`c}B{SC=sWHk-aB(c`eN-~3?M7wa zgD1|Sj2qLxK4swJ-PF|rqq9BKKAx5&&||0F8`KPEDGh(d-dc{NkZFAv7pcCDUJ$-F znnCnQvIQ>-z!XDD&Eyi={LvYcvN~08q)R5Q)qse;rLy_up=<|d)9t}{?qo6af*Gim z$y4d<2Wj%uo}Jnr!hrCj;&+b90-Vb6F(Mtbpoqp~93m~8;bTymjhxJj3ccqZ@4m{) z{=C<-vqa)w+h+$T`_=#EZ$POiu9M^bg@@NLhRDai$BUW-B@au%o{zCYTi?vTq33yZ zW3KYjHyKqHkIy!lLJRq{fH@o(@5f7s;L=jE;_xAkp(I|jedX{2p2-T9q62r!hZk`o z=}8K%9Bufgj`5c49qVz6dtX$|rizVR16~y*uI;CWdu!W*6O|njL251}zpH1-+0t2P z8i!eNy{qe~qN6)HCI=l@1(@2oPJs6zU>VtJr3{WMP#HmkT&Uqx>5>;tOR^ze2VMQKfq z4#WGCwssU((hrt%U!^E@#L{@fE137ZI&amU-WQPgqDnc^`bEJHi+47jE{z70oh%C7 z+!=<>hE*AQMklXz=01Y>fYv_CX^g;wrC~Na}Ml;s7)!;dkI&OikpL=L@A|FE&~TIBV%*8nO$ffbKv#OB9zU^!D$pJbCOGso!scFR zXX+-wA}X#R;<5Yfslg}gqt<#Zy@IqBA2~|;@O+NWg%Z%dMX}1VSXH~0kZto^Rh&M* zyE?&K-bT0jbK0*oXp=55bj{G$Q!tL3_RZnc3P*JxQk+P|L1yAR{e`?Um&LrXQ~m%z zgAwaH+19^OW5Q8ZB@9L^3uB2!%Q5(^>x<3Hqw}`>3>S^7;z)m_cPgMTl)OMZ?GO3B zpy&0M0btrqlpCNTS&G+mu8dYT)%5kndvZH0WPplFeB^67kd<|KY#VVXjzj%?x$LRu zD;2lI^I5N}y40hDnNht(O9@}x2= zyZU@r{IifcGwOQVea#i+4j8>k?+iq3*#zB{&h94><>8cbe(2y_V}{#*5H%>Ol?qgW z{oXdkt0P*Zsur7zfNm$s{zhw&1vvnpHpCSv0z^;XANB3_PXQRLl&B*pJ~H%u*6Ln; zm#xhe8~+p0*ru4`>Y8Ay<6`yPHII!NkV%;Vc#H&@Tj>Qc7A68J8(|ipX{Lqoq1lri zR@FJ?=Ir{D6SyF)^%{G9*llDsG=+JE_ zZz`~uAL04?r7+|6^=3oqa5|jorQm5q#~dl#SxFiJNdnf%6{HFDqv6c3rdSj1tk<$Sw_uElC{|)1fiJ9a!mIL z^gt7|vggfMt*`nZd^V$RJ;vyy>7$-J#<`PUu4cNj{fB0oq3nDmQ?(XRlgL%GAMdEG z)pARIainFvRTSzc#nfqh;G!eE_%o5mKD*k{9d+2h86;4bVB z44-O@;nk^Tb6%z?>yIH4`IL7{)DiC3W z{V-yQW#FX)YrbTtp{C+qlG}1=u5aVjjj>8h;TBeE6`beaL^RkIOF83t%K9o~O{ZK> zRTNLHPBC({QABQyv>`52yFre1@S)Lgf`BLZ7rv|N?}D0KjqchUg+_0j7_y6jNkAhy%V*T_Ppi=FSJ`xW0MA51bcFTkXN9t`r>$ps>?ps3)lXj9Hg#IUWCNx0 z0sDU!u2AbLt3U73p!1xS8+U4rnQ1E+Ttg?;zr&hqm}?|2BXaBDakO4mtjnf-80=9} zId*o8AJ84c*_-GszawFoX#BM+U`G*%f!r$I?dMc2P?3>cJo)i+P9q%u(G=Goq51Ww z2X)D26qzg1V!vWct1@NF{7on~Ql@tHmLK1;>RD^V#)eY7Dtn=Eipg2cm`}y!?2OT7 zrD=Nf)OF3+re#+}eG)iVrdg}fdl_jmUy)Iq^riv|d^Gr)BPOf7S?<{Ac;-6m@u(cY zqj|l`b zB^K(E8IF&_PS5u_LGbKfnxdQDjIB@Lm9kdYSGKDyzAS`97rzS4deLguG8 zDhH|GqcO{uKWR0VlD2b^dg8$>WC+T-G_N%q23zy=Gx|7X0qykn0`>OphX%;l&q~-L zZkErQraNyP=~u3drLp_u5-`rnAgUDlLvmFFOVL?$RGri*Xljivk?QpZ`8b2C z)PM%J>idp;&%e|+&0Xztud@DW!&`pO=;py_vZ-cYQc?gg_+Fms*{r_FMQv0B#UzUU7LRr zf4wRNHIA~}4)Z%ut3yEXIvsa5p(qcX#vc)*for5X?CJdFFze*)^GC6`t)80vELAI` z#h`-vytifP!caAgnDO~yJAaJr;BP7#ak=DV4td`yfAl$1@R@vrb5r9b?NnX&O{2Xc zMi2BZdn|3Cx&<$pH9q$3XR@D#T|l)DG^sBoA%_bAb*Vi;O%?pKx383_6T}b) zi%i$+$I{sCE?r<-f6mv7`YQ1|cM;J^Z0e(I{8;FeUG)mZrJTB4s!7|y1x31M)G>38 zRidH}Z0+8t)WX~vefNN>u20IMUPusBVrKm$MU!D<)BgN!F^`RI&d9`u++Z@8jQS+6 z=cGSx=qDj<@rwFIW*VdSWiC{|R^^+HJcrENIi!i@#9}vfhBF6~p^(oiZD&i(wV4!z z-yaua`%F$#IThE;ul)Ds_|I4zjcWCrF{gDsUU&s%1s_fa+D&>MZi>xI5h$`+-8rVV z+844`h6;NjR$7oQ|DmA<4F0lb#Wv z-5^hClTYz>XQe%2~Z5F0UR+&a{{j?SS)lg|EeeykR zY`bP*qUQMvbz>5BSt0Of8-& z{t+QvZ+44@jQM-K(`9}V7iVq?ZCL;frXP)G!)`T!nXWyQcPlxQRzH89hmZ8D+bYLYHh)?3Hz zny0@sjsmYI?0fg7nXKc2;JU79<%(@LXY&d9zg4@DSPOLF{D~ve%j)aw4@h^uz|q8m)GCr`_M>M(51qWlE)p zZ^g%v+I|J+#l@E5{j9l8{S!$vm*$~@yo|1}?n7hmZ5od58Ny+H<`;-Z?dNK>Zi5SW zkfJD?Lm6%Ix_j{qHTpT8X`%-P5)9Z+qP6qbmJTRr{j^>mbu|6--upAQSBH6|xL-W> z0?`0fe#g`7QYu2$F(&q9rh@D4tSS*FPJryUkEyL^P@YuEO6$143ozEaPwm&C)-igr zP%Vh1atd$l0lwKWm4M9DV$(;J?w-@zcerO#24hHi`tLMk*Dt$%k-F@x=Zt} z)NJLt{2;DD%8ixd^mY`A1jlfW7eXG12?uI)80l zP71r}4~B8sS^&-UfI?#bVd02$d8XGlqC5;ImC|Jo9OEP;XMsVy;~lm9cjhfq@MWr2 z%kt86$zq?PH&902&=_4W`E%HXfQum$S(I;~r{8(U{v7xiwYa&ujGGSd$t?5! z3_9E%AS%0cU4<3{=b0uCtl2Hb;JwkivJjOBBw$4wq~Yw$#^1B@7z*?Umc((|XvpB7 z;MH;*jis}6r6*j`@(h%|hw4KAtu{NN{6*6U{7_q}zK$}^yHCviMF;?8N3dYkw@a|B zS#2|ioiN9yHKXg?vNOJDe#(x+C1lni{6O1Tr=KY|jp`g(spbc(VeCkyTx7#V!s0_i zQQF^pn0DD%A^|pAz4CkU?RB(Oc`qIAqL| z3%8f4@i^*FCc|NB_1Rg@tK4f|wFEq73KWDCdYsEo-@MdR0$-r4Ip*^2<9qjyA`b=J z95BOPSJeY$r=||^6T&%pnt(k!z0;GvG^CG0%^>oR6AW$U3Rohxq`h(YH3@D6~1*RTv!S) z3`=niYFsVoTcTV`0(e|>g+YAN4xnNp7txD$*I0Q#I=^Q_7dH7bs7osZ5Z&1hljAby z$`Ju0Q>8DOt21A6xzUiD=}DA%*X2Wf2Ko9(>U{w*S#H>Q!L}iB^Fxc&a`X$j6(Xi2>y={Y-yj8?wsrUkX_T^>1`(}1r8Y3(dKpR(%J33p}2Ru7K@)} zMXQ}jhG!i7>tZRsQ-xK4LdXL~=%w9VQBQvX>s*i+^yni?1u7(iYvIri>!&&JpJPs_ zBI))Omo>)bs+A?Me|0)QFwW{rjXnl-Zr&~Q0JtE}2Lr{W?O`@>!M`yVoL>8@R({lyE^)rAmBL zcHMq`HG)k+C1dqv;zkvQQW*XW+T@`A6JLXLwB~6U_!6GtKGu9iD!3dGU-(;&cUP&^ zz^yfzw|ni)!p@AdoA9dLG{{B)cx71s`h5UV;EdO8%Is#oG~m*S-DO3sc^`vyL5&ES z0kiHwt@asm5%x1|r*~QRr;jfGWT=d{qq|p8Jo%wAb7V1uK}fp1p|eR|>f3!hvoN@a zC3vigwuH`g!3)*fqfYMf#=KGU3~XqB)AqhrurId6c>y{I)lX>m+TxP1<X197D#m{ZtE`ndfH)1(P~#DlC(Z=*)-7oF7wJq zPkJPyM%C;7B7qa~)os>6jnxMzv$9i98CC6Z*WZK4m3||JC27SFj7JP_~j+M;AU_H%;?-_Agkc9p}NqC2lqS^7BdKG_sjiWBjkMwMLhPp(6o{ByBY-huh;U zEZhw?t4reVy;Nl!Pw_E|8rM9DuAp4__TF@~aKHIZm90c(J3ULoIbg$ghLsS9nlwr^ z3Gp8STp_Kq$$r}?D^)LtwD?kRTg%-(24?y2)J90}vNu6YDQ-<8LHYN_oOwy=rxu0&)ibT<5uF+vN)%|c)^LD`XU63vl2a*mk?s^NST(KUzb@5mQYP&Pg{76dUN@8{9({7QVE#Xp;Og?dW zR!}^E@zJNctieb3WW&IbZ~n_KKee;vK(*Vh_dOr9nW#=K}N|*%|dK2?hp;-eC{~;#-)e<4m%dVH57f-KL^YI`%L80}3XT zZOAJal}$$${4PS8H48%84!!J}IqEZeH~AD*(MdS{P~GFezLA)Ms{uOKll~fIbQ+I& zYzht0PRrw)HkyWUt{3%zz$k5M0_U`!3cRxUWrdX)hwR0!U(? z)qP|d0=%;t(NK>nRAmBQefPu|bhm4yT4j=mBUmU(2bhs(_^Lk7nURfr2bXd(AwI1TB_^6(Fc)&MbUoCcJfOksjrV> zY}O*Kg*H(VNjlG;<8v0J>AjYh@+XVsjV>Ahi_OV`bW3Yt@}a>o*ra;W9r6qb|HCP+ zvP{aJH58qbpzEV=N>RtYvPX%=mHV>IjWPVT#{M>DH%ZrL@#WVym&CME!I!$*2Tt3y zHp)f~Qa3DNigsUXV^45FM5xF2yfW@ljq9k$&+^Ubw~YLFX8^lj^|rCPOpB97`)7WItya^HjC)X z|7shZ_za7L;j>(lBdvZGg5^&m-NO}MW3QhJq_E1;#hS$qP5H`#5*t#6V ztw?+`37JjOt>Mbi?=ZNEA9BHNnKbMV!{?YuMeDrdW1=6x2pdXhryztrjD2qtmm968 z4L(OfVI2@uK5IQv6mkLwgEkyLZ5Z_Q(3(eIS0YKs%gr&4gcuKW z^R2ZX`q1?b>&v`s%o33-MT3Mc+2TC5ahy6pP0Spgwg=bB9LqjlfPQ#XopNGH;>rwC zy0WZ)+E#wSUtxE;_U2p<77-_y=MyL<-%TICXPi$pjFHLFTr5k~y;*-snof*|!X}Xd z2^b4lGH>J;n1jtoow%kp1DVg3zJ#C>Z~2W`Ajw+)D;xg5OrhNva z{?zt_yMtbT1Nl(p>i_(iV1hNCBzbnZ+uw+pQBfXoj#81D2fJX6>dMn0fJt=;@j_hk z-JG<$O0Eu+1k{`@18-PZGz`z=jd@aHGw>wyoSlf%&PG1d3v!Aj{5n-VWkBCyrT8UT zV%xTh>zHDtg7~;MlElc#?|Krl1@>SOs>a$*Z+1lIs-_5CZ>AzH~ET5q*dqw+NZ~#-@DqR?T~Vu1*S2#-d*-l zt7HDEtB^`0x1BJ*ICdv|%t1`h4Aw0GWX?ss5CsX{uIoG}eVt3_`FbCSr)#CV@WCc` z6<EF9Dd@yM^np|xNQpcuAm5QSo`HHF~o7tDN`M=ii zpY=5Uhd|;mFr)Y91a``Z&u@U?FNVu{pFsK?ZecNPQpF`T{_3()23*{iw7HfVG zGyjiH%fH)u{>BhHWXWZ1uPb@v2|?S?|J__L!Y6u+>&*4OJ!bu=x|o1`0@0(Qq`jfT z48g;qAPNytWs!d=;AHsp3C=ACkK%H<v5h7`Bmg*c6`PO0K2 zfkekZ_a?t}gI=;xo&|LzPrqME@k?|KJ;X62dm2x2z0C#kjAcWebv2x2v(DU$*9|6; zjs3Qn4Jx4FZ-2(I@L9bAn$LoWa*qjl?DTdDgW<0*H}HqWLe2lzZTkhUA=UZ)S9#el zV?0sv-&S#i<lCKTv(?cEn!~&n0_P++Sep1*=I~_81Kt`8o3=dVZYXt9`-Iy zbVb0OFK_Ky-Pq!>W{W`XuS*k&iBHjRhNt7CNU5F@49@@)_3<72e|M1m(`v;4tD;*^ zuUn1DORr2!ioEqLM3o_C`GeXh)Hv|{$YkDBs!{b;ogQju3m7ATKK5szRptDY_)#6p zvi#M+yElR;(X&&YQtsdX_dWgRjQ#uIH6!Pl$27Bj&oQ~$E2sQ!s%*nx*Ke>x%aMyN z=chm#?HE6=3Uv~ohqf`u}>W zev9aSnh>tfvCzn?bbk=kWvtAa6RuQlNE4dS^~Y@ee~i6lTol~aJ}f0D4Fb|2N=QpL zNQ;1Uw{%N)w}6OrHv&UtJq*0#ed76_=ea$<_uIgS*|YcBEB9L06;L#( z=CQE)*)fVGp<^+y>Erw2-6Pk`7I(6YwGPpjdlLwD?M&w$phk}a`IGGe5!ue8;m;Uj z|4Tgo{_~C$!AdegC{yEW`G}T4erC0or{^mUaQ)_|MoHp%PlnB%Pqbk(QD5KQfZq>Y z7r_^_{?^y7^Mt2qapgqyh>Wz{2jQ%Bs0DbU%1l$*Qj(C6K&R*q@Pz#6;{01Z{l`Cj zqC`{ek_l(_OV#zJNKBtys@ID5DEU?E;aE_M32bY<&Pt>37~1^6l8pWdeHmeYViIed zCKrKh=lF97=?Q_6;*!D_n5Cr-!OTxj9Ty>{KbigCmfr)>ve`&m6T+p&D#kS<>J`nU z)4*IMq6@T%a)!-h%UwqDztqw05`4lqPVKED3PxxSvieewxVMB=b31`uZnZ2nUaY+^ zyJq&Izarm0+JF9eyBMPd4cwOaX)Qz@-9Ca- zry=1qNu9010i|q<5&@&@PJ?8ss5QB>#9`o@kXP3q5={u5Jx`axhxLNj27e{y}% zdC*tnAU3FDNKe*>AR_eyL9zohg&>i_u97nhn1_#lI z;2%SjUN*dJD%`5lMZUmPD_5`L`qMdP{60sQsnRL{VG(n+nMTbGi?r zPuF6Ot+V7Paq152ISr1!4`+0bTK{N?up$4G7N((HXaIf@WD?Ys5;glq+h0#SUpGQU zLGPWs)T(=qhqhY+)Gpw$dpC4TF(ZyOVD#oJL4&QqpXm;bpt5@{{3S02Vdtz^eDa{^ zc_lvAvYFKyB|t=xz%FfnFsbNp`Fml9zMK6XWqthLn{==&d^kw5(e4qvbb3;LE5Tq9*0%+x{;tHrGZz^m4*`5Ss!usC7b|grv{uo{P}FZGGIbgw4QMbytS_1! z`+gW*JywTdQK#(Cs<`A1B`WJp9l`h;uVhpbR@-E+Z#4=z>n-FqVmja0%|uv1Egre< zN8$9@p!n2^cUqIw+OYx-sVucgL{A7eCx!*$fd867`A^3YfQWYCmQkz~T{eGjM^qBC z7kcZfW`%oPIO{uADf&`+-;lkQgy=Std&DQ-B~xcjz`rCT=d>=rRxF1+&k_`4c?$w@ zkmtAX| z&_n>NvwV|D%ur`oj-%F|$nq&4!9sfOO5i5o0GE6g`5K znfj(U5d10VI-KnoE4Y)Du}^+n*Ra0AfwudE%nOTs$yImU!#7p1a*!!UMHmk-B|lPV#Hd56~xLX7kb($Y0y>!V-|#ynYe)eqh1) zH@wJy27nLm{qH_`dGgB-*yL|hv>2JU4lOeC;LNBgV<#-0u4?00<}bT$GQh!QNed_u+PI8_*31&o^v61^zUHs-S4O}UU8Kr5PE zqYH>eOd8L~f;$>@CBKlx=|iY8?yU1H?4R2@d08{N+0rR^CnX=!yIIfwF;X{sCQ1`T zD>3VfXqYv7Yk5fqw${*S?jv{5E#iGTM2F&>(gRP5pKti)fE|C8 za1iq6trfY9ab=M{_4~c1hyegnc4xJ9Fnl%;>z6cN;d9&UVf**KbbJL(bRmdPVwME) zayQ8Xq%F@qWmQ!?Kh3>#Z^sL28ntS@2}&4!o5b^h)vrCbb~HaRbxLuG zKilJJGhOc&`5m+!63U2^+lm5m2Dr|r_2M}hsx=~LPS>b8NQL#1$LZo`9{zf*%o9tR zx7BPUdAro5qMG6ZARA$#5OB?{&?Bq}`+I$tp?dw~D6DjNfrBg(#Lrl#Dvf;2(q~nz z&ZMC?t2aev^GLj#Uu$%nGt8?-Y(Pt86ZW2Zb`XsYJ=;opUI8uP;)&}w9DIV~rg)*G zhLqOg&M||jC&1|&{d6>rudS9>>sX&B|Ni1q1&LN_ekXkT?sN*{>astT`1yxyew)fx zt&r2VAv!`tayedQp`^by9q&Z#iZCI`GF(~ZY)PD}RvJ5~el{#_b-r~7xg=hR0Em_R zc%vE0u^Gf#;BEnTk!|vqyeL#>4C48>3*Ws%un-#eE>7WE9+s>uE&OR#hvCs&#(X5Y z@4B#-6vjL4K>cIEWi&_Ay5RwXNZ5ROdeA{{cxjMZb&A;ui+g00A5CyX92vxyitdLK zE8&0QhSqJXiMD(s*h{bm+GonuO~l3D-BX%K=ZIk^xch@aa>L_we8tv-4h$==LAKf} zumqb^1)}TgB$RPf@)gfRA7kp z0(#<=I||uI<(s{$!yu>fT;8oJ{z3xiu(_=~>zT-_wN$J3HToYcod5VG*%XLuJ&4g^ zY2Gv9%mTJFB*yBQwGIYQ3AuacR9axOboRUzRB|k&xV$Lt$ToQVwOnvPD(bKRs z96Dy)A$v=7GDm6#Ydv~4uE_8Ex;xbm8iRL>_DECnry(KR;e>HZ6o*>3AFI=CbTno} zQaD}&V3d1*+C*w2`>Qy<4B6#!jaQatIzAmPPGYx}PL_ z_Wj4MeG`fkx{`kjTWP10fbo;_x%TJ$BhlixcGArcZ~Hh(UL!l?^`Qse4$`UcvdT78 z693Om|DO|pr9a{1M%$WO#FuVfmhYVYm;-3OvjXFp_F7X2(LM`q%3gS{Irj6GcGv=@ z1qT4~5kSeHI(>qJfrKJB)ioL7AW7J}{_$O;+{tTF0+^!!*Yo_MTI4`dhgiP7d=_Y+ zKbFWJlL9E3p%INUiQeX1dn(jGK4XeCr=ppp^sY|cGxLzZqUMuTj|SEB`!Y<Q0&nKn zO;8}^

kFzHT3#3q#8O_OAIO!-Kh9bGD=?B2HqVtLRe3N42R^gJum?x#2Q`*y`Ln zwHw!-#i3s8TW*}8HQc`0{U$VgB8jID-~R#>^qqMo$LMO@-^H)zRlZ|XNv8T@IxmD} zmCJf6#WAta$I^70Sksb#CzTaY_CQ7$zS9r>g^1dE-Kiepbi?&=<4p*r)7V;=IC!pa zZ=LN=W?>N-(T8bfQk4~5J6f$LXN+PA(us|U!5Y31(6KnXK&g`?t@Cm6ou0a#x4H*f zg92)_%9C^HGo{8z>)eB(oA0=ro6=<5FhQ~cmY^bdUG`M2b2y^J5B)vb$=$N5iz035Q%7FqXb(zazk zW4wKTf)I9o?=%&sP64vaSdX|@N}g=Y{z%tZM;2&Udi}nFaiuV;IdQ?(hv7aUBew>( zOKAh?Mwb1BqdI+GLZ=D#ASZVvreAG_ZE>X9->b`CN^`-ESi*YO?VKGjGb1>8-V|Qz z&BFw&xYTNz{;A$Gc}_JVYZZU9qjk$qp#Pyu7qg;Rf}-bH8lx&#xog-{23P=YlGfLX z`Ez)VG@@4d;tzovJoX59hgS>;Dr{(;@ZKvxiRw{%J(gz~I-Etb)+(o-M6^^`R?m(1 zdkNBnbX)(BG^$?LV7+<+k3oL`Il*gLzt>`u7SDLEdpOIN^Wc4>73vBpsIqmg9Dk@8 z|F+24C-@F73YX@VsZ~MiM%;3%up&BdTdErU%XhO{r=V^JpRIGZsfoy&*H2 zWjM!fnSK=ciXSd4x6DJDwoGqp&fooO4)Z^Z{6FH~>_@w3SL#T@~klJu!;{ z?)m4KpoC#0TtTcZk55IRgO2`peG6~80T%56V1ZiaB_;i?#rNo|C936&!vV8L7Ay8+ zJ;D~wl})DgC9@>9I;G&AFMsQ;|FIpgVs?V|XC;#SM4Cw3_i54HNYJ_5kb!k~Q9MKs zs_zR8IKX}h7>dG@!V&h9;%!j>Ske1S5HAsd z7IAMu~~@kylT(nVlC5 zAqpK>4Q&Aire~VE@J7Q=_d)!J{O zZQ2EC%hNJG9bObX22+cl#`_Q|3^VvNk_Wz)+#Ei4vJQQdzf**{hS=`{IZpzQuvY30eY3v$FutK3Vg1UpYgcLDUwmgeuFAJ=QGZfRaPivt_zMs%d^ z1{oP-(cwiLOLQ}0V^?a`XAQ%b;*IS0?sXoDii%YEG*Kq?Z%($R1sXq$HHt)-( z6$uvpeQx@fz;?5cbu((Wkh7aY6SDPptIFl}GgIN^oI^~PB`P8L>c++MTNLB*`DL5n z&8Ii7?|Csip2{Si6x8gKGhG%+lXAvVKy-D5EFnJz{##|YE#AMvsUGrnzeM1NrQ@#( z`;z*okGX51k6@2zkAt^c&mkB6jH(L+N_ftZuEmVH3n|Y9bZ|!uEmG-C0y6KB@;X0g zz4)_KM6;;JWuw^@m&P>!{m+}GCq6H=?$4Tx^OYP}X=wGObB_>6XVTatOMfb$DZ@-h zD6eO2dzG;+05lO$d}RbeN#z^m8XfC#`m+`IZ92ioy3qs=G7{J|+G2Y5o>O=^lTu$; zkQeX-G(uS9IgEN1yQQLn^Xmd2J{TaxdZhr1a`FQ5U?)=AiMS3s;Rq{K8TpdQ^Z3Vfqr`kR0Cdo~Lr~tN0VvLusDYpNGqx3hYM=ye)(et%$?{kZ_Wo+I05~x;qvkTFYmu&GJC+7O9+eVp8`JR$L zVPNkYC-}o}o)iP`6GDdxZKr1%t9H^iHfkK${O=BBMdIAbw>-N9|I12L0WmnIT%R<0 z%5aL%ZE|!f?$n(6nnH6v)IQru&}KsZGWTMSoCQ=kJfn*)kdM8z$Ths~DrJfq%mGiq zEH;m+KD4MeI8Ck-|CfTdC}IXxHn^}f%1VG!VeCLieNu9%y5`=_H-B&cJNa8RwT2Rw zA4{T3(cn<`UHAk5T~eidy{s52GUH&tB-gKX*O--uVh*-6qoy!ZiHyx1w9(Rpl+2)< zzYS>r`Ok4-L`jR}amA%Cx*dLR;%46BKI=eRO;wCdCE9U(sk$_WVj8^ef!W4AI<0SD zsgcb16kX{h)=U4wes`1I!BLPmZ+EcV7pb0Tz}N{^_}!OxTz^)h-}ue`rKK}gO3L?G zE_0u9;V%^-_7ymr0f{8N`|Q77OzVNBw;F%IYrF)6woh=fd$zS#*FK`%<61OPUFM0e z&N}JZFl)%as3=O;zGTb%yNu{x6)R6tgwnY8?g<*H`jKqYn&J_HjuGM54{rkHT$JJA zheJ@K1F8p9WH$sSgJsjL?@L3_`l(G9nPn}cRvLbJzDobiZTokjWtS1zTb}d8Ma`-x z;m*o){@W;^+wN&+JKsYDw~o4oau*qf=@#$Q7bEjCJ`99uH@3E1mg83Ec&1&P{D?`* zQR7(C;~V3O9|iwKkvPS(^|Xy?Qj51IxcB*D&?X(O?-*a`hHrkSP=K`T4X?+lZCBUI zfCD^2pIZ=`eI*ul4lbO`^CK1#3Sc|#Z}AoJso6PR9rs+UG#fVe{quGL+NDM7df(}% z?5BaHrcE!y9pv+lu=5>-(dvQ(xRbb-nDYTp>ZLwuRn@55tE-cqHo)A$ z^ZEK5ahNRwKf4k%BaZE#mN@`5#;Sf*@Bd7){4Ws!JS8%KTyRN?w9lwPZ)-prjI;el zIq#fj4XfPM*|~Ku9ldh*s28-4xL-^F*Gf#CUsX>9Hf@C~pb1h_c4CZ_QBl>@#7XPJ zvym|KkT3!%)P^$pamXg~@NmD%pJj%CC-Y{!YBB3(_RW$Ah*nj+4iLyKJ zEmRHQs9R?IxcW69QE^G#>;iDXSWY`BeQU{iVjcGH^0@>WicZKJ`LCTPsnZN3L?cef zzxiz140VU@+gXB&%(VK?;h zn)JwDG+xJ^tM0b@GV(k9&Kv6YFgmsSbnX-3&)x~!&v^q6s-pNAKaB(}lJ7t9^LLlu z`bTTrA3W5Q{F7#rL}Be(+Mk!ZxOlw#<3uDUvk^{n&mCWbPe~+~Lev@0OyQQbO34C$ zRmW@yh*-}aL926``I5rr$7b|>gzk~RvvL}*(>rD{FZieeF`}HX-isxs9p=W)bnh>g zE89R_TF7_Nz)m+hg=D=J*VGJw`n07MH*jM_d||TXq&Y1J4q*%5xh~?0--<~hD{QgM zprb$C=L&O;(Ot9xvhZ|73y=oiKJwSJiJX``Q$>U`Ym0>TlRSA@P)Pu_DkH1nC!Aq(=qISQYT`f>T7 z6c_Ejh>-2Vt*lTdXh%K`VlThOn``$e^?)T1J#X=+e7qof*40`WsD<8s8*yD`=5(k9? z&ys5rv4E?tS-X=tkbO9jg?%8FoZ~lyCC6w=LP{d;(VI@FmzPIP{ex_FCe-xtwd$i< zyd`Ypcdt&iJEA2?!G5yjROmUtsdu*Q9CeP<$P;Z_eJ5cNZT3qb=)+}0YhaV8IBGF* z;woBdVr|H-TPRj|@dn;co#7>WI2XFfUR%l&-$n!H!L5`ee*(2(VFSEA4Y??ZAXLm4 z5J(&m7Tr0Takf8x{Bv?-^Y|wW6QKhB1lURAVEE|kwBGAT3WhAX>D!z;_x@7aX7#&m zFgr+;cQxg^hRK}oZ4#fHWaN)EL(u~~kCR{_vNf}^g+RQ|a0e3b3awT-PxWZJUd6?M zPb{OHqI4Fwf=zmT)wE_Xt1(0T&2gm}LmwRXfG=Ci0g;VxIG|Qee^8#YPQ`c}LFc0? z6)O3&9}%6q>?WA{T6SZ{dR2xjK{xR6gTW6&4tTrQ$huC?aYpw3N?7#~;T6Zo6hTv~ zw)y1tK4f=4dv-&@f3-@(MJA6|Ih;=2Zq^hWeq11~`=dhFm@gIOvZ_{-01yg? zFs55dQpV~Ab=M3VVf+>Qz@Z?$AwV9bc&TU#{M)^MWJkM8@NYj^f(^|4J4gEwB^t+H zTw1grXwHM7Vb^n{#1~8`Zk(X2&bNWgTE@nj#CY@^@}w4>gSI52jB#iwZ$a#x7InF@ zVMC7C=O)h}0CfN%UMF;OwF_zcG)Met{Kwz$z&)Xu_Ki}4$G)eX2tky!;$c`^J)yB| zbZ};ko~`M4Tr=ydlLhP0*q010*rjyVl3;bcfhrSeOo~B4&kG$62{3PSw6UF5`Ojaz zCA*D*hzj8u8}Khlg^%y01R$Z#I|@3icE0~$fBEu8;iLSjkk96+5y$6WI7pLt({$fT z4b)5f6{`No0bbLn(zh2SE%_II8(Z; z>o30L_P8sUwDg>;YdCi*X=qI%H652}&pkEwoE&Z>;v}zSeq^B)Q%{Kq*KMt*U&RM|i`x zTod?u7c?Q45V!qSICKWes}Y+psl)G2aCZXTV9$RvH-2BO62ZSQzTGh!q>CzR-2UPp z-m)4PHNv+u>I#S5bRV-x=ehtZwp?mjN zq<2O$Z3ngFRlEWB=$Xj@&*KBD(hJ|IVz2u(k=Mfbg!;}uwHzKoNXW^%td_vj+vvoK zaPF1x?rDw>zGT467SUxQr;(r4aTZ(}CohZD)wOlCZ!I2ku~#_U4Jct(3-qMD=Z;0u zJQ}(_06y3SNA9Urrr7`-4yx5~25<{jcbu-{NhRhO1C|{FW=-!ap7)C^R}krYYnf>; znh0xMv`1$NJbF8w6=-}e1mfq+M!=}KZFLReOur@%@=J63rPEzk)Slq^hQf#5&7h5k z8?@1M!aAvYSu3zRzqx1uC2k1@7dVZ>jzpHe6AWkuzDOalg*Q`^Lc&%a(u7X-`3LXM z?H`0AMKz?U4`LLFYOl25g0}$yHjG*KA4pAEhKy_8PyNk@fF=-asIIHiwTyNg36+Z%STa2IQj_z~|rjZUDNIrp{jg1s5<#7#BO!kw|LTcJe zU%#96K*R#JCk^*zqqz>{r<{yh9*H(Z`x04P_-gGqR1RL=r8RYElr|Kv@Ip5QV7b`K z2`j_`pT))>iihGA_~*aItq>Pw2`@9X-Y5s1KrPENK2GkNzl77VJkt0>t+_N9g&FOa zy;Dbr>TueHiaLIBk`r>2))=WXVZRULl;&*a98gBzenMtQRO!lw>~9KeB#5m=a`-Ah zJ;+_K^8J~+6rxXhXVP#|YQu^<*X3I{4~faL`=umQ++il&u=~7yvFcFK|r= zE$$^NTEcX1W$nF|5&boMnun8;^^U3)`?bn@w#90XcpqS2PGA6NCO*U%aDO_$Y} z{J^8ZBC>|-91R1{Wt+uHmib?&hliFNn0H8amn8}H08dKvcvZ3)fR=3xxqw*@b!$peh$3q2d0>3 znyLeCK#3iull>pN$Tm!tnx`kADEFmHG~D)JoPrECBV#Klu{(B(sYw7j}PVX{c_fvs<^ePUw|KPGLV`0Hj&8X z$bz-M=-th6%cgm%pO9zFMJnA*TRwZ&1f}&$nrZmma@rAL+;7vReG05*Tz4WAT5Uiw z0(vXF?#FnJp7AJL+Gt?aoj#D;FQM)tdEeg!HJaav0*0M*FqU32t~jABG?}FN*#3BS zEpXgCD?G8&@Kf(2%+mwj$U1(FraH+p>|Uqk`SiHLLCkvM>05oP=g+b*|DpQdHXk7<%evW~zpnUl-j!V&Ach8EyZ_w^za4fYGh zaHc9_N_5FUMTy1!q+>W$&D#$v#XByzdm167)k^(SmK=xAuhx2`lF83e2#xvLf33GY z_EnD^#k!*DTb3uxp81SjpPNE6WSCGX(H4s$Jz~YGpHm!{MZ}jekd=KKu&T7x;Ys7L z4i6~Zl5T6Rr9(*-@jMPWMAF+ zxkF~+eMU#ARn;mtLTN<6w>)`MU1jcenkhxnD*T;bH=fpQI1=(22>>@W9G2%Vm_hI{ z35Ihhg^k%gFwT9tlSQj5q=*irZnfrly6oRGRL(({zD1M-C7Zpyz>obU(OplE{c-kV zCdd|guVaaysomv+jJ5vq?jDXS*>EyVud-`-`|@pNY~7J58PD&Hn$H}1#LWeaA?orh z&Y!2S&#^acRJ2dmPx04RpF|zzxh_TN=}T5wZa*B?(+!{aJ~av9qTP1LD%+mn&FX?j zmuhjf#nu({jgkm}yY#o`ZHu{bv5~ev#M*8n@6w}5C_Ez@4QlUvBMR$1Kfbsm-Klw# zm5jaOCD8qqPGcZeq{laGfa`FjW2`KJZtwKRAi|l*!!9Lo>H4A3m|apJSt>do-bs#PqeKcOw?N-Nu57lf0KX0IBZmUpXo)3jsA zDfA}qngazgwl$p3n7F~dt!1p-Telac#)-2`ZP>krP9GdOxg}?whZWe!n7Hy~L2u?T zDv`$p?0gWCf?q}w8r12%N1zcUzcVCHqwPWNUiMhiN2$}Y&$IlcCA@AUFG6sE?KTLC zLV;v(zW0i>_U=GU4FzJ~C)^s@fFn}`3W{f~@5Px(|OPXc$mR0(4qv z@kUr1Pw|i@Nwly`7U7`goZYP`I~o_0SGGDp4n<*OI-klfr*M!3ym)@>2!TVEe_;W< z?)TCr_vjbZZf`YuiA_1U(e19dL>*Xe&+-&@3UX3R>oJZcKiPxxe@!l422mCe8ca2I zEu+lYxMT?e0x7>23)mV2)E2QAzI!F_53X}EpDIem;r%&;PxFYG$(g$C0=$rUQqP-qo&F(SMN-d8 zJuM2$etJhW?qn;sVxF}^>FMP*Z2TI62TPob5&) zJ_J@Z`}>@%t`)mso)Jv?jmP~udG7AuE>0zC#ozHg)$4BsvfI-nOZOg-*Psaf(q_h? zK)K!pCsK^8R*BP)enxaGTdU0pL{2T)n08LR7_&=)FL)$uHt7`x*wYDk}`h=;Cad zI%>oUuM=r}L})qZIe;CO|t|`a`Re0VLn-5_Zrg8oB2{hV@?7VV)k-Rp@@$_Bw_VfOBq=qFA`w-2T5lu(m zIFkd?cwJ4{ZcFY!bjy1u>qg~;3KbM5v?TG*@rI3dv&$3>+sF?4*D zuMEg(PaPi5PxQj2vsN5ueJ0#O2@IHHCA@$uK}2nJj)d~kiH zBD@?0=3dMfabyVT7@lJ=%G8i$@+#qDy5kLIg`e2Ymd|Qk?5Ns%vhoa5v9`dYeXyMn zp<~B*wYHF*Vq!yF}BfbcyqIo+Mjo|Qb@^|r6R12+Gf84_B z{$7h;O8fTJZSd%a6;C|!xt;^M|dsWPD?+`MKG`EX)8%-i(B&QO|X>Dx+t*vbHYigi7XW3`2UC4 zutsY3?88Xy+uQatpC)S(#dWQBFTb~p`l{Xa-4tHHy)Zy5FP~Rb-r!D1-M6DRL4?Io%yv ziDSMVL?6t~41FiI???}=a%a*8#(y>|#+!&a@Rz?99Mv;Dj$F6X7d&s`(=`&&OfGyD zX4+Kv3OZ=|DW9_RavBEbM2%=5@5|UB76edHD8d+el2vZC~C#kzHeVv{o7fj`qaFIA<@mn;WZj zRjd#?>jm{K!{NFn6D^?4d<$7U6*c_?%2f+@_yYLlJeeKywiovV&e35sIqzB8_uO*G zADxXI7v+?N)Rk9OD=Qr*w@=fb4qr#rwHzL-+!%CQJ6%O5YUzpFmN^P{aC_JLEY-FN z-=;5&j2Ry-Xy*0ZFlC~oanCBZE1wDYD9C^nJ;{cjKYsi!pVp8+P2Kilr#zqm=4HhI zV_JBnFl0F8YU`&DZe+IGfOLDy(`5E>v40KN<~p>8bG(%U(HLQnOr}b!iMZ z-E}r5qTUQzfu)uAcblF2m8?BYHS^1UvD>#dwHIT#WhDy+DbBB_WxKqEe=TNyGpGM1 z`MH3)JX;Du02l=z93f=W6P%95F9Y;hr4@$VT2hV^=4lqIZTKblSri7|P2L8GJkG~vxE_CPsWAaV z#-js&Y-RHuSZ9HwE3!pOQibvB?Eo<*6LB17S$9W6>0sR=XJ6YwgO!>V=Z0m<1&$Az zepW6N&B!Z-OW>bbj^zQhOU0J3D%T+fSO>eYFNmY}(kVWyzJio|;4{zU)!{NZ#dgZo z&zd0uXN({1MvgHe;LdV$fs(#M1Fxg$b93n8Wb~ISQcCfJm@N*A_u3e-_2Qf(Eyo?a_-F8vQD};Ch6=w5Y1GI0Z znR#@+Y-&D^r*oF+HVyea$QfY1Z))cUoLNX~%uR3kbt*z$&4oIRp84JD^PjHe)+9^A zh`Lva4`eXO<#Ig8<~ml|S~nz%HtayBU;U_xL!5ci6I?76;1KXYz|qaGI1jhe@f;8! z<}>jd8TT2?w4Oee+#vB7B9a}Fmj#cDQY~btu+~% zM<;siH8%@eUTFF$X5>z5a(P&tw#=BQ{hfNqJZ6+j5Dn}QthwQxX?ybz z*_`t=q-6Dh8>*yIA!Q;_=3RxrI!*(AsC(Wc`F+_Vxy?ZJW)_bkG;N+R)em)S`#Lf@ z2he!5q9V6vKL7InRi*<-_kA7t*nXwjJv1Td5{%pH2urKj<71mq0AVA* zDZ4wXq(nQ=)BGrNnR7khl!Z+AuljJu7)9)FGZ6d3&Br{%&^zfYqZVsY!B4~DM zW<#N8iXC3QL3Tq5e+AIbh~sv>J~f~+rxH%@;9{OWj@~y!`kCbJjxkl@a4I% zEXs)E!S>tK$ch!3rXgT*wgoAGHH)SU()N17?q|*74Z05$N@A!l)CC512QBky!nuCr??H5gC)f;IZ=>_n%d=tSIEsv|YMl9YHExf@A~d zhclmw#$sx?)5$@53(A>Z{IMa0NuaUw_dEPn(0UP8>7xclSGl9q@&1IYmHQ2~IcLhW%~c15737&Sd#}2+^wC9`4O_Yzd3|sx>W5=L zh_E|5sj2~eSVK1s4OOVV)3x%qXBNiSj0suq}GRMr*_=s!_KUkr>SFE?Cl~Pu1Lq_ z&T1Iz>-Eus&P>Jm3Wnysk@jqKjP}I*p8es1hQPo*-{T%LKB;&I|_?V5W5SJ$sXs)R6`NtQQDgJ5!*` zvle7+g^apPUBTT;4A@@dFcHLcw$I4glJrC9(BiOu=Pj? z>UgXz7rA?AXbwzq6n%n-gO;ULNX$fw6>lYfz$xF;PiVqW^E{Fn@Hy`In^~!n_HMqR za1me&AvtUZL28v@uJLs}mA1dL5)(P?-hoYy4JZ;|tIZwdJgI3m!RjMwh=%Dg3$8a&WU-SByIef?upO2i~MjOrTVQ)VD;Yrtb$ zq1fSKo#)L^0jm{>N_s~c4eO5DI59jzn~4^HE_(Ez@HRM1yh+lkCLA+9hWJ9d=yUe8 z2=Z#Jd(6Nh!*T&)qe`<(OB7|E+=JGovG9f_Q} zeb07{9c`*=KkXU+6a=Q(*E9&S!}ahZm5=|F0#ojW-Q#kD#VQ;!MTk7V$t?8oCE$O++bB~3l}&#h`Ve?bZ!~K? z&GYiY>vPV4=U31UlZ`T=?Zd~kxqQCEQ|Z#(!!bDfgz!h%7Cwlfsq58k6swfZd_nuU zYu@&oAsu(y9Sdu_D|yGEBhA)O&~1a`@0d#7dPZG~|GJ>{YVw9jf5@iRYcVj!RO#DI z8Ts~eym8QNNcSc)!1|zhoP&ai?1INO48=zdh}>#aKUrcAHTPh6_iMgzR*Ez$sp+wN zXQ7?8klny1$idukGxOf>d@_raKGE7q?yg4tC+_4+a^c^8`>R5>>=U8eOiZ25~t#SQB9Cj3mtSd~=m3!sw&fczrCe zDDrp%JuVWt)4#=0v3B~`^^FCnP{f|sWHt;!q!+*qw_2?~=F@%qQ|x@saCRM-q8?j} zTSQv!j~Z66l0-dK1UhJcWc4pzF5;ovw^mi$U;8I)IljHwa;X_=fYr3VAl@uncxWAg zDKf6~?-caUTye2S-?qn*H5CCFtYZvr`Pv%$>_Dhmfw_T}izE7e=;H|SjgpffTz*cA_w-hMzCKg&Qjp`Z8Y1YV)b(r)RH zYuUIQYwIe?jxEA`bG(8p-R%y}=GvjtD7txCsdtM=VxQfwjDrp>qsPvng|G1)ofS>N zVkkoH7c&MhZ$n7Y!$sqxcMA{Nmgmc#u1SS!njN9wb~~_smNP)TJQ6!jP3vuBKKDj` zQa6}&2JRsYIV=FIEHS!OCR#Q{x9KtdE#r^NFL4Ulueq#enm1q&`4<-_3_=YB2IDJO z*UL`Q9s4N>|CCzgUs7vGv4=vGO{}4*7EbNk*3%{XO`QDg@*?)IK-a{_v$(K3wd)^> zL6(YiFMhiSixkm^>shww>guEmJ#6)&IsIySW_boy^!=lj?+4|E#|ZsiSp5EPWL8i( za4#zmD_40~@{!x0h_uT0>iClS#O?xFzT*PfgGA|`$s9JHmsk%~EqPtrFKzgF=bxF2 z@8CY(n~Qv@Xs_-ehxcJ*INYZyi!zdbP5w6S(w{=QXx9NztuT1F&stiZz2>Z+Ii3om z^vo`cUrh|_OO+Om5%inrOG;Q@4Z>ce992pHCPuNi4kxmX0j+OYMt&Z-l*m{1eT`9R zx=Xc|i;WdN&xUT~0sZa|7jGk5E{S-O>hEYp@1|kZ#u2oI9!u#hjw;#JlPf>m%T^ao znox&fm&_BoYuDFHdYL@sdEsC*x-zYea-S2ZoQ6)8F9`PV;6(y=?!;df?0vPs`mTLy zLM!yVGZi`Y6>0UeMY@7nGO^<0W4G_!ZkR*r49dpV3p#1rxX+z=^K|_cFHIZLs8DCr(-3=ZpEG%vI|z+lF1!`ij7~8d>+vRfbSLPiPp5NulL);t zQg(0Ca|&yVnzP}NwM-)N{eC;XJ@>|8>5}pbf4}iq<`P1;y?I98yGn&~YpV?c6P%g;9mLIPXTU?PEgr%PV2I!OiYmBr&8ES)?*m-`=i`-s z9q9|!f62aZ1#HCwX?oyg@CjlO%9?A3O;=Ob>nZYr{uC|IeksRF*U`-k`QXn=X7KXV7@naYX1c$AYq<(uwcdtHKC}3>OQiJuaE}0=BAxkvU5H5{=OK^ zqMZ+}{K^=#v+4k0Rkv;M6*E;b~)cUIrMD)`7 zI%WA<9p}CI_V11=vf`K@-rw!*qvEN^7|_4>)+W>HeGC4bh7ltcdhEN4$`u906Iw;uy-Agh`m}S>~!NoT;p0&d!Pck}%*%LgM|9ts#Hr(SU9P7vXXSRIj*`=0FrD4fkLcpbWiKUi&*XKUZeedro_dh=?pFMl# z%*^@D%y;IbHaa><9rbKVpQp_;)A_J}c%y$0!e&O0do-LSjdHkvUpO*nI_pm%-TOFd z>zdAe5qZSMo(p6ii($c;V@>y#+bE-fINlP2)F%gC56oMjP=tz5y%2$83eRLLmym<* zS^1%fim}gP!*#b>U9|4-mr!0*dm7BR;DJU)oxX|1e652+UB=#(LhyODj>$;vJo8;) z;~+wa<7_`pYSbKlrqQ`zup4vwJv#Y@n0>K-E=Re^&$z^AgTufd{0>3?z8t&79HMBF zJpv9*E+WLK)>T9T$jl;F&s_J} zU)MtAWFCDMUiwIgoqT*pSTqL#pFd_k{28ZGZ&iy;MIfiD(8yXM-}VcyiPM;;E+R{H zal-v8yEhNqBeQQKLkpAO#MIrnF35l%1 zz5}pdsJ=EfbK)1OU*;R>HgussZWLMSC-mA0LPK9S{AgRis;b~R{%xo<`dD+54wo6$ z<=~Lt3z%a%PE24cSwt}UZ9uV~y)EC_KS3`?q--4>ypv)eGRZ0X=`5V_nCv^+Rrd}@ z`&*84n^Y%lvzQ^LQ&R$L-W&V9ztTt8AdUUb)iaXAn->FG1VZXj*bn~xzbGx7`nN!u z8*2q#M(fgU@z%zkY`Jp}ciZc8(ZDbSl?}5;5(VBh5NRpH%@4)?G?o!8=nL z$S=P!-Zre)Hbc%RMc#PJbgGVP5nt2b_ET;BiRovB#p9+H_8m5<%`i$Lv0vN%M*@x@ zlA{ChbB_y};3hL=*P4Ly?iO@aEItTmc!|M>|HsJ^{&~%XCHa`o{|1rNM8tW)zhIbR{6W*kJI}z zb>0LE8XztCGckfqBGghJ_vi$khXMv^$@=(yDBoHReC>($pciShE%3yp-j(rcAhv<| zPPOa$XH^+p$vOHC2t~;4gc1IqAHjV;FoLUohE}jrTtwdG>`6fb&yWyY{G;Rk>p|=5 z|CUw0{Fj$2njZDdf+*r+3~gUDg%v{1LXC2|KtLy=QnUdu!v8Thy%zf;T%;(zcm*0d zy>%F3-ai@2OFiMXR=aiAK`T!x=jmJuXR8|L6}v?EoxbR&ys!R!%D*HX_!QsJSJ!-#sClvQpKIhRYQCOcO`mOoL#6EL^N^*0S0Nd0EgdI3*5L#@ zjUo@l$|JbSI~^76s`kH{VnF@t@4J>=l8lTi+~f8VPaY8cdq@AN2w0t8Ed-xZg$ufNJx&lKi)w6Y9fcZW(6m|O0APK}|yUOK^HG(4)5*~uby!pp&x z;=lj-R~qYYlm17!{#JAAI0;tm=tU3GGYvqg(eQV+s+tMQ;*Nu;l3~waQv(7WpDxq@$HLNHLenWhw&eqV8_n56@#^F_9w0tlf$(wOLTN>8s@;Rvq$PDKcrKD-1-^ z0GEo!eHOGapi&c|@D1bl2M35)hB=!t+r!k8!A@-XVuc_N{?6(9Ql2MDLjfK%C52as zzoHav1?FM$Fe;QuDtrjpNuna5YwRdJ}P<$?0)SkIcqu zWccgONB_qZ|BtvnDw|f}t|$~BtK%Azf;L%=CKQKzFdU<>%{1U=I===U@FvIX$tpedr=aJd z`%j~g+$Lj}n*wEMtJL00Fj%L&KHtB@b<8;$4e57#)HjnhR8suL zbis=Cqk>Q&MYbAHq}qD>@!7OR3*X!MK*tuoy0NxN;1&1`l!Q=8)!|7FPG)Hf%-mL(j6DICa0B;0YTCxDgI zllu6@)aW+{B{ef?yRJ52js2Eu*j{x2VDwsv zt#ADwCH~KKP4jVIt=>r|7f!LW{B9k@MD04=t||q{LZ?L0Y0aO#ECD_e#+o5BX+5{h zh=C(P>*zoPgrry{7#F#F=Y4HzW+o9bCwj~ow3myNMMsfct^|)|TQ|zIV`WByl#p3=Z<-(CoXdj|ayHh87!og+6|8v`xjz z4SpBN6X)FVaupX0iNQxq32>-xBu0>;Td-zL zCZ@IzO_dWO>GnqYa&OG=n7h0@X+5zh^W|uL1#f1wB1TnPq2atPVX=kr+$<|=3hw&U zRfC&Ljx1zHNHDO}J+{DY?V(=%3zu{YF$mTf)P- z8_-8^7=sp4Mi1Uk6Va4!u)3rszm>0BUd0)G4K;Dn*wQyFURZ8iab2E*y~)uv=i$KN zp&}MRadY5oHJ;VL>(N?=ze<6V#y)(mE-0@R2nU6+p}W`5Z;d{C3!yVLfT219&bn_# z*mR|(QOZ%r))repK9*XhU+e3|EZ#l(AnuR$Ek_Zkd3wMTfqgxCc!(i9^uA(G89$F) z`b6UR_?~jrTJ9*XfDSPc%u{f>dp-~^8OPY|H2l^pb^)_S{TOSY9Oz!8U95eXQJHz{ zGt3l)4W%nV-~`_0{Kfl|9irwXV=QrG8C_O8SB$?r1oZ2kbvh|)TgD*aGcTDlHwVR5 z8uh6yQIM6vT?58MBY(0WFQNyx{&Y!8jJ<-7i6YpJ;$Ce8-3^}V%t9o-HW>3M*7MEvi^cL_2G)BkGe?@7o{0p^tG41lfnAly!?U;N zUa@TTdcNVdY!JMGMNoIAd@EZci%(I5ekRjse!I5=`BU4 z+)67>YnD27a&+gxJ{to9v&G{jv)DIU614w(ti<2&KITYAS){sY*ghW#t=MlU*)zC1 znm3X!+;7+J?Z_lEnYl+oceDmJfI6U~gkrYxP3g9;qex;=f~!m~NFL>~;i?%qR)ffu z+qeQ#i9Fb(+}}93XZ4p#3k6BY;bUL91KasRm$4kE`~g3?22^g`?EER@Vzy~-5xu}d zKFG8c5q|3rH*(ZdoU=}plj}Dp`_%1(l#<*@mX;e2W_e*_0U)K@OU=8cCi1mvybkNu zv3AFZP#z$KSr z5Pc&PgsH4F@48jHb2^ZwD0cha{vU7>jRtP_$Q_OJF@}BU3ueyA_fGWohN6B6DfVX8 zw|wl$=M_^yjyup_ILxi54LdryU@piR=HGYVq&z_uKl<{rr zh{}&2ZNZ2~D#if{$xK)O=;*kUB*dTkIF_)OuTWWM2++qRP73h$sSG57W*I)naR}_K z#HDBmk~|ulW@#YvIFL{H@bar?%z*S}RZksDF^7~r@f_MYJ*!$*l0s4}ql=2*jFa|X z&Y{|>)@P%vHZ6+v`NF%qbJrsD>UdwQ_o)=P-M*09nZA{WE#{v z#~VNnUU(cQTh(hQKX=M3eW6iJW-4G~!UbW95=i%}~$K1+G6bP#9F|#cn7W=zfdX*_r{DiZnyRa&EX-7(` z2u2m=`*AM!7HQvEz%EI+mL5;xD+=LpwYJ7D_uuV2M82G!Mg+~@8uTnB?H&Ar6oRnSM$D;{2#Ya_KEYFAT^$8n0d(1keVv7rjwv%zV z6D9L!bRN?Po#Q11ZQf6IyHA|<4(3mL0e;8dx8EA_{Ysjk*i6p6bBK&8u+9v2wiA1E z7Ci*ms(S8q=Iw!rpR7~lY-%THVe?skymv8sad`0=L3+yJdjX$km(h!FTJf8NU@ObU zCGMejXkF(|PcEo-U0nKRT26>Aib_*?voQAaHRGa3ty}HWt3~u@Gr)!)QXcz!lMPtb zPP#VulbGX=wjVdvL#vmQf6sMt`t)eRLn zSmTN4WW9&cNi)^P*a*Rp_zJKd!)z0KVsfz*a6q)IyG*ygaSrj>+7-~gGr1l=LRkSh z4_00j`bD$)FEki9O-iX{(a$#|BED9LkgIs+IXltITeIx{nk#U!rBNwKdYw^z#m@Ka zd=x-I`WFG~DP1=D3cTbK9M-ZmBPa~M7qUhu$u@oKy>sKyW;UhiGiNQa z!B}eZx?H@~Uf!o(m~a->`K^%N`B@K-B@qzxWDp*JJl~t5pN%v*4UhYl5js;)-iNIp zTA}KZL(BP*nA+39MU%N*o?7Iz71f4Toz)?VJ4%kux7BYMZ33Jr0-}4(!`YL$i$I37!)j4S)0UI2mQ$DL)l7KAe3Qrgf(J-^Ma;j$>QvzFo}v}V zvBYYBEKX&QZ^AA2=2!pYY7^HLph!yvE!l6P#pcK80u9mqXh77f=7E;H4OspdYg0J! z9{Q03f;IvR5pT*$vL9X%l@Gilk{+d9lo_4Vu=kA&0SLS|G|*)M1MJ8yIEXpJ{SjZO zdje1b1GNa|{g(|VxEaULr955#_u|DC9q(OcYy)oh`Ez}Cm_H~Lm$FbvdKdNMXymXN zalTk_4u%33{TIHSl3?|xg`>+Ed1G>LH~w5ce!N8Ikk@A=-D4)Y`2y+K8LDL~fT>?e z-L^S+IW@QT<$`=KgfD(HJ3}XDkALdME}ag+$#je?IBg*%wT7-Q z%eJ5ebsBn^_~K_fhmEZ#QqRes3a-pVnl*B5d5*j+ooFO;&5;T^-={xvv=O(F>S;dR z_PNjuzbMMo%4mvu{ykuD3kwz;K?)5^b1)mj`HKVby$@UZ6trvE=@?-Xzr1A)xz^LY z(0MnLvtxnN6|2+Rya7r(*IFPBUvz;0-h|Avy_+a5Y<+`f#9Z~m?-x4{6SsU$+Oq1p zx9p5o#zBG8TAu23cjJq>;!*Fm$q{g>r=$!)=%zv^YVpi$V_QFP0ejsthN?C{6qkkZRXHSZqSFJ!;mAAf88y!kBatT-?3Y%Z*p%su!Xsr9B_)2v{n=_#xM>d>X-7-|x4p zA<)3(i{a`(9fGRdD>tZb;{E}JB`^h=>07qw$q+&#evJ0<;9j(&Zn7`CD{7{T{o z9{el8`l)LPwTo9~u5*=t^^9Bx;q=72%>bU?wYpOAou7kx0;rlAQ+Q&bwRAh!7hM8z%t-4*;bbKi?1emW#BrmO-V( z#P>$l4X$Ymtd{yzKlmMSNSJ!#zF&(C_U7lXj#1JuvR~2CfwtbwaTQUJQ__Li`>xf0 zwoQON9uSV(%rz~Sl<}oAdt`9s?#yvaD!GnjR--i?MWK$MicTAO@plv3NiI?i?Pt}`G7x#sl#-EX5P7L>B#-0 z@Y(UF)63#W6&tSVv7p`{snqz%k3unebAeCyXr)YbK4~e-Bj2IDtLyNZK;woK(MA$y zNN@Or55vvCH4;I@n1-Blvse|p+Q(y_pEPi?)CgWHR^~aI83r^vD=N@)Hcg9?hRhyf zLf^t-8Lv?fesP{GUpI6Vt=q%0)Tm$^FO9itZ$&FFkIVh9pSw|U_> zfvuKr6L~91b2e;LzanxSuU~3tdoXg=Up;u|G?Bv~fuHudXfG^%y+Ty<-9$U}Niis% zCR+ct1n~Y!v!a#TY)f9skf8Ms!u@}P=db4LizEPwg zw&NdJ(s^rzLF)qjpc^-`7_x$*&4}c#&a3aZ~(l+(bAo=Mej#k7yyZ9lb z7F6@i`Himn77_764!0iut4LgWH>+(d(}&gUCHMHOPtqym>GtiT8_l!!j%$ZD!PK(Id0)H0{j9c_9KF3IoEMCKrs%dcZ}2CN;;G%7bj zoVmB!F`sD!c#AZ;iXBIq9EBTf4dv25{gBsNj^Iv zD@NXF%Ge90;dJ|C-WmJYy^=B!l7{@}%IBYO0;PkBmgWNOQahrm#`o`T|CTT`HOE<6 zjqze0c<2TMZ#V8kf4+njj;AV9p+^m}mlp}8G@$mWr>NXABDdx8qK0Ajk1ZEMh z$G1k-a`%!DG?Qo7P)?MBsKm|AWo>LMD7(n1@p5rpJ$_B5VNAbz4znA_%;ey;*@!FI zgk=bA%|tz&25*34akXj1KcPD5_FBK?`xj{CW8@oatJ3|u^?*y5)ckRyfh~NyiLu>Y zdst%^eVntI3P$SIqr%ie|8HCG11x(KfhPx6vl=R5#)AguT|e4xqC7H>#v-L5P&d9; z-1G5_TE~Gu8n~r&;n(v0o#U zjj)1TZX(wbkPj~L!zNEqSg7W5I5~fGi0Zt%vGK)dHRqjKnyUtLlOAK{hJM4j?0tIv zKZFdf!EbH8j0Qh~=!?-CX=M=9&wY)t3qIKJ7GPLzev`hHYf8sp-Q)RE`U!%}=lN{K z`|;!t0H^5}Z22-!H39_{%Hl3$z*XLQy1XGZ`xIXd_dSxS<2?;&<~*Lh%m9BM8;|q+ z0ZZ=(VXim#(D2%jCA0S!_`xPL@B8zWjFsIS)!tqid3$Ad*Ot9S0v_r>{|o_#qAjNR zn*IBSQR2>)s-DwZ!~s}!ogoxJkYDv7qfc3qRoii_k56 zd%xu;huhLbx6`HpwR4LHNI%i3)#EWIdcZpl15VBWRbwBdmdB(+0MBq)_whIB02V+E zHi$_^Ni7C(P{gCX&g%iF!1-ALN8JXJohFBi;FFVY}3E_#&py zz<=Tp{bNU5Z1FGlviNzM$Bc^q5$$1h<|)I>Di5=eGa|DY;{l#bZ#e^N9d`O0Y!=6_ zB~hlkiL>{g@azTnqoZfBvx>Z5W%}>p9}d&|&ufUS9z&%~GtoVrdv_0g($xNgZ}87Q zTgUL(HMcZ<&<~8Dpn-EsbEN7B=e_I2zG+LAMQM zcv4XhicPNF2zv2`prLIWO7A!M(o_r!6lZqH*k*tM+NXD;FGjG*o0mKUJ9+0>8EaLX zSSmZ^;OF^d8su)_JRv8Ep}>t!la(69)rsSmZ&f-%80Y$l6R}|5MgxQjO;-Ax5S$MED8q+C0 zO6y_RCXKMKT9W$NS^}Y+0P3c3qPXE^`}{R#?ql>2+`a8iyZJ+Zj~+nn4YU0;7l8&s ztIWGEx&`K@C|)QcHES41_8T_DO{cXN51THonaNeYGYrALwyJnIl|uwU^8=mNRfON0 zczK1wYXBw9VkGvDVE&s+nYYA)v%W|>Od4H0C|I&Q*qo-7-KTC5zCZ~(pbduzgRoTJ z&_?Sfwtft43q;=x(HoD-p>KJROR;;97418N?N97tkVvH$26pW#?Ic?P947;|J4Cg1 z4|C{!;WiX?*G2m^D#(m|C#`;9zp1SF{pm%Kb96?*0MXRZ8G2StsVe@~M`;@V6h8>9{mhKc^j{;>r%d?#8ts!`)kdCK>eZC)eyA-yu5ltTUozDAehtroa0J zbRnKdQ+Qb1Zz*wR%_=QfCo+99Sny(z>q2YQ2Ycav95WVp2|Vgwp^8?Cjq#5=JS;H) z9uUoy0@J=#Ma9|NN<2zm_a8UC_n_fv@?4(6t%@j|rYeJw*eOCV`j%Qm9o zee=c9sNtV*>Plq^B=H{8%;~I-9hb<(>6C}cHr)vBgzVjOs>AbE=&@fJen*Oy_nx#+(Uy-3z z#YW?H$PHxW#i+??yw3@;wmp_lsA2hq_@G+3_=dqs@daY?qMLrnVkQB}dMMh;z7`o+1R`ATe*KQb& zjEp-aByGwk0m1#VdY}vmDi)U8_`lU^D3wk1yNshCd^n0W)Q<9xW zUdU-uI86;XGgozumq>5dJ#zR74LNMnMfZPDQRIjM)j(ZppnkQmkCP@ds-^sxzT=Y9 zbbF1R^tG{Y#Fvs)K9f9O@BVPE0OT4QZ9%|6Os zF6pO55mg@T2(gTL?)C>f`q$0Z`c%U79>9s_cD|^7S}pB_iIE2%Nn`l{Ka#iN_$9xb zU7~9!co>^oI`@|*j_JTrkV~MXTkT#rhJ}MwrlkxFwG>`dke9Pos5b$M8*BMu^LF_^DM)e41 z+T+D>L#|P#Pe5SmvEHUDmK}xUCX$4A_&xg>v`c$OV9P8YLS1j2aSKsA`Eb>e0KW7( zD5%C4jIauNGWV(0sJ0n)))(?`xu!Zo5T^w6~lCf>?)K_v9T0_rx9Mh_4@ce#p<^Q|f z^p{mCzHj9=dP-3os$_5IgHDXjdFjD7Uj(SfJpb0PlN^^$0suI_oKb3Pq%mz=AzolM zuSY39VlyW&8~8lAAx~m3w@$%0i0v@EodesqQ~Cd7&;MG?ag$iSYfbHqXt`g`5QBeD z0u3Nk6z=@1jUgz63$lEcTG~A_7HyM&NNOCju~a%>ji`~#(o()2 zU&JpQI&)rJ%2=dKu>3gZUcTJ(S3VQwHj=NhIVrxn)>XbhaSf#&=11gbmVy72%=}xN zF?JC->)|=DNNHWM{aY5h*(HbpIWcWS$yb(?L@%2rrkRxBJ6*qO+=}M0hWEb3Pb@6& zym>+E&BD2kWxrKkxqjsQl(qVLo^ya#b0&H>d)-Rnav8oNrObbNr$oRy2bO#$=rkEd=$m%;}3wim|Q$VpHc4tdAS<~$ z_qDK0)@ei13NZbsvswwZ64w)~v#2Ks%cqY5@7P1BiR!kS4?lrmRr4 zDVN&K6FUcjjWVUYD{oB8bX%i6dg{T4ojPPhWgVup=piUC7MLnA9<*n8k93+Z*?uQw zc@kRl-Manj-eUUj^?`rv{&$gm#?3FXN=Sc=f43`mm5O&uy@?Q3+Wpx!i)o(D32i*R zZxyZ^h#5d@H-B@&I==^+@;`TC2u#bENWMb}I!9{+-RQG+Ud`K!@E|`AUbal&W0h9k z!9k_Yi1JZUr4x|uQfc&apsm>809m-h)3{2>2AAqv*Ra%hV4=t^Y2&EPD)YMn0(FPj z8e>oWvs^!p8H+t`!c0IUq4;H*)w+q7E48=j4M061`)2u6K!9Rz#NVRSb+GW~#GWCN zbF0uA%cYyiiGuV6FVcqCTrHDSa!;}gEWWPFIk$iZcm~mhY#*ND=+*VCX3bxpNC^0! z97IEbbk}9lZ%|XA{8Iz^qBm3NnBR-KgMO z56#EnQr3k|SJ#8JtEWIr;K|5um%2`3-F7UapEpxB4wwS}_^AT3(_mLzr=K1vjY*n_ zW#1ut2h*3^t<2XR4S6MS-6i0LZ`~5Zb^}}${+Khy>AC#_=a?GqszxD2=2|;OldTfM z=%e3{{vY+@7P<<3sZ;{;bR`PrK(yRz17}W$KD@P-e;!4H|FEKhYj5VYju(x?`h&W) zPVAN7k~K21P3*gQ!F9)`sMnYEf=WV7PgYHvt6JmbRF_~lI-kJmv-{LPU;i6~>bVgZ zV*MdsOMiyE3YK(hgpZ>S`jkr=>bb#Kw0StmCKU54(sNIsc*TP-NMb8IgLVsm!C$*R zz?)SmEbi?6j`{xH+pB1aM7~Zz{=0T;Peo{}^Vmjas##Nx{MMn@CO@qV-deu8Nuu&~ zs+is^ymYW}l)$pM>+oLG3| zrX>X+htq(L_>++0X#TV;*f6}DsJX~j)2*sbaqE{`ejKj2BgWZ2xx+qgeosYv-x7@$ zx><1RiM0WDd>$fx|EQ?u^!B*eOrbx-C4U){sSU}$*0d4`SF&|jE1cBl?AB(= z{N%Be@TOwnJ-T0sJ-RC3a9?DP=M(j=*oxUWxq=6mRBEM_NbdD{at048M3qti1T@QX zuI@8>ne|-3B~N{8lT}R}I z3UMzI+swn{!2m7iIj(Gh{2xGM2@HGucA)r?#XKSJM-ey8YZ*Y4lB|=QXq?w95f43} zLGQj^5eou*y?@=SVLNBa;@WTU{~tB~Va>>p&TE^Od^F3gW>dA3r*OA>DeU2>SMb|8 zD@NB~NHPU>?o9<@v2HM)248dQ#r&fn1HJmU)5m&e+`UYnqix0e>HkDN`&+FOgaXx9 z4jO803NdNR%cp_xJhR8c+C|wR{30IQW1zLck3-R{i=?thcAZkvrbRLItp+YuDSh%z zR8V~{RH%B3g7Nbqqx7XWa^*dI-nAShOC^^$;}qL`*i2etG8Y>cANmLPdV}N&AEFUB zJci8v?52SoUGw>7>w+#B5zp!}{}|q+^|TaAW`3D*6^Ht-Q7|n#{n-x3|H$Pxu$Dj` zXtN$x^aG%o(p$5y?mPf!ZgX{JkqDai9K0uP!*G66v~^TZ!}l6|ShGSk((R$ttzL75UlP&xwVj1?G4|^W`49{@ zVCUp2<8&_aI4|{h)k^WwGrSVWkN-+BlNZTHQ$gB^W@%4AG?$KAKaNjS0H$Sdh2waq zRs$v{cim^fD;#*U&dfo6K^%?bZKLD{cMaJ4h{Dk|!S+SL|4#P&xAj(P;(sg&={4r! z>avBn5O=5gCK>%yr|=k8&MGnjnVcRfqqrS9v*Rg=)xSwTQgKf$aC3ed^&!LE2Fb3x z@u!0FKN#v+V906;xF$r?&N7^1*5kZpdWQPZ_vYmYtq~{eC5raokP=kKUv_7H7&1}K zV5K$YQB?+qB^%O>O3=n0&>) zMdd)Py#mC!Go+h(SKWiHtd@68KKoZybGkUg2sbSu|Luvxf&d$b_SVBLvdR#97t#KL zW6&P=#aXvNDea~9+BzcH3VIPCmp+~gTNFI|S@WrJWhLiL>Vbp1mR{B<6IK17#Fi~~ zxDra>-OWH@DJ6N>eSwVpK$SM_2-SGljs4=+p2gpXDz&|+5vko%Lqmc!XTGX;F7rM# zKDckPIAohN>~oCSz8f(6u;iX$t8{&=fZDHXE%na@4SxQjXbjFL5w|u>an$V$2CLr? zLr}oS0)1v1ZYu$Ldmoua2=t4U)$%Vh4H-QDAD%Q#qL^>l&FGt4lg4rBL8uR*c9B#o zSl6Wz+qwc^_PS@oL1DkX=s=NA**%PnwOW^Uw7-xpz)|L!@mj@(_gVyZ9+psPM`%qG zDMZ?df1|iGJ8%W}m}Iqq{7D(#dfD0zVzS zY(-Q!VsX}{O+|%7R>+G*n~g( zof{bF`X;qczIJ^M(r z019ETAd4dI_s4}cGZnF9VR(y=#wViVP= zptMb?AaiFyflTQHr#Ws$M9dQU@vbAAtx%V&hW)&3s#&qB{ek@nk2TPa5KkKzt)JsdUNFmhK-FiN{VFbV1?g3cJ~aMeB9< zx+1bYA4SYrL~;@bXBxBDIK~&87cs%dbS@IKl;Wa`6YgEM!7G6wcPVv3X>?X-c-tE) z8eV5avrXMFo5{RETEl0jb-u*SE zklxgF1!lr9CnUAuM=i~ksczfw|6xb6IDqxEw#2ZR5Jx-MCd+YtO0KuVs|p*MLvwO> zY%9r!PHC;|RgScCM=}$%vWYP`1mE1-1gfmHYF-lAq>*F6{on5S|6?DKtHQF6QTBJ_ zoz>=c)wX!0g?U~m`6lc1Sqz;2$We5_rj{c@^WQV-hRb{~I1B-D%C9Kg_st`-#Z*>{ zB&eJ;5mrI?4{V4iFD_f7RZY=Uy~$ZscpR~xZM&Tx*;t1A=X*pO|A3#zAW zCI#}1kxOYJ5q5s**Ylu_D0N-8vKcB6B^(Rj6b&zrMv^|X=6WN(xEGhK+pT-3(}luw z#*Y`(zw?c_XD2JQsQt={J>a7F^4cM+a!ce&IXMg5$k-YB?1hOX3I1i!_6m+1NuZ4b z<)GTjBCwcJ4%oL+sncy**0Ofk13W&{vKaO-%`Bsu_EYpB_HAfh&h1;aH6@1bh<03_ z*?pElkNcx{$1fAd72Je}ferq!fE#)4u=&@fk^78QkXIB`We75gQP1ti@GrZtLFcY> zvNFLAUxsbav+#xHN81tYUX2uLS8HTcA%-G4$tI}^5;|>3X2aKc3xWMW?#r87D;YRk z?)esF2!%IIeq_2msyIV>s@PU z7n;Oj7OFgZEE1K3g)kWu%{z5CivbbAxzvXVJrBIvYV!{hn!9OtT#*`N-VLIWOS93o zVx*Dnu5SzE0=pM(ZIW=s7!2c#QS{rI8*;qHHn3VxunqyrfK*N`POoyfe5HN^j{o@C zZ}=ax)lwoCS>OjiYvnW9vI&LUH3g1CSPA~>ua@&g;H_n20 zI61XTYin{G1>o&Y(}@TQ(o~0t0iBFmXYeKiQ#H)uPjam%Xc8IXR7>QdBu><@Q;U(dhH_O9#O1MhD(t9l`7>W5HWS zh8b$y*<&Ux)MRxD5x86b7F9LX{y`ONxWAj0!J%m^rtF_3Mh^2&t1|z;6E6NESK%7h zD&^Mh1TXMjXLwkjo(p4gG8F5tVVXa_q2*OPb7$SnX&sx}(_s;5{IuEVIQee7w;ZSZ znGvPg)pn_|jZuCDxD*1h^pj%UA`97bpRMK+>AjT@z|uRI4Xsc7gCM;~vTt6q;4#ya ztk1=CL8PR%5v6;(cN}=Ou$hoA_hokOefKviS9Q~FCI*M)cF(*Xq?c!yo`Vykz*$GMv039yrobuj_2d|NXpCWll;_z$Qi!U zlr(l^05=P5iMa9uCf`2y>Y@Ty zX%W%8JOB8||BiwJA4^NX%yjlU3Y)xVwWI)ePqTIghu{z3Rr#ZGIfZmGE+#s!OOCDQ z^A53=gAD^`tI>&0WQk&e6v0{E`7!1FuV9_HvpUQA0J{ojpCWdejC300 z$XI!Cg2IMLemaG6lU%00#?OYkZpXLLcmo}`2+W5c+4e*{F#3=|P~<__-_}~!GbFL0 z``k8_NjTnyPRWhYH4^_a_fXl1|FM`b%2wb>l-YTiaRVTZ-%iL%pCW#jVYS;##!27< zYLGU`wNS%u@;mLaSxtlRs37t=7iSOcvi_GnUm62$J`ICux93wKaoFVWlBNA+2HRSt z$gr9mvYcX@Qa_kHA)_CrO5HU(7+#AV_u*EMg@(xkba+XPJWQ z5?`?hj_&wLVYgWBwbrk>R)=2q#^Z#^Y)u;Nv)=ENlRGC_`s{mUdNns?Y~x-Qm%1jm zGSIrj0IiYi*r!>8VI_m>9CTyRkvic3RX_>th$sxjAYNU*k!#9{XLj`&RXmMxp29Z% z_He1L1mtY>o@|i;|x}PtuJ*25)9!pXU~&5kz{%f_gp64 z067%~^4BK7L)O#?eRQ^`(#0To(HqReePPMk8(+P8>S!h?9=Mwk@Jfb8jCOKX%B-a+ zUkYC=5+vf9St(g-6WgA`d22w^nmv{}>Rwa2sI#tyYwgls)oMH_cYN6~SGw;wN zc*+kYR7T%`MsC*@4|&vylK&5*Go{jhgR1V815E6aQ=f^@gJ-0m`?2dvthM|`5yBiv z6fCL9ArN8`XWJOAaNJcVnN=qw^qk_;U{5o#2`J>ZM8n^geH0ze8K}cw|Bd*G+J__+ zv>C>^r%tCy%L);`ubvDJJCmn794gS1cZIP)dKSx@JoLG1A%r-Ev&)E}59U)4u0hs{ zcP@Q8)FWt_Tg(5Cu(u40`s><;NkK^kK^g@?>Fx%>pu3S~fT6oXK)Sm@x|^Xvx^sYG z0I8w7LEs(l`+c74e_iVJ9LMj&W{^ zMDbypq5RK<$Oy!N;^CUi0?HTy&{>}P@2&cOPFDT}GFizY!i?2=uCa*1Y$`uRJ#$boie-|R&wtks zv&T;?0`iqBKn(uwZ2zo`R78+mzpa)5QFlu!+ZpNoW@TYvtDy&x%of-JJ#&sf@7yye zcRvi~Sj_Ic9$A$NAzQOebRpGh*`@5Bx#lTOPE9R&3?obVu*vMhRAs>MH*xTp6(*^- z;f@~(i9cstdMuGssIfM(wCvZa7gJ0f$8+UzB}mnA zk>z%ua}1lO0WxF1CCW{jL4Ys}(H8ukA&GCKOLcsY&&hxEa6M$40C0p(Gj*voRQk<5 zzFVe16dwHzl}X7^2y$?kzU?=jNn6O=N>7d4Qy-{GHQ_g>rONQjL*Td8y-$3jg|<)2 zQMx$u`6)`)ygI3sGY1h3uqle6PYbh`sfj5pob?}C?7ue`siStj0^Brn<0CZ8lLHP? zE-hT==i%f8Kd%>rl>03EWF{thQUF-a2JP^ds{nZ$WR9L3_cWt39&P@p9)f-XG5k2} zjR9=?n$)C8OcJKNs!?1Foxl1E9{VawNl%HY#b@wsWO>0tH{~_exFQNCG>bSEdG}c( z`6|YY3+j^eX@A)>#BqzJZxS!Q8ac$@BzD_SpyL|1U25cYQ10Z68bMpR+aKd{oX2hNd!0 zYWqzb6dH+B{?+&p`AmcI&Emp572^=%p(#)-HKk>LTP;Ahypc3?^3C@{b4(9jQH!+H z%XBgr;#ej3f<9A}pUxgPvwk4zsLE*<0K{XyNm2~-lK2F9Xe11U$WGGi$NnjB^#4C# zRJa`DLT&}~($7*=&5x;tCb{yMh?S34HDZ>s7%sQR)v`w<9aVb=L}~NC6-4mI$x1@f z6#`n1#Y43~@ zaWVI*o1f9d6c{wd7dgJaea_O!SGJ4p9{67n^*Peiw6KgJ<2S1ch?xP?l5~Nz+%Nhz@g;Q2l%uWcf+9nV`%K$^MO&x?Kyt_ z_mBRH2M#elXXAhXZTI*CV%;7Z({tdR7U{XH20%Mrbr-9Eb6EAKlNt-o4*_|+t{$#0 zuU2-IqIq@Vss$>UNmBfK8!e~Q`R~M&Ba9`X{D%ImG=A~cTVn~w2RO|O){yWQFtGpC@-_AiBQkj zNGwQ}zrS)lRhF>kz&xtUqU1)EsrvAH*`PmLB7af{AH-~Qba$zuaRo{zV@0m_( zkl45VWhV3DJ`k_9ZvS`ybYa?CZIF*KY1Vj$Plhq771N~7OyN!Bh9k2{YA>Lt>|%iy?xbd5&U0H zdJh#YIX98f0$g+AD+3h8SC_(ZqA_FWU!vH5QDAft3vjVd!FTP z!eAlkD-+e4Uql&iXJwRr=x)v3M5Yk`<-QjA{1)#;J{fk2wZ*}t)P5e;prU!a+5^bR z+(~8GmK#*m+VZw8~p(mYpvbT7S&Mnrt#qaxHT{{d|4! ziwIDLnfqH;UM*@>eNUyjV?9f@v{)_Yo7HKq5_87X3;i{KT?RN0m}2?%@2|Y$kCBqM z=+c#LOp2X*YlBxq7GCy+misyVaTyy3#^N^|QrXTbUY^ z761XFo>;}HV3(u6-Z;MqE%zNJi3+^pBCe%bFGp3FTG{I&`O51hQ_Ho&aw@~!M=$00 zYK!W2*Tsc5{od%JFJ^exq10arkPqdcCc#P0F!V#S)hj69-_|9imT1a_uj2M?xCG8A zcv?Q4YXg?>J8a1(ox!=goqmO*bNK+rgx0Vrgh(O4IhzQ6+r=hdp)>pA!6AMq*fhhA zU)ODdOrUZ(awSU(WF8OCfZ#kBX6J4k(PFYAj=Yn`vb%!9Qwcl$(a@65%BE@_F|};P zw4LWFGko(t1`9|%zP&hbgwuI{U1)VrbT?dHI!b{DznAGWGqDK6SyLBZC+Z%kmT3xx zG*6mLA6ICDCd)Kx!0-rqw}!5Q1?=32+@fWgh@y6Qxsajs^}a=X$vO-31?7Yri((38 zuATF_i?Q)6R)ycFJNw9e>m9nY!Y?|xWhsg%^748l5d}ilcV5+1F{uy?<&Bu!lzwq! z-}?-AxIlSkQ2D>VFf6H=gT!Ire>sJrvM&I-WeBx-^8pTD$6kN>(dK^O08cx79rm!6 z`Eyvp@A?-;@VVC#d<*@k! zi8V@w+}*aVbEaNsXv^4T%cWa`$v%X9A>YrouYEZ5Y}2}_1D*qN*~H$0yQuoYnH112 zL#pIWaXm+S`vjH?$-Be)SaAPno^vMLPEdBuv=G1$_n%54+=-HfMK*EvAyg?v#u#u{ zSqssstfH&8qp$IJolwy9apSv(DEnZ@XRT?xh2<4vdiHqxj(hnlf}5%vMUDypZ1MRBf9rRF3`44zN0pG1ig%n_nh08ZcB~E! zxn?Gg=HmZT@hs5 zq~#Ya)9v!L)Fqq%e+FL1ATWCl&Tg^lz%fuLDwR@8)Vu!x=W5zrxPzD2I6P=Oz0kb- z#pqJrxI;X)r#xn<^W&`~-zqE{&0Fu_Oc z@}KX+a7r*5%~Wi2SN+QB`lvY?`|CO{=>iqY3b%IM{=`9YzaC(Cp^osch&T+F;}1os z!<1gBHo95op$6Hew#o$$p4xh(7NVHGhWO}Fhv zoW~9AKd;B2_jlG`oSg43W`8j`?WTAtnZVPJ7md}lXOuZ(tLtwP5xg$p{5O3qH&<(m z1;V#I8EaY&uyZ%&j9te8H~||i`9p5=K3K~ys(caFaKCDDsrIn544NsC;-A&_`^n

Mr?E}HIpUJ99JwjF}TVmwvd)o#l@u}}6Km&s;? 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

+ +
    +
  • Welcome, Flux — the new servers in v6.2-beta.1!
  • +
  • What's the problem?
  • +
  • Using two operators improves connection privacy.
  • +
  • SimpleX decentralization compared with Matrix, Session and Tor.
  • +
  • What's next for SimpleX decentralization?
  • +
\ No newline at end of file diff --git a/website/src/css/blog.css b/website/src/css/blog.css index dcbe842785..f92998f301 100644 --- a/website/src/css/blog.css +++ b/website/src/css/blog.css @@ -238,4 +238,60 @@ h6 { padding-left: 1em; border-left: 2px solid #c0c0c0; font-style: italic; +} + +#article table { + border-collapse: collapse; + border-spacing: 0; + margin-bottom: 16px; +} + +#article th, +#article td { + border: 1px solid #d0d7de; + padding: 8px 16px; +} + +#article th { + font-weight: 600; +} + +#article table { + border-collapse: collapse; + margin-bottom: 16px; + border: 1px solid rgba(255, 255, 255, 0.15); +} + +.dark #article th, +.dark #article td { + border: 1px solid rgba(255, 255, 255, 0.15); +} + +.dark #article th { + background-color: rgba(255, 255, 255, 0.1); +} + +#article tr:nth-child(even) { + background-color: #ffffff; +} + +#article tr:nth-child(odd) { + background-color: #f6f8fa; +} + + +.dark #article tr:nth-child(even) { + background-color: rgba(255, 255, 255, 0.05); +} + +.dark #article tr:nth-child(odd) { + background-color: transparent; +} + +.dark #article td { + color: rgba(255, 255, 255, 0.8); +} + +.dark #article th { + color: rgba(255, 255, 255, 1); } \ No newline at end of file From e5c83b20c91b3a030d4e12a473782550d2d48801 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:52:30 +0400 Subject: [PATCH 114/567] android, desktop: fix operator disabled indication (#5242) --- .../views/usersettings/networkAndServers/OperatorView.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt index c61a9f5ef7..e5d6f9150c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt @@ -145,7 +145,14 @@ fun OperatorViewLayout( Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically ) { - Image(painterResource(operator.largeLogo), null, Modifier.height(48.dp)) + Image( + painterResource(operator.largeLogo), + operator.tradeName, + modifier = Modifier.height(48.dp), + colorFilter = if (operator.enabled) null else ColorFilter.colorMatrix(ColorMatrix().apply { + setToSaturation(0f) + }) + ) Spacer(Modifier.fillMaxWidth().weight(1f)) Box(Modifier.padding(horizontal = 2.dp)) { Icon(painterResource(MR.images.ic_info), null, Modifier.size(24.dp), tint = MaterialTheme.colors.primaryVariant) From cfc21dfb51c58c2d129992a475783195037f1457 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 25 Nov 2024 14:15:32 +0000 Subject: [PATCH 115/567] ios: address or 1-time link (#5246) --- .../Onboarding/AddressCreationCard.swift | 4 +- .../UserSettings/UserAddressLearnMore.swift | 38 +++++++++++++++---- .../Views/UserSettings/UserAddressView.swift | 6 +-- 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift b/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift index eae64e4465..9cf755be78 100644 --- a/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift +++ b/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift @@ -87,8 +87,8 @@ struct AddressCreationCard: View { .sheet(isPresented: $showAddressInfoSheet) { NavigationView { UserAddressLearnMore(showCreateAddressButton: true) - .navigationTitle("SimpleX address") - .navigationBarTitleDisplayMode(.large) + .navigationTitle("Address or 1-time link?") + .navigationBarTitleDisplayMode(.inline) .modifier(ThemedBackground(grouped: true)) } } diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift b/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift index d4bc0959c9..22efbfcb85 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift @@ -15,21 +15,45 @@ struct UserAddressLearnMore: View { var body: some View { VStack { List { - VStack(alignment: .leading, spacing: 18) { - Text("You can share your address as a link or QR code - anybody can connect to you.") + VStack(alignment: .leading, spacing: 16) { + (Text(Image(systemName: "envelope")).foregroundColor(.secondary) + Text(" ") + Text("Share address publicly").bold().font(.title2)) + Text("Share SimpleX address on social media.") Text("You won't lose your contacts if you later delete your address.") - Text("When people request to connect, you can accept or reject it.") - Text("Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address).") + + (Text(Image(systemName: "link.badge.plus")).foregroundColor(.secondary) + Text(" ") + Text("Share 1-time link with a friend").font(.title2).bold()) + Text("1-time link can be used *with one contact only* - share in person or via any messenger.") + Text("You can set connection name, to remember who the link was shared with.") + + if !showCreateAddressButton { + (Text(Image(systemName: "shield")).foregroundColor(.secondary) + Text(" ") + Text("Connection security").font(.title2).bold()) + Text("SimpleX address and 1-time links are safe to share via any messenger.") + Text("To protect against your link being replaced, you can compare contact security codes.") + Text("Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses).") + } + } .listRowBackground(Color.clear) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } - .frame(maxHeight: .infinity) + .frame(maxHeight: .infinity, alignment: .top) + Spacer() + if showCreateAddressButton { - addressCreationButton() - .padding() + VStack { + addressCreationButton() + .padding(.bottom) + + Button("Create 1-time link") { + + } + .font(.footnote) + } + .padding() } } + .frame(maxHeight: .infinity, alignment: .top) } private func addressCreationButton() -> some View { diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift index cbc3e9b79e..8f212d8678 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift @@ -304,12 +304,12 @@ struct UserAddressView: View { private func learnMoreButton() -> some View { NavigationLink { UserAddressLearnMore() - .navigationTitle("SimpleX address") + .navigationTitle("Address or 1-time link?") .modifier(ThemedBackground(grouped: true)) - .navigationBarTitleDisplayMode(.large) + .navigationBarTitleDisplayMode(.inline) } label: { settingsRow("info.circle", color: theme.colors.secondary) { - Text("About SimpleX address") + Text("SimpleX address or 1-time link?") } } } From d912fe07a1f37a59ad8ede0883e7ba4c61185675 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Mon, 25 Nov 2024 18:51:49 +0400 Subject: [PATCH 116/567] core: fix pagination indexes (#5241) --- simplex-chat.cabal | 1 + .../Chat/Migrations/M20241125_indexes.hs | 50 +++++++ src/Simplex/Chat/Migrations/chat_schema.sql | 49 +++---- src/Simplex/Chat/Store/Messages.hs | 135 ++++++++++++++---- src/Simplex/Chat/Store/Migrations.hs | 4 +- tests/SchemaDump.hs | 6 +- 6 files changed, 186 insertions(+), 59 deletions(-) create mode 100644 src/Simplex/Chat/Migrations/M20241125_indexes.hs diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 1a65b87d0b..23071423b8 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -152,6 +152,7 @@ library Simplex.Chat.Migrations.M20241010_contact_requests_contact_id Simplex.Chat.Migrations.M20241023_chat_item_autoincrement_id Simplex.Chat.Migrations.M20241027_server_operators + Simplex.Chat.Migrations.M20241125_indexes Simplex.Chat.Mobile Simplex.Chat.Mobile.File Simplex.Chat.Mobile.Shared diff --git a/src/Simplex/Chat/Migrations/M20241125_indexes.hs b/src/Simplex/Chat/Migrations/M20241125_indexes.hs new file mode 100644 index 0000000000..2115de09a3 --- /dev/null +++ b/src/Simplex/Chat/Migrations/M20241125_indexes.hs @@ -0,0 +1,50 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Migrations.M20241125_indexes where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20241125_indexes :: Query +m20241125_indexes = + [sql| +-- contacts +DROP INDEX idx_chat_items_contacts; +DROP INDEX idx_chat_items_contacts_item_status; + +CREATE INDEX idx_chat_items_contacts ON chat_items(user_id, contact_id, item_status, created_at); + +-- groups +DROP INDEX idx_chat_items_groups; +DROP INDEX idx_chat_items_groups_item_status; + +CREATE INDEX idx_chat_items_groups ON chat_items(user_id, group_id, item_status, item_ts); +CREATE INDEX idx_chat_items_groups_item_ts ON chat_items(user_id, group_id, item_ts); + +-- notes +DROP INDEX idx_chat_items_notes_item_status; + +CREATE INDEX idx_chat_items_notes ON chat_items(user_id, note_folder_id, item_status, created_at); +|] + +down_m20241125_indexes :: Query +down_m20241125_indexes = + [sql| +-- contacts +DROP INDEX idx_chat_items_contacts; + +CREATE INDEX idx_chat_items_contacts ON chat_items(user_id, contact_id, chat_item_id); +CREATE INDEX idx_chat_items_contacts_item_status on chat_items (user_id, contact_id, item_status); + +-- groups +DROP INDEX idx_chat_items_groups; +DROP INDEX idx_chat_items_groups_item_ts; + +CREATE INDEX idx_chat_items_groups ON chat_items(user_id, group_id, item_ts, chat_item_id); +CREATE INDEX idx_chat_items_groups_item_status on chat_items (user_id, group_id, item_status); + +-- notes +DROP INDEX idx_chat_items_notes; + +CREATE INDEX idx_chat_items_notes_item_status on chat_items (user_id, note_folder_id, item_status); +|] diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index 0dc68034e7..6f944157c1 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -627,17 +627,6 @@ CREATE INDEX idx_contact_requests_xcontact_id ON contact_requests(xcontact_id); CREATE INDEX idx_contacts_xcontact_id ON contacts(xcontact_id); CREATE INDEX idx_messages_shared_msg_id ON messages(shared_msg_id); CREATE INDEX idx_chat_items_shared_msg_id ON chat_items(shared_msg_id); -CREATE INDEX idx_chat_items_groups ON chat_items( - user_id, - group_id, - item_ts, - chat_item_id -); -CREATE INDEX idx_chat_items_contacts ON chat_items( - user_id, - contact_id, - chat_item_id -); CREATE UNIQUE INDEX idx_chat_items_direct_shared_msg_id ON chat_items( user_id, contact_id, @@ -887,26 +876,11 @@ CREATE INDEX idx_chat_items_contacts_created_at on chat_items( contact_id, created_at ); -CREATE INDEX idx_chat_items_contacts_item_status on chat_items( - user_id, - contact_id, - item_status -); -CREATE INDEX idx_chat_items_groups_item_status on chat_items( - user_id, - group_id, - item_status -); CREATE INDEX idx_chat_items_notes_created_at on chat_items( user_id, note_folder_id, created_at ); -CREATE INDEX idx_chat_items_notes_item_status on chat_items( - user_id, - note_folder_id, - item_status -); CREATE INDEX idx_files_redirect_file_id on files(redirect_file_id); CREATE INDEX idx_chat_items_fwd_from_contact_id ON chat_items( fwd_from_contact_id @@ -926,3 +900,26 @@ CREATE UNIQUE INDEX idx_operator_usage_conditions_conditions_commit ON operator_ conditions_commit, server_operator_id ); +CREATE INDEX idx_chat_items_contacts ON chat_items( + user_id, + contact_id, + item_status, + created_at +); +CREATE INDEX idx_chat_items_groups ON chat_items( + user_id, + group_id, + item_status, + item_ts +); +CREATE INDEX idx_chat_items_groups_item_ts ON chat_items( + user_id, + group_id, + item_ts +); +CREATE INDEX idx_chat_items_notes ON chat_items( + user_id, + note_folder_id, + item_status, + created_at +); diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index ab8a52a98a..a79eb98f14 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -1145,27 +1145,52 @@ getContactNavInfo_ db User {userId} Contact {contactId} afterCI = do getAfterUnreadCount :: IO Int getAfterUnreadCount = fromOnly . head - <$> DB.query + <$> DB.queryNamed db [sql| SELECT COUNT(1) - FROM chat_items - WHERE user_id = ? AND contact_id = ? AND item_status = ? - AND (created_at > ? OR (created_at = ? AND chat_item_id > ?)) + FROM ( + SELECT 1 + FROM chat_items + WHERE user_id = :user_id AND contact_id = :contact_id AND item_status = :rcv_new + AND created_at > :created_at + UNION ALL + SELECT 1 + FROM chat_items + WHERE user_id = :user_id AND contact_id = :contact_id AND item_status = :rcv_new + AND created_at = :created_at AND chat_item_id > :item_id + ) |] - (userId, contactId, CISRcvNew, ciCreatedAt afterCI, ciCreatedAt afterCI, cChatItemId afterCI) + [ ":user_id" := userId, + ":contact_id" := contactId, + ":rcv_new" := CISRcvNew, + ":created_at" := ciCreatedAt afterCI, + ":item_id" := cChatItemId afterCI + ] getAfterTotalCount :: IO Int getAfterTotalCount = fromOnly . head - <$> DB.query + <$> DB.queryNamed db [sql| SELECT COUNT(1) - FROM chat_items - WHERE user_id = ? AND contact_id = ? - AND (created_at > ? OR (created_at = ? AND chat_item_id > ?)) + FROM ( + SELECT 1 + FROM chat_items + WHERE user_id = :user_id AND contact_id = :contact_id + AND created_at > :created_at + UNION ALL + SELECT 1 + FROM chat_items + WHERE user_id = :user_id AND contact_id = :contact_id + AND created_at = :created_at AND chat_item_id > :item_id + ) |] - (userId, contactId, ciCreatedAt afterCI, ciCreatedAt afterCI, cChatItemId afterCI) + [ ":user_id" := userId, + ":contact_id" := contactId, + ":created_at" := ciCreatedAt afterCI, + ":item_id" := cChatItemId afterCI + ] getGroupChat :: DB.Connection -> VersionRangeChat -> User -> Int64 -> ChatPagination -> Maybe String -> ExceptT StoreError IO (Chat 'CTGroup, Maybe NavigationInfo) getGroupChat db vr user groupId pagination search_ = do @@ -1363,27 +1388,52 @@ getGroupNavInfo_ db User {userId} GroupInfo {groupId} afterCI = do getAfterUnreadCount :: IO Int getAfterUnreadCount = fromOnly . head - <$> DB.query + <$> DB.queryNamed db [sql| SELECT COUNT(1) - FROM chat_items - WHERE user_id = ? AND group_id = ? AND item_status = ? - AND (item_ts > ? OR (item_ts = ? AND chat_item_id > ?)) + FROM ( + SELECT 1 + FROM chat_items + WHERE user_id = :user_id AND group_id = :group_id AND item_status = :rcv_new + AND item_ts > :item_ts + UNION ALL + SELECT 1 + FROM chat_items + WHERE user_id = :user_id AND group_id = :group_id AND item_status = :rcv_new + AND item_ts = :item_ts AND chat_item_id > :item_id + ) |] - (userId, groupId, CISRcvNew, chatItemTs afterCI, chatItemTs afterCI, cChatItemId afterCI) + [ ":user_id" := userId, + ":group_id" := groupId, + ":rcv_new" := CISRcvNew, + ":item_ts" := chatItemTs afterCI, + ":item_id" := cChatItemId afterCI + ] getAfterTotalCount :: IO Int getAfterTotalCount = fromOnly . head - <$> DB.query + <$> DB.queryNamed db [sql| SELECT COUNT(1) - FROM chat_items - WHERE user_id = ? AND group_id = ? - AND (item_ts > ? OR (item_ts = ? AND chat_item_id > ?)) + FROM ( + SELECT 1 + FROM chat_items + WHERE user_id = :user_id AND group_id = :group_id + AND item_ts > :item_ts + UNION ALL + SELECT 1 + FROM chat_items + WHERE user_id = :user_id AND group_id = :group_id + AND item_ts = :item_ts AND chat_item_id > :item_id + ) |] - (userId, groupId, chatItemTs afterCI, chatItemTs afterCI, cChatItemId afterCI) + [ ":user_id" := userId, + ":group_id" := groupId, + ":item_ts" := chatItemTs afterCI, + ":item_id" := cChatItemId afterCI + ] getLocalChat :: DB.Connection -> User -> Int64 -> ChatPagination -> Maybe String -> ExceptT StoreError IO (Chat 'CTLocal, Maybe NavigationInfo) getLocalChat db user folderId pagination search_ = do @@ -1565,27 +1615,52 @@ getLocalNavInfo_ db User {userId} NoteFolder {noteFolderId} afterCI = do getAfterUnreadCount :: IO Int getAfterUnreadCount = fromOnly . head - <$> DB.query + <$> DB.queryNamed db [sql| SELECT COUNT(1) - FROM chat_items - WHERE user_id = ? AND note_folder_id = ? AND item_status = ? - AND (created_at > ? OR (created_at = ? AND chat_item_id > ?)) + FROM ( + SELECT 1 + FROM chat_items + WHERE user_id = :user_id AND note_folder_id = :note_folder_id AND item_status = :rcv_new + AND created_at > :created_at + UNION ALL + SELECT 1 + FROM chat_items + WHERE user_id = :user_id AND note_folder_id = :note_folder_id AND item_status = :rcv_new + AND created_at = :created_at AND chat_item_id > :item_id + ) |] - (userId, noteFolderId, CISRcvNew, ciCreatedAt afterCI, ciCreatedAt afterCI, cChatItemId afterCI) + [ ":user_id" := userId, + ":note_folder_id" := noteFolderId, + ":rcv_new" := CISRcvNew, + ":created_at" := ciCreatedAt afterCI, + ":item_id" := cChatItemId afterCI + ] getAfterTotalCount :: IO Int getAfterTotalCount = fromOnly . head - <$> DB.query + <$> DB.queryNamed db [sql| SELECT COUNT(1) - FROM chat_items - WHERE user_id = ? AND note_folder_id = ? - AND (created_at > ? OR (created_at = ? AND chat_item_id > ?)) + FROM ( + SELECT 1 + FROM chat_items + WHERE user_id = :user_id AND note_folder_id = :note_folder_id + AND created_at > :created_at + UNION ALL + SELECT 1 + FROM chat_items + WHERE user_id = :user_id AND note_folder_id = :note_folder_id + AND created_at = :created_at AND chat_item_id > :item_id + ) |] - (userId, noteFolderId, ciCreatedAt afterCI, ciCreatedAt afterCI, cChatItemId afterCI) + [ ":user_id" := userId, + ":note_folder_id" := noteFolderId, + ":created_at" := ciCreatedAt afterCI, + ":item_id" := cChatItemId afterCI + ] toChatItemRef :: (ChatItemId, Maybe Int64, Maybe Int64, Maybe Int64) -> Either StoreError (ChatRef, ChatItemId) toChatItemRef = \case diff --git a/src/Simplex/Chat/Store/Migrations.hs b/src/Simplex/Chat/Store/Migrations.hs index 7218706239..9a91c7f970 100644 --- a/src/Simplex/Chat/Store/Migrations.hs +++ b/src/Simplex/Chat/Store/Migrations.hs @@ -116,6 +116,7 @@ import Simplex.Chat.Migrations.M20241008_indexes import Simplex.Chat.Migrations.M20241010_contact_requests_contact_id import Simplex.Chat.Migrations.M20241023_chat_item_autoincrement_id import Simplex.Chat.Migrations.M20241027_server_operators +import Simplex.Chat.Migrations.M20241125_indexes import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -231,7 +232,8 @@ schemaMigrations = ("20241008_indexes", m20241008_indexes, Just down_m20241008_indexes), ("20241010_contact_requests_contact_id", m20241010_contact_requests_contact_id, Just down_m20241010_contact_requests_contact_id), ("20241023_chat_item_autoincrement_id", m20241023_chat_item_autoincrement_id, Just down_m20241023_chat_item_autoincrement_id), - ("20241027_server_operators", m20241027_server_operators, Just down_m20241027_server_operators) + ("20241027_server_operators", m20241027_server_operators, Just down_m20241027_server_operators), + ("20241125_indexes", m20241125_indexes, Just down_m20241125_indexes) ] -- | The list of migrations in ascending order by date diff --git a/tests/SchemaDump.hs b/tests/SchemaDump.hs index 4e63a31001..d13dc94b63 100644 --- a/tests/SchemaDump.hs +++ b/tests/SchemaDump.hs @@ -103,8 +103,10 @@ skipComparisonForDownMigrations = "20231215_recreate_msg_deliveries", -- on down migration idx_msg_deliveries_agent_ack_cmd_id index moves down to the end of the file "20240313_drop_agent_ack_cmd_id", - -- on down migration chat_item_autoincrement_id makes sequence table creation move down on the file - "20241023_chat_item_autoincrement_id" + -- sequence table moves down to the end of the file + "20241023_chat_item_autoincrement_id", + -- indexes move down to the end of the file + "20241125_indexes" ] getSchema :: FilePath -> FilePath -> IO String From 7a91ed2ab244a6ee08365274ceaf2b9f96ea7cc0 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Mon, 25 Nov 2024 23:20:02 +0700 Subject: [PATCH 117/567] android, desktop: view conditions as markdown (#5247) * android, desktop: view conditions as markdown * better animation * unused * open chat links inside the app and removed divider, smaller font * paddings --- apps/multiplatform/common/build.gradle.kts | 4 + .../networkAndServers/OperatorView.kt | 93 ++++++++++++++++--- 2 files changed, 85 insertions(+), 12 deletions(-) diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts index 0e45c66efd..b9b307c8f4 100644 --- a/apps/multiplatform/common/build.gradle.kts +++ b/apps/multiplatform/common/build.gradle.kts @@ -48,6 +48,10 @@ kotlin { // Resources api("dev.icerock.moko:resources:0.23.0") api("dev.icerock.moko:resources-compose:0.23.0") + + // Markdown + implementation("com.mikepenz:multiplatform-markdown-renderer:0.27.0") + implementation("com.mikepenz:multiplatform-markdown-renderer-m2:0.27.0") } } val commonTest by getting { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt index e5d6f9150c..4e0fc6ec79 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt @@ -16,10 +16,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.* import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.platform.UriHandler import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.DpOffset -import androidx.compose.ui.unit.dp +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.unit.* import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatController.getUsageConditions @@ -29,14 +30,19 @@ import chat.simplex.common.views.chat.item.ItemAction import chat.simplex.common.views.helpers.* import chat.simplex.common.views.onboarding.* import chat.simplex.res.MR +import com.mikepenz.markdown.compose.Markdown +import com.mikepenz.markdown.compose.components.markdownComponents +import com.mikepenz.markdown.compose.elements.MarkdownHeader +import com.mikepenz.markdown.m2.markdownColor +import com.mikepenz.markdown.m2.markdownTypography +import com.mikepenz.markdown.model.markdownPadding import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import java.net.URI +import kotlinx.coroutines.* +import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor @Composable -fun ModalData.OperatorView( +fun OperatorView( currUserServers: MutableState>, userServers: MutableState>, serverErrors: MutableState>, @@ -610,13 +616,20 @@ fun ConditionsTextView( val defaultConditionsLink = "https://github.com/simplex-chat/simplex-chat/blob/stable/PRIVACY.md" val scope = rememberCoroutineScope() + // can show conditions when animation between modals finishes to prevent glitches + val canShowConditionsAt = remember { System.currentTimeMillis() + 300 } LaunchedEffect(Unit) { - scope.launch { + scope.launch(Dispatchers.Default) { try { val conditions = getUsageConditions(rh = rhId) if (conditions != null) { - conditionsData.value = conditions + val parentLink = "https://github.com/simplex-chat/simplex-chat/blob/${conditions.first.conditionsCommit}" + val conditionsText = conditions.second + val preparedText = if (conditionsText != null) prepareMarkdown(conditionsText.trimIndent(), parentLink) else null + val modifiedConditions = Triple(conditions.first, preparedText, conditions.third) + delay((canShowConditionsAt - System.currentTimeMillis()).coerceAtLeast(0)) + conditionsData.value = modifiedConditions } else { failedToLoad.value = true } @@ -639,10 +652,10 @@ fun ConditionsTextView( .verticalScroll(scrollState) .padding(8.dp) ) { - Text( - text = conditionsText.trimIndent(), - modifier = Modifier.padding(8.dp) - ) + val parentUriHandler = LocalUriHandler.current + CompositionLocalProvider(LocalUriHandler provides remember { internalUriHandler(parentUriHandler) }) { + ConditionsMarkdown(conditionsText) + } } } else { val conditionsLink = "https://github.com/simplex-chat/simplex-chat/blob/${usageConditions.conditionsCommit}/PRIVACY.md" @@ -655,6 +668,44 @@ fun ConditionsTextView( } } +@Composable +private fun ConditionsMarkdown(text: String) { + Markdown(text, + markdownColor(linkText = MaterialTheme.colors.primary), + markdownTypography( + h1 = MaterialTheme.typography.body1, + h2 = MaterialTheme.typography.h3.copy(fontSize = 22.sp, fontWeight = FontWeight.Bold), + h3 = MaterialTheme.typography.h4.copy(fontWeight = FontWeight.Bold), + h4 = MaterialTheme.typography.h5.copy(fontSize = 16.sp, fontWeight = FontWeight.Bold), + h5 = MaterialTheme.typography.h6.copy(fontWeight = FontWeight.Bold), + link = MaterialTheme.typography.body1.copy( + textDecoration = TextDecoration.Underline + ) + ), + Modifier.padding(8.dp), + // using CommonMarkFlavourDescriptor instead of GFMFlavourDescriptor because it shows `https://simplex.chat/` (link inside backticks) incorrectly + flavour = CommonMarkFlavourDescriptor(), + components = markdownComponents( + heading2 = { + Spacer(Modifier.height(10.dp)) + MarkdownHeader(it.content, it.node, it.typography.h2) + Spacer(Modifier.height(5.dp)) + }, + heading3 = { + Spacer(Modifier.height(10.dp)) + MarkdownHeader(it.content, it.node, it.typography.h3) + Spacer(Modifier.height(3.dp)) + }, + heading4 = { + Spacer(Modifier.height(10.dp)) + MarkdownHeader(it.content, it.node, it.typography.h4) + Spacer(Modifier.height(4.dp)) + }, + ), + padding = markdownPadding(block = 4.dp) + ) +} + @Composable private fun ConditionsLinkView(conditionsLink: String) { SectionItemView { @@ -706,6 +757,24 @@ fun ConditionsLinkButton() { } } +private fun internalUriHandler(parentUriHandler: UriHandler): UriHandler = object: UriHandler { + override fun openUri(uri: String) { + if (uri.startsWith("https://simplex.chat/contact#")) { + openVerifiedSimplexUri(uri) + } else { + parentUriHandler.openUriCatching(uri) + } + } +} + +private fun prepareMarkdown(text: String, parentLink: String): String { + val localLinkRegex = Regex("\\[([^\\)]*)\\]\\(#.*\\)", RegexOption.MULTILINE) + return text + .replace("](/", "]($parentLink/") + .replace("](./", "]($parentLink/") + .replace(localLinkRegex) { it.groupValues.getOrNull(1) ?: it.value } +} + private fun changeOperatorEnabled(userServers: MutableState>, operatorIndex: Int, enabled: Boolean) { userServers.value = userServers.value.toMutableList().apply { this[operatorIndex] = this[operatorIndex].copy( From 1f04984a3448e43514ea588c3ad571300946222d Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:43:39 +0400 Subject: [PATCH 118/567] ios: offer to create 1-time link on address views (#5249) --- apps/ios/Shared/Model/ChatModel.swift | 4 +- apps/ios/Shared/Views/ChatList/ChatHelp.swift | 3 +- .../Views/NewChat/NewChatMenuButton.swift | 14 +- .../Shared/Views/NewChat/NewChatView.swift | 58 ++- .../UserSettings/UserAddressLearnMore.swift | 32 +- .../Views/UserSettings/UserAddressView.swift | 356 +++++++++++------- 6 files changed, 273 insertions(+), 194 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 4bc5917ed7..6b6b0ac03f 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -845,7 +845,7 @@ final class ChatModel: ObservableObject { } func dismissConnReqView(_ id: String) { - if id == showingInvitation?.connId { + if id == showingInvitation?.pcc.id { markShowingInvitationUsed() dismissAllSheets() } @@ -898,7 +898,7 @@ final class ChatModel: ObservableObject { } struct ShowingInvitation { - var connId: String + var pcc: PendingContactConnection var connChatUsed: Bool } diff --git a/apps/ios/Shared/Views/ChatList/ChatHelp.swift b/apps/ios/Shared/Views/ChatList/ChatHelp.swift index a01c81bafb..776229f60a 100644 --- a/apps/ios/Shared/Views/ChatList/ChatHelp.swift +++ b/apps/ios/Shared/Views/ChatList/ChatHelp.swift @@ -42,7 +42,8 @@ struct ChatHelp: View { Text("above, then choose:") } - Text("**Add contact**: to create a new invitation link, or connect via a link you received.") + Text("**Create 1-time link**: to create a new invitation link.") + Text("**Scan / Paste link**: to connect via a link you received.") Text("**Create group**: to create a new group.") } .padding(.top, 24) diff --git a/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift b/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift index 3ca3e0e4d8..6f973983bf 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift @@ -18,7 +18,6 @@ struct NewChatMenuButton: View { // @EnvironmentObject var chatModel: ChatModel @State private var showNewChatSheet = false @State private var alert: SomeAlert? = nil - @State private var pendingConnection: PendingContactConnection? = nil var body: some View { Button { @@ -30,12 +29,8 @@ struct NewChatMenuButton: View { .frame(width: 24, height: 24) } .appSheet(isPresented: $showNewChatSheet) { - NewChatSheet(pendingConnection: $pendingConnection) + NewChatSheet() .environment(\EnvironmentValues.refresh as! WritableKeyPath, nil) - .onDisappear { - alert = cleanupPendingConnection(contactConnection: pendingConnection) - pendingConnection = nil - } } .alert(item: $alert) { a in return a.alert @@ -55,7 +50,6 @@ struct NewChatSheet: View { @State private var searchShowingSimplexLink = false @State private var searchChatFilteredBySimplexLink: String? = nil @State private var alert: SomeAlert? - @Binding var pendingConnection: PendingContactConnection? // Sheet height management @State private var isAddContactActive = false @@ -110,17 +104,17 @@ struct NewChatSheet: View { if (searchText.isEmpty) { Section { NavigationLink(isActive: $isAddContactActive) { - NewChatView(selection: .invite, parentAlert: $alert, contactConnection: $pendingConnection) + NewChatView(selection: .invite) .navigationTitle("New chat") .modifier(ThemedBackground(grouped: true)) .navigationBarTitleDisplayMode(.large) } label: { - navigateOnTap(Label("Add contact", systemImage: "link.badge.plus")) { + navigateOnTap(Label("Create 1-time link", systemImage: "link.badge.plus")) { isAddContactActive = true } } NavigationLink(isActive: $isScanPasteLinkActive) { - NewChatView(selection: .connect, showQRCodeScanner: true, parentAlert: $alert, contactConnection: $pendingConnection) + NewChatView(selection: .connect, showQRCodeScanner: true) .navigationTitle("New chat") .modifier(ThemedBackground(grouped: true)) .navigationBarTitleDisplayMode(.large) diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift index 4ca33e674d..19e810d034 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatView.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -45,32 +45,33 @@ enum NewChatOption: Identifiable { var id: Self { self } } -func cleanupPendingConnection(contactConnection: PendingContactConnection?) -> SomeAlert? { - var alert: SomeAlert? = nil - - if !(ChatModel.shared.showingInvitation?.connChatUsed ?? true), - let conn = contactConnection { - alert = SomeAlert( - alert: Alert( - title: Text("Keep unused invitation?"), - message: Text("You can view invitation link again in connection details."), - primaryButton: .default(Text("Keep")) {}, - secondaryButton: .destructive(Text("Delete")) { - Task { - await deleteChat(Chat( - chatInfo: .contactConnection(contactConnection: conn), - chatItems: [] - )) +func showKeepInvitationAlert() { + if let showingInvitation = ChatModel.shared.showingInvitation, + !showingInvitation.connChatUsed { + showAlert( + NSLocalizedString("Keep unused invitation?", comment: "alert title"), + message: NSLocalizedString("You can view invitation link again in connection details.", comment: "alert message"), + actions: {[ + UIAlertAction( + title: NSLocalizedString("Keep", comment: "alert action"), + style: .default + ), + UIAlertAction( + title: NSLocalizedString("Delete", comment: "alert action"), + style: .destructive, + handler: { _ in + Task { + await deleteChat(Chat( + chatInfo: .contactConnection(contactConnection: showingInvitation.pcc), + chatItems: [] + )) + } } - } - ), - id: "keepUnusedInvitation" + ) + ]} ) } - ChatModel.shared.showingInvitation = nil - - return alert } struct NewChatView: View { @@ -84,13 +85,12 @@ struct NewChatView: View { @State var choosingProfile = false @State private var pastedLink: String = "" @State private var alert: NewChatViewAlert? - @Binding var parentAlert: SomeAlert? - @Binding var contactConnection: PendingContactConnection? + @State private var contactConnection: PendingContactConnection? = nil var body: some View { VStack(alignment: .leading) { Picker("New chat", selection: $selection) { - Label("Add contact", systemImage: "link") + Label("1-time link", systemImage: "link") .tag(NewChatOption.invite) Label("Connect via link", systemImage: "qrcode") .tag(NewChatOption.connect) @@ -157,7 +157,7 @@ struct NewChatView: View { } .onDisappear { if !choosingProfile { - parentAlert = cleanupPendingConnection(contactConnection: contactConnection) + showKeepInvitationAlert() contactConnection = nil } } @@ -197,7 +197,7 @@ struct NewChatView: View { if let (connReq, pcc) = r { await MainActor.run { m.updateContactConnection(pcc) - m.showingInvitation = ShowingInvitation(connId: pcc.id, connChatUsed: false) + m.showingInvitation = ShowingInvitation(pcc: pcc, connChatUsed: false) connReqInvitation = connReq contactConnection = pcc } @@ -1278,9 +1278,7 @@ struct NewChatView_Previews: PreviewProvider { @State var contactConnection: PendingContactConnection? = nil NewChatView( - selection: .invite, - parentAlert: $parentAlert, - contactConnection: $contactConnection + selection: .invite ) } } diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift b/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift index 22efbfcb85..414e7efe85 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift @@ -11,7 +11,8 @@ import SwiftUI struct UserAddressLearnMore: View { @State var showCreateAddressButton = false @State private var createAddressLinkActive = false - + @State private var createOneTimeLinkActive = false + var body: some View { VStack { List { @@ -44,11 +45,8 @@ struct UserAddressLearnMore: View { VStack { addressCreationButton() .padding(.bottom) - - Button("Create 1-time link") { - - } - .font(.footnote) + + createOneTimeLinkButton() } .padding() } @@ -76,6 +74,28 @@ struct UserAddressLearnMore: View { .hidden() } } + + private func createOneTimeLinkButton() -> some View { + ZStack { + Button { + createOneTimeLinkActive = true + } label: { + Text("Create 1-time link") + .font(.footnote) + } + + NavigationLink(isActive: $createOneTimeLinkActive) { + NewChatView(selection: .invite) + .navigationTitle("New chat") + .navigationBarTitleDisplayMode(.large) + .modifier(ThemedBackground(grouped: true)) + } label: { + EmptyView() + } + .frame(width: 1, height: 1) + .hidden() + } + } } struct UserAddressLearnMore_Previews: PreviewProvider { diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift index 8f212d8678..28301c5ddb 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift @@ -16,45 +16,28 @@ struct UserAddressView: View { @EnvironmentObject var theme: AppTheme @State var shareViaProfile = false @State var autoCreate = false - @State private var aas = AutoAcceptState() - @State private var savedAAS = AutoAcceptState() - @State private var ignoreShareViaProfileChange = false @State private var showMailView = false @State private var mailViewResult: Result? = nil @State private var alert: UserAddressAlert? @State private var progressIndicator = false - @FocusState private var keyboardVisible: Bool private enum UserAddressAlert: Identifiable { case deleteAddress - case profileAddress(on: Bool) case shareOnCreate case error(title: LocalizedStringKey, error: LocalizedStringKey?) var id: String { switch self { case .deleteAddress: return "deleteAddress" - case let .profileAddress(on): return "profileAddress \(on)" case .shareOnCreate: return "shareOnCreate" case let .error(title, _): return "error \(title)" } } } - + var body: some View { ZStack { - userAddressScrollView() - .onDisappear { - if savedAAS != aas { - showAlert( - title: NSLocalizedString("Auto-accept settings", comment: "alert title"), - message: NSLocalizedString("Settings were changed.", comment: "alert message"), - buttonTitle: NSLocalizedString("Save", comment: "alert button"), - buttonAction: saveAAS, - cancelButton: true - ) - } - } + userAddressView() if progressIndicator { ZStack { @@ -75,39 +58,22 @@ struct UserAddressView: View { } } - @Namespace private var bottomID - - private func userAddressScrollView() -> some View { - ScrollViewReader { proxy in - userAddressView() - .onChange(of: keyboardVisible) { _ in - if keyboardVisible { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - withAnimation { - proxy.scrollTo(bottomID, anchor: .top) - } - } - } - } - } - } - private func userAddressView() -> some View { List { if let userAddress = chatModel.userAddress { existingAddressView(userAddress) - .onAppear { - aas = AutoAcceptState(userAddress: userAddress) - savedAAS = aas - } - .onChange(of: aas.enable) { _ in - if !aas.enable { aas = AutoAcceptState() } - } } else { Section { createAddressButton() - } footer: { - Text("Create an address to let people connect with you.") + } header: { + Text("For social media") + .foregroundColor(theme.colors.secondary) + } + + Section { + createOneTimeLinkButton() + } header: { + Text("Or to share privately") .foregroundColor(theme.colors.secondary) } @@ -123,8 +89,8 @@ struct UserAddressView: View { title: Text("Delete address?"), message: shareViaProfile - ? Text("All your contacts will remain connected. Profile update will be sent to your contacts.") - : Text("All your contacts will remain connected."), + ? Text("All your contacts will remain connected. Profile update will be sent to your contacts.") + : Text("All your contacts will remain connected."), primaryButton: .destructive(Text("Delete")) { progressIndicator = true Task { @@ -134,7 +100,6 @@ struct UserAddressView: View { chatModel.userAddress = nil chatModel.updateUser(u) if shareViaProfile { - ignoreShareViaProfileChange = true shareViaProfile = false } } @@ -147,37 +112,12 @@ struct UserAddressView: View { } }, secondaryButton: .cancel() ) - case let .profileAddress(on): - if on { - return Alert( - title: Text("Share address with contacts?"), - message: Text("Profile update will be sent to your contacts."), - primaryButton: .default(Text("Share")) { - setProfileAddress(on) - }, secondaryButton: .cancel() { - ignoreShareViaProfileChange = true - shareViaProfile = !on - } - ) - } else { - return Alert( - title: Text("Stop sharing address?"), - message: Text("Profile update will be sent to your contacts."), - primaryButton: .default(Text("Stop sharing")) { - setProfileAddress(on) - }, secondaryButton: .cancel() { - ignoreShareViaProfileChange = true - shareViaProfile = !on - } - ) - } case .shareOnCreate: return Alert( title: Text("Share address with contacts?"), message: Text("Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts."), primaryButton: .default(Text("Share")) { - setProfileAddress(true) - ignoreShareViaProfileChange = true + setProfileAddress($progressIndicator, true) shareViaProfile = true }, secondaryButton: .cancel() ) @@ -192,19 +132,24 @@ struct UserAddressView: View { SimpleXLinkQRCode(uri: userAddress.connReqContact) .id("simplex-contact-address-qrcode-\(userAddress.connReqContact)") shareQRCodeButton(userAddress) - if MFMailComposeViewController.canSendMail() { - shareViaEmailButton(userAddress) - } - shareWithContactsButton() - autoAcceptToggle() - learnMoreButton() + // if MFMailComposeViewController.canSendMail() { + // shareViaEmailButton(userAddress) + // } + addressSettingsButton(userAddress) } header: { - Text("Address") + Text("For social media") .foregroundColor(theme.colors.secondary) } - if aas.enable { - autoAcceptSection() + Section { + createOneTimeLinkButton() + } header: { + Text("Or to share privately") + .foregroundColor(theme.colors.secondary) + } + + Section { + learnMoreButton() } Section { @@ -213,7 +158,6 @@ struct UserAddressView: View { Text("Your contacts will remain connected.") .foregroundColor(theme.colors.secondary) } - .id(bottomID) } private func createAddressButton() -> some View { @@ -223,7 +167,7 @@ struct UserAddressView: View { Label("Create SimpleX address", systemImage: "qrcode") } } - + private func createAddress() { progressIndicator = true Task { @@ -243,6 +187,18 @@ struct UserAddressView: View { } } + private func createOneTimeLinkButton() -> some View { + NavigationLink { + NewChatView(selection: .invite) + .navigationTitle("New chat") + .navigationBarTitleDisplayMode(.large) + .modifier(ThemedBackground(grouped: true)) + } label: { + Label("Create 1-time link", systemImage: "link.badge.plus") + .foregroundColor(theme.colors.primary) + } + } + private func deleteAddressButton() -> some View { Button(role: .destructive) { alert = .deleteAddress @@ -292,12 +248,14 @@ struct UserAddressView: View { } } - private func autoAcceptToggle() -> some View { - settingsRow("checkmark", color: theme.colors.secondary) { - Toggle("Auto-accept", isOn: $aas.enable) - .onChange(of: aas.enable) { _ in - saveAAS() - } + private func addressSettingsButton(_ userAddress: UserContactLink) -> some View { + NavigationLink { + UserAddressSettingsView(shareViaProfile: $shareViaProfile) + .navigationTitle("Address settings") + .navigationBarTitleDisplayMode(.large) + .modifier(ThemedBackground(grouped: true)) + } label: { + Text("Address settings") } } @@ -313,6 +271,116 @@ struct UserAddressView: View { } } } +} + +private struct AutoAcceptState: Equatable { + var enable = false + var incognito = false + var welcomeText = "" + + init(enable: Bool = false, incognito: Bool = false, welcomeText: String = "") { + self.enable = enable + self.incognito = incognito + self.welcomeText = welcomeText + } + + init(userAddress: UserContactLink) { + if let aa = userAddress.autoAccept { + enable = true + incognito = aa.acceptIncognito + if let msg = aa.autoReply { + welcomeText = msg.text + } else { + welcomeText = "" + } + } else { + enable = false + incognito = false + welcomeText = "" + } + } + + var autoAccept: AutoAccept? { + if enable { + var autoReply: MsgContent? = nil + let s = welcomeText.trimmingCharacters(in: .whitespacesAndNewlines) + if s != "" { autoReply = .text(s) } + return AutoAccept(acceptIncognito: incognito, autoReply: autoReply) + } + return nil + } +} + +private func setProfileAddress(_ progressIndicator: Binding, _ on: Bool) { + progressIndicator.wrappedValue = true + Task { + do { + if let u = try await apiSetProfileAddress(on: on) { + DispatchQueue.main.async { + ChatModel.shared.updateUser(u) + } + } + await MainActor.run { progressIndicator.wrappedValue = false } + } catch let error { + logger.error("apiSetProfileAddress: \(responseError(error))") + await MainActor.run { progressIndicator.wrappedValue = false } + } + } +} + +struct UserAddressSettingsView: View { + @Environment(\.dismiss) var dismiss: DismissAction + @EnvironmentObject var theme: AppTheme + @Binding var shareViaProfile: Bool + @State private var aas = AutoAcceptState() + @State private var savedAAS = AutoAcceptState() + @State private var ignoreShareViaProfileChange = false + @State private var progressIndicator = false + @FocusState private var keyboardVisible: Bool + + var body: some View { + ZStack { + if let userAddress = ChatModel.shared.userAddress { + userAddressSettingsView() + .onAppear { + aas = AutoAcceptState(userAddress: userAddress) + savedAAS = aas + } + .onChange(of: aas.enable) { aasEnabled in + if !aasEnabled { aas = AutoAcceptState() } + } + .onDisappear { + if savedAAS != aas { + showAlert( + title: NSLocalizedString("Auto-accept settings", comment: "alert title"), + message: NSLocalizedString("Settings were changed.", comment: "alert message"), + buttonTitle: NSLocalizedString("Save", comment: "alert button"), + buttonAction: saveAAS, + cancelButton: true + ) + } + } + } else { + Text(String("Error opening address settings")) + } + if progressIndicator { + ProgressView().scaleEffect(2) + } + } + } + + private func userAddressSettingsView() -> some View { + List { + Section { + shareWithContactsButton() + autoAcceptToggle() + } + + if aas.enable { + autoAcceptSection() + } + } + } private func shareWithContactsButton() -> some View { settingsRow("person", color: theme.colors.secondary) { @@ -321,68 +389,66 @@ struct UserAddressView: View { if ignoreShareViaProfileChange { ignoreShareViaProfileChange = false } else { - alert = .profileAddress(on: on) + if on { + showAlert( + NSLocalizedString("Share address with contacts?", comment: "alert title"), + message: NSLocalizedString("Profile update will be sent to your contacts.", comment: "alert message"), + actions: {[ + UIAlertAction( + title: NSLocalizedString("Cancel", comment: "alert action"), + style: .default, + handler: { _ in + ignoreShareViaProfileChange = true + shareViaProfile = !on + } + ), + UIAlertAction( + title: NSLocalizedString("Share", comment: "alert action"), + style: .default, + handler: { _ in + setProfileAddress($progressIndicator, on) + } + ) + ]} + ) + } else { + showAlert( + NSLocalizedString("Stop sharing address?", comment: "alert title"), + message: NSLocalizedString("Profile update will be sent to your contacts.", comment: "alert message"), + actions: {[ + UIAlertAction( + title: NSLocalizedString("Cancel", comment: "alert action"), + style: .default, + handler: { _ in + ignoreShareViaProfileChange = true + shareViaProfile = !on + } + ), + UIAlertAction( + title: NSLocalizedString("Stop sharing", comment: "alert action"), + style: .default, + handler: { _ in + setProfileAddress($progressIndicator, on) + } + ) + ]} + ) + } } } } } - private func setProfileAddress(_ on: Bool) { - progressIndicator = true - Task { - do { - if let u = try await apiSetProfileAddress(on: on) { - DispatchQueue.main.async { - chatModel.updateUser(u) - } + private func autoAcceptToggle() -> some View { + settingsRow("checkmark", color: theme.colors.secondary) { + Toggle("Auto-accept", isOn: $aas.enable) + .onChange(of: aas.enable) { _ in + saveAAS() } - await MainActor.run { progressIndicator = false } - } catch let error { - logger.error("UserAddressView apiSetProfileAddress: \(responseError(error))") - await MainActor.run { progressIndicator = false } - } - } - } - - private struct AutoAcceptState: Equatable { - var enable = false - var incognito = false - var welcomeText = "" - - init(enable: Bool = false, incognito: Bool = false, welcomeText: String = "") { - self.enable = enable - self.incognito = incognito - self.welcomeText = welcomeText - } - - init(userAddress: UserContactLink) { - if let aa = userAddress.autoAccept { - enable = true - incognito = aa.acceptIncognito - if let msg = aa.autoReply { - welcomeText = msg.text - } else { - welcomeText = "" - } - } else { - enable = false - incognito = false - welcomeText = "" - } - } - - var autoAccept: AutoAccept? { - if enable { - var autoReply: MsgContent? = nil - let s = welcomeText.trimmingCharacters(in: .whitespacesAndNewlines) - if s != "" { autoReply = .text(s) } - return AutoAccept(acceptIncognito: incognito, autoReply: autoReply) - } - return nil } } - @ViewBuilder private func autoAcceptSection() -> some View { + private func autoAcceptSection() -> some View { Section { acceptIncognitoToggle() welcomeMessageEditor() @@ -434,7 +500,7 @@ struct UserAddressView: View { Task { do { if let address = try await userAddressAutoAccept(aas.autoAccept) { - chatModel.userAddress = address + ChatModel.shared.userAddress = address savedAAS = aas } } catch let error { From 345e0acdec4eed587519c99bfb94c23c0e580620 Mon Sep 17 00:00:00 2001 From: Diogo Date: Tue, 26 Nov 2024 12:26:35 +0000 Subject: [PATCH 119/567] ios: onboarding redesign (#5252) * ios: onboarding redesign * shorter texts * updates * more updates * remove extra padding when focused * strings --------- Co-authored-by: Evgeny Poberezkin --- .../Onboarding/ChooseServerOperators.swift | 45 ++++--- .../Views/Onboarding/CreateProfile.swift | 64 ++++++---- .../Shared/Views/Onboarding/HowItWorks.swift | 6 +- .../Views/Onboarding/OnboardingView.swift | 8 +- .../Onboarding/SetNotificationsMode.swift | 112 ++++++++++++++---- .../Shared/Views/Onboarding/SimpleXInfo.swift | 72 ++++++----- .../NetworkAndServers/NetworkAndServers.swift | 3 +- .../UserSettings/NotificationsView.swift | 8 ++ apps/ios/SimpleXChat/APITypes.swift | 14 ++- 9 files changed, 210 insertions(+), 122 deletions(-) diff --git a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift index 19d67bc62c..4efdb99f21 100644 --- a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift +++ b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift @@ -73,14 +73,20 @@ struct ChooseServerOperators: View { GeometryReader { g in ScrollView { VStack(alignment: .leading, spacing: 20) { - if !onboarding { - Text("Choose operators") - .font(.largeTitle) - .bold() + let title = Text("Server operators") + .font(.largeTitle) + .bold() + .frame(maxWidth: .infinity, alignment: .center) + + if onboarding { + title.padding(.top, 50) + } else { + title } infoText() - + .frame(maxWidth: .infinity, alignment: .center) + Spacer() ForEach(serverOperators) { srvOperator in @@ -117,11 +123,10 @@ struct ChooseServerOperators: View { .foregroundColor(.clear) } } - .font(.callout) - .padding(.top) + .font(.system(size: 17, weight: .semibold)) + .frame(minHeight: 40) } } - .padding(.bottom) if !onboarding && !reviewForOperators.isEmpty { VStack(spacing: 8) { @@ -162,21 +167,15 @@ struct ChooseServerOperators: View { } } .frame(maxHeight: .infinity) - .padding() + .padding(onboarding ? 25 : 16) } private func infoText() -> some View { - HStack(spacing: 12) { - Image(systemName: "info.circle") - .resizable() - .scaledToFit() - .frame(width: 20, height: 20) - .foregroundColor(theme.colors.primary) - .onTapGesture { - sheetItem = .showInfo - } - - Text("Select network operators to use.") + Button { + sheetItem = .showInfo + } label: { + Label("How it helps privacy", systemImage: "info.circle") + .font(.headline) } } @@ -305,8 +304,6 @@ struct ChooseServerOperators: View { private func notificationsModeDestinationView() -> some View { SetNotificationsMode() - .navigationTitle("Push notifications") - .navigationBarTitleDisplayMode(.large) .navigationBarBackButtonHidden(true) .modifier(ThemedBackground()) } @@ -334,7 +331,7 @@ struct ChooseServerOperators: View { .padding(.bottom) .padding(.bottom) } - .padding(.horizontal) + .padding(.horizontal, 25) .frame(maxHeight: .infinity) } @@ -411,7 +408,7 @@ struct ChooseServerOperators: View { struct ChooseServerOperatorsInfoView: View { var body: some View { VStack(alignment: .leading) { - Text("Network operators") + Text("Server operators") .font(.largeTitle) .bold() .padding(.vertical) diff --git a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift index c6760319b1..7665e57cc1 100644 --- a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift +++ b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift @@ -119,49 +119,67 @@ struct CreateFirstProfile: View { var body: some View { VStack(alignment: .leading, spacing: 20) { - Text("Your profile, contacts and delivered messages are stored on your device.") - .font(.callout) - .foregroundColor(theme.colors.secondary) - Text("The profile is only shared with your contacts.") - .font(.callout) - .foregroundColor(theme.colors.secondary) + VStack(alignment: .center, spacing: 20) { + Text("Create your profile") + .font(.largeTitle) + .bold() + .multilineTextAlignment(.center) + + Text("Your profile, contacts and delivered messages are stored on your device.") + .font(.callout) + .foregroundColor(theme.colors.secondary) + .multilineTextAlignment(.center) + + Text("The profile is only shared with your contacts.") + .font(.callout) + .foregroundColor(theme.colors.secondary) + .multilineTextAlignment(.center) + } + .frame(maxWidth: .infinity) // Ensures it takes up the full width + .padding(.top, 25) + .padding(.horizontal, 10) HStack { let name = displayName.trimmingCharacters(in: .whitespaces) let validName = mkValidName(name) - ZStack { + ZStack(alignment: .trailing) { + TextField("Enter your name…", text: $displayName) + .focused($focusDisplayName) + .padding(.horizontal) + .padding(.vertical, 10) + .background( + RoundedRectangle(cornerRadius: 10, style: .continuous) + .fill(Color(uiColor: .tertiarySystemFill)) + ) if name != validName { Button { showAlert(.invalidNameError(validName: validName)) } label: { - Image(systemName: "exclamationmark.circle").foregroundColor(.red) + Image(systemName: "exclamationmark.circle") + .foregroundColor(.red) + .padding(.horizontal, 10) } - } else { - Image(systemName: "exclamationmark.circle").foregroundColor(.clear) - Image(systemName: "pencil").foregroundColor(theme.colors.secondary) } } - TextField("Enter your name…", text: $displayName) - .focused($focusDisplayName) - .padding(.horizontal) - .padding(.vertical, 10) - .background( - RoundedRectangle(cornerRadius: 10, style: .continuous) - .fill(Color(uiColor: .tertiarySystemFill)) - ) } .padding(.top) Spacer() - createProfileButton() - .padding(.bottom) + VStack(spacing: 10) { + createProfileButton() + if !focusDisplayName { + onboardingButtonPlaceholder() + } + } } .onAppear() { focusDisplayName = true setLastVersionDefault() } - .padding() + .padding(.horizontal, 25) + .padding(.top, 10) + .padding(.bottom, 25) .frame(maxWidth: .infinity, alignment: .leading) } @@ -191,8 +209,6 @@ struct CreateFirstProfile: View { private func nextStepDestinationView() -> some View { ChooseServerOperators(onboarding: true) - .navigationTitle("Choose operators") - .navigationBarTitleDisplayMode(.large) .navigationBarBackButtonHidden(true) .modifier(ThemedBackground()) } diff --git a/apps/ios/Shared/Views/Onboarding/HowItWorks.swift b/apps/ios/Shared/Views/Onboarding/HowItWorks.swift index 9a0ee4ddeb..66e63fd9c2 100644 --- a/apps/ios/Shared/Views/Onboarding/HowItWorks.swift +++ b/apps/ios/Shared/Views/Onboarding/HowItWorks.swift @@ -40,8 +40,10 @@ struct HowItWorks: View { Spacer() if onboarding { - createFirstProfileButton() - .padding(.bottom) + VStack(spacing: 10) { + createFirstProfileButton() + onboardingButtonPlaceholder() + } } } .lineLimit(10) diff --git a/apps/ios/Shared/Views/Onboarding/OnboardingView.swift b/apps/ios/Shared/Views/Onboarding/OnboardingView.swift index d004e0306f..b2b1b8fa68 100644 --- a/apps/ios/Shared/Views/Onboarding/OnboardingView.swift +++ b/apps/ios/Shared/Views/Onboarding/OnboardingView.swift @@ -24,14 +24,10 @@ struct OnboardingView: View { CreateSimpleXAddress() case .step3_ChooseServerOperators: ChooseServerOperators(onboarding: true) - .navigationTitle("Choose operators") - .navigationBarTitleDisplayMode(.large) .navigationBarBackButtonHidden(true) .modifier(ThemedBackground()) case .step4_SetNotificationsMode: SetNotificationsMode() - .navigationTitle("Push notifications") - .navigationBarTitleDisplayMode(.large) .navigationBarBackButtonHidden(true) .modifier(ThemedBackground()) case .onboardingComplete: EmptyView() @@ -40,6 +36,10 @@ struct OnboardingView: View { } } +func onboardingButtonPlaceholder() -> some View { + Spacer().frame(height: 40) +} + enum OnboardingStage: String, Identifiable { case step1_SimpleXInfo case step2_CreateProfile // deprecated diff --git a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift index 91a755459a..cba290c286 100644 --- a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift +++ b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift @@ -13,41 +13,55 @@ struct SetNotificationsMode: View { @EnvironmentObject var m: ChatModel @State private var notificationMode = NotificationsMode.instant @State private var showAlert: NotificationAlert? + @State private var showInfo: Bool = false var body: some View { GeometryReader { g in ScrollView { - VStack(alignment: .leading, spacing: 20) { - Text("Send notifications:") + VStack(alignment: .center, spacing: 20) { + Text("Push Notifications") + .font(.largeTitle) + .bold() + .padding(.top, 50) + + infoText() + + Spacer() + ForEach(NotificationsMode.values) { mode in NtfModeSelector(mode: mode, selection: $notificationMode) } Spacer() - Button { - if let token = m.deviceToken { - setNotificationsMode(token, notificationMode) - } else { - AlertManager.shared.showAlertMsg(title: "No device token!") - } - onboardingStageDefault.set(.onboardingComplete) - m.onboardingStage = .onboardingComplete - } label: { - if case .off = notificationMode { - Text("Use chat") - } else { - Text("Enable notifications") + VStack(spacing: 10) { + Button { + if let token = m.deviceToken { + setNotificationsMode(token, notificationMode) + } else { + AlertManager.shared.showAlertMsg(title: "No device token!") + } + onboardingStageDefault.set(.onboardingComplete) + m.onboardingStage = .onboardingComplete + } label: { + if case .off = notificationMode { + Text("Use chat") + } else { + Text("Enable notifications") + } } + .buttonStyle(OnboardingButtonStyle()) + onboardingButtonPlaceholder() } - .buttonStyle(OnboardingButtonStyle()) - .padding(.bottom) } - .padding() + .padding(25) .frame(minHeight: g.size.height) } } .frame(maxHeight: .infinity) + .sheet(isPresented: $showInfo) { + NotificationsInfoView() + } } private func setNotificationsMode(_ token: DeviceToken, _ mode: NotificationsMode) { @@ -73,6 +87,15 @@ struct SetNotificationsMode: View { } } } + + private func infoText() -> some View { + Button { + showInfo = true + } label: { + Label("How it affects privacy", systemImage: "info.circle") + .font(.headline) + } + } } struct NtfModeSelector: View { @@ -83,15 +106,24 @@ struct NtfModeSelector: View { var body: some View { ZStack { - VStack(alignment: .leading, spacing: 4) { - Text(mode.label) - .font(.headline) + HStack(spacing: 16) { + Image(systemName: mode.icon) + .resizable() + .scaledToFill() + .frame(width: mode.icon == "bolt" ? 14 : 18, height: 18) .foregroundColor(selection == mode ? theme.colors.primary : theme.colors.secondary) - Text(ntfModeDescription(mode)) - .lineLimit(10) - .font(.subheadline) + VStack(alignment: .leading, spacing: 4) { + Text(mode.label) + .font(.headline) + .foregroundColor(selection == mode ? theme.colors.primary : theme.colors.secondary) + Text(ntfModeShortDescription(mode)) + .lineLimit(2) + .font(.callout) + } } - .padding(12) + .padding(.vertical, 12) + .padding(.trailing, 12) + .padding(.leading, 16) } .frame(maxWidth: .infinity, alignment: .leading) .background(tapped ? Color(uiColor: .secondarySystemFill) : theme.colors.background) @@ -107,6 +139,36 @@ struct NtfModeSelector: View { } } +struct NotificationsInfoView: View { + var body: some View { + VStack(alignment: .leading) { + Text("Notification modes") + .font(.largeTitle) + .bold() + .padding(.vertical) + ScrollView { + VStack(alignment: .leading) { + Group { + ForEach(NotificationsMode.values) { mode in + VStack(alignment: .leading, spacing: 4) { + Text(mode.label) + .font(.headline) + Text(ntfModeDescription(mode)) + .lineLimit(10) + .font(.callout) + } + } + } + .padding(.bottom) + } + } + } + .padding() + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) + .modifier(ThemedBackground()) + } +} + struct NotificationsModeView_Previews: PreviewProvider { static var previews: some View { SetNotificationsMode() diff --git a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift index 2d90fb2fb2..b6d4c59279 100644 --- a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift +++ b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift @@ -19,21 +19,26 @@ struct SimpleXInfo: View { var body: some View { GeometryReader { g in ScrollView { - VStack(alignment: .leading, spacing: 20) { - Image(colorScheme == .light ? "logo" : "logo-light") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: g.size.width * 0.67) - .padding(.bottom, 8) - .frame(maxWidth: .infinity, minHeight: 48, alignment: .top) + VStack(alignment: .leading) { + VStack(alignment: .center, spacing: 10) { + Image(colorScheme == .light ? "logo" : "logo-light") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: g.size.width * 0.67) + .padding(.bottom, 8) + .frame(maxWidth: .infinity, minHeight: 48, alignment: .top) + + Button { + showHowItWorks = true + } label: { + Label("The future of messaging", systemImage: "info.circle") + .font(.headline) + } + } + + Spacer() VStack(alignment: .leading) { - Text("The next generation of private messaging") - .font(.title2) - .padding(.bottom, 30) - .padding(.horizontal, 40) - .frame(maxWidth: .infinity) - .multilineTextAlignment(.center) infoRow("privacy", "Privacy redefined", "The 1st platform without any user identifiers – private by design.", width: 48) infoRow("shield", "Immune to spam and abuse", @@ -45,25 +50,19 @@ struct SimpleXInfo: View { Spacer() if onboarding { - createFirstProfileButton() + VStack(spacing: 10) { + createFirstProfileButton() - Button { - m.migrationState = .pasteOrScanLink - } label: { - Label("Migrate from another device", systemImage: "tray.and.arrow.down") - .font(.subheadline) + Button { + m.migrationState = .pasteOrScanLink + } label: { + Label("Migrate from another device", systemImage: "tray.and.arrow.down") + .font(.system(size: 17, weight: .semibold)) + .frame(minHeight: 40) + } + .frame(maxWidth: .infinity) } - .frame(maxWidth: .infinity) } - - Button { - showHowItWorks = true - } label: { - Label("How it works", systemImage: "info.circle") - .font(.subheadline) - } - .frame(maxWidth: .infinity) - .padding(.bottom) } .frame(minHeight: g.size.height) } @@ -89,7 +88,9 @@ struct SimpleXInfo: View { } } .frame(maxHeight: .infinity) - .padding() + .padding(.horizontal, 25) + .padding(.top, 75) + .padding(.bottom, 25) } private func infoRow(_ image: String, _ title: LocalizedStringKey, _ text: LocalizedStringKey, width: CGFloat) -> some View { @@ -104,7 +105,7 @@ struct SimpleXInfo: View { .padding(.trailing, 10) VStack(alignment: .leading, spacing: 4) { Text(title).font(.headline) - Text(text).frame(minHeight: 40, alignment: .top) + Text(text).frame(minHeight: 40, alignment: .top).font(.callout) } } .padding(.bottom, 20) @@ -121,7 +122,7 @@ struct SimpleXInfo: View { .buttonStyle(OnboardingButtonStyle(isDisabled: false)) NavigationLink(isActive: $createProfileNavLinkActive) { - createProfileDestinationView() + CreateFirstProfile() } label: { EmptyView() } @@ -129,13 +130,6 @@ struct SimpleXInfo: View { .hidden() } } - - private func createProfileDestinationView() -> some View { - CreateFirstProfile() - .navigationTitle("Create your profile") - .navigationBarTitleDisplayMode(.large) - .modifier(ThemedBackground()) - } } struct SimpleXInfo_Previews: PreviewProvider { diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift index 8b6421b502..16aa98bc5f 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift @@ -269,6 +269,7 @@ struct UsageConditionsView: View { } .padding(.bottom) .padding(.bottom) + case let .accepted(operators): Text("Conditions are accepted for the operator(s): **\(operators.map { $0.legalName_ }.joined(separator: ", "))**.") @@ -277,7 +278,7 @@ struct UsageConditionsView: View { .padding(.bottom) } } - .padding(.horizontal) + .padding(.horizontal, 25) .frame(maxHeight: .infinity) } diff --git a/apps/ios/Shared/Views/UserSettings/NotificationsView.swift b/apps/ios/Shared/Views/UserSettings/NotificationsView.swift index b9c92c9919..ee43a24557 100644 --- a/apps/ios/Shared/Views/UserSettings/NotificationsView.swift +++ b/apps/ios/Shared/Views/UserSettings/NotificationsView.swift @@ -243,6 +243,14 @@ func ntfModeDescription(_ mode: NotificationsMode) -> LocalizedStringKey { } } +func ntfModeShortDescription(_ mode: NotificationsMode) -> LocalizedStringKey { + switch mode { + case .off: return "Check messages when allowed." + case .periodic: return "Check messages every 20 min." + case .instant: return "E2E encrypted notifications." + } +} + struct SelectionListView: View { @EnvironmentObject var theme: AppTheme var list: [Item] diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 51aa9108a1..f8cc2ac8b7 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -2158,9 +2158,17 @@ public enum NotificationsMode: String, Decodable, SelectableItem { public var label: LocalizedStringKey { switch self { - case .off: "Local" - case .periodic: "Periodically" - case .instant: "Instantly" + case .off: "No push server" + case .periodic: "Periodic" + case .instant: "Instant" + } + } + + public var icon: String { + switch self { + case .off: return "arrow.clockwise" + case .periodic: return "timer" + case .instant: return "bolt" } } From 25893177d01ec84cef8331a5a3f970c480edc6d9 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 26 Nov 2024 20:00:39 +0700 Subject: [PATCH 120/567] ios: view conditions as markdown (#5248) * ios: view conditions as markdown * changes * removed Down * refactor * unused * react on theme change --- .../NetworkAndServers/ConditionsWebView.swift | 83 +++++++++++++++++++ .../NetworkAndServers/OperatorView.swift | 45 +++++++--- apps/ios/SimpleX.xcodeproj/project.pbxproj | 21 +++++ .../xcshareddata/swiftpm/Package.resolved | 11 ++- apps/ios/SimpleXChat/Theme/Color.swift | 17 ++++ 5 files changed, 164 insertions(+), 13 deletions(-) create mode 100644 apps/ios/Shared/Views/UserSettings/NetworkAndServers/ConditionsWebView.swift diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ConditionsWebView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ConditionsWebView.swift new file mode 100644 index 0000000000..1e38b7d5ec --- /dev/null +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ConditionsWebView.swift @@ -0,0 +1,83 @@ +// +// ConditionsWebView.swift +// SimpleX (iOS) +// +// Created by Stanislav Dmitrenko on 26.11.2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import WebKit + +struct ConditionsWebView: UIViewRepresentable { + @State var html: String + @EnvironmentObject var theme: AppTheme + @State var pageLoaded = false + + func makeUIView(context: Context) -> WKWebView { + let view = WKWebView() + view.backgroundColor = .clear + view.isOpaque = false + view.navigationDelegate = context.coordinator + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + // just to make sure that even if updateUIView will not be called for any reason, the page + // will be rendered anyway + if !pageLoaded { + loadPage(view) + } + } + return view + } + + func updateUIView(_ view: WKWebView, context: Context) { + loadPage(view) + } + + private func loadPage(_ webView: WKWebView) { + let styles = """ + + """ + let head = "\(styles)" + webView.loadHTMLString(head + html, baseURL: nil) + DispatchQueue.main.async { + pageLoaded = true + } + } + + func makeCoordinator() -> Cordinator { + Cordinator() + } + + class Cordinator: NSObject, WKNavigationDelegate { + func webView(_ webView: WKWebView, + decidePolicyFor navigationAction: WKNavigationAction, + decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + + guard let url = navigationAction.request.url else { return decisionHandler(.allow) } + + switch navigationAction.navigationType { + case .linkActivated: + decisionHandler(.cancel) + if url.absoluteString.starts(with: "https://simplex.chat/contact#") { + ChatModel.shared.appOpenUrl = url + } else { + UIApplication.shared.open(url) + } + default: + decisionHandler(.allow) + } + } + } +} diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift index 83152a001f..c544d8724c 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift @@ -8,6 +8,7 @@ import SwiftUI import SimpleXChat +import Ink struct OperatorView: View { @Environment(\.dismiss) var dismiss: DismissAction @@ -342,6 +343,7 @@ struct OperatorInfoView: View { struct ConditionsTextView: View { @State private var conditionsData: (UsageConditions, String?, UsageConditions?)? @State private var failedToLoad: Bool = false + @State private var conditionsHTML: String? = nil let defaultConditionsLink = "https://github.com/simplex-chat/simplex-chat/blob/stable/PRIVACY.md" @@ -350,7 +352,18 @@ struct ConditionsTextView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) .task { do { - conditionsData = try await getUsageConditions() + let conditions = try await getUsageConditions() + let conditionsText = conditions.1 + let parentLink = "https://github.com/simplex-chat/simplex-chat/blob/\(conditions.0.conditionsCommit)" + let preparedText: String? + if let conditionsText { + let prepared = prepareMarkdown(conditionsText.trimmingCharacters(in: .whitespacesAndNewlines), parentLink) + conditionsHTML = MarkdownParser().html(from: prepared) + preparedText = prepared + } else { + preparedText = nil + } + conditionsData = (conditions.0, preparedText, conditions.2) } catch let error { logger.error("ConditionsTextView getUsageConditions error: \(responseError(error))") failedToLoad = true @@ -358,18 +371,16 @@ struct ConditionsTextView: View { } } - // TODO Markdown & diff rendering + // TODO Diff rendering @ViewBuilder private func viewBody() -> some View { - if let (usageConditions, conditionsText, acceptedConditions) = conditionsData { - if let conditionsText = conditionsText { - ScrollView { - Text(conditionsText.trimmingCharacters(in: .whitespacesAndNewlines)) - .padding() - } - .background( - RoundedRectangle(cornerRadius: 12, style: .continuous) - .fill(Color(uiColor: .secondarySystemGroupedBackground)) - ) + if let (usageConditions, _, _) = conditionsData { + if let conditionsHTML { + ConditionsWebView(html: conditionsHTML) + .padding(6) + .background( + RoundedRectangle(cornerRadius: 12, style: .continuous) + .fill(Color(uiColor: .secondarySystemGroupedBackground)) + ) } else { let conditionsLink = "https://github.com/simplex-chat/simplex-chat/blob/\(usageConditions.conditionsCommit)/PRIVACY.md" conditionsLinkView(conditionsLink) @@ -391,6 +402,16 @@ struct ConditionsTextView: View { } } } + + private func prepareMarkdown(_ text: String, _ parentLink: String) -> String { + let localLinkRegex = try! NSRegularExpression(pattern: "\\[([^\\(]*)\\]\\(#.*\\)") + let h1Regex = try! NSRegularExpression(pattern: "^# ") + var text = localLinkRegex.stringByReplacingMatches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count), withTemplate: "$1") + text = h1Regex.stringByReplacingMatches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count), withTemplate: "") + return text + .replacingOccurrences(of: "](/", with: "](\(parentLink)/") + .replacingOccurrences(of: "](./", with: "](\(parentLink)/") + } } struct SingleOperatorUsageConditionsView: View { diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 4f03ced132..0ffe9d1f40 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -199,6 +199,8 @@ 8C8118722C220B5B00E6FC94 /* Yams in Frameworks */ = {isa = PBXBuildFile; productRef = 8C8118712C220B5B00E6FC94 /* Yams */; }; 8C81482C2BD91CD4002CBEC3 /* AudioDevicePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C81482B2BD91CD4002CBEC3 /* AudioDevicePicker.swift */; }; 8C9BC2652C240D5200875A27 /* ThemeModeEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C9BC2642C240D5100875A27 /* ThemeModeEditor.swift */; }; + 8CB3476C2CF5CFFA006787A5 /* Ink in Frameworks */ = {isa = PBXBuildFile; productRef = 8CB3476B2CF5CFFA006787A5 /* Ink */; }; + 8CB3476E2CF5F58B006787A5 /* ConditionsWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CB3476D2CF5F58B006787A5 /* ConditionsWebView.swift */; }; 8CC4ED902BD7B8530078AEE8 /* CallAudioDeviceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC4ED8F2BD7B8530078AEE8 /* CallAudioDeviceManager.swift */; }; 8CC956EE2BC0041000412A11 /* NetworkObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC956ED2BC0041000412A11 /* NetworkObserver.swift */; }; 8CE848A32C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CE848A22C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift */; }; @@ -547,6 +549,7 @@ 8C852B072C1086D100BA61E8 /* Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = ""; }; 8C86EBE42C0DAE4F00E12243 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = ""; }; 8C9BC2642C240D5100875A27 /* ThemeModeEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeModeEditor.swift; sourceTree = ""; }; + 8CB3476D2CF5F58B006787A5 /* ConditionsWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionsWebView.swift; sourceTree = ""; }; 8CC4ED8F2BD7B8530078AEE8 /* CallAudioDeviceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallAudioDeviceManager.swift; sourceTree = ""; }; 8CC956ED2BC0041000412A11 /* NetworkObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkObserver.swift; sourceTree = ""; }; 8CE848A22C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableChatItemToolbars.swift; sourceTree = ""; }; @@ -636,6 +639,7 @@ files = ( 5CE2BA702845308900EC33A6 /* SimpleXChat.framework in Frameworks */, 8C8118722C220B5B00E6FC94 /* Yams in Frameworks */, + 8CB3476C2CF5CFFA006787A5 /* Ink in Frameworks */, D741547829AF89AF0022400A /* StoreKit.framework in Frameworks */, D7197A1829AE89660055C05A /* WebRTC in Frameworks */, D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */, @@ -1072,6 +1076,7 @@ 643B3B4D2CCFD6400083A2CF /* OperatorView.swift */, 5C9C2DA6289957AE00CC63B1 /* AdvancedNetworkSettings.swift */, 5C9C2DA82899DA6F00CC63B1 /* NetworkAndServers.swift */, + 8CB3476D2CF5F58B006787A5 /* ConditionsWebView.swift */, ); path = NetworkAndServers; sourceTree = ""; @@ -1183,6 +1188,7 @@ D7F0E33829964E7E0068AF69 /* LZString */, D7197A1729AE89660055C05A /* WebRTC */, 8C8118712C220B5B00E6FC94 /* Yams */, + 8CB3476B2CF5CFFA006787A5 /* Ink */, ); productName = "SimpleX (iOS)"; productReference = 5CA059CA279559F40002BEB4 /* SimpleX.app */; @@ -1326,6 +1332,7 @@ D7F0E33729964E7D0068AF69 /* XCRemoteSwiftPackageReference "lzstring-swift" */, D7197A1629AE89660055C05A /* XCRemoteSwiftPackageReference "WebRTC" */, 8C73C1162C21E17B00892670 /* XCRemoteSwiftPackageReference "Yams" */, + 8CB3476A2CF5CFFA006787A5 /* XCRemoteSwiftPackageReference "ink" */, ); productRefGroup = 5CA059CB279559F40002BEB4 /* Products */; projectDirPath = ""; @@ -1516,6 +1523,7 @@ 5C764E89279CBCB3000C6508 /* ChatModel.swift in Sources */, 5C971E1D27AEBEF600C8A3CE /* ChatInfoView.swift in Sources */, 5CBD285C29575B8E00EC2CF4 /* WhatsNewView.swift in Sources */, + 8CB3476E2CF5F58B006787A5 /* ConditionsWebView.swift in Sources */, 5CC1C99527A6CF7F000D9FF6 /* ShareSheet.swift in Sources */, 5C5E5D3B2824468B00B0488A /* ActiveCallView.swift in Sources */, 5C2E260727A2941F00F70299 /* SimpleXAPI.swift in Sources */, @@ -2375,6 +2383,14 @@ version = 5.1.2; }; }; + 8CB3476A2CF5CFFA006787A5 /* XCRemoteSwiftPackageReference "ink" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/johnsundell/ink"; + requirement = { + kind = exactVersion; + version = 0.6.0; + }; + }; D7197A1629AE89660055C05A /* XCRemoteSwiftPackageReference "WebRTC" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/simplex-chat/WebRTC.git"; @@ -2412,6 +2428,11 @@ package = 8C73C1162C21E17B00892670 /* XCRemoteSwiftPackageReference "Yams" */; productName = Yams; }; + 8CB3476B2CF5CFFA006787A5 /* Ink */ = { + isa = XCSwiftPackageProductDependency; + package = 8CB3476A2CF5CFFA006787A5 /* XCRemoteSwiftPackageReference "ink" */; + productName = Ink; + }; CE38A29B2C3FCD72005ED185 /* SwiftyGif */ = { isa = XCSwiftPackageProductDependency; package = D77B92DA2952372200A5A1CC /* XCRemoteSwiftPackageReference "SwiftyGif" */; diff --git a/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c8623a95cb..7fdbff38af 100644 --- a/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "e2611d1e91fd8071abc106776ba14ee2e395d2ad08a78e073381294abc10f115", + "originHash" : "33afc44be5f4225325b3cb940ed71b6cbf3ef97290d348d7b6803697bcd0637d", "pins" : [ { "identity" : "codescanner", @@ -10,6 +10,15 @@ "version" : "2.5.0" } }, + { + "identity" : "ink", + "kind" : "remoteSourceControl", + "location" : "https://github.com/johnsundell/ink", + "state" : { + "revision" : "bcc9f219900a62c4210e6db726035d7f03ae757b", + "version" : "0.6.0" + } + }, { "identity" : "lzstring-swift", "kind" : "remoteSourceControl", diff --git a/apps/ios/SimpleXChat/Theme/Color.swift b/apps/ios/SimpleXChat/Theme/Color.swift index 3e8fe1b6e7..f307eaa5aa 100644 --- a/apps/ios/SimpleXChat/Theme/Color.swift +++ b/apps/ios/SimpleXChat/Theme/Color.swift @@ -63,6 +63,23 @@ extension Color { ) } + public func toHTMLHex() -> String { + let uiColor: UIColor = .init(self) + var (r, g, b, a): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0) + uiColor.getRed(&r, green: &g, blue: &b, alpha: &a) + // Can be negative values and more than 1. Extended color range, making it normal + r = min(1, max(0, r)) + g = min(1, max(0, g)) + b = min(1, max(0, b)) + a = min(1, max(0, a)) + return String(format: "#%02x%02x%02x%02x", + Int((r * 255).rounded()), + Int((g * 255).rounded()), + Int((b * 255).rounded()), + Int((a * 255).rounded()) + ) + } + public func darker(_ factor: CGFloat = 0.1) -> Color { var (r, g, b, a): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0) UIColor(self).getRed(&r, green: &g, blue: &b, alpha: &a) From 8c1abcccfb67c4c12c1a416dd3f0d9645ab054ac Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 26 Nov 2024 21:22:24 +0700 Subject: [PATCH 121/567] android, desktop: scroll to quoted item without known id (#5254) --- .../common/views/chat/ChatItemsLoader.kt | 10 ++++++++ .../simplex/common/views/chat/ChatView.kt | 25 ++++++++++++++++++- .../common/views/chat/item/ChatItemView.kt | 5 +++- .../common/views/chat/item/FramedItemView.kt | 8 +++--- 4 files changed, 42 insertions(+), 6 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt index 22fba59004..be09c04ec1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt @@ -10,6 +10,16 @@ import kotlin.math.min const val TRIM_KEEP_COUNT = 200 +suspend fun apiLoadSingleMessage( + rhId: Long?, + chatType: ChatType, + apiId: Long, + itemId: Long +): ChatItem? = coroutineScope { + val (chat, _) = chatModel.controller.apiGetChat(rhId, chatType, apiId, ChatPagination.Around(itemId, 0), "") ?: return@coroutineScope null + chat.chatItems.firstOrNull() +} + suspend fun apiLoadMessages( rhId: Long?, chatType: ChatType, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 79f97d9f6b..875868103e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -979,10 +979,12 @@ fun BoxScope.ChatItemsList( } } + val remoteHostIdUpdated = rememberUpdatedState(remoteHostId) val chatInfoUpdated = rememberUpdatedState(chatInfo) val highlightedItems = remember { mutableStateOf(setOf()) } val scope = rememberCoroutineScope() val scrollToItem: (Long) -> Unit = remember { scrollToItem(loadingMoreItems, highlightedItems, chatInfoUpdated, maxHeight, scope, reversedChatItems, mergedItems, listState, loadMessages) } + val scrollToQuotedItemFromItem: (Long) -> Unit = remember { findQuotedItemFromItem(remoteHostIdUpdated, chatInfoUpdated, scope, scrollToItem) } LoadLastItems(loadingMoreItems, remoteHostId, chatInfo) SmallScrollOnNewMessage(listState, chatModel.chatItems) @@ -1042,7 +1044,7 @@ fun BoxScope.ChatItemsList( highlightedItems.value = setOf() } } - ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, highlighted = highlighted, range = range, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, reveal = reveal, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp) + ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, highlighted = highlighted, range = range, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, scrollToQuotedItemFromItem = scrollToQuotedItemFromItem, setReaction = setReaction, showItemDetails = showItemDetails, reveal = reveal, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp) } } @@ -1867,6 +1869,27 @@ private fun scrollToItem( } } +private fun findQuotedItemFromItem( + rhId: State, + chatInfo: State, + scope: CoroutineScope, + scrollToItem: (Long) -> Unit +): (Long) -> Unit = { itemId: Long -> + scope.launch(Dispatchers.Default) { + val item = apiLoadSingleMessage(rhId.value, chatInfo.value.chatType, chatInfo.value.apiId, itemId) + if (item != null) { + withChats { + updateChatItem(chatInfo.value, item) + } + if (item.quotedItem?.itemId != null) { + scrollToItem(item.quotedItem.itemId) + } else { + showQuotedItemDoesNotExistAlert() + } + } + } +} + val chatViewScrollState = MutableStateFlow(false) fun addGroupMembers(groupInfo: GroupInfo, rhId: Long?, view: Any? = null, close: (() -> Unit)? = null) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index f0c85736af..bd79b78c45 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -71,6 +71,7 @@ fun ChatItemView( joinGroup: (Long, () -> Unit) -> Unit, acceptCall: (Contact) -> Unit, scrollToItem: (Long) -> Unit, + scrollToQuotedItemFromItem: (Long) -> Unit, acceptFeature: (Contact, ChatFeature, Int?) -> Unit, openDirectChat: (Long) -> Unit, forwardItem: (ChatInfo, ChatItem) -> Unit, @@ -155,7 +156,7 @@ fun ChatItemView( ) { @Composable fun framedItemView() { - FramedItemView(cInfo, cItem, uriHandler, imageProvider, linkMode = linkMode, showViaProxy = showViaProxy, showMenu, showTimestamp = showTimestamp, tailVisible = itemSeparation.largeGap, receiveFile, onLinkLongClick, scrollToItem) + FramedItemView(cInfo, cItem, uriHandler, imageProvider, linkMode = linkMode, showViaProxy = showViaProxy, showMenu, showTimestamp = showTimestamp, tailVisible = itemSeparation.largeGap, receiveFile, onLinkLongClick, scrollToItem, scrollToQuotedItemFromItem) } fun deleteMessageQuestionText(): String { @@ -1087,6 +1088,7 @@ fun PreviewChatItemView( joinGroup = { _, _ -> }, acceptCall = { _ -> }, scrollToItem = {}, + scrollToQuotedItemFromItem = {}, acceptFeature = { _, _, _ -> }, openDirectChat = { _ -> }, forwardItem = { _, _ -> }, @@ -1130,6 +1132,7 @@ fun PreviewChatItemViewDeletedContent() { joinGroup = { _, _ -> }, acceptCall = { _ -> }, scrollToItem = {}, + scrollToQuotedItemFromItem = {}, acceptFeature = { _, _, _ -> }, openDirectChat = { _ -> }, forwardItem = { _, _ -> }, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt index cfaa3eced5..c2df11a8ea 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt @@ -39,6 +39,7 @@ fun FramedItemView( receiveFile: (Long) -> Unit, onLinkLongClick: (link: String) -> Unit = {}, scrollToItem: (Long) -> Unit = {}, + scrollToQuotedItemFromItem: (Long) -> Unit = {}, ) { val sent = ci.chatDir.sent val chatTTL = chatInfo.timedMessagesTTL @@ -130,11 +131,10 @@ fun FramedItemView( .combinedClickable( onLongClick = { showMenu.value = true }, onClick = { - val itemId = qi.itemId - if (itemId != null) { - scrollToItem(itemId) + if (qi.itemId != null) { + scrollToItem(qi.itemId) } else { - showQuotedItemDoesNotExistAlert() + scrollToQuotedItemFromItem(ci.id) } } ) From 15fae29e5b9f8a32a513e5a7c026001f844b3236 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 27 Nov 2024 11:16:22 +0400 Subject: [PATCH 122/567] android, desktop: offer to create 1-time link on address views (#5253) --- .../common/views/newchat/NewChatSheet.kt | 2 +- .../common/views/newchat/NewChatView.kt | 2 +- .../usersettings/UserAddressLearnMore.kt | 62 ++++++- .../views/usersettings/UserAddressView.kt | 153 +++++++++++++----- .../commonMain/resources/MR/base/strings.xml | 14 ++ 5 files changed, 187 insertions(+), 46 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt index 02996381f8..72118224e6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt @@ -174,7 +174,7 @@ private fun ModalData.NewChatSheetLayout( val actionButtonsOriginal = listOf( Triple( painterResource(MR.images.ic_add_link), - stringResource(MR.strings.add_contact_tab), + stringResource(MR.strings.create_1_time_link), addContact, ), Triple( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index 61403e07a4..e08d46d880 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -98,7 +98,7 @@ fun ModalData.NewChatView(rh: RemoteHostInfo?, selection: NewChatOption, showQRC val tabTitles = NewChatOption.values().map { when(it) { NewChatOption.INVITE -> - stringResource(MR.strings.add_contact_tab) + stringResource(MR.strings.one_time_link_short) NewChatOption.CONNECT -> stringResource(MR.strings.connect_via_link) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressLearnMore.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressLearnMore.kt index efc161ac65..ea0cd4fe28 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressLearnMore.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressLearnMore.kt @@ -3,29 +3,57 @@ package chat.simplex.common.views.usersettings import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.* -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.common.platform.chatModel import chat.simplex.common.ui.theme.DEFAULT_PADDING +import chat.simplex.common.ui.theme.DEFAULT_PADDING_HALF import dev.icerock.moko.resources.compose.stringResource import chat.simplex.common.views.helpers.* -import chat.simplex.common.views.onboarding.ReadableText -import chat.simplex.common.views.onboarding.ReadableTextWithLink +import chat.simplex.common.views.newchat.* +import chat.simplex.common.views.onboarding.* import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource @Composable fun UserAddressLearnMore(showCreateAddressButton: Boolean = false) { ColumnWithScrollBar(Modifier .padding(horizontal = DEFAULT_PADDING)) { - AppBarTitle(stringResource(MR.strings.simplex_address), withPadding = false) - ReadableText(MR.strings.you_can_share_your_address) + AppBarTitle(stringResource(MR.strings.address_or_1_time_link), withPadding = false) + + Row { + Icon(painterResource(MR.images.ic_mail), null, tint = MaterialTheme.colors.secondary) + Spacer(Modifier.width(DEFAULT_PADDING_HALF)) + ReadableText(MR.strings.share_address_publicly, style = MaterialTheme.typography.h3.copy(fontWeight = FontWeight.Bold)) + } + ReadableText(MR.strings.share_simplex_address_on_social_media) ReadableText(MR.strings.you_wont_lose_your_contacts_if_delete_address) - ReadableText(MR.strings.you_can_accept_or_reject_connection) - ReadableTextWithLink(MR.strings.read_more_in_user_guide_with_link, "https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address") + + Row(Modifier.padding(top = DEFAULT_PADDING_HALF)) { + Icon(painterResource(MR.images.ic_add_link), null, tint = MaterialTheme.colors.secondary) + Spacer(Modifier.width(DEFAULT_PADDING_HALF)) + ReadableText(MR.strings.share_1_time_link_with_a_friend, style = MaterialTheme.typography.h3.copy(fontWeight = FontWeight.Bold)) + } + ReadableText(MR.strings.one_time_link_can_be_used_with_one_contact_only) + ReadableText(MR.strings.you_can_set_connection_name_to_remember) + + if (!showCreateAddressButton) { + Row(Modifier.padding(top = DEFAULT_PADDING_HALF)) { + Icon(painterResource(MR.images.ic_shield), null, tint = MaterialTheme.colors.secondary) + Spacer(Modifier.width(DEFAULT_PADDING_HALF)) + ReadableText(MR.strings.connection_security, style = MaterialTheme.typography.h3.copy(fontWeight = FontWeight.Bold)) + } + ReadableText(MR.strings.simplex_address_and_1_time_links_are_safe_to_share) + ReadableText(MR.strings.to_protect_against_your_link_replaced_compare_codes) + ReadableTextWithLink(MR.strings.read_more_in_user_guide_with_link, "https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses") + } if (showCreateAddressButton) { Spacer(Modifier.weight(1f)) @@ -33,7 +61,7 @@ fun UserAddressLearnMore(showCreateAddressButton: Boolean = false) { Button( onClick = { ModalManager.start.showModalCloseable { close -> - UserAddressView(chatModel = chatModel, shareViaProfile = false, autoCreateAddress = true, close = close) + UserAddressView(chatModel = chatModel, shareViaProfile = false, autoCreateAddress = true, close = { ModalManager.start.closeModals() }) } }, shape = CircleShape, @@ -42,6 +70,24 @@ fun UserAddressLearnMore(showCreateAddressButton: Boolean = false) { ) { Text(stringResource(MR.strings.create_simplex_address), style = MaterialTheme.typography.h2, color = Color.White, fontSize = 18.sp, fontWeight = FontWeight.Medium) } + + val closeAll = { ModalManager.start.closeModals() } + TextButton( + onClick = { + ModalManager.start.showModalCloseable(endButtons = { AddContactLearnMoreButton() }) { _ -> + NewChatView(chatModel.currentRemoteHost.value, NewChatOption.INVITE, close = closeAll) + } + }, + Modifier.padding(top = DEFAULT_PADDING, bottom = DEFAULT_PADDING * 2).clip(CircleShape) + ) { + Text( + stringResource(MR.strings.create_1_time_link), + Modifier.padding(start = DEFAULT_PADDING_HALF, end = DEFAULT_PADDING_HALF, bottom = 5.dp), + color = MaterialTheme.colors.primary, + fontWeight = FontWeight.Medium, + textAlign = TextAlign.Center + ) + } } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt index aa9ba70b02..836faee49b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt @@ -31,7 +31,6 @@ import chat.simplex.res.MR @Composable fun UserAddressView( chatModel: ChatModel, - viaCreateLinkView: Boolean = false, shareViaProfile: Boolean = false, autoCreateAddress: Boolean = false, close: () -> Unit @@ -39,7 +38,6 @@ fun UserAddressView( // TODO close when remote host changes val shareViaProfile = remember { mutableStateOf(shareViaProfile) } var progressIndicator by remember { mutableStateOf(false) } - val onCloseHandler: MutableState<(close: () -> Unit) -> Unit> = remember { mutableStateOf({ _ -> }) } val user = remember { chatModel.currentUser } KeyChangeEffect(user.value?.remoteHostId, user.value?.userId) { close() @@ -82,7 +80,7 @@ fun UserAddressView( } LaunchedEffect(autoCreateAddress) { - if (autoCreateAddress) { + if (chatModel.userAddress.value == null && autoCreateAddress) { createAddress() } } @@ -94,7 +92,6 @@ fun UserAddressView( user = user.value, userAddress = userAddress.value, shareViaProfile, - onCloseHandler, createAddress = { createAddress() }, learnMore = { ModalManager.start.showModal { @@ -105,7 +102,7 @@ fun UserAddressView( sendEmail = { userAddress -> uriHandler.sendEmail( generalGetString(MR.strings.email_invite_subject), - generalGetString(MR.strings.email_invite_body).format(simplexChatLink( userAddress.connReqContact)) + generalGetString(MR.strings.email_invite_body).format(simplexChatLink(userAddress.connReqContact)) ) }, setProfileAddress = ::setProfileAddress, @@ -141,12 +138,8 @@ fun UserAddressView( ) } - if (viaCreateLinkView) { + ModalView(close = close) { showLayout() - } else { - ModalView(close = { onCloseHandler.value(close) }) { - showLayout() - } } if (progressIndicator) { @@ -173,7 +166,6 @@ private fun UserAddressLayout( user: User?, userAddress: UserContactLinkRec?, shareViaProfile: MutableState, - onCloseHandler: MutableState<(close: () -> Unit) -> Unit>, createAddress: () -> Unit, learnMore: () -> Unit, share: (String) -> Unit, @@ -190,45 +182,41 @@ private fun UserAddressLayout( verticalArrangement = Arrangement.SpaceEvenly ) { if (userAddress == null) { - SectionView { + SectionView(generalGetString(MR.strings.for_social_media).uppercase()) { CreateAddressButton(createAddress) - SectionTextFooter(stringResource(MR.strings.create_address_and_let_people_connect)) } + + SectionDividerSpaced() + SectionView(generalGetString(MR.strings.or_to_share_privately).uppercase()) { + CreateOneTimeLinkButton() + } + SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false) SectionView { LearnMoreButton(learnMore) } - LaunchedEffect(Unit) { - onCloseHandler.value = { close -> close() } - } } else { - val autoAcceptState = remember { mutableStateOf(AutoAcceptState(userAddress)) } - val autoAcceptStateSaved = remember { mutableStateOf(autoAcceptState.value) } - SectionView(stringResource(MR.strings.address_section_title).uppercase()) { + SectionView(stringResource(MR.strings.for_social_media).uppercase()) { SimpleXLinkQRCode(userAddress.connReqContact) ShareAddressButton { share(simplexChatLink(userAddress.connReqContact)) } - ShareViaEmailButton { sendEmail(userAddress) } - ShareWithContactsButton(shareViaProfile, setProfileAddress) - AutoAcceptToggle(autoAcceptState) { saveAas(autoAcceptState.value, autoAcceptStateSaved) } - LearnMoreButton(learnMore) + // ShareViaEmailButton { sendEmail(userAddress) } + AddressSettingsButton(user, userAddress, shareViaProfile, setProfileAddress, saveAas) } - if (autoAcceptState.value.enable) { - SectionDividerSpaced() - AutoAcceptSection(autoAcceptState, autoAcceptStateSaved, saveAas) + + SectionDividerSpaced() + SectionView(generalGetString(MR.strings.or_to_share_privately).uppercase()) { + CreateOneTimeLinkButton() + } + SectionDividerSpaced(maxBottomPadding = false) + SectionView { + LearnMoreButton(learnMore) } SectionDividerSpaced(maxBottomPadding = false) - SectionView { DeleteAddressButton(deleteAddress) SectionTextFooter(stringResource(MR.strings.your_contacts_will_remain_connected)) } - LaunchedEffect(Unit) { - onCloseHandler.value = { close -> - if (autoAcceptState.value == autoAcceptStateSaved.value) close() - else showUnsavedChangesAlert({ saveAas(autoAcceptState.value, autoAcceptStateSaved); close() }, close) - } - } } } SectionBottomSpacer() @@ -246,11 +234,27 @@ private fun CreateAddressButton(onClick: () -> Unit) { ) } +@Composable +private fun CreateOneTimeLinkButton() { + val closeAll = { ModalManager.start.closeModals() } + SettingsActionItem( + painterResource(MR.images.ic_add_link), + stringResource(MR.strings.create_1_time_link), + click = { + ModalManager.start.showModalCloseable(endButtons = { AddContactLearnMoreButton() }) { _ -> + NewChatView(chatModel.currentRemoteHost.value, NewChatOption.INVITE, close = closeAll) + } + }, + iconColor = MaterialTheme.colors.primary, + textColor = MaterialTheme.colors.primary, + ) +} + @Composable private fun LearnMoreButton(onClick: () -> Unit) { SettingsActionItem( painterResource(MR.images.ic_info), - stringResource(MR.strings.learn_more_about_address), + stringResource(MR.strings.simplex_address_or_1_time_link), onClick, ) } @@ -266,6 +270,85 @@ fun ShareViaEmailButton(onClick: () -> Unit) { ) } +@Composable +private fun AddressSettingsButton( + user: User?, + userAddress: UserContactLinkRec, + shareViaProfile: MutableState, + setProfileAddress: (Boolean) -> Unit, + saveAas: (AutoAcceptState, MutableState) -> Unit, +) { + SettingsActionItem( + painterResource(MR.images.ic_settings), + stringResource(MR.strings.address_settings), + click = { + ModalManager.start.showCustomModal { close -> + UserAddressSettings(user, userAddress, shareViaProfile, setProfileAddress, saveAas, close = close) + } + } + ) +} + +@Composable +private fun ModalData.UserAddressSettings( + user: User?, + userAddress: UserContactLinkRec, + shareViaProfile: MutableState, + setProfileAddress: (Boolean) -> Unit, + saveAas: (AutoAcceptState, MutableState) -> Unit, + close: () -> Unit +) { + val autoAcceptState = remember { stateGetOrPut("autoAcceptState") { (AutoAcceptState(userAddress)) } } + val autoAcceptStateSaved = remember { stateGetOrPut("autoAcceptStateSaved") { (autoAcceptState.value) } } + + fun onClose(close: () -> Unit): Boolean = if (autoAcceptState.value == autoAcceptStateSaved.value) { + chatModel.centerPanelBackgroundClickHandler = null + close() + false + } else { + showUnsavedChangesAlert( + save = { + saveAas(autoAcceptState.value, autoAcceptStateSaved) + chatModel.centerPanelBackgroundClickHandler = null + close() + }, + revert = { + chatModel.centerPanelBackgroundClickHandler = null + close() + } + ) + true + } + + LaunchedEffect(Unit) { + // Enables unsaved changes alert on this view. + chatModel.centerPanelBackgroundClickHandler = { + onClose(close = { ModalManager.start.closeModals() }) + } + } + + ModalView(close = { onClose(close) }) { + ColumnWithScrollBar { + AppBarTitle(stringResource(MR.strings.address_settings), hostDevice(user?.remoteHostId)) + Column( + Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING_HALF), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceEvenly + ) { + SectionView { + ShareWithContactsButton(shareViaProfile, setProfileAddress) + AutoAcceptToggle(autoAcceptState) { saveAas(autoAcceptState.value, autoAcceptStateSaved) } + } + + if (autoAcceptState.value.enable) { + SectionDividerSpaced() + AutoAcceptSection(autoAcceptState, autoAcceptStateSaved, saveAas) + } + } + } + } +} + @Composable fun ShareWithContactsButton(shareViaProfile: MutableState, setProfileAddress: (Boolean) -> Unit) { PreferenceToggleWithIcon( @@ -441,7 +524,6 @@ fun PreviewUserAddressLayoutNoAddress() { setProfileAddress = { _ -> }, learnMore = {}, shareViaProfile = remember { mutableStateOf(false) }, - onCloseHandler = remember { mutableStateOf({}) }, sendEmail = {}, ) } @@ -475,7 +557,6 @@ fun PreviewUserAddressLayoutAddressCreated() { setProfileAddress = { _ -> }, learnMore = {}, shareViaProfile = remember { mutableStateOf(false) }, - onCloseHandler = remember { mutableStateOf({}) }, sendEmail = {}, ) } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 8b379d8212..5f095fbf7c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -675,10 +675,19 @@ If you can\'t meet in person, show QR code in a video call, or share the link. + Share address publicly + Share SimpleX address on social media. You can share your address as a link or QR code - anybody can connect to you. You won\'t lose your contacts if you later delete your address. + Share 1-time link with a friend + with one contact only
- share in person or via any messenger.]]> + You can set connection name, to remember who the link was shared with. + Connection security + SimpleX address and 1-time links are safe to share via any messenger. + To protect against your link being replaced, you can compare contact security codes. When people request to connect, you can accept or reject it. User Guide.]]> + Address or 1-time link? Connect via link @@ -917,6 +926,11 @@ Invite friends Let\'s talk in SimpleX Chat Hi!\nConnect to me via SimpleX Chat: %s + For social media + Or to share privately + SimpleX address or 1-time link? + Create 1-time link + Address settings Continue From 41b7ad01f9708be381547b255c226d7df8e32484 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 27 Nov 2024 15:51:35 +0400 Subject: [PATCH 123/567] core: apiGetReactionMembers (#5258) --- src/Simplex/Chat.hs | 3 +++ src/Simplex/Chat/Controller.hs | 2 ++ src/Simplex/Chat/Messages.hs | 8 ++++++++ src/Simplex/Chat/View.hs | 1 + 4 files changed, 14 insertions(+) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 5906da57de..707163fde7 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1078,6 +1078,8 @@ processChatCommand' vr = \case throwChatError (CECommandError $ "reaction already " <> if add then "added" else "removed") when (add && length rs >= maxMsgReactions) $ throwChatError (CECommandError "too many reactions") + APIGetReactionMembers _userId _groupId _itemId _reaction -> withUser $ \user -> do + pure $ chatCmdError (Just user) "not supported" APIPlanForwardChatItems (ChatRef fromCType fromChatId) itemIds -> withUser $ \user -> case fromCType of CTDirect -> planForward user . snd =<< getCommandDirectChatItems user fromChatId itemIds CTGroup -> planForward user . snd =<< getCommandGroupChatItems user fromChatId itemIds @@ -8267,6 +8269,7 @@ chatCommandP = "/_delete item " *> (APIDeleteChatItem <$> chatRefP <*> _strP <* A.space <*> ciDeleteMode), "/_delete member item #" *> (APIDeleteMemberChatItem <$> A.decimal <*> _strP), "/_reaction " *> (APIChatItemReaction <$> chatRefP <* A.space <*> A.decimal <* A.space <*> onOffP <* A.space <*> jsonP), + "/_reaction members " *> (APIGetReactionMembers <$> A.decimal <* A.space <*> A.decimal <* A.space <*> A.decimal <* A.space <*> jsonP), "/_forward plan " *> (APIPlanForwardChatItems <$> chatRefP <*> _strP), "/_forward " *> (APIForwardChatItems <$> chatRefP <* A.space <*> chatRefP <*> _strP <*> sendMessageTTLP), "/_read user " *> (APIUserRead <$> A.decimal), diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 23aa632478..b6f8d5e093 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -304,6 +304,7 @@ data ChatCommand | APIDeleteChatItem ChatRef (NonEmpty ChatItemId) CIDeleteMode | APIDeleteMemberChatItem GroupId (NonEmpty ChatItemId) | APIChatItemReaction {chatRef :: ChatRef, chatItemId :: ChatItemId, add :: Bool, reaction :: MsgReaction} + | APIGetReactionMembers UserId GroupId ChatItemId MsgReaction | APIPlanForwardChatItems {fromChatRef :: ChatRef, chatItemIds :: NonEmpty ChatItemId} | APIForwardChatItems {toChatRef :: ChatRef, fromChatRef :: ChatRef, chatItemIds :: NonEmpty ChatItemId, ttl :: Maybe Int} | APIUserRead UserId @@ -621,6 +622,7 @@ data ChatResponse | CRChatItemUpdated {user :: User, chatItem :: AChatItem} | CRChatItemNotChanged {user :: User, chatItem :: AChatItem} | CRChatItemReaction {user :: User, added :: Bool, reaction :: ACIReaction} + | CRReactionMembers {user :: User, memberReactions :: [MemberReaction]} | CRChatItemsDeleted {user :: User, chatItemDeletions :: [ChatItemDeletion], byUser :: Bool, timed :: Bool} | CRChatItemDeletedNotFound {user :: User, contact :: Contact, sharedMsgId :: SharedMsgId} | CRBroadcastSent {user :: User, msgContent :: MsgContent, successes :: Int, failures :: Int, timestamp :: UTCTime} diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index 0e3575b64c..274308176b 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -474,6 +474,12 @@ deriving instance Show ACIReaction data JSONCIReaction c d = JSONCIReaction {chatInfo :: ChatInfo c, chatReaction :: CIReaction c d} +data MemberReaction = MemberReaction + { groupMemberId :: GroupMemberId, + reactionTs :: UTCTime + } + deriving (Show) + type family ChatTypeQuotable (a :: ChatType) :: Constraint where ChatTypeQuotable 'CTDirect = () ChatTypeQuotable 'CTGroup = () @@ -1465,6 +1471,8 @@ instance ToJSON ACIReaction where toJSON (ACIReaction _ _ cInfo reaction) = J.toJSON $ JSONCIReaction cInfo reaction toEncoding (ACIReaction _ _ cInfo reaction) = J.toEncoding $ JSONCIReaction cInfo reaction +$(JQ.deriveJSON defaultJSON ''MemberReaction) + $(JQ.deriveJSON defaultJSON ''MsgMetaJSON) msgMetaJson :: MsgMeta -> Text diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index f9ec3f936c..e0c836d8d7 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -154,6 +154,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe ttyUser u $ unmuted u chat deletedItem $ viewItemDelete chat deletedItem toItem byUser timed ts tz testView deletions' -> ttyUser u [sShow (length deletions') <> " messages deleted"] CRChatItemReaction u added (ACIReaction _ _ chat reaction) -> ttyUser u $ unmutedReaction u chat reaction $ viewItemReaction showReactions chat reaction added ts tz + CRReactionMembers u memberReactions -> [] CRChatItemDeletedNotFound u Contact {localDisplayName = c} _ -> ttyUser u [ttyFrom $ c <> "> [deleted - original message not found]"] CRBroadcastSent u mc s f t -> ttyUser u $ viewSentBroadcast mc s f ts tz t CRMsgIntegrityError u mErr -> ttyUser u $ viewMsgIntegrityError mErr From 9fa968a5936d9f945b2d257f0db95746fd6397bf Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 27 Nov 2024 18:30:39 +0400 Subject: [PATCH 124/567] ui: fix marking chat read (don't use range api) (#5257) --- apps/ios/Shared/Model/SimpleXAPI.swift | 23 ++----- apps/ios/Shared/Views/Chat/ChatView.swift | 2 +- apps/ios/SimpleXChat/APITypes.swift | 4 +- .../chat/simplex/common/model/ChatModel.kt | 29 +++----- .../chat/simplex/common/model/SimpleXAPI.kt | 8 +-- .../simplex/common/views/chat/ChatView.kt | 66 +++++++++++-------- .../views/chatlist/ChatListNavLinkView.kt | 4 +- 7 files changed, 63 insertions(+), 73 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 13b11568d8..c03483311d 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1061,8 +1061,8 @@ func apiRejectContactRequest(contactReqId: Int64) async throws { throw r } -func apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)) async throws { - try await sendCommandOkResp(.apiChatRead(type: type, id: id, itemRange: itemRange)) +func apiChatRead(type: ChatType, id: Int64) async throws { + try await sendCommandOkResp(.apiChatRead(type: type, id: id)) } func apiChatItemsRead(type: ChatType, id: Int64, itemIds: [Int64]) async throws { @@ -1368,15 +1368,13 @@ func apiGetNetworkStatuses() throws -> [ConnNetworkStatus] { throw r } -func markChatRead(_ chat: Chat, aboveItem: ChatItem? = nil) async { +func markChatRead(_ chat: Chat) async { do { if chat.chatStats.unreadCount > 0 { - let minItemId = chat.chatStats.minUnreadItemId - let itemRange = (minItemId, aboveItem?.id ?? chat.chatItems.last?.id ?? minItemId) let cInfo = chat.chatInfo - try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId, itemRange: itemRange) + try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId) await MainActor.run { - withAnimation { ChatModel.shared.markChatItemsRead(cInfo, aboveItem: aboveItem) } + withAnimation { ChatModel.shared.markChatItemsRead(cInfo) } } } if chat.chatStats.unreadChat { @@ -1399,17 +1397,6 @@ func markChatUnread(_ chat: Chat, unreadChat: Bool = true) async { } } -func apiMarkChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) async { - do { - try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId, itemRange: (cItem.id, cItem.id)) - DispatchQueue.main.async { - ChatModel.shared.markChatItemsRead(cInfo, [cItem.id]) - } - } catch { - logger.error("apiChatRead error: \(responseError(error))") - } -} - func apiMarkChatItemsRead(_ cInfo: ChatInfo, _ itemIds: [ChatItem.ID]) async { do { try await apiChatItemsRead(type: cInfo.chatType, id: cInfo.apiId, itemIds: itemIds) diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 1acf08035c..6b287d52a1 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -987,7 +987,7 @@ struct ChatView: View { } } else if chatItem.isRcvNew { waitToMarkRead { - await apiMarkChatItemRead(chat.chatInfo, chatItem) + await apiMarkChatItemsRead(chat.chatInfo, [chatItem.id]) } } } diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index f8cc2ac8b7..1df6d07813 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -137,7 +137,7 @@ public enum ChatCommand { case apiCallStatus(contact: Contact, callStatus: WebRTCCallStatus) // WebRTC calls / case apiGetNetworkStatuses - case apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)) + case apiChatRead(type: ChatType, id: Int64) case apiChatItemsRead(type: ChatType, id: Int64, itemIds: [Int64]) case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) case receiveFile(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?, inline: Bool?) @@ -310,7 +310,7 @@ public enum ChatCommand { case .apiGetCallInvitations: return "/_call get" case let .apiCallStatus(contact, callStatus): return "/_call status @\(contact.apiId) \(callStatus.rawValue)" case .apiGetNetworkStatuses: return "/_network_statuses" - case let .apiChatRead(type, id, itemRange: (from, to)): return "/_read chat \(ref(type, id)) from=\(from) to=\(to)" + case let .apiChatRead(type, id): return "/_read chat \(ref(type, id))" case let .apiChatItemsRead(type, id, itemIds): return "/_read chat items \(ref(type, id)) \(joinedIds(itemIds))" case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id)) \(onOff(unreadChat))" case let .receiveFile(fileId, userApprovedRelays, encrypt, inline): return "/freceive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))\(onOffParam("inline", inline))" diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index ca03d0ce72..56e1376ea2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -333,9 +333,8 @@ object ChatModel { chatItems = arrayListOf(newPreviewItem), chatStats = if (cItem.meta.itemStatus is CIStatus.RcvNew) { - val minUnreadId = if(chat.chatStats.minUnreadItemId == 0L) cItem.id else chat.chatStats.minUnreadItemId increaseUnreadCounter(rhId, currentUser.value!!) - chat.chatStats.copy(unreadCount = chat.chatStats.unreadCount + 1, minUnreadItemId = minUnreadId) + chat.chatStats.copy(unreadCount = chat.chatStats.unreadCount + 1) } else chat.chatStats @@ -514,23 +513,19 @@ object ChatModel { } } - fun markChatItemsRead(remoteHostId: Long?, chatInfo: ChatInfo, range: CC.ItemRange? = null, unreadCountAfter: Int? = null) { + fun markChatItemsRead(remoteHostId: Long?, chatInfo: ChatInfo, itemIds: List? = null) { val cInfo = chatInfo - val markedRead = markItemsReadInCurrentChat(chatInfo, range) + val markedRead = markItemsReadInCurrentChat(chatInfo, itemIds) // update preview val chatIdx = getChatIndex(remoteHostId, cInfo.id) if (chatIdx >= 0) { val chat = chats[chatIdx] val lastId = chat.chatItems.lastOrNull()?.id if (lastId != null) { - val unreadCount = unreadCountAfter ?: if (range != null) chat.chatStats.unreadCount - markedRead else 0 + val unreadCount = if (itemIds != null) chat.chatStats.unreadCount - markedRead else 0 decreaseUnreadCounter(remoteHostId, currentUser.value!!, chat.chatStats.unreadCount - unreadCount) chats[chatIdx] = chat.copy( - chatStats = chat.chatStats.copy( - unreadCount = unreadCount, - // Can't use minUnreadItemId currently since chat items can have unread items between read items - //minUnreadItemId = if (range != null) kotlin.math.max(chat.chatStats.minUnreadItemId, range.to + 1) else lastId + 1 - ) + chatStats = chat.chatStats.copy(unreadCount = unreadCount) ) } } @@ -642,21 +637,17 @@ object ChatModel { } } - private fun markItemsReadInCurrentChat(chatInfo: ChatInfo, range: CC.ItemRange? = null): Int { + private fun markItemsReadInCurrentChat(chatInfo: ChatInfo, itemIds: List? = null): Int { val cInfo = chatInfo var markedRead = 0 if (chatId.value == cInfo.id) { val items = chatItems.value var i = items.lastIndex - val itemIdsFromRange = if (range != null) { - (range.from .. range.to).toMutableSet() - } else { - mutableSetOf() - } + val itemIdsFromRange = itemIds?.toMutableSet() ?: mutableSetOf() val markedReadIds = mutableSetOf() while (i >= 0) { val item = items[i] - if (item.meta.itemStatus is CIStatus.RcvNew && (range == null || itemIdsFromRange.contains(item.id))) { + if (item.meta.itemStatus is CIStatus.RcvNew && (itemIds == null || itemIdsFromRange.contains(item.id))) { val newItem = item.withStatus(CIStatus.RcvRead()) items[i] = newItem if (newItem.meta.itemLive != true && newItem.meta.itemTimed?.ttl != null) { @@ -666,7 +657,7 @@ object ChatModel { } markedReadIds.add(item.id) markedRead++ - if (range != null) { + if (itemIds != null) { itemIdsFromRange.remove(item.id) // already set all needed items as read, can finish the loop if (itemIdsFromRange.isEmpty()) break @@ -674,7 +665,7 @@ object ChatModel { } i-- } - chatItemsChangesListener?.read(if (range != null) markedReadIds else null, items) + chatItemsChangesListener?.read(if (itemIds != null) markedReadIds else null, items) } return markedRead } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 0cab7ce8e9..a18dd0ac14 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -1599,8 +1599,8 @@ object ChatController { return null } - suspend fun apiChatRead(rh: Long?, type: ChatType, id: Long, range: CC.ItemRange): Boolean { - val r = sendCmd(rh, CC.ApiChatRead(type, id, range)) + suspend fun apiChatRead(rh: Long?, type: ChatType, id: Long): Boolean { + val r = sendCmd(rh, CC.ApiChatRead(type, id)) if (r is CR.CmdOk) return true Log.e(TAG, "apiChatRead bad response: ${r.responseType} ${r.details}") return false @@ -3172,7 +3172,7 @@ sealed class CC { class ApiGetNetworkStatuses(): CC() class ApiAcceptContact(val incognito: Boolean, val contactReqId: Long): CC() class ApiRejectContact(val contactReqId: Long): CC() - class ApiChatRead(val type: ChatType, val id: Long, val range: ItemRange): CC() + class ApiChatRead(val type: ChatType, val id: Long): CC() class ApiChatItemsRead(val type: ChatType, val id: Long, val itemIds: List): CC() class ApiChatUnread(val type: ChatType, val id: Long, val unreadChat: Boolean): CC() class ReceiveFile(val fileId: Long, val userApprovedRelays: Boolean, val encrypt: Boolean, val inline: Boolean?): CC() @@ -3338,7 +3338,7 @@ sealed class CC { is ApiEndCall -> "/_call end @${contact.apiId}" is ApiCallStatus -> "/_call status @${contact.apiId} ${callStatus.value}" is ApiGetNetworkStatuses -> "/_network_statuses" - is ApiChatRead -> "/_read chat ${chatRef(type, id)} from=${range.from} to=${range.to}" + is ApiChatRead -> "/_read chat ${chatRef(type, id)}" is ApiChatItemsRead -> "/_read chat items ${chatRef(type, id)} ${itemIds.joinToString(",")}" is ApiChatUnread -> "/_unread chat ${chatRef(type, id)} ${onOff(unreadChat)}" is ReceiveFile -> diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 875868103e..2a66daf2db 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -490,19 +490,35 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - }, addMembers = { groupInfo -> addGroupMembers(view = view, groupInfo = groupInfo, rhId = chatRh, close = { ModalManager.end.closeModals() }) }, openGroupLink = { groupInfo -> openGroupLink(view = view, groupInfo = groupInfo, rhId = chatRh, close = { ModalManager.end.closeModals() }) }, - markRead = { range, unreadCountAfter -> + markItemsRead = { itemsIds -> withBGApi { withChats { // It's important to call it on Main thread. Otherwise, composable crash occurs from time-to-time without useful stacktrace withContext(Dispatchers.Main) { - markChatItemsRead(chatRh, chatInfo, range, unreadCountAfter) + markChatItemsRead(chatRh, chatInfo, itemsIds) + } + ntfManager.cancelNotificationsForChat(chatInfo.id) + chatModel.controller.apiChatItemsRead( + chatRh, + chatInfo.chatType, + chatInfo.apiId, + itemsIds + ) + } + } + }, + markChatRead = { + withBGApi { + withChats { + // It's important to call it on Main thread. Otherwise, composable crash occurs from time-to-time without useful stacktrace + withContext(Dispatchers.Main) { + markChatItemsRead(chatRh, chatInfo) } ntfManager.cancelNotificationsForChat(chatInfo.id) chatModel.controller.apiChatRead( chatRh, chatInfo.chatType, - chatInfo.apiId, - range + chatInfo.apiId ) } } @@ -602,7 +618,8 @@ fun ChatLayout( showItemDetails: (ChatInfo, ChatItem) -> Unit, addMembers: (GroupInfo) -> Unit, openGroupLink: (GroupInfo) -> Unit, - markRead: (CC.ItemRange, unreadCountAfter: Int?) -> Unit, + markItemsRead: (List) -> Unit, + markChatRead: () -> Unit, changeNtfsState: (Boolean, currentValue: MutableState) -> Unit, onSearchValueChanged: (String) -> Unit, onComposed: suspend (chatId: String) -> Unit, @@ -649,7 +666,7 @@ fun ChatLayout( useLinkPreviews, linkMode, selectedChatItems, showMemberInfo, loadMessages, deleteMessage, deleteMessages, receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat, forwardItem, updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember, - setReaction, showItemDetails, markRead, remember { { onComposed(it) } }, developerTools, showViaProxy, + setReaction, showItemDetails, markItemsRead, markChatRead, remember { { onComposed(it) } }, developerTools, showViaProxy, ) } } @@ -937,7 +954,8 @@ fun BoxScope.ChatItemsList( findModelMember: (String) -> GroupMember?, setReaction: (ChatInfo, ChatItem, Boolean, MsgReaction) -> Unit, showItemDetails: (ChatInfo, ChatItem) -> Unit, - markRead: (CC.ItemRange, unreadCountAfter: Int?) -> Unit, + markItemsRead: (List) -> Unit, + markChatRead: () -> Unit, onComposed: suspend (chatId: String) -> Unit, developerTools: Boolean, showViaProxy: Boolean @@ -1284,15 +1302,15 @@ fun BoxScope.ChatItemsList( DateSeparator(last.meta.itemTs) } if (item.isRcvNew) { - val (itemIdStart, itemIdEnd) = when (merged) { - is MergedItem.Single -> merged.item.item.id to merged.item.item.id - is MergedItem.Grouped -> merged.items.last().item.id to merged.items.first().item.id + val itemIds = when (merged) { + is MergedItem.Single -> listOf(merged.item.item.id) + is MergedItem.Grouped -> merged.items.map { it.item.id } } - MarkItemsReadAfterDelay(keyForItem(item), itemIdStart, itemIdEnd, finishedInitialComposition, chatInfo.id, listState, markRead) + MarkItemsReadAfterDelay(keyForItem(item), itemIds, finishedInitialComposition, chatInfo.id, listState, markItemsRead) } } } - FloatingButtons(loadingMoreItems, mergedItems, unreadCount, maxHeight, composeViewHeight, remoteHostId, chatInfo, searchValue, markRead, listState) + FloatingButtons(loadingMoreItems, mergedItems, unreadCount, maxHeight, composeViewHeight, remoteHostId, chatInfo, searchValue, markChatRead, listState) FloatingDate(Modifier.padding(top = 10.dp + topPaddingToContent()).align(Alignment.TopCenter), mergedItems, listState) LaunchedEffect(Unit) { @@ -1385,7 +1403,7 @@ fun BoxScope.FloatingButtons( remoteHostId: Long?, chatInfo: ChatInfo, searchValue: State, - markRead: (CC.ItemRange, unreadCountAfter: Int?) -> Unit, + markChatRead: () -> Unit, listState: State ) { val scope = rememberCoroutineScope() @@ -1453,12 +1471,7 @@ fun BoxScope.FloatingButtons( generalGetString(MR.strings.mark_read), painterResource(MR.images.ic_check), onClick = { - val chat = chatModel.chats.value.firstOrNull { it.remoteHostId == remoteHostId && it.id == chatInfo.id } ?: return@ItemAction - val minUnreadItemId = chat.chatStats.minUnreadItemId - markRead( - CC.ItemRange(minUnreadItemId, chat.chatItems.lastOrNull()?.id ?: return@ItemAction), - 0 - ) + markChatRead() showDropDown.value = false }) } @@ -1779,12 +1792,11 @@ private fun DateSeparator(date: Instant) { @Composable private fun MarkItemsReadAfterDelay( itemKey: String, - itemIdStart: Long, - itemIdEnd: Long, + itemIds: List, finishedInitialComposition: State, chatId: ChatId, listState: State, - markRead: (CC.ItemRange, unreadCountAfter: Int?) -> Unit + markItemsRead: (List) -> Unit ) { // items can be "visible" in terms of LazyColumn but hidden behind compose view/appBar. So don't count such item as visible and not mark read val itemIsPartiallyAboveCompose = remember { derivedStateOf { @@ -1795,11 +1807,11 @@ private fun MarkItemsReadAfterDelay( false } } } - LaunchedEffect(itemIsPartiallyAboveCompose.value, itemIdStart, itemIdEnd, finishedInitialComposition.value, chatId) { + LaunchedEffect(itemIsPartiallyAboveCompose.value, itemIds, finishedInitialComposition.value, chatId) { if (chatId != ChatModel.chatId.value || !itemIsPartiallyAboveCompose.value || !finishedInitialComposition.value) return@LaunchedEffect delay(600L) - markRead(CC.ItemRange(itemIdStart, itemIdEnd), null) + markItemsRead(itemIds) } } @@ -2406,7 +2418,8 @@ fun PreviewChatLayout() { showItemDetails = { _, _ -> }, addMembers = { _ -> }, openGroupLink = {}, - markRead = { _, _ -> }, + markItemsRead = { _ -> }, + markChatRead = {}, changeNtfsState = { _, _ -> }, onSearchValueChanged = {}, onComposed = {}, @@ -2478,7 +2491,8 @@ fun PreviewGroupChatLayout() { showItemDetails = { _, _ -> }, addMembers = { _ -> }, openGroupLink = {}, - markRead = { _, _ -> }, + markItemsRead = { _ -> }, + markChatRead = {}, changeNtfsState = { _, _ -> }, onSearchValueChanged = {}, onComposed = {}, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt index d071a9d4fd..226030fcd4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt @@ -537,15 +537,13 @@ fun markChatRead(c: Chat, chatModel: ChatModel) { var chat = c withApi { if (chat.chatStats.unreadCount > 0) { - val minUnreadItemId = chat.chatStats.minUnreadItemId withChats { markChatItemsRead(chat.remoteHostId, chat.chatInfo) } chatModel.controller.apiChatRead( chat.remoteHostId, chat.chatInfo.chatType, - chat.chatInfo.apiId, - CC.ItemRange(minUnreadItemId, chat.chatItems.last().id) + chat.chatInfo.apiId ) chat = chatModel.getChat(chat.id) ?: return@withApi } From ba7abcf6f7da0a8fbf51ee9c8abf100515f33541 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 27 Nov 2024 19:01:16 +0000 Subject: [PATCH 125/567] ios: update onboarding texts (#5255) * ios: update onboarding texts * translations * more translations * more translations 2 --- .../Shared/Views/Chat/ChatInfoToolbar.swift | 2 +- apps/ios/Shared/Views/Chat/ChatInfoView.swift | 2 +- .../ChatItem/CIFeaturePreferenceView.swift | 2 +- .../Chat/ChatItem/CIGroupInvitationView.swift | 4 +- .../ChatItem/CIMemberCreatedContactView.swift | 2 +- .../Views/Chat/ChatItem/CIMetaView.swift | 8 +- .../Chat/ChatItem/CIRcvDecryptionError.swift | 6 +- .../Chat/ChatItem/MarkedDeletedItemView.swift | 2 +- .../Views/Chat/ChatItem/MsgContentView.swift | 6 +- apps/ios/Shared/Views/Chat/ChatItemView.swift | 6 +- .../Chat/ComposeMessage/ContextItemView.swift | 2 +- .../Views/Chat/Group/GroupChatInfoView.swift | 2 +- .../Chat/Group/GroupMemberInfoView.swift | 2 +- apps/ios/Shared/Views/ChatList/ChatHelp.swift | 2 +- .../Views/ChatList/ChatPreviewView.swift | 10 +- .../Views/Contacts/ContactListNavLink.swift | 2 +- .../Onboarding/AddressCreationCard.swift | 8 +- .../Onboarding/ChooseServerOperators.swift | 9 +- .../Shared/Views/Onboarding/HowItWorks.swift | 13 +- .../Onboarding/SetNotificationsMode.swift | 5 +- .../Shared/Views/Onboarding/SimpleXInfo.swift | 29 +- .../RemoteAccess/ConnectDesktopView.swift | 4 +- .../Views/UserSettings/DeveloperView.swift | 2 +- .../UserSettings/NotificationsView.swift | 6 +- .../UserSettings/UserAddressLearnMore.swift | 13 +- .../ar.xcloc/Localized Contents/ar.xliff | 53 +- .../bg.xcloc/Localized Contents/bg.xliff | 684 ++++++++++----- .../en.lproj/Localizable.strings | 4 - .../bn.xcloc/Localized Contents/bn.xliff | 48 +- .../cs.xcloc/Localized Contents/cs.xliff | 681 ++++++++++----- .../en.lproj/Localizable.strings | 4 - .../de.xcloc/Localized Contents/de.xliff | 687 ++++++++++----- .../en.lproj/Localizable.strings | 4 - .../el.xcloc/Localized Contents/el.xliff | 48 +- .../en.xcloc/Localized Contents/en.xliff | 803 +++++++++++++----- .../en.lproj/Localizable.strings | 4 - .../es.xcloc/Localized Contents/es.xliff | 687 ++++++++++----- .../en.lproj/Localizable.strings | 4 - .../fi.xcloc/Localized Contents/fi.xliff | 681 ++++++++++----- .../en.lproj/Localizable.strings | 4 - .../fr.xcloc/Localized Contents/fr.xliff | 687 ++++++++++----- .../en.lproj/Localizable.strings | 4 - .../he.xcloc/Localized Contents/he.xliff | 49 +- .../hr.xcloc/Localized Contents/hr.xliff | 49 +- .../hu.xcloc/Localized Contents/hu.xliff | 685 ++++++++++----- .../en.lproj/Localizable.strings | 4 - .../it.xcloc/Localized Contents/it.xliff | 687 ++++++++++----- .../en.lproj/Localizable.strings | 4 - .../ja.xcloc/Localized Contents/ja.xliff | 683 ++++++++++----- .../en.lproj/Localizable.strings | 4 - .../ko.xcloc/Localized Contents/ko.xliff | 48 +- .../lt.xcloc/Localized Contents/lt.xliff | 48 +- .../nl.xcloc/Localized Contents/nl.xliff | 685 ++++++++++----- .../en.lproj/Localizable.strings | 4 - .../pl.xcloc/Localized Contents/pl.xliff | 685 ++++++++++----- .../en.lproj/Localizable.strings | 4 - .../Localized Contents/pt-BR.xliff | 53 +- .../pt.xcloc/Localized Contents/pt.xliff | 53 +- .../ru.xcloc/Localized Contents/ru.xliff | 696 ++++++++++----- .../en.lproj/Localizable.strings | 4 - .../th.xcloc/Localized Contents/th.xliff | 681 ++++++++++----- .../en.lproj/Localizable.strings | 4 - .../tr.xcloc/Localized Contents/tr.xliff | 685 ++++++++++----- .../en.lproj/Localizable.strings | 4 - .../uk.xcloc/Localized Contents/uk.xliff | 687 ++++++++++----- .../en.lproj/Localizable.strings | 4 - .../Localized Contents/zh-Hans.xliff | 685 ++++++++++----- .../en.lproj/Localizable.strings | 4 - .../Localized Contents/zh-Hant.xliff | 50 +- apps/ios/bg.lproj/Localizable.strings | 36 +- apps/ios/cs.lproj/Localizable.strings | 34 +- apps/ios/de.lproj/Localizable.strings | 36 +- apps/ios/en.lproj/Localizable.strings | 4 - apps/ios/es.lproj/Localizable.strings | 36 +- apps/ios/fi.lproj/Localizable.strings | 34 +- apps/ios/fr.lproj/Localizable.strings | 36 +- apps/ios/hu.lproj/Localizable.strings | 36 +- apps/ios/it.lproj/Localizable.strings | 36 +- apps/ios/ja.lproj/Localizable.strings | 38 +- apps/ios/nl.lproj/Localizable.strings | 36 +- apps/ios/pl.lproj/Localizable.strings | 36 +- apps/ios/ru.lproj/Localizable.strings | 41 +- apps/ios/th.lproj/Localizable.strings | 34 +- apps/ios/tr.lproj/Localizable.strings | 36 +- apps/ios/uk.lproj/Localizable.strings | 36 +- apps/ios/zh-Hans.lproj/Localizable.strings | 36 +- 86 files changed, 8876 insertions(+), 4190 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift b/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift index 8c9112a858..62a41c504a 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift @@ -45,7 +45,7 @@ struct ChatInfoToolbar: View { } private var contactVerifiedShield: Text { - (Text(Image(systemName: "checkmark.shield")) + Text(" ")) + (Text(Image(systemName: "checkmark.shield")) + textSpace) .font(.caption) .foregroundColor(theme.colors.secondary) .baselineOffset(1) diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index 35adcd49c1..c829e1a2b9 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -339,7 +339,7 @@ struct ChatInfoView: View { Text(Image(systemName: "checkmark.shield")) .foregroundColor(theme.colors.secondary) .font(.title2) - + Text(" ") + + textSpace + Text(contact.profile.displayName) .font(.largeTitle) ) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIFeaturePreferenceView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIFeaturePreferenceView.swift index 752f599c8d..2c9c261536 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIFeaturePreferenceView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIFeaturePreferenceView.swift @@ -47,7 +47,7 @@ struct CIFeaturePreferenceView: View { + Text(acceptText) .fontWeight(.medium) .foregroundColor(theme.colors.primary) - + Text(" ") + + Text(verbatim: " ") } r = r + chatItem.timestampText .fontWeight(.light) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift index 1a77b36d6f..107208a033 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift @@ -45,7 +45,7 @@ struct CIGroupInvitationView: View { Text(chatIncognito ? "Tap to join incognito" : "Tap to join") .foregroundColor(inProgress ? theme.colors.secondary : chatIncognito ? .indigo : theme.colors.primary) .font(.callout) - + Text(" ") + + Text(verbatim: " ") + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, colorMode: .transparent, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) ) .overlay(DetermineWidth()) @@ -53,7 +53,7 @@ struct CIGroupInvitationView: View { } else { ( groupInvitationText() - + Text(" ") + + Text(verbatim: " ") + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, colorMode: .transparent, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) ) .overlay(DetermineWidth()) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift index 463695ddb7..e9cd838234 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift @@ -45,7 +45,7 @@ struct CIMemberCreatedContactView: View { + Text(openText) .fontWeight(.medium) .foregroundColor(theme.colors.primary) - + Text(" ") + + Text(verbatim: " ") } r = r + chatItem.timestampText .fontWeight(.light) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift index 32249506d3..e58ad0f74e 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift @@ -83,7 +83,7 @@ enum MetaColorMode { ? Image("checkmark.wide") : Image(systemName: "circlebadge.fill") ).foregroundColor(.clear) - case .invertedMaterial: Text(" ").kerning(13) + case .invertedMaterial: textSpace.kerning(13) } } } @@ -120,7 +120,7 @@ func ciMetaText( if ttl != chatTTL { r = r + colored(Text(shortTimeText(ttl)), resolved) } - space = Text(" ") + space = textSpace } if showViaProxy, meta.sentViaProxy == true { appendSpace() @@ -138,12 +138,12 @@ func ciMetaText( } else if !meta.disappearing { r = r + colorMode.statusSpacer(meta.itemStatus.sent) } - space = Text(" ") + space = textSpace } if let enc = encrypted { appendSpace() r = r + statusIconText(enc ? "lock" : "lock.open", resolved) - space = Text(" ") + space = textSpace } if showTimesamp { appendSpace() diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift index 693641b1d3..4603a026cd 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift @@ -121,11 +121,11 @@ struct CIRcvDecryptionError: View { Text(Image(systemName: "exclamationmark.arrow.triangle.2.circlepath")) .foregroundColor(syncSupported ? theme.colors.primary : theme.colors.secondary) .font(.callout) - + Text(" ") + + textSpace + Text("Fix connection") .foregroundColor(syncSupported ? theme.colors.primary : theme.colors.secondary) .font(.callout) - + Text(" ") + + Text(verbatim: " ") + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, colorMode: .transparent, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) ) } @@ -144,7 +144,7 @@ struct CIRcvDecryptionError: View { Text(chatItem.content.text) .foregroundColor(.red) .italic() - + Text(" ") + + Text(verbatim: " ") + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, colorMode: .transparent, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) } .padding(.horizontal, 12) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift index 6631206987..c2b4021edc 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift @@ -17,7 +17,7 @@ struct MarkedDeletedItemView: View { var chatItem: ChatItem var body: some View { - (Text(mergedMarkedDeletedText).italic() + Text(" ") + chatItem.timestampText) + (Text(mergedMarkedDeletedText).italic() + textSpace + chatItem.timestampText) .font(.caption) .foregroundColor(theme.colors.secondary) .padding(.horizontal, 12) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift index 63d5dc30dc..914f7c8a2f 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift @@ -11,7 +11,7 @@ import SimpleXChat let uiLinkColor = UIColor(red: 0, green: 0.533, blue: 1, alpha: 1) -private let noTyping = Text(" ") +private let noTyping = Text(verbatim: " ") private let typingIndicators: [Text] = [ (typing(.black) + typing() + typing()), @@ -85,7 +85,7 @@ struct MsgContentView: View { } private func reserveSpaceForMeta(_ mt: CIMeta) -> Text { - (rightToLeft ? Text("\n") : Text(" ")) + ciMetaText(mt, chatTTL: chat.chatInfo.timedMessagesTTL, encrypted: nil, colorMode: .transparent, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) + (rightToLeft ? Text("\n") : Text(verbatim: " ")) + ciMetaText(mt, chatTTL: chat.chatInfo.timedMessagesTTL, encrypted: nil, colorMode: .transparent, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) } } @@ -104,7 +104,7 @@ func messageText(_ text: String, _ formattedText: [FormattedText]?, _ sender: St } if let i = icon { - res = Text(Image(systemName: i)).foregroundColor(secondaryColor) + Text(" ") + res + res = Text(Image(systemName: i)).foregroundColor(secondaryColor) + textSpace + res } if let s = sender { diff --git a/apps/ios/Shared/Views/Chat/ChatItemView.swift b/apps/ios/Shared/Views/Chat/ChatItemView.swift index 418c2d7c34..ebbc55a932 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemView.swift @@ -170,7 +170,7 @@ struct ChatItemContentView: View { private func eventItemViewText(_ secondaryColor: Color) -> Text { if !revealed, let t = mergedGroupEventText { - return chatEventText(t + Text(" ") + chatItem.timestampText, secondaryColor) + return chatEventText(t + textSpace + chatItem.timestampText, secondaryColor) } else if let member = chatItem.memberDisplayName { return Text(member + " ") .font(.caption) @@ -203,7 +203,7 @@ struct ChatItemContentView: View { } else if ns.count == 0 { Text("\(count) group events") } else if count > ns.count { - Text(members) + Text(" ") + Text("and \(count - ns.count) other events") + Text(members) + textSpace + Text("and \(count - ns.count) other events") } else { Text(members) } @@ -234,7 +234,7 @@ func chatEventText(_ text: Text, _ secondaryColor: Color) -> Text { } func chatEventText(_ eventText: LocalizedStringKey, _ ts: Text, _ secondaryColor: Color) -> Text { - chatEventText(Text(eventText) + Text(" ") + ts, secondaryColor) + chatEventText(Text(eventText) + textSpace + ts, secondaryColor) } func chatEventText(_ ci: ChatItem, _ secondaryColor: Color) -> Text { diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift index 8b988f5624..fa999961fc 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift @@ -85,7 +85,7 @@ struct ContextItemView: View { } func image(_ s: String) -> Text { - Text(Image(systemName: s)).foregroundColor(Color(uiColor: .tertiaryLabel)) + Text(" ") + Text(Image(systemName: s)).foregroundColor(Color(uiColor: .tertiaryLabel)) + textSpace } } } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index 9385633060..59df52df9f 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -418,7 +418,7 @@ struct GroupChatInfoView: View { } private var memberVerifiedShield: Text { - (Text(Image(systemName: "checkmark.shield")) + Text(" ")) + (Text(Image(systemName: "checkmark.shield")) + textSpace) .font(.caption) .baselineOffset(2) .kerning(-2) diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index fd72b5b515..ed40c0592b 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -388,7 +388,7 @@ struct GroupMemberInfoView: View { Text(Image(systemName: "checkmark.shield")) .foregroundColor(theme.colors.secondary) .font(.title2) - + Text(" ") + + textSpace + Text(mem.displayName) .font(.largeTitle) ) diff --git a/apps/ios/Shared/Views/ChatList/ChatHelp.swift b/apps/ios/Shared/Views/ChatList/ChatHelp.swift index 776229f60a..7abab33177 100644 --- a/apps/ios/Shared/Views/ChatList/ChatHelp.swift +++ b/apps/ios/Shared/Views/ChatList/ChatHelp.swift @@ -42,7 +42,7 @@ struct ChatHelp: View { Text("above, then choose:") } - Text("**Create 1-time link**: to create a new invitation link.") + Text("**Create 1-time link**: to create and share a new invitation link.") Text("**Scan / Paste link**: to connect via a link you received.") Text("**Create group**: to create a new group.") } diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift index d721d546c1..13701a40a2 100644 --- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift @@ -172,7 +172,7 @@ struct ChatPreviewView: View { } private var verifiedIcon: Text { - (Text(Image(systemName: "checkmark.shield")) + Text(" ")) + (Text(Image(systemName: "checkmark.shield")) + textSpace) .foregroundColor(theme.colors.secondary) .baselineOffset(1) .kerning(-2) @@ -232,12 +232,12 @@ struct ChatPreviewView: View { + messageText(msg, parseSimpleXMarkdown(msg), nil, preview: true, showSecrets: false, secondaryColor: theme.colors.secondary) func image(_ s: String, color: Color = Color(uiColor: .tertiaryLabel)) -> Text { - Text(Image(systemName: s)).foregroundColor(color) + Text(" ") + Text(Image(systemName: s)).foregroundColor(color) + textSpace } func attachment() -> Text { switch draft.preview { - case let .filePreview(fileName, _): return image("doc.fill") + Text(fileName) + Text(" ") + case let .filePreview(fileName, _): return image("doc.fill") + Text(fileName) + textSpace case .mediaPreviews: return image("photo") case let .voicePreview(_, duration): return image("play.fill") + Text(durationText(duration)) default: return Text("") @@ -367,11 +367,11 @@ struct ChatPreviewView: View { case .sndErrorAuth, .sndError: return Text(Image(systemName: "multiply")) .font(.caption) - .foregroundColor(.red) + Text(" ") + .foregroundColor(.red) + textSpace case .sndWarning: return Text(Image(systemName: "exclamationmark.triangle.fill")) .font(.caption) - .foregroundColor(.orange) + Text(" ") + .foregroundColor(.orange) + textSpace default: return Text("") } } diff --git a/apps/ios/Shared/Views/Contacts/ContactListNavLink.swift b/apps/ios/Shared/Views/Contacts/ContactListNavLink.swift index 4b43610236..898a47cc86 100644 --- a/apps/ios/Shared/Views/Contacts/ContactListNavLink.swift +++ b/apps/ios/Shared/Views/Contacts/ContactListNavLink.swift @@ -151,7 +151,7 @@ struct ContactListNavLink: View { } private var verifiedIcon: Text { - (Text(Image(systemName: "checkmark.shield")) + Text(" ")) + (Text(Image(systemName: "checkmark.shield")) + textSpace) .foregroundColor(.secondary) .baselineOffset(1) .kerning(-2) diff --git a/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift b/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift index 9cf755be78..c757dcfeeb 100644 --- a/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift +++ b/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift @@ -34,13 +34,7 @@ struct AddressCreationCard: View { Text("Your SimpleX address") .font(.title3) Spacer() - HStack(alignment: .center) { - Text("How to use it") - VStack { - Image(systemName: "info.circle") - .foregroundColor(theme.colors.secondary) - } - } + Text("How to use it") + textSpace + Text(Image(systemName: "info.circle")).foregroundColor(theme.colors.secondary) } } .frame(maxWidth: .infinity, alignment: .leading) diff --git a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift index 4efdb99f21..910f2a4127 100644 --- a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift +++ b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift @@ -133,7 +133,7 @@ struct ChooseServerOperators: View { reviewLaterButton() ( Text("Conditions will be accepted for enabled operators after 30 days.") - + Text(" ") + + textSpace + Text("You can configure operators in Network & servers settings.") ) .multilineTextAlignment(.center) @@ -405,6 +405,8 @@ struct ChooseServerOperators: View { } } +let operatorsPostLink = URL(string: "https://simplex.chat/blog/20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.html")! + struct ChooseServerOperatorsInfoView: View { var body: some View { VStack(alignment: .leading) { @@ -415,8 +417,9 @@ struct ChooseServerOperatorsInfoView: View { ScrollView { VStack(alignment: .leading) { Group { - Text("When more than one network operator is enabled, the app will use the servers of different operators for each conversation.") - Text("For example, if you receive messages via SimpleX Chat server, the app will use one of Flux servers for private routing.") + Text("The app protects your privacy by using different operators in each conversation.") + Text("When more than one operator is enabled, none of them has metadata to learn who communicates with whom.") + Text("For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server.") } .padding(.bottom) } diff --git a/apps/ios/Shared/Views/Onboarding/HowItWorks.swift b/apps/ios/Shared/Views/Onboarding/HowItWorks.swift index 66e63fd9c2..7452d74e91 100644 --- a/apps/ios/Shared/Views/Onboarding/HowItWorks.swift +++ b/apps/ios/Shared/Views/Onboarding/HowItWorks.swift @@ -23,13 +23,10 @@ struct HowItWorks: View { ScrollView { VStack(alignment: .leading) { Group { - Text("Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*") - Text("To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts.") - Text("You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them.") - Text("Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**.") - if onboarding { - Text("Read more in our GitHub repository.") - } else { + Text("To protect your privacy, SimpleX uses separate IDs for each of your contacts.") + Text("Only client devices store user profiles, contacts, groups, and messages.") + Text("All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.") + if !onboarding { Text("Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme).") } } @@ -47,7 +44,7 @@ struct HowItWorks: View { } } .lineLimit(10) - .padding() + .padding(onboarding ? 25 : 16) .frame(maxHeight: .infinity, alignment: .top) .modifier(ThemedBackground()) } diff --git a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift index cba290c286..6164fcae70 100644 --- a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift +++ b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift @@ -142,7 +142,7 @@ struct NtfModeSelector: View { struct NotificationsInfoView: View { var body: some View { VStack(alignment: .leading) { - Text("Notification modes") + Text("Notifications privacy") .font(.largeTitle) .bold() .padding(.vertical) @@ -151,8 +151,9 @@ struct NotificationsInfoView: View { Group { ForEach(NotificationsMode.values) { mode in VStack(alignment: .leading, spacing: 4) { - Text(mode.label) + (Text(Image(systemName: mode.icon)) + textSpace + Text(mode.label)) .font(.headline) + .foregroundColor(.secondary) Text(ntfModeDescription(mode)) .lineLimit(10) .font(.callout) diff --git a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift index b6d4c59279..a8704e964b 100644 --- a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift +++ b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift @@ -26,6 +26,7 @@ struct SimpleXInfo: View { .aspectRatio(contentMode: .fit) .frame(width: g.size.width * 0.67) .padding(.bottom, 8) + .padding(.leading, 4) .frame(maxWidth: .infinity, minHeight: 48, alignment: .top) Button { @@ -39,13 +40,14 @@ struct SimpleXInfo: View { Spacer() VStack(alignment: .leading) { - infoRow("privacy", "Privacy redefined", - "The 1st platform without any user identifiers – private by design.", width: 48) - infoRow("shield", "Immune to spam and abuse", - "People can connect to you only via the links you share.", width: 46) - infoRow(colorScheme == .light ? "decentralized" : "decentralized-light", "Decentralized", - "Open-source protocol and code – anybody can run the servers.", width: 44) + onboardingInfoRow("privacy", "Privacy redefined", + "No user identifiers.", width: 48) + onboardingInfoRow("shield", "Immune to spam", + "You decide who can connect.", width: 46) + onboardingInfoRow(colorScheme == .light ? "decentralized" : "decentralized-light", "Decentralized", + "Anybody can host servers.", width: 46) } + .padding(.leading, 16) Spacer() @@ -93,23 +95,24 @@ struct SimpleXInfo: View { .padding(.bottom, 25) } - private func infoRow(_ image: String, _ title: LocalizedStringKey, _ text: LocalizedStringKey, width: CGFloat) -> some View { + private func onboardingInfoRow(_ image: String, _ title: LocalizedStringKey, _ text: LocalizedStringKey, width: CGFloat) -> some View { HStack(alignment: .top) { Image(image) .resizable() .scaledToFit() .frame(width: width, height: 54) .frame(width: 54) - .padding(.top, 4) - .padding(.leading, 4) .padding(.trailing, 10) VStack(alignment: .leading, spacing: 4) { Text(title).font(.headline) - Text(text).frame(minHeight: 40, alignment: .top).font(.callout) + Text(text).frame(minHeight: 40, alignment: .top) + .font(.callout) + .lineLimit(3) + .fixedSize(horizontal: false, vertical: true) } + .padding(.top, 4) } - .padding(.bottom, 20) - .padding(.trailing, 6) + .padding(.bottom, 12) } private func createFirstProfileButton() -> some View { @@ -132,6 +135,8 @@ struct SimpleXInfo: View { } } +let textSpace = Text(verbatim: " ") + struct SimpleXInfo_Previews: PreviewProvider { static var previews: some View { SimpleXInfo(onboarding: true) diff --git a/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift b/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift index b99c054abb..67020e09e7 100644 --- a/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift +++ b/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift @@ -268,7 +268,7 @@ struct ConnectDesktopView: View { 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() + t = t + textSpace + Text("(new)").italic() } return t } @@ -277,7 +277,7 @@ struct ConnectDesktopView: View { 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() + t = t + textSpace + Text("(this device v\(session.appVersion))").italic() } return t } diff --git a/apps/ios/Shared/Views/UserSettings/DeveloperView.swift b/apps/ios/Shared/Views/UserSettings/DeveloperView.swift index 4ef05bd998..513a6c2708 100644 --- a/apps/ios/Shared/Views/UserSettings/DeveloperView.swift +++ b/apps/ios/Shared/Views/UserSettings/DeveloperView.swift @@ -45,7 +45,7 @@ struct DeveloperView: View { } header: { Text("") } footer: { - ((developerTools ? Text("Show:") : Text("Hide:")) + Text(" ") + Text("Database IDs and Transport isolation option.")) + ((developerTools ? Text("Show:") : Text("Hide:")) + textSpace + Text("Database IDs and Transport isolation option.")) .foregroundColor(theme.colors.secondary) } diff --git a/apps/ios/Shared/Views/UserSettings/NotificationsView.swift b/apps/ios/Shared/Views/UserSettings/NotificationsView.swift index ee43a24557..4e7f826f4f 100644 --- a/apps/ios/Shared/Views/UserSettings/NotificationsView.swift +++ b/apps/ios/Shared/Views/UserSettings/NotificationsView.swift @@ -237,9 +237,9 @@ struct NotificationsView: View { func ntfModeDescription(_ mode: NotificationsMode) -> LocalizedStringKey { switch mode { - case .off: return "**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." - case .periodic: return "**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." - case .instant: return "**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." + case .off: return "**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." + case .periodic: return "**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." + case .instant: return "**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." } } diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift b/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift index 414e7efe85..6c1ea8deb2 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift @@ -16,20 +16,23 @@ struct UserAddressLearnMore: View { var body: some View { VStack { List { - VStack(alignment: .leading, spacing: 16) { - (Text(Image(systemName: "envelope")).foregroundColor(.secondary) + Text(" ") + Text("Share address publicly").bold().font(.title2)) + VStack(alignment: .leading, spacing: 12) { + (Text(Image(systemName: "envelope")).foregroundColor(.secondary) + textSpace + Text("Share address publicly").bold().font(.title2)) Text("Share SimpleX address on social media.") Text("You won't lose your contacts if you later delete your address.") - (Text(Image(systemName: "link.badge.plus")).foregroundColor(.secondary) + Text(" ") + Text("Share 1-time link with a friend").font(.title2).bold()) + (Text(Image(systemName: "link.badge.plus")).foregroundColor(.secondary) + textSpace + Text("Share 1-time link with a friend").font(.title2).bold()) + .padding(.top) Text("1-time link can be used *with one contact only* - share in person or via any messenger.") Text("You can set connection name, to remember who the link was shared with.") if !showCreateAddressButton { - (Text(Image(systemName: "shield")).foregroundColor(.secondary) + Text(" ") + Text("Connection security").font(.title2).bold()) + (Text(Image(systemName: "shield")).foregroundColor(.secondary) + textSpace + Text("Connection security").font(.title2).bold()) + .padding(.top) Text("SimpleX address and 1-time links are safe to share via any messenger.") Text("To protect against your link being replaced, you can compare contact security codes.") Text("Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses).") + .padding(.top) } } @@ -81,7 +84,7 @@ struct UserAddressLearnMore: View { createOneTimeLinkActive = true } label: { Text("Create 1-time link") - .font(.footnote) + .font(.callout) } NavigationLink(isActive: $createOneTimeLinkActive) { diff --git a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff index 40481d81f1..53707d108f 100644 --- a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff +++ b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff @@ -187,23 +187,18 @@ ) No comment provided by engineer. - - **Add new contact**: to create your one-time QR Code or link for your contact. - ** إضافة جهة اتصال جديدة **: لإنشاء رمز QR لمرة واحدة أو رابط جهة الاتصال الخاصة بكم. - No comment provided by engineer. - **Create link / QR code** for your contact to use. ** أنشئ رابطًا / رمز QR ** لتستخدمه جهة الاتصال الخاصة بك. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. ** المزيد من الخصوصية **: تحققوا من الرسائل الجديدة كل 20 دقيقة. تتم مشاركة رمز الجهاز مع خادم SimpleX Chat ، ولكن ليس عدد جهات الاتصال أو الرسائل لديكم. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. ** الأكثر خصوصية **: لا تستخدم خادم إشعارات SimpleX Chat ، وتحقق من الرسائل بشكل دوري في الخلفية (يعتمد على عدد مرات استخدامكم للتطبيق). No comment provided by engineer. @@ -217,8 +212,8 @@ ** يرجى ملاحظة **: لن تتمكنوا من استعادة أو تغيير عبارة المرور إذا فقدتموها. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. ** موصى به **: يتم إرسال رمز الجهاز والإشعارات إلى خادم إشعارات SimpleX Chat ، ولكن ليس محتوى الرسالة أو حجمها أو مصدرها. No comment provided by engineer. @@ -1528,8 +1523,8 @@ Image will be received when your contact is online, please wait or check later! No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam No comment provided by engineer. @@ -1926,8 +1921,8 @@ We will be adding server redundancy to prevent lost messages. Onion hosts will not be used. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. No comment provided by engineer. @@ -1978,8 +1973,8 @@ We will be adding server redundancy to prevent lost messages. Open user profiles authentication reason - - Open-source protocol and code – anybody can run the servers. + + Anybody can host servers. No comment provided by engineer. @@ -2010,8 +2005,8 @@ We will be adding server redundancy to prevent lost messages. Paste the link you received into the box below to connect with your contact. No comment provided by engineer. - - People can connect to you only via the links you share. + + You decide who can connect. No comment provided by engineer. @@ -2590,8 +2585,8 @@ We will be adding server redundancy to prevent lost messages. Thanks to the users – contribute via Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. + + No user identifiers. No comment provided by engineer. @@ -2622,8 +2617,8 @@ We will be adding server redundancy to prevent lost messages. The microphone does not work when the app is in the background. No comment provided by engineer. - - The next generation of private messaging + + The future of messaging No comment provided by engineer. @@ -2686,8 +2681,8 @@ We will be adding server redundancy to prevent lost messages. To prevent the call interruption, enable Do Not Disturb mode. No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. No comment provided by engineer. @@ -2972,10 +2967,6 @@ To connect, please ask your contact to create another connection link and check You can use markdown to format messages: No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - No comment provided by engineer. - You could not be verified; please try again. No comment provided by engineer. @@ -3752,8 +3743,8 @@ SimpleX servers cannot see your profile. %u messages skipped. %u تم تخطي الرسائل. - - **Add contact**: to create a new invitation link, or connect via a link you received. + + **Create 1-time link**: to create and share a new invitation link. **إضافة جهة اتصال**: لإنشاء رابط دعوة جديد، أو الاتصال عبر الرابط الذي تلقيتوهم. diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 1a40820dce..310b5e8bb3 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -12,21 +12,6 @@ No comment provided by engineer. - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - ( ( @@ -127,6 +112,14 @@ %@ е потвърдено No comment provided by engineer. + + %@ server + No comment provided by engineer. + + + %@ servers + No comment provided by engineer. + %@ uploaded %@ качено @@ -346,14 +339,9 @@ ) No comment provided by engineer. - - **Add contact**: to create a new invitation link, or connect via a link you received. - **Добави контакт**: за създаване на нов линк или свързване чрез получен линк за връзка. - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Добави нов контакт**: за да създадете своя еднократен QR код или линк за вашия контакт. + + **Create 1-time link**: to create and share a new invitation link. + **Добави контакт**: за създаване на нов линк. No comment provided by engineer. @@ -361,13 +349,13 @@ **Създай група**: за създаване на нова група. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **По поверително**: проверявайте новите съобщения на всеки 20 минути. Токенът на устройството се споделя със сървъра за чат SimpleX, но не и колко контакти или съобщения имате. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **Най-поверително**: не използвайте сървъра за известия SimpleX Chat, периодично проверявайте съобщенията във фонов режим (зависи от това колко често използвате приложението). No comment provided by engineer. @@ -381,11 +369,15 @@ **Моля, обърнете внимание**: НЯМА да можете да възстановите или промените паролата, ако я загубите. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **Препоръчително**: токенът на устройството и известията се изпращат до сървъра за уведомяване на SimpleX Chat, но не и съдържанието, размерът на съобщението или от кого е. No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **Внимание**: Незабавните push известия изискват парола, запазена в Keychain. @@ -492,6 +484,14 @@ 1 седмица time interval + + 1-time link + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + No comment provided by engineer. + 5 minutes 5 минути @@ -561,21 +561,11 @@ Откажи смяна на адрес? No comment provided by engineer. - - About SimpleX - За SimpleX - No comment provided by engineer. - About SimpleX Chat За SimpleX Chat No comment provided by engineer. - - About SimpleX address - Повече за SimpleX адреса - No comment provided by engineer. - Accent No comment provided by engineer. @@ -587,6 +577,10 @@ accept incoming call via notification swipe action + + Accept conditions + No comment provided by engineer. + Accept connection request? Приемане на заявка за връзка? @@ -603,6 +597,10 @@ accept contact request via notification swipe action + + Accepted conditions + No comment provided by engineer. + Acknowledged No comment provided by engineer. @@ -620,16 +618,6 @@ Добавете адрес към вашия профил, така че вашите контакти да могат да го споделят с други хора. Актуализацията на профила ще бъде изпратена до вашите контакти. No comment provided by engineer. - - Add contact - Добави контакт - No comment provided by engineer. - - - Add preset servers - Добави предварително зададени сървъри - No comment provided by engineer. - Add profile Добави профил @@ -655,6 +643,14 @@ Добави съобщение при посрещане No comment provided by engineer. + + Added media & file servers + No comment provided by engineer. + + + Added message servers + No comment provided by engineer. + Additional accent No comment provided by engineer. @@ -677,6 +673,14 @@ Промяната на адреса ще бъде прекъсната. Ще се използва старият адрес за получаване. No comment provided by engineer. + + Address or 1-time link? + No comment provided by engineer. + + + Address settings + No comment provided by engineer. + Admins can block a member for all. Администраторите могат да блокират член за всички. @@ -720,6 +724,10 @@ Всички членове на групата ще останат свързани. No comment provided by engineer. + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + No comment provided by engineer. + All messages will be deleted - this cannot be undone! Всички съобщения ще бъдат изтрити - това не може да бъде отменено! @@ -895,6 +903,11 @@ Отговор на повикване No comment provided by engineer. + + Anybody can host servers. + Протокол и код с отворен код – всеки може да оперира собствени сървъри. + No comment provided by engineer. + App build: %@ Компилация на приложението: %@ @@ -1219,7 +1232,8 @@ Cancel Отказ - alert button + alert action + alert button Cancel migration @@ -1300,6 +1314,10 @@ authentication reason set passcode view + + Change user profiles + authentication reason + Chat archive Архив на чата @@ -1380,10 +1398,18 @@ Чатове No comment provided by engineer. + + Check messages every 20 min. + No comment provided by engineer. + + + Check messages when allowed. + No comment provided by engineer. + Check server address and try again. Проверете адреса на сървъра и опитайте отново. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1464,15 +1490,47 @@ Completed No comment provided by engineer. + + Conditions accepted on: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for following operator(s): **%@**. + No comment provided by engineer. + + + Conditions of use + No comment provided by engineer. + + + Conditions will be accepted for enabled operators after 30 days. + No comment provided by engineer. + + + Conditions will be accepted for operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + No comment provided by engineer. + Configure ICE servers Конфигурирай ICE сървъри No comment provided by engineer. - - Configured %@ servers - No comment provided by engineer. - Confirm Потвърди @@ -1653,6 +1711,10 @@ This is your own one-time link! Заявката за връзка е изпратена! No comment provided by engineer. + + Connection security + No comment provided by engineer. + Connection terminated Връзката е прекратена @@ -1760,6 +1822,10 @@ This is your own one-time link! Създай No comment provided by engineer. + + Create 1-time link + No comment provided by engineer. + Create SimpleX address Създай SimpleX адрес @@ -1770,11 +1836,6 @@ This is your own one-time link! Създай група с автоматично генериран профилл. No comment provided by engineer. - - Create an address to let people connect with you. - Създайте адрес, за да позволите на хората да се свързват с вас. - No comment provided by engineer. - Create file Създай файл @@ -1854,6 +1915,10 @@ This is your own one-time link! Текущ kод за достъп No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + No comment provided by engineer. + Current passphrase… Текуща парола… @@ -2005,7 +2070,8 @@ This is your own one-time link! Delete Изтрий - chat item action + alert action + chat item action swipe action @@ -2216,6 +2282,10 @@ This is your own one-time link! Deletion errors No comment provided by engineer. + + Delivered even when Apple drops them. + No comment provided by engineer. + Delivery Доставка @@ -2483,6 +2553,10 @@ This is your own one-time link! Продължителност No comment provided by engineer. + + E2E encrypted notifications. + No comment provided by engineer. + Edit Редактирай @@ -2503,6 +2577,10 @@ This is your own one-time link! Активиране (запазване на промените) No comment provided by engineer. + + Enable Flux + No comment provided by engineer. + Enable SimpleX Lock Активирай SimpleX заключване @@ -2707,6 +2785,10 @@ This is your own one-time link! Грешка при отказване на промяна на адреса No comment provided by engineer. + + Error accepting conditions + alert title + Error accepting contact request Грешка при приемане на заявка за контакт @@ -2722,6 +2804,10 @@ This is your own one-time link! Грешка при добавяне на член(ове) No comment provided by engineer. + + Error adding server + alert title + Error changing address Грешка при промяна на адреса @@ -2858,10 +2944,9 @@ This is your own one-time link! Грешка при присъединяване към група No comment provided by engineer. - - Error loading %@ servers - Грешка при зареждане на %@ сървъри - No comment provided by engineer. + + Error loading servers + alert title Error migrating settings @@ -2894,11 +2979,6 @@ This is your own one-time link! Error resetting statistics No comment provided by engineer. - - Error saving %@ servers - Грешка при запазване на %@ сървъра - No comment provided by engineer. - Error saving ICE servers Грешка при запазване на ICE сървърите @@ -2919,6 +2999,10 @@ This is your own one-time link! Грешка при запазване на парола в Кeychain No comment provided by engineer. + + Error saving servers + alert title + Error saving settings Грешка при запазване на настройките @@ -2988,6 +3072,10 @@ This is your own one-time link! Грешка при актуализиране на съобщението No comment provided by engineer. + + Error updating server + alert title + Error updating settings Грешка при актуализиране на настройките @@ -3032,6 +3120,10 @@ This is your own one-time link! Errors No comment provided by engineer. + + Errors in servers configuration. + servers error + Even when disabled in the conversation. Дори когато е деактивиран в разговора. @@ -3225,11 +3317,27 @@ This is your own one-time link! Поправката не се поддържа от члена на групата No comment provided by engineer. + + For chat profile %@: + servers error + For console За конзолата No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + No comment provided by engineer. + + + For private routing + No comment provided by engineer. + + + For social media + No comment provided by engineer. + Forward Препрати @@ -3525,9 +3633,12 @@ Error: %2$@ Как работи SimpleX No comment provided by engineer. - - How it works - Как работи + + How it affects privacy + No comment provided by engineer. + + + How it helps privacy No comment provided by engineer. @@ -3599,8 +3710,8 @@ Error: %2$@ Веднага No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Защитен от спам и злоупотреби No comment provided by engineer. @@ -3738,6 +3849,11 @@ More improvements are coming soon! Инсталирайте [SimpleX Chat за терминал](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Мигновено + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3745,11 +3861,6 @@ More improvements are coming soon! No comment provided by engineer. - - Instantly - Мигновено - No comment provided by engineer. - Interface Интерфейс @@ -3797,7 +3908,7 @@ More improvements are coming soon! Invalid server address! Невалиден адрес на сървъра! - No comment provided by engineer. + alert title Invalid status @@ -3924,7 +4035,7 @@ This is your link for group %@! Keep Запази - No comment provided by engineer. + alert action Keep conversation @@ -3938,7 +4049,7 @@ This is your link for group %@! Keep unused invitation? Запази неизползваната покана за връзка? - No comment provided by engineer. + alert title Keep your connections @@ -4025,11 +4136,6 @@ This is your link for group %@! Съобщения на живо No comment provided by engineer. - - Local - Локално - No comment provided by engineer. - Local name Локално име @@ -4050,11 +4156,6 @@ This is your link for group %@! Режим на заключване No comment provided by engineer. - - Make a private connection - Добави поверителна връзка - No comment provided by engineer. - Make one message disappear Накарайте едно съобщение да изчезне @@ -4065,21 +4166,11 @@ This is your link for group %@! Направи профила поверителен! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Уверете се, че %@ сървърните адреси са в правилен формат, разделени на редове и не се дублират (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. Уверете се, че адресите на WebRTC ICE сървъра са в правилен формат, разделени на редове и не са дублирани. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Много хора попитаха: *ако SimpleX няма потребителски идентификатори, как може да доставя съобщения?* - No comment provided by engineer. - Mark deleted for everyone Маркирай като изтрито за всички @@ -4344,6 +4435,10 @@ This is your link for group %@! По-надеждна мрежова връзка. No comment provided by engineer. + + More reliable notifications + No comment provided by engineer. + Most likely this connection is deleted. Най-вероятно тази връзка е изтрита. @@ -4379,6 +4474,10 @@ This is your link for group %@! Мрежова връзка No comment provided by engineer. + + Network decentralization + No comment provided by engineer. + Network issues - message expired after many attempts to send it. snd error text @@ -4388,6 +4487,10 @@ This is your link for group %@! Управление на мрежата No comment provided by engineer. + + Network operator + No comment provided by engineer. + Network settings Мрежови настройки @@ -4445,6 +4548,10 @@ This is your link for group %@! Ново име No comment provided by engineer. + + New events + notification + New in %@ Ново в %@ @@ -4469,6 +4576,10 @@ This is your link for group %@! Нова парола… No comment provided by engineer. + + New server + No comment provided by engineer. + No Не @@ -4522,6 +4633,14 @@ This is your link for group %@! No info, try to reload No comment provided by engineer. + + No media & file servers. + servers error + + + No message servers. + servers error + No network connection Няма мрежова връзка @@ -4540,11 +4659,37 @@ This is your link for group %@! Няма разрешение за запис на гласово съобщение No comment provided by engineer. + + No push server + Локално + No comment provided by engineer. + No received or sent files Няма получени или изпратени файлове No comment provided by engineer. + + No servers for private message routing. + servers error + + + No servers to receive files. + servers error + + + No servers to receive messages. + servers error + + + No servers to send files. + servers error + + + No user identifiers. + Първата платформа без никакви потребителски идентификатори – поверителна по дизайн. + No comment provided by engineer. + Not compatible! Несъвместим! @@ -4568,6 +4713,10 @@ This is your link for group %@! Известията са деактивирани! No comment provided by engineer. + + Notifications privacy + No comment provided by engineer. + Now admins can: - delete members' messages. @@ -4626,8 +4775,8 @@ Requires compatible VPN. Няма се използват Onion хостове. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. Само потребителските устройства съхраняват потребителски профили, контакти, групи и съобщения, изпратени с **двуслойно криптиране от край до край**. No comment provided by engineer. @@ -4710,6 +4859,10 @@ Requires compatible VPN. Отвори настройки No comment provided by engineer. + + Open changes + No comment provided by engineer. + Open chat Отвори чат @@ -4720,6 +4873,10 @@ Requires compatible VPN. Отвори конзолата authentication reason + + Open conditions + No comment provided by engineer. + Open group Отвори група @@ -4730,25 +4887,19 @@ Requires compatible VPN. Отвори миграцията към друго устройство authentication reason - - Open server settings - No comment provided by engineer. - - - Open user profiles - Отвори потребителските профили - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Протокол и код с отворен код – всеки може да оперира собствени сървъри. - No comment provided by engineer. - Opening app… Приложението се отваря… No comment provided by engineer. + + Operator + No comment provided by engineer. + + + Operator server + alert title + Or paste archive link Или постави архивен линк @@ -4769,15 +4920,15 @@ Requires compatible VPN. Или покажи този код No comment provided by engineer. + + Or to share privately + No comment provided by engineer. + Other Други No comment provided by engineer. - - Other %@ servers - No comment provided by engineer. - Other file errors: %@ @@ -4856,13 +5007,8 @@ Requires compatible VPN. Pending No comment provided by engineer. - - People can connect to you only via the links you share. - Хората могат да се свържат с вас само чрез ликовете, които споделяте. - No comment provided by engineer. - - - Periodically + + Periodic Периодично No comment provided by engineer. @@ -4980,16 +5126,15 @@ Error: %@ Запазете последната чернова на съобщението с прикачени файлове. No comment provided by engineer. - - Preset server - Предварително зададен сървър - No comment provided by engineer. - Preset server address Предварително зададен адрес на сървъра No comment provided by engineer. + + Preset servers + No comment provided by engineer. + Preview Визуализация @@ -5062,7 +5207,7 @@ Error: %@ Profile update will be sent to your contacts. Актуализацията на профила ще бъде изпратена до вашите контакти. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -5150,6 +5295,10 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. + + Push Notifications + No comment provided by engineer. + Push notifications Push известия @@ -5189,26 +5338,21 @@ Enable in *Network & servers* settings. Прочетете още No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - No comment provided by engineer. - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). Прочетете повече в [Ръководство на потребителя](https://simplex.chat/docs/guide/readme.html#connect-to-friends). No comment provided by engineer. - - Read more in our GitHub repository. - Прочетете повече в нашето хранилище в GitHub. - No comment provided by engineer. - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). Прочетете повече в нашето [GitHub хранилище](https://github.com/simplex-chat/simplex-chat#readme). @@ -5509,6 +5653,14 @@ Enable in *Network & servers* settings. Покажи chat item action + + Review conditions + No comment provided by engineer. + + + Review later + No comment provided by engineer. + Revoke Отзови @@ -5551,6 +5703,14 @@ Enable in *Network & servers* settings. По-безопасни групи No comment provided by engineer. + + Same conditions will apply to operator **%@**. + No comment provided by engineer. + + + Same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + Save Запази @@ -5619,7 +5779,7 @@ Enable in *Network & servers* settings. Save servers? Запази сървърите? - No comment provided by engineer. + alert title Save welcome message? @@ -5818,11 +5978,6 @@ Enable in *Network & servers* settings. Изпращай известия No comment provided by engineer. - - Send notifications: - Изпратени известия: - No comment provided by engineer. - Send questions and ideas Изпращайте въпроси и идеи @@ -5942,6 +6097,10 @@ Enable in *Network & servers* settings. Server No comment provided by engineer. + + Server added to operator %@. + alert message + Server address No comment provided by engineer. @@ -5954,6 +6113,18 @@ Enable in *Network & servers* settings. Server address is incompatible with network settings: %@. No comment provided by engineer. + + Server operator changed. + alert title + + + Server operators + No comment provided by engineer. + + + Server protocol changed. + alert title + Server requires authorization to create queues, check password Сървърът изисква оторизация за създаване на опашки, проверете паролата @@ -6065,22 +6236,35 @@ Enable in *Network & servers* settings. Share Сподели - chat item action + alert action + chat item action Share 1-time link Сподели еднократен линк No comment provided by engineer. + + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + No comment provided by engineer. + Share address Сподели адрес No comment provided by engineer. + + Share address publicly + No comment provided by engineer. + Share address with contacts? Сподели адреса с контактите? - No comment provided by engineer. + alert title Share from other apps. @@ -6190,6 +6374,14 @@ Enable in *Network & servers* settings. SimpleX адрес No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + No comment provided by engineer. + SimpleX contact address SimpleX адрес за контакт @@ -6274,6 +6466,11 @@ Enable in *Network & servers* settings. Some non-fatal errors occurred during import: No comment provided by engineer. + + Some servers failed the test: +%@ + alert message + Somebody Някой @@ -6355,12 +6552,12 @@ Enable in *Network & servers* settings. Stop sharing Спри споделянето - No comment provided by engineer. + alert action Stop sharing address? Спри споделянето на адреса? - No comment provided by engineer. + alert title Stopping chat @@ -6501,7 +6698,7 @@ Enable in *Network & servers* settings. Tests failed! Тестовете са неуспешни! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6518,11 +6715,6 @@ Enable in *Network & servers* settings. Благодарение на потребителите – допринесете през Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - Първата платформа без никакви потребителски идентификатори – поверителна по дизайн. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6535,6 +6727,10 @@ It can happen because of some bug or when the connection is compromised.Приложението може да ви уведоми, когато получите съобщения или заявки за контакт - моля, отворете настройките, за да активирате. No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). No comment provided by engineer. @@ -6549,6 +6745,10 @@ It can happen because of some bug or when the connection is compromised.QR кодът, който сканирахте, не е SimpleX линк за връзка. No comment provided by engineer. + + The connection reached the limit of undelivered messages, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! Връзката, която приехте, ще бъде отказана! @@ -6569,6 +6769,11 @@ It can happen because of some bug or when the connection is compromised.Криптирането работи и новото споразумение за криптиране не е необходимо. Това може да доведе до грешки при свързване! No comment provided by engineer. + + The future of messaging + Ново поколение поверителни съобщения + No comment provided by engineer. + The hash of the previous message is different. Хешът на предишното съобщение е различен. @@ -6592,11 +6797,6 @@ It can happen because of some bug or when the connection is compromised.The messages will be marked as moderated for all members. No comment provided by engineer. - - The next generation of private messaging - Ново поколение поверителни съобщения - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. Старата база данни не бе премахната по време на миграцията, тя може да бъде изтрита. @@ -6607,6 +6807,10 @@ It can happen because of some bug or when the connection is compromised.Профилът се споделя само с вашите контакти. No comment provided by engineer. + + The second preset operator in the app! + No comment provided by engineer. + The second tick we missed! ✅ Втората отметка, която пропуснахме! ✅ @@ -6622,6 +6826,10 @@ It can happen because of some bug or when the connection is compromised.Сървърите за нови връзки на текущия ви чат профил **%@**. No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. Текстът, който поставихте, не е SimpleX линк за връзка. @@ -6635,6 +6843,10 @@ It can happen because of some bug or when the connection is compromised.Themes No comment provided by engineer. + + These conditions will also apply for: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. Тези настройки са за текущия ви профил **%@**. @@ -6733,9 +6945,8 @@ It can happen because of some bug or when the connection is compromised.За да направите нова връзка No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - За да се защити поверителността, вместо потребителски идентификатори, използвани от всички други платформи, SimpleX има идентификатори за опашки от съобщения, отделни за всеки от вашите контакти. + + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -6754,6 +6965,15 @@ You will be prompted to complete authentication before this feature is enabled.< Ще бъдете подканени да извършите идентификация, преди тази функция да бъде активирана. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + За да се защити поверителността, вместо потребителски идентификатори, използвани от всички други платформи, SimpleX има идентификатори за опашки от съобщения, отделни за всеки от вашите контакти. + No comment provided by engineer. + + + To receive + No comment provided by engineer. + To record speech please grant permission to use Microphone. No comment provided by engineer. @@ -6772,11 +6992,19 @@ You will be prompted to complete authentication before this feature is enabled.< За да разкриете своя скрит профил, въведете пълна парола в полето за търсене на страницата **Вашите чат профили**. No comment provided by engineer. + + To send + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. За поддръжка на незабавни push известия, базата данни за чат трябва да бъде мигрирана. No comment provided by engineer. + + To use the servers of **%@**, accept conditions of use. + No comment provided by engineer. + To verify end-to-end encryption with your contact compare (or scan) the code on your devices. За да проверите криптирането от край до край с вашия контакт, сравнете (или сканирайте) кода на вашите устройства. @@ -6863,6 +7091,10 @@ You will be prompted to complete authentication before this feature is enabled.< Отблокирай член? No comment provided by engineer. + + Undelivered messages + No comment provided by engineer. + Unexpected migration state Неочаквано състояние на миграция @@ -7015,6 +7247,10 @@ To connect, please ask your contact to create another connection link and check Архивът се качва No comment provided by engineer. + + Use %@ + No comment provided by engineer. + Use .onion hosts Използвай .onion хостове @@ -7039,6 +7275,14 @@ To connect, please ask your contact to create another connection link and check Използвай текущия профил No comment provided by engineer. + + Use for files + No comment provided by engineer. + + + Use for messages + No comment provided by engineer. + Use for new connections Използвай за нови връзки @@ -7077,6 +7321,10 @@ To connect, please ask your contact to create another connection link and check Използвай сървър No comment provided by engineer. + + Use servers + No comment provided by engineer. + Use the app while in the call. Използвайте приложението по време на разговора. @@ -7164,11 +7412,19 @@ To connect, please ask your contact to create another connection link and check Видео и файлове до 1gb No comment provided by engineer. + + View conditions + No comment provided by engineer. + View security code Виж кода за сигурност No comment provided by engineer. + + View updated conditions + No comment provided by engineer. + Visible history Видима история @@ -7277,9 +7533,8 @@ To connect, please ask your contact to create another connection link and check При свързване на аудио и видео разговори. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - Когато хората искат да се свържат с вас, можете да ги приемете или отхвърлите. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -7432,6 +7687,18 @@ Repeat join request? You can change it in Appearance settings. No comment provided by engineer. + + You can configure operators in Network & servers settings. + No comment provided by engineer. + + + You can configure servers via settings. + No comment provided by engineer. + + + You can create it in user picker. + No comment provided by engineer. + You can create it later Можете да го създадете по-късно @@ -7471,6 +7738,10 @@ Repeat join request? You can send messages to %@ from Archived contacts. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + No comment provided by engineer. + You can set lock screen notification preview via settings. Можете да зададете визуализация на известията на заключен екран през настройките. @@ -7486,11 +7757,6 @@ Repeat join request? Можете да споделите този адрес с вашите контакти, за да им позволите да се свържат с **%@**. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - Можете да споделите адреса си като линк или QR код - всеки може да се свърже с вас. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app Можете да започнете чат през Настройки на приложението / База данни или като рестартирате приложението @@ -7513,23 +7779,23 @@ Repeat join request? You can view invitation link again in connection details. Можете да видите отново линкът за покана в подробностите за връзката. - No comment provided by engineer. + alert message You can't send messages! Не може да изпращате съобщения! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - Вие контролирате през кой сървър(и) **да получавате** съобщенията, вашите контакти – сървърите, които използвате, за да им изпращате съобщения. - No comment provided by engineer. - You could not be verified; please try again. Не можахте да бъдете потвърдени; Моля, опитайте отново. No comment provided by engineer. + + You decide who can connect. + Хората могат да се свържат с вас само чрез ликовете, които споделяте. + No comment provided by engineer. + You have already requested connection via this address! Вече сте заявили връзка през този адрес! @@ -7649,11 +7915,6 @@ Repeat connection request? Използвате инкогнито профил за тази група - за да се предотврати споделянето на основния ви профил, поканите на контакти не са разрешени No comment provided by engineer. - - Your %@ servers - Вашите %@ сървъри - No comment provided by engineer. - Your ICE servers Вашите ICE сървъри @@ -7669,11 +7930,6 @@ Repeat connection request? Вашият SimpleX адрес No comment provided by engineer. - - Your XFTP servers - Вашите XFTP сървъри - No comment provided by engineer. - Your calls Вашите обаждания @@ -7770,16 +8026,15 @@ Repeat connection request? Вашият автоматично генериран профил No comment provided by engineer. - - Your server - Вашият сървър - No comment provided by engineer. - Your server address Вашият адрес на сървъра No comment provided by engineer. + + Your servers + No comment provided by engineer. + Your settings Вашите настройки @@ -8195,6 +8450,10 @@ Repeat connection request? expired No comment provided by engineer. + + for better metadata privacy. + No comment provided by engineer. + forwarded препратено @@ -8802,6 +9061,33 @@ last received msg: %2$@ + +
+ +
+ + + %d new events + notification body + + + From: %@ + notification body + + + New events + notification + + + New messages + notification + + + New messages in %d chats + notification body + + +

u6|ATCu^_I;OM(rSn!ZCDR@HWjYlc3ZV8`_t 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 z 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 K;{QH={eJ+)h{*r| literal 0 HcmV?d00001 diff --git a/blog/images/20241125-operators-3.png b/blog/images/20241125-operators-3.png new file mode 100644 index 0000000000000000000000000000000000000000..7ae26ef35024a385c4bb9427a6070206e6f9e9b2 GIT binary patch literal 78223 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%0AcxPh*Ie8RJ%&Zruajm?A_`;+5o!|L2^XG9 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

diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/bg.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff b/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff index b92196b78b..d6fb9a40a4 100644 --- a/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff +++ b/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff @@ -193,20 +193,16 @@ ) No comment provided by engineer. - - **Add new contact**: to create your one-time QR Code or link for your contact. - No comment provided by engineer. - **Create link / QR code** for your contact to use. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. No comment provided by engineer. @@ -217,8 +213,8 @@ **Please note**: you will NOT be able to recover or change passphrase if you lose it. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. No comment provided by engineer. @@ -1899,8 +1895,8 @@ Immediately No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam No comment provided by engineer. @@ -2409,8 +2405,8 @@ Onion hosts will not be used. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. No comment provided by engineer. @@ -2477,8 +2473,8 @@ Open user profiles authentication reason - - Open-source protocol and code – anybody can run the servers. + + Anybody can host servers. No comment provided by engineer. @@ -2537,8 +2533,8 @@ Paste the link you received into the box below to connect with your contact. No comment provided by engineer. - - People can connect to you only via the links you share. + + You decide who can connect. No comment provided by engineer. @@ -3373,8 +3369,8 @@ Thanks to the users – contribute via Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. + + No user identifiers. No comment provided by engineer. @@ -3418,8 +3414,8 @@ It can happen because of some bug or when the connection is compromised.The message will be marked as moderated for all members. No comment provided by engineer. - - The next generation of private messaging + + The future of messaging No comment provided by engineer. @@ -3490,8 +3486,8 @@ It can happen because of some bug or when the connection is compromised.To make a new connection No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. No comment provided by engineer. @@ -3876,10 +3872,6 @@ To connect, please ask your contact to create another connection link and check You can't send messages! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - No comment provided by engineer. - You could not be verified; please try again. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index af56b6631f..79cb15d1ae 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -12,21 +12,6 @@ No comment provided by engineer. - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - ( ( @@ -126,6 +111,14 @@ %@ je ověřený No comment provided by engineer. + + %@ server + No comment provided by engineer. + + + %@ servers + No comment provided by engineer. + %@ uploaded No comment provided by engineer. @@ -336,26 +329,21 @@ ) No comment provided by engineer. - - **Add contact**: to create a new invitation link, or connect via a link you received. - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Přidat nový kontakt**: pro vytvoření jednorázového QR kódu nebo odkazu pro váš kontakt. + + **Create 1-time link**: to create and share a new invitation link. No comment provided by engineer. **Create group**: to create a new group. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **Soukromější**: kontrolovat nové zprávy každých 20 minut. Token zařízení je sdílen se serverem SimpleX Chat, ale ne kolik máte kontaktů nebo zpráv. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **Nejsoukromější**: nepoužívejte server oznámení SimpleX Chat, pravidelně kontrolujte zprávy na pozadí (závisí na tom, jak často aplikaci používáte). No comment provided by engineer. @@ -368,11 +356,15 @@ **Upozornění**: Pokud heslo ztratíte, NEBUDETE jej moci obnovit ani změnit. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **Doporučeno**: Token zařízení a oznámení se odesílají na oznamovací server SimpleX Chat, ale nikoli obsah, velikost nebo od koho jsou zprávy. No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **Upozornění**: Okamžitě doručovaná oznámení vyžadují přístupové heslo uložené v Klíčence. @@ -474,6 +466,14 @@ 1 týden time interval + + 1-time link + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + No comment provided by engineer. + 5 minutes 5 minut @@ -543,21 +543,11 @@ Přerušit změnu adresy? No comment provided by engineer. - - About SimpleX - O SimpleX - No comment provided by engineer. - About SimpleX Chat O SimpleX chat No comment provided by engineer. - - About SimpleX address - O SimpleX adrese - No comment provided by engineer. - Accent No comment provided by engineer. @@ -569,6 +559,10 @@ accept incoming call via notification swipe action + + Accept conditions + No comment provided by engineer. + Accept connection request? Přijmout kontakt? @@ -585,6 +579,10 @@ accept contact request via notification swipe action + + Accepted conditions + No comment provided by engineer. + Acknowledged No comment provided by engineer. @@ -602,15 +600,6 @@ Přidejte adresu do svého profilu, aby ji vaše kontakty mohly sdílet s dalšími lidmi. Aktualizace profilu bude zaslána vašim kontaktům. No comment provided by engineer. - - Add contact - No comment provided by engineer. - - - Add preset servers - Přidejte přednastavené servery - No comment provided by engineer. - Add profile Přidat profil @@ -636,6 +625,14 @@ Přidat uvítací zprávu No comment provided by engineer. + + Added media & file servers + No comment provided by engineer. + + + Added message servers + No comment provided by engineer. + Additional accent No comment provided by engineer. @@ -658,6 +655,14 @@ Změna adresy bude přerušena. Budou použity staré přijímací adresy. No comment provided by engineer. + + Address or 1-time link? + No comment provided by engineer. + + + Address settings + No comment provided by engineer. + Admins can block a member for all. No comment provided by engineer. @@ -700,6 +705,10 @@ Všichni členové skupiny zůstanou připojeni. No comment provided by engineer. + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + No comment provided by engineer. + All messages will be deleted - this cannot be undone! No comment provided by engineer. @@ -869,6 +878,11 @@ Přijmout hovor No comment provided by engineer. + + Anybody can host servers. + Servery může provozovat kdokoli. + No comment provided by engineer. + App build: %@ Sestavení aplikace: %@ @@ -1179,7 +1193,8 @@ Cancel Zrušit - alert button + alert action + alert button Cancel migration @@ -1258,6 +1273,10 @@ authentication reason set passcode view + + Change user profiles + authentication reason + Chat archive Chat se archivuje @@ -1336,10 +1355,18 @@ Chaty No comment provided by engineer. + + Check messages every 20 min. + No comment provided by engineer. + + + Check messages when allowed. + No comment provided by engineer. + Check server address and try again. Zkontrolujte adresu serveru a zkuste to znovu. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1418,15 +1445,47 @@ Completed No comment provided by engineer. + + Conditions accepted on: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for following operator(s): **%@**. + No comment provided by engineer. + + + Conditions of use + No comment provided by engineer. + + + Conditions will be accepted for enabled operators after 30 days. + No comment provided by engineer. + + + Conditions will be accepted for operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + No comment provided by engineer. + Configure ICE servers Konfigurace serverů ICE No comment provided by engineer. - - Configured %@ servers - No comment provided by engineer. - Confirm Potvrdit @@ -1592,6 +1651,10 @@ This is your own one-time link! Požadavek na připojení byl odeslán! No comment provided by engineer. + + Connection security + No comment provided by engineer. + Connection terminated No comment provided by engineer. @@ -1697,6 +1760,10 @@ This is your own one-time link! Vytvořit No comment provided by engineer. + + Create 1-time link + No comment provided by engineer. + Create SimpleX address Vytvořit SimpleX adresu @@ -1706,11 +1773,6 @@ This is your own one-time link! Create a group using a random profile. No comment provided by engineer. - - Create an address to let people connect with you. - Vytvořit adresu, aby se s vámi lidé mohli spojit. - No comment provided by engineer. - Create file Vytvořit soubor @@ -1784,6 +1846,10 @@ This is your own one-time link! Aktuální heslo No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + No comment provided by engineer. + Current passphrase… Aktuální přístupová fráze… @@ -1935,7 +2001,8 @@ This is your own one-time link! Delete Smazat - chat item action + alert action + chat item action swipe action @@ -2143,6 +2210,10 @@ This is your own one-time link! Deletion errors No comment provided by engineer. + + Delivered even when Apple drops them. + No comment provided by engineer. + Delivery Doručenka @@ -2400,6 +2471,10 @@ This is your own one-time link! Trvání No comment provided by engineer. + + E2E encrypted notifications. + No comment provided by engineer. + Edit Upravit @@ -2420,6 +2495,10 @@ This is your own one-time link! Povolit (zachovat přepsání) No comment provided by engineer. + + Enable Flux + No comment provided by engineer. + Enable SimpleX Lock Zapnutí zámku SimpleX @@ -2614,6 +2693,10 @@ This is your own one-time link! Chyba přerušení změny adresy No comment provided by engineer. + + Error accepting conditions + alert title + Error accepting contact request Chyba při přijímání žádosti o kontakt @@ -2629,6 +2712,10 @@ This is your own one-time link! Chyba přidávání člena(ů) No comment provided by engineer. + + Error adding server + alert title + Error changing address Chuba změny adresy @@ -2763,10 +2850,9 @@ This is your own one-time link! Chyba při připojování ke skupině No comment provided by engineer. - - Error loading %@ servers - Chyba načítání %@ serverů - No comment provided by engineer. + + Error loading servers + alert title Error migrating settings @@ -2798,11 +2884,6 @@ This is your own one-time link! Error resetting statistics No comment provided by engineer. - - Error saving %@ servers - Chyba při ukládání serverů %@ - No comment provided by engineer. - Error saving ICE servers Chyba při ukládání serverů ICE @@ -2823,6 +2904,10 @@ This is your own one-time link! Při ukládání přístupové fráze do klíčenky došlo k chybě No comment provided by engineer. + + Error saving servers + alert title + Error saving settings when migrating @@ -2890,6 +2975,10 @@ This is your own one-time link! Chyba aktualizace zprávy No comment provided by engineer. + + Error updating server + alert title + Error updating settings Chyba při aktualizaci nastavení @@ -2932,6 +3021,10 @@ This is your own one-time link! Errors No comment provided by engineer. + + Errors in servers configuration. + servers error + Even when disabled in the conversation. I při vypnutí v konverzaci. @@ -3119,11 +3212,27 @@ This is your own one-time link! Opravit nepodporované členem skupiny No comment provided by engineer. + + For chat profile %@: + servers error + For console Pro konzoli No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + No comment provided by engineer. + + + For private routing + No comment provided by engineer. + + + For social media + No comment provided by engineer. + Forward chat item action @@ -3409,9 +3518,12 @@ Error: %2$@ Jak SimpleX funguje No comment provided by engineer. - - How it works - Jak to funguje + + How it affects privacy + No comment provided by engineer. + + + How it helps privacy No comment provided by engineer. @@ -3482,8 +3594,8 @@ Error: %2$@ Ihned No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Odolná vůči spamu a zneužití No comment provided by engineer. @@ -3614,6 +3726,11 @@ More improvements are coming soon! Nainstalujte [SimpleX Chat pro terminál](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Okamžitě + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3621,11 +3738,6 @@ More improvements are coming soon! No comment provided by engineer. - - Instantly - Okamžitě - No comment provided by engineer. - Interface Rozhranní @@ -3667,7 +3779,7 @@ More improvements are coming soon! Invalid server address! Neplatná adresa serveru! - No comment provided by engineer. + alert title Invalid status @@ -3788,7 +3900,7 @@ This is your link for group %@! Keep - No comment provided by engineer. + alert action Keep conversation @@ -3800,7 +3912,7 @@ This is your link for group %@! Keep unused invitation? - No comment provided by engineer. + alert title Keep your connections @@ -3884,11 +3996,6 @@ This is your link for group %@! Živé zprávy No comment provided by engineer. - - Local - Místní - No comment provided by engineer. - Local name Místní název @@ -3909,11 +4016,6 @@ This is your link for group %@! Režim zámku No comment provided by engineer. - - Make a private connection - Vytvořte si soukromé připojení - No comment provided by engineer. - Make one message disappear Nechat jednu zprávu zmizet @@ -3924,21 +4026,11 @@ This is your link for group %@! Změnit profil na soukromý! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Ujistěte se, že adresy %@ serverů jsou ve správném formátu, oddělené řádky a nejsou duplicitní (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. Ujistěte se, že adresy serverů WebRTC ICE jsou ve správném formátu, oddělené na řádcích a nejsou duplicitní. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Mnoho lidí se ptalo: *Pokud SimpleX nemá žádné uživatelské identifikátory, jak může doručovat zprávy?* - No comment provided by engineer. - Mark deleted for everyone Označit jako smazané pro všechny @@ -4190,6 +4282,10 @@ This is your link for group %@! More reliable network connection. No comment provided by engineer. + + More reliable notifications + No comment provided by engineer. + Most likely this connection is deleted. Pravděpodobně je toto spojení smazáno. @@ -4224,6 +4320,10 @@ This is your link for group %@! Network connection No comment provided by engineer. + + Network decentralization + No comment provided by engineer. + Network issues - message expired after many attempts to send it. snd error text @@ -4232,6 +4332,10 @@ This is your link for group %@! Network management No comment provided by engineer. + + Network operator + No comment provided by engineer. + Network settings Nastavení sítě @@ -4288,6 +4392,10 @@ This is your link for group %@! Nově zobrazované jméno No comment provided by engineer. + + New events + notification + New in %@ Nový V %@ @@ -4312,6 +4420,10 @@ This is your link for group %@! Nová přístupová fráze… No comment provided by engineer. + + New server + No comment provided by engineer. + No Ne @@ -4365,6 +4477,14 @@ This is your link for group %@! No info, try to reload No comment provided by engineer. + + No media & file servers. + servers error + + + No message servers. + servers error + No network connection No comment provided by engineer. @@ -4382,11 +4502,37 @@ This is your link for group %@! Nemáte oprávnění nahrávat hlasové zprávy No comment provided by engineer. + + No push server + Místní + No comment provided by engineer. + No received or sent files Žádné přijaté ani odeslané soubory No comment provided by engineer. + + No servers for private message routing. + servers error + + + No servers to receive files. + servers error + + + No servers to receive messages. + servers error + + + No servers to send files. + servers error + + + No user identifiers. + Bez uživatelských identifikátorů + No comment provided by engineer. + Not compatible! No comment provided by engineer. @@ -4409,6 +4555,10 @@ This is your link for group %@! Oznámení jsou zakázána! No comment provided by engineer. + + Notifications privacy + No comment provided by engineer. + Now admins can: - delete members' messages. @@ -4466,8 +4616,8 @@ Vyžaduje povolení sítě VPN. Onion hostitelé nebudou použiti. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. Pouze klientská zařízení ukládají uživatelské profily, kontakty, skupiny a zprávy odeslané s **2vrstvým šifrováním typu end-to-end**. No comment provided by engineer. @@ -4550,6 +4700,10 @@ Vyžaduje povolení sítě VPN. Otevřít nastavení No comment provided by engineer. + + Open changes + No comment provided by engineer. + Open chat Otevřete chat @@ -4560,6 +4714,10 @@ Vyžaduje povolení sítě VPN. Otevřete konzolu chatu authentication reason + + Open conditions + No comment provided by engineer. + Open group No comment provided by engineer. @@ -4568,24 +4726,18 @@ Vyžaduje povolení sítě VPN. Open migration to another device authentication reason - - Open server settings - No comment provided by engineer. - - - Open user profiles - Otevřít uživatelské profily - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Protokol a kód s otevřeným zdrojovým kódem - servery může provozovat kdokoli. - No comment provided by engineer. - Opening app… No comment provided by engineer. + + Operator + No comment provided by engineer. + + + Operator server + alert title + Or paste archive link No comment provided by engineer. @@ -4602,12 +4754,12 @@ Vyžaduje povolení sítě VPN. Or show this code No comment provided by engineer. - - Other + + Or to share privately No comment provided by engineer. - - Other %@ servers + + Other No comment provided by engineer. @@ -4684,13 +4836,8 @@ Vyžaduje povolení sítě VPN. Pending No comment provided by engineer. - - People can connect to you only via the links you share. - Lidé se s vámi mohou spojit pouze prostřednictvím odkazů, které sdílíte. - No comment provided by engineer. - - - Periodically + + Periodic Pravidelně No comment provided by engineer. @@ -4804,16 +4951,15 @@ Error: %@ Zachování posledního návrhu zprávy s přílohami. No comment provided by engineer. - - Preset server - Přednastavený server - No comment provided by engineer. - Preset server address Přednastavená adresa serveru No comment provided by engineer. + + Preset servers + No comment provided by engineer. + Preview Náhled @@ -4884,7 +5030,7 @@ Error: %@ Profile update will be sent to your contacts. Aktualizace profilu bude zaslána vašim kontaktům. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -4971,6 +5117,10 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. + + Push Notifications + No comment provided by engineer. + Push notifications Nabízená oznámení @@ -5008,25 +5158,20 @@ Enable in *Network & servers* settings. Přečíst více No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - Další informace naleznete v [Uživatelské příručce](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - No comment provided by engineer. - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + Další informace naleznete v [Uživatelské příručce](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). Přečtěte si více v [Uživatelské příručce](https://simplex.chat/docs/guide/readme.html#connect-to-friends). 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. - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). Přečtěte si více v našem [GitHub repozitáři](https://github.com/simplex-chat/simplex-chat#readme). @@ -5319,6 +5464,14 @@ Enable in *Network & servers* settings. Odhalit chat item action + + Review conditions + No comment provided by engineer. + + + Review later + No comment provided by engineer. + Revoke Odvolat @@ -5360,6 +5513,14 @@ Enable in *Network & servers* settings. Safer groups No comment provided by engineer. + + Same conditions will apply to operator **%@**. + No comment provided by engineer. + + + Same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + Save Uložit @@ -5428,7 +5589,7 @@ Enable in *Network & servers* settings. Save servers? Uložit servery? - No comment provided by engineer. + alert title Save welcome message? @@ -5621,11 +5782,6 @@ Enable in *Network & servers* settings. Odeslat oznámení No comment provided by engineer. - - Send notifications: - Odeslat oznámení: - No comment provided by engineer. - Send questions and ideas Zasílání otázek a nápadů @@ -5744,6 +5900,10 @@ Enable in *Network & servers* settings. Server No comment provided by engineer. + + Server added to operator %@. + alert message + Server address No comment provided by engineer. @@ -5756,6 +5916,18 @@ Enable in *Network & servers* settings. Server address is incompatible with network settings: %@. No comment provided by engineer. + + Server operator changed. + alert title + + + Server operators + No comment provided by engineer. + + + Server protocol changed. + alert title + Server requires authorization to create queues, check password Server vyžaduje autorizaci pro vytváření front, zkontrolujte heslo @@ -5864,22 +6036,35 @@ Enable in *Network & servers* settings. Share Sdílet - chat item action + alert action + chat item action Share 1-time link Sdílet jednorázovou pozvánku No comment provided by engineer. + + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + No comment provided by engineer. + Share address Sdílet adresu No comment provided by engineer. + + Share address publicly + No comment provided by engineer. + Share address with contacts? Sdílet adresu s kontakty? - No comment provided by engineer. + alert title Share from other apps. @@ -5987,6 +6172,14 @@ Enable in *Network & servers* settings. Adresa SimpleX No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + No comment provided by engineer. + SimpleX contact address SimpleX kontaktní adresa @@ -6069,6 +6262,11 @@ Enable in *Network & servers* settings. Some non-fatal errors occurred during import: No comment provided by engineer. + + Some servers failed the test: +%@ + alert message + Somebody Někdo @@ -6147,12 +6345,12 @@ Enable in *Network & servers* settings. Stop sharing Přestat sdílet - No comment provided by engineer. + alert action Stop sharing address? Přestat sdílet adresu? - No comment provided by engineer. + alert title Stopping chat @@ -6289,7 +6487,7 @@ Enable in *Network & servers* settings. Tests failed! Testy selhaly! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6306,11 +6504,6 @@ Enable in *Network & servers* settings. Díky uživatelům - přispívejte prostřednictvím Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - 1. Platforma bez identifikátorů uživatelů - soukromá už od záměru. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6323,6 +6516,10 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Aplikace vás může upozornit na přijaté zprávy nebo žádosti o kontakt - povolte to v nastavení. No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). No comment provided by engineer. @@ -6336,6 +6533,10 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován The code you scanned is not a SimpleX link QR code. No comment provided by engineer. + + The connection reached the limit of undelivered messages, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! Připojení, které jste přijali, bude zrušeno! @@ -6356,6 +6557,11 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Š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 future of messaging + Nová generace soukromých zpráv + No comment provided by engineer. + The hash of the previous message is different. Hash předchozí zprávy se liší. @@ -6379,11 +6585,6 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován The messages will be marked as moderated for all members. No comment provided by engineer. - - The next generation of private messaging - Nová generace soukromých zpráv - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. Stará databáze nebyla během přenášení odstraněna, lze ji smazat. @@ -6394,6 +6595,10 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Profil je sdílen pouze s vašimi kontakty. No comment provided by engineer. + + The second preset operator in the app! + No comment provided by engineer. + The second tick we missed! ✅ Druhé zaškrtnutí jsme přehlédli! ✅ @@ -6409,6 +6614,10 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Servery pro nová připojení vašeho aktuálního chat profilu **%@**. No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. No comment provided by engineer. @@ -6421,6 +6630,10 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Themes No comment provided by engineer. + + These conditions will also apply for: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. Toto nastavení je pro váš aktuální profil **%@**. @@ -6512,9 +6725,8 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Vytvoření nového připojení No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Pro ochranu soukromí namísto ID uživatelů používaných všemi ostatními platformami má SimpleX identifikátory pro fronty zpráv, oddělené pro každý z vašich kontaktů. + + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -6533,6 +6745,15 @@ You will be prompted to complete authentication before this feature is enabled.< Před zapnutím této funkce budete vyzváni k dokončení ověření. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + Pro ochranu soukromí namísto ID uživatelů používaných všemi ostatními platformami má SimpleX identifikátory pro fronty zpráv, oddělené pro každý z vašich kontaktů. + No comment provided by engineer. + + + To receive + No comment provided by engineer. + To record speech please grant permission to use Microphone. No comment provided by engineer. @@ -6551,11 +6772,19 @@ Před zapnutím této funkce budete vyzváni k dokončení ověření. Chcete-li odhalit svůj skrytý profil, zadejte celé heslo do vyhledávacího pole na stránce **Chat profily**. No comment provided by engineer. + + To send + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. Pro podporu doručování okamžitých upozornění musí být přenesena chat databáze. No comment provided by engineer. + + To use the servers of **%@**, accept conditions of use. + No comment provided by engineer. + To verify end-to-end encryption with your contact compare (or scan) the code on your devices. Chcete-li ověřit koncové šifrování u svého kontaktu, porovnejte (nebo naskenujte) kód na svých zařízeních. @@ -6636,6 +6865,10 @@ Před zapnutím této funkce budete vyzváni k dokončení ověření. Unblock member? No comment provided by engineer. + + Undelivered messages + No comment provided by engineer. + Unexpected migration state Neočekávaný stav přenášení @@ -6783,6 +7016,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Uploading archive No comment provided by engineer. + + Use %@ + No comment provided by engineer. + Use .onion hosts Použít hostitele .onion @@ -6807,6 +7044,14 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Použít aktuální profil No comment provided by engineer. + + Use for files + No comment provided by engineer. + + + Use for messages + No comment provided by engineer. + Use for new connections Použít pro nová připojení @@ -6843,6 +7088,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Použít server No comment provided by engineer. + + Use servers + No comment provided by engineer. + Use the app while in the call. No comment provided by engineer. @@ -6923,11 +7172,19 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Videa a soubory až do velikosti 1 gb No comment provided by engineer. + + View conditions + No comment provided by engineer. + View security code Zobrazení bezpečnostního kódu No comment provided by engineer. + + View updated conditions + No comment provided by engineer. + Visible history chat feature @@ -7030,9 +7287,8 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu When connecting audio and video calls. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - Když někdo požádá o připojení, můžete žádost přijmout nebo odmítnout. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -7171,6 +7427,18 @@ Repeat join request? You can change it in Appearance settings. No comment provided by engineer. + + You can configure operators in Network & servers settings. + No comment provided by engineer. + + + You can configure servers via settings. + No comment provided by engineer. + + + You can create it in user picker. + No comment provided by engineer. + You can create it later Můžete vytvořit později @@ -7208,6 +7476,10 @@ Repeat join request? You can send messages to %@ from Archived contacts. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + No comment provided by engineer. + You can set lock screen notification preview via settings. Náhled oznámení na zamykací obrazovce můžete změnit v nastavení. @@ -7223,11 +7495,6 @@ Repeat join request? Tuto adresu můžete sdílet s vašimi kontakty, abyse se mohli spojit s **%@**. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - Můžete sdílet svou adresu jako odkaz nebo jako QR kód - kdokoli se k vám bude moci připojit. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app Chat můžete zahájit prostřednictvím aplikace Nastavení / Databáze nebo restartováním aplikace @@ -7249,23 +7516,23 @@ Repeat join request? You can view invitation link again in connection details. - No comment provided by engineer. + alert message You can't send messages! Nemůžete posílat zprávy! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - Sami řídíte, přes který server(y) **přijímat** zprávy, své kontakty – servery, které používáte k odesílání zpráv. - No comment provided by engineer. - You could not be verified; please try again. Nemohli jste být ověřeni; Zkuste to prosím znovu. No comment provided by engineer. + + You decide who can connect. + Lidé se s vámi mohou spojit pouze prostřednictvím odkazu, který sdílíte. + No comment provided by engineer. + You have already requested connection via this address! No comment provided by engineer. @@ -7380,11 +7647,6 @@ Repeat connection request? Pro tuto skupinu používáte inkognito profil - abyste zabránili sdílení svého hlavního profilu, není pozvání kontaktů povoleno No comment provided by engineer. - - Your %@ servers - Vaše servery %@ - No comment provided by engineer. - Your ICE servers Vaše servery ICE @@ -7400,11 +7662,6 @@ Repeat connection request? Vaše SimpleX adresa No comment provided by engineer. - - Your XFTP servers - Vaše XFTP servery - No comment provided by engineer. - Your calls Vaše hovory @@ -7500,16 +7757,15 @@ Repeat connection request? Váš náhodný profil No comment provided by engineer. - - Your server - Váš server - No comment provided by engineer. - Your server address Adresa vašeho serveru No comment provided by engineer. + + Your servers + No comment provided by engineer. + Your settings Vaše nastavení @@ -7915,6 +8171,10 @@ Repeat connection request? expired No comment provided by engineer. + + for better metadata privacy. + No comment provided by engineer. + forwarded No comment provided by engineer. @@ -8503,6 +8763,33 @@ last received msg: %2$@ + +
+ +
+ + + %d new events + notification body + + + From: %@ + notification body + + + New events + notification + + + New messages + notification + + + New messages in %d chats + notification body + + +
diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/cs.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 7ab1e3a588..743c08ed00 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -12,21 +12,6 @@ No comment provided by engineer. - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - ( ( @@ -127,6 +112,14 @@ %@ wurde erfolgreich überprüft No comment provided by engineer. + + %@ server + No comment provided by engineer. + + + %@ servers + No comment provided by engineer. + %@ uploaded %@ hochgeladen @@ -352,14 +345,9 @@ ) No comment provided by engineer. - - **Add contact**: to create a new invitation link, or connect via a link you received. - **Kontakt hinzufügen**: Um einen neuen Einladungslink zu erstellen oder eine Verbindung über einen Link herzustellen, den Sie erhalten haben. - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Neuen Kontakt hinzufügen**: Um einen Einmal-QR-Code oder -Link für Ihren Kontakt zu erzeugen. + + **Create 1-time link**: to create and share a new invitation link. + **Kontakt hinzufügen**: Um einen neuen Einladungslink zu erstellen. No comment provided by engineer. @@ -367,13 +355,13 @@ **Gruppe erstellen**: Um eine neue Gruppe zu erstellen. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **Mehr Privatsphäre**: Es wird alle 20 Minuten auf neue Nachrichten geprüft. Nur Ihr Geräte-Token wird dem SimpleX-Chat-Server mitgeteilt, aber nicht wie viele Kontakte Sie haben oder welche Nachrichten Sie empfangen. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **Beste Privatsphäre**: Es wird kein SimpleX-Chat-Benachrichtigungs-Server genutzt, Nachrichten werden in periodischen Abständen im Hintergrund geprüft (dies hängt davon ab, wie häufig Sie die App nutzen). No comment provided by engineer. @@ -387,11 +375,15 @@ **Bitte beachten Sie**: Das Passwort kann NICHT wiederhergestellt oder geändert werden, wenn Sie es vergessen haben oder verlieren. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **Empfohlen**: Nur Ihr Geräte-Token und ihre Benachrichtigungen werden an den SimpleX-Chat-Benachrichtigungs-Server gesendet, aber weder der Nachrichteninhalt noch deren Größe oder von wem sie gesendet wurde. No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **Warnung**: Sofortige Push-Benachrichtigungen erfordern die Eingabe eines Passworts, welches in Ihrem Schlüsselbund gespeichert ist. @@ -498,6 +490,14 @@ wöchentlich time interval + + 1-time link + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + No comment provided by engineer. + 5 minutes 5 Minuten @@ -567,21 +567,11 @@ Wechsel der Empfängeradresse beenden? No comment provided by engineer. - - About SimpleX - Über SimpleX - No comment provided by engineer. - About SimpleX Chat Über SimpleX Chat No comment provided by engineer. - - About SimpleX address - Über die SimpleX-Adresse - No comment provided by engineer. - Accent Akzent @@ -594,6 +584,10 @@ accept incoming call via notification swipe action + + Accept conditions + No comment provided by engineer. + Accept connection request? Kontaktanfrage annehmen? @@ -610,6 +604,10 @@ accept contact request via notification swipe action + + Accepted conditions + No comment provided by engineer. + Acknowledged Bestätigt @@ -630,16 +628,6 @@ Fügen Sie die Adresse Ihrem Profil hinzu, damit Ihre Kontakte sie mit anderen Personen teilen können. Es wird eine Profilaktualisierung an Ihre Kontakte gesendet. No comment provided by engineer. - - Add contact - Kontakt hinzufügen - No comment provided by engineer. - - - Add preset servers - Füge voreingestellte Server hinzu - No comment provided by engineer. - Add profile Profil hinzufügen @@ -665,6 +653,14 @@ Begrüßungsmeldung hinzufügen No comment provided by engineer. + + Added media & file servers + No comment provided by engineer. + + + Added message servers + No comment provided by engineer. + Additional accent Erste Akzentfarbe @@ -690,6 +686,14 @@ Der Wechsel der Empfängeradresse wird beendet. Die bisherige Adresse wird weiter verwendet. No comment provided by engineer. + + Address or 1-time link? + No comment provided by engineer. + + + Address settings + No comment provided by engineer. + Admins can block a member for all. Administratoren können ein Gruppenmitglied für Alle blockieren. @@ -735,6 +739,10 @@ Alle Gruppenmitglieder bleiben verbunden. No comment provided by engineer. + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + No comment provided by engineer. + All messages will be deleted - this cannot be undone! Es werden alle Nachrichten gelöscht. Dies kann nicht rückgängig gemacht werden! @@ -915,6 +923,11 @@ Anruf annehmen No comment provided by engineer. + + Anybody can host servers. + Jeder kann seine eigenen Server aufsetzen. + No comment provided by engineer. + App build: %@ App Build: %@ @@ -1258,7 +1271,8 @@ Cancel Abbrechen - alert button + alert action + alert button Cancel migration @@ -1341,6 +1355,10 @@ authentication reason set passcode view + + Change user profiles + authentication reason + Chat archive Datenbank Archiv @@ -1426,10 +1444,18 @@ Chats No comment provided by engineer. + + Check messages every 20 min. + No comment provided by engineer. + + + Check messages when allowed. + No comment provided by engineer. + Check server address and try again. Überprüfen Sie die Serveradresse und versuchen Sie es nochmal. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1516,16 +1542,47 @@ Abgeschlossen No comment provided by engineer. + + Conditions accepted on: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for following operator(s): **%@**. + No comment provided by engineer. + + + Conditions of use + No comment provided by engineer. + + + Conditions will be accepted for enabled operators after 30 days. + No comment provided by engineer. + + + Conditions will be accepted for operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + No comment provided by engineer. + Configure ICE servers ICE-Server konfigurieren No comment provided by engineer. - - Configured %@ servers - Konfigurierte %@ Server - No comment provided by engineer. - Confirm Bestätigen @@ -1715,6 +1772,10 @@ Das ist Ihr eigener Einmal-Link! Verbindungsanfrage wurde gesendet! No comment provided by engineer. + + Connection security + No comment provided by engineer. + Connection terminated Verbindung beendet @@ -1830,6 +1891,10 @@ Das ist Ihr eigener Einmal-Link! Erstellen No comment provided by engineer. + + Create 1-time link + No comment provided by engineer. + Create SimpleX address SimpleX-Adresse erstellen @@ -1840,11 +1905,6 @@ Das ist Ihr eigener Einmal-Link! 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. - No comment provided by engineer. - Create file Datei erstellen @@ -1925,6 +1985,10 @@ Das ist Ihr eigener Einmal-Link! Aktueller Zugangscode No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + No comment provided by engineer. + Current passphrase… Aktuelles Passwort… @@ -2081,7 +2145,8 @@ Das ist Ihr eigener Einmal-Link! Delete Löschen - chat item action + alert action + chat item action swipe action @@ -2299,6 +2364,10 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Löschen No comment provided by engineer. + + Delivered even when Apple drops them. + No comment provided by engineer. + Delivery Zustellung @@ -2580,6 +2649,10 @@ Das ist Ihr eigener Einmal-Link! Dauer No comment provided by engineer. + + E2E encrypted notifications. + No comment provided by engineer. + Edit Bearbeiten @@ -2600,6 +2673,10 @@ Das ist Ihr eigener Einmal-Link! Aktivieren (vorgenommene Einstellungen bleiben erhalten) No comment provided by engineer. + + Enable Flux + No comment provided by engineer. + Enable SimpleX Lock SimpleX-Sperre aktivieren @@ -2805,6 +2882,10 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Beenden des Adresswechsels No comment provided by engineer. + + Error accepting conditions + alert title + Error accepting contact request Fehler beim Annehmen der Kontaktanfrage @@ -2820,6 +2901,10 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Hinzufügen von Mitgliedern No comment provided by engineer. + + Error adding server + alert title + Error changing address Fehler beim Wechseln der Empfängeradresse @@ -2960,10 +3045,9 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Beitritt zur Gruppe No comment provided by engineer. - - Error loading %@ servers - Fehler beim Laden von %@ Servern - No comment provided by engineer. + + Error loading servers + alert title Error migrating settings @@ -3000,11 +3084,6 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Zurücksetzen der Statistiken No comment provided by engineer. - - Error saving %@ servers - Fehler beim Speichern der %@-Server - No comment provided by engineer. - Error saving ICE servers Fehler beim Speichern der ICE-Server @@ -3025,6 +3104,10 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Speichern des Passworts in den Schlüsselbund No comment provided by engineer. + + Error saving servers + alert title + Error saving settings Fehler beim Abspeichern der Einstellungen @@ -3095,6 +3178,10 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Aktualisieren der Nachricht No comment provided by engineer. + + Error updating server + alert title + Error updating settings Fehler beim Aktualisieren der Einstellungen @@ -3140,6 +3227,10 @@ Das ist Ihr eigener Einmal-Link! Fehler No comment provided by engineer. + + Errors in servers configuration. + servers error + Even when disabled in the conversation. Auch wenn sie im Chat deaktiviert sind. @@ -3342,11 +3433,27 @@ Das ist Ihr eigener Einmal-Link! Reparatur wird vom Gruppenmitglied nicht unterstützt No comment provided by engineer. + + For chat profile %@: + servers error + For console Für Konsole No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + No comment provided by engineer. + + + For private routing + No comment provided by engineer. + + + For social media + No comment provided by engineer. + Forward Weiterleiten @@ -3656,9 +3763,12 @@ Fehler: %2$@ Wie SimpleX funktioniert No comment provided by engineer. - - How it works - Wie es funktioniert + + How it affects privacy + No comment provided by engineer. + + + How it helps privacy No comment provided by engineer. @@ -3731,8 +3841,8 @@ Fehler: %2$@ Sofort No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Immun gegen Spam und Missbrauch No comment provided by engineer. @@ -3873,6 +3983,11 @@ Weitere Verbesserungen sind bald verfügbar! Installieren Sie [SimpleX Chat als Terminalanwendung](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Sofort + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3880,11 +3995,6 @@ Weitere Verbesserungen sind bald verfügbar! No comment provided by engineer. - - Instantly - Sofort - No comment provided by engineer. - Interface Schnittstelle @@ -3933,7 +4043,7 @@ Weitere Verbesserungen sind bald verfügbar! Invalid server address! Ungültige Serveradresse! - No comment provided by engineer. + alert title Invalid status @@ -4061,7 +4171,7 @@ Das ist Ihr Link für die Gruppe %@! Keep Behalten - No comment provided by engineer. + alert action Keep conversation @@ -4076,7 +4186,7 @@ Das ist Ihr Link für die Gruppe %@! Keep unused invitation? Nicht genutzte Einladung behalten? - No comment provided by engineer. + alert title Keep your connections @@ -4163,11 +4273,6 @@ Das ist Ihr Link für die Gruppe %@! Live Nachrichten No comment provided by engineer. - - Local - Lokal - No comment provided by engineer. - Local name Lokaler Name @@ -4188,11 +4293,6 @@ Das ist Ihr Link für die Gruppe %@! Sperr-Modus No comment provided by engineer. - - Make a private connection - Stellen Sie eine private Verbindung her - No comment provided by engineer. - Make one message disappear Eine verschwindende Nachricht verfassen @@ -4203,21 +4303,11 @@ Das ist Ihr Link für die Gruppe %@! Privates Profil erzeugen! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Stellen Sie sicher, dass die %@-Server-Adressen das richtige Format haben, zeilenweise getrennt und nicht doppelt vorhanden sind (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. Stellen Sie sicher, dass die WebRTC ICE-Server Adressen das richtige Format haben, zeilenweise getrennt und nicht doppelt vorhanden sind. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Viele Menschen haben gefragt: *Wie kann SimpleX Nachrichten zustellen, wenn es keine Benutzerkennungen gibt?* - No comment provided by engineer. - Mark deleted for everyone Für Alle als gelöscht markieren @@ -4498,6 +4588,10 @@ Das ist Ihr Link für die Gruppe %@! Zuverlässigere Netzwerkverbindung. No comment provided by engineer. + + More reliable notifications + No comment provided by engineer. + Most likely this connection is deleted. Wahrscheinlich ist diese Verbindung gelöscht worden. @@ -4533,6 +4627,10 @@ Das ist Ihr Link für die Gruppe %@! Netzwerkverbindung No comment provided by engineer. + + Network decentralization + No comment provided by engineer. + Network issues - message expired after many attempts to send it. Netzwerk-Fehler - die Nachricht ist nach vielen Sende-Versuchen abgelaufen. @@ -4543,6 +4641,10 @@ Das ist Ihr Link für die Gruppe %@! Netzwerk-Verwaltung No comment provided by engineer. + + Network operator + No comment provided by engineer. + Network settings Netzwerkeinstellungen @@ -4603,6 +4705,10 @@ Das ist Ihr Link für die Gruppe %@! Neuer Anzeigename No comment provided by engineer. + + New events + notification + New in %@ Neu in %@ @@ -4628,6 +4734,10 @@ Das ist Ihr Link für die Gruppe %@! Neues Passwort… No comment provided by engineer. + + New server + No comment provided by engineer. + No Nein @@ -4683,6 +4793,14 @@ Das ist Ihr Link für die Gruppe %@! Keine Information - es wird versucht neu zu laden No comment provided by engineer. + + No media & file servers. + servers error + + + No message servers. + servers error + No network connection Keine Netzwerkverbindung @@ -4703,11 +4821,37 @@ Das ist Ihr Link für die Gruppe %@! Keine Berechtigung für das Aufnehmen von Sprachnachrichten No comment provided by engineer. + + No push server + Lokal + No comment provided by engineer. + No received or sent files Keine empfangenen oder gesendeten Dateien No comment provided by engineer. + + No servers for private message routing. + servers error + + + No servers to receive files. + servers error + + + No servers to receive messages. + servers error + + + No servers to send files. + servers error + + + No user identifiers. + Keine Benutzerkennungen. + No comment provided by engineer. + Not compatible! Nicht kompatibel! @@ -4733,6 +4877,10 @@ Das ist Ihr Link für die Gruppe %@! Benachrichtigungen sind deaktiviert! No comment provided by engineer. + + Notifications privacy + No comment provided by engineer. + Now admins can: - delete members' messages. @@ -4791,8 +4939,8 @@ Dies erfordert die Aktivierung eines VPNs. Onion-Hosts werden nicht verwendet. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. Nur die Endgeräte speichern die Benutzerprofile, Kontakte, Gruppen und Nachrichten, welche über eine **2-Schichten Ende-zu-Ende-Verschlüsselung** gesendet werden. No comment provided by engineer. @@ -4876,6 +5024,10 @@ Dies erfordert die Aktivierung eines VPNs. Geräte-Einstellungen öffnen No comment provided by engineer. + + Open changes + No comment provided by engineer. + Open chat Chat öffnen @@ -4886,6 +5038,10 @@ Dies erfordert die Aktivierung eines VPNs. Chat-Konsole öffnen authentication reason + + Open conditions + No comment provided by engineer. + Open group Gruppe öffnen @@ -4896,26 +5052,19 @@ Dies erfordert die Aktivierung eines VPNs. Migration auf ein anderes Gerät öffnen authentication reason - - Open server settings - Server-Einstellungen öffnen - No comment provided by engineer. - - - Open user profiles - Benutzerprofile öffnen - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Open-Source-Protokoll und -Code – Jede Person kann ihre eigenen Server aufsetzen und nutzen. - No comment provided by engineer. - Opening app… App wird geöffnet… No comment provided by engineer. + + Operator + No comment provided by engineer. + + + Operator server + alert title + Or paste archive link Oder fügen Sie den Archiv-Link ein @@ -4936,16 +5085,15 @@ Dies erfordert die Aktivierung eines VPNs. Oder diesen QR-Code anzeigen No comment provided by engineer. + + Or to share privately + No comment provided by engineer. + Other Andere No comment provided by engineer. - - Other %@ servers - Andere %@ Server - No comment provided by engineer. - Other file errors: %@ @@ -5028,13 +5176,8 @@ Dies erfordert die Aktivierung eines VPNs. Ausstehend No comment provided by engineer. - - People can connect to you only via the links you share. - Verbindungen mit Kontakten sind nur über Links möglich, die Sie oder Ihre Kontakte untereinander teilen. - No comment provided by engineer. - - - Periodically + + Periodic Periodisch No comment provided by engineer. @@ -5157,16 +5300,15 @@ Fehler: %@ Den letzten Nachrichtenentwurf, auch mit seinen Anhängen, aufbewahren. No comment provided by engineer. - - Preset server - Voreingestellter Server - No comment provided by engineer. - Preset server address Voreingestellte Serveradresse No comment provided by engineer. + + Preset servers + No comment provided by engineer. + Preview Vorschau @@ -5245,7 +5387,7 @@ Fehler: %@ Profile update will be sent to your contacts. Profil-Aktualisierung wird an Ihre Kontakte gesendet. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -5339,6 +5481,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Der Proxy benötigt ein Passwort No comment provided by engineer. + + Push Notifications + No comment provided by engineer. + Push notifications Push-Benachrichtigungen @@ -5379,26 +5525,21 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Mehr erfahren No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - Mehr dazu in der [Benutzeranleitung](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address) lesen. - No comment provided by engineer. - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). Lesen Sie mehr dazu im [Benutzerhandbuch](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + Mehr dazu in der [Benutzeranleitung](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses) lesen. + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). Mehr dazu in der [Benutzeranleitung](https://simplex.chat/docs/guide/readme.html#connect-to-friends) lesen. No comment provided by engineer. - - Read more in our GitHub repository. - Erfahren Sie in unserem GitHub-Repository mehr dazu. - No comment provided by engineer. - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). Erfahren Sie in unserem [GitHub-Repository](https://github.com/simplex-chat/simplex-chat#readme) mehr dazu. @@ -5715,6 +5856,14 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Aufdecken chat item action + + Review conditions + No comment provided by engineer. + + + Review later + No comment provided by engineer. + Revoke Widerrufen @@ -5760,6 +5909,14 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Sicherere Gruppen No comment provided by engineer. + + Same conditions will apply to operator **%@**. + No comment provided by engineer. + + + Same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + Save Speichern @@ -5829,7 +5986,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Save servers? Alle Server speichern? - No comment provided by engineer. + alert title Save welcome message? @@ -6041,11 +6198,6 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Benachrichtigungen senden No comment provided by engineer. - - Send notifications: - Benachrichtigungen senden: - No comment provided by engineer. - Send questions and ideas Senden Sie Fragen und Ideen @@ -6171,6 +6323,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Server No comment provided by engineer. + + Server added to operator %@. + alert message + Server address Server-Adresse @@ -6186,6 +6342,18 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Die Server-Adresse ist nicht mit den Netzwerkeinstellungen kompatibel: %@. No comment provided by engineer. + + Server operator changed. + alert title + + + Server operators + No comment provided by engineer. + + + Server protocol changed. + alert title + Server requires authorization to create queues, check password Um Warteschlangen zu erzeugen benötigt der Server eine Authentifizierung. Bitte überprüfen Sie das Passwort @@ -6304,22 +6472,35 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Share Teilen - chat item action + alert action + chat item action Share 1-time link Einmal-Link teilen No comment provided by engineer. + + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + No comment provided by engineer. + Share address Adresse teilen No comment provided by engineer. + + Share address publicly + No comment provided by engineer. + Share address with contacts? Die Adresse mit Kontakten teilen? - No comment provided by engineer. + alert title Share from other apps. @@ -6436,6 +6617,14 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. SimpleX-Adresse No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + No comment provided by engineer. + SimpleX contact address SimpleX-Kontaktadressen-Link @@ -6526,6 +6715,11 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Während des Imports traten ein paar nicht schwerwiegende Fehler auf: No comment provided by engineer. + + Some servers failed the test: +%@ + alert message + Somebody Jemand @@ -6609,12 +6803,12 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Stop sharing Teilen beenden - No comment provided by engineer. + alert action Stop sharing address? Das Teilen der Adresse beenden? - No comment provided by engineer. + alert title Stopping chat @@ -6764,7 +6958,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Tests failed! Tests sind fehlgeschlagen! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6781,11 +6975,6 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Dank der Nutzer - Tragen Sie per Weblate bei! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - Die erste Plattform ohne Benutzerkennungen – Privat per Design. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6798,6 +6987,10 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Wenn sie Nachrichten oder Kontaktanfragen empfangen, kann Sie die App benachrichtigen - Um dies zu aktivieren, öffnen Sie bitte die Einstellungen. No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). Die App wird eine Bestätigung bei Downloads von unbekannten Datei-Servern anfordern (außer bei .onion). @@ -6813,6 +7006,10 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Der von Ihnen gescannte Code ist kein SimpleX-Link-QR-Code. No comment provided by engineer. + + The connection reached the limit of undelivered messages, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! Die von Ihnen akzeptierte Verbindung wird abgebrochen! @@ -6833,6 +7030,11 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Die Verschlüsselung funktioniert und ein neues Verschlüsselungsabkommen ist nicht erforderlich. Es kann zu Verbindungsfehlern kommen! No comment provided by engineer. + + The future of messaging + Die nächste Generation von privatem Messaging + No comment provided by engineer. + The hash of the previous message is different. Der Hash der vorherigen Nachricht unterscheidet sich. @@ -6858,11 +7060,6 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Die Nachrichten werden für alle Mitglieder als moderiert gekennzeichnet werden. No comment provided by engineer. - - The next generation of private messaging - Die nächste Generation von privatem Messaging - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. Die alte Datenbank wurde während der Migration nicht entfernt. Sie kann gelöscht werden. @@ -6873,6 +7070,10 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Das Profil wird nur mit Ihren Kontakten geteilt. No comment provided by engineer. + + The second preset operator in the app! + No comment provided by engineer. + The second tick we missed! ✅ Wir haben das zweite Häkchen vermisst! ✅ @@ -6888,6 +7089,10 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Mögliche Server für neue Verbindungen von Ihrem aktuellen Chat-Profil **%@**. No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. Der von Ihnen eingefügte Text ist kein SimpleX-Link. @@ -6903,6 +7108,10 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Design No comment provided by engineer. + + These conditions will also apply for: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. Diese Einstellungen betreffen Ihr aktuelles Profil **%@**. @@ -7003,9 +7212,8 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Um eine Verbindung mit einem neuen Kontakt zu erstellen No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Zum Schutz Ihrer Privatsphäre verwendet SimpleX an Stelle von Benutzerkennungen, die von allen anderen Plattformen verwendet werden, Kennungen für Nachrichtenwarteschlangen, die für jeden Ihrer Kontakte individuell sind. + + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -7025,6 +7233,15 @@ You will be prompted to complete authentication before this feature is enabled.< Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funktion aktiviert wird. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + Zum Schutz Ihrer Privatsphäre verwendet SimpleX an Stelle von Benutzerkennungen, die von allen anderen Plattformen verwendet werden, Kennungen für Nachrichtenwarteschlangen, die für jeden Ihrer Kontakte individuell sind. + No comment provided by engineer. + + + To receive + No comment provided by engineer. + To record speech please grant permission to use Microphone. Bitte erteilen Sie für Sprach-Aufnahmen die Genehmigung das Mikrofon zu nutzen. @@ -7045,11 +7262,19 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt 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 send + 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. No comment provided by engineer. + + To use the servers of **%@**, accept conditions of use. + No comment provided by engineer. + To verify end-to-end encryption with your contact compare (or scan) the code on your devices. Um die Ende-zu-Ende-Verschlüsselung mit Ihrem Kontakt zu überprüfen, müssen Sie den Sicherheitscode in Ihren Apps vergleichen oder scannen. @@ -7140,6 +7365,10 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt Mitglied freigeben? No comment provided by engineer. + + Undelivered messages + No comment provided by engineer. + Unexpected migration state Unerwarteter Migrationsstatus @@ -7297,6 +7526,10 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Archiv wird hochgeladen No comment provided by engineer. + + Use %@ + No comment provided by engineer. + Use .onion hosts Verwende .onion-Hosts @@ -7322,6 +7555,14 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Aktuelles Profil nutzen No comment provided by engineer. + + Use for files + No comment provided by engineer. + + + Use for messages + No comment provided by engineer. + Use for new connections Für neue Verbindungen nutzen @@ -7362,6 +7603,10 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Server nutzen No comment provided by engineer. + + Use servers + No comment provided by engineer. + Use the app while in the call. Die App kann während eines Anrufs genutzt werden. @@ -7452,11 +7697,19 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Videos und Dateien bis zu 1GB No comment provided by engineer. + + View conditions + No comment provided by engineer. + View security code Schauen Sie sich den Sicherheitscode an No comment provided by engineer. + + View updated conditions + No comment provided by engineer. + Visible history Sichtbarer Nachrichtenverlauf @@ -7567,9 +7820,8 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Bei der Verbindung über Audio- und Video-Anrufe. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - Wenn Personen eine Verbindung anfordern, können Sie diese annehmen oder ablehnen. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -7729,6 +7981,18 @@ Verbindungsanfrage wiederholen? Kann von Ihnen in den Erscheinungsbild-Einstellungen geändert werden. No comment provided by engineer. + + You can configure operators in Network & servers settings. + No comment provided by engineer. + + + You can configure servers via settings. + No comment provided by engineer. + + + You can create it in user picker. + No comment provided by engineer. + You can create it later Sie können dies später erstellen @@ -7769,6 +8033,10 @@ Verbindungsanfrage wiederholen? Sie können aus den archivierten Kontakten heraus Nachrichten an %@ versenden. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + No comment provided by engineer. + You can set lock screen notification preview via settings. Über die Geräte-Einstellungen können Sie die Benachrichtigungsvorschau im Sperrbildschirm erlauben. @@ -7784,11 +8052,6 @@ Verbindungsanfrage wiederholen? Sie können diese Adresse mit Ihren Kontakten teilen, um sie mit **%@** verbinden zu lassen. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - Sie können Ihre Adresse als Link oder als QR-Code teilen – Jede Person kann sich darüber mit Ihnen verbinden. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app Sie können den Chat über die App-Einstellungen / Datenbank oder durch Neustart der App starten @@ -7812,23 +8075,23 @@ Verbindungsanfrage wiederholen? You can view invitation link again in connection details. Den Einladungslink können Sie in den Details der Verbindung nochmals sehen. - No comment provided by engineer. + alert message You can't send messages! Sie können keine Nachrichten versenden! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - Sie können selbst festlegen, über welche Server Sie Ihre Nachrichten **empfangen** und an Ihre Kontakte **senden** wollen. - 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 decide who can connect. + Sie entscheiden, wer sich mit Ihnen verbinden kann. + No comment provided by engineer. + You have already requested connection via this address! Sie haben über diese Adresse bereits eine Verbindung beantragt! @@ -7951,11 +8214,6 @@ Verbindungsanfrage wiederholen? Sie verwenden ein Inkognito-Profil für diese Gruppe. Um zu verhindern, dass Sie Ihr Hauptprofil teilen, ist in diesem Fall das Einladen von Kontakten nicht erlaubt No comment provided by engineer. - - Your %@ servers - Ihre %@-Server - No comment provided by engineer. - Your ICE servers Ihre ICE-Server @@ -7971,11 +8229,6 @@ Verbindungsanfrage wiederholen? Ihre SimpleX-Adresse No comment provided by engineer. - - Your XFTP servers - Ihre XFTP-Server - No comment provided by engineer. - Your calls Anrufe @@ -8076,16 +8329,15 @@ Verbindungsanfrage wiederholen? Ihr Zufallsprofil No comment provided by engineer. - - Your server - Ihr Server - No comment provided by engineer. - Your server address Ihre Serveradresse No comment provided by engineer. + + Your servers + No comment provided by engineer. + Your settings Einstellungen @@ -8506,6 +8758,10 @@ Verbindungsanfrage wiederholen? Abgelaufen No comment provided by engineer. + + for better metadata privacy. + No comment provided by engineer. + forwarded weitergeleitet @@ -9128,6 +9384,33 @@ Zuletzt empfangene Nachricht: %2$@ + +
+ +
+ + + %d new events + notification body + + + From: %@ + notification body + + + New events + notification + + + New messages + notification + + + New messages in %d chats + notification body + + +
diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/de.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/de.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff b/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff index 799c61b448..d18eb4483c 100644 --- a/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff +++ b/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff @@ -186,20 +186,16 @@ Available in v5.1 ) No comment provided by engineer. - - **Add new contact**: to create your one-time QR Code or link for your contact. - No comment provided by engineer. - **Create link / QR code** for your contact to use. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. No comment provided by engineer. @@ -210,8 +206,8 @@ Available in v5.1 **Please note**: you will NOT be able to recover or change passphrase if you lose it. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. No comment provided by engineer. @@ -1708,8 +1704,8 @@ Available in v5.1 Immediately No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam No comment provided by engineer. @@ -2174,8 +2170,8 @@ Available in v5.1 Onion hosts will not be used. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. No comment provided by engineer. @@ -2234,8 +2230,8 @@ Available in v5.1 Open user profiles authentication reason - - Open-source protocol and code – anybody can run the servers. + + Anybody can host servers. No comment provided by engineer. @@ -2290,8 +2286,8 @@ Available in v5.1 Paste the link you received into the box below to connect with your contact. No comment provided by engineer. - - People can connect to you only via the links you share. + + You decide who can connect. No comment provided by engineer. @@ -2994,8 +2990,8 @@ Available in v5.1 Thanks to the users – contribute via Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. + + No user identifiers. No comment provided by engineer. @@ -3039,8 +3035,8 @@ It can happen because of some bug or when the connection is compromised.The message will be marked as moderated for all members. No comment provided by engineer. - - The next generation of private messaging + + The future of messaging No comment provided by engineer. @@ -3111,8 +3107,8 @@ It can happen because of some bug or when the connection is compromised.To make a new connection No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. No comment provided by engineer. @@ -3478,10 +3474,6 @@ SimpleX Lock must be enabled. You can't send messages! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - No comment provided by engineer. - You could not be verified; please try again. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 321b430a3f..5a2c41379b 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -12,21 +12,6 @@ No comment provided by engineer. - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - ( ( @@ -127,6 +112,16 @@ %@ is verified No comment provided by engineer. + + %@ server + %@ server + No comment provided by engineer. + + + %@ servers + %@ servers + No comment provided by engineer. + %@ uploaded %@ uploaded @@ -352,14 +347,9 @@ ) No comment provided by engineer. - - **Add contact**: to create a new invitation link, or connect via a link you received. - **Add contact**: to create a new invitation link, or connect via a link you received. - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Add new contact**: to create your one-time QR Code or link for your contact. + + **Create 1-time link**: to create and share a new invitation link. + **Create 1-time link**: to create and share a new invitation link. No comment provided by engineer. @@ -367,14 +357,14 @@ **Create group**: to create a new group. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. No comment provided by engineer. @@ -387,9 +377,14 @@ **Please note**: you will NOT be able to recover or change passphrase if you lose it. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. + No comment provided by engineer. + + + **Scan / Paste link**: to connect via a link you received. + **Scan / Paste link**: to connect via a link you received. No comment provided by engineer. @@ -498,6 +493,16 @@ 1 week time interval + + 1-time link + 1-time link + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + 1-time link can be used *with one contact only* - share in person or via any messenger. + No comment provided by engineer. + 5 minutes 5 minutes @@ -567,21 +572,11 @@ Abort changing address? No comment provided by engineer. - - About SimpleX - About SimpleX - No comment provided by engineer. - About SimpleX Chat About SimpleX Chat No comment provided by engineer. - - About SimpleX address - About SimpleX address - No comment provided by engineer. - Accent Accent @@ -594,6 +589,11 @@ accept incoming call via notification swipe action + + Accept conditions + Accept conditions + No comment provided by engineer. + Accept connection request? Accept connection request? @@ -610,6 +610,11 @@ accept contact request via notification swipe action + + Accepted conditions + Accepted conditions + No comment provided by engineer. + Acknowledged Acknowledged @@ -630,16 +635,6 @@ Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. No comment provided by engineer. - - Add contact - Add contact - No comment provided by engineer. - - - Add preset servers - Add preset servers - No comment provided by engineer. - Add profile Add profile @@ -665,6 +660,16 @@ Add welcome message No comment provided by engineer. + + Added media & file servers + Added media & file servers + No comment provided by engineer. + + + Added message servers + Added message servers + No comment provided by engineer. + Additional accent Additional accent @@ -690,6 +695,16 @@ Address change will be aborted. Old receiving address will be used. No comment provided by engineer. + + Address or 1-time link? + Address or 1-time link? + No comment provided by engineer. + + + Address settings + Address settings + No comment provided by engineer. + Admins can block a member for all. Admins can block a member for all. @@ -735,6 +750,11 @@ All group members will remain connected. No comment provided by engineer. + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + No comment provided by engineer. + All messages will be deleted - this cannot be undone! All messages will be deleted - this cannot be undone! @@ -915,6 +935,11 @@ Answer call No comment provided by engineer. + + Anybody can host servers. + Anybody can host servers. + No comment provided by engineer. + App build: %@ App build: %@ @@ -1258,7 +1283,8 @@ Cancel Cancel - alert button + alert action + alert button Cancel migration @@ -1341,6 +1367,11 @@ authentication reason set passcode view + + Change user profiles + Change user profiles + authentication reason + Chat archive Chat archive @@ -1426,10 +1457,20 @@ Chats No comment provided by engineer. + + Check messages every 20 min. + Check messages every 20 min. + No comment provided by engineer. + + + Check messages when allowed. + Check messages when allowed. + No comment provided by engineer. + Check server address and try again. Check server address and try again. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1516,16 +1557,56 @@ Completed No comment provided by engineer. + + Conditions accepted on: %@. + Conditions accepted on: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + Conditions are accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for following operator(s): **%@**. + Conditions are already accepted for following operator(s): **%@**. + No comment provided by engineer. + + + Conditions of use + Conditions of use + No comment provided by engineer. + + + Conditions will be accepted for enabled operators after 30 days. + Conditions will be accepted for enabled operators after 30 days. + No comment provided by engineer. + + + Conditions will be accepted for operator(s): **%@**. + Conditions will be accepted for operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + Conditions will be accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + Conditions will be accepted on: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + Conditions will be automatically accepted for enabled operators on: %@. + No comment provided by engineer. + Configure ICE servers Configure ICE servers No comment provided by engineer. - - Configured %@ servers - Configured %@ servers - No comment provided by engineer. - Confirm Confirm @@ -1715,6 +1796,11 @@ This is your own one-time link! Connection request sent! No comment provided by engineer. + + Connection security + Connection security + No comment provided by engineer. + Connection terminated Connection terminated @@ -1830,6 +1916,11 @@ This is your own one-time link! Create No comment provided by engineer. + + Create 1-time link + Create 1-time link + No comment provided by engineer. + Create SimpleX address Create SimpleX address @@ -1840,11 +1931,6 @@ This is your own one-time link! Create a group using a random profile. No comment provided by engineer. - - Create an address to let people connect with you. - Create an address to let people connect with you. - No comment provided by engineer. - Create file Create file @@ -1925,6 +2011,11 @@ This is your own one-time link! Current Passcode No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + Current conditions text couldn't be loaded, you can review conditions via this link: + No comment provided by engineer. + Current passphrase… Current passphrase… @@ -2081,7 +2172,8 @@ This is your own one-time link! Delete Delete - chat item action + alert action + chat item action swipe action @@ -2299,6 +2391,11 @@ This is your own one-time link! Deletion errors No comment provided by engineer. + + Delivered even when Apple drops them. + Delivered even when Apple drops them. + No comment provided by engineer. + Delivery Delivery @@ -2580,6 +2677,11 @@ This is your own one-time link! Duration No comment provided by engineer. + + E2E encrypted notifications. + E2E encrypted notifications. + No comment provided by engineer. + Edit Edit @@ -2600,6 +2702,11 @@ This is your own one-time link! Enable (keep overrides) No comment provided by engineer. + + Enable Flux + Enable Flux + No comment provided by engineer. + Enable SimpleX Lock Enable SimpleX Lock @@ -2805,6 +2912,11 @@ This is your own one-time link! Error aborting address change No comment provided by engineer. + + Error accepting conditions + Error accepting conditions + alert title + Error accepting contact request Error accepting contact request @@ -2820,6 +2932,11 @@ This is your own one-time link! Error adding member(s) No comment provided by engineer. + + Error adding server + Error adding server + alert title + Error changing address Error changing address @@ -2960,10 +3077,10 @@ This is your own one-time link! Error joining group No comment provided by engineer. - - Error loading %@ servers - Error loading %@ servers - No comment provided by engineer. + + Error loading servers + Error loading servers + alert title Error migrating settings @@ -3000,11 +3117,6 @@ This is your own one-time link! Error resetting statistics No comment provided by engineer. - - Error saving %@ servers - Error saving %@ servers - No comment provided by engineer. - Error saving ICE servers Error saving ICE servers @@ -3025,6 +3137,11 @@ This is your own one-time link! Error saving passphrase to keychain No comment provided by engineer. + + Error saving servers + Error saving servers + alert title + Error saving settings Error saving settings @@ -3095,6 +3212,11 @@ This is your own one-time link! Error updating message No comment provided by engineer. + + Error updating server + Error updating server + alert title + Error updating settings Error updating settings @@ -3140,6 +3262,11 @@ This is your own one-time link! Errors No comment provided by engineer. + + Errors in servers configuration. + Errors in servers configuration. + servers error + Even when disabled in the conversation. Even when disabled in the conversation. @@ -3342,11 +3469,31 @@ This is your own one-time link! Fix not supported by group member No comment provided by engineer. + + For chat profile %@: + For chat profile %@: + servers error + For console For console No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + No comment provided by engineer. + + + For private routing + For private routing + No comment provided by engineer. + + + For social media + For social media + No comment provided by engineer. + Forward Forward @@ -3656,9 +3803,14 @@ Error: %2$@ How SimpleX works No comment provided by engineer. - - How it works - How it works + + How it affects privacy + How it affects privacy + No comment provided by engineer. + + + How it helps privacy + How it helps privacy No comment provided by engineer. @@ -3731,9 +3883,9 @@ Error: %2$@ Immediately No comment provided by engineer. - - Immune to spam and abuse - Immune to spam and abuse + + Immune to spam + Immune to spam No comment provided by engineer. @@ -3873,6 +4025,11 @@ More improvements are coming soon! Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Instant + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3880,11 +4037,6 @@ More improvements are coming soon! No comment provided by engineer. - - Instantly - Instantly - No comment provided by engineer. - Interface Interface @@ -3933,7 +4085,7 @@ More improvements are coming soon! Invalid server address! Invalid server address! - No comment provided by engineer. + alert title Invalid status @@ -4061,7 +4213,7 @@ This is your link for group %@! Keep Keep - No comment provided by engineer. + alert action Keep conversation @@ -4076,7 +4228,7 @@ This is your link for group %@! Keep unused invitation? Keep unused invitation? - No comment provided by engineer. + alert title Keep your connections @@ -4163,11 +4315,6 @@ This is your link for group %@! Live messages No comment provided by engineer. - - Local - Local - No comment provided by engineer. - Local name Local name @@ -4188,11 +4335,6 @@ This is your link for group %@! Lock mode No comment provided by engineer. - - Make a private connection - Make a private connection - No comment provided by engineer. - Make one message disappear Make one message disappear @@ -4203,21 +4345,11 @@ This is your link for group %@! Make profile private! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - No comment provided by engineer. - Mark deleted for everyone Mark deleted for everyone @@ -4498,6 +4630,11 @@ This is your link for group %@! More reliable network connection. No comment provided by engineer. + + More reliable notifications + More reliable notifications + No comment provided by engineer. + Most likely this connection is deleted. Most likely this connection is deleted. @@ -4533,6 +4670,11 @@ This is your link for group %@! Network connection No comment provided by engineer. + + Network decentralization + Network decentralization + No comment provided by engineer. + Network issues - message expired after many attempts to send it. Network issues - message expired after many attempts to send it. @@ -4543,6 +4685,11 @@ This is your link for group %@! Network management No comment provided by engineer. + + Network operator + Network operator + No comment provided by engineer. + Network settings Network settings @@ -4603,6 +4750,11 @@ This is your link for group %@! New display name No comment provided by engineer. + + New events + New events + notification + New in %@ New in %@ @@ -4628,6 +4780,11 @@ This is your link for group %@! New passphrase… No comment provided by engineer. + + New server + New server + No comment provided by engineer. + No No @@ -4683,6 +4840,16 @@ This is your link for group %@! No info, try to reload No comment provided by engineer. + + No media & file servers. + No media & file servers. + servers error + + + No message servers. + No message servers. + servers error + No network connection No network connection @@ -4703,11 +4870,41 @@ This is your link for group %@! No permission to record voice message No comment provided by engineer. + + No push server + No push server + No comment provided by engineer. + No received or sent files No received or sent files No comment provided by engineer. + + No servers for private message routing. + No servers for private message routing. + servers error + + + No servers to receive files. + No servers to receive files. + servers error + + + No servers to receive messages. + No servers to receive messages. + servers error + + + No servers to send files. + No servers to send files. + servers error + + + No user identifiers. + No user identifiers. + No comment provided by engineer. + Not compatible! Not compatible! @@ -4733,6 +4930,11 @@ This is your link for group %@! Notifications are disabled! No comment provided by engineer. + + Notifications privacy + Notifications privacy + No comment provided by engineer. + Now admins can: - delete members' messages. @@ -4791,9 +4993,9 @@ Requires compatible VPN. Onion hosts will not be used. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. + Only client devices store user profiles, contacts, groups, and messages. No comment provided by engineer. @@ -4876,6 +5078,11 @@ Requires compatible VPN. Open Settings No comment provided by engineer. + + Open changes + Open changes + No comment provided by engineer. + Open chat Open chat @@ -4886,6 +5093,11 @@ Requires compatible VPN. Open chat console authentication reason + + Open conditions + Open conditions + No comment provided by engineer. + Open group Open group @@ -4896,26 +5108,21 @@ Requires compatible VPN. Open migration to another device authentication reason - - Open server settings - Open server settings - No comment provided by engineer. - - - Open user profiles - Open user profiles - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Open-source protocol and code – anybody can run the servers. - No comment provided by engineer. - Opening app… Opening app… No comment provided by engineer. + + Operator + Operator + No comment provided by engineer. + + + Operator server + Operator server + alert title + Or paste archive link Or paste archive link @@ -4936,16 +5143,16 @@ Requires compatible VPN. Or show this code No comment provided by engineer. + + Or to share privately + Or to share privately + No comment provided by engineer. + Other Other No comment provided by engineer. - - Other %@ servers - Other %@ servers - No comment provided by engineer. - Other file errors: %@ @@ -5028,14 +5235,9 @@ Requires compatible VPN. Pending No comment provided by engineer. - - People can connect to you only via the links you share. - People can connect to you only via the links you share. - No comment provided by engineer. - - - Periodically - Periodically + + Periodic + Periodic No comment provided by engineer. @@ -5157,16 +5359,16 @@ Error: %@ Preserve the last message draft, with attachments. No comment provided by engineer. - - Preset server - Preset server - No comment provided by engineer. - Preset server address Preset server address No comment provided by engineer. + + Preset servers + Preset servers + No comment provided by engineer. + Preview Preview @@ -5245,7 +5447,7 @@ Error: %@ Profile update will be sent to your contacts. Profile update will be sent to your contacts. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -5339,6 +5541,11 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. + + Push Notifications + Push Notifications + No comment provided by engineer. + Push notifications Push notifications @@ -5379,26 +5586,21 @@ Enable in *Network & servers* settings. Read more No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - No comment provided by engineer. - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). No comment provided by engineer. - - Read more in our GitHub repository. - Read more in our GitHub repository. - No comment provided by engineer. - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). @@ -5715,6 +5917,16 @@ Enable in *Network & servers* settings. Reveal chat item action + + Review conditions + Review conditions + No comment provided by engineer. + + + Review later + Review later + No comment provided by engineer. + Revoke Revoke @@ -5760,6 +5972,16 @@ Enable in *Network & servers* settings. Safer groups No comment provided by engineer. + + Same conditions will apply to operator **%@**. + Same conditions will apply to operator **%@**. + No comment provided by engineer. + + + Same conditions will apply to operator(s): **%@**. + Same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + Save Save @@ -5829,7 +6051,7 @@ Enable in *Network & servers* settings. Save servers? Save servers? - No comment provided by engineer. + alert title Save welcome message? @@ -6041,11 +6263,6 @@ Enable in *Network & servers* settings. Send notifications No comment provided by engineer. - - Send notifications: - Send notifications: - No comment provided by engineer. - Send questions and ideas Send questions and ideas @@ -6171,6 +6388,11 @@ Enable in *Network & servers* settings. Server No comment provided by engineer. + + Server added to operator %@. + Server added to operator %@. + alert message + Server address Server address @@ -6186,6 +6408,21 @@ Enable in *Network & servers* settings. Server address is incompatible with network settings: %@. No comment provided by engineer. + + Server operator changed. + Server operator changed. + alert title + + + Server operators + Server operators + No comment provided by engineer. + + + Server protocol changed. + Server protocol changed. + alert title + Server requires authorization to create queues, check password Server requires authorization to create queues, check password @@ -6304,22 +6541,38 @@ Enable in *Network & servers* settings. Share Share - chat item action + alert action + chat item action Share 1-time link Share 1-time link No comment provided by engineer. + + Share 1-time link with a friend + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + Share SimpleX address on social media. + No comment provided by engineer. + Share address Share address No comment provided by engineer. + + Share address publicly + Share address publicly + No comment provided by engineer. + Share address with contacts? Share address with contacts? - No comment provided by engineer. + alert title Share from other apps. @@ -6436,6 +6689,16 @@ Enable in *Network & servers* settings. SimpleX address No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + SimpleX address and 1-time links are safe to share via any messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + SimpleX address or 1-time link? + No comment provided by engineer. + SimpleX contact address SimpleX contact address @@ -6526,6 +6789,13 @@ Enable in *Network & servers* settings. Some non-fatal errors occurred during import: No comment provided by engineer. + + Some servers failed the test: +%@ + Some servers failed the test: +%@ + alert message + Somebody Somebody @@ -6609,12 +6879,12 @@ Enable in *Network & servers* settings. Stop sharing Stop sharing - No comment provided by engineer. + alert action Stop sharing address? Stop sharing address? - No comment provided by engineer. + alert title Stopping chat @@ -6764,7 +7034,7 @@ Enable in *Network & servers* settings. Tests failed! Tests failed! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6781,11 +7051,6 @@ Enable in *Network & servers* settings. Thanks to the users – contribute via Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - The 1st platform without any user identifiers – private by design. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6798,6 +7063,11 @@ It can happen because of some bug or when the connection is compromised.The app can notify you when you receive messages or contact requests - please open settings to enable. No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). The app will ask to confirm downloads from unknown file servers (except .onion). @@ -6813,6 +7083,11 @@ It can happen because of some bug or when the connection is compromised.The code you scanned is not a SimpleX link QR code. No comment provided by engineer. + + The connection reached the limit of undelivered messages, your contact may be offline. + The connection reached the limit of undelivered messages, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! The connection you accepted will be cancelled! @@ -6833,6 +7108,11 @@ It can happen because of some bug or when the connection is compromised.The encryption is working and the new encryption agreement is not required. It may result in connection errors! No comment provided by engineer. + + The future of messaging + The future of messaging + No comment provided by engineer. + The hash of the previous message is different. The hash of the previous message is different. @@ -6858,11 +7138,6 @@ It can happen because of some bug or when the connection is compromised.The messages will be marked as moderated for all members. No comment provided by engineer. - - The next generation of private messaging - The next generation of private messaging - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. The old database was not removed during the migration, it can be deleted. @@ -6873,6 +7148,11 @@ It can happen because of some bug or when the connection is compromised.The profile is only shared with your contacts. No comment provided by engineer. + + The second preset operator in the app! + The second preset operator in the app! + No comment provided by engineer. + The second tick we missed! ✅ The second tick we missed! ✅ @@ -6888,6 +7168,11 @@ It can happen because of some bug or when the connection is compromised.The servers for new connections of your current chat profile **%@**. No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. The text you pasted is not a SimpleX link. @@ -6903,6 +7188,11 @@ It can happen because of some bug or when the connection is compromised.Themes No comment provided by engineer. + + These conditions will also apply for: **%@**. + These conditions will also apply for: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. These settings are for your current profile **%@**. @@ -7003,9 +7293,9 @@ It can happen because of some bug or when the connection is compromised.To make a new connection No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + + To protect against your link being replaced, you can compare contact security codes. + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -7025,6 +7315,16 @@ You will be prompted to complete authentication before this feature is enabled.< You will be prompted to complete authentication before this feature is enabled. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + No comment provided by engineer. + + + To receive + To receive + No comment provided by engineer. + To record speech please grant permission to use Microphone. To record speech please grant permission to use Microphone. @@ -7045,11 +7345,21 @@ You will be prompted to complete authentication before this feature is enabled.< To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page. No comment provided by engineer. + + To send + To send + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. To support instant push notifications the chat database has to be migrated. No comment provided by engineer. + + To use the servers of **%@**, accept conditions of use. + To use the servers of **%@**, accept conditions of use. + No comment provided by engineer. + To verify end-to-end encryption with your contact compare (or scan) the code on your devices. To verify end-to-end encryption with your contact compare (or scan) the code on your devices. @@ -7140,6 +7450,11 @@ You will be prompted to complete authentication before this feature is enabled.< Unblock member? No comment provided by engineer. + + Undelivered messages + Undelivered messages + No comment provided by engineer. + Unexpected migration state Unexpected migration state @@ -7297,6 +7612,11 @@ To connect, please ask your contact to create another connection link and check Uploading archive No comment provided by engineer. + + Use %@ + Use %@ + No comment provided by engineer. + Use .onion hosts Use .onion hosts @@ -7322,6 +7642,16 @@ To connect, please ask your contact to create another connection link and check Use current profile No comment provided by engineer. + + Use for files + Use for files + No comment provided by engineer. + + + Use for messages + Use for messages + No comment provided by engineer. + Use for new connections Use for new connections @@ -7362,6 +7692,11 @@ To connect, please ask your contact to create another connection link and check Use server No comment provided by engineer. + + Use servers + Use servers + No comment provided by engineer. + Use the app while in the call. Use the app while in the call. @@ -7452,11 +7787,21 @@ To connect, please ask your contact to create another connection link and check Videos and files up to 1gb No comment provided by engineer. + + View conditions + View conditions + No comment provided by engineer. + View security code View security code No comment provided by engineer. + + View updated conditions + View updated conditions + No comment provided by engineer. + Visible history Visible history @@ -7567,9 +7912,9 @@ To connect, please ask your contact to create another connection link and check When connecting audio and video calls. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - When people request to connect, you can accept or reject it. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -7729,6 +8074,21 @@ Repeat join request? You can change it in Appearance settings. No comment provided by engineer. + + You can configure operators in Network & servers settings. + You can configure operators in Network & servers settings. + No comment provided by engineer. + + + You can configure servers via settings. + You can configure servers via settings. + No comment provided by engineer. + + + You can create it in user picker. + You can create it in user picker. + No comment provided by engineer. + You can create it later You can create it later @@ -7769,6 +8129,11 @@ Repeat join request? You can send messages to %@ from Archived contacts. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + You can set connection name, to remember who the link was shared with. + No comment provided by engineer. + You can set lock screen notification preview via settings. You can set lock screen notification preview via settings. @@ -7784,11 +8149,6 @@ Repeat join request? You can share this address with your contacts to let them connect with **%@**. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - You can share your address as a link or QR code - anybody can connect to you. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app You can start chat via app Settings / Database or by restarting the app @@ -7812,23 +8172,23 @@ Repeat join request? You can view invitation link again in connection details. You can view invitation link again in connection details. - No comment provided by engineer. + alert message You can't send messages! You can't send messages! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - No comment provided by engineer. - You could not be verified; please try again. You could not be verified; please try again. No comment provided by engineer. + + You decide who can connect. + You decide who can connect. + No comment provided by engineer. + You have already requested connection via this address! You have already requested connection via this address! @@ -7951,11 +8311,6 @@ Repeat connection request? You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed No comment provided by engineer. - - Your %@ servers - Your %@ servers - No comment provided by engineer. - Your ICE servers Your ICE servers @@ -7971,11 +8326,6 @@ Repeat connection request? Your SimpleX address No comment provided by engineer. - - Your XFTP servers - Your XFTP servers - No comment provided by engineer. - Your calls Your calls @@ -8076,16 +8426,16 @@ Repeat connection request? Your random profile No comment provided by engineer. - - Your server - Your server - No comment provided by engineer. - Your server address Your server address No comment provided by engineer. + + Your servers + Your servers + No comment provided by engineer. + Your settings Your settings @@ -8506,6 +8856,11 @@ Repeat connection request? expired No comment provided by engineer. + + for better metadata privacy. + for better metadata privacy. + No comment provided by engineer. + forwarded forwarded @@ -9128,6 +9483,38 @@ last received msg: %2$@ + +
+ +
+ + + %d new events + %d new events + notification body + + + From: %@ + From: %@ + notification body + + + New events + New events + notification + + + New messages + New messages + notification + + + New messages in %d chats + New messages in %d chats + notification body + + +
diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/en.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/en.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 21cd9919db..59c4bd167f 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -12,21 +12,6 @@ No comment provided by engineer. - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - ( ( @@ -127,6 +112,14 @@ %@ está verificado No comment provided by engineer. + + %@ server + No comment provided by engineer. + + + %@ servers + No comment provided by engineer. + %@ uploaded %@ subido @@ -352,14 +345,9 @@ ) No comment provided by engineer. - - **Add contact**: to create a new invitation link, or connect via a link you received. - **Añadir contacto**: crea un enlace de invitación nuevo o usa un enlace recibido. - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Añadir nuevo contacto**: para crear tu código QR o enlace de un uso para tu contacto. + + **Create 1-time link**: to create and share a new invitation link. + **Añadir contacto**: crea un enlace de invitación nuevo. No comment provided by engineer. @@ -367,13 +355,13 @@ **Crear grupo**: crea un grupo nuevo. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **Más privado**: comprueba los mensajes nuevos cada 20 minutos. El token del dispositivo se comparte con el servidor de SimpleX Chat, pero no cuántos contactos o mensajes tienes. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **Más privado**: no se usa el servidor de notificaciones de SimpleX Chat, los mensajes se comprueban periódicamente en segundo plano (dependiendo de la frecuencia con la que utilices la aplicación). No comment provided by engineer. @@ -387,11 +375,15 @@ **Atención**: NO podrás recuperar o cambiar la contraseña si la pierdes. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **Recomendado**: el token del dispositivo y las notificaciones se envían al servidor de notificaciones de SimpleX Chat, pero no el contenido del mensaje, su tamaño o su procedencia. No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **Advertencia**: Las notificaciones automáticas instantáneas requieren una contraseña guardada en Keychain. @@ -498,6 +490,14 @@ una semana time interval + + 1-time link + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + No comment provided by engineer. + 5 minutes 5 minutos @@ -567,21 +567,11 @@ ¿Cancelar el cambio de servidor? No comment provided by engineer. - - About SimpleX - Acerca de SimpleX - No comment provided by engineer. - About SimpleX Chat Sobre SimpleX Chat No comment provided by engineer. - - About SimpleX address - Acerca de la dirección SimpleX - No comment provided by engineer. - Accent Color @@ -594,6 +584,10 @@ accept incoming call via notification swipe action + + Accept conditions + No comment provided by engineer. + Accept connection request? ¿Aceptar solicitud de conexión? @@ -610,6 +604,10 @@ accept contact request via notification swipe action + + Accepted conditions + No comment provided by engineer. + Acknowledged Confirmaciones @@ -630,16 +628,6 @@ Añade la dirección a tu perfil para que tus contactos puedan compartirla con otros. La actualización del perfil se enviará a tus contactos. No comment provided by engineer. - - Add contact - Añadir contacto - No comment provided by engineer. - - - Add preset servers - Añadir servidores predefinidos - No comment provided by engineer. - Add profile Añadir perfil @@ -665,6 +653,14 @@ Añadir mensaje de bienvenida No comment provided by engineer. + + Added media & file servers + No comment provided by engineer. + + + Added message servers + No comment provided by engineer. + Additional accent Acento adicional @@ -690,6 +686,14 @@ El cambio de dirección se cancelará. Se usará la antigua dirección de recepción. No comment provided by engineer. + + Address or 1-time link? + No comment provided by engineer. + + + Address settings + No comment provided by engineer. + Admins can block a member for all. Los administradores pueden bloquear a un miembro para los demás. @@ -735,6 +739,10 @@ Todos los miembros del grupo permanecerán conectados. No comment provided by engineer. + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + No comment provided by engineer. + All messages will be deleted - this cannot be undone! Todos los mensajes serán borrados. ¡No podrá deshacerse! @@ -915,6 +923,11 @@ Responder llamada No comment provided by engineer. + + Anybody can host servers. + Cualquiera puede alojar servidores. + No comment provided by engineer. + App build: %@ Compilación app: %@ @@ -1258,7 +1271,8 @@ Cancel Cancelar - alert button + alert action + alert button Cancel migration @@ -1341,6 +1355,10 @@ authentication reason set passcode view + + Change user profiles + authentication reason + Chat archive Archivo del chat @@ -1426,10 +1444,18 @@ Chats No comment provided by engineer. + + Check messages every 20 min. + No comment provided by engineer. + + + Check messages when allowed. + No comment provided by engineer. + Check server address and try again. Comprueba la dirección del servidor e inténtalo de nuevo. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1516,16 +1542,47 @@ Completadas No comment provided by engineer. + + Conditions accepted on: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for following operator(s): **%@**. + No comment provided by engineer. + + + Conditions of use + No comment provided by engineer. + + + Conditions will be accepted for enabled operators after 30 days. + No comment provided by engineer. + + + Conditions will be accepted for operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + No comment provided by engineer. + Configure ICE servers Configure servidores ICE No comment provided by engineer. - - Configured %@ servers - %@ servidores configurados - No comment provided by engineer. - Confirm Confirmar @@ -1715,6 +1772,10 @@ This is your own one-time link! ¡Solicitud de conexión enviada! No comment provided by engineer. + + Connection security + No comment provided by engineer. + Connection terminated Conexión finalizada @@ -1830,6 +1891,10 @@ This is your own one-time link! Crear No comment provided by engineer. + + Create 1-time link + No comment provided by engineer. + Create SimpleX address Crear dirección SimpleX @@ -1840,11 +1905,6 @@ This is your own one-time link! Crear grupo usando perfil aleatorio. No comment provided by engineer. - - Create an address to let people connect with you. - Crea una dirección para que otras personas puedan conectar contigo. - No comment provided by engineer. - Create file Crear archivo @@ -1925,6 +1985,10 @@ This is your own one-time link! Código de Acceso No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + No comment provided by engineer. + Current passphrase… Contraseña actual… @@ -2081,7 +2145,8 @@ This is your own one-time link! Delete Eliminar - chat item action + alert action + chat item action swipe action @@ -2299,6 +2364,10 @@ This is your own one-time link! Errores de eliminación No comment provided by engineer. + + Delivered even when Apple drops them. + No comment provided by engineer. + Delivery Entrega @@ -2580,6 +2649,10 @@ This is your own one-time link! Duración No comment provided by engineer. + + E2E encrypted notifications. + No comment provided by engineer. + Edit Editar @@ -2600,6 +2673,10 @@ This is your own one-time link! Activar (conservar anulaciones) No comment provided by engineer. + + Enable Flux + No comment provided by engineer. + Enable SimpleX Lock Activar Bloqueo SimpleX @@ -2805,6 +2882,10 @@ This is your own one-time link! Error al cancelar cambio de dirección No comment provided by engineer. + + Error accepting conditions + alert title + Error accepting contact request Error al aceptar solicitud del contacto @@ -2820,6 +2901,10 @@ This is your own one-time link! Error al añadir miembro(s) No comment provided by engineer. + + Error adding server + alert title + Error changing address Error al cambiar servidor @@ -2960,10 +3045,9 @@ This is your own one-time link! Error al unirte al grupo No comment provided by engineer. - - Error loading %@ servers - Error al cargar servidores %@ - No comment provided by engineer. + + Error loading servers + alert title Error migrating settings @@ -3000,11 +3084,6 @@ This is your own one-time link! Error al restablecer las estadísticas No comment provided by engineer. - - Error saving %@ servers - Error al guardar servidores %@ - No comment provided by engineer. - Error saving ICE servers Error al guardar servidores ICE @@ -3025,6 +3104,10 @@ This is your own one-time link! Error al guardar contraseña en Keychain No comment provided by engineer. + + Error saving servers + alert title + Error saving settings Error al guardar ajustes @@ -3095,6 +3178,10 @@ This is your own one-time link! Error al actualizar mensaje No comment provided by engineer. + + Error updating server + alert title + Error updating settings Error al actualizar configuración @@ -3140,6 +3227,10 @@ This is your own one-time link! Errores No comment provided by engineer. + + Errors in servers configuration. + servers error + Even when disabled in the conversation. Incluso si está desactivado para la conversación. @@ -3342,11 +3433,27 @@ This is your own one-time link! Corrección no compatible con miembro del grupo No comment provided by engineer. + + For chat profile %@: + servers error + For console Para consola No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + No comment provided by engineer. + + + For private routing + No comment provided by engineer. + + + For social media + No comment provided by engineer. + Forward Reenviar @@ -3656,9 +3763,12 @@ Error: %2$@ Cómo funciona SimpleX No comment provided by engineer. - - How it works - Cómo funciona + + How it affects privacy + No comment provided by engineer. + + + How it helps privacy No comment provided by engineer. @@ -3731,8 +3841,8 @@ Error: %2$@ Inmediatamente No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Inmune a spam y abuso No comment provided by engineer. @@ -3873,6 +3983,11 @@ More improvements are coming soon! Instalar terminal para [SimpleX Chat](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Al instante + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3880,11 +3995,6 @@ More improvements are coming soon! No comment provided by engineer. - - Instantly - Al instante - No comment provided by engineer. - Interface Interfaz @@ -3933,7 +4043,7 @@ More improvements are coming soon! Invalid server address! ¡Dirección de servidor no válida! - No comment provided by engineer. + alert title Invalid status @@ -4061,7 +4171,7 @@ This is your link for group %@! Keep Guardar - No comment provided by engineer. + alert action Keep conversation @@ -4076,7 +4186,7 @@ This is your link for group %@! Keep unused invitation? ¿Guardar invitación no usada? - No comment provided by engineer. + alert title Keep your connections @@ -4163,11 +4273,6 @@ This is your link for group %@! Mensajes en vivo No comment provided by engineer. - - Local - Local - No comment provided by engineer. - Local name Nombre local @@ -4188,11 +4293,6 @@ This is your link for group %@! Modo bloqueo No comment provided by engineer. - - Make a private connection - Establecer una conexión privada - No comment provided by engineer. - Make one message disappear Escribir un mensaje temporal @@ -4203,21 +4303,11 @@ This is your link for group %@! ¡Hacer perfil privado! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Asegúrate de que las direcciones del servidor %@ tienen el formato correcto, están separadas por líneas y no duplicadas (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. Asegúrate de que las direcciones del servidor WebRTC ICE tienen el formato correcto, están separadas por líneas y no duplicadas. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Muchos se preguntarán: *si SimpleX no tiene identificadores de usuario, ¿cómo puede entregar los mensajes?* - No comment provided by engineer. - Mark deleted for everyone Marcar como eliminado para todos @@ -4498,6 +4588,10 @@ This is your link for group %@! Conexión de red más fiable. No comment provided by engineer. + + More reliable notifications + No comment provided by engineer. + Most likely this connection is deleted. Probablemente la conexión ha sido eliminada. @@ -4533,6 +4627,10 @@ This is your link for group %@! Conexión de red No comment provided by engineer. + + Network decentralization + No comment provided by engineer. + Network issues - message expired after many attempts to send it. Problema en la red - el mensaje ha expirado tras muchos intentos de envío. @@ -4543,6 +4641,10 @@ This is your link for group %@! Gestión de la red No comment provided by engineer. + + Network operator + No comment provided by engineer. + Network settings Configuración de red @@ -4603,6 +4705,10 @@ This is your link for group %@! Nuevo nombre mostrado No comment provided by engineer. + + New events + notification + New in %@ Nuevo en %@ @@ -4628,6 +4734,10 @@ This is your link for group %@! Contraseña nueva… No comment provided by engineer. + + New server + No comment provided by engineer. + No No @@ -4683,6 +4793,14 @@ This is your link for group %@! No hay información, intenta recargar No comment provided by engineer. + + No media & file servers. + servers error + + + No message servers. + servers error + No network connection Sin conexión de red @@ -4703,11 +4821,37 @@ This is your link for group %@! Sin permiso para grabar mensajes de voz No comment provided by engineer. + + No push server + No push server + No comment provided by engineer. + No received or sent files Sin archivos recibidos o enviados No comment provided by engineer. + + No servers for private message routing. + servers error + + + No servers to receive files. + servers error + + + No servers to receive messages. + servers error + + + No servers to send files. + servers error + + + No user identifiers. + Sin identificadores de usuario. + No comment provided by engineer. + Not compatible! ¡No compatible! @@ -4733,6 +4877,10 @@ This is your link for group %@! ¡Las notificaciones están desactivadas! No comment provided by engineer. + + Notifications privacy + No comment provided by engineer. + Now admins can: - delete members' messages. @@ -4791,8 +4939,8 @@ Requiere activación de la VPN. No se usarán hosts .onion. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. Sólo los dispositivos cliente almacenan perfiles de usuario, contactos, grupos y mensajes enviados con **cifrado de extremo a extremo de 2 capas**. No comment provided by engineer. @@ -4876,6 +5024,10 @@ Requiere activación de la VPN. Abrir Configuración No comment provided by engineer. + + Open changes + No comment provided by engineer. + Open chat Abrir chat @@ -4886,6 +5038,10 @@ Requiere activación de la VPN. Abrir consola de Chat authentication reason + + Open conditions + No comment provided by engineer. + Open group Grupo abierto @@ -4896,26 +5052,19 @@ Requiere activación de la VPN. Abrir menú migración a otro dispositivo authentication reason - - Open server settings - Abrir configuración del servidor - No comment provided by engineer. - - - Open user profiles - Abrir perfil de usuario - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Protocolo y código abiertos: cualquiera puede usar los servidores. - No comment provided by engineer. - Opening app… Iniciando aplicación… No comment provided by engineer. + + Operator + No comment provided by engineer. + + + Operator server + alert title + Or paste archive link O pegar enlace del archivo @@ -4936,16 +5085,15 @@ Requiere activación de la VPN. O muestra este código QR No comment provided by engineer. + + Or to share privately + No comment provided by engineer. + Other Otro No comment provided by engineer. - - Other %@ servers - Otros servidores %@ - No comment provided by engineer. - Other file errors: %@ @@ -5028,13 +5176,8 @@ Requiere activación de la VPN. Pendientes No comment provided by engineer. - - People can connect to you only via the links you share. - Las personas pueden conectarse contigo solo mediante los enlaces que compartes. - No comment provided by engineer. - - - Periodically + + Periodic Periódicamente No comment provided by engineer. @@ -5157,16 +5300,15 @@ Error: %@ Conserva el último borrador del mensaje con los datos adjuntos. No comment provided by engineer. - - Preset server - Servidor predefinido - No comment provided by engineer. - Preset server address Dirección del servidor predefinida No comment provided by engineer. + + Preset servers + No comment provided by engineer. + Preview Vista previa @@ -5245,7 +5387,7 @@ Error: %@ Profile update will be sent to your contacts. La actualización del perfil se enviará a tus contactos. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -5339,6 +5481,10 @@ Actívalo en ajustes de *Servidores y Redes*. El proxy requiere contraseña No comment provided by engineer. + + Push Notifications + No comment provided by engineer. + Push notifications Notificaciones automáticas @@ -5379,26 +5525,21 @@ Actívalo en ajustes de *Servidores y Redes*. Conoce más No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - Conoce más en el [Manual del Usuario](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - No comment provided by engineer. - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). Conoce más en la [Guía del Usuario](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + Conoce más en el [Manual del Usuario](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). Conoce más en el [Manual del Usuario](https://simplex.chat/docs/guide/readme.html#connect-to-friends). No comment provided by engineer. - - Read more in our GitHub repository. - Conoce más en nuestro repositorio GitHub. - No comment provided by engineer. - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). Conoce más en nuestro [repositorio GitHub](https://github.com/simplex-chat/simplex-chat#readme). @@ -5715,6 +5856,14 @@ Actívalo en ajustes de *Servidores y Redes*. Revelar chat item action + + Review conditions + No comment provided by engineer. + + + Review later + No comment provided by engineer. + Revoke Revocar @@ -5760,6 +5909,14 @@ Actívalo en ajustes de *Servidores y Redes*. Grupos más seguros No comment provided by engineer. + + Same conditions will apply to operator **%@**. + No comment provided by engineer. + + + Same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + Save Guardar @@ -5829,7 +5986,7 @@ Actívalo en ajustes de *Servidores y Redes*. Save servers? ¿Guardar servidores? - No comment provided by engineer. + alert title Save welcome message? @@ -6041,11 +6198,6 @@ Actívalo en ajustes de *Servidores y Redes*. Enviar notificaciones No comment provided by engineer. - - Send notifications: - Enviar notificaciones: - No comment provided by engineer. - Send questions and ideas Consultas y sugerencias @@ -6171,6 +6323,10 @@ Actívalo en ajustes de *Servidores y Redes*. Servidor No comment provided by engineer. + + Server added to operator %@. + alert message + Server address Dirección del servidor @@ -6186,6 +6342,18 @@ Actívalo en ajustes de *Servidores y Redes*. La dirección del servidor es incompatible con la configuración de la red: %@. No comment provided by engineer. + + Server operator changed. + alert title + + + Server operators + No comment provided by engineer. + + + Server protocol changed. + alert title + Server requires authorization to create queues, check password El servidor requiere autorización para crear colas, comprueba la contraseña @@ -6304,22 +6472,35 @@ Actívalo en ajustes de *Servidores y Redes*. Share Compartir - chat item action + alert action + chat item action Share 1-time link Compartir enlace de un uso No comment provided by engineer. + + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + No comment provided by engineer. + Share address Compartir dirección No comment provided by engineer. + + Share address publicly + No comment provided by engineer. + Share address with contacts? ¿Compartir la dirección con los contactos? - No comment provided by engineer. + alert title Share from other apps. @@ -6436,6 +6617,14 @@ Actívalo en ajustes de *Servidores y Redes*. Dirección SimpleX No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + No comment provided by engineer. + SimpleX contact address Dirección de contacto SimpleX @@ -6526,6 +6715,11 @@ Actívalo en ajustes de *Servidores y Redes*. Han ocurrido algunos errores no críticos durante la importación: No comment provided by engineer. + + Some servers failed the test: +%@ + alert message + Somebody Alguien @@ -6609,12 +6803,12 @@ Actívalo en ajustes de *Servidores y Redes*. Stop sharing Dejar de compartir - No comment provided by engineer. + alert action Stop sharing address? ¿Dejar de compartir la dirección? - No comment provided by engineer. + alert title Stopping chat @@ -6764,7 +6958,7 @@ Actívalo en ajustes de *Servidores y Redes*. Tests failed! ¡Pruebas no superadas! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6781,11 +6975,6 @@ Actívalo en ajustes de *Servidores y Redes*. ¡Nuestro agradecimiento a todos los colaboradores! Puedes contribuir a través de Weblate No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - La primera plataforma sin identificadores de usuario: diseñada para la privacidad. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6798,6 +6987,10 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. La aplicación puede notificarte cuando recibas mensajes o solicitudes de contacto: por favor, abre la configuración para activarlo. No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). La aplicación pedirá que confirmes las descargas desde servidores de archivos desconocidos (excepto si son .onion). @@ -6813,6 +7006,10 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. El código QR escaneado no es un enlace SimpleX. No comment provided by engineer. + + The connection reached the limit of undelivered messages, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! ¡La conexión que has aceptado se cancelará! @@ -6833,6 +7030,11 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. 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 future of messaging + La nueva generación de mensajería privada + No comment provided by engineer. + The hash of the previous message is different. El hash del mensaje anterior es diferente. @@ -6858,11 +7060,6 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. Los mensajes serán marcados como moderados para todos los miembros. No comment provided by engineer. - - The next generation of private messaging - La nueva generación de mensajería privada - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. La base de datos antigua no se eliminó durante la migración, puede eliminarse. @@ -6873,6 +7070,10 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. El perfil sólo se comparte con tus contactos. No comment provided by engineer. + + The second preset operator in the app! + No comment provided by engineer. + The second tick we missed! ✅ ¡El doble check que nos faltaba! ✅ @@ -6888,6 +7089,10 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. Lista de servidores para las conexiones nuevas de tu perfil actual **%@**. No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. El texto pegado no es un enlace SimpleX. @@ -6903,6 +7108,10 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. Temas No comment provided by engineer. + + These conditions will also apply for: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. Esta configuración afecta a tu perfil actual **%@**. @@ -7003,9 +7212,8 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. Para hacer una conexión nueva No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Para proteger tu privacidad, en lugar de los identificadores de usuario que usan el resto de plataformas, SimpleX dispone de identificadores para las colas de mensajes, independientes para cada uno de tus contactos. + + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -7025,6 +7233,15 @@ You will be prompted to complete authentication before this feature is enabled.< Se te pedirá que completes la autenticación antes de activar esta función. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + Para proteger tu privacidad, en lugar de los identificadores de usuario que usan el resto de plataformas, SimpleX dispone de identificadores para las colas de mensajes, independientes para cada uno de tus contactos. + No comment provided by engineer. + + + To receive + No comment provided by engineer. + To record speech please grant permission to use Microphone. Para grabación de voz, por favor concede el permiso para usar el micrófono. @@ -7045,11 +7262,19 @@ Se te pedirá que completes la autenticación antes de activar esta función.Para hacer visible tu perfil oculto, introduce la contraseña en el campo de búsqueda del menú **Mis perfiles**. No comment provided by engineer. + + To send + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. Para permitir las notificaciones automáticas instantáneas, la base de datos se debe migrar. No comment provided by engineer. + + To use the servers of **%@**, accept conditions of use. + No comment provided by engineer. + To verify end-to-end encryption with your contact compare (or scan) the code on your devices. Para verificar el cifrado de extremo a extremo con tu contacto, compara (o escanea) el código en ambos dispositivos. @@ -7140,6 +7365,10 @@ Se te pedirá que completes la autenticación antes de activar esta función.¿Desbloquear miembro? No comment provided by engineer. + + Undelivered messages + No comment provided by engineer. + Unexpected migration state Estado de migración inesperado @@ -7297,6 +7526,10 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Subiendo archivo No comment provided by engineer. + + Use %@ + No comment provided by engineer. + Use .onion hosts Usar hosts .onion @@ -7322,6 +7555,14 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Usar perfil actual No comment provided by engineer. + + Use for files + No comment provided by engineer. + + + Use for messages + No comment provided by engineer. + Use for new connections Usar para conexiones nuevas @@ -7362,6 +7603,10 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Usar servidor No comment provided by engineer. + + Use servers + No comment provided by engineer. + Use the app while in the call. Usar la aplicación durante la llamada. @@ -7452,11 +7697,19 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Vídeos y archivos de hasta 1Gb No comment provided by engineer. + + View conditions + No comment provided by engineer. + View security code Mostrar código de seguridad No comment provided by engineer. + + View updated conditions + No comment provided by engineer. + Visible history Historial visible @@ -7567,9 +7820,8 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Al iniciar llamadas de audio y vídeo. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - Cuando alguien solicite conectarse podrás aceptar o rechazar la solicitud. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -7729,6 +7981,18 @@ Repeat join request? Puedes cambiar la posición de la barra desde el menú Apariencia. No comment provided by engineer. + + You can configure operators in Network & servers settings. + No comment provided by engineer. + + + You can configure servers via settings. + No comment provided by engineer. + + + You can create it in user picker. + No comment provided by engineer. + You can create it later Puedes crearla más tarde @@ -7769,6 +8033,10 @@ Repeat join request? Puedes enviar mensajes a %@ desde Contactos archivados. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + No comment provided by engineer. + You can set lock screen notification preview via settings. Puedes configurar las notificaciones de la pantalla de bloqueo desde Configuración. @@ -7784,11 +8052,6 @@ Repeat join request? Puedes compartir esta dirección con tus contactos para que puedan conectar con **%@**. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - Puedes compartir tu dirección como enlace o código QR para que cualquiera pueda conectarse contigo. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app Puede iniciar Chat a través de la Configuración / Base de datos de la aplicación o reiniciando la aplicación @@ -7812,23 +8075,23 @@ Repeat join request? You can view invitation link again in connection details. Podrás ver el enlace de invitación en detalles de conexión. - No comment provided by engineer. + alert message You can't send messages! ¡No puedes enviar mensajes! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - Tú controlas a través de qué servidor(es) **recibes** los mensajes. Tus contactos controlan a través de qué servidor(es) **envías** tus mensajes. - No comment provided by engineer. - You could not be verified; please try again. No has podido ser autenticado. Inténtalo de nuevo. No comment provided by engineer. + + You decide who can connect. + Tu decides quién se conecta. + No comment provided by engineer. + You have already requested connection via this address! ¡Ya has solicitado la conexión mediante esta dirección! @@ -7951,11 +8214,6 @@ Repeat connection request? Estás usando un perfil incógnito en este grupo. Para evitar descubrir tu perfil principal no se permite invitar contactos No comment provided by engineer. - - Your %@ servers - Mis servidores %@ - No comment provided by engineer. - Your ICE servers Servidores ICE @@ -7971,11 +8229,6 @@ Repeat connection request? Mi dirección SimpleX No comment provided by engineer. - - Your XFTP servers - Servidores XFTP - No comment provided by engineer. - Your calls Llamadas @@ -8076,16 +8329,15 @@ Repeat connection request? Tu perfil aleatorio No comment provided by engineer. - - Your server - Tu servidor - No comment provided by engineer. - Your server address Dirección del servidor No comment provided by engineer. + + Your servers + No comment provided by engineer. + Your settings Configuración @@ -8506,6 +8758,10 @@ Repeat connection request? expirados No comment provided by engineer. + + for better metadata privacy. + No comment provided by engineer. + forwarded reenviado @@ -9128,6 +9384,33 @@ last received msg: %2$@ + +
+ +
+ + + %d new events + notification body + + + From: %@ + notification body + + + New events + notification + + + New messages + notification + + + New messages in %d chats + notification body + + +
diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/es.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/es.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index 4b384842b6..c41190e0f1 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -12,21 +12,6 @@ No comment provided by engineer. - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - ( ( @@ -124,6 +109,14 @@ %@ on vahvistettu No comment provided by engineer. + + %@ server + No comment provided by engineer. + + + %@ servers + No comment provided by engineer. + %@ uploaded No comment provided by engineer. @@ -334,26 +327,21 @@ ) No comment provided by engineer. - - **Add contact**: to create a new invitation link, or connect via a link you received. - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Lisää uusi kontakti**: luo kertakäyttöinen QR-koodi tai linkki kontaktille. + + **Create 1-time link**: to create and share a new invitation link. No comment provided by engineer. **Create group**: to create a new group. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **Yksityisempi**: tarkista uudet viestit 20 minuutin välein. Laitetunnus jaetaan SimpleX Chat -palvelimen kanssa, mutta ei sitä, kuinka monta yhteystietoa tai viestiä sinulla on. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **Yksityisin**: älä käytä SimpleX Chat -ilmoituspalvelinta, tarkista viestit ajoittain taustalla (riippuu siitä, kuinka usein käytät sovellusta). No comment provided by engineer. @@ -366,11 +354,15 @@ **Huomaa**: et voi palauttaa tai muuttaa tunnuslausetta, jos kadotat sen. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **Suositus**: laitetunnus ja ilmoitukset lähetetään SimpleX Chat -ilmoituspalvelimelle, mutta ei viestin sisältöä, kokoa tai sitä, keneltä se on peräisin. No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **Varoitus**: Välittömät push-ilmoitukset vaativat tunnuslauseen, joka on tallennettu Keychainiin. @@ -469,6 +461,14 @@ 1 viikko time interval + + 1-time link + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + No comment provided by engineer. + 5 minutes 5 minuuttia @@ -538,21 +538,11 @@ Keskeytä osoitteenvaihto? No comment provided by engineer. - - About SimpleX - Tietoja SimpleX:stä - No comment provided by engineer. - About SimpleX Chat Tietoja SimpleX Chatistä No comment provided by engineer. - - About SimpleX address - Tietoja SimpleX osoitteesta - No comment provided by engineer. - Accent No comment provided by engineer. @@ -564,6 +554,10 @@ accept incoming call via notification swipe action + + Accept conditions + No comment provided by engineer. + Accept connection request? Hyväksy yhteyspyyntö? @@ -580,6 +574,10 @@ accept contact request via notification swipe action + + Accepted conditions + No comment provided by engineer. + Acknowledged No comment provided by engineer. @@ -597,15 +595,6 @@ Lisää osoite profiiliisi, jotta kontaktisi voivat jakaa sen muiden kanssa. Profiilipäivitys lähetetään kontakteillesi. No comment provided by engineer. - - Add contact - No comment provided by engineer. - - - Add preset servers - Lisää esiasetettuja palvelimia - No comment provided by engineer. - Add profile Lisää profiili @@ -631,6 +620,14 @@ Lisää tervetuloviesti No comment provided by engineer. + + Added media & file servers + No comment provided by engineer. + + + Added message servers + No comment provided by engineer. + Additional accent No comment provided by engineer. @@ -653,6 +650,14 @@ Osoitteenmuutos keskeytetään. Käytetään vanhaa vastaanotto-osoitetta. No comment provided by engineer. + + Address or 1-time link? + No comment provided by engineer. + + + Address settings + No comment provided by engineer. + Admins can block a member for all. No comment provided by engineer. @@ -695,6 +700,10 @@ Kaikki ryhmän jäsenet pysyvät yhteydessä. No comment provided by engineer. + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + No comment provided by engineer. + All messages will be deleted - this cannot be undone! No comment provided by engineer. @@ -864,6 +873,11 @@ Vastaa puheluun No comment provided by engineer. + + Anybody can host servers. + Avoimen lähdekoodin protokolla ja koodi - kuka tahansa voi käyttää palvelimia. + No comment provided by engineer. + App build: %@ Sovellusversio: %@ @@ -1172,7 +1186,8 @@ Cancel Peruuta - alert button + alert action + alert button Cancel migration @@ -1251,6 +1266,10 @@ authentication reason set passcode view + + Change user profiles + authentication reason + Chat archive Chat-arkisto @@ -1329,10 +1348,18 @@ Keskustelut No comment provided by engineer. + + Check messages every 20 min. + No comment provided by engineer. + + + Check messages when allowed. + No comment provided by engineer. + Check server address and try again. Tarkista palvelimen osoite ja yritä uudelleen. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1411,15 +1438,47 @@ Completed No comment provided by engineer. + + Conditions accepted on: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for following operator(s): **%@**. + No comment provided by engineer. + + + Conditions of use + No comment provided by engineer. + + + Conditions will be accepted for enabled operators after 30 days. + No comment provided by engineer. + + + Conditions will be accepted for operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + No comment provided by engineer. + Configure ICE servers Määritä ICE-palvelimet No comment provided by engineer. - - Configured %@ servers - No comment provided by engineer. - Confirm Vahvista @@ -1585,6 +1644,10 @@ This is your own one-time link! Yhteyspyyntö lähetetty! No comment provided by engineer. + + Connection security + No comment provided by engineer. + Connection terminated No comment provided by engineer. @@ -1690,6 +1753,10 @@ This is your own one-time link! Luo No comment provided by engineer. + + Create 1-time link + No comment provided by engineer. + Create SimpleX address Luo SimpleX-osoite @@ -1699,11 +1766,6 @@ This is your own one-time link! Create a group using a random profile. No comment provided by engineer. - - Create an address to let people connect with you. - Luo osoite, jolla ihmiset voivat ottaa sinuun yhteyttä. - No comment provided by engineer. - Create file Luo tiedosto @@ -1777,6 +1839,10 @@ This is your own one-time link! Nykyinen pääsykoodi No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + No comment provided by engineer. + Current passphrase… Nykyinen tunnuslause… @@ -1928,7 +1994,8 @@ This is your own one-time link! Delete Poista - chat item action + alert action + chat item action swipe action @@ -2136,6 +2203,10 @@ This is your own one-time link! Deletion errors No comment provided by engineer. + + Delivered even when Apple drops them. + No comment provided by engineer. + Delivery Toimitus @@ -2393,6 +2464,10 @@ This is your own one-time link! Kesto No comment provided by engineer. + + E2E encrypted notifications. + No comment provided by engineer. + Edit Muokkaa @@ -2413,6 +2488,10 @@ This is your own one-time link! Salli (pidä ohitukset) No comment provided by engineer. + + Enable Flux + No comment provided by engineer. + Enable SimpleX Lock Ota SimpleX Lock käyttöön @@ -2606,6 +2685,10 @@ This is your own one-time link! Virhe osoitteenmuutoksen keskeytyksessä No comment provided by engineer. + + Error accepting conditions + alert title + Error accepting contact request Virhe kontaktipyynnön hyväksymisessä @@ -2621,6 +2704,10 @@ This is your own one-time link! Virhe lisättäessä jäseniä No comment provided by engineer. + + Error adding server + alert title + Error changing address Virhe osoitteenvaihdossa @@ -2754,10 +2841,9 @@ This is your own one-time link! Virhe ryhmään liittymisessä No comment provided by engineer. - - Error loading %@ servers - Virhe %@-palvelimien lataamisessa - No comment provided by engineer. + + Error loading servers + alert title Error migrating settings @@ -2789,11 +2875,6 @@ This is your own one-time link! Error resetting statistics No comment provided by engineer. - - Error saving %@ servers - Virhe %@ palvelimien tallentamisessa - No comment provided by engineer. - Error saving ICE servers Virhe ICE-palvelimien tallentamisessa @@ -2814,6 +2895,10 @@ This is your own one-time link! Virhe tunnuslauseen tallentamisessa avainnippuun No comment provided by engineer. + + Error saving servers + alert title + Error saving settings when migrating @@ -2880,6 +2965,10 @@ This is your own one-time link! Virhe viestin päivityksessä No comment provided by engineer. + + Error updating server + alert title + Error updating settings Virhe asetusten päivittämisessä @@ -2922,6 +3011,10 @@ This is your own one-time link! Errors No comment provided by engineer. + + Errors in servers configuration. + servers error + Even when disabled in the conversation. Jopa kun ei käytössä keskustelussa. @@ -3109,11 +3202,27 @@ This is your own one-time link! Ryhmän jäsen ei tue korjausta No comment provided by engineer. + + For chat profile %@: + servers error + For console Konsoliin No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + No comment provided by engineer. + + + For private routing + No comment provided by engineer. + + + For social media + No comment provided by engineer. + Forward chat item action @@ -3399,9 +3508,12 @@ Error: %2$@ Miten SimpleX toimii No comment provided by engineer. - - How it works - Kuinka se toimii + + How it affects privacy + No comment provided by engineer. + + + How it helps privacy No comment provided by engineer. @@ -3472,8 +3584,8 @@ Error: %2$@ Heti No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Immuuni roskapostille ja väärinkäytöksille No comment provided by engineer. @@ -3604,6 +3716,11 @@ More improvements are coming soon! Asenna [SimpleX Chat terminaalille](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Heti + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3611,11 +3728,6 @@ More improvements are coming soon! No comment provided by engineer. - - Instantly - Heti - No comment provided by engineer. - Interface Käyttöliittymä @@ -3657,7 +3769,7 @@ More improvements are coming soon! Invalid server address! Virheellinen palvelinosoite! - No comment provided by engineer. + alert title Invalid status @@ -3778,7 +3890,7 @@ This is your link for group %@! Keep - No comment provided by engineer. + alert action Keep conversation @@ -3790,7 +3902,7 @@ This is your link for group %@! Keep unused invitation? - No comment provided by engineer. + alert title Keep your connections @@ -3874,11 +3986,6 @@ This is your link for group %@! Live-viestit No comment provided by engineer. - - Local - Paikallinen - No comment provided by engineer. - Local name Paikallinen nimi @@ -3899,11 +4006,6 @@ This is your link for group %@! Lukitustila No comment provided by engineer. - - Make a private connection - Luo yksityinen yhteys - No comment provided by engineer. - Make one message disappear Hävitä yksi viesti @@ -3914,21 +4016,11 @@ This is your link for group %@! Tee profiilista yksityinen! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Varmista, että %@-palvelinosoitteet ovat oikeassa muodossa, että ne on erotettu toisistaan riveittäin ja että ne eivät ole päällekkäisiä (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. Varmista, että WebRTC ICE -palvelinosoitteet ovat oikeassa muodossa, rivieroteltuina ja että ne eivät ole päällekkäisiä. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Monet ihmiset kysyivät: *Jos SimpleX:llä ei ole käyttäjätunnuksia, miten se voi toimittaa viestejä?* - No comment provided by engineer. - Mark deleted for everyone Merkitse poistetuksi kaikilta @@ -4180,6 +4272,10 @@ This is your link for group %@! More reliable network connection. No comment provided by engineer. + + More reliable notifications + No comment provided by engineer. + Most likely this connection is deleted. Todennäköisesti tämä yhteys on poistettu. @@ -4214,6 +4310,10 @@ This is your link for group %@! Network connection No comment provided by engineer. + + Network decentralization + No comment provided by engineer. + Network issues - message expired after many attempts to send it. snd error text @@ -4222,6 +4322,10 @@ This is your link for group %@! Network management No comment provided by engineer. + + Network operator + No comment provided by engineer. + Network settings Verkkoasetukset @@ -4277,6 +4381,10 @@ This is your link for group %@! Uusi näyttönimi No comment provided by engineer. + + New events + notification + New in %@ Uutta %@ @@ -4301,6 +4409,10 @@ This is your link for group %@! Uusi tunnuslause… No comment provided by engineer. + + New server + No comment provided by engineer. + No Ei @@ -4354,6 +4466,14 @@ This is your link for group %@! No info, try to reload No comment provided by engineer. + + No media & file servers. + servers error + + + No message servers. + servers error + No network connection No comment provided by engineer. @@ -4371,11 +4491,37 @@ This is your link for group %@! Ei lupaa ääniviestin tallentamiseen No comment provided by engineer. + + No push server + Paikallinen + No comment provided by engineer. + No received or sent files Ei vastaanotettuja tai lähetettyjä tiedostoja No comment provided by engineer. + + No servers for private message routing. + servers error + + + No servers to receive files. + servers error + + + No servers to receive messages. + servers error + + + No servers to send files. + servers error + + + No user identifiers. + Ensimmäinen alusta ilman käyttäjätunnisteita – suunniteltu yksityiseksi. + No comment provided by engineer. + Not compatible! No comment provided by engineer. @@ -4398,6 +4544,10 @@ This is your link for group %@! Ilmoitukset on poistettu käytöstä! No comment provided by engineer. + + Notifications privacy + No comment provided by engineer. + Now admins can: - delete members' messages. @@ -4455,8 +4605,8 @@ Edellyttää VPN:n sallimista. Onion-isäntiä ei käytetä. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. Vain asiakaslaitteet tallentavat käyttäjäprofiileja, yhteystietoja, ryhmiä ja viestejä, jotka on lähetetty **kaksinkertaisella päästä päähän -salauksella**. No comment provided by engineer. @@ -4538,6 +4688,10 @@ Edellyttää VPN:n sallimista. Avaa Asetukset No comment provided by engineer. + + Open changes + No comment provided by engineer. + Open chat Avaa keskustelu @@ -4548,6 +4702,10 @@ Edellyttää VPN:n sallimista. Avaa keskustelukonsoli authentication reason + + Open conditions + No comment provided by engineer. + Open group No comment provided by engineer. @@ -4556,24 +4714,18 @@ Edellyttää VPN:n sallimista. Open migration to another device authentication reason - - Open server settings - No comment provided by engineer. - - - Open user profiles - Avaa käyttäjäprofiilit - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Avoimen lähdekoodin protokolla ja koodi - kuka tahansa voi käyttää palvelimia. - No comment provided by engineer. - Opening app… No comment provided by engineer. + + Operator + No comment provided by engineer. + + + Operator server + alert title + Or paste archive link No comment provided by engineer. @@ -4590,12 +4742,12 @@ Edellyttää VPN:n sallimista. Or show this code No comment provided by engineer. - - Other + + Or to share privately No comment provided by engineer. - - Other %@ servers + + Other No comment provided by engineer. @@ -4672,13 +4824,8 @@ Edellyttää VPN:n sallimista. Pending No comment provided by engineer. - - People can connect to you only via the links you share. - Ihmiset voivat ottaa sinuun yhteyttä vain jakamiesi linkkien kautta. - No comment provided by engineer. - - - Periodically + + Periodic Ajoittain No comment provided by engineer. @@ -4792,16 +4939,15 @@ Error: %@ Säilytä viimeinen viestiluonnos liitteineen. No comment provided by engineer. - - Preset server - Esiasetettu palvelin - No comment provided by engineer. - Preset server address Esiasetettu palvelimen osoite No comment provided by engineer. + + Preset servers + No comment provided by engineer. + Preview Esikatselu @@ -4872,7 +5018,7 @@ Error: %@ Profile update will be sent to your contacts. Profiilipäivitys lähetetään kontakteillesi. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -4959,6 +5105,10 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. + + Push Notifications + No comment provided by engineer. + Push notifications Push-ilmoitukset @@ -4996,25 +5146,20 @@ Enable in *Network & servers* settings. Lue lisää No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - Lue lisää [Käyttöoppaasta](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - No comment provided by engineer. - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + Lue lisää [Käyttöoppaasta](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). Lue lisää [Käyttöoppaasta](https://simplex.chat/docs/guide/readme.html#connect-to-friends). No comment provided by engineer. - - Read more in our GitHub repository. - Lue lisää GitHub-tietovarastostamme. - No comment provided by engineer. - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). Lue lisää [GitHub-arkistosta](https://github.com/simplex-chat/simplex-chat#readme). @@ -5307,6 +5452,14 @@ Enable in *Network & servers* settings. Paljasta chat item action + + Review conditions + No comment provided by engineer. + + + Review later + No comment provided by engineer. + Revoke Peruuta @@ -5348,6 +5501,14 @@ Enable in *Network & servers* settings. Safer groups No comment provided by engineer. + + Same conditions will apply to operator **%@**. + No comment provided by engineer. + + + Same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + Save Tallenna @@ -5416,7 +5577,7 @@ Enable in *Network & servers* settings. Save servers? Tallenna palvelimet? - No comment provided by engineer. + alert title Save welcome message? @@ -5608,11 +5769,6 @@ Enable in *Network & servers* settings. Lähetys ilmoitukset No comment provided by engineer. - - Send notifications: - Lähetys ilmoitukset: - No comment provided by engineer. - Send questions and ideas Lähetä kysymyksiä ja ideoita @@ -5731,6 +5887,10 @@ Enable in *Network & servers* settings. Server No comment provided by engineer. + + Server added to operator %@. + alert message + Server address No comment provided by engineer. @@ -5743,6 +5903,18 @@ Enable in *Network & servers* settings. Server address is incompatible with network settings: %@. No comment provided by engineer. + + Server operator changed. + alert title + + + Server operators + No comment provided by engineer. + + + Server protocol changed. + alert title + Server requires authorization to create queues, check password Palvelin vaatii valtuutuksen jonojen luomiseen, tarkista salasana @@ -5851,22 +6023,35 @@ Enable in *Network & servers* settings. Share Jaa - chat item action + alert action + chat item action Share 1-time link Jaa kertakäyttölinkki No comment provided by engineer. + + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + No comment provided by engineer. + Share address Jaa osoite No comment provided by engineer. + + Share address publicly + No comment provided by engineer. + Share address with contacts? Jaa osoite kontakteille? - No comment provided by engineer. + alert title Share from other apps. @@ -5974,6 +6159,14 @@ Enable in *Network & servers* settings. SimpleX-osoite No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + No comment provided by engineer. + SimpleX contact address SimpleX-yhteystiedot @@ -6055,6 +6248,11 @@ Enable in *Network & servers* settings. Some non-fatal errors occurred during import: No comment provided by engineer. + + Some servers failed the test: +%@ + alert message + Somebody Joku @@ -6133,12 +6331,12 @@ Enable in *Network & servers* settings. Stop sharing Lopeta jakaminen - No comment provided by engineer. + alert action Stop sharing address? Lopeta osoitteen jakaminen? - No comment provided by engineer. + alert title Stopping chat @@ -6275,7 +6473,7 @@ Enable in *Network & servers* settings. Tests failed! Testit epäonnistuivat! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6292,11 +6490,6 @@ Enable in *Network & servers* settings. Kiitokset käyttäjille – osallistu Weblaten kautta! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - Ensimmäinen alusta ilman käyttäjätunnisteita – suunniteltu yksityiseksi. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6309,6 +6502,10 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Sovellus voi ilmoittaa sinulle, kun saat viestejä tai yhteydenottopyyntöjä - avaa asetukset ottaaksesi ne käyttöön. No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). No comment provided by engineer. @@ -6322,6 +6519,10 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.The code you scanned is not a SimpleX link QR code. No comment provided by engineer. + + The connection reached the limit of undelivered messages, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! Hyväksymäsi yhteys peruuntuu! @@ -6342,6 +6543,11 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Salaus toimii ja uutta salaussopimusta ei tarvita. Tämä voi johtaa yhteysvirheisiin! No comment provided by engineer. + + The future of messaging + Seuraavan sukupolven yksityisviestit + No comment provided by engineer. + The hash of the previous message is different. Edellisen viestin tarkiste on erilainen. @@ -6365,11 +6571,6 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.The messages will be marked as moderated for all members. No comment provided by engineer. - - The next generation of private messaging - Seuraavan sukupolven yksityisviestit - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. Vanhaa tietokantaa ei poistettu siirron aikana, se voidaan kuitenkin poistaa. @@ -6380,6 +6581,10 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Profiili jaetaan vain kontaktiesi kanssa. No comment provided by engineer. + + The second preset operator in the app! + No comment provided by engineer. + The second tick we missed! ✅ Toinen kuittaus, joka uupui! ✅ @@ -6395,6 +6600,10 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Palvelimet nykyisen keskusteluprofiilisi uusille yhteyksille **%@**. No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. No comment provided by engineer. @@ -6407,6 +6616,10 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Themes No comment provided by engineer. + + These conditions will also apply for: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. Nämä asetukset koskevat nykyistä profiiliasi **%@**. @@ -6498,9 +6711,8 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Uuden yhteyden luominen No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Yksityisyyden suojaamiseksi kaikkien muiden alustojen käyttämien käyttäjätunnusten sijaan SimpleX käyttää viestijonojen tunnisteita, jotka ovat kaikille kontakteille erillisiä. + + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -6519,6 +6731,15 @@ You will be prompted to complete authentication before this feature is enabled.< Sinua kehotetaan suorittamaan todennus loppuun, ennen kuin tämä ominaisuus otetaan käyttöön. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + Yksityisyyden suojaamiseksi kaikkien muiden alustojen käyttämien käyttäjätunnusten sijaan SimpleX käyttää viestijonojen tunnisteita, jotka ovat kaikille kontakteille erillisiä. + No comment provided by engineer. + + + To receive + No comment provided by engineer. + To record speech please grant permission to use Microphone. No comment provided by engineer. @@ -6537,11 +6758,19 @@ Sinua kehotetaan suorittamaan todennus loppuun, ennen kuin tämä ominaisuus ote Voit paljastaa piilotetun profiilisi syöttämällä koko salasanan hakukenttään **Keskusteluprofiilisi** -sivulla. No comment provided by engineer. + + To send + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. Keskustelujen-tietokanta on siirrettävä välittömien push-ilmoitusten tukemiseksi. No comment provided by engineer. + + To use the servers of **%@**, accept conditions of use. + No comment provided by engineer. + To verify end-to-end encryption with your contact compare (or scan) the code on your devices. Voit tarkistaa päästä päähän -salauksen kontaktisi kanssa vertaamalla (tai skannaamalla) laitteidenne koodia. @@ -6621,6 +6850,10 @@ Sinua kehotetaan suorittamaan todennus loppuun, ennen kuin tämä ominaisuus ote Unblock member? No comment provided by engineer. + + Undelivered messages + No comment provided by engineer. + Unexpected migration state Odottamaton siirtotila @@ -6768,6 +7001,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Uploading archive No comment provided by engineer. + + Use %@ + No comment provided by engineer. + Use .onion hosts Käytä .onion-isäntiä @@ -6792,6 +7029,14 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Käytä nykyistä profiilia No comment provided by engineer. + + Use for files + No comment provided by engineer. + + + Use for messages + No comment provided by engineer. + Use for new connections Käytä uusiin yhteyksiin @@ -6828,6 +7073,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Käytä palvelinta No comment provided by engineer. + + Use servers + No comment provided by engineer. + Use the app while in the call. No comment provided by engineer. @@ -6908,11 +7157,19 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Videot ja tiedostot 1 Gt asti No comment provided by engineer. + + View conditions + No comment provided by engineer. + View security code Näytä turvakoodi No comment provided by engineer. + + View updated conditions + No comment provided by engineer. + Visible history chat feature @@ -7015,9 +7272,8 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja When connecting audio and video calls. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - Kun ihmiset pyytävät yhteyden muodostamista, voit hyväksyä tai hylätä sen. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -7156,6 +7412,18 @@ Repeat join request? You can change it in Appearance settings. No comment provided by engineer. + + You can configure operators in Network & servers settings. + No comment provided by engineer. + + + You can configure servers via settings. + No comment provided by engineer. + + + You can create it in user picker. + No comment provided by engineer. + You can create it later Voit luoda sen myöhemmin @@ -7193,6 +7461,10 @@ Repeat join request? You can send messages to %@ from Archived contacts. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + No comment provided by engineer. + You can set lock screen notification preview via settings. Voit määrittää lukitusnäytön ilmoituksen esikatselun asetuksista. @@ -7208,11 +7480,6 @@ Repeat join request? Voit jakaa tämän osoitteen kontaktiesi kanssa, jotta ne voivat muodostaa yhteyden **%@** kanssa. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - Voit jakaa osoitteesi linkkinä tai QR-koodina - kuka tahansa voi muodostaa yhteyden sinuun. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app Voit aloittaa keskustelun sovelluksen Asetukset / Tietokanta kautta tai käynnistämällä sovelluksen uudelleen @@ -7234,23 +7501,23 @@ Repeat join request? You can view invitation link again in connection details. - No comment provided by engineer. + alert message You can't send messages! Et voi lähettää viestejä! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - Sinä hallitset, minkä palvelim(i)en kautta **viestit vastaanotetaan**, kontaktisi - palvelimet, joita käytät viestien lähettämiseen niille. - No comment provided by engineer. - You could not be verified; please try again. Sinua ei voitu todentaa; yritä uudelleen. No comment provided by engineer. + + You decide who can connect. + Kimin bağlanabileceğine siz karar verirsiniz. + No comment provided by engineer. + You have already requested connection via this address! No comment provided by engineer. @@ -7365,11 +7632,6 @@ Repeat connection request? Käytät tässä ryhmässä incognito-profiilia. Kontaktien kutsuminen ei ole sallittua, jotta pääprofiilisi ei tule jaetuksi No comment provided by engineer. - - Your %@ servers - %@-palvelimesi - No comment provided by engineer. - Your ICE servers ICE-palvelimesi @@ -7385,11 +7647,6 @@ Repeat connection request? SimpleX-osoitteesi No comment provided by engineer. - - Your XFTP servers - XFTP-palvelimesi - No comment provided by engineer. - Your calls Puhelusi @@ -7485,16 +7742,15 @@ Repeat connection request? Satunnainen profiilisi No comment provided by engineer. - - Your server - Palvelimesi - No comment provided by engineer. - Your server address Palvelimesi osoite No comment provided by engineer. + + Your servers + No comment provided by engineer. + Your settings Asetuksesi @@ -7900,6 +8156,10 @@ Repeat connection request? expired No comment provided by engineer. + + for better metadata privacy. + No comment provided by engineer. + forwarded No comment provided by engineer. @@ -8487,6 +8747,33 @@ last received msg: %2$@ + +
+ +
+ + + %d new events + notification body + + + From: %@ + notification body + + + New events + notification + + + New messages + notification + + + New messages in %d chats + notification body + + +
diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/fi.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index b672bebc8c..3ed363cb10 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -12,21 +12,6 @@ No comment provided by engineer. - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - ( ( @@ -127,6 +112,14 @@ %@ est vérifié·e No comment provided by engineer. + + %@ server + No comment provided by engineer. + + + %@ servers + No comment provided by engineer. + %@ uploaded %@ envoyé @@ -346,14 +339,9 @@ ) No comment provided by engineer. - - **Add contact**: to create a new invitation link, or connect via a link you received. - **Ajouter un contact** : pour créer un nouveau lien d'invitation ou vous connecter via un lien que vous avez reçu. - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Ajouter un nouveau contact** : pour créer un lien ou code QR unique pour votre contact. + + **Create 1-time link**: to create and share a new invitation link. + **Ajouter un contact** : pour créer un nouveau lien d'invitation. No comment provided by engineer. @@ -361,13 +349,13 @@ **Créer un groupe** : pour créer un nouveau groupe. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **Vie privée** : vérification de nouveaux messages toute les 20 minutes. Le token de l'appareil est partagé avec le serveur SimpleX, mais pas le nombre de messages ou de contacts. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **Confidentiel** : ne pas utiliser le serveur de notifications SimpleX, vérification de nouveaux messages periodiquement en arrière plan (dépend de l'utilisation de l'app). No comment provided by engineer. @@ -381,11 +369,15 @@ **Veuillez noter** : vous NE pourrez PAS récupérer ou modifier votre phrase secrète si vous la perdez. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **Recommandé** : le token de l'appareil et les notifications sont envoyés au serveur de notifications SimpleX, mais pas le contenu du message, sa taille ou son auteur. No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **Avertissement** : les notifications push instantanées nécessitent une phrase secrète enregistrée dans la keychain. @@ -492,6 +484,14 @@ 1 semaine time interval + + 1-time link + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + No comment provided by engineer. + 5 minutes 5 minutes @@ -561,21 +561,11 @@ Abandonner le changement d'adresse ? No comment provided by engineer. - - About SimpleX - À propos de SimpleX - No comment provided by engineer. - About SimpleX Chat À propos de SimpleX Chat No comment provided by engineer. - - About SimpleX address - À propos de l'adresse SimpleX - No comment provided by engineer. - Accent Principale @@ -588,6 +578,10 @@ accept incoming call via notification swipe action + + Accept conditions + No comment provided by engineer. + Accept connection request? Accepter la demande de connexion ? @@ -604,6 +598,10 @@ accept contact request via notification swipe action + + Accepted conditions + No comment provided by engineer. + Acknowledged Reçu avec accusé de réception @@ -624,16 +622,6 @@ Ajoutez une adresse à votre profil, afin que vos contacts puissent la partager avec d'autres personnes. La mise à jour du profil sera envoyée à vos contacts. No comment provided by engineer. - - Add contact - Ajouter le contact - No comment provided by engineer. - - - Add preset servers - Ajouter des serveurs prédéfinis - No comment provided by engineer. - Add profile Ajouter un profil @@ -659,6 +647,14 @@ Ajouter un message d'accueil No comment provided by engineer. + + Added media & file servers + No comment provided by engineer. + + + Added message servers + No comment provided by engineer. + Additional accent Accent additionnel @@ -684,6 +680,14 @@ Le changement d'adresse sera annulé. L'ancienne adresse de réception sera utilisée. No comment provided by engineer. + + Address or 1-time link? + No comment provided by engineer. + + + Address settings + No comment provided by engineer. + Admins can block a member for all. Les admins peuvent bloquer un membre pour tous. @@ -729,6 +733,10 @@ Tous les membres du groupe resteront connectés. No comment provided by engineer. + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + No comment provided by engineer. + All messages will be deleted - this cannot be undone! Tous les messages seront supprimés - il n'est pas possible de revenir en arrière ! @@ -909,6 +917,11 @@ Répondre à l'appel No comment provided by engineer. + + Anybody can host servers. + N'importe qui peut heberger un serveur. + No comment provided by engineer. + App build: %@ Build de l'app : %@ @@ -1245,7 +1258,8 @@ Cancel Annuler - alert button + alert action + alert button Cancel migration @@ -1328,6 +1342,10 @@ authentication reason set passcode view + + Change user profiles + authentication reason + Chat archive Archives du chat @@ -1412,10 +1430,18 @@ Discussions No comment provided by engineer. + + Check messages every 20 min. + No comment provided by engineer. + + + Check messages when allowed. + No comment provided by engineer. + Check server address and try again. Vérifiez l'adresse du serveur et réessayez. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1502,16 +1528,47 @@ Complétées No comment provided by engineer. + + Conditions accepted on: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for following operator(s): **%@**. + No comment provided by engineer. + + + Conditions of use + No comment provided by engineer. + + + Conditions will be accepted for enabled operators after 30 days. + No comment provided by engineer. + + + Conditions will be accepted for operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + No comment provided by engineer. + Configure ICE servers Configurer les serveurs ICE No comment provided by engineer. - - Configured %@ servers - %@ serveurs configurés - No comment provided by engineer. - Confirm Confirmer @@ -1701,6 +1758,10 @@ Il s'agit de votre propre lien unique ! Demande de connexion envoyée ! No comment provided by engineer. + + Connection security + No comment provided by engineer. + Connection terminated Connexion terminée @@ -1815,6 +1876,10 @@ Il s'agit de votre propre lien unique ! Créer No comment provided by engineer. + + Create 1-time link + No comment provided by engineer. + Create SimpleX address Créer une adresse SimpleX @@ -1825,11 +1890,6 @@ Il s'agit de votre propre lien unique ! Création de groupes via un profil aléatoire. No comment provided by engineer. - - Create an address to let people connect with you. - Vous pouvez créer une adresse pour permettre aux autres utilisateurs de vous contacter. - No comment provided by engineer. - Create file Créer un fichier @@ -1910,6 +1970,10 @@ Il s'agit de votre propre lien unique ! Code d'accès actuel No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + No comment provided by engineer. + Current passphrase… Phrase secrète actuelle… @@ -2065,7 +2129,8 @@ Il s'agit de votre propre lien unique ! Delete Supprimer - chat item action + alert action + chat item action swipe action @@ -2282,6 +2347,10 @@ Il s'agit de votre propre lien unique ! Erreurs de suppression No comment provided by engineer. + + Delivered even when Apple drops them. + No comment provided by engineer. + Delivery Distribution @@ -2561,6 +2630,10 @@ Il s'agit de votre propre lien unique ! Durée No comment provided by engineer. + + E2E encrypted notifications. + No comment provided by engineer. + Edit Modifier @@ -2581,6 +2654,10 @@ Il s'agit de votre propre lien unique ! Activer (conserver les remplacements) No comment provided by engineer. + + Enable Flux + No comment provided by engineer. + Enable SimpleX Lock Activer SimpleX Lock @@ -2786,6 +2863,10 @@ Il s'agit de votre propre lien unique ! Erreur lors de l'annulation du changement d'adresse No comment provided by engineer. + + Error accepting conditions + alert title + Error accepting contact request Erreur de validation de la demande de contact @@ -2801,6 +2882,10 @@ Il s'agit de votre propre lien unique ! Erreur lors de l'ajout de membre·s No comment provided by engineer. + + Error adding server + alert title + Error changing address Erreur de changement d'adresse @@ -2939,10 +3024,9 @@ Il s'agit de votre propre lien unique ! Erreur lors de la liaison avec le groupe No comment provided by engineer. - - Error loading %@ servers - Erreur lors du chargement des serveurs %@ - No comment provided by engineer. + + Error loading servers + alert title Error migrating settings @@ -2978,11 +3062,6 @@ Il s'agit de votre propre lien unique ! Erreur de réinitialisation des statistiques No comment provided by engineer. - - Error saving %@ servers - Erreur lors de la sauvegarde des serveurs %@ - No comment provided by engineer. - Error saving ICE servers Erreur lors de la sauvegarde des serveurs ICE @@ -3003,6 +3082,10 @@ Il s'agit de votre propre lien unique ! Erreur lors de l'enregistrement de la phrase de passe dans la keychain No comment provided by engineer. + + Error saving servers + alert title + Error saving settings Erreur lors de l'enregistrement des paramètres @@ -3072,6 +3155,10 @@ Il s'agit de votre propre lien unique ! Erreur lors de la mise à jour du message No comment provided by engineer. + + Error updating server + alert title + Error updating settings Erreur lors de la mise à jour des paramètres @@ -3117,6 +3204,10 @@ Il s'agit de votre propre lien unique ! Erreurs No comment provided by engineer. + + Errors in servers configuration. + servers error + Even when disabled in the conversation. Même s'il est désactivé dans la conversation. @@ -3317,11 +3408,27 @@ Il s'agit de votre propre lien unique ! Correction non prise en charge par un membre du groupe No comment provided by engineer. + + For chat profile %@: + servers error + For console Pour la console No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + No comment provided by engineer. + + + For private routing + No comment provided by engineer. + + + For social media + No comment provided by engineer. + Forward Transférer @@ -3626,9 +3733,12 @@ Erreur : %2$@ Comment SimpleX fonctionne No comment provided by engineer. - - How it works - Comment ça fonctionne + + How it affects privacy + No comment provided by engineer. + + + How it helps privacy No comment provided by engineer. @@ -3700,8 +3810,8 @@ Erreur : %2$@ Immédiatement No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Protégé du spam et des abus No comment provided by engineer. @@ -3840,6 +3950,11 @@ More improvements are coming soon! Installer [SimpleX Chat pour terminal](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Instantané + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3847,11 +3962,6 @@ More improvements are coming soon! No comment provided by engineer. - - Instantly - Instantané - No comment provided by engineer. - Interface Interface @@ -3900,7 +4010,7 @@ More improvements are coming soon! Invalid server address! Adresse de serveur invalide ! - No comment provided by engineer. + alert title Invalid status @@ -4028,7 +4138,7 @@ Voici votre lien pour le groupe %@ ! Keep Conserver - No comment provided by engineer. + alert action Keep conversation @@ -4043,7 +4153,7 @@ Voici votre lien pour le groupe %@ ! Keep unused invitation? Conserver l'invitation inutilisée ? - No comment provided by engineer. + alert title Keep your connections @@ -4130,11 +4240,6 @@ Voici votre lien pour le groupe %@ ! Messages dynamiques No comment provided by engineer. - - Local - Local - No comment provided by engineer. - Local name Nom local @@ -4155,11 +4260,6 @@ Voici votre lien pour le groupe %@ ! Mode de verrouillage No comment provided by engineer. - - Make a private connection - Établir une connexion privée - No comment provided by engineer. - Make one message disappear Rendre un message éphémère @@ -4170,21 +4270,11 @@ Voici votre lien pour le groupe %@ ! Rendre un profil privé ! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Assurez-vous que les adresses des serveurs %@ sont au bon format et ne sont pas dupliquées, un par ligne (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. Assurez-vous que les adresses des serveurs WebRTC ICE sont au bon format et ne sont pas dupliquées, un par ligne. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Beaucoup se demandent : *si SimpleX n'a pas d'identifiant d'utilisateur, comment peut-il délivrer des messages ?* - No comment provided by engineer. - Mark deleted for everyone Marquer comme supprimé pour tout le monde @@ -4463,6 +4553,10 @@ Voici votre lien pour le groupe %@ ! Connexion réseau plus fiable. No comment provided by engineer. + + More reliable notifications + No comment provided by engineer. + Most likely this connection is deleted. Connexion probablement supprimée. @@ -4498,6 +4592,10 @@ Voici votre lien pour le groupe %@ ! Connexion au réseau No comment provided by engineer. + + Network decentralization + No comment provided by engineer. + Network issues - message expired after many attempts to send it. Problèmes de réseau - le message a expiré après plusieurs tentatives d'envoi. @@ -4508,6 +4606,10 @@ Voici votre lien pour le groupe %@ ! Gestion du réseau No comment provided by engineer. + + Network operator + No comment provided by engineer. + Network settings Paramètres réseau @@ -4566,6 +4668,10 @@ Voici votre lien pour le groupe %@ ! Nouveau nom d'affichage No comment provided by engineer. + + New events + notification + New in %@ Nouveautés de la %@ @@ -4591,6 +4697,10 @@ Voici votre lien pour le groupe %@ ! Nouvelle phrase secrète… No comment provided by engineer. + + New server + No comment provided by engineer. + No Non @@ -4646,6 +4756,14 @@ Voici votre lien pour le groupe %@ ! Pas d'info, essayez de recharger No comment provided by engineer. + + No media & file servers. + servers error + + + No message servers. + servers error + No network connection Pas de connexion au réseau @@ -4664,11 +4782,37 @@ Voici votre lien pour le groupe %@ ! Pas l'autorisation d'enregistrer un message vocal No comment provided by engineer. + + No push server + No push server + No comment provided by engineer. + No received or sent files Aucun fichier reçu ou envoyé No comment provided by engineer. + + No servers for private message routing. + servers error + + + No servers to receive files. + servers error + + + No servers to receive messages. + servers error + + + No servers to send files. + servers error + + + No user identifiers. + Aucun identifiant d'utilisateur. + No comment provided by engineer. + Not compatible! Non compatible ! @@ -4693,6 +4837,10 @@ Voici votre lien pour le groupe %@ ! Les notifications sont désactivées ! No comment provided by engineer. + + Notifications privacy + No comment provided by engineer. + Now admins can: - delete members' messages. @@ -4751,8 +4899,8 @@ Nécessite l'activation d'un VPN. Les hôtes .onion ne seront pas utilisés. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. Seuls les appareils clients stockent les profils des utilisateurs, les contacts, les groupes et les messages envoyés avec un **chiffrement de bout en bout à deux couches**. No comment provided by engineer. @@ -4836,6 +4984,10 @@ Nécessite l'activation d'un VPN. Ouvrir les Paramètres No comment provided by engineer. + + Open changes + No comment provided by engineer. + Open chat Ouvrir le chat @@ -4846,6 +4998,10 @@ Nécessite l'activation d'un VPN. Ouvrir la console du chat authentication reason + + Open conditions + No comment provided by engineer. + Open group Ouvrir le groupe @@ -4856,26 +5012,19 @@ Nécessite l'activation d'un VPN. Ouvrir le transfert vers un autre appareil authentication reason - - Open server settings - Ouvrir les paramètres du serveur - No comment provided by engineer. - - - Open user profiles - Ouvrir les profils d'utilisateurs - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Protocole et code open-source – n'importe qui peut heberger un serveur. - No comment provided by engineer. - Opening app… Ouverture de l'app… No comment provided by engineer. + + Operator + No comment provided by engineer. + + + Operator server + alert title + Or paste archive link Ou coller le lien de l'archive @@ -4896,16 +5045,15 @@ Nécessite l'activation d'un VPN. Ou présenter ce code No comment provided by engineer. + + Or to share privately + No comment provided by engineer. + Other Autres No comment provided by engineer. - - Other %@ servers - Autres serveurs %@ - No comment provided by engineer. - Other file errors: %@ @@ -4985,13 +5133,8 @@ Nécessite l'activation d'un VPN. En attente No comment provided by engineer. - - People can connect to you only via the links you share. - On ne peut se connecter à vous qu’avec les liens que vous partagez. - No comment provided by engineer. - - - Periodically + + Periodic Périodique No comment provided by engineer. @@ -5113,16 +5256,15 @@ Erreur : %@ Conserver le brouillon du dernier message, avec les pièces jointes. No comment provided by engineer. - - Preset server - Serveur prédéfini - No comment provided by engineer. - Preset server address Adresse du serveur prédéfinie No comment provided by engineer. + + Preset servers + No comment provided by engineer. + Preview Aperçu @@ -5201,7 +5343,7 @@ Erreur : %@ Profile update will be sent to your contacts. La mise à jour du profil sera envoyée à vos contacts. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -5294,6 +5436,10 @@ Activez-le dans les paramètres *Réseau et serveurs*. Proxy requires password No comment provided by engineer. + + Push Notifications + No comment provided by engineer. + Push notifications Notifications push @@ -5334,26 +5480,21 @@ Activez-le dans les paramètres *Réseau et serveurs*. En savoir plus No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - Pour en savoir plus, consultez le [Guide de l'utilisateur](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - No comment provided by engineer. - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). Pour en savoir plus, consultez le [Guide de l'utilisateur](https ://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + Pour en savoir plus, consultez le [Guide de l'utilisateur](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). Pour en savoir plus, consultez le [Guide de l'utilisateur](https://simplex.chat/docs/guide/readme.html#connect-to-friends). No comment provided by engineer. - - Read more in our GitHub repository. - Plus d'informations sur notre GitHub. - No comment provided by engineer. - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). Pour en savoir plus, consultez notre [dépôt GitHub](https://github.com/simplex-chat/simplex-chat#readme). @@ -5669,6 +5810,14 @@ Activez-le dans les paramètres *Réseau et serveurs*. Révéler chat item action + + Review conditions + No comment provided by engineer. + + + Review later + No comment provided by engineer. + Revoke Révoquer @@ -5713,6 +5862,14 @@ Activez-le dans les paramètres *Réseau et serveurs*. Groupes plus sûrs No comment provided by engineer. + + Same conditions will apply to operator **%@**. + No comment provided by engineer. + + + Same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + Save Enregistrer @@ -5782,7 +5939,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Save servers? Enregistrer les serveurs ? - No comment provided by engineer. + alert title Save welcome message? @@ -5991,11 +6148,6 @@ Activez-le dans les paramètres *Réseau et serveurs*. Envoi de notifications No comment provided by engineer. - - Send notifications: - Envoi de notifications : - No comment provided by engineer. - Send questions and ideas Envoyez vos questions et idées @@ -6120,6 +6272,10 @@ Activez-le dans les paramètres *Réseau et serveurs*. Server No comment provided by engineer. + + Server added to operator %@. + alert message + Server address Adresse du serveur @@ -6135,6 +6291,18 @@ Activez-le dans les paramètres *Réseau et serveurs*. L'adresse du serveur est incompatible avec les paramètres réseau : %@. No comment provided by engineer. + + Server operator changed. + alert title + + + Server operators + No comment provided by engineer. + + + Server protocol changed. + alert title + Server requires authorization to create queues, check password Le serveur requiert une autorisation pour créer des files d'attente, vérifiez le mot de passe @@ -6252,22 +6420,35 @@ Activez-le dans les paramètres *Réseau et serveurs*. Share Partager - chat item action + alert action + chat item action Share 1-time link Partager un lien unique No comment provided by engineer. + + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + No comment provided by engineer. + Share address Partager l'adresse No comment provided by engineer. + + Share address publicly + No comment provided by engineer. + Share address with contacts? Partager l'adresse avec vos contacts ? - No comment provided by engineer. + alert title Share from other apps. @@ -6383,6 +6564,14 @@ Activez-le dans les paramètres *Réseau et serveurs*. Adresse SimpleX No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + No comment provided by engineer. + SimpleX contact address Adresse de contact SimpleX @@ -6471,6 +6660,11 @@ Activez-le dans les paramètres *Réseau et serveurs*. L'importation a entraîné des erreurs non fatales : No comment provided by engineer. + + Some servers failed the test: +%@ + alert message + Somebody Quelqu'un @@ -6554,12 +6748,12 @@ Activez-le dans les paramètres *Réseau et serveurs*. Stop sharing Cesser le partage - No comment provided by engineer. + alert action Stop sharing address? Cesser le partage d'adresse ? - No comment provided by engineer. + alert title Stopping chat @@ -6706,7 +6900,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Tests failed! Échec des tests ! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6723,11 +6917,6 @@ Activez-le dans les paramètres *Réseau et serveurs*. Merci aux utilisateurs - contribuez via Weblate ! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - La 1ère plateforme sans aucun identifiant d'utilisateur – privée par design. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6740,6 +6929,10 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. L'application peut vous avertir lorsque vous recevez des messages ou des demandes de contact - veuillez ouvrir les paramètres pour les activer. No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). L'application demandera de confirmer les téléchargements à partir de serveurs de fichiers inconnus (sauf .onion). @@ -6755,6 +6948,10 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Le code scanné n'est pas un code QR de lien SimpleX. No comment provided by engineer. + + The connection reached the limit of undelivered messages, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! La connexion que vous avez acceptée sera annulée ! @@ -6775,6 +6972,11 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Le chiffrement fonctionne et le nouvel accord de chiffrement n'est pas nécessaire. Cela peut provoquer des erreurs de connexion ! No comment provided by engineer. + + The future of messaging + La nouvelle génération de messagerie privée + No comment provided by engineer. + The hash of the previous message is different. Le hash du message précédent est différent. @@ -6800,11 +7002,6 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Les messages seront marqués comme modérés pour tous les membres. No comment provided by engineer. - - The next generation of private messaging - La nouvelle génération de messagerie privée - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. L'ancienne base de données n'a pas été supprimée lors de la migration, elle peut être supprimée. @@ -6815,6 +7012,10 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Le profil n'est partagé qu'avec vos contacts. No comment provided by engineer. + + The second preset operator in the app! + No comment provided by engineer. + The second tick we missed! ✅ Le deuxième coche que nous avons manqué ! ✅ @@ -6830,6 +7031,10 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Les serveurs pour les nouvelles connexions de votre profil de chat actuel **%@**. No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. Le texte collé n'est pas un lien SimpleX. @@ -6844,6 +7049,10 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Thèmes No comment provided by engineer. + + These conditions will also apply for: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. Ces paramètres s'appliquent à votre profil actuel **%@**. @@ -6944,9 +7153,8 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Pour établir une nouvelle connexion No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Pour protéger votre vie privée, au lieu d’IDs utilisés par toutes les autres plateformes, SimpleX a des IDs pour les queues de messages, distinctes pour chacun de vos contacts. + + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -6966,6 +7174,15 @@ You will be prompted to complete authentication before this feature is enabled.< Vous serez invité à confirmer l'authentification avant que cette fonction ne soit activée. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + Pour protéger votre vie privée, au lieu d’IDs utilisés par toutes les autres plateformes, SimpleX a des IDs pour les queues de messages, distinctes pour chacun de vos contacts. + No comment provided by engineer. + + + To receive + No comment provided by engineer. + To record speech please grant permission to use Microphone. No comment provided by engineer. @@ -6984,11 +7201,19 @@ Vous serez invité à confirmer l'authentification avant que cette fonction ne s Pour révéler votre profil caché, entrez le mot de passe dans le champ de recherche de la page **Vos profils de chat**. No comment provided by engineer. + + To send + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. Pour prendre en charge les notifications push instantanées, la base de données du chat doit être migrée. No comment provided by engineer. + + To use the servers of **%@**, accept conditions of use. + No comment provided by engineer. + To verify end-to-end encryption with your contact compare (or scan) the code on your devices. Pour vérifier le chiffrement de bout en bout avec votre contact, comparez (ou scannez) le code sur vos appareils. @@ -7079,6 +7304,10 @@ Vous serez invité à confirmer l'authentification avant que cette fonction ne s Débloquer ce membre ? No comment provided by engineer. + + Undelivered messages + No comment provided by engineer. + Unexpected migration state État de la migration inattendu @@ -7236,6 +7465,10 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Envoi de l'archive No comment provided by engineer. + + Use %@ + No comment provided by engineer. + Use .onion hosts Utiliser les hôtes .onions @@ -7260,6 +7493,14 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Utiliser le profil actuel No comment provided by engineer. + + Use for files + No comment provided by engineer. + + + Use for messages + No comment provided by engineer. + Use for new connections Utiliser pour les nouvelles connexions @@ -7300,6 +7541,10 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Utiliser ce serveur No comment provided by engineer. + + Use servers + No comment provided by engineer. + Use the app while in the call. Utiliser l'application pendant l'appel. @@ -7389,11 +7634,19 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Vidéos et fichiers jusqu'à 1Go No comment provided by engineer. + + View conditions + No comment provided by engineer. + View security code Afficher le code de sécurité No comment provided by engineer. + + View updated conditions + No comment provided by engineer. + Visible history Historique visible @@ -7504,9 +7757,8 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Lors des appels audio et vidéo. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - Vous pouvez accepter ou refuser les demandes de contacts. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -7666,6 +7918,18 @@ Répéter la demande d'adhésion ? Vous pouvez choisir de le modifier dans les paramètres d'apparence. No comment provided by engineer. + + You can configure operators in Network & servers settings. + No comment provided by engineer. + + + You can configure servers via settings. + No comment provided by engineer. + + + You can create it in user picker. + No comment provided by engineer. + You can create it later Vous pouvez la créer plus tard @@ -7706,6 +7970,10 @@ Répéter la demande d'adhésion ? Vous pouvez envoyer des messages à %@ à partir des contacts archivés. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + No comment provided by engineer. + You can set lock screen notification preview via settings. Vous pouvez configurer l'aperçu des notifications sur l'écran de verrouillage via les paramètres. @@ -7721,11 +7989,6 @@ Répéter la demande d'adhésion ? Vous pouvez partager cette adresse avec vos contacts pour leur permettre de se connecter avec **%@**. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - Vous pouvez partager votre adresse sous la forme d'un lien ou d'un code QR - tout le monde peut l'utiliser pour vous contacter. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app Vous pouvez lancer le chat via Paramètres / Base de données ou en redémarrant l'app @@ -7749,23 +8012,23 @@ Répéter la demande d'adhésion ? You can view invitation link again in connection details. Vous pouvez à nouveau consulter le lien d'invitation dans les détails de la connexion. - No comment provided by engineer. + alert message You can't send messages! Vous ne pouvez pas envoyer de messages ! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - Vous contrôlez par quel·s serveur·s vous pouvez **transmettre** ainsi que par quel·s serveur·s vous pouvez **recevoir** les messages de vos contacts. - No comment provided by engineer. - You could not be verified; please try again. Vous n'avez pas pu être vérifié·e ; veuillez réessayer. No comment provided by engineer. + + You decide who can connect. + Vous choisissez qui peut se connecter. + No comment provided by engineer. + You have already requested connection via this address! Vous avez déjà demandé une connexion via cette adresse ! @@ -7888,11 +8151,6 @@ Répéter la demande de connexion ? Vous utilisez un profil incognito pour ce groupe - pour éviter de partager votre profil principal ; inviter des contacts n'est pas possible No comment provided by engineer. - - Your %@ servers - Vos serveurs %@ - No comment provided by engineer. - Your ICE servers Vos serveurs ICE @@ -7908,11 +8166,6 @@ Répéter la demande de connexion ? Votre adresse SimpleX No comment provided by engineer. - - Your XFTP servers - Vos serveurs XFTP - No comment provided by engineer. - Your calls Vos appels @@ -8009,16 +8262,15 @@ Répéter la demande de connexion ? Votre profil aléatoire No comment provided by engineer. - - Your server - Votre serveur - No comment provided by engineer. - Your server address Votre adresse de serveur No comment provided by engineer. + + Your servers + No comment provided by engineer. + Your settings Vos paramètres @@ -8439,6 +8691,10 @@ Répéter la demande de connexion ? expiré No comment provided by engineer. + + for better metadata privacy. + No comment provided by engineer. + forwarded transféré @@ -9061,6 +9317,33 @@ dernier message reçu : %2$@ + +
+ +
+ + + %d new events + notification body + + + From: %@ + notification body + + + New events + notification + + + New messages + notification + + + New messages in %d chats + notification body + + +
diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/fr.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff b/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff index 928a01dead..219812651a 100644 --- a/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff +++ b/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff @@ -217,23 +217,18 @@ Available in v5.1 ) No comment provided by engineer. - - **Add new contact**: to create your one-time QR Code or link for your contact. - **הוסיפו איש קשר חדש**: ליצירת קוד QR או קישור חד־פעמיים עבור איש הקשר שלכם. - No comment provided by engineer. - **Create link / QR code** for your contact to use. **צור קישור / קוד QR** לשימוש איש הקשר שלך. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **יותר פרטי**: בדוק הודעות חדשות כל 20 דקות. אסימון המכשיר משותף עם שרת SimpleX Chat, אך לא כמה אנשי קשר או הודעות יש לך. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **הכי פרטי**: אל תשתמש בשרת ההתראות של SimpleX Chat, בדוק הודעות מעת לעת ברקע (תלוי בתדירות השימוש באפליקציה). No comment provided by engineer. @@ -247,8 +242,8 @@ Available in v5.1 **שימו לב**: לא ניתן יהיה לשחזר או לשנות את הסיסמה אם תאבדו אותה. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **מומלץ**: אסימון מכשיר והתראות נשלחים לשרת ההתראות של SimpleX Chat, אך לא תוכן ההודעה, גודלה או ממי היא. No comment provided by engineer. @@ -2115,8 +2110,8 @@ Available in v5.1 מיד No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam חסין מפני ספאם ושימוש לרעה No comment provided by engineer. @@ -2701,8 +2696,8 @@ Available in v5.1 לא ייעשה שימוש במארחי Onion. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. No comment provided by engineer. @@ -2761,8 +2756,8 @@ Available in v5.1 Open user profiles authentication reason - - Open-source protocol and code – anybody can run the servers. + + Anybody can host servers. No comment provided by engineer. @@ -2817,8 +2812,8 @@ Available in v5.1 Paste the link you received into the box below to connect with your contact. No comment provided by engineer. - - People can connect to you only via the links you share. + + You decide who can connect. No comment provided by engineer. @@ -3521,8 +3516,8 @@ Available in v5.1 Thanks to the users – contribute via Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. + + No user identifiers. No comment provided by engineer. @@ -3566,8 +3561,8 @@ It can happen because of some bug or when the connection is compromised.The message will be marked as moderated for all members. No comment provided by engineer. - - The next generation of private messaging + + The future of messaging No comment provided by engineer. @@ -3638,8 +3633,8 @@ It can happen because of some bug or when the connection is compromised.To make a new connection No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. No comment provided by engineer. @@ -4005,10 +4000,6 @@ SimpleX Lock must be enabled. You can't send messages! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - No comment provided by engineer. - You could not be verified; please try again. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff b/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff index 50f5536e5e..7ae670185c 100644 --- a/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff +++ b/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff @@ -181,23 +181,18 @@ ) No comment provided by engineer. - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Dodajte novi kontakt**: da biste stvorili svoj jednokratni QR kôd ili vezu za svoj kontakt. - No comment provided by engineer. - **Create link / QR code** for your contact to use. **Stvorite vezu / QR kôd** za vaš kontakt. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **Privatnije**: provjeravajte nove poruke svakih 20 minuta. Token uređaja dijeli se s SimpleX Chat poslužiteljem, ali ne i s brojem kontakata ili poruka koje imate. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **Najprivatniji**: nemojte koristiti SimpleX Chat poslužitelj obavijesti, povremeno provjeravajte poruke u pozadini (ovisi o tome koliko često koristite aplikaciju). No comment provided by engineer. @@ -211,8 +206,8 @@ **Imajte na umu**: NEĆETE moći oporaviti ili promijeniti pristupni izraz ako ga izgubite. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **Preporučeno**: token uređaja i obavijesti šalju se na poslužitelj obavijesti SimpleX Chata, ali ne i sadržaj poruke, veličinu ili od koga je. No comment provided by engineer. @@ -1519,8 +1514,8 @@ Image will be received when your contact is online, please wait or check later! No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam No comment provided by engineer. @@ -1917,8 +1912,8 @@ We will be adding server redundancy to prevent lost messages. Onion hosts will not be used. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. No comment provided by engineer. @@ -1965,8 +1960,8 @@ We will be adding server redundancy to prevent lost messages. Open chat console authentication reason - - Open-source protocol and code – anybody can run the servers. + + Anybody can host servers. No comment provided by engineer. @@ -1997,8 +1992,8 @@ We will be adding server redundancy to prevent lost messages. Paste the link you received into the box below to connect with your contact. No comment provided by engineer. - - People can connect to you only via the links you share. + + You decide who can connect. No comment provided by engineer. @@ -2577,8 +2572,8 @@ We will be adding server redundancy to prevent lost messages. Thanks to the users – contribute via Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. + + No user identifiers. No comment provided by engineer. @@ -2609,8 +2604,8 @@ We will be adding server redundancy to prevent lost messages. The microphone does not work when the app is in the background. No comment provided by engineer. - - The next generation of private messaging + + The future of messaging No comment provided by engineer. @@ -2673,8 +2668,8 @@ We will be adding server redundancy to prevent lost messages. To prevent the call interruption, enable Do Not Disturb mode. No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. No comment provided by engineer. @@ -2959,10 +2954,6 @@ To connect, please ask your contact to create another connection link and check You can use markdown to format messages: No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - No comment provided by engineer. - You could not be verified; please try again. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 0c8c1635a5..81cece7794 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -12,21 +12,6 @@ No comment provided by engineer. - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - ( ( @@ -127,6 +112,14 @@ %@ hitelesítve No comment provided by engineer. + + %@ server + No comment provided by engineer. + + + %@ servers + No comment provided by engineer. + %@ uploaded %@ feltöltve @@ -352,28 +345,23 @@ ) No comment provided by engineer. - - **Add contact**: to create a new invitation link, or connect via a link you received. + + **Create 1-time link**: to create and share a new invitation link. **Ismerős hozzáadása:** új meghívó-hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz. No comment provided by engineer. - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Új ismerős hozzáadása:** egyszer használható QR-kód vagy hivatkozás létrehozása az ismerőse számára. - No comment provided by engineer. - **Create group**: to create a new group. **Csoport létrehozása:** új csoport létrehozásához. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **Privátabb:** 20 percenként ellenőrzi az új üzeneteket. Az eszköztoken megosztásra kerül a SimpleX Chat-kiszolgálóval, de az nem, hogy hány ismerőse vagy üzenete van. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **Legprivátabb:** ne használja a SimpleX Chat értesítési kiszolgálót, rendszeresen ellenőrizze az üzeneteket a háttérben (attól függően, hogy milyen gyakran használja az alkalmazást). No comment provided by engineer. @@ -387,11 +375,15 @@ **Megjegyzés:** NEM tudja visszaállítani vagy megváltoztatni jelmondatát, ha elveszíti azt. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **Megjegyzés:** az eszköztoken és az értesítések elküldésre kerülnek a SimpleX Chat értesítési kiszolgálóra, kivéve az üzenet tartalma, mérete vagy az, hogy kitől származik. No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **Figyelmeztetés:** Az azonnali push-értesítésekhez a kulcstartóban tárolt jelmondat megadása szükséges. @@ -498,6 +490,14 @@ 1 hét time interval + + 1-time link + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + No comment provided by engineer. + 5 minutes 5 perc @@ -567,21 +567,11 @@ Címváltoztatás megszakítása?? No comment provided by engineer. - - About SimpleX - A SimpleXről - No comment provided by engineer. - About SimpleX Chat A SimpleX Chatről No comment provided by engineer. - - About SimpleX address - A SimpleX-címről - No comment provided by engineer. - Accent Kiemelés @@ -594,6 +584,10 @@ accept incoming call via notification swipe action + + Accept conditions + No comment provided by engineer. + Accept connection request? Kapcsolatkérés elfogadása? @@ -610,6 +604,10 @@ accept contact request via notification swipe action + + Accepted conditions + No comment provided by engineer. + Acknowledged Nyugtázva @@ -630,16 +628,6 @@ Cím hozzáadása a profilhoz, hogy az ismerősei megoszthassák másokkal. A profilfrissítés elküldésre kerül az ismerősei számára. No comment provided by engineer. - - Add contact - Ismerős hozzáadása - No comment provided by engineer. - - - Add preset servers - Előre beállított kiszolgálók hozzáadása - No comment provided by engineer. - Add profile Profil hozzáadása @@ -665,6 +653,14 @@ Üdvözlőüzenet hozzáadása No comment provided by engineer. + + Added media & file servers + No comment provided by engineer. + + + Added message servers + No comment provided by engineer. + Additional accent További kiemelés @@ -690,6 +686,14 @@ A cím módosítása megszakad. A régi fogadási cím kerül felhasználásra. No comment provided by engineer. + + Address or 1-time link? + No comment provided by engineer. + + + Address settings + No comment provided by engineer. + Admins can block a member for all. Az adminok egy tagot mindenki számára letilthatnak. @@ -735,6 +739,10 @@ Minden csoporttag kapcsolódva marad. No comment provided by engineer. + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + No comment provided by engineer. + All messages will be deleted - this cannot be undone! Minden üzenet törlésre kerül – ez a művelet nem vonható vissza! @@ -915,6 +923,11 @@ Hívás fogadása No comment provided by engineer. + + Anybody can host servers. + Bárki üzemeltethet kiszolgálókat. + No comment provided by engineer. + App build: %@ Az alkalmazás build száma: %@ @@ -1258,7 +1271,8 @@ Cancel Mégse - alert button + alert action + alert button Cancel migration @@ -1341,6 +1355,10 @@ authentication reason set passcode view + + Change user profiles + authentication reason + Chat archive Csevegési archívum @@ -1426,10 +1444,18 @@ Csevegések No comment provided by engineer. + + Check messages every 20 min. + No comment provided by engineer. + + + Check messages when allowed. + No comment provided by engineer. + Check server address and try again. Kiszolgáló címének ellenőrzése és újrapróbálkozás. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1516,16 +1542,47 @@ Elkészült No comment provided by engineer. + + Conditions accepted on: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for following operator(s): **%@**. + No comment provided by engineer. + + + Conditions of use + No comment provided by engineer. + + + Conditions will be accepted for enabled operators after 30 days. + No comment provided by engineer. + + + Conditions will be accepted for operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + No comment provided by engineer. + Configure ICE servers ICE-kiszolgálók beállítása No comment provided by engineer. - - Configured %@ servers - Beállított %@ kiszolgálók - No comment provided by engineer. - Confirm Megerősítés @@ -1715,6 +1772,10 @@ Ez az Ön egyszer használható hivatkozása! Kapcsolatkérés elküldve! No comment provided by engineer. + + Connection security + No comment provided by engineer. + Connection terminated Kapcsolat megszakítva @@ -1830,6 +1891,10 @@ Ez az Ön egyszer használható hivatkozása! Létrehozás No comment provided by engineer. + + Create 1-time link + No comment provided by engineer. + Create SimpleX address SimpleX-cím létrehozása @@ -1840,11 +1905,6 @@ Ez az Ön egyszer használható hivatkozása! Csoport létrehozása véletlenszerűen létrehozott profillal. No comment provided by engineer. - - Create an address to let people connect with you. - Cím létrehozása, hogy az emberek kapcsolatba léphessenek Önnel. - No comment provided by engineer. - Create file Fájl létrehozása @@ -1925,6 +1985,10 @@ Ez az Ön egyszer használható hivatkozása! Jelenlegi jelkód No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + No comment provided by engineer. + Current passphrase… Jelenlegi jelmondat… @@ -2081,7 +2145,8 @@ Ez az Ön egyszer használható hivatkozása! Delete Törlés - chat item action + alert action + chat item action swipe action @@ -2299,6 +2364,10 @@ Ez az Ön egyszer használható hivatkozása! Törlési hibák No comment provided by engineer. + + Delivered even when Apple drops them. + No comment provided by engineer. + Delivery Kézbesítés @@ -2580,6 +2649,10 @@ Ez az Ön egyszer használható hivatkozása! Időtartam No comment provided by engineer. + + E2E encrypted notifications. + No comment provided by engineer. + Edit Szerkesztés @@ -2600,6 +2673,10 @@ Ez az Ön egyszer használható hivatkozása! Engedélyezés (felülírások megtartásával) No comment provided by engineer. + + Enable Flux + No comment provided by engineer. + Enable SimpleX Lock SimpleX-zár bekapcsolása @@ -2805,6 +2882,10 @@ Ez az Ön egyszer használható hivatkozása! Hiba a cím megváltoztatásának megszakításakor No comment provided by engineer. + + Error accepting conditions + alert title + Error accepting contact request Hiba történt a kapcsolatkérés elfogadásakor @@ -2820,6 +2901,10 @@ Ez az Ön egyszer használható hivatkozása! Hiba a tag(ok) hozzáadásakor No comment provided by engineer. + + Error adding server + alert title + Error changing address Hiba a cím megváltoztatásakor @@ -2960,10 +3045,9 @@ Ez az Ön egyszer használható hivatkozása! Hiba a csoporthoz való csatlakozáskor No comment provided by engineer. - - Error loading %@ servers - Hiba a(z) %@ -kiszolgálók betöltésekor - No comment provided by engineer. + + Error loading servers + alert title Error migrating settings @@ -3000,11 +3084,6 @@ Ez az Ön egyszer használható hivatkozása! Hiba a statisztikák visszaállításakor No comment provided by engineer. - - Error saving %@ servers - Hiba történt a(z) %@ -kiszolgálók mentésekor - No comment provided by engineer. - Error saving ICE servers Hiba az ICE-kiszolgálók mentésekor @@ -3025,6 +3104,10 @@ Ez az Ön egyszer használható hivatkozása! Hiba a jelmondat kulcstartóba történő mentésekor No comment provided by engineer. + + Error saving servers + alert title + Error saving settings Hiba a beállítások mentésekor @@ -3095,6 +3178,10 @@ Ez az Ön egyszer használható hivatkozása! Hiba az üzenet frissítésekor No comment provided by engineer. + + Error updating server + alert title + Error updating settings Hiba történt a beállítások frissítésekor @@ -3140,6 +3227,10 @@ Ez az Ön egyszer használható hivatkozása! Hibák No comment provided by engineer. + + Errors in servers configuration. + servers error + Even when disabled in the conversation. Akkor is, ha le van tiltva a beszélgetésben. @@ -3342,11 +3433,27 @@ Ez az Ön egyszer használható hivatkozása! Csoporttag általi javítás nem támogatott No comment provided by engineer. + + For chat profile %@: + servers error + For console Konzolhoz No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + No comment provided by engineer. + + + For private routing + No comment provided by engineer. + + + For social media + No comment provided by engineer. + Forward Továbbítás @@ -3656,9 +3763,12 @@ Hiba: %2$@ Hogyan működik a SimpleX No comment provided by engineer. - - How it works - Hogyan működik + + How it affects privacy + No comment provided by engineer. + + + How it helps privacy No comment provided by engineer. @@ -3731,8 +3841,8 @@ Hiba: %2$@ Azonnal No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Spam és visszaélések elleni védelem No comment provided by engineer. @@ -3873,6 +3983,11 @@ További fejlesztések hamarosan! A [SimpleX Chat terminálhoz] telepítése (https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Azonnal + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3880,11 +3995,6 @@ További fejlesztések hamarosan! No comment provided by engineer. - - Instantly - Azonnal - No comment provided by engineer. - Interface Felület @@ -3933,7 +4043,7 @@ További fejlesztések hamarosan! Invalid server address! Érvénytelen kiszolgálócím! - No comment provided by engineer. + alert title Invalid status @@ -4061,7 +4171,7 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! Keep Megtart - No comment provided by engineer. + alert action Keep conversation @@ -4076,7 +4186,7 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! Keep unused invitation? Fel nem használt meghívó megtartása? - No comment provided by engineer. + alert title Keep your connections @@ -4163,11 +4273,6 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! Élő üzenetek No comment provided by engineer. - - Local - Helyi - No comment provided by engineer. - Local name Helyi név @@ -4188,11 +4293,6 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! Zárolási mód No comment provided by engineer. - - Make a private connection - Privát kapcsolat létrehozása - No comment provided by engineer. - Make one message disappear Egy üzenet eltüntetése @@ -4203,21 +4303,11 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! Tegye priváttá a profilját! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Győződjön meg arról, hogy a(z) %@ kiszolgálócímek megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. Győződjön meg arról, hogy a WebRTC ICE-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Sokan kérdezték: *ha a SimpleX Chatnek nincsenek felhasználó-azonosítói, akkor hogyan tud üzeneteket kézbesíteni?* - No comment provided by engineer. - Mark deleted for everyone Jelölje meg mindenki számára töröltként @@ -4498,6 +4588,10 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! Megbízhatóbb hálózati kapcsolat. No comment provided by engineer. + + More reliable notifications + No comment provided by engineer. + Most likely this connection is deleted. Valószínűleg ez a kapcsolat törlésre került. @@ -4533,6 +4627,10 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! Internetkapcsolat No comment provided by engineer. + + Network decentralization + No comment provided by engineer. + Network issues - message expired after many attempts to send it. Hálózati problémák - az üzenet többszöri elküldési kísérlet után lejárt. @@ -4543,6 +4641,10 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! Hálózatkezelés No comment provided by engineer. + + Network operator + No comment provided by engineer. + Network settings Hálózati beállítások @@ -4603,6 +4705,10 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! Új megjelenítési név No comment provided by engineer. + + New events + notification + New in %@ Újdonságok a(z) %@ verzióban @@ -4628,6 +4734,10 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! Új jelmondat… No comment provided by engineer. + + New server + No comment provided by engineer. + No Nem @@ -4683,6 +4793,14 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! Nincs információ, próbálja meg újratölteni No comment provided by engineer. + + No media & file servers. + servers error + + + No message servers. + servers error + No network connection Nincs hálózati kapcsolat @@ -4703,11 +4821,37 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! Nincs engedély a hangüzenet rögzítésére No comment provided by engineer. + + No push server + Helyi + No comment provided by engineer. + No received or sent files Nincsenek fogadott vagy küldött fájlok No comment provided by engineer. + + No servers for private message routing. + servers error + + + No servers to receive files. + servers error + + + No servers to receive messages. + servers error + + + No servers to send files. + servers error + + + No user identifiers. + Nincsenek felhasználó-azonosítók. + No comment provided by engineer. + Not compatible! Nem kompatibilis! @@ -4733,6 +4877,10 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! Az értesítések le vannak tiltva! No comment provided by engineer. + + Notifications privacy + No comment provided by engineer. + Now admins can: - delete members' messages. @@ -4791,8 +4939,8 @@ VPN engedélyezése szükséges. Onion-kiszolgálók nem lesznek használva. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. Csak az eszközök alkalmazásai tárolják a felhasználó-profilokat, névjegyeket, csoportokat és a **2 rétegű végpontok közötti titkosítással** küldött üzeneteket. No comment provided by engineer. @@ -4876,6 +5024,10 @@ VPN engedélyezése szükséges. Beállítások megnyitása No comment provided by engineer. + + Open changes + No comment provided by engineer. + Open chat Csevegés megnyitása @@ -4886,6 +5038,10 @@ VPN engedélyezése szükséges. Csevegés konzol megnyitása authentication reason + + Open conditions + No comment provided by engineer. + Open group Csoport megnyitása @@ -4896,26 +5052,19 @@ VPN engedélyezése szükséges. Átköltöztetés megkezdése egy másik eszközre authentication reason - - Open server settings - Kiszolgáló-beállítások megnyitása - No comment provided by engineer. - - - Open user profiles - Felhasználó-profilok megnyitása - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Nyílt forráskódú protokoll és forráskód – bárki üzemeltethet kiszolgálókat. - No comment provided by engineer. - Opening app… Az alkalmazás megnyitása… No comment provided by engineer. + + Operator + No comment provided by engineer. + + + Operator server + alert title + Or paste archive link Vagy az archívum hivatkozásának beillesztése @@ -4936,16 +5085,15 @@ VPN engedélyezése szükséges. Vagy mutassa meg ezt a kódot No comment provided by engineer. + + Or to share privately + No comment provided by engineer. + Other További No comment provided by engineer. - - Other %@ servers - További %@ kiszolgálók - No comment provided by engineer. - Other file errors: %@ @@ -5028,13 +5176,8 @@ VPN engedélyezése szükséges. Függőben No comment provided by engineer. - - People can connect to you only via the links you share. - Az emberek csak az Ön által megosztott hivatkozáson keresztül kapcsolódhatnak. - No comment provided by engineer. - - - Periodically + + Periodic Rendszeresen No comment provided by engineer. @@ -5157,16 +5300,15 @@ Hiba: %@ Az utolsó üzenet tervezetének megőrzése a mellékletekkel együtt. No comment provided by engineer. - - Preset server - Előre beállított kiszolgáló - No comment provided by engineer. - Preset server address Előre beállított kiszolgáló címe No comment provided by engineer. + + Preset servers + No comment provided by engineer. + Preview Előnézet @@ -5245,7 +5387,7 @@ Hiba: %@ Profile update will be sent to your contacts. A profilfrissítés elküldésre került az ismerősök számára. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -5339,6 +5481,10 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. A proxy jelszót igényel No comment provided by engineer. + + Push Notifications + No comment provided by engineer. + Push notifications Push-értesítések @@ -5379,26 +5525,21 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Tudjon meg többet No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - További információ a [Használati útmutatóban](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - No comment provided by engineer. - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). További információ a [Használati útmutatóban](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + További információ a [Használati útmutatóban](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). További információ a [Használati útmutatóban](https://simplex.chat/docs/guide/readme.html#connect-to-friends). No comment provided by engineer. - - Read more in our GitHub repository. - További információ a GitHub tárolónkban. - No comment provided by engineer. - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). További információ a [GitHub tárolóban](https://github.com/simplex-chat/simplex-chat#readme). @@ -5715,6 +5856,14 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Felfedés chat item action + + Review conditions + No comment provided by engineer. + + + Review later + No comment provided by engineer. + Revoke Visszavonás @@ -5760,6 +5909,14 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Biztonságosabb csoportok No comment provided by engineer. + + Same conditions will apply to operator **%@**. + No comment provided by engineer. + + + Same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + Save Mentés @@ -5829,7 +5986,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Save servers? Kiszolgálók mentése? - No comment provided by engineer. + alert title Save welcome message? @@ -6041,11 +6198,6 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Értesítések küldése No comment provided by engineer. - - Send notifications: - Értesítések küldése: - No comment provided by engineer. - Send questions and ideas Ötletek és kérdések beküldése @@ -6171,6 +6323,10 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Kiszolgáló No comment provided by engineer. + + Server added to operator %@. + alert message + Server address Kiszolgáló címe @@ -6186,6 +6342,18 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. A kiszolgáló címe nem kompatibilis a hálózati beállításokkal: %@. No comment provided by engineer. + + Server operator changed. + alert title + + + Server operators + No comment provided by engineer. + + + Server protocol changed. + alert title + Server requires authorization to create queues, check password A kiszolgálónak engedélyre van szüksége a sorbaállítás létrehozásához, ellenőrizze jelszavát @@ -6304,22 +6472,35 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Share Megosztás - chat item action + alert action + chat item action Share 1-time link Egyszer használható hivatkozás megosztása No comment provided by engineer. + + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + No comment provided by engineer. + Share address Cím megosztása No comment provided by engineer. + + Share address publicly + No comment provided by engineer. + Share address with contacts? Megosztja a címet az ismerőseivel? - No comment provided by engineer. + alert title Share from other apps. @@ -6436,6 +6617,14 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. SimpleX-cím No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + No comment provided by engineer. + SimpleX contact address SimpleX kapcsolattartási cím @@ -6526,6 +6715,11 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Néhány nem végzetes hiba történt az importáláskor: No comment provided by engineer. + + Some servers failed the test: +%@ + alert message + Somebody Valaki @@ -6609,12 +6803,12 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Stop sharing Megosztás megállítása - No comment provided by engineer. + alert action Stop sharing address? Címmegosztás megállítása? - No comment provided by engineer. + alert title Stopping chat @@ -6764,7 +6958,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Tests failed! Sikertelen tesztek! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6781,11 +6975,6 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Köszönet a felhasználóknak - hozzájárulás a Weblate-en! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - Az első csevegési rendszer bármiféle felhasználó-azonosító nélkül - privátra lett tervezre. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6798,6 +6987,10 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. Az alkalmazás értesíteni fogja, amikor üzeneteket vagy kapcsolatkéréseket kap – beállítások megnyitása az engedélyezéshez. No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). Az alkalmazás kérni fogja az ismeretlen fájlkiszolgálókról (kivéve .onion) történő letöltések megerősítését. @@ -6813,6 +7006,10 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. A beolvasott QR-kód nem egy SimpleX QR-kód hivatkozás. No comment provided by engineer. + + The connection reached the limit of undelivered messages, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! Az Ön által elfogadott kérelem vissza lesz vonva! @@ -6833,6 +7030,11 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. A titkosítás működik, és új titkosítási egyezményre nincs szükség. Ez kapcsolati hibákat eredményezhet! No comment provided by engineer. + + The future of messaging + A privát üzenetküldés következő generációja + No comment provided by engineer. + The hash of the previous message is different. Az előző üzenet hasító értéke különbözik. @@ -6858,11 +7060,6 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. Az üzenetek moderáltként lesznek megjelölve minden tag számára. No comment provided by engineer. - - The next generation of private messaging - A privát üzenetküldés következő generációja - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. A régi adatbázis nem került eltávolításra az átköltöztetéskor, így törölhető. @@ -6873,6 +7070,10 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. A profilja csak az ismerőseivel kerül megosztásra. No comment provided by engineer. + + The second preset operator in the app! + No comment provided by engineer. + The second tick we missed! ✅ A második jelölés, amit kihagytunk! ✅ @@ -6888,6 +7089,10 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. A jelenlegi csevegési profilhoz tartozó új kapcsolatok kiszolgálói **%@**. No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. A beillesztett szöveg nem egy SimpleX-hivatkozás. @@ -6903,6 +7108,10 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. Témák No comment provided by engineer. + + These conditions will also apply for: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. Ezek a beállítások a jelenlegi **%@** profiljára vonatkoznak. @@ -7003,9 +7212,8 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. Új kapcsolat létrehozásához No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Az adatvédelem érdekében, a más csevegési platformokon megszokott felhasználó-azonosítók helyett, a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, minden egyes ismerőshöz egy-egy különbözőt. + + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -7025,6 +7233,15 @@ You will be prompted to complete authentication before this feature is enabled.< A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beállítására az eszközén. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + Az adatvédelem érdekében, a más csevegési platformokon megszokott felhasználó-azonosítók helyett, a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, minden egyes ismerőshöz egy-egy különbözőt. + No comment provided by engineer. + + + To receive + No comment provided by engineer. + To record speech please grant permission to use Microphone. A beszéd rögzítéséhez adjon engedélyt a Mikrofon használatára. @@ -7045,11 +7262,19 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll Rejtett profilja megjelenítéséhez írja be a teljes jelszavát a keresőmezőbe a **Csevegési profilok** menüben. No comment provided by engineer. + + To send + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. Az azonnali push-értesítések támogatásához a csevegési adatbázis átköltöztetése szükséges. No comment provided by engineer. + + To use the servers of **%@**, accept conditions of use. + No comment provided by engineer. + To verify end-to-end encryption with your contact compare (or scan) the code on your devices. A végpontok közötti titkosítás hitelesítéséhez hasonlítsa össze (vagy olvassa be a QR-kódot) az ismerőse eszközén lévő kóddal. @@ -7140,6 +7365,10 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll Tag feloldása? No comment provided by engineer. + + Undelivered messages + No comment provided by engineer. + Unexpected migration state Váratlan átköltöztetési állapot @@ -7297,6 +7526,10 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc Archívum feltöltése No comment provided by engineer. + + Use %@ + No comment provided by engineer. + Use .onion hosts Onion-kiszolgálók használata @@ -7322,6 +7555,14 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc Jelenlegi profil használata No comment provided by engineer. + + Use for files + No comment provided by engineer. + + + Use for messages + No comment provided by engineer. + Use for new connections Alkalmazás új kapcsolatokhoz @@ -7362,6 +7603,10 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc Kiszolgáló használata No comment provided by engineer. + + Use servers + No comment provided by engineer. + Use the app while in the call. Használja az alkalmazást hívás közben. @@ -7452,11 +7697,19 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc Videók és fájlok 1Gb méretig No comment provided by engineer. + + View conditions + No comment provided by engineer. + View security code Biztonsági kód megtekintése No comment provided by engineer. + + View updated conditions + No comment provided by engineer. + Visible history Látható előzmények @@ -7567,9 +7820,8 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc Amikor egy bejövő hang- vagy videóhívás érkezik. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - Amikor az emberek kapcsolatot kérnek, Ön elfogadhatja vagy elutasíthatja azokat. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -7729,6 +7981,18 @@ Csatlakozáskérés megismétlése? Ezt a „Megjelenés” menüben módosíthatja. No comment provided by engineer. + + You can configure operators in Network & servers settings. + No comment provided by engineer. + + + You can configure servers via settings. + No comment provided by engineer. + + + You can create it in user picker. + No comment provided by engineer. + You can create it later Létrehozás később @@ -7769,6 +8033,10 @@ Csatlakozáskérés megismétlése? Az „Archivált ismerősökből” továbbra is küldhet üzeneteket neki: %@. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + No comment provided by engineer. + You can set lock screen notification preview via settings. A beállításokon keresztül beállíthatja a lezárási képernyő értesítési előnézetét. @@ -7784,11 +8052,6 @@ Csatlakozáskérés megismétlése? Megoszthatja ezt a címet az ismerőseivel, hogy kapcsolatba léphessenek Önnel a(z) **%@** nevű profilján keresztül. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - Megoszthatja a címét egy hivatkozásként vagy QR-kódként – így bárki kapcsolódhat Önhöz. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app A csevegést az alkalmazás „Beállítások / Adatbázis” menüben vagy az alkalmazás újraindításával indíthatja el @@ -7812,23 +8075,23 @@ Csatlakozáskérés megismétlése? You can view invitation link again in connection details. A meghívó-hivatkozást újra megtekintheti a kapcsolat részleteinél. - No comment provided by engineer. + alert message You can't send messages! Nem lehet üzeneteket küldeni! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - Ön szabályozhatja, hogy mely kiszogál(ók)ón keresztül **kapja** az üzeneteket, az ismerősöket - az üzenetküldéshez használt kiszolgálókon. - No comment provided by engineer. - You could not be verified; please try again. Nem sikerült hitelesíteni; próbálja meg újra. No comment provided by engineer. + + You decide who can connect. + Ön dönti el, hogy kivel beszélget. + No comment provided by engineer. + You have already requested connection via this address! Már küldött egy kapcsolatkérést ezen a címen keresztül! @@ -7951,11 +8214,6 @@ Kapcsolatkérés megismétlése? Inkognitóprofilt használ ehhez a csoporthoz - fő profilja megosztásának elkerülése érdekében a meghívók küldése le van tiltva No comment provided by engineer. - - Your %@ servers - %@ nevű profiljához tartozó kiszolgálók - No comment provided by engineer. - Your ICE servers Saját ICE-kiszolgálók @@ -7971,11 +8229,6 @@ Kapcsolatkérés megismétlése? Profil SimpleX-címe No comment provided by engineer. - - Your XFTP servers - Saját XFTP-kiszolgálók - No comment provided by engineer. - Your calls Hívások @@ -8076,16 +8329,15 @@ Kapcsolatkérés megismétlése? Véletlenszerű profil No comment provided by engineer. - - Your server - Saját SMP-kiszolgáló - No comment provided by engineer. - Your server address Saját SMP-kiszolgálójának címe No comment provided by engineer. + + Your servers + No comment provided by engineer. + Your settings Beállítások @@ -8506,6 +8758,10 @@ Kapcsolatkérés megismétlése? lejárt No comment provided by engineer. + + for better metadata privacy. + No comment provided by engineer. + forwarded továbbított @@ -9128,6 +9384,33 @@ utoljára fogadott üzenet: %2$@ + +
+ +
+ + + %d new events + notification body + + + From: %@ + notification body + + + New events + notification + + + New messages + notification + + + New messages in %d chats + notification body + + +
diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/hu.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 488d51d225..3b75e36a86 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -12,21 +12,6 @@ No comment provided by engineer. - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - ( ( @@ -127,6 +112,14 @@ %@ è verificato/a No comment provided by engineer. + + %@ server + No comment provided by engineer. + + + %@ servers + No comment provided by engineer. + %@ uploaded %@ caricati @@ -352,14 +345,9 @@ ) No comment provided by engineer. - - **Add contact**: to create a new invitation link, or connect via a link you received. - **Aggiungi contatto**: per creare un nuovo link di invito o connetterti tramite un link che hai ricevuto. - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Aggiungi un contatto**: per creare il tuo codice QR o link una tantum per il tuo contatto. + + **Create 1-time link**: to create and share a new invitation link. + **Aggiungi contatto**: per creare un nuovo link di invito. No comment provided by engineer. @@ -367,13 +355,13 @@ **Crea gruppo**: per creare un nuovo gruppo. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **Più privato**: controlla messaggi nuovi ogni 20 minuti. Viene condiviso il token del dispositivo con il server di SimpleX Chat, ma non quanti contatti o messaggi hai. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **Il più privato**: non usare il server di notifica di SimpleX Chat, controlla i messaggi periodicamente in secondo piano (dipende da quanto spesso usi l'app). No comment provided by engineer. @@ -387,11 +375,15 @@ **Nota bene**: NON potrai recuperare o cambiare la password se la perdi. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **Consigliato**: vengono inviati il token del dispositivo e le notifiche al server di notifica di SimpleX Chat, ma non il contenuto del messaggio,la sua dimensione o il suo mittente. No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **Attenzione**: le notifiche push istantanee richiedono una password salvata nel portachiavi. @@ -498,6 +490,14 @@ 1 settimana time interval + + 1-time link + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + No comment provided by engineer. + 5 minutes 5 minuti @@ -567,21 +567,11 @@ Interrompere il cambio di indirizzo? No comment provided by engineer. - - About SimpleX - Riguardo SimpleX - No comment provided by engineer. - About SimpleX Chat Riguardo SimpleX Chat No comment provided by engineer. - - About SimpleX address - Info sull'indirizzo SimpleX - No comment provided by engineer. - Accent Principale @@ -594,6 +584,10 @@ accept incoming call via notification swipe action + + Accept conditions + No comment provided by engineer. + Accept connection request? Accettare la richiesta di connessione? @@ -610,6 +604,10 @@ accept contact request via notification swipe action + + Accepted conditions + No comment provided by engineer. + Acknowledged Riconosciuto @@ -630,16 +628,6 @@ Aggiungi l'indirizzo al tuo profilo, in modo che i tuoi contatti possano condividerlo con altre persone. L'aggiornamento del profilo verrà inviato ai tuoi contatti. No comment provided by engineer. - - Add contact - Aggiungi contatto - No comment provided by engineer. - - - Add preset servers - Aggiungi server preimpostati - No comment provided by engineer. - Add profile Aggiungi profilo @@ -665,6 +653,14 @@ Aggiungi messaggio di benvenuto No comment provided by engineer. + + Added media & file servers + No comment provided by engineer. + + + Added message servers + No comment provided by engineer. + Additional accent Principale aggiuntivo @@ -690,6 +686,14 @@ Il cambio di indirizzo verrà interrotto. Verrà usato il vecchio indirizzo di ricezione. No comment provided by engineer. + + Address or 1-time link? + No comment provided by engineer. + + + Address settings + No comment provided by engineer. + Admins can block a member for all. Gli amministratori possono bloccare un membro per tutti. @@ -735,6 +739,10 @@ Tutti i membri del gruppo resteranno connessi. No comment provided by engineer. + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + No comment provided by engineer. + All messages will be deleted - this cannot be undone! Tutti i messaggi verranno eliminati, non è reversibile! @@ -915,6 +923,11 @@ Rispondi alla chiamata No comment provided by engineer. + + Anybody can host servers. + Chiunque può installare i server. + No comment provided by engineer. + App build: %@ Build dell'app: %@ @@ -1258,7 +1271,8 @@ Cancel Annulla - alert button + alert action + alert button Cancel migration @@ -1341,6 +1355,10 @@ authentication reason set passcode view + + Change user profiles + authentication reason + Chat archive Archivio chat @@ -1426,10 +1444,18 @@ Chat No comment provided by engineer. + + Check messages every 20 min. + No comment provided by engineer. + + + Check messages when allowed. + No comment provided by engineer. + Check server address and try again. Controlla l'indirizzo del server e riprova. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1516,16 +1542,47 @@ Completato No comment provided by engineer. + + Conditions accepted on: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for following operator(s): **%@**. + No comment provided by engineer. + + + Conditions of use + No comment provided by engineer. + + + Conditions will be accepted for enabled operators after 30 days. + No comment provided by engineer. + + + Conditions will be accepted for operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + No comment provided by engineer. + Configure ICE servers Configura server ICE No comment provided by engineer. - - Configured %@ servers - Configurati %@ server - No comment provided by engineer. - Confirm Conferma @@ -1715,6 +1772,10 @@ Questo è il tuo link una tantum! Richiesta di connessione inviata! No comment provided by engineer. + + Connection security + No comment provided by engineer. + Connection terminated Connessione terminata @@ -1830,6 +1891,10 @@ Questo è il tuo link una tantum! Crea No comment provided by engineer. + + Create 1-time link + No comment provided by engineer. + Create SimpleX address Crea indirizzo SimpleX @@ -1840,11 +1905,6 @@ Questo è il tuo link una tantum! Crea un gruppo usando un profilo casuale. No comment provided by engineer. - - Create an address to let people connect with you. - Crea un indirizzo per consentire alle persone di connettersi con te. - No comment provided by engineer. - Create file Crea file @@ -1925,6 +1985,10 @@ Questo è il tuo link una tantum! Codice di accesso attuale No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + No comment provided by engineer. + Current passphrase… Password attuale… @@ -2081,7 +2145,8 @@ Questo è il tuo link una tantum! Delete Elimina - chat item action + alert action + chat item action swipe action @@ -2299,6 +2364,10 @@ Questo è il tuo link una tantum! Errori di eliminazione No comment provided by engineer. + + Delivered even when Apple drops them. + No comment provided by engineer. + Delivery Consegna @@ -2580,6 +2649,10 @@ Questo è il tuo link una tantum! Durata No comment provided by engineer. + + E2E encrypted notifications. + No comment provided by engineer. + Edit Modifica @@ -2600,6 +2673,10 @@ Questo è il tuo link una tantum! Attiva (mantieni sostituzioni) No comment provided by engineer. + + Enable Flux + No comment provided by engineer. + Enable SimpleX Lock Attiva SimpleX Lock @@ -2805,6 +2882,10 @@ Questo è il tuo link una tantum! Errore nell'interruzione del cambio di indirizzo No comment provided by engineer. + + Error accepting conditions + alert title + Error accepting contact request Errore nell'accettazione della richiesta di contatto @@ -2820,6 +2901,10 @@ Questo è il tuo link una tantum! Errore di aggiunta membro/i No comment provided by engineer. + + Error adding server + alert title + Error changing address Errore nella modifica dell'indirizzo @@ -2960,10 +3045,9 @@ Questo è il tuo link una tantum! Errore di ingresso nel gruppo No comment provided by engineer. - - Error loading %@ servers - Errore nel caricamento dei server %@ - No comment provided by engineer. + + Error loading servers + alert title Error migrating settings @@ -3000,11 +3084,6 @@ Questo è il tuo link una tantum! Errore di azzeramento statistiche No comment provided by engineer. - - Error saving %@ servers - Errore nel salvataggio dei server %@ - No comment provided by engineer. - Error saving ICE servers Errore nel salvataggio dei server ICE @@ -3025,6 +3104,10 @@ Questo è il tuo link una tantum! Errore nel salvataggio della password nel portachiavi No comment provided by engineer. + + Error saving servers + alert title + Error saving settings Errore di salvataggio delle impostazioni @@ -3095,6 +3178,10 @@ Questo è il tuo link una tantum! Errore nell'aggiornamento del messaggio No comment provided by engineer. + + Error updating server + alert title + Error updating settings Errore nell'aggiornamento delle impostazioni @@ -3140,6 +3227,10 @@ Questo è il tuo link una tantum! Errori No comment provided by engineer. + + Errors in servers configuration. + servers error + Even when disabled in the conversation. Anche quando disattivato nella conversazione. @@ -3342,11 +3433,27 @@ Questo è il tuo link una tantum! Correzione non supportata dal membro del gruppo No comment provided by engineer. + + For chat profile %@: + servers error + For console Per console No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + No comment provided by engineer. + + + For private routing + No comment provided by engineer. + + + For social media + No comment provided by engineer. + Forward Inoltra @@ -3656,9 +3763,12 @@ Errore: %2$@ Come funziona SimpleX No comment provided by engineer. - - How it works - Come funziona + + How it affects privacy + No comment provided by engineer. + + + How it helps privacy No comment provided by engineer. @@ -3731,8 +3841,8 @@ Errore: %2$@ Immediatamente No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Immune a spam e abusi No comment provided by engineer. @@ -3873,6 +3983,11 @@ Altri miglioramenti sono in arrivo! Installa [Simplex Chat per terminale](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Istantaneamente + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3880,11 +3995,6 @@ Altri miglioramenti sono in arrivo! No comment provided by engineer. - - Instantly - Istantaneamente - No comment provided by engineer. - Interface Interfaccia @@ -3933,7 +4043,7 @@ Altri miglioramenti sono in arrivo! Invalid server address! Indirizzo del server non valido! - No comment provided by engineer. + alert title Invalid status @@ -4061,7 +4171,7 @@ Questo è il tuo link per il gruppo %@! Keep Tieni - No comment provided by engineer. + alert action Keep conversation @@ -4076,7 +4186,7 @@ Questo è il tuo link per il gruppo %@! Keep unused invitation? Tenere l'invito inutilizzato? - No comment provided by engineer. + alert title Keep your connections @@ -4163,11 +4273,6 @@ Questo è il tuo link per il gruppo %@! Messaggi in diretta No comment provided by engineer. - - Local - Locale - No comment provided by engineer. - Local name Nome locale @@ -4188,11 +4293,6 @@ Questo è il tuo link per il gruppo %@! Modalità di blocco No comment provided by engineer. - - Make a private connection - Crea una connessione privata - No comment provided by engineer. - Make one message disappear Fai sparire un messaggio @@ -4203,21 +4303,11 @@ Questo è il tuo link per il gruppo %@! Rendi privato il profilo! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Assicurati che gli indirizzi dei server %@ siano nel formato corretto, uno per riga e non doppi (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. Assicurati che gli indirizzi dei server WebRTC ICE siano nel formato corretto, uno per riga e non doppi. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Molte persone hanno chiesto: *se SimpleX non ha identificatori utente, come può recapitare i messaggi?* - No comment provided by engineer. - Mark deleted for everyone Contrassegna eliminato per tutti @@ -4498,6 +4588,10 @@ Questo è il tuo link per il gruppo %@! Connessione di rete più affidabile. No comment provided by engineer. + + More reliable notifications + No comment provided by engineer. + Most likely this connection is deleted. Probabilmente questa connessione è stata eliminata. @@ -4533,6 +4627,10 @@ Questo è il tuo link per il gruppo %@! Connessione di rete No comment provided by engineer. + + Network decentralization + No comment provided by engineer. + Network issues - message expired after many attempts to send it. Problemi di rete - messaggio scaduto dopo molti tentativi di inviarlo. @@ -4543,6 +4641,10 @@ Questo è il tuo link per il gruppo %@! Gestione della rete No comment provided by engineer. + + Network operator + No comment provided by engineer. + Network settings Impostazioni di rete @@ -4603,6 +4705,10 @@ Questo è il tuo link per il gruppo %@! Nuovo nome da mostrare No comment provided by engineer. + + New events + notification + New in %@ Novità nella %@ @@ -4628,6 +4734,10 @@ Questo è il tuo link per il gruppo %@! Nuova password… No comment provided by engineer. + + New server + No comment provided by engineer. + No No @@ -4683,6 +4793,14 @@ Questo è il tuo link per il gruppo %@! Nessuna informazione, prova a ricaricare No comment provided by engineer. + + No media & file servers. + servers error + + + No message servers. + servers error + No network connection Nessuna connessione di rete @@ -4703,11 +4821,37 @@ Questo è il tuo link per il gruppo %@! Nessuna autorizzazione per registrare messaggi vocali No comment provided by engineer. + + No push server + Locale + No comment provided by engineer. + No received or sent files Nessun file ricevuto o inviato No comment provided by engineer. + + No servers for private message routing. + servers error + + + No servers to receive files. + servers error + + + No servers to receive messages. + servers error + + + No servers to send files. + servers error + + + No user identifiers. + Nessun identificatore utente. + No comment provided by engineer. + Not compatible! Non compatibile! @@ -4733,6 +4877,10 @@ Questo è il tuo link per il gruppo %@! Le notifiche sono disattivate! No comment provided by engineer. + + Notifications privacy + No comment provided by engineer. + Now admins can: - delete members' messages. @@ -4791,8 +4939,8 @@ Richiede l'attivazione della VPN. Gli host Onion non verranno usati. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. Solo i dispositivi client archiviano profili utente, i contatti, i gruppi e i messaggi inviati con la **crittografia end-to-end a 2 livelli**. No comment provided by engineer. @@ -4876,6 +5024,10 @@ Richiede l'attivazione della VPN. Apri le impostazioni No comment provided by engineer. + + Open changes + No comment provided by engineer. + Open chat Apri chat @@ -4886,6 +5038,10 @@ Richiede l'attivazione della VPN. Apri la console della chat authentication reason + + Open conditions + No comment provided by engineer. + Open group Apri gruppo @@ -4896,26 +5052,19 @@ Richiede l'attivazione della VPN. Apri migrazione ad un altro dispositivo authentication reason - - Open server settings - Apri impostazioni server - No comment provided by engineer. - - - Open user profiles - Apri i profili utente - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Protocollo e codice open source: chiunque può gestire i server. - No comment provided by engineer. - Opening app… Apertura dell'app… No comment provided by engineer. + + Operator + No comment provided by engineer. + + + Operator server + alert title + Or paste archive link O incolla il link dell'archivio @@ -4936,16 +5085,15 @@ Richiede l'attivazione della VPN. O mostra questo codice No comment provided by engineer. + + Or to share privately + No comment provided by engineer. + Other Altro No comment provided by engineer. - - Other %@ servers - Altri %@ server - No comment provided by engineer. - Other file errors: %@ @@ -5028,13 +5176,8 @@ Richiede l'attivazione della VPN. In attesa No comment provided by engineer. - - People can connect to you only via the links you share. - Le persone possono connettersi a te solo tramite i link che condividi. - No comment provided by engineer. - - - Periodically + + Periodic Periodicamente No comment provided by engineer. @@ -5157,16 +5300,15 @@ Errore: %@ Conserva la bozza dell'ultimo messaggio, con gli allegati. No comment provided by engineer. - - Preset server - Server preimpostato - No comment provided by engineer. - Preset server address Indirizzo server preimpostato No comment provided by engineer. + + Preset servers + No comment provided by engineer. + Preview Anteprima @@ -5245,7 +5387,7 @@ Errore: %@ Profile update will be sent to your contacts. L'aggiornamento del profilo verrà inviato ai tuoi contatti. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -5339,6 +5481,10 @@ Attivalo nelle impostazioni *Rete e server*. Il proxy richiede una password No comment provided by engineer. + + Push Notifications + No comment provided by engineer. + Push notifications Notifiche push @@ -5379,26 +5525,21 @@ Attivalo nelle impostazioni *Rete e server*. Leggi tutto No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - Maggiori informazioni nella [Guida per l'utente](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - No comment provided by engineer. - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). Leggi di più nella [Guida utente](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + Maggiori informazioni nella [Guida per l'utente](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). Maggiori informazioni nella [Guida per l'utente](https://simplex.chat/docs/guide/readme.html#connect-to-friends). No comment provided by engineer. - - Read more in our GitHub repository. - Maggiori informazioni nel nostro repository GitHub. - No comment provided by engineer. - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). Maggiori informazioni nel nostro [repository GitHub](https://github.com/simplex-chat/simplex-chat#readme). @@ -5715,6 +5856,14 @@ Attivalo nelle impostazioni *Rete e server*. Rivela chat item action + + Review conditions + No comment provided by engineer. + + + Review later + No comment provided by engineer. + Revoke Revoca @@ -5760,6 +5909,14 @@ Attivalo nelle impostazioni *Rete e server*. Gruppi più sicuri No comment provided by engineer. + + Same conditions will apply to operator **%@**. + No comment provided by engineer. + + + Same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + Save Salva @@ -5829,7 +5986,7 @@ Attivalo nelle impostazioni *Rete e server*. Save servers? Salvare i server? - No comment provided by engineer. + alert title Save welcome message? @@ -6041,11 +6198,6 @@ Attivalo nelle impostazioni *Rete e server*. Invia notifiche No comment provided by engineer. - - Send notifications: - Invia notifiche: - No comment provided by engineer. - Send questions and ideas Invia domande e idee @@ -6171,6 +6323,10 @@ Attivalo nelle impostazioni *Rete e server*. Server No comment provided by engineer. + + Server added to operator %@. + alert message + Server address Indirizzo server @@ -6186,6 +6342,18 @@ Attivalo nelle impostazioni *Rete e server*. L'indirizzo del server è incompatibile con le impostazioni di rete: %@. No comment provided by engineer. + + Server operator changed. + alert title + + + Server operators + No comment provided by engineer. + + + Server protocol changed. + alert title + Server requires authorization to create queues, check password Il server richiede l'autorizzazione di creare code, controlla la password @@ -6304,22 +6472,35 @@ Attivalo nelle impostazioni *Rete e server*. Share Condividi - chat item action + alert action + chat item action Share 1-time link Condividi link una tantum No comment provided by engineer. + + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + No comment provided by engineer. + Share address Condividi indirizzo No comment provided by engineer. + + Share address publicly + No comment provided by engineer. + Share address with contacts? Condividere l'indirizzo con i contatti? - No comment provided by engineer. + alert title Share from other apps. @@ -6436,6 +6617,14 @@ Attivalo nelle impostazioni *Rete e server*. Indirizzo SimpleX No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + No comment provided by engineer. + SimpleX contact address Indirizzo di contatto SimpleX @@ -6526,6 +6715,11 @@ Attivalo nelle impostazioni *Rete e server*. Si sono verificati alcuni errori non fatali durante l'importazione: No comment provided by engineer. + + Some servers failed the test: +%@ + alert message + Somebody Qualcuno @@ -6609,12 +6803,12 @@ Attivalo nelle impostazioni *Rete e server*. Stop sharing Smetti di condividere - No comment provided by engineer. + alert action Stop sharing address? Smettere di condividere l'indirizzo? - No comment provided by engineer. + alert title Stopping chat @@ -6764,7 +6958,7 @@ Attivalo nelle impostazioni *Rete e server*. Tests failed! Test falliti! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6781,11 +6975,6 @@ Attivalo nelle impostazioni *Rete e server*. Grazie agli utenti – contribuite via Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - La prima piattaforma senza alcun identificatore utente – privata by design. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6798,6 +6987,10 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.L'app può avvisarti quando ricevi messaggi o richieste di contatto: apri le impostazioni per attivare. No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). L'app chiederà di confermare i download da server di file sconosciuti (eccetto .onion). @@ -6813,6 +7006,10 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Il codice che hai scansionato non è un codice QR di link SimpleX. No comment provided by engineer. + + The connection reached the limit of undelivered messages, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! La connessione che hai accettato verrà annullata! @@ -6833,6 +7030,11 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.La crittografia funziona e il nuovo accordo sulla crittografia non è richiesto. Potrebbero verificarsi errori di connessione! No comment provided by engineer. + + The future of messaging + La nuova generazione di messaggistica privata + No comment provided by engineer. + The hash of the previous message is different. L'hash del messaggio precedente è diverso. @@ -6858,11 +7060,6 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.I messaggi verranno contrassegnati come moderati per tutti i membri. No comment provided by engineer. - - The next generation of private messaging - La nuova generazione di messaggistica privata - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. Il database vecchio non è stato rimosso durante la migrazione, può essere eliminato. @@ -6873,6 +7070,10 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Il profilo è condiviso solo con i tuoi contatti. No comment provided by engineer. + + The second preset operator in the app! + No comment provided by engineer. + The second tick we missed! ✅ Il secondo segno di spunta che ci mancava! ✅ @@ -6888,6 +7089,10 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.I server per le nuove connessioni del profilo di chat attuale **%@**. No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. Il testo che hai incollato non è un link SimpleX. @@ -6903,6 +7108,10 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Temi No comment provided by engineer. + + These conditions will also apply for: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. Queste impostazioni sono per il tuo profilo attuale **%@**. @@ -7003,9 +7212,8 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Per creare una nuova connessione No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Per proteggere la privacy, invece degli ID utente utilizzati da tutte le altre piattaforme, SimpleX ha identificatori per le code di messaggi, separati per ciascuno dei tuoi contatti. + + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -7025,6 +7233,15 @@ You will be prompted to complete authentication before this feature is enabled.< Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzionalità. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + Per proteggere la privacy, invece degli ID utente utilizzati da tutte le altre piattaforme, SimpleX ha identificatori per le code di messaggi, separati per ciascuno dei tuoi contatti. + No comment provided by engineer. + + + To receive + No comment provided by engineer. + To record speech please grant permission to use Microphone. Per registrare l'audio, concedi l'autorizzazione di usare il microfono. @@ -7045,11 +7262,19 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio Per rivelare il tuo profilo nascosto, inserisci una password completa in un campo di ricerca nella pagina **I tuoi profili di chat**. No comment provided by engineer. + + To send + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. Per supportare le notifiche push istantanee, il database della chat deve essere migrato. No comment provided by engineer. + + To use the servers of **%@**, accept conditions of use. + No comment provided by engineer. + To verify end-to-end encryption with your contact compare (or scan) the code on your devices. Per verificare la crittografia end-to-end con il tuo contatto, confrontate (o scansionate) il codice sui vostri dispositivi. @@ -7140,6 +7365,10 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio Sbloccare il membro? No comment provided by engineer. + + Undelivered messages + No comment provided by engineer. + Unexpected migration state Stato di migrazione imprevisto @@ -7297,6 +7526,10 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Invio dell'archivio No comment provided by engineer. + + Use %@ + No comment provided by engineer. + Use .onion hosts Usa gli host .onion @@ -7322,6 +7555,14 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Usa il profilo attuale No comment provided by engineer. + + Use for files + No comment provided by engineer. + + + Use for messages + No comment provided by engineer. + Use for new connections Usa per connessioni nuove @@ -7362,6 +7603,10 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Usa il server No comment provided by engineer. + + Use servers + No comment provided by engineer. + Use the app while in the call. Usa l'app mentre sei in chiamata. @@ -7452,11 +7697,19 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Video e file fino a 1 GB No comment provided by engineer. + + View conditions + No comment provided by engineer. + View security code Vedi codice di sicurezza No comment provided by engineer. + + View updated conditions + No comment provided by engineer. + Visible history Cronologia visibile @@ -7567,9 +7820,8 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Quando si connettono le chiamate audio e video. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - Quando le persone chiedono di connettersi, puoi accettare o rifiutare. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -7729,6 +7981,18 @@ Ripetere la richiesta di ingresso? Puoi cambiarlo nelle impostazioni dell'aspetto. No comment provided by engineer. + + You can configure operators in Network & servers settings. + No comment provided by engineer. + + + You can configure servers via settings. + No comment provided by engineer. + + + You can create it in user picker. + No comment provided by engineer. + You can create it later Puoi crearlo più tardi @@ -7769,6 +8033,10 @@ Ripetere la richiesta di ingresso? Puoi inviare messaggi a %@ dai contatti archiviati. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + No comment provided by engineer. + You can set lock screen notification preview via settings. Puoi impostare l'anteprima della notifica nella schermata di blocco tramite le impostazioni. @@ -7784,11 +8052,6 @@ Ripetere la richiesta di ingresso? Puoi condividere questo indirizzo con i tuoi contatti per consentire loro di connettersi con **%@**. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - Puoi condividere il tuo indirizzo come link o come codice QR: chiunque potrà connettersi a te. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app Puoi avviare la chat via Impostazioni / Database o riavviando l'app @@ -7812,23 +8075,23 @@ Ripetere la richiesta di ingresso? You can view invitation link again in connection details. Puoi vedere di nuovo il link di invito nei dettagli di connessione. - No comment provided by engineer. + alert message You can't send messages! Non puoi inviare messaggi! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - Tu decidi attraverso quale/i server **ricevere** i messaggi, i tuoi contatti quali server usi per inviare loro i messaggi. - No comment provided by engineer. - You could not be verified; please try again. Non è stato possibile verificarti, riprova. No comment provided by engineer. + + You decide who can connect. + Sei tu a decidere chi può connettersi. + No comment provided by engineer. + You have already requested connection via this address! Hai già richiesto la connessione tramite questo indirizzo! @@ -7951,11 +8214,6 @@ Ripetere la richiesta di connessione? Stai usando un profilo in incognito per questo gruppo: per impedire la condivisione del tuo profilo principale non è consentito invitare contatti No comment provided by engineer. - - Your %@ servers - I tuoi server %@ - No comment provided by engineer. - Your ICE servers I tuoi server ICE @@ -7971,11 +8229,6 @@ Ripetere la richiesta di connessione? Il tuo indirizzo SimpleX No comment provided by engineer. - - Your XFTP servers - I tuoi server XFTP - No comment provided by engineer. - Your calls Le tue chiamate @@ -8076,16 +8329,15 @@ Ripetere la richiesta di connessione? Il tuo profilo casuale No comment provided by engineer. - - Your server - Il tuo server - No comment provided by engineer. - Your server address L'indirizzo del tuo server No comment provided by engineer. + + Your servers + No comment provided by engineer. + Your settings Le tue impostazioni @@ -8506,6 +8758,10 @@ Ripetere la richiesta di connessione? scaduto No comment provided by engineer. + + for better metadata privacy. + No comment provided by engineer. + forwarded inoltrato @@ -9128,6 +9384,33 @@ ultimo msg ricevuto: %2$@ + +
+ +
+ + + %d new events + notification body + + + From: %@ + notification body + + + New events + notification + + + New messages + notification + + + New messages in %d chats + notification body + + +
diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/it.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/it.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 4fa8144d91..7f97220bc5 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -12,21 +12,6 @@ No comment provided by engineer. - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - ( ( @@ -127,6 +112,14 @@ %@ は検証されています No comment provided by engineer. + + %@ server + No comment provided by engineer. + + + %@ servers + No comment provided by engineer. + %@ uploaded %@ アップロード済 @@ -346,28 +339,23 @@ ) No comment provided by engineer. - - **Add contact**: to create a new invitation link, or connect via a link you received. + + **Create 1-time link**: to create and share a new invitation link. **コンタクトの追加**: 新しい招待リンクを作成するか、受け取ったリンクから接続します。 No comment provided by engineer. - - **Add new contact**: to create your one-time QR Code or link for your contact. - **新しい連絡先を追加**: 連絡先のワンタイム QR コードまたはリンクを作成します。 - No comment provided by engineer. - **Create group**: to create a new group. **グループ作成**: 新しいグループを作成する。 No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **よりプライベート**: 20 分ごとに新しいメッセージを確認します。 デバイス トークンは SimpleX Chat サーバーと共有されますが、連絡先やメッセージの数は共有されません。 No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **最もプライベート**: SimpleX Chat 通知サーバーを使用せず、バックグラウンドで定期的にメッセージをチェックします (アプリの使用頻度によって異なります)。 No comment provided by engineer. @@ -381,11 +369,15 @@ **注意**: パスフレーズを紛失すると、パスフレーズを復元または変更できなくなります。 No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **推奨**: デバイス トークンと通知は SimpleX Chat 通知サーバーに送信されますが、メッセージの内容、サイズ、送信者は送信されません。 No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **警告**: 即時の プッシュ通知には、キーチェーンに保存されたパスフレーズが必要です。 @@ -486,6 +478,14 @@ 1週間 time interval + + 1-time link + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + No comment provided by engineer. + 5 minutes 5分 @@ -555,21 +555,11 @@ アドレス変更を中止しますか? No comment provided by engineer. - - About SimpleX - SimpleXについて - No comment provided by engineer. - About SimpleX Chat SimpleX Chat について No comment provided by engineer. - - About SimpleX address - SimpleXアドレスについて - No comment provided by engineer. - Accent No comment provided by engineer. @@ -581,6 +571,10 @@ accept incoming call via notification swipe action + + Accept conditions + No comment provided by engineer. + Accept connection request? 接続要求を承認? @@ -597,6 +591,10 @@ accept contact request via notification swipe action + + Accepted conditions + No comment provided by engineer. + Acknowledged No comment provided by engineer. @@ -614,15 +612,6 @@ プロフィールにアドレスを追加し、連絡先があなたのアドレスを他の人と共有できるようにします。プロフィールの更新は連絡先に送信されます。 No comment provided by engineer. - - Add contact - No comment provided by engineer. - - - Add preset servers - 既存サーバを追加 - No comment provided by engineer. - Add profile プロフィールを追加 @@ -648,6 +637,14 @@ ウェルカムメッセージを追加 No comment provided by engineer. + + Added media & file servers + No comment provided by engineer. + + + Added message servers + No comment provided by engineer. + Additional accent No comment provided by engineer. @@ -670,6 +667,14 @@ アドレス変更は中止されます。古い受信アドレスが使用されます。 No comment provided by engineer. + + Address or 1-time link? + No comment provided by engineer. + + + Address settings + No comment provided by engineer. + Admins can block a member for all. No comment provided by engineer. @@ -712,6 +717,10 @@ グループ全員の接続が継続します。 No comment provided by engineer. + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + No comment provided by engineer. + All messages will be deleted - this cannot be undone! No comment provided by engineer. @@ -885,6 +894,11 @@ 通話に応答 No comment provided by engineer. + + Anybody can host servers. + プロトコル技術とコードはオープンソースで、どなたでもご自分のサーバを運用できます。 + No comment provided by engineer. + App build: %@ アプリのビルド: %@ @@ -1196,7 +1210,8 @@ Cancel 中止 - alert button + alert action + alert button Cancel migration @@ -1275,6 +1290,10 @@ authentication reason set passcode view + + Change user profiles + authentication reason + Chat archive チャットのアーカイブ @@ -1353,10 +1372,18 @@ チャット No comment provided by engineer. + + Check messages every 20 min. + No comment provided by engineer. + + + Check messages when allowed. + No comment provided by engineer. + Check server address and try again. サーバのアドレスを確認してから再度試してください。 - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1435,15 +1462,47 @@ Completed No comment provided by engineer. + + Conditions accepted on: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for following operator(s): **%@**. + No comment provided by engineer. + + + Conditions of use + No comment provided by engineer. + + + Conditions will be accepted for enabled operators after 30 days. + No comment provided by engineer. + + + Conditions will be accepted for operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + No comment provided by engineer. + Configure ICE servers ICEサーバを設定 No comment provided by engineer. - - Configured %@ servers - No comment provided by engineer. - Confirm 確認 @@ -1609,6 +1668,10 @@ This is your own one-time link! 接続リクエストを送信しました! No comment provided by engineer. + + Connection security + No comment provided by engineer. + Connection terminated No comment provided by engineer. @@ -1714,6 +1777,10 @@ This is your own one-time link! 作成 No comment provided by engineer. + + Create 1-time link + No comment provided by engineer. + Create SimpleX address SimpleXアドレスの作成 @@ -1723,11 +1790,6 @@ This is your own one-time link! Create a group using a random profile. No comment provided by engineer. - - Create an address to let people connect with you. - 人とつながるためのアドレスを作成する。 - No comment provided by engineer. - Create file ファイルを作成 @@ -1801,6 +1863,10 @@ This is your own one-time link! 現在のパスコード No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + No comment provided by engineer. + Current passphrase… 現在の暗証フレーズ… @@ -1952,7 +2018,8 @@ This is your own one-time link! Delete 削除 - chat item action + alert action + chat item action swipe action @@ -2160,6 +2227,10 @@ This is your own one-time link! Deletion errors No comment provided by engineer. + + Delivered even when Apple drops them. + No comment provided by engineer. + Delivery 配信 @@ -2417,6 +2488,10 @@ This is your own one-time link! 間隔 No comment provided by engineer. + + E2E encrypted notifications. + No comment provided by engineer. + Edit 編集する @@ -2437,6 +2512,10 @@ This is your own one-time link! 有効にする(設定の優先を維持) No comment provided by engineer. + + Enable Flux + No comment provided by engineer. + Enable SimpleX Lock SimpleXロックを有効にする @@ -2631,6 +2710,10 @@ This is your own one-time link! アドレス変更中止エラー No comment provided by engineer. + + Error accepting conditions + alert title + Error accepting contact request 連絡先リクエストの承諾にエラー発生 @@ -2646,6 +2729,10 @@ This is your own one-time link! メンバー追加にエラー発生 No comment provided by engineer. + + Error adding server + alert title + Error changing address アドレス変更にエラー発生 @@ -2779,10 +2866,9 @@ This is your own one-time link! グループ参加にエラー発生 No comment provided by engineer. - - Error loading %@ servers - %@ サーバーのロード中にエラーが発生 - No comment provided by engineer. + + Error loading servers + alert title Error migrating settings @@ -2814,11 +2900,6 @@ This is your own one-time link! Error resetting statistics No comment provided by engineer. - - Error saving %@ servers - %@ サーバの保存エラー - No comment provided by engineer. - Error saving ICE servers ICEサーバ保存にエラー発生 @@ -2839,6 +2920,10 @@ This is your own one-time link! キーチェーンにパスフレーズを保存にエラー発生 No comment provided by engineer. + + Error saving servers + alert title + Error saving settings when migrating @@ -2905,6 +2990,10 @@ This is your own one-time link! メッセージの更新にエラー発生 No comment provided by engineer. + + Error updating server + alert title + Error updating settings 設定の更新にエラー発生 @@ -2947,6 +3036,10 @@ This is your own one-time link! Errors No comment provided by engineer. + + Errors in servers configuration. + servers error + Even when disabled in the conversation. 会話中に無効になっている場合でも。 @@ -3134,11 +3227,27 @@ This is your own one-time link! グループメンバーによる修正はサポートされていません No comment provided by engineer. + + For chat profile %@: + servers error + For console コンソール No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + No comment provided by engineer. + + + For private routing + No comment provided by engineer. + + + For social media + No comment provided by engineer. + Forward chat item action @@ -3424,9 +3533,12 @@ Error: %2$@ SimpleX の仕組み No comment provided by engineer. - - How it works - 技術の説明 + + How it affects privacy + No comment provided by engineer. + + + How it helps privacy No comment provided by engineer. @@ -3497,8 +3609,8 @@ Error: %2$@ 即座に No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam スパムや悪質送信を防止 No comment provided by engineer. @@ -3629,6 +3741,11 @@ More improvements are coming soon! インストール [ターミナル用SimpleX Chat](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + すぐに + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3636,11 +3753,6 @@ More improvements are coming soon! No comment provided by engineer. - - Instantly - すぐに - No comment provided by engineer. - Interface インターフェース @@ -3682,7 +3794,7 @@ More improvements are coming soon! Invalid server address! 無効なサーバアドレス! - No comment provided by engineer. + alert title Invalid status @@ -3803,7 +3915,7 @@ This is your link for group %@! Keep - No comment provided by engineer. + alert action Keep conversation @@ -3815,7 +3927,7 @@ This is your link for group %@! Keep unused invitation? - No comment provided by engineer. + alert title Keep your connections @@ -3899,11 +4011,6 @@ This is your link for group %@! ライブメッセージ No comment provided by engineer. - - Local - 自分のみ - No comment provided by engineer. - Local name ローカルネーム @@ -3924,11 +4031,6 @@ This is your link for group %@! ロックモード No comment provided by engineer. - - Make a private connection - プライベートな接続をする - No comment provided by engineer. - Make one message disappear メッセージを1つ消す @@ -3939,21 +4041,11 @@ This is your link for group %@! プロフィールを非表示にできます! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - %@ サーバー アドレスが正しい形式で、行が区切られており、重複していないことを確認してください (%@)。 - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. WebRTC ICEサーバのアドレスを正しく1行ずつに分けて、重複しないように、形式もご確認ください。 No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - 多くの人が次のような質問をしました: *SimpleX にユーザー識別子がない場合、どうやってメッセージを配信できるのですか?* - No comment provided by engineer. - Mark deleted for everyone 全員に対して削除済みマークを付ける @@ -4206,6 +4298,10 @@ This is your link for group %@! More reliable network connection. No comment provided by engineer. + + More reliable notifications + No comment provided by engineer. + Most likely this connection is deleted. おそらく、この接続は削除されています。 @@ -4240,6 +4336,10 @@ This is your link for group %@! Network connection No comment provided by engineer. + + Network decentralization + No comment provided by engineer. + Network issues - message expired after many attempts to send it. snd error text @@ -4248,6 +4348,10 @@ This is your link for group %@! Network management No comment provided by engineer. + + Network operator + No comment provided by engineer. + Network settings ネットワーク設定 @@ -4304,6 +4408,10 @@ This is your link for group %@! 新たな表示名 No comment provided by engineer. + + New events + notification + New in %@ %@ の新機能 @@ -4328,6 +4436,10 @@ This is your link for group %@! 新しいパスフレーズ… No comment provided by engineer. + + New server + No comment provided by engineer. + No いいえ @@ -4381,6 +4493,14 @@ This is your link for group %@! No info, try to reload No comment provided by engineer. + + No media & file servers. + servers error + + + No message servers. + servers error + No network connection No comment provided by engineer. @@ -4398,11 +4518,37 @@ This is your link for group %@! 音声メッセージを録音する権限がありません No comment provided by engineer. + + No push server + 自分のみ + No comment provided by engineer. + No received or sent files 送受信済みのファイルがありません No comment provided by engineer. + + No servers for private message routing. + servers error + + + No servers to receive files. + servers error + + + No servers to receive messages. + servers error + + + No servers to send files. + servers error + + + No user identifiers. + 世界初のユーザーIDのないプラットフォーム|設計も元からプライベート。 + No comment provided by engineer. + Not compatible! No comment provided by engineer. @@ -4425,6 +4571,10 @@ This is your link for group %@! 通知が無効になっています! No comment provided by engineer. + + Notifications privacy + No comment provided by engineer. + Now admins can: - delete members' messages. @@ -4482,8 +4632,8 @@ VPN を有効にする必要があります。 オニオンのホストが使われません。 No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. **2 レイヤーのエンドツーエンド暗号化**を使用して送信されたユーザー プロファイル、連絡先、グループ、メッセージを保存できるのはクライアント デバイスのみです。 No comment provided by engineer. @@ -4566,6 +4716,10 @@ VPN を有効にする必要があります。 設定を開く No comment provided by engineer. + + Open changes + No comment provided by engineer. + Open chat チャットを開く @@ -4576,6 +4730,10 @@ VPN を有効にする必要があります。 チャットのコンソールを開く authentication reason + + Open conditions + No comment provided by engineer. + Open group No comment provided by engineer. @@ -4584,24 +4742,18 @@ VPN を有効にする必要があります。 Open migration to another device authentication reason - - Open server settings - No comment provided by engineer. - - - Open user profiles - ユーザープロフィールを開く - authentication reason - - - Open-source protocol and code – anybody can run the servers. - プロトコル技術とコードはオープンソースで、どなたでもご自分のサーバを運用できます。 - No comment provided by engineer. - Opening app… No comment provided by engineer. + + Operator + No comment provided by engineer. + + + Operator server + alert title + Or paste archive link No comment provided by engineer. @@ -4618,12 +4770,12 @@ VPN を有効にする必要があります。 Or show this code No comment provided by engineer. - - Other + + Or to share privately No comment provided by engineer. - - Other %@ servers + + Other No comment provided by engineer. @@ -4700,13 +4852,8 @@ VPN を有効にする必要があります。 Pending No comment provided by engineer. - - People can connect to you only via the links you share. - あなたと繋がることができるのは、あなたからリンクを頂いた方のみです。 - No comment provided by engineer. - - - Periodically + + Periodic 定期的に No comment provided by engineer. @@ -4820,16 +4967,15 @@ Error: %@ 添付を含めて、下書きを保存する。 No comment provided by engineer. - - Preset server - プレセットサーバ - No comment provided by engineer. - Preset server address プレセットサーバのアドレス No comment provided by engineer. + + Preset servers + No comment provided by engineer. + Preview プレビュー @@ -4900,7 +5046,7 @@ Error: %@ Profile update will be sent to your contacts. 連絡先にプロフィール更新のお知らせが届きます。 - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -4987,6 +5133,10 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. + + Push Notifications + No comment provided by engineer. + Push notifications プッシュ通知 @@ -5024,28 +5174,23 @@ Enable in *Network & servers* settings. 続きを読む No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - 詳しくは[ユーザーガイド](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)をご覧ください。 - No comment provided by engineer. - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + 詳しくは[ユーザーガイド](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)をご覧ください。 + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). 詳しくは[ユーザーガイド](https://simplex.chat/docs/guide/readme.html#connect-to-friends)をご覧ください。 No comment provided by engineer. - - Read more in our GitHub repository. - GitHubリポジトリで詳細をご確認ください。 - No comment provided by engineer. - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - 詳しくは[GitHubリポジトリ](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)をご覧ください。 + 詳しくは[GitHubリポジトリ](https://github.com/simplex-chat/simplex-chat#readme)をご覧ください。 No comment provided by engineer. @@ -5334,6 +5479,14 @@ Enable in *Network & servers* settings. 開示する chat item action + + Review conditions + No comment provided by engineer. + + + Review later + No comment provided by engineer. + Revoke 取り消す @@ -5375,6 +5528,14 @@ Enable in *Network & servers* settings. Safer groups No comment provided by engineer. + + Same conditions will apply to operator **%@**. + No comment provided by engineer. + + + Same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + Save 保存 @@ -5443,7 +5604,7 @@ Enable in *Network & servers* settings. Save servers? サーバを保存しますか? - No comment provided by engineer. + alert title Save welcome message? @@ -5635,11 +5796,6 @@ Enable in *Network & servers* settings. 通知を送信する No comment provided by engineer. - - Send notifications: - 通知を送信する: - No comment provided by engineer. - Send questions and ideas 質問やアイデアを送る @@ -5751,6 +5907,10 @@ Enable in *Network & servers* settings. Server No comment provided by engineer. + + Server added to operator %@. + alert message + Server address No comment provided by engineer. @@ -5763,6 +5923,18 @@ Enable in *Network & servers* settings. Server address is incompatible with network settings: %@. No comment provided by engineer. + + Server operator changed. + alert title + + + Server operators + No comment provided by engineer. + + + Server protocol changed. + alert title + Server requires authorization to create queues, check password キューを作成するにはサーバーの認証が必要です。パスワードを確認してください @@ -5871,22 +6043,35 @@ Enable in *Network & servers* settings. Share 共有する - chat item action + alert action + chat item action Share 1-time link 使い捨てのリンクを共有 No comment provided by engineer. + + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + No comment provided by engineer. + Share address アドレスを共有する No comment provided by engineer. + + Share address publicly + No comment provided by engineer. + Share address with contacts? アドレスを連絡先と共有しますか? - No comment provided by engineer. + alert title Share from other apps. @@ -5994,6 +6179,14 @@ Enable in *Network & servers* settings. SimpleXアドレス No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + No comment provided by engineer. + SimpleX contact address SimpleX連絡先アドレス @@ -6076,6 +6269,11 @@ Enable in *Network & servers* settings. Some non-fatal errors occurred during import: No comment provided by engineer. + + Some servers failed the test: +%@ + alert message + Somebody 誰か @@ -6154,12 +6352,12 @@ Enable in *Network & servers* settings. Stop sharing 共有を停止 - No comment provided by engineer. + alert action Stop sharing address? アドレスの共有を停止しますか? - No comment provided by engineer. + alert title Stopping chat @@ -6296,7 +6494,7 @@ Enable in *Network & servers* settings. Tests failed! テストは失敗しました! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6313,11 +6511,6 @@ Enable in *Network & servers* settings. ユーザーに感謝します – Weblate 経由で貢献してください! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - 世界初のユーザーIDのないプラットフォーム|設計も元からプライベート。 - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6330,6 +6523,10 @@ It can happen because of some bug or when the connection is compromised.アプリは、メッセージや連絡先のリクエストを受信したときに通知することができます - 設定を開いて有効にしてください。 No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). No comment provided by engineer. @@ -6343,6 +6540,10 @@ It can happen because of some bug or when the connection is compromised.The code you scanned is not a SimpleX link QR code. No comment provided by engineer. + + The connection reached the limit of undelivered messages, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! 承認済の接続がキャンセルされます! @@ -6363,6 +6564,11 @@ It can happen because of some bug or when the connection is compromised.暗号化は機能しており、新しい暗号化への同意は必要ありません。接続エラーが発生する可能性があります! No comment provided by engineer. + + The future of messaging + 次世代のプライバシー・メッセンジャー + No comment provided by engineer. + The hash of the previous message is different. 以前のメッセージとハッシュ値が異なります。 @@ -6386,11 +6592,6 @@ It can happen because of some bug or when the connection is compromised.The messages will be marked as moderated for all members. No comment provided by engineer. - - The next generation of private messaging - 次世代のプライバシー・メッセンジャー - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. 古いデータベースは移行時に削除されなかったので、削除することができます。 @@ -6401,6 +6602,10 @@ It can happen because of some bug or when the connection is compromised.プロフィールは連絡先にしか共有されません。 No comment provided by engineer. + + The second preset operator in the app! + No comment provided by engineer. + The second tick we missed! ✅ 長らくお待たせしました! ✅ @@ -6416,6 +6621,10 @@ It can happen because of some bug or when the connection is compromised.現在のチャットプロフィールの新しい接続のサーバ **%@**。 No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. No comment provided by engineer. @@ -6428,6 +6637,10 @@ It can happen because of some bug or when the connection is compromised.Themes No comment provided by engineer. + + These conditions will also apply for: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. これらの設定は現在のプロファイル **%@** 用です。 @@ -6518,9 +6731,8 @@ It can happen because of some bug or when the connection is compromised.新規に接続する場合 No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - プライバシーを保護するために、SimpleX には、他のすべてのプラットフォームで使用されるユーザー ID の代わりに、連絡先ごとに個別のメッセージ キューの識別子があります。 + + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -6539,6 +6751,15 @@ You will be prompted to complete authentication before this feature is enabled.< オンにするには、認証ステップが行われます。 No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + プライバシーを保護するために、SimpleX には、他のすべてのプラットフォームで使用されるユーザー ID の代わりに、連絡先ごとに個別のメッセージ キューの識別子があります。 + No comment provided by engineer. + + + To receive + No comment provided by engineer. + To record speech please grant permission to use Microphone. No comment provided by engineer. @@ -6557,11 +6778,19 @@ You will be prompted to complete authentication before this feature is enabled.< 非表示のプロフィールを表示するには、**チャット プロフィール** ページの検索フィールドに完全なパスワードを入力します。 No comment provided by engineer. + + To send + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. インスタント プッシュ通知をサポートするには、チャット データベースを移行する必要があります。 No comment provided by engineer. + + To use the servers of **%@**, accept conditions of use. + No comment provided by engineer. + To verify end-to-end encryption with your contact compare (or scan) the code on your devices. エンドツーエンド暗号化を確認するには、ご自分の端末と連絡先の端末のコードを比べます (スキャンします)。 @@ -6641,6 +6870,10 @@ You will be prompted to complete authentication before this feature is enabled.< Unblock member? No comment provided by engineer. + + Undelivered messages + No comment provided by engineer. + Unexpected migration state 予期しない移行状態 @@ -6788,6 +7021,10 @@ To connect, please ask your contact to create another connection link and check Uploading archive No comment provided by engineer. + + Use %@ + No comment provided by engineer. + Use .onion hosts .onionホストを使う @@ -6812,6 +7049,14 @@ To connect, please ask your contact to create another connection link and check 現在のプロファイルを使用する No comment provided by engineer. + + Use for files + No comment provided by engineer. + + + Use for messages + No comment provided by engineer. + Use for new connections 新しい接続に使う @@ -6848,6 +7093,10 @@ To connect, please ask your contact to create another connection link and check サーバを使う No comment provided by engineer. + + Use servers + No comment provided by engineer. + Use the app while in the call. No comment provided by engineer. @@ -6928,11 +7177,19 @@ To connect, please ask your contact to create another connection link and check 1GBまでのビデオとファイル No comment provided by engineer. + + View conditions + No comment provided by engineer. + View security code セキュリティコードを確認 No comment provided by engineer. + + View updated conditions + No comment provided by engineer. + Visible history chat feature @@ -7035,9 +7292,8 @@ To connect, please ask your contact to create another connection link and check When connecting audio and video calls. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - 接続が要求されたら、それを受け入れるか拒否するかを選択できます。 + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -7176,6 +7432,18 @@ Repeat join request? You can change it in Appearance settings. No comment provided by engineer. + + You can configure operators in Network & servers settings. + No comment provided by engineer. + + + You can configure servers via settings. + No comment provided by engineer. + + + You can create it in user picker. + No comment provided by engineer. + You can create it later 後からでも作成できます @@ -7213,6 +7481,10 @@ Repeat join request? You can send messages to %@ from Archived contacts. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + No comment provided by engineer. + You can set lock screen notification preview via settings. 設定からロック画面の通知プレビューを設定できます。 @@ -7228,11 +7500,6 @@ Repeat join request? このアドレスを連絡先と共有して、**%@** に接続できるようにすることができます。 No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - アドレスをリンクやQRコードとして共有することで、誰でも接続することができます。 - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app アプリの設定/データベースから、またはアプリを再起動することでチャットを開始できます @@ -7254,23 +7521,23 @@ Repeat join request? You can view invitation link again in connection details. - No comment provided by engineer. + alert message You can't send messages! メッセージを送信できませんでした! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - あなたはメッセージの受信に使用するサーバーを制御し、連絡先はあなたがメッセージの送信に使用するサーバーを使用することができます。 - No comment provided by engineer. - You could not be verified; please try again. 確認できませんでした。 もう一度お試しください。 No comment provided by engineer. + + You decide who can connect. + あなたと繋がることができるのは、あなたからリンクを頂いた方のみです。 + No comment provided by engineer. + You have already requested connection via this address! No comment provided by engineer. @@ -7385,11 +7652,6 @@ Repeat connection request? シークレットモードのプロフィールでこのグループに参加しています。メインのプロフィールを守るために、招待することができません No comment provided by engineer. - - Your %@ servers - あなたの %@ サーバー - No comment provided by engineer. - Your ICE servers あなたのICEサーバ @@ -7405,11 +7667,6 @@ Repeat connection request? あなたのSimpleXアドレス No comment provided by engineer. - - Your XFTP servers - あなたのXFTPサーバ - No comment provided by engineer. - Your calls あなたの通話 @@ -7505,16 +7762,15 @@ Repeat connection request? あなたのランダム・プロフィール No comment provided by engineer. - - Your server - あなたのサーバ - No comment provided by engineer. - Your server address あなたのサーバアドレス No comment provided by engineer. + + Your servers + No comment provided by engineer. + Your settings あなたの設定 @@ -7920,6 +8176,10 @@ Repeat connection request? expired No comment provided by engineer. + + for better metadata privacy. + No comment provided by engineer. + forwarded No comment provided by engineer. @@ -8507,6 +8767,33 @@ last received msg: %2$@ + +
+ +
+ + + %d new events + notification body + + + From: %@ + notification body + + + New events + notification + + + New messages + notification + + + New messages in %d chats + notification body + + +
diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/ja.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff b/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff index 511536427d..ac9d83e24b 100644 --- a/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff +++ b/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff @@ -152,20 +152,16 @@ ) No comment provided by engineer. - - **Add new contact**: to create your one-time QR Code or link for your contact. - No comment provided by engineer. - **Create link / QR code** for your contact to use. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. No comment provided by engineer. @@ -176,8 +172,8 @@ **Please note**: you will NOT be able to recover or change passphrase if you lose it. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. No comment provided by engineer. @@ -1537,8 +1533,8 @@ Image will be received when your contact is online, please wait or check later! No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam No comment provided by engineer. @@ -1961,8 +1957,8 @@ We will be adding server redundancy to prevent lost messages. Onion hosts will not be used. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. No comment provided by engineer. @@ -2013,8 +2009,8 @@ We will be adding server redundancy to prevent lost messages. Open user profiles authentication reason - - Open-source protocol and code – anybody can run the servers. + + Anybody can host servers. No comment provided by engineer. @@ -2049,8 +2045,8 @@ We will be adding server redundancy to prevent lost messages. Paste the link you received into the box below to connect with your contact. No comment provided by engineer. - - People can connect to you only via the links you share. + + You decide who can connect. No comment provided by engineer. @@ -2670,8 +2666,8 @@ We will be adding server redundancy to prevent lost messages. Thanks to the users – contribute via Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. + + No user identifiers. No comment provided by engineer. @@ -2706,8 +2702,8 @@ We will be adding server redundancy to prevent lost messages. The message will be marked as moderated for all members. No comment provided by engineer. - - The next generation of private messaging + + The future of messaging No comment provided by engineer. @@ -2774,8 +2770,8 @@ We will be adding server redundancy to prevent lost messages. To make a new connection No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. No comment provided by engineer. @@ -3093,10 +3089,6 @@ SimpleX Lock must be enabled. You can't send messages! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - No comment provided by engineer. - You could not be verified; please try again. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff b/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff index 6df24149e9..e16f585da8 100644 --- a/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff +++ b/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff @@ -162,20 +162,16 @@ ) No comment provided by engineer. - - **Add new contact**: to create your one-time QR Code or link for your contact. - No comment provided by engineer. - **Create link / QR code** for your contact to use. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. No comment provided by engineer. @@ -187,8 +183,8 @@ **Turėkite omenyje**: jeigu prarasite slaptafrazę, NEBEGALĖSITE jos atkurti ar pakeisti. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. No comment provided by engineer. @@ -1513,8 +1509,8 @@ Image will be received when your contact is online, please wait or check later! No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam No comment provided by engineer. @@ -1919,8 +1915,8 @@ We will be adding server redundancy to prevent lost messages. Onion hosts will not be used. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. No comment provided by engineer. @@ -1971,8 +1967,8 @@ We will be adding server redundancy to prevent lost messages. Open user profiles authentication reason - - Open-source protocol and code – anybody can run the servers. + + Anybody can host servers. No comment provided by engineer. @@ -2003,8 +1999,8 @@ We will be adding server redundancy to prevent lost messages. Paste the link you received into the box below to connect with your contact. No comment provided by engineer. - - People can connect to you only via the links you share. + + You decide who can connect. No comment provided by engineer. @@ -2591,8 +2587,8 @@ We will be adding server redundancy to prevent lost messages. Thanks to the users – contribute via Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. + + No user identifiers. No comment provided by engineer. @@ -2627,8 +2623,8 @@ We will be adding server redundancy to prevent lost messages. The message will be marked as moderated for all members. No comment provided by engineer. - - The next generation of private messaging + + The future of messaging No comment provided by engineer. @@ -2687,8 +2683,8 @@ We will be adding server redundancy to prevent lost messages. To make a new connection No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. No comment provided by engineer. @@ -2993,10 +2989,6 @@ To connect, please ask your contact to create another connection link and check You can't send messages! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - No comment provided by engineer. - You could not be verified; please try again. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index ce6faeccca..06ab82cf2a 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -12,21 +12,6 @@ No comment provided by engineer. - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - ( ( @@ -127,6 +112,14 @@ %@ is geverifieerd No comment provided by engineer. + + %@ server + No comment provided by engineer. + + + %@ servers + No comment provided by engineer. + %@ uploaded %@ geüpload @@ -352,28 +345,23 @@ ) No comment provided by engineer. - - **Add contact**: to create a new invitation link, or connect via a link you received. + + **Create 1-time link**: to create and share a new invitation link. **Contact toevoegen**: om een nieuwe uitnodigingslink aan te maken, of verbinding te maken via een link die u heeft ontvangen. No comment provided by engineer. - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Nieuw contact toevoegen**: om uw eenmalige QR-code of link voor uw contact te maken. - No comment provided by engineer. - **Create group**: to create a new group. **Groep aanmaken**: om een nieuwe groep aan te maken. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **Meer privé**: bekijk elke 20 minuten nieuwe berichten. Apparaattoken wordt gedeeld met de SimpleX Chat-server, maar niet hoeveel contacten of berichten u heeft. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **Meest privé**: gebruik geen SimpleX Chat-notificatie server, controleer berichten regelmatig op de achtergrond (afhankelijk van hoe vaak u de app gebruikt). No comment provided by engineer. @@ -387,11 +375,15 @@ **Let op**: u kunt het wachtwoord NIET herstellen of wijzigen als u het kwijtraakt. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **Aanbevolen**: apparaattoken en meldingen worden naar de SimpleX Chat-meldingsserver gestuurd, maar niet de berichtinhoud, -grootte of van wie het afkomstig is. No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **Waarschuwing**: voor directe push meldingen is een wachtwoord vereist dat is opgeslagen in de Keychain. @@ -498,6 +490,14 @@ 1 week time interval + + 1-time link + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + No comment provided by engineer. + 5 minutes 5 minuten @@ -567,21 +567,11 @@ Adres wijziging afbreken? No comment provided by engineer. - - About SimpleX - Over SimpleX - No comment provided by engineer. - About SimpleX Chat Over SimpleX Chat No comment provided by engineer. - - About SimpleX address - Over SimpleX adres - No comment provided by engineer. - Accent Accent @@ -594,6 +584,10 @@ accept incoming call via notification swipe action + + Accept conditions + No comment provided by engineer. + Accept connection request? Accepteer contact @@ -610,6 +604,10 @@ accept contact request via notification swipe action + + Accepted conditions + No comment provided by engineer. + Acknowledged Erkend @@ -630,16 +628,6 @@ Voeg een adres toe aan uw profiel, zodat uw contacten het met andere mensen kunnen delen. Profiel update wordt naar uw contacten verzonden. No comment provided by engineer. - - Add contact - Contact toevoegen - No comment provided by engineer. - - - Add preset servers - Vooraf ingestelde servers toevoegen - No comment provided by engineer. - Add profile Profiel toevoegen @@ -665,6 +653,14 @@ Welkom bericht toevoegen No comment provided by engineer. + + Added media & file servers + No comment provided by engineer. + + + Added message servers + No comment provided by engineer. + Additional accent Extra accent @@ -690,6 +686,14 @@ Adres wijziging wordt afgebroken. Het oude ontvangstadres wordt gebruikt. No comment provided by engineer. + + Address or 1-time link? + No comment provided by engineer. + + + Address settings + No comment provided by engineer. + Admins can block a member for all. Beheerders kunnen een lid voor iedereen blokkeren. @@ -735,6 +739,10 @@ Alle groepsleden blijven verbonden. No comment provided by engineer. + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + No comment provided by engineer. + All messages will be deleted - this cannot be undone! Alle berichten worden verwijderd. Dit kan niet ongedaan worden gemaakt! @@ -915,6 +923,11 @@ Beantwoord oproep No comment provided by engineer. + + Anybody can host servers. + Iedereen kan servers hosten. + No comment provided by engineer. + App build: %@ App build: %@ @@ -1258,7 +1271,8 @@ Cancel Annuleren - alert button + alert action + alert button Cancel migration @@ -1341,6 +1355,10 @@ authentication reason set passcode view + + Change user profiles + authentication reason + Chat archive Gesprek archief @@ -1426,10 +1444,18 @@ Chats No comment provided by engineer. + + Check messages every 20 min. + No comment provided by engineer. + + + Check messages when allowed. + No comment provided by engineer. + Check server address and try again. Controleer het server adres en probeer het opnieuw. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1516,16 +1542,47 @@ Voltooid No comment provided by engineer. + + Conditions accepted on: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for following operator(s): **%@**. + No comment provided by engineer. + + + Conditions of use + No comment provided by engineer. + + + Conditions will be accepted for enabled operators after 30 days. + No comment provided by engineer. + + + Conditions will be accepted for operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + No comment provided by engineer. + Configure ICE servers ICE servers configureren No comment provided by engineer. - - Configured %@ servers - %@ servers geconfigureerd - No comment provided by engineer. - Confirm Bevestigen @@ -1715,6 +1772,10 @@ Dit is uw eigen eenmalige link! Verbindingsverzoek verzonden! No comment provided by engineer. + + Connection security + No comment provided by engineer. + Connection terminated Verbinding beëindigd @@ -1830,6 +1891,10 @@ Dit is uw eigen eenmalige link! Maak No comment provided by engineer. + + Create 1-time link + No comment provided by engineer. + Create SimpleX address Maak een SimpleX adres aan @@ -1840,11 +1905,6 @@ Dit is uw eigen eenmalige link! Maak een groep met een willekeurig profiel. No comment provided by engineer. - - Create an address to let people connect with you. - Maak een adres aan zodat mensen contact met je kunnen opnemen. - No comment provided by engineer. - Create file Bestand maken @@ -1925,6 +1985,10 @@ Dit is uw eigen eenmalige link! Huidige toegangscode No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + No comment provided by engineer. + Current passphrase… Huidige wachtwoord… @@ -2081,7 +2145,8 @@ Dit is uw eigen eenmalige link! Delete Verwijderen - chat item action + alert action + chat item action swipe action @@ -2299,6 +2364,10 @@ Dit is uw eigen eenmalige link! Verwijderingsfouten No comment provided by engineer. + + Delivered even when Apple drops them. + No comment provided by engineer. + Delivery Bezorging @@ -2580,6 +2649,10 @@ Dit is uw eigen eenmalige link! Duur No comment provided by engineer. + + E2E encrypted notifications. + No comment provided by engineer. + Edit Bewerk @@ -2600,6 +2673,10 @@ Dit is uw eigen eenmalige link! Inschakelen (overschrijvingen behouden) No comment provided by engineer. + + Enable Flux + No comment provided by engineer. + Enable SimpleX Lock SimpleX Vergrendelen inschakelen @@ -2805,6 +2882,10 @@ Dit is uw eigen eenmalige link! Fout bij het afbreken van adres wijziging No comment provided by engineer. + + Error accepting conditions + alert title + Error accepting contact request Fout bij het accepteren van een contactverzoek @@ -2820,6 +2901,10 @@ Dit is uw eigen eenmalige link! Fout bij het toevoegen van leden No comment provided by engineer. + + Error adding server + alert title + Error changing address Fout bij wijzigen van adres @@ -2960,10 +3045,9 @@ Dit is uw eigen eenmalige link! Fout bij lid worden van groep No comment provided by engineer. - - Error loading %@ servers - Fout bij het laden van %@ servers - No comment provided by engineer. + + Error loading servers + alert title Error migrating settings @@ -3000,11 +3084,6 @@ Dit is uw eigen eenmalige link! Fout bij het resetten van statistieken No comment provided by engineer. - - Error saving %@ servers - Fout bij opslaan van %@ servers - No comment provided by engineer. - Error saving ICE servers Fout bij opslaan van ICE servers @@ -3025,6 +3104,10 @@ Dit is uw eigen eenmalige link! Fout bij opslaan van wachtwoord in de keychain No comment provided by engineer. + + Error saving servers + alert title + Error saving settings Fout bij opslaan van instellingen @@ -3095,6 +3178,10 @@ Dit is uw eigen eenmalige link! Fout bij updaten van bericht No comment provided by engineer. + + Error updating server + alert title + Error updating settings Fout bij bijwerken van instellingen @@ -3140,6 +3227,10 @@ Dit is uw eigen eenmalige link! Fouten No comment provided by engineer. + + Errors in servers configuration. + servers error + Even when disabled in the conversation. Zelfs wanneer uitgeschakeld in het gesprek. @@ -3342,11 +3433,27 @@ Dit is uw eigen eenmalige link! Herstel wordt niet ondersteund door groepslid No comment provided by engineer. + + For chat profile %@: + servers error + For console Voor console No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + No comment provided by engineer. + + + For private routing + No comment provided by engineer. + + + For social media + No comment provided by engineer. + Forward Doorsturen @@ -3656,9 +3763,12 @@ Fout: %2$@ Hoe SimpleX werkt No comment provided by engineer. - - How it works - Hoe het werkt + + How it affects privacy + No comment provided by engineer. + + + How it helps privacy No comment provided by engineer. @@ -3731,8 +3841,8 @@ Fout: %2$@ Onmiddellijk No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Immuun voor spam en misbruik No comment provided by engineer. @@ -3873,6 +3983,11 @@ Binnenkort meer verbeteringen! Installeer [SimpleX Chat voor terminal](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Direct + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3880,11 +3995,6 @@ Binnenkort meer verbeteringen! No comment provided by engineer. - - Instantly - Direct - No comment provided by engineer. - Interface Interface @@ -3933,7 +4043,7 @@ Binnenkort meer verbeteringen! Invalid server address! Ongeldig server adres! - No comment provided by engineer. + alert title Invalid status @@ -4061,7 +4171,7 @@ Dit is jouw link voor groep %@! Keep Bewaar - No comment provided by engineer. + alert action Keep conversation @@ -4076,7 +4186,7 @@ Dit is jouw link voor groep %@! Keep unused invitation? Ongebruikte uitnodiging bewaren? - No comment provided by engineer. + alert title Keep your connections @@ -4163,11 +4273,6 @@ Dit is jouw link voor groep %@! Live berichten No comment provided by engineer. - - Local - Lokaal - No comment provided by engineer. - Local name Lokale naam @@ -4188,11 +4293,6 @@ Dit is jouw link voor groep %@! Vergrendeling modus No comment provided by engineer. - - Make a private connection - Maak een privéverbinding - No comment provided by engineer. - Make one message disappear Eén bericht laten verdwijnen @@ -4203,21 +4303,11 @@ Dit is jouw link voor groep %@! Profiel privé maken! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Zorg ervoor dat %@ server adressen de juiste indeling hebben, regel gescheiden zijn en niet gedupliceerd zijn (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. Zorg ervoor dat WebRTC ICE server adressen de juiste indeling hebben, regel gescheiden zijn en niet gedupliceerd zijn. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Veel mensen vroegen: *als SimpleX geen gebruikers-ID's heeft, hoe kan het dan berichten bezorgen?* - No comment provided by engineer. - Mark deleted for everyone Markeer verwijderd voor iedereen @@ -4498,6 +4588,10 @@ Dit is jouw link voor groep %@! Betrouwbaardere netwerkverbinding. No comment provided by engineer. + + More reliable notifications + No comment provided by engineer. + Most likely this connection is deleted. Hoogstwaarschijnlijk is deze verbinding verwijderd. @@ -4533,6 +4627,10 @@ Dit is jouw link voor groep %@! Netwerkverbinding No comment provided by engineer. + + Network decentralization + No comment provided by engineer. + Network issues - message expired after many attempts to send it. Netwerkproblemen - bericht is verlopen na vele pogingen om het te verzenden. @@ -4543,6 +4641,10 @@ Dit is jouw link voor groep %@! Netwerkbeheer No comment provided by engineer. + + Network operator + No comment provided by engineer. + Network settings Netwerk instellingen @@ -4603,6 +4705,10 @@ Dit is jouw link voor groep %@! Nieuwe weergavenaam No comment provided by engineer. + + New events + notification + New in %@ Nieuw in %@ @@ -4628,6 +4734,10 @@ Dit is jouw link voor groep %@! Nieuw wachtwoord… No comment provided by engineer. + + New server + No comment provided by engineer. + No Nee @@ -4683,6 +4793,14 @@ Dit is jouw link voor groep %@! Geen info, probeer opnieuw te laden No comment provided by engineer. + + No media & file servers. + servers error + + + No message servers. + servers error + No network connection Geen netwerkverbinding @@ -4703,11 +4821,37 @@ Dit is jouw link voor groep %@! Geen toestemming om spraakbericht op te nemen No comment provided by engineer. + + No push server + Lokaal + No comment provided by engineer. + No received or sent files Geen ontvangen of verzonden bestanden No comment provided by engineer. + + No servers for private message routing. + servers error + + + No servers to receive files. + servers error + + + No servers to receive messages. + servers error + + + No servers to send files. + servers error + + + No user identifiers. + Geen gebruikers-ID's. + No comment provided by engineer. + Not compatible! Niet compatibel! @@ -4733,6 +4877,10 @@ Dit is jouw link voor groep %@! Meldingen zijn uitgeschakeld! No comment provided by engineer. + + Notifications privacy + No comment provided by engineer. + Now admins can: - delete members' messages. @@ -4791,8 +4939,8 @@ Vereist het inschakelen van VPN. Onion hosts worden niet gebruikt. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. Alleen client apparaten slaan gebruikers profielen, contacten, groepen en berichten op die zijn verzonden met **2-laags end-to-end-codering**. No comment provided by engineer. @@ -4876,6 +5024,10 @@ Vereist het inschakelen van VPN. Open instellingen No comment provided by engineer. + + Open changes + No comment provided by engineer. + Open chat Chat openen @@ -4886,6 +5038,10 @@ Vereist het inschakelen van VPN. Chat console openen authentication reason + + Open conditions + No comment provided by engineer. + Open group Open groep @@ -4896,26 +5052,19 @@ Vereist het inschakelen van VPN. Open de migratie naar een ander apparaat authentication reason - - Open server settings - Server instellingen openen - No comment provided by engineer. - - - Open user profiles - Gebruikers profielen openen - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Open-source protocol en code. Iedereen kan de servers draaien. - No comment provided by engineer. - Opening app… App openen… No comment provided by engineer. + + Operator + No comment provided by engineer. + + + Operator server + alert title + Or paste archive link Of plak de archief link @@ -4936,16 +5085,15 @@ Vereist het inschakelen van VPN. Of laat deze code zien No comment provided by engineer. + + Or to share privately + No comment provided by engineer. + Other Ander No comment provided by engineer. - - Other %@ servers - Andere %@ servers - No comment provided by engineer. - Other file errors: %@ @@ -5028,13 +5176,8 @@ Vereist het inschakelen van VPN. in behandeling No comment provided by engineer. - - People can connect to you only via the links you share. - Mensen kunnen alleen verbinding met u maken via de links die u deelt. - No comment provided by engineer. - - - Periodically + + Periodic Periodiek No comment provided by engineer. @@ -5157,16 +5300,15 @@ Fout: %@ Bewaar het laatste berichtconcept, met bijlagen. No comment provided by engineer. - - Preset server - Vooraf ingestelde server - No comment provided by engineer. - Preset server address Vooraf ingesteld server adres No comment provided by engineer. + + Preset servers + No comment provided by engineer. + Preview Voorbeeld @@ -5245,7 +5387,7 @@ Fout: %@ Profile update will be sent to your contacts. Profiel update wordt naar uw contacten verzonden. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -5339,6 +5481,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Proxy vereist wachtwoord No comment provided by engineer. + + Push Notifications + No comment provided by engineer. + Push notifications Push meldingen @@ -5379,26 +5525,21 @@ Schakel dit in in *Netwerk en servers*-instellingen. Lees meer No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/app-settings.html#uw-simplex-contactadres). - No comment provided by engineer. - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/app-settings.html#uw-simplex-contactadres). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/readme.html#connect-to-friends). No comment provided by engineer. - - Read more in our GitHub repository. - Lees meer in onze GitHub repository. - No comment provided by engineer. - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). Lees meer in onze [GitHub-repository](https://github.com/simplex-chat/simplex-chat#readme). @@ -5715,6 +5856,14 @@ Schakel dit in in *Netwerk en servers*-instellingen. Onthullen chat item action + + Review conditions + No comment provided by engineer. + + + Review later + No comment provided by engineer. + Revoke Intrekken @@ -5760,6 +5909,14 @@ Schakel dit in in *Netwerk en servers*-instellingen. Veiligere groepen No comment provided by engineer. + + Same conditions will apply to operator **%@**. + No comment provided by engineer. + + + Same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + Save Opslaan @@ -5829,7 +5986,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Save servers? Servers opslaan? - No comment provided by engineer. + alert title Save welcome message? @@ -6041,11 +6198,6 @@ Schakel dit in in *Netwerk en servers*-instellingen. Meldingen verzenden No comment provided by engineer. - - Send notifications: - Meldingen verzenden: - No comment provided by engineer. - Send questions and ideas Stuur vragen en ideeën @@ -6171,6 +6323,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Server No comment provided by engineer. + + Server added to operator %@. + alert message + Server address Server adres @@ -6186,6 +6342,18 @@ Schakel dit in in *Netwerk en servers*-instellingen. Serveradres is incompatibel met netwerkinstellingen: %@. No comment provided by engineer. + + Server operator changed. + alert title + + + Server operators + No comment provided by engineer. + + + Server protocol changed. + alert title + Server requires authorization to create queues, check password Server vereist autorisatie om wachtrijen te maken, controleer wachtwoord @@ -6304,22 +6472,35 @@ Schakel dit in in *Netwerk en servers*-instellingen. Share Deel - chat item action + alert action + chat item action Share 1-time link Eenmalige link delen No comment provided by engineer. + + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + No comment provided by engineer. + Share address Adres delen No comment provided by engineer. + + Share address publicly + No comment provided by engineer. + Share address with contacts? Adres delen met contacten? - No comment provided by engineer. + alert title Share from other apps. @@ -6436,6 +6617,14 @@ Schakel dit in in *Netwerk en servers*-instellingen. SimpleX adres No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + No comment provided by engineer. + SimpleX contact address SimpleX contact adres @@ -6526,6 +6715,11 @@ Schakel dit in in *Netwerk en servers*-instellingen. Er zijn enkele niet-fatale fouten opgetreden tijdens het importeren: No comment provided by engineer. + + Some servers failed the test: +%@ + alert message + Somebody Iemand @@ -6609,12 +6803,12 @@ Schakel dit in in *Netwerk en servers*-instellingen. Stop sharing Stop met delen - No comment provided by engineer. + alert action Stop sharing address? Stop met het delen van adres? - No comment provided by engineer. + alert title Stopping chat @@ -6764,7 +6958,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Tests failed! Testen mislukt! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6781,11 +6975,6 @@ Schakel dit in in *Netwerk en servers*-instellingen. Dank aan de gebruikers – draag bij via Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - Het eerste platform zonder gebruikers-ID's, privé door ontwerp. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6798,6 +6987,10 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. De app kan u op de hoogte stellen wanneer u berichten of contact verzoeken ontvangt - open de instellingen om dit in te schakelen. No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). De app vraagt om downloads van onbekende bestandsservers (behalve .onion) te bevestigen. @@ -6813,6 +7006,10 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. De code die u heeft gescand is geen SimpleX link QR-code. No comment provided by engineer. + + The connection reached the limit of undelivered messages, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! De door u geaccepteerde verbinding wordt geannuleerd! @@ -6833,6 +7030,11 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. De versleuteling werkt en de nieuwe versleutelingsovereenkomst is niet vereist. Dit kan leiden tot verbindingsfouten! No comment provided by engineer. + + The future of messaging + De volgende generatie privéberichten + No comment provided by engineer. + The hash of the previous message is different. De hash van het vorige bericht is anders. @@ -6858,11 +7060,6 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. De berichten worden voor alle leden als gemodereerd gemarkeerd. No comment provided by engineer. - - The next generation of private messaging - De volgende generatie privéberichten - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. De oude database is niet verwijderd tijdens de migratie, deze kan worden verwijderd. @@ -6873,6 +7070,10 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. Het profiel wordt alleen gedeeld met uw contacten. No comment provided by engineer. + + The second preset operator in the app! + No comment provided by engineer. + The second tick we missed! ✅ De tweede vink die we gemist hebben! ✅ @@ -6888,6 +7089,10 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. De servers voor nieuwe verbindingen van uw huidige chatprofiel **%@**. No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. De tekst die u hebt geplakt is geen SimpleX link. @@ -6903,6 +7108,10 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. Thema's No comment provided by engineer. + + These conditions will also apply for: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. Deze instellingen zijn voor uw huidige profiel **%@**. @@ -7003,9 +7212,8 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. Om een nieuwe verbinding te maken No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Om de privacy te beschermen, heeft SimpleX in plaats van gebruikers-ID's die door alle andere platforms worden gebruikt, ID's voor berichten wachtrijen, afzonderlijk voor elk van uw contacten. + + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -7025,6 +7233,15 @@ You will be prompted to complete authentication before this feature is enabled.< U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingeschakeld. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + Om de privacy te beschermen, heeft SimpleX in plaats van gebruikers-ID's die door alle andere platforms worden gebruikt, ID's voor berichten wachtrijen, afzonderlijk voor elk van uw contacten. + No comment provided by engineer. + + + To receive + No comment provided by engineer. + To record speech please grant permission to use Microphone. Geef toestemming om de microfoon te gebruiken om spraak op te nemen. @@ -7045,11 +7262,19 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc Om uw verborgen profiel te onthullen, voert u een volledig wachtwoord in een zoek veld in op de pagina **Uw chatprofielen**. No comment provided by engineer. + + To send + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. Om directe push meldingen te ondersteunen, moet de chat database worden gemigreerd. No comment provided by engineer. + + To use the servers of **%@**, accept conditions of use. + No comment provided by engineer. + To verify end-to-end encryption with your contact compare (or scan) the code on your devices. Vergelijk (of scan) de code op uw apparaten om end-to-end-codering met uw contact te verifiëren. @@ -7140,6 +7365,10 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc Lid deblokkeren? No comment provided by engineer. + + Undelivered messages + No comment provided by engineer. + Unexpected migration state Onverwachte migratiestatus @@ -7297,6 +7526,10 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Archief uploaden No comment provided by engineer. + + Use %@ + No comment provided by engineer. + Use .onion hosts Gebruik .onion-hosts @@ -7322,6 +7555,14 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Gebruik het huidige profiel No comment provided by engineer. + + Use for files + No comment provided by engineer. + + + Use for messages + No comment provided by engineer. + Use for new connections Gebruik voor nieuwe verbindingen @@ -7362,6 +7603,10 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Gebruik server No comment provided by engineer. + + Use servers + No comment provided by engineer. + Use the app while in the call. Gebruik de app tijdens het gesprek. @@ -7452,11 +7697,19 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Video's en bestanden tot 1 GB No comment provided by engineer. + + View conditions + No comment provided by engineer. + View security code Beveiligingscode bekijken No comment provided by engineer. + + View updated conditions + No comment provided by engineer. + Visible history Zichtbare geschiedenis @@ -7567,9 +7820,8 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Bij het verbinden van audio- en video-oproepen. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - Wanneer mensen vragen om verbinding te maken, kunt u dit accepteren of weigeren. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -7729,6 +7981,18 @@ Deelnameverzoek herhalen? U kunt dit wijzigen in de instellingen onder uiterlijk. No comment provided by engineer. + + You can configure operators in Network & servers settings. + No comment provided by engineer. + + + You can configure servers via settings. + No comment provided by engineer. + + + You can create it in user picker. + No comment provided by engineer. + You can create it later U kan het later maken @@ -7769,6 +8033,10 @@ Deelnameverzoek herhalen? U kunt berichten naar %@ sturen vanuit gearchiveerde contacten. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + No comment provided by engineer. + You can set lock screen notification preview via settings. U kunt een voorbeeld van een melding op het vergrendeld scherm instellen via instellingen. @@ -7784,11 +8052,6 @@ Deelnameverzoek herhalen? U kunt dit adres delen met uw contacten om hen verbinding te laten maken met **%@**. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - U kunt uw adres delen als een link of als een QR-code. Iedereen kan verbinding met u maken. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app U kunt de chat starten via app Instellingen / Database of door de app opnieuw op te starten @@ -7812,23 +8075,23 @@ Deelnameverzoek herhalen? You can view invitation link again in connection details. U kunt de uitnodigingslink opnieuw bekijken in de verbindingsdetails. - No comment provided by engineer. + alert message You can't send messages! Je kunt geen berichten versturen! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - U bepaalt via welke server(s) de berichten **ontvangen**, uw contacten de servers die u gebruikt om ze berichten te sturen. - No comment provided by engineer. - You could not be verified; please try again. U kon niet worden geverifieerd; probeer het opnieuw. No comment provided by engineer. + + You decide who can connect. + Jij bepaalt wie er verbinding mag maken. + No comment provided by engineer. + You have already requested connection via this address! U heeft al een verbinding aangevraagd via dit adres! @@ -7951,11 +8214,6 @@ Verbindingsverzoek herhalen? Je gebruikt een incognito profiel voor deze groep. Om te voorkomen dat je je hoofdprofiel deelt, is het niet toegestaan om contacten uit te nodigen No comment provided by engineer. - - Your %@ servers - Uw %@ servers - No comment provided by engineer. - Your ICE servers Uw ICE servers @@ -7971,11 +8229,6 @@ Verbindingsverzoek herhalen? Uw SimpleX adres No comment provided by engineer. - - Your XFTP servers - Uw XFTP servers - No comment provided by engineer. - Your calls Uw oproepen @@ -8076,16 +8329,15 @@ Verbindingsverzoek herhalen? Je willekeurige profiel No comment provided by engineer. - - Your server - Uw server - No comment provided by engineer. - Your server address Uw server adres No comment provided by engineer. + + Your servers + No comment provided by engineer. + Your settings Uw instellingen @@ -8506,6 +8758,10 @@ Verbindingsverzoek herhalen? verlopen No comment provided by engineer. + + for better metadata privacy. + No comment provided by engineer. + forwarded doorgestuurd @@ -9128,6 +9384,33 @@ laatst ontvangen bericht: %2$@ + +
+ +
+ + + %d new events + notification body + + + From: %@ + notification body + + + New events + notification + + + New messages + notification + + + New messages in %d chats + notification body + + +
diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/nl.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index ee3ef5b12e..531d50f522 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -12,21 +12,6 @@ No comment provided by engineer. - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - ( ( @@ -127,6 +112,14 @@ %@ jest zweryfikowany No comment provided by engineer. + + %@ server + No comment provided by engineer. + + + %@ servers + No comment provided by engineer. + %@ uploaded %@ wgrane @@ -352,28 +345,23 @@ ) No comment provided by engineer. - - **Add contact**: to create a new invitation link, or connect via a link you received. + + **Create 1-time link**: to create and share a new invitation link. **Dodaj kontakt**: aby utworzyć nowy link z zaproszeniem lub połączyć się za pomocą otrzymanego linku. No comment provided by engineer. - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Dodaj nowy kontakt**: aby stworzyć swój jednorazowy kod QR lub link dla kontaktu. - No comment provided by engineer. - **Create group**: to create a new group. **Utwórz grupę**: aby utworzyć nową grupę. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **Bardziej prywatny**: sprawdzanie nowych wiadomości odbywa się co 20 minut. Współdzielony z serwerem SimpleX Chat jest token urządzenia, lecz nie informacje o liczbie kontaktów lub wiadomości. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **Najbardziej prywatny**: nie korzystaj z serwera powiadomień SimpleX Chat, wiadomości sprawdzane są co jakiś czas w tle (zależne od tego jak często korzystasz z aplikacji). No comment provided by engineer. @@ -387,11 +375,15 @@ **Uwaga**: NIE będziesz w stanie odzyskać lub zmienić kodu dostępu, jeśli go stracisz. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **Zalecane**: do serwera powiadomień SimpleX Chat wysyłany jest token urządzenia i powiadomienia, lecz nie treść wiadomości, jej rozmiar lub od kogo ona jest. No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **Uwaga**: Natychmiastowe powiadomienia push wymagają zapisania kodu dostępu w Keychain. @@ -498,6 +490,14 @@ 1 tydzień time interval + + 1-time link + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + No comment provided by engineer. + 5 minutes 5 minut @@ -567,21 +567,11 @@ Przerwać zmianę adresu? No comment provided by engineer. - - About SimpleX - O SimpleX - No comment provided by engineer. - About SimpleX Chat O SimpleX Chat No comment provided by engineer. - - About SimpleX address - O adresie SimpleX - No comment provided by engineer. - Accent Akcent @@ -594,6 +584,10 @@ accept incoming call via notification swipe action + + Accept conditions + No comment provided by engineer. + Accept connection request? Zaakceptować prośbę o połączenie? @@ -610,6 +604,10 @@ accept contact request via notification swipe action + + Accepted conditions + No comment provided by engineer. + Acknowledged Potwierdzono @@ -630,16 +628,6 @@ Dodaj adres do swojego profilu, aby Twoje kontakty mogły go udostępnić innym osobom. Aktualizacja profilu zostanie wysłana do Twoich kontaktów. No comment provided by engineer. - - Add contact - Dodaj kontakt - No comment provided by engineer. - - - Add preset servers - Dodaj gotowe serwery - No comment provided by engineer. - Add profile Dodaj profil @@ -665,6 +653,14 @@ Dodaj wiadomość powitalną No comment provided by engineer. + + Added media & file servers + No comment provided by engineer. + + + Added message servers + No comment provided by engineer. + Additional accent Dodatkowy akcent @@ -690,6 +686,14 @@ Zmiana adresu zostanie przerwana. Użyty zostanie stary adres odbiorczy. No comment provided by engineer. + + Address or 1-time link? + No comment provided by engineer. + + + Address settings + No comment provided by engineer. + Admins can block a member for all. Administratorzy mogą blokować członka dla wszystkich. @@ -735,6 +739,10 @@ Wszyscy członkowie grupy pozostaną połączeni. No comment provided by engineer. + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + No comment provided by engineer. + All messages will be deleted - this cannot be undone! Wszystkie wiadomości zostaną usunięte – nie można tego cofnąć! @@ -915,6 +923,11 @@ Odbierz połączenie No comment provided by engineer. + + Anybody can host servers. + Każdy może hostować serwery. + No comment provided by engineer. + App build: %@ Kompilacja aplikacji: %@ @@ -1253,7 +1266,8 @@ Cancel Anuluj - alert button + alert action + alert button Cancel migration @@ -1336,6 +1350,10 @@ authentication reason set passcode view + + Change user profiles + authentication reason + Chat archive Archiwum czatu @@ -1421,10 +1439,18 @@ Czaty No comment provided by engineer. + + Check messages every 20 min. + No comment provided by engineer. + + + Check messages when allowed. + No comment provided by engineer. + Check server address and try again. Sprawdź adres serwera i spróbuj ponownie. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1511,16 +1537,47 @@ Zakończono No comment provided by engineer. + + Conditions accepted on: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for following operator(s): **%@**. + No comment provided by engineer. + + + Conditions of use + No comment provided by engineer. + + + Conditions will be accepted for enabled operators after 30 days. + No comment provided by engineer. + + + Conditions will be accepted for operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + No comment provided by engineer. + Configure ICE servers Skonfiguruj serwery ICE No comment provided by engineer. - - Configured %@ servers - Skonfigurowano %@ serwerów - No comment provided by engineer. - Confirm Potwierdź @@ -1710,6 +1767,10 @@ To jest twój jednorazowy link! Prośba o połączenie wysłana! No comment provided by engineer. + + Connection security + No comment provided by engineer. + Connection terminated Połączenie zakończone @@ -1825,6 +1886,10 @@ To jest twój jednorazowy link! Utwórz No comment provided by engineer. + + Create 1-time link + No comment provided by engineer. + Create SimpleX address Utwórz adres SimpleX @@ -1835,11 +1900,6 @@ To jest twój jednorazowy link! Utwórz grupę używając losowego profilu. No comment provided by engineer. - - Create an address to let people connect with you. - Utwórz adres, aby ludzie mogli się z Tobą połączyć. - No comment provided by engineer. - Create file Utwórz plik @@ -1920,6 +1980,10 @@ To jest twój jednorazowy link! Aktualny Pin No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + No comment provided by engineer. + Current passphrase… Obecne hasło… @@ -2075,7 +2139,8 @@ To jest twój jednorazowy link! Delete Usuń - chat item action + alert action + chat item action swipe action @@ -2292,6 +2357,10 @@ To jest twój jednorazowy link! Błędy usuwania No comment provided by engineer. + + Delivered even when Apple drops them. + No comment provided by engineer. + Delivery Dostarczenie @@ -2573,6 +2642,10 @@ To jest twój jednorazowy link! Czas trwania No comment provided by engineer. + + E2E encrypted notifications. + No comment provided by engineer. + Edit Edytuj @@ -2593,6 +2666,10 @@ To jest twój jednorazowy link! Włącz (zachowaj nadpisania) No comment provided by engineer. + + Enable Flux + No comment provided by engineer. + Enable SimpleX Lock Włącz blokadę SimpleX @@ -2798,6 +2875,10 @@ To jest twój jednorazowy link! Błąd przerwania zmiany adresu No comment provided by engineer. + + Error accepting conditions + alert title + Error accepting contact request Błąd przyjmowania prośby o kontakt @@ -2813,6 +2894,10 @@ To jest twój jednorazowy link! Błąd dodawania członka(ów) No comment provided by engineer. + + Error adding server + alert title + Error changing address Błąd zmiany adresu @@ -2953,10 +3038,9 @@ To jest twój jednorazowy link! Błąd dołączenia do grupy No comment provided by engineer. - - Error loading %@ servers - Błąd ładowania %@ serwerów - No comment provided by engineer. + + Error loading servers + alert title Error migrating settings @@ -2993,11 +3077,6 @@ To jest twój jednorazowy link! Błąd resetowania statystyk No comment provided by engineer. - - Error saving %@ servers - Błąd zapisu %@ serwerów - No comment provided by engineer. - Error saving ICE servers Błąd zapisu serwerów ICE @@ -3018,6 +3097,10 @@ To jest twój jednorazowy link! Błąd zapisu hasła do pęku kluczy No comment provided by engineer. + + Error saving servers + alert title + Error saving settings Błąd zapisywania ustawień @@ -3088,6 +3171,10 @@ To jest twój jednorazowy link! Błąd aktualizacji wiadomości No comment provided by engineer. + + Error updating server + alert title + Error updating settings Błąd aktualizacji ustawień @@ -3133,6 +3220,10 @@ To jest twój jednorazowy link! Błędy No comment provided by engineer. + + Errors in servers configuration. + servers error + Even when disabled in the conversation. Nawet po wyłączeniu w rozmowie. @@ -3335,11 +3426,27 @@ To jest twój jednorazowy link! Naprawa nie jest obsługiwana przez członka grupy No comment provided by engineer. + + For chat profile %@: + servers error + For console Dla konsoli No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + No comment provided by engineer. + + + For private routing + No comment provided by engineer. + + + For social media + No comment provided by engineer. + Forward Przekaż dalej @@ -3648,9 +3755,12 @@ Błąd: %2$@ Jak działa SimpleX No comment provided by engineer. - - How it works - Jak to działa + + How it affects privacy + No comment provided by engineer. + + + How it helps privacy No comment provided by engineer. @@ -3723,8 +3833,8 @@ Błąd: %2$@ Natychmiast No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Odporność na spam i nadużycia No comment provided by engineer. @@ -3863,6 +3973,11 @@ More improvements are coming soon! Zainstaluj [SimpleX Chat na terminal](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Natychmiastowo + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3870,11 +3985,6 @@ More improvements are coming soon! No comment provided by engineer. - - Instantly - Natychmiastowo - No comment provided by engineer. - Interface Interfejs @@ -3923,7 +4033,7 @@ More improvements are coming soon! Invalid server address! Nieprawidłowy adres serwera! - No comment provided by engineer. + alert title Invalid status @@ -4051,7 +4161,7 @@ To jest twój link do grupy %@! Keep Zachowaj - No comment provided by engineer. + alert action Keep conversation @@ -4066,7 +4176,7 @@ To jest twój link do grupy %@! Keep unused invitation? Zachować nieużyte zaproszenie? - No comment provided by engineer. + alert title Keep your connections @@ -4153,11 +4263,6 @@ To jest twój link do grupy %@! Wiadomości na żywo No comment provided by engineer. - - Local - Lokalnie - No comment provided by engineer. - Local name Nazwa lokalna @@ -4178,11 +4283,6 @@ To jest twój link do grupy %@! Tryb blokady No comment provided by engineer. - - Make a private connection - Nawiąż prywatne połączenie - No comment provided by engineer. - Make one message disappear Spraw, aby jedna wiadomość zniknęła @@ -4193,21 +4293,11 @@ To jest twój link do grupy %@! Ustaw profil jako prywatny! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Upewnij się, że adresy serwerów %@ są w poprawnym formacie, rozdzielone liniami i nie są zduplikowane (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. Upewnij się, że adresy serwerów WebRTC ICE są w poprawnym formacie, rozdzielone liniami i nie są zduplikowane. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Wiele osób pytało: *jeśli SimpleX nie ma identyfikatora użytkownika, jak może dostarczać wiadomości?* - No comment provided by engineer. - Mark deleted for everyone Oznacz jako usunięty dla wszystkich @@ -4488,6 +4578,10 @@ To jest twój link do grupy %@! Bardziej niezawodne połączenia sieciowe. No comment provided by engineer. + + More reliable notifications + No comment provided by engineer. + Most likely this connection is deleted. Najprawdopodobniej to połączenie jest usunięte. @@ -4523,6 +4617,10 @@ To jest twój link do grupy %@! Połączenie z siecią No comment provided by engineer. + + Network decentralization + No comment provided by engineer. + Network issues - message expired after many attempts to send it. Błąd sieciowy - wiadomość wygasła po wielu próbach wysłania jej. @@ -4533,6 +4631,10 @@ To jest twój link do grupy %@! Zarządzenie sieciowe No comment provided by engineer. + + Network operator + No comment provided by engineer. + Network settings Ustawienia sieci @@ -4593,6 +4695,10 @@ To jest twój link do grupy %@! Nowa wyświetlana nazwa No comment provided by engineer. + + New events + notification + New in %@ Nowość w %@ @@ -4618,6 +4724,10 @@ To jest twój link do grupy %@! Nowe hasło… No comment provided by engineer. + + New server + No comment provided by engineer. + No Nie @@ -4673,6 +4783,14 @@ To jest twój link do grupy %@! Brak informacji, spróbuj przeładować No comment provided by engineer. + + No media & file servers. + servers error + + + No message servers. + servers error + No network connection Brak połączenia z siecią @@ -4693,11 +4811,37 @@ To jest twój link do grupy %@! Brak uprawnień do nagrywania wiadomości głosowej No comment provided by engineer. + + No push server + Lokalnie + No comment provided by engineer. + No received or sent files Brak odebranych lub wysłanych plików No comment provided by engineer. + + No servers for private message routing. + servers error + + + No servers to receive files. + servers error + + + No servers to receive messages. + servers error + + + No servers to send files. + servers error + + + No user identifiers. + Brak identyfikatorów użytkownika. + No comment provided by engineer. + Not compatible! Nie kompatybilny! @@ -4723,6 +4867,10 @@ To jest twój link do grupy %@! Powiadomienia są wyłączone! No comment provided by engineer. + + Notifications privacy + No comment provided by engineer. + Now admins can: - delete members' messages. @@ -4781,8 +4929,8 @@ Wymaga włączenia VPN. Hosty onion nie będą używane. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. Tylko urządzenia klienckie przechowują profile użytkowników, kontakty, grupy i wiadomości wysyłane za pomocą **2-warstwowego szyfrowania end-to-end**. No comment provided by engineer. @@ -4866,6 +5014,10 @@ Wymaga włączenia VPN. Otwórz Ustawienia No comment provided by engineer. + + Open changes + No comment provided by engineer. + Open chat Otwórz czat @@ -4876,6 +5028,10 @@ Wymaga włączenia VPN. Otwórz konsolę czatu authentication reason + + Open conditions + No comment provided by engineer. + Open group Grupa otwarta @@ -4886,26 +5042,19 @@ Wymaga włączenia VPN. Otwórz migrację na innym urządzeniu authentication reason - - Open server settings - Otwórz ustawienia serwera - No comment provided by engineer. - - - Open user profiles - Otwórz profile użytkownika - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Otwarto źródłowy protokół i kod - każdy może uruchomić serwery. - No comment provided by engineer. - Opening app… Otwieranie aplikacji… No comment provided by engineer. + + Operator + No comment provided by engineer. + + + Operator server + alert title + Or paste archive link Lub wklej link archiwum @@ -4926,16 +5075,15 @@ Wymaga włączenia VPN. Lub pokaż ten kod No comment provided by engineer. + + Or to share privately + No comment provided by engineer. + Other Inne No comment provided by engineer. - - Other %@ servers - Inne %@ serwery - No comment provided by engineer. - Other file errors: %@ @@ -5018,13 +5166,8 @@ Wymaga włączenia VPN. Oczekujące No comment provided by engineer. - - People can connect to you only via the links you share. - Ludzie mogą się z Tobą połączyć tylko poprzez linki, które udostępniasz. - No comment provided by engineer. - - - Periodically + + Periodic Okresowo No comment provided by engineer. @@ -5147,16 +5290,15 @@ Błąd: %@ Zachowaj ostatnią wersję roboczą wiadomości wraz z załącznikami. No comment provided by engineer. - - Preset server - Wstępnie ustawiony serwer - No comment provided by engineer. - Preset server address Wstępnie ustawiony adres serwera No comment provided by engineer. + + Preset servers + No comment provided by engineer. + Preview Podgląd @@ -5235,7 +5377,7 @@ Błąd: %@ Profile update will be sent to your contacts. Aktualizacja profilu zostanie wysłana do Twoich kontaktów. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -5329,6 +5471,10 @@ Włącz w ustawianiach *Sieć i serwery* . Proxy wymaga hasła No comment provided by engineer. + + Push Notifications + No comment provided by engineer. + Push notifications Powiadomienia push @@ -5369,26 +5515,21 @@ Włącz w ustawianiach *Sieć i serwery* . Przeczytaj więcej No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - Przeczytaj więcej w [Podręczniku Użytkownika](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - No comment provided by engineer. - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). Przeczytaj więcej w [Poradniku Użytkownika](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + Przeczytaj więcej w [Podręczniku Użytkownika](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). Przeczytaj więcej w [Podręczniku Użytkownika](https://simplex.chat/docs/guide/readme.html#connect-to-friends). No comment provided by engineer. - - Read more in our GitHub repository. - Przeczytaj więcej na naszym repozytorium GitHub. - No comment provided by engineer. - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). Przeczytaj więcej na naszym [repozytorium GitHub](https://github.com/simplex-chat/simplex-chat#readme). @@ -5705,6 +5846,14 @@ Włącz w ustawianiach *Sieć i serwery* . Ujawnij chat item action + + Review conditions + No comment provided by engineer. + + + Review later + No comment provided by engineer. + Revoke Odwołaj @@ -5750,6 +5899,14 @@ Włącz w ustawianiach *Sieć i serwery* . Bezpieczniejsze grupy No comment provided by engineer. + + Same conditions will apply to operator **%@**. + No comment provided by engineer. + + + Same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + Save Zapisz @@ -5819,7 +5976,7 @@ Włącz w ustawianiach *Sieć i serwery* . Save servers? Zapisać serwery? - No comment provided by engineer. + alert title Save welcome message? @@ -6031,11 +6188,6 @@ Włącz w ustawianiach *Sieć i serwery* . Wyślij powiadomienia No comment provided by engineer. - - Send notifications: - Wyślij powiadomienia: - No comment provided by engineer. - Send questions and ideas Wyślij pytania i pomysły @@ -6161,6 +6313,10 @@ Włącz w ustawianiach *Sieć i serwery* . Serwer No comment provided by engineer. + + Server added to operator %@. + alert message + Server address Adres serwera @@ -6176,6 +6332,18 @@ Włącz w ustawianiach *Sieć i serwery* . Adres serwera jest niekompatybilny z ustawieniami sieci: %@. No comment provided by engineer. + + Server operator changed. + alert title + + + Server operators + No comment provided by engineer. + + + Server protocol changed. + alert title + Server requires authorization to create queues, check password Serwer wymaga autoryzacji do tworzenia kolejek, sprawdź hasło @@ -6294,22 +6462,35 @@ Włącz w ustawianiach *Sieć i serwery* . Share Udostępnij - chat item action + alert action + chat item action Share 1-time link Udostępnij 1-razowy link No comment provided by engineer. + + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + No comment provided by engineer. + Share address Udostępnij adres No comment provided by engineer. + + Share address publicly + No comment provided by engineer. + Share address with contacts? Udostępnić adres kontaktom? - No comment provided by engineer. + alert title Share from other apps. @@ -6426,6 +6607,14 @@ Włącz w ustawianiach *Sieć i serwery* . Adres SimpleX No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + No comment provided by engineer. + SimpleX contact address Adres kontaktowy SimpleX @@ -6515,6 +6704,11 @@ Włącz w ustawianiach *Sieć i serwery* . Podczas importu wystąpiły niekrytyczne błędy: No comment provided by engineer. + + Some servers failed the test: +%@ + alert message + Somebody Ktoś @@ -6598,12 +6792,12 @@ Włącz w ustawianiach *Sieć i serwery* . Stop sharing Przestań udostępniać - No comment provided by engineer. + alert action Stop sharing address? Przestać udostępniać adres? - No comment provided by engineer. + alert title Stopping chat @@ -6751,7 +6945,7 @@ Włącz w ustawianiach *Sieć i serwery* . Tests failed! Testy nie powiodły się! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6768,11 +6962,6 @@ Włącz w ustawianiach *Sieć i serwery* . Podziękowania dla użytkowników - wkład za pośrednictwem Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - Pierwsza platforma bez żadnych identyfikatorów użytkowników – z założenia prywatna. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6785,6 +6974,10 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Aplikacja może powiadamiać Cię, gdy otrzymujesz wiadomości lub prośby o kontakt — otwórz ustawienia, aby włączyć. No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). Aplikacja zapyta o potwierdzenie pobierania od nieznanych serwerów plików (poza .onion). @@ -6800,6 +6993,10 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Kod, który zeskanowałeś nie jest kodem QR linku SimpleX. No comment provided by engineer. + + The connection reached the limit of undelivered messages, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! Zaakceptowane przez Ciebie połączenie zostanie anulowane! @@ -6820,6 +7017,11 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Szyfrowanie działa, a nowe uzgodnienie szyfrowania nie jest wymagane. Może to spowodować błędy w połączeniu! No comment provided by engineer. + + The future of messaging + Następna generacja prywatnych wiadomości + No comment provided by engineer. + The hash of the previous message is different. Hash poprzedniej wiadomości jest inny. @@ -6845,11 +7047,6 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Wiadomości zostaną oznaczone jako moderowane dla wszystkich członków. No comment provided by engineer. - - The next generation of private messaging - Następna generacja prywatnych wiadomości - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. Stara baza danych nie została usunięta podczas migracji, można ją usunąć. @@ -6860,6 +7057,10 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Profil jest udostępniany tylko Twoim kontaktom. No comment provided by engineer. + + The second preset operator in the app! + No comment provided by engineer. + The second tick we missed! ✅ Drugi tik, który przegapiliśmy! ✅ @@ -6875,6 +7076,10 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Serwery dla nowych połączeń bieżącego profilu czatu **%@**. No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. Tekst, który wkleiłeś nie jest linkiem SimpleX. @@ -6890,6 +7095,10 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Motywy No comment provided by engineer. + + These conditions will also apply for: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. Te ustawienia dotyczą Twojego bieżącego profilu **%@**. @@ -6990,9 +7199,8 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Aby nawiązać nowe połączenie No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Aby chronić prywatność, zamiast identyfikatorów użytkowników używanych przez wszystkie inne platformy, SimpleX ma identyfikatory dla kolejek wiadomości, oddzielne dla każdego z Twoich kontaktów. + + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -7012,6 +7220,15 @@ You will be prompted to complete authentication before this feature is enabled.< Przed włączeniem tej funkcji zostanie wyświetlony monit uwierzytelniania. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + Aby chronić prywatność, zamiast identyfikatorów użytkowników używanych przez wszystkie inne platformy, SimpleX ma identyfikatory dla kolejek wiadomości, oddzielne dla każdego z Twoich kontaktów. + No comment provided by engineer. + + + To receive + No comment provided by engineer. + To record speech please grant permission to use Microphone. Aby nagrać rozmowę, proszę zezwolić na użycie Mikrofonu. @@ -7032,11 +7249,19 @@ Przed włączeniem tej funkcji zostanie wyświetlony monit uwierzytelniania.Aby ujawnić Twój ukryty profil, wprowadź pełne hasło w pole wyszukiwania na stronie **Twoich profili czatu**. No comment provided by engineer. + + To send + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. Aby obsługiwać natychmiastowe powiadomienia push, należy zmigrować bazę danych czatu. No comment provided by engineer. + + To use the servers of **%@**, accept conditions of use. + No comment provided by engineer. + To verify end-to-end encryption with your contact compare (or scan) the code on your devices. Aby zweryfikować szyfrowanie end-to-end z Twoim kontaktem porównaj (lub zeskanuj) kod na waszych urządzeniach. @@ -7127,6 +7352,10 @@ Przed włączeniem tej funkcji zostanie wyświetlony monit uwierzytelniania.Odblokować członka? No comment provided by engineer. + + Undelivered messages + No comment provided by engineer. + Unexpected migration state Nieoczekiwany stan migracji @@ -7284,6 +7513,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Wgrywanie archiwum No comment provided by engineer. + + Use %@ + No comment provided by engineer. + Use .onion hosts Użyj hostów .onion @@ -7309,6 +7542,14 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Użyj obecnego profilu No comment provided by engineer. + + Use for files + No comment provided by engineer. + + + Use for messages + No comment provided by engineer. + Use for new connections Użyj dla nowych połączeń @@ -7349,6 +7590,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Użyj serwera No comment provided by engineer. + + Use servers + No comment provided by engineer. + Use the app while in the call. Używaj aplikacji podczas połączenia. @@ -7439,11 +7684,19 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Filmy i pliki do 1gb No comment provided by engineer. + + View conditions + No comment provided by engineer. + View security code Pokaż kod bezpieczeństwa No comment provided by engineer. + + View updated conditions + No comment provided by engineer. + Visible history Widoczna historia @@ -7554,9 +7807,8 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Podczas łączenia połączeń audio i wideo. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - Kiedy ludzie proszą o połączenie, możesz je zaakceptować lub odrzucić. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -7716,6 +7968,18 @@ Powtórzyć prośbę dołączenia? Możesz to zmienić w ustawieniach wyglądu. No comment provided by engineer. + + You can configure operators in Network & servers settings. + No comment provided by engineer. + + + You can configure servers via settings. + No comment provided by engineer. + + + You can create it in user picker. + No comment provided by engineer. + You can create it later Możesz go utworzyć później @@ -7756,6 +8020,10 @@ Powtórzyć prośbę dołączenia? Możesz wysyłać wiadomości do %@ ze zarchiwizowanych kontaktów. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + No comment provided by engineer. + You can set lock screen notification preview via settings. Podgląd powiadomień na ekranie blokady można ustawić w ustawieniach. @@ -7771,11 +8039,6 @@ Powtórzyć prośbę dołączenia? Możesz udostępnić ten adres Twoim kontaktom, aby umożliwić im połączenie z **%@**. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - Możesz udostępnić swój adres jako link lub jako kod QR - każdy będzie mógł się z Tobą połączyć. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app Możesz rozpocząć czat poprzez Ustawienia aplikacji / Baza danych lub poprzez ponowne uruchomienie aplikacji @@ -7799,23 +8062,23 @@ Powtórzyć prośbę dołączenia? You can view invitation link again in connection details. Możesz zobaczyć link zaproszenia ponownie w szczegółach połączenia. - No comment provided by engineer. + alert message You can't send messages! Nie możesz wysyłać wiadomości! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - Kontrolujesz przez który serwer(y) **odbierać** wiadomości, Twoje kontakty - serwery, których używasz do wysyłania im wiadomości. - No comment provided by engineer. - You could not be verified; please try again. Nie można zweryfikować użytkownika; proszę spróbować ponownie. No comment provided by engineer. + + You decide who can connect. + Ty decydujesz, kto może się połączyć. + No comment provided by engineer. + You have already requested connection via this address! Już prosiłeś o połączenie na ten adres! @@ -7938,11 +8201,6 @@ Powtórzyć prośbę połączenia? Używasz profilu incognito dla tej grupy - aby zapobiec udostępnianiu głównego profilu zapraszanie kontaktów jest zabronione No comment provided by engineer. - - Your %@ servers - Twoje serwery %@ - No comment provided by engineer. - Your ICE servers Twoje serwery ICE @@ -7958,11 +8216,6 @@ Powtórzyć prośbę połączenia? Twój adres SimpleX No comment provided by engineer. - - Your XFTP servers - Twoje serwery XFTP - No comment provided by engineer. - Your calls Twoje połączenia @@ -8063,16 +8316,15 @@ Powtórzyć prośbę połączenia? Twój losowy profil No comment provided by engineer. - - Your server - Twój serwer - No comment provided by engineer. - Your server address Twój adres serwera No comment provided by engineer. + + Your servers + No comment provided by engineer. + Your settings Twoje ustawienia @@ -8493,6 +8745,10 @@ Powtórzyć prośbę połączenia? wygasły No comment provided by engineer. + + for better metadata privacy. + No comment provided by engineer. + forwarded przekazane dalej @@ -9115,6 +9371,33 @@ ostatnia otrzymana wiadomość: %2$@ + +
+ +
+ + + %d new events + notification body + + + From: %@ + notification body + + + New events + notification + + + New messages + notification + + + New messages in %d chats + notification body + + +
diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/pl.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff b/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff index c63fec4a08..ffbaec1d96 100644 --- a/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff +++ b/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff @@ -187,23 +187,18 @@ ) No comment provided by engineer. - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Adicionar novo contato**: para criar seu QR Code ou link único para seu contato. - No comment provided by engineer. - **Create link / QR code** for your contact to use. **Crie um link / QR code** para seu contato usar. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **Mais privado**: verifique as novas mensagens a cada 20 minutos. O token do dispositivo é compartilhado com o servidor do SimpleX Chat, mas não quantos contatos ou mensagens você tem. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **Mais privado**: não use o servidor de notificações do SimpleX Chat, verifique as mensagens periodicamente em segundo plano (depende da frequência com que você usa o aplicativo). No comment provided by engineer. @@ -217,8 +212,8 @@ **Observação**: NÃO será possível recuperar ou alterar a frase secreta se você a perder. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **Recomendado**: o token do dispositivo e as notificações são enviados para o servidor de notificações do SimpleX Chat, mas não o conteúdo, o tamanho ou o remetente da mensagem. No comment provided by engineer. @@ -1761,8 +1756,8 @@ A imagem será recebida quando seu contato estiver online, aguarde ou verifique mais tarde! No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Imune a spam e abuso No comment provided by engineer. @@ -2209,8 +2204,8 @@ We will be adding server redundancy to prevent lost messages. Hosts Onion não serão usados. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. No comment provided by engineer. @@ -2267,8 +2262,8 @@ We will be adding server redundancy to prevent lost messages. Abrir console de chat authentication reason - - Open-source protocol and code – anybody can run the servers. + + Anybody can host servers. Protocolo de código aberto – qualquer um pode executar os servidores. No comment provided by engineer. @@ -2306,8 +2301,8 @@ We will be adding server redundancy to prevent lost messages. Cole o link que você recebeu na caixa abaixo para conectar com o seu contato. No comment provided by engineer. - - People can connect to you only via the links you share. + + You decide who can connect. Pessoas podem se conectar com você somente via links compartilhados. No comment provided by engineer. @@ -2961,8 +2956,8 @@ We will be adding server redundancy to prevent lost messages. Thank you for installing SimpleX Chat! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. + + No user identifiers. A 1ª plataforma sem nenhum identificador de usuário – privada por design. No comment provided by engineer. @@ -2998,8 +2993,8 @@ We will be adding server redundancy to prevent lost messages. The microphone does not work when the app is in the background. No comment provided by engineer. - - The next generation of private messaging + + The future of messaging A próxima geração de mensageiros privados No comment provided by engineer. @@ -3071,8 +3066,8 @@ We will be adding server redundancy to prevent lost messages. To prevent the call interruption, enable Do Not Disturb mode. No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. No comment provided by engineer. @@ -3402,10 +3397,6 @@ Para se conectar, peça ao seu contato para criar outro link de conexão e verif Você pode usar markdown para formatar mensagens: No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - No comment provided by engineer. - You could not be verified; please try again. Você não pôde ser verificado; por favor, tente novamente. @@ -5482,8 +5473,8 @@ Isso pode acontecer por causa de algum bug ou quando a conexão está comprometi (this device v%@) este dispositivo - - **Add contact**: to create a new invitation link, or connect via a link you received. + + **Create 1-time link**: to create and share a new invitation link. **Adicionar contato**: criar um novo link de convite ou conectar via um link que você recebeu. diff --git a/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff b/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff index a9bf86e778..cdadd677f9 100644 --- a/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff +++ b/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff @@ -214,22 +214,17 @@ Available in v5.1 ) No comment provided by engineer. - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Adicionar novo contato**: para criar seu QR Code único ou link para seu contato. - No comment provided by engineer. - **Create link / QR code** for your contact to use. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **Mais privado**: verifique novas mensagens a cada 20 minutos. O token do dispositivo é compartilhado com o servidor SimpleX Chat, mas não com quantos contatos ou mensagens você possui. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **Totalmente privado**: não use o servidor de notificações do SimpleX Chat, verifique as mensagens periodicamente em segundo plano (depende da frequência com que você usa o aplicativo). No comment provided by engineer. @@ -242,8 +237,8 @@ Available in v5.1 **Atenção**: Você NÃO poderá recuperar ou alterar a senha caso a perca. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **Recomendado**: O token do dispositivo e as notificações são enviados ao servidor de notificação do SimpleX Chat, mas não o conteúdo, o tamanho da mensagem ou de quem ela é. No comment provided by engineer. @@ -1812,8 +1807,8 @@ Available in v5.1 Immediately No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam No comment provided by engineer. @@ -2278,8 +2273,8 @@ Available in v5.1 Onion hosts will not be used. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. No comment provided by engineer. @@ -2338,8 +2333,8 @@ Available in v5.1 Open user profiles authentication reason - - Open-source protocol and code – anybody can run the servers. + + Anybody can host servers. No comment provided by engineer. @@ -2394,8 +2389,8 @@ Available in v5.1 Paste the link you received into the box below to connect with your contact. No comment provided by engineer. - - People can connect to you only via the links you share. + + You decide who can connect. No comment provided by engineer. @@ -3098,8 +3093,8 @@ Available in v5.1 Thanks to the users – contribute via Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. + + No user identifiers. No comment provided by engineer. @@ -3143,8 +3138,8 @@ It can happen because of some bug or when the connection is compromised.The message will be marked as moderated for all members. No comment provided by engineer. - - The next generation of private messaging + + The future of messaging No comment provided by engineer. @@ -3215,8 +3210,8 @@ It can happen because of some bug or when the connection is compromised.To make a new connection No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. No comment provided by engineer. @@ -3582,10 +3577,6 @@ SimpleX Lock must be enabled. You can't send messages! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - No comment provided by engineer. - You could not be verified; please try again. No comment provided by engineer. @@ -4302,8 +4293,8 @@ SimpleX servers cannot see your profile. %lld novas interface de idiomas No comment provided by engineer. - - **Add contact**: to create a new invitation link, or connect via a link you received. + + **Create 1-time link**: to create and share a new invitation link. **Adicionar contato**: para criar um novo link de convite ou conectar-se por meio de um link que você recebeu. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index ced93b4c12..119f1650a0 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -12,21 +12,6 @@ No comment provided by engineer. - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - ( ( @@ -127,6 +112,14 @@ %@ подтверждён No comment provided by engineer. + + %@ server + No comment provided by engineer. + + + %@ servers + No comment provided by engineer. + %@ uploaded %@ загружено @@ -352,14 +345,9 @@ ) No comment provided by engineer. - - **Add contact**: to create a new invitation link, or connect via a link you received. - **Добавить контакт**: создать новую ссылку-приглашение или подключиться через полученную ссылку. - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Добавить новый контакт**: чтобы создать одноразовый QR код или ссылку для Вашего контакта. + + **Create 1-time link**: to create and share a new invitation link. + **Добавить контакт**: создать и поделиться новой ссылкой-приглашением. No comment provided by engineer. @@ -367,14 +355,14 @@ **Создать группу**: создать новую группу. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. - **Более конфиденциально**: проверять новые сообщения каждые 20 минут. Токен устройства будет отправлен на сервер уведомлений SimpleX Chat, но у сервера не будет информации о количестве контактов и сообщений. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. + **Более конфиденциально**: проверять новые сообщения каждые 20 минут. Только токен устройства будет отправлен на сервер уведомлений SimpleX Chat, но у сервера не будет информации о количестве контактов и какой либо информации о сообщениях. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). - **Самый конфиденциальный**: не использовать сервер уведомлений SimpleX Chat, проверять сообщения периодически в фоновом режиме (зависит от того насколько часто Вы используете приложение). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. + **Самый конфиденциальный**: не использовать сервер уведомлений SimpleX Chat. Сообщения проверяются в фоновом режиме, когда система позволяет, в зависимости от того, как часто Вы используете приложение. No comment provided by engineer. @@ -387,11 +375,15 @@ **Внимание**: Вы не сможете восстановить или поменять пароль, если Вы его потеряете. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **Рекомендовано**: токен устройства и уведомления отправляются на сервер SimpleX Chat, но сервер не получает сами сообщения, их размер или от кого они. No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **Внимание**: для работы мгновенных уведомлений пароль должен быть сохранен в Keychain. @@ -498,6 +490,14 @@ 1 неделю time interval + + 1-time link + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + No comment provided by engineer. + 5 minutes 5 минут @@ -567,21 +567,11 @@ Прекратить изменение адреса? No comment provided by engineer. - - About SimpleX - О SimpleX - No comment provided by engineer. - About SimpleX Chat Информация о SimpleX Chat No comment provided by engineer. - - About SimpleX address - Об адресе SimpleX - No comment provided by engineer. - Accent Акцент @@ -594,6 +584,10 @@ accept incoming call via notification swipe action + + Accept conditions + No comment provided by engineer. + Accept connection request? Принять запрос? @@ -610,6 +604,10 @@ accept contact request via notification swipe action + + Accepted conditions + No comment provided by engineer. + Acknowledged Подтверждено @@ -630,16 +628,6 @@ Добавьте адрес в свой профиль, чтобы Ваши контакты могли поделиться им. Профиль будет отправлен Вашим контактам. No comment provided by engineer. - - Add contact - Добавить контакт - No comment provided by engineer. - - - Add preset servers - Добавить серверы по умолчанию - No comment provided by engineer. - Add profile Добавить профиль @@ -665,6 +653,14 @@ Добавить приветственное сообщение No comment provided by engineer. + + Added media & file servers + No comment provided by engineer. + + + Added message servers + No comment provided by engineer. + Additional accent Дополнительный акцент @@ -690,6 +686,14 @@ Изменение адреса будет прекращено. Будет использоваться старый адрес. No comment provided by engineer. + + Address or 1-time link? + No comment provided by engineer. + + + Address settings + No comment provided by engineer. + Admins can block a member for all. Админы могут заблокировать члена группы. @@ -735,6 +739,11 @@ Все члены группы, которые соединились через эту ссылку, останутся в группе. No comment provided by engineer. + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + Все сообщения и файлы отправляются с **end-to-end шифрованием**, с постквантовой безопасностью в прямых разговорах. + No comment provided by engineer. + All messages will be deleted - this cannot be undone! Все сообщения будут удалены - это нельзя отменить! @@ -915,6 +924,11 @@ Принять звонок No comment provided by engineer. + + Anybody can host servers. + Кто угодно может запустить сервер. + No comment provided by engineer. + App build: %@ Сборка приложения: %@ @@ -1258,7 +1272,8 @@ Cancel Отменить - alert button + alert action + alert button Cancel migration @@ -1341,6 +1356,10 @@ authentication reason set passcode view + + Change user profiles + authentication reason + Chat archive Архив чата @@ -1426,10 +1445,18 @@ Чаты No comment provided by engineer. + + Check messages every 20 min. + No comment provided by engineer. + + + Check messages when allowed. + No comment provided by engineer. + Check server address and try again. Проверьте адрес сервера и попробуйте снова. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1516,16 +1543,47 @@ Готово No comment provided by engineer. + + Conditions accepted on: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for following operator(s): **%@**. + No comment provided by engineer. + + + Conditions of use + No comment provided by engineer. + + + Conditions will be accepted for enabled operators after 30 days. + No comment provided by engineer. + + + Conditions will be accepted for operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + No comment provided by engineer. + Configure ICE servers Настройка ICE серверов No comment provided by engineer. - - Configured %@ servers - Настроенные %@ серверы - No comment provided by engineer. - Confirm Подтвердить @@ -1715,6 +1773,10 @@ This is your own one-time link! Запрос на соединение отправлен! No comment provided by engineer. + + Connection security + No comment provided by engineer. + Connection terminated Подключение прервано @@ -1830,6 +1892,10 @@ This is your own one-time link! Создать No comment provided by engineer. + + Create 1-time link + No comment provided by engineer. + Create SimpleX address Создать адрес SimpleX @@ -1840,11 +1906,6 @@ This is your own one-time link! Создайте группу, используя случайный профиль. No comment provided by engineer. - - Create an address to let people connect with you. - Создайте адрес, чтобы можно было соединиться с вами. - No comment provided by engineer. - Create file Создание файла @@ -1925,6 +1986,10 @@ This is your own one-time link! Текущий Код No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + No comment provided by engineer. + Current passphrase… Текущий пароль… @@ -2081,7 +2146,8 @@ This is your own one-time link! Delete Удалить - chat item action + alert action + chat item action swipe action @@ -2299,6 +2365,10 @@ This is your own one-time link! Ошибки удаления No comment provided by engineer. + + Delivered even when Apple drops them. + No comment provided by engineer. + Delivery Доставка @@ -2580,6 +2650,10 @@ This is your own one-time link! Длительность No comment provided by engineer. + + E2E encrypted notifications. + No comment provided by engineer. + Edit Редактировать @@ -2600,6 +2674,10 @@ This is your own one-time link! Включить (кроме исключений) No comment provided by engineer. + + Enable Flux + No comment provided by engineer. + Enable SimpleX Lock Включить блокировку SimpleX @@ -2805,6 +2883,10 @@ This is your own one-time link! Ошибка при прекращении изменения адреса No comment provided by engineer. + + Error accepting conditions + alert title + Error accepting contact request Ошибка при принятии запроса на соединение @@ -2820,6 +2902,10 @@ This is your own one-time link! Ошибка при добавлении членов группы No comment provided by engineer. + + Error adding server + alert title + Error changing address Ошибка при изменении адреса @@ -2960,10 +3046,9 @@ This is your own one-time link! Ошибка при вступлении в группу No comment provided by engineer. - - Error loading %@ servers - Ошибка загрузки %@ серверов - No comment provided by engineer. + + Error loading servers + alert title Error migrating settings @@ -3000,11 +3085,6 @@ This is your own one-time link! Ошибка сброса статистики No comment provided by engineer. - - Error saving %@ servers - Ошибка при сохранении %@ серверов - No comment provided by engineer. - Error saving ICE servers Ошибка при сохранении ICE серверов @@ -3025,6 +3105,10 @@ This is your own one-time link! Ошибка сохранения пароля в Keychain No comment provided by engineer. + + Error saving servers + alert title + Error saving settings Ошибка сохранения настроек @@ -3095,6 +3179,10 @@ This is your own one-time link! Ошибка при обновлении сообщения No comment provided by engineer. + + Error updating server + alert title + Error updating settings Ошибка при сохранении настроек сети @@ -3140,6 +3228,10 @@ This is your own one-time link! Ошибки No comment provided by engineer. + + Errors in servers configuration. + servers error + Even when disabled in the conversation. Даже когда они выключены в разговоре. @@ -3342,11 +3434,27 @@ This is your own one-time link! Починка не поддерживается членом группы No comment provided by engineer. + + For chat profile %@: + servers error + For console Для консоли No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + No comment provided by engineer. + + + For private routing + No comment provided by engineer. + + + For social media + No comment provided by engineer. + Forward Переслать @@ -3656,9 +3764,12 @@ Error: %2$@ Как SimpleX работает No comment provided by engineer. - - How it works - Как это работает + + How it affects privacy + No comment provided by engineer. + + + How it helps privacy No comment provided by engineer. @@ -3668,7 +3779,7 @@ Error: %2$@ How to use it - Как использовать + Про адрес No comment provided by engineer. @@ -3731,8 +3842,8 @@ Error: %2$@ Сразу No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Защищен от спама No comment provided by engineer. @@ -3872,6 +3983,11 @@ More improvements are coming soon! [SimpleX Chat для терминала](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Мгновенно + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3879,11 +3995,6 @@ More improvements are coming soon! No comment provided by engineer. - - Instantly - Мгновенно - No comment provided by engineer. - Interface Интерфейс @@ -3932,7 +4043,7 @@ More improvements are coming soon! Invalid server address! Ошибка в адресе сервера! - No comment provided by engineer. + alert title Invalid status @@ -4060,7 +4171,7 @@ This is your link for group %@! Keep Оставить - No comment provided by engineer. + alert action Keep conversation @@ -4075,7 +4186,7 @@ This is your link for group %@! Keep unused invitation? Оставить неиспользованное приглашение? - No comment provided by engineer. + alert title Keep your connections @@ -4162,11 +4273,6 @@ This is your link for group %@! "Живые" сообщения No comment provided by engineer. - - Local - Локальные - No comment provided by engineer. - Local name Локальное имя @@ -4187,11 +4293,6 @@ This is your link for group %@! Режим блокировки No comment provided by engineer. - - Make a private connection - Добавьте контакт - No comment provided by engineer. - Make one message disappear Одно исчезающее сообщение @@ -4202,21 +4303,11 @@ This is your link for group %@! Сделайте профиль скрытым! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Пожалуйста, проверьте, что адреса %@ серверов имеют правильный формат, каждый адрес на отдельной строке и не повторяется (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. Пожалуйста, проверьте, что адреса WebRTC ICE серверов имеют правильный формат, каждый адрес на отдельной строке и не повторяется. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Много пользователей спросили: *как SimpleX доставляет сообщения без идентификаторов пользователей?* - No comment provided by engineer. - Mark deleted for everyone Пометить как удаленное для всех @@ -4497,6 +4588,10 @@ This is your link for group %@! Более надежное соединение с сетью. No comment provided by engineer. + + More reliable notifications + No comment provided by engineer. + Most likely this connection is deleted. Скорее всего, соединение удалено. @@ -4532,6 +4627,10 @@ This is your link for group %@! Интернет-соединение No comment provided by engineer. + + Network decentralization + No comment provided by engineer. + Network issues - message expired after many attempts to send it. Ошибка сети - сообщение не было отправлено после многократных попыток. @@ -4542,6 +4641,10 @@ This is your link for group %@! Статус сети No comment provided by engineer. + + Network operator + No comment provided by engineer. + Network settings Настройки сети @@ -4602,6 +4705,10 @@ This is your link for group %@! Новое имя No comment provided by engineer. + + New events + notification + New in %@ Новое в %@ @@ -4627,6 +4734,10 @@ This is your link for group %@! Новый пароль… No comment provided by engineer. + + New server + No comment provided by engineer. + No Нет @@ -4682,6 +4793,14 @@ This is your link for group %@! Нет информации, попробуйте перезагрузить No comment provided by engineer. + + No media & file servers. + servers error + + + No message servers. + servers error + No network connection Нет интернет-соединения @@ -4702,11 +4821,37 @@ This is your link for group %@! Нет разрешения для записи голосового сообщения No comment provided by engineer. + + No push server + Локальные + No comment provided by engineer. + No received or sent files Нет полученных или отправленных файлов No comment provided by engineer. + + No servers for private message routing. + servers error + + + No servers to receive files. + servers error + + + No servers to receive messages. + servers error + + + No servers to send files. + servers error + + + No user identifiers. + Без идентификаторов пользователей. + No comment provided by engineer. + Not compatible! Несовместимая версия! @@ -4732,6 +4877,10 @@ This is your link for group %@! Уведомления выключены No comment provided by engineer. + + Notifications privacy + No comment provided by engineer. + Now admins can: - delete members' messages. @@ -4790,9 +4939,9 @@ Requires compatible VPN. Onion хосты не используются. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. - Только пользовательские устройства хранят контакты, группы и сообщения, которые отправляются **с двухуровневым end-to-end шифрованием**. + + Only client devices store user profiles, contacts, groups, and messages. + Только пользовательские устройства хранят контакты, группы и сообщения. No comment provided by engineer. @@ -4875,6 +5024,10 @@ Requires compatible VPN. Открыть Настройки No comment provided by engineer. + + Open changes + No comment provided by engineer. + Open chat Открыть чат @@ -4885,6 +5038,10 @@ Requires compatible VPN. Открыть консоль authentication reason + + Open conditions + No comment provided by engineer. + Open group Открыть группу @@ -4895,26 +5052,19 @@ Requires compatible VPN. Открытие миграции на другое устройство authentication reason - - Open server settings - Открыть настройки серверов - No comment provided by engineer. - - - Open user profiles - Открыть профили пользователя - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Открытый протокол и код - кто угодно может запустить сервер. - No comment provided by engineer. - Opening app… Приложение отрывается… No comment provided by engineer. + + Operator + No comment provided by engineer. + + + Operator server + alert title + Or paste archive link Или вставьте ссылку архива @@ -4935,16 +5085,15 @@ Requires compatible VPN. Или покажите этот код No comment provided by engineer. + + Or to share privately + No comment provided by engineer. + Other Другaя сеть No comment provided by engineer. - - Other %@ servers - Другие %@ серверы - No comment provided by engineer. - Other file errors: %@ @@ -5027,13 +5176,8 @@ Requires compatible VPN. В ожидании No comment provided by engineer. - - People can connect to you only via the links you share. - С Вами можно соединиться только через созданные Вами ссылки. - No comment provided by engineer. - - - Periodically + + Periodic Периодически No comment provided by engineer. @@ -5156,16 +5300,15 @@ Error: %@ Сохранить последний черновик, вместе с вложениями. No comment provided by engineer. - - Preset server - Сервер по умолчанию - No comment provided by engineer. - Preset server address Адрес сервера по умолчанию No comment provided by engineer. + + Preset servers + No comment provided by engineer. + Preview Просмотр @@ -5244,7 +5387,7 @@ Error: %@ Profile update will be sent to your contacts. Обновлённый профиль будет отправлен Вашим контактам. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -5338,6 +5481,10 @@ Enable in *Network & servers* settings. Прокси требует пароль No comment provided by engineer. + + Push Notifications + No comment provided by engineer. + Push notifications Доставка уведомлений @@ -5378,26 +5525,21 @@ Enable in *Network & servers* settings. Узнать больше No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - Узнать больше в [Руководстве пользователя](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - No comment provided by engineer. - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). Дополнительная информация в [Руководстве пользователя](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + Узнать больше в [Руководстве пользователя](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). Узнать больше в [Руководстве пользователя](https://simplex.chat/docs/guide/readme.html#connect-to-friends). No comment provided by engineer. - - Read more in our GitHub repository. - Узнайте больше из нашего GitHub репозитория. - No comment provided by engineer. - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). Узнайте больше из нашего [GitHub репозитория](https://github.com/simplex-chat/simplex-chat#readme). @@ -5714,6 +5856,14 @@ Enable in *Network & servers* settings. Показать chat item action + + Review conditions + No comment provided by engineer. + + + Review later + No comment provided by engineer. + Revoke Отозвать @@ -5759,6 +5909,14 @@ Enable in *Network & servers* settings. Более безопасные группы No comment provided by engineer. + + Same conditions will apply to operator **%@**. + No comment provided by engineer. + + + Same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + Save Сохранить @@ -5828,7 +5986,7 @@ Enable in *Network & servers* settings. Save servers? Сохранить серверы? - No comment provided by engineer. + alert title Save welcome message? @@ -6040,11 +6198,6 @@ Enable in *Network & servers* settings. Отправлять уведомления No comment provided by engineer. - - Send notifications: - Отправлять уведомления: - No comment provided by engineer. - Send questions and ideas Отправьте вопросы и идеи @@ -6170,6 +6323,10 @@ Enable in *Network & servers* settings. Сервер No comment provided by engineer. + + Server added to operator %@. + alert message + Server address Адрес сервера @@ -6185,6 +6342,18 @@ Enable in *Network & servers* settings. Адрес сервера несовместим с сетевыми настройками: %@. No comment provided by engineer. + + Server operator changed. + alert title + + + Server operators + No comment provided by engineer. + + + Server protocol changed. + alert title + Server requires authorization to create queues, check password Сервер требует авторизации для создания очередей, проверьте пароль @@ -6303,22 +6472,35 @@ Enable in *Network & servers* settings. Share Поделиться - chat item action + alert action + chat item action Share 1-time link Поделиться одноразовой ссылкой No comment provided by engineer. + + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + No comment provided by engineer. + Share address Поделиться адресом No comment provided by engineer. + + Share address publicly + No comment provided by engineer. + Share address with contacts? Поделиться адресом с контактами? - No comment provided by engineer. + alert title Share from other apps. @@ -6435,6 +6617,14 @@ Enable in *Network & servers* settings. Адрес SimpleX No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + No comment provided by engineer. + SimpleX contact address SimpleX ссылка-контакт @@ -6525,6 +6715,11 @@ Enable in *Network & servers* settings. Во время импорта произошли некоторые ошибки: No comment provided by engineer. + + Some servers failed the test: +%@ + alert message + Somebody Контакт @@ -6608,12 +6803,12 @@ Enable in *Network & servers* settings. Stop sharing Прекратить делиться - No comment provided by engineer. + alert action Stop sharing address? Прекратить делиться адресом? - No comment provided by engineer. + alert title Stopping chat @@ -6763,7 +6958,7 @@ Enable in *Network & servers* settings. Tests failed! Ошибка тестов! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6780,11 +6975,6 @@ Enable in *Network & servers* settings. Благодаря пользователям – добавьте переводы через Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - Первая в мире платформа без идентификаторов пользователей. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6797,6 +6987,10 @@ It can happen because of some bug or when the connection is compromised.Приложение может посылать Вам уведомления о сообщениях и запросах на соединение - уведомления можно включить в Настройках. No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). Приложение будет запрашивать подтверждение загрузки с неизвестных серверов (за исключением .onion адресов). @@ -6812,6 +7006,10 @@ It can happen because of some bug or when the connection is compromised.Этот QR код не является SimpleX-ccылкой. No comment provided by engineer. + + The connection reached the limit of undelivered messages, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! Подтвержденное соединение будет отменено! @@ -6832,6 +7030,11 @@ It can happen because of some bug or when the connection is compromised.Шифрование работает, и новое соглашение не требуется. Это может привести к ошибкам соединения! No comment provided by engineer. + + The future of messaging + Будущее коммуникаций + No comment provided by engineer. + The hash of the previous message is different. Хэш предыдущего сообщения отличается. @@ -6857,11 +7060,6 @@ It can happen because of some bug or when the connection is compromised.Сообщения будут помечены как удаленные для всех членов группы. No comment provided by engineer. - - The next generation of private messaging - Новое поколение приватных сообщений - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. Предыдущая версия данных чата не удалена при перемещении, её можно удалить. @@ -6872,6 +7070,10 @@ It can happen because of some bug or when the connection is compromised.Профиль отправляется только Вашим контактам. No comment provided by engineer. + + The second preset operator in the app! + No comment provided by engineer. + The second tick we missed! ✅ Вторая галочка - знать, что доставлено! ✅ @@ -6887,6 +7089,10 @@ It can happen because of some bug or when the connection is compromised.Серверы для новых соединений Вашего текущего профиля чата **%@**. No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. Вставленный текст не является SimpleX-ссылкой. @@ -6902,6 +7108,10 @@ It can happen because of some bug or when the connection is compromised.Темы No comment provided by engineer. + + These conditions will also apply for: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. Установки для Вашего активного профиля **%@**. @@ -7002,9 +7212,8 @@ It can happen because of some bug or when the connection is compromised.Чтобы соединиться No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Чтобы защитить Вашу конфиденциальность, вместо ID пользователей, которые есть в других платформах, SimpleX использует ID для очередей сообщений, разные для каждого контакта. + + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -7024,6 +7233,15 @@ You will be prompted to complete authentication before this feature is enabled.< Вам будет нужно пройти аутентификацию для включения блокировки. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + Чтобы защитить Вашу конфиденциальность, SimpleX использует разные идентификаторы для каждого Вашeго контакта. + No comment provided by engineer. + + + To receive + No comment provided by engineer. + To record speech please grant permission to use Microphone. Для записи речи, пожалуйста, дайте разрешение на использование микрофона. @@ -7044,11 +7262,19 @@ You will be prompted to complete authentication before this feature is enabled.< Чтобы показать Ваш скрытый профиль, введите его пароль в поле поиска на странице **Ваши профили чата**. No comment provided by engineer. + + To send + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. Для поддержки мгновенный доставки уведомлений данные чата должны быть перемещены. No comment provided by engineer. + + To use the servers of **%@**, accept conditions of use. + No comment provided by engineer. + To verify end-to-end encryption with your contact compare (or scan) the code on your devices. Чтобы подтвердить end-to-end шифрование с Вашим контактом сравните (или сканируйте) код безопасности на Ваших устройствах. @@ -7139,6 +7365,10 @@ You will be prompted to complete authentication before this feature is enabled.< Разблокировать члена группы? No comment provided by engineer. + + Undelivered messages + No comment provided by engineer. + Unexpected migration state Неожиданная ошибка при перемещении данных чата @@ -7296,6 +7526,10 @@ To connect, please ask your contact to create another connection link and check Загрузка архива No comment provided by engineer. + + Use %@ + No comment provided by engineer. + Use .onion hosts Использовать .onion хосты @@ -7321,6 +7555,14 @@ To connect, please ask your contact to create another connection link and check Использовать активный профиль No comment provided by engineer. + + Use for files + No comment provided by engineer. + + + Use for messages + No comment provided by engineer. + Use for new connections Использовать для новых соединений @@ -7361,6 +7603,10 @@ To connect, please ask your contact to create another connection link and check Использовать сервер No comment provided by engineer. + + Use servers + No comment provided by engineer. + Use the app while in the call. Используйте приложение во время звонка. @@ -7451,11 +7697,19 @@ To connect, please ask your contact to create another connection link and check Видео и файлы до 1гб No comment provided by engineer. + + View conditions + No comment provided by engineer. + View security code Показать код безопасности No comment provided by engineer. + + View updated conditions + No comment provided by engineer. + Visible history Доступ к истории @@ -7566,9 +7820,8 @@ To connect, please ask your contact to create another connection link and check Во время соединения аудио и видео звонков. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - Когда Вы получите запрос на соединение, Вы можете принять или отклонить его. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -7728,6 +7981,18 @@ Repeat join request? Вы можете изменить это в настройках Интерфейса. No comment provided by engineer. + + You can configure operators in Network & servers settings. + No comment provided by engineer. + + + You can configure servers via settings. + No comment provided by engineer. + + + You can create it in user picker. + No comment provided by engineer. + You can create it later Вы можете создать его позже @@ -7768,6 +8033,10 @@ Repeat join request? Вы можете отправлять сообщения %@ из Архивированных контактов. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + No comment provided by engineer. + You can set lock screen notification preview via settings. Вы можете установить просмотр уведомлений на экране блокировки в настройках. @@ -7783,11 +8052,6 @@ Repeat join request? Вы можете поделиться этим адресом с Вашими контактами, чтобы они могли соединиться с **%@**. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - Вы можете использовать Ваш адрес как ссылку или как QR код - кто угодно сможет соединиться с Вами. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app Вы можете запустить чат через Настройки приложения или перезапустив приложение. @@ -7811,23 +8075,23 @@ Repeat join request? You can view invitation link again in connection details. Вы можете увидеть ссылку-приглашение снова открыв соединение. - No comment provided by engineer. + alert message You can't send messages! Вы не можете отправлять сообщения! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - Вы определяете через какие серверы Вы **получаете сообщения**, Ваши контакты - серверы, которые Вы используете для отправки. - No comment provided by engineer. - You could not be verified; please try again. Верификация не удалась; пожалуйста, попробуйте ещё раз. No comment provided by engineer. + + You decide who can connect. + Вы определяете, кто может соединиться. + No comment provided by engineer. + You have already requested connection via this address! Вы уже запросили соединение через этот адрес! @@ -7950,11 +8214,6 @@ Repeat connection request? Вы используете инкогнито профиль для этой группы - чтобы предотвратить раскрытие Вашего основного профиля, приглашать контакты не разрешено No comment provided by engineer. - - Your %@ servers - Ваши %@ серверы - No comment provided by engineer. - Your ICE servers Ваши ICE серверы @@ -7970,11 +8229,6 @@ Repeat connection request? Ваш адрес SimpleX No comment provided by engineer. - - Your XFTP servers - Ваши XFTP серверы - No comment provided by engineer. - Your calls Ваши звонки @@ -8075,16 +8329,15 @@ Repeat connection request? Случайный профиль No comment provided by engineer. - - Your server - Ваш сервер - No comment provided by engineer. - Your server address Адрес Вашего сервера No comment provided by engineer. + + Your servers + No comment provided by engineer. + Your settings Настройки @@ -8505,6 +8758,10 @@ Repeat connection request? истекло No comment provided by engineer. + + for better metadata privacy. + No comment provided by engineer. + forwarded переслано @@ -9127,6 +9384,33 @@ last received msg: %2$@ + +
+ +
+ + + %d new events + notification body + + + From: %@ + notification body + + + New events + notification + + + New messages + notification + + + New messages in %d chats + notification body + + +
diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/ru.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index 37ade821f0..e16565b6fa 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -12,21 +12,6 @@ No comment provided by engineer. - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - ( ( @@ -120,6 +105,14 @@ %@ ได้รับการตรวจสอบแล้ว No comment provided by engineer. + + %@ server + No comment provided by engineer. + + + %@ servers + No comment provided by engineer. + %@ uploaded No comment provided by engineer. @@ -328,26 +321,21 @@ ) No comment provided by engineer. - - **Add contact**: to create a new invitation link, or connect via a link you received. - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. - **เพิ่มผู้ติดต่อใหม่**: เพื่อสร้างคิวอาร์โค้ดแบบใช้ครั้งเดียวหรือลิงก์สำหรับผู้ติดต่อของคุณ + + **Create 1-time link**: to create and share a new invitation link. No comment provided by engineer. **Create group**: to create a new group. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **เป็นส่วนตัวมากขึ้น**: ตรวจสอบข้อความใหม่ทุกๆ 20 นาที โทเค็นอุปกรณ์แชร์กับเซิร์ฟเวอร์ SimpleX Chat แต่ไม่ระบุจำนวนผู้ติดต่อหรือข้อความที่คุณมี No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **ส่วนตัวที่สุด**: ไม่ใช้เซิร์ฟเวอร์การแจ้งเตือนของ SimpleX Chat ตรวจสอบข้อความเป็นระยะในพื้นหลัง (ขึ้นอยู่กับความถี่ที่คุณใช้แอป) No comment provided by engineer. @@ -360,11 +348,15 @@ **โปรดทราบ**: คุณจะไม่สามารถกู้คืนหรือเปลี่ยนรหัสผ่านได้หากคุณทำรหัสผ่านหาย No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **แนะนำ**: โทเค็นอุปกรณ์และการแจ้งเตือนจะถูกส่งไปยังเซิร์ฟเวอร์การแจ้งเตือนของ SimpleX Chat แต่ไม่ใช่เนื้อหาข้อความ ขนาด หรือผู้ที่ส่ง No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **คำเตือน**: การแจ้งเตือนแบบพุชทันทีจำเป็นต้องบันทึกรหัสผ่านไว้ใน Keychain @@ -463,6 +455,14 @@ 1 สัปดาห์ time interval + + 1-time link + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + No comment provided by engineer. + 5 minutes 5 นาที @@ -531,21 +531,11 @@ ยกเลิกการเปลี่ยนที่อยู่? No comment provided by engineer. - - About SimpleX - เกี่ยวกับ SimpleX - No comment provided by engineer. - About SimpleX Chat เกี่ยวกับ SimpleX Chat No comment provided by engineer. - - About SimpleX address - เกี่ยวกับที่อยู่ SimpleX - No comment provided by engineer. - Accent No comment provided by engineer. @@ -557,6 +547,10 @@ accept incoming call via notification swipe action + + Accept conditions + No comment provided by engineer. + Accept connection request? No comment provided by engineer. @@ -572,6 +566,10 @@ accept contact request via notification swipe action + + Accepted conditions + No comment provided by engineer. + Acknowledged No comment provided by engineer. @@ -589,15 +587,6 @@ เพิ่มที่อยู่ลงในโปรไฟล์ของคุณ เพื่อให้ผู้ติดต่อของคุณสามารถแชร์กับผู้อื่นได้ การอัปเดตโปรไฟล์จะถูกส่งไปยังผู้ติดต่อของคุณ No comment provided by engineer. - - Add contact - No comment provided by engineer. - - - Add preset servers - เพิ่มเซิร์ฟเวอร์ที่ตั้งไว้ล่วงหน้า - No comment provided by engineer. - Add profile เพิ่มโปรไฟล์ @@ -623,6 +612,14 @@ เพิ่มข้อความต้อนรับ No comment provided by engineer. + + Added media & file servers + No comment provided by engineer. + + + Added message servers + No comment provided by engineer. + Additional accent No comment provided by engineer. @@ -645,6 +642,14 @@ การเปลี่ยนแปลงที่อยู่จะถูกยกเลิก จะใช้ที่อยู่เก่าของผู้รับ No comment provided by engineer. + + Address or 1-time link? + No comment provided by engineer. + + + Address settings + No comment provided by engineer. + Admins can block a member for all. No comment provided by engineer. @@ -687,6 +692,10 @@ สมาชิกในกลุ่มทุกคนจะยังคงเชื่อมต่ออยู่. No comment provided by engineer. + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + No comment provided by engineer. + All messages will be deleted - this cannot be undone! No comment provided by engineer. @@ -856,6 +865,11 @@ รับสาย No comment provided by engineer. + + Anybody can host servers. + โปรโตคอลและโค้ดโอเพ่นซอร์ส – ใคร ๆ ก็สามารถเปิดใช้เซิร์ฟเวอร์ได้ + No comment provided by engineer. + App build: %@ รุ่นแอป: %@ @@ -1164,7 +1178,8 @@ Cancel ยกเลิก - alert button + alert action + alert button Cancel migration @@ -1243,6 +1258,10 @@ authentication reason set passcode view + + Change user profiles + authentication reason + Chat archive ที่เก็บแชทถาวร @@ -1321,10 +1340,18 @@ แชท No comment provided by engineer. + + Check messages every 20 min. + No comment provided by engineer. + + + Check messages when allowed. + No comment provided by engineer. + Check server address and try again. ตรวจสอบที่อยู่เซิร์ฟเวอร์แล้วลองอีกครั้ง - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1403,15 +1430,47 @@ Completed No comment provided by engineer. + + Conditions accepted on: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for following operator(s): **%@**. + No comment provided by engineer. + + + Conditions of use + No comment provided by engineer. + + + Conditions will be accepted for enabled operators after 30 days. + No comment provided by engineer. + + + Conditions will be accepted for operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + No comment provided by engineer. + Configure ICE servers กำหนดค่าเซิร์ฟเวอร์ ICE No comment provided by engineer. - - Configured %@ servers - No comment provided by engineer. - Confirm ยืนยัน @@ -1575,6 +1634,10 @@ This is your own one-time link! ส่งคําขอเชื่อมต่อแล้ว! No comment provided by engineer. + + Connection security + No comment provided by engineer. + Connection terminated No comment provided by engineer. @@ -1680,6 +1743,10 @@ This is your own one-time link! สร้าง No comment provided by engineer. + + Create 1-time link + No comment provided by engineer. + Create SimpleX address สร้างที่อยู่ SimpleX @@ -1689,11 +1756,6 @@ This is your own one-time link! Create a group using a random profile. No comment provided by engineer. - - Create an address to let people connect with you. - สร้างที่อยู่เพื่อให้ผู้อื่นเชื่อมต่อกับคุณ - No comment provided by engineer. - Create file สร้างไฟล์ @@ -1766,6 +1828,10 @@ This is your own one-time link! รหัสผ่านปัจจุบัน No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + No comment provided by engineer. + Current passphrase… รหัสผ่านปัจจุบัน… @@ -1917,7 +1983,8 @@ This is your own one-time link! Delete ลบ - chat item action + alert action + chat item action swipe action @@ -2125,6 +2192,10 @@ This is your own one-time link! Deletion errors No comment provided by engineer. + + Delivered even when Apple drops them. + No comment provided by engineer. + Delivery No comment provided by engineer. @@ -2380,6 +2451,10 @@ This is your own one-time link! ระยะเวลา No comment provided by engineer. + + E2E encrypted notifications. + No comment provided by engineer. + Edit แก้ไข @@ -2400,6 +2475,10 @@ This is your own one-time link! เปิดใช้งาน (เก็บการแทนที่) No comment provided by engineer. + + Enable Flux + No comment provided by engineer. + Enable SimpleX Lock เปิดใช้งาน SimpleX Lock @@ -2592,6 +2671,10 @@ This is your own one-time link! เกิดข้อผิดพลาดในการยกเลิกการเปลี่ยนที่อยู่ No comment provided by engineer. + + Error accepting conditions + alert title + Error accepting contact request เกิดข้อผิดพลาดในการรับคำขอติดต่อ @@ -2607,6 +2690,10 @@ This is your own one-time link! เกิดข้อผิดพลาดในการเพิ่มสมาชิก No comment provided by engineer. + + Error adding server + alert title + Error changing address เกิดข้อผิดพลาดในการเปลี่ยนที่อยู่ @@ -2739,10 +2826,9 @@ This is your own one-time link! เกิดข้อผิดพลาดในการเข้าร่วมกลุ่ม No comment provided by engineer. - - Error loading %@ servers - โหลดเซิร์ฟเวอร์ %@ ผิดพลาด - No comment provided by engineer. + + Error loading servers + alert title Error migrating settings @@ -2774,11 +2860,6 @@ This is your own one-time link! Error resetting statistics No comment provided by engineer. - - Error saving %@ servers - เกิดข้อผิดพลาดในการบันทึกเซิร์ฟเวอร์ %@ - No comment provided by engineer. - Error saving ICE servers เกิดข้อผิดพลาดในการบันทึกเซิร์ฟเวอร์ ICE @@ -2799,6 +2880,10 @@ This is your own one-time link! เกิดข้อผิดพลาดในการบันทึกรหัสผ่านไปยัง keychain No comment provided by engineer. + + Error saving servers + alert title + Error saving settings when migrating @@ -2865,6 +2950,10 @@ This is your own one-time link! เกิดข้อผิดพลาดในการอัปเดตข้อความ No comment provided by engineer. + + Error updating server + alert title + Error updating settings เกิดข้อผิดพลาดในการอัปเดตการตั้งค่า @@ -2907,6 +2996,10 @@ This is your own one-time link! Errors No comment provided by engineer. + + Errors in servers configuration. + servers error + Even when disabled in the conversation. แม้ในขณะที่ปิดใช้งานในการสนทนา @@ -3094,11 +3187,27 @@ This is your own one-time link! การแก้ไขไม่สนับสนุนโดยสมาชิกกลุ่ม No comment provided by engineer. + + For chat profile %@: + servers error + For console สำหรับคอนโซล No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + No comment provided by engineer. + + + For private routing + No comment provided by engineer. + + + For social media + No comment provided by engineer. + Forward chat item action @@ -3384,9 +3493,12 @@ Error: %2$@ วิธีการ SimpleX ทํางานอย่างไร No comment provided by engineer. - - How it works - มันทำงานอย่างไร + + How it affects privacy + No comment provided by engineer. + + + How it helps privacy No comment provided by engineer. @@ -3457,8 +3569,8 @@ Error: %2$@ โดยทันที No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam มีภูมิคุ้มกันต่อสแปมและการละเมิด No comment provided by engineer. @@ -3588,6 +3700,11 @@ More improvements are coming soon! ติดตั้ง [SimpleX Chat สำหรับเทอร์มินัล](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + ทันที + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3595,11 +3712,6 @@ More improvements are coming soon! No comment provided by engineer. - - Instantly - ทันที - No comment provided by engineer. - Interface อินเตอร์เฟซ @@ -3641,7 +3753,7 @@ More improvements are coming soon! Invalid server address! ที่อยู่เซิร์ฟเวอร์ไม่ถูกต้อง! - No comment provided by engineer. + alert title Invalid status @@ -3761,7 +3873,7 @@ This is your link for group %@! Keep - No comment provided by engineer. + alert action Keep conversation @@ -3773,7 +3885,7 @@ This is your link for group %@! Keep unused invitation? - No comment provided by engineer. + alert title Keep your connections @@ -3857,11 +3969,6 @@ This is your link for group %@! ข้อความสด No comment provided by engineer. - - Local - ในเครื่อง - No comment provided by engineer. - Local name ชื่อภายในเครื่องเท่านั้น @@ -3882,11 +3989,6 @@ This is your link for group %@! โหมดล็อค No comment provided by engineer. - - Make a private connection - สร้างการเชื่อมต่อแบบส่วนตัว - No comment provided by engineer. - Make one message disappear ทำให้ข้อความหายไปหนึ่งข้อความ @@ -3897,21 +3999,11 @@ This is your link for group %@! ทำให้โปรไฟล์เป็นส่วนตัว! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - ตรวจสอบให้แน่ใจว่าที่อยู่เซิร์ฟเวอร์ %@ อยู่ในรูปแบบที่ถูกต้อง แยกบรรทัดและไม่ซ้ำกัน (%@) - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. ตรวจสอบให้แน่ใจว่าที่อยู่เซิร์ฟเวอร์ WebRTC ICE อยู่ในรูปแบบที่ถูกต้อง แยกบรรทัดและไม่ซ้ำกัน No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - หลายคนถามว่า: *หาก SimpleX ไม่มีตัวระบุผู้ใช้ จะส่งข้อความได้อย่างไร?* - No comment provided by engineer. - Mark deleted for everyone ทำเครื่องหมายว่าลบแล้วสำหรับทุกคน @@ -4163,6 +4255,10 @@ This is your link for group %@! More reliable network connection. No comment provided by engineer. + + More reliable notifications + No comment provided by engineer. + Most likely this connection is deleted. item status description @@ -4196,6 +4292,10 @@ This is your link for group %@! Network connection No comment provided by engineer. + + Network decentralization + No comment provided by engineer. + Network issues - message expired after many attempts to send it. snd error text @@ -4204,6 +4304,10 @@ This is your link for group %@! Network management No comment provided by engineer. + + Network operator + No comment provided by engineer. + Network settings การตั้งค่าเครือข่าย @@ -4259,6 +4363,10 @@ This is your link for group %@! ชื่อที่แสดงใหม่ No comment provided by engineer. + + New events + notification + New in %@ ใหม่ใน %@ @@ -4283,6 +4391,10 @@ This is your link for group %@! รหัสผ่านใหม่… No comment provided by engineer. + + New server + No comment provided by engineer. + No เลขที่ @@ -4335,6 +4447,14 @@ This is your link for group %@! No info, try to reload No comment provided by engineer. + + No media & file servers. + servers error + + + No message servers. + servers error + No network connection No comment provided by engineer. @@ -4352,11 +4472,37 @@ This is your link for group %@! ไม่อนุญาตให้บันทึกข้อความเสียง No comment provided by engineer. + + No push server + ในเครื่อง + No comment provided by engineer. + No received or sent files ไม่มีไฟล์ที่ได้รับหรือส่ง No comment provided by engineer. + + No servers for private message routing. + servers error + + + No servers to receive files. + servers error + + + No servers to receive messages. + servers error + + + No servers to send files. + servers error + + + No user identifiers. + แพลตฟอร์มแรกที่ไม่มีตัวระบุผู้ใช้ - ถูกออกแบบให้เป็นส่วนตัว + No comment provided by engineer. + Not compatible! No comment provided by engineer. @@ -4379,6 +4525,10 @@ This is your link for group %@! ปิดการแจ้งเตือน! No comment provided by engineer. + + Notifications privacy + No comment provided by engineer. + Now admins can: - delete members' messages. @@ -4434,8 +4584,8 @@ Requires compatible VPN. โฮสต์หัวหอมจะไม่ถูกใช้ No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. เฉพาะอุปกรณ์ไคลเอนต์เท่านั้นที่จัดเก็บโปรไฟล์ผู้ใช้ ผู้ติดต่อ กลุ่ม และข้อความที่ส่งด้วย **การเข้ารหัส encrypt แบบ 2 ชั้น** No comment provided by engineer. @@ -4517,6 +4667,10 @@ Requires compatible VPN. เปิดการตั้งค่า No comment provided by engineer. + + Open changes + No comment provided by engineer. + Open chat เปิดแชท @@ -4527,6 +4681,10 @@ Requires compatible VPN. เปิดคอนโซลการแชท authentication reason + + Open conditions + No comment provided by engineer. + Open group No comment provided by engineer. @@ -4535,24 +4693,18 @@ Requires compatible VPN. Open migration to another device authentication reason - - Open server settings - No comment provided by engineer. - - - Open user profiles - เปิดโปรไฟล์ผู้ใช้ - authentication reason - - - Open-source protocol and code – anybody can run the servers. - โปรโตคอลและโค้ดโอเพ่นซอร์ส – ใคร ๆ ก็สามารถเปิดใช้เซิร์ฟเวอร์ได้ - No comment provided by engineer. - Opening app… No comment provided by engineer. + + Operator + No comment provided by engineer. + + + Operator server + alert title + Or paste archive link No comment provided by engineer. @@ -4569,12 +4721,12 @@ Requires compatible VPN. Or show this code No comment provided by engineer. - - Other + + Or to share privately No comment provided by engineer. - - Other %@ servers + + Other No comment provided by engineer. @@ -4651,13 +4803,8 @@ Requires compatible VPN. Pending No comment provided by engineer. - - People can connect to you only via the links you share. - ผู้คนสามารถเชื่อมต่อกับคุณผ่านลิงก์ที่คุณแบ่งปันเท่านั้น - No comment provided by engineer. - - - Periodically + + Periodic เป็นระยะๆ No comment provided by engineer. @@ -4771,16 +4918,15 @@ Error: %@ เก็บข้อความที่ร่างไว้ล่าสุดพร้อมไฟล์แนบ No comment provided by engineer. - - Preset server - เซิร์ฟเวอร์ที่ตั้งไว้ล่วงหน้า - No comment provided by engineer. - Preset server address ที่อยู่เซิร์ฟเวอร์ที่ตั้งไว้ล่วงหน้า No comment provided by engineer. + + Preset servers + No comment provided by engineer. + Preview ดูตัวอย่าง @@ -4851,7 +4997,7 @@ Error: %@ Profile update will be sent to your contacts. การอัปเดตโปรไฟล์จะถูกส่งไปยังผู้ติดต่อของคุณ - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -4938,6 +5084,10 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. + + Push Notifications + No comment provided by engineer. + Push notifications การแจ้งเตือนแบบทันที @@ -4975,25 +5125,20 @@ Enable in *Network & servers* settings. อ่านเพิ่มเติม No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - อ่านเพิ่มเติมใน[คู่มือผู้ใช้](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address) - No comment provided by engineer. - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + อ่านเพิ่มเติมใน[คู่มือผู้ใช้](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses) + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). อ่านเพิ่มเติมใน[คู่มือผู้ใช้](https://simplex.chat/docs/guide/readme.html#connect-to-friends) No comment provided by engineer. - - Read more in our GitHub repository. - อ่านเพิ่มเติมในที่เก็บ GitHub ของเรา - No comment provided by engineer. - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). อ่านเพิ่มเติมใน[พื้นที่เก็บข้อมูล GitHub](https://github.com/simplex-chat/simplex-chat#readme) @@ -5284,6 +5429,14 @@ Enable in *Network & servers* settings. เปิดเผย chat item action + + Review conditions + No comment provided by engineer. + + + Review later + No comment provided by engineer. + Revoke ถอน @@ -5325,6 +5478,14 @@ Enable in *Network & servers* settings. Safer groups No comment provided by engineer. + + Same conditions will apply to operator **%@**. + No comment provided by engineer. + + + Same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + Save บันทึก @@ -5393,7 +5554,7 @@ Enable in *Network & servers* settings. Save servers? บันทึกเซิร์ฟเวอร์? - No comment provided by engineer. + alert title Save welcome message? @@ -5585,11 +5746,6 @@ Enable in *Network & servers* settings. ส่งการแจ้งเตือน No comment provided by engineer. - - Send notifications: - ส่งการแจ้งเตือน: - No comment provided by engineer. - Send questions and ideas ส่งคําถามและความคิด @@ -5706,6 +5862,10 @@ Enable in *Network & servers* settings. Server No comment provided by engineer. + + Server added to operator %@. + alert message + Server address No comment provided by engineer. @@ -5718,6 +5878,18 @@ Enable in *Network & servers* settings. Server address is incompatible with network settings: %@. No comment provided by engineer. + + Server operator changed. + alert title + + + Server operators + No comment provided by engineer. + + + Server protocol changed. + alert title + Server requires authorization to create queues, check password เซิร์ฟเวอร์ต้องการการอนุญาตในการสร้างคิว โปรดตรวจสอบรหัสผ่าน @@ -5826,22 +5998,35 @@ Enable in *Network & servers* settings. Share แชร์ - chat item action + alert action + chat item action Share 1-time link แชร์ลิงก์แบบใช้ครั้งเดียว No comment provided by engineer. + + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + No comment provided by engineer. + Share address แชร์ที่อยู่ No comment provided by engineer. + + Share address publicly + No comment provided by engineer. + Share address with contacts? แชร์ที่อยู่กับผู้ติดต่อ? - No comment provided by engineer. + alert title Share from other apps. @@ -5948,6 +6133,14 @@ Enable in *Network & servers* settings. ที่อยู่ SimpleX No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + No comment provided by engineer. + SimpleX contact address ที่อยู่ติดต่อ SimpleX @@ -6028,6 +6221,11 @@ Enable in *Network & servers* settings. Some non-fatal errors occurred during import: No comment provided by engineer. + + Some servers failed the test: +%@ + alert message + Somebody ใครบางคน @@ -6106,12 +6304,12 @@ Enable in *Network & servers* settings. Stop sharing หยุดแชร์ - No comment provided by engineer. + alert action Stop sharing address? หยุดแชร์ที่อยู่ไหม? - No comment provided by engineer. + alert title Stopping chat @@ -6248,7 +6446,7 @@ Enable in *Network & servers* settings. Tests failed! การทดสอบล้มเหลว! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6265,11 +6463,6 @@ Enable in *Network & servers* settings. ขอบคุณผู้ใช้ – มีส่วนร่วมผ่าน Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - แพลตฟอร์มแรกที่ไม่มีตัวระบุผู้ใช้ - ถูกออกแบบให้เป็นส่วนตัว - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6283,6 +6476,10 @@ It can happen because of some bug or when the connection is compromised.แอปสามารถแจ้งให้คุณทราบเมื่อคุณได้รับข้อความหรือคำขอติดต่อ - โปรดเปิดการตั้งค่าเพื่อเปิดใช้งาน No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). No comment provided by engineer. @@ -6296,6 +6493,10 @@ It can happen because of some bug or when the connection is compromised.The code you scanned is not a SimpleX link QR code. No comment provided by engineer. + + The connection reached the limit of undelivered messages, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! การเชื่อมต่อที่คุณยอมรับจะถูกยกเลิก! @@ -6316,6 +6517,11 @@ It can happen because of some bug or when the connection is compromised.encryption กำลังทำงานและไม่จำเป็นต้องใช้ข้อตกลง encryption ใหม่ อาจทำให้การเชื่อมต่อผิดพลาดได้! No comment provided by engineer. + + The future of messaging + การส่งข้อความส่วนตัวรุ่นต่อไป + No comment provided by engineer. + The hash of the previous message is different. แฮชของข้อความก่อนหน้านี้แตกต่างกัน @@ -6339,11 +6545,6 @@ It can happen because of some bug or when the connection is compromised.The messages will be marked as moderated for all members. No comment provided by engineer. - - The next generation of private messaging - การส่งข้อความส่วนตัวรุ่นต่อไป - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. ฐานข้อมูลเก่าไม่ได้ถูกลบในระหว่างการย้ายข้อมูล แต่สามารถลบได้ @@ -6354,6 +6555,10 @@ It can happen because of some bug or when the connection is compromised.โปรไฟล์นี้แชร์กับผู้ติดต่อของคุณเท่านั้น No comment provided by engineer. + + The second preset operator in the app! + No comment provided by engineer. + The second tick we missed! ✅ ขีดที่สองที่เราพลาด! ✅ @@ -6369,6 +6574,10 @@ It can happen because of some bug or when the connection is compromised.เซิร์ฟเวอร์สำหรับการเชื่อมต่อใหม่ของโปรไฟล์การแชทปัจจุบันของคุณ **%@** No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. No comment provided by engineer. @@ -6381,6 +6590,10 @@ It can happen because of some bug or when the connection is compromised.Themes No comment provided by engineer. + + These conditions will also apply for: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. การตั้งค่าเหล่านี้ใช้สำหรับโปรไฟล์ปัจจุบันของคุณ **%@** @@ -6470,9 +6683,8 @@ It can happen because of some bug or when the connection is compromised.เพื่อสร้างการเชื่อมต่อใหม่ No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - เพื่อปกป้องความเป็นส่วนตัว แทนที่จะใช้ ID ผู้ใช้เหมือนที่แพลตฟอร์มอื่นๆใช้ SimpleX มีตัวระบุสำหรับคิวข้อความ โดยแยกจากกันสำหรับผู้ติดต่อแต่ละราย + + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -6491,6 +6703,15 @@ You will be prompted to complete authentication before this feature is enabled.< คุณจะได้รับแจ้งให้ยืนยันตัวตนให้เสร็จสมบูรณ์ก่อนที่จะเปิดใช้งานคุณลักษณะนี้ No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + เพื่อปกป้องความเป็นส่วนตัว แทนที่จะใช้ ID ผู้ใช้เหมือนที่แพลตฟอร์มอื่นๆใช้ SimpleX มีตัวระบุสำหรับคิวข้อความ โดยแยกจากกันสำหรับผู้ติดต่อแต่ละราย + No comment provided by engineer. + + + To receive + No comment provided by engineer. + To record speech please grant permission to use Microphone. No comment provided by engineer. @@ -6509,11 +6730,19 @@ You will be prompted to complete authentication before this feature is enabled.< หากต้องการเปิดเผยโปรไฟล์ที่ซ่อนอยู่ของคุณ ให้ป้อนรหัสผ่านแบบเต็มในช่องค้นหาในหน้า **โปรไฟล์แชทของคุณ** No comment provided by engineer. + + To send + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. เพื่อรองรับการแจ้งเตือนแบบทันที ฐานข้อมูลการแชทจะต้องได้รับการโยกย้าย No comment provided by engineer. + + To use the servers of **%@**, accept conditions of use. + No comment provided by engineer. + To verify end-to-end encryption with your contact compare (or scan) the code on your devices. ในการตรวจสอบการเข้ารหัสแบบ encrypt จากต้นจนจบ กับผู้ติดต่อของคุณ ให้เปรียบเทียบ (หรือสแกน) รหัสบนอุปกรณ์ของคุณ @@ -6593,6 +6822,10 @@ You will be prompted to complete authentication before this feature is enabled.< Unblock member? No comment provided by engineer. + + Undelivered messages + No comment provided by engineer. + Unexpected migration state สถานะการย้ายข้อมูลที่ไม่คาดคิด @@ -6740,6 +6973,10 @@ To connect, please ask your contact to create another connection link and check Uploading archive No comment provided by engineer. + + Use %@ + No comment provided by engineer. + Use .onion hosts ใช้โฮสต์ .onion @@ -6763,6 +7000,14 @@ To connect, please ask your contact to create another connection link and check Use current profile No comment provided by engineer. + + Use for files + No comment provided by engineer. + + + Use for messages + No comment provided by engineer. + Use for new connections ใช้สำหรับการเชื่อมต่อใหม่ @@ -6798,6 +7043,10 @@ To connect, please ask your contact to create another connection link and check ใช้เซิร์ฟเวอร์ No comment provided by engineer. + + Use servers + No comment provided by engineer. + Use the app while in the call. No comment provided by engineer. @@ -6878,11 +7127,19 @@ To connect, please ask your contact to create another connection link and check วิดีโอและไฟล์สูงสุด 1gb No comment provided by engineer. + + View conditions + No comment provided by engineer. + View security code ดูรหัสความปลอดภัย No comment provided by engineer. + + View updated conditions + No comment provided by engineer. + Visible history chat feature @@ -6985,9 +7242,8 @@ To connect, please ask your contact to create another connection link and check When connecting audio and video calls. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - เมื่อมีคนขอเชื่อมต่อ คุณสามารถยอมรับหรือปฏิเสธได้ + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -7126,6 +7382,18 @@ Repeat join request? You can change it in Appearance settings. No comment provided by engineer. + + You can configure operators in Network & servers settings. + No comment provided by engineer. + + + You can configure servers via settings. + No comment provided by engineer. + + + You can create it in user picker. + No comment provided by engineer. + You can create it later คุณสามารถสร้างได้ในภายหลัง @@ -7163,6 +7431,10 @@ Repeat join request? You can send messages to %@ from Archived contacts. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + No comment provided by engineer. + You can set lock screen notification preview via settings. คุณสามารถตั้งค่าแสดงตัวอย่างการแจ้งเตือนบนหน้าจอล็อคผ่านการตั้งค่า @@ -7178,11 +7450,6 @@ Repeat join request? คุณสามารถแบ่งปันที่อยู่นี้กับผู้ติดต่อของคุณเพื่อให้พวกเขาเชื่อมต่อกับ **%@** No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - คุณสามารถแชร์ที่อยู่ของคุณเป็นลิงก์หรือรหัสคิวอาร์ - ใคร ๆ ก็สามารถเชื่อมต่อกับคุณได้ - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app คุณสามารถเริ่มแชทผ่านการตั้งค่าแอป / ฐานข้อมูล หรือโดยการรีสตาร์ทแอป @@ -7204,23 +7471,23 @@ Repeat join request? You can view invitation link again in connection details. - No comment provided by engineer. + alert message You can't send messages! คุณไม่สามารถส่งข้อความได้! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - คุณควบคุมผ่านเซิร์ฟเวอร์ **เพื่อรับ** ข้อความผู้ติดต่อของคุณ - เซิร์ฟเวอร์ที่คุณใช้เพื่อส่งข้อความถึงพวกเขา - No comment provided by engineer. - You could not be verified; please try again. เราไม่สามารถตรวจสอบคุณได้ กรุณาลองอีกครั้ง. No comment provided by engineer. + + You decide who can connect. + ผู้คนสามารถเชื่อมต่อกับคุณผ่านลิงก์ที่คุณแบ่งปันเท่านั้น + No comment provided by engineer. + You have already requested connection via this address! No comment provided by engineer. @@ -7334,11 +7601,6 @@ Repeat connection request? คุณกำลังใช้โปรไฟล์ที่ไม่ระบุตัวตนสำหรับกลุ่มนี้ - ไม่อนุญาตให้เชิญผู้ติดต่อเพื่อป้องกันการแชร์โปรไฟล์หลักของคุณ No comment provided by engineer. - - Your %@ servers - เซิร์ฟเวอร์ %@ ของคุณ - No comment provided by engineer. - Your ICE servers เซิร์ฟเวอร์ ICE ของคุณ @@ -7354,11 +7616,6 @@ Repeat connection request? ที่อยู่ SimpleX ของคุณ No comment provided by engineer. - - Your XFTP servers - เซิร์ฟเวอร์ XFTP ของคุณ - No comment provided by engineer. - Your calls การโทรของคุณ @@ -7453,16 +7710,15 @@ Repeat connection request? โปรไฟล์แบบสุ่มของคุณ No comment provided by engineer. - - Your server - เซิร์ฟเวอร์ของคุณ - No comment provided by engineer. - Your server address ที่อยู่เซิร์ฟเวอร์ของคุณ No comment provided by engineer. + + Your servers + No comment provided by engineer. + Your settings การตั้งค่าของคุณ @@ -7866,6 +8122,10 @@ Repeat connection request? expired No comment provided by engineer. + + for better metadata privacy. + No comment provided by engineer. + forwarded No comment provided by engineer. @@ -8453,6 +8713,33 @@ last received msg: %2$@ + +
+ +
+ + + %d new events + notification body + + + From: %@ + notification body + + + New events + notification + + + New messages + notification + + + New messages in %d chats + notification body + + +
diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/th.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/th.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index b911eb1220..0b2149e9ce 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -12,21 +12,6 @@ No comment provided by engineer. - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - ( ( @@ -127,6 +112,14 @@ %@ onaylandı No comment provided by engineer. + + %@ server + No comment provided by engineer. + + + %@ servers + No comment provided by engineer. + %@ uploaded %@ yüklendi @@ -352,28 +345,23 @@ ) No comment provided by engineer. - - **Add contact**: to create a new invitation link, or connect via a link you received. + + **Create 1-time link**: to create and share a new invitation link. **Kişi ekle**: yeni bir davet bağlantısı oluşturmak için, ya da aldığın bağlantıyla bağlan. No comment provided by engineer. - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Yeni kişi ekleyin**: tek seferlik QR Kodunuzu oluşturmak veya kişisel ulaşım bilgileri bağlantısı için. - No comment provided by engineer. - **Create group**: to create a new group. **Grup oluştur**: yeni bir grup oluşturmak için. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **Daha gizli**: her 20 dakikada yeni mesajlar için kontrol et. Cihaz jetonu SimpleX Chat sunucusuyla paylaşılacak, ama ne kadar kişi veya mesaja sahip olduğun paylaşılmayacak. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **En gizli**: SimpleX Chat bildirim sunucusunu kullanma, arkaplanda mesajları periyodik olarak kontrol edin (uygulamayı ne sıklıkta kullandığınıza bağlıdır). No comment provided by engineer. @@ -387,11 +375,15 @@ **Lütfen aklınızda bulunsun**: eğer parolanızı kaybederseniz parolanızı değiştirme veya geri kurtarma ihtimaliniz YOKTUR. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **Önerilen**: cihaz tokeni ve bildirimler SimpleX Chat bildirim sunucularına gönderilir, ama mesajın içeriği, boyutu veya kimden geldiği gönderilmez. No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **Dikkat**: Anında iletilen bildirimlere Anahtar Zinciri'nde kaydedilmiş parola gereklidir. @@ -498,6 +490,14 @@ 1 hafta time interval + + 1-time link + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + No comment provided by engineer. + 5 minutes 5 dakika @@ -567,21 +567,11 @@ Adres değişimi iptal edilsin mi? No comment provided by engineer. - - About SimpleX - SimpleX Hakkında - No comment provided by engineer. - About SimpleX Chat SimpleX Chat hakkında No comment provided by engineer. - - About SimpleX address - SimpleX Chat adresi hakkında - No comment provided by engineer. - Accent Ana renk @@ -594,6 +584,10 @@ accept incoming call via notification swipe action + + Accept conditions + No comment provided by engineer. + Accept connection request? Bağlantı isteği kabul edilsin mi? @@ -610,6 +604,10 @@ accept contact request via notification swipe action + + Accepted conditions + No comment provided by engineer. + Acknowledged Onaylandı @@ -630,16 +628,6 @@ Kişilerinizin başkalarıyla paylaşabilmesi için profilinize adres ekleyin. Profil güncellemesi kişilerinize gönderilecek. No comment provided by engineer. - - Add contact - Kişi ekle - No comment provided by engineer. - - - Add preset servers - Önceden ayarlanmış sunucu ekle - No comment provided by engineer. - Add profile Profil ekle @@ -665,6 +653,14 @@ Karşılama mesajı ekleyin No comment provided by engineer. + + Added media & file servers + No comment provided by engineer. + + + Added message servers + No comment provided by engineer. + Additional accent Ek ana renk @@ -690,6 +686,14 @@ Adres değişikliği iptal edilecek. Eski alıcı adresi kullanılacaktır. No comment provided by engineer. + + Address or 1-time link? + No comment provided by engineer. + + + Address settings + No comment provided by engineer. + Admins can block a member for all. Yöneticiler bir üyeyi tamamen engelleyebilirler. @@ -735,6 +739,10 @@ Tüm grup üyeleri bağlı kalacaktır. No comment provided by engineer. + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + No comment provided by engineer. + All messages will be deleted - this cannot be undone! Tüm mesajlar silinecektir - bu geri alınamaz! @@ -915,6 +923,11 @@ Aramayı cevapla No comment provided by engineer. + + Anybody can host servers. + Açık kaynak protokolü ve kodu - herhangi biri sunucuları çalıştırabilir. + No comment provided by engineer. + App build: %@ Uygulama sürümü: %@ @@ -1258,7 +1271,8 @@ Cancel İptal et - alert button + alert action + alert button Cancel migration @@ -1341,6 +1355,10 @@ authentication reason set passcode view + + Change user profiles + authentication reason + Chat archive Sohbet arşivi @@ -1426,10 +1444,18 @@ Sohbetler No comment provided by engineer. + + Check messages every 20 min. + No comment provided by engineer. + + + Check messages when allowed. + No comment provided by engineer. + Check server address and try again. Sunucu adresini kontrol edip tekrar deneyin. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1516,16 +1542,47 @@ Tamamlandı No comment provided by engineer. + + Conditions accepted on: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for following operator(s): **%@**. + No comment provided by engineer. + + + Conditions of use + No comment provided by engineer. + + + Conditions will be accepted for enabled operators after 30 days. + No comment provided by engineer. + + + Conditions will be accepted for operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + No comment provided by engineer. + Configure ICE servers ICE sunucularını ayarla No comment provided by engineer. - - Configured %@ servers - Yapılandırılmış %@ sunucuları - No comment provided by engineer. - Confirm Onayla @@ -1715,6 +1772,10 @@ Bu senin kendi tek kullanımlık bağlantın! Bağlantı daveti gönderildi! No comment provided by engineer. + + Connection security + No comment provided by engineer. + Connection terminated Bağlantı sonlandırılmış @@ -1830,6 +1891,10 @@ Bu senin kendi tek kullanımlık bağlantın! Oluştur No comment provided by engineer. + + Create 1-time link + No comment provided by engineer. + Create SimpleX address SimpleX adresi oluştur @@ -1840,11 +1905,6 @@ Bu senin kendi tek kullanımlık bağlantın! Rasgele profil kullanarak grup oluştur. No comment provided by engineer. - - Create an address to let people connect with you. - İnsanların seninle bağlanması için bir adres oluştur. - No comment provided by engineer. - Create file Dosya oluştur @@ -1925,6 +1985,10 @@ Bu senin kendi tek kullanımlık bağlantın! Şu anki şifre No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + No comment provided by engineer. + Current passphrase… Şu anki parola… @@ -2081,7 +2145,8 @@ Bu senin kendi tek kullanımlık bağlantın! Delete Sil - chat item action + alert action + chat item action swipe action @@ -2299,6 +2364,10 @@ Bu senin kendi tek kullanımlık bağlantın! Silme hatası No comment provided by engineer. + + Delivered even when Apple drops them. + No comment provided by engineer. + Delivery Teslimat @@ -2580,6 +2649,10 @@ Bu senin kendi tek kullanımlık bağlantın! Süre No comment provided by engineer. + + E2E encrypted notifications. + No comment provided by engineer. + Edit Düzenle @@ -2600,6 +2673,10 @@ Bu senin kendi tek kullanımlık bağlantın! Etkinleştir (geçersiz kılmaları koru) No comment provided by engineer. + + Enable Flux + No comment provided by engineer. + Enable SimpleX Lock SimpleX Kilidini etkinleştir @@ -2805,6 +2882,10 @@ Bu senin kendi tek kullanımlık bağlantın! Adres değişikliği iptal edilirken hata oluştu No comment provided by engineer. + + Error accepting conditions + alert title + Error accepting contact request Bağlantı isteği kabul edilirken hata oluştu @@ -2820,6 +2901,10 @@ Bu senin kendi tek kullanımlık bağlantın! Üye(ler) eklenirken hata oluştu No comment provided by engineer. + + Error adding server + alert title + Error changing address Adres değiştirilirken hata oluştu @@ -2960,10 +3045,9 @@ Bu senin kendi tek kullanımlık bağlantın! Gruba katılırken hata oluştu No comment provided by engineer. - - Error loading %@ servers - %@ sunucuları yüklenirken hata oluştu - No comment provided by engineer. + + Error loading servers + alert title Error migrating settings @@ -3000,11 +3084,6 @@ Bu senin kendi tek kullanımlık bağlantın! Hata istatistikler sıfırlanıyor No comment provided by engineer. - - Error saving %@ servers - %@ sunucuları kaydedilirken sorun oluştu - No comment provided by engineer. - Error saving ICE servers ICE sunucularını kaydedirken sorun oluştu @@ -3025,6 +3104,10 @@ Bu senin kendi tek kullanımlık bağlantın! Parolayı Anahtar Zincirine kaydederken hata oluştu No comment provided by engineer. + + Error saving servers + alert title + Error saving settings Ayarlar kaydedilirken hata oluştu @@ -3095,6 +3178,10 @@ Bu senin kendi tek kullanımlık bağlantın! Mesaj güncellenirken hata oluştu No comment provided by engineer. + + Error updating server + alert title + Error updating settings Ayarları güncellerken hata oluştu @@ -3140,6 +3227,10 @@ Bu senin kendi tek kullanımlık bağlantın! Hatalar No comment provided by engineer. + + Errors in servers configuration. + servers error + Even when disabled in the conversation. Konuşma sırasında devre dışı bırakılsa bile. @@ -3342,11 +3433,27 @@ Bu senin kendi tek kullanımlık bağlantın! Düzeltme grup üyesi tarafından desteklenmiyor No comment provided by engineer. + + For chat profile %@: + servers error + For console Konsol için No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + No comment provided by engineer. + + + For private routing + No comment provided by engineer. + + + For social media + No comment provided by engineer. + Forward İlet @@ -3656,9 +3763,12 @@ Hata: %2$@ SimpleX nasıl çalışır No comment provided by engineer. - - How it works - Nasıl çalışıyor + + How it affects privacy + No comment provided by engineer. + + + How it helps privacy No comment provided by engineer. @@ -3731,8 +3841,8 @@ Hata: %2$@ Hemen No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Spam ve kötüye kullanıma karşı bağışıklı No comment provided by engineer. @@ -3873,6 +3983,11 @@ Daha fazla iyileştirme yakında geliyor! [Terminal için SimpleX Chat]i indir(https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Anında + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3880,11 +3995,6 @@ Daha fazla iyileştirme yakında geliyor! No comment provided by engineer. - - Instantly - Anında - No comment provided by engineer. - Interface Arayüz @@ -3933,7 +4043,7 @@ Daha fazla iyileştirme yakında geliyor! Invalid server address! Geçersiz sunucu adresi! - No comment provided by engineer. + alert title Invalid status @@ -4061,7 +4171,7 @@ Bu senin grup için bağlantın %@! Keep Tut - No comment provided by engineer. + alert action Keep conversation @@ -4076,7 +4186,7 @@ Bu senin grup için bağlantın %@! Keep unused invitation? Kullanılmamış davet tutulsun mu? - No comment provided by engineer. + alert title Keep your connections @@ -4163,11 +4273,6 @@ Bu senin grup için bağlantın %@! Canlı mesajlar No comment provided by engineer. - - Local - Yerel - No comment provided by engineer. - Local name Yerel isim @@ -4188,11 +4293,6 @@ Bu senin grup için bağlantın %@! Kilit modu No comment provided by engineer. - - Make a private connection - Gizli bir bağlantı oluştur - No comment provided by engineer. - Make one message disappear Bir mesajın kaybolmasını sağlayın @@ -4203,21 +4303,11 @@ Bu senin grup için bağlantın %@! Profili gizli yap! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - %@ sunucu adreslerinin doğru formatta olduğundan, satır ayrımı yapıldığından ve yinelenmediğinden (%@) emin olun. - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. WebRTC ICE sunucu adreslerinin doğru formatta olduğundan, satırlara ayrıldığından ve yinelenmediğinden emin olun. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Çoğu kişi sordu: *eğer SimpleX'in hiç kullanıcı tanımlayıcıları yok, o zaman mesajları nasıl gönderebiliyor?* - No comment provided by engineer. - Mark deleted for everyone Herkes için silinmiş olarak işaretle @@ -4498,6 +4588,10 @@ Bu senin grup için bağlantın %@! Daha güvenilir ağ bağlantısı. No comment provided by engineer. + + More reliable notifications + No comment provided by engineer. + Most likely this connection is deleted. Büyük ihtimalle bu bağlantı silinmiş. @@ -4533,6 +4627,10 @@ Bu senin grup için bağlantın %@! Ağ bağlantısı No comment provided by engineer. + + Network decentralization + No comment provided by engineer. + Network issues - message expired after many attempts to send it. Ağ sorunları - birçok gönderme denemesinden sonra mesajın süresi doldu. @@ -4543,6 +4641,10 @@ Bu senin grup için bağlantın %@! Ağ yönetimi No comment provided by engineer. + + Network operator + No comment provided by engineer. + Network settings Ağ ayarları @@ -4603,6 +4705,10 @@ Bu senin grup için bağlantın %@! Yeni görünen ad No comment provided by engineer. + + New events + notification + New in %@ %@ da yeni @@ -4628,6 +4734,10 @@ Bu senin grup için bağlantın %@! Yeni parola… No comment provided by engineer. + + New server + No comment provided by engineer. + No Hayır @@ -4683,6 +4793,14 @@ Bu senin grup için bağlantın %@! Bilgi yok, yenilemeyi deneyin No comment provided by engineer. + + No media & file servers. + servers error + + + No message servers. + servers error + No network connection Ağ bağlantısı yok @@ -4703,11 +4821,37 @@ Bu senin grup için bağlantın %@! Sesli mesaj kaydetmek için izin yok No comment provided by engineer. + + No push server + Yerel + No comment provided by engineer. + No received or sent files Hiç alınmış veya gönderilmiş dosya yok No comment provided by engineer. + + No servers for private message routing. + servers error + + + No servers to receive files. + servers error + + + No servers to receive messages. + servers error + + + No servers to send files. + servers error + + + No user identifiers. + Herhangi bir kullanıcı tanımlayıcısı yok. + No comment provided by engineer. + Not compatible! Uyumlu değil! @@ -4733,6 +4877,10 @@ Bu senin grup için bağlantın %@! Bildirimler devre dışı! No comment provided by engineer. + + Notifications privacy + No comment provided by engineer. + Now admins can: - delete members' messages. @@ -4791,8 +4939,8 @@ VPN'nin etkinleştirilmesi gerekir. Onion ana bilgisayarları kullanılmayacaktır. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. Yalnızca istemci cihazlar kullanıcı profillerini, kişileri, grupları ve **2 katmanlı uçtan uca şifreleme** ile gönderilen mesajları depolar. No comment provided by engineer. @@ -4876,6 +5024,10 @@ VPN'nin etkinleştirilmesi gerekir. Ayarları aç No comment provided by engineer. + + Open changes + No comment provided by engineer. + Open chat Sohbeti aç @@ -4886,6 +5038,10 @@ VPN'nin etkinleştirilmesi gerekir. Sohbet konsolunu aç authentication reason + + Open conditions + No comment provided by engineer. + Open group Grubu aç @@ -4896,26 +5052,19 @@ VPN'nin etkinleştirilmesi gerekir. Başka bir cihaza açık geçiş authentication reason - - Open server settings - Sunucu ayarlarını aç - No comment provided by engineer. - - - Open user profiles - Kullanıcı profillerini aç - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Açık kaynak protokolü ve kodu - herhangi biri sunucuları çalıştırabilir. - No comment provided by engineer. - Opening app… Uygulama açılıyor… No comment provided by engineer. + + Operator + No comment provided by engineer. + + + Operator server + alert title + Or paste archive link Veya arşiv bağlantısını yapıştırın @@ -4936,16 +5085,15 @@ VPN'nin etkinleştirilmesi gerekir. Veya bu kodu göster No comment provided by engineer. + + Or to share privately + No comment provided by engineer. + Other Diğer No comment provided by engineer. - - Other %@ servers - Diğer %@ sunucuları - No comment provided by engineer. - Other file errors: %@ @@ -5028,13 +5176,8 @@ VPN'nin etkinleştirilmesi gerekir. Bekleniyor No comment provided by engineer. - - People can connect to you only via the links you share. - İnsanlar size yalnızca paylaştığınız bağlantılar üzerinden ulaşabilir. - No comment provided by engineer. - - - Periodically + + Periodic Periyodik olarak No comment provided by engineer. @@ -5157,16 +5300,15 @@ Hata: %@ Son mesaj taslağını ekleriyle birlikte koru. No comment provided by engineer. - - Preset server - Ön ayarlı sunucu - No comment provided by engineer. - Preset server address Ön ayarlı sunucu adresi No comment provided by engineer. + + Preset servers + No comment provided by engineer. + Preview Ön izleme @@ -5245,7 +5387,7 @@ Hata: %@ Profile update will be sent to your contacts. Profil güncellemesi kişilerinize gönderilecektir. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -5339,6 +5481,10 @@ Enable in *Network & servers* settings. Proxy şifre gerektirir No comment provided by engineer. + + Push Notifications + No comment provided by engineer. + Push notifications Anında bildirimler @@ -5379,26 +5525,21 @@ Enable in *Network & servers* settings. Dahasını oku No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - [Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - No comment provided by engineer. - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). [Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + [Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). [Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/readme.html#connect-to-friends). No comment provided by engineer. - - Read more in our GitHub repository. - Daha fazlasını GitHub depomuzdan oku. - No comment provided by engineer. - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). [GitHub deposu]nda daha fazlasını okuyun(https://github.com/simplex-chat/simplex-chat#readme). @@ -5715,6 +5856,14 @@ Enable in *Network & servers* settings. Göster chat item action + + Review conditions + No comment provided by engineer. + + + Review later + No comment provided by engineer. + Revoke İptal et @@ -5760,6 +5909,14 @@ Enable in *Network & servers* settings. Daha güvenli gruplar No comment provided by engineer. + + Same conditions will apply to operator **%@**. + No comment provided by engineer. + + + Same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + Save Kaydet @@ -5829,7 +5986,7 @@ Enable in *Network & servers* settings. Save servers? Sunucular kaydedilsin mi? - No comment provided by engineer. + alert title Save welcome message? @@ -6041,11 +6198,6 @@ Enable in *Network & servers* settings. Bildirimler gönder No comment provided by engineer. - - Send notifications: - Bildirimler gönder: - No comment provided by engineer. - Send questions and ideas Fikirler ve sorular gönderin @@ -6171,6 +6323,10 @@ Enable in *Network & servers* settings. Sunucu No comment provided by engineer. + + Server added to operator %@. + alert message + Server address Sunucu adresi @@ -6186,6 +6342,18 @@ Enable in *Network & servers* settings. Sunucu adresi ağ ayarlarıyla uyumsuz: %@. No comment provided by engineer. + + Server operator changed. + alert title + + + Server operators + No comment provided by engineer. + + + Server protocol changed. + alert title + Server requires authorization to create queues, check password Sunucunun sıra oluşturması için yetki gereklidir, şifreyi kontrol edin @@ -6304,22 +6472,35 @@ Enable in *Network & servers* settings. Share Paylaş - chat item action + alert action + chat item action Share 1-time link Tek kullanımlık bağlantıyı paylaş No comment provided by engineer. + + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + No comment provided by engineer. + Share address Adresi paylaş No comment provided by engineer. + + Share address publicly + No comment provided by engineer. + Share address with contacts? Kişilerle adres paylaşılsın mı? - No comment provided by engineer. + alert title Share from other apps. @@ -6436,6 +6617,14 @@ Enable in *Network & servers* settings. SimpleX adresi No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + No comment provided by engineer. + SimpleX contact address SimpleX kişi adresi @@ -6526,6 +6715,11 @@ Enable in *Network & servers* settings. İçe aktarma sırasında bazı önemli olmayan hatalar oluştu: No comment provided by engineer. + + Some servers failed the test: +%@ + alert message + Somebody Biri @@ -6609,12 +6803,12 @@ Enable in *Network & servers* settings. Stop sharing Paylaşmayı durdur - No comment provided by engineer. + alert action Stop sharing address? Adresi paylaşmak durdurulsun mu? - No comment provided by engineer. + alert title Stopping chat @@ -6764,7 +6958,7 @@ Enable in *Network & servers* settings. Tests failed! Testler başarısız oldu! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6781,11 +6975,6 @@ Enable in *Network & servers* settings. Kullanıcılar için teşekkürler - Weblate aracılığıyla katkıda bulun! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - Herhangi bir kullanıcı tanımlayıcısı olmayan ilk platform - tasarım gereği gizli. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6798,6 +6987,10 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Uygulama, mesaj veya iletişim isteği aldığınızda sizi bilgilendirebilir - etkinleştirmek için lütfen ayarları açın. No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). Uygulama bilinmeyen dosya sunucularından indirmeleri onaylamanızı isteyecektir (.onion hariç). @@ -6813,6 +7006,10 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Taradığınız kod bir SimpleX bağlantı QR kodu değildir. No comment provided by engineer. + + The connection reached the limit of undelivered messages, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! Bağlantı kabulünüz iptal edilecektir! @@ -6833,6 +7030,11 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Şifreleme çalışıyor ve yeni şifreleme anlaşması gerekli değil. Bağlantı hatalarına neden olabilir! No comment provided by engineer. + + The future of messaging + Gizli mesajlaşmanın yeni nesli + No comment provided by engineer. + The hash of the previous message is different. Önceki mesajın hash'i farklı. @@ -6858,11 +7060,6 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Mesajlar tüm üyeler için moderasyonlu olarak işaretlenecektir. No comment provided by engineer. - - The next generation of private messaging - Gizli mesajlaşmanın yeni nesli - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. Eski veritabanı geçiş sırasında kaldırılmadı, silinebilir. @@ -6873,6 +7070,10 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Profil sadece kişilerinle paylaşılacak. No comment provided by engineer. + + The second preset operator in the app! + No comment provided by engineer. + The second tick we missed! ✅ Özlediğimiz ikinci tik! ✅ @@ -6888,6 +7089,10 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Mevcut sohbet profilinizin yeni bağlantıları için sunucular **%@**. No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. Yapıştırdığın metin bir SimpleX bağlantısı değildir. @@ -6903,6 +7108,10 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Temalar No comment provided by engineer. + + These conditions will also apply for: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. Bu ayarlar mevcut profiliniz **%@** içindir. @@ -7003,9 +7212,8 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Yeni bir bağlantı oluşturmak için No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Gizliliği korumak için, diğer tüm platformlar gibi kullanıcı kimliği kullanmak yerine, SimpleX mesaj kuyrukları için kişilerinizin her biri için ayrı tanımlayıcılara sahiptir. + + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -7025,6 +7233,15 @@ You will be prompted to complete authentication before this feature is enabled.< Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenecektir. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + Gizliliği korumak için, diğer tüm platformlar gibi kullanıcı kimliği kullanmak yerine, SimpleX mesaj kuyrukları için kişilerinizin her biri için ayrı tanımlayıcılara sahiptir. + No comment provided by engineer. + + + To receive + No comment provided by engineer. + To record speech please grant permission to use Microphone. Konuşmayı kaydetmek için lütfen Mikrofon kullanma izni verin. @@ -7045,11 +7262,19 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec Gizli profilinizi ortaya çıkarmak için **Sohbet profilleriniz** sayfasındaki arama alanına tam bir şifre girin. No comment provided by engineer. + + To send + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. Anlık anlık bildirimleri desteklemek için sohbet veritabanının taşınması gerekir. No comment provided by engineer. + + To use the servers of **%@**, accept conditions of use. + No comment provided by engineer. + To verify end-to-end encryption with your contact compare (or scan) the code on your devices. Kişinizle uçtan uca şifrelemeyi doğrulamak için cihazlarınızdaki kodu karşılaştırın (veya tarayın). @@ -7140,6 +7365,10 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec Üyenin engeli kaldırılsın mı? No comment provided by engineer. + + Undelivered messages + No comment provided by engineer. + Unexpected migration state Beklenmeyen geçiş durumu @@ -7297,6 +7526,10 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Arşiv yükleme No comment provided by engineer. + + Use %@ + No comment provided by engineer. + Use .onion hosts .onion ana bilgisayarlarını kullan @@ -7322,6 +7555,14 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Şu anki profili kullan No comment provided by engineer. + + Use for files + No comment provided by engineer. + + + Use for messages + No comment provided by engineer. + Use for new connections Yeni bağlantılar için kullan @@ -7362,6 +7603,10 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Sunucu kullan No comment provided by engineer. + + Use servers + No comment provided by engineer. + Use the app while in the call. Görüşme sırasında uygulamayı kullanın. @@ -7452,11 +7697,19 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste 1gb'a kadar videolar ve dosyalar No comment provided by engineer. + + View conditions + No comment provided by engineer. + View security code Güvenlik kodunu görüntüle No comment provided by engineer. + + View updated conditions + No comment provided by engineer. + Visible history Görünür geçmiş @@ -7567,9 +7820,8 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Sesli ve görüntülü aramalara bağlanırken. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - İnsanlar bağlantı talebinde bulunduğunda, kabul edebilir veya reddedebilirsiniz. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -7729,6 +7981,18 @@ Katılma isteği tekrarlansın mı? Görünüm ayarlarından değiştirebilirsiniz. No comment provided by engineer. + + You can configure operators in Network & servers settings. + No comment provided by engineer. + + + You can configure servers via settings. + No comment provided by engineer. + + + You can create it in user picker. + No comment provided by engineer. + You can create it later Daha sonra oluşturabilirsiniz @@ -7769,6 +8033,10 @@ Katılma isteği tekrarlansın mı? Arşivlenen kişilerden %@'ya mesaj gönderebilirsiniz. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + No comment provided by engineer. + You can set lock screen notification preview via settings. Kilit ekranı bildirim önizlemesini ayarlar üzerinden ayarlayabilirsiniz. @@ -7784,11 +8052,6 @@ Katılma isteği tekrarlansın mı? Bu adresi kişilerinizle paylaşarak onların **%@** ile bağlantı kurmasını sağlayabilirsiniz. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - Adresinizi bir bağlantı veya QR kodu olarak paylaşabilirsiniz - herkes size bağlanabilir. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app Sohbeti uygulamada Ayarlar / Veritabanı üzerinden veya uygulamayı yeniden başlatarak başlatabilirsiniz @@ -7812,23 +8075,23 @@ Katılma isteği tekrarlansın mı? You can view invitation link again in connection details. Bağlantı detaylarından davet bağlantısını yeniden görüntüleyebilirsin. - No comment provided by engineer. + alert message You can't send messages! Mesajlar gönderemezsiniz! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - Mesajların hangi sunucu(lar)dan **alınacağını**, kişilerinizi - onlara mesaj göndermek için kullandığınız sunucuları - siz kontrol edersiniz. - No comment provided by engineer. - You could not be verified; please try again. Doğrulanamadınız; lütfen tekrar deneyin. No comment provided by engineer. + + You decide who can connect. + Kimin bağlanabileceğine siz karar verirsiniz. + No comment provided by engineer. + You have already requested connection via this address! Bu adres üzerinden zaten bağlantı talebinde bulundunuz! @@ -7951,11 +8214,6 @@ Bağlantı isteği tekrarlansın mı? Bu grup için gizli bir profil kullanıyorsunuz - ana profilinizi paylaşmayı önlemek için kişileri davet etmeye izin verilmiyor No comment provided by engineer. - - Your %@ servers - %@ sunucularınız - No comment provided by engineer. - Your ICE servers ICE sunucularınız @@ -7971,11 +8229,6 @@ Bağlantı isteği tekrarlansın mı? SimpleX adresin No comment provided by engineer. - - Your XFTP servers - XFTP sunucularınız - No comment provided by engineer. - Your calls Aramaların @@ -8076,16 +8329,15 @@ Bağlantı isteği tekrarlansın mı? Rasgele profiliniz No comment provided by engineer. - - Your server - Sunucunuz - No comment provided by engineer. - Your server address Sunucu adresiniz No comment provided by engineer. + + Your servers + No comment provided by engineer. + Your settings Ayarlarınız @@ -8506,6 +8758,10 @@ Bağlantı isteği tekrarlansın mı? Süresi dolmuş No comment provided by engineer. + + for better metadata privacy. + No comment provided by engineer. + forwarded iletildi @@ -9128,6 +9384,33 @@ son alınan msj: %2$@ + +
+ +
+ + + %d new events + notification body + + + From: %@ + notification body + + + New events + notification + + + New messages + notification + + + New messages in %d chats + notification body + + +
diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/tr.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index ce37b43c23..339f06687d 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -12,21 +12,6 @@ No comment provided by engineer. - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - ( ( @@ -127,6 +112,14 @@ %@ перевірено No comment provided by engineer. + + %@ server + No comment provided by engineer. + + + %@ servers + No comment provided by engineer. + %@ uploaded %@ завантажено @@ -346,14 +339,9 @@ ) No comment provided by engineer. - - **Add contact**: to create a new invitation link, or connect via a link you received. - **Додати контакт**: створити нове посилання-запрошення або підключитися за отриманим посиланням. - No comment provided by engineer. - - - **Add new contact**: to create your one-time QR Code or link for your contact. - **Додати новий контакт**: щоб створити одноразовий QR-код або посилання для свого контакту. + + **Create 1-time link**: to create and share a new invitation link. + **Додати контакт**: створити нове посилання-запрошення. No comment provided by engineer. @@ -361,13 +349,13 @@ **Створити групу**: створити нову групу. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **Більш приватний**: перевіряти нові повідомлення кожні 20 хвилин. Серверу SimpleX Chat передається токен пристрою, але не кількість контактів або повідомлень, які ви маєте. No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **Найбільш приватний**: не використовуйте сервер сповіщень SimpleX Chat, періодично перевіряйте повідомлення у фоновому режимі (залежить від того, як часто ви користуєтесь додатком). No comment provided by engineer. @@ -381,11 +369,15 @@ **Зверніть увагу: ви НЕ зможете відновити або змінити пароль, якщо втратите його. No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **Рекомендується**: токен пристрою та сповіщення надсилаються на сервер сповіщень SimpleX Chat, але не вміст повідомлення, його розмір або від кого воно надійшло. No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **Попередження**: Для отримання миттєвих пуш-сповіщень потрібна парольна фраза, збережена у брелоку. @@ -492,6 +484,14 @@ 1 тиждень time interval + + 1-time link + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + No comment provided by engineer. + 5 minutes 5 хвилин @@ -561,21 +561,11 @@ Скасувати зміну адреси? No comment provided by engineer. - - About SimpleX - Про SimpleX - No comment provided by engineer. - About SimpleX Chat Про чат SimpleX No comment provided by engineer. - - About SimpleX address - Про адресу SimpleX - No comment provided by engineer. - Accent Акцент @@ -588,6 +578,10 @@ accept incoming call via notification swipe action + + Accept conditions + No comment provided by engineer. + Accept connection request? Прийняти запит на підключення? @@ -604,6 +598,10 @@ accept contact request via notification swipe action + + Accepted conditions + No comment provided by engineer. + Acknowledged Визнано @@ -624,16 +622,6 @@ Додайте адресу до свого профілю, щоб ваші контакти могли поділитися нею з іншими людьми. Повідомлення про оновлення профілю буде надіслано вашим контактам. No comment provided by engineer. - - Add contact - Додати контакт - No comment provided by engineer. - - - Add preset servers - Додавання попередньо встановлених серверів - No comment provided by engineer. - Add profile Додати профіль @@ -659,6 +647,14 @@ Додати вітальне повідомлення No comment provided by engineer. + + Added media & file servers + No comment provided by engineer. + + + Added message servers + No comment provided by engineer. + Additional accent Додатковий акцент @@ -684,6 +680,14 @@ Зміна адреси буде скасована. Буде використано стару адресу отримання. No comment provided by engineer. + + Address or 1-time link? + No comment provided by engineer. + + + Address settings + No comment provided by engineer. + Admins can block a member for all. Адміністратори можуть заблокувати користувача для всіх. @@ -729,6 +733,10 @@ Всі учасники групи залишаться на зв'язку. No comment provided by engineer. + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + No comment provided by engineer. + All messages will be deleted - this cannot be undone! Усі повідомлення будуть видалені - цю дію не можна скасувати! @@ -909,6 +917,11 @@ Відповісти на дзвінок No comment provided by engineer. + + Anybody can host servers. + Кожен може хостити сервери. + No comment provided by engineer. + App build: %@ Збірка програми: %@ @@ -1245,7 +1258,8 @@ Cancel Скасувати - alert button + alert action + alert button Cancel migration @@ -1328,6 +1342,10 @@ authentication reason set passcode view + + Change user profiles + authentication reason + Chat archive Архів чату @@ -1412,10 +1430,18 @@ Чати No comment provided by engineer. + + Check messages every 20 min. + No comment provided by engineer. + + + Check messages when allowed. + No comment provided by engineer. + Check server address and try again. Перевірте адресу сервера та спробуйте ще раз. - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1502,16 +1528,47 @@ Завершено No comment provided by engineer. + + Conditions accepted on: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for following operator(s): **%@**. + No comment provided by engineer. + + + Conditions of use + No comment provided by engineer. + + + Conditions will be accepted for enabled operators after 30 days. + No comment provided by engineer. + + + Conditions will be accepted for operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + No comment provided by engineer. + Configure ICE servers Налаштування серверів ICE No comment provided by engineer. - - Configured %@ servers - Налаштовані сервери %@ - No comment provided by engineer. - Confirm Підтвердити @@ -1701,6 +1758,10 @@ This is your own one-time link! Запит на підключення відправлено! No comment provided by engineer. + + Connection security + No comment provided by engineer. + Connection terminated З'єднання розірвано @@ -1815,6 +1876,10 @@ This is your own one-time link! Створити No comment provided by engineer. + + Create 1-time link + No comment provided by engineer. + Create SimpleX address Створіть адресу SimpleX @@ -1825,11 +1890,6 @@ This is your own one-time link! Створіть групу, використовуючи випадковий профіль. No comment provided by engineer. - - Create an address to let people connect with you. - Створіть адресу, щоб люди могли з вами зв'язатися. - No comment provided by engineer. - Create file Створити файл @@ -1910,6 +1970,10 @@ This is your own one-time link! Поточний пароль No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + No comment provided by engineer. + Current passphrase… Поточна парольна фраза… @@ -2065,7 +2129,8 @@ This is your own one-time link! Delete Видалити - chat item action + alert action + chat item action swipe action @@ -2282,6 +2347,10 @@ This is your own one-time link! Помилки видалення No comment provided by engineer. + + Delivered even when Apple drops them. + No comment provided by engineer. + Delivery Доставка @@ -2561,6 +2630,10 @@ This is your own one-time link! Тривалість No comment provided by engineer. + + E2E encrypted notifications. + No comment provided by engineer. + Edit Редагувати @@ -2581,6 +2654,10 @@ This is your own one-time link! Увімкнути (зберегти перевизначення) No comment provided by engineer. + + Enable Flux + No comment provided by engineer. + Enable SimpleX Lock Увімкнути SimpleX Lock @@ -2786,6 +2863,10 @@ This is your own one-time link! Помилка скасування зміни адреси No comment provided by engineer. + + Error accepting conditions + alert title + Error accepting contact request Помилка при прийнятті запиту на контакт @@ -2801,6 +2882,10 @@ This is your own one-time link! Помилка додавання користувача(ів) No comment provided by engineer. + + Error adding server + alert title + Error changing address Помилка зміни адреси @@ -2939,10 +3024,9 @@ This is your own one-time link! Помилка приєднання до групи No comment provided by engineer. - - Error loading %@ servers - Помилка завантаження %@ серверів - No comment provided by engineer. + + Error loading servers + alert title Error migrating settings @@ -2978,11 +3062,6 @@ This is your own one-time link! Статистика скидання помилок No comment provided by engineer. - - Error saving %@ servers - Помилка збереження %@ серверів - No comment provided by engineer. - Error saving ICE servers Помилка збереження серверів ICE @@ -3003,6 +3082,10 @@ This is your own one-time link! Помилка збереження пароля на keychain No comment provided by engineer. + + Error saving servers + alert title + Error saving settings Налаштування збереження помилок @@ -3072,6 +3155,10 @@ This is your own one-time link! Повідомлення про помилку оновлення No comment provided by engineer. + + Error updating server + alert title + Error updating settings Помилка оновлення налаштувань @@ -3117,6 +3204,10 @@ This is your own one-time link! Помилки No comment provided by engineer. + + Errors in servers configuration. + servers error + Even when disabled in the conversation. Навіть коли вимкнений у розмові. @@ -3317,11 +3408,27 @@ This is your own one-time link! Виправлення не підтримується учасником групи No comment provided by engineer. + + For chat profile %@: + servers error + For console Для консолі No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + No comment provided by engineer. + + + For private routing + No comment provided by engineer. + + + For social media + No comment provided by engineer. + Forward Пересилання @@ -3626,9 +3733,12 @@ Error: %2$@ Як працює SimpleX No comment provided by engineer. - - How it works - Як це працює + + How it affects privacy + No comment provided by engineer. + + + How it helps privacy No comment provided by engineer. @@ -3700,8 +3810,8 @@ Error: %2$@ Негайно No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam Імунітет до спаму та зловживань No comment provided by engineer. @@ -3840,6 +3950,11 @@ More improvements are coming soon! Встановіть [SimpleX Chat для терміналу](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + Миттєво + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3847,11 +3962,6 @@ More improvements are coming soon! No comment provided by engineer. - - Instantly - Миттєво - No comment provided by engineer. - Interface Інтерфейс @@ -3900,7 +4010,7 @@ More improvements are coming soon! Invalid server address! Неправильна адреса сервера! - No comment provided by engineer. + alert title Invalid status @@ -4028,7 +4138,7 @@ This is your link for group %@! Keep Тримай - No comment provided by engineer. + alert action Keep conversation @@ -4043,7 +4153,7 @@ This is your link for group %@! Keep unused invitation? Зберігати невикористані запрошення? - No comment provided by engineer. + alert title Keep your connections @@ -4130,11 +4240,6 @@ This is your link for group %@! Живі повідомлення No comment provided by engineer. - - Local - Локально - No comment provided by engineer. - Local name Місцева назва @@ -4155,11 +4260,6 @@ This is your link for group %@! Режим блокування No comment provided by engineer. - - Make a private connection - Створіть приватне з'єднання - No comment provided by engineer. - Make one message disappear Зробити так, щоб одне повідомлення зникло @@ -4170,21 +4270,11 @@ This is your link for group %@! Зробіть профіль приватним! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Переконайтеся, що адреси серверів %@ мають правильний формат, розділені рядками і не дублюються (%@). - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. Переконайтеся, що адреси серверів WebRTC ICE мають правильний формат, розділені рядками і не дублюються. No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Багато людей запитували: *якщо SimpleX не має ідентифікаторів користувачів, як він може доставляти повідомлення?* - No comment provided by engineer. - Mark deleted for everyone Позначити видалено для всіх @@ -4463,6 +4553,10 @@ This is your link for group %@! Більш надійне з'єднання з мережею. No comment provided by engineer. + + More reliable notifications + No comment provided by engineer. + Most likely this connection is deleted. Швидше за все, це з'єднання видалено. @@ -4498,6 +4592,10 @@ This is your link for group %@! Підключення до мережі No comment provided by engineer. + + Network decentralization + No comment provided by engineer. + Network issues - message expired after many attempts to send it. Проблеми з мережею - термін дії повідомлення закінчився після багатьох спроб надіслати його. @@ -4508,6 +4606,10 @@ This is your link for group %@! Керування мережею No comment provided by engineer. + + Network operator + No comment provided by engineer. + Network settings Налаштування мережі @@ -4566,6 +4668,10 @@ This is your link for group %@! Нове ім'я відображення No comment provided by engineer. + + New events + notification + New in %@ Нове в %@ @@ -4591,6 +4697,10 @@ This is your link for group %@! Новий пароль… No comment provided by engineer. + + New server + No comment provided by engineer. + No Ні @@ -4646,6 +4756,14 @@ This is your link for group %@! Немає інформації, спробуйте перезавантажити No comment provided by engineer. + + No media & file servers. + servers error + + + No message servers. + servers error + No network connection Немає підключення до мережі @@ -4664,11 +4782,37 @@ This is your link for group %@! Немає дозволу на запис голосового повідомлення No comment provided by engineer. + + No push server + Локально + No comment provided by engineer. + No received or sent files Немає отриманих або відправлених файлів No comment provided by engineer. + + No servers for private message routing. + servers error + + + No servers to receive files. + servers error + + + No servers to receive messages. + servers error + + + No servers to send files. + servers error + + + No user identifiers. + Ніяких ідентифікаторів користувачів. + No comment provided by engineer. + Not compatible! Не сумісні! @@ -4693,6 +4837,10 @@ This is your link for group %@! Сповіщення вимкнено! No comment provided by engineer. + + Notifications privacy + No comment provided by engineer. + Now admins can: - delete members' messages. @@ -4751,8 +4899,8 @@ Requires compatible VPN. Onion хости не будуть використовуватися. No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. Тільки клієнтські пристрої зберігають профілі користувачів, контакти, групи та повідомлення, надіслані за допомогою **2-шарового наскрізного шифрування**. No comment provided by engineer. @@ -4836,6 +4984,10 @@ Requires compatible VPN. Відкрийте Налаштування No comment provided by engineer. + + Open changes + No comment provided by engineer. + Open chat Відкритий чат @@ -4846,6 +4998,10 @@ Requires compatible VPN. Відкрийте консоль чату authentication reason + + Open conditions + No comment provided by engineer. + Open group Відкрита група @@ -4856,26 +5012,19 @@ Requires compatible VPN. Відкрита міграція на інший пристрій authentication reason - - Open server settings - Відкрити налаштування сервера - No comment provided by engineer. - - - Open user profiles - Відкрити профілі користувачів - authentication reason - - - Open-source protocol and code – anybody can run the servers. - Протокол і код з відкритим вихідним кодом - будь-хто може запускати сервери. - No comment provided by engineer. - Opening app… Відкриваємо програму… No comment provided by engineer. + + Operator + No comment provided by engineer. + + + Operator server + alert title + Or paste archive link Або вставте посилання на архів @@ -4896,16 +5045,15 @@ Requires compatible VPN. Або покажіть цей код No comment provided by engineer. + + Or to share privately + No comment provided by engineer. + Other Інше No comment provided by engineer. - - Other %@ servers - Інші сервери %@ - No comment provided by engineer. - Other file errors: %@ @@ -4985,13 +5133,8 @@ Requires compatible VPN. В очікуванні No comment provided by engineer. - - People can connect to you only via the links you share. - Люди можуть зв'язатися з вами лише за посиланнями, якими ви ділитеся. - No comment provided by engineer. - - - Periodically + + Periodic Періодично No comment provided by engineer. @@ -5113,16 +5256,15 @@ Error: %@ Зберегти чернетку останнього повідомлення з вкладеннями. No comment provided by engineer. - - Preset server - Попередньо встановлений сервер - No comment provided by engineer. - Preset server address Попередньо встановлена адреса сервера No comment provided by engineer. + + Preset servers + No comment provided by engineer. + Preview Попередній перегляд @@ -5201,7 +5343,7 @@ Error: %@ Profile update will be sent to your contacts. Оновлення профілю буде надіслано вашим контактам. - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -5294,6 +5436,10 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. + + Push Notifications + No comment provided by engineer. + Push notifications Push-повідомлення @@ -5334,26 +5480,21 @@ Enable in *Network & servers* settings. Читати далі No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - Читайте більше в [Посібнику користувача](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - No comment provided by engineer. - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). Читайте більше в [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + Читайте більше в [Посібнику користувача](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). Читайте більше в [Посібнику користувача](https://simplex.chat/docs/guide/readme.html#connect-to-friends). No comment provided by engineer. - - Read more in our GitHub repository. - Читайте більше в нашому репозиторії на GitHub. - No comment provided by engineer. - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). Читайте більше в нашому [GitHub репозиторії](https://github.com/simplex-chat/simplex-chat#readme). @@ -5669,6 +5810,14 @@ Enable in *Network & servers* settings. Показувати chat item action + + Review conditions + No comment provided by engineer. + + + Review later + No comment provided by engineer. + Revoke Відкликати @@ -5713,6 +5862,14 @@ Enable in *Network & servers* settings. Безпечніші групи No comment provided by engineer. + + Same conditions will apply to operator **%@**. + No comment provided by engineer. + + + Same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + Save Зберегти @@ -5782,7 +5939,7 @@ Enable in *Network & servers* settings. Save servers? Зберегти сервери? - No comment provided by engineer. + alert title Save welcome message? @@ -5991,11 +6148,6 @@ Enable in *Network & servers* settings. Надсилати сповіщення No comment provided by engineer. - - Send notifications: - Надсилати сповіщення: - No comment provided by engineer. - Send questions and ideas Надсилайте запитання та ідеї @@ -6120,6 +6272,10 @@ Enable in *Network & servers* settings. Server No comment provided by engineer. + + Server added to operator %@. + alert message + Server address Адреса сервера @@ -6135,6 +6291,18 @@ Enable in *Network & servers* settings. Адреса сервера несумісна з налаштуваннями мережі: %@. No comment provided by engineer. + + Server operator changed. + alert title + + + Server operators + No comment provided by engineer. + + + Server protocol changed. + alert title + Server requires authorization to create queues, check password Сервер вимагає авторизації для створення черг, перевірте пароль @@ -6252,22 +6420,35 @@ Enable in *Network & servers* settings. Share Поділіться - chat item action + alert action + chat item action Share 1-time link Поділитися 1-разовим посиланням No comment provided by engineer. + + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + No comment provided by engineer. + Share address Поділитися адресою No comment provided by engineer. + + Share address publicly + No comment provided by engineer. + Share address with contacts? Поділіться адресою з контактами? - No comment provided by engineer. + alert title Share from other apps. @@ -6383,6 +6564,14 @@ Enable in *Network & servers* settings. Адреса SimpleX No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + No comment provided by engineer. + SimpleX contact address Контактна адреса SimpleX @@ -6471,6 +6660,11 @@ Enable in *Network & servers* settings. Під час імпорту виникли деякі несмертельні помилки: No comment provided by engineer. + + Some servers failed the test: +%@ + alert message + Somebody Хтось @@ -6554,12 +6748,12 @@ Enable in *Network & servers* settings. Stop sharing Припиніть ділитися - No comment provided by engineer. + alert action Stop sharing address? Припинити ділитися адресою? - No comment provided by engineer. + alert title Stopping chat @@ -6706,7 +6900,7 @@ Enable in *Network & servers* settings. Tests failed! Тести не пройшли! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6723,11 +6917,6 @@ Enable in *Network & servers* settings. Дякуємо користувачам - зробіть свій внесок через Weblate! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - Перша платформа без жодних ідентифікаторів користувачів – приватна за дизайном. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6740,6 +6929,10 @@ It can happen because of some bug or when the connection is compromised.Додаток може сповіщати вас, коли ви отримуєте повідомлення або запити на контакт - будь ласка, відкрийте налаштування, щоб увімкнути цю функцію. No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). Програма попросить підтвердити завантаження з невідомих файлових серверів (крім .onion). @@ -6755,6 +6948,10 @@ It can happen because of some bug or when the connection is compromised.Відсканований вами код не є QR-кодом посилання SimpleX. No comment provided by engineer. + + The connection reached the limit of undelivered messages, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! Прийняте вами з'єднання буде скасовано! @@ -6775,6 +6972,11 @@ It can happen because of some bug or when the connection is compromised.Шифрування працює і нова угода про шифрування не потрібна. Це може призвести до помилок з'єднання! No comment provided by engineer. + + The future of messaging + Наступне покоління приватних повідомлень + No comment provided by engineer. + The hash of the previous message is different. Хеш попереднього повідомлення відрізняється. @@ -6800,11 +7002,6 @@ It can happen because of some bug or when the connection is compromised.Повідомлення будуть позначені як модеровані для всіх учасників. No comment provided by engineer. - - The next generation of private messaging - Наступне покоління приватних повідомлень - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. Стара база даних не була видалена під час міграції, її можна видалити. @@ -6815,6 +7012,10 @@ It can happen because of some bug or when the connection is compromised.Профіль доступний лише вашим контактам. No comment provided by engineer. + + The second preset operator in the app! + No comment provided by engineer. + The second tick we missed! ✅ Другу галочку ми пропустили! ✅ @@ -6830,6 +7031,10 @@ It can happen because of some bug or when the connection is compromised.Сервери для нових підключень вашого поточного профілю чату **%@**. No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. Текст, який ви вставили, не є посиланням SimpleX. @@ -6844,6 +7049,10 @@ It can happen because of some bug or when the connection is compromised.Теми No comment provided by engineer. + + These conditions will also apply for: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. Ці налаштування стосуються вашого поточного профілю **%@**. @@ -6944,9 +7153,8 @@ It can happen because of some bug or when the connection is compromised.Щоб створити нове з'єднання No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - Щоб захистити конфіденційність, замість ідентифікаторів користувачів, які використовуються на всіх інших платформах, SimpleX має ідентифікатори для черг повідомлень, окремі для кожного з ваших контактів. + + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -6966,6 +7174,15 @@ You will be prompted to complete authentication before this feature is enabled.< Перед увімкненням цієї функції вам буде запропоновано пройти автентифікацію. No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + Щоб захистити конфіденційність, замість ідентифікаторів користувачів, які використовуються на всіх інших платформах, SimpleX має ідентифікатори для черг повідомлень, окремі для кожного з ваших контактів. + No comment provided by engineer. + + + To receive + No comment provided by engineer. + To record speech please grant permission to use Microphone. No comment provided by engineer. @@ -6984,11 +7201,19 @@ You will be prompted to complete authentication before this feature is enabled.< Щоб відкрити свій прихований профіль, введіть повний пароль у поле пошуку на сторінці **Ваші профілі чату**. No comment provided by engineer. + + To send + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. Для підтримки миттєвих push-повідомлень необхідно перенести базу даних чату. No comment provided by engineer. + + To use the servers of **%@**, accept conditions of use. + No comment provided by engineer. + To verify end-to-end encryption with your contact compare (or scan) the code on your devices. Щоб перевірити наскрізне шифрування з вашим контактом, порівняйте (або відскануйте) код на ваших пристроях. @@ -7079,6 +7304,10 @@ You will be prompted to complete authentication before this feature is enabled.< Розблокувати учасника? No comment provided by engineer. + + Undelivered messages + No comment provided by engineer. + Unexpected migration state Неочікуваний стан міграції @@ -7236,6 +7465,10 @@ To connect, please ask your contact to create another connection link and check Завантаження архіву No comment provided by engineer. + + Use %@ + No comment provided by engineer. + Use .onion hosts Використовуйте хости .onion @@ -7260,6 +7493,14 @@ To connect, please ask your contact to create another connection link and check Використовувати поточний профіль No comment provided by engineer. + + Use for files + No comment provided by engineer. + + + Use for messages + No comment provided by engineer. + Use for new connections Використовуйте для нових з'єднань @@ -7300,6 +7541,10 @@ To connect, please ask your contact to create another connection link and check Використовувати сервер No comment provided by engineer. + + Use servers + No comment provided by engineer. + Use the app while in the call. Використовуйте додаток під час розмови. @@ -7389,11 +7634,19 @@ To connect, please ask your contact to create another connection link and check Відео та файли до 1 Гб No comment provided by engineer. + + View conditions + No comment provided by engineer. + View security code Переглянути код безпеки No comment provided by engineer. + + View updated conditions + No comment provided by engineer. + Visible history Видима історія @@ -7504,9 +7757,8 @@ To connect, please ask your contact to create another connection link and check При підключенні аудіо та відеодзвінків. No comment provided by engineer. - - When people request to connect, you can accept or reject it. - Коли люди звертаються із запитом на підключення, ви можете прийняти або відхилити його. + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -7666,6 +7918,18 @@ Repeat join request? Ви можете змінити його в налаштуваннях зовнішнього вигляду. No comment provided by engineer. + + You can configure operators in Network & servers settings. + No comment provided by engineer. + + + You can configure servers via settings. + No comment provided by engineer. + + + You can create it in user picker. + No comment provided by engineer. + You can create it later Ви можете створити його пізніше @@ -7706,6 +7970,10 @@ Repeat join request? Ви можете надсилати повідомлення на %@ з архівних контактів. No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + No comment provided by engineer. + You can set lock screen notification preview via settings. Ви можете налаштувати попередній перегляд сповіщень на екрані блокування за допомогою налаштувань. @@ -7721,11 +7989,6 @@ Repeat join request? Ви можете поділитися цією адресою зі своїми контактами, щоб вони могли зв'язатися з **%@**. No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - Ви можете поділитися своєю адресою у вигляді посилання або QR-коду - будь-хто зможе зв'язатися з вами. - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app Запустити чат можна через Налаштування програми / База даних або перезапустивши програму @@ -7749,23 +8012,23 @@ Repeat join request? You can view invitation link again in connection details. Ви можете переглянути посилання на запрошення ще раз у деталях підключення. - No comment provided by engineer. + alert message You can't send messages! Ви не можете надсилати повідомлення! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - Ви контролюєте, через який(і) сервер(и) **отримувати** повідомлення, ваші контакти - сервери, які ви використовуєте для надсилання їм повідомлень. - No comment provided by engineer. - You could not be verified; please try again. Вас не вдалося верифікувати, спробуйте ще раз. No comment provided by engineer. + + You decide who can connect. + Ви вирішуєте, хто може під'єднатися. + No comment provided by engineer. + You have already requested connection via this address! Ви вже надсилали запит на підключення за цією адресою! @@ -7888,11 +8151,6 @@ Repeat connection request? Ви використовуєте профіль інкогніто для цієї групи - щоб запобігти поширенню вашого основного профілю, запрошення контактів заборонено No comment provided by engineer. - - Your %@ servers - Ваші сервери %@ - No comment provided by engineer. - Your ICE servers Ваші сервери ICE @@ -7908,11 +8166,6 @@ Repeat connection request? Ваша адреса SimpleX No comment provided by engineer. - - Your XFTP servers - Ваші XFTP-сервери - No comment provided by engineer. - Your calls Твої дзвінки @@ -8009,16 +8262,15 @@ Repeat connection request? Ваш випадковий профіль No comment provided by engineer. - - Your server - Ваш сервер - No comment provided by engineer. - Your server address Адреса вашого сервера No comment provided by engineer. + + Your servers + No comment provided by engineer. + Your settings Ваші налаштування @@ -8439,6 +8691,10 @@ Repeat connection request? закінчився No comment provided by engineer. + + for better metadata privacy. + No comment provided by engineer. + forwarded переслано @@ -9061,6 +9317,33 @@ last received msg: %2$@ + +
+ +
+ + + %d new events + notification body + + + From: %@ + notification body + + + New events + notification + + + New messages + notification + + + New messages in %d chats + notification body + + +
diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/uk.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index 91893dd939..3f48211025 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -12,21 +12,6 @@ No comment provided by engineer. - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - ( ( @@ -127,6 +112,14 @@ %@ 已认证 No comment provided by engineer. + + %@ server + No comment provided by engineer. + + + %@ servers + No comment provided by engineer. + %@ uploaded %@ 已上传 @@ -346,28 +339,23 @@ ) No comment provided by engineer. - - **Add contact**: to create a new invitation link, or connect via a link you received. + + **Create 1-time link**: to create and share a new invitation link. **添加联系人**: 创建新的邀请链接,或通过您收到的链接进行连接. No comment provided by engineer. - - **Add new contact**: to create your one-time QR Code or link for your contact. - **添加新联系人**:为您的联系人创建一次性二维码或者链接。 - No comment provided by engineer. - **Create group**: to create a new group. **创建群组**: 创建一个新群组. No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **更私密**:每20分钟检查新消息。设备令牌和 SimpleX Chat 服务器共享,但是不会共享有您有多少联系人或者消息。 No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **最私密**:不使用 SimpleX Chat 通知服务器,在后台定期检查消息(取决于您多经常使用应用程序)。 No comment provided by engineer. @@ -381,11 +369,15 @@ **请注意**:如果您丢失密码,您将无法恢复或者更改密码。 No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **推荐**:设备令牌和通知会发送至 SimpleX Chat 通知服务器,但是消息内容、大小或者发送人不会。 No comment provided by engineer. + + **Scan / Paste link**: to connect via a link you received. + No comment provided by engineer. + **Warning**: Instant push notifications require passphrase saved in Keychain. **警告**:及时推送通知需要保存在钥匙串的密码。 @@ -492,6 +484,14 @@ 1周 time interval + + 1-time link + No comment provided by engineer. + + + 1-time link can be used *with one contact only* - share in person or via any messenger. + No comment provided by engineer. + 5 minutes 5分钟 @@ -561,21 +561,11 @@ 中止地址更改? No comment provided by engineer. - - About SimpleX - 关于SimpleX - No comment provided by engineer. - About SimpleX Chat 关于SimpleX Chat No comment provided by engineer. - - About SimpleX address - 关于 SimpleX 地址 - No comment provided by engineer. - Accent 强调 @@ -588,6 +578,10 @@ accept incoming call via notification swipe action + + Accept conditions + No comment provided by engineer. + Accept connection request? 接受联系人? @@ -604,6 +598,10 @@ accept contact request via notification swipe action + + Accepted conditions + No comment provided by engineer. + Acknowledged 确认 @@ -624,16 +622,6 @@ 将地址添加到您的个人资料,以便您的联系人可以与其他人共享。个人资料更新将发送给您的联系人。 No comment provided by engineer. - - Add contact - 添加联系人 - No comment provided by engineer. - - - Add preset servers - 添加预设服务器 - No comment provided by engineer. - Add profile 添加个人资料 @@ -659,6 +647,14 @@ 添加欢迎信息 No comment provided by engineer. + + Added media & file servers + No comment provided by engineer. + + + Added message servers + No comment provided by engineer. + Additional accent 附加重音 @@ -684,6 +680,14 @@ 将中止地址更改。将使用旧接收地址。 No comment provided by engineer. + + Address or 1-time link? + No comment provided by engineer. + + + Address settings + No comment provided by engineer. + Admins can block a member for all. 管理员可以为所有人封禁一名成员。 @@ -729,6 +733,10 @@ 所有群组成员将保持连接。 No comment provided by engineer. + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + No comment provided by engineer. + All messages will be deleted - this cannot be undone! 所有消息都将被删除 - 这无法被撤销! @@ -909,6 +917,11 @@ 接听来电 No comment provided by engineer. + + Anybody can host servers. + 任何人都可以托管服务器。 + No comment provided by engineer. + App build: %@ 应用程序构建:%@ @@ -1245,7 +1258,8 @@ Cancel 取消 - alert button + alert action + alert button Cancel migration @@ -1328,6 +1342,10 @@ authentication reason set passcode view + + Change user profiles + authentication reason + Chat archive 聊天档案 @@ -1412,10 +1430,18 @@ 聊天 No comment provided by engineer. + + Check messages every 20 min. + No comment provided by engineer. + + + Check messages when allowed. + No comment provided by engineer. + Check server address and try again. 检查服务器地址并再试一次。 - No comment provided by engineer. + alert title Chinese and Spanish interface @@ -1502,16 +1528,47 @@ 已完成 No comment provided by engineer. + + Conditions accepted on: %@. + No comment provided by engineer. + + + Conditions are accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions are already accepted for following operator(s): **%@**. + No comment provided by engineer. + + + Conditions of use + No comment provided by engineer. + + + Conditions will be accepted for enabled operators after 30 days. + No comment provided by engineer. + + + Conditions will be accepted for operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted for the operator(s): **%@**. + No comment provided by engineer. + + + Conditions will be accepted on: %@. + No comment provided by engineer. + + + Conditions will be automatically accepted for enabled operators on: %@. + No comment provided by engineer. + Configure ICE servers 配置 ICE 服务器 No comment provided by engineer. - - Configured %@ servers - 已配置 %@ 服务器 - No comment provided by engineer. - Confirm 确认 @@ -1701,6 +1758,10 @@ This is your own one-time link! 已发送连接请求! No comment provided by engineer. + + Connection security + No comment provided by engineer. + Connection terminated 连接被终止 @@ -1815,6 +1876,10 @@ This is your own one-time link! 创建 No comment provided by engineer. + + Create 1-time link + No comment provided by engineer. + Create SimpleX address 创建 SimpleX 地址 @@ -1825,11 +1890,6 @@ This is your own one-time link! 使用随机身份创建群组. No comment provided by engineer. - - Create an address to let people connect with you. - 创建一个地址,让人们与您联系。 - No comment provided by engineer. - Create file 创建文件 @@ -1910,6 +1970,10 @@ This is your own one-time link! 当前密码 No comment provided by engineer. + + Current conditions text couldn't be loaded, you can review conditions via this link: + No comment provided by engineer. + Current passphrase… 现有密码…… @@ -2065,7 +2129,8 @@ This is your own one-time link! Delete 删除 - chat item action + alert action + chat item action swipe action @@ -2282,6 +2347,10 @@ This is your own one-time link! 删除错误 No comment provided by engineer. + + Delivered even when Apple drops them. + No comment provided by engineer. + Delivery 传送 @@ -2561,6 +2630,10 @@ This is your own one-time link! 时长 No comment provided by engineer. + + E2E encrypted notifications. + No comment provided by engineer. + Edit 编辑 @@ -2581,6 +2654,10 @@ This is your own one-time link! 启用(保持覆盖) No comment provided by engineer. + + Enable Flux + No comment provided by engineer. + Enable SimpleX Lock 启用 SimpleX 锁定 @@ -2786,6 +2863,10 @@ This is your own one-time link! 中止地址更改错误 No comment provided by engineer. + + Error accepting conditions + alert title + Error accepting contact request 接受联系人请求错误 @@ -2801,6 +2882,10 @@ This is your own one-time link! 添加成员错误 No comment provided by engineer. + + Error adding server + alert title + Error changing address 更改地址错误 @@ -2939,10 +3024,9 @@ This is your own one-time link! 加入群组错误 No comment provided by engineer. - - Error loading %@ servers - 加载 %@ 服务器错误 - No comment provided by engineer. + + Error loading servers + alert title Error migrating settings @@ -2978,11 +3062,6 @@ This is your own one-time link! 重置统计信息时出错 No comment provided by engineer. - - Error saving %@ servers - 保存 %@ 服务器错误 - No comment provided by engineer. - Error saving ICE servers 保存 ICE 服务器错误 @@ -3003,6 +3082,10 @@ This is your own one-time link! 保存密码到钥匙串错误 No comment provided by engineer. + + Error saving servers + alert title + Error saving settings 保存设置出错 @@ -3072,6 +3155,10 @@ This is your own one-time link! 更新消息错误 No comment provided by engineer. + + Error updating server + alert title + Error updating settings 更新设置错误 @@ -3117,6 +3204,10 @@ This is your own one-time link! 错误 No comment provided by engineer. + + Errors in servers configuration. + servers error + Even when disabled in the conversation. 即使在对话中被禁用。 @@ -3317,11 +3408,27 @@ This is your own one-time link! 修复群组成员不支持的问题 No comment provided by engineer. + + For chat profile %@: + servers error + For console 用于控制台 No comment provided by engineer. + + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + No comment provided by engineer. + + + For private routing + No comment provided by engineer. + + + For social media + No comment provided by engineer. + Forward 转发 @@ -3626,9 +3733,12 @@ Error: %2$@ SimpleX的工作原理 No comment provided by engineer. - - How it works - 工作原理 + + How it affects privacy + No comment provided by engineer. + + + How it helps privacy No comment provided by engineer. @@ -3700,8 +3810,8 @@ Error: %2$@ 立即 No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam 不受垃圾和骚扰消息影响 No comment provided by engineer. @@ -3840,6 +3950,11 @@ More improvements are coming soon! 安装[用于终端的 SimpleX Chat](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. + + Instant + 即时 + No comment provided by engineer. + Instant push notifications will be hidden! @@ -3847,11 +3962,6 @@ More improvements are coming soon! No comment provided by engineer. - - Instantly - 即时 - No comment provided by engineer. - Interface 界面 @@ -3900,7 +4010,7 @@ More improvements are coming soon! Invalid server address! 无效的服务器地址! - No comment provided by engineer. + alert title Invalid status @@ -4028,7 +4138,7 @@ This is your link for group %@! Keep 保留 - No comment provided by engineer. + alert action Keep conversation @@ -4043,7 +4153,7 @@ This is your link for group %@! Keep unused invitation? 保留未使用的邀请吗? - No comment provided by engineer. + alert title Keep your connections @@ -4130,11 +4240,6 @@ This is your link for group %@! 实时消息 No comment provided by engineer. - - Local - 本地 - No comment provided by engineer. - Local name 本地名称 @@ -4155,11 +4260,6 @@ This is your link for group %@! 锁定模式 No comment provided by engineer. - - Make a private connection - 建立私密连接 - No comment provided by engineer. - Make one message disappear 使一条消息消失 @@ -4170,21 +4270,11 @@ This is your link for group %@! 将个人资料设为私密! No comment provided by engineer. - - Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - 请确保 %@服 务器地址格式正确,每行一个地址并且不重复 (%@)。 - No comment provided by engineer. - Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. 确保 WebRTC ICE 服务器地址格式正确、每行分开且不重复。 No comment provided by engineer. - - Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - 许多人问: *如果SimpleX没有用户标识符,它怎么传递信息?* - No comment provided by engineer. - Mark deleted for everyone 标记为所有人已删除 @@ -4463,6 +4553,10 @@ This is your link for group %@! 更可靠的网络连接。 No comment provided by engineer. + + More reliable notifications + No comment provided by engineer. + Most likely this connection is deleted. 此连接很可能已被删除。 @@ -4498,6 +4592,10 @@ This is your link for group %@! 网络连接 No comment provided by engineer. + + Network decentralization + No comment provided by engineer. + Network issues - message expired after many attempts to send it. 网络问题 - 消息在多次尝试发送后过期。 @@ -4508,6 +4606,10 @@ This is your link for group %@! 网络管理 No comment provided by engineer. + + Network operator + No comment provided by engineer. + Network settings 网络设置 @@ -4566,6 +4668,10 @@ This is your link for group %@! 新显示名 No comment provided by engineer. + + New events + notification + New in %@ %@ 的新内容 @@ -4591,6 +4697,10 @@ This is your link for group %@! 新密码…… No comment provided by engineer. + + New server + No comment provided by engineer. + No @@ -4646,6 +4756,14 @@ This is your link for group %@! 无信息,尝试重新加载 No comment provided by engineer. + + No media & file servers. + servers error + + + No message servers. + servers error + No network connection 无网络连接 @@ -4664,11 +4782,37 @@ This is your link for group %@! 没有录制语音消息的权限 No comment provided by engineer. + + No push server + 本地 + No comment provided by engineer. + No received or sent files 未收到或发送文件 No comment provided by engineer. + + No servers for private message routing. + servers error + + + No servers to receive files. + servers error + + + No servers to receive messages. + servers error + + + No servers to send files. + servers error + + + No user identifiers. + 没有用户标识符。 + No comment provided by engineer. + Not compatible! 不兼容! @@ -4693,6 +4837,10 @@ This is your link for group %@! 通知被禁用! No comment provided by engineer. + + Notifications privacy + No comment provided by engineer. + Now admins can: - delete members' messages. @@ -4751,8 +4899,8 @@ Requires compatible VPN. 将不会使用 Onion 主机。 No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. 只有客户端设备存储用户资料、联系人、群组和**双层端到端加密**发送的消息。 No comment provided by engineer. @@ -4836,6 +4984,10 @@ Requires compatible VPN. 打开设置 No comment provided by engineer. + + Open changes + No comment provided by engineer. + Open chat 打开聊天 @@ -4846,6 +4998,10 @@ Requires compatible VPN. 打开聊天控制台 authentication reason + + Open conditions + No comment provided by engineer. + Open group 打开群 @@ -4856,26 +5012,19 @@ Requires compatible VPN. 打开迁移到另一台设备 authentication reason - - Open server settings - 打开服务器设置 - No comment provided by engineer. - - - Open user profiles - 打开用户个人资料 - authentication reason - - - Open-source protocol and code – anybody can run the servers. - 开源协议和代码——任何人都可以运行服务器。 - No comment provided by engineer. - Opening app… 正在打开应用程序… No comment provided by engineer. + + Operator + No comment provided by engineer. + + + Operator server + alert title + Or paste archive link 或粘贴存档链接 @@ -4896,16 +5045,15 @@ Requires compatible VPN. 或者显示此码 No comment provided by engineer. + + Or to share privately + No comment provided by engineer. + Other 其他 No comment provided by engineer. - - Other %@ servers - 其他 %@ 服务器 - No comment provided by engineer. - Other file errors: %@ @@ -4985,13 +5133,8 @@ Requires compatible VPN. 待定 No comment provided by engineer. - - People can connect to you only via the links you share. - 人们只能通过您共享的链接与您建立联系。 - No comment provided by engineer. - - - Periodically + + Periodic 定期 No comment provided by engineer. @@ -5113,16 +5256,15 @@ Error: %@ 保留最后的消息草稿及其附件。 No comment provided by engineer. - - Preset server - 预设服务器 - No comment provided by engineer. - Preset server address 预设服务器地址 No comment provided by engineer. + + Preset servers + No comment provided by engineer. + Preview 预览 @@ -5201,7 +5343,7 @@ Error: %@ Profile update will be sent to your contacts. 个人资料更新将被发送给您的联系人。 - No comment provided by engineer. + alert message Prohibit audio/video calls. @@ -5294,6 +5436,10 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. + + Push Notifications + No comment provided by engineer. + Push notifications 推送通知 @@ -5334,26 +5480,21 @@ Enable in *Network & servers* settings. 阅读更多 No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - 在 [用户指南](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address) 中阅读更多内容。 - No comment provided by engineer. - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). 阅读更多[User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)。 No comment provided by engineer. + + Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). + 在 [用户指南](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses) 中阅读更多内容。 + No comment provided by engineer. + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). 在 [用户指南](https://simplex.chat/docs/guide/readme.html#connect-to-friends) 中阅读更多内容。 No comment provided by engineer. - - Read more in our GitHub repository. - 在我们的 GitHub 仓库中阅读更多内容。 - No comment provided by engineer. - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). 在我们的 [GitHub 仓库](https://github.com/simplex-chat/simplex-chat#readme) 中阅读更多信息。 @@ -5669,6 +5810,14 @@ Enable in *Network & servers* settings. 揭示 chat item action + + Review conditions + No comment provided by engineer. + + + Review later + No comment provided by engineer. + Revoke 撤销 @@ -5713,6 +5862,14 @@ Enable in *Network & servers* settings. 更安全的群组 No comment provided by engineer. + + Same conditions will apply to operator **%@**. + No comment provided by engineer. + + + Same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + Save 保存 @@ -5782,7 +5939,7 @@ Enable in *Network & servers* settings. Save servers? 保存服务器? - No comment provided by engineer. + alert title Save welcome message? @@ -5991,11 +6148,6 @@ Enable in *Network & servers* settings. 发送通知 No comment provided by engineer. - - Send notifications: - 发送通知: - No comment provided by engineer. - Send questions and ideas 发送问题和想法 @@ -6120,6 +6272,10 @@ Enable in *Network & servers* settings. Server No comment provided by engineer. + + Server added to operator %@. + alert message + Server address 服务器地址 @@ -6135,6 +6291,18 @@ Enable in *Network & servers* settings. 服务器地址与网络设置不兼容:%@。 No comment provided by engineer. + + Server operator changed. + alert title + + + Server operators + No comment provided by engineer. + + + Server protocol changed. + alert title + Server requires authorization to create queues, check password 服务器需要授权才能创建队列,检查密码 @@ -6252,22 +6420,35 @@ Enable in *Network & servers* settings. Share 分享 - chat item action + alert action + chat item action Share 1-time link 分享一次性链接 No comment provided by engineer. + + Share 1-time link with a friend + No comment provided by engineer. + + + Share SimpleX address on social media. + No comment provided by engineer. + Share address 分享地址 No comment provided by engineer. + + Share address publicly + No comment provided by engineer. + Share address with contacts? 与联系人分享地址? - No comment provided by engineer. + alert title Share from other apps. @@ -6383,6 +6564,14 @@ Enable in *Network & servers* settings. SimpleX 地址 No comment provided by engineer. + + SimpleX address and 1-time links are safe to share via any messenger. + No comment provided by engineer. + + + SimpleX address or 1-time link? + No comment provided by engineer. + SimpleX contact address SimpleX 联系地址 @@ -6471,6 +6660,11 @@ Enable in *Network & servers* settings. 导入过程中出现一些非致命错误: No comment provided by engineer. + + Some servers failed the test: +%@ + alert message + Somebody 某人 @@ -6554,12 +6748,12 @@ Enable in *Network & servers* settings. Stop sharing 停止分享 - No comment provided by engineer. + alert action Stop sharing address? 停止分享地址? - No comment provided by engineer. + alert title Stopping chat @@ -6706,7 +6900,7 @@ Enable in *Network & servers* settings. Tests failed! 测试失败! - No comment provided by engineer. + alert title Thank you for installing SimpleX Chat! @@ -6723,11 +6917,6 @@ Enable in *Network & servers* settings. 感谢用户——通过 Weblate 做出贡献! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. - 第一个没有任何用户标识符的平台 - 隐私设计. - No comment provided by engineer. - The ID of the next message is incorrect (less or equal to the previous). It can happen because of some bug or when the connection is compromised. @@ -6740,6 +6929,10 @@ It can happen because of some bug or when the connection is compromised.该应用可以在您收到消息或联系人请求时通知您——请打开设置以启用通知。 No comment provided by engineer. + + The app protects your privacy by using different operators in each conversation. + No comment provided by engineer. + The app will ask to confirm downloads from unknown file servers (except .onion). 该应用程序将要求确认从未知文件服务器(.onion 除外)下载。 @@ -6755,6 +6948,10 @@ It can happen because of some bug or when the connection is compromised.您扫描的码不是 SimpleX 链接的二维码。 No comment provided by engineer. + + The connection reached the limit of undelivered messages, your contact may be offline. + No comment provided by engineer. + The connection you accepted will be cancelled! 您接受的连接将被取消! @@ -6775,6 +6972,11 @@ It can happen because of some bug or when the connection is compromised.加密正在运行,不需要新的加密协议。这可能会导致连接错误! No comment provided by engineer. + + The future of messaging + 下一代私密通讯软件 + No comment provided by engineer. + The hash of the previous message is different. 上一条消息的散列不同。 @@ -6800,11 +7002,6 @@ It can happen because of some bug or when the connection is compromised.对于所有成员,这些消息将被标记为已审核。 No comment provided by engineer. - - The next generation of private messaging - 下一代私密通讯软件 - No comment provided by engineer. - The old database was not removed during the migration, it can be deleted. 旧数据库在迁移过程中没有被移除,可以删除。 @@ -6815,6 +7012,10 @@ It can happen because of some bug or when the connection is compromised.该资料仅与您的联系人共享。 No comment provided by engineer. + + The second preset operator in the app! + No comment provided by engineer. + The second tick we missed! ✅ 我们错过的第二个"√"!✅ @@ -6830,6 +7031,10 @@ It can happen because of some bug or when the connection is compromised.您当前聊天资料 **%@** 的新连接服务器。 No comment provided by engineer. + + The servers for new files of your current chat profile **%@**. + No comment provided by engineer. + The text you pasted is not a SimpleX link. 您粘贴的文本不是 SimpleX 链接。 @@ -6844,6 +7049,10 @@ It can happen because of some bug or when the connection is compromised.主题 No comment provided by engineer. + + These conditions will also apply for: **%@**. + No comment provided by engineer. + These settings are for your current profile **%@**. 这些设置适用于您当前的配置文件 **%@**。 @@ -6944,9 +7153,8 @@ It can happen because of some bug or when the connection is compromised.建立新连接 No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - 为了保护隐私,SimpleX使用针对消息队列的标识符,而不是所有其他平台使用的用户ID,每个联系人都有独立的标识符。 + + To protect against your link being replaced, you can compare contact security codes. No comment provided by engineer. @@ -6966,6 +7174,15 @@ You will be prompted to complete authentication before this feature is enabled.< 在启用此功能之前,系统将提示您完成身份验证。 No comment provided by engineer. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + 为了保护隐私,SimpleX使用针对消息队列的标识符,而不是所有其他平台使用的用户ID,每个联系人都有独立的标识符。 + No comment provided by engineer. + + + To receive + No comment provided by engineer. + To record speech please grant permission to use Microphone. No comment provided by engineer. @@ -6984,11 +7201,19 @@ You will be prompted to complete authentication before this feature is enabled.< 要显示您的隐藏的个人资料,请在**您的聊天个人资料**页面的搜索字段中输入完整密码。 No comment provided by engineer. + + To send + No comment provided by engineer. + To support instant push notifications the chat database has to be migrated. 为了支持即时推送通知,聊天数据库必须被迁移。 No comment provided by engineer. + + To use the servers of **%@**, accept conditions of use. + No comment provided by engineer. + To verify end-to-end encryption with your contact compare (or scan) the code on your devices. 要与您的联系人验证端到端加密,请比较(或扫描)您设备上的代码。 @@ -7079,6 +7304,10 @@ You will be prompted to complete authentication before this feature is enabled.< 解封成员吗? No comment provided by engineer. + + Undelivered messages + No comment provided by engineer. + Unexpected migration state 未预料的迁移状态 @@ -7236,6 +7465,10 @@ To connect, please ask your contact to create another connection link and check 正在上传存档 No comment provided by engineer. + + Use %@ + No comment provided by engineer. + Use .onion hosts 使用 .onion 主机 @@ -7260,6 +7493,14 @@ To connect, please ask your contact to create another connection link and check 使用当前配置文件 No comment provided by engineer. + + Use for files + No comment provided by engineer. + + + Use for messages + No comment provided by engineer. + Use for new connections 用于新连接 @@ -7300,6 +7541,10 @@ To connect, please ask your contact to create another connection link and check 使用服务器 No comment provided by engineer. + + Use servers + No comment provided by engineer. + Use the app while in the call. 通话时使用本应用. @@ -7389,11 +7634,19 @@ To connect, please ask your contact to create another connection link and check 最大 1gb 的视频和文件 No comment provided by engineer. + + View conditions + No comment provided by engineer. + View security code 查看安全码 No comment provided by engineer. + + View updated conditions + No comment provided by engineer. + Visible history 可见的历史 @@ -7504,9 +7757,8 @@ To connect, please ask your contact to create another connection link and check 连接音频和视频通话时。 No comment provided by engineer. - - When people request to connect, you can accept or reject it. - 当人们请求连接时,您可以接受或拒绝它。 + + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. No comment provided by engineer. @@ -7666,6 +7918,18 @@ Repeat join request? 您可以在外观设置中更改它。 No comment provided by engineer. + + You can configure operators in Network & servers settings. + No comment provided by engineer. + + + You can configure servers via settings. + No comment provided by engineer. + + + You can create it in user picker. + No comment provided by engineer. + You can create it later 您可以以后创建它 @@ -7706,6 +7970,10 @@ Repeat join request? 您可以从存档的联系人向%@发送消息。 No comment provided by engineer. + + You can set connection name, to remember who the link was shared with. + No comment provided by engineer. + You can set lock screen notification preview via settings. 您可以通过设置来设置锁屏通知预览。 @@ -7721,11 +7989,6 @@ Repeat join request? 您可以与您的联系人分享该地址,让他们与 **%@** 联系。 No comment provided by engineer. - - You can share your address as a link or QR code - anybody can connect to you. - 您可以将您的地址作为链接或二维码共享——任何人都可以连接到您。 - No comment provided by engineer. - You can start chat via app Settings / Database or by restarting the app 您可以通过应用程序设置/数据库或重新启动应用程序开始聊天 @@ -7749,23 +8012,23 @@ Repeat join request? You can view invitation link again in connection details. 您可以在连接详情中再次查看邀请链接。 - No comment provided by engineer. + alert message You can't send messages! 您无法发送消息! No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - 您可以控制接收信息使用的服务器,您的联系人则使用您发送信息时所使用的服务器。 - No comment provided by engineer. - You could not be verified; please try again. 您的身份无法验证,请再试一次。 No comment provided by engineer. + + You decide who can connect. + 你决定谁可以连接。 + No comment provided by engineer. + You have already requested connection via this address! 你已经请求通过此地址进行连接! @@ -7888,11 +8151,6 @@ Repeat connection request? 您正在为该群组使用隐身个人资料——为防止共享您的主要个人资料,不允许邀请联系人 No comment provided by engineer. - - Your %@ servers - 您的 %@ 服务器 - No comment provided by engineer. - Your ICE servers 您的 ICE 服务器 @@ -7908,11 +8166,6 @@ Repeat connection request? 您的 SimpleX 地址 No comment provided by engineer. - - Your XFTP servers - 您的 XFTP 服务器 - No comment provided by engineer. - Your calls 您的通话 @@ -8009,16 +8262,15 @@ Repeat connection request? 您的随机资料 No comment provided by engineer. - - Your server - 您的服务器 - No comment provided by engineer. - Your server address 您的服务器地址 No comment provided by engineer. + + Your servers + No comment provided by engineer. + Your settings 您的设置 @@ -8439,6 +8691,10 @@ Repeat connection request? 过期 No comment provided by engineer. + + for better metadata privacy. + No comment provided by engineer. + forwarded 已转发 @@ -9061,6 +9317,33 @@ last received msg: %2$@ + +
+ +
+ + + %d new events + notification body + + + From: %@ + notification body + + + New events + notification + + + New messages + notification + + + New messages in %d chats + notification body + + +
diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Source Contents/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff index 2b8649935c..1c1ae53673 100644 --- a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff @@ -187,23 +187,18 @@ ) No comment provided by engineer. - - **Add new contact**: to create your one-time QR Code or link for your contact. - **新增新的聯絡人**:建立一次性二維碼或連結連接聯絡人。 - No comment provided by engineer. - **Create link / QR code** for your contact to use. **建立連結 / 二維碼** 讓你的聯絡人使用。 No comment provided by engineer. - - **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + + **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. **更有私隱**:每20分鐘會檢查一次訊息。裝置權杖與 SimpleX Chat 伺服器分享中,但是不包括你的聯絡人和訊息資料。 No comment provided by engineer. - - **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + + **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. **最有私隱**:不使用 SimpleX Chat 通知服務器,在後台定期檢查訊息(取決於你使用應用程序的頻率)。 No comment provided by engineer. @@ -217,8 +212,8 @@ **請注意**:如果你忘記了密碼你將不能再次復原或更改密碼。 No comment provided by engineer. - - **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + + **Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from. **建議**:裝置權杖和通知都會傳送去 SimpeleX Chat 的通知伺服器,但是不包括訊息內容、大小或傳送者資料。 No comment provided by engineer. @@ -1747,8 +1742,8 @@ 下載圖片需要傳送者上線的時候才能下載圖片,請等待對方上線! No comment provided by engineer. - - Immune to spam and abuse + + Immune to spam 不受垃圾郵件和濫用行為影響 No comment provided by engineer. @@ -2217,8 +2212,8 @@ We will be adding server redundancy to prevent lost messages. Onion 主機不會啟用。 No comment provided by engineer. - - Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + + Only client devices store user profiles, contacts, groups, and messages. 只有客戶端裝置才會儲存你的個人檔案、聯絡人,群組,所有訊息都會經過**兩層的端對端加密**。 No comment provided by engineer. @@ -2277,8 +2272,8 @@ We will be adding server redundancy to prevent lost messages. 使用終端機開啟對話 authentication reason - - Open-source protocol and code – anybody can run the servers. + + Anybody can host servers. 開放源碼協議和程式碼 – 任何人也可以運行伺服器。 No comment provided by engineer. @@ -2317,8 +2312,8 @@ We will be adding server redundancy to prevent lost messages. 將你接收到的連結貼上至下面的框內,以開始你與你的聯絡人對話。 No comment provided by engineer. - - People can connect to you only via the links you share. + + You decide who can connect. 人們只能在你分享了連結後,才能和你連接。 No comment provided by engineer. @@ -3010,8 +3005,8 @@ We will be adding server redundancy to prevent lost messages. 感謝你安裝SimpleX Chat! No comment provided by engineer. - - The 1st platform without any user identifiers – private by design. + + No user identifiers. 第一個沒有任何用戶識別符的通訊平台 – 以私隱為設計。 No comment provided by engineer. @@ -3049,8 +3044,8 @@ We will be adding server redundancy to prevent lost messages. The microphone does not work when the app is in the background. No comment provided by engineer. - - The next generation of private messaging + + The future of messaging 新一代的私密訊息平台 No comment provided by engineer. @@ -3118,8 +3113,8 @@ We will be adding server redundancy to prevent lost messages. To prevent the call interruption, enable Do Not Disturb mode. No comment provided by engineer. - - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + + To protect your privacy, SimpleX uses separate IDs for each of your contacts. 為了保護隱私,而不像是其他平台般需要提取和存儲用戶的 IDs 資料,SimpleX 平台有自家佇列的標識符,這對於你的每個聯絡人也是獨一無二的。 No comment provided by engineer. @@ -3455,11 +3450,6 @@ To connect, please ask your contact to create another connection link and check 你可以使用 Markdown 語法以更清楚標明訊息: No comment provided by engineer. - - You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. - 你可以控制通過哪一個伺服器 **來接收** 你的聯絡人訊息 – 這些伺服器用來接收他們傳送給你的訊息。 - No comment provided by engineer. - You could not be verified; please try again. 你未能通過認證;請再試一次。 diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index ff8a76828c..ccfd9a7b98 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -65,10 +65,7 @@ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Звезда в GitHub](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Добави контакт**: за създаване на нов линк или свързване чрез получен линк за връзка."; - -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Добави нов контакт**: за да създадете своя еднократен QR код или линк за вашия контакт."; +"**Create 1-time link**: to create and share a new invitation link." = "**Добави контакт**: за създаване на нов линк."; /* No comment provided by engineer. */ "**Create group**: to create a new group." = "**Създай група**: за създаване на нова група."; @@ -80,10 +77,10 @@ "**e2e encrypted** video call" = "**e2e криптирано** видео разговор"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**По поверително**: проверявайте новите съобщения на всеки 20 минути. Токенът на устройството се споделя със сървъра за чат SimpleX, но не и колко контакти или съобщения имате."; +"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**По поверително**: проверявайте новите съобщения на всеки 20 минути. Токенът на устройството се споделя със сървъра за чат SimpleX, но не и колко контакти или съобщения имате."; /* No comment provided by engineer. */ -"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Най-поверително**: не използвайте сървъра за известия SimpleX Chat, периодично проверявайте съобщенията във фонов режим (зависи от това колко често използвате приложението)."; +"**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." = "**Най-поверително**: не използвайте сървъра за известия SimpleX Chat, периодично проверявайте съобщенията във фонов режим (зависи от това колко често използвате приложението)."; /* No comment provided by engineer. */ "**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Моля, обърнете внимание**: използването на една и съща база данни на две устройства ще наруши декриптирането на съобщенията от вашите връзки като защита на сигурността."; @@ -92,7 +89,7 @@ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Моля, обърнете внимание**: НЯМА да можете да възстановите или промените паролата, ако я загубите."; /* No comment provided by engineer. */ -"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Препоръчително**: токенът на устройството и известията се изпращат до сървъра за уведомяване на SimpleX Chat, но не и съдържанието, размерът на съобщението или от кого е."; +"**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Препоръчително**: токенът на устройството и известията се изпращат до сървъра за уведомяване на SimpleX Chat, но не и съдържанието, размерът на съобщението или от кого е."; /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Внимание**: Незабавните push известия изискват парола, запазена в Keychain."; @@ -2075,7 +2072,7 @@ "Immediately" = "Веднага"; /* No comment provided by engineer. */ -"Immune to spam and abuse" = "Защитен от спам и злоупотреби"; +"Immune to spam" = "Защитен от спам и злоупотреби"; /* No comment provided by engineer. */ "Import" = "Импортиране"; @@ -2168,7 +2165,7 @@ "Instant push notifications will be hidden!\n" = "Незабавните push известия ще бъдат скрити!\n"; /* No comment provided by engineer. */ -"Instantly" = "Мигновено"; +"Instant" = "Мигновено"; /* No comment provided by engineer. */ "Interface" = "Интерфейс"; @@ -2363,7 +2360,7 @@ "Live messages" = "Съобщения на живо"; /* No comment provided by engineer. */ -"Local" = "Локално"; +"No push server" = "Локално"; /* No comment provided by engineer. */ "Local name" = "Локално име"; @@ -2716,7 +2713,7 @@ "Onion hosts will not be used." = "Няма се използват Onion хостове."; /* No comment provided by engineer. */ -"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Само потребителските устройства съхраняват потребителски профили, контакти, групи и съобщения, изпратени с **двуслойно криптиране от край до край**."; +"Only client devices store user profiles, contacts, groups, and messages." = "Само потребителските устройства съхраняват потребителски профили, контакти, групи и съобщения, изпратени с **двуслойно криптиране от край до край**."; /* No comment provided by engineer. */ "Only group owners can change group preferences." = "Само собствениците на групата могат да променят груповите настройки."; @@ -2779,7 +2776,7 @@ "Open user profiles" = "Отвори потребителските профили"; /* No comment provided by engineer. */ -"Open-source protocol and code – anybody can run the servers." = "Протокол и код с отворен код – всеки може да оперира собствени сървъри."; +"Anybody can host servers." = "Протокол и код с отворен код – всеки може да оперира собствени сървъри."; /* No comment provided by engineer. */ "Opening app…" = "Приложението се отваря…"; @@ -2842,10 +2839,10 @@ "peer-to-peer" = "peer-to-peer"; /* No comment provided by engineer. */ -"People can connect to you only via the links you share." = "Хората могат да се свържат с вас само чрез ликовете, които споделяте."; +"You decide who can connect." = "Хората могат да се свържат с вас само чрез ликовете, които споделяте."; /* No comment provided by engineer. */ -"Periodically" = "Периодично"; +"Periodic" = "Периодично"; /* message decrypt error item */ "Permanent decryption error" = "Постоянна грешка при декриптиране"; @@ -3010,7 +3007,7 @@ "Read more" = "Прочетете още"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; @@ -3693,7 +3690,7 @@ "Thanks to the users – contribute via Weblate!" = "Благодарение на потребителите – допринесете през Weblate!"; /* No comment provided by engineer. */ -"The 1st platform without any user identifiers – private by design." = "Първата платформа без никакви потребителски идентификатори – поверителна по дизайн."; +"No user identifiers." = "Първата платформа без никакви потребителски идентификатори – поверителна по дизайн."; /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Приложението може да ви уведоми, когато получите съобщения или заявки за контакт - моля, отворете настройките, за да активирате."; @@ -3729,7 +3726,7 @@ "The message will be marked as moderated for all members." = "Съобщението ще бъде маркирано като модерирано за всички членове."; /* No comment provided by engineer. */ -"The next generation of private messaging" = "Ново поколение поверителни съобщения"; +"The future of messaging" = "Ново поколение поверителни съобщения"; /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Старата база данни не бе премахната по време на миграцията, тя може да бъде изтрита."; @@ -3807,7 +3804,7 @@ "To make a new connection" = "За да направите нова връзка"; /* No comment provided by engineer. */ -"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "За да се защити поверителността, вместо потребителски идентификатори, използвани от всички други платформи, SimpleX има идентификатори за опашки от съобщения, отделни за всеки от вашите контакти."; +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "За да се защити поверителността, вместо потребителски идентификатори, използвани от всички други платформи, SimpleX има идентификатори за опашки от съобщения, отделни за всеки от вашите контакти."; /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "За да не се разкрива часовата зона, файловете с изображения/глас използват UTC."; @@ -4280,9 +4277,6 @@ /* snd group event chat item */ "you changed role of %@ to %@" = "променихте ролята на %1$@ на %2$@"; -/* No comment provided by engineer. */ -"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "Вие контролирате през кой сървър(и) **да получавате** съобщенията, вашите контакти – сървърите, които използвате, за да им изпращате съобщения."; - /* No comment provided by engineer. */ "You could not be verified; please try again." = "Не можахте да бъдете потвърдени; Моля, опитайте отново."; diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index 618cd90aba..a00adef700 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -55,9 +55,6 @@ /* No comment provided by engineer. */ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Hvězda na GitHubu](https://github.com/simplex-chat/simplex-chat)"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Přidat nový kontakt**: pro vytvoření jednorázového QR kódu nebo odkazu pro váš kontakt."; - /* No comment provided by engineer. */ "**e2e encrypted** audio call" = "**e2e šifrovaný** audio hovor"; @@ -65,16 +62,16 @@ "**e2e encrypted** video call" = "**e2e šifrovaný** videohovor"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**Soukromější**: kontrolovat nové zprávy každých 20 minut. Token zařízení je sdílen se serverem SimpleX Chat, ale ne kolik máte kontaktů nebo zpráv."; +"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**Soukromější**: kontrolovat nové zprávy každých 20 minut. Token zařízení je sdílen se serverem SimpleX Chat, ale ne kolik máte kontaktů nebo zpráv."; /* No comment provided by engineer. */ -"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Nejsoukromější**: nepoužívejte server oznámení SimpleX Chat, pravidelně kontrolujte zprávy na pozadí (závisí na tom, jak často aplikaci používáte)."; +"**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." = "**Nejsoukromější**: nepoužívejte server oznámení SimpleX Chat, pravidelně kontrolujte zprávy na pozadí (závisí na tom, jak často aplikaci používáte)."; /* No comment provided by engineer. */ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Upozornění**: Pokud heslo ztratíte, NEBUDETE jej moci obnovit ani změnit."; /* No comment provided by engineer. */ -"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Doporučeno**: Token zařízení a oznámení se odesílají na oznamovací server SimpleX Chat, ale nikoli obsah, velikost nebo od koho jsou zprávy."; +"**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Doporučeno**: Token zařízení a oznámení se odesílají na oznamovací server SimpleX Chat, ale nikoli obsah, velikost nebo od koho jsou zprávy."; /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Upozornění**: Okamžitě doručovaná oznámení vyžadují přístupové heslo uložené v Klíčence."; @@ -1702,7 +1699,7 @@ "Immediately" = "Ihned"; /* No comment provided by engineer. */ -"Immune to spam and abuse" = "Odolná vůči spamu a zneužití"; +"Immune to spam" = "Odolná vůči spamu a zneužití"; /* No comment provided by engineer. */ "Import" = "Import"; @@ -1774,7 +1771,7 @@ "Instant push notifications will be hidden!\n" = "Okamžitá oznámení budou skryta!\n"; /* No comment provided by engineer. */ -"Instantly" = "Okamžitě"; +"Instant" = "Okamžitě"; /* No comment provided by engineer. */ "Interface" = "Rozhranní"; @@ -1921,7 +1918,7 @@ "Live messages" = "Živé zprávy"; /* No comment provided by engineer. */ -"Local" = "Místní"; +"No push server" = "Místní"; /* No comment provided by engineer. */ "Local name" = "Místní název"; @@ -2214,7 +2211,7 @@ "Onion hosts will not be used." = "Onion hostitelé nebudou použiti."; /* No comment provided by engineer. */ -"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Pouze klientská zařízení ukládají uživatelské profily, kontakty, skupiny a zprávy odeslané s **2vrstvým šifrováním typu end-to-end**."; +"Only client devices store user profiles, contacts, groups, and messages." = "Pouze klientská zařízení ukládají uživatelské profily, kontakty, skupiny a zprávy odeslané s **2vrstvým šifrováním typu end-to-end**."; /* No comment provided by engineer. */ "Only group owners can change group preferences." = "Předvolby skupiny mohou měnit pouze vlastníci skupiny."; @@ -2271,7 +2268,7 @@ "Open user profiles" = "Otevřít uživatelské profily"; /* No comment provided by engineer. */ -"Open-source protocol and code – anybody can run the servers." = "Protokol a kód s otevřeným zdrojovým kódem - servery může provozovat kdokoli."; +"Anybody can host servers." = "Servery může provozovat kdokoli."; /* member role */ "owner" = "vlastník"; @@ -2301,10 +2298,10 @@ "peer-to-peer" = "peer-to-peer"; /* No comment provided by engineer. */ -"People can connect to you only via the links you share." = "Lidé se s vámi mohou spojit pouze prostřednictvím odkazů, které sdílíte."; +"You decide who can connect." = "Lidé se s vámi mohou spojit pouze prostřednictvím odkazu, který sdílíte."; /* No comment provided by engineer. */ -"Periodically" = "Pravidelně"; +"Periodic" = "Pravidelně"; /* message decrypt error item */ "Permanent decryption error" = "Chyba dešifrování"; @@ -2442,7 +2439,7 @@ "Read more" = "Přečíst více"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "Další informace naleznete v [Uživatelské příručce](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Další informace naleznete v [Uživatelské příručce](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Přečtěte si více v [Uživatelské příručce](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; @@ -3011,7 +3008,7 @@ "Thanks to the users – contribute via Weblate!" = "Díky uživatelům - přispívejte prostřednictvím Weblate!"; /* No comment provided by engineer. */ -"The 1st platform without any user identifiers – private by design." = "1. Platforma bez identifikátorů uživatelů - soukromá už od záměru."; +"No user identifiers." = "Bez uživatelských identifikátorů"; /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Aplikace vás může upozornit na přijaté zprávy nebo žádosti o kontakt - povolte to v nastavení."; @@ -3044,7 +3041,7 @@ "The message will be marked as moderated for all members." = "Zpráva bude pro všechny členy označena jako moderovaná."; /* No comment provided by engineer. */ -"The next generation of private messaging" = "Nová generace soukromých zpráv"; +"The future of messaging" = "Nová generace soukromých zpráv"; /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Stará databáze nebyla během přenášení odstraněna, lze ji smazat."; @@ -3098,7 +3095,7 @@ "To make a new connection" = "Vytvoření nového připojení"; /* No comment provided by engineer. */ -"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "Pro ochranu soukromí namísto ID uživatelů používaných všemi ostatními platformami má SimpleX identifikátory pro fronty zpráv, oddělené pro každý z vašich kontaktů."; +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Pro ochranu soukromí namísto ID uživatelů používaných všemi ostatními platformami má SimpleX identifikátory pro fronty zpráv, oddělené pro každý z vašich kontaktů."; /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "K ochraně časového pásma používají obrazové/hlasové soubory UTC."; @@ -3427,9 +3424,6 @@ /* snd group event chat item */ "you changed role of %@ to %@" = "změnili jste roli z %1$@ na %2$@"; -/* No comment provided by engineer. */ -"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "Sami řídíte, přes který server(y) **přijímat** zprávy, své kontakty – servery, které používáte k odesílání zpráv."; - /* No comment provided by engineer. */ "You could not be verified; please try again." = "Nemohli jste být ověřeni; Zkuste to prosím znovu."; diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 7334314c3e..f526eaf7e1 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -65,10 +65,7 @@ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Stern auf GitHub vergeben](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Kontakt hinzufügen**: Um einen neuen Einladungslink zu erstellen oder eine Verbindung über einen Link herzustellen, den Sie erhalten haben."; - -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Neuen Kontakt hinzufügen**: Um einen Einmal-QR-Code oder -Link für Ihren Kontakt zu erzeugen."; +"**Create 1-time link**: to create and share a new invitation link." = "**Kontakt hinzufügen**: Um einen neuen Einladungslink zu erstellen."; /* No comment provided by engineer. */ "**Create group**: to create a new group." = "**Gruppe erstellen**: Um eine neue Gruppe zu erstellen."; @@ -80,10 +77,10 @@ "**e2e encrypted** video call" = "**E2E-verschlüsselter** Videoanruf"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**Mehr Privatsphäre**: Es wird alle 20 Minuten auf neue Nachrichten geprüft. Nur Ihr Geräte-Token wird dem SimpleX-Chat-Server mitgeteilt, aber nicht wie viele Kontakte Sie haben oder welche Nachrichten Sie empfangen."; +"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**Mehr Privatsphäre**: Es wird alle 20 Minuten auf neue Nachrichten geprüft. Nur Ihr Geräte-Token wird dem SimpleX-Chat-Server mitgeteilt, aber nicht wie viele Kontakte Sie haben oder welche Nachrichten Sie empfangen."; /* No comment provided by engineer. */ -"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Beste Privatsphäre**: Es wird kein SimpleX-Chat-Benachrichtigungs-Server genutzt, Nachrichten werden in periodischen Abständen im Hintergrund geprüft (dies hängt davon ab, wie häufig Sie die App nutzen)."; +"**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." = "**Beste Privatsphäre**: Es wird kein SimpleX-Chat-Benachrichtigungs-Server genutzt, Nachrichten werden in periodischen Abständen im Hintergrund geprüft (dies hängt davon ab, wie häufig Sie die App nutzen)."; /* No comment provided by engineer. */ "**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Bitte beachten Sie**: Aus Sicherheitsgründen wird die Nachrichtenentschlüsselung Ihrer Verbindungen abgebrochen, wenn Sie die gleiche Datenbank auf zwei Geräten nutzen."; @@ -92,7 +89,7 @@ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Bitte beachten Sie**: Das Passwort kann NICHT wiederhergestellt oder geändert werden, wenn Sie es vergessen haben oder verlieren."; /* No comment provided by engineer. */ -"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Empfohlen**: Nur Ihr Geräte-Token und ihre Benachrichtigungen werden an den SimpleX-Chat-Benachrichtigungs-Server gesendet, aber weder der Nachrichteninhalt noch deren Größe oder von wem sie gesendet wurde."; +"**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Empfohlen**: Nur Ihr Geräte-Token und ihre Benachrichtigungen werden an den SimpleX-Chat-Benachrichtigungs-Server gesendet, aber weder der Nachrichteninhalt noch deren Größe oder von wem sie gesendet wurde."; /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Warnung**: Sofortige Push-Benachrichtigungen erfordern die Eingabe eines Passworts, welches in Ihrem Schlüsselbund gespeichert ist."; @@ -2474,7 +2471,7 @@ "Immediately" = "Sofort"; /* No comment provided by engineer. */ -"Immune to spam and abuse" = "Immun gegen Spam und Missbrauch"; +"Immune to spam" = "Immun gegen Spam und Missbrauch"; /* No comment provided by engineer. */ "Import" = "Importieren"; @@ -2576,7 +2573,7 @@ "Instant push notifications will be hidden!\n" = "Sofortige Push-Benachrichtigungen werden verborgen!\n"; /* No comment provided by engineer. */ -"Instantly" = "Sofort"; +"Instant" = "Sofort"; /* No comment provided by engineer. */ "Interface" = "Schnittstelle"; @@ -2786,7 +2783,7 @@ "Live messages" = "Live Nachrichten"; /* No comment provided by engineer. */ -"Local" = "Lokal"; +"No push server" = "Lokal"; /* No comment provided by engineer. */ "Local name" = "Lokaler Name"; @@ -3226,7 +3223,7 @@ "Onion hosts will not be used." = "Onion-Hosts werden nicht verwendet."; /* No comment provided by engineer. */ -"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Nur die Endgeräte speichern die Benutzerprofile, Kontakte, Gruppen und Nachrichten, welche über eine **2-Schichten Ende-zu-Ende-Verschlüsselung** gesendet werden."; +"Only client devices store user profiles, contacts, groups, and messages." = "Nur die Endgeräte speichern die Benutzerprofile, Kontakte, Gruppen und Nachrichten, welche über eine **2-Schichten Ende-zu-Ende-Verschlüsselung** gesendet werden."; /* No comment provided by engineer. */ "Only delete conversation" = "Nur die Chat-Inhalte löschen"; @@ -3295,7 +3292,7 @@ "Open user profiles" = "Benutzerprofile öffnen"; /* No comment provided by engineer. */ -"Open-source protocol and code – anybody can run the servers." = "Open-Source-Protokoll und -Code – Jede Person kann ihre eigenen Server aufsetzen und nutzen."; +"Anybody can host servers." = "Jeder kann seine eigenen Server aufsetzen."; /* No comment provided by engineer. */ "Opening app…" = "App wird geöffnet…"; @@ -3376,10 +3373,10 @@ "Pending" = "Ausstehend"; /* No comment provided by engineer. */ -"People can connect to you only via the links you share." = "Verbindungen mit Kontakten sind nur über Links möglich, die Sie oder Ihre Kontakte untereinander teilen."; +"You decide who can connect." = "Sie entscheiden, wer sich mit Ihnen verbinden kann."; /* No comment provided by engineer. */ -"Periodically" = "Periodisch"; +"Periodic" = "Periodisch"; /* message decrypt error item */ "Permanent decryption error" = "Entschlüsselungsfehler"; @@ -3592,7 +3589,7 @@ "Read more" = "Mehr erfahren"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "Mehr dazu in der [Benutzeranleitung](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address) lesen."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Mehr dazu in der [Benutzeranleitung](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses) lesen."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Lesen Sie mehr dazu im [Benutzerhandbuch](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; @@ -4500,7 +4497,7 @@ "Thanks to the users – contribute via Weblate!" = "Dank der Nutzer - Tragen Sie per Weblate bei!"; /* No comment provided by engineer. */ -"The 1st platform without any user identifiers – private by design." = "Die erste Plattform ohne Benutzerkennungen – Privat per Design."; +"No user identifiers." = "Keine Benutzerkennungen."; /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Wenn sie Nachrichten oder Kontaktanfragen empfangen, kann Sie die App benachrichtigen - Um dies zu aktivieren, öffnen Sie bitte die Einstellungen."; @@ -4545,7 +4542,7 @@ "The messages will be marked as moderated for all members." = "Die Nachrichten werden für alle Mitglieder als moderiert gekennzeichnet werden."; /* No comment provided by engineer. */ -"The next generation of private messaging" = "Die nächste Generation von privatem Messaging"; +"The future of messaging" = "Die nächste Generation von privatem Messaging"; /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Die alte Datenbank wurde während der Migration nicht entfernt. Sie kann gelöscht werden."; @@ -4635,7 +4632,7 @@ "To make a new connection" = "Um eine Verbindung mit einem neuen Kontakt zu erstellen"; /* No comment provided by engineer. */ -"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "Zum Schutz Ihrer Privatsphäre verwendet SimpleX an Stelle von Benutzerkennungen, die von allen anderen Plattformen verwendet werden, Kennungen für Nachrichtenwarteschlangen, die für jeden Ihrer Kontakte individuell sind."; +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Zum Schutz Ihrer Privatsphäre verwendet SimpleX an Stelle von Benutzerkennungen, die von allen anderen Plattformen verwendet werden, Kennungen für Nachrichtenwarteschlangen, die für jeden Ihrer Kontakte individuell sind."; /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Bild- und Sprachdateinamen enthalten UTC, um Informationen zur Zeitzone zu schützen."; @@ -5210,9 +5207,6 @@ /* snd group event chat item */ "you changed role of %@ to %@" = "Sie haben die Rolle von %1$@ auf %2$@ geändert"; -/* No comment provided by engineer. */ -"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "Sie können selbst festlegen, über welche Server Sie Ihre Nachrichten **empfangen** und an Ihre Kontakte **senden** wollen."; - /* No comment provided by engineer. */ "You could not be verified; please try again." = "Sie konnten nicht überprüft werden; bitte versuchen Sie es erneut."; diff --git a/apps/ios/en.lproj/Localizable.strings b/apps/ios/en.lproj/Localizable.strings index cf485752ea..cb83427195 100644 --- a/apps/ios/en.lproj/Localizable.strings +++ b/apps/ios/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "_italic_" = "\\_italic_"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; - /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -27,4 +24,3 @@ /* No comment provided by engineer. */ "No group!" = "Group not found!"; - diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 70c29f49e0..c2f982d0d4 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -65,10 +65,7 @@ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Estrella en GitHub](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Añadir contacto**: crea un enlace de invitación nuevo o usa un enlace recibido."; - -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Añadir nuevo contacto**: para crear tu código QR o enlace de un uso para tu contacto."; +"**Create 1-time link**: to create and share a new invitation link." = "**Añadir contacto**: crea un enlace de invitación nuevo."; /* No comment provided by engineer. */ "**Create group**: to create a new group." = "**Crear grupo**: crea un grupo nuevo."; @@ -80,10 +77,10 @@ "**e2e encrypted** video call" = "Videollamada con **cifrado de extremo a extremo**"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**Más privado**: comprueba los mensajes nuevos cada 20 minutos. El token del dispositivo se comparte con el servidor de SimpleX Chat, pero no cuántos contactos o mensajes tienes."; +"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**Más privado**: comprueba los mensajes nuevos cada 20 minutos. El token del dispositivo se comparte con el servidor de SimpleX Chat, pero no cuántos contactos o mensajes tienes."; /* No comment provided by engineer. */ -"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Más privado**: no se usa el servidor de notificaciones de SimpleX Chat, los mensajes se comprueban periódicamente en segundo plano (dependiendo de la frecuencia con la que utilices la aplicación)."; +"**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." = "**Más privado**: no se usa el servidor de notificaciones de SimpleX Chat, los mensajes se comprueban periódicamente en segundo plano (dependiendo de la frecuencia con la que utilices la aplicación)."; /* No comment provided by engineer. */ "**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Recuarda**: usar la misma base de datos en dos dispositivos hará que falle el descifrado de mensajes como protección de seguridad."; @@ -92,7 +89,7 @@ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Atención**: NO podrás recuperar o cambiar la contraseña si la pierdes."; /* No comment provided by engineer. */ -"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Recomendado**: el token del dispositivo y las notificaciones se envían al servidor de notificaciones de SimpleX Chat, pero no el contenido del mensaje, su tamaño o su procedencia."; +"**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Recomendado**: el token del dispositivo y las notificaciones se envían al servidor de notificaciones de SimpleX Chat, pero no el contenido del mensaje, su tamaño o su procedencia."; /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Advertencia**: Las notificaciones automáticas instantáneas requieren una contraseña guardada en Keychain."; @@ -2474,7 +2471,7 @@ "Immediately" = "Inmediatamente"; /* No comment provided by engineer. */ -"Immune to spam and abuse" = "Inmune a spam y abuso"; +"Immune to spam" = "Inmune a spam y abuso"; /* No comment provided by engineer. */ "Import" = "Importar"; @@ -2576,7 +2573,7 @@ "Instant push notifications will be hidden!\n" = "¡Las notificaciones automáticas estarán ocultas!\n"; /* No comment provided by engineer. */ -"Instantly" = "Al instante"; +"Instant" = "Al instante"; /* No comment provided by engineer. */ "Interface" = "Interfaz"; @@ -2786,7 +2783,7 @@ "Live messages" = "Mensajes en vivo"; /* No comment provided by engineer. */ -"Local" = "Local"; +"No push server" = "No push server"; /* No comment provided by engineer. */ "Local name" = "Nombre local"; @@ -3226,7 +3223,7 @@ "Onion hosts will not be used." = "No se usarán hosts .onion."; /* No comment provided by engineer. */ -"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Sólo los dispositivos cliente almacenan perfiles de usuario, contactos, grupos y mensajes enviados con **cifrado de extremo a extremo de 2 capas**."; +"Only client devices store user profiles, contacts, groups, and messages." = "Sólo los dispositivos cliente almacenan perfiles de usuario, contactos, grupos y mensajes enviados con **cifrado de extremo a extremo de 2 capas**."; /* No comment provided by engineer. */ "Only delete conversation" = "Sólo borrar la conversación"; @@ -3295,7 +3292,7 @@ "Open user profiles" = "Abrir perfil de usuario"; /* No comment provided by engineer. */ -"Open-source protocol and code – anybody can run the servers." = "Protocolo y código abiertos: cualquiera puede usar los servidores."; +"Anybody can host servers." = "Cualquiera puede alojar servidores."; /* No comment provided by engineer. */ "Opening app…" = "Iniciando aplicación…"; @@ -3376,10 +3373,10 @@ "Pending" = "Pendientes"; /* No comment provided by engineer. */ -"People can connect to you only via the links you share." = "Las personas pueden conectarse contigo solo mediante los enlaces que compartes."; +"You decide who can connect." = "Tu decides quién se conecta."; /* No comment provided by engineer. */ -"Periodically" = "Periódicamente"; +"Periodic" = "Periódicamente"; /* message decrypt error item */ "Permanent decryption error" = "Error permanente descifrado"; @@ -3592,7 +3589,7 @@ "Read more" = "Conoce más"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "Conoce más en el [Manual del Usuario](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Conoce más en el [Manual del Usuario](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Conoce más en la [Guía del Usuario](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; @@ -4500,7 +4497,7 @@ "Thanks to the users – contribute via Weblate!" = "¡Nuestro agradecimiento a todos los colaboradores! Puedes contribuir a través de Weblate"; /* No comment provided by engineer. */ -"The 1st platform without any user identifiers – private by design." = "La primera plataforma sin identificadores de usuario: diseñada para la privacidad."; +"No user identifiers." = "Sin identificadores de usuario."; /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "La aplicación puede notificarte cuando recibas mensajes o solicitudes de contacto: por favor, abre la configuración para activarlo."; @@ -4545,7 +4542,7 @@ "The messages will be marked as moderated for all members." = "Los mensajes serán marcados como moderados para todos los miembros."; /* No comment provided by engineer. */ -"The next generation of private messaging" = "La nueva generación de mensajería privada"; +"The future of messaging" = "La nueva generación de mensajería privada"; /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "La base de datos antigua no se eliminó durante la migración, puede eliminarse."; @@ -4635,7 +4632,7 @@ "To make a new connection" = "Para hacer una conexión nueva"; /* No comment provided by engineer. */ -"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "Para proteger tu privacidad, en lugar de los identificadores de usuario que usan el resto de plataformas, SimpleX dispone de identificadores para las colas de mensajes, independientes para cada uno de tus contactos."; +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Para proteger tu privacidad, en lugar de los identificadores de usuario que usan el resto de plataformas, SimpleX dispone de identificadores para las colas de mensajes, independientes para cada uno de tus contactos."; /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Para proteger la zona horaria, los archivos de imagen/voz usan la hora UTC."; @@ -5210,9 +5207,6 @@ /* snd group event chat item */ "you changed role of %@ to %@" = "has cambiado el rol de %1$@ a %2$@"; -/* No comment provided by engineer. */ -"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "Tú controlas a través de qué servidor(es) **recibes** los mensajes. Tus contactos controlan a través de qué servidor(es) **envías** tus mensajes."; - /* No comment provided by engineer. */ "You could not be verified; please try again." = "No has podido ser autenticado. Inténtalo de nuevo."; diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index d1605152c0..2faab1dbd9 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -52,9 +52,6 @@ /* No comment provided by engineer. */ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Tähti GitHubissa](https://github.com/simplex-chat/simplex-chat)"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Lisää uusi kontakti**: luo kertakäyttöinen QR-koodi tai linkki kontaktille."; - /* No comment provided by engineer. */ "**e2e encrypted** audio call" = "**e2e-salattu** äänipuhelu"; @@ -62,16 +59,16 @@ "**e2e encrypted** video call" = "**e2e-salattu** videopuhelu"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**Yksityisempi**: tarkista uudet viestit 20 minuutin välein. Laitetunnus jaetaan SimpleX Chat -palvelimen kanssa, mutta ei sitä, kuinka monta yhteystietoa tai viestiä sinulla on."; +"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**Yksityisempi**: tarkista uudet viestit 20 minuutin välein. Laitetunnus jaetaan SimpleX Chat -palvelimen kanssa, mutta ei sitä, kuinka monta yhteystietoa tai viestiä sinulla on."; /* No comment provided by engineer. */ -"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Yksityisin**: älä käytä SimpleX Chat -ilmoituspalvelinta, tarkista viestit ajoittain taustalla (riippuu siitä, kuinka usein käytät sovellusta)."; +"**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." = "**Yksityisin**: älä käytä SimpleX Chat -ilmoituspalvelinta, tarkista viestit ajoittain taustalla (riippuu siitä, kuinka usein käytät sovellusta)."; /* No comment provided by engineer. */ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Huomaa**: et voi palauttaa tai muuttaa tunnuslausetta, jos kadotat sen."; /* No comment provided by engineer. */ -"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Suositus**: laitetunnus ja ilmoitukset lähetetään SimpleX Chat -ilmoituspalvelimelle, mutta ei viestin sisältöä, kokoa tai sitä, keneltä se on peräisin."; +"**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Suositus**: laitetunnus ja ilmoitukset lähetetään SimpleX Chat -ilmoituspalvelimelle, mutta ei viestin sisältöä, kokoa tai sitä, keneltä se on peräisin."; /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Varoitus**: Välittömät push-ilmoitukset vaativat tunnuslauseen, joka on tallennettu Keychainiin."; @@ -1678,7 +1675,7 @@ "Immediately" = "Heti"; /* No comment provided by engineer. */ -"Immune to spam and abuse" = "Immuuni roskapostille ja väärinkäytöksille"; +"Immune to spam" = "Immuuni roskapostille ja väärinkäytöksille"; /* No comment provided by engineer. */ "Import" = "Tuo"; @@ -1750,7 +1747,7 @@ "Instant push notifications will be hidden!\n" = "Välittömät push-ilmoitukset ovat piilossa!\n"; /* No comment provided by engineer. */ -"Instantly" = "Heti"; +"Instant" = "Heti"; /* No comment provided by engineer. */ "Interface" = "Käyttöliittymä"; @@ -1897,7 +1894,7 @@ "Live messages" = "Live-viestit"; /* No comment provided by engineer. */ -"Local" = "Paikallinen"; +"No push server" = "Paikallinen"; /* No comment provided by engineer. */ "Local name" = "Paikallinen nimi"; @@ -2187,7 +2184,7 @@ "Onion hosts will not be used." = "Onion-isäntiä ei käytetä."; /* No comment provided by engineer. */ -"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Vain asiakaslaitteet tallentavat käyttäjäprofiileja, yhteystietoja, ryhmiä ja viestejä, jotka on lähetetty **kaksinkertaisella päästä päähän -salauksella**."; +"Only client devices store user profiles, contacts, groups, and messages." = "Vain asiakaslaitteet tallentavat käyttäjäprofiileja, yhteystietoja, ryhmiä ja viestejä, jotka on lähetetty **kaksinkertaisella päästä päähän -salauksella**."; /* No comment provided by engineer. */ "Only group owners can change group preferences." = "Vain ryhmän omistajat voivat muuttaa ryhmän asetuksia."; @@ -2241,7 +2238,7 @@ "Open user profiles" = "Avaa käyttäjäprofiilit"; /* No comment provided by engineer. */ -"Open-source protocol and code – anybody can run the servers." = "Avoimen lähdekoodin protokolla ja koodi - kuka tahansa voi käyttää palvelimia."; +"Anybody can host servers." = "Avoimen lähdekoodin protokolla ja koodi - kuka tahansa voi käyttää palvelimia."; /* member role */ "owner" = "omistaja"; @@ -2271,10 +2268,10 @@ "peer-to-peer" = "vertais"; /* No comment provided by engineer. */ -"People can connect to you only via the links you share." = "Ihmiset voivat ottaa sinuun yhteyttä vain jakamiesi linkkien kautta."; +"You decide who can connect." = "Kimin bağlanabileceğine siz karar verirsiniz."; /* No comment provided by engineer. */ -"Periodically" = "Ajoittain"; +"Periodic" = "Ajoittain"; /* message decrypt error item */ "Permanent decryption error" = "Pysyvä salauksen purkuvirhe"; @@ -2412,7 +2409,7 @@ "Read more" = "Lue lisää"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "Lue lisää [Käyttöoppaasta](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Lue lisää [Käyttöoppaasta](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Lue lisää [Käyttöoppaasta](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; @@ -2972,7 +2969,7 @@ "Thanks to the users – contribute via Weblate!" = "Kiitokset käyttäjille – osallistu Weblaten kautta!"; /* No comment provided by engineer. */ -"The 1st platform without any user identifiers – private by design." = "Ensimmäinen alusta ilman käyttäjätunnisteita – suunniteltu yksityiseksi."; +"No user identifiers." = "Ensimmäinen alusta ilman käyttäjätunnisteita – suunniteltu yksityiseksi."; /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Sovellus voi ilmoittaa sinulle, kun saat viestejä tai yhteydenottopyyntöjä - avaa asetukset ottaaksesi ne käyttöön."; @@ -3005,7 +3002,7 @@ "The message will be marked as moderated for all members." = "Viesti merkitään moderoiduksi kaikille jäsenille."; /* No comment provided by engineer. */ -"The next generation of private messaging" = "Seuraavan sukupolven yksityisviestit"; +"The future of messaging" = "Seuraavan sukupolven yksityisviestit"; /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Vanhaa tietokantaa ei poistettu siirron aikana, se voidaan kuitenkin poistaa."; @@ -3059,7 +3056,7 @@ "To make a new connection" = "Uuden yhteyden luominen"; /* No comment provided by engineer. */ -"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "Yksityisyyden suojaamiseksi kaikkien muiden alustojen käyttämien käyttäjätunnusten sijaan SimpleX käyttää viestijonojen tunnisteita, jotka ovat kaikille kontakteille erillisiä."; +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Yksityisyyden suojaamiseksi kaikkien muiden alustojen käyttämien käyttäjätunnusten sijaan SimpleX käyttää viestijonojen tunnisteita, jotka ovat kaikille kontakteille erillisiä."; /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Aikavyöhykkeen suojaamiseksi kuva-/äänitiedostot käyttävät UTC:tä."; @@ -3385,9 +3382,6 @@ /* snd group event chat item */ "you changed role of %@ to %@" = "olet vaihtanut %1$@:n roolin %2$@:ksi"; -/* No comment provided by engineer. */ -"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "Sinä hallitset, minkä palvelim(i)en kautta **viestit vastaanotetaan**, kontaktisi - palvelimet, joita käytät viestien lähettämiseen niille."; - /* No comment provided by engineer. */ "You could not be verified; please try again." = "Sinua ei voitu todentaa; yritä uudelleen."; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index 5d08240a52..92b66efb72 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -65,10 +65,7 @@ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Star sur GitHub](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Ajouter un contact** : pour créer un nouveau lien d'invitation ou vous connecter via un lien que vous avez reçu."; - -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Ajouter un nouveau contact** : pour créer un lien ou code QR unique pour votre contact."; +"**Create 1-time link**: to create and share a new invitation link." = "**Ajouter un contact** : pour créer un nouveau lien d'invitation."; /* No comment provided by engineer. */ "**Create group**: to create a new group." = "**Créer un groupe** : pour créer un nouveau groupe."; @@ -80,10 +77,10 @@ "**e2e encrypted** video call" = "appel vidéo **chiffré de bout en bout**"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**Vie privée** : vérification de nouveaux messages toute les 20 minutes. Le token de l'appareil est partagé avec le serveur SimpleX, mais pas le nombre de messages ou de contacts."; +"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**Vie privée** : vérification de nouveaux messages toute les 20 minutes. Le token de l'appareil est partagé avec le serveur SimpleX, mais pas le nombre de messages ou de contacts."; /* No comment provided by engineer. */ -"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Confidentiel** : ne pas utiliser le serveur de notifications SimpleX, vérification de nouveaux messages periodiquement en arrière plan (dépend de l'utilisation de l'app)."; +"**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." = "**Confidentiel** : ne pas utiliser le serveur de notifications SimpleX, vérification de nouveaux messages periodiquement en arrière plan (dépend de l'utilisation de l'app)."; /* No comment provided by engineer. */ "**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Remarque** : l'utilisation de la même base de données sur deux appareils interrompt le déchiffrement des messages provenant de vos connexions, par mesure de sécurité."; @@ -92,7 +89,7 @@ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Veuillez noter** : vous NE pourrez PAS récupérer ou modifier votre phrase secrète si vous la perdez."; /* No comment provided by engineer. */ -"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Recommandé** : le token de l'appareil et les notifications sont envoyés au serveur de notifications SimpleX, mais pas le contenu du message, sa taille ou son auteur."; +"**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Recommandé** : le token de l'appareil et les notifications sont envoyés au serveur de notifications SimpleX, mais pas le contenu du message, sa taille ou son auteur."; /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Avertissement** : les notifications push instantanées nécessitent une phrase secrète enregistrée dans la keychain."; @@ -2387,7 +2384,7 @@ "Immediately" = "Immédiatement"; /* No comment provided by engineer. */ -"Immune to spam and abuse" = "Protégé du spam et des abus"; +"Immune to spam" = "Protégé du spam et des abus"; /* No comment provided by engineer. */ "Import" = "Importer"; @@ -2486,7 +2483,7 @@ "Instant push notifications will be hidden!\n" = "Les notifications push instantanées vont être cachées !\n"; /* No comment provided by engineer. */ -"Instantly" = "Instantané"; +"Instant" = "Instantané"; /* No comment provided by engineer. */ "Interface" = "Interface"; @@ -2693,7 +2690,7 @@ "Live messages" = "Messages dynamiques"; /* No comment provided by engineer. */ -"Local" = "Local"; +"No push server" = "No push server"; /* No comment provided by engineer. */ "Local name" = "Nom local"; @@ -3112,7 +3109,7 @@ "Onion hosts will not be used." = "Les hôtes .onion ne seront pas utilisés."; /* No comment provided by engineer. */ -"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Seuls les appareils clients stockent les profils des utilisateurs, les contacts, les groupes et les messages envoyés avec un **chiffrement de bout en bout à deux couches**."; +"Only client devices store user profiles, contacts, groups, and messages." = "Seuls les appareils clients stockent les profils des utilisateurs, les contacts, les groupes et les messages envoyés avec un **chiffrement de bout en bout à deux couches**."; /* No comment provided by engineer. */ "Only delete conversation" = "Ne supprimer que la conversation"; @@ -3181,7 +3178,7 @@ "Open user profiles" = "Ouvrir les profils d'utilisateurs"; /* No comment provided by engineer. */ -"Open-source protocol and code – anybody can run the servers." = "Protocole et code open-source – n'importe qui peut heberger un serveur."; +"Anybody can host servers." = "N\'importe qui peut heberger un serveur."; /* No comment provided by engineer. */ "Opening app…" = "Ouverture de l'app…"; @@ -3256,10 +3253,10 @@ "Pending" = "En attente"; /* No comment provided by engineer. */ -"People can connect to you only via the links you share." = "On ne peut se connecter à vous qu’avec les liens que vous partagez."; +"You decide who can connect." = "Vous choisissez qui peut se connecter."; /* No comment provided by engineer. */ -"Periodically" = "Périodique"; +"Periodic" = "Périodique"; /* message decrypt error item */ "Permanent decryption error" = "Erreur de déchiffrement"; @@ -3466,7 +3463,7 @@ "Read more" = "En savoir plus"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "Pour en savoir plus, consultez le [Guide de l'utilisateur](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Pour en savoir plus, consultez le [Guide de l'utilisateur](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Pour en savoir plus, consultez le [Guide de l'utilisateur](https ://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; @@ -4335,7 +4332,7 @@ "Thanks to the users – contribute via Weblate!" = "Merci aux utilisateurs - contribuez via Weblate !"; /* No comment provided by engineer. */ -"The 1st platform without any user identifiers – private by design." = "La 1ère plateforme sans aucun identifiant d'utilisateur – privée par design."; +"No user identifiers." = "Aucun identifiant d\'utilisateur."; /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "L'application peut vous avertir lorsque vous recevez des messages ou des demandes de contact - veuillez ouvrir les paramètres pour les activer."; @@ -4380,7 +4377,7 @@ "The messages will be marked as moderated for all members." = "Les messages seront marqués comme modérés pour tous les membres."; /* No comment provided by engineer. */ -"The next generation of private messaging" = "La nouvelle génération de messagerie privée"; +"The future of messaging" = "La nouvelle génération de messagerie privée"; /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "L'ancienne base de données n'a pas été supprimée lors de la migration, elle peut être supprimée."; @@ -4467,7 +4464,7 @@ "To make a new connection" = "Pour établir une nouvelle connexion"; /* No comment provided by engineer. */ -"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "Pour protéger votre vie privée, au lieu d’IDs utilisés par toutes les autres plateformes, SimpleX a des IDs pour les queues de messages, distinctes pour chacun de vos contacts."; +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Pour protéger votre vie privée, au lieu d’IDs utilisés par toutes les autres plateformes, SimpleX a des IDs pour les queues de messages, distinctes pour chacun de vos contacts."; /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Pour préserver le fuseau horaire, les fichiers image/voix utilisent le système UTC."; @@ -5030,9 +5027,6 @@ /* snd group event chat item */ "you changed role of %@ to %@" = "vous avez modifié le rôle de %1$@ pour %2$@"; -/* No comment provided by engineer. */ -"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "Vous contrôlez par quel·s serveur·s vous pouvez **transmettre** ainsi que par quel·s serveur·s vous pouvez **recevoir** les messages de vos contacts."; - /* No comment provided by engineer. */ "You could not be verified; please try again." = "Vous n'avez pas pu être vérifié·e ; veuillez réessayer."; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index c707f72bf6..5668b5367f 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -65,10 +65,7 @@ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Csillagozás a GitHubon](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Ismerős hozzáadása:** új meghívó-hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz."; - -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Új ismerős hozzáadása:** egyszer használható QR-kód vagy hivatkozás létrehozása az ismerőse számára."; +"**Create 1-time link**: to create and share a new invitation link." = "**Ismerős hozzáadása:** új meghívó-hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz."; /* No comment provided by engineer. */ "**Create group**: to create a new group." = "**Csoport létrehozása:** új csoport létrehozásához."; @@ -80,10 +77,10 @@ "**e2e encrypted** video call" = "**e2e titkosított** videóhívás"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**Privátabb:** 20 percenként ellenőrzi az új üzeneteket. Az eszköztoken megosztásra kerül a SimpleX Chat-kiszolgálóval, de az nem, hogy hány ismerőse vagy üzenete van."; +"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**Privátabb:** 20 percenként ellenőrzi az új üzeneteket. Az eszköztoken megosztásra kerül a SimpleX Chat-kiszolgálóval, de az nem, hogy hány ismerőse vagy üzenete van."; /* No comment provided by engineer. */ -"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Legprivátabb:** ne használja a SimpleX Chat értesítési kiszolgálót, rendszeresen ellenőrizze az üzeneteket a háttérben (attól függően, hogy milyen gyakran használja az alkalmazást)."; +"**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." = "**Legprivátabb:** ne használja a SimpleX Chat értesítési kiszolgálót, rendszeresen ellenőrizze az üzeneteket a háttérben (attól függően, hogy milyen gyakran használja az alkalmazást)."; /* No comment provided by engineer. */ "**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Megjegyzés:** ha két eszközön is ugyanazt az adatbázist használja, akkor biztonsági védelemként megszakítja az ismerőseitől érkező üzenetek visszafejtését."; @@ -92,7 +89,7 @@ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Megjegyzés:** NEM tudja visszaállítani vagy megváltoztatni jelmondatát, ha elveszíti azt."; /* No comment provided by engineer. */ -"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Megjegyzés:** az eszköztoken és az értesítések elküldésre kerülnek a SimpleX Chat értesítési kiszolgálóra, kivéve az üzenet tartalma, mérete vagy az, hogy kitől származik."; +"**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Megjegyzés:** az eszköztoken és az értesítések elküldésre kerülnek a SimpleX Chat értesítési kiszolgálóra, kivéve az üzenet tartalma, mérete vagy az, hogy kitől származik."; /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Figyelmeztetés:** Az azonnali push-értesítésekhez a kulcstartóban tárolt jelmondat megadása szükséges."; @@ -2474,7 +2471,7 @@ "Immediately" = "Azonnal"; /* No comment provided by engineer. */ -"Immune to spam and abuse" = "Spam és visszaélések elleni védelem"; +"Immune to spam" = "Spam és visszaélések elleni védelem"; /* No comment provided by engineer. */ "Import" = "Importálás"; @@ -2576,7 +2573,7 @@ "Instant push notifications will be hidden!\n" = "Az azonnali push-értesítések elrejtésre kerülnek!\n"; /* No comment provided by engineer. */ -"Instantly" = "Azonnal"; +"Instant" = "Azonnal"; /* No comment provided by engineer. */ "Interface" = "Felület"; @@ -2786,7 +2783,7 @@ "Live messages" = "Élő üzenetek"; /* No comment provided by engineer. */ -"Local" = "Helyi"; +"No push server" = "Helyi"; /* No comment provided by engineer. */ "Local name" = "Helyi név"; @@ -3226,7 +3223,7 @@ "Onion hosts will not be used." = "Onion-kiszolgálók nem lesznek használva."; /* No comment provided by engineer. */ -"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Csak az eszközök alkalmazásai tárolják a felhasználó-profilokat, névjegyeket, csoportokat és a **2 rétegű végpontok közötti titkosítással** küldött üzeneteket."; +"Only client devices store user profiles, contacts, groups, and messages." = "Csak az eszközök alkalmazásai tárolják a felhasználó-profilokat, névjegyeket, csoportokat és a **2 rétegű végpontok közötti titkosítással** küldött üzeneteket."; /* No comment provided by engineer. */ "Only delete conversation" = "Csak a beszélgetés törlése"; @@ -3295,7 +3292,7 @@ "Open user profiles" = "Felhasználó-profilok megnyitása"; /* No comment provided by engineer. */ -"Open-source protocol and code – anybody can run the servers." = "Nyílt forráskódú protokoll és forráskód – bárki üzemeltethet kiszolgálókat."; +"Anybody can host servers." = "Bárki üzemeltethet kiszolgálókat."; /* No comment provided by engineer. */ "Opening app…" = "Az alkalmazás megnyitása…"; @@ -3376,10 +3373,10 @@ "Pending" = "Függőben"; /* No comment provided by engineer. */ -"People can connect to you only via the links you share." = "Az emberek csak az Ön által megosztott hivatkozáson keresztül kapcsolódhatnak."; +"You decide who can connect." = "Ön dönti el, hogy kivel beszélget."; /* No comment provided by engineer. */ -"Periodically" = "Rendszeresen"; +"Periodic" = "Rendszeresen"; /* message decrypt error item */ "Permanent decryption error" = "Végleges visszafejtési hiba"; @@ -3592,7 +3589,7 @@ "Read more" = "Tudjon meg többet"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "További információ a [Használati útmutatóban](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "További információ a [Használati útmutatóban](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "További információ a [Használati útmutatóban](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; @@ -4500,7 +4497,7 @@ "Thanks to the users – contribute via Weblate!" = "Köszönet a felhasználóknak - hozzájárulás a Weblate-en!"; /* No comment provided by engineer. */ -"The 1st platform without any user identifiers – private by design." = "Az első csevegési rendszer bármiféle felhasználó-azonosító nélkül - privátra lett tervezre."; +"No user identifiers." = "Nincsenek felhasználó-azonosítók."; /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Az alkalmazás értesíteni fogja, amikor üzeneteket vagy kapcsolatkéréseket kap – beállítások megnyitása az engedélyezéshez."; @@ -4545,7 +4542,7 @@ "The messages will be marked as moderated for all members." = "Az üzenetek moderáltként lesznek megjelölve minden tag számára."; /* No comment provided by engineer. */ -"The next generation of private messaging" = "A privát üzenetküldés következő generációja"; +"The future of messaging" = "A privát üzenetküldés következő generációja"; /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "A régi adatbázis nem került eltávolításra az átköltöztetéskor, így törölhető."; @@ -4635,7 +4632,7 @@ "To make a new connection" = "Új kapcsolat létrehozásához"; /* No comment provided by engineer. */ -"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "Az adatvédelem érdekében, a más csevegési platformokon megszokott felhasználó-azonosítók helyett, a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, minden egyes ismerőshöz egy-egy különbözőt."; +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Az adatvédelem érdekében, a más csevegési platformokon megszokott felhasználó-azonosítók helyett, a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, minden egyes ismerőshöz egy-egy különbözőt."; /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Az időzóna védelme érdekében a kép-/hangfájlok UTC-t használnak."; @@ -5210,9 +5207,6 @@ /* snd group event chat item */ "you changed role of %@ to %@" = "Ön megváltoztatta %1$@ szerepkörét erre: %@"; -/* No comment provided by engineer. */ -"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "Ön szabályozhatja, hogy mely kiszogál(ók)ón keresztül **kapja** az üzeneteket, az ismerősöket - az üzenetküldéshez használt kiszolgálókon."; - /* No comment provided by engineer. */ "You could not be verified; please try again." = "Nem sikerült hitelesíteni; próbálja meg újra."; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index 308ff5d18e..2e2aea3f3c 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -65,10 +65,7 @@ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Dai una stella su GitHub](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Aggiungi contatto**: per creare un nuovo link di invito o connetterti tramite un link che hai ricevuto."; - -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Aggiungi un contatto**: per creare il tuo codice QR o link una tantum per il tuo contatto."; +"**Create 1-time link**: to create and share a new invitation link." = "**Aggiungi contatto**: per creare un nuovo link di invito."; /* No comment provided by engineer. */ "**Create group**: to create a new group." = "**Crea gruppo**: per creare un nuovo gruppo."; @@ -80,10 +77,10 @@ "**e2e encrypted** video call" = "Videochiamata **crittografata e2e**"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**Più privato**: controlla messaggi nuovi ogni 20 minuti. Viene condiviso il token del dispositivo con il server di SimpleX Chat, ma non quanti contatti o messaggi hai."; +"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**Più privato**: controlla messaggi nuovi ogni 20 minuti. Viene condiviso il token del dispositivo con il server di SimpleX Chat, ma non quanti contatti o messaggi hai."; /* No comment provided by engineer. */ -"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Il più privato**: non usare il server di notifica di SimpleX Chat, controlla i messaggi periodicamente in secondo piano (dipende da quanto spesso usi l'app)."; +"**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." = "**Il più privato**: non usare il server di notifica di SimpleX Chat, controlla i messaggi periodicamente in secondo piano (dipende da quanto spesso usi l'app)."; /* No comment provided by engineer. */ "**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Nota bene**: usare lo stesso database su due dispositivi bloccherà la decifrazione dei messaggi dalle tue connessioni, come misura di sicurezza."; @@ -92,7 +89,7 @@ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Nota bene**: NON potrai recuperare o cambiare la password se la perdi."; /* No comment provided by engineer. */ -"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Consigliato**: vengono inviati il token del dispositivo e le notifiche al server di notifica di SimpleX Chat, ma non il contenuto del messaggio,la sua dimensione o il suo mittente."; +"**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Consigliato**: vengono inviati il token del dispositivo e le notifiche al server di notifica di SimpleX Chat, ma non il contenuto del messaggio,la sua dimensione o il suo mittente."; /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Attenzione**: le notifiche push istantanee richiedono una password salvata nel portachiavi."; @@ -2474,7 +2471,7 @@ "Immediately" = "Immediatamente"; /* No comment provided by engineer. */ -"Immune to spam and abuse" = "Immune a spam e abusi"; +"Immune to spam" = "Immune a spam e abusi"; /* No comment provided by engineer. */ "Import" = "Importa"; @@ -2576,7 +2573,7 @@ "Instant push notifications will be hidden!\n" = "Le notifiche push istantanee saranno nascoste!\n"; /* No comment provided by engineer. */ -"Instantly" = "Istantaneamente"; +"Instant" = "Istantaneamente"; /* No comment provided by engineer. */ "Interface" = "Interfaccia"; @@ -2786,7 +2783,7 @@ "Live messages" = "Messaggi in diretta"; /* No comment provided by engineer. */ -"Local" = "Locale"; +"No push server" = "Locale"; /* No comment provided by engineer. */ "Local name" = "Nome locale"; @@ -3226,7 +3223,7 @@ "Onion hosts will not be used." = "Gli host Onion non verranno usati."; /* No comment provided by engineer. */ -"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Solo i dispositivi client archiviano profili utente, i contatti, i gruppi e i messaggi inviati con la **crittografia end-to-end a 2 livelli**."; +"Only client devices store user profiles, contacts, groups, and messages." = "Solo i dispositivi client archiviano profili utente, i contatti, i gruppi e i messaggi inviati con la **crittografia end-to-end a 2 livelli**."; /* No comment provided by engineer. */ "Only delete conversation" = "Elimina solo la conversazione"; @@ -3295,7 +3292,7 @@ "Open user profiles" = "Apri i profili utente"; /* No comment provided by engineer. */ -"Open-source protocol and code – anybody can run the servers." = "Protocollo e codice open source: chiunque può gestire i server."; +"Anybody can host servers." = "Chiunque può installare i server."; /* No comment provided by engineer. */ "Opening app…" = "Apertura dell'app…"; @@ -3376,10 +3373,10 @@ "Pending" = "In attesa"; /* No comment provided by engineer. */ -"People can connect to you only via the links you share." = "Le persone possono connettersi a te solo tramite i link che condividi."; +"You decide who can connect." = "Sei tu a decidere chi può connettersi."; /* No comment provided by engineer. */ -"Periodically" = "Periodicamente"; +"Periodic" = "Periodicamente"; /* message decrypt error item */ "Permanent decryption error" = "Errore di decifrazione"; @@ -3592,7 +3589,7 @@ "Read more" = "Leggi tutto"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "Maggiori informazioni nella [Guida per l'utente](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Maggiori informazioni nella [Guida per l'utente](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Leggi di più nella [Guida utente](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; @@ -4500,7 +4497,7 @@ "Thanks to the users – contribute via Weblate!" = "Grazie agli utenti – contribuite via Weblate!"; /* No comment provided by engineer. */ -"The 1st platform without any user identifiers – private by design." = "La prima piattaforma senza alcun identificatore utente – privata by design."; +"No user identifiers." = "Nessun identificatore utente."; /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "L'app può avvisarti quando ricevi messaggi o richieste di contatto: apri le impostazioni per attivare."; @@ -4545,7 +4542,7 @@ "The messages will be marked as moderated for all members." = "I messaggi verranno contrassegnati come moderati per tutti i membri."; /* No comment provided by engineer. */ -"The next generation of private messaging" = "La nuova generazione di messaggistica privata"; +"The future of messaging" = "La nuova generazione di messaggistica privata"; /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Il database vecchio non è stato rimosso durante la migrazione, può essere eliminato."; @@ -4635,7 +4632,7 @@ "To make a new connection" = "Per creare una nuova connessione"; /* No comment provided by engineer. */ -"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "Per proteggere la privacy, invece degli ID utente utilizzati da tutte le altre piattaforme, SimpleX ha identificatori per le code di messaggi, separati per ciascuno dei tuoi contatti."; +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Per proteggere la privacy, invece degli ID utente utilizzati da tutte le altre piattaforme, SimpleX ha identificatori per le code di messaggi, separati per ciascuno dei tuoi contatti."; /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Per proteggere il fuso orario, i file immagine/vocali usano UTC."; @@ -5210,9 +5207,6 @@ /* snd group event chat item */ "you changed role of %@ to %@" = "hai cambiato il ruolo di %1$@ in %2$@"; -/* No comment provided by engineer. */ -"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "Tu decidi attraverso quale/i server **ricevere** i messaggi, i tuoi contatti quali server usi per inviare loro i messaggi."; - /* No comment provided by engineer. */ "You could not be verified; please try again." = "Non è stato possibile verificarti, riprova."; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index 20c4819d87..c2f2717b1b 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -59,10 +59,7 @@ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[GitHub でスターを付ける](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"**Add contact**: to create a new invitation link, or connect via a link you received." = "**コンタクトの追加**: 新しい招待リンクを作成するか、受け取ったリンクから接続します。"; - -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**新しい連絡先を追加**: 連絡先のワンタイム QR コードまたはリンクを作成します。"; +"**Create 1-time link**: to create and share a new invitation link." = "**コンタクトの追加**: 新しい招待リンクを作成するか、受け取ったリンクから接続します。"; /* No comment provided by engineer. */ "**Create group**: to create a new group." = "**グループ作成**: 新しいグループを作成する。"; @@ -74,10 +71,10 @@ "**e2e encrypted** video call" = "**エンドツーエンド暗号化済み**の テレビ電話 通話"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**よりプライベート**: 20 分ごとに新しいメッセージを確認します。 デバイス トークンは SimpleX Chat サーバーと共有されますが、連絡先やメッセージの数は共有されません。"; +"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**よりプライベート**: 20 分ごとに新しいメッセージを確認します。 デバイス トークンは SimpleX Chat サーバーと共有されますが、連絡先やメッセージの数は共有されません。"; /* No comment provided by engineer. */ -"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**最もプライベート**: SimpleX Chat 通知サーバーを使用せず、バックグラウンドで定期的にメッセージをチェックします (アプリの使用頻度によって異なります)。"; +"**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." = "**最もプライベート**: SimpleX Chat 通知サーバーを使用せず、バックグラウンドで定期的にメッセージをチェックします (アプリの使用頻度によって異なります)。"; /* No comment provided by engineer. */ "**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**注意**: 2つの端末で同じデータベースを使用すると、セキュリティ保護として、あなたが接続しているメッセージの復号化が解除されます。"; @@ -86,7 +83,7 @@ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**注意**: パスフレーズを紛失すると、パスフレーズを復元または変更できなくなります。"; /* No comment provided by engineer. */ -"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**推奨**: デバイス トークンと通知は SimpleX Chat 通知サーバーに送信されますが、メッセージの内容、サイズ、送信者は送信されません。"; +"**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**推奨**: デバイス トークンと通知は SimpleX Chat 通知サーバーに送信されますが、メッセージの内容、サイズ、送信者は送信されません。"; /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**警告**: 即時の プッシュ通知には、キーチェーンに保存されたパスフレーズが必要です。"; @@ -1753,7 +1750,7 @@ "Immediately" = "即座に"; /* No comment provided by engineer. */ -"Immune to spam and abuse" = "スパムや悪質送信を防止"; +"Immune to spam" = "スパムや悪質送信を防止"; /* No comment provided by engineer. */ "Import" = "読み込む"; @@ -1825,7 +1822,7 @@ "Instant push notifications will be hidden!\n" = "インスタントプッシュ通知は非表示になります!\n"; /* No comment provided by engineer. */ -"Instantly" = "すぐに"; +"Instant" = "すぐに"; /* No comment provided by engineer. */ "Interface" = "インターフェース"; @@ -1972,7 +1969,7 @@ "Live messages" = "ライブメッセージ"; /* No comment provided by engineer. */ -"Local" = "自分のみ"; +"No push server" = "自分のみ"; /* No comment provided by engineer. */ "Local name" = "ローカルネーム"; @@ -2268,7 +2265,7 @@ "Onion hosts will not be used." = "オニオンのホストが使われません。"; /* No comment provided by engineer. */ -"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "**2 レイヤーのエンドツーエンド暗号化**を使用して送信されたユーザー プロファイル、連絡先、グループ、メッセージを保存できるのはクライアント デバイスのみです。"; +"Only client devices store user profiles, contacts, groups, and messages." = "**2 レイヤーのエンドツーエンド暗号化**を使用して送信されたユーザー プロファイル、連絡先、グループ、メッセージを保存できるのはクライアント デバイスのみです。"; /* No comment provided by engineer. */ "Only group owners can change group preferences." = "グループ設定を変えられるのはグループのオーナーだけです。"; @@ -2325,7 +2322,7 @@ "Open user profiles" = "ユーザープロフィールを開く"; /* No comment provided by engineer. */ -"Open-source protocol and code – anybody can run the servers." = "プロトコル技術とコードはオープンソースで、どなたでもご自分のサーバを運用できます。"; +"Anybody can host servers." = "プロトコル技術とコードはオープンソースで、どなたでもご自分のサーバを運用できます。"; /* member role */ "owner" = "オーナー"; @@ -2355,10 +2352,10 @@ "peer-to-peer" = "P2P"; /* No comment provided by engineer. */ -"People can connect to you only via the links you share." = "あなたと繋がることができるのは、あなたからリンクを頂いた方のみです。"; +"You decide who can connect." = "あなたと繋がることができるのは、あなたからリンクを頂いた方のみです。"; /* No comment provided by engineer. */ -"Periodically" = "定期的に"; +"Periodic" = "定期的に"; /* message decrypt error item */ "Permanent decryption error" = "永続的な復号化エラー"; @@ -2496,13 +2493,13 @@ "Read more" = "続きを読む"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "詳しくは[ユーザーガイド](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)をご覧ください。"; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "詳しくは[ユーザーガイド](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)をご覧ください。"; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "詳しくは[ユーザーガイド](https://simplex.chat/docs/guide/readme.html#connect-to-friends)をご覧ください。"; /* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "詳しくは[GitHubリポジトリ](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)をご覧ください。"; +"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "詳しくは[GitHubリポジトリ](https://github.com/simplex-chat/simplex-chat#readme)をご覧ください。"; /* No comment provided by engineer. */ "Read more in our GitHub repository." = "GitHubリポジトリで詳細をご確認ください。"; @@ -3035,7 +3032,7 @@ "Thanks to the users – contribute via Weblate!" = "ユーザーに感謝します – Weblate 経由で貢献してください!"; /* No comment provided by engineer. */ -"The 1st platform without any user identifiers – private by design." = "世界初のユーザーIDのないプラットフォーム|設計も元からプライベート。"; +"No user identifiers." = "世界初のユーザーIDのないプラットフォーム|設計も元からプライベート。"; /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "アプリは、メッセージや連絡先のリクエストを受信したときに通知することができます - 設定を開いて有効にしてください。"; @@ -3068,7 +3065,7 @@ "The message will be marked as moderated for all members." = "メッセージは、すべてのメンバーに対してモデレートされたものとして表示されます。"; /* No comment provided by engineer. */ -"The next generation of private messaging" = "次世代のプライバシー・メッセンジャー"; +"The future of messaging" = "次世代のプライバシー・メッセンジャー"; /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "古いデータベースは移行時に削除されなかったので、削除することができます。"; @@ -3119,7 +3116,7 @@ "To make a new connection" = "新規に接続する場合"; /* No comment provided by engineer. */ -"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "プライバシーを保護するために、SimpleX には、他のすべてのプラットフォームで使用されるユーザー ID の代わりに、連絡先ごとに個別のメッセージ キューの識別子があります。"; +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "プライバシーを保護するために、SimpleX には、他のすべてのプラットフォームで使用されるユーザー ID の代わりに、連絡先ごとに個別のメッセージ キューの識別子があります。"; /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "時間帯を漏らさないために、画像と音声ファイルはUTCを使います。"; @@ -3445,9 +3442,6 @@ /* snd group event chat item */ "you changed role of %@ to %@" = "%1$@ の役割を %2$@ に変更しました"; -/* No comment provided by engineer. */ -"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "あなたはメッセージの受信に使用するサーバーを制御し、連絡先はあなたがメッセージの送信に使用するサーバーを使用することができます。"; - /* No comment provided by engineer. */ "You could not be verified; please try again." = "確認できませんでした。 もう一度お試しください。"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index b9caba8463..e6320e6208 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -65,10 +65,7 @@ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Contact toevoegen**: om een nieuwe uitnodigingslink aan te maken, of verbinding te maken via een link die u heeft ontvangen."; - -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Nieuw contact toevoegen**: om uw eenmalige QR-code of link voor uw contact te maken."; +"**Create 1-time link**: to create and share a new invitation link." = "**Contact toevoegen**: om een nieuwe uitnodigingslink aan te maken, of verbinding te maken via een link die u heeft ontvangen."; /* No comment provided by engineer. */ "**Create group**: to create a new group." = "**Groep aanmaken**: om een nieuwe groep aan te maken."; @@ -80,10 +77,10 @@ "**e2e encrypted** video call" = "**e2e versleuteld** video gesprek"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**Meer privé**: bekijk elke 20 minuten nieuwe berichten. Apparaattoken wordt gedeeld met de SimpleX Chat-server, maar niet hoeveel contacten of berichten u heeft."; +"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**Meer privé**: bekijk elke 20 minuten nieuwe berichten. Apparaattoken wordt gedeeld met de SimpleX Chat-server, maar niet hoeveel contacten of berichten u heeft."; /* No comment provided by engineer. */ -"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Meest privé**: gebruik geen SimpleX Chat-notificatie server, controleer berichten regelmatig op de achtergrond (afhankelijk van hoe vaak u de app gebruikt)."; +"**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." = "**Meest privé**: gebruik geen SimpleX Chat-notificatie server, controleer berichten regelmatig op de achtergrond (afhankelijk van hoe vaak u de app gebruikt)."; /* No comment provided by engineer. */ "**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Let op**: als u dezelfde database op twee apparaten gebruikt, wordt de decodering van berichten van uw verbindingen verbroken, als veiligheidsmaatregel."; @@ -92,7 +89,7 @@ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Let op**: u kunt het wachtwoord NIET herstellen of wijzigen als u het kwijtraakt."; /* No comment provided by engineer. */ -"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Aanbevolen**: apparaattoken en meldingen worden naar de SimpleX Chat-meldingsserver gestuurd, maar niet de berichtinhoud, -grootte of van wie het afkomstig is."; +"**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Aanbevolen**: apparaattoken en meldingen worden naar de SimpleX Chat-meldingsserver gestuurd, maar niet de berichtinhoud, -grootte of van wie het afkomstig is."; /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Waarschuwing**: voor directe push meldingen is een wachtwoord vereist dat is opgeslagen in de Keychain."; @@ -2474,7 +2471,7 @@ "Immediately" = "Onmiddellijk"; /* No comment provided by engineer. */ -"Immune to spam and abuse" = "Immuun voor spam en misbruik"; +"Immune to spam" = "Immuun voor spam en misbruik"; /* No comment provided by engineer. */ "Import" = "Importeren"; @@ -2576,7 +2573,7 @@ "Instant push notifications will be hidden!\n" = "Directe push meldingen worden verborgen!\n"; /* No comment provided by engineer. */ -"Instantly" = "Direct"; +"Instant" = "Direct"; /* No comment provided by engineer. */ "Interface" = "Interface"; @@ -2786,7 +2783,7 @@ "Live messages" = "Live berichten"; /* No comment provided by engineer. */ -"Local" = "Lokaal"; +"No push server" = "Lokaal"; /* No comment provided by engineer. */ "Local name" = "Lokale naam"; @@ -3226,7 +3223,7 @@ "Onion hosts will not be used." = "Onion hosts worden niet gebruikt."; /* No comment provided by engineer. */ -"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Alleen client apparaten slaan gebruikers profielen, contacten, groepen en berichten op die zijn verzonden met **2-laags end-to-end-codering**."; +"Only client devices store user profiles, contacts, groups, and messages." = "Alleen client apparaten slaan gebruikers profielen, contacten, groepen en berichten op die zijn verzonden met **2-laags end-to-end-codering**."; /* No comment provided by engineer. */ "Only delete conversation" = "Alleen conversatie verwijderen"; @@ -3295,7 +3292,7 @@ "Open user profiles" = "Gebruikers profielen openen"; /* No comment provided by engineer. */ -"Open-source protocol and code – anybody can run the servers." = "Open-source protocol en code. Iedereen kan de servers draaien."; +"Anybody can host servers." = "Iedereen kan servers hosten."; /* No comment provided by engineer. */ "Opening app…" = "App openen…"; @@ -3376,10 +3373,10 @@ "Pending" = "in behandeling"; /* No comment provided by engineer. */ -"People can connect to you only via the links you share." = "Mensen kunnen alleen verbinding met u maken via de links die u deelt."; +"You decide who can connect." = "Jij bepaalt wie er verbinding mag maken."; /* No comment provided by engineer. */ -"Periodically" = "Periodiek"; +"Periodic" = "Periodiek"; /* message decrypt error item */ "Permanent decryption error" = "Decodering fout"; @@ -3592,7 +3589,7 @@ "Read more" = "Lees meer"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/app-settings.html#uw-simplex-contactadres)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/app-settings.html#uw-simplex-contactadres)."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; @@ -4500,7 +4497,7 @@ "Thanks to the users – contribute via Weblate!" = "Dank aan de gebruikers – draag bij via Weblate!"; /* No comment provided by engineer. */ -"The 1st platform without any user identifiers – private by design." = "Het eerste platform zonder gebruikers-ID's, privé door ontwerp."; +"No user identifiers." = "Geen gebruikers-ID\'s."; /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "De app kan u op de hoogte stellen wanneer u berichten of contact verzoeken ontvangt - open de instellingen om dit in te schakelen."; @@ -4545,7 +4542,7 @@ "The messages will be marked as moderated for all members." = "De berichten worden voor alle leden als gemodereerd gemarkeerd."; /* No comment provided by engineer. */ -"The next generation of private messaging" = "De volgende generatie privéberichten"; +"The future of messaging" = "De volgende generatie privéberichten"; /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "De oude database is niet verwijderd tijdens de migratie, deze kan worden verwijderd."; @@ -4635,7 +4632,7 @@ "To make a new connection" = "Om een nieuwe verbinding te maken"; /* No comment provided by engineer. */ -"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "Om de privacy te beschermen, heeft SimpleX in plaats van gebruikers-ID's die door alle andere platforms worden gebruikt, ID's voor berichten wachtrijen, afzonderlijk voor elk van uw contacten."; +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Om de privacy te beschermen, heeft SimpleX in plaats van gebruikers-ID's die door alle andere platforms worden gebruikt, ID's voor berichten wachtrijen, afzonderlijk voor elk van uw contacten."; /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Om de tijdzone te beschermen, gebruiken afbeeldings-/spraakbestanden UTC."; @@ -5210,9 +5207,6 @@ /* snd group event chat item */ "you changed role of %@ to %@" = "je veranderde de rol van %1$@ in %2$@"; -/* No comment provided by engineer. */ -"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "U bepaalt via welke server(s) de berichten **ontvangen**, uw contacten de servers die u gebruikt om ze berichten te sturen."; - /* No comment provided by engineer. */ "You could not be verified; please try again." = "U kon niet worden geverifieerd; probeer het opnieuw."; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index b8883ac092..2dde086020 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -65,10 +65,7 @@ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Daj gwiazdkę na GitHub](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Dodaj kontakt**: aby utworzyć nowy link z zaproszeniem lub połączyć się za pomocą otrzymanego linku."; - -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Dodaj nowy kontakt**: aby stworzyć swój jednorazowy kod QR lub link dla kontaktu."; +"**Create 1-time link**: to create and share a new invitation link." = "**Dodaj kontakt**: aby utworzyć nowy link z zaproszeniem lub połączyć się za pomocą otrzymanego linku."; /* No comment provided by engineer. */ "**Create group**: to create a new group." = "**Utwórz grupę**: aby utworzyć nową grupę."; @@ -80,10 +77,10 @@ "**e2e encrypted** video call" = "**szyfrowane e2e** połączenie wideo"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**Bardziej prywatny**: sprawdzanie nowych wiadomości odbywa się co 20 minut. Współdzielony z serwerem SimpleX Chat jest token urządzenia, lecz nie informacje o liczbie kontaktów lub wiadomości."; +"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**Bardziej prywatny**: sprawdzanie nowych wiadomości odbywa się co 20 minut. Współdzielony z serwerem SimpleX Chat jest token urządzenia, lecz nie informacje o liczbie kontaktów lub wiadomości."; /* No comment provided by engineer. */ -"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Najbardziej prywatny**: nie korzystaj z serwera powiadomień SimpleX Chat, wiadomości sprawdzane są co jakiś czas w tle (zależne od tego jak często korzystasz z aplikacji)."; +"**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." = "**Najbardziej prywatny**: nie korzystaj z serwera powiadomień SimpleX Chat, wiadomości sprawdzane są co jakiś czas w tle (zależne od tego jak często korzystasz z aplikacji)."; /* No comment provided by engineer. */ "**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "*Uwaga*: w celach bezpieczeństwa użycie tej samej bazy danych na dwóch różnych urządzeniach spowoduje brak możliwości odszyfrowywania wiadomości z Twoich połączeń."; @@ -92,7 +89,7 @@ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Uwaga**: NIE będziesz w stanie odzyskać lub zmienić kodu dostępu, jeśli go stracisz."; /* No comment provided by engineer. */ -"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Zalecane**: do serwera powiadomień SimpleX Chat wysyłany jest token urządzenia i powiadomienia, lecz nie treść wiadomości, jej rozmiar lub od kogo ona jest."; +"**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Zalecane**: do serwera powiadomień SimpleX Chat wysyłany jest token urządzenia i powiadomienia, lecz nie treść wiadomości, jej rozmiar lub od kogo ona jest."; /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Uwaga**: Natychmiastowe powiadomienia push wymagają zapisania kodu dostępu w Keychain."; @@ -2450,7 +2447,7 @@ "Immediately" = "Natychmiast"; /* No comment provided by engineer. */ -"Immune to spam and abuse" = "Odporność na spam i nadużycia"; +"Immune to spam" = "Odporność na spam i nadużycia"; /* No comment provided by engineer. */ "Import" = "Importuj"; @@ -2549,7 +2546,7 @@ "Instant push notifications will be hidden!\n" = "Natychmiastowe powiadomienia push będą ukryte!\n"; /* No comment provided by engineer. */ -"Instantly" = "Natychmiastowo"; +"Instant" = "Natychmiastowo"; /* No comment provided by engineer. */ "Interface" = "Interfejs"; @@ -2759,7 +2756,7 @@ "Live messages" = "Wiadomości na żywo"; /* No comment provided by engineer. */ -"Local" = "Lokalnie"; +"No push server" = "Lokalnie"; /* No comment provided by engineer. */ "Local name" = "Nazwa lokalna"; @@ -3199,7 +3196,7 @@ "Onion hosts will not be used." = "Hosty onion nie będą używane."; /* No comment provided by engineer. */ -"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Tylko urządzenia klienckie przechowują profile użytkowników, kontakty, grupy i wiadomości wysyłane za pomocą **2-warstwowego szyfrowania end-to-end**."; +"Only client devices store user profiles, contacts, groups, and messages." = "Tylko urządzenia klienckie przechowują profile użytkowników, kontakty, grupy i wiadomości wysyłane za pomocą **2-warstwowego szyfrowania end-to-end**."; /* No comment provided by engineer. */ "Only delete conversation" = "Usuń tylko rozmowę"; @@ -3268,7 +3265,7 @@ "Open user profiles" = "Otwórz profile użytkownika"; /* No comment provided by engineer. */ -"Open-source protocol and code – anybody can run the servers." = "Otwarto źródłowy protokół i kod - każdy może uruchomić serwery."; +"Anybody can host servers." = "Każdy może hostować serwery."; /* No comment provided by engineer. */ "Opening app…" = "Otwieranie aplikacji…"; @@ -3349,10 +3346,10 @@ "Pending" = "Oczekujące"; /* No comment provided by engineer. */ -"People can connect to you only via the links you share." = "Ludzie mogą się z Tobą połączyć tylko poprzez linki, które udostępniasz."; +"You decide who can connect." = "Ty decydujesz, kto może się połączyć."; /* No comment provided by engineer. */ -"Periodically" = "Okresowo"; +"Periodic" = "Okresowo"; /* message decrypt error item */ "Permanent decryption error" = "Stały błąd odszyfrowania"; @@ -3565,7 +3562,7 @@ "Read more" = "Przeczytaj więcej"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "Przeczytaj więcej w [Podręczniku Użytkownika](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Przeczytaj więcej w [Podręczniku Użytkownika](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Przeczytaj więcej w [Poradniku Użytkownika](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; @@ -4464,7 +4461,7 @@ "Thanks to the users – contribute via Weblate!" = "Podziękowania dla użytkowników - wkład za pośrednictwem Weblate!"; /* No comment provided by engineer. */ -"The 1st platform without any user identifiers – private by design." = "Pierwsza platforma bez żadnych identyfikatorów użytkowników – z założenia prywatna."; +"No user identifiers." = "Brak identyfikatorów użytkownika."; /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Aplikacja może powiadamiać Cię, gdy otrzymujesz wiadomości lub prośby o kontakt — otwórz ustawienia, aby włączyć."; @@ -4509,7 +4506,7 @@ "The messages will be marked as moderated for all members." = "Wiadomości zostaną oznaczone jako moderowane dla wszystkich członków."; /* No comment provided by engineer. */ -"The next generation of private messaging" = "Następna generacja prywatnych wiadomości"; +"The future of messaging" = "Następna generacja prywatnych wiadomości"; /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Stara baza danych nie została usunięta podczas migracji, można ją usunąć."; @@ -4599,7 +4596,7 @@ "To make a new connection" = "Aby nawiązać nowe połączenie"; /* No comment provided by engineer. */ -"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "Aby chronić prywatność, zamiast identyfikatorów użytkowników używanych przez wszystkie inne platformy, SimpleX ma identyfikatory dla kolejek wiadomości, oddzielne dla każdego z Twoich kontaktów."; +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Aby chronić prywatność, zamiast identyfikatorów użytkowników używanych przez wszystkie inne platformy, SimpleX ma identyfikatory dla kolejek wiadomości, oddzielne dla każdego z Twoich kontaktów."; /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Aby chronić strefę czasową, pliki obrazów/głosów używają UTC."; @@ -5174,9 +5171,6 @@ /* snd group event chat item */ "you changed role of %@ to %@" = "zmieniłeś rolę %1$@ na %2$@"; -/* No comment provided by engineer. */ -"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "Kontrolujesz przez który serwer(y) **odbierać** wiadomości, Twoje kontakty - serwery, których używasz do wysyłania im wiadomości."; - /* No comment provided by engineer. */ "You could not be verified; please try again." = "Nie można zweryfikować użytkownika; proszę spróbować ponownie."; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 63b7285a45..631280ae84 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -65,10 +65,7 @@ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Поставить звездочку в GitHub](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Добавить контакт**: создать новую ссылку-приглашение или подключиться через полученную ссылку."; - -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Добавить новый контакт**: чтобы создать одноразовый QR код или ссылку для Вашего контакта."; +"**Create 1-time link**: to create and share a new invitation link." = "**Добавить контакт**: создать и поделиться новой ссылкой-приглашением."; /* No comment provided by engineer. */ "**Create group**: to create a new group." = "**Создать группу**: создать новую группу."; @@ -80,10 +77,10 @@ "**e2e encrypted** video call" = "**e2e зашифрованный** видеозвонок"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**Более конфиденциально**: проверять новые сообщения каждые 20 минут. Токен устройства будет отправлен на сервер уведомлений SimpleX Chat, но у сервера не будет информации о количестве контактов и сообщений."; +"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**Более конфиденциально**: проверять новые сообщения каждые 20 минут. Только токен устройства будет отправлен на сервер уведомлений SimpleX Chat, но у сервера не будет информации о количестве контактов и какой либо информации о сообщениях."; /* No comment provided by engineer. */ -"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Самый конфиденциальный**: не использовать сервер уведомлений SimpleX Chat, проверять сообщения периодически в фоновом режиме (зависит от того насколько часто Вы используете приложение)."; +"**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." = "**Самый конфиденциальный**: не использовать сервер уведомлений SimpleX Chat. Сообщения проверяются в фоновом режиме, когда система позволяет, в зависимости от того, как часто Вы используете приложение."; /* No comment provided by engineer. */ "**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Обратите внимание**: использование одной и той же базы данных на двух устройствах нарушит расшифровку сообщений от ваших контактов, как свойство защиты соединений."; @@ -92,7 +89,7 @@ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Внимание**: Вы не сможете восстановить или поменять пароль, если Вы его потеряете."; /* No comment provided by engineer. */ -"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Рекомендовано**: токен устройства и уведомления отправляются на сервер SimpleX Chat, но сервер не получает сами сообщения, их размер или от кого они."; +"**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Рекомендовано**: токен устройства и уведомления отправляются на сервер SimpleX Chat, но сервер не получает сами сообщения, их размер или от кого они."; /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Внимание**: для работы мгновенных уведомлений пароль должен быть сохранен в Keychain."; @@ -2438,7 +2435,7 @@ "How to" = "Инфо"; /* No comment provided by engineer. */ -"How to use it" = "Как использовать"; +"How to use it" = "Про адрес"; /* No comment provided by engineer. */ "How to use your servers" = "Как использовать серверы"; @@ -2474,7 +2471,7 @@ "Immediately" = "Сразу"; /* No comment provided by engineer. */ -"Immune to spam and abuse" = "Защищен от спама"; +"Immune to spam" = "Защищен от спама"; /* No comment provided by engineer. */ "Import" = "Импортировать"; @@ -2576,7 +2573,7 @@ "Instant push notifications will be hidden!\n" = "Мгновенные уведомления будут скрыты!\n"; /* No comment provided by engineer. */ -"Instantly" = "Мгновенно"; +"Instant" = "Мгновенно"; /* No comment provided by engineer. */ "Interface" = "Интерфейс"; @@ -2786,7 +2783,7 @@ "Live messages" = "\"Живые\" сообщения"; /* No comment provided by engineer. */ -"Local" = "Локальные"; +"No push server" = "Локальные"; /* No comment provided by engineer. */ "Local name" = "Локальное имя"; @@ -3226,7 +3223,10 @@ "Onion hosts will not be used." = "Onion хосты не используются."; /* No comment provided by engineer. */ -"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Только пользовательские устройства хранят контакты, группы и сообщения, которые отправляются **с двухуровневым end-to-end шифрованием**."; +"Only client devices store user profiles, contacts, groups, and messages." = "Только пользовательские устройства хранят контакты, группы и сообщения."; + +/* No comment provided by engineer. */ +"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Все сообщения и файлы отправляются с **end-to-end шифрованием**, с постквантовой безопасностью в прямых разговорах."; /* No comment provided by engineer. */ "Only delete conversation" = "Удалить только разговор"; @@ -3295,7 +3295,7 @@ "Open user profiles" = "Открыть профили пользователя"; /* No comment provided by engineer. */ -"Open-source protocol and code – anybody can run the servers." = "Открытый протокол и код - кто угодно может запустить сервер."; +"Anybody can host servers." = "Кто угодно может запустить сервер."; /* No comment provided by engineer. */ "Opening app…" = "Приложение отрывается…"; @@ -3376,10 +3376,10 @@ "Pending" = "В ожидании"; /* No comment provided by engineer. */ -"People can connect to you only via the links you share." = "С Вами можно соединиться только через созданные Вами ссылки."; +"You decide who can connect." = "Вы определяете, кто может соединиться."; /* No comment provided by engineer. */ -"Periodically" = "Периодически"; +"Periodic" = "Периодически"; /* message decrypt error item */ "Permanent decryption error" = "Ошибка расшифровки"; @@ -3592,7 +3592,7 @@ "Read more" = "Узнать больше"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "Узнать больше в [Руководстве пользователя](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Узнать больше в [Руководстве пользователя](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Дополнительная информация в [Руководстве пользователя](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; @@ -4500,7 +4500,7 @@ "Thanks to the users – contribute via Weblate!" = "Благодаря пользователям – добавьте переводы через Weblate!"; /* No comment provided by engineer. */ -"The 1st platform without any user identifiers – private by design." = "Первая в мире платформа без идентификаторов пользователей."; +"No user identifiers." = "Без идентификаторов пользователей."; /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Приложение может посылать Вам уведомления о сообщениях и запросах на соединение - уведомления можно включить в Настройках."; @@ -4545,7 +4545,7 @@ "The messages will be marked as moderated for all members." = "Сообщения будут помечены как удаленные для всех членов группы."; /* No comment provided by engineer. */ -"The next generation of private messaging" = "Новое поколение приватных сообщений"; +"The future of messaging" = "Будущее коммуникаций"; /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Предыдущая версия данных чата не удалена при перемещении, её можно удалить."; @@ -4635,7 +4635,7 @@ "To make a new connection" = "Чтобы соединиться"; /* No comment provided by engineer. */ -"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "Чтобы защитить Вашу конфиденциальность, вместо ID пользователей, которые есть в других платформах, SimpleX использует ID для очередей сообщений, разные для каждого контакта."; +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Чтобы защитить Вашу конфиденциальность, SimpleX использует разные идентификаторы для каждого Вашeго контакта."; /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Чтобы защитить Ваш часовой пояс, файлы картинок и голосовых сообщений используют UTC."; @@ -5210,9 +5210,6 @@ /* snd group event chat item */ "you changed role of %@ to %@" = "Вы поменяли роль члена %1$@ на: %2$@"; -/* No comment provided by engineer. */ -"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "Вы определяете через какие серверы Вы **получаете сообщения**, Ваши контакты - серверы, которые Вы используете для отправки."; - /* No comment provided by engineer. */ "You could not be verified; please try again." = "Верификация не удалась; пожалуйста, попробуйте ещё раз."; diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index 6125694835..d37dd725df 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -52,9 +52,6 @@ /* No comment provided by engineer. */ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[ติดดาวบน GitHub](https://github.com/simplex-chat/simplex-chat)"; -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**เพิ่มผู้ติดต่อใหม่**: เพื่อสร้างคิวอาร์โค้ดแบบใช้ครั้งเดียวหรือลิงก์สำหรับผู้ติดต่อของคุณ"; - /* No comment provided by engineer. */ "**e2e encrypted** audio call" = "การโทรเสียงแบบ **encrypted จากต้นจนจบ**"; @@ -62,16 +59,16 @@ "**e2e encrypted** video call" = "**encrypted จากต้นจนจบ** การสนทนาทางวิดีโอ"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**เป็นส่วนตัวมากขึ้น**: ตรวจสอบข้อความใหม่ทุกๆ 20 นาที โทเค็นอุปกรณ์แชร์กับเซิร์ฟเวอร์ SimpleX Chat แต่ไม่ระบุจำนวนผู้ติดต่อหรือข้อความที่คุณมี"; +"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**เป็นส่วนตัวมากขึ้น**: ตรวจสอบข้อความใหม่ทุกๆ 20 นาที โทเค็นอุปกรณ์แชร์กับเซิร์ฟเวอร์ SimpleX Chat แต่ไม่ระบุจำนวนผู้ติดต่อหรือข้อความที่คุณมี"; /* No comment provided by engineer. */ -"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**ส่วนตัวที่สุด**: ไม่ใช้เซิร์ฟเวอร์การแจ้งเตือนของ SimpleX Chat ตรวจสอบข้อความเป็นระยะในพื้นหลัง (ขึ้นอยู่กับความถี่ที่คุณใช้แอป)"; +"**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." = "**ส่วนตัวที่สุด**: ไม่ใช้เซิร์ฟเวอร์การแจ้งเตือนของ SimpleX Chat ตรวจสอบข้อความเป็นระยะในพื้นหลัง (ขึ้นอยู่กับความถี่ที่คุณใช้แอป)"; /* No comment provided by engineer. */ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**โปรดทราบ**: คุณจะไม่สามารถกู้คืนหรือเปลี่ยนรหัสผ่านได้หากคุณทำรหัสผ่านหาย"; /* No comment provided by engineer. */ -"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**แนะนำ**: โทเค็นอุปกรณ์และการแจ้งเตือนจะถูกส่งไปยังเซิร์ฟเวอร์การแจ้งเตือนของ SimpleX Chat แต่ไม่ใช่เนื้อหาข้อความ ขนาด หรือผู้ที่ส่ง"; +"**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**แนะนำ**: โทเค็นอุปกรณ์และการแจ้งเตือนจะถูกส่งไปยังเซิร์ฟเวอร์การแจ้งเตือนของ SimpleX Chat แต่ไม่ใช่เนื้อหาข้อความ ขนาด หรือผู้ที่ส่ง"; /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**คำเตือน**: การแจ้งเตือนแบบพุชทันทีจำเป็นต้องบันทึกรหัสผ่านไว้ใน Keychain"; @@ -1627,7 +1624,7 @@ "Immediately" = "โดยทันที"; /* No comment provided by engineer. */ -"Immune to spam and abuse" = "มีภูมิคุ้มกันต่อสแปมและการละเมิด"; +"Immune to spam" = "มีภูมิคุ้มกันต่อสแปมและการละเมิด"; /* No comment provided by engineer. */ "Import" = "นำเข้า"; @@ -1696,7 +1693,7 @@ "Instant push notifications will be hidden!\n" = "การแจ้งเตือนโดยทันทีจะถูกซ่อน!\n"; /* No comment provided by engineer. */ -"Instantly" = "ทันที"; +"Instant" = "ทันที"; /* No comment provided by engineer. */ "Interface" = "อินเตอร์เฟซ"; @@ -1840,7 +1837,7 @@ "Live messages" = "ข้อความสด"; /* No comment provided by engineer. */ -"Local" = "ในเครื่อง"; +"No push server" = "ในเครื่อง"; /* No comment provided by engineer. */ "Local name" = "ชื่อภายในเครื่องเท่านั้น"; @@ -2124,7 +2121,7 @@ "Onion hosts will not be used." = "โฮสต์หัวหอมจะไม่ถูกใช้"; /* No comment provided by engineer. */ -"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "เฉพาะอุปกรณ์ไคลเอนต์เท่านั้นที่จัดเก็บโปรไฟล์ผู้ใช้ ผู้ติดต่อ กลุ่ม และข้อความที่ส่งด้วย **การเข้ารหัส encrypt แบบ 2 ชั้น**"; +"Only client devices store user profiles, contacts, groups, and messages." = "เฉพาะอุปกรณ์ไคลเอนต์เท่านั้นที่จัดเก็บโปรไฟล์ผู้ใช้ ผู้ติดต่อ กลุ่ม และข้อความที่ส่งด้วย **การเข้ารหัส encrypt แบบ 2 ชั้น**"; /* No comment provided by engineer. */ "Only group owners can change group preferences." = "เฉพาะเจ้าของกลุ่มเท่านั้นที่สามารถเปลี่ยนค่ากําหนดลักษณะกลุ่มได้"; @@ -2178,7 +2175,7 @@ "Open user profiles" = "เปิดโปรไฟล์ผู้ใช้"; /* No comment provided by engineer. */ -"Open-source protocol and code – anybody can run the servers." = "โปรโตคอลและโค้ดโอเพ่นซอร์ส – ใคร ๆ ก็สามารถเปิดใช้เซิร์ฟเวอร์ได้"; +"Anybody can host servers." = "โปรโตคอลและโค้ดโอเพ่นซอร์ส – ใคร ๆ ก็สามารถเปิดใช้เซิร์ฟเวอร์ได้"; /* member role */ "owner" = "เจ้าของ"; @@ -2208,10 +2205,10 @@ "peer-to-peer" = "เพื่อนต่อเพื่อน"; /* No comment provided by engineer. */ -"People can connect to you only via the links you share." = "ผู้คนสามารถเชื่อมต่อกับคุณผ่านลิงก์ที่คุณแบ่งปันเท่านั้น"; +"You decide who can connect." = "ผู้คนสามารถเชื่อมต่อกับคุณผ่านลิงก์ที่คุณแบ่งปันเท่านั้น"; /* No comment provided by engineer. */ -"Periodically" = "เป็นระยะๆ"; +"Periodic" = "เป็นระยะๆ"; /* message decrypt error item */ "Permanent decryption error" = "ข้อผิดพลาดในการถอดรหัสอย่างถาวร"; @@ -2349,7 +2346,7 @@ "Read more" = "อ่านเพิ่มเติม"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "อ่านเพิ่มเติมใน[คู่มือผู้ใช้](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)"; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "อ่านเพิ่มเติมใน[คู่มือผู้ใช้](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)"; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "อ่านเพิ่มเติมใน[คู่มือผู้ใช้](https://simplex.chat/docs/guide/readme.html#connect-to-friends)"; @@ -2891,7 +2888,7 @@ "Thanks to the users – contribute via Weblate!" = "ขอบคุณผู้ใช้ – มีส่วนร่วมผ่าน Weblate!"; /* No comment provided by engineer. */ -"The 1st platform without any user identifiers – private by design." = "แพลตฟอร์มแรกที่ไม่มีตัวระบุผู้ใช้ - ถูกออกแบบให้เป็นส่วนตัว"; +"No user identifiers." = "แพลตฟอร์มแรกที่ไม่มีตัวระบุผู้ใช้ - ถูกออกแบบให้เป็นส่วนตัว"; /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "แอปสามารถแจ้งให้คุณทราบเมื่อคุณได้รับข้อความหรือคำขอติดต่อ - โปรดเปิดการตั้งค่าเพื่อเปิดใช้งาน"; @@ -2924,7 +2921,7 @@ "The message will be marked as moderated for all members." = "ข้อความจะถูกทำเครื่องหมายว่ากลั่นกรองสำหรับสมาชิกทุกคน"; /* No comment provided by engineer. */ -"The next generation of private messaging" = "การส่งข้อความส่วนตัวรุ่นต่อไป"; +"The future of messaging" = "การส่งข้อความส่วนตัวรุ่นต่อไป"; /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "ฐานข้อมูลเก่าไม่ได้ถูกลบในระหว่างการย้ายข้อมูล แต่สามารถลบได้"; @@ -2972,7 +2969,7 @@ "To make a new connection" = "เพื่อสร้างการเชื่อมต่อใหม่"; /* No comment provided by engineer. */ -"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "เพื่อปกป้องความเป็นส่วนตัว แทนที่จะใช้ ID ผู้ใช้เหมือนที่แพลตฟอร์มอื่นๆใช้ SimpleX มีตัวระบุสำหรับคิวข้อความ โดยแยกจากกันสำหรับผู้ติดต่อแต่ละราย"; +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "เพื่อปกป้องความเป็นส่วนตัว แทนที่จะใช้ ID ผู้ใช้เหมือนที่แพลตฟอร์มอื่นๆใช้ SimpleX มีตัวระบุสำหรับคิวข้อความ โดยแยกจากกันสำหรับผู้ติดต่อแต่ละราย"; /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "ไฟล์ภาพ/เสียงใช้ UTC เพื่อป้องกันเขตเวลา"; @@ -3292,9 +3289,6 @@ /* snd group event chat item */ "you changed role of %@ to %@" = "คุณเปลี่ยนบทบาทของ %1$@ เป็น %2$@"; -/* No comment provided by engineer. */ -"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "คุณควบคุมผ่านเซิร์ฟเวอร์ **เพื่อรับ** ข้อความผู้ติดต่อของคุณ - เซิร์ฟเวอร์ที่คุณใช้เพื่อส่งข้อความถึงพวกเขา"; - /* No comment provided by engineer. */ "You could not be verified; please try again." = "เราไม่สามารถตรวจสอบคุณได้ กรุณาลองอีกครั้ง."; diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index 63ca78bccf..cc250808be 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -65,10 +65,7 @@ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Bize GitHub'da yıldız verin](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Kişi ekle**: yeni bir davet bağlantısı oluşturmak için, ya da aldığın bağlantıyla bağlan."; - -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Yeni kişi ekleyin**: tek seferlik QR Kodunuzu oluşturmak veya kişisel ulaşım bilgileri bağlantısı için."; +"**Create 1-time link**: to create and share a new invitation link." = "**Kişi ekle**: yeni bir davet bağlantısı oluşturmak için, ya da aldığın bağlantıyla bağlan."; /* No comment provided by engineer. */ "**Create group**: to create a new group." = "**Grup oluştur**: yeni bir grup oluşturmak için."; @@ -80,10 +77,10 @@ "**e2e encrypted** video call" = "**uçtan uca şifrelenmiş** görüntülü arama"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**Daha gizli**: her 20 dakikada yeni mesajlar için kontrol et. Cihaz jetonu SimpleX Chat sunucusuyla paylaşılacak, ama ne kadar kişi veya mesaja sahip olduğun paylaşılmayacak."; +"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**Daha gizli**: her 20 dakikada yeni mesajlar için kontrol et. Cihaz jetonu SimpleX Chat sunucusuyla paylaşılacak, ama ne kadar kişi veya mesaja sahip olduğun paylaşılmayacak."; /* No comment provided by engineer. */ -"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**En gizli**: SimpleX Chat bildirim sunucusunu kullanma, arkaplanda mesajları periyodik olarak kontrol edin (uygulamayı ne sıklıkta kullandığınıza bağlıdır)."; +"**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." = "**En gizli**: SimpleX Chat bildirim sunucusunu kullanma, arkaplanda mesajları periyodik olarak kontrol edin (uygulamayı ne sıklıkta kullandığınıza bağlıdır)."; /* No comment provided by engineer. */ "**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Lütfen dikkat**: Aynı veritabanını iki cihazda kullanmak, güvenlik koruması olarak bağlantılarınızdaki mesajların şifresinin çözülmesini engelleyecektir."; @@ -92,7 +89,7 @@ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Lütfen aklınızda bulunsun**: eğer parolanızı kaybederseniz parolanızı değiştirme veya geri kurtarma ihtimaliniz YOKTUR."; /* No comment provided by engineer. */ -"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Önerilen**: cihaz tokeni ve bildirimler SimpleX Chat bildirim sunucularına gönderilir, ama mesajın içeriği, boyutu veya kimden geldiği gönderilmez."; +"**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Önerilen**: cihaz tokeni ve bildirimler SimpleX Chat bildirim sunucularına gönderilir, ama mesajın içeriği, boyutu veya kimden geldiği gönderilmez."; /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Dikkat**: Anında iletilen bildirimlere Anahtar Zinciri'nde kaydedilmiş parola gereklidir."; @@ -2474,7 +2471,7 @@ "Immediately" = "Hemen"; /* No comment provided by engineer. */ -"Immune to spam and abuse" = "Spam ve kötüye kullanıma karşı bağışıklı"; +"Immune to spam" = "Spam ve kötüye kullanıma karşı bağışıklı"; /* No comment provided by engineer. */ "Import" = "İçe aktar"; @@ -2576,7 +2573,7 @@ "Instant push notifications will be hidden!\n" = "Anlık bildirimler gizlenecek!\n"; /* No comment provided by engineer. */ -"Instantly" = "Anında"; +"Instant" = "Anında"; /* No comment provided by engineer. */ "Interface" = "Arayüz"; @@ -2786,7 +2783,7 @@ "Live messages" = "Canlı mesajlar"; /* No comment provided by engineer. */ -"Local" = "Yerel"; +"No push server" = "Yerel"; /* No comment provided by engineer. */ "Local name" = "Yerel isim"; @@ -3226,7 +3223,7 @@ "Onion hosts will not be used." = "Onion ana bilgisayarları kullanılmayacaktır."; /* No comment provided by engineer. */ -"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Yalnızca istemci cihazlar kullanıcı profillerini, kişileri, grupları ve **2 katmanlı uçtan uca şifreleme** ile gönderilen mesajları depolar."; +"Only client devices store user profiles, contacts, groups, and messages." = "Yalnızca istemci cihazlar kullanıcı profillerini, kişileri, grupları ve **2 katmanlı uçtan uca şifreleme** ile gönderilen mesajları depolar."; /* No comment provided by engineer. */ "Only delete conversation" = "Sadece sohbeti sil"; @@ -3295,7 +3292,7 @@ "Open user profiles" = "Kullanıcı profillerini aç"; /* No comment provided by engineer. */ -"Open-source protocol and code – anybody can run the servers." = "Açık kaynak protokolü ve kodu - herhangi biri sunucuları çalıştırabilir."; +"Anybody can host servers." = "Açık kaynak protokolü ve kodu - herhangi biri sunucuları çalıştırabilir."; /* No comment provided by engineer. */ "Opening app…" = "Uygulama açılıyor…"; @@ -3376,10 +3373,10 @@ "Pending" = "Bekleniyor"; /* No comment provided by engineer. */ -"People can connect to you only via the links you share." = "İnsanlar size yalnızca paylaştığınız bağlantılar üzerinden ulaşabilir."; +"You decide who can connect." = "Kimin bağlanabileceğine siz karar verirsiniz."; /* No comment provided by engineer. */ -"Periodically" = "Periyodik olarak"; +"Periodic" = "Periyodik olarak"; /* message decrypt error item */ "Permanent decryption error" = "Kalıcı şifre çözümü hatası"; @@ -3592,7 +3589,7 @@ "Read more" = "Dahasını oku"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "[Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "[Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "[Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; @@ -4500,7 +4497,7 @@ "Thanks to the users – contribute via Weblate!" = "Kullanıcılar için teşekkürler - Weblate aracılığıyla katkıda bulun!"; /* No comment provided by engineer. */ -"The 1st platform without any user identifiers – private by design." = "Herhangi bir kullanıcı tanımlayıcısı olmayan ilk platform - tasarım gereği gizli."; +"No user identifiers." = "Herhangi bir kullanıcı tanımlayıcısı yok."; /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Uygulama, mesaj veya iletişim isteği aldığınızda sizi bilgilendirebilir - etkinleştirmek için lütfen ayarları açın."; @@ -4545,7 +4542,7 @@ "The messages will be marked as moderated for all members." = "Mesajlar tüm üyeler için moderasyonlu olarak işaretlenecektir."; /* No comment provided by engineer. */ -"The next generation of private messaging" = "Gizli mesajlaşmanın yeni nesli"; +"The future of messaging" = "Gizli mesajlaşmanın yeni nesli"; /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Eski veritabanı geçiş sırasında kaldırılmadı, silinebilir."; @@ -4635,7 +4632,7 @@ "To make a new connection" = "Yeni bir bağlantı oluşturmak için"; /* No comment provided by engineer. */ -"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "Gizliliği korumak için, diğer tüm platformlar gibi kullanıcı kimliği kullanmak yerine, SimpleX mesaj kuyrukları için kişilerinizin her biri için ayrı tanımlayıcılara sahiptir."; +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Gizliliği korumak için, diğer tüm platformlar gibi kullanıcı kimliği kullanmak yerine, SimpleX mesaj kuyrukları için kişilerinizin her biri için ayrı tanımlayıcılara sahiptir."; /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Zaman bölgesini korumak için,fotoğraf/ses dosyaları UTC kullanır."; @@ -5210,9 +5207,6 @@ /* snd group event chat item */ "you changed role of %@ to %@" = "%1$@'in yetkisini %2$@ olarak değiştirdiniz"; -/* No comment provided by engineer. */ -"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "Mesajların hangi sunucu(lar)dan **alınacağını**, kişilerinizi - onlara mesaj göndermek için kullandığınız sunucuları - siz kontrol edersiniz."; - /* No comment provided by engineer. */ "You could not be verified; please try again." = "Doğrulanamadınız; lütfen tekrar deneyin."; diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index 2647fe49d0..eb92d191c6 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -65,10 +65,7 @@ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Зірка на GitHub](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Додати контакт**: створити нове посилання-запрошення або підключитися за отриманим посиланням."; - -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Додати новий контакт**: щоб створити одноразовий QR-код або посилання для свого контакту."; +"**Create 1-time link**: to create and share a new invitation link." = "**Додати контакт**: створити нове посилання-запрошення."; /* No comment provided by engineer. */ "**Create group**: to create a new group." = "**Створити групу**: створити нову групу."; @@ -80,10 +77,10 @@ "**e2e encrypted** video call" = "**e2e encrypted** відеодзвінок"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**Більш приватний**: перевіряти нові повідомлення кожні 20 хвилин. Серверу SimpleX Chat передається токен пристрою, але не кількість контактів або повідомлень, які ви маєте."; +"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**Більш приватний**: перевіряти нові повідомлення кожні 20 хвилин. Серверу SimpleX Chat передається токен пристрою, але не кількість контактів або повідомлень, які ви маєте."; /* No comment provided by engineer. */ -"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Найбільш приватний**: не використовуйте сервер сповіщень SimpleX Chat, періодично перевіряйте повідомлення у фоновому режимі (залежить від того, як часто ви користуєтесь додатком)."; +"**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." = "**Найбільш приватний**: не використовуйте сервер сповіщень SimpleX Chat, періодично перевіряйте повідомлення у фоновому режимі (залежить від того, як часто ви користуєтесь додатком)."; /* No comment provided by engineer. */ "**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Зверніть увагу**: використання однієї і тієї ж бази даних на двох пристроях порушить розшифровку повідомлень з ваших з'єднань, як захист безпеки."; @@ -92,7 +89,7 @@ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Зверніть увагу: ви НЕ зможете відновити або змінити пароль, якщо втратите його."; /* No comment provided by engineer. */ -"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Рекомендується**: токен пристрою та сповіщення надсилаються на сервер сповіщень SimpleX Chat, але не вміст повідомлення, його розмір або від кого воно надійшло."; +"**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Рекомендується**: токен пристрою та сповіщення надсилаються на сервер сповіщень SimpleX Chat, але не вміст повідомлення, його розмір або від кого воно надійшло."; /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Попередження**: Для отримання миттєвих пуш-сповіщень потрібна парольна фраза, збережена у брелоку."; @@ -2387,7 +2384,7 @@ "Immediately" = "Негайно"; /* No comment provided by engineer. */ -"Immune to spam and abuse" = "Імунітет до спаму та зловживань"; +"Immune to spam" = "Імунітет до спаму та зловживань"; /* No comment provided by engineer. */ "Import" = "Імпорт"; @@ -2486,7 +2483,7 @@ "Instant push notifications will be hidden!\n" = "Миттєві пуш-сповіщення будуть приховані!\n"; /* No comment provided by engineer. */ -"Instantly" = "Миттєво"; +"Instant" = "Миттєво"; /* No comment provided by engineer. */ "Interface" = "Інтерфейс"; @@ -2693,7 +2690,7 @@ "Live messages" = "Живі повідомлення"; /* No comment provided by engineer. */ -"Local" = "Локально"; +"No push server" = "Локально"; /* No comment provided by engineer. */ "Local name" = "Місцева назва"; @@ -3112,7 +3109,7 @@ "Onion hosts will not be used." = "Onion хости не будуть використовуватися."; /* No comment provided by engineer. */ -"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Тільки клієнтські пристрої зберігають профілі користувачів, контакти, групи та повідомлення, надіслані за допомогою **2-шарового наскрізного шифрування**."; +"Only client devices store user profiles, contacts, groups, and messages." = "Тільки клієнтські пристрої зберігають профілі користувачів, контакти, групи та повідомлення, надіслані за допомогою **2-шарового наскрізного шифрування**."; /* No comment provided by engineer. */ "Only delete conversation" = "Видаляйте тільки розмови"; @@ -3181,7 +3178,7 @@ "Open user profiles" = "Відкрити профілі користувачів"; /* No comment provided by engineer. */ -"Open-source protocol and code – anybody can run the servers." = "Протокол і код з відкритим вихідним кодом - будь-хто може запускати сервери."; +"Anybody can host servers." = "Кожен може хостити сервери."; /* No comment provided by engineer. */ "Opening app…" = "Відкриваємо програму…"; @@ -3256,10 +3253,10 @@ "Pending" = "В очікуванні"; /* No comment provided by engineer. */ -"People can connect to you only via the links you share." = "Люди можуть зв'язатися з вами лише за посиланнями, якими ви ділитеся."; +"You decide who can connect." = "Ви вирішуєте, хто може під\'єднатися."; /* No comment provided by engineer. */ -"Periodically" = "Періодично"; +"Periodic" = "Періодично"; /* message decrypt error item */ "Permanent decryption error" = "Постійна помилка розшифрування"; @@ -3466,7 +3463,7 @@ "Read more" = "Читати далі"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "Читайте більше в [Посібнику користувача](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Читайте більше в [Посібнику користувача](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Читайте більше в [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; @@ -4335,7 +4332,7 @@ "Thanks to the users – contribute via Weblate!" = "Дякуємо користувачам - зробіть свій внесок через Weblate!"; /* No comment provided by engineer. */ -"The 1st platform without any user identifiers – private by design." = "Перша платформа без жодних ідентифікаторів користувачів – приватна за дизайном."; +"No user identifiers." = "Ніяких ідентифікаторів користувачів."; /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Додаток може сповіщати вас, коли ви отримуєте повідомлення або запити на контакт - будь ласка, відкрийте налаштування, щоб увімкнути цю функцію."; @@ -4380,7 +4377,7 @@ "The messages will be marked as moderated for all members." = "Повідомлення будуть позначені як модеровані для всіх учасників."; /* No comment provided by engineer. */ -"The next generation of private messaging" = "Наступне покоління приватних повідомлень"; +"The future of messaging" = "Наступне покоління приватних повідомлень"; /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Стара база даних не була видалена під час міграції, її можна видалити."; @@ -4467,7 +4464,7 @@ "To make a new connection" = "Щоб створити нове з'єднання"; /* No comment provided by engineer. */ -"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "Щоб захистити конфіденційність, замість ідентифікаторів користувачів, які використовуються на всіх інших платформах, SimpleX має ідентифікатори для черг повідомлень, окремі для кожного з ваших контактів."; +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Щоб захистити конфіденційність, замість ідентифікаторів користувачів, які використовуються на всіх інших платформах, SimpleX має ідентифікатори для черг повідомлень, окремі для кожного з ваших контактів."; /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Для захисту часового поясу у файлах зображень/голосу використовується UTC."; @@ -5030,9 +5027,6 @@ /* snd group event chat item */ "you changed role of %@ to %@" = "ви змінили роль %1$@ на %2$@"; -/* No comment provided by engineer. */ -"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "Ви контролюєте, через який(і) сервер(и) **отримувати** повідомлення, ваші контакти - сервери, які ви використовуєте для надсилання їм повідомлень."; - /* No comment provided by engineer. */ "You could not be verified; please try again." = "Вас не вдалося верифікувати, спробуйте ще раз."; diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index 2c3a5e588d..89773a7481 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -65,10 +65,7 @@ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[在 GitHub 上加星](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"**Add contact**: to create a new invitation link, or connect via a link you received." = "**添加联系人**: 创建新的邀请链接,或通过您收到的链接进行连接."; - -/* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**添加新联系人**:为您的联系人创建一次性二维码或者链接。"; +"**Create 1-time link**: to create and share a new invitation link." = "**添加联系人**: 创建新的邀请链接,或通过您收到的链接进行连接."; /* No comment provided by engineer. */ "**Create group**: to create a new group." = "**创建群组**: 创建一个新群组."; @@ -80,10 +77,10 @@ "**e2e encrypted** video call" = "**端到端加密** 视频通话"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**更私密**:每20分钟检查新消息。设备令牌和 SimpleX Chat 服务器共享,但是不会共享有您有多少联系人或者消息。"; +"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**更私密**:每20分钟检查新消息。设备令牌和 SimpleX Chat 服务器共享,但是不会共享有您有多少联系人或者消息。"; /* No comment provided by engineer. */ -"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**最私密**:不使用 SimpleX Chat 通知服务器,在后台定期检查消息(取决于您多经常使用应用程序)。"; +"**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." = "**最私密**:不使用 SimpleX Chat 通知服务器,在后台定期检查消息(取决于您多经常使用应用程序)。"; /* No comment provided by engineer. */ "**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**请注意**: 在两台设备上使用相同的数据库将破坏来自您的连接的消息解密,作为一种安全保护."; @@ -92,7 +89,7 @@ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**请注意**:如果您丢失密码,您将无法恢复或者更改密码。"; /* No comment provided by engineer. */ -"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**推荐**:设备令牌和通知会发送至 SimpleX Chat 通知服务器,但是消息内容、大小或者发送人不会。"; +"**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**推荐**:设备令牌和通知会发送至 SimpleX Chat 通知服务器,但是消息内容、大小或者发送人不会。"; /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**警告**:及时推送通知需要保存在钥匙串的密码。"; @@ -2387,7 +2384,7 @@ "Immediately" = "立即"; /* No comment provided by engineer. */ -"Immune to spam and abuse" = "不受垃圾和骚扰消息影响"; +"Immune to spam" = "不受垃圾和骚扰消息影响"; /* No comment provided by engineer. */ "Import" = "导入"; @@ -2486,7 +2483,7 @@ "Instant push notifications will be hidden!\n" = "即时推送通知将被隐藏!\n"; /* No comment provided by engineer. */ -"Instantly" = "即时"; +"Instant" = "即时"; /* No comment provided by engineer. */ "Interface" = "界面"; @@ -2693,7 +2690,7 @@ "Live messages" = "实时消息"; /* No comment provided by engineer. */ -"Local" = "本地"; +"No push server" = "本地"; /* No comment provided by engineer. */ "Local name" = "本地名称"; @@ -3112,7 +3109,7 @@ "Onion hosts will not be used." = "将不会使用 Onion 主机。"; /* No comment provided by engineer. */ -"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "只有客户端设备存储用户资料、联系人、群组和**双层端到端加密**发送的消息。"; +"Only client devices store user profiles, contacts, groups, and messages." = "只有客户端设备存储用户资料、联系人、群组和**双层端到端加密**发送的消息。"; /* No comment provided by engineer. */ "Only delete conversation" = "仅删除对话"; @@ -3181,7 +3178,7 @@ "Open user profiles" = "打开用户个人资料"; /* No comment provided by engineer. */ -"Open-source protocol and code – anybody can run the servers." = "开源协议和代码——任何人都可以运行服务器。"; +"Anybody can host servers." = "任何人都可以托管服务器。"; /* No comment provided by engineer. */ "Opening app…" = "正在打开应用程序…"; @@ -3256,10 +3253,10 @@ "Pending" = "待定"; /* No comment provided by engineer. */ -"People can connect to you only via the links you share." = "人们只能通过您共享的链接与您建立联系。"; +"You decide who can connect." = "你决定谁可以连接。"; /* No comment provided by engineer. */ -"Periodically" = "定期"; +"Periodic" = "定期"; /* message decrypt error item */ "Permanent decryption error" = "解密错误"; @@ -3466,7 +3463,7 @@ "Read more" = "阅读更多"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "在 [用户指南](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address) 中阅读更多内容。"; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "在 [用户指南](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses) 中阅读更多内容。"; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "阅读更多[User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)。"; @@ -4335,7 +4332,7 @@ "Thanks to the users – contribute via Weblate!" = "感谢用户——通过 Weblate 做出贡献!"; /* No comment provided by engineer. */ -"The 1st platform without any user identifiers – private by design." = "第一个没有任何用户标识符的平台 - 隐私设计."; +"No user identifiers." = "没有用户标识符。"; /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "该应用可以在您收到消息或联系人请求时通知您——请打开设置以启用通知。"; @@ -4380,7 +4377,7 @@ "The messages will be marked as moderated for all members." = "对于所有成员,这些消息将被标记为已审核。"; /* No comment provided by engineer. */ -"The next generation of private messaging" = "下一代私密通讯软件"; +"The future of messaging" = "下一代私密通讯软件"; /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "旧数据库在迁移过程中没有被移除,可以删除。"; @@ -4467,7 +4464,7 @@ "To make a new connection" = "建立新连接"; /* No comment provided by engineer. */ -"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "为了保护隐私,SimpleX使用针对消息队列的标识符,而不是所有其他平台使用的用户ID,每个联系人都有独立的标识符。"; +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "为了保护隐私,SimpleX使用针对消息队列的标识符,而不是所有其他平台使用的用户ID,每个联系人都有独立的标识符。"; /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "为了保护时区,图像/语音文件使用 UTC。"; @@ -5030,9 +5027,6 @@ /* snd group event chat item */ "you changed role of %@ to %@" = "您已将 %1$@ 的角色更改为 %2$@"; -/* No comment provided by engineer. */ -"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "您可以控制接收信息使用的服务器,您的联系人则使用您发送信息时所使用的服务器。"; - /* No comment provided by engineer. */ "You could not be verified; please try again." = "您的身份无法验证,请再试一次。"; From 22d7db89d8457b72be22bbbe4e87254038c26f94 Mon Sep 17 00:00:00 2001 From: Diogo Date: Wed, 27 Nov 2024 20:32:18 +0000 Subject: [PATCH 126/567] ios: database error screens redesign (#5256) * ios: database error screens redesign (wip) * refactor * remove code to simulate errors * fix * fix texts --------- Co-authored-by: Evgeny Poberezkin --- .../Views/Database/DatabaseErrorView.swift | 115 ++++++++++++++---- .../bg.xcloc/Localized Contents/bg.xliff | 6 +- .../bn.xcloc/Localized Contents/bn.xliff | 4 +- .../cs.xcloc/Localized Contents/cs.xliff | 6 +- .../de.xcloc/Localized Contents/de.xliff | 6 +- .../el.xcloc/Localized Contents/el.xliff | 4 +- .../en.xcloc/Localized Contents/en.xliff | 6 +- .../es.xcloc/Localized Contents/es.xliff | 6 +- .../fi.xcloc/Localized Contents/fi.xliff | 6 +- .../fr.xcloc/Localized Contents/fr.xliff | 6 +- .../he.xcloc/Localized Contents/he.xliff | 6 +- .../hu.xcloc/Localized Contents/hu.xliff | 6 +- .../it.xcloc/Localized Contents/it.xliff | 6 +- .../ja.xcloc/Localized Contents/ja.xliff | 6 +- .../nl.xcloc/Localized Contents/nl.xliff | 6 +- .../pl.xcloc/Localized Contents/pl.xliff | 6 +- .../pt.xcloc/Localized Contents/pt.xliff | 4 +- .../ru.xcloc/Localized Contents/ru.xliff | 6 +- .../th.xcloc/Localized Contents/th.xliff | 6 +- .../tr.xcloc/Localized Contents/tr.xliff | 6 +- .../uk.xcloc/Localized Contents/uk.xliff | 6 +- .../Localized Contents/zh-Hans.xliff | 6 +- .../Localized Contents/zh-Hant.xliff | 4 +- apps/ios/bg.lproj/Localizable.strings | 2 +- apps/ios/cs.lproj/Localizable.strings | 2 +- apps/ios/de.lproj/Localizable.strings | 2 +- apps/ios/es.lproj/Localizable.strings | 2 +- apps/ios/fi.lproj/Localizable.strings | 2 +- apps/ios/fr.lproj/Localizable.strings | 2 +- apps/ios/hu.lproj/Localizable.strings | 2 +- apps/ios/it.lproj/Localizable.strings | 2 +- apps/ios/ja.lproj/Localizable.strings | 2 +- apps/ios/nl.lproj/Localizable.strings | 2 +- apps/ios/pl.lproj/Localizable.strings | 2 +- apps/ios/ru.lproj/Localizable.strings | 2 +- apps/ios/th.lproj/Localizable.strings | 2 +- apps/ios/tr.lproj/Localizable.strings | 2 +- apps/ios/uk.lproj/Localizable.strings | 2 +- apps/ios/zh-Hans.lproj/Localizable.strings | 2 +- 39 files changed, 169 insertions(+), 102 deletions(-) diff --git a/apps/ios/Shared/Views/Database/DatabaseErrorView.swift b/apps/ios/Shared/Views/Database/DatabaseErrorView.swift index 9d71e2a788..6222a28fb4 100644 --- a/apps/ios/Shared/Views/Database/DatabaseErrorView.swift +++ b/apps/ios/Shared/Views/Database/DatabaseErrorView.swift @@ -11,6 +11,7 @@ import SimpleXChat struct DatabaseErrorView: View { @EnvironmentObject var m: ChatModel + @EnvironmentObject var theme: AppTheme @State var status: DBMigrationResult @State private var dbKey = "" @State private var storedDBKey = kcDatabasePassword.get() @@ -28,23 +29,39 @@ struct DatabaseErrorView: View { } @ViewBuilder private func databaseErrorView() -> some View { - VStack(alignment: .leading, spacing: 16) { + VStack(alignment: .center, spacing: 20) { switch status { case let .errorNotADatabase(dbFile): if useKeychain && storedDBKey != nil && storedDBKey != "" { titleText("Wrong database passphrase") Text("Database passphrase is different from saved in the keychain.") + .font(.callout) + .foregroundColor(theme.colors.secondary) + .multilineTextAlignment(.center) + .padding(.horizontal, 25) + databaseKeyField(onSubmit: saveAndRunChat) - saveAndOpenButton() - fileNameText(dbFile) + Spacer() + VStack(spacing: 10) { + saveAndOpenButton() + fileNameText(dbFile) + } } else { titleText("Encrypted database") Text("Database passphrase is required to open chat.") + .font(.callout) + .foregroundColor(theme.colors.secondary) + .multilineTextAlignment(.center) + .padding(.horizontal, 25) + .padding(.bottom, 5) + if useKeychain { databaseKeyField(onSubmit: saveAndRunChat) + Spacer() saveAndOpenButton() } else { databaseKeyField(onSubmit: { runChat() }) + Spacer() openChatButton() } } @@ -52,73 +69,105 @@ struct DatabaseErrorView: View { switch migrationError { case let .upgrade(upMigrations): titleText("Database upgrade") - Button("Upgrade and open chat") { runChat(confirmMigrations: .yesUp) } - fileNameText(dbFile) migrationsText(upMigrations.map(\.upName)) + Spacer() + VStack(spacing: 10) { + Button("Upgrade and open chat") { + runChat(confirmMigrations: .yesUp) + }.buttonStyle(OnboardingButtonStyle(isDisabled: false)) + fileNameText(dbFile) + } case let .downgrade(downMigrations): titleText("Database downgrade") - Text("Warning: you may lose some data!").bold() - Button("Downgrade and open chat") { runChat(confirmMigrations: .yesUpDown) } - fileNameText(dbFile) + Text("Warning: you may lose some data!") + .bold() + .padding(.horizontal, 25) + .multilineTextAlignment(.center) + migrationsText(downMigrations) + Spacer() + VStack(spacing: 10) { + Button("Downgrade and open chat") { + runChat(confirmMigrations: .yesUpDown) + }.buttonStyle(OnboardingButtonStyle(isDisabled: false)) + fileNameText(dbFile) + } case let .migrationError(mtrError): titleText("Incompatible database version") - fileNameText(dbFile) - Text("Error: ") + Text(mtrErrorDescription(mtrError)) + fileNameText(dbFile, font: .callout) + errorView(Text(mtrErrorDescription(mtrError))) } case let .errorSQL(dbFile, migrationSQLError): titleText("Database error") - fileNameText(dbFile) - Text("Error: \(migrationSQLError)") + fileNameText(dbFile, font: .callout) + errorView(Text("Error: \(migrationSQLError)")) case .errorKeychain: titleText("Keychain error") - Text("Cannot access keychain to save database password") + errorView(Text("Cannot access keychain to save database password")) case .invalidConfirmation: // this can only happen if incorrect parameter is passed - Text(String("Invalid migration confirmation")).font(.title) + titleText("Invalid migration confirmation") + errorView() + case let .unknown(json): titleText("Database error") - Text("Unknown database error: \(json)") + errorView(Text("Unknown database error: \(json)")) case .ok: EmptyView() } if showRestoreDbButton { - Spacer().frame(height: 10) + Spacer() Text("The attempt to change database passphrase was not completed.") + .multilineTextAlignment(.center) + .padding(.horizontal, 25) + .font(.footnote) + restoreDbButton() } } - .padding() + .padding(.horizontal, 25) + .padding(.top, 75) + .padding(.bottom, 25) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) .onAppear() { showRestoreDbButton = shouldShowRestoreDbButton() } } - private func titleText(_ s: LocalizedStringKey) -> Text { - Text(s).font(.title) + private func titleText(_ s: LocalizedStringKey) -> some View { + Text(s).font(.largeTitle).bold().multilineTextAlignment(.center) } - private func fileNameText(_ f: String) -> Text { - Text("File: \((f as NSString).lastPathComponent)") + private func fileNameText(_ f: String, font: Font = .caption) -> Text { + Text("File: \((f as NSString).lastPathComponent)").font(font) } - private func migrationsText(_ ms: [String]) -> Text { - Text("Migrations: \(ms.joined(separator: ", "))") + private func migrationsText(_ ms: [String]) -> some View { + (Text("Migrations:").font(.subheadline) + Text(verbatim: "\n") + Text(ms.joined(separator: "\n")).font(.caption)) + .multilineTextAlignment(.center) + .padding(.horizontal, 25) } private func databaseKeyField(onSubmit: @escaping () -> Void) -> some View { PassphraseField(key: $dbKey, placeholder: "Enter passphrase…", valid: validKey(dbKey), onSubmit: onSubmit) + .padding(.vertical, 10) + .padding(.horizontal) + .background( + RoundedRectangle(cornerRadius: 10, style: .continuous) + .fill(Color(uiColor: .tertiarySystemFill)) + ) } private func saveAndOpenButton() -> some View { Button("Save passphrase and open chat") { saveAndRunChat() } + .buttonStyle(OnboardingButtonStyle(isDisabled: false)) } private func openChatButton() -> some View { Button("Open chat") { runChat() } + .buttonStyle(OnboardingButtonStyle(isDisabled: false)) } private func saveAndRunChat() { @@ -192,8 +241,9 @@ struct DatabaseErrorView: View { secondaryButton: .cancel() )) } label: { - Text("Restore database backup").foregroundColor(.red) + Text("Restore database backup") } + .buttonStyle(OnboardingButtonStyle(isDisabled: false)) } private func restoreDb() { @@ -208,6 +258,23 @@ struct DatabaseErrorView: View { )) } } + + private func errorView(_ s: Text? = nil) -> some View { + VStack(spacing: 35) { + Image(systemName: "exclamationmark.triangle.fill") + .resizable() + .frame(width: 50, height: 50) + .foregroundColor(.red) + + if let text = s { + text + .multilineTextAlignment(.center) + .font(.footnote) + } + } + .padding() + .frame(maxWidth: .infinity) + } } struct DatabaseErrorView_Previews: PreviewProvider { diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 310b5e8bb3..597041e163 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -4405,9 +4405,9 @@ This is your link for group %@! Миграцията е завършена No comment provided by engineer. - - Migrations: %@ - Миграции: %@ + + Migrations: + Миграции: No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff b/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff index d6fb9a40a4..f7630b9e1f 100644 --- a/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff +++ b/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff @@ -2235,8 +2235,8 @@ Migration is completed No comment provided by engineer. - - Migrations: %@ + + Migrations: No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index 79cb15d1ae..e2e77572c5 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -4253,9 +4253,9 @@ This is your link for group %@! Přenesení dokončeno No comment provided by engineer. - - Migrations: %@ - Migrace: %@ + + Migrations: + Migrace: No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 743c08ed00..4d1508dce3 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -4558,9 +4558,9 @@ Das ist Ihr Link für die Gruppe %@! Die Migration wurde abgeschlossen No comment provided by engineer. - - Migrations: %@ - Migrationen: %@ + + Migrations: + Migrationen: No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff b/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff index d18eb4483c..9a112d12fa 100644 --- a/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff +++ b/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff @@ -2012,8 +2012,8 @@ Available in v5.1 Migration is completed No comment provided by engineer. - - Migrations: %@ + + Migrations: No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 5a2c41379b..9973cfeeba 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -4600,9 +4600,9 @@ This is your link for group %@! Migration is completed No comment provided by engineer. - - Migrations: %@ - Migrations: %@ + + Migrations: + Migrations: No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 59c4bd167f..6161340303 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -4558,9 +4558,9 @@ This is your link for group %@! Migración completada No comment provided by engineer. - - Migrations: %@ - Migraciones: %@ + + Migrations: + Migraciones: No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index c41190e0f1..fa5b2967e3 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -4243,9 +4243,9 @@ This is your link for group %@! Siirto on valmis No comment provided by engineer. - - Migrations: %@ - Siirrot: %@ + + Migrations: + Siirrot: No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 3ed363cb10..91359b5e66 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -4523,9 +4523,9 @@ Voici votre lien pour le groupe %@ ! La migration est terminée No comment provided by engineer. - - Migrations: %@ - Migrations : %@ + + Migrations: + Migrations : No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff b/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff index 219812651a..813eebc01a 100644 --- a/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff +++ b/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff @@ -2497,9 +2497,9 @@ Available in v5.1 ההעברה הושלמה No comment provided by engineer. - - Migrations: %@ - העברות: %@ + + Migrations: + העברות: No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 81cece7794..80ec462622 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -4558,9 +4558,9 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! Az átköltöztetés befejeződött No comment provided by engineer. - - Migrations: %@ - Átköltöztetések: %@ + + Migrations: + Átköltöztetések: No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 3b75e36a86..5b0c2cdb99 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -4558,9 +4558,9 @@ Questo è il tuo link per il gruppo %@! La migrazione è completata No comment provided by engineer. - - Migrations: %@ - Migrazioni: %@ + + Migrations: + Migrazioni: No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 7f97220bc5..12a34e3569 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -4269,9 +4269,9 @@ This is your link for group %@! 移行が完了しました No comment provided by engineer. - - Migrations: %@ - 移行: %@ + + Migrations: + 移行 No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 06ab82cf2a..b7d4260354 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -4558,9 +4558,9 @@ Dit is jouw link voor groep %@! Migratie is voltooid No comment provided by engineer. - - Migrations: %@ - Migraties: %@ + + Migrations: + Migraties: No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 531d50f522..38e6c8991d 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -4548,9 +4548,9 @@ To jest twój link do grupy %@! Migracja została zakończona No comment provided by engineer. - - Migrations: %@ - Migracje: %@ + + Migrations: + Migracje: No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff b/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff index cdadd677f9..e20181e4f7 100644 --- a/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff +++ b/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff @@ -2115,8 +2115,8 @@ Available in v5.1 Migration is completed No comment provided by engineer. - - Migrations: %@ + + Migrations: No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 119f1650a0..558dd682f1 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -4558,9 +4558,9 @@ This is your link for group %@! Перемещение данных завершено No comment provided by engineer. - - Migrations: %@ - Миграции: %@ + + Migrations: + Миграции: No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index e16565b6fa..6fc740ccea 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -4226,9 +4226,9 @@ This is your link for group %@! การโยกย้ายเสร็จสมบูรณ์ No comment provided by engineer. - - Migrations: %@ - การย้ายข้อมูล: %@ + + Migrations: + การย้ายข้อมูล No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index 0b2149e9ce..9c9fe3e253 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -4558,9 +4558,9 @@ Bu senin grup için bağlantın %@! Geçiş tamamlandı No comment provided by engineer. - - Migrations: %@ - Geçişler: %@ + + Migrations: + Geçişler: No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 339f06687d..b641bbe8cf 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -4523,9 +4523,9 @@ This is your link for group %@! Міграцію завершено No comment provided by engineer. - - Migrations: %@ - Міграції: %@ + + Migrations: + Міграції: No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index 3f48211025..1daae62a6d 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -4523,9 +4523,9 @@ This is your link for group %@! 迁移完成 No comment provided by engineer. - - Migrations: %@ - 迁移:%@ + + Migrations: + 迁移 No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff index 1c1ae53673..da4f843974 100644 --- a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff @@ -4781,8 +4781,8 @@ Available in v5.1 訊息 & 檔案 No comment provided by engineer. - - Migrations: %@ + + Migrations: 遷移:%@ No comment provided by engineer. diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index ccfd9a7b98..da890d4ecb 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -2510,7 +2510,7 @@ "Migration is completed" = "Миграцията е завършена"; /* No comment provided by engineer. */ -"Migrations: %@" = "Миграции: %@"; +"Migrations:" = "Миграции:"; /* time unit */ "minutes" = "минути"; diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index a00adef700..611be02606 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -2029,7 +2029,7 @@ "Migration is completed" = "Přenesení dokončeno"; /* No comment provided by engineer. */ -"Migrations: %@" = "Migrace: %@"; +"Migrations:" = "Migrace:"; /* time unit */ "minutes" = "minut"; diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index f526eaf7e1..8ea6a30716 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -2984,7 +2984,7 @@ "Migration is completed" = "Die Migration wurde abgeschlossen"; /* No comment provided by engineer. */ -"Migrations: %@" = "Migrationen: %@"; +"Migrations:" = "Migrationen:"; /* time unit */ "minutes" = "Minuten"; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index c2f982d0d4..10b8bc317c 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -2984,7 +2984,7 @@ "Migration is completed" = "Migración completada"; /* No comment provided by engineer. */ -"Migrations: %@" = "Migraciones: %@"; +"Migrations:" = "Migraciones:"; /* time unit */ "minutes" = "minutos"; diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index 2faab1dbd9..081e8735a1 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -2005,7 +2005,7 @@ "Migration is completed" = "Siirto on valmis"; /* No comment provided by engineer. */ -"Migrations: %@" = "Siirrot: %@"; +"Migrations:" = "Siirrot:"; /* time unit */ "minutes" = "minuuttia"; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index 92b66efb72..4b4a5aaf4d 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -2885,7 +2885,7 @@ "Migration is completed" = "La migration est terminée"; /* No comment provided by engineer. */ -"Migrations: %@" = "Migrations : %@"; +"Migrations:" = "Migrations :"; /* time unit */ "minutes" = "minutes"; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index 5668b5367f..a68a9e11b1 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -2984,7 +2984,7 @@ "Migration is completed" = "Az átköltöztetés befejeződött"; /* No comment provided by engineer. */ -"Migrations: %@" = "Átköltöztetések: %@"; +"Migrations:" = "Átköltöztetések:"; /* time unit */ "minutes" = "perc"; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index 2e2aea3f3c..43fd26e534 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -2984,7 +2984,7 @@ "Migration is completed" = "La migrazione è completata"; /* No comment provided by engineer. */ -"Migrations: %@" = "Migrazioni: %@"; +"Migrations:" = "Migrazioni:"; /* time unit */ "minutes" = "minuti"; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index c2f2717b1b..93735ef2d1 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -2083,7 +2083,7 @@ "Migration is completed" = "移行が完了しました"; /* No comment provided by engineer. */ -"Migrations: %@" = "移行: %@"; +"Migrations:" = "移行"; /* time unit */ "minutes" = "分"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index e6320e6208..94cb3115a9 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -2984,7 +2984,7 @@ "Migration is completed" = "Migratie is voltooid"; /* No comment provided by engineer. */ -"Migrations: %@" = "Migraties: %@"; +"Migrations:" = "Migraties:"; /* time unit */ "minutes" = "minuten"; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index 2dde086020..77c724a6b1 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -2957,7 +2957,7 @@ "Migration is completed" = "Migracja została zakończona"; /* No comment provided by engineer. */ -"Migrations: %@" = "Migracje: %@"; +"Migrations:" = "Migracje:"; /* time unit */ "minutes" = "minuty"; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 631280ae84..272484ac47 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -2984,7 +2984,7 @@ "Migration is completed" = "Перемещение данных завершено"; /* No comment provided by engineer. */ -"Migrations: %@" = "Миграции: %@"; +"Migrations:" = "Миграции:"; /* time unit */ "minutes" = "минут"; diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index d37dd725df..85295df87d 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -1948,7 +1948,7 @@ "Migration is completed" = "การโยกย้ายเสร็จสมบูรณ์"; /* No comment provided by engineer. */ -"Migrations: %@" = "การย้ายข้อมูล: %@"; +"Migrations:" = "การย้ายข้อมูล"; /* time unit */ "minutes" = "นาที"; diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index cc250808be..bbaa1a5657 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -2984,7 +2984,7 @@ "Migration is completed" = "Geçiş tamamlandı"; /* No comment provided by engineer. */ -"Migrations: %@" = "Geçişler: %@"; +"Migrations:" = "Geçişler:"; /* time unit */ "minutes" = "dakikalar"; diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index eb92d191c6..7f6a8bb677 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -2885,7 +2885,7 @@ "Migration is completed" = "Міграцію завершено"; /* No comment provided by engineer. */ -"Migrations: %@" = "Міграції: %@"; +"Migrations:" = "Міграції:"; /* time unit */ "minutes" = "хвилини"; diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index 89773a7481..a15b7d45fe 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -2885,7 +2885,7 @@ "Migration is completed" = "迁移完成"; /* No comment provided by engineer. */ -"Migrations: %@" = "迁移:%@"; +"Migrations:" = "迁移"; /* time unit */ "minutes" = "分钟"; From 096dec2c7b3a2d622e71643c9c6eb9d5e4b13e76 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 27 Nov 2024 23:42:02 +0000 Subject: [PATCH 127/567] ui: translations (#5264) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (Spanish) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Dutch) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/ * Translated using Weblate (Czech) Currently translated at 96.1% (2008 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Arabic) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Vietnamese) Currently translated at 44.0% (921 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Vietnamese) Currently translated at 44.8% (936 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Indonesian) Currently translated at 12.7% (267 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Vietnamese) Currently translated at 45.7% (956 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Korean) Currently translated at 46.2% (967 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ko/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Japanese) Currently translated at 89.4% (1869 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Vietnamese) Currently translated at 46.9% (980 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Indonesian) Currently translated at 12.8% (268 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (French) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/ * Translated using Weblate (French) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Vietnamese) Currently translated at 51.9% (1086 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Vietnamese) Currently translated at 52.8% (1104 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Ukrainian) Currently translated at 96.6% (1782 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/uk/ * Translated using Weblate (Italian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Japanese) Currently translated at 92.3% (1929 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 98.9% (2068 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 50.0% (923 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/pt_BR/ * Translated using Weblate (Greek) Currently translated at 19.0% (397 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/el/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Vietnamese) Currently translated at 55.2% (1155 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Vietnamese) Currently translated at 56.2% (1175 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Korean) Currently translated at 51.1% (1068 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ko/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Korean) Currently translated at 60.8% (1271 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ko/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Vietnamese) Currently translated at 58.0% (1213 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Korean) Currently translated at 63.0% (1317 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ko/ * Translated using Weblate (Indonesian) Currently translated at 16.1% (337 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (Japanese) Currently translated at 92.6% (1936 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/ * Translated using Weblate (Korean) Currently translated at 66.7% (1394 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ko/ * Translated using Weblate (Vietnamese) Currently translated at 59.8% (1251 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Indonesian) Currently translated at 54.6% (1141 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (Korean) Currently translated at 66.7% (1395 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ko/ * Translated using Weblate (Indonesian) Currently translated at 61.0% (1275 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (Vietnamese) Currently translated at 60.8% (1271 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Korean) Currently translated at 67.6% (1414 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ko/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2089 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1843 of 1843 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Vietnamese) Currently translated at 61.7% (1291 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Vietnamese) Currently translated at 62.6% (1308 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Lithuanian) Currently translated at 83.6% (1747 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/lt/ * Translated using Weblate (Vietnamese) Currently translated at 62.7% (1310 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Indonesian) Currently translated at 61.2% (1279 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (Vietnamese) Currently translated at 63.6% (1330 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Vietnamese) Currently translated at 64.3% (1344 of 2089 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * process localizations * ru * export localizations --------- Co-authored-by: No name Co-authored-by: M1K4 Co-authored-by: zenobit Co-authored-by: summoner001 Co-authored-by: jonnysemon Co-authored-by: tuananh-ng <158744840+tuananh-ng@users.noreply.github.com> Co-authored-by: Bezruchenko Simon Co-authored-by: billy appetie Co-authored-by: 장재원 Co-authored-by: Miyu Sakatsuki Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com> Co-authored-by: aquaticexpectancy Co-authored-by: Random Co-authored-by: Dampuzakura Co-authored-by: Luis Henrique Rodrigues Dos Santos Co-authored-by: Dinos B Co-authored-by: translatorforkr Co-authored-by: d4f5409d Co-authored-by: Rafi Co-authored-by: Vaclovas Intas --- .../es.xcloc/Localized Contents/es.xliff | 1 - .../fr.xcloc/Localized Contents/fr.xliff | 75 +- .../hu.xcloc/Localized Contents/hu.xliff | 156 +-- .../it.xcloc/Localized Contents/it.xliff | 2 +- .../Localized Contents/pt-BR.xliff | 84 ++ .../ru.xcloc/Localized Contents/ru.xliff | 2 +- .../uk.xcloc/Localized Contents/uk.xliff | 3 + apps/ios/bg.lproj/Localizable.strings | 144 +-- apps/ios/cs.lproj/Localizable.strings | 131 +-- apps/ios/de.lproj/Localizable.strings | 153 +-- apps/ios/es.lproj/Localizable.strings | 150 +-- apps/ios/fi.lproj/Localizable.strings | 131 +-- apps/ios/fr.lproj/Localizable.strings | 349 ++++-- apps/ios/hu.lproj/Localizable.strings | 307 ++--- apps/ios/it.lproj/Localizable.strings | 155 +-- apps/ios/ja.lproj/Localizable.strings | 131 +-- apps/ios/nl.lproj/Localizable.strings | 153 +-- apps/ios/pl.lproj/Localizable.strings | 153 +-- apps/ios/ru.lproj/Localizable.strings | 159 +-- apps/ios/th.lproj/Localizable.strings | 131 +-- apps/ios/tr.lproj/Localizable.strings | 153 +-- apps/ios/uk.lproj/Localizable.strings | 162 +-- apps/ios/zh-Hans.lproj/Localizable.strings | 153 +-- .../commonMain/resources/MR/ar/strings.xml | 4 +- .../commonMain/resources/MR/el/strings.xml | 155 +++ .../commonMain/resources/MR/fr/strings.xml | 71 +- .../commonMain/resources/MR/hu/strings.xml | 179 ++- .../commonMain/resources/MR/in/strings.xml | 1028 ++++++++++++++++- .../commonMain/resources/MR/it/strings.xml | 2 +- .../commonMain/resources/MR/ja/strings.xml | 89 +- .../commonMain/resources/MR/ko/strings.xml | 683 +++++++++-- .../commonMain/resources/MR/lt/strings.xml | 4 + .../resources/MR/pt-rBR/strings.xml | 1 + .../commonMain/resources/MR/uk/strings.xml | 10 + .../commonMain/resources/MR/vi/strings.xml | 453 ++++++++ 35 files changed, 3584 insertions(+), 2133 deletions(-) diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 6161340303..01e534f424 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -4823,7 +4823,6 @@ This is your link for group %@! No push server - No push server No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 91359b5e66..ecea1c6eb7 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -132,6 +132,7 @@ %1$@, %2$@ + %1$@, %2$@ format for date separator in chat @@ -156,18 +157,22 @@ %d file(s) are still being downloaded. + %d fichier(s) en cours de téléchargement. forward confirmation reason %d file(s) failed to download. + Le téléchargement de %d fichier(s) a échoué. forward confirmation reason %d file(s) were deleted. + Le(s) fichier(s) %d a(ont) été supprimé(s). forward confirmation reason %d file(s) were not downloaded. + Le(s) fichier(s) %d n'a (n'ont) pas été téléchargé(s). forward confirmation reason @@ -177,6 +182,7 @@ %d messages not forwarded + %d messages non transférés alert title @@ -954,6 +960,7 @@ App session + Session de l'app No comment provided by engineer. @@ -1063,6 +1070,7 @@ Auto-accept settings + Paramètres de réception automatique alert title @@ -1092,6 +1100,7 @@ Better calls + Appels améliorés No comment provided by engineer. @@ -1101,6 +1110,7 @@ Better message dates. + Meilleures dates de messages. No comment provided by engineer. @@ -1115,14 +1125,17 @@ Better notifications + Notifications améliorées No comment provided by engineer. Better security ✅ + Sécurité accrue ✅ No comment provided by engineer. Better user experience + Une meilleure expérience pour l'utilisateur No comment provided by engineer. @@ -1413,6 +1426,7 @@ Chat preferences were changed. + Les préférences de discussion ont été modifiées. alert message @@ -1864,6 +1878,7 @@ Il s'agit de votre propre lien unique ! Corner + Coin No comment provided by engineer. @@ -1996,6 +2011,7 @@ Il s'agit de votre propre lien unique ! Customizable message shape. + Forme des messages personnalisable. No comment provided by engineer. @@ -2295,6 +2311,7 @@ Il s'agit de votre propre lien unique ! Delete or moderate up to 200 messages. + Supprimer ou modérer jusqu'à 200 messages. No comment provided by engineer. @@ -2553,6 +2570,7 @@ Il s'agit de votre propre lien unique ! Do not use credentials with proxy. + Ne pas utiliser d'identifiants avec le proxy. No comment provided by engineer. @@ -2598,6 +2616,7 @@ Il s'agit de votre propre lien unique ! Download files + Télécharger les fichiers alert action @@ -2893,6 +2912,7 @@ Il s'agit de votre propre lien unique ! Error changing connection profile + Erreur lors du changement de profil de connexion No comment provided by engineer. @@ -2907,6 +2927,7 @@ Il s'agit de votre propre lien unique ! Error changing to incognito! + Erreur lors du passage en mode incognito ! No comment provided by engineer. @@ -3030,6 +3051,7 @@ Il s'agit de votre propre lien unique ! Error migrating settings + Erreur lors de la migration des paramètres No comment provided by engineer. @@ -3133,6 +3155,7 @@ Il s'agit de votre propre lien unique ! Error switching profile + Erreur lors du changement de profil No comment provided by engineer. @@ -3281,6 +3304,8 @@ Il s'agit de votre propre lien unique ! File errors: %@ + Erreurs de fichier : +%@ alert message @@ -3436,6 +3461,7 @@ Il s'agit de votre propre lien unique ! Forward %d message(s)? + Transférer %d message(s) ? alert title @@ -3445,14 +3471,17 @@ Il s'agit de votre propre lien unique ! Forward messages + Transférer les messages alert action Forward messages without files? + Transférer les messages sans les fichiers ? alert message Forward up to 20 messages at once. + Transférez jusqu'à 20 messages à la fois. No comment provided by engineer. @@ -3467,6 +3496,7 @@ Il s'agit de votre propre lien unique ! Forwarding %lld messages + Transfert des %lld messages No comment provided by engineer. @@ -3768,6 +3798,7 @@ Erreur : %2$@ IP address + Adresse IP No comment provided by engineer. @@ -3848,6 +3879,8 @@ Erreur : %2$@ Improved delivery, reduced traffic usage. More improvements are coming soon! + Amélioration de la distribution, réduction de l'utilisation du trafic. +D'autres améliorations sont à venir ! No comment provided by engineer. @@ -4402,6 +4435,7 @@ Voici votre lien pour le groupe %@ ! Message shape + Forme du message No comment provided by engineer. @@ -4456,6 +4490,7 @@ Voici votre lien pour le groupe %@ ! Messages were deleted after you selected them. + Les messages ont été supprimés après avoir été sélectionnés. alert message @@ -4627,10 +4662,12 @@ Voici votre lien pour le groupe %@ ! New SOCKS credentials will be used every time you start the app. + De nouveaux identifiants SOCKS seront utilisés chaque fois que vous démarrerez l'application. No comment provided by engineer. New SOCKS credentials will be used for each server. + De nouveaux identifiants SOCKS seront utilisées pour chaque serveur. No comment provided by engineer. @@ -4771,10 +4808,12 @@ Voici votre lien pour le groupe %@ ! No permission to record speech + Enregistrement des conversations non autorisé No comment provided by engineer. No permission to record video + Enregistrement de la vidéo non autorisé No comment provided by engineer. @@ -4825,6 +4864,7 @@ Voici votre lien pour le groupe %@ ! Nothing to forward! + Rien à transférer ! alert title @@ -5042,7 +5082,7 @@ Nécessite l'activation d'un VPN. Or show this code - Ou présenter ce code + Ou montrez ce code No comment provided by engineer. @@ -5057,6 +5097,8 @@ Nécessite l'activation d'un VPN. Other file errors: %@ + Autres erreurs de fichiers : +%@ alert message @@ -5096,6 +5138,7 @@ Nécessite l'activation d'un VPN. Password + Mot de passe No comment provided by engineer. @@ -5244,6 +5287,7 @@ Erreur : %@ Port + Port No comment provided by engineer. @@ -5434,6 +5478,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Proxy requires password + Le proxy est protégé par un mot de passe No comment provided by engineer. @@ -5658,6 +5703,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Remove archive? + Supprimer l'archive ? No comment provided by engineer. @@ -5850,6 +5896,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. SOCKS proxy + proxy SOCKS No comment provided by engineer. @@ -5948,6 +5995,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Save your profile? + Sauvegarder votre profil ? alert title @@ -5972,6 +6020,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Saving %lld messages + Sauvegarde de %lld messages No comment provided by engineer. @@ -5981,7 +6030,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Scan / Paste link - Scanner / Coller le lien + Scanner / Coller un lien No comment provided by engineer. @@ -6056,6 +6105,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Select chat profile + Sélectionner un profil de discussion No comment provided by engineer. @@ -6270,6 +6320,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Server + Serveur No comment provided by engineer. @@ -6410,6 +6461,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Settings were changed. + Les paramètres ont été modifiés. alert message @@ -6462,11 +6514,12 @@ Activez-le dans les paramètres *Réseau et serveurs*. Share profile + Partager le profil No comment provided by engineer. Share this 1-time invite link - Partager ce lien d'invitation unique + Partagez ce lien d'invitation unique No comment provided by engineer. @@ -6609,6 +6662,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. SimpleX protocols reviewed by Trail of Bits. + Protocoles SimpleX audité par Trail of Bits. No comment provided by engineer. @@ -6643,6 +6697,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Some app settings were not migrated. + Certains paramètres de l'application n'ont pas été migrés. No comment provided by engineer. @@ -6792,10 +6847,12 @@ Activez-le dans les paramètres *Réseau et serveurs*. Switch audio and video during the call. + Passer de l'audio à la vidéo pendant l'appel. No comment provided by engineer. Switch chat profile for 1-time invitations. + Changer de profil de chat pour les invitations à usage unique. No comment provided by engineer. @@ -6835,6 +6892,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Tail + Queue No comment provided by engineer. @@ -7042,6 +7100,7 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. The uploaded database archive will be permanently removed from the servers. + L'archive de la base de données envoyée sera définitivement supprimée des serveurs. No comment provided by engineer. @@ -7140,7 +7199,7 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. To connect, your contact can scan QR code or use the link in the app. - Pour se connecter, votre contact peut scanner le code QR ou utiliser le lien dans l'application. + Pour se connecter, votre contact peut scanner un code QR ou utiliser un lien dans l'app. No comment provided by engineer. @@ -7185,10 +7244,12 @@ Vous serez invité à confirmer l'authentification avant que cette fonction ne s To record speech please grant permission to use Microphone. + Si vous souhaitez enregistrer une conversation, veuillez autoriser l'utilisation du microphone. No comment provided by engineer. To record video please grant permission to use Camera. + Si vous souhaitez enregistrer une vidéo, veuillez autoriser l'utilisation de la caméra. No comment provided by engineer. @@ -7476,6 +7537,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Use SOCKS proxy + Utiliser un proxy SOCKS No comment provided by engineer. @@ -7562,6 +7624,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Username + Nom d'utilisateur No comment provided by engineer. @@ -8183,6 +8246,7 @@ Répéter la demande de connexion ? Your chat preferences + Vos préférences de discussion alert title @@ -8192,6 +8256,7 @@ Répéter la demande de connexion ? Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Votre connexion a été déplacée vers %@ mais une erreur inattendue s'est produite lors de la redirection vers le profil. No comment provided by engineer. @@ -8211,6 +8276,7 @@ Répéter la demande de connexion ? Your credentials may be sent unencrypted. + Vos informations d'identification peuvent être envoyées non chiffrées. No comment provided by engineer. @@ -8250,6 +8316,7 @@ Répéter la demande de connexion ? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + Votre profil a été modifié. Si vous l'enregistrez, le profil mis à jour sera envoyé à tous vos contacts. alert message diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 80ec462622..bca18b73e6 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -247,7 +247,7 @@ %lld messages blocked by admin - %lld üzenetet letiltott az admin + %lld üzenetet letiltott az adminisztrátor No comment provided by engineer. @@ -542,13 +542,13 @@ A separate TCP connection will be used **for each chat profile you have in the app**. - A rendszer külön TCP-kapcsolatot fog használni **az alkalmazásban található minden csevegési profilhoz**. + **Az összes csevegési profiljához az alkalmazásban** külön TCP-kapcsolat (és SOCKS-hitelesítőadat) lesz használva. No comment provided by engineer. A separate TCP connection will be used **for each contact and group member**. **Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail. - **Minden egyes kapcsolathoz és csoporttaghoz** külön TCP-kapcsolat lesz használva. + **Az összes ismerőséhez és csoporttaghoz** külön TCP-kapcsolat (és SOCKS-hitelesítőadat) lesz használva. **Megjegyzés:** ha sok kapcsolata van, az akkumulátor-használat és az adatforgalom jelentősen megnövekedhet, és néhány kapcsolódási kísérlet sikertelen lehet. No comment provided by engineer. @@ -696,12 +696,12 @@ Admins can block a member for all. - Az adminok egy tagot mindenki számára letilthatnak. + Az adminisztrátorok egy tagot a csoport összes tagja számára letilthatnak. No comment provided by engineer. Admins can create the links to join groups. - Az adminok hivatkozásokat hozhatnak létre a csoportokhoz való kapcsolódáshoz. + Az adminisztrátorok hivatkozásokat hozhatnak létre a csoportokhoz való kapcsolódáshoz. No comment provided by engineer. @@ -716,27 +716,27 @@ All app data is deleted. - Minden alkalmazásadat törölve. + Az összes alkalmazásadat törölve. No comment provided by engineer. All chats and messages will be deleted - this cannot be undone! - Minden csevegés és üzenet törlésre kerül - ez a művelet nem vonható vissza! + Az összes csevegés és üzenet törlésre kerül - ez a művelet nem vonható vissza! No comment provided by engineer. All data is erased when it is entered. - A jelkód megadása után minden adat törlésre kerül. + A jelkód megadása után az összes adat törlésre kerül. No comment provided by engineer. All data is private to your device. - Minden adat biztonságban van az eszközén. + Az összes adat biztonságban van az eszközén. No comment provided by engineer. All group members will remain connected. - Minden csoporttag kapcsolódva marad. + Az összes csoporttag kapcsolatban marad. No comment provided by engineer. @@ -745,27 +745,27 @@ All messages will be deleted - this cannot be undone! - Minden üzenet törlésre kerül – ez a művelet nem vonható vissza! + Az összes üzenet törlésre kerül – ez a művelet nem vonható vissza! No comment provided by engineer. All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you. - Minden üzenet törlésre kerül - ez a művelet nem vonható vissza! Az üzenetek CSAK az Ön számára törlődnek. + Az összes üzenet törlésre kerül - ez a művelet nem vonható vissza! Az üzenetek CSAK az Ön számára törlődnek. No comment provided by engineer. All new messages from %@ will be hidden! - Minden új üzenet elrejtésre kerül tőle: %@! + Az összes új üzenet elrejtésre kerül tőle: %@! No comment provided by engineer. All profiles - Minden profil + Összes profil profile dropdown All your contacts will remain connected. - Minden ismerősével kapcsolatban marad. + Az összes ismerősével kapcsolatban marad. No comment provided by engineer. @@ -775,7 +775,7 @@ All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. - Minden ismerőse, a beszélgetései és a fájljai biztonságosan titkosításra kerülnek, melyek részletekben feltöltődnek a beállított XFTP-közvetítő-kiszolgálóra. + Az összes ismerőse, -beszélgetése és -fájlja biztonságosan titkosításra kerülnek, melyek részletekben feltöltődnek a beállított XFTP-közvetítő-kiszolgálóra. No comment provided by engineer. @@ -945,7 +945,7 @@ App icon - Alkalmazás ikon + Alkalmazásikon No comment provided by engineer. @@ -1150,7 +1150,7 @@ Block for all - Letiltás mindenki számára + Letiltás az összes tag számára No comment provided by engineer. @@ -1165,7 +1165,7 @@ Block member for all? - Mindenki számára letiltja ezt a tagot? + Az összes tag számára letiltja ezt a tagot? No comment provided by engineer. @@ -1175,7 +1175,7 @@ Blocked by admin - Az admin letiltotta + Az adminisztrátor letiltotta No comment provided by engineer. @@ -1255,12 +1255,12 @@ Can't invite contact! - Ismerős meghívása nem lehetséges! + Nem lehet meghívni az ismerőst! No comment provided by engineer. Can't invite contacts! - Ismerősök meghívása nem lehetséges! + Nem lehet meghívni az ismerősöket! No comment provided by engineer. @@ -1605,7 +1605,7 @@ Confirm files from unknown servers. - Ismeretlen kiszolgálókról származó fájlok jóváhagyása. + Ismeretlen kiszolgálókról származó fájlok megerősítése. No comment provided by engineer. @@ -1694,12 +1694,12 @@ Ez az Ön egyszer használható hivatkozása! Connect with %@ - Kapcsolódás ezzel: %@ + Kapcsolódás a következővel: %@ No comment provided by engineer. Connected - Kapcsolódva + Kapcsolódott No comment provided by engineer. @@ -2176,7 +2176,7 @@ Ez az Ön egyszer használható hivatkozása! Delete all files - Minden fájl törlése + Az összes fájl törlése No comment provided by engineer. @@ -2241,12 +2241,12 @@ Ez az Ön egyszer használható hivatkozása! Delete files for all chat profiles - Fájlok törlése minden csevegési profilból + Fájlok törlése az összes csevegési profilból No comment provided by engineer. Delete for everyone - Törlés mindenkinél + Törlés az összes tagnál chat feature @@ -2485,7 +2485,7 @@ Ez az Ön egyszer használható hivatkozása! Disable for all - Letiltás mindenki számára + Letiltás az összes tag számára No comment provided by engineer. @@ -2699,7 +2699,7 @@ Ez az Ön egyszer használható hivatkozása! Enable for all - Engedélyezés mindenki számára + Engedélyezés az összes tag számára No comment provided by engineer. @@ -2744,7 +2744,7 @@ Ez az Ön egyszer használható hivatkozása! Enabled for - Engedélyezve + Számukra engedélyezve: No comment provided by engineer. @@ -3330,7 +3330,7 @@ Ez az Ön egyszer használható hivatkozása! File will be deleted from servers. - A fájl törölve lesz a kiszolgálóról. + A fájl törölve lesz a kiszolgálókról. No comment provided by engineer. @@ -3700,7 +3700,7 @@ Hiba: %2$@ Group will be deleted for all members - this cannot be undone! - A csoport törlésre kerül minden tag számára - ez a művelet nem vonható vissza! + A csoport törlésre kerül az összes tag számára - ez a művelet nem vonható vissza! No comment provided by engineer. @@ -3710,7 +3710,7 @@ Hiba: %2$@ Help - Segítség + Súgó No comment provided by engineer. @@ -3930,7 +3930,7 @@ További fejlesztések hamarosan! Incognito mode protects your privacy by using a new random profile for each contact. - Az inkognitómód védi személyes adatait azáltal, hogy minden ismerőshöz új véletlenszerű profilt használ. + Az inkognitómód védi személyes adatait azáltal, hogy az összes ismerőséhez új, véletlenszerű profilt használ. No comment provided by engineer. @@ -4160,7 +4160,7 @@ További fejlesztések hamarosan! Join your group? This is your link for group %@! Csatlakozik a csoportjához? -Ez az Ön hivatkozása a(z) %@ csoporthoz! +Ez az Ön hivatkozása a(z) %@ nevű csoporthoz! No comment provided by engineer. @@ -4310,12 +4310,12 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! Mark deleted for everyone - Jelölje meg mindenki számára töröltként + Jelölje meg az összes tag számára töröltként No comment provided by engineer. Mark read - Olvasottnak jelölés + Megjelölés olvasottként No comment provided by engineer. @@ -4355,7 +4355,7 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! Member role will be changed to "%@". All group members will be notified. - A tag szerepköre meg fog változni erre: „%@”. A csoport minden tagja értesítést kap róla. + A tag szerepköre meg fog változni erre: „%@”. A csoportban az összes tag értesítve lesz. No comment provided by engineer. @@ -4667,7 +4667,7 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! New SOCKS credentials will be used for each server. - Minden egyes kiszolgálóhoz új SOCKS-hitelesítő-adatok legyenek használva. + Az összes kiszolgálóhoz új, SOCKS-hitelesítő-adatok legyenek használva. No comment provided by engineer. @@ -4770,7 +4770,7 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! No direct connection yet, message is forwarded by admin. - Még nincs közvetlen kapcsolat, az üzenetet az admin továbbítja. + Még nincs közvetlen kapcsolat, az üzenetet az adminisztrátor továbbítja. item status description @@ -4885,7 +4885,7 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz! Now admins can: - delete members' messages. - disable members ("observer" role) - Most már az adminok is: + Most már az adminisztrátorok is: - törölhetik a tagok üzeneteit. - letilthatnak tagokat („megfigyelő” szerepkör) No comment provided by engineer. @@ -5220,7 +5220,7 @@ Minden további problémát osszon meg a fejlesztőkkel. Please check your network connection with %@ and try again. - Ellenőrizze a hálózati kapcsolatát a(z) %@ segítségével, és próbálja újra. + Ellenőrizze a hálózati kapcsolatát a következővel: %@, és próbálja újra. No comment provided by engineer. @@ -5242,7 +5242,7 @@ Hiba: %@ Please contact group admin. - Lépjen kapcsolatba a csoport adminnal. + Lépjen kapcsolatba a csoport adminisztrátorával. No comment provided by engineer. @@ -5517,7 +5517,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Read - Olvasd el + Olvasott swipe action @@ -5597,7 +5597,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Receiving file will be stopped. - A fájl fogadása leállt. + A fájl fogadása le fog állni. No comment provided by engineer. @@ -5622,7 +5622,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Reconnect - Újrakapcsolás + Újrakapcsolódás No comment provided by engineer. @@ -5632,12 +5632,12 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Reconnect all servers - Újrakapcsolódás minden kiszolgálóhoz + Újrakapcsolódás az összes kiszolgálóhoz No comment provided by engineer. Reconnect all servers? - Újrakapcsolódás minden kiszolgálóhoz? + Újrakapcsolódás az összes kiszolgálóhoz? No comment provided by engineer. @@ -5788,12 +5788,12 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Reset all statistics - Minden statisztika visszaállítása + Az összes statisztika visszaállítása No comment provided by engineer. Reset all statistics? - Minden statisztika visszaállítása? + Az összes statisztika visszaállítása? No comment provided by engineer. @@ -6070,7 +6070,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Search or paste SimpleX link - Keresés, vagy SimpleX-hivatkozás beillesztése + Keresés vagy SimpleX-hivatkozás beillesztése No comment provided by engineer. @@ -6230,17 +6230,17 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Sending delivery receipts will be enabled for all contacts in all visible chat profiles. - A kézbesítési jelentések küldése engedélyezésre kerül az összes látható csevegési profilban lévő minden ismerős számára. + A kézbesítési jelentések küldése engedélyezésre kerül az összes látható csevegési profilban lévő összes ismerőse számára. No comment provided by engineer. Sending delivery receipts will be enabled for all contacts. - A kézbesítési jelentés küldése minden ismerőse számára engedélyezésre kerül. + A kézbesítési jelentés küldése az összes ismerőse számára engedélyezésre kerül. No comment provided by engineer. Sending file will be stopped. - A fájl küldése leállt. + A fájl küldése le fog állni. No comment provided by engineer. @@ -6637,7 +6637,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. SimpleX group link - SimpleX csoporthivatkozás + SimpleX-csoporthivatkozás simplex link type @@ -6777,7 +6777,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. - A csevegés megállítása a csevegési adatbázis exportálásához, importálásához vagy törléséhez. A csevegés megállítása alatt nem tud üzeneteket fogadni és küldeni. + A csevegés megállítása a csevegési adatbázis exportálásához, importálásához vagy törléséhez. A csevegés megállításakor nem tud üzeneteket fogadni és küldeni. No comment provided by engineer. @@ -7003,7 +7003,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. The code you scanned is not a SimpleX link QR code. - A beolvasott QR-kód nem egy SimpleX QR-kód hivatkozás. + A beolvasott QR-kód nem egy SimpleX-QR-kód-hivatkozás. No comment provided by engineer. @@ -7042,22 +7042,22 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. The message will be deleted for all members. - Az üzenet minden tag számára törlésre kerül. + Az üzenet az összes tag számára törlésre kerül. No comment provided by engineer. The message will be marked as moderated for all members. - Az üzenet minden tag számára moderáltként lesz megjelölve. + Az üzenet az összes tag számára moderáltként lesz megjelölve. No comment provided by engineer. The messages will be deleted for all members. - Az üzenetek minden tag számára törlésre kerülnek. + Az üzenetek az összes tag számára törlésre kerülnek. No comment provided by engineer. The messages will be marked as moderated for all members. - Az üzenetek moderáltként lesznek megjelölve minden tag számára. + Az üzenetek az összes tag számára moderáltként lesznek megjelölve. No comment provided by engineer. @@ -7114,7 +7114,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. These settings are for your current profile **%@**. - Ezek a beállítások a jelenlegi **%@** profiljára vonatkoznak. + Ezek a beállítások csak a jelenlegi (**%@**) profiljára vonatkoznak. No comment provided by engineer. @@ -7184,7 +7184,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. This setting applies to messages in your current chat profile **%@**. - Ez a beállítás a jelenlegi **%@** profiljában lévő üzenetekre érvényes. + Ez a beállítás csak a jelenlegi (**%@**) profiljában lévő üzenetekre vonatkozik. No comment provided by engineer. @@ -7218,7 +7218,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. To protect timezone, image/voice files use UTC. - Az időzóna védelme érdekében a kép-/hangfájlok UTC-t használnak. + Az időzóna védelmének érdekében a kép-/hangfájlok UTC-t használnak. No comment provided by engineer. @@ -7235,7 +7235,7 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll To protect your privacy, SimpleX uses separate IDs for each of your contacts. - Az adatvédelem érdekében, a más csevegési platformokon megszokott felhasználó-azonosítók helyett, a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, minden egyes ismerőshöz egy-egy különbözőt. + Az adatvédelem érdekében (a más csevegési platformokon megszokott felhasználó-azonosítók helyett) a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, az összes ismerőséhez különbözőt. No comment provided by engineer. @@ -7347,7 +7347,7 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll Unblock for all - Letiltás feloldása mindenki számára + Letiltás feloldása az összes tag számára No comment provided by engineer. @@ -7357,7 +7357,7 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll Unblock member for all? - Mindenki számára feloldja a tag letiltását? + Az összes tag számára feloldja a tag letiltását? No comment provided by engineer. @@ -7807,7 +7807,7 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc What's new - Milyen újdonságok vannak + Újdonságok No comment provided by engineer. @@ -7906,7 +7906,7 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc You allow - Engedélyezte + Ön engedélyezi No comment provided by engineer. @@ -7931,7 +7931,7 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc You are already in group %@. - Már a(z) %@ csoport tagja. + Ön már a(z) %@ nevű csoport tagja. No comment provided by engineer. @@ -7958,7 +7958,7 @@ Csatlakozáskérés megismétlése? You are connected to the server used to receive messages from this contact. - Már kapcsolódott ahhoz a kiszolgálóhoz, amely az adott ismerősétől érkező üzenetek fogadására szolgál. + Ön már kapcsolódott ahhoz a kiszolgálóhoz, amely az adott ismerősétől érkező üzenetek fogadására szolgál. No comment provided by engineer. @@ -8380,12 +8380,12 @@ Kapcsolatkérés megismétlése? admin - admin + adminisztrátor member role admins - adminok + adminisztrátorok feature role @@ -8400,7 +8400,7 @@ Kapcsolatkérés megismétlése? all members - minden tag + összes tag feature role @@ -8450,7 +8450,7 @@ Kapcsolatkérés megismétlése? blocked by admin - letiltva az admin által + letiltva az adminisztrátor által marked deleted chat item preview text @@ -8525,7 +8525,7 @@ Kapcsolatkérés megismétlése? connected - kapcsolódva + kapcsolódott No comment provided by engineer. @@ -9260,7 +9260,7 @@ utoljára fogadott üzenet: %2$@ you are observer - megfigyelő szerep + Ön megfigyelő No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 5b0c2cdb99..acb46596ce 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -9115,7 +9115,7 @@ ultimo msg ricevuto: %2$@ set new profile picture - impostata nuova immagine del profilo + ha impostato una nuova immagine del profilo profile update event chat item diff --git a/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff b/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff index ffbaec1d96..40fc2cd4b3 100644 --- a/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff +++ b/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff @@ -5561,6 +5561,90 @@ Isso pode acontecer por causa de algum bug ou quando a conexão está comprometi Capacity exceeded - recipient did not receive previously sent messages. Capacidade excedida - o destinatário não recebeu as mensagens enviadas anteriormente. + + Chat migrated! + Conversa migrada! + + + Auto-accept settings + Aceitar automaticamente configurações + + + App encrypts new local files (except videos). + O aplicativo criptografa novos arquivos locais (exceto videos). + + + App session + Sessão do aplicativo + + + Acknowledged + Reconhecido + + + Acknowledgement errors + Erros conhecidos + + + Chat list + Lista de conversas + + + Chat database exported + Banco de dados da conversa exportado + + + Chat preferences were changed. + As preferências de bate-papo foram alteradas. + + + Chat theme + Tema da conversa + + + Better calls + Chamadas melhores + + + Better user experience + Melhor experiência do usuário + + + Allow downgrade + Permitir redução + + + Additional secondary + Secundária adicional + + + App data migration + Migração de dados do aplicativo + + + Archive and upload + Arquivar e enviar + + + Background + Fundo + + + Better message dates. + Datas de mensagens melhores. + + + Better notifications + Notificações melhores + + + Better security ✅ + Melhor segurança ✅ + + + Chat profile + Perfil da conversa + diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 558dd682f1..a36257c392 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -4823,7 +4823,7 @@ This is your link for group %@! No push server - Локальные + Без сервера нотификаций No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index b641bbe8cf..c4beadaf66 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -156,14 +156,17 @@ %d file(s) are still being downloaded. + %их файл(ів) ще досі завантажуються. forward confirmation reason %d file(s) failed to download. + %их файлів не вийшло завантажити. forward confirmation reason %d file(s) were deleted. + %их файл(ів) було видалено. forward confirmation reason diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index da890d4ecb..5734fc58cc 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -1,15 +1,6 @@ /* No comment provided by engineer. */ "\n" = "\n"; -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - /* No comment provided by engineer. */ " (" = " ("; @@ -319,12 +310,6 @@ /* No comment provided by engineer. */ "Abort changing address?" = "Откажи смяна на адрес?"; -/* No comment provided by engineer. */ -"About SimpleX" = "За SimpleX"; - -/* No comment provided by engineer. */ -"About SimpleX address" = "Повече за SimpleX адреса"; - /* No comment provided by engineer. */ "About SimpleX Chat" = "За SimpleX Chat"; @@ -352,12 +337,6 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Добавете адрес към вашия профил, така че вашите контакти да могат да го споделят с други хора. Актуализацията на профила ще бъде изпратена до вашите контакти."; -/* No comment provided by engineer. */ -"Add contact" = "Добави контакт"; - -/* No comment provided by engineer. */ -"Add preset servers" = "Добави предварително зададени сървъри"; - /* No comment provided by engineer. */ "Add profile" = "Добави профил"; @@ -514,6 +493,9 @@ /* No comment provided by engineer. */ "Answer call" = "Отговор на повикване"; +/* No comment provided by engineer. */ +"Anybody can host servers." = "Протокол и код с отворен код – всеки може да оперира собствени сървъри."; + /* No comment provided by engineer. */ "App build: %@" = "Компилация на приложението: %@"; @@ -694,7 +676,8 @@ /* No comment provided by engineer. */ "Can't invite contacts!" = "Не може да поканят контактите!"; -/* alert button */ +/* alert action + alert button */ "Cancel" = "Отказ"; /* No comment provided by engineer. */ @@ -794,7 +777,7 @@ /* No comment provided by engineer. */ "Chats" = "Чатове"; -/* No comment provided by engineer. */ +/* alert title */ "Check server address and try again." = "Проверете адреса на сървъра и опитайте отново."; /* No comment provided by engineer. */ @@ -1016,9 +999,6 @@ /* No comment provided by engineer. */ "Create a group using a random profile." = "Създай група с автоматично генериран профилл."; -/* No comment provided by engineer. */ -"Create an address to let people connect with you." = "Създайте адрес, за да позволите на хората да се свързват с вас."; - /* server test step */ "Create file" = "Създай файл"; @@ -1160,7 +1140,8 @@ /* No comment provided by engineer. */ "default (yes)" = "по подразбиране (да)"; -/* chat item action +/* alert action + chat item action swipe action */ "Delete" = "Изтрий"; @@ -1678,9 +1659,6 @@ /* No comment provided by engineer. */ "Error joining group" = "Грешка при присъединяване към група"; -/* No comment provided by engineer. */ -"Error loading %@ servers" = "Грешка при зареждане на %@ сървъри"; - /* No comment provided by engineer. */ "Error opening chat" = "Грешка при отваряне на чата"; @@ -1690,9 +1668,6 @@ /* No comment provided by engineer. */ "Error removing member" = "Грешка при отстраняване на член"; -/* No comment provided by engineer. */ -"Error saving %@ servers" = "Грешка при запазване на %@ сървъра"; - /* No comment provided by engineer. */ "Error saving group profile" = "Грешка при запазване на профила на групата"; @@ -2026,9 +2001,6 @@ /* time unit */ "hours" = "часове"; -/* No comment provided by engineer. */ -"How it works" = "Как работи"; - /* No comment provided by engineer. */ "How SimpleX works" = "Как работи SimpleX"; @@ -2162,10 +2134,10 @@ "Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Инсталирайте [SimpleX Chat за терминал](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"Instant push notifications will be hidden!\n" = "Незабавните push известия ще бъдат скрити!\n"; +"Instant" = "Мигновено"; /* No comment provided by engineer. */ -"Instant" = "Мигновено"; +"Instant push notifications will be hidden!\n" = "Незабавните push известия ще бъдат скрити!\n"; /* No comment provided by engineer. */ "Interface" = "Интерфейс"; @@ -2200,7 +2172,7 @@ /* No comment provided by engineer. */ "Invalid response" = "Невалиден отговор"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid server address!" = "Невалиден адрес на сървъра!"; /* item status text */ @@ -2296,13 +2268,13 @@ /* No comment provided by engineer. */ "Joining group" = "Присъединяване към групата"; -/* No comment provided by engineer. */ +/* alert action */ "Keep" = "Запази"; /* No comment provided by engineer. */ "Keep the app open to use it from desktop" = "Дръжте приложението отворено, за да го използвате от настолното устройство"; -/* No comment provided by engineer. */ +/* alert title */ "Keep unused invitation?" = "Запази неизползваната покана за връзка?"; /* No comment provided by engineer. */ @@ -2359,9 +2331,6 @@ /* No comment provided by engineer. */ "Live messages" = "Съобщения на живо"; -/* No comment provided by engineer. */ -"No push server" = "Локално"; - /* No comment provided by engineer. */ "Local name" = "Локално име"; @@ -2374,24 +2343,15 @@ /* No comment provided by engineer. */ "Lock mode" = "Режим на заключване"; -/* No comment provided by engineer. */ -"Make a private connection" = "Добави поверителна връзка"; - /* No comment provided by engineer. */ "Make one message disappear" = "Накарайте едно съобщение да изчезне"; /* No comment provided by engineer. */ "Make profile private!" = "Направи профила поверителен!"; -/* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Уверете се, че %@ сървърните адреси са в правилен формат, разделени на редове и не се дублират (%@)."; - /* No comment provided by engineer. */ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Уверете се, че адресите на WebRTC ICE сървъра са в правилен формат, разделени на редове и не са дублирани."; -/* No comment provided by engineer. */ -"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Много хора попитаха: *ако SimpleX няма потребителски идентификатори, как може да доставя съобщения?*"; - /* No comment provided by engineer. */ "Mark deleted for everyone" = "Маркирай като изтрито за всички"; @@ -2650,12 +2610,18 @@ /* No comment provided by engineer. */ "No permission to record voice message" = "Няма разрешение за запис на гласово съобщение"; +/* No comment provided by engineer. */ +"No push server" = "Локално"; + /* No comment provided by engineer. */ "No received or sent files" = "Няма получени или изпратени файлове"; /* copied message info in history */ "no text" = "няма текст"; +/* No comment provided by engineer. */ +"No user identifiers." = "Първата платформа без никакви потребителски идентификатори – поверителна по дизайн."; + /* No comment provided by engineer. */ "Not compatible!" = "Несъвместим!"; @@ -2772,12 +2738,6 @@ /* No comment provided by engineer. */ "Open Settings" = "Отвори настройки"; -/* authentication reason */ -"Open user profiles" = "Отвори потребителските профили"; - -/* No comment provided by engineer. */ -"Anybody can host servers." = "Протокол и код с отворен код – всеки може да оперира собствени сървъри."; - /* No comment provided by engineer. */ "Opening app…" = "Приложението се отваря…"; @@ -2838,9 +2798,6 @@ /* No comment provided by engineer. */ "peer-to-peer" = "peer-to-peer"; -/* No comment provided by engineer. */ -"You decide who can connect." = "Хората могат да се свържат с вас само чрез ликовете, които споделяте."; - /* No comment provided by engineer. */ "Periodic" = "Периодично"; @@ -2907,9 +2864,6 @@ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Запазете последната чернова на съобщението с прикачени файлове."; -/* No comment provided by engineer. */ -"Preset server" = "Предварително зададен сървър"; - /* No comment provided by engineer. */ "Preset server address" = "Предварително зададен адрес на сървъра"; @@ -2940,7 +2894,7 @@ /* No comment provided by engineer. */ "Profile password" = "Профилна парола"; -/* No comment provided by engineer. */ +/* alert message */ "Profile update will be sent to your contacts." = "Актуализацията на профила ще бъде изпратена до вашите контакти."; /* No comment provided by engineer. */ @@ -3007,10 +2961,10 @@ "Read more" = "Прочетете още"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Прочетете повече в [Ръководство на потребителя](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; @@ -3018,9 +2972,6 @@ /* No comment provided by engineer. */ "Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Прочетете повече в нашето [GitHub хранилище](https://github.com/simplex-chat/simplex-chat#readme)."; -/* No comment provided by engineer. */ -"Read more in our GitHub repository." = "Прочетете повече в нашето хранилище в GitHub."; - /* No comment provided by engineer. */ "Receipts are disabled" = "Потвърждениeто за доставка е деактивирано"; @@ -3239,7 +3190,7 @@ /* No comment provided by engineer. */ "Save servers" = "Запази сървърите"; -/* No comment provided by engineer. */ +/* alert title */ "Save servers?" = "Запази сървърите?"; /* No comment provided by engineer. */ @@ -3350,9 +3301,6 @@ /* No comment provided by engineer. */ "Send notifications" = "Изпращай известия"; -/* No comment provided by engineer. */ -"Send notifications:" = "Изпратени известия:"; - /* No comment provided by engineer. */ "Send questions and ideas" = "Изпращайте въпроси и идеи"; @@ -3464,7 +3412,8 @@ /* No comment provided by engineer. */ "Shape profile images" = "Променете формата на профилните изображения"; -/* chat item action */ +/* alert action + chat item action */ "Share" = "Сподели"; /* No comment provided by engineer. */ @@ -3473,7 +3422,7 @@ /* No comment provided by engineer. */ "Share address" = "Сподели адрес"; -/* No comment provided by engineer. */ +/* alert title */ "Share address with contacts?" = "Сподели адреса с контактите?"; /* No comment provided by engineer. */ @@ -3605,10 +3554,10 @@ /* No comment provided by engineer. */ "Stop sending file?" = "Спри изпращането на файла?"; -/* No comment provided by engineer. */ +/* alert action */ "Stop sharing" = "Спри споделянето"; -/* No comment provided by engineer. */ +/* alert title */ "Stop sharing address?" = "Спри споделянето на адреса?"; /* authentication reason */ @@ -3677,7 +3626,7 @@ /* No comment provided by engineer. */ "Test servers" = "Тествай сървърите"; -/* No comment provided by engineer. */ +/* alert title */ "Tests failed!" = "Тестовете са неуспешни!"; /* No comment provided by engineer. */ @@ -3689,9 +3638,6 @@ /* No comment provided by engineer. */ "Thanks to the users – contribute via Weblate!" = "Благодарение на потребителите – допринесете през Weblate!"; -/* No comment provided by engineer. */ -"No user identifiers." = "Първата платформа без никакви потребителски идентификатори – поверителна по дизайн."; - /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Приложението може да ви уведоми, когато получите съобщения или заявки за контакт - моля, отворете настройките, за да активирате."; @@ -3713,6 +3659,9 @@ /* 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 future of messaging" = "Ново поколение поверителни съобщения"; + /* No comment provided by engineer. */ "The hash of the previous message is different." = "Хешът на предишното съобщение е различен."; @@ -3725,9 +3674,6 @@ /* No comment provided by engineer. */ "The message will be marked as moderated for all members." = "Съобщението ще бъде маркирано като модерирано за всички членове."; -/* No comment provided by engineer. */ -"The future of messaging" = "Ново поколение поверителни съобщения"; - /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Старата база данни не бе премахната по време на миграцията, тя може да бъде изтрита."; @@ -3803,15 +3749,15 @@ /* No comment provided by engineer. */ "To make a new connection" = "За да направите нова връзка"; -/* No comment provided by engineer. */ -"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "За да се защити поверителността, вместо потребителски идентификатори, използвани от всички други платформи, SimpleX има идентификатори за опашки от съобщения, отделни за всеки от вашите контакти."; - /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "За да не се разкрива часовата зона, файловете с изображения/глас използват UTC."; /* No comment provided by engineer. */ "To protect your information, turn on SimpleX Lock.\nYou will be prompted to complete authentication before this feature is enabled." = "За да защитите информацията си, включете SimpleX заключване.\nЩе бъдете подканени да извършите идентификация, преди тази функция да бъде активирана."; +/* No comment provided by engineer. */ +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "За да се защити поверителността, вместо потребителски идентификатори, използвани от всички други платформи, SimpleX има идентификатори за опашки от съобщения, отделни за всеки от вашите контакти."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "За да запишете гласово съобщение, моля, дайте разрешение за използване на микрофон."; @@ -4127,9 +4073,6 @@ /* No comment provided by engineer. */ "When connecting audio and video calls." = "При свързване на аудио и видео разговори."; -/* No comment provided by engineer. */ -"When people request to connect, you can accept or reject it." = "Когато хората искат да се свържат с вас, можете да ги приемете или отхвърлите."; - /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Когато споделяте инкогнито профил с някого, този профил ще се използва за групите, в които той ви кани."; @@ -4247,9 +4190,6 @@ /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "Можете да споделите този адрес с вашите контакти, за да им позволите да се свържат с **%@**."; -/* No comment provided by engineer. */ -"You can share your address as a link or QR code - anybody can connect to you." = "Можете да споделите адреса си като линк или QR код - всеки може да се свърже с вас."; - /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "Можете да започнете чат през Настройки на приложението / База данни или като рестартирате приложението"; @@ -4259,7 +4199,7 @@ /* No comment provided by engineer. */ "You can use markdown to format messages:" = "Можете да използвате markdown за форматиране на съобщенията:"; -/* No comment provided by engineer. */ +/* alert message */ "You can view invitation link again in connection details." = "Можете да видите отново линкът за покана в подробностите за връзката."; /* No comment provided by engineer. */ @@ -4280,6 +4220,9 @@ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Не можахте да бъдете потвърдени; Моля, опитайте отново."; +/* No comment provided by engineer. */ +"You decide who can connect." = "Хората могат да се свържат с вас само чрез ликовете, които споделяте."; + /* No comment provided by engineer. */ "You have already requested connection via this address!" = "Вече сте заявили връзка през този адрес!"; @@ -4361,9 +4304,6 @@ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Използвате инкогнито профил за тази група - за да се предотврати споделянето на основния ви профил, поканите на контакти не са разрешени"; -/* No comment provided by engineer. */ -"Your %@ servers" = "Вашите %@ сървъри"; - /* No comment provided by engineer. */ "Your calls" = "Вашите обаждания"; @@ -4415,9 +4355,6 @@ /* No comment provided by engineer. */ "Your random profile" = "Вашият автоматично генериран профил"; -/* No comment provided by engineer. */ -"Your server" = "Вашият сървър"; - /* No comment provided by engineer. */ "Your server address" = "Вашият адрес на сървъра"; @@ -4430,6 +4367,3 @@ /* No comment provided by engineer. */ "Your SMP servers" = "Вашите SMP сървъри"; -/* No comment provided by engineer. */ -"Your XFTP servers" = "Вашите XFTP сървъри"; - diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index 611be02606..462988855b 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -1,15 +1,6 @@ /* No comment provided by engineer. */ "\n" = "\n"; -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - /* No comment provided by engineer. */ " (" = " ("; @@ -271,12 +262,6 @@ /* No comment provided by engineer. */ "Abort changing address?" = "Přerušit změnu adresy?"; -/* No comment provided by engineer. */ -"About SimpleX" = "O SimpleX"; - -/* No comment provided by engineer. */ -"About SimpleX address" = "O SimpleX adrese"; - /* No comment provided by engineer. */ "About SimpleX Chat" = "O SimpleX chat"; @@ -304,9 +289,6 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Přidejte adresu do svého profilu, aby ji vaše kontakty mohly sdílet s dalšími lidmi. Aktualizace profilu bude zaslána vašim kontaktům."; -/* No comment provided by engineer. */ -"Add preset servers" = "Přidejte přednastavené servery"; - /* No comment provided by engineer. */ "Add profile" = "Přidat profil"; @@ -433,6 +415,9 @@ /* No comment provided by engineer. */ "Answer call" = "Přijmout hovor"; +/* No comment provided by engineer. */ +"Anybody can host servers." = "Servery může provozovat kdokoli."; + /* No comment provided by engineer. */ "App build: %@" = "Sestavení aplikace: %@"; @@ -559,7 +544,8 @@ /* No comment provided by engineer. */ "Can't invite contacts!" = "Nelze pozvat kontakty!"; -/* alert button */ +/* alert action + alert button */ "Cancel" = "Zrušit"; /* feature offered item */ @@ -647,7 +633,7 @@ /* No comment provided by engineer. */ "Chats" = "Chaty"; -/* No comment provided by engineer. */ +/* alert title */ "Check server address and try again." = "Zkontrolujte adresu serveru a zkuste to znovu."; /* No comment provided by engineer. */ @@ -812,9 +798,6 @@ /* No comment provided by engineer. */ "Create" = "Vytvořit"; -/* No comment provided by engineer. */ -"Create an address to let people connect with you." = "Vytvořit adresu, aby se s vámi lidé mohli spojit."; - /* server test step */ "Create file" = "Vytvořit soubor"; @@ -938,7 +921,8 @@ /* No comment provided by engineer. */ "default (yes)" = "výchozí (ano)"; -/* chat item action +/* alert action + chat item action swipe action */ "Delete" = "Smazat"; @@ -1377,18 +1361,12 @@ /* No comment provided by engineer. */ "Error joining group" = "Chyba při připojování ke skupině"; -/* No comment provided by engineer. */ -"Error loading %@ servers" = "Chyba načítání %@ serverů"; - /* alert title */ "Error receiving file" = "Chyba při příjmu souboru"; /* No comment provided by engineer. */ "Error removing member" = "Chyba při odebrání člena"; -/* No comment provided by engineer. */ -"Error saving %@ servers" = "Chyba při ukládání serverů %@"; - /* No comment provided by engineer. */ "Error saving group profile" = "Chyba při ukládání profilu skupiny"; @@ -1656,9 +1634,6 @@ /* time unit */ "hours" = "hodin"; -/* No comment provided by engineer. */ -"How it works" = "Jak to funguje"; - /* No comment provided by engineer. */ "How SimpleX works" = "Jak SimpleX funguje"; @@ -1768,10 +1743,10 @@ "Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Nainstalujte [SimpleX Chat pro terminál](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"Instant push notifications will be hidden!\n" = "Okamžitá oznámení budou skryta!\n"; +"Instant" = "Okamžitě"; /* No comment provided by engineer. */ -"Instant" = "Okamžitě"; +"Instant push notifications will be hidden!\n" = "Okamžitá oznámení budou skryta!\n"; /* No comment provided by engineer. */ "Interface" = "Rozhranní"; @@ -1788,7 +1763,7 @@ /* invalid chat item */ "invalid data" = "neplatné údaje"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid server address!" = "Neplatná adresa serveru!"; /* item status text */ @@ -1917,9 +1892,6 @@ /* No comment provided by engineer. */ "Live messages" = "Živé zprávy"; -/* No comment provided by engineer. */ -"No push server" = "Místní"; - /* No comment provided by engineer. */ "Local name" = "Místní název"; @@ -1932,24 +1904,15 @@ /* No comment provided by engineer. */ "Lock mode" = "Režim zámku"; -/* No comment provided by engineer. */ -"Make a private connection" = "Vytvořte si soukromé připojení"; - /* No comment provided by engineer. */ "Make one message disappear" = "Nechat jednu zprávu zmizet"; /* No comment provided by engineer. */ "Make profile private!" = "Změnit profil na soukromý!"; -/* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Ujistěte se, že adresy %@ serverů jsou ve správném formátu, oddělené řádky a nejsou duplicitní (%@)."; - /* No comment provided by engineer. */ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Ujistěte se, že adresy serverů WebRTC ICE jsou ve správném formátu, oddělené na řádcích a nejsou duplicitní."; -/* No comment provided by engineer. */ -"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Mnoho lidí se ptalo: *Pokud SimpleX nemá žádné uživatelské identifikátory, jak může doručovat zprávy?*"; - /* No comment provided by engineer. */ "Mark deleted for everyone" = "Označit jako smazané pro všechny"; @@ -2154,12 +2117,18 @@ /* No comment provided by engineer. */ "No permission to record voice message" = "Nemáte oprávnění nahrávat hlasové zprávy"; +/* No comment provided by engineer. */ +"No push server" = "Místní"; + /* No comment provided by engineer. */ "No received or sent files" = "Žádné přijaté ani odeslané soubory"; /* copied message info in history */ "no text" = "žádný text"; +/* No comment provided by engineer. */ +"No user identifiers." = "Bez uživatelských identifikátorů"; + /* No comment provided by engineer. */ "Notifications" = "Oznámení"; @@ -2264,12 +2233,6 @@ /* No comment provided by engineer. */ "Open Settings" = "Otevřít nastavení"; -/* authentication reason */ -"Open user profiles" = "Otevřít uživatelské profily"; - -/* No comment provided by engineer. */ -"Anybody can host servers." = "Servery může provozovat kdokoli."; - /* member role */ "owner" = "vlastník"; @@ -2297,9 +2260,6 @@ /* No comment provided by engineer. */ "peer-to-peer" = "peer-to-peer"; -/* No comment provided by engineer. */ -"You decide who can connect." = "Lidé se s vámi mohou spojit pouze prostřednictvím odkazu, který sdílíte."; - /* No comment provided by engineer. */ "Periodic" = "Pravidelně"; @@ -2357,9 +2317,6 @@ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Zachování posledního návrhu zprávy s přílohami."; -/* No comment provided by engineer. */ -"Preset server" = "Přednastavený server"; - /* No comment provided by engineer. */ "Preset server address" = "Přednastavená adresa serveru"; @@ -2384,7 +2341,7 @@ /* No comment provided by engineer. */ "Profile password" = "Heslo profilu"; -/* No comment provided by engineer. */ +/* alert message */ "Profile update will be sent to your contacts." = "Aktualizace profilu bude zaslána vašim kontaktům."; /* No comment provided by engineer. */ @@ -2447,9 +2404,6 @@ /* No comment provided by engineer. */ "Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Přečtěte si více v našem [GitHub repozitáři](https://github.com/simplex-chat/simplex-chat#readme)."; -/* 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"; @@ -2635,7 +2589,7 @@ /* No comment provided by engineer. */ "Save servers" = "Uložit servery"; -/* No comment provided by engineer. */ +/* alert title */ "Save servers?" = "Uložit servery?"; /* No comment provided by engineer. */ @@ -2722,9 +2676,6 @@ /* No comment provided by engineer. */ "Send notifications" = "Odeslat oznámení"; -/* No comment provided by engineer. */ -"Send notifications:" = "Odeslat oznámení:"; - /* No comment provided by engineer. */ "Send questions and ideas" = "Zasílání otázek a nápadů"; @@ -2818,7 +2769,8 @@ /* No comment provided by engineer. */ "Settings" = "Nastavení"; -/* chat item action */ +/* alert action + chat item action */ "Share" = "Sdílet"; /* No comment provided by engineer. */ @@ -2827,7 +2779,7 @@ /* No comment provided by engineer. */ "Share address" = "Sdílet adresu"; -/* No comment provided by engineer. */ +/* alert title */ "Share address with contacts?" = "Sdílet adresu s kontakty?"; /* No comment provided by engineer. */ @@ -2935,10 +2887,10 @@ /* No comment provided by engineer. */ "Stop sending file?" = "Zastavit odesílání souboru?"; -/* No comment provided by engineer. */ +/* alert action */ "Stop sharing" = "Přestat sdílet"; -/* No comment provided by engineer. */ +/* alert title */ "Stop sharing address?" = "Přestat sdílet adresu?"; /* authentication reason */ @@ -2995,7 +2947,7 @@ /* No comment provided by engineer. */ "Test servers" = "Testovací servery"; -/* No comment provided by engineer. */ +/* alert title */ "Tests failed!" = "Testy selhaly!"; /* No comment provided by engineer. */ @@ -3007,9 +2959,6 @@ /* No comment provided by engineer. */ "Thanks to the users – contribute via Weblate!" = "Díky uživatelům - přispívejte prostřednictvím Weblate!"; -/* No comment provided by engineer. */ -"No user identifiers." = "Bez uživatelských identifikátorů"; - /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Aplikace vás může upozornit na přijaté zprávy nebo žádosti o kontakt - povolte to v nastavení."; @@ -3028,6 +2977,9 @@ /* 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 future of messaging" = "Nová generace soukromých zpráv"; + /* No comment provided by engineer. */ "The hash of the previous message is different." = "Hash předchozí zprávy se liší."; @@ -3040,9 +2992,6 @@ /* No comment provided by engineer. */ "The message will be marked as moderated for all members." = "Zpráva bude pro všechny členy označena jako moderovaná."; -/* No comment provided by engineer. */ -"The future of messaging" = "Nová generace soukromých zpráv"; - /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Stará databáze nebyla během přenášení odstraněna, lze ji smazat."; @@ -3094,15 +3043,15 @@ /* No comment provided by engineer. */ "To make a new connection" = "Vytvoření nového připojení"; -/* No comment provided by engineer. */ -"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Pro ochranu soukromí namísto ID uživatelů používaných všemi ostatními platformami má SimpleX identifikátory pro fronty zpráv, oddělené pro každý z vašich kontaktů."; - /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "K ochraně časového pásma používají obrazové/hlasové soubory UTC."; /* No comment provided by engineer. */ "To protect your information, turn on SimpleX Lock.\nYou will be prompted to complete authentication before this feature is enabled." = "Chcete-li chránit své informace, zapněte zámek SimpleX Lock.\nPřed zapnutím této funkce budete vyzváni k dokončení ověření."; +/* No comment provided by engineer. */ +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Pro ochranu soukromí namísto ID uživatelů používaných všemi ostatními platformami má SimpleX identifikátory pro fronty zpráv, oddělené pro každý z vašich kontaktů."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Chcete-li nahrávat hlasové zprávy, udělte povolení k použití mikrofonu."; @@ -3328,9 +3277,6 @@ /* No comment provided by engineer. */ "When available" = "Když je k dispozici"; -/* No comment provided by engineer. */ -"When people request to connect, you can accept or reject it." = "Když někdo požádá o připojení, můžete žádost přijmout nebo odmítnout."; - /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Pokud s někým sdílíte inkognito profil, bude tento profil použit pro skupiny, do kterých vás pozve."; @@ -3397,9 +3343,6 @@ /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "Tuto adresu můžete sdílet s vašimi kontakty, abyse se mohli spojit s **%@**."; -/* No comment provided by engineer. */ -"You can share your address as a link or QR code - anybody can connect to you." = "Můžete sdílet svou adresu jako odkaz nebo jako QR kód - kdokoli se k vám bude moci připojit."; - /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "Chat můžete zahájit prostřednictvím aplikace Nastavení / Databáze nebo restartováním aplikace"; @@ -3427,6 +3370,9 @@ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Nemohli jste být ověřeni; Zkuste to prosím znovu."; +/* No comment provided by engineer. */ +"You decide who can connect." = "Lidé se s vámi mohou spojit pouze prostřednictvím odkazu, který sdílíte."; + /* No comment provided by engineer. */ "You have to enter passphrase every time the app starts - it is not stored on the device." = "Musíte zadat přístupovou frázi při každém spuštění aplikace - není uložena v zařízení."; @@ -3493,9 +3439,6 @@ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Pro tuto skupinu používáte inkognito profil - abyste zabránili sdílení svého hlavního profilu, není pozvání kontaktů povoleno"; -/* No comment provided by engineer. */ -"Your %@ servers" = "Vaše servery %@"; - /* No comment provided by engineer. */ "Your calls" = "Vaše hovory"; @@ -3544,9 +3487,6 @@ /* No comment provided by engineer. */ "Your random profile" = "Váš náhodný profil"; -/* No comment provided by engineer. */ -"Your server" = "Váš server"; - /* No comment provided by engineer. */ "Your server address" = "Adresa vašeho serveru"; @@ -3559,6 +3499,3 @@ /* No comment provided by engineer. */ "Your SMP servers" = "Vaše servery SMP"; -/* No comment provided by engineer. */ -"Your XFTP servers" = "Vaše XFTP servery"; - diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 8ea6a30716..312cab136a 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -1,15 +1,6 @@ /* No comment provided by engineer. */ "\n" = "\n"; -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - /* No comment provided by engineer. */ " (" = " ("; @@ -337,12 +328,6 @@ /* No comment provided by engineer. */ "Abort changing address?" = "Wechsel der Empfängeradresse beenden?"; -/* No comment provided by engineer. */ -"About SimpleX" = "Über SimpleX"; - -/* No comment provided by engineer. */ -"About SimpleX address" = "Über die SimpleX-Adresse"; - /* No comment provided by engineer. */ "About SimpleX Chat" = "Über SimpleX Chat"; @@ -382,12 +367,6 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Fügen Sie die Adresse Ihrem Profil hinzu, damit Ihre Kontakte sie mit anderen Personen teilen können. Es wird eine Profilaktualisierung an Ihre Kontakte gesendet."; -/* No comment provided by engineer. */ -"Add contact" = "Kontakt hinzufügen"; - -/* No comment provided by engineer. */ -"Add preset servers" = "Füge voreingestellte Server hinzu"; - /* No comment provided by engineer. */ "Add profile" = "Profil hinzufügen"; @@ -574,6 +553,9 @@ /* No comment provided by engineer. */ "Answer call" = "Anruf annehmen"; +/* No comment provided by engineer. */ +"Anybody can host servers." = "Jeder kann seine eigenen Server aufsetzen."; + /* No comment provided by engineer. */ "App build: %@" = "App Build: %@"; @@ -817,7 +799,8 @@ /* No comment provided by engineer. */ "Can't message member" = "Mitglied kann nicht benachrichtigt werden"; -/* alert button */ +/* alert action + alert button */ "Cancel" = "Abbrechen"; /* No comment provided by engineer. */ @@ -938,7 +921,7 @@ /* No comment provided by engineer. */ "Chats" = "Chats"; -/* No comment provided by engineer. */ +/* alert title */ "Check server address and try again." = "Überprüfen Sie die Serveradresse und versuchen Sie es nochmal."; /* No comment provided by engineer. */ @@ -1001,9 +984,6 @@ /* No comment provided by engineer. */ "Configure ICE servers" = "ICE-Server konfigurieren"; -/* No comment provided by engineer. */ -"Configured %@ servers" = "Konfigurierte %@ Server"; - /* No comment provided by engineer. */ "Confirm" = "Bestätigen"; @@ -1232,9 +1212,6 @@ /* 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"; @@ -1397,7 +1374,8 @@ /* No comment provided by engineer. */ "default (yes)" = "Voreinstellung (Ja)"; -/* chat item action +/* alert action + chat item action swipe action */ "Delete" = "Löschen"; @@ -1996,9 +1974,6 @@ /* No comment provided by engineer. */ "Error joining group" = "Fehler beim Beitritt zur Gruppe"; -/* No comment provided by engineer. */ -"Error loading %@ servers" = "Fehler beim Laden von %@ Servern"; - /* No comment provided by engineer. */ "Error migrating settings" = "Fehler beim Migrieren der Einstellungen"; @@ -2020,9 +1995,6 @@ /* No comment provided by engineer. */ "Error resetting statistics" = "Fehler beim Zurücksetzen der Statistiken"; -/* No comment provided by engineer. */ -"Error saving %@ servers" = "Fehler beim Speichern der %@-Server"; - /* No comment provided by engineer. */ "Error saving group profile" = "Fehler beim Speichern des Gruppenprofils"; @@ -2425,9 +2397,6 @@ /* time unit */ "hours" = "Stunden"; -/* No comment provided by engineer. */ -"How it works" = "Wie es funktioniert"; - /* No comment provided by engineer. */ "How SimpleX works" = "Wie SimpleX funktioniert"; @@ -2570,10 +2539,10 @@ "Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Installieren Sie [SimpleX Chat als Terminalanwendung](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"Instant push notifications will be hidden!\n" = "Sofortige Push-Benachrichtigungen werden verborgen!\n"; +"Instant" = "Sofort"; /* No comment provided by engineer. */ -"Instant" = "Sofort"; +"Instant push notifications will be hidden!\n" = "Sofortige Push-Benachrichtigungen werden verborgen!\n"; /* No comment provided by engineer. */ "Interface" = "Schnittstelle"; @@ -2611,7 +2580,7 @@ /* No comment provided by engineer. */ "Invalid response" = "Ungültige Reaktion"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid server address!" = "Ungültige Serveradresse!"; /* item status text */ @@ -2716,7 +2685,7 @@ /* No comment provided by engineer. */ "Joining group" = "Der Gruppe beitreten"; -/* No comment provided by engineer. */ +/* alert action */ "Keep" = "Behalten"; /* No comment provided by engineer. */ @@ -2725,7 +2694,7 @@ /* 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. */ +/* alert title */ "Keep unused invitation?" = "Nicht genutzte Einladung behalten?"; /* No comment provided by engineer. */ @@ -2782,9 +2751,6 @@ /* No comment provided by engineer. */ "Live messages" = "Live Nachrichten"; -/* No comment provided by engineer. */ -"No push server" = "Lokal"; - /* No comment provided by engineer. */ "Local name" = "Lokaler Name"; @@ -2797,24 +2763,15 @@ /* No comment provided by engineer. */ "Lock mode" = "Sperr-Modus"; -/* No comment provided by engineer. */ -"Make a private connection" = "Stellen Sie eine private Verbindung her"; - /* No comment provided by engineer. */ "Make one message disappear" = "Eine verschwindende Nachricht verfassen"; /* No comment provided by engineer. */ "Make profile private!" = "Privates Profil erzeugen!"; -/* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Stellen Sie sicher, dass die %@-Server-Adressen das richtige Format haben, zeilenweise getrennt und nicht doppelt vorhanden sind (%@)."; - /* No comment provided by engineer. */ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Stellen Sie sicher, dass die WebRTC ICE-Server Adressen das richtige Format haben, zeilenweise getrennt und nicht doppelt vorhanden sind."; -/* No comment provided by engineer. */ -"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Viele Menschen haben gefragt: *Wie kann SimpleX Nachrichten zustellen, wenn es keine Benutzerkennungen gibt?*"; - /* No comment provided by engineer. */ "Mark deleted for everyone" = "Für Alle als gelöscht markieren"; @@ -3154,12 +3111,18 @@ /* No comment provided by engineer. */ "No permission to record voice message" = "Keine Berechtigung für das Aufnehmen von Sprachnachrichten"; +/* No comment provided by engineer. */ +"No push server" = "Lokal"; + /* No comment provided by engineer. */ "No received or sent files" = "Keine empfangenen oder gesendeten Dateien"; /* copied message info in history */ "no text" = "Kein Text"; +/* No comment provided by engineer. */ +"No user identifiers." = "Keine Benutzerkennungen."; + /* No comment provided by engineer. */ "Not compatible!" = "Nicht kompatibel!"; @@ -3282,18 +3245,9 @@ /* authentication reason */ "Open migration to another device" = "Migration auf ein anderes Gerät öffnen"; -/* No comment provided by engineer. */ -"Open server settings" = "Server-Einstellungen öffnen"; - /* No comment provided by engineer. */ "Open Settings" = "Geräte-Einstellungen öffnen"; -/* authentication reason */ -"Open user profiles" = "Benutzerprofile öffnen"; - -/* No comment provided by engineer. */ -"Anybody can host servers." = "Jeder kann seine eigenen Server aufsetzen."; - /* No comment provided by engineer. */ "Opening app…" = "App wird geöffnet…"; @@ -3315,9 +3269,6 @@ /* No comment provided by engineer. */ "Other" = "Andere"; -/* No comment provided by engineer. */ -"Other %@ servers" = "Andere %@ Server"; - /* No comment provided by engineer. */ "other errors" = "Andere Fehler"; @@ -3372,9 +3323,6 @@ /* No comment provided by engineer. */ "Pending" = "Ausstehend"; -/* No comment provided by engineer. */ -"You decide who can connect." = "Sie entscheiden, wer sich mit Ihnen verbinden kann."; - /* No comment provided by engineer. */ "Periodic" = "Periodisch"; @@ -3453,9 +3401,6 @@ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Den letzten Nachrichtenentwurf, auch mit seinen Anhängen, aufbewahren."; -/* No comment provided by engineer. */ -"Preset server" = "Voreingestellter Server"; - /* No comment provided by engineer. */ "Preset server address" = "Voreingestellte Serveradresse"; @@ -3504,7 +3449,7 @@ /* No comment provided by engineer. */ "Profile theme" = "Profil-Design"; -/* No comment provided by engineer. */ +/* alert message */ "Profile update will be sent to your contacts." = "Profil-Aktualisierung wird an Ihre Kontakte gesendet."; /* No comment provided by engineer. */ @@ -3589,10 +3534,10 @@ "Read more" = "Mehr erfahren"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Mehr dazu in der [Benutzeranleitung](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses) lesen."; +"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Lesen Sie mehr dazu im [Benutzerhandbuch](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Lesen Sie mehr dazu im [Benutzerhandbuch](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Mehr dazu in der [Benutzeranleitung](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses) lesen."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Mehr dazu in der [Benutzeranleitung](https://simplex.chat/docs/guide/readme.html#connect-to-friends) lesen."; @@ -3600,9 +3545,6 @@ /* No comment provided by engineer. */ "Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Erfahren Sie in unserem [GitHub-Repository](https://github.com/simplex-chat/simplex-chat#readme) mehr dazu."; -/* No comment provided by engineer. */ -"Read more in our GitHub repository." = "Erfahren Sie in unserem GitHub-Repository mehr dazu."; - /* No comment provided by engineer. */ "Receipts are disabled" = "Bestätigungen sind deaktiviert"; @@ -3875,7 +3817,7 @@ /* No comment provided by engineer. */ "Save servers" = "Alle Server speichern"; -/* No comment provided by engineer. */ +/* alert title */ "Save servers?" = "Alle Server speichern?"; /* No comment provided by engineer. */ @@ -4028,9 +3970,6 @@ /* No comment provided by engineer. */ "Send notifications" = "Benachrichtigungen senden"; -/* No comment provided by engineer. */ -"Send notifications:" = "Benachrichtigungen senden:"; - /* No comment provided by engineer. */ "Send questions and ideas" = "Senden Sie Fragen und Ideen"; @@ -4193,7 +4132,8 @@ /* No comment provided by engineer. */ "Shape profile images" = "Form der Profil-Bilder"; -/* chat item action */ +/* alert action + chat item action */ "Share" = "Teilen"; /* No comment provided by engineer. */ @@ -4202,7 +4142,7 @@ /* No comment provided by engineer. */ "Share address" = "Adresse teilen"; -/* No comment provided by engineer. */ +/* alert title */ "Share address with contacts?" = "Die Adresse mit Kontakten teilen?"; /* No comment provided by engineer. */ @@ -4385,10 +4325,10 @@ /* No comment provided by engineer. */ "Stop sending file?" = "Das Senden der Datei beenden?"; -/* No comment provided by engineer. */ +/* alert action */ "Stop sharing" = "Teilen beenden"; -/* No comment provided by engineer. */ +/* alert title */ "Stop sharing address?" = "Das Teilen der Adresse beenden?"; /* authentication reason */ @@ -4484,7 +4424,7 @@ /* No comment provided by engineer. */ "Test servers" = "Teste alle Server"; -/* No comment provided by engineer. */ +/* alert title */ "Tests failed!" = "Tests sind fehlgeschlagen!"; /* No comment provided by engineer. */ @@ -4496,9 +4436,6 @@ /* No comment provided by engineer. */ "Thanks to the users – contribute via Weblate!" = "Dank der Nutzer - Tragen Sie per Weblate bei!"; -/* No comment provided by engineer. */ -"No user identifiers." = "Keine Benutzerkennungen."; - /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Wenn sie Nachrichten oder Kontaktanfragen empfangen, kann Sie die App benachrichtigen - Um dies zu aktivieren, öffnen Sie bitte die Einstellungen."; @@ -4523,6 +4460,9 @@ /* 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 future of messaging" = "Die nächste Generation von privatem Messaging"; + /* No comment provided by engineer. */ "The hash of the previous message is different." = "Der Hash der vorherigen Nachricht unterscheidet sich."; @@ -4541,9 +4481,6 @@ /* No comment provided by engineer. */ "The messages will be marked as moderated for all members." = "Die Nachrichten werden für alle Mitglieder als moderiert gekennzeichnet werden."; -/* No comment provided by engineer. */ -"The future of messaging" = "Die nächste Generation von privatem Messaging"; - /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Die alte Datenbank wurde während der Migration nicht entfernt. Sie kann gelöscht werden."; @@ -4631,9 +4568,6 @@ /* No comment provided by engineer. */ "To make a new connection" = "Um eine Verbindung mit einem neuen Kontakt zu erstellen"; -/* No comment provided by engineer. */ -"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Zum Schutz Ihrer Privatsphäre verwendet SimpleX an Stelle von Benutzerkennungen, die von allen anderen Plattformen verwendet werden, Kennungen für Nachrichtenwarteschlangen, die für jeden Ihrer Kontakte individuell sind."; - /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Bild- und Sprachdateinamen enthalten UTC, um Informationen zur Zeitzone zu schützen."; @@ -4643,6 +4577,9 @@ /* No comment provided by engineer. */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "Zum Schutz Ihrer IP-Adresse, wird für die Nachrichten-Auslieferung privates Routing über Ihre konfigurierten SMP-Server genutzt."; +/* No comment provided by engineer. */ +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Zum Schutz Ihrer Privatsphäre verwendet SimpleX an Stelle von Benutzerkennungen, die von allen anderen Plattformen verwendet werden, Kennungen für Nachrichtenwarteschlangen, die für jeden Ihrer Kontakte individuell sind."; + /* No comment provided by engineer. */ "To record speech please grant permission to use Microphone." = "Bitte erteilen Sie für Sprach-Aufnahmen die Genehmigung das Mikrofon zu nutzen."; @@ -5030,9 +4967,6 @@ /* No comment provided by engineer. */ "when IP hidden" = "Wenn die IP-Adresse versteckt ist"; -/* No comment provided by engineer. */ -"When people request to connect, you can accept or reject it." = "Wenn Personen eine Verbindung anfordern, können Sie diese annehmen oder ablehnen."; - /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Wenn Sie ein Inkognito-Profil mit Jemandem teilen, wird dieses Profil auch für die Gruppen verwendet, für die Sie von diesem Kontakt eingeladen werden."; @@ -5174,9 +5108,6 @@ /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "Sie können diese Adresse mit Ihren Kontakten teilen, um sie mit **%@** verbinden zu lassen."; -/* No comment provided by engineer. */ -"You can share your address as a link or QR code - anybody can connect to you." = "Sie können Ihre Adresse als Link oder als QR-Code teilen – Jede Person kann sich darüber mit Ihnen verbinden."; - /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "Sie können den Chat über die App-Einstellungen / Datenbank oder durch Neustart der App starten"; @@ -5189,7 +5120,7 @@ /* No comment provided by engineer. */ "You can use markdown to format messages:" = "Um Nachrichteninhalte zu formatieren, können Sie Markdowns verwenden:"; -/* No comment provided by engineer. */ +/* alert message */ "You can view invitation link again in connection details." = "Den Einladungslink können Sie in den Details der Verbindung nochmals sehen."; /* No comment provided by engineer. */ @@ -5210,6 +5141,9 @@ /* 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 decide who can connect." = "Sie entscheiden, wer sich mit Ihnen verbinden kann."; + /* No comment provided by engineer. */ "You have already requested connection via this address!" = "Sie haben über diese Adresse bereits eine Verbindung beantragt!"; @@ -5300,9 +5234,6 @@ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Sie verwenden ein Inkognito-Profil für diese Gruppe. Um zu verhindern, dass Sie Ihr Hauptprofil teilen, ist in diesem Fall das Einladen von Kontakten nicht erlaubt"; -/* No comment provided by engineer. */ -"Your %@ servers" = "Ihre %@-Server"; - /* No comment provided by engineer. */ "Your calls" = "Anrufe"; @@ -5366,9 +5297,6 @@ /* No comment provided by engineer. */ "Your random profile" = "Ihr Zufallsprofil"; -/* No comment provided by engineer. */ -"Your server" = "Ihr Server"; - /* No comment provided by engineer. */ "Your server address" = "Ihre Serveradresse"; @@ -5381,6 +5309,3 @@ /* No comment provided by engineer. */ "Your SMP servers" = "Ihre SMP-Server"; -/* No comment provided by engineer. */ -"Your XFTP servers" = "Ihre XFTP-Server"; - diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 10b8bc317c..9f775acb54 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -1,15 +1,6 @@ /* No comment provided by engineer. */ "\n" = "\n"; -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - /* No comment provided by engineer. */ " (" = " ("; @@ -337,12 +328,6 @@ /* No comment provided by engineer. */ "Abort changing address?" = "¿Cancelar el cambio de servidor?"; -/* No comment provided by engineer. */ -"About SimpleX" = "Acerca de SimpleX"; - -/* No comment provided by engineer. */ -"About SimpleX address" = "Acerca de la dirección SimpleX"; - /* No comment provided by engineer. */ "About SimpleX Chat" = "Sobre SimpleX Chat"; @@ -382,12 +367,6 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Añade la dirección a tu perfil para que tus contactos puedan compartirla con otros. La actualización del perfil se enviará a tus contactos."; -/* No comment provided by engineer. */ -"Add contact" = "Añadir contacto"; - -/* No comment provided by engineer. */ -"Add preset servers" = "Añadir servidores predefinidos"; - /* No comment provided by engineer. */ "Add profile" = "Añadir perfil"; @@ -574,6 +553,9 @@ /* No comment provided by engineer. */ "Answer call" = "Responder llamada"; +/* No comment provided by engineer. */ +"Anybody can host servers." = "Cualquiera puede alojar servidores."; + /* No comment provided by engineer. */ "App build: %@" = "Compilación app: %@"; @@ -817,7 +799,8 @@ /* No comment provided by engineer. */ "Can't message member" = "No se pueden enviar mensajes al miembro"; -/* alert button */ +/* alert action + alert button */ "Cancel" = "Cancelar"; /* No comment provided by engineer. */ @@ -938,7 +921,7 @@ /* No comment provided by engineer. */ "Chats" = "Chats"; -/* No comment provided by engineer. */ +/* alert title */ "Check server address and try again." = "Comprueba la dirección del servidor e inténtalo de nuevo."; /* No comment provided by engineer. */ @@ -1001,9 +984,6 @@ /* No comment provided by engineer. */ "Configure ICE servers" = "Configure servidores ICE"; -/* No comment provided by engineer. */ -"Configured %@ servers" = "%@ servidores configurados"; - /* No comment provided by engineer. */ "Confirm" = "Confirmar"; @@ -1232,9 +1212,6 @@ /* No comment provided by engineer. */ "Create a group using a random profile." = "Crear grupo usando perfil aleatorio."; -/* No comment provided by engineer. */ -"Create an address to let people connect with you." = "Crea una dirección para que otras personas puedan conectar contigo."; - /* server test step */ "Create file" = "Crear archivo"; @@ -1397,7 +1374,8 @@ /* No comment provided by engineer. */ "default (yes)" = "predeterminado (sí)"; -/* chat item action +/* alert action + chat item action swipe action */ "Delete" = "Eliminar"; @@ -1996,9 +1974,6 @@ /* No comment provided by engineer. */ "Error joining group" = "Error al unirte al grupo"; -/* No comment provided by engineer. */ -"Error loading %@ servers" = "Error al cargar servidores %@"; - /* No comment provided by engineer. */ "Error migrating settings" = "Error al migrar la configuración"; @@ -2020,9 +1995,6 @@ /* No comment provided by engineer. */ "Error resetting statistics" = "Error al restablecer las estadísticas"; -/* No comment provided by engineer. */ -"Error saving %@ servers" = "Error al guardar servidores %@"; - /* No comment provided by engineer. */ "Error saving group profile" = "Error al guardar perfil de grupo"; @@ -2425,9 +2397,6 @@ /* time unit */ "hours" = "horas"; -/* No comment provided by engineer. */ -"How it works" = "Cómo funciona"; - /* No comment provided by engineer. */ "How SimpleX works" = "Cómo funciona SimpleX"; @@ -2570,10 +2539,10 @@ "Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Instalar terminal para [SimpleX Chat](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"Instant push notifications will be hidden!\n" = "¡Las notificaciones automáticas estarán ocultas!\n"; +"Instant" = "Al instante"; /* No comment provided by engineer. */ -"Instant" = "Al instante"; +"Instant push notifications will be hidden!\n" = "¡Las notificaciones automáticas estarán ocultas!\n"; /* No comment provided by engineer. */ "Interface" = "Interfaz"; @@ -2611,7 +2580,7 @@ /* No comment provided by engineer. */ "Invalid response" = "Respuesta no válida"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid server address!" = "¡Dirección de servidor no válida!"; /* item status text */ @@ -2716,7 +2685,7 @@ /* No comment provided by engineer. */ "Joining group" = "Entrando al grupo"; -/* No comment provided by engineer. */ +/* alert action */ "Keep" = "Guardar"; /* No comment provided by engineer. */ @@ -2725,7 +2694,7 @@ /* No comment provided by engineer. */ "Keep the app open to use it from desktop" = "Mantén la aplicación abierta para usarla desde el ordenador"; -/* No comment provided by engineer. */ +/* alert title */ "Keep unused invitation?" = "¿Guardar invitación no usada?"; /* No comment provided by engineer. */ @@ -2782,9 +2751,6 @@ /* No comment provided by engineer. */ "Live messages" = "Mensajes en vivo"; -/* No comment provided by engineer. */ -"No push server" = "No push server"; - /* No comment provided by engineer. */ "Local name" = "Nombre local"; @@ -2797,24 +2763,15 @@ /* No comment provided by engineer. */ "Lock mode" = "Modo bloqueo"; -/* No comment provided by engineer. */ -"Make a private connection" = "Establecer una conexión privada"; - /* No comment provided by engineer. */ "Make one message disappear" = "Escribir un mensaje temporal"; /* No comment provided by engineer. */ "Make profile private!" = "¡Hacer perfil privado!"; -/* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Asegúrate de que las direcciones del servidor %@ tienen el formato correcto, están separadas por líneas y no duplicadas (%@)."; - /* No comment provided by engineer. */ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Asegúrate de que las direcciones del servidor WebRTC ICE tienen el formato correcto, están separadas por líneas y no duplicadas."; -/* No comment provided by engineer. */ -"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Muchos se preguntarán: *si SimpleX no tiene identificadores de usuario, ¿cómo puede entregar los mensajes?*"; - /* No comment provided by engineer. */ "Mark deleted for everyone" = "Marcar como eliminado para todos"; @@ -3160,6 +3117,9 @@ /* copied message info in history */ "no text" = "sin texto"; +/* No comment provided by engineer. */ +"No user identifiers." = "Sin identificadores de usuario."; + /* No comment provided by engineer. */ "Not compatible!" = "¡No compatible!"; @@ -3282,18 +3242,9 @@ /* authentication reason */ "Open migration to another device" = "Abrir menú migración a otro dispositivo"; -/* No comment provided by engineer. */ -"Open server settings" = "Abrir configuración del servidor"; - /* No comment provided by engineer. */ "Open Settings" = "Abrir Configuración"; -/* authentication reason */ -"Open user profiles" = "Abrir perfil de usuario"; - -/* No comment provided by engineer. */ -"Anybody can host servers." = "Cualquiera puede alojar servidores."; - /* No comment provided by engineer. */ "Opening app…" = "Iniciando aplicación…"; @@ -3315,9 +3266,6 @@ /* No comment provided by engineer. */ "Other" = "Otro"; -/* No comment provided by engineer. */ -"Other %@ servers" = "Otros servidores %@"; - /* No comment provided by engineer. */ "other errors" = "otros errores"; @@ -3372,9 +3320,6 @@ /* No comment provided by engineer. */ "Pending" = "Pendientes"; -/* No comment provided by engineer. */ -"You decide who can connect." = "Tu decides quién se conecta."; - /* No comment provided by engineer. */ "Periodic" = "Periódicamente"; @@ -3453,9 +3398,6 @@ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Conserva el último borrador del mensaje con los datos adjuntos."; -/* No comment provided by engineer. */ -"Preset server" = "Servidor predefinido"; - /* No comment provided by engineer. */ "Preset server address" = "Dirección del servidor predefinida"; @@ -3504,7 +3446,7 @@ /* No comment provided by engineer. */ "Profile theme" = "Tema del perfil"; -/* No comment provided by engineer. */ +/* alert message */ "Profile update will be sent to your contacts." = "La actualización del perfil se enviará a tus contactos."; /* No comment provided by engineer. */ @@ -3589,10 +3531,10 @@ "Read more" = "Conoce más"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Conoce más en el [Manual del Usuario](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Conoce más en la [Guía del Usuario](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Conoce más en la [Guía del Usuario](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Conoce más en el [Manual del Usuario](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Conoce más en el [Manual del Usuario](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; @@ -3600,9 +3542,6 @@ /* No comment provided by engineer. */ "Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Conoce más en nuestro [repositorio GitHub](https://github.com/simplex-chat/simplex-chat#readme)."; -/* No comment provided by engineer. */ -"Read more in our GitHub repository." = "Conoce más en nuestro repositorio GitHub."; - /* No comment provided by engineer. */ "Receipts are disabled" = "Las confirmaciones están desactivadas"; @@ -3875,7 +3814,7 @@ /* No comment provided by engineer. */ "Save servers" = "Guardar servidores"; -/* No comment provided by engineer. */ +/* alert title */ "Save servers?" = "¿Guardar servidores?"; /* No comment provided by engineer. */ @@ -4028,9 +3967,6 @@ /* No comment provided by engineer. */ "Send notifications" = "Enviar notificaciones"; -/* No comment provided by engineer. */ -"Send notifications:" = "Enviar notificaciones:"; - /* No comment provided by engineer. */ "Send questions and ideas" = "Consultas y sugerencias"; @@ -4193,7 +4129,8 @@ /* No comment provided by engineer. */ "Shape profile images" = "Dar forma a las imágenes de perfil"; -/* chat item action */ +/* alert action + chat item action */ "Share" = "Compartir"; /* No comment provided by engineer. */ @@ -4202,7 +4139,7 @@ /* No comment provided by engineer. */ "Share address" = "Compartir dirección"; -/* No comment provided by engineer. */ +/* alert title */ "Share address with contacts?" = "¿Compartir la dirección con los contactos?"; /* No comment provided by engineer. */ @@ -4385,10 +4322,10 @@ /* No comment provided by engineer. */ "Stop sending file?" = "¿Dejar de enviar el archivo?"; -/* No comment provided by engineer. */ +/* alert action */ "Stop sharing" = "Dejar de compartir"; -/* No comment provided by engineer. */ +/* alert title */ "Stop sharing address?" = "¿Dejar de compartir la dirección?"; /* authentication reason */ @@ -4484,7 +4421,7 @@ /* No comment provided by engineer. */ "Test servers" = "Probar servidores"; -/* No comment provided by engineer. */ +/* alert title */ "Tests failed!" = "¡Pruebas no superadas!"; /* No comment provided by engineer. */ @@ -4496,9 +4433,6 @@ /* No comment provided by engineer. */ "Thanks to the users – contribute via Weblate!" = "¡Nuestro agradecimiento a todos los colaboradores! Puedes contribuir a través de Weblate"; -/* No comment provided by engineer. */ -"No user identifiers." = "Sin identificadores de usuario."; - /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "La aplicación puede notificarte cuando recibas mensajes o solicitudes de contacto: por favor, abre la configuración para activarlo."; @@ -4523,6 +4457,9 @@ /* 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 future of messaging" = "La nueva generación de mensajería privada"; + /* No comment provided by engineer. */ "The hash of the previous message is different." = "El hash del mensaje anterior es diferente."; @@ -4541,9 +4478,6 @@ /* No comment provided by engineer. */ "The messages will be marked as moderated for all members." = "Los mensajes serán marcados como moderados para todos los miembros."; -/* No comment provided by engineer. */ -"The future of messaging" = "La nueva generación de mensajería privada"; - /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "La base de datos antigua no se eliminó durante la migración, puede eliminarse."; @@ -4631,9 +4565,6 @@ /* No comment provided by engineer. */ "To make a new connection" = "Para hacer una conexión nueva"; -/* No comment provided by engineer. */ -"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Para proteger tu privacidad, en lugar de los identificadores de usuario que usan el resto de plataformas, SimpleX dispone de identificadores para las colas de mensajes, independientes para cada uno de tus contactos."; - /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Para proteger la zona horaria, los archivos de imagen/voz usan la hora UTC."; @@ -4643,6 +4574,9 @@ /* No comment provided by engineer. */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "Para proteger tu dirección IP, el enrutamiento privado usa tu lista de servidores SMP para enviar mensajes."; +/* No comment provided by engineer. */ +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Para proteger tu privacidad, en lugar de los identificadores de usuario que usan el resto de plataformas, SimpleX dispone de identificadores para las colas de mensajes, independientes para cada uno de tus contactos."; + /* No comment provided by engineer. */ "To record speech please grant permission to use Microphone." = "Para grabación de voz, por favor concede el permiso para usar el micrófono."; @@ -5030,9 +4964,6 @@ /* No comment provided by engineer. */ "when IP hidden" = "con IP oculta"; -/* No comment provided by engineer. */ -"When people request to connect, you can accept or reject it." = "Cuando alguien solicite conectarse podrás aceptar o rechazar la solicitud."; - /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Cuando compartes un perfil incógnito con alguien, este perfil también se usará para los grupos a los que te inviten."; @@ -5174,9 +5105,6 @@ /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "Puedes compartir esta dirección con tus contactos para que puedan conectar con **%@**."; -/* No comment provided by engineer. */ -"You can share your address as a link or QR code - anybody can connect to you." = "Puedes compartir tu dirección como enlace o código QR para que cualquiera pueda conectarse contigo."; - /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "Puede iniciar Chat a través de la Configuración / Base de datos de la aplicación o reiniciando la aplicación"; @@ -5189,7 +5117,7 @@ /* No comment provided by engineer. */ "You can use markdown to format messages:" = "Puedes usar la sintaxis markdown para dar formato a tus mensajes:"; -/* No comment provided by engineer. */ +/* alert message */ "You can view invitation link again in connection details." = "Podrás ver el enlace de invitación en detalles de conexión."; /* No comment provided by engineer. */ @@ -5210,6 +5138,9 @@ /* No comment provided by engineer. */ "You could not be verified; please try again." = "No has podido ser autenticado. Inténtalo de nuevo."; +/* No comment provided by engineer. */ +"You decide who can connect." = "Tu decides quién se conecta."; + /* No comment provided by engineer. */ "You have already requested connection via this address!" = "¡Ya has solicitado la conexión mediante esta dirección!"; @@ -5300,9 +5231,6 @@ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Estás usando un perfil incógnito en este grupo. Para evitar descubrir tu perfil principal no se permite invitar contactos"; -/* No comment provided by engineer. */ -"Your %@ servers" = "Mis servidores %@"; - /* No comment provided by engineer. */ "Your calls" = "Llamadas"; @@ -5366,9 +5294,6 @@ /* No comment provided by engineer. */ "Your random profile" = "Tu perfil aleatorio"; -/* No comment provided by engineer. */ -"Your server" = "Tu servidor"; - /* No comment provided by engineer. */ "Your server address" = "Dirección del servidor"; @@ -5381,6 +5306,3 @@ /* No comment provided by engineer. */ "Your SMP servers" = "Servidores SMP"; -/* No comment provided by engineer. */ -"Your XFTP servers" = "Servidores XFTP"; - diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index 081e8735a1..f927dbdabb 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -1,15 +1,6 @@ /* No comment provided by engineer. */ "\n" = "\n"; -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - /* No comment provided by engineer. */ " (" = " ("; @@ -262,12 +253,6 @@ /* No comment provided by engineer. */ "Abort changing address?" = "Keskeytä osoitteenvaihto?"; -/* No comment provided by engineer. */ -"About SimpleX" = "Tietoja SimpleX:stä"; - -/* No comment provided by engineer. */ -"About SimpleX address" = "Tietoja SimpleX osoitteesta"; - /* No comment provided by engineer. */ "About SimpleX Chat" = "Tietoja SimpleX Chatistä"; @@ -295,9 +280,6 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Lisää osoite profiiliisi, jotta kontaktisi voivat jakaa sen muiden kanssa. Profiilipäivitys lähetetään kontakteillesi."; -/* No comment provided by engineer. */ -"Add preset servers" = "Lisää esiasetettuja palvelimia"; - /* No comment provided by engineer. */ "Add profile" = "Lisää profiili"; @@ -424,6 +406,9 @@ /* No comment provided by engineer. */ "Answer call" = "Vastaa puheluun"; +/* No comment provided by engineer. */ +"Anybody can host servers." = "Avoimen lähdekoodin protokolla ja koodi - kuka tahansa voi käyttää palvelimia."; + /* No comment provided by engineer. */ "App build: %@" = "Sovellusversio: %@"; @@ -544,7 +529,8 @@ /* No comment provided by engineer. */ "Can't invite contacts!" = "Kontakteja ei voi kutsua!"; -/* alert button */ +/* alert action + alert button */ "Cancel" = "Peruuta"; /* feature offered item */ @@ -632,7 +618,7 @@ /* No comment provided by engineer. */ "Chats" = "Keskustelut"; -/* No comment provided by engineer. */ +/* alert title */ "Check server address and try again." = "Tarkista palvelimen osoite ja yritä uudelleen."; /* No comment provided by engineer. */ @@ -794,9 +780,6 @@ /* No comment provided by engineer. */ "Create" = "Luo"; -/* No comment provided by engineer. */ -"Create an address to let people connect with you." = "Luo osoite, jolla ihmiset voivat ottaa sinuun yhteyttä."; - /* server test step */ "Create file" = "Luo tiedosto"; @@ -920,7 +903,8 @@ /* No comment provided by engineer. */ "default (yes)" = "oletusarvo (kyllä)"; -/* chat item action +/* alert action + chat item action swipe action */ "Delete" = "Poista"; @@ -1353,18 +1337,12 @@ /* No comment provided by engineer. */ "Error joining group" = "Virhe ryhmään liittymisessä"; -/* No comment provided by engineer. */ -"Error loading %@ servers" = "Virhe %@-palvelimien lataamisessa"; - /* alert title */ "Error receiving file" = "Virhe tiedoston vastaanottamisessa"; /* No comment provided by engineer. */ "Error removing member" = "Virhe poistettaessa jäsentä"; -/* No comment provided by engineer. */ -"Error saving %@ servers" = "Virhe %@ palvelimien tallentamisessa"; - /* No comment provided by engineer. */ "Error saving group profile" = "Virhe ryhmäprofiilin tallentamisessa"; @@ -1632,9 +1610,6 @@ /* time unit */ "hours" = "tuntia"; -/* No comment provided by engineer. */ -"How it works" = "Kuinka se toimii"; - /* No comment provided by engineer. */ "How SimpleX works" = "Miten SimpleX toimii"; @@ -1744,10 +1719,10 @@ "Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Asenna [SimpleX Chat terminaalille](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"Instant push notifications will be hidden!\n" = "Välittömät push-ilmoitukset ovat piilossa!\n"; +"Instant" = "Heti"; /* No comment provided by engineer. */ -"Instant" = "Heti"; +"Instant push notifications will be hidden!\n" = "Välittömät push-ilmoitukset ovat piilossa!\n"; /* No comment provided by engineer. */ "Interface" = "Käyttöliittymä"; @@ -1764,7 +1739,7 @@ /* invalid chat item */ "invalid data" = "virheelliset tiedot"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid server address!" = "Virheellinen palvelinosoite!"; /* item status text */ @@ -1893,9 +1868,6 @@ /* No comment provided by engineer. */ "Live messages" = "Live-viestit"; -/* No comment provided by engineer. */ -"No push server" = "Paikallinen"; - /* No comment provided by engineer. */ "Local name" = "Paikallinen nimi"; @@ -1908,24 +1880,15 @@ /* No comment provided by engineer. */ "Lock mode" = "Lukitustila"; -/* No comment provided by engineer. */ -"Make a private connection" = "Luo yksityinen yhteys"; - /* No comment provided by engineer. */ "Make one message disappear" = "Hävitä yksi viesti"; /* No comment provided by engineer. */ "Make profile private!" = "Tee profiilista yksityinen!"; -/* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Varmista, että %@-palvelinosoitteet ovat oikeassa muodossa, että ne on erotettu toisistaan riveittäin ja että ne eivät ole päällekkäisiä (%@)."; - /* No comment provided by engineer. */ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Varmista, että WebRTC ICE -palvelinosoitteet ovat oikeassa muodossa, rivieroteltuina ja että ne eivät ole päällekkäisiä."; -/* No comment provided by engineer. */ -"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Monet ihmiset kysyivät: *Jos SimpleX:llä ei ole käyttäjätunnuksia, miten se voi toimittaa viestejä?*"; - /* No comment provided by engineer. */ "Mark deleted for everyone" = "Merkitse poistetuksi kaikilta"; @@ -2127,12 +2090,18 @@ /* No comment provided by engineer. */ "No permission to record voice message" = "Ei lupaa ääniviestin tallentamiseen"; +/* No comment provided by engineer. */ +"No push server" = "Paikallinen"; + /* No comment provided by engineer. */ "No received or sent files" = "Ei vastaanotettuja tai lähetettyjä tiedostoja"; /* copied message info in history */ "no text" = "ei tekstiä"; +/* No comment provided by engineer. */ +"No user identifiers." = "Ensimmäinen alusta ilman käyttäjätunnisteita – suunniteltu yksityiseksi."; + /* No comment provided by engineer. */ "Notifications" = "Ilmoitukset"; @@ -2234,12 +2203,6 @@ /* No comment provided by engineer. */ "Open Settings" = "Avaa Asetukset"; -/* authentication reason */ -"Open user profiles" = "Avaa käyttäjäprofiilit"; - -/* No comment provided by engineer. */ -"Anybody can host servers." = "Avoimen lähdekoodin protokolla ja koodi - kuka tahansa voi käyttää palvelimia."; - /* member role */ "owner" = "omistaja"; @@ -2267,9 +2230,6 @@ /* No comment provided by engineer. */ "peer-to-peer" = "vertais"; -/* No comment provided by engineer. */ -"You decide who can connect." = "Kimin bağlanabileceğine siz karar verirsiniz."; - /* No comment provided by engineer. */ "Periodic" = "Ajoittain"; @@ -2327,9 +2287,6 @@ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Säilytä viimeinen viestiluonnos liitteineen."; -/* No comment provided by engineer. */ -"Preset server" = "Esiasetettu palvelin"; - /* No comment provided by engineer. */ "Preset server address" = "Esiasetettu palvelimen osoite"; @@ -2354,7 +2311,7 @@ /* No comment provided by engineer. */ "Profile password" = "Profiilin salasana"; -/* No comment provided by engineer. */ +/* alert message */ "Profile update will be sent to your contacts." = "Profiilipäivitys lähetetään kontakteillesi."; /* No comment provided by engineer. */ @@ -2417,9 +2374,6 @@ /* No comment provided by engineer. */ "Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Lue lisää [GitHub-arkistosta](https://github.com/simplex-chat/simplex-chat#readme)."; -/* No comment provided by engineer. */ -"Read more in our GitHub repository." = "Lue lisää GitHub-tietovarastostamme."; - /* No comment provided by engineer. */ "Receipts are disabled" = "Kuittaukset pois käytöstä"; @@ -2605,7 +2559,7 @@ /* No comment provided by engineer. */ "Save servers" = "Tallenna palvelimet"; -/* No comment provided by engineer. */ +/* alert title */ "Save servers?" = "Tallenna palvelimet?"; /* No comment provided by engineer. */ @@ -2686,9 +2640,6 @@ /* No comment provided by engineer. */ "Send notifications" = "Lähetys ilmoitukset"; -/* No comment provided by engineer. */ -"Send notifications:" = "Lähetys ilmoitukset:"; - /* No comment provided by engineer. */ "Send questions and ideas" = "Lähetä kysymyksiä ja ideoita"; @@ -2782,7 +2733,8 @@ /* No comment provided by engineer. */ "Settings" = "Asetukset"; -/* chat item action */ +/* alert action + chat item action */ "Share" = "Jaa"; /* No comment provided by engineer. */ @@ -2791,7 +2743,7 @@ /* No comment provided by engineer. */ "Share address" = "Jaa osoite"; -/* No comment provided by engineer. */ +/* alert title */ "Share address with contacts?" = "Jaa osoite kontakteille?"; /* No comment provided by engineer. */ @@ -2896,10 +2848,10 @@ /* No comment provided by engineer. */ "Stop sending file?" = "Lopeta tiedoston lähettäminen?"; -/* No comment provided by engineer. */ +/* alert action */ "Stop sharing" = "Lopeta jakaminen"; -/* No comment provided by engineer. */ +/* alert title */ "Stop sharing address?" = "Lopeta osoitteen jakaminen?"; /* authentication reason */ @@ -2956,7 +2908,7 @@ /* No comment provided by engineer. */ "Test servers" = "Testipalvelimet"; -/* No comment provided by engineer. */ +/* alert title */ "Tests failed!" = "Testit epäonnistuivat!"; /* No comment provided by engineer. */ @@ -2968,9 +2920,6 @@ /* No comment provided by engineer. */ "Thanks to the users – contribute via Weblate!" = "Kiitokset käyttäjille – osallistu Weblaten kautta!"; -/* No comment provided by engineer. */ -"No user identifiers." = "Ensimmäinen alusta ilman käyttäjätunnisteita – suunniteltu yksityiseksi."; - /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Sovellus voi ilmoittaa sinulle, kun saat viestejä tai yhteydenottopyyntöjä - avaa asetukset ottaaksesi ne käyttöön."; @@ -2989,6 +2938,9 @@ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Salaus toimii ja uutta salaussopimusta ei tarvita. Tämä voi johtaa yhteysvirheisiin!"; +/* No comment provided by engineer. */ +"The future of messaging" = "Seuraavan sukupolven yksityisviestit"; + /* No comment provided by engineer. */ "The hash of the previous message is different." = "Edellisen viestin tarkiste on erilainen."; @@ -3001,9 +2953,6 @@ /* No comment provided by engineer. */ "The message will be marked as moderated for all members." = "Viesti merkitään moderoiduksi kaikille jäsenille."; -/* No comment provided by engineer. */ -"The future of messaging" = "Seuraavan sukupolven yksityisviestit"; - /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Vanhaa tietokantaa ei poistettu siirron aikana, se voidaan kuitenkin poistaa."; @@ -3055,15 +3004,15 @@ /* No comment provided by engineer. */ "To make a new connection" = "Uuden yhteyden luominen"; -/* No comment provided by engineer. */ -"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Yksityisyyden suojaamiseksi kaikkien muiden alustojen käyttämien käyttäjätunnusten sijaan SimpleX käyttää viestijonojen tunnisteita, jotka ovat kaikille kontakteille erillisiä."; - /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Aikavyöhykkeen suojaamiseksi kuva-/äänitiedostot käyttävät UTC:tä."; /* No comment provided by engineer. */ "To protect your information, turn on SimpleX Lock.\nYou will be prompted to complete authentication before this feature is enabled." = "Suojaa tietosi ottamalla SimpleX Lock käyttöön.\nSinua kehotetaan suorittamaan todennus loppuun, ennen kuin tämä ominaisuus otetaan käyttöön."; +/* No comment provided by engineer. */ +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Yksityisyyden suojaamiseksi kaikkien muiden alustojen käyttämien käyttäjätunnusten sijaan SimpleX käyttää viestijonojen tunnisteita, jotka ovat kaikille kontakteille erillisiä."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Jos haluat nauhoittaa ääniviestin, anna lupa käyttää mikrofonia."; @@ -3286,9 +3235,6 @@ /* No comment provided by engineer. */ "When available" = "Kun saatavilla"; -/* No comment provided by engineer. */ -"When people request to connect, you can accept or reject it." = "Kun ihmiset pyytävät yhteyden muodostamista, voit hyväksyä tai hylätä sen."; - /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Kun jaat inkognitoprofiilin jonkun kanssa, tätä profiilia käytetään ryhmissä, joihin tämä sinut kutsuu."; @@ -3355,9 +3301,6 @@ /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "Voit jakaa tämän osoitteen kontaktiesi kanssa, jotta ne voivat muodostaa yhteyden **%@** kanssa."; -/* No comment provided by engineer. */ -"You can share your address as a link or QR code - anybody can connect to you." = "Voit jakaa osoitteesi linkkinä tai QR-koodina - kuka tahansa voi muodostaa yhteyden sinuun."; - /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "Voit aloittaa keskustelun sovelluksen Asetukset / Tietokanta kautta tai käynnistämällä sovelluksen uudelleen"; @@ -3385,6 +3328,9 @@ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Sinua ei voitu todentaa; yritä uudelleen."; +/* No comment provided by engineer. */ +"You decide who can connect." = "Kimin bağlanabileceğine siz karar verirsiniz."; + /* No comment provided by engineer. */ "You have to enter passphrase every time the app starts - it is not stored on the device." = "Sinun on annettava tunnuslause aina, kun sovellus käynnistyy - sitä ei tallenneta laitteeseen."; @@ -3451,9 +3397,6 @@ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Käytät tässä ryhmässä incognito-profiilia. Kontaktien kutsuminen ei ole sallittua, jotta pääprofiilisi ei tule jaetuksi"; -/* No comment provided by engineer. */ -"Your %@ servers" = "%@-palvelimesi"; - /* No comment provided by engineer. */ "Your calls" = "Puhelusi"; @@ -3502,9 +3445,6 @@ /* No comment provided by engineer. */ "Your random profile" = "Satunnainen profiilisi"; -/* No comment provided by engineer. */ -"Your server" = "Palvelimesi"; - /* No comment provided by engineer. */ "Your server address" = "Palvelimesi osoite"; @@ -3517,6 +3457,3 @@ /* No comment provided by engineer. */ "Your SMP servers" = "SMP-palvelimesi"; -/* No comment provided by engineer. */ -"Your XFTP servers" = "XFTP-palvelimesi"; - diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index 4b4a5aaf4d..239d425973 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -1,15 +1,6 @@ /* No comment provided by engineer. */ "\n" = "\n"; -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - /* No comment provided by engineer. */ " (" = " ("; @@ -157,6 +148,9 @@ /* notification title */ "%@ wants to connect!" = "%@ veut se connecter !"; +/* format for date separator in chat */ +"%@, %@" = "%1$@, %2$@"; + /* No comment provided by engineer. */ "%@, %@ and %lld members" = "%@, %@ et %lld membres"; @@ -169,9 +163,24 @@ /* time interval */ "%d days" = "%d jours"; +/* forward confirmation reason */ +"%d file(s) are still being downloaded." = "%d fichier(s) en cours de téléchargement."; + +/* forward confirmation reason */ +"%d file(s) failed to download." = "Le téléchargement de %d fichier(s) a échoué."; + +/* forward confirmation reason */ +"%d file(s) were deleted." = "Le(s) fichier(s) %d a(ont) été supprimé(s)."; + +/* forward confirmation reason */ +"%d file(s) were not downloaded." = "Le(s) fichier(s) %d n'a (n'ont) pas été téléchargé(s)."; + /* time interval */ "%d hours" = "%d heures"; +/* alert title */ +"%d messages not forwarded" = "%d messages non transférés"; + /* time interval */ "%d min" = "%d min"; @@ -319,12 +328,6 @@ /* No comment provided by engineer. */ "Abort changing address?" = "Abandonner le changement d'adresse ?"; -/* No comment provided by engineer. */ -"About SimpleX" = "À propos de SimpleX"; - -/* No comment provided by engineer. */ -"About SimpleX address" = "À propos de l'adresse SimpleX"; - /* No comment provided by engineer. */ "About SimpleX Chat" = "À propos de SimpleX Chat"; @@ -364,12 +367,6 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Ajoutez une adresse à votre profil, afin que vos contacts puissent la partager avec d'autres personnes. La mise à jour du profil sera envoyée à vos contacts."; -/* No comment provided by engineer. */ -"Add contact" = "Ajouter le contact"; - -/* No comment provided by engineer. */ -"Add preset servers" = "Ajouter des serveurs prédéfinis"; - /* No comment provided by engineer. */ "Add profile" = "Ajouter un profil"; @@ -556,6 +553,9 @@ /* No comment provided by engineer. */ "Answer call" = "Répondre à l'appel"; +/* No comment provided by engineer. */ +"Anybody can host servers." = "N'importe qui peut heberger un serveur."; + /* No comment provided by engineer. */ "App build: %@" = "Build de l'app : %@"; @@ -574,6 +574,9 @@ /* No comment provided by engineer. */ "App passcode is replaced with self-destruct passcode." = "Le code d'accès de l'application est remplacé par un code d'autodestruction."; +/* No comment provided by engineer. */ +"App session" = "Session de l'app"; + /* No comment provided by engineer. */ "App version" = "Version de l'app"; @@ -646,6 +649,9 @@ /* No comment provided by engineer. */ "Auto-accept images" = "Images auto-acceptées"; +/* alert title */ +"Auto-accept settings" = "Paramètres de réception automatique"; + /* No comment provided by engineer. */ "Back" = "Retour"; @@ -667,15 +673,30 @@ /* No comment provided by engineer. */ "Bad message ID" = "Mauvais ID de message"; +/* No comment provided by engineer. */ +"Better calls" = "Appels améliorés"; + /* No comment provided by engineer. */ "Better groups" = "Des groupes plus performants"; +/* No comment provided by engineer. */ +"Better message dates." = "Meilleures dates de messages."; + /* No comment provided by engineer. */ "Better messages" = "Meilleurs messages"; /* No comment provided by engineer. */ "Better networking" = "Meilleure gestion de réseau"; +/* No comment provided by engineer. */ +"Better notifications" = "Notifications améliorées"; + +/* No comment provided by engineer. */ +"Better security ✅" = "Sécurité accrue ✅"; + +/* No comment provided by engineer. */ +"Better user experience" = "Une meilleure expérience pour l'utilisateur"; + /* No comment provided by engineer. */ "Black" = "Noir"; @@ -778,7 +799,8 @@ /* No comment provided by engineer. */ "Can't message member" = "Impossible d'envoyer un message à ce membre"; -/* alert button */ +/* alert action + alert button */ "Cancel" = "Annuler"; /* No comment provided by engineer. */ @@ -887,6 +909,9 @@ /* No comment provided by engineer. */ "Chat preferences" = "Préférences de chat"; +/* alert message */ +"Chat preferences were changed." = "Les préférences de discussion ont été modifiées."; + /* No comment provided by engineer. */ "Chat profile" = "Profil d'utilisateur"; @@ -896,7 +921,7 @@ /* No comment provided by engineer. */ "Chats" = "Discussions"; -/* No comment provided by engineer. */ +/* alert title */ "Check server address and try again." = "Vérifiez l'adresse du serveur et réessayez."; /* No comment provided by engineer. */ @@ -959,9 +984,6 @@ /* No comment provided by engineer. */ "Configure ICE servers" = "Configurer les serveurs ICE"; -/* No comment provided by engineer. */ -"Configured %@ servers" = "%@ serveurs configurés"; - /* No comment provided by engineer. */ "Confirm" = "Confirmer"; @@ -1178,6 +1200,9 @@ /* No comment provided by engineer. */ "Core version: v%@" = "Version du cœur : v%@"; +/* No comment provided by engineer. */ +"Corner" = "Coin"; + /* No comment provided by engineer. */ "Correct name to %@?" = "Corriger le nom pour %@ ?"; @@ -1187,9 +1212,6 @@ /* No comment provided by engineer. */ "Create a group using a random profile." = "Création de groupes via un profil aléatoire."; -/* No comment provided by engineer. */ -"Create an address to let people connect with you." = "Vous pouvez créer une adresse pour permettre aux autres utilisateurs de vous contacter."; - /* server test step */ "Create file" = "Créer un fichier"; @@ -1259,6 +1281,9 @@ /* No comment provided by engineer. */ "Custom time" = "Délai personnalisé"; +/* No comment provided by engineer. */ +"Customizable message shape." = "Forme des messages personnalisable."; + /* No comment provided by engineer. */ "Customize theme" = "Personnaliser le thème"; @@ -1349,7 +1374,8 @@ /* No comment provided by engineer. */ "default (yes)" = "par défaut (oui)"; -/* chat item action +/* alert action + chat item action swipe action */ "Delete" = "Supprimer"; @@ -1449,6 +1475,9 @@ /* No comment provided by engineer. */ "Delete old database?" = "Supprimer l'ancienne base de données ?"; +/* No comment provided by engineer. */ +"Delete or moderate up to 200 messages." = "Supprimer ou modérer jusqu'à 200 messages."; + /* No comment provided by engineer. */ "Delete pending connection?" = "Supprimer la connexion en attente ?"; @@ -1611,6 +1640,9 @@ /* No comment provided by engineer. */ "Do NOT send messages directly, even if your or destination server does not support private routing." = "Ne pas envoyer de messages directement, même si votre serveur ou le serveur de destination ne prend pas en charge le routage privé."; +/* No comment provided by engineer. */ +"Do not use credentials with proxy." = "Ne pas utiliser d'identifiants avec le proxy."; + /* No comment provided by engineer. */ "Do NOT use private routing." = "Ne pas utiliser de routage privé."; @@ -1642,6 +1674,9 @@ /* server test step */ "Download file" = "Télécharger le fichier"; +/* alert action */ +"Download files" = "Télécharger les fichiers"; + /* No comment provided by engineer. */ "Downloaded" = "Téléchargé"; @@ -1858,12 +1893,18 @@ /* No comment provided by engineer. */ "Error changing address" = "Erreur de changement d'adresse"; +/* No comment provided by engineer. */ +"Error changing connection profile" = "Erreur lors du changement de profil de connexion"; + /* No comment provided by engineer. */ "Error changing role" = "Erreur lors du changement de rôle"; /* No comment provided by engineer. */ "Error changing setting" = "Erreur de changement de paramètre"; +/* No comment provided by engineer. */ +"Error changing to incognito!" = "Erreur lors du passage en mode incognito !"; + /* No comment provided by engineer. */ "Error connecting to forwarding server %@. Please try later." = "Erreur de connexion au serveur de redirection %@. Veuillez réessayer plus tard."; @@ -1934,7 +1975,7 @@ "Error joining group" = "Erreur lors de la liaison avec le groupe"; /* No comment provided by engineer. */ -"Error loading %@ servers" = "Erreur lors du chargement des serveurs %@"; +"Error migrating settings" = "Erreur lors de la migration des paramètres"; /* No comment provided by engineer. */ "Error opening chat" = "Erreur lors de l'ouverture du chat"; @@ -1954,9 +1995,6 @@ /* No comment provided by engineer. */ "Error resetting statistics" = "Erreur de réinitialisation des statistiques"; -/* No comment provided by engineer. */ -"Error saving %@ servers" = "Erreur lors de la sauvegarde des serveurs %@"; - /* No comment provided by engineer. */ "Error saving group profile" = "Erreur lors de la sauvegarde du profil de groupe"; @@ -1996,6 +2034,9 @@ /* No comment provided by engineer. */ "Error stopping chat" = "Erreur lors de l'arrêt du chat"; +/* No comment provided by engineer. */ +"Error switching profile" = "Erreur lors du changement de profil"; + /* alertTitle */ "Error switching profile!" = "Erreur lors du changement de profil !"; @@ -2083,6 +2124,9 @@ /* No comment provided by engineer. */ "File error" = "Erreur de fichier"; +/* alert message */ +"File errors:\n%@" = "Erreurs de fichier :\n%@"; + /* file error text */ "File not found - most likely file was deleted or cancelled." = "Fichier introuvable - le fichier a probablement été supprimé ou annulé."; @@ -2164,9 +2208,21 @@ /* chat item action */ "Forward" = "Transférer"; +/* alert title */ +"Forward %d message(s)?" = "Transférer %d message(s) ?"; + /* No comment provided by engineer. */ "Forward and save messages" = "Transférer et sauvegarder des messages"; +/* alert action */ +"Forward messages" = "Transférer les messages"; + +/* alert message */ +"Forward messages without files?" = "Transférer les messages sans les fichiers ?"; + +/* No comment provided by engineer. */ +"Forward up to 20 messages at once." = "Transférez jusqu'à 20 messages à la fois."; + /* No comment provided by engineer. */ "forwarded" = "transféré"; @@ -2176,6 +2232,9 @@ /* No comment provided by engineer. */ "Forwarded from" = "Transféré depuis"; +/* No comment provided by engineer. */ +"Forwarding %lld messages" = "Transfert des %lld messages"; + /* No comment provided by engineer. */ "Forwarding server %@ failed to connect to destination server %@. Please try later." = "Le serveur de redirection %@ n'a pas réussi à se connecter au serveur de destination %@. Veuillez réessayer plus tard."; @@ -2338,9 +2397,6 @@ /* time unit */ "hours" = "heures"; -/* No comment provided by engineer. */ -"How it works" = "Comment ça fonctionne"; - /* No comment provided by engineer. */ "How SimpleX works" = "Comment SimpleX fonctionne"; @@ -2404,6 +2460,9 @@ /* No comment provided by engineer. */ "Importing archive" = "Importation de l'archive"; +/* No comment provided by engineer. */ +"Improved delivery, reduced traffic usage.\nMore improvements are coming soon!" = "Amélioration de la distribution, réduction de l'utilisation du trafic.\nD'autres améliorations sont à venir !"; + /* No comment provided by engineer. */ "Improved message delivery" = "Amélioration de la transmission des messages"; @@ -2480,10 +2539,10 @@ "Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Installer [SimpleX Chat pour terminal](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"Instant push notifications will be hidden!\n" = "Les notifications push instantanées vont être cachées !\n"; +"Instant" = "Instantané"; /* No comment provided by engineer. */ -"Instant" = "Instantané"; +"Instant push notifications will be hidden!\n" = "Les notifications push instantanées vont être cachées !\n"; /* No comment provided by engineer. */ "Interface" = "Interface"; @@ -2521,7 +2580,7 @@ /* No comment provided by engineer. */ "Invalid response" = "Réponse invalide"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid server address!" = "Adresse de serveur invalide !"; /* item status text */ @@ -2563,6 +2622,9 @@ /* No comment provided by engineer. */ "iOS Keychain will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving push notifications." = "La keychain d'iOS sera utilisée pour stocker en toute sécurité la phrase secrète après le redémarrage de l'app ou la modification de la phrase secrète - il permettra de recevoir les notifications push."; +/* No comment provided by engineer. */ +"IP address" = "Adresse IP"; + /* No comment provided by engineer. */ "Irreversible message deletion" = "Suppression irréversible des messages"; @@ -2623,7 +2685,7 @@ /* No comment provided by engineer. */ "Joining group" = "Entrain de rejoindre le groupe"; -/* No comment provided by engineer. */ +/* alert action */ "Keep" = "Conserver"; /* No comment provided by engineer. */ @@ -2632,7 +2694,7 @@ /* No comment provided by engineer. */ "Keep the app open to use it from desktop" = "Garder l'application ouverte pour l'utiliser depuis le bureau"; -/* No comment provided by engineer. */ +/* alert title */ "Keep unused invitation?" = "Conserver l'invitation inutilisée ?"; /* No comment provided by engineer. */ @@ -2689,9 +2751,6 @@ /* No comment provided by engineer. */ "Live messages" = "Messages dynamiques"; -/* No comment provided by engineer. */ -"No push server" = "No push server"; - /* No comment provided by engineer. */ "Local name" = "Nom local"; @@ -2704,24 +2763,15 @@ /* No comment provided by engineer. */ "Lock mode" = "Mode de verrouillage"; -/* No comment provided by engineer. */ -"Make a private connection" = "Établir une connexion privée"; - /* No comment provided by engineer. */ "Make one message disappear" = "Rendre un message éphémère"; /* No comment provided by engineer. */ "Make profile private!" = "Rendre un profil privé !"; -/* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Assurez-vous que les adresses des serveurs %@ sont au bon format et ne sont pas dupliquées, un par ligne (%@)."; - /* No comment provided by engineer. */ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Assurez-vous que les adresses des serveurs WebRTC ICE sont au bon format et ne sont pas dupliquées, un par ligne."; -/* No comment provided by engineer. */ -"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Beaucoup se demandent : *si SimpleX n'a pas d'identifiant d'utilisateur, comment peut-il délivrer des messages ?*"; - /* No comment provided by engineer. */ "Mark deleted for everyone" = "Marquer comme supprimé pour tout le monde"; @@ -2815,6 +2865,9 @@ /* No comment provided by engineer. */ "Message servers" = "Serveurs de messages"; +/* No comment provided by engineer. */ +"Message shape" = "Forme du message"; + /* No comment provided by engineer. */ "Message source remains private." = "La source du message reste privée."; @@ -2845,6 +2898,9 @@ /* No comment provided by engineer. */ "Messages sent" = "Messages envoyés"; +/* alert message */ +"Messages were deleted after you selected them." = "Les messages ont été supprimés après avoir été sélectionnés."; + /* No comment provided by engineer. */ "Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Les messages, fichiers et appels sont protégés par un chiffrement **de bout en bout** avec une confidentialité persistante, une répudiation et une récupération en cas d'effraction."; @@ -2998,6 +3054,12 @@ /* No comment provided by engineer. */ "New passphrase…" = "Nouvelle phrase secrète…"; +/* No comment provided by engineer. */ +"New SOCKS credentials will be used every time you start the app." = "De nouveaux identifiants SOCKS seront utilisés chaque fois que vous démarrerez l'application."; + +/* No comment provided by engineer. */ +"New SOCKS credentials will be used for each server." = "De nouveaux identifiants SOCKS seront utilisées pour chaque serveur."; + /* pref value */ "no" = "non"; @@ -3040,21 +3102,36 @@ /* No comment provided by engineer. */ "No network connection" = "Pas de connexion au réseau"; +/* No comment provided by engineer. */ +"No permission to record speech" = "Enregistrement des conversations non autorisé"; + +/* No comment provided by engineer. */ +"No permission to record video" = "Enregistrement de la vidéo non autorisé"; + /* No comment provided by engineer. */ "No permission to record voice message" = "Pas l'autorisation d'enregistrer un message vocal"; +/* No comment provided by engineer. */ +"No push server" = "No push server"; + /* No comment provided by engineer. */ "No received or sent files" = "Aucun fichier reçu ou envoyé"; /* copied message info in history */ "no text" = "aucun texte"; +/* No comment provided by engineer. */ +"No user identifiers." = "Aucun identifiant d'utilisateur."; + /* No comment provided by engineer. */ "Not compatible!" = "Non compatible !"; /* No comment provided by engineer. */ "Nothing selected" = "Aucune sélection"; +/* alert title */ +"Nothing to forward!" = "Rien à transférer !"; + /* No comment provided by engineer. */ "Notifications" = "Notifications"; @@ -3168,18 +3245,9 @@ /* authentication reason */ "Open migration to another device" = "Ouvrir le transfert vers un autre appareil"; -/* No comment provided by engineer. */ -"Open server settings" = "Ouvrir les paramètres du serveur"; - /* No comment provided by engineer. */ "Open Settings" = "Ouvrir les Paramètres"; -/* authentication reason */ -"Open user profiles" = "Ouvrir les profils d'utilisateurs"; - -/* No comment provided by engineer. */ -"Anybody can host servers." = "N\'importe qui peut heberger un serveur."; - /* No comment provided by engineer. */ "Opening app…" = "Ouverture de l'app…"; @@ -3193,7 +3261,7 @@ "Or securely share this file link" = "Ou partagez en toute sécurité le lien de ce fichier"; /* No comment provided by engineer. */ -"Or show this code" = "Ou présenter ce code"; +"Or show this code" = "Ou montrez ce code"; /* No comment provided by engineer. */ "other" = "autre"; @@ -3201,12 +3269,12 @@ /* No comment provided by engineer. */ "Other" = "Autres"; -/* No comment provided by engineer. */ -"Other %@ servers" = "Autres serveurs %@"; - /* No comment provided by engineer. */ "other errors" = "autres erreurs"; +/* alert message */ +"Other file errors:\n%@" = "Autres erreurs de fichiers :\n%@"; + /* member role */ "owner" = "propriétaire"; @@ -3228,6 +3296,9 @@ /* No comment provided by engineer. */ "Passcode set!" = "Code d'accès défini !"; +/* No comment provided by engineer. */ +"Password" = "Mot de passe"; + /* No comment provided by engineer. */ "Password to show" = "Mot de passe à entrer"; @@ -3252,9 +3323,6 @@ /* No comment provided by engineer. */ "Pending" = "En attente"; -/* No comment provided by engineer. */ -"You decide who can connect." = "Vous choisissez qui peut se connecter."; - /* No comment provided by engineer. */ "Periodic" = "Périodique"; @@ -3324,15 +3392,15 @@ /* No comment provided by engineer. */ "Polish interface" = "Interface en polonais"; +/* No comment provided by engineer. */ +"Port" = "Port"; + /* server test error */ "Possibly, certificate fingerprint in server address is incorrect" = "Il est possible que l'empreinte du certificat dans l'adresse du serveur soit incorrecte"; /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Conserver le brouillon du dernier message, avec les pièces jointes."; -/* No comment provided by engineer. */ -"Preset server" = "Serveur prédéfini"; - /* No comment provided by engineer. */ "Preset server address" = "Adresse du serveur prédéfinie"; @@ -3381,7 +3449,7 @@ /* No comment provided by engineer. */ "Profile theme" = "Thème de profil"; -/* No comment provided by engineer. */ +/* alert message */ "Profile update will be sent to your contacts." = "La mise à jour du profil sera envoyée à vos contacts."; /* No comment provided by engineer. */ @@ -3435,6 +3503,9 @@ /* No comment provided by engineer. */ "Proxied servers" = "Serveurs routés via des proxy"; +/* No comment provided by engineer. */ +"Proxy requires password" = "Le proxy est protégé par un mot de passe"; + /* No comment provided by engineer. */ "Push notifications" = "Notifications push"; @@ -3463,10 +3534,10 @@ "Read more" = "En savoir plus"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Pour en savoir plus, consultez le [Guide de l'utilisateur](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Pour en savoir plus, consultez le [Guide de l'utilisateur](https ://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Pour en savoir plus, consultez le [Guide de l'utilisateur](https ://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Pour en savoir plus, consultez le [Guide de l'utilisateur](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Pour en savoir plus, consultez le [Guide de l'utilisateur](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; @@ -3474,9 +3545,6 @@ /* No comment provided by engineer. */ "Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Pour en savoir plus, consultez notre [dépôt GitHub](https://github.com/simplex-chat/simplex-chat#readme)."; -/* No comment provided by engineer. */ -"Read more in our GitHub repository." = "Plus d'informations sur notre GitHub."; - /* No comment provided by engineer. */ "Receipts are disabled" = "Les accusés de réception sont désactivés"; @@ -3580,6 +3648,9 @@ /* No comment provided by engineer. */ "Remove" = "Supprimer"; +/* No comment provided by engineer. */ +"Remove archive?" = "Supprimer l'archive ?"; + /* No comment provided by engineer. */ "Remove image" = "Enlever l'image"; @@ -3746,12 +3817,15 @@ /* No comment provided by engineer. */ "Save servers" = "Enregistrer les serveurs"; -/* No comment provided by engineer. */ +/* alert title */ "Save servers?" = "Enregistrer les serveurs ?"; /* No comment provided by engineer. */ "Save welcome message?" = "Enregistrer le message d'accueil ?"; +/* alert title */ +"Save your profile?" = "Sauvegarder votre profil ?"; + /* No comment provided by engineer. */ "saved" = "enregistré"; @@ -3770,11 +3844,14 @@ /* No comment provided by engineer. */ "Saved WebRTC ICE servers will be removed" = "Les serveurs WebRTC ICE sauvegardés seront supprimés"; +/* No comment provided by engineer. */ +"Saving %lld messages" = "Sauvegarde de %lld messages"; + /* No comment provided by engineer. */ "Scale" = "Échelle"; /* No comment provided by engineer. */ -"Scan / Paste link" = "Scanner / Coller le lien"; +"Scan / Paste link" = "Scanner / Coller un lien"; /* No comment provided by engineer. */ "Scan code" = "Scanner le code"; @@ -3833,6 +3910,9 @@ /* chat item action */ "Select" = "Choisir"; +/* No comment provided by engineer. */ +"Select chat profile" = "Sélectionner un profil de discussion"; + /* No comment provided by engineer. */ "Selected %lld" = "%lld sélectionné(s)"; @@ -3890,9 +3970,6 @@ /* No comment provided by engineer. */ "Send notifications" = "Envoi de notifications"; -/* No comment provided by engineer. */ -"Send notifications:" = "Envoi de notifications :"; - /* No comment provided by engineer. */ "Send questions and ideas" = "Envoyez vos questions et idées"; @@ -3965,6 +4042,9 @@ /* No comment provided by engineer. */ "Sent via proxy" = "Envoyé via le proxy"; +/* No comment provided by engineer. */ +"Server" = "Serveur"; + /* No comment provided by engineer. */ "Server address" = "Adresse du serveur"; @@ -4046,10 +4126,14 @@ /* No comment provided by engineer. */ "Settings" = "Paramètres"; +/* alert message */ +"Settings were changed." = "Les paramètres ont été modifiés."; + /* No comment provided by engineer. */ "Shape profile images" = "Images de profil modelable"; -/* chat item action */ +/* alert action + chat item action */ "Share" = "Partager"; /* No comment provided by engineer. */ @@ -4058,7 +4142,7 @@ /* No comment provided by engineer. */ "Share address" = "Partager l'adresse"; -/* No comment provided by engineer. */ +/* alert title */ "Share address with contacts?" = "Partager l'adresse avec vos contacts ?"; /* No comment provided by engineer. */ @@ -4068,7 +4152,10 @@ "Share link" = "Partager le lien"; /* No comment provided by engineer. */ -"Share this 1-time invite link" = "Partager ce lien d'invitation unique"; +"Share profile" = "Partager le profil"; + +/* No comment provided by engineer. */ +"Share this 1-time invite link" = "Partagez ce lien d'invitation unique"; /* No comment provided by engineer. */ "Share to SimpleX" = "Partager sur SimpleX"; @@ -4148,6 +4235,9 @@ /* simplex link type */ "SimpleX one-time invitation" = "Invitation unique SimpleX"; +/* No comment provided by engineer. */ +"SimpleX protocols reviewed by Trail of Bits." = "Protocoles SimpleX audité par Trail of Bits."; + /* No comment provided by engineer. */ "Simplified incognito mode" = "Mode incognito simplifié"; @@ -4166,9 +4256,15 @@ /* No comment provided by engineer. */ "SMP server" = "Serveur SMP"; +/* No comment provided by engineer. */ +"SOCKS proxy" = "proxy SOCKS"; + /* blur media */ "Soft" = "Léger"; +/* No comment provided by engineer. */ +"Some app settings were not migrated." = "Certains paramètres de l'application n'ont pas été migrés."; + /* No comment provided by engineer. */ "Some file(s) were not exported:" = "Certains fichiers n'ont pas été exportés :"; @@ -4229,10 +4325,10 @@ /* No comment provided by engineer. */ "Stop sending file?" = "Arrêter l'envoi du fichier ?"; -/* No comment provided by engineer. */ +/* alert action */ "Stop sharing" = "Cesser le partage"; -/* No comment provided by engineer. */ +/* alert title */ "Stop sharing address?" = "Cesser le partage d'adresse ?"; /* authentication reason */ @@ -4262,12 +4358,21 @@ /* No comment provided by engineer. */ "Support SimpleX Chat" = "Supporter SimpleX Chat"; +/* No comment provided by engineer. */ +"Switch audio and video during the call." = "Passer de l'audio à la vidéo pendant l'appel."; + +/* No comment provided by engineer. */ +"Switch chat profile for 1-time invitations." = "Changer de profil de chat pour les invitations à usage unique."; + /* No comment provided by engineer. */ "System" = "Système"; /* No comment provided by engineer. */ "System authentication" = "Authentification du système"; +/* No comment provided by engineer. */ +"Tail" = "Queue"; + /* No comment provided by engineer. */ "Take picture" = "Prendre une photo"; @@ -4319,7 +4424,7 @@ /* No comment provided by engineer. */ "Test servers" = "Tester les serveurs"; -/* No comment provided by engineer. */ +/* alert title */ "Tests failed!" = "Échec des tests !"; /* No comment provided by engineer. */ @@ -4331,9 +4436,6 @@ /* No comment provided by engineer. */ "Thanks to the users – contribute via Weblate!" = "Merci aux utilisateurs - contribuez via Weblate !"; -/* No comment provided by engineer. */ -"No user identifiers." = "Aucun identifiant d\'utilisateur."; - /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "L'application peut vous avertir lorsque vous recevez des messages ou des demandes de contact - veuillez ouvrir les paramètres pour les activer."; @@ -4358,6 +4460,9 @@ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Le chiffrement fonctionne et le nouvel accord de chiffrement n'est pas nécessaire. Cela peut provoquer des erreurs de connexion !"; +/* No comment provided by engineer. */ +"The future of messaging" = "La nouvelle génération de messagerie privée"; + /* No comment provided by engineer. */ "The hash of the previous message is different." = "Le hash du message précédent est différent."; @@ -4376,9 +4481,6 @@ /* No comment provided by engineer. */ "The messages will be marked as moderated for all members." = "Les messages seront marqués comme modérés pour tous les membres."; -/* No comment provided by engineer. */ -"The future of messaging" = "La nouvelle génération de messagerie privée"; - /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "L'ancienne base de données n'a pas été supprimée lors de la migration, elle peut être supprimée."; @@ -4397,6 +4499,9 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Le texte collé n'est pas un lien SimpleX."; +/* No comment provided by engineer. */ +"The uploaded database archive will be permanently removed from the servers." = "L'archive de la base de données envoyée sera définitivement supprimée des serveurs."; + /* No comment provided by engineer. */ "Themes" = "Thèmes"; @@ -4455,7 +4560,7 @@ "To ask any questions and to receive updates:" = "Si vous avez des questions et que vous souhaitez des réponses :"; /* No comment provided by engineer. */ -"To connect, your contact can scan QR code or use the link in the app." = "Pour se connecter, votre contact peut scanner le code QR ou utiliser le lien dans l'application."; +"To connect, your contact can scan QR code or use the link in the app." = "Pour se connecter, votre contact peut scanner un code QR ou utiliser un lien dans l'app."; /* No comment provided by engineer. */ "To hide unwanted messages." = "Pour cacher les messages indésirables."; @@ -4463,9 +4568,6 @@ /* No comment provided by engineer. */ "To make a new connection" = "Pour établir une nouvelle connexion"; -/* No comment provided by engineer. */ -"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Pour protéger votre vie privée, au lieu d’IDs utilisés par toutes les autres plateformes, SimpleX a des IDs pour les queues de messages, distinctes pour chacun de vos contacts."; - /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Pour préserver le fuseau horaire, les fichiers image/voix utilisent le système UTC."; @@ -4475,6 +4577,15 @@ /* No comment provided by engineer. */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "Pour protéger votre adresse IP, le routage privé utilise vos serveurs SMP pour délivrer les messages."; +/* No comment provided by engineer. */ +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Pour protéger votre vie privée, au lieu d’IDs utilisés par toutes les autres plateformes, SimpleX a des IDs pour les queues de messages, distinctes pour chacun de vos contacts."; + +/* No comment provided by engineer. */ +"To record speech please grant permission to use Microphone." = "Si vous souhaitez enregistrer une conversation, veuillez autoriser l'utilisation du microphone."; + +/* No comment provided by engineer. */ +"To record video please grant permission to use Camera." = "Si vous souhaitez enregistrer une vidéo, veuillez autoriser l'utilisation de la caméra."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Pour enregistrer un message vocal, veuillez accorder la permission d'utiliser le microphone."; @@ -4691,6 +4802,9 @@ /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Utiliser les serveurs SimpleX Chat ?"; +/* No comment provided by engineer. */ +"Use SOCKS proxy" = "Utiliser un proxy SOCKS"; + /* No comment provided by engineer. */ "Use the app while in the call." = "Utiliser l'application pendant l'appel."; @@ -4700,6 +4814,9 @@ /* No comment provided by engineer. */ "User selection" = "Sélection de l'utilisateur"; +/* No comment provided by engineer. */ +"Username" = "Nom d'utilisateur"; + /* No comment provided by engineer. */ "Using SimpleX Chat servers." = "Vous utilisez les serveurs SimpleX."; @@ -4850,9 +4967,6 @@ /* No comment provided by engineer. */ "when IP hidden" = "lorsque l'IP est masquée"; -/* No comment provided by engineer. */ -"When people request to connect, you can accept or reject it." = "Vous pouvez accepter ou refuser les demandes de contacts."; - /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Lorsque vous partagez un profil incognito avec quelqu'un, ce profil sera utilisé pour les groupes auxquels il vous invite."; @@ -4994,9 +5108,6 @@ /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "Vous pouvez partager cette adresse avec vos contacts pour leur permettre de se connecter avec **%@**."; -/* No comment provided by engineer. */ -"You can share your address as a link or QR code - anybody can connect to you." = "Vous pouvez partager votre adresse sous la forme d'un lien ou d'un code QR - tout le monde peut l'utiliser pour vous contacter."; - /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "Vous pouvez lancer le chat via Paramètres / Base de données ou en redémarrant l'app"; @@ -5009,7 +5120,7 @@ /* No comment provided by engineer. */ "You can use markdown to format messages:" = "Vous pouvez utiliser le format markdown pour mettre en forme les messages :"; -/* No comment provided by engineer. */ +/* alert message */ "You can view invitation link again in connection details." = "Vous pouvez à nouveau consulter le lien d'invitation dans les détails de la connexion."; /* No comment provided by engineer. */ @@ -5030,6 +5141,9 @@ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Vous n'avez pas pu être vérifié·e ; veuillez réessayer."; +/* No comment provided by engineer. */ +"You decide who can connect." = "Vous choisissez qui peut se connecter."; + /* No comment provided by engineer. */ "You have already requested connection via this address!" = "Vous avez déjà demandé une connexion via cette adresse !"; @@ -5120,9 +5234,6 @@ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Vous utilisez un profil incognito pour ce groupe - pour éviter de partager votre profil principal ; inviter des contacts n'est pas possible"; -/* No comment provided by engineer. */ -"Your %@ servers" = "Vos serveurs %@"; - /* No comment provided by engineer. */ "Your calls" = "Vos appels"; @@ -5132,9 +5243,15 @@ /* No comment provided by engineer. */ "Your chat database is not encrypted - set passphrase to encrypt it." = "Votre base de données de chat n'est pas chiffrée - définisez une phrase secrète."; +/* alert title */ +"Your chat preferences" = "Vos préférences de discussion"; + /* No comment provided by engineer. */ "Your chat profiles" = "Vos profils de chat"; +/* No comment provided by engineer. */ +"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Votre connexion a été déplacée vers %@ mais une erreur inattendue s'est produite lors de la redirection vers le profil."; + /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "Votre contact a envoyé un fichier plus grand que la taille maximale supportée actuellement(%@)."; @@ -5144,6 +5261,9 @@ /* No comment provided by engineer. */ "Your contacts will remain connected." = "Vos contacts resteront connectés."; +/* No comment provided by engineer. */ +"Your credentials may be sent unencrypted." = "Vos informations d'identification peuvent être envoyées non chiffrées."; + /* No comment provided by engineer. */ "Your current chat database will be DELETED and REPLACED with the imported one." = "Votre base de données de chat actuelle va être SUPPRIMEE et REMPLACEE par celle importée."; @@ -5168,15 +5288,15 @@ /* No comment provided by engineer. */ "Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Votre profil est stocké sur votre appareil et est seulement partagé avec vos contacts. Les serveurs SimpleX ne peuvent pas voir votre profil."; +/* alert message */ +"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Votre profil a été modifié. Si vous l'enregistrez, le profil mis à jour sera envoyé à tous vos contacts."; + /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Votre profil, vos contacts et les messages reçus sont stockés sur votre appareil."; /* No comment provided by engineer. */ "Your random profile" = "Votre profil aléatoire"; -/* No comment provided by engineer. */ -"Your server" = "Votre serveur"; - /* No comment provided by engineer. */ "Your server address" = "Votre adresse de serveur"; @@ -5189,6 +5309,3 @@ /* No comment provided by engineer. */ "Your SMP servers" = "Vos serveurs SMP"; -/* No comment provided by engineer. */ -"Your XFTP servers" = "Vos serveurs XFTP"; - diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index a68a9e11b1..1704389267 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -1,15 +1,6 @@ /* No comment provided by engineer. */ "\n" = "\n"; -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - /* No comment provided by engineer. */ " (" = " ("; @@ -227,7 +218,7 @@ "%lld messages blocked" = "%lld üzenet letiltva"; /* No comment provided by engineer. */ -"%lld messages blocked by admin" = "%lld üzenetet letiltott az admin"; +"%lld messages blocked by admin" = "%lld üzenetet letiltott az adminisztrátor"; /* No comment provided by engineer. */ "%lld messages marked deleted" = "%lld törlésre megjelölt üzenet"; @@ -323,10 +314,10 @@ "A new random profile will be shared." = "Egy új, véletlenszerű profil kerül megosztásra."; /* No comment provided by engineer. */ -"A separate TCP connection will be used **for each chat profile you have in the app**." = "A rendszer külön TCP-kapcsolatot fog használni **az alkalmazásban található minden csevegési profilhoz**."; +"A separate TCP connection will be used **for each chat profile you have in the app**." = "**Az összes csevegési profiljához az alkalmazásban** külön TCP-kapcsolat (és SOCKS-hitelesítőadat) lesz használva."; /* No comment provided by engineer. */ -"A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail." = "**Minden egyes kapcsolathoz és csoporttaghoz** külön TCP-kapcsolat lesz használva.\n**Megjegyzés:** ha sok kapcsolata van, az akkumulátor-használat és az adatforgalom jelentősen megnövekedhet, és néhány kapcsolódási kísérlet sikertelen lehet."; +"A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail." = "**Az összes ismerőséhez és csoporttaghoz** külön TCP-kapcsolat (és SOCKS-hitelesítőadat) lesz használva.\n**Megjegyzés:** ha sok kapcsolata van, az akkumulátor-használat és az adatforgalom jelentősen megnövekedhet, és néhány kapcsolódási kísérlet sikertelen lehet."; /* No comment provided by engineer. */ "Abort" = "Megszakítás"; @@ -337,12 +328,6 @@ /* No comment provided by engineer. */ "Abort changing address?" = "Címváltoztatás megszakítása??"; -/* No comment provided by engineer. */ -"About SimpleX" = "A SimpleXről"; - -/* No comment provided by engineer. */ -"About SimpleX address" = "A SimpleX-címről"; - /* No comment provided by engineer. */ "About SimpleX Chat" = "A SimpleX Chatről"; @@ -382,12 +367,6 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Cím hozzáadása a profilhoz, hogy az ismerősei megoszthassák másokkal. A profilfrissítés elküldésre kerül az ismerősei számára."; -/* No comment provided by engineer. */ -"Add contact" = "Ismerős hozzáadása"; - -/* No comment provided by engineer. */ -"Add preset servers" = "Előre beállított kiszolgálók hozzáadása"; - /* No comment provided by engineer. */ "Add profile" = "Profil hozzáadása"; @@ -419,16 +398,16 @@ "Address change will be aborted. Old receiving address will be used." = "A cím módosítása megszakad. A régi fogadási cím kerül felhasználásra."; /* member role */ -"admin" = "admin"; +"admin" = "adminisztrátor"; /* feature role */ -"admins" = "adminok"; +"admins" = "adminisztrátorok"; /* No comment provided by engineer. */ -"Admins can block a member for all." = "Az adminok egy tagot mindenki számára letilthatnak."; +"Admins can block a member for all." = "Az adminisztrátorok egy tagot a csoport összes tagja számára letilthatnak."; /* No comment provided by engineer. */ -"Admins can create the links to join groups." = "Az adminok hivatkozásokat hozhatnak létre a csoportokhoz való kapcsolódáshoz."; +"Admins can create the links to join groups." = "Az adminisztrátorok hivatkozásokat hozhatnak létre a csoportokhoz való kapcsolódáshoz."; /* No comment provided by engineer. */ "Advanced network settings" = "Speciális hálózati beállítások"; @@ -443,43 +422,43 @@ "agreeing encryption…" = "titkosítás elfogadása…"; /* No comment provided by engineer. */ -"All app data is deleted." = "Minden alkalmazásadat törölve."; +"All app data is deleted." = "Az összes alkalmazásadat törölve."; /* No comment provided by engineer. */ -"All chats and messages will be deleted - this cannot be undone!" = "Minden csevegés és üzenet törlésre kerül - ez a művelet nem vonható vissza!"; +"All chats and messages will be deleted - this cannot be undone!" = "Az összes csevegés és üzenet törlésre kerül - ez a művelet nem vonható vissza!"; /* No comment provided by engineer. */ -"All data is erased when it is entered." = "A jelkód megadása után minden adat törlésre kerül."; +"All data is erased when it is entered." = "A jelkód megadása után az összes adat törlésre kerül."; /* No comment provided by engineer. */ -"All data is private to your device." = "Minden adat biztonságban van az eszközén."; +"All data is private to your device." = "Az összes adat biztonságban van az eszközén."; /* No comment provided by engineer. */ -"All group members will remain connected." = "Minden csoporttag kapcsolódva marad."; +"All group members will remain connected." = "Az összes csoporttag kapcsolatban marad."; /* feature role */ -"all members" = "minden tag"; +"all members" = "összes tag"; /* No comment provided by engineer. */ -"All messages will be deleted - this cannot be undone!" = "Minden üzenet törlésre kerül – ez a művelet nem vonható vissza!"; +"All messages will be deleted - this cannot be undone!" = "Az összes üzenet törlésre kerül – ez a művelet nem vonható vissza!"; /* No comment provided by engineer. */ -"All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "Minden üzenet törlésre kerül - ez a művelet nem vonható vissza! Az üzenetek CSAK az Ön számára törlődnek."; +"All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "Az összes üzenet törlésre kerül - ez a művelet nem vonható vissza! Az üzenetek CSAK az Ön számára törlődnek."; /* No comment provided by engineer. */ -"All new messages from %@ will be hidden!" = "Minden új üzenet elrejtésre kerül tőle: %@!"; +"All new messages from %@ will be hidden!" = "Az összes új üzenet elrejtésre kerül tőle: %@!"; /* profile dropdown */ -"All profiles" = "Minden profil"; +"All profiles" = "Összes profil"; /* No comment provided by engineer. */ -"All your contacts will remain connected." = "Minden ismerősével kapcsolatban marad."; +"All your contacts will remain connected." = "Az összes ismerősével kapcsolatban marad."; /* No comment provided by engineer. */ "All your contacts will remain connected. Profile update will be sent to your contacts." = "Az ismerőseivel kapcsolatban marad. A profil-változtatások frissítésre kerülnek az ismerősöknél."; /* No comment provided by engineer. */ -"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Minden ismerőse, a beszélgetései és a fájljai biztonságosan titkosításra kerülnek, melyek részletekben feltöltődnek a beállított XFTP-közvetítő-kiszolgálóra."; +"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Az összes ismerőse, -beszélgetése és -fájlja biztonságosan titkosításra kerülnek, melyek részletekben feltöltődnek a beállított XFTP-közvetítő-kiszolgálóra."; /* No comment provided by engineer. */ "Allow" = "Engedélyezés"; @@ -574,6 +553,9 @@ /* No comment provided by engineer. */ "Answer call" = "Hívás fogadása"; +/* No comment provided by engineer. */ +"Anybody can host servers." = "Bárki üzemeltethet kiszolgálókat."; + /* No comment provided by engineer. */ "App build: %@" = "Az alkalmazás build száma: %@"; @@ -584,7 +566,7 @@ "App encrypts new local files (except videos)." = "Az alkalmazás titkosítja a helyi fájlokat (a videók kivételével)."; /* No comment provided by engineer. */ -"App icon" = "Alkalmazás ikon"; +"App icon" = "Alkalmazásikon"; /* No comment provided by engineer. */ "App passcode" = "Alkalmazás jelkód"; @@ -722,7 +704,7 @@ "Block" = "Letiltás"; /* No comment provided by engineer. */ -"Block for all" = "Letiltás mindenki számára"; +"Block for all" = "Letiltás az összes tag számára"; /* No comment provided by engineer. */ "Block group members" = "Csoporttagok letiltása"; @@ -731,7 +713,7 @@ "Block member" = "Tag letiltása"; /* No comment provided by engineer. */ -"Block member for all?" = "Mindenki számára letiltja ezt a tagot?"; +"Block member for all?" = "Az összes tag számára letiltja ezt a tagot?"; /* No comment provided by engineer. */ "Block member?" = "Tag letiltása?"; @@ -743,10 +725,10 @@ "blocked %@" = "letiltotta %@-t"; /* marked deleted chat item preview text */ -"blocked by admin" = "letiltva az admin által"; +"blocked by admin" = "letiltva az adminisztrátor által"; /* No comment provided by engineer. */ -"Blocked by admin" = "Az admin letiltotta"; +"Blocked by admin" = "Az adminisztrátor letiltotta"; /* No comment provided by engineer. */ "Blur for better privacy." = "Elhomályosítás a jobb adatvédelemért."; @@ -809,15 +791,16 @@ "Can't call member" = "Nem lehet felhívni a tagot"; /* No comment provided by engineer. */ -"Can't invite contact!" = "Ismerős meghívása nem lehetséges!"; +"Can't invite contact!" = "Nem lehet meghívni az ismerőst!"; /* No comment provided by engineer. */ -"Can't invite contacts!" = "Ismerősök meghívása nem lehetséges!"; +"Can't invite contacts!" = "Nem lehet meghívni az ismerősöket!"; /* No comment provided by engineer. */ "Can't message member" = "Nem lehet üzenetet küldeni a tagnak"; -/* alert button */ +/* alert action + alert button */ "Cancel" = "Mégse"; /* No comment provided by engineer. */ @@ -938,7 +921,7 @@ /* No comment provided by engineer. */ "Chats" = "Csevegések"; -/* No comment provided by engineer. */ +/* alert title */ "Check server address and try again." = "Kiszolgáló címének ellenőrzése és újrapróbálkozás."; /* No comment provided by engineer. */ @@ -1001,9 +984,6 @@ /* No comment provided by engineer. */ "Configure ICE servers" = "ICE-kiszolgálók beállítása"; -/* No comment provided by engineer. */ -"Configured %@ servers" = "Beállított %@ kiszolgálók"; - /* No comment provided by engineer. */ "Confirm" = "Megerősítés"; @@ -1014,7 +994,7 @@ "Confirm database upgrades" = "Adatbázis fejlesztésének megerősítése"; /* No comment provided by engineer. */ -"Confirm files from unknown servers." = "Ismeretlen kiszolgálókról származó fájlok jóváhagyása."; +"Confirm files from unknown servers." = "Ismeretlen kiszolgálókról származó fájlok megerősítése."; /* No comment provided by engineer. */ "Confirm network settings" = "Hálózati beállítások megerősítése"; @@ -1071,13 +1051,13 @@ "Connect via one-time link" = "Kapcsolódás egyszer használható hivatkozáson keresztül"; /* No comment provided by engineer. */ -"Connect with %@" = "Kapcsolódás ezzel: %@"; +"Connect with %@" = "Kapcsolódás a következővel: %@"; /* No comment provided by engineer. */ -"connected" = "kapcsolódva"; +"connected" = "kapcsolódott"; /* No comment provided by engineer. */ -"Connected" = "Kapcsolódva"; +"Connected" = "Kapcsolódott"; /* No comment provided by engineer. */ "Connected desktop" = "Társított számítógép"; @@ -1232,9 +1212,6 @@ /* No comment provided by engineer. */ "Create a group using a random profile." = "Csoport létrehozása véletlenszerűen létrehozott profillal."; -/* No comment provided by engineer. */ -"Create an address to let people connect with you." = "Cím létrehozása, hogy az emberek kapcsolatba léphessenek Önnel."; - /* server test step */ "Create file" = "Fájl létrehozása"; @@ -1397,7 +1374,8 @@ /* No comment provided by engineer. */ "default (yes)" = "alapértelmezett (igen)"; -/* chat item action +/* alert action + chat item action swipe action */ "Delete" = "Törlés"; @@ -1417,7 +1395,7 @@ "Delete after" = "Törlés ennyi idő után"; /* No comment provided by engineer. */ -"Delete all files" = "Minden fájl törlése"; +"Delete all files" = "Az összes fájl törlése"; /* No comment provided by engineer. */ "Delete and notify contact" = "Törlés, és az ismerős értesítése"; @@ -1456,10 +1434,10 @@ "Delete files and media?" = "Fájlok és a médiatartalmak törlése?"; /* No comment provided by engineer. */ -"Delete files for all chat profiles" = "Fájlok törlése minden csevegési profilból"; +"Delete files for all chat profiles" = "Fájlok törlése az összes csevegési profilból"; /* chat feature */ -"Delete for everyone" = "Törlés mindenkinél"; +"Delete for everyone" = "Törlés az összes tagnál"; /* No comment provided by engineer. */ "Delete for me" = "Csak nálam"; @@ -1612,7 +1590,7 @@ "Disable (keep overrides)" = "Letiltás (felülírások megtartásával)"; /* No comment provided by engineer. */ -"Disable for all" = "Letiltás mindenki számára"; +"Disable for all" = "Letiltás az összes tag számára"; /* authentication reason */ "Disable SimpleX Lock" = "SimpleX-zár kikapcsolása"; @@ -1745,7 +1723,7 @@ "Enable camera access" = "Kamera hozzáférés engedélyezése"; /* No comment provided by engineer. */ -"Enable for all" = "Engedélyezés mindenki számára"; +"Enable for all" = "Engedélyezés az összes tag számára"; /* No comment provided by engineer. */ "Enable in direct chats (BETA)!" = "Engedélyezés a közvetlen csevegésekben (BÉTA)!"; @@ -1781,7 +1759,7 @@ "Enabled" = "Engedélyezve"; /* No comment provided by engineer. */ -"Enabled for" = "Engedélyezve"; +"Enabled for" = "Számukra engedélyezve:"; /* enabled status */ "enabled for contact" = "engedélyezve az ismerős számára"; @@ -1996,9 +1974,6 @@ /* No comment provided by engineer. */ "Error joining group" = "Hiba a csoporthoz való csatlakozáskor"; -/* No comment provided by engineer. */ -"Error loading %@ servers" = "Hiba a(z) %@ -kiszolgálók betöltésekor"; - /* No comment provided by engineer. */ "Error migrating settings" = "Hiba a beallítások átköltöztetésekor"; @@ -2020,9 +1995,6 @@ /* No comment provided by engineer. */ "Error resetting statistics" = "Hiba a statisztikák visszaállításakor"; -/* No comment provided by engineer. */ -"Error saving %@ servers" = "Hiba történt a(z) %@ -kiszolgálók mentésekor"; - /* No comment provided by engineer. */ "Error saving group profile" = "Hiba a csoportprofil mentésekor"; @@ -2168,7 +2140,7 @@ "File status: %@" = "Fájlállapot: %@"; /* No comment provided by engineer. */ -"File will be deleted from servers." = "A fájl törölve lesz a kiszolgálóról."; +"File will be deleted from servers." = "A fájl törölve lesz a kiszolgálókról."; /* No comment provided by engineer. */ "File will be received when your contact completes uploading it." = "A fájl akkor érkezik meg, amikor a küldője befejezte annak feltöltését."; @@ -2387,13 +2359,13 @@ "Group welcome message" = "A csoport üdvözlőüzenete"; /* No comment provided by engineer. */ -"Group will be deleted for all members - this cannot be undone!" = "A csoport törlésre kerül minden tag számára - ez a művelet nem vonható vissza!"; +"Group will be deleted for all members - this cannot be undone!" = "A csoport törlésre kerül az összes tag számára - ez a művelet nem vonható vissza!"; /* No comment provided by engineer. */ "Group will be deleted for you - this cannot be undone!" = "A csoport törlésre kerül az Ön számára - ez a művelet nem vonható vissza!"; /* No comment provided by engineer. */ -"Help" = "Segítség"; +"Help" = "Súgó"; /* No comment provided by engineer. */ "Hidden" = "Se név, se üzenet"; @@ -2425,9 +2397,6 @@ /* time unit */ "hours" = "óra"; -/* No comment provided by engineer. */ -"How it works" = "Hogyan működik"; - /* No comment provided by engineer. */ "How SimpleX works" = "Hogyan működik a SimpleX"; @@ -2525,7 +2494,7 @@ "Incognito mode" = "Inkognitómód"; /* No comment provided by engineer. */ -"Incognito mode protects your privacy by using a new random profile for each contact." = "Az inkognitómód védi személyes adatait azáltal, hogy minden ismerőshöz új véletlenszerű profilt használ."; +"Incognito mode protects your privacy by using a new random profile for each contact." = "Az inkognitómód védi személyes adatait azáltal, hogy az összes ismerőséhez új, véletlenszerű profilt használ."; /* chat list item description */ "incognito via contact address link" = "inkognitó a kapcsolattartási címhivatkozáson keresztül"; @@ -2570,10 +2539,10 @@ "Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "A [SimpleX Chat terminálhoz] telepítése (https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"Instant push notifications will be hidden!\n" = "Az azonnali push-értesítések elrejtésre kerülnek!\n"; +"Instant" = "Azonnal"; /* No comment provided by engineer. */ -"Instant" = "Azonnal"; +"Instant push notifications will be hidden!\n" = "Az azonnali push-értesítések elrejtésre kerülnek!\n"; /* No comment provided by engineer. */ "Interface" = "Felület"; @@ -2611,7 +2580,7 @@ /* No comment provided by engineer. */ "Invalid response" = "Érvénytelen válasz"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid server address!" = "Érvénytelen kiszolgálócím!"; /* item status text */ @@ -2711,12 +2680,12 @@ "Join with current profile" = "Csatlakozás a jelenlegi profillal"; /* No comment provided by engineer. */ -"Join your group?\nThis is your link for group %@!" = "Csatlakozik a csoportjához?\nEz az Ön hivatkozása a(z) %@ csoporthoz!"; +"Join your group?\nThis is your link for group %@!" = "Csatlakozik a csoportjához?\nEz az Ön hivatkozása a(z) %@ nevű csoporthoz!"; /* No comment provided by engineer. */ "Joining group" = "Csatlakozás a csoporthoz"; -/* No comment provided by engineer. */ +/* alert action */ "Keep" = "Megtart"; /* No comment provided by engineer. */ @@ -2725,7 +2694,7 @@ /* No comment provided by engineer. */ "Keep the app open to use it from desktop" = "A számítógépről való használathoz tartsd nyitva az alkalmazást"; -/* No comment provided by engineer. */ +/* alert title */ "Keep unused invitation?" = "Fel nem használt meghívó megtartása?"; /* No comment provided by engineer. */ @@ -2782,9 +2751,6 @@ /* No comment provided by engineer. */ "Live messages" = "Élő üzenetek"; -/* No comment provided by engineer. */ -"No push server" = "Helyi"; - /* No comment provided by engineer. */ "Local name" = "Helyi név"; @@ -2797,29 +2763,20 @@ /* No comment provided by engineer. */ "Lock mode" = "Zárolási mód"; -/* No comment provided by engineer. */ -"Make a private connection" = "Privát kapcsolat létrehozása"; - /* No comment provided by engineer. */ "Make one message disappear" = "Egy üzenet eltüntetése"; /* No comment provided by engineer. */ "Make profile private!" = "Tegye priváttá a profilját!"; -/* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Győződjön meg arról, hogy a(z) %@ kiszolgálócímek megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva (%@)."; - /* No comment provided by engineer. */ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Győződjön meg arról, hogy a WebRTC ICE-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva."; /* No comment provided by engineer. */ -"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Sokan kérdezték: *ha a SimpleX Chatnek nincsenek felhasználó-azonosítói, akkor hogyan tud üzeneteket kézbesíteni?*"; +"Mark deleted for everyone" = "Jelölje meg az összes tag számára töröltként"; /* No comment provided by engineer. */ -"Mark deleted for everyone" = "Jelölje meg mindenki számára töröltként"; - -/* No comment provided by engineer. */ -"Mark read" = "Olvasottnak jelölés"; +"Mark read" = "Megjelölés olvasottként"; /* No comment provided by engineer. */ "Mark verified" = "Hitelesítés"; @@ -2855,7 +2812,7 @@ "Member inactive" = "Inaktív tag"; /* No comment provided by engineer. */ -"Member role will be changed to \"%@\". All group members will be notified." = "A tag szerepköre meg fog változni erre: „%@”. A csoport minden tagja értesítést kap róla."; +"Member role will be changed to \"%@\". All group members will be notified." = "A tag szerepköre meg fog változni erre: „%@”. A csoportban az összes tag értesítve lesz."; /* No comment provided by engineer. */ "Member role will be changed to \"%@\". The member will receive a new invitation." = "A tag szerepköre meg fog változni erre: „%@”. A tag új meghívást fog kapni."; @@ -3101,7 +3058,7 @@ "New SOCKS credentials will be used every time you start the app." = "Minden alkalommal, amikor elindítja az alkalmazást, új SOCKS-hitelesítő-adatokat fog használni."; /* No comment provided by engineer. */ -"New SOCKS credentials will be used for each server." = "Minden egyes kiszolgálóhoz új SOCKS-hitelesítő-adatok legyenek használva."; +"New SOCKS credentials will be used for each server." = "Az összes kiszolgálóhoz új, SOCKS-hitelesítő-adatok legyenek használva."; /* pref value */ "no" = "nem"; @@ -3125,7 +3082,7 @@ "No device token!" = "Nincs kiszüléktoken!"; /* item status description */ -"No direct connection yet, message is forwarded by admin." = "Még nincs közvetlen kapcsolat, az üzenetet az admin továbbítja."; +"No direct connection yet, message is forwarded by admin." = "Még nincs közvetlen kapcsolat, az üzenetet az adminisztrátor továbbítja."; /* No comment provided by engineer. */ "no e2e encryption" = "nincs e2e titkosítás"; @@ -3154,12 +3111,18 @@ /* No comment provided by engineer. */ "No permission to record voice message" = "Nincs engedély a hangüzenet rögzítésére"; +/* No comment provided by engineer. */ +"No push server" = "Helyi"; + /* No comment provided by engineer. */ "No received or sent files" = "Nincsenek fogadott vagy küldött fájlok"; /* copied message info in history */ "no text" = "nincs szöveg"; +/* No comment provided by engineer. */ +"No user identifiers." = "Nincsenek felhasználó-azonosítók."; + /* No comment provided by engineer. */ "Not compatible!" = "Nem kompatibilis!"; @@ -3176,7 +3139,7 @@ "Notifications are disabled!" = "Az értesítések le vannak tiltva!"; /* No comment provided by engineer. */ -"Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "Most már az adminok is:\n- törölhetik a tagok üzeneteit.\n- letilthatnak tagokat („megfigyelő” szerepkör)"; +"Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "Most már az adminisztrátorok is:\n- törölhetik a tagok üzeneteit.\n- letilthatnak tagokat („megfigyelő” szerepkör)"; /* member role */ "observer" = "megfigyelő"; @@ -3282,18 +3245,9 @@ /* authentication reason */ "Open migration to another device" = "Átköltöztetés megkezdése egy másik eszközre"; -/* No comment provided by engineer. */ -"Open server settings" = "Kiszolgáló-beállítások megnyitása"; - /* No comment provided by engineer. */ "Open Settings" = "Beállítások megnyitása"; -/* authentication reason */ -"Open user profiles" = "Felhasználó-profilok megnyitása"; - -/* No comment provided by engineer. */ -"Anybody can host servers." = "Bárki üzemeltethet kiszolgálókat."; - /* No comment provided by engineer. */ "Opening app…" = "Az alkalmazás megnyitása…"; @@ -3315,9 +3269,6 @@ /* No comment provided by engineer. */ "Other" = "További"; -/* No comment provided by engineer. */ -"Other %@ servers" = "További %@ kiszolgálók"; - /* No comment provided by engineer. */ "other errors" = "egyéb hibák"; @@ -3372,9 +3323,6 @@ /* No comment provided by engineer. */ "Pending" = "Függőben"; -/* No comment provided by engineer. */ -"You decide who can connect." = "Ön dönti el, hogy kivel beszélget."; - /* No comment provided by engineer. */ "Periodic" = "Rendszeresen"; @@ -3406,7 +3354,7 @@ "Please check that you used the correct link or ask your contact to send you another one." = "Ellenőrizze, hogy a megfelelő hivatkozást használta-e, vagy kérje meg az ismerősét, hogy küldjön egy másikat."; /* No comment provided by engineer. */ -"Please check your network connection with %@ and try again." = "Ellenőrizze a hálózati kapcsolatát a(z) %@ segítségével, és próbálja újra."; +"Please check your network connection with %@ and try again." = "Ellenőrizze a hálózati kapcsolatát a következővel: %@, és próbálja újra."; /* No comment provided by engineer. */ "Please check yours and your contact preferences." = "Ellenőrizze a saját- és az ismerőse beállításait."; @@ -3418,7 +3366,7 @@ "Please contact developers.\nError: %@" = "Lépjen kapcsolatba a fejlesztőkkel.\nHiba: %@"; /* No comment provided by engineer. */ -"Please contact group admin." = "Lépjen kapcsolatba a csoport adminnal."; +"Please contact group admin." = "Lépjen kapcsolatba a csoport adminisztrátorával."; /* No comment provided by engineer. */ "Please enter correct current passphrase." = "Adja meg a helyes, jelenlegi jelmondatát."; @@ -3453,9 +3401,6 @@ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Az utolsó üzenet tervezetének megőrzése a mellékletekkel együtt."; -/* No comment provided by engineer. */ -"Preset server" = "Előre beállított kiszolgáló"; - /* No comment provided by engineer. */ "Preset server address" = "Előre beállított kiszolgáló címe"; @@ -3504,7 +3449,7 @@ /* No comment provided by engineer. */ "Profile theme" = "Profiltéma"; -/* No comment provided by engineer. */ +/* alert message */ "Profile update will be sent to your contacts." = "A profilfrissítés elküldésre került az ismerősök számára."; /* No comment provided by engineer. */ @@ -3583,16 +3528,16 @@ "React…" = "Reagálj…"; /* swipe action */ -"Read" = "Olvasd el"; +"Read" = "Olvasott"; /* No comment provided by engineer. */ "Read more" = "Tudjon meg többet"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "További információ a [Használati útmutatóban](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "További információ a [Használati útmutatóban](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "További információ a [Használati útmutatóban](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "További információ a [Használati útmutatóban](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "További információ a [Használati útmutatóban](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; @@ -3600,9 +3545,6 @@ /* No comment provided by engineer. */ "Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "További információ a [GitHub tárolóban](https://github.com/simplex-chat/simplex-chat#readme)."; -/* No comment provided by engineer. */ -"Read more in our GitHub repository." = "További információ a GitHub tárolónkban."; - /* No comment provided by engineer. */ "Receipts are disabled" = "A kézbesítési jelentések le vannak tiltva"; @@ -3640,7 +3582,7 @@ "Receiving address will be changed to a different server. Address change will complete after sender comes online." = "A fogadó cím egy másik kiszolgálóra változik. A címváltoztatás a feladó online állapotba kerülése után fejeződik be."; /* No comment provided by engineer. */ -"Receiving file will be stopped." = "A fájl fogadása leállt."; +"Receiving file will be stopped." = "A fájl fogadása le fog állni."; /* No comment provided by engineer. */ "Receiving via" = "Fogadás a"; @@ -3655,16 +3597,16 @@ "Recipients see updates as you type them." = "A címzettek a beírás közben látják a szövegváltozásokat."; /* No comment provided by engineer. */ -"Reconnect" = "Újrakapcsolás"; +"Reconnect" = "Újrakapcsolódás"; /* No comment provided by engineer. */ "Reconnect all connected servers to force message delivery. It uses additional traffic." = "Az összes kiszolgálóhoz való újrakapcsolódás az üzenetkézbesítési jelentések kikényszerítéséhez. Ez további adatforgalmat használ."; /* No comment provided by engineer. */ -"Reconnect all servers" = "Újrakapcsolódás minden kiszolgálóhoz"; +"Reconnect all servers" = "Újrakapcsolódás az összes kiszolgálóhoz"; /* No comment provided by engineer. */ -"Reconnect all servers?" = "Újrakapcsolódás minden kiszolgálóhoz?"; +"Reconnect all servers?" = "Újrakapcsolódás az összes kiszolgálóhoz?"; /* No comment provided by engineer. */ "Reconnect server to force message delivery. It uses additional traffic." = "A kiszolgálóhoz való újrakapcsolódás az üzenetkézbesítési jelentések kikényszerítéséhez. Ez további adatforgalmat használ."; @@ -3773,10 +3715,10 @@ "Reset all hints" = "Tippek visszaállítása"; /* No comment provided by engineer. */ -"Reset all statistics" = "Minden statisztika visszaállítása"; +"Reset all statistics" = "Az összes statisztika visszaállítása"; /* No comment provided by engineer. */ -"Reset all statistics?" = "Minden statisztika visszaállítása?"; +"Reset all statistics?" = "Az összes statisztika visszaállítása?"; /* No comment provided by engineer. */ "Reset colors" = "Színek visszaállítása"; @@ -3875,7 +3817,7 @@ /* No comment provided by engineer. */ "Save servers" = "Kiszolgálók mentése"; -/* No comment provided by engineer. */ +/* alert title */ "Save servers?" = "Kiszolgálók mentése?"; /* No comment provided by engineer. */ @@ -3936,7 +3878,7 @@ "Search bar accepts invitation links." = "A keresősáv elfogadja a meghívó-hivatkozásokat."; /* No comment provided by engineer. */ -"Search or paste SimpleX link" = "Keresés, vagy SimpleX-hivatkozás beillesztése"; +"Search or paste SimpleX link" = "Keresés vagy SimpleX-hivatkozás beillesztése"; /* network option */ "sec" = "mp"; @@ -4028,9 +3970,6 @@ /* No comment provided by engineer. */ "Send notifications" = "Értesítések küldése"; -/* No comment provided by engineer. */ -"Send notifications:" = "Értesítések küldése:"; - /* No comment provided by engineer. */ "Send questions and ideas" = "Ötletek és kérdések beküldése"; @@ -4050,13 +3989,13 @@ "Sender may have deleted the connection request." = "A küldő törölhette a kapcsolatkérést."; /* No comment provided by engineer. */ -"Sending delivery receipts will be enabled for all contacts in all visible chat profiles." = "A kézbesítési jelentések küldése engedélyezésre kerül az összes látható csevegési profilban lévő minden ismerős számára."; +"Sending delivery receipts will be enabled for all contacts in all visible chat profiles." = "A kézbesítési jelentések küldése engedélyezésre kerül az összes látható csevegési profilban lévő összes ismerőse számára."; /* No comment provided by engineer. */ -"Sending delivery receipts will be enabled for all contacts." = "A kézbesítési jelentés küldése minden ismerőse számára engedélyezésre kerül."; +"Sending delivery receipts will be enabled for all contacts." = "A kézbesítési jelentés küldése az összes ismerőse számára engedélyezésre kerül."; /* No comment provided by engineer. */ -"Sending file will be stopped." = "A fájl küldése leállt."; +"Sending file will be stopped." = "A fájl küldése le fog állni."; /* No comment provided by engineer. */ "Sending receipts is disabled for %lld contacts" = "A kézbesítési jelentések le vannak tiltva %lld ismerősnél"; @@ -4193,7 +4132,8 @@ /* No comment provided by engineer. */ "Shape profile images" = "Profilkép alakzat"; -/* chat item action */ +/* alert action + chat item action */ "Share" = "Megosztás"; /* No comment provided by engineer. */ @@ -4202,7 +4142,7 @@ /* No comment provided by engineer. */ "Share address" = "Cím megosztása"; -/* No comment provided by engineer. */ +/* alert title */ "Share address with contacts?" = "Megosztja a címet az ismerőseivel?"; /* No comment provided by engineer. */ @@ -4269,7 +4209,7 @@ "SimpleX encrypted message or connection event" = "SimpleX titkosított üzenet vagy kapcsolati esemény"; /* simplex link type */ -"SimpleX group link" = "SimpleX csoporthivatkozás"; +"SimpleX group link" = "SimpleX-csoporthivatkozás"; /* chat feature */ "SimpleX links" = "SimpleX-hivatkozások"; @@ -4371,7 +4311,7 @@ "Stop chat to enable database actions" = "Csevegés megállítása az adatbázis-műveletek engedélyezéséhez"; /* No comment provided by engineer. */ -"Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "A csevegés megállítása a csevegési adatbázis exportálásához, importálásához vagy törléséhez. A csevegés megállítása alatt nem tud üzeneteket fogadni és küldeni."; +"Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "A csevegés megállítása a csevegési adatbázis exportálásához, importálásához vagy törléséhez. A csevegés megállításakor nem tud üzeneteket fogadni és küldeni."; /* No comment provided by engineer. */ "Stop chat?" = "Csevegési szolgáltatás megállítása?"; @@ -4385,10 +4325,10 @@ /* No comment provided by engineer. */ "Stop sending file?" = "Fájlküldés megállítása?"; -/* No comment provided by engineer. */ +/* alert action */ "Stop sharing" = "Megosztás megállítása"; -/* No comment provided by engineer. */ +/* alert title */ "Stop sharing address?" = "Címmegosztás megállítása?"; /* authentication reason */ @@ -4484,7 +4424,7 @@ /* No comment provided by engineer. */ "Test servers" = "Kiszolgálók tesztelése"; -/* No comment provided by engineer. */ +/* alert title */ "Tests failed!" = "Sikertelen tesztek!"; /* No comment provided by engineer. */ @@ -4496,9 +4436,6 @@ /* No comment provided by engineer. */ "Thanks to the users – contribute via Weblate!" = "Köszönet a felhasználóknak - hozzájárulás a Weblate-en!"; -/* No comment provided by engineer. */ -"No user identifiers." = "Nincsenek felhasználó-azonosítók."; - /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Az alkalmazás értesíteni fogja, amikor üzeneteket vagy kapcsolatkéréseket kap – beállítások megnyitása az engedélyezéshez."; @@ -4509,7 +4446,7 @@ "The attempt to change database passphrase was not completed." = "Az adatbázis jelmondatának megváltoztatására tett kísérlet nem fejeződött be."; /* No comment provided by engineer. */ -"The code you scanned is not a SimpleX link QR code." = "A beolvasott QR-kód nem egy SimpleX QR-kód hivatkozás."; +"The code you scanned is not a SimpleX link QR code." = "A beolvasott QR-kód nem egy SimpleX-QR-kód-hivatkozás."; /* No comment provided by engineer. */ "The connection you accepted will be cancelled!" = "Az Ön által elfogadott kérelem vissza lesz vonva!"; @@ -4523,6 +4460,9 @@ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "A titkosítás működik, és új titkosítási egyezményre nincs szükség. Ez kapcsolati hibákat eredményezhet!"; +/* No comment provided by engineer. */ +"The future of messaging" = "A privát üzenetküldés következő generációja"; + /* No comment provided by engineer. */ "The hash of the previous message is different." = "Az előző üzenet hasító értéke különbözik."; @@ -4530,19 +4470,16 @@ "The ID of the next message is incorrect (less or equal to the previous).\nIt can happen because of some bug or when the connection is compromised." = "A következő üzenet azonosítója hibás (kisebb vagy egyenlő az előzővel).\nEz valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő."; /* No comment provided by engineer. */ -"The message will be deleted for all members." = "Az üzenet minden tag számára törlésre kerül."; +"The message will be deleted for all members." = "Az üzenet az összes tag számára törlésre kerül."; /* No comment provided by engineer. */ -"The message will be marked as moderated for all members." = "Az üzenet minden tag számára moderáltként lesz megjelölve."; +"The message will be marked as moderated for all members." = "Az üzenet az összes tag számára moderáltként lesz megjelölve."; /* No comment provided by engineer. */ -"The messages will be deleted for all members." = "Az üzenetek minden tag számára törlésre kerülnek."; +"The messages will be deleted for all members." = "Az üzenetek az összes tag számára törlésre kerülnek."; /* No comment provided by engineer. */ -"The messages will be marked as moderated for all members." = "Az üzenetek moderáltként lesznek megjelölve minden tag számára."; - -/* No comment provided by engineer. */ -"The future of messaging" = "A privát üzenetküldés következő generációja"; +"The messages will be marked as moderated for all members." = "Az üzenetek az összes tag számára moderáltként lesznek megjelölve."; /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "A régi adatbázis nem került eltávolításra az átköltöztetéskor, így törölhető."; @@ -4569,7 +4506,7 @@ "Themes" = "Témák"; /* No comment provided by engineer. */ -"These settings are for your current profile **%@**." = "Ezek a beállítások a jelenlegi **%@** profiljára vonatkoznak."; +"These settings are for your current profile **%@**." = "Ezek a beállítások csak a jelenlegi (**%@**) profiljára vonatkoznak."; /* No comment provided by engineer. */ "They can be overridden in contact and group settings." = "Ezek felülbírálhatók az ismerős- és csoportbeállításokban."; @@ -4614,7 +4551,7 @@ "This link was used with another mobile device, please create a new link on the desktop." = "Ezt a hivatkozást egy másik hordozható eszközön már használták, hozzon létre egy új hivatkozást a számítógépén."; /* No comment provided by engineer. */ -"This setting applies to messages in your current chat profile **%@**." = "Ez a beállítás a jelenlegi **%@** profiljában lévő üzenetekre érvényes."; +"This setting applies to messages in your current chat profile **%@**." = "Ez a beállítás csak a jelenlegi (**%@**) profiljában lévő üzenetekre vonatkozik."; /* No comment provided by engineer. */ "Title" = "Cím"; @@ -4632,10 +4569,7 @@ "To make a new connection" = "Új kapcsolat létrehozásához"; /* No comment provided by engineer. */ -"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Az adatvédelem érdekében, a más csevegési platformokon megszokott felhasználó-azonosítók helyett, a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, minden egyes ismerőshöz egy-egy különbözőt."; - -/* No comment provided by engineer. */ -"To protect timezone, image/voice files use UTC." = "Az időzóna védelme érdekében a kép-/hangfájlok UTC-t használnak."; +"To protect timezone, image/voice files use UTC." = "Az időzóna védelmének érdekében a kép-/hangfájlok UTC-t használnak."; /* No comment provided by engineer. */ "To protect your information, turn on SimpleX Lock.\nYou will be prompted to complete authentication before this feature is enabled." = "A biztonsága érdekében kapcsolja be a SimpleX-zár funkciót.\nA funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beállítására az eszközén."; @@ -4643,6 +4577,9 @@ /* No comment provided by engineer. */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "Az IP-cím védelmének érdekében a privát útválasztás az SMP-kiszolgálókat használja az üzenetek kézbesítéséhez."; +/* No comment provided by engineer. */ +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Az adatvédelem érdekében (a más csevegési platformokon megszokott felhasználó-azonosítók helyett) a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, az összes ismerőséhez különbözőt."; + /* No comment provided by engineer. */ "To record speech please grant permission to use Microphone." = "A beszéd rögzítéséhez adjon engedélyt a Mikrofon használatára."; @@ -4701,13 +4638,13 @@ "Unblock" = "Feloldás"; /* No comment provided by engineer. */ -"Unblock for all" = "Letiltás feloldása mindenki számára"; +"Unblock for all" = "Letiltás feloldása az összes tag számára"; /* No comment provided by engineer. */ "Unblock member" = "Tag feloldása"; /* No comment provided by engineer. */ -"Unblock member for all?" = "Mindenki számára feloldja a tag letiltását?"; +"Unblock member for all?" = "Az összes tag számára feloldja a tag letiltását?"; /* No comment provided by engineer. */ "Unblock member?" = "Tag feloldása?"; @@ -5019,7 +4956,7 @@ "Welcome message is too long" = "Az üdvözlőüzenet túl hosszú"; /* No comment provided by engineer. */ -"What's new" = "Milyen újdonságok vannak"; +"What's new" = "Újdonságok"; /* No comment provided by engineer. */ "When available" = "Amikor elérhető"; @@ -5030,9 +4967,6 @@ /* No comment provided by engineer. */ "when IP hidden" = "ha az IP-cím rejtett"; -/* No comment provided by engineer. */ -"When people request to connect, you can accept or reject it." = "Amikor az emberek kapcsolatot kérnek, Ön elfogadhatja vagy elutasíthatja azokat."; - /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Inkognitóprofil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott."; @@ -5088,7 +5022,7 @@ "You accepted connection" = "Kapcsolat létrehozása"; /* No comment provided by engineer. */ -"You allow" = "Engedélyezte"; +"You allow" = "Ön engedélyezi"; /* No comment provided by engineer. */ "You already have a chat profile with the same display name. Please choose another name." = "Már van egy csevegési profil ugyanezzel a megjelenített névvel. Válasszon egy másik nevet."; @@ -5103,7 +5037,7 @@ "You are already connecting via this one-time link!" = "A kapcsolódás már folyamatban van ezen az egyszer használható hivatkozáson keresztül!"; /* No comment provided by engineer. */ -"You are already in group %@." = "Már a(z) %@ csoport tagja."; +"You are already in group %@." = "Ön már a(z) %@ nevű csoport tagja."; /* No comment provided by engineer. */ "You are already joining the group %@." = "A csatlakozás már folyamatban van a(z) %@ nevű csoporthoz."; @@ -5118,7 +5052,7 @@ "You are already joining the group!\nRepeat join request?" = "Csatlakozás folyamatban!\nCsatlakozáskérés megismétlése?"; /* No comment provided by engineer. */ -"You are connected to the server used to receive messages from this contact." = "Már kapcsolódott ahhoz a kiszolgálóhoz, amely az adott ismerősétől érkező üzenetek fogadására szolgál."; +"You are connected to the server used to receive messages from this contact." = "Ön már kapcsolódott ahhoz a kiszolgálóhoz, amely az adott ismerősétől érkező üzenetek fogadására szolgál."; /* No comment provided by engineer. */ "you are invited to group" = "meghívást kapott a csoportba"; @@ -5130,7 +5064,7 @@ "You are not connected to these servers. Private routing is used to deliver messages to them." = "Ön nem kapcsolódik ezekhez a kiszolgálókhoz. A privát útválasztás az üzenetek kézbesítésére szolgál."; /* No comment provided by engineer. */ -"you are observer" = "megfigyelő szerep"; +"you are observer" = "Ön megfigyelő"; /* snd group event chat item */ "you blocked %@" = "Ön letiltotta őt: %@"; @@ -5174,9 +5108,6 @@ /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "Megoszthatja ezt a címet az ismerőseivel, hogy kapcsolatba léphessenek Önnel a(z) **%@** nevű profilján keresztül."; -/* No comment provided by engineer. */ -"You can share your address as a link or QR code - anybody can connect to you." = "Megoszthatja a címét egy hivatkozásként vagy QR-kódként – így bárki kapcsolódhat Önhöz."; - /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "A csevegést az alkalmazás „Beállítások / Adatbázis” menüben vagy az alkalmazás újraindításával indíthatja el"; @@ -5189,7 +5120,7 @@ /* No comment provided by engineer. */ "You can use markdown to format messages:" = "Üzenetek formázása a szövegbe szúrt speciális karakterekkel:"; -/* No comment provided by engineer. */ +/* alert message */ "You can view invitation link again in connection details." = "A meghívó-hivatkozást újra megtekintheti a kapcsolat részleteinél."; /* No comment provided by engineer. */ @@ -5210,6 +5141,9 @@ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Nem sikerült hitelesíteni; próbálja meg újra."; +/* No comment provided by engineer. */ +"You decide who can connect." = "Ön dönti el, hogy kivel beszélget."; + /* No comment provided by engineer. */ "You have already requested connection via this address!" = "Már küldött egy kapcsolatkérést ezen a címen keresztül!"; @@ -5300,9 +5234,6 @@ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Inkognitóprofilt használ ehhez a csoporthoz - fő profilja megosztásának elkerülése érdekében a meghívók küldése le van tiltva"; -/* No comment provided by engineer. */ -"Your %@ servers" = "%@ nevű profiljához tartozó kiszolgálók"; - /* No comment provided by engineer. */ "Your calls" = "Hívások"; @@ -5366,9 +5297,6 @@ /* No comment provided by engineer. */ "Your random profile" = "Véletlenszerű profil"; -/* No comment provided by engineer. */ -"Your server" = "Saját SMP-kiszolgáló"; - /* No comment provided by engineer. */ "Your server address" = "Saját SMP-kiszolgálójának címe"; @@ -5381,6 +5309,3 @@ /* No comment provided by engineer. */ "Your SMP servers" = "Saját SMP-kiszolgálók"; -/* No comment provided by engineer. */ -"Your XFTP servers" = "Saját XFTP-kiszolgálók"; - diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index 43fd26e534..c041228706 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -1,15 +1,6 @@ /* No comment provided by engineer. */ "\n" = "\n"; -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - /* No comment provided by engineer. */ " (" = " ("; @@ -337,12 +328,6 @@ /* No comment provided by engineer. */ "Abort changing address?" = "Interrompere il cambio di indirizzo?"; -/* No comment provided by engineer. */ -"About SimpleX" = "Riguardo SimpleX"; - -/* No comment provided by engineer. */ -"About SimpleX address" = "Info sull'indirizzo SimpleX"; - /* No comment provided by engineer. */ "About SimpleX Chat" = "Riguardo SimpleX Chat"; @@ -382,12 +367,6 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Aggiungi l'indirizzo al tuo profilo, in modo che i tuoi contatti possano condividerlo con altre persone. L'aggiornamento del profilo verrà inviato ai tuoi contatti."; -/* No comment provided by engineer. */ -"Add contact" = "Aggiungi contatto"; - -/* No comment provided by engineer. */ -"Add preset servers" = "Aggiungi server preimpostati"; - /* No comment provided by engineer. */ "Add profile" = "Aggiungi profilo"; @@ -574,6 +553,9 @@ /* No comment provided by engineer. */ "Answer call" = "Rispondi alla chiamata"; +/* No comment provided by engineer. */ +"Anybody can host servers." = "Chiunque può installare i server."; + /* No comment provided by engineer. */ "App build: %@" = "Build dell'app: %@"; @@ -817,7 +799,8 @@ /* No comment provided by engineer. */ "Can't message member" = "Impossibile inviare un messaggio al membro"; -/* alert button */ +/* alert action + alert button */ "Cancel" = "Annulla"; /* No comment provided by engineer. */ @@ -938,7 +921,7 @@ /* No comment provided by engineer. */ "Chats" = "Chat"; -/* No comment provided by engineer. */ +/* alert title */ "Check server address and try again." = "Controlla l'indirizzo del server e riprova."; /* No comment provided by engineer. */ @@ -1001,9 +984,6 @@ /* No comment provided by engineer. */ "Configure ICE servers" = "Configura server ICE"; -/* No comment provided by engineer. */ -"Configured %@ servers" = "Configurati %@ server"; - /* No comment provided by engineer. */ "Confirm" = "Conferma"; @@ -1232,9 +1212,6 @@ /* No comment provided by engineer. */ "Create a group using a random profile." = "Crea un gruppo usando un profilo casuale."; -/* No comment provided by engineer. */ -"Create an address to let people connect with you." = "Crea un indirizzo per consentire alle persone di connettersi con te."; - /* server test step */ "Create file" = "Crea file"; @@ -1397,7 +1374,8 @@ /* No comment provided by engineer. */ "default (yes)" = "predefinito (sì)"; -/* chat item action +/* alert action + chat item action swipe action */ "Delete" = "Elimina"; @@ -1996,9 +1974,6 @@ /* No comment provided by engineer. */ "Error joining group" = "Errore di ingresso nel gruppo"; -/* No comment provided by engineer. */ -"Error loading %@ servers" = "Errore nel caricamento dei server %@"; - /* No comment provided by engineer. */ "Error migrating settings" = "Errore nella migrazione delle impostazioni"; @@ -2020,9 +1995,6 @@ /* No comment provided by engineer. */ "Error resetting statistics" = "Errore di azzeramento statistiche"; -/* No comment provided by engineer. */ -"Error saving %@ servers" = "Errore nel salvataggio dei server %@"; - /* No comment provided by engineer. */ "Error saving group profile" = "Errore nel salvataggio del profilo del gruppo"; @@ -2425,9 +2397,6 @@ /* time unit */ "hours" = "ore"; -/* No comment provided by engineer. */ -"How it works" = "Come funziona"; - /* No comment provided by engineer. */ "How SimpleX works" = "Come funziona SimpleX"; @@ -2570,10 +2539,10 @@ "Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Installa [Simplex Chat per terminale](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"Instant push notifications will be hidden!\n" = "Le notifiche push istantanee saranno nascoste!\n"; +"Instant" = "Istantaneamente"; /* No comment provided by engineer. */ -"Instant" = "Istantaneamente"; +"Instant push notifications will be hidden!\n" = "Le notifiche push istantanee saranno nascoste!\n"; /* No comment provided by engineer. */ "Interface" = "Interfaccia"; @@ -2611,7 +2580,7 @@ /* No comment provided by engineer. */ "Invalid response" = "Risposta non valida"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid server address!" = "Indirizzo del server non valido!"; /* item status text */ @@ -2716,7 +2685,7 @@ /* No comment provided by engineer. */ "Joining group" = "Ingresso nel gruppo"; -/* No comment provided by engineer. */ +/* alert action */ "Keep" = "Tieni"; /* No comment provided by engineer. */ @@ -2725,7 +2694,7 @@ /* No comment provided by engineer. */ "Keep the app open to use it from desktop" = "Tieni aperta l'app per usarla dal desktop"; -/* No comment provided by engineer. */ +/* alert title */ "Keep unused invitation?" = "Tenere l'invito inutilizzato?"; /* No comment provided by engineer. */ @@ -2782,9 +2751,6 @@ /* No comment provided by engineer. */ "Live messages" = "Messaggi in diretta"; -/* No comment provided by engineer. */ -"No push server" = "Locale"; - /* No comment provided by engineer. */ "Local name" = "Nome locale"; @@ -2797,24 +2763,15 @@ /* No comment provided by engineer. */ "Lock mode" = "Modalità di blocco"; -/* No comment provided by engineer. */ -"Make a private connection" = "Crea una connessione privata"; - /* No comment provided by engineer. */ "Make one message disappear" = "Fai sparire un messaggio"; /* No comment provided by engineer. */ "Make profile private!" = "Rendi privato il profilo!"; -/* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Assicurati che gli indirizzi dei server %@ siano nel formato corretto, uno per riga e non doppi (%@)."; - /* No comment provided by engineer. */ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Assicurati che gli indirizzi dei server WebRTC ICE siano nel formato corretto, uno per riga e non doppi."; -/* No comment provided by engineer. */ -"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Molte persone hanno chiesto: *se SimpleX non ha identificatori utente, come può recapitare i messaggi?*"; - /* No comment provided by engineer. */ "Mark deleted for everyone" = "Contrassegna eliminato per tutti"; @@ -3154,12 +3111,18 @@ /* No comment provided by engineer. */ "No permission to record voice message" = "Nessuna autorizzazione per registrare messaggi vocali"; +/* No comment provided by engineer. */ +"No push server" = "Locale"; + /* No comment provided by engineer. */ "No received or sent files" = "Nessun file ricevuto o inviato"; /* copied message info in history */ "no text" = "nessun testo"; +/* No comment provided by engineer. */ +"No user identifiers." = "Nessun identificatore utente."; + /* No comment provided by engineer. */ "Not compatible!" = "Non compatibile!"; @@ -3282,18 +3245,9 @@ /* authentication reason */ "Open migration to another device" = "Apri migrazione ad un altro dispositivo"; -/* No comment provided by engineer. */ -"Open server settings" = "Apri impostazioni server"; - /* No comment provided by engineer. */ "Open Settings" = "Apri le impostazioni"; -/* authentication reason */ -"Open user profiles" = "Apri i profili utente"; - -/* No comment provided by engineer. */ -"Anybody can host servers." = "Chiunque può installare i server."; - /* No comment provided by engineer. */ "Opening app…" = "Apertura dell'app…"; @@ -3315,9 +3269,6 @@ /* No comment provided by engineer. */ "Other" = "Altro"; -/* No comment provided by engineer. */ -"Other %@ servers" = "Altri %@ server"; - /* No comment provided by engineer. */ "other errors" = "altri errori"; @@ -3372,9 +3323,6 @@ /* No comment provided by engineer. */ "Pending" = "In attesa"; -/* No comment provided by engineer. */ -"You decide who can connect." = "Sei tu a decidere chi può connettersi."; - /* No comment provided by engineer. */ "Periodic" = "Periodicamente"; @@ -3453,9 +3401,6 @@ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Conserva la bozza dell'ultimo messaggio, con gli allegati."; -/* No comment provided by engineer. */ -"Preset server" = "Server preimpostato"; - /* No comment provided by engineer. */ "Preset server address" = "Indirizzo server preimpostato"; @@ -3504,7 +3449,7 @@ /* No comment provided by engineer. */ "Profile theme" = "Tema del profilo"; -/* No comment provided by engineer. */ +/* alert message */ "Profile update will be sent to your contacts." = "L'aggiornamento del profilo verrà inviato ai tuoi contatti."; /* No comment provided by engineer. */ @@ -3589,10 +3534,10 @@ "Read more" = "Leggi tutto"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Maggiori informazioni nella [Guida per l'utente](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Leggi di più nella [Guida utente](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Leggi di più nella [Guida utente](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Maggiori informazioni nella [Guida per l'utente](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Maggiori informazioni nella [Guida per l'utente](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; @@ -3600,9 +3545,6 @@ /* No comment provided by engineer. */ "Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Maggiori informazioni nel nostro [repository GitHub](https://github.com/simplex-chat/simplex-chat#readme)."; -/* No comment provided by engineer. */ -"Read more in our GitHub repository." = "Maggiori informazioni nel nostro repository GitHub."; - /* No comment provided by engineer. */ "Receipts are disabled" = "Le ricevute sono disattivate"; @@ -3875,7 +3817,7 @@ /* No comment provided by engineer. */ "Save servers" = "Salva i server"; -/* No comment provided by engineer. */ +/* alert title */ "Save servers?" = "Salvare i server?"; /* No comment provided by engineer. */ @@ -4028,9 +3970,6 @@ /* No comment provided by engineer. */ "Send notifications" = "Invia notifiche"; -/* No comment provided by engineer. */ -"Send notifications:" = "Invia notifiche:"; - /* No comment provided by engineer. */ "Send questions and ideas" = "Invia domande e idee"; @@ -4167,7 +4106,7 @@ "set new contact address" = "impostato nuovo indirizzo di contatto"; /* profile update event chat item */ -"set new profile picture" = "impostata nuova immagine del profilo"; +"set new profile picture" = "ha impostato una nuova immagine del profilo"; /* No comment provided by engineer. */ "Set passcode" = "Imposta codice"; @@ -4193,7 +4132,8 @@ /* No comment provided by engineer. */ "Shape profile images" = "Forma delle immagini del profilo"; -/* chat item action */ +/* alert action + chat item action */ "Share" = "Condividi"; /* No comment provided by engineer. */ @@ -4202,7 +4142,7 @@ /* No comment provided by engineer. */ "Share address" = "Condividi indirizzo"; -/* No comment provided by engineer. */ +/* alert title */ "Share address with contacts?" = "Condividere l'indirizzo con i contatti?"; /* No comment provided by engineer. */ @@ -4385,10 +4325,10 @@ /* No comment provided by engineer. */ "Stop sending file?" = "Fermare l'invio del file?"; -/* No comment provided by engineer. */ +/* alert action */ "Stop sharing" = "Smetti di condividere"; -/* No comment provided by engineer. */ +/* alert title */ "Stop sharing address?" = "Smettere di condividere l'indirizzo?"; /* authentication reason */ @@ -4484,7 +4424,7 @@ /* No comment provided by engineer. */ "Test servers" = "Prova i server"; -/* No comment provided by engineer. */ +/* alert title */ "Tests failed!" = "Test falliti!"; /* No comment provided by engineer. */ @@ -4496,9 +4436,6 @@ /* No comment provided by engineer. */ "Thanks to the users – contribute via Weblate!" = "Grazie agli utenti – contribuite via Weblate!"; -/* No comment provided by engineer. */ -"No user identifiers." = "Nessun identificatore utente."; - /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "L'app può avvisarti quando ricevi messaggi o richieste di contatto: apri le impostazioni per attivare."; @@ -4523,6 +4460,9 @@ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "La crittografia funziona e il nuovo accordo sulla crittografia non è richiesto. Potrebbero verificarsi errori di connessione!"; +/* No comment provided by engineer. */ +"The future of messaging" = "La nuova generazione di messaggistica privata"; + /* No comment provided by engineer. */ "The hash of the previous message is different." = "L'hash del messaggio precedente è diverso."; @@ -4541,9 +4481,6 @@ /* No comment provided by engineer. */ "The messages will be marked as moderated for all members." = "I messaggi verranno contrassegnati come moderati per tutti i membri."; -/* No comment provided by engineer. */ -"The future of messaging" = "La nuova generazione di messaggistica privata"; - /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Il database vecchio non è stato rimosso durante la migrazione, può essere eliminato."; @@ -4631,9 +4568,6 @@ /* No comment provided by engineer. */ "To make a new connection" = "Per creare una nuova connessione"; -/* No comment provided by engineer. */ -"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Per proteggere la privacy, invece degli ID utente utilizzati da tutte le altre piattaforme, SimpleX ha identificatori per le code di messaggi, separati per ciascuno dei tuoi contatti."; - /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Per proteggere il fuso orario, i file immagine/vocali usano UTC."; @@ -4643,6 +4577,9 @@ /* No comment provided by engineer. */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "Per proteggere il tuo indirizzo IP, l'instradamento privato usa i tuoi server SMP per consegnare i messaggi."; +/* No comment provided by engineer. */ +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Per proteggere la privacy, invece degli ID utente utilizzati da tutte le altre piattaforme, SimpleX ha identificatori per le code di messaggi, separati per ciascuno dei tuoi contatti."; + /* No comment provided by engineer. */ "To record speech please grant permission to use Microphone." = "Per registrare l'audio, concedi l'autorizzazione di usare il microfono."; @@ -5030,9 +4967,6 @@ /* No comment provided by engineer. */ "when IP hidden" = "quando l'IP è nascosto"; -/* No comment provided by engineer. */ -"When people request to connect, you can accept or reject it." = "Quando le persone chiedono di connettersi, puoi accettare o rifiutare."; - /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Quando condividi un profilo in incognito con qualcuno, questo profilo verrà utilizzato per i gruppi a cui ti invitano."; @@ -5174,9 +5108,6 @@ /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "Puoi condividere questo indirizzo con i tuoi contatti per consentire loro di connettersi con **%@**."; -/* No comment provided by engineer. */ -"You can share your address as a link or QR code - anybody can connect to you." = "Puoi condividere il tuo indirizzo come link o come codice QR: chiunque potrà connettersi a te."; - /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "Puoi avviare la chat via Impostazioni / Database o riavviando l'app"; @@ -5189,7 +5120,7 @@ /* No comment provided by engineer. */ "You can use markdown to format messages:" = "Puoi usare il markdown per formattare i messaggi:"; -/* No comment provided by engineer. */ +/* alert message */ "You can view invitation link again in connection details." = "Puoi vedere di nuovo il link di invito nei dettagli di connessione."; /* No comment provided by engineer. */ @@ -5210,6 +5141,9 @@ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Non è stato possibile verificarti, riprova."; +/* No comment provided by engineer. */ +"You decide who can connect." = "Sei tu a decidere chi può connettersi."; + /* No comment provided by engineer. */ "You have already requested connection via this address!" = "Hai già richiesto la connessione tramite questo indirizzo!"; @@ -5300,9 +5234,6 @@ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Stai usando un profilo in incognito per questo gruppo: per impedire la condivisione del tuo profilo principale non è consentito invitare contatti"; -/* No comment provided by engineer. */ -"Your %@ servers" = "I tuoi server %@"; - /* No comment provided by engineer. */ "Your calls" = "Le tue chiamate"; @@ -5366,9 +5297,6 @@ /* No comment provided by engineer. */ "Your random profile" = "Il tuo profilo casuale"; -/* No comment provided by engineer. */ -"Your server" = "Il tuo server"; - /* No comment provided by engineer. */ "Your server address" = "L'indirizzo del tuo server"; @@ -5381,6 +5309,3 @@ /* No comment provided by engineer. */ "Your SMP servers" = "I tuoi server SMP"; -/* No comment provided by engineer. */ -"Your XFTP servers" = "I tuoi server XFTP"; - diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index 93735ef2d1..d8756cc788 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -1,15 +1,6 @@ /* No comment provided by engineer. */ "\n" = "\n"; -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - /* No comment provided by engineer. */ " (" = " ("; @@ -313,12 +304,6 @@ /* No comment provided by engineer. */ "Abort changing address?" = "アドレス変更を中止しますか?"; -/* No comment provided by engineer. */ -"About SimpleX" = "SimpleXについて"; - -/* No comment provided by engineer. */ -"About SimpleX address" = "SimpleXアドレスについて"; - /* No comment provided by engineer. */ "About SimpleX Chat" = "SimpleX Chat について"; @@ -346,9 +331,6 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "プロフィールにアドレスを追加し、連絡先があなたのアドレスを他の人と共有できるようにします。プロフィールの更新は連絡先に送信されます。"; -/* No comment provided by engineer. */ -"Add preset servers" = "既存サーバを追加"; - /* No comment provided by engineer. */ "Add profile" = "プロフィールを追加"; @@ -487,6 +469,9 @@ /* No comment provided by engineer. */ "Answer call" = "通話に応答"; +/* No comment provided by engineer. */ +"Anybody can host servers." = "プロトコル技術とコードはオープンソースで、どなたでもご自分のサーバを運用できます。"; + /* No comment provided by engineer. */ "App build: %@" = "アプリのビルド: %@"; @@ -616,7 +601,8 @@ /* No comment provided by engineer. */ "Can't invite contacts!" = "連絡先を招待できません!"; -/* alert button */ +/* alert action + alert button */ "Cancel" = "中止"; /* feature offered item */ @@ -704,7 +690,7 @@ /* No comment provided by engineer. */ "Chats" = "チャット"; -/* No comment provided by engineer. */ +/* alert title */ "Check server address and try again." = "サーバのアドレスを確認してから再度試してください。"; /* No comment provided by engineer. */ @@ -866,9 +852,6 @@ /* No comment provided by engineer. */ "Create" = "作成"; -/* No comment provided by engineer. */ -"Create an address to let people connect with you." = "人とつながるためのアドレスを作成する。"; - /* server test step */ "Create file" = "ファイルを作成"; @@ -992,7 +975,8 @@ /* No comment provided by engineer. */ "default (yes)" = "デフォルト(はい)"; -/* chat item action +/* alert action + chat item action swipe action */ "Delete" = "削除"; @@ -1428,18 +1412,12 @@ /* No comment provided by engineer. */ "Error joining group" = "グループ参加にエラー発生"; -/* No comment provided by engineer. */ -"Error loading %@ servers" = "%@ サーバーのロード中にエラーが発生"; - /* alert title */ "Error receiving file" = "ファイル受信にエラー発生"; /* No comment provided by engineer. */ "Error removing member" = "メンバー除名にエラー発生"; -/* No comment provided by engineer. */ -"Error saving %@ servers" = "%@ サーバの保存エラー"; - /* No comment provided by engineer. */ "Error saving group profile" = "グループのプロフィール保存にエラー発生"; @@ -1707,9 +1685,6 @@ /* time unit */ "hours" = "時間"; -/* No comment provided by engineer. */ -"How it works" = "技術の説明"; - /* No comment provided by engineer. */ "How SimpleX works" = "SimpleX の仕組み"; @@ -1819,10 +1794,10 @@ "Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "インストール [ターミナル用SimpleX Chat](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"Instant push notifications will be hidden!\n" = "インスタントプッシュ通知は非表示になります!\n"; +"Instant" = "すぐに"; /* No comment provided by engineer. */ -"Instant" = "すぐに"; +"Instant push notifications will be hidden!\n" = "インスタントプッシュ通知は非表示になります!\n"; /* No comment provided by engineer. */ "Interface" = "インターフェース"; @@ -1839,7 +1814,7 @@ /* invalid chat item */ "invalid data" = "無効なデータ"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid server address!" = "無効なサーバアドレス!"; /* item status text */ @@ -1968,9 +1943,6 @@ /* No comment provided by engineer. */ "Live messages" = "ライブメッセージ"; -/* No comment provided by engineer. */ -"No push server" = "自分のみ"; - /* No comment provided by engineer. */ "Local name" = "ローカルネーム"; @@ -1983,24 +1955,15 @@ /* No comment provided by engineer. */ "Lock mode" = "ロックモード"; -/* No comment provided by engineer. */ -"Make a private connection" = "プライベートな接続をする"; - /* No comment provided by engineer. */ "Make one message disappear" = "メッセージを1つ消す"; /* No comment provided by engineer. */ "Make profile private!" = "プロフィールを非表示にできます!"; -/* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "%@ サーバー アドレスが正しい形式で、行が区切られており、重複していないことを確認してください (%@)。"; - /* No comment provided by engineer. */ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "WebRTC ICEサーバのアドレスを正しく1行ずつに分けて、重複しないように、形式もご確認ください。"; -/* No comment provided by engineer. */ -"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "多くの人が次のような質問をしました: *SimpleX にユーザー識別子がない場合、どうやってメッセージを配信できるのですか?*"; - /* No comment provided by engineer. */ "Mark deleted for everyone" = "全員に対して削除済みマークを付ける"; @@ -2208,12 +2171,18 @@ /* No comment provided by engineer. */ "No permission to record voice message" = "音声メッセージを録音する権限がありません"; +/* No comment provided by engineer. */ +"No push server" = "自分のみ"; + /* No comment provided by engineer. */ "No received or sent files" = "送受信済みのファイルがありません"; /* copied message info in history */ "no text" = "テキストなし"; +/* No comment provided by engineer. */ +"No user identifiers." = "世界初のユーザーIDのないプラットフォーム|設計も元からプライベート。"; + /* No comment provided by engineer. */ "Notifications" = "通知"; @@ -2318,12 +2287,6 @@ /* No comment provided by engineer. */ "Open Settings" = "設定を開く"; -/* authentication reason */ -"Open user profiles" = "ユーザープロフィールを開く"; - -/* No comment provided by engineer. */ -"Anybody can host servers." = "プロトコル技術とコードはオープンソースで、どなたでもご自分のサーバを運用できます。"; - /* member role */ "owner" = "オーナー"; @@ -2351,9 +2314,6 @@ /* No comment provided by engineer. */ "peer-to-peer" = "P2P"; -/* No comment provided by engineer. */ -"You decide who can connect." = "あなたと繋がることができるのは、あなたからリンクを頂いた方のみです。"; - /* No comment provided by engineer. */ "Periodic" = "定期的に"; @@ -2411,9 +2371,6 @@ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "添付を含めて、下書きを保存する。"; -/* No comment provided by engineer. */ -"Preset server" = "プレセットサーバ"; - /* No comment provided by engineer. */ "Preset server address" = "プレセットサーバのアドレス"; @@ -2438,7 +2395,7 @@ /* No comment provided by engineer. */ "Profile password" = "プロフィールのパスワード"; -/* No comment provided by engineer. */ +/* alert message */ "Profile update will be sent to your contacts." = "連絡先にプロフィール更新のお知らせが届きます。"; /* No comment provided by engineer. */ @@ -2501,9 +2458,6 @@ /* No comment provided by engineer. */ "Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "詳しくは[GitHubリポジトリ](https://github.com/simplex-chat/simplex-chat#readme)をご覧ください。"; -/* No comment provided by engineer. */ -"Read more in our GitHub repository." = "GitHubリポジトリで詳細をご確認ください。"; - /* No comment provided by engineer. */ "received answer…" = "回答を受け取りました…"; @@ -2686,7 +2640,7 @@ /* No comment provided by engineer. */ "Save servers" = "サーバを保存"; -/* No comment provided by engineer. */ +/* alert title */ "Save servers?" = "サーバを保存しますか?"; /* No comment provided by engineer. */ @@ -2767,9 +2721,6 @@ /* No comment provided by engineer. */ "Send notifications" = "通知を送信する"; -/* No comment provided by engineer. */ -"Send notifications:" = "通知を送信する:"; - /* No comment provided by engineer. */ "Send questions and ideas" = "質問やアイデアを送る"; @@ -2842,7 +2793,8 @@ /* No comment provided by engineer. */ "Settings" = "設定"; -/* chat item action */ +/* alert action + chat item action */ "Share" = "共有する"; /* No comment provided by engineer. */ @@ -2851,7 +2803,7 @@ /* No comment provided by engineer. */ "Share address" = "アドレスを共有する"; -/* No comment provided by engineer. */ +/* alert title */ "Share address with contacts?" = "アドレスを連絡先と共有しますか?"; /* No comment provided by engineer. */ @@ -2959,10 +2911,10 @@ /* No comment provided by engineer. */ "Stop sending file?" = "ファイルの送信を停止しますか?"; -/* No comment provided by engineer. */ +/* alert action */ "Stop sharing" = "共有を停止"; -/* No comment provided by engineer. */ +/* alert title */ "Stop sharing address?" = "アドレスの共有を停止しますか?"; /* authentication reason */ @@ -3019,7 +2971,7 @@ /* No comment provided by engineer. */ "Test servers" = "テストサーバ"; -/* No comment provided by engineer. */ +/* alert title */ "Tests failed!" = "テストは失敗しました!"; /* No comment provided by engineer. */ @@ -3031,9 +2983,6 @@ /* No comment provided by engineer. */ "Thanks to the users – contribute via Weblate!" = "ユーザーに感謝します – Weblate 経由で貢献してください!"; -/* No comment provided by engineer. */ -"No user identifiers." = "世界初のユーザーIDのないプラットフォーム|設計も元からプライベート。"; - /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "アプリは、メッセージや連絡先のリクエストを受信したときに通知することができます - 設定を開いて有効にしてください。"; @@ -3052,6 +3001,9 @@ /* 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 future of messaging" = "次世代のプライバシー・メッセンジャー"; + /* No comment provided by engineer. */ "The hash of the previous message is different." = "以前のメッセージとハッシュ値が異なります。"; @@ -3064,9 +3016,6 @@ /* No comment provided by engineer. */ "The message will be marked as moderated for all members." = "メッセージは、すべてのメンバーに対してモデレートされたものとして表示されます。"; -/* No comment provided by engineer. */ -"The future of messaging" = "次世代のプライバシー・メッセンジャー"; - /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "古いデータベースは移行時に削除されなかったので、削除することができます。"; @@ -3115,15 +3064,15 @@ /* No comment provided by engineer. */ "To make a new connection" = "新規に接続する場合"; -/* No comment provided by engineer. */ -"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "プライバシーを保護するために、SimpleX には、他のすべてのプラットフォームで使用されるユーザー ID の代わりに、連絡先ごとに個別のメッセージ キューの識別子があります。"; - /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "時間帯を漏らさないために、画像と音声ファイルはUTCを使います。"; /* No comment provided by engineer. */ "To protect your information, turn on SimpleX Lock.\nYou will be prompted to complete authentication before this feature is enabled." = "あなたのデータを守るために、SimpleXロックをオンにしてください。\nオンにするには、認証ステップが行われます。"; +/* No comment provided by engineer. */ +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "プライバシーを保護するために、SimpleX には、他のすべてのプラットフォームで使用されるユーザー ID の代わりに、連絡先ごとに個別のメッセージ キューの識別子があります。"; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "音声メッセージを録音する場合は、マイクの使用を許可してください。"; @@ -3346,9 +3295,6 @@ /* No comment provided by engineer. */ "When available" = "利用可能時に"; -/* No comment provided by engineer. */ -"When people request to connect, you can accept or reject it." = "接続が要求されたら、それを受け入れるか拒否するかを選択できます。"; - /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "連絡相手にシークレットモードのプロフィールを共有すると、その連絡相手に招待されたグループでも同じプロフィールが使われます。"; @@ -3415,9 +3361,6 @@ /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "このアドレスを連絡先と共有して、**%@** に接続できるようにすることができます。"; -/* No comment provided by engineer. */ -"You can share your address as a link or QR code - anybody can connect to you." = "アドレスをリンクやQRコードとして共有することで、誰でも接続することができます。"; - /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "アプリの設定/データベースから、またはアプリを再起動することでチャットを開始できます"; @@ -3445,6 +3388,9 @@ /* No comment provided by engineer. */ "You could not be verified; please try again." = "確認できませんでした。 もう一度お試しください。"; +/* No comment provided by engineer. */ +"You decide who can connect." = "あなたと繋がることができるのは、あなたからリンクを頂いた方のみです。"; + /* No comment provided by engineer. */ "You have to enter passphrase every time the app starts - it is not stored on the device." = "アプリ起動時にパスフレーズを入力しなければなりません。端末に保存されてません。"; @@ -3511,9 +3457,6 @@ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "シークレットモードのプロフィールでこのグループに参加しています。メインのプロフィールを守るために、招待することができません"; -/* No comment provided by engineer. */ -"Your %@ servers" = "あなたの %@ サーバー"; - /* No comment provided by engineer. */ "Your calls" = "あなたの通話"; @@ -3562,9 +3505,6 @@ /* No comment provided by engineer. */ "Your random profile" = "あなたのランダム・プロフィール"; -/* No comment provided by engineer. */ -"Your server" = "あなたのサーバ"; - /* No comment provided by engineer. */ "Your server address" = "あなたのサーバアドレス"; @@ -3577,6 +3517,3 @@ /* No comment provided by engineer. */ "Your SMP servers" = "あなたのSMPサーバ"; -/* No comment provided by engineer. */ -"Your XFTP servers" = "あなたのXFTPサーバ"; - diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index 94cb3115a9..4729e50d46 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -1,15 +1,6 @@ /* No comment provided by engineer. */ "\n" = "\n"; -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - /* No comment provided by engineer. */ " (" = " ("; @@ -337,12 +328,6 @@ /* No comment provided by engineer. */ "Abort changing address?" = "Adres wijziging afbreken?"; -/* No comment provided by engineer. */ -"About SimpleX" = "Over SimpleX"; - -/* No comment provided by engineer. */ -"About SimpleX address" = "Over SimpleX adres"; - /* No comment provided by engineer. */ "About SimpleX Chat" = "Over SimpleX Chat"; @@ -382,12 +367,6 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Voeg een adres toe aan uw profiel, zodat uw contacten het met andere mensen kunnen delen. Profiel update wordt naar uw contacten verzonden."; -/* No comment provided by engineer. */ -"Add contact" = "Contact toevoegen"; - -/* No comment provided by engineer. */ -"Add preset servers" = "Vooraf ingestelde servers toevoegen"; - /* No comment provided by engineer. */ "Add profile" = "Profiel toevoegen"; @@ -574,6 +553,9 @@ /* No comment provided by engineer. */ "Answer call" = "Beantwoord oproep"; +/* No comment provided by engineer. */ +"Anybody can host servers." = "Iedereen kan servers hosten."; + /* No comment provided by engineer. */ "App build: %@" = "App build: %@"; @@ -817,7 +799,8 @@ /* No comment provided by engineer. */ "Can't message member" = "Kan geen bericht sturen naar lid"; -/* alert button */ +/* alert action + alert button */ "Cancel" = "Annuleren"; /* No comment provided by engineer. */ @@ -938,7 +921,7 @@ /* No comment provided by engineer. */ "Chats" = "Chats"; -/* No comment provided by engineer. */ +/* alert title */ "Check server address and try again." = "Controleer het server adres en probeer het opnieuw."; /* No comment provided by engineer. */ @@ -1001,9 +984,6 @@ /* No comment provided by engineer. */ "Configure ICE servers" = "ICE servers configureren"; -/* No comment provided by engineer. */ -"Configured %@ servers" = "%@ servers geconfigureerd"; - /* No comment provided by engineer. */ "Confirm" = "Bevestigen"; @@ -1232,9 +1212,6 @@ /* No comment provided by engineer. */ "Create a group using a random profile." = "Maak een groep met een willekeurig profiel."; -/* No comment provided by engineer. */ -"Create an address to let people connect with you." = "Maak een adres aan zodat mensen contact met je kunnen opnemen."; - /* server test step */ "Create file" = "Bestand maken"; @@ -1397,7 +1374,8 @@ /* No comment provided by engineer. */ "default (yes)" = "standaard (ja)"; -/* chat item action +/* alert action + chat item action swipe action */ "Delete" = "Verwijderen"; @@ -1996,9 +1974,6 @@ /* No comment provided by engineer. */ "Error joining group" = "Fout bij lid worden van groep"; -/* No comment provided by engineer. */ -"Error loading %@ servers" = "Fout bij het laden van %@ servers"; - /* No comment provided by engineer. */ "Error migrating settings" = "Fout bij migreren van instellingen"; @@ -2020,9 +1995,6 @@ /* No comment provided by engineer. */ "Error resetting statistics" = "Fout bij het resetten van statistieken"; -/* No comment provided by engineer. */ -"Error saving %@ servers" = "Fout bij opslaan van %@ servers"; - /* No comment provided by engineer. */ "Error saving group profile" = "Fout bij opslaan van groep profiel"; @@ -2425,9 +2397,6 @@ /* time unit */ "hours" = "uren"; -/* No comment provided by engineer. */ -"How it works" = "Hoe het werkt"; - /* No comment provided by engineer. */ "How SimpleX works" = "Hoe SimpleX werkt"; @@ -2570,10 +2539,10 @@ "Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Installeer [SimpleX Chat voor terminal](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"Instant push notifications will be hidden!\n" = "Directe push meldingen worden verborgen!\n"; +"Instant" = "Direct"; /* No comment provided by engineer. */ -"Instant" = "Direct"; +"Instant push notifications will be hidden!\n" = "Directe push meldingen worden verborgen!\n"; /* No comment provided by engineer. */ "Interface" = "Interface"; @@ -2611,7 +2580,7 @@ /* No comment provided by engineer. */ "Invalid response" = "Ongeldig antwoord"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid server address!" = "Ongeldig server adres!"; /* item status text */ @@ -2716,7 +2685,7 @@ /* No comment provided by engineer. */ "Joining group" = "Deel nemen aan groep"; -/* No comment provided by engineer. */ +/* alert action */ "Keep" = "Bewaar"; /* No comment provided by engineer. */ @@ -2725,7 +2694,7 @@ /* No comment provided by engineer. */ "Keep the app open to use it from desktop" = "Houd de app geopend om deze vanaf de desktop te gebruiken"; -/* No comment provided by engineer. */ +/* alert title */ "Keep unused invitation?" = "Ongebruikte uitnodiging bewaren?"; /* No comment provided by engineer. */ @@ -2782,9 +2751,6 @@ /* No comment provided by engineer. */ "Live messages" = "Live berichten"; -/* No comment provided by engineer. */ -"No push server" = "Lokaal"; - /* No comment provided by engineer. */ "Local name" = "Lokale naam"; @@ -2797,24 +2763,15 @@ /* No comment provided by engineer. */ "Lock mode" = "Vergrendeling modus"; -/* No comment provided by engineer. */ -"Make a private connection" = "Maak een privéverbinding"; - /* No comment provided by engineer. */ "Make one message disappear" = "Eén bericht laten verdwijnen"; /* No comment provided by engineer. */ "Make profile private!" = "Profiel privé maken!"; -/* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Zorg ervoor dat %@ server adressen de juiste indeling hebben, regel gescheiden zijn en niet gedupliceerd zijn (%@)."; - /* No comment provided by engineer. */ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Zorg ervoor dat WebRTC ICE server adressen de juiste indeling hebben, regel gescheiden zijn en niet gedupliceerd zijn."; -/* No comment provided by engineer. */ -"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Veel mensen vroegen: *als SimpleX geen gebruikers-ID's heeft, hoe kan het dan berichten bezorgen?*"; - /* No comment provided by engineer. */ "Mark deleted for everyone" = "Markeer verwijderd voor iedereen"; @@ -3154,12 +3111,18 @@ /* No comment provided by engineer. */ "No permission to record voice message" = "Geen toestemming om spraakbericht op te nemen"; +/* No comment provided by engineer. */ +"No push server" = "Lokaal"; + /* No comment provided by engineer. */ "No received or sent files" = "Geen ontvangen of verzonden bestanden"; /* copied message info in history */ "no text" = "geen tekst"; +/* No comment provided by engineer. */ +"No user identifiers." = "Geen gebruikers-ID's."; + /* No comment provided by engineer. */ "Not compatible!" = "Niet compatibel!"; @@ -3282,18 +3245,9 @@ /* authentication reason */ "Open migration to another device" = "Open de migratie naar een ander apparaat"; -/* No comment provided by engineer. */ -"Open server settings" = "Server instellingen openen"; - /* No comment provided by engineer. */ "Open Settings" = "Open instellingen"; -/* authentication reason */ -"Open user profiles" = "Gebruikers profielen openen"; - -/* No comment provided by engineer. */ -"Anybody can host servers." = "Iedereen kan servers hosten."; - /* No comment provided by engineer. */ "Opening app…" = "App openen…"; @@ -3315,9 +3269,6 @@ /* No comment provided by engineer. */ "Other" = "Ander"; -/* No comment provided by engineer. */ -"Other %@ servers" = "Andere %@ servers"; - /* No comment provided by engineer. */ "other errors" = "overige fouten"; @@ -3372,9 +3323,6 @@ /* No comment provided by engineer. */ "Pending" = "in behandeling"; -/* No comment provided by engineer. */ -"You decide who can connect." = "Jij bepaalt wie er verbinding mag maken."; - /* No comment provided by engineer. */ "Periodic" = "Periodiek"; @@ -3453,9 +3401,6 @@ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Bewaar het laatste berichtconcept, met bijlagen."; -/* No comment provided by engineer. */ -"Preset server" = "Vooraf ingestelde server"; - /* No comment provided by engineer. */ "Preset server address" = "Vooraf ingesteld server adres"; @@ -3504,7 +3449,7 @@ /* No comment provided by engineer. */ "Profile theme" = "Profiel thema"; -/* No comment provided by engineer. */ +/* alert message */ "Profile update will be sent to your contacts." = "Profiel update wordt naar uw contacten verzonden."; /* No comment provided by engineer. */ @@ -3589,10 +3534,10 @@ "Read more" = "Lees meer"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/app-settings.html#uw-simplex-contactadres)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/app-settings.html#uw-simplex-contactadres)."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; @@ -3600,9 +3545,6 @@ /* No comment provided by engineer. */ "Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Lees meer in onze [GitHub-repository](https://github.com/simplex-chat/simplex-chat#readme)."; -/* No comment provided by engineer. */ -"Read more in our GitHub repository." = "Lees meer in onze GitHub repository."; - /* No comment provided by engineer. */ "Receipts are disabled" = "Bevestigingen zijn uitgeschakeld"; @@ -3875,7 +3817,7 @@ /* No comment provided by engineer. */ "Save servers" = "Servers opslaan"; -/* No comment provided by engineer. */ +/* alert title */ "Save servers?" = "Servers opslaan?"; /* No comment provided by engineer. */ @@ -4028,9 +3970,6 @@ /* No comment provided by engineer. */ "Send notifications" = "Meldingen verzenden"; -/* No comment provided by engineer. */ -"Send notifications:" = "Meldingen verzenden:"; - /* No comment provided by engineer. */ "Send questions and ideas" = "Stuur vragen en ideeën"; @@ -4193,7 +4132,8 @@ /* No comment provided by engineer. */ "Shape profile images" = "Vorm profiel afbeeldingen"; -/* chat item action */ +/* alert action + chat item action */ "Share" = "Deel"; /* No comment provided by engineer. */ @@ -4202,7 +4142,7 @@ /* No comment provided by engineer. */ "Share address" = "Adres delen"; -/* No comment provided by engineer. */ +/* alert title */ "Share address with contacts?" = "Adres delen met contacten?"; /* No comment provided by engineer. */ @@ -4385,10 +4325,10 @@ /* No comment provided by engineer. */ "Stop sending file?" = "Bestand verzenden stoppen?"; -/* No comment provided by engineer. */ +/* alert action */ "Stop sharing" = "Stop met delen"; -/* No comment provided by engineer. */ +/* alert title */ "Stop sharing address?" = "Stop met het delen van adres?"; /* authentication reason */ @@ -4484,7 +4424,7 @@ /* No comment provided by engineer. */ "Test servers" = "Servers testen"; -/* No comment provided by engineer. */ +/* alert title */ "Tests failed!" = "Testen mislukt!"; /* No comment provided by engineer. */ @@ -4496,9 +4436,6 @@ /* No comment provided by engineer. */ "Thanks to the users – contribute via Weblate!" = "Dank aan de gebruikers – draag bij via Weblate!"; -/* No comment provided by engineer. */ -"No user identifiers." = "Geen gebruikers-ID\'s."; - /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "De app kan u op de hoogte stellen wanneer u berichten of contact verzoeken ontvangt - open de instellingen om dit in te schakelen."; @@ -4523,6 +4460,9 @@ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "De versleuteling werkt en de nieuwe versleutelingsovereenkomst is niet vereist. Dit kan leiden tot verbindingsfouten!"; +/* No comment provided by engineer. */ +"The future of messaging" = "De volgende generatie privéberichten"; + /* No comment provided by engineer. */ "The hash of the previous message is different." = "De hash van het vorige bericht is anders."; @@ -4541,9 +4481,6 @@ /* No comment provided by engineer. */ "The messages will be marked as moderated for all members." = "De berichten worden voor alle leden als gemodereerd gemarkeerd."; -/* No comment provided by engineer. */ -"The future of messaging" = "De volgende generatie privéberichten"; - /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "De oude database is niet verwijderd tijdens de migratie, deze kan worden verwijderd."; @@ -4631,9 +4568,6 @@ /* No comment provided by engineer. */ "To make a new connection" = "Om een nieuwe verbinding te maken"; -/* No comment provided by engineer. */ -"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Om de privacy te beschermen, heeft SimpleX in plaats van gebruikers-ID's die door alle andere platforms worden gebruikt, ID's voor berichten wachtrijen, afzonderlijk voor elk van uw contacten."; - /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Om de tijdzone te beschermen, gebruiken afbeeldings-/spraakbestanden UTC."; @@ -4643,6 +4577,9 @@ /* No comment provided by engineer. */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "Om uw IP-adres te beschermen, gebruikt privéroutering uw SMP-servers om berichten te bezorgen."; +/* No comment provided by engineer. */ +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Om de privacy te beschermen, heeft SimpleX in plaats van gebruikers-ID's die door alle andere platforms worden gebruikt, ID's voor berichten wachtrijen, afzonderlijk voor elk van uw contacten."; + /* No comment provided by engineer. */ "To record speech please grant permission to use Microphone." = "Geef toestemming om de microfoon te gebruiken om spraak op te nemen."; @@ -5030,9 +4967,6 @@ /* No comment provided by engineer. */ "when IP hidden" = "wanneer IP verborgen is"; -/* No comment provided by engineer. */ -"When people request to connect, you can accept or reject it." = "Wanneer mensen vragen om verbinding te maken, kunt u dit accepteren of weigeren."; - /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Wanneer je een incognito profiel met iemand deelt, wordt dit profiel gebruikt voor de groepen waarvoor ze je uitnodigen."; @@ -5174,9 +5108,6 @@ /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "U kunt dit adres delen met uw contacten om hen verbinding te laten maken met **%@**."; -/* No comment provided by engineer. */ -"You can share your address as a link or QR code - anybody can connect to you." = "U kunt uw adres delen als een link of als een QR-code. Iedereen kan verbinding met u maken."; - /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "U kunt de chat starten via app Instellingen / Database of door de app opnieuw op te starten"; @@ -5189,7 +5120,7 @@ /* No comment provided by engineer. */ "You can use markdown to format messages:" = "U kunt markdown gebruiken voor opmaak in berichten:"; -/* No comment provided by engineer. */ +/* alert message */ "You can view invitation link again in connection details." = "U kunt de uitnodigingslink opnieuw bekijken in de verbindingsdetails."; /* No comment provided by engineer. */ @@ -5210,6 +5141,9 @@ /* No comment provided by engineer. */ "You could not be verified; please try again." = "U kon niet worden geverifieerd; probeer het opnieuw."; +/* No comment provided by engineer. */ +"You decide who can connect." = "Jij bepaalt wie er verbinding mag maken."; + /* No comment provided by engineer. */ "You have already requested connection via this address!" = "U heeft al een verbinding aangevraagd via dit adres!"; @@ -5300,9 +5234,6 @@ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Je gebruikt een incognito profiel voor deze groep. Om te voorkomen dat je je hoofdprofiel deelt, is het niet toegestaan om contacten uit te nodigen"; -/* No comment provided by engineer. */ -"Your %@ servers" = "Uw %@ servers"; - /* No comment provided by engineer. */ "Your calls" = "Uw oproepen"; @@ -5366,9 +5297,6 @@ /* No comment provided by engineer. */ "Your random profile" = "Je willekeurige profiel"; -/* No comment provided by engineer. */ -"Your server" = "Uw server"; - /* No comment provided by engineer. */ "Your server address" = "Uw server adres"; @@ -5381,6 +5309,3 @@ /* No comment provided by engineer. */ "Your SMP servers" = "Uw SMP servers"; -/* No comment provided by engineer. */ -"Your XFTP servers" = "Uw XFTP servers"; - diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index 77c724a6b1..644ba366f6 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -1,15 +1,6 @@ /* No comment provided by engineer. */ "\n" = "\n"; -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - /* No comment provided by engineer. */ " (" = " ("; @@ -337,12 +328,6 @@ /* No comment provided by engineer. */ "Abort changing address?" = "Przerwać zmianę adresu?"; -/* No comment provided by engineer. */ -"About SimpleX" = "O SimpleX"; - -/* No comment provided by engineer. */ -"About SimpleX address" = "O adresie SimpleX"; - /* No comment provided by engineer. */ "About SimpleX Chat" = "O SimpleX Chat"; @@ -382,12 +367,6 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Dodaj adres do swojego profilu, aby Twoje kontakty mogły go udostępnić innym osobom. Aktualizacja profilu zostanie wysłana do Twoich kontaktów."; -/* No comment provided by engineer. */ -"Add contact" = "Dodaj kontakt"; - -/* No comment provided by engineer. */ -"Add preset servers" = "Dodaj gotowe serwery"; - /* No comment provided by engineer. */ "Add profile" = "Dodaj profil"; @@ -574,6 +553,9 @@ /* No comment provided by engineer. */ "Answer call" = "Odbierz połączenie"; +/* No comment provided by engineer. */ +"Anybody can host servers." = "Każdy może hostować serwery."; + /* No comment provided by engineer. */ "App build: %@" = "Kompilacja aplikacji: %@"; @@ -802,7 +784,8 @@ /* No comment provided by engineer. */ "Can't message member" = "Nie można wysłać wiadomości do członka"; -/* alert button */ +/* alert action + alert button */ "Cancel" = "Anuluj"; /* No comment provided by engineer. */ @@ -923,7 +906,7 @@ /* No comment provided by engineer. */ "Chats" = "Czaty"; -/* No comment provided by engineer. */ +/* alert title */ "Check server address and try again." = "Sprawdź adres serwera i spróbuj ponownie."; /* No comment provided by engineer. */ @@ -986,9 +969,6 @@ /* No comment provided by engineer. */ "Configure ICE servers" = "Skonfiguruj serwery ICE"; -/* No comment provided by engineer. */ -"Configured %@ servers" = "Skonfigurowano %@ serwerów"; - /* No comment provided by engineer. */ "Confirm" = "Potwierdź"; @@ -1217,9 +1197,6 @@ /* No comment provided by engineer. */ "Create a group using a random profile." = "Utwórz grupę używając losowego profilu."; -/* No comment provided by engineer. */ -"Create an address to let people connect with you." = "Utwórz adres, aby ludzie mogli się z Tobą połączyć."; - /* server test step */ "Create file" = "Utwórz plik"; @@ -1379,7 +1356,8 @@ /* No comment provided by engineer. */ "default (yes)" = "domyślnie (tak)"; -/* chat item action +/* alert action + chat item action swipe action */ "Delete" = "Usuń"; @@ -1975,9 +1953,6 @@ /* No comment provided by engineer. */ "Error joining group" = "Błąd dołączenia do grupy"; -/* No comment provided by engineer. */ -"Error loading %@ servers" = "Błąd ładowania %@ serwerów"; - /* No comment provided by engineer. */ "Error migrating settings" = "Błąd migracji ustawień"; @@ -1999,9 +1974,6 @@ /* No comment provided by engineer. */ "Error resetting statistics" = "Błąd resetowania statystyk"; -/* No comment provided by engineer. */ -"Error saving %@ servers" = "Błąd zapisu %@ serwerów"; - /* No comment provided by engineer. */ "Error saving group profile" = "Błąd zapisu profilu grupy"; @@ -2401,9 +2373,6 @@ /* time unit */ "hours" = "godziny"; -/* No comment provided by engineer. */ -"How it works" = "Jak to działa"; - /* No comment provided by engineer. */ "How SimpleX works" = "Jak działa SimpleX"; @@ -2543,10 +2512,10 @@ "Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Zainstaluj [SimpleX Chat na terminal](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"Instant push notifications will be hidden!\n" = "Natychmiastowe powiadomienia push będą ukryte!\n"; +"Instant" = "Natychmiastowo"; /* No comment provided by engineer. */ -"Instant" = "Natychmiastowo"; +"Instant push notifications will be hidden!\n" = "Natychmiastowe powiadomienia push będą ukryte!\n"; /* No comment provided by engineer. */ "Interface" = "Interfejs"; @@ -2584,7 +2553,7 @@ /* No comment provided by engineer. */ "Invalid response" = "Nieprawidłowa odpowiedź"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid server address!" = "Nieprawidłowy adres serwera!"; /* item status text */ @@ -2689,7 +2658,7 @@ /* No comment provided by engineer. */ "Joining group" = "Dołączanie do grupy"; -/* No comment provided by engineer. */ +/* alert action */ "Keep" = "Zachowaj"; /* No comment provided by engineer. */ @@ -2698,7 +2667,7 @@ /* No comment provided by engineer. */ "Keep the app open to use it from desktop" = "Zostaw aplikację otwartą i używaj ją z komputera"; -/* No comment provided by engineer. */ +/* alert title */ "Keep unused invitation?" = "Zachować nieużyte zaproszenie?"; /* No comment provided by engineer. */ @@ -2755,9 +2724,6 @@ /* No comment provided by engineer. */ "Live messages" = "Wiadomości na żywo"; -/* No comment provided by engineer. */ -"No push server" = "Lokalnie"; - /* No comment provided by engineer. */ "Local name" = "Nazwa lokalna"; @@ -2770,24 +2736,15 @@ /* No comment provided by engineer. */ "Lock mode" = "Tryb blokady"; -/* No comment provided by engineer. */ -"Make a private connection" = "Nawiąż prywatne połączenie"; - /* No comment provided by engineer. */ "Make one message disappear" = "Spraw, aby jedna wiadomość zniknęła"; /* No comment provided by engineer. */ "Make profile private!" = "Ustaw profil jako prywatny!"; -/* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Upewnij się, że adresy serwerów %@ są w poprawnym formacie, rozdzielone liniami i nie są zduplikowane (%@)."; - /* No comment provided by engineer. */ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Upewnij się, że adresy serwerów WebRTC ICE są w poprawnym formacie, rozdzielone liniami i nie są zduplikowane."; -/* No comment provided by engineer. */ -"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Wiele osób pytało: *jeśli SimpleX nie ma identyfikatora użytkownika, jak może dostarczać wiadomości?*"; - /* No comment provided by engineer. */ "Mark deleted for everyone" = "Oznacz jako usunięty dla wszystkich"; @@ -3127,12 +3084,18 @@ /* No comment provided by engineer. */ "No permission to record voice message" = "Brak uprawnień do nagrywania wiadomości głosowej"; +/* No comment provided by engineer. */ +"No push server" = "Lokalnie"; + /* No comment provided by engineer. */ "No received or sent files" = "Brak odebranych lub wysłanych plików"; /* copied message info in history */ "no text" = "brak tekstu"; +/* No comment provided by engineer. */ +"No user identifiers." = "Brak identyfikatorów użytkownika."; + /* No comment provided by engineer. */ "Not compatible!" = "Nie kompatybilny!"; @@ -3255,18 +3218,9 @@ /* authentication reason */ "Open migration to another device" = "Otwórz migrację na innym urządzeniu"; -/* No comment provided by engineer. */ -"Open server settings" = "Otwórz ustawienia serwera"; - /* No comment provided by engineer. */ "Open Settings" = "Otwórz Ustawienia"; -/* authentication reason */ -"Open user profiles" = "Otwórz profile użytkownika"; - -/* No comment provided by engineer. */ -"Anybody can host servers." = "Każdy może hostować serwery."; - /* No comment provided by engineer. */ "Opening app…" = "Otwieranie aplikacji…"; @@ -3288,9 +3242,6 @@ /* No comment provided by engineer. */ "Other" = "Inne"; -/* No comment provided by engineer. */ -"Other %@ servers" = "Inne %@ serwery"; - /* No comment provided by engineer. */ "other errors" = "inne błędy"; @@ -3345,9 +3296,6 @@ /* No comment provided by engineer. */ "Pending" = "Oczekujące"; -/* No comment provided by engineer. */ -"You decide who can connect." = "Ty decydujesz, kto może się połączyć."; - /* No comment provided by engineer. */ "Periodic" = "Okresowo"; @@ -3426,9 +3374,6 @@ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Zachowaj ostatnią wersję roboczą wiadomości wraz z załącznikami."; -/* No comment provided by engineer. */ -"Preset server" = "Wstępnie ustawiony serwer"; - /* No comment provided by engineer. */ "Preset server address" = "Wstępnie ustawiony adres serwera"; @@ -3477,7 +3422,7 @@ /* No comment provided by engineer. */ "Profile theme" = "Motyw profilu"; -/* No comment provided by engineer. */ +/* alert message */ "Profile update will be sent to your contacts." = "Aktualizacja profilu zostanie wysłana do Twoich kontaktów."; /* No comment provided by engineer. */ @@ -3562,10 +3507,10 @@ "Read more" = "Przeczytaj więcej"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Przeczytaj więcej w [Podręczniku Użytkownika](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Przeczytaj więcej w [Poradniku Użytkownika](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Przeczytaj więcej w [Poradniku Użytkownika](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Przeczytaj więcej w [Podręczniku Użytkownika](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Przeczytaj więcej w [Podręczniku Użytkownika](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; @@ -3573,9 +3518,6 @@ /* No comment provided by engineer. */ "Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Przeczytaj więcej na naszym [repozytorium GitHub](https://github.com/simplex-chat/simplex-chat#readme)."; -/* No comment provided by engineer. */ -"Read more in our GitHub repository." = "Przeczytaj więcej na naszym repozytorium GitHub."; - /* No comment provided by engineer. */ "Receipts are disabled" = "Potwierdzenia są wyłączone"; @@ -3848,7 +3790,7 @@ /* No comment provided by engineer. */ "Save servers" = "Zapisz serwery"; -/* No comment provided by engineer. */ +/* alert title */ "Save servers?" = "Zapisać serwery?"; /* No comment provided by engineer. */ @@ -4001,9 +3943,6 @@ /* No comment provided by engineer. */ "Send notifications" = "Wyślij powiadomienia"; -/* No comment provided by engineer. */ -"Send notifications:" = "Wyślij powiadomienia:"; - /* No comment provided by engineer. */ "Send questions and ideas" = "Wyślij pytania i pomysły"; @@ -4166,7 +4105,8 @@ /* No comment provided by engineer. */ "Shape profile images" = "Kształtuj obrazy profilowe"; -/* chat item action */ +/* alert action + chat item action */ "Share" = "Udostępnij"; /* No comment provided by engineer. */ @@ -4175,7 +4115,7 @@ /* No comment provided by engineer. */ "Share address" = "Udostępnij adres"; -/* No comment provided by engineer. */ +/* alert title */ "Share address with contacts?" = "Udostępnić adres kontaktom?"; /* No comment provided by engineer. */ @@ -4355,10 +4295,10 @@ /* No comment provided by engineer. */ "Stop sending file?" = "Przestać wysyłać plik?"; -/* No comment provided by engineer. */ +/* alert action */ "Stop sharing" = "Przestań udostępniać"; -/* No comment provided by engineer. */ +/* alert title */ "Stop sharing address?" = "Przestać udostępniać adres?"; /* authentication reason */ @@ -4448,7 +4388,7 @@ /* No comment provided by engineer. */ "Test servers" = "Przetestuj serwery"; -/* No comment provided by engineer. */ +/* alert title */ "Tests failed!" = "Testy nie powiodły się!"; /* No comment provided by engineer. */ @@ -4460,9 +4400,6 @@ /* No comment provided by engineer. */ "Thanks to the users – contribute via Weblate!" = "Podziękowania dla użytkowników - wkład za pośrednictwem Weblate!"; -/* No comment provided by engineer. */ -"No user identifiers." = "Brak identyfikatorów użytkownika."; - /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Aplikacja może powiadamiać Cię, gdy otrzymujesz wiadomości lub prośby o kontakt — otwórz ustawienia, aby włączyć."; @@ -4487,6 +4424,9 @@ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Szyfrowanie działa, a nowe uzgodnienie szyfrowania nie jest wymagane. Może to spowodować błędy w połączeniu!"; +/* No comment provided by engineer. */ +"The future of messaging" = "Następna generacja prywatnych wiadomości"; + /* No comment provided by engineer. */ "The hash of the previous message is different." = "Hash poprzedniej wiadomości jest inny."; @@ -4505,9 +4445,6 @@ /* No comment provided by engineer. */ "The messages will be marked as moderated for all members." = "Wiadomości zostaną oznaczone jako moderowane dla wszystkich członków."; -/* No comment provided by engineer. */ -"The future of messaging" = "Następna generacja prywatnych wiadomości"; - /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Stara baza danych nie została usunięta podczas migracji, można ją usunąć."; @@ -4595,9 +4532,6 @@ /* No comment provided by engineer. */ "To make a new connection" = "Aby nawiązać nowe połączenie"; -/* No comment provided by engineer. */ -"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Aby chronić prywatność, zamiast identyfikatorów użytkowników używanych przez wszystkie inne platformy, SimpleX ma identyfikatory dla kolejek wiadomości, oddzielne dla każdego z Twoich kontaktów."; - /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Aby chronić strefę czasową, pliki obrazów/głosów używają UTC."; @@ -4607,6 +4541,9 @@ /* No comment provided by engineer. */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "Aby chronić Twój adres IP, prywatne trasowanie używa Twoich serwerów SMP, aby dostarczyć wiadomości."; +/* No comment provided by engineer. */ +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Aby chronić prywatność, zamiast identyfikatorów użytkowników używanych przez wszystkie inne platformy, SimpleX ma identyfikatory dla kolejek wiadomości, oddzielne dla każdego z Twoich kontaktów."; + /* No comment provided by engineer. */ "To record speech please grant permission to use Microphone." = "Aby nagrać rozmowę, proszę zezwolić na użycie Mikrofonu."; @@ -4994,9 +4931,6 @@ /* No comment provided by engineer. */ "when IP hidden" = "gdy IP ukryty"; -/* No comment provided by engineer. */ -"When people request to connect, you can accept or reject it." = "Kiedy ludzie proszą o połączenie, możesz je zaakceptować lub odrzucić."; - /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Gdy udostępnisz komuś profil incognito, będzie on używany w grupach, do których Cię zaprosi."; @@ -5138,9 +5072,6 @@ /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "Możesz udostępnić ten adres Twoim kontaktom, aby umożliwić im połączenie z **%@**."; -/* No comment provided by engineer. */ -"You can share your address as a link or QR code - anybody can connect to you." = "Możesz udostępnić swój adres jako link lub jako kod QR - każdy będzie mógł się z Tobą połączyć."; - /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "Możesz rozpocząć czat poprzez Ustawienia aplikacji / Baza danych lub poprzez ponowne uruchomienie aplikacji"; @@ -5153,7 +5084,7 @@ /* No comment provided by engineer. */ "You can use markdown to format messages:" = "Możesz używać markdown do formatowania wiadomości:"; -/* No comment provided by engineer. */ +/* alert message */ "You can view invitation link again in connection details." = "Możesz zobaczyć link zaproszenia ponownie w szczegółach połączenia."; /* No comment provided by engineer. */ @@ -5174,6 +5105,9 @@ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Nie można zweryfikować użytkownika; proszę spróbować ponownie."; +/* No comment provided by engineer. */ +"You decide who can connect." = "Ty decydujesz, kto może się połączyć."; + /* No comment provided by engineer. */ "You have already requested connection via this address!" = "Już prosiłeś o połączenie na ten adres!"; @@ -5264,9 +5198,6 @@ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Używasz profilu incognito dla tej grupy - aby zapobiec udostępnianiu głównego profilu zapraszanie kontaktów jest zabronione"; -/* No comment provided by engineer. */ -"Your %@ servers" = "Twoje serwery %@"; - /* No comment provided by engineer. */ "Your calls" = "Twoje połączenia"; @@ -5330,9 +5261,6 @@ /* No comment provided by engineer. */ "Your random profile" = "Twój losowy profil"; -/* No comment provided by engineer. */ -"Your server" = "Twój serwer"; - /* No comment provided by engineer. */ "Your server address" = "Twój adres serwera"; @@ -5345,6 +5273,3 @@ /* No comment provided by engineer. */ "Your SMP servers" = "Twoje serwery SMP"; -/* No comment provided by engineer. */ -"Your XFTP servers" = "Twoje serwery XFTP"; - diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 272484ac47..536bbb62a8 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -1,15 +1,6 @@ /* No comment provided by engineer. */ "\n" = "\n"; -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - /* No comment provided by engineer. */ " (" = " ("; @@ -337,12 +328,6 @@ /* No comment provided by engineer. */ "Abort changing address?" = "Прекратить изменение адреса?"; -/* No comment provided by engineer. */ -"About SimpleX" = "О SimpleX"; - -/* No comment provided by engineer. */ -"About SimpleX address" = "Об адресе SimpleX"; - /* No comment provided by engineer. */ "About SimpleX Chat" = "Информация о SimpleX Chat"; @@ -382,12 +367,6 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Добавьте адрес в свой профиль, чтобы Ваши контакты могли поделиться им. Профиль будет отправлен Вашим контактам."; -/* No comment provided by engineer. */ -"Add contact" = "Добавить контакт"; - -/* No comment provided by engineer. */ -"Add preset servers" = "Добавить серверы по умолчанию"; - /* No comment provided by engineer. */ "Add profile" = "Добавить профиль"; @@ -460,6 +439,9 @@ /* feature role */ "all members" = "все члены"; +/* No comment provided by engineer. */ +"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Все сообщения и файлы отправляются с **end-to-end шифрованием**, с постквантовой безопасностью в прямых разговорах."; + /* No comment provided by engineer. */ "All messages will be deleted - this cannot be undone!" = "Все сообщения будут удалены - это нельзя отменить!"; @@ -574,6 +556,9 @@ /* No comment provided by engineer. */ "Answer call" = "Принять звонок"; +/* No comment provided by engineer. */ +"Anybody can host servers." = "Кто угодно может запустить сервер."; + /* No comment provided by engineer. */ "App build: %@" = "Сборка приложения: %@"; @@ -817,7 +802,8 @@ /* No comment provided by engineer. */ "Can't message member" = "Не удается написать члену группы"; -/* alert button */ +/* alert action + alert button */ "Cancel" = "Отменить"; /* No comment provided by engineer. */ @@ -938,7 +924,7 @@ /* No comment provided by engineer. */ "Chats" = "Чаты"; -/* No comment provided by engineer. */ +/* alert title */ "Check server address and try again." = "Проверьте адрес сервера и попробуйте снова."; /* No comment provided by engineer. */ @@ -1001,9 +987,6 @@ /* No comment provided by engineer. */ "Configure ICE servers" = "Настройка ICE серверов"; -/* No comment provided by engineer. */ -"Configured %@ servers" = "Настроенные %@ серверы"; - /* No comment provided by engineer. */ "Confirm" = "Подтвердить"; @@ -1232,9 +1215,6 @@ /* No comment provided by engineer. */ "Create a group using a random profile." = "Создайте группу, используя случайный профиль."; -/* No comment provided by engineer. */ -"Create an address to let people connect with you." = "Создайте адрес, чтобы можно было соединиться с вами."; - /* server test step */ "Create file" = "Создание файла"; @@ -1397,7 +1377,8 @@ /* No comment provided by engineer. */ "default (yes)" = "по умолчанию (да)"; -/* chat item action +/* alert action + chat item action swipe action */ "Delete" = "Удалить"; @@ -1996,9 +1977,6 @@ /* No comment provided by engineer. */ "Error joining group" = "Ошибка при вступлении в группу"; -/* No comment provided by engineer. */ -"Error loading %@ servers" = "Ошибка загрузки %@ серверов"; - /* No comment provided by engineer. */ "Error migrating settings" = "Ошибка миграции настроек"; @@ -2020,9 +1998,6 @@ /* No comment provided by engineer. */ "Error resetting statistics" = "Ошибка сброса статистики"; -/* No comment provided by engineer. */ -"Error saving %@ servers" = "Ошибка при сохранении %@ серверов"; - /* No comment provided by engineer. */ "Error saving group profile" = "Ошибка при сохранении профиля группы"; @@ -2425,9 +2400,6 @@ /* time unit */ "hours" = "часов"; -/* No comment provided by engineer. */ -"How it works" = "Как это работает"; - /* No comment provided by engineer. */ "How SimpleX works" = "Как SimpleX работает"; @@ -2570,10 +2542,10 @@ "Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "[SimpleX Chat для терминала](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"Instant push notifications will be hidden!\n" = "Мгновенные уведомления будут скрыты!\n"; +"Instant" = "Мгновенно"; /* No comment provided by engineer. */ -"Instant" = "Мгновенно"; +"Instant push notifications will be hidden!\n" = "Мгновенные уведомления будут скрыты!\n"; /* No comment provided by engineer. */ "Interface" = "Интерфейс"; @@ -2611,7 +2583,7 @@ /* No comment provided by engineer. */ "Invalid response" = "Ошибка ответа"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid server address!" = "Ошибка в адресе сервера!"; /* item status text */ @@ -2716,7 +2688,7 @@ /* No comment provided by engineer. */ "Joining group" = "Вступление в группу"; -/* No comment provided by engineer. */ +/* alert action */ "Keep" = "Оставить"; /* No comment provided by engineer. */ @@ -2725,7 +2697,7 @@ /* No comment provided by engineer. */ "Keep the app open to use it from desktop" = "Оставьте приложение открытым, чтобы использовать его с компьютера"; -/* No comment provided by engineer. */ +/* alert title */ "Keep unused invitation?" = "Оставить неиспользованное приглашение?"; /* No comment provided by engineer. */ @@ -2782,9 +2754,6 @@ /* No comment provided by engineer. */ "Live messages" = "\"Живые\" сообщения"; -/* No comment provided by engineer. */ -"No push server" = "Локальные"; - /* No comment provided by engineer. */ "Local name" = "Локальное имя"; @@ -2797,24 +2766,15 @@ /* No comment provided by engineer. */ "Lock mode" = "Режим блокировки"; -/* No comment provided by engineer. */ -"Make a private connection" = "Добавьте контакт"; - /* No comment provided by engineer. */ "Make one message disappear" = "Одно исчезающее сообщение"; /* No comment provided by engineer. */ "Make profile private!" = "Сделайте профиль скрытым!"; -/* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Пожалуйста, проверьте, что адреса %@ серверов имеют правильный формат, каждый адрес на отдельной строке и не повторяется (%@)."; - /* No comment provided by engineer. */ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Пожалуйста, проверьте, что адреса WebRTC ICE серверов имеют правильный формат, каждый адрес на отдельной строке и не повторяется."; -/* No comment provided by engineer. */ -"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Много пользователей спросили: *как SimpleX доставляет сообщения без идентификаторов пользователей?*"; - /* No comment provided by engineer. */ "Mark deleted for everyone" = "Пометить как удаленное для всех"; @@ -3154,12 +3114,18 @@ /* No comment provided by engineer. */ "No permission to record voice message" = "Нет разрешения для записи голосового сообщения"; +/* No comment provided by engineer. */ +"No push server" = "Без сервера нотификаций"; + /* No comment provided by engineer. */ "No received or sent files" = "Нет полученных или отправленных файлов"; /* copied message info in history */ "no text" = "нет текста"; +/* No comment provided by engineer. */ +"No user identifiers." = "Без идентификаторов пользователей."; + /* No comment provided by engineer. */ "Not compatible!" = "Несовместимая версия!"; @@ -3225,9 +3191,6 @@ /* No comment provided by engineer. */ "Only client devices store user profiles, contacts, groups, and messages." = "Только пользовательские устройства хранят контакты, группы и сообщения."; -/* No comment provided by engineer. */ -"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Все сообщения и файлы отправляются с **end-to-end шифрованием**, с постквантовой безопасностью в прямых разговорах."; - /* No comment provided by engineer. */ "Only delete conversation" = "Удалить только разговор"; @@ -3285,18 +3248,9 @@ /* authentication reason */ "Open migration to another device" = "Открытие миграции на другое устройство"; -/* No comment provided by engineer. */ -"Open server settings" = "Открыть настройки серверов"; - /* No comment provided by engineer. */ "Open Settings" = "Открыть Настройки"; -/* authentication reason */ -"Open user profiles" = "Открыть профили пользователя"; - -/* No comment provided by engineer. */ -"Anybody can host servers." = "Кто угодно может запустить сервер."; - /* No comment provided by engineer. */ "Opening app…" = "Приложение отрывается…"; @@ -3318,9 +3272,6 @@ /* No comment provided by engineer. */ "Other" = "Другaя сеть"; -/* No comment provided by engineer. */ -"Other %@ servers" = "Другие %@ серверы"; - /* No comment provided by engineer. */ "other errors" = "другие ошибки"; @@ -3375,9 +3326,6 @@ /* No comment provided by engineer. */ "Pending" = "В ожидании"; -/* No comment provided by engineer. */ -"You decide who can connect." = "Вы определяете, кто может соединиться."; - /* No comment provided by engineer. */ "Periodic" = "Периодически"; @@ -3456,9 +3404,6 @@ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Сохранить последний черновик, вместе с вложениями."; -/* No comment provided by engineer. */ -"Preset server" = "Сервер по умолчанию"; - /* No comment provided by engineer. */ "Preset server address" = "Адрес сервера по умолчанию"; @@ -3507,7 +3452,7 @@ /* No comment provided by engineer. */ "Profile theme" = "Тема профиля"; -/* No comment provided by engineer. */ +/* alert message */ "Profile update will be sent to your contacts." = "Обновлённый профиль будет отправлен Вашим контактам."; /* No comment provided by engineer. */ @@ -3592,10 +3537,10 @@ "Read more" = "Узнать больше"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Узнать больше в [Руководстве пользователя](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Дополнительная информация в [Руководстве пользователя](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Дополнительная информация в [Руководстве пользователя](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Узнать больше в [Руководстве пользователя](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Узнать больше в [Руководстве пользователя](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; @@ -3603,9 +3548,6 @@ /* No comment provided by engineer. */ "Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Узнайте больше из нашего [GitHub репозитория](https://github.com/simplex-chat/simplex-chat#readme)."; -/* No comment provided by engineer. */ -"Read more in our GitHub repository." = "Узнайте больше из нашего GitHub репозитория."; - /* No comment provided by engineer. */ "Receipts are disabled" = "Отчёты о доставке выключены"; @@ -3878,7 +3820,7 @@ /* No comment provided by engineer. */ "Save servers" = "Сохранить серверы"; -/* No comment provided by engineer. */ +/* alert title */ "Save servers?" = "Сохранить серверы?"; /* No comment provided by engineer. */ @@ -4031,9 +3973,6 @@ /* No comment provided by engineer. */ "Send notifications" = "Отправлять уведомления"; -/* No comment provided by engineer. */ -"Send notifications:" = "Отправлять уведомления:"; - /* No comment provided by engineer. */ "Send questions and ideas" = "Отправьте вопросы и идеи"; @@ -4196,7 +4135,8 @@ /* No comment provided by engineer. */ "Shape profile images" = "Форма картинок профилей"; -/* chat item action */ +/* alert action + chat item action */ "Share" = "Поделиться"; /* No comment provided by engineer. */ @@ -4205,7 +4145,7 @@ /* No comment provided by engineer. */ "Share address" = "Поделиться адресом"; -/* No comment provided by engineer. */ +/* alert title */ "Share address with contacts?" = "Поделиться адресом с контактами?"; /* No comment provided by engineer. */ @@ -4388,10 +4328,10 @@ /* No comment provided by engineer. */ "Stop sending file?" = "Остановить отправку файла?"; -/* No comment provided by engineer. */ +/* alert action */ "Stop sharing" = "Прекратить делиться"; -/* No comment provided by engineer. */ +/* alert title */ "Stop sharing address?" = "Прекратить делиться адресом?"; /* authentication reason */ @@ -4487,7 +4427,7 @@ /* No comment provided by engineer. */ "Test servers" = "Тестировать серверы"; -/* No comment provided by engineer. */ +/* alert title */ "Tests failed!" = "Ошибка тестов!"; /* No comment provided by engineer. */ @@ -4499,9 +4439,6 @@ /* No comment provided by engineer. */ "Thanks to the users – contribute via Weblate!" = "Благодаря пользователям – добавьте переводы через Weblate!"; -/* No comment provided by engineer. */ -"No user identifiers." = "Без идентификаторов пользователей."; - /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Приложение может посылать Вам уведомления о сообщениях и запросах на соединение - уведомления можно включить в Настройках."; @@ -4526,6 +4463,9 @@ /* 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 future of messaging" = "Будущее коммуникаций"; + /* No comment provided by engineer. */ "The hash of the previous message is different." = "Хэш предыдущего сообщения отличается."; @@ -4544,9 +4484,6 @@ /* No comment provided by engineer. */ "The messages will be marked as moderated for all members." = "Сообщения будут помечены как удаленные для всех членов группы."; -/* No comment provided by engineer. */ -"The future of messaging" = "Будущее коммуникаций"; - /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Предыдущая версия данных чата не удалена при перемещении, её можно удалить."; @@ -4634,9 +4571,6 @@ /* No comment provided by engineer. */ "To make a new connection" = "Чтобы соединиться"; -/* No comment provided by engineer. */ -"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Чтобы защитить Вашу конфиденциальность, SimpleX использует разные идентификаторы для каждого Вашeго контакта."; - /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Чтобы защитить Ваш часовой пояс, файлы картинок и голосовых сообщений используют UTC."; @@ -4646,6 +4580,9 @@ /* No comment provided by engineer. */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "Чтобы защитить ваш IP адрес, приложение использует Ваши SMP серверы для конфиденциальной доставки сообщений."; +/* No comment provided by engineer. */ +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Чтобы защитить Вашу конфиденциальность, SimpleX использует разные идентификаторы для каждого Вашeго контакта."; + /* No comment provided by engineer. */ "To record speech please grant permission to use Microphone." = "Для записи речи, пожалуйста, дайте разрешение на использование микрофона."; @@ -5033,9 +4970,6 @@ /* No comment provided by engineer. */ "when IP hidden" = "когда IP защищен"; -/* No comment provided by engineer. */ -"When people request to connect, you can accept or reject it." = "Когда Вы получите запрос на соединение, Вы можете принять или отклонить его."; - /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Когда Вы соединены с контактом инкогнито, тот же самый инкогнито профиль будет использоваться для групп с этим контактом."; @@ -5177,9 +5111,6 @@ /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "Вы можете поделиться этим адресом с Вашими контактами, чтобы они могли соединиться с **%@**."; -/* No comment provided by engineer. */ -"You can share your address as a link or QR code - anybody can connect to you." = "Вы можете использовать Ваш адрес как ссылку или как QR код - кто угодно сможет соединиться с Вами."; - /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "Вы можете запустить чат через Настройки приложения или перезапустив приложение."; @@ -5192,7 +5123,7 @@ /* No comment provided by engineer. */ "You can use markdown to format messages:" = "Вы можете форматировать сообщения:"; -/* No comment provided by engineer. */ +/* alert message */ "You can view invitation link again in connection details." = "Вы можете увидеть ссылку-приглашение снова открыв соединение."; /* No comment provided by engineer. */ @@ -5213,6 +5144,9 @@ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Верификация не удалась; пожалуйста, попробуйте ещё раз."; +/* No comment provided by engineer. */ +"You decide who can connect." = "Вы определяете, кто может соединиться."; + /* No comment provided by engineer. */ "You have already requested connection via this address!" = "Вы уже запросили соединение через этот адрес!"; @@ -5303,9 +5237,6 @@ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Вы используете инкогнито профиль для этой группы - чтобы предотвратить раскрытие Вашего основного профиля, приглашать контакты не разрешено"; -/* No comment provided by engineer. */ -"Your %@ servers" = "Ваши %@ серверы"; - /* No comment provided by engineer. */ "Your calls" = "Ваши звонки"; @@ -5369,9 +5300,6 @@ /* No comment provided by engineer. */ "Your random profile" = "Случайный профиль"; -/* No comment provided by engineer. */ -"Your server" = "Ваш сервер"; - /* No comment provided by engineer. */ "Your server address" = "Адрес Вашего сервера"; @@ -5384,6 +5312,3 @@ /* No comment provided by engineer. */ "Your SMP servers" = "Ваши SMP серверы"; -/* No comment provided by engineer. */ -"Your XFTP servers" = "Ваши XFTP серверы"; - diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index 85295df87d..b50986cf17 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -1,15 +1,6 @@ /* No comment provided by engineer. */ "\n" = "\n"; -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - /* No comment provided by engineer. */ " (" = " ("; @@ -241,12 +232,6 @@ /* No comment provided by engineer. */ "Abort changing address?" = "ยกเลิกการเปลี่ยนที่อยู่?"; -/* No comment provided by engineer. */ -"About SimpleX" = "เกี่ยวกับ SimpleX"; - -/* No comment provided by engineer. */ -"About SimpleX address" = "เกี่ยวกับที่อยู่ SimpleX"; - /* No comment provided by engineer. */ "About SimpleX Chat" = "เกี่ยวกับ SimpleX Chat"; @@ -271,9 +256,6 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "เพิ่มที่อยู่ลงในโปรไฟล์ของคุณ เพื่อให้ผู้ติดต่อของคุณสามารถแชร์กับผู้อื่นได้ การอัปเดตโปรไฟล์จะถูกส่งไปยังผู้ติดต่อของคุณ"; -/* No comment provided by engineer. */ -"Add preset servers" = "เพิ่มเซิร์ฟเวอร์ที่ตั้งไว้ล่วงหน้า"; - /* No comment provided by engineer. */ "Add profile" = "เพิ่มโปรไฟล์"; @@ -400,6 +382,9 @@ /* No comment provided by engineer. */ "Answer call" = "รับสาย"; +/* No comment provided by engineer. */ +"Anybody can host servers." = "โปรโตคอลและโค้ดโอเพ่นซอร์ส – ใคร ๆ ก็สามารถเปิดใช้เซิร์ฟเวอร์ได้"; + /* No comment provided by engineer. */ "App build: %@" = "รุ่นแอป: %@"; @@ -520,7 +505,8 @@ /* No comment provided by engineer. */ "Can't invite contacts!" = "ไม่สามารถเชิญผู้ติดต่อได้!"; -/* alert button */ +/* alert action + alert button */ "Cancel" = "ยกเลิก"; /* feature offered item */ @@ -608,7 +594,7 @@ /* No comment provided by engineer. */ "Chats" = "แชท"; -/* No comment provided by engineer. */ +/* alert title */ "Check server address and try again." = "ตรวจสอบที่อยู่เซิร์ฟเวอร์แล้วลองอีกครั้ง"; /* No comment provided by engineer. */ @@ -764,9 +750,6 @@ /* No comment provided by engineer. */ "Create" = "สร้าง"; -/* No comment provided by engineer. */ -"Create an address to let people connect with you." = "สร้างที่อยู่เพื่อให้ผู้อื่นเชื่อมต่อกับคุณ"; - /* server test step */ "Create file" = "สร้างไฟล์"; @@ -887,7 +870,8 @@ /* No comment provided by engineer. */ "default (yes)" = "ค่าเริ่มต้น (ใช่)"; -/* chat item action +/* alert action + chat item action swipe action */ "Delete" = "ลบ"; @@ -1305,18 +1289,12 @@ /* No comment provided by engineer. */ "Error joining group" = "เกิดข้อผิดพลาดในการเข้าร่วมกลุ่ม"; -/* No comment provided by engineer. */ -"Error loading %@ servers" = "โหลดเซิร์ฟเวอร์ %@ ผิดพลาด"; - /* alert title */ "Error receiving file" = "เกิดข้อผิดพลาดในการรับไฟล์"; /* No comment provided by engineer. */ "Error removing member" = "เกิดข้อผิดพลาดในการลบสมาชิก"; -/* No comment provided by engineer. */ -"Error saving %@ servers" = "เกิดข้อผิดพลาดในการบันทึกเซิร์ฟเวอร์ %@"; - /* No comment provided by engineer. */ "Error saving group profile" = "เกิดข้อผิดพลาดในการบันทึกโปรไฟล์กลุ่ม"; @@ -1581,9 +1559,6 @@ /* time unit */ "hours" = "ชั่วโมง"; -/* No comment provided by engineer. */ -"How it works" = "มันทำงานอย่างไร"; - /* No comment provided by engineer. */ "How SimpleX works" = "วิธีการ SimpleX ทํางานอย่างไร"; @@ -1690,10 +1665,10 @@ "Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "ติดตั้ง [SimpleX Chat สำหรับเทอร์มินัล](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"Instant push notifications will be hidden!\n" = "การแจ้งเตือนโดยทันทีจะถูกซ่อน!\n"; +"Instant" = "ทันที"; /* No comment provided by engineer. */ -"Instant" = "ทันที"; +"Instant push notifications will be hidden!\n" = "การแจ้งเตือนโดยทันทีจะถูกซ่อน!\n"; /* No comment provided by engineer. */ "Interface" = "อินเตอร์เฟซ"; @@ -1710,7 +1685,7 @@ /* invalid chat item */ "invalid data" = "ข้อมูลไม่ถูกต้อง"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid server address!" = "ที่อยู่เซิร์ฟเวอร์ไม่ถูกต้อง!"; /* No comment provided by engineer. */ @@ -1836,9 +1811,6 @@ /* No comment provided by engineer. */ "Live messages" = "ข้อความสด"; -/* No comment provided by engineer. */ -"No push server" = "ในเครื่อง"; - /* No comment provided by engineer. */ "Local name" = "ชื่อภายในเครื่องเท่านั้น"; @@ -1851,24 +1823,15 @@ /* No comment provided by engineer. */ "Lock mode" = "โหมดล็อค"; -/* No comment provided by engineer. */ -"Make a private connection" = "สร้างการเชื่อมต่อแบบส่วนตัว"; - /* No comment provided by engineer. */ "Make one message disappear" = "ทำให้ข้อความหายไปหนึ่งข้อความ"; /* No comment provided by engineer. */ "Make profile private!" = "ทำให้โปรไฟล์เป็นส่วนตัว!"; -/* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "ตรวจสอบให้แน่ใจว่าที่อยู่เซิร์ฟเวอร์ %@ อยู่ในรูปแบบที่ถูกต้อง แยกบรรทัดและไม่ซ้ำกัน (%@)"; - /* No comment provided by engineer. */ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "ตรวจสอบให้แน่ใจว่าที่อยู่เซิร์ฟเวอร์ WebRTC ICE อยู่ในรูปแบบที่ถูกต้อง แยกบรรทัดและไม่ซ้ำกัน"; -/* No comment provided by engineer. */ -"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "หลายคนถามว่า: *หาก SimpleX ไม่มีตัวระบุผู้ใช้ จะส่งข้อความได้อย่างไร?*"; - /* No comment provided by engineer. */ "Mark deleted for everyone" = "ทำเครื่องหมายว่าลบแล้วสำหรับทุกคน"; @@ -2064,12 +2027,18 @@ /* No comment provided by engineer. */ "No permission to record voice message" = "ไม่อนุญาตให้บันทึกข้อความเสียง"; +/* No comment provided by engineer. */ +"No push server" = "ในเครื่อง"; + /* No comment provided by engineer. */ "No received or sent files" = "ไม่มีไฟล์ที่ได้รับหรือส่ง"; /* copied message info in history */ "no text" = "ไม่มีข้อความ"; +/* No comment provided by engineer. */ +"No user identifiers." = "แพลตฟอร์มแรกที่ไม่มีตัวระบุผู้ใช้ - ถูกออกแบบให้เป็นส่วนตัว"; + /* No comment provided by engineer. */ "Notifications" = "การแจ้งเตือน"; @@ -2171,12 +2140,6 @@ /* No comment provided by engineer. */ "Open Settings" = "เปิดการตั้งค่า"; -/* authentication reason */ -"Open user profiles" = "เปิดโปรไฟล์ผู้ใช้"; - -/* No comment provided by engineer. */ -"Anybody can host servers." = "โปรโตคอลและโค้ดโอเพ่นซอร์ส – ใคร ๆ ก็สามารถเปิดใช้เซิร์ฟเวอร์ได้"; - /* member role */ "owner" = "เจ้าของ"; @@ -2204,9 +2167,6 @@ /* No comment provided by engineer. */ "peer-to-peer" = "เพื่อนต่อเพื่อน"; -/* No comment provided by engineer. */ -"You decide who can connect." = "ผู้คนสามารถเชื่อมต่อกับคุณผ่านลิงก์ที่คุณแบ่งปันเท่านั้น"; - /* No comment provided by engineer. */ "Periodic" = "เป็นระยะๆ"; @@ -2264,9 +2224,6 @@ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "เก็บข้อความที่ร่างไว้ล่าสุดพร้อมไฟล์แนบ"; -/* No comment provided by engineer. */ -"Preset server" = "เซิร์ฟเวอร์ที่ตั้งไว้ล่วงหน้า"; - /* No comment provided by engineer. */ "Preset server address" = "ที่อยู่เซิร์ฟเวอร์ที่ตั้งไว้ล่วงหน้า"; @@ -2291,7 +2248,7 @@ /* No comment provided by engineer. */ "Profile password" = "รหัสผ่านโปรไฟล์"; -/* No comment provided by engineer. */ +/* alert message */ "Profile update will be sent to your contacts." = "การอัปเดตโปรไฟล์จะถูกส่งไปยังผู้ติดต่อของคุณ"; /* No comment provided by engineer. */ @@ -2354,9 +2311,6 @@ /* No comment provided by engineer. */ "Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "อ่านเพิ่มเติมใน[พื้นที่เก็บข้อมูล GitHub](https://github.com/simplex-chat/simplex-chat#readme)"; -/* No comment provided by engineer. */ -"Read more in our GitHub repository." = "อ่านเพิ่มเติมในที่เก็บ GitHub ของเรา"; - /* No comment provided by engineer. */ "received answer…" = "ได้รับคำตอบ…"; @@ -2536,7 +2490,7 @@ /* No comment provided by engineer. */ "Save servers" = "บันทึกเซิร์ฟเวอร์"; -/* No comment provided by engineer. */ +/* alert title */ "Save servers?" = "บันทึกเซิร์ฟเวอร์?"; /* No comment provided by engineer. */ @@ -2617,9 +2571,6 @@ /* No comment provided by engineer. */ "Send notifications" = "ส่งการแจ้งเตือน"; -/* No comment provided by engineer. */ -"Send notifications:" = "ส่งการแจ้งเตือน:"; - /* No comment provided by engineer. */ "Send questions and ideas" = "ส่งคําถามและความคิด"; @@ -2707,7 +2658,8 @@ /* No comment provided by engineer. */ "Settings" = "การตั้งค่า"; -/* chat item action */ +/* alert action + chat item action */ "Share" = "แชร์"; /* No comment provided by engineer. */ @@ -2716,7 +2668,7 @@ /* No comment provided by engineer. */ "Share address" = "แชร์ที่อยู่"; -/* No comment provided by engineer. */ +/* alert title */ "Share address with contacts?" = "แชร์ที่อยู่กับผู้ติดต่อ?"; /* No comment provided by engineer. */ @@ -2815,10 +2767,10 @@ /* No comment provided by engineer. */ "Stop sending file?" = "หยุดส่งไฟล์ไหม?"; -/* No comment provided by engineer. */ +/* alert action */ "Stop sharing" = "หยุดแชร์"; -/* No comment provided by engineer. */ +/* alert title */ "Stop sharing address?" = "หยุดแชร์ที่อยู่ไหม?"; /* authentication reason */ @@ -2875,7 +2827,7 @@ /* No comment provided by engineer. */ "Test servers" = "เซิร์ฟเวอร์ทดสอบ"; -/* No comment provided by engineer. */ +/* alert title */ "Tests failed!" = "การทดสอบล้มเหลว!"; /* No comment provided by engineer. */ @@ -2887,9 +2839,6 @@ /* No comment provided by engineer. */ "Thanks to the users – contribute via Weblate!" = "ขอบคุณผู้ใช้ – มีส่วนร่วมผ่าน Weblate!"; -/* No comment provided by engineer. */ -"No user identifiers." = "แพลตฟอร์มแรกที่ไม่มีตัวระบุผู้ใช้ - ถูกออกแบบให้เป็นส่วนตัว"; - /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "แอปสามารถแจ้งให้คุณทราบเมื่อคุณได้รับข้อความหรือคำขอติดต่อ - โปรดเปิดการตั้งค่าเพื่อเปิดใช้งาน"; @@ -2908,6 +2857,9 @@ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "encryption กำลังทำงานและไม่จำเป็นต้องใช้ข้อตกลง encryption ใหม่ อาจทำให้การเชื่อมต่อผิดพลาดได้!"; +/* No comment provided by engineer. */ +"The future of messaging" = "การส่งข้อความส่วนตัวรุ่นต่อไป"; + /* No comment provided by engineer. */ "The hash of the previous message is different." = "แฮชของข้อความก่อนหน้านี้แตกต่างกัน"; @@ -2920,9 +2872,6 @@ /* No comment provided by engineer. */ "The message will be marked as moderated for all members." = "ข้อความจะถูกทำเครื่องหมายว่ากลั่นกรองสำหรับสมาชิกทุกคน"; -/* No comment provided by engineer. */ -"The future of messaging" = "การส่งข้อความส่วนตัวรุ่นต่อไป"; - /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "ฐานข้อมูลเก่าไม่ได้ถูกลบในระหว่างการย้ายข้อมูล แต่สามารถลบได้"; @@ -2968,15 +2917,15 @@ /* No comment provided by engineer. */ "To make a new connection" = "เพื่อสร้างการเชื่อมต่อใหม่"; -/* No comment provided by engineer. */ -"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "เพื่อปกป้องความเป็นส่วนตัว แทนที่จะใช้ ID ผู้ใช้เหมือนที่แพลตฟอร์มอื่นๆใช้ SimpleX มีตัวระบุสำหรับคิวข้อความ โดยแยกจากกันสำหรับผู้ติดต่อแต่ละราย"; - /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "ไฟล์ภาพ/เสียงใช้ UTC เพื่อป้องกันเขตเวลา"; /* No comment provided by engineer. */ "To protect your information, turn on SimpleX Lock.\nYou will be prompted to complete authentication before this feature is enabled." = "เพื่อปกป้องข้อมูลของคุณ ให้เปิด SimpleX Lock\nคุณจะได้รับแจ้งให้ยืนยันตัวตนให้เสร็จสมบูรณ์ก่อนที่จะเปิดใช้งานคุณลักษณะนี้"; +/* No comment provided by engineer. */ +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "เพื่อปกป้องความเป็นส่วนตัว แทนที่จะใช้ ID ผู้ใช้เหมือนที่แพลตฟอร์มอื่นๆใช้ SimpleX มีตัวระบุสำหรับคิวข้อความ โดยแยกจากกันสำหรับผู้ติดต่อแต่ละราย"; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "ในการบันทึกข้อความเสียง โปรดให้สิทธิ์ในการใช้ไมโครโฟน"; @@ -3193,9 +3142,6 @@ /* No comment provided by engineer. */ "When available" = "เมื่อพร้อมใช้งาน"; -/* No comment provided by engineer. */ -"When people request to connect, you can accept or reject it." = "เมื่อมีคนขอเชื่อมต่อ คุณสามารถยอมรับหรือปฏิเสธได้"; - /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "เมื่อคุณแชร์โปรไฟล์ที่ไม่ระบุตัวตนกับใครสักคน โปรไฟล์นี้จะใช้สำหรับกลุ่มที่พวกเขาเชิญคุณ"; @@ -3262,9 +3208,6 @@ /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "คุณสามารถแบ่งปันที่อยู่นี้กับผู้ติดต่อของคุณเพื่อให้พวกเขาเชื่อมต่อกับ **%@**"; -/* No comment provided by engineer. */ -"You can share your address as a link or QR code - anybody can connect to you." = "คุณสามารถแชร์ที่อยู่ของคุณเป็นลิงก์หรือรหัสคิวอาร์ - ใคร ๆ ก็สามารถเชื่อมต่อกับคุณได้"; - /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "คุณสามารถเริ่มแชทผ่านการตั้งค่าแอป / ฐานข้อมูล หรือโดยการรีสตาร์ทแอป"; @@ -3292,6 +3235,9 @@ /* No comment provided by engineer. */ "You could not be verified; please try again." = "เราไม่สามารถตรวจสอบคุณได้ กรุณาลองอีกครั้ง."; +/* No comment provided by engineer. */ +"You decide who can connect." = "ผู้คนสามารถเชื่อมต่อกับคุณผ่านลิงก์ที่คุณแบ่งปันเท่านั้น"; + /* No comment provided by engineer. */ "You have to enter passphrase every time the app starts - it is not stored on the device." = "คุณต้องใส่รหัสผ่านทุกครั้งที่เริ่มแอป - รหัสผ่านไม่ได้จัดเก็บไว้ในอุปกรณ์"; @@ -3355,9 +3301,6 @@ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "คุณกำลังใช้โปรไฟล์ที่ไม่ระบุตัวตนสำหรับกลุ่มนี้ - ไม่อนุญาตให้เชิญผู้ติดต่อเพื่อป้องกันการแชร์โปรไฟล์หลักของคุณ"; -/* No comment provided by engineer. */ -"Your %@ servers" = "เซิร์ฟเวอร์ %@ ของคุณ"; - /* No comment provided by engineer. */ "Your calls" = "การโทรของคุณ"; @@ -3403,9 +3346,6 @@ /* No comment provided by engineer. */ "Your random profile" = "โปรไฟล์แบบสุ่มของคุณ"; -/* No comment provided by engineer. */ -"Your server" = "เซิร์ฟเวอร์ของคุณ"; - /* No comment provided by engineer. */ "Your server address" = "ที่อยู่เซิร์ฟเวอร์ของคุณ"; @@ -3418,6 +3358,3 @@ /* No comment provided by engineer. */ "Your SMP servers" = "เซิร์ฟเวอร์ SMP ของคุณ"; -/* No comment provided by engineer. */ -"Your XFTP servers" = "เซิร์ฟเวอร์ XFTP ของคุณ"; - diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index bbaa1a5657..1583f01cda 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -1,15 +1,6 @@ /* No comment provided by engineer. */ "\n" = "\n"; -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - /* No comment provided by engineer. */ " (" = " ("; @@ -337,12 +328,6 @@ /* No comment provided by engineer. */ "Abort changing address?" = "Adres değişimi iptal edilsin mi?"; -/* No comment provided by engineer. */ -"About SimpleX" = "SimpleX Hakkında"; - -/* No comment provided by engineer. */ -"About SimpleX address" = "SimpleX Chat adresi hakkında"; - /* No comment provided by engineer. */ "About SimpleX Chat" = "SimpleX Chat hakkında"; @@ -382,12 +367,6 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Kişilerinizin başkalarıyla paylaşabilmesi için profilinize adres ekleyin. Profil güncellemesi kişilerinize gönderilecek."; -/* No comment provided by engineer. */ -"Add contact" = "Kişi ekle"; - -/* No comment provided by engineer. */ -"Add preset servers" = "Önceden ayarlanmış sunucu ekle"; - /* No comment provided by engineer. */ "Add profile" = "Profil ekle"; @@ -574,6 +553,9 @@ /* No comment provided by engineer. */ "Answer call" = "Aramayı cevapla"; +/* No comment provided by engineer. */ +"Anybody can host servers." = "Açık kaynak protokolü ve kodu - herhangi biri sunucuları çalıştırabilir."; + /* No comment provided by engineer. */ "App build: %@" = "Uygulama sürümü: %@"; @@ -817,7 +799,8 @@ /* No comment provided by engineer. */ "Can't message member" = "Üyeye mesaj gönderilemiyor"; -/* alert button */ +/* alert action + alert button */ "Cancel" = "İptal et"; /* No comment provided by engineer. */ @@ -938,7 +921,7 @@ /* No comment provided by engineer. */ "Chats" = "Sohbetler"; -/* No comment provided by engineer. */ +/* alert title */ "Check server address and try again." = "Sunucu adresini kontrol edip tekrar deneyin."; /* No comment provided by engineer. */ @@ -1001,9 +984,6 @@ /* No comment provided by engineer. */ "Configure ICE servers" = "ICE sunucularını ayarla"; -/* No comment provided by engineer. */ -"Configured %@ servers" = "Yapılandırılmış %@ sunucuları"; - /* No comment provided by engineer. */ "Confirm" = "Onayla"; @@ -1232,9 +1212,6 @@ /* No comment provided by engineer. */ "Create a group using a random profile." = "Rasgele profil kullanarak grup oluştur."; -/* No comment provided by engineer. */ -"Create an address to let people connect with you." = "İnsanların seninle bağlanması için bir adres oluştur."; - /* server test step */ "Create file" = "Dosya oluştur"; @@ -1397,7 +1374,8 @@ /* No comment provided by engineer. */ "default (yes)" = "varsayılan (evet)"; -/* chat item action +/* alert action + chat item action swipe action */ "Delete" = "Sil"; @@ -1996,9 +1974,6 @@ /* No comment provided by engineer. */ "Error joining group" = "Gruba katılırken hata oluştu"; -/* No comment provided by engineer. */ -"Error loading %@ servers" = "%@ sunucuları yüklenirken hata oluştu"; - /* No comment provided by engineer. */ "Error migrating settings" = "Ayarlar taşınırken hata oluştu"; @@ -2020,9 +1995,6 @@ /* No comment provided by engineer. */ "Error resetting statistics" = "Hata istatistikler sıfırlanıyor"; -/* No comment provided by engineer. */ -"Error saving %@ servers" = "%@ sunucuları kaydedilirken sorun oluştu"; - /* No comment provided by engineer. */ "Error saving group profile" = "Grup profili kaydedilirken sorun oluştu"; @@ -2425,9 +2397,6 @@ /* time unit */ "hours" = "saat"; -/* No comment provided by engineer. */ -"How it works" = "Nasıl çalışıyor"; - /* No comment provided by engineer. */ "How SimpleX works" = "SimpleX nasıl çalışır"; @@ -2570,10 +2539,10 @@ "Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "[Terminal için SimpleX Chat]i indir(https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"Instant push notifications will be hidden!\n" = "Anlık bildirimler gizlenecek!\n"; +"Instant" = "Anında"; /* No comment provided by engineer. */ -"Instant" = "Anında"; +"Instant push notifications will be hidden!\n" = "Anlık bildirimler gizlenecek!\n"; /* No comment provided by engineer. */ "Interface" = "Arayüz"; @@ -2611,7 +2580,7 @@ /* No comment provided by engineer. */ "Invalid response" = "Geçersiz yanıt"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid server address!" = "Geçersiz sunucu adresi!"; /* item status text */ @@ -2716,7 +2685,7 @@ /* No comment provided by engineer. */ "Joining group" = "Gruba katılınıyor"; -/* No comment provided by engineer. */ +/* alert action */ "Keep" = "Tut"; /* No comment provided by engineer. */ @@ -2725,7 +2694,7 @@ /* No comment provided by engineer. */ "Keep the app open to use it from desktop" = "Bilgisayardan kullanmak için uygulamayı açık tut"; -/* No comment provided by engineer. */ +/* alert title */ "Keep unused invitation?" = "Kullanılmamış davet tutulsun mu?"; /* No comment provided by engineer. */ @@ -2782,9 +2751,6 @@ /* No comment provided by engineer. */ "Live messages" = "Canlı mesajlar"; -/* No comment provided by engineer. */ -"No push server" = "Yerel"; - /* No comment provided by engineer. */ "Local name" = "Yerel isim"; @@ -2797,24 +2763,15 @@ /* No comment provided by engineer. */ "Lock mode" = "Kilit modu"; -/* No comment provided by engineer. */ -"Make a private connection" = "Gizli bir bağlantı oluştur"; - /* No comment provided by engineer. */ "Make one message disappear" = "Bir mesajın kaybolmasını sağlayın"; /* No comment provided by engineer. */ "Make profile private!" = "Profili gizli yap!"; -/* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "%@ sunucu adreslerinin doğru formatta olduğundan, satır ayrımı yapıldığından ve yinelenmediğinden (%@) emin olun."; - /* No comment provided by engineer. */ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "WebRTC ICE sunucu adreslerinin doğru formatta olduğundan, satırlara ayrıldığından ve yinelenmediğinden emin olun."; -/* No comment provided by engineer. */ -"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Çoğu kişi sordu: *eğer SimpleX'in hiç kullanıcı tanımlayıcıları yok, o zaman mesajları nasıl gönderebiliyor?*"; - /* No comment provided by engineer. */ "Mark deleted for everyone" = "Herkes için silinmiş olarak işaretle"; @@ -3154,12 +3111,18 @@ /* No comment provided by engineer. */ "No permission to record voice message" = "Sesli mesaj kaydetmek için izin yok"; +/* No comment provided by engineer. */ +"No push server" = "Yerel"; + /* No comment provided by engineer. */ "No received or sent files" = "Hiç alınmış veya gönderilmiş dosya yok"; /* copied message info in history */ "no text" = "metin yok"; +/* No comment provided by engineer. */ +"No user identifiers." = "Herhangi bir kullanıcı tanımlayıcısı yok."; + /* No comment provided by engineer. */ "Not compatible!" = "Uyumlu değil!"; @@ -3282,18 +3245,9 @@ /* authentication reason */ "Open migration to another device" = "Başka bir cihaza açık geçiş"; -/* No comment provided by engineer. */ -"Open server settings" = "Sunucu ayarlarını aç"; - /* No comment provided by engineer. */ "Open Settings" = "Ayarları aç"; -/* authentication reason */ -"Open user profiles" = "Kullanıcı profillerini aç"; - -/* No comment provided by engineer. */ -"Anybody can host servers." = "Açık kaynak protokolü ve kodu - herhangi biri sunucuları çalıştırabilir."; - /* No comment provided by engineer. */ "Opening app…" = "Uygulama açılıyor…"; @@ -3315,9 +3269,6 @@ /* No comment provided by engineer. */ "Other" = "Diğer"; -/* No comment provided by engineer. */ -"Other %@ servers" = "Diğer %@ sunucuları"; - /* No comment provided by engineer. */ "other errors" = "diğer hatalar"; @@ -3372,9 +3323,6 @@ /* No comment provided by engineer. */ "Pending" = "Bekleniyor"; -/* No comment provided by engineer. */ -"You decide who can connect." = "Kimin bağlanabileceğine siz karar verirsiniz."; - /* No comment provided by engineer. */ "Periodic" = "Periyodik olarak"; @@ -3453,9 +3401,6 @@ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Son mesaj taslağını ekleriyle birlikte koru."; -/* No comment provided by engineer. */ -"Preset server" = "Ön ayarlı sunucu"; - /* No comment provided by engineer. */ "Preset server address" = "Ön ayarlı sunucu adresi"; @@ -3504,7 +3449,7 @@ /* No comment provided by engineer. */ "Profile theme" = "Profil teması"; -/* No comment provided by engineer. */ +/* alert message */ "Profile update will be sent to your contacts." = "Profil güncellemesi kişilerinize gönderilecektir."; /* No comment provided by engineer. */ @@ -3589,10 +3534,10 @@ "Read more" = "Dahasını oku"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "[Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "[Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "[Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "[Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "[Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; @@ -3600,9 +3545,6 @@ /* No comment provided by engineer. */ "Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "[GitHub deposu]nda daha fazlasını okuyun(https://github.com/simplex-chat/simplex-chat#readme)."; -/* No comment provided by engineer. */ -"Read more in our GitHub repository." = "Daha fazlasını GitHub depomuzdan oku."; - /* No comment provided by engineer. */ "Receipts are disabled" = "Alıcılar devre dışı bırakıldı"; @@ -3875,7 +3817,7 @@ /* No comment provided by engineer. */ "Save servers" = "Sunucuları kaydet"; -/* No comment provided by engineer. */ +/* alert title */ "Save servers?" = "Sunucular kaydedilsin mi?"; /* No comment provided by engineer. */ @@ -4028,9 +3970,6 @@ /* No comment provided by engineer. */ "Send notifications" = "Bildirimler gönder"; -/* No comment provided by engineer. */ -"Send notifications:" = "Bildirimler gönder:"; - /* No comment provided by engineer. */ "Send questions and ideas" = "Fikirler ve sorular gönderin"; @@ -4193,7 +4132,8 @@ /* No comment provided by engineer. */ "Shape profile images" = "Profil resimlerini şekillendir"; -/* chat item action */ +/* alert action + chat item action */ "Share" = "Paylaş"; /* No comment provided by engineer. */ @@ -4202,7 +4142,7 @@ /* No comment provided by engineer. */ "Share address" = "Adresi paylaş"; -/* No comment provided by engineer. */ +/* alert title */ "Share address with contacts?" = "Kişilerle adres paylaşılsın mı?"; /* No comment provided by engineer. */ @@ -4385,10 +4325,10 @@ /* No comment provided by engineer. */ "Stop sending file?" = "Dosya gönderimi durdurulsun mu?"; -/* No comment provided by engineer. */ +/* alert action */ "Stop sharing" = "Paylaşmayı durdur"; -/* No comment provided by engineer. */ +/* alert title */ "Stop sharing address?" = "Adresi paylaşmak durdurulsun mu?"; /* authentication reason */ @@ -4484,7 +4424,7 @@ /* No comment provided by engineer. */ "Test servers" = "Sunucuları test et"; -/* No comment provided by engineer. */ +/* alert title */ "Tests failed!" = "Testler başarısız oldu!"; /* No comment provided by engineer. */ @@ -4496,9 +4436,6 @@ /* No comment provided by engineer. */ "Thanks to the users – contribute via Weblate!" = "Kullanıcılar için teşekkürler - Weblate aracılığıyla katkıda bulun!"; -/* No comment provided by engineer. */ -"No user identifiers." = "Herhangi bir kullanıcı tanımlayıcısı yok."; - /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Uygulama, mesaj veya iletişim isteği aldığınızda sizi bilgilendirebilir - etkinleştirmek için lütfen ayarları açın."; @@ -4523,6 +4460,9 @@ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Şifreleme çalışıyor ve yeni şifreleme anlaşması gerekli değil. Bağlantı hatalarına neden olabilir!"; +/* No comment provided by engineer. */ +"The future of messaging" = "Gizli mesajlaşmanın yeni nesli"; + /* No comment provided by engineer. */ "The hash of the previous message is different." = "Önceki mesajın hash'i farklı."; @@ -4541,9 +4481,6 @@ /* No comment provided by engineer. */ "The messages will be marked as moderated for all members." = "Mesajlar tüm üyeler için moderasyonlu olarak işaretlenecektir."; -/* No comment provided by engineer. */ -"The future of messaging" = "Gizli mesajlaşmanın yeni nesli"; - /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Eski veritabanı geçiş sırasında kaldırılmadı, silinebilir."; @@ -4631,9 +4568,6 @@ /* No comment provided by engineer. */ "To make a new connection" = "Yeni bir bağlantı oluşturmak için"; -/* No comment provided by engineer. */ -"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Gizliliği korumak için, diğer tüm platformlar gibi kullanıcı kimliği kullanmak yerine, SimpleX mesaj kuyrukları için kişilerinizin her biri için ayrı tanımlayıcılara sahiptir."; - /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Zaman bölgesini korumak için,fotoğraf/ses dosyaları UTC kullanır."; @@ -4643,6 +4577,9 @@ /* No comment provided by engineer. */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "IP adresinizi korumak için,gizli yönlendirme mesajları iletmek için SMP sunucularınızı kullanır."; +/* No comment provided by engineer. */ +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Gizliliği korumak için, diğer tüm platformlar gibi kullanıcı kimliği kullanmak yerine, SimpleX mesaj kuyrukları için kişilerinizin her biri için ayrı tanımlayıcılara sahiptir."; + /* No comment provided by engineer. */ "To record speech please grant permission to use Microphone." = "Konuşmayı kaydetmek için lütfen Mikrofon kullanma izni verin."; @@ -5030,9 +4967,6 @@ /* No comment provided by engineer. */ "when IP hidden" = "IP gizliyken"; -/* No comment provided by engineer. */ -"When people request to connect, you can accept or reject it." = "İnsanlar bağlantı talebinde bulunduğunda, kabul edebilir veya reddedebilirsiniz."; - /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Biriyle gizli bir profil paylaştığınızda, bu profil sizi davet ettikleri gruplar için kullanılacaktır."; @@ -5174,9 +5108,6 @@ /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "Bu adresi kişilerinizle paylaşarak onların **%@** ile bağlantı kurmasını sağlayabilirsiniz."; -/* No comment provided by engineer. */ -"You can share your address as a link or QR code - anybody can connect to you." = "Adresinizi bir bağlantı veya QR kodu olarak paylaşabilirsiniz - herkes size bağlanabilir."; - /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "Sohbeti uygulamada Ayarlar / Veritabanı üzerinden veya uygulamayı yeniden başlatarak başlatabilirsiniz"; @@ -5189,7 +5120,7 @@ /* No comment provided by engineer. */ "You can use markdown to format messages:" = "Mesajları biçimlendirmek için markdown kullanabilirsiniz:"; -/* No comment provided by engineer. */ +/* alert message */ "You can view invitation link again in connection details." = "Bağlantı detaylarından davet bağlantısını yeniden görüntüleyebilirsin."; /* No comment provided by engineer. */ @@ -5210,6 +5141,9 @@ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Doğrulanamadınız; lütfen tekrar deneyin."; +/* No comment provided by engineer. */ +"You decide who can connect." = "Kimin bağlanabileceğine siz karar verirsiniz."; + /* No comment provided by engineer. */ "You have already requested connection via this address!" = "Bu adres üzerinden zaten bağlantı talebinde bulundunuz!"; @@ -5300,9 +5234,6 @@ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Bu grup için gizli bir profil kullanıyorsunuz - ana profilinizi paylaşmayı önlemek için kişileri davet etmeye izin verilmiyor"; -/* No comment provided by engineer. */ -"Your %@ servers" = "%@ sunucularınız"; - /* No comment provided by engineer. */ "Your calls" = "Aramaların"; @@ -5366,9 +5297,6 @@ /* No comment provided by engineer. */ "Your random profile" = "Rasgele profiliniz"; -/* No comment provided by engineer. */ -"Your server" = "Sunucunuz"; - /* No comment provided by engineer. */ "Your server address" = "Sunucu adresiniz"; @@ -5381,6 +5309,3 @@ /* No comment provided by engineer. */ "Your SMP servers" = "SMP sunucularınız"; -/* No comment provided by engineer. */ -"Your XFTP servers" = "XFTP sunucularınız"; - diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index 7f6a8bb677..01a3196c03 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -1,15 +1,6 @@ /* No comment provided by engineer. */ "\n" = "\n"; -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - /* No comment provided by engineer. */ " (" = " ("; @@ -169,6 +160,15 @@ /* time interval */ "%d days" = "%d днів"; +/* forward confirmation reason */ +"%d file(s) are still being downloaded." = "%их файл(ів) ще досі завантажуються."; + +/* forward confirmation reason */ +"%d file(s) failed to download." = "%их файлів не вийшло завантажити."; + +/* forward confirmation reason */ +"%d file(s) were deleted." = "%их файл(ів) було видалено."; + /* time interval */ "%d hours" = "%d годин"; @@ -319,12 +319,6 @@ /* No comment provided by engineer. */ "Abort changing address?" = "Скасувати зміну адреси?"; -/* No comment provided by engineer. */ -"About SimpleX" = "Про SimpleX"; - -/* No comment provided by engineer. */ -"About SimpleX address" = "Про адресу SimpleX"; - /* No comment provided by engineer. */ "About SimpleX Chat" = "Про чат SimpleX"; @@ -364,12 +358,6 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Додайте адресу до свого профілю, щоб ваші контакти могли поділитися нею з іншими людьми. Повідомлення про оновлення профілю буде надіслано вашим контактам."; -/* No comment provided by engineer. */ -"Add contact" = "Додати контакт"; - -/* No comment provided by engineer. */ -"Add preset servers" = "Додавання попередньо встановлених серверів"; - /* No comment provided by engineer. */ "Add profile" = "Додати профіль"; @@ -556,6 +544,9 @@ /* No comment provided by engineer. */ "Answer call" = "Відповісти на дзвінок"; +/* No comment provided by engineer. */ +"Anybody can host servers." = "Кожен може хостити сервери."; + /* No comment provided by engineer. */ "App build: %@" = "Збірка програми: %@"; @@ -778,7 +769,8 @@ /* No comment provided by engineer. */ "Can't message member" = "Не можу надіслати повідомлення користувачеві"; -/* alert button */ +/* alert action + alert button */ "Cancel" = "Скасувати"; /* No comment provided by engineer. */ @@ -896,7 +888,7 @@ /* No comment provided by engineer. */ "Chats" = "Чати"; -/* No comment provided by engineer. */ +/* alert title */ "Check server address and try again." = "Перевірте адресу сервера та спробуйте ще раз."; /* No comment provided by engineer. */ @@ -959,9 +951,6 @@ /* No comment provided by engineer. */ "Configure ICE servers" = "Налаштування серверів ICE"; -/* No comment provided by engineer. */ -"Configured %@ servers" = "Налаштовані сервери %@"; - /* No comment provided by engineer. */ "Confirm" = "Підтвердити"; @@ -1187,9 +1176,6 @@ /* No comment provided by engineer. */ "Create a group using a random profile." = "Створіть групу, використовуючи випадковий профіль."; -/* No comment provided by engineer. */ -"Create an address to let people connect with you." = "Створіть адресу, щоб люди могли з вами зв'язатися."; - /* server test step */ "Create file" = "Створити файл"; @@ -1349,7 +1335,8 @@ /* No comment provided by engineer. */ "default (yes)" = "за замовчуванням (так)"; -/* chat item action +/* alert action + chat item action swipe action */ "Delete" = "Видалити"; @@ -1933,9 +1920,6 @@ /* No comment provided by engineer. */ "Error joining group" = "Помилка приєднання до групи"; -/* No comment provided by engineer. */ -"Error loading %@ servers" = "Помилка завантаження %@ серверів"; - /* No comment provided by engineer. */ "Error opening chat" = "Помилка відкриття чату"; @@ -1954,9 +1938,6 @@ /* No comment provided by engineer. */ "Error resetting statistics" = "Статистика скидання помилок"; -/* No comment provided by engineer. */ -"Error saving %@ servers" = "Помилка збереження %@ серверів"; - /* No comment provided by engineer. */ "Error saving group profile" = "Помилка збереження профілю групи"; @@ -2338,9 +2319,6 @@ /* time unit */ "hours" = "години"; -/* No comment provided by engineer. */ -"How it works" = "Як це працює"; - /* No comment provided by engineer. */ "How SimpleX works" = "Як працює SimpleX"; @@ -2480,10 +2458,10 @@ "Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Встановіть [SimpleX Chat для терміналу](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"Instant push notifications will be hidden!\n" = "Миттєві пуш-сповіщення будуть приховані!\n"; +"Instant" = "Миттєво"; /* No comment provided by engineer. */ -"Instant" = "Миттєво"; +"Instant push notifications will be hidden!\n" = "Миттєві пуш-сповіщення будуть приховані!\n"; /* No comment provided by engineer. */ "Interface" = "Інтерфейс"; @@ -2521,7 +2499,7 @@ /* No comment provided by engineer. */ "Invalid response" = "Неправильна відповідь"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid server address!" = "Неправильна адреса сервера!"; /* item status text */ @@ -2623,7 +2601,7 @@ /* No comment provided by engineer. */ "Joining group" = "Приєднання до групи"; -/* No comment provided by engineer. */ +/* alert action */ "Keep" = "Тримай"; /* No comment provided by engineer. */ @@ -2632,7 +2610,7 @@ /* No comment provided by engineer. */ "Keep the app open to use it from desktop" = "Тримайте додаток відкритим, щоб використовувати його з робочого столу"; -/* No comment provided by engineer. */ +/* alert title */ "Keep unused invitation?" = "Зберігати невикористані запрошення?"; /* No comment provided by engineer. */ @@ -2689,9 +2667,6 @@ /* No comment provided by engineer. */ "Live messages" = "Живі повідомлення"; -/* No comment provided by engineer. */ -"No push server" = "Локально"; - /* No comment provided by engineer. */ "Local name" = "Місцева назва"; @@ -2704,24 +2679,15 @@ /* No comment provided by engineer. */ "Lock mode" = "Режим блокування"; -/* No comment provided by engineer. */ -"Make a private connection" = "Створіть приватне з'єднання"; - /* No comment provided by engineer. */ "Make one message disappear" = "Зробити так, щоб одне повідомлення зникло"; /* No comment provided by engineer. */ "Make profile private!" = "Зробіть профіль приватним!"; -/* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Переконайтеся, що адреси серверів %@ мають правильний формат, розділені рядками і не дублюються (%@)."; - /* No comment provided by engineer. */ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Переконайтеся, що адреси серверів WebRTC ICE мають правильний формат, розділені рядками і не дублюються."; -/* No comment provided by engineer. */ -"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Багато людей запитували: *якщо SimpleX не має ідентифікаторів користувачів, як він може доставляти повідомлення?*"; - /* No comment provided by engineer. */ "Mark deleted for everyone" = "Позначити видалено для всіх"; @@ -3043,12 +3009,18 @@ /* No comment provided by engineer. */ "No permission to record voice message" = "Немає дозволу на запис голосового повідомлення"; +/* No comment provided by engineer. */ +"No push server" = "Локально"; + /* No comment provided by engineer. */ "No received or sent files" = "Немає отриманих або відправлених файлів"; /* copied message info in history */ "no text" = "без тексту"; +/* No comment provided by engineer. */ +"No user identifiers." = "Ніяких ідентифікаторів користувачів."; + /* No comment provided by engineer. */ "Not compatible!" = "Не сумісні!"; @@ -3168,18 +3140,9 @@ /* authentication reason */ "Open migration to another device" = "Відкрита міграція на інший пристрій"; -/* No comment provided by engineer. */ -"Open server settings" = "Відкрити налаштування сервера"; - /* No comment provided by engineer. */ "Open Settings" = "Відкрийте Налаштування"; -/* authentication reason */ -"Open user profiles" = "Відкрити профілі користувачів"; - -/* No comment provided by engineer. */ -"Anybody can host servers." = "Кожен може хостити сервери."; - /* No comment provided by engineer. */ "Opening app…" = "Відкриваємо програму…"; @@ -3201,9 +3164,6 @@ /* No comment provided by engineer. */ "Other" = "Інше"; -/* No comment provided by engineer. */ -"Other %@ servers" = "Інші сервери %@"; - /* No comment provided by engineer. */ "other errors" = "інші помилки"; @@ -3252,9 +3212,6 @@ /* No comment provided by engineer. */ "Pending" = "В очікуванні"; -/* No comment provided by engineer. */ -"You decide who can connect." = "Ви вирішуєте, хто може під\'єднатися."; - /* No comment provided by engineer. */ "Periodic" = "Періодично"; @@ -3330,9 +3287,6 @@ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Зберегти чернетку останнього повідомлення з вкладеннями."; -/* No comment provided by engineer. */ -"Preset server" = "Попередньо встановлений сервер"; - /* No comment provided by engineer. */ "Preset server address" = "Попередньо встановлена адреса сервера"; @@ -3381,7 +3335,7 @@ /* No comment provided by engineer. */ "Profile theme" = "Тема профілю"; -/* No comment provided by engineer. */ +/* alert message */ "Profile update will be sent to your contacts." = "Оновлення профілю буде надіслано вашим контактам."; /* No comment provided by engineer. */ @@ -3463,10 +3417,10 @@ "Read more" = "Читати далі"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Читайте більше в [Посібнику користувача](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Читайте більше в [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Читайте більше в [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Читайте більше в [Посібнику користувача](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Читайте більше в [Посібнику користувача](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; @@ -3474,9 +3428,6 @@ /* No comment provided by engineer. */ "Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Читайте більше в нашому [GitHub репозиторії](https://github.com/simplex-chat/simplex-chat#readme)."; -/* No comment provided by engineer. */ -"Read more in our GitHub repository." = "Читайте більше в нашому репозиторії на GitHub."; - /* No comment provided by engineer. */ "Receipts are disabled" = "Підтвердження виключені"; @@ -3746,7 +3697,7 @@ /* No comment provided by engineer. */ "Save servers" = "Зберегти сервери"; -/* No comment provided by engineer. */ +/* alert title */ "Save servers?" = "Зберегти сервери?"; /* No comment provided by engineer. */ @@ -3890,9 +3841,6 @@ /* No comment provided by engineer. */ "Send notifications" = "Надсилати сповіщення"; -/* No comment provided by engineer. */ -"Send notifications:" = "Надсилати сповіщення:"; - /* No comment provided by engineer. */ "Send questions and ideas" = "Надсилайте запитання та ідеї"; @@ -4049,7 +3997,8 @@ /* No comment provided by engineer. */ "Shape profile images" = "Сформуйте зображення профілю"; -/* chat item action */ +/* alert action + chat item action */ "Share" = "Поділіться"; /* No comment provided by engineer. */ @@ -4058,7 +4007,7 @@ /* No comment provided by engineer. */ "Share address" = "Поділитися адресою"; -/* No comment provided by engineer. */ +/* alert title */ "Share address with contacts?" = "Поділіться адресою з контактами?"; /* No comment provided by engineer. */ @@ -4229,10 +4178,10 @@ /* No comment provided by engineer. */ "Stop sending file?" = "Припинити надсилання файлу?"; -/* No comment provided by engineer. */ +/* alert action */ "Stop sharing" = "Припиніть ділитися"; -/* No comment provided by engineer. */ +/* alert title */ "Stop sharing address?" = "Припинити ділитися адресою?"; /* authentication reason */ @@ -4319,7 +4268,7 @@ /* No comment provided by engineer. */ "Test servers" = "Тестові сервери"; -/* No comment provided by engineer. */ +/* alert title */ "Tests failed!" = "Тести не пройшли!"; /* No comment provided by engineer. */ @@ -4331,9 +4280,6 @@ /* No comment provided by engineer. */ "Thanks to the users – contribute via Weblate!" = "Дякуємо користувачам - зробіть свій внесок через Weblate!"; -/* No comment provided by engineer. */ -"No user identifiers." = "Ніяких ідентифікаторів користувачів."; - /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Додаток може сповіщати вас, коли ви отримуєте повідомлення або запити на контакт - будь ласка, відкрийте налаштування, щоб увімкнути цю функцію."; @@ -4358,6 +4304,9 @@ /* 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 future of messaging" = "Наступне покоління приватних повідомлень"; + /* No comment provided by engineer. */ "The hash of the previous message is different." = "Хеш попереднього повідомлення відрізняється."; @@ -4376,9 +4325,6 @@ /* No comment provided by engineer. */ "The messages will be marked as moderated for all members." = "Повідомлення будуть позначені як модеровані для всіх учасників."; -/* No comment provided by engineer. */ -"The future of messaging" = "Наступне покоління приватних повідомлень"; - /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Стара база даних не була видалена під час міграції, її можна видалити."; @@ -4463,9 +4409,6 @@ /* No comment provided by engineer. */ "To make a new connection" = "Щоб створити нове з'єднання"; -/* No comment provided by engineer. */ -"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Щоб захистити конфіденційність, замість ідентифікаторів користувачів, які використовуються на всіх інших платформах, SimpleX має ідентифікатори для черг повідомлень, окремі для кожного з ваших контактів."; - /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Для захисту часового поясу у файлах зображень/голосу використовується UTC."; @@ -4475,6 +4418,9 @@ /* No comment provided by engineer. */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "Щоб захистити вашу IP-адресу, приватна маршрутизація використовує ваші SMP-сервери для доставки повідомлень."; +/* No comment provided by engineer. */ +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Щоб захистити конфіденційність, замість ідентифікаторів користувачів, які використовуються на всіх інших платформах, SimpleX має ідентифікатори для черг повідомлень, окремі для кожного з ваших контактів."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Щоб записати голосове повідомлення, будь ласка, надайте дозвіл на використання мікрофону."; @@ -4850,9 +4796,6 @@ /* No comment provided by engineer. */ "when IP hidden" = "коли IP приховано"; -/* No comment provided by engineer. */ -"When people request to connect, you can accept or reject it." = "Коли люди звертаються із запитом на підключення, ви можете прийняти або відхилити його."; - /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Коли ви ділитеся з кимось своїм профілем інкогніто, цей профіль буде використовуватися для груп, до яких вас запрошують."; @@ -4994,9 +4937,6 @@ /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "Ви можете поділитися цією адресою зі своїми контактами, щоб вони могли зв'язатися з **%@**."; -/* No comment provided by engineer. */ -"You can share your address as a link or QR code - anybody can connect to you." = "Ви можете поділитися своєю адресою у вигляді посилання або QR-коду - будь-хто зможе зв'язатися з вами."; - /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "Запустити чат можна через Налаштування програми / База даних або перезапустивши програму"; @@ -5009,7 +4949,7 @@ /* No comment provided by engineer. */ "You can use markdown to format messages:" = "Ви можете використовувати розмітку для форматування повідомлень:"; -/* No comment provided by engineer. */ +/* alert message */ "You can view invitation link again in connection details." = "Ви можете переглянути посилання на запрошення ще раз у деталях підключення."; /* No comment provided by engineer. */ @@ -5030,6 +4970,9 @@ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Вас не вдалося верифікувати, спробуйте ще раз."; +/* No comment provided by engineer. */ +"You decide who can connect." = "Ви вирішуєте, хто може під'єднатися."; + /* No comment provided by engineer. */ "You have already requested connection via this address!" = "Ви вже надсилали запит на підключення за цією адресою!"; @@ -5120,9 +5063,6 @@ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Ви використовуєте профіль інкогніто для цієї групи - щоб запобігти поширенню вашого основного профілю, запрошення контактів заборонено"; -/* No comment provided by engineer. */ -"Your %@ servers" = "Ваші сервери %@"; - /* No comment provided by engineer. */ "Your calls" = "Твої дзвінки"; @@ -5174,9 +5114,6 @@ /* No comment provided by engineer. */ "Your random profile" = "Ваш випадковий профіль"; -/* No comment provided by engineer. */ -"Your server" = "Ваш сервер"; - /* No comment provided by engineer. */ "Your server address" = "Адреса вашого сервера"; @@ -5189,6 +5126,3 @@ /* No comment provided by engineer. */ "Your SMP servers" = "Ваші SMP-сервери"; -/* No comment provided by engineer. */ -"Your XFTP servers" = "Ваші XFTP-сервери"; - diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index a15b7d45fe..ba8dcd6e2c 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -1,15 +1,6 @@ /* No comment provided by engineer. */ "\n" = "\n"; -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" " = " "; - /* No comment provided by engineer. */ " (" = " ("; @@ -319,12 +310,6 @@ /* No comment provided by engineer. */ "Abort changing address?" = "中止地址更改?"; -/* No comment provided by engineer. */ -"About SimpleX" = "关于SimpleX"; - -/* No comment provided by engineer. */ -"About SimpleX address" = "关于 SimpleX 地址"; - /* No comment provided by engineer. */ "About SimpleX Chat" = "关于SimpleX Chat"; @@ -364,12 +349,6 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "将地址添加到您的个人资料,以便您的联系人可以与其他人共享。个人资料更新将发送给您的联系人。"; -/* No comment provided by engineer. */ -"Add contact" = "添加联系人"; - -/* No comment provided by engineer. */ -"Add preset servers" = "添加预设服务器"; - /* No comment provided by engineer. */ "Add profile" = "添加个人资料"; @@ -556,6 +535,9 @@ /* No comment provided by engineer. */ "Answer call" = "接听来电"; +/* No comment provided by engineer. */ +"Anybody can host servers." = "任何人都可以托管服务器。"; + /* No comment provided by engineer. */ "App build: %@" = "应用程序构建:%@"; @@ -778,7 +760,8 @@ /* No comment provided by engineer. */ "Can't message member" = "无法向成员发送消息"; -/* alert button */ +/* alert action + alert button */ "Cancel" = "取消"; /* No comment provided by engineer. */ @@ -896,7 +879,7 @@ /* No comment provided by engineer. */ "Chats" = "聊天"; -/* No comment provided by engineer. */ +/* alert title */ "Check server address and try again." = "检查服务器地址并再试一次。"; /* No comment provided by engineer. */ @@ -959,9 +942,6 @@ /* No comment provided by engineer. */ "Configure ICE servers" = "配置 ICE 服务器"; -/* No comment provided by engineer. */ -"Configured %@ servers" = "已配置 %@ 服务器"; - /* No comment provided by engineer. */ "Confirm" = "确认"; @@ -1187,9 +1167,6 @@ /* No comment provided by engineer. */ "Create a group using a random profile." = "使用随机身份创建群组."; -/* No comment provided by engineer. */ -"Create an address to let people connect with you." = "创建一个地址,让人们与您联系。"; - /* server test step */ "Create file" = "创建文件"; @@ -1349,7 +1326,8 @@ /* No comment provided by engineer. */ "default (yes)" = "默认 (是)"; -/* chat item action +/* alert action + chat item action swipe action */ "Delete" = "删除"; @@ -1933,9 +1911,6 @@ /* No comment provided by engineer. */ "Error joining group" = "加入群组错误"; -/* No comment provided by engineer. */ -"Error loading %@ servers" = "加载 %@ 服务器错误"; - /* No comment provided by engineer. */ "Error opening chat" = "打开聊天时出错"; @@ -1954,9 +1929,6 @@ /* No comment provided by engineer. */ "Error resetting statistics" = "重置统计信息时出错"; -/* No comment provided by engineer. */ -"Error saving %@ servers" = "保存 %@ 服务器错误"; - /* No comment provided by engineer. */ "Error saving group profile" = "保存群组资料错误"; @@ -2338,9 +2310,6 @@ /* time unit */ "hours" = "小时"; -/* No comment provided by engineer. */ -"How it works" = "工作原理"; - /* No comment provided by engineer. */ "How SimpleX works" = "SimpleX的工作原理"; @@ -2480,10 +2449,10 @@ "Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "安装[用于终端的 SimpleX Chat](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"Instant push notifications will be hidden!\n" = "即时推送通知将被隐藏!\n"; +"Instant" = "即时"; /* No comment provided by engineer. */ -"Instant" = "即时"; +"Instant push notifications will be hidden!\n" = "即时推送通知将被隐藏!\n"; /* No comment provided by engineer. */ "Interface" = "界面"; @@ -2521,7 +2490,7 @@ /* No comment provided by engineer. */ "Invalid response" = "无效的响应"; -/* No comment provided by engineer. */ +/* alert title */ "Invalid server address!" = "无效的服务器地址!"; /* item status text */ @@ -2623,7 +2592,7 @@ /* No comment provided by engineer. */ "Joining group" = "加入群组中"; -/* No comment provided by engineer. */ +/* alert action */ "Keep" = "保留"; /* No comment provided by engineer. */ @@ -2632,7 +2601,7 @@ /* No comment provided by engineer. */ "Keep the app open to use it from desktop" = "保持应用程序打开状态以从桌面使用它"; -/* No comment provided by engineer. */ +/* alert title */ "Keep unused invitation?" = "保留未使用的邀请吗?"; /* No comment provided by engineer. */ @@ -2689,9 +2658,6 @@ /* No comment provided by engineer. */ "Live messages" = "实时消息"; -/* No comment provided by engineer. */ -"No push server" = "本地"; - /* No comment provided by engineer. */ "Local name" = "本地名称"; @@ -2704,24 +2670,15 @@ /* No comment provided by engineer. */ "Lock mode" = "锁定模式"; -/* No comment provided by engineer. */ -"Make a private connection" = "建立私密连接"; - /* No comment provided by engineer. */ "Make one message disappear" = "使一条消息消失"; /* No comment provided by engineer. */ "Make profile private!" = "将个人资料设为私密!"; -/* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "请确保 %@服 务器地址格式正确,每行一个地址并且不重复 (%@)。"; - /* No comment provided by engineer. */ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "确保 WebRTC ICE 服务器地址格式正确、每行分开且不重复。"; -/* No comment provided by engineer. */ -"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "许多人问: *如果SimpleX没有用户标识符,它怎么传递信息?*"; - /* No comment provided by engineer. */ "Mark deleted for everyone" = "标记为所有人已删除"; @@ -3043,12 +3000,18 @@ /* No comment provided by engineer. */ "No permission to record voice message" = "没有录制语音消息的权限"; +/* No comment provided by engineer. */ +"No push server" = "本地"; + /* No comment provided by engineer. */ "No received or sent files" = "未收到或发送文件"; /* copied message info in history */ "no text" = "无文本"; +/* No comment provided by engineer. */ +"No user identifiers." = "没有用户标识符。"; + /* No comment provided by engineer. */ "Not compatible!" = "不兼容!"; @@ -3168,18 +3131,9 @@ /* authentication reason */ "Open migration to another device" = "打开迁移到另一台设备"; -/* No comment provided by engineer. */ -"Open server settings" = "打开服务器设置"; - /* No comment provided by engineer. */ "Open Settings" = "打开设置"; -/* authentication reason */ -"Open user profiles" = "打开用户个人资料"; - -/* No comment provided by engineer. */ -"Anybody can host servers." = "任何人都可以托管服务器。"; - /* No comment provided by engineer. */ "Opening app…" = "正在打开应用程序…"; @@ -3201,9 +3155,6 @@ /* No comment provided by engineer. */ "Other" = "其他"; -/* No comment provided by engineer. */ -"Other %@ servers" = "其他 %@ 服务器"; - /* No comment provided by engineer. */ "other errors" = "其他错误"; @@ -3252,9 +3203,6 @@ /* No comment provided by engineer. */ "Pending" = "待定"; -/* No comment provided by engineer. */ -"You decide who can connect." = "你决定谁可以连接。"; - /* No comment provided by engineer. */ "Periodic" = "定期"; @@ -3330,9 +3278,6 @@ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "保留最后的消息草稿及其附件。"; -/* No comment provided by engineer. */ -"Preset server" = "预设服务器"; - /* No comment provided by engineer. */ "Preset server address" = "预设服务器地址"; @@ -3381,7 +3326,7 @@ /* No comment provided by engineer. */ "Profile theme" = "个人资料主题"; -/* No comment provided by engineer. */ +/* alert message */ "Profile update will be sent to your contacts." = "个人资料更新将被发送给您的联系人。"; /* No comment provided by engineer. */ @@ -3463,10 +3408,10 @@ "Read more" = "阅读更多"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "在 [用户指南](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses) 中阅读更多内容。"; +"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "阅读更多[User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)。"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "阅读更多[User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)。"; +"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "在 [用户指南](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses) 中阅读更多内容。"; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "在 [用户指南](https://simplex.chat/docs/guide/readme.html#connect-to-friends) 中阅读更多内容。"; @@ -3474,9 +3419,6 @@ /* No comment provided by engineer. */ "Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "在我们的 [GitHub 仓库](https://github.com/simplex-chat/simplex-chat#readme) 中阅读更多信息。"; -/* No comment provided by engineer. */ -"Read more in our GitHub repository." = "在我们的 GitHub 仓库中阅读更多内容。"; - /* No comment provided by engineer. */ "Receipts are disabled" = "回执已禁用"; @@ -3746,7 +3688,7 @@ /* No comment provided by engineer. */ "Save servers" = "保存服务器"; -/* No comment provided by engineer. */ +/* alert title */ "Save servers?" = "保存服务器?"; /* No comment provided by engineer. */ @@ -3890,9 +3832,6 @@ /* No comment provided by engineer. */ "Send notifications" = "发送通知"; -/* No comment provided by engineer. */ -"Send notifications:" = "发送通知:"; - /* No comment provided by engineer. */ "Send questions and ideas" = "发送问题和想法"; @@ -4049,7 +3988,8 @@ /* No comment provided by engineer. */ "Shape profile images" = "改变个人资料图形状"; -/* chat item action */ +/* alert action + chat item action */ "Share" = "分享"; /* No comment provided by engineer. */ @@ -4058,7 +3998,7 @@ /* No comment provided by engineer. */ "Share address" = "分享地址"; -/* No comment provided by engineer. */ +/* alert title */ "Share address with contacts?" = "与联系人分享地址?"; /* No comment provided by engineer. */ @@ -4229,10 +4169,10 @@ /* No comment provided by engineer. */ "Stop sending file?" = "停止发送文件?"; -/* No comment provided by engineer. */ +/* alert action */ "Stop sharing" = "停止分享"; -/* No comment provided by engineer. */ +/* alert title */ "Stop sharing address?" = "停止分享地址?"; /* authentication reason */ @@ -4319,7 +4259,7 @@ /* No comment provided by engineer. */ "Test servers" = "测试服务器"; -/* No comment provided by engineer. */ +/* alert title */ "Tests failed!" = "测试失败!"; /* No comment provided by engineer. */ @@ -4331,9 +4271,6 @@ /* No comment provided by engineer. */ "Thanks to the users – contribute via Weblate!" = "感谢用户——通过 Weblate 做出贡献!"; -/* No comment provided by engineer. */ -"No user identifiers." = "没有用户标识符。"; - /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "该应用可以在您收到消息或联系人请求时通知您——请打开设置以启用通知。"; @@ -4358,6 +4295,9 @@ /* 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 future of messaging" = "下一代私密通讯软件"; + /* No comment provided by engineer. */ "The hash of the previous message is different." = "上一条消息的散列不同。"; @@ -4376,9 +4316,6 @@ /* No comment provided by engineer. */ "The messages will be marked as moderated for all members." = "对于所有成员,这些消息将被标记为已审核。"; -/* No comment provided by engineer. */ -"The future of messaging" = "下一代私密通讯软件"; - /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "旧数据库在迁移过程中没有被移除,可以删除。"; @@ -4463,9 +4400,6 @@ /* No comment provided by engineer. */ "To make a new connection" = "建立新连接"; -/* No comment provided by engineer. */ -"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "为了保护隐私,SimpleX使用针对消息队列的标识符,而不是所有其他平台使用的用户ID,每个联系人都有独立的标识符。"; - /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "为了保护时区,图像/语音文件使用 UTC。"; @@ -4475,6 +4409,9 @@ /* No comment provided by engineer. */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "为了保护您的 IP 地址,私有路由使用您的 SMP 服务器来传递邮件。"; +/* No comment provided by engineer. */ +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "为了保护隐私,SimpleX使用针对消息队列的标识符,而不是所有其他平台使用的用户ID,每个联系人都有独立的标识符。"; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "请授权使用麦克风以录制语音消息。"; @@ -4850,9 +4787,6 @@ /* No comment provided by engineer. */ "when IP hidden" = "当 IP 隐藏时"; -/* No comment provided by engineer. */ -"When people request to connect, you can accept or reject it." = "当人们请求连接时,您可以接受或拒绝它。"; - /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "当您与某人共享隐身聊天资料时,该资料将用于他们邀请您加入的群组。"; @@ -4994,9 +4928,6 @@ /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "您可以与您的联系人分享该地址,让他们与 **%@** 联系。"; -/* No comment provided by engineer. */ -"You can share your address as a link or QR code - anybody can connect to you." = "您可以将您的地址作为链接或二维码共享——任何人都可以连接到您。"; - /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "您可以通过应用程序设置/数据库或重新启动应用程序开始聊天"; @@ -5009,7 +4940,7 @@ /* No comment provided by engineer. */ "You can use markdown to format messages:" = "您可以使用 markdown 来编排消息格式:"; -/* No comment provided by engineer. */ +/* alert message */ "You can view invitation link again in connection details." = "您可以在连接详情中再次查看邀请链接。"; /* No comment provided by engineer. */ @@ -5030,6 +4961,9 @@ /* No comment provided by engineer. */ "You could not be verified; please try again." = "您的身份无法验证,请再试一次。"; +/* No comment provided by engineer. */ +"You decide who can connect." = "你决定谁可以连接。"; + /* No comment provided by engineer. */ "You have already requested connection via this address!" = "你已经请求通过此地址进行连接!"; @@ -5120,9 +5054,6 @@ /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "您正在为该群组使用隐身个人资料——为防止共享您的主要个人资料,不允许邀请联系人"; -/* No comment provided by engineer. */ -"Your %@ servers" = "您的 %@ 服务器"; - /* No comment provided by engineer. */ "Your calls" = "您的通话"; @@ -5174,9 +5105,6 @@ /* No comment provided by engineer. */ "Your random profile" = "您的随机资料"; -/* No comment provided by engineer. */ -"Your server" = "您的服务器"; - /* No comment provided by engineer. */ "Your server address" = "您的服务器地址"; @@ -5189,6 +5117,3 @@ /* No comment provided by engineer. */ "Your SMP servers" = "您的 SMP 服务器"; -/* No comment provided by engineer. */ -"Your XFTP servers" = "您的 XFTP 服务器"; - diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index 147d79002b..d9d86634b1 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -2047,7 +2047,7 @@ استخدم التطبيق بيد واحدة. صُدرت قاعدة بيانات الدردشة التحكم في شبكتك - حذف ما يصل إلى 20 رسالة في وقت واحد. + حذف ما يصل إلى 20 رسالة في آن واحد. لم يتم تصدير بعض الملفات يحمي عنوان IP الخاص بك واتصالاتك. اتصال TCP @@ -2119,7 +2119,7 @@ شكل الرسالة قابل للتخصيص. تبديل الصوت والفيديو أثناء المكالمة. حذف أو إشراف ما يصل إلى 200 رسالة. - حوّل ما يصل إلى 20 رسالة آن واحد. + حوّل ما يصل إلى 20 رسالة في آن واحد. مكالمات أفضل تواريخ أفضل للرسائل. أمان أفضل ✅ diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml index cb4185ccb7..4b1d1b8838 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml @@ -243,4 +243,159 @@ Προσθέστε τη διεύθυνση στο προφίλ σας, έτσι ώστε οι επαφές σας να μπορούν να τη μοιραστούν με άλλα άτομα. Το ενημέρωμένο προφίλ θα σταλεί στις επαφές σας. διαχειριστές Λάθη αναγνώρισης + Προειδοποίηση: το αρχείο θα διαγραφεί.]]> + Υπέρβαση χωρητικότητας - ο παραλήπτης δεν έλαβε μηνύματα που στάλθηκαν προηγουμένως. + αποκλεισμένος από τον διαχειριστή + Συνομιλίες + όλα τα μέλη + Όλες οι επαφές σας θα παραμείνουν ενεργές. Το ανανεωμένο προφίλ σας θα αποσταλεί στις επαφές σας. + Να χρησιμοποιείται πάντα ιδιωτική δρομολόγηση. + Ένα κενό προφίλ συνομιλίας με το παρεχόμενο όνομα δημιουργείται και η εφαρμογή ανοίγει ως συνήθως. + Η βάση δεδομένων της συνομιλίας διαγράφηκε + Απενεργοποίηση ήχου + Eνεργοποίηση ήχου + Κακό μήνυμα hash + Θάμπωση των μέσων + ΒΑΣΗ ΔΕΔΟΜΕΝΩΝ ΣΥΝΟΜΙΛΙΑΣ + Το Android Keystore χρησιμοποιείται για την ασφαλή αποθήκευση της φράσης πρόσβασης - επιτρέπει την υπηρεσία ειδοποιήσεων να λειτουργεί. + αποκλεισμένος + Αποκλεισμένος από τον διαχειριστή + Δεν είναι δυνατή η κλήση επαφής + Θέμα εφαρμογής + Εφαρμογή σε + Η εφαρμογή κρυπτογραφεί νέα τοπικά αρχεία (εκτός απο βίντεο). + Καλύτερες ομάδες + Γίνεται ήδη συμμετοχή στην ομάδα! + Αρχειοθέτηση και αποστολή + %1$d διαφορετικό/κα σφάλμα/τα αρχείου/ων. + Η υπηρεσία παρασκηνίου λειτουργεί πάντα - οι ειδοποιήσεις θα εμφανίζονται μόλις τα μηνύματα είναι διαθέσιμα. + %1$d αρχείο/α ακόμα κατεβαίνουν. + %1$d αρχείο/α απέτυχε/χαν να παραληφθεί/ουν + %1$d αρχείο/α διαγράφηκε/καν. + %1$d αρχείο/α δεν κατέβηκε/καν. + %1$s μήνυμα/τα δεν προωθήθηκε/καν + Προφίλ συνομιλίας + για κάθε προφίλ συνομιλίας που έχετε στην εφαρμογή.]]> + Παρακαλώ σημειώστε: οι αναμεταδότες μηνυμάτων και αρχείων συνδέονται μέσω διακομιστή μεσολάβησης SOCKS. Οι κλήσεις και οι προεπισκοπήσεις συνδέσμων αποστολής χρησιμοποιούν άμεση σύνδεση.]]> + Πάντα + Η ενημέρωση της εφαρμογής κατεβαίνει + Έλεγχος για ενημερώσεις + Οποιοσδήποτε μπορεί να φιλοξενήσει διακομιστές. + κλήση ήχου (χωρίς κρυπτογράφηση e2e) + Κλήσεις στην οθόνη κλειδώματος: + Κλήση ήχου + \'Εκδοση Εφαρμογής: %s + Απαγορεύονται οι κλήσεις ήχου/βίντεο. + Αποκλεισμός μελών ομάδας + Τα chunks διαγράφηκαν + Όλα τα δεδομένα διαγράφονται κατά την εισαγωγή. + Αρχειοθετημένες επαφές + Ακύρωση μεταφοράς + Χρώματα συνομιλίας + ΒΑΣΗ ΔΕΔΟΜΕΝΩΝ ΣΥΝΟΜΙΛΙΑΣ + Η συνομιλία εκτελείται + Παρακαλώ σημειώστε: ΔΕΝ θα μπορείτε να ανακτήσετε ή να αλλάξετε τη φράση πρόσβασης εάν τη χάσετε.]]> + Αποκλεισμός για όλους + Και εσείς και η επαφή σας μπορείτε να προσθέστε αντιδράσεις μηνυμάτων. + Και εσείς και η επαφή σας μπορείτε να κάνετε κλήσεις. + Επιτρέψτε την αποστολή συνδέσμων SimpleX. + Αραβικά, Βουλγαρικά, Φινλανδικά, Εβραϊκά, Ταϊλανδέζικα και Ουκρανικά - χάρη στους χρήστες και το Weblate. + Μεταφορά δεδομένων εφαρμογής + Θάμπωμα για καλύτερη ιδιωτικότητα. + Η συνομιλία έχει μεταφερθεί! + Αρχειοθέτηση της βάσης δεδομένων + Όλες οι επαφές, συζητήσεις και αρχεία θα κρυπτογραφηθούν με ασφάλεια και θα μεταφορτωθούν σε διαμορφωμένα κομμάτια αναμετάδοσης XFTP. + Κινητή τηλεφωνία + Δημιουργία ομάδας : για την δημιουργίας νέας ομάδας.]]> + Ελέγξτε τη σύνδεσή σας στο διαδίκτυο και δοκιμάστε ξανά + Συζήτηση με τους προγραμματιστές + Ζήτησε να λάβει το βίντεο + Δεν είναι δυνατή η αποστολή μηνυμάτων στο μέλος της ομάδας + Αλλαγή λειτουργίας κλειδώματος + αποκλεισμένος %s + άλλαξε η διεύθυνση για εσάς + και %d άλλες εκδηλώσεις + Μαύρο + Πρόσθετο δευτερεύον + Και εσείς και η επαφή σας μπορείτε να διαγράψετε απεσταλμένα μηνύματα χωρίς ανατροπή. (24 ώρες) + Και εσείς και η επαφή σας μπορείτε να στείλετε ηχητικά μηνύματα. + Η συνομιλία σταμάτησε + Η συνομιλία έχει διακοπεί. Εάν χρησιμοποιήσατε ήδη αυτήν τη βάση δεδομένων σε άλλη συσκευή, θα πρέπει να τη μεταφέρετε πίσω προτού ξεκινήσετε τη συνομιλία. + Η λειτουργία βελτιστοποίησης της μπαταρίας είναι ενεργή, η υπηρεσία παρασκηνίου και τα περιοδικά αιτήματα για νέα μηνύματα θα απενεργοποιηθούν. Μπορείτε να τα ενεργοποιήσετε ξανά μέσω των ρυθμίσεων. + σύνδεσμος μιας χρήσης + Κλήσεις ήχου & βίντεο + Κλήσεις ήχου/βίντεο + Κωδικός εφαρμογής + Συνεδρία εφαρμογής + Η συνομιλία σταμάτησε + Έλεγχος για ενημερώσεις + Κινεζική και Ισπανική διεπαφή + Καλύτερες ημερομηνίες μηνυμάτων + Bluetooth + έντονο + Κονσόλα συνομιλίας + Παρακαλώ σημειώστε: η χρήση της ίδιας βάσης δεδομένων σε δύο συσκευές θα διακόψει την αποκρυπτογράφηση των μηνυμάτων από τις συνδέσεις σας, ως προστασία ασφαλείας.]]> + Χρησιμοποιεί περισσότερη μπαταρία! Η εφαρμογή εκτελείται πάντα στο παρασκήνιο - οι ειδοποιήσεις εμφανίζονται αμέσως.]]> + Η βάση δεδομένων της συνομιλίας εξάχθηκε + κλήση + Κακή διεύθυνση Desktop + Μεταφορά απο άλλη συσκευή στη νέα συσκευή και σαρώστε τον κωδικό QR.]]> + Με προφίλ συνομιλίας (προεπιλογή) ή μέσω σύνδεσης (BETA). + Κάμερα και μικρόφωνο + 6 νέες γλώσσες διεπαφής + Καλό για την μπαταρία. Η εφαρμογή ελέγχει για την παραλαβή μηνυμάτων κάθε 10 λεπτά. Ενδέχεται να χάσετε κλήσεις ή επείγοντα μηνύματα.]]> + Επισύναψη + Διακοπή αλλαγής διεύθυνσης; + Επιλέξτε ένα αρχείο + Όλα τα νέα μηνύνματα απο %s θα αποκρυφθούν! + Δεν είναι δυνατή η λήψη του αρχείου + Πιστοποίηση + Όλα τα μηνύματα θα διαγραφούν - αυτή η ενέργεια δεν μπορεί να αντιστραφεί! + Ελέγχει νέα μηνύματα κάθε 10 λεπτά για έως και 1 λεπτό + Η εφαρμογή μπορεί να λαμβάνει ειδοποιήσεις μόνο όταν εκτελείται, καμία υπηρεσία δεν θα ξεκινήσει στο παρασκήνιο + Μπορεί να απενεργοποιηθεί μέσω των ρυθμίσεων – οι ειδοποιήσεις θα εξακολουθούν να εμφανίζονται ενώ η εφαρμογή εκτελείται.]]> + Επιτρέψτε τις επαφές σας να χρησιμοποιούν αντιδράσεις μηνυμάτων. + Και εσείς και η επαφή σας μπορείτε να στείλετε μηνύματα που εξαφανίζονται. + Κάμερα μη διαθέσιμη + Ελέγξτε την διεύθυνση του διακομιστή και δοκιμάστε ξανά. + Επιτρέψτε αντιδράσεις μηνυμάτων εφόσον οι επαφές σας το επιτρέπουν. + %1$d μήνυμα/τα παραλήφθηκε/καν. + Κλήσεις απογορευμένες! + Δεν είναι δυνατή η αποστολή μηνύματος + Η κλήση έχει ήδη τερματιστεί! + Ο κωδικός πρόσβασης της εφαρμογής αντικαθίσταται με κωδικό πρόσβασης αυτοκαταστροφής. + Το Android Keystore θα χρησιμοποιηθεί για την ασφαλή αποθήκευση της φράσης πρόσβασης μετά την επανεκκίνηση της εφαρμογής ή την αλλαγή της φράσης πρόσβασης - θα επιτρέπει τη λήψη ειδοποιήσεων. + Δεν είναι δυνατή η πρόσβαση στο Keystore για αποθήκευση του κωδικού πρόσβασης της βάσης δεδομένων + Αποκλεισμός μέλους + Αποκλεισμός μέλους; + Προτιμήσεις συνομιλίας + Καλύτερα μηνύματα + Εφαρμογή + Συνέναιση υποβάθμισης + Κάμερα + κλήση ήχου + Αρχειοθετήστε τις επαφές για να συνομιλήσετε αργότερα. + Όλα τα προφίλ + %1$d μηνύμα/τα παραλείφθηκε/καν + κακό μήνυμα hash + κακό αναγνωριστικό μηνύματος + Απάντηση κλήσης + Κακό αναγνωριστικό μηνύματος + ΣΥΝΟΜΙΛΙΕΣ + Η βάση δεδεδομένων της συνομιλίας εισάχθηκε + "συμφωνία κρυπτογράφησης για %s…" + Να επιτραπούν οι κλήσεις; + Αποκλεισμός μέλους για όλους; + Κλήσεις ήχου και βίντεο + προσπάθειες + Θέμα συνομιλίας + Καλύτερη ασφάλεια✅ + Καλύτερη εμπειρία χρήστη + Δεν είναι δυνατή η κλήση μέλους ομάδας + Ζήτησε να λάβει την εικόνα + για κάθε επαφή και μέλος ομάδας .\nΛάβετε υπόψη: εάν έχετε πολλές συνδέσεις, η κατανάλωση της μπαταρίας και της κυκλοφορίας μπορεί να είναι σημαντικά υψηλότερη και ορισμένες συνδέσεις μπορεί να αποτύχουν.]]> + Προσθήκη επαφής : για να δημιουργήσετε έναν νέο σύνδεσμο πρόσκλησης ή να συνδεθείτε μέσω ενός συνδέσμου που λάβατε.]]> + Καλύτερο για τη ζωή της μπαταρίας . Θα λαμβάνετε ειδοποιήσεις μόνο όταν εκτελείται η εφαρμογή (ΧΩΡΙΣ υπηρεσία παρασκηνίου).]]> + Beta + Καλύτερες κλήσεις \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml index 553f70e31a..8c88c1ea67 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml @@ -711,7 +711,7 @@ Envoyer un message dynamique Envoyez un message dynamique - il sera mis à jour pour le⸱s destinataire⸱s au fur et à mesure que vous le tapez Envoyer - Le rôle sera changé pour «%s». Le membre va recevoir une nouvelle invitation. + Son rôle est désormais %s. Le membre recevra une nouvelle invitation. LIVE Inviter des membres Vous pouvez partager un lien ou un code QR - n\'importe qui pourra rejoindre le groupe. Vous ne perdrez pas les membres du groupe si vous le supprimez par la suite. @@ -722,7 +722,7 @@ Seuls les propriétaires du groupe peuvent modifier les préférences du groupe. POUR TERMINAL Changer le rôle du groupe \? - Le rôle sera changé pour «%s». Les membres du groupe seront notifiés. + Son rôle est désormais %s. Tous les membres du groupe en seront informés. Contact vérifié⸱e Effacer %d contact·s sélectionné·e·s @@ -1163,7 +1163,7 @@ La mise à jour du profil sera envoyée à vos contacts. Guide de l\'utilisateur.]]> Enregistrer les paramètres de validation automatique - Pour se connecter, votre contact peut scanner le code QR ou utiliser le lien dans l\'application. + Pour se connecter, votre contact peut scanner un code QR ou utiliser un lien dans l\'app. Le code d\'accès de l\'application est remplacé par un code d\'autodestruction. Activer l\'autodestruction Un profil de chat vierge portant le nom fourni est créé et l\'application s\'ouvre normalement. @@ -1528,14 +1528,14 @@ Envoi des 100 derniers messages aux nouveaux membres. Ajouter un contact : pour créer un nouveau lien d\'invitation ou se connecter via un lien que vous avez reçu.]]> Ne pas envoyer d\'historique aux nouveaux membres. - Ou présenter ce code + Ou montrez ce code Les 100 derniers messages sont envoyés aux nouveaux membres. Le code scanné n\'est pas un code QR de lien SimpleX. Le texte collé n\'est pas un lien SimpleX. Autoriser l\'accès à la caméra Vous pouvez à nouveau consulter le lien d\'invitation dans les détails de la connexion. Conserver l\'invitation inutilisée ? - Partager ce lien d\'invitation unique + Partagez ce lien d\'invitation unique Créer un groupe : pour créer un nouveau groupe.]]> Historique visible Code d\'accès à l\'app @@ -1544,7 +1544,7 @@ Création d\'un lien… Ou scanner le code QR Code QR invalide - Ajouter le contact + Ajouter un contact Appuyez pour scanner Conserver Appuyez pour coller le lien @@ -1883,7 +1883,7 @@ Téléchargement %s (%s) Erreur de reconnexion au serveur inactif - Scanner / Coller le lien + Scanner / Coller un lien Le message peut être transmis plus tard si le membre devient actif. Reconnecter tous les serveurs connectés pour forcer la livraison des messages. Cette méthode utilise du trafic supplémentaire. Sessions de transport @@ -2069,4 +2069,61 @@ Connexion TCP Certains fichiers n\'ont pas été exportés Vous pouvez migrer la base de données exportée. + %1$d erreur(s) de fichier :\n%2$s + %1$d autre(s) erreur(s) de fichier. + Erreur lors du transfert de messages + %1$d fichier(s) est(sont) en cours de téléchargement. + %1$s messages non transférés + Télécharger + Transfert de messages… + Les messages ont été supprimés après avoir été sélectionnés. + Erreur lors du changement de profil + Sélectionner un profil de discussion + Partager le profil + Votre connexion a été déplacée vers %s mais une erreur inattendue s\'est produite lors de la redirection vers le profil. + Ne pas utiliser d\'identifiants avec le proxy. + Erreur lors de l\'enregistrement du proxy + Mot de passe + Authentification proxy + Utilisez des identifiants de proxy différents pour chaque connexion. + Vos informations d\'identification peuvent être envoyées non chiffrées. + Le téléchargement de %1$d fichier(s) a échoué. + %1$d fichier(s) a(ont) été supprimé(s). + Sécurité accrue ✅ + Une meilleure expérience pour l\'utilisateur + %1$d fichier(s) n\'a (n\'ont) pas été téléchargé(s). + Session de l\'app + Meilleures dates de messages. + Transférer %1$s message(s) ? + Transfert de %1$s messages + Assurez-vous que la configuration du proxy est correcte. + Transférer les messages sans les fichiers ? + De nouveaux identifiants SOCKS seront utilisés chaque fois que vous démarrerez l\'application. + Rien à transférer ! + Ouvrez Safari Paramètres / Sites web / Microphone, puis choisissez Autoriser pour localhost. + Sauvegarde de %1$s messages + L\'archive de la base de données envoyée sera définitivement supprimée des serveurs. + Utilisez des identifiants de proxy différents pour chaque profil. + Utiliser des identifiants aléatoires + Nom d\'utilisateur + Les messages seront supprimés - il n\'est pas possible de revenir en arrière ! + BASE DE DONNÉES DU CHAT + Mode système + Serveur + De nouveaux identifiants SOCKS seront utilisées pour chaque serveur. + Erreur lors de l\'initialisation de WebView. Assurez-vous que WebView est installé et que l\'architecture supportée est arm64.\nErreur : %s + Son muet + Coin + Forme du message + Queue + Cliquez sur le bouton info près du champ d\'adresse pour autoriser l\'utilisation du microphone. + Pour passer des appels, autorisez l\'utilisation de votre microphone. Mettez fin à l\'appel et essayez d\'appeler à nouveau. + Supprimer l\'archive ? + Appels améliorés + Forme des messages personnalisable. + Supprimer ou modérer jusqu\'à 200 messages. + Transférez jusqu\'à 20 messages à la fois. + Protocoles SimpleX audité par Trail of Bits. + Passer de l\'audio à la vidéo pendant l\'appel. + Changer de profil de chat pour les invitations à usage unique. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index 5edf8e786f..6fe624d621 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -21,7 +21,7 @@ A SimpleXről Kiemelés fogadott hívás - Hozzáférés a kiszolgálókhoz SOCKS proxy segítségével a %d porton? A proxyt el kell indítani, mielőtt engedélyezné ezt az opciót. + Hozzáférés a kiszolgálókhoz SOCKS proxyn keresztül a(z) %d porton? A proxyt el kell indítani, mielőtt engedélyezné ezt az opciót. Elfogadás Elfogadás gombra fent, majd: @@ -38,10 +38,9 @@ %s visszavonva Előre beállított kiszolgálók hozzáadása A hívások kezdeményezése le van tiltva ebben a csevegésben. - Minden egyes kapcsolathoz és csoporttaghoz külön TCP-kapcsolat (és SOCKS-hitelesítőadat) lesz használva. -\nMegjegyzés: ha sok kapcsolata van, az akkumulátor-használat és az adatforgalom jelentősen megnövekedhet, és néhány kapcsolódási kísérlet sikertelen lehet. + Az összes ismerőséhez és csoporttaghoz külön TCP-kapcsolat (és SOCKS-hitelesítőadat) lesz használva.\nMegjegyzés: ha sok kapcsolata van, az akkumulátor-használat és az adatforgalom jelentősen megnövekedhet, és néhány kapcsolódási kísérlet sikertelen lehet.]]> hivatkozás előnézetének visszavonása - Minden egyes kapcsolathoz és csoporttaghoz külön TCP-kapcsolat (és SOCKS-hitelesítőadat) lesz használva.]]> + Az összes csevegési profiljához az alkalmazásban külön TCP-kapcsolat (és SOCKS-hitelesítőadat) lesz használva.]]> Mindkét fél küldhet eltűnő üzeneteket. Az Android Keystore-t a jelmondat biztonságos tárolására használják - lehetővé teszi az értesítési szolgáltatás működését. Hibás az üzenet hasító értéke @@ -71,7 +70,7 @@ \nElérhető a v5.1-ben" Mindkét fél véglegesen törölheti az elküldött üzeneteket. (24 óra) Továbbfejlesztett csoportok - Minden üzenet törlésre kerül - ez a művelet nem vonható vissza! Az üzenetek CSAK az Ön számára törlődnek. + Az összes üzenet törlésre kerül - ez a művelet nem vonható vissza! Az üzenetek CSAK az Ön számára törlődnek. Hívás befejeződött HÍVÁSOK és további %d esemény @@ -87,10 +86,10 @@ Az üzenetreakciók küldése csak abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. Vissza Kikapcsolható a beállításokban – az értesítések továbbra is megjelenítésre kerülnek amíg az alkalmazás fut.]]> - Az adminok hivatkozásokat hozhatnak létre a csoportokhoz való csatlakozáshoz. + Az adminisztrátorok hivatkozásokat hozhatnak létre a csoportokhoz való csatlakozáshoz. Hívások a zárolási képernyőn: titkosítás elfogadása… - Ismerős meghívása nem lehetséges! + Nem lehet meghívni az ismerőst! téves üzenet ID Kapcsolatkérések automatikus elfogadása Megjegyzés: NEM fogja tudni helyreállítani, vagy megváltoztatni a jelmondatot abban az esetben, ha elveszíti.]]> @@ -99,18 +98,18 @@ Hozzáadás egy másik eszközhöz Az üzenetreakciók küldése engedélyezve van. Fájlelőnézet visszavonása - Minden csoporttag kapcsolatban marad. + Az összes csoporttag kapcsolatban marad. Több akkumulátort használ! Az alkalmazás mindig fut a háttérben - az értesítések azonnal megjelennek.]]> Letiltás - admin + adminisztrátor Fénykép előnézet visszavonása - A jelkód megadása után minden adat törlésre kerül. + A jelkód megadása után az összes adat törlésre kerül. Felkérték a videó fogadására Letiltás Még néhány dolog Hitelesítés visszavonva A fájlok- és a médiatartalmak küldése engedélyezve van. - Minden csevegés és üzenet törlésre kerül - ez a művelet nem vonható vissza! + Az összes csevegés és üzenet törlésre kerül - ez a művelet nem vonható vissza! hanghívás félkövér Az alkalmazás jelkód helyettesítésre kerül egy önmegsemmisítő jelkóddal. @@ -120,14 +119,14 @@ mindig A hívás már befejeződött! Engedélyezés - Minden ismerősével kapcsolatban marad. + Az összes ismerősével kapcsolatban marad. Élő csevegési üzenet visszavonása Az üzenetek végleges törlése csak abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. (24 óra) Hang- és videóhívások hibás az üzenet hasító értéke Mindig fut Az Android Keystore biztonságosan fogja tárolni a jelmondatot az alkalmazás újraindítása, vagy a jelmondat megváltoztatás után - lehetővé teszi az értesítések fogadását. - Minden alkalmazásadat törölve. + Az összes alkalmazásadat törölve. Legjobb akkumulátoridő. Csak akkor kap értesítéseket, amikor az alkalmazás meg van nyitva. (NINCS háttérszolgáltatás.)]]> Megjelenés Az akkumulátor-optimalizálás aktív, ez kikapcsolja a háttérszolgáltatást és az új üzenetek rendszeres lekérdezését. A beállításokban újraengedélyezheti. @@ -151,7 +150,7 @@ hívás folyamatban Képek automatikus elfogadása A hívások kezdeményezése engedélyezve van az ismerősei számára. - ALKALMAZÁS IKON + ALKALMAZÁSIKON Kiszolgáló hozzáadása QR-kód beolvasásával. Az eltűnő üzenetek küldése engedélyezve van. Az eltűnő üzenetek küldése csak abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. @@ -162,7 +161,7 @@ Mindkét fél küldhet üzenetreakciókat. Mindkét fél tud hívásokat kezdeményezni. Sikertelen hitelesítés - Minden %s által írt új üzenet elrejtésre kerül! + Az összes %s által írt új üzenet elrejtésre kerül! Alkalmazás verzió: v%s A hívások kezdeményezése csak abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. Kiszolgáló hozzáadása @@ -220,7 +219,7 @@ Kapcsolat Név helyesbítése erre: %s? Időtúllépés kapcsolódáskor - Kapcsolódás %1$s által? + Kapcsolódás a következővel: %1$s? Létrehozás Ismerős beállításai Kapcsolat @@ -230,7 +229,7 @@ Ismerős engedélyezi Rejtett név: Társítás számítógéppel - Környezeti ikon + Szövegkörnyezeti ikon Kapcsolódás egy hivatkozáson keresztül Ismerősök Kapcsolódási hiba @@ -255,12 +254,12 @@ Törölve ekkor: %s Törölve ekkor: Kínai és spanyol kezelőfelület - Ismerősök meghívása nem lehetséges! + Nem lehet meghívni az ismerősöket! A csevegés leállt Sötét Profil létrehozása törölt csoport - Törlés mindenkinél + Törlés az összes tagnál Hivatkozás létrehozása Csevegési beállítások Csevegési archívum @@ -327,7 +326,7 @@ kapcsolódás (bejelentve) Csoporthivatkozás létrehozása Csevegési konzol - Fájlok törlése minden csevegési profilból + Fájlok törlése az összes csevegési profilból Sorbaállítás törlése Ismerős törlése Létrehozva ekkor: %1$s @@ -363,8 +362,8 @@ Kiszolgáló törlése Az eszközön nincs beállítva a képernyőzár. A SimpleX-zár ki van kapcsolva. Letiltás - Letiltás minden csoport számára - Engedélyezés minden csoport számára + Letiltás az összes csoport számára + Engedélyezés az összes csoport számára engedélyezve az ismerős számára Az eltűnő üzenetek küldése le van tiltva ebben a csoportban. Cím törlése @@ -384,7 +383,7 @@ Az adatbázis-jelmondat eltér a Keystore-ban lévőtől. Közvetlen üzenetek E-mail - Letiltás mindenki számára + Letiltás az összes tag számára Fejlesztői eszközök Adatbázis-jelmondat %d nap @@ -427,7 +426,7 @@ Törlés, és az ismerős értesítése letiltva %d másodperc - Minden fájl törlése + Az összes fájl törlése Az adatbázis titkosításra kerül. Adatbázis-jelmondat és -exportálás Az adatbázis titkosításra kerül és a jelmondat a Keystore-ban lesz tárolva. @@ -462,7 +461,7 @@ %d fájl %s összméretben A csevegés megnyitásához adja meg az adatbázis jelmondatát. %dnap - Engedélyezés mindenki számára + Engedélyezés az összes tag számára A kézbesítési jelentések le vannak tiltva! Kibontás Hiba az üzenet küldésekor @@ -528,11 +527,11 @@ Kézbesítési jelentések engedélyezése? Hiba a csoportprofil mentésekor hiba - A fájl törölve lesz a kiszolgálóról. + A fájl törölve lesz a kiszolgálókról. Akkor is, ha le van tiltva a beszélgetésben. Gyorsabb csatlakozás és megbízhatóbb üzenetkézbesítés. Zárolás engedélyezése - SEGÍTSÉG + SÚGÓ Teljesen decentralizált - csak a tagok számára látható. Fájl: %s Hívás befejezése @@ -587,7 +586,7 @@ Hiba A csoportmeghívó már nem érvényes, a küldője eltávolította. A csoport teljes neve: - segítség + súgó Önmegsemmisítő jelkód engedélyezése KÍSÉRLETI Hiba a cím megváltoztatásának megszakításakor @@ -604,7 +603,7 @@ Tovább csökkentett akkumulátor-használat Hiba a csevegés megállításakor titkosítás rendben %s számára - A csoport törlésre kerül minden tag számára - ez a művelet nem vonható vissza! + A csoport törlésre kerül az összes tag számára - ez a művelet nem vonható vissza! Titkosítás javítása az adatmentések helyreállítása után. Hiba a csevegési adatbázis törlésekor Teljes hivatkozás @@ -697,7 +696,7 @@ Nem fogadott hívás Világos Az üzenet törlésre kerül - ez a művelet nem vonható vissza! - Markdown segítség + Markdown súgó Rejtett üzenet Régi adatbázis-archívum Speciális beállítások @@ -728,9 +727,7 @@ Helytelen biztonsági kód! Ez akkor fordulhat elő, ha Ön vagy az ismerőse régi adatbázis biztonsági mentést használt. Új számítógép-alkalmazás! - Most már az adminok is: -\n- törölhetik a tagok üzeneteit. -\n- letilthatnak tagokat (megfigyelő szerepkör) + Most már az adminisztrátorok is:\n- törölhetik a tagok üzeneteit.\n- letilthatnak tagokat (megfigyelő szerepkör) meghívta őt: %1$s Az üzenetreakciók küldése le van tiltva ebben a csoportban. Nem @@ -740,7 +737,7 @@ Új tag szerepköre Kikapcsolva Érvénytelen hivatkozás! - Újdonságok a %s verzióban + Újdonságok a(z) %s verzióban Érvénytelen kiszolgálócím! k soha @@ -809,9 +806,9 @@ Csak a csoporttulajdonosok módosíthatják a csoportbeállításokat. Nincsenek előzmények Érvénytelen QR-kód - Olvasottnak jelölés + Megjelölés olvasottként ÉLŐ - Olvasatlannak jelölés + Megjelölés olvasatlanként Több Bejelentkezés hitelesítőadatokkal érvénytelen üzenet formátum @@ -821,7 +818,7 @@ (ez az eszköz: v%s)]]> ajánlott %s Csoport elhagyása - Minden %s által írt üzenet megjelenik! + Az összes %s által írt üzenet megjelenik! Ha a SimpleX Chatnek nincs felhasználó-azonosítója, hogyan lehet mégis üzeneteket küldeni?]]> Ez akkor fordulhat elő, ha:\n1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak.\n2. Az üzenet visszafejtése sikertelen volt, mert Ön, vagy az ismerőse régebbi adatbázis biztonsági mentést használt.\n3. A kapcsolat sérült. megfigyelő @@ -872,7 +869,7 @@ Bejövő hanghívás Kulcstartóhiba Csatlakozik a csoporthoz? - Az inkognitómód védi személyes adatait azáltal, hogy minden ismerőshöz új véletlenszerű profilt használ. + Az inkognitómód védi személyes adatait azáltal, hogy az összes ismerőséhez új, véletlenszerű profilt használ. - stabilabb üzenetkézbesítés.\n- picit továbbfejlesztett csoportok.\n- és még sok más! Üzenetreakciók Nincs társított hordozható eszköz @@ -922,7 +919,7 @@ Egyszer használható hivatkozás megosztása Hiba az adatbázis visszaállításakor %s és %s - Engedélyezve + Ön engedélyezi Csökkentett akkumulátor-használat Mentés és az ismerősök értesítése Előnézet @@ -956,7 +953,7 @@ A kapcsolódáshoz az ismerőse beolvashatja a QR-kódot, vagy használhatja az alkalmazásban található hivatkozást. visszaigazolás fogadása… Biztonsági kód beolvasása az ismerősének alkalmazásából. - Lépjen kapcsolatba a csoport adminnal. + Lépjen kapcsolatba a csoport adminisztrátorával. Videó bekapcsolva Profilnév: Beillesztés @@ -970,7 +967,7 @@ Cím Üzenet elküldése Adatbázismentés visszaállítása - Visszavon + Visszavonás Kérje meg az ismerősét, hogy engedélyezze a hangüzenetek küldését. egyszer használható hivatkozást osztott meg A hivatkozás megnyitása a böngészőben gyengítheti az adatvédelmet és a biztonságot. A megbízhatatlan SimpleX-hivatkozások pirossal vannak kiemelve. @@ -1142,7 +1139,7 @@ Adatbázismentés visszaállítása? Üzenetek fogadása… %s és %s kapcsolódott - megfigyelő szerep + Ön megfigyelő Port Jelkód beállítása Újdonságok @@ -1281,7 +1278,7 @@ Videók és fájlok 1Gb méretig TCP kapcsolat időtúllépése A(z) %1$s nevű profiljának SimpleX-címe megosztásra fog kerülni. - Ön már kapcsolódva van ehhez: %1$s. + Ön már kapcsolódott a következőhöz: %1$s. Jelenlegi csevegési adatbázis TÖRLÉSRE és FELCSERÉLÉSRE kerül az importált által! \nEz a művelet nem vonható vissza - profiljai, ismerősei, csevegési üzenetei és fájljai véglegesen törölve lesznek. Ötletek és javaslatok @@ -1312,14 +1309,14 @@ Az ismerősei továbbra is kapcsolódva maradnak. A kiszolgálónak engedélyre van szüksége a várólisták létrehozásához, ellenőrizze jelszavát Az adatbázis nem működik megfelelően. Koppintson ide a további információkért - A fájl küldése leállt. + A fájl küldése le fog állni. Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott ismerősétől érkező üzenetek fogadására szolgál. Nem sikerült hitelesíteni; próbálja meg újra. - Az üzenet minden tag számára moderáltként lesz megjelölve. + Az üzenet az összes tag számára moderáltként lesz megjelölve. Értesítések fogadásához adja meg az adatbázis jelmondatát A teszt a(z) %s lépésnél sikertelen volt. Az alkalmazás elindításához vagy 30 másodpercnyi háttérben töltött idő után, az alkalmazáshoz való visszatéréshez hitelesítésre lesz szükség. - Az üzenet minden tag számára törlésre kerül. + Az üzenet az összes tag számára törlésre kerül. A videó nem dekódolható. Próbálja ki egy másik videóval, vagy lépjen kapcsolatba a fejlesztőkkel. Ez a szöveg a „Beállításokban” érhető el A profilja elküldésre kerül az ismerőse számára, akitől ezt a hivatkozást kapta. @@ -1332,7 +1329,7 @@ A biztonsága érdekében kapcsolja be a SimpleX-zár funkciót. \nA funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beállítására az eszközén. A videó akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később! - Hálózati kapcsolat ellenőrzése a következővel: %1$s, és próbálja újra. + Ellenőrizze a hálózati kapcsolatát a következővel: %1$s, és próbálja újra. A SimpleX-zár az „Adatvédelem és biztonság” menüben kapcsolható be. Az alkalmazás összeomlott Ellenőrizze, hogy a megfelelő hivatkozást használta-e, vagy kérje meg az ismerősét, hogy küldjön egy másikat. @@ -1340,20 +1337,20 @@ Érvénytelen fájl elérési útvonalat osztott meg. Jelentse a problémát az alkalmazás fejlesztőinek. Már van egy csevegési profil ugyanezzel a megjelenített névvel. Válasszon egy másik nevet. Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott ismerősétől érkező üzenetek fogadására szolgál (hiba: %1$s). - A fájl fogadása leállt. + A fájl fogadása le fog állni. Ne felejtse el, vagy tárolja biztonságosan – az elveszett jelszót nem lehet visszaállítani! A videó akkor érkezik meg, amikor a küldője befejezte annak feltöltését. egyszer használható hivatkozást osztott meg inkognitóban - Már kapcsolódott ahhoz a kiszolgálóhoz, amely az adott ismerősétől érkező üzenetek fogadására szolgál. + Ön már kapcsolódott ahhoz a kiszolgálóhoz, amely az adott ismerősétől érkező üzenetek fogadására szolgál. Később engedélyezheti a „Beállításokban” Akkor lesz kapcsolódva a csoporthoz, amikor a csoport tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később! különböző átköltöztetés az alkalmazásban/adatbázisban: %s / %s - %1$s.]]> + %1$s.]]> Profil felfedése Ez nem egy érvényes kapcsolattartási hivatkozás! A végpontok közötti titkosítás hitelesítéséhez hasonlítsa össze (vagy olvassa be a QR-kódot) az ismerőse eszközén lévő kóddal. A csevegési adatbázis legfrissebb verzióját CSAK egy eszközön kell használnia, ellenkező esetben előfordulhat, hogy az üzeneteket nem fogja megkapni valamennyi ismerősétől. - Ez a beállítás a jelenlegi csevegési profilban lévő üzenetekre érvényes + Ez a beállítás csak a jelenlegi csevegési profiljában lévő üzenetekre vonatkozik Meghívást kapott a csoportba. Csatlakozzon, hogy kapcsolatba léphessen a csoport tagjaival. Ez a csoport már nem létezik. A csatlakozás már folyamatban van a csoporthoz ezen a hivatkozáson keresztül. @@ -1386,7 +1383,7 @@ A profilja az eszközén van tárolva és csak az ismerőseivel kerül megosztásra. A SimpleX-kiszolgálók nem láthatják a profilját. Ön megváltoztatta %s szerepkörét erre: %s Csoportmeghívó elutasítva - Az adatvédelem érdekében, a más csevegési platformokon megszokott felhasználó-azonosítók helyett, a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, minden egyes ismerőshöz egy-egy különbözőt. + Az adatvédelem érdekében (a más csevegési platformokon megszokott felhasználó-azonosítók helyett) a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, az összes ismerőséhez különbözőt. (a megosztáshoz az ismerősével) Csoportmeghívó elküldve Átvitel-izoláció módjának frissítése? @@ -1401,7 +1398,7 @@ Fejlesztés és a csevegés megnyitása Engedélyeznie kell a hangüzenetek küldését az ismerőse számára, hogy hangüzeneteket küldhessenek egymásnak. fogadja az üzeneteket, ismerősöket – a kiszolgálók, amelyeket az üzenetküldéshez használ.]]> - %1$s csoport tagja.]]> + %1$s nevű csoport tagja.]]> cím megváltoztatva Az ismerősei engedélyezhetik a teljes üzenet törlést. A jelmondatot minden alkalommal meg kell adnia, amikor az alkalmazás elindul - nem az eszközön kerül tárolásra. @@ -1418,18 +1415,18 @@ A csevegési szolgáltatás elindítható a „Beállítások / Adatbázis” menüben vagy az alkalmazás újraindításával. Kód hitelesítése a hordozható eszközön Csatlakozott ehhez a csoporthoz. Kapcsolódás a meghívó csoporttaghoz. - a SimpleX Chat fejlesztőivel, ahol bármiről kérdezhet és értesülhet az újdonságokról.]]> + a SimpleX Chat fejlesztőivel, ahol bármiről kérdezhet és értesülhet a friss hírekről.]]> Nem kötelező üdvözlőüzenettel. Ismeretlen adatbázishiba: %s Elrejtheti vagy lenémíthatja a felhasználó-profiljait - koppintson (vagy számítógép-alkalmazásban kattintson) hosszan a profilra a felugró menühöz. Inkognitómód használata kapcsolódáskor. Megoszthat egy hivatkozást vagy QR-kódot - így bárki csatlakozhat a csoporthoz. Ha a csoport később törlésre kerül, akkor nem fogja elveszíteni annak tagjait. Csatlakozott ehhez a csoporthoz - %1$s csoporthoz!]]> + %1$s nevű csoporthoz!]]> A hangüzenetek küldése le van tiltva ebben a csevegésben. Ön irányítja csevegését! Kód hitelesítése a számítógépen - Az időzóna védelme érdekében a kép-/hangfájlok UTC-t használnak. + Az időzóna védelmének érdekében a kép-/hangfájlok UTC-t használnak. A kapcsolatkérés elküldésre kerül ezen csoporttag számára. Inkognitó-profil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott. Már küldött egy kapcsolatkérést ezen a címen keresztül! @@ -1437,7 +1434,7 @@ Amikor az emberek kapcsolatot kérnek, Ön elfogadhatja vagy elutasíthatja azokat. Megjelenítendő üzenet beállítása az új tagok számára! Köszönet a felhasználóknak - hozzájárulás a Weblate-en! - A kézbesítési jelentés küldése minden ismerőse számára engedélyezésre kerül. + A kézbesítési jelentés küldése az összes ismerőse számára engedélyezésre kerül. Protokoll időtúllépése KB-onként Az adatbázis-jelmondat megváltoztatására tett kísérlet nem fejeződött be. Ez a művelet nem vonható vissza - a kiválasztottnál korábban küldött és fogadott üzenetek törlésre kerülnek. Ez több percet is igénybe vehet. @@ -1448,7 +1445,7 @@ A kézbesítési jelentések engedélyezve vannak %d ismerősnél Küldés ezen keresztül: Köszönet a felhasználóknak - hozzájárulás a Weblate-en! - A kézbesítési jelentések küldése engedélyezésre kerül az összes látható csevegési profilban lévő minden ismerős számára. + A kézbesítési jelentések küldése engedélyezésre kerül az összes látható csevegési profilban lévő összes ismerőse számára. Bluetooth támogatás és további fejlesztések. Ez a funkció még nem támogatott. Próbálja meg a következő kiadásban. A bejegyzés frissítve: %s @@ -1465,7 +1462,7 @@ A közvetítő-kiszolgáló csak szükség esetén kerül használatra. Egy másik fél megfigyelheti az IP-címet. Rendszerhitelesítés helyetti beállítás. A fogadó cím egy másik kiszolgálóra változik. A címváltoztatás a feladó online állapotba kerülése után fejeződik be. - A csevegés megállítása a csevegési adatbázis exportálásához, importálásához vagy törléséhez. A csevegés megállítása alatt nem tud üzeneteket fogadni és küldeni. + A csevegés megállítása a csevegési adatbázis exportálásához, importálásához vagy törléséhez. A csevegés megállításakor nem tud üzeneteket fogadni és küldeni. Jelmondat mentése a Keystore-ba Köszönet a felhasználóknak - hozzájárulás a Weblate-en! Jelmondat mentése a beállításokban @@ -1476,7 +1473,7 @@ Az utolsó üzenet tervezetének megőrzése a mellékletekkel együtt. A mentett WebRTC ICE-kiszolgálók eltávolításra kerülnek. A kézbesítési jelentések engedélyezve vannak %d csoportban - A szerepkör meg fog változni erre: %s. A csoportban mindenki értesítve lesz. + A szerepkör meg fog változni erre: %s. A csoportban az összes tag értesítve lesz. Profil és kiszolgálókapcsolatok Egy üzenetküldő- és alkalmazásplatform, amely védi az adatait és biztonságát. A profil aktiválásához koppintson az ikonra. @@ -1505,14 +1502,14 @@ A jelmondat a beállításokban egyszerű szövegként van tárolva. Konzol megjelenítése új ablakban Az előző üzenet hasító értéke különbözik. - Ezek a beállítások a jelenlegi profiljára vonatkoznak + Ezek a beállítások csak a jelenlegi profiljára vonatkoznak Várjon, amíg a fájl betöltődik a társított hordozható eszközről GitHub tárolónkban.]]> hiba a tartalom megjelenítésekor hiba az üzenet megjelenítésekor Láthatóvá teheti a SimpleXbeli ismerősei számára a „Beállításokban”. Legfeljebb az utolsó 100 üzenet kerül elküldésre az új tagok számára. - A beolvasott QR-kód nem egy SimpleX QR-kód hivatkozás. + A beolvasott QR-kód nem egy SimpleX-QR-kód-hivatkozás. A beillesztett szöveg nem egy SimpleX-hivatkozás. A meghívó-hivatkozását újra megtekintheti a kapcsolat részleteinél. Csevegés indítása? @@ -1539,7 +1536,7 @@ Vagy QR-kód beolvasása Érvénytelen QR-kód Megtartás - Keresés, vagy SimpleX-hivatkozás beillesztése + Keresés vagy SimpleX-hivatkozás beillesztése Belső hibák megjelenítése Kritikus hiba Belső hiba @@ -1591,7 +1588,7 @@ Létrehozva ekkor: Mentett üzenet Megosztva ekkor: %s - Minden üzenet törlésre kerül – ez a művelet nem vonható vissza! + Az összes üzenet törlésre kerül – ez a művelet nem vonható vissza! Továbbfejlesztett üzenetkézbesítés Csatlakozás csoportos beszélgetésekhez Hivatkozás beillesztése a kapcsolódáshoz! @@ -1604,16 +1601,16 @@ feloldotta %s letiltását Ön feloldotta %s letiltását letiltva - letiltva az admin által - Letiltva az admin által + letiltva az adminisztrátor által + Letiltva az adminisztrátor által letiltotta őt: %s - Letiltás mindenki számára - Mindenki számára letiltja ezt a tagot? - %d üzenetet letiltott az admin - Letiltás feloldása mindenki számára - Mindenki számára feloldja a tag letiltását? + Letiltás az összes tag számára + Az összes tag számára letiltja ezt a tagot? + %d üzenetet letiltott az adminisztrátor + Letiltás feloldása az összes tag számára + Az összes tag számára feloldja a tag letiltását? Ön letiltotta őt: %s - Hiba a tag mindenki számára való letiltásakor + Hiba a tag az összes csoporttag számára való letiltásakor Az üzenet túl nagy Az üdvözlőüzenet túl hosszú Az adatbázis átköltöztetése folyamatban van. @@ -1629,8 +1626,8 @@ Archiválás és feltöltés Feltöltés megerősítése Hiba az adatbázis törlésekor - Az adminok egy tagot mindenki számára letilthatnak. - Minden ismerőse, a beszélgetései és a fájljai biztonságosan titkosításra kerülnek, melyek részletekben feltöltődnek a beállított XFTP-közvetítő-kiszolgálóra. + Az adminisztrátorok egy tagot a csoport összes tagja számára letilthatnak. + Az összes ismerőse, -beszélgetése és -fájlja biztonságosan titkosításra kerülnek, melyek részletekben feltöltődnek a beállított XFTP-közvetítő-kiszolgálóra. Alkalmazásadatok átköltöztetése Adatbázis archiválása Átköltöztetés visszavonása @@ -1718,14 +1715,14 @@ A SimpleX-hivatkozások küldése le van tiltva A csoport tagjai küldhetnek SimpleX-hivatkozásokat. tulajdonosok - adminok - minden tag + adminisztrátorok + összes tag SimpleX-hivatkozások A hangüzenetek küldése le van tiltva A SimpleX-hivatkozások küldése le van tiltva ebben a csoportban. A SimpleX-hivatkozások küldése le van tiltva A fájlok- és médiatartalmak nincsenek engedélyezve - A SimpleX hivatkozások küldése engedélyezve van. + A SimpleX-hivatkozások küldése engedélyezve van. Számukra engedélyezve: mentett mentve innen: %s @@ -1793,7 +1790,7 @@ Ismeretlen kiszolgálók! Tor vagy VPN nélkül az IP-címe látható lesz az XFTP-közvetítő-kiszolgálók számára: \n%1$s. - Minden színmód + Összes színmód Fekete Színmód Sötét @@ -1826,7 +1823,7 @@ Perzsa kezelőfelület Védje IP-címét az ismerősei által kiválasztott üzenet-közvetítő-kiszolgálókkal szemben. \nEngedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. - Ismeretlen kiszolgálókról származó fájlok jóváhagyása. + Ismeretlen kiszolgálókról származó fájlok megerősítése. Javított üzenetkézbesítés Alkalmazás témájának visszaállítása Tegye egyedivé a csevegéseit! @@ -1862,7 +1859,7 @@ Inaktív tag Továbbított üzenet Az üzenet később is kézbesíthető, ha a tag aktívvá válik. - Még nincs közvetlen kapcsolat, az üzenetet az admin továbbítja. + Még nincs közvetlen kapcsolat, az üzenetet az adminisztrátor továbbítja. Hivatkozás beolvasása / beillesztése Konfigurált SMP-kiszolgálók Egyéb SMP-kiszolgálók @@ -1874,17 +1871,17 @@ Kapcsolódás Hibák Függőben - Statisztikagyűjtés kezdete: %s.\nMinden adat biztonságban van az eszközén. + Statisztikagyűjtés kezdete: %s.\nAz összes adat biztonságban van az eszközén. Elküldött üzenetek Proxyzott kiszolgálók Újrakapcsolódás a kiszolgálókhoz? Újrakapcsolódás a kiszolgálóhoz? Hiba a kiszolgálóhoz való újrakapcsolódáskor - Újrakapcsolódás minden kiszolgálóhoz + Újrakapcsolódás az összes kiszolgálóhoz Hiba a statisztikák visszaállításakor Visszaállítás - Minden statisztika visszaállítása - Minden statisztika visszaállítása? + Az összes statisztika visszaállítása + Az összes statisztika visszaállítása? A kiszolgálók statisztikái visszaállnak - ez a művelet nem vonható vissza! Részletes statisztikák Letöltve @@ -1892,7 +1889,7 @@ egyéb Összes fogadott üzenet Üzenetfogadási hibák - Újrakapcsolás + Újrakapcsolódás Üzenetküldési hibák Közvetlenül küldött Összes elküldött üzenet @@ -1924,7 +1921,7 @@ Elkészült Kapcsolódott kiszolgálók Konfigurált XFTP-kiszolgálók - Kapcsolódva + Kapcsolódott Jelenlegi profil További részletek visszafejtési hibák @@ -2017,12 +2014,12 @@ A(z) %1$s nevű ismerősével folytatott beszélgetéseit továbbra is megtekintheti a csevegések listájában. Üzenet… Kiválasztás - Az üzenetek minden tag számára moderáltként lesznek megjelölve. + Az üzenetek az összes tag számára moderáltként lesznek megjelölve. Nincs kiválasztva semmi Az üzenetek törlésre lesznek jelölve. A címzett(ek) képes(ek) lesz(nek) felfedni ezt az üzenetet. Törli a tagok %d üzenetét? %d kiválasztva - Az üzenetek minden tag számára törlésre kerülnek. + Az üzenetek az összes tag számára törlésre kerülnek. Csevegési adatbázis exportálva Kapcsolatok- és kiszolgálók állapotának megjelenítése. Kapcsolódjon gyorsabban az ismerőseihez. @@ -2066,8 +2063,8 @@ Rendszerbeállítások használata Csevegési profil kiválasztása Ne használja a hitelesítőadatokat proxyval. - Különböző proxy-hitelesítőadatok használata minden egyes profilhoz. - Különböző proxy-hitelesítőadatok használata minden egyes kapcsolathoz. + Különböző proxy-hitelesítőadatok használata az összes profilhoz. + Különböző proxy-hitelesítőadatok használata az összes kapcsolathoz. Jelszó Felhasználónév A hitelesítőadatai titkosítatlanul is elküldhetők. @@ -2101,7 +2098,7 @@ Kiszolgáló Minden alkalommal, amikor elindítja az alkalmazást, új SOCKS-hitelesítő-adatokat fog használni. Alkalmazás munkamenete - Minden egyes kiszolgálóhoz új SOCKS-hitelesítő-adatok legyenek használva. + Az összes kiszolgálóhoz új, SOCKS-hitelesítő-adatok legyenek használva. Kattintson a címmező melletti info gombra a mikrofon használatának engedélyezéséhez. Nyissa meg a Safari Beállítások / Weboldalak / Mikrofon menüt, majd válassza a helyi kiszolgálók engedélyezése lehetőséget. Hívások kezdeményezéséhez engedélyezze a mikrofon használatát. Fejezze be a hívást, és próbálja meg a hívást újra. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml index 9c256b4a4b..29c6430839 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml @@ -12,7 +12,7 @@ Tambah kontak Tentang SimpleX %1$d pesan gagal terdekripsi. - Tolong laporkan hal ini ke pengembang. + Mohon laporkan ke pengembang. 1 bulan 1 minggu Terima @@ -52,7 +52,7 @@ Batalkan Kamera tebal - menelepon… + memanggil… Bluetooth Panggilan diakhiri Ubah @@ -112,7 +112,7 @@ Tampilan macet Anda membagikan lokasi file yang tidak valid. Laporkan masalah ini ke pengembang aplikasi. error - Tuatan 1 kali pakai + Tautan sekali %1$d pesan yang terlewati %1$d pesan yang dilewati %s tidak didukung. Harap pastikan kamu menggunakan versi yang sama pada kedua perangkat.]]> @@ -170,7 +170,7 @@ Host Onion tidak akan digunakan. Selalu gunakan perutean pribadi. Pemberitahuan akan berhenti bekerja sampai kamu meluncurkan ulang aplikasi - Semua kontak kamu akan tetap terhubung. Pembaruan profil akan dikirim ke kontak kamu. + Semua kontak Anda akan tetap terhubung. Pembaruan profil akan dikirim ke kontak Anda. Tidak ada enkripsi ujung-ujung Kode sandi baru Mati @@ -263,4 +263,1024 @@ Izinkan untuk mengirim file dan media. Semua kontak kamu akan tetap terhubung. %1$d berkas telah dihapus. + Corak tambahan + Corak tambahan 2 + %1$s pesan tidak diteruskan + Koneksi aktif + admin + %1$d berkas galat :\n%2$s + Balas + Bagikan + Salin + Edit + Info + Tersimpan + Cari + Diteruskan + Disimpan dari + Perluas + Sedang + Hapus pesan? + Hapus %d pesan? + Pesan akan dihapus - Tindakan ini tidak dapat dibatalkan! + %1$d berkas masih diunduh. + Jawab panggilan + Pesan terkirim + Pesan diterima + Anggota tidak aktif + Pesan diteruskan + Cabut berkas? + Terlalu banyak gambar! + cari + panggilan + PENGATURAN + Untuk semua orang + Hentikan berkas + Cabut berkas + Mengirim berkas akan dihentikan. + Berhenti + Pengaturan + Berhenti kirim berkas? + Penerimaan berkas akan dihentikan. + Gambar disimpan ke Galeri + video + Kontak + %d pesan dihapus + dihapus + Mencoba terhubung ke server untuk menerima pesan dari kontak ini. + disimpan + diundang untuk terhubung + Deskripsi + Nama tampilan duplikat! + Mengirim + Diteruskan dari + Berhenti menerima berkas? + Tarik + Berkas akan dihapus dari server. + terkirim + Teruskan + Unduh + diedit + Obrolan + Selamat Datang! + Chat dengan pengembang + Setel nama kontak… + Pengaturan + Simpan + Kirim pesan langsung untuk terhubung + Berkas disimpan + Alamat server tujuan %1$s tidak kompatibel dengan pengaturan server penerusan %2$s. + Riwayat + Hapus + Perlihat + Sembunyikan + Grup kecil (maks 20) + Pilih + Hapus untuk saya + Gagal meneruskan pesan + %1$d berkas lainnya gagal. + Kontak sudah ada + menghubungkan… + kirim pesan langsung + Anda tidak memiliki obrolan + Memuat obrolan… + Ketuk untuk Hubungkan + Terhubung dengan %1$s? + Teruskan %1$s pesan? + Tidak ada yang diteruskan! + Teruskan pesan tanpa berkas? + Pesan dihapus setelah Anda memilihnya. + %1$d berkas tidak diunduh. + Unduh + Teruskan pesan… + Meneruskan %1$s pesan + Tautan SimpleX tidak diizinkan + Berkas dan media tidak diizinkan + Video + Menunggu video + Berkas tidak ditemukan + Tolak + Bagikan profil + Pilih profil obrolan + Kontak arsipan + Kode yang Anda pindai bukan kode QR tautan SimpleX. + Untuk memverifikasi enkripsi end-to-end dengan kontak Anda, bandingkan (atau pindai) kode pada perangkat Anda. + Kontak Anda + Pindai kode + Tandai terverifikasi + %s telah terverifikasi + %s belum terverifikasi + Bersihkan verifikasi + Konsol obrolan + Server pesan + Server SMP + Beri nilai aplikasi + Bintang di GitHub + Gunakan server SimpleX Chat? + Server SMP Anda + Server XFTP + Kredensial Anda mungkin dikirim tidak terenkripsi. + Username + Server XFTP Anda + Gunakan server SimpleX Chat. + Gunakan kredensial proxy yang berbeda untuk setiap koneksi. + Kata sandi + port %d + Host + Port + Gunakan proxy SOCKS? + Akses server melalui proxy SOCKS pada port %d? Proxy harus dimulai sebelum mengaktifkan opsi ini. + Gunakan koneksi Internet langsung? + Pastikan konfigurasi proxy sudah benar. + Gagal simpan proxy + Saat tersedia + Diperlukan + Jika Anda konfirmasi, server perpesanan akan dapat melihat alamat IP Anda, dan penyedia Anda - server mana yang Anda hubungkan. + Profil obrolan + Koneksi + Server tak dikenal + Kirim pesan secara langsung ketika alamat IP dilindungi dan server Anda atau tujuan tidak mendukung routing pribadi. + Kirim pesan secara langsung ketika server Anda atau server tujuan tidak mendukung routing pribadi. + Tampilkan status pesan + Terima otomatis + Undang teman + Buat + Nama tidak valid! + Buat profil + Masukkan nama Anda: + rahasia + miring + berwarna + menghubung panggilan… + panggilan berakhir %1$s + panggilan berlangsung + berakhir + Headphone + Kesalahan saat menginisialisasi WebView. Perbarui sistem Anda ke versi baru. Mohon hubungi pengembang.\nKesalahan: %s + Anda pilih siapa yang dapat terhubung. + Kebal terhadap spam + Buat profil Anda + Buat koneksi pribadi + Lewati + Panggilan Anda + Lihat + Server relai hanya digunakan jika diperlukan. Pihak lain dapat mengamati alamat IP Anda. + Speaker mati + Video nyala + Suara mati + Suara nyala + Speaker nyala + Balik kamera + Panggilan berlangsung + Menghubungkan panggilan + Pesan yang terlewati + Hash dari pesan sebelumnya berbeda. + Privasi & keamanan + Enkripsi berkas lokal + Terima gambar otomatis + Lindungi layar aplikasi + Lindungi alamat IP + Lihat pesan terakhir + Kirim pratinjau tautan + Draf pesan + Konfirmasi kode sandi + Kunci setelah + Kirim + Kode sandi salah + Kode sandi aplikasi + Kode sandi + %s detik + Simpan arsip + Ketuk untuk gabung + diblokir %s + Buka + enkripsi aman + status tidak diketahui + diundang + admin + dihapus + grup dihapus + Gagal impor tema + Impor tema + Pastikan berkas memiliki sintaksis YAML yang benar. Ekspor tema untuk mendapatkan contoh struktur berkas tema. + Ekspor tema + Sekunder + Latar + Pesan terkirim + Reset ke tema pengguna + Pasang tema bawaan + Terapkan + Sesuai + Reset ke tema aplikasi + Izin Anda + Izin kontak + bawaan (%s) + Pesan sementara + Pesan pribadi + Pesan suara + Setel preferensi grup + Preferensi Anda + Hapus untuk semua orang + diterima, dilarang + Pasang 1 hari + Kontak dapat menandai pesan untuk dihapus; Anda akan dapat melihatnya. + Melarang pengiriman pesan suara. + Hapus pesan tidak dapat dibatalkan dilarang dalam obrolan ini. + Panggilan audio/video dilarang. + Reaksi pesan dilarang. + Anggota grup dapat mengirim pesan sementara. + Pesan sementara dilarang di grup ini. + %d minggu + %d minggu + Keamanan SimpleX Chat diaudit oleh Trail of Bits. + Tautan grup + Admin dapat membuat tautan untuk gabung ke grup. + Maks 40 detik, diterima secara instan. + Hapus pesan tidak dapat dibatalkan + Penerima melihat pesan langsung saat Anda mengetik. + Isolasi transport + Pesan sambutan grup + Antarmuka bahasa Cina dan Spanyol + Video dan berkas hingga 1GB + Terima kasih kepada pengguna – berkontribusi melalui Weblate! + Reaksi pesan + Jaga koneksi Anda + Grup lebih baik + Grup samaran + Melalui protokol quantum resistant yang aman. + Gabung lebih cepat dan pesan lebih handal. + Untuk sembunyikan pesan tak diinginkan. + Gunakan aplikasi saat dalam panggilan. + Konfirmasi berkas dari server tak dikenal. + Tanda terima pengiriman dimatikan! + kustom + Verifikasi kode dengan desktop + Fitur ini belum didukung. Coba pada versi berikutnya. + Kesalahan internal + Mengunduh arsip + Unduhan gagal + Buat tautan arsip + Mulai obrolan + Pastikan Anda mengingat frasa sandi basis data untuk memindahkan. + Profil saat ini + Buat + Reset warna + kirim berkas belum didukung + data tidak valid + gagal menampilkan pesan + gagal menampilkan konten + Gagal dekripsi + Gagal negosiasi ulang enkripsi + enkripsi end-to-end dengan perfect forward secrecy, penolakan dan pemulihan pembobolan.]]> + Via peramban + Gagal mengganti profil + Buka tautan di peramban mengurangi privasi dan keamanan koneksi. Tautan SimpleX tidak tepercaya akan berwarna merah. + Gagal simpan server SMP + Pastikan alamat server SMP dalam format yang benar, pisahkan baris dan tidak terduplikasi. + Pastikan alamat server XFTP dalam format yang benar, pisahkan baris dan tidak terduplikasi. + gagal terkirim + belum dibaca + Pesan dapat disampaikan kemudian jika anggota menjadi aktif. + Selamat Datang %1$s! + %d Dipilih + Terlalu banyak video! + Pesan suara + pesan + buka + Sistem + Arsip obrolan + setel alamat kontak baru + Tema gelap + Tema + Mode warna + Balasan terkirim + Pesan diterima + Perbesar + Melarang kirim pesan sementara. + Reaksi pesan dilarang. + Panggilan suara/video dilarang. + Anda dan kontak Anda dapat menghapus pesan terkirim secara permanen. (24 jam) + Pesan sementara dilarang dalam obrolan ini. + Pesan suara dilarang dalam obrolan ini. + Anda dan kontak dapat menambahkan reaksi pesan. + Anggota grup dapat hapus pesan terkirim secara permanen. (24 jam) + Anggota grup dapat mengirim pesan suara. + Hapus pesan yang tidak dapat dibatalkan dilarang di grup ini. + Pesan pribadi antar anggota dilarang di grup ini. + Anggota grup dapat kirim tautan SimpleX. + %d jam + %d jam + %d hari + %d hari + %dmg + %dj + dibatalkan %s + Dengan pesan sambutan opsional. + Terima permintaan kontak secara otomatis + Selengkapnya + Penilaian keamanan + Konfigurasi server ditingkatkan + Tambah server dengan pindai kode QR. + Peningkatan privasi dan keamanan + Terima kasih kepada pengguna – berkontribusi melalui Weblate! + Antarmuka Prancis + Bandingkan kode keamanan dengan kontak Anda. + Simpan draf pesan terakhir, dengan lampiran. + Berdasarkan profil obrolan (bawaan) atau berdasarkan koneksi (BETA). + Untuk melindungi zona waktu, berkas gambar/suara menggunakan UTC. + Penggunaan baterai semakin sedikit + Atur pesan yang ditampilkan kepada anggota baru! + Pindahan data aplikasi + Terima berkas dengan aman + Buat obrolan Anda terlihat berbeda! + Peningkatan pengiriman pesan + UI Lituania + Arsip kontak untuk mengobrol nanti. + Gunakan aplikasi dengan satu tangan. + Terhubung dengan teman lebih cepat. + Ini melindungi alamat IP dan koneksi Anda. + Unduh versi baru dari GitHub. + Nama perangkat ini + Koneksi terputus + Desktop tidak aktif + Ingin bergabung lagi? + Mengimpor arsipan + Pindah perangkat + Gagal Ekspor basis data obrolan + Informasi server + Lihat info untuk + Galat + Diunggah + Diunduh + Pesan terkirim + Statistik server akan direset - ini tidak dapat dibatalkan! + Perbesar ukuran font. + Sumber pesan tetap pribadi. + Siapa pun dapat menjadi pemegang server. + Terdesentralisasi + Kesalahan saat menginisialisasi WebView. Pastikan Anda telah menginstal WebView dan arsitektur yang didukung adalah arm64.\nKesalahan: %s + Gunakan obrolan + Jika SimpleX tidak memiliki pengenal pengguna, bagaimana ia dapat menyampaikan pesan?]]> + Bagaimana caranya + Cara kerja SimpleX + Berkala + Panggilan suara masuk + panggilan suara terenkripsi e2e + panggilan video terenkripsi e2e + Gunakan frasa sandi acak + Panggilan suara & video + Tolak + panggilan video + kontak memiliki enkripsi e2e + Tutup + Video mati + Suara dibisukan + Panggilan tertunda + Aktifkan kunci + Tanpa Tor atau VPN, alamat IP Anda akan terlihat oleh server file. + Mode kunci + Autentikasi dibatalkan + Ubah mode kunci + Kode sandi dipasang! + Kode sandi diubah! + Kode sandi hapus otomatis diubah! + Kode sandi hapus otomatis + Aktifkan hapus otomatis + Pasang kode sandi + BANTUAN + DUKUNG SIMPLEX CHAT + PANGGILAN + Mulai ulang aplikasi untuk buat profil obrolan baru. + Hapus pesan + keluar + Anda keluar + Terang + Warna mode gelap + Reset warna + Mode gelap + Mode terang + Preferensi obrolan + Preferensi kontak + diaktifkan + diaktifkan untuk anda + \nTersedia di v5.1 + Kirim pesan suara tidak diizinkan. + Hingga 100 pesan terakhir dikirim ke anggota baru. + Pesan suara + Pesan sementara + Pesan langsung + Profil obrolan tersembunyi + Moderasi grup + Kustomisasi dan bagikan warna tema. + Perbaiki enkripsi setelah memulihkan cadangan. + Bahasa Arab, Bulgaria, Finlandia, Ibrani, Thailand, dan Ukraina - terima kasih kepada pengguna dan Weblate. + Gabung ke percakapan grup + Bilah pencarian menerima tautan undangan. + Teruskan dan simpan pesan + Panggilan gambar-dalam-gambar + Persegi, lingkaran, atau apa pun di antaranya. + Bentuk gambar profil + Putuskan desktop? + Segarkan + Acak + Buka port di firewall + Hubungkan via tautan? + Kesalahan besar + Gambar + pesan + Menunggu gambar + Diminta untuk menerima gambar + Gambar terkirim + Membuat tautan… + Kode QR tidak valid + Kesalahan saat mengganti profil + Ketuk untuk tempel tautan + Koneksi Anda dipindahkan ke %s tetapi terjadi kesalahan tak terduga saat mengarahkan Anda ke profil. + Kirim kami email + Kirim pertanyaan dan ide + Kunci SimpleX + Bantuan Markdown + Server media & berkas + Server Anda + Alamat server ditetapkan + Gunakan di koneksi baru + Simpan server? + Server untuk koneksi baru profil obrolan Anda saat ini + Gunakan host .onion + Proxy SOCKS + Bagaimana + Konfigurasi server ICE + Server ICE (satu per baris) + Bagikan tautan + Simpan pengaturan? + Buat profil + Simpan kata sandi profil + Kata sandi profil tersembunyi + Mikrofon + Privasi didefinisikan ulang + Ini dapat diubah nanti di pengaturan. + Saat aplikasi sedang berjalan + Notifikasi pribadi + Instan + Ubah kode sandi hapus otomatis + Kode sandi tidak diubah! + Hapus otomatis + Aktifkan kode sandi hapus otomatis + Ubah mode hapus otomatis + Kode sandi hapus otomatis diaktifkan! + Matikan tanda terima? + Aktifkan tanda terima? + Sedang + Buram media + Kuat + ANDA + Lunak + BASIS DATA OBROLAN + Setel frasa sandi untuk diekspor + Buka folder basis data + menghapus anda + Admin dapat memblokir anggota untuk semua. + Akan diaktifkan dalam obrolan pribadi! + Grup aman + Kode sesi + Verifikasi koneksi + %s]]> + Ini adalah alamat SimpleX Anda! + Permintaan koneksi berulang? + Gabung ke grup Anda? + Statistik + Total + ditandai dihapus + diblokir oleh admin + %d pesan diblokir + %d pesan diblokir oleh admin + diblokir + LIVE + dimoderasi + obrolan tidak valid + diteruskan + disimpan dari %s + terima berkas belum didukung + anda + format pesan tak diketahui + format pesan tidak valid + Obrolan ini dilindungi oleh enkripsi end-to-end quantum resistant. + enkripsi quantum resistant e2e dengan perfect forward secrecy, penolakan dan pemulihan pembobolan.]]> + Obrolan ini dilindungi oleh enkripsi end-to-end. + Catatan pribadi + koneksi %1$d + koneksi terjalin + menghubungkan… + Anda bagikan tautan sekali + Anda bagikan tautan sekali samaran + via tautan grup + samaran via tautan grup + via tautan alamat kontak + samaran via tautan alamat kontak + via tautan sekali + Alamat kontak SimpleX + samaran via tautan sekali + Tautan lengkap + Tautan grup SimpleX + Tautan SimpleX + Undangan sekali SimpleX + via %1$s + Gagal simpan server XFTP + Nama tampilan tidak valid! + Gagal membuat profil! + Kesalahan koneksi + Gagal mengirim pesan + Gagal membuat pesan + Waktu koneksi habis + pengiriman tidak sah + Teks ini tersedia di pengaturan + Ketuk untuk memulai obrolan baru + Anda diundang ke grup + gabung sebagai %s + menghubungkan… + Tidak dapat kirim pesan + Bagikan pesan… + Teruskan pesan… + Bagikan media… + Bagikan berkas… + Kesalahan decoding + Video terkirim + Diminta untuk menerima video + Menunggu berkas + Perubahan alamat akan dibatalkan. Alamat penerima lama akan digunakan. + Kembali + Lebih lanjut + Atau pindai kode QR + Simpan + Simpan undangan tidak terpakai? + Anda dapat melihat tautan undangan lagi dalam detail koneksi. + Alamat SimpleX + Atau perlihatkan kode ini + Pindai kode keamanan dari aplikasi kontak Anda. + Kode keamanan + Kode keamanan salah! + Cara menggunakannya + Frasa sandi & ekspor basis data + Buat profil obrolan + Markdown dalam pesan + Simpan server + Tambah server + Tambah server prasetel + Uji server gagal! + Server uji + Server uji + Masukkan server manual + Server Prasetel + Pindai kode QR server + Beberapa server gagal dalam pengujian: + Alamat server Anda + Hapus server + Server WebRTC ICE yang disimpan akan dihapus. + Server ICE Anda + Cara menggunakan server Anda + Simpan + Pengaturan jaringan lainnya + Gagal simpan server ICE + Pastikan alamat server WebRTC ICE dalam format yang benar, dipisahkan baris dan tidak terduplikasi. + Gunakan proxy SOCKS + Pengaturan proxy SOCKS + memulai… + Privasi Anda + Cadangan data aplikasi + %s dan %s + Sistem + Balasan diterima + Skala + Hanya kontak yang dapat kirim pesan suara. + Nama berkas pribadi + Pesan yang lebih baik + Menyimpan %1$s pesan + %1$d berkas gagal diunduh. + Gabung + Batal pratinjau berkas + Berkas + Pesan suara tidak diizinkan + Hapus alamat? + Keluar dari grup? + ARSIP OBROLAN + Rangkaian ini bukan tautan koneksi! + Tempel tautan yang Anda terima + Bagikan dengan kontak + Ya + Kustomisasi tema + Anda bergabung dengan grup melalui tautan ini. + Mendukung bluetooth dan peningkatan lainnya. + Pengarsipan basis data + Protokol SimpleX ditinjau oleh Trail of Bits. + - pesan suara hingga 5 menit.\n- kustom waktu pesan sementara.\n- riwayat edit. + Diaktifkan untuk + %dh + Alamat server tidak valid! + Server XFTP lainnya + Gunakan kredensial proksi yang berbeda untuk setiap profil. + Server penerusan %1$s gagal terhubung ke server tujuan %2$s. Coba lagi nanti. + Cari atau tempel tautan SimpleX + Preferensi obrolan yang dipilih melarang pesan ini. + Lampiran + Ikon konteks + Batal pratinjau gambar + panggilan ditolak + panggilan video (tidak dienkripsi e2e) + Negosiasi ulang enkripsi gagal. + gandakan pesan + hash pesan buruk + Mulai obrolan? + Hapus arsip + Jelajah dan gabung ke grup + Perutean pesan pribadi 🚀 + Lindungi alamat IP Anda dari relai pesan yang dipilih oleh kontak Anda.\nAktifkan di pengaturan *Jaringan & server*. + Tempel tautan + Bagikan tautan undangan 1-kali + Tempel + Coba lagi + Hubungkan melalui tautan + Teks yang Anda tempel bukan tautan SimpleX. + Pengaturan Anda + Alamat SimpleX Anda + Profil obrolan Anda + Server SMP dikonfigurasi + Server SMP lainnya + Gunakan server + Server XFTP dikonfigurasi + Periksa alamat server dan coba lagi. + Tampilkan persentase + Kontribusi + Instal SimpleX Chat untuk terminal + Panggilan video masuk + mengundang %1$s + pemilik + Kode sandi hapus otomatis + Temukan obrolan lebih cepat + Statistik terperinci + Berkas besar! + Pindai / Tempel tautan + Berikan izin untuk melakukan panggilan + panggilan suara (tidak dienkripsi e2e) + Tema kustom + - terhubung ke layanan direktori (BETA)!\n- tanda terima pengiriman (hingga 20 anggota).\n- lebih cepat dan stabil. + Mode samaran sederhana + Terapkan + Hubung ulang + Kesalahan pengenalan + Keluar + enkripsi disetujui + Anda dan kontak Anda dapat mengirim pesan sementara. + Hanya kontak Anda yang dapat mengirim pesan sementara. + Anda dan kontak Anda dapat mengirim pesan suara. + Kode sandi aplikasi + Atur sebagai ganti autentikasi sistem. + Akhirnya, kita mendapatkannya! 🚀 + Antarmuka Polandia + Membuat satu pesan dihapus + Bahkan saat dimatikan dalam percakapan. + Centang kedua yang terlewat! ✅ + Filter obrolan belum dibaca dan favorit. + Buat profil baru di aplikasi desktop. 💻 + - opsional memberi tahu kontak yang dihapus.\n- nama profil dengan spasi.\n- dan masih banyak lagi! + Buramkan untuk privasi lebih baik. + Perangkat ini + Berkas + Kabel ethernet + Hapus pesan tidak bisa dibatalkan dilarang. + Kirim berkas dan media dilarang. + Aplikasi mengenkripsi berkas lokal baru (kecuali video). + Aktifkan mode samaran saat menghubungkan. + Peningkatan pengiriman pesan + Penggunaan baterai yang sedikit. + UI Hongaria dan Turki + Enkripsi quantum resistant + Periksa pembaruan + Silakan coba lagi nanti. + Kesalahan perutean pribadi + Tambah alamat ke profil Anda, sehingga kontak dapat membagikannya dengan orang lain. Pembaruan profil akan dikirim ke kontak Anda. + Android Keystore digunakan untuk simpan frasa sandi dengan aman setelah Anda memulai ulang aplikasi atau ubah frasa sandi - ini mungkin dapat menerima notifikasi. + Aktifkan panggilan dari layar kunci melalui Pengaturan. + Hal ini dapat terjadi ketika:\n1. Pesan kedaluwarsa di klien pengirim setelah 2 hari atau di server setelah 30 hari.\n2. Dekripsi pesan gagal, karena Anda atau kontak Anda menggunakan cadangan basis data lama.\n3. Koneksi terganggu. + Hapus gambar + Ukuran huruf + Kirim pesan pribadi ke anggota dilarang. + Kirim tautan SimpleX dilarang + Reaksi pesan dilarang di grup ini. + Tautan SimpleX dilarang di grup ini. + Server + Tak terlindungi + Sesi aplikasi + Kredensial SOCKS baru akan digunakan untuk setiap server. + Build aplikasi: %s + Versi inti: v%s + Ketika IP disembunyikan + WARNA ANTARMUKA + Fallback perutean pesan + Mode routing pesan + Routing pribadi + JANGAN gunakan routing pribadi. + Gunakan routing pribadi dengan server yang tak dikenal ketika alamat IP tidak dilindungi. + Stable + Pembaruan tersedia: %s + Dimatikan + Lewati versi ini + Pembaruan aplikasi diunduh + Buat alamat + ID Basis Data dan Opsi Isolasi Transport. + panggilan tak terjawab + Anda dapat membuatnya terlihat oleh kontak SimpleX Anda melalui Pengaturan. + Undang + Hai!\nHubungi saya melalui SimpleX Chat: %s + Konfirmasi kata sandi + panggilan gagal + menunggu jawaban… + menerima jawaban… + menerima konfirmasi… + Pindah dari perangkat lain + Buka pengaturan + Kamera dan mikrofon + Panggilan pada layar terkunci: + Speaker + Izin dalam pengaturan + Generasi baru\ndari perpesanan pribadi + Temukan izin ini di pengaturan Android dan ubah secara manual. + Earpiece + Matikan + Server ICE Anda + Server ICE WebRTC + Jika Anda memasukkan kode sandi hapus otomatis saat membuka aplikasi: + IKON APLIKASI + Aplikasi akan meminta untuk mengonfirmasi unduhan dari server berkas yang tidak dikenal (kecuali .onion atau saat proxy SOCKS diaktifkan). + Reaksi pesan dilarang dalam obrolan ini. + Pindah ke perangkat lain + Untuk melakukan panggilan, izinkan penggunaan mikrofon. Akhiri panggilan dan coba panggil lagi. + Klik tombol info di dekat kolom alamat untuk mengizinkan penggunaan mikrofon. + Server relai melindungi alamat IP Anda, tetapi dapat mengamati durasi panggilan. + Buka Pengaturan Safari / Situs Web / Mikrofon, lalu pilih Izinkan untuk localhost. + Buka SimpleX Chat untuk terima panggilan + Buka + terenkripsi e2e + peer-to-peer + kontak tidak memiliki enkripsi e2e + via relai + Panggilan tak terjawab + Panggilan ditolak + ID pesan berikutnya salah (kurang atau sama dengan yang sebelumnya).\nHal ini dapat terjadi karena beberapa bug atau ketika koneksi terganggu. + TEMA + KIRIM TANDA TERIMA KIRIMAN KE + Hal ini dapat terjadi ketika Anda atau koneksi Anda menggunakan cadangan basis data lama. + Android Keystore digunakan untuk menyimpan frasa sandi dengan aman - memungkinkan layanan notifikasi berfungsi. + Hapus + Enkripsi + Pesan + pembuat + diundang melalui tautan grup Anda + anggota + keluar + Gelap + Gelap + Sistem + Menu & peringatan + Judul + Latar wallpaper + Aksen wallpaper + Mode sistem + Selamat siang! + Selamat pagi! + Ulangi + Isi + ya + Preferensi grup + Reaksi pesan + diaktifkan untuk kontak + Anda dan kontak dapat melakukan panggilan. + Kirim hingga 100 pesan terakhir untuk anggota baru. + Kirim pesan sementara dilarang. + Jangan perlihat pesan riwayat ke anggota baru. + Anggota grup dapat mengirim pesan pribadi. + Pesan suara dilarang di grup ini. + Anggota grup dapat memberi reaksi pesan. + %d bulan + pemilik + %d dtk + %dd + %d mnt + %d bulan + %db + %dbln + Apa yang baru + Anggota grup dapat kirim berkas dan media. + Berkas dan media dilarang di grup ini. + Riwayat pesan tidak dikirim ke anggota baru. + Sembunyikan layar aplikasi di aplikasi terbaru. + Kontak Anda dapat mengizinkan hapus semua pesan. + Pesan terkirim akan dihapus setelah waktu yang ditentukan. + Verifikasi keamanan koneksi + Nama, avatar, dan isolasi transport yang berbeda. + Draf pesan + Lindungi profil obrolan Anda dengan kata sandi! + Terima kasih kepada pengguna – berkontribusi melalui Weblate! + Cepat dan tidak perlu menunggu pengirim online! + UI Jepang dan Portugis + Catatan pribadi + Enkripsi berkas & media tersimpan + Dengan berkas dan media terenkripsi. + Hubungkan aplikasi ponsel dan desktop! 🔗 + Tanda terima kirim pesan! + Buat grup menggunakan profil acak. + Blokir anggota grup + Suara panggilan masuk + Saat menghubungkan panggilan suara dan video. + Tempel tautan untuk terhubung! + Riwayat terkini dan peningkatan bot direktori. + Aktifkan dalam obrolan pribadi (BETA)! + Pindah ke perangkat lain melalui kode QR. + Hapus hingga 20 pesan sekaligus. + Periksa pembaruan + Baca selengkapnya di repositori GitHub kami. + Unduh %s (%s) + simplexmq: v%s (%2s) + Gunakan routing pribadi dengan server yang tak dikenal. + Panggilan lebih baik + Terhubung ke diri sendiri? + Hapus atau moderasi hingga 200 pesan. + Teruskan hingga 20 pesan sekaligus. + Segera hadir! + Ganti suara dan video selama panggilan. + Pengalaman pengguna lebih baik + Total terkirim + Pesan diterima + Mulai ulang obrolan + Otentikasi proxy + Gunakan kredensial acak + menunggu konfirmasi… + Berikan izin + Gagal membuka peramban + Peramban web bawaan diperlukan untuk panggilan. Harap konfigurasikan peramban bawaan dalam sistem, dan bagikan informasi lebih lanjut dengan pengembang. + Aktifkan untuk semua + Nonaktifkan untuk semua grup + Undang anggota + Sistem + Terang + SimpleX + Berkas dan media + Tautan SimpleX + Perlihat riwayat + Panggilan suara/video + Hapus setelah + Dengan kurangi penggunaan baterai. + Toolbar obrolan mudah diakses + UI Persia + Grup terbuka + WiFi + Mulai dari %s.\nSemua data bersifat pribadi di perangkat Anda. + Sesi transport + Total diterima + Terima galat + upaya + Dikenal + Menunggu gambar + Menunggu video + PERANGKAT + OBROLAN + BERKAS + Reset semua petunjuk + Gagal menambah anggota + Gagal gabung ke grup + Pengirim batalkan kirim berkas. + Tak bisa menerima berkas + Tanpa Tor atau VPN, alamat IP Anda akan terlihat oleh relay XFTP ini:\n%1$s. + Gagal menerima berkas + Gagal membuat alamat + Anda sudah terhubung ke %1$s. + Tautan koneksi tidak valid + Harap periksa apakah tautan yang digunakan benar atau minta kontak Anda untuk kirim tautan lain. + Gagal menerima permintaan kontak + Galat + Mungkin sidik jari sertifikat di alamat server salah + Gagal mengatur alamat + Gagal hapus profil pengguna + Hapus antrian + Buat berkas + Hapus berkas + Gagal perbarui privasi pengguna + Fungsi lambat + Notifikasi instan + izinkan SimpleX berjalan di latar belakang pada dialog berikutnya. Jika tidak, notifikasi akan dimatikan.]]> + Optimalisasi baterai aktif, mematikan layanan latar belakang dan permintaan pesan baru secara berkala. Anda dapat aktifkan kembali di pengaturan. + Buka pengaturan aplikasi + Notifikasi berkala dinonaktifkan! + Matikan notifikasi + Penggunaan baterai aplikasi / Tidak dibatasi di pengaturan aplikasi.]]> + Frasa sandi diperlukan + Untuk menerima notifikasi, mohon masukkan frasa sandi basis data + Akhiri + Tidak dapat inisialisasi basis data + Basis data tidak berfungsi dengan benar. pelajari lebih lanjut + Tutup + Dimulai secara berkala + Berjalan saat aplikasi terbuka + Aplikasi hanya menerima notifikasi saat sedang berjalan, tidak ada layanan latar belakang yang dimulai + Memeriksa pesan baru setiap 10 menit hingga 1 menit + Tersembunyi + Tampilkan kontak dan pesan + Kontak tersembunyi: + Nyalakan + Mode Kunci SimpleX + Kunci SimpleX diaktifkan + Anda akan diminta untuk lakukan autentikasi saat mulai atau lanjutkan aplikasi setelah 30 detik di latar belakang. + Kode sandi aplikasi diganti dengan kode sandi hapus otomatis. + Mereka dapat ditimpa dalam pengaturan kontak dan grup. + Aktifkan (tetap ditimpa) + Matikan (tetap ditimpa) + Gagal memuat obrolan + Gagal hapus kontak + Gagal memuat server SMP + Gagal memuat obrolan + Nama tampilan ini tidak valid. Silakan pilih nama lain. + Pengirim mungkin telah hapus permintaan koneksi. + Server perlu otorisasi untuk membuat antrian, periksa kata sandi + Gagal menghapus permintaan kontak + Gagal menghapus koneksi kontak tertunda + Gagal mengubah alamat + Gagal batalkan perubahan alamat + Pesan SimpleX Chat + Penggunaan baterai aplikasi / Tidak dibatasi di pengaturan aplikasi.]]> + Aplikasi mungkin ditutup setelah 1 menit di latar belakang. + Untuk melindungi informasi Anda, aktifkan Kunci SimpleX.\nAnda akan diminta untuk menyelesaikan autentikasi sebelum fitur ini aktif. + %d menit + Gagal menampilkan notifikasi, hubungi pengembang. + Aktifkan tanda terima untuk grup? + Profil obrolan kosong dengan nama yang disediakan dibuat, dan aplikasi terbuka seperti biasa. + Jika Anda memasukkan kode sandi saat membuka aplikasi, semua data aplikasi akan dihapus secara permanen! + Pengaturan ini untuk profil Anda saat ini + Kirim tanda terima diaktifkan untuk %d kontak + Kirim tanda terima dimatikan untuk %d kontak + Gagal memuat server XFTP + Gagal memperbarui konfigurasi jaringan + Mohon perbarui aplikasi dan hubungi pengembang. + Anda sudah memiliki nama tampilan profil obrolan yang sama. Silakan pilih nama lain. + Periksa koneksi jaringan Anda dengan %1$s dan coba lagi. + Gagal memuat detail + Gagal menghapus grup + Gagal hapus catatan pribadi + Putuskan + Amankan antrian + Unduh berkas + Bandingkan berkas + Notifikasi instan! + Notifikasi instan dimatikan! + Notifikasi berkala + SimpleX tidak dapat berjalan di latar belakang. Anda hanya menerima notifikasi saat aplikasi berjalan. + Panggilan video + Menerima pesan… + Lihat pratinjau + Teks pesan + Layanan latar belakang selalu berjalan – notifikasi selalu menampilkan pesan yang tersedia. + Tampilkan hanya kontak + Sembunyikan kontak dan pesan + Kunci SimpleX + Autentikasi sistem + Autentikasi gagal + Entri kode sandi + Anda tidak dapat diverifikasi; silakan coba lagi. + Versi server penerusan tidak kompatibel dengan pengaturan jaringan: %1$s. + Versi server tujuan %1$s tidak kompatibel dengan server penerusan %2$s. + Kesalahan koneksi (AUTH) + Gagal menghubungkan ke server penerusan %1$s. Coba lagi nanti. + Alamat server penerusan tidak kompatibel dengan pengaturan jaringan: %1$s. + Unggah berkas + Eksekusi fungsi memakan waktu terlalu lama: %1$d detik: %2$s + Panggilan SimpleX Chat + Alamat server tidak kompatibel dengan pengaturan jaringan: %1$s. + Uji gagal pada langkah %s. + %d detik + Segera + Matikan tanda terima untuk grup? + Kirim tanda terima diaktifkan untuk %d grup + Kirim tanda terima dimatikan untuk %d grup + Aktifkan (grup tetap ditimpa) + Matikan (grup tetap ditimpa) + Matikan untuk semua + Aktifkan untuk semua grup + Autentikasi + Versi server tidak kompatibel dengan aplikasi Anda: %1$s. + Server tak dikenal! + Kecuali kontak Anda hapus koneksi atau tautan ini sudah digunakan, mungkin ini adalah bug - harap laporkan.\nUntuk terhubung, harap minta kontak Anda untuk buat tautan koneksi lain dan periksa apakah Anda memiliki koneksi jaringan stabil. + Gagal sinkronkan koneksi + Server perlu otorisasi untuk mengunggah, periksa kata sandi + Buat antrian + Dapat dimatikan melalui pengaturan – notifikasi akan tetap ditampilkan saat aplikasi berjalan.]]> + layanan latar belakang SimpleX – yang gunakan beberapa persen baterai per hari.]]> + Aplikasi terima pesan baru secara berkala — aplikasi ini memakai beberapa persen baterai per hari. Aplikasi ini tidak gunakan notifikasi push — data dari perangkat tidak dikirim ke server. + Layanan SimpleX Chat + Nama kontak + Masukkan Kode Sandi + Kode Sandi Saat Ini + Ubah kode sandi + Harap diingat dan simpan dengan aman - tidak ada cara untuk pulihkan kata sandi yang hilang! + Kiriman debug + Hapus + Dibuat pada: %s + diblokir \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index c1b6a0808c..f5334d4e61 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -1607,7 +1607,7 @@ immagine del profilo rimossa Con file e multimediali criptati. La barra di ricerca accetta i link di invito. - impostata nuova immagine del profilo + ha impostato una nuova immagine del profilo impostato nuovo indirizzo di contatto profilo aggiornato Messaggio salvato diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml index a0c382f5af..cce95b5286 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml @@ -26,7 +26,7 @@ 全チャットとメッセージが削除されます(※元に戻せません※)! 送信相手からの音声メッセージを許可する。 あなたと連絡相手が音声メッセージを送信できます。 - 電池省エネに良い:バックグラウンド機能で10分毎に新着メッセージを確認します。通話と緊急メッセージを見逃す可能性があります。]]> + バッテリーに優しい。アプリは10分ごとにメッセージを確認します。ただし、電話や緊急のメッセージを見逃す可能性があります。]]> 音声オフ 添付する アプリ・ビルド番号: %s @@ -159,7 +159,7 @@ if SimpleX にユーザIDがなければ、メッセージをどうやって届けるのでしょうかと。]]> SimpleX の仕様 通話中 - 電池消費がより高い!非アクティブ時でもバックグラウンドのサービスが常に稼働します(着信次第に通知がすぐに出ます)。]]> + 電池消費がより高い!非アクティブ時でもバックグラウンドのサービスが常に稼働します(着信してすぐに通知が出ます)。]]> 発信中 通話終了 %1$s 通話が終了しました。 @@ -324,7 +324,7 @@ 接続中 発信中… 終了 - プロトコル技術とコードはオープンソースで、どなたでもご自分のサーバを運用できます。 + 誰でもサーバーをホストできます。 プライバシーを再定義 技術の説明 プライベートな接続をする @@ -1870,7 +1870,7 @@ 連絡先が削除されました。 無効 一度に最大20件のメッセージを削除できます。 - サーバに接続中 + 接続中のサーバ エラー サーバーへの再接続エラー エラー @@ -1891,4 +1891,85 @@ 保存して再接続 強め 普通 + パーセンテージを表示 + これは見た目の設定から変更できます。 + 以前接続していたサーバ + アクティブな接続 + 統計情報 + アーカイブされた連絡先 + 角丸 + 送信されたメッセージ数 + 全ての統計情報をリセットしますか? + 合計 + しっぽ + サーバ情報 + %sから計測されています。\nデバイス上の全てのデータはプライベートです。 + 受信したメッセージ数 + サーバの統計情報をリセットしようとしています - これは元に戻せません! + 全統計情報をリセットする + ヒントをリセットする + 容量を超えました - 受信者は以前に送信されたメッセージを受け取っていません。 + 試行 + 確認 + 確認エラー + 削除完了 + 作成完了 + %1$d件のその他のファイルエラー。 + メッセージの転送エラー + %1$d件のファイルエラー:\n%2$s + %1$s件のメッセージを転送しますか? + %1$d件のファイルがまだダウンロード中です。 + %1$d件のファイルがダウンロードされませんでした。 + %1$d件のファイルのダウンロードに失敗しました。 + %1$d件のファイルが削除されました。 + %1$s件のメッセージが転送されませんでした。 + ダウンロード + %1$s件のメッセージを転送中 + 会話が削除されました! + アプリのアップデートがダウンロードされました + アプリの更新をダウンロード中です。アプリを閉じないでください + %s(%s)をダウンロード + グループメンバーにメッセージを送信できません。 + 後でチャットするために連絡先をアーカイブします。 + 接続とサーバーのステータス + アドレスフィールドの近くにある情報ボタンをクリックして、マイクの使用を許可してください。 + 接続 + カスタマイズ可能なメッセージの形。 + プライベートルーティングをサポートしていなくても、メッセージを直接送信しないでください。 + 転送サーバー%1$sへの接続エラーです。後ほど再試行してください。 + WebViewの初期化エラーです。WebViewがインストールされており、サポートされているアーキテクチャがarm64であることを確認してください。\nエラー:%s + WebViewの初期化エラーです。システムを新しいバージョンに更新してください。開発者にお問い合わせください。\nエラー:%s + %1$sの宛先サーバーアドレスは、転送サーバー%2$sの設定と互換性がありません。 + %1$sの宛先サーバーバージョンは、転送サーバー%2$sと互換性がありません。 + 通知なしで削除 + チャンクが削除されました + すべてのカラーモード + グループメンバーに電話できません + 連絡先に接続中です。しばらくお待ちいただくか、後で確認してください! + ネットワークを管理 + ダウンロードしたファイル + メンバーの%d件のメッセージを削除しますか? + チャットデータベースがエクスポートされました + 通話禁止! + チャンクがダウンロードされました + チャンクがアップロードされました + ダウンロードエラー + アプリセッション + 配信のデバッグ + 改善された通話機能 + メッセージの日付 + より強力なセキュリティ ✅ + より良いユーザー体験 + 最大200件のメッセージを削除または管理します。 + プライバシー向上のためのぼかし処理。 + 友達ともっと速くつながりましょう。 + 新しいバージョンをGitHubからダウンロードしてください。 + ダウンロード完了 + 再接続 + 送信エラー + SOCKSプロキシ + パスワード + 設定 + 情報がありません、リロードしてください + SMPサーバ \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml index 639a888bc9..eab9df3b92 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml @@ -2,8 +2,8 @@ 연결됨 연결 중 - 그룹 링크를 통해 연결하시겠습니까\? - 초대 링크로 연결하시겠습니까\? + 그룹에 참여할까요? + 일회용 링크로 연결하시겠습니까? 연결 수립됨 연결 시간 초과 파일을 받을 수 없음 @@ -12,7 +12,7 @@ 연결 오류(인증) 대기열 만들기 데이터베이스를 초기화할 수 없음 - 백그라운드 서비스가 항상 실행 됩니다. - 메시지를 받는 즉시 알림이 표시됩니다. + 백그라운드 서비스가 항상 실행됨 – 메시지를 받는 즉시 알림이 표시됩니다. 10분마다 최대 1분간 새 메시지 확인 연결됨 숨긴 대화 상대: @@ -29,13 +29,13 @@ 뒤로 취소 라이브 메시지 취소 - 파일 선택 + 파일 확인 링크 / QR 코드로 연결 클립보드로 복사됨 비밀 그룹 생성 수락 - 모든 메시지가 삭제됩니다 - 삭제 후 되돌릴 수 없습니다! 메시지는 나에게서만 삭제됩니다. + 모든 메시지가 삭제됩니다. 이 결정은 되돌릴 수 없습니다! 메시지는 나에게서만 삭제됩니다. 지우기 지우기 채팅 지우기 @@ -56,7 +56,7 @@ 앱 버전 앱 버전 : v%s 코어 버전 : v%s - 전화 응답 + 통화 응답 굵게 전화 연결 중 색깔 @@ -68,13 +68,13 @@ 연결됨 연결 중… 내 프로필 생성 - 배터리에 좋음. 백그라운드 서비스는 10분마다 메시지를 확인합니다. 전화나 긴급 메시지를 놓칠 수 있습니다.]]> + 배터리에 좋음. 앱이 10분마다 메시지를 확인합니다. 전화나 긴급 메시지를 놓칠 수 있습니다.]]> 통화가 이미 종료되었습니다! 항상 릴레이 사용 음성 통화 음성 & 영상 통화 잠금 화면에서의 통화: - 대화 상대와 종단간 암호화되지 않음 + 대화 상대와 종단 간 암호화되지 않음 응답 소리 켜기 소리 끄기 @@ -87,9 +87,9 @@ 채팅이 작동 중 채팅 채팅 데이터베이스를 가져옴 - 주의: 비밀구절(passphrase)을 분실하면 복구하거나 비밀번호 변경을 할 수 없어요.]]> - 데이터베이스 암호구절(passphrase)을 바꾸시겠습니까\? - 새로운 암호구절(passphrase) 확인… + 주의: 암호를 분실하면 복구하거나 비밀번호 변경을 할 수 없어요.]]> + 데이터베이스 암호를 바꾸시겠습니까? + 새로운 암호 확인… 채팅 기록 보관함 내 역할이 %s 역할로 변경됨 주소 바꾸는 중… @@ -115,13 +115,13 @@ 대화 상대가 허용함 연락처 개별 설정 대화 상대가 허용한 경우에만 음성 메시지를 보낼 수 있습니다. - 대화 상대가 전송한 메시지 영구 삭제를 허용합니다. + 대화 상대가 전송한 메시지 영구 삭제를 허용합니다. (24 시간) 대화 상대가 사라지는 메시지를 전송할 수 있도록 허용합니다. 대화 상대의 음성 메시지 전송을 허용합니다. - 당신과 대화 상대 모두 메시지를 영구 삭제할 수 있습니다. + 당신과 대화 상대 모두 메시지를 영구 삭제할 수 있습니다. (24 시간) 당신과 대화 상대 모두 음성 메시지를 보낼 수 있습니다. 상대가 메시지에 삭제 표시를 할 수 있습니다. 그러나 삭제 표시된 메시지 내용은 여전히 볼 수 있습니다. - 보낸 메시지 영구 삭제를 허용합니다. + 보낸 메시지 영구 삭제를 허용합니다. (24 시간) %s 취소됨 대화 요청 자동 수락 채팅 프로필(기본값) 또는 연결(베타). @@ -142,35 +142,34 @@ QR 코드 스캔으로 서버 추가 환영 메시지 추가 관리자 - 관리자는 그룹 가입을 위한 링크를 만들 수 있습니다. + 관리자는 그룹 참여 링크를 만들 수 있습니다. 사라지는 메시지를 보낼 수 있습니다. - 모든 채팅과 메시지가 삭제됩니다 - 되돌릴 수 없습니다! + 모든 채팅과 메시지가 삭제됩니다. 이 결정은 되돌릴 수 없습니다! 음성 메시지 전송을 허용합니다. 음성 메시지를 허용하시겠습니까\? 대화상대가 허용하는 경우에만 사라지는 메시지를 허용합니다. - 그룹 구성원에게 다이렉트 메시지 보내는 것을 허용합니다. - 모든 그룹 구성원이 연결된 상태로 유지됩니다. - 대화 상대가 허용하는 경우에만 영구적인 메시지 삭제를 허용합니다. + 그룹 멤버에게 다이렉트 메시지 보내는 것을 허용합니다. + 모든 그룹 멤버가 연결된 상태로 유지됩니다. + 대화 상대가 허용하는 경우에만 영구적인 메시지 삭제를 허용합니다. (24 시간) 모든 대화 상대가 연결된 상태로 유지됩니다. 항상 켜기 - Android Keystore는 암호를 안전하게 저장하는 데 사용됩니다 - 알림 서비스가 작동할 수 있습니다. - 앱을 다시 시작하거나 암호를 변경한 후 Android Keystore를 사용하여 암호를 안전하게 저장합니다. - 알림을 받을 수 있습니다. + Android 암호 저장소는 암호를 안전하게 저장하는 데 사용됩니다 - 알림 서비스가 작동할 수 있습니다. + 앱을 다시 시작하거나 암호를 변경한 후 Android 암호 저장소를 사용하여 암호를 안전하게 저장합니다. - 알림을 받을 수 있습니다. 앱이 실행 중일 때만 알림을 받을 수 있으며, 백그라운드 서비스는 시작되지 않습니다. 앱 데이터 백업 앱 아이콘 각각의 채팅 프로필에 사용될 겁니다.]]> - 별도로 분리된 TCP 연결(및 SOCKS 자격 증명)이 각각의 대화 상대 및 그룹 구성원에게 사용될 겁니다. -\n참고: 연결이 많은 경우 배터리 및 트래픽 소비가 높을 수 있고 일부 연결이 실패할 수 있습니다. + 각각의 대화 상대 및 그룹 멤버에게 사용될 겁니다. \n참고: 연결이 많은 경우 배터리 및 트래픽 소비가 높을 수 있고 일부 연결이 실패할 수 있습니다.]]> 이미지 수신 요청됨 음성 및 영상 통화 - 음성 통화 (종단간 암호화 아님) + 음성 통화 (종단 간 암호화 아님) 인증을 사용할 수 없음 배터리 최적화가 활성화되어, 백그라운드 서비스 및 새 메시지에 대한 주기적 요청이 꺼집니다. 설정을 통해 다시 활성화할 수 있습니다. 배터리에 가장 좋음. 앱이 실행 중일 때만 알림을 받게 됩니다 (백그라운드에서 실행되지 않음).]]> 설정을 통해 비활성화할 수 있습니다. – 앱이 실행되는 동안 알림이 표시됩니다.]]> 당신과 대화 상대 모두 사라지는 메시지를 보낼 수 있습니다. - 데이터베이스 암호를 저장하고 있는 Keystore에 접근할 수 없습니다. - 배터리 더욱 사용! 백그라운드 서비스가 항상 실행됩니다. - 메시지를 수신되는 즉시 알림이 표시됩니다.]]> + 데이터베이스 암호를 저장하고 있는 암호 저장소에 접근할 수 없습니다. + 배터리를 더욱 사용함! 앱이 항상 백그라운드에서 실행됩니다. - 수신되는 즉시 알림이 표시됩니다.]]> 통화 종료됨 %1$s 전화 중… 전화 연결 중 @@ -194,9 +193,9 @@ 연결 중 (도입) 연결 오류 연결 %1$d - 링크를 통해 연결하시겠습니까\? - 대화 상대와 모든 메시지가 삭제됩니다. - 삭제 후 되돌릴 수 없습니다! - 대화 상대와 종단간 암호화됨 + 주소를 통해 연결하시겠습니까? + 대화 상대와 모든 메시지가 삭제됩니다. 이 결정은 되돌릴 수 없습니다! + 대화 상대와 종단 간 암호화됨 대화 상대와 아직 연결되지 않았습니다! %1$s에 생성 완료 비밀 그룹 생성 @@ -220,7 +219,7 @@ 삭제 대기 중인 연결을 삭제할까요\? 인증 지우기 - 데이터베이스 비밀구절(passphrase) & 내보내기 + 데이터베이스 암호 & 내보내기 서버 삭제 주소 삭제 주소를 삭제할까요\? @@ -228,12 +227,12 @@ 탈중앙화 개발자 도구 기기 - 데이터베이스 비밀구절(passphrase) + 데이터베이스 암호 모든 채팅 프로필 파일 삭제 데이터베이스 에러 - 데이터베이스 비밀구절(passphrase)이 Keystore에 저장된 것과 일치하지 않습니다. - 채팅을 열려면 데이터베이스 비밀구절(passphrase)이 필요합니다. - 보관된 채팅 삭제 + 데이터베이스 암호가 암호 저장소에 저장된 것과 일치하지 않습니다. + 채팅을 열려면 데이터베이스 암호가 필요합니다. + 보관함 삭제 보관된 채팅을 삭제할까요\? %d 개의 대화 상대가 선택되었습니다. 데이터베이스 ID @@ -242,12 +241,12 @@ 다음 기간 이후 자동 삭제 위, 다음 : 데이터베이스 삭제 - 데이터베이스는 임의의 비밀구절(passphrase)로 암호화되었습니다. 내보내기 기능 사용 전 비밀구절을 변경해 주세요. - 파일과 미디어를 삭제할까요\? - 현재 비밀구절(passphrase)… + 데이터베이스는 임의의 암호로 암호화되었습니다. 내보내기 기능 사용 전 암호를 변경해 주세요. + 파일 및 미디어를 삭제하겠습니까? + 현재 암호… 데이터베이스 암호화 완료! - 데이터베이스 암호화 비밀구절(passphrase)이 업데이트됩니다. - 데이터베이스는 임의의 비밀구절(passphrase)로 암호화되었고, 원하시면 변경할 수 있습니다. + 데이터베이스 암호화 암호가 업데이트됩니다. + 데이터베이스는 임의의 암호로 암호화되며 변경할 수 있습니다. 데이터베이스는 암호화될 것입니다. 메시지 삭제 다음 기간 이후 자동 삭제 @@ -260,14 +259,14 @@ %d일 그룹 삭제 주소 변경됨 - 데이터베이스 암호화 비밀구절(passphrase)이 업데이트되며 Keystore에 보관됩니다. - 데이터베이스는 암호화되고, 비밀구절(passphrase)은 Keystore에 보관됩니다. + 데이터베이스 암호화 암호가 업데이트되며 암호 저장소에 보관됩니다. + 데이터베이스는 암호화되고, 암호는 암호 저장소에 보관됩니다. 채팅 프로필을 삭제할까요\? 모든 파일 삭제 채팅 프로필을 삭제할까요\? 모두에게서 삭제 그룹을 삭제할까요\? - 표시 이름이 중복되어요! + 표시 이름이 중복됩니다! 연결 끊기 기기 인증이 비활성화되어 SimpleX 잠금 기능이 작동하지 않아요. SimpleX 잠금 비활성화 @@ -276,11 +275,11 @@ 새로운 채팅 시작 표시 이름 표시 이름에는 공백문자가 쓰일 수 없어요. - 표시 이름 - 종단간 암호화된 음성 전화 - 종단간 암호화된 영상 전화 + 이름을 입력: + 종단 간 암호화된 음성 전화 + 종단 간 암호화된 영상 통화 비활성화 - 종단간 암호화 + 종단 간 암호화 중복된 메시지 1일로 설정 사라지는 메시지 @@ -294,9 +293,9 @@ %d 개월 %d 주 다운그레이드하고 채팅 열기 - 1:1 메시지 + 다이렉트 메시지 사라지는 메시지 - 이 그룹에서는 멤버들의 1:1 채팅이 금지되어 있어요. + 이 그룹에서는 멤버들의 다이렉트 메시지가 금지되어 있어요. %d초 %d 초 %d시 @@ -307,7 +306,7 @@ 기기 인증을 하고 있지 않아요. 기기 인증을 켜면 설정에서 SimpleX 잠금 기능을 사용할 수 있어요. %d 시간 %d 시간 - 앱/데이터베이스의 다른 마이그레이션: %s / %s + 앱/데이터베이스의 다른 이전: %s / %s 다른 이름, 아바타 그리고 전송 격리. 다시 보지 않기 이 대화 상대로부터의 메시지를 수신할 서버와 연결되었어요. @@ -336,7 +335,7 @@ 환영 메시지 그룹 프로필 수정 그룹 나가기 - 1:1 채팅 시작하기 + 다이렉트 메시지 보내기 서버 인다이렉트 (%1$s) 허용함 @@ -375,12 +374,12 @@ 프로필 생성 오류! 그룹 링크로 익명 채팅 그룹 링크로 채팅 - 일회용 링크로 채팅 + 일회용 링크를 통해 일회용 익명 링크를 공유했어요. 일회용 링크를 공유했어요. 상대의 연락처 링크로 익명 연결 상대의 연락처 링크로 연결 - 일회용 연락처로 익명 연결 + 일회용 링크로 익명 연결 SMP 서버 주소가 올바른 형식이고 줄로 구분되어 있고 중복이 없는지 확인해 주세요. SMP 서버 저장 오류 네트워크 설정 업데이트 오류 @@ -403,13 +402,13 @@ 테스트가 %s단계에서 실패했어요. 서버는 대기열을 생성하고 비밀번호를 확인하려면 인증이 필요해요. 알림을 받으려면 데이터베이스 암호를 입력해 주세요. - 비밀번호가 필요해요. - 프로필 삭제 오류 + 암호가 필요해요. + 사용자 프로필 삭제 오류 사용자 개인정보 업데이트 오류 데이터베이스가 올바르게 작동하지 안하요. 자세히 알아보려면 탭하세요. 수정하기 - 메시지가 삭제돼요. 삭제 후 복구할 수 없어요! - 메시지가 삭제 표시될 거예요. 대화 상대는 여전히 삭제된 내용을 볼 수 있어요. + 메시지가 삭제됩니다. 이 결정은 되돌릴 수 없습니다! + 메시지가 삭제 표시됩니다. 수신자는 여전히 삭제된 내용을 볼 수 있습니다. WebRTC ICE 서버 주소가 올바른 형식이고 줄로 구분되고 중복이 없는지 확인해 주세요. ICE 서버(한 줄에 하나씩) ICE 서버 저장 오류 @@ -425,39 +424,39 @@ 데이터베이스 내보내기 자동 삭제되는 메시지를 사용할까요\? 설정 변경 오류 - 이 작업은 되돌릴 수 없어요. 선택한 시간보다 일찍 보내거나 받은 메시지는 삭제돼요. 이는 몇 분 걸릴 수 있어요. + 이 결정은 되돌릴 수 없습니다. 선택한 시간보다 일찍 보내거나 받은 메시지는 삭제됩니다. 이는 몇 분 걸릴 수 있습니다. 오류: %s - 올바른 비밀번호를 입력해 주세요. - 데이터베이스 비밀번호 변경이 완료되지 않았어요. + 올바른 암호를 입력해 주세요. + 데이터베이스 암호 변경이 완료되지 않았어요. 데이터베이스 오류 복구 그룹 링크 생성 오류 그룹 링크 업데이트 오류 역할 변경 오류 멤버 삭제 오류 데이터베이스 다운그레이드 - 마이그레이션: %s - 모든 멤버에게서 그룹이 삭제돼요. 삭제 후 복구할 수 없어요! - 나에게서만 그룹이 삭제되요. 삭제 후 복구할 수 없어요! + 이전: %s + 모든 멤버에게서 그룹이 삭제됩니다. 이 결정은 되돌릴 수 없습니다! + 나에게서만 그룹이 삭제됩니다. 이 결정은 되돌릴 수 없습니다! 파일을 찾을 수 없음 사용자 비밀번호 저장 오류 채팅 정지하기 오류 - 채팅 데이터베이스 내보내기 오류 - 채팅 데이터베이스가 암호화되지 않았어요. 비밀번호를 설정하여 보호해 주세요. - 비밀번호를 입력해 주세요… + 채팅 데이터베이스를 내보내는 동안 오류 + 채팅 데이터베이스가 암호화되지 않았어요. 암호를 설정하여 보호해 주세요. + 암호를 입력해 주세요… 검색에 비밀번호 입력 이미지 수정하기 - 이 작업은 실행 취소될 수 없어요. 프로필, 연락처, 메시지 및 파일이 영구적으로 손실돼요. + 이 결정은 되돌릴 수 없습니다. 프로필, 연락처, 메시지 및 파일이 영구적으로 손실됩니다. 채팅 데이터베이스 가져오기 오류 데이터베이스를 암호화할까요\? 데이터베이스 ID 및 전송 격리 옵션. 채팅 시작하기 오류 데이터베이스 암호화 오류 - 올바른 현재 비밀번호를 입력해 주세요. + 올바른 현재 암호를 입력해 주세요. 채팅 프로필 삭제 프로필 삭제 경고: 일부 데이터가 손실될 수 있어요! 데이터베이스 업그레이드 - 이 작업은 실행 취소될 수 없어요. 수신 및 전송된 모든 파일과 미디어가 삭제돼요. 저해상도 사진만 삭제되지 않아요. + 이 결정은 되돌릴 수 없습니다. 수신 및 전송된 모든 파일과 미디어가 삭제됩니다. 저해상도 사진은 삭제되지 않습니다. 채팅 데이터베이스 삭제 오류 그룹 링크 삭제 오류 파일 저장 오류 @@ -488,14 +487,14 @@ 그룹으로 초대 %1$s 그룹 링크 환영 메시지 - 보여지는 그룹 이름 + 그룹 이름 입력: 그룹 이름 : - 그룹은 완전히 탈중앙화되어 있으며 구성원만 그룹을 볼 수 있어요. - 프로필이 그룹 구성원에게 전송될 거예요. + 완전히 탈중앙화됨 – 멤버만 볼 수 있습니다. + 프로필이 그룹 멤버에게 전송될 거예요. 그룹 프로필은 서버가 아닌 멤버들의 기기에 저장되어요. 그룹 설정 - 그룹 구성원은 사라지는 메시지를 보낼 수 있습니다. - 그룹 멤버들끼리 1:1 채팅을 할 수 있어요. + 그룹 멤버는 사라지는 메시지를 보낼 수 있습니다. + 그룹 멤버들끼리 다이렉트 메시지를 보낼 수 있어요. 멤버 초대하기 비활성 그룹 관찰자 @@ -513,7 +512,7 @@ 마크다운 사용법 SimpleX 작동 방식 그룹 초대가 만료되었어요. - 그룹 멤버는 보낸 메시지를 영구 삭제할 수 있어요. + 그룹 멤버는 보낸 메시지를 영구 삭제할 수 있습니다. (24 시간) 그룹 멤버는 음성 메시지를 보낼 수 있어요. 숨긴 프로필 비밀번호 작동 방식 @@ -545,7 +544,7 @@ 이미지 수가 너무 많아요! 거절해도 상대에게 알림이 전송되지 않아요. 영상 통화에서 QR 코드를 보여주거나 링크를 공유해 주세요.]]> - 영상 전화 + 영상 통화 영상 끄기 스피커 켜기 영상 켜기 @@ -556,7 +555,7 @@ 대화 상대가 업로드를 완료하면 이미지가 수신될 거예요. 프로필 이미지 하나의 프로필로 여러 사람과 연락할 필요 없이 무수히 많은 익명 프로필로 연락할 수 있어요. - 스팸 및 남용에 면역 + 스팸 방지 무시하기 SimpleX Chat 초대 링크를 받으면 브라우저에서 참여할 수 있어요 : 링크 미리보기 이미지 @@ -586,16 +585,16 @@ 그룹 호환되지 않는 데이터베이스 버전 그룹에 참여 중 - 익명 모드는 기본 프로필 이름과 사진과 같은 개인 정보를 보호해줘요. 새 대화 상대마다 새로운 랜덤 프로필이 만들어져요. + 익명 모드는 대화 상대마다 새로운 무작위 프로필을 사용하여 개인 정보를 보호합니다. %s 은(는) 인증되었어요. 기울게 익명 프로필 사용 중 초대받은 그룹에 참여하면, 그 그룹에서도 동일한 익명 프로필이 사용되어요. - 내 랜덤 프로필 + 내 무작위 프로필 음성 전화 옴 %s은(는) 인증되지 않았어요. 터미널용 SimpleX Chat를 설치하세요 - 영상 전화 옴 - 잘못된 마이그레이션 확인 + 영상 통화 옴 + 잘못된 이전 확인 익명 모드로 참여 잘못된 QR 코드 잘못된 보안 코드! @@ -617,7 +616,7 @@ 연락처 이름 및 메시지 숨기기 켜기 대화 상대가 나와의 연결을 삭제했을 가능성이 커요. - 메시지 전달 오류 + 메시지 전송 오류 조정 모든 멤버에게서 메시지가 삭제될 거예요. 이 메시지는 모든 멤버에게 조정됨으로 표시될 거예요. @@ -634,11 +633,11 @@ 메시지 이 설정은 현재 내 프로필의 메시지에 적용되어요. 멤버 - 역할이 "%s"(으)로 변경되고, 회원은 새로운 초대를 받게 될 거예요. + 역할이 %s(으)로 변경되고, 멤버는 새로운 초대를 받게 될 거예요. 이 채팅에서는 메시지 영구 삭제가 허용되지 않았어요. 나가기 큰 파일! - 네트워크 설정 + 고급 설정 연결하려면 Onion 호스트가 필요해요. 핑 횟수 핑 간격 @@ -667,14 +666,14 @@ Onion 호스트가 사용되지 않을 거예요. 전송 격리 차세대 사생활 보호 메시징 - 새 비밀번호… + 새 암호… TCP 연결 유지 활성화 %s의 새로운 기능 마크다운 도움말 SimpleX에는 사용자 식별자가 없는데도 어떻게 메시지를 전달할 수 있어요\?]]> 그룹에서 나갈까요\? - 데이터베이스 버전이 앱보다 최신이지만, 다음에 대한 다운 마이그레이션 없음: %s - 멤버가 그룹에서 제거되어요. 이 작업은 되돌릴 수 없어요! + 앱 버전보다 최신 버전의 데이터베이스를 사용하고 있지만 데이터베이스를 다운그레이드할 수 없습니다: %s + 멤버가 그룹에서 제거됩니다. 이 결정은 되돌릴 수 없습니다! 역할이 "%s"(으)로 변경되어요. 그룹의 모든 멤버에게 알림이 전송됩니다. 기본값으로 재설정 메시지 내용 @@ -690,18 +689,18 @@ SimpleX Chat 메시지 그룹 관리자에게 문의해 주세요. 메시지를 보낼 수 없습니다! - + OK 거절 비밀번호 표시 2계층 종단 간 암호화 로 전송된 사용자 프로필, 연락처, 그룹 및 메시지를 저장되어요.]]> 자세한 내용은 GitHub에서 확인해 주세요. - 개인 정보 및 보안 + 개인 정보 보호 및 보안 알림은 앱이 중지되기 전까지만 전달될 거예요! 당신만 사라지는 메시지를 보낼 수 있습니다. 사라지는 메시지 전송은 허용되지 않습니다. 음성 메시지 허용되지 않음. 사라지는 메시지 전송은 허용되지 않습니다. - 이전 데이터베이스 기록 + 이전 데이터베이스 보관함 %1$s 초대됨 나감 강퇴됨 @@ -709,7 +708,7 @@ 그룹 소유자만 그룹 설정을 변경할 수 있어요. 다음을 통해 수신 대화 상대만 사라지는 메시지를 보낼 수 있습니다. - 멤버들 간의 1:1 채팅이 허용되지 않음. + 멤버들 간의 다이렉트 메시지가 허용되지 않음. 나만 음성 메시지를 보낼 수 있어요. 대화 상대만 음성 메시지를 보낼 수 있어요. 메시지 영구 삭제 허용되지 않음. @@ -720,9 +719,9 @@ 붙여넣기 프로필은 대화 상대들하고만 공유됩니다. 프라이버시의 재정의 - 오픈 소스 프로토콜과 코드 - 누구나 자신만의 서버를 구축할 수 있어요. + 누구나 서버를 호스팅할 수 있습니다. 앱이 실행 중일 때 - GitHub 에서 확인해 주세요.]]> + GitHub 에서 확인해 주세요.]]> 릴레이 서버는 IP 주소를 숨겨주지만, 통화 시간을 관찰 할 수 있어요. 그룹 링크로 초대 설정을 통해 나중에 변경할 수 있어요. @@ -732,7 +731,7 @@ 열기 앱 잠금 %1$s님, 환영합니다! - (그룹 구성원에게만 저장됨) + (그룹 멤버에게만 저장됨) 앱 평가하기 주기적 즉시 @@ -752,12 +751,12 @@ 저장하고 그룹 멤버들에게 알리기 저장하고 대화 상대에게 알리기 지우기 - 아카이브 저장하기 - 암호 저장소에 비밀번호 저장하기 + 보관함 저장하기 + 암호 저장소에 암호 저장하기 데이터베이스 백업 복원하기 - 데이터베이스 백업을 복원한 후 이전 비밀번호를 입력해 주세요. 이 작업은 되돌릴 수 없어요. + 데이터베이스 백업을 복원한 후 이전 비밀번호를 입력해 주십시오. 이 결정은 되돌릴 수 없습니다. 데이터베이스 백업을 복원할까요\? - 키스토어에서 암호를 찾을 수 없어요. 직접 입력해 주세요. 백업 도구를 사용하여 복원했을 때 이 문제가 발생할 수 있는데, 그런 경우가 아니라면 개발자에게 알려주세요. + 암호 저장소에서 암호를 찾을 수 없어요. 직접 입력해 주세요. 백업 도구를 사용하여 복원했을 때 이 문제가 발생할 수 있는데, 그런 경우가 아니라면 개발자에게 알려주세요. 저장하고 그룹 프로필 업데이트하기 환영 메시지를 저장할까요\? 그룹 프로필 저장하기 @@ -766,13 +765,13 @@ 코드 스캔하기 대화 상대의 앱에서 보안 코드를 스캔해 주세요. 저장된 WebRTC ICE 서버가 제거될 거예요. - 비밀번호 저장하고 채팅 열기 + 암호를 저장하고 채팅 열기 역할 설정을 저장할까요\? 프로필 비밀번호 저장하기 가져온 채팅 데이터베이스를 사용하려면 앱을 다시 실행해 주세요. 새 프로필을 만드려면 앱을 다시 실행해 주세요. - 암호 저장소에서 비밀번호를 삭제할까요\? + 암호 저장소에서 암호를 삭제할까요? 채팅 기능 실행하기 복원하기 대화 상대가 파일 전송을 취소했어요. @@ -805,7 +804,7 @@ 파일 공유… 이미지 공유… 메시지 공유… - 라이브 메시지 보내기 - 입력 과정을 실시간으로 상대에게 보여줘요. + 라이브 메시지 보내기 - 입력 과정을 실시간으로 상대에게 보여줍니다. 보내기 초대 링크 공유 보안 코드 @@ -854,16 +853,16 @@ 메시지 및 파일 익명 모드 실험적 - 내보낼 비밀번호 설정 + 내보낼 암호 설정 SMP 서버 미리 설정된 서버 주소 내 서버 내 서버 주소 %1$s을(를) 강퇴했어요. 채팅 데이터베이스를 내보내기, 가져오기 또는 삭제 하려면 채팅 기능을 중지해 주세요. 채팅 기능이 중지된 동안에는 메시지를 주고받을 수 없어요. - 비밀번호를 모르면 변경하거나 찾을 수 없으므로 비밀번호를 안전하게 보관해 주세요. + 암호를 모르면 변경하거나 찾을 수 없으므로 암호를 안전하게 보관해 주세요. 제출하기 - 비밀번호를 모르면 채팅에 액세스할 수 없으니 비밀번호를 안전하게 보관해 주세요. + 암호를 모르면 채팅에 액세스할 수 없으니 암호를 안전하게 보관해 주세요. 채팅 기능을 중지할까요\? 데이터베이스 작업을 할 수 있도록 채팅 기능을 중지하기 수신 주소 바꾸기 @@ -884,7 +883,7 @@ 사용자화 전송 - 자폭 패스코드 변경 + 자체 소멸 패스코드 변경 모든 앱 데이터가 삭제되었습니다. 인증 패스코드 변경 @@ -897,12 +896,12 @@ 다른 사용자와 연결할 수 있도록 주소를 만듭니다. 커스텀 테마 자동 수락 - 자폭 모드 변경 + 자체 소멸 모드 변경 (현재) 다크 테마 메시지 반응을 허용합니다. 당신과 대화 상대 모두 메시지 반응을 추가할 수 있습니다. - 대화 상대가 당신에게 전화할 수 있도록 허용합니다. + 대화 상대가 당신에게 통화할 수 있도록 허용합니다. 현재 패스코드 SimpleX 주소에 대하여 인증 취소됨 @@ -917,12 +916,12 @@ 대화 상대가 허용하는 경우에만 메시지 반응을 허용합니다. 모든 대화가 연결된 상태로 유지됩니다. 프로필 업데이트가 대화 상대에게 전송됩니다. 전송된 메시지는 설정된 시간이 지나면 삭제됩니다. - 앱 패스코드가 자체소멸 패스코드로 대체되었습니다. + 앱 패스코드가 자체 소멸 패스코드로 대체되었습니다. 음성/영상 통화가 허가되지 않았습니다. 잘못된 메시지 해쉬 인증 실패 카메라 - 대화 상대가 메시지 응답을 추가할 수 있도록 허용합니다. + 대화 상대가 메시지 반응을 추가할 수 있도록 허용합니다. 잘못된 메시지 아이디 당신과 대화 상대 모두 전화를 걸 수 있습니다. 앱 패스코드 @@ -947,7 +946,7 @@ 연락처 추가 관리자 관리자는 모든 멤버를 위해 특정 멤버를 차단할 수 있습니다. - 메시지 전달 확인서! + 메시지 전송 확인서! 우리가 놓친 두 번째 체크! ✅ 주소 변경 중지 - 최대 5분의 음성 메시지. @@ -963,4 +962,470 @@ 더 보기 보안 평가 SimpleX Chat 보안은 Trail of Bits에 의해 감사되었습니다. + 이미 연결 중입니다! + %1$d개 기타 파일 오류. + %1$d개 파일이 다운로드되지 않았습니다. + %1$d개의 파일을 다운로드하지 못했습니다. + 암호화 동의 중… + 몇 가지 더 + 허용 + %1$s개의 메시지가 전송되지 않았습니다. + %1$d개의 파일이 아직 다운로드 중입니다. + %1$d개 파일이 삭제되었습니다. + 확인됨 + 승인 오류 + SimpleX 링크 전송을 허용합니다. + 앱 데이터 이전 + 모든 연락처, 대화 및 파일은 안전하게 암호화되어 구성된 XFTP 릴레이에 일괄 업로드됩니다. + 활성 연결 + 모든 프로필 + 적용 + 모든 메시지가 삭제됩니다. 이 결정은 취소할 수 없습니다! + 파일 및 미디어 전송을 허용합니다. + 새로운 무작위 프로필이 공유됩니다. + 모든 색상 모드 + 항상 + 외 %d개 이벤트 + 앱이 새 로컬 파일 (비디오 제외)을 암호화합니다. + 이미 그룹에 있습니다! + 항상 프라이빗 라우팅 사용. + %s에 대한 암호화에 동의 중… + %s의 새 메시지는 모두 숨겨집니다! + 통화를 허용할까요? + 다운그레이드 허용 + 채팅 + 데스크톱과의 연결이 잘못된 상태입니다. + 보관 및 업로드 + 연결 중 + %1$d항목의 파일 오류:\n%2$s + 생성 + 올바른 이름을 %s 로 지정하시겠습니까? + 카메라 + 카메라와 마이크 + 연락처 + 연락처 %1$s가 %2$s (으)로 변경됨 + 직접 연결하시겠습니까? + 적용 대상 + 무작위 프로필을 사용하여 그룹을 만듭니다. + 잘못된 데스크톱 주소 + 연결이 종료됨 + 연결됨 + 연결 + 음성 통화 + 곧 출시 예정입니다! + 채팅 데이터베이스 + 채팅 테마 + 친구들과 더 빠르게 연결하세요. + 연결이 중지됨 + 데스크톱에 연결됨 + 데스크톱에 연결 중 + 자신과 연결하겠습니까? + 채팅이 이전되었습니다! + - 디렉터리 서비스(베타)에 연결하세요!\n- 전송 알림(최대 20명).\n- 더 빠르고 안정적입니다. + 이전 취소 + 이전하려는 데이터베이스의 암호를 기억하고 있는지 확인합니다. + 익명 모드로 연결 + 베타 + 앱 패스코드 + 계속 + 채팅이 중지되었습니다. 다른 기기에서 이 데이터베이스를 이미 사용하고 있다면, 채팅을 시작하기 전에 다시 전송해야 합니다. + 직접 연결됨 + 일괄 다운로드됨 + %1$s과(와) 연결하시겠습니까? + 앱 테마 + 앱 업데이트가 다운로드되었음 + 아랍어, 불가리아어, 핀란드어, 히브리어, 태국어 및 우크라이나어 - 사용자와 Weblate 덕분입니다. + 향상된 그룹 기능 + 데이터베이스를 저장 중 + 저자 + 용량 초과 - 수신자가 이전에 보낸 메시지를 받지 못했습니다. + 셀룰러 + 채팅 색상 + 업데이트 확인 + 인터넷 연결을 확인하고 다시 시도하십시오 + 다른 기기에서 이전을 선택하고 QR 코드를 스캔합니다.]]> + 완료 + 알 수 없는 서버의 파일을 확인합니다. + 연결된 서버 + 연결 및 서버 상태. + %s 과의 연결이 잘못된 상태입니다.]]> + 네트워크 관리 + 수신 주소를 변경하겠습니까? + 데스크톱에 연결 + 메시지를 보낼 수 없음 + 연락처 삭제를 확인하시겠습니까? + 연락처가 삭제됩니다. 이 결정은 되돌릴 수 없습니다! + 대화가 삭제되었습니다! + 연락처가 삭제되었어요! + 파일 선택 + 카메라를 사용할 수 없음 + 그룹 생성: 새로운 그룹을 생성합니다.]]> + 연락처 추가 : 새 초대 링크를 만들거나 받은 링크를 통해 연결합니다.]]> + 개인 메모를 지우시겠습니까? + XFTP 서버 구성 + 나중에 채팅할 수 있도록 연락처를 보관합니다. + 연결된 데스크톱 + 일괄 업로드됨 + 보관된 연락처 + SMP 서버 구성 + 채팅 프로필 생성 + 그룹 멤버에 전화를 걸 수 없음 + 자동 연결 + 연결이 중지됨 + 시도 + 앱 세션 + 업데이트 확인 + 주소 필드 근처에 있는 정보 버튼을 클릭하여 마이크를 사용할 수 있습니다. + 모서리 + 채팅 데이터베이스를 내보냈습니다 + 연락처에 전화를 걸 수 없음 + 연락처에 연결하니, 잠시 기다리거나 나중에 확인하십시오! + 연락처가 삭제됩니다. + 색상 모드 + 복사 오류 + 향상된 통화 기능 + 향상된 메시지 날짜 기능 + 향상된 보안 ✅ + 링크로 연결하시겠습니까? + 네트워크 설정 확인 + 일괄 삭제됨 + 업로드 확인 + 모바일에 연결 + 연결된 모바일 + 연결 + 그룹 멤버에게 메시지를 보낼 수 없음 + 전송 알림을 활성화하시겠습니까? + 암호화 OK + 데스크톱 + 기능 처리 시간이: %1$d 초 이상: %2$s + 주소를 만들지 않음 + 기기 + 파일 및 미디어 금지됨! + 링크 생성 중… + 전달 서버 %1$s에 연결하는 동안 오류가 발생했습니다. 나중에 다시 시도하십시오. + %d 초 + 모든 그룹에 사용 안 함 + 사용 안 함 (그룹 변경 사항 유지) + 데스크톱 앱에서 새 프로필 생성합니다. 💻 + 오류 + 주소 변경을 중단하는 중 오류 + 메시지를 전송하는 동안 오류 + 멤버의 %d 메시지를 삭제하시겠습니까? + 파일을 찾을 수 없음 - 파일이 삭제되었거나 취소되었을 가능성이 큽니다. + 통화 + 다운로드 + 프록시에서 자격 증명을 사용하지 마십시오. + 프록시를 저장하는 중 오류 + 이어폰 + 블루투스 + 암호화 재협상 실패 + 자체 소멸 활성화 + 활성화 (변경 사항 유지) + 전송 알림을 비활성화하시겠습니까? + 그룹 전송 알림을 비활성화하시겠습니까? + 모두 사용 안 함 + 모두 활성화 + 데이터베이스가 암호화되고 암호가 설정에 저장됩니다. + %s 차단됨 + %s 의 암호화 재협상 필요 + %s 의 암호화 재협상 허용 + 멤버 블록 오류 + 그룹 검색 및 참여하기 + 더 빠른 참여와 더 안정적인 메시지. + 전송 알림! + 활성화 + 데스크톱 연결을 끊으시겠습니까? + 데스크톱에 잘못된 초대 코드가 잘못되었습니다 + 데스크톱 연결이 끊어졌습니다 + 다운로드 실패 + 채팅 데이터베이스를 내보내는 동안 오류 + 파일 + 오류 + 다운로드됨 + 만료 + 삭제됨 + 삭제 오류 + 새 멤버에게 내역을 보내지 마십시오. + 삭제 및 연락처에 알림 + 차단됨 + 관리자에 의해 차단됨 + %d 개의 메시지가 차단됨 + %d 개의 메시지가 관리자에 의해 차단됨 + %1$s의 대상 서버 주소가 전달 서버 %2$s의 설정과 호환되지 않습니다. + 대상 서버 버전 %1$s이 전달 서버 %2$s와 호환되지 않습니다. + 확장 + 카메라 권한 활성화 + 잠금 활성화 + 로컬 파일 암호화 + 암호화 재협상 필요 + 멤버 연락처를 만드는 동안 오류 + 초대장을 보내는 중 오류 + 비활성화됨 + 모두 차단 + 이 멤버를 차단하시겠습니까? + 저장된 파일 & 미디어 암호화 + 한 번에 최대 20개의 메시지를 삭제할 수 있습니다. + 더 나은 개인정보 보호를 위한 흐리기 + 오류 + %s 에서 연결이 끊어졌습니다]]> + 데스크톱 기기 + 데스크톱 버전이 지원되지 않습니다. 두 기기가 동일한 버전에 있는지 확인하십시오. + 암호 입력 + 보관함을 다운로드하는 동안 오류 + 보관함을 업로드하는 중 오류 + 설정을 저장하는 중 오류 + 내보낸 파일이 없음 + 이 기기에서 데이터베이스 삭제 + 경고: 보관된 데이터가 삭제됩니다.]]> + 상세 + 서버를 다시 연결하는 중 오류 + 다운로드 오류 + 사용 안 함 + 전송 알림을 활성화하는 동안 오류! + 데스크톱이 비활성 상태입니다 + 보관함 다운로드 중 + 보관 링크를 생성 중 + 파일 오류 + 프로필 생성 + 파일 및 미디어 + 로컬 네트워크를 통해 탐색 가능 + 암호화 재협상 오류 + %d 메시지를 삭제하시겠습니까? + 다운로드 + GitHub에서 새 버전을 다운로드합니다. + 데스크톱 주소 + 패스코드 + 모든 그룹에 활성화 + 활성화 (그룹 변경 사항 유지) + 파일 + 삭제된 연락처 + %s 의암호화에 동의함 + %s 의 암호화 OK + 상대가 온라인 상태가 될 때까지 기다릴 필요가 없습니다! + 그룹 멤버 차단 + 현재 프로필 + SMP 서버를 로드하는 중 오류 + 개인 메모를 삭제하는 동안 오류 + 데이터베이스를 삭제하는 동안 오류 + 다음과 같은 이유로 끊어졌습니다: %s + 통화 종료 + 알림을 표시하는 동안 오류가 발생하였으니, 개발자에게 문의하십시오. + %d 분 + %d 개의 메시지가 삭제됨 + XFTP 서버를 저장하는 중 오류 + 알림 비활성화 + 패스코드 입력 + 주소를 설정하는 중 오류 + 좋아함 + 연결을 동기화하는 중 오류 + 콘텐츠를 표시하는 중 오류 + 메시지를 표시하는 중 오류 + 미디어 흐리기 + 참고: 두 기기에서 동일한 데이터베이스를 사용하면 연결된 사람들의 메시지 복호화가 깨질 수 있으며, 이는 보안 보호 조치입니다.]]> + 다크 + 데이터베이스 이전이 진행 중입니다.\n이 작업은 몇 분 정도 걸릴 수 있습니다. + 대상 서버 오류: %1$s + 전송 + 상세 통계 + 개발자 옵션 + 사용자 또는 대상의 서버가 프라이빗 라우팅을 지원하지 않는 경우에도 메시지를 직접 보내지 마십시오. + 알림 없이 삭제 + 프로필을 전환하는 중 오류 + 삭제 완료 + 에 사라짐 + 다크 모드 + 로컬 네트워크를 통해 탐색 + 차단됨 + 다이렉트 채팅에서 활성화함 (베타)! + 자체 소멸 + 환영 메시지 입력…(선택사항) + WebView를 초기화하는 중 오류가 발생했습니다. WebView가 설치되어 있고 지원되는 아키텍처가 arm64인지 확인합니다.\n오류: %s + XFTP 서버를 로드하는 중 오류가 발생했습니다. + 세부 정보를 로드하는 중 오류 + 통계를 재설정하는 중 오류 + 대화에서 비활성화 된 경우에도 마찬가지입니다. + 테마 내보내기 + 삭제 완료: %s + 보낸 날짜: %s + 관리자에 의해 차단됨 + 비활성화됨 + 치명적 오류 + 오류: %1$s + WebView를 초기화하는 중 오류가 발생했습니다. 시스템을 새 버전으로 업데이트하십시오. 개발자에게 문의하세요.\n오류: %s + 오류 + 생성 완료 + 암호화에 동의함 + 암호화 재협상 허용 + %s :에 사라짐 + 멤버 차단 + 멤버를 차단하시겠습니까? + 통화 금지! + 전송 알림이 더 이상 유효하지 않습니다! + 연결 끊기 + 데스크톱 앱 버전 %s은(는) 이 앱과 호환되지 않습니다. + 모바일 연결 끊기 + 데스크톱이 사용 중입니다 + 서버를 다시 연결하는 중 오류 + 메시지를 만드는 동안 오류 + 참고: 메시지 및 파일 릴레이는 SOCKS 프록시를 통해 연결됩니다. 통화 및 전송 링크 미리 보기는 직접 연결을 사용합니다.]]> + 비활성화됨 + 프라이빗 라우팅 사용안함 + 앱 업데이트를 다운로드하는 중입니다. 앱을 닫지 마세요 + %s (%s) 를 다운로드 + 비활성화 + 브라우저를 여는 중 오류 + 차단 + 검은색 + 사용 안 함 (변경 사항 유지) + 자체 소멸 패스코드 활성화 + 데이터베이스 암호화 암호가 업데이트되고 설정에 저장됩니다. + 환영 메시지 입력… + 그룹 생성 + 다크 모드 색상 + 파일 및 미디어가 허용되지 않음 + 향상된 사용자 경험 + 사용자 지정 가능한 메시지 모양. + 최대 200개의 메시지를 삭제하거나 관리할 수 있습니다. + 링크 세부 정보를 다운로드하는 중 + 암호 해독 오류 + 다운로드한 파일 + 중복 + 암호를 확인하는 중 오류: + 그룹 전송 알림을 활성화하시겠습니까? + %d 그룹 이벤트 + 보낸 날짜 + 전송 디버그 + 파일 및 미디어는 이 그룹에서 금지됩니다. + 이 장치의 이름을 입력하십시오… + 데스크톱을 찾음 + 전달 서버: %1$s\n대상 서버 오류: %2$s + 전달 서버: %1$s\n오류: %2$s + %1$s 메시지를 전송하시겠습니까? + 파일 없이 메시지를 전달하시겠습니까? + %1$s 메시지 전송 중 + 메시지 전송… + Android 설정에서 이 권한을 찾아 수동으로 허용하십시오. + 글꼴 크기 + 안녕하세요! + 안녕하세요! + 파일 서버 오류: %1$s + 파일이 서버에서 삭제됩니다. + 전달 서버 %1$s가 대상 서버 %2$s에 연결하지 못했습니다. 나중에 시도하십시오. + 전달 서버 버전이 네트워크 설정과 호환되지 않습니다: %1$s. + 전달 서버 주소가 네트워크 설정과 호환되지 않습니다: %1$s. + 전송됨 + 에서 전송됨 + 파일 상태: %s + 맞춤 + 드디어, 우리는 그것들을 얻었냈습니다! 🚀 + 파일이 삭제되었거나 링크가 유효하지 않음 + 이전 완료 + 전송 + 전송됨 + 메시지 전송… + 연락처에서 지원하지 않는 수정 + 채우기 + 수정 + 연결 수정 + 연결을 수정하시겠습니까? + 그룹 멤버에서 지원하지 않는 수정 + 메시지 전송 및 저장 + 배터리 사용량을 더욱 줄임 + 한 번에 최대 20개의 메시지를 전달할 수 있습니다. + 다른 기기로 이전을 완료합니다. + 파일 상태 + 프랑스어 인터페이스 + 메시지 전송 경고 + 종단 간 암호화로 보호되며, 완벽한 전방 비밀성, 부인 방지 및 침입 복구 기능이 포함되어 있습니다.]]> + 양자 저항 종단 간 암호화로 보호되며, 완벽한 전방 비밀성, 메시지 부인 방지 및 침입 복구 기능이 포함되어 있습니다.]]> + 보관함을 가져오는 중 + 메시지를 선택한 후 메시지가 삭제되었습니다. + 메시지 + 권한 부여 + 설정에서 부여 + 전화 권한 부여 + 헤드폰 + 메시지 초안 + 앱을 열 때 자체 소멸 패스코드를 입력하는 경우: + 메시지 초안 + 향상된 메시지 전송 + 메시지 출처는 비공개로 유지됩니다. + 호환되지 않는 버전 + 가져오기 실패 + 수신된 메시지 + 보낸 메시지 + 메시지 상태 + 익명 그룹 + 내역 + 정보 + 답장 대상 + 숨기기 + 테마 가져오기 + 메시지 반응 + 이 채팅에서는 메시지 반응이 금지됩니다. + 그룹 관리 + 헝가리어 및 튀르키예어 UI + 향상된 메시지 전송 + 시간 + 글꼴 크기 키우기 + 최근 앱 목록에서 앱 화면을 숨깁니다. + (신규)]]> + 호스트 + 즉시 + 잘못된 패스코드 + 앱을 열 때 이 패스코드를 입력하면 모든 앱 데이터가 되돌릴 수 없게 제거됩니다! + 메시지 수신 + 그룹 멤버는 파일 및 미디어를 보낼 수 있습니다. + 그룹이 이미 존재합니다! + 벨소리 + 메시지 전송됨 + 멤버가 활동 상태가 되면 나중에 메시지가 전달될 수 있습니다. + 메시지가 삭제됩니다. 이 결정은 되돌릴 수 없습니다! + 이미지 + 직접 만날 수 없는 경우 영상 통화에서 QR 코드를 보여주거나 링크를 공유하세요. + 테마 가져오기 오류 + 향상된 익명성 및 보안 + 비활성 + 즉시 알림! + 메시지 반응 + 성공적으로 설치됨 + 업데이트 설치 + 인터페이스 색상 + 메시지 라우팅 대체 + 그룹 멤버가 메시지 반응을 추가할 수 있습니다. + 메시지 모양 + 메시지 대기열 정보 + 메뉴 & 알림 + 그룹 환영 메시지 + 숨겨진 채팅 프로필 + 계속하려면 채팅을 중지시켜야 합니다. + 메시지 상태: %s + %s에서 보낸 메시지가 표시됩니다! + 내역이 새 멤버에게 전송되지 않습니다. + 이 그룹에서는 메시지 반응이 금지됩니다. + 그룹 멤버가 SimpleX 링크를 보낼 수 있습니다. + 향상된 서버 구성 + 메시지 + 메시지 서버 + 메시지 라우팅 모드 + 안녕하세요!\nSimpleX Chat 초대장이 도착했습니다: %s + 내부 오류 + 잘못된 이름입니다! + 유효하지 않은 링크 + 유지 + 유효하지 않은 링크 + 이 문제는 이전 데이터베이스의 백업을 사용하는 경우에 발생할 수 있습니다. + 이탈리아어 인터페이스 + 그룹에 참여하시겠습니까? + k + 그룹 대화에 참여 + (이 기기 v%s)]]> + 잘못된 표시 이름입니다! + 잘못된 파일 경로 + 잘못된 QR 코드 + IP 주소와 연결을 보호합니다. + 초대 + 친구 초대 + 메시지 영구 삭제 + 초대 \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml index 8d8d73b206..bf50e67bb8 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml @@ -1780,4 +1780,8 @@ Taip Kopijavimo klaida Pritaikyti prie + %1$d failo klaida (-os):\n%2$s. + %1$d failas (-ai, -ų) vis dar atsisiunčiamas (-i, -a). + Nepavyko atsisiųsti %1$d failo (-ų). + %d pasirinkta \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml index b890213da2..d683d194ba 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml @@ -2104,4 +2104,5 @@ Erro ao salvar proxy Senha Nome de usuário + Sessão do aplicativo \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index fd9492c8d7..604fd1fa1a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -2115,4 +2115,14 @@ Натисніть кнопку інформації поруч із полем адреси, щоб дозволити використання мікрофона. Відкрийте Налаштування Safari / Сайти / Мікрофон, а потім виберіть \"Дозволити для localhost\". Щоб здійснювати дзвінки, дозволіть використовувати ваш мікрофон. Завершіть дзвінок і спробуйте зателефонувати знову. + Кращі дзвінки + Краща безпека ✅ + Налаштовувана форма повідомлень. + Протоколи SimpleX перевірені компанією Trail of Bits. + Переключити аудіо та відео під час дзвінка. + Кращі дати повідомлень. + Кращий користувацький досвід + Видалити або модерувати до 200 повідомлень. + Переслати до 20 повідомлень одночасно. + Переключити профіль чату для одноразових запрошень. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml index d464ce6bba..eb19247a12 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml @@ -904,4 +904,457 @@ Chuyển tiếp tối đa 20 tin nhắn cùng một lúc. Cách sử dụng máy chủ của bạn Giao diện Hungary và Thổ Nhĩ Kỳ + Miễn nhiễm với tin nhắn rác + Nhập cơ sở dữ liệu trò chuyện? + Nếu bạn nhập mã tự hủy của mình khi mở ứng dụng: + Máy chủ ICE (một dòng mỗi máy) + Nếu bạn nhập mã truy cập này khi mở ứng dụng, tất cả dữ liệu ứng dụng sẽ bị xóa vĩnh viễn! + Hình ảnh sẽ được nhận khi liên hệ của bạn trực tuyến, xin vui lòng chờ hoặc kiểm tra lại sau! + Ngay lập tức + Hình ảnh sẽ được nhận khi liên hệ của bạn hoàn thành việc tải lên. + Hình ảnh + Nếu bạn không thể gặp mặt trực tiếp, cho liên hệ của bạn xem mã QR trong một cuộc gọi video, hoặc chia sẻ liên kết. + Hình ảnh đã được gửi + Hình ảnh + Hình ảnh đã được lưu vào Thư viện + Nếu bạn nhận được liên kết mời SimpleX Chat, bạn có thể mở nó trong trình duyệt của mình: + quét mã QR trong cuộc gọi video, hoặc liên hệ của bạn có thể chia sẻ một liên kết mời.]]> + Nếu bạn xác nhận, các máy chủ truyền tin nhắn sẽ có thể biết địa chỉ IP, và nhà cung cấp của bạn - máy chủ nào mà bạn đang kết nối. + Nếu bạn chọn từ chối người gửi sẽ KHÔNG được thông báo. + cho liên hệ của bạn xem mã QR trong cuộc gọi video, hoặc chia sẻ liên kết.]]> + Nhập + Bỏ qua + không hoạt động + Nhập dữ liệu không thành công + Đã cải thiện việc chuyển gửi tin nhắn + Lỗi nhập chủ đề + Chế độ ẩn danh + Ẩn danh + Đang nhập dữ liệu từ kho lưu trữ + Nhập chủ đề + Đã cải thiện cấu hình máy chủ + Nhóm ẩn danh + Chế độ ẩn danh bảo vệ sự riêng tư của bạn bằng cách sử dụng một hồ sơ ngẫu nhiên mới với mỗi liên hệ. + Đã cải thiện việc chuyển gửi tin nhắn + Nhập cơ sở dữ liệu + Âm thanh trong cuộc gọi + Nâng cao bảo mật và sự riêng tư + ẩn danh qua liên kết dùng một lần + Thông tin + gián tiếp (%1$s) + Để tiếp tục, hãy ngắt kết nối tới các máy chủ dùng để truyền dẫn tin nhắn. + Cài đặt SimpleX Chat cho cửa sổ câu lệnh + ẩn danh qua liên kết địa chỉ liên lạc + Cuộc gọi thoại đến + Quyền hạn ban đầu + ẩn danh qua liên kết nhóm + Mã bảo mật không đúng! + Trả lời đến + Phiên bản cơ sở dữ liệu không tương thích + Ngay lập tức + Mã truy cập không đúng + Tăng cỡ chữ. + (mới)]]> + Đã cài đặt thành công + Cài đặt cập nhật + Cuộc gọi video đến + Phiên bản không tương thích + MÀU SẮC GIAO DIỆN + đã được mời + Liên kết không hợp lệ + tác vụ trò chuyện không hợp lệ + dữ liệu không hợp lệ + định dạng tin nhắn không hợp lệ + Mời + Lời mời đã hết hạn! + Liên kết không hợp lệ! + Liên kết không hợp lệ + Tên hiển thị không hợp lệ! + Liên kết kết nối không hợp lệ + Thông báo tức thời! + Mã QR không hợp lệ + Thông báo tức thời + Thông báo tức thời đã bị tắt! + Đường dẫn tệp không hợp lệ + Lỗi nội bộ + Mã QR không hợp lệ + Địa chỉ máy chủ không hợp lệ! + Xác nhận di dời không hợp lệ + Tên không hợp lệ! + Mời + lời mời tham gia nhóm %1$s + Mời thành viên + Tên cục bộ + Cảnh báo chuyển gửi tin nhắn + Đảm bảo địa chỉ máy chủ SMP ở đúng định dạng, dòng được phân tách và không bị trùng lặp. + Đảm bảo địa chỉ máy chủ XFTP ở đúng định dạng, dòng được phân tách và không bị trùng lặp. + Liên kết với điện thoại + Thành viên không hoạt động + Tin nhắn đã được chuyển tiếp + Tin nhắn có thể được gửi sau nếu thành viên hoạt động + Đảm bảo cấu hình proxy là chính xác. + Đang tham gia nhóm + Sáng + Sáng + Các thiết bị di động đã được liên kết + Tham gia nhóm của bạn? + UI Nhật Bản và Bồ Đào Nha + được đánh dấu là đã xóa + TRỰC TIẾP + Tham gia + Tham gia ẩn danh + Bản nháp tin nhắn + Rời nhóm? + Mời thành viên + Mời vào nhóm + Rời nhóm + THÀNH VIÊN + Thông tin hàng đợi tin nhắn + Chỉ dữ liệu hồ sơ cục bộ + Giữ lại các kết nối của bạn + Làm cho một tin nhắn biến mất + Tin nhắn động! + Giữ lại lời mời chưa sử dụng? + Dự phòng định tuyến tin nhắn + Mời bạn bè + Đánh dấu đã xác thực + Điều này có thể xảy ra khi:\n1. Tin nhắn hết hạn sau 2 ngày trên máy gửi hoặc sau 30 ngày trên máy chủ.\n2. Quá trình giải mã tin nhắn thất bại do bạn hoặc liên hệ của bạn sử dụng bản sao lưu cơ sở dữ liệu cũ.\n3. Kết nối bị xâm phạm. + Tham gia nhóm? + Liên kết ứng dụng trên điện thoại và máy tính! 🔗 + thành viên %1$s đã đổi thành %2$s + Tạo kết nối riêng tư + Tạo hồ sơ riêng tư! + Đảm bảo địa chỉ máy chủ WebRTC ICE ở đúng định dạng, dòng được phân tách và không bị trùng lặp. + nếu SimpleX không có thông tin định danh người dùng, thì làm thế nào mà nó có thể chuyển tin nhắn đi được?]]> + Nó có thể xảy ra khi bạn hoặc liên hệ của bạn sử dụng bản sao lưu cơ sở dữ liệu cũ. + Chế độ khóa + Giao diện tiếng Ý + thiết bị này v%s)]]> + Biểu đạt cảm xúc tin nhắn bị cấm trong nhóm này. + đã được mời để kết nối + Tìm hiểu thêm + Giữ + Rời + Đăng nhập bằng thông tin xác thực của bạn + Lỗi chuyển gửi tin nhắn + tham gia với tư cách %s + Trợ giúp markdown + Sử dụng markdown trong tin nhắn + Tham gia nhóm? + k + Đang tải tệp + Các máy tính đã được liên kết + Đang tải các cuộc trò chuyện… + tin nhắn + Đánh dấu chưa đọc + Tối đa 40 giây, được nhận ngay lập tức. + Chỉ báo đã nhận tin nhắn! + Tệp lớn! + Đánh dấu đã đọc + Trung bình + Tin nhắn động + Giữ lại cuộc trò chuyện + Hình ảnh xem trước của liên kết + Thành viên sẽ bị xóa khỏi nhóm - việc này không thể được hoàn tác! + Chế độ sáng + UI tiếng Litva + TIN NHẮN VÀ TỆP + Việc xóa tin nhắn mà không thể phục hồi bị cấm trong nhóm này. + Tham gia vào các cuộc trò chuyện nhóm + Chế độ định tuyến tin nhắn + Hãy trò chuyện trên SimpleX Chat + in nghiêng + Nó có thể được thay đổi sau trong phần cài đặt. + Việc xóa tin nhắn mà không thể phục hồi bị cấm trong cuộc trò chuyện này. + Biểu đạt cảm xúc tin nhắn bị cấm trong cuộc trò chuyện này. + Bản nháp tin nhắn + Nó bảo vệ địa chỉ IP và các kết nối của bạn. + Khóa sau + Tin nhắn + Lỗi keychain + đã rời + đã được mời thông qua liên kết nhóm của bạn + thành viên + đã rời + Nó cho phép việc có các kết nối ẩn danh mà không có bất kỳ dữ liệu chung nào giữa chúng trong một hồ sơ trò chuyện + Đảm bảo tệp có cú pháp YAML chính xác. Xuất chủ đề để có một ví dụ về cấu trúc tệp chủ đề. + Menu và cảnh báo + Cảm xúc tin nhắn + Cảm xúc tin nhắn + Làm cho các cuộc trò chuyện của bạn trở nên khác biệt! + Tiếp nhận tin nhắn + Cài đặt máy tính đã được liên kết + đã được mời %1$s + Xóa tin nhắn mà không thể phục hồi + Tin nhắn + Máy chủ tin nhắn + Máy chủ tệp và phương tiện + Nội dung tin nhắn + Tin nhắn đã bị xóa sau khi bạn chọn chúng. + Tin nhắn từ %s sẽ được hiển thị! + Trạng thái tin nhắn + Tin nhắn đã được nhận + Tin nhắn đã được gửi + Trạng thái tin nhắn: %s + Tin nhắn quá lớn + mã hóa đầu cuốivới bí mật chuyển tiếp hoàn hảo, sự cự tuyệt và khôi phục xâm nhập.]]> + Tin nhắn sẽ bị xóa - việc này không thể được hoàn tác! + Di chuyển từ một thiết bị khác + Tin nhắn sẽ bị xóa - việc này không thể được hoàn tác! + Tin nhắn sẽ được đánh dấu để xóa. Người nhận sẽ có thể xem lại những tin nhắn này. + mã hóa đầu cuối kháng lượng tử với bí mật chuyển tiếp hoàn hảo, sự cự tuyệt và khôi phục xâm nhập.]]> + Mic + Hình dạng tin nhắn + Nguồn tin nhắn vẫn còn riêng tư. + Di chuyển thiết bị + Tin nhắn sẽ được đánh dấu để xóa. Người nhận sẽ có thể xem lại những tin nhắn này. + Di chuyển tới đây + tháng + Nhiều hồ sơ trò chuyện + Thiết bị di động mới + Không bao giờ + Trải nghiệm trò chuyện mới 🎉 + Ứng dụng máy tính mới! + Mạng & máy chủ + cuộc gọi nhỡ + Mã truy cập mới + Tên hiển thị mới: + Kết nối mạng + Trạng thái mạng + Chủ đề trò chuyện mới + đã được kiểm duyệt bởi %s + đã được kiểm duyệt + Đã được kiểm duyệt vào + Đã được kiểm duyệt vào: %s + %s đã bị ngắt kết nối]]> + Mở trong ứng dụng di động, sau đó nhấn Kết nối trong ứng dung.]]> + Tin nhắn mới + Đã tắt thông báo khi không hoạt động! + Mới trong %s + - chuyển gửi tin nhắn ổn định hơn.\n- các nhóm đã được cải thiện hơn một chút.\n- và hơn thế nữa! + Kết nối mạng ổn định hơn. + Quản lý mạng + %s bị thiếu]]> + %s đang bận]]> + Di chuyển sang một thiết bị khác + Đang di chuyển + Hơn nữa + tin nhắn mới + Khả năng cao liên hệ này đã xóa kết nối với bạn. + Sự cố mạng - tin nhắn đã hết hạn sau nhiều lần cố gắng gửi đi. + Di chuyển: %s + Tắt thông báo + Yêu cầu liên lạc mới + Kiểm duyệt + không bao giờ + Tắt thông báo + Cuộc trò chuyện mới + phút + Kho lưu trữ cơ sở dữ liệu mới + Quyền hạn thành viên mới + Nhiều cải tiến hơn nữa sắp ra mắt! + %s có một phiên bản không được hỗ trợ. Xin vui lòng đảm bảo rằng bạn dùng cùng một phiên bản trên cả hai thiết bị.]]> + Các tùy chọn phương tiện mới + Cuộc gọi nhỡ + Nhiều cải tiến hơn nữa sắp ra mắt! + Di chuyển sang một thiết bị khác qua mã QR. + Quá trình di chuyển hoàn tất + %s đang không hoạt động]]> + %s đã bị ngắt kết nối]]> + Không có thông tin chuyển gửi + Chưa có kết nối trực tiếp, tin nhắn được chuyển tiếp bởi quản trị viên. + Không có liên hệ để thêm + Không có thiết bị di động nào được kết nối + không + không có thông tin + Mật khẩu mới… + Thông tin xác thực SOCKS mới sẽ được sử dụng mỗi khi bạn khởi động ứng dụng. + Không + Không có liên hệ nào được chọn + Không có cuộc gọi nền + Không có mã truy cập ứng dụng + Không có cuộc trò chuyện nào được lọc + Không có lịch sử + Không có liên hệ nào được lọc + không + Không + Thông tin xác thực SOCKS mới sẽ được sử dụng cho mỗi máy chủ. + không có mã hóa đầu cuối + Không có thông tin, hãy thử tải lại + bật + Không có gì để chuyển tiếp! + Thông báo sẽ dừng hoạt động cho đến khi bạn khởi động lại ứng dụng + Không có thông tin định danh người dùng. + không có nội dung + tắt + tắt` + Chỉ bạn mới có thể thực hiện cuộc gọi. + Chỉ liên hệ của bạn mới có thể thả cảm xúc tin nhắn. + Chỉ có thể gửi 10 video cùng một lúc + Liên kết lời mời dùng một lần + (chỉ được lưu trữ bởi thành viên nhóm) + Xem trước thông báo + Dịch vụ thông báo + Tắt + Không có tệp nào được gửi hay được nhận + quan sát viên + Không có kết nối mạng + Chỉ chủ nhóm mới có thể bật tính năng cho phép gửi tệp và phương tiện. + Chỉ có thể gửi 10 hình ảnh cùng một lúc + Chỉ một thiết bị mới có thể hoạt động cùng một lúc + Không tương thích! + Thông báo + OK + Chỉ xóa cuộc trò chuyện + Chỉ bạn mới có thể gửi tin nhắn thoại. + bảo mật đầu cuối 2 lớp.]]> + Chỉ chủ nhóm mới có thể bật tính năng tin nhắn thoại. + Chỉ bạn mới có thể gửi tin nhắn tự xóa. + Liên kết lời mời dùng một lần + Chỉ có bạn mới có thể thả cảm xúc tin nhắn. + Thông báo sẽ chỉ được gửi cho đến khi ứng dụng dừng! + Chỉ bạn mới có thể xóa tin nhắn mà không thể phục hồi (liên hệ của bạn có thể đánh dấu chúng để xóa). (24 giờ) + Bản lưu trữ cơ sở dữ liệu cũ + được đề nghị %s + được đề nghị %s: %2s + Tắt + Chỉ chủ nhóm mới có thể điều chỉnh các tùy chọn nhóm. + Giờ thì quản trị viên có thể:\n- xóa tin nhắn của thành viên\n- vô hiệu hóa thành viên (quyền hạn quan sát viên) + Không có cuộc trò chuyện nào được chọn + Không có gì được chọn + Mở + Mở bảng điều khiển trò chuyện + Mở hồ sơ trò chuyện + Dịch vụ onion sẽ được yêu cầu để kết nối.\nXin lưu ý: bạn sẽ không thể kết nối tới các máy chủ mà không có địa chỉ .onion. + Mở + Mở thư mục cơ sở dữ liệu + Chỉ liên hệ của bạn mới có thể gửi tin nhắn tự xóa. + Khác + Hoặc quét mã QR + Dịch vụ onion sẽ được sử dụng khi có sẵn. + Chỉ liên hệ của bạn mới có thể gửi tin nhắn thoại. + chủ sở hữu + Mở cài đặt máy chủ + Mở liên kết trong trình duyệt có thể làm giảm sự riêng tư và bảo mật của kết nối. Liên kết SimpleX không đáng tin cậy sẽ được đánh dấu màu đỏ. + Chỉ liên hệ của bạn mới có thể xóa tin nhắn mà không thể phục hồi (bạn có thể đánh dấu chúng để xóa). (24 giờ) + Mở cài đặt ứng dụng + Mục mã truy cập + Mở màn hình di chuyển + Dịch vụ onion sẽ không được sử dụng. + Đang mở cơ sở dữ liệu… + Hoặc hiển thị mã này + Chỉ liên hệ của bạn mới có thể thực hiện cuộc gọi. + mở + - tùy chọn thông báo khi xóa liên hệ.\n- tên hồ sơ với dấu cách.\n- và hơn thế nữa! + Mở cài đặt + Hoặc chia sẻ đường dẫn tệp này một cách an toàn. + Mở SimpleX Chat để chấp nhận cuộc gọi + Mở Cài đặt Safari / Trang Web / Mic, rồi chọn Cho phép với localhost. + Mã truy cập + Mở cuộc hội thoại + Mở vị trí tệp + Sử dụng từ máy tính trong ứng dụng di động và quét mã QR.]]> + Hoặc dán đường dẫn lưu trữ + các chủ sở hữu + Mở cổng trong tường lửa + Mã truy cập đã được đổi! + Mở nhóm + khác + các lỗi khác + Các máy chủ SMP khác + Các máy chủ XFTP khác + Dán đường dẫn mà bạn nhận được để kết nối với liên hệ hệ của bạn… + Dán đường dẫn + Mật khẩu + Định kỳ + Đang chờ xử lý + Đang chờ xử lý + Không tìm thấy mật khẩu trong Keystore, vui lòng nhập thủ công. Điều này có thể xảy ra nếu bạn khôi phục dữ liệu ứng dụng bằng một công cụ sao lưu. Nếu không phải như vậy, xin vui lòng liên hệ với nhà phát triển. + Thành viên cũ %1$s + Dán đường dẫn để kết nối! + Thông báo định kỳ + Dán đường dẫn mà bạn nhận được + Dán + ngang hàng + Cần có mật khẩu + Dán địa chỉ máy tính + Dán đường dẫn sao lưu + Mật khẩu để hiển thị + Cuộc gọi chờ + Mã truy cập không đổi! + Mã truy cập đã được đặt! + bộ đếm PING + UI tiếng Ba Tư + Xin vui lòng xác nhận rằng cài đặt mạng cho thiết bị này là chính xác. + Xin vui lòng kiểm tra rằng đường dẫn SimpleX là chính xác. + Xin vui lòng yêu cầu liên hệ của bạn mở tính năng thực hiện cuộc gọi. + Xin vui lòng kiểm tra kết nối mạng của bạn với %1$s và thử lại. + Xin vui lòng nhập đúng mật khẩu hiện tại. + Mở từ danh sách cuộc trò chuyện. + Cuộc gọi hình trong hình + Xin vui lòng báo cáo với các nhà phát triển:\n%s + Thông báo định kỳ đã bị tắt! + Xin vui lòng kiểm tra rằng thiết bị di động và máy tính kết nối tới cùng một mạng cục bộ, và tường lửa của máy tính cho phép kết nối.\nHãy chia sẻ bất kỳ vấn đề nào khác với nhà phát triển. + Xin vui lòng kiểm tra rằng bạn đã dùng đúng đường dẫn hoặc yêu cầu liên hệ của bạn gửi cho bạn một đường dẫn khác. + Xin vui lòng ghi nhớ hoặc lưu trữ nó một cách an toàn - không có cách nào để khôi phục một mật khẩu đã bị mất! + Quyền truy cập bị tự chối! + Xin vui lòng yêu cầu liên hệ của bạn mở tính năng gửi tin nhắn thoại. + khoảng PING + Xin vui lòng nhập mật khẩu trước đó sau khi khôi phục bản sao lưu cơ sở dữ liệu. Việc này không thể được hoàn tác. + Xin vui lòng liên lạc với quản trị viên nhóm. + Xin vui lòng báo cáo với các nhà phát triển. + Xin vui lòng báo cáo tới các nhà phát triển:\n%s\n\nGợi ý rằng bạn nên khởi động lại ứng dụng. + Có lẽ vân tay chứng chỉ trong địa chỉ máy chủ là không chính xác + Đang chuẩn bị tải lên + Cổng + cổng %d + Xin vui lòng lưu trữ mật khẩu một cách an toàn, bạn sẽ KHÔNG thể trò chuyện nếu bạn làm mất nó. + Xin vui lòng chờ trong khi tệp đang được tải từ thiết bị được liên kết + Địa chỉ máy chủ cài sẵn + Lưu lại bản nháp tin nhắn cuối cùng, với các tệp đính kèm. + Đang chuẩn bị tải xuống + Xin vui lòng cập nhật ứng dụng và liên lạc với các nhà phát triển. + Máy chủ cài sẵn + Xin vui lòng thử lại sau. + Xem trước + Xin vui lòng lưu trữ mật khẩu một cách an toàn, bạn sẽ KHÔNG thể thay đổi nếu bạn làm mất nó. + Giao diện tiếng Ba Lan + Xin vui lòng khởi động lại ứng dụng. + Các máy chủ đã kết nối trước đó + Định hình lại sự riêng tư + Quyền riêng tư & bảo mật + Bản cập nhật hồ sơ sẽ được gửi đến các liên hệ của bạn. + Cấm thả cảm xúc tin nhắn. + Cấm các cuộc gọi thoại/video. + Thông báo riêng tư + Các ảnh đại diện + Mật khẩu hồ sơ + Ghi chú riêng tư + Cấm xóa tin nhắn mà không thể phục hồi. + ĐỊNH TUYẾN TIN NHẮN RIÊNG TƯ + Tên hồ sơ: + ảnh đại diện + Hồ sơ và các kết nối máy chủ + chỗ để ảnh đại diện + Tên tệp riêng tư + Định tuyến tin nhắn riêng tư 🚀 + Lỗi định tuyến riêng tư + Ghi chú riêng tư + Định tuyến riêng tư + Chủ đề hồ sơ + Cấm thả cảm xúc tin nhắn. + Thời gian chờ giao thức trên mỗi KB + Bảo vệ các hồ sơ trò chuyện của bạn bằng mật khẩu! + Bảo vệ địa chỉ IP của bạn khỏi các máy chủ tiếp tin được chọn bởi liên hệ của bạn.\nBật trong cài đặt *Mạng & các máy chủ* + Bảo vệ địa chỉ IP + Cấm gửi tin nhắn trực tiếp tới các thành viên. + Cấm gửi tin nhắn thoại. + Thời gian chờ giao thức + Cấm gửi tin nhắn tự xóa. + Bảo vệ màn hình ứng dụng + Cấm gửi tệp và phương tiện truyền thông. + Cấm gửi tin nhắn thoại. + Cấm gửi tin nhắn tự xóa. + Cấm gửi đường dẫn SimpleX + Được proxy \ No newline at end of file From 614846465fb069cb039d1416c6ed7c549359c3ce Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 27 Nov 2024 23:51:51 +0000 Subject: [PATCH 128/567] website: translations (#5266) * ep/blog-v61 * Translated using Weblate (Hungarian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ --------- Co-authored-by: summoner001 --- website/langs/hu.json | 72 +++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/website/langs/hu.json b/website/langs/hu.json index f4a40a02bb..7ff66a5ab9 100644 --- a/website/langs/hu.json +++ b/website/langs/hu.json @@ -1,5 +1,5 @@ { - "home": "Főoldal", + "home": "Kezdőoldal", "developers": "Fejlesztők", "reference": "Referencia", "blog": "Blog", @@ -11,17 +11,17 @@ "simplex-explained-tab-1-text": "1. Felhasználói élmény", "simplex-explained-tab-2-text": "2. Hogyan működik", "simplex-explained-tab-3-text": "3. Mit látnak a kiszolgálók", - "simplex-explained-tab-1-p-1": "Létrehozhat kapcsolatokat és csoportokat, valamint kétirányú beszélgetéseket folytathat, mint bármely más üzenetküldőben.", - "simplex-explained-tab-1-p-2": "Hogyan működhet egyirányú üzenet várakoztatással és felhasználói profil azonosítók nélkül?", - "simplex-explained-tab-2-p-1": "Minden kapcsolathoz két különböző üzenetküldési várakoztatást használ a különböző kiszolgálókon keresztül történő üzenetküldéshez és -fogadáshoz.", + "simplex-explained-tab-1-p-1": "Létrehozhat kapcsolatokat és csoportokat, valamint kétirányú beszélgetéseket folytathat, ugyanúgy mint bármely más üzenetküldőben.", + "simplex-explained-tab-1-p-2": "Hogyan működhet egyirányú üzenet sorbaállítással és felhasználói profil-azonosítók nélkül?", + "simplex-explained-tab-2-p-1": "Minden kapcsolathoz két különböző üzenetküldési sorbaállítást használ a különböző kiszolgálókon keresztül történő üzenetküldéshez és -fogadáshoz.", "simplex-explained-tab-2-p-2": "A kiszolgálók csak egyirányú üzeneteket továbbítanak, anélkül, hogy teljes képet kapnának a felhasználók beszélgetéseiről vagy kapcsolatairól.", - "simplex-explained-tab-3-p-1": "A kiszolgálók minden egyes üzenet várakoztatáshoz külön névtelen hitelesítő adatokkal rendelkeznek, és nem tudják, hogy melyik felhasználóhoz tartoznak.", - "simplex-explained-tab-3-p-2": "A felhasználók tovább fokozhatják a metaadatok adatvédelmét, ha a Tor segítségével férnek hozzá a kiszolgálókhoz, megakadályozva az IP-cím szerinti korrelációt.", + "simplex-explained-tab-3-p-1": "A kiszolgálók minden egyes üzenetsorbaállításhoz külön névtelen hitelesítő-adatokkal rendelkeznek, és nem tudják, hogy melyik felhasználóhoz tartoznak.", + "simplex-explained-tab-3-p-2": "A felhasználók tovább fokozhatják a metaadatok adatvédelmét, ha a Tor segítségével férnek hozzá a kiszolgálókhoz, így megakadályozva az IP-cím szerinti korrelációt.", "smp-protocol": "SMP-protokoll", - "chat-protocol": "Csevegés protokoll", + "chat-protocol": "Csevegésprotokoll", "donate": "Támogatás", "copyright-label": "© 2020-2024 SimpleX | Nyílt forráskódú projekt", - "simplex-chat-protocol": "SimpleX Chat protokoll", + "simplex-chat-protocol": "SimpleX Chat-protokoll", "terminal-cli": "Terminál CLI", "terms-and-privacy-policy": "Adatvédelmi irányelvek", "hero-header": "Újradefiniált adatvédelem", @@ -31,7 +31,7 @@ "hero-overlay-2-textlink": "Hogyan működik a SimpleX?", "hero-overlay-3-textlink": "A biztonság értékelése", "hero-2-header": "Privát kapcsolat létrehozása", - "hero-2-header-desc": "A videó bemutatja, hogyan kapcsolódhat az ismerőséhez egy egyszer használatos QR-kód segítségével, személyesen vagy videokapcsolaton keresztül. Ugyanakkor egy meghívó megosztásával is kapcsolódhat.", + "hero-2-header-desc": "A videó bemutatja, hogyan kapcsolódhat az ismerőséhez egy egyszer használható QR-kód segítségével, személyesen vagy videokapcsolaton keresztül. Ugyanakkor egy meghívó-hivatkozás megosztásával is kapcsolódhat.", "hero-overlay-1-title": "Hogyan működik a SimpleX?", "hero-overlay-2-title": "Miért ártanak a felhasználói azonosítók az adatvédelemnek?", "hero-overlay-3-title": "A biztonság értékelése", @@ -41,33 +41,33 @@ "feature-4-title": "E2E-titkosított hangüzenetek", "feature-5-title": "Eltűnő üzenetek", "feature-6-title": "E2E-titkosított
hang- és videohívások", - "feature-7-title": "Hordozható titkosított alkalmazás-adattárolás — profil áthelyezése egy másik eszközre", - "feature-8-title": "Az inkognitó mód —
egyedülálló a SimpleX Chatben", + "feature-7-title": "Hordozható titkosított alkalmazás-adattárolás — profil átköltöztetése egy másik eszközre", + "feature-8-title": "Az inkognitómód —
egyedülálló a SimpleX Chatben", "simplex-network-overlay-1-title": "Összehasonlítás más P2P üzenetküldő protokollokkal", "simplex-private-1-title": "2 rétegű végpontok közötti titkosítás", - "simplex-private-2-title": "További rétege a
kiszolgáló titkosítás", + "simplex-private-2-title": "További rétege a
kiszolgáló-titkosítás", "simplex-private-4-title": "Nem kötelező
hozzáférés Tor-on keresztül", "simplex-private-5-title": "Több rétegű
tartalom kitöltés", "simplex-private-6-title": "Sávon kívüli
kulcscsere", "simplex-private-7-title": "Üzenetintegritás
hitelesítés", "simplex-private-8-title": "Üzenetek keverése
a korreláció csökkentése érdekében", - "simplex-private-9-title": "Egyirányú
üzenet várakoztatás", - "simplex-private-10-title": "Ideiglenes névtelen páronkénti azonosítók", - "simplex-private-card-1-point-1": "Dupla-ratchet protokoll —
OTR üzenetküldés, sérülés utáni titkosság-védelemmel és -helyreállítással.", - "simplex-private-card-1-point-2": "NaCL cryptobox minden egyes üzenet várakoztatáshoz, hogy megakadályozza a forgalom korrelációját az üzenet várakoztatások között, ha a TLS veszélybe kerül.", + "simplex-private-9-title": "Egyirányú
üzenetsorbaállítás", + "simplex-private-10-title": "Ideiglenes, névtelen, páronkénti azonosítók", + "simplex-private-card-1-point-1": "Double-Ratchet-protokoll —
OTR-üzenetküldés, sérülés utáni titkosság-védelemmel és -helyreállítással.", + "simplex-private-card-1-point-2": "NaCL cryptobox minden egyes üzenet sorbaállításához, hogy megakadályozza a forgalom korrelációját az üzenet-sorbaállítások között, ha a TLS veszélybe kerül.", "simplex-private-card-2-point-1": "Kiegészítő kiszolgáló titkosítási réteg a címzettnek történő kézbesítéshez, hogy megakadályozza a fogadott és az elküldött kiszolgálóforgalom közötti korrelációt, ha a TLS veszélybe kerül.", - "simplex-private-card-3-point-1": "Az ügyfél-kiszolgáló kapcsolatokhoz csak az erős algoritmusokkal rendelkező TLS 1.2/1.3 protokollt használ.", + "simplex-private-card-3-point-1": "A kliens és a kiszolgálók közötti kapcsolatokhoz csak az erős algoritmusokkal rendelkező TLS 1.2/1.3 protokollt használja.", "simplex-private-card-3-point-2": "A kiszolgáló ujjlenyomata és a csatornakötés megakadályozza a MITM- és a visszajátszási támadásokat.", "simplex-private-card-3-point-3": "Az újrakapcsolódás le van tiltva a munkamenet elleni támadások megelőzése érdekében.", - "simplex-private-card-4-point-1": "Az IP-címe védelme érdekében a kiszolgálókat a Tor-on vagy más átviteli fedett hálózaton keresztül érheti el.", - "simplex-private-card-6-point-1": "Számos kommunikációs platform sebezhető a kiszolgálók vagy a hálózati szolgáltatók MITM-támadásaival szemben.", + "simplex-private-card-4-point-1": "Az IP-címe védelme érdekében a kiszolgálókat a TORon vagy más átvitel-átfedő-hálózaton keresztül is elérheti.", + "simplex-private-card-6-point-1": "Számos kommunikációs platform sebezhető a kiszolgálók vagy a hálózat-szolgáltatók MITM-támadásaival szemben.", "simplex-private-card-6-point-2": "Ennek megakadályozása érdekében a SimpleX-alkalmazások egyszeri kulcsokat adnak át sávon kívül, amikor egy címet hivatkozásként vagy QR-kódként oszt meg.", - "simplex-private-card-7-point-1": "Az integritás garantálása érdekében az üzenetek sorszámozással vannak ellátva, és tartalmazzák az előző üzenet hash-ét.", + "simplex-private-card-7-point-1": "Az integritás garantálása érdekében az üzenetek sorszámozással vannak ellátva, és tartalmazzák az előző üzenet hasítóértékét.", "simplex-private-card-7-point-2": "Ha bármilyen üzenetet hozzáadnak, eltávolítanak vagy módosítanak, a címzett értesítést kap róla.", "simplex-private-card-8-point-1": "A SimpleX-kiszolgálók alacsony késleltetésű keverési csomópontokként működnek — a bejövő és kimenő üzenetek sorrendje eltérő.", - "simplex-private-card-9-point-1": "Minden üzenetet egyetlen irányba várakoztat, a különböző küldési és vételi címekkel.", + "simplex-private-card-9-point-1": "Minden üzenetsorbaállítás egy irányba továbbítja az üzeneteket, a különböző küldési és vételi címekkel.", "simplex-private-card-9-point-2": "A hagyományos üzenetküldőkhöz képest csökkenti a támadási vektorokat és a rendelkezésre álló metaadatokat.", - "simplex-private-card-10-point-1": "A SimpleX ideiglenes névtelen páros címeket és hitelesítő adatokat használ minden egyes felhasználói kapcsolat vagy csoporttag számára.", + "simplex-private-card-10-point-1": "A SimpleX ideiglenes, névtelen, páros címeket és hitelesítő adatokat használ minden egyes felhasználói kapcsolathoz vagy csoporttaghoz.", "simplex-private-card-10-point-2": "Lehetővé teszi az üzenetek felhasználói profilazonosítók nélküli kézbesítését, ami az alternatíváknál jobb metaadat-védelmet biztosít.", "privacy-matters-1-overlay-1-title": "Az adatvédelemmel pénzt spórol meg", "privacy-matters-1-overlay-1-linkText": "Az adatvédelemmel pénzt spórol meg", @@ -79,28 +79,28 @@ "privacy-matters-3-overlay-1-linkText": "Az adatvédelem szabaddá tesz", "simplex-unique-1-title": "Teljes magánéletet élvezhet", "simplex-unique-1-overlay-1-title": "Személyazonosságának, profiljának, kapcsolatainak és metaadatainak teljes körű védelme", - "simplex-unique-2-title": "Véd
a spamektől és a visszaélésektől", - "simplex-unique-2-overlay-1-title": "A legjobb védelem a spam és a visszaélések ellen", - "simplex-unique-3-title": "Az ön adatai fölött csak ön rendelkezik", - "simplex-unique-3-overlay-1-title": "Az ön adatai fölött csak ön rendelkezik", + "simplex-unique-2-title": "Véd
a kéretlen üzenetektől és a visszaélésektől", + "simplex-unique-2-overlay-1-title": "A legjobb védelem a kéretlen üzenetek és a visszaélések ellen", + "simplex-unique-3-title": "Ön kezeli az adatait", + "simplex-unique-3-overlay-1-title": "Az adatok biztonsága és kezelése az Ön kezében van", "simplex-unique-4-title": "Öné a SimpleX-hálózat", "simplex-unique-4-overlay-1-title": "Teljesen decentralizált — a SimpleX-hálózat a felhasználóké", "hero-overlay-card-1-p-1": "Sok felhasználó kérdezte: ha a SimpleXnek nincsenek felhasználói azonosítói, honnan tudja, hogy hová kell eljuttatni az üzeneteket?", - "hero-overlay-card-1-p-2": "Az üzenetek kézbesítéséhez az összes többi platform által használt felhasználói azonosítók helyett a SimpleX az üzenetek várakoztatásához ideiglenes, névtelen, páros azonosítókat használ, külön-külön minden egyes kapcsolathoz — nincsenek hosszú távú azonosítók.", - "hero-overlay-card-1-p-4": "Ez a kialakítás megakadályozza a felhasználók metaadatainak kiszivárgását az alkalmazás szintjén. Az adatvédelem további javítása és az IP-cím védelme érdekében az üzenetküldő kiszolgálókhoz Tor hálózaton keresztül is kapcsolódhat.", + "hero-overlay-card-1-p-2": "Az üzenetek kézbesítéséhez az összes többi platform által használt felhasználói azonosítók helyett a SimpleX az üzenetek sorbaállításához ideiglenes, névtelen, páros azonosítókat használ, külön-külön minden egyes kapcsolathoz — nincsenek hosszú távú azonosítók.", + "hero-overlay-card-1-p-4": "Ez a kialakítás megakadályozza a felhasználók metaadatainak kiszivárgását az alkalmazás szintjén. Az adatvédelem további javítása és az IP-cím védelme érdekében az üzenetküldő kiszolgálókhoz TOR hálózaton keresztül is kapcsolódhat.", "hero-overlay-card-1-p-5": "Csak a kliensek tárolják a felhasználói profilokat, kapcsolatokat és csoportokat; az üzenetek küldése 2 rétegű végpontok közötti titkosítással történik.", "hero-overlay-card-1-p-6": "További leírást a SimpleX ismertetőben olvashat.", - "hero-overlay-card-2-p-1": "Ha a felhasználók állandó azonosítóval rendelkeznek, még akkor is, ha ez csak egy véletlenszerű szám, például egy munkamenet-azonosító, fennáll annak a veszélye, hogy a szolgáltató vagy egy támadó megfigyelheti, hogyan kapcsolódnak a felhasználók, és hány üzenetet küldenek.", + "hero-overlay-card-2-p-1": "Ha a felhasználók állandó azonosítóval rendelkeznek, még akkor is, ha ez csak egy véletlenszerű szám, például egy munkamenet-azonosító, fennáll annak a veszélye, hogy a szolgáltató vagy egy támadó megfigyelheti, azt hogy hogyan kapcsolódnak a felhasználók egymáshoz, és hány üzenetet küldenek egymásnak.", "hero-overlay-card-2-p-2": "Ezt az információt aztán összefüggésbe hozhatják a meglévő nyilvános közösségi hálózatokkal, és meghatározhatnak néhány valódi személyazonosságot.", - "hero-overlay-card-2-p-3": "Még a Tor v3 szolgáltatásokat használó, legprivátabb alkalmazások esetében is, ha két különböző kapcsolattartóval beszél ugyanazon a profilon keresztül, bizonyítani tudják, hogy ugyanahhoz a személyhez kapcsolódnak.", - "hero-overlay-card-2-p-4": "A SimpleX úgy védekezik ezen támadások ellen, hogy nem tartalmaz felhasználói azonosítókat. Ha pedig használja az inkognitó módot, akkor minden egyes létrejött kapcsolatban más-más felhasználó név jelenik meg, így elkerülhető a közöttük lévő összefüggések bizonyítása.", - "hero-overlay-card-3-p-1": "Trail of Bits egy vezető biztonsági és technológiai tanácsadó cég, amelynek ügyfelei közé tartoznak a nagy technológiai cégek, kormányzati ügynökségek és jelentős blokklánc projektek.", + "hero-overlay-card-2-p-3": "Még a TOR v3 szolgáltatásokat használó, legprivátabb alkalmazások esetében is, ha két különböző kapcsolattartóval beszél ugyanazon a profilon keresztül, bizonyítani tudják, hogy ugyanahhoz a személyhez kapcsolódnak.", + "hero-overlay-card-2-p-4": "A SimpleX úgy védekezik ezen támadások ellen, hogy nem tartalmaz felhasználói azonosítókat. Ha pedig használja az inkognitómódot, akkor minden egyes létrejött kapcsolatban más-más felhasználó név jelenik meg, így elkerülhető a közöttük lévő összefüggések teljes bizonyítása.", + "hero-overlay-card-3-p-1": "Trail of Bits egy vezető biztonsági és technológiai tanácsadó cég, amelynek az ügyfelei közé tartoznak nagy technológiai cégek, kormányzati ügynökségek és jelentős blokklánc projektek.", "hero-overlay-card-3-p-2": "A Trail of Bits 2022 novemberében áttekintette a SimpleX-platform kriptográfiai és hálózati komponenseit. További információk.", "simplex-network-overlay-card-1-li-1": "A P2P-hálózatok az üzenetek továbbítására a DHT valamelyik változatát használják. A DHT kialakításakor egyensúlyt kell teremteni a kézbesítési garancia és a késleltetés között. A SimpleX jobb kézbesítési garanciával és alacsonyabb késleltetéssel rendelkezik, mint a P2P, mivel az üzenet redundánsan, a címzett által kiválasztott kiszolgálók segítségével több kiszolgálón keresztül párhuzamosan továbbítható. A P2P-hálózatokban az üzenet O(log N) csomóponton halad át szekvenciálisan, az algoritmus által kiválasztott csomópontok segítségével.", - "simplex-network-overlay-card-1-li-2": "A SimpleX kialakítása a legtöbb P2P-hálózattól eltérően nem rendelkezik semmiféle globális felhasználói azonosítóval, még ideiglenesen sem, és csak ideiglenes páros azonosítókat használ, ami jobb névtelenséget és metaadatvédelmet biztosít.", + "simplex-network-overlay-card-1-li-2": "A SimpleX kialakítása a legtöbb P2P-hálózattól eltérően nem rendelkezik semmiféle globális felhasználói azonosítóval, még ideiglenessel sem, és csak az üzenetekhez használ ideiglenes, páros azonosítókat, ami jobb névtelenséget és metaadatvédelmet biztosít.", "simplex-network-overlay-card-1-li-3": "A P2P nem oldja meg a MITM-támadás problémát, és a legtöbb létező implementáció nem használ sávon kívüli üzeneteket a kezdeti kulcscseréhez. A SimpleX a kezdeti kulcscseréhez sávon kívüli üzeneteket, vagy bizonyos esetekben már meglévő biztonságos és megbízható kapcsolatokat használ.", - "simplex-network-overlay-card-1-li-5": "Minden ismert P2P-hálózat sebezhető Sybil támadással, mert minden egyes csomópont felderíthető, és a hálózat egészként működik. A támadások enyhítésére szolgáló ismert intézkedés lehet egy központi kiszolgáló (pl.: tracker), vagy egy drága tanúsítvány. A SimpleX-hálózat nem ismeri fel a kiszolgálókat, töredezett és több elszigetelt alhálózatként működik, ami lehetetlenné teszi az egész hálózatra kiterjedő támadásokat.", "simplex-network-overlay-card-1-li-6": "A P2P-hálózatok sebezhetőek lehetnek a DRDoS-támadással szemben, amikor a kliensek képesek a forgalmat újraközvetíteni és felerősíteni, ami az egész hálózatra kiterjedő szolgáltatásmegtagadást eredményez. A SimpleX-kliensek csak az ismert kapcsolatból származó forgalmat továbbítják, és a támadó nem használhatja őket arra, hogy az egész hálózatban felerősítse a forgalmat.", + "simplex-network-overlay-card-1-li-5": "Minden ismert P2P-hálózat sebezhető Sybil támadással, mert minden egyes csomópont felderíthető, és a hálózat egészként működik. A támadások enyhítésére szolgáló ismert intézkedés lehet egy központi kiszolgáló (pl.: tracker), vagy egy drága tanúsítvány. A SimpleX-hálózat nem ismeri fel a kiszolgálókat, töredezett és több elszigetelt alhálózatként működik, ami lehetetlenné teszi az egész hálózatra kiterjedő támadásokat.", "privacy-matters-overlay-card-1-p-1": "Sok nagyvállalat arra használja fel az önnel kapcsolatban álló személyek adatait, hogy megbecsülje az ön jövedelmét, hogy olyan termékeket adjon el önnek, amelyekre valójában nincs is szüksége, és hogy meghatározza az árakat.", "privacy-matters-overlay-card-1-p-2": "Az online kiskereskedők tudják, hogy az alacsonyabb jövedelműek nagyobb valószínűséggel vásárolnak azonnal, ezért magasabb árakat számíthatnak fel, vagy eltörölhetik a kedvezményeket.", "privacy-matters-overlay-card-1-p-3": "Egyes pénzügyi és biztosítótársaságok szociális grafikonokat használnak a kamatlábak és a díjak meghatározásához. Ez gyakran arra készteti az alacsonyabb jövedelmű embereket, hogy többet fizessenek — ez az úgynevezett „szegénységi prémium”.", @@ -223,8 +223,8 @@ "please-use-link-in-mobile-app": "Használja a mobilalkalmazásban található hivatkozást", "contact-hero-header": "Kapott egy címet a SimpleX Chat-en való kapcsolódáshoz", "invitation-hero-header": "Kapott egy egyszer használatos hivatkozást a SimpleX Chat-en való kapcsolódáshoz", - "simplex-network-overlay-card-1-li-4": "A P2P-megvalósításokat egyes internetszolgáltatók blokkolhatják (mint például a BitTorrent). A SimpleX átvitel-független - a szabványos webes protokollokon, pl. WebSockets-en keresztül is működik.", - "simplex-private-card-4-point-2": "A SimpleX Toron keresztüli használatához telepítse az Orbot alkalmazást és engedélyezze a SOCKS5 proxyt (vagy a VPN-t az iOS-ban).", + "simplex-network-overlay-card-1-li-4": "A P2P-megvalósításokat egyes internetszolgáltatók blokkolhatják (mint például a BitTorrent). A SimpleX átvitel-független - a szabványos webes protokollokon, pl. WebSocketsen keresztül is működik.", + "simplex-private-card-4-point-2": "A SimpleX TORon keresztüli használatához telepítse az Orbot alkalmazást és engedélyezze a SOCKS5 proxyt (vagy a VPN-t az iOS-ban).", "simplex-private-card-5-point-1": "A SimpleX minden titkosítási réteghez tartalomkitöltést használ, hogy meghiúsítsa az üzenetméret ellen irányuló támadásokat.", "simplex-private-card-5-point-2": "A kiszolgálók és a hálózatot megfigyelők számára a különböző méretű üzenetek egyformának tűnnek.", "privacy-matters-1-title": "Hirdetés és árdiszkrimináció", From c3991aad87526620d2b0c3fc33da906715a5f9f0 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 28 Nov 2024 00:12:53 +0000 Subject: [PATCH 129/567] website: translations corrections * Translated using Weblate (French) Currently translated at 98.8% (254 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/fr/ * Translated using Weblate (German) Currently translated at 98.8% (254 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/de/ * Translated using Weblate (Dutch) Currently translated at 98.8% (254 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/nl/ * Translated using Weblate (Czech) Currently translated at 98.8% (254 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/cs/ * Translated using Weblate (Arabic) Currently translated at 98.8% (254 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ar/ * Translated using Weblate (Italian) Currently translated at 98.8% (254 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/it/ * Translated using Weblate (Spanish) Currently translated at 98.8% (254 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/es/ * Translated using Weblate (Ukrainian) Currently translated at 98.8% (254 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/uk/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 98.8% (254 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 98.0% (252 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/pt_BR/ * Translated using Weblate (Polish) Currently translated at 98.8% (254 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/pl/ * Translated using Weblate (Japanese) Currently translated at 98.8% (254 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ja/ * Translated using Weblate (Russian) Currently translated at 99.2% (255 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ru/ * Translated using Weblate (Hebrew) Currently translated at 98.8% (254 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/he/ * Translated using Weblate (Finnish) Currently translated at 96.4% (248 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/fi/ * Translated using Weblate (Hungarian) Currently translated at 98.8% (254 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ --------- Co-authored-by: Anonymous --- website/langs/ar.json | 2 +- website/langs/cs.json | 2 +- website/langs/de.json | 2 +- website/langs/es.json | 2 +- website/langs/fi.json | 2 +- website/langs/fr.json | 2 +- website/langs/he.json | 2 +- website/langs/hu.json | 2 +- website/langs/it.json | 2 +- website/langs/ja.json | 2 +- website/langs/nl.json | 2 +- website/langs/pl.json | 2 +- website/langs/pt_BR.json | 2 +- website/langs/ru.json | 2 +- website/langs/uk.json | 2 +- website/langs/zh_Hans.json | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/website/langs/ar.json b/website/langs/ar.json index 5c3bb0dd48..927dcb0c49 100644 --- a/website/langs/ar.json +++ b/website/langs/ar.json @@ -255,4 +255,4 @@ "docs-dropdown-10": "الشفافية", "docs-dropdown-11": "الأسئلة الأكثر شيوعًا", "docs-dropdown-12": "الأمان" -} \ No newline at end of file +} diff --git a/website/langs/cs.json b/website/langs/cs.json index d10b14a85c..0e19fdbfe4 100644 --- a/website/langs/cs.json +++ b/website/langs/cs.json @@ -255,4 +255,4 @@ "docs-dropdown-10": "Transparentnost", "docs-dropdown-11": "FAQ (často kladené dotazy)", "docs-dropdown-12": "Bezpečnost" -} \ No newline at end of file +} diff --git a/website/langs/de.json b/website/langs/de.json index c57d059fb3..1a5c42d980 100644 --- a/website/langs/de.json +++ b/website/langs/de.json @@ -255,4 +255,4 @@ "docs-dropdown-10": "Transparent", "docs-dropdown-11": "FAQ", "docs-dropdown-12": "Sicherheit" -} \ No newline at end of file +} diff --git a/website/langs/es.json b/website/langs/es.json index 8f4ff0912e..b88a592ac4 100644 --- a/website/langs/es.json +++ b/website/langs/es.json @@ -255,4 +255,4 @@ "docs-dropdown-10": "Transparencia", "docs-dropdown-11": "FAQ", "docs-dropdown-12": "Seguridad" -} \ No newline at end of file +} diff --git a/website/langs/fi.json b/website/langs/fi.json index 13ef20bfa5..68c0d4f1b4 100644 --- a/website/langs/fi.json +++ b/website/langs/fi.json @@ -252,4 +252,4 @@ "hero-overlay-card-3-p-2": "Trail of Bits tarkasteli SimpleX-alustan salaus- ja verkkokomponentteja marraskuussa 2022. Lue lisää ilmoituksesta.", "please-enable-javascript": "Ota JavaScript käyttöön nähdäksesi QR-koodin.", "please-use-link-in-mobile-app": "Käytä mobiilisovelluksessa olevaa linkkiä" -} \ No newline at end of file +} diff --git a/website/langs/fr.json b/website/langs/fr.json index f907757388..61be2c8621 100644 --- a/website/langs/fr.json +++ b/website/langs/fr.json @@ -256,4 +256,4 @@ "docs-dropdown-10": "Transparence", "docs-dropdown-12": "Sécurité", "docs-dropdown-11": "FAQ" -} \ No newline at end of file +} diff --git a/website/langs/he.json b/website/langs/he.json index 9cf7ce194d..4fd966f05d 100644 --- a/website/langs/he.json +++ b/website/langs/he.json @@ -255,4 +255,4 @@ "docs-dropdown-10": "שקיפות", "docs-dropdown-11": "שאלות ותשובות", "docs-dropdown-12": "אבטחה" -} \ No newline at end of file +} diff --git a/website/langs/hu.json b/website/langs/hu.json index 7ff66a5ab9..f7cf1c558b 100644 --- a/website/langs/hu.json +++ b/website/langs/hu.json @@ -255,4 +255,4 @@ "simplex-chat-via-f-droid": "SimpleX Chat az F-Droidon keresztül", "simplex-chat-repo": "SimpleX Chat tároló", "stable-and-beta-versions-built-by-developers": "A fejlesztők által készített stabil és béta verziók" -} \ No newline at end of file +} diff --git a/website/langs/it.json b/website/langs/it.json index b593c395d8..431354c068 100644 --- a/website/langs/it.json +++ b/website/langs/it.json @@ -255,4 +255,4 @@ "docs-dropdown-10": "Trasparenza", "docs-dropdown-12": "Sicurezza", "docs-dropdown-11": "Domande frequenti" -} \ No newline at end of file +} diff --git a/website/langs/ja.json b/website/langs/ja.json index 4adf8705da..6f994b59da 100644 --- a/website/langs/ja.json +++ b/website/langs/ja.json @@ -255,4 +255,4 @@ "docs-dropdown-10": "透明性", "docs-dropdown-11": "よくある質問", "docs-dropdown-12": "セキュリティ" -} \ No newline at end of file +} diff --git a/website/langs/nl.json b/website/langs/nl.json index 02be8cbe41..18edf45369 100644 --- a/website/langs/nl.json +++ b/website/langs/nl.json @@ -255,4 +255,4 @@ "docs-dropdown-10": "Transparantie", "docs-dropdown-11": "FAQ", "docs-dropdown-12": "Beveiliging" -} \ No newline at end of file +} diff --git a/website/langs/pl.json b/website/langs/pl.json index c25ab4cd05..d0674e3d8a 100644 --- a/website/langs/pl.json +++ b/website/langs/pl.json @@ -255,4 +255,4 @@ "docs-dropdown-10": "Przezroczystość", "docs-dropdown-12": "Bezpieczeństwo", "docs-dropdown-11": "Często zadawane pytania" -} \ No newline at end of file +} diff --git a/website/langs/pt_BR.json b/website/langs/pt_BR.json index 12fd10d10c..73095c6db2 100644 --- a/website/langs/pt_BR.json +++ b/website/langs/pt_BR.json @@ -255,4 +255,4 @@ "docs-dropdown-11": "FAQ", "docs-dropdown-10": "Transparência", "docs-dropdown-12": "Segurança" -} \ No newline at end of file +} diff --git a/website/langs/ru.json b/website/langs/ru.json index 3c71bc24eb..5d59ec2c76 100644 --- a/website/langs/ru.json +++ b/website/langs/ru.json @@ -256,4 +256,4 @@ "docs-dropdown-10": "Прозрачность", "docs-dropdown-12": "Безопасность", "docs-dropdown-11": "Часто задаваемые вопросы" -} \ No newline at end of file +} diff --git a/website/langs/uk.json b/website/langs/uk.json index de18cbda85..5fa2f02b99 100644 --- a/website/langs/uk.json +++ b/website/langs/uk.json @@ -255,4 +255,4 @@ "docs-dropdown-11": "ПОШИРЕНІ ЗАПИТАННЯ", "docs-dropdown-10": "Прозорість", "docs-dropdown-12": "Безпека" -} \ No newline at end of file +} diff --git a/website/langs/zh_Hans.json b/website/langs/zh_Hans.json index 7e44bf75e2..87a8cc3c78 100644 --- a/website/langs/zh_Hans.json +++ b/website/langs/zh_Hans.json @@ -255,4 +255,4 @@ "docs-dropdown-10": "透明度", "docs-dropdown-11": "常问问题", "docs-dropdown-12": "安全性" -} \ No newline at end of file +} From 13efdf259501b9290c918bdc56c17b200cdcccd1 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Thu, 28 Nov 2024 11:24:29 +0400 Subject: [PATCH 130/567] core: apiGetReactionMembers api implementation (#5263) --- src/Simplex/Chat.hs | 17 ++++++++++------- src/Simplex/Chat/Store/Messages.hs | 16 ++++++++++++++++ src/Simplex/Chat/View.hs | 9 ++++++--- tests/ChatTests/Groups.hs | 3 +++ 4 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 707163fde7..66c11dac15 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -316,7 +316,7 @@ newChatController randomPresetServers <- chooseRandomServers presetServers' let rndSrvs = L.toList randomPresetServers operatorWithId (i, op) = (\o -> o {operatorId = DBEntityId i}) <$> pOperator op - opDomains = operatorDomains $ mapMaybe operatorWithId $ zip [1..] rndSrvs + opDomains = operatorDomains $ mapMaybe operatorWithId $ zip [1 ..] rndSrvs agentSMP <- randomServerCfgs "agent SMP servers" SPSMP opDomains rndSrvs agentXFTP <- randomServerCfgs "agent XFTP servers" SPXFTP opDomains rndSrvs let randomAgentServers = RandomAgentServers {smpServers = agentSMP, xftpServers = agentXFTP} @@ -1078,8 +1078,11 @@ processChatCommand' vr = \case throwChatError (CECommandError $ "reaction already " <> if add then "added" else "removed") when (add && length rs >= maxMsgReactions) $ throwChatError (CECommandError "too many reactions") - APIGetReactionMembers _userId _groupId _itemId _reaction -> withUser $ \user -> do - pure $ chatCmdError (Just user) "not supported" + APIGetReactionMembers userId groupId itemId reaction -> withUserId userId $ \user -> do + memberReactions <- withStore $ \db -> do + CChatItem _ ChatItem {meta = CIMeta {itemSharedMsgId = Just itemSharedMId}} <- getGroupChatItem db user groupId itemId + liftIO $ getReactionMembers db groupId itemSharedMId reaction + pure $ CRReactionMembers user memberReactions APIPlanForwardChatItems (ChatRef fromCType fromChatId) itemIds -> withUser $ \user -> case fromCType of CTDirect -> planForward user . snd =<< getCommandDirectChatItems user fromChatId itemIds CTGroup -> planForward user . snd =<< getCommandGroupChatItems user fromChatId itemIds @@ -1633,7 +1636,7 @@ processChatCommand' vr = \case liftIO $ fmap (opsConds,) . mapM (getServers db as ops' opDomains) =<< getUsers db lift $ withAgent' $ \a -> forM_ srvs $ \(auId, (smp', xftp')) -> do setProtocolServers a auId smp' - setProtocolServers a auId xftp' + setProtocolServers a auId xftp' pure $ CRServerOperatorConditions opsConds where getServers :: DB.Connection -> RandomAgentServers -> [Maybe ServerOperator] -> [(Text, ServerOperator)] -> User -> IO (UserId, (NonEmpty (ServerCfg 'PSMP), NonEmpty (ServerCfg 'PXFTP))) @@ -1942,7 +1945,7 @@ processChatCommand' vr = \case canKeepLink (CRInvitationUri crData _) newUser = do let ConnReqUriData {crSmpQueues = q :| _} = crData SMPQueueUri {queueAddress = SMPQueueAddress {smpServer}} = q - newUserServers <- + newUserServers <- map protoServer' . L.filter (\ServerCfg {enabled} -> enabled) <$> getKnownAgentServers SPSMP newUser pure $ smpServer `elem` newUserServers @@ -3430,7 +3433,7 @@ processChatCommand' vr = \case msgInfo <- withFastStore' (`getLastRcvMsgInfo` connId) CRQueueInfo user msgInfo <$> withAgent (`getConnectionQueueInfo` acId) -protocolServers :: UserProtocol p => SProtocolType p -> ([Maybe ServerOperator], [UserServer 'PSMP], [UserServer 'PXFTP]) -> ([Maybe ServerOperator], [UserServer 'PSMP], [UserServer 'PXFTP]) +protocolServers :: UserProtocol p => SProtocolType p -> ([Maybe ServerOperator], [UserServer 'PSMP], [UserServer 'PXFTP]) -> ([Maybe ServerOperator], [UserServer 'PSMP], [UserServer 'PXFTP]) protocolServers p (operators, smpServers, xftpServers) = case p of SPSMP -> (operators, smpServers, []) SPXFTP -> (operators, [], xftpServers) @@ -8269,7 +8272,7 @@ chatCommandP = "/_delete item " *> (APIDeleteChatItem <$> chatRefP <*> _strP <* A.space <*> ciDeleteMode), "/_delete member item #" *> (APIDeleteMemberChatItem <$> A.decimal <*> _strP), "/_reaction " *> (APIChatItemReaction <$> chatRefP <* A.space <*> A.decimal <* A.space <*> onOffP <* A.space <*> jsonP), - "/_reaction members " *> (APIGetReactionMembers <$> A.decimal <* A.space <*> A.decimal <* A.space <*> A.decimal <* A.space <*> jsonP), + "/_reaction members " *> (APIGetReactionMembers <$> A.decimal <* " #" <*> A.decimal <* A.space <*> A.decimal <* A.space <*> jsonP), "/_forward plan " *> (APIPlanForwardChatItems <$> chatRefP <*> _strP), "/_forward " *> (APIForwardChatItems <$> chatRefP <* A.space <*> chatRefP <*> _strP <*> sendMessageTTLP), "/_read user " *> (APIUserRead <$> A.decimal), diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index a79eb98f14..74951cf3d1 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -76,6 +76,7 @@ module Simplex.Chat.Store.Messages getGroupCIReactions, getGroupReactions, setGroupReaction, + getReactionMembers, getChatItemIdsByAgentMsgId, getDirectChatItem, getDirectCIWithReactions, @@ -2852,6 +2853,21 @@ setGroupReaction db GroupInfo {groupId} m itemMemberId itemSharedMId sent reacti |] (groupId, groupMemberId' m, itemSharedMId, itemMemberId, sent, reaction) +getReactionMembers :: DB.Connection -> GroupId -> SharedMsgId -> MsgReaction -> IO [MemberReaction] +getReactionMembers db groupId itemSharedMId reaction = + map toMemberReaction + <$> DB.query + db + [sql| + SELECT group_member_id, reaction_ts + FROM chat_item_reactions + WHERE group_id = ? AND shared_msg_id = ? AND reaction = ? + |] + (groupId, itemSharedMId, reaction) + where + toMemberReaction :: (GroupMemberId, UTCTime) -> MemberReaction + toMemberReaction (groupMemberId, reactionTs) = MemberReaction {groupMemberId, reactionTs} + getTimedItems :: DB.Connection -> User -> UTCTime -> IO [((ChatRef, ChatItemId), UTCTime)] getTimedItems db User {userId} startTimedThreadCutoff = mapMaybe toCIRefDeleteAt diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index e0c836d8d7..093d750a42 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -154,7 +154,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe ttyUser u $ unmuted u chat deletedItem $ viewItemDelete chat deletedItem toItem byUser timed ts tz testView deletions' -> ttyUser u [sShow (length deletions') <> " messages deleted"] CRChatItemReaction u added (ACIReaction _ _ chat reaction) -> ttyUser u $ unmutedReaction u chat reaction $ viewItemReaction showReactions chat reaction added ts tz - CRReactionMembers u memberReactions -> [] + CRReactionMembers u memberReactions -> ttyUser u $ viewReactionMembers memberReactions CRChatItemDeletedNotFound u Contact {localDisplayName = c} _ -> ttyUser u [ttyFrom $ c <> "> [deleted - original message not found]"] CRBroadcastSent u mc s f t -> ttyUser u $ viewSentBroadcast mc s f ts tz t CRMsgIntegrityError u mErr -> ttyUser u $ viewMsgIntegrityError mErr @@ -848,6 +848,9 @@ viewItemReactions ChatItem {reactions} = [" " <> viewReactions reactions | viewReaction CIReactionCount {reaction = MREmoji (MREmojiChar emoji), userReacted, totalReacted} = plain [emoji, ' '] <> (if userReacted then styled Italic else plain) (show totalReacted) +viewReactionMembers :: [MemberReaction] -> [StyledString] +viewReactionMembers memberReactions = [sShow (length memberReactions) <> " member(s) reacted"] + directQuote :: forall d'. MsgDirectionI d' => CIDirection 'CTDirect d' -> CIQuote 'CTDirect -> [StyledString] directQuote _ CIQuote {content = qmc, chatDir = quoteDir} = quoteText qmc $ if toMsgDirection (msgDirection @d') == quoteMsgDirection quoteDir then ">>" else ">" @@ -1227,7 +1230,7 @@ viewUserServers UserOperatorServers {operator, smpServers, xftpServers} = viewServers p srvs | maybe True (\ServerOperator {enabled} -> enabled) operator = [" " <> protocolName p <> " servers" <> maybe "" ((" " <>) . viewRoles) operator] - <> map (plain . (" " <> ) . viewServer) srvs + <> map (plain . (" " <>) . viewServer) srvs | otherwise = [] where viewServer UserServer {server, preset, tested, enabled} = safeDecodeUtf8 (strEncode server) <> serverInfo @@ -1280,7 +1283,7 @@ viewOperator op@ServerOperator {tradeName, legalName, serverDomains, conditionsA viewOpIdTag op <> tradeName <> maybe "" parens legalName - <> (", domains: " <> T.intercalate ", " serverDomains) + <> (", domains: " <> T.intercalate ", " serverDomains) <> (", servers: " <> viewOpEnabled op) <> (", conditions: " <> viewOpConditions conditionsAcceptance) diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index bdd3b53829..a1d9951088 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -3738,6 +3738,9 @@ testSetGroupMessageReactions = cath ##> "/tail #team 1" cath <# "#team alice> hi" cath <## " 👍 2 🚀 1" + itemId' <- lastItemId alice + alice ##> ("/_reaction members 1 #1 " <> itemId' <> " {\"type\": \"emoji\", \"emoji\": \"👍\"}") + alice <## "2 member(s) reacted" bob ##> "-1 #team hi" bob <## "removed 👍" alice <# "#team bob> > alice hi" From 68be4b4ba510038af5bc5875398758de325947da Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Thu, 28 Nov 2024 13:49:20 +0400 Subject: [PATCH 131/567] core: return member records from apiGetReactionMembers (#5270) --- src/Simplex/Chat.hs | 2 +- src/Simplex/Chat/Messages.hs | 2 +- src/Simplex/Chat/Store/Messages.hs | 15 +++++++++------ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 66c11dac15..2555582fe9 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1081,7 +1081,7 @@ processChatCommand' vr = \case APIGetReactionMembers userId groupId itemId reaction -> withUserId userId $ \user -> do memberReactions <- withStore $ \db -> do CChatItem _ ChatItem {meta = CIMeta {itemSharedMsgId = Just itemSharedMId}} <- getGroupChatItem db user groupId itemId - liftIO $ getReactionMembers db groupId itemSharedMId reaction + liftIO $ getReactionMembers db vr user groupId itemSharedMId reaction pure $ CRReactionMembers user memberReactions APIPlanForwardChatItems (ChatRef fromCType fromChatId) itemIds -> withUser $ \user -> case fromCType of CTDirect -> planForward user . snd =<< getCommandDirectChatItems user fromChatId itemIds diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index 274308176b..a477deeb2c 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -475,7 +475,7 @@ deriving instance Show ACIReaction data JSONCIReaction c d = JSONCIReaction {chatInfo :: ChatInfo c, chatReaction :: CIReaction c d} data MemberReaction = MemberReaction - { groupMemberId :: GroupMemberId, + { groupMember :: GroupMember, reactionTs :: UTCTime } deriving (Show) diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index 74951cf3d1..f94cbbd81d 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -2853,10 +2853,10 @@ setGroupReaction db GroupInfo {groupId} m itemMemberId itemSharedMId sent reacti |] (groupId, groupMemberId' m, itemSharedMId, itemMemberId, sent, reaction) -getReactionMembers :: DB.Connection -> GroupId -> SharedMsgId -> MsgReaction -> IO [MemberReaction] -getReactionMembers db groupId itemSharedMId reaction = - map toMemberReaction - <$> DB.query +getReactionMembers :: DB.Connection -> VersionRangeChat -> User -> GroupId -> SharedMsgId -> MsgReaction -> IO [MemberReaction] +getReactionMembers db vr user groupId itemSharedMId reaction = do + reactions <- + DB.query db [sql| SELECT group_member_id, reaction_ts @@ -2864,9 +2864,12 @@ getReactionMembers db groupId itemSharedMId reaction = WHERE group_id = ? AND shared_msg_id = ? AND reaction = ? |] (groupId, itemSharedMId, reaction) + rights <$> mapM (runExceptT . toMemberReaction) reactions where - toMemberReaction :: (GroupMemberId, UTCTime) -> MemberReaction - toMemberReaction (groupMemberId, reactionTs) = MemberReaction {groupMemberId, reactionTs} + toMemberReaction :: (GroupMemberId, UTCTime) -> ExceptT StoreError IO MemberReaction + toMemberReaction (groupMemberId, reactionTs) = do + groupMember <- getGroupMemberById db vr user groupMemberId + pure MemberReaction {groupMember, reactionTs} getTimedItems :: DB.Connection -> User -> UTCTime -> IO [((ChatRef, ChatItemId), UTCTime)] getTimedItems db User {userId} startTimedThreadCutoff = From 9c6e0a7051c4dc90503722ee14622edd7db17236 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Thu, 28 Nov 2024 17:37:52 +0400 Subject: [PATCH 132/567] desktop: fix avatar crop (#5271) * desktop: fix avatar crop wip * fix --- .../chat/simplex/common/platform/Images.desktop.kt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt index 0f53adaf0b..53f3301507 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt @@ -1,6 +1,8 @@ package chat.simplex.common.platform import androidx.compose.ui.graphics.* +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize import boofcv.io.image.ConvertBufferedImage import boofcv.struct.image.GrayU8 import chat.simplex.res.MR @@ -67,8 +69,16 @@ actual fun cropToSquare(image: ImageBitmap): ImageBitmap { } else { yOffset = (image.height - side) / 2 } - // LALAL MAKE REAL CROP - return image + val croppedImage = ImageBitmap(side, side) + val canvas = Canvas(croppedImage) + canvas.drawImageRect( + image, + srcOffset = IntOffset(xOffset, yOffset), + srcSize = IntSize(side, side), + dstSize = IntSize(side, side), + paint = Paint() + ) + return croppedImage } actual fun compressImageStr(bitmap: ImageBitmap): String { From 9915e5572e274afea0701a9cb352085391202909 Mon Sep 17 00:00:00 2001 From: Diogo Date: Fri, 29 Nov 2024 11:19:11 +0000 Subject: [PATCH 133/567] desktop, android: show group reactions (#5277) * desktop, android: show group reactions * handle long names in single line * swap ordering for composable item action * Revert "swap ordering for composable item action" This reverts commit 385825e7f26dd5562ee76f3e379994bc692d4359. --------- Co-authored-by: Evgeny Poberezkin --- .../chat/simplex/common/model/ChatModel.kt | 6 ++ .../chat/simplex/common/model/SimpleXAPI.kt | 14 ++++ .../simplex/common/views/chat/ChatView.kt | 2 +- .../common/views/chat/item/ChatItemView.kt | 81 ++++++++++++++++++- 4 files changed, 98 insertions(+), 5 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 56e1376ea2..857d21b966 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -1962,6 +1962,12 @@ class ACIReaction( val chatReaction: CIReaction ) +@Serializable +data class MemberReaction( + val groupMember: GroupMember, + val reactionTs: Instant +) + @Serializable class CIReaction( val chatDir: CIDirection, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index a18dd0ac14..def6d81897 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -951,6 +951,14 @@ object ChatController { return null } + suspend fun apiGetReactionMembers(rh: Long?, groupId: Long, itemId: Long, reaction: MsgReaction): List? { + val userId = currentUserId("apiGetReactionMembers") + val r = sendCmd(rh, CC.ApiGetReactionMembers(userId, groupId, itemId, reaction)) + if (r is CR.ReactionMembers) return r.memberReactions + Log.e(TAG, "apiGetReactionMembers bad response: ${r.responseType} ${r.details}") + return null + } + suspend fun apiDeleteChatItems(rh: Long?, type: ChatType, id: Long, itemIds: List, mode: CIDeleteMode): List? { val r = sendCmd(rh, CC.ApiDeleteChatItem(type, id, itemIds, mode)) if (r is CR.ChatItemsDeleted) return r.chatItemDeletions @@ -3092,6 +3100,7 @@ sealed class CC { class ApiDeleteChatItem(val type: ChatType, val id: Long, val itemIds: List, val mode: CIDeleteMode): CC() class ApiDeleteMemberChatItem(val groupId: Long, val itemIds: List): CC() class ApiChatItemReaction(val type: ChatType, val id: Long, val itemId: Long, val add: Boolean, val reaction: MsgReaction): CC() + class ApiGetReactionMembers(val userId: Long, val groupId: Long, val itemId: Long, val reaction: MsgReaction): CC() class ApiPlanForwardChatItems(val fromChatType: ChatType, val fromChatId: Long, val chatItemIds: List): CC() class ApiForwardChatItems(val toChatType: ChatType, val toChatId: Long, val fromChatType: ChatType, val fromChatId: Long, val itemIds: List, val ttl: Int?): CC() class ApiNewGroup(val userId: Long, val incognito: Boolean, val groupProfile: GroupProfile): CC() @@ -3253,6 +3262,7 @@ sealed class CC { is ApiDeleteChatItem -> "/_delete item ${chatRef(type, id)} ${itemIds.joinToString(",")} ${mode.deleteMode}" is ApiDeleteMemberChatItem -> "/_delete member item #$groupId ${itemIds.joinToString(",")}" is ApiChatItemReaction -> "/_reaction ${chatRef(type, id)} $itemId ${onOff(add)} ${json.encodeToString(reaction)}" + is ApiGetReactionMembers -> "/_reaction members $userId #$groupId $itemId ${json.encodeToString(reaction)}" is ApiForwardChatItems -> { val ttlStr = if (ttl != null) "$ttl" else "default" "/_forward ${chatRef(toChatType, toChatId)} ${chatRef(fromChatType, fromChatId)} ${itemIds.joinToString(",")} ttl=${ttlStr}" @@ -3409,6 +3419,7 @@ sealed class CC { is ApiDeleteChatItem -> "apiDeleteChatItem" is ApiDeleteMemberChatItem -> "apiDeleteMemberChatItem" is ApiChatItemReaction -> "apiChatItemReaction" + is ApiGetReactionMembers -> "apiGetReactionMembers" is ApiForwardChatItems -> "apiForwardChatItems" is ApiPlanForwardChatItems -> "apiPlanForwardChatItems" is ApiNewGroup -> "apiNewGroup" @@ -5429,6 +5440,7 @@ sealed class CR { @Serializable @SerialName("chatItemUpdated") class ChatItemUpdated(val user: UserRef, val chatItem: AChatItem): CR() @Serializable @SerialName("chatItemNotChanged") class ChatItemNotChanged(val user: UserRef, val chatItem: AChatItem): CR() @Serializable @SerialName("chatItemReaction") class ChatItemReaction(val user: UserRef, val added: Boolean, val reaction: ACIReaction): CR() + @Serializable @SerialName("reactionMembers") class ReactionMembers(val user: UserRef, val memberReactions: List): CR() @Serializable @SerialName("chatItemsDeleted") class ChatItemsDeleted(val user: UserRef, val chatItemDeletions: List, val byUser: Boolean): CR() @Serializable @SerialName("forwardPlan") class ForwardPlan(val user: UserRef, val itemsCount: Int, val chatItemIds: List, val forwardConfirmation: ForwardConfirmation? = null): CR() // group events @@ -5610,6 +5622,7 @@ sealed class CR { is ChatItemUpdated -> "chatItemUpdated" is ChatItemNotChanged -> "chatItemNotChanged" is ChatItemReaction -> "chatItemReaction" + is ReactionMembers -> "reactionMembers" is ChatItemsDeleted -> "chatItemsDeleted" is ForwardPlan -> "forwardPlan" is GroupCreated -> "groupCreated" @@ -5783,6 +5796,7 @@ sealed class CR { is ChatItemUpdated -> withUser(user, json.encodeToString(chatItem)) is ChatItemNotChanged -> withUser(user, json.encodeToString(chatItem)) is ChatItemReaction -> withUser(user, "added: $added\n${json.encodeToString(reaction)}") + is ReactionMembers -> withUser(user, "memberReactions: ${json.encodeToString(memberReactions)}") is ChatItemsDeleted -> withUser(user, "${chatItemDeletions.map { (deletedChatItem, toChatItem) -> "deletedChatItem: ${json.encodeToString(deletedChatItem)}\ntoChatItem: ${json.encodeToString(toChatItem)}" }} \nbyUser: $byUser") is ForwardPlan -> withUser(user, "itemsCount: $itemsCount\nchatItemIds: ${json.encodeToString(chatItemIds)}\nforwardConfirmation: ${json.encodeToString(forwardConfirmation)}") is GroupCreated -> withUser(user, json.encodeToString(groupInfo)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 2a66daf2db..5db413a17a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -1062,7 +1062,7 @@ fun BoxScope.ChatItemsList( highlightedItems.value = setOf() } } - ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, highlighted = highlighted, range = range, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, scrollToQuotedItemFromItem = scrollToQuotedItemFromItem, setReaction = setReaction, showItemDetails = showItemDetails, reveal = reveal, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp) + ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, highlighted = highlighted, range = range, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, scrollToQuotedItemFromItem = scrollToQuotedItemFromItem, setReaction = setReaction, showItemDetails = showItemDetails, reveal = reveal, showMemberInfo = showMemberInfo, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index bd79b78c45..82744fdc39 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.text.* import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* import chat.simplex.common.model.* import chat.simplex.common.model.ChatModel.controller @@ -28,6 +29,7 @@ import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chat.* import chat.simplex.common.views.helpers.* import chat.simplex.res.MR +import kotlinx.coroutines.launch import kotlinx.datetime.Clock import kotlin.math.* @@ -84,6 +86,7 @@ fun ChatItemView( setReaction: (ChatInfo, ChatItem, Boolean, MsgReaction) -> Unit, showItemDetails: (ChatInfo, ChatItem) -> Unit, reveal: (Boolean) -> Unit, + showMemberInfo: (GroupInfo, GroupMember) -> Unit, developerTools: Boolean, showViaProxy: Boolean, showTimestamp: Boolean, @@ -116,14 +119,54 @@ fun ChatItemView( fun ChatItemReactions() { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.chatItemOffset(cItem, itemSeparation.largeGap, inverted = true, revealed = true)) { cItem.reactions.forEach { r -> + val showReactionMenu = remember { mutableStateOf(false) } + val reactionMembers = remember { mutableStateOf(emptyList()) } + var modifier = Modifier.padding(horizontal = 5.dp, vertical = 2.dp).clip(RoundedCornerShape(8.dp)) - if (cInfo.featureEnabled(ChatFeature.Reactions) && (cItem.allowAddReaction || r.userReacted)) { - modifier = modifier.clickable { - setReaction(cInfo, cItem, !r.userReacted, r.reaction) - } + if (cInfo.featureEnabled(ChatFeature.Reactions)) { + modifier = modifier + .combinedClickable( + onClick = { + if (cItem.allowAddReaction || r.userReacted) { + setReaction(cInfo, cItem, !r.userReacted, r.reaction) + } + }, + onLongClick = { + if (cInfo is ChatInfo.Group) { + withBGApi { + try { + val members = controller.apiGetReactionMembers(rhId, cInfo.groupInfo.groupId, cItem.id, r.reaction) + if (members != null) { + showReactionMenu.value = true + reactionMembers.value = members + } + } catch (e: Exception) { + Log.d(TAG, "hatItemView ChatItemReactions onLongClick: unexpected exception: ${e.stackTraceToString()}") + } + } + } + } + ) } Row(modifier.padding(2.dp), verticalAlignment = Alignment.CenterVertically) { ReactionIcon(r.reaction.text, fontSize = 12.sp) + DefaultDropdownMenu(showMenu = showReactionMenu) { + reactionMembers.value.forEach { m -> + ItemAction( + text = m.groupMember.displayName, + composable = { ProfileImage(44.dp, m.groupMember.image) }, + onClick = { + if (cInfo is ChatInfo.Group && cInfo.groupInfo.membership.groupMemberId != m.groupMember.groupMemberId) { + showMemberInfo(cInfo.groupInfo, m.groupMember) + showReactionMenu.value = false + } else { + showReactionMenu.value = false + } + }, + lineLimit = 1 + ) + } + } if (r.totalReacted > 1) { Spacer(Modifier.width(4.dp)) Text( @@ -781,6 +824,34 @@ fun ItemAction(text: String, icon: Painter, color: Color = Color.Unspecified, on } } +@Composable +fun ItemAction( + text: String, + composable: @Composable () -> Unit, + color: Color = Color.Unspecified, + onClick: () -> Unit, + lineLimit: Int = Int.MAX_VALUE +) { + val finalColor = if (color == Color.Unspecified) { + MenuTextColor + } else color + DropdownMenuItem(onClick, contentPadding = PaddingValues(horizontal = DEFAULT_PADDING * 1.5f)) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text, + modifier = Modifier + .fillMaxWidth() + .weight(1F) + .padding(end = 15.dp), + color = finalColor, + maxLines = lineLimit, + overflow = TextOverflow.Ellipsis + ) + composable() + } + } +} + @Composable fun ItemAction(text: String, icon: ImageVector, onClick: () -> Unit, color: Color = Color.Unspecified) { val finalColor = if (color == Color.Unspecified) { @@ -1101,6 +1172,7 @@ fun PreviewChatItemView( setReaction = { _, _, _, _ -> }, showItemDetails = { _, _ -> }, reveal = {}, + showMemberInfo = { _, _ ->}, developerTools = false, showViaProxy = false, showTimestamp = true, @@ -1145,6 +1217,7 @@ fun PreviewChatItemViewDeletedContent() { setReaction = { _, _, _, _ -> }, showItemDetails = { _, _ -> }, reveal = {}, + showMemberInfo = { _, _ ->}, developerTools = false, showViaProxy = false, preview = true, From b0f3f0a523b2d8b42afcc444f2f95ac1735ea148 Mon Sep 17 00:00:00 2001 From: Diogo Date: Fri, 29 Nov 2024 16:04:29 +0000 Subject: [PATCH 134/567] ios: fix alignment on operators review later button and notice (#5280) --- apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift index 910f2a4127..fb3db2b585 100644 --- a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift +++ b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift @@ -140,6 +140,7 @@ struct ChooseServerOperators: View { .font(.footnote) .padding(.horizontal, 32) } + .frame(maxWidth: .infinity) .disabled(!canReviewLater) .padding(.bottom) } From 03bc4e5d01ad92ca8eeb82351d15a444401fcd66 Mon Sep 17 00:00:00 2001 From: Diogo Date: Sat, 30 Nov 2024 12:23:51 +0000 Subject: [PATCH 135/567] ios: display reactions in groups by member (#5265) * ios: display reactions in groups by member * fetch data * load on open * wip * fix text * less api calls * matching image sizes * updates * progress dump * mostly works * add member to list needed * open member faster --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Model/SimpleXAPI.swift | 7 ++ apps/ios/Shared/Views/Chat/ChatView.swift | 129 ++++++++++++++++++++-- apps/ios/SimpleXChat/APITypes.swift | 6 + apps/ios/SimpleXChat/ChatTypes.swift | 5 + apps/ios/SimpleXChat/ImageUtils.swift | 2 +- 5 files changed, 138 insertions(+), 11 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index c03483311d..d99e97f2e1 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -446,6 +446,13 @@ func apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, re throw r } +func apiGetReactionMembers(groupId: Int64, itemId: Int64, reaction: MsgReaction) async throws -> [MemberReaction] { + let userId = try currentUserId("apiGetReactionMemebers") + let r = await chatSendCmd(.apiGetReactionMembers(userId: userId, groupId: groupId, itemId: itemId, reaction: reaction )) + if case let .reactionMembers(_, memberReactions) = r { return memberReactions } + throw r +} + func apiDeleteChatItems(type: ChatType, id: Int64, itemIds: [Int64], mode: CIDeleteMode) async throws -> [ChatItemDeletion] { let r = await chatSendCmd(.apiDeleteChatItem(type: type, id: id, itemIds: itemIds, mode: mode), bgDelay: msgDelay) if case let .chatItemsDeleted(_, items, _) = r { return items } diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 6b287d52a1..cfbbfe6080 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -901,7 +901,7 @@ struct ChatView: View { @State private var showChatItemInfoSheet: Bool = false @State private var chatItemInfo: ChatItemInfo? @State private var msgWidth: CGFloat = 0 - + @Binding var selectedChatItems: Set? @Binding var forwardedChatItems: [ChatItem] @@ -1117,14 +1117,12 @@ struct ChatView: View { HStack(alignment: .top, spacing: 10) { MemberProfileImage(member, size: memberImageSize, backgroundColor: theme.colors.background) .onTapGesture { - if let member = m.getGroupMember(member.groupMemberId) { - selectedMember = member + if let mem = m.getGroupMember(member.groupMemberId) { + selectedMember = mem } else { - Task { - await m.loadGroupMembers(groupInfo) { - selectedMember = m.getGroupMember(member.groupMemberId) - } - } + let mem = GMember.init(member) + m.groupMembers.append(mem) + selectedMember = mem } } chatItemWithMenu(ci, range, maxWidth, itemSeparation) @@ -1244,11 +1242,20 @@ struct ChatView: View { } .padding(.horizontal, 6) .padding(.vertical, 4) - - if chat.chatInfo.featureEnabled(.reactions) && (ci.allowAddReaction || r.userReacted) { + .if(chat.chatInfo.featureEnabled(.reactions) && (ci.allowAddReaction || r.userReacted)) { v in v.onTapGesture { setReaction(ci, add: !r.userReacted, reaction: r.reaction) } + } + if case let .group(groupInfo) = chat.chatInfo { + v.contextMenu { + ReactionContextMenu( + groupInfo: groupInfo, + itemId: ci.id, + reactionCount: r, + selectedMember: $selectedMember + ) + } } else { v } @@ -1838,6 +1845,108 @@ private func buildTheme() -> AppTheme { } } +struct ReactionContextMenu: View { + @EnvironmentObject var m: ChatModel + let groupInfo: GroupInfo + var itemId: Int64 + var reactionCount: CIReactionCount + @Binding var selectedMember: GMember? + @State private var memberReactions: [MemberReaction] = [] + @AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var radius = defaultProfileImageCorner + + var body: some View { + groupMemberReactionList() + .task { + logger.debug("ReactionContextMenu task \(radius)") + await loadChatItemReaction() + } + } + + @ViewBuilder private func groupMemberReactionList() -> some View { + if memberReactions.isEmpty { + ForEach(Array(repeating: 0, count: reactionCount.totalReacted), id: \.self) { _ in + Text(verbatim: " ") + } + } else { + ForEach(memberReactions, id: \.groupMember.groupMemberId) { mr in + let mem = mr.groupMember + let userMember = mem.groupMemberId == groupInfo.membership.groupMemberId + Button { + if let member = m.getGroupMember(mem.groupMemberId) { + selectedMember = member + } else { + let member = GMember.init(mem) + m.groupMembers.append(member) + selectedMember = member + } + } label: { + HStack { + Text(mem.displayName) + if let img = cropImage(mem.image) { + Image(uiImage: img) + } else { + Image(systemName: "person.crop.circle") + } + } + } + .disabled(userMember) + } + } + } + + private func cropImage(_ img: String?) -> UIImage? { + return if let originalImage = imageFromBase64(img) { + maskToCustomShape(originalImage, size: 30, radius: radius) + } else { + nil + } + } + + private func loadChatItemReaction() async { + do { + let memberReactions = try await apiGetReactionMembers( + groupId: groupInfo.groupId, + itemId: itemId, + reaction: reactionCount.reaction + ) + await MainActor.run { + self.memberReactions = memberReactions + } + } catch let error { + logger.error("apiGetReactionMembers error: \(responseError(error))") + } + } +} + +func maskToCustomShape(_ image: UIImage, size: CGFloat, radius: CGFloat) -> UIImage { + let path = Path { path in + if radius >= 50 { + path.addEllipse(in: CGRect(x: 0, y: 0, width: size, height: size)) + } else if radius <= 0 { + path.addRect(CGRect(x: 0, y: 0, width: size, height: size)) + } else { + let cornerRadius = size * CGFloat(radius) / 100 + path.addRoundedRect( + in: CGRect(x: 0, y: 0, width: size, height: size), + cornerSize: CGSize(width: cornerRadius, height: cornerRadius), + style: .continuous + ) + } + } + + return UIGraphicsImageRenderer(size: CGSize(width: size, height: size)).image { context in + context.cgContext.addPath(path.cgPath) + context.cgContext.clip() + let scale = size / max(image.size.width, image.size.height) + let imageSize = CGSize(width: image.size.width * scale, height: image.size.height * scale) + let imageOrigin = CGPoint( + x: (size - imageSize.width) / 2, + y: (size - imageSize.height) / 2 + ) + image.draw(in: CGRect(origin: imageOrigin, size: imageSize)) + } +} + struct ToggleNtfsButton: View { @ObservedObject var chat: Chat diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 1df6d07813..83c74178ba 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -49,6 +49,7 @@ public enum ChatCommand { case apiDeleteChatItem(type: ChatType, id: Int64, itemIds: [Int64], mode: CIDeleteMode) case apiDeleteMemberChatItem(groupId: Int64, itemIds: [Int64]) case apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, reaction: MsgReaction) + case apiGetReactionMembers(userId: Int64, groupId: Int64, itemId: Int64, reaction: MsgReaction) case apiPlanForwardChatItems(toChatType: ChatType, toChatId: Int64, itemIds: [Int64]) case apiForwardChatItems(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemIds: [Int64], ttl: Int?) case apiGetNtfToken @@ -212,6 +213,7 @@ public enum ChatCommand { case let .apiDeleteChatItem(type, id, itemIds, mode): return "/_delete item \(ref(type, id)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) \(mode.rawValue)" case let .apiDeleteMemberChatItem(groupId, itemIds): return "/_delete member item #\(groupId) \(itemIds.map({ "\($0)" }).joined(separator: ","))" case let .apiChatItemReaction(type, id, itemId, add, reaction): return "/_reaction \(ref(type, id)) \(itemId) \(onOff(add)) \(encodeJSON(reaction))" + case let .apiGetReactionMembers(userId, groupId, itemId, reaction): return "/_reaction members \(userId) #\(groupId) \(itemId) \(encodeJSON(reaction))" case let .apiPlanForwardChatItems(type, id, itemIds): return "/_forward plan \(ref(type, id)) \(itemIds.map({ "\($0)" }).joined(separator: ","))" case let .apiForwardChatItems(toChatType, toChatId, fromChatType, fromChatId, itemIds, ttl): let ttlStr = ttl != nil ? "\(ttl!)" : "default" @@ -375,6 +377,7 @@ public enum ChatCommand { case .apiConnectContactViaAddress: return "apiConnectContactViaAddress" case .apiDeleteMemberChatItem: return "apiDeleteMemberChatItem" case .apiChatItemReaction: return "apiChatItemReaction" + case .apiGetReactionMembers: return "apiGetReactionMembers" case .apiPlanForwardChatItems: return "apiPlanForwardChatItems" case .apiForwardChatItems: return "apiForwardChatItems" case .apiGetNtfToken: return "apiGetNtfToken" @@ -629,6 +632,7 @@ public enum ChatResponse: Decodable, Error { case chatItemUpdated(user: UserRef, chatItem: AChatItem) case chatItemNotChanged(user: UserRef, chatItem: AChatItem) case chatItemReaction(user: UserRef, added: Bool, reaction: ACIReaction) + case reactionMembers(user: UserRef, memberReactions: [MemberReaction]) case chatItemsDeleted(user: UserRef, chatItemDeletions: [ChatItemDeletion], byUser: Bool) case contactsList(user: UserRef, contacts: [Contact]) // group events @@ -805,6 +809,7 @@ public enum ChatResponse: Decodable, Error { case .chatItemUpdated: return "chatItemUpdated" case .chatItemNotChanged: return "chatItemNotChanged" case .chatItemReaction: return "chatItemReaction" + case .reactionMembers: return "reactionMembers" case .chatItemsDeleted: return "chatItemsDeleted" case .contactsList: return "contactsList" case .groupCreated: return "groupCreated" @@ -983,6 +988,7 @@ public enum ChatResponse: Decodable, Error { case let .chatItemUpdated(u, chatItem): return withUser(u, String(describing: chatItem)) case let .chatItemNotChanged(u, chatItem): return withUser(u, String(describing: chatItem)) case let .chatItemReaction(u, added, reaction): return withUser(u, "added: \(added)\n\(String(describing: reaction))") + case let .reactionMembers(u, reaction): return withUser(u, "memberReactions: \(String(describing: reaction))") case let .chatItemsDeleted(u, items, byUser): let itemsString = items.map { item in "deletedChatItem:\n\(String(describing: item.deletedChatItem))\ntoChatItem:\n\(String(describing: item.toChatItem))" }.joined(separator: "\n") diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 1bd5673f01..de671ee203 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2311,6 +2311,11 @@ public struct ACIReaction: Decodable, Hashable { public var chatReaction: CIReaction } +public struct MemberReaction: Decodable, Hashable { + public var groupMember: GroupMember + public var reactionTs: Date +} + public struct CIReaction: Decodable, Hashable { public var chatDir: CIDirection public var chatItem: ChatItem diff --git a/apps/ios/SimpleXChat/ImageUtils.swift b/apps/ios/SimpleXChat/ImageUtils.swift index 9702408c27..89cc45c4f5 100644 --- a/apps/ios/SimpleXChat/ImageUtils.swift +++ b/apps/ios/SimpleXChat/ImageUtils.swift @@ -138,7 +138,7 @@ private func reduceSize(_ image: UIImage, ratio: CGFloat, hasAlpha: Bool) -> UII return resizeImage(image, newBounds: bounds, drawIn: bounds, hasAlpha: hasAlpha) } -private func resizeImage(_ image: UIImage, newBounds: CGRect, drawIn: CGRect, hasAlpha: Bool) -> UIImage { +public func resizeImage(_ image: UIImage, newBounds: CGRect, drawIn: CGRect, hasAlpha: Bool) -> UIImage { let format = UIGraphicsImageRendererFormat() format.scale = 1.0 format.opaque = !hasAlpha From 961bdbfc59ed9034fbbba8989bcfe7d17e87689d Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Sat, 30 Nov 2024 23:29:27 +0700 Subject: [PATCH 136/567] ios: start/stop chat toggle refactoring (#5275) * ios: start/stop chat toggle refactoring * changes * changes * return back * reduce diff * better * update button * ios: do not start chat after export, always show run toggle (#5284) --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/AppDelegate.swift | 1 + apps/ios/Shared/Model/SimpleXAPI.swift | 9 + .../Views/Database/ChatArchiveView.swift | 68 ---- .../Database/DatabaseEncryptionView.swift | 87 +++-- .../Shared/Views/Database/DatabaseView.swift | 356 +++++++++++------- .../Database/MigrateToAppGroupView.swift | 8 +- .../Views/Migration/MigrateFromDevice.swift | 5 +- .../Views/Migration/MigrateToDevice.swift | 37 ++ .../Views/UserSettings/SettingsView.swift | 3 + apps/ios/SimpleX.xcodeproj/project.pbxproj | 4 - 10 files changed, 340 insertions(+), 238 deletions(-) delete mode 100644 apps/ios/Shared/Views/Database/ChatArchiveView.swift diff --git a/apps/ios/Shared/AppDelegate.swift b/apps/ios/Shared/AppDelegate.swift index 5845793aa7..ad8c661e1c 100644 --- a/apps/ios/Shared/AppDelegate.swift +++ b/apps/ios/Shared/AppDelegate.swift @@ -17,6 +17,7 @@ class AppDelegate: NSObject, UIApplicationDelegate { application.registerForRemoteNotifications() removePasscodesIfReinstalled() prepareForLaunch() + deleteOldChatArchive() return true } diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index d99e97f2e1..459ece32da 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1600,6 +1600,15 @@ func initializeChat(start: Bool, confirmStart: Bool = false, dbKey: String? = ni m.chatInitialized = true m.currentUser = try apiGetActiveUser() m.conditions = try getServerOperators() + if shouldImportAppSettingsDefault.get() { + do { + let appSettings = try apiGetAppSettings(settings: AppSettings.current.prepareForExport()) + appSettings.importIntoApp() + shouldImportAppSettingsDefault.set(false) + } catch { + logger.error("Error while importing app settings: \(error)") + } + } if m.currentUser == nil { onboardingStageDefault.set(.step1_SimpleXInfo) privacyDeliveryReceiptsSet.set(true) diff --git a/apps/ios/Shared/Views/Database/ChatArchiveView.swift b/apps/ios/Shared/Views/Database/ChatArchiveView.swift deleted file mode 100644 index 3ab4ac9a31..0000000000 --- a/apps/ios/Shared/Views/Database/ChatArchiveView.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// ChatArchiveView.swift -// SimpleXChat -// -// Created by Evgeny on 23/06/2022. -// Copyright © 2022 SimpleX Chat. All rights reserved. -// - -import SwiftUI -import SimpleXChat - -struct ChatArchiveView: View { - @EnvironmentObject var theme: AppTheme - var archiveName: String - @AppStorage(DEFAULT_CHAT_ARCHIVE_NAME) private var chatArchiveName: String? - @AppStorage(DEFAULT_CHAT_ARCHIVE_TIME) private var chatArchiveTime: Double = 0 - @State private var showDeleteAlert = false - - var body: some View { - let fileUrl = getDocumentsDirectory().appendingPathComponent(archiveName) - let fileTs = chatArchiveTimeDefault.get() - List { - Section { - settingsRow("square.and.arrow.up", color: theme.colors.secondary) { - Button { - showShareSheet(items: [fileUrl]) - } label: { - Text("Save archive") - } - } - settingsRow("trash", color: theme.colors.secondary) { - Button { - showDeleteAlert = true - } label: { - Text("Delete archive").foregroundColor(.red) - } - } - } header: { - Text("Chat archive") - .foregroundColor(theme.colors.secondary) - } footer: { - Text("Created on \(fileTs)") - .foregroundColor(theme.colors.secondary) - } - } - .alert(isPresented: $showDeleteAlert) { - Alert( - title: Text("Delete chat archive?"), - primaryButton: .destructive(Text("Delete")) { - do { - try FileManager.default.removeItem(atPath: fileUrl.path) - chatArchiveName = nil - chatArchiveTime = 0 - } catch let error { - logger.error("removeItem error \(String(describing: error))") - } - }, - secondaryButton: .cancel() - ) - } - } -} - -struct ChatArchiveView_Previews: PreviewProvider { - static var previews: some View { - ChatArchiveView(archiveName: "") - } -} diff --git a/apps/ios/Shared/Views/Database/DatabaseEncryptionView.swift b/apps/ios/Shared/Views/Database/DatabaseEncryptionView.swift index be167b92b9..3cd37e4930 100644 --- a/apps/ios/Shared/Views/Database/DatabaseEncryptionView.swift +++ b/apps/ios/Shared/Views/Database/DatabaseEncryptionView.swift @@ -48,6 +48,8 @@ struct DatabaseEncryptionView: View { @State private var confirmNewKey = "" @State private var currentKeyShown = false + let stopChatRunBlockStartChat: (Binding, @escaping () async throws -> Bool) -> Void + var body: some View { ZStack { List { @@ -134,46 +136,61 @@ struct DatabaseEncryptionView: View { .onAppear { if initialRandomDBPassphrase { currentKey = kcDatabasePassword.get() ?? "" } } - .disabled(m.chatRunning != false) + .disabled(progressIndicator) .alert(item: $alert) { item in databaseEncryptionAlert(item) } } - private func encryptDatabase() { - progressIndicator = true - Task { - do { - encryptionStartedDefault.set(true) - encryptionStartedAtDefault.set(Date.now) - if !m.chatDbChanged { - try apiSaveAppSettings(settings: AppSettings.current.prepareForExport()) - } - try await apiStorageEncryption(currentKey: currentKey, newKey: newKey) - encryptionStartedDefault.set(false) - initialRandomDBPassphraseGroupDefault.set(false) - if migration { - storeDBPassphraseGroupDefault.set(useKeychain) - } - if useKeychain { - if kcDatabasePassword.set(newKey) { - await resetFormAfterEncryption(true) - await operationEnded(.databaseEncrypted) - } else { - await resetFormAfterEncryption() - await operationEnded(.error(title: "Keychain error", error: "Error saving passphrase to keychain")) - } - } else { - if migration { - removePassphraseFromKeyChain() - } - await resetFormAfterEncryption() + private func encryptDatabaseAsync() async -> Bool { + await MainActor.run { + progressIndicator = true + } + do { + encryptionStartedDefault.set(true) + encryptionStartedAtDefault.set(Date.now) + if !m.chatDbChanged { + try apiSaveAppSettings(settings: AppSettings.current.prepareForExport()) + } + try await apiStorageEncryption(currentKey: currentKey, newKey: newKey) + encryptionStartedDefault.set(false) + initialRandomDBPassphraseGroupDefault.set(false) + if migration { + storeDBPassphraseGroupDefault.set(useKeychain) + } + if useKeychain { + if kcDatabasePassword.set(newKey) { + await resetFormAfterEncryption(true) await operationEnded(.databaseEncrypted) - } - } catch let error { - if case .chatCmdError(_, .errorDatabase(.errorExport(.errorNotADatabase))) = error as? ChatResponse { - await operationEnded(.currentPassphraseError) } else { - await operationEnded(.error(title: "Error encrypting database", error: "\(responseError(error))")) + await resetFormAfterEncryption() + await operationEnded(.error(title: "Keychain error", error: "Error saving passphrase to keychain")) } + } else { + if migration { + removePassphraseFromKeyChain() + } + await resetFormAfterEncryption() + await operationEnded(.databaseEncrypted) + } + return true + } catch let error { + if case .chatCmdError(_, .errorDatabase(.errorExport(.errorNotADatabase))) = error as? ChatResponse { + await operationEnded(.currentPassphraseError) + } else { + await operationEnded(.error(title: "Error encrypting database", error: "\(responseError(error))")) + } + return false + } + } + + private func encryptDatabase() { + // it will try to stop and start the chat in case of: non-migration && successful encryption. In migration the chat will remain stopped + if migration { + Task { + await encryptDatabaseAsync() + } + } else { + stopChatRunBlockStartChat($progressIndicator) { + return await encryptDatabaseAsync() } } } @@ -371,6 +388,6 @@ func validKey(_ s: String) -> Bool { struct DatabaseEncryptionView_Previews: PreviewProvider { static var previews: some View { - DatabaseEncryptionView(useKeychain: Binding.constant(true), migration: false) + DatabaseEncryptionView(useKeychain: Binding.constant(true), migration: false, stopChatRunBlockStartChat: { _, _ in true }) } } diff --git a/apps/ios/Shared/Views/Database/DatabaseView.swift b/apps/ios/Shared/Views/Database/DatabaseView.swift index 804f2307ef..4a367f7722 100644 --- a/apps/ios/Shared/Views/Database/DatabaseView.swift +++ b/apps/ios/Shared/Views/Database/DatabaseView.swift @@ -46,6 +46,7 @@ struct DatabaseView: View { @EnvironmentObject var theme: AppTheme let dismissSettingsSheet: DismissAction @State private var runChat = false + @State private var stoppingChat = false @State private var alert: DatabaseAlert? = nil @State private var showFileImporter = false @State private var importedArchivePath: URL? @@ -57,6 +58,8 @@ struct DatabaseView: View { @State private var useKeychain = storeDBPassphraseGroupDefault.get() @State private var appFilesCountAndSize: (Int, Int)? + @State private var showDatabaseEncryptionView = false + @State var chatItemTTL: ChatItemTTL @State private var currentChatItemTTL: ChatItemTTL = .none @@ -69,7 +72,20 @@ struct DatabaseView: View { } } + @ViewBuilder private func chatDatabaseView() -> some View { + NavigationLink(isActive: $showDatabaseEncryptionView) { + DatabaseEncryptionView(useKeychain: $useKeychain, migration: false, stopChatRunBlockStartChat: { progressIndicator, block in + stopChatRunBlockStartChat(false, progressIndicator, block) + }) + .navigationTitle("Database passphrase") + .modifier(ThemedBackground(grouped: true)) + } label: { + EmptyView() + } + .frame(width: 1, height: 1) + .hidden() + List { let stopped = m.chatRunning == false Section { @@ -101,9 +117,10 @@ struct DatabaseView: View { isOn: $runChat ) .onChange(of: runChat) { _ in - if (runChat) { - startChat() - } else { + if runChat { + DatabaseView.startChat($runChat, $progressIndicator) + } else if !stoppingChat { + stoppingChat = false alert = .stopChat } } @@ -123,7 +140,9 @@ struct DatabaseView: View { let color: Color = unencrypted ? .orange : theme.colors.secondary settingsRow(unencrypted ? "lock.open" : useKeychain ? "key" : "lock", color: color) { NavigationLink { - DatabaseEncryptionView(useKeychain: $useKeychain, migration: false) + DatabaseEncryptionView(useKeychain: $useKeychain, migration: false, stopChatRunBlockStartChat: { progressIndicator, block in + stopChatRunBlockStartChat(false, progressIndicator, block) + }) .navigationTitle("Database passphrase") .modifier(ThemedBackground(grouped: true)) } label: { @@ -133,9 +152,14 @@ struct DatabaseView: View { settingsRow("square.and.arrow.up", color: theme.colors.secondary) { Button("Export database") { if initialRandomDBPassphraseGroupDefault.get() && !unencrypted { - alert = .exportProhibited + showDatabaseEncryptionView = true + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + alert = .exportProhibited + } } else { - exportArchive() + stopChatRunBlockStartChat(stopped, $progressIndicator) { + await exportArchive() + } } } } @@ -144,20 +168,6 @@ struct DatabaseView: View { showFileImporter = true } } - if let archiveName = chatArchiveName { - let title: LocalizedStringKey = chatArchiveTimeDefault.get() < chatLastStartGroupDefault.get() - ? "Old database archive" - : "New database archive" - settingsRow("archivebox", color: theme.colors.secondary) { - NavigationLink { - ChatArchiveView(archiveName: archiveName) - .navigationTitle(title) - .modifier(ThemedBackground(grouped: true)) - } label: { - Text(title) - } - } - } settingsRow("trash.slash", color: theme.colors.secondary) { Button("Delete database", role: .destructive) { alert = .deleteChat @@ -167,14 +177,10 @@ struct DatabaseView: View { Text("Chat database") .foregroundColor(theme.colors.secondary) } footer: { - Text( - stopped - ? "You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts." - : "Stop chat to enable database actions" - ) + Text("You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts.") .foregroundColor(theme.colors.secondary) } - .disabled(!stopped) + .disabled(progressIndicator) if case .group = dbContainer, legacyDatabase { Section(header: Text("Old database").foregroundColor(theme.colors.secondary)) { @@ -190,7 +196,7 @@ struct DatabaseView: View { Button(m.users.count > 1 ? "Delete files for all chat profiles" : "Delete all files", role: .destructive) { alert = .deleteFilesAndMedia } - .disabled(!stopped || appFilesCountAndSize?.0 == 0) + .disabled(progressIndicator || appFilesCountAndSize?.0 == 0) } header: { Text("Files & media") .foregroundColor(theme.colors.secondary) @@ -255,7 +261,10 @@ struct DatabaseView: View { title: Text("Import chat database?"), message: Text("Your current chat database will be DELETED and REPLACED with the imported one.") + Text("This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost."), primaryButton: .destructive(Text("Import")) { - importArchive(fileURL) + stopChatRunBlockStartChat(m.chatRunning == false, $progressIndicator) { + _ = await DatabaseView.importArchive(fileURL, $progressIndicator, $alert) + return true + } }, secondaryButton: .cancel() ) @@ -263,19 +272,15 @@ struct DatabaseView: View { return Alert(title: Text("Error: no database file")) } case .archiveImported: - return Alert( - title: Text("Chat database imported"), - message: Text("Restart the app to use imported chat database") - ) + let (title, message) = archiveImportedAlertText() + return Alert(title: Text(title), message: Text(message)) case let .archiveImportedWithErrors(errs): - return Alert( - title: Text("Chat database imported"), - message: Text("Restart the app to use imported chat database") + Text(verbatim: "\n") + Text("Some non-fatal errors occurred during import:") + archiveErrorsText(errs) - ) + let (title, message) = archiveImportedWithErrorsAlertText(errs: errs) + return Alert(title: Text(title), message: Text(message)) case let .archiveExportedWithErrors(archivePath, errs): return Alert( title: Text("Chat database exported"), - message: Text("You may save the exported archive.") + Text(verbatim: "\n") + Text("Some file(s) were not exported:") + archiveErrorsText(errs), + message: Text("You may save the exported archive.") + Text(verbatim: "\n") + Text("Some file(s) were not exported:") + Text(archiveErrorsText(errs)), dismissButton: .default(Text("Continue")) { showShareSheet(items: [archivePath]) } @@ -285,15 +290,17 @@ struct DatabaseView: View { title: Text("Delete chat profile?"), message: Text("This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost."), primaryButton: .destructive(Text("Delete")) { - deleteChat() + let wasStopped = m.chatRunning == false + stopChatRunBlockStartChat(wasStopped, $progressIndicator) { + _ = await deleteChat() + return true + } }, secondaryButton: .cancel() ) case .chatDeleted: - return Alert( - title: Text("Chat database deleted"), - message: Text("Restart the app to create a new chat profile") - ) + let (title, message) = chatDeletedAlertText() + return Alert(title: Text(title), message: Text(message)) case .deleteLegacyDatabase: return Alert( title: Text("Delete old database?"), @@ -308,7 +315,10 @@ struct DatabaseView: View { title: Text("Delete files and media?"), message: Text("This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain."), primaryButton: .destructive(Text("Delete")) { - deleteFiles() + stopChatRunBlockStartChat(m.chatRunning == false, $progressIndicator) { + deleteFiles() + return true + } }, secondaryButton: .cancel() ) @@ -328,95 +338,180 @@ struct DatabaseView: View { } } - private func authStopChat() { + private func authStopChat(_ onStop: (() -> Void)? = nil) { if UserDefaults.standard.bool(forKey: DEFAULT_PERFORM_LA) { authenticate(reason: NSLocalizedString("Stop SimpleX", comment: "authentication reason")) { laResult in switch laResult { - case .success: stopChat() - case .unavailable: stopChat() + case .success: stopChat(onStop) + case .unavailable: stopChat(onStop) case .failed: withAnimation { runChat = true } } } } else { - stopChat() + stopChat(onStop) } } - private func stopChat() { + private func stopChat(_ onStop: (() -> Void)? = nil) { Task { do { try await stopChatAsync() + onStop?() } catch let error { await MainActor.run { runChat = true - alert = .error(title: "Error stopping chat", error: responseError(error)) + showAlert("Error stopping chat", message: responseError(error)) } } } } - private func exportArchive() { - progressIndicator = true - Task { - do { - let (archivePath, archiveErrors) = try await exportChatArchive() - if archiveErrors.isEmpty { - showShareSheet(items: [archivePath]) - await MainActor.run { progressIndicator = false } - } else { - await MainActor.run { - alert = .archiveExportedWithErrors(archivePath: archivePath, archiveErrors: archiveErrors) - progressIndicator = false + func stopChatRunBlockStartChat( + _ stopped: Bool, + _ progressIndicator: Binding, + _ block: @escaping () async throws -> Bool + ) { + // if the chat was running, the sequence is: stop chat, run block, start chat. + // Otherwise, just run block and do nothing - the toggle will be visible anyway and the user can start the chat or not + if stopped { + Task { + do { + _ = try await block() + } catch { + logger.error("Error while executing block: \(error)") + } + } + } else { + authStopChat { + stoppingChat = true + runChat = false + Task { + // if it throws, let's start chat again anyway + var canStart = false + do { + canStart = try await block() + } catch { + logger.error("Error executing block: \(error)") + canStart = true + } + if canStart { + await MainActor.run { + DatabaseView.startChat($runChat, $progressIndicator) + } } } + } + } + } + + static func startChat(_ runChat: Binding, _ progressIndicator: Binding) { + progressIndicator.wrappedValue = true + let m = ChatModel.shared + if m.chatDbChanged { + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + resetChatCtrl() + do { + let hadDatabase = hasDatabase() + try initializeChat(start: true) + m.chatDbChanged = false + AppChatState.shared.set(.active) + if m.chatDbStatus != .ok || !hadDatabase { + // Hide current view and show `DatabaseErrorView` + dismissAllSheets(animated: true) + } + } catch let error { + fatalError("Error starting chat \(responseError(error))") + } + progressIndicator.wrappedValue = false + } + } else { + do { + _ = try apiStartChat() + runChat.wrappedValue = true + m.chatRunning = true + ChatReceiver.shared.start() + chatLastStartGroupDefault.set(Date.now) + AppChatState.shared.set(.active) } catch let error { + runChat.wrappedValue = false + showAlert(NSLocalizedString("Error starting chat", comment: ""), message: responseError(error)) + } + progressIndicator.wrappedValue = false + } + } + + private func exportArchive() async -> Bool { + await MainActor.run { + progressIndicator = true + } + do { + let (archivePath, archiveErrors) = try await exportChatArchive() + if archiveErrors.isEmpty { + showShareSheet(items: [archivePath]) + await MainActor.run { progressIndicator = false } + } else { await MainActor.run { - alert = .error(title: "Error exporting chat database", error: responseError(error)) + alert = .archiveExportedWithErrors(archivePath: archivePath, archiveErrors: archiveErrors) progressIndicator = false } } + } catch let error { + await MainActor.run { + alert = .error(title: "Error exporting chat database", error: responseError(error)) + progressIndicator = false + } } + return false } - private func importArchive(_ archivePath: URL) { + static func importArchive( + _ archivePath: URL, + _ progressIndicator: Binding, + _ alert: Binding + ) async -> Bool { if archivePath.startAccessingSecurityScopedResource() { - progressIndicator = true - Task { - do { - try await apiDeleteStorage() - try? FileManager.default.createDirectory(at: getWallpaperDirectory(), withIntermediateDirectories: true) - do { - let config = ArchiveConfig(archivePath: archivePath.path) - let archiveErrors = try await apiImportArchive(config: config) - _ = kcDatabasePassword.remove() - if archiveErrors.isEmpty { - await operationEnded(.archiveImported) - } else { - await operationEnded(.archiveImportedWithErrors(archiveErrors: archiveErrors)) - } - } catch let error { - await operationEnded(.error(title: "Error importing chat database", error: responseError(error))) - } - } catch let error { - await operationEnded(.error(title: "Error deleting chat database", error: responseError(error))) - } - archivePath.stopAccessingSecurityScopedResource() + await MainActor.run { + progressIndicator.wrappedValue = true } + do { + try await apiDeleteStorage() + try? FileManager.default.createDirectory(at: getWallpaperDirectory(), withIntermediateDirectories: true) + do { + let config = ArchiveConfig(archivePath: archivePath.path) + let archiveErrors = try await apiImportArchive(config: config) + shouldImportAppSettingsDefault.set(true) + _ = kcDatabasePassword.remove() + if archiveErrors.isEmpty { + await operationEnded(.archiveImported, progressIndicator, alert) + } else { + await operationEnded(.archiveImportedWithErrors(archiveErrors: archiveErrors), progressIndicator, alert) + } + return true + } catch let error { + await operationEnded(.error(title: "Error importing chat database", error: responseError(error)), progressIndicator, alert) + } + } catch let error { + await operationEnded(.error(title: "Error deleting chat database", error: responseError(error)), progressIndicator, alert) + } + archivePath.stopAccessingSecurityScopedResource() } else { - alert = .error(title: "Error accessing database file") + showAlert("Error accessing database file") } + return false } - private func deleteChat() { - progressIndicator = true - Task { - do { - try await deleteChatAsync() - await operationEnded(.chatDeleted) - appFilesCountAndSize = directoryFileCountAndSize(getAppFilesDirectory()) - } catch let error { - await operationEnded(.error(title: "Error deleting database", error: responseError(error))) - } + private func deleteChat() async -> Bool { + await MainActor.run { + progressIndicator = true + } + do { + try await deleteChatAsync() + appFilesCountAndSize = directoryFileCountAndSize(getAppFilesDirectory()) + await DatabaseView.operationEnded(.chatDeleted, $progressIndicator, $alert) + return true + } catch let error { + await DatabaseView.operationEnded(.error(title: "Error deleting database", error: responseError(error)), $progressIndicator, $alert) + return false } } @@ -428,39 +523,28 @@ struct DatabaseView: View { } } - private func operationEnded(_ dbAlert: DatabaseAlert) async { + private static func operationEnded(_ dbAlert: DatabaseAlert, _ progressIndicator: Binding, _ alert: Binding) async { await MainActor.run { + let m = ChatModel.shared m.chatDbChanged = true m.chatInitialized = false - progressIndicator = false - alert = dbAlert + progressIndicator.wrappedValue = false } - } - - private func startChat() { - if m.chatDbChanged { - dismissSettingsSheet() - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - resetChatCtrl() - do { - try initializeChat(start: true) - m.chatDbChanged = false - AppChatState.shared.set(.active) - } catch let error { - fatalError("Error starting chat \(responseError(error))") - } - } - } else { - do { - _ = try apiStartChat() - runChat = true - m.chatRunning = true - ChatReceiver.shared.start() - chatLastStartGroupDefault.set(Date.now) - AppChatState.shared.set(.active) - } catch let error { - runChat = false - alert = .error(title: "Error starting chat", error: responseError(error)) + await withCheckedContinuation { cont in + let okAlertActionWaiting = UIAlertAction(title: NSLocalizedString("Ok", comment: "alert button"), style: .default, handler: { _ in cont.resume() }) + // show these alerts globally so they are visible when all sheets will be hidden + if case .archiveImported = dbAlert { + let (title, message) = archiveImportedAlertText() + showAlert(title, message: message, actions: { [okAlertActionWaiting] }) + } else if case .archiveImportedWithErrors(let errs) = dbAlert { + let (title, message) = archiveImportedWithErrorsAlertText(errs: errs) + showAlert(title, message: message, actions: { [okAlertActionWaiting] }) + } else if case .chatDeleted = dbAlert { + let (title, message) = chatDeletedAlertText() + showAlert(title, message: message, actions: { [okAlertActionWaiting] }) + } else { + alert.wrappedValue = dbAlert + cont.resume() } } } @@ -503,8 +587,28 @@ struct DatabaseView: View { } } -func archiveErrorsText(_ errs: [ArchiveError]) -> Text { - return Text("\n" + errs.map(showArchiveError).joined(separator: "\n")) +private func archiveImportedAlertText() -> (String, String) { + ( + NSLocalizedString("Chat database imported", comment: ""), + NSLocalizedString("Restart the app to use imported chat database", comment: "") + ) +} +private func archiveImportedWithErrorsAlertText(errs: [ArchiveError]) -> (String, String) { + ( + NSLocalizedString("Chat database imported", comment: ""), + NSLocalizedString("Restart the app to use imported chat database", comment: "") + "\n" + NSLocalizedString("Some non-fatal errors occurred during import:", comment: "") + archiveErrorsText(errs) + ) +} + +private func chatDeletedAlertText() -> (String, String) { + ( + NSLocalizedString("Chat database deleted", comment: ""), + NSLocalizedString("Restart the app to create a new chat profile", comment: "") + ) +} + +func archiveErrorsText(_ errs: [ArchiveError]) -> String { + return "\n" + errs.map(showArchiveError).joined(separator: "\n") func showArchiveError(_ err: ArchiveError) -> String { switch err { diff --git a/apps/ios/Shared/Views/Database/MigrateToAppGroupView.swift b/apps/ios/Shared/Views/Database/MigrateToAppGroupView.swift index e79f24c6d9..79c0a42ae0 100644 --- a/apps/ios/Shared/Views/Database/MigrateToAppGroupView.swift +++ b/apps/ios/Shared/Views/Database/MigrateToAppGroupView.swift @@ -117,7 +117,7 @@ struct MigrateToAppGroupView: View { setV3DBMigration(.migration_error) migrationError = "Error starting chat: \(responseError(error))" } - deleteOldArchive() + deleteOldChatArchive() } label: { Text("Start chat") .font(.title) @@ -235,14 +235,16 @@ func exportChatArchive(_ storagePath: URL? = nil) async throws -> (URL, [Archive try? FileManager.default.createDirectory(at: getWallpaperDirectory(), withIntermediateDirectories: true) let errs = try await apiExportArchive(config: config) if storagePath == nil { - deleteOldArchive() + deleteOldChatArchive() UserDefaults.standard.set(archiveName, forKey: DEFAULT_CHAT_ARCHIVE_NAME) chatArchiveTimeDefault.set(archiveTime) } return (archivePath, errs) } -func deleteOldArchive() { +/// Deprecated. Remove in the end of 2025. All unused archives should be deleted for the most users til then. +/// Remove DEFAULT_CHAT_ARCHIVE_NAME and DEFAULT_CHAT_ARCHIVE_TIME as well +func deleteOldChatArchive() { let d = UserDefaults.standard if let archiveName = d.string(forKey: DEFAULT_CHAT_ARCHIVE_NAME) { do { diff --git a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift index 829cea0165..eb8df5fb04 100644 --- a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift +++ b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift @@ -177,7 +177,7 @@ struct MigrateFromDevice: View { case let .archiveExportedWithErrors(archivePath, errs): return Alert( title: Text("Chat database exported"), - message: Text("You may migrate the exported database.") + Text(verbatim: "\n") + Text("Some file(s) were not exported:") + archiveErrorsText(errs), + message: Text("You may migrate the exported database.") + Text(verbatim: "\n") + Text("Some file(s) were not exported:") + Text(archiveErrorsText(errs)), dismissButton: .default(Text("Continue")) { Task { await uploadArchive(path: archivePath) } } @@ -222,7 +222,8 @@ struct MigrateFromDevice: View { } private func passphraseNotSetView() -> some View { - DatabaseEncryptionView(useKeychain: $useKeychain, migration: true) + DatabaseEncryptionView(useKeychain: $useKeychain, migration: true, stopChatRunBlockStartChat: { _, _ in + }) .onChange(of: initialRandomDBPassphrase) { initial in if !initial { migrationState = .uploadConfirmation diff --git a/apps/ios/Shared/Views/Migration/MigrateToDevice.swift b/apps/ios/Shared/Views/Migration/MigrateToDevice.swift index fe0eec609b..763cd473fe 100644 --- a/apps/ios/Shared/Views/Migration/MigrateToDevice.swift +++ b/apps/ios/Shared/Views/Migration/MigrateToDevice.swift @@ -103,6 +103,9 @@ struct MigrateToDevice: View { @State private var showQRCodeScanner: Bool = true @State private var pasteboardHasStrings = UIPasteboard.general.hasStrings + @State private var importingArchiveFromFileProgressIndicator = false + @State private var showFileImporter = false + var body: some View { VStack { switch migrationState { @@ -200,6 +203,12 @@ struct MigrateToDevice: View { Section(header: Text("Or paste archive link").foregroundColor(theme.colors.secondary)) { pasteLinkView() } + Section(header: Text("Or import archive file").foregroundColor(theme.colors.secondary)) { + archiveImportFromFileView() + } + } + if importingArchiveFromFileProgressIndicator { + progressView() } } } @@ -220,6 +229,34 @@ struct MigrateToDevice: View { .frame(maxWidth: .infinity, alignment: .center) } + private func archiveImportFromFileView() -> some View { + Button { + showFileImporter = true + } label: { + Label("Import database", systemImage: "square.and.arrow.down") + } + .disabled(importingArchiveFromFileProgressIndicator) + .fileImporter( + isPresented: $showFileImporter, + allowedContentTypes: [.zip], + allowsMultipleSelection: false + ) { result in + if case let .success(files) = result, let fileURL = files.first { + Task { + let success = await DatabaseView.importArchive(fileURL, $importingArchiveFromFileProgressIndicator, Binding.constant(nil)) + if success { + DatabaseView.startChat( + Binding.constant(false), + $importingArchiveFromFileProgressIndicator + ) + hideView() + } + } + } + } + } + + private func linkDownloadingView(_ link: String) -> some View { ZStack { List { diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index 95bf327f1b..8a4ccce91b 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -39,6 +39,7 @@ let DEFAULT_EXPERIMENTAL_CALLS = "experimentalCalls" let DEFAULT_CHAT_ARCHIVE_NAME = "chatArchiveName" let DEFAULT_CHAT_ARCHIVE_TIME = "chatArchiveTime" let DEFAULT_CHAT_V3_DB_MIGRATION = "chatV3DBMigration" +let DEFAULT_SHOULD_IMPORT_APP_SETTINGS = "shouldImportAppSettings" let DEFAULT_DEVELOPER_TOOLS = "developerTools" let DEFAULT_ENCRYPTION_STARTED = "encryptionStarted" let DEFAULT_ENCRYPTION_STARTED_AT = "encryptionStartedAt" @@ -192,6 +193,8 @@ let customDisappearingMessageTimeDefault = IntDefault(defaults: UserDefaults.sta let showDeleteConversationNoticeDefault = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SHOW_DELETE_CONVERSATION_NOTICE) let showDeleteContactNoticeDefault = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SHOW_DELETE_CONTACT_NOTICE) +/// after importing new database, this flag will be set and unset only after importing app settings in `initializeChat` */ +let shouldImportAppSettingsDefault = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SHOULD_IMPORT_APP_SETTINGS) let currentThemeDefault = StringDefault(defaults: UserDefaults.standard, forKey: DEFAULT_CURRENT_THEME, withDefault: DefaultTheme.SYSTEM_THEME_NAME) let systemDarkThemeDefault = StringDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SYSTEM_DARK_THEME, withDefault: DefaultTheme.DARK.themeName) let currentThemeIdsDefault = CodableDefault<[String: String]>(defaults: UserDefaults.standard, forKey: DEFAULT_CURRENT_THEME_IDS, withDefault: [:] ) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 0ffe9d1f40..8d2d05489d 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -139,7 +139,6 @@ 5CF937202B24DE8C00E1D781 /* SharedFileSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */; }; 5CF937232B2503D000E1D781 /* NSESubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF937212B25034A00E1D781 /* NSESubscriber.swift */; }; 5CFA59C42860BC6200863A68 /* MigrateToAppGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */; }; - 5CFA59D12864782E00863A68 /* ChatArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFA59CF286477B400863A68 /* ChatArchiveView.swift */; }; 5CFE0921282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; }; 5CFE0922282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; }; 640417CD2B29B8C200CCB412 /* NewChatMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640417CB2B29B8C200CCB412 /* NewChatMenuButton.swift */; }; @@ -489,7 +488,6 @@ 5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedFileSubscriber.swift; sourceTree = ""; }; 5CF937212B25034A00E1D781 /* NSESubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSESubscriber.swift; sourceTree = ""; }; 5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateToAppGroupView.swift; sourceTree = ""; }; - 5CFA59CF286477B400863A68 /* ChatArchiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatArchiveView.swift; sourceTree = ""; }; 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ZoomableScrollView.swift; path = Shared/Views/ZoomableScrollView.swift; sourceTree = SOURCE_ROOT; }; 640417CB2B29B8C200CCB412 /* NewChatMenuButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewChatMenuButton.swift; sourceTree = ""; }; 640417CC2B29B8C200CCB412 /* NewChatView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewChatView.swift; sourceTree = ""; }; @@ -1058,7 +1056,6 @@ isa = PBXGroup; children = ( 5C4B3B09285FB130003915F2 /* DatabaseView.swift */, - 5CFA59CF286477B400863A68 /* ChatArchiveView.swift */, 5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */, 5C9CC7A828C532AB00BEF955 /* DatabaseErrorView.swift */, 5C9CC7AC28C55D7800BEF955 /* DatabaseEncryptionView.swift */, @@ -1511,7 +1508,6 @@ 8C9BC2652C240D5200875A27 /* ThemeModeEditor.swift in Sources */, 5CB346E92869E8BA001FD2EF /* PushEnvironment.swift in Sources */, 5C55A91F283AD0E400C4E99E /* CallManager.swift in Sources */, - 5CFA59D12864782E00863A68 /* ChatArchiveView.swift in Sources */, 649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */, 5CADE79C292131E900072E13 /* ContactPreferencesView.swift in Sources */, CEA6E91C2CBD21B0002B5DB4 /* UserDefault.swift in Sources */, From 4738286f4e2fb8726da2ad2fe80f155c5a6316c5 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Sat, 30 Nov 2024 23:33:38 +0700 Subject: [PATCH 137/567] android, desktop: start/stop chat toggle refactoring (#5261) * android, desktop: start/stop chat toggle refactoring * changes * translations * better * better * do not start chat after export, always show run toggle (#5283) * update heading --------- Co-authored-by: Evgeny Poberezkin --- .../main/java/chat/simplex/app/SimplexApp.kt | 2 + apps/multiplatform/common/build.gradle.kts | 4 +- .../chat/simplex/common/model/SimpleXAPI.kt | 6 +- .../chat/simplex/common/platform/Core.kt | 9 + .../chat/simplex/common/platform/Files.kt | 2 +- .../common/views/database/ChatArchiveView.kt | 108 ------- .../views/database/DatabaseEncryptionView.kt | 8 +- .../common/views/database/DatabaseView.kt | 297 +++++++++--------- .../views/migration/MigrateFromDevice.kt | 2 +- .../common/views/migration/MigrateToDevice.kt | 60 +++- .../common/views/usersettings/SettingsView.kt | 2 +- .../commonMain/resources/MR/ar/strings.xml | 6 - .../commonMain/resources/MR/base/strings.xml | 10 +- .../commonMain/resources/MR/bg/strings.xml | 6 - .../commonMain/resources/MR/cs/strings.xml | 6 - .../commonMain/resources/MR/de/strings.xml | 6 - .../commonMain/resources/MR/el/strings.xml | 3 - .../commonMain/resources/MR/es/strings.xml | 6 - .../commonMain/resources/MR/fa/strings.xml | 6 - .../commonMain/resources/MR/fi/strings.xml | 6 - .../commonMain/resources/MR/fr/strings.xml | 6 - .../commonMain/resources/MR/hi/strings.xml | 4 - .../commonMain/resources/MR/hu/strings.xml | 6 - .../commonMain/resources/MR/in/strings.xml | 4 - .../commonMain/resources/MR/it/strings.xml | 6 - .../commonMain/resources/MR/iw/strings.xml | 6 - .../commonMain/resources/MR/ja/strings.xml | 6 - .../commonMain/resources/MR/ko/strings.xml | 6 - .../commonMain/resources/MR/lt/strings.xml | 6 - .../commonMain/resources/MR/nl/strings.xml | 6 - .../commonMain/resources/MR/pl/strings.xml | 6 - .../resources/MR/pt-rBR/strings.xml | 6 - .../commonMain/resources/MR/pt/strings.xml | 6 - .../commonMain/resources/MR/ro/strings.xml | 6 - .../commonMain/resources/MR/ru/strings.xml | 6 - .../commonMain/resources/MR/th/strings.xml | 6 - .../commonMain/resources/MR/tr/strings.xml | 6 - .../commonMain/resources/MR/uk/strings.xml | 6 - .../commonMain/resources/MR/vi/strings.xml | 5 - .../resources/MR/zh-rCN/strings.xml | 6 - .../resources/MR/zh-rTW/strings.xml | 6 - .../common/platform/AppCommon.desktop.kt | 2 + .../views/database/DatabaseView.desktop.kt | 4 +- 43 files changed, 236 insertions(+), 446 deletions(-) delete mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt index 13f9b888b9..f46ed4775f 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt @@ -28,6 +28,7 @@ import chat.simplex.common.model.ChatModel.withChats import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.call.* +import chat.simplex.common.views.database.deleteOldChatArchive import chat.simplex.common.views.helpers.* import chat.simplex.common.views.onboarding.OnboardingStage import com.jakewharton.processphoenix.ProcessPhoenix @@ -72,6 +73,7 @@ class SimplexApp: Application(), LifecycleEventObserver { runMigrations() tmpDir.deleteRecursively() tmpDir.mkdir() + deleteOldChatArchive() // Present screen for continue migration if it wasn't finished yet if (chatModel.migrationState.value != null) { diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts index b9b307c8f4..ad67b7cf1e 100644 --- a/apps/multiplatform/common/build.gradle.kts +++ b/apps/multiplatform/common/build.gradle.kts @@ -266,7 +266,9 @@ afterEvaluate { if (isBase) { baseFormatting[lineId] = fixedLine.formatting(file.absolutePath) } else if (baseFormatting[lineId] != fixedLine.formatting(file.absolutePath)) { - errors.add("Incorrect formatting in string: $fixedLine \nin ${file.absolutePath}") + errors.add("Incorrect formatting in string: $fixedLine \nin ${file.absolutePath}.\n" + + "If you want to remove non-base translation, search this Regex and replace with empty value in IDE:\n" + + "[ ]*<.*\"${line.substringAfter("\"").substringBefore("\"")}\"[^/]*\\n*.*string>\\n") } finalLines.add(fixedLine) } else if (multiline.isEmpty() && startStringRegex.containsMatchIn(line)) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index def6d81897..7ada27d000 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -211,6 +211,9 @@ class AppPreferences { // Note that this situation can only happen if passphrase for the first database is incorrect because, otherwise, backend will re-create second database automatically val newDatabaseInitialized = mkBoolPreference(SHARED_PREFS_NEW_DATABASE_INITIALIZED, false) + /** after importing new database, this flag will be set and unset only after importing app settings in [initChatController] */ + val shouldImportAppSettings = mkBoolPreference(SHARED_PREFS_SHOULD_IMPORT_APP_SETTINGS, false) + val currentTheme = mkStrPreference(SHARED_PREFS_CURRENT_THEME, DefaultTheme.SYSTEM_THEME_NAME) val systemDarkTheme = mkStrPreference(SHARED_PREFS_SYSTEM_DARK_THEME, DefaultTheme.SIMPLEX.themeName) val currentThemeIds = mkMapPreference(SHARED_PREFS_CURRENT_THEME_IDs, mapOf(), encode = { @@ -425,6 +428,7 @@ class AppPreferences { private const val SHARED_PREFS_INITIALIZATION_VECTOR_SELF_DESTRUCT_PASSPHRASE = "InitializationVectorSelfDestructPassphrase" private const val SHARED_PREFS_ENCRYPTION_STARTED_AT = "EncryptionStartedAt" private const val SHARED_PREFS_NEW_DATABASE_INITIALIZED = "NewDatabaseInitialized" + private const val SHARED_PREFS_SHOULD_IMPORT_APP_SETTINGS = "ShouldImportAppSettings" private const val SHARED_PREFS_CONFIRM_DB_UPGRADES = "ConfirmDBUpgrades" private const val SHARED_PREFS_ONE_HAND_UI = "OneHandUI" private const val SHARED_PREFS_SELF_DESTRUCT = "LocalAuthenticationSelfDestruct" @@ -6909,7 +6913,7 @@ data class AppSettings( uiDarkColorScheme?.let { def.systemDarkTheme.set(it) } uiCurrentThemeIds?.let { def.currentThemeIds.set(it) } uiThemes?.let { def.themeOverrides.set(it.skipDuplicates()) } - oneHandUI?.let { def.oneHandUI.set(if (appPlatform.isAndroid) it else false) } + oneHandUI?.let { def.oneHandUI.set(it) } } companion object { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt index 08ca72c6bd..5262714099 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt @@ -119,6 +119,15 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat val user = chatController.apiGetActiveUser(null) chatModel.currentUser.value = user chatModel.conditions.value = chatController.getServerOperators(null) ?: ServerOperatorConditionsDetail.empty + if (appPrefs.shouldImportAppSettings.get()) { + try { + val appSettings = controller.apiGetAppSettings(AppSettings.current.prepareForExport()) + appSettings.importIntoApp() + appPrefs.shouldImportAppSettings.set(false) + } catch (e: Exception) { + Log.e(TAG, "Error while importing app settings: " + e.stackTraceToString()) + } + } if (user == null) { chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true) chatModel.currentUser.value = null diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt index 9110987190..e9fc8c97f9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt @@ -26,7 +26,7 @@ expect val agentDatabaseFileName: String /** * This is used only for temporary storing db archive for export. -* Providing [tmpDir] instead crashes the app. Check db export before moving from this path to something else +* Providing [tmpDir] instead crashes the app on Android (only). Check db export before moving from this path to something else * */ expect val databaseExportDir: File diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt deleted file mode 100644 index 96acea5446..0000000000 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt +++ /dev/null @@ -1,108 +0,0 @@ -package chat.simplex.common.views.database - -import SectionBottomSpacer -import SectionTextFooter -import SectionView -import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.foundation.layout.* -import androidx.compose.material.* -import androidx.compose.runtime.* -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource -import chat.simplex.common.model.ChatModel -import chat.simplex.common.platform.* -import chat.simplex.common.ui.theme.SimpleXTheme -import chat.simplex.common.views.helpers.* -import chat.simplex.common.views.usersettings.* -import chat.simplex.res.MR -import kotlinx.datetime.* -import java.io.File -import java.net.URI -import java.text.SimpleDateFormat -import java.util.* - -@Composable -fun ChatArchiveView(m: ChatModel, title: String, archiveName: String, archiveTime: Instant) { - val archivePath = filesDir.absolutePath + File.separator + archiveName - val saveArchiveLauncher = rememberFileChooserLauncher(false) { to: URI? -> - if (to != null) { - copyFileToFile(File(archivePath), to) {} - } - } - ChatArchiveLayout( - title, - archiveTime, - saveArchive = { withLongRunningApi { saveArchiveLauncher.launch(archivePath.substringAfterLast(File.separator)) }}, - deleteArchiveAlert = { deleteArchiveAlert(m, archivePath) } - ) -} - -@Composable -fun ChatArchiveLayout( - title: String, - archiveTime: Instant, - saveArchive: () -> Unit, - deleteArchiveAlert: () -> Unit -) { - ColumnWithScrollBar { - AppBarTitle(title) - SectionView(stringResource(MR.strings.chat_archive_section)) { - SettingsActionItem( - painterResource(MR.images.ic_ios_share), - stringResource(MR.strings.save_archive), - saveArchive, - textColor = MaterialTheme.colors.primary, - iconColor = MaterialTheme.colors.primary, - ) - SettingsActionItem( - painterResource(MR.images.ic_delete), - stringResource(MR.strings.delete_archive), - deleteArchiveAlert, - textColor = Color.Red, - iconColor = Color.Red, - ) - } - val archiveTs = SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.US).format(Date.from(archiveTime.toJavaInstant())) - SectionTextFooter( - String.format(generalGetString(MR.strings.archive_created_on_ts), archiveTs) - ) - SectionBottomSpacer() - } -} - -private fun deleteArchiveAlert(m: ChatModel, archivePath: String) { - AlertManager.shared.showAlertDialog( - title = generalGetString(MR.strings.delete_chat_archive_question), - confirmText = generalGetString(MR.strings.delete_verb), - onConfirm = { - val fileDeleted = File(archivePath).delete() - if (fileDeleted) { - m.controller.appPrefs.chatArchiveName.set(null) - m.controller.appPrefs.chatArchiveTime.set(null) - ModalManager.start.closeModal() - } else { - Log.e(TAG, "deleteArchiveAlert delete() error") - } - }, - destructive = true, - ) -} - -@Preview/*( - uiMode = Configuration.UI_MODE_NIGHT_YES, - showBackground = true, - name = "Dark Mode" -)*/ -@Composable -fun PreviewChatArchiveLayout() { - SimpleXTheme { - ChatArchiveLayout( - "New database archive", - archiveTime = Clock.System.now(), - saveArchive = {}, - deleteArchiveAlert = {} - ) - } -} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt index 654d250274..c2e1d67d50 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt @@ -48,6 +48,7 @@ fun DatabaseEncryptionView(m: ChatModel, migration: Boolean) { val currentKey = remember { mutableStateOf(if (initialRandomDBPassphrase.value) DatabaseUtils.ksDatabasePassword.get() ?: "" else "") } val newKey = rememberSaveable { mutableStateOf("") } val confirmNewKey = rememberSaveable { mutableStateOf("") } + val chatLastStart = remember { mutableStateOf(appPrefs.chatLastStart.get()) } Box( Modifier.fillMaxSize(), @@ -63,8 +64,9 @@ fun DatabaseEncryptionView(m: ChatModel, migration: Boolean) { progressIndicator, migration, onConfirmEncrypt = { - withLongRunningApi { - encryptDatabase( + // it will try to stop and start the chat in case of: non-migration && successful encryption. In migration the chat will remain stopped + stopChatRunBlockStartChat(migration, chatLastStart, progressIndicator, ) { + val success = encryptDatabase( currentKey = currentKey, newKey = newKey, confirmNewKey = confirmNewKey, @@ -74,6 +76,7 @@ fun DatabaseEncryptionView(m: ChatModel, migration: Boolean) { progressIndicator = progressIndicator, migration = migration ) + success && !migration } } ) @@ -306,7 +309,6 @@ private fun operationEnded(m: ChatModel, progressIndicator: MutableState, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index 3dd7e673c4..8185122089 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -26,7 +26,6 @@ import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.* import chat.simplex.common.platform.* import chat.simplex.res.MR -import kotlinx.coroutines.sync.withLock import kotlinx.datetime.* import java.io.* import java.net.URI @@ -36,18 +35,14 @@ import java.util.* import kotlin.collections.ArrayList @Composable -fun DatabaseView( - m: ChatModel, - showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit) -) { - val currentRemoteHost by remember { chatModel.currentRemoteHost } +fun DatabaseView() { + val m = chatModel val progressIndicator = remember { mutableStateOf(false) } val prefs = m.controller.appPrefs val useKeychain = remember { mutableStateOf(prefs.storeDBPassphrase.get()) } - val chatArchiveName = remember { mutableStateOf(prefs.chatArchiveName.get()) } - val chatArchiveTime = remember { mutableStateOf(prefs.chatArchiveTime.get()) } val chatLastStart = remember { mutableStateOf(prefs.chatLastStart.get()) } val chatArchiveFile = remember { mutableStateOf(null) } + val stopped = remember { m.chatRunning }.value == false val saveArchiveLauncher = rememberFileChooserLauncher(false) { to: URI? -> val file = chatArchiveFile.value if (file != null && to != null) { @@ -59,8 +54,11 @@ fun DatabaseView( val appFilesCountAndSize = remember { mutableStateOf(directoryFileCountAndSize(appFilesDir.absolutePath)) } val importArchiveLauncher = rememberFileChooserLauncher(true) { to: URI? -> if (to != null) { - importArchiveAlert(m, to, appFilesCountAndSize, progressIndicator) { - startChat(m, chatLastStart, m.chatDbChanged) + importArchiveAlert { + stopChatRunBlockStartChat(stopped, chatLastStart, progressIndicator) { + importArchive(to, appFilesCountAndSize, progressIndicator) + true + } } } } @@ -71,27 +69,40 @@ fun DatabaseView( val user = m.currentUser.value val rhId = user?.remoteHostId DatabaseLayout( - currentRemoteHost = currentRemoteHost, progressIndicator.value, - remember { m.chatRunning }.value != false, - m.chatDbChanged.value, + stopped, useKeychain.value, m.chatDbEncrypted.value, m.controller.appPrefs.storeDBPassphrase.state.value, m.controller.appPrefs.initialRandomDBPassphrase, importArchiveLauncher, - chatArchiveName, - chatArchiveTime, - chatLastStart, appFilesCountAndSize, chatItemTTL, user, m.users, startChat = { startChat(m, chatLastStart, m.chatDbChanged, progressIndicator) }, stopChatAlert = { stopChatAlert(m, progressIndicator) }, - exportArchive = { exportArchive(m, progressIndicator, chatArchiveName, chatArchiveTime, chatArchiveFile, saveArchiveLauncher) }, - deleteChatAlert = { deleteChatAlert(m, progressIndicator) }, - deleteAppFilesAndMedia = { deleteFilesAndMediaAlert(appFilesCountAndSize) }, + exportArchive = { + stopChatRunBlockStartChat(stopped, chatLastStart, progressIndicator) { + exportArchive(m, progressIndicator, chatArchiveFile, saveArchiveLauncher) + } + }, + deleteChatAlert = { + deleteChatAlert { + stopChatRunBlockStartChat(stopped, chatLastStart, progressIndicator) { + deleteChat(m, progressIndicator) + true + } + } + }, + deleteAppFilesAndMedia = { + deleteFilesAndMediaAlert { + stopChatRunBlockStartChat(stopped, chatLastStart, progressIndicator) { + deleteFiles(appFilesCountAndSize) + true + } + } + }, onChatItemTTLSelected = { val oldValue = chatItemTTL.value chatItemTTL.value = it @@ -101,7 +112,6 @@ fun DatabaseView( setCiTTL(m, rhId, chatItemTTL, progressIndicator, appFilesCountAndSize) } }, - showSettingsModal, disconnectAllHosts = { val connected = chatModel.remoteHosts.filter { it.sessionState is RemoteHostSessionState.Connected } connected.forEachIndexed { index, h -> @@ -128,18 +138,13 @@ fun DatabaseView( @Composable fun DatabaseLayout( - currentRemoteHost: RemoteHostInfo?, progressIndicator: Boolean, - runChat: Boolean, - chatDbChanged: Boolean, + stopped: Boolean, useKeyChain: Boolean, chatDbEncrypted: Boolean?, passphraseSaved: Boolean, initialRandomDBPassphrase: SharedPreference, importArchiveLauncher: FileChooserLauncher, - chatArchiveName: MutableState, - chatArchiveTime: MutableState, - chatLastStart: MutableState, appFilesCountAndSize: MutableState>, chatItemTTL: MutableState, currentUser: User?, @@ -150,11 +155,9 @@ fun DatabaseLayout( deleteChatAlert: () -> Unit, deleteAppFilesAndMedia: () -> Unit, onChatItemTTLSelected: (ChatItemTTL) -> Unit, - showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), disconnectAllHosts: () -> Unit, ) { - val stopped = !runChat - val operationsDisabled = (!stopped || progressIndicator) && !chatModel.desktopNoUserNoRemote + val operationsDisabled = progressIndicator && !chatModel.desktopNoUserNoRemote ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.your_chat_database)) @@ -178,13 +181,16 @@ fun DatabaseLayout( } val toggleEnabled = remember { chatModel.remoteHosts }.none { it.sessionState is RemoteHostSessionState.Connected } if (chatModel.localUserCreated.value == true) { + // still show the toggle in case database was stopped when the user opened this screen because it can be in the following situations: + // - database was stopped after migration and the app relaunched + // - something wrong happened with database operations and the database couldn't be launched when it should SectionView(stringResource(MR.strings.run_chat_section)) { if (!toggleEnabled) { SectionItemView(disconnectAllHosts) { Text(generalGetString(MR.strings.disconnect_remote_hosts), Modifier.fillMaxWidth(), color = WarningOrange) } } - RunChatSetting(runChat, stopped, toggleEnabled && !progressIndicator, startChat, stopChatAlert) + RunChatSetting(stopped, toggleEnabled && !progressIndicator, startChat, stopChatAlert) } SectionTextFooter( if (stopped) { @@ -207,7 +213,7 @@ fun DatabaseLayout( if (unencrypted) painterResource(MR.images.ic_lock_open_right) else if (useKeyChain) painterResource(MR.images.ic_vpn_key_filled) else painterResource(MR.images.ic_lock), stringResource(MR.strings.database_passphrase), - click = showSettingsModal() { DatabaseEncryptionView(it, false) }, + click = { ModalManager.start.showModal { DatabaseEncryptionView(chatModel, false) } }, iconColor = if (unencrypted || (appPlatform.isDesktop && passphraseSaved)) WarningOrange else MaterialTheme.colors.secondary, disabled = operationsDisabled ) @@ -225,6 +231,9 @@ fun DatabaseLayout( click = { if (initialRandomDBPassphrase.get()) { exportProhibitedAlert() + ModalManager.start.showModal { + DatabaseEncryptionView(chatModel, false) + } } else { exportArchive() } @@ -241,18 +250,6 @@ fun DatabaseLayout( iconColor = Color.Red, disabled = operationsDisabled ) - val chatArchiveNameVal = chatArchiveName.value - val chatArchiveTimeVal = chatArchiveTime.value - val chatLastStartVal = chatLastStart.value - if (chatArchiveNameVal != null && chatArchiveTimeVal != null && chatLastStartVal != null) { - val title = chatArchiveTitle(chatArchiveTimeVal, chatLastStartVal) - SettingsActionItem( - painterResource(MR.images.ic_inventory_2), - title, - click = showSettingsModal { ChatArchiveView(it, title, chatArchiveNameVal, chatArchiveTimeVal) }, - disabled = operationsDisabled - ) - } SettingsActionItem( painterResource(MR.images.ic_delete_forever), stringResource(MR.strings.delete_database), @@ -333,7 +330,6 @@ private fun TtlOptions(current: State, enabled: State, onS @Composable fun RunChatSetting( - runChat: Boolean, stopped: Boolean, enabled: Boolean, startChat: () -> Unit, @@ -346,7 +342,7 @@ fun RunChatSetting( iconColor = if (stopped) Color.Red else MaterialTheme.colors.primary, ) { DefaultSwitch( - checked = runChat, + checked = !stopped, onCheckedChange = { runChatSwitch -> if (runChatSwitch) { startChat() @@ -359,12 +355,12 @@ fun RunChatSetting( } } -@Composable -fun chatArchiveTitle(chatArchiveTime: Instant, chatLastStart: Instant): String { - return stringResource(if (chatArchiveTime < chatLastStart) MR.strings.old_database_archive else MR.strings.new_database_archive) -} - -fun startChat(m: ChatModel, chatLastStart: MutableState, chatDbChanged: MutableState, progressIndicator: MutableState? = null) { +fun startChat( + m: ChatModel, + chatLastStart: MutableState, + chatDbChanged: MutableState, + progressIndicator: MutableState? = null +) { withLongRunningApi { try { progressIndicator?.value = true @@ -469,6 +465,40 @@ suspend fun stopChatAsync(m: ChatModel) { controller.appPrefs.chatStopped.set(true) } +fun stopChatRunBlockStartChat( + stopped: Boolean, + chatLastStart: MutableState, + progressIndicator: MutableState, + block: suspend () -> Boolean +) { + // if the chat was running, the sequence is: stop chat, run block, start chat. + // Otherwise, just run block and do nothing - the toggle will be visible anyway and the user can start the chat or not + if (stopped) { + withLongRunningApi { + try { + block() + } catch (e: Throwable) { + Log.e(TAG, e.stackTraceToString()) + } + } + } else { + authStopChat(chatModel, progressIndicator) { + withLongRunningApi { + // if it throws, let's start chat again anyway + val canStart = try { + block() + } catch (e: Throwable) { + Log.e(TAG, e.stackTraceToString()) + true + } + if (canStart) { + startChat(chatModel, chatLastStart, chatModel.chatDbChanged, progressIndicator) + } + } + } + } +} + suspend fun deleteChatAsync(m: ChatModel) { m.controller.apiDeleteStorage() DatabaseUtils.ksDatabasePassword.remove() @@ -511,47 +541,42 @@ fun deleteChatDatabaseFilesAndState() { ntfManager.cancelAllNotifications() } -private fun exportArchive( +private suspend fun exportArchive( m: ChatModel, progressIndicator: MutableState, - chatArchiveName: MutableState, - chatArchiveTime: MutableState, chatArchiveFile: MutableState, saveArchiveLauncher: FileChooserLauncher -) { +): Boolean { progressIndicator.value = true - withLongRunningApi { - try { - val (archiveFile, archiveErrors) = exportChatArchive(m, null, chatArchiveName, chatArchiveTime, chatArchiveFile) - chatArchiveFile.value = archiveFile - if (archiveErrors.isEmpty()) { - saveArchiveLauncher.launch(archiveFile.substringAfterLast(File.separator)) - } else { - showArchiveExportedWithErrorsAlert(generalGetString(MR.strings.chat_database_exported_save), archiveErrors) { - withLongRunningApi { - saveArchiveLauncher.launch(archiveFile.substringAfterLast(File.separator)) - } + try { + val (archiveFile, archiveErrors) = exportChatArchive(m, null, chatArchiveFile) + chatArchiveFile.value = archiveFile + if (archiveErrors.isEmpty()) { + saveArchiveLauncher.launch(archiveFile.substringAfterLast(File.separator)) + } else { + showArchiveExportedWithErrorsAlert(generalGetString(MR.strings.chat_database_exported_save), archiveErrors) { + withLongRunningApi { + saveArchiveLauncher.launch(archiveFile.substringAfterLast(File.separator)) } } - progressIndicator.value = false - } catch (e: Error) { - AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_exporting_chat_database), e.toString()) - progressIndicator.value = false } + progressIndicator.value = false + } catch (e: Throwable) { + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_exporting_chat_database), e.toString()) + progressIndicator.value = false } + return false } suspend fun exportChatArchive( m: ChatModel, storagePath: File?, - chatArchiveName: MutableState, - chatArchiveTime: MutableState, chatArchiveFile: MutableState ): Pair> { val archiveTime = Clock.System.now() val ts = SimpleDateFormat("yyyy-MM-dd'T'HHmmss", Locale.US).format(Date.from(archiveTime.toJavaInstant())) val archiveName = "simplex-chat.$ts.zip" - val archivePath = "${(storagePath ?: filesDir).absolutePath}${File.separator}$archiveName" + val archivePath = "${(storagePath ?: databaseExportDir).absolutePath}${File.separator}$archiveName" val config = ArchiveConfig(archivePath, parentTempDirectory = databaseExportDir.toString()) // Settings should be saved before changing a passphrase, otherwise the database needs to be migrated first if (!m.chatDbChanged.value) { @@ -560,42 +585,37 @@ suspend fun exportChatArchive( wallpapersDir.mkdirs() val archiveErrors = m.controller.apiExportArchive(config) if (storagePath == null) { - deleteOldArchive(m) + deleteOldChatArchive() m.controller.appPrefs.chatArchiveName.set(archiveName) m.controller.appPrefs.chatArchiveTime.set(archiveTime) } - chatArchiveName.value = archiveName - chatArchiveTime.value = archiveTime chatArchiveFile.value = archivePath return archivePath to archiveErrors } -private fun deleteOldArchive(m: ChatModel) { - val chatArchiveName = m.controller.appPrefs.chatArchiveName.get() +// Deprecated. Remove in the end of 2025. All unused archives should be deleted for the most users til then. +/** Remove [AppPreferences.chatArchiveName] and [AppPreferences.chatArchiveTime] as well */ +fun deleteOldChatArchive() { + val chatArchiveName = chatModel.controller.appPrefs.chatArchiveName.get() if (chatArchiveName != null) { - val file = File("${filesDir.absolutePath}${File.separator}$chatArchiveName") - val fileDeleted = file.delete() - if (fileDeleted) { - m.controller.appPrefs.chatArchiveName.set(null) - m.controller.appPrefs.chatArchiveTime.set(null) + val file1 = File("${filesDir.absolutePath}${File.separator}$chatArchiveName") + val file2 = File("${databaseExportDir.absolutePath}${File.separator}$chatArchiveName") + val fileDeleted = file1.delete() || file2.delete() + if (fileDeleted || (!file1.exists() && !file2.exists())) { + chatModel.controller.appPrefs.chatArchiveName.set(null) + chatModel.controller.appPrefs.chatArchiveTime.set(null) } else { Log.e(TAG, "deleteOldArchive file.delete() error") } } } -private fun importArchiveAlert( - m: ChatModel, - importedArchiveURI: URI, - appFilesCountAndSize: MutableState>, - progressIndicator: MutableState, - startChat: () -> Unit, -) { +private fun importArchiveAlert(onConfirm: () -> Unit, ) { AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.import_database_question), text = generalGetString(MR.strings.your_current_chat_database_will_be_deleted_and_replaced_with_the_imported_one), confirmText = generalGetString(MR.strings.import_database_confirmation), - onConfirm = { importArchive(m, importedArchiveURI, appFilesCountAndSize, progressIndicator, startChat) }, + onConfirm = onConfirm, destructive = true, ) } @@ -622,52 +642,51 @@ private fun archiveErrorsText(errs: List): String = "\n" + errs.ma } }.joinToString(separator = "\n") -private fun importArchive( - m: ChatModel, +suspend fun importArchive( importedArchiveURI: URI, appFilesCountAndSize: MutableState>, progressIndicator: MutableState, - startChat: () -> Unit, -) { +): Boolean { + val m = chatModel progressIndicator.value = true val archivePath = saveArchiveFromURI(importedArchiveURI) if (archivePath != null) { - withLongRunningApi { + try { + m.controller.apiDeleteStorage() + wallpapersDir.mkdirs() try { - m.controller.apiDeleteStorage() - wallpapersDir.mkdirs() - try { - val config = ArchiveConfig(archivePath, parentTempDirectory = databaseExportDir.toString()) - val archiveErrors = m.controller.apiImportArchive(config) - DatabaseUtils.ksDatabasePassword.remove() - appFilesCountAndSize.value = directoryFileCountAndSize(appFilesDir.absolutePath) - if (archiveErrors.isEmpty()) { - operationEnded(m, progressIndicator) { - AlertManager.shared.showAlertMsg(generalGetString(MR.strings.chat_database_imported), text = generalGetString(MR.strings.restart_the_app_to_use_imported_chat_database)) - } - if (chatModel.localUserCreated.value == false) { - chatModel.chatRunning.value = false - startChat() - } - } else { - operationEnded(m, progressIndicator) { - showArchiveImportedWithErrorsAlert(archiveErrors) - } - } - } catch (e: Error) { + val config = ArchiveConfig(archivePath, parentTempDirectory = databaseExportDir.toString()) + val archiveErrors = m.controller.apiImportArchive(config) + appPrefs.shouldImportAppSettings.set(true) + DatabaseUtils.ksDatabasePassword.remove() + appFilesCountAndSize.value = directoryFileCountAndSize(appFilesDir.absolutePath) + if (archiveErrors.isEmpty()) { operationEnded(m, progressIndicator) { - AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_importing_database), e.toString()) + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.chat_database_imported), text = generalGetString(MR.strings.restart_the_app_to_use_imported_chat_database)) + } + if (chatModel.localUserCreated.value == false) { + chatModel.chatRunning.value = false + } + } else { + operationEnded(m, progressIndicator) { + showArchiveImportedWithErrorsAlert(archiveErrors) } } + return true } catch (e: Error) { operationEnded(m, progressIndicator) { - AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_deleting_database), e.toString()) + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_importing_database), e.toString()) } - } finally { - File(archivePath).delete() } + } catch (e: Error) { + operationEnded(m, progressIndicator) { + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_deleting_database), e.toString()) + } + } finally { + File(archivePath).delete() } } + return false } private fun saveArchiveFromURI(importedArchiveURI: URI): String? { @@ -689,28 +708,26 @@ private fun saveArchiveFromURI(importedArchiveURI: URI): String? { } } -private fun deleteChatAlert(m: ChatModel, progressIndicator: MutableState) { +private fun deleteChatAlert(onConfirm: () -> Unit) { AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.delete_chat_profile_question), text = generalGetString(MR.strings.delete_chat_profile_action_cannot_be_undone_warning), confirmText = generalGetString(MR.strings.delete_verb), - onConfirm = { deleteChat(m, progressIndicator) }, + onConfirm = onConfirm, destructive = true, ) } -private fun deleteChat(m: ChatModel, progressIndicator: MutableState) { +private suspend fun deleteChat(m: ChatModel, progressIndicator: MutableState) { progressIndicator.value = true - withBGApi { - try { - deleteChatAsync(m) - operationEnded(m, progressIndicator) { - AlertManager.shared.showAlertMsg(generalGetString(MR.strings.chat_database_deleted), generalGetString(MR.strings.restart_the_app_to_create_a_new_chat_profile)) - } - } catch (e: Error) { - operationEnded(m, progressIndicator) { - AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_deleting_database), e.toString()) - } + try { + deleteChatAsync(m) + operationEnded(m, progressIndicator) { + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.chat_database_deleted), generalGetString(MR.strings.restart_the_app_to_create_a_new_chat_profile)) + } + } catch (e: Error) { + operationEnded(m, progressIndicator) { + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_deleting_database), e.toString()) } } } @@ -759,12 +776,12 @@ private fun afterSetCiTTL( } } -private fun deleteFilesAndMediaAlert(appFilesCountAndSize: MutableState>) { +private fun deleteFilesAndMediaAlert(onConfirm: () -> Unit) { AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.delete_files_and_media_question), text = generalGetString(MR.strings.delete_files_and_media_desc), confirmText = generalGetString(MR.strings.delete_verb), - onConfirm = { deleteFiles(appFilesCountAndSize) }, + onConfirm = onConfirm, destructive = true ) } @@ -789,18 +806,13 @@ private fun operationEnded(m: ChatModel, progressIndicator: MutableState.exportArchive() { withLongRunningApi { try { getMigrationTempFilesDirectory().mkdir() - val (archivePath, archiveErrors) = exportChatArchive(chatModel, getMigrationTempFilesDirectory(), mutableStateOf(""), mutableStateOf(Instant.DISTANT_PAST), mutableStateOf("")) + val (archivePath, archiveErrors) = exportChatArchive(chatModel, getMigrationTempFilesDirectory(), mutableStateOf("")) if (archiveErrors.isEmpty()) { uploadArchive(archivePath) } else { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt index 28ec77de70..f4f537aab7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt @@ -35,6 +35,7 @@ import kotlinx.datetime.Clock import kotlinx.datetime.toJavaInstant import kotlinx.serialization.* import java.io.File +import java.net.URI import java.text.SimpleDateFormat import java.util.* import kotlin.math.max @@ -180,7 +181,7 @@ private fun ModalData.SectionByState( ) { when (val s = migrationState.value) { null -> {} - is MigrationToState.PasteOrScanLink -> migrationState.PasteOrScanLinkView() + is MigrationToState.PasteOrScanLink -> migrationState.PasteOrScanLinkView(close) is MigrationToState.Onion -> OnionView(s.link, s.legacySocksProxy, s.networkProxy, s.hostMode, s.requiredHostMode, migrationState) is MigrationToState.DatabaseInit -> migrationState.DatabaseInitView(s.link, tempDatabaseFile, s.netCfg, s.networkProxy) is MigrationToState.LinkDownloading -> migrationState.LinkDownloadingView(s.link, s.ctrl, s.user, s.archivePath, tempDatabaseFile, chatReceiver, s.netCfg, s.networkProxy) @@ -195,18 +196,30 @@ private fun ModalData.SectionByState( } @Composable -private fun MutableState.PasteOrScanLinkView() { - if (appPlatform.isAndroid) { - SectionView(stringResource(MR.strings.scan_QR_code).replace('\n', ' ').uppercase()) { - QRCodeScanner(showQRCodeScanner = remember { mutableStateOf(true) }) { text -> - withBGApi { checkUserLink(text) } +private fun MutableState.PasteOrScanLinkView(close: () -> Unit) { + Box { + val progressIndicator = remember { mutableStateOf(false) } + Column { + if (appPlatform.isAndroid) { + SectionView(stringResource(MR.strings.scan_QR_code).replace('\n', ' ').uppercase()) { + QRCodeScanner(showQRCodeScanner = remember { mutableStateOf(true) }) { text -> + withBGApi { checkUserLink(text) } + } + } + SectionSpacer() + } + + SectionView(stringResource(if (appPlatform.isAndroid) MR.strings.or_paste_archive_link else MR.strings.paste_archive_link).uppercase()) { + PasteLinkView() + } + SectionSpacer() + + SectionView(stringResource(MR.strings.chat_archive).uppercase()) { + ArchiveImportView(progressIndicator, close) } } - SectionSpacer() - } - - SectionView(stringResource(if (appPlatform.isAndroid) MR.strings.or_paste_archive_link else MR.strings.paste_archive_link).uppercase()) { - PasteLinkView() + if (progressIndicator.value) + ProgressView() } } @@ -221,6 +234,31 @@ private fun MutableState.PasteLinkView() { } } +@Composable +private fun ArchiveImportView(progressIndicator: MutableState, close: () -> Unit) { + val importArchiveLauncher = rememberFileChooserLauncher(true) { to: URI? -> + if (to != null) { + withLongRunningApi { + val success = importArchive(to, mutableStateOf(0 to 0), progressIndicator) + if (success) { + startChat( + chatModel, + mutableStateOf(Clock.System.now()), + chatModel.chatDbChanged, + progressIndicator + ) + hideView(close) + } + } + } + } + SectionItemView({ + withLongRunningApi { importArchiveLauncher.launch("application/zip") } + }) { + Text(stringResource(MR.strings.import_database)) + } +} + @Composable private fun ModalData.OnionView(link: String, legacyLinkSocksProxy: String?, linkNetworkProxy: NetworkProxy?, hostMode: HostMode, requiredHostMode: Boolean, state: MutableState) { val onionHosts = remember { stateGetOrPut("onionHosts") { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt index f3d22e0cdf..ac6431f6fc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt @@ -109,7 +109,7 @@ fun SettingsLayout( SectionDividerSpaced() SectionView(stringResource(MR.strings.settings_section_title_chat_database)) { - DatabaseItem(encrypted, passphraseSaved, showSettingsModal { DatabaseView(it, showSettingsModal) }, stopped) + DatabaseItem(encrypted, passphraseSaved, showSettingsModal { DatabaseView() }, stopped) SettingsActionItem(painterResource(MR.images.ic_ios_share), stringResource(MR.strings.migrate_from_device_to_another_device), { withAuth(generalGetString(MR.strings.auth_open_migration_to_another_device), generalGetString(MR.strings.auth_log_in_using_credential)) { ModalManager.fullscreen.showCustomModal { close -> MigrateFromDeviceView(close) } } }, disabled = stopped) } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index d9d86634b1..b2cb0acc4b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -242,7 +242,6 @@ تغيير وضع التدمير الذاتي تغيير رمز المرور التدمير الذاتي تأكيد ترقيات قاعدة البيانات - أرشيف الدردشة الاتصال (دعوة مقدمة) مسح خطأ في إنشاء رابط المجموعة @@ -252,8 +251,6 @@ جار الاتصال… أرسلت طلب الاتصال! حُذفت قاعدة بيانات الدردشة - أرشيف الدردشة - نشأ في %1$s جارِ تغيير العنوان… جار الاتصال (قُبِل) فُحصت جهة الاتصال @@ -349,7 +346,6 @@ تختلف عبارة مرور قاعدة البيانات عن تلك المحفوظة في Keystore. خطأ في قاعدة البيانات ترقية قاعدة البيانات - حذف أرشيف الدردشة؟ حُددت %d جهة اتصال حذف المجموعة حذف المجموعة؟ @@ -372,7 +368,6 @@ أيام حذف العنوان سيتم تحديث عبارة مرور تعمية قاعدة البيانات. - حذف الأرشيف حذف الرابط؟ الرجوع إلى إصدار سابق من قاعدة البيانات قاعدة البيانات مُعمّاة باستخدام عبارة مرور عشوائية. يُرجى تغييره قبل التصدير. @@ -844,7 +839,6 @@ دليل المستخدم.]]> افتح ملفات تعريف الدردشة اسحب الوصول - حفظ الأرشيف كشف سيتم إيقاف استلام الملف. رفض diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 5f095fbf7c..6d0200256c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1263,6 +1263,7 @@ Your chat database RUN CHAT + Remote mobiles Chat is running Chat is stopped CHAT DATABASE @@ -1404,14 +1405,6 @@ Start chat? Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. - - Chat archive - CHAT ARCHIVE - Save archive - Delete archive - Created on %1$s - Delete chat archive? - invitation to group %1$s Join group? @@ -2292,6 +2285,7 @@ Or paste archive link Paste archive link Invalid link + Or import archive file Migrating Preparing download Downloading link details diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml index 54eb0e9034..042089e226 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml @@ -228,7 +228,6 @@ Базата данни на чат е импортирана Потвърди новата парола… Потвърди актуализаациите на базата данни - Архив на чата свързан промяна на адреса… промяна на адреса за %s… @@ -317,7 +316,6 @@ Промени режима на самоунищожение Промени кода за достъп за самоунищожение ЧАТОВЕ - АРХИВ НА ЧАТА промяна на адреса… В момента максималният поддържан размер на файла е %1$s. ID в базата данни @@ -341,9 +339,6 @@ Понижаване на версията на базата данни Актуализация на базата данни версията на базата данни е по-нова от приложението, но няма миграция надолу за: %s - Създаден на %1$s - Изтрий архив - Изтриване на архива на чата\? групата изтрита Контактът е проверен създател @@ -996,7 +991,6 @@ Режим на заключване Моля, докладвайте го на разработчиците. Защити екрана на приложението - Запази архив ЧЛЕН Премахване PING бройка diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml index 59c531f3d3..64a87736c4 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml @@ -303,8 +303,6 @@ Obnovte zálohu databáze Po obnovení zálohy databáze zadejte předchozí frázi. Tuto akci nelze vrátit zpět. Chat je zastaven - Chat se archivuje - Smazat archiv chatu? Připojit se ke skupině\? Připojte se na Opustit @@ -352,7 +350,6 @@ Databáze chatu importována Nová přístupová fráze… Uložte přístupovou frázi a otevřete chat - ARCHIV CHATU Nebyl vybrán žádný kontakt Snažíte se pozvat kontakt, se kterým jste sdíleli inkognito profil, do skupiny, ve které používáte svůj hlavní profil Skupina @@ -776,10 +773,7 @@ Chyba při obnovování databáze Přístupová fráze nebyla v klíčence nalezena, zadejte jej prosím ručně. K této situaci mohlo dojít, pokud jste obnovili data aplikace pomocí zálohovacího nástroje. Pokud tomu tak není, obraťte se na vývojáře. Chat můžete spustit v Nastavení / Databáze nebo restartováním aplikace. - Uložit archiv - Smazat archiv pozvánka do skupiny %1$s - Vytvořeno dne %1$s Jste zváni do skupiny. Připojte se k členům skupiny. Připojit se inkognito Připojit ke skupině diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index 912f3fb84e..75f6ac2c29 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -672,12 +672,6 @@ Der Chat wurde beendet Sie können den Chat über die App-Einstellungen/Datenbank oder durch Neustart der App starten. - Datenbank-Archiv - CHAT-ARCHIV - Archiv speichern - Archiv löschen - Erstellt am %1$s - Chat-Archiv löschen\? Einladung zur Gruppe %1$s Der Gruppe beitreten? diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml index 4b1d1b8838..179c7fec52 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml @@ -100,7 +100,6 @@ Δεν είναι δυνατή η προετοιμασία της βάσης δεδομένων Ένα νέο τυχαίο προφίλ θα μοιραστεί. Δεν είναι δυνατή η πρόσκληση επαφών! - Αρχείο συνομιλίας Αλλαγή διεύθυνσης λήψης Πιστοποίηση μη διαθέσιμη Αλλαγή @@ -112,7 +111,6 @@ %1$d αποτυχία κρυπτογράφησης μηνύματος αλλαγή διεύθυνσης για %s… Αλλαγή ρόλου ομάδας; - ΑΡΧΕΙΟ ΣΥΝΟΜΙΛΙΑΣ Δεν είναι δυνατή η πρόσκληση επαφής! Αυτόματη αποδοχή αιτήματος επαφής Κλήση… @@ -189,7 +187,6 @@ συνδέεται… Δημιουργία σύνδεσμο ομάδας Σύνδεση σε επιφάνεια εργασίας - Δημιουργήθηκε στις %1$s Συνδεδεμένο στο κινητό Σύνδεση μέσω σύνδεσμο Επαφές diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index 5ce86cd374..1cde9ed7c7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -191,7 +191,6 @@ conectando… Descentralizada La base de datos será cifrada. - ¿Eliminar archivo del chat\? Crear enlace de grupo Eliminar enlace ¿Eliminar perfil? @@ -215,8 +214,6 @@ %dd %d días ¿Eliminar archivos y multimedia\? - Creado: %1$s - Eliminar archivo conectado directa El contacto permite @@ -289,8 +286,6 @@ Llamada en curso ¿Cambiar contraseña de la base de datos\? No se puede acceder a Keystore para guardar la base de datos de contraseñas - Archivo del chat - ARCHIVOS DE CHAT Cancelar Cancelar mensaje en directo Confirmar @@ -662,7 +657,6 @@ Llamada pendiente Privacidad y Seguridad Guarda la contraseña de forma segura, NO podrás acceder al chat si la pierdes. - Guardar archivo Introduce la contraseña anterior después de restaurar la copia de seguridad de la base de datos. Esta acción no se puede deshacer. te ha expulsado Recibiendo vía diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml index 2fe4ec452e..dc4552a33e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml @@ -956,9 +956,6 @@ حذف خودکار پیام فعال شود؟ برگرداندن ارتقا و گشودن گپ - آرشیو گپ - ذخیره آرشیو - حذف آرشیو دعوت به گروه %1$s به گروه می‌پیوندید؟ ترک @@ -1092,9 +1089,6 @@ نسخه پایگاه داده از برنامه جدیدتر است، اما بدون جابه‌جایی تنزلی برای: %s جابه‌جایی متفاوت در برنامه/پایگاه داده: %s / %s جابه‌جایی‌ها: %s - آرشیو گپ - ایجاد شده در %1$s - آرشیو گپ حذف شود؟ شما به گروه دعوت شده‌اید. برای متصل شدن به اعضای گروه، به گروه بپیوندید. پیوستن به صورت ناشناس این گروه دیگر وجود ندارد. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml index c3ea7d89f7..682854f9dd 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml @@ -108,7 +108,6 @@ Tietoja SimpleX:stä Hajautettu Ääni pois päältä - ARKISTO Vaihda rooli Poista kaikilta Luodaan tyhjä chat-profiili annetulla nimellä, ja sovellus avautuu normaalisti. @@ -168,9 +167,6 @@ Tumma Tunnistautuminen epäonnistui Lisää esiasetettuja palvelimia - Arkisto - Poista keskusteluarkisto\? - Luotu %1$s poistettu ryhmä yhdistää yhdistäminen (hyväksytty) @@ -326,7 +322,6 @@ Tietokannan alentaminen Tietokannan päivitys Tietokanta salataan. - Poista arkisto yhdistäminen (esittelykutsu) Poistettu klo Muuta @@ -969,7 +964,6 @@ Turvallinen jono Tunnuslausetta ei löydy Keystoresta, kirjoita se manuaalisesti. Tämä on saattanut tapahtua, jos olet palauttanut sovelluksen tiedot varmuuskopiointityökalulla. Jos näin ei ole, ota yhteyttä kehittäjiin. Anna edellinen salasana tietokannan varmuuskopion palauttamisen jälkeen. Tätä toimintoa ei voi kumota. - Tallenna arkisto Tuonnin aikana tapahtui joitakin ei-vakavia virheitä – saatat nähdä Chat-konsolissa lisätietoja. Tietue päivitetty klo Moderoitu klo diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml index 8c88c1ea67..3bef77138e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml @@ -475,7 +475,6 @@ La phrase secrète n\'a pas été trouvée dans le Keystore, veuillez la saisir manuellement. Cela a pu se produire si vous avez restauré les données de l\'app à l\'aide d\'un outil de sauvegarde. Si ce n\'est pas le cas, veuillez contacter les développeurs. Veuillez entrer le mot de passe précédent après avoir restauré la sauvegarde de la base de données. Cette action ne peut pas être annulée. Erreur de restauration de la base de données - Créé le %1$s appel vidéo (chiffrement de bout en bout) appel audio (sans chiffrement) appel audio (chiffrement de bout en bout) @@ -653,11 +652,6 @@ Restaurer Le chat est arrêté Vous pouvez lancer le chat via les Paramètres / la Base de données de l\'app ou en la redémarrant. - Archives du chat - ARCHIVE DU CHAT - Enregistrer l\'archive - Supprimer l\'archive - Supprimer l\'archive du chat \? Invitation au groupe %1$s Rejoindre le groupe \? Vous êtes invité·e dans un groupe. Rejoignez le pour vous connecter avec ses membres. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hi/strings.xml index 9c283a98e9..9e0d476dc1 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hi/strings.xml @@ -117,7 +117,6 @@ नेटवर्क की स्थिति नया संपर्क अनुरोध सभी फाइलों को मिटा दें - संग्रह हटा देना नया डेटाबेस संग्रह नए सदस्य की भूमिका अधिसूचना सेवा @@ -129,7 +128,6 @@ अधिसूचना पूर्वावलोकन सूचनाएं सभी के लिए हटाएं - लिखचीत संग्रह हटा दे\? चैट प्रोफ़ाइल हटाएं\? चैट प्रोफ़ाइल हटाएं\? के लिए चैट प्रोफ़ाइल हटाएं @@ -218,9 +216,7 @@ कॉल समाप्त कॉल चल रहा है छवियों को स्वत: स्वीकार करें - चैट संग्रह चैट रोक दी गई है - चैट संग्रह आप इस समूह से संदेश प्राप्त करना बंद कर देंगे। चैट इतिहास संरक्षित किया जाएगा। %s की भूमिका को %s में बदला पूर्ण diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index 6fe624d621..f0355d51ac 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -262,7 +262,6 @@ Törlés az összes tagnál Hivatkozás létrehozása Csevegési beállítások - Csevegési archívum Profil törlése Jelenlegi jelkód kapcsolódás @@ -277,7 +276,6 @@ Hamarosan! cím megváltoztatása nála: %s … Csevegési adatbázis importálva - CSEVEGÉSI ARCHÍVUM Üzenetek törlése Kiürítés Bezárás gomb @@ -329,7 +327,6 @@ Fájlok törlése az összes csevegési profilból Sorbaállítás törlése Ismerős törlése - Létrehozva ekkor: %1$s cím megváltoztatása… Társítva a hordozható eszközhöz Jelenlegi jelmondat… @@ -413,7 +410,6 @@ Kézbesítés jelentések letiltása a csoportok számára? nap %d nap - Csevegési archívum törlése? Duplikált megjelenített név! Letiltás (felülírások megtartásával) Adatbázis fejlesztése @@ -451,7 +447,6 @@ Eszközök Látható a helyi hálózaton Ne engedélyezze - Archívum törlése Az eltűnő üzenetek küldése le van tiltva ebben a csevegésben. alapértelmezett (%s) duplikált üzenet @@ -998,7 +993,6 @@ %s: %s A SimpleX nem tud a háttérben futni. Csak akkor fog értesítéseket kapni, amikor az alkalmazás meg van nyitva. Túl sok kép! - Archívum mentése %s, %s és %d tag Csevegési szolgáltatás megállítása SimpleX-hivatkozások diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml index 29c6430839..dec20e2a36 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml @@ -450,7 +450,6 @@ Kode sandi aplikasi Kode sandi %s detik - Simpan arsip Ketuk untuk gabung diblokir %s Buka @@ -548,7 +547,6 @@ pesan buka Sistem - Arsip obrolan setel alamat kontak baru Tema gelap Tema @@ -862,7 +860,6 @@ Pesan suara tidak diizinkan Hapus alamat? Keluar dari grup? - ARSIP OBROLAN Rangkaian ini bukan tautan koneksi! Tempel tautan yang Anda terima Bagikan dengan kontak @@ -890,7 +887,6 @@ gandakan pesan hash pesan buruk Mulai obrolan? - Hapus arsip Jelajah dan gabung ke grup Perutean pesan pribadi 🚀 Lindungi alamat IP Anda dari relai pesan yang dipilih oleh kontak Anda.\nAktifkan di pengaturan *Jaringan & server*. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index f5334d4e61..e699714a6a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -277,7 +277,6 @@ Impossibile accedere al Keystore per salvare la password del database Impossibile invitare i contatti! Cambia ruolo - ARCHIVIO CHAT cambio indirizzo… Chat fermata in connessione (presentato) @@ -398,13 +397,9 @@ Funzionalità sperimentali Esporta database AIUTO - Archivio chat Chat fermata - Creato il %1$s Errore del database La password del database è diversa da quella salvata nel Keystore. - Elimina archivio - Eliminare l\'archivio della chat\? Database crittografato Inserisci la password giusta. Inserisci la password… @@ -745,7 +740,6 @@ Ripristina backup del database Ripristinare il backup del database\? Errore di ripristino del database - Salva archivio Salva la password e apri la chat Il tentativo di cambiare la password del database non è stato completato. Errore del database sconosciuto: %s diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml index d207057d0c..64f86a6ecb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml @@ -165,8 +165,6 @@ מסד הנתונים של הצ׳אט נמחק ‬מסד הנתונים של הצ׳אט יובא אשר שדרוגי מסד נתונים - ארכיון צ׳אט - ארכיון צ׳אט צ׳אט מופסק לא ניתן להזמין את אנשי הקשר! שונה תפקידך ל%s @@ -222,7 +220,6 @@ צור צור פרופיל יצירת הפרופיל שלך - נוצר ב־%1$s צור קישור קבוצה צור קישור יוצר הקבוצה @@ -253,7 +250,6 @@ צרו כתובת כדי לאפשר לאנשים להתחבר אליכם. מבוזר מסד הנתונים מוצפן באמצעות סיסמה אקראית. אנא שנו אותה לפני הייצוא. - למחוק ארכיון צ׳אט\? מחק פרופיל צ׳אט ברירת מחדל (%s) %d יום @@ -307,7 +303,6 @@ סיסמה וייצוא של מסד הנתונים מחק אחרי מחק את כל הקבצים - מחק ארכיון מחק עבורי מחק קישור למחוק פרופיל צ׳אט\? @@ -893,7 +888,6 @@ שחזור גיבוי מסד נתונים לשחזר גיבוי מסד נתונים\? שמור סיסמה ופתח את הצ׳אט - שמור ארכיון בחירת אנשי קשר קוד גישה להשמדה עצמית שניות diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml index cce95b5286..632e62ef09 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml @@ -77,8 +77,6 @@ 電池消費が最少:アプリがアクティブ時のみに通知が出ます(バックグラウンドサービス無し)。]]> 設定メニューにてオフにできます。 アプリがアクティブ時に通知が出ます。]]> あなたと連絡相手が送信済みメッセージを永久削除できます。(24時間) - チャットのアーカイブ - チャットのアーカイブを削除しますか? シークレットモードで参加 接続待ち (招待) 接続待ち (承諾済み) @@ -369,8 +367,6 @@ データベース暗号化のパスフレーズが更新されます。 チャットを開くにはデータベースパスフレーズが必要です。 ファイル: %s - 作成日時 %1$s - アーカイブを削除 参加 グループに参加しますか? グループに参加 @@ -551,7 +547,6 @@ 端末 送受信済みのファイルがありません メッセージを削除 - チャットのアーカイブ 接続中 あなたを除名しました。 グループのリンク @@ -921,7 +916,6 @@ データベースのエクスポート、読み込み、削除するにはチャット機能を停止する必要があります。チャット機能を停止すると送受信ができなくなります。 あなたのプロフィール、連絡先、メッセージ、ファイルが完全削除されます (※元に戻せません※)。 データベースパスフレーズを更新 - アーカイブを保存 あなたが自分の役割を次に変えました:%s アドレスを変えました %sのアドレスを変えました diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml index eab9df3b92..6af84d88c1 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml @@ -90,7 +90,6 @@ 주의: 암호를 분실하면 복구하거나 비밀번호 변경을 할 수 없어요.]]> 데이터베이스 암호를 바꾸시겠습니까? 새로운 암호 확인… - 채팅 기록 보관함 내 역할이 %s 역할로 변경됨 주소 바꾸는 중… 주소 바꾸는 중… @@ -179,7 +178,6 @@ 채팅 데이터베이스 대화 상대를 초대할 수 없습니다! 변경 - 채팅 기록 보관함 역할 변경 채팅 데이터베이스가 삭제됨 채팅이 멈춤 @@ -197,7 +195,6 @@ 대화 상대와 모든 메시지가 삭제됩니다. 이 결정은 되돌릴 수 없습니다! 대화 상대와 종단 간 암호화됨 대화 상대와 아직 연결되지 않았습니다! - %1$s에 생성 완료 비밀 그룹 생성 익명 수락 1개월 @@ -232,8 +229,6 @@ 데이터베이스 에러 데이터베이스 암호가 암호 저장소에 저장된 것과 일치하지 않습니다. 채팅을 열려면 데이터베이스 암호가 필요합니다. - 보관함 삭제 - 보관된 채팅을 삭제할까요\? %d 개의 대화 상대가 선택되었습니다. 데이터베이스 ID 다음 채팅 프로필 삭제 @@ -751,7 +746,6 @@ 저장하고 그룹 멤버들에게 알리기 저장하고 대화 상대에게 알리기 지우기 - 보관함 저장하기 암호 저장소에 암호 저장하기 데이터베이스 백업 복원하기 데이터베이스 백업을 복원한 후 이전 비밀번호를 입력해 주십시오. 이 결정은 되돌릴 수 없습니다. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml index bf50e67bb8..c3505460ae 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml @@ -125,8 +125,6 @@ Failai ir medija Ištrinti visus failus Ištrinti failus ir mediją\? - Ištrinti archyvą - Ištrinti pokalbio archyvą\? grupės profilis atnaujintas Grupė Ištrinti pokalbio profilį\? @@ -393,7 +391,6 @@ Žymėti kaip patvirtintą SimpleX užraktas Įrašyti WebRTC ICE serveriai bus pašalinti. - Įrašyti archyvą Siųsti tiesioginę žinutę Šalinti narį Šviesus @@ -834,7 +831,6 @@ Prašome įvesti praeitą slaptažodį po duomenų bazės atsarginės kopijos atstatymo. Šis veiksmas negali būti atšauktas. duomenų bazė naujesnė nei programėlė, bet nėra perkėlimo į senesnę versiją: %s skirtinga migracija programėlėje/duomenų bazėje: %s / %s - Pokalbio archyvas Grupė neaktyvi Gauta Užblokuota administratoriaus @@ -903,7 +899,6 @@ Atsitiktinė slaptafrazė yra saugoma nustatymuose kaip paprastas tekstas. \nJūs galite tai pakeisti vėliau. Pokalbiai veikia - POKALBIO ARCHYVAS Pokalbiai sustabdyti. Jei jau naudojote šią duomenų bazę kitame įrenginyje, turėtumėte perkelti ją atgal prieš pradedant pokalbius. užblokavo %s Keisti gavimo adresą @@ -1061,7 +1056,6 @@ Pokalbiai sustabdyti jungiamasi (priimtas) Kontaktas patikrintas - Sukurta %1$s Duomenų bazės slaptafrazė yra kitokia nei išsaugota raktų saugykloje. Duomenų bazė bus užšifruota ir slaptafrazė bus saugoma nustatymuose. Grupės pakvietimas nebegalioja, siuntėjas jį pašalino. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index 22112a8376..649f586620 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -24,7 +24,6 @@ Chat is actief Wissen CHAT DATABASE - CHAT ARCHIEF Chat console Chat database geïmporteerd Chat database verwijderd @@ -117,7 +116,6 @@ Chat is gestopt Controleert nieuwe berichten elke 10 minuten gedurende maximaal 1 minuut je rol gewijzigd in %s - Gesprek archief Wachtwoord database wijzigen\? Chat is gestopt Chat voorkeuren @@ -215,7 +213,6 @@ Huidige wachtwoord… Database versleuteld! is toegetreden - Gemaakt op %1$s compleet Wissen verbonden @@ -230,8 +227,6 @@ Contact personen kunnen berichten markeren voor verwijdering; u kunt ze wel bekijken. Donker standaard (%s) - Chat archief verwijderen\? - Archief verwijderen Verwijder contact\? Chatprofiel verwijderen? Verwijderen voor iedereen @@ -621,7 +616,6 @@ Video aan Deze actie kan niet ongedaan worden gemaakt. Uw profiel, contacten, berichten en bestanden gaan onomkeerbaar verloren. Deze instelling is van toepassing op berichten in uw huidige chatprofiel - Bewaar archief bijgewerkt groep profiel verwijderd Uw chatprofiel wordt verzonden naar de groepsleden diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml index 8056422e5f..e793ff2a72 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml @@ -601,14 +601,11 @@ Nieprawidłowe hasło bazy danych Nieprawidłowe hasło! Musisz wprowadzić hasło przy każdym uruchomieniu aplikacji - nie jest one przechowywane na urządzeniu. - Archiwum czatu - ARCHIWUM CZATU Czat jest zatrzymany Potwierdź aktualizacje bazy danych Obniż wersję bazy danych Aktualizacja bazy danych wersja bazy danych jest nowsza od aplikacji, ale nie ma migracji w dół dla: %s - Usuń archiwum różne migracje w aplikacji/bazy danych: %s / %s Obniż wersję i otwórz czat Grupa nieaktywna @@ -630,7 +627,6 @@ Przywróć kopię zapasową bazy danych Przywrócić kopię zapasową bazy danych\? Błąd przywracania bazy danych - Zapisz archiwum Próba zmiany hasła bazy danych nie została zakończona. Ta grupa już nie istnieje. Zaktualizuj i otwórz czat @@ -925,13 +921,11 @@ Kontakt i wszystkie wiadomości zostaną usunięte - nie można tego cofnąć! Błąd połączenia (UWIERZYTELNIANIE) Połącz się przez link / kod QR - Utworzony na %1$s Utwórz tajną grupę Utwórz tajną grupę Baza danych jest zaszyfrowana przy użyciu losowego hasła. Proszę zmienić je przed eksportem. %d dni Usuń - Usunąć archiwum czatu\? Usuń wiadomości po Znikające wiadomości są zabronione w tej grupie. Błąd usuwania prośby o kontakt diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml index d683d194ba..140b208db9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml @@ -57,7 +57,6 @@ Android Keystore é usada para armazenar a senha com segurança - permite que o serviço de notificação funcione. A Android Keystore será usada para armazenar a senha com segurança depois que você reiniciar o aplicativo ou alterar a senha - isso permitirá o recebimento de notificações. Não é possível acessar a Keystore para salvar a senha do banco de dados - ARQUIVO DE BATE-PAPO O bate-papo está parado Limpar Preferências de bate-papo @@ -81,7 +80,6 @@ O bate-papo está em execução O bate-papo está parado Alterar senha do banco de dados\? - Arquivo de chat endereço alterado para você Você e seu contato podem enviar mensagens temporárias. Backup de dados do aplicativo @@ -180,8 +178,6 @@ Confirmar nova senha… Senha atual… Senha do banco de dados é necessária para abrir o chat. - Excluir arquivo - Excluir arquivo de chat\? cargo alterado de %s para %s conectado Excluir link @@ -218,7 +214,6 @@ Erro de conexão (AUTH) conexão estabelecida conexão %1$d - Criado em %1$s Atualmente, o tamanho máximo de arquivo suportado é %1$s. Excluir Ssnha de criptografia do banco de dados será atualizada e armazenada na Keystore. @@ -685,7 +680,6 @@ Esta ação não pode ser desfeita - seu perfil, contatos, mensagens e arquivos serão irreversivelmente perdidos. Remover Senha do banco de dados incorreta - Salvar arquivo Senha não encontrada na Keystore, por favor digite-a manualmente. Isso pode ter ocorrido se você recuperou os dados do app usando uma ferramenta de backup. Se esse não é o caso, por favor, contate os desenvolvedores. Você se juntou a este grupo. Conectando-se a um membro convidado do grupo. Sair do grupo\? diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml index 548a495222..ee5b82d490 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml @@ -33,7 +33,6 @@ ÍCONE DA APLICAÇÃO 1 mês Mensagens - ARQUIVO DE CONVERSA Adicionar mensagem de boas-vindas Adicional perfil Apenas dados de perfil local @@ -135,11 +134,8 @@ Eliminar após Eliminar Eliminar - Arquivo de conversa Eliminar Eliminar todos os ficheiros - Eliminar arquivo - Eliminar arquivo de conversa\? Eliminar base de dados BASE DE DADOS DE CONVERSA Base de dados de conversa eliminada @@ -355,7 +351,6 @@ Contribuir Copiar Versão principal: v%s - Criado a %1$s Criar ligação de grupo Ligações de grupo conectando chamada… @@ -383,7 +378,6 @@ Salvar e atualizar o perfil do grupo Salvar Salvar senha e abrir conversa - Salvar arquivo Junte-se em modo anónimo Apagar ligação Endereço diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml index 498d3282de..7f8e0e2743 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml @@ -229,7 +229,6 @@ Salvezi servere? Apel respins Mesaj salvat - Salvează arhiva Repornește aplicația pentru a crea un nou profil Salvează fraza de acces în Keystore Salvează profilul grupului @@ -441,7 +440,6 @@ Baza ta de date a conversațiilor Baza ta de date a conversațiilor nu este criptată - setează frază de acces pentru a o proteja. Fraza de acces de criptare a bazei de date va fi actualizată și stocată în setări. - Creat pe %1$s ai schimbat rolul %s la %s Nu se pot invita contactele! Te-ai alăturat grupului @@ -595,7 +593,6 @@ Consolă conversație Confirmați parola colorat - Arhivă conversație Toate contactele, conversațiile și fișierele dumneavoastră vor fi encriptate într-un mod sigur și încărcate pe bucăți pe releurile XFTP configurate. Rugat să primească videoclipul Comparați fișierul @@ -616,7 +613,6 @@ Vă rugăm să rețineți: folosind aceeași bază de date pe două dispozitive, va intrerupe decripția mesajelor de la conexiunile dumneavoastră, ca protecție de securitate.]]> Confirmați încărcarea Confirmați că țineți minte parola de la baza de date pentru a o migra. - ARHIVĂ CONVERSAȚIE Conexiune Ștergi profilul de conversație? Șterge pentru mine @@ -626,7 +622,6 @@ Șters la Versiunea aplicației desktop %s nu este compatibilă cu această aplicație. Ștergi mesajul membrului? - Șterge arhiva Șterge grup Șterge și notifică contactele Decentralizat @@ -652,7 +647,6 @@ Șterge Șterge Ștergi fișiere și media? - Ștergi arhiva conversației? %d zile %d zi Șterge adresa diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml index aa9856eb9f..10d1de26ae 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -676,12 +676,6 @@ Чат остановлен Вы можете запустить чат через Настройки приложения или перезапустив приложение. - Архив чата - АРХИВ ЧАТА - Сохранить архив - Удалить архив - Дата создания %1$s - Удалить архив чата? приглашение в группу %1$s Вступить в группу? diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml index 03bf9f0f27..c1f88cf3b1 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml @@ -160,7 +160,6 @@ หมดเวลาการเชื่อมต่อ เชื่อมต่อผ่านลิงค์กลุ่ม\? ผู้ติดต่อและข้อความทั้งหมดจะถูกลบ - ไม่สามารถยกเลิกได้! - สร้างเมื่อ %1$s ขนาดไฟล์สูงสุดที่รองรับในปัจจุบันคือ %1$s ธีมที่กำหนดเอง ID ฐานข้อมูลและตัวเลือกการแยกการส่งผ่าน @@ -239,8 +238,6 @@ ความผิดพลาดในฐานข้อมูล ยืนยันการอัพเกรดฐานข้อมูล ดาวน์เกรดฐานข้อมูล - ที่เก็บแชทถาวร - ที่เก็บแชทถาวร การแชทหยุดทํางานแล้ว เชื่อมต่อสำเร็จ กำลังเปลี่ยนที่อยู่… @@ -432,8 +429,6 @@ เวอร์ชันฐานข้อมูลใหม่กว่าแอป แต่ไม่มีการย้ายข้อมูลลงสำหรับ: %s การย้ายข้อมูลที่แตกต่างกันในแอป/ฐานข้อมูล: %s / %s ปรับลดรุ่นและเปิดแชท - ลบที่เก็บถาวร - ลบที่เก็บแชทถาวร\? กลุ่มที่ไม่ได้ใช้งาน ไม่พบกลุ่ม! คำเชิญเข้าร่วมกลุ่มหมดอายุแล้ว @@ -837,7 +832,6 @@ คืนค่า คืนค่าฐานข้อมูลสำรองไหม\? กู้คืนข้อผิดพลาดของฐานข้อมูล - บันทึกไฟล์เก็บถาวร ลบแล้ว %1$s ลบคุณออกแล้ว ถูกลบแล้ว diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml index 30afe21e50..9dcc45e6c4 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml @@ -106,7 +106,6 @@ Veri tabanı yedeğini geri yükle\? Veri tabanını geri yüklerken hata Veritabanı sürüm düşürme - Arşivi kaydet %s (mevcut) Kaydet ve grup profilini güncelle Karşılama mesajı kaydedilsin mi? @@ -429,9 +428,6 @@ Veri tabanı, rastgele bir parola ile şifrelendi. Dışa aktarmadan önce lütfen değiştir. Dosyaları ve medyayı sil\? Veri tabanı şifrelenecektir. - %1$s tarihinde oluşturuldu - Belgeliği sil - Konuşma belgeliğini sil\? %s üyesinin yetkisi %s olarak değiştirildi silinmiş grup kendi yetkini, %s olarak değiştirdin @@ -740,8 +736,6 @@ kişi uçtan uca şifrelemeye sahip değildir Sohbet durduruldu Aklınızda bulunsun: kaybederseniz, parolayı kurtaramaz veya değiştiremezsiniz.]]> - Sohbet arşivi - SOHBET ARŞİVİ %1$s grubuna davet Gruba katıl\? %1$s davet edildi diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index 604fd1fa1a..732f85e473 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -294,8 +294,6 @@ Зашифрувати базу даних\? Неправильна ключова фраза! Введіть правильну ключову фразу. - Архів чату - АРХІВ ЧАТУ підключив(лась) змінив(ла) вашу роль на %s ви змінили свою роль на %s @@ -375,7 +373,6 @@ Виклики на екрані блокування: від абонента до абонента Завершити дзвінок - Створено %1$s Розгорнути вибір ролі так Налаштування контакту @@ -528,7 +525,6 @@ Відео увімкнено Це може трапитися, якщо ви або ваше з\'єднання використовували застарілу резервну копію бази даних. Відновити резервну копію бази даних - Зберегти архів запрошення до групи %1$s Вас запрошено в групу. Приєднуйтесь, щоб спілкуватися з учасниками групи. Реакції на повідомлення @@ -1070,8 +1066,6 @@ Для відкриття чату потрібна ключова фраза бази даних. Приєднатися до групи\? Оновлення бази даних - Видалити архів - Видалити архів чату\? Вийти з групи? Групу не знайдено! Ця група більше не існує. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml index eb19247a12..d12db70de6 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml @@ -226,8 +226,6 @@ Thay đổi quyền hạn đang thay đổi địa chỉ… đang thay đổi địa chỉ… - KHO LƯU TRỮ SIMPLEX CHAT - Kho lưu trữ SimpleX Chat Bảng điều khiển trò chuyện Ứng dụng SimpleX Chat đang hoạt động Cơ sở dữ liệu SimpleX Chat đã bị xóa @@ -361,7 +359,6 @@ Tạo liên kết Được tạo ra tại Tạo địa chỉ - Được tạo ra vào %1$s Được tạo ra tại: %s Tạo hồ sơ trò chuyện Tạo nhóm @@ -437,9 +434,7 @@ Xóa cơ sở dữ liệu Xóa tất cả các tệp Xóa tệp và đa phương tiện? - Xóa kho lữu trữ mặc định (%s) - Xóa kho lữu trữ SimpleX Chat? đã xóa nhóm Xóa tệp Xóa liên hệ? diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index eefd310fd0..ed60383eb1 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -47,7 +47,6 @@ 删除 删除地址? 在此后删除 - 删除档案 已删除 删除文件和媒体文件? 为所有人删除 @@ -81,7 +80,6 @@ 允许向成员发送私信。 允许发送限时消息。 删除地址 - 删除聊天档案? 删除聊天资料? 删除联系人 删除联系人? @@ -161,8 +159,6 @@ 聊天数据库已删除 聊天数据库已导入 钥匙串错误 - 聊天档案 - 聊天档案 聊天控制台 聊天数据库 聊天已停止 @@ -719,7 +715,6 @@ 回复 重置为默认 运行聊天程序 - 保存存档 扫码 从您联系人的应用程序中扫描安全码。 安全码 @@ -873,7 +868,6 @@ 接收消息,您的联系人 - 您用来向他们发送消息的服务器。]]> 您将在组主设备上线时连接到该群组,请稍等或稍后再检查! 当您启动应用或在应用程序驻留后台超过30 秒后,您将需要进行身份验证。 - 创建于 %1$s 连接到 SimpleX Chat 开发者提出任何问题并接收更新 。]]> 您已接受连接 您的 SMP 服务器 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml index 17cc45334e..58372bfa7a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml @@ -632,7 +632,6 @@ %s 秒(s) 加密數據庫時出錯 在金鑰庫儲存密碼 - 建立於 %1$s 還原數據庫的備份 群組為不活躍狀態 邀請連結過時! @@ -700,8 +699,6 @@ 還原 還原數據庫的備份? 還原數據庫時出錯 - 儲存存檔 - 刪除存檔 加入 確定要加入群組? 加入匿名聊天模式 @@ -777,7 +774,6 @@ 匯出數據庫時出錯 匯入數據庫時出錯 受加密的數據庫密碼會再次更新。 - 刪除封存對話? 加密數據庫? 邀請至群組 %1$s 邀請成員 @@ -799,13 +795,11 @@ 對話沒有經過端對端加密 數據庫已加密! 已加密數據庫 - 封存對話 群組資料已經更新 成員 你:%1$s 刪除群組 即時訊息 - 封存對話 移除成員時出錯 修改身份時出錯 群組 diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt index 38d87fc497..d425d81875 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt @@ -3,6 +3,7 @@ package chat.simplex.common.platform import chat.simplex.common.model.* import chat.simplex.common.simplexWindowState import chat.simplex.common.views.call.RcvCallInvitation +import chat.simplex.common.views.database.deleteOldChatArchive import chat.simplex.common.views.helpers.* import java.util.* import chat.simplex.res.MR @@ -30,6 +31,7 @@ fun initApp() { override fun showMessage(title: String, text: String) = chat.simplex.common.model.NtfManager.showMessage(title, text) } applyAppLocale() + deleteOldChatArchive() if (DatabaseUtils.ksSelfDestructPassword.get() == null) { initChatControllerOnStart() } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/database/DatabaseView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/database/DatabaseView.desktop.kt index 889ac98e2d..9ed7170a31 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/database/DatabaseView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/database/DatabaseView.desktop.kt @@ -9,14 +9,14 @@ import kotlinx.datetime.Instant actual fun restartChatOrApp() { if (chatModel.chatRunning.value == false) { chatModel.chatDbChanged.value = true - startChat(chatModel, mutableStateOf(Instant.DISTANT_PAST), chatModel.chatDbChanged) + startChat(chatModel, mutableStateOf(Instant.DISTANT_PAST), chatModel.chatDbChanged, mutableStateOf(false)) } else { authStopChat(chatModel) { withBGApi { // adding delay in order to prevent locked database by previous initialization delay(1000) chatModel.chatDbChanged.value = true - startChat(chatModel, mutableStateOf(Instant.DISTANT_PAST), chatModel.chatDbChanged) + startChat(chatModel, mutableStateOf(Instant.DISTANT_PAST), chatModel.chatDbChanged, mutableStateOf(false)) } } } From e9853fe3fcc2f97f63ee22b98451efcf6d6e58a6 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 30 Nov 2024 18:06:36 +0000 Subject: [PATCH 138/567] ios: update alert message for SimpleX address card --- .../Onboarding/AddressCreationCard.swift | 2 +- .../bg.xcloc/Localized Contents/bg.xliff | 57 +++--------------- .../cs.xcloc/Localized Contents/cs.xliff | 57 +++--------------- .../de.xcloc/Localized Contents/de.xliff | 57 +++--------------- .../en.xcloc/Localized Contents/en.xliff | 60 ++++--------------- .../es.xcloc/Localized Contents/es.xliff | 57 +++--------------- .../fi.xcloc/Localized Contents/fi.xliff | 57 +++--------------- .../fr.xcloc/Localized Contents/fr.xliff | 57 +++--------------- .../hu.xcloc/Localized Contents/hu.xliff | 57 +++--------------- .../it.xcloc/Localized Contents/it.xliff | 57 +++--------------- .../ja.xcloc/Localized Contents/ja.xliff | 57 +++--------------- .../nl.xcloc/Localized Contents/nl.xliff | 57 +++--------------- .../pl.xcloc/Localized Contents/pl.xliff | 57 +++--------------- .../ru.xcloc/Localized Contents/ru.xliff | 57 +++--------------- .../th.xcloc/Localized Contents/th.xliff | 57 +++--------------- .../tr.xcloc/Localized Contents/tr.xliff | 57 +++--------------- .../uk.xcloc/Localized Contents/uk.xliff | 57 +++--------------- .../Localized Contents/zh-Hans.xliff | 57 +++--------------- apps/ios/bg.lproj/Localizable.strings | 27 --------- apps/ios/cs.lproj/Localizable.strings | 27 --------- apps/ios/de.lproj/Localizable.strings | 27 --------- apps/ios/es.lproj/Localizable.strings | 27 --------- apps/ios/fi.lproj/Localizable.strings | 27 --------- apps/ios/fr.lproj/Localizable.strings | 27 --------- apps/ios/hu.lproj/Localizable.strings | 27 --------- apps/ios/it.lproj/Localizable.strings | 27 --------- apps/ios/ja.lproj/Localizable.strings | 27 --------- apps/ios/nl.lproj/Localizable.strings | 27 --------- apps/ios/pl.lproj/Localizable.strings | 27 --------- apps/ios/ru.lproj/Localizable.strings | 27 --------- apps/ios/th.lproj/Localizable.strings | 27 --------- apps/ios/tr.lproj/Localizable.strings | 27 --------- apps/ios/uk.lproj/Localizable.strings | 27 --------- apps/ios/zh-Hans.lproj/Localizable.strings | 27 --------- 34 files changed, 139 insertions(+), 1267 deletions(-) diff --git a/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift b/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift index c757dcfeeb..2069ca9487 100644 --- a/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift +++ b/apps/ios/Shared/Views/Onboarding/AddressCreationCard.swift @@ -62,7 +62,7 @@ struct AddressCreationCard: View { .alert(isPresented: $showAddressCreationAlert) { Alert( title: Text("SimpleX address"), - message: Text("You can create it in user picker."), + message: Text("Tap Create SimpleX address in the menu to create it later."), dismissButton: .default(Text("Ok")) { withAnimation { addressCreationCardShown = true diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 597041e163..2964742c85 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -1318,11 +1318,6 @@ Change user profiles authentication reason - - Chat archive - Архив на чата - No comment provided by engineer. - Chat colors No comment provided by engineer. @@ -1895,11 +1890,6 @@ This is your own one-time link! Създаден на: %@ copied message info - - Created on %@ - Създаден на %@ - No comment provided by engineer. - Creating archive link Създаване на архивен линк @@ -2108,16 +2098,6 @@ This is your own one-time link! Изтрий и уведоми контакт No comment provided by engineer. - - Delete archive - Изтрий архив - No comment provided by engineer. - - - Delete chat archive? - Изтриване на архива на чата? - No comment provided by engineer. - Delete chat profile Изтрий чат профила @@ -2794,11 +2774,6 @@ This is your own one-time link! Грешка при приемане на заявка за контакт No comment provided by engineer. - - Error accessing database file - Грешка при достъпа до файла с базата данни - No comment provided by engineer. - Error adding member(s) Грешка при добавяне на член(ове) @@ -4533,11 +4508,6 @@ This is your link for group %@! Нов контакт: notification - - New database archive - Нов архив на база данни - No comment provided by engineer. - New desktop app! Ново настолно приложение! @@ -4746,11 +4716,6 @@ This is your link for group %@! Стара база данни No comment provided by engineer. - - Old database archive - Стар архив на база данни - No comment provided by engineer. - One-time invitation link Линк за еднократна покана @@ -4900,6 +4865,10 @@ Requires compatible VPN. Operator server alert title + + Or import archive file + No comment provided by engineer. + Or paste archive link Или постави архивен линк @@ -5741,11 +5710,6 @@ Enable in *Network & servers* settings. Запази и актуализирай профила на групата No comment provided by engineer. - - Save archive - Запази архив - No comment provided by engineer. - Save group profile Запази профила на групата @@ -6519,11 +6483,6 @@ Enable in *Network & servers* settings. Спри чата No comment provided by engineer. - - Stop chat to enable database actions - Спрете чата, за да активирате действията с базата данни - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. Спрете чата, за да експортирате, импортирате или изтриете чат базата данни. Няма да можете да получавате и изпращате съобщения, докато чатът е спрян. @@ -6641,6 +6600,10 @@ Enable in *Network & servers* settings. Направи снимка No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + No comment provided by engineer. + Tap button Докосни бутона @@ -7695,10 +7658,6 @@ Repeat join request? You can configure servers via settings. No comment provided by engineer. - - You can create it in user picker. - No comment provided by engineer. - You can create it later Можете да го създадете по-късно diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index e2e77572c5..bf9436afe3 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -1277,11 +1277,6 @@ Change user profiles authentication reason - - Chat archive - Chat se archivuje - No comment provided by engineer. - Chat colors No comment provided by engineer. @@ -1828,11 +1823,6 @@ This is your own one-time link! Created at: %@ copied message info - - Created on %@ - Vytvořeno na %@ - No comment provided by engineer. - Creating archive link No comment provided by engineer. @@ -2037,16 +2027,6 @@ This is your own one-time link! Delete and notify contact No comment provided by engineer. - - Delete archive - Smazat archiv - No comment provided by engineer. - - - Delete chat archive? - Smazat archiv chatu? - No comment provided by engineer. - Delete chat profile Smazat chat profil @@ -2702,11 +2682,6 @@ This is your own one-time link! Chyba při přijímání žádosti o kontakt No comment provided by engineer. - - Error accessing database file - Chyba přístupu k souboru databáze - No comment provided by engineer. - Error adding member(s) Chyba přidávání člena(ů) @@ -4377,11 +4352,6 @@ This is your link for group %@! Nový kontakt: notification - - New database archive - Archiv nové databáze - No comment provided by engineer. - New desktop app! Nová desktopová aplikace! @@ -4587,11 +4557,6 @@ This is your link for group %@! Stará databáze No comment provided by engineer. - - Old database archive - Archiv staré databáze - No comment provided by engineer. - One-time invitation link Jednorázový zvací odkaz @@ -4738,6 +4703,10 @@ Vyžaduje povolení sítě VPN. Operator server alert title + + Or import archive file + No comment provided by engineer. + Or paste archive link No comment provided by engineer. @@ -5551,11 +5520,6 @@ Enable in *Network & servers* settings. Uložit a aktualizovat profil skupiny No comment provided by engineer. - - Save archive - Uložit archiv - No comment provided by engineer. - Save group profile Uložení profilu skupiny @@ -6312,11 +6276,6 @@ Enable in *Network & servers* settings. Stop chat No comment provided by engineer. - - Stop chat to enable database actions - Zastavte chat pro povolení akcí databáze - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. Zastavení chatu pro export, import nebo smazání databáze chatu. Během zastavení chatu nebudete moci přijímat a odesílat zprávy. @@ -6433,6 +6392,10 @@ Enable in *Network & servers* settings. Vyfotit No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + No comment provided by engineer. + Tap button Klepněte na tlačítko @@ -7435,10 +7398,6 @@ Repeat join request? You can configure servers via settings. No comment provided by engineer. - - You can create it in user picker. - No comment provided by engineer. - You can create it later Můžete vytvořit později diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 4d1508dce3..6a92589851 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -1359,11 +1359,6 @@ Change user profiles authentication reason - - Chat archive - Datenbank Archiv - No comment provided by engineer. - Chat colors Chat-Farben @@ -1965,11 +1960,6 @@ Das ist Ihr eigener Einmal-Link! Erstellt um: %@ copied message info - - Created on %@ - Erstellt am %@ - No comment provided by engineer. - Creating archive link Archiv-Link erzeugen @@ -2184,16 +2174,6 @@ Das ist Ihr eigener Einmal-Link! Kontakt löschen und benachrichtigen No comment provided by engineer. - - Delete archive - Archiv löschen - No comment provided by engineer. - - - Delete chat archive? - Chat Archiv löschen? - No comment provided by engineer. - Delete chat profile Chat-Profil löschen @@ -2891,11 +2871,6 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Annehmen der Kontaktanfrage No comment provided by engineer. - - Error accessing database file - Fehler beim Zugriff auf die Datenbankdatei - No comment provided by engineer. - Error adding member(s) Fehler beim Hinzufügen von Mitgliedern @@ -4690,11 +4665,6 @@ Das ist Ihr Link für die Gruppe %@! Neuer Kontakt: notification - - New database archive - Neues Datenbankarchiv - No comment provided by engineer. - New desktop app! Neue Desktop-App! @@ -4910,11 +4880,6 @@ Das ist Ihr Link für die Gruppe %@! Alte Datenbank No comment provided by engineer. - - Old database archive - Altes Datenbankarchiv - No comment provided by engineer. - One-time invitation link Einmal-Einladungslink @@ -5065,6 +5030,10 @@ Dies erfordert die Aktivierung eines VPNs. Operator server alert title + + Or import archive file + No comment provided by engineer. + Or paste archive link Oder fügen Sie den Archiv-Link ein @@ -5948,11 +5917,6 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Gruppen-Profil sichern und aktualisieren No comment provided by engineer. - - Save archive - Archiv speichern - No comment provided by engineer. - Save group profile Gruppenprofil speichern @@ -6770,11 +6734,6 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Chat beenden No comment provided by engineer. - - Stop chat to enable database actions - Chat beenden, um Datenbankaktionen zu erlauben - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. Beenden Sie den Chat, um die Chat-Datenbank zu exportieren, zu importieren oder zu löschen. Solange der Chat angehalten ist, können Sie keine Nachrichten empfangen oder senden. @@ -6900,6 +6859,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Machen Sie ein Foto No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + No comment provided by engineer. + Tap button Schaltfläche antippen @@ -7989,10 +7952,6 @@ Verbindungsanfrage wiederholen? You can configure servers via settings. No comment provided by engineer. - - You can create it in user picker. - No comment provided by engineer. - You can create it later Sie können dies später erstellen diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 9973cfeeba..09a63ab3c4 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -1372,11 +1372,6 @@ Change user profiles authentication reason - - Chat archive - Chat archive - No comment provided by engineer. - Chat colors Chat colors @@ -1991,11 +1986,6 @@ This is your own one-time link! Created at: %@ copied message info - - Created on %@ - Created on %@ - No comment provided by engineer. - Creating archive link Creating archive link @@ -2211,16 +2201,6 @@ This is your own one-time link! Delete and notify contact No comment provided by engineer. - - Delete archive - Delete archive - No comment provided by engineer. - - - Delete chat archive? - Delete chat archive? - No comment provided by engineer. - Delete chat profile Delete chat profile @@ -2922,11 +2902,6 @@ This is your own one-time link! Error accepting contact request No comment provided by engineer. - - Error accessing database file - Error accessing database file - No comment provided by engineer. - Error adding member(s) Error adding member(s) @@ -4735,11 +4710,6 @@ This is your link for group %@! New contact: notification - - New database archive - New database archive - No comment provided by engineer. - New desktop app! New desktop app! @@ -4964,11 +4934,6 @@ This is your link for group %@! Old database No comment provided by engineer. - - Old database archive - Old database archive - No comment provided by engineer. - One-time invitation link One-time invitation link @@ -5123,6 +5088,11 @@ Requires compatible VPN. Operator server alert title + + Or import archive file + Or import archive file + No comment provided by engineer. + Or paste archive link Or paste archive link @@ -6013,11 +5983,6 @@ Enable in *Network & servers* settings. Save and update group profile No comment provided by engineer. - - Save archive - Save archive - No comment provided by engineer. - Save group profile Save group profile @@ -6846,11 +6811,6 @@ Enable in *Network & servers* settings. Stop chat No comment provided by engineer. - - Stop chat to enable database actions - Stop chat to enable database actions - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. @@ -6976,6 +6936,11 @@ Enable in *Network & servers* settings. Take picture No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + Tap Create SimpleX address in the menu to create it later. + No comment provided by engineer. + Tap button Tap button @@ -8084,11 +8049,6 @@ Repeat join request? You can configure servers via settings. No comment provided by engineer. - - You can create it in user picker. - You can create it in user picker. - No comment provided by engineer. - You can create it later You can create it later diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 01e534f424..8d109187c2 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -1359,11 +1359,6 @@ Change user profiles authentication reason - - Chat archive - Archivo del chat - No comment provided by engineer. - Chat colors Colores del chat @@ -1965,11 +1960,6 @@ This is your own one-time link! Creado: %@ copied message info - - Created on %@ - Creado en %@ - No comment provided by engineer. - Creating archive link Creando enlace al archivo @@ -2184,16 +2174,6 @@ This is your own one-time link! Eliminar y notificar contacto No comment provided by engineer. - - Delete archive - Eliminar archivo - No comment provided by engineer. - - - Delete chat archive? - ¿Eliminar archivo del chat? - No comment provided by engineer. - Delete chat profile Eliminar perfil @@ -2891,11 +2871,6 @@ This is your own one-time link! Error al aceptar solicitud del contacto No comment provided by engineer. - - Error accessing database file - Error al acceder al archivo de la base de datos - No comment provided by engineer. - Error adding member(s) Error al añadir miembro(s) @@ -4690,11 +4665,6 @@ This is your link for group %@! Contacto nuevo: notification - - New database archive - Nuevo archivo de bases de datos - No comment provided by engineer. - New desktop app! Nueva aplicación para PC! @@ -4909,11 +4879,6 @@ This is your link for group %@! Base de datos antigua No comment provided by engineer. - - Old database archive - Archivo de bases de datos antiguas - No comment provided by engineer. - One-time invitation link Enlace de invitación de un solo uso @@ -5064,6 +5029,10 @@ Requiere activación de la VPN. Operator server alert title + + Or import archive file + No comment provided by engineer. + Or paste archive link O pegar enlace del archivo @@ -5947,11 +5916,6 @@ Actívalo en ajustes de *Servidores y Redes*. Guardar y actualizar perfil del grupo No comment provided by engineer. - - Save archive - Guardar archivo - No comment provided by engineer. - Save group profile Guardar perfil de grupo @@ -6769,11 +6733,6 @@ Actívalo en ajustes de *Servidores y Redes*. Parar SimpleX No comment provided by engineer. - - Stop chat to enable database actions - Para habilitar las acciones sobre la base de datos, debes parar SimpleX - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. Para poder exportar, importar o eliminar la base de datos primero debes parar SimpleX. Mientras tanto no podrás recibir ni enviar mensajes. @@ -6899,6 +6858,10 @@ Actívalo en ajustes de *Servidores y Redes*. Tomar foto No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + No comment provided by engineer. + Tap button Pulsa el botón @@ -7988,10 +7951,6 @@ Repeat join request? You can configure servers via settings. No comment provided by engineer. - - You can create it in user picker. - No comment provided by engineer. - You can create it later Puedes crearla más tarde diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index fa5b2967e3..325732cb8d 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -1270,11 +1270,6 @@ Change user profiles authentication reason - - Chat archive - Chat-arkisto - No comment provided by engineer. - Chat colors No comment provided by engineer. @@ -1821,11 +1816,6 @@ This is your own one-time link! Created at: %@ copied message info - - Created on %@ - Luotu %@ - No comment provided by engineer. - Creating archive link No comment provided by engineer. @@ -2030,16 +2020,6 @@ This is your own one-time link! Delete and notify contact No comment provided by engineer. - - Delete archive - Poista arkisto - No comment provided by engineer. - - - Delete chat archive? - Poista keskusteluarkisto? - No comment provided by engineer. - Delete chat profile Poista keskusteluprofiili @@ -2694,11 +2674,6 @@ This is your own one-time link! Virhe kontaktipyynnön hyväksymisessä No comment provided by engineer. - - Error accessing database file - Virhe tietokantatiedoston käyttämisessä - No comment provided by engineer. - Error adding member(s) Virhe lisättäessä jäseniä @@ -4367,11 +4342,6 @@ This is your link for group %@! Uusi kontakti: notification - - New database archive - Uusi tietokanta-arkisto - No comment provided by engineer. - New desktop app! No comment provided by engineer. @@ -4576,11 +4546,6 @@ This is your link for group %@! Vanha tietokanta No comment provided by engineer. - - Old database archive - Vanha tietokanta-arkisto - No comment provided by engineer. - One-time invitation link Kertakutsulinkki @@ -4726,6 +4691,10 @@ Edellyttää VPN:n sallimista. Operator server alert title + + Or import archive file + No comment provided by engineer. + Or paste archive link No comment provided by engineer. @@ -5539,11 +5508,6 @@ Enable in *Network & servers* settings. Tallenna ja päivitä ryhmäprofiili No comment provided by engineer. - - Save archive - Tallenna arkisto - No comment provided by engineer. - Save group profile Tallenna ryhmäprofiili @@ -6298,11 +6262,6 @@ Enable in *Network & servers* settings. Stop chat No comment provided by engineer. - - Stop chat to enable database actions - Pysäytä keskustelu tietokantatoimien mahdollistamiseksi - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. Pysäytä keskustelut viedäksesi, tuodaksesi tai poistaaksesi keskustelujen tietokannan. Et voi vastaanottaa ja lähettää viestejä, kun keskustelut on pysäytetty. @@ -6419,6 +6378,10 @@ Enable in *Network & servers* settings. Ota kuva No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + No comment provided by engineer. + Tap button Napauta painiketta @@ -7420,10 +7383,6 @@ Repeat join request? You can configure servers via settings. No comment provided by engineer. - - You can create it in user picker. - No comment provided by engineer. - You can create it later Voit luoda sen myöhemmin diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index ecea1c6eb7..56c57a2237 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -1359,11 +1359,6 @@ Change user profiles authentication reason - - Chat archive - Archives du chat - No comment provided by engineer. - Chat colors Couleurs de chat @@ -1965,11 +1960,6 @@ Il s'agit de votre propre lien unique ! Créé à : %@ copied message info - - Created on %@ - Créé le %@ - No comment provided by engineer. - Creating archive link Création d'un lien d'archive @@ -2184,16 +2174,6 @@ Il s'agit de votre propre lien unique ! Supprimer et en informer le contact No comment provided by engineer. - - Delete archive - Supprimer l'archive - No comment provided by engineer. - - - Delete chat archive? - Supprimer l'archive du chat ? - No comment provided by engineer. - Delete chat profile Supprimer le profil de chat @@ -2891,11 +2871,6 @@ Il s'agit de votre propre lien unique ! Erreur de validation de la demande de contact No comment provided by engineer. - - Error accessing database file - Erreur d'accès au fichier de la base de données - No comment provided by engineer. - Error adding member(s) Erreur lors de l'ajout de membre·s @@ -4690,11 +4665,6 @@ Voici votre lien pour le groupe %@ ! Nouveau contact : notification - - New database archive - Nouvelle archive de base de données - No comment provided by engineer. - New desktop app! Nouvelle application de bureau ! @@ -4910,11 +4880,6 @@ Voici votre lien pour le groupe %@ ! Ancienne base de données No comment provided by engineer. - - Old database archive - Archives de l'ancienne base de données - No comment provided by engineer. - One-time invitation link Lien d'invitation unique @@ -5065,6 +5030,10 @@ Nécessite l'activation d'un VPN. Operator server alert title + + Or import archive file + No comment provided by engineer. + Or paste archive link Ou coller le lien de l'archive @@ -5948,11 +5917,6 @@ Activez-le dans les paramètres *Réseau et serveurs*. Enregistrer et mettre à jour le profil du groupe No comment provided by engineer. - - Save archive - Enregistrer l'archive - No comment provided by engineer. - Save group profile Enregistrer le profil du groupe @@ -6770,11 +6734,6 @@ Activez-le dans les paramètres *Réseau et serveurs*. Arrêter le chat No comment provided by engineer. - - Stop chat to enable database actions - Arrêter le chat pour permettre des actions sur la base de données - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. Arrêtez le chat pour exporter, importer ou supprimer la base de données du chat. Vous ne pourrez pas recevoir et envoyer de messages pendant que le chat est arrêté. @@ -6900,6 +6859,10 @@ Activez-le dans les paramètres *Réseau et serveurs*. Prendre une photo No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + No comment provided by engineer. + Tap button Appuyez sur le bouton @@ -7989,10 +7952,6 @@ Répéter la demande d'adhésion ? You can configure servers via settings. No comment provided by engineer. - - You can create it in user picker. - No comment provided by engineer. - You can create it later Vous pouvez la créer plus tard diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index bca18b73e6..b8b760b5a0 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -1359,11 +1359,6 @@ Change user profiles authentication reason - - Chat archive - Csevegési archívum - No comment provided by engineer. - Chat colors Csevegés színei @@ -1965,11 +1960,6 @@ Ez az Ön egyszer használható hivatkozása! Létrehozva ekkor: %@ copied message info - - Created on %@ - Létrehozva %@ - No comment provided by engineer. - Creating archive link Archívum hivatkozás létrehozása @@ -2184,16 +2174,6 @@ Ez az Ön egyszer használható hivatkozása! Törlés, és az ismerős értesítése No comment provided by engineer. - - Delete archive - Archívum törlése - No comment provided by engineer. - - - Delete chat archive? - Csevegési archívum törlése? - No comment provided by engineer. - Delete chat profile Csevegési profil törlése @@ -2891,11 +2871,6 @@ Ez az Ön egyszer használható hivatkozása! Hiba történt a kapcsolatkérés elfogadásakor No comment provided by engineer. - - Error accessing database file - Hiba az adatbázisfájl elérésekor - No comment provided by engineer. - Error adding member(s) Hiba a tag(ok) hozzáadásakor @@ -4690,11 +4665,6 @@ Ez az Ön hivatkozása a(z) %@ nevű csoporthoz! Új kapcsolat: notification - - New database archive - Új adatbázis-archívum - No comment provided by engineer. - New desktop app! Új számítógép-alkalmazás! @@ -4910,11 +4880,6 @@ Ez az Ön hivatkozása a(z) %@ nevű csoporthoz! Régi adatbázis No comment provided by engineer. - - Old database archive - Régi adatbázis-archívum - No comment provided by engineer. - One-time invitation link Egyszer használható meghívó-hivatkozás @@ -5065,6 +5030,10 @@ VPN engedélyezése szükséges. Operator server alert title + + Or import archive file + No comment provided by engineer. + Or paste archive link Vagy az archívum hivatkozásának beillesztése @@ -5948,11 +5917,6 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Mentés és a csoportprofil frissítése No comment provided by engineer. - - Save archive - Archívum mentése - No comment provided by engineer. - Save group profile Csoportprofil mentése @@ -6770,11 +6734,6 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Csevegési szolgáltatás megállítása No comment provided by engineer. - - Stop chat to enable database actions - Csevegés megállítása az adatbázis-műveletek engedélyezéséhez - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. A csevegés megállítása a csevegési adatbázis exportálásához, importálásához vagy törléséhez. A csevegés megállításakor nem tud üzeneteket fogadni és küldeni. @@ -6900,6 +6859,10 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Kép készítése No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + No comment provided by engineer. + Tap button Koppintson a @@ -7989,10 +7952,6 @@ Csatlakozáskérés megismétlése? You can configure servers via settings. No comment provided by engineer. - - You can create it in user picker. - No comment provided by engineer. - You can create it later Létrehozás később diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index acb46596ce..55eee758d5 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -1359,11 +1359,6 @@ Change user profiles authentication reason - - Chat archive - Archivio chat - No comment provided by engineer. - Chat colors Colori della chat @@ -1965,11 +1960,6 @@ Questo è il tuo link una tantum! Creato il: %@ copied message info - - Created on %@ - Creato il %@ - No comment provided by engineer. - Creating archive link Creazione link dell'archivio @@ -2184,16 +2174,6 @@ Questo è il tuo link una tantum! Elimina e avvisa il contatto No comment provided by engineer. - - Delete archive - Elimina archivio - No comment provided by engineer. - - - Delete chat archive? - Eliminare l'archivio della chat? - No comment provided by engineer. - Delete chat profile Elimina il profilo di chat @@ -2891,11 +2871,6 @@ Questo è il tuo link una tantum! Errore nell'accettazione della richiesta di contatto No comment provided by engineer. - - Error accessing database file - Errore nell'accesso al file del database - No comment provided by engineer. - Error adding member(s) Errore di aggiunta membro/i @@ -4690,11 +4665,6 @@ Questo è il tuo link per il gruppo %@! Nuovo contatto: notification - - New database archive - Nuovo archivio database - No comment provided by engineer. - New desktop app! Nuova app desktop! @@ -4910,11 +4880,6 @@ Questo è il tuo link per il gruppo %@! Database vecchio No comment provided by engineer. - - Old database archive - Vecchio archivio del database - No comment provided by engineer. - One-time invitation link Link di invito una tantum @@ -5065,6 +5030,10 @@ Richiede l'attivazione della VPN. Operator server alert title + + Or import archive file + No comment provided by engineer. + Or paste archive link O incolla il link dell'archivio @@ -5948,11 +5917,6 @@ Attivalo nelle impostazioni *Rete e server*. Salva e aggiorna il profilo del gruppo No comment provided by engineer. - - Save archive - Salva archivio - No comment provided by engineer. - Save group profile Salva il profilo del gruppo @@ -6770,11 +6734,6 @@ Attivalo nelle impostazioni *Rete e server*. Ferma la chat No comment provided by engineer. - - Stop chat to enable database actions - Ferma la chat per attivare le azioni del database - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. Ferma la chat per esportare, importare o eliminare il database della chat. Non potrai ricevere e inviare messaggi mentre la chat è ferma. @@ -6900,6 +6859,10 @@ Attivalo nelle impostazioni *Rete e server*. Scatta foto No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + No comment provided by engineer. + Tap button Tocca il pulsante @@ -7989,10 +7952,6 @@ Ripetere la richiesta di ingresso? You can configure servers via settings. No comment provided by engineer. - - You can create it in user picker. - No comment provided by engineer. - You can create it later Puoi crearlo più tardi diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 12a34e3569..6b833800c0 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -1294,11 +1294,6 @@ Change user profiles authentication reason - - Chat archive - チャットのアーカイブ - No comment provided by engineer. - Chat colors No comment provided by engineer. @@ -1845,11 +1840,6 @@ This is your own one-time link! Created at: %@ copied message info - - Created on %@ - %@ によって作成されました - No comment provided by engineer. - Creating archive link No comment provided by engineer. @@ -2054,16 +2044,6 @@ This is your own one-time link! Delete and notify contact No comment provided by engineer. - - Delete archive - アーカイブを削除 - No comment provided by engineer. - - - Delete chat archive? - チャットのアーカイブを削除しますか? - No comment provided by engineer. - Delete chat profile チャットのプロフィールを削除する @@ -2719,11 +2699,6 @@ This is your own one-time link! 連絡先リクエストの承諾にエラー発生 No comment provided by engineer. - - Error accessing database file - データベースファイルへのアクセスエラー - No comment provided by engineer. - Error adding member(s) メンバー追加にエラー発生 @@ -4393,11 +4368,6 @@ This is your link for group %@! 新しい連絡先: notification - - New database archive - 新しいデータベースのアーカイブ - No comment provided by engineer. - New desktop app! 新しいデスクトップアプリ! @@ -4603,11 +4573,6 @@ This is your link for group %@! 古いデータベース No comment provided by engineer. - - Old database archive - 過去のデータベースアーカイブ - No comment provided by engineer. - One-time invitation link 使い捨ての招待リンク @@ -4754,6 +4719,10 @@ VPN を有効にする必要があります。 Operator server alert title + + Or import archive file + No comment provided by engineer. + Or paste archive link No comment provided by engineer. @@ -5566,11 +5535,6 @@ Enable in *Network & servers* settings. グループプロファイルの保存と更新 No comment provided by engineer. - - Save archive - アーカイブを保存 - No comment provided by engineer. - Save group profile グループプロフィールの保存 @@ -6319,11 +6283,6 @@ Enable in *Network & servers* settings. Stop chat No comment provided by engineer. - - Stop chat to enable database actions - チャットを停止してデータベースアクションを有効にします - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. データベースのエクスポート、読み込み、削除するにはチャットを閉じてからです。チャットを閉じると送受信ができなくなります。 @@ -6440,6 +6399,10 @@ Enable in *Network & servers* settings. 写真を撮影 No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + No comment provided by engineer. + Tap button ボタンをタップ @@ -7440,10 +7403,6 @@ Repeat join request? You can configure servers via settings. No comment provided by engineer. - - You can create it in user picker. - No comment provided by engineer. - You can create it later 後からでも作成できます diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index b7d4260354..603db9d75a 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -1359,11 +1359,6 @@ Change user profiles authentication reason - - Chat archive - Gesprek archief - No comment provided by engineer. - Chat colors Chat kleuren @@ -1965,11 +1960,6 @@ Dit is uw eigen eenmalige link! Aangemaakt op: %@ copied message info - - Created on %@ - Gemaakt op %@ - No comment provided by engineer. - Creating archive link Archief link maken @@ -2184,16 +2174,6 @@ Dit is uw eigen eenmalige link! Verwijderen en contact op de hoogte stellen No comment provided by engineer. - - Delete archive - Archief verwijderen - No comment provided by engineer. - - - Delete chat archive? - Chat archief verwijderen? - No comment provided by engineer. - Delete chat profile Chatprofiel verwijderen @@ -2891,11 +2871,6 @@ Dit is uw eigen eenmalige link! Fout bij het accepteren van een contactverzoek No comment provided by engineer. - - Error accessing database file - Fout bij toegang tot database bestand - No comment provided by engineer. - Error adding member(s) Fout bij het toevoegen van leden @@ -4690,11 +4665,6 @@ Dit is jouw link voor groep %@! Nieuw contact: notification - - New database archive - Nieuw database archief - No comment provided by engineer. - New desktop app! Nieuwe desktop app! @@ -4910,11 +4880,6 @@ Dit is jouw link voor groep %@! Oude database No comment provided by engineer. - - Old database archive - Oud database archief - No comment provided by engineer. - One-time invitation link Eenmalige uitnodiging link @@ -5065,6 +5030,10 @@ Vereist het inschakelen van VPN. Operator server alert title + + Or import archive file + No comment provided by engineer. + Or paste archive link Of plak de archief link @@ -5948,11 +5917,6 @@ Schakel dit in in *Netwerk en servers*-instellingen. Groep profiel opslaan en bijwerken No comment provided by engineer. - - Save archive - Bewaar archief - No comment provided by engineer. - Save group profile Groep profiel opslaan @@ -6770,11 +6734,6 @@ Schakel dit in in *Netwerk en servers*-instellingen. Stop chat No comment provided by engineer. - - Stop chat to enable database actions - Stop de chat om database acties mogelijk te maken - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. Stop de chat om de chat database te exporteren, importeren of verwijderen. U kunt geen berichten ontvangen en verzenden terwijl de chat is gestopt. @@ -6900,6 +6859,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Foto nemen No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + No comment provided by engineer. + Tap button Tik op de knop @@ -7989,10 +7952,6 @@ Deelnameverzoek herhalen? You can configure servers via settings. No comment provided by engineer. - - You can create it in user picker. - No comment provided by engineer. - You can create it later U kan het later maken diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 38e6c8991d..8a772bf470 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -1354,11 +1354,6 @@ Change user profiles authentication reason - - Chat archive - Archiwum czatu - No comment provided by engineer. - Chat colors Kolory czatu @@ -1960,11 +1955,6 @@ To jest twój jednorazowy link! Utworzony o: %@ copied message info - - Created on %@ - Utworzony w dniu %@ - No comment provided by engineer. - Creating archive link Tworzenie linku archiwum @@ -2178,16 +2168,6 @@ To jest twój jednorazowy link! Usuń i powiadom kontakt No comment provided by engineer. - - Delete archive - Usuń archiwum - No comment provided by engineer. - - - Delete chat archive? - Usunąć archiwum czatu? - No comment provided by engineer. - Delete chat profile Usuń profil czatu @@ -2884,11 +2864,6 @@ To jest twój jednorazowy link! Błąd przyjmowania prośby o kontakt No comment provided by engineer. - - Error accessing database file - Błąd dostępu do pliku bazy danych - No comment provided by engineer. - Error adding member(s) Błąd dodawania członka(ów) @@ -4680,11 +4655,6 @@ To jest twój link do grupy %@! Nowy kontakt: notification - - New database archive - Nowe archiwum bazy danych - No comment provided by engineer. - New desktop app! Nowa aplikacja desktopowa! @@ -4900,11 +4870,6 @@ To jest twój link do grupy %@! Stara baza danych No comment provided by engineer. - - Old database archive - Stare archiwum bazy danych - No comment provided by engineer. - One-time invitation link Jednorazowy link zaproszenia @@ -5055,6 +5020,10 @@ Wymaga włączenia VPN. Operator server alert title + + Or import archive file + No comment provided by engineer. + Or paste archive link Lub wklej link archiwum @@ -5938,11 +5907,6 @@ Włącz w ustawianiach *Sieć i serwery* . Zapisz i zaktualizuj profil grupowy No comment provided by engineer. - - Save archive - Zapisz archiwum - No comment provided by engineer. - Save group profile Zapisz profil grupy @@ -6759,11 +6723,6 @@ Włącz w ustawianiach *Sieć i serwery* . Zatrzymaj czat No comment provided by engineer. - - Stop chat to enable database actions - Zatrzymaj czat, aby umożliwić działania na bazie danych - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. Zatrzymaj czat, aby wyeksportować, zaimportować lub usunąć bazę danych czatu. Podczas zatrzymania chatu nie będzie można odbierać ani wysyłać wiadomości. @@ -6887,6 +6846,10 @@ Włącz w ustawianiach *Sieć i serwery* . Zrób zdjęcie No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + No comment provided by engineer. + Tap button Naciśnij przycisk @@ -7976,10 +7939,6 @@ Powtórzyć prośbę dołączenia? You can configure servers via settings. No comment provided by engineer. - - You can create it in user picker. - No comment provided by engineer. - You can create it later Możesz go utworzyć później diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index a36257c392..9b6cbf519e 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -1360,11 +1360,6 @@ Change user profiles authentication reason - - Chat archive - Архив чата - No comment provided by engineer. - Chat colors Цвета чата @@ -1966,11 +1961,6 @@ This is your own one-time link! Создано: %@ copied message info - - Created on %@ - Дата создания %@ - No comment provided by engineer. - Creating archive link Создание ссылки на архив @@ -2185,16 +2175,6 @@ This is your own one-time link! Удалить и уведомить контакт No comment provided by engineer. - - Delete archive - Удалить архив - No comment provided by engineer. - - - Delete chat archive? - Удалить архив чата? - No comment provided by engineer. - Delete chat profile Удалить профиль чата @@ -2892,11 +2872,6 @@ This is your own one-time link! Ошибка при принятии запроса на соединение No comment provided by engineer. - - Error accessing database file - Ошибка при доступе к данным чата - No comment provided by engineer. - Error adding member(s) Ошибка при добавлении членов группы @@ -4690,11 +4665,6 @@ This is your link for group %@! Новый контакт: notification - - New database archive - Новый архив чата - No comment provided by engineer. - New desktop app! Приложение для компьютера! @@ -4910,11 +4880,6 @@ This is your link for group %@! Предыдущая версия данных чата No comment provided by engineer. - - Old database archive - Старый архив чата - No comment provided by engineer. - One-time invitation link Одноразовая ссылка @@ -5065,6 +5030,10 @@ Requires compatible VPN. Operator server alert title + + Or import archive file + No comment provided by engineer. + Or paste archive link Или вставьте ссылку архива @@ -5948,11 +5917,6 @@ Enable in *Network & servers* settings. Сохранить сообщение и обновить группу No comment provided by engineer. - - Save archive - Сохранить архив - No comment provided by engineer. - Save group profile Сохранить профиль группы @@ -6770,11 +6734,6 @@ Enable in *Network & servers* settings. Остановить чат No comment provided by engineer. - - Stop chat to enable database actions - Остановите чат, чтобы разблокировать операции с архивом чата - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. Остановите чат, чтобы экспортировать или импортировать архив чата или удалить данные чата. Вы не сможете получать и отправлять сообщения, пока чат остановлен. @@ -6900,6 +6859,10 @@ Enable in *Network & servers* settings. Сделать фото No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + No comment provided by engineer. + Tap button Нажмите кнопку @@ -7989,10 +7952,6 @@ Repeat join request? You can configure servers via settings. No comment provided by engineer. - - You can create it in user picker. - No comment provided by engineer. - You can create it later Вы можете создать его позже diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index 6fc740ccea..8066daf54d 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -1262,11 +1262,6 @@ Change user profiles authentication reason - - Chat archive - ที่เก็บแชทถาวร - No comment provided by engineer. - Chat colors No comment provided by engineer. @@ -1810,11 +1805,6 @@ This is your own one-time link! Created at: %@ copied message info - - Created on %@ - สร้างเมื่อ %@ - No comment provided by engineer. - Creating archive link No comment provided by engineer. @@ -2019,16 +2009,6 @@ This is your own one-time link! Delete and notify contact No comment provided by engineer. - - Delete archive - ลบที่เก็บถาวร - No comment provided by engineer. - - - Delete chat archive? - ลบที่เก็บแชทถาวร? - No comment provided by engineer. - Delete chat profile ลบโปรไฟล์แชท @@ -2680,11 +2660,6 @@ This is your own one-time link! เกิดข้อผิดพลาดในการรับคำขอติดต่อ No comment provided by engineer. - - Error accessing database file - เกิดข้อผิดพลาดในการเข้าถึงไฟล์ฐานข้อมูล - No comment provided by engineer. - Error adding member(s) เกิดข้อผิดพลาดในการเพิ่มสมาชิก @@ -4349,11 +4324,6 @@ This is your link for group %@! คำขอติดต่อใหม่: notification - - New database archive - ฐานข้อมูลใหม่สำหรับการเก็บถาวร - No comment provided by engineer. - New desktop app! No comment provided by engineer. @@ -4557,11 +4527,6 @@ This is your link for group %@! ฐานข้อมูลเก่า No comment provided by engineer. - - Old database archive - คลังฐานข้อมูลเก่า - No comment provided by engineer. - One-time invitation link ลิงก์คำเชิญแบบใช้ครั้งเดียว @@ -4705,6 +4670,10 @@ Requires compatible VPN. Operator server alert title + + Or import archive file + No comment provided by engineer. + Or paste archive link No comment provided by engineer. @@ -5516,11 +5485,6 @@ Enable in *Network & servers* settings. บันทึกและอัปเดตโปรไฟล์กลุ่ม No comment provided by engineer. - - Save archive - บันทึกไฟล์เก็บถาวร - No comment provided by engineer. - Save group profile บันทึกโปรไฟล์กลุ่ม @@ -6271,11 +6235,6 @@ Enable in *Network & servers* settings. Stop chat No comment provided by engineer. - - Stop chat to enable database actions - หยุดการแชทเพื่อเปิดใช้งานการดำเนินการกับฐานข้อมูล - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. หยุดแชทเพื่อส่งออก นำเข้า หรือลบฐานข้อมูลแชท คุณจะไม่สามารถรับและส่งข้อความได้ในขณะที่การแชทหยุดลง @@ -6392,6 +6351,10 @@ Enable in *Network & servers* settings. ถ่ายภาพ No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + No comment provided by engineer. + Tap button แตะปุ่ม @@ -7390,10 +7353,6 @@ Repeat join request? You can configure servers via settings. No comment provided by engineer. - - You can create it in user picker. - No comment provided by engineer. - You can create it later คุณสามารถสร้างได้ในภายหลัง diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index 9c9fe3e253..f578a6225d 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -1359,11 +1359,6 @@ Change user profiles authentication reason - - Chat archive - Sohbet arşivi - No comment provided by engineer. - Chat colors Sohbet renkleri @@ -1965,11 +1960,6 @@ Bu senin kendi tek kullanımlık bağlantın! Şurada oluşturuldu: %@ copied message info - - Created on %@ - %@ de oluşturuldu - No comment provided by engineer. - Creating archive link Arşiv bağlantısı oluşturuluyor @@ -2184,16 +2174,6 @@ Bu senin kendi tek kullanımlık bağlantın! Sil ve kişiye bildir No comment provided by engineer. - - Delete archive - Arşivi sil - No comment provided by engineer. - - - Delete chat archive? - Sohbet arşivi silinsin mi? - No comment provided by engineer. - Delete chat profile Sohbet profilini sil @@ -2891,11 +2871,6 @@ Bu senin kendi tek kullanımlık bağlantın! Bağlantı isteği kabul edilirken hata oluştu No comment provided by engineer. - - Error accessing database file - Veritabanı dosyasına erişilirken hata oluştu - No comment provided by engineer. - Error adding member(s) Üye(ler) eklenirken hata oluştu @@ -4690,11 +4665,6 @@ Bu senin grup için bağlantın %@! Yeni kişi: notification - - New database archive - Yeni veritabanı arşivi - No comment provided by engineer. - New desktop app! Yeni bilgisayar uygulaması! @@ -4910,11 +4880,6 @@ Bu senin grup için bağlantın %@! Eski veritabanı No comment provided by engineer. - - Old database archive - Eski veritabanı arşivi - No comment provided by engineer. - One-time invitation link Tek zamanlı bağlantı daveti @@ -5065,6 +5030,10 @@ VPN'nin etkinleştirilmesi gerekir. Operator server alert title + + Or import archive file + No comment provided by engineer. + Or paste archive link Veya arşiv bağlantısını yapıştırın @@ -5948,11 +5917,6 @@ Enable in *Network & servers* settings. Kaydet ve grup profilini güncelle No comment provided by engineer. - - Save archive - Arşivi kaydet - No comment provided by engineer. - Save group profile Grup profilini kaydet @@ -6770,11 +6734,6 @@ Enable in *Network & servers* settings. Sohbeti kes No comment provided by engineer. - - Stop chat to enable database actions - Veritabanı eylemlerini etkinleştirmek için sohbeti durdur - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. Sohbet veritabanını dışa aktarmak, içe aktarmak veya silmek için sohbeti durdurun. Sohbet durdurulduğunda mesaj alamaz ve gönderemezsiniz. @@ -6900,6 +6859,10 @@ Enable in *Network & servers* settings. Fotoğraf çek No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + No comment provided by engineer. + Tap button Tuşa bas @@ -7989,10 +7952,6 @@ Katılma isteği tekrarlansın mı? You can configure servers via settings. No comment provided by engineer. - - You can create it in user picker. - No comment provided by engineer. - You can create it later Daha sonra oluşturabilirsiniz diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index c4beadaf66..136f45830b 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -1349,11 +1349,6 @@ Change user profiles authentication reason - - Chat archive - Архів чату - No comment provided by engineer. - Chat colors Кольори чату @@ -1953,11 +1948,6 @@ This is your own one-time link! Створено за адресою: %@ copied message info - - Created on %@ - Створено %@ - No comment provided by engineer. - Creating archive link Створення архівного посилання @@ -2171,16 +2161,6 @@ This is your own one-time link! Видалити та повідомити контакт No comment provided by engineer. - - Delete archive - Видалити архів - No comment provided by engineer. - - - Delete chat archive? - Видалити архів чату? - No comment provided by engineer. - Delete chat profile Видалити профіль чату @@ -2875,11 +2855,6 @@ This is your own one-time link! Помилка при прийнятті запиту на контакт No comment provided by engineer. - - Error accessing database file - Помилка доступу до файлу бази даних - No comment provided by engineer. - Error adding member(s) Помилка додавання користувача(ів) @@ -4656,11 +4631,6 @@ This is your link for group %@! Новий контакт: notification - - New database archive - Новий архів бази даних - No comment provided by engineer. - New desktop app! Новий десктопний додаток! @@ -4873,11 +4843,6 @@ This is your link for group %@! Стара база даних No comment provided by engineer. - - Old database archive - Старий архів бази даних - No comment provided by engineer. - One-time invitation link Посилання на одноразове запрошення @@ -5028,6 +4993,10 @@ Requires compatible VPN. Operator server alert title + + Or import archive file + No comment provided by engineer. + Or paste archive link Або вставте посилання на архів @@ -5904,11 +5873,6 @@ Enable in *Network & servers* settings. Збереження та оновлення профілю групи No comment provided by engineer. - - Save archive - Зберегти архів - No comment provided by engineer. - Save group profile Зберегти профіль групи @@ -6718,11 +6682,6 @@ Enable in *Network & servers* settings. Припинити чат No comment provided by engineer. - - Stop chat to enable database actions - Зупиніть чат, щоб увімкнути дії з базою даних - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. Зупиніть чат, щоб експортувати, імпортувати або видалити базу даних чату. Ви не зможете отримувати та надсилати повідомлення, поки чат зупинено. @@ -6845,6 +6804,10 @@ Enable in *Network & servers* settings. Сфотографуйте No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + No comment provided by engineer. + Tap button Натисніть кнопку @@ -7929,10 +7892,6 @@ Repeat join request? You can configure servers via settings. No comment provided by engineer. - - You can create it in user picker. - No comment provided by engineer. - You can create it later Ви можете створити його пізніше diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index 1daae62a6d..1663530290 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -1346,11 +1346,6 @@ Change user profiles authentication reason - - Chat archive - 聊天档案 - No comment provided by engineer. - Chat colors 聊天颜色 @@ -1950,11 +1945,6 @@ This is your own one-time link! 创建于:%@ copied message info - - Created on %@ - 创建于 %@ - No comment provided by engineer. - Creating archive link 正在创建存档链接 @@ -2168,16 +2158,6 @@ This is your own one-time link! 删除并通知联系人 No comment provided by engineer. - - Delete archive - 删除档案 - No comment provided by engineer. - - - Delete chat archive? - 删除聊天档案? - No comment provided by engineer. - Delete chat profile 删除聊天资料 @@ -2872,11 +2852,6 @@ This is your own one-time link! 接受联系人请求错误 No comment provided by engineer. - - Error accessing database file - 访问数据库文件错误 - No comment provided by engineer. - Error adding member(s) 添加成员错误 @@ -4653,11 +4628,6 @@ This is your link for group %@! 新联系人: notification - - New database archive - 新数据库存档 - No comment provided by engineer. - New desktop app! 全新桌面应用! @@ -4870,11 +4840,6 @@ This is your link for group %@! 旧的数据库 No comment provided by engineer. - - Old database archive - 旧数据库存档 - No comment provided by engineer. - One-time invitation link 一次性邀请链接 @@ -5025,6 +4990,10 @@ Requires compatible VPN. Operator server alert title + + Or import archive file + No comment provided by engineer. + Or paste archive link 或粘贴存档链接 @@ -5901,11 +5870,6 @@ Enable in *Network & servers* settings. 保存和更新组配置文件 No comment provided by engineer. - - Save archive - 保存存档 - No comment provided by engineer. - Save group profile 保存群组资料 @@ -6715,11 +6679,6 @@ Enable in *Network & servers* settings. 停止聊天程序 No comment provided by engineer. - - Stop chat to enable database actions - 停止聊天以启用数据库操作 - No comment provided by engineer. - Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. 停止聊天以便导出、导入或删除聊天数据库。在聊天停止期间,您将无法收发消息。 @@ -6842,6 +6801,10 @@ Enable in *Network & servers* settings. 拍照 No comment provided by engineer. + + Tap Create SimpleX address in the menu to create it later. + No comment provided by engineer. + Tap button 点击按钮 @@ -7926,10 +7889,6 @@ Repeat join request? You can configure servers via settings. No comment provided by engineer. - - You can create it in user picker. - No comment provided by engineer. - You can create it later 您可以以后创建它 diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index 5734fc58cc..aa955d7a7a 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -741,9 +741,6 @@ /* chat item text */ "changing address…" = "промяна на адреса…"; -/* No comment provided by engineer. */ -"Chat archive" = "Архив на чата"; - /* No comment provided by engineer. */ "Chat console" = "Конзола"; @@ -1035,9 +1032,6 @@ /* copied message info */ "Created at: %@" = "Създаден на: %@"; -/* No comment provided by engineer. */ -"Created on %@" = "Създаден на %@"; - /* No comment provided by engineer. */ "Creating archive link" = "Създаване на архивен линк"; @@ -1163,12 +1157,6 @@ /* No comment provided by engineer. */ "Delete and notify contact" = "Изтрий и уведоми контакт"; -/* No comment provided by engineer. */ -"Delete archive" = "Изтрий архив"; - -/* No comment provided by engineer. */ -"Delete chat archive?" = "Изтриване на архива на чата?"; - /* No comment provided by engineer. */ "Delete chat profile" = "Изтрий чат профила"; @@ -1581,9 +1569,6 @@ /* No comment provided by engineer. */ "Error accepting contact request" = "Грешка при приемане на заявка за контакт"; -/* No comment provided by engineer. */ -"Error accessing database file" = "Грешка при достъпа до файла с базата данни"; - /* No comment provided by engineer. */ "Error adding member(s)" = "Грешка при добавяне на член(ове)"; @@ -2544,9 +2529,6 @@ /* notification */ "New contact:" = "Нов контакт:"; -/* No comment provided by engineer. */ -"New database archive" = "Нов архив на база данни"; - /* No comment provided by engineer. */ "New desktop app!" = "Ново настолно приложение!"; @@ -2660,9 +2642,6 @@ /* No comment provided by engineer. */ "Old database" = "Стара база данни"; -/* No comment provided by engineer. */ -"Old database archive" = "Стар архив на база данни"; - /* group pref value */ "on" = "включено"; @@ -3169,9 +3148,6 @@ /* No comment provided by engineer. */ "Save and update group profile" = "Запази и актуализирай профила на групата"; -/* No comment provided by engineer. */ -"Save archive" = "Запази архив"; - /* No comment provided by engineer. */ "Save group profile" = "Запази профила на групата"; @@ -3536,9 +3512,6 @@ /* No comment provided by engineer. */ "Stop chat" = "Спри чата"; -/* No comment provided by engineer. */ -"Stop chat to enable database actions" = "Спрете чата, за да активирате действията с базата данни"; - /* No comment provided by engineer. */ "Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "Спрете чата, за да експортирате, импортирате или изтриете чат базата данни. Няма да можете да получавате и изпращате съобщения, докато чатът е спрян."; diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index 462988855b..9e96aafcfd 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -603,9 +603,6 @@ /* chat item text */ "changing address…" = "změna adresy…"; -/* No comment provided by engineer. */ -"Chat archive" = "Chat se archivuje"; - /* No comment provided by engineer. */ "Chat console" = "Konzola pro chat"; @@ -822,9 +819,6 @@ /* No comment provided by engineer. */ "Create your profile" = "Vytvořte si profil"; -/* No comment provided by engineer. */ -"Created on %@" = "Vytvořeno na %@"; - /* No comment provided by engineer. */ "creator" = "tvůrce"; @@ -938,12 +932,6 @@ /* No comment provided by engineer. */ "Delete all files" = "Odstranit všechny soubory"; -/* No comment provided by engineer. */ -"Delete archive" = "Smazat archiv"; - -/* No comment provided by engineer. */ -"Delete chat archive?" = "Smazat archiv chatu?"; - /* No comment provided by engineer. */ "Delete chat profile" = "Smazat chat profil"; @@ -1289,9 +1277,6 @@ /* No comment provided by engineer. */ "Error accepting contact request" = "Chyba při přijímání žádosti o kontakt"; -/* No comment provided by engineer. */ -"Error accessing database file" = "Chyba přístupu k souboru databáze"; - /* No comment provided by engineer. */ "Error adding member(s)" = "Chyba přidávání člena(ů)"; @@ -2054,9 +2039,6 @@ /* notification */ "New contact:" = "Nový kontakt:"; -/* No comment provided by engineer. */ -"New database archive" = "Archiv nové databáze"; - /* No comment provided by engineer. */ "New desktop app!" = "Nová desktopová aplikace!"; @@ -2161,9 +2143,6 @@ /* No comment provided by engineer. */ "Old database" = "Stará databáze"; -/* No comment provided by engineer. */ -"Old database archive" = "Archiv staré databáze"; - /* group pref value */ "on" = "zapnuto"; @@ -2568,9 +2547,6 @@ /* No comment provided by engineer. */ "Save and update group profile" = "Uložit a aktualizovat profil skupiny"; -/* No comment provided by engineer. */ -"Save archive" = "Uložit archiv"; - /* No comment provided by engineer. */ "Save group profile" = "Uložení profilu skupiny"; @@ -2869,9 +2845,6 @@ /* No comment provided by engineer. */ "Stop" = "Zastavit"; -/* No comment provided by engineer. */ -"Stop chat to enable database actions" = "Zastavte chat pro povolení akcí databáze"; - /* No comment provided by engineer. */ "Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "Zastavení chatu pro export, import nebo smazání databáze chatu. Během zastavení chatu nebudete moci přijímat a odesílat zprávy."; diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 312cab136a..f680727010 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -870,9 +870,6 @@ /* chat item text */ "changing address…" = "Wechsel der Empfängeradresse wurde gestartet…"; -/* No comment provided by engineer. */ -"Chat archive" = "Datenbank Archiv"; - /* No comment provided by engineer. */ "Chat colors" = "Chat-Farben"; @@ -1251,9 +1248,6 @@ /* copied message info */ "Created at: %@" = "Erstellt um: %@"; -/* No comment provided by engineer. */ -"Created on %@" = "Erstellt am %@"; - /* No comment provided by engineer. */ "Creating archive link" = "Archiv-Link erzeugen"; @@ -1400,12 +1394,6 @@ /* No comment provided by engineer. */ "Delete and notify contact" = "Kontakt löschen und benachrichtigen"; -/* No comment provided by engineer. */ -"Delete archive" = "Archiv löschen"; - -/* No comment provided by engineer. */ -"Delete chat archive?" = "Chat Archiv löschen?"; - /* No comment provided by engineer. */ "Delete chat profile" = "Chat-Profil löschen"; @@ -1884,9 +1872,6 @@ /* No comment provided by engineer. */ "Error accepting contact request" = "Fehler beim Annehmen der Kontaktanfrage"; -/* No comment provided by engineer. */ -"Error accessing database file" = "Fehler beim Zugriff auf die Datenbankdatei"; - /* No comment provided by engineer. */ "Error adding member(s)" = "Fehler beim Hinzufügen von Mitgliedern"; @@ -3024,9 +3009,6 @@ /* notification */ "New contact:" = "Neuer Kontakt:"; -/* No comment provided by engineer. */ -"New database archive" = "Neues Datenbankarchiv"; - /* No comment provided by engineer. */ "New desktop app!" = "Neue Desktop-App!"; @@ -3167,9 +3149,6 @@ /* No comment provided by engineer. */ "Old database" = "Alte Datenbank"; -/* No comment provided by engineer. */ -"Old database archive" = "Altes Datenbankarchiv"; - /* group pref value */ "on" = "Ein"; @@ -3796,9 +3775,6 @@ /* No comment provided by engineer. */ "Save and update group profile" = "Gruppen-Profil sichern und aktualisieren"; -/* No comment provided by engineer. */ -"Save archive" = "Archiv speichern"; - /* No comment provided by engineer. */ "Save group profile" = "Gruppenprofil speichern"; @@ -4307,9 +4283,6 @@ /* No comment provided by engineer. */ "Stop chat" = "Chat beenden"; -/* No comment provided by engineer. */ -"Stop chat to enable database actions" = "Chat beenden, um Datenbankaktionen zu erlauben"; - /* No comment provided by engineer. */ "Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "Beenden Sie den Chat, um die Chat-Datenbank zu exportieren, zu importieren oder zu löschen. Solange der Chat angehalten ist, können Sie keine Nachrichten empfangen oder senden."; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 9f775acb54..103065a05a 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -870,9 +870,6 @@ /* chat item text */ "changing address…" = "cambiando de servidor…"; -/* No comment provided by engineer. */ -"Chat archive" = "Archivo del chat"; - /* No comment provided by engineer. */ "Chat colors" = "Colores del chat"; @@ -1251,9 +1248,6 @@ /* copied message info */ "Created at: %@" = "Creado: %@"; -/* No comment provided by engineer. */ -"Created on %@" = "Creado en %@"; - /* No comment provided by engineer. */ "Creating archive link" = "Creando enlace al archivo"; @@ -1400,12 +1394,6 @@ /* No comment provided by engineer. */ "Delete and notify contact" = "Eliminar y notificar contacto"; -/* No comment provided by engineer. */ -"Delete archive" = "Eliminar archivo"; - -/* No comment provided by engineer. */ -"Delete chat archive?" = "¿Eliminar archivo del chat?"; - /* No comment provided by engineer. */ "Delete chat profile" = "Eliminar perfil"; @@ -1884,9 +1872,6 @@ /* No comment provided by engineer. */ "Error accepting contact request" = "Error al aceptar solicitud del contacto"; -/* No comment provided by engineer. */ -"Error accessing database file" = "Error al acceder al archivo de la base de datos"; - /* No comment provided by engineer. */ "Error adding member(s)" = "Error al añadir miembro(s)"; @@ -3024,9 +3009,6 @@ /* notification */ "New contact:" = "Contacto nuevo:"; -/* 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!"; @@ -3164,9 +3146,6 @@ /* No comment provided by engineer. */ "Old database" = "Base de datos antigua"; -/* No comment provided by engineer. */ -"Old database archive" = "Archivo de bases de datos antiguas"; - /* group pref value */ "on" = "Activado"; @@ -3793,9 +3772,6 @@ /* No comment provided by engineer. */ "Save and update group profile" = "Guardar y actualizar perfil del grupo"; -/* No comment provided by engineer. */ -"Save archive" = "Guardar archivo"; - /* No comment provided by engineer. */ "Save group profile" = "Guardar perfil de grupo"; @@ -4304,9 +4280,6 @@ /* No comment provided by engineer. */ "Stop chat" = "Parar SimpleX"; -/* No comment provided by engineer. */ -"Stop chat to enable database actions" = "Para habilitar las acciones sobre la base de datos, debes parar SimpleX"; - /* No comment provided by engineer. */ "Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "Para poder exportar, importar o eliminar la base de datos primero debes parar SimpleX. Mientras tanto no podrás recibir ni enviar mensajes."; diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index f927dbdabb..6f28ddd3b0 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -588,9 +588,6 @@ /* chat item text */ "changing address…" = "muuttamassa osoitetta…"; -/* No comment provided by engineer. */ -"Chat archive" = "Chat-arkisto"; - /* No comment provided by engineer. */ "Chat console" = "Chat-konsoli"; @@ -804,9 +801,6 @@ /* No comment provided by engineer. */ "Create your profile" = "Luo profiilisi"; -/* No comment provided by engineer. */ -"Created on %@" = "Luotu %@"; - /* No comment provided by engineer. */ "creator" = "luoja"; @@ -920,12 +914,6 @@ /* No comment provided by engineer. */ "Delete all files" = "Poista kaikki tiedostot"; -/* No comment provided by engineer. */ -"Delete archive" = "Poista arkisto"; - -/* No comment provided by engineer. */ -"Delete chat archive?" = "Poista keskusteluarkisto?"; - /* No comment provided by engineer. */ "Delete chat profile" = "Poista keskusteluprofiili"; @@ -1268,9 +1256,6 @@ /* No comment provided by engineer. */ "Error accepting contact request" = "Virhe kontaktipyynnön hyväksymisessä"; -/* No comment provided by engineer. */ -"Error accessing database file" = "Virhe tietokantatiedoston käyttämisessä"; - /* No comment provided by engineer. */ "Error adding member(s)" = "Virhe lisättäessä jäseniä"; @@ -2030,9 +2015,6 @@ /* notification */ "New contact:" = "Uusi kontakti:"; -/* No comment provided by engineer. */ -"New database archive" = "Uusi tietokanta-arkisto"; - /* No comment provided by engineer. */ "New display name" = "Uusi näyttönimi"; @@ -2134,9 +2116,6 @@ /* No comment provided by engineer. */ "Old database" = "Vanha tietokanta"; -/* No comment provided by engineer. */ -"Old database archive" = "Vanha tietokanta-arkisto"; - /* group pref value */ "on" = "päällä"; @@ -2538,9 +2517,6 @@ /* No comment provided by engineer. */ "Save and update group profile" = "Tallenna ja päivitä ryhmäprofiili"; -/* No comment provided by engineer. */ -"Save archive" = "Tallenna arkisto"; - /* No comment provided by engineer. */ "Save group profile" = "Tallenna ryhmäprofiili"; @@ -2830,9 +2806,6 @@ /* No comment provided by engineer. */ "Stop" = "Lopeta"; -/* No comment provided by engineer. */ -"Stop chat to enable database actions" = "Pysäytä keskustelu tietokantatoimien mahdollistamiseksi"; - /* No comment provided by engineer. */ "Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "Pysäytä keskustelut viedäksesi, tuodaksesi tai poistaaksesi keskustelujen tietokannan. Et voi vastaanottaa ja lähettää viestejä, kun keskustelut on pysäytetty."; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index 239d425973..273fb76d6e 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -870,9 +870,6 @@ /* chat item text */ "changing address…" = "changement d'adresse…"; -/* No comment provided by engineer. */ -"Chat archive" = "Archives du chat"; - /* No comment provided by engineer. */ "Chat colors" = "Couleurs de chat"; @@ -1251,9 +1248,6 @@ /* copied message info */ "Created at: %@" = "Créé à : %@"; -/* No comment provided by engineer. */ -"Created on %@" = "Créé le %@"; - /* No comment provided by engineer. */ "Creating archive link" = "Création d'un lien d'archive"; @@ -1400,12 +1394,6 @@ /* No comment provided by engineer. */ "Delete and notify contact" = "Supprimer et en informer le contact"; -/* No comment provided by engineer. */ -"Delete archive" = "Supprimer l'archive"; - -/* No comment provided by engineer. */ -"Delete chat archive?" = "Supprimer l'archive du chat ?"; - /* No comment provided by engineer. */ "Delete chat profile" = "Supprimer le profil de chat"; @@ -1884,9 +1872,6 @@ /* No comment provided by engineer. */ "Error accepting contact request" = "Erreur de validation de la demande de contact"; -/* No comment provided by engineer. */ -"Error accessing database file" = "Erreur d'accès au fichier de la base de données"; - /* No comment provided by engineer. */ "Error adding member(s)" = "Erreur lors de l'ajout de membre·s"; @@ -3024,9 +3009,6 @@ /* notification */ "New contact:" = "Nouveau contact :"; -/* No comment provided by engineer. */ -"New database archive" = "Nouvelle archive de base de données"; - /* No comment provided by engineer. */ "New desktop app!" = "Nouvelle application de bureau !"; @@ -3167,9 +3149,6 @@ /* No comment provided by engineer. */ "Old database" = "Ancienne base de données"; -/* No comment provided by engineer. */ -"Old database archive" = "Archives de l'ancienne base de données"; - /* group pref value */ "on" = "on"; @@ -3796,9 +3775,6 @@ /* No comment provided by engineer. */ "Save and update group profile" = "Enregistrer et mettre à jour le profil du groupe"; -/* No comment provided by engineer. */ -"Save archive" = "Enregistrer l'archive"; - /* No comment provided by engineer. */ "Save group profile" = "Enregistrer le profil du groupe"; @@ -4307,9 +4283,6 @@ /* No comment provided by engineer. */ "Stop chat" = "Arrêter le chat"; -/* No comment provided by engineer. */ -"Stop chat to enable database actions" = "Arrêter le chat pour permettre des actions sur la base de données"; - /* No comment provided by engineer. */ "Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "Arrêtez le chat pour exporter, importer ou supprimer la base de données du chat. Vous ne pourrez pas recevoir et envoyer de messages pendant que le chat est arrêté."; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index 1704389267..b64c75fd1d 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -870,9 +870,6 @@ /* chat item text */ "changing address…" = "cím megváltoztatása…"; -/* No comment provided by engineer. */ -"Chat archive" = "Csevegési archívum"; - /* No comment provided by engineer. */ "Chat colors" = "Csevegés színei"; @@ -1251,9 +1248,6 @@ /* copied message info */ "Created at: %@" = "Létrehozva ekkor: %@"; -/* No comment provided by engineer. */ -"Created on %@" = "Létrehozva %@"; - /* No comment provided by engineer. */ "Creating archive link" = "Archívum hivatkozás létrehozása"; @@ -1400,12 +1394,6 @@ /* No comment provided by engineer. */ "Delete and notify contact" = "Törlés, és az ismerős értesítése"; -/* No comment provided by engineer. */ -"Delete archive" = "Archívum törlése"; - -/* No comment provided by engineer. */ -"Delete chat archive?" = "Csevegési archívum törlése?"; - /* No comment provided by engineer. */ "Delete chat profile" = "Csevegési profil törlése"; @@ -1884,9 +1872,6 @@ /* No comment provided by engineer. */ "Error accepting contact request" = "Hiba történt a kapcsolatkérés elfogadásakor"; -/* No comment provided by engineer. */ -"Error accessing database file" = "Hiba az adatbázisfájl elérésekor"; - /* No comment provided by engineer. */ "Error adding member(s)" = "Hiba a tag(ok) hozzáadásakor"; @@ -3024,9 +3009,6 @@ /* notification */ "New contact:" = "Új kapcsolat:"; -/* No comment provided by engineer. */ -"New database archive" = "Új adatbázis-archívum"; - /* No comment provided by engineer. */ "New desktop app!" = "Új számítógép-alkalmazás!"; @@ -3167,9 +3149,6 @@ /* No comment provided by engineer. */ "Old database" = "Régi adatbázis"; -/* No comment provided by engineer. */ -"Old database archive" = "Régi adatbázis-archívum"; - /* group pref value */ "on" = "bekapcsolva"; @@ -3796,9 +3775,6 @@ /* No comment provided by engineer. */ "Save and update group profile" = "Mentés és a csoportprofil frissítése"; -/* No comment provided by engineer. */ -"Save archive" = "Archívum mentése"; - /* No comment provided by engineer. */ "Save group profile" = "Csoportprofil mentése"; @@ -4307,9 +4283,6 @@ /* No comment provided by engineer. */ "Stop chat" = "Csevegési szolgáltatás megállítása"; -/* No comment provided by engineer. */ -"Stop chat to enable database actions" = "Csevegés megállítása az adatbázis-műveletek engedélyezéséhez"; - /* No comment provided by engineer. */ "Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "A csevegés megállítása a csevegési adatbázis exportálásához, importálásához vagy törléséhez. A csevegés megállításakor nem tud üzeneteket fogadni és küldeni."; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index c041228706..06d78256c7 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -870,9 +870,6 @@ /* chat item text */ "changing address…" = "cambio indirizzo…"; -/* No comment provided by engineer. */ -"Chat archive" = "Archivio chat"; - /* No comment provided by engineer. */ "Chat colors" = "Colori della chat"; @@ -1251,9 +1248,6 @@ /* copied message info */ "Created at: %@" = "Creato il: %@"; -/* No comment provided by engineer. */ -"Created on %@" = "Creato il %@"; - /* No comment provided by engineer. */ "Creating archive link" = "Creazione link dell'archivio"; @@ -1400,12 +1394,6 @@ /* No comment provided by engineer. */ "Delete and notify contact" = "Elimina e avvisa il contatto"; -/* No comment provided by engineer. */ -"Delete archive" = "Elimina archivio"; - -/* No comment provided by engineer. */ -"Delete chat archive?" = "Eliminare l'archivio della chat?"; - /* No comment provided by engineer. */ "Delete chat profile" = "Elimina il profilo di chat"; @@ -1884,9 +1872,6 @@ /* No comment provided by engineer. */ "Error accepting contact request" = "Errore nell'accettazione della richiesta di contatto"; -/* No comment provided by engineer. */ -"Error accessing database file" = "Errore nell'accesso al file del database"; - /* No comment provided by engineer. */ "Error adding member(s)" = "Errore di aggiunta membro/i"; @@ -3024,9 +3009,6 @@ /* notification */ "New contact:" = "Nuovo contatto:"; -/* No comment provided by engineer. */ -"New database archive" = "Nuovo archivio database"; - /* No comment provided by engineer. */ "New desktop app!" = "Nuova app desktop!"; @@ -3167,9 +3149,6 @@ /* No comment provided by engineer. */ "Old database" = "Database vecchio"; -/* No comment provided by engineer. */ -"Old database archive" = "Vecchio archivio del database"; - /* group pref value */ "on" = "on"; @@ -3796,9 +3775,6 @@ /* No comment provided by engineer. */ "Save and update group profile" = "Salva e aggiorna il profilo del gruppo"; -/* No comment provided by engineer. */ -"Save archive" = "Salva archivio"; - /* No comment provided by engineer. */ "Save group profile" = "Salva il profilo del gruppo"; @@ -4307,9 +4283,6 @@ /* No comment provided by engineer. */ "Stop chat" = "Ferma la chat"; -/* No comment provided by engineer. */ -"Stop chat to enable database actions" = "Ferma la chat per attivare le azioni del database"; - /* No comment provided by engineer. */ "Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "Ferma la chat per esportare, importare o eliminare il database della chat. Non potrai ricevere e inviare messaggi mentre la chat è ferma."; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index d8756cc788..7e1d7f0527 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -660,9 +660,6 @@ /* chat item text */ "changing address…" = "アドレスを変更しています…"; -/* No comment provided by engineer. */ -"Chat archive" = "チャットのアーカイブ"; - /* No comment provided by engineer. */ "Chat console" = "チャットのコンソール"; @@ -876,9 +873,6 @@ /* No comment provided by engineer. */ "Create your profile" = "プロフィールを作成する"; -/* No comment provided by engineer. */ -"Created on %@" = "%@ によって作成されました"; - /* No comment provided by engineer. */ "creator" = "作成者"; @@ -992,12 +986,6 @@ /* No comment provided by engineer. */ "Delete all files" = "ファイルを全て削除"; -/* No comment provided by engineer. */ -"Delete archive" = "アーカイブを削除"; - -/* No comment provided by engineer. */ -"Delete chat archive?" = "チャットのアーカイブを削除しますか?"; - /* No comment provided by engineer. */ "Delete chat profile" = "チャットのプロフィールを削除する"; @@ -1343,9 +1331,6 @@ /* No comment provided by engineer. */ "Error accepting contact request" = "連絡先リクエストの承諾にエラー発生"; -/* No comment provided by engineer. */ -"Error accessing database file" = "データベースファイルへのアクセスエラー"; - /* No comment provided by engineer. */ "Error adding member(s)" = "メンバー追加にエラー発生"; @@ -2108,9 +2093,6 @@ /* notification */ "New contact:" = "新しい連絡先:"; -/* No comment provided by engineer. */ -"New database archive" = "新しいデータベースのアーカイブ"; - /* No comment provided by engineer. */ "New desktop app!" = "新しいデスクトップアプリ!"; @@ -2215,9 +2197,6 @@ /* No comment provided by engineer. */ "Old database" = "古いデータベース"; -/* No comment provided by engineer. */ -"Old database archive" = "過去のデータベースアーカイブ"; - /* group pref value */ "on" = "オン"; @@ -2619,9 +2598,6 @@ /* No comment provided by engineer. */ "Save and update group profile" = "グループプロファイルの保存と更新"; -/* No comment provided by engineer. */ -"Save archive" = "アーカイブを保存"; - /* No comment provided by engineer. */ "Save group profile" = "グループプロフィールの保存"; @@ -2893,9 +2869,6 @@ /* No comment provided by engineer. */ "Stop" = "停止"; -/* No comment provided by engineer. */ -"Stop chat to enable database actions" = "チャットを停止してデータベースアクションを有効にします"; - /* No comment provided by engineer. */ "Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "データベースのエクスポート、読み込み、削除するにはチャットを閉じてからです。チャットを閉じると送受信ができなくなります。"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index 4729e50d46..aa324a2ee0 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -870,9 +870,6 @@ /* chat item text */ "changing address…" = "adres wijzigen…"; -/* No comment provided by engineer. */ -"Chat archive" = "Gesprek archief"; - /* No comment provided by engineer. */ "Chat colors" = "Chat kleuren"; @@ -1251,9 +1248,6 @@ /* copied message info */ "Created at: %@" = "Aangemaakt op: %@"; -/* No comment provided by engineer. */ -"Created on %@" = "Gemaakt op %@"; - /* No comment provided by engineer. */ "Creating archive link" = "Archief link maken"; @@ -1400,12 +1394,6 @@ /* No comment provided by engineer. */ "Delete and notify contact" = "Verwijderen en contact op de hoogte stellen"; -/* No comment provided by engineer. */ -"Delete archive" = "Archief verwijderen"; - -/* No comment provided by engineer. */ -"Delete chat archive?" = "Chat archief verwijderen?"; - /* No comment provided by engineer. */ "Delete chat profile" = "Chatprofiel verwijderen"; @@ -1884,9 +1872,6 @@ /* No comment provided by engineer. */ "Error accepting contact request" = "Fout bij het accepteren van een contactverzoek"; -/* No comment provided by engineer. */ -"Error accessing database file" = "Fout bij toegang tot database bestand"; - /* No comment provided by engineer. */ "Error adding member(s)" = "Fout bij het toevoegen van leden"; @@ -3024,9 +3009,6 @@ /* notification */ "New contact:" = "Nieuw contact:"; -/* No comment provided by engineer. */ -"New database archive" = "Nieuw database archief"; - /* No comment provided by engineer. */ "New desktop app!" = "Nieuwe desktop app!"; @@ -3167,9 +3149,6 @@ /* No comment provided by engineer. */ "Old database" = "Oude database"; -/* No comment provided by engineer. */ -"Old database archive" = "Oud database archief"; - /* group pref value */ "on" = "aan"; @@ -3796,9 +3775,6 @@ /* No comment provided by engineer. */ "Save and update group profile" = "Groep profiel opslaan en bijwerken"; -/* No comment provided by engineer. */ -"Save archive" = "Bewaar archief"; - /* No comment provided by engineer. */ "Save group profile" = "Groep profiel opslaan"; @@ -4307,9 +4283,6 @@ /* No comment provided by engineer. */ "Stop chat" = "Stop chat"; -/* No comment provided by engineer. */ -"Stop chat to enable database actions" = "Stop de chat om database acties mogelijk te maken"; - /* No comment provided by engineer. */ "Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "Stop de chat om de chat database te exporteren, importeren of verwijderen. U kunt geen berichten ontvangen en verzenden terwijl de chat is gestopt."; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index 644ba366f6..6650b1d8c8 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -855,9 +855,6 @@ /* chat item text */ "changing address…" = "zmiana adresu…"; -/* No comment provided by engineer. */ -"Chat archive" = "Archiwum czatu"; - /* No comment provided by engineer. */ "Chat colors" = "Kolory czatu"; @@ -1236,9 +1233,6 @@ /* copied message info */ "Created at: %@" = "Utworzony o: %@"; -/* No comment provided by engineer. */ -"Created on %@" = "Utworzony w dniu %@"; - /* No comment provided by engineer. */ "Creating archive link" = "Tworzenie linku archiwum"; @@ -1382,12 +1376,6 @@ /* No comment provided by engineer. */ "Delete and notify contact" = "Usuń i powiadom kontakt"; -/* No comment provided by engineer. */ -"Delete archive" = "Usuń archiwum"; - -/* No comment provided by engineer. */ -"Delete chat archive?" = "Usunąć archiwum czatu?"; - /* No comment provided by engineer. */ "Delete chat profile" = "Usuń profil czatu"; @@ -1863,9 +1851,6 @@ /* No comment provided by engineer. */ "Error accepting contact request" = "Błąd przyjmowania prośby o kontakt"; -/* No comment provided by engineer. */ -"Error accessing database file" = "Błąd dostępu do pliku bazy danych"; - /* No comment provided by engineer. */ "Error adding member(s)" = "Błąd dodawania członka(ów)"; @@ -2997,9 +2982,6 @@ /* notification */ "New contact:" = "Nowy kontakt:"; -/* No comment provided by engineer. */ -"New database archive" = "Nowe archiwum bazy danych"; - /* No comment provided by engineer. */ "New desktop app!" = "Nowa aplikacja desktopowa!"; @@ -3140,9 +3122,6 @@ /* No comment provided by engineer. */ "Old database" = "Stara baza danych"; -/* No comment provided by engineer. */ -"Old database archive" = "Stare archiwum bazy danych"; - /* group pref value */ "on" = "włączone"; @@ -3769,9 +3748,6 @@ /* No comment provided by engineer. */ "Save and update group profile" = "Zapisz i zaktualizuj profil grupowy"; -/* No comment provided by engineer. */ -"Save archive" = "Zapisz archiwum"; - /* No comment provided by engineer. */ "Save group profile" = "Zapisz profil grupy"; @@ -4277,9 +4253,6 @@ /* No comment provided by engineer. */ "Stop chat" = "Zatrzymaj czat"; -/* No comment provided by engineer. */ -"Stop chat to enable database actions" = "Zatrzymaj czat, aby umożliwić działania na bazie danych"; - /* No comment provided by engineer. */ "Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "Zatrzymaj czat, aby wyeksportować, zaimportować lub usunąć bazę danych czatu. Podczas zatrzymania chatu nie będzie można odbierać ani wysyłać wiadomości."; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 536bbb62a8..6db8181e8d 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -873,9 +873,6 @@ /* chat item text */ "changing address…" = "смена адреса…"; -/* No comment provided by engineer. */ -"Chat archive" = "Архив чата"; - /* No comment provided by engineer. */ "Chat colors" = "Цвета чата"; @@ -1254,9 +1251,6 @@ /* copied message info */ "Created at: %@" = "Создано: %@"; -/* No comment provided by engineer. */ -"Created on %@" = "Дата создания %@"; - /* No comment provided by engineer. */ "Creating archive link" = "Создание ссылки на архив"; @@ -1403,12 +1397,6 @@ /* No comment provided by engineer. */ "Delete and notify contact" = "Удалить и уведомить контакт"; -/* No comment provided by engineer. */ -"Delete archive" = "Удалить архив"; - -/* No comment provided by engineer. */ -"Delete chat archive?" = "Удалить архив чата?"; - /* No comment provided by engineer. */ "Delete chat profile" = "Удалить профиль чата"; @@ -1887,9 +1875,6 @@ /* No comment provided by engineer. */ "Error accepting contact request" = "Ошибка при принятии запроса на соединение"; -/* No comment provided by engineer. */ -"Error accessing database file" = "Ошибка при доступе к данным чата"; - /* No comment provided by engineer. */ "Error adding member(s)" = "Ошибка при добавлении членов группы"; @@ -3027,9 +3012,6 @@ /* notification */ "New contact:" = "Новый контакт:"; -/* No comment provided by engineer. */ -"New database archive" = "Новый архив чата"; - /* No comment provided by engineer. */ "New desktop app!" = "Приложение для компьютера!"; @@ -3170,9 +3152,6 @@ /* No comment provided by engineer. */ "Old database" = "Предыдущая версия данных чата"; -/* No comment provided by engineer. */ -"Old database archive" = "Старый архив чата"; - /* group pref value */ "on" = "да"; @@ -3799,9 +3778,6 @@ /* No comment provided by engineer. */ "Save and update group profile" = "Сохранить сообщение и обновить группу"; -/* No comment provided by engineer. */ -"Save archive" = "Сохранить архив"; - /* No comment provided by engineer. */ "Save group profile" = "Сохранить профиль группы"; @@ -4310,9 +4286,6 @@ /* No comment provided by engineer. */ "Stop chat" = "Остановить чат"; -/* No comment provided by engineer. */ -"Stop chat to enable database actions" = "Остановите чат, чтобы разблокировать операции с архивом чата"; - /* No comment provided by engineer. */ "Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "Остановите чат, чтобы экспортировать или импортировать архив чата или удалить данные чата. Вы не сможете получать и отправлять сообщения, пока чат остановлен."; diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index b50986cf17..1b3dec5ee1 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -564,9 +564,6 @@ /* chat item text */ "changing address…" = "กำลังเปลี่ยนที่อยู่…"; -/* No comment provided by engineer. */ -"Chat archive" = "ที่เก็บแชทถาวร"; - /* No comment provided by engineer. */ "Chat console" = "คอนโซลแชท"; @@ -771,9 +768,6 @@ /* No comment provided by engineer. */ "Create your profile" = "สร้างโปรไฟล์ของคุณ"; -/* No comment provided by engineer. */ -"Created on %@" = "สร้างเมื่อ %@"; - /* No comment provided by engineer. */ "creator" = "ผู้สร้าง"; @@ -887,12 +881,6 @@ /* No comment provided by engineer. */ "Delete all files" = "ลบไฟล์ทั้งหมด"; -/* No comment provided by engineer. */ -"Delete archive" = "ลบที่เก็บถาวร"; - -/* No comment provided by engineer. */ -"Delete chat archive?" = "ลบที่เก็บแชทถาวร?"; - /* No comment provided by engineer. */ "Delete chat profile" = "ลบโปรไฟล์แชท"; @@ -1223,9 +1211,6 @@ /* No comment provided by engineer. */ "Error accepting contact request" = "เกิดข้อผิดพลาดในการรับคำขอติดต่อ"; -/* No comment provided by engineer. */ -"Error accessing database file" = "เกิดข้อผิดพลาดในการเข้าถึงไฟล์ฐานข้อมูล"; - /* No comment provided by engineer. */ "Error adding member(s)" = "เกิดข้อผิดพลาดในการเพิ่มสมาชิก"; @@ -1970,9 +1955,6 @@ /* notification */ "New contact:" = "คำขอติดต่อใหม่:"; -/* No comment provided by engineer. */ -"New database archive" = "ฐานข้อมูลใหม่สำหรับการเก็บถาวร"; - /* No comment provided by engineer. */ "New display name" = "ชื่อที่แสดงใหม่"; @@ -2071,9 +2053,6 @@ /* No comment provided by engineer. */ "Old database" = "ฐานข้อมูลเก่า"; -/* No comment provided by engineer. */ -"Old database archive" = "คลังฐานข้อมูลเก่า"; - /* group pref value */ "on" = "เปิด"; @@ -2469,9 +2448,6 @@ /* No comment provided by engineer. */ "Save and update group profile" = "บันทึกและอัปเดตโปรไฟล์กลุ่ม"; -/* No comment provided by engineer. */ -"Save archive" = "บันทึกไฟล์เก็บถาวร"; - /* No comment provided by engineer. */ "Save group profile" = "บันทึกโปรไฟล์กลุ่ม"; @@ -2749,9 +2725,6 @@ /* No comment provided by engineer. */ "Stop" = "หยุด"; -/* No comment provided by engineer. */ -"Stop chat to enable database actions" = "หยุดการแชทเพื่อเปิดใช้งานการดำเนินการกับฐานข้อมูล"; - /* No comment provided by engineer. */ "Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "หยุดแชทเพื่อส่งออก นำเข้า หรือลบฐานข้อมูลแชท คุณจะไม่สามารถรับและส่งข้อความได้ในขณะที่การแชทหยุดลง"; diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index 1583f01cda..b849dda85a 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -870,9 +870,6 @@ /* chat item text */ "changing address…" = "adres değiştiriliyor…"; -/* No comment provided by engineer. */ -"Chat archive" = "Sohbet arşivi"; - /* No comment provided by engineer. */ "Chat colors" = "Sohbet renkleri"; @@ -1251,9 +1248,6 @@ /* copied message info */ "Created at: %@" = "Şurada oluşturuldu: %@"; -/* No comment provided by engineer. */ -"Created on %@" = "%@ de oluşturuldu"; - /* No comment provided by engineer. */ "Creating archive link" = "Arşiv bağlantısı oluşturuluyor"; @@ -1400,12 +1394,6 @@ /* No comment provided by engineer. */ "Delete and notify contact" = "Sil ve kişiye bildir"; -/* No comment provided by engineer. */ -"Delete archive" = "Arşivi sil"; - -/* No comment provided by engineer. */ -"Delete chat archive?" = "Sohbet arşivi silinsin mi?"; - /* No comment provided by engineer. */ "Delete chat profile" = "Sohbet profilini sil"; @@ -1884,9 +1872,6 @@ /* No comment provided by engineer. */ "Error accepting contact request" = "Bağlantı isteği kabul edilirken hata oluştu"; -/* No comment provided by engineer. */ -"Error accessing database file" = "Veritabanı dosyasına erişilirken hata oluştu"; - /* No comment provided by engineer. */ "Error adding member(s)" = "Üye(ler) eklenirken hata oluştu"; @@ -3024,9 +3009,6 @@ /* notification */ "New contact:" = "Yeni kişi:"; -/* No comment provided by engineer. */ -"New database archive" = "Yeni veritabanı arşivi"; - /* No comment provided by engineer. */ "New desktop app!" = "Yeni bilgisayar uygulaması!"; @@ -3167,9 +3149,6 @@ /* No comment provided by engineer. */ "Old database" = "Eski veritabanı"; -/* No comment provided by engineer. */ -"Old database archive" = "Eski veritabanı arşivi"; - /* group pref value */ "on" = "açık"; @@ -3796,9 +3775,6 @@ /* No comment provided by engineer. */ "Save and update group profile" = "Kaydet ve grup profilini güncelle"; -/* No comment provided by engineer. */ -"Save archive" = "Arşivi kaydet"; - /* No comment provided by engineer. */ "Save group profile" = "Grup profilini kaydet"; @@ -4307,9 +4283,6 @@ /* No comment provided by engineer. */ "Stop chat" = "Sohbeti kes"; -/* No comment provided by engineer. */ -"Stop chat to enable database actions" = "Veritabanı eylemlerini etkinleştirmek için sohbeti durdur"; - /* No comment provided by engineer. */ "Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "Sohbet veritabanını dışa aktarmak, içe aktarmak veya silmek için sohbeti durdurun. Sohbet durdurulduğunda mesaj alamaz ve gönderemezsiniz."; diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index 01a3196c03..9af4581140 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -840,9 +840,6 @@ /* chat item text */ "changing address…" = "змінює адресу…"; -/* No comment provided by engineer. */ -"Chat archive" = "Архів чату"; - /* No comment provided by engineer. */ "Chat colors" = "Кольори чату"; @@ -1215,9 +1212,6 @@ /* copied message info */ "Created at: %@" = "Створено за адресою: %@"; -/* No comment provided by engineer. */ -"Created on %@" = "Створено %@"; - /* No comment provided by engineer. */ "Creating archive link" = "Створення архівного посилання"; @@ -1361,12 +1355,6 @@ /* No comment provided by engineer. */ "Delete and notify contact" = "Видалити та повідомити контакт"; -/* No comment provided by engineer. */ -"Delete archive" = "Видалити архів"; - -/* No comment provided by engineer. */ -"Delete chat archive?" = "Видалити архів чату?"; - /* No comment provided by engineer. */ "Delete chat profile" = "Видалити профіль чату"; @@ -1836,9 +1824,6 @@ /* No comment provided by engineer. */ "Error accepting contact request" = "Помилка при прийнятті запиту на контакт"; -/* No comment provided by engineer. */ -"Error accessing database file" = "Помилка доступу до файлу бази даних"; - /* No comment provided by engineer. */ "Error adding member(s)" = "Помилка додавання користувача(ів)"; @@ -2934,9 +2919,6 @@ /* notification */ "New contact:" = "Новий контакт:"; -/* No comment provided by engineer. */ -"New database archive" = "Новий архів бази даних"; - /* No comment provided by engineer. */ "New desktop app!" = "Новий десктопний додаток!"; @@ -3062,9 +3044,6 @@ /* No comment provided by engineer. */ "Old database" = "Стара база даних"; -/* No comment provided by engineer. */ -"Old database archive" = "Старий архів бази даних"; - /* group pref value */ "on" = "увімкненo"; @@ -3676,9 +3655,6 @@ /* No comment provided by engineer. */ "Save and update group profile" = "Збереження та оновлення профілю групи"; -/* No comment provided by engineer. */ -"Save archive" = "Зберегти архів"; - /* No comment provided by engineer. */ "Save group profile" = "Зберегти профіль групи"; @@ -4160,9 +4136,6 @@ /* No comment provided by engineer. */ "Stop chat" = "Припинити чат"; -/* No comment provided by engineer. */ -"Stop chat to enable database actions" = "Зупиніть чат, щоб увімкнути дії з базою даних"; - /* No comment provided by engineer. */ "Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "Зупиніть чат, щоб експортувати, імпортувати або видалити базу даних чату. Ви не зможете отримувати та надсилати повідомлення, поки чат зупинено."; diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index ba8dcd6e2c..a524b5739d 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -831,9 +831,6 @@ /* chat item text */ "changing address…" = "更改地址…"; -/* No comment provided by engineer. */ -"Chat archive" = "聊天档案"; - /* No comment provided by engineer. */ "Chat colors" = "聊天颜色"; @@ -1206,9 +1203,6 @@ /* copied message info */ "Created at: %@" = "创建于:%@"; -/* No comment provided by engineer. */ -"Created on %@" = "创建于 %@"; - /* No comment provided by engineer. */ "Creating archive link" = "正在创建存档链接"; @@ -1352,12 +1346,6 @@ /* No comment provided by engineer. */ "Delete and notify contact" = "删除并通知联系人"; -/* No comment provided by engineer. */ -"Delete archive" = "删除档案"; - -/* No comment provided by engineer. */ -"Delete chat archive?" = "删除聊天档案?"; - /* No comment provided by engineer. */ "Delete chat profile" = "删除聊天资料"; @@ -1827,9 +1815,6 @@ /* No comment provided by engineer. */ "Error accepting contact request" = "接受联系人请求错误"; -/* No comment provided by engineer. */ -"Error accessing database file" = "访问数据库文件错误"; - /* No comment provided by engineer. */ "Error adding member(s)" = "添加成员错误"; @@ -2925,9 +2910,6 @@ /* notification */ "New contact:" = "新联系人:"; -/* No comment provided by engineer. */ -"New database archive" = "新数据库存档"; - /* No comment provided by engineer. */ "New desktop app!" = "全新桌面应用!"; @@ -3053,9 +3035,6 @@ /* No comment provided by engineer. */ "Old database" = "旧的数据库"; -/* No comment provided by engineer. */ -"Old database archive" = "旧数据库存档"; - /* group pref value */ "on" = "开启"; @@ -3667,9 +3646,6 @@ /* No comment provided by engineer. */ "Save and update group profile" = "保存和更新组配置文件"; -/* No comment provided by engineer. */ -"Save archive" = "保存存档"; - /* No comment provided by engineer. */ "Save group profile" = "保存群组资料"; @@ -4151,9 +4127,6 @@ /* No comment provided by engineer. */ "Stop chat" = "停止聊天程序"; -/* No comment provided by engineer. */ -"Stop chat to enable database actions" = "停止聊天以启用数据库操作"; - /* No comment provided by engineer. */ "Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "停止聊天以便导出、导入或删除聊天数据库。在聊天停止期间,您将无法收发消息。"; From 94377d0b7ae08cf4d9f9876d4c9da2e3d3fd64f4 Mon Sep 17 00:00:00 2001 From: Diogo Date: Sat, 30 Nov 2024 18:21:48 +0000 Subject: [PATCH 139/567] android, desktop: bottom bar and update texts in onboarding (#5279) * android, desktop: remove one hand ui bar from onboarding and design matching latest ios * padding before text * stop reserving space in conditions view * notifications view * revert unwanted * update heading * translations for new how it works * how it works redone * show create profile in how it works * revert * conditions of use same padding bottom * unused str * swapped instant and off notifications order --------- Co-authored-by: Evgeny Poberezkin --- .../platform/ScrollableColumn.android.kt | 3 +- .../chat/simplex/common/views/WelcomeView.kt | 11 +++-- .../simplex/common/views/helpers/ModalView.kt | 3 +- .../views/onboarding/ChooseServerOperators.kt | 28 +++++------ .../common/views/onboarding/HowItWorks.kt | 6 +-- .../views/onboarding/SetNotificationsMode.kt | 44 ++++++++++++----- .../common/views/onboarding/SimpleXInfo.kt | 49 +++++++++++-------- .../commonMain/resources/MR/ar/strings.xml | 3 -- .../commonMain/resources/MR/base/strings.xml | 19 ++++--- .../commonMain/resources/MR/bg/strings.xml | 3 -- .../commonMain/resources/MR/cs/strings.xml | 3 -- .../commonMain/resources/MR/de/strings.xml | 3 -- .../commonMain/resources/MR/es/strings.xml | 7 +-- .../commonMain/resources/MR/fa/strings.xml | 3 -- .../commonMain/resources/MR/fi/strings.xml | 3 -- .../commonMain/resources/MR/fr/strings.xml | 3 -- .../commonMain/resources/MR/hu/strings.xml | 3 -- .../commonMain/resources/MR/in/strings.xml | 2 - .../commonMain/resources/MR/it/strings.xml | 3 -- .../commonMain/resources/MR/iw/strings.xml | 3 -- .../commonMain/resources/MR/ja/strings.xml | 3 -- .../commonMain/resources/MR/ko/strings.xml | 2 - .../commonMain/resources/MR/lt/strings.xml | 3 -- .../commonMain/resources/MR/nl/strings.xml | 3 -- .../commonMain/resources/MR/pl/strings.xml | 3 -- .../resources/MR/pt-rBR/strings.xml | 7 +-- .../commonMain/resources/MR/pt/strings.xml | 2 +- .../commonMain/resources/MR/ru/strings.xml | 3 -- .../commonMain/resources/MR/th/strings.xml | 3 -- .../commonMain/resources/MR/tr/strings.xml | 3 -- .../commonMain/resources/MR/uk/strings.xml | 3 -- .../commonMain/resources/MR/vi/strings.xml | 1 - .../resources/MR/zh-rCN/strings.xml | 3 -- .../resources/MR/zh-rTW/strings.xml | 3 -- .../platform/ScrollableColumn.desktop.kt | 3 +- 35 files changed, 101 insertions(+), 146 deletions(-) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/ScrollableColumn.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/ScrollableColumn.android.kt index d70177ffb9..cf95604504 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/ScrollableColumn.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/ScrollableColumn.android.kt @@ -14,6 +14,7 @@ import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.ui.theme.DEFAULT_PADDING import chat.simplex.common.views.chatlist.NavigationBarBackground import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.onboarding.OnboardingStage import kotlinx.coroutines.flow.filter import kotlin.math.absoluteValue @@ -124,7 +125,7 @@ actual fun ColumnWithScrollBar( } } } - val oneHandUI = remember { appPrefs.oneHandUI.state } + val oneHandUI = remember { derivedStateOf { if (appPrefs.onboardingStage.state.value == OnboardingStage.OnboardingComplete) appPrefs.oneHandUI.state.value else false } } Box(Modifier.fillMaxHeight()) { Column( if (maxIntrinsicSize) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt index 15d38c5490..024929030e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt @@ -117,14 +117,15 @@ fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) { ColumnWithScrollBar { val displayName = rememberSaveable { mutableStateOf("") } val focusRequester = remember { FocusRequester() } - Column(if (appPlatform.isAndroid) Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING) else Modifier.widthIn(max = 600.dp).fillMaxHeight().padding(horizontal = DEFAULT_PADDING).align(Alignment.CenterHorizontally)) { + Column(if (appPlatform.isAndroid) Modifier.fillMaxSize().padding(start = DEFAULT_PADDING * 2, end = DEFAULT_PADDING * 2, bottom = DEFAULT_PADDING) else Modifier.widthIn(max = 600.dp).fillMaxHeight().padding(horizontal = DEFAULT_PADDING).align(Alignment.CenterHorizontally)) { Box(Modifier.align(Alignment.CenterHorizontally)) { - AppBarTitle(stringResource(MR.strings.create_profile), bottomPadding = DEFAULT_PADDING, withPadding = false) + AppBarTitle(stringResource(MR.strings.create_your_profile), bottomPadding = DEFAULT_PADDING, withPadding = false) } - ProfileNameField(displayName, stringResource(MR.strings.display_name), { it.trim() == mkValidName(it) }, focusRequester) + ReadableText(MR.strings.your_profile_is_stored_on_your_device, TextAlign.Center, padding = PaddingValues(), style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary)) Spacer(Modifier.height(DEFAULT_PADDING)) - ReadableText(MR.strings.your_profile_is_stored_on_your_device, TextAlign.Start, padding = PaddingValues(), style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary)) - ReadableText(MR.strings.profile_is_only_shared_with_your_contacts, TextAlign.Start, style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary)) + ReadableText(MR.strings.profile_is_only_shared_with_your_contacts, TextAlign.Center, style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary)) + Spacer(Modifier.height(DEFAULT_PADDING)) + ProfileNameField(displayName, stringResource(MR.strings.display_name), { it.trim() == mkValidName(it) }, focusRequester) } Spacer(Modifier.fillMaxHeight().weight(1f)) Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt index c181f74e99..819efcdd9a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt @@ -14,6 +14,7 @@ import chat.simplex.common.model.ChatModel import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chatlist.StatusBarBackground +import chat.simplex.common.views.onboarding.OnboardingStage import kotlinx.coroutines.flow.MutableStateFlow import java.util.concurrent.atomic.AtomicBoolean import kotlin.math.min @@ -36,7 +37,7 @@ fun ModalView( if (showClose && showAppBar) { BackHandler(enabled = enableClose, onBack = close) } - val oneHandUI = remember { appPrefs.oneHandUI.state } + val oneHandUI = remember { derivedStateOf { if (appPrefs.onboardingStage.state.value == OnboardingStage.OnboardingComplete) appPrefs.oneHandUI.state.value else false } } Surface(Modifier.fillMaxSize(), contentColor = LocalContentColor.current) { Box(if (background != Color.Unspecified) Modifier.background(background) else Modifier.themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer)) { Box(modifier = modifier) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt index 8b383e0146..84ed7d1651 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt @@ -31,13 +31,8 @@ fun ModalData.ChooseServerOperators( LaunchedEffect(Unit) { prepareChatBeforeFinishingOnboarding() } - CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { - ModalView({}, showClose = false, endButtons = { - IconButton({ modalManager.showModal { ChooseServerOperatorsInfoView() } }) { - Icon(painterResource(MR.images.ic_info), null, Modifier.size(28.dp), tint = MaterialTheme.colors.primary) - } - }) { + ModalView({}, showClose = false) { val serverOperators = remember { derivedStateOf { chatModel.conditions.value.serverOperators } } val selectedOperatorIds = remember { stateGetOrPut("selectedOperatorIds") { serverOperators.value.filter { it.enabled }.map { it.operatorId }.toSet() } } val selectedOperators = remember { derivedStateOf { serverOperators.value.filter { selectedOperatorIds.value.contains(it.operatorId) } } } @@ -48,15 +43,16 @@ fun ModalData.ChooseServerOperators( maxIntrinsicSize = true ) { Box(Modifier.align(Alignment.CenterHorizontally)) { - AppBarTitle(stringResource(MR.strings.onboarding_choose_server_operators)) + AppBarTitle(stringResource(MR.strings.onboarding_choose_server_operators), bottomPadding = DEFAULT_PADDING) } - Column(( - if (appPlatform.isDesktop) Modifier.width(600.dp).align(Alignment.CenterHorizontally) else Modifier) - .padding(horizontal = DEFAULT_PADDING) - ) { - Text(stringResource(MR.strings.onboarding_select_network_operators_to_use)) - Spacer(Modifier.height(DEFAULT_PADDING)) + + Column(Modifier.fillMaxWidth().padding(horizontal = DEFAULT_PADDING), horizontalAlignment = Alignment.CenterHorizontally) { + OnboardingInformationButton( + stringResource(MR.strings.how_it_helps_privacy), + onClick = { modalManager.showModal { ChooseServerOperatorsInfoView() } } + ) } + Spacer(Modifier.weight(1f)) Column(( if (appPlatform.isDesktop) Modifier.width(600.dp).align(Alignment.CenterHorizontally) else Modifier) @@ -93,7 +89,7 @@ fun ModalData.ChooseServerOperators( currUserServers = remember { mutableStateOf(emptyList()) }, userServers = remember { mutableStateOf(emptyList()) }, close = close, - rhId = null + rhId = null, ) } } @@ -249,10 +245,8 @@ private fun ReviewConditionsView( Column(modifier = Modifier.weight(1f).padding(top = DEFAULT_PADDING_HALF)) { ConditionsTextView(chatModel.remoteHostId()) } - Column(Modifier.padding(top = DEFAULT_PADDING).widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + Column(Modifier.padding(vertical = DEFAULT_PADDING).widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { AcceptConditionsButton(onboarding, selectedOperators, selectedOperatorIds, close) - // Reserve space - TextButtonBelowOnboardingButton("", null) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt index 34b6209ffe..f7e7f456bb 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt @@ -25,14 +25,11 @@ import dev.icerock.moko.resources.StringResource fun HowItWorks(user: User?, onboardingStage: SharedPreference? = null) { ColumnWithScrollBar(Modifier.padding(DEFAULT_PADDING)) { AppBarTitle(stringResource(MR.strings.how_simplex_works), withPadding = false) - ReadableText(MR.strings.many_people_asked_how_can_it_deliver) ReadableText(MR.strings.to_protect_privacy_simplex_has_ids_for_queues) - ReadableText(MR.strings.you_control_servers_to_receive_your_contacts_to_send) ReadableText(MR.strings.only_client_devices_store_contacts_groups_e2e_encrypted_messages) + ReadableText(MR.strings.all_message_and_files_e2e_encrypted) if (onboardingStage == null) { ReadableTextWithLink(MR.strings.read_more_in_github_with_link, "https://github.com/simplex-chat/simplex-chat#readme") - } else { - ReadableText(MR.strings.read_more_in_github) } Spacer(Modifier.fillMaxHeight().weight(1f)) @@ -41,7 +38,6 @@ fun HowItWorks(user: User?, onboardingStage: SharedPreference? Box(Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING), contentAlignment = Alignment.Center) { OnboardingActionButton(user, onboardingStage, onclick = { ModalManager.fullscreen.closeModal() }) } - Spacer(Modifier.fillMaxHeight().weight(1f)) } Spacer(Modifier.height(DEFAULT_PADDING)) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt index 49c91813dc..9e6287771f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt @@ -32,23 +32,28 @@ fun SetNotificationsMode(m: ChatModel) { ModalView({}, showClose = false) { ColumnWithScrollBar(Modifier.themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer)) { Box(Modifier.align(Alignment.CenterHorizontally)) { - AppBarTitle(stringResource(MR.strings.onboarding_notifications_mode_title)) + AppBarTitle(stringResource(MR.strings.onboarding_notifications_mode_title), bottomPadding = DEFAULT_PADDING) } val currentMode = rememberSaveable { mutableStateOf(NotificationsMode.default) } - Column(Modifier.padding(horizontal = DEFAULT_PADDING * 1f)) { - Text(stringResource(MR.strings.onboarding_notifications_mode_subtitle), Modifier.fillMaxWidth(), textAlign = TextAlign.Center) - Spacer(Modifier.height(DEFAULT_PADDING * 2f)) - SelectableCard(currentMode, NotificationsMode.OFF, stringResource(MR.strings.onboarding_notifications_mode_off), annotatedStringResource(MR.strings.onboarding_notifications_mode_off_desc)) { - currentMode.value = NotificationsMode.OFF - } - SelectableCard(currentMode, NotificationsMode.PERIODIC, stringResource(MR.strings.onboarding_notifications_mode_periodic), annotatedStringResource(MR.strings.onboarding_notifications_mode_periodic_desc)) { - currentMode.value = NotificationsMode.PERIODIC - } - SelectableCard(currentMode, NotificationsMode.SERVICE, stringResource(MR.strings.onboarding_notifications_mode_service), annotatedStringResource(MR.strings.onboarding_notifications_mode_service_desc)) { + Column(Modifier.padding(horizontal = DEFAULT_PADDING).fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { + OnboardingInformationButton( + stringResource(MR.strings.onboarding_notifications_mode_subtitle), + onClick = { ModalManager.fullscreen.showModalCloseable { NotificationBatteryUsageInfo() } } + ) + } + Spacer(Modifier.weight(1f)) + Column(Modifier.padding(horizontal = DEFAULT_PADDING)) { + SelectableCard(currentMode, NotificationsMode.SERVICE, stringResource(MR.strings.onboarding_notifications_mode_service), annotatedStringResource(MR.strings.onboarding_notifications_mode_service_desc_short)) { currentMode.value = NotificationsMode.SERVICE } + SelectableCard(currentMode, NotificationsMode.PERIODIC, stringResource(MR.strings.onboarding_notifications_mode_periodic), annotatedStringResource(MR.strings.onboarding_notifications_mode_periodic_desc_short)) { + currentMode.value = NotificationsMode.PERIODIC + } + SelectableCard(currentMode, NotificationsMode.OFF, stringResource(MR.strings.onboarding_notifications_mode_off), annotatedStringResource(MR.strings.onboarding_notifications_mode_off_desc_short)) { + currentMode.value = NotificationsMode.OFF + } } - Spacer(Modifier.fillMaxHeight().weight(1f)) + Spacer(Modifier.weight(1f)) Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { OnboardingActionButton( modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier, @@ -99,6 +104,21 @@ fun SelectableCard(currentValue: State, newValue: T, title: String, descr Spacer(Modifier.height(14.dp)) } +@Composable +private fun NotificationBatteryUsageInfo() { + ColumnWithScrollBar(Modifier.padding(DEFAULT_PADDING)) { + AppBarTitle(stringResource(MR.strings.onboarding_notifications_mode_battery), withPadding = false) + Text(stringResource(MR.strings.onboarding_notifications_mode_service), style = MaterialTheme.typography.h3, color = MaterialTheme.colors.secondary) + ReadableText(MR.strings.onboarding_notifications_mode_service_desc) + Spacer(Modifier.height(DEFAULT_PADDING_HALF)) + Text(stringResource(MR.strings.onboarding_notifications_mode_periodic), style = MaterialTheme.typography.h3, color = MaterialTheme.colors.secondary) + ReadableText(MR.strings.onboarding_notifications_mode_periodic_desc) + Spacer(Modifier.height(DEFAULT_PADDING_HALF)) + Text(stringResource(MR.strings.onboarding_notifications_mode_off), style = MaterialTheme.typography.h3, color = MaterialTheme.colors.secondary) + ReadableText(MR.strings.onboarding_notifications_mode_off_desc) + } +} + fun prepareChatBeforeFinishingOnboarding() { // No visible users but may have hidden. In this case chat should be started anyway because it's stopped on this stage with hidden users if (chatModel.users.any { u -> !u.user.hidden }) return diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt index b133ae27d4..a77c25dd1d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt @@ -32,11 +32,7 @@ import dev.icerock.moko.resources.StringResource fun SimpleXInfo(chatModel: ChatModel, onboarding: Boolean = true) { if (onboarding) { CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { - ModalView({}, showClose = false, endButtons = { - IconButton({ ModalManager.fullscreen.showModal { HowItWorks(chatModel.currentUser.value, null) } }) { - Icon(painterResource(MR.images.ic_info), null, Modifier.size(28.dp), tint = MaterialTheme.colors.primary) - } - }) { + ModalView({}, showClose = false, showAppBar = false) { SimpleXInfoLayout( user = chatModel.currentUser.value, onboardingStage = chatModel.controller.appPrefs.onboardingStage @@ -56,22 +52,14 @@ fun SimpleXInfoLayout( user: User?, onboardingStage: SharedPreference? ) { - ColumnWithScrollBar( - Modifier - .padding(horizontal = DEFAULT_PADDING), - horizontalAlignment = Alignment.CenterHorizontally - ) { + ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_PADDING), horizontalAlignment = Alignment.CenterHorizontally) { Box(Modifier.widthIn(max = if (appPlatform.isAndroid) 250.dp else 500.dp).padding(top = DEFAULT_PADDING + 8.dp), contentAlignment = Alignment.Center) { SimpleXLogo() } - Spacer(Modifier.weight(1f)) - - Text( + OnboardingInformationButton( stringResource(MR.strings.next_generation_of_private_messaging), - style = MaterialTheme.typography.h3, - color = MaterialTheme.colors.secondary, - textAlign = TextAlign.Center + onClick = { ModalManager.fullscreen.showModal { HowItWorks(user, onboardingStage) } }, ) Spacer(Modifier.weight(1f)) @@ -82,10 +70,10 @@ fun SimpleXInfoLayout( InfoRow(painterResource(if (isInDarkTheme()) MR.images.decentralized_light else MR.images.decentralized), MR.strings.decentralized, MR.strings.opensource_protocol_and_code_anybody_can_run_servers) } - Spacer(Modifier.fillMaxHeight().weight(1f)) + Column(Modifier.fillMaxHeight().weight(1f)) { } if (onboardingStage != null) { - Column(Modifier.padding(horizontal = DEFAULT_PADDING).widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + Column(Modifier.padding(horizontal = DEFAULT_PADDING).widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally,) { OnboardingActionButton(user, onboardingStage) TextButtonBelowOnboardingButton(stringResource(MR.strings.migrate_from_another_device)) { chatModel.migrationState.value = MigrationToState.PasteOrScanLink @@ -165,8 +153,8 @@ fun OnboardingActionButton( fun TextButtonBelowOnboardingButton(text: String, onClick: (() -> Unit)?) { val state = getKeyboardState() val enabled = onClick != null - val topPadding by animateDpAsState(if (appPlatform.isAndroid && state.value == KeyboardState.Opened) 0.dp else DEFAULT_PADDING) - val bottomPadding by animateDpAsState(if (appPlatform.isAndroid && state.value == KeyboardState.Opened) 0.dp else DEFAULT_PADDING * 2) + val topPadding by animateDpAsState(if (appPlatform.isAndroid && state.value == KeyboardState.Opened) 0.dp else DEFAULT_PADDING_HALF) + val bottomPadding by animateDpAsState(if (appPlatform.isAndroid && state.value == KeyboardState.Opened) 0.dp else DEFAULT_PADDING_HALF) if ((appPlatform.isAndroid && state.value == KeyboardState.Closed) || topPadding > 0.dp) { TextButton({ onClick?.invoke() }, Modifier.padding(top = topPadding, bottom = bottomPadding).clip(CircleShape), enabled = enabled) { Text( @@ -183,6 +171,27 @@ fun TextButtonBelowOnboardingButton(text: String, onClick: (() -> Unit)?) { } } +@Composable +fun OnboardingInformationButton( + text: String, + onClick: () -> Unit, +) { + Box( + modifier = Modifier + .clip(CircleShape) + .clickable { onClick() } + ) { + Row(Modifier.padding(8.dp), horizontalArrangement = Arrangement.spacedBy(4.dp) ) { + Icon( + painterResource(MR.images.ic_info), + null, + tint = MaterialTheme.colors.primary + ) + Text(text, style = MaterialTheme.typography.button, color = MaterialTheme.colors.primary) + } + } +} + @Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index b2cb0acc4b..2f3f245f54 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -703,7 +703,6 @@ تأكد من أن عناوين خادم WebRTC ICE بالتنسيق الصحيح، وأن تكون مفصولة بأسطر وليست مكررة. علّم تحقق منه خطأ في حفظ كلمة مرور المستخدم - إذا SimpleX ليس لديه معرّفات مستخدم، كيف يمكنه توصيل الرسائل؟]]> خطأ في حفظ ملف تعريف المجموعة رسالة نصية ردود فعل الرسائل @@ -932,7 +931,6 @@ صفّر المنفذ %d خادم محدد مسبقًا - قراءة المزيد في مستودعنا على GitHub. يتم استخدام خادم الترحيل فقط إذا لزم الأمر. يمكن لطرف آخر مراقبة عنوان IP الخاص بك. حفظ وإشعار جهة الاتصال إعادة التشغيل @@ -1215,7 +1213,6 @@ غادرت يجب عليك استخدام أحدث إصدار من قاعدة بيانات الدردشة الخاصة بك على جهاز واحد فقط، وإلا فقد تتوقف عن تلقي الرسائل من بعض جهات الاتصال. سيتم استلام الفيديو عندما تكون جهة اتصالك متصلة بالإنترنت، يرجى الانتظار أو التحقق لاحقًا! - لاستلام الرسائل وجهات اتصالك - الخوادم التي تستخدمها لمراسلتهم.]]> يمكنك مشاركة هذا العنوان مع جهات اتصالك للسماح لهم بالاتصال بـ%s. أُزيلت %1$s تحديث diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 6d0200256c..1c036d76b2 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1026,7 +1026,7 @@ Error initializing WebView. Make sure you have WebView installed and it\'s supported architecture is arm64.\nError: %s - The next generation\nof private messaging + The future of messaging Privacy redefined No user identifiers. Immune to spam @@ -1040,23 +1040,25 @@ How SimpleX works - if SimpleX has no user identifiers, how can it deliver messages?]]> - To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. - to receive the messages, your contacts – the servers you use to message them.]]> - 2-layer end-to-end encryption.]]> - Read more in our GitHub repository. + To protect your privacy, SimpleX uses separate IDs for each of your contacts. + Only client devices store user profiles, contacts, groups, and messages. + end-to-end encrypted, with post-quantum security in direct messages.]]> GitHub repository.]]> Use chat Private notifications - It can be changed later via settings. + How it affects battery When app is running Periodic Instant Best for battery. You will receive notifications only when the app is running (NO background service).]]> + No background service Good for battery. App checks messages every 10 minutes. You may miss calls or urgent messages.]]> + Check messages every 10 minutes Uses more battery! App always runs in background – notifications are shown instantly.]]> + App always runs in background + Notifications and battery Setup database passphrase @@ -1064,11 +1066,12 @@ Use random passphrase - Choose operators + Server operators Network operators When more than one network operator is enabled, the app will use the servers of different operators for each conversation. For example, if you receive messages via SimpleX Chat server, the app will use one of Flux servers for private routing. Select network operators to use. + How it helps privacy You can configure servers via settings. Conditions will be accepted for enabled operators after 30 days. You can configure operators in Network & servers settings. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml index 042089e226..748c264918 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml @@ -982,9 +982,7 @@ Протокол и код с отворен код – всеки може да оперира собствени сървъри. Хората могат да се свържат с вас само чрез ликовете, които споделяте. Поверителността преосмислена - Прочетете повече в нашето хранилище в GitHub. Добави поверителна връзка - ако SimpleX няма потребителски идентификатори, как може да доставя съобщения\?]]> Отвори Реле сървър се използва само ако е необходимо. Друга страна може да наблюдава вашия IP адрес. Заключване след @@ -1220,7 +1218,6 @@ Когато са налични Вашият профил, контакти и доставени съобщения се съхраняват на вашето устройство. Можете да използвате markdown за форматиране на съобщенията: - да получавате съобщенията, вашите контакти – сървърите, които използвате, за да им изпращате съобщения.]]> Използвай чата Актуализация Трябва да въвеждате парола при всяко стартиране на приложението - тя не се съхранява на устройството. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml index 64a87736c4..548de53a21 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml @@ -667,9 +667,6 @@ Bez uživatelských identifikátorů Odolná vůči spamu K ochraně soukromí, místo uživatelských ID užívaných všemi ostatními platformami, SimpleX používá identifikátory pro fronty zpráv, zvlášť pro každý z vašich kontaktů. - když SimpleX nemá žádný identifikátor uživatelů, jak může doručovat zprávy\?]]> - přijímat zprávy, vaše kontakty – servery, které používáte k zasílání zpráv.]]> - Další informace najdete v našem repozitáři na GitHubu. úložišti GitHub.]]> Použijte chat Lze změnit později v nastavení. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index 75f6ac2c29..05ed6366a1 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -475,11 +475,8 @@ Wie es funktioniert Wie SimpleX funktioniert - Wie kann SimpleX Nachrichten zustellen, wenn es keine Benutzerkennungen gibt?]]> Zum Schutz Ihrer Privatsphäre verwendet SimpleX anstelle von Benutzerkennungen, die von allen anderen Plattformen verwendet werden, Kennungen für Nachrichtenwarteschlangen, die für jeden Ihrer Kontakte individuell sind. - empfangen und an Ihre Kontakte senden wollen.]]> zweischichtige Ende-zu-Ende-Verschlüsselung gesendet werden.]]> - Erfahren Sie in unserem GitHub-Repository mehr dazu. GitHub-Repository mehr dazu.]]> Fügen Sie den erhaltenen Link ein diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index 1cde9ed7c7..0f31210a9a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -442,7 +442,7 @@ Asegúrate de que las direcciones del servidor SMP tienen el formato correcto, están separadas por líneas y no están duplicadas. Notificación instantánea Configuración avanzada - cifrado de extremo a extremo de 2 capas .]]> + Sólo los dispositivos cliente almacenan perfiles de usuario, contactos, grupos y mensajes. Puedes cambiar estos ajustes más tarde en Configuración. Instantánea Unirte @@ -505,7 +505,6 @@ Se requieren hosts .onion para la conexión \nRecuerda: no podrás conectarte a servidores que no tengan dirección .onion. Inmune al spam - si SimpleX no tiene identificadores de usuario, ¿cómo puede entregar los mensajes\?]]> Videollamada entrante has salido has cambiado de servidor @@ -651,7 +650,6 @@ confirmación recibida… Periódico Privacidad redefinida - Conoce más en nuestro repositorio GitHub. Rechazar Abrir Llamada pendiente @@ -751,7 +749,7 @@ Inciar chat nuevo Para exportar, importar o eliminar la base de datos debes parar SimpleX. Mientra tanto no podrás recibir o enviar mensajes. Gracias por instalar SimpleX Chat! - Para proteger tu privacidad, en lugar de los identificadores de usuario que usan el resto de plataformas, SimpleX dispone de identificadores para las colas de mensajes, independientes para cada uno de tus contactos. + Para proteger tu privacidad, SimpleX dispone de identificadores para las colas de mensajes, independientes para cada uno de tus contactos. Para proteger tu información, activa el Bloqueo SimpleX. \nSe te pedirá que completes la autenticación antes de activar esta función. Al actualizar la configuración el cliente se reconectará a todos los servidores. @@ -843,7 +841,6 @@ Has sido invitado al grupo Mensajes de voz Tus contactos pueden permitir la eliminación completa de mensajes. - recibes los mensajes. Tus contactos controlan a través de qué servidor(es) envías tus mensajes.]]> Mensajes de voz Los mensajes de voz no están permitidos en este grupo. Comprobar la seguridad de la conexión diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml index dc4552a33e..23e2392fdc 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml @@ -733,8 +733,6 @@ نامتمرکز نمایه خود را ایجاد کنید SimpleX چگونه کار می‌کند - اگر SimpleX هیچ شناسه کاربری ندارد، چگونه می‌تواند پیام‌ها را تحویل دهد؟]]> - مطالعه بیشتر در مخزن GitHub ما. مخزن GitHub ما.]]> استفاده از گپ بهترین گزینه برای باتری. شما اعلان‌ها را فقط وقتی دریافت می‌کنید که برنامه در حال اجراست (بدون سرویس پس‌زمینه).]]> @@ -777,7 +775,6 @@ بلوتوث وارد کردن پایگاه داده اشخاص فقط از طریق لینک‌هایی که به اشتراک می‌گذارید می‌توانند به شما متصل شوند. - دریافت شوند و از چه سرورهایی به مخاطبان خود پیام می‌فرستید.]]> تماس از پیش پایان یافته! هش پیام ناصحیح پذیرفتن خودکار تصاویر diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml index 682854f9dd..28b29c59af 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml @@ -657,7 +657,6 @@ PING-väli Profiili- ja palvelinyhteydet Aseta ryhmän asetukset - jos SimpleX ei sisällä käyttäjätunnuksia, kuinka se voi toimittaa viestejä\?]]> PALVELIMET Tallenna ja ilmoita kontaktille Tallenna ja ilmoita kontakteille @@ -835,7 +834,6 @@ GitHub-arkistostamme.]]> Säännölliset 2-kerroksisella päästä päähän -salauksella.]]> - Lue lisää GitHub-tietovarastostamme. Liitä vastaanotettu linkki Välityspalvelin suojaa IP-osoitteesi, mutta se voi tarkkailla puhelun kestoa. Avaa SimpleX Chat hyväksyäksesi puhelun @@ -1051,7 +1049,6 @@ Odottaa tiedostoa Aloita uusi keskustelu Käyttää SimpleX Chat -palvelimia. - vastaanotetaan, kontaktiesi – palvelimet, joita käytät viestien lähettämiseen.]]> Yksityisyytesi poistit %1$s kyllä diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml index 3bef77138e..07b99ebe1d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml @@ -321,7 +321,6 @@ Assurez-vous que les adresses des serveurs WebRTC ICE sont au bon format et ne sont pas dupliquées, un par ligne. Accéder aux serveurs via un proxy SOCKS sur le port %d \? Le proxy doit être démarré avant d\'activer cette option. Utiliser les hôtes .onions - transmettre ainsi que par quel·s serveur·s vous pouvez recevoir les messages de vos contacts.]]> Vos paramètres SimpleX Lock Console du chat @@ -372,7 +371,6 @@ connexion… N\'importe qui peut heberger un serveur. Pour protéger votre vie privée, au lieu d\'IDs utilisés par toutes les autres plateformes, SimpleX possède des IDs pour les queues de messages, distinctes pour chacun de vos contacts. - Plus d\'informations sur notre GitHub. Collez le lien que vous avez reçu Utiliser le chat Notifications privées @@ -451,7 +449,6 @@ Établir une connexion privée Comment ça fonctionne Comment SimpleX fonctionne - si SimpleX n\'a pas d\'identifiant d\'utilisateur, comment peut-il transmettre des messages \?]]> chiffrement de bout en bout à deux couches.]]> GitHub repository.]]> Batterie peu utilisée. L\'app vérifie les messages toutes les 10 minutes. Vous risquez de manquer des appels ou des messages urgents.]]> diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index f0355d51ac..216b666100 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -814,7 +814,6 @@ ajánlott %s Csoport elhagyása Az összes %s által írt üzenet megjelenik! - Ha a SimpleX Chatnek nincs felhasználó-azonosítója, hogyan lehet mégis üzeneteket küldeni?]]> Ez akkor fordulhat elő, ha:\n1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak.\n2. Az üzenet visszafejtése sikertelen volt, mert Ön, vagy az ismerőse régebbi adatbázis biztonsági mentést használt.\n3. A kapcsolat sérült. megfigyelő inkognitó a csoporthivatkozáson keresztül @@ -1391,7 +1390,6 @@ Rejtett profilja felfedéséhez írja be a teljes jelszavát a keresőmezőbe a „Csevegési profilok” menüben. Fejlesztés és a csevegés megnyitása Engedélyeznie kell a hangüzenetek küldését az ismerőse számára, hogy hangüzeneteket küldhessenek egymásnak. - fogadja az üzeneteket, ismerősöket – a kiszolgálók, amelyeket az üzenetküldéshez használ.]]> %1$s nevű csoport tagja.]]> cím megváltoztatva Az ismerősei engedélyezhetik a teljes üzenet törlést. @@ -1463,7 +1461,6 @@ Ennek a csoportnak több mint %1$d tagja van, a kézbesítési jelentések nem kerülnek elküldésre. A második jelölés, amit kihagytunk! ✅ A közvetítő-kiszolgáló megvédi az IP-címet, de megfigyelheti a hívás időtartamát. - További információ a GitHub tárolónkban. Az utolsó üzenet tervezetének megőrzése a mellékletekkel együtt. A mentett WebRTC ICE-kiszolgálók eltávolításra kerülnek. A kézbesítési jelentések engedélyezve vannak %d csoportban diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml index dec20e2a36..caeec02deb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml @@ -618,7 +618,6 @@ Terdesentralisasi Kesalahan saat menginisialisasi WebView. Pastikan Anda telah menginstal WebView dan arsitektur yang didukung adalah arm64.\nKesalahan: %s Gunakan obrolan - Jika SimpleX tidak memiliki pengenal pengguna, bagaimana ia dapat menyampaikan pesan?]]> Bagaimana caranya Cara kerja SimpleX Berkala @@ -1093,7 +1092,6 @@ Pindah ke perangkat lain melalui kode QR. Hapus hingga 20 pesan sekaligus. Periksa pembaruan - Baca selengkapnya di repositori GitHub kami. Unduh %s (%s) simplexmq: v%s (%2s) Gunakan routing pribadi dengan server yang tak dikenal. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index e699714a6a..74c2397edd 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -650,7 +650,6 @@ Istantaneo Può essere cambiato in seguito via impostazioni. Crea una connessione privata - se SimpleX non ha identificatori utente, come può recapitare i messaggi\?]]> crittografia end-to-end a 2 livelli.]]> Chiunque può installare i server. Incolla il link che hai ricevuto @@ -659,7 +658,6 @@ Privacy ridefinita Notifiche private repository GitHub.]]> - Maggiori informazioni nel nostro repository GitHub. Rifiuta Nessun identificatore utente. La nuova generazione @@ -670,7 +668,6 @@ videochiamata (non crittografata e2e) Quando l\'app è in esecuzione %1$s vuole connettersi con te via - ricevere i messaggi, i tuoi contatti quali server usi per inviare loro i messaggi.]]> Può accadere quando: \n1. I messaggi sono scaduti sul client mittente dopo 2 giorni o sul server dopo 30 giorni. \n2. La decifrazione del messaggio è fallita, perché tu o il tuo contatto avete usato un backup del database vecchio. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml index 64f86a6ecb..c163458097 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml @@ -587,7 +587,6 @@ למדו עוד עזרה במרקדאון בואו נדבר ב־Simplex Chat - אם ל־SimpleX אין מזהי משתמש, איך ניתן להעביר הודעות\?]]> שגיאת Keychain הצטרף עם זהות נסתרת לעזוב קבוצה\? @@ -792,7 +791,6 @@ דחיה מדריך למשתמש.]]> דרגו את האפליקציה - קראו עוד ב־GitHub repository שלנו. GitHub repository שלנו.]]> יבוצע שימוש בשרת ממסר רק במידת הצורך. גורם אחר יכול לצפות בכתובת ה־IP שלך. שרת ממסר מגן על כתובת ה־IP שלך, אך הוא יכול לראות את משך השיחה. @@ -1133,7 +1131,6 @@ עליכם לאפשר לאיש הקשר שלכם לשלוח הודעות קוליות כדי שתוכלו לשלוח אותן. סרטון להתחבר למפתחי SimpleX Chat כדי לשאול כל שאלה ולקבל עדכונים.]]> - לקבל את ההודעות, אנשי הקשר שלכם – השרתים דרכם אתם שולחים להם הודעות.]]> שרתי WebRTC ICE %1$d הודעות שדולגו שבועות diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml index 632e62ef09..edd22933ec 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml @@ -154,7 +154,6 @@ 即時通知 SMPサーバのアドレスを正しく1行ずつに分けて、重複しないように、形式もご確認ください。 WebRTC ICEサーバのアドレスを正しく1行ずつに分けて、重複しないように、形式もご確認ください。 - if SimpleX にユーザIDがなければ、メッセージをどうやって届けるのでしょうかと。]]> SimpleX の仕様 通話中 電池消費がより高い!非アクティブ時でもバックグラウンドのサービスが常に稼働します(着信してすぐに通知が出ます)。]]> @@ -328,7 +327,6 @@ プライベートな接続をする プライベートな通知 GitHubリポジトリで詳細をご確認ください。]]> - GitHubリポジトリで詳細をご確認ください。 エンドツーエンド暗号化済みビデオ通話 無効にする エンドツーエンド暗号化がありません @@ -743,7 +741,6 @@ 取り消し線 接続中… 次世代のプライベートメッセンジャー - 受信サーバを決められます。あなたの連絡先が同じく、自分に対する受信サーバを決められます。]]> ビデオ通話 アプリが稼働中に WebRTC ICEサーバ diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml index 6af84d88c1..9d129847e4 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml @@ -665,7 +665,6 @@ TCP 연결 유지 활성화 %s의 새로운 기능 마크다운 도움말 - SimpleX에는 사용자 식별자가 없는데도 어떻게 메시지를 전달할 수 있어요\?]]> 그룹에서 나갈까요\? 앱 버전보다 최신 버전의 데이터베이스를 사용하고 있지만 데이터베이스를 다운그레이드할 수 없습니다: %s 멤버가 그룹에서 제거됩니다. 이 결정은 되돌릴 수 없습니다! @@ -688,7 +687,6 @@ 거절 비밀번호 표시 2계층 종단 간 암호화 로 전송된 사용자 프로필, 연락처, 그룹 및 메시지를 저장되어요.]]> - 자세한 내용은 GitHub에서 확인해 주세요. 개인 정보 보호 및 보안 알림은 앱이 중지되기 전까지만 전달될 거예요! 당신만 사라지는 메시지를 보낼 수 있습니다. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml index c3505460ae..da7738f49e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml @@ -348,7 +348,6 @@ Kas naujo Įrašyti ir pranešti grupės nariams gautas patvirtinimas… - Išsamiau skaitykite mūsų „GitHub“ saugykloje Praleistas skambutis POKALBIAI APIPAVIDALINIMAI @@ -1531,7 +1530,6 @@ Patvirtinti duomenų bazės slaptafrazę Patvirtinti slaptafrazę Nutildyti - gauti žinutes, jūsų kontaktai - serverius kuriuos naudojate siųsti jiems žinutes.]]> Tarpinis serveris apsaugo jūsų IP adresą, bet jis gali stebėti skambučio trukmę. nėra visapusio šifravimo Naujas duomenų bazės archyvas @@ -1600,7 +1598,6 @@ pakeitėte adresą %s Išplėsti rolių pasirinkimą %1$s.]]> - jei SimpleX neturi naudotojų identifikatorių, kaip jis gali pristatyti žinutes?]]> dviejų sluoksnių visapusiu šifravimu.]]> Kad apsaugoti privatumą, vietoj naudotojų ID naudojamų visose kitose platformose, SimpleX turi identifikatorius žinučių eilėms, skirtingus kiekvienam jūsų kontaktui. Žinutės juodraštis diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index 649f586620..2b71c3243b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -426,7 +426,6 @@ Hoe het werkt gemiste oproep Hoe SimpleX werkt - als SimpleX geen gebruikers-ID\'s heeft, hoe kan het dan berichten bezorgen\?]]> Inkomende audio oproep Inkomend video gesprek Negeren @@ -612,7 +611,6 @@ Jij beheert je gesprek! Uw profiel, contacten en afgeleverde berichten worden op uw apparaat opgeslagen. beginnen… - ontvangt, uw contacten de servers die u gebruikt om ze berichten te sturen.]]> Video aan Deze actie kan niet ongedaan worden gemaakt. Uw profiel, contacten, berichten en bestanden gaan onomkeerbaar verloren. Deze instelling is van toepassing op berichten in uw huidige chatprofiel @@ -931,7 +929,6 @@ je hebt een eenmalige link incognito gedeeld Tik op de knop GitHub repository.]]> - Lees meer in onze GitHub repository. %1$d bericht(en) overgeslagen gemodereerd gemodereerd door %s diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml index e793ff2a72..c679651e7d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml @@ -432,15 +432,12 @@ Natychmiastowy Można to później zmienić w ustawieniach. Nawiąż prywatne połączenie - jeśli SimpleX nie ma identyfikatora użytkownika, jak może dostarczać wiadomości\?]]> dwuwarstwowego szyfrowania end-to-end.]]> Okresowo Prywatne powiadomienia repozytorium GitHub.]]> - Przeczytaj więcej na naszym repozytorium GitHub. Użyj czatu Gdy aplikacja jest uruchomiona - odbierać wiadomości, Twoje kontakty - serwery, których używasz do wysyłania im wiadomości.]]> Zużywa więcej baterii! Aplikacja zawsze działa w tle - powiadomienia są wyświetlane natychmiastowo.]]> Przychodzące połączenie audio Przychodzące połączenie wideo diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml index 140b208db9..8b16e01b4e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml @@ -469,7 +469,6 @@ Compilação do aplicativo: %s Salvar e notificar contato resposta recebida… - Leia mais no nosso repositório do GitHub. Cole o link que você recebeu Quando o aplicativo está em execução Periódico @@ -587,7 +586,6 @@ Markdown em mensagens Servidores SMP Endereço do servidor pré-definido - se SimpleX não tem identificadores de usuários, como ele pode mandar mensagens\?]]> Rejeitar %1$d mensagem(s) ignorada(s) Proteger a tela do aplicativo @@ -889,7 +887,7 @@ você é um observador Mensagem de voz (%1$s) Compartilhar link - Para proteger a privacidade, em vez dos IDs de usuário usados por todas as outras plataformas, SimpleX tem identificadores para filas de mensagens, separados para cada um de seus contatos. + Para proteger a privacidade, SimpleX usa identificadores separados para cada um de seus contatos. chamada de vídeo Mostrar Servidores ICE WebRTC @@ -1010,11 +1008,10 @@ APOIE SIMPLEX CHAT Esta ação não pode ser desfeita - as mensagens enviadas e recebidas antes do selecionado serão excluídas. Pode levar vários minutos. Confirme as atualizações do banco de dados - criptografia de ponta a ponta em duas camadas.]]> + Somente o cliente dos dispositivos armazenam perfis de usuários, contatos, grupos e mensagens. Obrigado por instalar o SimpleX Chat! A plataforma de mensagens que protege sua privacidade e segurança. Você está tentando convidar um contato com quem compartilhou um perfil anônimo para o grupo no qual está usando seu perfil principal - receber as mensagens, seus contatos controlam os servidores que você usa para enviar mensagens.]]> Fila segura imagem de perfil temporária Erro ao carregar servidores SMP diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml index ee5b82d490..18c20eba50 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml @@ -635,7 +635,7 @@ Para verificar a encriptação de ponta a ponta com o seu contato, compare (ou leia) o código nos seus dispositivos. Ler o código de segurança a partir da aplicação do seu contacto. Ler o código QR do servidor - encriptação de ponta a ponta de 2 camadas.]]> + Apenas dispositivos pessoais armazenam perfis de utilizador, contatos, grupos e mensagens. o contacto tem encriptação ponta a ponta sem encriptação ponta a ponta criador diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml index 10d1de26ae..3d1e92f83f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -475,11 +475,8 @@ Как это работает Как SimpleX работает - как SimpleX доставляет сообщения без идентификаторов пользователей?]]> Чтобы защитить Вашу конфиденциальность, вместо ID пользователей, которые есть в других платформах, SimpleX использует ID для очередей сообщений, разные для каждого контакта. - получаете сообщения, Ваши контакты - серверы, которые Вы используете для отправки.]]> с двухуровневым end-to-end шифрованием.]]> - Узнайте больше из нашего GitHub репозитория. GitHub репозитория.]]> Использовать чат diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml index c1f88cf3b1..2487c7d5cd 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml @@ -746,7 +746,6 @@ เป็นไปได้มากว่าผู้ติดต่อนี้ได้ลบการเชื่อมต่อกับคุณ ข้อผิดพลาดในการส่งข้อความ ตรวจสอบให้แน่ใจว่าที่อยู่เซิร์ฟเวอร์ SMP อยู่ในรูปแบบที่ถูกต้อง แยกบรรทัดและไม่ซ้ำกัน - ถ้า SimpleX ไม่มีตัวระบุผู้ใช้ จะส่งข้อความได้อย่างไร\?]]> โฮสต์หัวหอมจะถูกใช้เมื่อมี ผู้ติดต่อของคุณเท่านั้นที่สามารถส่งข้อความเสียงได้ การเปิดลิงก์ในเบราว์เซอร์อาจลดความเป็นส่วนตัวและความปลอดภัยของการเชื่อมต่อ ลิงก์ SimpleX ที่ไม่น่าเชื่อถือจะเป็นสีแดง @@ -768,7 +767,6 @@ ได้รับการยืนยัน… นิยามความเป็นส่วนตัวใหม่ GitHub repository ของเรา]]> - อ่านเพิ่มเติมใน GitHub repository ของเรา การแจ้งเตือนส่วนตัว โปรดรายงานไปยังผู้พัฒนาแอป ความเป็นส่วนตัวและความปลอดภัย @@ -1149,7 +1147,6 @@ โปรไฟล์ รายชื่อผู้ติดต่อ และข้อความที่ส่งของคุณจะถูกจัดเก็บไว้ในอุปกรณ์ของคุณ คุณสามารถใช้มาร์กดาวน์เพื่อจัดรูปแบบข้อความ: รอคำตอบ… - รับข้อความและผู้ติดต่อของคุณ – เซิร์ฟเวอร์ที่คุณใช้เพื่อส่งข้อความถึงพวกเขา]]> ใช้แชท การสนทนาทางวิดีโอ การโทรของคุณ diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml index 9dcc45e6c4..48e26132c8 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml @@ -851,7 +851,6 @@ ICE sunucularınız Nasıl Mevcut profiliniz - alınacağını siz kontrol edersiniz, kişileriniz - onlara mesaj göndermek için kullandığınız sunucular.]]> video arama (uçtan uca şifreli değil) ICE sunucularınız Video kapalı @@ -985,7 +984,6 @@ Yeni bir sohbet başlatmak için Kimin bağlanabileceğine siz karar verirsiniz. Gizlilik yeniden tanımlanıyor - GitHub repomuzda daha fazlasını okuyun. Periyodik Gizli bildirimler Aldığın bağlantıyı yapıştır @@ -1462,7 +1460,6 @@ Bir sonraki mesajın kimliği yanlış (bir öncekinden az veya aynı). \nBazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Kişi gizlendi: - eğer SimpleX’in hiç kullanıcı tanımlayıcısı yok, nasıl mesajları gönderiyor? ]]> Şu durumlarda gerçekleşebilir: \n1. Mesajların süresi, gönderen istemcide 2 gün sonra veya sunucuda 30 gün sonra sona erdi. \n2. Siz veya kişiniz eski veritabanı yedeğini kullandığınız için mesajın şifresini çözme işlemi başarısız oldu. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index 732f85e473..a37d13bd51 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -410,7 +410,6 @@ Приватність перевизначена Ви вирішуєте, хто може під\'єднатися. Як працює SimpleX - Докладніше читайте в нашому репозиторії на GitHub. зашифрований e2e аудіовиклик Відкрийте SimpleX Chat для прийняття виклику e2e зашифровано @@ -651,8 +650,6 @@ Ви можете використовувати markdown для форматування повідомлень: Створіть свій профіль Створіть приватне підключення - як в SimpleX можливо доставляти повідомлення, якщо він не має ідентифікаторів користувачів?]]> - отримувати повідомлення, ваші контакти – сервери, які ви використовуєте для надсилання повідомлень їм.]]> шифрування на двох рівнях.]]> Приватні сповіщення Споживає більше акумулятора! Додаток завжди працює у фоновому режимі – сповіщення відображаються миттєво.]]> diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml index d12db70de6..cefaa982a5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml @@ -1020,7 +1020,6 @@ Tạo kết nối riêng tư Tạo hồ sơ riêng tư! Đảm bảo địa chỉ máy chủ WebRTC ICE ở đúng định dạng, dòng được phân tách và không bị trùng lặp. - nếu SimpleX không có thông tin định danh người dùng, thì làm thế nào mà nó có thể chuyển tin nhắn đi được?]]> Nó có thể xảy ra khi bạn hoặc liên hệ của bạn sử dụng bản sao lưu cơ sở dữ liệu cũ. Chế độ khóa Giao diện tiếng Ý diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index ed60383eb1..4c1c60e247 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -628,7 +628,6 @@ %d 秒 SimpleX 是如何工作的 确保 WebRTC ICE 服务器地址格式正确、每行分开且不重复。 - 如果SimpleX没有用户标识符,它是怎样传递信息的?]]> 确保 SMP 服务器地址格式正确、每行分开且不重复。 Markdown 帮助 标记为已验证 @@ -694,7 +693,6 @@ 必须 保存并通知联系人 保存并通知联系人 - 在我们的 GitHub 仓库中阅读更多内容。 拒绝 为了保护隐私,而不是所有其他平台使用的用户 ID,SimpleX 具有消息队列的标识符,每个联系人都是分开的。 TCP 连接超时 @@ -865,7 +863,6 @@ SimpleX 团队 %1$s 成员 - 接收消息,您的联系人 - 您用来向他们发送消息的服务器。]]> 您将在组主设备上线时连接到该群组,请稍等或稍后再检查! 当您启动应用或在应用程序驻留后台超过30 秒后,您将需要进行身份验证。 连接到 SimpleX Chat 开发者提出任何问题并接收更新 。]]> diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml index 58372bfa7a..65c16db612 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml @@ -554,7 +554,6 @@ 透過群組連結使用匿名聊天模式 一個使用了匿名聊天模式的人透過連結加入了群組 透過使用一次性連結匿名聊天模式連接 - 如果 SimpleX 沒有任何的用戶標識符,它如何傳送訊息?]]> 即時 定期的 關閉 @@ -787,7 +786,6 @@ %ds 私人通知 GitHub內查看更多。]]> - 於 GitHub 儲存庫內查看更多。 視訊通話來電 掛斷電話來電 點對點 @@ -874,7 +872,6 @@ 更新傳輸隔離模式? 為了保護隱私,而不像是其他平台般需要提取和存儲用戶的 IDs 資料, SimpleX 平台有自家佇列的標識符,這對於你的每個聯絡人也是獨一無二的。 當應用程式是運行中 - 來接收 你的聯絡人訊息 – 這些伺服器用來接收他們傳送給你的訊息。]]> 透過設定啟用於上鎖畫面顯示來電通知。 這操作不能還原 - 你目前的個人檔案,聯絡人,訊息和檔案將不可逆地遺失。 你必須在裝置上使用最新版本的對話數據庫,否則你可能會停止接收某些聯絡人的訊息。 diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt index a294f1cc60..fc806feb2b 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.unit.dp import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.ui.theme.DEFAULT_PADDING import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.onboarding.OnboardingStage import kotlinx.coroutines.* import kotlinx.coroutines.flow.filter import kotlin.math.* @@ -206,7 +207,7 @@ actual fun ColumnWithScrollBar( } val modifier = if (fillMaxSize) Modifier.fillMaxSize().then(modifier) else modifier Box(Modifier.nestedScroll(connection)) { - val oneHandUI = remember { appPrefs.oneHandUI.state } + val oneHandUI = remember { derivedStateOf { if (appPrefs.onboardingStage.state.value == OnboardingStage.OnboardingComplete) appPrefs.oneHandUI.state.value else false } } val padding = if (oneHandUI.value) PaddingValues(bottom = AppBarHeight * fontSizeSqrtMultiplier) else PaddingValues(top = AppBarHeight * fontSizeSqrtMultiplier) Column( if (maxIntrinsicSize) { From 2c0de36439164c97451a74f75bdff50c9c3c6d1f Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 30 Nov 2024 18:44:59 +0000 Subject: [PATCH 140/567] ios: large Conditions screen heading during onboarding --- apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift index fb3db2b585..14e08ff219 100644 --- a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift +++ b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift @@ -328,12 +328,12 @@ struct ChooseServerOperators: View { Text("Conditions will be accepted for operator(s): **\(acceptForOperators.map { $0.legalName_ }.joined(separator: ", "))**.") } ConditionsTextView() + .frame(maxHeight: .infinity) acceptConditionsButton() .padding(.bottom) .padding(.bottom) } .padding(.horizontal, 25) - .frame(maxHeight: .infinity) } private func acceptConditionsButton() -> some View { From a9e7635e00444ee716ad95e9edfddc642599c438 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 30 Nov 2024 20:15:11 +0000 Subject: [PATCH 141/567] core: disable Flux XFTP servers to prevent unknown server warning for the previous version users --- src/Simplex/Chat.hs | 2 +- tests/ChatTests/Direct.hs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 2555582fe9..cc06d1b677 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -166,7 +166,7 @@ operatorFlux = conditionsAcceptance = CARequired Nothing, enabled = False, smpRoles = ServerRoles {storage = False, proxy = True}, - xftpRoles = allRoles + xftpRoles = ServerRoles {storage = False, proxy = True} } defaultChatConfig :: ChatConfig diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index d305055d94..25bcc8659b 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -1249,9 +1249,9 @@ testOperators = alice <##. "1 (simplex). SimpleX Chat (SimpleX Chat Ltd), domains: simplex.im, servers: enabled, conditions: accepted (" alice <##. "2 (flux). Flux (InFlux Technologies Limited), domains: simplexonflux.com, servers: disabled, conditions: accepted (" -- update operators - alice ##> "/operators 2:on:smp=proxy" + alice ##> "/operators 2:on:smp=proxy:xftp=off" alice <##. "1 (simplex). SimpleX Chat (SimpleX Chat Ltd), domains: simplex.im, servers: enabled, conditions: accepted (" - alice <##. "2 (flux). Flux (InFlux Technologies Limited), domains: simplexonflux.com, servers: SMP enabled proxy, XFTP enabled, conditions: accepted (" + alice <##. "2 (flux). Flux (InFlux Technologies Limited), domains: simplexonflux.com, servers: SMP enabled proxy, XFTP disabled (servers known), conditions: accepted (" where opts' = testOpts {coreOptions = testCoreOpts {smpServers = [], xftpServers = []}} From 79d5573169443a01d73ce9b2b95253e8999ee040 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 30 Nov 2024 20:51:35 +0000 Subject: [PATCH 142/567] cli: fix option --yes-migrate to confirm up migrations automatically, closes #5200 (#5286) * fix(cli): option to confirm up migrations didn't work fix #5200 * diff * import --------- Co-authored-by: mervyn <6359152+reply2future@users.noreply.github.com> --- src/Simplex/Chat/Core.hs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Simplex/Chat/Core.hs b/src/Simplex/Chat/Core.hs index ad2f1367da..94af3a9dad 100644 --- a/src/Simplex/Chat/Core.hs +++ b/src/Simplex/Chat/Core.hs @@ -25,21 +25,22 @@ import Simplex.Chat.Options (ChatOpts (..), CoreChatOpts (..)) import Simplex.Chat.Store.Profiles import Simplex.Chat.Types import Simplex.Chat.View (serializeChatResponse) -import Simplex.Messaging.Agent.Store.SQLite (SQLiteStore, withTransaction) +import Simplex.Messaging.Agent.Store.SQLite (SQLiteStore, withTransaction, MigrationConfirmation (..)) import System.Exit (exitFailure) import System.IO (hFlush, stdout) import Text.Read (readMaybe) import UnliftIO.Async simplexChatCore :: ChatConfig -> ChatOpts -> (User -> ChatController -> IO ()) -> IO () -simplexChatCore cfg@ChatConfig {confirmMigrations, testView} opts@ChatOpts {coreOptions = CoreChatOpts {dbFilePrefix, dbKey, logAgent}} chat = +simplexChatCore cfg@ChatConfig {confirmMigrations, testView} opts@ChatOpts {coreOptions = CoreChatOpts {dbFilePrefix, dbKey, logAgent, yesToUpMigrations}} chat = case logAgent of Just level -> do setLogLevel level withGlobalLogging logCfg initRun _ -> initRun where - initRun = createChatDatabase dbFilePrefix dbKey False confirmMigrations >>= either exit run + initRun = createChatDatabase dbFilePrefix dbKey False confirm' >>= either exit run + confirm' = if confirmMigrations == MCConsole && yesToUpMigrations then MCYesUp else confirmMigrations exit e = do putStrLn $ "Error opening database: " <> show e exitFailure From 8f32c6a61ac3fc82b4c27d3674af30e58c947d8f Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 30 Nov 2024 20:54:39 +0000 Subject: [PATCH 143/567] core: 6.2.0.2 --- package.yaml | 2 +- simplex-chat.cabal | 2 +- src/Simplex/Chat/Remote.hs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.yaml b/package.yaml index 8e45db71e2..a4073f9df0 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.2.0.1 +version: 6.2.0.2 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 23071423b8..7ede1f99fc 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.2.0.1 +version: 6.2.0.2 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index f818c8ea3a..729ee502e8 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -73,11 +73,11 @@ import UnliftIO.Directory (copyFile, createDirectoryIfMissing, doesDirectoryExis -- when acting as host minRemoteCtrlVersion :: AppVersion -minRemoteCtrlVersion = AppVersion [6, 2, 0, 1] +minRemoteCtrlVersion = AppVersion [6, 2, 0, 2] -- when acting as controller minRemoteHostVersion :: AppVersion -minRemoteHostVersion = AppVersion [6, 2, 0, 1] +minRemoteHostVersion = AppVersion [6, 2, 0, 2] currentAppVersion :: AppVersion currentAppVersion = AppVersion SC.version From 98a3437f43085ebac80f4eb4c17a06615d45b582 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 30 Nov 2024 22:26:05 +0000 Subject: [PATCH 144/567] 6.2-beta.2: ios 248, android 253, desktop 77 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 36 +++++++++++----------- apps/multiplatform/gradle.properties | 8 ++--- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 8d2d05489d..4fd5527ea7 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -148,11 +148,11 @@ 6419EC562AB8BC8B004A607A /* ContextInvitingContactMemberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */; }; 6419EC582AB97507004A607A /* CIMemberCreatedContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */; }; 642BA82D2CE50495005E9412 /* NewServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 642BA82C2CE50495005E9412 /* NewServerView.swift */; }; - 642BA8332CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14.a */; }; + 642BA8332CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.2-5kRGnUsa36hjwDTjUoXRa.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.2-5kRGnUsa36hjwDTjUoXRa.a */; }; 642BA8342CEB3D4B005E9412 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA82F2CEB3D4B005E9412 /* libffi.a */; }; 642BA8352CEB3D4B005E9412 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA8302CEB3D4B005E9412 /* libgmp.a */; }; 642BA8362CEB3D4B005E9412 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA8312CEB3D4B005E9412 /* libgmpxx.a */; }; - 642BA8372CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14-ghc9.6.3.a */; }; + 642BA8372CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.2-5kRGnUsa36hjwDTjUoXRa-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.2-5kRGnUsa36hjwDTjUoXRa-ghc9.6.3.a */; }; 6432857C2925443C00FBE5C8 /* GroupPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */; }; 643B3B4E2CCFD6400083A2CF /* OperatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 643B3B4D2CCFD6400083A2CF /* OperatorView.swift */; }; 6440CA00288857A10062C672 /* CIEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440C9FF288857A10062C672 /* CIEventView.swift */; }; @@ -496,11 +496,11 @@ 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextInvitingContactMemberView.swift; sourceTree = ""; }; 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMemberCreatedContactView.swift; sourceTree = ""; }; 642BA82C2CE50495005E9412 /* NewServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewServerView.swift; sourceTree = ""; }; - 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14.a"; sourceTree = ""; }; + 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.2-5kRGnUsa36hjwDTjUoXRa.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.2-5kRGnUsa36hjwDTjUoXRa.a"; sourceTree = ""; }; 642BA82F2CEB3D4B005E9412 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; 642BA8302CEB3D4B005E9412 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 642BA8312CEB3D4B005E9412 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14-ghc9.6.3.a"; sourceTree = ""; }; + 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.2-5kRGnUsa36hjwDTjUoXRa-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.2-5kRGnUsa36hjwDTjUoXRa-ghc9.6.3.a"; sourceTree = ""; }; 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupPreferencesView.swift; sourceTree = ""; }; 643B3B4D2CCFD6400083A2CF /* OperatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperatorView.swift; sourceTree = ""; }; 6440C9FF288857A10062C672 /* CIEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIEventView.swift; sourceTree = ""; }; @@ -672,8 +672,8 @@ CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, 642BA8342CEB3D4B005E9412 /* libffi.a in Frameworks */, 642BA8352CEB3D4B005E9412 /* libgmp.a in Frameworks */, - 642BA8372CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14-ghc9.6.3.a in Frameworks */, - 642BA8332CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14.a in Frameworks */, + 642BA8372CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.2-5kRGnUsa36hjwDTjUoXRa-ghc9.6.3.a in Frameworks */, + 642BA8332CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.2-5kRGnUsa36hjwDTjUoXRa.a in Frameworks */, 642BA8362CEB3D4B005E9412 /* libgmpxx.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -754,8 +754,8 @@ 642BA82F2CEB3D4B005E9412 /* libffi.a */, 642BA8302CEB3D4B005E9412 /* libgmp.a */, 642BA8312CEB3D4B005E9412 /* libgmpxx.a */, - 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14-ghc9.6.3.a */, - 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.1-3FFlorLJSLlCbWWiG2Vp14.a */, + 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.2-5kRGnUsa36hjwDTjUoXRa-ghc9.6.3.a */, + 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.2-5kRGnUsa36hjwDTjUoXRa.a */, ); path = Libraries; sourceTree = ""; @@ -1931,7 +1931,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 247; + CURRENT_PROJECT_VERSION = 248; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1980,7 +1980,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 247; + CURRENT_PROJECT_VERSION = 248; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2021,7 +2021,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 247; + CURRENT_PROJECT_VERSION = 248; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2041,7 +2041,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 247; + CURRENT_PROJECT_VERSION = 248; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2066,7 +2066,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 247; + CURRENT_PROJECT_VERSION = 248; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2103,7 +2103,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 247; + CURRENT_PROJECT_VERSION = 248; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2140,7 +2140,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 247; + CURRENT_PROJECT_VERSION = 248; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2191,7 +2191,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 247; + CURRENT_PROJECT_VERSION = 248; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2242,7 +2242,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 247; + CURRENT_PROJECT_VERSION = 248; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2276,7 +2276,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 247; + CURRENT_PROJECT_VERSION = 248; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index b2d7875074..08056080ea 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -24,11 +24,11 @@ android.nonTransitiveRClass=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.2-beta.1 -android.version_code=252 +android.version_name=6.2-beta.2 +android.version_code=253 -desktop.version_name=6.2-beta.1 -desktop.version_code=76 +desktop.version_name=6.2-beta.2 +desktop.version_code=77 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From b8442d92a4817f95ff8fde6664e2c9312fc58f05 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 1 Dec 2024 13:11:30 +0000 Subject: [PATCH 145/567] core: improve performance of marking chat items as read (#5290) * core: improve performance of marking chat items as read * fix tests --- src/Simplex/Chat.hs | 18 +-- src/Simplex/Chat/Controller.hs | 2 +- src/Simplex/Chat/Store/Messages.hs | 223 +++++++++++++--------------- src/Simplex/Chat/Terminal/Output.hs | 3 +- tests/ChatTests/Direct.hs | 2 - tests/ChatTests/Groups.hs | 3 - tests/ChatTests/Local.hs | 2 +- 7 files changed, 117 insertions(+), 136 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index cc06d1b677..cc7fc992fb 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1250,13 +1250,13 @@ processChatCommand' vr = \case when (size' > 0) $ copyChunks r w size' APIUserRead userId -> withUserId userId $ \user -> withFastStore' (`setUserChatsRead` user) >> ok user UserRead -> withUser $ \User {userId} -> processChatCommand $ APIUserRead userId - APIChatRead chatRef@(ChatRef cType chatId) fromToIds -> withUser $ \_ -> case cType of + APIChatRead chatRef@(ChatRef cType chatId) -> withUser $ \_ -> case cType of CTDirect -> do user <- withFastStore $ \db -> getUserByContactId db chatId ts <- liftIO getCurrentTime timedItems <- withFastStore' $ \db -> do - timedItems <- getDirectUnreadTimedItems db user chatId fromToIds - updateDirectChatItemsRead db user chatId fromToIds + timedItems <- getDirectUnreadTimedItems db user chatId + updateDirectChatItemsRead db user chatId setDirectChatItemsDeleteAt db user chatId timedItems ts forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt ok user @@ -1264,14 +1264,14 @@ processChatCommand' vr = \case user <- withFastStore $ \db -> getUserByGroupId db chatId ts <- liftIO getCurrentTime timedItems <- withFastStore' $ \db -> do - timedItems <- getGroupUnreadTimedItems db user chatId fromToIds - updateGroupChatItemsRead db user chatId fromToIds + timedItems <- getGroupUnreadTimedItems db user chatId + updateGroupChatItemsRead db user chatId setGroupChatItemsDeleteAt db user chatId timedItems ts forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt ok user CTLocal -> do user <- withFastStore $ \db -> getUserByNoteFolderId db chatId - withFastStore' $ \db -> updateLocalChatItemsRead db user chatId fromToIds + withFastStore' $ \db -> updateLocalChatItemsRead db user chatId ok user CTContactRequest -> pure $ chatCmdError Nothing "not supported" CTContactConnection -> pure $ chatCmdError Nothing "not supported" @@ -1471,7 +1471,7 @@ processChatCommand' vr = \case withCurrentCall contactId $ \user ct Call {chatItemId, callState} -> case callState of CallInvitationReceived {} -> do let aciContent = ACIContent SMDRcv $ CIRcvCall CISCallRejected 0 - withFastStore' $ \db -> updateDirectChatItemsRead db user contactId $ Just (chatItemId, chatItemId) + withFastStore' $ \db -> setDirectChatItemRead db user contactId chatItemId timed_ <- contactCITimed ct updateDirectChatItemView user ct chatItemId aciContent False False timed_ Nothing forM_ (timed_ >>= timedDeleteAt') $ @@ -1487,7 +1487,7 @@ processChatCommand' vr = \case callState' = CallOfferSent {localCallType = callType, peerCallType, localCallSession = rtcSession, sharedKey} aciContent = ACIContent SMDRcv $ CIRcvCall CISCallAccepted 0 (SndMessage {msgId}, _) <- sendDirectContactMessage user ct (XCallOffer callId offer) - withFastStore' $ \db -> updateDirectChatItemsRead db user contactId $ Just (chatItemId, chatItemId) + withFastStore' $ \db -> setDirectChatItemRead db user contactId chatItemId updateDirectChatItemView user ct chatItemId aciContent False False Nothing $ Just msgId pure $ Just call {callState = callState'} _ -> throwChatError . CECallState $ callStateTag callState @@ -8277,7 +8277,7 @@ chatCommandP = "/_forward " *> (APIForwardChatItems <$> chatRefP <* A.space <*> chatRefP <*> _strP <*> sendMessageTTLP), "/_read user " *> (APIUserRead <$> A.decimal), "/read user" $> UserRead, - "/_read chat " *> (APIChatRead <$> chatRefP <*> optional (A.space *> ((,) <$> ("from=" *> A.decimal) <* A.space <*> ("to=" *> A.decimal)))), + "/_read chat " *> (APIChatRead <$> chatRefP), "/_read chat items " *> (APIChatItemsRead <$> chatRefP <*> _strP), "/_unread chat " *> (APIChatUnread <$> chatRefP <* A.space <*> onOffP), "/_delete " *> (APIDeleteChat <$> chatRefP <*> chatDeleteMode), diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index b6f8d5e093..d208efce77 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -309,7 +309,7 @@ data ChatCommand | APIForwardChatItems {toChatRef :: ChatRef, fromChatRef :: ChatRef, chatItemIds :: NonEmpty ChatItemId, ttl :: Maybe Int} | APIUserRead UserId | UserRead - | APIChatRead ChatRef (Maybe (ChatItemId, ChatItemId)) + | APIChatRead ChatRef | APIChatItemsRead ChatRef (NonEmpty ChatItemId) | APIChatUnread ChatRef Bool | APIDeleteChat ChatRef ChatDeleteMode -- currently delete mode settings are only applied to direct chats diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index f94cbbd81d..cff7f6b785 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -62,6 +62,7 @@ module Simplex.Chat.Store.Messages updateDirectChatItemsRead, getDirectUnreadTimedItems, updateDirectChatItemsReadList, + setDirectChatItemRead, setDirectChatItemsDeleteAt, updateGroupChatItemsRead, getGroupUnreadTimedItems, @@ -1670,61 +1671,61 @@ toChatItemRef = \case (itemId, Nothing, Nothing, Just folderId) -> Right (ChatRef CTLocal folderId, itemId) (itemId, _, _, _) -> Left $ SEBadChatItem itemId Nothing -updateDirectChatItemsRead :: DB.Connection -> User -> ContactId -> Maybe (ChatItemId, ChatItemId) -> IO () -updateDirectChatItemsRead db User {userId} contactId itemsRange_ = do +updateDirectChatItemsRead :: DB.Connection -> User -> ContactId -> IO () +updateDirectChatItemsRead db User {userId} contactId = do currentTs <- getCurrentTime - case itemsRange_ of - Just (fromItemId, toItemId) -> - DB.execute - db - [sql| - UPDATE chat_items SET item_status = ?, updated_at = ? - WHERE user_id = ? AND contact_id = ? AND chat_item_id >= ? AND chat_item_id <= ? AND item_status = ? - |] - (CISRcvRead, currentTs, userId, contactId, fromItemId, toItemId, CISRcvNew) - _ -> - DB.execute - db - [sql| - UPDATE chat_items SET item_status = ?, updated_at = ? - WHERE user_id = ? AND contact_id = ? AND item_status = ? - |] - (CISRcvRead, currentTs, userId, contactId, CISRcvNew) + DB.execute + db + [sql| + UPDATE chat_items SET item_status = ?, updated_at = ? + WHERE user_id = ? AND contact_id = ? AND item_status = ? + |] + (CISRcvRead, currentTs, userId, contactId, CISRcvNew) -getDirectUnreadTimedItems :: DB.Connection -> User -> ContactId -> Maybe (ChatItemId, ChatItemId) -> IO [(ChatItemId, Int)] -getDirectUnreadTimedItems db User {userId} contactId itemsRange_ = case itemsRange_ of - Just (fromItemId, toItemId) -> - DB.query - db - [sql| - SELECT chat_item_id, timed_ttl - FROM chat_items - WHERE user_id = ? AND contact_id = ? - AND chat_item_id >= ? AND chat_item_id <= ? - AND item_status = ? - AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL - AND (item_live IS NULL OR item_live = ?) - |] - (userId, contactId, fromItemId, toItemId, CISRcvNew, False) - _ -> - DB.query - db - [sql| - SELECT chat_item_id, timed_ttl - FROM chat_items - WHERE user_id = ? AND contact_id = ? AND item_status = ? AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL - |] - (userId, contactId, CISRcvNew) +getDirectUnreadTimedItems :: DB.Connection -> User -> ContactId -> IO [(ChatItemId, Int)] +getDirectUnreadTimedItems db User {userId} contactId = + DB.query + db + [sql| + SELECT chat_item_id, timed_ttl + FROM chat_items + WHERE user_id = ? AND contact_id = ? AND item_status = ? AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL + |] + (userId, contactId, CISRcvNew) updateDirectChatItemsReadList :: DB.Connection -> User -> ContactId -> NonEmpty ChatItemId -> IO [(ChatItemId, Int)] -updateDirectChatItemsReadList db user contactId itemIds = do - catMaybes . L.toList <$> mapM getUpdateDirectItem itemIds +updateDirectChatItemsReadList db user@User {userId} contactId itemIds = do + currentTs <- getCurrentTime + catMaybes . L.toList <$> mapM (getUpdateDirectItem currentTs) itemIds where - getUpdateDirectItem chatItemId = do - let itemsRange = Just (chatItemId, chatItemId) - timedItem <- maybeFirstRow id $ getDirectUnreadTimedItems db user contactId itemsRange - updateDirectChatItemsRead db user contactId itemsRange - pure timedItem + getUpdateDirectItem currentTs itemId = do + ttl_ <- maybeFirstRow fromOnly getUnreadTimedItem + setDirectChatItemRead_ db user contactId itemId currentTs + pure $ (itemId,) <$> ttl_ + where + getUnreadTimedItem = + DB.query + db + [sql| + SELECT timed_ttl + FROM chat_items + WHERE user_id = ? AND contact_id = ? AND item_status = ? AND chat_item_id = ? AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL + |] + (userId, contactId, CISRcvNew, itemId) + +setDirectChatItemRead :: DB.Connection -> User -> ContactId -> ChatItemId -> IO () +setDirectChatItemRead db user contactId itemId = + setDirectChatItemRead_ db user contactId itemId =<< getCurrentTime + +setDirectChatItemRead_ :: DB.Connection -> User -> ContactId -> ChatItemId -> UTCTime -> IO () +setDirectChatItemRead_ db User {userId} contactId itemId currentTs = + DB.execute + db + [sql| + UPDATE chat_items SET item_status = ?, updated_at = ? + WHERE user_id = ? AND contact_id = ? AND item_status = ? AND chat_item_id = ? + |] + (CISRcvRead, currentTs, userId, contactId, CISRcvNew, itemId) setDirectChatItemsDeleteAt :: DB.Connection -> User -> ContactId -> [(ChatItemId, Int)] -> UTCTime -> IO [(ChatItemId, UTCTime)] setDirectChatItemsDeleteAt db User {userId} contactId itemIds currentTs = forM itemIds $ \(chatItemId, ttl) -> do @@ -1735,61 +1736,55 @@ setDirectChatItemsDeleteAt db User {userId} contactId itemIds currentTs = forM i (deleteAt, userId, contactId, chatItemId) pure (chatItemId, deleteAt) -updateGroupChatItemsRead :: DB.Connection -> User -> GroupId -> Maybe (ChatItemId, ChatItemId) -> IO () -updateGroupChatItemsRead db User {userId} groupId itemsRange_ = do +updateGroupChatItemsRead :: DB.Connection -> User -> GroupId -> IO () +updateGroupChatItemsRead db User {userId} groupId = do currentTs <- getCurrentTime - case itemsRange_ of - Just (fromItemId, toItemId) -> - DB.execute - db - [sql| - UPDATE chat_items SET item_status = ?, updated_at = ? - WHERE user_id = ? AND group_id = ? AND chat_item_id >= ? AND chat_item_id <= ? AND item_status = ? - |] - (CISRcvRead, currentTs, userId, groupId, fromItemId, toItemId, CISRcvNew) - _ -> - DB.execute - db - [sql| - UPDATE chat_items SET item_status = ?, updated_at = ? - WHERE user_id = ? AND group_id = ? AND item_status = ? - |] - (CISRcvRead, currentTs, userId, groupId, CISRcvNew) + DB.execute + db + [sql| + UPDATE chat_items SET item_status = ?, updated_at = ? + WHERE user_id = ? AND group_id = ? AND item_status = ? + |] + (CISRcvRead, currentTs, userId, groupId, CISRcvNew) -getGroupUnreadTimedItems :: DB.Connection -> User -> GroupId -> Maybe (ChatItemId, ChatItemId) -> IO [(ChatItemId, Int)] -getGroupUnreadTimedItems db User {userId} groupId itemsRange_ = case itemsRange_ of - Just (fromItemId, toItemId) -> - DB.query - db - [sql| - SELECT chat_item_id, timed_ttl - FROM chat_items - WHERE user_id = ? AND group_id = ? - AND chat_item_id >= ? AND chat_item_id <= ? - AND item_status = ? - AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL - AND (item_live IS NULL OR item_live = ?) - |] - (userId, groupId, fromItemId, toItemId, CISRcvNew, False) - _ -> - DB.query - db - [sql| - SELECT chat_item_id, timed_ttl - FROM chat_items - WHERE user_id = ? AND group_id = ? AND item_status = ? AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL - |] - (userId, groupId, CISRcvNew) +getGroupUnreadTimedItems :: DB.Connection -> User -> GroupId -> IO [(ChatItemId, Int)] +getGroupUnreadTimedItems db User {userId} groupId = + DB.query + db + [sql| + SELECT chat_item_id, timed_ttl + FROM chat_items + WHERE user_id = ? AND group_id = ? AND item_status = ? AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL + |] + (userId, groupId, CISRcvNew) updateGroupChatItemsReadList :: DB.Connection -> User -> GroupId -> NonEmpty ChatItemId -> IO [(ChatItemId, Int)] -updateGroupChatItemsReadList db user groupId itemIds = do - catMaybes . L.toList <$> mapM getUpdateGroupItem itemIds +updateGroupChatItemsReadList db User {userId} groupId itemIds = do + currentTs <- getCurrentTime + catMaybes . L.toList <$> mapM (getUpdateGroupItem currentTs) itemIds where - getUpdateGroupItem chatItemId = do - let itemsRange = Just (chatItemId, chatItemId) - timedItem <- maybeFirstRow id $ getGroupUnreadTimedItems db user groupId itemsRange - updateGroupChatItemsRead db user groupId itemsRange - pure timedItem + getUpdateGroupItem currentTs itemId = do + ttl_ <- maybeFirstRow fromOnly getUnreadTimedItem + setItemRead + pure $ (itemId,) <$> ttl_ + where + getUnreadTimedItem = + DB.query + db + [sql| + SELECT timed_ttl + FROM chat_items + WHERE user_id = ? AND group_id = ? AND item_status = ? AND chat_item_id = ? AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL + |] + (userId, groupId, CISRcvNew, itemId) + setItemRead = + DB.execute + db + [sql| + UPDATE chat_items SET item_status = ?, updated_at = ? + WHERE user_id = ? AND group_id = ? AND item_status = ? AND chat_item_id = ? + |] + (CISRcvRead, currentTs, userId, groupId, CISRcvNew, itemId) setGroupChatItemsDeleteAt :: DB.Connection -> User -> GroupId -> [(ChatItemId, Int)] -> UTCTime -> IO [(ChatItemId, UTCTime)] setGroupChatItemsDeleteAt db User {userId} groupId itemIds currentTs = forM itemIds $ \(chatItemId, ttl) -> do @@ -1800,26 +1795,16 @@ setGroupChatItemsDeleteAt db User {userId} groupId itemIds currentTs = forM item (deleteAt, userId, groupId, chatItemId) pure (chatItemId, deleteAt) -updateLocalChatItemsRead :: DB.Connection -> User -> NoteFolderId -> Maybe (ChatItemId, ChatItemId) -> IO () -updateLocalChatItemsRead db User {userId} noteFolderId itemsRange_ = do +updateLocalChatItemsRead :: DB.Connection -> User -> NoteFolderId -> IO () +updateLocalChatItemsRead db User {userId} noteFolderId = do currentTs <- getCurrentTime - case itemsRange_ of - Just (fromItemId, toItemId) -> - DB.execute - db - [sql| - UPDATE chat_items SET item_status = ?, updated_at = ? - WHERE user_id = ? AND note_folder_id = ? AND chat_item_id >= ? AND chat_item_id <= ? AND item_status = ? - |] - (CISRcvRead, currentTs, userId, noteFolderId, fromItemId, toItemId, CISRcvNew) - _ -> - DB.execute - db - [sql| - UPDATE chat_items SET item_status = ?, updated_at = ? - WHERE user_id = ? AND note_folder_id = ? AND item_status = ? - |] - (CISRcvRead, currentTs, userId, noteFolderId, CISRcvNew) + DB.execute + db + [sql| + UPDATE chat_items SET item_status = ?, updated_at = ? + WHERE user_id = ? AND note_folder_id = ? AND item_status = ? + |] + (CISRcvRead, currentTs, userId, noteFolderId, CISRcvNew) type MaybeCIFIleRow = (Maybe Int64, Maybe String, Maybe Integer, Maybe FilePath, Maybe C.SbKey, Maybe C.CbNonce, Maybe ACIFileStatus, Maybe FileProtocol) diff --git a/src/Simplex/Chat/Terminal/Output.hs b/src/Simplex/Chat/Terminal/Output.hs index 0ead850b86..37c5c039c1 100644 --- a/src/Simplex/Chat/Terminal/Output.hs +++ b/src/Simplex/Chat/Terminal/Output.hs @@ -3,6 +3,7 @@ {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -164,7 +165,7 @@ runTerminalOutput ct cc@ChatController {outputQ, showLiveItems, logFilePath} Cha (True, CISRcvNew) -> do let itemId = chatItemId' ci chatRef = chatInfoToRef chat - void $ runReaderT (runExceptT $ processChatCommand (APIChatRead chatRef (Just (itemId, itemId)))) cc + void $ runReaderT (runExceptT $ processChatCommand (APIChatItemsRead chatRef [itemId])) cc _ -> pure () logResponse path s = withFile path AppendMode $ \h -> mapM_ (hPutStrLn h . unStyle) s getRemoteUser rhId = diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index 25bcc8659b..72a28c3ada 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -217,8 +217,6 @@ testAddContact = versionTestMatrix2 runTestAddContact -- search alice #$> ("/_get chat @2 count=100 search=ello ther", chat, [(1, "hello there 🙂"), (0, "hello there")]) -- read messages - alice #$> ("/_read chat @2 from=1 to=100", id, "ok") - bob #$> ("/_read chat @2 from=1 to=100", id, "ok") alice #$> ("/_read chat @2", id, "ok") bob #$> ("/_read chat @2", id, "ok") alice #$> ("/read user", id, "ok") diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index a1d9951088..89462b2b61 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -353,9 +353,6 @@ testGroupShared alice bob cath checkMessages directConnections = do bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "added cath (Catherine)"), (0, "connected"), (0, "hello"), (1, "hi there"), (0, "hey team")]) cath @@@ [("@bob", "hey"), ("#team", "hey team"), ("@alice", "received invitation to join group team as admin")] cath #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "connected"), (0, "hello"), (0, "hi there"), (1, "hey team")]) - alice #$> ("/_read chat #1 from=1 to=100", id, "ok") - bob #$> ("/_read chat #1 from=1 to=100", id, "ok") - cath #$> ("/_read chat #1 from=1 to=100", id, "ok") alice #$> ("/_read chat #1", id, "ok") bob #$> ("/_read chat #1", id, "ok") cath #$> ("/_read chat #1", id, "ok") diff --git a/tests/ChatTests/Local.hs b/tests/ChatTests/Local.hs index 40df02252d..c17b893be1 100644 --- a/tests/ChatTests/Local.hs +++ b/tests/ChatTests/Local.hs @@ -41,7 +41,7 @@ testNotes tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do alice ##> "/? keep" alice <# "* keep in mind" - alice #$> ("/_read chat *1 from=1 to=100", id, "ok") + alice #$> ("/_read chat *1", id, "ok") alice ##> "/_unread chat *1 on" alice <## "ok" From 3143cc960e44ebee4a9b201234e53720fd66cd5d Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sun, 1 Dec 2024 13:18:57 +0000 Subject: [PATCH 146/567] core: 6.2.0.3 --- package.yaml | 2 +- simplex-chat.cabal | 2 +- src/Simplex/Chat/Remote.hs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.yaml b/package.yaml index a4073f9df0..98571e1342 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.2.0.2 +version: 6.2.0.3 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 7ede1f99fc..1b1a0b9753 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.2.0.2 +version: 6.2.0.3 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index 729ee502e8..3a7d450691 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -73,11 +73,11 @@ import UnliftIO.Directory (copyFile, createDirectoryIfMissing, doesDirectoryExis -- when acting as host minRemoteCtrlVersion :: AppVersion -minRemoteCtrlVersion = AppVersion [6, 2, 0, 2] +minRemoteCtrlVersion = AppVersion [6, 2, 0, 3] -- when acting as controller minRemoteHostVersion :: AppVersion -minRemoteHostVersion = AppVersion [6, 2, 0, 2] +minRemoteHostVersion = AppVersion [6, 2, 0, 3] currentAppVersion :: AppVersion currentAppVersion = AppVersion SC.version From c488c4fcd52c5716fbf7b4bcab86262575dc8d35 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sun, 1 Dec 2024 19:09:53 +0000 Subject: [PATCH 147/567] 6.2-beta.3: ios 249, android 254, desktop 78 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 36 +++++++++++----------- apps/multiplatform/gradle.properties | 8 ++--- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 4fd5527ea7..31917f1ab9 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -148,11 +148,11 @@ 6419EC562AB8BC8B004A607A /* ContextInvitingContactMemberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */; }; 6419EC582AB97507004A607A /* CIMemberCreatedContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */; }; 642BA82D2CE50495005E9412 /* NewServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 642BA82C2CE50495005E9412 /* NewServerView.swift */; }; - 642BA8332CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.2-5kRGnUsa36hjwDTjUoXRa.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.2-5kRGnUsa36hjwDTjUoXRa.a */; }; + 642BA8332CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a */; }; 642BA8342CEB3D4B005E9412 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA82F2CEB3D4B005E9412 /* libffi.a */; }; 642BA8352CEB3D4B005E9412 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA8302CEB3D4B005E9412 /* libgmp.a */; }; 642BA8362CEB3D4B005E9412 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA8312CEB3D4B005E9412 /* libgmpxx.a */; }; - 642BA8372CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.2-5kRGnUsa36hjwDTjUoXRa-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.2-5kRGnUsa36hjwDTjUoXRa-ghc9.6.3.a */; }; + 642BA8372CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a */; }; 6432857C2925443C00FBE5C8 /* GroupPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */; }; 643B3B4E2CCFD6400083A2CF /* OperatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 643B3B4D2CCFD6400083A2CF /* OperatorView.swift */; }; 6440CA00288857A10062C672 /* CIEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440C9FF288857A10062C672 /* CIEventView.swift */; }; @@ -496,11 +496,11 @@ 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextInvitingContactMemberView.swift; sourceTree = ""; }; 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMemberCreatedContactView.swift; sourceTree = ""; }; 642BA82C2CE50495005E9412 /* NewServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewServerView.swift; sourceTree = ""; }; - 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.2-5kRGnUsa36hjwDTjUoXRa.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.2-5kRGnUsa36hjwDTjUoXRa.a"; sourceTree = ""; }; + 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a"; sourceTree = ""; }; 642BA82F2CEB3D4B005E9412 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; 642BA8302CEB3D4B005E9412 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 642BA8312CEB3D4B005E9412 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.2-5kRGnUsa36hjwDTjUoXRa-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.2-5kRGnUsa36hjwDTjUoXRa-ghc9.6.3.a"; sourceTree = ""; }; + 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a"; sourceTree = ""; }; 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupPreferencesView.swift; sourceTree = ""; }; 643B3B4D2CCFD6400083A2CF /* OperatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperatorView.swift; sourceTree = ""; }; 6440C9FF288857A10062C672 /* CIEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIEventView.swift; sourceTree = ""; }; @@ -672,8 +672,8 @@ CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, 642BA8342CEB3D4B005E9412 /* libffi.a in Frameworks */, 642BA8352CEB3D4B005E9412 /* libgmp.a in Frameworks */, - 642BA8372CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.2-5kRGnUsa36hjwDTjUoXRa-ghc9.6.3.a in Frameworks */, - 642BA8332CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.2-5kRGnUsa36hjwDTjUoXRa.a in Frameworks */, + 642BA8372CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a in Frameworks */, + 642BA8332CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a in Frameworks */, 642BA8362CEB3D4B005E9412 /* libgmpxx.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -754,8 +754,8 @@ 642BA82F2CEB3D4B005E9412 /* libffi.a */, 642BA8302CEB3D4B005E9412 /* libgmp.a */, 642BA8312CEB3D4B005E9412 /* libgmpxx.a */, - 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.2-5kRGnUsa36hjwDTjUoXRa-ghc9.6.3.a */, - 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.2-5kRGnUsa36hjwDTjUoXRa.a */, + 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a */, + 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a */, ); path = Libraries; sourceTree = ""; @@ -1931,7 +1931,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 248; + CURRENT_PROJECT_VERSION = 249; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1980,7 +1980,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 248; + CURRENT_PROJECT_VERSION = 249; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2021,7 +2021,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 248; + CURRENT_PROJECT_VERSION = 249; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2041,7 +2041,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 248; + CURRENT_PROJECT_VERSION = 249; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2066,7 +2066,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 248; + CURRENT_PROJECT_VERSION = 249; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2103,7 +2103,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 248; + CURRENT_PROJECT_VERSION = 249; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2140,7 +2140,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 248; + CURRENT_PROJECT_VERSION = 249; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2191,7 +2191,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 248; + CURRENT_PROJECT_VERSION = 249; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2242,7 +2242,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 248; + CURRENT_PROJECT_VERSION = 249; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2276,7 +2276,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 248; + CURRENT_PROJECT_VERSION = 249; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 08056080ea..0893c75520 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -24,11 +24,11 @@ android.nonTransitiveRClass=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.2-beta.2 -android.version_code=253 +android.version_name=6.2-beta.3 +android.version_code=254 -desktop.version_name=6.2-beta.2 -desktop.version_code=77 +desktop.version_name=6.2-beta.3 +desktop.version_code=78 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From 5f01dc1a3f6b922a3025a60f933942ea2fe4294d Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 2 Dec 2024 14:01:23 +0000 Subject: [PATCH 148/567] core: support business addresses and chats (#5272) * core: support business addresses and chats * types * connect plan, add link type * ios: toggle on address UI * make compile * todo * fix migration * types * comments * fix * remove * fix schema * comment * simplify * remove diff * comment * comment * diff * acceptBusinessJoinRequestAsync wip * comment * update * simplify types * remove business * wip * read/write columns * createBusinessRequestGroup * remove comments * read/write business_address column * validate that business address is not set to be incognito * replace contact card * update simplexmq * refactor * event when accepting business address request * sendGroupAutoReply * delete contact request earlier * test, fix * refactor * refactor2 --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --- .../Views/UserSettings/UserAddressView.swift | 37 +++- apps/ios/SimpleX.xcodeproj/project.pbxproj | 4 + apps/ios/SimpleXChat/APITypes.swift | 4 +- cabal.project | 2 +- docs/rfcs/2024-11-28-business-address.md | 29 +++ scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 1 + src/Simplex/Chat.hs | 165 +++++++++++++----- src/Simplex/Chat/Bot.hs | 2 +- src/Simplex/Chat/Controller.hs | 2 + .../Migrations/M20241128_business_chats.hs | 22 +++ src/Simplex/Chat/Migrations/chat_schema.sql | 5 +- src/Simplex/Chat/Protocol.hs | 7 +- src/Simplex/Chat/Store/Connections.hs | 2 +- src/Simplex/Chat/Store/Groups.hs | 90 ++++++++-- src/Simplex/Chat/Store/Migrations.hs | 4 +- src/Simplex/Chat/Store/Profiles.hs | 31 ++-- src/Simplex/Chat/Types.hs | 39 ++++- src/Simplex/Chat/Types/Preferences.hs | 27 +++ src/Simplex/Chat/View.hs | 14 +- tests/ChatTests/Profiles.hs | 47 ++++- tests/ChatTests/Utils.hs | 3 + tests/ProtocolTests.hs | 12 +- 23 files changed, 454 insertions(+), 97 deletions(-) create mode 100644 docs/rfcs/2024-11-28-business-address.md create mode 100644 src/Simplex/Chat/Migrations/M20241128_business_chats.hs diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift index 28301c5ddb..6bc3a221b2 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift @@ -16,6 +16,8 @@ struct UserAddressView: View { @EnvironmentObject var theme: AppTheme @State var shareViaProfile = false @State var autoCreate = false + @State private var aas = AutoAcceptState() + @State private var savedAAS = AutoAcceptState() @State private var showMailView = false @State private var mailViewResult: Result? = nil @State private var alert: UserAddressAlert? @@ -55,7 +57,15 @@ struct UserAddressView: View { if chatModel.userAddress == nil, autoCreate { createAddress() } + if let userAddress = chatModel.userAddress { + aas = AutoAcceptState(userAddress: userAddress) + savedAAS = aas + } } + .onChange(of: aas.enable) { aasEnabled in + if !aasEnabled { aas = AutoAcceptState() } + } + } private func userAddressView() -> some View { @@ -135,10 +145,23 @@ struct UserAddressView: View { // if MFMailComposeViewController.canSendMail() { // shareViaEmailButton(userAddress) // } + settingsRow("hand.wave", color: theme.colors.secondary) { + Toggle("Business address", isOn: $aas.business) + .onChange(of: aas.business) { ba in + if ba { + aas.enable = true + aas.incognito = false + } + } + } addressSettingsButton(userAddress) } header: { Text("For social media") .foregroundColor(theme.colors.secondary) + } footer: { + if aas.business { + Text("Add your team members to the conversations").foregroundColor(theme.colors.secondary) + } } Section { @@ -276,11 +299,13 @@ struct UserAddressView: View { private struct AutoAcceptState: Equatable { var enable = false var incognito = false + var business = false var welcomeText = "" - init(enable: Bool = false, incognito: Bool = false, welcomeText: String = "") { + init(enable: Bool = false, incognito: Bool = false, business: Bool = false, welcomeText: String = "") { self.enable = enable self.incognito = incognito + self.business = business self.welcomeText = welcomeText } @@ -288,6 +313,7 @@ private struct AutoAcceptState: Equatable { if let aa = userAddress.autoAccept { enable = true incognito = aa.acceptIncognito + business = aa.businessAddress == true if let msg = aa.autoReply { welcomeText = msg.text } else { @@ -296,6 +322,7 @@ private struct AutoAcceptState: Equatable { } else { enable = false incognito = false + business = false welcomeText = "" } } @@ -305,7 +332,7 @@ private struct AutoAcceptState: Equatable { var autoReply: MsgContent? = nil let s = welcomeText.trimmingCharacters(in: .whitespacesAndNewlines) if s != "" { autoReply = .text(s) } - return AutoAccept(acceptIncognito: incognito, autoReply: autoReply) + return AutoAccept(businessAddress: business, acceptIncognito: incognito, autoReply: autoReply) } return nil } @@ -373,7 +400,7 @@ struct UserAddressSettingsView: View { List { Section { shareWithContactsButton() - autoAcceptToggle() + autoAcceptToggle().disabled(aas.business) } if aas.enable { @@ -450,7 +477,9 @@ struct UserAddressSettingsView: View { private func autoAcceptSection() -> some View { Section { - acceptIncognitoToggle() + if !aas.business { + acceptIncognitoToggle() + } welcomeMessageEditor() saveAASButton() .disabled(aas == savedAAS) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 31917f1ab9..7e5a48013b 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -229,6 +229,7 @@ D741547A29AF90B00022400A /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D741547929AF90B00022400A /* PushKit.framework */; }; D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; + E504516F2CFA3BFB00DE3F74 /* ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = E504516E2CFA3BFB00DE3F74 /* ContextMenu.swift */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; }; E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; @@ -575,6 +576,7 @@ D741547729AF89AF0022400A /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; }; D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + E504516E2CFA3BFB00DE3F74 /* ContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenu.swift; sourceTree = ""; }; E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = ""; }; E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; @@ -791,6 +793,7 @@ 5C971E1F27AEBF7000C8A3CE /* Helpers */ = { isa = PBXGroup; children = ( + E504516E2CFA3BFB00DE3F74 /* ContextMenu.swift */, 5C971E2027AEBF8300C8A3CE /* ChatInfoImage.swift */, 5C7505A427B679EE00BE3227 /* NavLinkPlain.swift */, 5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */, @@ -1445,6 +1448,7 @@ CE984D4B2C36C5D500E3AEFF /* ChatItemClipShape.swift in Sources */, 64D0C2C629FAC1EC00B38D5F /* AddContactLearnMore.swift in Sources */, 5C3A88D127DF57800060F1C2 /* FramedItemView.swift in Sources */, + E504516F2CFA3BFB00DE3F74 /* ContextMenu.swift in Sources */, 5C65F343297D45E100B67AF3 /* VersionView.swift in Sources */, 64F1CC3B28B39D8600CD1FB1 /* IncognitoHelp.swift in Sources */, 5CB0BA90282713D900B3292C /* SimpleXInfo.swift in Sources */, diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 83c74178ba..954022c312 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -2103,10 +2103,12 @@ public struct UserContactLink: Decodable, Hashable { } public struct AutoAccept: Codable, Hashable { + public var businessAddress: Bool? // make not nullable public var acceptIncognito: Bool public var autoReply: MsgContent? - public init(acceptIncognito: Bool, autoReply: MsgContent? = nil) { + public init(businessAddress: Bool, acceptIncognito: Bool, autoReply: MsgContent? = nil) { + self.businessAddress = businessAddress self.acceptIncognito = acceptIncognito self.autoReply = autoReply } diff --git a/cabal.project b/cabal.project index 2246cfeb1d..b89dc764cb 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 601620bdde612ebdd33da2637d99b15ff32170c9 + tag: 38ad3c046e1bd5eb1ffe696dd24b10dd69001ba2 source-repository-package type: git diff --git a/docs/rfcs/2024-11-28-business-address.md b/docs/rfcs/2024-11-28-business-address.md new file mode 100644 index 0000000000..e41c904457 --- /dev/null +++ b/docs/rfcs/2024-11-28-business-address.md @@ -0,0 +1,29 @@ +# Business address + +## Problem + +When business uses a communication system for support and other business scenarios, it's important for the customer: +- to be able to talk to multiple people in the business, and know who they are. +- potentially, add friends or relatives to the conversation if this is about a group purchase. + +It's important for the business: +- to have bot accept incoming requests. +- to be able to add other people to the coversation, as transfer and as escalation. + +This is how all messaging support system works, and how WeChat business accounts work, but no messenger provides it. + +## Solution + +Make current contact addresses to support business mode. We already have all the elements for that. + +- connection requests will be accepted automatically (non-optionally), and auto-reply will be sent (if provided). +- the request sender will be made member, can be made admin later manually. +- the new group with the customer will be created on each request instead of direct conversation. + +Group will function differently from a normal group: +- Show business name and avatar to customer, customer name and avatar to business. +- Use different icon for customer and for the business if the avatar is not provided. +- Possibly, a sub-icon on business avatar for customers. +- Members added by business are marked as business, by customer as customer (not MVP). + +This functionality allows to develop support bots that automatically reply, potentially answer some questions, and add support agents as required, who can escalate further. diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 72d2ddd59b..7a812dbc6e 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."601620bdde612ebdd33da2637d99b15ff32170c9" = "0lgiphb9sf5i29d378pah24mhf7m8df75jk6asvw8ns527g4amj1"; + "https://github.com/simplex-chat/simplexmq.git"."38ad3c046e1bd5eb1ffe696dd24b10dd69001ba2" = "0nq2a2lklbxpc049zjxa5w8c63l9l9nf08jb7pny42nmah0mlc20"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 1b1a0b9753..4e339fc5da 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -153,6 +153,7 @@ library Simplex.Chat.Migrations.M20241023_chat_item_autoincrement_id Simplex.Chat.Migrations.M20241027_server_operators Simplex.Chat.Migrations.M20241125_indexes + Simplex.Chat.Migrations.M20241128_business_chats Simplex.Chat.Mobile Simplex.Chat.Mobile.File Simplex.Chat.Mobile.Shared diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index cc7fc992fb..92ec04d11b 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2065,6 +2065,8 @@ processChatCommand' vr = \case SetProfileAddress onOff -> withUser $ \User {userId} -> processChatCommand $ APISetProfileAddress userId onOff APIAddressAutoAccept userId autoAccept_ -> withUserId userId $ \user -> do + forM_ autoAccept_ $ \AutoAccept {businessAddress, acceptIncognito} -> + when (businessAddress && acceptIncognito) $ throwChatError $ CECommandError "requests to business address cannot be accepted incognito" contactLink <- withFastStore (\db -> updateUserAddressAutoAccept db user autoAccept_) pure $ CRUserContactLinkUpdated user contactLink AddressAutoAccept autoAccept_ -> withUser $ \User {userId} -> @@ -3007,7 +3009,7 @@ processChatCommand' vr = \case groupMemberId <- getGroupMemberIdByName db user groupId groupMemberName pure (groupId, groupMemberId) sendGrpInvitation :: User -> Contact -> GroupInfo -> GroupMember -> ConnReqInvitation -> CM () - sendGrpInvitation user ct@Contact {contactId, localDisplayName} gInfo@GroupInfo {groupId, groupProfile, membership} GroupMember {groupMemberId, memberId, memberRole = memRole} cReq = do + sendGrpInvitation user ct@Contact {contactId, localDisplayName} gInfo@GroupInfo {groupId, groupProfile, membership, businessChat} GroupMember {groupMemberId, memberId, memberRole = memRole} cReq = do currentMemCount <- withStore' $ \db -> getGroupCurrentMembersCount db user gInfo let GroupMember {memberRole = userRole, memberId = userMemberId} = membership groupInv = @@ -3016,6 +3018,7 @@ processChatCommand' vr = \case invitedMember = MemberIdRole memberId memRole, connRequest = cReq, groupProfile, + businessChat, groupLinkId = Nothing, groupSize = Just currentMemCount } @@ -3972,12 +3975,14 @@ acceptContactRequestAsync user cReq@UserContactRequest {agentInvitationId = Agen acceptGroupJoinRequestAsync :: User -> GroupInfo -> UserContactRequest -> GroupMemberRole -> Maybe IncognitoProfile -> CM GroupMember acceptGroupJoinRequestAsync user - gInfo@GroupInfo {groupProfile, membership} + gInfo@GroupInfo {groupProfile, membership, businessChat} ucr@UserContactRequest {agentInvitationId = AgentInvId invId, cReqChatVRange} gLinkMemRole incognitoProfile = do gVar <- asks random - (groupMemberId, memberId) <- withStore $ \db -> createAcceptedMember db gVar user gInfo ucr gLinkMemRole + (groupMemberId, memberId) <- withStore $ \db -> do + liftIO $ deleteContactRequestRec db user ucr + createAcceptedMember db gVar user gInfo ucr gLinkMemRole currentMemCount <- withStore' $ \db -> getGroupCurrentMembersCount db user gInfo let Profile {displayName} = profileToSendOnAccept user incognitoProfile True GroupMember {memberRole = userRole, memberId = userMemberId} = membership @@ -3988,6 +3993,7 @@ acceptGroupJoinRequestAsync fromMemberName = displayName, invitedMember = MemberIdRole memberId gLinkMemRole, groupProfile, + businessChat, groupSize = Just currentMemCount } subMode <- chatReadVar subscriptionMode @@ -3998,6 +4004,43 @@ acceptGroupJoinRequestAsync liftIO $ createAcceptedMemberConnection db user connIds chatV ucr groupMemberId subMode getGroupMemberById db vr user groupMemberId +acceptBusinessJoinRequestAsync :: User -> UserContactRequest -> CM GroupInfo +acceptBusinessJoinRequestAsync + user + ucr@UserContactRequest {agentInvitationId = AgentInvId invId, cReqChatVRange} = do + vr <- chatVersionRange + gVar <- asks random + let userProfile@Profile {displayName, preferences} = profileToSendOnAccept user Nothing True + groupPreferences = maybe defaultBusinessGroupPrefs businessGroupPrefs preferences + (gInfo, clientMember) <- withStore $ \db -> do + liftIO $ deleteContactRequestRec db user ucr + createBusinessRequestGroup db vr gVar user ucr groupPreferences + let GroupInfo {membership} = gInfo + GroupMember {memberRole = userRole, memberId = userMemberId} = membership + GroupMember {groupMemberId, memberId} = clientMember + msg = + XGrpLinkInv $ + GroupLinkInvitation + { fromMember = MemberIdRole userMemberId userRole, + fromMemberName = displayName, + invitedMember = MemberIdRole memberId GRMember, + groupProfile = businessGroupProfile userProfile groupPreferences, + -- This refers to the "title member" that defines the group name and profile. + -- This coincides with fromMember to be current user when accepting the connecting user, + -- but it will be different when inviting somebody else. + businessChat = Just $ BusinessChatInfo userMemberId BCBusiness, + groupSize = Just 1 + } + subMode <- chatReadVar subscriptionMode + let chatV = vr `peerConnChatVersion` cReqChatVRange + connIds <- agentAcceptContactAsync user True invId msg subMode PQSupportOff chatV + withStore' $ \db -> createAcceptedMemberConnection db user connIds chatV ucr groupMemberId subMode + pure gInfo + where + businessGroupProfile :: Profile -> GroupPreferences -> GroupProfile + businessGroupProfile Profile {displayName, fullName, image} groupPreferences = + GroupProfile {displayName, fullName, description = Nothing, image, groupPreferences = Just groupPreferences} + profileToSendOnAccept :: User -> Maybe IncognitoProfile -> Bool -> Profile profileToSendOnAccept user ip = userProfileToSend user (getIncognitoProfile <$> ip) Nothing where @@ -4683,15 +4726,14 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = CONF confId pqSupport _ connInfo -> do conn' <- processCONFpqSupport conn pqSupport -- [incognito] send saved profile + (conn'', inGroup) <- saveConnInfo conn' connInfo incognitoProfile <- forM customUserProfileId $ \profileId -> withStore (\db -> getProfileById db userId profileId) - let profileToSend = userProfileToSend user (fromLocalProfile <$> incognitoProfile) Nothing False - conn'' <- saveConnInfo conn' connInfo + let profileToSend = userProfileToSend user (fromLocalProfile <$> incognitoProfile) Nothing inGroup -- [async agent commands] no continuation needed, but command should be asynchronous for stability allowAgentConnectionAsync user conn'' confId $ XInfo profileToSend INFO pqSupport connInfo -> do processINFOpqSupport conn pqSupport - _conn' <- saveConnInfo conn connInfo - pure () + void $ saveConnInfo conn connInfo MSG meta _msgFlags _msgBody -> -- We are not saving message (saveDirectRcvMSG) as contact hasn't been created yet, -- chat item is also not created here @@ -4806,6 +4848,16 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = let p = userProfileToSend user (fromLocalProfile <$> incognitoProfile) (Just ct') False allowAgentConnectionAsync user conn'' confId $ XInfo p void $ withStore' $ \db -> resetMemberContactFields db ct' + XGrpLinkInv glInv -> do + -- XGrpLinkInv here means we are connecting via business contact card, so we replace contact with group + (gInfo, host) <- withStore $ \db -> do + liftIO $ deleteContactCardKeepConn db connId ct + createGroupInvitedViaLink db vr user conn'' glInv + -- [incognito] send saved profile + incognitoProfile <- forM customUserProfileId $ \pId -> withStore (\db -> getProfileById db userId pId) + let profileToSend = userProfileToSend user (fromLocalProfile <$> incognitoProfile) Nothing True + allowAgentConnectionAsync user conn'' confId $ XInfo profileToSend + toView $ CRBusinessLinkConnecting user gInfo host ct _ -> messageError "CONF for existing contact must have x.grp.mem.info or x.info" INFO pqSupport connInfo -> do processINFOpqSupport conn pqSupport @@ -4936,7 +4988,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = _ -> pure () processGroupMessage :: AEvent e -> ConnectionEntity -> Connection -> GroupInfo -> GroupMember -> CM () - processGroupMessage agentMsg connEntity conn@Connection {connId, connectionCode} gInfo@GroupInfo {groupId, groupProfile, membership, chatSettings} m = case agentMsg of + processGroupMessage agentMsg connEntity conn@Connection {connId, connChatVersion, connectionCode} gInfo@GroupInfo {groupId, groupProfile, membership, chatSettings} m = case agentMsg of INV (ACR _ cReq) -> withCompletedCommand conn agentMsg $ \CommandData {cmdFunction} -> case cReq of @@ -4977,6 +5029,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = invitedMember = MemberIdRole memberId memRole, connRequest = cReq, groupProfile, + businessChat = Nothing, groupLinkId = groupLinkId, groupSize = Just currentMemCount } @@ -5049,6 +5102,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = void . sendGroupMessage user gInfo members . XGrpMemNew $ memberInfo m sendIntroductions members when (groupFeatureAllowed SGFHistory gInfo) sendHistory + when (connChatVersion < batchSend2Version) $ sendGroupAutoReply members where sendXGrpLinkMem = do let profileMode = ExistingIncognito <$> incognitoMembershipProfile gInfo @@ -5311,9 +5365,12 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = OK -> -- [async agent commands] continuation on receiving OK when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () - JOINED _ -> + JOINED sqSecured -> -- [async agent commands] continuation on receiving JOINED - when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () + when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> + when sqSecured $ do + members <- withStore' $ \db -> getGroupMembers db vr user gInfo + when (connChatVersion >= batchSend2Version) $ sendGroupAutoReply members QCONT -> do continued <- continueSending connEntity conn when continued $ sendPendingGroupMessages user m conn @@ -5341,6 +5398,23 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = updateGroupItemsErrorStatus db msgId groupMemberId newStatus = do itemIds <- getChatItemIdsByAgentMsgId db connId msgId forM_ itemIds $ \itemId -> updateGroupMemSndStatus' db itemId groupMemberId newStatus + sendGroupAutoReply members = autoReplyMC >>= mapM_ send + where + autoReplyMC = do + let GroupInfo {businessChat} = gInfo + GroupMember {memberId = joiningMemberId} = m + case businessChat of + Just BusinessChatInfo {memberId, chatType = BCCustomer} + | joiningMemberId == memberId -> useReply <$> withStore (`getUserAddress` user) + where + useReply UserContactLink {autoAccept} = case autoAccept of + Just AutoAccept {businessAddress, autoReply} | businessAddress -> autoReply + _ -> Nothing + _ -> pure Nothing + send mc = do + msg <- sendGroupMessage' user gInfo members (XMsgNew $ MCSimple (extMsgContent mc Nothing)) + ci <- saveSndChatItem user (CDGroupSnd gInfo) msg (CISndMsgContent mc) + toView $ CRNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci] agentMsgDecryptError :: AgentCryptoError -> (MsgDecryptError, Word32) agentMsgDecryptError = \case @@ -5525,26 +5599,37 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = CORContact contact -> toView $ CRContactRequestAlreadyAccepted user contact CORRequest cReq -> do ucl <- withStore $ \db -> getUserContactLinkById db userId userContactLinkId - let (UserContactLink {autoAccept}, groupId_, gLinkMemRole) = ucl + let (UserContactLink {connReqContact, autoAccept}, groupId_, gLinkMemRole) = ucl + isSimplexTeam = sameConnReqContact connReqContact adminContactReq + v = maxVersion chatVRange case autoAccept of - Just AutoAccept {acceptIncognito} -> case groupId_ of - Nothing -> do - -- [incognito] generate profile to send, create connection with incognito profile - incognitoProfile <- if acceptIncognito then Just . NewIncognito <$> liftIO generateRandomProfile else pure Nothing - ct <- acceptContactRequestAsync user cReq incognitoProfile True reqPQSup - toView $ CRAcceptingContactRequest user ct - Just groupId -> do - gInfo <- withStore $ \db -> getGroupInfo db vr user groupId - let profileMode = ExistingIncognito <$> incognitoMembershipProfile gInfo - if maxVersion chatVRange >= groupFastLinkJoinVersion - then do - mem <- acceptGroupJoinRequestAsync user gInfo cReq gLinkMemRole profileMode - createInternalChatItem user (CDGroupRcv gInfo mem) (CIRcvGroupEvent RGEInvitedViaGroupLink) Nothing - toView $ CRAcceptingGroupJoinRequestMember user gInfo mem - else do - -- TODO v5.7 remove old API (or v6.0?) - ct <- acceptContactRequestAsync user cReq profileMode False PQSupportOff - toView $ CRAcceptingGroupJoinRequest user gInfo ct + Just AutoAccept {acceptIncognito, businessAddress} + | businessAddress -> + if v < groupFastLinkJoinVersion || (isSimplexTeam && v < businessChatsVersion) + then do + ct <- acceptContactRequestAsync user cReq Nothing True reqPQSup + toView $ CRAcceptingContactRequest user ct + else do + gInfo <- acceptBusinessJoinRequestAsync user cReq + toView $ CRAcceptingBusinessRequest user gInfo + | otherwise -> case groupId_ of + Nothing -> do + -- [incognito] generate profile to send, create connection with incognito profile + incognitoProfile <- if acceptIncognito then Just . NewIncognito <$> liftIO generateRandomProfile else pure Nothing + ct <- acceptContactRequestAsync user cReq incognitoProfile True reqPQSup + toView $ CRAcceptingContactRequest user ct + Just groupId -> do + gInfo <- withStore $ \db -> getGroupInfo db vr user groupId + let profileMode = ExistingIncognito <$> incognitoMembershipProfile gInfo + if v >= groupFastLinkJoinVersion + then do + mem <- acceptGroupJoinRequestAsync user gInfo cReq gLinkMemRole profileMode + createInternalChatItem user (CDGroupRcv gInfo mem) (CIRcvGroupEvent RGEInvitedViaGroupLink) Nothing + toView $ CRAcceptingGroupJoinRequestMember user gInfo mem + else do + -- TODO v5.7 remove old API (or v6.0?) + ct <- acceptContactRequestAsync user cReq profileMode False PQSupportOff + toView $ CRAcceptingGroupJoinRequest user gInfo ct _ -> toView $ CRReceivedContactRequest user cReq memberCanSend :: GroupMember -> CM () -> CM () @@ -6353,9 +6438,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = xInfoMember gInfo m p' brokerTs = void $ processMemberProfileUpdate gInfo m p' True (Just brokerTs) xGrpLinkMem :: GroupInfo -> GroupMember -> Connection -> Profile -> CM () - xGrpLinkMem gInfo@GroupInfo {membership} m@GroupMember {groupMemberId, memberCategory} Connection {viaGroupLink} p' = do + xGrpLinkMem gInfo@GroupInfo {membership, businessChat} m@GroupMember {groupMemberId, memberCategory} Connection {viaGroupLink} p' = do xGrpLinkMemReceived <- withStore $ \db -> getXGrpLinkMemReceived db groupMemberId - if viaGroupLink && isNothing (memberContactId m) && memberCategory == GCHostMember && not xGrpLinkMemReceived + if (viaGroupLink || isJust businessChat) && isNothing (memberContactId m) && memberCategory == GCHostMember && not xGrpLinkMemReceived then do m' <- processMemberProfileUpdate gInfo m p' False Nothing withStore' $ \db -> setXGrpLinkMemReceived db groupMemberId True @@ -6652,7 +6737,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = toView $ CRContactAndMemberAssociated user c2 g m1 c2' pure c2' - saveConnInfo :: Connection -> ConnInfo -> CM Connection + saveConnInfo :: Connection -> ConnInfo -> CM (Connection, Bool) saveConnInfo activeConn connInfo = do ChatMessage {chatVRange, chatMsgEvent} <- parseChatMessage activeConn connInfo conn' <- updatePeerChatVRange activeConn chatVRange @@ -6661,13 +6746,13 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = let contactUsed = connDirect activeConn ct <- withStore $ \db -> createDirectContact db user conn' p contactUsed toView $ CRContactConnecting user ct - pure conn' + pure (conn', False) XGrpLinkInv glInv -> do (gInfo, host) <- withStore $ \db -> createGroupInvitedViaLink db vr user conn' glInv toView $ CRGroupLinkConnecting user gInfo host - pure conn' + pure (conn', True) -- TODO show/log error, other events in SMP confirmation - _ -> pure conn' + _ -> pure (conn', False) xGrpMemNew :: GroupInfo -> GroupMember -> MemberInfo -> RcvMessage -> UTCTime -> CM () xGrpMemNew gInfo m memInfo@(MemberInfo memId memRole _ _) msg brokerTs = do @@ -8683,11 +8768,11 @@ chatCommandP = dbKeyP = nonEmptyKey <$?> strP nonEmptyKey k@(DBEncryptionKey s) = if BA.null s then Left "empty key" else Right k dbEncryptionConfig currentKey newKey = DBEncryptionConfig {currentKey, newKey, keepKey = Just False} - autoAcceptP = - ifM - onOffP - (Just <$> (AutoAccept <$> (" incognito=" *> onOffP <|> pure False) <*> optional (A.space *> msgContentP))) - (pure Nothing) + autoAcceptP = ifM onOffP (Just <$> (businessAA <|> addressAA)) (pure Nothing) + where + addressAA = AutoAccept False <$> (" incognito=" *> onOffP <|> pure False) <*> autoReply + businessAA = AutoAccept True <$> (" business" *> pure False) <*> autoReply + autoReply = optional (A.space *> msgContentP) rcCtrlAddressP = RCCtrlAddress <$> ("addr=" *> strP) <*> (" iface=" *> (jsonP <|> text1P)) text1P = safeDecodeUtf8 <$> A.takeTill (== ' ') char_ = optional . A.char diff --git a/src/Simplex/Chat/Bot.hs b/src/Simplex/Chat/Bot.hs index 8c0978a98f..2f7e2f2abd 100644 --- a/src/Simplex/Chat/Bot.hs +++ b/src/Simplex/Chat/Bot.hs @@ -56,7 +56,7 @@ initializeBotAddress' logAddress cc = do where showBotAddress uri = do when logAddress $ putStrLn $ "Bot's contact address is: " <> B.unpack (strEncode uri) - void $ sendChatCmd cc $ AddressAutoAccept $ Just AutoAccept {acceptIncognito = False, autoReply = Nothing} + void $ sendChatCmd cc $ AddressAutoAccept $ Just AutoAccept {businessAddress = False, acceptIncognito = False, autoReply = Nothing} sendMessage :: ChatController -> Contact -> Text -> IO () sendMessage cc ct = sendComposedMessage cc ct Nothing . MCText diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index d208efce77..0ab3ba5652 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -639,6 +639,7 @@ data ChatResponse | CRContactRequestRejected {user :: User, contactRequest :: UserContactRequest} | CRUserAcceptedGroupSent {user :: User, groupInfo :: GroupInfo, hostContact :: Maybe Contact} | CRGroupLinkConnecting {user :: User, groupInfo :: GroupInfo, hostMember :: GroupMember} + | CRBusinessLinkConnecting {user :: User, groupInfo :: GroupInfo, hostMember :: GroupMember, fromContact :: Contact} | CRUserDeletedMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember} | CRGroupsList {user :: User, groups :: [(GroupInfo, GroupSummary)]} | CRSentGroupInvitation {user :: User, groupInfo :: GroupInfo, contact :: Contact, member :: GroupMember} @@ -665,6 +666,7 @@ data ChatResponse | CRUserContactLinkDeleted {user :: User} | CRReceivedContactRequest {user :: User, contactRequest :: UserContactRequest} | CRAcceptingContactRequest {user :: User, contact :: Contact} + | CRAcceptingBusinessRequest {user :: User, groupInfo :: GroupInfo} | CRContactAlreadyExists {user :: User, contact :: Contact} | CRContactRequestAlreadyAccepted {user :: User, contact :: Contact} | CRLeftMemberUser {user :: User, groupInfo :: GroupInfo} diff --git a/src/Simplex/Chat/Migrations/M20241128_business_chats.hs b/src/Simplex/Chat/Migrations/M20241128_business_chats.hs new file mode 100644 index 0000000000..f068b1bd81 --- /dev/null +++ b/src/Simplex/Chat/Migrations/M20241128_business_chats.hs @@ -0,0 +1,22 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Migrations.M20241128_business_chats where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20241128_business_chats :: Query +m20241128_business_chats = + [sql| +ALTER TABLE user_contact_links ADD business_address INTEGER DEFAULT 0; +ALTER TABLE groups ADD COLUMN business_member_id BLOB NULL; +ALTER TABLE groups ADD COLUMN business_chat TEXT NULL; +|] + +down_m20241128_business_chats :: Query +down_m20241128_business_chats = + [sql| +ALTER TABLE user_contact_links DROP COLUMN business_address; +ALTER TABLE groups DROP COLUMN business_member_id; +ALTER TABLE groups DROP COLUMN business_chat; +|] diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index 6f944157c1..460b348b4d 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -127,7 +127,9 @@ CREATE TABLE groups( via_group_link_uri_hash BLOB, user_member_profile_sent_at TEXT, custom_data BLOB, - ui_themes TEXT, -- received + ui_themes TEXT, + business_member_id BLOB NULL, + business_chat TEXT NULL, -- received FOREIGN KEY(user_id, local_display_name) REFERENCES display_names(user_id, local_display_name) ON DELETE CASCADE @@ -309,6 +311,7 @@ CREATE TABLE user_contact_links( auto_accept_incognito INTEGER DEFAULT 0 CHECK(auto_accept_incognito NOT NULL), group_link_id BLOB, group_link_member_role TEXT NULL, + business_address INTEGER DEFAULT 0, UNIQUE(user_id, local_display_name) ); CREATE TABLE contact_requests( diff --git a/src/Simplex/Chat/Protocol.hs b/src/Simplex/Chat/Protocol.hs index ea39293b9f..8afefdc850 100644 --- a/src/Simplex/Chat/Protocol.hs +++ b/src/Simplex/Chat/Protocol.hs @@ -66,12 +66,13 @@ import Simplex.Messaging.Version hiding (version) -- 7 - update member profiles (1/15/2024) -- 8 - compress messages and PQ e2e encryption (2024-03-08) -- 9 - batch sending in direct connections (2024-07-24) +-- 10 - business chats (2024-11-29) -- This should not be used directly in code, instead use `maxVersion chatVRange` from ChatConfig. -- This indirection is needed for backward/forward compatibility testing. -- Testing with real app versions is still needed, as tests use the current code with different version ranges, not the old code. currentChatVersion :: VersionChat -currentChatVersion = VersionChat 9 +currentChatVersion = VersionChat 10 -- This should not be used directly in code, instead use `chatVRange` from ChatConfig (see comment above) supportedChatVRange :: VersionRangeChat @@ -110,6 +111,10 @@ pqEncryptionCompressionVersion = VersionChat 8 batchSend2Version :: VersionChat batchSend2Version = VersionChat 9 +-- supports differentiating business chats when joining contact addresses +businessChatsVersion :: VersionChat +businessChatsVersion = VersionChat 10 + agentToChatVersion :: VersionSMPA -> VersionChat agentToChatVersion v | v < pqdrSMPAgentVersion = initialChatVersion diff --git a/src/Simplex/Chat/Store/Connections.hs b/src/Simplex/Chat/Store/Connections.hs index 2c7543f08a..fe52c6d7b7 100644 --- a/src/Simplex/Chat/Store/Connections.hs +++ b/src/Simplex/Chat/Store/Connections.hs @@ -123,7 +123,7 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, - g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.ui_themes, g.custom_data, + g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data, -- GroupInfo {membership} mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 142c702f77..b07adf407b 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -30,6 +30,7 @@ module Simplex.Chat.Store.Groups getGroupAndMember, createNewGroup, createGroupInvitation, + deleteContactCardKeepConn, createGroupInvitedViaLink, setViaGroupLinkHash, setGroupInvitationChatItemId, @@ -62,6 +63,7 @@ module Simplex.Chat.Store.Groups createNewContactMemberAsync, createAcceptedMember, createAcceptedMemberConnection, + createBusinessRequestGroup, getContactViaMember, setNewContactMemberConnRequest, getMemberInvitation, @@ -153,19 +155,20 @@ import Simplex.Messaging.Util (eitherToMaybe, ($>>=), (<$$>)) import Simplex.Messaging.Version import UnliftIO.STM -type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe MsgFilter, Maybe Bool, Bool, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime, Maybe UIThemeEntityOverrides, Maybe CustomData) :. GroupMemberRow +type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe MsgFilter, Maybe Bool, Bool, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime, Maybe MemberId, Maybe BusinessChatType, Maybe UIThemeEntityOverrides, Maybe CustomData) :. GroupMemberRow type GroupMemberRow = ((Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, Bool, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences)) type MaybeGroupMemberRow = ((Maybe Int64, Maybe Int64, Maybe MemberId, Maybe VersionChat, Maybe VersionChat, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe Bool, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId, Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe ImageData, Maybe ConnReqContact, Maybe LocalAlias, Maybe Preferences)) toGroupInfo :: VersionRangeChat -> Int64 -> GroupInfoRow -> GroupInfo -toGroupInfo vr userContactId ((groupId, localDisplayName, displayName, fullName, description, image, hostConnCustomUserProfileId, enableNtfs_, sendRcpts, favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt, uiThemes, customData) :. userMemberRow) = +toGroupInfo vr userContactId ((groupId, localDisplayName, displayName, fullName, description, image, hostConnCustomUserProfileId, enableNtfs_, sendRcpts, favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt, businessMemberId, businessChatType, uiThemes, customData) :. userMemberRow) = let membership = (toGroupMember userContactId userMemberRow) {memberChatVRange = vr} chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts, favorite} fullGroupPreferences = mergeGroupPreferences groupPreferences groupProfile = GroupProfile {displayName, fullName, description, image, groupPreferences} - in GroupInfo {groupId, localDisplayName, groupProfile, fullGroupPreferences, membership, hostConnCustomUserProfileId, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, uiThemes, customData} + businessChat = BusinessChatInfo <$> businessMemberId <*> businessChatType + in GroupInfo {groupId, localDisplayName, groupProfile, businessChat, fullGroupPreferences, membership, hostConnCustomUserProfileId, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, uiThemes, customData} toGroupMember :: Int64 -> GroupMemberRow -> GroupMember toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, showMessages, memberRestriction_) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, preferences)) = @@ -276,7 +279,7 @@ getGroupAndMember db User {userId, userContactId} groupMemberId vr = -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, - g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.ui_themes, g.custom_data, + g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data, -- GroupInfo {membership} mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, @@ -342,6 +345,7 @@ createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = Exc { groupId, localDisplayName = ldn, groupProfile, + businessChat = Nothing, fullGroupPreferences, membership, hostConnCustomUserProfileId = Nothing, @@ -357,7 +361,7 @@ createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = Exc -- | creates a new group record for the group the current user was invited to, or returns an existing one createGroupInvitation :: DB.Connection -> VersionRangeChat -> User -> Contact -> GroupInvitation -> Maybe ProfileId -> ExceptT StoreError IO (GroupInfo, GroupMemberId) createGroupInvitation _ _ _ Contact {localDisplayName, activeConn = Nothing} _ _ = throwError $ SEContactNotReady localDisplayName -createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activeConn = Just Connection {customUserProfileId, peerChatVRange}} GroupInvitation {fromMember, invitedMember, connRequest, groupProfile} incognitoProfileId = do +createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activeConn = Just Connection {customUserProfileId, peerChatVRange}} GroupInvitation {fromMember, invitedMember, connRequest, groupProfile, businessChat} incognitoProfileId = do liftIO getInvitationGroupId_ >>= \case Nothing -> createGroupInvitation_ Just gId -> do @@ -395,10 +399,10 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ [sql| INSERT INTO groups (group_profile_id, local_display_name, inv_queue_info, host_conn_custom_user_profile_id, user_id, enable_ntfs, - created_at, updated_at, chat_ts, user_member_profile_sent_at) - VALUES (?,?,?,?,?,?,?,?,?,?) + created_at, updated_at, chat_ts, user_member_profile_sent_at, business_member_id, business_chat) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?) |] - (profileId, localDisplayName, connRequest, customUserProfileId, userId, True, currentTs, currentTs, currentTs, currentTs) + ((profileId, localDisplayName, connRequest, customUserProfileId, userId, True, currentTs, currentTs, currentTs, currentTs) :. businessChatTuple businessChat) insertedRowId db let hostVRange = adjustedMemberVRange vr peerChatVRange GroupMember {groupMemberId} <- createContactMemberInv_ db user groupId Nothing contact fromMember GCHostMember GSMemInvited IBUnknown Nothing currentTs hostVRange @@ -409,6 +413,7 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ { groupId, localDisplayName, groupProfile, + businessChat = Nothing, fullGroupPreferences, membership, hostConnCustomUserProfileId = customUserProfileId, @@ -423,6 +428,11 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ groupMemberId ) +businessChatTuple :: Maybe BusinessChatInfo -> (Maybe MemberId, Maybe BusinessChatType) +businessChatTuple = \case + Just BusinessChatInfo {memberId, chatType} -> (Just memberId, Just chatType) + Nothing -> (Nothing, Nothing) + adjustedMemberVRange :: VersionRangeChat -> VersionRangeChat -> VersionRangeChat adjustedMemberVRange chatVR vr@(VersionRange minV maxV) = let maxV' = min maxV (maxVersion chatVR) @@ -497,13 +507,19 @@ createContactMemberInv_ db User {userId, userContactId} groupId invitedByGroupMe ) pure $ Right incognitoLdn +deleteContactCardKeepConn :: DB.Connection -> Int64 -> Contact -> IO () +deleteContactCardKeepConn db connId Contact {contactId, profile = LocalProfile {profileId}} = do + DB.execute db "UPDATE connections SET contact_id = NULL WHERE connection_id = ?" (Only connId) + DB.execute db "DELETE FROM contacts WHERE contact_id = ?" (Only contactId) + DB.execute db "DELETE FROM contact_profiles WHERE contact_profile_id = ?" (Only profileId) + createGroupInvitedViaLink :: DB.Connection -> VersionRangeChat -> User -> Connection -> GroupLinkInvitation -> ExceptT StoreError IO (GroupInfo, GroupMember) createGroupInvitedViaLink db vr user@User {userId, userContactId} Connection {connId, customUserProfileId} - GroupLinkInvitation {fromMember, fromMemberName, invitedMember, groupProfile} = do + GroupLinkInvitation {fromMember, fromMemberName, invitedMember, groupProfile, businessChat} = do currentTs <- liftIO getCurrentTime groupId <- insertGroup_ currentTs hostMemberId <- insertHost_ currentTs groupId @@ -527,10 +543,10 @@ createGroupInvitedViaLink [sql| INSERT INTO groups (group_profile_id, local_display_name, host_conn_custom_user_profile_id, user_id, enable_ntfs, - created_at, updated_at, chat_ts, user_member_profile_sent_at) - VALUES (?,?,?,?,?,?,?,?,?) + created_at, updated_at, chat_ts, user_member_profile_sent_at, business_member_id, business_chat) + VALUES (?,?,?,?,?,?,?,?,?,?,?) |] - (profileId, localDisplayName, customUserProfileId, userId, True, currentTs, currentTs, currentTs, currentTs) + ((profileId, localDisplayName, customUserProfileId, userId, True, currentTs, currentTs, currentTs, currentTs) :. businessChatTuple businessChat) insertedRowId db insertHost_ currentTs groupId = do let fromMemberProfile = profileFromName fromMemberName @@ -637,7 +653,7 @@ getUserGroupDetails db vr User {userId, userContactId} _contactId_ search_ = SELECT g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, - g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.ui_themes, g.custom_data, + g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data, mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences FROM groups g @@ -879,9 +895,7 @@ createAcceptedMember User {userId, userContactId} GroupInfo {groupId, membership} UserContactRequest {cReqChatVRange, localDisplayName, profileId} - memberRole = do - liftIO $ - DB.execute db "DELETE FROM contact_requests WHERE user_id = ? AND local_display_name = ?" (userId, localDisplayName) + memberRole = createWithRandomId gVar $ \memId -> do createdAt <- liftIO getCurrentTime insertMember_ (MemberId memId) createdAt @@ -917,6 +931,46 @@ createAcceptedMemberConnection Connection {connId} <- createConnection_ db userId ConnMember (Just groupMemberId) agentConnId ConnNew chatV cReqChatVRange Nothing (Just userContactLinkId) Nothing 0 createdAt subMode PQSupportOff setCommandConnId db user cmdId connId +createBusinessRequestGroup :: DB.Connection -> VersionRangeChat -> TVar ChaChaDRG -> User -> UserContactRequest -> GroupPreferences -> ExceptT StoreError IO (GroupInfo, GroupMember) +createBusinessRequestGroup + db + vr + gVar + user@User {userId} + ucr@UserContactRequest {profile} + groupPreferences = do + currentTs <- liftIO getCurrentTime + groupInfo <- insertGroup_ currentTs + (groupMemberId, memberId) <- createAcceptedMember db gVar user groupInfo ucr GRMember + liftIO $ setBusinessMemberId groupInfo memberId + acceptedMember <- getGroupMemberById db vr user groupMemberId + pure (groupInfo, acceptedMember) + where + insertGroup_ currentTs = ExceptT $ do + let Profile {displayName, fullName, image} = profile + withLocalDisplayName db userId displayName $ \localDisplayName -> runExceptT $ do + groupId <- liftIO $ do + DB.execute + db + "INSERT INTO group_profiles (display_name, full_name, image, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?)" + (displayName, fullName, image, userId, groupPreferences, currentTs, currentTs) + profileId <- insertedRowId db + DB.execute + db + [sql| + INSERT INTO groups + (group_profile_id, local_display_name, user_id, enable_ntfs, + created_at, updated_at, chat_ts, user_member_profile_sent_at, business_chat) + VALUES (?,?,?,?,?,?,?,?,?) + |] + (profileId, localDisplayName, userId, True, currentTs, currentTs, currentTs, currentTs, BCCustomer) + insertedRowId db + memberId <- liftIO $ encodedRandomBytes gVar 12 + void $ createContactMemberInv_ db user groupId Nothing user (MemberIdRole (MemberId memberId) GROwner) GCUserMember GSMemCreator IBUser Nothing currentTs vr + getGroupInfo db vr user groupId + setBusinessMemberId GroupInfo {groupId} businessMemberId = do + DB.execute db "UPDATE groups SET business_member_id = ? WHERE group_id = ?" (businessMemberId, groupId) + getContactViaMember :: DB.Connection -> VersionRangeChat -> User -> GroupMember -> ExceptT StoreError IO Contact getContactViaMember db vr user@User {userId} GroupMember {groupMemberId} = do contactId <- @@ -1315,7 +1369,7 @@ getViaGroupMember db vr User {userId, userContactId} Contact {contactId} = -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, - g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.ui_themes, g.custom_data, + g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data, -- GroupInfo {membership} mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, @@ -1411,7 +1465,7 @@ getGroupInfo db vr User {userId, userContactId} groupId = -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, - g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.ui_themes, g.custom_data, + g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data, -- GroupMember - membership mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, diff --git a/src/Simplex/Chat/Store/Migrations.hs b/src/Simplex/Chat/Store/Migrations.hs index 9a91c7f970..6654dec034 100644 --- a/src/Simplex/Chat/Store/Migrations.hs +++ b/src/Simplex/Chat/Store/Migrations.hs @@ -117,6 +117,7 @@ import Simplex.Chat.Migrations.M20241010_contact_requests_contact_id import Simplex.Chat.Migrations.M20241023_chat_item_autoincrement_id import Simplex.Chat.Migrations.M20241027_server_operators import Simplex.Chat.Migrations.M20241125_indexes +import Simplex.Chat.Migrations.M20241128_business_chats import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -233,7 +234,8 @@ schemaMigrations = ("20241010_contact_requests_contact_id", m20241010_contact_requests_contact_id, Just down_m20241010_contact_requests_contact_id), ("20241023_chat_item_autoincrement_id", m20241023_chat_item_autoincrement_id, Just down_m20241023_chat_item_autoincrement_id), ("20241027_server_operators", m20241027_server_operators, Just down_m20241027_server_operators), - ("20241125_indexes", m20241125_indexes, Just down_m20241125_indexes) + ("20241125_indexes", m20241125_indexes, Just down_m20241125_indexes), + ("20241128_business_chats", m20241128_business_chats, Just down_m20241128_business_chats) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index ec657fd6f7..e88cf39feb 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -445,7 +445,8 @@ data UserContactLink = UserContactLink deriving (Show) data AutoAccept = AutoAccept - { acceptIncognito :: IncognitoEnabled, + { businessAddress :: Bool, -- possibly, it can be wrapped together with acceptIncognito, or AutoAccept made sum type + acceptIncognito :: IncognitoEnabled, autoReply :: Maybe MsgContent } deriving (Show) @@ -454,10 +455,10 @@ $(J.deriveJSON defaultJSON ''AutoAccept) $(J.deriveJSON defaultJSON ''UserContactLink) -toUserContactLink :: (ConnReqContact, Bool, IncognitoEnabled, Maybe MsgContent) -> UserContactLink -toUserContactLink (connReq, autoAccept, acceptIncognito, autoReply) = +toUserContactLink :: (ConnReqContact, Bool, Bool, IncognitoEnabled, Maybe MsgContent) -> UserContactLink +toUserContactLink (connReq, autoAccept, businessAddress, acceptIncognito, autoReply) = UserContactLink connReq $ - if autoAccept then Just AutoAccept {acceptIncognito, autoReply} else Nothing + if autoAccept then Just AutoAccept {businessAddress, acceptIncognito, autoReply} else Nothing getUserAddress :: DB.Connection -> User -> ExceptT StoreError IO UserContactLink getUserAddress db User {userId} = @@ -465,7 +466,7 @@ getUserAddress db User {userId} = DB.query db [sql| - SELECT conn_req_contact, auto_accept, auto_accept_incognito, auto_reply_msg_content + SELECT conn_req_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content FROM user_contact_links WHERE user_id = ? AND local_display_name = '' AND group_id IS NULL |] @@ -477,7 +478,7 @@ getUserContactLinkById db userId userContactLinkId = DB.query db [sql| - SELECT conn_req_contact, auto_accept, auto_accept_incognito, auto_reply_msg_content, group_id, group_link_member_role + SELECT conn_req_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content, group_id, group_link_member_role FROM user_contact_links WHERE user_id = ? AND user_contact_link_id = ? @@ -490,7 +491,7 @@ getUserContactLinkByConnReq db User {userId} (cReqSchema1, cReqSchema2) = DB.query db [sql| - SELECT conn_req_contact, auto_accept, auto_accept_incognito, auto_reply_msg_content + SELECT conn_req_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content FROM user_contact_links WHERE user_id = ? AND conn_req_contact IN (?,?) |] @@ -522,13 +523,13 @@ updateUserAddressAutoAccept db user@User {userId} autoAccept = do db [sql| UPDATE user_contact_links - SET auto_accept = ?, auto_accept_incognito = ?, auto_reply_msg_content = ? + SET auto_accept = ?, business_address = ?, auto_accept_incognito = ?, auto_reply_msg_content = ? WHERE user_id = ? AND local_display_name = '' AND group_id IS NULL |] (ucl :. Only userId) ucl = case autoAccept of - Just AutoAccept {acceptIncognito, autoReply} -> (True, acceptIncognito, autoReply) - _ -> (False, False, Nothing) + Just AutoAccept {businessAddress, acceptIncognito, autoReply} -> (True, businessAddress, acceptIncognito, autoReply) + _ -> (False, False, False, Nothing) getProtocolServers :: forall p. ProtocolTypeI p => DB.Connection -> SProtocolType p -> User -> IO [UserServer p] getProtocolServers db p User {userId} = @@ -589,7 +590,7 @@ getServerOperators db = do let conditionsAction = usageConditionsAction ops currentConditions now pure ServerOperatorConditions {serverOperators = ops, currentConditions, conditionsAction} -getUserServers :: DB.Connection -> User -> ExceptT StoreError IO ([Maybe ServerOperator], [UserServer 'PSMP], [UserServer 'PXFTP]) +getUserServers :: DB.Connection -> User -> ExceptT StoreError IO ([Maybe ServerOperator], [UserServer 'PSMP], [UserServer 'PXFTP]) getUserServers db user = (,,) <$> (map Just . serverOperators <$> getServerOperators db) @@ -620,7 +621,8 @@ getUpdateServerOperators db presetOps newUser = do mapM_ insertConditions condsToAdd latestAcceptedConds_ <- getLatestAcceptedConditions db ops <- updatedServerOperators presetOps <$> getServerOperators_ db - forM ops $ traverse $ mapM $ \(ASO _ op) -> -- traverse for tuple, mapM for Maybe + forM ops $ traverse $ mapM $ \(ASO _ op) -> + -- traverse for tuple, mapM for Maybe case operatorId op of DBNewEntity -> do op' <- insertOperator op @@ -765,8 +767,9 @@ acceptConditions db condId opIds acceptedAt = do liftIO $ forM_ operators $ \op -> acceptConditions_ db op conditionsCommit ts where getServerOperator_ opId = - ExceptT $ firstRow toServerOperator (SEOperatorNotFound opId) $ - DB.query db (serverOperatorQuery <> " WHERE server_operator_id = ?") (Only opId) + ExceptT $ + firstRow toServerOperator (SEOperatorNotFound opId) $ + DB.query db (serverOperatorQuery <> " WHERE server_operator_id = ?") (Only opId) acceptConditions_ :: DB.Connection -> ServerOperator -> Text -> Maybe UTCTime -> IO () acceptConditions_ db ServerOperator {operatorId, operatorTag} conditionsCommit acceptedAt = diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index 36bf9edb52..cc98b4101d 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -52,7 +52,7 @@ import Simplex.Messaging.Agent.Protocol (ACorrId, AEventTag (..), AEvtTag (..), import Simplex.Messaging.Crypto.File (CryptoFileArgs (..)) import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport, pattern PQEncOff) import Simplex.Messaging.Encoding.String -import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, sumTypeJSON, taggedObjectJSON) +import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, sumTypeJSON) import Simplex.Messaging.Util (safeDecodeUtf8, (<$?>)) import Simplex.Messaging.Version import Simplex.Messaging.Version.Internal @@ -371,6 +371,7 @@ data GroupInfo = GroupInfo { groupId :: GroupId, localDisplayName :: GroupName, groupProfile :: GroupProfile, + businessChat :: Maybe BusinessChatInfo, fullGroupPreferences :: FullGroupPreferences, membership :: GroupMember, hostConnCustomUserProfileId :: Maybe ProfileId, @@ -384,6 +385,24 @@ data GroupInfo = GroupInfo } deriving (Eq, Show) +data BusinessChatType + = BCBusiness -- used on the customer side + | BCCustomer -- used on the business side + deriving (Eq, Show) + +instance TextEncoding BusinessChatType where + textEncode = \case + BCBusiness -> "business" + BCCustomer -> "customer" + textDecode = \case + "business" -> Just BCBusiness + "customer" -> Just BCCustomer + _ -> Nothing + +instance FromField BusinessChatType where fromField = fromTextField_ textDecode + +instance ToField BusinessChatType where toField = toField . textEncode + groupName' :: GroupInfo -> GroupName groupName' GroupInfo {localDisplayName = g} = g @@ -598,6 +617,7 @@ data GroupInvitation = GroupInvitation invitedMember :: MemberIdRole, connRequest :: ConnReqInvitation, groupProfile :: GroupProfile, + businessChat :: Maybe BusinessChatInfo, groupLinkId :: Maybe GroupLinkId, groupSize :: Maybe Int } @@ -608,6 +628,7 @@ data GroupLinkInvitation = GroupLinkInvitation fromMemberName :: ContactName, invitedMember :: MemberIdRole, groupProfile :: GroupProfile, + businessChat :: Maybe BusinessChatInfo, groupSize :: Maybe Int } deriving (Eq, Show) @@ -632,6 +653,12 @@ data MemberInfo = MemberInfo } deriving (Eq, Show) +data BusinessChatInfo = BusinessChatInfo + { memberId :: MemberId, + chatType :: BusinessChatType + } + deriving (Eq, Show) + memberInfo :: GroupMember -> MemberInfo memberInfo GroupMember {memberId, memberRole, memberProfile, activeConn} = MemberInfo @@ -1696,6 +1723,10 @@ $(JQ.deriveJSON (enumJSON $ dropPrefix "MF") ''MsgFilter) $(JQ.deriveJSON defaultJSON ''ChatSettings) +$(JQ.deriveJSON (enumJSON $ dropPrefix "BC") ''BusinessChatType) + +$(JQ.deriveJSON defaultJSON ''BusinessChatInfo) + $(JQ.deriveJSON defaultJSON ''GroupInfo) $(JQ.deriveJSON defaultJSON ''Group) @@ -1706,18 +1737,18 @@ instance FromField MsgFilter where fromField = fromIntField_ msgFilterIntP instance ToField MsgFilter where toField = toField . msgFilterInt -$(JQ.deriveJSON (taggedObjectJSON $ dropPrefix "CRData") ''CReqClientData) +$(JQ.deriveJSON defaultJSON ''CReqClientData) $(JQ.deriveJSON defaultJSON ''MemberIdRole) +$(JQ.deriveJSON defaultJSON ''MemberInfo) + $(JQ.deriveJSON defaultJSON ''GroupInvitation) $(JQ.deriveJSON defaultJSON ''GroupLinkInvitation) $(JQ.deriveJSON defaultJSON ''IntroInvitation) -$(JQ.deriveJSON defaultJSON ''MemberInfo) - $(JQ.deriveJSON defaultJSON ''MemberRestrictions) $(JQ.deriveJSON defaultJSON ''GroupMemberRef) diff --git a/src/Simplex/Chat/Types/Preferences.hs b/src/Simplex/Chat/Types/Preferences.hs index bccfd4bdce..8465caeee0 100644 --- a/src/Simplex/Chat/Types/Preferences.hs +++ b/src/Simplex/Chat/Types/Preferences.hs @@ -390,6 +390,33 @@ defaultGroupPrefs = emptyGroupPrefs :: GroupPreferences emptyGroupPrefs = GroupPreferences Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing +businessGroupPrefs :: Preferences -> GroupPreferences +businessGroupPrefs Preferences {timedMessages, fullDelete, reactions, voice} = + defaultBusinessGroupPrefs + { timedMessages = Just TimedMessagesGroupPreference {enable = maybe FEOff enableFeature timedMessages, ttl = maybe Nothing prefParam timedMessages}, + fullDelete = Just FullDeleteGroupPreference {enable = maybe FEOff enableFeature fullDelete}, + reactions = Just ReactionsGroupPreference {enable = maybe FEOn enableFeature reactions}, + voice = Just VoiceGroupPreference {enable = maybe FEOff enableFeature voice, role = Nothing} + } + where + enableFeature :: FeatureI f => FeaturePreference f -> GroupFeatureEnabled + enableFeature p = case getField @"allow" p of + FANo -> FEOff + _ -> FEOn + +defaultBusinessGroupPrefs :: GroupPreferences +defaultBusinessGroupPrefs = + GroupPreferences + { timedMessages = Just $ TimedMessagesGroupPreference FEOff Nothing, + directMessages = Just $ DirectMessagesGroupPreference FEOff Nothing, + fullDelete = Just $ FullDeleteGroupPreference FEOff, + reactions = Just $ ReactionsGroupPreference FEOn, + voice = Just $ VoiceGroupPreference FEOff Nothing, + files = Just $ FilesGroupPreference FEOn Nothing, + simplexLinks = Just $ SimplexLinksGroupPreference FEOn Nothing, + history = Just $ HistoryGroupPreference FEOn + } + data TimedMessagesPreference = TimedMessagesPreference { allow :: FeatureAllowed, ttl :: Maybe Int diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 093d750a42..4458f8ee7b 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -204,12 +204,14 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRContactDeletedByContact u c -> ttyUser u [ttyFullContact c <> " deleted contact with you"] CRChatCleared u chatInfo -> ttyUser u $ viewChatCleared chatInfo CRAcceptingContactRequest u c -> ttyUser u $ viewAcceptingContactRequest c + CRAcceptingBusinessRequest u g -> ttyUser u $ viewAcceptingBusinessRequest g CRContactAlreadyExists u c -> ttyUser u [ttyFullContact c <> ": contact already exists"] CRContactRequestAlreadyAccepted u c -> ttyUser u [ttyFullContact c <> ": sent you a duplicate contact request, but you are already connected, no action needed"] CRUserContactLinkCreated u cReq -> ttyUser u $ connReqContact_ "Your new chat address is created!" cReq CRUserContactLinkDeleted u -> ttyUser u viewUserContactLinkDeleted CRUserAcceptedGroupSent u _g _ -> ttyUser u [] -- [ttyGroup' g <> ": joining the group..."] CRGroupLinkConnecting u g _ -> ttyUser u [ttyGroup' g <> ": joining the group..."] + CRBusinessLinkConnecting u g _ _ -> ttyUser u [ttyGroup' g <> ": joining the group..."] CRUserDeletedMember u g m -> ttyUser u [ttyGroup' g <> ": you removed " <> ttyMember m <> " from the group"] CRLeftMemberUser u g -> ttyUser u $ [ttyGroup' g <> ": you left the group"] <> groupPreserved g CRUnknownMemberCreated u g fwdM um -> ttyUser u [ttyGroup' g <> ": " <> ttyMember fwdM <> " forwarded a message from an unknown member, creating unknown member record " <> ttyMember um] @@ -979,9 +981,14 @@ simplexChatContact (CRContactUri crData) = CRContactUri crData {crScheme = simpl autoAcceptStatus_ :: Maybe AutoAccept -> [StyledString] autoAcceptStatus_ = \case - Just AutoAccept {acceptIncognito, autoReply} -> - ("auto_accept on" <> if acceptIncognito then ", incognito" else "") + Just AutoAccept {businessAddress, acceptIncognito, autoReply} -> + ("auto_accept on" <> aaInfo) : maybe [] ((["auto reply:"] <>) . ttyMsgContent) autoReply + where + aaInfo + | businessAddress = ", business" + | acceptIncognito = ", incognito" + | otherwise = "" _ -> ["auto_accept off"] groupLink_ :: StyledString -> GroupInfo -> ConnReqContact -> GroupMemberRole -> [StyledString] @@ -1017,6 +1024,9 @@ viewAcceptingContactRequest ct | contactReady ct = [ttyFullContact ct <> ": accepting contact request, you can send messages to contact"] | otherwise = [ttyFullContact ct <> ": accepting contact request..."] +viewAcceptingBusinessRequest :: GroupInfo -> [StyledString] +viewAcceptingBusinessRequest g = [ttyFullGroup g <> ": accepting business address request..."] + viewReceivedContactRequest :: ContactName -> Profile -> [StyledString] viewReceivedContactRequest c Profile {fullName} = [ ttyFullName c fullName <> " wants to connect to you!", diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index 3ff8808541..b2db679f29 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -47,6 +47,8 @@ chatProfileTests = do it "delete connection requests when contact link deleted" testDeleteConnectionRequests it "auto-reply message" testAutoReplyMessage it "auto-reply message in incognito" testAutoReplyMessageInIncognito + describe "business address" $ do + it "create and connect via business address" testBusinessAddress describe "contact address connection plan" $ do it "contact address ok to connect; known contact" testPlanAddressOkKnown it "own contact address" testPlanAddressOwn @@ -677,6 +679,49 @@ testAutoReplyMessageInIncognito = testChat2 aliceProfile bobProfile $ alice <## "use /i bob to print out this incognito profile again" ] +testBusinessAddress :: HasCallStack => FilePath -> IO () +testBusinessAddress = testChat3 businessProfile aliceProfile {fullName = "Alice @ Biz"} bobProfile $ + \biz alice bob -> do + biz ##> "/ad" + cLink <- getContactLink biz True + biz ##> "/auto_accept on business" + biz <## "auto_accept on, business" + bob ##> ("/c " <> cLink) + bob <## "connection request sent!" + biz <## "#bob_1 (Bob): accepting business address request..." + biz <## "#bob_1: bob joined the group" + bob <## "#biz: joining the group..." + bob <## "#biz: you joined the group" + biz #> "#bob_1 hi" + bob <# "#biz biz_1> hi" + bob #> "#biz hello" + biz <# "#bob_1 bob> hello" + connectUsers biz alice + biz <##> alice + biz ##> "/a #bob_1 alice" + biz <## "invitation to join the group #bob_1 sent to alice" + alice <## "#bob (Bob): biz invites you to join the group as member" + alice <## "use /j bob to accept" + alice ##> "/j bob" + concurrentlyN_ + [ do + alice <## "#bob: you joined the group" + alice <### [WithTime "#bob biz> hi [>>]", WithTime "#bob bob_1> hello [>>]"] + alice <## "#bob: member bob_1 (Bob) is connected", + biz <## "#bob_1: alice joined the group", + do + bob <## "#biz: biz_1 added alice (Alice @ Biz) to the group (connecting...)" + bob <## "#biz: new member alice is connected" + ] + alice #> "#bob hey" + concurrently_ + (bob <# "#biz alice> hey") + (biz <# "#bob_1 alice> hey") + bob #> "#biz hey there" + concurrently_ + (alice <# "#bob bob_1> hey there") + (biz <# "#bob_1 bob> hey there") + testPlanAddressOkKnown :: HasCallStack => FilePath -> IO () testPlanAddressOkKnown = testChat2 aliceProfile bobProfile $ @@ -2380,7 +2425,7 @@ testSetUITheme = a <## "you've shared main profile with this contact" a <## "connection not verified, use /code command to see security code" a <## "quantum resistant end-to-end encryption" - a <## "peer chat protocol version range: (Version 1, Version 9)" + a <## "peer chat protocol version range: (Version 1, Version 10)" groupInfo a = do a <## "group ID: 1" a <## "current members: 1" diff --git a/tests/ChatTests/Utils.hs b/tests/ChatTests/Utils.hs index 8c022d5bfd..6459522134 100644 --- a/tests/ChatTests/Utils.hs +++ b/tests/ChatTests/Utils.hs @@ -64,6 +64,9 @@ cathProfile = Profile {displayName = "cath", fullName = "Catherine", image = Not danProfile :: Profile danProfile = Profile {displayName = "dan", fullName = "Daniel", image = Nothing, contactLink = Nothing, preferences = defaultPrefs} +businessProfile :: Profile +businessProfile = Profile {displayName = "biz", fullName = "Biz Inc", image = Nothing, contactLink = Nothing, preferences = defaultPrefs} + it :: HasCallStack => String -> (FilePath -> Expectation) -> SpecWith (Arg (FilePath -> Expectation)) it name test = Hspec.it name $ \tmp -> timeout t (test tmp) >>= maybe (error "test timed out") pure diff --git a/tests/ProtocolTests.hs b/tests/ProtocolTests.hs index f64efe108f..2eb946d731 100644 --- a/tests/ProtocolTests.hs +++ b/tests/ProtocolTests.hs @@ -133,7 +133,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do "{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" ##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing))) it "x.msg.new chat message with chat version range" $ - "{\"v\":\"1-9\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" + "{\"v\":\"1-10\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" ##==## ChatMessage supportedChatVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing))) it "x.msg.new quote" $ "{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello to you too\",\"type\":\"text\"},\"quote\":{\"content\":{\"text\":\"hello there!\",\"type\":\"text\"},\"msgRef\":{\"msgId\":\"BQYHCA==\",\"sent\":true,\"sentAt\":\"1970-01-01T00:00:01.000000001Z\"}}}}" @@ -232,10 +232,10 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do ==# XContact testProfile Nothing it "x.grp.inv" $ "{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}}}}" - #==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, groupLinkId = Nothing, groupSize = Nothing} + #==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, businessChat = Nothing, groupLinkId = Nothing, groupSize = Nothing} it "x.grp.inv with group link id" $ "{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}, \"groupLinkId\":\"AQIDBA==\"}}}" - #==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, groupLinkId = Just $ GroupLinkId "\1\2\3\4", groupSize = Nothing} + #==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, businessChat = Nothing, groupLinkId = Just $ GroupLinkId "\1\2\3\4", groupSize = Nothing} it "x.grp.acpt without incognito profile" $ "{\"v\":\"1\",\"event\":\"x.grp.acpt\",\"params\":{\"memberId\":\"AQIDBA==\"}}" #==# XGrpAcpt (MemberId "\1\2\3\4") @@ -243,13 +243,13 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do "{\"v\":\"1\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemNew MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} it "x.grp.mem.new with member chat version range" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-9\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" + "{\"v\":\"1\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-10\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemNew MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile} it "x.grp.mem.intro" $ "{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} Nothing it "x.grp.mem.intro with member chat version range" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-9\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" + "{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-10\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile} Nothing it "x.grp.mem.intro with member restrictions" $ "{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberRestrictions\":{\"restriction\":\"blocked\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" @@ -264,7 +264,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do "{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} IntroInvitation {groupConnReq = testConnReq, directConnReq = Just testConnReq} it "x.grp.mem.fwd with member chat version range and w/t directConnReq" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-9\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" + "{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-10\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile} IntroInvitation {groupConnReq = testConnReq, directConnReq = Nothing} it "x.grp.mem.info" $ "{\"v\":\"1\",\"event\":\"x.grp.mem.info\",\"params\":{\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" From 5a59fdd91c01fe1b420d7e45f5b467207b14f05e Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Mon, 2 Dec 2024 22:28:58 +0700 Subject: [PATCH 149/567] android, desktop: fix Can't represent a width ... and a height ... in Constraints (#5293) --- .../common/views/chat/item/FramedItemView.kt | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt index c2df11a8ea..e955428031 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt @@ -23,7 +23,7 @@ import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.res.MR -import kotlin.math.min +import kotlin.math.ceil @Composable fun FramedItemView( @@ -330,23 +330,16 @@ const val CHAT_IMAGE_LAYOUT_ID = "chatImage" const val CHAT_BUBBLE_LAYOUT_ID = "chatBubble" const val CHAT_COMPOSE_LAYOUT_ID = "chatCompose" const val CONSOLE_COMPOSE_LAYOUT_ID = "consoleCompose" -/** - * Equal to [androidx.compose.ui.unit.Constraints.MaxFocusMask], which is 0x3FFFF - 1 - * Other values make a crash `java.lang.IllegalArgumentException: Can't represent a width of 123456 and height of 9909 in Constraints` - * See [androidx.compose.ui.unit.Constraints.createConstraints] - * */ -const val MAX_SAFE_WIDTH = 0x3FFFF - 1 /** - * Limiting max value for height + width in order to not crash the app, see [androidx.compose.ui.unit.Constraints.createConstraints] - * */ -private fun maxSafeHeight(width: Int) = when { // width bits + height bits should be <= 31 - width < 0x1FFF /*MaxNonFocusMask*/ -> 0x3FFFF - 1 /* MaxFocusMask */ // 13 bits width + 18 bits height - width < 0x7FFF /*MinNonFocusMask*/ -> 0xFFFF - 1 /* MinFocusMask */ // 15 bits width + 16 bits height - width < 0xFFFF /*MinFocusMask*/ -> 0x7FFF - 1 /* MinFocusMask */ // 16 bits width + 15 bits height - width < 0x3FFFF /*MaxFocusMask*/ -> 0x1FFF - 1 /* MaxNonFocusMask */ // 18 bits width + 13 bits height - else -> 0x1FFF // shouldn't happen since width is limited already -} + * Compose shows "Can't represent a width of ... and height ... in Constraints" even when using built-in method for measuring max + * available size. It seems like padding around such layout prevents showing them in parent layout when such child layouts are placed. + * So calculating the expected padding here based on the values Compose printed in the exception (removing some pixels from + * [Constraints.fitPrioritizingHeight] result makes it working well) +*/ +private fun horizontalPaddingAroundCustomLayouts(density: Float): Int = + // currently, it's 18. Doubling it just to cover possible changes in the future + 36 * ceil(density).toInt() @Composable fun PriorityLayout( @@ -365,11 +358,15 @@ fun PriorityLayout( if (it.layoutId == priorityLayoutId) imagePlaceable!! else - it.measure(constraints.copy(maxWidth = imagePlaceable?.width ?: min(MAX_SAFE_WIDTH, constraints.maxWidth))) } + it.measure(constraints.copy(maxWidth = imagePlaceable?.width ?: constraints.maxWidth)) } // Limit width for every other element to width of important element and height for a sum of all elements. - val width = imagePlaceable?.measuredWidth ?: min(MAX_SAFE_WIDTH, placeables.maxOf { it.width }) - val height = minOf(maxSafeHeight(width), placeables.sumOf { it.height }) - layout(width, height) { + val width = imagePlaceable?.measuredWidth ?: placeables.maxOf { it.width } + val height = placeables.sumOf { it.height } + val adjustedConstraints = Constraints.fitPrioritizingHeight(constraints.minWidth, width, constraints.minHeight, height) + layout( + if (width > adjustedConstraints.maxWidth) adjustedConstraints.maxWidth - horizontalPaddingAroundCustomLayouts(density) else adjustedConstraints.maxWidth, + adjustedConstraints.maxHeight + ) { var y = 0 placeables.forEach { it.place(0, y) @@ -396,10 +393,14 @@ fun DependentLayout( if (it.layoutId == mainLayoutId) mainPlaceable!! else - it.measure(constraints.copy(minWidth = mainPlaceable?.width ?: 0, maxWidth = min(MAX_SAFE_WIDTH, constraints.maxWidth))) } - val width = mainPlaceable?.measuredWidth ?: min(MAX_SAFE_WIDTH, placeables.maxOf { it.width }) - val height = minOf(maxSafeHeight(width), placeables.sumOf { it.height }) - layout(width, height) { + it.measure(constraints.copy(minWidth = mainPlaceable?.width ?: 0, maxWidth = constraints.maxWidth)) } + val width = mainPlaceable?.measuredWidth ?: placeables.maxOf { it.width } + val height = placeables.sumOf { it.height } + val adjustedConstraints = Constraints.fitPrioritizingHeight(constraints.minWidth, width, constraints.minHeight, height) + layout( + if (width > adjustedConstraints.maxWidth) adjustedConstraints.maxWidth - horizontalPaddingAroundCustomLayouts(density) else adjustedConstraints.maxWidth, + adjustedConstraints.maxHeight + ) { var y = 0 placeables.forEach { it.place(0, y) From f6b611aa30655ab61ac37c69b3fa961434e4fc7b Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Mon, 2 Dec 2024 22:58:21 +0700 Subject: [PATCH 150/567] android, desktop: check existence before deleting database (#5298) --- .../chat/simplex/common/views/database/DatabaseView.kt | 5 ++++- .../chat/simplex/common/views/helpers/DatabaseUtils.kt | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index 8185122089..af40aa2e70 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -719,13 +719,16 @@ private fun deleteChatAlert(onConfirm: () -> Unit) { } private suspend fun deleteChat(m: ChatModel, progressIndicator: MutableState) { + if (!DatabaseUtils.hasAtLeastOneDatabase(dataDir.absolutePath)) { + return + } progressIndicator.value = true try { deleteChatAsync(m) operationEnded(m, progressIndicator) { AlertManager.shared.showAlertMsg(generalGetString(MR.strings.chat_database_deleted), generalGetString(MR.strings.restart_the_app_to_create_a_new_chat_profile)) } - } catch (e: Error) { + } catch (e: Throwable) { operationEnded(m, progressIndicator) { AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_deleting_database), e.toString()) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt index c621f186cd..4827e6ae61 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt @@ -39,7 +39,7 @@ object DatabaseUtils { } } - private fun hasAtLeastOneDatabase(rootDir: String): Boolean = + fun hasAtLeastOneDatabase(rootDir: String): Boolean = File(rootDir + File.separator + chatDatabaseFileName).exists() || File(rootDir + File.separator + agentDatabaseFileName).exists() fun hasOnlyOneDatabase(rootDir: String): Boolean = From a62ce9168e227bbf159f83f2b3361ddef0b556c0 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Mon, 2 Dec 2024 20:30:05 +0400 Subject: [PATCH 151/567] core: fix names created for business request group and member (#5296) --- src/Simplex/Chat.hs | 4 ++-- src/Simplex/Chat/Store/Groups.hs | 40 +++++++++++++++++++++++++------- tests/ChatTests/Profiles.hs | 18 +++++++------- 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 92ec04d11b..7a48914abf 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -4007,13 +4007,13 @@ acceptGroupJoinRequestAsync acceptBusinessJoinRequestAsync :: User -> UserContactRequest -> CM GroupInfo acceptBusinessJoinRequestAsync user - ucr@UserContactRequest {agentInvitationId = AgentInvId invId, cReqChatVRange} = do + ucr@UserContactRequest {contactRequestId, agentInvitationId = AgentInvId invId, cReqChatVRange} = do vr <- chatVersionRange gVar <- asks random let userProfile@Profile {displayName, preferences} = profileToSendOnAccept user Nothing True groupPreferences = maybe defaultBusinessGroupPrefs businessGroupPrefs preferences (gInfo, clientMember) <- withStore $ \db -> do - liftIO $ deleteContactRequestRec db user ucr + liftIO $ deleteContactRequest db user contactRequestId createBusinessRequestGroup db vr gVar user ucr groupPreferences let GroupInfo {membership} = gInfo GroupMember {memberRole = userRole, memberId = userMemberId} = membership diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index b07adf407b..bb5252ddb0 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -155,7 +155,7 @@ import Simplex.Messaging.Util (eitherToMaybe, ($>>=), (<$$>)) import Simplex.Messaging.Version import UnliftIO.STM -type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe MsgFilter, Maybe Bool, Bool, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime, Maybe MemberId, Maybe BusinessChatType, Maybe UIThemeEntityOverrides, Maybe CustomData) :. GroupMemberRow +type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe MsgFilter, Maybe Bool, Bool, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime, Maybe MemberId, Maybe BusinessChatType, Maybe UIThemeEntityOverrides, Maybe CustomData) :. GroupMemberRow type GroupMemberRow = ((Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, Bool, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences)) @@ -936,18 +936,17 @@ createBusinessRequestGroup db vr gVar - user@User {userId} - ucr@UserContactRequest {profile} + user@User {userId, userContactId} + UserContactRequest {cReqChatVRange, profile = Profile {displayName, fullName, image, contactLink, preferences}} groupPreferences = do currentTs <- liftIO getCurrentTime groupInfo <- insertGroup_ currentTs - (groupMemberId, memberId) <- createAcceptedMember db gVar user groupInfo ucr GRMember + (groupMemberId, memberId) <- insertClientMember_ currentTs groupInfo liftIO $ setBusinessMemberId groupInfo memberId - acceptedMember <- getGroupMemberById db vr user groupMemberId - pure (groupInfo, acceptedMember) + clientMember <- getGroupMemberById db vr user groupMemberId + pure (groupInfo, clientMember) where - insertGroup_ currentTs = ExceptT $ do - let Profile {displayName, fullName, image} = profile + insertGroup_ currentTs = ExceptT $ withLocalDisplayName db userId displayName $ \localDisplayName -> runExceptT $ do groupId <- liftIO $ do DB.execute @@ -968,6 +967,31 @@ createBusinessRequestGroup memberId <- liftIO $ encodedRandomBytes gVar 12 void $ createContactMemberInv_ db user groupId Nothing user (MemberIdRole (MemberId memberId) GROwner) GCUserMember GSMemCreator IBUser Nothing currentTs vr getGroupInfo db vr user groupId + VersionRange minV maxV = cReqChatVRange + insertClientMember_ currentTs GroupInfo {groupId, membership} = ExceptT $ do + withLocalDisplayName db userId displayName $ \localDisplayName -> runExceptT $ do + liftIO $ + DB.execute + db + "INSERT INTO contact_profiles (display_name, full_name, image, contact_link, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)" + (displayName, fullName, image, contactLink, userId, preferences, currentTs, currentTs) + profileId <- liftIO $ insertedRowId db + createWithRandomId gVar $ \memId -> do + DB.execute + db + [sql| + INSERT INTO group_members + ( group_id, member_id, member_role, member_category, member_status, invited_by, invited_by_group_member_id, + user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at, + peer_chat_min_version, peer_chat_max_version) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) + |] + ( (groupId, MemberId memId, GRMember, GCInviteeMember, GSMemAccepted, fromInvitedBy userContactId IBUser, groupMemberId' membership) + :. (userId, localDisplayName, Nothing :: (Maybe Int64), profileId, currentTs, currentTs) + :. (minV, maxV) + ) + groupMemberId <- liftIO $ insertedRowId db + pure (groupMemberId, MemberId memId) setBusinessMemberId GroupInfo {groupId} businessMemberId = do DB.execute db "UPDATE groups SET business_member_id = ? WHERE group_id = ?" (businessMemberId, groupId) diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index b2db679f29..3ffe57ba2e 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -688,18 +688,18 @@ testBusinessAddress = testChat3 businessProfile aliceProfile {fullName = "Alice biz <## "auto_accept on, business" bob ##> ("/c " <> cLink) bob <## "connection request sent!" - biz <## "#bob_1 (Bob): accepting business address request..." - biz <## "#bob_1: bob joined the group" + biz <## "#bob (Bob): accepting business address request..." + biz <## "#bob: bob_1 joined the group" bob <## "#biz: joining the group..." bob <## "#biz: you joined the group" - biz #> "#bob_1 hi" + biz #> "#bob hi" bob <# "#biz biz_1> hi" bob #> "#biz hello" - biz <# "#bob_1 bob> hello" + biz <# "#bob bob_1> hello" connectUsers biz alice biz <##> alice - biz ##> "/a #bob_1 alice" - biz <## "invitation to join the group #bob_1 sent to alice" + biz ##> "/a #bob alice" + biz <## "invitation to join the group #bob sent to alice" alice <## "#bob (Bob): biz invites you to join the group as member" alice <## "use /j bob to accept" alice ##> "/j bob" @@ -708,7 +708,7 @@ testBusinessAddress = testChat3 businessProfile aliceProfile {fullName = "Alice alice <## "#bob: you joined the group" alice <### [WithTime "#bob biz> hi [>>]", WithTime "#bob bob_1> hello [>>]"] alice <## "#bob: member bob_1 (Bob) is connected", - biz <## "#bob_1: alice joined the group", + biz <## "#bob: alice joined the group", do bob <## "#biz: biz_1 added alice (Alice @ Biz) to the group (connecting...)" bob <## "#biz: new member alice is connected" @@ -716,11 +716,11 @@ testBusinessAddress = testChat3 businessProfile aliceProfile {fullName = "Alice alice #> "#bob hey" concurrently_ (bob <# "#biz alice> hey") - (biz <# "#bob_1 alice> hey") + (biz <# "#bob alice> hey") bob #> "#biz hey there" concurrently_ (alice <# "#bob bob_1> hey there") - (biz <# "#bob_1 bob> hey there") + (biz <# "#bob bob_1> hey there") testPlanAddressOkKnown :: HasCallStack => FilePath -> IO () testPlanAddressOkKnown = From 665501026dde8542898418f45afbef14fda5f414 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Mon, 2 Dec 2024 23:32:04 +0700 Subject: [PATCH 152/567] android: show info for Xiaomi users about autostart restrictions (#5295) * android: show info for Xiaomi users about autostart restrictions * text and placement * update strings --------- Co-authored-by: Evgeny Poberezkin --- .../src/main/java/chat/simplex/app/SimplexApp.kt | 2 ++ .../src/main/java/chat/simplex/app/SimplexService.kt | 8 +++++++- .../kotlin/chat/simplex/common/platform/Platform.kt | 1 + .../views/usersettings/NotificationsSettingsView.kt | 10 ++++++++-- .../src/commonMain/resources/MR/base/strings.xml | 5 +++-- 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt index f46ed4775f..0ebd9ca2c6 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt @@ -332,6 +332,8 @@ class SimplexApp: Application(), LifecycleEventObserver { NetworkObserver.shared.restartNetworkObserver() } + override fun androidIsXiaomiDevice(): Boolean = setOf("xiaomi", "redmi", "poco").contains(Build.BRAND.lowercase()) + @SuppressLint("SourceLockedOrientationActivity") @Composable override fun androidLockPortraitOrientation() { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt index ce3f0825b8..33799087a7 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt @@ -497,6 +497,12 @@ class SimplexService: Service() { Modifier.padding(bottom = 8.dp) ) Text(annotatedStringResource(MR.strings.turn_off_battery_optimization)) + + if (platform.androidIsXiaomiDevice() && (mode == NotificationsMode.PERIODIC || mode == NotificationsMode.SERVICE)) { + Text(annotatedStringResource(MR.strings.xiaomi_ignore_battery_optimization), + Modifier.padding(top = 8.dp) + ) + } } }, dismissButton = { @@ -623,7 +629,7 @@ class SimplexService: Service() { fun isBackgroundAllowed(): Boolean = isIgnoringBatteryOptimizations() && !isBackgroundRestricted() - fun isIgnoringBatteryOptimizations(): Boolean { + private fun isIgnoringBatteryOptimizations(): Boolean { val powerManager = androidAppContext.getSystemService(Application.POWER_SERVICE) as PowerManager return powerManager.isIgnoringBatteryOptimizations(androidAppContext.packageName) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt index 23ab450cb6..e0a9e22f71 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt @@ -26,6 +26,7 @@ interface PlatformInterface { fun androidPictureInPictureAllowed(): Boolean = true fun androidCallEnded() {} fun androidRestartNetworkObserver() {} + fun androidIsXiaomiDevice(): Boolean = false val androidApiLevel: Int? get() = null @Composable fun androidLockPortraitOrientation() {} suspend fun androidAskToAllowBackgroundCalls(): Boolean = true diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt index 60bde83c17..66b518e9aa 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt @@ -1,11 +1,10 @@ package chat.simplex.common.views.usersettings import SectionBottomSpacer +import SectionTextFooter import SectionView import SectionViewSelectable import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier @@ -16,6 +15,7 @@ import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.style.TextOverflow import chat.simplex.common.model.* import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.DEFAULT_PADDING_HALF import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import kotlin.collections.ArrayList @@ -77,6 +77,9 @@ fun NotificationsSettingsLayout( color = MaterialTheme.colors.secondary ) } + if (platform.androidIsXiaomiDevice() && (notificationsMode.value == NotificationsMode.PERIODIC || notificationsMode.value == NotificationsMode.SERVICE)) { + SectionTextFooter(stringResource(MR.strings.xiaomi_ignore_battery_optimization)) + } } SectionBottomSpacer() } @@ -91,6 +94,9 @@ fun NotificationsModeView( ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.settings_notifications_mode_title).lowercase().capitalize(Locale.current)) SectionViewSelectable(null, notificationsMode, modes, onNotificationsModeSelected) + if (platform.androidIsXiaomiDevice() && (notificationsMode.value == NotificationsMode.PERIODIC || notificationsMode.value == NotificationsMode.SERVICE)) { + SectionTextFooter(stringResource(MR.strings.xiaomi_ignore_battery_optimization)) + } } } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 1c036d76b2..c390096ee2 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -191,9 +191,9 @@ Instant notifications Instant notifications! Instant notifications are disabled! - SimpleX background service – it uses a few percent of the battery per day.]]> + SimpleX runs in background instead of using push notifications.]]> It can be disabled via settings – notifications will still be shown while the app is running.]]> - allow SimpleX to run in background in the next dialog. Otherwise, the notifications will be disabled.]]> + Allow it in the next dialog to receive notifications instantly.]]> Battery optimization is active, turning off background service and periodic requests for new messages. You can re-enable them via settings. Periodic notifications Periodic notifications are disabled! @@ -210,6 +210,7 @@ To receive notifications, please, enter the database passphrase Can\'t initialize the database The database is not working correctly. Tap to learn more + Xiaomi devices: please enable Autostart in the system settings for notifications to work.]]> SimpleX Chat service From a588e7003d79ff540c8ea4c4c3915c0814274dd1 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Mon, 2 Dec 2024 23:33:22 +0700 Subject: [PATCH 153/567] android: alert round corners (#5299) Co-authored-by: Evgeny Poberezkin --- .../java/chat/simplex/app/SimplexService.kt | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt index 33799087a7..3b9f2ade26 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt @@ -10,6 +10,8 @@ import android.os.* import android.os.SystemClock import android.provider.Settings import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier @@ -462,7 +464,8 @@ class SimplexService: Service() { }, confirmButton = { TextButton(onClick = AlertManager.shared::hideAlert) { Text(stringResource(MR.strings.ok)) } - } + }, + shape = RoundedCornerShape(corner = CornerSize(25.dp)) ) } @@ -510,7 +513,8 @@ class SimplexService: Service() { }, confirmButton = { TextButton(onClick = ignoreOptimization) { Text(stringResource(MR.strings.turn_off_battery_optimization_button)) } - } + }, + shape = RoundedCornerShape(corner = CornerSize(25.dp)) ) } @@ -552,7 +556,8 @@ class SimplexService: Service() { }, confirmButton = { TextButton(onClick = unrestrict) { Text(stringResource(MR.strings.turn_off_system_restriction_button)) } - } + }, + shape = RoundedCornerShape(corner = CornerSize(25.dp)) ) } @@ -579,7 +584,8 @@ class SimplexService: Service() { }, confirmButton = { TextButton(onClick = unrestrict) { Text(stringResource(MR.strings.turn_off_system_restriction_button)) } - } + }, + shape = RoundedCornerShape(corner = CornerSize(25.dp)) ) val scope = rememberCoroutineScope() DisposableEffect(Unit) { @@ -623,7 +629,8 @@ class SimplexService: Service() { }, confirmButton = { TextButton(onClick = AlertManager.shared::hideAlert) { Text(stringResource(MR.strings.ok)) } - } + }, + shape = RoundedCornerShape(corner = CornerSize(25.dp)) ) } From bc960001310cb454ceddc3650a6d155721d48988 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Mon, 2 Dec 2024 21:40:22 +0400 Subject: [PATCH 154/567] ios: support business addresses and chats (#5300) * ios: support business addresses and chats * improve * words * fix --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Model/SimpleXAPI.swift | 7 +++ .../Views/Chat/Group/GroupChatInfoView.swift | 24 ++++++---- .../Chat/Group/GroupPreferencesView.swift | 6 +-- .../Views/Chat/Group/GroupWelcomeView.swift | 2 +- .../Views/UserSettings/UserAddressView.swift | 42 +++++++++--------- apps/ios/SimpleX.xcodeproj/project.pbxproj | 44 +++++++++---------- apps/ios/SimpleXChat/APITypes.swift | 12 ++++- apps/ios/SimpleXChat/ChatTypes.swift | 13 +++++- apps/ios/SimpleXChat/ChatUtils.swift | 7 ++- 9 files changed, 96 insertions(+), 61 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 459ece32da..5f29a848ef 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -2014,6 +2014,13 @@ func processReceivedMsg(_ res: ChatResponse) async { m.removeChat(hostConn.id) } } + case let .businessLinkConnecting(user, groupInfo, hostMember, fromContact): + if !active(user) { return } + + await MainActor.run { + m.updateGroup(groupInfo) + m.removeChat(fromContact.id) + } case let .joinedGroupMemberConnecting(user, groupInfo, _, member): if active(user) { await MainActor.run { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index 59df52df9f..89f0fcbedf 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -81,10 +81,10 @@ struct GroupChatInfoView: View { .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) Section { - if groupInfo.canEdit { + if groupInfo.isOwner && groupInfo.businessChat == nil { editGroupButton() } - if groupInfo.groupProfile.description != nil || groupInfo.canEdit { + if groupInfo.groupProfile.description != nil || (groupInfo.isOwner && groupInfo.businessChat == nil) { addOrEditWelcomeMessage() } groupPreferencesButton($groupInfo) @@ -107,7 +107,9 @@ struct GroupChatInfoView: View { Section(header: Text("\(members.count + 1) members").foregroundColor(theme.colors.secondary)) { if groupInfo.canAddMembers { - groupLinkButton() + if groupInfo.businessChat == nil { + groupLinkButton() + } if (chat.chatInfo.incognito) { Label("Invite members", systemImage: "plus") .foregroundColor(Color(uiColor: .tertiaryLabel)) @@ -276,10 +278,15 @@ struct GroupChatInfoView: View { } private func addMembersButton() -> some View { - NavigationLink { + let label: LocalizedStringKey = switch groupInfo.businessChat?.chatType { + case .customer: "Add team members" + case .business: "Add friends" + case .none: "Invite members" + } + return NavigationLink { addMembersDestinationView() } label: { - Label("Invite members", systemImage: "plus") + Label(label, systemImage: "plus") } } @@ -625,21 +632,22 @@ struct GroupChatInfoView: View { } func groupPreferencesButton(_ groupInfo: Binding, _ creatingGroup: Bool = false) -> some View { - NavigationLink { + let label: LocalizedStringKey = groupInfo.wrappedValue.businessChat == nil ? "Group preferences" : "Chat preferences" + return NavigationLink { GroupPreferencesView( groupInfo: groupInfo, preferences: groupInfo.wrappedValue.fullGroupPreferences, currentPreferences: groupInfo.wrappedValue.fullGroupPreferences, creatingGroup: creatingGroup ) - .navigationBarTitle("Group preferences") + .navigationBarTitle(label) .modifier(ThemedBackground(grouped: true)) .navigationBarTitleDisplayMode(.large) } label: { if creatingGroup { Text("Set group preferences") } else { - Label("Group preferences", systemImage: "switch.2") + Label(label, systemImage: "switch.2") } } } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift index 2b0d05375b..bbbbe4d4c3 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift @@ -38,7 +38,7 @@ struct GroupPreferencesView: View { featureSection(.simplexLinks, $preferences.simplexLinks.enable, $preferences.simplexLinks.role) featureSection(.history, $preferences.history.enable) - if groupInfo.canEdit { + if groupInfo.isOwner { Section { Button("Reset") { preferences = currentPreferences } Button(saveText) { savePreferences() } @@ -77,7 +77,7 @@ struct GroupPreferencesView: View { let color: Color = enableFeature.wrappedValue == .on ? .green : theme.colors.secondary let icon = enableFeature.wrappedValue == .on ? feature.iconFilled : feature.icon let timedOn = feature == .timedMessages && enableFeature.wrappedValue == .on - if groupInfo.canEdit { + if groupInfo.isOwner { let enable = Binding( get: { enableFeature.wrappedValue == .on }, set: { on, _ in enableFeature.wrappedValue = on ? .on : .off } @@ -123,7 +123,7 @@ struct GroupPreferencesView: View { } } } footer: { - Text(feature.enableDescription(enableFeature.wrappedValue, groupInfo.canEdit)) + Text(feature.enableDescription(enableFeature.wrappedValue, groupInfo.isOwner)) .foregroundColor(theme.colors.secondary) } .onChange(of: enableFeature.wrappedValue) { enabled in diff --git a/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift b/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift index 9a9002f9dc..8dfc32f6ea 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift @@ -23,7 +23,7 @@ struct GroupWelcomeView: View { var body: some View { VStack { - if groupInfo.canEdit { + if groupInfo.isOwner && groupInfo.businessChat == nil { editorView() .modifier(BackButton(disabled: Binding.constant(false)) { if welcomeTextUnchanged() { diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift index 6bc3a221b2..7965215b49 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift @@ -57,21 +57,17 @@ struct UserAddressView: View { if chatModel.userAddress == nil, autoCreate { createAddress() } - if let userAddress = chatModel.userAddress { - aas = AutoAcceptState(userAddress: userAddress) - savedAAS = aas - } } - .onChange(of: aas.enable) { aasEnabled in - if !aasEnabled { aas = AutoAcceptState() } - } - } private func userAddressView() -> some View { List { if let userAddress = chatModel.userAddress { existingAddressView(userAddress) + .onAppear { + aas = AutoAcceptState(userAddress: userAddress) + savedAAS = aas + } } else { Section { createAddressButton() @@ -145,13 +141,14 @@ struct UserAddressView: View { // if MFMailComposeViewController.canSendMail() { // shareViaEmailButton(userAddress) // } - settingsRow("hand.wave", color: theme.colors.secondary) { + settingsRow("briefcase", color: theme.colors.secondary) { Toggle("Business address", isOn: $aas.business) .onChange(of: aas.business) { ba in if ba { aas.enable = true aas.incognito = false } + saveAAS($aas, $savedAAS) } } addressSettingsButton(userAddress) @@ -160,7 +157,8 @@ struct UserAddressView: View { .foregroundColor(theme.colors.secondary) } footer: { if aas.business { - Text("Add your team members to the conversations").foregroundColor(theme.colors.secondary) + Text("Add your team members to the conversations.") + .foregroundColor(theme.colors.secondary) } } @@ -313,7 +311,7 @@ private struct AutoAcceptState: Equatable { if let aa = userAddress.autoAccept { enable = true incognito = aa.acceptIncognito - business = aa.businessAddress == true + business = aa.businessAddress if let msg = aa.autoReply { welcomeText = msg.text } else { @@ -382,7 +380,7 @@ struct UserAddressSettingsView: View { title: NSLocalizedString("Auto-accept settings", comment: "alert title"), message: NSLocalizedString("Settings were changed.", comment: "alert message"), buttonTitle: NSLocalizedString("Save", comment: "alert button"), - buttonAction: saveAAS, + buttonAction: { saveAAS($aas, $savedAAS) }, cancelButton: true ) } @@ -470,7 +468,7 @@ struct UserAddressSettingsView: View { settingsRow("checkmark", color: theme.colors.secondary) { Toggle("Auto-accept", isOn: $aas.enable) .onChange(of: aas.enable) { _ in - saveAAS() + saveAAS($aas, $savedAAS) } } } @@ -519,22 +517,24 @@ struct UserAddressSettingsView: View { private func saveAASButton() -> some View { Button { keyboardVisible = false - saveAAS() + saveAAS($aas, $savedAAS) } label: { Text("Save") } } +} - private func saveAAS() { - Task { - do { - if let address = try await userAddressAutoAccept(aas.autoAccept) { +private func saveAAS(_ aas: Binding, _ savedAAS: Binding) { + Task { + do { + if let address = try await userAddressAutoAccept(aas.wrappedValue.autoAccept) { + await MainActor.run { ChatModel.shared.userAddress = address - savedAAS = aas + savedAAS.wrappedValue = aas.wrappedValue } - } catch let error { - logger.error("userAddressAutoAccept error: \(responseError(error))") } + } catch let error { + logger.error("userAddressAutoAccept error: \(responseError(error))") } } } diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 7e5a48013b..8d4e4fe5c4 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -148,11 +148,6 @@ 6419EC562AB8BC8B004A607A /* ContextInvitingContactMemberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */; }; 6419EC582AB97507004A607A /* CIMemberCreatedContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */; }; 642BA82D2CE50495005E9412 /* NewServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 642BA82C2CE50495005E9412 /* NewServerView.swift */; }; - 642BA8332CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a */; }; - 642BA8342CEB3D4B005E9412 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA82F2CEB3D4B005E9412 /* libffi.a */; }; - 642BA8352CEB3D4B005E9412 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA8302CEB3D4B005E9412 /* libgmp.a */; }; - 642BA8362CEB3D4B005E9412 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA8312CEB3D4B005E9412 /* libgmpxx.a */; }; - 642BA8372CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a */; }; 6432857C2925443C00FBE5C8 /* GroupPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */; }; 643B3B4E2CCFD6400083A2CF /* OperatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 643B3B4D2CCFD6400083A2CF /* OperatorView.swift */; }; 6440CA00288857A10062C672 /* CIEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440C9FF288857A10062C672 /* CIEventView.swift */; }; @@ -171,6 +166,11 @@ 647F090E288EA27B00644C40 /* GroupMemberInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647F090D288EA27B00644C40 /* GroupMemberInfoView.swift */; }; 648010AB281ADD15009009B9 /* CIFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648010AA281ADD15009009B9 /* CIFileView.swift */; }; 648679AB2BC96A74006456E7 /* ChatItemForwardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648679AA2BC96A74006456E7 /* ChatItemForwardingView.swift */; }; + 649B28DD2CFE07CF00536B68 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28D82CFE07CF00536B68 /* libffi.a */; }; + 649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a */; }; + 649B28DF2CFE07CF00536B68 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DA2CFE07CF00536B68 /* libgmpxx.a */; }; + 649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a */; }; + 649B28E12CFE07CF00536B68 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DC2CFE07CF00536B68 /* libgmp.a */; }; 649BCDA0280460FD00C3A862 /* ComposeImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */; }; 649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCDA12805D6EF00C3A862 /* CIImageView.swift */; }; 64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6827EE10C800AC7277 /* ContextItemView.swift */; }; @@ -229,7 +229,6 @@ D741547A29AF90B00022400A /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D741547929AF90B00022400A /* PushKit.framework */; }; D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; - E504516F2CFA3BFB00DE3F74 /* ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = E504516E2CFA3BFB00DE3F74 /* ContextMenu.swift */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; }; E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; @@ -497,11 +496,6 @@ 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextInvitingContactMemberView.swift; sourceTree = ""; }; 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMemberCreatedContactView.swift; sourceTree = ""; }; 642BA82C2CE50495005E9412 /* NewServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewServerView.swift; sourceTree = ""; }; - 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a"; sourceTree = ""; }; - 642BA82F2CEB3D4B005E9412 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 642BA8302CEB3D4B005E9412 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - 642BA8312CEB3D4B005E9412 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a"; sourceTree = ""; }; 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupPreferencesView.swift; sourceTree = ""; }; 643B3B4D2CCFD6400083A2CF /* OperatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperatorView.swift; sourceTree = ""; }; 6440C9FF288857A10062C672 /* CIEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIEventView.swift; sourceTree = ""; }; @@ -521,6 +515,11 @@ 648010AA281ADD15009009B9 /* CIFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIFileView.swift; sourceTree = ""; }; 648679AA2BC96A74006456E7 /* ChatItemForwardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemForwardingView.swift; sourceTree = ""; }; 6493D667280ED77F007A76FB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 649B28D82CFE07CF00536B68 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a"; sourceTree = ""; }; + 649B28DA2CFE07CF00536B68 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a"; sourceTree = ""; }; + 649B28DC2CFE07CF00536B68 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeImageView.swift; sourceTree = ""; }; 649BCDA12805D6EF00C3A862 /* CIImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIImageView.swift; sourceTree = ""; }; 64AA1C6827EE10C800AC7277 /* ContextItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextItemView.swift; sourceTree = ""; }; @@ -576,7 +575,6 @@ D741547729AF89AF0022400A /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; }; D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - E504516E2CFA3BFB00DE3F74 /* ContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenu.swift; sourceTree = ""; }; E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = ""; }; E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; @@ -669,14 +667,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 649B28DF2CFE07CF00536B68 /* libgmpxx.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, + 649B28E12CFE07CF00536B68 /* libgmp.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, + 649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - 642BA8342CEB3D4B005E9412 /* libffi.a in Frameworks */, - 642BA8352CEB3D4B005E9412 /* libgmp.a in Frameworks */, - 642BA8372CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a in Frameworks */, - 642BA8332CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a in Frameworks */, - 642BA8362CEB3D4B005E9412 /* libgmpxx.a in Frameworks */, + 649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a in Frameworks */, + 649B28DD2CFE07CF00536B68 /* libffi.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -753,11 +751,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - 642BA82F2CEB3D4B005E9412 /* libffi.a */, - 642BA8302CEB3D4B005E9412 /* libgmp.a */, - 642BA8312CEB3D4B005E9412 /* libgmpxx.a */, - 642BA8322CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a */, - 642BA82E2CEB3D4B005E9412 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a */, + 649B28D82CFE07CF00536B68 /* libffi.a */, + 649B28DC2CFE07CF00536B68 /* libgmp.a */, + 649B28DA2CFE07CF00536B68 /* libgmpxx.a */, + 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a */, + 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a */, ); path = Libraries; sourceTree = ""; @@ -793,7 +791,6 @@ 5C971E1F27AEBF7000C8A3CE /* Helpers */ = { isa = PBXGroup; children = ( - E504516E2CFA3BFB00DE3F74 /* ContextMenu.swift */, 5C971E2027AEBF8300C8A3CE /* ChatInfoImage.swift */, 5C7505A427B679EE00BE3227 /* NavLinkPlain.swift */, 5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */, @@ -1448,7 +1445,6 @@ CE984D4B2C36C5D500E3AEFF /* ChatItemClipShape.swift in Sources */, 64D0C2C629FAC1EC00B38D5F /* AddContactLearnMore.swift in Sources */, 5C3A88D127DF57800060F1C2 /* FramedItemView.swift in Sources */, - E504516F2CFA3BFB00DE3F74 /* ContextMenu.swift in Sources */, 5C65F343297D45E100B67AF3 /* VersionView.swift in Sources */, 64F1CC3B28B39D8600CD1FB1 /* IncognitoHelp.swift in Sources */, 5CB0BA90282713D900B3292C /* SimpleXInfo.swift in Sources */, diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 954022c312..07095ed5e1 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -640,6 +640,7 @@ public enum ChatResponse: Decodable, Error { 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 businessLinkConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember, fromContact: Contact) case userDeletedMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember) case leftMemberUser(user: UserRef, groupInfo: GroupInfo) case groupMembers(user: UserRef, group: Group) @@ -816,6 +817,7 @@ public enum ChatResponse: Decodable, Error { case .sentGroupInvitation: return "sentGroupInvitation" case .userAcceptedGroupSent: return "userAcceptedGroupSent" case .groupLinkConnecting: return "groupLinkConnecting" + case .businessLinkConnecting: return "businessLinkConnecting" case .userDeletedMember: return "userDeletedMember" case .leftMemberUser: return "leftMemberUser" case .groupMembers: return "groupMembers" @@ -998,6 +1000,7 @@ public enum ChatResponse: Decodable, Error { 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 .businessLinkConnecting(u, groupInfo, hostMember, fromContact): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(String(describing: hostMember))\nfromContact: \(String(describing: fromContact))") 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)) @@ -2103,7 +2106,7 @@ public struct UserContactLink: Decodable, Hashable { } public struct AutoAccept: Codable, Hashable { - public var businessAddress: Bool? // make not nullable + public var businessAddress: Bool public var acceptIncognito: Bool public var autoReply: MsgContent? @@ -2115,7 +2118,12 @@ public struct AutoAccept: Codable, Hashable { static func cmdString(_ autoAccept: AutoAccept?) -> String { guard let autoAccept = autoAccept else { return "off" } - let s = "on" + (autoAccept.acceptIncognito ? " incognito=on" : "") + var s = "on" + if autoAccept.acceptIncognito { + s += " incognito=on" + } else if autoAccept.businessAddress { + s += " business" + } guard let msg = autoAccept.autoReply else { return s } return s + " " + msg.cmdString } diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index de671ee203..b2532c1dc1 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -1890,6 +1890,7 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable { public var groupId: Int64 var localDisplayName: GroupName public var groupProfile: GroupProfile + public var businessChat: BusinessChatInfo? public var fullGroupPreferences: FullGroupPreferences public var membership: GroupMember public var hostConnCustomUserProfileId: Int64? @@ -1908,7 +1909,7 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable { public var image: String? { get { groupProfile.image } } public var localAlias: String { "" } - public var canEdit: Bool { + public var isOwner: Bool { return membership.memberRole == .owner && membership.memberCurrent } @@ -1960,6 +1961,16 @@ public struct GroupProfile: Codable, NamedChat, Hashable { ) } +public struct BusinessChatInfo: Decodable, Hashable { + public var memberId: String + public var chatType: BusinessChatType +} + +public enum BusinessChatType: String, Codable, Hashable { + case business + case customer +} + public struct GroupMember: Identifiable, Decodable, Hashable { public var groupMemberId: Int64 public var groupId: Int64 diff --git a/apps/ios/SimpleXChat/ChatUtils.swift b/apps/ios/SimpleXChat/ChatUtils.swift index 5f56180918..2bf861f437 100644 --- a/apps/ios/SimpleXChat/ChatUtils.swift +++ b/apps/ios/SimpleXChat/ChatUtils.swift @@ -93,7 +93,12 @@ private func canForwardToChat(_ cInfo: ChatInfo) -> Bool { public func chatIconName(_ cInfo: ChatInfo) -> String { switch cInfo { case .direct: "person.crop.circle.fill" - case .group: "person.2.circle.fill" + case let .group(groupInfo): + switch groupInfo.businessChat?.chatType { + case .none: "person.2.circle.fill" + case .business: "briefcase.circle.fill" + case .customer: "person.crop.circle.fill" + } case .local: "folder.circle.fill" case .contactRequest: "person.crop.circle.fill" default: "circle.fill" From 92967dfe0c57c5281583c243956cd30ae087d390 Mon Sep 17 00:00:00 2001 From: Diogo Date: Mon, 2 Dec 2024 20:14:26 +0000 Subject: [PATCH 155/567] ios: disable autocorrect add group member search (#5301) --- apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift index 859d2dfd27..691bda39a6 100644 --- a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift +++ b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift @@ -231,6 +231,7 @@ func searchFieldView(text: Binding, focussed: FocusState.Binding, .focused(focussed) .foregroundColor(onBackgroundColor) .frame(maxWidth: .infinity) + .autocorrectionDisabled(true) Image(systemName: "xmark.circle.fill") .resizable() .scaledToFit() From c04e952620ac3b6a3a01724d88607485837121d3 Mon Sep 17 00:00:00 2001 From: Diogo Date: Mon, 2 Dec 2024 21:00:55 +0000 Subject: [PATCH 156/567] desktop: onboarding improvements (#5294) * consistent space to bottom on future of messaging * consistent button suze on server operators * updated setup database passphrase screen * ability to cancel random passphrase * reduce conditions padding to header * show scrollbar in desktop * EOLs * EOL * fix random passphrase param when deleting database and recreating new one --------- Co-authored-by: Evgeny Co-authored-by: Avently <7953703+avently@users.noreply.github.com> --- .../networkAndServers/OperatorView.android.kt | 19 ++++ .../common/views/database/DatabaseView.kt | 1 + .../views/onboarding/ChooseServerOperators.kt | 8 +- .../common/views/onboarding/HowItWorks.kt | 8 +- .../onboarding/SetupDatabasePassphrase.kt | 94 +++++++++---------- .../networkAndServers/NetworkAndServers.kt | 2 +- .../networkAndServers/OperatorView.kt | 27 +++--- .../networkAndServers/OperatorView.desktop.kt | 51 ++++++++++ 8 files changed, 141 insertions(+), 69 deletions(-) create mode 100644 apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.android.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.desktop.kt diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.android.kt new file mode 100644 index 0000000000..e52515b345 --- /dev/null +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.android.kt @@ -0,0 +1,19 @@ +package chat.simplex.common.views.usersettings.networkAndServers + +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +actual fun ConditionsBox(modifier: Modifier, scrollState: ScrollState, content: @Composable() (BoxScope.() -> Unit)){ + Box( + modifier = modifier + .verticalScroll(scrollState) + .padding(8.dp) + ) { + content() + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index af40aa2e70..e1f53760e5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -525,6 +525,7 @@ fun deleteChatDatabaseFilesAndState() { wallpapersDir.mkdirs() DatabaseUtils.ksDatabasePassword.remove() appPrefs.newDatabaseInitialized.set(false) + chatModel.desktopOnboardingRandomPassword.value = false controller.appPrefs.storeDBPassphrase.set(true) controller.ctrl = null diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt index 84ed7d1651..782f51c205 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt @@ -169,7 +169,7 @@ private fun ReviewConditionsButton( modalManager: ModalManager ) { OnboardingActionButton( - modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier, + modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp), labelId = MR.strings.operator_review_conditions, onboarding = null, enabled = enabled, @@ -184,7 +184,7 @@ private fun ReviewConditionsButton( @Composable private fun SetOperatorsButton(enabled: Boolean, onboarding: Boolean, serverOperators: State>, selectedOperatorIds: State>, close: () -> Unit) { OnboardingActionButton( - modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier, + modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp), labelId = MR.strings.onboarding_network_operators_update, onboarding = null, enabled = enabled, @@ -206,7 +206,7 @@ private fun SetOperatorsButton(enabled: Boolean, onboarding: Boolean, serverOper @Composable private fun ContinueButton(enabled: Boolean, onboarding: Boolean, close: () -> Unit) { OnboardingActionButton( - modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier, + modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp), labelId = MR.strings.onboarding_network_operators_continue, onboarding = null, enabled = enabled, @@ -235,7 +235,7 @@ private fun ReviewConditionsView( val operatorsWithConditionsAccepted = remember { chatModel.conditions.value.serverOperators.filter { it.conditionsAcceptance.conditionsAccepted } } val acceptForOperators = remember { selectedOperators.value.filter { !it.conditionsAcceptance.conditionsAccepted } } ColumnWithScrollBar(modifier = Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING)) { - AppBarTitle(stringResource(MR.strings.operator_conditions_of_use), withPadding = false, enableAlphaChanges = false) + AppBarTitle(stringResource(MR.strings.operator_conditions_of_use), withPadding = false, enableAlphaChanges = false, bottomPadding = DEFAULT_PADDING) if (operatorsWithConditionsAccepted.isNotEmpty()) { ReadableText(MR.strings.operator_conditions_accepted_for_some, args = operatorsWithConditionsAccepted.joinToString(", ") { it.legalName_ }) ReadableText(MR.strings.operator_same_conditions_will_apply_to_operators, args = acceptForOperators.joinToString(", ") { it.legalName_ }) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt index f7e7f456bb..aff02e90f5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt @@ -1,6 +1,7 @@ package chat.simplex.common.views.onboarding import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -23,7 +24,7 @@ import dev.icerock.moko.resources.StringResource @Composable fun HowItWorks(user: User?, onboardingStage: SharedPreference? = null) { - ColumnWithScrollBar(Modifier.padding(DEFAULT_PADDING)) { + ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_PADDING)) { AppBarTitle(stringResource(MR.strings.how_simplex_works), withPadding = false) ReadableText(MR.strings.to_protect_privacy_simplex_has_ids_for_queues) ReadableText(MR.strings.only_client_devices_store_contacts_groups_e2e_encrypted_messages) @@ -35,11 +36,12 @@ fun HowItWorks(user: User?, onboardingStage: SharedPreference? Spacer(Modifier.fillMaxHeight().weight(1f)) if (onboardingStage != null) { - Box(Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING), contentAlignment = Alignment.Center) { + Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { OnboardingActionButton(user, onboardingStage, onclick = { ModalManager.fullscreen.closeModal() }) + // Reserve space + TextButtonBelowOnboardingButton("", null) } } - Spacer(Modifier.height(DEFAULT_PADDING)) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt index f20cb38dad..e7db51c768 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt @@ -1,6 +1,5 @@ package chat.simplex.common.views.onboarding -import SectionTextFooter import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.KeyboardActions import androidx.compose.material.* @@ -12,7 +11,6 @@ import androidx.compose.ui.focus.* import androidx.compose.ui.input.key.* import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.text.input.ImeAction -import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -106,14 +104,31 @@ private fun SetupDatabasePassphraseLayout( CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { ModalView({}, showClose = false) { ColumnWithScrollBar( - Modifier.themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer).padding(bottom = DEFAULT_PADDING * 2), + Modifier.themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer).padding(horizontal = DEFAULT_PADDING), horizontalAlignment = Alignment.CenterHorizontally, ) { AppBarTitle(stringResource(MR.strings.setup_database_passphrase)) - Spacer(Modifier.weight(1f)) + val onClickUpdate = { + // Don't do things concurrently. Shouldn't be here concurrently, just in case + if (!progressIndicator.value) { + encryptDatabaseAlert(onConfirmEncrypt) + } + } + val disabled = currentKey.value == newKey.value || + newKey.value != confirmNewKey.value || + newKey.value.isEmpty() || + !validKey(currentKey.value) || + !validKey(newKey.value) || + progressIndicator.value + + Column(Modifier.width(600.dp), horizontalAlignment = Alignment.CenterHorizontally) { + val textStyle = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary) + ReadableText(MR.strings.you_have_to_enter_passphrase_every_time, TextAlign.Center, padding = PaddingValues(), style = textStyle ) + Spacer(Modifier.height(DEFAULT_PADDING)) + ReadableText(MR.strings.impossible_to_recover_passphrase, TextAlign.Center, padding = PaddingValues(), style = textStyle) + Spacer(Modifier.height(DEFAULT_PADDING)) - Column(Modifier.width(600.dp)) { val focusRequester = remember { FocusRequester() } val focusManager = LocalFocusManager.current LaunchedEffect(Unit) { @@ -138,18 +153,6 @@ private fun SetupDatabasePassphraseLayout( isValid = ::validKey, keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), ) - val onClickUpdate = { - // Don't do things concurrently. Shouldn't be here concurrently, just in case - if (!progressIndicator.value) { - encryptDatabaseAlert(onConfirmEncrypt) - } - } - val disabled = currentKey.value == newKey.value || - newKey.value != confirmNewKey.value || - newKey.value.isEmpty() || - !validKey(currentKey.value) || - !validKey(newKey.value) || - progressIndicator.value PassphraseField( confirmNewKey, @@ -167,21 +170,17 @@ private fun SetupDatabasePassphraseLayout( isValid = { confirmNewKey.value == "" || newKey.value == confirmNewKey.value }, keyboardActions = KeyboardActions(onDone = { defaultKeyboardAction(ImeAction.Done) }), ) - - Box(Modifier.align(Alignment.CenterHorizontally).padding(vertical = DEFAULT_PADDING)) { - SetPassphraseButton(disabled, onClickUpdate) - } - - Column { - SectionTextFooter(generalGetString(MR.strings.you_have_to_enter_passphrase_every_time)) - SectionTextFooter(annotatedStringResource(MR.strings.impossible_to_recover_passphrase)) - } } - Spacer(Modifier.weight(1f)) - SkipButton(progressIndicator.value) { - chatModel.desktopOnboardingRandomPassword.value = true - nextStep() + + Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp), horizontalAlignment = Alignment.CenterHorizontally) { + SetPassphraseButton(disabled, onClickUpdate) + SkipButton(progressIndicator.value) { + randomPassphraseAlert { + chatModel.desktopOnboardingRandomPassword.value = true + nextStep() + } + } } } } @@ -190,30 +189,18 @@ private fun SetupDatabasePassphraseLayout( @Composable private fun SetPassphraseButton(disabled: Boolean, onClick: () -> Unit) { - SimpleButtonIconEnded( - stringResource(MR.strings.set_database_passphrase), - painterResource(MR.images.ic_check), - style = MaterialTheme.typography.h2, - color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary, - disabled = disabled, - click = onClick + OnboardingActionButton( + if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp), + labelId = MR.strings.set_database_passphrase, + onboarding = null, + onclick = onClick, + enabled = !disabled ) } @Composable private fun SkipButton(disabled: Boolean, onClick: () -> Unit) { - SimpleButtonIconEnded(stringResource(MR.strings.use_random_passphrase), painterResource(MR.images.ic_chevron_right), color = - if (disabled) MaterialTheme.colors.secondary else WarningOrange, disabled = disabled, click = onClick) - Text( - stringResource(MR.strings.you_can_change_it_later), - Modifier - .fillMaxWidth() - .padding(horizontal = DEFAULT_PADDING * 3) - .padding(top = DEFAULT_PADDING, bottom = DEFAULT_PADDING - 5.dp), - style = MaterialTheme.typography.subtitle1, - color = MaterialTheme.colors.secondary, - textAlign = TextAlign.Center, - ) + TextButtonBelowOnboardingButton(stringResource(MR.strings.use_random_passphrase), onClick = if (disabled) null else onClick) } @Composable @@ -238,3 +225,12 @@ private suspend fun startChat(key: String?) { m.chatDbChanged.value = false m.chatRunning.value = true } + +private fun randomPassphraseAlert(onConfirm: () -> Unit) { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.use_random_passphrase), + text = generalGetString(MR.strings.you_can_change_it_later), + confirmText = generalGetString(MR.strings.ok), + onConfirm = onConfirm, + ) +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt index 6b1dcec5d8..835e01ec27 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt @@ -734,7 +734,7 @@ fun UsageConditionsView( } ColumnWithScrollBar(modifier = Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING)) { - AppBarTitle(stringResource(MR.strings.operator_conditions_of_use), enableAlphaChanges = false, withPadding = false) + AppBarTitle(stringResource(MR.strings.operator_conditions_of_use), enableAlphaChanges = false, withPadding = false, bottomPadding = DEFAULT_PADDING) when (val conditionsAction = chatModel.conditions.value.conditionsAction) { is UsageConditionsAction.Review -> { if (conditionsAction.operators.isNotEmpty()) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt index 4e0fc6ec79..3836d5b6df 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt @@ -14,6 +14,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.* import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.UriHandler @@ -615,7 +616,6 @@ fun ConditionsTextView( val failedToLoad = remember { mutableStateOf(false) } val defaultConditionsLink = "https://github.com/simplex-chat/simplex-chat/blob/stable/PRIVACY.md" val scope = rememberCoroutineScope() - // can show conditions when animation between modals finishes to prevent glitches val canShowConditionsAt = remember { System.currentTimeMillis() + 300 } LaunchedEffect(Unit) { @@ -645,18 +645,18 @@ fun ConditionsTextView( if (conditionsText != null) { val scrollState = rememberScrollState() - Box( - modifier = Modifier - .fillMaxSize() - .border(border = BorderStroke(1.dp, CurrentColors.value.colors.secondary.copy(alpha = 0.6f)), shape = RoundedCornerShape(12.dp)) - .verticalScroll(scrollState) - .padding(8.dp) - ) { - val parentUriHandler = LocalUriHandler.current - CompositionLocalProvider(LocalUriHandler provides remember { internalUriHandler(parentUriHandler) }) { - ConditionsMarkdown(conditionsText) - } + ConditionsBox( + Modifier + .fillMaxSize() + .border(border = BorderStroke(1.dp, CurrentColors.value.colors.secondary.copy(alpha = 0.6f)), shape = RoundedCornerShape(12.dp)) + .clip(shape = RoundedCornerShape(12.dp)), + scrollState + ) { + val parentUriHandler = LocalUriHandler.current + CompositionLocalProvider(LocalUriHandler provides remember { internalUriHandler(parentUriHandler) }) { + ConditionsMarkdown(conditionsText) } + } } else { val conditionsLink = "https://github.com/simplex-chat/simplex-chat/blob/${usageConditions.conditionsCommit}/PRIVACY.md" ConditionsLinkView(conditionsLink) @@ -668,6 +668,9 @@ fun ConditionsTextView( } } +@Composable +expect fun ConditionsBox(modifier: Modifier, scrollState: ScrollState, content: @Composable() (BoxScope.() -> Unit)) + @Composable private fun ConditionsMarkdown(text: String) { Markdown(text, diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.desktop.kt new file mode 100644 index 0000000000..c9f62a5b79 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.desktop.kt @@ -0,0 +1,51 @@ +package chat.simplex.common.views.usersettings.networkAndServers + +import androidx.compose.animation.core.Animatable +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.unit.dp +import chat.simplex.common.platform.DesktopScrollBar +import chat.simplex.common.views.helpers.detectCursorMove +import kotlinx.coroutines.* + +@Composable +actual fun ConditionsBox(modifier: Modifier, scrollState: ScrollState, content: @Composable() (BoxScope.() -> Unit)) { + val scope = rememberCoroutineScope() + val scrollBarAlpha = remember { Animatable(0f) } + val scrollJob: MutableState = remember { mutableStateOf(Job()) } + val scrollBarDraggingState = remember { mutableStateOf(false) } + val scrollModifier = remember { + Modifier + .pointerInput(Unit) { + detectCursorMove { + scope.launch { + scrollBarAlpha.animateTo(1f) + } + scrollJob.value.cancel() + scrollJob.value = scope.launch { + delay(1000L) + scrollBarAlpha.animateTo(0f) + } + } + } + } + + Box(modifier = modifier) { + Box( + Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .padding(8.dp) + .then(scrollModifier) + ) { + content() + } + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.CenterEnd) { + DesktopScrollBar(rememberScrollbarAdapter(scrollState), Modifier.fillMaxHeight(), scrollBarAlpha, scrollJob, false, scrollBarDraggingState) + } + } +} From e61babdc8f4646a00bf66b43903c9845fce944c2 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 3 Dec 2024 06:36:48 +0700 Subject: [PATCH 157/567] android, desktop: preserving long message when failed to send (#5297) * android, desktop: preserving long message when failed to send * forwarding * unused code * strings --------- Co-authored-by: Evgeny Poberezkin --- .../chat/simplex/common/model/SimpleXAPI.kt | 41 +++++++++++++++++-- .../simplex/common/views/chat/ComposeView.kt | 38 +++++++++++++---- .../commonMain/resources/MR/base/strings.xml | 4 ++ 3 files changed, 71 insertions(+), 12 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 7ada27d000..89c6f40cfe 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -894,8 +894,27 @@ object ChatController { private suspend fun processSendMessageCmd(rh: Long?, cmd: CC): List? { val r = sendCmd(rh, cmd) - return when (r) { - is CR.NewChatItems -> r.chatItems + return when { + r is CR.NewChatItems -> r.chatItems + r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore && r.chatError.storeError is StoreError.LargeMsg && cmd is CC.ApiSendMessages -> { + val mc = cmd.composedMessages.last().msgContent + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.maximum_message_size_title), + if (mc is MsgContent.MCImage || mc is MsgContent.MCVideo || mc is MsgContent.MCLink) { + generalGetString(MR.strings.maximum_message_size_reached_non_text) + } else { + generalGetString(MR.strings.maximum_message_size_reached_text) + } + ) + null + } + r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore && r.chatError.storeError is StoreError.LargeMsg && cmd is CC.ApiForwardChatItems -> { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.maximum_message_size_title), + generalGetString(MR.strings.maximum_message_size_reached_forwarding) + ) + null + } else -> { if (!(networkErrorAlert(r))) { apiErrorAlert("processSendMessageCmd", generalGetString(MR.strings.error_sending_message), r) @@ -943,7 +962,21 @@ object ChatController { suspend fun apiUpdateChatItem(rh: Long?, type: ChatType, id: Long, itemId: Long, mc: MsgContent, live: Boolean = false): AChatItem? { val r = sendCmd(rh, CC.ApiUpdateChatItem(type, id, itemId, mc, live)) - if (r is CR.ChatItemUpdated) return r.chatItem + when { + r is CR.ChatItemUpdated -> return r.chatItem + r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore && r.chatError.storeError is StoreError.LargeMsg -> { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.maximum_message_size_title), + if (mc is MsgContent.MCImage || mc is MsgContent.MCVideo || mc is MsgContent.MCLink) { + generalGetString(MR.strings.maximum_message_size_reached_non_text) + } else { + generalGetString(MR.strings.maximum_message_size_reached_text) + } + ) + return null + } + } + Log.e(TAG, "apiUpdateChatItem bad response: ${r.responseType} ${r.details}") return null } @@ -6357,6 +6390,7 @@ sealed class StoreError { is HostMemberIdNotFound -> "hostMemberIdNotFound" is ContactNotFoundByFileId -> "contactNotFoundByFileId" is NoGroupSndStatus -> "noGroupSndStatus" + is LargeMsg -> "largeMsg" } @Serializable @SerialName("duplicateName") object DuplicateName: StoreError() @@ -6416,6 +6450,7 @@ sealed class StoreError { @Serializable @SerialName("hostMemberIdNotFound") class HostMemberIdNotFound(val groupId: Long): StoreError() @Serializable @SerialName("contactNotFoundByFileId") class ContactNotFoundByFileId(val fileId: Long): StoreError() @Serializable @SerialName("noGroupSndStatus") class NoGroupSndStatus(val itemId: Long, val groupMemberId: Long): StoreError() + @Serializable @SerialName("largeMsg") object LargeMsg: StoreError() } @Serializable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index 6cd46d49a6..3a63cf508e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -412,6 +412,7 @@ fun ComposeView( val cInfo = chat.chatInfo val cs = composeState.value var sent: List? + var lastMessageFailedToSend: ComposeState? = null val msgText = text ?: cs.message fun sending() { @@ -461,6 +462,19 @@ fun ComposeView( } } + fun constructFailedMessage(cs: ComposeState): ComposeState { + val preview = when (cs.preview) { + is ComposePreview.MediaPreview -> { + ComposePreview.MediaPreview( + if (cs.preview.images.isNotEmpty()) listOf(cs.preview.images.last()) else emptyList(), + if (cs.preview.content.isNotEmpty()) listOf(cs.preview.content.last()) else emptyList() + ) + } + else -> cs.preview + } + return cs.copy(inProgress = false, preview = preview) + } + fun updateMsgContent(msgContent: MsgContent): MsgContent { return when (msgContent) { is MsgContent.MCText -> checkLinkPreview() @@ -517,6 +531,9 @@ fun ComposeView( sent = null } else if (cs.contextItem is ComposeContextItem.ForwardingItems) { sent = forwardItem(chat.remoteHostId, cs.contextItem.chatItems, cs.contextItem.fromChatInfo, ttl = ttl) + if (sent == null) { + lastMessageFailedToSend = constructFailedMessage(cs) + } if (cs.message.isNotEmpty()) { sent?.mapIndexed { index, message -> if (index == sent!!.lastIndex) { @@ -531,6 +548,7 @@ fun ComposeView( val ei = cs.contextItem.chatItem val updatedMessage = updateMessage(ei, chat, live) sent = if (updatedMessage != null) listOf(updatedMessage) else null + lastMessageFailedToSend = if (updatedMessage == null) constructFailedMessage(cs) else null } else if (liveMessage != null && liveMessage.sent) { val updatedMessage = updateMessage(liveMessage.chatItem, chat, live) sent = if (updatedMessage != null) listOf(updatedMessage) else null @@ -631,19 +649,21 @@ fun ComposeView( ttl = ttl ) sent = if (sendResult != null) listOf(sendResult) else null - } - if (sent == null && - (cs.preview is ComposePreview.MediaPreview || - cs.preview is ComposePreview.FilePreview || - cs.preview is ComposePreview.VoicePreview) - ) { - val sendResult = send(chat, MsgContent.MCText(msgText), quotedItemId, null, live, ttl) - sent = if (sendResult != null) listOf(sendResult) else null + if (sent == null && index == msgs.lastIndex && cs.liveMessage == null) { + constructFailedMessage(cs) + // it's the last message in the series so if it fails, restore it in ComposeView for editing + lastMessageFailedToSend = constructFailedMessage(cs) + } } } val wasForwarding = cs.forwarding val forwardingFromChatId = (cs.contextItem as? ComposeContextItem.ForwardingItems)?.fromChatInfo?.id - clearState(live) + val lastFailed = lastMessageFailedToSend + if (lastFailed == null) { + clearState(live) + } else { + composeState.value = lastFailed + } val draft = chatModel.draft.value if (wasForwarding && chatModel.draftChatId.value == chat.chatInfo.id && forwardingFromChatId != chat.chatInfo.id && draft != null) { composeState.value = draft diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index c390096ee2..d931abab70 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -441,6 +441,10 @@ Files and media not allowed Voice messages not allowed Message + Message is too large! + Please reduce the message size and send again. + Please reduce the message size or remove media and send again. + You can copy and reduce the message size to send it. Image From 9d992735f41bcfd5d5fd8c3203e4571ac19aba5a Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 3 Dec 2024 12:11:38 +0000 Subject: [PATCH 158/567] core, ios: improve business address (connection plan, repeat requests, feature items) (#5303) * core, ios: connection plan for business address * core: store xcontact_id on business groups to prevent duplicate contact requests * core: create feature items in new groups and in business groups * fix tests * error message --- .../Shared/Views/NewChat/NewChatView.swift | 15 +++++- apps/ios/SimpleXChat/APITypes.swift | 3 -- .../chat/simplex/common/model/SimpleXAPI.kt | 3 -- src/Simplex/Chat.hs | 45 ++++++++++------- src/Simplex/Chat/Controller.hs | 1 + .../Migrations/M20241128_business_chats.hs | 6 +++ src/Simplex/Chat/Migrations/chat_schema.sql | 4 +- src/Simplex/Chat/Store/Direct.hs | 20 ++++++-- src/Simplex/Chat/Store/Groups.hs | 48 ++----------------- src/Simplex/Chat/Store/Shared.hs | 43 +++++++++++++++++ src/Simplex/Chat/Types.hs | 2 +- src/Simplex/Chat/View.hs | 8 +++- tests/ChatTests/ChatList.hs | 18 +++---- tests/ChatTests/Direct.hs | 4 +- tests/ChatTests/Groups.hs | 43 +++++++++-------- tests/ChatTests/Profiles.hs | 34 ++++++++----- tests/ChatTests/Utils.hs | 30 +++++++----- 17 files changed, 195 insertions(+), 132 deletions(-) diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift index 19e810d034..e18d932278 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatView.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -916,11 +916,17 @@ func planAndConnectAlert(_ alert: PlanAndConnectAlert, dismiss: Bool, cleanup: ( ) case let .groupLinkConnecting(_, groupInfo): if let groupInfo = groupInfo { - return Alert( + return groupInfo.businessChat == nil + ? Alert( title: Text("Group already exists!"), message: Text("You are already joining the group \(groupInfo.displayName)."), dismissButton: .default(Text("OK")) { cleanup?() } ) + : Alert( + title: Text("Chat already exists!"), + message: Text("You are already connecting to \(groupInfo.displayName)."), + dismissButton: .default(Text("OK")) { cleanup?() } + ) } else { return Alert( title: Text("Already joining the group!"), @@ -1237,10 +1243,15 @@ func contactAlreadyConnectingAlert(_ contact: Contact) -> Alert { } func groupAlreadyExistsAlert(_ groupInfo: GroupInfo) -> Alert { - mkAlert( + groupInfo.businessChat == nil + ? mkAlert( title: "Group already exists", message: "You are already in group \(groupInfo.displayName)." ) + : mkAlert( + title: "Chat already exists", + message: "You are already connected with \(groupInfo.displayName)." + ) } enum ConnReqType: Equatable { diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 07095ed5e1..ca9bb70ea4 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -598,7 +598,6 @@ public enum ChatResponse: Decodable, Error { case sentInvitation(user: UserRef, connection: PendingContactConnection) 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) case contactDeletedByContact(user: UserRef, contact: Contact) case chatCleared(user: UserRef, chatInfo: ChatInfo) @@ -776,7 +775,6 @@ public enum ChatResponse: Decodable, Error { case .sentInvitation: return "sentInvitation" case .sentInvitationToContact: return "sentInvitationToContact" case .contactAlreadyExists: return "contactAlreadyExists" - case .contactRequestAlreadyAccepted: return "contactRequestAlreadyAccepted" case .contactDeleted: return "contactDeleted" case .contactDeletedByContact: return "contactDeletedByContact" case .chatCleared: return "chatCleared" @@ -952,7 +950,6 @@ public enum ChatResponse: Decodable, Error { case let .sentInvitation(u, connection): return withUser(u, String(describing: connection)) 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)) case let .contactDeletedByContact(u, contact): return withUser(u, String(describing: contact)) case let .chatCleared(u, chatInfo): return withUser(u, String(describing: chatInfo)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 89c6f40cfe..d9c7df9d81 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -5439,7 +5439,6 @@ sealed class CR { @Serializable @SerialName("sentInvitation") class SentInvitation(val user: UserRef, val connection: PendingContactConnection): CR() @Serializable @SerialName("sentInvitationToContact") class SentInvitationToContact(val user: UserRef, val contact: Contact, val customUserProfile: Profile?): CR() @Serializable @SerialName("contactAlreadyExists") class ContactAlreadyExists(val user: UserRef, val contact: Contact): CR() - @Serializable @SerialName("contactRequestAlreadyAccepted") class ContactRequestAlreadyAccepted(val user: UserRef, val contact: Contact): CR() @Serializable @SerialName("contactDeleted") class ContactDeleted(val user: UserRef, val contact: Contact): CR() @Serializable @SerialName("contactDeletedByContact") class ContactDeletedByContact(val user: UserRef, val contact: Contact): CR() @Serializable @SerialName("chatCleared") class ChatCleared(val user: UserRef, val chatInfo: ChatInfo): CR() @@ -5623,7 +5622,6 @@ sealed class CR { is SentInvitation -> "sentInvitation" is SentInvitationToContact -> "sentInvitationToContact" is ContactAlreadyExists -> "contactAlreadyExists" - is ContactRequestAlreadyAccepted -> "contactRequestAlreadyAccepted" is ContactDeleted -> "contactDeleted" is ContactDeletedByContact -> "contactDeletedByContact" is ChatCleared -> "chatCleared" @@ -5797,7 +5795,6 @@ sealed class CR { is SentInvitation -> withUser(user, json.encodeToString(connection)) is SentInvitationToContact -> withUser(user, json.encodeToString(contact)) is ContactAlreadyExists -> withUser(user, json.encodeToString(contact)) - is ContactRequestAlreadyAccepted -> withUser(user, json.encodeToString(contact)) is ContactDeleted -> withUser(user, json.encodeToString(contact)) is ContactDeletedByContact -> withUser(user, json.encodeToString(contact)) is ChatCleared -> withUser(user, json.encodeToString(chatInfo)) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 7a48914abf..31862253dd 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2208,9 +2208,11 @@ processChatCommand' vr = \case gVar <- asks random -- [incognito] generate incognito profile for group membership incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing - groupInfo <- withFastStore $ \db -> createNewGroup db vr gVar user gProfile incognitoProfile - createInternalChatItem user (CDGroupSnd groupInfo) (CISndGroupE2EEInfo $ E2EInfo {pqEnabled = PQEncOff}) Nothing - pure $ CRGroupCreated user groupInfo + gInfo <- withFastStore $ \db -> createNewGroup db vr gVar user gProfile incognitoProfile + let cd = CDGroupSnd gInfo + createInternalChatItem user cd (CISndGroupE2EEInfo E2EInfo {pqEnabled = PQEncOff}) Nothing + createGroupFeatureItems user cd CISndGroupFeature gInfo + pure $ CRGroupCreated user gInfo NewGroup incognito gProfile -> withUser $ \User {userId} -> processChatCommand $ APINewGroup userId incognito gProfile APIAddMember groupId contactId memRole -> withUser $ \user -> withGroupLock "addMember" groupId $ do @@ -3137,7 +3139,8 @@ processChatCommand' vr = \case | not (contactReady ct) && contactActive ct -> pure $ CPContactAddress (CAPConnectingProhibit ct) | contactDeleted ct -> pure $ CPContactAddress CAPOk | otherwise -> pure $ CPContactAddress (CAPKnown ct) - Just _ -> throwChatError $ CECommandError "found connection entity is not RcvDirectMsgConnection" + Just (RcvGroupMsgConnection _ gInfo _) -> groupPlan gInfo + Just _ -> throwChatError $ CECommandError "found connection entity is not RcvDirectMsgConnection or RcvGroupMsgConnection" -- group link Just _ -> withFastStore' (\db -> getGroupInfoByUserContactLinkConnReq db vr user cReqSchemas) >>= \case @@ -3152,12 +3155,13 @@ processChatCommand' vr = \case | not (contactReady ct) && contactActive ct -> pure $ CPGroupLink (GLPConnectingProhibit gInfo_) | otherwise -> pure $ CPGroupLink GLPOk (Nothing, Just _) -> throwChatError $ CECommandError "found connection entity is not RcvDirectMsgConnection" - (Just gInfo@GroupInfo {membership}, _) - | not (memberActive membership) && not (memberRemoved membership) -> - pure $ CPGroupLink (GLPConnectingProhibit gInfo_) - | memberActive membership -> pure $ CPGroupLink (GLPKnown gInfo) - | otherwise -> pure $ CPGroupLink GLPOk + (Just gInfo, _) -> groupPlan gInfo where + groupPlan gInfo@GroupInfo {membership} + | not (memberActive membership) && not (memberRemoved membership) = + pure $ CPGroupLink (GLPConnectingProhibit $ Just gInfo) + | memberActive membership = pure $ CPGroupLink (GLPKnown gInfo) + | otherwise = pure $ CPGroupLink GLPOk cReqSchemas :: (ConnReqContact, ConnReqContact) cReqSchemas = ( CRContactUri crData {crScheme = SSSimplex}, @@ -4035,6 +4039,9 @@ acceptBusinessJoinRequestAsync let chatV = vr `peerConnChatVersion` cReqChatVRange connIds <- agentAcceptContactAsync user True invId msg subMode PQSupportOff chatV withStore' $ \db -> createAcceptedMemberConnection db user connIds chatV ucr groupMemberId subMode + let cd = CDGroupSnd gInfo + createInternalChatItem user cd (CISndGroupE2EEInfo E2EInfo {pqEnabled = PQEncOff}) Nothing + createGroupFeatureItems user cd CISndGroupFeature gInfo pure gInfo where businessGroupProfile :: Profile -> GroupPreferences -> GroupProfile @@ -5086,8 +5093,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = case memberCategory m of GCHostMember -> do toView $ CRUserJoinedGroup user gInfo {membership = membership {memberStatus = GSMemConnected}} m {memberStatus = GSMemConnected} - createInternalChatItem user (CDGroupRcv gInfo m) (CIRcvGroupE2EEInfo $ E2EInfo {pqEnabled = PQEncOff}) Nothing - createGroupFeatureItems gInfo m + let cd = CDGroupRcv gInfo m + createInternalChatItem user cd (CIRcvGroupE2EEInfo E2EInfo {pqEnabled = PQEncOff}) Nothing + createGroupFeatureItems user cd CIRcvGroupFeature gInfo let GroupInfo {groupProfile = GroupProfile {description}} = gInfo memberConnectedChatItem gInfo m unless expectHistory $ forM_ description $ groupDescriptionChatItem gInfo m @@ -5597,6 +5605,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = profileContactRequest invId chatVRange p xContactId_ reqPQSup = do withStore (\db -> createOrUpdateContactRequest db vr user userContactLinkId invId chatVRange p xContactId_ reqPQSup) >>= \case CORContact contact -> toView $ CRContactRequestAlreadyAccepted user contact + CORGroup gInfo -> toView $ CRBusinessRequestAlreadyAccepted user gInfo CORRequest cReq -> do ucl <- withStore $ \db -> getUserContactLinkById db userId userContactLinkId let (UserContactLink {connReqContact, autoAccept}, groupId_, gLinkMemRole) = ucl @@ -6487,13 +6496,6 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = let state = featureState $ getContactUserPreference f mergedPreferences createInternalChatItem user (CDDirectRcv ct) (uncurry (CIRcvChatFeature $ chatFeature f) state) Nothing - createGroupFeatureItems :: GroupInfo -> GroupMember -> CM () - createGroupFeatureItems g@GroupInfo {fullGroupPreferences} m = - forM_ allGroupFeatures $ \(AGF f) -> do - let p = getGroupPreference f fullGroupPreferences - (_, param, role) = groupFeatureState p - createInternalChatItem user (CDGroupRcv g m) (CIRcvGroupFeature (toGroupFeature f) (toGroupPreference p) param role) Nothing - xInfoProbe :: ContactOrMember -> Probe -> CM () xInfoProbe cgm2 probe = do contactMerge <- readTVarIO =<< asks contactMergeEnabled @@ -8179,6 +8181,13 @@ createGroupFeatureChangedItems user cd ciContent GroupInfo {fullGroupPreferences sameGroupProfileInfo :: GroupProfile -> GroupProfile -> Bool sameGroupProfileInfo p p' = p {groupPreferences = Nothing} == p' {groupPreferences = Nothing} +createGroupFeatureItems :: MsgDirectionI d => User -> ChatDirection 'CTGroup d -> (GroupFeature -> GroupPreference -> Maybe Int -> Maybe GroupMemberRole -> CIContent d) -> GroupInfo -> CM () +createGroupFeatureItems user cd ciContent GroupInfo {fullGroupPreferences} = + forM_ allGroupFeatures $ \(AGF f) -> do + let p = getGroupPreference f fullGroupPreferences + (_, param, role) = groupFeatureState p + createInternalChatItem user cd (ciContent (toGroupFeature f) (toGroupPreference p) param role) Nothing + createInternalChatItem :: (ChatTypeI c, MsgDirectionI d) => User -> ChatDirection c d -> CIContent d -> Maybe UTCTime -> CM () createInternalChatItem user cd content itemTs_ = lift (createInternalItemsForChats user itemTs_ [(cd, [content])]) >>= \case diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 0ab3ba5652..593c328d0c 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -669,6 +669,7 @@ data ChatResponse | CRAcceptingBusinessRequest {user :: User, groupInfo :: GroupInfo} | CRContactAlreadyExists {user :: User, contact :: Contact} | CRContactRequestAlreadyAccepted {user :: User, contact :: Contact} + | CRBusinessRequestAlreadyAccepted {user :: User, groupInfo :: GroupInfo} | CRLeftMemberUser {user :: User, groupInfo :: GroupInfo} | CRGroupDeletedUser {user :: User, groupInfo :: GroupInfo} | CRForwardPlan {user :: User, itemsCount :: Int, chatItemIds :: [ChatItemId], forwardConfirmation :: Maybe ForwardConfirmation} diff --git a/src/Simplex/Chat/Migrations/M20241128_business_chats.hs b/src/Simplex/Chat/Migrations/M20241128_business_chats.hs index f068b1bd81..2b3be38030 100644 --- a/src/Simplex/Chat/Migrations/M20241128_business_chats.hs +++ b/src/Simplex/Chat/Migrations/M20241128_business_chats.hs @@ -11,12 +11,18 @@ m20241128_business_chats = ALTER TABLE user_contact_links ADD business_address INTEGER DEFAULT 0; ALTER TABLE groups ADD COLUMN business_member_id BLOB NULL; ALTER TABLE groups ADD COLUMN business_chat TEXT NULL; +ALTER TABLE groups ADD COLUMN business_xcontact_id BLOB NULL; + +CREATE INDEX idx_groups_business_xcontact_id ON groups(business_xcontact_id); |] down_m20241128_business_chats :: Query down_m20241128_business_chats = [sql| +DROP INDEX idx_groups_business_xcontact_id; + ALTER TABLE user_contact_links DROP COLUMN business_address; ALTER TABLE groups DROP COLUMN business_member_id; ALTER TABLE groups DROP COLUMN business_chat; +ALTER TABLE groups DROP COLUMN business_xcontact_id; |] diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index 460b348b4d..621c28f91e 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -129,7 +129,8 @@ CREATE TABLE groups( custom_data BLOB, ui_themes TEXT, business_member_id BLOB NULL, - business_chat TEXT NULL, -- received + business_chat TEXT NULL, + business_xcontact_id BLOB NULL, -- received FOREIGN KEY(user_id, local_display_name) REFERENCES display_names(user_id, local_display_name) ON DELETE CASCADE @@ -926,3 +927,4 @@ CREATE INDEX idx_chat_items_notes ON chat_items( item_status, created_at ); +CREATE INDEX idx_groups_business_xcontact_id ON groups(business_xcontact_id); diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs index 4d33fa113d..b7759f0905 100644 --- a/src/Simplex/Chat/Store/Direct.hs +++ b/src/Simplex/Chat/Store/Direct.hs @@ -102,6 +102,7 @@ import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow) import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import Simplex.Messaging.Crypto.Ratchet (PQSupport) import Simplex.Messaging.Protocol (SubscriptionMode (..)) +import Simplex.Messaging.Util ((<$$>)) import Simplex.Messaging.Version getPendingContactConnection :: DB.Connection -> UserId -> Int64 -> ExceptT StoreError IO PendingContactConnection @@ -591,13 +592,17 @@ getUserContacts db vr user@User {userId} = do contacts <- rights <$> mapM (runExceptT . getContact db vr user) contactIds pure $ filter (\Contact {activeConn} -> isJust activeConn) contacts -createOrUpdateContactRequest :: DB.Connection -> VersionRangeChat -> User -> Int64 -> InvitationId -> VersionRangeChat -> Profile -> Maybe XContactId -> PQSupport -> ExceptT StoreError IO ContactOrRequest -createOrUpdateContactRequest db vr user@User {userId} userContactLinkId invId (VersionRange minV maxV) Profile {displayName, fullName, image, contactLink, preferences} xContactId_ pqSup = - liftIO (maybeM getContact' xContactId_) >>= \case - Just contact -> pure $ CORContact contact +createOrUpdateContactRequest :: DB.Connection -> VersionRangeChat -> User -> Int64 -> InvitationId -> VersionRangeChat -> Profile -> Maybe XContactId -> PQSupport -> ExceptT StoreError IO ChatOrRequest +createOrUpdateContactRequest db vr user@User {userId, userContactId} userContactLinkId invId (VersionRange minV maxV) Profile {displayName, fullName, image, contactLink, preferences} xContactId_ pqSup = + liftIO (maybeM getContactOrGroup xContactId_) >>= \case + Just cr -> pure cr Nothing -> CORRequest <$> createOrUpdate_ where maybeM = maybe (pure Nothing) + getContactOrGroup xContactId = + getContact' xContactId >>= \case + Just ct -> pure $ Just $ CORContact ct + Nothing -> CORGroup <$$> getGroupInfo' xContactId createOrUpdate_ :: ExceptT StoreError IO UserContactRequest createOrUpdate_ = do cReqId <- @@ -651,6 +656,13 @@ createOrUpdateContactRequest db vr user@User {userId} userContactLinkId invId (V LIMIT 1 |] (userId, xContactId) + getGroupInfo' :: XContactId -> IO (Maybe GroupInfo) + getGroupInfo' xContactId = + maybeFirstRow (toGroupInfo vr userContactId) $ + DB.query + db + (groupInfoQuery <> " WHERE g.business_xcontact_id = ? AND g.user_id = ? AND mu.contact_id = ?") + (xContactId, userId, userContactId) getContactRequestByXContactId :: XContactId -> IO (Maybe UserContactRequest) getContactRequestByXContactId xContactId = maybeFirstRow toContactRequest $ diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index bb5252ddb0..2c938ecc44 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -155,31 +155,8 @@ import Simplex.Messaging.Util (eitherToMaybe, ($>>=), (<$$>)) import Simplex.Messaging.Version import UnliftIO.STM -type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe MsgFilter, Maybe Bool, Bool, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime, Maybe MemberId, Maybe BusinessChatType, Maybe UIThemeEntityOverrides, Maybe CustomData) :. GroupMemberRow - -type GroupMemberRow = ((Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, Bool, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences)) - type MaybeGroupMemberRow = ((Maybe Int64, Maybe Int64, Maybe MemberId, Maybe VersionChat, Maybe VersionChat, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe Bool, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId, Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe ImageData, Maybe ConnReqContact, Maybe LocalAlias, Maybe Preferences)) -toGroupInfo :: VersionRangeChat -> Int64 -> GroupInfoRow -> GroupInfo -toGroupInfo vr userContactId ((groupId, localDisplayName, displayName, fullName, description, image, hostConnCustomUserProfileId, enableNtfs_, sendRcpts, favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt, businessMemberId, businessChatType, uiThemes, customData) :. userMemberRow) = - let membership = (toGroupMember userContactId userMemberRow) {memberChatVRange = vr} - chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts, favorite} - fullGroupPreferences = mergeGroupPreferences groupPreferences - groupProfile = GroupProfile {displayName, fullName, description, image, groupPreferences} - businessChat = BusinessChatInfo <$> businessMemberId <*> businessChatType - in GroupInfo {groupId, localDisplayName, groupProfile, businessChat, fullGroupPreferences, membership, hostConnCustomUserProfileId, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, uiThemes, customData} - -toGroupMember :: Int64 -> GroupMemberRow -> GroupMember -toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, showMessages, memberRestriction_) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, preferences)) = - let memberProfile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} - memberSettings = GroupMemberSettings {showMessages} - blockedByAdmin = maybe False mrsBlocked memberRestriction_ - invitedBy = toInvitedBy userContactId invitedById - activeConn = Nothing - memberChatVRange = fromMaybe (versionToRange maxVer) $ safeVersionRange minVer maxVer - in GroupMember {..} - toMaybeGroupMember :: Int64 -> MaybeGroupMemberRow -> Maybe GroupMember toMaybeGroupMember userContactId ((Just groupMemberId, Just groupId, Just memberId, Just minVer, Just maxVer, Just memberRole, Just memberCategory, Just memberStatus, Just showMessages, memberBlocked) :. (invitedById, invitedByGroupMemberId, Just localDisplayName, memberContactId, Just memberContactProfileId, Just profileId, Just displayName, Just fullName, image, contactLink, Just localAlias, contactPreferences)) = Just $ toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, showMessages, memberBlocked) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, contactPreferences)) @@ -937,7 +914,7 @@ createBusinessRequestGroup vr gVar user@User {userId, userContactId} - UserContactRequest {cReqChatVRange, profile = Profile {displayName, fullName, image, contactLink, preferences}} + UserContactRequest {cReqChatVRange, xContactId, profile = Profile {displayName, fullName, image, contactLink, preferences}} groupPreferences = do currentTs <- liftIO getCurrentTime groupInfo <- insertGroup_ currentTs @@ -959,10 +936,10 @@ createBusinessRequestGroup [sql| INSERT INTO groups (group_profile_id, local_display_name, user_id, enable_ntfs, - created_at, updated_at, chat_ts, user_member_profile_sent_at, business_chat) - VALUES (?,?,?,?,?,?,?,?,?) + created_at, updated_at, chat_ts, user_member_profile_sent_at, business_chat, business_xcontact_id) + VALUES (?,?,?,?,?,?,?,?,?,?) |] - (profileId, localDisplayName, userId, True, currentTs, currentTs, currentTs, currentTs, BCCustomer) + (profileId, localDisplayName, userId, True, currentTs, currentTs, currentTs, currentTs, BCCustomer, xContactId) insertedRowId db memberId <- liftIO $ encodedRandomBytes gVar 12 void $ createContactMemberInv_ db user groupId Nothing user (MemberIdRole (MemberId memberId) GROwner) GCUserMember GSMemCreator IBUser Nothing currentTs vr @@ -1484,22 +1461,7 @@ getGroupInfo db vr User {userId, userContactId} groupId = ExceptT . firstRow (toGroupInfo vr userContactId) (SEGroupNotFound groupId) $ DB.query db - [sql| - SELECT - -- GroupInfo - g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, - g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, - g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data, - -- GroupMember - membership - mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, - mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, - pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences - FROM groups g - JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id - JOIN group_members mu ON mu.group_id = g.group_id - JOIN contact_profiles pu ON pu.contact_profile_id = COALESCE(mu.member_profile_id, mu.contact_profile_id) - WHERE g.group_id = ? AND g.user_id = ? AND mu.contact_id = ? - |] + (groupInfoQuery <> " WHERE g.group_id = ? AND g.user_id = ? AND mu.contact_id = ?") (groupId, userId, userContactId) getGroupInfoByUserContactLinkConnReq :: DB.Connection -> VersionRangeChat -> User -> (ConnReqContact, ConnReqContact) -> IO (Maybe GroupInfo) diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index fcd9896917..b00ba5705f 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -5,6 +5,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeOperators #-} @@ -33,6 +34,7 @@ import Simplex.Chat.Protocol import Simplex.Chat.Remote.Types import Simplex.Chat.Types import Simplex.Chat.Types.Preferences +import Simplex.Chat.Types.Shared import Simplex.Chat.Types.UITheme import Simplex.Messaging.Agent.Protocol (ConnId, UserId) import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow) @@ -543,3 +545,44 @@ safeDeleteLDN db User {userId} localDisplayName = do AND local_display_name NOT IN (SELECT local_display_name FROM users WHERE user_id = ?) |] (userId, localDisplayName, userId) + +type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe MsgFilter, Maybe Bool, Bool, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime, Maybe MemberId, Maybe BusinessChatType, Maybe UIThemeEntityOverrides, Maybe CustomData) :. GroupMemberRow + +type GroupMemberRow = ((Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, Bool, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences)) + +toGroupInfo :: VersionRangeChat -> Int64 -> GroupInfoRow -> GroupInfo +toGroupInfo vr userContactId ((groupId, localDisplayName, displayName, fullName, description, image, hostConnCustomUserProfileId, enableNtfs_, sendRcpts, favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt, businessMemberId, businessChatType, uiThemes, customData) :. userMemberRow) = + let membership = (toGroupMember userContactId userMemberRow) {memberChatVRange = vr} + chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts, favorite} + fullGroupPreferences = mergeGroupPreferences groupPreferences + groupProfile = GroupProfile {displayName, fullName, description, image, groupPreferences} + businessChat = BusinessChatInfo <$> businessMemberId <*> businessChatType + in GroupInfo {groupId, localDisplayName, groupProfile, businessChat, fullGroupPreferences, membership, hostConnCustomUserProfileId, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, uiThemes, customData} + +toGroupMember :: Int64 -> GroupMemberRow -> GroupMember +toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, showMessages, memberRestriction_) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, preferences)) = + let memberProfile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} + memberSettings = GroupMemberSettings {showMessages} + blockedByAdmin = maybe False mrsBlocked memberRestriction_ + invitedBy = toInvitedBy userContactId invitedById + activeConn = Nothing + memberChatVRange = fromMaybe (versionToRange maxVer) $ safeVersionRange minVer maxVer + in GroupMember {..} + +groupInfoQuery :: Query +groupInfoQuery = + [sql| + SELECT + -- GroupInfo + g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, + g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, + g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data, + -- GroupMember - membership + mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, + mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, + pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences + FROM groups g + JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id + JOIN group_members mu ON mu.group_id = g.group_id + JOIN contact_profiles pu ON pu.contact_profile_id = COALESCE(mu.member_profile_id, mu.contact_profile_id) + |] diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index cc98b4101d..ec8f546d2f 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -349,7 +349,7 @@ instance ToJSON ConnReqUriHash where toJSON = strToJSON toEncoding = strToJEncoding -data ContactOrRequest = CORContact Contact | CORRequest UserContactRequest +data ChatOrRequest = CORContact Contact | CORGroup GroupInfo | CORRequest UserContactRequest type UserName = Text diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 4458f8ee7b..8b6a545637 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -207,6 +207,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRAcceptingBusinessRequest u g -> ttyUser u $ viewAcceptingBusinessRequest g CRContactAlreadyExists u c -> ttyUser u [ttyFullContact c <> ": contact already exists"] CRContactRequestAlreadyAccepted u c -> ttyUser u [ttyFullContact c <> ": sent you a duplicate contact request, but you are already connected, no action needed"] + CRBusinessRequestAlreadyAccepted u g -> ttyUser u [ttyFullGroup g <> ": sent you a duplicate connection request, but you are already connected, no action needed"] CRUserContactLinkCreated u cReq -> ttyUser u $ connReqContact_ "Your new chat address is created!" cReq CRUserContactLinkDeleted u -> ttyUser u viewUserContactLinkDeleted CRUserAcceptedGroupSent u _g _ -> ttyUser u [] -- [ttyGroup' g <> ": joining the group..."] @@ -1670,13 +1671,16 @@ viewConnectionPlan = \case GLPOwnLink g -> [grpLink "own link for group " <> ttyGroup' g] GLPConnectingConfirmReconnect -> [grpLink "connecting, allowed to reconnect"] GLPConnectingProhibit Nothing -> [grpLink "connecting"] - GLPConnectingProhibit (Just g) -> [grpLink ("connecting to group " <> ttyGroup' g)] + GLPConnectingProhibit (Just g) -> [grpOrBiz g <> " link: connecting to " <> grpOrBiz g <> " " <> ttyGroup' g] GLPKnown g -> - [ grpLink ("known group " <> ttyGroup' g), + [ grpOrBiz g <> " link: known " <> grpOrBiz g <> " " <> ttyGroup' g, "use " <> ttyToGroup g <> highlight' "" <> " to send messages" ] where grpLink = ("group link: " <>) + grpOrBiz GroupInfo {businessChat} = case businessChat of + Just _ -> "business" + Nothing -> "group" viewContactUpdated :: Contact -> Contact -> [StyledString] viewContactUpdated diff --git a/tests/ChatTests/ChatList.hs b/tests/ChatTests/ChatList.hs index 7f02fafc2c..de12caf648 100644 --- a/tests/ChatTests/ChatList.hs +++ b/tests/ChatTests/ChatList.hs @@ -199,14 +199,14 @@ testPaginationAllChatTypes = ts7 <- iso8601Show <$> getCurrentTime - getChats_ alice "count=10" [("*", "psst"), ("@dan", "hey"), ("#team", e2eeInfoNoPQStr), (":3", ""), ("<@cath", ""), ("@bob", "hey")] - getChats_ alice "count=3" [("*", "psst"), ("@dan", "hey"), ("#team", e2eeInfoNoPQStr)] + getChats_ alice "count=10" [("*", "psst"), ("@dan", "hey"), ("#team", "Recent history: on"), (":3", ""), ("<@cath", ""), ("@bob", "hey")] + getChats_ alice "count=3" [("*", "psst"), ("@dan", "hey"), ("#team", "Recent history: on")] getChats_ alice ("after=" <> ts2 <> " count=2") [(":3", ""), ("<@cath", "")] - getChats_ alice ("before=" <> ts5 <> " count=2") [("#team", e2eeInfoNoPQStr), (":3", "")] - getChats_ alice ("after=" <> ts3 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", e2eeInfoNoPQStr), (":3", "")] + getChats_ alice ("before=" <> ts5 <> " count=2") [("#team", "Recent history: on"), (":3", "")] + getChats_ alice ("after=" <> ts3 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", "Recent history: on"), (":3", "")] getChats_ alice ("before=" <> ts4 <> " count=10") [(":3", ""), ("<@cath", ""), ("@bob", "hey")] - getChats_ alice ("after=" <> ts1 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", e2eeInfoNoPQStr), (":3", ""), ("<@cath", ""), ("@bob", "hey")] - getChats_ alice ("before=" <> ts7 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", e2eeInfoNoPQStr), (":3", ""), ("<@cath", ""), ("@bob", "hey")] + getChats_ alice ("after=" <> ts1 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", "Recent history: on"), (":3", ""), ("<@cath", ""), ("@bob", "hey")] + getChats_ alice ("before=" <> ts7 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", "Recent history: on"), (":3", ""), ("<@cath", ""), ("@bob", "hey")] getChats_ alice ("after=" <> ts7 <> " count=10") [] getChats_ alice ("before=" <> ts1 <> " count=10") [] @@ -218,11 +218,11 @@ testPaginationAllChatTypes = alice ##> "/_settings #1 {\"enableNtfs\":\"all\",\"favorite\":true}" alice <## "ok" - getChats_ alice queryFavorite [("#team", e2eeInfoNoPQStr), ("@bob", "hey")] + getChats_ alice queryFavorite [("#team", "Recent history: on"), ("@bob", "hey")] getChats_ alice ("before=" <> ts4 <> " count=1 " <> queryFavorite) [("@bob", "hey")] - getChats_ alice ("before=" <> ts5 <> " count=1 " <> queryFavorite) [("#team", e2eeInfoNoPQStr)] + getChats_ alice ("before=" <> ts5 <> " count=1 " <> queryFavorite) [("#team", "Recent history: on")] getChats_ alice ("after=" <> ts1 <> " count=1 " <> queryFavorite) [("@bob", "hey")] - getChats_ alice ("after=" <> ts4 <> " count=1 " <> queryFavorite) [("#team", e2eeInfoNoPQStr)] + getChats_ alice ("after=" <> ts4 <> " count=1 " <> queryFavorite) [("#team", "Recent history: on")] let queryUnread = "{\"type\": \"filters\", \"favorite\": false, \"unread\": true}" diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index 72a28c3ada..95d27801f6 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -2620,7 +2620,7 @@ testSwitchGroupMember = bob <## "#team: alice changed address for you" alice <## "#team: you changed address for bob" threadDelay 100000 - alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (1, "started changing address for bob..."), (1, "you changed address for bob")]) + alice #$> ("/_get chat #1 count=100", chat, sndGroupFeatures <> [(0, "connected"), (1, "started changing address for bob..."), (1, "you changed address for bob")]) bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "started changing address for you..."), (0, "changed address for you")]) alice #> "#team hey" bob <# "#team alice> hey" @@ -2652,7 +2652,7 @@ testAbortSwitchGroupMember tmp = do bob <## "#team: alice changed address for you" alice <## "#team: you changed address for bob" threadDelay 100000 - alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (1, "started changing address for bob..."), (1, "started changing address for bob..."), (1, "you changed address for bob")]) + alice #$> ("/_get chat #1 count=100", chat, sndGroupFeatures <> [(0, "connected"), (1, "started changing address for bob..."), (1, "started changing address for bob..."), (1, "you changed address for bob")]) bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "started changing address for you..."), (0, "started changing address for you..."), (0, "changed address for you")]) alice #> "#team hey" bob <# "#team alice> hey" diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index 89462b2b61..a185b0038e 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -342,11 +342,11 @@ testGroupShared alice bob cath checkMessages directConnections = do getReadChats :: HasCallStack => String -> String -> IO () getReadChats msgItem1 msgItem2 = do alice @@@ [("#team", "hey team"), ("@cath", "sent invitation to join group team as admin"), ("@bob", "sent invitation to join group team as admin")] - alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (0, "connected"), (1, "hello"), (0, "hi there"), (0, "hey team")]) + alice #$> ("/_get chat #1 count=100", chat, sndGroupFeatures <> [(0, "connected"), (0, "connected"), (1, "hello"), (0, "hi there"), (0, "hey team")]) -- "before" and "after" define a chat item id across all chats, -- so we take into account group event items as well as sent group invitations in direct chats alice #$> ("/_get chat #1 after=" <> msgItem1 <> " count=100", chat, [(0, "hi there"), (0, "hey team")]) - alice #$> ("/_get chat #1 before=" <> msgItem2 <> " count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (0, "connected"), (1, "hello"), (0, "hi there")]) + alice #$> ("/_get chat #1 before=" <> msgItem2 <> " count=100", chat, sndGroupFeatures <> [(0, "connected"), (0, "connected"), (1, "hello"), (0, "hi there")]) alice #$> ("/_get chat #1 around=" <> msgItem1 <> " count=2", chat, [(0, "connected"), (0, "connected"), (1, "hello"), (0, "hi there"), (0, "hey team")]) alice #$> ("/_get chat #1 count=100 search=team", chat, [(0, "hey team")]) bob @@@ [("@cath", "hey"), ("#team", "hey team"), ("@alice", "received invitation to join group team as admin")] @@ -565,18 +565,21 @@ testGroup2 = dan <##> cath dan <##> alice -- show last messages - alice ##> "/t #club 9" + alice ##> "/t #club 17" alice -- these strings are expected in any order because of sorting by time and rounding of time for sent - <##? [ ConsoleString ("#club " <> e2eeInfoNoPQStr), - "#club bob> connected", - "#club cath> connected", - "#club bob> added dan (Daniel)", - "#club dan> connected", - "#club hello", - "#club bob> hi there", - "#club cath> hey", - "#club dan> how is it going?" - ] + <##? + ( map (ConsoleString . ("#club " <> )) groupFeatureStrs + <> + [ "#club bob> connected", + "#club cath> connected", + "#club bob> added dan (Daniel)", + "#club dan> connected", + "#club hello", + "#club bob> hi there", + "#club cath> hey", + "#club dan> how is it going?" + ] + ) alice ##> "/t @dan 2" alice <##? [ "dan> hi", @@ -2139,7 +2142,7 @@ testGroupLink = bob <## "#team: you joined the group" ] threadDelay 100000 - alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "invited via your group link"), (0, "connected")]) + alice #$> ("/_get chat #1 count=100", chat, sndGroupFeatures <> [(0, "invited via your group link"), (0, "connected")]) -- contacts connected via group link are not in chat previews alice @@@ [("#team", "connected")] bob @@@ [("#team", "connected")] @@ -3000,7 +3003,7 @@ testGroupLinkNoContact = ] threadDelay 100000 - alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (1, "Recent history: off"), (0, "invited via your group link"), (0, "connected")]) + alice #$> ("/_get chat #1 count=100", chat, sndGroupFeatures <> [(1, "Recent history: off"), (0, "invited via your group link"), (0, "connected")]) alice @@@ [("#team", "connected")] bob @@@ [("#team", "connected")] @@ -3063,7 +3066,7 @@ testGroupLinkNoContactInviteesWereConnected = ] threadDelay 100000 - alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (1, "Recent history: off"), (0, "invited via your group link"), (0, "connected")]) + alice #$> ("/_get chat #1 count=100", chat, sndGroupFeatures <> [(1, "Recent history: off"), (0, "invited via your group link"), (0, "connected")]) alice @@@ [("#team", "connected")] bob @@@ [("#team", "connected"), ("@cath", "hey")] @@ -3144,7 +3147,7 @@ testGroupLinkNoContactAllMembersWereConnected = ] threadDelay 100000 - alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (1, "Recent history: off"), (0, "invited via your group link"), (0, "connected")]) + alice #$> ("/_get chat #1 count=100", chat, sndGroupFeatures <> [(1, "Recent history: off"), (0, "invited via your group link"), (0, "connected")]) alice @@@ [("#team", "connected"), ("@bob", "hey"), ("@cath", "hey")] bob @@@ [("#team", "connected"), ("@alice", "hey"), ("@cath", "hey")] @@ -3300,7 +3303,7 @@ testGroupLinkNoContactHostIncognito = ] threadDelay 100000 - alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "invited via your group link"), (0, "connected")]) + alice #$> ("/_get chat #1 count=100", chat, sndGroupFeatures <> [(0, "invited via your group link"), (0, "connected")]) alice @@@ [("#team", "connected")] bob @@@ [("#team", "connected")] @@ -3334,7 +3337,7 @@ testGroupLinkNoContactInviteeIncognito = ] threadDelay 100000 - alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "invited via your group link"), (0, "connected")]) + alice #$> ("/_get chat #1 count=100", chat, sndGroupFeatures <> [(0, "invited via your group link"), (0, "connected")]) alice @@@ [("#team", "connected")] bob @@@ [("#team", "connected")] @@ -3401,7 +3404,7 @@ testGroupLinkNoContactExistingContactMerged = ] threadDelay 100000 - alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "invited via your group link"), (0, "connected")]) + alice #$> ("/_get chat #1 count=100", chat, sndGroupFeatures <> [(0, "invited via your group link"), (0, "connected")]) alice <##> bob diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index 3ffe57ba2e..c5a464d969 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -686,16 +686,26 @@ testBusinessAddress = testChat3 businessProfile aliceProfile {fullName = "Alice cLink <- getContactLink biz True biz ##> "/auto_accept on business" biz <## "auto_accept on, business" + bob ##> ("/_connect plan 1 " <> cLink) + bob <## "contact address: ok to connect" bob ##> ("/c " <> cLink) bob <## "connection request sent!" + bob ##> ("/_connect plan 1 " <> cLink) + bob <## "contact address: connecting, allowed to reconnect" biz <## "#bob (Bob): accepting business address request..." - biz <## "#bob: bob_1 joined the group" bob <## "#biz: joining the group..." + -- the next command can be prone to race conditions + bob ##> ("/_connect plan 1 " <> cLink) + bob <## "business link: connecting to business #biz" + biz <## "#bob: bob_1 joined the group" bob <## "#biz: you joined the group" biz #> "#bob hi" bob <# "#biz biz_1> hi" bob #> "#biz hello" biz <# "#bob bob_1> hello" + bob ##> ("/_connect plan 1 " <> cLink) + bob <## "business link: known business #biz" + bob <## "use #biz to send messages" connectUsers biz alice biz <##> alice biz ##> "/a #bob alice" @@ -1948,13 +1958,13 @@ testUpdateGroupPrefs = testChat2 aliceProfile bobProfile $ \alice bob -> do createGroup2 "team" alice bob - alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected")]) + alice #$> ("/_get chat #1 count=100", chat, sndGroupFeatures <> [(0, "connected")]) threadDelay 500000 bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected")]) alice ##> "/_group_profile #1 {\"displayName\": \"team\", \"fullName\": \"\", \"groupPreferences\": {\"fullDelete\": {\"enable\": \"on\"}, \"directMessages\": {\"enable\": \"on\"}, \"history\": {\"enable\": \"on\"}}}" alice <## "updated group preferences:" alice <## "Full deletion: on" - alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (1, "Full deletion: on")]) + alice #$> ("/_get chat #1 count=100", chat, sndGroupFeatures <> [(0, "connected"), (1, "Full deletion: on")]) bob <## "alice updated group #team:" bob <## "updated group preferences:" bob <## "Full deletion: on" @@ -1964,7 +1974,7 @@ testUpdateGroupPrefs = alice <## "updated group preferences:" alice <## "Full deletion: off" alice <## "Voice messages: off" - alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (1, "Full deletion: on"), (1, "Full deletion: off"), (1, "Voice messages: off")]) + alice #$> ("/_get chat #1 count=100", chat, sndGroupFeatures <> [(0, "connected"), (1, "Full deletion: on"), (1, "Full deletion: off"), (1, "Voice messages: off")]) bob <## "alice updated group #team:" bob <## "updated group preferences:" bob <## "Full deletion: off" @@ -1974,7 +1984,7 @@ testUpdateGroupPrefs = alice ##> "/set voice #team on" alice <## "updated group preferences:" alice <## "Voice messages: on" - alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (1, "Full deletion: on"), (1, "Full deletion: off"), (1, "Voice messages: off"), (1, "Voice messages: on")]) + alice #$> ("/_get chat #1 count=100", chat, sndGroupFeatures <> [(0, "connected"), (1, "Full deletion: on"), (1, "Full deletion: off"), (1, "Voice messages: off"), (1, "Voice messages: on")]) bob <## "alice updated group #team:" bob <## "updated group preferences:" bob <## "Voice messages: on" @@ -1984,14 +1994,14 @@ testUpdateGroupPrefs = alice ##> "/_group_profile #1 {\"displayName\": \"team\", \"fullName\": \"\", \"groupPreferences\": {\"fullDelete\": {\"enable\": \"off\"}, \"voice\": {\"enable\": \"on\"}, \"directMessages\": {\"enable\": \"on\"}, \"history\": {\"enable\": \"on\"}}}" -- no update threadDelay 500000 - alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (1, "Full deletion: on"), (1, "Full deletion: off"), (1, "Voice messages: off"), (1, "Voice messages: on")]) + alice #$> ("/_get chat #1 count=100", chat, sndGroupFeatures <> [(0, "connected"), (1, "Full deletion: on"), (1, "Full deletion: off"), (1, "Voice messages: off"), (1, "Voice messages: on")]) alice #> "#team hey" bob <# "#team alice> hey" threadDelay 1000000 bob #> "#team hi" alice <# "#team bob> hi" threadDelay 500000 - alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (1, "Full deletion: on"), (1, "Full deletion: off"), (1, "Voice messages: off"), (1, "Voice messages: on"), (1, "hey"), (0, "hi")]) + alice #$> ("/_get chat #1 count=100", chat, sndGroupFeatures <> [(0, "connected"), (1, "Full deletion: on"), (1, "Full deletion: off"), (1, "Voice messages: off"), (1, "Voice messages: on"), (1, "hey"), (0, "hi")]) bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "Full deletion: on"), (0, "Full deletion: off"), (0, "Voice messages: off"), (0, "Voice messages: on"), (0, "hey"), (1, "hi")]) testAllowFullDeletionContact :: HasCallStack => FilePath -> IO () @@ -2031,11 +2041,11 @@ testAllowFullDeletionGroup = bob <## "alice updated group #team:" bob <## "updated group preferences:" bob <## "Full deletion: on" - alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (1, "hi"), (0, "hey"), (1, "Full deletion: on")]) + alice #$> ("/_get chat #1 count=100", chat, sndGroupFeatures <> [(0, "connected"), (1, "hi"), (0, "hey"), (1, "Full deletion: on")]) bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "hi"), (1, "hey"), (0, "Full deletion: on")]) bob #$> ("/_delete item #1 " <> msgItemId <> " broadcast", id, "message deleted") alice <# "#team bob> [deleted] hey" - alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (1, "hi"), (1, "Full deletion: on")]) + alice #$> ("/_get chat #1 count=100", chat, sndGroupFeatures <> [(0, "connected"), (1, "hi"), (1, "Full deletion: on")]) bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "hi"), (0, "Full deletion: on")]) testProhibitDirectMessages :: HasCallStack => FilePath -> IO () @@ -2157,12 +2167,12 @@ testEnableTimedMessagesGroup = alice #> "#team hi" bob <# "#team alice> hi" threadDelay 500000 - alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (1, "Disappearing messages: on (1 sec)"), (1, "hi")]) + alice #$> ("/_get chat #1 count=100", chat, sndGroupFeatures <> [(0, "connected"), (1, "Disappearing messages: on (1 sec)"), (1, "hi")]) bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "Disappearing messages: on (1 sec)"), (0, "hi")]) threadDelay 1000000 alice <## "timed message deleted: hi" bob <## "timed message deleted: hi" - alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (1, "Disappearing messages: on (1 sec)")]) + alice #$> ("/_get chat #1 count=100", chat, sndGroupFeatures <> [(0, "connected"), (1, "Disappearing messages: on (1 sec)")]) bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "Disappearing messages: on (1 sec)")]) -- turn off, messages are not disappearing alice ##> "/set disappear #team off" @@ -2175,7 +2185,7 @@ testEnableTimedMessagesGroup = alice #> "#team hey" bob <# "#team alice> hey" threadDelay 1500000 - alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (1, "Disappearing messages: on (1 sec)"), (1, "Disappearing messages: off"), (1, "hey")]) + alice #$> ("/_get chat #1 count=100", chat, sndGroupFeatures <> [(0, "connected"), (1, "Disappearing messages: on (1 sec)"), (1, "Disappearing messages: off"), (1, "hey")]) bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "Disappearing messages: on (1 sec)"), (0, "Disappearing messages: off"), (0, "hey")]) -- test api alice ##> "/set disappear #team on 30s" diff --git a/tests/ChatTests/Utils.hs b/tests/ChatTests/Utils.hs index 6459522134..ce820b12a7 100644 --- a/tests/ChatTests/Utils.hs +++ b/tests/ChatTests/Utils.hs @@ -281,19 +281,25 @@ lastChatFeature :: String lastChatFeature = snd $ last chatFeatures groupFeatures :: [(Int, String)] -groupFeatures = map (\(a, _, _) -> a) groupFeatures'' +groupFeatures = map (\(a, _, _) -> a) $ groupFeatures'' 0 -groupFeatures'' :: [((Int, String), Maybe (Int, String), Maybe String)] -groupFeatures'' = - [ ((0, e2eeInfoNoPQStr), Nothing, Nothing), - ((0, "Disappearing messages: off"), Nothing, Nothing), - ((0, "Direct messages: on"), Nothing, Nothing), - ((0, "Full deletion: off"), Nothing, Nothing), - ((0, "Message reactions: on"), Nothing, Nothing), - ((0, "Voice messages: on"), Nothing, Nothing), - ((0, "Files and media: on"), Nothing, Nothing), - ((0, "SimpleX links: on"), Nothing, Nothing), - ((0, "Recent history: on"), Nothing, Nothing) +sndGroupFeatures :: [(Int, String)] +sndGroupFeatures = map (\(a, _, _) -> a) $ groupFeatures'' 1 + +groupFeatureStrs :: [String] +groupFeatureStrs = map (\(a, _, _) -> snd a) $ groupFeatures'' 0 + +groupFeatures'' :: Int -> [((Int, String), Maybe (Int, String), Maybe String)] +groupFeatures'' dir = + [ ((dir, e2eeInfoNoPQStr), Nothing, Nothing), + ((dir, "Disappearing messages: off"), Nothing, Nothing), + ((dir, "Direct messages: on"), Nothing, Nothing), + ((dir, "Full deletion: off"), Nothing, Nothing), + ((dir, "Message reactions: on"), Nothing, Nothing), + ((dir, "Voice messages: on"), Nothing, Nothing), + ((dir, "Files and media: on"), Nothing, Nothing), + ((dir, "SimpleX links: on"), Nothing, Nothing), + ((dir, "Recent history: on"), Nothing, Nothing) ] itemId :: Int -> String From 85e7a13dba593a8a3ca4680ecfef97d31c1c5c6a Mon Sep 17 00:00:00 2001 From: Diogo Date: Tue, 3 Dec 2024 12:26:50 +0000 Subject: [PATCH 159/567] desktop: show group message reaction on right click (#5304) * desktop: show group message reaction on right click * open on ctrl + click too --- .../common/views/chat/item/ChatItemView.kt | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index 82744fdc39..bf871ab626 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -121,9 +121,33 @@ fun ChatItemView( cItem.reactions.forEach { r -> val showReactionMenu = remember { mutableStateOf(false) } val reactionMembers = remember { mutableStateOf(emptyList()) } + val interactionSource = remember { MutableInteractionSource() } + val enterInteraction = remember { HoverInteraction.Enter() } + KeyChangeEffect(highlighted.value) { + if (highlighted.value) { + interactionSource.emit(enterInteraction) + } else { + interactionSource.emit(HoverInteraction.Exit(enterInteraction)) + } + } var modifier = Modifier.padding(horizontal = 5.dp, vertical = 2.dp).clip(RoundedCornerShape(8.dp)) if (cInfo.featureEnabled(ChatFeature.Reactions)) { + fun showReactionsMenu() { + if (cInfo is ChatInfo.Group) { + withBGApi { + try { + val members = controller.apiGetReactionMembers(rhId, cInfo.groupInfo.groupId, cItem.id, r.reaction) + if (members != null) { + showReactionMenu.value = true + reactionMembers.value = members + } + } catch (e: Exception) { + Log.d(TAG, "hatItemView ChatItemReactions onLongClick: unexpected exception: ${e.stackTraceToString()}") + } + } + } + } modifier = modifier .combinedClickable( onClick = { @@ -132,21 +156,12 @@ fun ChatItemView( } }, onLongClick = { - if (cInfo is ChatInfo.Group) { - withBGApi { - try { - val members = controller.apiGetReactionMembers(rhId, cInfo.groupInfo.groupId, cItem.id, r.reaction) - if (members != null) { - showReactionMenu.value = true - reactionMembers.value = members - } - } catch (e: Exception) { - Log.d(TAG, "hatItemView ChatItemReactions onLongClick: unexpected exception: ${e.stackTraceToString()}") - } - } - } - } + showReactionsMenu() + }, + interactionSource = interactionSource, + indication = LocalIndication.current ) + .onRightClick { showReactionsMenu() } } Row(modifier.padding(2.dp), verticalAlignment = Alignment.CenterVertically) { ReactionIcon(r.reaction.text, fontSize = 12.sp) From 43fa4c43a29672d949361894caa9e8ef38b0d397 Mon Sep 17 00:00:00 2001 From: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com> Date: Tue, 3 Dec 2024 12:48:54 +0000 Subject: [PATCH 160/567] docs: update FAQ (#5179) * Update FAQ.md Added: - Why invite links use simplex.chat domain? - I do not know my database passphrase * Update FAQ.md * Add flatpak directory * corrections * correction * invitation --------- Co-authored-by: Evgeny --- docs/FAQ.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/FAQ.md b/docs/FAQ.md index 932a4c33ee..0d0426d7c9 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -18,6 +18,7 @@ revision: 23.04.2024 - [I want to see when my contacts read my messages](#i-want-to-see-when-my-contacts-read-my-messages) - [Can I use the same profile on desktop? Do messages sync cross-platform?](#can-i-use-the-same-profile-on-desktop-do-messages-sync-cross-platform) - [Why cannot I delete messages I sent from my contact's device?](#why-cannot-i-delete-messages-i-sent-from-my-contacts-device) +- [Why invitation links use simplex.chat domain?](#why-invitation-links-use-simplex.chat-domain) [Troubleshooting](#troubleshooting) - [I do not receive messages or message notifications](#i-do-not-receive-messages-or-message-notifications) @@ -27,6 +28,7 @@ revision: 23.04.2024 - [Audio or video calls do not connect](#audio-or-video-calls-do-not-connect) - [Audio or video calls without e2e encryption](#audio-or-video-calls-without-e2e-encryption) - [I clicked the link to connect, but could not connect](#i-clicked-the-link-to-connect-but-could-not-connect) +- [I do not know my database passphrase](#i-do-not-know-my-database-passphrase) [Privacy and security](#privacy-and-security) - [Does SimpleX support post quantum cryptography?](#does-simplex-support-post-quantum-cryptography) @@ -120,6 +122,14 @@ It is also important to remember, that even if your contact enabled "Delete for When "Delete for everyone" is not enabled, you can still mark the sent message as deleted within 24 hours of sending it. In this case the recipient will see it as "deleted message", and will be able to reveal the original message. +### Why invitation links use simplex.chat domain? + +You can replace `https://simplex.chat/` with `simplex:/` or with any other domain - the app never connect with it, ignoring it completely. It is only used to make it easier to connect for the new users who did not install the app yet. + +The invitation links will soon move to servers' domains. The servers already can host the pages that will be used to show QR codes. + +The link itself and the key exchange are not hosted anywhere, and the server that hosts the page to show QR code does not observe the actual connection link, because it is in the hash part of the link. The part after hash character (`#`) is not sent over the internet - the server can only see `https://simplex.chat/contact/` and the rest is processed on user's device in the browser, if you open it as a page. + ## Troubleshooting ### I do not receive messages or message notifications @@ -226,6 +236,20 @@ For connection to complete, your contact has to be online and have the app runni Once the connection is established you don't need to be online at the same time to send messages. +### I do not know my database passphrase + +If you are prompted to enter database passphrase and you do not know it, this could have happened due to: +- You may have forgotten the passphrase. (There is no other way to access your data). +- Migration of app data from one device to another while using unsupported migration process, e.g. via iCloud backup. Use SimpleX Chat's own migration process in the app Settings. + +In the previous desktop app versions it could also happen in case of error during SimpleX Chat installation. + +You can resolve it by deleting the app's database: (WARNING: this results in deletion of all profiles, contacts and messages) +- on Android/iOS, uninstall the app and install it again. +- on Windows, delete folder `C:\AppData\Roaming\SimpleX`, you can find it by pressing Windows key + R and entering `%appdata%`. +- on Linux/Mac, delete directories `~/.local/share/simplex` and `~/.config/simplex`, where `~` represents your home directory (/home/user) +- on Flatpak, delete directory `~/.var/app/chat.simplex.simplex`. + ## Privacy and security ### Does SimpleX support post quantum cryptography? From a0b8cf62be27a9b701f65279a1d772ce544130b8 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:27:00 +0400 Subject: [PATCH 161/567] android, desktop: support business addresses and chats (#5302) --- .../chat/simplex/common/model/ChatModel.kt | 15 ++++- .../chat/simplex/common/model/SimpleXAPI.kt | 20 ++++++- .../views/chat/group/GroupChatInfoView.kt | 33 +++++++---- .../views/chat/group/GroupPreferences.kt | 14 ++--- .../views/chat/group/WelcomeMessageView.kt | 5 +- .../common/views/helpers/ChatInfoImage.kt | 8 ++- .../common/views/newchat/ConnectPlan.kt | 33 ++++++++--- .../common/views/usersettings/Preferences.kt | 4 +- .../common/views/usersettings/SettingsView.kt | 2 + .../views/usersettings/UserAddressView.kt | 55 +++++++++++++++---- .../commonMain/resources/MR/base/strings.xml | 6 ++ .../resources/MR/images/ic_work.svg | 1 + .../MR/images/ic_work_filled_padded.svg | 8 +++ 13 files changed, 155 insertions(+), 49 deletions(-) create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_work.svg create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_work_filled_padded.svg diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 857d21b966..4d75d37b99 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -1467,6 +1467,7 @@ data class GroupInfo ( val groupId: Long, override val localDisplayName: String, val groupProfile: GroupProfile, + val businessChat: BusinessChatInfo? = null, val fullGroupPreferences: FullGroupPreferences, val membership: GroupMember, val hostConnCustomUserProfileId: Long? = null, @@ -1497,7 +1498,7 @@ data class GroupInfo ( override val image get() = groupProfile.image override val localAlias get() = "" - val canEdit: Boolean + val isOwner: Boolean get() = membership.memberRole == GroupMemberRole.Owner && membership.memberCurrent val canDelete: Boolean @@ -1543,6 +1544,18 @@ data class GroupProfile ( } } +@Serializable +data class BusinessChatInfo ( + val memberId: String, + val chatType: BusinessChatType +) + +@Serializable +enum class BusinessChatType { + @SerialName("business") Business, + @SerialName("customer") Customer, +} + @Serializable data class GroupMember ( val groupMemberId: Long, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index d9c7df9d81..f0f63f2c72 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -2526,6 +2526,14 @@ object ChatController { } } } + is CR.BusinessLinkConnecting -> { + if (!active(r.user)) return + + withChats { + updateGroup(rhId, r.groupInfo) + removeChat(rhId, r.fromContact.id) + } + } is CR.JoinedGroupMemberConnecting -> if (active(r.user)) { withChats { @@ -5484,6 +5492,7 @@ sealed class CR { @Serializable @SerialName("sentGroupInvitation") class SentGroupInvitation(val user: UserRef, val groupInfo: GroupInfo, val contact: Contact, val member: GroupMember): CR() @Serializable @SerialName("userAcceptedGroupSent") class UserAcceptedGroupSent (val user: UserRef, val groupInfo: GroupInfo, val hostContact: Contact? = null): CR() @Serializable @SerialName("groupLinkConnecting") class GroupLinkConnecting (val user: UserRef, val groupInfo: GroupInfo, val hostMember: GroupMember): CR() + @Serializable @SerialName("businessLinkConnecting") class BusinessLinkConnecting (val user: UserRef, val groupInfo: GroupInfo, val hostMember: GroupMember, val fromContact: Contact): CR() @Serializable @SerialName("userDeletedMember") class UserDeletedMember(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember): CR() @Serializable @SerialName("leftMemberUser") class LeftMemberUser(val user: UserRef, val groupInfo: GroupInfo): CR() @Serializable @SerialName("groupMembers") class GroupMembers(val user: UserRef, val group: Group): CR() @@ -5664,6 +5673,7 @@ sealed class CR { is SentGroupInvitation -> "sentGroupInvitation" is UserAcceptedGroupSent -> "userAcceptedGroupSent" is GroupLinkConnecting -> "groupLinkConnecting" + is BusinessLinkConnecting -> "businessLinkConnecting" is UserDeletedMember -> "userDeletedMember" is LeftMemberUser -> "leftMemberUser" is GroupMembers -> "groupMembers" @@ -5837,6 +5847,7 @@ sealed class CR { is SentGroupInvitation -> withUser(user, "groupInfo: $groupInfo\ncontact: $contact\nmember: $member") is UserAcceptedGroupSent -> json.encodeToString(groupInfo) is GroupLinkConnecting -> withUser(user, "groupInfo: $groupInfo\nhostMember: $hostMember") + is BusinessLinkConnecting -> withUser(user, "groupInfo: $groupInfo\nhostMember: $hostMember\nfromContact: $fromContact") is UserDeletedMember -> withUser(user, "groupInfo: $groupInfo\nmember: $member") is LeftMemberUser -> withUser(user, json.encodeToString(groupInfo)) is GroupMembers -> withUser(user, json.encodeToString(group)) @@ -6108,11 +6119,16 @@ class UserContactLinkRec(val connReqContact: String, val autoAccept: AutoAccept? } @Serializable -class AutoAccept(val acceptIncognito: Boolean, val autoReply: MsgContent?) { +class AutoAccept(val businessAddress: Boolean, val acceptIncognito: Boolean, val autoReply: MsgContent?) { companion object { fun cmdString(autoAccept: AutoAccept?): String { if (autoAccept == null) return "off" - val s = "on" + if (autoAccept.acceptIncognito) " incognito=on" else "" + var s = "on" + if (autoAccept.acceptIncognito) { + s += " incognito=on" + } else if (autoAccept.businessAddress) { + s += " business" + } val msg = autoAccept.autoReply ?: return s return s + " " + msg.cmdString } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index 76f2866950..870df388b2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -36,6 +36,7 @@ import chat.simplex.common.views.chat.* import chat.simplex.common.views.chat.item.ItemAction import chat.simplex.common.views.chatlist.* import chat.simplex.res.MR +import dev.icerock.moko.resources.StringResource import kotlinx.coroutines.launch const val SMALL_GROUPS_RCPS_MEM_LIMIT: Int = 20 @@ -328,13 +329,14 @@ fun ModalData.GroupChatInfoLayout( SectionSpacer() SectionView { - if (groupInfo.canEdit) { + if (groupInfo.isOwner && groupInfo.businessChat?.chatType == null) { EditGroupProfileButton(editGroupProfile) } - if (groupInfo.groupProfile.description != null || groupInfo.canEdit) { + if (groupInfo.groupProfile.description != null || (groupInfo.isOwner && groupInfo.businessChat?.chatType == null)) { AddOrEditWelcomeMessage(groupInfo.groupProfile.description, addOrEditWelcomeMessage) } - GroupPreferencesButton(openPreferences) + val prefsTitleId = if (groupInfo.businessChat == null) MR.strings.group_preferences else MR.strings.chat_preferences + GroupPreferencesButton(prefsTitleId, openPreferences) if (members.filter { it.memberCurrent }.size <= SMALL_GROUPS_RCPS_MEM_LIMIT) { SendReceiptsOption(currentUser, sendReceipts, setSendReceipts) } else { @@ -356,14 +358,21 @@ fun ModalData.GroupChatInfoLayout( SectionView(title = String.format(generalGetString(MR.strings.group_info_section_title_num_members), members.count() + 1)) { if (groupInfo.canAddMembers) { - if (groupLink == null) { - CreateGroupLinkButton(manageGroupLink) - } else { - GroupLinkButton(manageGroupLink) + if (groupInfo.businessChat == null) { + if (groupLink == null) { + CreateGroupLinkButton(manageGroupLink) + } else { + GroupLinkButton(manageGroupLink) + } } val onAddMembersClick = if (chat.chatInfo.incognito) ::cantInviteIncognitoAlert else addMembers val tint = if (chat.chatInfo.incognito) MaterialTheme.colors.secondary else MaterialTheme.colors.primary - AddMembersButton(tint, onAddMembersClick) + val addMembersTitleId = when (groupInfo.businessChat?.chatType) { + BusinessChatType.Customer -> MR.strings.button_add_team_members + BusinessChatType.Business -> MR.strings.button_add_friends + null -> MR.strings.button_add_members + } + AddMembersButton(addMembersTitleId, tint, onAddMembersClick) } if (members.size > 8) { SectionItemView(padding = PaddingValues(start = 14.dp, end = DEFAULT_PADDING_HALF)) { @@ -439,10 +448,10 @@ private fun GroupChatInfoHeader(cInfo: ChatInfo) { } @Composable -private fun GroupPreferencesButton(onClick: () -> Unit) { +private fun GroupPreferencesButton(titleId: StringResource, onClick: () -> Unit) { SettingsActionItem( painterResource(MR.images.ic_toggle_on), - stringResource(MR.strings.group_preferences), + stringResource(titleId), click = onClick ) } @@ -479,10 +488,10 @@ fun SendReceiptsOptionDisabled() { } @Composable -private fun AddMembersButton(tint: Color = MaterialTheme.colors.primary, onClick: () -> Unit) { +private fun AddMembersButton(titleId: StringResource, tint: Color = MaterialTheme.colors.primary, onClick: () -> Unit) { SettingsActionItem( painterResource(MR.images.ic_add), - stringResource(MR.strings.button_add_members), + stringResource(titleId), onClick, iconColor = tint, textColor = tint diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt index 128dfe2d97..0a807e1d63 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt @@ -6,12 +6,10 @@ import SectionDividerSpaced import SectionItemView import SectionTextFooter import SectionView -import androidx.compose.foundation.layout.* import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.stringResource import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* @@ -20,7 +18,6 @@ import chat.simplex.common.model.* import chat.simplex.common.model.ChatModel.withChats import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.res.MR -import dev.icerock.moko.resources.compose.painterResource private val featureRoles: List> = listOf( null to generalGetString(MR.strings.feature_roles_all_members), @@ -83,7 +80,8 @@ private fun GroupPreferencesLayout( savePrefs: () -> Unit, ) { ColumnWithScrollBar { - AppBarTitle(stringResource(MR.strings.group_preferences)) + val titleId = if (groupInfo.businessChat == null) MR.strings.group_preferences else MR.strings.chat_preferences + AppBarTitle(stringResource(titleId)) val timedMessages = remember(preferences) { mutableStateOf(preferences.timedMessages.enable) } val onTTLUpdated = { ttl: Int? -> applyPrefs(preferences.copy(timedMessages = preferences.timedMessages.copy(ttl = ttl))) @@ -136,7 +134,7 @@ private fun GroupPreferencesLayout( FeatureSection(GroupFeature.History, enableHistory, null, groupInfo, preferences, onTTLUpdated) { enable, _ -> applyPrefs(preferences.copy(history = GroupPreference(enable = enable))) } - if (groupInfo.canEdit) { + if (groupInfo.isOwner) { SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false) ResetSaveButtons( reset = reset, @@ -163,12 +161,12 @@ private fun FeatureSection( val icon = if (on) feature.iconFilled() else feature.icon val iconTint = if (on) SimplexGreen else MaterialTheme.colors.secondary val timedOn = feature == GroupFeature.TimedMessages && enableFeature.value == GroupFeatureEnabled.ON - if (groupInfo.canEdit) { + if (groupInfo.isOwner) { PreferenceToggleWithIcon( feature.text, icon, iconTint, - enableFeature.value == GroupFeatureEnabled.ON, + checked = enableFeature.value == GroupFeatureEnabled.ON, ) { checked -> onSelected(if (checked) GroupFeatureEnabled.ON else GroupFeatureEnabled.OFF, enableForRole?.value) } @@ -214,7 +212,7 @@ private fun FeatureSection( onSelected(enableFeature.value, null) } } - SectionTextFooter(feature.enableDescription(enableFeature.value, groupInfo.canEdit)) + SectionTextFooter(feature.enableDescription(enableFeature.value, groupInfo.isOwner)) } @Composable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt index 7f0af360e7..6ebd4b13c3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt @@ -7,9 +7,7 @@ import SectionTextFooter import SectionView import TextIconSpaced import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.selection.SelectionContainer -import androidx.compose.foundation.verticalScroll import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable @@ -32,7 +30,6 @@ import chat.simplex.common.model.ChatModel.withChats import chat.simplex.common.model.GroupInfo import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.common.platform.chatJsonLength -import chat.simplex.common.ui.theme.DEFAULT_PADDING_HALF import chat.simplex.res.MR import kotlinx.coroutines.delay @@ -99,7 +96,7 @@ private fun GroupWelcomeLayout( val editMode = remember { mutableStateOf(true) } AppBarTitle(stringResource(MR.strings.group_welcome_title)) val wt = rememberSaveable { welcomeText } - if (groupInfo.canEdit) { + if (groupInfo.isOwner && groupInfo.businessChat?.chatType == null) { if (editMode.value) { val focusRequester = remember { FocusRequester() } TextEditor( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatInfoImage.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatInfoImage.kt index d338c57e61..c3e97dd27b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatInfoImage.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatInfoImage.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.graphics.* import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.InspectableValue import androidx.compose.ui.unit.* +import chat.simplex.common.model.BusinessChatType import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import chat.simplex.common.model.ChatInfo @@ -30,7 +31,12 @@ import kotlin.math.max fun ChatInfoImage(chatInfo: ChatInfo, size: Dp, iconColor: Color = MaterialTheme.colors.secondaryVariant, shadow: Boolean = false) { val icon = when (chatInfo) { - is ChatInfo.Group -> MR.images.ic_supervised_user_circle_filled + is ChatInfo.Group -> + when (chatInfo.groupInfo.businessChat?.chatType) { + BusinessChatType.Business -> MR.images.ic_work_filled_padded + BusinessChatType.Customer -> MR.images.ic_account_circle_filled + null -> MR.images.ic_supervised_user_circle_filled + } is ChatInfo.Local -> MR.images.ic_folder_filled else -> MR.images.ic_account_circle_filled } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt index 7cd272c109..e8190e0767 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt @@ -275,10 +275,17 @@ suspend fun planAndConnect( Log.d(TAG, "planAndConnect, .GroupLink, .ConnectingProhibit, incognito=$incognito") val groupInfo = connectionPlan.groupLinkPlan.groupInfo_ if (groupInfo != null) { - AlertManager.privacySensitive.showAlertMsg( - generalGetString(MR.strings.connect_plan_group_already_exists), - String.format(generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_vName), groupInfo.displayName) + linkText - ) + if (groupInfo.businessChat == null) { + AlertManager.privacySensitive.showAlertMsg( + generalGetString(MR.strings.connect_plan_group_already_exists), + String.format(generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_vName), groupInfo.displayName) + linkText + ) + } else { + AlertManager.privacySensitive.showAlertMsg( + generalGetString(MR.strings.connect_plan_chat_already_exists), + String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), groupInfo.displayName) + linkText + ) + } } else { AlertManager.privacySensitive.showAlertMsg( generalGetString(MR.strings.connect_plan_already_joining_the_group), @@ -295,11 +302,19 @@ suspend fun planAndConnect( filterKnownGroup(groupInfo) } else { openKnownGroup(chatModel, rhId, close, groupInfo) - AlertManager.privacySensitive.showAlertMsg( - generalGetString(MR.strings.connect_plan_group_already_exists), - String.format(generalGetString(MR.strings.connect_plan_you_are_already_in_group_vName), groupInfo.displayName) + linkText, - hostDevice = hostDevice(rhId), - ) + if (groupInfo.businessChat == null) { + AlertManager.privacySensitive.showAlertMsg( + generalGetString(MR.strings.connect_plan_group_already_exists), + String.format(generalGetString(MR.strings.connect_plan_you_are_already_in_group_vName), groupInfo.displayName) + linkText, + hostDevice = hostDevice(rhId), + ) + } else { + AlertManager.privacySensitive.showAlertMsg( + generalGetString(MR.strings.connect_plan_chat_already_exists), + String.format(generalGetString(MR.strings.connect_plan_you_are_already_connected_with_vName), groupInfo.displayName) + linkText, + hostDevice = hostDevice(rhId), + ) + } cleanup?.invoke() } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt index bc27773ca6..5132516669 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt @@ -123,9 +123,9 @@ private fun TimedMessagesFeatureSection(allowFeature: State, onS ChatFeature.TimedMessages.text, ChatFeature.TimedMessages.icon, MaterialTheme.colors.secondary, - allowFeature.value == FeatureAllowed.ALWAYS || allowFeature.value == FeatureAllowed.YES, + checked = allowFeature.value == FeatureAllowed.ALWAYS || allowFeature.value == FeatureAllowed.YES, extraPadding = false, - onSelected + onChange = onSelected ) } SectionTextFooter(ChatFeature.TimedMessages.allowDescription(allowFeature.value)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt index ac6431f6fc..51a0ffad8d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt @@ -408,6 +408,7 @@ fun PreferenceToggleWithIcon( text: String, icon: Painter? = null, iconColor: Color? = MaterialTheme.colors.secondary, + disabled: Boolean = false, checked: Boolean, extraPadding: Boolean = false, onChange: (Boolean) -> Unit = {}, @@ -418,6 +419,7 @@ fun PreferenceToggleWithIcon( onCheckedChange = { onChange(it) }, + enabled = !disabled ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt index 836faee49b..7bc35bc0de 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt @@ -196,14 +196,22 @@ private fun UserAddressLayout( LearnMoreButton(learnMore) } } else { + val autoAcceptState = remember { mutableStateOf(AutoAcceptState(userAddress)) } + val autoAcceptStateSaved = remember { mutableStateOf(autoAcceptState.value) } + SectionView(stringResource(MR.strings.for_social_media).uppercase()) { SimpleXLinkQRCode(userAddress.connReqContact) ShareAddressButton { share(simplexChatLink(userAddress.connReqContact)) } // ShareViaEmailButton { sendEmail(userAddress) } + BusinessAddressToggle(autoAcceptState) { saveAas(autoAcceptState.value, autoAcceptStateSaved) } AddressSettingsButton(user, userAddress, shareViaProfile, setProfileAddress, saveAas) + + if (autoAcceptState.value.business) { + SectionTextFooter(stringResource(MR.strings.add_your_team_members_to_conversations)) + } } - SectionDividerSpaced() + SectionDividerSpaced(maxTopPadding = autoAcceptState.value.business) SectionView(generalGetString(MR.strings.or_to_share_privately).uppercase()) { CreateOneTimeLinkButton() } @@ -385,17 +393,37 @@ fun ShareWithContactsButton(shareViaProfile: MutableState, setProfileAd onDismissRequest = { shareViaProfile.value = !on }) + } } +} + +@Composable +private fun BusinessAddressToggle(autoAcceptState: MutableState, saveAas: (AutoAcceptState) -> Unit) { + PreferenceToggleWithIcon( + stringResource(MR.strings.business_address), + painterResource(MR.images.ic_work), + checked = autoAcceptState.value.business, + ) { ba -> + autoAcceptState.value = if (ba) + AutoAcceptState(enable = true, incognito = false, business = true, autoAcceptState.value.welcomeText) + else + AutoAcceptState(autoAcceptState.value.enable, autoAcceptState.value.incognito, business = false, autoAcceptState.value.welcomeText) + saveAas(autoAcceptState.value) } } @Composable private fun AutoAcceptToggle(autoAcceptState: MutableState, saveAas: (AutoAcceptState) -> Unit) { - PreferenceToggleWithIcon(stringResource(MR.strings.auto_accept_contact), painterResource(MR.images.ic_check), checked = autoAcceptState.value.enable) { + PreferenceToggleWithIcon( + stringResource(MR.strings.auto_accept_contact), + painterResource(MR.images.ic_check), + disabled = autoAcceptState.value.business, + checked = autoAcceptState.value.enable + ) { autoAcceptState.value = if (!it) AutoAcceptState() else - AutoAcceptState(it, autoAcceptState.value.incognito, autoAcceptState.value.welcomeText) + AutoAcceptState(it, autoAcceptState.value.incognito, autoAcceptState.value.business, autoAcceptState.value.welcomeText) saveAas(autoAcceptState.value) } } @@ -416,12 +444,15 @@ private class AutoAcceptState { private set var incognito: Boolean = false private set + var business: Boolean = false + private set var welcomeText: String = "" private set - constructor(enable: Boolean = false, incognito: Boolean = false, welcomeText: String = "") { + constructor(enable: Boolean = false, incognito: Boolean = false, business: Boolean = false, welcomeText: String = "") { this.enable = enable this.incognito = incognito + this.business = business this.welcomeText = welcomeText } @@ -429,6 +460,7 @@ private class AutoAcceptState { contactLink.autoAccept?.let { aa -> enable = true incognito = aa.acceptIncognito + business = aa.businessAddress aa.autoReply?.let { msg -> welcomeText = msg.text } ?: run { @@ -445,19 +477,20 @@ private class AutoAcceptState { if (s != "") { autoReply = MsgContent.MCText(s) } - return AutoAccept(incognito, autoReply) + return AutoAccept(business, incognito, autoReply) } return null } override fun equals(other: Any?): Boolean { if (other !is AutoAcceptState) return false - return this.enable == other.enable && this.incognito == other.incognito && this.welcomeText == other.welcomeText + return this.enable == other.enable && this.incognito == other.incognito && this.business == other.business && this.welcomeText == other.welcomeText } override fun hashCode(): Int { var result = enable.hashCode() result = 31 * result + incognito.hashCode() + result = 31 * result + business.hashCode() result = 31 * result + welcomeText.hashCode() return result } @@ -470,7 +503,9 @@ private fun AutoAcceptSection( saveAas: (AutoAcceptState, MutableState) -> Unit ) { SectionView(stringResource(MR.strings.auto_accept_contact).uppercase()) { - AcceptIncognitoToggle(autoAcceptState) + if (!autoAcceptState.value.business) { + AcceptIncognitoToggle(autoAcceptState) + } WelcomeMessageEditor(autoAcceptState) SaveAASButton(autoAcceptState.value == savedAutoAcceptState.value) { saveAas(autoAcceptState.value, savedAutoAcceptState) } } @@ -482,9 +517,9 @@ private fun AcceptIncognitoToggle(autoAcceptState: MutableState stringResource(MR.strings.accept_contact_incognito_button), if (autoAcceptState.value.incognito) painterResource(MR.images.ic_theater_comedy_filled) else painterResource(MR.images.ic_theater_comedy), if (autoAcceptState.value.incognito) Indigo else MaterialTheme.colors.secondary, - autoAcceptState.value.incognito, + checked = autoAcceptState.value.incognito, ) { - autoAcceptState.value = AutoAcceptState(autoAcceptState.value.enable, it, autoAcceptState.value.welcomeText) + autoAcceptState.value = AutoAcceptState(autoAcceptState.value.enable, it, autoAcceptState.value.business, autoAcceptState.value.welcomeText) } } @@ -494,7 +529,7 @@ private fun WelcomeMessageEditor(autoAcceptState: MutableState) TextEditor(welcomeText, Modifier.height(100.dp), placeholder = stringResource(MR.strings.enter_welcome_message_optional)) LaunchedEffect(welcomeText.value) { if (welcomeText.value != autoAcceptState.value.welcomeText) { - autoAcceptState.value = AutoAcceptState(autoAcceptState.value.enable, autoAcceptState.value.incognito, welcomeText.value) + autoAcceptState.value = AutoAcceptState(autoAcceptState.value.enable, autoAcceptState.value.incognito, autoAcceptState.value.business, welcomeText.value) } } } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index d931abab70..55b7e2b0e7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -936,6 +936,8 @@ SimpleX address or 1-time link? Create 1-time link Address settings + Business address + Add your team members to the conversations. Continue @@ -1551,6 +1553,8 @@ Invite members + Add team members + Add friends %1$s MEMBERS you: %1$s Delete group @@ -2275,10 +2279,12 @@ Open group Repeat join request? Group already exists! + Chat already exists! %1$s.]]> Already joining the group! You are already joining the group via this link. %1$s.]]> + %1$s.]]> Connect via link? diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_work.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_work.svg new file mode 100644 index 0000000000..4ea483b006 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_work.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_work_filled_padded.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_work_filled_padded.svg new file mode 100644 index 0000000000..3d8c05e2c8 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_work_filled_padded.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file From 29b9abf2411b04c1eddea06901153309b2e0bd67 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 3 Dec 2024 15:22:41 +0000 Subject: [PATCH 162/567] ui: translations (#5267) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (German) Currently translated at 95.6% (2087 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Russian) Currently translated at 95.6% (2087 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (French) Currently translated at 95.6% (2087 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/ * Translated using Weblate (Italian) Currently translated at 95.6% (2087 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 95.6% (2087 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 80.7% (1763 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hant/ * Translated using Weblate (Dutch) Currently translated at 95.6% (2087 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Japanese) Currently translated at 88.6% (1935 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/ * Translated using Weblate (Czech) Currently translated at 91.9% (2006 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Arabic) Currently translated at 95.6% (2087 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Ukrainian) Currently translated at 95.5% (2085 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Finnish) Currently translated at 67.5% (1474 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fi/ * Translated using Weblate (Polish) Currently translated at 95.6% (2086 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pl/ * Translated using Weblate (Portuguese) Currently translated at 43.9% (959 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/ * Translated using Weblate (Hebrew) Currently translated at 86.2% (1883 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/ * Translated using Weblate (Bulgarian) Currently translated at 79.1% (1726 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/bg/ * Translated using Weblate (Turkish) Currently translated at 95.6% (2087 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/tr/ * Translated using Weblate (Persian) Currently translated at 83.3% (1819 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fa/ * Translated using Weblate (Romanian) Currently translated at 33.1% (723 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ro/ * Translated using Weblate (Vietnamese) Currently translated at 61.5% (1342 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (German) Currently translated at 95.6% (2087 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Russian) Currently translated at 95.6% (2087 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (French) Currently translated at 95.6% (2087 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/ * Translated using Weblate (Italian) Currently translated at 95.6% (2087 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 95.6% (2087 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 80.7% (1763 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hant/ * Translated using Weblate (Dutch) Currently translated at 95.6% (2087 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Japanese) Currently translated at 88.6% (1935 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/ * Translated using Weblate (Czech) Currently translated at 91.9% (2006 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Arabic) Currently translated at 95.6% (2087 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Ukrainian) Currently translated at 95.5% (2085 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Finnish) Currently translated at 67.5% (1474 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fi/ * Translated using Weblate (Polish) Currently translated at 95.6% (2086 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pl/ * Translated using Weblate (Portuguese) Currently translated at 43.9% (959 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/ * Translated using Weblate (Hebrew) Currently translated at 86.2% (1883 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/ * Translated using Weblate (Bulgarian) Currently translated at 79.1% (1726 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/bg/ * Translated using Weblate (Turkish) Currently translated at 95.6% (2087 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/tr/ * Translated using Weblate (Persian) Currently translated at 83.3% (1819 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fa/ * Translated using Weblate (Romanian) Currently translated at 33.1% (723 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ro/ * Translated using Weblate (Vietnamese) Currently translated at 61.5% (1342 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2182 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Dutch) Currently translated at 95.9% (2093 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Turkish) Currently translated at 96.1% (2099 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/tr/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2182 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (German) Currently translated at 100.0% (2182 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (German) Currently translated at 100.0% (1918 of 1918 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (Italian) Currently translated at 100.0% (2182 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (1918 of 1918 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2182 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Spanish) Currently translated at 98.4% (2148 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Dutch) Currently translated at 100.0% (2182 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Dutch) Currently translated at 99.0% (1900 of 1918 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/ * Translated using Weblate (Arabic) Currently translated at 100.0% (2182 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2182 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2182 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (1918 of 1918 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/uk/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2182 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1918 of 1918 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Vietnamese) Currently translated at 62.4% (1362 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Spanish) Currently translated at 99.0% (2161 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Dutch) Currently translated at 100.0% (1918 of 1918 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2182 of 2182 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 99.5% (1910 of 1918 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Spanish) Currently translated at 99.6% (1911 of 1918 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2178 of 2178 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1918 of 1918 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2178 of 2178 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2178 of 2178 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2178 of 2178 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Italian) Currently translated at 100.0% (2178 of 2178 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2178 of 2178 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Korean) Currently translated at 66.6% (1452 of 2178 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ko/ * Translated using Weblate (Vietnamese) Currently translated at 62.7% (1367 of 2178 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Indonesian) Currently translated at 59.3% (1293 of 2178 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1918 of 1918 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Dutch) Currently translated at 99.9% (2177 of 2178 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Czech) Currently translated at 91.9% (2002 of 2178 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Vietnamese) Currently translated at 62.9% (1372 of 2178 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Vietnamese) Currently translated at 63.6% (1387 of 2178 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (German) Currently translated at 100.0% (2178 of 2178 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Japanese) Currently translated at 62.5% (1199 of 1918 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ja/ * fix/process localizations --------- Co-authored-by: Anonymous Co-authored-by: 大王叫我来巡山 Co-authored-by: M1K4 Co-authored-by: Abdullah Koyuncu Co-authored-by: summoner001 Co-authored-by: mlanp Co-authored-by: Random Co-authored-by: No name Co-authored-by: jonnysemon Co-authored-by: Max Co-authored-by: Bezruchenko Simon Co-authored-by: tuananh-ng <158744840+tuananh-ng@users.noreply.github.com> Co-authored-by: Ghost of Sparta Co-authored-by: Максим Горпиніч Co-authored-by: translatorforkr Co-authored-by: Rafi Co-authored-by: zenobit Co-authored-by: Miyu Sakatsuki --- .../bg.xcloc/Localized Contents/bg.xliff | 28 ++ .../cs.xcloc/Localized Contents/cs.xliff | 28 ++ .../de.xcloc/Localized Contents/de.xliff | 131 +++++ .../en.xcloc/Localized Contents/en.xliff | 35 ++ .../es.xcloc/Localized Contents/es.xliff | 148 +++++- .../fi.xcloc/Localized Contents/fi.xliff | 28 ++ .../fr.xcloc/Localized Contents/fr.xliff | 28 ++ .../hu.xcloc/Localized Contents/hu.xliff | 165 +++++- .../it.xcloc/Localized Contents/it.xliff | 131 +++++ .../ja.xcloc/Localized Contents/ja.xliff | 50 ++ .../nl.xcloc/Localized Contents/nl.xliff | 131 +++++ .../pl.xcloc/Localized Contents/pl.xliff | 28 ++ .../ru.xcloc/Localized Contents/ru.xliff | 28 ++ .../th.xcloc/Localized Contents/th.xliff | 28 ++ .../tr.xcloc/Localized Contents/tr.xliff | 28 ++ .../uk.xcloc/Localized Contents/uk.xliff | 195 +++++++ .../Localized Contents/zh-Hans.xliff | 28 ++ .../SimpleX NSE/de.lproj/Localizable.strings | 20 +- .../SimpleX NSE/es.lproj/Localizable.strings | 20 +- .../SimpleX NSE/hu.lproj/Localizable.strings | 20 +- .../SimpleX NSE/it.lproj/Localizable.strings | 20 +- .../SimpleX NSE/nl.lproj/Localizable.strings | 20 +- .../SimpleX NSE/uk.lproj/Localizable.strings | 20 +- apps/ios/de.lproj/Localizable.strings | 291 +++++++++++ apps/ios/es.lproj/Localizable.strings | 310 +++++++++++- apps/ios/hu.lproj/Localizable.strings | 325 +++++++++++- apps/ios/it.lproj/Localizable.strings | 291 +++++++++++ apps/ios/ja.lproj/Localizable.strings | 66 +++ apps/ios/nl.lproj/Localizable.strings | 291 +++++++++++ apps/ios/uk.lproj/Localizable.strings | 474 ++++++++++++++++++ .../commonMain/resources/MR/ar/strings.xml | 112 ++++- .../commonMain/resources/MR/base/strings.xml | 1 - .../commonMain/resources/MR/bg/strings.xml | 3 +- .../commonMain/resources/MR/cs/strings.xml | 5 +- .../commonMain/resources/MR/de/strings.xml | 103 +++- .../commonMain/resources/MR/es/strings.xml | 103 +++- .../commonMain/resources/MR/fa/strings.xml | 3 +- .../commonMain/resources/MR/fi/strings.xml | 3 +- .../commonMain/resources/MR/fr/strings.xml | 3 +- .../commonMain/resources/MR/hu/strings.xml | 205 +++++--- .../commonMain/resources/MR/in/strings.xml | 17 + .../commonMain/resources/MR/it/strings.xml | 100 +++- .../commonMain/resources/MR/iw/strings.xml | 3 +- .../commonMain/resources/MR/ja/strings.xml | 3 +- .../commonMain/resources/MR/ko/strings.xml | 52 +- .../commonMain/resources/MR/nl/strings.xml | 99 +++- .../commonMain/resources/MR/pl/strings.xml | 3 +- .../commonMain/resources/MR/pt/strings.xml | 3 +- .../commonMain/resources/MR/ro/strings.xml | 3 +- .../commonMain/resources/MR/ru/strings.xml | 3 +- .../commonMain/resources/MR/tr/strings.xml | 14 +- .../commonMain/resources/MR/uk/strings.xml | 126 ++++- .../commonMain/resources/MR/vi/strings.xml | 52 +- .../resources/MR/zh-rCN/strings.xml | 100 +++- .../resources/MR/zh-rTW/strings.xml | 3 +- 55 files changed, 4293 insertions(+), 235 deletions(-) diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 2964742c85..f1e9ee0f39 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -618,6 +618,10 @@ Добавете адрес към вашия профил, така че вашите контакти да могат да го споделят с други хора. Актуализацията на профила ще бъде изпратена до вашите контакти. No comment provided by engineer. + + Add friends + No comment provided by engineer. + Add profile Добави профил @@ -633,6 +637,10 @@ Добави сървъри чрез сканиране на QR кодове. No comment provided by engineer. + + Add team members + No comment provided by engineer. + Add to another device Добави към друго устройство @@ -643,6 +651,10 @@ Добави съобщение при посрещане No comment provided by engineer. + + Add your team members to the conversations. + No comment provided by engineer. + Added media & file servers No comment provided by engineer. @@ -1183,6 +1195,10 @@ Български, финландски, тайландски и украински - благодарение на потребителите и [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! No comment provided by engineer. + + Business address + 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). Чрез чат профил (по подразбиране) или [чрез връзка](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (БЕТА). @@ -1318,6 +1334,14 @@ Change user profiles authentication reason + + Chat already exists + No comment provided by engineer. + + + Chat already exists! + No comment provided by engineer. + Chat colors No comment provided by engineer. @@ -7590,6 +7614,10 @@ To connect, please ask your contact to create another connection link and check Вече сте вече свързани с %@. No comment provided by engineer. + + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. Вече се свързвате с %@. diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index bf9436afe3..a4bff0f321 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -600,6 +600,10 @@ Přidejte adresu do svého profilu, aby ji vaše kontakty mohly sdílet s dalšími lidmi. Aktualizace profilu bude zaslána vašim kontaktům. No comment provided by engineer. + + Add friends + No comment provided by engineer. + Add profile Přidat profil @@ -615,6 +619,10 @@ Přidejte servery skenováním QR kódů. No comment provided by engineer. + + Add team members + No comment provided by engineer. + Add to another device Přidat do jiného zařízení @@ -625,6 +633,10 @@ Přidat uvítací zprávu No comment provided by engineer. + + Add your team members to the conversations. + No comment provided by engineer. + Added media & file servers No comment provided by engineer. @@ -1145,6 +1157,10 @@ 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. + + Business address + 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). @@ -1277,6 +1293,14 @@ Change user profiles authentication reason + + Chat already exists + No comment provided by engineer. + + + Chat already exists! + No comment provided by engineer. + Chat colors No comment provided by engineer. @@ -7338,6 +7362,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Již jste připojeni k %@. No comment provided by engineer. + + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 6a92589851..3770207e39 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -114,10 +114,12 @@ %@ server + %@ Server No comment provided by engineer. %@ servers + %@ Server No comment provided by engineer. @@ -382,6 +384,7 @@ **Scan / Paste link**: to connect via a link you received. + **Link scannen / einfügen**: Um eine Verbindung über den Link herzustellen, den Sie erhalten haben. No comment provided by engineer. @@ -492,10 +495,12 @@ 1-time link + Einmal-Link No comment provided by engineer. 1-time link can be used *with one contact only* - share in person or via any messenger. + Ein Einmal-Link kann *nur mit einem Kontakt* genutzt werden - teilen Sie in nur persönlich oder über einen beliebigen Messenger. No comment provided by engineer. @@ -586,6 +591,7 @@ Accept conditions + Nutzungsbedingungen akzeptieren No comment provided by engineer. @@ -606,6 +612,7 @@ Accepted conditions + Akzeptierte Nutzungsbedingungen No comment provided by engineer. @@ -628,6 +635,10 @@ Fügen Sie die Adresse Ihrem Profil hinzu, damit Ihre Kontakte sie mit anderen Personen teilen können. Es wird eine Profilaktualisierung an Ihre Kontakte gesendet. No comment provided by engineer. + + Add friends + No comment provided by engineer. + Add profile Profil hinzufügen @@ -643,6 +654,10 @@ Fügen Sie Server durch Scannen der QR Codes hinzu. No comment provided by engineer. + + Add team members + No comment provided by engineer. + Add to another device Einem anderen Gerät hinzufügen @@ -653,12 +668,18 @@ Begrüßungsmeldung hinzufügen No comment provided by engineer. + + Add your team members to the conversations. + No comment provided by engineer. + Added media & file servers + Medien- und Dateiserver hinzugefügt No comment provided by engineer. Added message servers + Nachrichtenserver hinzugefügt No comment provided by engineer. @@ -688,10 +709,12 @@ Address or 1-time link? + Adress- oder Einmal-Link? No comment provided by engineer. Address settings + Adress-Einstellungen No comment provided by engineer. @@ -741,6 +764,7 @@ All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + Alle Nachrichten und Dateien werden **Ende-zu-Ende verschlüsselt** versendet - in Direkt-Nachrichten mit Post-Quantum-Security. No comment provided by engineer. @@ -1218,6 +1242,10 @@ 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. + + Business address + 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). @@ -1357,8 +1385,17 @@ Change user profiles + Chat-Profile wechseln authentication reason + + Chat already exists + No comment provided by engineer. + + + Chat already exists! + No comment provided by engineer. + Chat colors Chat-Farben @@ -1441,10 +1478,12 @@ Check messages every 20 min. + Alle 20min Nachrichten überprüfen. No comment provided by engineer. Check messages when allowed. + Wenn es erlaubt ist, Nachrichten überprüfen. No comment provided by engineer. @@ -1539,38 +1578,47 @@ Conditions accepted on: %@. + Die Nutzungsbedingungen wurden akzeptiert am: %@. No comment provided by engineer. Conditions are accepted for the operator(s): **%@**. + Die Nutzungsbedingungen der/des Betreiber(s) werden akzeptiert: **%@**. No comment provided by engineer. Conditions are already accepted for following operator(s): **%@**. + Die Nutzungsbedingungen der/des folgenden Betreiber(s) wurden schon akzeptiert: **%@**. No comment provided by engineer. Conditions of use + Nutzungsbedingungen No comment provided by engineer. Conditions will be accepted for enabled operators after 30 days. + Die Nutzungsbedingungen der aktivierten Betreiber werden nach 30 Tagen akzeptiert. No comment provided by engineer. Conditions will be accepted for operator(s): **%@**. + Die Nutzungsbedingungen der/des Betreiber(s) werden akzeptiert: **%@**. No comment provided by engineer. Conditions will be accepted for the operator(s): **%@**. + Die Nutzungsbedingungen der/des Betreiber(s) werden akzeptiert: **%@**. No comment provided by engineer. Conditions will be accepted on: %@. + Die Nutzungsbedingungen werden akzeptiert am: %@. No comment provided by engineer. Conditions will be automatically accepted for enabled operators on: %@. + Die Nutzungsbedingungen der aktivierten Betreiber werden automatisch akzeptiert am: %@. No comment provided by engineer. @@ -1769,6 +1817,7 @@ Das ist Ihr eigener Einmal-Link! Connection security + Verbindungs-Sicherheit No comment provided by engineer. @@ -1888,6 +1937,7 @@ Das ist Ihr eigener Einmal-Link! Create 1-time link + Einmal-Link erstellen No comment provided by engineer. @@ -1977,6 +2027,7 @@ Das ist Ihr eigener Einmal-Link! Current conditions text couldn't be loaded, you can review conditions via this link: + Der Text der aktuellen Nutzungsbedingungen konnte nicht geladen werden. Sie können die Nutzungsbedingungen unter diesem Link einsehen: No comment provided by engineer. @@ -2346,6 +2397,7 @@ Das ist Ihr eigener Einmal-Link! Delivered even when Apple drops them. + Auslieferung, selbst wenn Apple sie löscht. No comment provided by engineer. @@ -2631,6 +2683,7 @@ Das ist Ihr eigener Einmal-Link! E2E encrypted notifications. + E2E-verschlüsselte Benachrichtigungen. No comment provided by engineer. @@ -2655,6 +2708,7 @@ Das ist Ihr eigener Einmal-Link! Enable Flux + Flux aktivieren No comment provided by engineer. @@ -2864,6 +2918,7 @@ Das ist Ihr eigener Einmal-Link! Error accepting conditions + Fehler beim Akzeptieren der Nutzungsbedingungen alert title @@ -2878,6 +2933,7 @@ Das ist Ihr eigener Einmal-Link! Error adding server + Fehler beim Hinzufügen des Servers alert title @@ -3022,6 +3078,7 @@ Das ist Ihr eigener Einmal-Link! Error loading servers + Fehler beim Laden der Server alert title @@ -3081,6 +3138,7 @@ Das ist Ihr eigener Einmal-Link! Error saving servers + Fehler beim Speichern der Server alert title @@ -3155,6 +3213,7 @@ Das ist Ihr eigener Einmal-Link! Error updating server + Fehler beim Aktualisieren des Servers alert title @@ -3204,6 +3263,7 @@ Das ist Ihr eigener Einmal-Link! Errors in servers configuration. + Fehler in der Server-Konfiguration. servers error @@ -3410,6 +3470,7 @@ Das ist Ihr eigener Einmal-Link! For chat profile %@: + Für das Chat-Profil %@: servers error @@ -3419,14 +3480,17 @@ Das ist Ihr eigener Einmal-Link! For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + Wenn Ihr Kontakt beispielsweise Nachrichten über einen SimpleX-Chatserver empfängt, wird Ihre App diese über einen der Server von Flux versenden. No comment provided by engineer. For private routing + Für privates Routing No comment provided by engineer. For social media + Für soziale Medien No comment provided by engineer. @@ -3740,10 +3804,12 @@ Fehler: %2$@ How it affects privacy + Wie es die Privatsphäre beeinflusst No comment provided by engineer. How it helps privacy + Wie es die Privatsphäre schützt No comment provided by engineer. @@ -4565,6 +4631,7 @@ Das ist Ihr Link für die Gruppe %@! More reliable notifications + Zuverlässigere Benachrichtigungen No comment provided by engineer. @@ -4604,6 +4671,7 @@ Das ist Ihr Link für die Gruppe %@! Network decentralization + Dezentralisiertes Netzwerk No comment provided by engineer. @@ -4618,6 +4686,7 @@ Das ist Ihr Link für die Gruppe %@! Network operator + Netzwerk-Betreiber No comment provided by engineer. @@ -4677,6 +4746,7 @@ Das ist Ihr Link für die Gruppe %@! New events + Neue Ereignisse notification @@ -4706,6 +4776,7 @@ Das ist Ihr Link für die Gruppe %@! New server + Neuer Server No comment provided by engineer. @@ -4765,10 +4836,12 @@ Das ist Ihr Link für die Gruppe %@! No media & file servers. + Keine Medien- und Dateiserver. servers error No message servers. + Keine Nachrichten-Server. servers error @@ -4803,18 +4876,22 @@ Das ist Ihr Link für die Gruppe %@! No servers for private message routing. + Keine Server für privates Nachrichten-Routing. servers error No servers to receive files. + Keine Server für den Empfang von Dateien. servers error No servers to receive messages. + Keine Server für den Empfang von Nachrichten. servers error No servers to send files. + Keine Server für das Versenden von Dateien. servers error @@ -4849,6 +4926,7 @@ Das ist Ihr Link für die Gruppe %@! Notifications privacy + Datenschutz für Benachrichtigungen No comment provided by engineer. @@ -4991,6 +5069,7 @@ Dies erfordert die Aktivierung eines VPNs. Open changes + Änderungen öffnen No comment provided by engineer. @@ -5005,6 +5084,7 @@ Dies erfordert die Aktivierung eines VPNs. Open conditions + Nutzungsbedingungen öffnen No comment provided by engineer. @@ -5024,10 +5104,12 @@ Dies erfordert die Aktivierung eines VPNs. Operator + Betreiber No comment provided by engineer. Operator server + Betreiber-Server alert title @@ -5056,6 +5138,7 @@ Dies erfordert die Aktivierung eines VPNs. Or to share privately + Oder zum privaten Teilen No comment provided by engineer. @@ -5276,6 +5359,7 @@ Fehler: %@ Preset servers + Voreingestellte Server No comment provided by engineer. @@ -5452,6 +5536,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Push Notifications + Push-Benachrichtigungen No comment provided by engineer. @@ -5827,10 +5912,12 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Review conditions + Nutzungsbedingungen einsehen No comment provided by engineer. Review later + Später einsehen No comment provided by engineer. @@ -5880,10 +5967,12 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Same conditions will apply to operator **%@**. + Dieselben Nutzungsbedingungen gelten auch für den Betreiber **%@**. No comment provided by engineer. Same conditions will apply to operator(s): **%@**. + Dieselben Nutzungsbedingungen gelten auch für den/die Betreiber: **%@**. No comment provided by engineer. @@ -6289,6 +6378,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Server added to operator %@. + Der Server wurde dem Betreiber %@ hinzugefügt. alert message @@ -6308,14 +6398,17 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Server operator changed. + Der Server-Betreiber wurde geändert. alert title Server operators + Server-Betreiber No comment provided by engineer. Server protocol changed. + Das Server-Protokoll wurde geändert. alert title @@ -6446,10 +6539,12 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Share 1-time link with a friend + Den Einmal-Einladungslink mit einem Freund teilen No comment provided by engineer. Share SimpleX address on social media. + Die SimpleX-Adresse auf sozialen Medien teilen. No comment provided by engineer. @@ -6459,6 +6554,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Share address publicly + Die Adresse öffentlich teilen No comment provided by engineer. @@ -6583,10 +6679,12 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. SimpleX address and 1-time links are safe to share via any messenger. + Die SimpleX-Adresse und Einmal-Links können sicher über beliebige Messenger geteilt werden. No comment provided by engineer. SimpleX address or 1-time link? + SimpleX-Adresse oder Einmal-Link? No comment provided by engineer. @@ -6682,6 +6780,8 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Some servers failed the test: %@ + Einige Server haben den Test nicht bestanden: +%@ alert message @@ -6952,6 +7052,7 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro The app protects your privacy by using different operators in each conversation. + Durch Verwendung verschiedener Netzwerk-Betreiber für jede Unterhaltung schützt die App Ihre Privatsphäre. No comment provided by engineer. @@ -6971,6 +7072,7 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro The connection reached the limit of undelivered messages, your contact may be offline. + Diese Verbindung hat das Limit der nicht ausgelieferten Nachrichten erreicht. Ihr Kontakt ist möglicherweise offline. No comment provided by engineer. @@ -7035,6 +7137,7 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro The second preset operator in the app! + Der zweite voreingestellte Netzwerk-Betreiber in der App! No comment provided by engineer. @@ -7054,6 +7157,7 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro The servers for new files of your current chat profile **%@**. + Die Server Deines aktuellen Chat-Profils für neue Dateien **%@**. No comment provided by engineer. @@ -7073,6 +7177,7 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro These conditions will also apply for: **%@**. + Diese Nutzungsbedingungen gelten auch für: **%@**. No comment provided by engineer. @@ -7177,6 +7282,7 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro To protect against your link being replaced, you can compare contact security codes. + Zum Schutz vor dem Austausch Ihres Links können Sie die Sicherheitscodes Ihrer Kontakte vergleichen. No comment provided by engineer. @@ -7203,6 +7309,7 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt To receive + Für den Empfang No comment provided by engineer. @@ -7227,6 +7334,7 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt To send + Für das Senden No comment provided by engineer. @@ -7236,6 +7344,7 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt To use the servers of **%@**, accept conditions of use. + Um die Server von **%@** zu nutzen, müssen Sie dessen Nutzungsbedingungen akzeptieren. No comment provided by engineer. @@ -7330,6 +7439,7 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt Undelivered messages + Nicht ausgelieferte Nachrichten No comment provided by engineer. @@ -7491,6 +7601,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Use %@ + Verwende %@ No comment provided by engineer. @@ -7520,10 +7631,12 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Use for files + Für Dateien verwenden No comment provided by engineer. Use for messages + Für Nachrichten verwenden No comment provided by engineer. @@ -7568,6 +7681,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Use servers + Verwende Server No comment provided by engineer. @@ -7662,6 +7776,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s View conditions + Nutzungsbedingungen anschauen No comment provided by engineer. @@ -7671,6 +7786,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s View updated conditions + Aktualisierte Nutzungsbedingungen anschauen No comment provided by engineer. @@ -7785,6 +7901,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s When more than one operator is enabled, none of them has metadata to learn who communicates with whom. + Wenn mehrere Netzwerk-Betreiber aktiviert sind, hat keiner von ihnen Metadaten, um zu erfahren, wer mit wem kommuniziert. No comment provided by engineer. @@ -7882,6 +7999,10 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Sie sind bereits mit %@ verbunden. No comment provided by engineer. + + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. Sie sind bereits mit %@ verbunden. @@ -7946,10 +8067,12 @@ Verbindungsanfrage wiederholen? You can configure operators in Network & servers settings. + Sie können die Betreiber in den Netzwerk- und Servereinstellungen konfigurieren. No comment provided by engineer. You can configure servers via settings. + Sie können die Server über die Einstellungen konfigurieren. No comment provided by engineer. @@ -7994,6 +8117,7 @@ Verbindungsanfrage wiederholen? You can set connection name, to remember who the link was shared with. + Sie können einen Verbindungsnamen festlegen, um sich zu merken, mit wem der Link geteilt wurde. No comment provided by engineer. @@ -8295,6 +8419,7 @@ Verbindungsanfrage wiederholen? Your servers + Ihre Server No comment provided by engineer. @@ -8719,6 +8844,7 @@ Verbindungsanfrage wiederholen? for better metadata privacy. + für einen besseren Metadatenschutz. No comment provided by engineer. @@ -9350,22 +9476,27 @@ Zuletzt empfangene Nachricht: %2$@ %d new events + %d neue Ereignisse notification body From: %@ + Von: %@ notification body New events + Neue Ereignisse notification New messages + Neue Nachrichten notification New messages in %d chats + Neue Nachrichten in %d Chats notification body diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 09a63ab3c4..72ad43f136 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -635,6 +635,11 @@ Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. No comment provided by engineer. + + Add friends + Add friends + No comment provided by engineer. + Add profile Add profile @@ -650,6 +655,11 @@ Add servers by scanning QR codes. No comment provided by engineer. + + Add team members + Add team members + No comment provided by engineer. + Add to another device Add to another device @@ -660,6 +670,11 @@ Add welcome message No comment provided by engineer. + + Add your team members to the conversations. + Add your team members to the conversations. + No comment provided by engineer. + Added media & file servers Added media & file servers @@ -1230,6 +1245,11 @@ Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! No comment provided by engineer. + + Business address + Business address + 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). By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). @@ -1372,6 +1392,16 @@ Change user profiles authentication reason + + Chat already exists + Chat already exists + No comment provided by engineer. + + + Chat already exists! + Chat already exists! + No comment provided by engineer. + Chat colors Chat colors @@ -7977,6 +8007,11 @@ To connect, please ask your contact to create another connection link and check You are already connected to %@. No comment provided by engineer. + + You are already connected with %@. + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. You are already connecting to %@. diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 8d109187c2..cfffd783d9 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -114,10 +114,12 @@ %@ server + %@ servidor No comment provided by engineer. %@ servers + %@ servidores No comment provided by engineer. @@ -152,7 +154,7 @@ %d days - %d días + %d día(s) time interval @@ -177,37 +179,37 @@ %d hours - %d horas + %d hora(s) time interval %d messages not forwarded - %d mensajes no enviados + %d mensaje(s) no enviado(s) alert title %d min - %d minutos + %d minuto(s) time interval %d months - %d meses + %d mes(es) time interval %d sec - %d segundos + %d segundo(s) time interval %d skipped message(s) - %d mensaje(s) saltado(s + %d mensaje(s) omitido(s) integrity error chat item %d weeks - %d semanas + %d semana(s) time interval @@ -382,6 +384,7 @@ **Scan / Paste link**: to connect via a link you received. + **Escanear / Pegar enlace**: para conectar mediante un enlace recibido. No comment provided by engineer. @@ -492,10 +495,12 @@ 1-time link + Enlace de un uso No comment provided by engineer. 1-time link can be used *with one contact only* - share in person or via any messenger. + Los enlaces de un uso pueden ser usados *solamente con un contacto* - compártelos en persona o mediante cualquier aplicación de mensajería. No comment provided by engineer. @@ -586,6 +591,7 @@ Accept conditions + Aceptar condiciones No comment provided by engineer. @@ -606,6 +612,7 @@ Accepted conditions + Condiciones aceptadas No comment provided by engineer. @@ -628,6 +635,10 @@ Añade la dirección a tu perfil para que tus contactos puedan compartirla con otros. La actualización del perfil se enviará a tus contactos. No comment provided by engineer. + + Add friends + No comment provided by engineer. + Add profile Añadir perfil @@ -643,6 +654,10 @@ Añadir servidores mediante el escaneo de códigos QR. No comment provided by engineer. + + Add team members + No comment provided by engineer. + Add to another device Añadir a otro dispositivo @@ -653,12 +668,18 @@ Añadir mensaje de bienvenida No comment provided by engineer. + + Add your team members to the conversations. + No comment provided by engineer. + Added media & file servers + Servidores de archivos y multimedia añadidos No comment provided by engineer. Added message servers + Servidores de mensajes añadidos No comment provided by engineer. @@ -688,10 +709,12 @@ Address or 1-time link? + ¿Dirección o enlace de un uso? No comment provided by engineer. Address settings + Configuración de dirección No comment provided by engineer. @@ -741,6 +764,7 @@ All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + Todos los mensajes y archivos son enviados **cifrados de extremo a extremo** y con seguridad de cifrado postcuántico en mensajes directos. No comment provided by engineer. @@ -1218,6 +1242,10 @@ 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. + + Business address + 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 (predeterminado) o [por conexión](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). @@ -1357,8 +1385,17 @@ Change user profiles + Cambiar perfil de usuario authentication reason + + Chat already exists + No comment provided by engineer. + + + Chat already exists! + No comment provided by engineer. + Chat colors Colores del chat @@ -1441,10 +1478,12 @@ Check messages every 20 min. + Comprobar mensajes cada 20 min. No comment provided by engineer. Check messages when allowed. + Comprobar mensajes cuando se permita. No comment provided by engineer. @@ -1539,38 +1578,47 @@ Conditions accepted on: %@. + Condiciones aceptadas el: %@. No comment provided by engineer. Conditions are accepted for the operator(s): **%@**. + Las condiciones se han aceptado para el(los) operador(s): **%@**. No comment provided by engineer. Conditions are already accepted for following operator(s): **%@**. + Las condiciones ya se han aceptado para el/los siguiente(s) operador(s): **%@**. No comment provided by engineer. Conditions of use + Condiciones de uso No comment provided by engineer. Conditions will be accepted for enabled operators after 30 days. + Las condiciones de los operadores habilitados serán aceptadas después de 30 días. No comment provided by engineer. Conditions will be accepted for operator(s): **%@**. + Las condiciones serán aceptadas para el/los operador(es): **%@**. No comment provided by engineer. Conditions will be accepted for the operator(s): **%@**. + Las condiciones serán aceptadas para el/los operador(es): **%@**. No comment provided by engineer. Conditions will be accepted on: %@. + Las condiciones serán aceptadas el: %@. No comment provided by engineer. Conditions will be automatically accepted for enabled operators on: %@. + Las condiciones serán aceptadas automáticamente para los operadores habilitados el: %@. No comment provided by engineer. @@ -1769,6 +1817,7 @@ This is your own one-time link! Connection security + Seguridad de conexión No comment provided by engineer. @@ -1888,6 +1937,7 @@ This is your own one-time link! Create 1-time link + Crear enlace de un uso No comment provided by engineer. @@ -1977,6 +2027,7 @@ This is your own one-time link! Current conditions text couldn't be loaded, you can review conditions via this link: + El texto con las condiciones actuales no se ha podido cargar, puedes revisar las condiciones en el siguiente enlace: No comment provided by engineer. @@ -2346,6 +2397,7 @@ This is your own one-time link! Delivered even when Apple drops them. + Entregados incluso cuando Apple los descarta. No comment provided by engineer. @@ -2631,6 +2683,7 @@ This is your own one-time link! E2E encrypted notifications. + Notificaciones cifradas E2E. No comment provided by engineer. @@ -2655,6 +2708,7 @@ This is your own one-time link! Enable Flux + Habilitar Flux No comment provided by engineer. @@ -2864,6 +2918,7 @@ This is your own one-time link! Error accepting conditions + Error al aceptar las condiciones alert title @@ -2878,6 +2933,7 @@ This is your own one-time link! Error adding server + Error al añadir servidor alert title @@ -3022,6 +3078,7 @@ This is your own one-time link! Error loading servers + Error al cargar servidores alert title @@ -3081,6 +3138,7 @@ This is your own one-time link! Error saving servers + Error al guardar servidores alert title @@ -3155,6 +3213,7 @@ This is your own one-time link! Error updating server + Error al actualizar el servidor alert title @@ -3204,6 +3263,7 @@ This is your own one-time link! Errors in servers configuration. + Error en la configuración del servidor. servers error @@ -3410,6 +3470,7 @@ This is your own one-time link! For chat profile %@: + Para el perfil de chat %@: servers error @@ -3419,14 +3480,17 @@ This is your own one-time link! For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + Si por ejemplo tu contacto recibe los mensajes a través de un servidor de SimpleX Chat, tu aplicación los entregará a través de un servidor de Flux. No comment provided by engineer. For private routing + Para el enrutamiento privado No comment provided by engineer. For social media + Para redes sociales No comment provided by engineer. @@ -3740,10 +3804,12 @@ Error: %2$@ How it affects privacy + Cómo afecta a la privacidad No comment provided by engineer. How it helps privacy + Cómo ayuda a la privacidad No comment provided by engineer. @@ -4565,6 +4631,7 @@ This is your link for group %@! More reliable notifications + Notificaciones más fiables No comment provided by engineer. @@ -4604,6 +4671,7 @@ This is your link for group %@! Network decentralization + Descentralización de la red No comment provided by engineer. @@ -4618,6 +4686,7 @@ This is your link for group %@! Network operator + Operador de red No comment provided by engineer. @@ -4677,6 +4746,7 @@ This is your link for group %@! New events + Eventos nuevos notification @@ -4706,6 +4776,7 @@ This is your link for group %@! New server + Servidor nuevo No comment provided by engineer. @@ -4765,10 +4836,12 @@ This is your link for group %@! No media & file servers. + Ningún servidor de archivos y multimedia. servers error No message servers. + Ningún servidor de mensajes. servers error @@ -4793,6 +4866,7 @@ This is your link for group %@! No push server + Ningún servidor push No comment provided by engineer. @@ -4802,18 +4876,22 @@ This is your link for group %@! No servers for private message routing. + Ningún servidor para enrutamiento privado. servers error No servers to receive files. + Ningún servidor para recibir archivos. servers error No servers to receive messages. + Ningún servidor para recibir mensajes. servers error No servers to send files. + Ningún servidor para enviar archivos. servers error @@ -4848,6 +4926,7 @@ This is your link for group %@! Notifications privacy + Privacidad en las notificaciones No comment provided by engineer. @@ -4990,6 +5069,7 @@ Requiere activación de la VPN. Open changes + Abrir cambios No comment provided by engineer. @@ -5004,6 +5084,7 @@ Requiere activación de la VPN. Open conditions + Abrir condiciones No comment provided by engineer. @@ -5023,10 +5104,12 @@ Requiere activación de la VPN. Operator + Operador No comment provided by engineer. Operator server + Servidor del operador alert title @@ -5055,6 +5138,7 @@ Requiere activación de la VPN. Or to share privately + O para compartir en privado No comment provided by engineer. @@ -5275,6 +5359,7 @@ Error: %@ Preset servers + Servidores predefinidos No comment provided by engineer. @@ -5451,6 +5536,7 @@ Actívalo en ajustes de *Servidores y Redes*. Push Notifications + Notificaciones push No comment provided by engineer. @@ -5826,10 +5912,12 @@ Actívalo en ajustes de *Servidores y Redes*. Review conditions + Revisar condiciones No comment provided by engineer. Review later + Revisar más tarde No comment provided by engineer. @@ -5879,10 +5967,12 @@ Actívalo en ajustes de *Servidores y Redes*. Same conditions will apply to operator **%@**. + Las mismas condiciones se aplicarán al operador **%@**. No comment provided by engineer. Same conditions will apply to operator(s): **%@**. + Las mismas condiciones se aplicarán a el/los operador(es) **%@**. No comment provided by engineer. @@ -6288,6 +6378,7 @@ Actívalo en ajustes de *Servidores y Redes*. Server added to operator %@. + Servidor añadido al operador %@. alert message @@ -6307,14 +6398,17 @@ Actívalo en ajustes de *Servidores y Redes*. Server operator changed. + El operador del servidor ha cambiado. alert title Server operators + Operadores de servidores No comment provided by engineer. Server protocol changed. + El protocolo del servidor ha cambiado. alert title @@ -6445,10 +6539,12 @@ Actívalo en ajustes de *Servidores y Redes*. Share 1-time link with a friend + Compartir enlace de un uso con un amigo No comment provided by engineer. Share SimpleX address on social media. + Compartir dirección SimpleX en redes sociales. No comment provided by engineer. @@ -6458,6 +6554,7 @@ Actívalo en ajustes de *Servidores y Redes*. Share address publicly + Campartir dirección públicamente No comment provided by engineer. @@ -6582,10 +6679,12 @@ Actívalo en ajustes de *Servidores y Redes*. SimpleX address and 1-time links are safe to share via any messenger. + Compartir enlaces de un uso y direcciones SimpleX es seguro a través de cualquier medio. No comment provided by engineer. SimpleX address or 1-time link? + Dirección SimpleX o enlace de un uso? No comment provided by engineer. @@ -6681,6 +6780,8 @@ Actívalo en ajustes de *Servidores y Redes*. Some servers failed the test: %@ + Algunos servidores no han superado la prueba: +%@ alert message @@ -6951,6 +7052,7 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. The app protects your privacy by using different operators in each conversation. + La aplicación protege tu privacidad mediante el uso de diferentes operadores en cada conversación. No comment provided by engineer. @@ -6970,6 +7072,7 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. The connection reached the limit of undelivered messages, your contact may be offline. + La conexión ha alcanzado el límite de mensajes no entregados. es posible que tu contacto esté desconectado. No comment provided by engineer. @@ -7034,6 +7137,7 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. The second preset operator in the app! + El segundo operador predefinido! No comment provided by engineer. @@ -7053,6 +7157,7 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. The servers for new files of your current chat profile **%@**. + Los servidores para archivos nuevos en tu perfil actual **%@**. No comment provided by engineer. @@ -7072,6 +7177,7 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. These conditions will also apply for: **%@**. + Estas condiciones también se aplican para: **%@**. No comment provided by engineer. @@ -7176,6 +7282,7 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. To protect against your link being replaced, you can compare contact security codes. + Para protegerte contra una sustitución del enlace, puedes comparar los códigos de seguridad con tu contacto. No comment provided by engineer. @@ -7202,6 +7309,7 @@ Se te pedirá que completes la autenticación antes de activar esta función. To receive + Para recibir No comment provided by engineer. @@ -7226,6 +7334,7 @@ Se te pedirá que completes la autenticación antes de activar esta función. To send + Para enviar No comment provided by engineer. @@ -7235,6 +7344,7 @@ Se te pedirá que completes la autenticación antes de activar esta función. To use the servers of **%@**, accept conditions of use. + Para usar los servidores de **%@**, acepta las condiciones de uso. No comment provided by engineer. @@ -7329,6 +7439,7 @@ Se te pedirá que completes la autenticación antes de activar esta función. Undelivered messages + Mensajes no entregados No comment provided by engineer. @@ -7490,6 +7601,7 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Use %@ + Usar %@ No comment provided by engineer. @@ -7519,10 +7631,12 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Use for files + Usar para archivos No comment provided by engineer. Use for messages + Usar para mensajes No comment provided by engineer. @@ -7567,6 +7681,7 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Use servers + Usar servidores No comment provided by engineer. @@ -7661,6 +7776,7 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión View conditions + Ver condiciones No comment provided by engineer. @@ -7670,6 +7786,7 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión View updated conditions + Ver condiciones actualizadas No comment provided by engineer. @@ -7784,6 +7901,7 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión When more than one operator is enabled, none of them has metadata to learn who communicates with whom. + Cuando está habilitado más de un operador, ninguno dispone de los metadatos para conocer quién se comunica con quién. No comment provided by engineer. @@ -7881,6 +7999,10 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Ya estás conectado a %@. No comment provided by engineer. + + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. Ya estás conectando con %@. @@ -7945,10 +8067,12 @@ Repeat join request? You can configure operators in Network & servers settings. + Puedes configurar los operadores desde Servidores y Redes. No comment provided by engineer. You can configure servers via settings. + Puedes configurar los servidores a través de su configuración. No comment provided by engineer. @@ -7993,6 +8117,7 @@ Repeat join request? You can set connection name, to remember who the link was shared with. + Puedes añadir un nombre a la conexión para recordar a quién corresponde. No comment provided by engineer. @@ -8294,6 +8419,7 @@ Repeat connection request? Your servers + Tus servidores No comment provided by engineer. @@ -8718,6 +8844,7 @@ Repeat connection request? for better metadata privacy. + para mayor privacidad de los metadatos. No comment provided by engineer. @@ -9349,22 +9476,27 @@ last received msg: %2$@ %d new events + %d evento(s) nuevo(s) notification body From: %@ + De: %@ notification body New events + Eventos nuevos notification New messages + Mensajes nuevos notification New messages in %d chats + Mensajes nuevos en %d chat(s) notification body diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index 325732cb8d..763b502ddb 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -595,6 +595,10 @@ Lisää osoite profiiliisi, jotta kontaktisi voivat jakaa sen muiden kanssa. Profiilipäivitys lähetetään kontakteillesi. No comment provided by engineer. + + Add friends + No comment provided by engineer. + Add profile Lisää profiili @@ -610,6 +614,10 @@ Lisää palvelimia skannaamalla QR-koodeja. No comment provided by engineer. + + Add team members + No comment provided by engineer. + Add to another device Lisää toiseen laitteeseen @@ -620,6 +628,10 @@ Lisää tervetuloviesti No comment provided by engineer. + + Add your team members to the conversations. + No comment provided by engineer. + Added media & file servers No comment provided by engineer. @@ -1138,6 +1150,10 @@ Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! No comment provided by engineer. + + Business address + 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). Chat-profiilin mukaan (oletus) tai [yhteyden mukaan](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). @@ -1270,6 +1286,14 @@ Change user profiles authentication reason + + Chat already exists + No comment provided by engineer. + + + Chat already exists! + No comment provided by engineer. + Chat colors No comment provided by engineer. @@ -7323,6 +7347,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Olet jo muodostanut yhteyden %@:n kanssa. No comment provided by engineer. + + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 56c57a2237..d91ce3c106 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -628,6 +628,10 @@ Ajoutez une adresse à votre profil, afin que vos contacts puissent la partager avec d'autres personnes. La mise à jour du profil sera envoyée à vos contacts. No comment provided by engineer. + + Add friends + No comment provided by engineer. + Add profile Ajouter un profil @@ -643,6 +647,10 @@ Ajoutez des serveurs en scannant des codes QR. No comment provided by engineer. + + Add team members + No comment provided by engineer. + Add to another device Ajouter à un autre appareil @@ -653,6 +661,10 @@ Ajouter un message d'accueil No comment provided by engineer. + + Add your team members to the conversations. + No comment provided by engineer. + Added media & file servers No comment provided by engineer. @@ -1218,6 +1230,10 @@ Bulgare, finnois, thaïlandais et ukrainien - grâce aux utilisateurs et à [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat) ! No comment provided by engineer. + + Business address + 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). Par profil de chat (par défaut) ou [par connexion](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). @@ -1359,6 +1375,14 @@ Change user profiles authentication reason + + Chat already exists + No comment provided by engineer. + + + Chat already exists! + No comment provided by engineer. + Chat colors Couleurs de chat @@ -7882,6 +7906,10 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Vous êtes déjà connecté·e à %@ via ce lien. No comment provided by engineer. + + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. Vous êtes déjà en train de vous connecter à %@. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index b8b760b5a0..44750dfbb2 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -44,7 +44,7 @@ #secret# - #titkos# + #titok# No comment provided by engineer. @@ -114,10 +114,12 @@ %@ server + %@ kiszolgáló No comment provided by engineer. %@ servers + %@ kiszolgáló No comment provided by engineer. @@ -267,7 +269,7 @@ %lld new interface languages - %lld új nyelvi csomag + %lld új kezelőfelületi nyelv No comment provided by engineer. @@ -382,6 +384,7 @@ **Scan / Paste link**: to connect via a link you received. + **Hivatkozás beolvasása / beillesztése**: egy kapott hivatkozáson keresztüli kapcsolódáshoz. No comment provided by engineer. @@ -492,10 +495,12 @@ 1-time link + Egyszer használható meghívó-hivatkozás No comment provided by engineer. 1-time link can be used *with one contact only* - share in person or via any messenger. + Az egyszer használható meghívó-hivatkozás csak *egyetlen ismerőssel használható* - személyesen vagy bármilyen üzenetküldőn keresztül megosztható. No comment provided by engineer. @@ -586,6 +591,7 @@ Accept conditions + Feltételek elfogadása No comment provided by engineer. @@ -606,6 +612,7 @@ Accepted conditions + Elfogadott feltételek No comment provided by engineer. @@ -628,6 +635,10 @@ Cím hozzáadása a profilhoz, hogy az ismerősei megoszthassák másokkal. A profilfrissítés elküldésre kerül az ismerősei számára. No comment provided by engineer. + + Add friends + No comment provided by engineer. + Add profile Profil hozzáadása @@ -643,6 +654,10 @@ Kiszolgáló hozzáadása QR-kód beolvasásával. No comment provided by engineer. + + Add team members + No comment provided by engineer. + Add to another device Hozzáadás egy másik eszközhöz @@ -653,12 +668,18 @@ Üdvözlőüzenet hozzáadása No comment provided by engineer. + + Add your team members to the conversations. + No comment provided by engineer. + Added media & file servers + Hozzáadott média- és fájlkiszolgálók No comment provided by engineer. Added message servers + Hozzáadott üzenetkiszolgálók No comment provided by engineer. @@ -688,10 +709,12 @@ Address or 1-time link? + Cím vagy egyszer használható meghívó-hivatkozás? No comment provided by engineer. Address settings + Címbeállítások No comment provided by engineer. @@ -741,6 +764,7 @@ All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + Az összes üzenetet és fájlt **végpontok közötti titkosítással** küldi, a közvetlen üzenetekben pedig kvantumrezisztens biztonsággal. No comment provided by engineer. @@ -1218,6 +1242,10 @@ Bolgár, finn, thai és ukrán – köszönet a felhasználóknak és a [Weblate-nek](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! No comment provided by engineer. + + Business address + 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). A csevegési profillal (alapértelmezett), vagy a [kapcsolattal] (https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BÉTA). @@ -1357,8 +1385,17 @@ Change user profiles + Felhasználói profilok megváltoztatása authentication reason + + Chat already exists + No comment provided by engineer. + + + Chat already exists! + No comment provided by engineer. + Chat colors Csevegés színei @@ -1441,10 +1478,12 @@ Check messages every 20 min. + Üzenetek ellenőrzése 20 percenként. No comment provided by engineer. Check messages when allowed. + Üzenetek ellenőrzése, amikor engedélyezett. No comment provided by engineer. @@ -1539,38 +1578,47 @@ Conditions accepted on: %@. + Feltételek elfogadva ekkor: %@. No comment provided by engineer. Conditions are accepted for the operator(s): **%@**. + A következő üzemeltető(k) számára elfogadott feltételek: **%@**. No comment provided by engineer. Conditions are already accepted for following operator(s): **%@**. + A feltételek már el lettek fogadva a következő üzemeltető(k) számára: **%@**. No comment provided by engineer. Conditions of use + Használati feltételek No comment provided by engineer. Conditions will be accepted for enabled operators after 30 days. + A feltételek 30 nap elteltével lesznek elfogadva az engedélyezett üzemeltető számára. No comment provided by engineer. Conditions will be accepted for operator(s): **%@**. + A feltételek el lesznek fogadva a következő üzemeltető(k) számára: **%@**. No comment provided by engineer. Conditions will be accepted for the operator(s): **%@**. + A feltételek el lesznek fogadva a következő üzemeltető(k) számára: **%@**. No comment provided by engineer. Conditions will be accepted on: %@. + A feltételek ekkor lesznek elfogadva: %@. No comment provided by engineer. Conditions will be automatically accepted for enabled operators on: %@. + A feltételek automatikusan elfogadásra kerülnek az engedélyezett üzemeltető számára: %@. No comment provided by engineer. @@ -1669,7 +1717,7 @@ Ez az Ön SimpleX-címe! Connect to yourself? This is your own one-time link! Kapcsolódás saját magához? -Ez az Ön egyszer használható hivatkozása! +Ez az Ön egyszer használható meghívó-hivatkozása! No comment provided by engineer. @@ -1684,7 +1732,7 @@ Ez az Ön egyszer használható hivatkozása! Connect via one-time link - Kapcsolódás egyszer használható hivatkozáson keresztül + Kapcsolódás egyszer használható meghívó-hivatkozáson keresztül No comment provided by engineer. @@ -1769,6 +1817,7 @@ Ez az Ön egyszer használható hivatkozása! Connection security + Kapcsolatbiztonság No comment provided by engineer. @@ -1888,6 +1937,7 @@ Ez az Ön egyszer használható hivatkozása! Create 1-time link + Egyszer használható meghívó-hivatkozás létrehozása No comment provided by engineer. @@ -1977,6 +2027,7 @@ Ez az Ön egyszer használható hivatkozása! Current conditions text couldn't be loaded, you can review conditions via this link: + A jelenlegi feltételek szövegét nem lehetett betölteni, a feltételeket ezen a hivatkozáson keresztül vizsgálhatja felül: No comment provided by engineer. @@ -2346,6 +2397,7 @@ Ez az Ön egyszer használható hivatkozása! Delivered even when Apple drops them. + Kézbesítés akkor is, amikor az Apple eldobja őket. No comment provided by engineer. @@ -2631,6 +2683,7 @@ Ez az Ön egyszer használható hivatkozása! E2E encrypted notifications. + Végpontok közötti titkosított értesítések. No comment provided by engineer. @@ -2655,6 +2708,7 @@ Ez az Ön egyszer használható hivatkozása! Enable Flux + Flux engedélyezése No comment provided by engineer. @@ -2864,6 +2918,7 @@ Ez az Ön egyszer használható hivatkozása! Error accepting conditions + Hiba a feltételek elfogadásakor alert title @@ -2878,6 +2933,7 @@ Ez az Ön egyszer használható hivatkozása! Error adding server + Hiba a kiszolgáló hozzáadásakor alert title @@ -3022,6 +3078,7 @@ Ez az Ön egyszer használható hivatkozása! Error loading servers + Hiba a kiszolgálók betöltésekor alert title @@ -3081,6 +3138,7 @@ Ez az Ön egyszer használható hivatkozása! Error saving servers + Hiba a kiszolgálók mentésekor alert title @@ -3155,6 +3213,7 @@ Ez az Ön egyszer használható hivatkozása! Error updating server + Hiba a kiszolgáló frissítésekor alert title @@ -3204,6 +3263,7 @@ Ez az Ön egyszer használható hivatkozása! Errors in servers configuration. + Hibák a kiszolgálók konfigurációjában. servers error @@ -3410,6 +3470,7 @@ Ez az Ön egyszer használható hivatkozása! For chat profile %@: + A(z) %@ nevű csevegési profilhoz: servers error @@ -3419,14 +3480,17 @@ Ez az Ön egyszer használható hivatkozása! For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + Ha például az ismerőse a SimpleX Chat kiszolgálón keresztül fogadja az üzeneteket, az Ön alkalmazása a Flux egyik kiszolgálóját használja a kézbesítéshez. No comment provided by engineer. For private routing + A privát útválasztáshoz No comment provided by engineer. For social media + A közösségi médiához No comment provided by engineer. @@ -3740,10 +3804,12 @@ Hiba: %2$@ How it affects privacy + Hogyan érinti az adatvédelmet No comment provided by engineer. How it helps privacy + Hogyan segíti az adatvédelmet No comment provided by engineer. @@ -3972,7 +4038,7 @@ További fejlesztések hamarosan! Interface - Felület + Kezelőfelület No comment provided by engineer. @@ -4062,7 +4128,7 @@ További fejlesztések hamarosan! It allows having many anonymous connections without any shared data between them in a single chat profile. - Lehetővé teszi, hogy egyetlen csevegőprofilon belül több anonim kapcsolat legyen, anélkül, hogy megosztott adatok lennének közöttük. + Lehetővé teszi, hogy egyetlen csevegőprofilon belül több névtelen kapcsolat legyen, anélkül, hogy megosztott adatok lennének közöttük. No comment provided by engineer. @@ -4565,6 +4631,7 @@ Ez az Ön hivatkozása a(z) %@ nevű csoporthoz! More reliable notifications + Megbízhatóbb értesítések No comment provided by engineer. @@ -4604,6 +4671,7 @@ Ez az Ön hivatkozása a(z) %@ nevű csoporthoz! Network decentralization + Hálózati decentralizáció No comment provided by engineer. @@ -4618,6 +4686,7 @@ Ez az Ön hivatkozása a(z) %@ nevű csoporthoz! Network operator + Hálózati üzemeltető No comment provided by engineer. @@ -4677,6 +4746,7 @@ Ez az Ön hivatkozása a(z) %@ nevű csoporthoz! New events + Új események notification @@ -4706,6 +4776,7 @@ Ez az Ön hivatkozása a(z) %@ nevű csoporthoz! New server + Új kiszolgáló No comment provided by engineer. @@ -4765,10 +4836,12 @@ Ez az Ön hivatkozása a(z) %@ nevű csoporthoz! No media & file servers. + Nincsenek média- és fájlkiszolgálók. servers error No message servers. + Nincsenek üzenet-kiszolgálók. servers error @@ -4803,18 +4876,22 @@ Ez az Ön hivatkozása a(z) %@ nevű csoporthoz! No servers for private message routing. + Nincsenek kiszolgálók a privát üzenet-útválasztáshoz. servers error No servers to receive files. + Nincsenek fájlfogadó-kiszolgálók. servers error No servers to receive messages. + Nincsenek üzenetfogadó-kiszolgálók. servers error No servers to send files. + Nincsenek fájlküldő-kiszolgálók. servers error @@ -4849,6 +4926,7 @@ Ez az Ön hivatkozása a(z) %@ nevű csoporthoz! Notifications privacy + Értesítési adatvédelem No comment provided by engineer. @@ -4991,6 +5069,7 @@ VPN engedélyezése szükséges. Open changes + Változások megnyitása No comment provided by engineer. @@ -5005,6 +5084,7 @@ VPN engedélyezése szükséges. Open conditions + Feltételek megnyitása No comment provided by engineer. @@ -5024,10 +5104,12 @@ VPN engedélyezése szükséges. Operator + Üzemeltető No comment provided by engineer. Operator server + Kiszolgáló üzemeltető alert title @@ -5056,6 +5138,7 @@ VPN engedélyezése szükséges. Or to share privately + Vagy a privát megosztáshoz No comment provided by engineer. @@ -5276,6 +5359,7 @@ Hiba: %@ Preset servers + Előre beállított kiszolgálók No comment provided by engineer. @@ -5452,6 +5536,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Push Notifications + Push értesítések No comment provided by engineer. @@ -5827,10 +5912,12 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Review conditions + Feltételek felülvizsgálata No comment provided by engineer. Review later + Felülvizsgálat később No comment provided by engineer. @@ -5880,10 +5967,12 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Same conditions will apply to operator **%@**. + Ugyanezek a feltételek vonatkoznak a következő üzemeltetőre is: **%@**. No comment provided by engineer. Same conditions will apply to operator(s): **%@**. + Ugyanezek a feltételek lesznek elfogadva a következő üzemeltető(k)re is: **%@**. No comment provided by engineer. @@ -6289,6 +6378,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Server added to operator %@. + Kiszolgáló hozzáadva a következő üzemeltetőhöz: %@. alert message @@ -6308,14 +6398,17 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Server operator changed. + A kiszolgáló üzemeltetője megváltozott. alert title Server operators + Kiszolgáló-üzemeltetők No comment provided by engineer. Server protocol changed. + A kiszolgáló-protokoll megváltozott. alert title @@ -6446,10 +6539,12 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Share 1-time link with a friend + Egyszer használható meghívó-hivatkozás megosztása egy baráttal No comment provided by engineer. Share SimpleX address on social media. + SimpleX-cím megosztása a közösségi médiában. No comment provided by engineer. @@ -6459,6 +6554,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Share address publicly + Cím nyilvános megosztása No comment provided by engineer. @@ -6583,10 +6679,12 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. SimpleX address and 1-time links are safe to share via any messenger. + A SimpleX-cím és az egyszer használható meghívó-hivatkozás biztonságosan megosztható bármilyen üzenetküldőn keresztül. No comment provided by engineer. SimpleX address or 1-time link? + SimpleX-cím vagy egyszer használható meghívó-hivatkozás? No comment provided by engineer. @@ -6621,12 +6719,12 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. SimpleX one-time invitation - Egyszer használható SimpleX-meghívó + Egyszer használható SimpleX-meghívó-hivatkozás simplex link type SimpleX protocols reviewed by Trail of Bits. - A SimpleX Chat biztonsága a Trail of Bits által lett újraauditálva. + A SimpleX Chat biztonsága a Trail of Bits által lett felülvizsgálva. No comment provided by engineer. @@ -6682,6 +6780,8 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Some servers failed the test: %@ + Néhány kiszolgáló megbukott a teszten: +%@ alert message @@ -6811,7 +6911,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Switch chat profile for 1-time invitations. - Csevegési profilváltás az egyszer használható meghívókhoz. + Csevegési profilváltás az egyszer használható meghívó-hivatkozásokhoz. No comment provided by engineer. @@ -6952,6 +7052,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. The app protects your privacy by using different operators in each conversation. + Az alkalmazás úgy védi az adatait, hogy minden egyes beszélgetésben más-más üzemeltetőket használ. No comment provided by engineer. @@ -6971,6 +7072,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. The connection reached the limit of undelivered messages, your contact may be offline. + A kapcsolat elérte a kézbesítetlen üzenetek számának határát, az Ön ismerőse lehet, hogy offline állapotban van. No comment provided by engineer. @@ -7035,6 +7137,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. The second preset operator in the app! + A második előre beállított üzemeltető az alkalmazásban! No comment provided by engineer. @@ -7054,6 +7157,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. The servers for new files of your current chat profile **%@**. + Az Ön jelenlegi **%@** nevű csevegőprofiljához tartozó új fájlok kiszolgálói. No comment provided by engineer. @@ -7073,6 +7177,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. These conditions will also apply for: **%@**. + Ezek a feltételek lesznek elfogadva a következő számára is: **%@**. No comment provided by engineer. @@ -7137,7 +7242,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. This is your own one-time link! - Ez az Ön egyszer használható hivatkozása! + Ez az Ön egyszer használható meghívó-hivatkozása! No comment provided by engineer. @@ -7177,6 +7282,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. To protect against your link being replaced, you can compare contact security codes. + A hivatkozás cseréje elleni védelem érdekében összehasonlíthatja a biztonsági kódokat az ismerősével. No comment provided by engineer. @@ -7203,6 +7309,7 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll To receive + A fogadáshoz No comment provided by engineer. @@ -7227,6 +7334,7 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll To send + A küldéshez No comment provided by engineer. @@ -7236,6 +7344,7 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll To use the servers of **%@**, accept conditions of use. + A(z) **%@** kiszolgálóinak használatához fogadja el a használati feltételeket. No comment provided by engineer. @@ -7330,6 +7439,7 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll Undelivered messages + Kézbesítetlen üzenetek No comment provided by engineer. @@ -7491,6 +7601,7 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc Use %@ + %@ használata No comment provided by engineer. @@ -7520,10 +7631,12 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc Use for files + Használat a fájlokhoz No comment provided by engineer. Use for messages + Használat az üzenetekhez No comment provided by engineer. @@ -7538,7 +7651,7 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc Use iOS call interface - Az iOS hívófelület használata + Az iOS hívási felületét használata No comment provided by engineer. @@ -7568,6 +7681,7 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc Use servers + Kiszolgálók használata No comment provided by engineer. @@ -7662,6 +7776,7 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc View conditions + Feltételek megtekintése No comment provided by engineer. @@ -7671,6 +7786,7 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc View updated conditions + Frissített feltételek megtekintése No comment provided by engineer. @@ -7785,6 +7901,7 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc When more than one operator is enabled, none of them has metadata to learn who communicates with whom. + Amikor egynél több hálózati üzemeltető van engedélyezve, egyikük sem rendelkezik olyan metaadatokkal ahhoz, hogy felderítse, ki kommunikál kivel. No comment provided by engineer. @@ -7882,6 +7999,10 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc Ön már kapcsolódva van ehhez: %@. No comment provided by engineer. + + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. Már folyamatban van a kapcsolódás ehhez: %@. @@ -7889,7 +8010,7 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc You are already connecting via this one-time link! - A kapcsolódás már folyamatban van ezen az egyszer használható hivatkozáson keresztül! + A kapcsolódás már folyamatban van ezen az egyszer használható meghívó-hivatkozáson keresztül! No comment provided by engineer. @@ -7946,10 +8067,12 @@ Csatlakozáskérés megismétlése? You can configure operators in Network & servers settings. + Az üzemeltetőket a „Hálózat és kiszolgálók” beállításaban konfigurálhatja. No comment provided by engineer. You can configure servers via settings. + A kiszolgálókat a beállításokon keresztül konfigurálhatja. No comment provided by engineer. @@ -7994,6 +8117,7 @@ Csatlakozáskérés megismétlése? You can set connection name, to remember who the link was shared with. + Beállíthatja az ismerős nevét, hogy emlékezzen arra, hogy kivel osztotta meg a hivatkozást. No comment provided by engineer. @@ -8295,6 +8419,7 @@ Kapcsolatkérés megismétlése? Your servers + Az Ön kiszolgálói No comment provided by engineer. @@ -8719,6 +8844,7 @@ Kapcsolatkérés megismétlése? for better metadata privacy. + a metaadatok jobb védelme érdekében. No comment provided by engineer. @@ -8768,7 +8894,7 @@ Kapcsolatkérés megismétlése? incognito via one-time link - inkognitó egy egyszer használható hivatkozáson keresztül + inkognitó egy egyszer használható meghívó-hivatkozáson keresztül chat list item description @@ -8903,7 +9029,7 @@ Kapcsolatkérés megismétlése? new message - Rejtett üzenet + új üzenet notification @@ -9159,7 +9285,7 @@ utoljára fogadott üzenet: %2$@ via one-time link - egyszer használható hivatkozáson keresztül + egyszer használható meghívó-hivatkozáson keresztül chat list item description @@ -9259,12 +9385,12 @@ utoljára fogadott üzenet: %2$@ you shared one-time link - egyszer használható hivatkozást osztott meg + Ön egy egyszer használható meghívó-hivatkozást osztott meg chat list item description you shared one-time link incognito - egyszer használható hivatkozást osztott meg inkognitóban + Ön egy egyszer használható meghívó-hivatkozást osztott meg inkognitóban chat list item description @@ -9350,22 +9476,27 @@ utoljára fogadott üzenet: %2$@ %d new events + %d új esemény notification body From: %@ + Tőle: %@ notification body New events + Új események notification New messages + Új üzenetek notification New messages in %d chats + Új üzenetek %d csevegésben notification body diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 55eee758d5..8e54ba40dd 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -114,10 +114,12 @@ %@ server + %@ server No comment provided by engineer. %@ servers + %@ server No comment provided by engineer. @@ -382,6 +384,7 @@ **Scan / Paste link**: to connect via a link you received. + **Scansiona / Incolla link**: per connetterti tramite un link che hai ricevuto. No comment provided by engineer. @@ -492,10 +495,12 @@ 1-time link + Link una tantum No comment provided by engineer. 1-time link can be used *with one contact only* - share in person or via any messenger. + Il link una tantum può essere usato *con un solo contatto* - condividilo di persona o tramite qualsiasi messenger. No comment provided by engineer. @@ -586,6 +591,7 @@ Accept conditions + Accetta le condizioni No comment provided by engineer. @@ -606,6 +612,7 @@ Accepted conditions + Condizioni accettate No comment provided by engineer. @@ -628,6 +635,10 @@ Aggiungi l'indirizzo al tuo profilo, in modo che i tuoi contatti possano condividerlo con altre persone. L'aggiornamento del profilo verrà inviato ai tuoi contatti. No comment provided by engineer. + + Add friends + No comment provided by engineer. + Add profile Aggiungi profilo @@ -643,6 +654,10 @@ Aggiungi server scansionando codici QR. No comment provided by engineer. + + Add team members + No comment provided by engineer. + Add to another device Aggiungi ad un altro dispositivo @@ -653,12 +668,18 @@ Aggiungi messaggio di benvenuto No comment provided by engineer. + + Add your team members to the conversations. + No comment provided by engineer. + Added media & file servers + Server di multimediali e file aggiunti No comment provided by engineer. Added message servers + Server dei messaggi aggiunti No comment provided by engineer. @@ -688,10 +709,12 @@ Address or 1-time link? + Indirizzo o link una tantum? No comment provided by engineer. Address settings + Impostazioni dell'indirizzo No comment provided by engineer. @@ -741,6 +764,7 @@ All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + Tutti i messaggi e i file vengono inviati **crittografati end-to-end**, con sicurezza resistenti alla quantistica nei messaggi diretti. No comment provided by engineer. @@ -1218,6 +1242,10 @@ Bulgaro, finlandese, tailandese e ucraino - grazie agli utenti e a [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! No comment provided by engineer. + + Business address + 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 profilo di chat (predefinito) o [per connessione](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). @@ -1357,8 +1385,17 @@ Change user profiles + Modifica profili utente authentication reason + + Chat already exists + No comment provided by engineer. + + + Chat already exists! + No comment provided by engineer. + Chat colors Colori della chat @@ -1441,10 +1478,12 @@ Check messages every 20 min. + Controlla i messaggi ogni 20 min. No comment provided by engineer. Check messages when allowed. + Controlla i messaggi quando consentito. No comment provided by engineer. @@ -1539,38 +1578,47 @@ Conditions accepted on: %@. + Condizioni accettate il: %@. No comment provided by engineer. Conditions are accepted for the operator(s): **%@**. + Le condizioni sono state accettate per gli operatori: **%@**. No comment provided by engineer. Conditions are already accepted for following operator(s): **%@**. + Le condizioni sono già state accettate per i seguenti operatori: **%@**. No comment provided by engineer. Conditions of use + Condizioni d'uso No comment provided by engineer. Conditions will be accepted for enabled operators after 30 days. + Le condizioni verranno accettate per gli operatori attivati dopo 30 giorni. No comment provided by engineer. Conditions will be accepted for operator(s): **%@**. + Le condizioni verranno accettate per gli operatori: **%@**. No comment provided by engineer. Conditions will be accepted for the operator(s): **%@**. + Le condizioni verranno accettate per gli operatori: **%@**. No comment provided by engineer. Conditions will be accepted on: %@. + Le condizioni verranno accettate il: %@. No comment provided by engineer. Conditions will be automatically accepted for enabled operators on: %@. + Le condizioni verranno accettate automaticamente per gli operatori attivi il: %@. No comment provided by engineer. @@ -1769,6 +1817,7 @@ Questo è il tuo link una tantum! Connection security + Sicurezza della connessione No comment provided by engineer. @@ -1888,6 +1937,7 @@ Questo è il tuo link una tantum! Create 1-time link + Crea link una tantum No comment provided by engineer. @@ -1977,6 +2027,7 @@ Questo è il tuo link una tantum! Current conditions text couldn't be loaded, you can review conditions via this link: + Il testo delle condizioni attuali testo non è stato caricato, puoi consultare le condizioni tramite questo link: No comment provided by engineer. @@ -2346,6 +2397,7 @@ Questo è il tuo link una tantum! Delivered even when Apple drops them. + Consegnati anche quando Apple li scarta. No comment provided by engineer. @@ -2631,6 +2683,7 @@ Questo è il tuo link una tantum! E2E encrypted notifications. + Notifiche crittografate E2E. No comment provided by engineer. @@ -2655,6 +2708,7 @@ Questo è il tuo link una tantum! Enable Flux + Attiva Flux No comment provided by engineer. @@ -2864,6 +2918,7 @@ Questo è il tuo link una tantum! Error accepting conditions + Errore di accettazione delle condizioni alert title @@ -2878,6 +2933,7 @@ Questo è il tuo link una tantum! Error adding server + Errore di aggiunta del server alert title @@ -3022,6 +3078,7 @@ Questo è il tuo link una tantum! Error loading servers + Errore nel caricamento dei server alert title @@ -3081,6 +3138,7 @@ Questo è il tuo link una tantum! Error saving servers + Errore di salvataggio dei server alert title @@ -3155,6 +3213,7 @@ Questo è il tuo link una tantum! Error updating server + Errore di aggiornamento del server alert title @@ -3204,6 +3263,7 @@ Questo è il tuo link una tantum! Errors in servers configuration. + Errori nella configurazione dei server. servers error @@ -3410,6 +3470,7 @@ Questo è il tuo link una tantum! For chat profile %@: + Per il profilo di chat %@: servers error @@ -3419,14 +3480,17 @@ Questo è il tuo link una tantum! For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + Ad esempio, se il tuo contatto riceve messaggi tramite un server di SimpleX Chat, la tua app li consegnerà tramite un server Flux. No comment provided by engineer. For private routing + Per l'instradamento privato No comment provided by engineer. For social media + Per i social media No comment provided by engineer. @@ -3740,10 +3804,12 @@ Errore: %2$@ How it affects privacy + Come influisce sulla privacy No comment provided by engineer. How it helps privacy + Come aiuta la privacy No comment provided by engineer. @@ -4565,6 +4631,7 @@ Questo è il tuo link per il gruppo %@! More reliable notifications + Notifiche più affidabili No comment provided by engineer. @@ -4604,6 +4671,7 @@ Questo è il tuo link per il gruppo %@! Network decentralization + Decentralizzazione della rete No comment provided by engineer. @@ -4618,6 +4686,7 @@ Questo è il tuo link per il gruppo %@! Network operator + Operatore di rete No comment provided by engineer. @@ -4677,6 +4746,7 @@ Questo è il tuo link per il gruppo %@! New events + Nuovi eventi notification @@ -4706,6 +4776,7 @@ Questo è il tuo link per il gruppo %@! New server + Nuovo server No comment provided by engineer. @@ -4765,10 +4836,12 @@ Questo è il tuo link per il gruppo %@! No media & file servers. + Nessun server di multimediali e file. servers error No message servers. + Nessun server dei messaggi. servers error @@ -4803,18 +4876,22 @@ Questo è il tuo link per il gruppo %@! No servers for private message routing. + Nessun server per l'instradamento dei messaggi privati. servers error No servers to receive files. + Nessun server per ricevere file. servers error No servers to receive messages. + Nessun server per ricevere messaggi. servers error No servers to send files. + Nessun server per inviare file. servers error @@ -4849,6 +4926,7 @@ Questo è il tuo link per il gruppo %@! Notifications privacy + Privacy delle notifiche No comment provided by engineer. @@ -4991,6 +5069,7 @@ Richiede l'attivazione della VPN. Open changes + Apri le modifiche No comment provided by engineer. @@ -5005,6 +5084,7 @@ Richiede l'attivazione della VPN. Open conditions + Apri le condizioni No comment provided by engineer. @@ -5024,10 +5104,12 @@ Richiede l'attivazione della VPN. Operator + Operatore No comment provided by engineer. Operator server + Server dell'operatore alert title @@ -5056,6 +5138,7 @@ Richiede l'attivazione della VPN. Or to share privately + O per condividere in modo privato No comment provided by engineer. @@ -5276,6 +5359,7 @@ Errore: %@ Preset servers + Server preimpostati No comment provided by engineer. @@ -5452,6 +5536,7 @@ Attivalo nelle impostazioni *Rete e server*. Push Notifications + Notifiche push No comment provided by engineer. @@ -5827,10 +5912,12 @@ Attivalo nelle impostazioni *Rete e server*. Review conditions + Esamina le condizioni No comment provided by engineer. Review later + Esamina più tardi No comment provided by engineer. @@ -5880,10 +5967,12 @@ Attivalo nelle impostazioni *Rete e server*. Same conditions will apply to operator **%@**. + Le stesse condizioni si applicheranno all'operatore **%@**. No comment provided by engineer. Same conditions will apply to operator(s): **%@**. + Le stesse condizioni si applicheranno agli operatori **%@**. No comment provided by engineer. @@ -6289,6 +6378,7 @@ Attivalo nelle impostazioni *Rete e server*. Server added to operator %@. + Server aggiunto all'operatore %@. alert message @@ -6308,14 +6398,17 @@ Attivalo nelle impostazioni *Rete e server*. Server operator changed. + L'operatore del server è cambiato. alert title Server operators + Operatori server No comment provided by engineer. Server protocol changed. + Il protocollo del server è cambiato. alert title @@ -6446,10 +6539,12 @@ Attivalo nelle impostazioni *Rete e server*. Share 1-time link with a friend + Condividi link una tantum con un amico No comment provided by engineer. Share SimpleX address on social media. + Condividi indirizzo SimpleX sui social media. No comment provided by engineer. @@ -6459,6 +6554,7 @@ Attivalo nelle impostazioni *Rete e server*. Share address publicly + Condividi indirizzo pubblicamente No comment provided by engineer. @@ -6583,10 +6679,12 @@ Attivalo nelle impostazioni *Rete e server*. SimpleX address and 1-time links are safe to share via any messenger. + L'indirizzo SimpleX e i link una tantum sono sicuri da condividere tramite qualsiasi messenger. No comment provided by engineer. SimpleX address or 1-time link? + Indirizzo SimpleX o link una tantum? No comment provided by engineer. @@ -6682,6 +6780,8 @@ Attivalo nelle impostazioni *Rete e server*. Some servers failed the test: %@ + Alcuni server hanno fallito il test: +%@ alert message @@ -6952,6 +7052,7 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa. The app protects your privacy by using different operators in each conversation. + L'app protegge la tua privacy usando diversi operatori in ogni conversazione. No comment provided by engineer. @@ -6971,6 +7072,7 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa. The connection reached the limit of undelivered messages, your contact may be offline. + La connessione ha raggiunto il limite di messaggi non consegnati, il contatto potrebbe essere offline. No comment provided by engineer. @@ -7035,6 +7137,7 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa. The second preset operator in the app! + Il secondo operatore preimpostato nell'app! No comment provided by engineer. @@ -7054,6 +7157,7 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa. The servers for new files of your current chat profile **%@**. + I server per nuovi file del tuo profilo di chat attuale **%@**. No comment provided by engineer. @@ -7073,6 +7177,7 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa. These conditions will also apply for: **%@**. + Queste condizioni si applicheranno anche per: **%@**. No comment provided by engineer. @@ -7177,6 +7282,7 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa. To protect against your link being replaced, you can compare contact security codes. + Per proteggerti dalla sostituzione del tuo link, puoi confrontare i codici di sicurezza del contatto. No comment provided by engineer. @@ -7203,6 +7309,7 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio To receive + Per ricevere No comment provided by engineer. @@ -7227,6 +7334,7 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio To send + Per inviare No comment provided by engineer. @@ -7236,6 +7344,7 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio To use the servers of **%@**, accept conditions of use. + Per usare i server di **%@**, accetta le condizioni d'uso. No comment provided by engineer. @@ -7330,6 +7439,7 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio Undelivered messages + Messaggi non consegnati No comment provided by engineer. @@ -7491,6 +7601,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Use %@ + Usa %@ No comment provided by engineer. @@ -7520,10 +7631,12 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Use for files + Usa per i file No comment provided by engineer. Use for messages + Usa per i messaggi No comment provided by engineer. @@ -7568,6 +7681,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Use servers + Usa i server No comment provided by engineer. @@ -7662,6 +7776,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e View conditions + Vedi le condizioni No comment provided by engineer. @@ -7671,6 +7786,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e View updated conditions + Vedi le condizioni aggiornate No comment provided by engineer. @@ -7785,6 +7901,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e When more than one operator is enabled, none of them has metadata to learn who communicates with whom. + Quando più di un operatore è attivato, nessuno di essi ha metadati per scoprire chi comunica con chi. No comment provided by engineer. @@ -7882,6 +7999,10 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Sei già connesso/a a %@. No comment provided by engineer. + + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. Ti stai già connettendo a %@. @@ -7946,10 +8067,12 @@ Ripetere la richiesta di ingresso? You can configure operators in Network & servers settings. + Puoi configurare gli operatori nelle impostazioni di rete e server. No comment provided by engineer. You can configure servers via settings. + Puoi configurare i server nelle impostazioni. No comment provided by engineer. @@ -7994,6 +8117,7 @@ Ripetere la richiesta di ingresso? You can set connection name, to remember who the link was shared with. + Puoi impostare il nome della connessione per ricordare con chi è stato condiviso il link. No comment provided by engineer. @@ -8295,6 +8419,7 @@ Ripetere la richiesta di connessione? Your servers + I tuoi server No comment provided by engineer. @@ -8719,6 +8844,7 @@ Ripetere la richiesta di connessione? for better metadata privacy. + per una migliore privacy dei metadati. No comment provided by engineer. @@ -9350,22 +9476,27 @@ ultimo msg ricevuto: %2$@ %d new events + %d nuovi eventi notification body From: %@ + Da: %@ notification body New events + Nuovi eventi notification New messages + Nuovi messaggi notification New messages in %d chats + Nuovi messaggi in %d chat notification body diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 6b833800c0..1a5ffdd680 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -612,6 +612,10 @@ プロフィールにアドレスを追加し、連絡先があなたのアドレスを他の人と共有できるようにします。プロフィールの更新は連絡先に送信されます。 No comment provided by engineer. + + Add friends + No comment provided by engineer. + Add profile プロフィールを追加 @@ -627,6 +631,10 @@ QRコードでサーバを追加する。 No comment provided by engineer. + + Add team members + No comment provided by engineer. + Add to another device 別の端末に追加 @@ -637,6 +645,10 @@ ウェルカムメッセージを追加 No comment provided by engineer. + + Add your team members to the conversations. + No comment provided by engineer. + Added media & file servers No comment provided by engineer. @@ -1162,6 +1174,10 @@ ブルガリア語、フィンランド語、タイ語、ウクライナ語 - ユーザーと [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)に感謝します! No comment provided by engineer. + + Business address + 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). チャット プロファイル経由 (デフォルト) または [接続経由](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). @@ -1294,6 +1310,14 @@ Change user profiles authentication reason + + Chat already exists + No comment provided by engineer. + + + Chat already exists! + No comment provided by engineer. + Chat colors No comment provided by engineer. @@ -1360,6 +1384,7 @@ Chat theme + チャットテーマ No comment provided by engineer. @@ -1401,10 +1426,12 @@ Chunks deleted + チャンクが削除されました No comment provided by engineer. Chunks downloaded + チャンクがダウンロードされました No comment provided by engineer. @@ -1428,6 +1455,7 @@ Clear private notes? + プライベートノートを消しますか? No comment provided by engineer. @@ -1441,6 +1469,7 @@ Color mode + 色設定 No comment provided by engineer. @@ -1455,6 +1484,7 @@ Completed + 完了 No comment provided by engineer. @@ -1559,10 +1589,12 @@ Connect to desktop + デスクトップに接続 No comment provided by engineer. Connect to your friends faster. + 友達ともっと速くつながりましょう。 No comment provided by engineer. @@ -1599,22 +1631,27 @@ This is your own one-time link! Connected + 接続中 No comment provided by engineer. Connected desktop + デスクトップに接続済 No comment provided by engineer. Connected servers + 接続中のサーバ No comment provided by engineer. Connected to desktop + デスクトップに接続済 No comment provided by engineer. Connecting + 接続待ち No comment provided by engineer. @@ -1629,10 +1666,12 @@ This is your own one-time link! Connecting to contact, please wait or check later! + 連絡先に接続中です。しばらくお待ちいただくか、後で確認してください! No comment provided by engineer. Connecting to desktop + デスクトップに接続中 No comment provided by engineer. @@ -1642,6 +1681,7 @@ This is your own one-time link! Connection and servers status. + 接続とサーバーのステータス No comment provided by engineer. @@ -1669,6 +1709,7 @@ This is your own one-time link! Connection terminated + 接続停止 No comment provided by engineer. @@ -1882,6 +1923,7 @@ This is your own one-time link! Customize theme + カスタムテーマ No comment provided by engineer. @@ -1891,6 +1933,7 @@ This is your own one-time link! Dark mode colors + ダークモードカラー No comment provided by engineer. @@ -1993,6 +2036,7 @@ This is your own one-time link! Debug delivery + 配信のデバッグ No comment provided by engineer. @@ -2241,6 +2285,7 @@ This is your own one-time link! Desktop devices + デスクトップ機器 No comment provided by engineer. @@ -2270,6 +2315,7 @@ This is your own one-time link! Developer options + 開発者向けの設定 No comment provided by engineer. @@ -7343,6 +7389,10 @@ To connect, please ask your contact to create another connection link and check すでに %@ に接続されています。 No comment provided by engineer. + + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 603db9d75a..50b3fdae3e 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -114,10 +114,12 @@ %@ server + %@ server No comment provided by engineer. %@ servers + %@ servers No comment provided by engineer. @@ -382,6 +384,7 @@ **Scan / Paste link**: to connect via a link you received. + **Link scannen/plakken**: om verbinding te maken via een link die u hebt ontvangen. No comment provided by engineer. @@ -492,10 +495,12 @@ 1-time link + Eenmalige link No comment provided by engineer. 1-time link can be used *with one contact only* - share in person or via any messenger. + Eenmalige link die *slechts met één contactpersoon* kan worden gebruikt - deel persoonlijk of via een messenger. No comment provided by engineer. @@ -586,6 +591,7 @@ Accept conditions + Accepteer voorwaarden No comment provided by engineer. @@ -606,6 +612,7 @@ Accepted conditions + Geaccepteerde voorwaarden No comment provided by engineer. @@ -628,6 +635,10 @@ Voeg een adres toe aan uw profiel, zodat uw contacten het met andere mensen kunnen delen. Profiel update wordt naar uw contacten verzonden. No comment provided by engineer. + + Add friends + No comment provided by engineer. + Add profile Profiel toevoegen @@ -643,6 +654,10 @@ Servers toevoegen door QR-codes te scannen. No comment provided by engineer. + + Add team members + No comment provided by engineer. + Add to another device Toevoegen aan een ander apparaat @@ -653,12 +668,18 @@ Welkom bericht toevoegen No comment provided by engineer. + + Add your team members to the conversations. + No comment provided by engineer. + Added media & file servers + Media- en bestandsservers toegevoegd No comment provided by engineer. Added message servers + Berichtservers toegevoegd No comment provided by engineer. @@ -688,10 +709,12 @@ Address or 1-time link? + Adres of eenmalige link? No comment provided by engineer. Address settings + Adres instellingen No comment provided by engineer. @@ -741,6 +764,7 @@ All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + Alle berichten en bestanden worden **end-to-end versleuteld** verzonden, met post-quantumbeveiliging in directe berichten. No comment provided by engineer. @@ -1218,6 +1242,10 @@ Bulgaars, Fins, Thais en Oekraïens - dankzij de gebruikers en [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! No comment provided by engineer. + + Business address + 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). Via chatprofiel (standaard) of [via verbinding](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). @@ -1357,8 +1385,17 @@ Change user profiles + Gebruikersprofielen wijzigen authentication reason + + Chat already exists + No comment provided by engineer. + + + Chat already exists! + No comment provided by engineer. + Chat colors Chat kleuren @@ -1441,10 +1478,12 @@ Check messages every 20 min. + Controleer uw berichten elke 20 minuten. No comment provided by engineer. Check messages when allowed. + Controleer berichten indien toegestaan. No comment provided by engineer. @@ -1539,38 +1578,47 @@ Conditions accepted on: %@. + Voorwaarden geaccepteerd op: %@. No comment provided by engineer. Conditions are accepted for the operator(s): **%@**. + Voorwaarden worden geaccepteerd voor de operator(s): **%@**. No comment provided by engineer. Conditions are already accepted for following operator(s): **%@**. + Voorwaarden zijn reeds geaccepteerd voor de volgende operator(s): **%@**. No comment provided by engineer. Conditions of use + Gebruiksvoorwaarden No comment provided by engineer. Conditions will be accepted for enabled operators after 30 days. + Voor ingeschakelde operators worden de voorwaarden na 30 dagen geaccepteerd. No comment provided by engineer. Conditions will be accepted for operator(s): **%@**. + Voorwaarden worden geaccepteerd voor operator(s): **%@**. No comment provided by engineer. Conditions will be accepted for the operator(s): **%@**. + Voorwaarden worden geaccepteerd voor de operator(s): **%@**. No comment provided by engineer. Conditions will be accepted on: %@. + Voorwaarden worden geaccepteerd op: %@. No comment provided by engineer. Conditions will be automatically accepted for enabled operators on: %@. + Voorwaarden worden automatisch geaccepteerd voor ingeschakelde operators op: %@. No comment provided by engineer. @@ -1769,6 +1817,7 @@ Dit is uw eigen eenmalige link! Connection security + Beveiliging van de verbinding No comment provided by engineer. @@ -1888,6 +1937,7 @@ Dit is uw eigen eenmalige link! Create 1-time link + Eenmalige link maken No comment provided by engineer. @@ -1977,6 +2027,7 @@ Dit is uw eigen eenmalige link! Current conditions text couldn't be loaded, you can review conditions via this link: + De tekst van de huidige voorwaarden kon niet worden geladen. U kunt de voorwaarden bekijken via deze link: No comment provided by engineer. @@ -2346,6 +2397,7 @@ Dit is uw eigen eenmalige link! Delivered even when Apple drops them. + Geleverd ook als Apple ze verliest No comment provided by engineer. @@ -2631,6 +2683,7 @@ Dit is uw eigen eenmalige link! E2E encrypted notifications. + E2E versleutelde meldingen. No comment provided by engineer. @@ -2655,6 +2708,7 @@ Dit is uw eigen eenmalige link! Enable Flux + Flux inschakelen No comment provided by engineer. @@ -2864,6 +2918,7 @@ Dit is uw eigen eenmalige link! Error accepting conditions + Fout bij het accepteren van voorwaarden alert title @@ -2878,6 +2933,7 @@ Dit is uw eigen eenmalige link! Error adding server + Fout bij toevoegen server alert title @@ -3022,6 +3078,7 @@ Dit is uw eigen eenmalige link! Error loading servers + Fout bij het laden van servers alert title @@ -3081,6 +3138,7 @@ Dit is uw eigen eenmalige link! Error saving servers + Fout bij het opslaan van servers alert title @@ -3155,6 +3213,7 @@ Dit is uw eigen eenmalige link! Error updating server + Fout bij het updaten van de server alert title @@ -3204,6 +3263,7 @@ Dit is uw eigen eenmalige link! Errors in servers configuration. + Fouten in de serverconfiguratie. servers error @@ -3410,6 +3470,7 @@ Dit is uw eigen eenmalige link! For chat profile %@: + Voor chatprofiel %@: servers error @@ -3419,14 +3480,17 @@ Dit is uw eigen eenmalige link! For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + Als uw contactpersoon bijvoorbeeld berichten ontvangt via een SimpleX Chat-server, worden deze door uw app via een Flux-server verzonden. No comment provided by engineer. For private routing + Voor privé-routering No comment provided by engineer. For social media + Voor social media No comment provided by engineer. @@ -3740,10 +3804,12 @@ Fout: %2$@ How it affects privacy + Hoe het de privacy beïnvloedt No comment provided by engineer. How it helps privacy + Hoe het de privacy helpt No comment provided by engineer. @@ -4565,6 +4631,7 @@ Dit is jouw link voor groep %@! More reliable notifications + Betrouwbaardere meldingen No comment provided by engineer. @@ -4604,6 +4671,7 @@ Dit is jouw link voor groep %@! Network decentralization + Netwerk decentralisatie No comment provided by engineer. @@ -4618,6 +4686,7 @@ Dit is jouw link voor groep %@! Network operator + Netwerkbeheerder No comment provided by engineer. @@ -4677,6 +4746,7 @@ Dit is jouw link voor groep %@! New events + Nieuwe gebeurtenissen notification @@ -4706,6 +4776,7 @@ Dit is jouw link voor groep %@! New server + Nieuwe server No comment provided by engineer. @@ -4765,10 +4836,12 @@ Dit is jouw link voor groep %@! No media & file servers. + Geen media- en bestandsservers. servers error No message servers. + Geen berichtenservers. servers error @@ -4803,18 +4876,22 @@ Dit is jouw link voor groep %@! No servers for private message routing. + Geen servers voor het routeren van privéberichten. servers error No servers to receive files. + Geen servers om bestanden te ontvangen. servers error No servers to receive messages. + Geen servers om berichten te ontvangen. servers error No servers to send files. + Geen servers om bestanden te verzenden. servers error @@ -4849,6 +4926,7 @@ Dit is jouw link voor groep %@! Notifications privacy + Privacy van meldingen No comment provided by engineer. @@ -4991,6 +5069,7 @@ Vereist het inschakelen van VPN. Open changes + Wijzigingen openen No comment provided by engineer. @@ -5005,6 +5084,7 @@ Vereist het inschakelen van VPN. Open conditions + Open voorwaarden No comment provided by engineer. @@ -5024,10 +5104,12 @@ Vereist het inschakelen van VPN. Operator + Operator No comment provided by engineer. Operator server + Operatorserver alert title @@ -5056,6 +5138,7 @@ Vereist het inschakelen van VPN. Or to share privately + Of om privé te delen No comment provided by engineer. @@ -5276,6 +5359,7 @@ Fout: %@ Preset servers + Vooraf ingestelde servers No comment provided by engineer. @@ -5452,6 +5536,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Push Notifications + Pushmeldingen No comment provided by engineer. @@ -5827,10 +5912,12 @@ Schakel dit in in *Netwerk en servers*-instellingen. Review conditions + Voorwaarden bekijken No comment provided by engineer. Review later + Later beoordelen No comment provided by engineer. @@ -5880,10 +5967,12 @@ Schakel dit in in *Netwerk en servers*-instellingen. Same conditions will apply to operator **%@**. + Dezelfde voorwaarden gelden voor operator **%@**. No comment provided by engineer. Same conditions will apply to operator(s): **%@**. + Dezelfde voorwaarden gelden voor operator(s): **%@**. No comment provided by engineer. @@ -6289,6 +6378,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Server added to operator %@. + Server toegevoegd aan operator %@. alert message @@ -6308,14 +6398,17 @@ Schakel dit in in *Netwerk en servers*-instellingen. Server operator changed. + Serveroperator gewijzigd. alert title Server operators + Serverbeheerders No comment provided by engineer. Server protocol changed. + Serverprotocol gewijzigd. alert title @@ -6446,10 +6539,12 @@ Schakel dit in in *Netwerk en servers*-instellingen. Share 1-time link with a friend + Deel eenmalig een link met een vriend No comment provided by engineer. Share SimpleX address on social media. + Deel het SimpleX-adres op sociale media. No comment provided by engineer. @@ -6459,6 +6554,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Share address publicly + Adres openbaar delen No comment provided by engineer. @@ -6583,10 +6679,12 @@ Schakel dit in in *Netwerk en servers*-instellingen. SimpleX address and 1-time links are safe to share via any messenger. + SimpleX-adressen en eenmalige links kunnen veilig worden gedeeld via elke messenger. No comment provided by engineer. SimpleX address or 1-time link? + SimpleX adres of eenmalige link? No comment provided by engineer. @@ -6682,6 +6780,8 @@ Schakel dit in in *Netwerk en servers*-instellingen. Some servers failed the test: %@ + Sommige servers zijn niet geslaagd voor de test: +%@ alert message @@ -6952,6 +7052,7 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. The app protects your privacy by using different operators in each conversation. + De app beschermt uw privacy door in elk gesprek andere operatoren te gebruiken. No comment provided by engineer. @@ -6971,6 +7072,7 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. The connection reached the limit of undelivered messages, your contact may be offline. + De verbinding heeft de limiet van niet-afgeleverde berichten bereikt. Uw contactpersoon is mogelijk offline. No comment provided by engineer. @@ -7035,6 +7137,7 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. The second preset operator in the app! + De tweede vooraf ingestelde operator in de app! No comment provided by engineer. @@ -7054,6 +7157,7 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. The servers for new files of your current chat profile **%@**. + De servers voor nieuwe bestanden van uw huidige chatprofiel **%@**. No comment provided by engineer. @@ -7073,6 +7177,7 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. These conditions will also apply for: **%@**. + Deze voorwaarden zijn ook van toepassing op: **%@**. No comment provided by engineer. @@ -7177,6 +7282,7 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. To protect against your link being replaced, you can compare contact security codes. + Om te voorkomen dat uw link wordt vervangen, kunt u contactbeveiligingscodes vergelijken. No comment provided by engineer. @@ -7203,6 +7309,7 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc To receive + Om te ontvangen No comment provided by engineer. @@ -7227,6 +7334,7 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc To send + Om te verzenden No comment provided by engineer. @@ -7236,6 +7344,7 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc To use the servers of **%@**, accept conditions of use. + Om de servers van **%@** te gebruiken, moet u de gebruiksvoorwaarden accepteren. No comment provided by engineer. @@ -7330,6 +7439,7 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc Undelivered messages + Niet afgeleverde berichten No comment provided by engineer. @@ -7491,6 +7601,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Use %@ + Gebruik %@ No comment provided by engineer. @@ -7520,10 +7631,12 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Use for files + Gebruik voor bestanden No comment provided by engineer. Use for messages + Gebruik voor berichten No comment provided by engineer. @@ -7568,6 +7681,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Use servers + Gebruik servers No comment provided by engineer. @@ -7662,6 +7776,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak View conditions + Bekijk voorwaarden No comment provided by engineer. @@ -7671,6 +7786,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak View updated conditions + Bekijk de bijgewerkte voorwaarden No comment provided by engineer. @@ -7785,6 +7901,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak When more than one operator is enabled, none of them has metadata to learn who communicates with whom. + Wanneer er meer dan één operator is ingeschakeld, beschikt geen enkele operator over metagegevens om te achterhalen wie met wie communiceert. No comment provided by engineer. @@ -7882,6 +7999,10 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak U bent al verbonden met %@. No comment provided by engineer. + + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. U maakt al verbinding met %@. @@ -7946,10 +8067,12 @@ Deelnameverzoek herhalen? You can configure operators in Network & servers settings. + U kunt operators configureren in Netwerk- en serverinstellingen. No comment provided by engineer. You can configure servers via settings. + U kunt servers configureren via instellingen. No comment provided by engineer. @@ -7994,6 +8117,7 @@ Deelnameverzoek herhalen? You can set connection name, to remember who the link was shared with. + U kunt een verbindingsnaam instellen, zodat u kunt onthouden met wie de link is gedeeld. No comment provided by engineer. @@ -8295,6 +8419,7 @@ Verbindingsverzoek herhalen? Your servers + Uw servers No comment provided by engineer. @@ -8719,6 +8844,7 @@ Verbindingsverzoek herhalen? for better metadata privacy. + voor betere privacy van metagegevens. No comment provided by engineer. @@ -9350,22 +9476,27 @@ laatst ontvangen bericht: %2$@ %d new events + ‐%d nieuwe gebeurtenissen notification body From: %@ + Van: %@ notification body New events + Nieuwe gebeurtenissen notification New messages + Nieuwe berichten notification New messages in %d chats + Nieuwe berichten in %d chats notification body diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 8a772bf470..0095b5e031 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -628,6 +628,10 @@ Dodaj adres do swojego profilu, aby Twoje kontakty mogły go udostępnić innym osobom. Aktualizacja profilu zostanie wysłana do Twoich kontaktów. No comment provided by engineer. + + Add friends + No comment provided by engineer. + Add profile Dodaj profil @@ -643,6 +647,10 @@ Dodaj serwery, skanując kody QR. No comment provided by engineer. + + Add team members + No comment provided by engineer. + Add to another device Dodaj do innego urządzenia @@ -653,6 +661,10 @@ Dodaj wiadomość powitalną No comment provided by engineer. + + Add your team members to the conversations. + No comment provided by engineer. + Added media & file servers No comment provided by engineer. @@ -1213,6 +1225,10 @@ Bułgarski, fiński, tajski i ukraiński – dzięki użytkownikom i [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! No comment provided by engineer. + + Business address + 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). Według profilu czatu (domyślnie) lub [według połączenia](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). @@ -1354,6 +1370,14 @@ Change user profiles authentication reason + + Chat already exists + No comment provided by engineer. + + + Chat already exists! + No comment provided by engineer. + Chat colors Kolory czatu @@ -7869,6 +7893,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Jesteś już połączony z %@. No comment provided by engineer. + + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. Już się łączysz z %@. diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 9b6cbf519e..e7230dbcb2 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -628,6 +628,10 @@ Добавьте адрес в свой профиль, чтобы Ваши контакты могли поделиться им. Профиль будет отправлен Вашим контактам. No comment provided by engineer. + + Add friends + No comment provided by engineer. + Add profile Добавить профиль @@ -643,6 +647,10 @@ Добавить серверы через QR код. No comment provided by engineer. + + Add team members + No comment provided by engineer. + Add to another device Добавить на другое устройство @@ -653,6 +661,10 @@ Добавить приветственное сообщение No comment provided by engineer. + + Add your team members to the conversations. + No comment provided by engineer. + Added media & file servers No comment provided by engineer. @@ -1219,6 +1231,10 @@ Болгарский, финский, тайский и украинский - благодаря пользователям и [Weblate] (https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! No comment provided by engineer. + + Business address + 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). По профилю чата или [по соединению](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (БЕТА). @@ -1360,6 +1376,14 @@ Change user profiles authentication reason + + Chat already exists + No comment provided by engineer. + + + Chat already exists! + No comment provided by engineer. + Chat colors Цвета чата @@ -7882,6 +7906,10 @@ To connect, please ask your contact to create another connection link and check Вы уже соединены с контактом %@. No comment provided by engineer. + + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. Вы уже соединяетесь с %@. diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index 8066daf54d..f3097bf8f2 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -587,6 +587,10 @@ เพิ่มที่อยู่ลงในโปรไฟล์ของคุณ เพื่อให้ผู้ติดต่อของคุณสามารถแชร์กับผู้อื่นได้ การอัปเดตโปรไฟล์จะถูกส่งไปยังผู้ติดต่อของคุณ No comment provided by engineer. + + Add friends + No comment provided by engineer. + Add profile เพิ่มโปรไฟล์ @@ -602,6 +606,10 @@ เพิ่มเซิร์ฟเวอร์โดยการสแกนรหัสคิวอาร์โค้ด No comment provided by engineer. + + Add team members + No comment provided by engineer. + Add to another device เพิ่มเข้าไปในอุปกรณ์อื่น @@ -612,6 +620,10 @@ เพิ่มข้อความต้อนรับ No comment provided by engineer. + + Add your team members to the conversations. + No comment provided by engineer. + Added media & file servers No comment provided by engineer. @@ -1130,6 +1142,10 @@ Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! No comment provided by engineer. + + Business address + 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). ตามโปรไฟล์แชท (ค่าเริ่มต้น) หรือ [โดยการเชื่อมต่อ](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (เบต้า) @@ -1262,6 +1278,14 @@ Change user profiles authentication reason + + Chat already exists + No comment provided by engineer. + + + Chat already exists! + No comment provided by engineer. + Chat colors No comment provided by engineer. @@ -7293,6 +7317,10 @@ To connect, please ask your contact to create another connection link and check คุณได้เชื่อมต่อกับ %@ แล้ว No comment provided by engineer. + + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index f578a6225d..eca1c67b85 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -628,6 +628,10 @@ Kişilerinizin başkalarıyla paylaşabilmesi için profilinize adres ekleyin. Profil güncellemesi kişilerinize gönderilecek. No comment provided by engineer. + + Add friends + No comment provided by engineer. + Add profile Profil ekle @@ -643,6 +647,10 @@ Karekod taratarak sunucuları ekleyin. No comment provided by engineer. + + Add team members + No comment provided by engineer. + Add to another device Başka bir cihaza ekle @@ -653,6 +661,10 @@ Karşılama mesajı ekleyin No comment provided by engineer. + + Add your team members to the conversations. + No comment provided by engineer. + Added media & file servers No comment provided by engineer. @@ -1218,6 +1230,10 @@ Bulgarca, Fince, Tayca ve Ukraynaca - kullanıcılara ve [Weblate] e teşekkürler! (https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! No comment provided by engineer. + + Business address + 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). Sohbet profiline göre (varsayılan) veya [bağlantıya göre](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). @@ -1359,6 +1375,14 @@ Change user profiles authentication reason + + Chat already exists + No comment provided by engineer. + + + Chat already exists! + No comment provided by engineer. + Chat colors Sohbet renkleri @@ -7882,6 +7906,10 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Zaten %@'a bağlısınız. No comment provided by engineer. + + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. Zaten %@'a bağlanıyorsunuz. diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 136f45830b..0d05edbfbe 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -114,10 +114,12 @@ %@ server + %@ сервер No comment provided by engineer. %@ servers + %@ сервери No comment provided by engineer. @@ -132,6 +134,7 @@ %1$@, %2$@ + %1$@, %2$@ format for date separator in chat @@ -171,6 +174,7 @@ %d file(s) were not downloaded. + %d файл(и) не було завантажено. forward confirmation reason @@ -180,6 +184,7 @@ %d messages not forwarded + %d повідомлень не переслано alert title @@ -379,6 +384,7 @@ **Scan / Paste link**: to connect via a link you received. + **Відсканувати / Вставити посилання**: підключитися за отриманим посиланням. No comment provided by engineer. @@ -489,10 +495,12 @@ 1-time link + Одноразове посилання No comment provided by engineer. 1-time link can be used *with one contact only* - share in person or via any messenger. + Одноразове посилання можна використовувати *тільки з одним контактом* - поділіться ним особисто або через будь-який месенджер. No comment provided by engineer. @@ -583,6 +591,7 @@ Accept conditions + Прийняти умови No comment provided by engineer. @@ -603,6 +612,7 @@ Accepted conditions + Прийняті умови No comment provided by engineer. @@ -625,6 +635,10 @@ Додайте адресу до свого профілю, щоб ваші контакти могли поділитися нею з іншими людьми. Повідомлення про оновлення профілю буде надіслано вашим контактам. No comment provided by engineer. + + Add friends + No comment provided by engineer. + Add profile Додати профіль @@ -640,6 +654,10 @@ Додайте сервери, відсканувавши QR-код. No comment provided by engineer. + + Add team members + No comment provided by engineer. + Add to another device Додати до іншого пристрою @@ -650,12 +668,18 @@ Додати вітальне повідомлення No comment provided by engineer. + + Add your team members to the conversations. + No comment provided by engineer. + Added media & file servers + Додано медіа та файлові сервери No comment provided by engineer. Added message servers + Додано сервери повідомлень No comment provided by engineer. @@ -685,10 +709,12 @@ Address or 1-time link? + Адреса чи одноразове посилання? No comment provided by engineer. Address settings + Налаштування адреси No comment provided by engineer. @@ -738,6 +764,7 @@ All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + Всі повідомлення та файли надсилаються **наскрізним шифруванням**, з пост-квантовим захистом у прямих повідомленнях. No comment provided by engineer. @@ -957,6 +984,7 @@ App session + Сесія програми No comment provided by engineer. @@ -1066,6 +1094,7 @@ Auto-accept settings + Автоприйняття налаштувань alert title @@ -1095,6 +1124,7 @@ Better calls + Кращі дзвінки No comment provided by engineer. @@ -1104,6 +1134,7 @@ Better message dates. + Кращі дати повідомлень. No comment provided by engineer. @@ -1118,14 +1149,17 @@ Better notifications + Кращі сповіщення No comment provided by engineer. Better security ✅ + Краща безпека ✅ No comment provided by engineer. Better user experience + Покращений користувацький досвід No comment provided by engineer. @@ -1208,6 +1242,10 @@ Болгарською, фінською, тайською та українською мовами - завдяки користувачам та [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! No comment provided by engineer. + + Business address + 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). Через профіль чату (за замовчуванням) або [за з'єднанням](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). @@ -1347,8 +1385,17 @@ Change user profiles + Зміна профілів користувачів authentication reason + + Chat already exists + No comment provided by engineer. + + + Chat already exists! + No comment provided by engineer. + Chat colors Кольори чату @@ -1411,6 +1458,7 @@ Chat preferences were changed. + Змінено налаштування чату. alert message @@ -1430,10 +1478,12 @@ Check messages every 20 min. + Перевіряйте повідомлення кожні 20 хв. No comment provided by engineer. Check messages when allowed. + Перевірте повідомлення, коли це дозволено. No comment provided by engineer. @@ -1528,38 +1578,47 @@ Conditions accepted on: %@. + Умови приймаються на: %@. No comment provided by engineer. Conditions are accepted for the operator(s): **%@**. + Для оператора(ів) приймаються умови: **%@**. No comment provided by engineer. Conditions are already accepted for following operator(s): **%@**. + Умови вже прийняті для наступних операторів: **%@**. No comment provided by engineer. Conditions of use + Умови використання No comment provided by engineer. Conditions will be accepted for enabled operators after 30 days. + Умови будуть прийняті для ввімкнених операторів через 30 днів. No comment provided by engineer. Conditions will be accepted for operator(s): **%@**. + Умови приймаються для оператора(ів): **%@**. No comment provided by engineer. Conditions will be accepted for the operator(s): **%@**. + Для оператора(ів) приймаються умови: **%@**. No comment provided by engineer. Conditions will be accepted on: %@. + Умови приймаються на: %@. No comment provided by engineer. Conditions will be automatically accepted for enabled operators on: %@. + Умови будуть автоматично прийняті для увімкнених операторів на: %@. No comment provided by engineer. @@ -1758,6 +1817,7 @@ This is your own one-time link! Connection security + Безпека з'єднання No comment provided by engineer. @@ -1862,6 +1922,7 @@ This is your own one-time link! Corner + Кут No comment provided by engineer. @@ -1876,6 +1937,7 @@ This is your own one-time link! Create 1-time link + Створити одноразове посилання No comment provided by engineer. @@ -1965,6 +2027,7 @@ This is your own one-time link! Current conditions text couldn't be loaded, you can review conditions via this link: + Текст поточних умов не вдалося завантажити, ви можете переглянути умови за цим посиланням: No comment provided by engineer. @@ -1989,6 +2052,7 @@ This is your own one-time link! Customizable message shape. + Налаштовується форма повідомлення. No comment provided by engineer. @@ -2278,6 +2342,7 @@ This is your own one-time link! Delete or moderate up to 200 messages. + Видалити або модерувати до 200 повідомлень. No comment provided by engineer. @@ -2332,6 +2397,7 @@ This is your own one-time link! Delivered even when Apple drops them. + Доставляються навіть тоді, коли Apple кидає їх. No comment provided by engineer. @@ -2536,6 +2602,7 @@ This is your own one-time link! Do not use credentials with proxy. + Не використовуйте облікові дані з проксі. No comment provided by engineer. @@ -2581,6 +2648,7 @@ This is your own one-time link! Download files + Завантажити файли alert action @@ -2615,6 +2683,7 @@ This is your own one-time link! E2E encrypted notifications. + Зашифровані сповіщення E2E. No comment provided by engineer. @@ -2639,6 +2708,7 @@ This is your own one-time link! Enable Flux + Увімкнути Flux No comment provided by engineer. @@ -2848,6 +2918,7 @@ This is your own one-time link! Error accepting conditions + Помилка прийняття умов alert title @@ -2862,6 +2933,7 @@ This is your own one-time link! Error adding server + Помилка додавання сервера alert title @@ -2871,6 +2943,7 @@ This is your own one-time link! Error changing connection profile + Помилка при зміні профілю з'єднання No comment provided by engineer. @@ -2885,6 +2958,7 @@ This is your own one-time link! Error changing to incognito! + Помилка переходу на інкогніто! No comment provided by engineer. @@ -3004,10 +3078,12 @@ This is your own one-time link! Error loading servers + Помилка завантаження серверів alert title Error migrating settings + Помилка міграції налаштувань No comment provided by engineer. @@ -3062,6 +3138,7 @@ This is your own one-time link! Error saving servers + Сервери збереження помилок alert title @@ -3111,6 +3188,7 @@ This is your own one-time link! Error switching profile + Помилка перемикання профілю No comment provided by engineer. @@ -3135,6 +3213,7 @@ This is your own one-time link! Error updating server + Помилка оновлення сервера alert title @@ -3184,6 +3263,7 @@ This is your own one-time link! Errors in servers configuration. + Помилки в конфігурації серверів. servers error @@ -3259,6 +3339,8 @@ This is your own one-time link! File errors: %@ + Помилки файлів: +%@ alert message @@ -3388,6 +3470,7 @@ This is your own one-time link! For chat profile %@: + Для профілю чату %@: servers error @@ -3397,14 +3480,17 @@ This is your own one-time link! For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + Наприклад, якщо ваш контакт отримує повідомлення через сервер SimpleX Chat, ваш додаток доставлятиме їх через сервер Flux. No comment provided by engineer. For private routing + Для приватної маршрутизації No comment provided by engineer. For social media + Для соціальних мереж No comment provided by engineer. @@ -3414,6 +3500,7 @@ This is your own one-time link! Forward %d message(s)? + Переслати %d повідомлення(ь)? alert title @@ -3423,14 +3510,17 @@ This is your own one-time link! Forward messages + Пересилання повідомлень alert action Forward messages without files? + Пересилати повідомлення без файлів? alert message Forward up to 20 messages at once. + Пересилайте до 20 повідомлень одночасно. No comment provided by engineer. @@ -3445,6 +3535,7 @@ This is your own one-time link! Forwarding %lld messages + Пересилання повідомлень %lld No comment provided by engineer. @@ -3713,10 +3804,12 @@ Error: %2$@ How it affects privacy + Як це впливає на конфіденційність No comment provided by engineer. How it helps privacy + Як це захищає приватність No comment provided by engineer. @@ -3746,6 +3839,7 @@ Error: %2$@ IP address + IP-адреса No comment provided by engineer. @@ -3826,6 +3920,8 @@ Error: %2$@ Improved delivery, reduced traffic usage. More improvements are coming soon! + Покращена доставка, зменшене використання трафіку. +Незабаром з'являться нові покращення! No comment provided by engineer. @@ -4380,6 +4476,7 @@ This is your link for group %@! Message shape + Форма повідомлення No comment provided by engineer. @@ -4434,6 +4531,7 @@ This is your link for group %@! Messages were deleted after you selected them. + Повідомлення були видалені після того, як ви їх вибрали. alert message @@ -4533,6 +4631,7 @@ This is your link for group %@! More reliable notifications + Більш надійні сповіщення No comment provided by engineer. @@ -4572,6 +4671,7 @@ This is your link for group %@! Network decentralization + Децентралізація мережі No comment provided by engineer. @@ -4586,6 +4686,7 @@ This is your link for group %@! Network operator + Мережевий оператор No comment provided by engineer. @@ -4605,10 +4706,12 @@ This is your link for group %@! New SOCKS credentials will be used every time you start the app. + Нові облікові дані SOCKS будуть використовуватися при кожному запуску програми. No comment provided by engineer. New SOCKS credentials will be used for each server. + Для кожного сервера будуть використовуватися нові облікові дані SOCKS. No comment provided by engineer. @@ -4643,6 +4746,7 @@ This is your link for group %@! New events + Нові події notification @@ -4672,6 +4776,7 @@ This is your link for group %@! New server + Новий сервер No comment provided by engineer. @@ -4731,10 +4836,12 @@ This is your link for group %@! No media & file servers. + Ніяких медіа та файлових серверів. servers error No message servers. + Ніяких серверів повідомлень. servers error @@ -4744,10 +4851,12 @@ This is your link for group %@! No permission to record speech + Немає дозволу на запис промови No comment provided by engineer. No permission to record video + Немає дозволу на запис відео No comment provided by engineer. @@ -4767,18 +4876,22 @@ This is your link for group %@! No servers for private message routing. + Немає серверів для маршрутизації приватних повідомлень. servers error No servers to receive files. + Немає серверів для отримання файлів. servers error No servers to receive messages. + Немає серверів для отримання повідомлень. servers error No servers to send files. + Немає серверів для надсилання файлів. servers error @@ -4798,6 +4911,7 @@ This is your link for group %@! Nothing to forward! + Нічого пересилати! alert title @@ -4812,6 +4926,7 @@ This is your link for group %@! Notifications privacy + Сповіщення про приватність No comment provided by engineer. @@ -4954,6 +5069,7 @@ Requires compatible VPN. Open changes + Відкриті зміни No comment provided by engineer. @@ -4968,6 +5084,7 @@ Requires compatible VPN. Open conditions + Відкриті умови No comment provided by engineer. @@ -4987,10 +5104,12 @@ Requires compatible VPN. Operator + Оператор No comment provided by engineer. Operator server + Сервер оператора alert title @@ -5019,6 +5138,7 @@ Requires compatible VPN. Or to share privately + Або поділитися приватно No comment provided by engineer. @@ -5029,6 +5149,8 @@ Requires compatible VPN. Other file errors: %@ + Інші помилки файлів: +%@ alert message @@ -5068,6 +5190,7 @@ Requires compatible VPN. Password + Пароль No comment provided by engineer. @@ -5216,6 +5339,7 @@ Error: %@ Port + Порт No comment provided by engineer. @@ -5235,6 +5359,7 @@ Error: %@ Preset servers + Попередньо встановлені сервери No comment provided by engineer. @@ -5406,10 +5531,12 @@ Enable in *Network & servers* settings. Proxy requires password + Проксі вимагає пароль No comment provided by engineer. Push Notifications + Push-сповіщення No comment provided by engineer. @@ -5630,6 +5757,7 @@ Enable in *Network & servers* settings. Remove archive? + Видалити архів? No comment provided by engineer. @@ -5784,10 +5912,12 @@ Enable in *Network & servers* settings. Review conditions + Умови перегляду No comment provided by engineer. Review later + Перегляньте пізніше No comment provided by engineer. @@ -5822,6 +5952,7 @@ Enable in *Network & servers* settings. SOCKS proxy + Проксі SOCKS No comment provided by engineer. @@ -5836,10 +5967,12 @@ Enable in *Network & servers* settings. Same conditions will apply to operator **%@**. + Такі ж умови діятимуть і для оператора **%@**. No comment provided by engineer. Same conditions will apply to operator(s): **%@**. + Такі ж умови будуть застосовуватися до оператора(ів): **%@**. No comment provided by engineer. @@ -5915,6 +6048,7 @@ Enable in *Network & servers* settings. Save your profile? + Зберегти свій профіль? alert title @@ -5939,6 +6073,7 @@ Enable in *Network & servers* settings. Saving %lld messages + Збереження повідомлень %lld No comment provided by engineer. @@ -6023,6 +6158,7 @@ Enable in *Network & servers* settings. Select chat profile + Виберіть профіль чату No comment provided by engineer. @@ -6237,10 +6373,12 @@ Enable in *Network & servers* settings. Server + Сервер No comment provided by engineer. Server added to operator %@. + Сервер додано до оператора %@. alert message @@ -6260,14 +6398,17 @@ Enable in *Network & servers* settings. Server operator changed. + Оператор сервера змінився. alert title Server operators + Оператори серверів No comment provided by engineer. Server protocol changed. + Протокол сервера змінено. alert title @@ -6377,6 +6518,7 @@ Enable in *Network & servers* settings. Settings were changed. + Налаштування були змінені. alert message @@ -6397,10 +6539,12 @@ Enable in *Network & servers* settings. Share 1-time link with a friend + Поділіться одноразовим посиланням з другом No comment provided by engineer. Share SimpleX address on social media. + Поділіться адресою SimpleX у соціальних мережах. No comment provided by engineer. @@ -6410,6 +6554,7 @@ Enable in *Network & servers* settings. Share address publicly + Поділіться адресою публічно No comment provided by engineer. @@ -6429,6 +6574,7 @@ Enable in *Network & servers* settings. Share profile + Поділіться профілем No comment provided by engineer. @@ -6533,10 +6679,12 @@ Enable in *Network & servers* settings. SimpleX address and 1-time links are safe to share via any messenger. + SimpleX-адреси та одноразові посилання можна безпечно ділитися через будь-який месенджер. No comment provided by engineer. SimpleX address or 1-time link? + SimpleX адреса або одноразове посилання? No comment provided by engineer. @@ -6576,6 +6724,7 @@ Enable in *Network & servers* settings. SimpleX protocols reviewed by Trail of Bits. + Протоколи SimpleX, розглянуті Trail of Bits. No comment provided by engineer. @@ -6610,6 +6759,7 @@ Enable in *Network & servers* settings. Some app settings were not migrated. + Деякі налаштування програми не були перенесені. No comment provided by engineer. @@ -6630,6 +6780,8 @@ Enable in *Network & servers* settings. Some servers failed the test: %@ + Деякі сервери не пройшли тестування: +%@ alert message @@ -6754,10 +6906,12 @@ Enable in *Network & servers* settings. Switch audio and video during the call. + Перемикайте аудіо та відео під час дзвінка. No comment provided by engineer. Switch chat profile for 1-time invitations. + Переключіть профіль чату для отримання одноразових запрошень. No comment provided by engineer. @@ -6797,6 +6951,7 @@ Enable in *Network & servers* settings. Tail + Хвіст No comment provided by engineer. @@ -6897,6 +7052,7 @@ It can happen because of some bug or when the connection is compromised. The app protects your privacy by using different operators in each conversation. + Додаток захищає вашу конфіденційність, використовуючи різних операторів у кожній розмові. No comment provided by engineer. @@ -6916,6 +7072,7 @@ It can happen because of some bug or when the connection is compromised. The connection reached the limit of undelivered messages, your contact may be offline. + З'єднання досягло ліміту недоставлених повідомлень, ваш контакт може бути офлайн. No comment provided by engineer. @@ -6980,6 +7137,7 @@ It can happen because of some bug or when the connection is compromised. The second preset operator in the app! + Другий попередньо встановлений оператор у застосунку! No comment provided by engineer. @@ -6999,6 +7157,7 @@ It can happen because of some bug or when the connection is compromised. The servers for new files of your current chat profile **%@**. + Сервери для нових файлів вашого поточного профілю чату **%@**. No comment provided by engineer. @@ -7008,6 +7167,7 @@ It can happen because of some bug or when the connection is compromised. The uploaded database archive will be permanently removed from the servers. + Завантажений архів бази даних буде назавжди видалено з серверів. No comment provided by engineer. @@ -7017,6 +7177,7 @@ It can happen because of some bug or when the connection is compromised. These conditions will also apply for: **%@**. + Ці умови також поширюються на: **%@**. No comment provided by engineer. @@ -7121,6 +7282,7 @@ It can happen because of some bug or when the connection is compromised. To protect against your link being replaced, you can compare contact security codes. + Щоб захиститися від заміни вашого посилання, ви можете порівняти коди безпеки контактів. No comment provided by engineer. @@ -7147,14 +7309,17 @@ You will be prompted to complete authentication before this feature is enabled.< To receive + Щоб отримати No comment provided by engineer. To record speech please grant permission to use Microphone. + Для запису промови, будь ласка, надайте дозвіл на використання мікрофону. No comment provided by engineer. To record video please grant permission to use Camera. + Для запису відео, будь ласка, надайте дозвіл на використання камери. No comment provided by engineer. @@ -7169,6 +7334,7 @@ You will be prompted to complete authentication before this feature is enabled.< To send + Щоб відправити No comment provided by engineer. @@ -7178,6 +7344,7 @@ You will be prompted to complete authentication before this feature is enabled.< To use the servers of **%@**, accept conditions of use. + Щоб користуватися серверами **%@**, прийміть умови використання. No comment provided by engineer. @@ -7272,6 +7439,7 @@ You will be prompted to complete authentication before this feature is enabled.< Undelivered messages + Недоставлені повідомлення No comment provided by engineer. @@ -7433,6 +7601,7 @@ To connect, please ask your contact to create another connection link and check Use %@ + Використовуйте %@ No comment provided by engineer. @@ -7442,6 +7611,7 @@ To connect, please ask your contact to create another connection link and check Use SOCKS proxy + Використовуйте SOCKS проксі No comment provided by engineer. @@ -7461,10 +7631,12 @@ To connect, please ask your contact to create another connection link and check Use for files + Використовуйте для файлів No comment provided by engineer. Use for messages + Використовуйте для повідомлень No comment provided by engineer. @@ -7509,6 +7681,7 @@ To connect, please ask your contact to create another connection link and check Use servers + Використовуйте сервери No comment provided by engineer. @@ -7528,6 +7701,7 @@ To connect, please ask your contact to create another connection link and check Username + Ім'я користувача No comment provided by engineer. @@ -7602,6 +7776,7 @@ To connect, please ask your contact to create another connection link and check View conditions + Умови перегляду No comment provided by engineer. @@ -7611,6 +7786,7 @@ To connect, please ask your contact to create another connection link and check View updated conditions + Переглянути оновлені умови No comment provided by engineer. @@ -7725,6 +7901,7 @@ To connect, please ask your contact to create another connection link and check When more than one operator is enabled, none of them has metadata to learn who communicates with whom. + Коли увімкнено більше одного оператора, жоден з них не має метаданих, щоб дізнатися, хто з ким спілкується. No comment provided by engineer. @@ -7822,6 +7999,10 @@ To connect, please ask your contact to create another connection link and check Ви вже підключені до %@. No comment provided by engineer. + + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. Ви вже з'єднані з %@. @@ -7886,10 +8067,12 @@ Repeat join request? You can configure operators in Network & servers settings. + Ви можете налаштувати операторів у налаштуваннях Мережі та серверів. No comment provided by engineer. You can configure servers via settings. + Ви можете налаштувати сервери за допомогою налаштувань. No comment provided by engineer. @@ -7934,6 +8117,7 @@ Repeat join request? You can set connection name, to remember who the link was shared with. + Ви можете задати ім'я з'єднання, щоб запам'ятати, з ким ви поділилися посиланням. No comment provided by engineer. @@ -8145,6 +8329,7 @@ Repeat connection request? Your chat preferences + Ваші налаштування чату alert title @@ -8154,6 +8339,7 @@ Repeat connection request? Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Ваше з'єднання було переміщено на %@, але під час перенаправлення на профіль сталася несподівана помилка. No comment provided by engineer. @@ -8173,6 +8359,7 @@ Repeat connection request? Your credentials may be sent unencrypted. + Ваші облікові дані можуть бути надіслані незашифрованими. No comment provided by engineer. @@ -8212,6 +8399,7 @@ Repeat connection request? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + Ваш профіль було змінено. Якщо ви збережете його, оновлений профіль буде надіслано всім вашим контактам. alert message @@ -8231,6 +8419,7 @@ Repeat connection request? Your servers + Ваші сервери No comment provided by engineer. @@ -8655,6 +8844,7 @@ Repeat connection request? for better metadata privacy. + для кращої конфіденційності метаданих. No comment provided by engineer. @@ -9286,22 +9476,27 @@ last received msg: %2$@ %d new events + %d нових подій notification body From: %@ + Від: %@ notification body New events + Нові події notification New messages + Нові повідомлення notification New messages in %d chats + Нові повідомлення в чатах %d notification body diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index 1663530290..300a33d7b4 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -622,6 +622,10 @@ 将地址添加到您的个人资料,以便您的联系人可以与其他人共享。个人资料更新将发送给您的联系人。 No comment provided by engineer. + + Add friends + No comment provided by engineer. + Add profile 添加个人资料 @@ -637,6 +641,10 @@ 扫描二维码来添加服务器。 No comment provided by engineer. + + Add team members + No comment provided by engineer. + Add to another device 添加另一设备 @@ -647,6 +655,10 @@ 添加欢迎信息 No comment provided by engineer. + + Add your team members to the conversations. + No comment provided by engineer. + Added media & file servers No comment provided by engineer. @@ -1205,6 +1217,10 @@ 保加利亚语、芬兰语、泰语和乌克兰语——感谢用户和[Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! No comment provided by engineer. + + Business address + 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). 通过聊天资料(默认)或者[通过连接](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)。 @@ -1346,6 +1362,14 @@ Change user profiles authentication reason + + Chat already exists + No comment provided by engineer. + + + Chat already exists! + No comment provided by engineer. + Chat colors 聊天颜色 @@ -7819,6 +7843,10 @@ To connect, please ask your contact to create another connection link and check 您已经连接到 %@。 No comment provided by engineer. + + You are already connected with %@. + No comment provided by engineer. + You are already connecting to %@. 您已连接到 %@。 diff --git a/apps/ios/SimpleX NSE/de.lproj/Localizable.strings b/apps/ios/SimpleX NSE/de.lproj/Localizable.strings index 5ef592ec70..f9779c6e05 100644 --- a/apps/ios/SimpleX NSE/de.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/de.lproj/Localizable.strings @@ -1,7 +1,15 @@ -/* - Localizable.strings - SimpleX +/* notification body */ +"%d new events" = "%d neue Ereignisse"; + +/* notification body */ +"From: %@" = "Von: %@"; + +/* notification */ +"New events" = "Neue Ereignisse"; + +/* notification */ +"New messages" = "Neue Nachrichten"; + +/* notification body */ +"New messages in %d chats" = "Neue Nachrichten in %d Chats"; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/SimpleX NSE/es.lproj/Localizable.strings b/apps/ios/SimpleX NSE/es.lproj/Localizable.strings index 5ef592ec70..fb190400e1 100644 --- a/apps/ios/SimpleX NSE/es.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/es.lproj/Localizable.strings @@ -1,7 +1,15 @@ -/* - Localizable.strings - SimpleX +/* notification body */ +"%d new events" = "%d evento(s) nuevo(s)"; + +/* notification body */ +"From: %@" = "De: %@"; + +/* notification */ +"New events" = "Eventos nuevos"; + +/* notification */ +"New messages" = "Mensajes nuevos"; + +/* notification body */ +"New messages in %d chats" = "Mensajes nuevos en %d chat(s)"; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/SimpleX NSE/hu.lproj/Localizable.strings b/apps/ios/SimpleX NSE/hu.lproj/Localizable.strings index 5ef592ec70..e64c98df9e 100644 --- a/apps/ios/SimpleX NSE/hu.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/hu.lproj/Localizable.strings @@ -1,7 +1,15 @@ -/* - Localizable.strings - SimpleX +/* notification body */ +"%d new events" = "%d új esemény"; + +/* notification body */ +"From: %@" = "Tőle: %@"; + +/* notification */ +"New events" = "Új események"; + +/* notification */ +"New messages" = "Új üzenetek"; + +/* notification body */ +"New messages in %d chats" = "Új üzenetek %d csevegésben"; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/SimpleX NSE/it.lproj/Localizable.strings b/apps/ios/SimpleX NSE/it.lproj/Localizable.strings index 5ef592ec70..31f463eb5b 100644 --- a/apps/ios/SimpleX NSE/it.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/it.lproj/Localizable.strings @@ -1,7 +1,15 @@ -/* - Localizable.strings - SimpleX +/* notification body */ +"%d new events" = "%d nuovi eventi"; + +/* notification body */ +"From: %@" = "Da: %@"; + +/* notification */ +"New events" = "Nuovi eventi"; + +/* notification */ +"New messages" = "Nuovi messaggi"; + +/* notification body */ +"New messages in %d chats" = "Nuovi messaggi in %d chat"; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/SimpleX NSE/nl.lproj/Localizable.strings b/apps/ios/SimpleX NSE/nl.lproj/Localizable.strings index 5ef592ec70..4cf91689b5 100644 --- a/apps/ios/SimpleX NSE/nl.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/nl.lproj/Localizable.strings @@ -1,7 +1,15 @@ -/* - Localizable.strings - SimpleX +/* notification body */ +"%d new events" = "‐%d nieuwe gebeurtenissen"; + +/* notification body */ +"From: %@" = "Van: %@"; + +/* notification */ +"New events" = "Nieuwe gebeurtenissen"; + +/* notification */ +"New messages" = "Nieuwe berichten"; + +/* notification body */ +"New messages in %d chats" = "Nieuwe berichten in %d chats"; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/SimpleX NSE/uk.lproj/Localizable.strings b/apps/ios/SimpleX NSE/uk.lproj/Localizable.strings index 5ef592ec70..69cc53bff1 100644 --- a/apps/ios/SimpleX NSE/uk.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/uk.lproj/Localizable.strings @@ -1,7 +1,15 @@ -/* - Localizable.strings - SimpleX +/* notification body */ +"%d new events" = "%d нових подій"; + +/* notification body */ +"From: %@" = "Від: %@"; + +/* notification */ +"New events" = "Нові події"; + +/* notification */ +"New messages" = "Нові повідомлення"; + +/* notification body */ +"New messages in %d chats" = "Нові повідомлення в чатах %d"; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index f680727010..1e92d094b4 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -82,6 +82,9 @@ /* No comment provided by engineer. */ "**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Empfohlen**: Nur Ihr Geräte-Token und ihre Benachrichtigungen werden an den SimpleX-Chat-Benachrichtigungs-Server gesendet, aber weder der Nachrichteninhalt noch deren Größe oder von wem sie gesendet wurde."; +/* No comment provided by engineer. */ +"**Scan / Paste link**: to connect via a link you received." = "**Link scannen / einfügen**: Um eine Verbindung über den Link herzustellen, den Sie erhalten haben."; + /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Warnung**: Sofortige Push-Benachrichtigungen erfordern die Eingabe eines Passworts, welches in Ihrem Schlüsselbund gespeichert ist."; @@ -142,6 +145,12 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ wurde erfolgreich überprüft"; +/* No comment provided by engineer. */ +"%@ server" = "%@ Server"; + +/* No comment provided by engineer. */ +"%@ servers" = "%@ Server"; + /* No comment provided by engineer. */ "%@ uploaded" = "%@ hochgeladen"; @@ -295,6 +304,12 @@ /* time interval */ "1 week" = "wöchentlich"; +/* No comment provided by engineer. */ +"1-time link" = "Einmal-Link"; + +/* No comment provided by engineer. */ +"1-time link can be used *with one contact only* - share in person or via any messenger." = "Ein Einmal-Link kann *nur mit einem Kontakt* genutzt werden - teilen Sie in nur persönlich oder über einen beliebigen Messenger."; + /* No comment provided by engineer. */ "5 minutes" = "5 Minuten"; @@ -342,6 +357,9 @@ swipe action */ "Accept" = "Annehmen"; +/* No comment provided by engineer. */ +"Accept conditions" = "Nutzungsbedingungen akzeptieren"; + /* No comment provided by engineer. */ "Accept connection request?" = "Kontaktanfrage annehmen?"; @@ -355,6 +373,9 @@ /* call status */ "accepted call" = "Anruf angenommen"; +/* No comment provided by engineer. */ +"Accepted conditions" = "Akzeptierte Nutzungsbedingungen"; + /* No comment provided by engineer. */ "Acknowledged" = "Bestätigt"; @@ -382,6 +403,12 @@ /* No comment provided by engineer. */ "Add welcome message" = "Begrüßungsmeldung hinzufügen"; +/* No comment provided by engineer. */ +"Added media & file servers" = "Medien- und Dateiserver hinzugefügt"; + +/* No comment provided by engineer. */ +"Added message servers" = "Nachrichtenserver hinzugefügt"; + /* No comment provided by engineer. */ "Additional accent" = "Erste Akzentfarbe"; @@ -397,6 +424,12 @@ /* No comment provided by engineer. */ "Address change will be aborted. Old receiving address will be used." = "Der Wechsel der Empfängeradresse wird beendet. Die bisherige Adresse wird weiter verwendet."; +/* No comment provided by engineer. */ +"Address or 1-time link?" = "Adress- oder Einmal-Link?"; + +/* No comment provided by engineer. */ +"Address settings" = "Adress-Einstellungen"; + /* member role */ "admin" = "Admin"; @@ -439,6 +472,9 @@ /* feature role */ "all members" = "Alle Mitglieder"; +/* No comment provided by engineer. */ +"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Alle Nachrichten und Dateien werden **Ende-zu-Ende verschlüsselt** versendet - in Direkt-Nachrichten mit Post-Quantum-Security."; + /* No comment provided by engineer. */ "All messages will be deleted - this cannot be undone!" = "Es werden alle Nachrichten gelöscht. Dies kann nicht rückgängig gemacht werden!"; @@ -855,6 +891,9 @@ set passcode view */ "Change self-destruct passcode" = "Selbstzerstörungs-Zugangscode ändern"; +/* authentication reason */ +"Change user profiles" = "Chat-Profile wechseln"; + /* chat item text */ "changed address for you" = "Wechselte die Empfängeradresse von Ihnen"; @@ -918,6 +957,12 @@ /* No comment provided by engineer. */ "Chats" = "Chats"; +/* No comment provided by engineer. */ +"Check messages every 20 min." = "Alle 20min Nachrichten überprüfen."; + +/* No comment provided by engineer. */ +"Check messages when allowed." = "Wenn es erlaubt ist, Nachrichten überprüfen."; + /* alert title */ "Check server address and try again." = "Überprüfen Sie die Serveradresse und versuchen Sie es nochmal."; @@ -978,6 +1023,33 @@ /* No comment provided by engineer. */ "Completed" = "Abgeschlossen"; +/* No comment provided by engineer. */ +"Conditions accepted on: %@." = "Die Nutzungsbedingungen wurden akzeptiert am: %@."; + +/* No comment provided by engineer. */ +"Conditions are accepted for the operator(s): **%@**." = "Die Nutzungsbedingungen der/des Betreiber(s) werden akzeptiert: **%@**."; + +/* No comment provided by engineer. */ +"Conditions are already accepted for following operator(s): **%@**." = "Die Nutzungsbedingungen der/des folgenden Betreiber(s) wurden schon akzeptiert: **%@**."; + +/* No comment provided by engineer. */ +"Conditions of use" = "Nutzungsbedingungen"; + +/* No comment provided by engineer. */ +"Conditions will be accepted for enabled operators after 30 days." = "Die Nutzungsbedingungen der aktivierten Betreiber werden nach 30 Tagen akzeptiert."; + +/* No comment provided by engineer. */ +"Conditions will be accepted for operator(s): **%@**." = "Die Nutzungsbedingungen der/des Betreiber(s) werden akzeptiert: **%@**."; + +/* No comment provided by engineer. */ +"Conditions will be accepted for the operator(s): **%@**." = "Die Nutzungsbedingungen der/des Betreiber(s) werden akzeptiert: **%@**."; + +/* No comment provided by engineer. */ +"Conditions will be accepted on: %@." = "Die Nutzungsbedingungen werden akzeptiert am: %@."; + +/* No comment provided by engineer. */ +"Conditions will be automatically accepted for enabled operators on: %@." = "Die Nutzungsbedingungen der aktivierten Betreiber werden automatisch akzeptiert am: %@."; + /* No comment provided by engineer. */ "Configure ICE servers" = "ICE-Server konfigurieren"; @@ -1125,6 +1197,9 @@ /* No comment provided by engineer. */ "Connection request sent!" = "Verbindungsanfrage wurde gesendet!"; +/* No comment provided by engineer. */ +"Connection security" = "Verbindungs-Sicherheit"; + /* No comment provided by engineer. */ "Connection terminated" = "Verbindung beendet"; @@ -1206,6 +1281,9 @@ /* No comment provided by engineer. */ "Create" = "Erstellen"; +/* No comment provided by engineer. */ +"Create 1-time link" = "Einmal-Link erstellen"; + /* No comment provided by engineer. */ "Create a group using a random profile." = "Erstellen Sie eine Gruppe mit einem zufälligen Profil."; @@ -1257,6 +1335,9 @@ /* No comment provided by engineer. */ "creator" = "Ersteller"; +/* No comment provided by engineer. */ +"Current conditions text couldn't be loaded, you can review conditions via this link:" = "Der Text der aktuellen Nutzungsbedingungen konnte nicht geladen werden. Sie können die Nutzungsbedingungen unter diesem Link einsehen:"; + /* No comment provided by engineer. */ "Current Passcode" = "Aktueller Zugangscode"; @@ -1505,6 +1586,9 @@ /* No comment provided by engineer. */ "Deletion errors" = "Fehler beim Löschen"; +/* No comment provided by engineer. */ +"Delivered even when Apple drops them." = "Auslieferung, selbst wenn Apple sie löscht."; + /* No comment provided by engineer. */ "Delivery" = "Zustellung"; @@ -1692,6 +1776,9 @@ /* No comment provided by engineer. */ "e2e encrypted" = "E2E-verschlüsselt"; +/* No comment provided by engineer. */ +"E2E encrypted notifications." = "E2E-verschlüsselte Benachrichtigungen."; + /* chat item action */ "Edit" = "Bearbeiten"; @@ -1710,6 +1797,9 @@ /* No comment provided by engineer. */ "Enable camera access" = "Kamera-Zugriff aktivieren"; +/* No comment provided by engineer. */ +"Enable Flux" = "Flux aktivieren"; + /* No comment provided by engineer. */ "Enable for all" = "Für Alle aktivieren"; @@ -1869,12 +1959,18 @@ /* No comment provided by engineer. */ "Error aborting address change" = "Fehler beim Beenden des Adresswechsels"; +/* alert title */ +"Error accepting conditions" = "Fehler beim Akzeptieren der Nutzungsbedingungen"; + /* No comment provided by engineer. */ "Error accepting contact request" = "Fehler beim Annehmen der Kontaktanfrage"; /* No comment provided by engineer. */ "Error adding member(s)" = "Fehler beim Hinzufügen von Mitgliedern"; +/* alert title */ +"Error adding server" = "Fehler beim Hinzufügen des Servers"; + /* No comment provided by engineer. */ "Error changing address" = "Fehler beim Wechseln der Empfängeradresse"; @@ -1959,6 +2055,9 @@ /* No comment provided by engineer. */ "Error joining group" = "Fehler beim Beitritt zur Gruppe"; +/* alert title */ +"Error loading servers" = "Fehler beim Laden der Server"; + /* No comment provided by engineer. */ "Error migrating settings" = "Fehler beim Migrieren der Einstellungen"; @@ -1992,6 +2091,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "Fehler beim Speichern des Passworts in den Schlüsselbund"; +/* alert title */ +"Error saving servers" = "Fehler beim Speichern der Server"; + /* when migrating */ "Error saving settings" = "Fehler beim Abspeichern der Einstellungen"; @@ -2034,6 +2136,9 @@ /* No comment provided by engineer. */ "Error updating message" = "Fehler beim Aktualisieren der Nachricht"; +/* alert title */ +"Error updating server" = "Fehler beim Aktualisieren des Servers"; + /* No comment provided by engineer. */ "Error updating settings" = "Fehler beim Aktualisieren der Einstellungen"; @@ -2061,6 +2166,9 @@ /* No comment provided by engineer. */ "Errors" = "Fehler"; +/* servers error */ +"Errors in servers configuration." = "Fehler in der Server-Konfiguration."; + /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Auch wenn sie im Chat deaktiviert sind."; @@ -2187,9 +2295,24 @@ /* No comment provided by engineer. */ "Fix not supported by group member" = "Reparatur wird vom Gruppenmitglied nicht unterstützt"; +/* No comment provided by engineer. */ +"for better metadata privacy." = "für einen besseren Metadatenschutz."; + +/* servers error */ +"For chat profile %@:" = "Für das Chat-Profil %@:"; + /* No comment provided by engineer. */ "For console" = "Für Konsole"; +/* No comment provided by engineer. */ +"For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server." = "Wenn Ihr Kontakt beispielsweise Nachrichten über einen SimpleX-Chatserver empfängt, wird Ihre App diese über einen der Server von Flux versenden."; + +/* No comment provided by engineer. */ +"For private routing" = "Für privates Routing"; + +/* No comment provided by engineer. */ +"For social media" = "Für soziale Medien"; + /* chat item action */ "Forward" = "Weiterleiten"; @@ -2382,6 +2505,12 @@ /* time unit */ "hours" = "Stunden"; +/* No comment provided by engineer. */ +"How it affects privacy" = "Wie es die Privatsphäre beeinflusst"; + +/* No comment provided by engineer. */ +"How it helps privacy" = "Wie es die Privatsphäre schützt"; + /* No comment provided by engineer. */ "How SimpleX works" = "Wie SimpleX funktioniert"; @@ -2958,6 +3087,9 @@ /* No comment provided by engineer. */ "More reliable network connection." = "Zuverlässigere Netzwerkverbindung."; +/* No comment provided by engineer. */ +"More reliable notifications" = "Zuverlässigere Benachrichtigungen"; + /* item status description */ "Most likely this connection is deleted." = "Wahrscheinlich ist diese Verbindung gelöscht worden."; @@ -2982,12 +3114,18 @@ /* No comment provided by engineer. */ "Network connection" = "Netzwerkverbindung"; +/* No comment provided by engineer. */ +"Network decentralization" = "Dezentralisiertes Netzwerk"; + /* snd error text */ "Network issues - message expired after many attempts to send it." = "Netzwerk-Fehler - die Nachricht ist nach vielen Sende-Versuchen abgelaufen."; /* No comment provided by engineer. */ "Network management" = "Netzwerk-Verwaltung"; +/* No comment provided by engineer. */ +"Network operator" = "Netzwerk-Betreiber"; + /* No comment provided by engineer. */ "Network settings" = "Netzwerkeinstellungen"; @@ -3015,6 +3153,9 @@ /* No comment provided by engineer. */ "New display name" = "Neuer Anzeigename"; +/* notification */ +"New events" = "Neue Ereignisse"; + /* No comment provided by engineer. */ "New in %@" = "Neu in %@"; @@ -3036,6 +3177,9 @@ /* No comment provided by engineer. */ "New passphrase…" = "Neues Passwort…"; +/* No comment provided by engineer. */ +"New server" = "Neuer Server"; + /* No comment provided by engineer. */ "New SOCKS credentials will be used every time you start the app." = "Jedes Mal wenn Sie die App starten, werden neue SOCKS-Anmeldeinformationen genutzt"; @@ -3081,6 +3225,12 @@ /* No comment provided by engineer. */ "No info, try to reload" = "Keine Information - es wird versucht neu zu laden"; +/* servers error */ +"No media & file servers." = "Keine Medien- und Dateiserver."; + +/* servers error */ +"No message servers." = "Keine Nachrichten-Server."; + /* No comment provided by engineer. */ "No network connection" = "Keine Netzwerkverbindung"; @@ -3099,6 +3249,18 @@ /* No comment provided by engineer. */ "No received or sent files" = "Keine empfangenen oder gesendeten Dateien"; +/* servers error */ +"No servers for private message routing." = "Keine Server für privates Nachrichten-Routing."; + +/* servers error */ +"No servers to receive files." = "Keine Server für den Empfang von Dateien."; + +/* servers error */ +"No servers to receive messages." = "Keine Server für den Empfang von Nachrichten."; + +/* servers error */ +"No servers to send files." = "Keine Server für das Versenden von Dateien."; + /* copied message info in history */ "no text" = "Kein Text"; @@ -3120,6 +3282,9 @@ /* No comment provided by engineer. */ "Notifications are disabled!" = "Benachrichtigungen sind deaktiviert!"; +/* No comment provided by engineer. */ +"Notifications privacy" = "Datenschutz für Benachrichtigungen"; + /* No comment provided by engineer. */ "Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "Administratoren können nun\n- Nachrichten von Gruppenmitgliedern löschen\n- Gruppenmitglieder deaktivieren (\"Beobachter\"-Rolle)"; @@ -3212,12 +3377,18 @@ /* No comment provided by engineer. */ "Open" = "Öffnen"; +/* No comment provided by engineer. */ +"Open changes" = "Änderungen öffnen"; + /* No comment provided by engineer. */ "Open chat" = "Chat öffnen"; /* authentication reason */ "Open chat console" = "Chat-Konsole öffnen"; +/* No comment provided by engineer. */ +"Open conditions" = "Nutzungsbedingungen öffnen"; + /* No comment provided by engineer. */ "Open group" = "Gruppe öffnen"; @@ -3230,6 +3401,12 @@ /* No comment provided by engineer. */ "Opening app…" = "App wird geöffnet…"; +/* No comment provided by engineer. */ +"Operator" = "Betreiber"; + +/* alert title */ +"Operator server" = "Betreiber-Server"; + /* No comment provided by engineer. */ "Or paste archive link" = "Oder fügen Sie den Archiv-Link ein"; @@ -3242,6 +3419,9 @@ /* No comment provided by engineer. */ "Or show this code" = "Oder diesen QR-Code anzeigen"; +/* No comment provided by engineer. */ +"Or to share privately" = "Oder zum privaten Teilen"; + /* No comment provided by engineer. */ "other" = "Andere"; @@ -3383,6 +3563,9 @@ /* No comment provided by engineer. */ "Preset server address" = "Voreingestellte Serveradresse"; +/* No comment provided by engineer. */ +"Preset servers" = "Voreingestellte Server"; + /* No comment provided by engineer. */ "Preview" = "Vorschau"; @@ -3488,6 +3671,9 @@ /* No comment provided by engineer. */ "Push notifications" = "Push-Benachrichtigungen"; +/* No comment provided by engineer. */ +"Push Notifications" = "Push-Benachrichtigungen"; + /* No comment provided by engineer. */ "Push server" = "Push-Server"; @@ -3735,6 +3921,12 @@ /* chat item action */ "Reveal" = "Aufdecken"; +/* No comment provided by engineer. */ +"Review conditions" = "Nutzungsbedingungen einsehen"; + +/* No comment provided by engineer. */ +"Review later" = "Später einsehen"; + /* No comment provided by engineer. */ "Revoke" = "Widerrufen"; @@ -3756,6 +3948,12 @@ /* No comment provided by engineer. */ "Safer groups" = "Sicherere Gruppen"; +/* No comment provided by engineer. */ +"Same conditions will apply to operator **%@**." = "Dieselben Nutzungsbedingungen gelten auch für den Betreiber **%@**."; + +/* No comment provided by engineer. */ +"Same conditions will apply to operator(s): **%@**." = "Dieselben Nutzungsbedingungen gelten auch für den/die Betreiber: **%@**."; + /* alert button chat item action */ "Save" = "Speichern"; @@ -4021,6 +4219,9 @@ /* No comment provided by engineer. */ "Server" = "Server"; +/* alert message */ +"Server added to operator %@." = "Der Server wurde dem Betreiber %@ hinzugefügt."; + /* No comment provided by engineer. */ "Server address" = "Server-Adresse"; @@ -4030,6 +4231,15 @@ /* srv error text. */ "Server address is incompatible with network settings." = "Die Server-Adresse ist nicht mit den Netzwerkeinstellungen kompatibel."; +/* alert title */ +"Server operator changed." = "Der Server-Betreiber wurde geändert."; + +/* No comment provided by engineer. */ +"Server operators" = "Server-Betreiber"; + +/* alert title */ +"Server protocol changed." = "Das Server-Protokoll wurde geändert."; + /* queue info */ "server queue info: %@\n\nlast received msg: %@" = "Server-Warteschlangen-Information: %1$@\n\nZuletzt empfangene Nachricht: %2$@"; @@ -4115,9 +4325,15 @@ /* No comment provided by engineer. */ "Share 1-time link" = "Einmal-Link teilen"; +/* No comment provided by engineer. */ +"Share 1-time link with a friend" = "Den Einmal-Einladungslink mit einem Freund teilen"; + /* No comment provided by engineer. */ "Share address" = "Adresse teilen"; +/* No comment provided by engineer. */ +"Share address publicly" = "Die Adresse öffentlich teilen"; + /* alert title */ "Share address with contacts?" = "Die Adresse mit Kontakten teilen?"; @@ -4130,6 +4346,9 @@ /* No comment provided by engineer. */ "Share profile" = "Profil teilen"; +/* No comment provided by engineer. */ +"Share SimpleX address on social media." = "Die SimpleX-Adresse auf sozialen Medien teilen."; + /* No comment provided by engineer. */ "Share this 1-time invite link" = "Teilen Sie diesen Einmal-Einladungslink"; @@ -4175,6 +4394,12 @@ /* No comment provided by engineer. */ "SimpleX Address" = "SimpleX-Adresse"; +/* No comment provided by engineer. */ +"SimpleX address and 1-time links are safe to share via any messenger." = "Die SimpleX-Adresse und Einmal-Links können sicher über beliebige Messenger geteilt werden."; + +/* No comment provided by engineer. */ +"SimpleX address or 1-time link?" = "SimpleX-Adresse oder Einmal-Link?"; + /* No comment provided by engineer. */ "SimpleX Chat security was audited by Trail of Bits." = "Die Sicherheit von SimpleX Chat wurde von Trail of Bits überprüft."; @@ -4250,6 +4475,9 @@ /* No comment provided by engineer. */ "Some non-fatal errors occurred during import:" = "Während des Imports traten ein paar nicht schwerwiegende Fehler auf:"; +/* alert message */ +"Some servers failed the test:\n%@" = "Einige Server haben den Test nicht bestanden:\n%@"; + /* notification title */ "Somebody" = "Jemand"; @@ -4412,6 +4640,9 @@ /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Wenn sie Nachrichten oder Kontaktanfragen empfangen, kann Sie die App benachrichtigen - Um dies zu aktivieren, öffnen Sie bitte die Einstellungen."; +/* No comment provided by engineer. */ +"The app protects your privacy by using different operators in each conversation." = "Durch Verwendung verschiedener Netzwerk-Betreiber für jede Unterhaltung schützt die App Ihre Privatsphäre."; + /* No comment provided by engineer. */ "The app will ask to confirm downloads from unknown file servers (except .onion)." = "Die App wird eine Bestätigung bei Downloads von unbekannten Datei-Servern anfordern (außer bei .onion)."; @@ -4421,6 +4652,9 @@ /* No comment provided by engineer. */ "The code you scanned is not a SimpleX link QR code." = "Der von Ihnen gescannte Code ist kein SimpleX-Link-QR-Code."; +/* No comment provided by engineer. */ +"The connection reached the limit of undelivered messages, your contact may be offline." = "Diese Verbindung hat das Limit der nicht ausgelieferten Nachrichten erreicht. Ihr Kontakt ist möglicherweise offline."; + /* No comment provided by engineer. */ "The connection you accepted will be cancelled!" = "Die von Ihnen akzeptierte Verbindung wird abgebrochen!"; @@ -4460,6 +4694,9 @@ /* No comment provided by engineer. */ "The profile is only shared with your contacts." = "Das Profil wird nur mit Ihren Kontakten geteilt."; +/* No comment provided by engineer. */ +"The second preset operator in the app!" = "Der zweite voreingestellte Netzwerk-Betreiber in der App!"; + /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Wir haben das zweite Häkchen vermisst! ✅"; @@ -4469,6 +4706,9 @@ /* No comment provided by engineer. */ "The servers for new connections of your current chat profile **%@**." = "Mögliche Server für neue Verbindungen von Ihrem aktuellen Chat-Profil **%@**."; +/* No comment provided by engineer. */ +"The servers for new files of your current chat profile **%@**." = "Die Server Deines aktuellen Chat-Profils für neue Dateien **%@**."; + /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Der von Ihnen eingefügte Text ist kein SimpleX-Link."; @@ -4478,6 +4718,9 @@ /* No comment provided by engineer. */ "Themes" = "Design"; +/* No comment provided by engineer. */ +"These conditions will also apply for: **%@**." = "Diese Nutzungsbedingungen gelten auch für: **%@**."; + /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Diese Einstellungen betreffen Ihr aktuelles Profil **%@**."; @@ -4541,6 +4784,9 @@ /* No comment provided by engineer. */ "To make a new connection" = "Um eine Verbindung mit einem neuen Kontakt zu erstellen"; +/* No comment provided by engineer. */ +"To protect against your link being replaced, you can compare contact security codes." = "Zum Schutz vor dem Austausch Ihres Links können Sie die Sicherheitscodes Ihrer Kontakte vergleichen."; + /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Bild- und Sprachdateinamen enthalten UTC, um Informationen zur Zeitzone zu schützen."; @@ -4553,6 +4799,9 @@ /* No comment provided by engineer. */ "To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Zum Schutz Ihrer Privatsphäre verwendet SimpleX an Stelle von Benutzerkennungen, die von allen anderen Plattformen verwendet werden, Kennungen für Nachrichtenwarteschlangen, die für jeden Ihrer Kontakte individuell sind."; +/* No comment provided by engineer. */ +"To receive" = "Für den Empfang"; + /* No comment provided by engineer. */ "To record speech please grant permission to use Microphone." = "Bitte erteilen Sie für Sprach-Aufnahmen die Genehmigung das Mikrofon zu nutzen."; @@ -4565,9 +4814,15 @@ /* 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 **Ihre Chat-Profile** ein, um Ihr verborgenes Profil zu sehen."; +/* No comment provided by engineer. */ +"To send" = "Für das Senden"; + /* 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."; +/* No comment provided by engineer. */ +"To use the servers of **%@**, accept conditions of use." = "Um die Server von **%@** zu nutzen, müssen Sie dessen Nutzungsbedingungen akzeptieren."; + /* 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."; @@ -4625,6 +4880,9 @@ /* rcv group event chat item */ "unblocked %@" = "%@ wurde freigegeben"; +/* No comment provided by engineer. */ +"Undelivered messages" = "Nicht ausgelieferte Nachrichten"; + /* No comment provided by engineer. */ "Unexpected migration state" = "Unerwarteter Migrationsstatus"; @@ -4742,12 +5000,21 @@ /* No comment provided by engineer. */ "Use .onion hosts" = "Verwende .onion-Hosts"; +/* No comment provided by engineer. */ +"Use %@" = "Verwende %@"; + /* No comment provided by engineer. */ "Use chat" = "Verwenden Sie Chat"; /* No comment provided by engineer. */ "Use current profile" = "Aktuelles Profil nutzen"; +/* No comment provided by engineer. */ +"Use for files" = "Für Dateien verwenden"; + +/* No comment provided by engineer. */ +"Use for messages" = "Für Nachrichten verwenden"; + /* No comment provided by engineer. */ "Use for new connections" = "Für neue Verbindungen nutzen"; @@ -4772,6 +5039,9 @@ /* No comment provided by engineer. */ "Use server" = "Server nutzen"; +/* No comment provided by engineer. */ +"Use servers" = "Verwende Server"; + /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Verwenden Sie SimpleX-Chat-Server?"; @@ -4856,9 +5126,15 @@ /* No comment provided by engineer. */ "Videos and files up to 1gb" = "Videos und Dateien bis zu 1GB"; +/* No comment provided by engineer. */ +"View conditions" = "Nutzungsbedingungen anschauen"; + /* No comment provided by engineer. */ "View security code" = "Schauen Sie sich den Sicherheitscode an"; +/* No comment provided by engineer. */ +"View updated conditions" = "Aktualisierte Nutzungsbedingungen anschauen"; + /* chat feature */ "Visible history" = "Sichtbarer Nachrichtenverlauf"; @@ -4940,6 +5216,9 @@ /* No comment provided by engineer. */ "when IP hidden" = "Wenn die IP-Adresse versteckt ist"; +/* No comment provided by engineer. */ +"When more than one operator is enabled, none of them has metadata to learn who communicates with whom." = "Wenn mehrere Netzwerk-Betreiber aktiviert sind, hat keiner von ihnen Metadaten, um zu erfahren, wer mit wem kommuniziert."; + /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Wenn Sie ein Inkognito-Profil mit Jemandem teilen, wird dieses Profil auch für die Gruppen verwendet, für die Sie von diesem Kontakt eingeladen werden."; @@ -5048,6 +5327,12 @@ /* No comment provided by engineer. */ "You can change it in Appearance settings." = "Kann von Ihnen in den Erscheinungsbild-Einstellungen geändert werden."; +/* No comment provided by engineer. */ +"You can configure operators in Network & servers settings." = "Sie können die Betreiber in den Netzwerk- und Servereinstellungen konfigurieren."; + +/* No comment provided by engineer. */ +"You can configure servers via settings." = "Sie können die Server über die Einstellungen konfigurieren."; + /* No comment provided by engineer. */ "You can create it later" = "Sie können dies später erstellen"; @@ -5072,6 +5357,9 @@ /* No comment provided by engineer. */ "You can send messages to %@ from Archived contacts." = "Sie können aus den archivierten Kontakten heraus Nachrichten an %@ versenden."; +/* No comment provided by engineer. */ +"You can set connection name, to remember who the link was shared with." = "Sie können einen Verbindungsnamen festlegen, um sich zu merken, mit wem der Link geteilt wurde."; + /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Über die Geräte-Einstellungen können Sie die Benachrichtigungsvorschau im Sperrbildschirm erlauben."; @@ -5273,6 +5561,9 @@ /* No comment provided by engineer. */ "Your server address" = "Ihre Serveradresse"; +/* No comment provided by engineer. */ +"Your servers" = "Ihre Server"; + /* No comment provided by engineer. */ "Your settings" = "Einstellungen"; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 103065a05a..d02497515e 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -82,6 +82,9 @@ /* No comment provided by engineer. */ "**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Recomendado**: el token del dispositivo y las notificaciones se envían al servidor de notificaciones de SimpleX Chat, pero no el contenido del mensaje, su tamaño o su procedencia."; +/* No comment provided by engineer. */ +"**Scan / Paste link**: to connect via a link you received." = "**Escanear / Pegar enlace**: para conectar mediante un enlace recibido."; + /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Advertencia**: Las notificaciones automáticas instantáneas requieren una contraseña guardada en Keychain."; @@ -142,6 +145,12 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ está verificado"; +/* No comment provided by engineer. */ +"%@ server" = "%@ servidor"; + +/* No comment provided by engineer. */ +"%@ servers" = "%@ servidores"; + /* No comment provided by engineer. */ "%@ uploaded" = "%@ subido"; @@ -161,7 +170,7 @@ "%@:" = "%@:"; /* time interval */ -"%d days" = "%d días"; +"%d days" = "%d día(s)"; /* forward confirmation reason */ "%d file(s) are still being downloaded." = "%d archivo(s) se está(n) descargando todavía."; @@ -176,25 +185,25 @@ "%d file(s) were not downloaded." = "%d archivo(s) no se ha(n) descargado."; /* time interval */ -"%d hours" = "%d horas"; +"%d hours" = "%d hora(s)"; /* alert title */ -"%d messages not forwarded" = "%d mensajes no enviados"; +"%d messages not forwarded" = "%d mensaje(s) no enviado(s)"; /* time interval */ -"%d min" = "%d minutos"; +"%d min" = "%d minuto(s)"; /* time interval */ -"%d months" = "%d meses"; +"%d months" = "%d mes(es)"; /* time interval */ -"%d sec" = "%d segundos"; +"%d sec" = "%d segundo(s)"; /* integrity error chat item */ -"%d skipped message(s)" = "%d mensaje(s) saltado(s"; +"%d skipped message(s)" = "%d mensaje(s) omitido(s)"; /* time interval */ -"%d weeks" = "%d semanas"; +"%d weeks" = "%d semana(s)"; /* No comment provided by engineer. */ "%lld" = "%lld"; @@ -295,6 +304,12 @@ /* time interval */ "1 week" = "una semana"; +/* No comment provided by engineer. */ +"1-time link" = "Enlace de un uso"; + +/* No comment provided by engineer. */ +"1-time link can be used *with one contact only* - share in person or via any messenger." = "Los enlaces de un uso pueden ser usados *solamente con un contacto* - compártelos en persona o mediante cualquier aplicación de mensajería."; + /* No comment provided by engineer. */ "5 minutes" = "5 minutos"; @@ -342,6 +357,9 @@ swipe action */ "Accept" = "Aceptar"; +/* No comment provided by engineer. */ +"Accept conditions" = "Aceptar condiciones"; + /* No comment provided by engineer. */ "Accept connection request?" = "¿Aceptar solicitud de conexión?"; @@ -355,6 +373,9 @@ /* call status */ "accepted call" = "llamada aceptada"; +/* No comment provided by engineer. */ +"Accepted conditions" = "Condiciones aceptadas"; + /* No comment provided by engineer. */ "Acknowledged" = "Confirmaciones"; @@ -382,6 +403,12 @@ /* No comment provided by engineer. */ "Add welcome message" = "Añadir mensaje de bienvenida"; +/* No comment provided by engineer. */ +"Added media & file servers" = "Servidores de archivos y multimedia añadidos"; + +/* No comment provided by engineer. */ +"Added message servers" = "Servidores de mensajes añadidos"; + /* No comment provided by engineer. */ "Additional accent" = "Acento adicional"; @@ -397,6 +424,12 @@ /* No comment provided by engineer. */ "Address change will be aborted. Old receiving address will be used." = "El cambio de dirección se cancelará. Se usará la antigua dirección de recepción."; +/* No comment provided by engineer. */ +"Address or 1-time link?" = "¿Dirección o enlace de un uso?"; + +/* No comment provided by engineer. */ +"Address settings" = "Configuración de dirección"; + /* member role */ "admin" = "administrador"; @@ -439,6 +472,9 @@ /* feature role */ "all members" = "todos los miembros"; +/* No comment provided by engineer. */ +"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Todos los mensajes y archivos son enviados **cifrados de extremo a extremo** y con seguridad de cifrado postcuántico en mensajes directos."; + /* No comment provided by engineer. */ "All messages will be deleted - this cannot be undone!" = "Todos los mensajes serán borrados. ¡No podrá deshacerse!"; @@ -855,6 +891,9 @@ set passcode view */ "Change self-destruct passcode" = "Cambiar código autodestrucción"; +/* authentication reason */ +"Change user profiles" = "Cambiar perfil de usuario"; + /* chat item text */ "changed address for you" = "ha cambiado tu servidor de envío"; @@ -918,6 +957,12 @@ /* No comment provided by engineer. */ "Chats" = "Chats"; +/* No comment provided by engineer. */ +"Check messages every 20 min." = "Comprobar mensajes cada 20 min."; + +/* No comment provided by engineer. */ +"Check messages when allowed." = "Comprobar mensajes cuando se permita."; + /* alert title */ "Check server address and try again." = "Comprueba la dirección del servidor e inténtalo de nuevo."; @@ -978,6 +1023,33 @@ /* No comment provided by engineer. */ "Completed" = "Completadas"; +/* No comment provided by engineer. */ +"Conditions accepted on: %@." = "Condiciones aceptadas el: %@."; + +/* No comment provided by engineer. */ +"Conditions are accepted for the operator(s): **%@**." = "Las condiciones se han aceptado para el(los) operador(s): **%@**."; + +/* No comment provided by engineer. */ +"Conditions are already accepted for following operator(s): **%@**." = "Las condiciones ya se han aceptado para el/los siguiente(s) operador(s): **%@**."; + +/* No comment provided by engineer. */ +"Conditions of use" = "Condiciones de uso"; + +/* No comment provided by engineer. */ +"Conditions will be accepted for enabled operators after 30 days." = "Las condiciones de los operadores habilitados serán aceptadas después de 30 días."; + +/* No comment provided by engineer. */ +"Conditions will be accepted for operator(s): **%@**." = "Las condiciones serán aceptadas para el/los operador(es): **%@**."; + +/* No comment provided by engineer. */ +"Conditions will be accepted for the operator(s): **%@**." = "Las condiciones serán aceptadas para el/los operador(es): **%@**."; + +/* No comment provided by engineer. */ +"Conditions will be accepted on: %@." = "Las condiciones serán aceptadas el: %@."; + +/* No comment provided by engineer. */ +"Conditions will be automatically accepted for enabled operators on: %@." = "Las condiciones serán aceptadas automáticamente para los operadores habilitados el: %@."; + /* No comment provided by engineer. */ "Configure ICE servers" = "Configure servidores ICE"; @@ -1125,6 +1197,9 @@ /* No comment provided by engineer. */ "Connection request sent!" = "¡Solicitud de conexión enviada!"; +/* No comment provided by engineer. */ +"Connection security" = "Seguridad de conexión"; + /* No comment provided by engineer. */ "Connection terminated" = "Conexión finalizada"; @@ -1206,6 +1281,9 @@ /* No comment provided by engineer. */ "Create" = "Crear"; +/* No comment provided by engineer. */ +"Create 1-time link" = "Crear enlace de un uso"; + /* No comment provided by engineer. */ "Create a group using a random profile." = "Crear grupo usando perfil aleatorio."; @@ -1257,6 +1335,9 @@ /* No comment provided by engineer. */ "creator" = "creador"; +/* No comment provided by engineer. */ +"Current conditions text couldn't be loaded, you can review conditions via this link:" = "El texto con las condiciones actuales no se ha podido cargar, puedes revisar las condiciones en el siguiente enlace:"; + /* No comment provided by engineer. */ "Current Passcode" = "Código de Acceso"; @@ -1505,6 +1586,9 @@ /* No comment provided by engineer. */ "Deletion errors" = "Errores de eliminación"; +/* No comment provided by engineer. */ +"Delivered even when Apple drops them." = "Entregados incluso cuando Apple los descarta."; + /* No comment provided by engineer. */ "Delivery" = "Entrega"; @@ -1692,6 +1776,9 @@ /* No comment provided by engineer. */ "e2e encrypted" = "cifrado de extremo a extremo"; +/* No comment provided by engineer. */ +"E2E encrypted notifications." = "Notificaciones cifradas E2E."; + /* chat item action */ "Edit" = "Editar"; @@ -1710,6 +1797,9 @@ /* No comment provided by engineer. */ "Enable camera access" = "Permitir acceso a la cámara"; +/* No comment provided by engineer. */ +"Enable Flux" = "Habilitar Flux"; + /* No comment provided by engineer. */ "Enable for all" = "Activar para todos"; @@ -1869,12 +1959,18 @@ /* No comment provided by engineer. */ "Error aborting address change" = "Error al cancelar cambio de dirección"; +/* alert title */ +"Error accepting conditions" = "Error al aceptar las condiciones"; + /* No comment provided by engineer. */ "Error accepting contact request" = "Error al aceptar solicitud del contacto"; /* No comment provided by engineer. */ "Error adding member(s)" = "Error al añadir miembro(s)"; +/* alert title */ +"Error adding server" = "Error al añadir servidor"; + /* No comment provided by engineer. */ "Error changing address" = "Error al cambiar servidor"; @@ -1959,6 +2055,9 @@ /* No comment provided by engineer. */ "Error joining group" = "Error al unirte al grupo"; +/* alert title */ +"Error loading servers" = "Error al cargar servidores"; + /* No comment provided by engineer. */ "Error migrating settings" = "Error al migrar la configuración"; @@ -1992,6 +2091,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "Error al guardar contraseña en Keychain"; +/* alert title */ +"Error saving servers" = "Error al guardar servidores"; + /* when migrating */ "Error saving settings" = "Error al guardar ajustes"; @@ -2034,6 +2136,9 @@ /* No comment provided by engineer. */ "Error updating message" = "Error al actualizar mensaje"; +/* alert title */ +"Error updating server" = "Error al actualizar el servidor"; + /* No comment provided by engineer. */ "Error updating settings" = "Error al actualizar configuración"; @@ -2061,6 +2166,9 @@ /* No comment provided by engineer. */ "Errors" = "Errores"; +/* servers error */ +"Errors in servers configuration." = "Error en la configuración del servidor."; + /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Incluso si está desactivado para la conversación."; @@ -2187,9 +2295,24 @@ /* No comment provided by engineer. */ "Fix not supported by group member" = "Corrección no compatible con miembro del grupo"; +/* No comment provided by engineer. */ +"for better metadata privacy." = "para mayor privacidad de los metadatos."; + +/* servers error */ +"For chat profile %@:" = "Para el perfil de chat %@:"; + /* No comment provided by engineer. */ "For console" = "Para consola"; +/* No comment provided by engineer. */ +"For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server." = "Si por ejemplo tu contacto recibe los mensajes a través de un servidor de SimpleX Chat, tu aplicación los entregará a través de un servidor de Flux."; + +/* No comment provided by engineer. */ +"For private routing" = "Para el enrutamiento privado"; + +/* No comment provided by engineer. */ +"For social media" = "Para redes sociales"; + /* chat item action */ "Forward" = "Reenviar"; @@ -2382,6 +2505,12 @@ /* time unit */ "hours" = "horas"; +/* No comment provided by engineer. */ +"How it affects privacy" = "Cómo afecta a la privacidad"; + +/* No comment provided by engineer. */ +"How it helps privacy" = "Cómo ayuda a la privacidad"; + /* No comment provided by engineer. */ "How SimpleX works" = "Cómo funciona SimpleX"; @@ -2958,6 +3087,9 @@ /* No comment provided by engineer. */ "More reliable network connection." = "Conexión de red más fiable."; +/* No comment provided by engineer. */ +"More reliable notifications" = "Notificaciones más fiables"; + /* item status description */ "Most likely this connection is deleted." = "Probablemente la conexión ha sido eliminada."; @@ -2982,12 +3114,18 @@ /* No comment provided by engineer. */ "Network connection" = "Conexión de red"; +/* No comment provided by engineer. */ +"Network decentralization" = "Descentralización de la red"; + /* snd error text */ "Network issues - message expired after many attempts to send it." = "Problema en la red - el mensaje ha expirado tras muchos intentos de envío."; /* No comment provided by engineer. */ "Network management" = "Gestión de la red"; +/* No comment provided by engineer. */ +"Network operator" = "Operador de red"; + /* No comment provided by engineer. */ "Network settings" = "Configuración de red"; @@ -3015,6 +3153,9 @@ /* No comment provided by engineer. */ "New display name" = "Nuevo nombre mostrado"; +/* notification */ +"New events" = "Eventos nuevos"; + /* No comment provided by engineer. */ "New in %@" = "Nuevo en %@"; @@ -3036,6 +3177,9 @@ /* No comment provided by engineer. */ "New passphrase…" = "Contraseña nueva…"; +/* No comment provided by engineer. */ +"New server" = "Servidor nuevo"; + /* No comment provided by engineer. */ "New SOCKS credentials will be used every time you start the app." = "Se usarán credenciales SOCKS nuevas cada vez que inicies la aplicación."; @@ -3081,6 +3225,12 @@ /* No comment provided by engineer. */ "No info, try to reload" = "No hay información, intenta recargar"; +/* servers error */ +"No media & file servers." = "Ningún servidor de archivos y multimedia."; + +/* servers error */ +"No message servers." = "Ningún servidor de mensajes."; + /* No comment provided by engineer. */ "No network connection" = "Sin conexión de red"; @@ -3093,9 +3243,24 @@ /* No comment provided by engineer. */ "No permission to record voice message" = "Sin permiso para grabar mensajes de voz"; +/* No comment provided by engineer. */ +"No push server" = "Ningún servidor push"; + /* No comment provided by engineer. */ "No received or sent files" = "Sin archivos recibidos o enviados"; +/* servers error */ +"No servers for private message routing." = "Ningún servidor para enrutamiento privado."; + +/* servers error */ +"No servers to receive files." = "Ningún servidor para recibir archivos."; + +/* servers error */ +"No servers to receive messages." = "Ningún servidor para recibir mensajes."; + +/* servers error */ +"No servers to send files." = "Ningún servidor para enviar archivos."; + /* copied message info in history */ "no text" = "sin texto"; @@ -3117,6 +3282,9 @@ /* No comment provided by engineer. */ "Notifications are disabled!" = "¡Las notificaciones están desactivadas!"; +/* No comment provided by engineer. */ +"Notifications privacy" = "Privacidad en las notificaciones"; + /* No comment provided by engineer. */ "Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "Ahora los administradores pueden:\n- eliminar mensajes de los miembros.\n- desactivar el rol miembro (a rol \"observador\")"; @@ -3209,12 +3377,18 @@ /* No comment provided by engineer. */ "Open" = "Abrir"; +/* No comment provided by engineer. */ +"Open changes" = "Abrir cambios"; + /* No comment provided by engineer. */ "Open chat" = "Abrir chat"; /* authentication reason */ "Open chat console" = "Abrir consola de Chat"; +/* No comment provided by engineer. */ +"Open conditions" = "Abrir condiciones"; + /* No comment provided by engineer. */ "Open group" = "Grupo abierto"; @@ -3227,6 +3401,12 @@ /* No comment provided by engineer. */ "Opening app…" = "Iniciando aplicación…"; +/* No comment provided by engineer. */ +"Operator" = "Operador"; + +/* alert title */ +"Operator server" = "Servidor del operador"; + /* No comment provided by engineer. */ "Or paste archive link" = "O pegar enlace del archivo"; @@ -3239,6 +3419,9 @@ /* No comment provided by engineer. */ "Or show this code" = "O muestra este código QR"; +/* No comment provided by engineer. */ +"Or to share privately" = "O para compartir en privado"; + /* No comment provided by engineer. */ "other" = "otros"; @@ -3380,6 +3563,9 @@ /* No comment provided by engineer. */ "Preset server address" = "Dirección del servidor predefinida"; +/* No comment provided by engineer. */ +"Preset servers" = "Servidores predefinidos"; + /* No comment provided by engineer. */ "Preview" = "Vista previa"; @@ -3485,6 +3671,9 @@ /* No comment provided by engineer. */ "Push notifications" = "Notificaciones automáticas"; +/* No comment provided by engineer. */ +"Push Notifications" = "Notificaciones push"; + /* No comment provided by engineer. */ "Push server" = "Servidor push"; @@ -3732,6 +3921,12 @@ /* chat item action */ "Reveal" = "Revelar"; +/* No comment provided by engineer. */ +"Review conditions" = "Revisar condiciones"; + +/* No comment provided by engineer. */ +"Review later" = "Revisar más tarde"; + /* No comment provided by engineer. */ "Revoke" = "Revocar"; @@ -3753,6 +3948,12 @@ /* No comment provided by engineer. */ "Safer groups" = "Grupos más seguros"; +/* No comment provided by engineer. */ +"Same conditions will apply to operator **%@**." = "Las mismas condiciones se aplicarán al operador **%@**."; + +/* No comment provided by engineer. */ +"Same conditions will apply to operator(s): **%@**." = "Las mismas condiciones se aplicarán a el/los operador(es) **%@**."; + /* alert button chat item action */ "Save" = "Guardar"; @@ -4018,6 +4219,9 @@ /* No comment provided by engineer. */ "Server" = "Servidor"; +/* alert message */ +"Server added to operator %@." = "Servidor añadido al operador %@."; + /* No comment provided by engineer. */ "Server address" = "Dirección del servidor"; @@ -4027,6 +4231,15 @@ /* srv error text. */ "Server address is incompatible with network settings." = "La dirección del servidor es incompatible con la configuración de la red."; +/* alert title */ +"Server operator changed." = "El operador del servidor ha cambiado."; + +/* No comment provided by engineer. */ +"Server operators" = "Operadores de servidores"; + +/* alert title */ +"Server protocol changed." = "El protocolo del servidor ha cambiado."; + /* queue info */ "server queue info: %@\n\nlast received msg: %@" = "información cola del servidor: %1$@\n\núltimo mensaje recibido: %2$@"; @@ -4112,9 +4325,15 @@ /* No comment provided by engineer. */ "Share 1-time link" = "Compartir enlace de un uso"; +/* No comment provided by engineer. */ +"Share 1-time link with a friend" = "Compartir enlace de un uso con un amigo"; + /* No comment provided by engineer. */ "Share address" = "Compartir dirección"; +/* No comment provided by engineer. */ +"Share address publicly" = "Campartir dirección públicamente"; + /* alert title */ "Share address with contacts?" = "¿Compartir la dirección con los contactos?"; @@ -4127,6 +4346,9 @@ /* No comment provided by engineer. */ "Share profile" = "Comparte perfil"; +/* No comment provided by engineer. */ +"Share SimpleX address on social media." = "Compartir dirección SimpleX en redes sociales."; + /* No comment provided by engineer. */ "Share this 1-time invite link" = "Comparte este enlace de un solo uso"; @@ -4172,6 +4394,12 @@ /* No comment provided by engineer. */ "SimpleX Address" = "Dirección SimpleX"; +/* No comment provided by engineer. */ +"SimpleX address and 1-time links are safe to share via any messenger." = "Compartir enlaces de un uso y direcciones SimpleX es seguro a través de cualquier medio."; + +/* No comment provided by engineer. */ +"SimpleX address or 1-time link?" = "Dirección SimpleX o enlace de un uso?"; + /* No comment provided by engineer. */ "SimpleX Chat security was audited by Trail of Bits." = "La seguridad de SimpleX Chat ha sido auditada por Trail of Bits."; @@ -4247,6 +4475,9 @@ /* No comment provided by engineer. */ "Some non-fatal errors occurred during import:" = "Han ocurrido algunos errores no críticos durante la importación:"; +/* alert message */ +"Some servers failed the test:\n%@" = "Algunos servidores no han superado la prueba:\n%@"; + /* notification title */ "Somebody" = "Alguien"; @@ -4409,6 +4640,9 @@ /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "La aplicación puede notificarte cuando recibas mensajes o solicitudes de contacto: por favor, abre la configuración para activarlo."; +/* No comment provided by engineer. */ +"The app protects your privacy by using different operators in each conversation." = "La aplicación protege tu privacidad mediante el uso de diferentes operadores en cada conversación."; + /* No comment provided by engineer. */ "The app will ask to confirm downloads from unknown file servers (except .onion)." = "La aplicación pedirá que confirmes las descargas desde servidores de archivos desconocidos (excepto si son .onion)."; @@ -4418,6 +4652,9 @@ /* No comment provided by engineer. */ "The code you scanned is not a SimpleX link QR code." = "El código QR escaneado no es un enlace SimpleX."; +/* No comment provided by engineer. */ +"The connection reached the limit of undelivered messages, your contact may be offline." = "La conexión ha alcanzado el límite de mensajes no entregados. es posible que tu contacto esté desconectado."; + /* No comment provided by engineer. */ "The connection you accepted will be cancelled!" = "¡La conexión que has aceptado se cancelará!"; @@ -4457,6 +4694,9 @@ /* No comment provided by engineer. */ "The profile is only shared with your contacts." = "El perfil sólo se comparte con tus contactos."; +/* No comment provided by engineer. */ +"The second preset operator in the app!" = "El segundo operador predefinido!"; + /* No comment provided by engineer. */ "The second tick we missed! ✅" = "¡El doble check que nos faltaba! ✅"; @@ -4466,6 +4706,9 @@ /* No comment provided by engineer. */ "The servers for new connections of your current chat profile **%@**." = "Lista de servidores para las conexiones nuevas de tu perfil actual **%@**."; +/* No comment provided by engineer. */ +"The servers for new files of your current chat profile **%@**." = "Los servidores para archivos nuevos en tu perfil actual **%@**."; + /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "El texto pegado no es un enlace SimpleX."; @@ -4475,6 +4718,9 @@ /* No comment provided by engineer. */ "Themes" = "Temas"; +/* No comment provided by engineer. */ +"These conditions will also apply for: **%@**." = "Estas condiciones también se aplican para: **%@**."; + /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Esta configuración afecta a tu perfil actual **%@**."; @@ -4538,6 +4784,9 @@ /* No comment provided by engineer. */ "To make a new connection" = "Para hacer una conexión nueva"; +/* No comment provided by engineer. */ +"To protect against your link being replaced, you can compare contact security codes." = "Para protegerte contra una sustitución del enlace, puedes comparar los códigos de seguridad con tu contacto."; + /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Para proteger la zona horaria, los archivos de imagen/voz usan la hora UTC."; @@ -4550,6 +4799,9 @@ /* No comment provided by engineer. */ "To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Para proteger tu privacidad, en lugar de los identificadores de usuario que usan el resto de plataformas, SimpleX dispone de identificadores para las colas de mensajes, independientes para cada uno de tus contactos."; +/* No comment provided by engineer. */ +"To receive" = "Para recibir"; + /* No comment provided by engineer. */ "To record speech please grant permission to use Microphone." = "Para grabación de voz, por favor concede el permiso para usar el micrófono."; @@ -4562,9 +4814,15 @@ /* No comment provided by engineer. */ "To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page." = "Para hacer visible tu perfil oculto, introduce la contraseña en el campo de búsqueda del menú **Mis perfiles**."; +/* No comment provided by engineer. */ +"To send" = "Para enviar"; + /* No comment provided by engineer. */ "To support instant push notifications the chat database has to be migrated." = "Para permitir las notificaciones automáticas instantáneas, la base de datos se debe migrar."; +/* No comment provided by engineer. */ +"To use the servers of **%@**, accept conditions of use." = "Para usar los servidores de **%@**, acepta las condiciones de uso."; + /* No comment provided by engineer. */ "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Para verificar el cifrado de extremo a extremo con tu contacto, compara (o escanea) el código en ambos dispositivos."; @@ -4622,6 +4880,9 @@ /* rcv group event chat item */ "unblocked %@" = "ha desbloqueado a %@"; +/* No comment provided by engineer. */ +"Undelivered messages" = "Mensajes no entregados"; + /* No comment provided by engineer. */ "Unexpected migration state" = "Estado de migración inesperado"; @@ -4739,12 +5000,21 @@ /* No comment provided by engineer. */ "Use .onion hosts" = "Usar hosts .onion"; +/* No comment provided by engineer. */ +"Use %@" = "Usar %@"; + /* No comment provided by engineer. */ "Use chat" = "Usar Chat"; /* No comment provided by engineer. */ "Use current profile" = "Usar perfil actual"; +/* No comment provided by engineer. */ +"Use for files" = "Usar para archivos"; + +/* No comment provided by engineer. */ +"Use for messages" = "Usar para mensajes"; + /* No comment provided by engineer. */ "Use for new connections" = "Usar para conexiones nuevas"; @@ -4769,6 +5039,9 @@ /* No comment provided by engineer. */ "Use server" = "Usar servidor"; +/* No comment provided by engineer. */ +"Use servers" = "Usar servidores"; + /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "¿Usar servidores SimpleX Chat?"; @@ -4853,9 +5126,15 @@ /* No comment provided by engineer. */ "Videos and files up to 1gb" = "Vídeos y archivos de hasta 1Gb"; +/* No comment provided by engineer. */ +"View conditions" = "Ver condiciones"; + /* No comment provided by engineer. */ "View security code" = "Mostrar código de seguridad"; +/* No comment provided by engineer. */ +"View updated conditions" = "Ver condiciones actualizadas"; + /* chat feature */ "Visible history" = "Historial visible"; @@ -4937,6 +5216,9 @@ /* No comment provided by engineer. */ "when IP hidden" = "con IP oculta"; +/* No comment provided by engineer. */ +"When more than one operator is enabled, none of them has metadata to learn who communicates with whom." = "Cuando está habilitado más de un operador, ninguno dispone de los metadatos para conocer quién se comunica con quién."; + /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Cuando compartes un perfil incógnito con alguien, este perfil también se usará para los grupos a los que te inviten."; @@ -5045,6 +5327,12 @@ /* No comment provided by engineer. */ "You can change it in Appearance settings." = "Puedes cambiar la posición de la barra desde el menú Apariencia."; +/* No comment provided by engineer. */ +"You can configure operators in Network & servers settings." = "Puedes configurar los operadores desde Servidores y Redes."; + +/* No comment provided by engineer. */ +"You can configure servers via settings." = "Puedes configurar los servidores a través de su configuración."; + /* No comment provided by engineer. */ "You can create it later" = "Puedes crearla más tarde"; @@ -5069,6 +5357,9 @@ /* No comment provided by engineer. */ "You can send messages to %@ from Archived contacts." = "Puedes enviar mensajes a %@ desde Contactos archivados."; +/* No comment provided by engineer. */ +"You can set connection name, to remember who the link was shared with." = "Puedes añadir un nombre a la conexión para recordar a quién corresponde."; + /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Puedes configurar las notificaciones de la pantalla de bloqueo desde Configuración."; @@ -5270,6 +5561,9 @@ /* No comment provided by engineer. */ "Your server address" = "Dirección del servidor"; +/* No comment provided by engineer. */ +"Your servers" = "Tus servidores"; + /* No comment provided by engineer. */ "Your settings" = "Configuración"; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index b64c75fd1d..9103f4baf3 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -82,6 +82,9 @@ /* No comment provided by engineer. */ "**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Megjegyzés:** az eszköztoken és az értesítések elküldésre kerülnek a SimpleX Chat értesítési kiszolgálóra, kivéve az üzenet tartalma, mérete vagy az, hogy kitől származik."; +/* No comment provided by engineer. */ +"**Scan / Paste link**: to connect via a link you received." = "**Hivatkozás beolvasása / beillesztése**: egy kapott hivatkozáson keresztüli kapcsolódáshoz."; + /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Figyelmeztetés:** Az azonnali push-értesítésekhez a kulcstartóban tárolt jelmondat megadása szükséges."; @@ -101,7 +104,7 @@ "## In reply to" = "## Válaszul erre:"; /* No comment provided by engineer. */ -"#secret#" = "#titkos#"; +"#secret#" = "#titok#"; /* No comment provided by engineer. */ "%@" = "%@"; @@ -142,6 +145,12 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ hitelesítve"; +/* No comment provided by engineer. */ +"%@ server" = "%@ kiszolgáló"; + +/* No comment provided by engineer. */ +"%@ servers" = "%@ kiszolgáló"; + /* No comment provided by engineer. */ "%@ uploaded" = "%@ feltöltve"; @@ -230,7 +239,7 @@ "%lld minutes" = "%lld perc"; /* No comment provided by engineer. */ -"%lld new interface languages" = "%lld új nyelvi csomag"; +"%lld new interface languages" = "%lld új kezelőfelületi nyelv"; /* No comment provided by engineer. */ "%lld second(s)" = "%lld másodperc"; @@ -295,6 +304,12 @@ /* time interval */ "1 week" = "1 hét"; +/* No comment provided by engineer. */ +"1-time link" = "Egyszer használható meghívó-hivatkozás"; + +/* No comment provided by engineer. */ +"1-time link can be used *with one contact only* - share in person or via any messenger." = "Az egyszer használható meghívó-hivatkozás csak *egyetlen ismerőssel használható* - személyesen vagy bármilyen üzenetküldőn keresztül megosztható."; + /* No comment provided by engineer. */ "5 minutes" = "5 perc"; @@ -342,6 +357,9 @@ swipe action */ "Accept" = "Elfogadás"; +/* No comment provided by engineer. */ +"Accept conditions" = "Feltételek elfogadása"; + /* No comment provided by engineer. */ "Accept connection request?" = "Kapcsolatkérés elfogadása?"; @@ -355,6 +373,9 @@ /* call status */ "accepted call" = "elfogadott hívás"; +/* No comment provided by engineer. */ +"Accepted conditions" = "Elfogadott feltételek"; + /* No comment provided by engineer. */ "Acknowledged" = "Nyugtázva"; @@ -382,6 +403,12 @@ /* No comment provided by engineer. */ "Add welcome message" = "Üdvözlőüzenet hozzáadása"; +/* No comment provided by engineer. */ +"Added media & file servers" = "Hozzáadott média- és fájlkiszolgálók"; + +/* No comment provided by engineer. */ +"Added message servers" = "Hozzáadott üzenetkiszolgálók"; + /* No comment provided by engineer. */ "Additional accent" = "További kiemelés"; @@ -397,6 +424,12 @@ /* No comment provided by engineer. */ "Address change will be aborted. Old receiving address will be used." = "A cím módosítása megszakad. A régi fogadási cím kerül felhasználásra."; +/* No comment provided by engineer. */ +"Address or 1-time link?" = "Cím vagy egyszer használható meghívó-hivatkozás?"; + +/* No comment provided by engineer. */ +"Address settings" = "Címbeállítások"; + /* member role */ "admin" = "adminisztrátor"; @@ -439,6 +472,9 @@ /* feature role */ "all members" = "összes tag"; +/* No comment provided by engineer. */ +"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Az összes üzenetet és fájlt **végpontok közötti titkosítással** küldi, a közvetlen üzenetekben pedig kvantumrezisztens biztonsággal."; + /* No comment provided by engineer. */ "All messages will be deleted - this cannot be undone!" = "Az összes üzenet törlésre kerül – ez a művelet nem vonható vissza!"; @@ -855,6 +891,9 @@ set passcode view */ "Change self-destruct passcode" = "Önmegsemmisító jelkód megváltoztatása"; +/* authentication reason */ +"Change user profiles" = "Felhasználói profilok megváltoztatása"; + /* chat item text */ "changed address for you" = "cím megváltoztatva"; @@ -918,6 +957,12 @@ /* No comment provided by engineer. */ "Chats" = "Csevegések"; +/* No comment provided by engineer. */ +"Check messages every 20 min." = "Üzenetek ellenőrzése 20 percenként."; + +/* No comment provided by engineer. */ +"Check messages when allowed." = "Üzenetek ellenőrzése, amikor engedélyezett."; + /* alert title */ "Check server address and try again." = "Kiszolgáló címének ellenőrzése és újrapróbálkozás."; @@ -978,6 +1023,33 @@ /* No comment provided by engineer. */ "Completed" = "Elkészült"; +/* No comment provided by engineer. */ +"Conditions accepted on: %@." = "Feltételek elfogadva ekkor: %@."; + +/* No comment provided by engineer. */ +"Conditions are accepted for the operator(s): **%@**." = "A következő üzemeltető(k) számára elfogadott feltételek: **%@**."; + +/* No comment provided by engineer. */ +"Conditions are already accepted for following operator(s): **%@**." = "A feltételek már el lettek fogadva a következő üzemeltető(k) számára: **%@**."; + +/* No comment provided by engineer. */ +"Conditions of use" = "Használati feltételek"; + +/* No comment provided by engineer. */ +"Conditions will be accepted for enabled operators after 30 days." = "A feltételek 30 nap elteltével lesznek elfogadva az engedélyezett üzemeltető számára."; + +/* No comment provided by engineer. */ +"Conditions will be accepted for operator(s): **%@**." = "A feltételek el lesznek fogadva a következő üzemeltető(k) számára: **%@**."; + +/* No comment provided by engineer. */ +"Conditions will be accepted for the operator(s): **%@**." = "A feltételek el lesznek fogadva a következő üzemeltető(k) számára: **%@**."; + +/* No comment provided by engineer. */ +"Conditions will be accepted on: %@." = "A feltételek ekkor lesznek elfogadva: %@."; + +/* No comment provided by engineer. */ +"Conditions will be automatically accepted for enabled operators on: %@." = "A feltételek automatikusan elfogadásra kerülnek az engedélyezett üzemeltető számára: %@."; + /* No comment provided by engineer. */ "Configure ICE servers" = "ICE-kiszolgálók beállítása"; @@ -1033,7 +1105,7 @@ "Connect to yourself?" = "Kapcsolódás saját magához?"; /* No comment provided by engineer. */ -"Connect to yourself?\nThis is your own one-time link!" = "Kapcsolódás saját magához?\nEz az Ön egyszer használható hivatkozása!"; +"Connect to yourself?\nThis is your own one-time link!" = "Kapcsolódás saját magához?\nEz az Ön egyszer használható meghívó-hivatkozása!"; /* No comment provided by engineer. */ "Connect to yourself?\nThis is your own SimpleX address!" = "Kapcsolódás saját magához?\nEz az Ön SimpleX-címe!"; @@ -1045,7 +1117,7 @@ "Connect via link" = "Kapcsolódás egy hivatkozáson keresztül"; /* No comment provided by engineer. */ -"Connect via one-time link" = "Kapcsolódás egyszer használható hivatkozáson keresztül"; +"Connect via one-time link" = "Kapcsolódás egyszer használható meghívó-hivatkozáson keresztül"; /* No comment provided by engineer. */ "Connect with %@" = "Kapcsolódás a következővel: %@"; @@ -1125,6 +1197,9 @@ /* No comment provided by engineer. */ "Connection request sent!" = "Kapcsolatkérés elküldve!"; +/* No comment provided by engineer. */ +"Connection security" = "Kapcsolatbiztonság"; + /* No comment provided by engineer. */ "Connection terminated" = "Kapcsolat megszakítva"; @@ -1206,6 +1281,9 @@ /* No comment provided by engineer. */ "Create" = "Létrehozás"; +/* No comment provided by engineer. */ +"Create 1-time link" = "Egyszer használható meghívó-hivatkozás létrehozása"; + /* No comment provided by engineer. */ "Create a group using a random profile." = "Csoport létrehozása véletlenszerűen létrehozott profillal."; @@ -1257,6 +1335,9 @@ /* No comment provided by engineer. */ "creator" = "készítő"; +/* No comment provided by engineer. */ +"Current conditions text couldn't be loaded, you can review conditions via this link:" = "A jelenlegi feltételek szövegét nem lehetett betölteni, a feltételeket ezen a hivatkozáson keresztül vizsgálhatja felül:"; + /* No comment provided by engineer. */ "Current Passcode" = "Jelenlegi jelkód"; @@ -1505,6 +1586,9 @@ /* No comment provided by engineer. */ "Deletion errors" = "Törlési hibák"; +/* No comment provided by engineer. */ +"Delivered even when Apple drops them." = "Kézbesítés akkor is, amikor az Apple eldobja őket."; + /* No comment provided by engineer. */ "Delivery" = "Kézbesítés"; @@ -1692,6 +1776,9 @@ /* No comment provided by engineer. */ "e2e encrypted" = "e2e titkosított"; +/* No comment provided by engineer. */ +"E2E encrypted notifications." = "Végpontok közötti titkosított értesítések."; + /* chat item action */ "Edit" = "Szerkesztés"; @@ -1710,6 +1797,9 @@ /* No comment provided by engineer. */ "Enable camera access" = "Kamera hozzáférés engedélyezése"; +/* No comment provided by engineer. */ +"Enable Flux" = "Flux engedélyezése"; + /* No comment provided by engineer. */ "Enable for all" = "Engedélyezés az összes tag számára"; @@ -1869,12 +1959,18 @@ /* No comment provided by engineer. */ "Error aborting address change" = "Hiba a cím megváltoztatásának megszakításakor"; +/* alert title */ +"Error accepting conditions" = "Hiba a feltételek elfogadásakor"; + /* No comment provided by engineer. */ "Error accepting contact request" = "Hiba történt a kapcsolatkérés elfogadásakor"; /* No comment provided by engineer. */ "Error adding member(s)" = "Hiba a tag(ok) hozzáadásakor"; +/* alert title */ +"Error adding server" = "Hiba a kiszolgáló hozzáadásakor"; + /* No comment provided by engineer. */ "Error changing address" = "Hiba a cím megváltoztatásakor"; @@ -1959,6 +2055,9 @@ /* No comment provided by engineer. */ "Error joining group" = "Hiba a csoporthoz való csatlakozáskor"; +/* alert title */ +"Error loading servers" = "Hiba a kiszolgálók betöltésekor"; + /* No comment provided by engineer. */ "Error migrating settings" = "Hiba a beallítások átköltöztetésekor"; @@ -1992,6 +2091,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "Hiba a jelmondat kulcstartóba történő mentésekor"; +/* alert title */ +"Error saving servers" = "Hiba a kiszolgálók mentésekor"; + /* when migrating */ "Error saving settings" = "Hiba a beállítások mentésekor"; @@ -2034,6 +2136,9 @@ /* No comment provided by engineer. */ "Error updating message" = "Hiba az üzenet frissítésekor"; +/* alert title */ +"Error updating server" = "Hiba a kiszolgáló frissítésekor"; + /* No comment provided by engineer. */ "Error updating settings" = "Hiba történt a beállítások frissítésekor"; @@ -2061,6 +2166,9 @@ /* No comment provided by engineer. */ "Errors" = "Hibák"; +/* servers error */ +"Errors in servers configuration." = "Hibák a kiszolgálók konfigurációjában."; + /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Akkor is, ha le van tiltva a beszélgetésben."; @@ -2187,9 +2295,24 @@ /* No comment provided by engineer. */ "Fix not supported by group member" = "Csoporttag általi javítás nem támogatott"; +/* No comment provided by engineer. */ +"for better metadata privacy." = "a metaadatok jobb védelme érdekében."; + +/* servers error */ +"For chat profile %@:" = "A(z) %@ nevű csevegési profilhoz:"; + /* No comment provided by engineer. */ "For console" = "Konzolhoz"; +/* No comment provided by engineer. */ +"For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server." = "Ha például az ismerőse a SimpleX Chat kiszolgálón keresztül fogadja az üzeneteket, az Ön alkalmazása a Flux egyik kiszolgálóját használja a kézbesítéshez."; + +/* No comment provided by engineer. */ +"For private routing" = "A privát útválasztáshoz"; + +/* No comment provided by engineer. */ +"For social media" = "A közösségi médiához"; + /* chat item action */ "Forward" = "Továbbítás"; @@ -2382,6 +2505,12 @@ /* time unit */ "hours" = "óra"; +/* No comment provided by engineer. */ +"How it affects privacy" = "Hogyan érinti az adatvédelmet"; + +/* No comment provided by engineer. */ +"How it helps privacy" = "Hogyan segíti az adatvédelmet"; + /* No comment provided by engineer. */ "How SimpleX works" = "Hogyan működik a SimpleX"; @@ -2488,7 +2617,7 @@ "incognito via group link" = "inkognitó a csoporthivatkozáson keresztül"; /* chat list item description */ -"incognito via one-time link" = "inkognitó egy egyszer használható hivatkozáson keresztül"; +"incognito via one-time link" = "inkognitó egy egyszer használható meghívó-hivatkozáson keresztül"; /* notification */ "Incoming audio call" = "Bejövő hanghívás"; @@ -2530,7 +2659,7 @@ "Instant push notifications will be hidden!\n" = "Az azonnali push-értesítések elrejtésre kerülnek!\n"; /* No comment provided by engineer. */ -"Interface" = "Felület"; +"Interface" = "Kezelőfelület"; /* No comment provided by engineer. */ "Interface colors" = "Kezelőfelület színei"; @@ -2620,7 +2749,7 @@ "Irreversible message deletion is prohibited in this group." = "Az üzenetek végleges törlése le van tiltva ebben a csoportban."; /* No comment provided by engineer. */ -"It allows having many anonymous connections without any shared data between them in a single chat profile." = "Lehetővé teszi, hogy egyetlen csevegőprofilon belül több anonim kapcsolat legyen, anélkül, hogy megosztott adatok lennének közöttük."; +"It allows having many anonymous connections without any shared data between them in a single chat profile." = "Lehetővé teszi, hogy egyetlen csevegőprofilon belül több névtelen kapcsolat legyen, anélkül, hogy megosztott adatok lennének közöttük."; /* No comment provided by engineer. */ "It can happen when you or your connection used the old database backup." = "Ez akkor fordulhat elő, ha Ön vagy az ismerőse régi adatbázis biztonsági mentést használt."; @@ -2958,6 +3087,9 @@ /* No comment provided by engineer. */ "More reliable network connection." = "Megbízhatóbb hálózati kapcsolat."; +/* No comment provided by engineer. */ +"More reliable notifications" = "Megbízhatóbb értesítések"; + /* item status description */ "Most likely this connection is deleted." = "Valószínűleg ez a kapcsolat törlésre került."; @@ -2982,12 +3114,18 @@ /* No comment provided by engineer. */ "Network connection" = "Internetkapcsolat"; +/* No comment provided by engineer. */ +"Network decentralization" = "Hálózati decentralizáció"; + /* snd error text */ "Network issues - message expired after many attempts to send it." = "Hálózati problémák - az üzenet többszöri elküldési kísérlet után lejárt."; /* No comment provided by engineer. */ "Network management" = "Hálózatkezelés"; +/* No comment provided by engineer. */ +"Network operator" = "Hálózati üzemeltető"; + /* No comment provided by engineer. */ "Network settings" = "Hálózati beállítások"; @@ -3015,6 +3153,9 @@ /* No comment provided by engineer. */ "New display name" = "Új megjelenítési név"; +/* notification */ +"New events" = "Új események"; + /* No comment provided by engineer. */ "New in %@" = "Újdonságok a(z) %@ verzióban"; @@ -3025,7 +3166,7 @@ "New member role" = "Új tag szerepköre"; /* notification */ -"new message" = "Rejtett üzenet"; +"new message" = "új üzenet"; /* notification */ "New message" = "Új üzenet"; @@ -3036,6 +3177,9 @@ /* No comment provided by engineer. */ "New passphrase…" = "Új jelmondat…"; +/* No comment provided by engineer. */ +"New server" = "Új kiszolgáló"; + /* No comment provided by engineer. */ "New SOCKS credentials will be used every time you start the app." = "Minden alkalommal, amikor elindítja az alkalmazást, új SOCKS-hitelesítő-adatokat fog használni."; @@ -3081,6 +3225,12 @@ /* No comment provided by engineer. */ "No info, try to reload" = "Nincs információ, próbálja meg újratölteni"; +/* servers error */ +"No media & file servers." = "Nincsenek média- és fájlkiszolgálók."; + +/* servers error */ +"No message servers." = "Nincsenek üzenet-kiszolgálók."; + /* No comment provided by engineer. */ "No network connection" = "Nincs hálózati kapcsolat"; @@ -3099,6 +3249,18 @@ /* No comment provided by engineer. */ "No received or sent files" = "Nincsenek fogadott vagy küldött fájlok"; +/* servers error */ +"No servers for private message routing." = "Nincsenek kiszolgálók a privát üzenet-útválasztáshoz."; + +/* servers error */ +"No servers to receive files." = "Nincsenek fájlfogadó-kiszolgálók."; + +/* servers error */ +"No servers to receive messages." = "Nincsenek üzenetfogadó-kiszolgálók."; + +/* servers error */ +"No servers to send files." = "Nincsenek fájlküldő-kiszolgálók."; + /* copied message info in history */ "no text" = "nincs szöveg"; @@ -3120,6 +3282,9 @@ /* No comment provided by engineer. */ "Notifications are disabled!" = "Az értesítések le vannak tiltva!"; +/* No comment provided by engineer. */ +"Notifications privacy" = "Értesítési adatvédelem"; + /* No comment provided by engineer. */ "Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "Most már az adminisztrátorok is:\n- törölhetik a tagok üzeneteit.\n- letilthatnak tagokat („megfigyelő” szerepkör)"; @@ -3212,12 +3377,18 @@ /* No comment provided by engineer. */ "Open" = "Megnyitás"; +/* No comment provided by engineer. */ +"Open changes" = "Változások megnyitása"; + /* No comment provided by engineer. */ "Open chat" = "Csevegés megnyitása"; /* authentication reason */ "Open chat console" = "Csevegés konzol megnyitása"; +/* No comment provided by engineer. */ +"Open conditions" = "Feltételek megnyitása"; + /* No comment provided by engineer. */ "Open group" = "Csoport megnyitása"; @@ -3230,6 +3401,12 @@ /* No comment provided by engineer. */ "Opening app…" = "Az alkalmazás megnyitása…"; +/* No comment provided by engineer. */ +"Operator" = "Üzemeltető"; + +/* alert title */ +"Operator server" = "Kiszolgáló üzemeltető"; + /* No comment provided by engineer. */ "Or paste archive link" = "Vagy az archívum hivatkozásának beillesztése"; @@ -3242,6 +3419,9 @@ /* No comment provided by engineer. */ "Or show this code" = "Vagy mutassa meg ezt a kódot"; +/* No comment provided by engineer. */ +"Or to share privately" = "Vagy a privát megosztáshoz"; + /* No comment provided by engineer. */ "other" = "egyéb"; @@ -3383,6 +3563,9 @@ /* No comment provided by engineer. */ "Preset server address" = "Előre beállított kiszolgáló címe"; +/* No comment provided by engineer. */ +"Preset servers" = "Előre beállított kiszolgálók"; + /* No comment provided by engineer. */ "Preview" = "Előnézet"; @@ -3488,6 +3671,9 @@ /* No comment provided by engineer. */ "Push notifications" = "Push-értesítések"; +/* No comment provided by engineer. */ +"Push Notifications" = "Push értesítések"; + /* No comment provided by engineer. */ "Push server" = "Push-kiszolgáló"; @@ -3735,6 +3921,12 @@ /* chat item action */ "Reveal" = "Felfedés"; +/* No comment provided by engineer. */ +"Review conditions" = "Feltételek felülvizsgálata"; + +/* No comment provided by engineer. */ +"Review later" = "Felülvizsgálat később"; + /* No comment provided by engineer. */ "Revoke" = "Visszavonás"; @@ -3756,6 +3948,12 @@ /* No comment provided by engineer. */ "Safer groups" = "Biztonságosabb csoportok"; +/* No comment provided by engineer. */ +"Same conditions will apply to operator **%@**." = "Ugyanezek a feltételek vonatkoznak a következő üzemeltetőre is: **%@**."; + +/* No comment provided by engineer. */ +"Same conditions will apply to operator(s): **%@**." = "Ugyanezek a feltételek lesznek elfogadva a következő üzemeltető(k)re is: **%@**."; + /* alert button chat item action */ "Save" = "Mentés"; @@ -4021,6 +4219,9 @@ /* No comment provided by engineer. */ "Server" = "Kiszolgáló"; +/* alert message */ +"Server added to operator %@." = "Kiszolgáló hozzáadva a következő üzemeltetőhöz: %@."; + /* No comment provided by engineer. */ "Server address" = "Kiszolgáló címe"; @@ -4030,6 +4231,15 @@ /* srv error text. */ "Server address is incompatible with network settings." = "A kiszolgáló címe nem kompatibilis a hálózati beállításokkal."; +/* alert title */ +"Server operator changed." = "A kiszolgáló üzemeltetője megváltozott."; + +/* No comment provided by engineer. */ +"Server operators" = "Kiszolgáló-üzemeltetők"; + +/* alert title */ +"Server protocol changed." = "A kiszolgáló-protokoll megváltozott."; + /* queue info */ "server queue info: %@\n\nlast received msg: %@" = "a kiszolgáló üzenet-sorbaállítási információi: %1$@\n\nutoljára fogadott üzenet: %2$@"; @@ -4115,9 +4325,15 @@ /* No comment provided by engineer. */ "Share 1-time link" = "Egyszer használható hivatkozás megosztása"; +/* No comment provided by engineer. */ +"Share 1-time link with a friend" = "Egyszer használható meghívó-hivatkozás megosztása egy baráttal"; + /* No comment provided by engineer. */ "Share address" = "Cím megosztása"; +/* No comment provided by engineer. */ +"Share address publicly" = "Cím nyilvános megosztása"; + /* alert title */ "Share address with contacts?" = "Megosztja a címet az ismerőseivel?"; @@ -4130,6 +4346,9 @@ /* No comment provided by engineer. */ "Share profile" = "Profil megosztása"; +/* No comment provided by engineer. */ +"Share SimpleX address on social media." = "SimpleX-cím megosztása a közösségi médiában."; + /* No comment provided by engineer. */ "Share this 1-time invite link" = "Egyszer használható meghívó-hivatkozás megosztása"; @@ -4175,6 +4394,12 @@ /* No comment provided by engineer. */ "SimpleX Address" = "SimpleX-cím"; +/* No comment provided by engineer. */ +"SimpleX address and 1-time links are safe to share via any messenger." = "A SimpleX-cím és az egyszer használható meghívó-hivatkozás biztonságosan megosztható bármilyen üzenetküldőn keresztül."; + +/* No comment provided by engineer. */ +"SimpleX address or 1-time link?" = "SimpleX-cím vagy egyszer használható meghívó-hivatkozás?"; + /* No comment provided by engineer. */ "SimpleX Chat security was audited by Trail of Bits." = "A SimpleX Chat biztonsága a Trail of Bits által lett auditálva."; @@ -4209,10 +4434,10 @@ "SimpleX Lock turned on" = "SimpleX-zár bekapcsolva"; /* simplex link type */ -"SimpleX one-time invitation" = "Egyszer használható SimpleX-meghívó"; +"SimpleX one-time invitation" = "Egyszer használható SimpleX-meghívó-hivatkozás"; /* No comment provided by engineer. */ -"SimpleX protocols reviewed by Trail of Bits." = "A SimpleX Chat biztonsága a Trail of Bits által lett újraauditálva."; +"SimpleX protocols reviewed by Trail of Bits." = "A SimpleX Chat biztonsága a Trail of Bits által lett felülvizsgálva."; /* No comment provided by engineer. */ "Simplified incognito mode" = "Egyszerűsített inkognitómód"; @@ -4250,6 +4475,9 @@ /* No comment provided by engineer. */ "Some non-fatal errors occurred during import:" = "Néhány nem végzetes hiba történt az importáláskor:"; +/* alert message */ +"Some servers failed the test:\n%@" = "Néhány kiszolgáló megbukott a teszten:\n%@"; + /* notification title */ "Somebody" = "Valaki"; @@ -4335,7 +4563,7 @@ "Switch audio and video during the call." = "Hang/Videó váltása hívás közben."; /* No comment provided by engineer. */ -"Switch chat profile for 1-time invitations." = "Csevegési profilváltás az egyszer használható meghívókhoz."; +"Switch chat profile for 1-time invitations." = "Csevegési profilváltás az egyszer használható meghívó-hivatkozásokhoz."; /* No comment provided by engineer. */ "System" = "Rendszer"; @@ -4412,6 +4640,9 @@ /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Az alkalmazás értesíteni fogja, amikor üzeneteket vagy kapcsolatkéréseket kap – beállítások megnyitása az engedélyezéshez."; +/* No comment provided by engineer. */ +"The app protects your privacy by using different operators in each conversation." = "Az alkalmazás úgy védi az adatait, hogy minden egyes beszélgetésben más-más üzemeltetőket használ."; + /* No comment provided by engineer. */ "The app will ask to confirm downloads from unknown file servers (except .onion)." = "Az alkalmazás kérni fogja az ismeretlen fájlkiszolgálókról (kivéve .onion) történő letöltések megerősítését."; @@ -4421,6 +4652,9 @@ /* No comment provided by engineer. */ "The code you scanned is not a SimpleX link QR code." = "A beolvasott QR-kód nem egy SimpleX-QR-kód-hivatkozás."; +/* No comment provided by engineer. */ +"The connection reached the limit of undelivered messages, your contact may be offline." = "A kapcsolat elérte a kézbesítetlen üzenetek számának határát, az Ön ismerőse lehet, hogy offline állapotban van."; + /* No comment provided by engineer. */ "The connection you accepted will be cancelled!" = "Az Ön által elfogadott kérelem vissza lesz vonva!"; @@ -4460,6 +4694,9 @@ /* No comment provided by engineer. */ "The profile is only shared with your contacts." = "A profilja csak az ismerőseivel kerül megosztásra."; +/* No comment provided by engineer. */ +"The second preset operator in the app!" = "A második előre beállított üzemeltető az alkalmazásban!"; + /* No comment provided by engineer. */ "The second tick we missed! ✅" = "A második jelölés, amit kihagytunk! ✅"; @@ -4469,6 +4706,9 @@ /* No comment provided by engineer. */ "The servers for new connections of your current chat profile **%@**." = "A jelenlegi csevegési profilhoz tartozó új kapcsolatok kiszolgálói **%@**."; +/* No comment provided by engineer. */ +"The servers for new files of your current chat profile **%@**." = "Az Ön jelenlegi **%@** nevű csevegőprofiljához tartozó új fájlok kiszolgálói."; + /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "A beillesztett szöveg nem egy SimpleX-hivatkozás."; @@ -4478,6 +4718,9 @@ /* No comment provided by engineer. */ "Themes" = "Témák"; +/* No comment provided by engineer. */ +"These conditions will also apply for: **%@**." = "Ezek a feltételek lesznek elfogadva a következő számára is: **%@**."; + /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Ezek a beállítások csak a jelenlegi (**%@**) profiljára vonatkoznak."; @@ -4515,7 +4758,7 @@ "This group no longer exists." = "Ez a csoport már nem létezik."; /* No comment provided by engineer. */ -"This is your own one-time link!" = "Ez az Ön egyszer használható hivatkozása!"; +"This is your own one-time link!" = "Ez az Ön egyszer használható meghívó-hivatkozása!"; /* No comment provided by engineer. */ "This is your own SimpleX address!" = "Ez az Ön SimpleX-címe!"; @@ -4541,6 +4784,9 @@ /* No comment provided by engineer. */ "To make a new connection" = "Új kapcsolat létrehozásához"; +/* No comment provided by engineer. */ +"To protect against your link being replaced, you can compare contact security codes." = "A hivatkozás cseréje elleni védelem érdekében összehasonlíthatja a biztonsági kódokat az ismerősével."; + /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Az időzóna védelmének érdekében a kép-/hangfájlok UTC-t használnak."; @@ -4553,6 +4799,9 @@ /* No comment provided by engineer. */ "To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Az adatvédelem érdekében (a más csevegési platformokon megszokott felhasználó-azonosítók helyett) a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, az összes ismerőséhez különbözőt."; +/* No comment provided by engineer. */ +"To receive" = "A fogadáshoz"; + /* No comment provided by engineer. */ "To record speech please grant permission to use Microphone." = "A beszéd rögzítéséhez adjon engedélyt a Mikrofon használatára."; @@ -4565,9 +4814,15 @@ /* No comment provided by engineer. */ "To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page." = "Rejtett profilja megjelenítéséhez írja be a teljes jelszavát a keresőmezőbe a **Csevegési profilok** menüben."; +/* No comment provided by engineer. */ +"To send" = "A küldéshez"; + /* No comment provided by engineer. */ "To support instant push notifications the chat database has to be migrated." = "Az azonnali push-értesítések támogatásához a csevegési adatbázis átköltöztetése szükséges."; +/* No comment provided by engineer. */ +"To use the servers of **%@**, accept conditions of use." = "A(z) **%@** kiszolgálóinak használatához fogadja el a használati feltételeket."; + /* No comment provided by engineer. */ "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "A végpontok közötti titkosítás hitelesítéséhez hasonlítsa össze (vagy olvassa be a QR-kódot) az ismerőse eszközén lévő kóddal."; @@ -4625,6 +4880,9 @@ /* rcv group event chat item */ "unblocked %@" = "feloldotta %@ letiltását"; +/* No comment provided by engineer. */ +"Undelivered messages" = "Kézbesítetlen üzenetek"; + /* No comment provided by engineer. */ "Unexpected migration state" = "Váratlan átköltöztetési állapot"; @@ -4742,12 +5000,21 @@ /* No comment provided by engineer. */ "Use .onion hosts" = "Onion-kiszolgálók használata"; +/* No comment provided by engineer. */ +"Use %@" = "%@ használata"; + /* No comment provided by engineer. */ "Use chat" = "Csevegés használata"; /* No comment provided by engineer. */ "Use current profile" = "Jelenlegi profil használata"; +/* No comment provided by engineer. */ +"Use for files" = "Használat a fájlokhoz"; + +/* No comment provided by engineer. */ +"Use for messages" = "Használat az üzenetekhez"; + /* No comment provided by engineer. */ "Use for new connections" = "Alkalmazás új kapcsolatokhoz"; @@ -4755,7 +5022,7 @@ "Use from desktop" = "Társítás számítógéppel"; /* No comment provided by engineer. */ -"Use iOS call interface" = "Az iOS hívófelület használata"; +"Use iOS call interface" = "Az iOS hívási felületét használata"; /* No comment provided by engineer. */ "Use new incognito profile" = "Új inkognitóprofil használata"; @@ -4772,6 +5039,9 @@ /* No comment provided by engineer. */ "Use server" = "Kiszolgáló használata"; +/* No comment provided by engineer. */ +"Use servers" = "Kiszolgálók használata"; + /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "SimpleX Chat-kiszolgálók használata?"; @@ -4830,7 +5100,7 @@ "via group link" = "a csoporthivatkozáson keresztül"; /* chat list item description */ -"via one-time link" = "egyszer használható hivatkozáson keresztül"; +"via one-time link" = "egyszer használható meghívó-hivatkozáson keresztül"; /* No comment provided by engineer. */ "via relay" = "közvetítő-kiszolgálón keresztül"; @@ -4856,9 +5126,15 @@ /* No comment provided by engineer. */ "Videos and files up to 1gb" = "Videók és fájlok 1Gb méretig"; +/* No comment provided by engineer. */ +"View conditions" = "Feltételek megtekintése"; + /* No comment provided by engineer. */ "View security code" = "Biztonsági kód megtekintése"; +/* No comment provided by engineer. */ +"View updated conditions" = "Frissített feltételek megtekintése"; + /* chat feature */ "Visible history" = "Látható előzmények"; @@ -4940,6 +5216,9 @@ /* No comment provided by engineer. */ "when IP hidden" = "ha az IP-cím rejtett"; +/* No comment provided by engineer. */ +"When more than one operator is enabled, none of them has metadata to learn who communicates with whom." = "Amikor egynél több hálózati üzemeltető van engedélyezve, egyikük sem rendelkezik olyan metaadatokkal ahhoz, hogy felderítse, ki kommunikál kivel."; + /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Inkognitóprofil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott."; @@ -5007,7 +5286,7 @@ "You are already connecting to %@." = "Már folyamatban van a kapcsolódás ehhez: %@."; /* No comment provided by engineer. */ -"You are already connecting via this one-time link!" = "A kapcsolódás már folyamatban van ezen az egyszer használható hivatkozáson keresztül!"; +"You are already connecting via this one-time link!" = "A kapcsolódás már folyamatban van ezen az egyszer használható meghívó-hivatkozáson keresztül!"; /* No comment provided by engineer. */ "You are already in group %@." = "Ön már a(z) %@ nevű csoport tagja."; @@ -5048,6 +5327,12 @@ /* No comment provided by engineer. */ "You can change it in Appearance settings." = "Ezt a „Megjelenés” menüben módosíthatja."; +/* No comment provided by engineer. */ +"You can configure operators in Network & servers settings." = "Az üzemeltetőket a „Hálózat és kiszolgálók” beállításaban konfigurálhatja."; + +/* No comment provided by engineer. */ +"You can configure servers via settings." = "A kiszolgálókat a beállításokon keresztül konfigurálhatja."; + /* No comment provided by engineer. */ "You can create it later" = "Létrehozás később"; @@ -5072,6 +5357,9 @@ /* No comment provided by engineer. */ "You can send messages to %@ from Archived contacts." = "Az „Archivált ismerősökből” továbbra is küldhet üzeneteket neki: %@."; +/* No comment provided by engineer. */ +"You can set connection name, to remember who the link was shared with." = "Beállíthatja az ismerős nevét, hogy emlékezzen arra, hogy kivel osztotta meg a hivatkozást."; + /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "A beállításokon keresztül beállíthatja a lezárási képernyő értesítési előnézetét."; @@ -5163,10 +5451,10 @@ "You sent group invitation" = "Csoportmeghívó elküldve"; /* chat list item description */ -"you shared one-time link" = "egyszer használható hivatkozást osztott meg"; +"you shared one-time link" = "Ön egy egyszer használható meghívó-hivatkozást osztott meg"; /* chat list item description */ -"you shared one-time link incognito" = "egyszer használható hivatkozást osztott meg inkognitóban"; +"you shared one-time link incognito" = "Ön egy egyszer használható meghívó-hivatkozást osztott meg inkognitóban"; /* snd group event chat item */ "you unblocked %@" = "Ön feloldotta %@ letiltását"; @@ -5273,6 +5561,9 @@ /* No comment provided by engineer. */ "Your server address" = "Saját SMP-kiszolgálójának címe"; +/* No comment provided by engineer. */ +"Your servers" = "Az Ön kiszolgálói"; + /* No comment provided by engineer. */ "Your settings" = "Beállítások"; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index 06d78256c7..9abd660f0c 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -82,6 +82,9 @@ /* No comment provided by engineer. */ "**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Consigliato**: vengono inviati il token del dispositivo e le notifiche al server di notifica di SimpleX Chat, ma non il contenuto del messaggio,la sua dimensione o il suo mittente."; +/* No comment provided by engineer. */ +"**Scan / Paste link**: to connect via a link you received." = "**Scansiona / Incolla link**: per connetterti tramite un link che hai ricevuto."; + /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Attenzione**: le notifiche push istantanee richiedono una password salvata nel portachiavi."; @@ -142,6 +145,12 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ è verificato/a"; +/* No comment provided by engineer. */ +"%@ server" = "%@ server"; + +/* No comment provided by engineer. */ +"%@ servers" = "%@ server"; + /* No comment provided by engineer. */ "%@ uploaded" = "%@ caricati"; @@ -295,6 +304,12 @@ /* time interval */ "1 week" = "1 settimana"; +/* No comment provided by engineer. */ +"1-time link" = "Link una tantum"; + +/* No comment provided by engineer. */ +"1-time link can be used *with one contact only* - share in person or via any messenger." = "Il link una tantum può essere usato *con un solo contatto* - condividilo di persona o tramite qualsiasi messenger."; + /* No comment provided by engineer. */ "5 minutes" = "5 minuti"; @@ -342,6 +357,9 @@ swipe action */ "Accept" = "Accetta"; +/* No comment provided by engineer. */ +"Accept conditions" = "Accetta le condizioni"; + /* No comment provided by engineer. */ "Accept connection request?" = "Accettare la richiesta di connessione?"; @@ -355,6 +373,9 @@ /* call status */ "accepted call" = "chiamata accettata"; +/* No comment provided by engineer. */ +"Accepted conditions" = "Condizioni accettate"; + /* No comment provided by engineer. */ "Acknowledged" = "Riconosciuto"; @@ -382,6 +403,12 @@ /* No comment provided by engineer. */ "Add welcome message" = "Aggiungi messaggio di benvenuto"; +/* No comment provided by engineer. */ +"Added media & file servers" = "Server di multimediali e file aggiunti"; + +/* No comment provided by engineer. */ +"Added message servers" = "Server dei messaggi aggiunti"; + /* No comment provided by engineer. */ "Additional accent" = "Principale aggiuntivo"; @@ -397,6 +424,12 @@ /* No comment provided by engineer. */ "Address change will be aborted. Old receiving address will be used." = "Il cambio di indirizzo verrà interrotto. Verrà usato il vecchio indirizzo di ricezione."; +/* No comment provided by engineer. */ +"Address or 1-time link?" = "Indirizzo o link una tantum?"; + +/* No comment provided by engineer. */ +"Address settings" = "Impostazioni dell'indirizzo"; + /* member role */ "admin" = "amministratore"; @@ -439,6 +472,9 @@ /* feature role */ "all members" = "tutti i membri"; +/* No comment provided by engineer. */ +"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Tutti i messaggi e i file vengono inviati **crittografati end-to-end**, con sicurezza resistenti alla quantistica nei messaggi diretti."; + /* No comment provided by engineer. */ "All messages will be deleted - this cannot be undone!" = "Tutti i messaggi verranno eliminati, non è reversibile!"; @@ -855,6 +891,9 @@ set passcode view */ "Change self-destruct passcode" = "Cambia codice di autodistruzione"; +/* authentication reason */ +"Change user profiles" = "Modifica profili utente"; + /* chat item text */ "changed address for you" = "indirizzo cambiato per te"; @@ -918,6 +957,12 @@ /* No comment provided by engineer. */ "Chats" = "Chat"; +/* No comment provided by engineer. */ +"Check messages every 20 min." = "Controlla i messaggi ogni 20 min."; + +/* No comment provided by engineer. */ +"Check messages when allowed." = "Controlla i messaggi quando consentito."; + /* alert title */ "Check server address and try again." = "Controlla l'indirizzo del server e riprova."; @@ -978,6 +1023,33 @@ /* No comment provided by engineer. */ "Completed" = "Completato"; +/* No comment provided by engineer. */ +"Conditions accepted on: %@." = "Condizioni accettate il: %@."; + +/* No comment provided by engineer. */ +"Conditions are accepted for the operator(s): **%@**." = "Le condizioni sono state accettate per gli operatori: **%@**."; + +/* No comment provided by engineer. */ +"Conditions are already accepted for following operator(s): **%@**." = "Le condizioni sono già state accettate per i seguenti operatori: **%@**."; + +/* No comment provided by engineer. */ +"Conditions of use" = "Condizioni d'uso"; + +/* No comment provided by engineer. */ +"Conditions will be accepted for enabled operators after 30 days." = "Le condizioni verranno accettate per gli operatori attivati dopo 30 giorni."; + +/* No comment provided by engineer. */ +"Conditions will be accepted for operator(s): **%@**." = "Le condizioni verranno accettate per gli operatori: **%@**."; + +/* No comment provided by engineer. */ +"Conditions will be accepted for the operator(s): **%@**." = "Le condizioni verranno accettate per gli operatori: **%@**."; + +/* No comment provided by engineer. */ +"Conditions will be accepted on: %@." = "Le condizioni verranno accettate il: %@."; + +/* No comment provided by engineer. */ +"Conditions will be automatically accepted for enabled operators on: %@." = "Le condizioni verranno accettate automaticamente per gli operatori attivi il: %@."; + /* No comment provided by engineer. */ "Configure ICE servers" = "Configura server ICE"; @@ -1125,6 +1197,9 @@ /* No comment provided by engineer. */ "Connection request sent!" = "Richiesta di connessione inviata!"; +/* No comment provided by engineer. */ +"Connection security" = "Sicurezza della connessione"; + /* No comment provided by engineer. */ "Connection terminated" = "Connessione terminata"; @@ -1206,6 +1281,9 @@ /* No comment provided by engineer. */ "Create" = "Crea"; +/* No comment provided by engineer. */ +"Create 1-time link" = "Crea link una tantum"; + /* No comment provided by engineer. */ "Create a group using a random profile." = "Crea un gruppo usando un profilo casuale."; @@ -1257,6 +1335,9 @@ /* No comment provided by engineer. */ "creator" = "creatore"; +/* No comment provided by engineer. */ +"Current conditions text couldn't be loaded, you can review conditions via this link:" = "Il testo delle condizioni attuali testo non è stato caricato, puoi consultare le condizioni tramite questo link:"; + /* No comment provided by engineer. */ "Current Passcode" = "Codice di accesso attuale"; @@ -1505,6 +1586,9 @@ /* No comment provided by engineer. */ "Deletion errors" = "Errori di eliminazione"; +/* No comment provided by engineer. */ +"Delivered even when Apple drops them." = "Consegnati anche quando Apple li scarta."; + /* No comment provided by engineer. */ "Delivery" = "Consegna"; @@ -1692,6 +1776,9 @@ /* No comment provided by engineer. */ "e2e encrypted" = "crittografato e2e"; +/* No comment provided by engineer. */ +"E2E encrypted notifications." = "Notifiche crittografate E2E."; + /* chat item action */ "Edit" = "Modifica"; @@ -1710,6 +1797,9 @@ /* No comment provided by engineer. */ "Enable camera access" = "Attiva l'accesso alla fotocamera"; +/* No comment provided by engineer. */ +"Enable Flux" = "Attiva Flux"; + /* No comment provided by engineer. */ "Enable for all" = "Attiva per tutti"; @@ -1869,12 +1959,18 @@ /* No comment provided by engineer. */ "Error aborting address change" = "Errore nell'interruzione del cambio di indirizzo"; +/* alert title */ +"Error accepting conditions" = "Errore di accettazione delle condizioni"; + /* No comment provided by engineer. */ "Error accepting contact request" = "Errore nell'accettazione della richiesta di contatto"; /* No comment provided by engineer. */ "Error adding member(s)" = "Errore di aggiunta membro/i"; +/* alert title */ +"Error adding server" = "Errore di aggiunta del server"; + /* No comment provided by engineer. */ "Error changing address" = "Errore nella modifica dell'indirizzo"; @@ -1959,6 +2055,9 @@ /* No comment provided by engineer. */ "Error joining group" = "Errore di ingresso nel gruppo"; +/* alert title */ +"Error loading servers" = "Errore nel caricamento dei server"; + /* No comment provided by engineer. */ "Error migrating settings" = "Errore nella migrazione delle impostazioni"; @@ -1992,6 +2091,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "Errore nel salvataggio della password nel portachiavi"; +/* alert title */ +"Error saving servers" = "Errore di salvataggio dei server"; + /* when migrating */ "Error saving settings" = "Errore di salvataggio delle impostazioni"; @@ -2034,6 +2136,9 @@ /* No comment provided by engineer. */ "Error updating message" = "Errore nell'aggiornamento del messaggio"; +/* alert title */ +"Error updating server" = "Errore di aggiornamento del server"; + /* No comment provided by engineer. */ "Error updating settings" = "Errore nell'aggiornamento delle impostazioni"; @@ -2061,6 +2166,9 @@ /* No comment provided by engineer. */ "Errors" = "Errori"; +/* servers error */ +"Errors in servers configuration." = "Errori nella configurazione dei server."; + /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Anche quando disattivato nella conversazione."; @@ -2187,9 +2295,24 @@ /* No comment provided by engineer. */ "Fix not supported by group member" = "Correzione non supportata dal membro del gruppo"; +/* No comment provided by engineer. */ +"for better metadata privacy." = "per una migliore privacy dei metadati."; + +/* servers error */ +"For chat profile %@:" = "Per il profilo di chat %@:"; + /* No comment provided by engineer. */ "For console" = "Per console"; +/* No comment provided by engineer. */ +"For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server." = "Ad esempio, se il tuo contatto riceve messaggi tramite un server di SimpleX Chat, la tua app li consegnerà tramite un server Flux."; + +/* No comment provided by engineer. */ +"For private routing" = "Per l'instradamento privato"; + +/* No comment provided by engineer. */ +"For social media" = "Per i social media"; + /* chat item action */ "Forward" = "Inoltra"; @@ -2382,6 +2505,12 @@ /* time unit */ "hours" = "ore"; +/* No comment provided by engineer. */ +"How it affects privacy" = "Come influisce sulla privacy"; + +/* No comment provided by engineer. */ +"How it helps privacy" = "Come aiuta la privacy"; + /* No comment provided by engineer. */ "How SimpleX works" = "Come funziona SimpleX"; @@ -2958,6 +3087,9 @@ /* No comment provided by engineer. */ "More reliable network connection." = "Connessione di rete più affidabile."; +/* No comment provided by engineer. */ +"More reliable notifications" = "Notifiche più affidabili"; + /* item status description */ "Most likely this connection is deleted." = "Probabilmente questa connessione è stata eliminata."; @@ -2982,12 +3114,18 @@ /* No comment provided by engineer. */ "Network connection" = "Connessione di rete"; +/* No comment provided by engineer. */ +"Network decentralization" = "Decentralizzazione della rete"; + /* snd error text */ "Network issues - message expired after many attempts to send it." = "Problemi di rete - messaggio scaduto dopo molti tentativi di inviarlo."; /* No comment provided by engineer. */ "Network management" = "Gestione della rete"; +/* No comment provided by engineer. */ +"Network operator" = "Operatore di rete"; + /* No comment provided by engineer. */ "Network settings" = "Impostazioni di rete"; @@ -3015,6 +3153,9 @@ /* No comment provided by engineer. */ "New display name" = "Nuovo nome da mostrare"; +/* notification */ +"New events" = "Nuovi eventi"; + /* No comment provided by engineer. */ "New in %@" = "Novità nella %@"; @@ -3036,6 +3177,9 @@ /* No comment provided by engineer. */ "New passphrase…" = "Nuova password…"; +/* No comment provided by engineer. */ +"New server" = "Nuovo server"; + /* No comment provided by engineer. */ "New SOCKS credentials will be used every time you start the app." = "Le nuove credenziali SOCKS verranno usate ogni volta che avvii l'app."; @@ -3081,6 +3225,12 @@ /* No comment provided by engineer. */ "No info, try to reload" = "Nessuna informazione, prova a ricaricare"; +/* servers error */ +"No media & file servers." = "Nessun server di multimediali e file."; + +/* servers error */ +"No message servers." = "Nessun server dei messaggi."; + /* No comment provided by engineer. */ "No network connection" = "Nessuna connessione di rete"; @@ -3099,6 +3249,18 @@ /* No comment provided by engineer. */ "No received or sent files" = "Nessun file ricevuto o inviato"; +/* servers error */ +"No servers for private message routing." = "Nessun server per l'instradamento dei messaggi privati."; + +/* servers error */ +"No servers to receive files." = "Nessun server per ricevere file."; + +/* servers error */ +"No servers to receive messages." = "Nessun server per ricevere messaggi."; + +/* servers error */ +"No servers to send files." = "Nessun server per inviare file."; + /* copied message info in history */ "no text" = "nessun testo"; @@ -3120,6 +3282,9 @@ /* No comment provided by engineer. */ "Notifications are disabled!" = "Le notifiche sono disattivate!"; +/* No comment provided by engineer. */ +"Notifications privacy" = "Privacy delle notifiche"; + /* No comment provided by engineer. */ "Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "Ora gli amministratori possono:\n- eliminare i messaggi dei membri.\n- disattivare i membri (ruolo \"osservatore\")"; @@ -3212,12 +3377,18 @@ /* No comment provided by engineer. */ "Open" = "Apri"; +/* No comment provided by engineer. */ +"Open changes" = "Apri le modifiche"; + /* No comment provided by engineer. */ "Open chat" = "Apri chat"; /* authentication reason */ "Open chat console" = "Apri la console della chat"; +/* No comment provided by engineer. */ +"Open conditions" = "Apri le condizioni"; + /* No comment provided by engineer. */ "Open group" = "Apri gruppo"; @@ -3230,6 +3401,12 @@ /* No comment provided by engineer. */ "Opening app…" = "Apertura dell'app…"; +/* No comment provided by engineer. */ +"Operator" = "Operatore"; + +/* alert title */ +"Operator server" = "Server dell'operatore"; + /* No comment provided by engineer. */ "Or paste archive link" = "O incolla il link dell'archivio"; @@ -3242,6 +3419,9 @@ /* No comment provided by engineer. */ "Or show this code" = "O mostra questo codice"; +/* No comment provided by engineer. */ +"Or to share privately" = "O per condividere in modo privato"; + /* No comment provided by engineer. */ "other" = "altro"; @@ -3383,6 +3563,9 @@ /* No comment provided by engineer. */ "Preset server address" = "Indirizzo server preimpostato"; +/* No comment provided by engineer. */ +"Preset servers" = "Server preimpostati"; + /* No comment provided by engineer. */ "Preview" = "Anteprima"; @@ -3488,6 +3671,9 @@ /* No comment provided by engineer. */ "Push notifications" = "Notifiche push"; +/* No comment provided by engineer. */ +"Push Notifications" = "Notifiche push"; + /* No comment provided by engineer. */ "Push server" = "Server push"; @@ -3735,6 +3921,12 @@ /* chat item action */ "Reveal" = "Rivela"; +/* No comment provided by engineer. */ +"Review conditions" = "Esamina le condizioni"; + +/* No comment provided by engineer. */ +"Review later" = "Esamina più tardi"; + /* No comment provided by engineer. */ "Revoke" = "Revoca"; @@ -3756,6 +3948,12 @@ /* No comment provided by engineer. */ "Safer groups" = "Gruppi più sicuri"; +/* No comment provided by engineer. */ +"Same conditions will apply to operator **%@**." = "Le stesse condizioni si applicheranno all'operatore **%@**."; + +/* No comment provided by engineer. */ +"Same conditions will apply to operator(s): **%@**." = "Le stesse condizioni si applicheranno agli operatori **%@**."; + /* alert button chat item action */ "Save" = "Salva"; @@ -4021,6 +4219,9 @@ /* No comment provided by engineer. */ "Server" = "Server"; +/* alert message */ +"Server added to operator %@." = "Server aggiunto all'operatore %@."; + /* No comment provided by engineer. */ "Server address" = "Indirizzo server"; @@ -4030,6 +4231,15 @@ /* srv error text. */ "Server address is incompatible with network settings." = "L'indirizzo del server non è compatibile con le impostazioni di rete."; +/* alert title */ +"Server operator changed." = "L'operatore del server è cambiato."; + +/* No comment provided by engineer. */ +"Server operators" = "Operatori server"; + +/* alert title */ +"Server protocol changed." = "Il protocollo del server è cambiato."; + /* queue info */ "server queue info: %@\n\nlast received msg: %@" = "info coda server: %1$@\n\nultimo msg ricevuto: %2$@"; @@ -4115,9 +4325,15 @@ /* No comment provided by engineer. */ "Share 1-time link" = "Condividi link una tantum"; +/* No comment provided by engineer. */ +"Share 1-time link with a friend" = "Condividi link una tantum con un amico"; + /* No comment provided by engineer. */ "Share address" = "Condividi indirizzo"; +/* No comment provided by engineer. */ +"Share address publicly" = "Condividi indirizzo pubblicamente"; + /* alert title */ "Share address with contacts?" = "Condividere l'indirizzo con i contatti?"; @@ -4130,6 +4346,9 @@ /* No comment provided by engineer. */ "Share profile" = "Condividi il profilo"; +/* No comment provided by engineer. */ +"Share SimpleX address on social media." = "Condividi indirizzo SimpleX sui social media."; + /* No comment provided by engineer. */ "Share this 1-time invite link" = "Condividi questo link di invito una tantum"; @@ -4175,6 +4394,12 @@ /* No comment provided by engineer. */ "SimpleX Address" = "Indirizzo SimpleX"; +/* No comment provided by engineer. */ +"SimpleX address and 1-time links are safe to share via any messenger." = "L'indirizzo SimpleX e i link una tantum sono sicuri da condividere tramite qualsiasi messenger."; + +/* No comment provided by engineer. */ +"SimpleX address or 1-time link?" = "Indirizzo SimpleX o link una tantum?"; + /* No comment provided by engineer. */ "SimpleX Chat security was audited by Trail of Bits." = "La sicurezza di SimpleX Chat è stata verificata da Trail of Bits."; @@ -4250,6 +4475,9 @@ /* No comment provided by engineer. */ "Some non-fatal errors occurred during import:" = "Si sono verificati alcuni errori non fatali durante l'importazione:"; +/* alert message */ +"Some servers failed the test:\n%@" = "Alcuni server hanno fallito il test:\n%@"; + /* notification title */ "Somebody" = "Qualcuno"; @@ -4412,6 +4640,9 @@ /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "L'app può avvisarti quando ricevi messaggi o richieste di contatto: apri le impostazioni per attivare."; +/* No comment provided by engineer. */ +"The app protects your privacy by using different operators in each conversation." = "L'app protegge la tua privacy usando diversi operatori in ogni conversazione."; + /* No comment provided by engineer. */ "The app will ask to confirm downloads from unknown file servers (except .onion)." = "L'app chiederà di confermare i download da server di file sconosciuti (eccetto .onion)."; @@ -4421,6 +4652,9 @@ /* No comment provided by engineer. */ "The code you scanned is not a SimpleX link QR code." = "Il codice che hai scansionato non è un codice QR di link SimpleX."; +/* No comment provided by engineer. */ +"The connection reached the limit of undelivered messages, your contact may be offline." = "La connessione ha raggiunto il limite di messaggi non consegnati, il contatto potrebbe essere offline."; + /* No comment provided by engineer. */ "The connection you accepted will be cancelled!" = "La connessione che hai accettato verrà annullata!"; @@ -4460,6 +4694,9 @@ /* No comment provided by engineer. */ "The profile is only shared with your contacts." = "Il profilo è condiviso solo con i tuoi contatti."; +/* No comment provided by engineer. */ +"The second preset operator in the app!" = "Il secondo operatore preimpostato nell'app!"; + /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Il secondo segno di spunta che ci mancava! ✅"; @@ -4469,6 +4706,9 @@ /* No comment provided by engineer. */ "The servers for new connections of your current chat profile **%@**." = "I server per le nuove connessioni del profilo di chat attuale **%@**."; +/* No comment provided by engineer. */ +"The servers for new files of your current chat profile **%@**." = "I server per nuovi file del tuo profilo di chat attuale **%@**."; + /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Il testo che hai incollato non è un link SimpleX."; @@ -4478,6 +4718,9 @@ /* No comment provided by engineer. */ "Themes" = "Temi"; +/* No comment provided by engineer. */ +"These conditions will also apply for: **%@**." = "Queste condizioni si applicheranno anche per: **%@**."; + /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Queste impostazioni sono per il tuo profilo attuale **%@**."; @@ -4541,6 +4784,9 @@ /* No comment provided by engineer. */ "To make a new connection" = "Per creare una nuova connessione"; +/* No comment provided by engineer. */ +"To protect against your link being replaced, you can compare contact security codes." = "Per proteggerti dalla sostituzione del tuo link, puoi confrontare i codici di sicurezza del contatto."; + /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Per proteggere il fuso orario, i file immagine/vocali usano UTC."; @@ -4553,6 +4799,9 @@ /* No comment provided by engineer. */ "To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Per proteggere la privacy, invece degli ID utente utilizzati da tutte le altre piattaforme, SimpleX ha identificatori per le code di messaggi, separati per ciascuno dei tuoi contatti."; +/* No comment provided by engineer. */ +"To receive" = "Per ricevere"; + /* No comment provided by engineer. */ "To record speech please grant permission to use Microphone." = "Per registrare l'audio, concedi l'autorizzazione di usare il microfono."; @@ -4565,9 +4814,15 @@ /* No comment provided by engineer. */ "To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page." = "Per rivelare il tuo profilo nascosto, inserisci una password completa in un campo di ricerca nella pagina **I tuoi profili di chat**."; +/* No comment provided by engineer. */ +"To send" = "Per inviare"; + /* No comment provided by engineer. */ "To support instant push notifications the chat database has to be migrated." = "Per supportare le notifiche push istantanee, il database della chat deve essere migrato."; +/* No comment provided by engineer. */ +"To use the servers of **%@**, accept conditions of use." = "Per usare i server di **%@**, accetta le condizioni d'uso."; + /* No comment provided by engineer. */ "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Per verificare la crittografia end-to-end con il tuo contatto, confrontate (o scansionate) il codice sui vostri dispositivi."; @@ -4625,6 +4880,9 @@ /* rcv group event chat item */ "unblocked %@" = "ha sbloccato %@"; +/* No comment provided by engineer. */ +"Undelivered messages" = "Messaggi non consegnati"; + /* No comment provided by engineer. */ "Unexpected migration state" = "Stato di migrazione imprevisto"; @@ -4742,12 +5000,21 @@ /* No comment provided by engineer. */ "Use .onion hosts" = "Usa gli host .onion"; +/* No comment provided by engineer. */ +"Use %@" = "Usa %@"; + /* No comment provided by engineer. */ "Use chat" = "Usa la chat"; /* No comment provided by engineer. */ "Use current profile" = "Usa il profilo attuale"; +/* No comment provided by engineer. */ +"Use for files" = "Usa per i file"; + +/* No comment provided by engineer. */ +"Use for messages" = "Usa per i messaggi"; + /* No comment provided by engineer. */ "Use for new connections" = "Usa per connessioni nuove"; @@ -4772,6 +5039,9 @@ /* No comment provided by engineer. */ "Use server" = "Usa il server"; +/* No comment provided by engineer. */ +"Use servers" = "Usa i server"; + /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Usare i server di SimpleX Chat?"; @@ -4856,9 +5126,15 @@ /* No comment provided by engineer. */ "Videos and files up to 1gb" = "Video e file fino a 1 GB"; +/* No comment provided by engineer. */ +"View conditions" = "Vedi le condizioni"; + /* No comment provided by engineer. */ "View security code" = "Vedi codice di sicurezza"; +/* No comment provided by engineer. */ +"View updated conditions" = "Vedi le condizioni aggiornate"; + /* chat feature */ "Visible history" = "Cronologia visibile"; @@ -4940,6 +5216,9 @@ /* No comment provided by engineer. */ "when IP hidden" = "quando l'IP è nascosto"; +/* No comment provided by engineer. */ +"When more than one operator is enabled, none of them has metadata to learn who communicates with whom." = "Quando più di un operatore è attivato, nessuno di essi ha metadati per scoprire chi comunica con chi."; + /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Quando condividi un profilo in incognito con qualcuno, questo profilo verrà utilizzato per i gruppi a cui ti invitano."; @@ -5048,6 +5327,12 @@ /* No comment provided by engineer. */ "You can change it in Appearance settings." = "Puoi cambiarlo nelle impostazioni dell'aspetto."; +/* No comment provided by engineer. */ +"You can configure operators in Network & servers settings." = "Puoi configurare gli operatori nelle impostazioni di rete e server."; + +/* No comment provided by engineer. */ +"You can configure servers via settings." = "Puoi configurare i server nelle impostazioni."; + /* No comment provided by engineer. */ "You can create it later" = "Puoi crearlo più tardi"; @@ -5072,6 +5357,9 @@ /* No comment provided by engineer. */ "You can send messages to %@ from Archived contacts." = "Puoi inviare messaggi a %@ dai contatti archiviati."; +/* No comment provided by engineer. */ +"You can set connection name, to remember who the link was shared with." = "Puoi impostare il nome della connessione per ricordare con chi è stato condiviso il link."; + /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Puoi impostare l'anteprima della notifica nella schermata di blocco tramite le impostazioni."; @@ -5273,6 +5561,9 @@ /* No comment provided by engineer. */ "Your server address" = "L'indirizzo del tuo server"; +/* No comment provided by engineer. */ +"Your servers" = "I tuoi server"; + /* No comment provided by engineer. */ "Your settings" = "Le tue impostazioni"; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index 7e1d7f0527..5bcd706702 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -684,6 +684,9 @@ /* No comment provided by engineer. */ "Chat profile" = "ユーザープロフィール"; +/* No comment provided by engineer. */ +"Chat theme" = "チャットテーマ"; + /* No comment provided by engineer. */ "Chats" = "チャット"; @@ -699,6 +702,12 @@ /* No comment provided by engineer. */ "Choose from library" = "ライブラリから選択"; +/* No comment provided by engineer. */ +"Chunks deleted" = "チャンクが削除されました"; + +/* No comment provided by engineer. */ +"Chunks downloaded" = "チャンクがダウンロードされました"; + /* swipe action */ "Clear" = "消す"; @@ -708,9 +717,15 @@ /* No comment provided by engineer. */ "Clear conversation?" = "ダイアログのクリアしますか?"; +/* No comment provided by engineer. */ +"Clear private notes?" = "プライベートノートを消しますか?"; + /* No comment provided by engineer. */ "Clear verification" = "検証を消す"; +/* No comment provided by engineer. */ +"Color mode" = "色設定"; + /* No comment provided by engineer. */ "colored" = "色付き"; @@ -723,6 +738,9 @@ /* No comment provided by engineer. */ "complete" = "完了"; +/* No comment provided by engineer. */ +"Completed" = "完了"; + /* No comment provided by engineer. */ "Configure ICE servers" = "ICEサーバを設定"; @@ -747,9 +765,15 @@ /* No comment provided by engineer. */ "Connect incognito" = "シークレットモードで接続"; +/* No comment provided by engineer. */ +"Connect to desktop" = "デスクトップに接続"; + /* No comment provided by engineer. */ "connect to SimpleX Chat developers." = "SimpleX Chat 開発者に接続します。"; +/* No comment provided by engineer. */ +"Connect to your friends faster." = "友達ともっと速くつながりましょう。"; + /* No comment provided by engineer. */ "Connect via link" = "リンク経由で接続"; @@ -759,9 +783,24 @@ /* No comment provided by engineer. */ "connected" = "接続中"; +/* No comment provided by engineer. */ +"Connected" = "接続中"; + +/* No comment provided by engineer. */ +"Connected desktop" = "デスクトップに接続済"; + +/* No comment provided by engineer. */ +"Connected servers" = "接続中のサーバ"; + +/* No comment provided by engineer. */ +"Connected to desktop" = "デスクトップに接続済"; + /* No comment provided by engineer. */ "connecting" = "接続待ち"; +/* No comment provided by engineer. */ +"Connecting" = "接続待ち"; + /* No comment provided by engineer. */ "connecting (accepted)" = "接続待ち (承諾済み)"; @@ -783,12 +822,21 @@ /* No comment provided by engineer. */ "Connecting server… (error: %@)" = "サーバーに接続中… (エラー: %@)"; +/* No comment provided by engineer. */ +"Connecting to contact, please wait or check later!" = "連絡先に接続中です。しばらくお待ちいただくか、後で確認してください!"; + +/* No comment provided by engineer. */ +"Connecting to desktop" = "デスクトップに接続中"; + /* chat list item title */ "connecting…" = "接続待ち…"; /* No comment provided by engineer. */ "Connection" = "接続"; +/* No comment provided by engineer. */ +"Connection and servers status." = "接続とサーバーのステータス"; + /* No comment provided by engineer. */ "Connection error" = "接続エラー"; @@ -801,6 +849,9 @@ /* No comment provided by engineer. */ "Connection request sent!" = "接続リクエストを送信しました!"; +/* No comment provided by engineer. */ +"Connection terminated" = "接続停止"; + /* No comment provided by engineer. */ "Connection timeout" = "接続タイムアウト"; @@ -891,9 +942,15 @@ /* No comment provided by engineer. */ "Custom time" = "カスタム時間"; +/* No comment provided by engineer. */ +"Customize theme" = "カスタムテーマ"; + /* No comment provided by engineer. */ "Dark" = "ダークモード"; +/* No comment provided by engineer. */ +"Dark mode colors" = "ダークモードカラー"; + /* No comment provided by engineer. */ "Database downgrade" = "データーベースのダウングレード"; @@ -954,6 +1011,9 @@ /* time unit */ "days" = "日"; +/* No comment provided by engineer. */ +"Debug delivery" = "配信のデバッグ"; + /* No comment provided by engineer. */ "Decentralized" = "分散型"; @@ -1085,9 +1145,15 @@ /* No comment provided by engineer. */ "Description" = "説明"; +/* No comment provided by engineer. */ +"Desktop devices" = "デスクトップ機器"; + /* No comment provided by engineer. */ "Develop" = "開発"; +/* No comment provided by engineer. */ +"Developer options" = "開発者向けの設定"; + /* No comment provided by engineer. */ "Developer tools" = "開発ツール"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index aa324a2ee0..fadec1b09b 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -82,6 +82,9 @@ /* No comment provided by engineer. */ "**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Aanbevolen**: apparaattoken en meldingen worden naar de SimpleX Chat-meldingsserver gestuurd, maar niet de berichtinhoud, -grootte of van wie het afkomstig is."; +/* No comment provided by engineer. */ +"**Scan / Paste link**: to connect via a link you received." = "**Link scannen/plakken**: om verbinding te maken via een link die u hebt ontvangen."; + /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Waarschuwing**: voor directe push meldingen is een wachtwoord vereist dat is opgeslagen in de Keychain."; @@ -142,6 +145,12 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ is geverifieerd"; +/* No comment provided by engineer. */ +"%@ server" = "%@ server"; + +/* No comment provided by engineer. */ +"%@ servers" = "%@ servers"; + /* No comment provided by engineer. */ "%@ uploaded" = "%@ geüpload"; @@ -295,6 +304,12 @@ /* time interval */ "1 week" = "1 week"; +/* No comment provided by engineer. */ +"1-time link" = "Eenmalige link"; + +/* No comment provided by engineer. */ +"1-time link can be used *with one contact only* - share in person or via any messenger." = "Eenmalige link die *slechts met één contactpersoon* kan worden gebruikt - deel persoonlijk of via een messenger."; + /* No comment provided by engineer. */ "5 minutes" = "5 minuten"; @@ -342,6 +357,9 @@ swipe action */ "Accept" = "Accepteer"; +/* No comment provided by engineer. */ +"Accept conditions" = "Accepteer voorwaarden"; + /* No comment provided by engineer. */ "Accept connection request?" = "Accepteer contact"; @@ -355,6 +373,9 @@ /* call status */ "accepted call" = "geaccepteerde oproep"; +/* No comment provided by engineer. */ +"Accepted conditions" = "Geaccepteerde voorwaarden"; + /* No comment provided by engineer. */ "Acknowledged" = "Erkend"; @@ -382,6 +403,12 @@ /* No comment provided by engineer. */ "Add welcome message" = "Welkom bericht toevoegen"; +/* No comment provided by engineer. */ +"Added media & file servers" = "Media- en bestandsservers toegevoegd"; + +/* No comment provided by engineer. */ +"Added message servers" = "Berichtservers toegevoegd"; + /* No comment provided by engineer. */ "Additional accent" = "Extra accent"; @@ -397,6 +424,12 @@ /* No comment provided by engineer. */ "Address change will be aborted. Old receiving address will be used." = "Adres wijziging wordt afgebroken. Het oude ontvangstadres wordt gebruikt."; +/* No comment provided by engineer. */ +"Address or 1-time link?" = "Adres of eenmalige link?"; + +/* No comment provided by engineer. */ +"Address settings" = "Adres instellingen"; + /* member role */ "admin" = "Beheerder"; @@ -439,6 +472,9 @@ /* feature role */ "all members" = "alle leden"; +/* No comment provided by engineer. */ +"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Alle berichten en bestanden worden **end-to-end versleuteld** verzonden, met post-quantumbeveiliging in directe berichten."; + /* No comment provided by engineer. */ "All messages will be deleted - this cannot be undone!" = "Alle berichten worden verwijderd. Dit kan niet ongedaan worden gemaakt!"; @@ -855,6 +891,9 @@ set passcode view */ "Change self-destruct passcode" = "Zelfvernietigings code wijzigen"; +/* authentication reason */ +"Change user profiles" = "Gebruikersprofielen wijzigen"; + /* chat item text */ "changed address for you" = "adres voor u gewijzigd"; @@ -918,6 +957,12 @@ /* No comment provided by engineer. */ "Chats" = "Chats"; +/* No comment provided by engineer. */ +"Check messages every 20 min." = "Controleer uw berichten elke 20 minuten."; + +/* No comment provided by engineer. */ +"Check messages when allowed." = "Controleer berichten indien toegestaan."; + /* alert title */ "Check server address and try again." = "Controleer het server adres en probeer het opnieuw."; @@ -978,6 +1023,33 @@ /* No comment provided by engineer. */ "Completed" = "Voltooid"; +/* No comment provided by engineer. */ +"Conditions accepted on: %@." = "Voorwaarden geaccepteerd op: %@."; + +/* No comment provided by engineer. */ +"Conditions are accepted for the operator(s): **%@**." = "Voorwaarden worden geaccepteerd voor de operator(s): **%@**."; + +/* No comment provided by engineer. */ +"Conditions are already accepted for following operator(s): **%@**." = "Voorwaarden zijn reeds geaccepteerd voor de volgende operator(s): **%@**."; + +/* No comment provided by engineer. */ +"Conditions of use" = "Gebruiksvoorwaarden"; + +/* No comment provided by engineer. */ +"Conditions will be accepted for enabled operators after 30 days." = "Voor ingeschakelde operators worden de voorwaarden na 30 dagen geaccepteerd."; + +/* No comment provided by engineer. */ +"Conditions will be accepted for operator(s): **%@**." = "Voorwaarden worden geaccepteerd voor operator(s): **%@**."; + +/* No comment provided by engineer. */ +"Conditions will be accepted for the operator(s): **%@**." = "Voorwaarden worden geaccepteerd voor de operator(s): **%@**."; + +/* No comment provided by engineer. */ +"Conditions will be accepted on: %@." = "Voorwaarden worden geaccepteerd op: %@."; + +/* No comment provided by engineer. */ +"Conditions will be automatically accepted for enabled operators on: %@." = "Voorwaarden worden automatisch geaccepteerd voor ingeschakelde operators op: %@."; + /* No comment provided by engineer. */ "Configure ICE servers" = "ICE servers configureren"; @@ -1125,6 +1197,9 @@ /* No comment provided by engineer. */ "Connection request sent!" = "Verbindingsverzoek verzonden!"; +/* No comment provided by engineer. */ +"Connection security" = "Beveiliging van de verbinding"; + /* No comment provided by engineer. */ "Connection terminated" = "Verbinding beëindigd"; @@ -1206,6 +1281,9 @@ /* No comment provided by engineer. */ "Create" = "Maak"; +/* No comment provided by engineer. */ +"Create 1-time link" = "Eenmalige link maken"; + /* No comment provided by engineer. */ "Create a group using a random profile." = "Maak een groep met een willekeurig profiel."; @@ -1257,6 +1335,9 @@ /* No comment provided by engineer. */ "creator" = "creator"; +/* No comment provided by engineer. */ +"Current conditions text couldn't be loaded, you can review conditions via this link:" = "De tekst van de huidige voorwaarden kon niet worden geladen. U kunt de voorwaarden bekijken via deze link:"; + /* No comment provided by engineer. */ "Current Passcode" = "Huidige toegangscode"; @@ -1505,6 +1586,9 @@ /* No comment provided by engineer. */ "Deletion errors" = "Verwijderingsfouten"; +/* No comment provided by engineer. */ +"Delivered even when Apple drops them." = "Geleverd ook als Apple ze verliest"; + /* No comment provided by engineer. */ "Delivery" = "Bezorging"; @@ -1692,6 +1776,9 @@ /* No comment provided by engineer. */ "e2e encrypted" = "e2e versleuteld"; +/* No comment provided by engineer. */ +"E2E encrypted notifications." = "E2E versleutelde meldingen."; + /* chat item action */ "Edit" = "Bewerk"; @@ -1710,6 +1797,9 @@ /* No comment provided by engineer. */ "Enable camera access" = "Schakel cameratoegang in"; +/* No comment provided by engineer. */ +"Enable Flux" = "Flux inschakelen"; + /* No comment provided by engineer. */ "Enable for all" = "Inschakelen voor iedereen"; @@ -1869,12 +1959,18 @@ /* No comment provided by engineer. */ "Error aborting address change" = "Fout bij het afbreken van adres wijziging"; +/* alert title */ +"Error accepting conditions" = "Fout bij het accepteren van voorwaarden"; + /* No comment provided by engineer. */ "Error accepting contact request" = "Fout bij het accepteren van een contactverzoek"; /* No comment provided by engineer. */ "Error adding member(s)" = "Fout bij het toevoegen van leden"; +/* alert title */ +"Error adding server" = "Fout bij toevoegen server"; + /* No comment provided by engineer. */ "Error changing address" = "Fout bij wijzigen van adres"; @@ -1959,6 +2055,9 @@ /* No comment provided by engineer. */ "Error joining group" = "Fout bij lid worden van groep"; +/* alert title */ +"Error loading servers" = "Fout bij het laden van servers"; + /* No comment provided by engineer. */ "Error migrating settings" = "Fout bij migreren van instellingen"; @@ -1992,6 +2091,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "Fout bij opslaan van wachtwoord in de keychain"; +/* alert title */ +"Error saving servers" = "Fout bij het opslaan van servers"; + /* when migrating */ "Error saving settings" = "Fout bij opslaan van instellingen"; @@ -2034,6 +2136,9 @@ /* No comment provided by engineer. */ "Error updating message" = "Fout bij updaten van bericht"; +/* alert title */ +"Error updating server" = "Fout bij het updaten van de server"; + /* No comment provided by engineer. */ "Error updating settings" = "Fout bij bijwerken van instellingen"; @@ -2061,6 +2166,9 @@ /* No comment provided by engineer. */ "Errors" = "Fouten"; +/* servers error */ +"Errors in servers configuration." = "Fouten in de serverconfiguratie."; + /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Zelfs wanneer uitgeschakeld in het gesprek."; @@ -2187,9 +2295,24 @@ /* No comment provided by engineer. */ "Fix not supported by group member" = "Herstel wordt niet ondersteund door groepslid"; +/* No comment provided by engineer. */ +"for better metadata privacy." = "voor betere privacy van metagegevens."; + +/* servers error */ +"For chat profile %@:" = "Voor chatprofiel %@:"; + /* No comment provided by engineer. */ "For console" = "Voor console"; +/* No comment provided by engineer. */ +"For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server." = "Als uw contactpersoon bijvoorbeeld berichten ontvangt via een SimpleX Chat-server, worden deze door uw app via een Flux-server verzonden."; + +/* No comment provided by engineer. */ +"For private routing" = "Voor privé-routering"; + +/* No comment provided by engineer. */ +"For social media" = "Voor social media"; + /* chat item action */ "Forward" = "Doorsturen"; @@ -2382,6 +2505,12 @@ /* time unit */ "hours" = "uren"; +/* No comment provided by engineer. */ +"How it affects privacy" = "Hoe het de privacy beïnvloedt"; + +/* No comment provided by engineer. */ +"How it helps privacy" = "Hoe het de privacy helpt"; + /* No comment provided by engineer. */ "How SimpleX works" = "Hoe SimpleX werkt"; @@ -2958,6 +3087,9 @@ /* No comment provided by engineer. */ "More reliable network connection." = "Betrouwbaardere netwerkverbinding."; +/* No comment provided by engineer. */ +"More reliable notifications" = "Betrouwbaardere meldingen"; + /* item status description */ "Most likely this connection is deleted." = "Hoogstwaarschijnlijk is deze verbinding verwijderd."; @@ -2982,12 +3114,18 @@ /* No comment provided by engineer. */ "Network connection" = "Netwerkverbinding"; +/* No comment provided by engineer. */ +"Network decentralization" = "Netwerk decentralisatie"; + /* snd error text */ "Network issues - message expired after many attempts to send it." = "Netwerkproblemen - bericht is verlopen na vele pogingen om het te verzenden."; /* No comment provided by engineer. */ "Network management" = "Netwerkbeheer"; +/* No comment provided by engineer. */ +"Network operator" = "Netwerkbeheerder"; + /* No comment provided by engineer. */ "Network settings" = "Netwerk instellingen"; @@ -3015,6 +3153,9 @@ /* No comment provided by engineer. */ "New display name" = "Nieuwe weergavenaam"; +/* notification */ +"New events" = "Nieuwe gebeurtenissen"; + /* No comment provided by engineer. */ "New in %@" = "Nieuw in %@"; @@ -3036,6 +3177,9 @@ /* No comment provided by engineer. */ "New passphrase…" = "Nieuw wachtwoord…"; +/* No comment provided by engineer. */ +"New server" = "Nieuwe server"; + /* No comment provided by engineer. */ "New SOCKS credentials will be used every time you start the app." = "Elke keer dat u de app start, worden er nieuwe SOCKS-inloggegevens gebruikt."; @@ -3081,6 +3225,12 @@ /* No comment provided by engineer. */ "No info, try to reload" = "Geen info, probeer opnieuw te laden"; +/* servers error */ +"No media & file servers." = "Geen media- en bestandsservers."; + +/* servers error */ +"No message servers." = "Geen berichtenservers."; + /* No comment provided by engineer. */ "No network connection" = "Geen netwerkverbinding"; @@ -3099,6 +3249,18 @@ /* No comment provided by engineer. */ "No received or sent files" = "Geen ontvangen of verzonden bestanden"; +/* servers error */ +"No servers for private message routing." = "Geen servers voor het routeren van privéberichten."; + +/* servers error */ +"No servers to receive files." = "Geen servers om bestanden te ontvangen."; + +/* servers error */ +"No servers to receive messages." = "Geen servers om berichten te ontvangen."; + +/* servers error */ +"No servers to send files." = "Geen servers om bestanden te verzenden."; + /* copied message info in history */ "no text" = "geen tekst"; @@ -3120,6 +3282,9 @@ /* No comment provided by engineer. */ "Notifications are disabled!" = "Meldingen zijn uitgeschakeld!"; +/* No comment provided by engineer. */ +"Notifications privacy" = "Privacy van meldingen"; + /* No comment provided by engineer. */ "Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "Nu kunnen beheerders: \n- berichten van leden verwijderen.\n- schakel leden uit (\"waarnemer\" rol)"; @@ -3212,12 +3377,18 @@ /* No comment provided by engineer. */ "Open" = "Open"; +/* No comment provided by engineer. */ +"Open changes" = "Wijzigingen openen"; + /* No comment provided by engineer. */ "Open chat" = "Chat openen"; /* authentication reason */ "Open chat console" = "Chat console openen"; +/* No comment provided by engineer. */ +"Open conditions" = "Open voorwaarden"; + /* No comment provided by engineer. */ "Open group" = "Open groep"; @@ -3230,6 +3401,12 @@ /* No comment provided by engineer. */ "Opening app…" = "App openen…"; +/* No comment provided by engineer. */ +"Operator" = "Operator"; + +/* alert title */ +"Operator server" = "Operatorserver"; + /* No comment provided by engineer. */ "Or paste archive link" = "Of plak de archief link"; @@ -3242,6 +3419,9 @@ /* No comment provided by engineer. */ "Or show this code" = "Of laat deze code zien"; +/* No comment provided by engineer. */ +"Or to share privately" = "Of om privé te delen"; + /* No comment provided by engineer. */ "other" = "overig"; @@ -3383,6 +3563,9 @@ /* No comment provided by engineer. */ "Preset server address" = "Vooraf ingesteld server adres"; +/* No comment provided by engineer. */ +"Preset servers" = "Vooraf ingestelde servers"; + /* No comment provided by engineer. */ "Preview" = "Voorbeeld"; @@ -3488,6 +3671,9 @@ /* No comment provided by engineer. */ "Push notifications" = "Push meldingen"; +/* No comment provided by engineer. */ +"Push Notifications" = "Pushmeldingen"; + /* No comment provided by engineer. */ "Push server" = "Push server"; @@ -3735,6 +3921,12 @@ /* chat item action */ "Reveal" = "Onthullen"; +/* No comment provided by engineer. */ +"Review conditions" = "Voorwaarden bekijken"; + +/* No comment provided by engineer. */ +"Review later" = "Later beoordelen"; + /* No comment provided by engineer. */ "Revoke" = "Intrekken"; @@ -3756,6 +3948,12 @@ /* No comment provided by engineer. */ "Safer groups" = "Veiligere groepen"; +/* No comment provided by engineer. */ +"Same conditions will apply to operator **%@**." = "Dezelfde voorwaarden gelden voor operator **%@**."; + +/* No comment provided by engineer. */ +"Same conditions will apply to operator(s): **%@**." = "Dezelfde voorwaarden gelden voor operator(s): **%@**."; + /* alert button chat item action */ "Save" = "Opslaan"; @@ -4021,6 +4219,9 @@ /* No comment provided by engineer. */ "Server" = "Server"; +/* alert message */ +"Server added to operator %@." = "Server toegevoegd aan operator %@."; + /* No comment provided by engineer. */ "Server address" = "Server adres"; @@ -4030,6 +4231,15 @@ /* srv error text. */ "Server address is incompatible with network settings." = "Serveradres is niet compatibel met netwerkinstellingen."; +/* alert title */ +"Server operator changed." = "Serveroperator gewijzigd."; + +/* No comment provided by engineer. */ +"Server operators" = "Serverbeheerders"; + +/* alert title */ +"Server protocol changed." = "Serverprotocol gewijzigd."; + /* queue info */ "server queue info: %@\n\nlast received msg: %@" = "informatie over serverwachtrij: %1$@\n\nlaatst ontvangen bericht: %2$@"; @@ -4115,9 +4325,15 @@ /* No comment provided by engineer. */ "Share 1-time link" = "Eenmalige link delen"; +/* No comment provided by engineer. */ +"Share 1-time link with a friend" = "Deel eenmalig een link met een vriend"; + /* No comment provided by engineer. */ "Share address" = "Adres delen"; +/* No comment provided by engineer. */ +"Share address publicly" = "Adres openbaar delen"; + /* alert title */ "Share address with contacts?" = "Adres delen met contacten?"; @@ -4130,6 +4346,9 @@ /* No comment provided by engineer. */ "Share profile" = "Profiel delen"; +/* No comment provided by engineer. */ +"Share SimpleX address on social media." = "Deel het SimpleX-adres op sociale media."; + /* No comment provided by engineer. */ "Share this 1-time invite link" = "Deel deze eenmalige uitnodigingslink"; @@ -4175,6 +4394,12 @@ /* No comment provided by engineer. */ "SimpleX Address" = "SimpleX adres"; +/* No comment provided by engineer. */ +"SimpleX address and 1-time links are safe to share via any messenger." = "SimpleX-adressen en eenmalige links kunnen veilig worden gedeeld via elke messenger."; + +/* No comment provided by engineer. */ +"SimpleX address or 1-time link?" = "SimpleX adres of eenmalige link?"; + /* No comment provided by engineer. */ "SimpleX Chat security was audited by Trail of Bits." = "De beveiliging van SimpleX Chat is gecontroleerd door Trail of Bits."; @@ -4250,6 +4475,9 @@ /* No comment provided by engineer. */ "Some non-fatal errors occurred during import:" = "Er zijn enkele niet-fatale fouten opgetreden tijdens het importeren:"; +/* alert message */ +"Some servers failed the test:\n%@" = "Sommige servers zijn niet geslaagd voor de test:\n%@"; + /* notification title */ "Somebody" = "Iemand"; @@ -4412,6 +4640,9 @@ /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "De app kan u op de hoogte stellen wanneer u berichten of contact verzoeken ontvangt - open de instellingen om dit in te schakelen."; +/* No comment provided by engineer. */ +"The app protects your privacy by using different operators in each conversation." = "De app beschermt uw privacy door in elk gesprek andere operatoren te gebruiken."; + /* No comment provided by engineer. */ "The app will ask to confirm downloads from unknown file servers (except .onion)." = "De app vraagt om downloads van onbekende bestandsservers (behalve .onion) te bevestigen."; @@ -4421,6 +4652,9 @@ /* No comment provided by engineer. */ "The code you scanned is not a SimpleX link QR code." = "De code die u heeft gescand is geen SimpleX link QR-code."; +/* No comment provided by engineer. */ +"The connection reached the limit of undelivered messages, your contact may be offline." = "De verbinding heeft de limiet van niet-afgeleverde berichten bereikt. Uw contactpersoon is mogelijk offline."; + /* No comment provided by engineer. */ "The connection you accepted will be cancelled!" = "De door u geaccepteerde verbinding wordt geannuleerd!"; @@ -4460,6 +4694,9 @@ /* No comment provided by engineer. */ "The profile is only shared with your contacts." = "Het profiel wordt alleen gedeeld met uw contacten."; +/* No comment provided by engineer. */ +"The second preset operator in the app!" = "De tweede vooraf ingestelde operator in de app!"; + /* No comment provided by engineer. */ "The second tick we missed! ✅" = "De tweede vink die we gemist hebben! ✅"; @@ -4469,6 +4706,9 @@ /* No comment provided by engineer. */ "The servers for new connections of your current chat profile **%@**." = "De servers voor nieuwe verbindingen van uw huidige chatprofiel **%@**."; +/* No comment provided by engineer. */ +"The servers for new files of your current chat profile **%@**." = "De servers voor nieuwe bestanden van uw huidige chatprofiel **%@**."; + /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "De tekst die u hebt geplakt is geen SimpleX link."; @@ -4478,6 +4718,9 @@ /* No comment provided by engineer. */ "Themes" = "Thema's"; +/* No comment provided by engineer. */ +"These conditions will also apply for: **%@**." = "Deze voorwaarden zijn ook van toepassing op: **%@**."; + /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Deze instellingen zijn voor uw huidige profiel **%@**."; @@ -4541,6 +4784,9 @@ /* No comment provided by engineer. */ "To make a new connection" = "Om een nieuwe verbinding te maken"; +/* No comment provided by engineer. */ +"To protect against your link being replaced, you can compare contact security codes." = "Om te voorkomen dat uw link wordt vervangen, kunt u contactbeveiligingscodes vergelijken."; + /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Om de tijdzone te beschermen, gebruiken afbeeldings-/spraakbestanden UTC."; @@ -4553,6 +4799,9 @@ /* No comment provided by engineer. */ "To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Om de privacy te beschermen, heeft SimpleX in plaats van gebruikers-ID's die door alle andere platforms worden gebruikt, ID's voor berichten wachtrijen, afzonderlijk voor elk van uw contacten."; +/* No comment provided by engineer. */ +"To receive" = "Om te ontvangen"; + /* No comment provided by engineer. */ "To record speech please grant permission to use Microphone." = "Geef toestemming om de microfoon te gebruiken om spraak op te nemen."; @@ -4565,9 +4814,15 @@ /* No comment provided by engineer. */ "To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page." = "Om uw verborgen profiel te onthullen, voert u een volledig wachtwoord in een zoek veld in op de pagina **Uw chatprofielen**."; +/* No comment provided by engineer. */ +"To send" = "Om te verzenden"; + /* No comment provided by engineer. */ "To support instant push notifications the chat database has to be migrated." = "Om directe push meldingen te ondersteunen, moet de chat database worden gemigreerd."; +/* No comment provided by engineer. */ +"To use the servers of **%@**, accept conditions of use." = "Om de servers van **%@** te gebruiken, moet u de gebruiksvoorwaarden accepteren."; + /* No comment provided by engineer. */ "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Vergelijk (of scan) de code op uw apparaten om end-to-end-codering met uw contact te verifiëren."; @@ -4625,6 +4880,9 @@ /* rcv group event chat item */ "unblocked %@" = "gedeblokkeerd %@"; +/* No comment provided by engineer. */ +"Undelivered messages" = "Niet afgeleverde berichten"; + /* No comment provided by engineer. */ "Unexpected migration state" = "Onverwachte migratiestatus"; @@ -4742,12 +5000,21 @@ /* No comment provided by engineer. */ "Use .onion hosts" = "Gebruik .onion-hosts"; +/* No comment provided by engineer. */ +"Use %@" = "Gebruik %@"; + /* No comment provided by engineer. */ "Use chat" = "Gebruik chat"; /* No comment provided by engineer. */ "Use current profile" = "Gebruik het huidige profiel"; +/* No comment provided by engineer. */ +"Use for files" = "Gebruik voor bestanden"; + +/* No comment provided by engineer. */ +"Use for messages" = "Gebruik voor berichten"; + /* No comment provided by engineer. */ "Use for new connections" = "Gebruik voor nieuwe verbindingen"; @@ -4772,6 +5039,9 @@ /* No comment provided by engineer. */ "Use server" = "Gebruik server"; +/* No comment provided by engineer. */ +"Use servers" = "Gebruik servers"; + /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "SimpleX Chat servers gebruiken?"; @@ -4856,9 +5126,15 @@ /* No comment provided by engineer. */ "Videos and files up to 1gb" = "Video's en bestanden tot 1 GB"; +/* No comment provided by engineer. */ +"View conditions" = "Bekijk voorwaarden"; + /* No comment provided by engineer. */ "View security code" = "Beveiligingscode bekijken"; +/* No comment provided by engineer. */ +"View updated conditions" = "Bekijk de bijgewerkte voorwaarden"; + /* chat feature */ "Visible history" = "Zichtbare geschiedenis"; @@ -4940,6 +5216,9 @@ /* No comment provided by engineer. */ "when IP hidden" = "wanneer IP verborgen is"; +/* No comment provided by engineer. */ +"When more than one operator is enabled, none of them has metadata to learn who communicates with whom." = "Wanneer er meer dan één operator is ingeschakeld, beschikt geen enkele operator over metagegevens om te achterhalen wie met wie communiceert."; + /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Wanneer je een incognito profiel met iemand deelt, wordt dit profiel gebruikt voor de groepen waarvoor ze je uitnodigen."; @@ -5048,6 +5327,12 @@ /* No comment provided by engineer. */ "You can change it in Appearance settings." = "U kunt dit wijzigen in de instellingen onder uiterlijk."; +/* No comment provided by engineer. */ +"You can configure operators in Network & servers settings." = "U kunt operators configureren in Netwerk- en serverinstellingen."; + +/* No comment provided by engineer. */ +"You can configure servers via settings." = "U kunt servers configureren via instellingen."; + /* No comment provided by engineer. */ "You can create it later" = "U kan het later maken"; @@ -5072,6 +5357,9 @@ /* No comment provided by engineer. */ "You can send messages to %@ from Archived contacts." = "U kunt berichten naar %@ sturen vanuit gearchiveerde contacten."; +/* No comment provided by engineer. */ +"You can set connection name, to remember who the link was shared with." = "U kunt een verbindingsnaam instellen, zodat u kunt onthouden met wie de link is gedeeld."; + /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "U kunt een voorbeeld van een melding op het vergrendeld scherm instellen via instellingen."; @@ -5273,6 +5561,9 @@ /* No comment provided by engineer. */ "Your server address" = "Uw server adres"; +/* No comment provided by engineer. */ +"Your servers" = "Uw servers"; + /* No comment provided by engineer. */ "Your settings" = "Uw instellingen"; diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index 9af4581140..7b66aa5efb 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -82,6 +82,9 @@ /* No comment provided by engineer. */ "**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Рекомендується**: токен пристрою та сповіщення надсилаються на сервер сповіщень SimpleX Chat, але не вміст повідомлення, його розмір або від кого воно надійшло."; +/* No comment provided by engineer. */ +"**Scan / Paste link**: to connect via a link you received." = "**Відсканувати / Вставити посилання**: підключитися за отриманим посиланням."; + /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Попередження**: Для отримання миттєвих пуш-сповіщень потрібна парольна фраза, збережена у брелоку."; @@ -142,12 +145,21 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ перевірено"; +/* No comment provided by engineer. */ +"%@ server" = "%@ сервер"; + +/* No comment provided by engineer. */ +"%@ servers" = "%@ сервери"; + /* No comment provided by engineer. */ "%@ uploaded" = "%@ завантажено"; /* notification title */ "%@ wants to connect!" = "%@ хоче підключитися!"; +/* format for date separator in chat */ +"%@, %@" = "%1$@, %2$@"; + /* No comment provided by engineer. */ "%@, %@ and %lld members" = "%@, %@ та %lld учасників"; @@ -169,9 +181,15 @@ /* forward confirmation reason */ "%d file(s) were deleted." = "%их файл(ів) було видалено."; +/* forward confirmation reason */ +"%d file(s) were not downloaded." = "%d файл(и) не було завантажено."; + /* time interval */ "%d hours" = "%d годин"; +/* alert title */ +"%d messages not forwarded" = "%d повідомлень не переслано"; + /* time interval */ "%d min" = "%d хв"; @@ -286,6 +304,12 @@ /* time interval */ "1 week" = "1 тиждень"; +/* No comment provided by engineer. */ +"1-time link" = "Одноразове посилання"; + +/* No comment provided by engineer. */ +"1-time link can be used *with one contact only* - share in person or via any messenger." = "Одноразове посилання можна використовувати *тільки з одним контактом* - поділіться ним особисто або через будь-який месенджер."; + /* No comment provided by engineer. */ "5 minutes" = "5 хвилин"; @@ -333,6 +357,9 @@ swipe action */ "Accept" = "Прийняти"; +/* No comment provided by engineer. */ +"Accept conditions" = "Прийняти умови"; + /* No comment provided by engineer. */ "Accept connection request?" = "Прийняти запит на підключення?"; @@ -346,6 +373,9 @@ /* call status */ "accepted call" = "прийнято виклик"; +/* No comment provided by engineer. */ +"Accepted conditions" = "Прийняті умови"; + /* No comment provided by engineer. */ "Acknowledged" = "Визнано"; @@ -373,6 +403,12 @@ /* No comment provided by engineer. */ "Add welcome message" = "Додати вітальне повідомлення"; +/* No comment provided by engineer. */ +"Added media & file servers" = "Додано медіа та файлові сервери"; + +/* No comment provided by engineer. */ +"Added message servers" = "Додано сервери повідомлень"; + /* No comment provided by engineer. */ "Additional accent" = "Додатковий акцент"; @@ -388,6 +424,12 @@ /* No comment provided by engineer. */ "Address change will be aborted. Old receiving address will be used." = "Зміна адреси буде скасована. Буде використано стару адресу отримання."; +/* No comment provided by engineer. */ +"Address or 1-time link?" = "Адреса чи одноразове посилання?"; + +/* No comment provided by engineer. */ +"Address settings" = "Налаштування адреси"; + /* member role */ "admin" = "адмін"; @@ -430,6 +472,9 @@ /* feature role */ "all members" = "всі учасники"; +/* No comment provided by engineer. */ +"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Всі повідомлення та файли надсилаються **наскрізним шифруванням**, з пост-квантовим захистом у прямих повідомленнях."; + /* No comment provided by engineer. */ "All messages will be deleted - this cannot be undone!" = "Усі повідомлення будуть видалені - цю дію не можна скасувати!"; @@ -565,6 +610,9 @@ /* No comment provided by engineer. */ "App passcode is replaced with self-destruct passcode." = "Пароль програми замінено на пароль самознищення."; +/* No comment provided by engineer. */ +"App session" = "Сесія програми"; + /* No comment provided by engineer. */ "App version" = "Версія програми"; @@ -637,6 +685,9 @@ /* No comment provided by engineer. */ "Auto-accept images" = "Автоматичне прийняття зображень"; +/* alert title */ +"Auto-accept settings" = "Автоприйняття налаштувань"; + /* No comment provided by engineer. */ "Back" = "Назад"; @@ -658,15 +709,30 @@ /* No comment provided by engineer. */ "Bad message ID" = "Неправильний ідентифікатор повідомлення"; +/* No comment provided by engineer. */ +"Better calls" = "Кращі дзвінки"; + /* No comment provided by engineer. */ "Better groups" = "Кращі групи"; +/* No comment provided by engineer. */ +"Better message dates." = "Кращі дати повідомлень."; + /* No comment provided by engineer. */ "Better messages" = "Кращі повідомлення"; /* No comment provided by engineer. */ "Better networking" = "Краща мережа"; +/* No comment provided by engineer. */ +"Better notifications" = "Кращі сповіщення"; + +/* No comment provided by engineer. */ +"Better security ✅" = "Краща безпека ✅"; + +/* No comment provided by engineer. */ +"Better user experience" = "Покращений користувацький досвід"; + /* No comment provided by engineer. */ "Black" = "Чорний"; @@ -825,6 +891,9 @@ set passcode view */ "Change self-destruct passcode" = "Змінити пароль самознищення"; +/* authentication reason */ +"Change user profiles" = "Зміна профілів користувачів"; + /* chat item text */ "changed address for you" = "змінили для вас адресу"; @@ -876,6 +945,9 @@ /* No comment provided by engineer. */ "Chat preferences" = "Налаштування чату"; +/* alert message */ +"Chat preferences were changed." = "Змінено налаштування чату."; + /* No comment provided by engineer. */ "Chat profile" = "Профіль користувача"; @@ -885,6 +957,12 @@ /* No comment provided by engineer. */ "Chats" = "Чати"; +/* No comment provided by engineer. */ +"Check messages every 20 min." = "Перевіряйте повідомлення кожні 20 хв."; + +/* No comment provided by engineer. */ +"Check messages when allowed." = "Перевірте повідомлення, коли це дозволено."; + /* alert title */ "Check server address and try again." = "Перевірте адресу сервера та спробуйте ще раз."; @@ -945,6 +1023,33 @@ /* No comment provided by engineer. */ "Completed" = "Завершено"; +/* No comment provided by engineer. */ +"Conditions accepted on: %@." = "Умови приймаються на: %@."; + +/* No comment provided by engineer. */ +"Conditions are accepted for the operator(s): **%@**." = "Для оператора(ів) приймаються умови: **%@**."; + +/* No comment provided by engineer. */ +"Conditions are already accepted for following operator(s): **%@**." = "Умови вже прийняті для наступних операторів: **%@**."; + +/* No comment provided by engineer. */ +"Conditions of use" = "Умови використання"; + +/* No comment provided by engineer. */ +"Conditions will be accepted for enabled operators after 30 days." = "Умови будуть прийняті для ввімкнених операторів через 30 днів."; + +/* No comment provided by engineer. */ +"Conditions will be accepted for operator(s): **%@**." = "Умови приймаються для оператора(ів): **%@**."; + +/* No comment provided by engineer. */ +"Conditions will be accepted for the operator(s): **%@**." = "Для оператора(ів) приймаються умови: **%@**."; + +/* No comment provided by engineer. */ +"Conditions will be accepted on: %@." = "Умови приймаються на: %@."; + +/* No comment provided by engineer. */ +"Conditions will be automatically accepted for enabled operators on: %@." = "Умови будуть автоматично прийняті для увімкнених операторів на: %@."; + /* No comment provided by engineer. */ "Configure ICE servers" = "Налаштування серверів ICE"; @@ -1092,6 +1197,9 @@ /* No comment provided by engineer. */ "Connection request sent!" = "Запит на підключення відправлено!"; +/* No comment provided by engineer. */ +"Connection security" = "Безпека з'єднання"; + /* No comment provided by engineer. */ "Connection terminated" = "З'єднання розірвано"; @@ -1164,12 +1272,18 @@ /* No comment provided by engineer. */ "Core version: v%@" = "Основна версія: v%@"; +/* No comment provided by engineer. */ +"Corner" = "Кут"; + /* No comment provided by engineer. */ "Correct name to %@?" = "Виправити ім'я на %@?"; /* No comment provided by engineer. */ "Create" = "Створити"; +/* No comment provided by engineer. */ +"Create 1-time link" = "Створити одноразове посилання"; + /* No comment provided by engineer. */ "Create a group using a random profile." = "Створіть групу, використовуючи випадковий профіль."; @@ -1221,6 +1335,9 @@ /* No comment provided by engineer. */ "creator" = "творець"; +/* No comment provided by engineer. */ +"Current conditions text couldn't be loaded, you can review conditions via this link:" = "Текст поточних умов не вдалося завантажити, ви можете переглянути умови за цим посиланням:"; + /* No comment provided by engineer. */ "Current Passcode" = "Поточний пароль"; @@ -1239,6 +1356,9 @@ /* No comment provided by engineer. */ "Custom time" = "Індивідуальний час"; +/* No comment provided by engineer. */ +"Customizable message shape." = "Налаштовується форма повідомлення."; + /* No comment provided by engineer. */ "Customize theme" = "Налаштувати тему"; @@ -1424,6 +1544,9 @@ /* No comment provided by engineer. */ "Delete old database?" = "Видалити стару базу даних?"; +/* No comment provided by engineer. */ +"Delete or moderate up to 200 messages." = "Видалити або модерувати до 200 повідомлень."; + /* No comment provided by engineer. */ "Delete pending connection?" = "Видалити очікуване з'єднання?"; @@ -1463,6 +1586,9 @@ /* No comment provided by engineer. */ "Deletion errors" = "Помилки видалення"; +/* No comment provided by engineer. */ +"Delivered even when Apple drops them." = "Доставляються навіть тоді, коли Apple кидає їх."; + /* No comment provided by engineer. */ "Delivery" = "Доставка"; @@ -1586,6 +1712,9 @@ /* No comment provided by engineer. */ "Do NOT send messages directly, even if your or destination server does not support private routing." = "НЕ надсилайте повідомлення напряму, навіть якщо ваш сервер або сервер призначення не підтримує приватну маршрутизацію."; +/* No comment provided by engineer. */ +"Do not use credentials with proxy." = "Не використовуйте облікові дані з проксі."; + /* No comment provided by engineer. */ "Do NOT use private routing." = "НЕ використовуйте приватну маршрутизацію."; @@ -1617,6 +1746,9 @@ /* server test step */ "Download file" = "Завантажити файл"; +/* alert action */ +"Download files" = "Завантажити файли"; + /* No comment provided by engineer. */ "Downloaded" = "Завантажено"; @@ -1644,6 +1776,9 @@ /* No comment provided by engineer. */ "e2e encrypted" = "e2e зашифрований"; +/* No comment provided by engineer. */ +"E2E encrypted notifications." = "Зашифровані сповіщення E2E."; + /* chat item action */ "Edit" = "Редагувати"; @@ -1662,6 +1797,9 @@ /* No comment provided by engineer. */ "Enable camera access" = "Увімкніть доступ до камери"; +/* No comment provided by engineer. */ +"Enable Flux" = "Увімкнути Flux"; + /* No comment provided by engineer. */ "Enable for all" = "Увімкнути для всіх"; @@ -1821,21 +1959,33 @@ /* No comment provided by engineer. */ "Error aborting address change" = "Помилка скасування зміни адреси"; +/* alert title */ +"Error accepting conditions" = "Помилка прийняття умов"; + /* No comment provided by engineer. */ "Error accepting contact request" = "Помилка при прийнятті запиту на контакт"; /* No comment provided by engineer. */ "Error adding member(s)" = "Помилка додавання користувача(ів)"; +/* alert title */ +"Error adding server" = "Помилка додавання сервера"; + /* No comment provided by engineer. */ "Error changing address" = "Помилка зміни адреси"; +/* No comment provided by engineer. */ +"Error changing connection profile" = "Помилка при зміні профілю з'єднання"; + /* No comment provided by engineer. */ "Error changing role" = "Помилка зміни ролі"; /* No comment provided by engineer. */ "Error changing setting" = "Помилка зміни налаштування"; +/* No comment provided by engineer. */ +"Error changing to incognito!" = "Помилка переходу на інкогніто!"; + /* No comment provided by engineer. */ "Error connecting to forwarding server %@. Please try later." = "Помилка підключення до сервера переадресації %@. Спробуйте пізніше."; @@ -1905,6 +2055,12 @@ /* No comment provided by engineer. */ "Error joining group" = "Помилка приєднання до групи"; +/* alert title */ +"Error loading servers" = "Помилка завантаження серверів"; + +/* No comment provided by engineer. */ +"Error migrating settings" = "Помилка міграції налаштувань"; + /* No comment provided by engineer. */ "Error opening chat" = "Помилка відкриття чату"; @@ -1935,6 +2091,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "Помилка збереження пароля на keychain"; +/* alert title */ +"Error saving servers" = "Сервери збереження помилок"; + /* when migrating */ "Error saving settings" = "Налаштування збереження помилок"; @@ -1962,6 +2121,9 @@ /* No comment provided by engineer. */ "Error stopping chat" = "Помилка зупинки чату"; +/* No comment provided by engineer. */ +"Error switching profile" = "Помилка перемикання профілю"; + /* alertTitle */ "Error switching profile!" = "Помилка перемикання профілю!"; @@ -1974,6 +2136,9 @@ /* No comment provided by engineer. */ "Error updating message" = "Повідомлення про помилку оновлення"; +/* alert title */ +"Error updating server" = "Помилка оновлення сервера"; + /* No comment provided by engineer. */ "Error updating settings" = "Помилка оновлення налаштувань"; @@ -2001,6 +2166,9 @@ /* No comment provided by engineer. */ "Errors" = "Помилки"; +/* servers error */ +"Errors in servers configuration." = "Помилки в конфігурації серверів."; + /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Навіть коли вимкнений у розмові."; @@ -2049,6 +2217,9 @@ /* No comment provided by engineer. */ "File error" = "Помилка файлу"; +/* alert message */ +"File errors:\n%@" = "Помилки файлів:\n%@"; + /* file error text */ "File not found - most likely file was deleted or cancelled." = "Файл не знайдено - найімовірніше, файл було видалено або скасовано."; @@ -2124,15 +2295,42 @@ /* No comment provided by engineer. */ "Fix not supported by group member" = "Виправлення не підтримується учасником групи"; +/* No comment provided by engineer. */ +"for better metadata privacy." = "для кращої конфіденційності метаданих."; + +/* servers error */ +"For chat profile %@:" = "Для профілю чату %@:"; + /* No comment provided by engineer. */ "For console" = "Для консолі"; +/* No comment provided by engineer. */ +"For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server." = "Наприклад, якщо ваш контакт отримує повідомлення через сервер SimpleX Chat, ваш додаток доставлятиме їх через сервер Flux."; + +/* No comment provided by engineer. */ +"For private routing" = "Для приватної маршрутизації"; + +/* No comment provided by engineer. */ +"For social media" = "Для соціальних мереж"; + /* chat item action */ "Forward" = "Пересилання"; +/* alert title */ +"Forward %d message(s)?" = "Переслати %d повідомлення(ь)?"; + /* No comment provided by engineer. */ "Forward and save messages" = "Пересилання та збереження повідомлень"; +/* alert action */ +"Forward messages" = "Пересилання повідомлень"; + +/* alert message */ +"Forward messages without files?" = "Пересилати повідомлення без файлів?"; + +/* No comment provided by engineer. */ +"Forward up to 20 messages at once." = "Пересилайте до 20 повідомлень одночасно."; + /* No comment provided by engineer. */ "forwarded" = "переслано"; @@ -2142,6 +2340,9 @@ /* No comment provided by engineer. */ "Forwarded from" = "Переслано з"; +/* No comment provided by engineer. */ +"Forwarding %lld messages" = "Пересилання повідомлень %lld"; + /* No comment provided by engineer. */ "Forwarding server %@ failed to connect to destination server %@. Please try later." = "Серверу переадресації %@ не вдалося з'єднатися з сервером призначення %@. Спробуйте пізніше."; @@ -2304,6 +2505,12 @@ /* time unit */ "hours" = "години"; +/* No comment provided by engineer. */ +"How it affects privacy" = "Як це впливає на конфіденційність"; + +/* No comment provided by engineer. */ +"How it helps privacy" = "Як це захищає приватність"; + /* No comment provided by engineer. */ "How SimpleX works" = "Як працює SimpleX"; @@ -2367,6 +2574,9 @@ /* No comment provided by engineer. */ "Importing archive" = "Імпорт архіву"; +/* No comment provided by engineer. */ +"Improved delivery, reduced traffic usage.\nMore improvements are coming soon!" = "Покращена доставка, зменшене використання трафіку.\nНезабаром з'являться нові покращення!"; + /* No comment provided by engineer. */ "Improved message delivery" = "Покращена доставка повідомлень"; @@ -2526,6 +2736,9 @@ /* No comment provided by engineer. */ "iOS Keychain will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving push notifications." = "Пароль бази даних буде безпечно збережено в iOS Keychain після запуску чату або зміни пароля - це дасть змогу отримувати миттєві повідомлення."; +/* No comment provided by engineer. */ +"IP address" = "IP-адреса"; + /* No comment provided by engineer. */ "Irreversible message deletion" = "Безповоротне видалення повідомлення"; @@ -2766,6 +2979,9 @@ /* No comment provided by engineer. */ "Message servers" = "Сервери повідомлень"; +/* No comment provided by engineer. */ +"Message shape" = "Форма повідомлення"; + /* No comment provided by engineer. */ "Message source remains private." = "Джерело повідомлення залишається приватним."; @@ -2796,6 +3012,9 @@ /* No comment provided by engineer. */ "Messages sent" = "Надіслані повідомлення"; +/* alert message */ +"Messages were deleted after you selected them." = "Повідомлення були видалені після того, як ви їх вибрали."; + /* No comment provided by engineer. */ "Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Повідомлення, файли та дзвінки захищені **наскрізним шифруванням** з ідеальною секретністю переадресації, відмовою та відновленням після злому."; @@ -2868,6 +3087,9 @@ /* No comment provided by engineer. */ "More reliable network connection." = "Більш надійне з'єднання з мережею."; +/* No comment provided by engineer. */ +"More reliable notifications" = "Більш надійні сповіщення"; + /* item status description */ "Most likely this connection is deleted." = "Швидше за все, це з'єднання видалено."; @@ -2892,12 +3114,18 @@ /* No comment provided by engineer. */ "Network connection" = "Підключення до мережі"; +/* No comment provided by engineer. */ +"Network decentralization" = "Децентралізація мережі"; + /* snd error text */ "Network issues - message expired after many attempts to send it." = "Проблеми з мережею - термін дії повідомлення закінчився після багатьох спроб надіслати його."; /* No comment provided by engineer. */ "Network management" = "Керування мережею"; +/* No comment provided by engineer. */ +"Network operator" = "Мережевий оператор"; + /* No comment provided by engineer. */ "Network settings" = "Налаштування мережі"; @@ -2925,6 +3153,9 @@ /* No comment provided by engineer. */ "New display name" = "Нове ім'я відображення"; +/* notification */ +"New events" = "Нові події"; + /* No comment provided by engineer. */ "New in %@" = "Нове в %@"; @@ -2946,6 +3177,15 @@ /* No comment provided by engineer. */ "New passphrase…" = "Новий пароль…"; +/* No comment provided by engineer. */ +"New server" = "Новий сервер"; + +/* No comment provided by engineer. */ +"New SOCKS credentials will be used every time you start the app." = "Нові облікові дані SOCKS будуть використовуватися при кожному запуску програми."; + +/* No comment provided by engineer. */ +"New SOCKS credentials will be used for each server." = "Для кожного сервера будуть використовуватися нові облікові дані SOCKS."; + /* pref value */ "no" = "ні"; @@ -2985,9 +3225,21 @@ /* No comment provided by engineer. */ "No info, try to reload" = "Немає інформації, спробуйте перезавантажити"; +/* servers error */ +"No media & file servers." = "Ніяких медіа та файлових серверів."; + +/* servers error */ +"No message servers." = "Ніяких серверів повідомлень."; + /* No comment provided by engineer. */ "No network connection" = "Немає підключення до мережі"; +/* No comment provided by engineer. */ +"No permission to record speech" = "Немає дозволу на запис промови"; + +/* No comment provided by engineer. */ +"No permission to record video" = "Немає дозволу на запис відео"; + /* No comment provided by engineer. */ "No permission to record voice message" = "Немає дозволу на запис голосового повідомлення"; @@ -2997,6 +3249,18 @@ /* No comment provided by engineer. */ "No received or sent files" = "Немає отриманих або відправлених файлів"; +/* servers error */ +"No servers for private message routing." = "Немає серверів для маршрутизації приватних повідомлень."; + +/* servers error */ +"No servers to receive files." = "Немає серверів для отримання файлів."; + +/* servers error */ +"No servers to receive messages." = "Немає серверів для отримання повідомлень."; + +/* servers error */ +"No servers to send files." = "Немає серверів для надсилання файлів."; + /* copied message info in history */ "no text" = "без тексту"; @@ -3009,12 +3273,18 @@ /* No comment provided by engineer. */ "Nothing selected" = "Нічого не вибрано"; +/* alert title */ +"Nothing to forward!" = "Нічого пересилати!"; + /* No comment provided by engineer. */ "Notifications" = "Сповіщення"; /* No comment provided by engineer. */ "Notifications are disabled!" = "Сповіщення вимкнено!"; +/* No comment provided by engineer. */ +"Notifications privacy" = "Сповіщення про приватність"; + /* No comment provided by engineer. */ "Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "Тепер адміністратори можуть\n- видаляти повідомлення користувачів.\n- відключати користувачів (роль \"спостерігач\")"; @@ -3107,12 +3377,18 @@ /* No comment provided by engineer. */ "Open" = "Відкрито"; +/* No comment provided by engineer. */ +"Open changes" = "Відкриті зміни"; + /* No comment provided by engineer. */ "Open chat" = "Відкритий чат"; /* authentication reason */ "Open chat console" = "Відкрийте консоль чату"; +/* No comment provided by engineer. */ +"Open conditions" = "Відкриті умови"; + /* No comment provided by engineer. */ "Open group" = "Відкрита група"; @@ -3125,6 +3401,12 @@ /* No comment provided by engineer. */ "Opening app…" = "Відкриваємо програму…"; +/* No comment provided by engineer. */ +"Operator" = "Оператор"; + +/* alert title */ +"Operator server" = "Сервер оператора"; + /* No comment provided by engineer. */ "Or paste archive link" = "Або вставте посилання на архів"; @@ -3137,6 +3419,9 @@ /* No comment provided by engineer. */ "Or show this code" = "Або покажіть цей код"; +/* No comment provided by engineer. */ +"Or to share privately" = "Або поділитися приватно"; + /* No comment provided by engineer. */ "other" = "інший"; @@ -3146,6 +3431,9 @@ /* No comment provided by engineer. */ "other errors" = "інші помилки"; +/* alert message */ +"Other file errors:\n%@" = "Інші помилки файлів:\n%@"; + /* member role */ "owner" = "власник"; @@ -3167,6 +3455,9 @@ /* No comment provided by engineer. */ "Passcode set!" = "Пароль встановлено!"; +/* No comment provided by engineer. */ +"Password" = "Пароль"; + /* No comment provided by engineer. */ "Password to show" = "Показати пароль"; @@ -3260,6 +3551,9 @@ /* No comment provided by engineer. */ "Polish interface" = "Польський інтерфейс"; +/* No comment provided by engineer. */ +"Port" = "Порт"; + /* server test error */ "Possibly, certificate fingerprint in server address is incorrect" = "Можливо, в адресі сервера неправильно вказано відбиток сертифіката"; @@ -3269,6 +3563,9 @@ /* No comment provided by engineer. */ "Preset server address" = "Попередньо встановлена адреса сервера"; +/* No comment provided by engineer. */ +"Preset servers" = "Попередньо встановлені сервери"; + /* No comment provided by engineer. */ "Preview" = "Попередній перегляд"; @@ -3368,9 +3665,15 @@ /* No comment provided by engineer. */ "Proxied servers" = "Проксі-сервери"; +/* No comment provided by engineer. */ +"Proxy requires password" = "Проксі вимагає пароль"; + /* No comment provided by engineer. */ "Push notifications" = "Push-повідомлення"; +/* No comment provided by engineer. */ +"Push Notifications" = "Push-сповіщення"; + /* No comment provided by engineer. */ "Push server" = "Push-сервер"; @@ -3510,6 +3813,9 @@ /* No comment provided by engineer. */ "Remove" = "Видалити"; +/* No comment provided by engineer. */ +"Remove archive?" = "Видалити архів?"; + /* No comment provided by engineer. */ "Remove image" = "Видалити зображення"; @@ -3615,6 +3921,12 @@ /* chat item action */ "Reveal" = "Показувати"; +/* No comment provided by engineer. */ +"Review conditions" = "Умови перегляду"; + +/* No comment provided by engineer. */ +"Review later" = "Перегляньте пізніше"; + /* No comment provided by engineer. */ "Revoke" = "Відкликати"; @@ -3636,6 +3948,12 @@ /* No comment provided by engineer. */ "Safer groups" = "Безпечніші групи"; +/* No comment provided by engineer. */ +"Same conditions will apply to operator **%@**." = "Такі ж умови діятимуть і для оператора **%@**."; + +/* No comment provided by engineer. */ +"Same conditions will apply to operator(s): **%@**." = "Такі ж умови будуть застосовуватися до оператора(ів): **%@**."; + /* alert button chat item action */ "Save" = "Зберегти"; @@ -3679,6 +3997,9 @@ /* No comment provided by engineer. */ "Save welcome message?" = "Зберегти вітальне повідомлення?"; +/* alert title */ +"Save your profile?" = "Зберегти свій профіль?"; + /* No comment provided by engineer. */ "saved" = "збережено"; @@ -3697,6 +4018,9 @@ /* No comment provided by engineer. */ "Saved WebRTC ICE servers will be removed" = "Збережені сервери WebRTC ICE буде видалено"; +/* No comment provided by engineer. */ +"Saving %lld messages" = "Збереження повідомлень %lld"; + /* No comment provided by engineer. */ "Scale" = "Масштаб"; @@ -3760,6 +4084,9 @@ /* chat item action */ "Select" = "Виберіть"; +/* No comment provided by engineer. */ +"Select chat profile" = "Виберіть профіль чату"; + /* No comment provided by engineer. */ "Selected %lld" = "Вибрано %lld"; @@ -3889,6 +4216,12 @@ /* No comment provided by engineer. */ "Sent via proxy" = "Відправлено через проксі"; +/* No comment provided by engineer. */ +"Server" = "Сервер"; + +/* alert message */ +"Server added to operator %@." = "Сервер додано до оператора %@."; + /* No comment provided by engineer. */ "Server address" = "Адреса сервера"; @@ -3898,6 +4231,15 @@ /* srv error text. */ "Server address is incompatible with network settings." = "Адреса сервера несумісна з налаштуваннями мережі."; +/* alert title */ +"Server operator changed." = "Оператор сервера змінився."; + +/* No comment provided by engineer. */ +"Server operators" = "Оператори серверів"; + +/* alert title */ +"Server protocol changed." = "Протокол сервера змінено."; + /* queue info */ "server queue info: %@\n\nlast received msg: %@" = "інформація про чергу на сервері: %1$@\n\nостаннє отримане повідомлення: %2$@"; @@ -3970,6 +4312,9 @@ /* No comment provided by engineer. */ "Settings" = "Налаштування"; +/* alert message */ +"Settings were changed." = "Налаштування були змінені."; + /* No comment provided by engineer. */ "Shape profile images" = "Сформуйте зображення профілю"; @@ -3980,9 +4325,15 @@ /* No comment provided by engineer. */ "Share 1-time link" = "Поділитися 1-разовим посиланням"; +/* No comment provided by engineer. */ +"Share 1-time link with a friend" = "Поділіться одноразовим посиланням з другом"; + /* No comment provided by engineer. */ "Share address" = "Поділитися адресою"; +/* No comment provided by engineer. */ +"Share address publicly" = "Поділіться адресою публічно"; + /* alert title */ "Share address with contacts?" = "Поділіться адресою з контактами?"; @@ -3992,6 +4343,12 @@ /* No comment provided by engineer. */ "Share link" = "Поділіться посиланням"; +/* No comment provided by engineer. */ +"Share profile" = "Поділіться профілем"; + +/* No comment provided by engineer. */ +"Share SimpleX address on social media." = "Поділіться адресою SimpleX у соціальних мережах."; + /* No comment provided by engineer. */ "Share this 1-time invite link" = "Поділіться цим одноразовим посиланням-запрошенням"; @@ -4037,6 +4394,12 @@ /* No comment provided by engineer. */ "SimpleX Address" = "Адреса SimpleX"; +/* No comment provided by engineer. */ +"SimpleX address and 1-time links are safe to share via any messenger." = "SimpleX-адреси та одноразові посилання можна безпечно ділитися через будь-який месенджер."; + +/* No comment provided by engineer. */ +"SimpleX address or 1-time link?" = "SimpleX адреса або одноразове посилання?"; + /* No comment provided by engineer. */ "SimpleX Chat security was audited by Trail of Bits." = "Безпека SimpleX Chat була перевірена компанією Trail of Bits."; @@ -4073,6 +4436,9 @@ /* simplex link type */ "SimpleX one-time invitation" = "Одноразове запрошення SimpleX"; +/* No comment provided by engineer. */ +"SimpleX protocols reviewed by Trail of Bits." = "Протоколи SimpleX, розглянуті Trail of Bits."; + /* No comment provided by engineer. */ "Simplified incognito mode" = "Спрощений режим інкогніто"; @@ -4091,9 +4457,15 @@ /* No comment provided by engineer. */ "SMP server" = "Сервер SMP"; +/* No comment provided by engineer. */ +"SOCKS proxy" = "Проксі SOCKS"; + /* blur media */ "Soft" = "М'який"; +/* No comment provided by engineer. */ +"Some app settings were not migrated." = "Деякі налаштування програми не були перенесені."; + /* No comment provided by engineer. */ "Some file(s) were not exported:" = "Деякі файли не було експортовано:"; @@ -4103,6 +4475,9 @@ /* No comment provided by engineer. */ "Some non-fatal errors occurred during import:" = "Під час імпорту виникли деякі несмертельні помилки:"; +/* alert message */ +"Some servers failed the test:\n%@" = "Деякі сервери не пройшли тестування:\n%@"; + /* notification title */ "Somebody" = "Хтось"; @@ -4184,12 +4559,21 @@ /* No comment provided by engineer. */ "Support SimpleX Chat" = "Підтримка чату SimpleX"; +/* No comment provided by engineer. */ +"Switch audio and video during the call." = "Перемикайте аудіо та відео під час дзвінка."; + +/* No comment provided by engineer. */ +"Switch chat profile for 1-time invitations." = "Переключіть профіль чату для отримання одноразових запрошень."; + /* No comment provided by engineer. */ "System" = "Система"; /* No comment provided by engineer. */ "System authentication" = "Автентифікація системи"; +/* No comment provided by engineer. */ +"Tail" = "Хвіст"; + /* No comment provided by engineer. */ "Take picture" = "Сфотографуйте"; @@ -4256,6 +4640,9 @@ /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Додаток може сповіщати вас, коли ви отримуєте повідомлення або запити на контакт - будь ласка, відкрийте налаштування, щоб увімкнути цю функцію."; +/* No comment provided by engineer. */ +"The app protects your privacy by using different operators in each conversation." = "Додаток захищає вашу конфіденційність, використовуючи різних операторів у кожній розмові."; + /* No comment provided by engineer. */ "The app will ask to confirm downloads from unknown file servers (except .onion)." = "Програма попросить підтвердити завантаження з невідомих файлових серверів (крім .onion)."; @@ -4265,6 +4652,9 @@ /* No comment provided by engineer. */ "The code you scanned is not a SimpleX link QR code." = "Відсканований вами код не є QR-кодом посилання SimpleX."; +/* No comment provided by engineer. */ +"The connection reached the limit of undelivered messages, your contact may be offline." = "З'єднання досягло ліміту недоставлених повідомлень, ваш контакт може бути офлайн."; + /* No comment provided by engineer. */ "The connection you accepted will be cancelled!" = "Прийняте вами з'єднання буде скасовано!"; @@ -4304,6 +4694,9 @@ /* No comment provided by engineer. */ "The profile is only shared with your contacts." = "Профіль доступний лише вашим контактам."; +/* No comment provided by engineer. */ +"The second preset operator in the app!" = "Другий попередньо встановлений оператор у застосунку!"; + /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Другу галочку ми пропустили! ✅"; @@ -4313,12 +4706,21 @@ /* No comment provided by engineer. */ "The servers for new connections of your current chat profile **%@**." = "Сервери для нових підключень вашого поточного профілю чату **%@**."; +/* No comment provided by engineer. */ +"The servers for new files of your current chat profile **%@**." = "Сервери для нових файлів вашого поточного профілю чату **%@**."; + /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Текст, який ви вставили, не є посиланням SimpleX."; +/* No comment provided by engineer. */ +"The uploaded database archive will be permanently removed from the servers." = "Завантажений архів бази даних буде назавжди видалено з серверів."; + /* No comment provided by engineer. */ "Themes" = "Теми"; +/* No comment provided by engineer. */ +"These conditions will also apply for: **%@**." = "Ці умови також поширюються на: **%@**."; + /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Ці налаштування стосуються вашого поточного профілю **%@**."; @@ -4382,6 +4784,9 @@ /* No comment provided by engineer. */ "To make a new connection" = "Щоб створити нове з'єднання"; +/* No comment provided by engineer. */ +"To protect against your link being replaced, you can compare contact security codes." = "Щоб захиститися від заміни вашого посилання, ви можете порівняти коди безпеки контактів."; + /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Для захисту часового поясу у файлах зображень/голосу використовується UTC."; @@ -4394,15 +4799,30 @@ /* No comment provided by engineer. */ "To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Щоб захистити конфіденційність, замість ідентифікаторів користувачів, які використовуються на всіх інших платформах, SimpleX має ідентифікатори для черг повідомлень, окремі для кожного з ваших контактів."; +/* No comment provided by engineer. */ +"To receive" = "Щоб отримати"; + +/* No comment provided by engineer. */ +"To record speech please grant permission to use Microphone." = "Для запису промови, будь ласка, надайте дозвіл на використання мікрофону."; + +/* No comment provided by engineer. */ +"To record video please grant permission to use Camera." = "Для запису відео, будь ласка, надайте дозвіл на використання камери."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Щоб записати голосове повідомлення, будь ласка, надайте дозвіл на використання мікрофону."; /* No comment provided by engineer. */ "To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page." = "Щоб відкрити свій прихований профіль, введіть повний пароль у поле пошуку на сторінці **Ваші профілі чату**."; +/* No comment provided by engineer. */ +"To send" = "Щоб відправити"; + /* No comment provided by engineer. */ "To support instant push notifications the chat database has to be migrated." = "Для підтримки миттєвих push-повідомлень необхідно перенести базу даних чату."; +/* No comment provided by engineer. */ +"To use the servers of **%@**, accept conditions of use." = "Щоб користуватися серверами **%@**, прийміть умови використання."; + /* No comment provided by engineer. */ "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Щоб перевірити наскрізне шифрування з вашим контактом, порівняйте (або відскануйте) код на ваших пристроях."; @@ -4460,6 +4880,9 @@ /* rcv group event chat item */ "unblocked %@" = "розблоковано %@"; +/* No comment provided by engineer. */ +"Undelivered messages" = "Недоставлені повідомлення"; + /* No comment provided by engineer. */ "Unexpected migration state" = "Неочікуваний стан міграції"; @@ -4577,12 +5000,21 @@ /* No comment provided by engineer. */ "Use .onion hosts" = "Використовуйте хости .onion"; +/* No comment provided by engineer. */ +"Use %@" = "Використовуйте %@"; + /* No comment provided by engineer. */ "Use chat" = "Використовуйте чат"; /* No comment provided by engineer. */ "Use current profile" = "Використовувати поточний профіль"; +/* No comment provided by engineer. */ +"Use for files" = "Використовуйте для файлів"; + +/* No comment provided by engineer. */ +"Use for messages" = "Використовуйте для повідомлень"; + /* No comment provided by engineer. */ "Use for new connections" = "Використовуйте для нових з'єднань"; @@ -4607,9 +5039,15 @@ /* No comment provided by engineer. */ "Use server" = "Використовувати сервер"; +/* No comment provided by engineer. */ +"Use servers" = "Використовуйте сервери"; + /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Використовувати сервери SimpleX Chat?"; +/* No comment provided by engineer. */ +"Use SOCKS proxy" = "Використовуйте SOCKS проксі"; + /* No comment provided by engineer. */ "Use the app while in the call." = "Використовуйте додаток під час розмови."; @@ -4619,6 +5057,9 @@ /* No comment provided by engineer. */ "User selection" = "Вибір користувача"; +/* No comment provided by engineer. */ +"Username" = "Ім'я користувача"; + /* No comment provided by engineer. */ "Using SimpleX Chat servers." = "Використання серверів SimpleX Chat."; @@ -4685,9 +5126,15 @@ /* No comment provided by engineer. */ "Videos and files up to 1gb" = "Відео та файли до 1 Гб"; +/* No comment provided by engineer. */ +"View conditions" = "Умови перегляду"; + /* No comment provided by engineer. */ "View security code" = "Переглянути код безпеки"; +/* No comment provided by engineer. */ +"View updated conditions" = "Переглянути оновлені умови"; + /* chat feature */ "Visible history" = "Видима історія"; @@ -4769,6 +5216,9 @@ /* No comment provided by engineer. */ "when IP hidden" = "коли IP приховано"; +/* No comment provided by engineer. */ +"When more than one operator is enabled, none of them has metadata to learn who communicates with whom." = "Коли увімкнено більше одного оператора, жоден з них не має метаданих, щоб дізнатися, хто з ким спілкується."; + /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Коли ви ділитеся з кимось своїм профілем інкогніто, цей профіль буде використовуватися для груп, до яких вас запрошують."; @@ -4877,6 +5327,12 @@ /* No comment provided by engineer. */ "You can change it in Appearance settings." = "Ви можете змінити його в налаштуваннях зовнішнього вигляду."; +/* No comment provided by engineer. */ +"You can configure operators in Network & servers settings." = "Ви можете налаштувати операторів у налаштуваннях Мережі та серверів."; + +/* No comment provided by engineer. */ +"You can configure servers via settings." = "Ви можете налаштувати сервери за допомогою налаштувань."; + /* No comment provided by engineer. */ "You can create it later" = "Ви можете створити його пізніше"; @@ -4901,6 +5357,9 @@ /* No comment provided by engineer. */ "You can send messages to %@ from Archived contacts." = "Ви можете надсилати повідомлення на %@ з архівних контактів."; +/* No comment provided by engineer. */ +"You can set connection name, to remember who the link was shared with." = "Ви можете задати ім'я з'єднання, щоб запам'ятати, з ким ви поділилися посиланням."; + /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Ви можете налаштувати попередній перегляд сповіщень на екрані блокування за допомогою налаштувань."; @@ -5045,9 +5504,15 @@ /* No comment provided by engineer. */ "Your chat database is not encrypted - set passphrase to encrypt it." = "Ваша база даних чату не зашифрована - встановіть ключову фразу, щоб зашифрувати її."; +/* alert title */ +"Your chat preferences" = "Ваші налаштування чату"; + /* No comment provided by engineer. */ "Your chat profiles" = "Ваші профілі чату"; +/* No comment provided by engineer. */ +"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Ваше з'єднання було переміщено на %@, але під час перенаправлення на профіль сталася несподівана помилка."; + /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "Ваш контакт надіслав файл, розмір якого перевищує підтримуваний на цей момент максимальний розмір (%@)."; @@ -5057,6 +5522,9 @@ /* No comment provided by engineer. */ "Your contacts will remain connected." = "Ваші контакти залишаться на зв'язку."; +/* No comment provided by engineer. */ +"Your credentials may be sent unencrypted." = "Ваші облікові дані можуть бути надіслані незашифрованими."; + /* No comment provided by engineer. */ "Your current chat database will be DELETED and REPLACED with the imported one." = "Ваша поточна база даних чату буде ВИДАЛЕНА і ЗАМІНЕНА імпортованою."; @@ -5081,6 +5549,9 @@ /* No comment provided by engineer. */ "Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Ваш профіль зберігається на вашому пристрої і доступний лише вашим контактам. Сервери SimpleX не бачать ваш профіль."; +/* alert message */ +"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Ваш профіль було змінено. Якщо ви збережете його, оновлений профіль буде надіслано всім вашим контактам."; + /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Ваш профіль, контакти та доставлені повідомлення зберігаються на вашому пристрої."; @@ -5090,6 +5561,9 @@ /* No comment provided by engineer. */ "Your server address" = "Адреса вашого сервера"; +/* No comment provided by engineer. */ +"Your servers" = "Ваші сервери"; + /* No comment provided by engineer. */ "Your settings" = "Ваші налаштування"; diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index 2f3f245f54..c8d995cfd9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -28,7 +28,7 @@ سيتم تغيير عنوان الاستلام إلى خادم مختلف. سيتم إكمال تغيير العنوان بعد اتصال المرسل بالإنترنت. هذا الرابط ليس رابط اتصال صالح! يسمح - أضِف خوادم محدّدة مسبقًا + أضِف خوادم مُعدة مسبقًا أضِف إلى جهاز آخر سيتم حذف جميع الدردشات والرسائل - لا يمكن التراجع عن هذا! الوصول إلى الخوادم عبر وكيل SOCKS على المنفذ %d؟ يجب بدء تشغيل الوكيل قبل تمكين هذا الخيار. @@ -124,8 +124,7 @@ 30 ثانية إلغاء الرسالة المباشرة إلغاء - سيتم استخدام اتصال TCP منفصل (وبيانات اعتماد SOCKS) لكل جهة اتصال وعضو في المجموعة. -\n الرجاء ملاحظة: إذا كان لديك العديد من التوصيلات ، فقد يكون استهلاك البطارية وحركة المرور أعلى بكثير وقد تفشل بعض الاتصالات. + لكل جهة اتصال وعضو في المجموعة\n. الرجاء ملاحظة: إذا كان لديك العديد من الاتصالات، فقد يكون استهلاك البطارية وحركة المرور أعلى بكثير وقد تفشل بعض الاتصالات.]]> جارٍ الاتصال… مكالمة صوتية المكالمات على شاشة القفل: @@ -199,7 +198,7 @@ خطأ في إحباط تغيير العنوان تفعيل قفل SimpleX تأكد من بيانات الاعتماد الخاصة بك - إنشاء عنوان SimpleX + أنشئ عنوان SimpleX متابعة تحدث مع المطورين سياق الأيقونة @@ -477,7 +476,7 @@ كيفية الاستخدام كيف يعمل SimpleX التخفي عبر رابط عنوان جهة الاتصال - رمز الحماية غير صحيحة! + رمز الأمان غير صحيحة! الإشعارات الفورية مُعطَّلة إشعارات فورية! إخفاء جهة الاتصال والرسالة @@ -824,7 +823,7 @@ تعمية ثنائية الطبقات من بين الطريفين.]]> صفّر الألوان حفظ - عنوان الخادم المحدد مسبقًا + عنوان الخادم المُعد مسبقًا حفظ وإشعار أعضاء المجموعة دوري أعد تشغيل التطبيق لاستخدام قاعدة بيانات الدردشة المستوردة. @@ -836,7 +835,7 @@ يرجى تخزين عبارة المرور بشكل آمن، فلن تتمكن من الوصول إلى الدردشة إذا فقدتها. يُرجى تحديث التطبيق والتواصل مع المطورين. دليل المستخدم.]]> - افتح ملفات تعريف الدردشة + غيّر ملفات تعريف الدردشة اسحب الوصول كشف سيتم إيقاف استلام الملف. @@ -930,7 +929,7 @@ رمز QR صفّر المنفذ %d - خادم محدد مسبقًا + خادم مُعد مسبقًا يتم استخدام خادم الترحيل فقط إذا لزم الأمر. يمكن لطرف آخر مراقبة عنوان IP الخاص بك. حفظ وإشعار جهة الاتصال إعادة التشغيل @@ -1163,7 +1162,7 @@ أنت متصل بالفعل بـ%1$s. في انتظار الفيديو سيتم استلام الفيديو عند اكتمال تحميل جهة اتصالك. - تحقق من رمز الحماية + تحقق من رمز الأمان رسائل صوتية عندما يطلب الأشخاص الاتصال، يمكنك قبوله أو رفضه. سوف تكون متصلاً بالمجموعة عندما يكون جهاز مضيف المجموعة متصلاً بالإنترنت، يرجى الانتظار أو التحقق لاحقًا! @@ -1796,7 +1795,7 @@ أظهِر قائمة الدردشة في نافذة جديدة ألوان الدردشة سمة الدردشة - تلقى الرد + تلقيت رد أزِل الصورة تكرار صفّر اللون @@ -2116,4 +2115,97 @@ أمان أفضل ✅ بروتوكولات SimpleX تمت مراجعتها بواسطة Trail of Bits. تبديل ملف تعريف الدردشة لدعوات لمرة واحدة. + أخطاء في تضبيط الخوادم. + لملف تعريف الدردشة %s: + لا يوجد وسائط أو خوادم ملفات. + لا يوجد خوادم لإرسال الملفات. + لقد وصل الاتصال إلى الحد الأقصى من الرسائل غير المُسلمة، قد يكون جهة الاتصال الخاصة بك غير متصلة بالإنترنت. + الرسائل غير المُسلَّمة + شارك رابطًا لمرة واحدة مع صديق + أمان الاتصال + لحماية الرابط الخاص بك من الاستبدال، يمكنك مقارنة رموز أمان جهات الاتصال. + خادم جديد + لوسائل التواصل الاجتماعي + أو للمشاركة بشكل خاص + إعدادات العنوان + أنشئ رابط لمرة واحدة + عنوان SimpleX أو رابط لمرة واحدة؟ + مُشغلي الشبكة + يمكنك تضبيط الخوادم عبر الإعدادات. + حدد مشغلي الشبكة الذين تريد استخدامهم. + يمكنك تضبيط المُشغلين في إعدادات الشبكة والخوادم. + حدّث + تابع + قُبل الشروط + راجع الشروط + الخوادم المُعدة مسبقًا + سيتم قبول الشروط تلقائيًا للمُشغلين المفعّلين في: %s. + خوادمك + %s.]]> + %s.]]> + مُشغل الشبكة + المُشغل + %s خوادم + الموقع الإلكتروني + سيتم قبول الشروط في: %s. + قُبل الشروط في: %s. + استخدم %s + استخدم الخوادم + %s.]]> + شروط الاستخدام + للتوجيه الخاص + لتلقي + استخدم للملفات + اعرض الشروط + %s.]]> + %s، يجب قبول شروط الاستخدام.]]> + %s.]]> + أُضيفت خوادم الوسائط والملفات + الشروط المفتوحة + الخوادم الخاصة بالملفات الجديدة لملف الدردشة الحالي الخاص بك + لإرسال + خطأ في إضافة الخادم + خطأ في تحديث الخادم + التغييرات المفتوحة + خادم المُشغل + أُضيف الخادم إلى المُشغل %s. + تغيّر مُشغل الخادم. + أشرطة أدوات التطبيق + تمويه + الشفافية + فعّل flux + اللامركزية الشبكية + المُشغل المُعد مسبقًا الثاني في التطبيق! + لتحسين خصوصية البيانات الوصفية. + تحسين التنقل في الدردشة + اعرض الشروط المُحدثة + اقبل الشروط + أُضيفت خوادم الرسائل + عنوان أو رابط لمرة واحدة؟ + مع جهة اتصال واحدة فقط - المشاركة شخصيًا أو عبر أي مُراسل.]]> + سيتم قبول الشروط للمُشغلين المفعّلين بعد 30 يومًا. + اختر المُشغلين + لا يمكن تحميل نص الشروط الحالية، يمكنك مراجعة الشروط عبر هذا الرابط: + خطأ في قبول الشروط + خطأ في حفظ الخوادم + على سبيل المثال، إذا تلقيت رسائل عبر خادم SimpleX Chat، فسيستخدم التطبيق أحد خوادم Flux للتوجيه الخاص. + لا يوجد خوادم لتوجيه الرسائل الخاصة. + لا يوجد خوادم رسائل. + لا يوجد خوادم لاستقبال الملفات. + لا توجد رسالة + لا يوجد خوادم لاستقبال الرسائل. + - فتح الدردشة عند أول رسالة غير مقروءة.\n- الانتقال إلى الرسائل المقتبسة. + يمكنك تعيين اسم الاتصال، لتذكر الأشخاص الذين تمت مشاركة الرابط معهم. + راجع لاحقًا + تغيّر بروتوكول الخادم. + شارك العنوان علناً + شارك عنوان SimpleX على وسائل التواصل الاجتماعي. + عنوان SimpleX والروابط لمرة واحدة آمنة للمشاركة عبر أي برنامج مُراسلة. + انقر فوق أنشئ عنوان SimpleX في القائمة لإنشائه لاحقًا. + حُذفت هذه الرسالة أو لم يتم استلامها بعد. + استخدم للرسائل + عندما تفعّل أكثر من مُشغل شبكة واحد، سيستخدم التطبيق خوادم مُشغلين مختلفين لكل مُحادثة. + %s.]]> + %s.]]> + %s.]]> \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 55b7e2b0e7..53df6d4818 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1733,7 +1733,6 @@ %s.]]> %s.]]> %s.]]> - %s.]]> %s.]]> %s.]]> %s.]]> diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml index 748c264918..d59867574d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml @@ -89,8 +89,7 @@ за всеки чат профил, който имате в приложението.]]> аудио разговор Най-добро за батерията. Ще получавате известия само когато приложението работи (БЕЗ фонова услуга).]]> - Ще се използва отделна TCP връзка (и идентификационни данни за SOCKS) за всеки контакт и член на група. -\nМоля, обърнете внимание: ако имате много връзки, консумацията на батерията и трафика може да бъде значително по-висока и някои връзки може да се провалят. + за всеки контакт и член на група. \nМоля, обърнете внимание: ако имате много връзки, консумацията на батерията и трафика може да бъде значително по-висока и някои връзки може да се провалят.]]> Помолен да получи изображението Аудио и видео разговори аудио разговор (не е e2e криптиран) diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml index 548de53a21..b107ea1df7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml @@ -631,8 +631,7 @@ Onion hostitelé nebudou použiti. Izolace přenosu for each chat profile you have in the app.]]> - Oddělit TCP připojení (a SOCKS pověření) bude použito pro všechny kontakty a členy skupin. -\nUpozornění: Pokud máte mnoho připojení, může být spotřeba baterie a provoz podstatně vyšší a některá připojení mohou selhat. + pro všechny kontakty a členy skupin. \nUpozornění: Pokud máte mnoho připojení, může být spotřeba baterie a provoz podstatně vyšší a některá připojení mohou selhat.]]> Vzhled Verze aplikace Verze aplikace: v%s @@ -2041,4 +2040,6 @@ Jiné FXTP servery Pozvat Pošlete zprávu pro povolení volání. + Přijmout podmínky + Přijaté podmínky \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index 05ed6366a1..29812d0a3e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -264,7 +264,7 @@ Danke, dass Sie SimpleX Chat installiert haben! mit den SimpleX-Chat-Entwicklern verbinden, um Fragen zu stellen und aktuelle Informationen zu erhalten.]]> Um einen neuen Chat zu starten - Schaltfläche antippen + Schaltfläche tippen Danach die gewünschte Aktion auswählen: Über Link verbinden Wenn Sie einen SimpleX-Chat-Einladungslink erhalten haben, können Sie ihn in Ihrem Browser öffnen: @@ -989,9 +989,7 @@ Verbindung Chat-Profil Dateien für alle Chat-Profile löschen - Für jeden Kontakt und jedes Gruppenmitglied wird eine separate TCP-Verbindung (und SOCKS-Berechtigung) genutzt. -\n -\nBitte beachten Sie: Wenn Sie viele Verbindungen haben, können Akkuverbrauch und Datennutzung wesentlich höher ausfallen und einige Verbindungen scheitern. + Für jeden Kontakt und jedes Gruppenmitglied wird eine separate TCP-Verbindung (und SOCKS-Berechtigung) genutzt.\nBitte beachten Sie: Wenn Sie viele Verbindungen haben, können Akkuverbrauch und Datennutzung wesentlich höher ausfallen und einige Verbindungen scheitern.]]> Für jedes von Ihnen in der App genutzte Chat-Profil wird eine separate TCP-Verbindung (und SOCKS-Berechtigung) genutzt.]]> Nur lokale Profildaten Profil und Serververbindungen @@ -1235,7 +1233,7 @@ Falls Sie sich nicht persönlich treffen können, zeigen Sie den QR-Code in einem Videoanruf oder teilen Sie den Link. Benutzeranleitung.]]> Stellen Sie sicher, dass die Datei die korrekte YAML-Syntax hat. Exportieren Sie das Design, um ein Beispiel für die Dateistruktur des Designs zu erhalten. - Offene Chat-Profile + Chat-Profile wechseln Sie können Ihre Adresse als Link oder QR-Code teilen – jede Person kann sich mit Ihnen verbinden. Werden die App-Daten komplett gelöscht. Es wurde ein leeres Chat-Profil mit dem eingegebenen Namen erstellt und die App öffnet wie gewohnt. @@ -2200,4 +2198,99 @@ Verbesserte Sicherheit ✅ Verbesserte Nachrichten-Datumsinformation Verbesserte Nutzer-Erfahrung + Fehler beim Speichern der Server + Keine Nachrichten-Server. + Keine Server für den Empfang von Nachrichten. + Fehler in der Server-Konfiguration. + Für das Chat-Profil %s: + Keine Medien- und Dateiserver. + Keine Server für den Empfang von Dateien. + Keine Server für das Versenden von Dateien. + Nicht ausgelieferte Nachrichten + Die SimpleX-Adresse auf sozialen Medien teilen. + Verbindungs-Sicherheit + Den Einmal-Einladungslink mit einem Freund teilen + Die SimpleX-Adresse und Einmal-Links können sicher über beliebige Messenger geteilt werden. + Sie können einen Verbindungsnamen festlegen, um sich zu merken, mit wem der Link geteilt wurde. + Adress-Einstellungen + Einmal-Link erstellen + Für soziale Medien + Oder zum privaten Teilen + SimpleX-Adresse oder Einmal-Link? + Betreiber auswählen + Netzwerk-Betreiber + Wenn mehr als ein Netzwerk-Betreiber aktiviert ist, verwendet die App für jede Unterhaltung Server der verschiedenen Betreiber. + Die Nutzungsbedingungen der aktivierten Betreiber werden nach 30 Tagen akzeptiert. + Wenn Sie beispielsweise Nachrichten über einen SimpleX-Chatserver empfangen, verwendet die App einen der Server von Flux für die private Weiterleitung. + Später einsehen + Wählen sie die zu nutzenden Netzwerk-Betreiber aus. + Sie können die Betreiber in den Netzwerk- und Servereinstellungen konfigurieren. + Sie können die Server über die Einstellungen konfigurieren. + Weiter + Aktualisieren + Voreingestellte Server + Nutzungsbedingungen einsehen + Die Nutzungsbedingungen der aktivierten Betreiber werden automatisch akzeptiert am: %s. + Ihre Server + Betreiber + %s Server + Netzwerk-Betreiber + Verwende Server + Webseite + Verwende %s + %s.]]> + %s.]]> + Nutzungsbedingungen + Nutzungsbedingungen anschauen + Nutzungsbedingungen akzeptieren + %s.]]> + Für den Empfang + Für Nachrichten verwenden + Nachrichtenserver hinzugefügt + Für privates Routing + Die Server Deines aktuellen Chat-Profils für neue Dateien + Für das Senden + Für Dateien verwenden + Fehler beim Hinzufügen des Servers + Änderungen öffnen + Nutzungsbedingungen öffnen + Betreiber-Server + Der Server wurde dem Betreiber %s hinzugefügt. + Der Server-Betreiber wurde geändert. + Das Server-Protokoll wurde geändert. + Transparenz + Flux aktivieren + Dezentralisiertes Netzwerk + Der zweite voreingestellte Netzwerk-Betreiber in der App! + Verbesserte Chat-Navigation + - Den Chat bei der ersten ungelesenen Nachricht öffnen.\n- Zu zitierten Nachrichten springen. + Aktualisierte Nutzungsbedingungen anschauen + Akzeptierte Nutzungsbedingungen + Medien- und Dateiserver hinzugefügt + Adress- oder Einmal-Link? + App-Symbolleiste + Verpixeln + nur mit einem Kontakt genutzt werden - teilen Sie in nur persönlich oder über einen beliebigen Messenger.]]> + %s.]]> + %s.]]> + Die Nutzungsbedingungen werden akzeptiert am: %s. + %s.]]> + %s zu nutzen, müssen Sie dessen Nutzungsbedingungen akzeptieren.]]> + Fehler beim Akzeptieren der Nutzungsbedingungen + Fehler beim Aktualisieren des Servers + für einen besseren Metadatenschutz. + Neuer Server + Keine Nachricht + Keine Server für privates Nachrichten-Routing. + Die Adresse öffentlich teilen + Tippen Sie im Menü auf SimpleX-Adresse erstellen, um sie später zu erstellen. + Diese Verbindung hat das Limit der nicht ausgelieferten Nachrichten erreicht. Ihr Kontakt ist möglicherweise offline. + Diese Nachricht wurde gelöscht oder bisher noch nicht empfangen. + Zum Schutz vor dem Austausch Ihres Links können Sie die Sicherheitscodes Ihrer Kontakte vergleichen. + %s.]]> + %s.]]> + Die Nutzungsbedingungen wurden akzeptiert am: %s. + Der Text der aktuellen Nutzungsbedingungen konnte nicht geladen werden. Sie können die Nutzungsbedingungen unter diesem Link einsehen: + Ferngesteuerte Mobiltelefone + Oder importieren Sie eine Archiv-Datei \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index 0f31210a9a..3d22d1fcc5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -1101,7 +1101,7 @@ ¡Nuestro agradecimiento a todos los colaboradores! Puedes contribuir a través de Weblate. Vídeos y archivos de hasta 1Gb ¡Rápido y sin necesidad de esperar a que el remitente esté en línea! - Abrir perfiles + Cambiar perfil Más información Si no puedes reunirte en persona, muestra el código QR por videollamada o comparte el enlace. Para conectarse, tu contacto puede escanear el código QR o usar el enlace en la aplicación. @@ -1111,7 +1111,7 @@ Abriendo base de datos… Error al introducir dirección Guía de Usuario.]]> - Enlace un uso + Enlace de un uso Dirección SimpleX Cuando alguien solicite conectarse podrás aceptar o rechazar su solicitud. Compartir dirección @@ -1927,7 +1927,7 @@ Abrir ubicación del archivo Por favor, reinicia la aplicación. Recordar más tarde - Saltar esta versión + Omitir esta versión Estable inactivo Error @@ -2058,7 +2058,7 @@ Nuevas opciones multimedia Puedes cambiar la posición de la barra desde el menú Apariencia. Descarga nuevas versiones desde GitHub. - Nuevo mensaje + Mensaje nuevo Enlace no válido Por favor, comprueba que el enlace SimpleX es correcto. %1$d archivo(s) se está(n) descargando todavía. @@ -2119,4 +2119,99 @@ Seguridad mejorada ✅ Borra o modera hasta 200 mensajes a la vez. Cambia el perfil de chat para invitaciones de un solo uso. + Error al guardar servidores + Error en la configuración del servidor. + Para el perfil de chat %s: + Ningún servidor de mensajes. + Ningún servidor para recibir archivos. + Ningún servidor para enviar archivos. + Seguridad de conexión + Compartir enlace de un uso con un amigo + Compartir dirección SimpleX en redes sociales. + Configuración de dirección + Crear enlace de un uso + Para redes sociales + Dirección SimpleX o enlace de un uso? + Selecciona operadores + Operadores de red + Las condiciones de los operadores habilitados serán aceptadas después de 30 días. + Revisar más tarde + Condiciones aceptadas el: %s. + Operador de red + Operador + Servidores predefinidos + Revisar condiciones + %s servidores + Las condiciones serán aceptadas el: %s. + Condiciones de uso + Para el enrutamiento privado + Error al añadir servidor + Abrir cambios + Abrir condiciones + Servidor añadido al operador %s. + El operador del servidor ha cambiado. + El protocolo del servidor ha cambiado. + Barras de herramientas + Difuminar + Navegación en el chat mejorada + Descentralización de la red + - El chat abre en el primer mensaje no leído.\n- Desplazamiento hasta los mensajes citados. + Aceptar condiciones + Condiciones aceptadas + Servidores de archivos y multimedia añadidos + Servidores de mensajes añadidos + ¿Dirección o enlace de un uso? + Las condiciones serán aceptadas automáticamente para los operadores habilitados el: %s. + Continuar + El texto con las condiciones actuales no se ha podido cargar, puedes revisar las condiciones en el siguiente enlace: + Habilitar Flux + Error al aceptar las condiciones + Error al actualizar el servidor + para mayor privacidad de los metadatos. + Ningún mensaje + Servidor nuevo + Ningún servidor de archivos y multimedia. + Ningún servidor para enrutamiento privado. + Ningún servidor para recibir mensajes. + Servidor del operador + O para compartir en privado + Selecciona los operadores de red a utilizar + Campartir dirección públicamente + Compartir enlaces de un uso y direcciones SimpleX es seguro a través de cualquier medio. + Actualizar + Sitio web + Tus servidores + Usar %s + Usar servidores + Usar para mensajes + Ver condiciones + Para recibir + Para enviar + Usar para archivos + Transparencia + Ver condiciones actualizadas + Mensajes no entregados + solamente con un contacto - comparte en persona o mediante cualquier aplicación de mensajería.]]> + Puedes añadir un nombre a la conexión para recordar a quién corresponde. + Cuando está habilitado más de un operador de red, la aplicación usa servidores de diferentes operadores para cada conversación. + Puedes configurar los operadores desde Servidores y Redes. + %s.]]> + %s.]]> + %s.]]> + %s.]]> + %s.]]> + %s.]]> + %s, acepta las condiciones de uso.]]> + Los servidores para archivos nuevos en tu perfil actual + El segundo operador predefinido! + Puedes configurar los servidores a través de su configuración. + Para protegerte contra una sustitución del enlace, puedes comparar los códigos de seguridad con tu contacto. + %s.]]> + %s.]]> + Si por ejemplo recibes los mensajes a través de un servidor de SimpleX Chat, la aplicación usará uno de Flux para el enrutamiento privado. + Pulsa Crear dirección SimpleX en el menú para crearla más tarde. + La conexión ha alcanzado el límite de mensajes no entregados. es posible que tu contacto esté desconectado. + El mensaje ha sido borrado o aún no se ha recibido. + Móvil remoto + O importa desde un archivo \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml index 23e2392fdc..8850e33a3e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml @@ -636,8 +636,7 @@ تمام مخاطبانتان متصل باقی خواهند ماند. به‌روزرسانی نمایه به مخاطبانتان ارسال خواهد شد. اگر تایید کنید، سرورهای پیام‌رسانی خواهند توانست نشانی‌ IP، و فراهم‌کننده شما را ببینند - و این که به چه سرورهایی متصل می‌شوید. مطمئن شوید قالب نشانی‌های سرور WebRTC ICE صحیح است، در خط‌های جدا نوشته شده و تکرار نشده‌اند. - یک اتصال جدای TCP (و اطلاعات ورود SOCKS) برای هر مخاطب و عضو گروه استفاده خواهد شد. -\nلطفا توجه داشته باشید: اگر اتصال‌های زیادی داشته باشید، مصرف باتری و ترافیک شما می‌تواند به شکل قابل توجه بالاتر باشد و بعضی اتصال‌ها ممکن است با موفقیت انجام نشوند. + برای هر مخاطب و عضو گروه استفاده خواهد شد. \nلطفا توجه داشته باشید: اگر اتصال‌های زیادی داشته باشید، مصرف باتری و ترافیک شما می‌تواند به شکل قابل توجه بالاتر باشد و بعضی اتصال‌ها ممکن است با موفقیت انجام نشوند.]]> حالت انزوای ترابری به روز شود؟ ویرایش تصویر ایجاد نشانی‌ SimpleX diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml index 28b29c59af..f933b2d6cc 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml @@ -251,8 +251,7 @@ Poista kontakti Yllä, sitten: Keskustelujen profiili - Jokaiselle kontaktille ja ryhmän jäsenelle käytetään erillistä TCP-yhteyttä (ja SOCKS-tunnistetietoja). -\nHuomaa: jos sinulla on useita yhteyksiä, akun ja data-liikenteen määrä voi olla huomattavasti korkeampi ja jotkin yhteydet voivat epäonnistua. + Jokaiselle kontaktille ja ryhmän jäsenelle käytetään erillistä TCP-yhteyttä (ja SOCKS-tunnistetietoja). \nHuomaa: jos sinulla on useita yhteyksiä, akun ja data-liikenteen määrä voi olla huomattavasti korkeampi ja jotkin yhteydet voivat epäonnistua.]]> Äänipuhelu Paras akulle. Saat ilmoituksia vain, kun sovellus on käynnissä (EI taustapalvelua).]]> %d tuntia diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml index 07b99ebe1d..6caf5b61ef 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml @@ -907,8 +907,7 @@ Profil et connexions au serveur Transport isolé Mettre à jour le mode d\'isolement du transport \? - Une connexion TCP distincte (et identifiant SOCKS) sera utilisée pour chaque contact et membre de groupe. -\nVeuillez noter : si vous avez de nombreuses connexions, votre consommation de batterie et de réseau peut être nettement plus élevée et certaines liaisons peuvent échouer. + pour chaque contact et membre de groupe. \nVeuillez noter : si vous avez de nombreuses connexions, votre consommation de batterie et de réseau peut être nettement plus élevée et certaines liaisons peuvent échouer.]]> Profil de chat Ajouter un profil Données de profil local uniquement diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index 216b666100..d8ce80884e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -13,7 +13,7 @@ Címváltoztatás megszakítása? Megszakítás 30 másodperc - Egyszer használható hivatkozás + Egyszer használható meghívó-hivatkozás %1$s szeretne kapcsolatba lépni Önnel ezen keresztül: A SimpleX Chatről 1 nap @@ -102,7 +102,7 @@ Több akkumulátort használ! Az alkalmazás mindig fut a háttérben - az értesítések azonnal megjelennek.]]> Letiltás adminisztrátor - Fénykép előnézet visszavonása + Képelőnézet visszavonása A jelkód megadása után az összes adat törlésre kerül. Felkérték a videó fogadására Letiltás @@ -200,7 +200,7 @@ Csoport létrehozása véletlenszerű profillal. Az ismerős és az összes üzenet törlésre kerül - ez a művelet nem vonható vissza! Az ismerősei törlésre jelölhetnek üzeneteket; Ön majd meg tudja nézni azokat. - Kapcsolódás egyszer használható hivatkozással? + Kapcsolódás egyszer használható meghívó-hivatkozással? Kapcsolódás egy hivatkozáson vagy QR-kódon keresztül Kapcsolódási hiba (AUTH) Csak név @@ -234,9 +234,7 @@ Ismerősök Kapcsolódási hiba Az ismerős még nem kapcsolódott! - - kapcsolódás könyvtár szolgáltatáshoz (BÉTA)! -\n- kézbesítési jelentések (20 tagig). -\n- gyorsabb és stabilabb. + - kapcsolódás könyvtár szolgáltatáshoz (BÉTA)!\n- kézbesítési jelentések (20 tagig).\n- gyorsabb és stabilabb. Hozzájárulás kapcsolódás (bemutatkozó meghívó) SimpleX-cím létrehozása @@ -477,8 +475,7 @@ Hiba a hálózat konfigurációjának frissítésekor TCP életben tartása Kamera váltás - Üdvözlöm! -\nCsatlakozzon hozzám a SimpleX Chaten keresztül: %s + Üdvözlöm!\nCsatlakozzon hozzám a SimpleX Chaten keresztül: %s A megjelenített név nem tartalmazhat szóközöket. Csoport Üdvözlőüzenet megadása… (nem kötelező) @@ -692,7 +689,7 @@ Világos Az üzenet törlésre kerül - ez a művelet nem vonható vissza! Markdown súgó - Rejtett üzenet + új üzenet Régi adatbázis-archívum Speciális beállítások Nincs kézbesítési információ @@ -771,7 +768,7 @@ Nincsenek háttérhívások Üzenetek Társított hordozható eszköz - Lehetővé teszi, hogy egyetlen csevegőprofilon belül több anonim kapcsolat legyen, anélkül, hogy megosztott adatok lennének közöttük. + Lehetővé teszi, hogy egyetlen csevegőprofilon belül több névtelen kapcsolat legyen, anélkül, hogy megosztott adatok lennének közöttük. Az üzenet törlésre lesz jelölve. A címzett(ek) képes(ek) lesz(nek) felfedni ezt az üzenetet. Elhagyás Rendben @@ -885,7 +882,7 @@ Bárki üzemeltethet kiszolgálókat. Megnyitás Protokoll időtúllépése - titkos + titok Értesítés előnézete várakozás a visszaigazolásra… Fájl megállítása @@ -910,7 +907,7 @@ Csak Ön tud hívásokat indítani. Biztonságos sorbaállítás Értékelje az alkalmazást - Egyszer használható hivatkozás megosztása + Egyszer használható meghívó-hivatkozás megosztása Hiba az adatbázis visszaállításakor %s és %s Ön engedélyezi @@ -931,7 +928,7 @@ (beolvasás, vagy beillesztés a vágólapról) Várakozás a videóra Válasz - Ez az Ön egyszer használható hivatkozása! + Ez az Ön egyszer használható meghívó-hivatkozása! SimpleX Chat hívások Új inkognitóprofil használata Frissítse az alkalmazást, és lépjen kapcsolatba a fejlesztőkkel. @@ -963,7 +960,7 @@ Adatbázismentés visszaállítása Visszavonás Kérje meg az ismerősét, hogy engedélyezze a hangüzenetek küldését. - egyszer használható hivatkozást osztott meg + Ön egy egyszer használható meghívó-hivatkozást osztott meg A hivatkozás megnyitása a böngészőben gyengítheti az adatvédelmet és a biztonságot. A megbízhatatlan SimpleX-hivatkozások pirossal vannak kiemelve. Saját ICE-kiszolgálók Kapcsolat létrehozása @@ -1075,7 +1072,7 @@ tulajdonos Bekapcsolás %s, %s és %s kapcsolódott - Egyszer használható SimpleX-meghívó + Egyszer használható SimpleX-meghívó-hivatkozás Hívások nem sikerült elküldeni KEZELŐFELÜLET SZÍNEI @@ -1106,7 +1103,7 @@ Saját SMP-kiszolgálók A kézbesítési jelentések le vannak tiltva Adatbázismappa megnyitása - egyszer használható hivatkozáson keresztül + egyszer használható meghívó-hivatkozáson keresztül Csoportbeállítások megadása ezen keresztül: %1$s igen @@ -1119,7 +1116,7 @@ A kiszolgáló QR-kódjának beolvasása Megállítás Címmegosztás megállítása? - Csevegés profilok megnyitása + Csevegési profilok megváltoztatása Csatlakozáskérés megismétlése? Várakozás a képre Hangüzenetek @@ -1230,9 +1227,7 @@ Némítás megszüntetése SimpleX Chat megnyitása a hívás fogadásához Fájlfogadás megállítása? - - értesíti az ismerősöket a törlésről (nem kötelező) -\n- profil nevek szóközökkel -\n- és még sok más! + - értesíti az ismerősöket a törlésről (nem kötelező)\n- profil nevek szóközökkel\n- és még sok más! Lengyel kezelőfelület Kiszolgáló használata Fogadva ekkor: %s @@ -1272,14 +1267,12 @@ TCP kapcsolat időtúllépése A(z) %1$s nevű profiljának SimpleX-címe megosztásra fog kerülni. Ön már kapcsolódott a következőhöz: %1$s. - Jelenlegi csevegési adatbázis TÖRLÉSRE és FELCSERÉLÉSRE kerül az importált által! -\nEz a művelet nem vonható vissza - profiljai, ismerősei, csevegési üzenetei és fájljai véglegesen törölve lesznek. + Jelenlegi csevegési adatbázis TÖRLÉSRE és FELCSERÉLÉSRE kerül az importált által!\nEz a művelet nem vonható vissza - profiljai, ismerősei, csevegési üzenetei és fájljai véglegesen törölve lesznek. Ötletek és javaslatok Figyelmeztetés: néhány adat elveszhet! Koppintson ide az új csevegés indításához Várakozás a számítógépre… - A privát üzenetküldés -\nkövetkező generációja + A privát üzenetküldés\nkövetkező generációja Hálózati beállítások megváltoztatása? Várakozás a hordozható eszköz társítására: Biztonságos kapcsolat hitelesítése @@ -1288,16 +1281,14 @@ fájlok fogadása egyelőre még nem támogatott Csoportprofil mentése Visszaállítás alapértelmezettre - Hacsak az ismerőse nem törölte a kapcsolatot, vagy ez a hivatkozás már használatban volt egyszer, lehet hogy ez egy hiba – jelentse a problémát. -\nA kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapcsolattartási hivatkozást, és ellenőrizze, hogy a hálózati kapcsolat stabil-e. + Hacsak az ismerőse nem törölte a kapcsolatot, vagy ez a hivatkozás már használatban volt egyszer, lehet hogy ez egy hiba – jelentse a problémát.\nA kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapcsolattartási hivatkozást, és ellenőrizze, hogy a hálózati kapcsolat stabil-e. videóhívás (nem e2e titkosított) Alkalmazás új kapcsolatokhoz Az új üzenetek rendszeresen letöltésre kerülnek az alkalmazás által – naponta néhány százalékot használ az akkumulátorból. Az alkalmazás nem használ push-értesítéseket – az eszközről származó adatok nem kerülnek elküldésre a kiszolgálóknak. Számítógép címének beillesztése kapcsolattartási cím-hivatkozáson keresztül SimpleX-háttérszolgáltatást használja - az akkumulátornak csak néhány százalékát használja naponta.]]> - Az ismerősének online kell lennie ahhoz, hogy a kapcsolat létrejöjjön. -\nVisszavonhatja ezt az ismerőskérelmet és eltávolíthatja az ismerőst (ezt később ismét megpróbálhatja egy új hivatkozással). + Az ismerősének online kell lennie ahhoz, hogy a kapcsolat létrejöjjön.\nVisszavonhatja ezt az ismerőskérelmet és eltávolíthatja az ismerőst (ezt később ismét megpróbálhatja egy új hivatkozással). A jelszó nem található a Keystore-ban, ezért kézzel szükséges megadni. Ez akkor történhetett meg, ha visszaállította az alkalmazás adatait egy biztonságimentési eszközzel. Ha nem így történt, akkor lépjen kapcsolatba a fejlesztőkkel. Az ismerősei továbbra is kapcsolódva maradnak. A kiszolgálónak engedélyre van szüksége a várólisták létrehozásához, ellenőrizze jelszavát @@ -1319,8 +1310,7 @@ A kiszolgálónak engedélyre van szüksége a sorbaállítás létrehozásához, ellenőrizze jelszavát Kapcsolódni fog a csoport összes tagjához. Lehetséges, hogy a kiszolgáló címében szereplő tanúsítvány-ujjlenyomat helytelen - A biztonsága érdekében kapcsolja be a SimpleX-zár funkciót. -\nA funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beállítására az eszközén. + A biztonsága érdekében kapcsolja be a SimpleX-zár funkciót.\nA funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beállítására az eszközén. A videó akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később! Ellenőrizze a hálózati kapcsolatát a következővel: %1$s, és próbálja újra. A SimpleX-zár az „Adatvédelem és biztonság” menüben kapcsolható be. @@ -1333,7 +1323,7 @@ A fájl fogadása le fog állni. Ne felejtse el, vagy tárolja biztonságosan – az elveszett jelszót nem lehet visszaállítani! A videó akkor érkezik meg, amikor a küldője befejezte annak feltöltését. - egyszer használható hivatkozást osztott meg inkognitóban + Ön egy egyszer használható meghívó-hivatkozást osztott meg inkognitóban Ön már kapcsolódott ahhoz a kiszolgálóhoz, amely az adott ismerősétől érkező üzenetek fogadására szolgál. Később engedélyezheti a „Beállításokban” Akkor lesz kapcsolódva a csoporthoz, amikor a csoport tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később! @@ -1352,8 +1342,7 @@ Az ismerősei és az üzenetek (kézbesítés után) nem kerülnek tárolásra a SimpleX-kiszolgálókon. Üzenetek formázása a szövegbe szúrt speciális karakterekkel: Megnyitás az alkalmazásban gombra.]]> - A csevegési profilja elküldésre kerül -\naz ismerőse számára + A csevegési profilja elküldésre kerül\naz ismerőse számára Egy olyan ismerősét próbálja meghívni, akivel inkognitó-profilt osztott meg abban a csoportban, amelyben a saját fő profilja van használatban %1$s nevű csoporthoz.]]> Amikor az alkalmazás fut @@ -1363,9 +1352,7 @@ A hangüzenetek küldése le van tiltva ebben a csoportban. Alkalmazás akkumulátor-használata / Korlátlan módot az alkalmazás beállításaiban.]]> Biztonságos kvantumrezisztens-protokollon keresztül. - - 5 perc hosszúságú hangüzenetek. -\n- egyéni üzenet-eltűnési időkorlát. -\n- előzmények szerkesztése. + - 5 perc hosszúságú hangüzenetek.\n- egyéni üzenet-eltűnési időkorlát.\n- előzmények szerkesztése. Társítás számítógéppel menüt a hordozható eszköz alkalmazásban és olvassa be a QR-kódot.]]> %s ekkor: %s Akkor lesz kapcsolódva, amikor az ismerősének eszköze online lesz, várjon, vagy ellenőrizze később! @@ -1399,7 +1386,7 @@ Alkalmazás akkumulátor-használata / Korlátlan módot az alkalmazás beállításaiban.]]> Ez a karakterlánc nem egy meghívó-hivatkozás! Új csevegés kezdése - A kapcsolódás már folyamatban van ezen az egyszer használható hivatkozáson keresztül! + A kapcsolódás már folyamatban van ezen az egyszer használható meghívó-hivatkozáson keresztül! Nem veszíti el az ismerőseit, ha később törli a címét. A beállítások frissítése a kiszolgálókhoz való újra kapcsolódással jár. kapcsolatba akar lépni Önnel! @@ -1444,8 +1431,7 @@ Tagok meghívásának kihagyása Ezek felülbírálhatók az ismerős- és csoportbeállításokban. Az ismerőse, akivel megosztotta ezt a hivatkozást, NEM fog tudni kapcsolódni! - A véletlenszerű jelmondat egyszerű szövegként van tárolva a beállításokban. -\nEz később megváltoztatható. + A véletlenszerű jelmondat egyszerű szövegként van tárolva a beállításokban.\nEz később megváltoztatható. Koppintson ide az inkognitóban való kapcsolódáshoz Jelmondat beállítása az exportáláshoz A kézbesítési jelentések le vannak tiltva %d csoportban @@ -1475,8 +1461,7 @@ Az Ön által elfogadott kérelem vissza lesz vonva! Élő üzenet küldése - a címzett(ek) számára frissül, ahogy beírja A KÉZBESÍTÉSI JELENTÉSEKET A KÖVETKEZŐ CÍMRE KELL KÜLDENI - A következő üzenet azonosítója hibás (kisebb vagy egyenlő az előzővel). -\nEz valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. + A következő üzenet azonosítója hibás (kisebb vagy egyenlő az előzővel).\nEz valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. Az eszköz neve megosztásra kerül a társított hordozható eszközön használt alkalmazással. A címzettek a beírás közben látják a szövegváltozásokat. Tárolja el biztonságosan jelmondatát, mert ha elveszíti azt, NEM tudja megváltoztatni. @@ -1520,7 +1505,7 @@ Vagy mutassa meg ezt a kódot Kamera hozzáférés engedélyezése Fel nem használt meghívó megtartása? - Egyszer használható meghívó-hivatkozás megosztása + Ennek az egyszer használható meghívó-hivatkozásnak a megosztása Új csevegés Csevegések betöltése… Hivatkozás létrehozása… @@ -1541,10 +1526,7 @@ A kapcsolat megszakadt A kapcsolat megszakadt Számítógép kapcsolata rossz állapotban van - Jelentse a fejlesztőknek: -\n%s -\n -\nAz alkalmazás újraindítása javasolt. + Jelentse a fejlesztőknek:\n%s\n\nAz alkalmazás újraindítása javasolt. Jelentse a fejlesztőknek: \n%s %s hordozható eszköz által használt alkalmazás verziója nem támogatott. Győződjön meg arról, hogy mindkét eszközön ugyanazt a verziót használja]]> @@ -1587,7 +1569,7 @@ A keresősáv elfogadja a meghívó-hivatkozásokat. Titkosított fájlokkal és médiatartalmakkal. Csökkentett akkumulátor-használattal. - Magyar és török felhasználói felület + Magyar és török kezelőfelület A közelmúlt eseményei és továbbfejlesztett könyvtárbot. feloldotta %s letiltását Ön feloldotta %s letiltását @@ -1604,8 +1586,7 @@ Hiba a tag az összes csoporttag számára való letiltásakor Az üzenet túl nagy Az üdvözlőüzenet túl hosszú - Az adatbázis átköltöztetése folyamatban van. -\nEz eltarthat néhány percig. + Az adatbázis átköltöztetése folyamatban van.\nEz eltarthat néhány percig. Hanghívás A hívás befejeződött Videóhívás @@ -1742,13 +1723,11 @@ Profilkép alakzat Négyzet, kör vagy bármi a kettő között. Célkiszolgáló-hiba: %1$s - Továbbító kiszolgáló: %1$s -\nHiba: %2$s + Továbbító kiszolgáló: %1$s\nHiba: %2$s Hálózati problémák - az üzenet többszöri elküldési kísérlet után lejárt. A kiszolgáló verziója nem kompatibilis a hálózati beállításokkal. Hibás kulcs vagy ismeretlen kapcsolat - valószínűleg ez a kapcsolat törlődött. - Továbbító-kiszolgáló: %1$s -\nCélkiszolgáló hiba: %2$s + Továbbító-kiszolgáló: %1$s\nCélkiszolgáló hiba: %2$s Hiba: %1$s Kapacitás túllépés - a címzett nem kapta meg a korábban elküldött üzeneteket. Üzenetkézbesítési figyelmeztetés @@ -1779,8 +1758,7 @@ IP-cím védelem Az alkalmazás kérni fogja az ismeretlen fájlkiszolgálókról történő letöltések megerősítését (kivéve, ha az .onion vagy a SOCKS proxy engedélyezve van). Ismeretlen kiszolgálók! - Tor vagy VPN nélkül az IP-címe látható lesz az XFTP-közvetítő-kiszolgálók számára: -\n%1$s. + Tor vagy VPN nélkül az IP-címe látható lesz az XFTP-közvetítő-kiszolgálók számára:\n%1$s. Összes színmód Fekete Színmód @@ -1812,8 +1790,7 @@ További kiemelés 2 Alkalmazás téma Perzsa kezelőfelület - Védje IP-címét az ismerősei által kiválasztott üzenet-közvetítő-kiszolgálókkal szemben. -\nEngedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. + Védje IP-címét az ismerősei által kiválasztott üzenet-közvetítő-kiszolgálókkal szemben.\nEngedélyezze a „Beállítások / Hálózat és kiszolgálók” menüben. Ismeretlen kiszolgálókról származó fájlok megerősítése. Javított üzenetkézbesítés Alkalmazás témájának visszaállítása @@ -1822,14 +1799,12 @@ Privát üzenet-útválasztás 🚀 Fájlok biztonságos fogadása Csökkentett akkumulátor-használattal. - Hiba a WebView előkészítésekor. Frissítse rendszerét az új verzióra. Lépjen kapcsolatba a fejlesztőkkel. -\nHiba: %s + Hiba a WebView előkészítésekor. Frissítse rendszerét az új verzióra. Lépjen kapcsolatba a fejlesztőkkel.\nHiba: %s Felhasználó által létrehozott téma visszaállítása Üzenet-sorbaállítási információ nincs Kézbesítési hibák felderítése - a kiszolgáló sorbaállítási információi: %1$s -\nutoljára kézbesített üzenet: %2$s + a kiszolgáló sorbaállítási információi: %1$s\n\nutoljára kézbesített üzenet: %2$s Hibás kulcs vagy ismeretlen fájltöredék cím - valószínűleg a fájl törlődött. Ideiglenesfájl-hiba Üzenetállapot @@ -1841,8 +1816,7 @@ Fájlállapot: %s Másolási hiba Ezt a hivatkozást egy másik hordozható eszközön már használták, hozzon létre egy új hivatkozást a számítógépén. - Ellenőrizze, hogy a hordozható eszköz és a számítógép ugyanahhoz a helyi hálózathoz csatlakozik-e, valamint a számítógép tűzfalában engedélyezve van-e a kapcsolat. -\nMinden további problémát osszon meg a fejlesztőkkel. + Ellenőrizze, hogy a hordozható eszköz és a számítógép ugyanahhoz a helyi hálózathoz csatlakozik-e, valamint a számítógép tűzfalában engedélyezve van-e a kapcsolat.\nMinden további problémát osszon meg a fejlesztőkkel. Nem lehet üzenetet küldeni A kiválasztott csevegési beállítások tiltják ezt az üzenetet. Próbálja meg később. @@ -2072,8 +2046,7 @@ %1$d fájl törölve lett. %1$s üzenet továbbítása Üzenetek továbbítása… - %1$d fájlhiba: -\n%2$s + %1$d fájlhiba:\n%2$s %1$s üzenet nem lett továbbítva %1$s üzenet továbbítása? Üzenetek továbbítása fájlok nélkül? @@ -2081,8 +2054,7 @@ %1$s üzenet mentése Hiba az üzenetek továbbításakor Hang elnémítva - Hiba a WebView előkészítésekor. Győződjön meg arról, hogy a WebView telepítve van-e, és támogatja-e az arm64 architektúrát. -\nHiba: %s + Hiba a WebView előkészítésekor. Győződjön meg arról, hogy a WebView telepítve van-e, és támogatja-e az arm64 architektúrát.\nHiba: %s Sarok Üzenetbuborék alakja Farok @@ -2100,7 +2072,102 @@ Legfeljebb 200 üzenet egyszerre való törlése, vagy moderálása. Legfeljebb 20 üzenet egyszerre való továbbítása. Hang/Videó váltása hívás közben. - Csevegési profilváltás az egyszer használható meghívókhoz. + Csevegési profilváltás az egyszer használható meghívó-hivatkozásokhoz. Továbbfejlesztett biztonság ✅ - A SimpleX Chat biztonsága a Trail of Bits által lett újraauditálva. + A SimpleX Chat biztonsága a Trail of Bits által lett felülvizsgálva. + Hiba a kiszolgálók mentésekor + Nincsenek üzenet-kiszolgálók. + Nincsenek üzenetfogadó-kiszolgálók. + Nincsenek média- és fájlkiszolgálók. + A(z) %s nevű csevegési profilhoz: + Cím vagy egyszer használható meghívó-hivatkozás? + Új kiszolgáló + Címbeállítások + Előre beállított kiszolgálók + Üzemeltető + Feltételek megtekintése + Nincsenek kiszolgálók a privát üzenet-útválasztáshoz. + Nincsenek fájlküldő-kiszolgálók. + Nincsenek fájlfogadó-kiszolgálók. + Hibák a kiszolgálók konfigurációjában. + Hiba a feltételek elfogadásakor + Kézbesítetlen üzenetek + A kapcsolat elérte a kézbesítetlen üzenetek számának határát, az Ön ismerőse lehet, hogy offline állapotban van. + Nincs üzenet + Ez az üzenet törlésre került vagy még nem érkezett meg. + Koppintson a SimpleX-cím létrehozása menüpontra a későbbi létrehozáshoz. + Cím nyilvános megosztása + SimpleX-cím megosztása a közösségi médiában. + Egyszer használható meghívó-hivatkozás megosztása egy baráttal + egyetlen ismerőssel használható - személyesen vagy bármilyen üzenetküldőn keresztül megosztható.]]> + Beállíthatja az ismerős nevét, hogy emlékezzen arra, hogy kivel osztotta meg a hivatkozást. + Kapcsolatbiztonság + A SimpleX-cím és az egyszer használható meghívó-hivatkozás biztonságosan megosztható bármilyen üzenetküldőn keresztül. + A hivatkozás cseréje elleni védelem érdekében összehasonlíthatja a biztonsági kódokat az ismerősével. + A közösségi médiához + Vagy a privát megosztáshoz + SimpleX-cím vagy egyszer használható meghívó-hivatkozás? + Egyszer használható meghívó-hivatkozás létrehozása + Üzemeltetők kiválasztása + Hálózati üzemeltetők + Amikor egynél több hálózati üzemeltető van engedélyezve, akkor az alkalmazás minden egyes beszélgetéshez a különböző üzemeltetők kiszolgálóit használja. + Ha például a SimpleX Chat kiszolgálón keresztül fogadja az üzeneteket, az alkalmazás a Flux egyik kiszolgálóját használja a privát útválasztáshoz. + Válassza ki a használni kívánt hálózati üzemeltetőket. + Felülvizsgálat később + A kiszolgálókat a beállításokon keresztül konfigurálhatja. + A feltételek 30 nap elteltével lesznek elfogadva az engedélyezett üzemeltetők számára. + Az üzemeltetőket a „Hálózat és kiszolgálók” beállításaban konfigurálhatja. + Frissítés + Folytatás + Feltételek felülvizsgálata + Elfogadott feltételek + A feltételek automatikusan elfogadásra kerülnek az engedélyezett üzemeltetők számára: %s. + Az Ön kiszolgálói + %s.]]> + %s.]]> + %s kiszolgáló + Hálózati üzemeltető + Weboldal + Feltételek elfogadva ekkor: %s. + A feltételek ekkor lesznek elfogadva: %s. + Kiszolgálók használata + %s használata + A jelenlegi feltételek szövegét nem lehetett betölteni, a feltételeket ezen a hivatkozáson keresztül vizsgálhatja felül: + %s.]]> + %s.]]> + %s.]]> + %s.]]> + %s.]]> + %s.]]> + Feltételek elfogadása + Használati feltételek + %s kiszolgálóinak használatához fogadja el a használati feltételeket.]]> + Használat az üzenetekhez + A fogadáshoz + A privát útválasztáshoz + Hozzáadott üzenetkiszolgálók + Használat a fájlokhoz + A küldéshez + Hozzáadott média- és fájlkiszolgálók + Feltételek megnyitása + Változások megnyitása + Hiba a kiszolgáló frissítésekor + A kiszolgáló-protokoll megváltozott. + A kiszolgáló üzemeltetője megváltozott. + Kiszolgáló-üzemeltető + Kiszolgáló hozzáadva a következő üzemeltetőhöz: %s. + Hiba a kiszolgáló hozzáadásakor + Átlátszóság + Elhomályosítás + Hálózati decentralizáció + A második előre beállított üzemeltető az alkalmazásban! + Flux engedélyezése + Alkalmazás-eszköztárak + a metaadatok jobb védelme érdekében. + Javított csevegési navigáció + - Csevegés megnyitása az első olvasatlan üzenetnél.\n- Ugrás az idézett üzenetekre. + Frissített feltételek megtekintése + Az Ön jelenlegi csevegőprofiljához tartozó új fájlok kiszolgálói + Vagy archívumfájl importálása + Távoli hordozható eszközök \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml index caeec02deb..a081eb37bf 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml @@ -1277,4 +1277,21 @@ Hapus Dibuat pada: %s diblokir + Ganti + Buka blokir + Buka blokir anggota untuk semua? + Buka untuk semua + Diblokir oleh admin + ANGGOTA + Hapus anggota + Status pesan: %s + Status berkas: %s + Grup + dimatikan + Buka blokir anggota? + Buka blokir anggota + tidak aktif + Hapus anggota? + Hapus anggota + Pesan tersimpan \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index 74c2397edd..0b827df4d7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -904,8 +904,7 @@ Aggiornare la modalità di isolamento del trasporto\? Tutte le chat e i messaggi verranno eliminati. Non è reversibile! Profilo di chat - Verrà usata una connessione TCP separata (e le credenziali SOCKS) per ogni contatto e membro del gruppo . -\n Nota: : se hai molte connessioni, il consumo di batteria e traffico può essere notevolmente superiore e alcune connessioni potrebbero fallire. + per ogni contatto e membro del gruppo .\n Nota: : se hai molte connessioni, il consumo di batteria e traffico può essere notevolmente superiore e alcune connessioni potrebbero fallire.]]> Connessione Questa impostazione si applica ai messaggi del profilo di chat attuale Isolamento del trasporto @@ -1101,7 +1100,7 @@ Grazie agli utenti – contribuite via Weblate! Video e file fino a 1 GB Accetta automaticamente - Apri i profili di chat + Cambia i profili di chat Maggiori informazioni Per connettervi, il tuo contatto può scansionare il codice QR o usare il link nell\'app. Quando le persone chiedono di connettersi, puoi accettare o rifiutare. @@ -2119,4 +2118,99 @@ Cambia profilo di chat per inviti una tantum. Elimina o modera fino a 200 messaggi. Inoltra fino a 20 messaggi alla volta. + Nessun server dei messaggi. + Nessun server per ricevere messaggi. + Errori nella configurazione dei server. + Per il profilo di chat %s: + Messaggi non consegnati + Nessun messaggio + Sicurezza della connessione + L\'indirizzo SimpleX e i link una tantum sono sicuri da condividere tramite qualsiasi messenger. + Per proteggerti dalla sostituzione del tuo link, puoi confrontare i codici di sicurezza del contatto. + Puoi impostare il nome della connessione per ricordare con chi è stato condiviso il link. + Condividi link una tantum con un amico + Crea link una tantum + Per i social media + O per condividere in modo privato + Operatori di rete + Quando più di un operatore di rete è attivato, l\'app userà i server di diversi operatori per ogni conversazione. + Puoi configurare gli operatori nelle impostazioni di rete e server. + Scegli gli operatori + Seleziona gli operatori di rete da usare. + Continua + Aggiorna + Esamina più tardi + Server preimpostati + Condizioni accettate + Le condizioni verranno accettate automaticamente per gli operatori attivati il: %s. + I tuoi server + Esamina le condizioni + %s.]]> + %s.]]> + Il testo delle condizioni attuali testo non è stato caricato, puoi consultare le condizioni tramite questo link: + Operatore di rete + Server di %s + Usa %s + Sito web + %s.]]> + Condizioni accettate il: %s. + Operatore + %s.]]> + %s.]]> + %s.]]> + %s.]]> + Accetta le condizioni + Errore di aggiornamento del server + Per l\'instradamento privato + I server per nuovi file del tuo profilo di chat attuale + Per ricevere + Per inviare + Usa per i messaggi + Vedi le condizioni + %s.]]> + %s, accetta le condizioni d'uso.]]> + Condizioni d\'uso + Apri le modifiche + Apri le condizioni + Il protocollo del server è cambiato. + Errore di aggiunta del server + Server dell\'operatore + Server aggiunto all\'operatore %s. + L\'operatore del server è cambiato. + Barre degli strumenti + Trasparenza + Decentralizzazione della rete + Il secondo operatore preimpostato nell\'app! + Attiva Flux + Vedi le condizioni aggiornate + Sfocatura + Server dei messaggi aggiunti + Server di multimediali e file aggiunti + Indirizzo o link una tantum? + Impostazioni dell\'indirizzo + con un solo contatto - condividilo di persona o tramite un messenger.]]> + Le condizioni verranno accettate per gli operatori attivati dopo 30 giorni. + Le condizioni verranno accettate il: %s. + Errore di accettazione delle condizioni + Errore di salvataggio dei server + per una migliore privacy dei metadati. + Ad esempio, se ricevi messaggi tramite il server di SimpleX Chat, l\'app userà uno dei server Flux per l\'instradamento privato. + Navigazione della chat migliorata + Nuovo server + Usa per i file + Indirizzo SimpleX o link una tantum? + Questo messaggio è stato eliminato o non ancora ricevuto. + Tocca \"Crea indirizzo SimpleX\" nel menu per crearlo più tardi. + La connessione ha raggiunto il limite di messaggi non consegnati, il contatto potrebbe essere offline. + Usa i server + Puoi configurare i server nelle impostazioni. + Nessun server di multimediali e file. + Nessun server per l\'instradamento dei messaggi privati. + Nessun server per ricevere file. + Nessun server per inviare file. + - Apri la chat sul primo messaggio non letto.\n- Salta ai messaggi citati. + Condividi indirizzo pubblicamente + Condividi indirizzo SimpleX sui social media. + O importa file archivio + Telefoni remoti \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml index c163458097..01c19a20f0 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml @@ -60,8 +60,7 @@ קוד גישה לאפליקציה גרסת האפליקציה לכל פרופיל צ׳אט שיש ברשותך באפליקציה.]]> - חיבור TCP נפרד (ואישור SOCKS) ייווצר לכל איש קשר וחבר קבוצה. -\nשימו לב: אם ברשותכם חיבורים רבים, צריכת הסוללה ותעבורת האינטרנט עשויה להיות גבוהה משמעותית וחלק מהחיבורים עלולים להיכשל. + לכל איש קשר וחבר קבוצה. \nשימו לב: אם ברשותכם חיבורים רבים, צריכת הסוללה ותעבורת האינטרנט עשויה להיות גבוהה משמעותית וחלק מהחיבורים עלולים להיכשל.]]> הנמען התבקש לקבל את הסרטון הנמען התבקש לקבל את התמונה צרף diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml index edd22933ec..83b29a0b4c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml @@ -41,8 +41,7 @@ アプリのバージョン アプリのバージョン: v%s アプリ内の各チャットプロフィールに、.連絡先毎にそれぞれのTCP接続(とSOCKS資格情報)が使われます。]]> - 各連絡先とグループに、それぞれのTCP接続(とSOCKS資格情報)が使われます。 -\n※注意※ 接続が多かったら、電池とデータの使用量が増えて、切断する可能性もあります。 + 各連絡先とグループに、それぞれのTCP接続(とSOCKS資格情報)が使われます。 \n※注意※ 接続が多かったら、電池とデータの使用量が増えて、切断する可能性もあります。]]> 太文字 音声通話 音声とビデオ通話 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml index 9d129847e4..8395f5ea48 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml @@ -351,12 +351,7 @@ 설정에서 잠금 화면에서 바로 전화를 받을 수 있도록 설정할 수 있어요. 연결을 완료하려면 대화 상대가 온라인 상태여야 해요. \n연결 요청을 취소하고 대화 상대를 삭제할 수 있어요 (그리고 새 링크로 재시도). - 다음과 같은 경우에 발생할 수 있어요. -\n1. 대화 상대가 메시지를 보낸 지 30일 지나서 서버에서 삭제된 경우 -\n2. 메시지를 수신하는 데 사용된 서버가 업데이트되고 재부팅된 경우 -\n3. 침해된 연결의 경우 -\n서버 업데이트를 받으려면 설정에서 개발자에게 연락해 주세요. -\n저희 개발팀은 메시지 손실을 방지하기 위해 중복된 서버를 추가할 예정이에요. + 다음과 같은 경우에 발생할 수 있습니다. \n1. 대화 상대가 메시지를 보낸 지 30일 지나서 서버에서 삭제된 경우 \n2. 메시지를 수신하는 데 사용된 서버가 업데이트되고 재부팅된 경우 \n3. 침해된 연결의 경우 SimpleX 잠금 켜짐 응답됨… 확인 받음… @@ -1288,7 +1283,7 @@ 보낸 날짜 전송 디버그 파일 및 미디어는 이 그룹에서 금지됩니다. - 이 장치의 이름을 입력하십시오… + 이 기기의 이름을 입력하십시오… 데스크톱을 찾음 전달 서버: %1$s\n대상 서버 오류: %2$s 전달 서버: %1$s\n오류: %2$s @@ -1420,4 +1415,47 @@ 친구 초대 메시지 영구 삭제 초대 + 운영자 선택 + %s.]]> + 채팅 프로필 변경 + SOCKS 프록시가 지원하지 않는 경우 .onion 호스트를 No로 사용합니다.]]> + %s 이 찾을 수 없음]]> + 앱 설정에서 앱 배터리 사용량 / 제한 없음 을 선택하세요.]]> + %s 이 연결 끊김]]> + %1$s!]]> + 사용해서는 안 됩니다.]]> + 사용자 가이드에서 확인하세요.]]> + 모바일 앱에서 열기 버튼을 클릭합니다.]]> + 운영자 + %s.]]> + 약관 수락 날짜: %s. + %s.]]> + %s.]]> + %s.]]> + 추가된 미디어 및 파일 서버 + 앱 툴바 + 흐리기 + 약관 수락 + 약관을 수락함 + 추가된 메시지 서버 + 주소 또는 일회용 링크? + 주소 설정 + 한 명의 연락처에만 사용할 수 있으며 - 직접 또는 메신저를 통해 공유하십시오.]]> + %s.]]> + %s.]]> + %s 이 현재 사용 중]]> + %s.]]> + SimpleX 백그라운드 서비스를 제공합니다. - 이 기능은 하루에 몇 퍼센트의 배터리를 소모합니다.]]> + %s 의 서버를 사용하려면 사용 약관에 동의하십시오.]]> + %1$s 에 연결 중입니다.]]> + SimpleX의 백그라운드에서 실행되도록 허용하십시오. 그렇지 않으면 알림을 사용할 수 없습니다.]]> + 앱 설정에서 앱 배터리 사용량 / 제한 없음 을 선택하십시오.]]> + %s 이 현재 비활성화됨]]> + SimpleX Chat 개발자에게 연결하여 질문하고 업데이트를 받을 수 있습니다.]]> + %s 이 연결 끊김]]> + 에서 데스크톱에서 사용을 열고 QR 코드를 스캔합니다.]]> + %s]]> + %1$s 그룹에 가입하는 중 입니다.]]> + %s 버전이 지원되지 않습니다. 두 기기에서 동일한 버전을 사용하는지 확인하십시오.]]> + %1$s 그룹에 속해 있습니다.]]> \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index 2b71c3243b..3d3aac1957 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -47,8 +47,7 @@ Toestaan 1 dag Accepteer - Er wordt een afzonderlijke TCP-verbinding (en SOCKS-referentie) gebruikt voor elk contact en groepslid . -\nLet op: als u veel verbindingen heeft, kan uw batterij- en verkeersverbruik aanzienlijk hoger zijn en kunnen sommige verbindingen uitvallen. + voor elk contact en groepslid.\nLet op: als u veel verbindingen hebt, kan het batterij- en verkeersverbruik aanzienlijk hoger zijn en kunnen sommige verbindingen mislukken.]]> audio oproep Geluid aan Audio en video gesprekken @@ -1098,7 +1097,7 @@ Poolse interface Dank aan de gebruikers – draag bij via Weblate! Video\'s en bestanden tot 1 GB - Open chatprofielen + Chatprofielen wijzigen Over SimpleX adres Kom meer te weten Om verbinding te maken, kan uw contact de QR-code scannen of de link in de app gebruiken. @@ -2116,4 +2115,98 @@ Maximaal 200 berichten verwijderen of modereren. Stuur maximaal 20 berichten tegelijk door. Betere gesprekken + Accepteer voorwaarden + Berichtservers toegevoegd + Media- en bestandsservers toegevoegd + Geaccepteerde voorwaarden + Adres of eenmalige link? + %s.]]> + Fout bij het opslaan van servers + Geen berichtenservers. + Geen media- en bestandsservers. + Geen servers om bestanden te ontvangen. + Geen servers om berichten te ontvangen. + Fouten in de serverconfiguratie. + Voor chatprofiel %s: + Niet afgeleverde berichten + De verbinding heeft de limiet van niet-afgeleverde berichten bereikt. Uw contactpersoon is mogelijk offline. + Dit bericht is verwijderd of nog niet ontvangen. + Tik op SimpleX-adres maken in het menu om het later te maken. + Adres openbaar delen + Deel eenmalig een link met een vriend + Deel het SimpleX-adres op sociale media. + slechts met één contactpersoon worden gebruikt - deel persoonlijk of via een messenger.]]> + Beveiliging van de verbinding + U kunt een verbindingsnaam instellen, zodat u kunt onthouden met wie de link is gedeeld. + SimpleX-adressen en eenmalige links kunnen veilig worden gedeeld via elke messenger. + Nieuwe server + Voor social media + Of om privé te delen + Adres instellingen + Eenmalige link maken + Operators kiezen + Voor ingeschakelde operators worden de voorwaarden na 30 dagen geaccepteerd. + Netwerkbeheerders + Later beoordelen + Selecteer welke netwerkoperators u wilt gebruiken. + Update + Wanneer er meer dan één netwerkoperator is ingeschakeld, gebruikt de app voor elk gesprek de servers van verschillende operators. + U kunt operators configureren in Netwerk- en serverinstellingen. + Doorgaan + Voorwaarden bekijken + Uw servers + %s.]]> + %s.]]> + %s.]]> + Voorwaarden geaccepteerd op: %s. + Voorwaarden worden geaccepteerd op: %s. + De tekst van de huidige voorwaarden kon niet worden geladen. U kunt de voorwaarden bekijken via deze link: + Netwerkbeheerder + Operator + %s servers + Gebruik %s + Gebruik servers + Website + %s.]]> + %s.]]> + %s te gebruiken, moet u de gebruiksvoorwaarden accepteren.]]> + Gebruiksvoorwaarden + Voor privé-routering + Wijzigingen openen + De servers voor nieuwe bestanden van uw huidige chatprofiel + Om te ontvangen + Gebruik voor bestanden + Gebruik voor berichten + Bekijk voorwaarden + Serverprotocol gewijzigd. + Operatorserver + Server toegevoegd aan operator %s. + Transparantie + voor betere privacy van metagegevens. + Verbeterde chatnavigatie + Netwerk decentralisatie + De tweede vooraf ingestelde operator in de app! + Als u bijvoorbeeld berichten ontvangt via de SimpleX Chat-server, gebruikt de app een van de Flux-servers voor privéroutering. + Flux inschakelen + Geen bericht + App-werkbalken + Vervagen + %s.]]> + %s.]]> + Voorwaarden worden automatisch geaccepteerd voor ingeschakelde operators op: %s. + Fout bij het updaten van de server + Fout bij het accepteren van voorwaarden + Fout bij toevoegen server + Geen servers voor het routeren van privéberichten. + Serveroperator gewijzigd. + Geen servers om bestanden te verzenden. + Open voorwaarden + - Open chat op het eerste ongelezen bericht.\n- Ga naar geciteerde berichten. + Vooraf ingestelde servers + Bekijk de bijgewerkte voorwaarden + SimpleX adres of eenmalige link? + Om te voorkomen dat uw link wordt vervangen, kunt u contactbeveiligingscodes vergelijken. + Om te verzenden + U kunt servers configureren via instellingen. + Of importeer archiefbestand \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml index c679651e7d..10f4f79d47 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml @@ -335,8 +335,7 @@ Wersja aplikacji Wersja aplikacji: v%s dla każdego profilu czatu, który masz w aplikacji.]]> - Oddzielne połączenie TCP (i poświadczenia SOCKS) będą używane dla każdego kontaktu i członka grupy. -\nUwaga: jeśli masz wiele połączeń, zużycie baterii i ruchu może być znacznie wyższe, a niektóre połączenia mogą się nie udać. + dla każdego kontaktu i członka grupy. \nUwaga: jeśli masz wiele połączeń, zużycie baterii i ruchu może być znacznie wyższe, a niektóre połączenia mogą się nie udać.]]> Profil czatu Połączenie Wersja rdzenia: v%s diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml index 18c20eba50..02e5547e04 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml @@ -194,8 +194,7 @@ Preservar o último rascunho da mensagem, com anexos. Solicitada a recepção do vídeo para cada perfil de conversa que você tiver na aplicação .]]> - Uma conexão TCP separada (e credencial SOCKS) será usada para cada contato e membro do grupo. -\n Por favor note: se você tiver muitas conexões, o seu consumo de bateria e consumo de tráfego pode ser substancialmente maior e algumas conexões podem falhar. + para cada contato e membro do grupo. \n Por favor note: se você tiver muitas conexões, o seu consumo de bateria e consumo de tráfego pode ser substancialmente maior e algumas conexões podem falhar.]]> Solicitada a recepção da imagem Autenticação indisponível negrito diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml index 7f8e0e2743..c02e17f568 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml @@ -549,8 +549,7 @@ Versiunea aplicației: %s pentru fiecare profil de conversație pe care le aveți în aplicație]]> Permiteți downgrade-ul - O conexiune separată TCP (și SOCKS credential) va fi folosită pentru fiecare contact și membru de grup -\nVa rugăm considerați că: dacă aveți prea multe conexiuni, consumul dumneavoastră de baterie și trafic de internet pot fi considerabil mai mari, iar unele conexiuni pot eșua. + pentru fiecare contact și membru de grup \nVa rugăm considerați că: dacă aveți prea multe conexiuni, consumul dumneavoastră de baterie și trafic de internet pot fi considerabil mai mari, iar unele conexiuni pot eșua.]]> Conexiune terminată Se conectează la desktop Rugat să primească imaginea diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml index 3d1e92f83f..59b355fd2b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -987,8 +987,7 @@ Все чаты и сообщения будут удалены - это нельзя отменить! Сборка приложения: %s Версия приложения: v%s - Отдельное TCP-соединение (и авторизация SOCKS) будет использоваться для каждого контакта и члена группы. -\nОбратите внимание: если у Вас много контактов, потребление батареи и трафика может быть значительно выше, и некоторые соединения могут не работать. + для каждого контакта и члена группы. \nОбратите внимание: если у Вас много контактов, потребление батареи и трафика может быть значительно выше, и некоторые соединения могут не работать.]]> для каждого профиля чата, который Вы имеете в приложении.]]> Версия ядра: v%s Удалить профиль чата\? diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml index 48e26132c8..8df7457e0f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml @@ -71,8 +71,7 @@ İzin ver Uygulama erişim kodu Uygulamadaki her konuşma profliniz için ayrı bir TCP bağlantısı (ve SOCKS kimliği) kullanılacaktır.]]> - Konuştuğun kişilerin ve grup üyelerinin tamamı için ayrı bir TCP bağlantısı (ve SOCKS kimliği) kullanılacaktır. -\nBilgin olsun: Çok sayıda bağlantın varsa pilin ve veri kullanımın önemli ölçüde artabilir ve bazı bağlantılar başarısız olabilir. + Konuştuğun kişilerin ve grup üyelerinin tamamı için ayrı bir TCP bağlantısı (ve SOCKS kimliği) kullanılacaktır.\nBilgin olsun: Çok sayıda bağlantın varsa pilin ve veri kullanımın önemli ölçüde artabilir ve bazı bağlantılar başarısız olabilir.]]> Kaydet ve grup üyelerini bilgilendir Uygulama açıkken çalışır Ara @@ -2115,4 +2114,15 @@ 200\'e kadar mesajı silin veya düzenleyin. Daha iyi güvenlik ✅ SimpleX protokolleri Trail of Bits tarafından incelenmiştir. + Adres mi yoksa tek seferlik bağlantı mı? + Mesaj sunucuları eklendi + Koşulları kabul edin + Kabul edilen koşullar + Medya ve dosya sunucuları eklendi + Uygulama araç çubukları + Adres ayarları + Bulanıklık + sadece bir kişiyle kullanılabilir - yüz yüze veya herhangi bir mesajlaşma programı aracılığıyla paylaşın]]> + %s.]]> + %s.]]> \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index a37d13bd51..fcae74db1b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -75,8 +75,7 @@ для кожного профілю чату, який у вас є в додатку.]]> Вигляд Версія додатку: v%s - Окреме TCP-підключення (і обліковий запис SOCKS) буде використовуватися для кожного контакту та учасника групи. -\nЗверніть увагу: якщо у вас багато підключень, споживання заряду батареї та трафіку може значно збільшитися, і деякі підключення можуть бути невдалими. + для кожного контакту та члена групи.\nЗверніть увагу: якщо у вас багато з’єднань, заряд акумулятора та споживання трафіку можуть бути значно вищими, а деякі з’єднання можуть бути невдалими.]]> Активована оптимізація батареї, вимикається фоновий сервіс і періодичні запити нових повідомлень. Ви можете знову увімкнути їх у налаштуваннях. Назад жирний @@ -104,7 +103,7 @@ поганий хеш повідомлення поганий ідентифікатор повідомлення Фон - Це можна вимкнути у налаштуваннях – сповіщення все одно будуть відображатися, коли програма працює. + Це можна вимкнути в налаштуваннях – сповіщення все одно відображатимуться під час роботи програми.]]> Служба фонового режиму завжди активна – сповіщення відображатимуться, як тільки повідомлення будуть доступні. Запит на отримання зображення Прикріпити @@ -244,7 +243,7 @@ Використовувати для нових підключень Видалити сервер Оцінити на GitHub - Як використовувати ваші сервери + Як використовувати власні сервери Збережені сервери WebRTC ICE будуть видалені. Налаштувати сервери ICE Мережа та сервери @@ -621,14 +620,14 @@ Цей рядок не є з\'єднувальним посиланням! Код безпеки Позначити, що перевірено - Ваші профілі чату + Ваші профілі Пароль бази даних та експорт Markdown у повідомленнях Надсилайте питання та ідеї - Ввести адресу сервера вручну + Ввести сервер вручну Попередньо встановлений сервер Адреса вашого сервера - Сервери для нових підключень вашого поточного профілю чату + Сервери для нових підключень до вашого поточного профілю Використовувати сервери SimpleX Chat? Сервери ICE (один на рядок) Помилка збереження серверів ICE @@ -832,7 +831,7 @@ Відео Прийняте вами з\'єднання буде скасоване! Контакт ще не підключений! - Тестові сервери + Тестування серверів Зберегти сервери Ваш сервер Тест сервера не вдався! @@ -994,7 +993,7 @@ Помилка встановлення адреси Підключити Будь ласка, запам\'ятайте або збережіть його надійно - немає можливості відновлення втраченого пароля! - Відкрити профілі чату + Змінити профілі чату Очікування на відео Очікування на файл Голосове повідомлення (%1$s) @@ -1134,7 +1133,7 @@ Файл не знайдено Підключитися через посилання Підключитися - Щоб показати ваш схований профіль, введіть повний пароль у поле пошуку на сторінці Ваші профілі чату. + Щоб показати ваш схований профіль, введіть повний пароль у поле пошуку на сторінці Ваші профілі. Підтвердити пароль %dd Захистіть свої чат-профілі паролем! @@ -1424,7 +1423,7 @@ %d подій в групі Невірне ім\'я! Перемикайте інкогніто під час підключення. - Це ваше посилання для групи %1$s! + %1$s!]]> Розблокувати Неправильний шлях до файлу - підключайтесь до служби каталогів (BETA)! @@ -1470,7 +1469,7 @@ Пристрої робочого столу Не сумісно! Зв\'язати з мобільним - Використовувати зі стаціонарного комп\'ютера + Використовувати з комп\'ютера Підключений мобільний Код сеансу Підключення завершено @@ -1487,7 +1486,7 @@ Некоректна адреса робочого столу Вставити адресу робочого столу Перевірити код з робочим столом - Сканувати QR-код з робочого столу + Сканувати QR-код з комп\'ютера Пристрої Виявлено через локальну мережу - за бажанням повідомляйте про видалених контактів. @@ -1912,7 +1911,7 @@ Будь ласка, попросіть вашого контакту увімкнути дзвінки. Зберегти і перепідключитися Видалити до 20 повідомлень за один раз. - Доступна панель інструментів чату + Доступна панель чату Користуватися застосунком однією рукою. З\'єднуйтеся з друзями швидше. Керуйте своєю мережею @@ -2014,7 +2013,7 @@ Завантажити %s (%s) Відкрити розташування файлу Пропустити цю версію - Доступна панель інструментів чату + Доступна панель чату Не можна зателефонувати контакту Підключення до контакту, будь ласка, зачекайте або перевірте пізніше! Дзвінки заборонені! @@ -2097,7 +2096,7 @@ Звук вимкнено Помилка ініціалізації WebView. Переконайтеся, що WebView встановлено, і його підтримувана архітектура — arm64. \nПомилка: %s Хвіст - Куточок + Кут Форма повідомлення Сесія додатку Нові облікові дані SOCKS будуть використовуватись щоразу, коли ви запускаєте додаток. @@ -2116,4 +2115,99 @@ Видалити або модерувати до 200 повідомлень. Переслати до 20 повідомлень одночасно. Переключити профіль чату для одноразових запрошень. + Помилка прийняття умов + Сервери збереження помилок + Для профілю чату %s: + Ніяких медіа та файлових серверів. + Немає серверів повідомлень. + Немає серверів для маршрутизації приватних повідомлень. + Недоставлені повідомлення + Немає повідомлення + Це повідомлення було видалено або ще не отримано. + Поділіться одноразовим посиланням з другом + Поділіться адресою публічно + Ви можете задати ім\'я з\'єднання, щоб запам\'ятати, з ким ви поділилися посиланням. + Безпека з\'єднання + Щоб захиститися від заміни вашого посилання, ви можете порівняти коди безпеки контактів. + Для соціальних мереж + Або поділитися приватно + Обирайте операторів + Мережеві оператори + Умови будуть прийняті для ввімкнених операторів через 30 днів. + Наприклад, якщо ви отримуєте повідомлення через сервер SimpleX Chat, програма використовуватиме один із серверів Flux для приватної маршрутизації. + Виберіть мережевих операторів для використання. + Ви можете налаштувати сервери за допомогою налаштувань. + Перегляньте пізніше + Оновлення + Прийняті умови + Умови будуть автоматично прийняті для увімкнених операторів: %s. + Оператор мережі + %s серверів + Вебсайт + Ваші сервери + Використовуйте %s + Використовуйте сервери + %s.]]> + %s.]]> + Прийняти умови + Умови перегляду + Додано сервери повідомлень + Для приватної маршрутизації + Щоб отримати + Використовуйте для файлів + Додано медіа та файлові сервери + Відкриті зміни + Відкриті умови + Сервери для нових файлів вашого поточного профілю чату + Щоб відправити + Помилка додавання сервера + Сервер оператора + Сервер додано до оператора %s. + Оператор сервера змінився. + Панелі інструментів додатків + Розмиття + Прозорість + Покращена навігація в чаті + - Відкрити чат на першому непрочитаному повідомленні.\n- Перейти до цитованих повідомлень. + Другий попередньо встановлений оператор у застосунку! + Переглянути оновлені умови + Налаштування адреси + %s.]]> + Адреса або одноразове посилання? + Умови використання + лише з одним контактом – поділіться особисто чи через будь-який месенджер.]]> + %s.]]> + %s.]]> + %s.]]> + %s.]]> + %s.]]> + %s, прийміть умови використання.]]> + Текст поточних умов не вдалося завантажити, ви можете переглянути умови за цим посиланням: + Помилки в конфігурації серверів. + Умови приймаються з: %s. + Увімкнути flux + Умови приймаються до: %s. + Продовжити + Створити одноразове посилання + Помилка оновлення сервера + для кращої конфіденційності метаданих. + Децентралізація мережі + Оператор + Немає серверів для отримання повідомлень. + SimpleX адреса або одноразове посилання? + Новий сервер + Немає серверів для отримання файлів. + Умови перегляду + Немає серверів для надсилання файлів. + Попередньо встановлені сервери + Протокол сервера змінено. + Поділіться адресою SimpleX у соціальних мережах. + SimpleX-адреси та одноразові посилання можна безпечно ділитися через будь-який месенджер. + З\'єднання досягло ліміту недоставлених повідомлень, ваш контакт може бути офлайн. + Натисніть Створити адресу SimpleX у меню, щоб створити її пізніше. + Якщо увімкнено більше одного оператора, програма використовуватиме сервери різних операторів для кожної розмови. + Використовуйте для повідомлень + Ви можете налаштувати операторів у налаштуваннях Мережі та серверів. + Або імпортуйте архівний файл + Віддалені мобільні \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml index cefaa982a5..f02f7abc15 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml @@ -115,8 +115,7 @@ Tắt âm Yêu cầu để nhận hình ảnh cho mỗi hồ sơ trò chuyện bạn có trong ứng dụng.]]> - Một kết nối TCP riêng biệt (và thông tin xác thực SOCKS) sẽ được sử dụng cho từng liên hệ và thành viên nhóm. -\nXin lưu ý: nếu bạn có nhiều kết nối, mức tiêu thụ pin và lưu lượng truy cập của bạn có thể cao hơn đáng kể và một số kết nối có thể không thành công. + cho từng liên hệ và thành viên nhóm. \nXin lưu ý: nếu bạn có nhiều kết nối, mức tiêu thụ pin và lưu lượng truy cập của bạn có thể cao hơn đáng kể và một số kết nối có thể không thành công.]]> cuộc gọi thoại Các cuộc gọi thoại và video Các cuộc gọi thoại và video @@ -1351,4 +1350,53 @@ Cấm gửi tin nhắn tự xóa. Cấm gửi đường dẫn SimpleX Được proxy + Địa chỉ hay đường dẫn dùng một lần? + với chỉ một liên hệ - chia sẻ trực tiếp hoặc thông qua bất kỳ ứng dụng tin nhắn nào.]]> + Cài đặt máy chủ .onion thành Không nếu proxy SOCKS không hỗ trợ chúng.]]> + %s.]]> + %s.]]> + %s.]]> + Đã thêm các máy chủ truyền tin nhắn + Thanh công cụ ứng dụng + Làm mờ + Chấp nhận điều kiện + Đã thêm các máy chủ truyền tệp & phương tiện + Đã chấp nhận điều kiện + Cài đặt địa chỉ + %s.]]> + %s.]]> + Kho lưu trữ GitHub của chúng tôi.]]> + %s.]]> + %s.]]> + %s.]]> + Hướng dẫn người dùng.]]> + %1$s rồi.]]> + Mức sử dụng pin ứng dụng / Không hạn chế trong phần cài đặt ứng dụng.]]> + %s]]> + cho phép SimpleX chạy trong nền trong hộp thoại tiếp theo. Nếu không, thông báo sẽ bị vô hiệu hóa.]]> + %s, vui lòng chấp nhận điều kiện sử dụng.]]> + Dịch vụ nền SimpleX – nó tiêu tốn một vài phần trăm pin mỗi ngày.]]> + Mức sử dụng pin ứng dụng / Không hạn chế trong phần cài đặt ứng dụng.]]> + %1$s rồi.]]> + %1$s!]]> + %1$s rồi.]]> + Mở trong ứng dụng di động.]]> + Chọn nhà cung cấp + kết nối với các nhà phát triển SimpleX Chat để hỏi bất kỳ câu hỏi nào và nhận thông tin cập nhật.]]> + không được sử dụng cùng một cơ sở dữ liệu trên hai thiết .]]> + Lỗi chấp nhận điều kiện + Lỗi lưu máy chủ + Lỗi trong cấu hình máy chủ. + Bảo mật kết nối + Tạo đường dẫn dùng một lần + Các điều kiện sẽ được chấp nhận với các nhà cung cấp được cho phép sau 30 ngày. + Tiếp tục + Không thể tải văn bản về các điều kiện hiện tại, bạn có thể xem xét các điều kiện thông qua đường dẫn này: + Các điều kiện sử dụng + Cho phép flux + Các điều kiện sẽ được chấp nhận vào: %s. + Lỗi thêm máy chủ + Lỗi cập nhật máy chủ + Các điều kiện đã được chấp nhận vào: %s. + Các điều kiện sẽ được tự động chấp nhận với các nhà cung cấp được cho phép vào: %s. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index 4c1c60e247..4c0b66d216 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -113,8 +113,7 @@ 开启音频 已要求接收图片 ,用于您在应用程序中的每个聊天资料 。]]> - 每个联系人和群组成员 将使用单独的 TCP 连接(和 SOCKS 凭证)。 -\n请注意:如果您有很多连接,您的电池和流量消耗可能会大大增加,并且某些连接可能会失败。 + 每个联系人和群成员。\n请注意:如果您有很多连接,您的电池和流量消耗可能会大大增加,并且某些连接可能会失败。]]> 返回 最长续航 。您只会在应用程序运行时收到通知(无后台服务)。]]> 较长续航 。应用每 10 分钟检查一次消息。您可能会错过来电或者紧急信息。]]> @@ -1172,7 +1171,7 @@ 收到的信息 只有您的联系人可以添加消息回应。 打开数据库中…… - 打开聊天资料 + 更改聊天资料 您的联系人可以扫描二维码或使用应用程序中的链接来建立连接。 您可以将您的地址作为链接或二维码共享——任何人都可以连接到您。 如果您不能亲自见面,可以在视频通话中展示二维码,或分享链接。 @@ -2117,4 +2116,99 @@ 对一次性邀请切换聊天配置文件。 更佳的通话 允许自行删除或管理员移除最多200条消息。 + 保存服务器出错 + 服务器配置有错误。 + 用于聊天资料 %s: + 无消息服务器 + 无私密消息路由服务器。 + 无消息接收服务器。 + 无文件发送服务器。 + 未送达的消息 + 无消息 + 连接安全性 + 和一位好友分享一次性链接 + 公开分享地址 + 在社媒上分享 SimpleX 地址。 + 你可以设置连接名称,用来记住和谁分享了这个链接。 + 可以通过任何消息应用安全分享 SimpleX 地址和一次性链接。 + 创建一次性链接 + 用于社交媒体 + 或者私下分享 + 选择运营者 + 网络运营者 + 30 天后将接受已启用的运营者的条款。 + 继续 + 稍后审阅 + 选择要使用的网络运营者。 + 更新 + 你可以通过设置配置服务器。 + %s.]]> + 将于下列日期自动接受已启用的运营者的条款:%s。 + 预设服务器 + 你的服务器 + 接受条款的将来日期为:%s。 + 网络运营者 + 运营者 + %s 台服务器 + 网站 + 无法加载当前条款文本,你可以通过此链接审阅条款: + 使用 %s + 使用服务器 + %s.]]> + %s.]]> + 查看条款 + 使用条款 + 用于私密路由 + 消息接收 + 发送 + 用于文件 + 用于消息 + 打开更改 + 打开条款 + 运营者服务器 + 已添加服务器到运营者 %s + 服务器运营者已更改。 + 服务器协议已更改。 + 透明度 + 网络去中心化 + 应用中的第二个预设运营者! + 改进了聊天导航 + 查看更新后的条款 + 比如,如果你通过 SimpleX 服务器收到消息,应用会使用 Flux 服务器中的一台进行私密路由。 + 启用了多于一个网络运营者时,应用会为每个对话使用不同运营者的服务器。 + 接受条款 + 模糊 + 地址或一次性链接? + 已添加消息服务器 + 已添加媒体和文件服务器 + 地址设置 + 已接受条款 + 应用工具栏 + 仅用于一名联系人 - 面对面或通过任何消息应用分享.]]> + %s.]]> + %s.]]> + %s.]]> + %s.]]> + %s的服务器,请接受使用条款。]]> + %s.]]> + 开启 flux + 接受条款出错 + 为了更好的元数据隐私。 + 添加服务器出错 + 无媒体和文件服务器。 + 更新服务器出错 + 新服务器 + 无文件接收服务器。 + - 在第一条未读消息上打开聊天.\n- 跳转到引用的消息. + 审阅条款 + SimpleX 地址或一次性链接? + 要稍后创建 SimpleX 地址,请在菜单中轻按“创建 SimpleX 地址” + 当前聊天资料的新文件服务器 + 此消息被删除或尚未收到。 + 连接达到了未送达消息上限,你的联系人可能处于离线状态。 + 为了防止链接被替换,你可以比较联系人安全代码。 + 你可以在“网络和服务器”设置中配置运营者。 + 接受运营者条款的日期:%s + 远程移动设备 + 或者导入压缩文件 \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml index 65c16db612..22d226829e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml @@ -143,8 +143,7 @@ 取消檔案預覽 無法接收檔案 重複的顯示名稱! - 一個單獨的 TCP 連接(和 SOCKS 憑證)將用於每個聯絡人和群組內的成員。 -\n請注意:如果你有很多連接,你的電話電量和數據流量的消耗率會大大增加,一些連接有機會會連接失敗。 + 每個聯絡人和群組內的成員。 \n請注意:如果你有很多連接,你的電話電量和數據流量的消耗率會大大增加,一些連接有機會會連接失敗。]]> 每個聊天室的設定。]]> 返回 省電模式運行中,關閉了背景通知服務和定期更新接收訊息。你可以在通知設定內重新啟用。 From a1e25620f7091433ce3888aa660a95eb6ade31ae Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 3 Dec 2024 15:23:23 +0000 Subject: [PATCH 163/567] website: translations (#5306) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (German) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/de/ * Translated using Weblate (Dutch) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/nl/ * Translated using Weblate (Arabic) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ar/ * Translated using Weblate (Italian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/it/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/uk/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ * Translated using Weblate (German) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/de/ * Translated using Weblate (Dutch) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/nl/ * Translated using Weblate (Arabic) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ar/ * Translated using Weblate (Italian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/it/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/uk/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ --------- Co-authored-by: mlanp Co-authored-by: M1K4 Co-authored-by: jonnysemon Co-authored-by: Random Co-authored-by: Max Co-authored-by: 大王叫我来巡山 Co-authored-by: summoner001 --- website/langs/ar.json | 7 ++++--- website/langs/de.json | 3 ++- website/langs/hu.json | 7 ++++--- website/langs/it.json | 7 ++++--- website/langs/nl.json | 3 ++- website/langs/uk.json | 3 ++- website/langs/zh_Hans.json | 3 ++- 7 files changed, 20 insertions(+), 13 deletions(-) diff --git a/website/langs/ar.json b/website/langs/ar.json index 927dcb0c49..9274e71850 100644 --- a/website/langs/ar.json +++ b/website/langs/ar.json @@ -244,15 +244,16 @@ "f-droid-page-simplex-chat-repo-section-text": "لإضافته إلى عميل F-Droid، امسح رمز QR أو استخدم عنوان URL هذا:", "f-droid-page-f-droid-org-repo-section-text": "مستودعات SimpleX Chat و F-Droid.org مبنية على مفاتيح مختلفة. للتبديل، يُرجى تصدير قاعدة بيانات الدردشة وإعادة تثبيت التطبيق.", "comparison-section-list-point-4a": "مُرحلات SimpleX لا يمكنها أن تتنازل عن تعمية بين الطرفين. تحقق من رمز الأمان للتخفيف من الهجوم على القناة خارج النطاق", - "hero-overlay-3-title": "التقييم الأمني", + "hero-overlay-3-title": "التقييمات الأمنية", "hero-overlay-card-3-p-2": "قامت Trail of Bits بمراجعة مكونات التشفير والشبكات الخاصة بمنصة SimpleX في نوفمبر 2022. اقرأ المزيد في الإعلان.", "jobs": "انضم للفريق", - "hero-overlay-3-textlink": "التقييم الأمني", + "hero-overlay-3-textlink": "التقييمات الأمنية", "hero-overlay-card-3-p-1": "Trail of Bits هي شركة رائدة في مجال الاستشارات الأمنية والتكنولوجية، ومن بين عملائها شركات التكنولوجيا الكبرى والوكالات الحكومية ومشاريع blockchain الكبرى.", "docs-dropdown-9": "التنزيلات", "please-enable-javascript": "الرجاء تفعيل جافا سكريبت (JavaScript) لرؤية رمز QR.", "please-use-link-in-mobile-app": "يُرجى استخدام الرابط في تطبيق الجوال", "docs-dropdown-10": "الشفافية", "docs-dropdown-11": "الأسئلة الأكثر شيوعًا", - "docs-dropdown-12": "الأمان" + "docs-dropdown-12": "الأمان", + "hero-overlay-card-3-p-3": "قامت Trail of Bits بمراجعة التصميم التعموي لبروتوكولات شبكة SimpleX في يوليو 2024. اقرأ المزيد." } diff --git a/website/langs/de.json b/website/langs/de.json index 1a5c42d980..c5d1fceefa 100644 --- a/website/langs/de.json +++ b/website/langs/de.json @@ -254,5 +254,6 @@ "please-use-link-in-mobile-app": "Bitte nutzen Sie den Link in der Mobiltelefon-App", "docs-dropdown-10": "Transparent", "docs-dropdown-11": "FAQ", - "docs-dropdown-12": "Sicherheit" + "docs-dropdown-12": "Sicherheit", + "hero-overlay-card-3-p-3": "Trail of Bits hat das kryptografische Design des Netzwerk-Protokolls von SimpleX im Juli 2024 überprüft. Hier finden Sie weitere Informationen dazu." } diff --git a/website/langs/hu.json b/website/langs/hu.json index f7cf1c558b..b0b76714bc 100644 --- a/website/langs/hu.json +++ b/website/langs/hu.json @@ -29,12 +29,12 @@ "hero-p-1": "Más alkalmazások felhasználói azonosítókkal rendelkeznek: Signal, Matrix, Session, Briar, Jami, Cwtch, stb.
A SimpleX nem, még véletlenszerű számokkal sem.
Ez radikálisan javítja az adatvédelmet.", "hero-overlay-1-textlink": "Miért ártanak a felhasználói azonosítók az adatvédelemnek?", "hero-overlay-2-textlink": "Hogyan működik a SimpleX?", - "hero-overlay-3-textlink": "A biztonság értékelése", + "hero-overlay-3-textlink": "Biztonsági felmérések", "hero-2-header": "Privát kapcsolat létrehozása", "hero-2-header-desc": "A videó bemutatja, hogyan kapcsolódhat az ismerőséhez egy egyszer használható QR-kód segítségével, személyesen vagy videokapcsolaton keresztül. Ugyanakkor egy meghívó-hivatkozás megosztásával is kapcsolódhat.", "hero-overlay-1-title": "Hogyan működik a SimpleX?", "hero-overlay-2-title": "Miért ártanak a felhasználói azonosítók az adatvédelemnek?", - "hero-overlay-3-title": "A biztonság értékelése", + "hero-overlay-3-title": "Biztonsági felmérések", "feature-1-title": "E2E-titkosított üzenetek markdown formázással és szerkesztéssel", "feature-2-title": "E2E-titkosított
képek, videók és fájlok", "feature-3-title": "E2E-titkosított decentralizált csoportok — csak a felhasználók tudják, hogy ezek léteznek", @@ -254,5 +254,6 @@ "glossary": "Fogalomtár", "simplex-chat-via-f-droid": "SimpleX Chat az F-Droidon keresztül", "simplex-chat-repo": "SimpleX Chat tároló", - "stable-and-beta-versions-built-by-developers": "A fejlesztők által készített stabil és béta verziók" + "stable-and-beta-versions-built-by-developers": "A fejlesztők által készített stabil és béta verziók", + "hero-overlay-card-3-p-3": "A Trail of Bits 2024 júliusában felülvizsgálta a SimpleX hálózati protokollok kriptográfiai felépítését. Tudjon meg többet." } diff --git a/website/langs/it.json b/website/langs/it.json index 431354c068..bdc4c38d45 100644 --- a/website/langs/it.json +++ b/website/langs/it.json @@ -244,15 +244,16 @@ "stable-and-beta-versions-built-by-developers": "Versioni stabili e beta compilate dagli sviluppatori", "f-droid-page-simplex-chat-repo-section-text": "Per aggiungerlo al tuo client F-Droid scansiona il codice QR o usa questo URL:", "comparison-section-list-point-4a": "I relay di SimpleX non possono compromettere la crittografia e2e. Verifica il codice di sicurezza per mitigare gli attacchi sul canale fuori banda", - "hero-overlay-3-title": "Valutazione della sicurezza", + "hero-overlay-3-title": "Valutazioni della sicurezza", "hero-overlay-card-3-p-2": "Trail of Bits ha revisionato i componenti di crittografia e di rete della piattaforma SimpleX nel novembre 2022. Maggiori informazioni.", "jobs": "Unisciti al team", - "hero-overlay-3-textlink": "Valutazione della sicurezza", + "hero-overlay-3-textlink": "Valutazioni della sicurezza", "hero-overlay-card-3-p-1": "Trail of Bits è leader nella consulenza di sicurezza e tecnologia, i cui clienti includono grandi aziende, agenzie governative e importanti progetti di blockchain.", "docs-dropdown-9": "Download", "please-enable-javascript": "Attiva JavaScript per vedere il codice QR.", "please-use-link-in-mobile-app": "Usa il link nell'app mobile", "docs-dropdown-10": "Trasparenza", "docs-dropdown-12": "Sicurezza", - "docs-dropdown-11": "Domande frequenti" + "docs-dropdown-11": "Domande frequenti", + "hero-overlay-card-3-p-3": "Trail of Bits ha analizzato la progettazione crittografica dei protocolli della rete SimpleX nel luglio 2024. Leggi di più." } diff --git a/website/langs/nl.json b/website/langs/nl.json index 18edf45369..c725cc3d3e 100644 --- a/website/langs/nl.json +++ b/website/langs/nl.json @@ -254,5 +254,6 @@ "please-use-link-in-mobile-app": "Gebruik de link in de mobiele app", "docs-dropdown-10": "Transparantie", "docs-dropdown-11": "FAQ", - "docs-dropdown-12": "Beveiliging" + "docs-dropdown-12": "Beveiliging", + "hero-overlay-card-3-p-3": "Trail of Bits heeft in juli 2024 het cryptografische ontwerp van SimpleX-netwerkprotocollen beoordeeld. Lees meer." } diff --git a/website/langs/uk.json b/website/langs/uk.json index 5fa2f02b99..d055aa68a2 100644 --- a/website/langs/uk.json +++ b/website/langs/uk.json @@ -254,5 +254,6 @@ "please-use-link-in-mobile-app": "Будь ласка, скористайтеся посиланням у мобільному додатку", "docs-dropdown-11": "ПОШИРЕНІ ЗАПИТАННЯ", "docs-dropdown-10": "Прозорість", - "docs-dropdown-12": "Безпека" + "docs-dropdown-12": "Безпека", + "hero-overlay-card-3-p-3": "Trail of Bits переглянув криптографічний дизайн мережевих протоколів SimpleX в липні 2024 року. Детальніше." } diff --git a/website/langs/zh_Hans.json b/website/langs/zh_Hans.json index 87a8cc3c78..e482056565 100644 --- a/website/langs/zh_Hans.json +++ b/website/langs/zh_Hans.json @@ -254,5 +254,6 @@ "please-enable-javascript": "请启用 JavaScript 以查看二维码。", "docs-dropdown-10": "透明度", "docs-dropdown-11": "常问问题", - "docs-dropdown-12": "安全性" + "docs-dropdown-12": "安全性", + "hero-overlay-card-3-p-3": "Trail of Bits 于 2024 年 7 月审核了 SimpleX 网络协议的加密设计。了解更多信息。" } From 6593de89c200051cff66a1412d2ec10e984ac7c0 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 3 Dec 2024 19:25:15 +0400 Subject: [PATCH 164/567] ios: make more texts different for groups and business chats (#5307) --- .../Chat/Group/AddGroupMembersView.swift | 5 +- .../Views/Chat/Group/GroupChatInfoView.swift | 55 +++++++++++++------ .../Chat/Group/GroupMemberInfoView.swift | 31 +++++++++-- .../Views/ChatList/ChatListNavLink.swift | 21 ++++--- 4 files changed, 79 insertions(+), 33 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift index 691bda39a6..925f4120bc 100644 --- a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift +++ b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift @@ -140,12 +140,13 @@ struct AddGroupMembersViewCommon: View { return dummy }() - private func inviteMembersButton() -> some View { + @ViewBuilder private func inviteMembersButton() -> some View { + let label: LocalizedStringKey = groupInfo.businessChat == nil ? "Invite to group" : "Invite to chat" Button { inviteMembers() } label: { HStack { - Text("Invite to group") + Text(label) Image(systemName: "checkmark") } } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index 89f0fcbedf..27aa0edb5b 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -101,7 +101,12 @@ struct GroupChatInfoView: View { } header: { Text("") } footer: { - Text("Only group owners can change group preferences.") + let label: LocalizedStringKey = ( + groupInfo.businessChat == nil + ? "Only group owners can change group preferences." + : "Only chat owners can change preferences." + ) + Text(label) .foregroundColor(theme.colors.secondary) } @@ -494,11 +499,12 @@ struct GroupChatInfoView: View { } } - private func deleteGroupButton() -> some View { + @ViewBuilder private func deleteGroupButton() -> some View { + let label: LocalizedStringKey = groupInfo.businessChat == nil ? "Delete group" : "Delete chat" Button(role: .destructive) { alert = .deleteGroupAlert } label: { - Label("Delete group", systemImage: "trash") + Label(label, systemImage: "trash") .foregroundColor(Color.red) } } @@ -512,20 +518,22 @@ struct GroupChatInfoView: View { } } - private func leaveGroupButton() -> some View { + @ViewBuilder private func leaveGroupButton() -> some View { + let label: LocalizedStringKey = groupInfo.businessChat == nil ? "Leave group" : "Leave chat" Button(role: .destructive) { alert = .leaveGroupAlert } label: { - Label("Leave group", systemImage: "rectangle.portrait.and.arrow.right") + Label(label, systemImage: "rectangle.portrait.and.arrow.right") .foregroundColor(Color.red) } } // TODO reuse this and clearChatAlert with ChatInfoView private func deleteGroupAlert() -> Alert { + let label: LocalizedStringKey = groupInfo.businessChat == nil ? "Delete group?" : "Delete chat?" return Alert( - title: Text("Delete group?"), - message: deleteGroupAlertMessage(), + title: Text(label), + message: deleteGroupAlertMessage(groupInfo), primaryButton: .destructive(Text("Delete")) { Task { do { @@ -544,10 +552,6 @@ struct GroupChatInfoView: View { ) } - private func deleteGroupAlertMessage() -> Text { - groupInfo.membership.memberCurrent ? Text("Group will be deleted for all members - this cannot be undone!") : Text("Group will be deleted for you - this cannot be undone!") - } - private func clearChatAlert() -> Alert { Alert( title: Text("Clear conversation?"), @@ -563,9 +567,15 @@ struct GroupChatInfoView: View { } private func leaveGroupAlert() -> Alert { - Alert( - title: Text("Leave group?"), - message: Text("You will stop receiving messages from this group. Chat history will be preserved."), + let titleLabel: LocalizedStringKey = groupInfo.businessChat == nil ? "Leave group?" : "Leave chat?" + let messageLabel: LocalizedStringKey = ( + groupInfo.businessChat == nil + ? "You will stop receiving messages from this group. Chat history will be preserved." + : "You will stop receiving messages from this chat. Chat history will be preserved." + ) + return Alert( + title: Text(titleLabel), + message: Text(messageLabel), primaryButton: .destructive(Text("Leave")) { Task { await leaveGroup(chat.chatInfo.apiId) @@ -609,9 +619,14 @@ struct GroupChatInfoView: View { } private func removeMemberAlert(_ mem: GroupMember) -> Alert { - Alert( + let messageLabel: LocalizedStringKey = ( + groupInfo.businessChat == nil + ? "Member will be removed from group - this cannot be undone!" + : "Member will be removed from chat - this cannot be undone!" + ) + return Alert( title: Text("Remove member?"), - message: Text("Member will be removed from group - this cannot be undone!"), + message: Text(messageLabel), primaryButton: .destructive(Text("Remove")) { Task { do { @@ -631,6 +646,14 @@ struct GroupChatInfoView: View { } } +func deleteGroupAlertMessage(_ groupInfo: GroupInfo) -> Text { + groupInfo.businessChat == nil ? ( + groupInfo.membership.memberCurrent ? Text("Group will be deleted for all members - this cannot be undone!") : Text("Group will be deleted for you - this cannot be undone!") + ) : ( + groupInfo.membership.memberCurrent ? Text("Chat will be deleted for all members - this cannot be undone!") : Text("Chat will be deleted for you - this cannot be undone!") + ) +} + func groupPreferencesButton(_ groupInfo: Binding, _ creatingGroup: Bool = false) -> some View { let label: LocalizedStringKey = groupInfo.wrappedValue.businessChat == nil ? "Group preferences" : "Chat preferences" return NavigationLink { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index ed40c0592b..9f3de7ac59 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -135,7 +135,8 @@ struct GroupMemberInfoView: View { } Section(header: Text("Member").foregroundColor(theme.colors.secondary)) { - infoRow("Group", groupInfo.displayName) + let label: LocalizedStringKey = groupInfo.businessChat == nil ? "Group" : "Chat" + infoRow(label, groupInfo.displayName) if let roles = member.canChangeRoleTo(groupInfo: groupInfo) { Picker("Change role", selection: $newRole) { @@ -305,10 +306,15 @@ struct GroupMemberInfoView: View { } func showDirectMessagesProhibitedAlert(_ title: LocalizedStringKey) { + let messageLabel: LocalizedStringKey = ( + groupInfo.businessChat == nil + ? "Direct messages between members are prohibited in this group." + : "Direct messages between members are prohibited in this chat." + ) alert = .someAlert(alert: SomeAlert( alert: mkAlert( title: title, - message: "Direct messages between members are prohibited in this group." + message: messageLabel ), id: "can't message member, direct messages prohibited" )) @@ -537,9 +543,14 @@ struct GroupMemberInfoView: View { } private func removeMemberAlert(_ mem: GroupMember) -> Alert { - Alert( + let label: LocalizedStringKey = ( + groupInfo.businessChat == nil + ? "Member will be removed from group - this cannot be undone!" + : "Member will be removed from chat - this cannot be undone!" + ) + return Alert( title: Text("Remove member?"), - message: Text("Member will be removed from group - this cannot be undone!"), + message: Text(label), primaryButton: .destructive(Text("Remove")) { Task { do { @@ -562,7 +573,15 @@ struct GroupMemberInfoView: View { private func changeMemberRoleAlert(_ mem: GroupMember) -> Alert { Alert( title: Text("Change member role?"), - message: mem.memberCurrent ? Text("Member role will be changed to \"\(newRole.text)\". All group members will be notified.") : Text("Member role will be changed to \"\(newRole.text)\". The member will receive a new invitation."), + message: ( + mem.memberCurrent + ? ( + groupInfo.businessChat == nil + ? Text("Member role will be changed to \"\(newRole.text)\". All group members will be notified.") + : Text("Member role will be changed to \"\(newRole.text)\". All chat members will be notified.") + ) + : Text("Member role will be changed to \"\(newRole.text)\". The member will receive a new invitation.") + ), primaryButton: .default(Text("Change")) { Task { do { @@ -570,7 +589,7 @@ struct GroupMemberInfoView: View { await MainActor.run { _ = chatModel.upsertGroupMember(groupInfo, updatedMember) } - + } catch let error { newRole = mem.memberRole logger.error("apiMemberRole error: \(responseError(error))") diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index d2a93b9bd1..6c5dad1f74 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -404,8 +404,9 @@ struct ChatListNavLink: View { } private func deleteGroupAlert(_ groupInfo: GroupInfo) -> Alert { - Alert( - title: Text("Delete group?"), + let label: LocalizedStringKey = groupInfo.businessChat == nil ? "Delete group?" : "Delete chat?" + return Alert( + title: Text(label), message: deleteGroupAlertMessage(groupInfo), primaryButton: .destructive(Text("Delete")) { Task { await deleteChat(chat) } @@ -414,10 +415,6 @@ struct ChatListNavLink: View { ) } - private func deleteGroupAlertMessage(_ groupInfo: GroupInfo) -> Text { - groupInfo.membership.memberCurrent ? Text("Group will be deleted for all members - this cannot be undone!") : Text("Group will be deleted for you - this cannot be undone!") - } - private func clearChatAlert() -> Alert { Alert( title: Text("Clear conversation?"), @@ -441,9 +438,15 @@ struct ChatListNavLink: View { } private func leaveGroupAlert(_ groupInfo: GroupInfo) -> Alert { - Alert( - title: Text("Leave group?"), - message: Text("You will stop receiving messages from this group. Chat history will be preserved."), + let titleLabel: LocalizedStringKey = groupInfo.businessChat == nil ? "Leave group?" : "Leave chat?" + let messageLabel: LocalizedStringKey = ( + groupInfo.businessChat == nil + ? "You will stop receiving messages from this group. Chat history will be preserved." + : "You will stop receiving messages from this chat. Chat history will be preserved." + ) + return Alert( + title: Text(titleLabel), + message: Text(messageLabel), primaryButton: .destructive(Text("Leave")) { Task { await leaveGroup(groupInfo.groupId) } }, From 247d12fa4015ee07210352a766eb74f62e9b8e97 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 3 Dec 2024 21:44:06 +0400 Subject: [PATCH 165/567] android, desktop: make more texts different for groups and business chats; ios: preferences texts (#5308) --- .../Chat/Group/GroupMemberInfoView.swift | 2 +- .../ar.xcloc/Localized Contents/ar.xliff | 28 +++---- .../bg.xcloc/Localized Contents/bg.xliff | 56 ++++++------- .../bn.xcloc/Localized Contents/bn.xliff | 36 ++++---- .../cs.xcloc/Localized Contents/cs.xliff | 56 ++++++------- .../de.xcloc/Localized Contents/de.xliff | 56 ++++++------- .../el.xcloc/Localized Contents/el.xliff | 28 +++---- .../en.xcloc/Localized Contents/en.xliff | 84 +++++++++---------- .../es.xcloc/Localized Contents/es.xliff | 56 ++++++------- .../fi.xcloc/Localized Contents/fi.xliff | 56 ++++++------- .../fr.xcloc/Localized Contents/fr.xliff | 56 ++++++------- .../he.xcloc/Localized Contents/he.xliff | 44 +++++----- .../hr.xcloc/Localized Contents/hr.xliff | 28 +++---- .../hu.xcloc/Localized Contents/hu.xliff | 56 ++++++------- .../it.xcloc/Localized Contents/it.xliff | 56 ++++++------- .../ja.xcloc/Localized Contents/ja.xliff | 56 ++++++------- .../ko.xcloc/Localized Contents/ko.xliff | 28 +++---- .../lt.xcloc/Localized Contents/lt.xliff | 28 +++---- .../nl.xcloc/Localized Contents/nl.xliff | 56 ++++++------- .../pl.xcloc/Localized Contents/pl.xliff | 56 ++++++------- .../Localized Contents/pt-BR.xliff | 28 +++---- .../pt.xcloc/Localized Contents/pt.xliff | 28 +++---- .../ru.xcloc/Localized Contents/ru.xliff | 56 ++++++------- .../th.xcloc/Localized Contents/th.xliff | 56 ++++++------- .../tr.xcloc/Localized Contents/tr.xliff | 56 ++++++------- .../uk.xcloc/Localized Contents/uk.xliff | 56 ++++++------- .../Localized Contents/zh-Hans.xliff | 56 ++++++------- .../Localized Contents/zh-Hant.xliff | 36 ++++---- apps/ios/SimpleXChat/ChatTypes.swift | 28 +++---- apps/ios/bg.lproj/Localizable.strings | 28 +++---- apps/ios/cs.lproj/Localizable.strings | 24 +++--- apps/ios/de.lproj/Localizable.strings | 28 +++---- apps/ios/es.lproj/Localizable.strings | 28 +++---- apps/ios/fi.lproj/Localizable.strings | 24 +++--- apps/ios/fr.lproj/Localizable.strings | 28 +++---- apps/ios/hu.lproj/Localizable.strings | 28 +++---- apps/ios/it.lproj/Localizable.strings | 28 +++---- apps/ios/ja.lproj/Localizable.strings | 24 +++--- apps/ios/nl.lproj/Localizable.strings | 28 +++---- apps/ios/pl.lproj/Localizable.strings | 28 +++---- apps/ios/ru.lproj/Localizable.strings | 28 +++---- apps/ios/th.lproj/Localizable.strings | 24 +++--- apps/ios/tr.lproj/Localizable.strings | 28 +++---- apps/ios/uk.lproj/Localizable.strings | 28 +++---- apps/ios/zh-Hans.lproj/Localizable.strings | 28 +++---- .../chat/simplex/common/model/SimpleXAPI.kt | 2 +- .../views/chat/group/AddGroupMembersView.kt | 11 ++- .../views/chat/group/GroupChatInfoView.kt | 48 +++++++---- .../views/chat/group/GroupMemberInfoView.kt | 33 +++++--- .../AdvancedNetworkSettings.kt | 4 +- .../commonMain/resources/MR/ar/strings.xml | 2 +- .../commonMain/resources/MR/base/strings.xml | 43 ++++++---- .../commonMain/resources/MR/bg/strings.xml | 2 +- .../commonMain/resources/MR/cs/strings.xml | 2 +- .../commonMain/resources/MR/de/strings.xml | 2 +- .../commonMain/resources/MR/es/strings.xml | 2 +- .../commonMain/resources/MR/fa/strings.xml | 2 +- .../commonMain/resources/MR/fi/strings.xml | 2 +- .../commonMain/resources/MR/fr/strings.xml | 2 +- .../commonMain/resources/MR/hu/strings.xml | 2 +- .../commonMain/resources/MR/in/strings.xml | 2 +- .../commonMain/resources/MR/it/strings.xml | 8 +- .../commonMain/resources/MR/iw/strings.xml | 2 +- .../commonMain/resources/MR/ja/strings.xml | 2 +- .../commonMain/resources/MR/ko/strings.xml | 2 +- .../commonMain/resources/MR/lt/strings.xml | 2 +- .../commonMain/resources/MR/nl/strings.xml | 2 +- .../commonMain/resources/MR/pl/strings.xml | 2 +- .../resources/MR/pt-rBR/strings.xml | 2 +- .../commonMain/resources/MR/pt/strings.xml | 2 +- .../commonMain/resources/MR/ru/strings.xml | 2 +- .../commonMain/resources/MR/th/strings.xml | 2 +- .../commonMain/resources/MR/tr/strings.xml | 2 +- .../commonMain/resources/MR/uk/strings.xml | 2 +- .../commonMain/resources/MR/vi/strings.xml | 2 +- .../resources/MR/zh-rCN/strings.xml | 2 +- .../resources/MR/zh-rTW/strings.xml | 2 +- 77 files changed, 1000 insertions(+), 953 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index 9f3de7ac59..90d6829d93 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -308,7 +308,7 @@ struct GroupMemberInfoView: View { func showDirectMessagesProhibitedAlert(_ title: LocalizedStringKey) { let messageLabel: LocalizedStringKey = ( groupInfo.businessChat == nil - ? "Direct messages between members are prohibited in this group." + ? "Direct messages between members are prohibited." : "Direct messages between members are prohibited in this chat." ) alert = .someAlert(alert: SomeAlert( diff --git a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff index 53707d108f..ef91bb30fd 100644 --- a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff +++ b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff @@ -1043,8 +1043,8 @@ Direct messages chat feature
- - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. No comment provided by engineer. @@ -1059,8 +1059,8 @@ Disappearing messages are prohibited in this chat. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. No comment provided by engineer. @@ -1423,16 +1423,16 @@ Group members can irreversibly delete sent messages. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. No comment provided by engineer. @@ -1620,8 +1620,8 @@ Irreversible message deletion is prohibited in this chat. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. No comment provided by engineer. @@ -2855,8 +2855,8 @@ To connect, please ask your contact to create another connection link and check Voice messages are prohibited in this chat. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index f1e9ee0f39..e483406fe5 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -2384,8 +2384,8 @@ This is your own one-time link! Лични съобщения chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. Личните съобщения между членовете са забранени в тази група. No comment provided by engineer. @@ -2423,8 +2423,8 @@ This is your own one-time link! Изчезващите съобщения са забранени в този чат. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. Изчезващите съобщения са забранени в тази група. No comment provided by engineer. @@ -3246,8 +3246,8 @@ This is your own one-time link! Файлове и медия chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. Файловете и медията са забранени в тази група. No comment provided by engineer. @@ -3502,38 +3502,38 @@ Error: %2$@ Групови линкове No comment provided by engineer. - - Group members can add message reactions. + + Members can add message reactions. Членовете на групата могат да добавят реакции към съобщенията. No comment provided by engineer. - - Group members can irreversibly delete sent messages. (24 hours) + + Members can irreversibly delete sent messages. (24 hours) Членовете на групата могат необратимо да изтриват изпратените съобщения. (24 часа) No comment provided by engineer. - - Group members can send SimpleX links. + + Members can send SimpleX links. Членовете на групата могат да изпращат SimpleX линкове. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. Членовете на групата могат да изпращат лични съобщения. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. Членовете на групата могат да изпращат изчезващи съобщения. No comment provided by engineer. - - Group members can send files and media. + + Members can send files and media. Членовете на групата могат да изпращат файлове и медия. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. Членовете на групата могат да изпращат гласови съобщения. No comment provided by engineer. @@ -3944,8 +3944,8 @@ More improvements are coming soon! Необратимото изтриване на съобщения е забранено в този чат. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. Необратимото изтриване на съобщения е забранено в тази група. No comment provided by engineer. @@ -4272,8 +4272,8 @@ This is your link for group %@! Реакциите на съобщения са забранени в този чат. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. Реакциите на съобщения са забранени в тази група. No comment provided by engineer. @@ -6390,8 +6390,8 @@ Enable in *Network & servers* settings. SimpleX линкове chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. SimpleX линкове са забранени в тази група. No comment provided by engineer. @@ -7427,8 +7427,8 @@ To connect, please ask your contact to create another connection link and check Гласовите съобщения са забранени в този чат. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. Гласовите съобщения са забранени в тази група. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff b/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff index f7630b9e1f..7002f790df 100644 --- a/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff +++ b/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff @@ -1247,8 +1247,8 @@ Direct messages chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. No comment provided by engineer. @@ -1267,8 +1267,8 @@ Disappearing messages are prohibited in this chat. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. No comment provided by engineer. @@ -1747,24 +1747,24 @@ Group links No comment provided by engineer. - - Group members can add message reactions. + + Members can add message reactions. No comment provided by engineer. Group members can irreversibly delete sent messages. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. No comment provided by engineer. @@ -2016,8 +2016,8 @@ Irreversible message deletion is prohibited in this chat. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. No comment provided by engineer. @@ -2203,8 +2203,8 @@ Message reactions are prohibited in this chat. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. No comment provided by engineer. @@ -3720,8 +3720,8 @@ To connect, please ask your contact to create another connection link and check Voice messages are prohibited in this chat. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index a4bff0f321..7efd941d11 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -2309,8 +2309,8 @@ This is your own one-time link! Přímé zprávy chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. Přímé zprávy mezi členy jsou v této skupině zakázány. No comment provided by engineer. @@ -2348,8 +2348,8 @@ This is your own one-time link! Mizící zprávy jsou v tomto chatu zakázány. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. Mizící zprávy jsou v této skupině zakázány. No comment provided by engineer. @@ -3144,8 +3144,8 @@ This is your own one-time link! Soubory a média chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. Soubory a média jsou zakázány v této skupině. No comment provided by engineer. @@ -3389,37 +3389,37 @@ Error: %2$@ Odkazy na skupiny No comment provided by engineer. - - Group members can add message reactions. + + Members can add message reactions. Členové skupin mohou přidávat reakce na zprávy. No comment provided by engineer. - - Group members can irreversibly delete sent messages. (24 hours) + + Members can irreversibly delete sent messages. (24 hours) Členové skupiny mohou nevratně mazat odeslané zprávy. (24 hodin) No comment provided by engineer. - - Group members can send SimpleX links. + + Members can send SimpleX links. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. Členové skupiny mohou posílat přímé zprávy. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. Členové skupiny mohou posílat mizící zprávy. No comment provided by engineer. - - Group members can send files and media. + + Members can send files and media. Členové skupiny mohou posílat soubory a média. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. Členové skupiny mohou posílat hlasové zprávy. No comment provided by engineer. @@ -3815,8 +3815,8 @@ More improvements are coming soon! Nevratné mazání zpráv je v tomto chatu zakázáno. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. Nevratné mazání zpráv je v této skupině zakázáno. No comment provided by engineer. @@ -4132,8 +4132,8 @@ This is your link for group %@! Reakce na zprávy jsou v tomto chatu zakázány. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. Reakce na zprávy jsou v této skupině zakázány. No comment provided by engineer. @@ -6188,8 +6188,8 @@ Enable in *Network & servers* settings. Odkazy na SimpleX chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. No comment provided by engineer. @@ -7186,8 +7186,8 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Hlasové zprávy jsou v tomto chatu zakázány. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. Hlasové zprávy jsou v této skupině zakázány. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 3770207e39..afce946eea 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -2500,8 +2500,8 @@ Das ist Ihr eigener Einmal-Link! Direkte Nachrichten chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. In dieser Gruppe sind Direktnachrichten zwischen Mitgliedern nicht erlaubt. No comment provided by engineer. @@ -2540,8 +2540,8 @@ Das ist Ihr eigener Einmal-Link! In diesem Chat sind verschwindende Nachrichten nicht erlaubt. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. In dieser Gruppe sind verschwindende Nachrichten nicht erlaubt. No comment provided by engineer. @@ -3398,8 +3398,8 @@ Das ist Ihr eigener Einmal-Link! Dateien und Medien chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. In dieser Gruppe sind Dateien und Medien nicht erlaubt. No comment provided by engineer. @@ -3672,38 +3672,38 @@ Fehler: %2$@ Gruppen-Links No comment provided by engineer. - - Group members can add message reactions. + + Members can add message reactions. Gruppenmitglieder können eine Reaktion auf Nachrichten geben. No comment provided by engineer. - - Group members can irreversibly delete sent messages. (24 hours) + + Members can irreversibly delete sent messages. (24 hours) Gruppenmitglieder können gesendete Nachrichten unwiederbringlich löschen. (24 Stunden) No comment provided by engineer. - - Group members can send SimpleX links. + + Members can send SimpleX links. Gruppenmitglieder können SimpleX-Links senden. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. Gruppenmitglieder können Direktnachrichten versenden. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. Gruppenmitglieder können verschwindende Nachrichten senden. No comment provided by engineer. - - Group members can send files and media. + + Members can send files and media. Gruppenmitglieder können Dateien und Medien senden. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. Gruppenmitglieder können Sprachnachrichten versenden. No comment provided by engineer. @@ -4121,8 +4121,8 @@ Weitere Verbesserungen sind bald verfügbar! In diesem Chat ist das unwiederbringliche Löschen von Nachrichten nicht erlaubt. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. In dieser Gruppe ist das unwiederbringliche Löschen von Nachrichten nicht erlaubt. No comment provided by engineer. @@ -4459,8 +4459,8 @@ Das ist Ihr Link für die Gruppe %@! In diesem Chat sind Reaktionen auf Nachrichten nicht erlaubt. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. In dieser Gruppe sind Reaktionen auf Nachrichten nicht erlaubt. No comment provided by engineer. @@ -6707,8 +6707,8 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. SimpleX-Links chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. In dieser Gruppe sind SimpleX-Links nicht erlaubt. No comment provided by engineer. @@ -7804,8 +7804,8 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s In diesem Chat sind Sprachnachrichten nicht erlaubt. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. In dieser Gruppe sind Sprachnachrichten nicht erlaubt. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff b/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff index 9a112d12fa..b601d1fa74 100644 --- a/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff +++ b/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff @@ -1124,8 +1124,8 @@ Available in v5.1 Direct messages chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. No comment provided by engineer. @@ -1140,8 +1140,8 @@ Available in v5.1 Disappearing messages are prohibited in this chat. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. No comment provided by engineer. @@ -1576,16 +1576,16 @@ Available in v5.1 Group members can irreversibly delete sent messages. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. No comment provided by engineer. @@ -1817,8 +1817,8 @@ Available in v5.1 Irreversible message deletion is prohibited in this chat. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. No comment provided by engineer. @@ -3333,8 +3333,8 @@ To connect, please ask your contact to create another connection link and check Voice messages are prohibited in this chat. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 72ad43f136..2301f671a4 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -2506,9 +2506,9 @@ This is your own one-time link! Direct messages chat feature - - Direct messages between members are prohibited in this group. - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. + Direct messages between members are prohibited. No comment provided by engineer. @@ -2546,9 +2546,9 @@ This is your own one-time link! Disappearing messages are prohibited in this chat. No comment provided by engineer. - - Disappearing messages are prohibited in this group. - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. + Disappearing messages are prohibited. No comment provided by engineer. @@ -3404,9 +3404,9 @@ This is your own one-time link! Files and media chat feature - - Files and media are prohibited in this group. - Files and media are prohibited in this group. + + Files and media are prohibited. + Files and media are prohibited. No comment provided by engineer. @@ -3678,39 +3678,39 @@ Error: %2$@ Group links No comment provided by engineer. - - Group members can add message reactions. - Group members can add message reactions. + + Members can add message reactions. + Members can add message reactions. No comment provided by engineer. - - Group members can irreversibly delete sent messages. (24 hours) - Group members can irreversibly delete sent messages. (24 hours) + + Members can irreversibly delete sent messages. (24 hours) + Members can irreversibly delete sent messages. (24 hours) No comment provided by engineer. - - Group members can send SimpleX links. - Group members can send SimpleX links. + + Members can send SimpleX links. + Members can send SimpleX links. No comment provided by engineer. - - Group members can send direct messages. - Group members can send direct messages. + + Members can send direct messages. + Members can send direct messages. No comment provided by engineer. - - Group members can send disappearing messages. - Group members can send disappearing messages. + + Members can send disappearing messages. + Members can send disappearing messages. No comment provided by engineer. - - Group members can send files and media. - Group members can send files and media. + + Members can send files and media. + Members can send files and media. No comment provided by engineer. - - Group members can send voice messages. - Group members can send voice messages. + + Members can send voice messages. + Members can send voice messages. No comment provided by engineer. @@ -4127,9 +4127,9 @@ More improvements are coming soon! Irreversible message deletion is prohibited in this chat. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. + Irreversible message deletion is prohibited. No comment provided by engineer. @@ -4465,9 +4465,9 @@ This is your link for group %@! Message reactions are prohibited in this chat. No comment provided by engineer. - - Message reactions are prohibited in this group. - Message reactions are prohibited in this group. + + Message reactions are prohibited. + Message reactions are prohibited. No comment provided by engineer. @@ -6714,9 +6714,9 @@ Enable in *Network & servers* settings. SimpleX links chat feature - - SimpleX links are prohibited in this group. - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. + SimpleX links are prohibited. No comment provided by engineer. @@ -7812,9 +7812,9 @@ To connect, please ask your contact to create another connection link and check Voice messages are prohibited in this chat. No comment provided by engineer. - - Voice messages are prohibited in this group. - Voice messages are prohibited in this group. + + Voice messages are prohibited. + Voice messages are prohibited. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index cfffd783d9..647d650698 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -2500,8 +2500,8 @@ This is your own one-time link! Mensajes directos chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. Los mensajes directos entre miembros del grupo no están permitidos. No comment provided by engineer. @@ -2540,8 +2540,8 @@ This is your own one-time link! Los mensajes temporales no están permitidos en este chat. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. Los mensajes temporales no están permitidos en este grupo. No comment provided by engineer. @@ -3398,8 +3398,8 @@ This is your own one-time link! Archivos y multimedia chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. Los archivos y multimedia no están permitidos en este grupo. No comment provided by engineer. @@ -3672,38 +3672,38 @@ Error: %2$@ Enlaces de grupo No comment provided by engineer. - - Group members can add message reactions. + + Members can add message reactions. Los miembros pueden añadir reacciones a los mensajes. No comment provided by engineer. - - Group members can irreversibly delete sent messages. (24 hours) + + Members can irreversibly delete sent messages. (24 hours) Los miembros del grupo pueden eliminar mensajes de forma irreversible. (24 horas) No comment provided by engineer. - - Group members can send SimpleX links. + + Members can send SimpleX links. Los miembros del grupo pueden enviar enlaces SimpleX. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. Los miembros del grupo pueden enviar mensajes directos. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. Los miembros del grupo pueden enviar mensajes temporales. No comment provided by engineer. - - Group members can send files and media. + + Members can send files and media. Los miembros del grupo pueden enviar archivos y multimedia. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. Los miembros del grupo pueden enviar mensajes de voz. No comment provided by engineer. @@ -4121,8 +4121,8 @@ More improvements are coming soon! La eliminación irreversible de mensajes no está permitida en este chat. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. La eliminación irreversible de mensajes no está permitida en este grupo. No comment provided by engineer. @@ -4459,8 +4459,8 @@ This is your link for group %@! Las reacciones a los mensajes no están permitidas en este chat. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. Las reacciones a los mensajes no están permitidas en este grupo. No comment provided by engineer. @@ -6707,8 +6707,8 @@ Actívalo en ajustes de *Servidores y Redes*. Enlaces SimpleX chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. Los enlaces SimpleX no se permiten en este grupo. No comment provided by engineer. @@ -7804,8 +7804,8 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Los mensajes de voz no están permitidos en este chat. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. Los mensajes de voz no están permitidos en este grupo. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index 763b502ddb..00996b4a4f 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -2302,8 +2302,8 @@ This is your own one-time link! Yksityisviestit chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. Yksityisviestit jäsenten välillä ovat kiellettyjä tässä ryhmässä. No comment provided by engineer. @@ -2341,8 +2341,8 @@ This is your own one-time link! Katoavat viestit ovat kiellettyjä tässä keskustelussa. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. Katoavat viestit ovat kiellettyjä tässä ryhmässä. No comment provided by engineer. @@ -3134,8 +3134,8 @@ This is your own one-time link! Tiedostot ja media chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. Tiedostot ja media ovat tässä ryhmässä kiellettyjä. No comment provided by engineer. @@ -3379,37 +3379,37 @@ Error: %2$@ Ryhmälinkit No comment provided by engineer. - - Group members can add message reactions. + + Members can add message reactions. Ryhmän jäsenet voivat lisätä viestireaktioita. No comment provided by engineer. - - Group members can irreversibly delete sent messages. (24 hours) + + Members can irreversibly delete sent messages. (24 hours) Ryhmän jäsenet voivat poistaa lähetetyt viestit peruuttamattomasti. (24 tuntia) No comment provided by engineer. - - Group members can send SimpleX links. + + Members can send SimpleX links. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. Ryhmän jäsenet voivat lähettää suoraviestejä. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. Ryhmän jäsenet voivat lähettää katoavia viestejä. No comment provided by engineer. - - Group members can send files and media. + + Members can send files and media. Ryhmän jäsenet voivat lähettää tiedostoja ja mediaa. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. Ryhmän jäsenet voivat lähettää ääniviestejä. No comment provided by engineer. @@ -3805,8 +3805,8 @@ More improvements are coming soon! Viestien peruuttamaton poisto on kielletty tässä keskustelussa. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. Viestien peruuttamaton poisto on kielletty tässä ryhmässä. No comment provided by engineer. @@ -4122,8 +4122,8 @@ This is your link for group %@! Viestireaktiot ovat kiellettyjä tässä keskustelussa. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. Viestireaktiot ovat kiellettyjä tässä ryhmässä. No comment provided by engineer. @@ -6175,8 +6175,8 @@ Enable in *Network & servers* settings. SimpleX-linkit chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. No comment provided by engineer. @@ -7171,8 +7171,8 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Ääniviestit ovat kiellettyjä tässä keskustelussa. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. Ääniviestit ovat kiellettyjä tässä ryhmässä. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index d91ce3c106..9498c255c8 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -2472,8 +2472,8 @@ Il s'agit de votre propre lien unique ! Messages directs chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. Les messages directs entre membres sont interdits dans ce groupe. No comment provided by engineer. @@ -2512,8 +2512,8 @@ Il s'agit de votre propre lien unique ! Les messages éphémères sont interdits dans cette discussion. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. Les messages éphémères sont interdits dans ce groupe. No comment provided by engineer. @@ -3362,8 +3362,8 @@ Il s'agit de votre propre lien unique ! Fichiers et médias chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. Les fichiers et les médias sont interdits dans ce groupe. No comment provided by engineer. @@ -3632,38 +3632,38 @@ Erreur : %2$@ Liens de groupe No comment provided by engineer. - - Group members can add message reactions. + + Members can add message reactions. Les membres du groupe peuvent ajouter des réactions aux messages. No comment provided by engineer. - - Group members can irreversibly delete sent messages. (24 hours) + + Members can irreversibly delete sent messages. (24 hours) Les membres du groupe peuvent supprimer de manière irréversible les messages envoyés. (24 heures) No comment provided by engineer. - - Group members can send SimpleX links. + + Members can send SimpleX links. Les membres du groupe peuvent envoyer des liens SimpleX. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. Les membres du groupe peuvent envoyer des messages directs. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. Les membres du groupes peuvent envoyer des messages éphémères. No comment provided by engineer. - - Group members can send files and media. + + Members can send files and media. Les membres du groupe peuvent envoyer des fichiers et des médias. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. Les membres du groupe peuvent envoyer des messages vocaux. No comment provided by engineer. @@ -4079,8 +4079,8 @@ D'autres améliorations sont à venir ! La suppression irréversible de message est interdite dans ce chat. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. La suppression irréversible de messages est interdite dans ce groupe. No comment provided by engineer. @@ -4417,8 +4417,8 @@ Voici votre lien pour le groupe %@ ! Les réactions aux messages sont interdites dans ce chat. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. Les réactions aux messages sont interdites dans ce groupe. No comment provided by engineer. @@ -6633,8 +6633,8 @@ Activez-le dans les paramètres *Réseau et serveurs*. Liens SimpleX chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. Les liens SimpleX sont interdits dans ce groupe. No comment provided by engineer. @@ -7712,8 +7712,8 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Les messages vocaux sont interdits dans ce chat. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. Les messages vocaux sont interdits dans ce groupe. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff b/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff index 813eebc01a..08f46bb056 100644 --- a/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff +++ b/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff @@ -1386,8 +1386,8 @@ Available in v5.1 הודעות ישירות chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. הודעות ישירות בין חברי קבוצה אסורות בקבוצה זו. No comment provided by engineer. @@ -1406,8 +1406,8 @@ Available in v5.1 הודעות נעלמות אסורות בצ׳אט זה. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. הודעות נעלמות אסורות בקבוצה זו. No comment provided by engineer. @@ -1951,18 +1951,18 @@ Available in v5.1 חברי הקבוצה יכולים למחוק באופן בלתי הפיך הודעות שנשלחו. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. חברי הקבוצה יכולים לשלוח הודעות ישירות. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. חברי הקבוצה יכולים לשלוח הודעות נעלמות. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. חברי הקבוצה יכולים לשלוח הודעות קוליות. No comment provided by engineer. @@ -2252,8 +2252,8 @@ Available in v5.1 מחיקה בלתי הפיכה של הודעות אסורה בצ׳אט זה. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. מחיקה בלתי הפיכה של הודעות אסורה בקבוצה זו. No comment provided by engineer. @@ -3859,8 +3859,8 @@ To connect, please ask your contact to create another connection link and check Voice messages are prohibited in this chat. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. No comment provided by engineer. @@ -4958,8 +4958,8 @@ SimpleX servers cannot see your profile. נמחק No comment provided by engineer. - - Files and media are prohibited in this group. + + Files and media are prohibited. קבצים ומדיה אסורים בקבוצה זו. No comment provided by engineer. @@ -5018,13 +5018,13 @@ SimpleX servers cannot see your profile. הזמן חברים No comment provided by engineer. - - Group members can add message reactions. + + Members can add message reactions. חברי הקבוצה יכולים להוסיף תגובות אמוג׳י להודעות. No comment provided by engineer. - - Group members can send files and media. + + Members can send files and media. חברי הקבוצה יכולים לשלוח קבצים ומדיה. No comment provided by engineer. @@ -5222,8 +5222,8 @@ SimpleX servers cannot see your profile. תגובות אמוג׳י להודעות אסורות בצ׳אט זה. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. תגובות אמוג׳י להודעות אסורות בקבוצה זו. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff b/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff index 7ae670185c..2fd96e3492 100644 --- a/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff +++ b/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff @@ -1034,8 +1034,8 @@ Direct messages chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. No comment provided by engineer. @@ -1050,8 +1050,8 @@ Disappearing messages are prohibited in this chat. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. No comment provided by engineer. @@ -1414,16 +1414,16 @@ Group members can irreversibly delete sent messages. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. No comment provided by engineer. @@ -1611,8 +1611,8 @@ Irreversible message deletion is prohibited in this chat. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. No comment provided by engineer. @@ -2842,8 +2842,8 @@ To connect, please ask your contact to create another connection link and check Voice messages are prohibited in this chat. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 44750dfbb2..a17a6430d1 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -2500,8 +2500,8 @@ Ez az Ön egyszer használható meghívó-hivatkozása! Közvetlen üzenetek chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. A közvetlen üzenetek küldése a tagok között le van tiltva ebben a csoportban. No comment provided by engineer. @@ -2540,8 +2540,8 @@ Ez az Ön egyszer használható meghívó-hivatkozása! Az eltűnő üzenetek küldése le van tiltva ebben a csevegésben. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. Az eltűnő üzenetek küldése le van tiltva ebben a csoportban. No comment provided by engineer. @@ -3398,8 +3398,8 @@ Ez az Ön egyszer használható meghívó-hivatkozása! Fájlok és médiatartalmak chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. A fájlok- és a médiatartalmak le vannak tiltva ebben a csoportban. No comment provided by engineer. @@ -3672,38 +3672,38 @@ Hiba: %2$@ Csoporthivatkozások No comment provided by engineer. - - Group members can add message reactions. + + Members can add message reactions. Csoporttagok üzenetreakciókat adhatnak hozzá. No comment provided by engineer. - - Group members can irreversibly delete sent messages. (24 hours) + + Members can irreversibly delete sent messages. (24 hours) A csoport tagjai véglegesen törölhetik az elküldött üzeneteiket. (24 óra) No comment provided by engineer. - - Group members can send SimpleX links. + + Members can send SimpleX links. A csoport tagjai küldhetnek SimpleX-hivatkozásokat. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. A csoport tagjai küldhetnek egymásnak közvetlen üzeneteket. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. A csoport tagjai küldhetnek eltűnő üzeneteket. No comment provided by engineer. - - Group members can send files and media. + + Members can send files and media. A csoport tagjai küldhetnek fájlokat és médiatartalmakat. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. A csoport tagjai küldhetnek hangüzeneteket. No comment provided by engineer. @@ -4121,8 +4121,8 @@ További fejlesztések hamarosan! Az üzenetek végleges törlése le van tiltva ebben a csevegésben. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. Az üzenetek végleges törlése le van tiltva ebben a csoportban. No comment provided by engineer. @@ -4459,8 +4459,8 @@ Ez az Ön hivatkozása a(z) %@ nevű csoporthoz! Az üzenetreakciók küldése le van tiltva ebben a csevegésben. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. Az üzenetreakciók küldése le van tiltva ebben a csoportban. No comment provided by engineer. @@ -6707,8 +6707,8 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. SimpleX-hivatkozások chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. A SimpleX-hivatkozások küldése le van tiltva ebben a csoportban. No comment provided by engineer. @@ -7804,8 +7804,8 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc A hangüzenetek küldése le van tiltva ebben a csevegésben. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. A hangüzenetek küldése le van tiltva ebben a csoportban. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 8e54ba40dd..0992a1f6bc 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -2500,8 +2500,8 @@ Questo è il tuo link una tantum! Messaggi diretti chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. I messaggi diretti tra i membri sono vietati in questo gruppo. No comment provided by engineer. @@ -2540,8 +2540,8 @@ Questo è il tuo link una tantum! I messaggi a tempo sono vietati in questa chat. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. I messaggi a tempo sono vietati in questo gruppo. No comment provided by engineer. @@ -3398,8 +3398,8 @@ Questo è il tuo link una tantum! File e multimediali chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. File e contenuti multimediali sono vietati in questo gruppo. No comment provided by engineer. @@ -3672,38 +3672,38 @@ Errore: %2$@ Link del gruppo No comment provided by engineer. - - Group members can add message reactions. + + Members can add message reactions. I membri del gruppo possono aggiungere reazioni ai messaggi. No comment provided by engineer. - - Group members can irreversibly delete sent messages. (24 hours) + + Members can irreversibly delete sent messages. (24 hours) I membri del gruppo possono eliminare irreversibilmente i messaggi inviati. (24 ore) No comment provided by engineer. - - Group members can send SimpleX links. + + Members can send SimpleX links. I membri del gruppo possono inviare link di Simplex. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. I membri del gruppo possono inviare messaggi diretti. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. I membri del gruppo possono inviare messaggi a tempo. No comment provided by engineer. - - Group members can send files and media. + + Members can send files and media. I membri del gruppo possono inviare file e contenuti multimediali. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. I membri del gruppo possono inviare messaggi vocali. No comment provided by engineer. @@ -4121,8 +4121,8 @@ Altri miglioramenti sono in arrivo! L'eliminazione irreversibile dei messaggi è vietata in questa chat. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. L'eliminazione irreversibile dei messaggi è vietata in questo gruppo. No comment provided by engineer. @@ -4459,8 +4459,8 @@ Questo è il tuo link per il gruppo %@! Le reazioni ai messaggi sono vietate in questa chat. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. Le reazioni ai messaggi sono vietate in questo gruppo. No comment provided by engineer. @@ -6707,8 +6707,8 @@ Attivalo nelle impostazioni *Rete e server*. Link di SimpleX chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. I link di SimpleX sono vietati in questo gruppo. No comment provided by engineer. @@ -7804,8 +7804,8 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e I messaggi vocali sono vietati in questa chat. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. I messaggi vocali sono vietati in questo gruppo. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 1a5ffdd680..32d2db371b 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -2348,8 +2348,8 @@ This is your own one-time link! ダイレクトメッセージ chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. このグループではメンバー間のダイレクトメッセージが使用禁止です。 No comment provided by engineer. @@ -2387,8 +2387,8 @@ This is your own one-time link! このチャットでは消えるメッセージが使用禁止です。 No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. このグループでは消えるメッセージが使用禁止です。 No comment provided by engineer. @@ -3181,8 +3181,8 @@ This is your own one-time link! ファイルとメディア chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. このグループでは、ファイルとメディアは禁止されています。 No comment provided by engineer. @@ -3426,37 +3426,37 @@ Error: %2$@ グループのリンク No comment provided by engineer. - - Group members can add message reactions. + + Members can add message reactions. グループメンバーはメッセージへのリアクションを追加できます。 No comment provided by engineer. - - Group members can irreversibly delete sent messages. (24 hours) + + Members can irreversibly delete sent messages. (24 hours) グループのメンバーがメッセージを完全削除することができます。(24時間) No comment provided by engineer. - - Group members can send SimpleX links. + + Members can send SimpleX links. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. グループのメンバーがダイレクトメッセージを送信できます。 No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. グループのメンバーが消えるメッセージを送信できます。 No comment provided by engineer. - - Group members can send files and media. + + Members can send files and media. グループメンバーはファイルやメディアを送信できます。 No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. グループのメンバーが音声メッセージを送信できます。 No comment provided by engineer. @@ -3852,8 +3852,8 @@ More improvements are coming soon! このチャットではメッセージの完全削除が使用禁止です。 No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. このグループではメッセージの完全削除が使用禁止です。 No comment provided by engineer. @@ -4168,8 +4168,8 @@ This is your link for group %@! このチャットではメッセージへのリアクションは禁止されています。 No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. このグループではメッセージへのリアクションは禁止されています。 No comment provided by engineer. @@ -6217,8 +6217,8 @@ Enable in *Network & servers* settings. SimpleXリンク chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. No comment provided by engineer. @@ -7213,8 +7213,8 @@ To connect, please ask your contact to create another connection link and check このチャットでは音声メッセージが使用禁止です。 No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. このグループでは音声メッセージが使用禁止です。 No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff b/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff index ac9d83e24b..9aaa83afc3 100644 --- a/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff +++ b/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff @@ -1009,8 +1009,8 @@ Direct messages chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. No comment provided by engineer. @@ -1025,8 +1025,8 @@ Disappearing messages are prohibited in this chat. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. No comment provided by engineer. @@ -1417,16 +1417,16 @@ Group members can irreversibly delete sent messages. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. No comment provided by engineer. @@ -1638,8 +1638,8 @@ Irreversible message deletion is prohibited in this chat. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. No comment provided by engineer. @@ -2964,8 +2964,8 @@ To connect, please ask your contact to create another connection link and check Voice messages are prohibited in this chat. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff b/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff index e16f585da8..54a713478f 100644 --- a/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff +++ b/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff @@ -1029,8 +1029,8 @@ Direct messages chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. No comment provided by engineer. @@ -1045,8 +1045,8 @@ Disappearing messages are prohibited in this chat. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. No comment provided by engineer. @@ -1413,16 +1413,16 @@ Group members can irreversibly delete sent messages. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. No comment provided by engineer. @@ -1610,8 +1610,8 @@ Irreversible message deletion is prohibited in this chat. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. No comment provided by engineer. @@ -2869,8 +2869,8 @@ To connect, please ask your contact to create another connection link and check Voice messages are prohibited in this chat. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 50b3fdae3e..b9cba70c3a 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -2500,8 +2500,8 @@ Dit is uw eigen eenmalige link! Directe berichten chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. Directe berichten tussen leden zijn verboden in deze groep. No comment provided by engineer. @@ -2540,8 +2540,8 @@ Dit is uw eigen eenmalige link! Verdwijnende berichten zijn verboden in dit gesprek. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. Verdwijnende berichten zijn verboden in deze groep. No comment provided by engineer. @@ -3398,8 +3398,8 @@ Dit is uw eigen eenmalige link! Bestanden en media chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. Bestanden en media zijn verboden in deze groep. No comment provided by engineer. @@ -3672,38 +3672,38 @@ Fout: %2$@ Groep links No comment provided by engineer. - - Group members can add message reactions. + + Members can add message reactions. Groepsleden kunnen bericht reacties toevoegen. No comment provided by engineer. - - Group members can irreversibly delete sent messages. (24 hours) + + Members can irreversibly delete sent messages. (24 hours) Groepsleden kunnen verzonden berichten onherroepelijk verwijderen. (24 uur) No comment provided by engineer. - - Group members can send SimpleX links. + + Members can send SimpleX links. Groepsleden kunnen SimpleX-links verzenden. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. Groepsleden kunnen directe berichten sturen. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. Groepsleden kunnen verdwijnende berichten sturen. No comment provided by engineer. - - Group members can send files and media. + + Members can send files and media. Groepsleden kunnen bestanden en media verzenden. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. Groepsleden kunnen spraak berichten verzenden. No comment provided by engineer. @@ -4121,8 +4121,8 @@ Binnenkort meer verbeteringen! Het onomkeerbaar verwijderen van berichten is verboden in dit gesprek. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. Het onomkeerbaar verwijderen van berichten is verboden in deze groep. No comment provided by engineer. @@ -4459,8 +4459,8 @@ Dit is jouw link voor groep %@! Reacties op berichten zijn verboden in deze chat. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. Reacties op berichten zijn verboden in deze groep. No comment provided by engineer. @@ -6707,8 +6707,8 @@ Schakel dit in in *Netwerk en servers*-instellingen. SimpleX links chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. SimpleX-links zijn in deze groep verboden. No comment provided by engineer. @@ -7804,8 +7804,8 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Spraak berichten zijn verboden in deze chat. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. Spraak berichten zijn verboden in deze groep. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 0095b5e031..bdfe502952 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -2465,8 +2465,8 @@ To jest twój jednorazowy link! Bezpośrednie wiadomości chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. Bezpośrednie wiadomości między członkami są zabronione w tej grupie. No comment provided by engineer. @@ -2505,8 +2505,8 @@ To jest twój jednorazowy link! Znikające wiadomości są zabronione na tym czacie. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. Znikające wiadomości są zabronione w tej grupie. No comment provided by engineer. @@ -3355,8 +3355,8 @@ To jest twój jednorazowy link! Pliki i media chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. Pliki i media są zabronione w tej grupie. No comment provided by engineer. @@ -3624,38 +3624,38 @@ Błąd: %2$@ Linki grupowe No comment provided by engineer. - - Group members can add message reactions. + + Members can add message reactions. Członkowie grupy mogą dodawać reakcje wiadomości. No comment provided by engineer. - - Group members can irreversibly delete sent messages. (24 hours) + + Members can irreversibly delete sent messages. (24 hours) Członkowie grupy mogą nieodwracalnie usuwać wysłane wiadomości. (24 godziny) No comment provided by engineer. - - Group members can send SimpleX links. + + Members can send SimpleX links. Członkowie grupy mogą wysyłać linki SimpleX. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. Członkowie grupy mogą wysyłać bezpośrednie wiadomości. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. Członkowie grupy mogą wysyłać znikające wiadomości. No comment provided by engineer. - - Group members can send files and media. + + Members can send files and media. Członkowie grupy mogą wysyłać pliki i media. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. Członkowie grupy mogą wysyłać wiadomości głosowe. No comment provided by engineer. @@ -4069,8 +4069,8 @@ More improvements are coming soon! Nieodwracalne usuwanie wiadomości jest na tym czacie zabronione. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. Nieodwracalne usuwanie wiadomości jest w tej grupie zabronione. No comment provided by engineer. @@ -4407,8 +4407,8 @@ To jest twój link do grupy %@! Reakcje wiadomości są zabronione na tym czacie. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. Reakcje wiadomości są zabronione w tej grupie. No comment provided by engineer. @@ -6623,8 +6623,8 @@ Włącz w ustawianiach *Sieć i serwery* . Linki SimpleX chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. Linki SimpleX są zablokowane na tej grupie. No comment provided by engineer. @@ -7699,8 +7699,8 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Wiadomości głosowe są zabronione na tym czacie. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. Wiadomości głosowe są zabronione w tej grupie. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff b/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff index 40fc2cd4b3..9badf9c2e4 100644 --- a/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff +++ b/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff @@ -1204,8 +1204,8 @@ Mensagens diretas chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. Mensagens diretas entre membros são proibidas neste grupo. No comment provided by engineer. @@ -1224,8 +1224,8 @@ Mensagens temporárias são proibidas nesse chat. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. Mensagens que temporárias são proibidas neste grupo. No comment provided by engineer. @@ -1638,18 +1638,18 @@ Os membros do grupo podem excluir mensagens enviadas de forma irreversível. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. Os membros do grupo podem enviar DMs. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. Os membros do grupo podem enviar mensagens que desaparecem. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. Os membros do grupo podem enviar mensagens de voz. No comment provided by engineer. @@ -1873,8 +1873,8 @@ A exclusão irreversível de mensagens é proibida neste chat. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. A exclusão irreversível de mensagens é proibida neste grupo. No comment provided by engineer. @@ -3269,8 +3269,8 @@ Para se conectar, peça ao seu contato para criar outro link de conexão e verif Mensagens de voz são proibidas neste chat. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. Mensagens de voz são proibidas neste grupo. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff b/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff index e20181e4f7..de1787bdad 100644 --- a/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff +++ b/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff @@ -1227,8 +1227,8 @@ Available in v5.1 Direct messages chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. No comment provided by engineer. @@ -1243,8 +1243,8 @@ Available in v5.1 Disappearing messages are prohibited in this chat. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. No comment provided by engineer. @@ -1679,16 +1679,16 @@ Available in v5.1 Group members can irreversibly delete sent messages. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. No comment provided by engineer. @@ -1920,8 +1920,8 @@ Available in v5.1 Irreversible message deletion is prohibited in this chat. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. No comment provided by engineer. @@ -3436,8 +3436,8 @@ To connect, please ask your contact to create another connection link and check Voice messages are prohibited in this chat. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index e7230dbcb2..6d57734259 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -2473,8 +2473,8 @@ This is your own one-time link! Прямые сообщения chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. Прямые сообщения между членами группы запрещены. No comment provided by engineer. @@ -2513,8 +2513,8 @@ This is your own one-time link! Исчезающие сообщения запрещены в этом чате. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. Исчезающие сообщения запрещены в этой группе. No comment provided by engineer. @@ -3363,8 +3363,8 @@ This is your own one-time link! Файлы и медиа chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. Файлы и медиа запрещены в этой группе. No comment provided by engineer. @@ -3633,38 +3633,38 @@ Error: %2$@ Ссылки групп No comment provided by engineer. - - Group members can add message reactions. + + Members can add message reactions. Члены группы могут добавлять реакции на сообщения. No comment provided by engineer. - - Group members can irreversibly delete sent messages. (24 hours) + + Members can irreversibly delete sent messages. (24 hours) Члены группы могут необратимо удалять отправленные сообщения. (24 часа) No comment provided by engineer. - - Group members can send SimpleX links. + + Members can send SimpleX links. Члены группы могут отправлять ссылки SimpleX. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. Члены группы могут посылать прямые сообщения. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. Члены группы могут посылать исчезающие сообщения. No comment provided by engineer. - - Group members can send files and media. + + Members can send files and media. Члены группы могут слать файлы и медиа. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. Члены группы могут отправлять голосовые сообщения. No comment provided by engineer. @@ -4079,8 +4079,8 @@ More improvements are coming soon! Необратимое удаление сообщений запрещено в этом чате. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. Необратимое удаление сообщений запрещено в этой группе. No comment provided by engineer. @@ -4417,8 +4417,8 @@ This is your link for group %@! Реакции на сообщения в этом чате запрещены. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. Реакции на сообщения запрещены в этой группе. No comment provided by engineer. @@ -6633,8 +6633,8 @@ Enable in *Network & servers* settings. SimpleX ссылки chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. Ссылки SimpleX запрещены в этой группе. No comment provided by engineer. @@ -7712,8 +7712,8 @@ To connect, please ask your contact to create another connection link and check Голосовые сообщения запрещены в этом чате. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. Голосовые сообщения запрещены в этой группе. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index f3097bf8f2..cec1637438 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -2290,8 +2290,8 @@ This is your own one-time link! ข้อความโดยตรง chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. ข้อความโดยตรงระหว่างสมาชิกเป็นสิ่งต้องห้ามในกลุ่มนี้ No comment provided by engineer. @@ -2329,8 +2329,8 @@ This is your own one-time link! ข้อความที่จะหายไปหลังเวลาที่กําหนด (disappearing message) เป็นสิ่งต้องห้ามในแชทนี้ No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. ข้อความที่จะหายไปหลังเวลาที่กําหนด (disappearing message) เป็นสิ่งต้องห้ามในกลุ่มนี้ No comment provided by engineer. @@ -3119,8 +3119,8 @@ This is your own one-time link! ไฟล์และสื่อ chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. ไฟล์และสื่อเป็นสิ่งต้องห้ามในกลุ่มนี้ No comment provided by engineer. @@ -3364,37 +3364,37 @@ Error: %2$@ ลิงค์กลุ่ม No comment provided by engineer. - - Group members can add message reactions. + + Members can add message reactions. สมาชิกกลุ่มสามารถเพิ่มการแสดงปฏิกิริยาต่อข้อความได้ No comment provided by engineer. - - Group members can irreversibly delete sent messages. (24 hours) + + Members can irreversibly delete sent messages. (24 hours) สมาชิกกลุ่มสามารถลบข้อความที่ส่งแล้วอย่างถาวร No comment provided by engineer. - - Group members can send SimpleX links. + + Members can send SimpleX links. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. สมาชิกกลุ่มสามารถส่งข้อความโดยตรงได้ No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. สมาชิกกลุ่มสามารถส่งข้อความที่จะหายไปหลังจากเวลาที่กำหนดหลังการอ่าน (disappearing messages) ได้ No comment provided by engineer. - - Group members can send files and media. + + Members can send files and media. สมาชิกกลุ่มสามารถส่งไฟล์และสื่อ No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. สมาชิกกลุ่มสามารถส่งข้อความเสียง No comment provided by engineer. @@ -3788,8 +3788,8 @@ More improvements are coming soon! ไม่สามารถลบข้อความแบบแก้ไขไม่ได้ในแชทนี้ No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. การลบข้อความแบบแก้ไขไม่ได้เป็นสิ่งที่ห้ามในกลุ่มนี้ No comment provided by engineer. @@ -4105,8 +4105,8 @@ This is your link for group %@! ห้ามแสดงปฏิกิริยาบนข้อความในแชทนี้ No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. ปฏิกิริยาบนข้อความเป็นสิ่งต้องห้ามในกลุ่มนี้ No comment provided by engineer. @@ -6149,8 +6149,8 @@ Enable in *Network & servers* settings. ลิงก์ SimpleX chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. No comment provided by engineer. @@ -7141,8 +7141,8 @@ To connect, please ask your contact to create another connection link and check ห้ามส่งข้อความเสียงในแชทนี้ No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. ข้อความเสียงเป็นสิ่งต้องห้ามในกลุ่มนี้ No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index eca1c67b85..8ab4b2419d 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -2472,8 +2472,8 @@ Bu senin kendi tek kullanımlık bağlantın! Doğrudan mesajlar chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. Bu grupta üyeler arasında direkt mesajlaşma yasaktır. No comment provided by engineer. @@ -2512,8 +2512,8 @@ Bu senin kendi tek kullanımlık bağlantın! Kaybolan mesajlar bu sohbette yasaklanmış. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. Kaybolan mesajlar bu grupta yasaklanmış. No comment provided by engineer. @@ -3362,8 +3362,8 @@ Bu senin kendi tek kullanımlık bağlantın! Dosyalar ve medya chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. Dosyalar ve medya bu grupta yasaklandı. No comment provided by engineer. @@ -3632,38 +3632,38 @@ Hata: %2$@ Grup bağlantıları No comment provided by engineer. - - Group members can add message reactions. + + Members can add message reactions. Grup üyeleri mesaj tepkileri ekleyebilir. No comment provided by engineer. - - Group members can irreversibly delete sent messages. (24 hours) + + Members can irreversibly delete sent messages. (24 hours) Grup üyeleri, gönderilen mesajları kalıcı olarak silebilir. (24 saat içinde) No comment provided by engineer. - - Group members can send SimpleX links. + + Members can send SimpleX links. Grup üyeleri SimpleX bağlantıları gönderebilir. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. Grup üyeleri doğrudan mesajlar gönderebilir. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. Grup üyeleri kaybolan mesajlar gönderebilir. No comment provided by engineer. - - Group members can send files and media. + + Members can send files and media. Grup üyeleri dosyalar ve medya gönderebilir. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. Grup üyeleri sesli mesajlar gönderebilir. No comment provided by engineer. @@ -4079,8 +4079,8 @@ Daha fazla iyileştirme yakında geliyor! Bu sohbette geri döndürülemez mesaj silme yasaktır. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. Bu grupta geri döndürülemez mesaj silme yasaktır. No comment provided by engineer. @@ -4417,8 +4417,8 @@ Bu senin grup için bağlantın %@! Mesaj tepkileri bu sohbette yasaklandı. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. Mesaj tepkileri bu grupta yasaklandı. No comment provided by engineer. @@ -6633,8 +6633,8 @@ Enable in *Network & servers* settings. SimpleX bağlantıları chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. SimpleX bağlantıları bu grupta yasaklandı. No comment provided by engineer. @@ -7712,8 +7712,8 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Bu sohbette sesli mesajlar yasaktır. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. Bu grupta sesli mesajlar yasaktır. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 0d05edbfbe..801b3d0c79 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -2500,8 +2500,8 @@ This is your own one-time link! Прямі повідомлення chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. У цій групі заборонені прямі повідомлення між учасниками. No comment provided by engineer. @@ -2540,8 +2540,8 @@ This is your own one-time link! Зникаючі повідомлення в цьому чаті заборонені. No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. У цій групі заборонено зникаючі повідомлення. No comment provided by engineer. @@ -3398,8 +3398,8 @@ This is your own one-time link! Файли і медіа chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. Файли та медіа в цій групі заборонені. No comment provided by engineer. @@ -3672,38 +3672,38 @@ Error: %2$@ Групові посилання No comment provided by engineer. - - Group members can add message reactions. + + Members can add message reactions. Учасники групи можуть додавати реакції на повідомлення. No comment provided by engineer. - - Group members can irreversibly delete sent messages. (24 hours) + + Members can irreversibly delete sent messages. (24 hours) Учасники групи можуть безповоротно видаляти надіслані повідомлення. (24 години) No comment provided by engineer. - - Group members can send SimpleX links. + + Members can send SimpleX links. Учасники групи можуть надсилати посилання SimpleX. No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. Учасники групи можуть надсилати прямі повідомлення. No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. Учасники групи можуть надсилати зникаючі повідомлення. No comment provided by engineer. - - Group members can send files and media. + + Members can send files and media. Учасники групи можуть надсилати файли та медіа. No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. Учасники групи можуть надсилати голосові повідомлення. No comment provided by engineer. @@ -4121,8 +4121,8 @@ More improvements are coming soon! У цьому чаті заборонено безповоротне видалення повідомлень. No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. У цій групі заборонено безповоротне видалення повідомлень. No comment provided by engineer. @@ -4459,8 +4459,8 @@ This is your link for group %@! Реакції на повідомлення в цьому чаті заборонені. No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. Реакції на повідомлення в цій групі заборонені. No comment provided by engineer. @@ -6707,8 +6707,8 @@ Enable in *Network & servers* settings. Посилання SimpleX chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. У цій групі заборонені посилання на SimpleX. No comment provided by engineer. @@ -7804,8 +7804,8 @@ To connect, please ask your contact to create another connection link and check Голосові повідомлення в цьому чаті заборонені. No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. Голосові повідомлення в цій групі заборонені. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index 300a33d7b4..d9cb36f971 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -2455,8 +2455,8 @@ This is your own one-time link! 私信 chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. 此群中禁止成员之间私信。 No comment provided by engineer. @@ -2495,8 +2495,8 @@ This is your own one-time link! 此聊天中禁止显示限时消息。 No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. 该组禁止限时消息。 No comment provided by engineer. @@ -3337,8 +3337,8 @@ This is your own one-time link! 文件和媒体 chat feature - - Files and media are prohibited in this group. + + Files and media are prohibited. 此群组中禁止文件和媒体。 No comment provided by engineer. @@ -3602,38 +3602,38 @@ Error: %2$@ 群组链接 No comment provided by engineer. - - Group members can add message reactions. + + Members can add message reactions. 群组成员可以添加信息回应。 No comment provided by engineer. - - Group members can irreversibly delete sent messages. (24 hours) + + Members can irreversibly delete sent messages. (24 hours) 群组成员可以不可撤回地删除已发送的消息 No comment provided by engineer. - - Group members can send SimpleX links. + + Members can send SimpleX links. 群成员可发送 SimpleX 链接。 No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. 群组成员可以私信。 No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. 群组成员可以发送限时消息。 No comment provided by engineer. - - Group members can send files and media. + + Members can send files and media. 群组成员可以发送文件和媒体。 No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. 群组成员可以发送语音消息。 No comment provided by engineer. @@ -4046,8 +4046,8 @@ More improvements are coming soon! 此聊天中禁止不可撤回消息移除。 No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. 此群组中禁止不可撤回消息移除。 No comment provided by engineer. @@ -4384,8 +4384,8 @@ This is your link for group %@! 该聊天禁用了消息回应。 No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. 该群组禁用了消息回应。 No comment provided by engineer. @@ -6580,8 +6580,8 @@ Enable in *Network & servers* settings. SimpleX 链接 chat feature - - SimpleX links are prohibited in this group. + + SimpleX links are prohibited. 此群禁止 SimpleX 链接。 No comment provided by engineer. @@ -7649,8 +7649,8 @@ To connect, please ask your contact to create another connection link and check 语音信息在此聊天中被禁止。 No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. 语音信息在该群组中被禁用。 No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff index da4f843974..93b9725131 100644 --- a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff @@ -1173,8 +1173,8 @@ 私訊 chat feature - - Direct messages between members are prohibited in this group. + + Direct messages between members are prohibited. 私訊群組內的成員於這個群組內是禁用的。 No comment provided by engineer. @@ -1193,8 +1193,8 @@ 自動銷毀訊息已被禁止於此聊天室。 No comment provided by engineer. - - Disappearing messages are prohibited in this group. + + Disappearing messages are prohibited. 自動銷毀訊息於這個群組內是禁用的。 No comment provided by engineer. @@ -1618,18 +1618,18 @@ 群組內的成員可以不可逆地刪除訊息。 No comment provided by engineer. - - Group members can send direct messages. + + Members can send direct messages. 群組內的成員可以私訊群組內的成員。 No comment provided by engineer. - - Group members can send disappearing messages. + + Members can send disappearing messages. 群組內的成員可以傳送自動銷毀的訊息。 No comment provided by engineer. - - Group members can send voice messages. + + Members can send voice messages. 群組內的成員可以傳送語音訊息。 No comment provided by engineer. @@ -1864,8 +1864,8 @@ 不可逆地刪除訊息於這個聊天室內是禁用的。 No comment provided by engineer. - - Irreversible message deletion is prohibited in this group. + + Irreversible message deletion is prohibited. 不可逆地刪除訊息於這個群組內是禁用的。 No comment provided by engineer. @@ -3316,8 +3316,8 @@ To connect, please ask your contact to create another connection link and check 語音訊息於這個聊天窒是禁用的。 No comment provided by engineer. - - Voice messages are prohibited in this group. + + Voice messages are prohibited. 語音訊息於這個群組內是禁用的。 No comment provided by engineer. @@ -5513,8 +5513,8 @@ It can happen because of some bug or when the connection is compromised.啟用自毀密碼 set passcode view - - Group members can add message reactions. + + Members can add message reactions. 群組內的成員可以新增訊息互動。 No comment provided by engineer. @@ -5689,8 +5689,8 @@ It can happen because of some bug or when the connection is compromised.已移除在 No comment provided by engineer. - - Message reactions are prohibited in this group. + + Message reactions are prohibited. 訊息互動於這個群組內是禁用的。 No comment provided by engineer. diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index b2532c1dc1..5379cce236 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -821,38 +821,38 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { switch self { case .timedMessages: switch enabled { - case .on: return "Group members can send disappearing messages." - case .off: return "Disappearing messages are prohibited in this group." + case .on: return "Members can send disappearing messages." + case .off: return "Disappearing messages are prohibited." } case .directMessages: switch enabled { - case .on: return "Group members can send direct messages." - case .off: return "Direct messages between members are prohibited in this group." + case .on: return "Members can send direct messages." + case .off: return "Direct messages between members are prohibited." } case .fullDelete: switch enabled { - case .on: return "Group members can irreversibly delete sent messages. (24 hours)" - case .off: return "Irreversible message deletion is prohibited in this group." + case .on: return "Members can irreversibly delete sent messages. (24 hours)" + case .off: return "Irreversible message deletion is prohibited." } case .reactions: switch enabled { - case .on: return "Group members can add message reactions." - case .off: return "Message reactions are prohibited in this group." + case .on: return "Members can add message reactions." + case .off: return "Message reactions are prohibited." } case .voice: switch enabled { - case .on: return "Group members can send voice messages." - case .off: return "Voice messages are prohibited in this group." + case .on: return "Members can send voice messages." + case .off: return "Voice messages are prohibited." } case .files: switch enabled { - case .on: return "Group members can send files and media." - case .off: return "Files and media are prohibited in this group." + case .on: return "Members can send files and media." + case .off: return "Files and media are prohibited." } case .simplexLinks: switch enabled { - case .on: return "Group members can send SimpleX links." - case .off: return "SimpleX links are prohibited in this group." + case .on: return "Members can send SimpleX links." + case .off: return "SimpleX links are prohibited." } case .history: switch enabled { diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index aa955d7a7a..a63ca87a99 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -1299,7 +1299,7 @@ "Direct messages" = "Лични съобщения"; /* No comment provided by engineer. */ -"Direct messages between members are prohibited in this group." = "Личните съобщения между членовете са забранени в тази група."; +"Direct messages between members are prohibited." = "Личните съобщения между членовете са забранени в тази група."; /* No comment provided by engineer. */ "Disable (keep overrides)" = "Деактивиране (запазване на промените)"; @@ -1323,7 +1323,7 @@ "Disappearing messages are prohibited in this chat." = "Изчезващите съобщения са забранени в този чат."; /* No comment provided by engineer. */ -"Disappearing messages are prohibited in this group." = "Изчезващите съобщения са забранени в тази група."; +"Disappearing messages are prohibited." = "Изчезващите съобщения са забранени в тази група."; /* No comment provided by engineer. */ "Disappears at" = "Изчезва в"; @@ -1786,7 +1786,7 @@ "Files and media" = "Файлове и медия"; /* No comment provided by engineer. */ -"Files and media are prohibited in this group." = "Файловете и медията са забранени в тази група."; +"Files and media are prohibited." = "Файловете и медията са забранени в тази група."; /* No comment provided by engineer. */ "Files and media not allowed" = "Файлове и медия не са разрешени"; @@ -1906,25 +1906,25 @@ "Group links" = "Групови линкове"; /* No comment provided by engineer. */ -"Group members can add message reactions." = "Членовете на групата могат да добавят реакции към съобщенията."; +"Members can add message reactions." = "Членовете на групата могат да добавят реакции към съобщенията."; /* No comment provided by engineer. */ -"Group members can irreversibly delete sent messages. (24 hours)" = "Членовете на групата могат необратимо да изтриват изпратените съобщения. (24 часа)"; +"Members can irreversibly delete sent messages. (24 hours)" = "Членовете на групата могат необратимо да изтриват изпратените съобщения. (24 часа)"; /* No comment provided by engineer. */ -"Group members can send direct messages." = "Членовете на групата могат да изпращат лични съобщения."; +"Members can send direct messages." = "Членовете на групата могат да изпращат лични съобщения."; /* No comment provided by engineer. */ -"Group members can send disappearing messages." = "Членовете на групата могат да изпращат изчезващи съобщения."; +"Members can send disappearing messages." = "Членовете на групата могат да изпращат изчезващи съобщения."; /* No comment provided by engineer. */ -"Group members can send files and media." = "Членовете на групата могат да изпращат файлове и медия."; +"Members can send files and media." = "Членовете на групата могат да изпращат файлове и медия."; /* No comment provided by engineer. */ -"Group members can send SimpleX links." = "Членовете на групата могат да изпращат SimpleX линкове."; +"Members can send SimpleX links." = "Членовете на групата могат да изпращат SimpleX линкове."; /* No comment provided by engineer. */ -"Group members can send voice messages." = "Членовете на групата могат да изпращат гласови съобщения."; +"Members can send voice messages." = "Членовете на групата могат да изпращат гласови съобщения."; /* notification */ "Group message:" = "Групово съобщение:"; @@ -2203,7 +2203,7 @@ "Irreversible message deletion is prohibited in this chat." = "Необратимото изтриване на съобщения е забранено в този чат."; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this group." = "Необратимото изтриване на съобщения е забранено в тази група."; +"Irreversible message deletion is prohibited." = "Необратимото изтриване на съобщения е забранено в тази група."; /* No comment provided by engineer. */ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "Позволява да имате много анонимни връзки без споделени данни между тях в един чат профил ."; @@ -2392,7 +2392,7 @@ "Message reactions are prohibited in this chat." = "Реакциите на съобщения са забранени в този чат."; /* No comment provided by engineer. */ -"Message reactions are prohibited in this group." = "Реакциите на съобщения са забранени в тази група."; +"Message reactions are prohibited." = "Реакциите на съобщения са забранени в тази група."; /* notification */ "message received" = "получено съобщение"; @@ -3450,7 +3450,7 @@ "SimpleX links" = "SimpleX линкове"; /* No comment provided by engineer. */ -"SimpleX links are prohibited in this group." = "SimpleX линкове са забранени в тази група."; +"SimpleX links are prohibited." = "SimpleX линкове са забранени в тази група."; /* No comment provided by engineer. */ "SimpleX links not allowed" = "SimpleX линковете не са разрешени"; @@ -3987,7 +3987,7 @@ "Voice messages are prohibited in this chat." = "Гласовите съобщения са забранени в този чат."; /* No comment provided by engineer. */ -"Voice messages are prohibited in this group." = "Гласовите съобщения са забранени в тази група."; +"Voice messages are prohibited." = "Гласовите съобщения са забранени в тази група."; /* No comment provided by engineer. */ "Voice messages not allowed" = "Гласовите съобщения не са разрешени"; diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index 9e96aafcfd..a150c2427f 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -1059,7 +1059,7 @@ "Direct messages" = "Přímé zprávy"; /* No comment provided by engineer. */ -"Direct messages between members are prohibited in this group." = "Přímé zprávy mezi členy jsou v této skupině zakázány."; +"Direct messages between members are prohibited." = "Přímé zprávy mezi členy jsou v této skupině zakázány."; /* No comment provided by engineer. */ "Disable (keep overrides)" = "Vypnout (zachovat přepsání)"; @@ -1083,7 +1083,7 @@ "Disappearing messages are prohibited in this chat." = "Mizící zprávy jsou v tomto chatu zakázány."; /* No comment provided by engineer. */ -"Disappearing messages are prohibited in this group." = "Mizící zprávy jsou v této skupině zakázány."; +"Disappearing messages are prohibited." = "Mizící zprávy jsou v této skupině zakázány."; /* No comment provided by engineer. */ "Disappears at" = "Zmizí v"; @@ -1461,7 +1461,7 @@ "Files and media" = "Soubory a média"; /* No comment provided by engineer. */ -"Files and media are prohibited in this group." = "Soubory a média jsou zakázány v této skupině."; +"Files and media are prohibited." = "Soubory a média jsou zakázány v této skupině."; /* No comment provided by engineer. */ "Files and media prohibited!" = "Soubory a média jsou zakázány!"; @@ -1545,22 +1545,22 @@ "Group links" = "Odkazy na skupiny"; /* No comment provided by engineer. */ -"Group members can add message reactions." = "Členové skupin mohou přidávat reakce na zprávy."; +"Members can add message reactions." = "Členové skupin mohou přidávat reakce na zprávy."; /* No comment provided by engineer. */ -"Group members can irreversibly delete sent messages. (24 hours)" = "Členové skupiny mohou nevratně mazat odeslané zprávy. (24 hodin)"; +"Members can irreversibly delete sent messages. (24 hours)" = "Členové skupiny mohou nevratně mazat odeslané zprávy. (24 hodin)"; /* No comment provided by engineer. */ -"Group members can send direct messages." = "Členové skupiny mohou posílat přímé zprávy."; +"Members can send direct messages." = "Členové skupiny mohou posílat přímé zprávy."; /* No comment provided by engineer. */ -"Group members can send disappearing messages." = "Členové skupiny mohou posílat mizící zprávy."; +"Members can send disappearing messages." = "Členové skupiny mohou posílat mizící zprávy."; /* No comment provided by engineer. */ -"Group members can send files and media." = "Členové skupiny mohou posílat soubory a média."; +"Members can send files and media." = "Členové skupiny mohou posílat soubory a média."; /* No comment provided by engineer. */ -"Group members can send voice messages." = "Členové skupiny mohou posílat hlasové zprávy."; +"Members can send voice messages." = "Členové skupiny mohou posílat hlasové zprávy."; /* notification */ "Group message:" = "Skupinová zpráva:"; @@ -1794,7 +1794,7 @@ "Irreversible message deletion is prohibited in this chat." = "Nevratné mazání zpráv je v tomto chatu zakázáno."; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this group." = "Nevratné mazání zpráv je v této skupině zakázáno."; +"Irreversible message deletion is prohibited." = "Nevratné mazání zpráv je v této skupině zakázáno."; /* No comment provided by engineer. */ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "Umožňuje mít v jednom profilu chatu mnoho anonymních spojení bez jakýchkoli sdílených údajů mezi nimi."; @@ -1950,7 +1950,7 @@ "Message reactions are prohibited in this chat." = "Reakce na zprávy jsou v tomto chatu zakázány."; /* No comment provided by engineer. */ -"Message reactions are prohibited in this group." = "Reakce na zprávy jsou v této skupině zakázány."; +"Message reactions are prohibited." = "Reakce na zprávy jsou v této skupině zakázány."; /* notification */ "message received" = "zpráva přijata"; @@ -3206,7 +3206,7 @@ "Voice messages are prohibited in this chat." = "Hlasové zprávy jsou v tomto chatu zakázány."; /* No comment provided by engineer. */ -"Voice messages are prohibited in this group." = "Hlasové zprávy jsou v této skupině zakázány."; +"Voice messages are prohibited." = "Hlasové zprávy jsou v této skupině zakázány."; /* No comment provided by engineer. */ "Voice messages prohibited!" = "Hlasové zprávy jsou zakázány!"; diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 1e92d094b4..6231000330 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -1656,7 +1656,7 @@ "Direct messages" = "Direkte Nachrichten"; /* No comment provided by engineer. */ -"Direct messages between members are prohibited in this group." = "In dieser Gruppe sind Direktnachrichten zwischen Mitgliedern nicht erlaubt."; +"Direct messages between members are prohibited." = "In dieser Gruppe sind Direktnachrichten zwischen Mitgliedern nicht erlaubt."; /* No comment provided by engineer. */ "Disable (keep overrides)" = "Deaktivieren (vorgenommene Einstellungen bleiben erhalten)"; @@ -1683,7 +1683,7 @@ "Disappearing messages are prohibited in this chat." = "In diesem Chat sind verschwindende Nachrichten nicht erlaubt."; /* No comment provided by engineer. */ -"Disappearing messages are prohibited in this group." = "In dieser Gruppe sind verschwindende Nachrichten nicht erlaubt."; +"Disappearing messages are prohibited." = "In dieser Gruppe sind verschwindende Nachrichten nicht erlaubt."; /* No comment provided by engineer. */ "Disappears at" = "Verschwindet um"; @@ -2254,7 +2254,7 @@ "Files and media" = "Dateien und Medien"; /* No comment provided by engineer. */ -"Files and media are prohibited in this group." = "In dieser Gruppe sind Dateien und Medien nicht erlaubt."; +"Files and media are prohibited." = "In dieser Gruppe sind Dateien und Medien nicht erlaubt."; /* No comment provided by engineer. */ "Files and media not allowed" = "Dateien und Medien sind nicht erlaubt"; @@ -2425,25 +2425,25 @@ "Group links" = "Gruppen-Links"; /* No comment provided by engineer. */ -"Group members can add message reactions." = "Gruppenmitglieder können eine Reaktion auf Nachrichten geben."; +"Members can add message reactions." = "Gruppenmitglieder können eine Reaktion auf Nachrichten geben."; /* No comment provided by engineer. */ -"Group members can irreversibly delete sent messages. (24 hours)" = "Gruppenmitglieder können gesendete Nachrichten unwiederbringlich löschen. (24 Stunden)"; +"Members can irreversibly delete sent messages. (24 hours)" = "Gruppenmitglieder können gesendete Nachrichten unwiederbringlich löschen. (24 Stunden)"; /* No comment provided by engineer. */ -"Group members can send direct messages." = "Gruppenmitglieder können Direktnachrichten versenden."; +"Members can send direct messages." = "Gruppenmitglieder können Direktnachrichten versenden."; /* No comment provided by engineer. */ -"Group members can send disappearing messages." = "Gruppenmitglieder können verschwindende Nachrichten senden."; +"Members can send disappearing messages." = "Gruppenmitglieder können verschwindende Nachrichten senden."; /* No comment provided by engineer. */ -"Group members can send files and media." = "Gruppenmitglieder können Dateien und Medien senden."; +"Members can send files and media." = "Gruppenmitglieder können Dateien und Medien senden."; /* No comment provided by engineer. */ -"Group members can send SimpleX links." = "Gruppenmitglieder können SimpleX-Links senden."; +"Members can send SimpleX links." = "Gruppenmitglieder können SimpleX-Links senden."; /* No comment provided by engineer. */ -"Group members can send voice messages." = "Gruppenmitglieder können Sprachnachrichten versenden."; +"Members can send voice messages." = "Gruppenmitglieder können Sprachnachrichten versenden."; /* notification */ "Group message:" = "Grppennachricht:"; @@ -2746,7 +2746,7 @@ "Irreversible message deletion is prohibited in this chat." = "In diesem Chat ist das unwiederbringliche Löschen von Nachrichten nicht erlaubt."; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this group." = "In dieser Gruppe ist das unwiederbringliche Löschen von Nachrichten nicht erlaubt."; +"Irreversible message deletion is prohibited." = "In dieser Gruppe ist das unwiederbringliche Löschen von Nachrichten nicht erlaubt."; /* No comment provided by engineer. */ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "Er ermöglicht mehrere anonyme Verbindungen in einem einzigen Chat-Profil ohne Daten zwischen diesen zu teilen."; @@ -2968,7 +2968,7 @@ "Message reactions are prohibited in this chat." = "In diesem Chat sind Reaktionen auf Nachrichten nicht erlaubt."; /* No comment provided by engineer. */ -"Message reactions are prohibited in this group." = "In dieser Gruppe sind Reaktionen auf Nachrichten nicht erlaubt."; +"Message reactions are prohibited." = "In dieser Gruppe sind Reaktionen auf Nachrichten nicht erlaubt."; /* notification */ "message received" = "Nachricht empfangen"; @@ -4416,7 +4416,7 @@ "SimpleX links" = "SimpleX-Links"; /* No comment provided by engineer. */ -"SimpleX links are prohibited in this group." = "In dieser Gruppe sind SimpleX-Links nicht erlaubt."; +"SimpleX links are prohibited." = "In dieser Gruppe sind SimpleX-Links nicht erlaubt."; /* No comment provided by engineer. */ "SimpleX links not allowed" = "SimpleX-Links sind nicht erlaubt"; @@ -5148,7 +5148,7 @@ "Voice messages are prohibited in this chat." = "In diesem Chat sind Sprachnachrichten nicht erlaubt."; /* No comment provided by engineer. */ -"Voice messages are prohibited in this group." = "In dieser Gruppe sind Sprachnachrichten nicht erlaubt."; +"Voice messages are prohibited." = "In dieser Gruppe sind Sprachnachrichten nicht erlaubt."; /* No comment provided by engineer. */ "Voice messages not allowed" = "Sprachnachrichten sind nicht erlaubt"; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index d02497515e..d15e6d75ce 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -1656,7 +1656,7 @@ "Direct messages" = "Mensajes directos"; /* No comment provided by engineer. */ -"Direct messages between members are prohibited in this group." = "Los mensajes directos entre miembros del grupo no están permitidos."; +"Direct messages between members are prohibited." = "Los mensajes directos entre miembros del grupo no están permitidos."; /* No comment provided by engineer. */ "Disable (keep overrides)" = "Desactivar (conservando anulaciones)"; @@ -1683,7 +1683,7 @@ "Disappearing messages are prohibited in this chat." = "Los mensajes temporales no están permitidos en este chat."; /* No comment provided by engineer. */ -"Disappearing messages are prohibited in this group." = "Los mensajes temporales no están permitidos en este grupo."; +"Disappearing messages are prohibited." = "Los mensajes temporales no están permitidos en este grupo."; /* No comment provided by engineer. */ "Disappears at" = "Desaparecerá"; @@ -2254,7 +2254,7 @@ "Files and media" = "Archivos y multimedia"; /* No comment provided by engineer. */ -"Files and media are prohibited in this group." = "Los archivos y multimedia no están permitidos en este grupo."; +"Files and media are prohibited." = "Los archivos y multimedia no están permitidos en este grupo."; /* No comment provided by engineer. */ "Files and media not allowed" = "Archivos y multimedia no permitidos"; @@ -2425,25 +2425,25 @@ "Group links" = "Enlaces de grupo"; /* No comment provided by engineer. */ -"Group members can add message reactions." = "Los miembros pueden añadir reacciones a los mensajes."; +"Members can add message reactions." = "Los miembros pueden añadir reacciones a los mensajes."; /* No comment provided by engineer. */ -"Group members can irreversibly delete sent messages. (24 hours)" = "Los miembros del grupo pueden eliminar mensajes de forma irreversible. (24 horas)"; +"Members can irreversibly delete sent messages. (24 hours)" = "Los miembros del grupo pueden eliminar mensajes de forma irreversible. (24 horas)"; /* No comment provided by engineer. */ -"Group members can send direct messages." = "Los miembros del grupo pueden enviar mensajes directos."; +"Members can send direct messages." = "Los miembros del grupo pueden enviar mensajes directos."; /* No comment provided by engineer. */ -"Group members can send disappearing messages." = "Los miembros del grupo pueden enviar mensajes temporales."; +"Members can send disappearing messages." = "Los miembros del grupo pueden enviar mensajes temporales."; /* No comment provided by engineer. */ -"Group members can send files and media." = "Los miembros del grupo pueden enviar archivos y multimedia."; +"Members can send files and media." = "Los miembros del grupo pueden enviar archivos y multimedia."; /* No comment provided by engineer. */ -"Group members can send SimpleX links." = "Los miembros del grupo pueden enviar enlaces SimpleX."; +"Members can send SimpleX links." = "Los miembros del grupo pueden enviar enlaces SimpleX."; /* No comment provided by engineer. */ -"Group members can send voice messages." = "Los miembros del grupo pueden enviar mensajes de voz."; +"Members can send voice messages." = "Los miembros del grupo pueden enviar mensajes de voz."; /* notification */ "Group message:" = "Mensaje de grupo:"; @@ -2746,7 +2746,7 @@ "Irreversible message deletion is prohibited in this chat." = "La eliminación irreversible de mensajes no está permitida en este chat."; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this group." = "La eliminación irreversible de mensajes no está permitida en este grupo."; +"Irreversible message deletion is prohibited." = "La eliminación irreversible de mensajes no está permitida en este grupo."; /* No comment provided by engineer. */ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "Permite tener varias conexiones anónimas sin datos compartidos entre estas dentro del mismo perfil."; @@ -2968,7 +2968,7 @@ "Message reactions are prohibited in this chat." = "Las reacciones a los mensajes no están permitidas en este chat."; /* No comment provided by engineer. */ -"Message reactions are prohibited in this group." = "Las reacciones a los mensajes no están permitidas en este grupo."; +"Message reactions are prohibited." = "Las reacciones a los mensajes no están permitidas en este grupo."; /* notification */ "message received" = "mensaje recibido"; @@ -4416,7 +4416,7 @@ "SimpleX links" = "Enlaces SimpleX"; /* No comment provided by engineer. */ -"SimpleX links are prohibited in this group." = "Los enlaces SimpleX no se permiten en este grupo."; +"SimpleX links are prohibited." = "Los enlaces SimpleX no se permiten en este grupo."; /* No comment provided by engineer. */ "SimpleX links not allowed" = "Enlaces SimpleX no permitidos"; @@ -5148,7 +5148,7 @@ "Voice messages are prohibited in this chat." = "Los mensajes de voz no están permitidos en este chat."; /* No comment provided by engineer. */ -"Voice messages are prohibited in this group." = "Los mensajes de voz no están permitidos en este grupo."; +"Voice messages are prohibited." = "Los mensajes de voz no están permitidos en este grupo."; /* No comment provided by engineer. */ "Voice messages not allowed" = "Mensajes de voz no permitidos"; diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index 6f28ddd3b0..8946be02b4 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -1041,7 +1041,7 @@ "Direct messages" = "Yksityisviestit"; /* No comment provided by engineer. */ -"Direct messages between members are prohibited in this group." = "Yksityisviestit jäsenten välillä ovat kiellettyjä tässä ryhmässä."; +"Direct messages between members are prohibited." = "Yksityisviestit jäsenten välillä ovat kiellettyjä tässä ryhmässä."; /* No comment provided by engineer. */ "Disable (keep overrides)" = "Poista käytöstä (pidä ohitukset)"; @@ -1065,7 +1065,7 @@ "Disappearing messages are prohibited in this chat." = "Katoavat viestit ovat kiellettyjä tässä keskustelussa."; /* No comment provided by engineer. */ -"Disappearing messages are prohibited in this group." = "Katoavat viestit ovat kiellettyjä tässä ryhmässä."; +"Disappearing messages are prohibited." = "Katoavat viestit ovat kiellettyjä tässä ryhmässä."; /* No comment provided by engineer. */ "Disappears at" = "Katoaa klo"; @@ -1437,7 +1437,7 @@ "Files and media" = "Tiedostot ja media"; /* No comment provided by engineer. */ -"Files and media are prohibited in this group." = "Tiedostot ja media ovat tässä ryhmässä kiellettyjä."; +"Files and media are prohibited." = "Tiedostot ja media ovat tässä ryhmässä kiellettyjä."; /* No comment provided by engineer. */ "Files and media prohibited!" = "Tiedostot ja media kielletty!"; @@ -1521,22 +1521,22 @@ "Group links" = "Ryhmälinkit"; /* No comment provided by engineer. */ -"Group members can add message reactions." = "Ryhmän jäsenet voivat lisätä viestireaktioita."; +"Members can add message reactions." = "Ryhmän jäsenet voivat lisätä viestireaktioita."; /* No comment provided by engineer. */ -"Group members can irreversibly delete sent messages. (24 hours)" = "Ryhmän jäsenet voivat poistaa lähetetyt viestit peruuttamattomasti. (24 tuntia)"; +"Members can irreversibly delete sent messages. (24 hours)" = "Ryhmän jäsenet voivat poistaa lähetetyt viestit peruuttamattomasti. (24 tuntia)"; /* No comment provided by engineer. */ -"Group members can send direct messages." = "Ryhmän jäsenet voivat lähettää suoraviestejä."; +"Members can send direct messages." = "Ryhmän jäsenet voivat lähettää suoraviestejä."; /* No comment provided by engineer. */ -"Group members can send disappearing messages." = "Ryhmän jäsenet voivat lähettää katoavia viestejä."; +"Members can send disappearing messages." = "Ryhmän jäsenet voivat lähettää katoavia viestejä."; /* No comment provided by engineer. */ -"Group members can send files and media." = "Ryhmän jäsenet voivat lähettää tiedostoja ja mediaa."; +"Members can send files and media." = "Ryhmän jäsenet voivat lähettää tiedostoja ja mediaa."; /* No comment provided by engineer. */ -"Group members can send voice messages." = "Ryhmän jäsenet voivat lähettää ääniviestejä."; +"Members can send voice messages." = "Ryhmän jäsenet voivat lähettää ääniviestejä."; /* notification */ "Group message:" = "Ryhmäviesti:"; @@ -1770,7 +1770,7 @@ "Irreversible message deletion is prohibited in this chat." = "Viestien peruuttamaton poisto on kielletty tässä keskustelussa."; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this group." = "Viestien peruuttamaton poisto on kielletty tässä ryhmässä."; +"Irreversible message deletion is prohibited." = "Viestien peruuttamaton poisto on kielletty tässä ryhmässä."; /* No comment provided by engineer. */ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "Se mahdollistaa useiden nimettömien yhteyksien muodostamisen yhdessä keskusteluprofiilissa ilman, että niiden välillä on jaettuja tietoja."; @@ -1926,7 +1926,7 @@ "Message reactions are prohibited in this chat." = "Viestireaktiot ovat kiellettyjä tässä keskustelussa."; /* No comment provided by engineer. */ -"Message reactions are prohibited in this group." = "Viestireaktiot ovat kiellettyjä tässä ryhmässä."; +"Message reactions are prohibited." = "Viestireaktiot ovat kiellettyjä tässä ryhmässä."; /* notification */ "message received" = "viesti vastaanotettu"; @@ -3164,7 +3164,7 @@ "Voice messages are prohibited in this chat." = "Ääniviestit ovat kiellettyjä tässä keskustelussa."; /* No comment provided by engineer. */ -"Voice messages are prohibited in this group." = "Ääniviestit ovat kiellettyjä tässä ryhmässä."; +"Voice messages are prohibited." = "Ääniviestit ovat kiellettyjä tässä ryhmässä."; /* No comment provided by engineer. */ "Voice messages prohibited!" = "Ääniviestit kielletty!"; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index 273fb76d6e..f1a8e97758 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -1572,7 +1572,7 @@ "Direct messages" = "Messages directs"; /* No comment provided by engineer. */ -"Direct messages between members are prohibited in this group." = "Les messages directs entre membres sont interdits dans ce groupe."; +"Direct messages between members are prohibited." = "Les messages directs entre membres sont interdits dans ce groupe."; /* No comment provided by engineer. */ "Disable (keep overrides)" = "Désactiver (conserver les remplacements)"; @@ -1599,7 +1599,7 @@ "Disappearing messages are prohibited in this chat." = "Les messages éphémères sont interdits dans cette discussion."; /* No comment provided by engineer. */ -"Disappearing messages are prohibited in this group." = "Les messages éphémères sont interdits dans ce groupe."; +"Disappearing messages are prohibited." = "Les messages éphémères sont interdits dans ce groupe."; /* No comment provided by engineer. */ "Disappears at" = "Disparaîtra le"; @@ -2146,7 +2146,7 @@ "Files and media" = "Fichiers et médias"; /* No comment provided by engineer. */ -"Files and media are prohibited in this group." = "Les fichiers et les médias sont interdits dans ce groupe."; +"Files and media are prohibited." = "Les fichiers et les médias sont interdits dans ce groupe."; /* No comment provided by engineer. */ "Files and media not allowed" = "Fichiers et médias non autorisés"; @@ -2302,25 +2302,25 @@ "Group links" = "Liens de groupe"; /* No comment provided by engineer. */ -"Group members can add message reactions." = "Les membres du groupe peuvent ajouter des réactions aux messages."; +"Members can add message reactions." = "Les membres du groupe peuvent ajouter des réactions aux messages."; /* No comment provided by engineer. */ -"Group members can irreversibly delete sent messages. (24 hours)" = "Les membres du groupe peuvent supprimer de manière irréversible les messages envoyés. (24 heures)"; +"Members can irreversibly delete sent messages. (24 hours)" = "Les membres du groupe peuvent supprimer de manière irréversible les messages envoyés. (24 heures)"; /* No comment provided by engineer. */ -"Group members can send direct messages." = "Les membres du groupe peuvent envoyer des messages directs."; +"Members can send direct messages." = "Les membres du groupe peuvent envoyer des messages directs."; /* No comment provided by engineer. */ -"Group members can send disappearing messages." = "Les membres du groupes peuvent envoyer des messages éphémères."; +"Members can send disappearing messages." = "Les membres du groupes peuvent envoyer des messages éphémères."; /* No comment provided by engineer. */ -"Group members can send files and media." = "Les membres du groupe peuvent envoyer des fichiers et des médias."; +"Members can send files and media." = "Les membres du groupe peuvent envoyer des fichiers et des médias."; /* No comment provided by engineer. */ -"Group members can send SimpleX links." = "Les membres du groupe peuvent envoyer des liens SimpleX."; +"Members can send SimpleX links." = "Les membres du groupe peuvent envoyer des liens SimpleX."; /* No comment provided by engineer. */ -"Group members can send voice messages." = "Les membres du groupe peuvent envoyer des messages vocaux."; +"Members can send voice messages." = "Les membres du groupe peuvent envoyer des messages vocaux."; /* notification */ "Group message:" = "Message du groupe :"; @@ -2617,7 +2617,7 @@ "Irreversible message deletion is prohibited in this chat." = "La suppression irréversible de message est interdite dans ce chat."; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this group." = "La suppression irréversible de messages est interdite dans ce groupe."; +"Irreversible message deletion is prohibited." = "La suppression irréversible de messages est interdite dans ce groupe."; /* No comment provided by engineer. */ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "Cela permet d'avoir plusieurs connections anonymes sans aucune données partagées entre elles sur un même profil."; @@ -2839,7 +2839,7 @@ "Message reactions are prohibited in this chat." = "Les réactions aux messages sont interdites dans ce chat."; /* No comment provided by engineer. */ -"Message reactions are prohibited in this group." = "Les réactions aux messages sont interdites dans ce groupe."; +"Message reactions are prohibited." = "Les réactions aux messages sont interdites dans ce groupe."; /* notification */ "message received" = "message reçu"; @@ -4191,7 +4191,7 @@ "SimpleX links" = "Liens SimpleX"; /* No comment provided by engineer. */ -"SimpleX links are prohibited in this group." = "Les liens SimpleX sont interdits dans ce groupe."; +"SimpleX links are prohibited." = "Les liens SimpleX sont interdits dans ce groupe."; /* No comment provided by engineer. */ "SimpleX links not allowed" = "Les liens SimpleX ne sont pas autorisés"; @@ -4872,7 +4872,7 @@ "Voice messages are prohibited in this chat." = "Les messages vocaux sont interdits dans ce chat."; /* No comment provided by engineer. */ -"Voice messages are prohibited in this group." = "Les messages vocaux sont interdits dans ce groupe."; +"Voice messages are prohibited." = "Les messages vocaux sont interdits dans ce groupe."; /* No comment provided by engineer. */ "Voice messages not allowed" = "Les messages vocaux ne sont pas autorisés"; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index 9103f4baf3..c1008ad30b 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -1656,7 +1656,7 @@ "Direct messages" = "Közvetlen üzenetek"; /* No comment provided by engineer. */ -"Direct messages between members are prohibited in this group." = "A közvetlen üzenetek küldése a tagok között le van tiltva ebben a csoportban."; +"Direct messages between members are prohibited." = "A közvetlen üzenetek küldése a tagok között le van tiltva ebben a csoportban."; /* No comment provided by engineer. */ "Disable (keep overrides)" = "Letiltás (felülírások megtartásával)"; @@ -1683,7 +1683,7 @@ "Disappearing messages are prohibited in this chat." = "Az eltűnő üzenetek küldése le van tiltva ebben a csevegésben."; /* No comment provided by engineer. */ -"Disappearing messages are prohibited in this group." = "Az eltűnő üzenetek küldése le van tiltva ebben a csoportban."; +"Disappearing messages are prohibited." = "Az eltűnő üzenetek küldése le van tiltva ebben a csoportban."; /* No comment provided by engineer. */ "Disappears at" = "Eltűnik ekkor:"; @@ -2254,7 +2254,7 @@ "Files and media" = "Fájlok és médiatartalmak"; /* No comment provided by engineer. */ -"Files and media are prohibited in this group." = "A fájlok- és a médiatartalmak le vannak tiltva ebben a csoportban."; +"Files and media are prohibited." = "A fájlok- és a médiatartalmak le vannak tiltva ebben a csoportban."; /* No comment provided by engineer. */ "Files and media not allowed" = "A fájlok- és médiatartalmak nincsenek engedélyezve"; @@ -2425,25 +2425,25 @@ "Group links" = "Csoporthivatkozások"; /* No comment provided by engineer. */ -"Group members can add message reactions." = "Csoporttagok üzenetreakciókat adhatnak hozzá."; +"Members can add message reactions." = "Csoporttagok üzenetreakciókat adhatnak hozzá."; /* No comment provided by engineer. */ -"Group members can irreversibly delete sent messages. (24 hours)" = "A csoport tagjai véglegesen törölhetik az elküldött üzeneteiket. (24 óra)"; +"Members can irreversibly delete sent messages. (24 hours)" = "A csoport tagjai véglegesen törölhetik az elküldött üzeneteiket. (24 óra)"; /* No comment provided by engineer. */ -"Group members can send direct messages." = "A csoport tagjai küldhetnek egymásnak közvetlen üzeneteket."; +"Members can send direct messages." = "A csoport tagjai küldhetnek egymásnak közvetlen üzeneteket."; /* No comment provided by engineer. */ -"Group members can send disappearing messages." = "A csoport tagjai küldhetnek eltűnő üzeneteket."; +"Members can send disappearing messages." = "A csoport tagjai küldhetnek eltűnő üzeneteket."; /* No comment provided by engineer. */ -"Group members can send files and media." = "A csoport tagjai küldhetnek fájlokat és médiatartalmakat."; +"Members can send files and media." = "A csoport tagjai küldhetnek fájlokat és médiatartalmakat."; /* No comment provided by engineer. */ -"Group members can send SimpleX links." = "A csoport tagjai küldhetnek SimpleX-hivatkozásokat."; +"Members can send SimpleX links." = "A csoport tagjai küldhetnek SimpleX-hivatkozásokat."; /* No comment provided by engineer. */ -"Group members can send voice messages." = "A csoport tagjai küldhetnek hangüzeneteket."; +"Members can send voice messages." = "A csoport tagjai küldhetnek hangüzeneteket."; /* notification */ "Group message:" = "Csoport üzenet:"; @@ -2746,7 +2746,7 @@ "Irreversible message deletion is prohibited in this chat." = "Az üzenetek végleges törlése le van tiltva ebben a csevegésben."; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this group." = "Az üzenetek végleges törlése le van tiltva ebben a csoportban."; +"Irreversible message deletion is prohibited." = "Az üzenetek végleges törlése le van tiltva ebben a csoportban."; /* No comment provided by engineer. */ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "Lehetővé teszi, hogy egyetlen csevegőprofilon belül több névtelen kapcsolat legyen, anélkül, hogy megosztott adatok lennének közöttük."; @@ -2968,7 +2968,7 @@ "Message reactions are prohibited in this chat." = "Az üzenetreakciók küldése le van tiltva ebben a csevegésben."; /* No comment provided by engineer. */ -"Message reactions are prohibited in this group." = "Az üzenetreakciók küldése le van tiltva ebben a csoportban."; +"Message reactions are prohibited." = "Az üzenetreakciók küldése le van tiltva ebben a csoportban."; /* notification */ "message received" = "üzenet érkezett"; @@ -4416,7 +4416,7 @@ "SimpleX links" = "SimpleX-hivatkozások"; /* No comment provided by engineer. */ -"SimpleX links are prohibited in this group." = "A SimpleX-hivatkozások küldése le van tiltva ebben a csoportban."; +"SimpleX links are prohibited." = "A SimpleX-hivatkozások küldése le van tiltva ebben a csoportban."; /* No comment provided by engineer. */ "SimpleX links not allowed" = "A SimpleX-hivatkozások küldése le van tiltva"; @@ -5148,7 +5148,7 @@ "Voice messages are prohibited in this chat." = "A hangüzenetek küldése le van tiltva ebben a csevegésben."; /* No comment provided by engineer. */ -"Voice messages are prohibited in this group." = "A hangüzenetek küldése le van tiltva ebben a csoportban."; +"Voice messages are prohibited." = "A hangüzenetek küldése le van tiltva ebben a csoportban."; /* No comment provided by engineer. */ "Voice messages not allowed" = "A hangüzenetek küldése le van tiltva"; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index 9abd660f0c..42f399b710 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -1656,7 +1656,7 @@ "Direct messages" = "Messaggi diretti"; /* No comment provided by engineer. */ -"Direct messages between members are prohibited in this group." = "I messaggi diretti tra i membri sono vietati in questo gruppo."; +"Direct messages between members are prohibited." = "I messaggi diretti tra i membri sono vietati in questo gruppo."; /* No comment provided by engineer. */ "Disable (keep overrides)" = "Disattiva (mantieni sostituzioni)"; @@ -1683,7 +1683,7 @@ "Disappearing messages are prohibited in this chat." = "I messaggi a tempo sono vietati in questa chat."; /* No comment provided by engineer. */ -"Disappearing messages are prohibited in this group." = "I messaggi a tempo sono vietati in questo gruppo."; +"Disappearing messages are prohibited." = "I messaggi a tempo sono vietati in questo gruppo."; /* No comment provided by engineer. */ "Disappears at" = "Scompare il"; @@ -2254,7 +2254,7 @@ "Files and media" = "File e multimediali"; /* No comment provided by engineer. */ -"Files and media are prohibited in this group." = "File e contenuti multimediali sono vietati in questo gruppo."; +"Files and media are prohibited." = "File e contenuti multimediali sono vietati in questo gruppo."; /* No comment provided by engineer. */ "Files and media not allowed" = "File e multimediali non consentiti"; @@ -2425,25 +2425,25 @@ "Group links" = "Link del gruppo"; /* No comment provided by engineer. */ -"Group members can add message reactions." = "I membri del gruppo possono aggiungere reazioni ai messaggi."; +"Members can add message reactions." = "I membri del gruppo possono aggiungere reazioni ai messaggi."; /* No comment provided by engineer. */ -"Group members can irreversibly delete sent messages. (24 hours)" = "I membri del gruppo possono eliminare irreversibilmente i messaggi inviati. (24 ore)"; +"Members can irreversibly delete sent messages. (24 hours)" = "I membri del gruppo possono eliminare irreversibilmente i messaggi inviati. (24 ore)"; /* No comment provided by engineer. */ -"Group members can send direct messages." = "I membri del gruppo possono inviare messaggi diretti."; +"Members can send direct messages." = "I membri del gruppo possono inviare messaggi diretti."; /* No comment provided by engineer. */ -"Group members can send disappearing messages." = "I membri del gruppo possono inviare messaggi a tempo."; +"Members can send disappearing messages." = "I membri del gruppo possono inviare messaggi a tempo."; /* No comment provided by engineer. */ -"Group members can send files and media." = "I membri del gruppo possono inviare file e contenuti multimediali."; +"Members can send files and media." = "I membri del gruppo possono inviare file e contenuti multimediali."; /* No comment provided by engineer. */ -"Group members can send SimpleX links." = "I membri del gruppo possono inviare link di Simplex."; +"Members can send SimpleX links." = "I membri del gruppo possono inviare link di Simplex."; /* No comment provided by engineer. */ -"Group members can send voice messages." = "I membri del gruppo possono inviare messaggi vocali."; +"Members can send voice messages." = "I membri del gruppo possono inviare messaggi vocali."; /* notification */ "Group message:" = "Messaggio del gruppo:"; @@ -2746,7 +2746,7 @@ "Irreversible message deletion is prohibited in this chat." = "L'eliminazione irreversibile dei messaggi è vietata in questa chat."; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this group." = "L'eliminazione irreversibile dei messaggi è vietata in questo gruppo."; +"Irreversible message deletion is prohibited." = "L'eliminazione irreversibile dei messaggi è vietata in questo gruppo."; /* No comment provided by engineer. */ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "Permette di avere molte connessioni anonime senza dati condivisi tra di loro in un unico profilo di chat."; @@ -2968,7 +2968,7 @@ "Message reactions are prohibited in this chat." = "Le reazioni ai messaggi sono vietate in questa chat."; /* No comment provided by engineer. */ -"Message reactions are prohibited in this group." = "Le reazioni ai messaggi sono vietate in questo gruppo."; +"Message reactions are prohibited." = "Le reazioni ai messaggi sono vietate in questo gruppo."; /* notification */ "message received" = "messaggio ricevuto"; @@ -4416,7 +4416,7 @@ "SimpleX links" = "Link di SimpleX"; /* No comment provided by engineer. */ -"SimpleX links are prohibited in this group." = "I link di SimpleX sono vietati in questo gruppo."; +"SimpleX links are prohibited." = "I link di SimpleX sono vietati in questo gruppo."; /* No comment provided by engineer. */ "SimpleX links not allowed" = "Link di SimpleX non consentiti"; @@ -5148,7 +5148,7 @@ "Voice messages are prohibited in this chat." = "I messaggi vocali sono vietati in questa chat."; /* No comment provided by engineer. */ -"Voice messages are prohibited in this group." = "I messaggi vocali sono vietati in questo gruppo."; +"Voice messages are prohibited." = "I messaggi vocali sono vietati in questo gruppo."; /* No comment provided by engineer. */ "Voice messages not allowed" = "Messaggi vocali non consentiti"; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index 5bcd706702..019472b804 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -1179,7 +1179,7 @@ "Direct messages" = "ダイレクトメッセージ"; /* No comment provided by engineer. */ -"Direct messages between members are prohibited in this group." = "このグループではメンバー間のダイレクトメッセージが使用禁止です。"; +"Direct messages between members are prohibited." = "このグループではメンバー間のダイレクトメッセージが使用禁止です。"; /* No comment provided by engineer. */ "Disable (keep overrides)" = "無効にする(設定の優先を維持)"; @@ -1203,7 +1203,7 @@ "Disappearing messages are prohibited in this chat." = "このチャットでは消えるメッセージが使用禁止です。"; /* No comment provided by engineer. */ -"Disappearing messages are prohibited in this group." = "このグループでは消えるメッセージが使用禁止です。"; +"Disappearing messages are prohibited." = "このグループでは消えるメッセージが使用禁止です。"; /* No comment provided by engineer. */ "Disappears at" = "に消えます"; @@ -1578,7 +1578,7 @@ "Files and media" = "ファイルとメディア"; /* No comment provided by engineer. */ -"Files and media are prohibited in this group." = "このグループでは、ファイルとメディアは禁止されています。"; +"Files and media are prohibited." = "このグループでは、ファイルとメディアは禁止されています。"; /* No comment provided by engineer. */ "Files and media prohibited!" = "ファイルとメディアは禁止されています!"; @@ -1662,22 +1662,22 @@ "Group links" = "グループのリンク"; /* No comment provided by engineer. */ -"Group members can add message reactions." = "グループメンバーはメッセージへのリアクションを追加できます。"; +"Members can add message reactions." = "グループメンバーはメッセージへのリアクションを追加できます。"; /* No comment provided by engineer. */ -"Group members can irreversibly delete sent messages. (24 hours)" = "グループのメンバーがメッセージを完全削除することができます。(24時間)"; +"Members can irreversibly delete sent messages. (24 hours)" = "グループのメンバーがメッセージを完全削除することができます。(24時間)"; /* No comment provided by engineer. */ -"Group members can send direct messages." = "グループのメンバーがダイレクトメッセージを送信できます。"; +"Members can send direct messages." = "グループのメンバーがダイレクトメッセージを送信できます。"; /* No comment provided by engineer. */ -"Group members can send disappearing messages." = "グループのメンバーが消えるメッセージを送信できます。"; +"Members can send disappearing messages." = "グループのメンバーが消えるメッセージを送信できます。"; /* No comment provided by engineer. */ -"Group members can send files and media." = "グループメンバーはファイルやメディアを送信できます。"; +"Members can send files and media." = "グループメンバーはファイルやメディアを送信できます。"; /* No comment provided by engineer. */ -"Group members can send voice messages." = "グループのメンバーが音声メッセージを送信できます。"; +"Members can send voice messages." = "グループのメンバーが音声メッセージを送信できます。"; /* notification */ "Group message:" = "グループメッセージ:"; @@ -1911,7 +1911,7 @@ "Irreversible message deletion is prohibited in this chat." = "このチャットではメッセージの完全削除が使用禁止です。"; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this group." = "このグループではメッセージの完全削除が使用禁止です。"; +"Irreversible message deletion is prohibited." = "このグループではメッセージの完全削除が使用禁止です。"; /* No comment provided by engineer. */ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "これにより単一のチャット プロファイル内で、データを共有せずに多数の匿名の接続をすることができます。"; @@ -2064,7 +2064,7 @@ "Message reactions are prohibited in this chat." = "このチャットではメッセージへのリアクションは禁止されています。"; /* No comment provided by engineer. */ -"Message reactions are prohibited in this group." = "このグループではメッセージへのリアクションは禁止されています。"; +"Message reactions are prohibited." = "このグループではメッセージへのリアクションは禁止されています。"; /* notification */ "message received" = "メッセージを受信"; @@ -3290,7 +3290,7 @@ "Voice messages are prohibited in this chat." = "このチャットでは音声メッセージが使用禁止です。"; /* No comment provided by engineer. */ -"Voice messages are prohibited in this group." = "このグループでは音声メッセージが使用禁止です。"; +"Voice messages are prohibited." = "このグループでは音声メッセージが使用禁止です。"; /* No comment provided by engineer. */ "Voice messages prohibited!" = "音声メッセージは使用禁止です!"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index fadec1b09b..652ccdf63c 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -1656,7 +1656,7 @@ "Direct messages" = "Directe berichten"; /* No comment provided by engineer. */ -"Direct messages between members are prohibited in this group." = "Directe berichten tussen leden zijn verboden in deze groep."; +"Direct messages between members are prohibited." = "Directe berichten tussen leden zijn verboden in deze groep."; /* No comment provided by engineer. */ "Disable (keep overrides)" = "Uitschakelen (overschrijvingen behouden)"; @@ -1683,7 +1683,7 @@ "Disappearing messages are prohibited in this chat." = "Verdwijnende berichten zijn verboden in dit gesprek."; /* No comment provided by engineer. */ -"Disappearing messages are prohibited in this group." = "Verdwijnende berichten zijn verboden in deze groep."; +"Disappearing messages are prohibited." = "Verdwijnende berichten zijn verboden in deze groep."; /* No comment provided by engineer. */ "Disappears at" = "Verdwijnt op"; @@ -2254,7 +2254,7 @@ "Files and media" = "Bestanden en media"; /* No comment provided by engineer. */ -"Files and media are prohibited in this group." = "Bestanden en media zijn verboden in deze groep."; +"Files and media are prohibited." = "Bestanden en media zijn verboden in deze groep."; /* No comment provided by engineer. */ "Files and media not allowed" = "Bestanden en media niet toegestaan"; @@ -2425,25 +2425,25 @@ "Group links" = "Groep links"; /* No comment provided by engineer. */ -"Group members can add message reactions." = "Groepsleden kunnen bericht reacties toevoegen."; +"Members can add message reactions." = "Groepsleden kunnen bericht reacties toevoegen."; /* No comment provided by engineer. */ -"Group members can irreversibly delete sent messages. (24 hours)" = "Groepsleden kunnen verzonden berichten onherroepelijk verwijderen. (24 uur)"; +"Members can irreversibly delete sent messages. (24 hours)" = "Groepsleden kunnen verzonden berichten onherroepelijk verwijderen. (24 uur)"; /* No comment provided by engineer. */ -"Group members can send direct messages." = "Groepsleden kunnen directe berichten sturen."; +"Members can send direct messages." = "Groepsleden kunnen directe berichten sturen."; /* No comment provided by engineer. */ -"Group members can send disappearing messages." = "Groepsleden kunnen verdwijnende berichten sturen."; +"Members can send disappearing messages." = "Groepsleden kunnen verdwijnende berichten sturen."; /* No comment provided by engineer. */ -"Group members can send files and media." = "Groepsleden kunnen bestanden en media verzenden."; +"Members can send files and media." = "Groepsleden kunnen bestanden en media verzenden."; /* No comment provided by engineer. */ -"Group members can send SimpleX links." = "Groepsleden kunnen SimpleX-links verzenden."; +"Members can send SimpleX links." = "Groepsleden kunnen SimpleX-links verzenden."; /* No comment provided by engineer. */ -"Group members can send voice messages." = "Groepsleden kunnen spraak berichten verzenden."; +"Members can send voice messages." = "Groepsleden kunnen spraak berichten verzenden."; /* notification */ "Group message:" = "Groep bericht:"; @@ -2746,7 +2746,7 @@ "Irreversible message deletion is prohibited in this chat." = "Het onomkeerbaar verwijderen van berichten is verboden in dit gesprek."; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this group." = "Het onomkeerbaar verwijderen van berichten is verboden in deze groep."; +"Irreversible message deletion is prohibited." = "Het onomkeerbaar verwijderen van berichten is verboden in deze groep."; /* No comment provided by engineer. */ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "Het maakt het mogelijk om veel anonieme verbindingen te hebben zonder enige gedeelde gegevens tussen hen in een enkel chatprofiel."; @@ -2968,7 +2968,7 @@ "Message reactions are prohibited in this chat." = "Reacties op berichten zijn verboden in deze chat."; /* No comment provided by engineer. */ -"Message reactions are prohibited in this group." = "Reacties op berichten zijn verboden in deze groep."; +"Message reactions are prohibited." = "Reacties op berichten zijn verboden in deze groep."; /* notification */ "message received" = "bericht ontvangen"; @@ -4416,7 +4416,7 @@ "SimpleX links" = "SimpleX links"; /* No comment provided by engineer. */ -"SimpleX links are prohibited in this group." = "SimpleX-links zijn in deze groep verboden."; +"SimpleX links are prohibited." = "SimpleX-links zijn in deze groep verboden."; /* No comment provided by engineer. */ "SimpleX links not allowed" = "SimpleX-links zijn niet toegestaan"; @@ -5148,7 +5148,7 @@ "Voice messages are prohibited in this chat." = "Spraak berichten zijn verboden in deze chat."; /* No comment provided by engineer. */ -"Voice messages are prohibited in this group." = "Spraak berichten zijn verboden in deze groep."; +"Voice messages are prohibited." = "Spraak berichten zijn verboden in deze groep."; /* No comment provided by engineer. */ "Voice messages not allowed" = "Spraakberichten niet toegestaan"; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index 6650b1d8c8..04279064ee 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -1551,7 +1551,7 @@ "Direct messages" = "Bezpośrednie wiadomości"; /* No comment provided by engineer. */ -"Direct messages between members are prohibited in this group." = "Bezpośrednie wiadomości między członkami są zabronione w tej grupie."; +"Direct messages between members are prohibited." = "Bezpośrednie wiadomości między członkami są zabronione w tej grupie."; /* No comment provided by engineer. */ "Disable (keep overrides)" = "Wyłącz (zachowaj nadpisania)"; @@ -1578,7 +1578,7 @@ "Disappearing messages are prohibited in this chat." = "Znikające wiadomości są zabronione na tym czacie."; /* No comment provided by engineer. */ -"Disappearing messages are prohibited in this group." = "Znikające wiadomości są zabronione w tej grupie."; +"Disappearing messages are prohibited." = "Znikające wiadomości są zabronione w tej grupie."; /* No comment provided by engineer. */ "Disappears at" = "Znika o"; @@ -2125,7 +2125,7 @@ "Files and media" = "Pliki i media"; /* No comment provided by engineer. */ -"Files and media are prohibited in this group." = "Pliki i media są zabronione w tej grupie."; +"Files and media are prohibited." = "Pliki i media są zabronione w tej grupie."; /* No comment provided by engineer. */ "Files and media not allowed" = "Pliki i multimedia nie są dozwolone"; @@ -2278,25 +2278,25 @@ "Group links" = "Linki grupowe"; /* No comment provided by engineer. */ -"Group members can add message reactions." = "Członkowie grupy mogą dodawać reakcje wiadomości."; +"Members can add message reactions." = "Członkowie grupy mogą dodawać reakcje wiadomości."; /* No comment provided by engineer. */ -"Group members can irreversibly delete sent messages. (24 hours)" = "Członkowie grupy mogą nieodwracalnie usuwać wysłane wiadomości. (24 godziny)"; +"Members can irreversibly delete sent messages. (24 hours)" = "Członkowie grupy mogą nieodwracalnie usuwać wysłane wiadomości. (24 godziny)"; /* No comment provided by engineer. */ -"Group members can send direct messages." = "Członkowie grupy mogą wysyłać bezpośrednie wiadomości."; +"Members can send direct messages." = "Członkowie grupy mogą wysyłać bezpośrednie wiadomości."; /* No comment provided by engineer. */ -"Group members can send disappearing messages." = "Członkowie grupy mogą wysyłać znikające wiadomości."; +"Members can send disappearing messages." = "Członkowie grupy mogą wysyłać znikające wiadomości."; /* No comment provided by engineer. */ -"Group members can send files and media." = "Członkowie grupy mogą wysyłać pliki i media."; +"Members can send files and media." = "Członkowie grupy mogą wysyłać pliki i media."; /* No comment provided by engineer. */ -"Group members can send SimpleX links." = "Członkowie grupy mogą wysyłać linki SimpleX."; +"Members can send SimpleX links." = "Członkowie grupy mogą wysyłać linki SimpleX."; /* No comment provided by engineer. */ -"Group members can send voice messages." = "Członkowie grupy mogą wysyłać wiadomości głosowe."; +"Members can send voice messages." = "Członkowie grupy mogą wysyłać wiadomości głosowe."; /* notification */ "Group message:" = "Wiadomość grupowa:"; @@ -2590,7 +2590,7 @@ "Irreversible message deletion is prohibited in this chat." = "Nieodwracalne usuwanie wiadomości jest na tym czacie zabronione."; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this group." = "Nieodwracalne usuwanie wiadomości jest w tej grupie zabronione."; +"Irreversible message deletion is prohibited." = "Nieodwracalne usuwanie wiadomości jest w tej grupie zabronione."; /* No comment provided by engineer. */ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "To pozwala na posiadanie wielu anonimowych połączeń bez żadnych wspólnych danych między nimi w pojedynczym profilu czatu."; @@ -2812,7 +2812,7 @@ "Message reactions are prohibited in this chat." = "Reakcje wiadomości są zabronione na tym czacie."; /* No comment provided by engineer. */ -"Message reactions are prohibited in this group." = "Reakcje wiadomości są zabronione w tej grupie."; +"Message reactions are prohibited." = "Reakcje wiadomości są zabronione w tej grupie."; /* notification */ "message received" = "wiadomość otrzymana"; @@ -4164,7 +4164,7 @@ "SimpleX links" = "Linki SimpleX"; /* No comment provided by engineer. */ -"SimpleX links are prohibited in this group." = "Linki SimpleX są zablokowane na tej grupie."; +"SimpleX links are prohibited." = "Linki SimpleX są zablokowane na tej grupie."; /* No comment provided by engineer. */ "SimpleX links not allowed" = "Linki SimpleX są niedozwolone"; @@ -4836,7 +4836,7 @@ "Voice messages are prohibited in this chat." = "Wiadomości głosowe są zabronione na tym czacie."; /* No comment provided by engineer. */ -"Voice messages are prohibited in this group." = "Wiadomości głosowe są zabronione w tej grupie."; +"Voice messages are prohibited." = "Wiadomości głosowe są zabronione w tej grupie."; /* No comment provided by engineer. */ "Voice messages not allowed" = "Wiadomości głosowe są niedozwolone"; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 6db8181e8d..bb7ec81ace 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -1575,7 +1575,7 @@ "Direct messages" = "Прямые сообщения"; /* No comment provided by engineer. */ -"Direct messages between members are prohibited in this group." = "Прямые сообщения между членами группы запрещены."; +"Direct messages between members are prohibited." = "Прямые сообщения между членами группы запрещены."; /* No comment provided by engineer. */ "Disable (keep overrides)" = "Выключить (кроме исключений)"; @@ -1602,7 +1602,7 @@ "Disappearing messages are prohibited in this chat." = "Исчезающие сообщения запрещены в этом чате."; /* No comment provided by engineer. */ -"Disappearing messages are prohibited in this group." = "Исчезающие сообщения запрещены в этой группе."; +"Disappearing messages are prohibited." = "Исчезающие сообщения запрещены в этой группе."; /* No comment provided by engineer. */ "Disappears at" = "Исчезает"; @@ -2149,7 +2149,7 @@ "Files and media" = "Файлы и медиа"; /* No comment provided by engineer. */ -"Files and media are prohibited in this group." = "Файлы и медиа запрещены в этой группе."; +"Files and media are prohibited." = "Файлы и медиа запрещены в этой группе."; /* No comment provided by engineer. */ "Files and media not allowed" = "Файлы и медиа не разрешены"; @@ -2305,25 +2305,25 @@ "Group links" = "Ссылки групп"; /* No comment provided by engineer. */ -"Group members can add message reactions." = "Члены группы могут добавлять реакции на сообщения."; +"Members can add message reactions." = "Члены группы могут добавлять реакции на сообщения."; /* No comment provided by engineer. */ -"Group members can irreversibly delete sent messages. (24 hours)" = "Члены группы могут необратимо удалять отправленные сообщения. (24 часа)"; +"Members can irreversibly delete sent messages. (24 hours)" = "Члены группы могут необратимо удалять отправленные сообщения. (24 часа)"; /* No comment provided by engineer. */ -"Group members can send direct messages." = "Члены группы могут посылать прямые сообщения."; +"Members can send direct messages." = "Члены группы могут посылать прямые сообщения."; /* No comment provided by engineer. */ -"Group members can send disappearing messages." = "Члены группы могут посылать исчезающие сообщения."; +"Members can send disappearing messages." = "Члены группы могут посылать исчезающие сообщения."; /* No comment provided by engineer. */ -"Group members can send files and media." = "Члены группы могут слать файлы и медиа."; +"Members can send files and media." = "Члены группы могут слать файлы и медиа."; /* No comment provided by engineer. */ -"Group members can send SimpleX links." = "Члены группы могут отправлять ссылки SimpleX."; +"Members can send SimpleX links." = "Члены группы могут отправлять ссылки SimpleX."; /* No comment provided by engineer. */ -"Group members can send voice messages." = "Члены группы могут отправлять голосовые сообщения."; +"Members can send voice messages." = "Члены группы могут отправлять голосовые сообщения."; /* notification */ "Group message:" = "Групповое сообщение:"; @@ -2620,7 +2620,7 @@ "Irreversible message deletion is prohibited in this chat." = "Необратимое удаление сообщений запрещено в этом чате."; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this group." = "Необратимое удаление сообщений запрещено в этой группе."; +"Irreversible message deletion is prohibited." = "Необратимое удаление сообщений запрещено в этой группе."; /* No comment provided by engineer. */ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "Это позволяет иметь много анонимных соединений без общих данных между ними в одном профиле пользователя."; @@ -2842,7 +2842,7 @@ "Message reactions are prohibited in this chat." = "Реакции на сообщения в этом чате запрещены."; /* No comment provided by engineer. */ -"Message reactions are prohibited in this group." = "Реакции на сообщения запрещены в этой группе."; +"Message reactions are prohibited." = "Реакции на сообщения запрещены в этой группе."; /* notification */ "message received" = "получено сообщение"; @@ -4194,7 +4194,7 @@ "SimpleX links" = "SimpleX ссылки"; /* No comment provided by engineer. */ -"SimpleX links are prohibited in this group." = "Ссылки SimpleX запрещены в этой группе."; +"SimpleX links are prohibited." = "Ссылки SimpleX запрещены в этой группе."; /* No comment provided by engineer. */ "SimpleX links not allowed" = "Ссылки SimpleX не разрешены"; @@ -4875,7 +4875,7 @@ "Voice messages are prohibited in this chat." = "Голосовые сообщения запрещены в этом чате."; /* No comment provided by engineer. */ -"Voice messages are prohibited in this group." = "Голосовые сообщения запрещены в этой группе."; +"Voice messages are prohibited." = "Голосовые сообщения запрещены в этой группе."; /* No comment provided by engineer. */ "Voice messages not allowed" = "Голосовые сообщения не разрешены"; diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index 1b3dec5ee1..962c64b710 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -1005,7 +1005,7 @@ "Direct messages" = "ข้อความโดยตรง"; /* No comment provided by engineer. */ -"Direct messages between members are prohibited in this group." = "ข้อความโดยตรงระหว่างสมาชิกเป็นสิ่งต้องห้ามในกลุ่มนี้"; +"Direct messages between members are prohibited." = "ข้อความโดยตรงระหว่างสมาชิกเป็นสิ่งต้องห้ามในกลุ่มนี้"; /* No comment provided by engineer. */ "Disable (keep overrides)" = "ปิดใช้งาน (เก็บการแทนที่)"; @@ -1026,7 +1026,7 @@ "Disappearing messages are prohibited in this chat." = "ข้อความที่จะหายไปหลังเวลาที่กําหนด (disappearing message) เป็นสิ่งต้องห้ามในแชทนี้"; /* No comment provided by engineer. */ -"Disappearing messages are prohibited in this group." = "ข้อความที่จะหายไปหลังเวลาที่กําหนด (disappearing message) เป็นสิ่งต้องห้ามในกลุ่มนี้"; +"Disappearing messages are prohibited." = "ข้อความที่จะหายไปหลังเวลาที่กําหนด (disappearing message) เป็นสิ่งต้องห้ามในกลุ่มนี้"; /* No comment provided by engineer. */ "Disappears at" = "หายไปที่"; @@ -1386,7 +1386,7 @@ "Files and media" = "ไฟล์และสื่อ"; /* No comment provided by engineer. */ -"Files and media are prohibited in this group." = "ไฟล์และสื่อเป็นสิ่งต้องห้ามในกลุ่มนี้"; +"Files and media are prohibited." = "ไฟล์และสื่อเป็นสิ่งต้องห้ามในกลุ่มนี้"; /* No comment provided by engineer. */ "Files and media prohibited!" = "ไฟล์และสื่อต้องห้าม!"; @@ -1470,22 +1470,22 @@ "Group links" = "ลิงค์กลุ่ม"; /* No comment provided by engineer. */ -"Group members can add message reactions." = "สมาชิกกลุ่มสามารถเพิ่มการแสดงปฏิกิริยาต่อข้อความได้"; +"Members can add message reactions." = "สมาชิกกลุ่มสามารถเพิ่มการแสดงปฏิกิริยาต่อข้อความได้"; /* No comment provided by engineer. */ -"Group members can irreversibly delete sent messages. (24 hours)" = "สมาชิกกลุ่มสามารถลบข้อความที่ส่งแล้วอย่างถาวร"; +"Members can irreversibly delete sent messages. (24 hours)" = "สมาชิกกลุ่มสามารถลบข้อความที่ส่งแล้วอย่างถาวร"; /* No comment provided by engineer. */ -"Group members can send direct messages." = "สมาชิกกลุ่มสามารถส่งข้อความโดยตรงได้"; +"Members can send direct messages." = "สมาชิกกลุ่มสามารถส่งข้อความโดยตรงได้"; /* No comment provided by engineer. */ -"Group members can send disappearing messages." = "สมาชิกกลุ่มสามารถส่งข้อความที่จะหายไปหลังจากเวลาที่กำหนดหลังการอ่าน (disappearing messages) ได้"; +"Members can send disappearing messages." = "สมาชิกกลุ่มสามารถส่งข้อความที่จะหายไปหลังจากเวลาที่กำหนดหลังการอ่าน (disappearing messages) ได้"; /* No comment provided by engineer. */ -"Group members can send files and media." = "สมาชิกกลุ่มสามารถส่งไฟล์และสื่อ"; +"Members can send files and media." = "สมาชิกกลุ่มสามารถส่งไฟล์และสื่อ"; /* No comment provided by engineer. */ -"Group members can send voice messages." = "สมาชิกกลุ่มสามารถส่งข้อความเสียง"; +"Members can send voice messages." = "สมาชิกกลุ่มสามารถส่งข้อความเสียง"; /* notification */ "Group message:" = "ข้อความกลุ่ม:"; @@ -1713,7 +1713,7 @@ "Irreversible message deletion is prohibited in this chat." = "ไม่สามารถลบข้อความแบบแก้ไขไม่ได้ในแชทนี้"; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this group." = "การลบข้อความแบบแก้ไขไม่ได้เป็นสิ่งที่ห้ามในกลุ่มนี้"; +"Irreversible message deletion is prohibited." = "การลบข้อความแบบแก้ไขไม่ได้เป็นสิ่งที่ห้ามในกลุ่มนี้"; /* No comment provided by engineer. */ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "อนุญาตให้มีการเชื่อมต่อที่ไม่ระบุตัวตนจำนวนมากโดยไม่มีข้อมูลที่ใช้ร่วมกันระหว่างกันในโปรไฟล์การแชทเดียว"; @@ -1869,7 +1869,7 @@ "Message reactions are prohibited in this chat." = "ห้ามแสดงปฏิกิริยาบนข้อความในแชทนี้"; /* No comment provided by engineer. */ -"Message reactions are prohibited in this group." = "ปฏิกิริยาบนข้อความเป็นสิ่งต้องห้ามในกลุ่มนี้"; +"Message reactions are prohibited." = "ปฏิกิริยาบนข้อความเป็นสิ่งต้องห้ามในกลุ่มนี้"; /* notification */ "message received" = "ข้อความที่ได้รับ"; @@ -3071,7 +3071,7 @@ "Voice messages are prohibited in this chat." = "ห้ามส่งข้อความเสียงในแชทนี้"; /* No comment provided by engineer. */ -"Voice messages are prohibited in this group." = "ข้อความเสียงเป็นสิ่งต้องห้ามในกลุ่มนี้"; +"Voice messages are prohibited." = "ข้อความเสียงเป็นสิ่งต้องห้ามในกลุ่มนี้"; /* No comment provided by engineer. */ "Voice messages prohibited!" = "ห้ามข้อความเสียง!"; diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index b849dda85a..ec29de0cf3 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -1572,7 +1572,7 @@ "Direct messages" = "Doğrudan mesajlar"; /* No comment provided by engineer. */ -"Direct messages between members are prohibited in this group." = "Bu grupta üyeler arasında direkt mesajlaşma yasaktır."; +"Direct messages between members are prohibited." = "Bu grupta üyeler arasında direkt mesajlaşma yasaktır."; /* No comment provided by engineer. */ "Disable (keep overrides)" = "Devre dışı bırak (geçersiz kılmaları koru)"; @@ -1599,7 +1599,7 @@ "Disappearing messages are prohibited in this chat." = "Kaybolan mesajlar bu sohbette yasaklanmış."; /* No comment provided by engineer. */ -"Disappearing messages are prohibited in this group." = "Kaybolan mesajlar bu grupta yasaklanmış."; +"Disappearing messages are prohibited." = "Kaybolan mesajlar bu grupta yasaklanmış."; /* No comment provided by engineer. */ "Disappears at" = "da kaybolur"; @@ -2146,7 +2146,7 @@ "Files and media" = "Dosyalar ve medya"; /* No comment provided by engineer. */ -"Files and media are prohibited in this group." = "Dosyalar ve medya bu grupta yasaklandı."; +"Files and media are prohibited." = "Dosyalar ve medya bu grupta yasaklandı."; /* No comment provided by engineer. */ "Files and media not allowed" = "Dosyalar ve medyaya izin verilmiyor"; @@ -2302,25 +2302,25 @@ "Group links" = "Grup bağlantıları"; /* No comment provided by engineer. */ -"Group members can add message reactions." = "Grup üyeleri mesaj tepkileri ekleyebilir."; +"Members can add message reactions." = "Grup üyeleri mesaj tepkileri ekleyebilir."; /* No comment provided by engineer. */ -"Group members can irreversibly delete sent messages. (24 hours)" = "Grup üyeleri, gönderilen mesajları kalıcı olarak silebilir. (24 saat içinde)"; +"Members can irreversibly delete sent messages. (24 hours)" = "Grup üyeleri, gönderilen mesajları kalıcı olarak silebilir. (24 saat içinde)"; /* No comment provided by engineer. */ -"Group members can send direct messages." = "Grup üyeleri doğrudan mesajlar gönderebilir."; +"Members can send direct messages." = "Grup üyeleri doğrudan mesajlar gönderebilir."; /* No comment provided by engineer. */ -"Group members can send disappearing messages." = "Grup üyeleri kaybolan mesajlar gönderebilir."; +"Members can send disappearing messages." = "Grup üyeleri kaybolan mesajlar gönderebilir."; /* No comment provided by engineer. */ -"Group members can send files and media." = "Grup üyeleri dosyalar ve medya gönderebilir."; +"Members can send files and media." = "Grup üyeleri dosyalar ve medya gönderebilir."; /* No comment provided by engineer. */ -"Group members can send SimpleX links." = "Grup üyeleri SimpleX bağlantıları gönderebilir."; +"Members can send SimpleX links." = "Grup üyeleri SimpleX bağlantıları gönderebilir."; /* No comment provided by engineer. */ -"Group members can send voice messages." = "Grup üyeleri sesli mesajlar gönderebilir."; +"Members can send voice messages." = "Grup üyeleri sesli mesajlar gönderebilir."; /* notification */ "Group message:" = "Grup mesajı:"; @@ -2617,7 +2617,7 @@ "Irreversible message deletion is prohibited in this chat." = "Bu sohbette geri döndürülemez mesaj silme yasaktır."; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this group." = "Bu grupta geri döndürülemez mesaj silme yasaktır."; +"Irreversible message deletion is prohibited." = "Bu grupta geri döndürülemez mesaj silme yasaktır."; /* No comment provided by engineer. */ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "Tek bir sohbet profilinde aralarında herhangi bir veri paylaşımı olmadan birçok anonim bağlantıya sahip olmaya izin verir."; @@ -2839,7 +2839,7 @@ "Message reactions are prohibited in this chat." = "Mesaj tepkileri bu sohbette yasaklandı."; /* No comment provided by engineer. */ -"Message reactions are prohibited in this group." = "Mesaj tepkileri bu grupta yasaklandı."; +"Message reactions are prohibited." = "Mesaj tepkileri bu grupta yasaklandı."; /* notification */ "message received" = "mesaj alındı"; @@ -4191,7 +4191,7 @@ "SimpleX links" = "SimpleX bağlantıları"; /* No comment provided by engineer. */ -"SimpleX links are prohibited in this group." = "SimpleX bağlantıları bu grupta yasaklandı."; +"SimpleX links are prohibited." = "SimpleX bağlantıları bu grupta yasaklandı."; /* No comment provided by engineer. */ "SimpleX links not allowed" = "SimpleX bağlantılarına izin verilmiyor"; @@ -4872,7 +4872,7 @@ "Voice messages are prohibited in this chat." = "Bu sohbette sesli mesajlar yasaktır."; /* No comment provided by engineer. */ -"Voice messages are prohibited in this group." = "Bu grupta sesli mesajlar yasaktır."; +"Voice messages are prohibited." = "Bu grupta sesli mesajlar yasaktır."; /* No comment provided by engineer. */ "Voice messages not allowed" = "Sesli mesajlara izin verilmiyor"; diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index 7b66aa5efb..28cc2839e8 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -1656,7 +1656,7 @@ "Direct messages" = "Прямі повідомлення"; /* No comment provided by engineer. */ -"Direct messages between members are prohibited in this group." = "У цій групі заборонені прямі повідомлення між учасниками."; +"Direct messages between members are prohibited." = "У цій групі заборонені прямі повідомлення між учасниками."; /* No comment provided by engineer. */ "Disable (keep overrides)" = "Вимкнути (зберегти перевизначення)"; @@ -1683,7 +1683,7 @@ "Disappearing messages are prohibited in this chat." = "Зникаючі повідомлення в цьому чаті заборонені."; /* No comment provided by engineer. */ -"Disappearing messages are prohibited in this group." = "У цій групі заборонено зникаючі повідомлення."; +"Disappearing messages are prohibited." = "У цій групі заборонено зникаючі повідомлення."; /* No comment provided by engineer. */ "Disappears at" = "Зникає за"; @@ -2254,7 +2254,7 @@ "Files and media" = "Файли і медіа"; /* No comment provided by engineer. */ -"Files and media are prohibited in this group." = "Файли та медіа в цій групі заборонені."; +"Files and media are prohibited." = "Файли та медіа в цій групі заборонені."; /* No comment provided by engineer. */ "Files and media not allowed" = "Файли та медіафайли заборонені"; @@ -2425,25 +2425,25 @@ "Group links" = "Групові посилання"; /* No comment provided by engineer. */ -"Group members can add message reactions." = "Учасники групи можуть додавати реакції на повідомлення."; +"Members can add message reactions." = "Учасники групи можуть додавати реакції на повідомлення."; /* No comment provided by engineer. */ -"Group members can irreversibly delete sent messages. (24 hours)" = "Учасники групи можуть безповоротно видаляти надіслані повідомлення. (24 години)"; +"Members can irreversibly delete sent messages. (24 hours)" = "Учасники групи можуть безповоротно видаляти надіслані повідомлення. (24 години)"; /* No comment provided by engineer. */ -"Group members can send direct messages." = "Учасники групи можуть надсилати прямі повідомлення."; +"Members can send direct messages." = "Учасники групи можуть надсилати прямі повідомлення."; /* No comment provided by engineer. */ -"Group members can send disappearing messages." = "Учасники групи можуть надсилати зникаючі повідомлення."; +"Members can send disappearing messages." = "Учасники групи можуть надсилати зникаючі повідомлення."; /* No comment provided by engineer. */ -"Group members can send files and media." = "Учасники групи можуть надсилати файли та медіа."; +"Members can send files and media." = "Учасники групи можуть надсилати файли та медіа."; /* No comment provided by engineer. */ -"Group members can send SimpleX links." = "Учасники групи можуть надсилати посилання SimpleX."; +"Members can send SimpleX links." = "Учасники групи можуть надсилати посилання SimpleX."; /* No comment provided by engineer. */ -"Group members can send voice messages." = "Учасники групи можуть надсилати голосові повідомлення."; +"Members can send voice messages." = "Учасники групи можуть надсилати голосові повідомлення."; /* notification */ "Group message:" = "Групове повідомлення:"; @@ -2746,7 +2746,7 @@ "Irreversible message deletion is prohibited in this chat." = "У цьому чаті заборонено безповоротне видалення повідомлень."; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this group." = "У цій групі заборонено безповоротне видалення повідомлень."; +"Irreversible message deletion is prohibited." = "У цій групі заборонено безповоротне видалення повідомлень."; /* No comment provided by engineer. */ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "Це дозволяє мати багато анонімних з'єднань без будь-яких спільних даних між ними в одному профілі чату."; @@ -2968,7 +2968,7 @@ "Message reactions are prohibited in this chat." = "Реакції на повідомлення в цьому чаті заборонені."; /* No comment provided by engineer. */ -"Message reactions are prohibited in this group." = "Реакції на повідомлення в цій групі заборонені."; +"Message reactions are prohibited." = "Реакції на повідомлення в цій групі заборонені."; /* notification */ "message received" = "повідомлення отримано"; @@ -4416,7 +4416,7 @@ "SimpleX links" = "Посилання SimpleX"; /* No comment provided by engineer. */ -"SimpleX links are prohibited in this group." = "У цій групі заборонені посилання на SimpleX."; +"SimpleX links are prohibited." = "У цій групі заборонені посилання на SimpleX."; /* No comment provided by engineer. */ "SimpleX links not allowed" = "Посилання SimpleX заборонені"; @@ -5148,7 +5148,7 @@ "Voice messages are prohibited in this chat." = "Голосові повідомлення в цьому чаті заборонені."; /* No comment provided by engineer. */ -"Voice messages are prohibited in this group." = "Голосові повідомлення в цій групі заборонені."; +"Voice messages are prohibited." = "Голосові повідомлення в цій групі заборонені."; /* No comment provided by engineer. */ "Voice messages not allowed" = "Голосові повідомлення заборонені"; diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index a524b5739d..5fac1a8577 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -1521,7 +1521,7 @@ "Direct messages" = "私信"; /* No comment provided by engineer. */ -"Direct messages between members are prohibited in this group." = "此群中禁止成员之间私信。"; +"Direct messages between members are prohibited." = "此群中禁止成员之间私信。"; /* No comment provided by engineer. */ "Disable (keep overrides)" = "禁用(保留覆盖)"; @@ -1548,7 +1548,7 @@ "Disappearing messages are prohibited in this chat." = "此聊天中禁止显示限时消息。"; /* No comment provided by engineer. */ -"Disappearing messages are prohibited in this group." = "该组禁止限时消息。"; +"Disappearing messages are prohibited." = "该组禁止限时消息。"; /* No comment provided by engineer. */ "Disappears at" = "消失于"; @@ -2074,7 +2074,7 @@ "Files and media" = "文件和媒体"; /* No comment provided by engineer. */ -"Files and media are prohibited in this group." = "此群组中禁止文件和媒体。"; +"Files and media are prohibited." = "此群组中禁止文件和媒体。"; /* No comment provided by engineer. */ "Files and media not allowed" = "不允许文件和媒体"; @@ -2215,25 +2215,25 @@ "Group links" = "群组链接"; /* No comment provided by engineer. */ -"Group members can add message reactions." = "群组成员可以添加信息回应。"; +"Members can add message reactions." = "群组成员可以添加信息回应。"; /* No comment provided by engineer. */ -"Group members can irreversibly delete sent messages. (24 hours)" = "群组成员可以不可撤回地删除已发送的消息"; +"Members can irreversibly delete sent messages. (24 hours)" = "群组成员可以不可撤回地删除已发送的消息"; /* No comment provided by engineer. */ -"Group members can send direct messages." = "群组成员可以私信。"; +"Members can send direct messages." = "群组成员可以私信。"; /* No comment provided by engineer. */ -"Group members can send disappearing messages." = "群组成员可以发送限时消息。"; +"Members can send disappearing messages." = "群组成员可以发送限时消息。"; /* No comment provided by engineer. */ -"Group members can send files and media." = "群组成员可以发送文件和媒体。"; +"Members can send files and media." = "群组成员可以发送文件和媒体。"; /* No comment provided by engineer. */ -"Group members can send SimpleX links." = "群成员可发送 SimpleX 链接。"; +"Members can send SimpleX links." = "群成员可发送 SimpleX 链接。"; /* No comment provided by engineer. */ -"Group members can send voice messages." = "群组成员可以发送语音消息。"; +"Members can send voice messages." = "群组成员可以发送语音消息。"; /* notification */ "Group message:" = "群组消息:"; @@ -2524,7 +2524,7 @@ "Irreversible message deletion is prohibited in this chat." = "此聊天中禁止不可撤回消息移除。"; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this group." = "此群组中禁止不可撤回消息移除。"; +"Irreversible message deletion is prohibited." = "此群组中禁止不可撤回消息移除。"; /* No comment provided by engineer. */ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "它允许在一个聊天资料中有多个匿名连接,而它们之间没有任何共享数据。"; @@ -2746,7 +2746,7 @@ "Message reactions are prohibited in this chat." = "该聊天禁用了消息回应。"; /* No comment provided by engineer. */ -"Message reactions are prohibited in this group." = "该群组禁用了消息回应。"; +"Message reactions are prohibited." = "该群组禁用了消息回应。"; /* notification */ "message received" = "消息已收到"; @@ -4044,7 +4044,7 @@ "SimpleX links" = "SimpleX 链接"; /* No comment provided by engineer. */ -"SimpleX links are prohibited in this group." = "此群禁止 SimpleX 链接。"; +"SimpleX links are prohibited." = "此群禁止 SimpleX 链接。"; /* No comment provided by engineer. */ "SimpleX links not allowed" = "不允许SimpleX 链接"; @@ -4692,7 +4692,7 @@ "Voice messages are prohibited in this chat." = "语音信息在此聊天中被禁止。"; /* No comment provided by engineer. */ -"Voice messages are prohibited in this group." = "语音信息在该群组中被禁用。"; +"Voice messages are prohibited." = "语音信息在该群组中被禁用。"; /* No comment provided by engineer. */ "Voice messages not allowed" = "不允许语音消息"; diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index f0f63f2c72..9531712554 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -5001,7 +5001,7 @@ enum class GroupFeature: Feature { } DirectMessages -> when(enabled) { GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_send_dms) - GroupFeatureEnabled.OFF -> generalGetString(MR.strings.direct_messages_are_prohibited_in_chat) + GroupFeatureEnabled.OFF -> generalGetString(MR.strings.direct_messages_are_prohibited) } FullDelete -> when(enabled) { GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_delete) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt index b351f56c29..25661f00a0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt @@ -33,6 +33,7 @@ import chat.simplex.common.views.usersettings.SettingsActionItem import chat.simplex.common.model.GroupInfo import chat.simplex.common.platform.* import chat.simplex.res.MR +import dev.icerock.moko.resources.StringResource @Composable fun AddGroupMembersView(rhId: Long?, groupInfo: GroupInfo, creatingGroup: Boolean = false, chatModel: ChatModel, close: () -> Unit) { @@ -126,7 +127,8 @@ fun AddGroupMembersLayout( tint = MaterialTheme.colors.secondary, modifier = Modifier.padding(end = 10.dp).size(20.dp) ) - Text(generalGetString(MR.strings.group_main_profile_sent), textAlign = TextAlign.Center, style = MaterialTheme.typography.body2) + val textId = if (groupInfo.businessChat == null) MR.strings.group_main_profile_sent else MR.strings.chat_main_profile_sent + Text(generalGetString(textId), textAlign = TextAlign.Center, style = MaterialTheme.typography.body2) } } @@ -168,7 +170,8 @@ fun AddGroupMembersLayout( if (creatingGroup && selectedContacts.isEmpty()) { SkipInvitingButton(close) } else { - InviteMembersButton(inviteMembers, disabled = selectedContacts.isEmpty() || !allowModifyMembers) + val titleId = if (groupInfo.businessChat == null) MR.strings.invite_to_group_button else MR.strings.invite_to_chat_button + InviteMembersButton(titleId, inviteMembers, disabled = selectedContacts.isEmpty() || !allowModifyMembers) } } SectionCustomFooter { @@ -220,10 +223,10 @@ private fun RoleSelectionRow(groupInfo: GroupInfo, selectedRole: MutableState Unit, disabled: Boolean) { +fun InviteMembersButton(titleId: StringResource, onClick: () -> Unit, disabled: Boolean) { SettingsActionItem( painterResource(MR.images.ic_check), - stringResource(MR.strings.invite_to_group_button), + stringResource(titleId), click = onClick, textColor = MaterialTheme.colors.primary, iconColor = MaterialTheme.colors.primary, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index 870df388b2..804222f264 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -123,12 +123,18 @@ fun ModalData.GroupChatInfoView(chatModel: ChatModel, rhId: Long?, chatId: Strin fun deleteGroupDialog(chat: Chat, groupInfo: GroupInfo, chatModel: ChatModel, close: (() -> Unit)? = null) { val chatInfo = chat.chatInfo - val alertTextKey = - if (groupInfo.membership.memberCurrent) MR.strings.delete_group_for_all_members_cannot_undo_warning - else MR.strings.delete_group_for_self_cannot_undo_warning + val titleId = if (groupInfo.businessChat == null) MR.strings.delete_group_question else MR.strings.delete_chat_question + val messageId = + if (groupInfo.businessChat == null) { + if (groupInfo.membership.memberCurrent) MR.strings.delete_group_for_all_members_cannot_undo_warning + else MR.strings.delete_group_for_self_cannot_undo_warning + } else { + if (groupInfo.membership.memberCurrent) MR.strings.delete_chat_for_all_members_cannot_undo_warning + else MR.strings.delete_chat_for_self_cannot_undo_warning + } AlertManager.shared.showAlertDialog( - title = generalGetString(MR.strings.delete_group_question), - text = generalGetString(alertTextKey), + title = generalGetString(titleId), + text = generalGetString(messageId), confirmText = generalGetString(MR.strings.delete_verb), onConfirm = { withBGApi { @@ -151,9 +157,14 @@ fun deleteGroupDialog(chat: Chat, groupInfo: GroupInfo, chatModel: ChatModel, cl } fun leaveGroupDialog(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, close: (() -> Unit)? = null) { + val titleId = if (groupInfo.businessChat == null) MR.strings.leave_group_question else MR.strings.leave_chat_question + val messageId = if (groupInfo.businessChat == null) + MR.strings.you_will_stop_receiving_messages_from_this_group_chat_history_will_be_preserved + else + MR.strings.you_will_stop_receiving_messages_from_this_chat_chat_history_will_be_preserved AlertManager.shared.showAlertDialog( - title = generalGetString(MR.strings.leave_group_question), - text = generalGetString(MR.strings.you_will_stop_receiving_messages_from_this_group_chat_history_will_be_preserved), + title = generalGetString(titleId), + text = generalGetString(messageId), confirmText = generalGetString(MR.strings.leave_group_button), onConfirm = { withLongRunningApi(60_000) { @@ -166,9 +177,13 @@ fun leaveGroupDialog(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, cl } private fun removeMemberAlert(rhId: Long?, groupInfo: GroupInfo, mem: GroupMember) { + val messageId = if (groupInfo.businessChat == null) + MR.strings.member_will_be_removed_from_group_cannot_be_undone + else + MR.strings.member_will_be_removed_from_chat_cannot_be_undone AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.button_remove_member_question), - text = generalGetString(MR.strings.member_will_be_removed_from_group_cannot_be_undone), + text = generalGetString(messageId), confirmText = generalGetString(MR.strings.remove_member_confirmation), onConfirm = { withBGApi { @@ -353,7 +368,8 @@ fun ModalData.GroupChatInfoLayout( } } } - SectionTextFooter(stringResource(MR.strings.only_group_owners_can_change_prefs)) + val footerId = if (groupInfo.businessChat == null) MR.strings.only_group_owners_can_change_prefs else MR.strings.only_chat_owners_can_change_prefs + SectionTextFooter(stringResource(footerId)) SectionDividerSpaced(maxTopPadding = true) SectionView(title = String.format(generalGetString(MR.strings.group_info_section_title_num_members), members.count() + 1)) { @@ -397,10 +413,12 @@ fun ModalData.GroupChatInfoLayout( SectionView { ClearChatButton(clearChat) if (groupInfo.canDelete) { - DeleteGroupButton(deleteGroup) + val titleId = if (groupInfo.businessChat == null) MR.strings.button_delete_group else MR.strings.button_delete_chat + DeleteGroupButton(titleId, deleteGroup) } if (groupInfo.membership.memberCurrent) { - LeaveGroupButton(leaveGroup) + val titleId = if (groupInfo.businessChat == null) MR.strings.button_leave_group else MR.strings.button_leave_chat + LeaveGroupButton(titleId, leaveGroup) } } @@ -655,10 +673,10 @@ private fun AddOrEditWelcomeMessage(welcomeMessage: String?, onClick: () -> Unit } @Composable -private fun LeaveGroupButton(onClick: () -> Unit) { +private fun LeaveGroupButton(titleId: StringResource, onClick: () -> Unit) { SettingsActionItem( painterResource(MR.images.ic_logout), - stringResource(MR.strings.button_leave_group), + stringResource(titleId), onClick, iconColor = Color.Red, textColor = Color.Red @@ -666,10 +684,10 @@ private fun LeaveGroupButton(onClick: () -> Unit) { } @Composable -private fun DeleteGroupButton(onClick: () -> Unit) { +private fun DeleteGroupButton(titleId: StringResource, onClick: () -> Unit) { SettingsActionItem( painterResource(MR.images.ic_delete), - stringResource(MR.strings.button_delete_group), + stringResource(titleId), onClick, iconColor = Color.Red, textColor = Color.Red diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt index a78dd36887..7f0d5f088e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt @@ -38,6 +38,7 @@ import chat.simplex.common.model.GroupInfo import chat.simplex.common.platform.* import chat.simplex.common.views.chatlist.openLoadedChat import chat.simplex.res.MR +import dev.icerock.moko.resources.StringResource import kotlinx.datetime.Clock @Composable @@ -104,7 +105,7 @@ fun GroupMemberInfoView( if (it == newRole.value) return@GroupMemberInfoLayout val prevValue = newRole.value newRole.value = it - updateMemberRoleDialog(it, member, onDismiss = { + updateMemberRoleDialog(it, groupInfo, member, onDismiss = { newRole.value = prevValue }) { withBGApi { @@ -211,9 +212,13 @@ fun GroupMemberInfoView( } fun removeMemberDialog(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, chatModel: ChatModel, close: (() -> Unit)? = null) { + val messageId = if (groupInfo.businessChat == null) + MR.strings.member_will_be_removed_from_group_cannot_be_undone + else + MR.strings.member_will_be_removed_from_chat_cannot_be_undone AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.button_remove_member), - text = generalGetString(MR.strings.member_will_be_removed_from_group_cannot_be_undone), + text = generalGetString(messageId), confirmText = generalGetString(MR.strings.remove_member_confirmation), onConfirm = { withBGApi { @@ -346,14 +351,15 @@ fun GroupMemberInfoLayout( showSendMessageToEnableCallsAlert() }) } else { // no known contact chat && directMessages are off + val messageId = if (groupInfo.businessChat == null) MR.strings.direct_messages_are_prohibited_in_group else MR.strings.direct_messages_are_prohibited_in_chat InfoViewActionButton(modifier = Modifier.fillMaxWidth(0.33f), painterResource(MR.images.ic_chat_bubble), generalGetString(MR.strings.info_view_message_button), disabled = false, disabledLook = true, onClick = { - showDirectMessagesProhibitedAlert(generalGetString(MR.strings.cant_send_message_to_member_alert_title)) + showDirectMessagesProhibitedAlert(generalGetString(MR.strings.cant_send_message_to_member_alert_title), messageId) }) InfoViewActionButton(modifier = Modifier.fillMaxWidth(0.5f), painterResource(MR.images.ic_call), generalGetString(MR.strings.info_view_call_button), disabled = false, disabledLook = true, onClick = { - showDirectMessagesProhibitedAlert(generalGetString(MR.strings.cant_call_member_alert_title)) + showDirectMessagesProhibitedAlert(generalGetString(MR.strings.cant_call_member_alert_title), messageId) }) InfoViewActionButton(modifier = Modifier.fillMaxWidth(1f), painterResource(MR.images.ic_videocam), generalGetString(MR.strings.info_view_video_button), disabled = false, disabledLook = true, onClick = { - showDirectMessagesProhibitedAlert(generalGetString(MR.strings.cant_call_member_alert_title)) + showDirectMessagesProhibitedAlert(generalGetString(MR.strings.cant_call_member_alert_title), messageId) }) } } @@ -394,7 +400,8 @@ fun GroupMemberInfoLayout( } SectionView(title = stringResource(MR.strings.member_info_section_title_member)) { - InfoRow(stringResource(MR.strings.info_row_group), groupInfo.displayName) + val titleId = if (groupInfo.businessChat == null) MR.strings.info_row_group else MR.strings.info_row_chat + InfoRow(stringResource(titleId), groupInfo.displayName) val roles = remember { member.canChangeRoleTo(groupInfo) } if (roles != null) { RoleSelectionRow(roles, newRole, onRoleSelected) @@ -470,10 +477,10 @@ private fun showSendMessageToEnableCallsAlert() { ) } -private fun showDirectMessagesProhibitedAlert(title: String) { +private fun showDirectMessagesProhibitedAlert(title: String, messageId: StringResource) { AlertManager.shared.showAlertMsg( title = title, - text = generalGetString(MR.strings.direct_messages_are_prohibited_in_chat) + text = generalGetString(messageId) ) } @@ -635,15 +642,19 @@ fun MemberProfileImage( private fun updateMemberRoleDialog( newRole: GroupMemberRole, + groupInfo: GroupInfo, member: GroupMember, onDismiss: () -> Unit, onConfirm: () -> Unit ) { AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.change_member_role_question), - text = if (member.memberCurrent) - String.format(generalGetString(MR.strings.member_role_will_be_changed_with_notification), newRole.text) - else + text = if (member.memberCurrent) { + if (groupInfo.businessChat == null) + String.format(generalGetString(MR.strings.member_role_will_be_changed_with_notification), newRole.text) + else + String.format(generalGetString(MR.strings.member_role_will_be_changed_with_notification_chat), newRole.text) + } else String.format(generalGetString(MR.strings.member_role_will_be_changed_with_invitation), newRole.text), confirmText = generalGetString(MR.strings.change_verb), onDismiss = onDismiss, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/AdvancedNetworkSettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/AdvancedNetworkSettings.kt index 838cac0172..fc042cc46c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/AdvancedNetworkSettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/AdvancedNetworkSettings.kt @@ -418,7 +418,7 @@ fun IntSettingRow(title: String, selection: MutableState, values: List Spacer(Modifier.size(4.dp)) Icon( if (!expanded.value) painterResource(MR.images.ic_arrow_drop_down) else painterResource(MR.images.ic_arrow_drop_up), - generalGetString(MR.strings.invite_to_group_button), + contentDescription = null, modifier = Modifier.padding(start = 8.dp), tint = MaterialTheme.colors.secondary ) @@ -478,7 +478,7 @@ fun TimeoutSettingRow(title: String, selection: MutableState, values: List Spacer(Modifier.size(4.dp)) Icon( if (!expanded.value) painterResource(MR.images.ic_arrow_drop_down) else painterResource(MR.images.ic_arrow_drop_up), - generalGetString(MR.strings.invite_to_group_button), + contentDescription = null, modifier = Modifier.padding(start = 8.dp), tint = MaterialTheme.colors.secondary ) diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index c8d995cfd9..1171b1d1ae 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -621,7 +621,7 @@ %d أسبوع لا يمكن أن يحتوي اسم العرض على مسافة فارغة. مكالمة فيديو مُعمّاة بين الطريفين - الرسائل المباشرة بين الأعضاء ممنوعة في هذه المجموعة. + الرسائل المباشرة بين الأعضاء ممنوعة في هذه المجموعة. %d ساعة %d ساعة %d ساعات diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 53df6d4818..7291c8cbb1 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1425,7 +1425,9 @@ You joined this group. Connecting to inviting group member. Leave Leave group? + Leave chat? You will stop receiving messages from this group. Chat history will be preserved. + You will stop receiving messages from this chat. Chat history will be preserved. Invite members Group inactive Invitation expired! @@ -1542,6 +1544,7 @@ Initial role Expand role selection Invite to group + Invite to chat Skip inviting members Select contacts Contact checked @@ -1558,10 +1561,15 @@ %1$s MEMBERS you: %1$s Delete group + Delete chat Delete group? + Delete chat? Group will be deleted for all members - this cannot be undone! + Chat will be deleted for all members - this cannot be undone! Group will be deleted for you - this cannot be undone! + Chat will be deleted for you - this cannot be undone! Leave group + Leave chat Edit group profile Add welcome message Welcome message @@ -1578,6 +1586,7 @@ Error creating member contact Error sending invitation Only group owners can change group preferences. + Only chat owners can change preferences. Address Share address You can share this address with your contacts to let them connect with %s. @@ -1624,6 +1633,7 @@ Send direct message Member will be removed from group - this cannot be undone! + Member will be removed from chat - this cannot be undone! Remove Remove member Block member? @@ -1649,6 +1659,7 @@ Switch Change group role? The role will be changed to "%s". Everyone in the group will be notified. + The role will be changed to "%s". Everyone in the chat will be notified. The role will be changed to "%s". The member will receive a new invitation. Connect directly? Сonnection request will be sent to this group member. @@ -1656,6 +1667,7 @@ Error changing role Error blocking member for all Group + Chat Connection direct indirect (%1$s) @@ -1703,6 +1715,7 @@ Enter group name: Group full name: Your chat profile will be sent to group members + Your chat profile will be sent to chat members Create group @@ -1957,20 +1970,22 @@ Prohibit sending SimpleX links Send up to 100 last messages to new members. Do not send history to new members. - Group members can send disappearing messages. - Disappearing messages are prohibited in this group. - Group members can send direct messages. - Direct messages between members are prohibited in this group. - Group members can irreversibly delete sent messages. (24 hours) - Irreversible message deletion is prohibited in this group. - Group members can send voice messages. - Voice messages are prohibited in this group. - Group members can add message reactions. - Message reactions are prohibited in this group. - Group members can send files and media. - Files and media are prohibited in this group. - Group members can send SimpleX links. - SimpleX links are prohibited in this group. + Members can send disappearing messages. + Disappearing messages are prohibited. + Members can send direct messages. + Direct messages between members are prohibited. + Direct messages between members are prohibited in this group. + Direct messages between members are prohibited in this chat. + Members can irreversibly delete sent messages. (24 hours) + Irreversible message deletion is prohibited. + Members can send voice messages. + Voice messages are prohibited. + Members can add message reactions. + Message reactions are prohibited. + Members can send files and media. + Files and media are prohibited. + Members can send SimpleX links. + SimpleX links are prohibited. Up to 100 last messages are sent to new members. History is not sent to new members. Delete after diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml index d59867574d..22e93b041a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml @@ -460,7 +460,7 @@ Изчезващи съобщения активирано активирано за контакт - Личните съобщения между членовете са забранени в тази група. + Личните съобщения между членовете са забранени в тази група. Различни имена, аватари и транспортна изолация. Оправяне на криптирането след възстановяване от резервни копия. Потвърждениe за доставка! diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml index b107ea1df7..a6ea5b1208 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml @@ -897,7 +897,7 @@ Členové skupiny mohou posílat mizící zprávy. Mizící zprávy jsou v této skupině zakázány. Členové skupiny mohou posílat přímé zprávy. - Přímé zprávy mezi členy jsou v této skupině zakázány. + Přímé zprávy mezi členy jsou v této skupině zakázány. Členové skupin mohou nevratně mazat odeslané zprávy. (24 hodin) Nevratné mazání zpráv je v této skupině zakázáno. Členové skupiny mohou posílat hlasové zprávy. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index 29812d0a3e..3c3711be25 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -875,7 +875,7 @@ Das Senden von Sprachnachrichten erlauben. Das Senden von Sprachnachrichten nicht erlauben. Gruppenmitglieder können Direktnachrichten versenden. - In dieser Gruppe sind Direktnachrichten zwischen Mitgliedern nicht erlaubt. + In dieser Gruppe sind Direktnachrichten zwischen Mitgliedern nicht erlaubt. Gruppenmitglieder können gesendete Nachrichten unwiederbringlich löschen (bis zu 24 Stunden). In dieser Gruppe ist das unwiederbringliche Löschen von Nachrichten nicht erlaubt. Gruppenmitglieder können Sprachnachrichten versenden. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index 3d22d1fcc5..98f34e4c81 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -82,7 +82,7 @@ Crea grupo secreto La contraseña de cifrado de la base de datos será actualizada. ID base de datos - Los mensajes directos entre miembros del grupo no están permitidos. + Los mensajes directos entre miembros del grupo no están permitidos. La contraseña de la base de datos es diferente a la almacenada en Keystore. La base de datos será cifrada y la contraseña se guardará en Keystore. ¿Eliminar contacto\? diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml index 8850e33a3e..7e59b69082 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml @@ -1415,7 +1415,7 @@ عدم ارسال تاریخچه به اعضای جدید. اعضای گروه می‌توانند پیام‌های ناپدید شونده ارسال کنند. اعضای گروه می‌توانند پیام‌های مستقیم ارسال کنند. - پیام‌های مستقیم بین اعضا در این گروه ممنوع هستند. + پیام‌های مستقیم بین اعضا در این گروه ممنوع هستند. حذف غیرقابل برگشت در این گروه ممنوع است. پیام‌های صوتی در این گروه ممنوع هستند. واکنش‌های پیام در این گروه ممنوع هستند. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml index f933b2d6cc..62a02986b2 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml @@ -339,7 +339,7 @@ Sekä sinä että kontaktisi voivat käyttää viestireaktioita. Sekä sinä että kontaktisi voitte peruuttamattomasti poistaa lähetetyt viestit. Sekä sinä että kontaktisi voitte soittaa puheluita. - Yksityisviestit jäsenten välillä ovat kiellettyjä tässä ryhmässä. + Yksityisviestit jäsenten välillä ovat kiellettyjä tässä ryhmässä. Chat-profiilin (oletus) tai yhteyden (BETA) perusteella. peruttu %s %d päivä diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml index 6caf5b61ef..480aa2e10c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml @@ -856,7 +856,7 @@ Interdire l’envoi de messages éphémères. Interdire la suppression irréversible des messages. Les membres du groupe peuvent envoyer des messages directs. - Les messages directs entre membres sont interdits dans ce groupe. + Les messages directs entre membres sont interdits dans ce groupe. Les destinataires voient les mises à jour au fur et à mesure que vous les tapez. Vérifier la sécurité de la connexion Comparez les codes de sécurité avec vos contacts. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index d8ce80884e..4f80e33166 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -402,7 +402,7 @@ %d ismerős kiválasztva Engedélyezés %dhónap - A közvetlen üzenetek küldése a tagok között le van tiltva ebben a csoportban. + A közvetlen üzenetek küldése a tagok között le van tiltva ebben a csoportban. %d perc Az adatbázis egy véletlenszerű jelmondattal van titkosítva. Exportálás előtt változtassa meg a jelmondatot. Kézbesítés jelentések letiltása a csoportok számára? diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml index a081eb37bf..1050f1d575 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml @@ -564,7 +564,7 @@ Anggota grup dapat hapus pesan terkirim secara permanen. (24 jam) Anggota grup dapat mengirim pesan suara. Hapus pesan yang tidak dapat dibatalkan dilarang di grup ini. - Pesan pribadi antar anggota dilarang di grup ini. + Pesan pribadi antar anggota dilarang di grup ini. Anggota grup dapat kirim tautan SimpleX. %d jam %d jam diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index 0b827df4d7..2f41824bd1 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -289,7 +289,7 @@ Il database è crittografato con una password casuale, puoi cambiarla. La password del database è necessaria per aprire la chat. Elimina - I messaggi diretti tra i membri sono vietati in questo gruppo. + I messaggi diretti tra i membri sono vietati in questo gruppo. Inserisci il tuo nome: File Svuota chat @@ -2156,7 +2156,7 @@ Condizioni accettate il: %s. Operatore %s.]]> - %s.]]> + %s.]]> %s.]]> %s.]]> Accetta le condizioni @@ -2168,7 +2168,7 @@ Usa per i messaggi Vedi le condizioni %s.]]> - %s, accetta le condizioni d'uso.]]> + %s, accetta le condizioni d\'uso.]]> Condizioni d\'uso Apri le modifiche Apri le condizioni @@ -2188,7 +2188,7 @@ Server di multimediali e file aggiunti Indirizzo o link una tantum? Impostazioni dell\'indirizzo - con un solo contatto - condividilo di persona o tramite un messenger.]]> + con un solo contatto - condividilo di persona o tramite un messenger.]]> Le condizioni verranno accettate per gli operatori attivati dopo 30 giorni. Le condizioni verranno accettate il: %s. Errore di accettazione delle condizioni diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml index 01c19a20f0..d9ddd08a57 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml @@ -382,7 +382,7 @@ %d שבועות שמות שונים, אווטארים ובידוד תעבורה. ישיר - הודעות ישירות בין חברי קבוצה אסורות בקבוצה זו. + הודעות ישירות בין חברי קבוצה אסורות בקבוצה זו. הזן את שמך: שם תצוגה אינו יכול להכיל רווחים. %d חודשים diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml index 83b29a0b4c..c6775f6639 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml @@ -250,7 +250,7 @@ 招待が期限切れました! サーバを削除 端末認証がオフです。SimpleXロックを解除します。 - このグループではメンバー間のダイレクトメッセージが無効です。 + このグループではメンバー間のダイレクトメッセージが無効です。 このグループでは消えるメッセージが無効です。 %d 分 %d 週 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml index 8395f5ea48..c03b4e648b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml @@ -290,7 +290,7 @@ 다운그레이드하고 채팅 열기 다이렉트 메시지 사라지는 메시지 - 이 그룹에서는 멤버들의 다이렉트 메시지가 금지되어 있어요. + 이 그룹에서는 멤버들의 다이렉트 메시지가 금지되어 있어요. %d초 %d 초 %d시 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml index da7738f49e..5db1442fc6 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml @@ -507,7 +507,7 @@ Išeiti iš grupės\? Nežinoma duomenų bazės klaida: %s Profilis ir ryšiai su serveriu - Tiesioginės žinutės tarp narių šioje grupėje yra uždraustos. + Tiesioginės žinutės tarp narių šioje grupėje yra uždraustos. Garso/vaizdo skambučiai " \nPrieinama versijoje v5.1" diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index 3d3aac1957..26ec40da60 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -254,7 +254,7 @@ Apparaatverificatie is uitgeschakeld. SimpleX Vergrendelen uitschakelen. Vul uw naam in: Apparaatverificatie is niet ingeschakeld. Je kunt SimpleX Vergrendelen inschakelen via Instellingen zodra je apparaatverificatie hebt ingeschakeld. - Directe berichten tussen leden zijn verboden in deze groep. + Directe berichten tussen leden zijn verboden in deze groep. %d bestand(en) met een totale grootte van %s %d uur Uitzetten diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml index 10f4f79d47..7b46d10921 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml @@ -807,7 +807,7 @@ Zarówno Ty, jak i Twój kontakt możecie wysyłać znikające wiadomości. Kontakty mogą oznaczać wiadomości do usunięcia; będziesz mógł je zobaczyć. Usuń po - Bezpośrednie wiadomości między członkami są zabronione w tej grupie. + Bezpośrednie wiadomości między członkami są zabronione w tej grupie. Znikające wiadomości są zabronione na tym czacie. %dm %d min diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml index 8b16e01b4e..034e5d19db 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml @@ -333,7 +333,7 @@ Funcionalidades experimentais Erro ao criar o link de grupo Erro ao excluir o link de grupo - Mensagens diretas entre membros são proibidas neste grupo. + Mensagens diretas entre membros são proibidas neste grupo. %dh %d horas anônimo via link de endereço de contato diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml index 02e5547e04..5cdc67e9e5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml @@ -486,7 +486,7 @@ Atualização da base de dados %d semana %d semanas - Mensagens diretas entre membros são proibidas neste grupo. + Mensagens diretas entre membros são proibidas neste grupo. Eliminar fila Transferir ficheiro Ficheiro diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml index 59b355fd2b..bcd0848e8d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -879,7 +879,7 @@ Разрешить отправлять голосовые сообщения. Запретить отправлять голосовые сообщений. Члены группы могут посылать прямые сообщения. - Прямые сообщения между членами группы запрещены. + Прямые сообщения между членами группы запрещены. Члены группы могут необратимо удалять отправленные сообщения. (24 часа) Необратимое удаление сообщений запрещено в этой группе. Члены группы могут отправлять голосовые сообщения. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml index 2487c7d5cd..a0027df1d8 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml @@ -480,7 +480,7 @@ สมาชิกกลุ่มสามารถส่งข้อความที่จะหายไปหลังจากเวลาที่กำหนดหลังการอ่าน (disappearing messages) ได้ สมาชิกกลุ่มสามารถส่งข้อความโดยตรงได้ สมาชิกกลุ่มสามารถส่งข้อความเสียง - ข้อความโดยตรงระหว่างสมาชิกเป็นสิ่งต้องห้ามในกลุ่มนี้ + ข้อความโดยตรงระหว่างสมาชิกเป็นสิ่งต้องห้ามในกลุ่มนี้ ข้อความที่จะหายไปหลังจากเวลาที่กำหนดหลังการอ่าน (disappearing messages) เป็นสิ่งต้องห้ามในกลุ่มนี้ สมาชิกกลุ่มสามารถเพิ่มการแสดงปฏิกิริยาต่อข้อความได้ ลบหลังจาก diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml index 8df7457e0f..67b6226b0e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml @@ -524,7 +524,7 @@ %s üyesi için şifreleme kabul edildi doğrudan Yeniden gösterme - Bu grupta üyeler arası doğrudan mesajlaşma yasaklıdır. + Bu grupta üyeler arası doğrudan mesajlaşma yasaklıdır. konuşulan kişi için etkinleşti senin için etkinleştirildi %d sn diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index fcae74db1b..73daa373c1 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -748,7 +748,7 @@ Заборонити реакції на повідомлення. Самознищувальні повідомлення заборонені в цій групі. Учасники групи можуть надсилати приватні повідомлення. - Приватні повідомлення між учасниками заборонені в цій групі. + Приватні повідомлення між учасниками заборонені в цій групі. Учасники групи можуть назавжди видаляти відправлені повідомлення. (24 години) Назавжди видалення повідомлень заборонене в цій групі. Голосові повідомлення заборонені в цій групі. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml index f02f7abc15..c9b30c652a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml @@ -503,7 +503,7 @@ Đã xóa Lỗi xóa %d sự kiện nhóm - Tin nhắn trực tiếp giữa các thành viên bị cấm trong nhóm này. + Tin nhắn trực tiếp giữa các thành viên bị cấm trong nhóm này. %d tệp với tổng kích thước là %s phần di dời khác nhau trong ứng dụng/cơ sở dữ liệu: %s / %s Tin nhắn trực tiếp diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index 4c0b66d216..9df7b74c33 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -423,7 +423,7 @@ 翻转相机 启用自动删除消息? 用于控制台 - 此群中禁止成员之间私信。 + 此群中禁止成员之间私信。 该组禁止限时消息。 群组成员可以不可逆地删除已发送的消息。(24小时) 群组成员可以私信。 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml index 22d226829e..11a086f795 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml @@ -752,7 +752,7 @@ 自動銷毀訊息於這個聊天室內是禁用的。 不可逆地刪除訊息於這個聊天室內是禁用的。 只有你可以傳送語音訊息。 - 私訊群組內的成員於這個群組內是禁用的。 + 私訊群組內的成員於這個群組內是禁用的。 群組內的成員可以不可逆地刪除訊息。(24小時) 語音訊息 改善伺服器配置 From a182cf5730e1d11575a9229dd2213bf0156645b8 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 3 Dec 2024 18:23:24 +0000 Subject: [PATCH 166/567] ui, site: v6.2 whats new, business (#5309) * ui, site: v6.2 whats new, business * icon Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> * business Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> * typo Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> * typo Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --- .../Views/Onboarding/WhatsNewView.swift | 9 ++- .../common/views/onboarding/WhatsNewView.kt | 9 ++- .../commonMain/resources/MR/base/strings.xml | 2 + ...ork-v6-2-servers-by-flux-business-chats.md | 60 +++++++++++++++++++ docs/BUSINESS.md | 60 +++++++++++++++++++ website/langs/en.json | 1 + website/src/_data/docs_dropdown.json | 12 ++-- website/src/_data/docs_sidebar.json | 1 + website/src/_includes/navbar.html | 8 +++ 9 files changed, 150 insertions(+), 12 deletions(-) create mode 100644 blog/20241210-simplex-network-v6-2-servers-by-flux-business-chats.md create mode 100755 docs/BUSINESS.md diff --git a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift index 92b2820681..182c5652d7 100644 --- a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift +++ b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift @@ -520,14 +520,19 @@ private let versionDescriptions: [VersionDescription] = [ ] ), VersionDescription( - version: "v6.2 (beta.1)", - post: URL(string: "https://simplex.chat/blog/20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.html"), + version: "v6.2", + post: URL(string: "https://simplex.chat/blog/20241210-simplex-network-v6-2-servers-by-flux-business-chats.html"), features: [ .view(FeatureView( icon: nil, title: "Network decentralization", view: { NewOperatorsView() } )), + .feature(Description( + icon: "briefcase", + title: "Business chats", + description: "Privacy for your customers." + )), .feature(Description( icon: "bolt", title: "More reliable notifications", diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt index 8eb89931b3..a5cb944f0a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt @@ -724,8 +724,8 @@ private val versionDescriptions: List = listOf( ), ), VersionDescription( - version = "v6.2-beta.1", - post = "https://simplex.chat/blog/20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.html", + version = "v6.2", + post = "https://simplex.chat/blog/20241210-simplex-network-v6-2-servers-by-flux-business-chats.html", features = listOf( VersionFeature.FeatureView( icon = null, @@ -749,6 +749,11 @@ private val versionDescriptions: List = listOf( } } ), + VersionFeature.FeatureDescription( + icon = MR.images.ic_work, + titleId = MR.strings.v6_2_business_chats, + descrId = MR.strings.v6_2_business_chats_descr + ), VersionFeature.FeatureDescription( icon = MR.images.ic_chat, titleId = MR.strings.v6_2_improved_chat_navigation, diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 7291c8cbb1..ddf8805e8a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -2175,6 +2175,8 @@ for better metadata privacy. Improved chat navigation - Open chat on the first unread message.\n- Jump to quoted messages. + Business chats + Privacy for your customers. View updated conditions diff --git a/blog/20241210-simplex-network-v6-2-servers-by-flux-business-chats.md b/blog/20241210-simplex-network-v6-2-servers-by-flux-business-chats.md new file mode 100644 index 0000000000..d59d8e6003 --- /dev/null +++ b/blog/20241210-simplex-network-v6-2-servers-by-flux-business-chats.md @@ -0,0 +1,60 @@ +--- +layout: layouts/article.html +title: "Servers operated by Flux - true privacy and decentralization for all users" +date: 2024-12-10 +# previewBody: blog_previews/20241210.html +# image: images/simplexonflux.png +# imageWide: true +draft: true +permalink: "/blog/20241210-simplex-network-v6-2-servers-by-flux-business-chats.html" +--- + +# SimpleX network: preset servers operated by Flux, business chats and more with v6.2 of the apps + +**Will be published:** Dec 10, 2024 + +This is a placeholder page for the upcoming v6.2 release announcement! + +- Preset servers are now operated by two companies - SimpleX Chat and Flux. Read [this post](./20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.md). +- Business chats to provide support from your business to users of SimpleX network. Read [this page](../docs/BUSINESS.md). +- and more! + +## SimpleX network + +Some links to answer the most common questions: + +[How can SimpleX deliver messages without user identifiers](./20220511-simplex-chat-v2-images-files.md#the-first-messaging-platform-without-user-identifiers). + +[What are the risks to have identifiers assigned to the users](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md#why-having-users-identifiers-is-bad-for-the-users). + +[Technical details and limitations](https://github.com/simplex-chat/simplex-chat#privacy-and-security-technical-details-and-limitations). + +[Frequently asked questions](../docs/FAQ.md). + +Please also see our [website](https://simplex.chat). + +## Please support us with your donations + +Huge *thank you* to everybody who donated to SimpleX Chat! + +Prioritizing users privacy and security, and also raising the investment, would have been impossible without your support and donations. + +Also, funding the work to transition the protocols to non-profit governance model would not have been possible without the donations we received from the users. + +Our pledge to our users is that SimpleX protocols are and will remain open, and in public domain, so anybody can build the future implementations of the clients and the servers. We are building SimpleX platform based on the same principles as email and web, but much more private and secure. + +Your donations help us raise more funds — any amount, even the price of the cup of coffee, makes a big difference for us. + +See [this section](https://github.com/simplex-chat/simplex-chat/#please-support-us-with-your-donations) for the ways to donate. + +Thank you, + +Evgeny + +SimpleX Chat founder + +[1] You can also to self-host your own SimpleX servers on [Flux decentralized cloud](https://home.runonflux.io/apps/marketplace?q=simplex). + +[2] The probability of connection being de-anonymized and the number of random server choices follow this equation: `(1 - s ^ 2) ^ n = 1 - p`, where `s` is the share of attacker-controlled servers in the network, `n` is the number of random choices of entry and exit nodes for the circuit, and `p` is the probability of both entry and exit nodes, and the connection privacy being compromised. Substituting `0.02` (2%) for `s`, `0.5` (50%) for `p`, and solving this equation for `n` we obtain that `1733` random circuits have 50% probability of privacy being compromised. + +Also see [this presentation about Tor](https://ritter.vg/p/tor-v1.6.pdf), specifically the approximate calculations on page 76, and also [Tor project post](https://blog.torproject.org/announcing-vanguards-add-onion-services/) about the changes that made attack on hidden service anonymity harder, but still viable in case the it is used for a long time. diff --git a/docs/BUSINESS.md b/docs/BUSINESS.md new file mode 100755 index 0000000000..aed5fd877c --- /dev/null +++ b/docs/BUSINESS.md @@ -0,0 +1,60 @@ +--- +title: SimpleX for business +revision: 03.12.2024 +--- + +# Using SimpleX Chat in business + +SimpleX Chat (aka SimpleX) is a decentralized communication network that provides private and secure messaging. Its users are rapidly growing, and providing customer services via SimpleX can offer you a unique opportunity to engage people who are the most enthusiastic about trying out early stage technology products and services. + +This document aims to help you make the best use of SimpleX Chat if you choose to engage with its users. + +## Communcate with customers via business address + +In the same way you can connect to our "SimpleX Chat team" profile via the app, you can provide the address for your existing and prospective customers: +- to buy your product and services via chat, +- to ask any questions, make suggestions and provide feedback, +- to discover more information about your business. + +Customers who value privacy and security, and want to engage with you without sharing any personal data and minimizing any metadata that is shared with you, will be really happy to use this communication channel. + +From v6.2 SimpleX Chat supports business addresses. Their design allows you to accept requests from multiple customers, with the app creating a new business chat with each of them. + +Business chats operate in a way similar to dedicated customer support systems by combining features of direct conversations and groups, and the only widely used messenger that provides such functionality is WeChat with Chinese business accounts. + +When a customer connects to your business via the business contact address, a new conversation is created. Similarly to how direct chats work, the customer will see the name and logo of your business, and you will see the name and avatar of your customer. + +But the business conversation works as a group - once the customer is connected, other people from the business can be added to the conversation, and the customers will see who are they talking with. This can be used to transfer business conversation to another person, or for escalation - in the same way as with the dedicated support systems. + +SimpleX Chat profile with the business address can be used in one of these ways: +- for small teams it can be managed by one person running the app on their desktop computer, who would respond to customer questions and manually add to the conversation other people in the business, as required. +- if you have multiple support agents, you can run business profile in CLI client running in cloud VM or on any machine with high speed Internet (see Technical advice below), and they can connect to this client from desktop client, in turns. This is how we use our business profile ourselves, even though it requires some configuration. You can manage 100s of thousands of connected customers in this way. +- For larger teams, it would be appropriate to have this profile managed by chat bot that can reply to some simple questions, and to add support agents, based on their availability and the questions asked. These scenarios would require programming a chat bot, and we are currently working to simplify it. + +In any case, it is important that the client application remains running and connected to the Internet for you to receive support requests. + +## Customer broadcasts + +While currently supported only via CLI clients (or via chat console in desktop and mobile clients), it can be used to broadcast important announcements to all connected customers. We will be adding this feature to desktop clients soon. We use it to broadcast release updates to a very large number of users who are connected to our own support profile. + +## Community groups and promotion in group directory + +In addition to providing support to clients individually, you can create a community group, and promote it via our experimental and growing [directory of public groups](./DIRECTORY.md). Community groups require ongoing moderation. + +## Limitations + +With all advantages in privacy and security of e2e encryption in SimpleX Chat, there are some important limitations: +- **protecting your data from loss is your responsibility**. This is the price of privacy - if you lose your device, or database passphrase, there is absolutely no way we would be able to support you to recover access. There are ways to work around these limitations. +- **you cannot access the same profile from multiple devices**. For all communication products it's a basic expectation, and yet there is not a single one that delivered it without some very serious privacy and security compromises. Better solutions are possible, and we will be implementing it, but reasonably secure approach is much more complex to implement than what is affordable at the current stage. You can access mobile or CLI profile from desktop, and the latter allows to use one profile by multiple people in turns, as we explain below. +- **your owner role in the groups cannot be restored if you lose the device**. The solution is to create owner profiles on multiple devices for all your important groups. This way if you lose device or data for one of profiles, you won't lose control of the group, and you can add a new one. Think about it as about keys to your cryptowallet. +- **current groups are highly experimental**. Message delivery can be delayed or fail in some cases, lists of members can be out of sync. There are approaches to make them more stable we use for our groups. + +## Technical advice + +### Running SimpleX Chat in the cloud + +### Using remote profiles via Desktop app + +## Organizations using SimpleX Chat for customer service, support and sales + +Please let us know if you use SimpleX Chat to communicate with your customers and want to be included in this list. diff --git a/website/langs/en.json b/website/langs/en.json index a9b31f2a6e..e57b3375de 100644 --- a/website/langs/en.json +++ b/website/langs/en.json @@ -238,6 +238,7 @@ "docs-dropdown-10": "Transparency", "docs-dropdown-11": "FAQ", "docs-dropdown-12": "Security", + "docs-dropdown-14": "SimpleX for business", "newer-version-of-eng-msg": "There is a newer version of this page in English.", "click-to-see": "Click to see", "menu": "Menu", diff --git a/website/src/_data/docs_dropdown.json b/website/src/_data/docs_dropdown.json index 88fd5826dd..f97c2aeff2 100644 --- a/website/src/_data/docs_dropdown.json +++ b/website/src/_data/docs_dropdown.json @@ -5,12 +5,12 @@ "url": "/docs/simplex.html" }, { - "title": "docs-dropdown-8", - "url": "/docs/directory.html" + "title": "docs-dropdown-14", + "url": "/docs/business.html" }, { - "title": "docs-dropdown-2", - "url": "/docs/android.html" + "title": "docs-dropdown-8", + "url": "/docs/directory.html" }, { "title": "docs-dropdown-3", @@ -28,10 +28,6 @@ "title": "docs-dropdown-6", "url": "/docs/webrtc.html" }, - { - "title": "docs-dropdown-7", - "url": "/docs/translations.html" - }, { "title": "docs-dropdown-9", "url": "/downloads/" diff --git a/website/src/_data/docs_sidebar.json b/website/src/_data/docs_sidebar.json index e9ccb7ce02..e370ccc078 100644 --- a/website/src/_data/docs_sidebar.json +++ b/website/src/_data/docs_sidebar.json @@ -18,6 +18,7 @@ "menu": "Reference", "data": [ "SIMPLEX.md", + "BUSINESS.md", "DIRECTORY.md", "ANDROID.md", "CLI.md", diff --git a/website/src/_includes/navbar.html b/website/src/_includes/navbar.html index 5f9d4be3f3..6e69c559b0 100644 --- a/website/src/_includes/navbar.html +++ b/website/src/_includes/navbar.html @@ -80,6 +80,14 @@
+
  • {{ "docs-dropdown-7" | i18n({}, lang ) | safe }} +
  • +
  • {{ "docs-dropdown-2" | i18n({}, lang ) | safe }} +
  • {{ "chat-bot-example" | i18n({}, lang ) | safe }} From f3be723cde619ff4ce93402dc0e15cdeea42a85f Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 3 Dec 2024 18:40:58 +0000 Subject: [PATCH 167/567] ios: export localizations --- .../bg.xcloc/Localized Contents/bg.xliff | 130 +++++++++++----- .../cs.xcloc/Localized Contents/cs.xliff | 128 ++++++++++++---- .../de.xcloc/Localized Contents/de.xliff | 130 +++++++++++----- .../en.xcloc/Localized Contents/en.xliff | 145 +++++++++++++----- .../es.xcloc/Localized Contents/es.xliff | 130 +++++++++++----- .../fi.xcloc/Localized Contents/fi.xliff | 128 ++++++++++++---- .../fr.xcloc/Localized Contents/fr.xliff | 130 +++++++++++----- .../hu.xcloc/Localized Contents/hu.xliff | 130 +++++++++++----- .../it.xcloc/Localized Contents/it.xliff | 130 +++++++++++----- .../ja.xcloc/Localized Contents/ja.xliff | 128 ++++++++++++---- .../nl.xcloc/Localized Contents/nl.xliff | 130 +++++++++++----- .../pl.xcloc/Localized Contents/pl.xliff | 130 +++++++++++----- .../ru.xcloc/Localized Contents/ru.xliff | 130 +++++++++++----- .../th.xcloc/Localized Contents/th.xliff | 128 ++++++++++++---- .../tr.xcloc/Localized Contents/tr.xliff | 130 +++++++++++----- .../uk.xcloc/Localized Contents/uk.xliff | 130 +++++++++++----- .../Localized Contents/zh-Hans.xliff | 130 +++++++++++----- 17 files changed, 1626 insertions(+), 591 deletions(-) diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index e483406fe5..0e8e30d8aa 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -1199,6 +1199,10 @@ Business address No comment provided by engineer. + + Business chats + 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). Чрез чат профил (по подразбиране) или [чрез връзка](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (БЕТА). @@ -1334,6 +1338,10 @@ Change user profiles authentication reason + + Chat + No comment provided by engineer. + Chat already exists No comment provided by engineer. @@ -1412,6 +1420,14 @@ Chat theme No comment provided by engineer. + + Chat will be deleted for all members - this cannot be undone! + No comment provided by engineer. + + + Chat will be deleted for you - this cannot be undone! + No comment provided by engineer. + Chats Чатове @@ -2122,6 +2138,10 @@ This is your own one-time link! Изтрий и уведоми контакт No comment provided by engineer. + + Delete chat + No comment provided by engineer. + Delete chat profile Изтрий чат профила @@ -2132,6 +2152,10 @@ This is your own one-time link! Изтриване на чат профила? No comment provided by engineer. + + Delete chat? + No comment provided by engineer. + Delete connection Изтрий връзката @@ -2384,6 +2408,10 @@ This is your own one-time link! Лични съобщения chat feature + + Direct messages between members are prohibited in this chat. + No comment provided by engineer. + Direct messages between members are prohibited. Личните съобщения между членовете са забранени в тази група. @@ -3502,41 +3530,6 @@ Error: %2$@ Групови линкове No comment provided by engineer. - - Members can add message reactions. - Членовете на групата могат да добавят реакции към съобщенията. - No comment provided by engineer. - - - Members can irreversibly delete sent messages. (24 hours) - Членовете на групата могат необратимо да изтриват изпратените съобщения. (24 часа) - No comment provided by engineer. - - - Members can send SimpleX links. - Членовете на групата могат да изпращат SimpleX линкове. - No comment provided by engineer. - - - Members can send direct messages. - Членовете на групата могат да изпращат лични съобщения. - No comment provided by engineer. - - - Members can send disappearing messages. - Членовете на групата могат да изпращат изчезващи съобщения. - No comment provided by engineer. - - - Members can send files and media. - Членовете на групата могат да изпращат файлове и медия. - No comment provided by engineer. - - - Members can send voice messages. - Членовете на групата могат да изпращат гласови съобщения. - No comment provided by engineer. - Group message: Групово съобщение: @@ -3929,6 +3922,10 @@ More improvements are coming soon! Покани членове No comment provided by engineer. + + Invite to chat + No comment provided by engineer. + Invite to group Покани в групата @@ -4085,6 +4082,14 @@ This is your link for group %@! Напусни swipe action + + Leave chat + No comment provided by engineer. + + + Leave chat? + No comment provided by engineer. + Leave group Напусни групата @@ -4212,6 +4217,10 @@ This is your link for group %@! Member inactive item status text + + Member role will be changed to "%@". All chat members will be notified. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. Ролята на члена ще бъде променена на "%@". Всички членове на групата ще бъдат уведомени. @@ -4222,11 +4231,50 @@ This is your link for group %@! Ролята на члена ще бъде променена на "%@". Членът ще получи нова покана. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! Членът ще бъде премахнат от групата - това не може да бъде отменено! No comment provided by engineer. + + Members can add message reactions. + Членовете на групата могат да добавят реакции към съобщенията. + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + Членовете на групата могат необратимо да изтриват изпратените съобщения. (24 часа) + No comment provided by engineer. + + + Members can send SimpleX links. + Членовете на групата могат да изпращат SimpleX линкове. + No comment provided by engineer. + + + Members can send direct messages. + Членовете на групата могат да изпращат лични съобщения. + No comment provided by engineer. + + + Members can send disappearing messages. + Членовете на групата могат да изпращат изчезващи съобщения. + No comment provided by engineer. + + + Members can send files and media. + Членовете на групата могат да изпращат файлове и медия. + No comment provided by engineer. + + + Members can send voice messages. + Членовете на групата могат да изпращат гласови съобщения. + No comment provided by engineer. + Menus No comment provided by engineer. @@ -4764,6 +4812,10 @@ Requires compatible VPN. Няма се използват Onion хостове. No comment provided by engineer. + + Only chat owners can change preferences. + No comment provided by engineer. + Only client devices store user profiles, contacts, groups, and messages. Само потребителските устройства съхраняват потребителски профили, контакти, групи и съобщения, изпратени с **двуслойно криптиране от край до край**. @@ -5142,6 +5194,10 @@ Error: %@ Поверителност и сигурност No comment provided by engineer. + + Privacy for your customers. + No comment provided by engineer. + Privacy redefined Поверителността преосмислена @@ -7882,6 +7938,10 @@ Repeat connection request? Все още ще получавате обаждания и известия от заглушени профили, когато са активни. No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Ще спрете да получавате съобщения от тази група. Историята на чата ще бъде запазена. diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index 7efd941d11..10f6355692 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -1161,6 +1161,10 @@ Business address No comment provided by engineer. + + Business chats + 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). @@ -1293,6 +1297,10 @@ Change user profiles authentication reason + + Chat + No comment provided by engineer. + Chat already exists No comment provided by engineer. @@ -1369,6 +1377,14 @@ Chat theme No comment provided by engineer. + + Chat will be deleted for all members - this cannot be undone! + No comment provided by engineer. + + + Chat will be deleted for you - this cannot be undone! + No comment provided by engineer. + Chats Chaty @@ -2051,6 +2067,10 @@ This is your own one-time link! Delete and notify contact No comment provided by engineer. + + Delete chat + No comment provided by engineer. + Delete chat profile Smazat chat profil @@ -2061,6 +2081,10 @@ This is your own one-time link! Smazat chat profil? No comment provided by engineer. + + Delete chat? + No comment provided by engineer. + Delete connection Smazat připojení @@ -2309,6 +2333,10 @@ This is your own one-time link! Přímé zprávy chat feature + + Direct messages between members are prohibited in this chat. + No comment provided by engineer. + Direct messages between members are prohibited. Přímé zprávy mezi členy jsou v této skupině zakázány. @@ -3389,40 +3417,6 @@ Error: %2$@ Odkazy na skupiny No comment provided by engineer. - - Members can add message reactions. - Členové skupin mohou přidávat reakce na zprávy. - No comment provided by engineer. - - - Members can irreversibly delete sent messages. (24 hours) - Členové skupiny mohou nevratně mazat odeslané zprávy. (24 hodin) - No comment provided by engineer. - - - Members can send SimpleX links. - No comment provided by engineer. - - - Members can send direct messages. - Členové skupiny mohou posílat přímé zprávy. - No comment provided by engineer. - - - Members can send disappearing messages. - Členové skupiny mohou posílat mizící zprávy. - No comment provided by engineer. - - - Members can send files and media. - Členové skupiny mohou posílat soubory a média. - No comment provided by engineer. - - - Members can send voice messages. - Členové skupiny mohou posílat hlasové zprávy. - No comment provided by engineer. - Group message: Skupinová zpráva: @@ -3800,6 +3794,10 @@ More improvements are coming soon! Pozvat členy No comment provided by engineer. + + Invite to chat + No comment provided by engineer. + Invite to group Pozvat do skupiny @@ -3948,6 +3946,14 @@ This is your link for group %@! Opustit swipe action + + Leave chat + No comment provided by engineer. + + + Leave chat? + No comment provided by engineer. + Leave group Opustit skupinu @@ -4072,6 +4078,10 @@ This is your link for group %@! Member inactive item status text + + Member role will be changed to "%@". All chat members will be notified. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. Role člena se změní na "%@". Všichni členové skupiny budou upozorněni. @@ -4082,11 +4092,49 @@ This is your link for group %@! Role člena se změní na "%@". Člen obdrží novou pozvánku. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! Člen bude odstraněn ze skupiny - toto nelze vzít zpět! No comment provided by engineer. + + Members can add message reactions. + Členové skupin mohou přidávat reakce na zprávy. + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + Členové skupiny mohou nevratně mazat odeslané zprávy. (24 hodin) + No comment provided by engineer. + + + Members can send SimpleX links. + No comment provided by engineer. + + + Members can send direct messages. + Členové skupiny mohou posílat přímé zprávy. + No comment provided by engineer. + + + Members can send disappearing messages. + Členové skupiny mohou posílat mizící zprávy. + No comment provided by engineer. + + + Members can send files and media. + Členové skupiny mohou posílat soubory a média. + No comment provided by engineer. + + + Members can send voice messages. + Členové skupiny mohou posílat hlasové zprávy. + No comment provided by engineer. + Menus No comment provided by engineer. @@ -4605,6 +4653,10 @@ Vyžaduje povolení sítě VPN. Onion hostitelé nebudou použiti. No comment provided by engineer. + + Only chat owners can change preferences. + No comment provided by engineer. + Only client devices store user profiles, contacts, groups, and messages. Pouze klientská zařízení ukládají uživatelské profily, kontakty, skupiny a zprávy odeslané s **2vrstvým šifrováním typu end-to-end**. @@ -4967,6 +5019,10 @@ Error: %@ Ochrana osobních údajů a zabezpečení No comment provided by engineer. + + Privacy for your customers. + No comment provided by engineer. + Privacy redefined Nové vymezení soukromí @@ -7614,6 +7670,10 @@ Repeat connection request? Stále budete přijímat volání a upozornění od umlčených profilů pokud budou aktivní. No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Přestanete dostávat zprávy z této skupiny. Historie chatu bude zachována. diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index afce946eea..35e11b4861 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -1246,6 +1246,10 @@ Business address No comment provided by engineer. + + Business chats + 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). @@ -1388,6 +1392,10 @@ Chat-Profile wechseln authentication reason + + Chat + No comment provided by engineer. + Chat already exists No comment provided by engineer. @@ -1471,6 +1479,14 @@ Chat-Design No comment provided by engineer. + + Chat will be deleted for all members - this cannot be undone! + No comment provided by engineer. + + + Chat will be deleted for you - this cannot be undone! + No comment provided by engineer. + Chats Chats @@ -2225,6 +2241,10 @@ Das ist Ihr eigener Einmal-Link! Kontakt löschen und benachrichtigen No comment provided by engineer. + + Delete chat + No comment provided by engineer. + Delete chat profile Chat-Profil löschen @@ -2235,6 +2255,10 @@ Das ist Ihr eigener Einmal-Link! Chat-Profil löschen? No comment provided by engineer. + + Delete chat? + No comment provided by engineer. + Delete connection Verbindung löschen @@ -2500,6 +2524,10 @@ Das ist Ihr eigener Einmal-Link! Direkte Nachrichten chat feature + + Direct messages between members are prohibited in this chat. + No comment provided by engineer. + Direct messages between members are prohibited. In dieser Gruppe sind Direktnachrichten zwischen Mitgliedern nicht erlaubt. @@ -3672,41 +3700,6 @@ Fehler: %2$@ Gruppen-Links No comment provided by engineer. - - Members can add message reactions. - Gruppenmitglieder können eine Reaktion auf Nachrichten geben. - No comment provided by engineer. - - - Members can irreversibly delete sent messages. (24 hours) - Gruppenmitglieder können gesendete Nachrichten unwiederbringlich löschen. (24 Stunden) - No comment provided by engineer. - - - Members can send SimpleX links. - Gruppenmitglieder können SimpleX-Links senden. - No comment provided by engineer. - - - Members can send direct messages. - Gruppenmitglieder können Direktnachrichten versenden. - No comment provided by engineer. - - - Members can send disappearing messages. - Gruppenmitglieder können verschwindende Nachrichten senden. - No comment provided by engineer. - - - Members can send files and media. - Gruppenmitglieder können Dateien und Medien senden. - No comment provided by engineer. - - - Members can send voice messages. - Gruppenmitglieder können Sprachnachrichten versenden. - No comment provided by engineer. - Group message: Grppennachricht: @@ -4106,6 +4099,10 @@ Weitere Verbesserungen sind bald verfügbar! Mitglieder einladen No comment provided by engineer. + + Invite to chat + No comment provided by engineer. + Invite to group In Gruppe einladen @@ -4264,6 +4261,14 @@ Das ist Ihr Link für die Gruppe %@! Verlassen swipe action + + Leave chat + No comment provided by engineer. + + + Leave chat? + No comment provided by engineer. + Leave group Gruppe verlassen @@ -4394,6 +4399,10 @@ Das ist Ihr Link für die Gruppe %@! Mitglied inaktiv item status text + + Member role will be changed to "%@". All chat members will be notified. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. Die Mitgliederrolle wird auf "%@" geändert. Alle Mitglieder der Gruppe werden benachrichtigt. @@ -4404,11 +4413,50 @@ Das ist Ihr Link für die Gruppe %@! Die Mitgliederrolle wird auf "%@" geändert. Das Mitglied wird eine neue Einladung erhalten. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! Das Mitglied wird aus der Gruppe entfernt. Dies kann nicht rückgängig gemacht werden! No comment provided by engineer. + + Members can add message reactions. + Gruppenmitglieder können eine Reaktion auf Nachrichten geben. + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + Gruppenmitglieder können gesendete Nachrichten unwiederbringlich löschen. (24 Stunden) + No comment provided by engineer. + + + Members can send SimpleX links. + Gruppenmitglieder können SimpleX-Links senden. + No comment provided by engineer. + + + Members can send direct messages. + Gruppenmitglieder können Direktnachrichten versenden. + No comment provided by engineer. + + + Members can send disappearing messages. + Gruppenmitglieder können verschwindende Nachrichten senden. + No comment provided by engineer. + + + Members can send files and media. + Gruppenmitglieder können Dateien und Medien senden. + No comment provided by engineer. + + + Members can send voice messages. + Gruppenmitglieder können Sprachnachrichten versenden. + No comment provided by engineer. + Menus Menüs @@ -4982,6 +5030,10 @@ Dies erfordert die Aktivierung eines VPNs. Onion-Hosts werden nicht verwendet. No comment provided by engineer. + + Only chat owners can change preferences. + No comment provided by engineer. + Only client devices store user profiles, contacts, groups, and messages. Nur die Endgeräte speichern die Benutzerprofile, Kontakte, Gruppen und Nachrichten, welche über eine **2-Schichten Ende-zu-Ende-Verschlüsselung** gesendet werden. @@ -5377,6 +5429,10 @@ Fehler: %@ Datenschutz & Sicherheit No comment provided by engineer. + + Privacy for your customers. + No comment provided by engineer. + Privacy redefined Datenschutz neu definiert @@ -8277,6 +8333,10 @@ Verbindungsanfrage wiederholen? Sie können Anrufe und Benachrichtigungen auch von stummgeschalteten Profilen empfangen, solange diese aktiv sind. No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Sie werden von dieser Gruppe keine Nachrichten mehr erhalten. Der Nachrichtenverlauf wird beibehalten. diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 2301f671a4..1d7f3f16be 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -1250,6 +1250,11 @@ Business address No comment provided by engineer. + + Business chats + Business chats + 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). By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). @@ -1392,6 +1397,11 @@ Change user profiles authentication reason + + Chat + Chat + No comment provided by engineer. + Chat already exists Chat already exists @@ -1477,6 +1487,16 @@ Chat theme No comment provided by engineer. + + Chat will be deleted for all members - this cannot be undone! + Chat will be deleted for all members - this cannot be undone! + No comment provided by engineer. + + + Chat will be deleted for you - this cannot be undone! + Chat will be deleted for you - this cannot be undone! + No comment provided by engineer. + Chats Chats @@ -2231,6 +2251,11 @@ This is your own one-time link! Delete and notify contact No comment provided by engineer. + + Delete chat + Delete chat + No comment provided by engineer. + Delete chat profile Delete chat profile @@ -2241,6 +2266,11 @@ This is your own one-time link! Delete chat profile? No comment provided by engineer. + + Delete chat? + Delete chat? + No comment provided by engineer. + Delete connection Delete connection @@ -2506,6 +2536,11 @@ This is your own one-time link! Direct messages chat feature + + Direct messages between members are prohibited in this chat. + Direct messages between members are prohibited in this chat. + No comment provided by engineer. + Direct messages between members are prohibited. Direct messages between members are prohibited. @@ -3678,41 +3713,6 @@ Error: %2$@ Group links No comment provided by engineer. - - Members can add message reactions. - Members can add message reactions. - No comment provided by engineer. - - - Members can irreversibly delete sent messages. (24 hours) - Members can irreversibly delete sent messages. (24 hours) - No comment provided by engineer. - - - Members can send SimpleX links. - Members can send SimpleX links. - No comment provided by engineer. - - - Members can send direct messages. - Members can send direct messages. - No comment provided by engineer. - - - Members can send disappearing messages. - Members can send disappearing messages. - No comment provided by engineer. - - - Members can send files and media. - Members can send files and media. - No comment provided by engineer. - - - Members can send voice messages. - Members can send voice messages. - No comment provided by engineer. - Group message: Group message: @@ -4112,6 +4112,11 @@ More improvements are coming soon! Invite members No comment provided by engineer. + + Invite to chat + Invite to chat + No comment provided by engineer. + Invite to group Invite to group @@ -4270,6 +4275,16 @@ This is your link for group %@! Leave swipe action + + Leave chat + Leave chat + No comment provided by engineer. + + + Leave chat? + Leave chat? + No comment provided by engineer. + Leave group Leave group @@ -4400,6 +4415,11 @@ This is your link for group %@! Member inactive item status text + + Member role will be changed to "%@". All chat members will be notified. + Member role will be changed to "%@". All chat members will be notified. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. Member role will be changed to "%@". All group members will be notified. @@ -4410,11 +4430,51 @@ This is your link for group %@! Member role will be changed to "%@". The member will receive a new invitation. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + Member will be removed from chat - this cannot be undone! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! Member will be removed from group - this cannot be undone! No comment provided by engineer. + + Members can add message reactions. + Members can add message reactions. + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + Members can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Members can send SimpleX links. + Members can send SimpleX links. + No comment provided by engineer. + + + Members can send direct messages. + Members can send direct messages. + No comment provided by engineer. + + + Members can send disappearing messages. + Members can send disappearing messages. + No comment provided by engineer. + + + Members can send files and media. + Members can send files and media. + No comment provided by engineer. + + + Members can send voice messages. + Members can send voice messages. + No comment provided by engineer. + Menus Menus @@ -4988,6 +5048,11 @@ Requires compatible VPN. Onion hosts will not be used. No comment provided by engineer. + + Only chat owners can change preferences. + Only chat owners can change preferences. + No comment provided by engineer. + Only client devices store user profiles, contacts, groups, and messages. Only client devices store user profiles, contacts, groups, and messages. @@ -5384,6 +5449,11 @@ Error: %@ Privacy & security No comment provided by engineer. + + Privacy for your customers. + Privacy for your customers. + No comment provided by engineer. + Privacy redefined Privacy redefined @@ -8286,6 +8356,11 @@ Repeat connection request? You will still receive calls and notifications from muted profiles when they are active. No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. You will stop receiving messages from this group. Chat history will be preserved. diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 647d650698..147ad6128f 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -1246,6 +1246,10 @@ Business address No comment provided by engineer. + + Business chats + 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 (predeterminado) o [por conexión](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). @@ -1388,6 +1392,10 @@ Cambiar perfil de usuario authentication reason + + Chat + No comment provided by engineer. + Chat already exists No comment provided by engineer. @@ -1471,6 +1479,14 @@ Tema de chat No comment provided by engineer. + + Chat will be deleted for all members - this cannot be undone! + No comment provided by engineer. + + + Chat will be deleted for you - this cannot be undone! + No comment provided by engineer. + Chats Chats @@ -2225,6 +2241,10 @@ This is your own one-time link! Eliminar y notificar contacto No comment provided by engineer. + + Delete chat + No comment provided by engineer. + Delete chat profile Eliminar perfil @@ -2235,6 +2255,10 @@ This is your own one-time link! ¿Eliminar perfil? No comment provided by engineer. + + Delete chat? + No comment provided by engineer. + Delete connection Eliminar conexión @@ -2500,6 +2524,10 @@ This is your own one-time link! Mensajes directos chat feature + + Direct messages between members are prohibited in this chat. + No comment provided by engineer. + Direct messages between members are prohibited. Los mensajes directos entre miembros del grupo no están permitidos. @@ -3672,41 +3700,6 @@ Error: %2$@ Enlaces de grupo No comment provided by engineer. - - Members can add message reactions. - Los miembros pueden añadir reacciones a los mensajes. - No comment provided by engineer. - - - Members can irreversibly delete sent messages. (24 hours) - Los miembros del grupo pueden eliminar mensajes de forma irreversible. (24 horas) - No comment provided by engineer. - - - Members can send SimpleX links. - Los miembros del grupo pueden enviar enlaces SimpleX. - No comment provided by engineer. - - - Members can send direct messages. - Los miembros del grupo pueden enviar mensajes directos. - No comment provided by engineer. - - - Members can send disappearing messages. - Los miembros del grupo pueden enviar mensajes temporales. - No comment provided by engineer. - - - Members can send files and media. - Los miembros del grupo pueden enviar archivos y multimedia. - No comment provided by engineer. - - - Members can send voice messages. - Los miembros del grupo pueden enviar mensajes de voz. - No comment provided by engineer. - Group message: Mensaje de grupo: @@ -4106,6 +4099,10 @@ More improvements are coming soon! Invitar miembros No comment provided by engineer. + + Invite to chat + No comment provided by engineer. + Invite to group Invitar al grupo @@ -4264,6 +4261,14 @@ This is your link for group %@! Salir swipe action + + Leave chat + No comment provided by engineer. + + + Leave chat? + No comment provided by engineer. + Leave group Salir del grupo @@ -4394,6 +4399,10 @@ This is your link for group %@! Miembro inactivo item status text + + Member role will be changed to "%@". All chat members will be notified. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. El rol del miembro cambiará a "%@" y se notificará al grupo. @@ -4404,11 +4413,50 @@ This is your link for group %@! El rol del miembro cambiará a "%@" y recibirá una invitación nueva. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! El miembro será expulsado del grupo. ¡No podrá deshacerse! No comment provided by engineer. + + Members can add message reactions. + Los miembros pueden añadir reacciones a los mensajes. + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + Los miembros del grupo pueden eliminar mensajes de forma irreversible. (24 horas) + No comment provided by engineer. + + + Members can send SimpleX links. + Los miembros del grupo pueden enviar enlaces SimpleX. + No comment provided by engineer. + + + Members can send direct messages. + Los miembros del grupo pueden enviar mensajes directos. + No comment provided by engineer. + + + Members can send disappearing messages. + Los miembros del grupo pueden enviar mensajes temporales. + No comment provided by engineer. + + + Members can send files and media. + Los miembros del grupo pueden enviar archivos y multimedia. + No comment provided by engineer. + + + Members can send voice messages. + Los miembros del grupo pueden enviar mensajes de voz. + No comment provided by engineer. + Menus Menus @@ -4982,6 +5030,10 @@ Requiere activación de la VPN. No se usarán hosts .onion. No comment provided by engineer. + + Only chat owners can change preferences. + No comment provided by engineer. + Only client devices store user profiles, contacts, groups, and messages. Sólo los dispositivos cliente almacenan perfiles de usuario, contactos, grupos y mensajes enviados con **cifrado de extremo a extremo de 2 capas**. @@ -5377,6 +5429,10 @@ Error: %@ Seguridad y Privacidad No comment provided by engineer. + + Privacy for your customers. + No comment provided by engineer. + Privacy redefined Privacidad redefinida @@ -8277,6 +8333,10 @@ Repeat connection request? Seguirás recibiendo llamadas y notificaciones de los perfiles silenciados cuando estén activos. No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Dejarás de recibir mensajes de este grupo. El historial del chat se conservará. diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index 00996b4a4f..39277bbcce 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -1154,6 +1154,10 @@ Business address No comment provided by engineer. + + Business chats + 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). Chat-profiilin mukaan (oletus) tai [yhteyden mukaan](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). @@ -1286,6 +1290,10 @@ Change user profiles authentication reason + + Chat + No comment provided by engineer. + Chat already exists No comment provided by engineer. @@ -1362,6 +1370,14 @@ Chat theme No comment provided by engineer. + + Chat will be deleted for all members - this cannot be undone! + No comment provided by engineer. + + + Chat will be deleted for you - this cannot be undone! + No comment provided by engineer. + Chats Keskustelut @@ -2044,6 +2060,10 @@ This is your own one-time link! Delete and notify contact No comment provided by engineer. + + Delete chat + No comment provided by engineer. + Delete chat profile Poista keskusteluprofiili @@ -2054,6 +2074,10 @@ This is your own one-time link! Poista keskusteluprofiili? No comment provided by engineer. + + Delete chat? + No comment provided by engineer. + Delete connection Poista yhteys @@ -2302,6 +2326,10 @@ This is your own one-time link! Yksityisviestit chat feature + + Direct messages between members are prohibited in this chat. + No comment provided by engineer. + Direct messages between members are prohibited. Yksityisviestit jäsenten välillä ovat kiellettyjä tässä ryhmässä. @@ -3379,40 +3407,6 @@ Error: %2$@ Ryhmälinkit No comment provided by engineer. - - Members can add message reactions. - Ryhmän jäsenet voivat lisätä viestireaktioita. - No comment provided by engineer. - - - Members can irreversibly delete sent messages. (24 hours) - Ryhmän jäsenet voivat poistaa lähetetyt viestit peruuttamattomasti. (24 tuntia) - No comment provided by engineer. - - - Members can send SimpleX links. - No comment provided by engineer. - - - Members can send direct messages. - Ryhmän jäsenet voivat lähettää suoraviestejä. - No comment provided by engineer. - - - Members can send disappearing messages. - Ryhmän jäsenet voivat lähettää katoavia viestejä. - No comment provided by engineer. - - - Members can send files and media. - Ryhmän jäsenet voivat lähettää tiedostoja ja mediaa. - No comment provided by engineer. - - - Members can send voice messages. - Ryhmän jäsenet voivat lähettää ääniviestejä. - No comment provided by engineer. - Group message: Ryhmäviesti: @@ -3790,6 +3784,10 @@ More improvements are coming soon! Kutsu jäseniä No comment provided by engineer. + + Invite to chat + No comment provided by engineer. + Invite to group Kutsu ryhmään @@ -3938,6 +3936,14 @@ This is your link for group %@! Poistu swipe action + + Leave chat + No comment provided by engineer. + + + Leave chat? + No comment provided by engineer. + Leave group Poistu ryhmästä @@ -4062,6 +4068,10 @@ This is your link for group %@! Member inactive item status text + + Member role will be changed to "%@". All chat members will be notified. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. Jäsenen rooli muuttuu muotoon "%@". Kaikille ryhmän jäsenille ilmoitetaan asiasta. @@ -4072,11 +4082,49 @@ This is your link for group %@! Jäsenen rooli muutetaan muotoon "%@". Jäsen saa uuden kutsun. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! Jäsen poistetaan ryhmästä - tätä ei voi perua! No comment provided by engineer. + + Members can add message reactions. + Ryhmän jäsenet voivat lisätä viestireaktioita. + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + Ryhmän jäsenet voivat poistaa lähetetyt viestit peruuttamattomasti. (24 tuntia) + No comment provided by engineer. + + + Members can send SimpleX links. + No comment provided by engineer. + + + Members can send direct messages. + Ryhmän jäsenet voivat lähettää suoraviestejä. + No comment provided by engineer. + + + Members can send disappearing messages. + Ryhmän jäsenet voivat lähettää katoavia viestejä. + No comment provided by engineer. + + + Members can send files and media. + Ryhmän jäsenet voivat lähettää tiedostoja ja mediaa. + No comment provided by engineer. + + + Members can send voice messages. + Ryhmän jäsenet voivat lähettää ääniviestejä. + No comment provided by engineer. + Menus No comment provided by engineer. @@ -4594,6 +4642,10 @@ Edellyttää VPN:n sallimista. Onion-isäntiä ei käytetä. No comment provided by engineer. + + Only chat owners can change preferences. + No comment provided by engineer. + Only client devices store user profiles, contacts, groups, and messages. Vain asiakaslaitteet tallentavat käyttäjäprofiileja, yhteystietoja, ryhmiä ja viestejä, jotka on lähetetty **kaksinkertaisella päästä päähän -salauksella**. @@ -4955,6 +5007,10 @@ Error: %@ Yksityisyys ja turvallisuus No comment provided by engineer. + + Privacy for your customers. + No comment provided by engineer. + Privacy redefined Yksityisyys uudelleen määritettynä @@ -7599,6 +7655,10 @@ Repeat connection request? Saat edelleen puheluita ja ilmoituksia mykistetyiltä profiileilta, kun ne ovat aktiivisia. No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Et enää saa viestejä tästä ryhmästä. Keskusteluhistoria säilytetään. diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 9498c255c8..c2030ab657 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -1234,6 +1234,10 @@ Business address No comment provided by engineer. + + Business chats + 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). Par profil de chat (par défaut) ou [par connexion](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). @@ -1375,6 +1379,10 @@ Change user profiles authentication reason + + Chat + No comment provided by engineer. + Chat already exists No comment provided by engineer. @@ -1458,6 +1466,14 @@ Thème de chat No comment provided by engineer. + + Chat will be deleted for all members - this cannot be undone! + No comment provided by engineer. + + + Chat will be deleted for you - this cannot be undone! + No comment provided by engineer. + Chats Discussions @@ -2198,6 +2214,10 @@ Il s'agit de votre propre lien unique ! Supprimer et en informer le contact No comment provided by engineer. + + Delete chat + No comment provided by engineer. + Delete chat profile Supprimer le profil de chat @@ -2208,6 +2228,10 @@ Il s'agit de votre propre lien unique ! Supprimer le profil du chat ? No comment provided by engineer. + + Delete chat? + No comment provided by engineer. + Delete connection Supprimer la connexion @@ -2472,6 +2496,10 @@ Il s'agit de votre propre lien unique ! Messages directs chat feature + + Direct messages between members are prohibited in this chat. + No comment provided by engineer. + Direct messages between members are prohibited. Les messages directs entre membres sont interdits dans ce groupe. @@ -3632,41 +3660,6 @@ Erreur : %2$@ Liens de groupe No comment provided by engineer. - - Members can add message reactions. - Les membres du groupe peuvent ajouter des réactions aux messages. - No comment provided by engineer. - - - Members can irreversibly delete sent messages. (24 hours) - Les membres du groupe peuvent supprimer de manière irréversible les messages envoyés. (24 heures) - No comment provided by engineer. - - - Members can send SimpleX links. - Les membres du groupe peuvent envoyer des liens SimpleX. - No comment provided by engineer. - - - Members can send direct messages. - Les membres du groupe peuvent envoyer des messages directs. - No comment provided by engineer. - - - Members can send disappearing messages. - Les membres du groupes peuvent envoyer des messages éphémères. - No comment provided by engineer. - - - Members can send files and media. - Les membres du groupe peuvent envoyer des fichiers et des médias. - No comment provided by engineer. - - - Members can send voice messages. - Les membres du groupe peuvent envoyer des messages vocaux. - No comment provided by engineer. - Group message: Message du groupe : @@ -4064,6 +4057,10 @@ D'autres améliorations sont à venir ! Inviter des membres No comment provided by engineer. + + Invite to chat + No comment provided by engineer. + Invite to group Inviter au groupe @@ -4222,6 +4219,14 @@ Voici votre lien pour le groupe %@ ! Quitter swipe action + + Leave chat + No comment provided by engineer. + + + Leave chat? + No comment provided by engineer. + Leave group Quitter le groupe @@ -4352,6 +4357,10 @@ Voici votre lien pour le groupe %@ ! Membre inactif item status text + + Member role will be changed to "%@". All chat members will be notified. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. Le rôle du membre sera changé pour "%@". Tous les membres du groupe en seront informés. @@ -4362,11 +4371,50 @@ Voici votre lien pour le groupe %@ ! Le rôle du membre sera changé pour "%@". Ce membre recevra une nouvelle invitation. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! Ce membre sera retiré du groupe - impossible de revenir en arrière ! No comment provided by engineer. + + Members can add message reactions. + Les membres du groupe peuvent ajouter des réactions aux messages. + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + Les membres du groupe peuvent supprimer de manière irréversible les messages envoyés. (24 heures) + No comment provided by engineer. + + + Members can send SimpleX links. + Les membres du groupe peuvent envoyer des liens SimpleX. + No comment provided by engineer. + + + Members can send direct messages. + Les membres du groupe peuvent envoyer des messages directs. + No comment provided by engineer. + + + Members can send disappearing messages. + Les membres du groupes peuvent envoyer des messages éphémères. + No comment provided by engineer. + + + Members can send files and media. + Les membres du groupe peuvent envoyer des fichiers et des médias. + No comment provided by engineer. + + + Members can send voice messages. + Les membres du groupe peuvent envoyer des messages vocaux. + No comment provided by engineer. + Menus Menus @@ -4928,6 +4976,10 @@ Nécessite l'activation d'un VPN. Les hôtes .onion ne seront pas utilisés. No comment provided by engineer. + + Only chat owners can change preferences. + No comment provided by engineer. + Only client devices store user profiles, contacts, groups, and messages. Seuls les appareils clients stockent les profils des utilisateurs, les contacts, les groupes et les messages envoyés avec un **chiffrement de bout en bout à deux couches**. @@ -5317,6 +5369,10 @@ Erreur : %@ Vie privée et sécurité No comment provided by engineer. + + Privacy for your customers. + No comment provided by engineer. + Privacy redefined La vie privée redéfinie @@ -8181,6 +8237,10 @@ Répéter la demande de connexion ? Vous continuerez à recevoir des appels et des notifications des profils mis en sourdine lorsqu'ils sont actifs. No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Vous ne recevrez plus de messages de ce groupe. L'historique du chat sera conservé. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index a17a6430d1..437d97274d 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -1246,6 +1246,10 @@ Business address No comment provided by engineer. + + Business chats + 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). A csevegési profillal (alapértelmezett), vagy a [kapcsolattal] (https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BÉTA). @@ -1388,6 +1392,10 @@ Felhasználói profilok megváltoztatása authentication reason + + Chat + No comment provided by engineer. + Chat already exists No comment provided by engineer. @@ -1471,6 +1479,14 @@ Csevegés témája No comment provided by engineer. + + Chat will be deleted for all members - this cannot be undone! + No comment provided by engineer. + + + Chat will be deleted for you - this cannot be undone! + No comment provided by engineer. + Chats Csevegések @@ -2225,6 +2241,10 @@ Ez az Ön egyszer használható meghívó-hivatkozása! Törlés, és az ismerős értesítése No comment provided by engineer. + + Delete chat + No comment provided by engineer. + Delete chat profile Csevegési profil törlése @@ -2235,6 +2255,10 @@ Ez az Ön egyszer használható meghívó-hivatkozása! Csevegési profil törlése? No comment provided by engineer. + + Delete chat? + No comment provided by engineer. + Delete connection Kapcsolat törlése @@ -2500,6 +2524,10 @@ Ez az Ön egyszer használható meghívó-hivatkozása! Közvetlen üzenetek chat feature + + Direct messages between members are prohibited in this chat. + No comment provided by engineer. + Direct messages between members are prohibited. A közvetlen üzenetek küldése a tagok között le van tiltva ebben a csoportban. @@ -3672,41 +3700,6 @@ Hiba: %2$@ Csoporthivatkozások No comment provided by engineer. - - Members can add message reactions. - Csoporttagok üzenetreakciókat adhatnak hozzá. - No comment provided by engineer. - - - Members can irreversibly delete sent messages. (24 hours) - A csoport tagjai véglegesen törölhetik az elküldött üzeneteiket. (24 óra) - No comment provided by engineer. - - - Members can send SimpleX links. - A csoport tagjai küldhetnek SimpleX-hivatkozásokat. - No comment provided by engineer. - - - Members can send direct messages. - A csoport tagjai küldhetnek egymásnak közvetlen üzeneteket. - No comment provided by engineer. - - - Members can send disappearing messages. - A csoport tagjai küldhetnek eltűnő üzeneteket. - No comment provided by engineer. - - - Members can send files and media. - A csoport tagjai küldhetnek fájlokat és médiatartalmakat. - No comment provided by engineer. - - - Members can send voice messages. - A csoport tagjai küldhetnek hangüzeneteket. - No comment provided by engineer. - Group message: Csoport üzenet: @@ -4106,6 +4099,10 @@ További fejlesztések hamarosan! Tagok meghívása No comment provided by engineer. + + Invite to chat + No comment provided by engineer. + Invite to group Meghívás a csoportba @@ -4264,6 +4261,14 @@ Ez az Ön hivatkozása a(z) %@ nevű csoporthoz! Elhagyás swipe action + + Leave chat + No comment provided by engineer. + + + Leave chat? + No comment provided by engineer. + Leave group Csoport elhagyása @@ -4394,6 +4399,10 @@ Ez az Ön hivatkozása a(z) %@ nevű csoporthoz! Inaktív tag item status text + + Member role will be changed to "%@". All chat members will be notified. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. A tag szerepköre meg fog változni erre: „%@”. A csoportban az összes tag értesítve lesz. @@ -4404,11 +4413,50 @@ Ez az Ön hivatkozása a(z) %@ nevű csoporthoz! A tag szerepköre meg fog változni erre: „%@”. A tag új meghívást fog kapni. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! A tag eltávolítása a csoportból - ez a művelet nem vonható vissza! No comment provided by engineer. + + Members can add message reactions. + Csoporttagok üzenetreakciókat adhatnak hozzá. + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + A csoport tagjai véglegesen törölhetik az elküldött üzeneteiket. (24 óra) + No comment provided by engineer. + + + Members can send SimpleX links. + A csoport tagjai küldhetnek SimpleX-hivatkozásokat. + No comment provided by engineer. + + + Members can send direct messages. + A csoport tagjai küldhetnek egymásnak közvetlen üzeneteket. + No comment provided by engineer. + + + Members can send disappearing messages. + A csoport tagjai küldhetnek eltűnő üzeneteket. + No comment provided by engineer. + + + Members can send files and media. + A csoport tagjai küldhetnek fájlokat és médiatartalmakat. + No comment provided by engineer. + + + Members can send voice messages. + A csoport tagjai küldhetnek hangüzeneteket. + No comment provided by engineer. + Menus Menük @@ -4982,6 +5030,10 @@ VPN engedélyezése szükséges. Onion-kiszolgálók nem lesznek használva. No comment provided by engineer. + + Only chat owners can change preferences. + No comment provided by engineer. + Only client devices store user profiles, contacts, groups, and messages. Csak az eszközök alkalmazásai tárolják a felhasználó-profilokat, névjegyeket, csoportokat és a **2 rétegű végpontok közötti titkosítással** küldött üzeneteket. @@ -5377,6 +5429,10 @@ Hiba: %@ Adatvédelem és biztonság No comment provided by engineer. + + Privacy for your customers. + No comment provided by engineer. + Privacy redefined Adatvédelem újraértelmezve @@ -8277,6 +8333,10 @@ Kapcsolatkérés megismétlése? Továbbra is kap hívásokat és értesítéseket a némított profiloktól, ha azok aktívak. No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Ettől a csoporttól nem fog értesítéseket kapni. A csevegési előzmények megmaradnak. diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 0992a1f6bc..f20b0515db 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -1246,6 +1246,10 @@ Business address No comment provided by engineer. + + Business chats + 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 profilo di chat (predefinito) o [per connessione](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). @@ -1388,6 +1392,10 @@ Modifica profili utente authentication reason + + Chat + No comment provided by engineer. + Chat already exists No comment provided by engineer. @@ -1471,6 +1479,14 @@ Tema della chat No comment provided by engineer. + + Chat will be deleted for all members - this cannot be undone! + No comment provided by engineer. + + + Chat will be deleted for you - this cannot be undone! + No comment provided by engineer. + Chats Chat @@ -2225,6 +2241,10 @@ Questo è il tuo link una tantum! Elimina e avvisa il contatto No comment provided by engineer. + + Delete chat + No comment provided by engineer. + Delete chat profile Elimina il profilo di chat @@ -2235,6 +2255,10 @@ Questo è il tuo link una tantum! Eliminare il profilo di chat? No comment provided by engineer. + + Delete chat? + No comment provided by engineer. + Delete connection Elimina connessione @@ -2500,6 +2524,10 @@ Questo è il tuo link una tantum! Messaggi diretti chat feature + + Direct messages between members are prohibited in this chat. + No comment provided by engineer. + Direct messages between members are prohibited. I messaggi diretti tra i membri sono vietati in questo gruppo. @@ -3672,41 +3700,6 @@ Errore: %2$@ Link del gruppo No comment provided by engineer. - - Members can add message reactions. - I membri del gruppo possono aggiungere reazioni ai messaggi. - No comment provided by engineer. - - - Members can irreversibly delete sent messages. (24 hours) - I membri del gruppo possono eliminare irreversibilmente i messaggi inviati. (24 ore) - No comment provided by engineer. - - - Members can send SimpleX links. - I membri del gruppo possono inviare link di Simplex. - No comment provided by engineer. - - - Members can send direct messages. - I membri del gruppo possono inviare messaggi diretti. - No comment provided by engineer. - - - Members can send disappearing messages. - I membri del gruppo possono inviare messaggi a tempo. - No comment provided by engineer. - - - Members can send files and media. - I membri del gruppo possono inviare file e contenuti multimediali. - No comment provided by engineer. - - - Members can send voice messages. - I membri del gruppo possono inviare messaggi vocali. - No comment provided by engineer. - Group message: Messaggio del gruppo: @@ -4106,6 +4099,10 @@ Altri miglioramenti sono in arrivo! Invita membri No comment provided by engineer. + + Invite to chat + No comment provided by engineer. + Invite to group Invita al gruppo @@ -4264,6 +4261,14 @@ Questo è il tuo link per il gruppo %@! Esci swipe action + + Leave chat + No comment provided by engineer. + + + Leave chat? + No comment provided by engineer. + Leave group Esci dal gruppo @@ -4394,6 +4399,10 @@ Questo è il tuo link per il gruppo %@! Membro inattivo item status text + + Member role will be changed to "%@". All chat members will be notified. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. Il ruolo del membro verrà cambiato in "%@". Tutti i membri del gruppo verranno avvisati. @@ -4404,11 +4413,50 @@ Questo è il tuo link per il gruppo %@! Il ruolo del membro verrà cambiato in "%@". Il membro riceverà un invito nuovo. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! Il membro verrà rimosso dal gruppo, non è reversibile! No comment provided by engineer. + + Members can add message reactions. + I membri del gruppo possono aggiungere reazioni ai messaggi. + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + I membri del gruppo possono eliminare irreversibilmente i messaggi inviati. (24 ore) + No comment provided by engineer. + + + Members can send SimpleX links. + I membri del gruppo possono inviare link di Simplex. + No comment provided by engineer. + + + Members can send direct messages. + I membri del gruppo possono inviare messaggi diretti. + No comment provided by engineer. + + + Members can send disappearing messages. + I membri del gruppo possono inviare messaggi a tempo. + No comment provided by engineer. + + + Members can send files and media. + I membri del gruppo possono inviare file e contenuti multimediali. + No comment provided by engineer. + + + Members can send voice messages. + I membri del gruppo possono inviare messaggi vocali. + No comment provided by engineer. + Menus Menu @@ -4982,6 +5030,10 @@ Richiede l'attivazione della VPN. Gli host Onion non verranno usati. No comment provided by engineer. + + Only chat owners can change preferences. + No comment provided by engineer. + Only client devices store user profiles, contacts, groups, and messages. Solo i dispositivi client archiviano profili utente, i contatti, i gruppi e i messaggi inviati con la **crittografia end-to-end a 2 livelli**. @@ -5377,6 +5429,10 @@ Errore: %@ Privacy e sicurezza No comment provided by engineer. + + Privacy for your customers. + No comment provided by engineer. + Privacy redefined Privacy ridefinita @@ -8277,6 +8333,10 @@ Ripetere la richiesta di connessione? Continuerai a ricevere chiamate e notifiche da profili silenziati quando sono attivi. No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Non riceverai più messaggi da questo gruppo. La cronologia della chat verrà conservata. diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 32d2db371b..288276124c 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -1178,6 +1178,10 @@ Business address No comment provided by engineer. + + Business chats + 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). チャット プロファイル経由 (デフォルト) または [接続経由](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). @@ -1310,6 +1314,10 @@ Change user profiles authentication reason + + Chat + No comment provided by engineer. + Chat already exists No comment provided by engineer. @@ -1387,6 +1395,14 @@ チャットテーマ No comment provided by engineer. + + Chat will be deleted for all members - this cannot be undone! + No comment provided by engineer. + + + Chat will be deleted for you - this cannot be undone! + No comment provided by engineer. + Chats チャット @@ -2088,6 +2104,10 @@ This is your own one-time link! Delete and notify contact No comment provided by engineer. + + Delete chat + No comment provided by engineer. + Delete chat profile チャットのプロフィールを削除する @@ -2098,6 +2118,10 @@ This is your own one-time link! チャットのプロフィールを削除しますか? No comment provided by engineer. + + Delete chat? + No comment provided by engineer. + Delete connection 接続を削除する @@ -2348,6 +2372,10 @@ This is your own one-time link! ダイレクトメッセージ chat feature + + Direct messages between members are prohibited in this chat. + No comment provided by engineer. + Direct messages between members are prohibited. このグループではメンバー間のダイレクトメッセージが使用禁止です。 @@ -3426,40 +3454,6 @@ Error: %2$@ グループのリンク No comment provided by engineer. - - Members can add message reactions. - グループメンバーはメッセージへのリアクションを追加できます。 - No comment provided by engineer. - - - Members can irreversibly delete sent messages. (24 hours) - グループのメンバーがメッセージを完全削除することができます。(24時間) - No comment provided by engineer. - - - Members can send SimpleX links. - No comment provided by engineer. - - - Members can send direct messages. - グループのメンバーがダイレクトメッセージを送信できます。 - No comment provided by engineer. - - - Members can send disappearing messages. - グループのメンバーが消えるメッセージを送信できます。 - No comment provided by engineer. - - - Members can send files and media. - グループメンバーはファイルやメディアを送信できます。 - No comment provided by engineer. - - - Members can send voice messages. - グループのメンバーが音声メッセージを送信できます。 - No comment provided by engineer. - Group message: グループメッセージ: @@ -3837,6 +3831,10 @@ More improvements are coming soon! メンバーを招待する No comment provided by engineer. + + Invite to chat + No comment provided by engineer. + Invite to group グループに招待する @@ -3985,6 +3983,14 @@ This is your link for group %@! 脱退 swipe action + + Leave chat + No comment provided by engineer. + + + Leave chat? + No comment provided by engineer. + Leave group グループを脱退 @@ -4109,6 +4115,10 @@ This is your link for group %@! Member inactive item status text + + Member role will be changed to "%@". All chat members will be notified. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. メンバーの役割が "%@" に変更されます。 グループメンバー全員に通知されます。 @@ -4119,11 +4129,49 @@ This is your link for group %@! メンバーの役割が "%@" に変更されます。 メンバーは新たな招待を受け取ります。 No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! メンバーをグループから除名する (※元に戻せません※)! No comment provided by engineer. + + Members can add message reactions. + グループメンバーはメッセージへのリアクションを追加できます。 + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + グループのメンバーがメッセージを完全削除することができます。(24時間) + No comment provided by engineer. + + + Members can send SimpleX links. + No comment provided by engineer. + + + Members can send direct messages. + グループのメンバーがダイレクトメッセージを送信できます。 + No comment provided by engineer. + + + Members can send disappearing messages. + グループのメンバーが消えるメッセージを送信できます。 + No comment provided by engineer. + + + Members can send files and media. + グループメンバーはファイルやメディアを送信できます。 + No comment provided by engineer. + + + Members can send voice messages. + グループのメンバーが音声メッセージを送信できます。 + No comment provided by engineer. + Menus No comment provided by engineer. @@ -4643,6 +4691,10 @@ VPN を有効にする必要があります。 オニオンのホストが使われません。 No comment provided by engineer. + + Only chat owners can change preferences. + No comment provided by engineer. + Only client devices store user profiles, contacts, groups, and messages. **2 レイヤーのエンドツーエンド暗号化**を使用して送信されたユーザー プロファイル、連絡先、グループ、メッセージを保存できるのはクライアント デバイスのみです。 @@ -5005,6 +5057,10 @@ Error: %@ プライバシーとセキュリティ No comment provided by engineer. + + Privacy for your customers. + No comment provided by engineer. + Privacy redefined プライバシーの基準を新境地に @@ -7641,6 +7697,10 @@ Repeat connection request? ミュートされたプロフィールがアクティブな場合でも、そのプロフィールからの通話や通知は引き続き受信します。 No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. このグループからのメッセージが届かなくなります。チャットの履歴が残ります。 diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index b9cba70c3a..6bf4808025 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -1246,6 +1246,10 @@ Business address No comment provided by engineer. + + Business chats + 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). Via chatprofiel (standaard) of [via verbinding](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). @@ -1388,6 +1392,10 @@ Gebruikersprofielen wijzigen authentication reason + + Chat + No comment provided by engineer. + Chat already exists No comment provided by engineer. @@ -1471,6 +1479,14 @@ Chat thema No comment provided by engineer. + + Chat will be deleted for all members - this cannot be undone! + No comment provided by engineer. + + + Chat will be deleted for you - this cannot be undone! + No comment provided by engineer. + Chats Chats @@ -2225,6 +2241,10 @@ Dit is uw eigen eenmalige link! Verwijderen en contact op de hoogte stellen No comment provided by engineer. + + Delete chat + No comment provided by engineer. + Delete chat profile Chatprofiel verwijderen @@ -2235,6 +2255,10 @@ Dit is uw eigen eenmalige link! Chatprofiel verwijderen? No comment provided by engineer. + + Delete chat? + No comment provided by engineer. + Delete connection Verbinding verwijderen @@ -2500,6 +2524,10 @@ Dit is uw eigen eenmalige link! Directe berichten chat feature + + Direct messages between members are prohibited in this chat. + No comment provided by engineer. + Direct messages between members are prohibited. Directe berichten tussen leden zijn verboden in deze groep. @@ -3672,41 +3700,6 @@ Fout: %2$@ Groep links No comment provided by engineer. - - Members can add message reactions. - Groepsleden kunnen bericht reacties toevoegen. - No comment provided by engineer. - - - Members can irreversibly delete sent messages. (24 hours) - Groepsleden kunnen verzonden berichten onherroepelijk verwijderen. (24 uur) - No comment provided by engineer. - - - Members can send SimpleX links. - Groepsleden kunnen SimpleX-links verzenden. - No comment provided by engineer. - - - Members can send direct messages. - Groepsleden kunnen directe berichten sturen. - No comment provided by engineer. - - - Members can send disappearing messages. - Groepsleden kunnen verdwijnende berichten sturen. - No comment provided by engineer. - - - Members can send files and media. - Groepsleden kunnen bestanden en media verzenden. - No comment provided by engineer. - - - Members can send voice messages. - Groepsleden kunnen spraak berichten verzenden. - No comment provided by engineer. - Group message: Groep bericht: @@ -4106,6 +4099,10 @@ Binnenkort meer verbeteringen! Nodig leden uit No comment provided by engineer. + + Invite to chat + No comment provided by engineer. + Invite to group Uitnodigen voor groep @@ -4264,6 +4261,14 @@ Dit is jouw link voor groep %@! Verlaten swipe action + + Leave chat + No comment provided by engineer. + + + Leave chat? + No comment provided by engineer. + Leave group Groep verlaten @@ -4394,6 +4399,10 @@ Dit is jouw link voor groep %@! Lid inactief item status text + + Member role will be changed to "%@". All chat members will be notified. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. De rol van lid wordt gewijzigd in "%@". Alle groepsleden worden op de hoogte gebracht. @@ -4404,11 +4413,50 @@ Dit is jouw link voor groep %@! De rol van lid wordt gewijzigd in "%@". Het lid ontvangt een nieuwe uitnodiging. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! Lid wordt uit de groep verwijderd, dit kan niet ongedaan worden gemaakt! No comment provided by engineer. + + Members can add message reactions. + Groepsleden kunnen bericht reacties toevoegen. + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + Groepsleden kunnen verzonden berichten onherroepelijk verwijderen. (24 uur) + No comment provided by engineer. + + + Members can send SimpleX links. + Groepsleden kunnen SimpleX-links verzenden. + No comment provided by engineer. + + + Members can send direct messages. + Groepsleden kunnen directe berichten sturen. + No comment provided by engineer. + + + Members can send disappearing messages. + Groepsleden kunnen verdwijnende berichten sturen. + No comment provided by engineer. + + + Members can send files and media. + Groepsleden kunnen bestanden en media verzenden. + No comment provided by engineer. + + + Members can send voice messages. + Groepsleden kunnen spraak berichten verzenden. + No comment provided by engineer. + Menus Menu's @@ -4982,6 +5030,10 @@ Vereist het inschakelen van VPN. Onion hosts worden niet gebruikt. No comment provided by engineer. + + Only chat owners can change preferences. + No comment provided by engineer. + Only client devices store user profiles, contacts, groups, and messages. Alleen client apparaten slaan gebruikers profielen, contacten, groepen en berichten op die zijn verzonden met **2-laags end-to-end-codering**. @@ -5377,6 +5429,10 @@ Fout: %@ Privacy en beveiliging No comment provided by engineer. + + Privacy for your customers. + No comment provided by engineer. + Privacy redefined Privacy opnieuw gedefinieerd @@ -8277,6 +8333,10 @@ Verbindingsverzoek herhalen? U ontvangt nog steeds oproepen en meldingen van gedempte profielen wanneer deze actief zijn. No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Je ontvangt geen berichten meer van deze groep. Je gesprek geschiedenis blijft behouden. diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index bdfe502952..ea40b78582 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -1229,6 +1229,10 @@ Business address No comment provided by engineer. + + Business chats + 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). Według profilu czatu (domyślnie) lub [według połączenia](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). @@ -1370,6 +1374,10 @@ Change user profiles authentication reason + + Chat + No comment provided by engineer. + Chat already exists No comment provided by engineer. @@ -1453,6 +1461,14 @@ Motyw czatu No comment provided by engineer. + + Chat will be deleted for all members - this cannot be undone! + No comment provided by engineer. + + + Chat will be deleted for you - this cannot be undone! + No comment provided by engineer. + Chats Czaty @@ -2192,6 +2208,10 @@ To jest twój jednorazowy link! Usuń i powiadom kontakt No comment provided by engineer. + + Delete chat + No comment provided by engineer. + Delete chat profile Usuń profil czatu @@ -2202,6 +2222,10 @@ To jest twój jednorazowy link! Usunąć profil czatu? No comment provided by engineer. + + Delete chat? + No comment provided by engineer. + Delete connection Usuń połączenie @@ -2465,6 +2489,10 @@ To jest twój jednorazowy link! Bezpośrednie wiadomości chat feature + + Direct messages between members are prohibited in this chat. + No comment provided by engineer. + Direct messages between members are prohibited. Bezpośrednie wiadomości między członkami są zabronione w tej grupie. @@ -3624,41 +3652,6 @@ Błąd: %2$@ Linki grupowe No comment provided by engineer. - - Members can add message reactions. - Członkowie grupy mogą dodawać reakcje wiadomości. - No comment provided by engineer. - - - Members can irreversibly delete sent messages. (24 hours) - Członkowie grupy mogą nieodwracalnie usuwać wysłane wiadomości. (24 godziny) - No comment provided by engineer. - - - Members can send SimpleX links. - Członkowie grupy mogą wysyłać linki SimpleX. - No comment provided by engineer. - - - Members can send direct messages. - Członkowie grupy mogą wysyłać bezpośrednie wiadomości. - No comment provided by engineer. - - - Members can send disappearing messages. - Członkowie grupy mogą wysyłać znikające wiadomości. - No comment provided by engineer. - - - Members can send files and media. - Członkowie grupy mogą wysyłać pliki i media. - No comment provided by engineer. - - - Members can send voice messages. - Członkowie grupy mogą wysyłać wiadomości głosowe. - No comment provided by engineer. - Group message: Wiadomość grupowa: @@ -4054,6 +4047,10 @@ More improvements are coming soon! Zaproś członków No comment provided by engineer. + + Invite to chat + No comment provided by engineer. + Invite to group Zaproś do grupy @@ -4212,6 +4209,14 @@ To jest twój link do grupy %@! Opuść swipe action + + Leave chat + No comment provided by engineer. + + + Leave chat? + No comment provided by engineer. + Leave group Opuść grupę @@ -4342,6 +4347,10 @@ To jest twój link do grupy %@! Członek nieaktywny item status text + + Member role will be changed to "%@". All chat members will be notified. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. Rola członka grupy zostanie zmieniona na "%@". Wszyscy członkowie grupy zostaną powiadomieni. @@ -4352,11 +4361,50 @@ To jest twój link do grupy %@! Rola członka zostanie zmieniona na "%@". Członek otrzyma nowe zaproszenie. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! Członek zostanie usunięty z grupy - nie można tego cofnąć! No comment provided by engineer. + + Members can add message reactions. + Członkowie grupy mogą dodawać reakcje wiadomości. + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + Członkowie grupy mogą nieodwracalnie usuwać wysłane wiadomości. (24 godziny) + No comment provided by engineer. + + + Members can send SimpleX links. + Członkowie grupy mogą wysyłać linki SimpleX. + No comment provided by engineer. + + + Members can send direct messages. + Członkowie grupy mogą wysyłać bezpośrednie wiadomości. + No comment provided by engineer. + + + Members can send disappearing messages. + Członkowie grupy mogą wysyłać znikające wiadomości. + No comment provided by engineer. + + + Members can send files and media. + Członkowie grupy mogą wysyłać pliki i media. + No comment provided by engineer. + + + Members can send voice messages. + Członkowie grupy mogą wysyłać wiadomości głosowe. + No comment provided by engineer. + Menus Menu @@ -4918,6 +4966,10 @@ Wymaga włączenia VPN. Hosty onion nie będą używane. No comment provided by engineer. + + Only chat owners can change preferences. + No comment provided by engineer. + Only client devices store user profiles, contacts, groups, and messages. Tylko urządzenia klienckie przechowują profile użytkowników, kontakty, grupy i wiadomości wysyłane za pomocą **2-warstwowego szyfrowania end-to-end**. @@ -5307,6 +5359,10 @@ Błąd: %@ Prywatność i bezpieczeństwo No comment provided by engineer. + + Privacy for your customers. + No comment provided by engineer. + Privacy redefined Redefinicja prywatności @@ -8168,6 +8224,10 @@ Powtórzyć prośbę połączenia? Nadal będziesz otrzymywać połączenia i powiadomienia z wyciszonych profili, gdy są one aktywne. No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Przestaniesz otrzymywać wiadomości od tej grupy. Historia czatu zostanie zachowana. diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 6d57734259..3fd5d194de 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -1235,6 +1235,10 @@ Business address No comment provided by engineer. + + Business chats + 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). По профилю чата или [по соединению](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (БЕТА). @@ -1376,6 +1380,10 @@ Change user profiles authentication reason + + Chat + No comment provided by engineer. + Chat already exists No comment provided by engineer. @@ -1459,6 +1467,14 @@ Тема чата No comment provided by engineer. + + Chat will be deleted for all members - this cannot be undone! + No comment provided by engineer. + + + Chat will be deleted for you - this cannot be undone! + No comment provided by engineer. + Chats Чаты @@ -2199,6 +2215,10 @@ This is your own one-time link! Удалить и уведомить контакт No comment provided by engineer. + + Delete chat + No comment provided by engineer. + Delete chat profile Удалить профиль чата @@ -2209,6 +2229,10 @@ This is your own one-time link! Удалить профиль? No comment provided by engineer. + + Delete chat? + No comment provided by engineer. + Delete connection Удалить соединение @@ -2473,6 +2497,10 @@ This is your own one-time link! Прямые сообщения chat feature + + Direct messages between members are prohibited in this chat. + No comment provided by engineer. + Direct messages between members are prohibited. Прямые сообщения между членами группы запрещены. @@ -3633,41 +3661,6 @@ Error: %2$@ Ссылки групп No comment provided by engineer. - - Members can add message reactions. - Члены группы могут добавлять реакции на сообщения. - No comment provided by engineer. - - - Members can irreversibly delete sent messages. (24 hours) - Члены группы могут необратимо удалять отправленные сообщения. (24 часа) - No comment provided by engineer. - - - Members can send SimpleX links. - Члены группы могут отправлять ссылки SimpleX. - No comment provided by engineer. - - - Members can send direct messages. - Члены группы могут посылать прямые сообщения. - No comment provided by engineer. - - - Members can send disappearing messages. - Члены группы могут посылать исчезающие сообщения. - No comment provided by engineer. - - - Members can send files and media. - Члены группы могут слать файлы и медиа. - No comment provided by engineer. - - - Members can send voice messages. - Члены группы могут отправлять голосовые сообщения. - No comment provided by engineer. - Group message: Групповое сообщение: @@ -4064,6 +4057,10 @@ More improvements are coming soon! Пригласить членов группы No comment provided by engineer. + + Invite to chat + No comment provided by engineer. + Invite to group Пригласить в группу @@ -4222,6 +4219,14 @@ This is your link for group %@! Выйти swipe action + + Leave chat + No comment provided by engineer. + + + Leave chat? + No comment provided by engineer. + Leave group Выйти из группы @@ -4352,6 +4357,10 @@ This is your link for group %@! Член неактивен item status text + + Member role will be changed to "%@". All chat members will be notified. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. Роль члена группы будет изменена на "%@". Все члены группы получат сообщение. @@ -4362,11 +4371,50 @@ This is your link for group %@! Роль члена группы будет изменена на "%@". Будет отправлено новое приглашение. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! Член группы будет удален - это действие нельзя отменить! No comment provided by engineer. + + Members can add message reactions. + Члены группы могут добавлять реакции на сообщения. + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + Члены группы могут необратимо удалять отправленные сообщения. (24 часа) + No comment provided by engineer. + + + Members can send SimpleX links. + Члены группы могут отправлять ссылки SimpleX. + No comment provided by engineer. + + + Members can send direct messages. + Члены группы могут посылать прямые сообщения. + No comment provided by engineer. + + + Members can send disappearing messages. + Члены группы могут посылать исчезающие сообщения. + No comment provided by engineer. + + + Members can send files and media. + Члены группы могут слать файлы и медиа. + No comment provided by engineer. + + + Members can send voice messages. + Члены группы могут отправлять голосовые сообщения. + No comment provided by engineer. + Menus Меню @@ -4928,6 +4976,10 @@ Requires compatible VPN. Onion хосты не используются. No comment provided by engineer. + + Only chat owners can change preferences. + No comment provided by engineer. + Only client devices store user profiles, contacts, groups, and messages. Только пользовательские устройства хранят контакты, группы и сообщения. @@ -5317,6 +5369,10 @@ Error: %@ Конфиденциальность No comment provided by engineer. + + Privacy for your customers. + No comment provided by engineer. + Privacy redefined Более конфиденциальный @@ -8181,6 +8237,10 @@ Repeat connection request? Вы все равно получите звонки и уведомления в профилях без звука, когда они активные. No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Вы перестанете получать сообщения от этой группы. История чата будет сохранена. diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index cec1637438..edf32b06f2 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -1146,6 +1146,10 @@ Business address No comment provided by engineer. + + Business chats + 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). ตามโปรไฟล์แชท (ค่าเริ่มต้น) หรือ [โดยการเชื่อมต่อ](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (เบต้า) @@ -1278,6 +1282,10 @@ Change user profiles authentication reason + + Chat + No comment provided by engineer. + Chat already exists No comment provided by engineer. @@ -1354,6 +1362,14 @@ Chat theme No comment provided by engineer. + + Chat will be deleted for all members - this cannot be undone! + No comment provided by engineer. + + + Chat will be deleted for you - this cannot be undone! + No comment provided by engineer. + Chats แชท @@ -2033,6 +2049,10 @@ This is your own one-time link! Delete and notify contact No comment provided by engineer. + + Delete chat + No comment provided by engineer. + Delete chat profile ลบโปรไฟล์แชท @@ -2043,6 +2063,10 @@ This is your own one-time link! ลบโปรไฟล์แชทไหม? No comment provided by engineer. + + Delete chat? + No comment provided by engineer. + Delete connection ลบการเชื่อมต่อ @@ -2290,6 +2314,10 @@ This is your own one-time link! ข้อความโดยตรง chat feature + + Direct messages between members are prohibited in this chat. + No comment provided by engineer. + Direct messages between members are prohibited. ข้อความโดยตรงระหว่างสมาชิกเป็นสิ่งต้องห้ามในกลุ่มนี้ @@ -3364,40 +3392,6 @@ Error: %2$@ ลิงค์กลุ่ม No comment provided by engineer. - - Members can add message reactions. - สมาชิกกลุ่มสามารถเพิ่มการแสดงปฏิกิริยาต่อข้อความได้ - No comment provided by engineer. - - - Members can irreversibly delete sent messages. (24 hours) - สมาชิกกลุ่มสามารถลบข้อความที่ส่งแล้วอย่างถาวร - No comment provided by engineer. - - - Members can send SimpleX links. - No comment provided by engineer. - - - Members can send direct messages. - สมาชิกกลุ่มสามารถส่งข้อความโดยตรงได้ - No comment provided by engineer. - - - Members can send disappearing messages. - สมาชิกกลุ่มสามารถส่งข้อความที่จะหายไปหลังจากเวลาที่กำหนดหลังการอ่าน (disappearing messages) ได้ - No comment provided by engineer. - - - Members can send files and media. - สมาชิกกลุ่มสามารถส่งไฟล์และสื่อ - No comment provided by engineer. - - - Members can send voice messages. - สมาชิกกลุ่มสามารถส่งข้อความเสียง - No comment provided by engineer. - Group message: ข้อความกลุ่ม: @@ -3773,6 +3767,10 @@ More improvements are coming soon! เชิญสมาชิก No comment provided by engineer. + + Invite to chat + No comment provided by engineer. + Invite to group เชิญเข้าร่วมกลุ่ม @@ -3921,6 +3919,14 @@ This is your link for group %@! ออกจาก swipe action + + Leave chat + No comment provided by engineer. + + + Leave chat? + No comment provided by engineer. + Leave group ออกจากกลุ่ม @@ -4045,6 +4051,10 @@ This is your link for group %@! Member inactive item status text + + Member role will be changed to "%@". All chat members will be notified. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. บทบาทของสมาชิกจะถูกเปลี่ยนเป็น "%@" สมาชิกกลุ่มทั้งหมดจะได้รับแจ้ง @@ -4055,11 +4065,49 @@ This is your link for group %@! บทบาทของสมาชิกจะถูกเปลี่ยนเป็น "%@" สมาชิกจะได้รับคำเชิญใหม่ No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! สมาชิกจะถูกลบออกจากกลุ่ม - ไม่สามารถยกเลิกได้! No comment provided by engineer. + + Members can add message reactions. + สมาชิกกลุ่มสามารถเพิ่มการแสดงปฏิกิริยาต่อข้อความได้ + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + สมาชิกกลุ่มสามารถลบข้อความที่ส่งแล้วอย่างถาวร + No comment provided by engineer. + + + Members can send SimpleX links. + No comment provided by engineer. + + + Members can send direct messages. + สมาชิกกลุ่มสามารถส่งข้อความโดยตรงได้ + No comment provided by engineer. + + + Members can send disappearing messages. + สมาชิกกลุ่มสามารถส่งข้อความที่จะหายไปหลังจากเวลาที่กำหนดหลังการอ่าน (disappearing messages) ได้ + No comment provided by engineer. + + + Members can send files and media. + สมาชิกกลุ่มสามารถส่งไฟล์และสื่อ + No comment provided by engineer. + + + Members can send voice messages. + สมาชิกกลุ่มสามารถส่งข้อความเสียง + No comment provided by engineer. + Menus No comment provided by engineer. @@ -4573,6 +4621,10 @@ Requires compatible VPN. โฮสต์หัวหอมจะไม่ถูกใช้ No comment provided by engineer. + + Only chat owners can change preferences. + No comment provided by engineer. + Only client devices store user profiles, contacts, groups, and messages. เฉพาะอุปกรณ์ไคลเอนต์เท่านั้นที่จัดเก็บโปรไฟล์ผู้ใช้ ผู้ติดต่อ กลุ่ม และข้อความที่ส่งด้วย **การเข้ารหัส encrypt แบบ 2 ชั้น** @@ -4934,6 +4986,10 @@ Error: %@ ความเป็นส่วนตัวและความปลอดภัย No comment provided by engineer. + + Privacy for your customers. + No comment provided by engineer. + Privacy redefined นิยามความเป็นส่วนตัวใหม่ @@ -7568,6 +7624,10 @@ Repeat connection request? คุณจะยังได้รับสายเรียกเข้าและการแจ้งเตือนจากโปรไฟล์ที่ปิดเสียงเมื่อโปรไฟล์ของเขามีการใช้งาน No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. คุณจะหยุดได้รับข้อความจากกลุ่มนี้ ประวัติการแชทจะถูกรักษาไว้ diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index 8ab4b2419d..6d69920454 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -1234,6 +1234,10 @@ Business address No comment provided by engineer. + + Business chats + 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). Sohbet profiline göre (varsayılan) veya [bağlantıya göre](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). @@ -1375,6 +1379,10 @@ Change user profiles authentication reason + + Chat + No comment provided by engineer. + Chat already exists No comment provided by engineer. @@ -1458,6 +1466,14 @@ Sohbet teması No comment provided by engineer. + + Chat will be deleted for all members - this cannot be undone! + No comment provided by engineer. + + + Chat will be deleted for you - this cannot be undone! + No comment provided by engineer. + Chats Sohbetler @@ -2198,6 +2214,10 @@ Bu senin kendi tek kullanımlık bağlantın! Sil ve kişiye bildir No comment provided by engineer. + + Delete chat + No comment provided by engineer. + Delete chat profile Sohbet profilini sil @@ -2208,6 +2228,10 @@ Bu senin kendi tek kullanımlık bağlantın! Sohbet profili silinsin mi? No comment provided by engineer. + + Delete chat? + No comment provided by engineer. + Delete connection Bağlantıyı sil @@ -2472,6 +2496,10 @@ Bu senin kendi tek kullanımlık bağlantın! Doğrudan mesajlar chat feature + + Direct messages between members are prohibited in this chat. + No comment provided by engineer. + Direct messages between members are prohibited. Bu grupta üyeler arasında direkt mesajlaşma yasaktır. @@ -3632,41 +3660,6 @@ Hata: %2$@ Grup bağlantıları No comment provided by engineer. - - Members can add message reactions. - Grup üyeleri mesaj tepkileri ekleyebilir. - No comment provided by engineer. - - - Members can irreversibly delete sent messages. (24 hours) - Grup üyeleri, gönderilen mesajları kalıcı olarak silebilir. (24 saat içinde) - No comment provided by engineer. - - - Members can send SimpleX links. - Grup üyeleri SimpleX bağlantıları gönderebilir. - No comment provided by engineer. - - - Members can send direct messages. - Grup üyeleri doğrudan mesajlar gönderebilir. - No comment provided by engineer. - - - Members can send disappearing messages. - Grup üyeleri kaybolan mesajlar gönderebilir. - No comment provided by engineer. - - - Members can send files and media. - Grup üyeleri dosyalar ve medya gönderebilir. - No comment provided by engineer. - - - Members can send voice messages. - Grup üyeleri sesli mesajlar gönderebilir. - No comment provided by engineer. - Group message: Grup mesajı: @@ -4064,6 +4057,10 @@ Daha fazla iyileştirme yakında geliyor! Üyeleri davet et No comment provided by engineer. + + Invite to chat + No comment provided by engineer. + Invite to group Gruba davet et @@ -4222,6 +4219,14 @@ Bu senin grup için bağlantın %@! Ayrıl swipe action + + Leave chat + No comment provided by engineer. + + + Leave chat? + No comment provided by engineer. + Leave group Gruptan ayrıl @@ -4352,6 +4357,10 @@ Bu senin grup için bağlantın %@! Üye inaktif item status text + + Member role will be changed to "%@". All chat members will be notified. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. Üye rolü "%@" olarak değiştirilecektir. Ve tüm grup üyeleri bilgilendirilecektir. @@ -4362,11 +4371,50 @@ Bu senin grup için bağlantın %@! Üye rolü "%@" olarak değiştirilecektir. Ve üye yeni bir davetiye alacaktır. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! Üye gruptan çıkarılacaktır - bu geri alınamaz! No comment provided by engineer. + + Members can add message reactions. + Grup üyeleri mesaj tepkileri ekleyebilir. + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + Grup üyeleri, gönderilen mesajları kalıcı olarak silebilir. (24 saat içinde) + No comment provided by engineer. + + + Members can send SimpleX links. + Grup üyeleri SimpleX bağlantıları gönderebilir. + No comment provided by engineer. + + + Members can send direct messages. + Grup üyeleri doğrudan mesajlar gönderebilir. + No comment provided by engineer. + + + Members can send disappearing messages. + Grup üyeleri kaybolan mesajlar gönderebilir. + No comment provided by engineer. + + + Members can send files and media. + Grup üyeleri dosyalar ve medya gönderebilir. + No comment provided by engineer. + + + Members can send voice messages. + Grup üyeleri sesli mesajlar gönderebilir. + No comment provided by engineer. + Menus Menüler @@ -4928,6 +4976,10 @@ VPN'nin etkinleştirilmesi gerekir. Onion ana bilgisayarları kullanılmayacaktır. No comment provided by engineer. + + Only chat owners can change preferences. + No comment provided by engineer. + Only client devices store user profiles, contacts, groups, and messages. Yalnızca istemci cihazlar kullanıcı profillerini, kişileri, grupları ve **2 katmanlı uçtan uca şifreleme** ile gönderilen mesajları depolar. @@ -5317,6 +5369,10 @@ Hata: %@ Gizlilik & güvenlik No comment provided by engineer. + + Privacy for your customers. + No comment provided by engineer. + Privacy redefined Gizlilik yeniden tanımlandı @@ -8181,6 +8237,10 @@ Bağlantı isteği tekrarlansın mı? Aktif olduklarında sessize alınmış profillerden arama ve bildirim almaya devam edersiniz. No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Bu gruptan artık mesaj almayacaksınız. Sohbet geçmişi korunacaktır. diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 801b3d0c79..2f1e5d96a6 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -1246,6 +1246,10 @@ Business address No comment provided by engineer. + + Business chats + 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). Через профіль чату (за замовчуванням) або [за з'єднанням](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). @@ -1388,6 +1392,10 @@ Зміна профілів користувачів authentication reason + + Chat + No comment provided by engineer. + Chat already exists No comment provided by engineer. @@ -1471,6 +1479,14 @@ Тема чату No comment provided by engineer. + + Chat will be deleted for all members - this cannot be undone! + No comment provided by engineer. + + + Chat will be deleted for you - this cannot be undone! + No comment provided by engineer. + Chats Чати @@ -2225,6 +2241,10 @@ This is your own one-time link! Видалити та повідомити контакт No comment provided by engineer. + + Delete chat + No comment provided by engineer. + Delete chat profile Видалити профіль чату @@ -2235,6 +2255,10 @@ This is your own one-time link! Видалити профіль чату? No comment provided by engineer. + + Delete chat? + No comment provided by engineer. + Delete connection Видалити підключення @@ -2500,6 +2524,10 @@ This is your own one-time link! Прямі повідомлення chat feature + + Direct messages between members are prohibited in this chat. + No comment provided by engineer. + Direct messages between members are prohibited. У цій групі заборонені прямі повідомлення між учасниками. @@ -3672,41 +3700,6 @@ Error: %2$@ Групові посилання No comment provided by engineer. - - Members can add message reactions. - Учасники групи можуть додавати реакції на повідомлення. - No comment provided by engineer. - - - Members can irreversibly delete sent messages. (24 hours) - Учасники групи можуть безповоротно видаляти надіслані повідомлення. (24 години) - No comment provided by engineer. - - - Members can send SimpleX links. - Учасники групи можуть надсилати посилання SimpleX. - No comment provided by engineer. - - - Members can send direct messages. - Учасники групи можуть надсилати прямі повідомлення. - No comment provided by engineer. - - - Members can send disappearing messages. - Учасники групи можуть надсилати зникаючі повідомлення. - No comment provided by engineer. - - - Members can send files and media. - Учасники групи можуть надсилати файли та медіа. - No comment provided by engineer. - - - Members can send voice messages. - Учасники групи можуть надсилати голосові повідомлення. - No comment provided by engineer. - Group message: Групове повідомлення: @@ -4106,6 +4099,10 @@ More improvements are coming soon! Запросити учасників No comment provided by engineer. + + Invite to chat + No comment provided by engineer. + Invite to group Запросити до групи @@ -4264,6 +4261,14 @@ This is your link for group %@! Залишити swipe action + + Leave chat + No comment provided by engineer. + + + Leave chat? + No comment provided by engineer. + Leave group Покинути групу @@ -4394,6 +4399,10 @@ This is your link for group %@! Користувач неактивний item status text + + Member role will be changed to "%@". All chat members will be notified. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. Роль учасника буде змінено на "%@". Всі учасники групи будуть повідомлені про це. @@ -4404,11 +4413,50 @@ This is your link for group %@! Роль учасника буде змінено на "%@". Учасник отримає нове запрошення. No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! Учасник буде видалений з групи - це неможливо скасувати! No comment provided by engineer. + + Members can add message reactions. + Учасники групи можуть додавати реакції на повідомлення. + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + Учасники групи можуть безповоротно видаляти надіслані повідомлення. (24 години) + No comment provided by engineer. + + + Members can send SimpleX links. + Учасники групи можуть надсилати посилання SimpleX. + No comment provided by engineer. + + + Members can send direct messages. + Учасники групи можуть надсилати прямі повідомлення. + No comment provided by engineer. + + + Members can send disappearing messages. + Учасники групи можуть надсилати зникаючі повідомлення. + No comment provided by engineer. + + + Members can send files and media. + Учасники групи можуть надсилати файли та медіа. + No comment provided by engineer. + + + Members can send voice messages. + Учасники групи можуть надсилати голосові повідомлення. + No comment provided by engineer. + Menus Меню @@ -4982,6 +5030,10 @@ Requires compatible VPN. Onion хости не будуть використовуватися. No comment provided by engineer. + + Only chat owners can change preferences. + No comment provided by engineer. + Only client devices store user profiles, contacts, groups, and messages. Тільки клієнтські пристрої зберігають профілі користувачів, контакти, групи та повідомлення, надіслані за допомогою **2-шарового наскрізного шифрування**. @@ -5377,6 +5429,10 @@ Error: %@ Конфіденційність і безпека No comment provided by engineer. + + Privacy for your customers. + No comment provided by engineer. + Privacy redefined Конфіденційність переглянута @@ -8277,6 +8333,10 @@ Repeat connection request? Ви все одно отримуватимете дзвінки та сповіщення від вимкнених профілів, якщо вони активні. No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. Ви перестанете отримувати повідомлення від цієї групи. Історія чату буде збережена. diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index d9cb36f971..a113c75603 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -1221,6 +1221,10 @@ Business address No comment provided by engineer. + + Business chats + 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). 通过聊天资料(默认)或者[通过连接](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)。 @@ -1362,6 +1366,10 @@ Change user profiles authentication reason + + Chat + No comment provided by engineer. + Chat already exists No comment provided by engineer. @@ -1444,6 +1452,14 @@ 聊天主题 No comment provided by engineer. + + Chat will be deleted for all members - this cannot be undone! + No comment provided by engineer. + + + Chat will be deleted for you - this cannot be undone! + No comment provided by engineer. + Chats 聊天 @@ -2182,6 +2198,10 @@ This is your own one-time link! 删除并通知联系人 No comment provided by engineer. + + Delete chat + No comment provided by engineer. + Delete chat profile 删除聊天资料 @@ -2192,6 +2212,10 @@ This is your own one-time link! 删除聊天资料? No comment provided by engineer. + + Delete chat? + No comment provided by engineer. + Delete connection 删除连接 @@ -2455,6 +2479,10 @@ This is your own one-time link! 私信 chat feature + + Direct messages between members are prohibited in this chat. + No comment provided by engineer. + Direct messages between members are prohibited. 此群中禁止成员之间私信。 @@ -3602,41 +3630,6 @@ Error: %2$@ 群组链接 No comment provided by engineer. - - Members can add message reactions. - 群组成员可以添加信息回应。 - No comment provided by engineer. - - - Members can irreversibly delete sent messages. (24 hours) - 群组成员可以不可撤回地删除已发送的消息 - No comment provided by engineer. - - - Members can send SimpleX links. - 群成员可发送 SimpleX 链接。 - No comment provided by engineer. - - - Members can send direct messages. - 群组成员可以私信。 - No comment provided by engineer. - - - Members can send disappearing messages. - 群组成员可以发送限时消息。 - No comment provided by engineer. - - - Members can send files and media. - 群组成员可以发送文件和媒体。 - No comment provided by engineer. - - - Members can send voice messages. - 群组成员可以发送语音消息。 - No comment provided by engineer. - Group message: 群组消息: @@ -4031,6 +4024,10 @@ More improvements are coming soon! 邀请成员 No comment provided by engineer. + + Invite to chat + No comment provided by engineer. + Invite to group 邀请加入群组 @@ -4189,6 +4186,14 @@ This is your link for group %@! 离开 swipe action + + Leave chat + No comment provided by engineer. + + + Leave chat? + No comment provided by engineer. + Leave group 离开群组 @@ -4319,6 +4324,10 @@ This is your link for group %@! 成员不活跃 item status text + + Member role will be changed to "%@". All chat members will be notified. + No comment provided by engineer. + Member role will be changed to "%@". All group members will be notified. 成员角色将更改为 "%@"。所有群成员将收到通知。 @@ -4329,11 +4338,50 @@ This is your link for group %@! 成员角色将更改为 "%@"。该成员将收到一份新的邀请。 No comment provided by engineer. + + Member will be removed from chat - this cannot be undone! + No comment provided by engineer. + Member will be removed from group - this cannot be undone! 成员将被移出群组——此操作无法撤消! No comment provided by engineer. + + Members can add message reactions. + 群组成员可以添加信息回应。 + No comment provided by engineer. + + + Members can irreversibly delete sent messages. (24 hours) + 群组成员可以不可撤回地删除已发送的消息 + No comment provided by engineer. + + + Members can send SimpleX links. + 群成员可发送 SimpleX 链接。 + No comment provided by engineer. + + + Members can send direct messages. + 群组成员可以私信。 + No comment provided by engineer. + + + Members can send disappearing messages. + 群组成员可以发送限时消息。 + No comment provided by engineer. + + + Members can send files and media. + 群组成员可以发送文件和媒体。 + No comment provided by engineer. + + + Members can send voice messages. + 群组成员可以发送语音消息。 + No comment provided by engineer. + Menus 菜单 @@ -4888,6 +4936,10 @@ Requires compatible VPN. 将不会使用 Onion 主机。 No comment provided by engineer. + + Only chat owners can change preferences. + No comment provided by engineer. + Only client devices store user profiles, contacts, groups, and messages. 只有客户端设备存储用户资料、联系人、群组和**双层端到端加密**发送的消息。 @@ -5273,6 +5325,10 @@ Error: %@ 隐私和安全 No comment provided by engineer. + + Privacy for your customers. + No comment provided by engineer. + Privacy redefined 重新定义隐私 @@ -8118,6 +8174,10 @@ Repeat connection request? 当静音配置文件处于活动状态时,您仍会收到来自静音配置文件的电话和通知。 No comment provided by engineer. + + You will stop receiving messages from this chat. Chat history will be preserved. + No comment provided by engineer. + You will stop receiving messages from this group. Chat history will be preserved. 您将停止接收来自该群组的消息。聊天记录将被保留。 From b9777c92a519c4e18aae8274bda989ce14577bc3 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 3 Dec 2024 18:52:06 +0000 Subject: [PATCH 168/567] core: 6.2.0.4 (simplexmq: 6.2.0.5) --- ...0-simplex-network-v6-2-servers-by-flux-business-chats.md | 6 ------ cabal.project | 2 +- package.yaml | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- src/Simplex/Chat/Remote.hs | 4 ++-- 6 files changed, 6 insertions(+), 12 deletions(-) diff --git a/blog/20241210-simplex-network-v6-2-servers-by-flux-business-chats.md b/blog/20241210-simplex-network-v6-2-servers-by-flux-business-chats.md index d59d8e6003..55de82df47 100644 --- a/blog/20241210-simplex-network-v6-2-servers-by-flux-business-chats.md +++ b/blog/20241210-simplex-network-v6-2-servers-by-flux-business-chats.md @@ -52,9 +52,3 @@ Thank you, Evgeny SimpleX Chat founder - -[1] You can also to self-host your own SimpleX servers on [Flux decentralized cloud](https://home.runonflux.io/apps/marketplace?q=simplex). - -[2] The probability of connection being de-anonymized and the number of random server choices follow this equation: `(1 - s ^ 2) ^ n = 1 - p`, where `s` is the share of attacker-controlled servers in the network, `n` is the number of random choices of entry and exit nodes for the circuit, and `p` is the probability of both entry and exit nodes, and the connection privacy being compromised. Substituting `0.02` (2%) for `s`, `0.5` (50%) for `p`, and solving this equation for `n` we obtain that `1733` random circuits have 50% probability of privacy being compromised. - -Also see [this presentation about Tor](https://ritter.vg/p/tor-v1.6.pdf), specifically the approximate calculations on page 76, and also [Tor project post](https://blog.torproject.org/announcing-vanguards-add-onion-services/) about the changes that made attack on hidden service anonymity harder, but still viable in case the it is used for a long time. diff --git a/cabal.project b/cabal.project index b89dc764cb..414bae94dd 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 38ad3c046e1bd5eb1ffe696dd24b10dd69001ba2 + tag: 4b43cb805437dc0822eb81c57b2c85e77ad333ca source-repository-package type: git diff --git a/package.yaml b/package.yaml index 98571e1342..ca29dc549b 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.2.0.3 +version: 6.2.0.4 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 7a812dbc6e..963ff27092 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."38ad3c046e1bd5eb1ffe696dd24b10dd69001ba2" = "0nq2a2lklbxpc049zjxa5w8c63l9l9nf08jb7pny42nmah0mlc20"; + "https://github.com/simplex-chat/simplexmq.git"."4b43cb805437dc0822eb81c57b2c85e77ad333ca" = "143cskyh3z6yxn6fnaw8biskbspa2cndc65rzziajlw13yd0wggg"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 4e339fc5da..7d8920e3e7 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.2.0.3 +version: 6.2.0.4 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index 3a7d450691..ba713420fc 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -73,11 +73,11 @@ import UnliftIO.Directory (copyFile, createDirectoryIfMissing, doesDirectoryExis -- when acting as host minRemoteCtrlVersion :: AppVersion -minRemoteCtrlVersion = AppVersion [6, 2, 0, 3] +minRemoteCtrlVersion = AppVersion [6, 2, 0, 4] -- when acting as controller minRemoteHostVersion :: AppVersion -minRemoteHostVersion = AppVersion [6, 2, 0, 3] +minRemoteHostVersion = AppVersion [6, 2, 0, 4] currentAppVersion :: AppVersion currentAppVersion = AppVersion SC.version From 3acc69c6d88544f3bf184c3b9aa2d1ee2e6c037c Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 3 Dec 2024 20:13:09 +0000 Subject: [PATCH 169/567] 6.2-beta.4: ios 250, android 255, desktop 79 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 36 +++++++++++----------- apps/multiplatform/gradle.properties | 8 ++--- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 8d4e4fe5c4..24984e5efc 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -167,9 +167,9 @@ 648010AB281ADD15009009B9 /* CIFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648010AA281ADD15009009B9 /* CIFileView.swift */; }; 648679AB2BC96A74006456E7 /* ChatItemForwardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648679AA2BC96A74006456E7 /* ChatItemForwardingView.swift */; }; 649B28DD2CFE07CF00536B68 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28D82CFE07CF00536B68 /* libffi.a */; }; - 649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a */; }; + 649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy-ghc9.6.3.a */; }; 649B28DF2CFE07CF00536B68 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DA2CFE07CF00536B68 /* libgmpxx.a */; }; - 649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a */; }; + 649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy.a */; }; 649B28E12CFE07CF00536B68 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DC2CFE07CF00536B68 /* libgmp.a */; }; 649BCDA0280460FD00C3A862 /* ComposeImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */; }; 649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCDA12805D6EF00C3A862 /* CIImageView.swift */; }; @@ -516,9 +516,9 @@ 648679AA2BC96A74006456E7 /* ChatItemForwardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemForwardingView.swift; sourceTree = ""; }; 6493D667280ED77F007A76FB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 649B28D82CFE07CF00536B68 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a"; sourceTree = ""; }; + 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy-ghc9.6.3.a"; sourceTree = ""; }; 649B28DA2CFE07CF00536B68 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a"; sourceTree = ""; }; + 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy.a"; sourceTree = ""; }; 649B28DC2CFE07CF00536B68 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeImageView.swift; sourceTree = ""; }; 649BCDA12805D6EF00C3A862 /* CIImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIImageView.swift; sourceTree = ""; }; @@ -671,9 +671,9 @@ 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, 649B28E12CFE07CF00536B68 /* libgmp.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - 649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a in Frameworks */, + 649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - 649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a in Frameworks */, + 649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy-ghc9.6.3.a in Frameworks */, 649B28DD2CFE07CF00536B68 /* libffi.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -754,8 +754,8 @@ 649B28D82CFE07CF00536B68 /* libffi.a */, 649B28DC2CFE07CF00536B68 /* libgmp.a */, 649B28DA2CFE07CF00536B68 /* libgmpxx.a */, - 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a */, - 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a */, + 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy-ghc9.6.3.a */, + 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy.a */, ); path = Libraries; sourceTree = ""; @@ -1931,7 +1931,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 249; + CURRENT_PROJECT_VERSION = 250; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1980,7 +1980,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 249; + CURRENT_PROJECT_VERSION = 250; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2021,7 +2021,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 249; + CURRENT_PROJECT_VERSION = 250; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2041,7 +2041,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 249; + CURRENT_PROJECT_VERSION = 250; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2066,7 +2066,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 249; + CURRENT_PROJECT_VERSION = 250; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2103,7 +2103,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 249; + CURRENT_PROJECT_VERSION = 250; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2140,7 +2140,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 249; + CURRENT_PROJECT_VERSION = 250; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2191,7 +2191,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 249; + CURRENT_PROJECT_VERSION = 250; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2242,7 +2242,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 249; + CURRENT_PROJECT_VERSION = 250; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2276,7 +2276,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 249; + CURRENT_PROJECT_VERSION = 250; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 0893c75520..e940f077ff 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -24,11 +24,11 @@ android.nonTransitiveRClass=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.2-beta.3 -android.version_code=254 +android.version_name=6.2-beta.4 +android.version_code=255 -desktop.version_name=6.2-beta.3 -desktop.version_code=78 +desktop.version_name=6.2-beta.4 +desktop.version_code=79 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From 6ff7d4a73c07a1d9dc8c58bdaa0977beb80f0649 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 4 Dec 2024 12:12:30 +0400 Subject: [PATCH 170/567] core: fix business chat state on accept (fixes icon) (#5312) --- src/Simplex/Chat/Store/Groups.hs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 2c938ecc44..dec4b6847e 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -919,9 +919,9 @@ createBusinessRequestGroup currentTs <- liftIO getCurrentTime groupInfo <- insertGroup_ currentTs (groupMemberId, memberId) <- insertClientMember_ currentTs groupInfo - liftIO $ setBusinessMemberId groupInfo memberId + groupInfo' <- liftIO $ setBusinessMemberId groupInfo memberId clientMember <- getGroupMemberById db vr user groupMemberId - pure (groupInfo, clientMember) + pure (groupInfo', clientMember) where insertGroup_ currentTs = ExceptT $ withLocalDisplayName db userId displayName $ \localDisplayName -> runExceptT $ do @@ -969,8 +969,9 @@ createBusinessRequestGroup ) groupMemberId <- liftIO $ insertedRowId db pure (groupMemberId, MemberId memId) - setBusinessMemberId GroupInfo {groupId} businessMemberId = do - DB.execute db "UPDATE groups SET business_member_id = ? WHERE group_id = ?" (businessMemberId, groupId) + setBusinessMemberId groupInfo@GroupInfo {groupId} memberId = do + DB.execute db "UPDATE groups SET business_member_id = ? WHERE group_id = ?" (memberId, groupId) + pure (groupInfo {businessChat = Just BusinessChatInfo {memberId, chatType = BCCustomer}} :: GroupInfo) getContactViaMember :: DB.Connection -> VersionRangeChat -> User -> GroupMember -> ExceptT StoreError IO Contact getContactViaMember db vr user@User {userId} GroupMember {groupMemberId} = do From 4f1cf6e79f5ce3c18274a9c34d3573e176b5078c Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Wed, 4 Dec 2024 10:24:31 +0000 Subject: [PATCH 171/567] docs: business page, technical advice (#5314) * docs/business: populate technical advice sections * dev tools * update --------- Co-authored-by: Evgeny Poberezkin --- docs/BUSINESS.md | 67 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/docs/BUSINESS.md b/docs/BUSINESS.md index aed5fd877c..8fd5df5c36 100755 --- a/docs/BUSINESS.md +++ b/docs/BUSINESS.md @@ -53,8 +53,75 @@ With all advantages in privacy and security of e2e encryption in SimpleX Chat, t ### Running SimpleX Chat in the cloud +To install SimpleX Chat CLI in the cloud, follow this: + +1. Create dedicated user for CLI: + + ```sh + useradd -m -s /bin/bash simplex-cli + ``` + +2. Create new tmux session + + ```sh + tmux new -s simplex-cli + ``` + +3. Login to dedicated user: + + ```sh + su - simplex-cli + ``` + +4. Install CLI: + + ```sh + curl -o- https://raw.githubusercontent.com/simplex-chat/simplex-chat/stable/install.sh | bash + ``` + +5. Run the CLI: + + ```sh + simplex-chat + ``` + +To deattach from running CLI simply press `Ctrl+B` and then `D`. + +To reattach back to CLI, run: `tmux attach -t simplex-cli`. + ### Using remote profiles via Desktop app +To use CLI from Desktop app, follow this: + +1. Enable Developer tools in desktop app. + +2. In the Desktop app, click - `Linked mobile` -> `+ Link a mobile`, choose local address `127.0.0.1`, enter some fixed port (can be any free port, e.g. 12345), and copy the link. + +3. In the same machine where Desktop app is running, execute: + + Change `PORT` to port, chosen in the previous step in Desktop app and `SERVER_IP` to your server. + + ```sh + ssh -R PORT:127.0.0.1:PORT -N root@SERVER_IP + ``` + +4. In the CLI on the server: + + Change `LINK` to link, copied in the first step and enter the following: + + ```sh + /crc LINK + ``` + + CLI will print verification code: + + ```sh + Compare session code with controller and use: + /verify remote ctrl ... + ``` + + Simply copy the whole line starting with `/verify ...` from the terminal and paste it. Now you can control the CLI from your Desktop app. + ## Organizations using SimpleX Chat for customer service, support and sales Please let us know if you use SimpleX Chat to communicate with your customers and want to be included in this list. From 89f380400e9b973f6b83417b069fbef88984c0dc Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 4 Dec 2024 16:32:01 +0000 Subject: [PATCH 172/567] core: update business chat profile (#5313) * core: update business chat profile * fix, test * refactor * test changing non-title member profile * fix history --- src/Simplex/Chat.hs | 21 ++++---- src/Simplex/Chat/Store/Groups.hs | 40 ++++++++++++---- src/Simplex/Chat/Store/Messages.hs | 20 ++++---- tests/ChatTests/Profiles.hs | 77 ++++++++++++++++++++++++++++++ 4 files changed, 131 insertions(+), 27 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 31862253dd..e78d6de288 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -5110,7 +5110,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = void . sendGroupMessage user gInfo members . XGrpMemNew $ memberInfo m sendIntroductions members when (groupFeatureAllowed SGFHistory gInfo) sendHistory - when (connChatVersion < batchSend2Version) $ sendGroupAutoReply members + when (connChatVersion < batchSend2Version) sendGroupAutoReply where sendXGrpLinkMem = do let profileMode = ExistingIncognito <$> incognitoMembershipProfile gInfo @@ -5145,7 +5145,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = withStore' $ \db -> updateIntroStatus db introId GMIntroSent sendHistory = when (m `supportsVersion` batchSendVersion) $ do - (errs, items) <- partitionEithers <$> withStore' (\db -> getGroupHistoryItems db user gInfo 100) + (errs, items) <- partitionEithers <$> withStore' (\db -> getGroupHistoryItems db user gInfo m 100) (errs', events) <- partitionEithers <$> mapM (tryChatError . itemForwardEvents) items let errors = map ChatErrorStore errs <> errs' unless (null errors) $ toView $ CRChatErrors (Just user) errors @@ -5376,9 +5376,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = JOINED sqSecured -> -- [async agent commands] continuation on receiving JOINED when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> - when sqSecured $ do - members <- withStore' $ \db -> getGroupMembers db vr user gInfo - when (connChatVersion >= batchSend2Version) $ sendGroupAutoReply members + when (sqSecured && connChatVersion >= batchSend2Version) sendGroupAutoReply QCONT -> do continued <- continueSending connEntity conn when continued $ sendPendingGroupMessages user m conn @@ -5406,7 +5404,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = updateGroupItemsErrorStatus db msgId groupMemberId newStatus = do itemIds <- getChatItemIdsByAgentMsgId db connId msgId forM_ itemIds $ \itemId -> updateGroupMemSndStatus' db itemId groupMemberId newStatus - sendGroupAutoReply members = autoReplyMC >>= mapM_ send + sendGroupAutoReply = autoReplyMC >>= mapM_ send where autoReplyMC = do let GroupInfo {businessChat} = gInfo @@ -5420,8 +5418,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = _ -> Nothing _ -> pure Nothing send mc = do - msg <- sendGroupMessage' user gInfo members (XMsgNew $ MCSimple (extMsgContent mc Nothing)) + msg <- sendGroupMessage' user gInfo [m] (XMsgNew $ MCSimple (extMsgContent mc Nothing)) ci <- saveSndChatItem user (CDGroupSnd gInfo) msg (CISndMsgContent mc) + withStore' $ \db -> createGroupSndStatus db (chatItemId' ci) (groupMemberId' m) GSSNew toView $ CRNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci] agentMsgDecryptError :: AgentCryptoError -> (MsgDecryptError, Word32) @@ -6459,7 +6458,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = processMemberProfileUpdate :: GroupInfo -> GroupMember -> Profile -> Bool -> Maybe UTCTime -> CM GroupMember processMemberProfileUpdate gInfo m@GroupMember {memberProfile = p, memberContactId} p' createItems itemTs_ - | redactedMemberProfile (fromLocalProfile p) /= redactedMemberProfile p' = + | redactedMemberProfile (fromLocalProfile p) /= redactedMemberProfile p' = do + updateBusinessChatProfile gInfo m case memberContactId of Nothing -> do m' <- withStore $ \db -> updateMemberProfile db user m p' @@ -6485,6 +6485,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = | otherwise = pure m where + updateBusinessChatProfile g@GroupInfo {businessChat} GroupMember {memberId} = case businessChat of + Just BusinessChatInfo {memberId = mId} | mId == memberId -> do + g' <- withStore $ \db -> updateGroupProfileFromMember db user g p' + toView $ CRGroupUpdated user g g' (Just m) + _ -> pure () createProfileUpdatedItem m' = when createItems $ do let ciContent = CIRcvGroupEvent $ RGEMemberProfileUpdated (fromLocalProfile p) p' diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index dec4b6847e..895acf0a7d 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -39,6 +39,7 @@ module Simplex.Chat.Store.Groups getGroupInfoByUserContactLinkConnReq, getGroupInfoByGroupLinkHash, updateGroupProfile, + updateGroupProfileFromMember, getGroupIdByName, getGroupMemberIdByName, getActiveMembersByName, @@ -917,11 +918,12 @@ createBusinessRequestGroup UserContactRequest {cReqChatVRange, xContactId, profile = Profile {displayName, fullName, image, contactLink, preferences}} groupPreferences = do currentTs <- liftIO getCurrentTime - groupInfo <- insertGroup_ currentTs - (groupMemberId, memberId) <- insertClientMember_ currentTs groupInfo - groupInfo' <- liftIO $ setBusinessMemberId groupInfo memberId + (groupId, membership) <- insertGroup_ currentTs + (groupMemberId, memberId) <- insertClientMember_ currentTs groupId membership + liftIO $ DB.execute db "UPDATE groups SET business_member_id = ? WHERE group_id = ?" (memberId, groupId) + groupInfo <- getGroupInfo db vr user groupId clientMember <- getGroupMemberById db vr user groupMemberId - pure (groupInfo', clientMember) + pure (groupInfo, clientMember) where insertGroup_ currentTs = ExceptT $ withLocalDisplayName db userId displayName $ \localDisplayName -> runExceptT $ do @@ -942,10 +944,10 @@ createBusinessRequestGroup (profileId, localDisplayName, userId, True, currentTs, currentTs, currentTs, currentTs, BCCustomer, xContactId) insertedRowId db memberId <- liftIO $ encodedRandomBytes gVar 12 - void $ createContactMemberInv_ db user groupId Nothing user (MemberIdRole (MemberId memberId) GROwner) GCUserMember GSMemCreator IBUser Nothing currentTs vr - getGroupInfo db vr user groupId + membership <- createContactMemberInv_ db user groupId Nothing user (MemberIdRole (MemberId memberId) GROwner) GCUserMember GSMemCreator IBUser Nothing currentTs vr + pure (groupId, membership) VersionRange minV maxV = cReqChatVRange - insertClientMember_ currentTs GroupInfo {groupId, membership} = ExceptT $ do + insertClientMember_ currentTs groupId membership = ExceptT $ do withLocalDisplayName db userId displayName $ \localDisplayName -> runExceptT $ do liftIO $ DB.execute @@ -969,9 +971,6 @@ createBusinessRequestGroup ) groupMemberId <- liftIO $ insertedRowId db pure (groupMemberId, MemberId memId) - setBusinessMemberId groupInfo@GroupInfo {groupId} memberId = do - DB.execute db "UPDATE groups SET business_member_id = ? WHERE group_id = ?" (memberId, groupId) - pure (groupInfo {businessChat = Just BusinessChatInfo {memberId, chatType = BCCustomer}} :: GroupInfo) getContactViaMember :: DB.Connection -> VersionRangeChat -> User -> GroupMember -> ExceptT StoreError IO Contact getContactViaMember db vr user@User {userId} GroupMember {groupMemberId} = do @@ -1457,6 +1456,27 @@ updateGroupProfile db user@User {userId} g@GroupInfo {groupId, localDisplayName, (ldn, currentTs, userId, groupId) safeDeleteLDN db user localDisplayName +updateGroupProfileFromMember :: DB.Connection -> User -> GroupInfo -> Profile -> ExceptT StoreError IO GroupInfo +updateGroupProfileFromMember db user g@GroupInfo {groupId} Profile {displayName = n, fullName = fn, image = img} = do + p <- getGroupProfile -- to avoid any race conditions with UI + let g' = g {groupProfile = p} :: GroupInfo + p' = p {displayName = n, fullName = fn, image = img} :: GroupProfile + updateGroupProfile db user g' p' + where + getGroupProfile = + ExceptT $ firstRow toGroupProfile (SEGroupNotFound groupId) $ + DB.query + db + [sql| + SELECT gp.display_name, gp.full_name, gp.description, gp.image, gp.preferences + FROM group_profiles gp + JOIN groups g ON gp.group_profile_id = g.group_profile_id + WHERE g.group_id = ? + |] + (Only groupId) + toGroupProfile (displayName, fullName, description, image, groupPreferences) = + GroupProfile {displayName, fullName, description, image, groupPreferences} + getGroupInfo :: DB.Connection -> VersionRangeChat -> User -> Int64 -> ExceptT StoreError IO GroupInfo getGroupInfo db vr User {userId, userContactId} groupId = ExceptT . firstRow (toGroupInfo vr userContactId) (SEGroupNotFound groupId) $ diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index cff7f6b785..13cadcca77 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -3034,8 +3034,8 @@ getGroupSndStatusCounts db itemId = |] (Only itemId) -getGroupHistoryItems :: DB.Connection -> User -> GroupInfo -> Int -> IO [Either StoreError (CChatItem 'CTGroup)] -getGroupHistoryItems db user@User {userId} GroupInfo {groupId} count = do +getGroupHistoryItems :: DB.Connection -> User -> GroupInfo -> GroupMember -> Int -> IO [Either StoreError (CChatItem 'CTGroup)] +getGroupHistoryItems db user@User {userId} GroupInfo {groupId} m count = do ciIds <- getLastItemIds_ -- use getGroupCIWithReactions to read reactions data reverse <$> mapM (runExceptT . getGroupChatItem db user groupId) ciIds @@ -3046,12 +3046,14 @@ getGroupHistoryItems db user@User {userId} GroupInfo {groupId} count = do <$> DB.query db [sql| - SELECT chat_item_id - FROM chat_items - WHERE user_id = ? AND group_id = ? - AND item_content_tag IN (?,?) - AND item_deleted = 0 - ORDER BY item_ts DESC, chat_item_id DESC + SELECT i.chat_item_id + FROM chat_items i + LEFT JOIN group_snd_item_statuses s ON s.chat_item_id = i.chat_item_id AND s.group_member_id = ? + WHERE i.user_id = ? AND i.group_id = ? + AND i.item_content_tag IN (?,?) + AND i.item_deleted = 0 + AND s.group_snd_item_status_id IS NULL + ORDER BY i.item_ts DESC, i.chat_item_id DESC LIMIT ? |] - (userId, groupId, rcvMsgContentTag, sndMsgContentTag, count) + (groupMemberId' m, userId, groupId, rcvMsgContentTag, sndMsgContentTag, count) diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index c5a464d969..ebfeecdbca 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -49,6 +49,7 @@ chatProfileTests = do it "auto-reply message in incognito" testAutoReplyMessageInIncognito describe "business address" $ do it "create and connect via business address" testBusinessAddress + it "update profiles with business address" testBusinessUpdateProfiles describe "contact address connection plan" $ do it "contact address ok to connect; known contact" testPlanAddressOkKnown it "own contact address" testPlanAddressOwn @@ -732,6 +733,82 @@ testBusinessAddress = testChat3 businessProfile aliceProfile {fullName = "Alice (alice <# "#bob bob_1> hey there") (biz <# "#bob bob_1> hey there") +testBusinessUpdateProfiles :: HasCallStack => FilePath -> IO () +testBusinessUpdateProfiles = testChat3 businessProfile aliceProfile bobProfile $ + \biz alice bob -> do + biz ##> "/ad" + cLink <- getContactLink biz True + biz ##> "/auto_accept on business text Welcome" + biz <## "auto_accept on, business" + biz <## "auto reply:" + biz <## "Welcome" + alice ##> ("/c " <> cLink) + alice <## "connection request sent!" + biz <## "#alice (Alice): accepting business address request..." + alice <## "#biz: joining the group..." + biz <# "#alice Welcome" -- auto reply + biz <## "#alice: alice_1 joined the group" + alice <# "#biz biz_1> Welcome" + alice <## "#biz: you joined the group" + biz #> "#alice hi" + alice <# "#biz biz_1> hi" + alice #> "#biz hello" + biz <# "#alice alice_1> hello" + alice ##> "/p alisa" + alice <## "user profile is changed to alisa (your 0 contacts are notified)" + alice #> "#biz hello again" -- profile update is sent with message + biz <## "alice_1 updated group #alice:" + biz <## "changed to #alisa" + biz <# "#alisa alisa_1> hello again" + -- customer can invite members too, if business allows + biz ##> "/mr alisa alisa_1 admin" + biz <## "#alisa: you changed the role of alisa_1 from member to admin" + alice <## "#biz: biz_1 changed your role from member to admin" + connectUsers alice bob + alice ##> "/a #biz bob" + alice <## "invitation to join the group #biz sent to bob" + bob <## "#biz (Biz Inc): alisa invites you to join the group as member" + bob <## "use /j biz to accept" + bob ##> "/j biz" + concurrentlyN_ + [ do + bob <## "#biz: you joined the group" + bob + <### + [ WithTime "#biz biz_1> Welcome [>>]", + WithTime "#biz biz_1> hi [>>]", + WithTime "#biz alisa> hello [>>]", + WithTime "#biz alisa> hello again [>>]" + ] + bob <## "#biz: member biz_1 (Biz Inc) is connected", + alice <## "#biz: bob joined the group", + do + biz <## "#alisa: alisa_1 added bob (Bob) to the group (connecting...)" + biz <## "#alisa: new member bob is connected" + ] + -- changing other member profiles does not change group profile + bob ##> "/p robert" + bob <## "user profile is changed to robert (your 1 contacts are notified)" + alice <## "contact bob changed to robert" -- only alice receives profile update + alice <## "use @robert to send messages" + bob #> "#biz hi there" -- profile update is sent to group with message + alice <# "#biz robert> hi there" + biz <# "#alisa robert> hi there" + -- both customers receive business profile change + biz ##> "/p business" + biz <## "user profile is changed to business (your 0 contacts are notified)" + biz #> "#alisa hey" + concurrentlyN_ + [ do + alice <## "biz_1 updated group #biz:" + alice <## "changed to #business" + alice <# "#business business_1> hey", + do + bob <## "biz_1 updated group #biz:" + bob <## "changed to #business" + bob <# "#business business_1> hey" + ] + testPlanAddressOkKnown :: HasCallStack => FilePath -> IO () testPlanAddressOkKnown = testChat2 aliceProfile bobProfile $ From 219381f9411a6aad72533ca0e4d713d03839cdbf Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 4 Dec 2024 23:33:14 +0700 Subject: [PATCH 173/567] android, desktop: don't load new messages when pressing quote while searching (#5315) --- .../kotlin/chat/simplex/common/views/chat/ChatView.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 5db413a17a..30f76bb878 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -1001,7 +1001,7 @@ fun BoxScope.ChatItemsList( val chatInfoUpdated = rememberUpdatedState(chatInfo) val highlightedItems = remember { mutableStateOf(setOf()) } val scope = rememberCoroutineScope() - val scrollToItem: (Long) -> Unit = remember { scrollToItem(loadingMoreItems, highlightedItems, chatInfoUpdated, maxHeight, scope, reversedChatItems, mergedItems, listState, loadMessages) } + val scrollToItem: (Long) -> Unit = remember { scrollToItem(searchValue, loadingMoreItems, highlightedItems, chatInfoUpdated, maxHeight, scope, reversedChatItems, mergedItems, listState, loadMessages) } val scrollToQuotedItemFromItem: (Long) -> Unit = remember { findQuotedItemFromItem(remoteHostIdUpdated, chatInfoUpdated, scope, scrollToItem) } LoadLastItems(loadingMoreItems, remoteHostId, chatInfo) @@ -1834,6 +1834,7 @@ private fun lastFullyVisibleIemInListState(topPaddingToContentPx: State, de } private fun scrollToItem( + searchValue: State, loadingMoreItems: MutableState, highlightedItems: MutableState>, chatInfo: State, @@ -1847,6 +1848,8 @@ private fun scrollToItem( withApi { try { var index = mergedItems.value.indexInParentItems[itemId] ?: -1 + // Don't try to load messages while in search + if (index == -1 && searchValue.value.isNotBlank()) return@withApi // setting it to 'loading' even if the item is loaded because in rare cases when the resulting item is near the top, scrolling to // it will trigger loading more items and will scroll to incorrect position (because of trimming) loadingMoreItems.value = true From ee146cdc7b4be10f5d6e6396d144d8830fff6402 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 4 Dec 2024 23:49:45 +0700 Subject: [PATCH 174/567] android, desktop, core: option to show toolbar in chat at the top in reachable UI (#5316) * android, desktop: ability to show toolbar in chat at the top in reachable UI * rename * core AppSettings * ios AppSettings * rename * strings, enable reachable chat toolbar when enabled reachable app toolbars --------- Co-authored-by: Evgeny Poberezkin --- .../Views/UserSettings/AppSettings.swift | 2 + apps/ios/SimpleXChat/APITypes.swift | 5 ++- apps/ios/SimpleXChat/AppGroup.swift | 4 +- .../platform/ScrollableColumn.android.kt | 2 + .../views/usersettings/Appearance.android.kt | 7 ++- .../chat/simplex/common/model/SimpleXAPI.kt | 14 ++++-- .../common/platform/ScrollableColumn.kt | 2 + .../chat/simplex/common/views/TerminalView.kt | 2 +- .../simplex/common/views/chat/ChatView.kt | 45 ++++++++++--------- .../views/chat/group/GroupChatInfoView.kt | 2 +- .../common/views/chatlist/ChatListView.kt | 2 +- .../common/views/chatlist/ShareListView.kt | 2 +- .../common/views/newchat/NewChatSheet.kt | 4 +- .../common/views/newchat/NewChatView.kt | 2 +- .../commonMain/resources/MR/base/strings.xml | 3 +- .../platform/ScrollableColumn.desktop.kt | 13 +++--- .../views/usersettings/Appearance.desktop.kt | 3 ++ src/Simplex/Chat/AppSettings.hs | 16 ++++--- 18 files changed, 83 insertions(+), 47 deletions(-) diff --git a/apps/ios/Shared/Views/UserSettings/AppSettings.swift b/apps/ios/Shared/Views/UserSettings/AppSettings.swift index aa7f885ac6..44e0b20958 100644 --- a/apps/ios/Shared/Views/UserSettings/AppSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/AppSettings.swift @@ -65,6 +65,7 @@ extension AppSettings { if let val = uiCurrentThemeIds { currentThemeIdsDefault.set(val) } if let val = uiThemes { themeOverridesDefault.set(val.skipDuplicates()) } if let val = oneHandUI { groupDefaults.setValue(val, forKey: GROUP_DEFAULT_ONE_HAND_UI) } + if let val = chatBottomBar { groupDefaults.setValue(val, forKey: GROUP_DEFAULT_CHAT_BOTTOM_BAR) } } public static var current: AppSettings { @@ -100,6 +101,7 @@ extension AppSettings { c.uiCurrentThemeIds = currentThemeIdsDefault.get() c.uiThemes = themeOverridesDefault.get() c.oneHandUI = groupDefaults.bool(forKey: GROUP_DEFAULT_ONE_HAND_UI) + c.chatBottomBar = groupDefaults.bool(forKey: GROUP_DEFAULT_CHAT_BOTTOM_BAR) return c } } diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index ca9bb70ea4..2bd76dea63 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -2655,6 +2655,7 @@ public struct AppSettings: Codable, Equatable { public var uiCurrentThemeIds: [String: String]? = nil public var uiThemes: [ThemeOverrides]? = nil public var oneHandUI: Bool? = nil + public var chatBottomBar: Bool? = nil public func prepareForExport() -> AppSettings { var empty = AppSettings() @@ -2689,6 +2690,7 @@ public struct AppSettings: Codable, Equatable { if uiCurrentThemeIds != def.uiCurrentThemeIds { empty.uiCurrentThemeIds = uiCurrentThemeIds } if uiThemes != def.uiThemes { empty.uiThemes = uiThemes } if oneHandUI != def.oneHandUI { empty.oneHandUI = oneHandUI } + if chatBottomBar != def.chatBottomBar { empty.chatBottomBar = chatBottomBar } return empty } @@ -2723,7 +2725,8 @@ public struct AppSettings: Codable, Equatable { uiDarkColorScheme: DefaultTheme.SIMPLEX.themeName, uiCurrentThemeIds: nil as [String: String]?, uiThemes: nil as [ThemeOverrides]?, - oneHandUI: false + oneHandUI: false, + chatBottomBar: true ) } } diff --git a/apps/ios/SimpleXChat/AppGroup.swift b/apps/ios/SimpleXChat/AppGroup.swift index 5ae3c9b901..c754f0740d 100644 --- a/apps/ios/SimpleXChat/AppGroup.swift +++ b/apps/ios/SimpleXChat/AppGroup.swift @@ -57,6 +57,7 @@ public let GROUP_DEFAULT_CONFIRM_DB_UPGRADES = "confirmDBUpgrades" public let GROUP_DEFAULT_CALL_KIT_ENABLED = "callKitEnabled" public let GROUP_DEFAULT_PQ_EXPERIMENTAL_ENABLED = "pqExperimentalEnabled" // no longer used public let GROUP_DEFAULT_ONE_HAND_UI = "oneHandUI" +public let GROUP_DEFAULT_CHAT_BOTTOM_BAR = "chatBottomBar" public let APP_GROUP_NAME = "group.chat.simplex.app" @@ -94,7 +95,8 @@ public func registerGroupDefaults() { GROUP_DEFAULT_CONFIRM_DB_UPGRADES: false, GROUP_DEFAULT_CALL_KIT_ENABLED: true, GROUP_DEFAULT_PQ_EXPERIMENTAL_ENABLED: false, - GROUP_DEFAULT_ONE_HAND_UI: true + GROUP_DEFAULT_ONE_HAND_UI: true, + GROUP_DEFAULT_CHAT_BOTTOM_BAR: true ]) } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/ScrollableColumn.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/ScrollableColumn.android.kt index cf95604504..60197f3851 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/ScrollableColumn.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/ScrollableColumn.android.kt @@ -29,6 +29,7 @@ actual fun LazyColumnWithScrollBar( flingBehavior: FlingBehavior, userScrollEnabled: Boolean, additionalBarOffset: State?, + chatBottomBar: State, fillMaxSize: Boolean, content: LazyListScope.() -> Unit ) { @@ -91,6 +92,7 @@ actual fun LazyColumnWithScrollBarNoAppBar( flingBehavior: FlingBehavior, userScrollEnabled: Boolean, additionalBarOffset: State?, + chatBottomBar: State, content: LazyListScope.() -> Unit ) { val state = state ?: rememberLazyListState() diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt index e5450e8e49..320a8e876a 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt @@ -106,7 +106,12 @@ fun AppearanceScope.AppearanceLayout( } // } - SettingsPreferenceItem(icon = null, stringResource(MR.strings.one_hand_ui), ChatModel.controller.appPrefs.oneHandUI) + SettingsPreferenceItem(icon = null, stringResource(MR.strings.one_hand_ui), ChatModel.controller.appPrefs.oneHandUI) { enabled -> + if (enabled) appPrefs.chatBottomBar.set(true) + } + if (remember { appPrefs.oneHandUI.state }.value) { + SettingsPreferenceItem(icon = null, stringResource(MR.strings.chat_bottom_bar), ChatModel.controller.appPrefs.chatBottomBar) + } } SectionDividerSpaced() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 9531712554..701b32f6f6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -257,6 +257,7 @@ class AppPreferences { val iosCallKitCallsInRecents = mkBoolPreference(SHARED_PREFS_IOS_CALL_KIT_CALLS_IN_RECENTS, false) val oneHandUI = mkBoolPreference(SHARED_PREFS_ONE_HAND_UI, true) + val chatBottomBar = mkBoolPreference(SHARED_PREFS_CHAT_BOTTOM_BAR, true) val hintPreferences: List, Boolean>> = listOf( laNoticeShown to false, @@ -431,6 +432,7 @@ class AppPreferences { private const val SHARED_PREFS_SHOULD_IMPORT_APP_SETTINGS = "ShouldImportAppSettings" private const val SHARED_PREFS_CONFIRM_DB_UPGRADES = "ConfirmDBUpgrades" private const val SHARED_PREFS_ONE_HAND_UI = "OneHandUI" + private const val SHARED_PREFS_CHAT_BOTTOM_BAR = "ChatBottomBar" private const val SHARED_PREFS_SELF_DESTRUCT = "LocalAuthenticationSelfDestruct" private const val SHARED_PREFS_SELF_DESTRUCT_DISPLAY_NAME = "LocalAuthenticationSelfDestructDisplayName" private const val SHARED_PREFS_PQ_EXPERIMENTAL_ENABLED = "PQExperimentalEnabled" // no longer used @@ -438,7 +440,6 @@ class AppPreferences { private const val SHARED_PREFS_CURRENT_THEME_IDs = "CurrentThemeIds" private const val SHARED_PREFS_SYSTEM_DARK_THEME = "SystemDarkTheme" private const val SHARED_PREFS_THEMES_OLD = "Themes" - private const val SHARED_PREFS_THEME_OVERRIDES = "ThemeOverrides" private const val SHARED_PREFS_PROFILE_IMAGE_CORNER_RADIUS = "ProfileImageCornerRadius" private const val SHARED_PREFS_CHAT_ITEM_ROUNDNESS = "ChatItemRoundness" private const val SHARED_PREFS_CHAT_ITEM_TAIL = "ChatItemTail" @@ -6882,7 +6883,8 @@ data class AppSettings( var uiDarkColorScheme: String? = null, var uiCurrentThemeIds: Map? = null, var uiThemes: List? = null, - var oneHandUI: Boolean? = null + var oneHandUI: Boolean? = null, + var chatBottomBar: Boolean? = null ) { fun prepareForExport(): AppSettings { val empty = AppSettings() @@ -6917,6 +6919,7 @@ data class AppSettings( if (uiCurrentThemeIds != def.uiCurrentThemeIds) { empty.uiCurrentThemeIds = uiCurrentThemeIds } if (uiThemes != def.uiThemes) { empty.uiThemes = uiThemes } if (oneHandUI != def.oneHandUI) { empty.oneHandUI = oneHandUI } + if (chatBottomBar != def.chatBottomBar) { empty.chatBottomBar = chatBottomBar } return empty } @@ -6962,6 +6965,7 @@ data class AppSettings( uiCurrentThemeIds?.let { def.currentThemeIds.set(it) } uiThemes?.let { def.themeOverrides.set(it.skipDuplicates()) } oneHandUI?.let { def.oneHandUI.set(it) } + chatBottomBar?.let { if (appPlatform.isAndroid) def.chatBottomBar.set(it) else def.chatBottomBar.set(true) } } companion object { @@ -6996,7 +7000,8 @@ data class AppSettings( uiDarkColorScheme = DefaultTheme.SIMPLEX.themeName, uiCurrentThemeIds = null, uiThemes = null, - oneHandUI = true + oneHandUI = true, + chatBottomBar = true, ) val current: AppSettings @@ -7032,7 +7037,8 @@ data class AppSettings( uiDarkColorScheme = def.systemDarkTheme.get() ?: DefaultTheme.SIMPLEX.themeName, uiCurrentThemeIds = def.currentThemeIds.get(), uiThemes = def.themeOverrides.get(), - oneHandUI = def.oneHandUI.get() + oneHandUI = def.oneHandUI.get(), + chatBottomBar = def.chatBottomBar.get() ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/ScrollableColumn.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/ScrollableColumn.kt index b0be547a31..b4e823bd45 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/ScrollableColumn.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/ScrollableColumn.kt @@ -23,6 +23,7 @@ expect fun LazyColumnWithScrollBar( flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(), userScrollEnabled: Boolean = true, additionalBarOffset: State? = null, + chatBottomBar: State = remember { mutableStateOf(true) }, // by default, this function will include .fillMaxSize() without you doing anything. If you don't need it, pass `false` here // maxSize (at least maxHeight) is needed for blur on appBars to work correctly fillMaxSize: Boolean = true, @@ -41,6 +42,7 @@ expect fun LazyColumnWithScrollBarNoAppBar( flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(), userScrollEnabled: Boolean = true, additionalBarOffset: State? = null, + chatBottomBar: State = remember { mutableStateOf(true) }, content: LazyListScope.() -> Unit ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt index b6eb4c8996..6a6db0da85 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt @@ -156,7 +156,7 @@ fun TerminalLog(floating: Boolean, composeViewHeight: State) { LazyColumnWithScrollBar ( reverseLayout = true, contentPadding = PaddingValues( - top = topPaddingToContent(), + top = topPaddingToContent(false), bottom = composeViewHeight.value ), state = listState, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 30f76bb878..ddf25a6e3b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -658,6 +658,8 @@ fun ChatLayout( Box(Modifier.fillMaxSize().chatViewBackgroundModifier(MaterialTheme.colors, MaterialTheme.wallpaper, LocalAppBarHandler.current?.backgroundGraphicsLayerSize, LocalAppBarHandler.current?.backgroundGraphicsLayer)) { val remoteHostId = remember { remoteHostId }.value val chatInfo = remember { chatInfo }.value + val oneHandUI = remember { appPrefs.oneHandUI.state } + val chatBottomBar = remember { appPrefs.chatBottomBar.state } AdaptingBottomPaddingLayout(Modifier, CHAT_COMPOSE_LAYOUT_ID, composeViewHeight) { if (chatInfo != null) { Box(Modifier.fillMaxSize()) { @@ -670,25 +672,23 @@ fun ChatLayout( ) } } - val oneHandUI = remember { appPrefs.oneHandUI.state } Box( Modifier .layoutId(CHAT_COMPOSE_LAYOUT_ID) .align(Alignment.BottomCenter) .imePadding() .navigationBarsPadding() - .then(if (oneHandUI.value) Modifier.padding(bottom = AppBarHeight * fontSizeSqrtMultiplier) else Modifier) + .then(if (oneHandUI.value && chatBottomBar.value) Modifier.padding(bottom = AppBarHeight * fontSizeSqrtMultiplier) else Modifier) ) { composeView() } } - val oneHandUI = remember { appPrefs.oneHandUI.state } - if (oneHandUI.value) { + if (oneHandUI.value && chatBottomBar.value) { StatusBarBackground() } else { NavigationBarBackground(true, oneHandUI.value, noAlpha = true) } - Box(if (oneHandUI.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) { + Box(if (oneHandUI.value && chatBottomBar.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) { if (selectedChatItems.value == null) { if (chatInfo != null) { ChatInfoToolbar(chatInfo, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch) @@ -856,12 +856,13 @@ fun BoxScope.ChatInfoToolbar( } } val oneHandUI = remember { appPrefs.oneHandUI.state } + val chatBottomBar = remember { appPrefs.chatBottomBar.state } DefaultAppBar( navigationButton = { if (appPlatform.isAndroid || showSearch.value) { NavigationButtonBack(onBackClicked) } }, title = { ChatInfoToolbarTitle(chatInfo) }, onTitleClick = if (chatInfo is ChatInfo.Local) null else info, showSearch = showSearch.value, - onTop = !oneHandUI.value, + onTop = !oneHandUI.value || !chatBottomBar.value, onSearchValueChanged = onSearchValueChanged, buttons = { barButtons.forEach { it() } } ) @@ -873,11 +874,11 @@ fun BoxScope.ChatInfoToolbar( showMenu, modifier = Modifier.onSizeChanged { with(density) { width.value = it.width.toDp().coerceAtLeast(250.dp) - if (oneHandUI.value && (appPlatform.isDesktop || (platform.androidApiLevel ?: 0) >= 30)) height.value = it.height.toDp() + if (oneHandUI.value && chatBottomBar.value && (appPlatform.isDesktop || (platform.androidApiLevel ?: 0) >= 30)) height.value = it.height.toDp() } }, - offset = DpOffset(-width.value, if (oneHandUI.value) -height.value else AppBarHeight) + offset = DpOffset(-width.value, if (oneHandUI.value && chatBottomBar.value) -height.value else AppBarHeight) ) { - if (oneHandUI.value) { + if (oneHandUI.value && chatBottomBar.value) { menuItems.asReversed().forEach { it() } } else { menuItems.forEach { it() } @@ -964,7 +965,7 @@ fun BoxScope.ChatItemsList( val reversedChatItems = remember { derivedStateOf { chatModel.chatItems.asReversed() } } val revealedItems = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(setOf()) } val mergedItems = remember { derivedStateOf { MergedItems.create(reversedChatItems.value, unreadCount, revealedItems.value, chatModel.chatState) } } - val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent().roundToPx() }) + val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent(true).roundToPx() }) /** determines height based on window info and static height of two AppBars. It's needed because in the first graphic frame height of * [composeViewHeight] is unknown, but we need to set scroll position for unread messages already so it will be correct before the first frame appears * */ @@ -1264,10 +1265,11 @@ fun BoxScope.ChatItemsList( state = listState.value, reverseLayout = true, contentPadding = PaddingValues( - top = topPaddingToContent(), + top = topPaddingToContent(true), bottom = composeViewHeight.value ), - additionalBarOffset = composeViewHeight + additionalBarOffset = composeViewHeight, + chatBottomBar = remember { appPrefs.chatBottomBar.state } ) { val mergedItemsValue = mergedItems.value itemsIndexed(mergedItemsValue.items, key = { _, merged -> keyForItem(merged.newest().item) }) { index, merged -> @@ -1310,8 +1312,8 @@ fun BoxScope.ChatItemsList( } } } - FloatingButtons(loadingMoreItems, mergedItems, unreadCount, maxHeight, composeViewHeight, remoteHostId, chatInfo, searchValue, markChatRead, listState) - FloatingDate(Modifier.padding(top = 10.dp + topPaddingToContent()).align(Alignment.TopCenter), mergedItems, listState) + FloatingButtons(loadingMoreItems, mergedItems, unreadCount, maxHeight, composeViewHeight, searchValue, markChatRead, listState) + FloatingDate(Modifier.padding(top = 10.dp + topPaddingToContent(true)).align(Alignment.TopCenter), mergedItems, listState) LaunchedEffect(Unit) { snapshotFlow { listState.value.isScrollInProgress } @@ -1400,14 +1402,12 @@ fun BoxScope.FloatingButtons( unreadCount: State, maxHeight: State, composeViewHeight: State, - remoteHostId: Long?, - chatInfo: ChatInfo, searchValue: State, markChatRead: () -> Unit, listState: State ) { val scope = rememberCoroutineScope() - val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent().roundToPx() }) + val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent(true).roundToPx() }) val bottomUnreadCount = remember { derivedStateOf { if (unreadCount.value == 0) return@derivedStateOf 0 @@ -1447,7 +1447,7 @@ fun BoxScope.FloatingButtons( val showDropDown = remember { mutableStateOf(false) } TopEndFloatingButton( - Modifier.padding(end = DEFAULT_PADDING, top = 24.dp + topPaddingToContent()).align(Alignment.TopEnd), + Modifier.padding(end = DEFAULT_PADDING, top = 24.dp + topPaddingToContent(true)).align(Alignment.TopEnd), topUnreadCount, onClick = { val index = mergedItems.value.items.indexOfLast { it.hasUnread() } @@ -1465,7 +1465,7 @@ fun BoxScope.FloatingButtons( DefaultDropdownMenu( showDropDown, modifier = Modifier.onSizeChanged { with(density) { width.value = it.width.toDp().coerceAtLeast(250.dp) } }, - offset = DpOffset(-DEFAULT_PADDING - width.value, 24.dp + fabSize + topPaddingToContent()) + offset = DpOffset(-DEFAULT_PADDING - width.value, 24.dp + fabSize + topPaddingToContent(true)) ) { ItemAction( generalGetString(MR.strings.mark_read), @@ -1615,9 +1615,10 @@ private fun TopEndFloatingButton( } @Composable -fun topPaddingToContent(): Dp { +fun topPaddingToContent(chatView: Boolean): Dp { val oneHandUI = remember { appPrefs.oneHandUI.state } - return if (oneHandUI.value) { + val chatBottomBar = remember { appPrefs.chatBottomBar.state } + return if (oneHandUI.value && (!chatView || chatBottomBar.value)) { WindowInsets.statusBars.asPaddingValues().calculateTopPadding() } else { AppBarHeight * fontSizeSqrtMultiplier + WindowInsets.statusBars.asPaddingValues().calculateTopPadding() @@ -1634,7 +1635,7 @@ private fun FloatingDate( val nearBottomIndex = remember(chatModel.chatId) { mutableStateOf(if (isNearBottom.value) -1 else 0) } val showDate = remember(chatModel.chatId) { mutableStateOf(false) } val density = LocalDensity.current.density - val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent().roundToPx() }) + val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent(true).roundToPx() }) val fontSizeSqrtMultiplier = fontSizeSqrtMultiplier val lastVisibleItemDate = remember { derivedStateOf { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index 804222f264..5ee6e40e6e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -306,7 +306,7 @@ fun ModalData.GroupChatInfoLayout( contentPadding = if (oneHandUI.value) { PaddingValues(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + DEFAULT_PADDING + 5.dp, bottom = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()) } else { - PaddingValues(top = topPaddingToContent()) + PaddingValues(top = topPaddingToContent(false)) }, state = listState ) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 20bb65ec7d..ff776bc8ca 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -761,7 +761,7 @@ private fun BoxScope.ChatList(searchText: MutableState, listStat val searchShowingSimplexLink = remember { mutableStateOf(false) } val searchChatFilteredBySimplexLink = remember { mutableStateOf(null) } val chats = filteredChats(showUnreadAndFavorites, searchShowingSimplexLink, searchChatFilteredBySimplexLink, searchText.value.text, allChats.value.toList()) - val topPaddingToContent = topPaddingToContent() + val topPaddingToContent = topPaddingToContent(false) val blankSpaceSize = if (oneHandUI.value) WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + AppBarHeight * fontSizeSqrtMultiplier else topPaddingToContent LazyColumnWithScrollBar( if (!oneHandUI.value) Modifier.imePadding() else Modifier, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt index 9ca2c1e2cd..e048c39fe7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt @@ -194,7 +194,7 @@ private fun ShareList( filteredChats(false, mutableStateOf(false), mutableStateOf(null), search, sorted) } } - val topPaddingToContent = topPaddingToContent() + val topPaddingToContent = topPaddingToContent(false) LazyColumnWithScrollBar( modifier = Modifier.then(if (oneHandUI.value) Modifier.consumeWindowInsets(WindowInsets.navigationBars.only(WindowInsetsSides.Vertical)) else Modifier).imePadding(), contentPadding = PaddingValues( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt index 72118224e6..cb4991c99f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt @@ -334,7 +334,7 @@ private fun ModalData.NewChatSheetLayout( @Composable fun NonOneHandLazyColumn() { - val blankSpaceSize = topPaddingToContent() + val blankSpaceSize = topPaddingToContent(false) LazyColumnWithScrollBar( Modifier.imePadding(), state = listState, @@ -646,7 +646,7 @@ private fun ModalData.DeletedContactsView(rh: RemoteHostInfo?, closeDeletedChats ) Box { - val topPaddingToContent = topPaddingToContent() + val topPaddingToContent = topPaddingToContent(false) LazyColumnWithScrollBar( if (!oneHandUI.value) Modifier.imePadding() else Modifier, contentPadding = PaddingValues( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index e08d46d880..058c82c2fe 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -400,7 +400,7 @@ fun ActiveProfilePicker( .fillMaxSize() .alpha(if (progressByTimeout) 0.6f else 1f) ) { - LazyColumnWithScrollBar(Modifier.padding(top = topPaddingToContent()), userScrollEnabled = !switchingProfile.value) { + LazyColumnWithScrollBar(Modifier.padding(top = topPaddingToContent(false)), userScrollEnabled = !switchingProfile.value) { item { val oneHandUI = remember { appPrefs.oneHandUI.state } if (oneHandUI.value) { diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index ddf8805e8a..df0b3d5cd7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1396,7 +1396,8 @@ Database downgrade Incompatible database version Confirm database upgrades - Reachable chat toolbar + Reachable app toolbars + Reachable chat toolbar Toggle chat list: You can change it in Appearance settings. Show console in new window diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt index fc806feb2b..785c3b40fa 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt @@ -36,6 +36,7 @@ actual fun LazyColumnWithScrollBar( flingBehavior: FlingBehavior, userScrollEnabled: Boolean, additionalBarOffset: State?, + chatBottomBar: State, fillMaxSize: Boolean, content: LazyListScope.() -> Unit ) { @@ -92,7 +93,7 @@ actual fun LazyColumnWithScrollBar( val modifier = if (fillMaxSize) Modifier.fillMaxSize().then(modifier) else modifier Box(Modifier.copyViewToAppBar(remember { appPrefs.appearanceBarsBlurRadius.state }.value, LocalAppBarHandler.current?.graphicsLayer).nestedScroll(connection)) { LazyColumn(modifier.then(scrollModifier), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content) - ScrollBar(reverseLayout, state, scrollBarAlpha, scrollJob, scrollBarDraggingState, additionalBarOffset) + ScrollBar(reverseLayout, state, scrollBarAlpha, scrollJob, scrollBarDraggingState, additionalBarOffset, chatBottomBar) } } @@ -107,6 +108,7 @@ actual fun LazyColumnWithScrollBarNoAppBar( flingBehavior: FlingBehavior, userScrollEnabled: Boolean, additionalBarOffset: State?, + chatBottomBar: State, content: LazyListScope.() -> Unit ) { val scope = rememberCoroutineScope() @@ -133,7 +135,7 @@ actual fun LazyColumnWithScrollBarNoAppBar( val scrollBarDraggingState = remember { mutableStateOf(false) } Box { LazyColumn(modifier.then(scrollModifier), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content) - ScrollBar(reverseLayout, state, scrollBarAlpha, scrollJob, scrollBarDraggingState, additionalBarOffset) + ScrollBar(reverseLayout, state, scrollBarAlpha, scrollJob, scrollBarDraggingState, additionalBarOffset, chatBottomBar) } } @@ -144,15 +146,16 @@ private fun ScrollBar( scrollBarAlpha: Animatable, scrollJob: MutableState, scrollBarDraggingState: MutableState, - additionalBarHeight: State? + additionalBarHeight: State?, + chatBottomBar: State, ) { val oneHandUI = remember { appPrefs.oneHandUI.state } val padding = if (additionalBarHeight != null) { - PaddingValues(top = if (oneHandUI.value) 0.dp else AppBarHeight * fontSizeSqrtMultiplier, bottom = additionalBarHeight.value) + PaddingValues(top = if (oneHandUI.value && chatBottomBar.value) 0.dp else AppBarHeight * fontSizeSqrtMultiplier, bottom = additionalBarHeight.value) } else if (reverseLayout) { PaddingValues(bottom = AppBarHeight * fontSizeSqrtMultiplier) } else { - PaddingValues(top = if (oneHandUI.value) 0.dp else AppBarHeight * fontSizeSqrtMultiplier) + PaddingValues(top = if (oneHandUI.value && chatBottomBar.value) 0.dp else AppBarHeight * fontSizeSqrtMultiplier) } Box(Modifier.fillMaxSize().padding(padding), contentAlignment = Alignment.CenterEnd) { DesktopScrollBar(rememberScrollbarAdapter(state), Modifier.fillMaxHeight(), scrollBarAlpha, scrollJob, reverseLayout, scrollBarDraggingState) diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt index 91ff8831ce..c270bddb73 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt @@ -58,6 +58,9 @@ fun AppearanceScope.AppearanceLayout( } } SettingsPreferenceItem(icon = null, stringResource(MR.strings.one_hand_ui), ChatModel.controller.appPrefs.oneHandUI) + if (remember { appPrefs.oneHandUI.state }.value && !remember { appPrefs.chatBottomBar.state }.value) { + SettingsPreferenceItem(icon = null, stringResource(MR.strings.chat_bottom_bar), ChatModel.controller.appPrefs.chatBottomBar) + } } SectionDividerSpaced() ThemesSection(systemDarkTheme) diff --git a/src/Simplex/Chat/AppSettings.hs b/src/Simplex/Chat/AppSettings.hs index e75546d206..1efa69fad4 100644 --- a/src/Simplex/Chat/AppSettings.hs +++ b/src/Simplex/Chat/AppSettings.hs @@ -56,7 +56,8 @@ data AppSettings = AppSettings uiDarkColorScheme :: Maybe DarkColorScheme, uiCurrentThemeIds :: Maybe (Map ThemeColorScheme Text), uiThemes :: Maybe [UITheme], - oneHandUI :: Maybe Bool + oneHandUI :: Maybe Bool, + chatBottomBar :: Maybe Bool } deriving (Show) @@ -105,7 +106,8 @@ defaultAppSettings = uiDarkColorScheme = Just DCSSimplex, uiCurrentThemeIds = Nothing, uiThemes = Nothing, - oneHandUI = Just True + oneHandUI = Just True, + chatBottomBar = Just True } defaultParseAppSettings :: AppSettings @@ -141,7 +143,8 @@ defaultParseAppSettings = uiDarkColorScheme = Nothing, uiCurrentThemeIds = Nothing, uiThemes = Nothing, - oneHandUI = Nothing + oneHandUI = Nothing, + chatBottomBar = Nothing } combineAppSettings :: AppSettings -> AppSettings -> AppSettings @@ -177,7 +180,8 @@ combineAppSettings platformDefaults storedSettings = uiDarkColorScheme = p uiDarkColorScheme, uiCurrentThemeIds = p uiCurrentThemeIds, uiThemes = p uiThemes, - oneHandUI = p oneHandUI + oneHandUI = p oneHandUI, + chatBottomBar = p chatBottomBar } where p :: (AppSettings -> Maybe a) -> Maybe a @@ -230,6 +234,7 @@ instance FromJSON AppSettings where uiCurrentThemeIds <- p "uiCurrentThemeIds" uiThemes <- p "uiThemes" oneHandUI <- p "oneHandUI" + chatBottomBar <- p "chatBottomBar" pure AppSettings { appPlatform, @@ -262,7 +267,8 @@ instance FromJSON AppSettings where uiDarkColorScheme, uiCurrentThemeIds, uiThemes, - oneHandUI + oneHandUI, + chatBottomBar } where p key = v .:? key <|> pure Nothing From 3fa1d7b07c7ee3f2f52fc887e96d3a141a05cc22 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 4 Dec 2024 20:50:19 +0400 Subject: [PATCH 175/567] core: fix cli servers override (#5317) --- src/Simplex/Chat.hs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index e78d6de288..516d033ebf 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -101,7 +101,7 @@ import qualified Simplex.FileTransfer.Transport as XFTP import Simplex.FileTransfer.Types (FileErrorType (..), RcvFileId, SndFileId) import Simplex.Messaging.Agent as Agent import Simplex.Messaging.Agent.Client (SubInfo (..), agentClientStore, getAgentQueuesInfo, getAgentWorkersDetails, getAgentWorkersSummary, getFastNetworkConfig, ipAddressProtected, withLockMap) -import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), InitialAgentServers (..), ServerCfg (..), ServerRoles (..), allRoles, createAgentStore, defaultAgentConfig) +import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), InitialAgentServers (..), ServerCfg (..), ServerRoles (..), allRoles, createAgentStore, defaultAgentConfig, presetServerCfg) import Simplex.Messaging.Agent.Lock (withLock) import Simplex.Messaging.Agent.Protocol import qualified Simplex.Messaging.Agent.Protocol as AP (AgentErrorType (..)) @@ -427,8 +427,12 @@ newChatController ops <- getUpdateServerOperators db presetOps (null users) let opDomains = operatorDomains $ mapMaybe snd ops (smp', xftp') <- unzip <$> mapM (getServers ops opDomains) users - pure InitialAgentServers {smp = M.fromList smp', xftp = M.fromList xftp', ntf, netCfg} + pure InitialAgentServers {smp = M.fromList (optServers smp' smpServers), xftp = M.fromList (optServers xftp' xftpServers), ntf, netCfg} where + optServers :: [(UserId, NonEmpty (ServerCfg p))] -> [ProtoServerWithAuth p] -> [(UserId, NonEmpty (ServerCfg p))] + optServers srvs overrides_ = case L.nonEmpty overrides_ of + Just overrides -> map (second $ const $ L.map (presetServerCfg True allRoles Nothing) overrides) srvs + Nothing -> srvs getServers :: [(Maybe PresetOperator, Maybe ServerOperator)] -> [(Text, ServerOperator)] -> User -> IO ((UserId, NonEmpty (ServerCfg 'PSMP)), (UserId, NonEmpty (ServerCfg 'PXFTP))) getServers ops opDomains user' = do smpSrvs <- getProtocolServers db SPSMP user' From 892f6498be3ebbab92402e4cf7d75b6d6b0e9399 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 4 Dec 2024 21:33:12 +0400 Subject: [PATCH 176/567] ios: fix contact cards opening empty page on connection 2 (#5319) --- apps/ios/Shared/Model/SimpleXAPI.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 5f29a848ef..e29748f3af 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -2014,11 +2014,16 @@ func processReceivedMsg(_ res: ChatResponse) async { m.removeChat(hostConn.id) } } - case let .businessLinkConnecting(user, groupInfo, hostMember, fromContact): + case let .businessLinkConnecting(user, groupInfo, _, fromContact): if !active(user) { return } await MainActor.run { m.updateGroup(groupInfo) + } + if m.chatId == fromContact.id { + ItemsModel.shared.loadOpenChat(groupInfo.id) + } + await MainActor.run { m.removeChat(fromContact.id) } case let .joinedGroupMemberConnecting(user, groupInfo, _, member): From 009d13210f217edb2dee2d36c540caa48d9ef4ce Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 4 Dec 2024 21:33:30 +0400 Subject: [PATCH 177/567] android: fix simplex chat team contact card opening empty page on connection (#5320) --- .../kotlin/chat/simplex/common/model/SimpleXAPI.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 701b32f6f6..bd9c3ddc7b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -23,6 +23,7 @@ import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.call.* import chat.simplex.common.views.chat.item.showQuotedItemDoesNotExistAlert +import chat.simplex.common.views.chatlist.openGroupChat import chat.simplex.common.views.migration.MigrationFileLinkData import chat.simplex.common.views.onboarding.OnboardingStage import chat.simplex.common.views.usersettings.* @@ -2532,6 +2533,11 @@ object ChatController { withChats { updateGroup(rhId, r.groupInfo) + } + if (chatModel.chatId.value == r.fromContact.id) { + openGroupChat(rhId, r.groupInfo.groupId) + } + withChats { removeChat(rhId, r.fromContact.id) } } From c1c17d1f19aab9893874f60462ef0c4480cc964c Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 4 Dec 2024 19:59:42 +0000 Subject: [PATCH 178/567] core: 6.2.0.5 (simplexmq: 6.2.0.6) --- cabal.project | 2 +- package.yaml | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cabal.project b/cabal.project index 414bae94dd..da9c71587f 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 4b43cb805437dc0822eb81c57b2c85e77ad333ca + tag: 966b9990e0bf5fdb701f79b6efd722baddd1ee1d source-repository-package type: git diff --git a/package.yaml b/package.yaml index ca29dc549b..8752a141f7 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.2.0.4 +version: 6.2.0.5 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 963ff27092..3eec88a25b 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."4b43cb805437dc0822eb81c57b2c85e77ad333ca" = "143cskyh3z6yxn6fnaw8biskbspa2cndc65rzziajlw13yd0wggg"; + "https://github.com/simplex-chat/simplexmq.git"."966b9990e0bf5fdb701f79b6efd722baddd1ee1d" = "0gmycrmyrgy5wbhr3f7qy6hbpppsamfypq7y650dinpbqyrfs9fb"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 7d8920e3e7..92eea030c0 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.2.0.4 +version: 6.2.0.5 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From c1a0943448a767022bfd0644e39b3378c301c1ad Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 4 Dec 2024 21:56:09 +0000 Subject: [PATCH 179/567] 6.2-beta.5: ios 251, android 256, desktop 80 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 36 +++++++++++----------- apps/multiplatform/gradle.properties | 8 ++--- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 24984e5efc..690ba2579a 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -167,9 +167,9 @@ 648010AB281ADD15009009B9 /* CIFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648010AA281ADD15009009B9 /* CIFileView.swift */; }; 648679AB2BC96A74006456E7 /* ChatItemForwardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648679AA2BC96A74006456E7 /* ChatItemForwardingView.swift */; }; 649B28DD2CFE07CF00536B68 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28D82CFE07CF00536B68 /* libffi.a */; }; - 649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy-ghc9.6.3.a */; }; + 649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.5-592uBhlQO6KIf70TJf5KpP-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.5-592uBhlQO6KIf70TJf5KpP-ghc9.6.3.a */; }; 649B28DF2CFE07CF00536B68 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DA2CFE07CF00536B68 /* libgmpxx.a */; }; - 649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy.a */; }; + 649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.5-592uBhlQO6KIf70TJf5KpP.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.5-592uBhlQO6KIf70TJf5KpP.a */; }; 649B28E12CFE07CF00536B68 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DC2CFE07CF00536B68 /* libgmp.a */; }; 649BCDA0280460FD00C3A862 /* ComposeImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */; }; 649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCDA12805D6EF00C3A862 /* CIImageView.swift */; }; @@ -516,9 +516,9 @@ 648679AA2BC96A74006456E7 /* ChatItemForwardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemForwardingView.swift; sourceTree = ""; }; 6493D667280ED77F007A76FB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 649B28D82CFE07CF00536B68 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy-ghc9.6.3.a"; sourceTree = ""; }; + 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.5-592uBhlQO6KIf70TJf5KpP-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.5-592uBhlQO6KIf70TJf5KpP-ghc9.6.3.a"; sourceTree = ""; }; 649B28DA2CFE07CF00536B68 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy.a"; sourceTree = ""; }; + 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.5-592uBhlQO6KIf70TJf5KpP.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.5-592uBhlQO6KIf70TJf5KpP.a"; sourceTree = ""; }; 649B28DC2CFE07CF00536B68 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeImageView.swift; sourceTree = ""; }; 649BCDA12805D6EF00C3A862 /* CIImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIImageView.swift; sourceTree = ""; }; @@ -671,9 +671,9 @@ 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, 649B28E12CFE07CF00536B68 /* libgmp.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - 649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy.a in Frameworks */, + 649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.5-592uBhlQO6KIf70TJf5KpP.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - 649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy-ghc9.6.3.a in Frameworks */, + 649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.5-592uBhlQO6KIf70TJf5KpP-ghc9.6.3.a in Frameworks */, 649B28DD2CFE07CF00536B68 /* libffi.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -754,8 +754,8 @@ 649B28D82CFE07CF00536B68 /* libffi.a */, 649B28DC2CFE07CF00536B68 /* libgmp.a */, 649B28DA2CFE07CF00536B68 /* libgmpxx.a */, - 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy-ghc9.6.3.a */, - 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy.a */, + 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.5-592uBhlQO6KIf70TJf5KpP-ghc9.6.3.a */, + 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.5-592uBhlQO6KIf70TJf5KpP.a */, ); path = Libraries; sourceTree = ""; @@ -1931,7 +1931,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 250; + CURRENT_PROJECT_VERSION = 251; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1980,7 +1980,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 250; + CURRENT_PROJECT_VERSION = 251; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2021,7 +2021,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 250; + CURRENT_PROJECT_VERSION = 251; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2041,7 +2041,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 250; + CURRENT_PROJECT_VERSION = 251; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2066,7 +2066,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 250; + CURRENT_PROJECT_VERSION = 251; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2103,7 +2103,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 250; + CURRENT_PROJECT_VERSION = 251; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2140,7 +2140,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 250; + CURRENT_PROJECT_VERSION = 251; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2191,7 +2191,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 250; + CURRENT_PROJECT_VERSION = 251; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2242,7 +2242,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 250; + CURRENT_PROJECT_VERSION = 251; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2276,7 +2276,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 250; + CURRENT_PROJECT_VERSION = 251; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index e940f077ff..b490c26f87 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -24,11 +24,11 @@ android.nonTransitiveRClass=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.2-beta.4 -android.version_code=255 +android.version_name=6.2-beta.5 +android.version_code=256 -desktop.version_name=6.2-beta.4 -desktop.version_code=79 +desktop.version_name=6.2-beta.5 +desktop.version_code=80 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From 4f8a70a6c1d8c5a4df4b2c12d792030067de62d4 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Thu, 5 Dec 2024 22:52:45 +0700 Subject: [PATCH 180/567] android, desktop: allow to scan QR multiple times after fail (#5323) --- .../views/newchat/QRCodeScanner.android.kt | 42 ++++++----- .../simplex/common/views/chat/ScanCodeView.kt | 18 ++--- .../common/views/chat/VerifyCodeView.kt | 23 +++--- .../common/views/migration/MigrateToDevice.kt | 8 +- .../common/views/newchat/ConnectPlan.kt | 19 ++++- .../common/views/newchat/NewChatView.kt | 32 ++++---- .../common/views/newchat/QRCodeScanner.kt | 2 +- .../common/views/remote/ConnectDesktopView.kt | 75 ++++++++++--------- .../networkAndServers/ScanProtocolServer.kt | 1 + .../views/newchat/QRCodeScanner.desktop.kt | 2 +- 10 files changed, 123 insertions(+), 99 deletions(-) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.android.kt index df38295787..6cf432a15f 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.android.kt @@ -33,6 +33,7 @@ import com.google.accompanist.permissions.rememberPermissionState import com.google.common.util.concurrent.ListenableFuture import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.delay import java.util.concurrent.* // Adapted from learntodroid - https://gist.github.com/learntodroid/8f839be0b29d0378f843af70607bd7f5 @@ -41,13 +42,13 @@ import java.util.concurrent.* actual fun QRCodeScanner( showQRCodeScanner: MutableState, padding: PaddingValues, - onBarcode: (String) -> Unit + onBarcode: suspend (String) -> Boolean ) { val context = LocalContext.current val lifecycleOwner = LocalLifecycleOwner.current - var preview by remember { mutableStateOf(null) } - var lastAnalyzedTimeStamp = 0L - var contactLink = "" + val preview = remember { mutableStateOf(null) } + val contactLink = remember { mutableStateOf("") } + val checkingLink = remember { mutableStateOf(false) } val cameraProviderFuture by produceState?>(initialValue = null) { value = ProcessCameraProvider.getInstance(context) @@ -86,28 +87,33 @@ actual fun QRCodeScanner( .build() val cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor() cameraProviderFuture?.addListener({ - preview = Preview.Builder().build().also { + preview.value = Preview.Builder().build().also { it.setSurfaceProvider(previewView.surfaceProvider) } val detector: QrCodeDetector = FactoryFiducial.qrcode(null, GrayU8::class.java) - fun getQR(imageProxy: ImageProxy) { - val currentTimeStamp = System.currentTimeMillis() - if (currentTimeStamp - lastAnalyzedTimeStamp >= TimeUnit.SECONDS.toMillis(1)) { - detector.process(imageProxyToGrayU8(imageProxy)) - val found = detector.detections - val qr = found.firstOrNull() - if (qr != null) { - if (qr.message != contactLink) { - // Make sure link is new and not a repeat - contactLink = qr.message - onBarcode(contactLink) + suspend fun getQR(imageProxy: ImageProxy) { + if (checkingLink.value) return + checkingLink.value = true + + detector.process(imageProxyToGrayU8(imageProxy)) + val found = detector.detections + val qr = found.firstOrNull() + if (qr != null) { + if (qr.message != contactLink.value) { + // Make sure link is new and not a repeat if that link was handled successfully + if (onBarcode(qr.message)) { + contactLink.value = qr.message } + // just some delay to not spam endlessly with alert in case the user scan something wrong, and it fails fast + // (for example, scan user's address while verifying contact code - it prevents alert spam) + delay(1000) } } + checkingLink.value = false imageProxy.close() } - val imageAnalyzer = ImageAnalysis.Analyzer { proxy -> getQR(proxy) } + val imageAnalyzer = ImageAnalysis.Analyzer { proxy -> withApi { getQR(proxy) } } val imageAnalysis: ImageAnalysis = ImageAnalysis.Builder() .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .setImageQueueDepth(1) @@ -115,7 +121,7 @@ actual fun QRCodeScanner( .also { it.setAnalyzer(cameraExecutor, imageAnalyzer) } try { cameraProviderFuture?.get()?.unbindAll() - cameraProviderFuture?.get()?.bindToLifecycle(lifecycleOwner, cameraSelector, preview, imageAnalysis) + cameraProviderFuture?.get()?.bindToLifecycle(lifecycleOwner, cameraSelector, preview.value, imageAnalysis) } catch (e: Exception) { Log.d(TAG, "CameraPreview: ${e.localizedMessage}") } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ScanCodeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ScanCodeView.kt index a12a75b747..428d4b1b8f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ScanCodeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ScanCodeView.kt @@ -13,19 +13,19 @@ import chat.simplex.res.MR import dev.icerock.moko.resources.compose.stringResource @Composable -fun ScanCodeView(verifyCode: (String?, cb: (Boolean) -> Unit) -> Unit, close: () -> Unit) { +fun ScanCodeView(verifyCode: suspend (String?) -> Boolean, close: () -> Unit) { ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.scan_code)) QRCodeScanner { text -> - verifyCode(text) { - if (it) { - close() - } else { - AlertManager.shared.showAlertMsg( - title = generalGetString(MR.strings.incorrect_code) - ) - } + val success = verifyCode(text) + if (success) { + close() + } else { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.incorrect_code) + ) } + success } Text(stringResource(MR.strings.scan_code_from_contacts_app), Modifier.padding(horizontal = DEFAULT_PADDING)) SectionBottomSpacer() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt index 69087ecd60..e670fae5ef 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt @@ -35,14 +35,14 @@ fun VerifyCodeView( displayName, connectionCode, connectionVerified, - verifyCode = { newCode, cb -> - withBGApi { - val res = verify(newCode) - if (res != null) { - val (verified) = res - cb(verified) - if (verified) close() - } + verifyCode = { newCode -> + val res = verify(newCode) + if (res != null) { + val (verified) = res + if (verified) close() + verified + } else { + false } } ) @@ -54,7 +54,7 @@ private fun VerifyCodeLayout( displayName: String, connectionCode: String, connectionVerified: Boolean, - verifyCode: (String?, cb: (Boolean) -> Unit) -> Unit, + verifyCode: suspend (String?) -> Boolean, ) { ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_PADDING)) { AppBarTitle(stringResource(MR.strings.security_code), withPadding = false) @@ -100,7 +100,7 @@ private fun VerifyCodeLayout( ) { if (connectionVerified) { SimpleButton(generalGetString(MR.strings.clear_verification), painterResource(MR.images.ic_shield)) { - verifyCode(null) {} + withApi { verifyCode(null) } } } else { if (appPlatform.isAndroid) { @@ -111,7 +111,8 @@ private fun VerifyCodeLayout( } } SimpleButton(generalGetString(MR.strings.mark_code_verified), painterResource(MR.images.ic_verified_user)) { - verifyCode(connectionCode) { verified -> + withApi { + val verified = verifyCode(connectionCode) if (!verified) { AlertManager.shared.showAlertMsg( title = generalGetString(MR.strings.incorrect_code) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt index f4f537aab7..788c07a9d2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt @@ -203,7 +203,7 @@ private fun MutableState.PasteOrScanLinkView(close: () -> Uni if (appPlatform.isAndroid) { SectionView(stringResource(MR.strings.scan_QR_code).replace('\n', ' ').uppercase()) { QRCodeScanner(showQRCodeScanner = remember { mutableStateOf(true) }) { text -> - withBGApi { checkUserLink(text) } + checkUserLink(text) } } SectionSpacer() @@ -518,8 +518,8 @@ private fun ProgressView() { DefaultProgressView(null) } -private suspend fun MutableState.checkUserLink(link: String) { - if (strHasSimplexFileLink(link.trim())) { +private suspend fun MutableState.checkUserLink(link: String): Boolean { + return if (strHasSimplexFileLink(link.trim())) { val data = MigrationFileLinkData.readFromLink(link) val hasProxyConfigured = data?.networkConfig?.hasProxyConfigured() ?: false val networkConfig = data?.networkConfig?.transformToPlatformSupported() @@ -537,11 +537,13 @@ private suspend fun MutableState.checkUserLink(link: String) networkProxy = null ) } + true } else { AlertManager.shared.showAlertMsg( title = generalGetString(MR.strings.invalid_file_link), text = generalGetString(MR.strings.the_text_you_pasted_is_not_a_link) ) + false } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt index e8190e0767..1b5b475b35 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt @@ -12,6 +12,7 @@ import chat.simplex.common.platform.* import chat.simplex.common.views.chatlist.* import chat.simplex.common.views.helpers.* import chat.simplex.res.MR +import kotlinx.coroutines.* import java.net.URI enum class ConnectionLinkType { @@ -26,8 +27,18 @@ suspend fun planAndConnect( cleanup: (() -> Unit)? = null, filterKnownContact: ((Contact) -> Unit)? = null, filterKnownGroup: ((GroupInfo) -> Unit)? = null, -) { - val connectionPlan = chatModel.controller.apiConnectPlan(rhId, uri.toString()) +): CompletableDeferred { + val completable = CompletableDeferred() + val close: (() -> Unit)? = { + close?.invoke() + // if close was called, it means the connection was created + completable.complete(true) + } + val cleanup: (() -> Unit)? = { + cleanup?.invoke() + completable.complete(!completable.isActive) + } + val connectionPlan = chatModel.controller.apiConnectPlan(rhId, uri) if (connectionPlan != null) { val link = strHasSingleSimplexLink(uri.trim()) val linkText = if (link?.format is Format.SimplexLink) @@ -333,6 +344,7 @@ suspend fun planAndConnect( ) } } + return completable } suspend fun connectViaUri( @@ -343,7 +355,7 @@ suspend fun connectViaUri( connectionPlan: ConnectionPlan?, close: (() -> Unit)?, cleanup: (() -> Unit)?, -) { +): Boolean { val pcc = chatModel.controller.apiConnect(rhId, incognito, uri) val connLinkType = if (connectionPlan != null) planToConnectionLinkType(connectionPlan) else ConnectionLinkType.INVITATION if (pcc != null) { @@ -363,6 +375,7 @@ suspend fun connectViaUri( ) } cleanup?.invoke() + return pcc != null } fun planToConnectionLinkType(connectionPlan: ConnectionPlan): ConnectionLinkType { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index 058c82c2fe..923c0256a8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -38,8 +38,7 @@ import chat.simplex.common.views.chat.topPaddingToContent import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.* import chat.simplex.res.MR -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import java.net.URI enum class NewChatOption { @@ -559,15 +558,14 @@ private fun ConnectView(rhId: Long?, showQRCodeScanner: MutableState, p SectionView(stringResource(MR.strings.or_scan_qr_code).uppercase(), headerBottomPadding = 5.dp) { QRCodeScanner(showQRCodeScanner) { text -> - withBGApi { - val res = verify(rhId, text, close) - if (!res) { - AlertManager.shared.showAlertMsg( - title = generalGetString(MR.strings.invalid_qr_code), - text = generalGetString(MR.strings.code_you_scanned_is_not_simplex_link_qr_code) - ) - } + val linkVerified = verifyOnly(text) + if (!linkVerified) { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.invalid_qr_code), + text = generalGetString(MR.strings.code_you_scanned_is_not_simplex_link_qr_code) + ) } + verifyAndConnect(rhId, text, close) } } } @@ -656,23 +654,25 @@ private fun filteredProfiles(users: List, searchTextOrPassword: String): L } } -private suspend fun verify(rhId: Long?, text: String?, close: () -> Unit): Boolean { +private fun verifyOnly(text: String?): Boolean = text != null && strIsSimplexLink(text) + +private suspend fun verifyAndConnect(rhId: Long?, text: String?, close: () -> Unit): Boolean { if (text != null && strIsSimplexLink(text)) { - connect(rhId, text, close) - return true + return withContext(Dispatchers.Default) { + connect(rhId, text, close) + } } return false } -private suspend fun connect(rhId: Long?, link: String, close: () -> Unit, cleanup: (() -> Unit)? = null) { +private suspend fun connect(rhId: Long?, link: String, close: () -> Unit, cleanup: (() -> Unit)? = null): Boolean = planAndConnect( rhId, link, close = close, cleanup = cleanup, incognito = null - ) -} + ).await() private fun createInvitation( rhId: Long?, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.kt index 1e497e0581..f368edea1b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.kt @@ -10,5 +10,5 @@ import chat.simplex.common.ui.theme.DEFAULT_PADDING_HALF expect fun QRCodeScanner( showQRCodeScanner: MutableState = remember { mutableStateOf(true) }, padding: PaddingValues = PaddingValues(horizontal = DEFAULT_PADDING * 2f, vertical = DEFAULT_PADDING_HALF), - onBarcode: (String) -> Unit + onBarcode: suspend (String) -> Boolean ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt index c3eed3118e..3b6e176ca3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt @@ -40,6 +40,8 @@ import chat.simplex.res.MR import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext @Composable fun ConnectDesktopView(close: () -> Unit) { @@ -233,7 +235,7 @@ private fun FoundDesktop( SectionSpacer() if (compatible) { - SectionItemView({ confirmKnownDesktop(sessionAddress, rc) }) { + SectionItemView({ withBGApi { confirmKnownDesktop(sessionAddress, rc) } }) { Icon(painterResource(MR.images.ic_check), generalGetString(MR.strings.connect_button), tint = MaterialTheme.colors.secondary) TextIconSpaced(false) Text(generalGetString(MR.strings.connect_button)) @@ -356,7 +358,7 @@ private fun ScanDesktopAddressView(sessionAddress: MutableState) { SectionView(stringResource(MR.strings.scan_qr_code_from_desktop).uppercase()) { QRCodeScanner { text -> sessionAddress.value = text - processDesktopQRCode(sessionAddress, text) + connectDesktopAddress(sessionAddress, text) } } } @@ -398,7 +400,7 @@ private fun DesktopAddressView(sessionAddress: MutableState) { stringResource(MR.strings.connect_to_desktop), disabled = sessionAddress.value.isEmpty(), click = { - connectDesktopAddress(sessionAddress, sessionAddress.value) + withBGApi { connectDesktopAddress(sessionAddress, sessionAddress.value) } }, ) } @@ -461,10 +463,6 @@ private suspend fun updateRemoteCtrls(remoteCtrls: SnapshotStateList, resp: String) { - connectDesktopAddress(sessionAddress, resp) -} - private fun findKnownDesktop(showConnectScreen: MutableState) { withBGApi { if (controller.findKnownRemoteCtrl()) { @@ -478,45 +476,48 @@ private fun findKnownDesktop(showConnectScreen: MutableState) { } } -private fun confirmKnownDesktop(sessionAddress: MutableState, rc: RemoteCtrlInfo) { - connectDesktop(sessionAddress) { - controller.confirmRemoteCtrl(rc.remoteCtrlId) +private suspend fun confirmKnownDesktop(sessionAddress: MutableState, rc: RemoteCtrlInfo): Boolean { + return withContext(Dispatchers.Default) { + connectDesktop(sessionAddress) { + controller.confirmRemoteCtrl(rc.remoteCtrlId) + } } } -private fun connectDesktopAddress(sessionAddress: MutableState, addr: String) { - connectDesktop(sessionAddress) { - controller.connectRemoteCtrl(addr) +private suspend fun connectDesktopAddress(sessionAddress: MutableState, addr: String): Boolean { + return withContext(Dispatchers.Default) { + connectDesktop(sessionAddress) { + controller.connectRemoteCtrl(addr) + } } } -private fun connectDesktop(sessionAddress: MutableState, connect: suspend () -> Pair) { - withBGApi { - val res = connect() - if (res.first != null) { - val (rc_, ctrlAppInfo, v) = res.first!! - sessionAddress.value = "" - chatModel.remoteCtrlSession.value = RemoteCtrlSession( - ctrlAppInfo = ctrlAppInfo, - appVersion = v, - sessionState = UIRemoteCtrlSessionState.Connecting(remoteCtrl_ = rc_) - ) - } else { - val e = res.second ?: return@withBGApi - when { - e.chatError is ChatError.ChatErrorRemoteCtrl && e.chatError.remoteCtrlError is RemoteCtrlError.BadInvitation -> showBadInvitationErrorAlert() - e.chatError is ChatError.ChatErrorChat && e.chatError.errorType is ChatErrorType.CommandError -> showBadInvitationErrorAlert() - e.chatError is ChatError.ChatErrorRemoteCtrl && e.chatError.remoteCtrlError is RemoteCtrlError.BadVersion -> showBadVersionAlert(v = e.chatError.remoteCtrlError.appVersion) - e.chatError is ChatError.ChatErrorAgent && e.chatError.agentError is AgentErrorType.RCP && e.chatError.agentError.rcpErr is RCErrorType.VERSION -> showBadVersionAlert(v = null) - e.chatError is ChatError.ChatErrorAgent && e.chatError.agentError is AgentErrorType.RCP && e.chatError.agentError.rcpErr is RCErrorType.CTRL_AUTH -> showDesktopDisconnectedErrorAlert() - else -> { - val errMsg = "${e.responseType}: ${e.details}" - Log.e(TAG, "bad response: $errMsg") - AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error), errMsg) - } +private suspend fun connectDesktop(sessionAddress: MutableState, connect: suspend () -> Pair): Boolean { + val res = connect() + if (res.first != null) { + val (rc_, ctrlAppInfo, v) = res.first!! + sessionAddress.value = "" + chatModel.remoteCtrlSession.value = RemoteCtrlSession( + ctrlAppInfo = ctrlAppInfo, + appVersion = v, + sessionState = UIRemoteCtrlSessionState.Connecting(remoteCtrl_ = rc_) + ) + } else { + val e = res.second ?: return false + when { + e.chatError is ChatError.ChatErrorRemoteCtrl && e.chatError.remoteCtrlError is RemoteCtrlError.BadInvitation -> showBadInvitationErrorAlert() + e.chatError is ChatError.ChatErrorChat && e.chatError.errorType is ChatErrorType.CommandError -> showBadInvitationErrorAlert() + e.chatError is ChatError.ChatErrorRemoteCtrl && e.chatError.remoteCtrlError is RemoteCtrlError.BadVersion -> showBadVersionAlert(v = e.chatError.remoteCtrlError.appVersion) + e.chatError is ChatError.ChatErrorAgent && e.chatError.agentError is AgentErrorType.RCP && e.chatError.agentError.rcpErr is RCErrorType.VERSION -> showBadVersionAlert(v = null) + e.chatError is ChatError.ChatErrorAgent && e.chatError.agentError is AgentErrorType.RCP && e.chatError.agentError.rcpErr is RCErrorType.CTRL_AUTH -> showDesktopDisconnectedErrorAlert() + else -> { + val errMsg = "${e.responseType}: ${e.details}" + Log.e(TAG, "bad response: $errMsg") + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error), errMsg) } } } + return res.first != null } private fun verifyDesktopSessionCode(remoteCtrls: SnapshotStateList, sessCode: String) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ScanProtocolServer.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ScanProtocolServer.kt index 56f16d4eb1..d280773976 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ScanProtocolServer.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ScanProtocolServer.kt @@ -26,6 +26,7 @@ fun ScanProtocolServerLayout(rhId: Long?, onNext: (UserServer) -> Unit) { text = generalGetString(MR.strings.smp_servers_check_address) ) } + res != null } } } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.desktop.kt index 0142afb4ac..6ae35e1a41 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/QRCodeScanner.desktop.kt @@ -7,7 +7,7 @@ import androidx.compose.runtime.* actual fun QRCodeScanner( showQRCodeScanner: MutableState, padding: PaddingValues, - onBarcode: (String) -> Unit + onBarcode: suspend (String) -> Boolean ) { //LALAL } From 97cd2682d77980849f91a19ffea213952ca51111 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Thu, 5 Dec 2024 20:10:44 +0400 Subject: [PATCH 181/567] core: take address lock before reading contact request data (to prevent possible race condition if user quickly accepts request several times in a row); android, desktop: show error context in agent CMD errors (#5324) --- .../kotlin/chat/simplex/common/model/SimpleXAPI.kt | 4 ++-- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat.hs | 12 +++++++----- src/Simplex/Chat/Store/Direct.hs | 6 ++++++ 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index bd9c3ddc7b..757d80193c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -6498,7 +6498,7 @@ sealed class SQLiteError { @Serializable sealed class AgentErrorType { val string: String get() = when (this) { - is CMD -> "CMD ${cmdErr.string}" + is CMD -> "CMD ${cmdErr.string} $errContext" is CONN -> "CONN ${connErr.string}" is SMP -> "SMP ${smpErr.string}" // is NTF -> "NTF ${ntfErr.string}" @@ -6511,7 +6511,7 @@ sealed class AgentErrorType { is CRITICAL -> "CRITICAL $offerRestart $criticalErr" is INACTIVE -> "INACTIVE" } - @Serializable @SerialName("CMD") class CMD(val cmdErr: CommandErrorType): AgentErrorType() + @Serializable @SerialName("CMD") class CMD(val cmdErr: CommandErrorType, val errContext: String): AgentErrorType() @Serializable @SerialName("CONN") class CONN(val connErr: ConnectionErrorType): AgentErrorType() @Serializable @SerialName("SMP") class SMP(val serverAddress: String, val smpErr: SMPErrorType): AgentErrorType() // @Serializable @SerialName("NTF") class NTF(val ntfErr: SMPErrorType): AgentErrorType() diff --git a/cabal.project b/cabal.project index da9c71587f..3212768506 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 966b9990e0bf5fdb701f79b6efd722baddd1ee1d + tag: 9893935e7c3cf8d102c85730a4e48d32f05c2ec7 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 3eec88a25b..098851e9ff 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."966b9990e0bf5fdb701f79b6efd722baddd1ee1d" = "0gmycrmyrgy5wbhr3f7qy6hbpppsamfypq7y650dinpbqyrfs9fb"; + "https://github.com/simplex-chat/simplexmq.git"."9893935e7c3cf8d102c85730a4e48d32f05c2ec7" = "1bpgsdnmk8fml6ad9bjbvyichvd0kq0nqj562xyy5y1npymaxpyn"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 516d033ebf..af730e4c06 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1423,8 +1423,9 @@ processChatCommand' vr = \case CTContactConnection -> pure $ chatCmdError (Just user) "not supported" CTContactRequest -> pure $ chatCmdError (Just user) "not supported" APIAcceptContact incognito connReqId -> withUser $ \_ -> do - (user@User {userId}, cReq@UserContactRequest {userContactLinkId}) <- withFastStore $ \db -> getContactRequest' db connReqId + userContactLinkId <- withFastStore $ \db -> getUserContactLinkIdByCReq db connReqId withUserContactLock "acceptContact" userContactLinkId $ do + (user@User {userId}, cReq) <- withFastStore $ \db -> getContactRequest' db connReqId (ct, conn@Connection {connId}, sqSecured) <- acceptContactRequest user cReq incognito ucl <- withFastStore $ \db -> getUserContactLinkById db userId userContactLinkId let contactUsed = (\(_, groupId_, _) -> isNothing groupId_) ucl @@ -1438,11 +1439,12 @@ processChatCommand' vr = \case pure ct {contactUsed, activeConn = Just conn'} pure $ CRAcceptingContactRequest user ct' APIRejectContact connReqId -> withUser $ \user -> do - cReq@UserContactRequest {userContactLinkId, agentContactConnId = AgentConnId connId, agentInvitationId = AgentInvId invId} <- - withFastStore $ \db -> - getContactRequest db user connReqId - `storeFinally` liftIO (deleteContactRequest db user connReqId) + userContactLinkId <- withFastStore $ \db -> getUserContactLinkIdByCReq db connReqId withUserContactLock "rejectContact" userContactLinkId $ do + cReq@UserContactRequest {agentContactConnId = AgentConnId connId, agentInvitationId = AgentInvId invId} <- + withFastStore $ \db -> + getContactRequest db user connReqId + `storeFinally` liftIO (deleteContactRequest db user connReqId) withAgent $ \a -> rejectContact a connId invId pure $ CRContactRequestRejected user cReq APISendCallInvitation contactId callType -> withUser $ \user -> do diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs index b7759f0905..d5396a0fef 100644 --- a/src/Simplex/Chat/Store/Direct.hs +++ b/src/Simplex/Chat/Store/Direct.hs @@ -57,6 +57,7 @@ module Simplex.Chat.Store.Direct setQuotaErrCounter, getUserContacts, createOrUpdateContactRequest, + getUserContactLinkIdByCReq, getContactRequest', getContactRequest, getContactRequestIdByName, @@ -727,6 +728,11 @@ createOrUpdateContactRequest db vr user@User {userId, userContactId} userContact |] (displayName, fullName, image, contactLink, currentTs, userId, cReqId) +getUserContactLinkIdByCReq :: DB.Connection -> Int64 -> ExceptT StoreError IO Int64 +getUserContactLinkIdByCReq db contactRequestId = + ExceptT . firstRow fromOnly (SEContactRequestNotFound contactRequestId) $ + DB.query db "SELECT user_contact_link_id FROM contact_requests WHERE contact_request_id = ?" (Only contactRequestId) + getContactRequest' :: DB.Connection -> Int64 -> ExceptT StoreError IO (User, UserContactRequest) getContactRequest' db contactRequestId = do user <- getUserByContactRequestId db contactRequestId From 5f66c29dbdbde40fe2f36782fea24eb77576f978 Mon Sep 17 00:00:00 2001 From: Diogo Date: Thu, 5 Dec 2024 16:15:24 +0000 Subject: [PATCH 182/567] ios: fix open from notification and connected directly chat item chat loading (#5326) * ios: fix opening from notification and connected directly chat item chat loading * better fix --- apps/ios/Shared/Model/NtfManager.swift | 6 +++++- apps/ios/Shared/SimpleXApp.swift | 3 ++- .../Views/Chat/ChatItem/CIMemberCreatedContactView.swift | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/ios/Shared/Model/NtfManager.swift b/apps/ios/Shared/Model/NtfManager.swift index b2fa6a0200..6c33031eeb 100644 --- a/apps/ios/Shared/Model/NtfManager.swift +++ b/apps/ios/Shared/Model/NtfManager.swift @@ -26,6 +26,7 @@ enum NtfCallAction { class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject { static let shared = NtfManager() + public var navigatingToChat = false private var granted = false private var prevNtfTime: Dictionary = [:] @@ -74,7 +75,10 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject { } } else { if let chatId = content.targetContentIdentifier { - ItemsModel.shared.loadOpenChat(chatId) + self.navigatingToChat = true + ItemsModel.shared.loadOpenChat(chatId) { + self.navigatingToChat = false + } } } } diff --git a/apps/ios/Shared/SimpleXApp.swift b/apps/ios/Shared/SimpleXApp.swift index 0dd54782b4..10120db185 100644 --- a/apps/ios/Shared/SimpleXApp.swift +++ b/apps/ios/Shared/SimpleXApp.swift @@ -143,7 +143,8 @@ struct SimpleXApp: App { let chats = try await apiGetChatsAsync() await MainActor.run { chatModel.updateChats(chats) } if let id = chatModel.chatId, - let chat = chatModel.getChat(id) { + let chat = chatModel.getChat(id), + !NtfManager.shared.navigatingToChat { Task { await loadChat(chat: chat, clearItems: false) } } if let ncr = chatModel.ntfContactRequest { diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift index e9cd838234..d24c737907 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift @@ -23,7 +23,7 @@ struct CIMemberCreatedContactView: View { .onTapGesture { dismissAllSheets(animated: true) DispatchQueue.main.async { - m.chatId = "@\(contactId)" + ItemsModel.shared.loadOpenChat("@\(contactId)") } } } else { From de76e271a85e9e358a36ca8ce602a5961addf237 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 6 Dec 2024 01:10:36 +0700 Subject: [PATCH 183/567] android, desktop: displaying deleted message with file in chat list (#5329) --- .../chat/simplex/common/views/chatlist/ChatPreviewView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt index 036768c6e7..0e0c3e74f4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt @@ -347,7 +347,7 @@ fun ChatPreviewView( chatItemContentPreview(chat, ci) } if (mc !is MsgContent.MCVoice || !showContentPreview || mc.text.isNotEmpty() || chatModelDraftChatId == chat.id) { - Box(Modifier.offset(x = if (mc is MsgContent.MCFile) -15.sp.toDp() else 0.dp)) { + Box(Modifier.offset(x = if (mc is MsgContent.MCFile && ci.meta.itemDeleted == null) -15.sp.toDp() else 0.dp)) { chatPreviewText() } } From 60e0e454e8b0dcf3fe43c926e160cc39059801d9 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 5 Dec 2024 18:32:00 +0000 Subject: [PATCH 184/567] core: only update business chat preferences (#5325) * core: only update business chat preferences * rework * migration * test, bump protocol version * fix tests --- simplex-chat.cabal | 1 + src/Simplex/Chat.hs | 71 +++++++++++++------ .../M20241205_business_chat_members.hs | 18 +++++ src/Simplex/Chat/Migrations/chat_schema.sql | 3 +- src/Simplex/Chat/Protocol.hs | 16 ++++- src/Simplex/Chat/Store/Connections.hs | 2 +- src/Simplex/Chat/Store/Groups.hs | 52 +++++++++----- src/Simplex/Chat/Store/Migrations.hs | 4 +- src/Simplex/Chat/Store/Shared.hs | 14 ++-- src/Simplex/Chat/Types.hs | 9 +-- tests/ChatTests/Profiles.hs | 59 +++++++++++++-- tests/ProtocolTests.hs | 12 ++-- 12 files changed, 201 insertions(+), 60 deletions(-) create mode 100644 src/Simplex/Chat/Migrations/M20241205_business_chat_members.hs diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 92eea030c0..e23305faf9 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -154,6 +154,7 @@ library Simplex.Chat.Migrations.M20241027_server_operators Simplex.Chat.Migrations.M20241125_indexes Simplex.Chat.Migrations.M20241128_business_chats + Simplex.Chat.Migrations.M20241205_business_chat_members Simplex.Chat.Mobile Simplex.Chat.Mobile.File Simplex.Chat.Mobile.Shared diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index af730e4c06..d5ad68079f 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2929,11 +2929,22 @@ processChatCommand' vr = \case lift . when (directOrUsed ct') $ createSndFeatureItems user ct ct' pure $ CRContactPrefsUpdated user ct ct' runUpdateGroupProfile :: User -> Group -> GroupProfile -> CM ChatResponse - runUpdateGroupProfile user (Group g@GroupInfo {groupProfile = p@GroupProfile {displayName = n}} ms) p'@GroupProfile {displayName = n'} = do + runUpdateGroupProfile user (Group g@GroupInfo {businessChat, groupProfile = p@GroupProfile {displayName = n}} ms) p'@GroupProfile {displayName = n'} = do assertUserGroupRole g GROwner when (n /= n') $ checkValidName n' g' <- withStore $ \db -> updateGroupProfile db user g p' - msg <- sendGroupMessage user g' ms (XGrpInfo p') + msg <- case businessChat of + Just BusinessChatInfo {businessId} -> do + let (newMs, oldMs) = partition (\m -> maxVersion (memberChatVRange m) >= businessChatPrefsVersion) ms + -- this is a fallback to send the members with the old version correct profile of the business when preferences change + unless (null oldMs) $ do + GroupMember {memberProfile = LocalProfile {displayName, fullName, image}} <- + withStore $ \db -> getGroupMemberByMemberId db vr user g businessId + let p'' = p' {displayName, fullName, image} :: GroupProfile + void $ sendGroupMessage user g' oldMs (XGrpInfo p'') + let ps' = fromMaybe defaultBusinessGroupPrefs $ groupPreferences p' + sendGroupMessage user g' newMs $ XGrpPrefs ps' + Nothing -> sendGroupMessage user g' ms (XGrpInfo p') let cd = CDGroupSnd g' unless (sameGroupProfileInfo p p') $ do ci <- saveSndChatItem user cd msg (CISndGroupEvent $ SGEGroupUpdated p') @@ -3026,7 +3037,7 @@ processChatCommand' vr = \case invitedMember = MemberIdRole memberId memRole, connRequest = cReq, groupProfile, - businessChat, + business = businessChat, groupLinkId = Nothing, groupSize = Just currentMemCount } @@ -4003,7 +4014,7 @@ acceptGroupJoinRequestAsync fromMemberName = displayName, invitedMember = MemberIdRole memberId gLinkMemRole, groupProfile, - businessChat, + business = businessChat, groupSize = Just currentMemCount } subMode <- chatReadVar subscriptionMode @@ -4038,7 +4049,7 @@ acceptBusinessJoinRequestAsync -- This refers to the "title member" that defines the group name and profile. -- This coincides with fromMember to be current user when accepting the connecting user, -- but it will be different when inviting somebody else. - businessChat = Just $ BusinessChatInfo userMemberId BCBusiness, + business = Just $ BusinessChatInfo {chatType = BCBusiness, businessId = userMemberId, customerId = memberId}, groupSize = Just 1 } subMode <- chatReadVar subscriptionMode @@ -5042,7 +5053,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = invitedMember = MemberIdRole memberId memRole, connRequest = cReq, groupProfile, - businessChat = Nothing, + business = Nothing, groupLinkId = groupLinkId, groupSize = Just currentMemCount } @@ -5299,6 +5310,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = XGrpLeave -> xGrpLeave gInfo m' msg brokerTs XGrpDel -> xGrpDel gInfo m' msg brokerTs XGrpInfo p' -> xGrpInfo gInfo m' p' msg brokerTs + XGrpPrefs ps' -> xGrpPrefs gInfo m' ps' XGrpDirectInv connReq mContent_ -> memberCanSend m' $ xGrpDirectInv gInfo m' conn' connReq mContent_ msg brokerTs XGrpMsgForward memberId msg' msgTs -> xGrpMsgForward gInfo m' memberId msg' msgTs XInfoProbe probe -> xInfoProbe (COMGroupMember m') probe @@ -5416,8 +5428,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = let GroupInfo {businessChat} = gInfo GroupMember {memberId = joiningMemberId} = m case businessChat of - Just BusinessChatInfo {memberId, chatType = BCCustomer} - | joiningMemberId == memberId -> useReply <$> withStore (`getUserAddress` user) + Just BusinessChatInfo {customerId, chatType = BCCustomer} + | joiningMemberId == customerId -> useReply <$> withStore (`getUserAddress` user) where useReply UserContactLink {autoAccept} = case autoAccept of Just AutoAccept {businessAddress, autoReply} | businessAddress -> autoReply @@ -6465,7 +6477,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = processMemberProfileUpdate :: GroupInfo -> GroupMember -> Profile -> Bool -> Maybe UTCTime -> CM GroupMember processMemberProfileUpdate gInfo m@GroupMember {memberProfile = p, memberContactId} p' createItems itemTs_ | redactedMemberProfile (fromLocalProfile p) /= redactedMemberProfile p' = do - updateBusinessChatProfile gInfo m + updateBusinessChatProfile gInfo case memberContactId of Nothing -> do m' <- withStore $ \db -> updateMemberProfile db user m p' @@ -6491,11 +6503,14 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = | otherwise = pure m where - updateBusinessChatProfile g@GroupInfo {businessChat} GroupMember {memberId} = case businessChat of - Just BusinessChatInfo {memberId = mId} | mId == memberId -> do + updateBusinessChatProfile g@GroupInfo {businessChat} = case businessChat of + Just bc | isMainBusinessMember bc m -> do g' <- withStore $ \db -> updateGroupProfileFromMember db user g p' toView $ CRGroupUpdated user g g' (Just m) _ -> pure () + isMainBusinessMember BusinessChatInfo {chatType, businessId, customerId} GroupMember {memberId} = case chatType of + BCBusiness -> businessId == memberId + BCCustomer -> customerId == memberId createProfileUpdatedItem m' = when createItems $ do let ciContent = CIRcvGroupEvent $ RGEMemberProfileUpdated (fromLocalProfile p) p' @@ -7008,16 +7023,31 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = toView $ CRGroupDeleted user gInfo {membership = membership {memberStatus = GSMemGroupDeleted}} m xGrpInfo :: GroupInfo -> GroupMember -> GroupProfile -> RcvMessage -> UTCTime -> CM () - xGrpInfo g@GroupInfo {groupProfile = p} m@GroupMember {memberRole} p' msg brokerTs + xGrpInfo g@GroupInfo {groupProfile = p, businessChat} m@GroupMember {memberRole} p' msg brokerTs | memberRole < GROwner = messageError "x.grp.info with insufficient member permissions" - | otherwise = unless (p == p') $ do - g' <- withStore $ \db -> updateGroupProfile db user g p' - toView $ CRGroupUpdated user g g' (Just m) - let cd = CDGroupRcv g' m - unless (sameGroupProfileInfo p p') $ do - ci <- saveRcvChatItem user cd msg brokerTs (CIRcvGroupEvent $ RGEGroupUpdated p') - groupMsgToView g' ci - createGroupFeatureChangedItems user cd CIRcvGroupFeature g g' + | otherwise = case businessChat of + Nothing -> unless (p == p') $ do + g' <- withStore $ \db -> updateGroupProfile db user g p' + toView $ CRGroupUpdated user g g' (Just m) + let cd = CDGroupRcv g' m + unless (sameGroupProfileInfo p p') $ do + ci <- saveRcvChatItem user cd msg brokerTs (CIRcvGroupEvent $ RGEGroupUpdated p') + groupMsgToView g' ci + createGroupFeatureChangedItems user cd CIRcvGroupFeature g g' + Just _ -> updateGroupPrefs_ g m $ fromMaybe defaultBusinessGroupPrefs $ groupPreferences p' + + xGrpPrefs :: GroupInfo -> GroupMember -> GroupPreferences -> CM () + xGrpPrefs g m@GroupMember {memberRole} ps' + | memberRole < GROwner = messageError "x.grp.prefs with insufficient member permissions" + | otherwise = updateGroupPrefs_ g m ps' + + updateGroupPrefs_ :: GroupInfo -> GroupMember -> GroupPreferences -> CM () + updateGroupPrefs_ g@GroupInfo {groupProfile = p} m ps' = + unless (groupPreferences p == Just ps') $ do + g' <- withStore' $ \db -> updateGroupPreferences db user g ps' + toView $ CRGroupUpdated user g g' (Just m) + let cd = CDGroupRcv g' m + createGroupFeatureChangedItems user cd CIRcvGroupFeature g g' xGrpDirectInv :: GroupInfo -> GroupMember -> Connection -> ConnReqInvitation -> Maybe MsgContent -> RcvMessage -> UTCTime -> CM () xGrpDirectInv g m mConn connReq mContent_ msg brokerTs = do @@ -7098,6 +7128,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = XGrpLeave -> xGrpLeave gInfo author rcvMsg msgTs XGrpDel -> xGrpDel gInfo author rcvMsg msgTs XGrpInfo p' -> xGrpInfo gInfo author p' rcvMsg msgTs + XGrpPrefs ps' -> xGrpPrefs gInfo author ps' _ -> messageError $ "x.grp.msg.forward: unsupported forwarded event " <> T.pack (show $ toCMEventTag event) createUnknownMember :: GroupInfo -> MemberId -> CM GroupMember diff --git a/src/Simplex/Chat/Migrations/M20241205_business_chat_members.hs b/src/Simplex/Chat/Migrations/M20241205_business_chat_members.hs new file mode 100644 index 0000000000..5d019d73e1 --- /dev/null +++ b/src/Simplex/Chat/Migrations/M20241205_business_chat_members.hs @@ -0,0 +1,18 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Migrations.M20241205_business_chat_members where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20241205_business_chat_members :: Query +m20241205_business_chat_members = + [sql| +ALTER TABLE groups ADD COLUMN customer_member_id BLOB NULL; +|] + +down_m20241205_business_chat_members :: Query +down_m20241205_business_chat_members = + [sql| +ALTER TABLE groups DROP COLUMN customer_member_id; +|] diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index 621c28f91e..94ccc65b7f 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -130,7 +130,8 @@ CREATE TABLE groups( ui_themes TEXT, business_member_id BLOB NULL, business_chat TEXT NULL, - business_xcontact_id BLOB NULL, -- received + business_xcontact_id BLOB NULL, + customer_member_id BLOB NULL, -- received FOREIGN KEY(user_id, local_display_name) REFERENCES display_names(user_id, local_display_name) ON DELETE CASCADE diff --git a/src/Simplex/Chat/Protocol.hs b/src/Simplex/Chat/Protocol.hs index 8afefdc850..934f23007d 100644 --- a/src/Simplex/Chat/Protocol.hs +++ b/src/Simplex/Chat/Protocol.hs @@ -46,6 +46,7 @@ import Database.SQLite.Simple.FromField (FromField (..)) import Database.SQLite.Simple.ToField (ToField (..)) import Simplex.Chat.Call import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Shared import Simplex.Messaging.Agent.Protocol (VersionSMPA, pqdrSMPAgentVersion) import Simplex.Messaging.Compression (Compressed, compress1, decompress1) @@ -67,12 +68,13 @@ import Simplex.Messaging.Version hiding (version) -- 8 - compress messages and PQ e2e encryption (2024-03-08) -- 9 - batch sending in direct connections (2024-07-24) -- 10 - business chats (2024-11-29) +-- 11 - fix profile update in business chats (2024-12-05) -- This should not be used directly in code, instead use `maxVersion chatVRange` from ChatConfig. -- This indirection is needed for backward/forward compatibility testing. -- Testing with real app versions is still needed, as tests use the current code with different version ranges, not the old code. currentChatVersion :: VersionChat -currentChatVersion = VersionChat 10 +currentChatVersion = VersionChat 11 -- This should not be used directly in code, instead use `chatVRange` from ChatConfig (see comment above) supportedChatVRange :: VersionRangeChat @@ -115,6 +117,10 @@ batchSend2Version = VersionChat 9 businessChatsVersion :: VersionChat businessChatsVersion = VersionChat 10 +-- support updating preferences in business chats (XGrpPrefs message) +businessChatPrefsVersion :: VersionChat +businessChatPrefsVersion = VersionChat 11 + agentToChatVersion :: VersionSMPA -> VersionChat agentToChatVersion v | v < pqdrSMPAgentVersion = initialChatVersion @@ -299,6 +305,7 @@ data ChatMsgEvent (e :: MsgEncoding) where XGrpLeave :: ChatMsgEvent 'Json XGrpDel :: ChatMsgEvent 'Json XGrpInfo :: GroupProfile -> ChatMsgEvent 'Json + XGrpPrefs :: GroupPreferences -> ChatMsgEvent 'Json XGrpDirectInv :: ConnReqInvitation -> Maybe MsgContent -> ChatMsgEvent 'Json XGrpMsgForward :: MemberId -> ChatMessage 'Json -> UTCTime -> ChatMsgEvent 'Json XInfoProbe :: Probe -> ChatMsgEvent 'Json @@ -339,6 +346,7 @@ isForwardedGroupMsg ev = case ev of XGrpLeave -> True XGrpDel -> True -- TODO there should be a special logic - host should forward before deleting connections XGrpInfo _ -> True + XGrpPrefs _ -> True _ -> False forwardedGroupMsg :: forall e. MsgEncodingI e => ChatMessage e -> Maybe (ChatMessage 'Json) @@ -721,6 +729,7 @@ data CMEventTag (e :: MsgEncoding) where XGrpLeave_ :: CMEventTag 'Json XGrpDel_ :: CMEventTag 'Json XGrpInfo_ :: CMEventTag 'Json + XGrpPrefs_ :: CMEventTag 'Json XGrpDirectInv_ :: CMEventTag 'Json XGrpMsgForward_ :: CMEventTag 'Json XInfoProbe_ :: CMEventTag 'Json @@ -771,6 +780,7 @@ instance MsgEncodingI e => StrEncoding (CMEventTag e) where XGrpLeave_ -> "x.grp.leave" XGrpDel_ -> "x.grp.del" XGrpInfo_ -> "x.grp.info" + XGrpPrefs_ -> "x.grp.prefs" XGrpDirectInv_ -> "x.grp.direct.inv" XGrpMsgForward_ -> "x.grp.msg.forward" XInfoProbe_ -> "x.info.probe" @@ -822,6 +832,7 @@ instance StrEncoding ACMEventTag where "x.grp.leave" -> XGrpLeave_ "x.grp.del" -> XGrpDel_ "x.grp.info" -> XGrpInfo_ + "x.grp.prefs" -> XGrpPrefs_ "x.grp.direct.inv" -> XGrpDirectInv_ "x.grp.msg.forward" -> XGrpMsgForward_ "x.info.probe" -> XInfoProbe_ @@ -869,6 +880,7 @@ toCMEventTag msg = case msg of XGrpLeave -> XGrpLeave_ XGrpDel -> XGrpDel_ XGrpInfo _ -> XGrpInfo_ + XGrpPrefs _ -> XGrpPrefs_ XGrpDirectInv _ _ -> XGrpDirectInv_ XGrpMsgForward {} -> XGrpMsgForward_ XInfoProbe _ -> XInfoProbe_ @@ -969,6 +981,7 @@ appJsonToCM AppMessageJson {v, msgId, event, params} = do XGrpLeave_ -> pure XGrpLeave XGrpDel_ -> pure XGrpDel XGrpInfo_ -> XGrpInfo <$> p "groupProfile" + XGrpPrefs_ -> XGrpPrefs <$> p "groupPreferences" XGrpDirectInv_ -> XGrpDirectInv <$> p "connReq" <*> opt "content" XGrpMsgForward_ -> XGrpMsgForward <$> p "memberId" <*> p "msg" <*> p "msgTs" XInfoProbe_ -> XInfoProbe <$> p "probe" @@ -1030,6 +1043,7 @@ chatToAppMessage ChatMessage {chatVRange, msgId, chatMsgEvent} = case encoding @ XGrpLeave -> JM.empty XGrpDel -> JM.empty XGrpInfo p -> o ["groupProfile" .= p] + XGrpPrefs p -> o ["groupPreferences" .= p] XGrpDirectInv connReq content -> o $ ("content" .=? content) ["connReq" .= connReq] XGrpMsgForward memberId msg msgTs -> o ["memberId" .= memberId, "msg" .= msg, "msgTs" .= msgTs] XInfoProbe probe -> o ["probe" .= probe] diff --git a/src/Simplex/Chat/Store/Connections.hs b/src/Simplex/Chat/Store/Connections.hs index fe52c6d7b7..db787b0112 100644 --- a/src/Simplex/Chat/Store/Connections.hs +++ b/src/Simplex/Chat/Store/Connections.hs @@ -123,7 +123,7 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, - g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data, + g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, -- GroupInfo {membership} mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 895acf0a7d..49158a60c9 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -39,6 +39,7 @@ module Simplex.Chat.Store.Groups getGroupInfoByUserContactLinkConnReq, getGroupInfoByGroupLinkHash, updateGroupProfile, + updateGroupPreferences, updateGroupProfileFromMember, getGroupIdByName, getGroupMemberIdByName, @@ -257,7 +258,7 @@ getGroupAndMember db User {userId, userContactId} groupMemberId vr = -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, - g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data, + g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, -- GroupInfo {membership} mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, @@ -339,7 +340,7 @@ createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = Exc -- | creates a new group record for the group the current user was invited to, or returns an existing one createGroupInvitation :: DB.Connection -> VersionRangeChat -> User -> Contact -> GroupInvitation -> Maybe ProfileId -> ExceptT StoreError IO (GroupInfo, GroupMemberId) createGroupInvitation _ _ _ Contact {localDisplayName, activeConn = Nothing} _ _ = throwError $ SEContactNotReady localDisplayName -createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activeConn = Just Connection {customUserProfileId, peerChatVRange}} GroupInvitation {fromMember, invitedMember, connRequest, groupProfile, businessChat} incognitoProfileId = do +createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activeConn = Just Connection {customUserProfileId, peerChatVRange}} GroupInvitation {fromMember, invitedMember, connRequest, groupProfile, business} incognitoProfileId = do liftIO getInvitationGroupId_ >>= \case Nothing -> createGroupInvitation_ Just gId -> do @@ -377,10 +378,10 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ [sql| INSERT INTO groups (group_profile_id, local_display_name, inv_queue_info, host_conn_custom_user_profile_id, user_id, enable_ntfs, - created_at, updated_at, chat_ts, user_member_profile_sent_at, business_member_id, business_chat) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?) + created_at, updated_at, chat_ts, user_member_profile_sent_at, business_chat, business_member_id, customer_member_id) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) |] - ((profileId, localDisplayName, connRequest, customUserProfileId, userId, True, currentTs, currentTs, currentTs, currentTs) :. businessChatTuple businessChat) + ((profileId, localDisplayName, connRequest, customUserProfileId, userId, True, currentTs, currentTs, currentTs, currentTs) :. businessChatInfoRow business) insertedRowId db let hostVRange = adjustedMemberVRange vr peerChatVRange GroupMember {groupMemberId} <- createContactMemberInv_ db user groupId Nothing contact fromMember GCHostMember GSMemInvited IBUnknown Nothing currentTs hostVRange @@ -406,10 +407,10 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ groupMemberId ) -businessChatTuple :: Maybe BusinessChatInfo -> (Maybe MemberId, Maybe BusinessChatType) -businessChatTuple = \case - Just BusinessChatInfo {memberId, chatType} -> (Just memberId, Just chatType) - Nothing -> (Nothing, Nothing) +businessChatInfoRow :: Maybe BusinessChatInfo -> BusinessChatInfoRow +businessChatInfoRow = \case + Just BusinessChatInfo {chatType, businessId, customerId} -> (Just chatType, Just businessId, Just customerId) + Nothing -> (Nothing, Nothing, Nothing) adjustedMemberVRange :: VersionRangeChat -> VersionRangeChat -> VersionRangeChat adjustedMemberVRange chatVR vr@(VersionRange minV maxV) = @@ -497,7 +498,7 @@ createGroupInvitedViaLink vr user@User {userId, userContactId} Connection {connId, customUserProfileId} - GroupLinkInvitation {fromMember, fromMemberName, invitedMember, groupProfile, businessChat} = do + GroupLinkInvitation {fromMember, fromMemberName, invitedMember, groupProfile, business} = do currentTs <- liftIO getCurrentTime groupId <- insertGroup_ currentTs hostMemberId <- insertHost_ currentTs groupId @@ -521,10 +522,10 @@ createGroupInvitedViaLink [sql| INSERT INTO groups (group_profile_id, local_display_name, host_conn_custom_user_profile_id, user_id, enable_ntfs, - created_at, updated_at, chat_ts, user_member_profile_sent_at, business_member_id, business_chat) - VALUES (?,?,?,?,?,?,?,?,?,?,?) + created_at, updated_at, chat_ts, user_member_profile_sent_at, business_chat, business_member_id, customer_member_id) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?) |] - ((profileId, localDisplayName, customUserProfileId, userId, True, currentTs, currentTs, currentTs, currentTs) :. businessChatTuple businessChat) + ((profileId, localDisplayName, customUserProfileId, userId, True, currentTs, currentTs, currentTs, currentTs) :. businessChatInfoRow business) insertedRowId db insertHost_ currentTs groupId = do let fromMemberProfile = profileFromName fromMemberName @@ -631,7 +632,7 @@ getUserGroupDetails db vr User {userId, userContactId} _contactId_ search_ = SELECT g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, - g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data, + g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences FROM groups g @@ -918,9 +919,9 @@ createBusinessRequestGroup UserContactRequest {cReqChatVRange, xContactId, profile = Profile {displayName, fullName, image, contactLink, preferences}} groupPreferences = do currentTs <- liftIO getCurrentTime - (groupId, membership) <- insertGroup_ currentTs + (groupId, membership@GroupMember {memberId = userMemberId}) <- insertGroup_ currentTs (groupMemberId, memberId) <- insertClientMember_ currentTs groupId membership - liftIO $ DB.execute db "UPDATE groups SET business_member_id = ? WHERE group_id = ?" (memberId, groupId) + liftIO $ DB.execute db "UPDATE groups SET business_member_id = ?, customer_member_id = ? WHERE group_id = ?" (userMemberId, memberId, groupId) groupInfo <- getGroupInfo db vr user groupId clientMember <- getGroupMemberById db vr user groupMemberId pure (groupInfo, clientMember) @@ -1370,7 +1371,7 @@ getViaGroupMember db vr User {userId, userContactId} Contact {contactId} = -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, - g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data, + g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, -- GroupInfo {membership} mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, @@ -1456,6 +1457,23 @@ updateGroupProfile db user@User {userId} g@GroupInfo {groupId, localDisplayName, (ldn, currentTs, userId, groupId) safeDeleteLDN db user localDisplayName +updateGroupPreferences :: DB.Connection -> User -> GroupInfo -> GroupPreferences -> IO GroupInfo +updateGroupPreferences db User {userId} g@GroupInfo {groupId, groupProfile = p} ps = do + currentTs <- getCurrentTime + DB.execute + db + [sql| + UPDATE group_profiles + SET preferences = ?, updated_at = ? + WHERE group_profile_id IN ( + SELECT group_profile_id + FROM groups + WHERE user_id = ? AND group_id = ? + ) + |] + (ps, currentTs, userId, groupId) + pure (g :: GroupInfo) {groupProfile = p {groupPreferences = Just ps}} + updateGroupProfileFromMember :: DB.Connection -> User -> GroupInfo -> Profile -> ExceptT StoreError IO GroupInfo updateGroupProfileFromMember db user g@GroupInfo {groupId} Profile {displayName = n, fullName = fn, image = img} = do p <- getGroupProfile -- to avoid any race conditions with UI diff --git a/src/Simplex/Chat/Store/Migrations.hs b/src/Simplex/Chat/Store/Migrations.hs index 6654dec034..65fe8223fe 100644 --- a/src/Simplex/Chat/Store/Migrations.hs +++ b/src/Simplex/Chat/Store/Migrations.hs @@ -118,6 +118,7 @@ import Simplex.Chat.Migrations.M20241023_chat_item_autoincrement_id import Simplex.Chat.Migrations.M20241027_server_operators import Simplex.Chat.Migrations.M20241125_indexes import Simplex.Chat.Migrations.M20241128_business_chats +import Simplex.Chat.Migrations.M20241205_business_chat_members import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -235,7 +236,8 @@ schemaMigrations = ("20241023_chat_item_autoincrement_id", m20241023_chat_item_autoincrement_id, Just down_m20241023_chat_item_autoincrement_id), ("20241027_server_operators", m20241027_server_operators, Just down_m20241027_server_operators), ("20241125_indexes", m20241125_indexes, Just down_m20241125_indexes), - ("20241128_business_chats", m20241128_business_chats, Just down_m20241128_business_chats) + ("20241128_business_chats", m20241128_business_chats, Just down_m20241128_business_chats), + ("20241205_business_chat_members", m20241205_business_chat_members, Just down_m20241205_business_chat_members) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index b00ba5705f..851078ec1f 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -546,17 +546,19 @@ safeDeleteLDN db User {userId} localDisplayName = do |] (userId, localDisplayName, userId) -type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe MsgFilter, Maybe Bool, Bool, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime, Maybe MemberId, Maybe BusinessChatType, Maybe UIThemeEntityOverrides, Maybe CustomData) :. GroupMemberRow +type BusinessChatInfoRow = (Maybe BusinessChatType, Maybe MemberId, Maybe MemberId) + +type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe MsgFilter, Maybe Bool, Bool, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. BusinessChatInfoRow :. (Maybe UIThemeEntityOverrides, Maybe CustomData) :. GroupMemberRow type GroupMemberRow = ((Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, Bool, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences)) toGroupInfo :: VersionRangeChat -> Int64 -> GroupInfoRow -> GroupInfo -toGroupInfo vr userContactId ((groupId, localDisplayName, displayName, fullName, description, image, hostConnCustomUserProfileId, enableNtfs_, sendRcpts, favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt, businessMemberId, businessChatType, uiThemes, customData) :. userMemberRow) = +toGroupInfo vr userContactId ((groupId, localDisplayName, displayName, fullName, description, image, hostConnCustomUserProfileId, enableNtfs_, sendRcpts, favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. businessRow :. (uiThemes, customData) :. userMemberRow) = let membership = (toGroupMember userContactId userMemberRow) {memberChatVRange = vr} chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts, favorite} fullGroupPreferences = mergeGroupPreferences groupPreferences groupProfile = GroupProfile {displayName, fullName, description, image, groupPreferences} - businessChat = BusinessChatInfo <$> businessMemberId <*> businessChatType + businessChat = toBusinessChatInfo businessRow in GroupInfo {groupId, localDisplayName, groupProfile, businessChat, fullGroupPreferences, membership, hostConnCustomUserProfileId, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, uiThemes, customData} toGroupMember :: Int64 -> GroupMemberRow -> GroupMember @@ -569,6 +571,10 @@ toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberChatVRange = fromMaybe (versionToRange maxVer) $ safeVersionRange minVer maxVer in GroupMember {..} +toBusinessChatInfo :: BusinessChatInfoRow -> Maybe BusinessChatInfo +toBusinessChatInfo (Just chatType, Just businessId, Just customerId) = Just BusinessChatInfo {chatType, businessId, customerId} +toBusinessChatInfo _ = Nothing + groupInfoQuery :: Query groupInfoQuery = [sql| @@ -576,7 +582,7 @@ groupInfoQuery = -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, - g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data, + g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, -- GroupMember - membership mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index ec8f546d2f..77a02a4bc1 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -617,7 +617,7 @@ data GroupInvitation = GroupInvitation invitedMember :: MemberIdRole, connRequest :: ConnReqInvitation, groupProfile :: GroupProfile, - businessChat :: Maybe BusinessChatInfo, + business :: Maybe BusinessChatInfo, groupLinkId :: Maybe GroupLinkId, groupSize :: Maybe Int } @@ -628,7 +628,7 @@ data GroupLinkInvitation = GroupLinkInvitation fromMemberName :: ContactName, invitedMember :: MemberIdRole, groupProfile :: GroupProfile, - businessChat :: Maybe BusinessChatInfo, + business :: Maybe BusinessChatInfo, groupSize :: Maybe Int } deriving (Eq, Show) @@ -654,8 +654,9 @@ data MemberInfo = MemberInfo deriving (Eq, Show) data BusinessChatInfo = BusinessChatInfo - { memberId :: MemberId, - chatType :: BusinessChatType + { chatType :: BusinessChatType, + businessId :: MemberId, + customerId :: MemberId } deriving (Eq, Show) diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index ebfeecdbca..2bf157419c 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -734,8 +734,8 @@ testBusinessAddress = testChat3 businessProfile aliceProfile {fullName = "Alice (biz <# "#bob bob_1> hey there") testBusinessUpdateProfiles :: HasCallStack => FilePath -> IO () -testBusinessUpdateProfiles = testChat3 businessProfile aliceProfile bobProfile $ - \biz alice bob -> do +testBusinessUpdateProfiles = testChat4 businessProfile aliceProfile bobProfile cathProfile $ + \biz alice bob cath -> do biz ##> "/ad" cLink <- getContactLink biz True biz ##> "/auto_accept on business text Welcome" @@ -794,9 +794,37 @@ testBusinessUpdateProfiles = testChat3 businessProfile aliceProfile bobProfile $ bob #> "#biz hi there" -- profile update is sent to group with message alice <# "#biz robert> hi there" biz <# "#alisa robert> hi there" + -- add business team member + connectUsers biz cath + biz ##> "/a #alisa cath" + biz <## "invitation to join the group #alisa sent to cath" + cath <## "#alisa: biz invites you to join the group as member" + cath <## "use /j alisa to accept" + cath ##> "/j alisa" + concurrentlyN_ + [ do + cath <## "#alisa: you joined the group" + cath + <### + [ WithTime "#alisa biz> Welcome [>>]", + WithTime "#alisa biz> hi [>>]", + WithTime "#alisa alisa_1> hello [>>]", + WithTime "#alisa alisa_1> hello again [>>]", + WithTime "#alisa robert> hi there [>>]" + ] + cath <## "#alisa: member alisa_1 is connected" + cath <## "#alisa: member robert is connected", + biz <## "#alisa: cath joined the group", + do + alice <## "#biz: biz_1 added cath (Catherine) to the group (connecting...)" + alice <## "#biz: new member cath is connected", + do + bob <## "#biz: biz_1 added cath (Catherine) to the group (connecting...)" + bob <## "#biz: new member cath is connected" + ] -- both customers receive business profile change biz ##> "/p business" - biz <## "user profile is changed to business (your 0 contacts are notified)" + biz <## "user profile is changed to business (your 1 contacts are notified)" biz #> "#alisa hey" concurrentlyN_ [ do @@ -806,7 +834,28 @@ testBusinessUpdateProfiles = testChat3 businessProfile aliceProfile bobProfile $ do bob <## "biz_1 updated group #biz:" bob <## "changed to #business" - bob <# "#business business_1> hey" + bob <# "#business business_1> hey", + do + cath <## "contact biz changed to business" + cath <## "use @business to send messages" + cath <# "#alisa business> hey" + ] + biz ##> "/set voice #alisa on" + biz <## "updated group preferences:" + biz <## "Voice messages: on" + concurrentlyN_ + [ do + alice <## "business_1 updated group #business:" + alice <## "updated group preferences:" + alice <## "Voice messages: on", + do + bob <## "business_1 updated group #business:" + bob <## "updated group preferences:" + bob <## "Voice messages: on", + do + cath <## "business updated group #alisa:" + cath <## "updated group preferences:" + cath <## "Voice messages: on" ] testPlanAddressOkKnown :: HasCallStack => FilePath -> IO () @@ -2512,7 +2561,7 @@ testSetUITheme = a <## "you've shared main profile with this contact" a <## "connection not verified, use /code command to see security code" a <## "quantum resistant end-to-end encryption" - a <## "peer chat protocol version range: (Version 1, Version 10)" + a <## "peer chat protocol version range: (Version 1, Version 11)" groupInfo a = do a <## "group ID: 1" a <## "current members: 1" diff --git a/tests/ProtocolTests.hs b/tests/ProtocolTests.hs index 2eb946d731..523df81ade 100644 --- a/tests/ProtocolTests.hs +++ b/tests/ProtocolTests.hs @@ -133,7 +133,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do "{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" ##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing))) it "x.msg.new chat message with chat version range" $ - "{\"v\":\"1-10\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" + "{\"v\":\"1-11\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" ##==## ChatMessage supportedChatVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing))) it "x.msg.new quote" $ "{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello to you too\",\"type\":\"text\"},\"quote\":{\"content\":{\"text\":\"hello there!\",\"type\":\"text\"},\"msgRef\":{\"msgId\":\"BQYHCA==\",\"sent\":true,\"sentAt\":\"1970-01-01T00:00:01.000000001Z\"}}}}" @@ -232,10 +232,10 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do ==# XContact testProfile Nothing it "x.grp.inv" $ "{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}}}}" - #==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, businessChat = Nothing, groupLinkId = Nothing, groupSize = Nothing} + #==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, business = Nothing, groupLinkId = Nothing, groupSize = Nothing} it "x.grp.inv with group link id" $ "{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}, \"groupLinkId\":\"AQIDBA==\"}}}" - #==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, businessChat = Nothing, groupLinkId = Just $ GroupLinkId "\1\2\3\4", groupSize = Nothing} + #==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, business = Nothing, groupLinkId = Just $ GroupLinkId "\1\2\3\4", groupSize = Nothing} it "x.grp.acpt without incognito profile" $ "{\"v\":\"1\",\"event\":\"x.grp.acpt\",\"params\":{\"memberId\":\"AQIDBA==\"}}" #==# XGrpAcpt (MemberId "\1\2\3\4") @@ -243,13 +243,13 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do "{\"v\":\"1\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemNew MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} it "x.grp.mem.new with member chat version range" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-10\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" + "{\"v\":\"1\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-11\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemNew MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile} it "x.grp.mem.intro" $ "{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} Nothing it "x.grp.mem.intro with member chat version range" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-10\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" + "{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-11\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile} Nothing it "x.grp.mem.intro with member restrictions" $ "{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberRestrictions\":{\"restriction\":\"blocked\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" @@ -264,7 +264,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do "{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} IntroInvitation {groupConnReq = testConnReq, directConnReq = Just testConnReq} it "x.grp.mem.fwd with member chat version range and w/t directConnReq" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-10\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" + "{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-11\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile} IntroInvitation {groupConnReq = testConnReq, directConnReq = Nothing} it "x.grp.mem.info" $ "{\"v\":\"1\",\"event\":\"x.grp.mem.info\",\"params\":{\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" From e9bf229a9d374b4be72074a698f2d27b99db11de Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Thu, 5 Dec 2024 18:36:07 +0000 Subject: [PATCH 185/567] core: 6.2.0.6 --- package.yaml | 2 +- simplex-chat.cabal | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.yaml b/package.yaml index 8752a141f7..b9c41ccdc0 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.2.0.5 +version: 6.2.0.6 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/simplex-chat.cabal b/simplex-chat.cabal index e23305faf9..ace5afd851 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.2.0.5 +version: 6.2.0.6 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From 69e23ad58ffc32509cebaf0749b22ef4486206d1 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Thu, 5 Dec 2024 22:45:19 +0400 Subject: [PATCH 186/567] android, desktop: don't show unwanted notifications (#5328) * android, desktop: don't show unwanted notifications * format * fix * code style --------- Co-authored-by: Avently <7953703+avently@users.noreply.github.com> --- .../chat/simplex/common/model/SimpleXAPI.kt | 16 +++++++++++----- .../chat/simplex/common/platform/NtfManager.kt | 14 +++++++++++--- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 757d80193c..94ce22d356 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -2436,9 +2436,7 @@ object ChatController { ) { receiveFile(rhId, r.user, file.fileId, auto = true) } - if (cItem.showNotification && (allowedToShowNotification() || chatModel.chatId.value != cInfo.id || chatModel.remoteHostId() != rhId)) { - ntfManager.notifyMessageReceived(r.user, cInfo, cItem) - } + ntfManager.notifyMessageReceived(rhId, r.user, cInfo, cItem) } } is CR.ChatItemsStatusesUpdated -> @@ -2452,7 +2450,7 @@ object ChatController { } } is CR.ChatItemUpdated -> - chatItemSimpleUpdate(rhId, r.user, r.chatItem) + chatItemUpdateNotify(rhId, r.user, r.chatItem) is CR.ChatItemReaction -> { if (active(r.user)) { withChats { @@ -2950,9 +2948,17 @@ object ChatController { } private suspend fun chatItemSimpleUpdate(rh: Long?, user: UserLike, aChatItem: AChatItem) { + if (activeUser(rh, user)) { + val cInfo = aChatItem.chatInfo + val cItem = aChatItem.chatItem + withChats { upsertChatItem(rh, cInfo, cItem) } + } + } + + private suspend fun chatItemUpdateNotify(rh: Long?, user: UserLike, aChatItem: AChatItem) { val cInfo = aChatItem.chatInfo val cItem = aChatItem.chatItem - val notify = { ntfManager.notifyMessageReceived(user, cInfo, cItem) } + val notify = { ntfManager.notifyMessageReceived(rh, user, cInfo, cItem) } if (!activeUser(rh, user)) { notify() } else if (withChats { upsertChatItem(rh, cInfo, cItem) }) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt index 1f1cb45d48..51d26f8ff2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt @@ -36,9 +36,17 @@ abstract class NtfManager { ) ) - fun notifyMessageReceived(user: UserLike, cInfo: ChatInfo, cItem: ChatItem) { - if (!cInfo.ntfsEnabled) return - displayNotification(user = user, chatId = cInfo.id, displayName = cInfo.displayName, msgText = hideSecrets(cItem)) + fun notifyMessageReceived(rhId: Long?, user: UserLike, cInfo: ChatInfo, cItem: ChatItem) { + if ( + cItem.showNotification && + cInfo.ntfsEnabled && + ( + allowedToShowNotification() || + chatModel.chatId.value != cInfo.id || + chatModel.remoteHostId() != rhId) + ) { + displayNotification(user = user, chatId = cInfo.id, displayName = cInfo.displayName, msgText = hideSecrets(cItem)) + } } fun acceptContactRequestAction(userId: Long?, incognito: Boolean, chatId: ChatId) { From ff504702de112206b9a10a752f079e6218d02c25 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 5 Dec 2024 21:42:53 +0000 Subject: [PATCH 187/567] ui: translations (#5330) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (German) Currently translated at 97.5% (2155 of 2209 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (French) Currently translated at 93.2% (2060 of 2209 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/ * Translated using Weblate (Italian) Currently translated at 97.5% (2155 of 2209 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Spanish) Currently translated at 97.6% (2157 of 2209 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Dutch) Currently translated at 97.5% (2154 of 2209 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 92.3% (2041 of 2209 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/ * Translated using Weblate (Arabic) Currently translated at 97.4% (2153 of 2209 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Ukrainian) Currently translated at 97.5% (2155 of 2209 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Polish) Currently translated at 93.2% (2059 of 2209 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pl/ * Translated using Weblate (Russian) Currently translated at 93.2% (2061 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 97.4% (2154 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2210 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Dutch) Currently translated at 100.0% (2210 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (1932 of 1932 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2210 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1932 of 1932 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Italian) Currently translated at 98.9% (2187 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Italian) Currently translated at 99.5% (2201 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Russian) Currently translated at 100.0% (2210 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (Russian) Currently translated at 96.6% (1868 of 1932 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ru/ * Translated using Weblate (Italian) Currently translated at 100.0% (2210 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (1932 of 1932 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Indonesian) Currently translated at 60.0% (1326 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (Russian) Currently translated at 100.0% (2210 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (Russian) Currently translated at 100.0% (1932 of 1932 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ru/ * Translated using Weblate (German) Currently translated at 97.5% (2155 of 2209 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (French) Currently translated at 93.2% (2060 of 2209 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/ * Translated using Weblate (Italian) Currently translated at 97.5% (2155 of 2209 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Spanish) Currently translated at 97.6% (2157 of 2209 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Dutch) Currently translated at 97.5% (2154 of 2209 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 92.3% (2041 of 2209 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/ * Translated using Weblate (Arabic) Currently translated at 97.4% (2153 of 2209 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Ukrainian) Currently translated at 97.5% (2155 of 2209 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Polish) Currently translated at 93.2% (2059 of 2209 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pl/ * Translated using Weblate (Russian) Currently translated at 93.2% (2061 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 97.4% (2154 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2210 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Dutch) Currently translated at 100.0% (2210 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (1932 of 1932 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2210 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1932 of 1932 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Italian) Currently translated at 98.9% (2187 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Italian) Currently translated at 99.5% (2201 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Russian) Currently translated at 100.0% (2210 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (Russian) Currently translated at 96.6% (1868 of 1932 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ru/ * Translated using Weblate (Italian) Currently translated at 100.0% (2210 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (1932 of 1932 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Indonesian) Currently translated at 60.0% (1326 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (Russian) Currently translated at 100.0% (2210 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (Russian) Currently translated at 100.0% (1932 of 1932 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ru/ * process localizations * update translations --------- Co-authored-by: Anonymous Co-authored-by: 大王叫我来巡山 Co-authored-by: M1K4 Co-authored-by: summoner001 Co-authored-by: Random Co-authored-by: Rafi --- .../Onboarding/ChooseServerOperators.swift | 2 +- .../Onboarding/SetNotificationsMode.swift | 3 +- .../NetworkAndServers/OperatorView.swift | 2 +- .../Views/UserSettings/UserProfilesView.swift | 2 +- .../bg.xcloc/Localized Contents/bg.xliff | 28 +- .../cs.xcloc/Localized Contents/cs.xliff | 28 +- .../de.xcloc/Localized Contents/de.xliff | 35 +- .../en.xcloc/Localized Contents/en.xliff | 35 +- .../es.xcloc/Localized Contents/es.xliff | 37 +- .../fi.xcloc/Localized Contents/fi.xliff | 28 +- .../fr.xcloc/Localized Contents/fr.xliff | 28 +- .../hu.xcloc/Localized Contents/hu.xliff | 65 +- .../it.xcloc/Localized Contents/it.xliff | 59 +- .../ja.xcloc/Localized Contents/ja.xliff | 28 +- .../nl.xcloc/Localized Contents/nl.xliff | 103 +-- .../pl.xcloc/Localized Contents/pl.xliff | 28 +- .../ru.xcloc/Localized Contents/ru.xliff | 153 ++++- .../th.xcloc/Localized Contents/th.xliff | 28 +- .../tr.xcloc/Localized Contents/tr.xliff | 28 +- .../uk.xcloc/Localized Contents/uk.xliff | 37 +- .../Localized Contents/zh-Hans.xliff | 28 +- .../SimpleX NSE/ru.lproj/Localizable.strings | 20 +- apps/ios/bg.lproj/Localizable.strings | 42 +- apps/ios/cs.lproj/Localizable.strings | 36 +- apps/ios/de.lproj/Localizable.strings | 51 +- apps/ios/es.lproj/Localizable.strings | 53 +- apps/ios/fi.lproj/Localizable.strings | 36 +- apps/ios/fr.lproj/Localizable.strings | 42 +- apps/ios/hu.lproj/Localizable.strings | 129 +++- apps/ios/it.lproj/Localizable.strings | 123 +++- apps/ios/ja.lproj/Localizable.strings | 36 +- apps/ios/nl.lproj/Localizable.strings | 167 +++-- apps/ios/pl.lproj/Localizable.strings | 42 +- apps/ios/ru.lproj/Localizable.strings | 399 ++++++++++- apps/ios/th.lproj/Localizable.strings | 36 +- apps/ios/tr.lproj/Localizable.strings | 42 +- apps/ios/uk.lproj/Localizable.strings | 50 +- apps/ios/zh-Hans.lproj/Localizable.strings | 42 +- .../commonMain/resources/MR/ar/strings.xml | 3 +- .../commonMain/resources/MR/base/strings.xml | 6 +- .../commonMain/resources/MR/de/strings.xml | 3 +- .../commonMain/resources/MR/es/strings.xml | 3 +- .../commonMain/resources/MR/fr/strings.xml | 3 +- .../commonMain/resources/MR/hu/strings.xml | 95 ++- .../commonMain/resources/MR/in/strings.xml | 53 ++ .../commonMain/resources/MR/it/strings.xml | 78 ++- .../commonMain/resources/MR/nl/strings.xml | 118 ++-- .../commonMain/resources/MR/pl/strings.xml | 3 +- .../resources/MR/pt-rBR/strings.xml | 3 +- .../commonMain/resources/MR/ru/strings.xml | 172 ++++- .../commonMain/resources/MR/uk/strings.xml | 3 +- .../resources/MR/zh-rCN/strings.xml | 621 +++++++++--------- 52 files changed, 2141 insertions(+), 1154 deletions(-) diff --git a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift index 14e08ff219..318e0b2f0d 100644 --- a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift +++ b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift @@ -323,7 +323,7 @@ struct ChooseServerOperators: View { VStack(alignment: .leading, spacing: 20) { if !operatorsWithConditionsAccepted.isEmpty { Text("Conditions are already accepted for following operator(s): **\(operatorsWithConditionsAccepted.map { $0.legalName_ }.joined(separator: ", "))**.") - Text("Same conditions will apply to operator(s): **\(acceptForOperators.map { $0.legalName_ }.joined(separator: ", "))**.") + Text("The same conditions will apply to operator(s): **\(acceptForOperators.map { $0.legalName_ }.joined(separator: ", "))**.") } else { Text("Conditions will be accepted for operator(s): **\(acceptForOperators.map { $0.legalName_ }.joined(separator: ", "))**.") } diff --git a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift index 6164fcae70..642220454c 100644 --- a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift +++ b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift @@ -19,7 +19,7 @@ struct SetNotificationsMode: View { GeometryReader { g in ScrollView { VStack(alignment: .center, spacing: 20) { - Text("Push Notifications") + Text("Push notifications") .font(.largeTitle) .bold() .padding(.top, 50) @@ -119,6 +119,7 @@ struct NtfModeSelector: View { Text(ntfModeShortDescription(mode)) .lineLimit(2) .font(.callout) + .fixedSize(horizontal: false, vertical: true) } } .padding(.vertical, 12) diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift index c544d8724c..b1e4d36eda 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift @@ -450,7 +450,7 @@ struct SingleOperatorUsageConditionsView: View { Group { viewHeader() Text("Conditions are already accepted for following operator(s): **\(operatorsWithConditionsAccepted.map { $0.legalName_ }.joined(separator: ", "))**.") - Text("Same conditions will apply to operator **\(userServers[operatorIndex].operator_.legalName_)**.") + Text("The same conditions will apply to operator **\(userServers[operatorIndex].operator_.legalName_)**.") conditionsAppliedToOtherOperatorsText() usageConditionsNavLinkButton() diff --git a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift index c3dce183bb..7cd86ef1ef 100644 --- a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift @@ -197,7 +197,7 @@ struct UserProfilesView: View { action() } else { authenticate( - reason: NSLocalizedString("Change user profiles", comment: "authentication reason") + reason: NSLocalizedString("Change chat profiles", comment: "authentication reason") ) { laResult in switch laResult { case .success, .unavailable: diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 0e8e30d8aa..901bee26dd 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -1288,6 +1288,10 @@ Промени No comment provided by engineer. + + Change chat profiles + authentication reason + Change database passphrase? Промяна на паролата на базата данни? @@ -1334,10 +1338,6 @@ authentication reason set passcode view - - Change user profiles - authentication reason - Chat No comment provided by engineer. @@ -5344,10 +5344,6 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. - - Push Notifications - No comment provided by engineer. - Push notifications Push известия @@ -5752,14 +5748,6 @@ Enable in *Network & servers* settings. По-безопасни групи No comment provided by engineer. - - Same conditions will apply to operator **%@**. - No comment provided by engineer. - - - Same conditions will apply to operator(s): **%@**. - No comment provided by engineer. - Save Запази @@ -6850,6 +6838,14 @@ It can happen because of some bug or when the connection is compromised.Профилът се споделя само с вашите контакти. No comment provided by engineer. + + The same conditions will apply to operator **%@**. + No comment provided by engineer. + + + The same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + The second preset operator in the app! No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index 10f6355692..fd8958f5f8 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -1247,6 +1247,10 @@ Změnit No comment provided by engineer. + + Change chat profiles + authentication reason + Change database passphrase? Změnit přístupovou frázi databáze? @@ -1293,10 +1297,6 @@ authentication reason set passcode view - - Change user profiles - authentication reason - Chat No comment provided by engineer. @@ -5166,10 +5166,6 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. - - Push Notifications - No comment provided by engineer. - Push notifications Nabízená oznámení @@ -5562,14 +5558,6 @@ Enable in *Network & servers* settings. Safer groups No comment provided by engineer. - - Same conditions will apply to operator **%@**. - No comment provided by engineer. - - - Same conditions will apply to operator(s): **%@**. - No comment provided by engineer. - Save Uložit @@ -6638,6 +6626,14 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Profil je sdílen pouze s vašimi kontakty. No comment provided by engineer. + + The same conditions will apply to operator **%@**. + No comment provided by engineer. + + + The same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + The second preset operator in the app! No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 35e11b4861..77ddbfb69b 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -1341,6 +1341,11 @@ Ändern No comment provided by engineer. + + Change chat profiles + Chat-Profile wechseln + authentication reason + Change database passphrase? Datenbank-Passwort ändern? @@ -1387,11 +1392,6 @@ authentication reason set passcode view - - Change user profiles - Chat-Profile wechseln - authentication reason - Chat No comment provided by engineer. @@ -5590,11 +5590,6 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Der Proxy benötigt ein Passwort No comment provided by engineer. - - Push Notifications - Push-Benachrichtigungen - No comment provided by engineer. - Push notifications Push-Benachrichtigungen @@ -6021,16 +6016,6 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Sicherere Gruppen No comment provided by engineer. - - Same conditions will apply to operator **%@**. - Dieselben Nutzungsbedingungen gelten auch für den Betreiber **%@**. - No comment provided by engineer. - - - Same conditions will apply to operator(s): **%@**. - Dieselben Nutzungsbedingungen gelten auch für den/die Betreiber: **%@**. - No comment provided by engineer. - Save Speichern @@ -7191,6 +7176,16 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Das Profil wird nur mit Ihren Kontakten geteilt. No comment provided by engineer. + + The same conditions will apply to operator **%@**. + Dieselben Nutzungsbedingungen gelten auch für den Betreiber **%@**. + No comment provided by engineer. + + + The same conditions will apply to operator(s): **%@**. + Dieselben Nutzungsbedingungen gelten auch für den/die Betreiber: **%@**. + No comment provided by engineer. + The second preset operator in the app! Der zweite voreingestellte Netzwerk-Betreiber in der App! diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 1d7f3f16be..a70a22581b 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -1346,6 +1346,11 @@ Change No comment provided by engineer. + + Change chat profiles + Change chat profiles + authentication reason + Change database passphrase? Change database passphrase? @@ -1392,11 +1397,6 @@ authentication reason set passcode view - - Change user profiles - Change user profiles - authentication reason - Chat Chat @@ -5611,11 +5611,6 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. - - Push Notifications - Push Notifications - No comment provided by engineer. - Push notifications Push notifications @@ -6042,16 +6037,6 @@ Enable in *Network & servers* settings. Safer groups No comment provided by engineer. - - Same conditions will apply to operator **%@**. - Same conditions will apply to operator **%@**. - No comment provided by engineer. - - - Same conditions will apply to operator(s): **%@**. - Same conditions will apply to operator(s): **%@**. - No comment provided by engineer. - Save Save @@ -7213,6 +7198,16 @@ It can happen because of some bug or when the connection is compromised.The profile is only shared with your contacts. No comment provided by engineer. + + The same conditions will apply to operator **%@**. + The same conditions will apply to operator **%@**. + No comment provided by engineer. + + + The same conditions will apply to operator(s): **%@**. + The same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + The second preset operator in the app! The second preset operator in the app! diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 147ad6128f..32af6e9cfd 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -1341,6 +1341,11 @@ Cambiar No comment provided by engineer. + + Change chat profiles + Cambiar perfil de usuario + authentication reason + Change database passphrase? ¿Cambiar contraseña de la base de datos? @@ -1387,11 +1392,6 @@ authentication reason set passcode view - - Change user profiles - Cambiar perfil de usuario - authentication reason - Chat No comment provided by engineer. @@ -5590,14 +5590,9 @@ Actívalo en ajustes de *Servidores y Redes*. El proxy requiere contraseña No comment provided by engineer. - - Push Notifications - Notificaciones push - No comment provided by engineer. - Push notifications - Notificaciones automáticas + Notificaciones push No comment provided by engineer. @@ -6021,16 +6016,6 @@ Actívalo en ajustes de *Servidores y Redes*. Grupos más seguros No comment provided by engineer. - - Same conditions will apply to operator **%@**. - Las mismas condiciones se aplicarán al operador **%@**. - No comment provided by engineer. - - - Same conditions will apply to operator(s): **%@**. - Las mismas condiciones se aplicarán a el/los operador(es) **%@**. - No comment provided by engineer. - Save Guardar @@ -7191,6 +7176,16 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. El perfil sólo se comparte con tus contactos. No comment provided by engineer. + + The same conditions will apply to operator **%@**. + Las mismas condiciones se aplicarán al operador **%@**. + No comment provided by engineer. + + + The same conditions will apply to operator(s): **%@**. + Las mismas condiciones se aplicarán a el/los operador(es) **%@**. + No comment provided by engineer. + The second preset operator in the app! El segundo operador predefinido! diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index 39277bbcce..bbd8a338bc 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -1240,6 +1240,10 @@ Muuta No comment provided by engineer. + + Change chat profiles + authentication reason + Change database passphrase? Muutetaanko tietokannan tunnuslause? @@ -1286,10 +1290,6 @@ authentication reason set passcode view - - Change user profiles - authentication reason - Chat No comment provided by engineer. @@ -5154,10 +5154,6 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. - - Push Notifications - No comment provided by engineer. - Push notifications Push-ilmoitukset @@ -5550,14 +5546,6 @@ Enable in *Network & servers* settings. Safer groups No comment provided by engineer. - - Same conditions will apply to operator **%@**. - No comment provided by engineer. - - - Same conditions will apply to operator(s): **%@**. - No comment provided by engineer. - Save Tallenna @@ -6624,6 +6612,14 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Profiili jaetaan vain kontaktiesi kanssa. No comment provided by engineer. + + The same conditions will apply to operator **%@**. + No comment provided by engineer. + + + The same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + The second preset operator in the app! No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index c2030ab657..9c44fe91e4 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -1329,6 +1329,10 @@ Changer No comment provided by engineer. + + Change chat profiles + authentication reason + Change database passphrase? Changer la phrase secrète de la base de données ? @@ -1375,10 +1379,6 @@ authentication reason set passcode view - - Change user profiles - authentication reason - Chat No comment provided by engineer. @@ -5530,10 +5530,6 @@ Activez-le dans les paramètres *Réseau et serveurs*. Le proxy est protégé par un mot de passe No comment provided by engineer. - - Push Notifications - No comment provided by engineer. - Push notifications Notifications push @@ -5958,14 +5954,6 @@ Activez-le dans les paramètres *Réseau et serveurs*. Groupes plus sûrs No comment provided by engineer. - - Same conditions will apply to operator **%@**. - No comment provided by engineer. - - - Same conditions will apply to operator(s): **%@**. - No comment provided by engineer. - Save Enregistrer @@ -7113,6 +7101,14 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Le profil n'est partagé qu'avec vos contacts. No comment provided by engineer. + + The same conditions will apply to operator **%@**. + No comment provided by engineer. + + + The same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + The second preset operator in the app! No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 437d97274d..ffc162633f 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -637,6 +637,7 @@ Add friends + Barátok hozzáadása No comment provided by engineer. @@ -656,6 +657,7 @@ Add team members + Csapattagok hozzáadása No comment provided by engineer. @@ -670,6 +672,7 @@ Add your team members to the conversations. + Adja hozzá csapattagjait a beszélgetésekhez. No comment provided by engineer. @@ -1209,7 +1212,7 @@ Blur media - Média elhomályosítása + Médiatartalom elhomályosítása No comment provided by engineer. @@ -1244,10 +1247,12 @@ Business address + Üzleti cím No comment provided by engineer. Business chats + Üzleti csevegések No comment provided by engineer. @@ -1341,6 +1346,11 @@ Változtatás No comment provided by engineer. + + Change chat profiles + Felhasználói profilok megváltoztatása + authentication reason + Change database passphrase? Adatbázis-jelmondat megváltoztatása? @@ -1387,21 +1397,19 @@ authentication reason set passcode view - - Change user profiles - Felhasználói profilok megváltoztatása - authentication reason - Chat + Csevegés No comment provided by engineer. Chat already exists + A csevegés már létezik No comment provided by engineer. Chat already exists! + A csevegés már létezik! No comment provided by engineer. @@ -1481,10 +1489,12 @@ Chat will be deleted for all members - this cannot be undone! + A csevegés minden tag számára törlésre kerül - ezt a műveletet nem lehet visszavonni! No comment provided by engineer. Chat will be deleted for you - this cannot be undone! + A csevegés törlésre kerül az Ön számára - ezt a műveletet nem lehet visszavonni! No comment provided by engineer. @@ -2243,6 +2253,7 @@ Ez az Ön egyszer használható meghívó-hivatkozása! Delete chat + Csevegés törlése No comment provided by engineer. @@ -2257,6 +2268,7 @@ Ez az Ön egyszer használható meghívó-hivatkozása! Delete chat? + Csevegés törlése? No comment provided by engineer. @@ -2526,6 +2538,7 @@ Ez az Ön egyszer használható meghívó-hivatkozása! Direct messages between members are prohibited in this chat. + A tagok közötti közvetlen üzenetek le vannak tiltva ebben a csevegésben. No comment provided by engineer. @@ -4101,6 +4114,7 @@ További fejlesztések hamarosan! Invite to chat + Meghívás a csevegésbe No comment provided by engineer. @@ -4263,10 +4277,12 @@ Ez az Ön hivatkozása a(z) %@ nevű csoporthoz! Leave chat + Csevegés elhagyása No comment provided by engineer. Leave chat? + Csevegés elhagyása? No comment provided by engineer. @@ -4401,6 +4417,7 @@ Ez az Ön hivatkozása a(z) %@ nevű csoporthoz! Member role will be changed to "%@". All chat members will be notified. + A tag szerepeköre meg fog változni a következőre: "%@". A csevegés tagjai értesítést fognak kapni. No comment provided by engineer. @@ -4415,6 +4432,7 @@ Ez az Ön hivatkozása a(z) %@ nevű csoporthoz! Member will be removed from chat - this cannot be undone! + A tag el lesz távolítva a csevegésből - ezt a műveletet nem lehet visszavonni! No comment provided by engineer. @@ -5032,6 +5050,7 @@ VPN engedélyezése szükséges. Only chat owners can change preferences. + Csak a csevegés tulajdonosai módosíthatják a beállításokat. No comment provided by engineer. @@ -5166,6 +5185,7 @@ VPN engedélyezése szükséges. Or import archive file + Vagy archívumfájl importálása No comment provided by engineer. @@ -5431,6 +5451,7 @@ Hiba: %@ Privacy for your customers. + Az Ön ügyfeleinek adatvédelme. No comment provided by engineer. @@ -5590,11 +5611,6 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. A proxy jelszót igényel No comment provided by engineer. - - Push Notifications - Push értesítések - No comment provided by engineer. - Push notifications Push-értesítések @@ -6021,16 +6037,6 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Biztonságosabb csoportok No comment provided by engineer. - - Same conditions will apply to operator **%@**. - Ugyanezek a feltételek vonatkoznak a következő üzemeltetőre is: **%@**. - No comment provided by engineer. - - - Same conditions will apply to operator(s): **%@**. - Ugyanezek a feltételek lesznek elfogadva a következő üzemeltető(k)re is: **%@**. - No comment provided by engineer. - Save Mentés @@ -6119,7 +6125,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Saved from - Mentve innen: + Elmentve innen: No comment provided by engineer. @@ -7017,6 +7023,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Tap Create SimpleX address in the menu to create it later. + Koppintson a SimpleX-cím létrehozása menüpontra a későbbi létrehozáshoz. No comment provided by engineer. @@ -7191,6 +7198,16 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. A profilja csak az ismerőseivel kerül megosztásra. No comment provided by engineer. + + The same conditions will apply to operator **%@**. + Ugyanezek a feltételek vonatkoznak a következő üzemeltetőre is: **%@**. + No comment provided by engineer. + + + The same conditions will apply to operator(s): **%@**. + Ugyanezek a feltételek lesznek elfogadva a következő üzemeltető(k)re is: **%@**. + No comment provided by engineer. + The second preset operator in the app! A második előre beállított üzemeltető az alkalmazásban! @@ -8057,6 +8074,7 @@ A kapcsolódáshoz kérje meg az ismerősét, hogy hozzon létre egy másik kapc You are already connected with %@. + Ön már kapcsolódva van vele: %@. No comment provided by engineer. @@ -8335,6 +8353,7 @@ Kapcsolatkérés megismétlése? You will stop receiving messages from this chat. Chat history will be preserved. + Ön nem fog több üzenetet kapni ebből a csevegésből, de a csevegés előzményei megmaradnak. No comment provided by engineer. @@ -9211,7 +9230,7 @@ Kapcsolatkérés megismétlése? saved from %@ - mentve innen: %@ + elmentve innen: %@ No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index f20b0515db..0b578e6a25 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -637,6 +637,7 @@ Add friends + Aggiungi amici No comment provided by engineer. @@ -656,6 +657,7 @@ Add team members + Aggiungi membri del team No comment provided by engineer. @@ -670,6 +672,7 @@ Add your team members to the conversations. + Aggiungi i membri del tuo team alle conversazioni. No comment provided by engineer. @@ -1244,10 +1247,12 @@ Business address + Indirizzo di lavoro No comment provided by engineer. Business chats + Chat di lavoro No comment provided by engineer. @@ -1341,6 +1346,11 @@ Cambia No comment provided by engineer. + + Change chat profiles + Modifica profili utente + authentication reason + Change database passphrase? Cambiare password del database? @@ -1387,21 +1397,19 @@ authentication reason set passcode view - - Change user profiles - Modifica profili utente - authentication reason - Chat + Chat No comment provided by engineer. Chat already exists + La chat esiste già No comment provided by engineer. Chat already exists! + La chat esiste già! No comment provided by engineer. @@ -1481,10 +1489,12 @@ Chat will be deleted for all members - this cannot be undone! + La chat verrà eliminata per tutti i membri, non è reversibile! No comment provided by engineer. Chat will be deleted for you - this cannot be undone! + La chat verrà eliminata solo per te, non è reversibile! No comment provided by engineer. @@ -2243,6 +2253,7 @@ Questo è il tuo link una tantum! Delete chat + Elimina chat No comment provided by engineer. @@ -2257,6 +2268,7 @@ Questo è il tuo link una tantum! Delete chat? + Eliminare la chat? No comment provided by engineer. @@ -2526,6 +2538,7 @@ Questo è il tuo link una tantum! Direct messages between members are prohibited in this chat. + I messaggi diretti tra i membri sono vietati in questa chat. No comment provided by engineer. @@ -4101,6 +4114,7 @@ Altri miglioramenti sono in arrivo! Invite to chat + Invita in chat No comment provided by engineer. @@ -4263,10 +4277,12 @@ Questo è il tuo link per il gruppo %@! Leave chat + Esci dalla chat No comment provided by engineer. Leave chat? + Uscire dalla chat? No comment provided by engineer. @@ -4401,6 +4417,7 @@ Questo è il tuo link per il gruppo %@! Member role will be changed to "%@". All chat members will be notified. + Il ruolo del membro verrà cambiato in "%@". Verranno notificati tutti i membri della chat. No comment provided by engineer. @@ -4415,6 +4432,7 @@ Questo è il tuo link per il gruppo %@! Member will be removed from chat - this cannot be undone! + Il membro verrà rimosso dalla chat, non è reversibile! No comment provided by engineer. @@ -5032,6 +5050,7 @@ Richiede l'attivazione della VPN. Only chat owners can change preferences. + Solo i proprietari della chat possono modificarne le preferenze. No comment provided by engineer. @@ -5166,6 +5185,7 @@ Richiede l'attivazione della VPN. Or import archive file + O importa file archivio No comment provided by engineer. @@ -5431,6 +5451,7 @@ Errore: %@ Privacy for your customers. + Privacy per i tuoi clienti. No comment provided by engineer. @@ -5590,11 +5611,6 @@ Attivalo nelle impostazioni *Rete e server*. Il proxy richiede una password No comment provided by engineer. - - Push Notifications - Notifiche push - No comment provided by engineer. - Push notifications Notifiche push @@ -6021,16 +6037,6 @@ Attivalo nelle impostazioni *Rete e server*. Gruppi più sicuri No comment provided by engineer. - - Same conditions will apply to operator **%@**. - Le stesse condizioni si applicheranno all'operatore **%@**. - No comment provided by engineer. - - - Same conditions will apply to operator(s): **%@**. - Le stesse condizioni si applicheranno agli operatori **%@**. - No comment provided by engineer. - Save Salva @@ -7017,6 +7023,7 @@ Attivalo nelle impostazioni *Rete e server*. Tap Create SimpleX address in the menu to create it later. + Tocca "Crea indirizzo SimpleX" nel menu per crearlo più tardi. No comment provided by engineer. @@ -7191,6 +7198,16 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Il profilo è condiviso solo con i tuoi contatti. No comment provided by engineer. + + The same conditions will apply to operator **%@**. + Le stesse condizioni si applicheranno all'operatore **%@**. + No comment provided by engineer. + + + The same conditions will apply to operator(s): **%@**. + Le stesse condizioni si applicheranno agli operatori **%@**. + No comment provided by engineer. + The second preset operator in the app! Il secondo operatore preimpostato nell'app! @@ -8057,6 +8074,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e You are already connected with %@. + Sei già connesso/a con %@. No comment provided by engineer. @@ -8335,6 +8353,7 @@ Ripetere la richiesta di connessione? You will stop receiving messages from this chat. Chat history will be preserved. + Non riceverai più messaggi da questa chat. La cronologia della chat verrà conservata. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 288276124c..87058e0bdc 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -1264,6 +1264,10 @@ 変更 No comment provided by engineer. + + Change chat profiles + authentication reason + Change database passphrase? データベースのパスフレーズを更新しますか? @@ -1310,10 +1314,6 @@ authentication reason set passcode view - - Change user profiles - authentication reason - Chat No comment provided by engineer. @@ -5204,10 +5204,6 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. - - Push Notifications - No comment provided by engineer. - Push notifications プッシュ通知 @@ -5599,14 +5595,6 @@ Enable in *Network & servers* settings. Safer groups No comment provided by engineer. - - Same conditions will apply to operator **%@**. - No comment provided by engineer. - - - Same conditions will apply to operator(s): **%@**. - No comment provided by engineer. - Save 保存 @@ -6667,6 +6655,14 @@ It can happen because of some bug or when the connection is compromised.プロフィールは連絡先にしか共有されません。 No comment provided by engineer. + + The same conditions will apply to operator **%@**. + No comment provided by engineer. + + + The same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + The second preset operator in the app! No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 6bf4808025..ce3adc3c41 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -637,6 +637,7 @@ Add friends + Vrienden toevoegen No comment provided by engineer. @@ -656,6 +657,7 @@ Add team members + Teamleden toevoegen No comment provided by engineer. @@ -670,6 +672,7 @@ Add your team members to the conversations. + Voeg uw teamleden toe aan de gesprekken. No comment provided by engineer. @@ -829,7 +832,7 @@ Allow irreversible message deletion only if your contact allows it to you. (24 hours) - Sta het onomkeerbaar verwijderen van berichten alleen toe als uw contact dit toestaat. (24 uur) + Sta het definitief verwijderen van berichten alleen toe als uw contact dit toestaat. (24 uur) No comment provided by engineer. @@ -859,7 +862,7 @@ Allow to irreversibly delete sent messages. (24 hours) - Sta toe om verzonden berichten onomkeerbaar te verwijderen. (24 uur) + Sta toe om verzonden berichten definitief te verwijderen. (24 uur) No comment provided by engineer. @@ -899,7 +902,7 @@ Allow your contacts to irreversibly delete sent messages. (24 hours) - Laat uw contacten verzonden berichten onomkeerbaar verwijderen. (24 uur) + Laat uw contacten verzonden berichten definitief verwijderen. (24 uur) No comment provided by engineer. @@ -1054,7 +1057,7 @@ Audio/video calls are prohibited. - Audio/video gesprekken zijn verboden. + Audio/video gesprekken zijn niet toegestaan. No comment provided by engineer. @@ -1244,10 +1247,12 @@ Business address + Zakelijk adres No comment provided by engineer. Business chats + Zakelijke chats No comment provided by engineer. @@ -1267,7 +1272,7 @@ Calls prohibited! - Bellen verboden! + Bellen niet toegestaan! No comment provided by engineer. @@ -1341,6 +1346,11 @@ Veranderen No comment provided by engineer. + + Change chat profiles + Gebruikersprofielen wijzigen + authentication reason + Change database passphrase? Wachtwoord database wijzigen? @@ -1387,21 +1397,19 @@ authentication reason set passcode view - - Change user profiles - Gebruikersprofielen wijzigen - authentication reason - Chat + Chat No comment provided by engineer. Chat already exists + Chat bestaat al No comment provided by engineer. Chat already exists! + Chat bestaat al! No comment provided by engineer. @@ -1481,10 +1489,12 @@ Chat will be deleted for all members - this cannot be undone! + De chat wordt voor alle leden verwijderd - dit kan niet ongedaan worden gemaakt! No comment provided by engineer. Chat will be deleted for you - this cannot be undone! + De chat wordt voor je verwijderd - dit kan niet ongedaan worden gemaakt! No comment provided by engineer. @@ -2243,6 +2253,7 @@ Dit is uw eigen eenmalige link! Delete chat + Chat verwijderen No comment provided by engineer. @@ -2257,6 +2268,7 @@ Dit is uw eigen eenmalige link! Delete chat? + Chat verwijderen? No comment provided by engineer. @@ -2526,11 +2538,12 @@ Dit is uw eigen eenmalige link! Direct messages between members are prohibited in this chat. + Directe berichten tussen leden zijn in deze chat niet toegestaan. No comment provided by engineer. Direct messages between members are prohibited. - Directe berichten tussen leden zijn verboden in deze groep. + Directe berichten tussen leden zijn niet toegestaan. No comment provided by engineer. @@ -2565,12 +2578,12 @@ Dit is uw eigen eenmalige link! Disappearing messages are prohibited in this chat. - Verdwijnende berichten zijn verboden in dit gesprek. + Verdwijnende berichten zijn niet toegestaan in dit gesprek. No comment provided by engineer. Disappearing messages are prohibited. - Verdwijnende berichten zijn verboden in deze groep. + Verdwijnende berichten zijn niet toegestaan. No comment provided by engineer. @@ -3428,7 +3441,7 @@ Dit is uw eigen eenmalige link! Files and media are prohibited. - Bestanden en media zijn verboden in deze groep. + Bestanden en media zijn niet toegestaan. No comment provided by engineer. @@ -3438,7 +3451,7 @@ Dit is uw eigen eenmalige link! Files and media prohibited! - Bestanden en media verboden! + Bestanden en media niet toegestaan! No comment provided by engineer. @@ -3842,7 +3855,7 @@ Fout: %2$@ If you enter this passcode when opening the app, all app data will be irreversibly removed! - Als u deze toegangscode invoert bij het openen van de app, worden alle app-gegevens onomkeerbaar verwijderd! + Als u deze toegangscode invoert bij het openen van de app, worden alle app-gegevens definitief verwijderd! No comment provided by engineer. @@ -4101,6 +4114,7 @@ Binnenkort meer verbeteringen! Invite to chat + Uitnodigen voor een chat No comment provided by engineer. @@ -4115,12 +4129,12 @@ Binnenkort meer verbeteringen! Irreversible message deletion is prohibited in this chat. - Het onomkeerbaar verwijderen van berichten is verboden in dit gesprek. + Het definitief verwijderen van berichten is niet toegestaan in dit gesprek. No comment provided by engineer. Irreversible message deletion is prohibited. - Het onomkeerbaar verwijderen van berichten is verboden in deze groep. + Het definitief verwijderen van berichten is verbHet definitief verwijderen van berichten is niet toegestaan.. No comment provided by engineer. @@ -4263,10 +4277,12 @@ Dit is jouw link voor groep %@! Leave chat + Chat verlaten No comment provided by engineer. Leave chat? + Chat verlaten? No comment provided by engineer. @@ -4401,6 +4417,7 @@ Dit is jouw link voor groep %@! Member role will be changed to "%@". All chat members will be notified. + De rol van het lid wordt gewijzigd naar "%@". Alle chatleden worden op de hoogte gebracht. No comment provided by engineer. @@ -4415,6 +4432,7 @@ Dit is jouw link voor groep %@! Member will be removed from chat - this cannot be undone! + Lid wordt verwijderd uit de chat - dit kan niet ongedaan worden gemaakt! No comment provided by engineer. @@ -4504,12 +4522,12 @@ Dit is jouw link voor groep %@! Message reactions are prohibited in this chat. - Reacties op berichten zijn verboden in deze chat. + Reacties op berichten zijn niet toegestaan in deze chat. No comment provided by engineer. Message reactions are prohibited. - Reacties op berichten zijn verboden in deze groep. + Reacties op berichten zijn niet toegestaan. No comment provided by engineer. @@ -5032,6 +5050,7 @@ Vereist het inschakelen van VPN. Only chat owners can change preferences. + Alleen chateigenaren kunnen voorkeuren wijzigen. No comment provided by engineer. @@ -5066,7 +5085,7 @@ Vereist het inschakelen van VPN. Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours) - Alleen jij kunt berichten onomkeerbaar verwijderen (je contact kan ze markeren voor verwijdering). (24 uur) + Alleen jij kunt berichten definitief verwijderen (je contact kan ze markeren voor verwijdering). (24 uur) No comment provided by engineer. @@ -5166,6 +5185,7 @@ Vereist het inschakelen van VPN. Or import archive file + Of importeer archiefbestand No comment provided by engineer. @@ -5431,6 +5451,7 @@ Fout: %@ Privacy for your customers. + Privacy voor uw klanten. No comment provided by engineer. @@ -5505,7 +5526,7 @@ Fout: %@ Prohibit irreversible message deletion. - Verbied het onomkeerbaar verwijderen van berichten. + Verbied het definitief verwijderen van berichten. No comment provided by engineer. @@ -5590,11 +5611,6 @@ Schakel dit in in *Netwerk en servers*-instellingen. Proxy vereist wachtwoord No comment provided by engineer. - - Push Notifications - Pushmeldingen - No comment provided by engineer. - Push notifications Push meldingen @@ -6021,16 +6037,6 @@ Schakel dit in in *Netwerk en servers*-instellingen. Veiligere groepen No comment provided by engineer. - - Same conditions will apply to operator **%@**. - Dezelfde voorwaarden gelden voor operator **%@**. - No comment provided by engineer. - - - Same conditions will apply to operator(s): **%@**. - Dezelfde voorwaarden gelden voor operator(s): **%@**. - No comment provided by engineer. - Save Opslaan @@ -6765,7 +6771,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. SimpleX links are prohibited. - SimpleX-links zijn in deze groep verboden. + SimpleX-links zijn niet toegestaan. No comment provided by engineer. @@ -7017,6 +7023,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Tap Create SimpleX address in the menu to create it later. + Tik op SimpleX-adres maken in het menu om het later te maken. No comment provided by engineer. @@ -7191,6 +7198,16 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. Het profiel wordt alleen gedeeld met uw contacten. No comment provided by engineer. + + The same conditions will apply to operator **%@**. + Dezelfde voorwaarden gelden voor operator **%@**. + No comment provided by engineer. + + + The same conditions will apply to operator(s): **%@**. + Dezelfde voorwaarden gelden voor operator(s): **%@**. + No comment provided by engineer. + The second preset operator in the app! De tweede vooraf ingestelde operator in de app! @@ -7258,7 +7275,7 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. - Deze actie kan niet ongedaan worden gemaakt. Uw profiel, contacten, berichten en bestanden gaan onomkeerbaar verloren. + Deze actie kan niet ongedaan worden gemaakt. Uw profiel, contacten, berichten en bestanden gaan definitief verloren. No comment provided by engineer. @@ -7857,12 +7874,12 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Voice messages are prohibited in this chat. - Spraak berichten zijn verboden in deze chat. + Spraak berichten zijn niet toegestaan in dit gesprek. No comment provided by engineer. Voice messages are prohibited. - Spraak berichten zijn verboden in deze groep. + Spraak berichten zijn niet toegestaan. No comment provided by engineer. @@ -7872,7 +7889,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Voice messages prohibited! - Spraak berichten verboden! + Spraak berichten niet toegestaan! No comment provided by engineer. @@ -8057,6 +8074,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak You are already connected with %@. + U bent al verbonden met %@. No comment provided by engineer. @@ -8335,6 +8353,7 @@ Verbindingsverzoek herhalen? You will stop receiving messages from this chat. Chat history will be preserved. + U ontvangt geen berichten meer van deze chat. De chatgeschiedenis blijft bewaard. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index ea40b78582..e99439f3ae 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -1324,6 +1324,10 @@ Zmień No comment provided by engineer. + + Change chat profiles + authentication reason + Change database passphrase? Zmienić hasło bazy danych? @@ -1370,10 +1374,6 @@ authentication reason set passcode view - - Change user profiles - authentication reason - Chat No comment provided by engineer. @@ -5520,10 +5520,6 @@ Włącz w ustawianiach *Sieć i serwery* . Proxy wymaga hasła No comment provided by engineer. - - Push Notifications - No comment provided by engineer. - Push notifications Powiadomienia push @@ -5948,14 +5944,6 @@ Włącz w ustawianiach *Sieć i serwery* . Bezpieczniejsze grupy No comment provided by engineer. - - Same conditions will apply to operator **%@**. - No comment provided by engineer. - - - Same conditions will apply to operator(s): **%@**. - No comment provided by engineer. - Save Zapisz @@ -7100,6 +7088,14 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Profil jest udostępniany tylko Twoim kontaktom. No comment provided by engineer. + + The same conditions will apply to operator **%@**. + No comment provided by engineer. + + + The same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + The second preset operator in the app! No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 3fd5d194de..85b4aaa4ae 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -114,10 +114,12 @@ %@ server + %@ сервер No comment provided by engineer. %@ servers + %@ серверы No comment provided by engineer. @@ -382,6 +384,7 @@ **Scan / Paste link**: to connect via a link you received. + **Сканировать / Вставить ссылку**: чтобы соединится через полученную ссылку. No comment provided by engineer. @@ -492,10 +495,12 @@ 1-time link + Одноразовая ссылка No comment provided by engineer. 1-time link can be used *with one contact only* - share in person or via any messenger. + Одноразовая ссылка может быть использована *только с одним контактом* - поделитесь при встрече или через любой мессенджер. No comment provided by engineer. @@ -586,6 +591,7 @@ Accept conditions + Принять условия No comment provided by engineer. @@ -606,6 +612,7 @@ Accepted conditions + Принятые условия No comment provided by engineer. @@ -630,6 +637,7 @@ Add friends + Добавить друзей No comment provided by engineer. @@ -649,6 +657,7 @@ Add team members + Добавить сотрудников No comment provided by engineer. @@ -663,14 +672,17 @@ Add your team members to the conversations. + Добавьте сотрудников в разговор. No comment provided by engineer. Added media & file servers + Дополнительные серверы файлов и медиа No comment provided by engineer. Added message servers + Дополнительные серверы сообщений No comment provided by engineer. @@ -700,10 +712,12 @@ Address or 1-time link? + Адрес или одноразовая ссылка? No comment provided by engineer. Address settings + Настройки адреса No comment provided by engineer. @@ -1233,10 +1247,12 @@ Business address + Бизнес адрес No comment provided by engineer. Business chats + Бизнес разговоры No comment provided by engineer. @@ -1330,6 +1346,11 @@ Поменять No comment provided by engineer. + + Change chat profiles + Поменять профили + authentication reason + Change database passphrase? Поменять пароль базы данных? @@ -1376,20 +1397,19 @@ authentication reason set passcode view - - Change user profiles - authentication reason - Chat + Разговор No comment provided by engineer. Chat already exists + Разговор уже существует No comment provided by engineer. Chat already exists! + Разговор уже существует! No comment provided by engineer. @@ -1469,10 +1489,12 @@ Chat will be deleted for all members - this cannot be undone! + Разговор будет удален для всех участников - это действие нельзя отменить! No comment provided by engineer. Chat will be deleted for you - this cannot be undone! + Разговор будет удален для Вас - это действие нельзя отменить! No comment provided by engineer. @@ -1482,10 +1504,12 @@ Check messages every 20 min. + Проверять сообщения каждые 20 минут. No comment provided by engineer. Check messages when allowed. + Проверять сообщения по возможности. No comment provided by engineer. @@ -1580,38 +1604,47 @@ Conditions accepted on: %@. + Условия приняты: %@. No comment provided by engineer. Conditions are accepted for the operator(s): **%@**. + Условия приняты для оператора(ов): **%@**. No comment provided by engineer. Conditions are already accepted for following operator(s): **%@**. + Условия уже приняты для следующих оператора(ов): **%@**. No comment provided by engineer. Conditions of use + Условия использования No comment provided by engineer. Conditions will be accepted for enabled operators after 30 days. + Условия будут приняты для включенных операторов через 30 дней. No comment provided by engineer. Conditions will be accepted for operator(s): **%@**. + Условия будут приняты для оператора(ов): **%@**. No comment provided by engineer. Conditions will be accepted for the operator(s): **%@**. + Условия будут приняты для оператора(ов): **%@**. No comment provided by engineer. Conditions will be accepted on: %@. + Условия будут приняты: %@. No comment provided by engineer. Conditions will be automatically accepted for enabled operators on: %@. + Условия будут автоматически приняты для включенных операторов: %@. No comment provided by engineer. @@ -1810,6 +1843,7 @@ This is your own one-time link! Connection security + Безопасность соединения No comment provided by engineer. @@ -1929,6 +1963,7 @@ This is your own one-time link! Create 1-time link + Создать одноразовую ссылку No comment provided by engineer. @@ -2018,6 +2053,7 @@ This is your own one-time link! Current conditions text couldn't be loaded, you can review conditions via this link: + Текст условий использования не может быть показан, вы можете посмотреть их через ссылку: No comment provided by engineer. @@ -2217,6 +2253,7 @@ This is your own one-time link! Delete chat + Удалить разговор No comment provided by engineer. @@ -2231,6 +2268,7 @@ This is your own one-time link! Delete chat? + Удалить разговор? No comment provided by engineer. @@ -2395,6 +2433,7 @@ This is your own one-time link! Delivered even when Apple drops them. + Доставляются даже тогда, когда Apple их теряет. No comment provided by engineer. @@ -2499,6 +2538,7 @@ This is your own one-time link! Direct messages between members are prohibited in this chat. + Прямые сообщения между членами запрещены в этом разговоре. No comment provided by engineer. @@ -2684,6 +2724,7 @@ This is your own one-time link! E2E encrypted notifications. + E2E зашифрованные нотификации. No comment provided by engineer. @@ -2708,6 +2749,7 @@ This is your own one-time link! Enable Flux + Включить Flux No comment provided by engineer. @@ -2917,6 +2959,7 @@ This is your own one-time link! Error accepting conditions + Ошибка приема условий alert title @@ -2931,6 +2974,7 @@ This is your own one-time link! Error adding server + Ошибка добавления сервера alert title @@ -3075,6 +3119,7 @@ This is your own one-time link! Error loading servers + Ошибка загрузки серверов alert title @@ -3134,6 +3179,7 @@ This is your own one-time link! Error saving servers + Ошибка сохранения серверов alert title @@ -3208,6 +3254,7 @@ This is your own one-time link! Error updating server + Ошибка сохранения сервера alert title @@ -3257,6 +3304,7 @@ This is your own one-time link! Errors in servers configuration. + Ошибки в настройках серверов. servers error @@ -3463,6 +3511,7 @@ This is your own one-time link! For chat profile %@: + Для профиля чата %@: servers error @@ -3472,14 +3521,17 @@ This is your own one-time link! For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + Например, если Ваш контакт получает сообщения через сервер SimpleX Chat, Ваше приложение доставит их через сервер Flux. No comment provided by engineer. For private routing + Для доставки сообщений No comment provided by engineer. For social media + Для социальных сетей No comment provided by engineer. @@ -3758,10 +3810,12 @@ Error: %2$@ How it affects privacy + Как это влияет на конфиденциальность No comment provided by engineer. How it helps privacy + Как это улучшает конфиденциальность No comment provided by engineer. @@ -4059,6 +4113,7 @@ More improvements are coming soon! Invite to chat + Пригласить в разговор No comment provided by engineer. @@ -4221,10 +4276,12 @@ This is your link for group %@! Leave chat + Покинуть разговор No comment provided by engineer. Leave chat? + Покинуть разговор? No comment provided by engineer. @@ -4359,6 +4416,7 @@ This is your link for group %@! Member role will be changed to "%@". All chat members will be notified. + Роль участника будет изменена на "%@". Все участники разговора получат уведомление. No comment provided by engineer. @@ -4373,6 +4431,7 @@ This is your link for group %@! Member will be removed from chat - this cannot be undone! + Член будет удален из разговора - это действие нельзя отменить! No comment provided by engineer. @@ -4637,6 +4696,7 @@ This is your link for group %@! More reliable notifications + Более надежные уведомления No comment provided by engineer. @@ -4676,6 +4736,7 @@ This is your link for group %@! Network decentralization + Децентрализация сети No comment provided by engineer. @@ -4690,6 +4751,7 @@ This is your link for group %@! Network operator + Оператор сети No comment provided by engineer. @@ -4749,6 +4811,7 @@ This is your link for group %@! New events + Новые события notification @@ -4778,6 +4841,7 @@ This is your link for group %@! New server + Новый сервер No comment provided by engineer. @@ -4837,10 +4901,12 @@ This is your link for group %@! No media & file servers. + Нет серверов файлов и медиа. servers error No message servers. + Нет серверов сообщений. servers error @@ -4875,18 +4941,22 @@ This is your link for group %@! No servers for private message routing. + Нет серверов для доставки сообщений. servers error No servers to receive files. + Нет серверов для приема файлов. servers error No servers to receive messages. + Нет серверов для приема сообщений. servers error No servers to send files. + Нет серверов для отправки файлов. servers error @@ -4921,6 +4991,7 @@ This is your link for group %@! Notifications privacy + Конфиденциальность уведомлений No comment provided by engineer. @@ -4978,6 +5049,7 @@ Requires compatible VPN. Only chat owners can change preferences. + Только владельцы разговора могут поменять предпочтения. No comment provided by engineer. @@ -5067,6 +5139,7 @@ Requires compatible VPN. Open changes + Открыть изменения No comment provided by engineer. @@ -5081,6 +5154,7 @@ Requires compatible VPN. Open conditions + Открыть условия No comment provided by engineer. @@ -5100,14 +5174,17 @@ Requires compatible VPN. Operator + Оператор No comment provided by engineer. Operator server + Сервер оператора alert title Or import archive file + Или импортировать файл архива No comment provided by engineer. @@ -5132,6 +5209,7 @@ Requires compatible VPN. Or to share privately + Или поделиться конфиденциально No comment provided by engineer. @@ -5352,6 +5430,7 @@ Error: %@ Preset servers + Серверы по умолчанию No comment provided by engineer. @@ -5371,6 +5450,7 @@ Error: %@ Privacy for your customers. + Конфиденциальность для ваших покупателей. No comment provided by engineer. @@ -5530,10 +5610,6 @@ Enable in *Network & servers* settings. Прокси требует пароль No comment provided by engineer. - - Push Notifications - No comment provided by engineer. - Push notifications Доставка уведомлений @@ -5907,10 +5983,12 @@ Enable in *Network & servers* settings. Review conditions + Посмотреть условия No comment provided by engineer. Review later + Посмотреть позже No comment provided by engineer. @@ -5958,14 +6036,6 @@ Enable in *Network & servers* settings. Более безопасные группы No comment provided by engineer. - - Same conditions will apply to operator **%@**. - No comment provided by engineer. - - - Same conditions will apply to operator(s): **%@**. - No comment provided by engineer. - Save Сохранить @@ -6369,6 +6439,7 @@ Enable in *Network & servers* settings. Server added to operator %@. + Сервер добавлен к оператору %@. alert message @@ -6388,14 +6459,17 @@ Enable in *Network & servers* settings. Server operator changed. + Оператор серверов изменен. alert title Server operators + Операторы серверов No comment provided by engineer. Server protocol changed. + Протокол сервера изменен. alert title @@ -6526,10 +6600,12 @@ Enable in *Network & servers* settings. Share 1-time link with a friend + Поделитесь одноразовой ссылкой с другом No comment provided by engineer. Share SimpleX address on social media. + Поделитесь SimpleX адресом в социальных сетях. No comment provided by engineer. @@ -6539,6 +6615,7 @@ Enable in *Network & servers* settings. Share address publicly + Поделитесь адресом No comment provided by engineer. @@ -6663,10 +6740,12 @@ Enable in *Network & servers* settings. SimpleX address and 1-time links are safe to share via any messenger. + Адрес SimpleX и одноразовые ссылки безопасно отправлять через любой мессенджер. No comment provided by engineer. SimpleX address or 1-time link? + Адрес SimpleX или одноразовая ссылка? No comment provided by engineer. @@ -6762,6 +6841,8 @@ Enable in *Network & servers* settings. Some servers failed the test: %@ + Серверы не прошли тест: +%@ alert message @@ -6941,6 +7022,7 @@ Enable in *Network & servers* settings. Tap Create SimpleX address in the menu to create it later. + Нажмите Создать адрес SimpleX в меню, чтобы создать его позже. No comment provided by engineer. @@ -7032,6 +7114,7 @@ It can happen because of some bug or when the connection is compromised. The app protects your privacy by using different operators in each conversation. + Приложение улучшает конфиденциальность используя разных операторов в каждом разговоре. No comment provided by engineer. @@ -7051,6 +7134,7 @@ It can happen because of some bug or when the connection is compromised. The connection reached the limit of undelivered messages, your contact may be offline. + Соединение достигло предела недоставленных сообщений. Возможно, Ваш контакт не в сети. No comment provided by engineer. @@ -7113,8 +7197,19 @@ It can happen because of some bug or when the connection is compromised.Профиль отправляется только Вашим контактам. No comment provided by engineer. + + The same conditions will apply to operator **%@**. + Те же самые условия будут приняты для оператора **%@**. + No comment provided by engineer. + + + The same conditions will apply to operator(s): **%@**. + Те же самые условия будут приняты для оператора(ов): **%@**. + No comment provided by engineer. + The second preset operator in the app! + Второй оператор серверов в приложении! No comment provided by engineer. @@ -7134,6 +7229,7 @@ It can happen because of some bug or when the connection is compromised. The servers for new files of your current chat profile **%@**. + Серверы для новых файлов Вашего текущего профиля **%@**. No comment provided by engineer. @@ -7153,6 +7249,7 @@ It can happen because of some bug or when the connection is compromised. These conditions will also apply for: **%@**. + Эти условия также будут применены к: **%@**. No comment provided by engineer. @@ -7257,6 +7354,7 @@ It can happen because of some bug or when the connection is compromised. To protect against your link being replaced, you can compare contact security codes. + Чтобы защитить Вашу ссылку от замены, Вы можете сравнить код безопасности. No comment provided by engineer. @@ -7283,6 +7381,7 @@ You will be prompted to complete authentication before this feature is enabled.< To receive + Для получения No comment provided by engineer. @@ -7307,6 +7406,7 @@ You will be prompted to complete authentication before this feature is enabled.< To send + Для оправки No comment provided by engineer. @@ -7316,6 +7416,7 @@ You will be prompted to complete authentication before this feature is enabled.< To use the servers of **%@**, accept conditions of use. + Чтобы использовать серверы оператора **%@**, примите условия использования. No comment provided by engineer. @@ -7410,6 +7511,7 @@ You will be prompted to complete authentication before this feature is enabled.< Undelivered messages + Недоставленные сообщения No comment provided by engineer. @@ -7571,6 +7673,7 @@ To connect, please ask your contact to create another connection link and check Use %@ + Использовать %@ No comment provided by engineer. @@ -7600,10 +7703,12 @@ To connect, please ask your contact to create another connection link and check Use for files + Использовать для файлов No comment provided by engineer. Use for messages + Использовать для сообщений No comment provided by engineer. @@ -7648,6 +7753,7 @@ To connect, please ask your contact to create another connection link and check Use servers + Использовать серверы No comment provided by engineer. @@ -7742,6 +7848,7 @@ To connect, please ask your contact to create another connection link and check View conditions + Посмотреть условия No comment provided by engineer. @@ -7751,6 +7858,7 @@ To connect, please ask your contact to create another connection link and check View updated conditions + Посмотреть измененные условия No comment provided by engineer. @@ -7865,6 +7973,7 @@ To connect, please ask your contact to create another connection link and check When more than one operator is enabled, none of them has metadata to learn who communicates with whom. + Когда больше чем один оператор включен, ни один из них не видит метаданные, чтобы определить, кто соединен с кем. No comment provided by engineer. @@ -7964,6 +8073,7 @@ To connect, please ask your contact to create another connection link and check You are already connected with %@. + Вы уже соединены с %@. No comment provided by engineer. @@ -8030,10 +8140,12 @@ Repeat join request? You can configure operators in Network & servers settings. + Вы можете настроить операторов в настройках Сеть и серверы. No comment provided by engineer. You can configure servers via settings. + Вы можете сконфигурировать серверы через настройки. No comment provided by engineer. @@ -8078,6 +8190,7 @@ Repeat join request? You can set connection name, to remember who the link was shared with. + Вы можете установить имя соединения, чтобы запомнить кому Вы отправили ссылку. No comment provided by engineer. @@ -8239,6 +8352,7 @@ Repeat connection request? You will stop receiving messages from this chat. Chat history will be preserved. + Вы прекратите получать сообщения в этом разговоре. История будет сохранена. No comment provided by engineer. @@ -8383,6 +8497,7 @@ Repeat connection request? Your servers + Ваши серверы No comment provided by engineer. @@ -8807,6 +8922,7 @@ Repeat connection request? for better metadata privacy. + для лучшей конфиденциальности метаданных. No comment provided by engineer. @@ -9438,22 +9554,27 @@ last received msg: %2$@ %d new events + %d новых сообщений notification body From: %@ + От: %@ notification body New events + Новые события notification New messages + Новые сообщения notification New messages in %d chats + Новые сообщения в %d разговоре(ах) notification body diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index edf32b06f2..438fae5c47 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -1232,6 +1232,10 @@ เปลี่ยน No comment provided by engineer. + + Change chat profiles + authentication reason + Change database passphrase? เปลี่ยนรหัสผ่านฐานข้อมูล? @@ -1278,10 +1282,6 @@ authentication reason set passcode view - - Change user profiles - authentication reason - Chat No comment provided by engineer. @@ -5133,10 +5133,6 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. - - Push Notifications - No comment provided by engineer. - Push notifications การแจ้งเตือนแบบทันที @@ -5527,14 +5523,6 @@ Enable in *Network & servers* settings. Safer groups No comment provided by engineer. - - Same conditions will apply to operator **%@**. - No comment provided by engineer. - - - Same conditions will apply to operator(s): **%@**. - No comment provided by engineer. - Save บันทึก @@ -6598,6 +6586,14 @@ It can happen because of some bug or when the connection is compromised.โปรไฟล์นี้แชร์กับผู้ติดต่อของคุณเท่านั้น No comment provided by engineer. + + The same conditions will apply to operator **%@**. + No comment provided by engineer. + + + The same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + The second preset operator in the app! No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index 6d69920454..5919cc4d49 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -1329,6 +1329,10 @@ Değiştir No comment provided by engineer. + + Change chat profiles + authentication reason + Change database passphrase? Veritabanı parolasını değiştir? @@ -1375,10 +1379,6 @@ authentication reason set passcode view - - Change user profiles - authentication reason - Chat No comment provided by engineer. @@ -5530,10 +5530,6 @@ Enable in *Network & servers* settings. Proxy şifre gerektirir No comment provided by engineer. - - Push Notifications - No comment provided by engineer. - Push notifications Anında bildirimler @@ -5958,14 +5954,6 @@ Enable in *Network & servers* settings. Daha güvenli gruplar No comment provided by engineer. - - Same conditions will apply to operator **%@**. - No comment provided by engineer. - - - Same conditions will apply to operator(s): **%@**. - No comment provided by engineer. - Save Kaydet @@ -7113,6 +7101,14 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Profil sadece kişilerinle paylaşılacak. No comment provided by engineer. + + The same conditions will apply to operator **%@**. + No comment provided by engineer. + + + The same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + The second preset operator in the app! No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 2f1e5d96a6..72e9896872 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -1341,6 +1341,11 @@ Зміна No comment provided by engineer. + + Change chat profiles + Зміна профілів користувачів + authentication reason + Change database passphrase? Змінити пароль до бази даних? @@ -1387,11 +1392,6 @@ authentication reason set passcode view - - Change user profiles - Зміна профілів користувачів - authentication reason - Chat No comment provided by engineer. @@ -5590,14 +5590,9 @@ Enable in *Network & servers* settings. Проксі вимагає пароль No comment provided by engineer. - - Push Notifications - Push-сповіщення - No comment provided by engineer. - Push notifications - Push-повідомлення + Push-сповіщення No comment provided by engineer. @@ -6021,16 +6016,6 @@ Enable in *Network & servers* settings. Безпечніші групи No comment provided by engineer. - - Same conditions will apply to operator **%@**. - Такі ж умови діятимуть і для оператора **%@**. - No comment provided by engineer. - - - Same conditions will apply to operator(s): **%@**. - Такі ж умови будуть застосовуватися до оператора(ів): **%@**. - No comment provided by engineer. - Save Зберегти @@ -7191,6 +7176,16 @@ It can happen because of some bug or when the connection is compromised.Профіль доступний лише вашим контактам. No comment provided by engineer. + + The same conditions will apply to operator **%@**. + Такі ж умови діятимуть і для оператора **%@**. + No comment provided by engineer. + + + The same conditions will apply to operator(s): **%@**. + Такі ж умови будуть застосовуватися до оператора(ів): **%@**. + No comment provided by engineer. + The second preset operator in the app! Другий попередньо встановлений оператор у застосунку! diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index a113c75603..ede559c968 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -1316,6 +1316,10 @@ 更改 No comment provided by engineer. + + Change chat profiles + authentication reason + Change database passphrase? 更改数据库密码? @@ -1362,10 +1366,6 @@ authentication reason set passcode view - - Change user profiles - authentication reason - Chat No comment provided by engineer. @@ -5485,10 +5485,6 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. - - Push Notifications - No comment provided by engineer. - Push notifications 推送通知 @@ -5911,14 +5907,6 @@ Enable in *Network & servers* settings. 更安全的群组 No comment provided by engineer. - - Same conditions will apply to operator **%@**. - No comment provided by engineer. - - - Same conditions will apply to operator(s): **%@**. - No comment provided by engineer. - Save 保存 @@ -7055,6 +7043,14 @@ It can happen because of some bug or when the connection is compromised.该资料仅与您的联系人共享。 No comment provided by engineer. + + The same conditions will apply to operator **%@**. + No comment provided by engineer. + + + The same conditions will apply to operator(s): **%@**. + No comment provided by engineer. + The second preset operator in the app! No comment provided by engineer. diff --git a/apps/ios/SimpleX NSE/ru.lproj/Localizable.strings b/apps/ios/SimpleX NSE/ru.lproj/Localizable.strings index 5ef592ec70..6ba39ccc63 100644 --- a/apps/ios/SimpleX NSE/ru.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/ru.lproj/Localizable.strings @@ -1,7 +1,15 @@ -/* - Localizable.strings - SimpleX +/* notification body */ +"%d new events" = "%d новых сообщений"; + +/* notification body */ +"From: %@" = "От: %@"; + +/* notification */ +"New events" = "Новые события"; + +/* notification */ +"New messages" = "Новые сообщения"; + +/* notification body */ +"New messages in %d chats" = "Новые сообщения в %d разговоре(ах)"; - Created by EP on 30/07/2024. - Copyright © 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index a63ca87a99..41f6730fdc 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -1905,27 +1905,6 @@ /* No comment provided by engineer. */ "Group links" = "Групови линкове"; -/* No comment provided by engineer. */ -"Members can add message reactions." = "Членовете на групата могат да добавят реакции към съобщенията."; - -/* No comment provided by engineer. */ -"Members can irreversibly delete sent messages. (24 hours)" = "Членовете на групата могат необратимо да изтриват изпратените съобщения. (24 часа)"; - -/* No comment provided by engineer. */ -"Members can send direct messages." = "Членовете на групата могат да изпращат лични съобщения."; - -/* No comment provided by engineer. */ -"Members can send disappearing messages." = "Членовете на групата могат да изпращат изчезващи съобщения."; - -/* No comment provided by engineer. */ -"Members can send files and media." = "Членовете на групата могат да изпращат файлове и медия."; - -/* No comment provided by engineer. */ -"Members can send SimpleX links." = "Членовете на групата могат да изпращат SimpleX линкове."; - -/* No comment provided by engineer. */ -"Members can send voice messages." = "Членовете на групата могат да изпращат гласови съобщения."; - /* notification */ "Group message:" = "Групово съобщение:"; @@ -2376,6 +2355,27 @@ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Членът ще бъде премахнат от групата - това не може да бъде отменено!"; +/* No comment provided by engineer. */ +"Members can add message reactions." = "Членовете на групата могат да добавят реакции към съобщенията."; + +/* No comment provided by engineer. */ +"Members can irreversibly delete sent messages. (24 hours)" = "Членовете на групата могат необратимо да изтриват изпратените съобщения. (24 часа)"; + +/* No comment provided by engineer. */ +"Members can send direct messages." = "Членовете на групата могат да изпращат лични съобщения."; + +/* No comment provided by engineer. */ +"Members can send disappearing messages." = "Членовете на групата могат да изпращат изчезващи съобщения."; + +/* No comment provided by engineer. */ +"Members can send files and media." = "Членовете на групата могат да изпращат файлове и медия."; + +/* No comment provided by engineer. */ +"Members can send SimpleX links." = "Членовете на групата могат да изпращат SimpleX линкове."; + +/* No comment provided by engineer. */ +"Members can send voice messages." = "Членовете на групата могат да изпращат гласови съобщения."; + /* item status text */ "Message delivery error" = "Грешка при доставката на съобщението"; diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index a150c2427f..bbe754aa47 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -1544,24 +1544,6 @@ /* No comment provided by engineer. */ "Group links" = "Odkazy na skupiny"; -/* No comment provided by engineer. */ -"Members can add message reactions." = "Členové skupin mohou přidávat reakce na zprávy."; - -/* No comment provided by engineer. */ -"Members can irreversibly delete sent messages. (24 hours)" = "Členové skupiny mohou nevratně mazat odeslané zprávy. (24 hodin)"; - -/* No comment provided by engineer. */ -"Members can send direct messages." = "Členové skupiny mohou posílat přímé zprávy."; - -/* No comment provided by engineer. */ -"Members can send disappearing messages." = "Členové skupiny mohou posílat mizící zprávy."; - -/* No comment provided by engineer. */ -"Members can send files and media." = "Členové skupiny mohou posílat soubory a média."; - -/* No comment provided by engineer. */ -"Members can send voice messages." = "Členové skupiny mohou posílat hlasové zprávy."; - /* notification */ "Group message:" = "Skupinová zpráva:"; @@ -1934,6 +1916,24 @@ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Člen bude odstraněn ze skupiny - toto nelze vzít zpět!"; +/* No comment provided by engineer. */ +"Members can add message reactions." = "Členové skupin mohou přidávat reakce na zprávy."; + +/* No comment provided by engineer. */ +"Members can irreversibly delete sent messages. (24 hours)" = "Členové skupiny mohou nevratně mazat odeslané zprávy. (24 hodin)"; + +/* No comment provided by engineer. */ +"Members can send direct messages." = "Členové skupiny mohou posílat přímé zprávy."; + +/* No comment provided by engineer. */ +"Members can send disappearing messages." = "Členové skupiny mohou posílat mizící zprávy."; + +/* No comment provided by engineer. */ +"Members can send files and media." = "Členové skupiny mohou posílat soubory a média."; + +/* No comment provided by engineer. */ +"Members can send voice messages." = "Členové skupiny mohou posílat hlasové zprávy."; + /* item status text */ "Message delivery error" = "Chyba doručení zprávy"; diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 6231000330..28cc658d30 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -892,7 +892,7 @@ "Change self-destruct passcode" = "Selbstzerstörungs-Zugangscode ändern"; /* authentication reason */ -"Change user profiles" = "Chat-Profile wechseln"; +"Change chat profiles" = "Chat-Profile wechseln"; /* chat item text */ "changed address for you" = "Wechselte die Empfängeradresse von Ihnen"; @@ -2424,27 +2424,6 @@ /* No comment provided by engineer. */ "Group links" = "Gruppen-Links"; -/* No comment provided by engineer. */ -"Members can add message reactions." = "Gruppenmitglieder können eine Reaktion auf Nachrichten geben."; - -/* No comment provided by engineer. */ -"Members can irreversibly delete sent messages. (24 hours)" = "Gruppenmitglieder können gesendete Nachrichten unwiederbringlich löschen. (24 Stunden)"; - -/* No comment provided by engineer. */ -"Members can send direct messages." = "Gruppenmitglieder können Direktnachrichten versenden."; - -/* No comment provided by engineer. */ -"Members can send disappearing messages." = "Gruppenmitglieder können verschwindende Nachrichten senden."; - -/* No comment provided by engineer. */ -"Members can send files and media." = "Gruppenmitglieder können Dateien und Medien senden."; - -/* No comment provided by engineer. */ -"Members can send SimpleX links." = "Gruppenmitglieder können SimpleX-Links senden."; - -/* No comment provided by engineer. */ -"Members can send voice messages." = "Gruppenmitglieder können Sprachnachrichten versenden."; - /* notification */ "Group message:" = "Grppennachricht:"; @@ -2934,6 +2913,27 @@ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Das Mitglied wird aus der Gruppe entfernt. Dies kann nicht rückgängig gemacht werden!"; +/* No comment provided by engineer. */ +"Members can add message reactions." = "Gruppenmitglieder können eine Reaktion auf Nachrichten geben."; + +/* No comment provided by engineer. */ +"Members can irreversibly delete sent messages. (24 hours)" = "Gruppenmitglieder können gesendete Nachrichten unwiederbringlich löschen. (24 Stunden)"; + +/* No comment provided by engineer. */ +"Members can send direct messages." = "Gruppenmitglieder können Direktnachrichten versenden."; + +/* No comment provided by engineer. */ +"Members can send disappearing messages." = "Gruppenmitglieder können verschwindende Nachrichten senden."; + +/* No comment provided by engineer. */ +"Members can send files and media." = "Gruppenmitglieder können Dateien und Medien senden."; + +/* No comment provided by engineer. */ +"Members can send SimpleX links." = "Gruppenmitglieder können SimpleX-Links senden."; + +/* No comment provided by engineer. */ +"Members can send voice messages." = "Gruppenmitglieder können Sprachnachrichten versenden."; + /* No comment provided by engineer. */ "Menus" = "Menüs"; @@ -3671,9 +3671,6 @@ /* No comment provided by engineer. */ "Push notifications" = "Push-Benachrichtigungen"; -/* No comment provided by engineer. */ -"Push Notifications" = "Push-Benachrichtigungen"; - /* No comment provided by engineer. */ "Push server" = "Push-Server"; @@ -3949,10 +3946,10 @@ "Safer groups" = "Sicherere Gruppen"; /* No comment provided by engineer. */ -"Same conditions will apply to operator **%@**." = "Dieselben Nutzungsbedingungen gelten auch für den Betreiber **%@**."; +"The same conditions will apply to operator **%@**." = "Dieselben Nutzungsbedingungen gelten auch für den Betreiber **%@**."; /* No comment provided by engineer. */ -"Same conditions will apply to operator(s): **%@**." = "Dieselben Nutzungsbedingungen gelten auch für den/die Betreiber: **%@**."; +"The same conditions will apply to operator(s): **%@**." = "Dieselben Nutzungsbedingungen gelten auch für den/die Betreiber: **%@**."; /* alert button chat item action */ diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index d15e6d75ce..1ccb679069 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -892,7 +892,7 @@ "Change self-destruct passcode" = "Cambiar código autodestrucción"; /* authentication reason */ -"Change user profiles" = "Cambiar perfil de usuario"; +"Change chat profiles" = "Cambiar perfil de usuario"; /* chat item text */ "changed address for you" = "ha cambiado tu servidor de envío"; @@ -2424,27 +2424,6 @@ /* No comment provided by engineer. */ "Group links" = "Enlaces de grupo"; -/* No comment provided by engineer. */ -"Members can add message reactions." = "Los miembros pueden añadir reacciones a los mensajes."; - -/* No comment provided by engineer. */ -"Members can irreversibly delete sent messages. (24 hours)" = "Los miembros del grupo pueden eliminar mensajes de forma irreversible. (24 horas)"; - -/* No comment provided by engineer. */ -"Members can send direct messages." = "Los miembros del grupo pueden enviar mensajes directos."; - -/* No comment provided by engineer. */ -"Members can send disappearing messages." = "Los miembros del grupo pueden enviar mensajes temporales."; - -/* No comment provided by engineer. */ -"Members can send files and media." = "Los miembros del grupo pueden enviar archivos y multimedia."; - -/* No comment provided by engineer. */ -"Members can send SimpleX links." = "Los miembros del grupo pueden enviar enlaces SimpleX."; - -/* No comment provided by engineer. */ -"Members can send voice messages." = "Los miembros del grupo pueden enviar mensajes de voz."; - /* notification */ "Group message:" = "Mensaje de grupo:"; @@ -2934,6 +2913,27 @@ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "El miembro será expulsado del grupo. ¡No podrá deshacerse!"; +/* No comment provided by engineer. */ +"Members can add message reactions." = "Los miembros pueden añadir reacciones a los mensajes."; + +/* No comment provided by engineer. */ +"Members can irreversibly delete sent messages. (24 hours)" = "Los miembros del grupo pueden eliminar mensajes de forma irreversible. (24 horas)"; + +/* No comment provided by engineer. */ +"Members can send direct messages." = "Los miembros del grupo pueden enviar mensajes directos."; + +/* No comment provided by engineer. */ +"Members can send disappearing messages." = "Los miembros del grupo pueden enviar mensajes temporales."; + +/* No comment provided by engineer. */ +"Members can send files and media." = "Los miembros del grupo pueden enviar archivos y multimedia."; + +/* No comment provided by engineer. */ +"Members can send SimpleX links." = "Los miembros del grupo pueden enviar enlaces SimpleX."; + +/* No comment provided by engineer. */ +"Members can send voice messages." = "Los miembros del grupo pueden enviar mensajes de voz."; + /* No comment provided by engineer. */ "Menus" = "Menus"; @@ -3669,10 +3669,7 @@ "Proxy requires password" = "El proxy requiere contraseña"; /* No comment provided by engineer. */ -"Push notifications" = "Notificaciones automáticas"; - -/* No comment provided by engineer. */ -"Push Notifications" = "Notificaciones push"; +"Push notifications" = "Notificaciones push"; /* No comment provided by engineer. */ "Push server" = "Servidor push"; @@ -3949,10 +3946,10 @@ "Safer groups" = "Grupos más seguros"; /* No comment provided by engineer. */ -"Same conditions will apply to operator **%@**." = "Las mismas condiciones se aplicarán al operador **%@**."; +"The same conditions will apply to operator **%@**." = "Las mismas condiciones se aplicarán al operador **%@**."; /* No comment provided by engineer. */ -"Same conditions will apply to operator(s): **%@**." = "Las mismas condiciones se aplicarán a el/los operador(es) **%@**."; +"The same conditions will apply to operator(s): **%@**." = "Las mismas condiciones se aplicarán a el/los operador(es) **%@**."; /* alert button chat item action */ diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index 8946be02b4..486c0e7650 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -1520,24 +1520,6 @@ /* No comment provided by engineer. */ "Group links" = "Ryhmälinkit"; -/* No comment provided by engineer. */ -"Members can add message reactions." = "Ryhmän jäsenet voivat lisätä viestireaktioita."; - -/* No comment provided by engineer. */ -"Members can irreversibly delete sent messages. (24 hours)" = "Ryhmän jäsenet voivat poistaa lähetetyt viestit peruuttamattomasti. (24 tuntia)"; - -/* No comment provided by engineer. */ -"Members can send direct messages." = "Ryhmän jäsenet voivat lähettää suoraviestejä."; - -/* No comment provided by engineer. */ -"Members can send disappearing messages." = "Ryhmän jäsenet voivat lähettää katoavia viestejä."; - -/* No comment provided by engineer. */ -"Members can send files and media." = "Ryhmän jäsenet voivat lähettää tiedostoja ja mediaa."; - -/* No comment provided by engineer. */ -"Members can send voice messages." = "Ryhmän jäsenet voivat lähettää ääniviestejä."; - /* notification */ "Group message:" = "Ryhmäviesti:"; @@ -1910,6 +1892,24 @@ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Jäsen poistetaan ryhmästä - tätä ei voi perua!"; +/* No comment provided by engineer. */ +"Members can add message reactions." = "Ryhmän jäsenet voivat lisätä viestireaktioita."; + +/* No comment provided by engineer. */ +"Members can irreversibly delete sent messages. (24 hours)" = "Ryhmän jäsenet voivat poistaa lähetetyt viestit peruuttamattomasti. (24 tuntia)"; + +/* No comment provided by engineer. */ +"Members can send direct messages." = "Ryhmän jäsenet voivat lähettää suoraviestejä."; + +/* No comment provided by engineer. */ +"Members can send disappearing messages." = "Ryhmän jäsenet voivat lähettää katoavia viestejä."; + +/* No comment provided by engineer. */ +"Members can send files and media." = "Ryhmän jäsenet voivat lähettää tiedostoja ja mediaa."; + +/* No comment provided by engineer. */ +"Members can send voice messages." = "Ryhmän jäsenet voivat lähettää ääniviestejä."; + /* item status text */ "Message delivery error" = "Viestin toimitusvirhe"; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index f1a8e97758..1a9e289404 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -2301,27 +2301,6 @@ /* No comment provided by engineer. */ "Group links" = "Liens de groupe"; -/* No comment provided by engineer. */ -"Members can add message reactions." = "Les membres du groupe peuvent ajouter des réactions aux messages."; - -/* No comment provided by engineer. */ -"Members can irreversibly delete sent messages. (24 hours)" = "Les membres du groupe peuvent supprimer de manière irréversible les messages envoyés. (24 heures)"; - -/* No comment provided by engineer. */ -"Members can send direct messages." = "Les membres du groupe peuvent envoyer des messages directs."; - -/* No comment provided by engineer. */ -"Members can send disappearing messages." = "Les membres du groupes peuvent envoyer des messages éphémères."; - -/* No comment provided by engineer. */ -"Members can send files and media." = "Les membres du groupe peuvent envoyer des fichiers et des médias."; - -/* No comment provided by engineer. */ -"Members can send SimpleX links." = "Les membres du groupe peuvent envoyer des liens SimpleX."; - -/* No comment provided by engineer. */ -"Members can send voice messages." = "Les membres du groupe peuvent envoyer des messages vocaux."; - /* notification */ "Group message:" = "Message du groupe :"; @@ -2805,6 +2784,27 @@ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Ce membre sera retiré du groupe - impossible de revenir en arrière !"; +/* No comment provided by engineer. */ +"Members can add message reactions." = "Les membres du groupe peuvent ajouter des réactions aux messages."; + +/* No comment provided by engineer. */ +"Members can irreversibly delete sent messages. (24 hours)" = "Les membres du groupe peuvent supprimer de manière irréversible les messages envoyés. (24 heures)"; + +/* No comment provided by engineer. */ +"Members can send direct messages." = "Les membres du groupe peuvent envoyer des messages directs."; + +/* No comment provided by engineer. */ +"Members can send disappearing messages." = "Les membres du groupes peuvent envoyer des messages éphémères."; + +/* No comment provided by engineer. */ +"Members can send files and media." = "Les membres du groupe peuvent envoyer des fichiers et des médias."; + +/* No comment provided by engineer. */ +"Members can send SimpleX links." = "Les membres du groupe peuvent envoyer des liens SimpleX."; + +/* No comment provided by engineer. */ +"Members can send voice messages." = "Les membres du groupe peuvent envoyer des messages vocaux."; + /* No comment provided by engineer. */ "Menus" = "Menus"; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index c1008ad30b..4893d5a13f 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -388,6 +388,9 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Cím hozzáadása a profilhoz, hogy az ismerősei megoszthassák másokkal. A profilfrissítés elküldésre kerül az ismerősei számára."; +/* No comment provided by engineer. */ +"Add friends" = "Barátok hozzáadása"; + /* No comment provided by engineer. */ "Add profile" = "Profil hozzáadása"; @@ -397,12 +400,18 @@ /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "Kiszolgáló hozzáadása QR-kód beolvasásával."; +/* No comment provided by engineer. */ +"Add team members" = "Csapattagok hozzáadása"; + /* No comment provided by engineer. */ "Add to another device" = "Hozzáadás egy másik eszközhöz"; /* No comment provided by engineer. */ "Add welcome message" = "Üdvözlőüzenet hozzáadása"; +/* No comment provided by engineer. */ +"Add your team members to the conversations." = "Adja hozzá csapattagjait a beszélgetésekhez."; + /* No comment provided by engineer. */ "Added media & file servers" = "Hozzáadott média- és fájlkiszolgálók"; @@ -770,7 +779,7 @@ "Blur for better privacy." = "Elhomályosítás a jobb adatvédelemért."; /* No comment provided by engineer. */ -"Blur media" = "Média elhomályosítása"; +"Blur media" = "Médiatartalom elhomályosítása"; /* No comment provided by engineer. */ "bold" = "félkövér"; @@ -793,6 +802,12 @@ /* 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)!" = "Bolgár, finn, thai és ukrán – köszönet a felhasználóknak és a [Weblate-nek](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; +/* No comment provided by engineer. */ +"Business address" = "Üzleti cím"; + +/* No comment provided by engineer. */ +"Business chats" = "Üzleti csevegések"; + /* 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)." = "A csevegési profillal (alapértelmezett), vagy a [kapcsolattal] (https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BÉTA)."; @@ -892,7 +907,7 @@ "Change self-destruct passcode" = "Önmegsemmisító jelkód megváltoztatása"; /* authentication reason */ -"Change user profiles" = "Felhasználói profilok megváltoztatása"; +"Change chat profiles" = "Felhasználói profilok megváltoztatása"; /* chat item text */ "changed address for you" = "cím megváltoztatva"; @@ -909,6 +924,15 @@ /* chat item text */ "changing address…" = "cím megváltoztatása…"; +/* No comment provided by engineer. */ +"Chat" = "Csevegés"; + +/* No comment provided by engineer. */ +"Chat already exists" = "A csevegés már létezik"; + +/* No comment provided by engineer. */ +"Chat already exists!" = "A csevegés már létezik!"; + /* No comment provided by engineer. */ "Chat colors" = "Csevegés színei"; @@ -954,6 +978,12 @@ /* No comment provided by engineer. */ "Chat theme" = "Csevegés témája"; +/* No comment provided by engineer. */ +"Chat will be deleted for all members - this cannot be undone!" = "A csevegés minden tag számára törlésre kerül - ezt a műveletet nem lehet visszavonni!"; + +/* No comment provided by engineer. */ +"Chat will be deleted for you - this cannot be undone!" = "A csevegés törlésre kerül az Ön számára - ezt a műveletet nem lehet visszavonni!"; + /* No comment provided by engineer. */ "Chats" = "Csevegések"; @@ -1475,12 +1505,18 @@ /* No comment provided by engineer. */ "Delete and notify contact" = "Törlés, és az ismerős értesítése"; +/* No comment provided by engineer. */ +"Delete chat" = "Csevegés törlése"; + /* No comment provided by engineer. */ "Delete chat profile" = "Csevegési profil törlése"; /* No comment provided by engineer. */ "Delete chat profile?" = "Csevegési profil törlése?"; +/* No comment provided by engineer. */ +"Delete chat?" = "Csevegés törlése?"; + /* No comment provided by engineer. */ "Delete connection" = "Kapcsolat törlése"; @@ -1655,6 +1691,9 @@ /* chat feature */ "Direct messages" = "Közvetlen üzenetek"; +/* No comment provided by engineer. */ +"Direct messages between members are prohibited in this chat." = "A tagok közötti közvetlen üzenetek le vannak tiltva ebben a csevegésben."; + /* No comment provided by engineer. */ "Direct messages between members are prohibited." = "A közvetlen üzenetek küldése a tagok között le van tiltva ebben a csoportban."; @@ -2424,27 +2463,6 @@ /* No comment provided by engineer. */ "Group links" = "Csoporthivatkozások"; -/* No comment provided by engineer. */ -"Members can add message reactions." = "Csoporttagok üzenetreakciókat adhatnak hozzá."; - -/* No comment provided by engineer. */ -"Members can irreversibly delete sent messages. (24 hours)" = "A csoport tagjai véglegesen törölhetik az elküldött üzeneteiket. (24 óra)"; - -/* No comment provided by engineer. */ -"Members can send direct messages." = "A csoport tagjai küldhetnek egymásnak közvetlen üzeneteket."; - -/* No comment provided by engineer. */ -"Members can send disappearing messages." = "A csoport tagjai küldhetnek eltűnő üzeneteket."; - -/* No comment provided by engineer. */ -"Members can send files and media." = "A csoport tagjai küldhetnek fájlokat és médiatartalmakat."; - -/* No comment provided by engineer. */ -"Members can send SimpleX links." = "A csoport tagjai küldhetnek SimpleX-hivatkozásokat."; - -/* No comment provided by engineer. */ -"Members can send voice messages." = "A csoport tagjai küldhetnek hangüzeneteket."; - /* notification */ "Group message:" = "Csoport üzenet:"; @@ -2715,6 +2733,9 @@ /* No comment provided by engineer. */ "Invite members" = "Tagok meghívása"; +/* No comment provided by engineer. */ +"Invite to chat" = "Meghívás a csevegésbe"; + /* No comment provided by engineer. */ "Invite to group" = "Meghívás a csoportba"; @@ -2829,6 +2850,12 @@ /* swipe action */ "Leave" = "Elhagyás"; +/* No comment provided by engineer. */ +"Leave chat" = "Csevegés elhagyása"; + +/* No comment provided by engineer. */ +"Leave chat?" = "Csevegés elhagyása?"; + /* No comment provided by engineer. */ "Leave group" = "Csoport elhagyása"; @@ -2925,15 +2952,42 @@ /* item status text */ "Member inactive" = "Inaktív tag"; +/* No comment provided by engineer. */ +"Member role will be changed to \"%@\". All chat members will be notified." = "A tag szerepeköre meg fog változni a következőre: \"%@\". A csevegés tagjai értesítést fognak kapni."; + /* No comment provided by engineer. */ "Member role will be changed to \"%@\". All group members will be notified." = "A tag szerepköre meg fog változni erre: „%@”. A csoportban az összes tag értesítve lesz."; /* No comment provided by engineer. */ "Member role will be changed to \"%@\". The member will receive a new invitation." = "A tag szerepköre meg fog változni erre: „%@”. A tag új meghívást fog kapni."; +/* No comment provided by engineer. */ +"Member will be removed from chat - this cannot be undone!" = "A tag el lesz távolítva a csevegésből - ezt a műveletet nem lehet visszavonni!"; + /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "A tag eltávolítása a csoportból - ez a művelet nem vonható vissza!"; +/* No comment provided by engineer. */ +"Members can add message reactions." = "Csoporttagok üzenetreakciókat adhatnak hozzá."; + +/* No comment provided by engineer. */ +"Members can irreversibly delete sent messages. (24 hours)" = "A csoport tagjai véglegesen törölhetik az elküldött üzeneteiket. (24 óra)"; + +/* No comment provided by engineer. */ +"Members can send direct messages." = "A csoport tagjai küldhetnek egymásnak közvetlen üzeneteket."; + +/* No comment provided by engineer. */ +"Members can send disappearing messages." = "A csoport tagjai küldhetnek eltűnő üzeneteket."; + +/* No comment provided by engineer. */ +"Members can send files and media." = "A csoport tagjai küldhetnek fájlokat és médiatartalmakat."; + +/* No comment provided by engineer. */ +"Members can send SimpleX links." = "A csoport tagjai küldhetnek SimpleX-hivatkozásokat."; + +/* No comment provided by engineer. */ +"Members can send voice messages." = "A csoport tagjai küldhetnek hangüzeneteket."; + /* No comment provided by engineer. */ "Menus" = "Menük"; @@ -3329,6 +3383,9 @@ /* No comment provided by engineer. */ "Onion hosts will not be used." = "Onion-kiszolgálók nem lesznek használva."; +/* No comment provided by engineer. */ +"Only chat owners can change preferences." = "Csak a csevegés tulajdonosai módosíthatják a beállításokat."; + /* No comment provided by engineer. */ "Only client devices store user profiles, contacts, groups, and messages." = "Csak az eszközök alkalmazásai tárolják a felhasználó-profilokat, névjegyeket, csoportokat és a **2 rétegű végpontok közötti titkosítással** küldött üzeneteket."; @@ -3407,6 +3464,9 @@ /* alert title */ "Operator server" = "Kiszolgáló üzemeltető"; +/* No comment provided by engineer. */ +"Or import archive file" = "Vagy archívumfájl importálása"; + /* No comment provided by engineer. */ "Or paste archive link" = "Vagy az archívum hivatkozásának beillesztése"; @@ -3575,6 +3635,9 @@ /* No comment provided by engineer. */ "Privacy & security" = "Adatvédelem és biztonság"; +/* No comment provided by engineer. */ +"Privacy for your customers." = "Az Ön ügyfeleinek adatvédelme."; + /* No comment provided by engineer. */ "Privacy redefined" = "Adatvédelem újraértelmezve"; @@ -3671,9 +3734,6 @@ /* No comment provided by engineer. */ "Push notifications" = "Push-értesítések"; -/* No comment provided by engineer. */ -"Push Notifications" = "Push értesítések"; - /* No comment provided by engineer. */ "Push server" = "Push-kiszolgáló"; @@ -3949,10 +4009,10 @@ "Safer groups" = "Biztonságosabb csoportok"; /* No comment provided by engineer. */ -"Same conditions will apply to operator **%@**." = "Ugyanezek a feltételek vonatkoznak a következő üzemeltetőre is: **%@**."; +"The same conditions will apply to operator **%@**." = "Ugyanezek a feltételek vonatkoznak a következő üzemeltetőre is: **%@**."; /* No comment provided by engineer. */ -"Same conditions will apply to operator(s): **%@**." = "Ugyanezek a feltételek lesznek elfogadva a következő üzemeltető(k)re is: **%@**."; +"The same conditions will apply to operator(s): **%@**." = "Ugyanezek a feltételek lesznek elfogadva a következő üzemeltető(k)re is: **%@**."; /* alert button chat item action */ @@ -4007,10 +4067,10 @@ "Saved" = "Mentett"; /* No comment provided by engineer. */ -"Saved from" = "Mentve innen:"; +"Saved from" = "Elmentve innen:"; /* No comment provided by engineer. */ -"saved from %@" = "mentve innen: %@"; +"saved from %@" = "elmentve innen: %@"; /* message info title */ "Saved message" = "Mentett üzenet"; @@ -4580,6 +4640,9 @@ /* No comment provided by engineer. */ "Tap button " = "Koppintson a "; +/* No comment provided by engineer. */ +"Tap Create SimpleX address in the menu to create it later." = "Koppintson a SimpleX-cím létrehozása menüpontra a későbbi létrehozáshoz."; + /* No comment provided by engineer. */ "Tap to activate profile." = "A profil aktiválásához koppintson az ikonra."; @@ -5282,6 +5345,9 @@ /* No comment provided by engineer. */ "You are already connected to %@." = "Ön már kapcsolódva van ehhez: %@."; +/* No comment provided by engineer. */ +"You are already connected with %@." = "Ön már kapcsolódva van vele: %@."; + /* No comment provided by engineer. */ "You are already connecting to %@." = "Már folyamatban van a kapcsolódás ehhez: %@."; @@ -5480,6 +5546,9 @@ /* No comment provided by engineer. */ "You will still receive calls and notifications from muted profiles when they are active." = "Továbbra is kap hívásokat és értesítéseket a némított profiloktól, ha azok aktívak."; +/* No comment provided by engineer. */ +"You will stop receiving messages from this chat. Chat history will be preserved." = "Ön nem fog több üzenetet kapni ebből a csevegésből, de a csevegés előzményei megmaradnak."; + /* No comment provided by engineer. */ "You will stop receiving messages from this group. Chat history will be preserved." = "Ettől a csoporttól nem fog értesítéseket kapni. A csevegési előzmények megmaradnak."; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index 42f399b710..2fe1216f35 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -388,6 +388,9 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Aggiungi l'indirizzo al tuo profilo, in modo che i tuoi contatti possano condividerlo con altre persone. L'aggiornamento del profilo verrà inviato ai tuoi contatti."; +/* No comment provided by engineer. */ +"Add friends" = "Aggiungi amici"; + /* No comment provided by engineer. */ "Add profile" = "Aggiungi profilo"; @@ -397,12 +400,18 @@ /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "Aggiungi server scansionando codici QR."; +/* No comment provided by engineer. */ +"Add team members" = "Aggiungi membri del team"; + /* No comment provided by engineer. */ "Add to another device" = "Aggiungi ad un altro dispositivo"; /* No comment provided by engineer. */ "Add welcome message" = "Aggiungi messaggio di benvenuto"; +/* No comment provided by engineer. */ +"Add your team members to the conversations." = "Aggiungi i membri del tuo team alle conversazioni."; + /* No comment provided by engineer. */ "Added media & file servers" = "Server di multimediali e file aggiunti"; @@ -793,6 +802,12 @@ /* 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)!" = "Bulgaro, finlandese, tailandese e ucraino - grazie agli utenti e a [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; +/* No comment provided by engineer. */ +"Business address" = "Indirizzo di lavoro"; + +/* No comment provided by engineer. */ +"Business chats" = "Chat di lavoro"; + /* 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 profilo di chat (predefinito) o [per connessione](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; @@ -892,7 +907,7 @@ "Change self-destruct passcode" = "Cambia codice di autodistruzione"; /* authentication reason */ -"Change user profiles" = "Modifica profili utente"; +"Change chat profiles" = "Modifica profili utente"; /* chat item text */ "changed address for you" = "indirizzo cambiato per te"; @@ -909,6 +924,15 @@ /* chat item text */ "changing address…" = "cambio indirizzo…"; +/* No comment provided by engineer. */ +"Chat" = "Chat"; + +/* No comment provided by engineer. */ +"Chat already exists" = "La chat esiste già"; + +/* No comment provided by engineer. */ +"Chat already exists!" = "La chat esiste già!"; + /* No comment provided by engineer. */ "Chat colors" = "Colori della chat"; @@ -954,6 +978,12 @@ /* No comment provided by engineer. */ "Chat theme" = "Tema della chat"; +/* No comment provided by engineer. */ +"Chat will be deleted for all members - this cannot be undone!" = "La chat verrà eliminata per tutti i membri, non è reversibile!"; + +/* No comment provided by engineer. */ +"Chat will be deleted for you - this cannot be undone!" = "La chat verrà eliminata solo per te, non è reversibile!"; + /* No comment provided by engineer. */ "Chats" = "Chat"; @@ -1475,12 +1505,18 @@ /* No comment provided by engineer. */ "Delete and notify contact" = "Elimina e avvisa il contatto"; +/* No comment provided by engineer. */ +"Delete chat" = "Elimina chat"; + /* No comment provided by engineer. */ "Delete chat profile" = "Elimina il profilo di chat"; /* No comment provided by engineer. */ "Delete chat profile?" = "Eliminare il profilo di chat?"; +/* No comment provided by engineer. */ +"Delete chat?" = "Eliminare la chat?"; + /* No comment provided by engineer. */ "Delete connection" = "Elimina connessione"; @@ -1655,6 +1691,9 @@ /* chat feature */ "Direct messages" = "Messaggi diretti"; +/* No comment provided by engineer. */ +"Direct messages between members are prohibited in this chat." = "I messaggi diretti tra i membri sono vietati in questa chat."; + /* No comment provided by engineer. */ "Direct messages between members are prohibited." = "I messaggi diretti tra i membri sono vietati in questo gruppo."; @@ -2424,27 +2463,6 @@ /* No comment provided by engineer. */ "Group links" = "Link del gruppo"; -/* No comment provided by engineer. */ -"Members can add message reactions." = "I membri del gruppo possono aggiungere reazioni ai messaggi."; - -/* No comment provided by engineer. */ -"Members can irreversibly delete sent messages. (24 hours)" = "I membri del gruppo possono eliminare irreversibilmente i messaggi inviati. (24 ore)"; - -/* No comment provided by engineer. */ -"Members can send direct messages." = "I membri del gruppo possono inviare messaggi diretti."; - -/* No comment provided by engineer. */ -"Members can send disappearing messages." = "I membri del gruppo possono inviare messaggi a tempo."; - -/* No comment provided by engineer. */ -"Members can send files and media." = "I membri del gruppo possono inviare file e contenuti multimediali."; - -/* No comment provided by engineer. */ -"Members can send SimpleX links." = "I membri del gruppo possono inviare link di Simplex."; - -/* No comment provided by engineer. */ -"Members can send voice messages." = "I membri del gruppo possono inviare messaggi vocali."; - /* notification */ "Group message:" = "Messaggio del gruppo:"; @@ -2715,6 +2733,9 @@ /* No comment provided by engineer. */ "Invite members" = "Invita membri"; +/* No comment provided by engineer. */ +"Invite to chat" = "Invita in chat"; + /* No comment provided by engineer. */ "Invite to group" = "Invita al gruppo"; @@ -2829,6 +2850,12 @@ /* swipe action */ "Leave" = "Esci"; +/* No comment provided by engineer. */ +"Leave chat" = "Esci dalla chat"; + +/* No comment provided by engineer. */ +"Leave chat?" = "Uscire dalla chat?"; + /* No comment provided by engineer. */ "Leave group" = "Esci dal gruppo"; @@ -2925,15 +2952,42 @@ /* item status text */ "Member inactive" = "Membro inattivo"; +/* No comment provided by engineer. */ +"Member role will be changed to \"%@\". All chat members will be notified." = "Il ruolo del membro verrà cambiato in \"%@\". Verranno notificati tutti i membri della chat."; + /* No comment provided by engineer. */ "Member role will be changed to \"%@\". All group members will be notified." = "Il ruolo del membro verrà cambiato in \"%@\". Tutti i membri del gruppo verranno avvisati."; /* No comment provided by engineer. */ "Member role will be changed to \"%@\". The member will receive a new invitation." = "Il ruolo del membro verrà cambiato in \"%@\". Il membro riceverà un invito nuovo."; +/* No comment provided by engineer. */ +"Member will be removed from chat - this cannot be undone!" = "Il membro verrà rimosso dalla chat, non è reversibile!"; + /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Il membro verrà rimosso dal gruppo, non è reversibile!"; +/* No comment provided by engineer. */ +"Members can add message reactions." = "I membri del gruppo possono aggiungere reazioni ai messaggi."; + +/* No comment provided by engineer. */ +"Members can irreversibly delete sent messages. (24 hours)" = "I membri del gruppo possono eliminare irreversibilmente i messaggi inviati. (24 ore)"; + +/* No comment provided by engineer. */ +"Members can send direct messages." = "I membri del gruppo possono inviare messaggi diretti."; + +/* No comment provided by engineer. */ +"Members can send disappearing messages." = "I membri del gruppo possono inviare messaggi a tempo."; + +/* No comment provided by engineer. */ +"Members can send files and media." = "I membri del gruppo possono inviare file e contenuti multimediali."; + +/* No comment provided by engineer. */ +"Members can send SimpleX links." = "I membri del gruppo possono inviare link di Simplex."; + +/* No comment provided by engineer. */ +"Members can send voice messages." = "I membri del gruppo possono inviare messaggi vocali."; + /* No comment provided by engineer. */ "Menus" = "Menu"; @@ -3329,6 +3383,9 @@ /* No comment provided by engineer. */ "Onion hosts will not be used." = "Gli host Onion non verranno usati."; +/* No comment provided by engineer. */ +"Only chat owners can change preferences." = "Solo i proprietari della chat possono modificarne le preferenze."; + /* No comment provided by engineer. */ "Only client devices store user profiles, contacts, groups, and messages." = "Solo i dispositivi client archiviano profili utente, i contatti, i gruppi e i messaggi inviati con la **crittografia end-to-end a 2 livelli**."; @@ -3407,6 +3464,9 @@ /* alert title */ "Operator server" = "Server dell'operatore"; +/* No comment provided by engineer. */ +"Or import archive file" = "O importa file archivio"; + /* No comment provided by engineer. */ "Or paste archive link" = "O incolla il link dell'archivio"; @@ -3575,6 +3635,9 @@ /* No comment provided by engineer. */ "Privacy & security" = "Privacy e sicurezza"; +/* No comment provided by engineer. */ +"Privacy for your customers." = "Privacy per i tuoi clienti."; + /* No comment provided by engineer. */ "Privacy redefined" = "Privacy ridefinita"; @@ -3671,9 +3734,6 @@ /* No comment provided by engineer. */ "Push notifications" = "Notifiche push"; -/* No comment provided by engineer. */ -"Push Notifications" = "Notifiche push"; - /* No comment provided by engineer. */ "Push server" = "Server push"; @@ -3949,10 +4009,10 @@ "Safer groups" = "Gruppi più sicuri"; /* No comment provided by engineer. */ -"Same conditions will apply to operator **%@**." = "Le stesse condizioni si applicheranno all'operatore **%@**."; +"The same conditions will apply to operator **%@**." = "Le stesse condizioni si applicheranno all'operatore **%@**."; /* No comment provided by engineer. */ -"Same conditions will apply to operator(s): **%@**." = "Le stesse condizioni si applicheranno agli operatori **%@**."; +"The same conditions will apply to operator(s): **%@**." = "Le stesse condizioni si applicheranno agli operatori **%@**."; /* alert button chat item action */ @@ -4580,6 +4640,9 @@ /* No comment provided by engineer. */ "Tap button " = "Tocca il pulsante "; +/* No comment provided by engineer. */ +"Tap Create SimpleX address in the menu to create it later." = "Tocca \"Crea indirizzo SimpleX\" nel menu per crearlo più tardi."; + /* No comment provided by engineer. */ "Tap to activate profile." = "Tocca per attivare il profilo."; @@ -5282,6 +5345,9 @@ /* No comment provided by engineer. */ "You are already connected to %@." = "Sei già connesso/a a %@."; +/* No comment provided by engineer. */ +"You are already connected with %@." = "Sei già connesso/a con %@."; + /* No comment provided by engineer. */ "You are already connecting to %@." = "Ti stai già connettendo a %@."; @@ -5480,6 +5546,9 @@ /* No comment provided by engineer. */ "You will still receive calls and notifications from muted profiles when they are active." = "Continuerai a ricevere chiamate e notifiche da profili silenziati quando sono attivi."; +/* No comment provided by engineer. */ +"You will stop receiving messages from this chat. Chat history will be preserved." = "Non riceverai più messaggi da questa chat. La cronologia della chat verrà conservata."; + /* No comment provided by engineer. */ "You will stop receiving messages from this group. Chat history will be preserved." = "Non riceverai più messaggi da questo gruppo. La cronologia della chat verrà conservata."; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index 019472b804..06fa3f70b3 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -1661,24 +1661,6 @@ /* No comment provided by engineer. */ "Group links" = "グループのリンク"; -/* No comment provided by engineer. */ -"Members can add message reactions." = "グループメンバーはメッセージへのリアクションを追加できます。"; - -/* No comment provided by engineer. */ -"Members can irreversibly delete sent messages. (24 hours)" = "グループのメンバーがメッセージを完全削除することができます。(24時間)"; - -/* No comment provided by engineer. */ -"Members can send direct messages." = "グループのメンバーがダイレクトメッセージを送信できます。"; - -/* No comment provided by engineer. */ -"Members can send disappearing messages." = "グループのメンバーが消えるメッセージを送信できます。"; - -/* No comment provided by engineer. */ -"Members can send files and media." = "グループメンバーはファイルやメディアを送信できます。"; - -/* No comment provided by engineer. */ -"Members can send voice messages." = "グループのメンバーが音声メッセージを送信できます。"; - /* notification */ "Group message:" = "グループメッセージ:"; @@ -2051,6 +2033,24 @@ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "メンバーをグループから除名する (※元に戻せません※)!"; +/* No comment provided by engineer. */ +"Members can add message reactions." = "グループメンバーはメッセージへのリアクションを追加できます。"; + +/* No comment provided by engineer. */ +"Members can irreversibly delete sent messages. (24 hours)" = "グループのメンバーがメッセージを完全削除することができます。(24時間)"; + +/* No comment provided by engineer. */ +"Members can send direct messages." = "グループのメンバーがダイレクトメッセージを送信できます。"; + +/* No comment provided by engineer. */ +"Members can send disappearing messages." = "グループのメンバーが消えるメッセージを送信できます。"; + +/* No comment provided by engineer. */ +"Members can send files and media." = "グループメンバーはファイルやメディアを送信できます。"; + +/* No comment provided by engineer. */ +"Members can send voice messages." = "グループのメンバーが音声メッセージを送信できます。"; + /* item status text */ "Message delivery error" = "メッセージ送信エラー"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index 652ccdf63c..edb123334d 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -388,6 +388,9 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Voeg een adres toe aan uw profiel, zodat uw contacten het met andere mensen kunnen delen. Profiel update wordt naar uw contacten verzonden."; +/* No comment provided by engineer. */ +"Add friends" = "Vrienden toevoegen"; + /* No comment provided by engineer. */ "Add profile" = "Profiel toevoegen"; @@ -397,12 +400,18 @@ /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "Servers toevoegen door QR-codes te scannen."; +/* No comment provided by engineer. */ +"Add team members" = "Teamleden toevoegen"; + /* No comment provided by engineer. */ "Add to another device" = "Toevoegen aan een ander apparaat"; /* No comment provided by engineer. */ "Add welcome message" = "Welkom bericht toevoegen"; +/* No comment provided by engineer. */ +"Add your team members to the conversations." = "Voeg uw teamleden toe aan de gesprekken."; + /* No comment provided by engineer. */ "Added media & file servers" = "Media- en bestandsservers toegevoegd"; @@ -512,7 +521,7 @@ "Allow downgrade" = "Downgraden toestaan"; /* No comment provided by engineer. */ -"Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Sta het onomkeerbaar verwijderen van berichten alleen toe als uw contact dit toestaat. (24 uur)"; +"Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Sta het definitief verwijderen van berichten alleen toe als uw contact dit toestaat. (24 uur)"; /* No comment provided by engineer. */ "Allow message reactions only if your contact allows them." = "Sta bericht reacties alleen toe als uw contact dit toestaat."; @@ -530,7 +539,7 @@ "Allow sharing" = "Delen toestaan"; /* No comment provided by engineer. */ -"Allow to irreversibly delete sent messages. (24 hours)" = "Sta toe om verzonden berichten onomkeerbaar te verwijderen. (24 uur)"; +"Allow to irreversibly delete sent messages. (24 hours)" = "Sta toe om verzonden berichten definitief te verwijderen. (24 uur)"; /* No comment provided by engineer. */ "Allow to send files and media." = "Sta toe om bestanden en media te verzenden."; @@ -554,7 +563,7 @@ "Allow your contacts to call you." = "Sta toe dat uw contacten u bellen."; /* No comment provided by engineer. */ -"Allow your contacts to irreversibly delete sent messages. (24 hours)" = "Laat uw contacten verzonden berichten onomkeerbaar verwijderen. (24 uur)"; +"Allow your contacts to irreversibly delete sent messages. (24 hours)" = "Laat uw contacten verzonden berichten definitief verwijderen. (24 uur)"; /* No comment provided by engineer. */ "Allow your contacts to send disappearing messages." = "Sta toe dat uw contacten verdwijnende berichten verzenden."; @@ -659,7 +668,7 @@ "Audio/video calls" = "Audio/video oproepen"; /* No comment provided by engineer. */ -"Audio/video calls are prohibited." = "Audio/video gesprekken zijn verboden."; +"Audio/video calls are prohibited." = "Audio/video gesprekken zijn niet toegestaan."; /* PIN entry */ "Authentication cancelled" = "Verificatie geannuleerd"; @@ -793,6 +802,12 @@ /* 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)!" = "Bulgaars, Fins, Thais en Oekraïens - dankzij de gebruikers en [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; +/* No comment provided by engineer. */ +"Business address" = "Zakelijk adres"; + +/* No comment provided by engineer. */ +"Business chats" = "Zakelijke chats"; + /* 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)." = "Via chatprofiel (standaard) of [via verbinding](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; @@ -815,7 +830,7 @@ "Calls" = "Oproepen"; /* No comment provided by engineer. */ -"Calls prohibited!" = "Bellen verboden!"; +"Calls prohibited!" = "Bellen niet toegestaan!"; /* No comment provided by engineer. */ "Camera not available" = "Camera niet beschikbaar"; @@ -892,7 +907,7 @@ "Change self-destruct passcode" = "Zelfvernietigings code wijzigen"; /* authentication reason */ -"Change user profiles" = "Gebruikersprofielen wijzigen"; +"Change chat profiles" = "Gebruikersprofielen wijzigen"; /* chat item text */ "changed address for you" = "adres voor u gewijzigd"; @@ -909,6 +924,15 @@ /* chat item text */ "changing address…" = "adres wijzigen…"; +/* No comment provided by engineer. */ +"Chat" = "Chat"; + +/* No comment provided by engineer. */ +"Chat already exists" = "Chat bestaat al"; + +/* No comment provided by engineer. */ +"Chat already exists!" = "Chat bestaat al!"; + /* No comment provided by engineer. */ "Chat colors" = "Chat kleuren"; @@ -954,6 +978,12 @@ /* No comment provided by engineer. */ "Chat theme" = "Chat thema"; +/* No comment provided by engineer. */ +"Chat will be deleted for all members - this cannot be undone!" = "De chat wordt voor alle leden verwijderd - dit kan niet ongedaan worden gemaakt!"; + +/* No comment provided by engineer. */ +"Chat will be deleted for you - this cannot be undone!" = "De chat wordt voor je verwijderd - dit kan niet ongedaan worden gemaakt!"; + /* No comment provided by engineer. */ "Chats" = "Chats"; @@ -1475,12 +1505,18 @@ /* No comment provided by engineer. */ "Delete and notify contact" = "Verwijderen en contact op de hoogte stellen"; +/* No comment provided by engineer. */ +"Delete chat" = "Chat verwijderen"; + /* No comment provided by engineer. */ "Delete chat profile" = "Chatprofiel verwijderen"; /* No comment provided by engineer. */ "Delete chat profile?" = "Chatprofiel verwijderen?"; +/* No comment provided by engineer. */ +"Delete chat?" = "Chat verwijderen?"; + /* No comment provided by engineer. */ "Delete connection" = "Verbinding verwijderen"; @@ -1656,7 +1692,10 @@ "Direct messages" = "Directe berichten"; /* No comment provided by engineer. */ -"Direct messages between members are prohibited." = "Directe berichten tussen leden zijn verboden in deze groep."; +"Direct messages between members are prohibited in this chat." = "Directe berichten tussen leden zijn in deze chat niet toegestaan."; + +/* No comment provided by engineer. */ +"Direct messages between members are prohibited." = "Directe berichten tussen leden zijn niet toegestaan."; /* No comment provided by engineer. */ "Disable (keep overrides)" = "Uitschakelen (overschrijvingen behouden)"; @@ -1680,10 +1719,10 @@ "Disappearing messages" = "Verdwijnende berichten"; /* No comment provided by engineer. */ -"Disappearing messages are prohibited in this chat." = "Verdwijnende berichten zijn verboden in dit gesprek."; +"Disappearing messages are prohibited in this chat." = "Verdwijnende berichten zijn niet toegestaan in dit gesprek."; /* No comment provided by engineer. */ -"Disappearing messages are prohibited." = "Verdwijnende berichten zijn verboden in deze groep."; +"Disappearing messages are prohibited." = "Verdwijnende berichten zijn niet toegestaan."; /* No comment provided by engineer. */ "Disappears at" = "Verdwijnt op"; @@ -2254,13 +2293,13 @@ "Files and media" = "Bestanden en media"; /* No comment provided by engineer. */ -"Files and media are prohibited." = "Bestanden en media zijn verboden in deze groep."; +"Files and media are prohibited." = "Bestanden en media zijn niet toegestaan."; /* No comment provided by engineer. */ "Files and media not allowed" = "Bestanden en media niet toegestaan"; /* No comment provided by engineer. */ -"Files and media prohibited!" = "Bestanden en media verboden!"; +"Files and media prohibited!" = "Bestanden en media niet toegestaan!"; /* No comment provided by engineer. */ "Filter unread and favorite chats." = "Filter ongelezen en favoriete chats."; @@ -2424,27 +2463,6 @@ /* No comment provided by engineer. */ "Group links" = "Groep links"; -/* No comment provided by engineer. */ -"Members can add message reactions." = "Groepsleden kunnen bericht reacties toevoegen."; - -/* No comment provided by engineer. */ -"Members can irreversibly delete sent messages. (24 hours)" = "Groepsleden kunnen verzonden berichten onherroepelijk verwijderen. (24 uur)"; - -/* No comment provided by engineer. */ -"Members can send direct messages." = "Groepsleden kunnen directe berichten sturen."; - -/* No comment provided by engineer. */ -"Members can send disappearing messages." = "Groepsleden kunnen verdwijnende berichten sturen."; - -/* No comment provided by engineer. */ -"Members can send files and media." = "Groepsleden kunnen bestanden en media verzenden."; - -/* No comment provided by engineer. */ -"Members can send SimpleX links." = "Groepsleden kunnen SimpleX-links verzenden."; - -/* No comment provided by engineer. */ -"Members can send voice messages." = "Groepsleden kunnen spraak berichten verzenden."; - /* notification */ "Group message:" = "Groep bericht:"; @@ -2533,7 +2551,7 @@ "If you can't meet in person, show QR code in a video call, or share the link." = "Als je elkaar niet persoonlijk kunt ontmoeten, laat dan de QR-code zien in een videogesprek of deel de link."; /* No comment provided by engineer. */ -"If you enter this passcode when opening the app, all app data will be irreversibly removed!" = "Als u deze toegangscode invoert bij het openen van de app, worden alle app-gegevens onomkeerbaar verwijderd!"; +"If you enter this passcode when opening the app, all app data will be irreversibly removed!" = "Als u deze toegangscode invoert bij het openen van de app, worden alle app-gegevens definitief verwijderd!"; /* No comment provided by engineer. */ "If you enter your self-destruct passcode while opening the app:" = "Als u uw zelfvernietigings wachtwoord invoert tijdens het openen van de app:"; @@ -2715,6 +2733,9 @@ /* No comment provided by engineer. */ "Invite members" = "Nodig leden uit"; +/* No comment provided by engineer. */ +"Invite to chat" = "Uitnodigen voor een chat"; + /* No comment provided by engineer. */ "Invite to group" = "Uitnodigen voor groep"; @@ -2743,10 +2764,10 @@ "Irreversible message deletion" = "Onomkeerbare berichtverwijdering"; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this chat." = "Het onomkeerbaar verwijderen van berichten is verboden in dit gesprek."; +"Irreversible message deletion is prohibited in this chat." = "Het definitief verwijderen van berichten is niet toegestaan in dit gesprek."; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited." = "Het onomkeerbaar verwijderen van berichten is verboden in deze groep."; +"Irreversible message deletion is prohibited." = "Het definitief verwijderen van berichten is verbHet definitief verwijderen van berichten is niet toegestaan.."; /* No comment provided by engineer. */ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "Het maakt het mogelijk om veel anonieme verbindingen te hebben zonder enige gedeelde gegevens tussen hen in een enkel chatprofiel."; @@ -2829,6 +2850,12 @@ /* swipe action */ "Leave" = "Verlaten"; +/* No comment provided by engineer. */ +"Leave chat" = "Chat verlaten"; + +/* No comment provided by engineer. */ +"Leave chat?" = "Chat verlaten?"; + /* No comment provided by engineer. */ "Leave group" = "Groep verlaten"; @@ -2925,15 +2952,42 @@ /* item status text */ "Member inactive" = "Lid inactief"; +/* No comment provided by engineer. */ +"Member role will be changed to \"%@\". All chat members will be notified." = "De rol van het lid wordt gewijzigd naar \"%@\". Alle chatleden worden op de hoogte gebracht."; + /* No comment provided by engineer. */ "Member role will be changed to \"%@\". All group members will be notified." = "De rol van lid wordt gewijzigd in \"%@\". Alle groepsleden worden op de hoogte gebracht."; /* No comment provided by engineer. */ "Member role will be changed to \"%@\". The member will receive a new invitation." = "De rol van lid wordt gewijzigd in \"%@\". Het lid ontvangt een nieuwe uitnodiging."; +/* No comment provided by engineer. */ +"Member will be removed from chat - this cannot be undone!" = "Lid wordt verwijderd uit de chat - dit kan niet ongedaan worden gemaakt!"; + /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Lid wordt uit de groep verwijderd, dit kan niet ongedaan worden gemaakt!"; +/* No comment provided by engineer. */ +"Members can add message reactions." = "Groepsleden kunnen bericht reacties toevoegen."; + +/* No comment provided by engineer. */ +"Members can irreversibly delete sent messages. (24 hours)" = "Groepsleden kunnen verzonden berichten onherroepelijk verwijderen. (24 uur)"; + +/* No comment provided by engineer. */ +"Members can send direct messages." = "Groepsleden kunnen directe berichten sturen."; + +/* No comment provided by engineer. */ +"Members can send disappearing messages." = "Groepsleden kunnen verdwijnende berichten sturen."; + +/* No comment provided by engineer. */ +"Members can send files and media." = "Groepsleden kunnen bestanden en media verzenden."; + +/* No comment provided by engineer. */ +"Members can send SimpleX links." = "Groepsleden kunnen SimpleX-links verzenden."; + +/* No comment provided by engineer. */ +"Members can send voice messages." = "Groepsleden kunnen spraak berichten verzenden."; + /* No comment provided by engineer. */ "Menus" = "Menu's"; @@ -2965,10 +3019,10 @@ "Message reactions" = "Reacties op berichten"; /* No comment provided by engineer. */ -"Message reactions are prohibited in this chat." = "Reacties op berichten zijn verboden in deze chat."; +"Message reactions are prohibited in this chat." = "Reacties op berichten zijn niet toegestaan in deze chat."; /* No comment provided by engineer. */ -"Message reactions are prohibited." = "Reacties op berichten zijn verboden in deze groep."; +"Message reactions are prohibited." = "Reacties op berichten zijn niet toegestaan."; /* notification */ "message received" = "bericht ontvangen"; @@ -3329,6 +3383,9 @@ /* No comment provided by engineer. */ "Onion hosts will not be used." = "Onion hosts worden niet gebruikt."; +/* No comment provided by engineer. */ +"Only chat owners can change preferences." = "Alleen chateigenaren kunnen voorkeuren wijzigen."; + /* No comment provided by engineer. */ "Only client devices store user profiles, contacts, groups, and messages." = "Alleen client apparaten slaan gebruikers profielen, contacten, groepen en berichten op die zijn verzonden met **2-laags end-to-end-codering**."; @@ -3348,7 +3405,7 @@ "Only you can add message reactions." = "Alleen jij kunt bericht reacties toevoegen."; /* No comment provided by engineer. */ -"Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)" = "Alleen jij kunt berichten onomkeerbaar verwijderen (je contact kan ze markeren voor verwijdering). (24 uur)"; +"Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)" = "Alleen jij kunt berichten definitief verwijderen (je contact kan ze markeren voor verwijdering). (24 uur)"; /* No comment provided by engineer. */ "Only you can make calls." = "Alleen jij kunt bellen."; @@ -3407,6 +3464,9 @@ /* alert title */ "Operator server" = "Operatorserver"; +/* No comment provided by engineer. */ +"Or import archive file" = "Of importeer archiefbestand"; + /* No comment provided by engineer. */ "Or paste archive link" = "Of plak de archief link"; @@ -3575,6 +3635,9 @@ /* No comment provided by engineer. */ "Privacy & security" = "Privacy en beveiliging"; +/* No comment provided by engineer. */ +"Privacy for your customers." = "Privacy voor uw klanten."; + /* No comment provided by engineer. */ "Privacy redefined" = "Privacy opnieuw gedefinieerd"; @@ -3618,7 +3681,7 @@ "Prohibit audio/video calls." = "Audio/video gesprekken verbieden."; /* No comment provided by engineer. */ -"Prohibit irreversible message deletion." = "Verbied het onomkeerbaar verwijderen van berichten."; +"Prohibit irreversible message deletion." = "Verbied het definitief verwijderen van berichten."; /* No comment provided by engineer. */ "Prohibit message reactions." = "Bericht reacties verbieden."; @@ -3671,9 +3734,6 @@ /* No comment provided by engineer. */ "Push notifications" = "Push meldingen"; -/* No comment provided by engineer. */ -"Push Notifications" = "Pushmeldingen"; - /* No comment provided by engineer. */ "Push server" = "Push server"; @@ -3949,10 +4009,10 @@ "Safer groups" = "Veiligere groepen"; /* No comment provided by engineer. */ -"Same conditions will apply to operator **%@**." = "Dezelfde voorwaarden gelden voor operator **%@**."; +"The same conditions will apply to operator **%@**." = "Dezelfde voorwaarden gelden voor operator **%@**."; /* No comment provided by engineer. */ -"Same conditions will apply to operator(s): **%@**." = "Dezelfde voorwaarden gelden voor operator(s): **%@**."; +"The same conditions will apply to operator(s): **%@**." = "Dezelfde voorwaarden gelden voor operator(s): **%@**."; /* alert button chat item action */ @@ -4416,7 +4476,7 @@ "SimpleX links" = "SimpleX links"; /* No comment provided by engineer. */ -"SimpleX links are prohibited." = "SimpleX-links zijn in deze groep verboden."; +"SimpleX links are prohibited." = "SimpleX-links zijn niet toegestaan."; /* No comment provided by engineer. */ "SimpleX links not allowed" = "SimpleX-links zijn niet toegestaan"; @@ -4580,6 +4640,9 @@ /* No comment provided by engineer. */ "Tap button " = "Tik op de knop "; +/* No comment provided by engineer. */ +"Tap Create SimpleX address in the menu to create it later." = "Tik op SimpleX-adres maken in het menu om het later te maken."; + /* No comment provided by engineer. */ "Tap to activate profile." = "Tik hier om profiel te activeren."; @@ -4734,7 +4797,7 @@ "This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "Deze actie kan niet ongedaan worden gemaakt, de berichten die eerder zijn verzonden en ontvangen dan geselecteerd, worden verwijderd. Het kan enkele minuten duren."; /* No comment provided by engineer. */ -"This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Deze actie kan niet ongedaan worden gemaakt. Uw profiel, contacten, berichten en bestanden gaan onomkeerbaar verloren."; +"This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Deze actie kan niet ongedaan worden gemaakt. Uw profiel, contacten, berichten en bestanden gaan definitief verloren."; /* E2EE info chat item */ "This chat is protected by end-to-end encryption." = "Deze chat is beveiligd met end-to-end codering."; @@ -5145,16 +5208,16 @@ "Voice messages" = "Spraak berichten"; /* No comment provided by engineer. */ -"Voice messages are prohibited in this chat." = "Spraak berichten zijn verboden in deze chat."; +"Voice messages are prohibited in this chat." = "Spraak berichten zijn niet toegestaan in dit gesprek."; /* No comment provided by engineer. */ -"Voice messages are prohibited." = "Spraak berichten zijn verboden in deze groep."; +"Voice messages are prohibited." = "Spraak berichten zijn niet toegestaan."; /* No comment provided by engineer. */ "Voice messages not allowed" = "Spraakberichten niet toegestaan"; /* No comment provided by engineer. */ -"Voice messages prohibited!" = "Spraak berichten verboden!"; +"Voice messages prohibited!" = "Spraak berichten niet toegestaan!"; /* No comment provided by engineer. */ "waiting for answer…" = "wachten op antwoord…"; @@ -5282,6 +5345,9 @@ /* No comment provided by engineer. */ "You are already connected to %@." = "U bent al verbonden met %@."; +/* No comment provided by engineer. */ +"You are already connected with %@." = "U bent al verbonden met %@."; + /* No comment provided by engineer. */ "You are already connecting to %@." = "U maakt al verbinding met %@."; @@ -5480,6 +5546,9 @@ /* No comment provided by engineer. */ "You will still receive calls and notifications from muted profiles when they are active." = "U ontvangt nog steeds oproepen en meldingen van gedempte profielen wanneer deze actief zijn."; +/* No comment provided by engineer. */ +"You will stop receiving messages from this chat. Chat history will be preserved." = "U ontvangt geen berichten meer van deze chat. De chatgeschiedenis blijft bewaard."; + /* No comment provided by engineer. */ "You will stop receiving messages from this group. Chat history will be preserved." = "Je ontvangt geen berichten meer van deze groep. Je gesprek geschiedenis blijft behouden."; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index 04279064ee..782e1c18f4 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -2277,27 +2277,6 @@ /* No comment provided by engineer. */ "Group links" = "Linki grupowe"; -/* No comment provided by engineer. */ -"Members can add message reactions." = "Członkowie grupy mogą dodawać reakcje wiadomości."; - -/* No comment provided by engineer. */ -"Members can irreversibly delete sent messages. (24 hours)" = "Członkowie grupy mogą nieodwracalnie usuwać wysłane wiadomości. (24 godziny)"; - -/* No comment provided by engineer. */ -"Members can send direct messages." = "Członkowie grupy mogą wysyłać bezpośrednie wiadomości."; - -/* No comment provided by engineer. */ -"Members can send disappearing messages." = "Członkowie grupy mogą wysyłać znikające wiadomości."; - -/* No comment provided by engineer. */ -"Members can send files and media." = "Członkowie grupy mogą wysyłać pliki i media."; - -/* No comment provided by engineer. */ -"Members can send SimpleX links." = "Członkowie grupy mogą wysyłać linki SimpleX."; - -/* No comment provided by engineer. */ -"Members can send voice messages." = "Członkowie grupy mogą wysyłać wiadomości głosowe."; - /* notification */ "Group message:" = "Wiadomość grupowa:"; @@ -2778,6 +2757,27 @@ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Członek zostanie usunięty z grupy - nie można tego cofnąć!"; +/* No comment provided by engineer. */ +"Members can add message reactions." = "Członkowie grupy mogą dodawać reakcje wiadomości."; + +/* No comment provided by engineer. */ +"Members can irreversibly delete sent messages. (24 hours)" = "Członkowie grupy mogą nieodwracalnie usuwać wysłane wiadomości. (24 godziny)"; + +/* No comment provided by engineer. */ +"Members can send direct messages." = "Członkowie grupy mogą wysyłać bezpośrednie wiadomości."; + +/* No comment provided by engineer. */ +"Members can send disappearing messages." = "Członkowie grupy mogą wysyłać znikające wiadomości."; + +/* No comment provided by engineer. */ +"Members can send files and media." = "Członkowie grupy mogą wysyłać pliki i media."; + +/* No comment provided by engineer. */ +"Members can send SimpleX links." = "Członkowie grupy mogą wysyłać linki SimpleX."; + +/* No comment provided by engineer. */ +"Members can send voice messages." = "Członkowie grupy mogą wysyłać wiadomości głosowe."; + /* No comment provided by engineer. */ "Menus" = "Menu"; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index bb7ec81ace..c2124b34a4 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -82,6 +82,9 @@ /* No comment provided by engineer. */ "**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Рекомендовано**: токен устройства и уведомления отправляются на сервер SimpleX Chat, но сервер не получает сами сообщения, их размер или от кого они."; +/* No comment provided by engineer. */ +"**Scan / Paste link**: to connect via a link you received." = "**Сканировать / Вставить ссылку**: чтобы соединится через полученную ссылку."; + /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Внимание**: для работы мгновенных уведомлений пароль должен быть сохранен в Keychain."; @@ -142,6 +145,12 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ подтверждён"; +/* No comment provided by engineer. */ +"%@ server" = "%@ сервер"; + +/* No comment provided by engineer. */ +"%@ servers" = "%@ серверы"; + /* No comment provided by engineer. */ "%@ uploaded" = "%@ загружено"; @@ -295,6 +304,12 @@ /* time interval */ "1 week" = "1 неделю"; +/* No comment provided by engineer. */ +"1-time link" = "Одноразовая ссылка"; + +/* No comment provided by engineer. */ +"1-time link can be used *with one contact only* - share in person or via any messenger." = "Одноразовая ссылка может быть использована *только с одним контактом* - поделитесь при встрече или через любой мессенджер."; + /* No comment provided by engineer. */ "5 minutes" = "5 минут"; @@ -342,6 +357,9 @@ swipe action */ "Accept" = "Принять"; +/* No comment provided by engineer. */ +"Accept conditions" = "Принять условия"; + /* No comment provided by engineer. */ "Accept connection request?" = "Принять запрос?"; @@ -355,6 +373,9 @@ /* call status */ "accepted call" = "принятый звонок"; +/* No comment provided by engineer. */ +"Accepted conditions" = "Принятые условия"; + /* No comment provided by engineer. */ "Acknowledged" = "Подтверждено"; @@ -367,6 +388,9 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Добавьте адрес в свой профиль, чтобы Ваши контакты могли поделиться им. Профиль будет отправлен Вашим контактам."; +/* No comment provided by engineer. */ +"Add friends" = "Добавить друзей"; + /* No comment provided by engineer. */ "Add profile" = "Добавить профиль"; @@ -376,12 +400,24 @@ /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "Добавить серверы через QR код."; +/* No comment provided by engineer. */ +"Add team members" = "Добавить сотрудников"; + /* No comment provided by engineer. */ "Add to another device" = "Добавить на другое устройство"; /* No comment provided by engineer. */ "Add welcome message" = "Добавить приветственное сообщение"; +/* No comment provided by engineer. */ +"Add your team members to the conversations." = "Добавьте сотрудников в разговор."; + +/* No comment provided by engineer. */ +"Added media & file servers" = "Дополнительные серверы файлов и медиа"; + +/* No comment provided by engineer. */ +"Added message servers" = "Дополнительные серверы сообщений"; + /* No comment provided by engineer. */ "Additional accent" = "Дополнительный акцент"; @@ -397,6 +433,12 @@ /* No comment provided by engineer. */ "Address change will be aborted. Old receiving address will be used." = "Изменение адреса будет прекращено. Будет использоваться старый адрес."; +/* No comment provided by engineer. */ +"Address or 1-time link?" = "Адрес или одноразовая ссылка?"; + +/* No comment provided by engineer. */ +"Address settings" = "Настройки адреса"; + /* member role */ "admin" = "админ"; @@ -760,6 +802,12 @@ /* 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)!" = "Болгарский, финский, тайский и украинский - благодаря пользователям и [Weblate] (https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; +/* No comment provided by engineer. */ +"Business address" = "Бизнес адрес"; + +/* No comment provided by engineer. */ +"Business chats" = "Бизнес разговоры"; + /* 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)." = "По профилю чата или [по соединению](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (БЕТА)."; @@ -858,6 +906,9 @@ set passcode view */ "Change self-destruct passcode" = "Изменить код самоуничтожения"; +/* authentication reason */ +"Change chat profiles" = "Поменять профили"; + /* chat item text */ "changed address for you" = "поменял(а) адрес для Вас"; @@ -873,6 +924,15 @@ /* chat item text */ "changing address…" = "смена адреса…"; +/* No comment provided by engineer. */ +"Chat" = "Разговор"; + +/* No comment provided by engineer. */ +"Chat already exists" = "Разговор уже существует"; + +/* No comment provided by engineer. */ +"Chat already exists!" = "Разговор уже существует!"; + /* No comment provided by engineer. */ "Chat colors" = "Цвета чата"; @@ -918,9 +978,21 @@ /* No comment provided by engineer. */ "Chat theme" = "Тема чата"; +/* No comment provided by engineer. */ +"Chat will be deleted for all members - this cannot be undone!" = "Разговор будет удален для всех участников - это действие нельзя отменить!"; + +/* No comment provided by engineer. */ +"Chat will be deleted for you - this cannot be undone!" = "Разговор будет удален для Вас - это действие нельзя отменить!"; + /* No comment provided by engineer. */ "Chats" = "Чаты"; +/* No comment provided by engineer. */ +"Check messages every 20 min." = "Проверять сообщения каждые 20 минут."; + +/* No comment provided by engineer. */ +"Check messages when allowed." = "Проверять сообщения по возможности."; + /* alert title */ "Check server address and try again." = "Проверьте адрес сервера и попробуйте снова."; @@ -981,6 +1053,33 @@ /* No comment provided by engineer. */ "Completed" = "Готово"; +/* No comment provided by engineer. */ +"Conditions accepted on: %@." = "Условия приняты: %@."; + +/* No comment provided by engineer. */ +"Conditions are accepted for the operator(s): **%@**." = "Условия приняты для оператора(ов): **%@**."; + +/* No comment provided by engineer. */ +"Conditions are already accepted for following operator(s): **%@**." = "Условия уже приняты для следующих оператора(ов): **%@**."; + +/* No comment provided by engineer. */ +"Conditions of use" = "Условия использования"; + +/* No comment provided by engineer. */ +"Conditions will be accepted for enabled operators after 30 days." = "Условия будут приняты для включенных операторов через 30 дней."; + +/* No comment provided by engineer. */ +"Conditions will be accepted for operator(s): **%@**." = "Условия будут приняты для оператора(ов): **%@**."; + +/* No comment provided by engineer. */ +"Conditions will be accepted for the operator(s): **%@**." = "Условия будут приняты для оператора(ов): **%@**."; + +/* No comment provided by engineer. */ +"Conditions will be accepted on: %@." = "Условия будут приняты: %@."; + +/* No comment provided by engineer. */ +"Conditions will be automatically accepted for enabled operators on: %@." = "Условия будут автоматически приняты для включенных операторов: %@."; + /* No comment provided by engineer. */ "Configure ICE servers" = "Настройка ICE серверов"; @@ -1128,6 +1227,9 @@ /* No comment provided by engineer. */ "Connection request sent!" = "Запрос на соединение отправлен!"; +/* No comment provided by engineer. */ +"Connection security" = "Безопасность соединения"; + /* No comment provided by engineer. */ "Connection terminated" = "Подключение прервано"; @@ -1209,6 +1311,9 @@ /* No comment provided by engineer. */ "Create" = "Создать"; +/* No comment provided by engineer. */ +"Create 1-time link" = "Создать одноразовую ссылку"; + /* No comment provided by engineer. */ "Create a group using a random profile." = "Создайте группу, используя случайный профиль."; @@ -1260,6 +1365,9 @@ /* No comment provided by engineer. */ "creator" = "создатель"; +/* No comment provided by engineer. */ +"Current conditions text couldn't be loaded, you can review conditions via this link:" = "Текст условий использования не может быть показан, вы можете посмотреть их через ссылку:"; + /* No comment provided by engineer. */ "Current Passcode" = "Текущий Код"; @@ -1397,12 +1505,18 @@ /* No comment provided by engineer. */ "Delete and notify contact" = "Удалить и уведомить контакт"; +/* No comment provided by engineer. */ +"Delete chat" = "Удалить разговор"; + /* No comment provided by engineer. */ "Delete chat profile" = "Удалить профиль чата"; /* No comment provided by engineer. */ "Delete chat profile?" = "Удалить профиль?"; +/* No comment provided by engineer. */ +"Delete chat?" = "Удалить разговор?"; + /* No comment provided by engineer. */ "Delete connection" = "Удалить соединение"; @@ -1508,6 +1622,9 @@ /* No comment provided by engineer. */ "Deletion errors" = "Ошибки удаления"; +/* No comment provided by engineer. */ +"Delivered even when Apple drops them." = "Доставляются даже тогда, когда Apple их теряет."; + /* No comment provided by engineer. */ "Delivery" = "Доставка"; @@ -1574,6 +1691,9 @@ /* chat feature */ "Direct messages" = "Прямые сообщения"; +/* No comment provided by engineer. */ +"Direct messages between members are prohibited in this chat." = "Прямые сообщения между членами запрещены в этом разговоре."; + /* No comment provided by engineer. */ "Direct messages between members are prohibited." = "Прямые сообщения между членами группы запрещены."; @@ -1695,6 +1815,9 @@ /* No comment provided by engineer. */ "e2e encrypted" = "e2e зашифровано"; +/* No comment provided by engineer. */ +"E2E encrypted notifications." = "E2E зашифрованные нотификации."; + /* chat item action */ "Edit" = "Редактировать"; @@ -1713,6 +1836,9 @@ /* No comment provided by engineer. */ "Enable camera access" = "Включить доступ к камере"; +/* No comment provided by engineer. */ +"Enable Flux" = "Включить Flux"; + /* No comment provided by engineer. */ "Enable for all" = "Включить для всех"; @@ -1872,12 +1998,18 @@ /* No comment provided by engineer. */ "Error aborting address change" = "Ошибка при прекращении изменения адреса"; +/* alert title */ +"Error accepting conditions" = "Ошибка приема условий"; + /* No comment provided by engineer. */ "Error accepting contact request" = "Ошибка при принятии запроса на соединение"; /* No comment provided by engineer. */ "Error adding member(s)" = "Ошибка при добавлении членов группы"; +/* alert title */ +"Error adding server" = "Ошибка добавления сервера"; + /* No comment provided by engineer. */ "Error changing address" = "Ошибка при изменении адреса"; @@ -1962,6 +2094,9 @@ /* No comment provided by engineer. */ "Error joining group" = "Ошибка при вступлении в группу"; +/* alert title */ +"Error loading servers" = "Ошибка загрузки серверов"; + /* No comment provided by engineer. */ "Error migrating settings" = "Ошибка миграции настроек"; @@ -1995,6 +2130,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "Ошибка сохранения пароля в Keychain"; +/* alert title */ +"Error saving servers" = "Ошибка сохранения серверов"; + /* when migrating */ "Error saving settings" = "Ошибка сохранения настроек"; @@ -2037,6 +2175,9 @@ /* No comment provided by engineer. */ "Error updating message" = "Ошибка при обновлении сообщения"; +/* alert title */ +"Error updating server" = "Ошибка сохранения сервера"; + /* No comment provided by engineer. */ "Error updating settings" = "Ошибка при сохранении настроек сети"; @@ -2064,6 +2205,9 @@ /* No comment provided by engineer. */ "Errors" = "Ошибки"; +/* servers error */ +"Errors in servers configuration." = "Ошибки в настройках серверов."; + /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Даже когда они выключены в разговоре."; @@ -2190,9 +2334,24 @@ /* No comment provided by engineer. */ "Fix not supported by group member" = "Починка не поддерживается членом группы"; +/* No comment provided by engineer. */ +"for better metadata privacy." = "для лучшей конфиденциальности метаданных."; + +/* servers error */ +"For chat profile %@:" = "Для профиля чата %@:"; + /* No comment provided by engineer. */ "For console" = "Для консоли"; +/* No comment provided by engineer. */ +"For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server." = "Например, если Ваш контакт получает сообщения через сервер SimpleX Chat, Ваше приложение доставит их через сервер Flux."; + +/* No comment provided by engineer. */ +"For private routing" = "Для доставки сообщений"; + +/* No comment provided by engineer. */ +"For social media" = "Для социальных сетей"; + /* chat item action */ "Forward" = "Переслать"; @@ -2304,27 +2463,6 @@ /* No comment provided by engineer. */ "Group links" = "Ссылки групп"; -/* No comment provided by engineer. */ -"Members can add message reactions." = "Члены группы могут добавлять реакции на сообщения."; - -/* No comment provided by engineer. */ -"Members can irreversibly delete sent messages. (24 hours)" = "Члены группы могут необратимо удалять отправленные сообщения. (24 часа)"; - -/* No comment provided by engineer. */ -"Members can send direct messages." = "Члены группы могут посылать прямые сообщения."; - -/* No comment provided by engineer. */ -"Members can send disappearing messages." = "Члены группы могут посылать исчезающие сообщения."; - -/* No comment provided by engineer. */ -"Members can send files and media." = "Члены группы могут слать файлы и медиа."; - -/* No comment provided by engineer. */ -"Members can send SimpleX links." = "Члены группы могут отправлять ссылки SimpleX."; - -/* No comment provided by engineer. */ -"Members can send voice messages." = "Члены группы могут отправлять голосовые сообщения."; - /* notification */ "Group message:" = "Групповое сообщение:"; @@ -2385,6 +2523,12 @@ /* time unit */ "hours" = "часов"; +/* No comment provided by engineer. */ +"How it affects privacy" = "Как это влияет на конфиденциальность"; + +/* No comment provided by engineer. */ +"How it helps privacy" = "Как это улучшает конфиденциальность"; + /* No comment provided by engineer. */ "How SimpleX works" = "Как SimpleX работает"; @@ -2589,6 +2733,9 @@ /* No comment provided by engineer. */ "Invite members" = "Пригласить членов группы"; +/* No comment provided by engineer. */ +"Invite to chat" = "Пригласить в разговор"; + /* No comment provided by engineer. */ "Invite to group" = "Пригласить в группу"; @@ -2703,6 +2850,12 @@ /* swipe action */ "Leave" = "Выйти"; +/* No comment provided by engineer. */ +"Leave chat" = "Покинуть разговор"; + +/* No comment provided by engineer. */ +"Leave chat?" = "Покинуть разговор?"; + /* No comment provided by engineer. */ "Leave group" = "Выйти из группы"; @@ -2799,15 +2952,42 @@ /* item status text */ "Member inactive" = "Член неактивен"; +/* No comment provided by engineer. */ +"Member role will be changed to \"%@\". All chat members will be notified." = "Роль участника будет изменена на \"%@\". Все участники разговора получат уведомление."; + /* No comment provided by engineer. */ "Member role will be changed to \"%@\". All group members will be notified." = "Роль члена группы будет изменена на \"%@\". Все члены группы получат сообщение."; /* No comment provided by engineer. */ "Member role will be changed to \"%@\". The member will receive a new invitation." = "Роль члена группы будет изменена на \"%@\". Будет отправлено новое приглашение."; +/* No comment provided by engineer. */ +"Member will be removed from chat - this cannot be undone!" = "Член будет удален из разговора - это действие нельзя отменить!"; + /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Член группы будет удален - это действие нельзя отменить!"; +/* No comment provided by engineer. */ +"Members can add message reactions." = "Члены группы могут добавлять реакции на сообщения."; + +/* No comment provided by engineer. */ +"Members can irreversibly delete sent messages. (24 hours)" = "Члены группы могут необратимо удалять отправленные сообщения. (24 часа)"; + +/* No comment provided by engineer. */ +"Members can send direct messages." = "Члены группы могут посылать прямые сообщения."; + +/* No comment provided by engineer. */ +"Members can send disappearing messages." = "Члены группы могут посылать исчезающие сообщения."; + +/* No comment provided by engineer. */ +"Members can send files and media." = "Члены группы могут слать файлы и медиа."; + +/* No comment provided by engineer. */ +"Members can send SimpleX links." = "Члены группы могут отправлять ссылки SimpleX."; + +/* No comment provided by engineer. */ +"Members can send voice messages." = "Члены группы могут отправлять голосовые сообщения."; + /* No comment provided by engineer. */ "Menus" = "Меню"; @@ -2961,6 +3141,9 @@ /* No comment provided by engineer. */ "More reliable network connection." = "Более надежное соединение с сетью."; +/* No comment provided by engineer. */ +"More reliable notifications" = "Более надежные уведомления"; + /* item status description */ "Most likely this connection is deleted." = "Скорее всего, соединение удалено."; @@ -2985,12 +3168,18 @@ /* No comment provided by engineer. */ "Network connection" = "Интернет-соединение"; +/* No comment provided by engineer. */ +"Network decentralization" = "Децентрализация сети"; + /* snd error text */ "Network issues - message expired after many attempts to send it." = "Ошибка сети - сообщение не было отправлено после многократных попыток."; /* No comment provided by engineer. */ "Network management" = "Статус сети"; +/* No comment provided by engineer. */ +"Network operator" = "Оператор сети"; + /* No comment provided by engineer. */ "Network settings" = "Настройки сети"; @@ -3018,6 +3207,9 @@ /* No comment provided by engineer. */ "New display name" = "Новое имя"; +/* notification */ +"New events" = "Новые события"; + /* No comment provided by engineer. */ "New in %@" = "Новое в %@"; @@ -3039,6 +3231,9 @@ /* No comment provided by engineer. */ "New passphrase…" = "Новый пароль…"; +/* No comment provided by engineer. */ +"New server" = "Новый сервер"; + /* No comment provided by engineer. */ "New SOCKS credentials will be used every time you start the app." = "Новые учетные данные SOCKS будут использоваться при каждом запуске приложения."; @@ -3084,6 +3279,12 @@ /* No comment provided by engineer. */ "No info, try to reload" = "Нет информации, попробуйте перезагрузить"; +/* servers error */ +"No media & file servers." = "Нет серверов файлов и медиа."; + +/* servers error */ +"No message servers." = "Нет серверов сообщений."; + /* No comment provided by engineer. */ "No network connection" = "Нет интернет-соединения"; @@ -3102,6 +3303,18 @@ /* No comment provided by engineer. */ "No received or sent files" = "Нет полученных или отправленных файлов"; +/* servers error */ +"No servers for private message routing." = "Нет серверов для доставки сообщений."; + +/* servers error */ +"No servers to receive files." = "Нет серверов для приема файлов."; + +/* servers error */ +"No servers to receive messages." = "Нет серверов для приема сообщений."; + +/* servers error */ +"No servers to send files." = "Нет серверов для отправки файлов."; + /* copied message info in history */ "no text" = "нет текста"; @@ -3123,6 +3336,9 @@ /* No comment provided by engineer. */ "Notifications are disabled!" = "Уведомления выключены"; +/* No comment provided by engineer. */ +"Notifications privacy" = "Конфиденциальность уведомлений"; + /* No comment provided by engineer. */ "Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "Теперь админы могут:\n- удалять сообщения членов.\n- приостанавливать членов (роль \"наблюдатель\")"; @@ -3167,6 +3383,9 @@ /* No comment provided by engineer. */ "Onion hosts will not be used." = "Onion хосты не используются."; +/* No comment provided by engineer. */ +"Only chat owners can change preferences." = "Только владельцы разговора могут поменять предпочтения."; + /* No comment provided by engineer. */ "Only client devices store user profiles, contacts, groups, and messages." = "Только пользовательские устройства хранят контакты, группы и сообщения."; @@ -3215,12 +3434,18 @@ /* No comment provided by engineer. */ "Open" = "Открыть"; +/* No comment provided by engineer. */ +"Open changes" = "Открыть изменения"; + /* No comment provided by engineer. */ "Open chat" = "Открыть чат"; /* authentication reason */ "Open chat console" = "Открыть консоль"; +/* No comment provided by engineer. */ +"Open conditions" = "Открыть условия"; + /* No comment provided by engineer. */ "Open group" = "Открыть группу"; @@ -3233,6 +3458,15 @@ /* No comment provided by engineer. */ "Opening app…" = "Приложение отрывается…"; +/* No comment provided by engineer. */ +"Operator" = "Оператор"; + +/* alert title */ +"Operator server" = "Сервер оператора"; + +/* No comment provided by engineer. */ +"Or import archive file" = "Или импортировать файл архива"; + /* No comment provided by engineer. */ "Or paste archive link" = "Или вставьте ссылку архива"; @@ -3245,6 +3479,9 @@ /* No comment provided by engineer. */ "Or show this code" = "Или покажите этот код"; +/* No comment provided by engineer. */ +"Or to share privately" = "Или поделиться конфиденциально"; + /* No comment provided by engineer. */ "other" = "другое"; @@ -3386,6 +3623,9 @@ /* No comment provided by engineer. */ "Preset server address" = "Адрес сервера по умолчанию"; +/* No comment provided by engineer. */ +"Preset servers" = "Серверы по умолчанию"; + /* No comment provided by engineer. */ "Preview" = "Просмотр"; @@ -3395,6 +3635,9 @@ /* No comment provided by engineer. */ "Privacy & security" = "Конфиденциальность"; +/* No comment provided by engineer. */ +"Privacy for your customers." = "Конфиденциальность для ваших покупателей."; + /* No comment provided by engineer. */ "Privacy redefined" = "Более конфиденциальный"; @@ -3738,6 +3981,12 @@ /* chat item action */ "Reveal" = "Показать"; +/* No comment provided by engineer. */ +"Review conditions" = "Посмотреть условия"; + +/* No comment provided by engineer. */ +"Review later" = "Посмотреть позже"; + /* No comment provided by engineer. */ "Revoke" = "Отозвать"; @@ -3759,6 +4008,12 @@ /* No comment provided by engineer. */ "Safer groups" = "Более безопасные группы"; +/* No comment provided by engineer. */ +"The same conditions will apply to operator **%@**." = "Те же самые условия будут приняты для оператора **%@**."; + +/* No comment provided by engineer. */ +"The same conditions will apply to operator(s): **%@**." = "Те же самые условия будут приняты для оператора(ов): **%@**."; + /* alert button chat item action */ "Save" = "Сохранить"; @@ -4024,6 +4279,9 @@ /* No comment provided by engineer. */ "Server" = "Сервер"; +/* alert message */ +"Server added to operator %@." = "Сервер добавлен к оператору %@."; + /* No comment provided by engineer. */ "Server address" = "Адрес сервера"; @@ -4033,6 +4291,15 @@ /* srv error text. */ "Server address is incompatible with network settings." = "Адрес сервера несовместим с настройками сети."; +/* alert title */ +"Server operator changed." = "Оператор серверов изменен."; + +/* No comment provided by engineer. */ +"Server operators" = "Операторы серверов"; + +/* alert title */ +"Server protocol changed." = "Протокол сервера изменен."; + /* queue info */ "server queue info: %@\n\nlast received msg: %@" = "информация сервера об очереди: %1$@\n\nпоследнее полученное сообщение: %2$@"; @@ -4118,9 +4385,15 @@ /* No comment provided by engineer. */ "Share 1-time link" = "Поделиться одноразовой ссылкой"; +/* No comment provided by engineer. */ +"Share 1-time link with a friend" = "Поделитесь одноразовой ссылкой с другом"; + /* No comment provided by engineer. */ "Share address" = "Поделиться адресом"; +/* No comment provided by engineer. */ +"Share address publicly" = "Поделитесь адресом"; + /* alert title */ "Share address with contacts?" = "Поделиться адресом с контактами?"; @@ -4133,6 +4406,9 @@ /* No comment provided by engineer. */ "Share profile" = "Поделиться профилем"; +/* No comment provided by engineer. */ +"Share SimpleX address on social media." = "Поделитесь SimpleX адресом в социальных сетях."; + /* No comment provided by engineer. */ "Share this 1-time invite link" = "Поделиться одноразовой ссылкой-приглашением"; @@ -4178,6 +4454,12 @@ /* No comment provided by engineer. */ "SimpleX Address" = "Адрес SimpleX"; +/* No comment provided by engineer. */ +"SimpleX address and 1-time links are safe to share via any messenger." = "Адрес SimpleX и одноразовые ссылки безопасно отправлять через любой мессенджер."; + +/* No comment provided by engineer. */ +"SimpleX address or 1-time link?" = "Адрес SimpleX или одноразовая ссылка?"; + /* No comment provided by engineer. */ "SimpleX Chat security was audited by Trail of Bits." = "Безопасность SimpleX Chat была проверена Trail of Bits."; @@ -4253,6 +4535,9 @@ /* No comment provided by engineer. */ "Some non-fatal errors occurred during import:" = "Во время импорта произошли некоторые ошибки:"; +/* alert message */ +"Some servers failed the test:\n%@" = "Серверы не прошли тест:\n%@"; + /* notification title */ "Somebody" = "Контакт"; @@ -4355,6 +4640,9 @@ /* No comment provided by engineer. */ "Tap button " = "Нажмите кнопку "; +/* No comment provided by engineer. */ +"Tap Create SimpleX address in the menu to create it later." = "Нажмите Создать адрес SimpleX в меню, чтобы создать его позже."; + /* No comment provided by engineer. */ "Tap to activate profile." = "Нажмите, чтобы сделать профиль активным."; @@ -4415,6 +4703,9 @@ /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Приложение может посылать Вам уведомления о сообщениях и запросах на соединение - уведомления можно включить в Настройках."; +/* No comment provided by engineer. */ +"The app protects your privacy by using different operators in each conversation." = "Приложение улучшает конфиденциальность используя разных операторов в каждом разговоре."; + /* No comment provided by engineer. */ "The app will ask to confirm downloads from unknown file servers (except .onion)." = "Приложение будет запрашивать подтверждение загрузки с неизвестных серверов (за исключением .onion адресов)."; @@ -4424,6 +4715,9 @@ /* No comment provided by engineer. */ "The code you scanned is not a SimpleX link QR code." = "Этот QR код не является SimpleX-ccылкой."; +/* No comment provided by engineer. */ +"The connection reached the limit of undelivered messages, your contact may be offline." = "Соединение достигло предела недоставленных сообщений. Возможно, Ваш контакт не в сети."; + /* No comment provided by engineer. */ "The connection you accepted will be cancelled!" = "Подтвержденное соединение будет отменено!"; @@ -4463,6 +4757,9 @@ /* No comment provided by engineer. */ "The profile is only shared with your contacts." = "Профиль отправляется только Вашим контактам."; +/* No comment provided by engineer. */ +"The second preset operator in the app!" = "Второй оператор серверов в приложении!"; + /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Вторая галочка - знать, что доставлено! ✅"; @@ -4472,6 +4769,9 @@ /* No comment provided by engineer. */ "The servers for new connections of your current chat profile **%@**." = "Серверы для новых соединений Вашего текущего профиля чата **%@**."; +/* No comment provided by engineer. */ +"The servers for new files of your current chat profile **%@**." = "Серверы для новых файлов Вашего текущего профиля **%@**."; + /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Вставленный текст не является SimpleX-ссылкой."; @@ -4481,6 +4781,9 @@ /* No comment provided by engineer. */ "Themes" = "Темы"; +/* No comment provided by engineer. */ +"These conditions will also apply for: **%@**." = "Эти условия также будут применены к: **%@**."; + /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Установки для Вашего активного профиля **%@**."; @@ -4544,6 +4847,9 @@ /* No comment provided by engineer. */ "To make a new connection" = "Чтобы соединиться"; +/* No comment provided by engineer. */ +"To protect against your link being replaced, you can compare contact security codes." = "Чтобы защитить Вашу ссылку от замены, Вы можете сравнить код безопасности."; + /* No comment provided by engineer. */ "To protect timezone, image/voice files use UTC." = "Чтобы защитить Ваш часовой пояс, файлы картинок и голосовых сообщений используют UTC."; @@ -4556,6 +4862,9 @@ /* No comment provided by engineer. */ "To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Чтобы защитить Вашу конфиденциальность, SimpleX использует разные идентификаторы для каждого Вашeго контакта."; +/* No comment provided by engineer. */ +"To receive" = "Для получения"; + /* No comment provided by engineer. */ "To record speech please grant permission to use Microphone." = "Для записи речи, пожалуйста, дайте разрешение на использование микрофона."; @@ -4568,9 +4877,15 @@ /* No comment provided by engineer. */ "To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page." = "Чтобы показать Ваш скрытый профиль, введите его пароль в поле поиска на странице **Ваши профили чата**."; +/* No comment provided by engineer. */ +"To send" = "Для оправки"; + /* No comment provided by engineer. */ "To support instant push notifications the chat database has to be migrated." = "Для поддержки мгновенный доставки уведомлений данные чата должны быть перемещены."; +/* No comment provided by engineer. */ +"To use the servers of **%@**, accept conditions of use." = "Чтобы использовать серверы оператора **%@**, примите условия использования."; + /* No comment provided by engineer. */ "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Чтобы подтвердить end-to-end шифрование с Вашим контактом сравните (или сканируйте) код безопасности на Ваших устройствах."; @@ -4628,6 +4943,9 @@ /* rcv group event chat item */ "unblocked %@" = "%@ разблокирован"; +/* No comment provided by engineer. */ +"Undelivered messages" = "Недоставленные сообщения"; + /* No comment provided by engineer. */ "Unexpected migration state" = "Неожиданная ошибка при перемещении данных чата"; @@ -4745,12 +5063,21 @@ /* No comment provided by engineer. */ "Use .onion hosts" = "Использовать .onion хосты"; +/* No comment provided by engineer. */ +"Use %@" = "Использовать %@"; + /* No comment provided by engineer. */ "Use chat" = "Использовать чат"; /* No comment provided by engineer. */ "Use current profile" = "Использовать активный профиль"; +/* No comment provided by engineer. */ +"Use for files" = "Использовать для файлов"; + +/* No comment provided by engineer. */ +"Use for messages" = "Использовать для сообщений"; + /* No comment provided by engineer. */ "Use for new connections" = "Использовать для новых соединений"; @@ -4775,6 +5102,9 @@ /* No comment provided by engineer. */ "Use server" = "Использовать сервер"; +/* No comment provided by engineer. */ +"Use servers" = "Использовать серверы"; + /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Использовать серверы предосталенные SimpleX Chat?"; @@ -4859,9 +5189,15 @@ /* No comment provided by engineer. */ "Videos and files up to 1gb" = "Видео и файлы до 1гб"; +/* No comment provided by engineer. */ +"View conditions" = "Посмотреть условия"; + /* No comment provided by engineer. */ "View security code" = "Показать код безопасности"; +/* No comment provided by engineer. */ +"View updated conditions" = "Посмотреть измененные условия"; + /* chat feature */ "Visible history" = "Доступ к истории"; @@ -4943,6 +5279,9 @@ /* No comment provided by engineer. */ "when IP hidden" = "когда IP защищен"; +/* No comment provided by engineer. */ +"When more than one operator is enabled, none of them has metadata to learn who communicates with whom." = "Когда больше чем один оператор включен, ни один из них не видит метаданные, чтобы определить, кто соединен с кем."; + /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Когда Вы соединены с контактом инкогнито, тот же самый инкогнито профиль будет использоваться для групп с этим контактом."; @@ -5006,6 +5345,9 @@ /* No comment provided by engineer. */ "You are already connected to %@." = "Вы уже соединены с контактом %@."; +/* No comment provided by engineer. */ +"You are already connected with %@." = "Вы уже соединены с %@."; + /* No comment provided by engineer. */ "You are already connecting to %@." = "Вы уже соединяетесь с %@."; @@ -5051,6 +5393,12 @@ /* No comment provided by engineer. */ "You can change it in Appearance settings." = "Вы можете изменить это в настройках Интерфейса."; +/* No comment provided by engineer. */ +"You can configure operators in Network & servers settings." = "Вы можете настроить операторов в настройках Сеть и серверы."; + +/* No comment provided by engineer. */ +"You can configure servers via settings." = "Вы можете сконфигурировать серверы через настройки."; + /* No comment provided by engineer. */ "You can create it later" = "Вы можете создать его позже"; @@ -5075,6 +5423,9 @@ /* No comment provided by engineer. */ "You can send messages to %@ from Archived contacts." = "Вы можете отправлять сообщения %@ из Архивированных контактов."; +/* No comment provided by engineer. */ +"You can set connection name, to remember who the link was shared with." = "Вы можете установить имя соединения, чтобы запомнить кому Вы отправили ссылку."; + /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Вы можете установить просмотр уведомлений на экране блокировки в настройках."; @@ -5195,6 +5546,9 @@ /* No comment provided by engineer. */ "You will still receive calls and notifications from muted profiles when they are active." = "Вы все равно получите звонки и уведомления в профилях без звука, когда они активные."; +/* No comment provided by engineer. */ +"You will stop receiving messages from this chat. Chat history will be preserved." = "Вы прекратите получать сообщения в этом разговоре. История будет сохранена."; + /* No comment provided by engineer. */ "You will stop receiving messages from this group. Chat history will be preserved." = "Вы перестанете получать сообщения от этой группы. История чата будет сохранена."; @@ -5276,6 +5630,9 @@ /* No comment provided by engineer. */ "Your server address" = "Адрес Вашего сервера"; +/* No comment provided by engineer. */ +"Your servers" = "Ваши серверы"; + /* No comment provided by engineer. */ "Your settings" = "Настройки"; diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index 962c64b710..b496fe11b4 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -1469,24 +1469,6 @@ /* No comment provided by engineer. */ "Group links" = "ลิงค์กลุ่ม"; -/* No comment provided by engineer. */ -"Members can add message reactions." = "สมาชิกกลุ่มสามารถเพิ่มการแสดงปฏิกิริยาต่อข้อความได้"; - -/* No comment provided by engineer. */ -"Members can irreversibly delete sent messages. (24 hours)" = "สมาชิกกลุ่มสามารถลบข้อความที่ส่งแล้วอย่างถาวร"; - -/* No comment provided by engineer. */ -"Members can send direct messages." = "สมาชิกกลุ่มสามารถส่งข้อความโดยตรงได้"; - -/* No comment provided by engineer. */ -"Members can send disappearing messages." = "สมาชิกกลุ่มสามารถส่งข้อความที่จะหายไปหลังจากเวลาที่กำหนดหลังการอ่าน (disappearing messages) ได้"; - -/* No comment provided by engineer. */ -"Members can send files and media." = "สมาชิกกลุ่มสามารถส่งไฟล์และสื่อ"; - -/* No comment provided by engineer. */ -"Members can send voice messages." = "สมาชิกกลุ่มสามารถส่งข้อความเสียง"; - /* notification */ "Group message:" = "ข้อความกลุ่ม:"; @@ -1853,6 +1835,24 @@ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "สมาชิกจะถูกลบออกจากกลุ่ม - ไม่สามารถยกเลิกได้!"; +/* No comment provided by engineer. */ +"Members can add message reactions." = "สมาชิกกลุ่มสามารถเพิ่มการแสดงปฏิกิริยาต่อข้อความได้"; + +/* No comment provided by engineer. */ +"Members can irreversibly delete sent messages. (24 hours)" = "สมาชิกกลุ่มสามารถลบข้อความที่ส่งแล้วอย่างถาวร"; + +/* No comment provided by engineer. */ +"Members can send direct messages." = "สมาชิกกลุ่มสามารถส่งข้อความโดยตรงได้"; + +/* No comment provided by engineer. */ +"Members can send disappearing messages." = "สมาชิกกลุ่มสามารถส่งข้อความที่จะหายไปหลังจากเวลาที่กำหนดหลังการอ่าน (disappearing messages) ได้"; + +/* No comment provided by engineer. */ +"Members can send files and media." = "สมาชิกกลุ่มสามารถส่งไฟล์และสื่อ"; + +/* No comment provided by engineer. */ +"Members can send voice messages." = "สมาชิกกลุ่มสามารถส่งข้อความเสียง"; + /* item status text */ "Message delivery error" = "ข้อผิดพลาดในการส่งข้อความ"; diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index ec29de0cf3..99668bec79 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -2301,27 +2301,6 @@ /* No comment provided by engineer. */ "Group links" = "Grup bağlantıları"; -/* No comment provided by engineer. */ -"Members can add message reactions." = "Grup üyeleri mesaj tepkileri ekleyebilir."; - -/* No comment provided by engineer. */ -"Members can irreversibly delete sent messages. (24 hours)" = "Grup üyeleri, gönderilen mesajları kalıcı olarak silebilir. (24 saat içinde)"; - -/* No comment provided by engineer. */ -"Members can send direct messages." = "Grup üyeleri doğrudan mesajlar gönderebilir."; - -/* No comment provided by engineer. */ -"Members can send disappearing messages." = "Grup üyeleri kaybolan mesajlar gönderebilir."; - -/* No comment provided by engineer. */ -"Members can send files and media." = "Grup üyeleri dosyalar ve medya gönderebilir."; - -/* No comment provided by engineer. */ -"Members can send SimpleX links." = "Grup üyeleri SimpleX bağlantıları gönderebilir."; - -/* No comment provided by engineer. */ -"Members can send voice messages." = "Grup üyeleri sesli mesajlar gönderebilir."; - /* notification */ "Group message:" = "Grup mesajı:"; @@ -2805,6 +2784,27 @@ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Üye gruptan çıkarılacaktır - bu geri alınamaz!"; +/* No comment provided by engineer. */ +"Members can add message reactions." = "Grup üyeleri mesaj tepkileri ekleyebilir."; + +/* No comment provided by engineer. */ +"Members can irreversibly delete sent messages. (24 hours)" = "Grup üyeleri, gönderilen mesajları kalıcı olarak silebilir. (24 saat içinde)"; + +/* No comment provided by engineer. */ +"Members can send direct messages." = "Grup üyeleri doğrudan mesajlar gönderebilir."; + +/* No comment provided by engineer. */ +"Members can send disappearing messages." = "Grup üyeleri kaybolan mesajlar gönderebilir."; + +/* No comment provided by engineer. */ +"Members can send files and media." = "Grup üyeleri dosyalar ve medya gönderebilir."; + +/* No comment provided by engineer. */ +"Members can send SimpleX links." = "Grup üyeleri SimpleX bağlantıları gönderebilir."; + +/* No comment provided by engineer. */ +"Members can send voice messages." = "Grup üyeleri sesli mesajlar gönderebilir."; + /* No comment provided by engineer. */ "Menus" = "Menüler"; diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index 28cc2839e8..d470a2a1e3 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -892,7 +892,7 @@ "Change self-destruct passcode" = "Змінити пароль самознищення"; /* authentication reason */ -"Change user profiles" = "Зміна профілів користувачів"; +"Change chat profiles" = "Зміна профілів користувачів"; /* chat item text */ "changed address for you" = "змінили для вас адресу"; @@ -2424,27 +2424,6 @@ /* No comment provided by engineer. */ "Group links" = "Групові посилання"; -/* No comment provided by engineer. */ -"Members can add message reactions." = "Учасники групи можуть додавати реакції на повідомлення."; - -/* No comment provided by engineer. */ -"Members can irreversibly delete sent messages. (24 hours)" = "Учасники групи можуть безповоротно видаляти надіслані повідомлення. (24 години)"; - -/* No comment provided by engineer. */ -"Members can send direct messages." = "Учасники групи можуть надсилати прямі повідомлення."; - -/* No comment provided by engineer. */ -"Members can send disappearing messages." = "Учасники групи можуть надсилати зникаючі повідомлення."; - -/* No comment provided by engineer. */ -"Members can send files and media." = "Учасники групи можуть надсилати файли та медіа."; - -/* No comment provided by engineer. */ -"Members can send SimpleX links." = "Учасники групи можуть надсилати посилання SimpleX."; - -/* No comment provided by engineer. */ -"Members can send voice messages." = "Учасники групи можуть надсилати голосові повідомлення."; - /* notification */ "Group message:" = "Групове повідомлення:"; @@ -2934,6 +2913,27 @@ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Учасник буде видалений з групи - це неможливо скасувати!"; +/* No comment provided by engineer. */ +"Members can add message reactions." = "Учасники групи можуть додавати реакції на повідомлення."; + +/* No comment provided by engineer. */ +"Members can irreversibly delete sent messages. (24 hours)" = "Учасники групи можуть безповоротно видаляти надіслані повідомлення. (24 години)"; + +/* No comment provided by engineer. */ +"Members can send direct messages." = "Учасники групи можуть надсилати прямі повідомлення."; + +/* No comment provided by engineer. */ +"Members can send disappearing messages." = "Учасники групи можуть надсилати зникаючі повідомлення."; + +/* No comment provided by engineer. */ +"Members can send files and media." = "Учасники групи можуть надсилати файли та медіа."; + +/* No comment provided by engineer. */ +"Members can send SimpleX links." = "Учасники групи можуть надсилати посилання SimpleX."; + +/* No comment provided by engineer. */ +"Members can send voice messages." = "Учасники групи можуть надсилати голосові повідомлення."; + /* No comment provided by engineer. */ "Menus" = "Меню"; @@ -3672,7 +3672,7 @@ "Push notifications" = "Push-повідомлення"; /* No comment provided by engineer. */ -"Push Notifications" = "Push-сповіщення"; +"Push notifications" = "Push-сповіщення"; /* No comment provided by engineer. */ "Push server" = "Push-сервер"; @@ -3949,10 +3949,10 @@ "Safer groups" = "Безпечніші групи"; /* No comment provided by engineer. */ -"Same conditions will apply to operator **%@**." = "Такі ж умови діятимуть і для оператора **%@**."; +"The same conditions will apply to operator **%@**." = "Такі ж умови діятимуть і для оператора **%@**."; /* No comment provided by engineer. */ -"Same conditions will apply to operator(s): **%@**." = "Такі ж умови будуть застосовуватися до оператора(ів): **%@**."; +"The same conditions will apply to operator(s): **%@**." = "Такі ж умови будуть застосовуватися до оператора(ів): **%@**."; /* alert button chat item action */ diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index 5fac1a8577..6a924eea1f 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -2214,27 +2214,6 @@ /* No comment provided by engineer. */ "Group links" = "群组链接"; -/* No comment provided by engineer. */ -"Members can add message reactions." = "群组成员可以添加信息回应。"; - -/* No comment provided by engineer. */ -"Members can irreversibly delete sent messages. (24 hours)" = "群组成员可以不可撤回地删除已发送的消息"; - -/* No comment provided by engineer. */ -"Members can send direct messages." = "群组成员可以私信。"; - -/* No comment provided by engineer. */ -"Members can send disappearing messages." = "群组成员可以发送限时消息。"; - -/* No comment provided by engineer. */ -"Members can send files and media." = "群组成员可以发送文件和媒体。"; - -/* No comment provided by engineer. */ -"Members can send SimpleX links." = "群成员可发送 SimpleX 链接。"; - -/* No comment provided by engineer. */ -"Members can send voice messages." = "群组成员可以发送语音消息。"; - /* notification */ "Group message:" = "群组消息:"; @@ -2712,6 +2691,27 @@ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "成员将被移出群组——此操作无法撤消!"; +/* No comment provided by engineer. */ +"Members can add message reactions." = "群组成员可以添加信息回应。"; + +/* No comment provided by engineer. */ +"Members can irreversibly delete sent messages. (24 hours)" = "群组成员可以不可撤回地删除已发送的消息"; + +/* No comment provided by engineer. */ +"Members can send direct messages." = "群组成员可以私信。"; + +/* No comment provided by engineer. */ +"Members can send disappearing messages." = "群组成员可以发送限时消息。"; + +/* No comment provided by engineer. */ +"Members can send files and media." = "群组成员可以发送文件和媒体。"; + +/* No comment provided by engineer. */ +"Members can send SimpleX links." = "群成员可发送 SimpleX 链接。"; + +/* No comment provided by engineer. */ +"Members can send voice messages." = "群组成员可以发送语音消息。"; + /* No comment provided by engineer. */ "Menus" = "菜单"; diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index 1171b1d1ae..259411688c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -15,8 +15,7 @@ لا يمكن التراجع عن هذا الإجراء - سيتم فقد ملف التعريف وجهات الاتصال والرسائل والملفات الخاصة بك بشكل نهائي. هذه المجموعة لم تعد موجودة. رمز QR هذا ليس رابطًا! - الجيل القادم من -\nالرسائل الخاصة + الجيل القادم من \nالرسائل الخاصة لا يمكن التراجع عن هذا الإجراء - سيتم حذف جميع الملفات والوسائط المستلمة والمرسلة. ستبقى الصور منخفضة الدقة. لا يمكن التراجع عن هذا الإجراء - سيتم حذف الرسائل المرسلة والمستلمة قبل التحديد. قد تأخذ عدة دقائق. ينطبق هذا الإعداد على الرسائل الموجودة في ملف تعريف الدردشة الحالي الخاص بك diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index df0b3d5cd7..d56a7fe87c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1745,10 +1745,10 @@ Use %s Current conditions text couldn\'t be loaded, you can review conditions via this link: %s.]]> - %s.]]> - %s.]]> + %s.]]> + %s.]]> %s.]]> - %s.]]> + %s.]]> %s.]]> %s.]]> View conditions diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index 3c3711be25..e90dd26aff 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -462,8 +462,7 @@ Verbunden Beendet - Die nächste Generation -\ndes privaten Messagings + Die nächste Generation \ndes privaten Messagings Datenschutz neu definiert Keine Benutzerkennungen. Immun gegen Spam diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index 98f34e4c81..02f69b0550 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -737,8 +737,7 @@ ¡La conexión que has aceptado se cancelará! La base de datos no funciona correctamente. Pulsa para conocer más El mensaje será marcado como moderado para todos los miembros. - La nueva generación -\nde mensajería privada + La nueva generación \nde mensajería privada Esta acción es irreversible. Se eliminarán todos los archivos y multimedia recibidos y enviados. Las imágenes de baja resolución permanecerán. Esta acción es irreversible. Los mensajes enviados y recibidos anteriores a la selección serán eliminados. Podría tardar varios minutos. Esta configuración se aplica a los mensajes del perfil actual diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml index 480aa2e10c..5eea31e670 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml @@ -438,8 +438,7 @@ en attente de confirmation… connecté terminé - La nouvelle génération -\nde messagerie privée + La nouvelle génération \nde messagerie privée La vie privée redéfinie Aucun identifiant d\'utilisateur. Protégé du spam diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index 4f80e33166..34dc1227a7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -156,7 +156,7 @@ Az eltűnő üzenetek küldése csak abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. Hang kikapcsolva A közvetlen üzenetek küldése a tagok között engedélyezve van. - Alkalmazás + ALKALMAZÁS Hívás folyamatban Mindkét fél küldhet üzenetreakciókat. Mindkét fél tud hívásokat kezdeményezni. @@ -360,7 +360,7 @@ Letiltás az összes csoport számára Engedélyezés az összes csoport számára engedélyezve az ismerős számára - Az eltűnő üzenetek küldése le van tiltva ebben a csoportban. + Az eltűnő üzenetek küldése le van tiltva. Cím törlése %d hét Számítógép címe @@ -547,15 +547,15 @@ A galériából Engedélyezés (csoport felülírások megtartásával) Hiba az ismerős törlésekor - A csoport tagjai véglegesen törölhetik az elküldött üzeneteiket. (24 óra) + A tagok véglegesen törölhetik az elküldött üzeneteiket. (24 óra) Hiba a szerepkör megváltoztatásakor Javítás - A csoport tagjai küldhetnek eltűnő üzeneteket. + A tagok küldhetnek eltűnő üzeneteket. Kapcsolat javítása Hiba a profil létrehozásakor! Hiba a tag(ok) hozzáadásakor Fájl - A csoport tagjai küldhetnek fájlokat és médiatartalmakat. + A tagok küldhetnek fájlokat és médiatartalmakat. Törlés ennyi idő után Hiba a beállítás megváltoztatásakor Hiba a csoporthivatkozás frissítésekor @@ -565,7 +565,7 @@ Hiba a csevegési adatbázis importálásakor Hiba a kézbesítési jelentések engedélyezésekor! Hiba az XFTP-kiszolgálók mentésekor - A csoport tagjai küldhetnek egymásnak közvetlen üzeneteket. + A tagok küldhetnek egymásnak közvetlen üzeneteket. Hiba a tag eltávolításakor befejeződött A csoport üdvözlőüzenete @@ -589,7 +589,7 @@ Ismerős általi javítás nem támogatott Fájl nem található Kapcsolat bontása - A csoport tagjai üzenetreakciókat adhatnak hozzá. + A tagok reakciókat adhatnak hozzá az üzenetekhez. Adatbázis exportálása Teljes név: Tovább csökkentett akkumulátor-használat @@ -600,7 +600,7 @@ Hiba a csevegési adatbázis törlésekor Teljes hivatkozás Hiba a cím megváltoztatásakor - A csoport tagjai küldhetnek hangüzeneteket. + A tagok küldhetnek hangüzeneteket. Csoportbeállítások Hiba: %s Eltűnő üzenetek @@ -614,7 +614,7 @@ titkosítás-újraegyeztetés szükséges Rejtett csevegési profilok Fájlok és médiatartalmak - A kép mentve a „Galériába” + A kép elmentve a „Galériába” Elrejtés Azonnal A fájlok- és a médiatartalmak küldése le van tiltva! @@ -657,7 +657,7 @@ Az azonnali értesítések le vannak tiltva! Azonnali értesítések! Kép - A fájlok- és a médiatartalmak le vannak tiltva ebben a csoportban. + A fájlok- és a médiatartalmak küldése le van tiltva. Hogyan működik Elrejtés: Hiba az ismerőssel történő kapcsolat létrehozásában @@ -721,11 +721,11 @@ Új számítógép-alkalmazás! Most már az adminisztrátorok is:\n- törölhetik a tagok üzeneteit.\n- letilthatnak tagokat (megfigyelő szerepkör) meghívta őt: %1$s - Az üzenetreakciók küldése le van tiltva ebben a csoportban. + A reakciók küldése az üzenetekre le van tiltva. Nem nincs szöveg TAG - Ez később a beállításokon keresztül módosítható. + Hogyan befolyásolja az akkumulátort Új tag szerepköre Kikapcsolva Érvénytelen hivatkozás! @@ -751,7 +751,7 @@ Moderálás bekapcsolva Japán és portugál kezelőfelület - Az üzenetek végleges törlése le van tiltva ebben a csoportban. + Az üzenetek végleges törlése le van tiltva. %s nevű hordozható eszközzel]]> hónap Üzenetvázlat @@ -793,7 +793,7 @@ Hordozható eszköz társítása Értesítési szolgáltatás Csak a csoporttulajdonosok engedélyezhetik a hangüzenetek küldését. - 2 rétegű végpontok közötti titkosítással küldött üzeneteket.]]> + A felhasználói profilok, névjegyek, csoportok és üzenetek csak az eszközön kerülnek tárolásra a kliensen belül. Érvénytelen átköltöztetési visszaigazolás Csak a csoporttulajdonosok módosíthatják a csoportbeállításokat. Nincsenek előzmények @@ -801,7 +801,7 @@ Megjelölés olvasottként ÉLŐ Megjelölés olvasatlanként - Több + Továbbiak Bejelentkezés hitelesítőadatokkal érvénytelen üzenet formátum Csatlakozás @@ -1163,7 +1163,7 @@ A hangüzenetek küldése le van tiltva. Ismerős nevének beállítása Csak Ön tud eltűnő üzeneteket küldeni. - Média megosztása… + Médiatartalom megosztása… Ön: %1$s Beállítások Színek visszaállítása @@ -1272,7 +1272,7 @@ Figyelmeztetés: néhány adat elveszhet! Koppintson ide az új csevegés indításához Várakozás a számítógépre… - A privát üzenetküldés\nkövetkező generációja + Az üzenetküldés jövője Hálózati beállítások megváltoztatása? Várakozás a hordozható eszköz társítására: Biztonságos kapcsolat hitelesítése @@ -1287,7 +1287,7 @@ Az új üzenetek rendszeresen letöltésre kerülnek az alkalmazás által – naponta néhány százalékot használ az akkumulátorból. Az alkalmazás nem használ push-értesítéseket – az eszközről származó adatok nem kerülnek elküldésre a kiszolgálóknak. Számítógép címének beillesztése kapcsolattartási cím-hivatkozáson keresztül - SimpleX-háttérszolgáltatást használja - az akkumulátornak csak néhány százalékát használja naponta.]]> + a SimpleX a háttérben fut a push értesítések használata helyett.]]> Az ismerősének online kell lennie ahhoz, hogy a kapcsolat létrejöjjön.\nVisszavonhatja ezt az ismerőskérelmet és eltávolíthatja az ismerőst (ezt később ismét megpróbálhatja egy új hivatkozással). A jelszó nem található a Keystore-ban, ezért kézzel szükséges megadni. Ez akkor történhetett meg, ha visszaállította az alkalmazás adatait egy biztonságimentési eszközzel. Ha nem így történt, akkor lépjen kapcsolatba a fejlesztőkkel. Az ismerősei továbbra is kapcsolódva maradnak. @@ -1306,7 +1306,7 @@ A profilja elküldésre kerül az ismerőse számára, akitől ezt a hivatkozást kapta. Az alkalmazás 1 perc után bezárható a háttérben. meghívást kapott a csoportba - engedélyezze a SimpleX háttérben történő futását a következő párbeszédpanelen. Ellenkező esetben az értesítések letiltásra kerülnek.]]> + Engedélyezze a következő párbeszédpanelen az azonnali értesítések fogadásához.]]> A kiszolgálónak engedélyre van szüksége a sorbaállítás létrehozásához, ellenőrizze jelszavát Kapcsolódni fog a csoport összes tagjához. Lehetséges, hogy a kiszolgáló címében szereplő tanúsítvány-ujjlenyomat helytelen @@ -1349,7 +1349,7 @@ Inkognitóprofilt használ ehhez a csoporthoz - fő profilja megosztásának elkerülése érdekében a meghívók küldése le van tiltva Átvitel-izoláció Akkor lesz kapcsolódva, ha a kapcsolatkérése elfogadásra kerül, várjon, vagy ellenőrizze később! - A hangüzenetek küldése le van tiltva ebben a csoportban. + A hangüzenetek küldése le van tiltva. Alkalmazás akkumulátor-használata / Korlátlan módot az alkalmazás beállításaiban.]]> Biztonságos kvantumrezisztens-protokollon keresztül. - 5 perc hosszúságú hangüzenetek.\n- egyéni üzenet-eltűnési időkorlát.\n- előzmények szerkesztése. @@ -1363,7 +1363,7 @@ A profilja az eszközén van tárolva és csak az ismerőseivel kerül megosztásra. A SimpleX-kiszolgálók nem láthatják a profilját. Ön megváltoztatta %s szerepkörét erre: %s Csoportmeghívó elutasítva - Az adatvédelem érdekében (a más csevegési platformokon megszokott felhasználó-azonosítók helyett) a SimpleX csak az üzenetek sorbaállításához használ azonosítókat, az összes ismerőséhez különbözőt. + Az Ön adatainak védelme érdekében a SimpleX külön üzenet-azonosítókat használ minden egyes kapcsolatához. (a megosztáshoz az ismerősével) Csoportmeghívó elküldve Átvitel-izoláció módjának frissítése? @@ -1450,7 +1450,7 @@ Az utolsó üzenet tervezetének megőrzése a mellékletekkel együtt. A mentett WebRTC ICE-kiszolgálók eltávolításra kerülnek. A kézbesítési jelentések engedélyezve vannak %d csoportban - A szerepkör meg fog változni erre: %s. A csoportban az összes tag értesítve lesz. + A szerepkör meg fog változni erre: %s. A csoport tagjai értesítést fognak kapni. Profil és kiszolgálókapcsolatok Egy üzenetküldő- és alkalmazásplatform, amely védi az adatait és biztonságát. A profil aktiválásához koppintson az ikonra. @@ -1685,23 +1685,23 @@ Wi-Fi továbbított A SimpleX-hivatkozások küldése le van tiltva - A csoport tagjai küldhetnek SimpleX-hivatkozásokat. + A tagok küldhetnek SimpleX-hivatkozásokat. tulajdonosok adminisztrátorok összes tag SimpleX-hivatkozások A hangüzenetek küldése le van tiltva - A SimpleX-hivatkozások küldése le van tiltva ebben a csoportban. + A SimpleX-hivatkozások küldése le van tiltva. A SimpleX-hivatkozások küldése le van tiltva A fájlok- és médiatartalmak nincsenek engedélyezve A SimpleX-hivatkozások küldése engedélyezve van. Számukra engedélyezve: mentett - mentve innen: %s + elmentve innen: %s Továbbítva innen: A címzett(ek) nem látja(k), hogy kitől származik ez az üzenet. Mentett - Mentve innen: + Elmentve innen: Letöltés Továbbítás Továbbított @@ -1790,7 +1790,7 @@ További kiemelés 2 Alkalmazás téma Perzsa kezelőfelület - Védje IP-címét az ismerősei által kiválasztott üzenet-közvetítő-kiszolgálókkal szemben.\nEngedélyezze a „Beállítások / Hálózat és kiszolgálók” menüben. + Védje IP-címét az ismerősei által kiválasztott üzenet-közvetítő-kiszolgálókkal szemben.\nEngedélyezze a *Hálózat és kiszolgálók* menüben. Ismeretlen kiszolgálókról származó fájlok megerősítése. Javított üzenetkézbesítés Alkalmazás témájának visszaállítása @@ -1938,7 +1938,7 @@ A(z) %1$s célkiszolgáló verziója nem kompatibilis a(z) %2$s továbbító kiszolgálóval. A(z) %1$s továbbító-kiszolgáló nem tudott csatlakozni a(z) %2$s célkiszolgálóhoz. Próbálja meg később. A(z) %1$s célkiszolgáló címe nem kompatibilis a(z) %2$s továbbító-kiszolgáló beállításaival. - Média elhomályosítása + Médiatartalom elhomályosítása Közepes Kikapcsolva Enyhe @@ -1966,7 +1966,7 @@ Beszélgetés megtartása Biztosan törli az ismerőst? kapcsolódás - Könnyen elérhető eszköztár + Könnyen elérhető alkalmazás-eszköztárak Törlés értesítés nélkül Beállítások keresés @@ -2108,7 +2108,7 @@ Vagy a privát megosztáshoz SimpleX-cím vagy egyszer használható meghívó-hivatkozás? Egyszer használható meghívó-hivatkozás létrehozása - Üzemeltetők kiválasztása + Kiszolgáló-üzemeltetők Hálózati üzemeltetők Amikor egynél több hálózati üzemeltető van engedélyezve, akkor az alkalmazás minden egyes beszélgetéshez a különböző üzemeltetők kiszolgálóit használja. Ha például a SimpleX Chat kiszolgálón keresztül fogadja az üzeneteket, az alkalmazás a Flux egyik kiszolgálóját használja a privát útválasztáshoz. @@ -2170,4 +2170,39 @@ Az Ön jelenlegi csevegőprofiljához tartozó új fájlok kiszolgálói Vagy archívumfájl importálása Távoli hordozható eszközök + Xiaomi eszközök: engedélyezze az automatikus indítást a rendszerbeállításokban, hogy az értesítések működjenek.]]> + A küldéshez másolhatja és csökkentheti az üzenet méretét. + Adja hozzá csapattagjait a beszélgetésekhez. + Üzleti cím + végpontok közötti titkosítással, a közvetlen üzenetek továbbá kvantumrezisztens titkosítással is rendelkeznek.]]> + Hogyan segíti az adatvédelmet + Nincs háttérszolgáltatás + Értesítések és akkumulátor + Az alkalmazás mindig fut a háttérben + Csevegés elhagyása? + Ön nem fog több üzenetet kapni ebből a csevegésből, de a csevegés előzményei megmaradnak. + Csevegés törlése + Meghívás a csevegésbe + Barátok hozzáadása + Csapattagok hozzáadása + A csevegés minden tag számára törlésre kerül - ezt a műveletet nem lehet visszavonni! + A csevegés törlésre kerül az Ön számára - ezt a műveletet nem lehet visszavonni! + Csevegés törlése? + Csevegés elhagyása + Csak a csevegés tulajdonosai módosíthatják a beállításokat. + Könnyen elérhető csevegési eszköztár + A tag el lesz távolítva a csevegésből - ezt a műveletet nem lehet visszavonni! + Csevegés + A szerepkör meg fog változni a következőre: %s. A csevegés tagjai értesítést fognak kapni. + Az Ön csevegési profilja el lesz küldve a csevegésben résztvevő tagok számára + A tagok közötti közvetlen üzenetek le vannak tiltva. + Üzleti csevegések + Az Ön ügyfeleinek adatvédelme. + %1$s.]]> + A csevegés már létezik! + Csökkentse az üzenet méretét, és küldje el újra. + Üzenetek ellenőrzése 10 percenként + Az üzenet túl nagy! + Csökkentse az üzenet méretét vagy távolítsa el a médiát, és küldje el újra. + A tagok közötti közvetlen üzenetek le vannak tiltva ebben a csevegésben. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml index 1050f1d575..eeedcaa450 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml @@ -1294,4 +1294,57 @@ Hapus anggota? Hapus anggota Pesan tersimpan + Tak dapat memanggil anggota grup + %s.]]> + %s.]]> + Operator + Gunakan server + Gunakan %s + Tak dapat kirim pesan ke anggota grup + Pesan sambutan + Simpan pesan sambutan? + Perbaikan tidak didukung oleh anggota grup + Buat grup rahasia + Negosiasi ulang enkripsi + Masukkan nama grup: + Pratinjau + Kirim pesan untuk aktifkan panggilan. + Kontak dihapus. + Tak dapat memanggil kontak + Perbaiki + Sepenuhnya terdesentralisasi – hanya terlihat oleh anggota. + Perbaiki koneksi + Perbaiki koneksi? + Tinjau ketentuan + Server prasetel + Ketentuan diterima + Ketentuan akan otomatis diterima untuk operator yang diaktifkan pada: %s. + Anda perlu izinkan kontak Anda agar dapat memanggilnya. + Pesan terlalu besar + Masukkan pesan sambutan… + Simpan dan perbarui profil grup + Pesan sambutan terlalu panjang + Perbaikan tidak didukung oleh kontak + Obrolan + Terima kondisi + SERVER + Buat grup + Nama lengkap grup: + Simpan profil grup + Peramban + Server Anda + Gagal simpan profil grup + %s server + Operator jaringan + Ketentuan diterima pada: %s. + Ketentuan akan diterima pada: %s. + Koneksi + Rol + Ganti rol + Profil obrolan Anda akan dikirim ke anggota grup + Profil obrolan Anda akan dikirim ke anggota obrolan + Profil grup disimpan di perangkat anggota, bukan di server. + langsung + Kirim via + Terima via \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index 2f41824bd1..92c0105d09 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -211,8 +211,8 @@ A meno che il tuo contatto non abbia eliminato la connessione o che questo link non sia già stato usato, potrebbe essere un errore; per favore segnalalo. \nPer connetterti, chiedi al tuo contatto di creare un altro link di connessione e controlla di avere una connessione di rete stabile. Probabilmente l\'impronta del certificato nell\'indirizzo del server è sbagliata - servizio SimpleX in secondo piano; usa una piccola percentuale di batteria al giorno.]]> - consenti a SimpleX di funzionare in secondo piano nella prossima schermata. Altrimenti le notifiche saranno disattivate.]]> + SimpleX funziona in secondo piano invece di usare le notifiche push.]]> + Consentilo nella prossima schermata per ricevere le notifiche immediatamente.]]> Servizio SimpleX Chat Servizio in secondo piano sempre attivo. Le notifiche verranno mostrate appena i messaggi saranno disponibili. SimpleX Lock @@ -478,7 +478,7 @@ %do %d ora %d ore - I messaggi a tempo sono vietati in questo gruppo. + I messaggi a tempo sono vietati. %dm %d min %d mese @@ -490,10 +490,10 @@ %d settimana %d settimane Link del gruppo - I membri del gruppo possono eliminare irreversibilmente i messaggi inviati. (24 ore) - I membri del gruppo possono inviare messaggi diretti. - I membri del gruppo possono inviare messaggi a tempo. - I membri del gruppo possono inviare messaggi vocali. + I membri possono eliminare irreversibilmente i messaggi inviati. (24 ore) + I membri possono inviare messaggi diretti. + I membri possono inviare messaggi a tempo. + I membri possono inviare messaggi vocali. Confronta i codici di sicurezza con i tuoi contatti. Messaggi a tempo Nascondi la schermata dell\'app nelle app recenti. @@ -648,9 +648,9 @@ Chiamata in arrivo Videochiamata in arrivo Istantaneo - Può essere cambiato in seguito via impostazioni. + Come influisce sulla batteria Crea una connessione privata - crittografia end-to-end a 2 livelli.]]> + Solo i dispositivi client memorizzano i profili utente, i contatti, i gruppi e i messaggi. Chiunque può installare i server. Incolla il link che hai ricevuto Sei tu a decidere chi può connettersi. @@ -660,9 +660,8 @@ repository GitHub.]]> Rifiuta Nessun identificatore utente. - La nuova generazione -\ndi messaggistica privata - Per proteggere la privacy, invece degli ID utente usati da tutte le altre piattaforme, SimpleX dispone di identificatori per le code dei messaggi, separati per ciascuno dei tuoi contatti. + Il futuro dei messaggi + Per proteggere la tua privacy, SimpleX usa ID separati per ciascuno dei tuoi contatti. Usa la chat videochiamata videochiamata (non crittografata e2e) @@ -843,7 +842,7 @@ Le tue preferenze Configurazione del server migliorata Eliminazione irreversibile del messaggio - L\'eliminazione irreversibile dei messaggi è vietata in questo gruppo. + L\'eliminazione irreversibile dei messaggi è vietata. Max 40 secondi, ricevuto istantaneamente. Novità nella %s Solo tu puoi inviare messaggi vocali. @@ -856,7 +855,7 @@ La sicurezza di SimpleX Chat è stata verificata da Trail of Bits. Messaggi vocali I messaggi vocali sono vietati in questa chat. - I messaggi vocali sono vietati in questo gruppo. + I messaggi vocali sono vietati. Novità Con messaggio di benvenuto facoltativo. I tuoi contatti possono consentire l\'eliminazione completa dei messaggi. @@ -1171,7 +1170,7 @@ Cambia codice di autodistruzione Reazioni ai messaggi Le reazioni ai messaggi sono vietate in questa chat. - Le reazioni ai messaggi sono vietate in questo gruppo. + Le reazioni ai messaggi sono vietate. Solo tu puoi aggiungere reazioni ai messaggi. Proibisci le reazioni ai messaggi. Proibisci le reazioni ai messaggi. @@ -1180,7 +1179,7 @@ Solo il tuo contatto può aggiungere reazioni ai messaggi. Consenti ai tuoi contatti di aggiungere reazioni ai messaggi. Consenti reazioni ai messaggi solo se il tuo contatto le consente. - I membri del gruppo possono aggiungere reazioni ai messaggi. + I membri possono aggiungere reazioni ai messaggi. 30 secondi Invia Invia messaggio a tempo @@ -1244,10 +1243,10 @@ Errore nell\'interruzione del cambio di indirizzo Solo i proprietari del gruppo possono attivare file e contenuti multimediali. File e multimediali - I membri del gruppo possono inviare file e contenuti multimediali. + I membri possono inviare file e contenuti multimediali. Proibisci l\'invio di file e contenuti multimediali. Il cambio di indirizzo verrà interrotto. Verrà usato il vecchio indirizzo di ricezione. - File e contenuti multimediali sono vietati in questo gruppo. + File e contenuti multimediali sono vietati. File e contenuti multimediali vietati! Off Nessuna chat filtrata @@ -1723,11 +1722,11 @@ Consenti di inviare link di SimpleX. Vieta l\'invio di link di SimpleX Attivo per - I membri del gruppo possono inviare link di Simplex. + I membri possono inviare link di Simplex. proprietari amministratori tutti i membri - I link di SimpleX sono vietati in questo gruppo. + I link di SimpleX sono vietati. salvato Salvato da Inoltra messaggio… @@ -1999,7 +1998,7 @@ Nessun contatto filtrato Incolla link I tuoi contatti - Barra degli strumenti di chat accessibile + Barre degli strumenti dell\'app accessibili Invita Consentire le chiamate? Chiamate proibite! @@ -2135,7 +2134,7 @@ Operatori di rete Quando più di un operatore di rete è attivato, l\'app userà i server di diversi operatori per ogni conversazione. Puoi configurare gli operatori nelle impostazioni di rete e server. - Scegli gli operatori + Operatori del server Seleziona gli operatori di rete da usare. Continua Aggiorna @@ -2213,4 +2212,39 @@ Condividi indirizzo SimpleX sui social media. O importa file archivio Telefoni remoti + I messaggi diretti tra i membri sono vietati in questa chat. + Dispositivi Xiaomi: attiva l\'avvio automatico nelle impostazioni di sistema per fare funzionare le notifiche.]]> + Aggiungi i membri del tuo team alle conversazioni. + Indirizzo di lavoro + cifrati end-to-end, con sicurezza quantistica nei messaggi diretti.]]> + Controlla i messaggi ogni 10 minuti + Come aiuta la privacy + Invita in chat + Aggiungi membri del team + Chat + I messaggi diretti tra i membri sono vietati. + La chat esiste già! + %1$s.]]> + Uscire dalla chat? + La chat verrà eliminata solo per te, non è reversibile! + Esci dalla chat + Chat di lavoro + La chat verrà eliminata per tutti i membri, non è reversibile! + Aggiungi amici + L\'app funziona sempre in secondo piano + Elimina chat + Eliminare la chat? + Il messaggio è troppo grande! + Riduci la dimensione del messaggio e invialo di nuovo. + Riduci la dimensione del messaggio o rimuovi i media e invialo di nuovo. + Nessun servizio in secondo piano + Il membro verrà rimosso dalla chat, non è reversibile! + Privacy per i tuoi clienti. + Barra degli strumenti di chat accessibile + Solo i proprietari della chat possono modificarne le preferenze. + Notifiche e batteria + Puoi copiare e ridurre la dimensione del messaggio per inviarlo. + Il ruolo verrà cambiato in %s. Verrà notificato a tutti nella chat. + Il tuo profilo di chat verrà inviato ai membri della chat + Non riceverai più messaggi da questa chat. La cronologia della chat verrà conservata. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index 26ec40da60..1a5eaa403d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -19,7 +19,7 @@ Toegang tot de servers via SOCKS proxy op poort %d\? De proxy moet worden gestart voordat u deze optie inschakelt. Kan geen contacten uitnodigen! Sta het verzenden van directe berichten naar leden toe. - Sta toe om verzonden berichten onomkeerbaar te verwijderen. (24 uur) + Sta toe om verzonden berichten definitief te verwijderen. (24 uur) Sta toe om spraak berichten te verzenden. Chat is actief Wissen @@ -57,7 +57,7 @@ Contact verzoeken automatisch accepteren vetgedrukt Bijvoegen - Sta het onomkeerbaar verwijderen van berichten alleen toe als uw contact dit toestaat. (24 uur) + Sta het definitief verwijderen van berichten alleen toe als uw contact dit toestaat. (24 uur) Sta toe om verdwijnende berichten te verzenden. Sta toe dat uw contacten spraak berichten verzenden. Al uw contacten blijven verbonden. @@ -74,7 +74,7 @@ Alle berichten worden verwijderd, dit kan niet ongedaan worden gemaakt! De berichten worden ALLEEN voor jou verwijderd. Sta verdwijnende berichten alleen toe als uw contact dit toestaat. Sta spraak berichten alleen toe als uw contact ze toestaat. - Laat uw contacten verzonden berichten onomkeerbaar verwijderen. (24 uur) + Laat uw contacten verzonden berichten definitief verwijderen. (24 uur) Sta toe dat uw contacten verdwijnende berichten verzenden. altijd Geluid uit @@ -95,7 +95,7 @@ Batterijoptimalisatie is actief, waardoor achtergrondservice en periodieke verzoeken om nieuwe berichten worden uitgeschakeld. Je kunt ze weer inschakelen via instellingen. Het beste voor de batterij. U ontvangt alleen meldingen wanneer de app wordt uitgevoerd (GEEN achtergrondservice).]]> Het kan worden uitgeschakeld via instellingen, meldingen worden nog steeds weergegeven terwijl de app actief is.]]> - Zowel u als uw contact kunnen verzonden berichten onomkeerbaar verwijderen. (24 uur) + Zowel u als uw contact kunnen verzonden berichten definitief verwijderen. (24 uur) Zowel jij als je contact kunnen verdwijnende berichten sturen. Zowel jij als je contact kunnen spraak berichten verzenden. Let op: u kunt het wachtwoord NIET herstellen of wijzigen als u het kwijt raakt.]]> @@ -254,12 +254,12 @@ Apparaatverificatie is uitgeschakeld. SimpleX Vergrendelen uitschakelen. Vul uw naam in: Apparaatverificatie is niet ingeschakeld. Je kunt SimpleX Vergrendelen inschakelen via Instellingen zodra je apparaatverificatie hebt ingeschakeld. - Directe berichten tussen leden zijn verboden in deze groep. + Directe berichten tussen leden zijn niet toegestaan in deze groep. %d bestand(en) met een totale grootte van %s %d uur Uitzetten Verdwijnende berichten - Verdwijnende berichten zijn verboden in dit gesprek. + Verdwijnende berichten zijn niet toegestaan in dit gesprek. SimpleX Vergrendelen uitschakelen Verdwijnende berichten Verbinding verbreken @@ -272,7 +272,7 @@ %ds Verwijder contact Server verwijderen - Verdwijnende berichten zijn verboden in deze groep. + Verdwijnende berichten zijn niet toegestaan. %d sec %dm %dmth @@ -338,9 +338,9 @@ ingeschakeld ingeschakeld voor contact voor u ingeschakeld - Groepsleden kunnen verzonden berichten onomkeerbaar verwijderen. (24 uur) - Groepsleden kunnen directe berichten sturen - Groepsleden kunnen spraak berichten verzenden. + Leden kunnen verzonden berichten definitief verwijderen. (24 uur) + Leden kunnen directe berichten sturen. + Leden kunnen spraak berichten verzenden. Per chatprofiel (standaard) of per verbinding (BETA). Verschillende namen, avatars en transportisolatie. Franse interface @@ -373,7 +373,7 @@ Video Fout bij opslaan van ICE servers geëindigd - Groepsleden kunnen verdwijnende berichten sturen. + Leden kunnen verdwijnende berichten sturen. %d week %dw %d weken @@ -396,7 +396,7 @@ Afbeelding verzonden Live bericht! Als je een uitnodiging link voor SimpleX Chat hebt ontvangen, kun je deze in je browser openen: - Dit kan later worden gewijzigd via instellingen. + Hoe dit de batterij beïnvloedt Deelnemen aan groep\? Nodig leden uit Geen contacten geselecteerd @@ -439,7 +439,7 @@ Groep verlaten Lokale naam Alleen lokale profielgegevens - Het onomkeerbaar verwijderen van berichten is verboden in deze groep. + Het definitief verwijderen van berichten is niet toegestaan. App scherm verbergen in de recente apps. Incognito modus Berichten @@ -458,7 +458,7 @@ Meer verbeteringen volgen snel! Nodig leden uit Verberg contact en bericht - op de achtergrond uitvoeren. Anders worden de meldingen uitgeschakeld.]]> + Sta dit toe in het volgende dialoogvenster om direct meldingen te ontvangen.]]> Als u ervoor kiest om te weigeren, wordt de afzender NIET op de hoogte gesteld. Onmiddellijk heeft %1$s uitgenodigd @@ -508,7 +508,7 @@ Nee Immuun voor spam Maak een privéverbinding - Het onomkeerbaar verwijderen van berichten is verboden in dit gesprek. + Het definitief verwijderen van berichten is niet toegestaan in dit gesprek. Nieuw in %s Max 40 seconden, direct ontvangen. Verbeterde serverconfiguratie @@ -552,7 +552,7 @@ aan Alleen jij kunt verdwijnende berichten verzenden. Alleen uw contact kan verdwijnende berichten verzenden. - Alleen u kunt berichten onomkeerbaar verwijderen (uw contact kan ze markeren voor verwijdering). (24 uur) + Alleen u kunt berichten definitief verwijderen (uw contact kan ze markeren voor verwijdering). (24 uur) voorgesteld %s: %2s Oud database archief Voer het juiste huidige wachtwoord in. @@ -581,7 +581,7 @@ Alleen uw contact kan berichten onherroepelijk verwijderen (u kunt ze markeren voor verwijdering). (24 uur) Alleen jij kunt spraak berichten verzenden. Alleen uw contact kan spraak berichten verzenden. - Verbied het onomkeerbaar verwijderen van berichten. + Verbied het definitief verwijderen van berichten. voorgesteld %s Sla het wachtwoord veilig op. Als u deze kwijtraakt, heeft u GEEN toegang tot de chats. Bewaar het wachtwoord veilig, u kunt deze NIET wijzigen als u deze kwijtraakt. @@ -590,7 +590,7 @@ Oproep in behandeling Het openen van de link in de browser kan de privacy en beveiliging van de verbinding verminderen. Niet vertrouwde SimpleX links worden rood weergegeven. Werk de app bij en neem contact op met de ontwikkelaars. - 2-laags end-to-end codering.]]> + Alleen clientapparaten slaan gebruikersprofielen, contacten, groepen en berichten op. De afzender heeft mogelijk het verbindingsverzoek verwijderd. Schakel SimpleX Vergrendelen in om uw informatie te beschermen. \nU wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingeschakeld. @@ -611,7 +611,7 @@ Uw profiel, contacten en afgeleverde berichten worden op uw apparaat opgeslagen. beginnen… Video aan - Deze actie kan niet ongedaan worden gemaakt. Uw profiel, contacten, berichten en bestanden gaan onomkeerbaar verloren. + Deze actie kan niet ongedaan worden gemaakt. Uw profiel, contacten, berichten en bestanden gaan definitief verloren. Deze instelling is van toepassing op berichten in uw huidige chatprofiel bijgewerkt groep profiel verwijderd @@ -710,10 +710,9 @@ U kunt markdown gebruiken voor opmaak in berichten: geweigerde oproep geheim - De volgende generatie -\nprivéberichten + De toekomst van berichtenuitwisseling wachten op antwoord… - Om de privacy te beschermen, heeft SimpleX in plaats van gebruikers-ID\'s die door alle andere platforms worden gebruikt, ID\'s voor berichten wachtrijen, afzonderlijk voor elk van uw contacten. + Om uw privacy te beschermen, gebruikt SimpleX voor elk van uw contacten afzonderlijke ID\'s. Gebruik chat Wanneer de app actief is video gesprek (niet e2e versleuteld) @@ -764,7 +763,7 @@ Volledig gedecentraliseerd – alleen zichtbaar voor leden. Timeout van TCP-verbinding Spraak berichten - Spraak berichten zijn verboden in dit gesprek. + Spraak berichten zijn niet toegestaan in dit gesprek. Verbied het verzenden van verdwijnende berichten. Verminderd batterijgebruik Om de tijdzone te beschermen, gebruiken afbeeldings-/spraakbestanden UTC. @@ -804,7 +803,7 @@ Kleuren resetten Systeem ja - gekregen, verboden + gekregen, niet toegestaan Jouw voorkeuren Stel 1 dag in Wat is er nieuw @@ -824,7 +823,7 @@ Spraakbericht… Spraakbericht (%1$s) Contactnaam instellen… - Spraak berichten verboden! + Spraak berichten niet toegestaan! Resetten Verstuur Stuur een live bericht, het wordt bijgewerkt voor de ontvanger(s) terwijl u het typt @@ -886,21 +885,20 @@ Deze string is geen verbinding link! Deze actie kan niet ongedaan worden gemaakt, de berichten die eerder zijn verzonden en ontvangen dan geselecteerd, worden verwijderd. Het kan enkele minuten duren. Het ontvangstadres wordt gewijzigd naar een andere server. Adres wijziging wordt voltooid nadat de afzender online is. - SimpleX achtergrond service - deze gebruikt een paar procent van de batterij per dag.]]> + draait SimpleX op de achtergrond in plaats van pushmeldingen te gebruiken.]]> Jij staat toe Je bent uitgenodigd voor de groep verbinding maken met SimpleX Chat ontwikkelaars om vragen te stellen en updates te ontvangen.]]> Tenzij uw contact de verbinding heeft verwijderd of deze link al is gebruikt, kan het een bug zijn. Meld het alstublieft. \nOm verbinding te maken, vraagt u uw contact om een andere verbinding link te maken en te controleren of u een stabiele netwerkverbinding heeft. SimpleX Chat servers gebruiken\? - Spraak berichten zijn verboden in deze groep. + Spraak berichten zijn niet toegestaan. Welkom %1$s! U kunt de chat starten via app Instellingen / Database of door de app opnieuw op te starten. je hebt het adres gewijzigd voor %s je hebt %1$s verwijderd Je contact heeft een bestand verzonden dat groter is dan de momenteel ondersteunde maximale grootte (%1$s). - Uw huidige chatdatabase wordt VERWIJDERD en VERVANGEN door de geïmporteerde. -\nDeze actie kan niet ongedaan worden gemaakt. Uw profiel, contacten, berichten en bestanden gaan onomkeerbaar verloren. + Uw huidige chatdatabase wordt VERWIJDERD en VERVANGEN door de geïmporteerde. \nDeze actie kan niet ongedaan worden gemaakt. Uw profiel, contacten, berichten en bestanden gaan definitief verloren. Je probeert een contact met wie je een incognito profiel hebt gedeeld uit te nodigen voor de groep waarin je je hoofdprofiel gebruikt Uw profiel wordt op uw apparaat opgeslagen en alleen gedeeld met uw contacten. SimpleX servers kunnen uw profiel niet zien. U moet zich authenticeren wanneer u de app na 30 seconden op de achtergrond start of hervat. @@ -1090,7 +1088,7 @@ " \nBeschikbaar in v5.1" Audio/video gesprekken verbieden. - Audio/video gesprekken zijn verboden. + Audio/video gesprekken zijn niet toegestaan. Snel en niet wachten tot de afzender online is! App toegangscode Stel het in in plaats van systeemverificatie. @@ -1165,19 +1163,19 @@ Zelfvernietigings wachtwoord ingeschakeld! De app-toegangscode wordt vervangen door een zelfvernietigings wachtwoord. Als u uw zelfvernietigings wachtwoord invoert tijdens het openen van de app: - Als u deze toegangscode invoert bij het openen van de app, worden alle app-gegevens onomkeerbaar verwijderd! + Als u deze toegangscode invoert bij het openen van de app, worden alle app-gegevens definitief verwijderd! Toegangscode instellen Bericht reacties verbieden. Alleen jij kunt bericht reacties toevoegen. - Reacties op berichten zijn verboden in deze groep. + Reacties op berichten zijn niet toegestaan. Berichten reacties verbieden. Sta bericht reacties alleen toe als uw contact dit toestaat. Sta uw contactpersonen toe om bericht reacties toe te voegen. Sta bericht reacties toe. - Groepsleden kunnen bericht reacties toevoegen. + Leden kunnen reacties op berichten toevoegen. Zowel u als uw contact kunnen bericht reacties toevoegen. Reacties op berichten - Reacties op berichten zijn verboden in deze chat. + Reacties op berichten zijn niet toegestaan in deze chat. Alleen uw contact kan bericht reacties toevoegen. dagen uren @@ -1241,14 +1239,14 @@ Afbreken Geen gefilterde chats Alleen groep eigenaren kunnen bestanden en media inschakelen. - Bestanden en media zijn verboden in deze groep. + Bestanden en media zijn niet toegestaan. Favoriet - Bestanden en media verboden! + Bestanden en media niet toegestaan! Niet favoriet Bestanden en media Verbied het verzenden van bestanden en media. Sta toe om bestanden en media te verzenden. - Groepsleden kunnen bestanden en media verzenden. + Leden kunnen bestanden en media verzenden. Zoeken Uit Protocol timeout per KB @@ -1722,10 +1720,10 @@ beheerders alle leden eigenaren - SimpleX-links zijn in deze groep verboden. + SimpleX-links zijn niet toegestaan. Ingeschakeld voor Sta het verzenden van SimpleX-links toe. - Groepsleden kunnen SimpleX-links verzenden. + Leden kunnen SimpleX-links verzenden. opgeslagen opgeslagen van %s Doorsturen @@ -1988,7 +1986,7 @@ Oproepen toestaan? bellen Kan geen groepslid bellen - Bellen verboden! + Bellen niet toegestaan! Contact verwijderen bevestigen? Gesprek verwijderd! Verwijderen zonder melding @@ -2009,7 +2007,7 @@ open Plak de link Uitnodiging - Toegankelijke chatwerkbalk + Bereikbare app-toolbars Vraag uw contactpersoon om oproepen in te schakelen. Geen gefilterde contacten Selecteer @@ -2144,7 +2142,7 @@ Of om privé te delen Adres instellingen Eenmalige link maken - Operators kiezen + Serverbeheerders Voor ingeschakelde operators worden de voorwaarden na 30 dagen geaccepteerd. Netwerkbeheerders Later beoordelen @@ -2209,4 +2207,40 @@ Om te verzenden U kunt servers configureren via instellingen. Of importeer archiefbestand + Directe berichten tussen leden zijn in deze chat niet toegestaan. + Externe mobiele telefoons + Xiaomi-apparaten: schakel Automatisch starten in de systeeminstellingen in om meldingen te laten werken.]]> + Bericht is te groot! + Verklein het bericht en verstuur het opnieuw. + U kunt het bericht kopiëren en verkleinen om het te verzenden. + Voeg uw teamleden toe aan de gesprekken. + Zakelijk adres + end-to-end-versleuteld verzonden, met post-kwantumbeveiliging in directe berichten.]]> + App draait altijd op de achtergrond + Controleer berichten elke 10 minuten + Meldingen en batterij + Hoe het de privacy helpt + U ontvangt geen berichten meer van deze chat. De chatgeschiedenis blijft bewaard. + Chat verlaten? + Vrienden toevoegen + Teamleden toevoegen + Uitnodigen voor een chat + De chat wordt voor je verwijderd - dit kan niet ongedaan worden gemaakt! + Chat + Directe berichten tussen leden zijn niet toegestaan. + Chat bestaat al! + Chat verwijderen + Chat verwijderen? + Chat verlaten + Lid wordt verwijderd uit de chat - dit kan niet ongedaan worden gemaakt! + De rol wordt gewijzigd naar %s. Iedereen in de chat wordt op de hoogte gebracht. + Uw chatprofiel wordt naar chatleden verzonden + Privacy voor uw klanten. + %1$s.]]> + De chat wordt voor alle leden verwijderd - dit kan niet ongedaan worden gemaakt! + Bereikbare chat-toolbar + Zakelijke chats + Geen achtergrondservice + Alleen chateigenaren kunnen voorkeuren wijzigen. + Verklein de berichtgrootte of verwijder de media en verzend het bericht opnieuw. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml index 7b46d10921..56c80f8c89 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml @@ -420,8 +420,7 @@ uruchamianie… strajk Brak identyfikatorów użytkownika. - Następna generacja -\nprywatnych wiadomości + Następna generacja \nprywatnych wiadomości oczekiwanie na odpowiedź… oczekiwanie na potwierdzenie… Możesz używać markdown do formatowania wiadomości: diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml index 034e5d19db..27cc039c34 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml @@ -849,8 +849,7 @@ Para começar um novo bate-papo Ligar Bem-vindo(a)! - A próxima geração -\nde mensageiros privados + A próxima geração \nde mensageiros privados PROXY SOCKS A tentativa de alterar a senha do banco de dados não foi concluída. Pare o bate-papo para exportar, importar ou excluir o banco de dados do chat. Você não poderá receber e enviar mensagens enquanto o chat estiver interrompido. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml index bcd0848e8d..00df4f75cb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -88,9 +88,9 @@ Мгновенные уведомления Мгновенные уведомления! Мгновенные уведомления выключены! - фоновый сервис SimpleX, который потребляет несколько процентов батареи в день.]]> + SimpleX выполняется в фоне вместо уведомлений через сервер.]]> Он может быть выключен через Настройки – Вы продолжите получать уведомления о сообщениях пока приложение запущено.]]> - разрешите SimpleX выполняться в фоне в следующем диалоге. Иначе уведомления будут выключены.]]> + Разрешите это в следующем окне чтобы получать нотификации мгновенно.]]> Оптимизация батареи включена, поэтому сервис уведомлений выключен. Вы можете снова включить его через Настройки. Периодические уведомления Периодические уведомления выключены! @@ -463,7 +463,7 @@ соединено завершен - Новое поколение\nприватных сообщений + Будущее коммуникаций Более конфиденциальный Без идентификаторов пользователей. Защищен от спама @@ -475,8 +475,8 @@ Как это работает Как SimpleX работает - Чтобы защитить Вашу конфиденциальность, вместо ID пользователей, которые есть в других платформах, SimpleX использует ID для очередей сообщений, разные для каждого контакта. - с двухуровневым end-to-end шифрованием.]]> + Чтобы защитить Вашу конфиденциальность, SimpleX использует разные ID для всех ваших контактов. + Только пользовательские устройства хранят контакты, группы и сообщения. GitHub репозитория.]]> Использовать чат @@ -878,12 +878,12 @@ Запретить необратимое удаление сообщений. Разрешить отправлять голосовые сообщения. Запретить отправлять голосовые сообщений. - Члены группы могут посылать прямые сообщения. + Члены могут посылать прямые сообщения. Прямые сообщения между членами группы запрещены. - Члены группы могут необратимо удалять отправленные сообщения. (24 часа) - Необратимое удаление сообщений запрещено в этой группе. - Члены группы могут отправлять голосовые сообщения. - Голосовые сообщения запрещены в этой группе. + Члены могут необратимо удалять отправленные сообщения. (24 часа) + Необратимое удаление сообщений запрещено. + Члены могут отправлять голосовые сообщения. + Голосовые сообщения запрещены. Минимальный расход батареи. Вы получите уведомления только когда приложение запущено, без фонового сервиса.]]> Уведомления Когда приложение запущено @@ -891,7 +891,7 @@ Мгновенно Больше расход батареи! Приложение постоянно запущено в фоне - уведомления будут показаны сразу же.]]> Меньше расход батареи. Приложение проверяет сообщения каждые 10 минут. Вы можете пропустить звонки и срочные сообщения.]]> - Можно изменить позже в настройках. + Как это влияет на потребление энергии LIVE Отправить живое сообщение Живое сообщение! @@ -927,7 +927,7 @@ Отправить живое сообщение — оно будет обновляться для получателей по мере того, как Вы его вводите Создать ссылку группы Запретить отправлять исчезающие сообщения. - Исчезающие сообщения запрещены в этой группе. + Исчезающие сообщения запрещены. %dнед %dд %d нед. @@ -940,7 +940,7 @@ Сбросить подтверждение Разрешить исчезающие сообщения, только если Ваш контакт разрешает их Вам. Запретить посылать исчезающие сообщения. - Члены группы могут посылать исчезающие сообщения. + Члены могут посылать исчезающие сообщения. Что нового Новое в %s Аудит безопасности @@ -1193,7 +1193,7 @@ Установить код доступа История Информация - Открыть профили чата + Изменить профили чата Полученное сообщение Отправленное сообщение Исчезающее сообщение @@ -1227,9 +1227,9 @@ Разрешить реакции на сообщения. Разрешить реакции на сообщения, только если ваш контакт разрешает их. Разрешить контактам добавлять реакции на сообщения. - Члены группы могут добавлять реакции на сообщения. + Члены могут добавлять реакции на сообщения. Реакции на сообщения в этом чате запрещены. - Реакции на сообщения запрещены в этой группе. + Реакции на сообщения запрещены. Только Ваш контакт может добавлять реакции на сообщения. Запретить реакции на сообщения. Запретить реакции на сообщения. @@ -1348,8 +1348,8 @@ Нотификации перестанут работать, пока вы не перезапустите приложение Таймаут протокола на KB Разрешить посылать файлы и медиа. - Члены группы могут слать файлы и медиа. - Файлы и медиа запрещены в этой группе. + Члены могут слать файлы и медиа. + Файлы и медиа запрещены. Файлы и медиа запрещены! Только владельцы группы могут разрешить файлы и медиа. Файлы и медиа @@ -1818,7 +1818,7 @@ Ссылки SimpleX Разрешить отправлять ссылки SimpleX. Запретить отправку ссылок SimpleX - Члены группы могут отправлять ссылки SimpleX + Члены могут отправлять ссылки SimpleX админы все члены владельцы @@ -1828,7 +1828,7 @@ Включено для Переслать Переслать и сохранить сообщение - Ссылки SimpleX запрещены в этой группе. + Ссылки SimpleX запрещены. Переслать сообщение… Литовский интерфейс Источник сообщения остаётся конфиденциальным. @@ -2021,7 +2021,7 @@ Слабое Среднее Выключено - Доступная панель чата + Доступная панель приложения Текущий профиль Нет информации, попробуйте перезагрузить Информация о серверах @@ -2199,4 +2199,134 @@ Ваши учетные данные могут быть отправлены в незашифрованном виде. Удалить архив? Загруженный архив базы данных будет навсегда удален с серверов. + Принятые условия + Принять условия + Нет серверов сообщений. + Нет серверов для приема сообщений. + Ошибки в настройках серверов. + Для профиля %s: + Нет серверов файлов и медиа. + Нет серверов для приема файлов. + Нет серверов для отправки файлов. + Недоставленные сообщения + Нажмите Создать адрес SimpleX в меню, чтобы создать его позже. + Адрес или одноразовая ссылка? + Безопасность соединения + Операторы серверов + Ваши серверы + Посмотреть условия + Посмотреть условия + %s.]]> + Условия будут автоматически приняты для включенных операторов: %s + Условия приняты: %s. + Вебсайт + %s.]]> + %s.]]> + %s, примите условия использования.]]> + Для оправки + Дополнительные серверы сообщений + Использовать для файлов + Открыть условия + Ошибка добавления сервера + Сервер оператора + Сервер добавлен к оператору %s. + Тулбары приложения + Прозрачность + Децентрализация сети + Второй оператор серверов в приложении! + Включить Flux + для лучшей конфиденциальности метаданных. + Улучшенная навигация в разговоре + Посмотреть измененные условия + Устройства Xiaomi: пожалуйста, включите опцию Autostart в системных настройках для работы нотификаций.]]> + Нет сообщения + Это сообщение было удалено или еще не получено. + Сообщение слишком большое! + Пожалуйста, уменьшите размер сообщения и отправьте снова. + Пожалуйста, уменьшите размер сообщения или уберите медиа и отправьте снова. + Чтобы отправить сообщение, скопируйте и уменьшите его размер. + Поделитесь одноразовой ссылкой с другом + Поделитесь адресом + Поделитесь SimpleX адресом в социальных сетях. + Адрес SimpleX и одноразовые ссылки безопасно отправлять через любой мессенджер. + Вы можете установить имя соединения, чтобы запомнить кому Вы отправили ссылку. + Новый сервер + Создать одноразовую ссылку + Для социальных сетей + Или поделиться конфиденциально + Адрес SimpleX или одноразовая ссылка? + Настройки адреса + Добавьте сотрудников в разговор. + Бизнес адрес + end-to-end шифрованием, с пост-квантовой безопасностью в прямых разговорах.]]> + Приложение всегда выполняется в фоне + Проверять сообщения каждые 10 минут + Без фонового сервиса + Нотификации и батарейка + Как это улучшает конфиденциальность + Операторы сети + Выберите операторов сети. + Вы можете настроить операторов в настройках Сеть и серверы. + Продолжить + Посмотреть позже + Обновить + Связанные мобильные устройства + Покинуть разговор? + Вы прекратите получать сообщения в этом разговоре. История будет сохранена. + Добавить друзей + Добавить сотрудников + Удалить разговор + Удалить разговор? + Пригласить в разговор + Разговор будет удален для всех участников - это действие нельзя отменить! + Оператор + %s серверы + %s.]]> + Условия будут приняты: %s + Оператор сети + Использовать %s + Использовать серверы + %s.]]> + %s.]]> + Или импортировать файл архива + Доступная панель чата + Разговор будет удален для Вас - это действие нельзя отменить! + Покинуть разговор + Только владельцы разговора могут поменять предпочтения. + Текст условий использования не может быть показан, вы можете посмотреть их через ссылку: + Разговор + Член будет удален из разговора - это действие нельзя отменить! + Серверы по умолчанию + Роль будет изменена на %s. Все участники разговора получат уведомление. + Ваш профиль будет отправлен участникам разговора. + %s.]]> + %s.]]> + Условия использования + Дополнительные серверы файлов и медиа + Ошибка сохранения сервера + Для доставки сообщений + Открыть изменения + Оператор серверов изменен. + Протокол сервера изменен. + Серверы для новых файлов Вашего текущего профиля + Для получения + Использовать для сообщений + Размыть + Прямые сообщения между членами запрещены. + Бизнес разговоры + - Открывает разговор на первом непрочитанном сообщении.\n- Перейти к цитируемому сообщению. + Конфиденциальность для ваших покупателей. + %1$s.]]> + Разговор уже существует! + только с одним контактом - поделитесь при встрече или через любой мессенджер.]]> + Нет серверов для доставки сообщений. + Вы можете сконфигурировать серверы через настройки. + Когда больше чем один оператор сети включен, приложение использует серверы разных операторов в каждом разговоре. + Ошибка сохранения серверов + Условия будут приняты для включенных операторов через 30 дней. + Ошибка приема условий + Соединение достигло предела недоставленных сообщений. Возможно, Ваш контакт не в сети. + Чтобы защитить Вашу ссылку от замены, Вы можете сравнить код безопасности. + Например, если Ваш контакт получает сообщения через сервер SimpleX Chat, Ваше приложение доставит их через сервер Flux. + Прямые сообщения между членами запрещены в этом разговоре. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index 73daa373c1..0afb405097 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -1162,8 +1162,7 @@ кольоровий дзвінок завершено %1$s помилка дзвінка - Наступне покоління -\nприватних повідомлень + Наступне покоління \nприватних повідомлень Кожен може хостити сервери. Інструменти розробника Експериментальні функції diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index 9df7b74c33..41e9793ba1 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -3,7 +3,7 @@ a + b 1天 关于 SimpleX - 所有群组成员将保持连接。 + 所有群成员将保持连接。 关于 SimpleX Chat 以上,然后: 接受 @@ -22,7 +22,7 @@ 高级网络设置 接受连接请求? 接受隐身聊天 - 管理员可以创建链接以加入群组。 + 管理员可以创建链接以加入群。 添加预设服务器 通过链接连接 已建立连接 @@ -39,7 +39,7 @@ 消息和文件 添加个人资料 所有聊天记录和消息将被删除——这一行为无法撤销! - 所有聊天记录和消息将被删除——这一行为无法撤销!只有您的消息会被删除。 + 所有聊天记录和消息将被删除——这一行为无法撤销!只有你的消息会被删除。 允许发送语音消息。 允许语音消息? 删除 @@ -52,8 +52,8 @@ 为所有人删除 为我删除 为所有聊天资料删除文件 - 删除群组 - 删除群组? + 删除群 + 删除群? 删除链接 删除链接? 连接 @@ -63,17 +63,17 @@ 连接 通过一次性链接进行连接? 通过联系人地址进行连接? - 加入群组? - 通过群组链接/二维码连接 + 加入群? + 通过群链接/二维码连接 总是通过中继连接 - 允许您的联系人不不可逆地删除已发送消息。(24小时) + 允许你的联系人不不可逆地删除已发送消息。(24小时) 联系人允许 允许语音消息,前提是你的联系人允许这样的消息。 - 您: %1$s - 允许您的联系人发送语音消息。 + 你: %1$s + 允许你的联系人发送语音消息。 始终 始终开启 - 允许您的联系人发送限时消息。 + 允许你的联系人发送限时消息。 应用程序构建:%s 所有联系人会保持连接。 允许 @@ -83,14 +83,14 @@ 删除聊天资料? 删除联系人 删除联系人? - 已删除群组 + 已删除群 删除图片 允许限时消息,前提是你的联系人允许这样的消息。 允许不可逆的消息删除,前提是你的联系人允许这样做。(24小时) 允许不可逆地删除已发送消息。(24小时) 为此删除聊天资料 删除数据库 - 在您重启应用程序或者更换密码后安卓密钥库系统用来安全地保存密码——来确保收到通知。 + 在你重启应用程序或者更换密码后安卓密钥库系统用来安全地保存密码——来确保收到通知。 安卓密钥库系统用来安全地保存密码——来确保通知服务运作。 外观 应用程序版本 @@ -107,23 +107,23 @@ 消息散列值错误 错误消息 ID 语音和视频通话 - 启用电池优化,关闭了后台服务和对新消息的定期请求。您可以在设置里重新启用它们。 + 启用电池优化,关闭了后台服务和对新消息的定期请求。你可以在设置里重新启用它们。 后台服务始终运行——一旦有消息,就会显示通知。 关闭音频 开启音频 已要求接收图片 - ,用于您在应用程序中的每个聊天资料 。]]> - 每个联系人和群成员。\n请注意:如果您有很多连接,您的电池和流量消耗可能会大大增加,并且某些连接可能会失败。]]> + ,用于你在应用程序中的每个聊天资料 。]]> + 每个联系人和群成员。\n请注意:如果你有很多连接,你的电池和流量消耗可能会大大增加,并且某些连接可能会失败。]]> 返回 - 最长续航 。您只会在应用程序运行时收到通知(无后台服务)。]]> - 较长续航 。应用每 10 分钟检查一次消息。您可能会错过来电或者紧急信息。]]> + 最长续航 。你只会在应用程序运行时收到通知(无后台服务)。]]> + 较长续航 。应用每 10 分钟检查一次消息。你可能会错过来电或者紧急信息。]]> 加粗 - 您和您的联系人都可以不可逆地删除已发送的消息。(24小时) - 您和您的联系人都可以发送限时消息。 - 您和您的联系人都可以发送语音消息。 + 你和你的联系人都可以不可逆地删除已发送的消息。(24小时) + 你和你的联系人都可以发送限时消息。 + 你和你的联系人都可以发送语音消息。 可以在设置里禁用它 - 应用程序运行时仍会显示通知。]]> 使用更多电量 !应用始终在后台运行——一即刻显示通知。]]> - 请注意:如果您丢失密码,您将无法恢复或者更改密码。]]> + 请注意:如果你丢失密码,你将无法恢复或者更改密码。]]> 通话已结束! 无法邀请联系人! 无法邀请联系人! @@ -133,7 +133,7 @@ 通话结束 更改数据库密码? 通话错误 - 为您更改地址 + 为你更改地址 通话中 通话进行中 呼叫中…… @@ -147,14 +147,14 @@ 无法接收文件 无法初始化数据库 将 %s 的角色更改为 %s - 将您的角色更改为 %s + 将你的角色更改为 %s 改变角色 - 更改群组角色? + 更改群角色? 取消链接预览 正在为 %s 更改地址…… 更改地址中…… 更改地址中…… - 创建您的资料 + 创建你的资料 聊天数据库已删除 聊天数据库已导入 钥匙串错误 @@ -164,47 +164,47 @@ 聊天运行中 聊天已停止 联系人偏好设置 - 您的偏好设置 - 群组偏好设置 - 只有群主可以改变群组偏好设置。 + 你的偏好设置 + 群偏好设置 + 只有群主可以改变群偏好设置。 保存偏好设置? - 设置群组偏好设置 + 设置群偏好设置 重新定义隐私 改进的隐私和安全 隐身聊天 - 加入群组中 + 加入群中 加入隐身聊天 隐身模式 点击开始一个新聊天 - 您的随机资料 + 你的随机资料 通过联系地址链接隐身 - 通过群组链接隐身 - 您分享了一次性链接隐身聊天 + 通过群链接隐身 + 你分享了一次性链接隐身聊天 点击以加入隐身聊天 - 您的聊天资料将被发送给群组成员 - 您正在尝试邀请与您共享隐身个人资料的联系人加入您使用主要个人资料的群组 - 隐身模式通过为每个联系人使用新的随机配置文件来保护您的隐私。 - 您正在为该群组使用隐身个人资料——为防止共享您的主要个人资料,不允许邀请联系人 + 你的聊天资料将被发送给群成员 + 你正在尝试邀请与你共享隐身个人资料的联系人加入你使用主要个人资料的群 + 隐身模式通过为每个联系人使用新的随机配置文件来保护你的隐私。 + 你正在为该群使用隐身个人资料——为防止共享你的主要个人资料,不允许邀请联系人 通过一次性链接隐身 只有群主可以启用语音信息。 - 您的隐私设置 + 你的隐私设置 隐私和安全 保存服务器 它允许在一个聊天资料中有多个匿名连接,而它们之间没有任何共享数据。 - 当您与某人共享隐身聊天资料时,该资料将用于他们邀请您加入的群组。 + 当你与某人共享隐身聊天资料时,该资料将用于他们邀请你加入的群。 改进的服务器配置 电邮 编辑图片 - 编辑群组资料 + 编辑群资料 加密数据库错误 导出聊天数据库错误 导入聊天数据库错误 - 加入群组错误 + 加入群错误 删除用户资料错误 数据库密码不同于保存在密钥库中的密码。 数据库加密密码将被更新并存储在密钥库中。 数据库将被加密,密码存储在密钥库中。 - 在密匙库中没有找到密码,请手动输入。如果您使用备份工具恢复了应用程序的数据,可能会发生这种情况。如果不是这种情况,请联系开发者。 + 在密匙库中没有找到密码,请手动输入。如果你使用备份工具恢复了应用程序的数据,可能会发生这种情况。如果不是这种情况,请联系开发者。 从密钥库中删除密码? 在密钥库中保存密码 SimpleX Chat 服务 @@ -232,7 +232,7 @@ 关闭按键 配置 ICE 服务器 确认 - 确认您的证书 + 确认你的证书 已连接 已连接 连接 @@ -241,14 +241,14 @@ 连接中 每10分钟检查一次新消息,最长检查1分钟 已连接 - 与您的联系人比较安全码。 + 与你的联系人比较安全码。 语音通话来电 更改设置错误 - 群组邀请不再有效,已被发件人删除。 + 群邀请不再有效,已被发件人删除。 重复的显示名! 创建资料错误! 接受联系人请求错误 - 删除群组错误 + 删除群错误 删除待定的联系人连接错误 接收文件错误 切换资料错误! @@ -259,7 +259,7 @@ 对于每个人 解码错误 图片保存到相册 - 图片将在您的联系人在线时收到,请稍等或稍后查看! + 图片将在你的联系人在线时收到,请稍等或稍后查看! 保存文件错误 文件 未找到文件 @@ -296,17 +296,17 @@ 加密 加密数据库 输入正确密码。 - 不活跃群组 + 不活跃群 创建者 连接中(已接受) 连接中(已宣布) 扩展角色选择 - 群组链接 - 删除群组链接错误 + 群链接 + 删除群链接错误 数据库 ID 删除成员错误 更改角色错误 - 群组 + 限时消息 %d 天 连接中…… @@ -315,7 +315,7 @@ 联系人姓名 连接中(介绍邀请) 连接中…… - 联系人可以将信息标记为删除;您将可以查看这些信息。 + 联系人可以将信息标记为删除;你将可以查看这些信息。 贡献 已检查联系人 联系人已隐藏: @@ -323,12 +323,12 @@ 上下文图标 已复制到剪贴板 连接中…… - 创建群组链接 - 创建私密群组 + 创建群链接 + 创建私密群 创建链接 创建一次性邀请链接 创建队列 - 创建私密群组 + 创建私密群 不同的名字、头像和传输隔离。 法语界面 如何使用它 @@ -352,17 +352,17 @@ 全名: 输入你的名字: 已结束 - 群组已删除 - 将为所有成员删除群组——此操作无法撤消! + 群已删除 + 将为所有成员删除群——此操作无法撤消! 直接 私信 已启用 - 群组成员可以发送语音消息。 - 群组链接 + 成员可以发送语音消息。 + 群链接 启动聊天错误 数据库已加密! 加密数据库? - 数据库使用随机密码进行加密,您可以更改它。 + 数据库使用随机密码进行加密,你可以更改它。 打开聊天需要数据库密码。 停止聊天错误 导入聊天数据库? @@ -371,16 +371,16 @@ 输入密码…… 数据库加密密码将被更新。 数据库错误 - 群组资料已更新 - 群组全名: + 群资料已更新 + 群全名: 深色 已为联系人启用 - 为您启用 - 群组成员可以发送限时消息。 + 为你启用 + 成员可以发送限时消息。 创建个人资料 工作原理 - 未找到群组! - 创建群组链接错误 + 未找到群! + 创建群链接错误 连接中(已介绍) 数据库使用随机密码进行加密。请在导出前更改它。 数据库密码和导出 @@ -397,25 +397,25 @@ 删除联系人错误 更新网络配置错误 删除联系人请求错误 - 保存群组资料错误 + 保存群资料错误 保存 SMP 服务器错误 保存 ICE 服务器错误 发送消息错误 完整链接 - 输入群组名: - 群组邀请已过期 - 将为您删除群组——此操作无法撤消! - 群组资料存储在成员的设备上,而不是服务器上。 - 如果您选择拒绝发件人,将不会收到通知。 - 如何使用您的服务器 - 如果您确认,消息服务器将能够看到您的 IP 地址和您的提供商——以及您正在连接的服务器。 + 输入群名: + 群邀请已过期 + 将为你删除群——此操作无法撤消! + 群资料存储在成员的设备上,而不是服务器上。 + 如果你选择拒绝发件人,将不会收到通知。 + 如何使用你的服务器 + 如果你确认,消息服务器将能够看到你的 IP 地址和你的提供商——以及你正在连接的服务器。 图片 图片已发送 设备验证被禁用。关闭 SimpleX 锁定。 - 没有启用设备验证。一旦启用设备验证,您可以通过设置打开 SimpleX 锁定。 + 没有启用设备验证。一旦启用设备验证,你可以通过设置打开 SimpleX 锁定。 禁用 SimpleX 锁定 目前支持的最大文件尺寸是 %1$s。 - 文件将在您的联系人在线时收到,请稍等或稍后再查看! + 文件将在你的联系人在线时收到,请稍等或稍后再查看! 从图库 照片 视频 @@ -424,9 +424,9 @@ 启用自动删除消息? 用于控制台 此群中禁止成员之间私信。 - 该组禁止限时消息。 - 群组成员可以不可逆地删除已发送的消息。(24小时) - 群组成员可以私信。 + 限时消息被禁止。 + 成员可以不可逆地删除已发送的消息。(24小时) + 成员可以发送私信。 限时消息 在最近的应用程序中隐藏应用程序屏幕。 离开 @@ -434,13 +434,10 @@ 实时消息! 链接预览图片 无效的二维码 - 以后可以通过设置进行更改。 - 它可能在以下情况发生: -\n1. 消息在发送客户端 2 天后或在服务器上 30 天后过期。 -\n2. 消息解密失败,因为您或您的联系人使用了旧的数据库备份。 -\n3.连接被破坏。 - 离开群组? - 通过您的群组链接邀请 + 它如何影响电量 + 它可能在以下情况发生: \n1. 消息在发送客户端 2 天后或在服务器上 30 天后过期。 \n2. 消息解密失败,因为你或你的联系人使用了旧的数据库备份。 \n3.连接被破坏。 + 离开群? + 通过你的群链接邀请 本地名称 无效的消息格式 无效数据 @@ -449,9 +446,9 @@ 无效的连接链接 斜体 已邀请 - 邀请加入群组 + 邀请加入群 加入 - 加入群组? + 加入群? 邀请成员 已离开 浅色 @@ -462,25 +459,24 @@ 无效聊天 无效的服务器地址! 邀请成员 - 离开群组 + 离开群 仅本地配置文件数据 即时通知 即时通知! - 使用您的凭据登录 + 使用你的凭据登录 大文件! 链接无效! 已离开 - 此群组中禁止不可逆消息移除。 + 不可逆消息删除被禁止。 不可逆消息移除 实时消息 消息正文 等待确认中…… 即时 - 只有您的联系人才可以发送限时消息。 + 只有你的联系人才可以发送限时消息。 显示联系人和消息 只显示联系人 - 为保护您的信息,请打开 SimpleX 锁定。 -\n在启用此功能之前,系统将提示您完成身份验证。 + 为保护你的信息,请打开 SimpleX 锁定。 \n在启用此功能之前,系统将提示你完成身份验证。 聊天 分享文件…… 分享媒体…… @@ -488,16 +484,16 @@ 设置联系人姓名…… 已收到回复…… 已受到确认…… - 双层端到端加密 发送的消息。]]> + 仅客户端设备存储用户个人资料、联系人、群和消息。 视频通话(非端到端加密) 定期 私密通知 应用程序运行时 无端到端加密 显示 - 您只能在一台设备上使用最新版本的聊天数据库,否则您可能会停止接收来自某些联系人的消息。 + 你只能在一台设备上使用最新版本的聊天数据库,否则你可能会停止接收来自某些联系人的消息。 新密码…… - 该角色将更改为 %s。群组中每个人都会收到通知。 + 该角色将更改为 %s。群中每个人都会收到通知。 SimpleX 锁定 定期通知 定期启动 @@ -507,8 +503,8 @@ 启动中…… 禁止发送限时消息。 - 只有您可以发送限时消息。 - 只有您的联系人能不可逆地删除消息(您可以将它们标记为删除)。(24小时) + 只有你可以发送限时消息。 + 只有你的联系人能不可逆地删除消息(你可以将它们标记为删除)。(24小时) 禁止发送限时消息。 通知只会在应用程序停止之前发送! @@ -520,18 +516,18 @@ 通知 正在接收消息…… 要接收通知,请输入数据库密码 - SimpleX 后台服务 ——它每天使用百分之几的电池。]]> - 您的设置 - 允许 SimpleX 在后台运行。 否则,通知将被禁用。]]> + SimpleX 在后台运行而不是使用推送通知。]]> + 你的设置 + 允许它 来立即接收通知。]]> 通知预览 需要密码 定期通知被禁用! 在应用程序打开时运行 显示预览 - 该应用程序会定期获取新消息——它每天会消耗百分之几的电量。该应用程序不使用推送通知——您设备中的数据不会发送到服务器。 - 您的联系人可以允许完全删除消息。 + 该应用程序会定期获取新消息——它每天会消耗百分之几的电量。该应用程序不使用推送通知——你设备中的数据不会发送到服务器。 + 你的联系人可以允许完全删除消息。 已发送的消息将在设定的时间后被删除。 - 您的聊天数据库 + 你的聊天数据库 密码错误! 保存 打开 @@ -540,50 +536,48 @@ 打开聊天 更改数据库密码的尝试未完成。 移除 - + 保存 - + 移除 - 您必须在每次应用程序启动时输入密码——它不存储在设备上。 + 你必须在每次应用程序启动时输入密码——它不存储在设备上。 设置密码来导出 请输入正确的当前密码。 更新数据库密码 - 您的聊天数据库未加密——设置密码来保护它。 - 请安全地保存密码,如果您丢失了密码,您将无法访问聊天。 - 请安全地保存密码,如果您丢失了密码,您将无法更改它。 + 你的聊天数据库未加密——设置密码来保护它。 + 请安全地保存密码,如果你丢失了密码,你将无法访问聊天。 + 请安全地保存密码,如果你丢失了密码,你将无法更改它。 数据库密码错误 保存 更新 打开 SimpleX Chat 来接听电话 视频通话 - %1$s 想通过以下方式与您联系 + %1$s 想通过以下账户与你连接 拒接来电 点对点 错误:%s - 扫描视频通话中的二维码,或者您的联系人可以分享邀请链接。]]> - 您的通话 + 扫描视频通话中的二维码,或者你的联系人可以分享邀请链接。]]> + 你的通话 通过中继 未接来电 拒接来电 语音消息 语音消息 SimpleX Chat 通话 - 在视频通话中出示二维码,或分享链接。]]> - 您的聊天资料 + 在视频通话中出示二维码,或分享链接。]]> + 你的聊天资料 未接来电 待定来电 - 除非您的联系人已删除此连接或此链接已被使用,否则它可能是一个错误——请报告。 -\n如果要连接,请让您的联系人创建另一个连接链接,并检查您的网络连接是否稳定。 - 您已经连接到 %1$s。 - 您的聊天资料将被发送 -\n给您的联系人 + 除非你的联系人已删除此连接或此链接已被使用,否则它可能是一个错误——请报告。 \n如果要连接,请让你的联系人创建另一个连接链接,并检查你的网络连接是否稳定。 + 你已经连接到 %1$s。 + 你的聊天资料将被发送 \n给你的联系人 资料和服务器连接 更新网络设置? - 只有您可以不可逆地删除消息(您的联系人可以将它们标记为删除)。(24小时) + 只有你可以不可逆地删除消息(你的联系人可以将它们标记为删除)。(24小时) 重新启动应用程序以创建新的聊天资料。 服务器需要授权才能创建队列,检查密码 测试在步骤 %s 失败。 - 您已经有一个显示名相同的聊天资料。请选择另一个名字。 + 你已经有一个显示名相同的聊天资料。请选择另一个名字。 已发送 静音 资料图片 @@ -591,33 +585,32 @@ 设置 未知错误 未知数据库错误:%s - 已更新的群组资料 + 已更新的群资料 已删除 %1$s - 您删除了 %1$s - 您的个人资料将发送给您收到此链接的联系人。 + 你删除了 %1$s + 你的个人资料将发送给你收到此链接的联系人。 正在尝试连接到用于从该联系人接收消息的服务器(错误:%1$s)。 - 您已连接到用于接收该联系人消息的服务器。 - 您分享了一次性链接 - 很可能此联系人已经删除了与您的联系。 + 你已连接到用于接收该联系人消息的服务器。 + 你分享了一次性链接 + 很可能此联系人已经删除了与你的联系。 资料图片占位符 - 您当前聊天资料的新连接服务器 - 您当前的资料 - 您的资料存储在您的设备上并且仅与您的联系人共享。SimpleX 服务器无法看见您的资料。 - 您的资料、联系人和发送的消息存储在您的设备上。 - 该资料仅与您的联系人共享。 + 你当前聊天资料的新连接服务器 + 你当前的资料 + 你的资料存储在你的设备上并且仅与你的联系人共享。SimpleX 服务器无法看见你的资料。 + 你的资料、联系人和发送的消息存储在你的设备上。 + 该资料仅与你的联系人共享。 开启 - 此操作无法撤消——您的个人资料、联系人、消息和文件将不可逆地丢失。 - 此设置适用于您当前聊天资料中的消息 + 此操作无法撤消——你的个人资料、联系人、消息和文件将不可逆地丢失。 + 此设置适用于你当前聊天资料中的消息 恢复数据库错误 恢复 - 您发送了群组邀请 - 您拒绝了群组邀请 - 您当前的聊天数据库将被删除并替换为导入的数据库。 -\n此操作无法撤消——您的个人资料、联系人、消息和文件将不可逆地丢失。 + 你发送了群邀请 + 你拒绝了群邀请 + 你当前的聊天数据库将被删除并替换为导入的数据库。 \n此操作无法撤消——你的个人资料、联系人、消息和文件将不可逆地丢失。 已邀请 %1$s - 保存群组资料 + 保存群资料 服务器地址中的证书指纹可能不正确 - 请使用 %1$s 检查您的网络连接,然后重试。 + 请使用 %1$s 检查你的网络连接,然后重试。 多个聊天资料 数据库不能正常工作。点击了解更多 消息传递错误 @@ -632,22 +625,22 @@ 标记为已验证 建立私密连接 以 %s 身份加入 - 如果您收到 SimpleX Chat 邀请链接,您可以在浏览器中打开它: + 如果你收到 SimpleX Chat 邀请链接,你可以在浏览器中打开它: 标记为已读 标记为未读 在消息中使用 Markdown 文件:%s - 成员将被移出群组——此操作无法撤消! + 成员将被移出群——此操作无法撤消! 消息草稿 k 标记为已删除 %d 星期 - 您将停止接收来自该群组的消息。聊天记录将被保留。 + 你将停止接收来自该群的消息。聊天记录将被保留。 成员 成员 %d 星期 %d 分钟 - %d 月 + %d 个月 网络和服务器 高级设置 已被管理员移除 @@ -659,8 +652,7 @@ 未选择联系人 一次性邀请链接 关闭 - 连接需要 Onion 主机。 -\n请注意:如果没有 .onion 地址,您将无法连接到服务器。 + 连接需要 Onion 主机。 \n请注意:如果没有 .onion 地址,你将无法连接到服务器。 从不 已提供 %s 已提供 %s:%2s @@ -668,8 +660,8 @@ 一次性邀请链接 好的 没有细节 - (仅由群组成员存储) - 只有您可以发送语音消息。 + (仅由群成员存储) + 只有你可以发送语音消息。 消息将被删除——此操作无法撤消! 一次只能发送10张图片 更多 @@ -693,7 +685,7 @@ 保存并通知联系人 保存并通知联系人 拒绝 - 为了保护隐私,而不是所有其他平台使用的用户 ID,SimpleX 具有消息队列的标识符,每个联系人都是分开的。 + 为了保护隐私,SimpleX 对你的每一个联系人使用不同的 ID。 TCP 连接超时 收到,禁止 设定1天 @@ -706,14 +698,14 @@ PING 次数 禁止发送语音消息。 PING 间隔 - 请检查您使用的链接是否正确,或者让您的联系人给您发送另一个链接。 + 请检查你使用的链接是否正确,或者让你的联系人给你发送另一个链接。 协议超时 拒绝 回复 重置为默认 运行聊天程序 扫码 - 从您联系人的应用程序中扫描安全码。 + 从你联系人的应用程序中扫描安全码。 安全码 秘密 安全评估 @@ -731,7 +723,7 @@ 接收地址将变更到不同的服务器。地址更改将在发件人上线后完成。 此链接不是有效的连接链接! 开始新的聊天 - 要与您的联系人验证端到端加密,请比较(或扫描)您设备上的代码。 + 要与你的联系人验证端到端加密,请比较(或扫描)你设备上的代码。 取消静音 更新传输隔离模式? (从剪贴板扫描或粘贴) @@ -743,31 +735,31 @@ 太多图片! 待办的 更改接收地址? - 请让您的联系人启用发送语音消息。 + 请让你的联系人启用发送语音消息。 录制语音消息 发消息 重置 发送 - 发送实时消息——它会在您键入时为收件人更新 + 发送实时消息——它会在你键入时为收件人更新 开始新聊天 - (与您的联系人分享) + (与你的联系人分享) 通过链接连接 设置联系人姓名 - 您接受的连接将被取消! - 您与之共享此链接的联系人将无法连接! + 你接受的连接将被取消! + 你与之共享此链接的联系人将无法连接! 显示二维码 发送问题和想法 - 保护您的隐私和安全的消息传递和应用程序平台。 + 保护你的隐私和安全的消息传递和应用程序平台。 删去 你决定谁可以连接。 下一代私密通讯软件 - 粘贴您收到的链接 + 粘贴你收到的链接 已跳过消息 支持 SIMPLEX CHAT 发送链接预览 SOCKS 代理 停止聊天程序? - 停止聊天以便导出、导入或删除聊天数据库。在聊天停止期间,您将无法收发消息。 + 停止聊天以便导出、导入或删除聊天数据库。在聊天停止期间,你将无法收发消息。 恢复数据库备份 恢复数据库备份? 删除成员 @@ -792,7 +784,7 @@ 测试服务器 SimpleX 联系地址 SimpleX 一次性邀请 - SimpleX 群组链接 + SimpleX 群链接 SimpleX 链接 发送人可能已删除连接请求。 预设服务器 @@ -803,13 +795,13 @@ 跳过邀请成员 转变 禁止发送语音消息。 - 只有您的联系人可以发送语音消息。 + 只有你的联系人可以发送语音消息。 禁止向成员发送私信。 保护应用程序屏幕 主题 停止聊天以启用数据库操作。 %s 秒 - 该群组已不存在。 + 该群已不存在。 点击加入 停止 重新启动应用程序以使用导入的聊天数据库。 @@ -826,108 +818,107 @@ 停止聊天程序 权限被拒绝! 点击按钮 - 感谢您安装 SimpleX Chat! + 感谢你安装 SimpleX Chat! 相机 预设服务器地址 扫描服务器二维码 服务器测试失败! 一些服务器未通过测试: 在 GitHub 上加星 - 保存并通知群组成员 + 保存并通知群成员 扬声器关闭 扬声器开启 - 已将您移除 + 已将你移除 更新设置会将客户端重新连接到所有服务器。 系统 - 对方会在您键入时看到更新。 + 对方会在你键入时看到更新。 查看安全码 语音消息 (%1$s) 等待图像中 欢迎! 欢迎 %1$s! - 当您的联系人设备在线时,您将可以连接,请稍等或稍后查看! + 当你的联系人设备在线时,你将可以连接,请稍等或稍后查看! 评价此应用程序 使用 SOCKS 代理? %d 个文件,总大小为 %s - 您已加入此群组 - 您被邀请加入群组 + 你已加入此群 + 你被邀请加入群 默认(%s) 此聊天中禁止语音消息。 - 语音信息在该群组中被禁用。 + 语音信息被禁止。 验证安全码 使用 SimpleX Chat 服务器。 通过 %1$s - 邀请至群组 %1$s + 邀请至群 %1$s SimpleX 地址 SimpleX 团队 - %1$s 成员 + %1$s 名成员 - 您将在组主设备上线时连接到该群组,请稍等或稍后再检查! - 当您启动应用或在应用程序驻留后台超过30 秒后,您将需要进行身份验证。 - 连接到 SimpleX Chat 开发者提出任何问题并接收更新 。]]> - 您已接受连接 - 您的 SMP 服务器 - %1$d 已跳过消息 + 你将在组主设备上线时连接到该群,请稍等或稍后再检查! + 当你启动应用或在应用程序驻留后台超过30 秒后,你将需要进行身份验证。 + 连接到 SimpleX Chat 开发者提出任何问题并接收更新 。]]> + 你已接受连接 + 你的 SMP 服务器 + %1$d 条已跳过消息 %ds 更新内容 - 您被邀请加入群组 - 您没有聊天记录 + 你被邀请加入群 + 你没有聊天记录 等待图像中 语音消息 语音消息禁止发送! - 您需要允许您的联系人发送语音消息才能发送它们。 + 你需要允许你的联系人发送语音消息才能发送它们。 扫描二维码 - 您邀请了您的联系人 - 想要与您连接! - 您的联系人需要在线才能完成连接。 -\n您可以取消此连接并删除联系人(稍后尝试使用新链接)。 + 你邀请了一名联系人 + 想要与你连接! + 你的联系人需要在线才能完成连接。 \n你可以取消此连接并删除联系人(稍后尝试使用新链接)。 SimpleX 标志 - 您的 SimpleX 地址 + 你的 SimpleX 地址 为终端安装 SimpleX Chat 使用 SimpleX Chat 服务器? - 我们不会在服务器上存储您的任何联系人或消息(一旦发送)。 + 我们不会在服务器上存储你的任何联系人或消息(一旦发送)。 WebRTC ICE 服务器 - 中继服务器保护您的 IP 地址,但它可以观察通话的持续时间。 - 中继服务器仅在必要时使用。其他人可能会观察到您的IP地址。 - 您的 ICE 服务器 + 中继服务器保护你的 IP 地址,但它可以观察通话的持续时间。 + 中继服务器仅在必要时使用。其他人可能会观察到你的IP地址。 + 你的 ICE 服务器 视频关闭 - 您可以通过应用设置/数据库或重启应用开始聊天。 - 您将 %s 的角色更改为 %s - 您将自己的角色更改为 %s - 您已更改地址 - 您可以共享链接或二维码——任何人都可以加入该群组。如果您稍后将其删除,您不会失去该组的成员。 + 你可以通过应用设置/数据库或重启应用开始聊天。 + 你将 %s 的角色更改为 %s + 你将自己的角色更改为 %s + 你已更改地址 + 你可以共享链接或二维码——任何人都可以加入该群。如果你稍后将其删除,你不会失去该组的成员。 间接(%1$s) - 在移动应用程序中打开按钮。]]> + 在移动应用程序中打开按钮。]]> SimpleX 你将连接到所有群成员。 - 通过群组链接 + 通过群链接 通过一次性链接 通过联系地址链接 通过浏览器 - 您的服务器 + 你的服务器 当可用时 使用 .onion 主机 - 您的 ICE 服务器 + 你的 ICE 服务器 simplexmq: v%s (%2s) - 您的聊天由您掌控! - 您可以使用 markdown 来编排消息格式: + 你的聊天由你掌控! + 你可以使用 markdown 来编排消息格式: %dh %d 天 %dw - 您被邀请加入群组。 加入以与群组成员联系。 - 你加入了这个群组。连接到邀请组成员。 - 您更改了 %s 的地址 - 您已离开 - %d 已选择联系人 - 您允许 + 你被邀请加入群。 加入以与群成员联系。 + 你加入了这个群。连接到邀请组成员。 + 你更改了 %s 的地址 + 你已离开 + 已选择 %d 名联系人 + 你允许 带有可选的欢迎消息。 %dm %dmth 等待文件中 - 您的联系人发送的文件大于当前支持的最大大小 (%1$s). - 当您的连接请求被接受后,您将可以连接,请稍等或稍后检查! + 你的联系人发送的文件大于当前支持的最大大小 (%1$s). + 当你的连接请求被接受后,你将可以连接,请稍等或稍后检查! 使用服务器 - 您的服务器地址 + 你的服务器地址 视频开启 最多 40 秒,立即收到。 验证连接安全 @@ -937,11 +928,11 @@ 该消息将对所有成员标记为已被管理员移除。 删除成员消息? 观察员 - 您是观察者 - 更新群组链接错误 - 您无法发送消息! + 你是观察者 + 更新群链接错误 + 你无法发送消息! 初始角色 - 请联系群组管理员。 + 请联系群管理员。 系统 用于显示的密码 保存个人资料密码 @@ -959,18 +950,18 @@ 现在管理员可以: \n- 删除成员的消息。 \n- 禁用成员(观察员角色) - 使用密码保护您的聊天资料! + 使用密码保护你的聊天资料! 确认密码 更新用户隐私错误 保存用户密码错误 在搜索中输入密码 - 群组欢迎消息 - 群组管理员移除 + 群欢迎消息 + 群管理员移除 隐藏的个人资料密码 隐藏的聊天资料 隐藏个人资料 保存服务器? - 要显示您的隐藏的个人资料,请在您的聊天个人资料页面的搜索字段中输入完整密码。 + 要显示你的隐藏的个人资料,请在你的聊天个人资料页面的搜索字段中输入完整密码。 保存欢迎信息? 点击以激活个人资料。 取消隐藏 @@ -979,8 +970,8 @@ 感谢用户——通过 Weblate 做出贡献! 解除静音 欢迎消息 - 当静音配置文件处于活动状态时,您仍会收到来自静音配置文件的电话和通知。 - 您可以隐藏或静音用户配置文件——长按以显示菜单。 + 当静音配置文件处于活动状态时,你仍会收到来自静音配置文件的电话和通知。 + 你可以隐藏或静音用户配置文件——长按以显示菜单。 欢迎消息 确认数据库升级 实验性 @@ -991,13 +982,13 @@ 数据库版本比应用程序更新,但无法降级迁移:%s 降级并打开聊天 隐藏: - 文件将在您的联系人完成上传后收到。 + 文件将在你的联系人完成上传后收到。 数据库版本不兼容 迁移:%s - 图片将在您的联系人完成上传后收到。 + 图片将在你的联系人完成上传后收到。 显示开发者选项 升级并打开聊天 - 警告:您可能会丢失部分数据! + 警告:你可能会丢失部分数据! 迁移确认无效 显示: 删除个人资料 @@ -1009,15 +1000,15 @@ 过多视频! 视频 等待视频中 - 视频将在您的联系人在线时收到,请稍等或稍后查看! + 视频将在你的联系人在线时收到,请稍等或稍后查看! 等待视频中 视频已发送 要求接收视频 - 视频将在您的联系人完成上传后收到。 + 视频将在你的联系人完成上传后收到。 服务器需要授权来上传,检查密码 上传文件 XFTP 服务器 - 您的 XFTP 服务器 + 你的 XFTP 服务器 Use .onion hosts 设置为否。]]> 使用 SOCKS 代理 端口 @@ -1040,7 +1031,7 @@ 没有应用程序密码 密码输入 请牢记或妥善保管——丢失的密码将无法恢复! - 您可以通过设置开启 SimpleX 锁定。 + 你可以通过设置开启 SimpleX 锁定。 身份验证 身份验证失败 更改密码 @@ -1058,15 +1049,15 @@ 密码已设置! 系统 未启用 SimpleX 锁定! - 您的身份无法验证,请再试一次。 + 你的身份无法验证,请再试一次。 身份验证已取消 当前密码 立即 错误消息散列 错误消息 ID - %1$d 消息解密失败。 - %1$d 已跳过消息。 - 当您或您的连接使用旧数据库备份时,可能会发生这种情况。 + %1$d 条消息解密失败。 + 跳过了 %1$d 条消息。 + 当你或你的连接使用旧数据库备份时,可能会发生这种情况。 解密错误 请向开发者报告。 上一条消息的散列不同。 @@ -1092,30 +1083,30 @@ 最大 1gb 的视频和文件 快速且无需等待发件人在线! 禁止音频/视频通话。 - 您和您的联系人都可以进行呼叫。 - 只有您可以进行呼叫。 - 只有您的联系人可以进行呼叫。 + 你和你的联系人都可以进行呼叫。 + 只有你可以进行呼叫。 + 只有你的联系人可以进行呼叫。 允许联系人呼叫你。 允许通话,前提是你的联系人允许它们。 禁止音频/视频通话。 1分钟 一次性链接 - 您和您的联系人都可以添加消息回应。 + 你和你的联系人都可以添加消息回应。 允许消息回应。 允许消息回应,前提是你的联系人允许它们。 应用程序密码被替换为自毁密码。 更改自毁模式 关于 SimpleX 地址 继续 - 您的所有联系人将保持连接。个人资料更新将发送给您的联系人。 + 你的所有联系人将保持连接。个人资料更新将发送给你的联系人。 自动接受 额外的次要 背景 5分钟 30秒 地址 - 将地址添加到您的个人资料,以便您的联系人可以与其他人共享。个人资料更新将发送给您的联系人。 - 允许您的联系人添加消息回应。 + 将地址添加到你的个人资料,以便你的联系人可以与其他人共享。个人资料更新将发送给你的联系人。 + 允许你的联系人添加消息回应。 额外的强调色 已删除所有应用程序数据。 已创建一个包含所提供名字的空白聊天资料,应用程序照常打开。 @@ -1131,7 +1122,7 @@ 消失于 设置地址错误 自定义主题 - 创建一个地址,让人们与您联系。 + 创建一个地址,让人们与你联系。 创建 SimpleX 地址 输入欢迎消息……(可选) 不创建地址 @@ -1149,9 +1140,9 @@ 已删除于:%s 消失于:%s 禁止消息回应。 - 只有您可以添加消息回应。 - 群组成员可以添加信息回应。 - 该群组禁用了消息回应。 + 只有你可以添加消息回应。 + 成员可以添加信息回应。 + 消息回应被禁止。 自毁密码已更改! 自毁密码已启用! 设置密码 @@ -1159,7 +1150,7 @@ 已发信息 历史记录 发送 - 如果您在打开应用时输入该密码,所有应用程序数据将被不可逆地删除! + 如果你在打开应用时输入该密码,所有应用程序数据将被不可逆地删除! 新的显示名: 已被管理员移除于 已发送于 @@ -1169,18 +1160,18 @@ %s (当前) 发送于 %s 收到的信息 - 只有您的联系人可以添加消息回应。 + 只有你的联系人可以添加消息回应。 打开数据库中…… 更改聊天资料 - 您的联系人可以扫描二维码或使用应用程序中的链接来建立连接。 - 您可以将您的地址作为链接或二维码共享——任何人都可以连接到您。 - 如果您不能亲自见面,可以在视频通话中展示二维码,或分享链接。 + 你的联系人可以扫描二维码或使用应用程序中的链接来建立连接。 + 你可以将你的地址作为链接或二维码共享——任何人都可以连接到你。 + 如果你不能亲自见面,可以在视频通话中展示二维码,或分享链接。 了解更多 - 当人们请求连接时,您可以接受或拒绝它。 - 如果您以后删除您的地址,您不会丢失您的联系人。 + 当人们请求连接时,你可以接受或拒绝它。 + 如果你以后删除你的地址,你不会丢失你的联系人。 用户指南中阅读更多。]]> 界面颜色 - 与您的联系人保持连接。 + 与你的联系人保持连接。 与联系人分享 邀请朋友 保存自动接受设置 @@ -1189,9 +1180,9 @@ 你好! \n用 SimpleX Chat 与我联系:%s 让我们一起在 SimpleX Chat 里聊天 - 您可以以后创建它 + 你可以以后创建它 分享地址 - 您可以与您的联系人分享该地址,让他们与 %s 联系。 + 你可以与你的联系人分享该地址,让他们与 %s 联系。 预览 导入主题 SimpleX @@ -1216,8 +1207,8 @@ 已发信息 消息回应 该聊天禁用了消息回应。 - 如果您在打开应用程序时输入自毁密码: - 个人资料更新将被发送给您的联系人。 + 如果你在打开应用程序时输入自毁密码: + 个人资料更新将被发送给你的联系人。 记录更新于 禁止消息回应。 已收到于 @@ -1232,7 +1223,7 @@ 导入过程中发生了一些非致命错误: 应用程序 重启 - 通知将停止工作直到您重启应用程序 + 通知将停止工作直到你重启应用程序 关闭? 关闭 中止地址更改错误 @@ -1243,8 +1234,8 @@ 允许发送文件和媒体。 文件和媒体 只有组主可以启用文件和媒体。 - 此群组中禁止文件和媒体。 - 群组成员可以发送文件和媒体。 + 文件和媒体被禁止。 + 成员可以发送文件和媒体。 禁止发送文件和媒体。 禁止文件和媒体! 无过滤聊天 @@ -1261,9 +1252,7 @@ 选择一个文件 联系人 协调加密中… - - 更稳定的消息送达. -\n- 更好的群组. -\n- 还有更多! + - 更稳定的消息传送. \n- 更好的群. \n- 还有更多! 一个新的随机个人档案将被分享。 与 %s 协调加密中… 该功能还没支持。请尝试下一个版本。 @@ -1274,10 +1263,10 @@ %s: %s 敬请期待! 数据库将被加密,密码将存储在设置中。 - 您可以稍后在“设置”中启用它 - 对所有群组关闭 + 你可以稍后在“设置”中启用它 + 对所有群关闭 无送货信息 - 您的个人资料 %1$s 将被共享。 + 你的个人资料 %1$s 将被共享。 将为所有联系人启用送达回执功能。 打开应用程序设置 为所有组启用 @@ -1294,13 +1283,12 @@ %s 在 %s 禁用回执? 重新协商加密? - 可以在联系人和群组设置中覆盖它们。 + 可以在联系人和群设置中覆盖它们。 对所有联系人关闭 - 随机密码以明文形式存储在设置中。 -\n您可以稍后更改。 + 随机密码以明文形式存储在设置中。 \n你可以稍后更改。 已禁用 %d 组的送达回执功能 需要为 %s 重新协商加密 - SimpleX 无法在后台运行。只有在应用程序运行时,您才会收到通知。 + SimpleX 无法在后台运行。只有在应用程序运行时,你才会收到通知。 启用(保留覆盖) 即将更新数据库加密密码并将其存储在设置中。 使用当前配置文件 @@ -1312,16 +1300,16 @@ 即使在对话中禁用。 使用随机密码 无后台通话 - 您可以稍后通过应用程序隐私和安全设置启用它们。 + 你可以稍后通过应用程序隐私和安全设置启用它们。 在设置中保存密码 启用 - 该群组成员超过 %1$d ,未发送送达回执。 + 该群成员超过 %1$d ,未发送送达回执。 修复连接? 我们错过的第二个"√"!✅ 设定数据库密码 - 为群组禁用回执吗? + 为群禁用回执吗? %s、%s 和 %s 已连接 - 修复群组成员不支持的问题 + 修复群成员不支持的问题 已为 %d 组启用送达回执功能 重新协商 禁用(保留覆盖) @@ -1336,18 +1324,18 @@ 修复连接 %s 和 %s 已连接 关闭 - 小群组(最多 20 人) + 小群(最多 20 人) 显示最近的消息 将送达回执发送给 启用已读回执时出错! 更改密码或重启应用后,密码将以明文形式保存在设置中。 - 粘贴您收到的链接以与您的联系人联系… + 粘贴你收到的链接以与你的联系人联系… 送达回执 没有选择聊天 可以加密 重新协商加密 禁用(保留组覆盖) - 为群组启用回执吗? + 为群启用回执吗? 修复联系人不支持的问题 对 %s 加密正常 修复还原备份后的加密问题。 @@ -1358,7 +1346,7 @@ 连接请求将发送给该组成员。 密码以明文形式存储在设置中。 同步连接时出错 - 这些设置适用于您当前的配置文件 + 这些设置适用于你当前的配置文件 允许为 %s 重新协商加密 为所有人启用 需要重新协商加密 @@ -1369,14 +1357,12 @@ 全新桌面应用! 6种全新的界面语言 应用程序为新的本地文件(视频除外)加密。 - 发现和加入群组 + 发现和加入群 简化的隐身模式 阿拉伯语、保加利亚语、芬兰语、希伯莱语、泰国语和乌克兰语——得益于用户和Weblate。 在桌面应用里创建新的账号。💻 在连接时切换隐身模式。 - - 连接到目录服务(BETA)! -\n- 发送回执(至多20名成员)。 -\n- 更快,更稳定。 + - 连接到目录服务(BETA)! \n- 发送回执(至多20名成员)。 \n- 更快、更稳定。 打开 创建成员联系人时出错 发送私信来连接 @@ -1394,14 +1380,14 @@ 加入你的群吗? %1$s 群。]]> 这是你自己的一次性链接! - %d 条消息被标记为删除 + %d 条消息被标记为已删除 群已存在! 已经在连接了! 无法解码该视频。请尝试不同视频或联络开发者。 %s 已连接 及其他 %d 个事件 通过链接进行连接吗? - 已经加入了该群组! + 已经加入了该群! %s、 %s 和 %d 名成员 解封成员 连接到你自己? @@ -1480,13 +1466,13 @@ 从已链接移动设备加载文件时请稍候片刻 桌面应用版本 %s 不兼容此应用。 验证连接 - 屏蔽群组成员 - 使用随机身份创建群组 + 屏蔽群成员 + 使用随机身份创建群 连接移动端和桌面端应用程序!🔗 通过安全的、抗量子计算机破解的协议。 隐藏不需要的信息。 - 更佳的群组 - 匿名群组 + 更佳的群 + 匿名群 %s 连接断开]]> 加入速度更快、信息更可靠。 - 可选择通知已删除的联系人。 @@ -1519,10 +1505,10 @@ 不给新成员发送历史消息。 或者显示此码 给新成员发送了最多 100 条历史消息。 - 您扫描的码不是 SimpleX 链接的二维码。 - 您粘贴的文本不是 SimpleX 链接。 + 你扫描的码不是 SimpleX 链接的二维码。 + 你粘贴的文本不是 SimpleX 链接。 启用相机访问 - 您可以在连接详情中再次查看邀请链接。 + 你可以在连接详情中再次查看邀请链接。 保留未使用的邀请吗? 分享此一次性邀请链接 建群: 来建立新群。]]> @@ -1635,7 +1621,7 @@ 应用数据迁移 通过二维码迁移到另一部设备。 画中画通话 - 更安全的群组 + 更安全的群 通话时使用本应用 迁移到此处 或粘贴存档链接 @@ -1720,9 +1706,9 @@ 不允许语音消息 SimpleX 链接 允许发送 SimpleX 链接。 - 群成员可发送 SimpleX 链接。 + 成员可发送 SimpleX 链接。 禁止发送 SimpleX 链接 - 此群禁止 SimpleX 链接。 + SimpleX 链接被禁止。 所有者 启用对象 管理员 @@ -1824,8 +1810,7 @@ 缩放 Webview 初始化失败。更新你的系统到新版本。请联系开发者。 \n错误:%s - 保护您的真实 IP 地址。不让你的联系人选择的消息中继看到它。 -\n在*网络&服务器*设置中开启。 + 保护你的真实 IP 地址。不让你的联系人选择的消息中继看到它。 \n在*网络&服务器*设置中开启。 确认来自未知服务器的文件。 安全地接收文件 改进了消息传递 @@ -1996,7 +1981,7 @@ 你仍可以在聊天列表中查看与 %1$s 的对话。 粘贴链接 联系人 - 单手用户界面 + 单手应用工具栏 正在连接联系人,请等候或稍后检查! 联系人被删除了。 要能够呼叫联系人,你需要先允许联系人进行呼叫。 @@ -2079,8 +2064,7 @@ 分享配置文件 转发消息出错 在你选中消息后这些消息被删除。 - %1$d 个文件错误: -\n%2$s + %1$d 个文件错误:\n%2$s 其他 %1$d 个文件错误。 %1$d 个文件未被下载。 转发 %1$s 条消息? @@ -2134,7 +2118,7 @@ 创建一次性链接 用于社交媒体 或者私下分享 - 选择运营者 + 服务器运营者 网络运营者 30 天后将接受已启用的运营者的条款。 继续 @@ -2211,4 +2195,39 @@ 接受运营者条款的日期:%s 远程移动设备 或者导入压缩文件 + 小米设备:请在系统设置中开启“自动启动”让通知正常工作。]]> + 消息太大! + 你可以复制并减小消息大小来发送它。 + 请减小消息大小或删除媒体并再次发送。 + 将你的团队成员加入对话。 + 企业地址 + 端到端加密,私信具备后量子密码安全性。]]> + 无后台服务 + 每 10 分钟检查消息 + 它如何帮助隐私 + 应用始终在后台运行 + 通知和电量 + 离开聊天? + 你将停止从这个聊天收到消息。聊天历史将被保留。 + 邀请加入聊天 + 将为你删除聊天 - 此操作无法撤销! + 删除聊天 + 删除聊天? + 添加好友 + 添加团队成员 + 将为所有成员删除聊天 - 此操作无法撤销! + 仅聊天所有人可更改首选项。 + 角色将被更改为 %s。聊天中的每个人都会收到通知。 + 成员之间的私信被禁止。 + 此聊天禁止成员之间的私信。 + 企业聊天 + 客户隐私。 + %1$s连接。]]> + 聊天已存在! + 单手聊天工具栏 + 离开聊天 + 你的聊天个人资料将被发送给聊天成员 + 聊天 + 将从聊天中删除成员 - 此操作无法撤销! + 请减小消息尺寸并再次发送。 \ No newline at end of file From 586671c3076cc4c4e39f7deb79dd190d518c4e82 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 5 Dec 2024 21:46:35 +0000 Subject: [PATCH 188/567] website: translations (#5331) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (258 of 258 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (258 of 258 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/ * Translated using Weblate (Italian) Currently translated at 100.0% (258 of 258 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/it/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (258 of 258 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ --------- Co-authored-by: 大王叫我来巡山 Co-authored-by: Random Co-authored-by: Ghost of Sparta --- website/langs/hu.json | 3 ++- website/langs/it.json | 3 ++- website/langs/zh_Hans.json | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/website/langs/hu.json b/website/langs/hu.json index b0b76714bc..7702b56f47 100644 --- a/website/langs/hu.json +++ b/website/langs/hu.json @@ -255,5 +255,6 @@ "simplex-chat-via-f-droid": "SimpleX Chat az F-Droidon keresztül", "simplex-chat-repo": "SimpleX Chat tároló", "stable-and-beta-versions-built-by-developers": "A fejlesztők által készített stabil és béta verziók", - "hero-overlay-card-3-p-3": "A Trail of Bits 2024 júliusában felülvizsgálta a SimpleX hálózati protokollok kriptográfiai felépítését. Tudjon meg többet." + "hero-overlay-card-3-p-3": "A Trail of Bits 2024 júliusában felülvizsgálta a SimpleX hálózati protokollok kriptográfiai felépítését. Tudjon meg többet.", + "docs-dropdown-14": "SimpleX üzleti célra" } diff --git a/website/langs/it.json b/website/langs/it.json index bdc4c38d45..502ab6d886 100644 --- a/website/langs/it.json +++ b/website/langs/it.json @@ -255,5 +255,6 @@ "docs-dropdown-10": "Trasparenza", "docs-dropdown-12": "Sicurezza", "docs-dropdown-11": "Domande frequenti", - "hero-overlay-card-3-p-3": "Trail of Bits ha analizzato la progettazione crittografica dei protocolli della rete SimpleX nel luglio 2024. Leggi di più." + "hero-overlay-card-3-p-3": "Trail of Bits ha analizzato la progettazione crittografica dei protocolli della rete SimpleX nel luglio 2024. Leggi di più.", + "docs-dropdown-14": "SimpleX per il lavoro" } diff --git a/website/langs/zh_Hans.json b/website/langs/zh_Hans.json index e482056565..836f7057ee 100644 --- a/website/langs/zh_Hans.json +++ b/website/langs/zh_Hans.json @@ -255,5 +255,6 @@ "docs-dropdown-10": "透明度", "docs-dropdown-11": "常问问题", "docs-dropdown-12": "安全性", - "hero-overlay-card-3-p-3": "Trail of Bits 于 2024 年 7 月审核了 SimpleX 网络协议的加密设计。了解更多信息。" + "hero-overlay-card-3-p-3": "Trail of Bits 于 2024 年 7 月审核了 SimpleX 网络协议的加密设计。了解更多信息。", + "docs-dropdown-14": "企业版 SimpleX" } From bd2ca749872a3d62d396198cae764d0a3c8bcf73 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Thu, 5 Dec 2024 22:22:03 +0000 Subject: [PATCH 189/567] ui: update "server operators privacy" in onboarding --- apps/ios/ru.lproj/Localizable.strings | 2 +- .../common/views/onboarding/ChooseServerOperators.kt | 1 + .../chat/simplex/common/views/onboarding/SimpleXInfo.kt | 2 +- .../common/src/commonMain/resources/MR/base/strings.xml | 5 +++-- .../common/src/commonMain/resources/MR/ru/strings.xml | 7 ++++--- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index c2124b34a4..57c1b91769 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -5397,7 +5397,7 @@ "You can configure operators in Network & servers settings." = "Вы можете настроить операторов в настройках Сеть и серверы."; /* No comment provided by engineer. */ -"You can configure servers via settings." = "Вы можете сконфигурировать серверы через настройки."; +"You can configure servers via settings." = "Вы можете настроить серверы позже."; /* No comment provided by engineer. */ "You can create it later" = "Вы можете создать его позже"; diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt index 782f51c205..132381294f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt @@ -342,6 +342,7 @@ private fun ChooseServerOperatorsInfoView() { ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_PADDING)) { AppBarTitle(stringResource(MR.strings.onboarding_network_operators), withPadding = false) ReadableText(stringResource(MR.strings.onboarding_network_operators_app_will_use_different_operators)) + ReadableText(stringResource(MR.strings.onboarding_network_operators_cant_see_who_talks_to_whom)) ReadableText(stringResource(MR.strings.onboarding_network_operators_app_will_use_for_routing)) SectionBottomSpacer() } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt index a77c25dd1d..020d3493b9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt @@ -181,7 +181,7 @@ fun OnboardingInformationButton( .clip(CircleShape) .clickable { onClick() } ) { - Row(Modifier.padding(8.dp), horizontalArrangement = Arrangement.spacedBy(4.dp) ) { + Row(Modifier.padding(8.dp), horizontalArrangement = Arrangement.spacedBy(4.dp)) { Icon( painterResource(MR.images.ic_info), null, diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index d56a7fe87c..7fc46cf3a7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1075,8 +1075,9 @@ Server operators Network operators - When more than one network operator is enabled, the app will use the servers of different operators for each conversation. - For example, if you receive messages via SimpleX Chat server, the app will use one of Flux servers for private routing. + The app protects your privacy by using different operators in each conversation. + When more than one operator is enabled, none of them has metadata to learn who communicates with whom. + For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. Select network operators to use. How it helps privacy You can configure servers via settings. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml index 00df4f75cb..9e39ad61a4 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -2264,7 +2264,7 @@ Без фонового сервиса Нотификации и батарейка Как это улучшает конфиденциальность - Операторы сети + Операторы серверов Выберите операторов сети. Вы можете настроить операторов в настройках Сеть и серверы. Продолжить @@ -2320,8 +2320,9 @@ Разговор уже существует! только с одним контактом - поделитесь при встрече или через любой мессенджер.]]> Нет серверов для доставки сообщений. - Вы можете сконфигурировать серверы через настройки. - Когда больше чем один оператор сети включен, приложение использует серверы разных операторов в каждом разговоре. + Вы можете настроить серверы позже. + Приложение улучшает конфиденциальность используя разных операторов в каждом разговоре. + Когда больше чем один оператор включен, ни один из них не видит метаданные, чтобы определить, кто соединен с кем. Ошибка сохранения серверов Условия будут приняты для включенных операторов через 30 дней. Ошибка приема условий From 886dc56de864fdf7bc44ed8b95d2dcd8fcccfdff Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Thu, 5 Dec 2024 23:01:37 +0000 Subject: [PATCH 190/567] ui: update business chat info type --- apps/ios/SimpleXChat/ChatTypes.swift | 3 ++- .../commonMain/kotlin/chat/simplex/common/model/ChatModel.kt | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 5379cce236..a2d44d59d0 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -1962,8 +1962,9 @@ public struct GroupProfile: Codable, NamedChat, Hashable { } public struct BusinessChatInfo: Decodable, Hashable { - public var memberId: String public var chatType: BusinessChatType + public var businessId: String + public var customerId: String } public enum BusinessChatType: String, Codable, Hashable { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 4d75d37b99..b9e52763a6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -1546,8 +1546,9 @@ data class GroupProfile ( @Serializable data class BusinessChatInfo ( - val memberId: String, - val chatType: BusinessChatType + val chatType: BusinessChatType, + val businessId: String, + val customerId: String, ) @Serializable From 5ef14ca95e6301d687d3ad7e69777f50720e9237 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Thu, 5 Dec 2024 23:30:05 +0000 Subject: [PATCH 191/567] 6.2-beta.6: ios 253, android 258, desktop 81 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 36 +++++++++++----------- apps/multiplatform/gradle.properties | 8 ++--- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 690ba2579a..22d5ba971b 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -167,9 +167,9 @@ 648010AB281ADD15009009B9 /* CIFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648010AA281ADD15009009B9 /* CIFileView.swift */; }; 648679AB2BC96A74006456E7 /* ChatItemForwardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648679AA2BC96A74006456E7 /* ChatItemForwardingView.swift */; }; 649B28DD2CFE07CF00536B68 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28D82CFE07CF00536B68 /* libffi.a */; }; - 649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.5-592uBhlQO6KIf70TJf5KpP-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.5-592uBhlQO6KIf70TJf5KpP-ghc9.6.3.a */; }; + 649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.6-5lGV6gtq9gSDlEsE8DHXYo-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.6-5lGV6gtq9gSDlEsE8DHXYo-ghc9.6.3.a */; }; 649B28DF2CFE07CF00536B68 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DA2CFE07CF00536B68 /* libgmpxx.a */; }; - 649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.5-592uBhlQO6KIf70TJf5KpP.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.5-592uBhlQO6KIf70TJf5KpP.a */; }; + 649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.6-5lGV6gtq9gSDlEsE8DHXYo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.6-5lGV6gtq9gSDlEsE8DHXYo.a */; }; 649B28E12CFE07CF00536B68 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DC2CFE07CF00536B68 /* libgmp.a */; }; 649BCDA0280460FD00C3A862 /* ComposeImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */; }; 649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCDA12805D6EF00C3A862 /* CIImageView.swift */; }; @@ -516,9 +516,9 @@ 648679AA2BC96A74006456E7 /* ChatItemForwardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemForwardingView.swift; sourceTree = ""; }; 6493D667280ED77F007A76FB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 649B28D82CFE07CF00536B68 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.5-592uBhlQO6KIf70TJf5KpP-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.5-592uBhlQO6KIf70TJf5KpP-ghc9.6.3.a"; sourceTree = ""; }; + 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.6-5lGV6gtq9gSDlEsE8DHXYo-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.6-5lGV6gtq9gSDlEsE8DHXYo-ghc9.6.3.a"; sourceTree = ""; }; 649B28DA2CFE07CF00536B68 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.5-592uBhlQO6KIf70TJf5KpP.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.5-592uBhlQO6KIf70TJf5KpP.a"; sourceTree = ""; }; + 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.6-5lGV6gtq9gSDlEsE8DHXYo.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.6-5lGV6gtq9gSDlEsE8DHXYo.a"; sourceTree = ""; }; 649B28DC2CFE07CF00536B68 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeImageView.swift; sourceTree = ""; }; 649BCDA12805D6EF00C3A862 /* CIImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIImageView.swift; sourceTree = ""; }; @@ -671,9 +671,9 @@ 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, 649B28E12CFE07CF00536B68 /* libgmp.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - 649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.5-592uBhlQO6KIf70TJf5KpP.a in Frameworks */, + 649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.6-5lGV6gtq9gSDlEsE8DHXYo.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - 649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.5-592uBhlQO6KIf70TJf5KpP-ghc9.6.3.a in Frameworks */, + 649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.6-5lGV6gtq9gSDlEsE8DHXYo-ghc9.6.3.a in Frameworks */, 649B28DD2CFE07CF00536B68 /* libffi.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -754,8 +754,8 @@ 649B28D82CFE07CF00536B68 /* libffi.a */, 649B28DC2CFE07CF00536B68 /* libgmp.a */, 649B28DA2CFE07CF00536B68 /* libgmpxx.a */, - 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.5-592uBhlQO6KIf70TJf5KpP-ghc9.6.3.a */, - 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.5-592uBhlQO6KIf70TJf5KpP.a */, + 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.6-5lGV6gtq9gSDlEsE8DHXYo-ghc9.6.3.a */, + 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.6-5lGV6gtq9gSDlEsE8DHXYo.a */, ); path = Libraries; sourceTree = ""; @@ -1931,7 +1931,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 251; + CURRENT_PROJECT_VERSION = 253; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1980,7 +1980,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 251; + CURRENT_PROJECT_VERSION = 253; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2021,7 +2021,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 251; + CURRENT_PROJECT_VERSION = 253; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2041,7 +2041,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 251; + CURRENT_PROJECT_VERSION = 253; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2066,7 +2066,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 251; + CURRENT_PROJECT_VERSION = 253; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2103,7 +2103,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 251; + CURRENT_PROJECT_VERSION = 253; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2140,7 +2140,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 251; + CURRENT_PROJECT_VERSION = 253; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2191,7 +2191,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 251; + CURRENT_PROJECT_VERSION = 253; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2242,7 +2242,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 251; + CURRENT_PROJECT_VERSION = 253; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2276,7 +2276,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 251; + CURRENT_PROJECT_VERSION = 253; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index b490c26f87..e620b4992d 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -24,11 +24,11 @@ android.nonTransitiveRClass=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.2-beta.5 -android.version_code=256 +android.version_name=6.2-beta.6 +android.version_code=258 -desktop.version_name=6.2-beta.5 -desktop.version_code=80 +desktop.version_name=6.2-beta.6 +desktop.version_code=81 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From 19e2cebd6899bceb47ccf5126967b947ff18a020 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 6 Dec 2024 01:16:42 +0000 Subject: [PATCH 192/567] android, desktop: remove footer in Run chat section when chat is running --- .../chat/simplex/common/views/database/DatabaseView.kt | 8 +------- .../common/src/commonMain/resources/MR/ar/strings.xml | 1 - .../common/src/commonMain/resources/MR/base/strings.xml | 1 - .../common/src/commonMain/resources/MR/bg/strings.xml | 1 - .../common/src/commonMain/resources/MR/cs/strings.xml | 1 - .../common/src/commonMain/resources/MR/de/strings.xml | 1 - .../common/src/commonMain/resources/MR/es/strings.xml | 1 - .../common/src/commonMain/resources/MR/fa/strings.xml | 1 - .../common/src/commonMain/resources/MR/fi/strings.xml | 1 - .../common/src/commonMain/resources/MR/fr/strings.xml | 1 - .../common/src/commonMain/resources/MR/hu/strings.xml | 1 - .../common/src/commonMain/resources/MR/it/strings.xml | 1 - .../common/src/commonMain/resources/MR/iw/strings.xml | 1 - .../common/src/commonMain/resources/MR/ja/strings.xml | 1 - .../common/src/commonMain/resources/MR/ko/strings.xml | 1 - .../common/src/commonMain/resources/MR/lt/strings.xml | 1 - .../common/src/commonMain/resources/MR/nl/strings.xml | 1 - .../common/src/commonMain/resources/MR/pl/strings.xml | 1 - .../common/src/commonMain/resources/MR/pt-rBR/strings.xml | 1 - .../common/src/commonMain/resources/MR/pt/strings.xml | 1 - .../common/src/commonMain/resources/MR/ru/strings.xml | 1 - .../common/src/commonMain/resources/MR/th/strings.xml | 1 - .../common/src/commonMain/resources/MR/tr/strings.xml | 1 - .../common/src/commonMain/resources/MR/uk/strings.xml | 1 - .../common/src/commonMain/resources/MR/zh-rCN/strings.xml | 1 - .../common/src/commonMain/resources/MR/zh-rTW/strings.xml | 1 - 26 files changed, 1 insertion(+), 32 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index e1f53760e5..ab908e4c5f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -192,13 +192,7 @@ fun DatabaseLayout( } RunChatSetting(stopped, toggleEnabled && !progressIndicator, startChat, stopChatAlert) } - SectionTextFooter( - if (stopped) { - stringResource(MR.strings.you_must_use_the_most_recent_version_of_database) - } else { - stringResource(MR.strings.stop_chat_to_enable_database_actions) - } - ) + if (stopped) SectionTextFooter(stringResource(MR.strings.you_must_use_the_most_recent_version_of_database)) SectionDividerSpaced(maxTopPadding = true) } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index 259411688c..c67b6258a2 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -1049,7 +1049,6 @@ إيقاف مشاركة العنوان؟ إيقاف المشاركة أوقف الدردشة لتصدير أو استيراد أو حذف قاعدة بيانات الدردشة. لن تتمكّن من استلام الرسائل وإرسالها أثناء إيقاف الدردشة. - أوقف الدردشة لتمكين إجراءات قاعدة البيانات. %s ثانية/ثواني يبدأ… تم تشغيل القفل SimpleX diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 7fc46cf3a7..43ffded767 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1306,7 +1306,6 @@ Chat database deleted Restart the app to create a new chat profile. You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts. - Stop chat to enable database actions. Files & media Delete files for all chat profiles Delete all files diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml index 22e93b041a..c51b33e456 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml @@ -1133,7 +1133,6 @@ Запази настройките\? Високоговорителят е включен Високоговорителят е изключен - Спрете чата, за да активирате действията с базата данни. Роля Запази Нулирай цветовете diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml index a6ea5b1208..c80d24f0bd 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml @@ -280,7 +280,6 @@ Tuto akci nelze vzít zpět! Váš profil, kontakty, zprávy a soubory budou nenávratně ztraceny. Restartujte aplikaci a vytvořte nový chat profil. Nejnovější verzi databáze chatu musíte používat POUZE v jednom zařízení, jinak se může stát, že přestanete přijímat zprávy od některých kontaktů. - Zastavte chat a povolte akce s databází. Soubory a média Smazat soubory a média\? Odstranit zprávy diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index e90dd26aff..5ab956ae85 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -597,7 +597,6 @@ Chat-Datenbank gelöscht Starten Sie die App neu, um ein neues Chat-Profil zu erstellen. Sie dürfen die neueste Version Ihrer Chat-Datenbank NUR auf einem Gerät verwenden, andernfalls erhalten Sie möglicherweise keine Nachrichten mehr von einigen Ihrer Kontakte. - Chat beenden, um Datenbankaktionen zu erlauben. Dateien und Medien löschen? Diese Aktion kann nicht rückgängig gemacht werden! Es werden alle empfangenen und gesendeten Dateien und Medien gelöscht. Bilder mit niedriger Auflösung bleiben erhalten. Keine empfangenen oder gesendeten Dateien diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index 02f69b0550..0e53f59c06 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -733,7 +733,6 @@ Compartir enlace de un uso ¿Actualizar el modo de aislamiento de transporte\? Altavoz activado - Para habilitar las acciones sobre la base de datos, debes parar SimpleX ¡La conexión que has aceptado se cancelará! La base de datos no funciona correctamente. Pulsa para conocer más El mensaje será marcado como moderado para todos los miembros. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml index 7e59b69082..866506460c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml @@ -942,7 +942,6 @@ این عمل قابل برگشت نیست - تمام پرونده‌ها و رسانه دریافتی حذف خواهند شد. عکس‌های با کیفیت پایین باقی خواهند ماند. شما باید از تازه‌ترین نسخه پایگاه داده گپ خود روی فقط یک دستگاه استفاده کنید، در غیر این صورت ممکن است از بعضی از مخاطب‌ها ‌دیگر پیامی دریافت نکنید. پیام‌ها - به منظور فعال‌سازی اقدامات پایگاه داده، گپ را متوقف کنید. این عمل قابل برگشت نیست - پیام‌های ارسالی و دریافتی قدیمی‌تر از زمان انتخابی حذف خواهند شد. این کار ممکن است چندین دقیقه زمان ببرد. خطا در تغییر تنظیمات ذخیره عبارت عبور در تنظیمات diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml index 62a02986b2..26847aeaf5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml @@ -666,7 +666,6 @@ Itsetuhoutuva pääsykoodi Käynnistä sovellus uudelleen käyttääksesi tuotua keskustelutietokantaa. Vanha tietokanta-arkisto - Pysäytä keskustelu, jotta tietokantatoiminnot voidaan ottaa käyttöön. Luo uusi keskusteluprofiili käynnistämällä sovellus uudelleen. Viestit ei koskaan diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml index 5eea31e670..62891fd8b8 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml @@ -617,7 +617,6 @@ Erreur lors de l\'importation de la base de données du chat Base de données du chat importée Supprimer le profil du chat \? - Arrêter le chat pour agir sur la base de données. Supprimer les fichiers et médias \? Cette action ne peut être annulée - tous les fichiers et médias reçus et envoyés seront supprimés. Les photos à faible résolution seront conservées. Aucun fichier reçu ou envoyé diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index 34dc1227a7..83f408054f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -922,7 +922,6 @@ Csak az ismerőse tud hívást indítani. TÉMÁK Túl sok videó! - Csevegési szolgáltatás megállítása az adatbázis műveletek elvégzéséhez. Üdvözöljük! Önmegsemmisítési jelkód (beolvasás, vagy beillesztés a vágólapról) diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index 92c0105d09..c342319dbb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -874,7 +874,6 @@ Rimuovere la password dal Keystore\? Salva la password nel Keystore %s secondo/i - Ferma la chat per attivare le azioni del database. Questa azione non può essere annullata: tutti i file e i media ricevuti e inviati verranno eliminati. Rimarranno le immagini a bassa risoluzione. Questa azione non può essere annullata: i messaggi inviati e ricevuti prima di quanto selezionato verranno eliminati. Potrebbe richiedere diversi minuti. Aggiorna diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml index d9ddd08a57..2bb007b6e8 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml @@ -981,7 +981,6 @@ לעצור צ׳אט\? עיצרו את הצ׳אט כדי לייצא, לייבא או למחוק את מסד הנתונים. לא תוכלו לקבל ולשלוח הודעות בזמן שהצ׳אט מופסק. עצור - עיצרו את הצ׳אט כדי לאפשר פעולות מסד נתונים. דלג על הזמנת חברים שתף כתובת SimpleX diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml index c6775f6639..ff6b4e456c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml @@ -662,7 +662,6 @@ スピーカーオフ あなたのチャットデータベース 停止 - データベース操作をするにはチャットを停止する必要があります。 SimpleX連絡先アドレス SimpleX使い捨て招待リンク 連絡先アドレスリンク経由 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml index c03b4e648b..bf07c10a6f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml @@ -851,7 +851,6 @@ 제출하기 암호를 모르면 채팅에 액세스할 수 없으니 암호를 안전하게 보관해 주세요. 채팅 기능을 중지할까요\? - 데이터베이스 작업을 할 수 있도록 채팅 기능을 중지하기 수신 주소 바꾸기 복호화 오류 패스코드 확인 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml index 5db1442fc6..fbad2dc4e9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml @@ -1219,7 +1219,6 @@ Nustatyti duomenų slaptafrazę Nustatyti slaptafrazę Rodyti paskutines žinutes - Sustabdykite pokalbius, kad įgalinti duomenų bazės veiksmus. PALAIKYKITE SIMPLEX CHAT Jų galima nepaisyti kontaktų ir grupių nustatymuose. Šis veiksmas negali būti atšauktas - žinutės išsiųstos ir gautos anksčiau nei pasirinkta bus ištrintos. Tai gali užtrukti kelias minutes. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index 1a5eaa403d..f00182b469 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -740,7 +740,6 @@ Start de app opnieuw om een nieuw chatprofiel aan te maken. U mag ALLEEN de meest recente versie van uw chat-database op één apparaat gebruiken, anders ontvangt u mogelijk geen berichten meer van sommige contacten. Start de app opnieuw om de geïmporteerde chat database te gebruiken. - Stop de chat om database acties mogelijk te maken. Deze actie kan niet ongedaan worden gemaakt, alle ontvangen en verzonden bestanden en media worden verwijderd. Foto\'s met een lage resolutie blijven behouden. Wachtwoord verwijderen uit Keychain\? Verwijderen diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml index 56c80f8c89..a748bf2741 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml @@ -562,7 +562,6 @@ Uruchom ponownie aplikację, aby utworzyć nowy profil czatu. Zapisz hasło w Keystore %s sekund(y) - Zatrzymaj czat, aby umożliwić działania na bazie danych. Tego działania nie można cofnąć - wiadomości wysłane i odebrane wcześniej niż wybrane zostaną usunięte. Może to potrwać kilka minut. Tego działania nie można cofnąć - Twój profil, kontakty, wiadomości i pliki zostaną nieodwracalnie utracone. To ustawienie dotyczy wiadomości Twojego bieżącego profilu czatu diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml index 27cc039c34..3b139013fc 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml @@ -853,7 +853,6 @@ PROXY SOCKS A tentativa de alterar a senha do banco de dados não foi concluída. Pare o bate-papo para exportar, importar ou excluir o banco de dados do chat. Você não poderá receber e enviar mensagens enquanto o chat estiver interrompido. - Pare o bate-papo para ativar ações no banco de dados. %s segundo(s) Erro de banco de dados desconhecido: %s Erro desconhecido diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml index 5cdc67e9e5..c9db7de2e6 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml @@ -760,7 +760,6 @@ %s, %s e %d membros Iniciar nova conversa Sistema - Parar conversa para habilitar ações do banco de dados Toque para participar %s, %s e %s conectado Tempo esgotado da conexão TCP diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml index 9e39ad61a4..584377bc99 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -600,7 +600,6 @@ Данные чата удалены Перезапустите приложение, чтобы создать новый профиль. Используйте самую последнюю версию архива чата и ТОЛЬКО на одном устройстве, иначе Вы можете перестать получать сообщения от некоторых контактов. - Остановите чат, чтобы разблокировать операции с архивом чата. Удалить файлы во всех профилях чата Удалить все файлы Удалить файлы и медиа? diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml index a0027df1d8..ebf57836b5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml @@ -937,7 +937,6 @@ ติดดาวบน GitHub เปลี่ยน กำลังเริ่มต้น… - หยุดการแชทเพื่อเปิดใช้งานการดำเนินการกับฐานข้อมูล หยุดแชทเพื่อส่งออก นำเข้า หรือลบฐานข้อมูลแชท คุณจะไม่สามารถรับและส่งข้อความได้ในขณะที่การแชทหยุดลง รองรับบลูทูธและการปรับปรุงอื่นๆ หยุดแชร์ที่อยู่ไหม\? diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml index 67b6226b0e..503d82158f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml @@ -1034,7 +1034,6 @@ Dosyayı durdur Hata ProfilProfil oluştur - Veri tabanı eylemlerini etkinleştirmek için sohbeti durdur. Dosya göndermeyi durdur? Sohbeti durdur Mevcut profili kullan diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index 0afb405097..548e29f836 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -899,7 +899,6 @@ Увімкнути блокування Пароль не змінено! Змінити режим блокування - Зупиніть чат, щоб увімкнути дії з базою даних. Перезапустіть додаток, щоб створити новий профіль чату. Видалити файли для всіх профілів чату Видалити файли та медіа? diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index 41e9793ba1..ca42ccc902 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -799,7 +799,6 @@ 禁止向成员发送私信。 保护应用程序屏幕 主题 - 停止聊天以启用数据库操作。 %s 秒 该群已不存在。 点击加入 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml index 11a086f795..fd58811439 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml @@ -119,7 +119,6 @@ 聊天室已停止運作 停止 已刪除數據庫的對話內容 - 停止聊天室以啟用數據庫功能。 修改數據庫密碼? 確定要退出群組? 退出 From 9b82cc33030469012a12d48d266f17e43421978f Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 6 Dec 2024 10:18:48 +0000 Subject: [PATCH 193/567] core: fix feature items when updating preferences in business chats --- src/Simplex/Chat/Store/Groups.hs | 2 +- tests/ChatTests/Profiles.hs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 49158a60c9..36ce7f3575 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -1472,7 +1472,7 @@ updateGroupPreferences db User {userId} g@GroupInfo {groupId, groupProfile = p} ) |] (ps, currentTs, userId, groupId) - pure (g :: GroupInfo) {groupProfile = p {groupPreferences = Just ps}} + pure (g :: GroupInfo) {groupProfile = p {groupPreferences = Just ps}, fullGroupPreferences = mergeGroupPreferences $ Just ps} updateGroupProfileFromMember :: DB.Connection -> User -> GroupInfo -> Profile -> ExceptT StoreError IO GroupInfo updateGroupProfileFromMember db user g@GroupInfo {groupId} Profile {displayName = n, fullName = fn, image = img} = do diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index 2bf157419c..71edbb93b0 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -857,6 +857,10 @@ testBusinessUpdateProfiles = testChat4 businessProfile aliceProfile bobProfile c cath <## "updated group preferences:" cath <## "Voice messages: on" ] + biz #$> ("/_get chat #1 count=1", chat, [(1, "Voice messages: on")]) + alice #$> ("/_get chat #1 count=1", chat, [(0, "Voice messages: on")]) + bob #$> ("/_get chat #1 count=1", chat, [(0, "Voice messages: on")]) + cath #$> ("/_get chat #1 count=1", chat, [(0, "Voice messages: on")]) testPlanAddressOkKnown :: HasCallStack => FilePath -> IO () testPlanAddressOkKnown = From 924273191ec1dd34d13e94f43ce11872bfc06671 Mon Sep 17 00:00:00 2001 From: Diogo Date: Fri, 6 Dec 2024 10:21:58 +0000 Subject: [PATCH 194/567] ios: ask for confirmation of save on group preferences sheet dismiss (#5327) * ios: ask for confirmation of save on group preferences sheet dismiss * fix exit without saving temporary state and also apply fix on dismiss during group creation --- apps/ios/Shared/Views/Chat/ChatView.swift | 3 +- .../Chat/Group/AddGroupMembersView.swift | 5 +- .../Views/Chat/Group/GroupChatInfoView.swift | 26 ++++++++-- .../Chat/Group/GroupPreferencesView.swift | 47 +++++++++++-------- .../Shared/Views/NewChat/AddGroupView.swift | 1 + 5 files changed, 54 insertions(+), 28 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index cfbbfe6080..f2f25ff272 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -246,7 +246,8 @@ struct ChatView: View { chat.created = Date.now } ), - onSearch: { focusSearch() } + onSearch: { focusSearch() }, + preferences: groupInfo.fullGroupPreferences ) } } else if case .local = cInfo { diff --git a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift index 925f4120bc..0d03b21ca0 100644 --- a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift +++ b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift @@ -15,7 +15,7 @@ struct AddGroupMembersView: View { var groupInfo: GroupInfo var body: some View { - AddGroupMembersViewCommon(chat: chat, groupInfo: groupInfo, addedMembersCb: { _ in dismiss() }) + AddGroupMembersViewCommon(chat: chat, groupInfo: groupInfo, preferences: groupInfo.fullGroupPreferences, addedMembersCb: { _ in dismiss() }) } } @@ -24,6 +24,7 @@ struct AddGroupMembersViewCommon: View { @EnvironmentObject var theme: AppTheme var chat: Chat @State var groupInfo: GroupInfo + @State var preferences: FullGroupPreferences var creatingGroup: Bool = false var showFooterCounter: Bool = true var addedMembersCb: ((Set) -> Void) @@ -78,7 +79,7 @@ struct AddGroupMembersViewCommon: View { let count = selectedContacts.count Section { if creatingGroup { - groupPreferencesButton($groupInfo, true) + groupPreferencesButton($groupInfo, $preferences, true) } rolePicker() inviteMembersButton() diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index 27aa0edb5b..640b8c6b1d 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -19,6 +19,7 @@ struct GroupChatInfoView: View { @Binding var groupInfo: GroupInfo var onSearch: () -> Void @State private var alert: GroupChatInfoViewAlert? = nil + @State var preferences: FullGroupPreferences @State private var groupLink: String? @State private var groupLinkMemberRole: GroupMemberRole = .member @State private var groupLinkNavLinkActive: Bool = false @@ -87,7 +88,7 @@ struct GroupChatInfoView: View { if groupInfo.groupProfile.description != nil || (groupInfo.isOwner && groupInfo.businessChat == nil) { addOrEditWelcomeMessage() } - groupPreferencesButton($groupInfo) + groupPreferencesButton($groupInfo, $preferences) if members.filter({ $0.wrapped.memberCurrent }).count <= SMALL_GROUPS_RCPS_MEM_LIMIT { sendReceiptsOption() } else { @@ -654,18 +655,32 @@ func deleteGroupAlertMessage(_ groupInfo: GroupInfo) -> Text { ) } -func groupPreferencesButton(_ groupInfo: Binding, _ creatingGroup: Bool = false) -> some View { +func groupPreferencesButton(_ groupInfo: Binding, _ preferences: Binding, _ creatingGroup: Bool = false) -> some View { let label: LocalizedStringKey = groupInfo.wrappedValue.businessChat == nil ? "Group preferences" : "Chat preferences" return NavigationLink { GroupPreferencesView( groupInfo: groupInfo, - preferences: groupInfo.wrappedValue.fullGroupPreferences, - currentPreferences: groupInfo.wrappedValue.fullGroupPreferences, + preferences: preferences, + currentPreferences: groupInfo.fullGroupPreferences, creatingGroup: creatingGroup ) .navigationBarTitle(label) .modifier(ThemedBackground(grouped: true)) .navigationBarTitleDisplayMode(.large) + .onDisappear { + let saveText = NSLocalizedString(creatingGroup ? "Save" : "Save and notify group members", comment: "alert button") + + if groupInfo.fullGroupPreferences.wrappedValue != preferences.wrappedValue { + showAlert( + title: NSLocalizedString("Save preferences?", comment: "alert title"), + buttonTitle: saveText, + buttonAction: { + savePreferences(groupInfo: groupInfo, preferences: preferences, currentPreferences: groupInfo.fullGroupPreferences) + }, + cancelButton: true + ) + } + } } label: { if creatingGroup { Text("Set group preferences") @@ -694,7 +709,8 @@ struct GroupChatInfoView_Previews: PreviewProvider { GroupChatInfoView( chat: Chat(chatInfo: ChatInfo.sampleData.group, chatItems: []), groupInfo: Binding.constant(GroupInfo.sampleData), - onSearch: {} + onSearch: {}, + preferences: GroupInfo.sampleData.fullGroupPreferences ) } } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift index bbbbe4d4c3..b27ff37d95 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift @@ -20,8 +20,8 @@ struct GroupPreferencesView: View { @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var theme: AppTheme @Binding var groupInfo: GroupInfo - @State var preferences: FullGroupPreferences - @State var currentPreferences: FullGroupPreferences + @Binding var preferences: FullGroupPreferences + @Binding var currentPreferences: FullGroupPreferences let creatingGroup: Bool @State private var showSaveDialogue = false @@ -41,7 +41,7 @@ struct GroupPreferencesView: View { if groupInfo.isOwner { Section { Button("Reset") { preferences = currentPreferences } - Button(saveText) { savePreferences() } + Button(saveText) { savePreferences(groupInfo: $groupInfo, preferences: $preferences, currentPreferences: $currentPreferences) } } .disabled(currentPreferences == preferences) } @@ -65,10 +65,13 @@ struct GroupPreferencesView: View { }) .confirmationDialog("Save preferences?", isPresented: $showSaveDialogue) { Button(saveText) { - savePreferences() + savePreferences(groupInfo: $groupInfo, preferences: $preferences, currentPreferences: $currentPreferences) + dismiss() + } + Button("Exit without saving") { + preferences = currentPreferences dismiss() } - Button("Exit without saving") { dismiss() } } } @@ -132,21 +135,25 @@ struct GroupPreferencesView: View { } } } +} - private func savePreferences() { - Task { - do { - var gp = groupInfo.groupProfile - gp.groupPreferences = toGroupPreferences(preferences) - let gInfo = try await apiUpdateGroup(groupInfo.groupId, gp) - await MainActor.run { - groupInfo = gInfo - chatModel.updateGroup(gInfo) - currentPreferences = preferences - } - } catch { - logger.error("GroupPreferencesView apiUpdateGroup error: \(responseError(error))") +func savePreferences( + groupInfo: Binding, + preferences: Binding, + currentPreferences: Binding +) { + Task { + do { + var gp = groupInfo.groupProfile.wrappedValue + gp.groupPreferences = toGroupPreferences(preferences.wrappedValue) + let gInfo = try await apiUpdateGroup(groupInfo.groupId.wrappedValue, gp) + await MainActor.run { + groupInfo.wrappedValue = gInfo + ChatModel.shared.updateGroup(gInfo) + currentPreferences.wrappedValue = preferences.wrappedValue } + } catch { + logger.error("GroupPreferencesView apiUpdateGroup error: \(responseError(error))") } } } @@ -155,8 +162,8 @@ struct GroupPreferencesView_Previews: PreviewProvider { static var previews: some View { GroupPreferencesView( groupInfo: Binding.constant(GroupInfo.sampleData), - preferences: FullGroupPreferences.sampleData, - currentPreferences: FullGroupPreferences.sampleData, + preferences: Binding.constant(FullGroupPreferences.sampleData), + currentPreferences: Binding.constant(FullGroupPreferences.sampleData), creatingGroup: false ) } diff --git a/apps/ios/Shared/Views/NewChat/AddGroupView.swift b/apps/ios/Shared/Views/NewChat/AddGroupView.swift index 0c7f6136ff..5207c3a472 100644 --- a/apps/ios/Shared/Views/NewChat/AddGroupView.swift +++ b/apps/ios/Shared/Views/NewChat/AddGroupView.swift @@ -32,6 +32,7 @@ struct AddGroupView: View { AddGroupMembersViewCommon( chat: chat, groupInfo: groupInfo, + preferences: groupInfo.fullGroupPreferences, creatingGroup: true, showFooterCounter: false ) { _ in From 2e431c5afa7f62972daf6740cc6c7edb5cc36936 Mon Sep 17 00:00:00 2001 From: Diogo Date: Fri, 6 Dec 2024 11:10:52 +0000 Subject: [PATCH 195/567] ios: fix some real time updates in group members (#5332) * ios: fix some real time updates in group members * use chat instead of binding for group info updates --- apps/ios/Shared/Views/Chat/ChatView.swift | 9 ++++++++- apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift | 2 +- .../Shared/Views/Chat/Group/GroupMemberInfoView.swift | 7 +++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index f2f25ff272..b436147a8e 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -133,7 +133,12 @@ struct ChatView: View { .appSheet(item: $selectedMember) { member in Group { if case let .group(groupInfo) = chat.chatInfo { - GroupMemberInfoView(groupInfo: groupInfo, groupMember: member, navigation: true) + GroupMemberInfoView( + groupInfo: groupInfo, + chat: chat, + groupMember: member, + navigation: true + ) } } } @@ -1123,6 +1128,7 @@ struct ChatView: View { } else { let mem = GMember.init(member) m.groupMembers.append(mem) + m.groupMembersIndexes[member.groupMemberId] = m.groupMembers.count - 1 selectedMember = mem } } @@ -1878,6 +1884,7 @@ struct ReactionContextMenu: View { } else { let member = GMember.init(mem) m.groupMembers.append(member) + m.groupMembersIndexes[member.groupMemberId] = m.groupMembers.count - 1 selectedMember = member } } label: { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index 640b8c6b1d..7dc57f9642 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -440,7 +440,7 @@ struct GroupChatInfoView: View { } private func memberInfoView(_ groupMember: GMember) -> some View { - GroupMemberInfoView(groupInfo: groupInfo, groupMember: groupMember) + GroupMemberInfoView(groupInfo: groupInfo, chat: chat, groupMember: groupMember) .navigationBarHidden(false) } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index 90d6829d93..b73c5e10f5 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -14,6 +14,7 @@ struct GroupMemberInfoView: View { @EnvironmentObject var theme: AppTheme @Environment(\.dismiss) var dismiss: DismissAction @State var groupInfo: GroupInfo + @ObservedObject var chat: Chat @ObservedObject var groupMember: GMember var navigation: Bool = false @State private var connectionStats: ConnectionStats? = nil @@ -261,6 +262,11 @@ struct GroupMemberInfoView: View { ProgressView().scaleEffect(2) } } + .onChange(of: chat.chatInfo) { c in + if case let .group(gI) = chat.chatInfo { + groupInfo = gI + } + } .modifier(ThemedBackground(grouped: true)) } @@ -758,6 +764,7 @@ struct GroupMemberInfoView_Previews: PreviewProvider { static var previews: some View { GroupMemberInfoView( groupInfo: GroupInfo.sampleData, + chat: Chat.sampleData, groupMember: GMember.sampleData ) } From 945c5015d8b6cd0d0ac660c0c6ab3e5ecf3b91ff Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:35:26 +0400 Subject: [PATCH 196/567] ui: improve pending connection texts (#5333) * ui: improve contact request text * android * ternary * shorter * kotlin * change --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/SimpleXChat/ChatTypes.swift | 6 ++++-- .../kotlin/chat/simplex/common/model/ChatModel.kt | 5 +++-- .../common/src/commonMain/resources/MR/base/strings.xml | 2 ++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index a2d44d59d0..da1ce24b73 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -1782,9 +1782,11 @@ public struct PendingContactConnection: Decodable, NamedChat, Hashable { public var displayName: String { get { if let initiated = pccConnStatus.initiated { - return initiated && !viaContactUri + return viaContactUri + ? NSLocalizedString("requested to connect", comment: "chat list item title") + : initiated ? NSLocalizedString("invited to connect", comment: "chat list item title") - : NSLocalizedString("connecting…", comment: "chat list item title") + : NSLocalizedString("accepted invitation", comment: "chat list item title") } else { // this should not be in the list return NSLocalizedString("connection established", comment: "chat list item title (it should not be shown") diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index b9e52763a6..d407174e52 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -1895,8 +1895,9 @@ class PendingContactConnection( generalGetString(MR.strings.display_name_connection_established) } else { generalGetString( - if (initiated && !viaContactUri) MR.strings.display_name_invited_to_connect - else MR.strings.display_name_connecting + if (viaContactUri) MR.strings.display_name_requested_to_connect + else if (initiated) MR.strings.display_name_invited_to_connect + else MR.strings.display_name_accepted_invitation ) } } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 43ffded767..c412ec42ee 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -71,6 +71,8 @@ connection %1$d connection established invited to connect + requested to connect + accepted invitation connecting… you shared one-time link you shared one-time link incognito From f4089880353b1de274d846d2f7b8dee37dcff960 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 6 Dec 2024 17:05:39 +0400 Subject: [PATCH 197/567] ios: fix oneHandUI setting becoming enabled on import (#5335) --- apps/ios/SimpleXChat/APITypes.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 2bd76dea63..a9cf2ee599 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -2725,7 +2725,7 @@ public struct AppSettings: Codable, Equatable { uiDarkColorScheme: DefaultTheme.SIMPLEX.themeName, uiCurrentThemeIds: nil as [String: String]?, uiThemes: nil as [ThemeOverrides]?, - oneHandUI: false, + oneHandUI: true, chatBottomBar: true ) } From 1408d75eb333c06e3ab49758cb1a1ae56aa63f3c Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 6 Dec 2024 17:21:55 +0400 Subject: [PATCH 198/567] ios: use async getServerOperators api (#5334) --- apps/ios/Shared/Model/SimpleXAPI.swift | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index e29748f3af..51be3191ec 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -519,7 +519,14 @@ func testProtoServer(server: String) async throws -> Result<(), ProtocolTestFail throw r } -func getServerOperators() throws -> ServerOperatorConditions { +func getServerOperators() async throws -> ServerOperatorConditions { + let r = await chatSendCmd(.apiGetServerOperators) + if case let .serverOperatorConditions(conditions) = r { return conditions } + logger.error("getServerOperators error: \(String(describing: r))") + throw r +} + +func getServerOperatorsSync() throws -> ServerOperatorConditions { let r = chatSendCmdSync(.apiGetServerOperators) if case let .serverOperatorConditions(conditions) = r { return conditions } logger.error("getServerOperators error: \(String(describing: r))") @@ -1599,7 +1606,7 @@ func initializeChat(start: Bool, confirmStart: Bool = false, dbKey: String? = ni try apiSetEncryptLocalFiles(privacyEncryptLocalFilesGroupDefault.get()) m.chatInitialized = true m.currentUser = try apiGetActiveUser() - m.conditions = try getServerOperators() + m.conditions = try getServerOperatorsSync() if shouldImportAppSettingsDefault.get() { do { let appSettings = try apiGetAppSettings(settings: AppSettings.current.prepareForExport()) From ae8ad5c639a9d4a8af925e78d336fa7e33598195 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 6 Dec 2024 17:49:57 +0400 Subject: [PATCH 199/567] ios: operators info on onboarding (#5336) --- .../Onboarding/ChooseServerOperators.swift | 56 ++++++++++++++----- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift index 318e0b2f0d..cc47374257 100644 --- a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift +++ b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift @@ -409,26 +409,54 @@ struct ChooseServerOperators: View { let operatorsPostLink = URL(string: "https://simplex.chat/blog/20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.html")! struct ChooseServerOperatorsInfoView: View { + @Environment(\.colorScheme) var colorScheme: ColorScheme + @EnvironmentObject var theme: AppTheme + var body: some View { - VStack(alignment: .leading) { - Text("Server operators") - .font(.largeTitle) - .bold() - .padding(.vertical) - ScrollView { + NavigationView { + List { VStack(alignment: .leading) { - Group { - Text("The app protects your privacy by using different operators in each conversation.") - Text("When more than one operator is enabled, none of them has metadata to learn who communicates with whom.") - Text("For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server.") + Text("The app protects your privacy by using different operators in each conversation.") + .padding(.bottom) + Text("When more than one operator is enabled, none of them has metadata to learn who communicates with whom.") + .padding(.bottom) + Text("For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server.") + } + .listRowBackground(Color.clear) + .listRowSeparator(.hidden) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + .padding(.top) + + Section { + ForEach(ChatModel.shared.conditions.serverOperators) { op in + operatorInfoNavLinkView(op) } - .padding(.bottom) + } header: { + Text("About operators") + .foregroundColor(theme.colors.secondary) } } + .navigationTitle("Server operators") + .navigationBarTitleDisplayMode(.large) + .modifier(ThemedBackground(grouped: true)) + } + } + + private func operatorInfoNavLinkView(_ op: ServerOperator) -> some View { + NavigationLink() { + OperatorInfoView(serverOperator: op) + .navigationBarTitle("Network operator") + .modifier(ThemedBackground(grouped: true)) + .navigationBarTitleDisplayMode(.large) + } label: { + HStack { + Image(op.logo(colorScheme)) + .resizable() + .scaledToFit() + .frame(width: 24, height: 24) + Text(op.tradeName) + } } - .padding() - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) - .modifier(ThemedBackground()) } } From 7d43a43e826158c9040cd11ee593a13a2b112aaa Mon Sep 17 00:00:00 2001 From: Diogo Date: Fri, 6 Dec 2024 14:44:56 +0000 Subject: [PATCH 200/567] ios: ask for confirmation of save on contact preferences sheet dismiss (#5337) --- apps/ios/Shared/Views/Chat/ChatInfoView.swift | 36 +++++++++++++++++-- apps/ios/Shared/Views/Chat/ChatView.swift | 2 ++ .../Views/Chat/ContactPreferencesView.swift | 32 ++++++----------- 3 files changed, 46 insertions(+), 24 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index c829e1a2b9..ea9daa74bc 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -96,6 +96,8 @@ struct ChatInfoView: View { @ObservedObject var chat: Chat @State var contact: Contact @State var localAlias: String + @State var featuresAllowed: ContactFeaturesAllowed + @State var currentFeaturesAllowed: ContactFeaturesAllowed var onSearch: () -> Void @State private var connectionStats: ConnectionStats? = nil @State private var customUserProfile: Profile? = nil @@ -327,6 +329,16 @@ struct ChatInfoView: View { $0.content } } + .onDisappear { + if currentFeaturesAllowed != featuresAllowed { + showAlert( + title: NSLocalizedString("Save preferences?", comment: "alert title"), + buttonTitle: NSLocalizedString("Save and notify contact", comment: "alert button"), + buttonAction: { savePreferences() }, + cancelButton: true + ) + } + } } private func contactInfoHeader() -> some View { @@ -447,8 +459,9 @@ struct ChatInfoView: View { NavigationLink { ContactPreferencesView( contact: $contact, - featuresAllowed: contactUserPrefsToFeaturesAllowed(contact.mergedPreferences), - currentFeaturesAllowed: contactUserPrefsToFeaturesAllowed(contact.mergedPreferences) + featuresAllowed: $featuresAllowed, + currentFeaturesAllowed: $currentFeaturesAllowed, + savePreferences: savePreferences ) .navigationBarTitle("Contact preferences") .modifier(ThemedBackground(grouped: true)) @@ -617,6 +630,23 @@ struct ChatInfoView: View { } } } + + private func savePreferences() { + Task { + do { + let prefs = contactFeaturesAllowedToPrefs(featuresAllowed) + if let toContact = try await apiSetContactPrefs(contactId: contact.contactId, preferences: prefs) { + await MainActor.run { + contact = toContact + chatModel.updateContact(toContact) + currentFeaturesAllowed = featuresAllowed + } + } + } catch { + logger.error("ContactPreferencesView apiSetContactPrefs error: \(responseError(error))") + } + } + } } struct AudioCallButton: View { @@ -1173,6 +1203,8 @@ struct ChatInfoView_Previews: PreviewProvider { chat: Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []), contact: Contact.sampleData, localAlias: "", + featuresAllowed: contactUserPrefsToFeaturesAllowed(Contact.sampleData.mergedPreferences), + currentFeaturesAllowed: contactUserPrefsToFeaturesAllowed(Contact.sampleData.mergedPreferences), onSearch: {} ) } diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index b436147a8e..df5fde9e7a 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -231,6 +231,8 @@ struct ChatView: View { chat: chat, contact: contact, localAlias: chat.chatInfo.localAlias, + featuresAllowed: contactUserPrefsToFeaturesAllowed(contact.mergedPreferences), + currentFeaturesAllowed: contactUserPrefsToFeaturesAllowed(contact.mergedPreferences), onSearch: { focusSearch() } ) } diff --git a/apps/ios/Shared/Views/Chat/ContactPreferencesView.swift b/apps/ios/Shared/Views/Chat/ContactPreferencesView.swift index b3fab958bc..e4489e46ee 100644 --- a/apps/ios/Shared/Views/Chat/ContactPreferencesView.swift +++ b/apps/ios/Shared/Views/Chat/ContactPreferencesView.swift @@ -14,9 +14,10 @@ struct ContactPreferencesView: View { @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var theme: AppTheme @Binding var contact: Contact - @State var featuresAllowed: ContactFeaturesAllowed - @State var currentFeaturesAllowed: ContactFeaturesAllowed + @Binding var featuresAllowed: ContactFeaturesAllowed + @Binding var currentFeaturesAllowed: ContactFeaturesAllowed @State private var showSaveDialogue = false + let savePreferences: () -> Void var body: some View { let user: User = chatModel.currentUser! @@ -48,7 +49,10 @@ struct ContactPreferencesView: View { savePreferences() dismiss() } - Button("Exit without saving") { dismiss() } + Button("Exit without saving") { + featuresAllowed = currentFeaturesAllowed + dismiss() + } } } @@ -118,31 +122,15 @@ struct ContactPreferencesView: View { private func featureFooter(_ feature: ChatFeature, _ enabled: FeatureEnabled) -> some View { Text(feature.enabledDescription(enabled)) } - - private func savePreferences() { - Task { - do { - let prefs = contactFeaturesAllowedToPrefs(featuresAllowed) - if let toContact = try await apiSetContactPrefs(contactId: contact.contactId, preferences: prefs) { - await MainActor.run { - contact = toContact - chatModel.updateContact(toContact) - currentFeaturesAllowed = featuresAllowed - } - } - } catch { - logger.error("ContactPreferencesView apiSetContactPrefs error: \(responseError(error))") - } - } - } } struct ContactPreferencesView_Previews: PreviewProvider { static var previews: some View { ContactPreferencesView( contact: Binding.constant(Contact.sampleData), - featuresAllowed: ContactFeaturesAllowed.sampleData, - currentFeaturesAllowed: ContactFeaturesAllowed.sampleData + featuresAllowed: Binding.constant(ContactFeaturesAllowed.sampleData), + currentFeaturesAllowed: Binding.constant(ContactFeaturesAllowed.sampleData), + savePreferences: {} ) } } From df1a471c563a7ffa556a12bc1e4f95915e2038e4 Mon Sep 17 00:00:00 2001 From: Diogo Date: Fri, 6 Dec 2024 15:55:15 +0000 Subject: [PATCH 201/567] ios: remove all unsafe warnings in group preferences save (#5340) --- apps/ios/Shared/Views/Chat/ChatView.swift | 3 +- .../Chat/Group/AddGroupMembersView.swift | 10 +- .../Views/Chat/Group/GroupChatInfoView.swift | 93 ++++++++++++------- .../Chat/Group/GroupPreferencesView.swift | 33 ++----- .../Shared/Views/NewChat/AddGroupView.swift | 1 - 5 files changed, 76 insertions(+), 64 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index df5fde9e7a..0c5a458930 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -253,8 +253,7 @@ struct ChatView: View { chat.created = Date.now } ), - onSearch: { focusSearch() }, - preferences: groupInfo.fullGroupPreferences + onSearch: { focusSearch() } ) } } else if case .local = cInfo { diff --git a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift index 0d03b21ca0..bdef8d0a62 100644 --- a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift +++ b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift @@ -15,7 +15,7 @@ struct AddGroupMembersView: View { var groupInfo: GroupInfo var body: some View { - AddGroupMembersViewCommon(chat: chat, groupInfo: groupInfo, preferences: groupInfo.fullGroupPreferences, addedMembersCb: { _ in dismiss() }) + AddGroupMembersViewCommon(chat: chat, groupInfo: groupInfo, addedMembersCb: { _ in dismiss() }) } } @@ -24,7 +24,6 @@ struct AddGroupMembersViewCommon: View { @EnvironmentObject var theme: AppTheme var chat: Chat @State var groupInfo: GroupInfo - @State var preferences: FullGroupPreferences var creatingGroup: Bool = false var showFooterCounter: Bool = true var addedMembersCb: ((Set) -> Void) @@ -79,7 +78,12 @@ struct AddGroupMembersViewCommon: View { let count = selectedContacts.count Section { if creatingGroup { - groupPreferencesButton($groupInfo, $preferences, true) + GroupPreferencesButton( + groupInfo: $groupInfo, + preferences: groupInfo.fullGroupPreferences, + currentPreferences: groupInfo.fullGroupPreferences, + creatingGroup: true + ) } rolePicker() inviteMembersButton() diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index 7dc57f9642..c4df91bb8b 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -19,7 +19,6 @@ struct GroupChatInfoView: View { @Binding var groupInfo: GroupInfo var onSearch: () -> Void @State private var alert: GroupChatInfoViewAlert? = nil - @State var preferences: FullGroupPreferences @State private var groupLink: String? @State private var groupLinkMemberRole: GroupMemberRole = .member @State private var groupLinkNavLinkActive: Bool = false @@ -88,7 +87,7 @@ struct GroupChatInfoView: View { if groupInfo.groupProfile.description != nil || (groupInfo.isOwner && groupInfo.businessChat == nil) { addOrEditWelcomeMessage() } - groupPreferencesButton($groupInfo, $preferences) + GroupPreferencesButton(groupInfo: $groupInfo, preferences: groupInfo.fullGroupPreferences, currentPreferences: groupInfo.fullGroupPreferences) if members.filter({ $0.wrapped.memberCurrent }).count <= SMALL_GROUPS_RCPS_MEM_LIMIT { sendReceiptsOption() } else { @@ -655,41 +654,72 @@ func deleteGroupAlertMessage(_ groupInfo: GroupInfo) -> Text { ) } -func groupPreferencesButton(_ groupInfo: Binding, _ preferences: Binding, _ creatingGroup: Bool = false) -> some View { - let label: LocalizedStringKey = groupInfo.wrappedValue.businessChat == nil ? "Group preferences" : "Chat preferences" - return NavigationLink { - GroupPreferencesView( - groupInfo: groupInfo, - preferences: preferences, - currentPreferences: groupInfo.fullGroupPreferences, - creatingGroup: creatingGroup - ) - .navigationBarTitle(label) - .modifier(ThemedBackground(grouped: true)) - .navigationBarTitleDisplayMode(.large) - .onDisappear { - let saveText = NSLocalizedString(creatingGroup ? "Save" : "Save and notify group members", comment: "alert button") - - if groupInfo.fullGroupPreferences.wrappedValue != preferences.wrappedValue { - showAlert( - title: NSLocalizedString("Save preferences?", comment: "alert title"), - buttonTitle: saveText, - buttonAction: { - savePreferences(groupInfo: groupInfo, preferences: preferences, currentPreferences: groupInfo.fullGroupPreferences) - }, - cancelButton: true +struct GroupPreferencesButton: View { + @Binding var groupInfo: GroupInfo + @State var preferences: FullGroupPreferences + @State var currentPreferences: FullGroupPreferences + var creatingGroup: Bool = false + + private var label: LocalizedStringKey { + groupInfo.businessChat == nil ? "Group preferences" : "Chat preferences" + } + + var body: some View { + NavigationLink { + GroupPreferencesView( + groupInfo: $groupInfo, + preferences: $preferences, + currentPreferences: currentPreferences, + creatingGroup: creatingGroup, + savePreferences: savePreferences + ) + .navigationBarTitle(label) + .modifier(ThemedBackground(grouped: true)) + .navigationBarTitleDisplayMode(.large) + .onDisappear { + let saveText = NSLocalizedString( + creatingGroup ? "Save" : "Save and notify group members", + comment: "alert button" ) + + if groupInfo.fullGroupPreferences != preferences { + showAlert( + title: NSLocalizedString("Save preferences?", comment: "alert title"), + buttonTitle: saveText, + buttonAction: { savePreferences() }, + cancelButton: true + ) + } + } + } label: { + if creatingGroup { + Text("Set group preferences") + } else { + Label(label, systemImage: "switch.2") } } - } label: { - if creatingGroup { - Text("Set group preferences") - } else { - Label(label, systemImage: "switch.2") + } + + private func savePreferences() { + Task { + do { + var gp = groupInfo.groupProfile + gp.groupPreferences = toGroupPreferences(preferences) + let gInfo = try await apiUpdateGroup(groupInfo.groupId, gp) + await MainActor.run { + groupInfo = gInfo + ChatModel.shared.updateGroup(gInfo) + currentPreferences = preferences + } + } catch { + logger.error("GroupPreferencesView apiUpdateGroup error: \(responseError(error))") + } } } + } + func cantInviteIncognitoAlert() -> Alert { Alert( title: Text("Can't invite contacts!"), @@ -709,8 +739,7 @@ struct GroupChatInfoView_Previews: PreviewProvider { GroupChatInfoView( chat: Chat(chatInfo: ChatInfo.sampleData.group, chatItems: []), groupInfo: Binding.constant(GroupInfo.sampleData), - onSearch: {}, - preferences: GroupInfo.sampleData.fullGroupPreferences + onSearch: {} ) } } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift index b27ff37d95..9ef53258aa 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift @@ -21,8 +21,9 @@ struct GroupPreferencesView: View { @EnvironmentObject var theme: AppTheme @Binding var groupInfo: GroupInfo @Binding var preferences: FullGroupPreferences - @Binding var currentPreferences: FullGroupPreferences + var currentPreferences: FullGroupPreferences let creatingGroup: Bool + let savePreferences: () -> Void @State private var showSaveDialogue = false var body: some View { @@ -41,7 +42,7 @@ struct GroupPreferencesView: View { if groupInfo.isOwner { Section { Button("Reset") { preferences = currentPreferences } - Button(saveText) { savePreferences(groupInfo: $groupInfo, preferences: $preferences, currentPreferences: $currentPreferences) } + Button(saveText) { savePreferences() } } .disabled(currentPreferences == preferences) } @@ -65,7 +66,7 @@ struct GroupPreferencesView: View { }) .confirmationDialog("Save preferences?", isPresented: $showSaveDialogue) { Button(saveText) { - savePreferences(groupInfo: $groupInfo, preferences: $preferences, currentPreferences: $currentPreferences) + savePreferences() dismiss() } Button("Exit without saving") { @@ -137,34 +138,14 @@ struct GroupPreferencesView: View { } } -func savePreferences( - groupInfo: Binding, - preferences: Binding, - currentPreferences: Binding -) { - Task { - do { - var gp = groupInfo.groupProfile.wrappedValue - gp.groupPreferences = toGroupPreferences(preferences.wrappedValue) - let gInfo = try await apiUpdateGroup(groupInfo.groupId.wrappedValue, gp) - await MainActor.run { - groupInfo.wrappedValue = gInfo - ChatModel.shared.updateGroup(gInfo) - currentPreferences.wrappedValue = preferences.wrappedValue - } - } catch { - logger.error("GroupPreferencesView apiUpdateGroup error: \(responseError(error))") - } - } -} - struct GroupPreferencesView_Previews: PreviewProvider { static var previews: some View { GroupPreferencesView( groupInfo: Binding.constant(GroupInfo.sampleData), preferences: Binding.constant(FullGroupPreferences.sampleData), - currentPreferences: Binding.constant(FullGroupPreferences.sampleData), - creatingGroup: false + currentPreferences: FullGroupPreferences.sampleData, + creatingGroup: false, + savePreferences: {} ) } } diff --git a/apps/ios/Shared/Views/NewChat/AddGroupView.swift b/apps/ios/Shared/Views/NewChat/AddGroupView.swift index 5207c3a472..0c7f6136ff 100644 --- a/apps/ios/Shared/Views/NewChat/AddGroupView.swift +++ b/apps/ios/Shared/Views/NewChat/AddGroupView.swift @@ -32,7 +32,6 @@ struct AddGroupView: View { AddGroupMembersViewCommon( chat: chat, groupInfo: groupInfo, - preferences: groupInfo.fullGroupPreferences, creatingGroup: true, showFooterCounter: false ) { _ in From 362581432cb9b7f236d9807b3efa1637a6ff0e4e Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 6 Dec 2024 20:01:55 +0400 Subject: [PATCH 202/567] ios: export localizations, add translations (#5339) * ios: export localizations, add translations * import --- .../bg.xcloc/Localized Contents/bg.xliff | 16 ++++++++-- .../cs.xcloc/Localized Contents/cs.xliff | 16 ++++++++-- .../de.xcloc/Localized Contents/de.xliff | 16 ++++++++-- .../en.xcloc/Localized Contents/en.xliff | 19 ++++++++++-- .../es.xcloc/Localized Contents/es.xliff | 16 ++++++++-- .../fi.xcloc/Localized Contents/fi.xliff | 16 ++++++++-- .../fr.xcloc/Localized Contents/fr.xliff | 16 ++++++++-- .../hu.xcloc/Localized Contents/hu.xliff | 16 ++++++++-- .../it.xcloc/Localized Contents/it.xliff | 16 ++++++++-- .../ja.xcloc/Localized Contents/ja.xliff | 16 ++++++++-- .../nl.xcloc/Localized Contents/nl.xliff | 16 ++++++++-- .../pl.xcloc/Localized Contents/pl.xliff | 16 ++++++++-- .../ru.xcloc/Localized Contents/ru.xliff | 21 +++++++++++-- .../th.xcloc/Localized Contents/th.xliff | 16 ++++++++-- .../tr.xcloc/Localized Contents/tr.xliff | 16 ++++++++-- .../uk.xcloc/Localized Contents/uk.xliff | 16 ++++++++-- .../Localized Contents/zh-Hans.xliff | 16 ++++++++-- apps/ios/bg.lproj/Localizable.strings | 4 +-- apps/ios/cs.lproj/Localizable.strings | 4 +-- apps/ios/de.lproj/Localizable.strings | 22 ++++++------- apps/ios/es.lproj/Localizable.strings | 22 ++++++------- apps/ios/fi.lproj/Localizable.strings | 4 +-- apps/ios/fr.lproj/Localizable.strings | 4 +-- apps/ios/hu.lproj/Localizable.strings | 22 ++++++------- apps/ios/it.lproj/Localizable.strings | 22 ++++++------- apps/ios/ja.lproj/Localizable.strings | 4 +-- apps/ios/nl.lproj/Localizable.strings | 22 ++++++------- apps/ios/pl.lproj/Localizable.strings | 4 +-- apps/ios/ru.lproj/Localizable.strings | 31 ++++++++++++------- apps/ios/th.lproj/Localizable.strings | 4 +-- apps/ios/tr.lproj/Localizable.strings | 4 +-- apps/ios/uk.lproj/Localizable.strings | 25 +++++++-------- apps/ios/zh-Hans.lproj/Localizable.strings | 4 +-- 33 files changed, 349 insertions(+), 133 deletions(-) diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 901bee26dd..4af0007eea 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -566,6 +566,10 @@ За SimpleX Chat No comment provided by engineer. + + About operators + No comment provided by engineer. + Accent No comment provided by engineer. @@ -5796,7 +5800,7 @@ Enable in *Network & servers* settings. Save preferences? Запази настройките? - No comment provided by engineer. + alert title Save profile password @@ -8118,6 +8122,10 @@ Repeat connection request? обаждането прието call status + + accepted invitation + chat list item title + admin админ @@ -8304,7 +8312,7 @@ Repeat connection request? connecting… свързване… - chat list item title + No comment provided by engineer. connection established @@ -8788,6 +8796,10 @@ Repeat connection request? ви острани rcv group event chat item + + requested to connect + chat list item title + saved запазено diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index fd8958f5f8..7d92f62f12 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -548,6 +548,10 @@ O SimpleX chat No comment provided by engineer. + + About operators + No comment provided by engineer. + Accent No comment provided by engineer. @@ -5606,7 +5610,7 @@ Enable in *Network & servers* settings. Save preferences? Uložit předvolby? - No comment provided by engineer. + alert title Save profile password @@ -7849,6 +7853,10 @@ Repeat connection request? přijatý hovor call status + + accepted invitation + chat list item title + admin správce @@ -8028,7 +8036,7 @@ Repeat connection request? connecting… připojení… - chat list item title + No comment provided by engineer. connection established @@ -8503,6 +8511,10 @@ Repeat connection request? odstranil vás rcv group event chat item + + requested to connect + chat list item title + saved No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 77ddbfb69b..516baf49b7 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -577,6 +577,10 @@ Über SimpleX Chat No comment provided by engineer. + + About operators + No comment provided by engineer. + Accent Akzent @@ -6065,7 +6069,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Save preferences? Präferenzen speichern? - No comment provided by engineer. + alert title Save profile password @@ -8517,6 +8521,10 @@ Verbindungsanfrage wiederholen? Anruf angenommen call status + + accepted invitation + chat list item title + admin Admin @@ -8705,7 +8713,7 @@ Verbindungsanfrage wiederholen? connecting… Verbinde… - chat list item title + No comment provided by engineer. connection established @@ -9199,6 +9207,10 @@ Verbindungsanfrage wiederholen? hat Sie aus der Gruppe entfernt rcv group event chat item + + requested to connect + chat list item title + saved abgespeichert diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index a70a22581b..699091e2d8 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -577,6 +577,11 @@ About SimpleX Chat No comment provided by engineer. + + About operators + About operators + No comment provided by engineer. + Accent Accent @@ -6086,7 +6091,7 @@ Enable in *Network & servers* settings. Save preferences? Save preferences? - No comment provided by engineer. + alert title Save profile password @@ -8541,6 +8546,11 @@ Repeat connection request? accepted call call status + + accepted invitation + accepted invitation + chat list item title + admin admin @@ -8729,7 +8739,7 @@ Repeat connection request? connecting… connecting… - chat list item title + No comment provided by engineer. connection established @@ -9223,6 +9233,11 @@ Repeat connection request? removed you rcv group event chat item + + requested to connect + requested to connect + chat list item title + saved saved diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 32af6e9cfd..ddb5d6b1a1 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -577,6 +577,10 @@ Sobre SimpleX Chat No comment provided by engineer. + + About operators + No comment provided by engineer. + Accent Color @@ -6065,7 +6069,7 @@ Actívalo en ajustes de *Servidores y Redes*. Save preferences? ¿Guardar preferencias? - No comment provided by engineer. + alert title Save profile password @@ -8517,6 +8521,10 @@ Repeat connection request? llamada aceptada call status + + accepted invitation + chat list item title + admin administrador @@ -8705,7 +8713,7 @@ Repeat connection request? connecting… conectando… - chat list item title + No comment provided by engineer. connection established @@ -9199,6 +9207,10 @@ Repeat connection request? te ha expulsado rcv group event chat item + + requested to connect + chat list item title + saved guardado diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index bbd8a338bc..12c8d0e3ca 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -543,6 +543,10 @@ Tietoja SimpleX Chatistä No comment provided by engineer. + + About operators + No comment provided by engineer. + Accent No comment provided by engineer. @@ -5594,7 +5598,7 @@ Enable in *Network & servers* settings. Save preferences? Tallenna asetukset? - No comment provided by engineer. + alert title Save profile password @@ -7834,6 +7838,10 @@ Repeat connection request? hyväksytty puhelu call status + + accepted invitation + chat list item title + admin ylläpitäjä @@ -8012,7 +8020,7 @@ Repeat connection request? connecting… yhdistää… - chat list item title + No comment provided by engineer. connection established @@ -8488,6 +8496,10 @@ Repeat connection request? poisti sinut rcv group event chat item + + requested to connect + chat list item title + saved No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 9c44fe91e4..e82f01e33b 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -572,6 +572,10 @@ À propos de SimpleX Chat No comment provided by engineer. + + About operators + No comment provided by engineer. + Accent Principale @@ -6003,7 +6007,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Save preferences? Enregistrer les préférences ? - No comment provided by engineer. + alert title Save profile password @@ -8421,6 +8425,10 @@ Répéter la demande de connexion ? appel accepté call status + + accepted invitation + chat list item title + admin admin @@ -8609,7 +8617,7 @@ Répéter la demande de connexion ? connecting… connexion… - chat list item title + No comment provided by engineer. connection established @@ -9102,6 +9110,10 @@ Répéter la demande de connexion ? vous a retiré rcv group event chat item + + requested to connect + chat list item title + saved enregistré diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index ffc162633f..5f05efaa4d 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -577,6 +577,10 @@ A SimpleX Chatről No comment provided by engineer. + + About operators + No comment provided by engineer. + Accent Kiemelés @@ -6086,7 +6090,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Save preferences? Beállítások mentése? - No comment provided by engineer. + alert title Save profile password @@ -8541,6 +8545,10 @@ Kapcsolatkérés megismétlése? elfogadott hívás call status + + accepted invitation + chat list item title + admin adminisztrátor @@ -8729,7 +8737,7 @@ Kapcsolatkérés megismétlése? connecting… kapcsolódás… - chat list item title + No comment provided by engineer. connection established @@ -9223,6 +9231,10 @@ Kapcsolatkérés megismétlése? eltávolította Önt rcv group event chat item + + requested to connect + chat list item title + saved mentett diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 0b578e6a25..ebfba7e415 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -577,6 +577,10 @@ Riguardo SimpleX Chat No comment provided by engineer. + + About operators + No comment provided by engineer. + Accent Principale @@ -6086,7 +6090,7 @@ Attivalo nelle impostazioni *Rete e server*. Save preferences? Salvare le preferenze? - No comment provided by engineer. + alert title Save profile password @@ -8541,6 +8545,10 @@ Ripetere la richiesta di connessione? chiamata accettata call status + + accepted invitation + chat list item title + admin amministratore @@ -8729,7 +8737,7 @@ Ripetere la richiesta di connessione? connecting… in connessione… - chat list item title + No comment provided by engineer. connection established @@ -9223,6 +9231,10 @@ Ripetere la richiesta di connessione? ti ha rimosso/a rcv group event chat item + + requested to connect + chat list item title + saved salvato diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 87058e0bdc..ba35db0c03 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -560,6 +560,10 @@ SimpleX Chat について No comment provided by engineer. + + About operators + No comment provided by engineer. + Accent No comment provided by engineer. @@ -5643,7 +5647,7 @@ Enable in *Network & servers* settings. Save preferences? この設定でよろしいですか? - No comment provided by engineer. + alert title Save profile password @@ -7876,6 +7880,10 @@ Repeat connection request? 受けた通話 call status + + accepted invitation + chat list item title + admin 管理者 @@ -8054,7 +8062,7 @@ Repeat connection request? connecting… 接続待ち… - chat list item title + No comment provided by engineer. connection established @@ -8530,6 +8538,10 @@ Repeat connection request? あなたを除名しました rcv group event chat item + + requested to connect + chat list item title + saved No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index ce3adc3c41..5631d9bd7d 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -577,6 +577,10 @@ Over SimpleX Chat No comment provided by engineer. + + About operators + No comment provided by engineer. + Accent Accent @@ -6086,7 +6090,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Save preferences? Voorkeuren opslaan? - No comment provided by engineer. + alert title Save profile password @@ -8541,6 +8545,10 @@ Verbindingsverzoek herhalen? geaccepteerde oproep call status + + accepted invitation + chat list item title + admin Beheerder @@ -8729,7 +8737,7 @@ Verbindingsverzoek herhalen? connecting… Verbinden… - chat list item title + No comment provided by engineer. connection established @@ -9223,6 +9231,10 @@ Verbindingsverzoek herhalen? heeft je verwijderd rcv group event chat item + + requested to connect + chat list item title + saved opgeslagen diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index e99439f3ae..dbc95b6527 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -572,6 +572,10 @@ O SimpleX Chat No comment provided by engineer. + + About operators + No comment provided by engineer. + Accent Akcent @@ -5993,7 +5997,7 @@ Włącz w ustawianiach *Sieć i serwery* . Save preferences? Zapisać preferencje? - No comment provided by engineer. + alert title Save profile password @@ -8408,6 +8412,10 @@ Powtórzyć prośbę połączenia? zaakceptowane połączenie call status + + accepted invitation + chat list item title + admin administrator @@ -8596,7 +8604,7 @@ Powtórzyć prośbę połączenia? connecting… łączenie… - chat list item title + No comment provided by engineer. connection established @@ -9089,6 +9097,10 @@ Powtórzyć prośbę połączenia? usunął cię rcv group event chat item + + requested to connect + chat list item title + saved zapisane diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 85b4aaa4ae..95bd74e484 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -577,6 +577,11 @@ Информация о SimpleX Chat No comment provided by engineer. + + About operators + Об операторах + No comment provided by engineer. + Accent Акцент @@ -6085,7 +6090,7 @@ Enable in *Network & servers* settings. Save preferences? Сохранить предпочтения? - No comment provided by engineer. + alert title Save profile password @@ -8145,7 +8150,7 @@ Repeat join request? You can configure servers via settings. - Вы можете сконфигурировать серверы через настройки. + Вы можете настроить серверы позже. No comment provided by engineer. @@ -8540,6 +8545,11 @@ Repeat connection request? принятый звонок call status + + accepted invitation + принятое приглашение + chat list item title + admin админ @@ -8728,7 +8738,7 @@ Repeat connection request? connecting… соединяется… - chat list item title + No comment provided by engineer. connection established @@ -9222,6 +9232,11 @@ Repeat connection request? удалил(а) Вас из группы rcv group event chat item + + requested to connect + запрошено соединение + chat list item title + saved сохранено diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index 438fae5c47..14827be1b5 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -536,6 +536,10 @@ เกี่ยวกับ SimpleX Chat No comment provided by engineer. + + About operators + No comment provided by engineer. + Accent No comment provided by engineer. @@ -5571,7 +5575,7 @@ Enable in *Network & servers* settings. Save preferences? บันทึกการตั้งค่า? - No comment provided by engineer. + alert title Save profile password @@ -7802,6 +7806,10 @@ Repeat connection request? รับสายแล้ว call status + + accepted invitation + chat list item title + admin ผู้ดูแลระบบ @@ -7980,7 +7988,7 @@ Repeat connection request? connecting… กำลังเชื่อมต่อ… - chat list item title + No comment provided by engineer. connection established @@ -8454,6 +8462,10 @@ Repeat connection request? ลบคุณออกแล้ว rcv group event chat item + + requested to connect + chat list item title + saved No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index 5919cc4d49..d12ef93f69 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -572,6 +572,10 @@ SimpleX Chat hakkında No comment provided by engineer. + + About operators + No comment provided by engineer. + Accent Ana renk @@ -6003,7 +6007,7 @@ Enable in *Network & servers* settings. Save preferences? Tercihler kaydedilsin mi? - No comment provided by engineer. + alert title Save profile password @@ -8421,6 +8425,10 @@ Bağlantı isteği tekrarlansın mı? kabul edilen arama call status + + accepted invitation + chat list item title + admin yönetici @@ -8609,7 +8617,7 @@ Bağlantı isteği tekrarlansın mı? connecting… bağlanılıyor… - chat list item title + No comment provided by engineer. connection established @@ -9102,6 +9110,10 @@ Bağlantı isteği tekrarlansın mı? sen kaldırıldın rcv group event chat item + + requested to connect + chat list item title + saved kaydedildi diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 72e9896872..e228fd01e6 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -577,6 +577,10 @@ Про чат SimpleX No comment provided by engineer. + + About operators + No comment provided by engineer. + Accent Акцент @@ -6065,7 +6069,7 @@ Enable in *Network & servers* settings. Save preferences? Зберегти настройки? - No comment provided by engineer. + alert title Save profile password @@ -8517,6 +8521,10 @@ Repeat connection request? прийнято виклик call status + + accepted invitation + chat list item title + admin адмін @@ -8705,7 +8713,7 @@ Repeat connection request? connecting… з'єднання… - chat list item title + No comment provided by engineer. connection established @@ -9199,6 +9207,10 @@ Repeat connection request? прибрали вас rcv group event chat item + + requested to connect + chat list item title + saved збережено diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index ede559c968..0b1b568385 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -566,6 +566,10 @@ 关于SimpleX Chat No comment provided by engineer. + + About operators + No comment provided by engineer. + Accent 强调 @@ -5956,7 +5960,7 @@ Enable in *Network & servers* settings. Save preferences? 保存偏好设置? - No comment provided by engineer. + alert title Save profile password @@ -8354,6 +8358,10 @@ Repeat connection request? 已接受通话 call status + + accepted invitation + chat list item title + admin 管理员 @@ -8542,7 +8550,7 @@ Repeat connection request? connecting… 连接中…… - chat list item title + No comment provided by engineer. connection established @@ -9035,6 +9043,10 @@ Repeat connection request? 已将您移除 rcv group event chat item + + requested to connect + chat list item title + saved 已保存 diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index 41f6730fdc..91606d8569 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -918,7 +918,7 @@ /* No comment provided by engineer. */ "Connecting to desktop" = "Свързване с настолно устройство"; -/* chat list item title */ +/* No comment provided by engineer. */ "connecting…" = "свързване…"; /* No comment provided by engineer. */ @@ -3157,7 +3157,7 @@ /* No comment provided by engineer. */ "Save passphrase in Keychain" = "Запази паролата в Keychain"; -/* No comment provided by engineer. */ +/* alert title */ "Save preferences?" = "Запази настройките?"; /* No comment provided by engineer. */ diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index bbe754aa47..96b149a8d5 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -729,7 +729,7 @@ /* No comment provided by engineer. */ "Connecting server… (error: %@)" = "Připojování k serveru... (chyba: %@)"; -/* chat list item title */ +/* No comment provided by engineer. */ "connecting…" = "připojení…"; /* No comment provided by engineer. */ @@ -2556,7 +2556,7 @@ /* No comment provided by engineer. */ "Save passphrase in Keychain" = "Uložit přístupovou frázi do Klíčenky"; -/* No comment provided by engineer. */ +/* alert title */ "Save preferences?" = "Uložit předvolby?"; /* No comment provided by engineer. */ diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 28cc658d30..25f9cf32c1 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -863,6 +863,9 @@ /* No comment provided by engineer. */ "Change" = "Ändern"; +/* authentication reason */ +"Change chat profiles" = "Chat-Profile wechseln"; + /* No comment provided by engineer. */ "Change database passphrase?" = "Datenbank-Passwort ändern?"; @@ -891,9 +894,6 @@ set passcode view */ "Change self-destruct passcode" = "Selbstzerstörungs-Zugangscode ändern"; -/* authentication reason */ -"Change chat profiles" = "Chat-Profile wechseln"; - /* chat item text */ "changed address for you" = "Wechselte die Empfängeradresse von Ihnen"; @@ -1173,7 +1173,7 @@ /* No comment provided by engineer. */ "Connecting to desktop" = "Mit dem Desktop verbinden"; -/* chat list item title */ +/* No comment provided by engineer. */ "connecting…" = "Verbinde…"; /* No comment provided by engineer. */ @@ -3945,12 +3945,6 @@ /* No comment provided by engineer. */ "Safer groups" = "Sicherere Gruppen"; -/* No comment provided by engineer. */ -"The same conditions will apply to operator **%@**." = "Dieselben Nutzungsbedingungen gelten auch für den Betreiber **%@**."; - -/* No comment provided by engineer. */ -"The same conditions will apply to operator(s): **%@**." = "Dieselben Nutzungsbedingungen gelten auch für den/die Betreiber: **%@**."; - /* alert button chat item action */ "Save" = "Speichern"; @@ -3979,7 +3973,7 @@ /* No comment provided by engineer. */ "Save passphrase in Keychain" = "Passwort im Schlüsselbund speichern"; -/* No comment provided by engineer. */ +/* alert title */ "Save preferences?" = "Präferenzen speichern?"; /* No comment provided by engineer. */ @@ -4691,6 +4685,12 @@ /* No comment provided by engineer. */ "The profile is only shared with your contacts." = "Das Profil wird nur mit Ihren Kontakten geteilt."; +/* No comment provided by engineer. */ +"The same conditions will apply to operator **%@**." = "Dieselben Nutzungsbedingungen gelten auch für den Betreiber **%@**."; + +/* No comment provided by engineer. */ +"The same conditions will apply to operator(s): **%@**." = "Dieselben Nutzungsbedingungen gelten auch für den/die Betreiber: **%@**."; + /* No comment provided by engineer. */ "The second preset operator in the app!" = "Der zweite voreingestellte Netzwerk-Betreiber in der App!"; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 1ccb679069..a1665cd716 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -863,6 +863,9 @@ /* No comment provided by engineer. */ "Change" = "Cambiar"; +/* authentication reason */ +"Change chat profiles" = "Cambiar perfil de usuario"; + /* No comment provided by engineer. */ "Change database passphrase?" = "¿Cambiar contraseña de la base de datos?"; @@ -891,9 +894,6 @@ set passcode view */ "Change self-destruct passcode" = "Cambiar código autodestrucción"; -/* authentication reason */ -"Change chat profiles" = "Cambiar perfil de usuario"; - /* chat item text */ "changed address for you" = "ha cambiado tu servidor de envío"; @@ -1173,7 +1173,7 @@ /* No comment provided by engineer. */ "Connecting to desktop" = "Conectando con ordenador"; -/* chat list item title */ +/* No comment provided by engineer. */ "connecting…" = "conectando…"; /* No comment provided by engineer. */ @@ -3945,12 +3945,6 @@ /* No comment provided by engineer. */ "Safer groups" = "Grupos más seguros"; -/* No comment provided by engineer. */ -"The same conditions will apply to operator **%@**." = "Las mismas condiciones se aplicarán al operador **%@**."; - -/* No comment provided by engineer. */ -"The same conditions will apply to operator(s): **%@**." = "Las mismas condiciones se aplicarán a el/los operador(es) **%@**."; - /* alert button chat item action */ "Save" = "Guardar"; @@ -3979,7 +3973,7 @@ /* No comment provided by engineer. */ "Save passphrase in Keychain" = "Guardar la contraseña en Keychain"; -/* No comment provided by engineer. */ +/* alert title */ "Save preferences?" = "¿Guardar preferencias?"; /* No comment provided by engineer. */ @@ -4691,6 +4685,12 @@ /* No comment provided by engineer. */ "The profile is only shared with your contacts." = "El perfil sólo se comparte con tus contactos."; +/* No comment provided by engineer. */ +"The same conditions will apply to operator **%@**." = "Las mismas condiciones se aplicarán al operador **%@**."; + +/* No comment provided by engineer. */ +"The same conditions will apply to operator(s): **%@**." = "Las mismas condiciones se aplicarán a el/los operador(es) **%@**."; + /* No comment provided by engineer. */ "The second preset operator in the app!" = "El segundo operador predefinido!"; diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index 486c0e7650..e4b56e76a4 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -711,7 +711,7 @@ /* No comment provided by engineer. */ "Connecting server… (error: %@)" = "Yhteyden muodostaminen palvelimeen... (virhe: %@)"; -/* chat list item title */ +/* No comment provided by engineer. */ "connecting…" = "yhdistää…"; /* No comment provided by engineer. */ @@ -2526,7 +2526,7 @@ /* No comment provided by engineer. */ "Save passphrase in Keychain" = "Tallenna tunnuslause Avainnippuun"; -/* No comment provided by engineer. */ +/* alert title */ "Save preferences?" = "Tallenna asetukset?"; /* No comment provided by engineer. */ diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index 1a9e289404..e50d2c0967 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -1101,7 +1101,7 @@ /* No comment provided by engineer. */ "Connecting to desktop" = "Connexion au bureau"; -/* chat list item title */ +/* No comment provided by engineer. */ "connecting…" = "connexion…"; /* No comment provided by engineer. */ @@ -3784,7 +3784,7 @@ /* No comment provided by engineer. */ "Save passphrase in Keychain" = "Enregistrer la phrase secrète dans la Keychain"; -/* No comment provided by engineer. */ +/* alert title */ "Save preferences?" = "Enregistrer les préférences ?"; /* No comment provided by engineer. */ diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index 4893d5a13f..8c0da0ed57 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -878,6 +878,9 @@ /* No comment provided by engineer. */ "Change" = "Változtatás"; +/* authentication reason */ +"Change chat profiles" = "Felhasználói profilok megváltoztatása"; + /* No comment provided by engineer. */ "Change database passphrase?" = "Adatbázis-jelmondat megváltoztatása?"; @@ -906,9 +909,6 @@ set passcode view */ "Change self-destruct passcode" = "Önmegsemmisító jelkód megváltoztatása"; -/* authentication reason */ -"Change chat profiles" = "Felhasználói profilok megváltoztatása"; - /* chat item text */ "changed address for you" = "cím megváltoztatva"; @@ -1203,7 +1203,7 @@ /* No comment provided by engineer. */ "Connecting to desktop" = "Kapcsolódás a számítógéphez"; -/* chat list item title */ +/* No comment provided by engineer. */ "connecting…" = "kapcsolódás…"; /* No comment provided by engineer. */ @@ -4008,12 +4008,6 @@ /* No comment provided by engineer. */ "Safer groups" = "Biztonságosabb csoportok"; -/* No comment provided by engineer. */ -"The same conditions will apply to operator **%@**." = "Ugyanezek a feltételek vonatkoznak a következő üzemeltetőre is: **%@**."; - -/* No comment provided by engineer. */ -"The same conditions will apply to operator(s): **%@**." = "Ugyanezek a feltételek lesznek elfogadva a következő üzemeltető(k)re is: **%@**."; - /* alert button chat item action */ "Save" = "Mentés"; @@ -4042,7 +4036,7 @@ /* No comment provided by engineer. */ "Save passphrase in Keychain" = "Jelmondat mentése a kulcstartóba"; -/* No comment provided by engineer. */ +/* alert title */ "Save preferences?" = "Beállítások mentése?"; /* No comment provided by engineer. */ @@ -4757,6 +4751,12 @@ /* No comment provided by engineer. */ "The profile is only shared with your contacts." = "A profilja csak az ismerőseivel kerül megosztásra."; +/* No comment provided by engineer. */ +"The same conditions will apply to operator **%@**." = "Ugyanezek a feltételek vonatkoznak a következő üzemeltetőre is: **%@**."; + +/* No comment provided by engineer. */ +"The same conditions will apply to operator(s): **%@**." = "Ugyanezek a feltételek lesznek elfogadva a következő üzemeltető(k)re is: **%@**."; + /* No comment provided by engineer. */ "The second preset operator in the app!" = "A második előre beállított üzemeltető az alkalmazásban!"; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index 2fe1216f35..1242b488ac 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -878,6 +878,9 @@ /* No comment provided by engineer. */ "Change" = "Cambia"; +/* authentication reason */ +"Change chat profiles" = "Modifica profili utente"; + /* No comment provided by engineer. */ "Change database passphrase?" = "Cambiare password del database?"; @@ -906,9 +909,6 @@ set passcode view */ "Change self-destruct passcode" = "Cambia codice di autodistruzione"; -/* authentication reason */ -"Change chat profiles" = "Modifica profili utente"; - /* chat item text */ "changed address for you" = "indirizzo cambiato per te"; @@ -1203,7 +1203,7 @@ /* No comment provided by engineer. */ "Connecting to desktop" = "Connessione al desktop"; -/* chat list item title */ +/* No comment provided by engineer. */ "connecting…" = "in connessione…"; /* No comment provided by engineer. */ @@ -4008,12 +4008,6 @@ /* No comment provided by engineer. */ "Safer groups" = "Gruppi più sicuri"; -/* No comment provided by engineer. */ -"The same conditions will apply to operator **%@**." = "Le stesse condizioni si applicheranno all'operatore **%@**."; - -/* No comment provided by engineer. */ -"The same conditions will apply to operator(s): **%@**." = "Le stesse condizioni si applicheranno agli operatori **%@**."; - /* alert button chat item action */ "Save" = "Salva"; @@ -4042,7 +4036,7 @@ /* No comment provided by engineer. */ "Save passphrase in Keychain" = "Salva password nel portachiavi"; -/* No comment provided by engineer. */ +/* alert title */ "Save preferences?" = "Salvare le preferenze?"; /* No comment provided by engineer. */ @@ -4757,6 +4751,12 @@ /* No comment provided by engineer. */ "The profile is only shared with your contacts." = "Il profilo è condiviso solo con i tuoi contatti."; +/* No comment provided by engineer. */ +"The same conditions will apply to operator **%@**." = "Le stesse condizioni si applicheranno all'operatore **%@**."; + +/* No comment provided by engineer. */ +"The same conditions will apply to operator(s): **%@**." = "Le stesse condizioni si applicheranno agli operatori **%@**."; + /* No comment provided by engineer. */ "The second preset operator in the app!" = "Il secondo operatore preimpostato nell'app!"; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index 06fa3f70b3..3aa64f9b55 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -828,7 +828,7 @@ /* No comment provided by engineer. */ "Connecting to desktop" = "デスクトップに接続中"; -/* chat list item title */ +/* No comment provided by engineer. */ "connecting…" = "接続待ち…"; /* No comment provided by engineer. */ @@ -2673,7 +2673,7 @@ /* No comment provided by engineer. */ "Save passphrase in Keychain" = "パスフレーズをキーチェーンに保存"; -/* No comment provided by engineer. */ +/* alert title */ "Save preferences?" = "この設定でよろしいですか?"; /* No comment provided by engineer. */ diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index edb123334d..4ead9a726d 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -878,6 +878,9 @@ /* No comment provided by engineer. */ "Change" = "Veranderen"; +/* authentication reason */ +"Change chat profiles" = "Gebruikersprofielen wijzigen"; + /* No comment provided by engineer. */ "Change database passphrase?" = "Wachtwoord database wijzigen?"; @@ -906,9 +909,6 @@ set passcode view */ "Change self-destruct passcode" = "Zelfvernietigings code wijzigen"; -/* authentication reason */ -"Change chat profiles" = "Gebruikersprofielen wijzigen"; - /* chat item text */ "changed address for you" = "adres voor u gewijzigd"; @@ -1203,7 +1203,7 @@ /* No comment provided by engineer. */ "Connecting to desktop" = "Verbinding maken met desktop"; -/* chat list item title */ +/* No comment provided by engineer. */ "connecting…" = "Verbinden…"; /* No comment provided by engineer. */ @@ -4008,12 +4008,6 @@ /* No comment provided by engineer. */ "Safer groups" = "Veiligere groepen"; -/* No comment provided by engineer. */ -"The same conditions will apply to operator **%@**." = "Dezelfde voorwaarden gelden voor operator **%@**."; - -/* No comment provided by engineer. */ -"The same conditions will apply to operator(s): **%@**." = "Dezelfde voorwaarden gelden voor operator(s): **%@**."; - /* alert button chat item action */ "Save" = "Opslaan"; @@ -4042,7 +4036,7 @@ /* No comment provided by engineer. */ "Save passphrase in Keychain" = "Sla het wachtwoord op in de Keychain"; -/* No comment provided by engineer. */ +/* alert title */ "Save preferences?" = "Voorkeuren opslaan?"; /* No comment provided by engineer. */ @@ -4757,6 +4751,12 @@ /* No comment provided by engineer. */ "The profile is only shared with your contacts." = "Het profiel wordt alleen gedeeld met uw contacten."; +/* No comment provided by engineer. */ +"The same conditions will apply to operator **%@**." = "Dezelfde voorwaarden gelden voor operator **%@**."; + +/* No comment provided by engineer. */ +"The same conditions will apply to operator(s): **%@**." = "Dezelfde voorwaarden gelden voor operator(s): **%@**."; + /* No comment provided by engineer. */ "The second preset operator in the app!" = "De tweede vooraf ingestelde operator in de app!"; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index 782e1c18f4..e0bcedc965 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -1086,7 +1086,7 @@ /* No comment provided by engineer. */ "Connecting to desktop" = "Łączenie z komputerem"; -/* chat list item title */ +/* No comment provided by engineer. */ "connecting…" = "łączenie…"; /* No comment provided by engineer. */ @@ -3757,7 +3757,7 @@ /* No comment provided by engineer. */ "Save passphrase in Keychain" = "Zapisz hasło w pęku kluczy"; -/* No comment provided by engineer. */ +/* alert title */ "Save preferences?" = "Zapisać preferencje?"; /* No comment provided by engineer. */ diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 57c1b91769..09ee9a2e5a 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -343,6 +343,9 @@ /* No comment provided by engineer. */ "Abort changing address?" = "Прекратить изменение адреса?"; +/* No comment provided by engineer. */ +"About operators" = "Об операторах"; + /* No comment provided by engineer. */ "About SimpleX Chat" = "Информация о SimpleX Chat"; @@ -376,6 +379,9 @@ /* No comment provided by engineer. */ "Accepted conditions" = "Принятые условия"; +/* chat list item title */ +"accepted invitation" = "принятое приглашение"; + /* No comment provided by engineer. */ "Acknowledged" = "Подтверждено"; @@ -878,6 +884,9 @@ /* No comment provided by engineer. */ "Change" = "Поменять"; +/* authentication reason */ +"Change chat profiles" = "Поменять профили"; + /* No comment provided by engineer. */ "Change database passphrase?" = "Поменять пароль базы данных?"; @@ -906,9 +915,6 @@ set passcode view */ "Change self-destruct passcode" = "Изменить код самоуничтожения"; -/* authentication reason */ -"Change chat profiles" = "Поменять профили"; - /* chat item text */ "changed address for you" = "поменял(а) адрес для Вас"; @@ -1203,7 +1209,7 @@ /* No comment provided by engineer. */ "Connecting to desktop" = "Подключение к компьютеру"; -/* chat list item title */ +/* No comment provided by engineer. */ "connecting…" = "соединяется…"; /* No comment provided by engineer. */ @@ -3930,6 +3936,9 @@ /* chat item action */ "Reply" = "Ответить"; +/* chat list item title */ +"requested to connect" = "запрошено соединение"; + /* No comment provided by engineer. */ "Required" = "Обязательно"; @@ -4008,12 +4017,6 @@ /* No comment provided by engineer. */ "Safer groups" = "Более безопасные группы"; -/* No comment provided by engineer. */ -"The same conditions will apply to operator **%@**." = "Те же самые условия будут приняты для оператора **%@**."; - -/* No comment provided by engineer. */ -"The same conditions will apply to operator(s): **%@**." = "Те же самые условия будут приняты для оператора(ов): **%@**."; - /* alert button chat item action */ "Save" = "Сохранить"; @@ -4042,7 +4045,7 @@ /* No comment provided by engineer. */ "Save passphrase in Keychain" = "Сохранить пароль в Keychain"; -/* No comment provided by engineer. */ +/* alert title */ "Save preferences?" = "Сохранить предпочтения?"; /* No comment provided by engineer. */ @@ -4757,6 +4760,12 @@ /* No comment provided by engineer. */ "The profile is only shared with your contacts." = "Профиль отправляется только Вашим контактам."; +/* No comment provided by engineer. */ +"The same conditions will apply to operator **%@**." = "Те же самые условия будут приняты для оператора **%@**."; + +/* No comment provided by engineer. */ +"The same conditions will apply to operator(s): **%@**." = "Те же самые условия будут приняты для оператора(ов): **%@**."; + /* No comment provided by engineer. */ "The second preset operator in the app!" = "Второй оператор серверов в приложении!"; diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index b496fe11b4..3fee154931 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -681,7 +681,7 @@ /* No comment provided by engineer. */ "Connecting server… (error: %@)" = "กำลังเชื่อมต่อกับเซิร์ฟเวอร์... (ข้อผิดพลาด: %@)"; -/* chat list item title */ +/* No comment provided by engineer. */ "connecting…" = "กำลังเชื่อมต่อ…"; /* No comment provided by engineer. */ @@ -2457,7 +2457,7 @@ /* No comment provided by engineer. */ "Save passphrase in Keychain" = "บันทึกข้อความรหัสผ่านใน Keychain"; -/* No comment provided by engineer. */ +/* alert title */ "Save preferences?" = "บันทึกการตั้งค่า?"; /* No comment provided by engineer. */ diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index 99668bec79..a78faed4cd 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -1101,7 +1101,7 @@ /* No comment provided by engineer. */ "Connecting to desktop" = "Bilgisayara bağlanıyor"; -/* chat list item title */ +/* No comment provided by engineer. */ "connecting…" = "bağlanılıyor…"; /* No comment provided by engineer. */ @@ -3784,7 +3784,7 @@ /* No comment provided by engineer. */ "Save passphrase in Keychain" = "Parolayı Anahtar Zincirinde kaydet"; -/* No comment provided by engineer. */ +/* alert title */ "Save preferences?" = "Tercihler kaydedilsin mi?"; /* No comment provided by engineer. */ diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index d470a2a1e3..ce607753fe 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -863,6 +863,9 @@ /* No comment provided by engineer. */ "Change" = "Зміна"; +/* authentication reason */ +"Change chat profiles" = "Зміна профілів користувачів"; + /* No comment provided by engineer. */ "Change database passphrase?" = "Змінити пароль до бази даних?"; @@ -891,9 +894,6 @@ set passcode view */ "Change self-destruct passcode" = "Змінити пароль самознищення"; -/* authentication reason */ -"Change chat profiles" = "Зміна профілів користувачів"; - /* chat item text */ "changed address for you" = "змінили для вас адресу"; @@ -1173,7 +1173,7 @@ /* No comment provided by engineer. */ "Connecting to desktop" = "Підключення до ПК"; -/* chat list item title */ +/* No comment provided by engineer. */ "connecting…" = "з'єднання…"; /* No comment provided by engineer. */ @@ -3668,9 +3668,6 @@ /* No comment provided by engineer. */ "Proxy requires password" = "Проксі вимагає пароль"; -/* No comment provided by engineer. */ -"Push notifications" = "Push-повідомлення"; - /* No comment provided by engineer. */ "Push notifications" = "Push-сповіщення"; @@ -3948,12 +3945,6 @@ /* No comment provided by engineer. */ "Safer groups" = "Безпечніші групи"; -/* No comment provided by engineer. */ -"The same conditions will apply to operator **%@**." = "Такі ж умови діятимуть і для оператора **%@**."; - -/* No comment provided by engineer. */ -"The same conditions will apply to operator(s): **%@**." = "Такі ж умови будуть застосовуватися до оператора(ів): **%@**."; - /* alert button chat item action */ "Save" = "Зберегти"; @@ -3982,7 +3973,7 @@ /* No comment provided by engineer. */ "Save passphrase in Keychain" = "Збережіть парольну фразу в Keychain"; -/* No comment provided by engineer. */ +/* alert title */ "Save preferences?" = "Зберегти настройки?"; /* No comment provided by engineer. */ @@ -4694,6 +4685,12 @@ /* No comment provided by engineer. */ "The profile is only shared with your contacts." = "Профіль доступний лише вашим контактам."; +/* No comment provided by engineer. */ +"The same conditions will apply to operator **%@**." = "Такі ж умови діятимуть і для оператора **%@**."; + +/* No comment provided by engineer. */ +"The same conditions will apply to operator(s): **%@**." = "Такі ж умови будуть застосовуватися до оператора(ів): **%@**."; + /* No comment provided by engineer. */ "The second preset operator in the app!" = "Другий попередньо встановлений оператор у застосунку!"; diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index 6a924eea1f..627bfd0c30 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -1059,7 +1059,7 @@ /* No comment provided by engineer. */ "Connecting to desktop" = "正连接到桌面"; -/* chat list item title */ +/* No comment provided by engineer. */ "connecting…" = "连接中……"; /* No comment provided by engineer. */ @@ -3655,7 +3655,7 @@ /* No comment provided by engineer. */ "Save passphrase in Keychain" = "在钥匙串中保存密码"; -/* No comment provided by engineer. */ +/* alert title */ "Save preferences?" = "保存偏好设置?"; /* No comment provided by engineer. */ From e0c2272fcb3099d3e9ac87eb02108ec7d57dc5e0 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 6 Dec 2024 21:35:10 +0400 Subject: [PATCH 203/567] android, desktop: operators info on onboarding (#5341) --- .../views/onboarding/ChooseServerOperators.kt | 50 ++++++++++++++++--- .../networkAndServers/OperatorView.kt | 2 +- .../commonMain/resources/MR/base/strings.xml | 1 + 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt index 132381294f..e706a0d8e9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt @@ -1,7 +1,11 @@ package chat.simplex.common.views.onboarding import SectionBottomSpacer +import SectionDividerSpaced +import SectionItemView import SectionTextFooter +import SectionView +import TextIconSpaced import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.* @@ -12,8 +16,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs -import chat.simplex.common.model.ServerOperator import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* @@ -339,11 +343,45 @@ private fun enabledOperators(operators: List, selectedOperatorId @Composable private fun ChooseServerOperatorsInfoView() { - ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_PADDING)) { - AppBarTitle(stringResource(MR.strings.onboarding_network_operators), withPadding = false) - ReadableText(stringResource(MR.strings.onboarding_network_operators_app_will_use_different_operators)) - ReadableText(stringResource(MR.strings.onboarding_network_operators_cant_see_who_talks_to_whom)) - ReadableText(stringResource(MR.strings.onboarding_network_operators_app_will_use_for_routing)) + ColumnWithScrollBar { + AppBarTitle(stringResource(MR.strings.onboarding_network_operators)) + + Column( + Modifier.padding(horizontal = DEFAULT_PADDING) + ) { + ReadableText(stringResource(MR.strings.onboarding_network_operators_app_will_use_different_operators)) + ReadableText(stringResource(MR.strings.onboarding_network_operators_cant_see_who_talks_to_whom)) + ReadableText(stringResource(MR.strings.onboarding_network_operators_app_will_use_for_routing)) + } + + SectionDividerSpaced() + + SectionView(title = stringResource(MR.strings.onboarding_network_about_operators).uppercase()) { + chatModel.conditions.value.serverOperators.forEach { op -> + ServerOperatorRow(op) + } + } SectionBottomSpacer() } } + +@Composable() +private fun ServerOperatorRow( + operator: ServerOperator +) { + SectionItemView( + { + ModalManager.start.showModalCloseable { close -> + OperatorInfoView(operator) + } + } + ) { + Image( + painterResource(operator.logo), + operator.tradeName, + modifier = Modifier.size(24.dp) + ) + TextIconSpaced() + Text(operator.tradeName) + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt index 3836d5b6df..28ca88584d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt @@ -410,7 +410,7 @@ fun OperatorViewLayout( } @Composable -private fun OperatorInfoView(serverOperator: ServerOperator) { +fun OperatorInfoView(serverOperator: ServerOperator) { ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.operator_info_title)) diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index c412ec42ee..29df338079 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1080,6 +1080,7 @@ The app protects your privacy by using different operators in each conversation. When more than one operator is enabled, none of them has metadata to learn who communicates with whom. For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + About operators Select network operators to use. How it helps privacy You can configure servers via settings. From 615c4839122177cd08b854716c0c784ea8eb62ff Mon Sep 17 00:00:00 2001 From: Diogo Date: Sat, 7 Dec 2024 14:20:01 +0000 Subject: [PATCH 204/567] android: onboarding small design adjustments (#5346) * android: onboarding small design adjustments * bigger --------- Co-authored-by: Evgeny Poberezkin --- .../kotlin/chat/simplex/common/ui/theme/Theme.kt | 1 + .../kotlin/chat/simplex/common/views/WelcomeView.kt | 4 ++-- .../views/onboarding/ChooseServerOperators.kt | 13 +++++++------ .../common/views/onboarding/SetNotificationsMode.kt | 6 +++--- .../views/onboarding/SetupDatabasePassphrase.kt | 2 +- .../simplex/common/views/onboarding/SimpleXInfo.kt | 11 ++++++----- 6 files changed, 20 insertions(+), 17 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt index 80542ced02..01e19ea478 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt @@ -609,6 +609,7 @@ fun themedBackgroundBrush(): Brush = Brush.linearGradient( ) val DEFAULT_PADDING = 20.dp +val DEFAULT_ONBOARDING_HORIZONTAL_PADDING = 25.dp val DEFAULT_SPACE_AFTER_ICON = 4.dp val DEFAULT_PADDING_HALF = DEFAULT_PADDING / 2 val DEFAULT_BOTTOM_PADDING = 48.dp diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt index 024929030e..8317c6cf6c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt @@ -117,7 +117,7 @@ fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) { ColumnWithScrollBar { val displayName = rememberSaveable { mutableStateOf("") } val focusRequester = remember { FocusRequester() } - Column(if (appPlatform.isAndroid) Modifier.fillMaxSize().padding(start = DEFAULT_PADDING * 2, end = DEFAULT_PADDING * 2, bottom = DEFAULT_PADDING) else Modifier.widthIn(max = 600.dp).fillMaxHeight().padding(horizontal = DEFAULT_PADDING).align(Alignment.CenterHorizontally)) { + Column(if (appPlatform.isAndroid) Modifier.fillMaxSize().padding(start = DEFAULT_ONBOARDING_HORIZONTAL_PADDING * 2, end = DEFAULT_ONBOARDING_HORIZONTAL_PADDING * 2, bottom = DEFAULT_PADDING) else Modifier.widthIn(max = 600.dp).fillMaxHeight().padding(horizontal = DEFAULT_PADDING).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { Box(Modifier.align(Alignment.CenterHorizontally)) { AppBarTitle(stringResource(MR.strings.create_your_profile), bottomPadding = DEFAULT_PADDING, withPadding = false) } @@ -130,7 +130,7 @@ fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) { Spacer(Modifier.fillMaxHeight().weight(1f)) Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { OnboardingActionButton( - if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp), + if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth() else Modifier.widthIn(min = 300.dp), labelId = MR.strings.create_profile_button, onboarding = null, enabled = canCreateProfile(displayName.value), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt index e706a0d8e9..dde1fb68ce 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt @@ -61,7 +61,8 @@ fun ModalData.ChooseServerOperators( Column(( if (appPlatform.isDesktop) Modifier.width(600.dp).align(Alignment.CenterHorizontally) else Modifier) .fillMaxWidth() - .padding(horizontal = DEFAULT_PADDING) + .padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING), + horizontalAlignment = Alignment.CenterHorizontally ) { serverOperators.value.forEachIndexed { index, srvOperator -> OperatorCheckView(srvOperator, selectedOperatorIds) @@ -173,7 +174,7 @@ private fun ReviewConditionsButton( modalManager: ModalManager ) { OnboardingActionButton( - modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp), + modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth() else Modifier.widthIn(min = 300.dp), labelId = MR.strings.operator_review_conditions, onboarding = null, enabled = enabled, @@ -188,7 +189,7 @@ private fun ReviewConditionsButton( @Composable private fun SetOperatorsButton(enabled: Boolean, onboarding: Boolean, serverOperators: State>, selectedOperatorIds: State>, close: () -> Unit) { OnboardingActionButton( - modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp), + modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth() else Modifier.widthIn(min = 300.dp), labelId = MR.strings.onboarding_network_operators_update, onboarding = null, enabled = enabled, @@ -210,7 +211,7 @@ private fun SetOperatorsButton(enabled: Boolean, onboarding: Boolean, serverOper @Composable private fun ContinueButton(enabled: Boolean, onboarding: Boolean, close: () -> Unit) { OnboardingActionButton( - modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp), + modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth() else Modifier.widthIn(min = 300.dp), labelId = MR.strings.onboarding_network_operators_continue, onboarding = null, enabled = enabled, @@ -238,7 +239,7 @@ private fun ReviewConditionsView( // remembering both since we don't want to reload the view after the user accepts conditions val operatorsWithConditionsAccepted = remember { chatModel.conditions.value.serverOperators.filter { it.conditionsAcceptance.conditionsAccepted } } val acceptForOperators = remember { selectedOperators.value.filter { !it.conditionsAcceptance.conditionsAccepted } } - ColumnWithScrollBar(modifier = Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING)) { + ColumnWithScrollBar(modifier = Modifier.fillMaxSize().padding(horizontal = if (onboarding) DEFAULT_ONBOARDING_HORIZONTAL_PADDING else DEFAULT_PADDING)) { AppBarTitle(stringResource(MR.strings.operator_conditions_of_use), withPadding = false, enableAlphaChanges = false, bottomPadding = DEFAULT_PADDING) if (operatorsWithConditionsAccepted.isNotEmpty()) { ReadableText(MR.strings.operator_conditions_accepted_for_some, args = operatorsWithConditionsAccepted.joinToString(", ") { it.legalName_ }) @@ -271,7 +272,7 @@ private fun AcceptConditionsButton( } } OnboardingActionButton( - modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier, + modifier = if (appPlatform.isAndroid) Modifier.fillMaxWidth() else Modifier, labelId = MR.strings.accept_conditions, onboarding = null, onclick = { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt index 9e6287771f..84f473067f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt @@ -35,14 +35,14 @@ fun SetNotificationsMode(m: ChatModel) { AppBarTitle(stringResource(MR.strings.onboarding_notifications_mode_title), bottomPadding = DEFAULT_PADDING) } val currentMode = rememberSaveable { mutableStateOf(NotificationsMode.default) } - Column(Modifier.padding(horizontal = DEFAULT_PADDING).fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { + Column(Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { OnboardingInformationButton( stringResource(MR.strings.onboarding_notifications_mode_subtitle), onClick = { ModalManager.fullscreen.showModalCloseable { NotificationBatteryUsageInfo() } } ) } Spacer(Modifier.weight(1f)) - Column(Modifier.padding(horizontal = DEFAULT_PADDING)) { + Column(Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING)) { SelectableCard(currentMode, NotificationsMode.SERVICE, stringResource(MR.strings.onboarding_notifications_mode_service), annotatedStringResource(MR.strings.onboarding_notifications_mode_service_desc_short)) { currentMode.value = NotificationsMode.SERVICE } @@ -56,7 +56,7 @@ fun SetNotificationsMode(m: ChatModel) { Spacer(Modifier.weight(1f)) Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { OnboardingActionButton( - modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier, + modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth() else Modifier, labelId = MR.strings.use_chat, onboarding = OnboardingStage.OnboardingComplete, onclick = { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt index e7db51c768..c6eceb0ce2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt @@ -190,7 +190,7 @@ private fun SetupDatabasePassphraseLayout( @Composable private fun SetPassphraseButton(disabled: Boolean, onClick: () -> Unit) { OnboardingActionButton( - if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp), + if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING).fillMaxWidth() else Modifier.widthIn(min = 300.dp), labelId = MR.strings.set_database_passphrase, onboarding = null, onclick = onClick, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt index 020d3493b9..85ef1b513a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt @@ -5,6 +5,7 @@ import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -52,7 +53,7 @@ fun SimpleXInfoLayout( user: User?, onboardingStage: SharedPreference? ) { - ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_PADDING), horizontalAlignment = Alignment.CenterHorizontally) { + ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING), horizontalAlignment = Alignment.CenterHorizontally) { Box(Modifier.widthIn(max = if (appPlatform.isAndroid) 250.dp else 500.dp).padding(top = DEFAULT_PADDING + 8.dp), contentAlignment = Alignment.Center) { SimpleXLogo() } @@ -73,7 +74,7 @@ fun SimpleXInfoLayout( Column(Modifier.fillMaxHeight().weight(1f)) { } if (onboardingStage != null) { - Column(Modifier.padding(horizontal = DEFAULT_PADDING).widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally,) { + Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally,) { OnboardingActionButton(user, onboardingStage) TextButtonBelowOnboardingButton(stringResource(MR.strings.migrate_from_another_device)) { chatModel.migrationState.value = MigrationToState.PasteOrScanLink @@ -139,7 +140,7 @@ fun OnboardingActionButton( shape = CircleShape, enabled = enabled, // elevation = ButtonDefaults.elevation(defaultElevation = 0.dp, focusedElevation = 0.dp, pressedElevation = 0.dp, hoveredElevation = 0.dp), - contentPadding = PaddingValues(horizontal = if (icon == null) DEFAULT_PADDING * 2 else DEFAULT_PADDING * 1.5f, vertical = DEFAULT_PADDING), + contentPadding = PaddingValues(horizontal = if (icon == null) DEFAULT_PADDING * 2 else DEFAULT_PADDING * 1.5f, vertical = 17.dp), colors = ButtonDefaults.buttonColors(MaterialTheme.colors.primary, disabledBackgroundColor = MaterialTheme.colors.secondary) ) { if (icon != null) { @@ -153,8 +154,8 @@ fun OnboardingActionButton( fun TextButtonBelowOnboardingButton(text: String, onClick: (() -> Unit)?) { val state = getKeyboardState() val enabled = onClick != null - val topPadding by animateDpAsState(if (appPlatform.isAndroid && state.value == KeyboardState.Opened) 0.dp else DEFAULT_PADDING_HALF) - val bottomPadding by animateDpAsState(if (appPlatform.isAndroid && state.value == KeyboardState.Opened) 0.dp else DEFAULT_PADDING_HALF) + val topPadding by animateDpAsState(if (appPlatform.isAndroid && state.value == KeyboardState.Opened) 0.dp else 7.5.dp) + val bottomPadding by animateDpAsState(if (appPlatform.isAndroid && state.value == KeyboardState.Opened) 0.dp else 7.5.dp) if ((appPlatform.isAndroid && state.value == KeyboardState.Closed) || topPadding > 0.dp) { TextButton({ onClick?.invoke() }, Modifier.padding(top = topPadding, bottom = bottomPadding).clip(CircleShape), enabled = enabled) { Text( From 83f0bd9fd33c982a8e8b5e8ea87abdacb352891d Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Sat, 7 Dec 2024 21:24:14 +0700 Subject: [PATCH 205/567] android, desktop: onboarding button multiline layout (#5348) Co-authored-by: Evgeny Poberezkin --- .../common/views/onboarding/SimpleXInfo.kt | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt index 85ef1b513a..e5d00fddd1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt @@ -14,6 +14,8 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.layout +import androidx.compose.ui.text.TextLayoutResult import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight @@ -28,6 +30,8 @@ import chat.simplex.common.views.migration.MigrateToDeviceView import chat.simplex.common.views.migration.MigrationToState import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource +import kotlin.math.ceil +import kotlin.math.floor @Composable fun SimpleXInfo(chatModel: ChatModel, onboarding: Boolean = true) { @@ -188,7 +192,35 @@ fun OnboardingInformationButton( null, tint = MaterialTheme.colors.primary ) - Text(text, style = MaterialTheme.typography.button, color = MaterialTheme.colors.primary) + // https://issuetracker.google.com/issues/206039942#comment32 + var textLayoutResult: TextLayoutResult? by remember { mutableStateOf(null) } + Text( + text, + Modifier + .layout { measurable, constraints -> + val placeable = measurable.measure(constraints) + val newTextLayoutResult = textLayoutResult + + if (newTextLayoutResult == null || newTextLayoutResult.lineCount == 0) { + // Default behavior if there is no text or the text layout is not measured yet + layout(placeable.width, placeable.height) { + placeable.placeRelative(0, 0) + } + } else { + val minX = (0 until newTextLayoutResult.lineCount).minOf(newTextLayoutResult::getLineLeft) + val maxX = (0 until newTextLayoutResult.lineCount).maxOf(newTextLayoutResult::getLineRight) + + layout(ceil(maxX - minX).toInt(), placeable.height) { + placeable.place(-floor(minX).toInt(), 0) + } + } + }, + onTextLayout = { + textLayoutResult = it + }, + style = MaterialTheme.typography.button, + color = MaterialTheme.colors.primary + ) } } } From cbb3da8f835246f0bad43f5176aa74659b2ba6b7 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 7 Dec 2024 14:40:35 +0000 Subject: [PATCH 206/567] core: 6.2.0.7 (simplexmq: 6.2.0.7) --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat/Remote.hs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cabal.project b/cabal.project index 3212768506..ae24afd374 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 9893935e7c3cf8d102c85730a4e48d32f05c2ec7 + tag: 79e9447b73cc315ce35042b0a5f210c07ea39b07 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 098851e9ff..d0411c584d 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."9893935e7c3cf8d102c85730a4e48d32f05c2ec7" = "1bpgsdnmk8fml6ad9bjbvyichvd0kq0nqj562xyy5y1npymaxpyn"; + "https://github.com/simplex-chat/simplexmq.git"."79e9447b73cc315ce35042b0a5f210c07ea39b07" = "16z7z5a3f7gw0h188manykp008d1bqpydlrj7h497mgyjmp4cy9m"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index ba713420fc..cfc4fe2fa0 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -73,11 +73,11 @@ import UnliftIO.Directory (copyFile, createDirectoryIfMissing, doesDirectoryExis -- when acting as host minRemoteCtrlVersion :: AppVersion -minRemoteCtrlVersion = AppVersion [6, 2, 0, 4] +minRemoteCtrlVersion = AppVersion [6, 2, 0, 7] -- when acting as controller minRemoteHostVersion :: AppVersion -minRemoteHostVersion = AppVersion [6, 2, 0, 4] +minRemoteHostVersion = AppVersion [6, 2, 0, 7] currentAppVersion :: AppVersion currentAppVersion = AppVersion SC.version From fe0d811bf7e579b96089ead4ee0d7b42c81ad10c Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 7 Dec 2024 14:41:54 +0000 Subject: [PATCH 207/567] ui: operator information (#5343) * ios: operator information * android, desktop: operator information * move texts, simplify navigation --- .../Onboarding/ChooseServerOperators.swift | 58 +++++------- .../Views/Onboarding/CreateProfile.swift | 1 - .../Onboarding/SetNotificationsMode.swift | 2 +- .../NetworkAndServers/OperatorView.swift | 94 ++++++++----------- .../bg.xcloc/Localized Contents/bg.xliff | 10 +- .../cs.xcloc/Localized Contents/cs.xliff | 10 +- .../de.xcloc/Localized Contents/de.xliff | 11 ++- .../en.xcloc/Localized Contents/en.xliff | 13 ++- .../es.xcloc/Localized Contents/es.xliff | 11 ++- .../fi.xcloc/Localized Contents/fi.xliff | 10 +- .../fr.xcloc/Localized Contents/fr.xliff | 10 +- .../hu.xcloc/Localized Contents/hu.xliff | 10 +- .../it.xcloc/Localized Contents/it.xliff | 10 +- .../ja.xcloc/Localized Contents/ja.xliff | 10 +- .../nl.xcloc/Localized Contents/nl.xliff | 11 ++- .../pl.xcloc/Localized Contents/pl.xliff | 10 +- .../ru.xcloc/Localized Contents/ru.xliff | 11 ++- .../th.xcloc/Localized Contents/th.xliff | 10 +- .../tr.xcloc/Localized Contents/tr.xliff | 10 +- .../uk.xcloc/Localized Contents/uk.xliff | 10 +- .../Localized Contents/zh-Hans.xliff | 10 +- apps/ios/SimpleXChat/APITypes.swift | 61 +++--------- apps/ios/bg.lproj/Localizable.strings | 2 +- apps/ios/cs.lproj/Localizable.strings | 2 +- apps/ios/de.lproj/Localizable.strings | 7 +- apps/ios/es.lproj/Localizable.strings | 7 +- apps/ios/fi.lproj/Localizable.strings | 2 +- apps/ios/fr.lproj/Localizable.strings | 2 +- apps/ios/hu.lproj/Localizable.strings | 4 +- apps/ios/it.lproj/Localizable.strings | 4 +- apps/ios/ja.lproj/Localizable.strings | 2 +- apps/ios/nl.lproj/Localizable.strings | 7 +- apps/ios/pl.lproj/Localizable.strings | 2 +- apps/ios/ru.lproj/Localizable.strings | 7 +- apps/ios/th.lproj/Localizable.strings | 2 +- apps/ios/tr.lproj/Localizable.strings | 2 +- apps/ios/uk.lproj/Localizable.strings | 4 +- apps/ios/zh-Hans.lproj/Localizable.strings | 2 +- .../chat/simplex/common/model/SimpleXAPI.kt | 53 ++--------- .../views/onboarding/ChooseServerOperators.kt | 3 + .../networkAndServers/OperatorView.kt | 18 ++-- .../commonMain/resources/MR/base/strings.xml | 1 + .../commonMain/resources/MR/ru/strings.xml | 1 + 43 files changed, 254 insertions(+), 273 deletions(-) diff --git a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift index cc47374257..1a0a736acd 100644 --- a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift +++ b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift @@ -62,7 +62,6 @@ struct ChooseServerOperators: View { var onboarding: Bool @State private var serverOperators: [ServerOperator] = [] @State private var selectedOperatorIds = Set() - @State private var reviewConditionsNavLinkActive = false @State private var sheetItem: ChooseServerOperatorsSheet? = nil @State private var notificationsModeNavLinkActive = false @State private var justOpened = true @@ -79,7 +78,7 @@ struct ChooseServerOperators: View { .frame(maxWidth: .infinity, alignment: .center) if onboarding { - title.padding(.top, 50) + title.padding(.top, 25) } else { title } @@ -92,11 +91,14 @@ struct ChooseServerOperators: View { ForEach(serverOperators) { srvOperator in operatorCheckView(srvOperator) } - Text("You can configure servers via settings.") - .font(.footnote) - .multilineTextAlignment(.center) - .frame(maxWidth: .infinity, alignment: .center) - .padding(.horizontal, 32) + VStack { + Text("SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app.").padding(.bottom, 8) + Text("You can configure servers via settings.") + } + .font(.footnote) + .multilineTextAlignment(.center) + .frame(maxWidth: .infinity, alignment: .center) + .padding(.horizontal, 16) Spacer() @@ -166,8 +168,9 @@ struct ChooseServerOperators: View { .modifier(ThemedBackground(grouped: true)) } } + .frame(maxHeight: .infinity, alignment: .top) } - .frame(maxHeight: .infinity) + .frame(maxHeight: .infinity, alignment: .top) .padding(onboarding ? 25 : 16) } @@ -214,23 +217,15 @@ struct ChooseServerOperators: View { } private func reviewConditionsButton() -> some View { - ZStack { - Button { - reviewConditionsNavLinkActive = true - } label: { - Text("Review conditions") - } - .buttonStyle(OnboardingButtonStyle(isDisabled: selectedOperatorIds.isEmpty)) - .disabled(selectedOperatorIds.isEmpty) - - NavigationLink(isActive: $reviewConditionsNavLinkActive) { - reviewConditionsDestinationView() - } label: { - EmptyView() - } - .frame(width: 1, height: 1) - .hidden() + NavigationLink("Review conditions") { + reviewConditionsView() + .navigationTitle("Conditions of use") + .navigationBarTitleDisplayMode(.large) + .toolbar { ToolbarItem(placement: .navigationBarTrailing, content: conditionsLinkButton) } + .modifier(ThemedBackground(grouped: true)) } + .buttonStyle(OnboardingButtonStyle(isDisabled: selectedOperatorIds.isEmpty)) + .disabled(selectedOperatorIds.isEmpty) } private func setOperatorsButton() -> some View { @@ -309,20 +304,12 @@ struct ChooseServerOperators: View { .modifier(ThemedBackground()) } - private func reviewConditionsDestinationView() -> some View { - reviewConditionsView() - .navigationTitle("Conditions of use") - .navigationBarTitleDisplayMode(.large) - .toolbar { ToolbarItem(placement: .navigationBarTrailing, content: conditionsLinkButton) } - .modifier(ThemedBackground(grouped: true)) - } - @ViewBuilder private func reviewConditionsView() -> some View { let operatorsWithConditionsAccepted = ChatModel.shared.conditions.serverOperators.filter { $0.conditionsAcceptance.conditionsAccepted } let acceptForOperators = selectedOperators.filter { !$0.conditionsAcceptance.conditionsAccepted } VStack(alignment: .leading, spacing: 20) { if !operatorsWithConditionsAccepted.isEmpty { - Text("Conditions are already accepted for following operator(s): **\(operatorsWithConditionsAccepted.map { $0.legalName_ }.joined(separator: ", "))**.") + Text("Conditions are already accepted for these operator(s): **\(operatorsWithConditionsAccepted.map { $0.legalName_ }.joined(separator: ", "))**.") Text("The same conditions will apply to operator(s): **\(acceptForOperators.map { $0.legalName_ }.joined(separator: ", "))**.") } else { Text("Conditions will be accepted for operator(s): **\(acceptForOperators.map { $0.legalName_ }.joined(separator: ", "))**.") @@ -415,13 +402,12 @@ struct ChooseServerOperatorsInfoView: View { var body: some View { NavigationView { List { - VStack(alignment: .leading) { + VStack(alignment: .leading, spacing: 12) { Text("The app protects your privacy by using different operators in each conversation.") - .padding(.bottom) Text("When more than one operator is enabled, none of them has metadata to learn who communicates with whom.") - .padding(.bottom) Text("For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server.") } + .fixedSize(horizontal: false, vertical: true) .listRowBackground(Color.clear) .listRowSeparator(.hidden) .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) diff --git a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift index 7665e57cc1..14ad9dfb08 100644 --- a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift +++ b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift @@ -136,7 +136,6 @@ struct CreateFirstProfile: View { .multilineTextAlignment(.center) } .frame(maxWidth: .infinity) // Ensures it takes up the full width - .padding(.top, 25) .padding(.horizontal, 10) HStack { diff --git a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift index 642220454c..97e1f49382 100644 --- a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift +++ b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift @@ -22,7 +22,7 @@ struct SetNotificationsMode: View { Text("Push notifications") .font(.largeTitle) .bold() - .padding(.top, 50) + .padding(.top, 25) infoText() diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift index b1e4d36eda..cea9dd0635 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift @@ -331,9 +331,12 @@ struct OperatorInfoView: View { Text(d) } } + Link(serverOperator.info.website.absoluteString, destination: serverOperator.info.website) } - Section { - Link("\(serverOperator.info.website)", destination: URL(string: serverOperator.info.website)!) + if let selfhost = serverOperator.info.selfhost { + Section { + Link(selfhost.text, destination: selfhost.link) + } } } } @@ -421,7 +424,6 @@ struct SingleOperatorUsageConditionsView: View { @Binding var userServers: [UserOperatorServers] @Binding var serverErrors: [UserServersError] var operatorIndex: Int - @State private var usageConditionsNavLinkActive: Bool = false var body: some View { viewBody() @@ -433,52 +435,45 @@ struct SingleOperatorUsageConditionsView: View { // In current UI implementation this branch doesn't get shown - as conditions can't be opened from inside operator once accepted VStack(alignment: .leading, spacing: 20) { - Group { - viewHeader() - ConditionsTextView() - .padding(.bottom) - .padding(.bottom) - } - .padding(.horizontal) + viewHeader() + ConditionsTextView() } + .padding(.bottom) + .padding(.bottom) + .padding(.horizontal) .frame(maxHeight: .infinity) } else if !operatorsWithConditionsAccepted.isEmpty { NavigationView { VStack(alignment: .leading, spacing: 20) { - Group { - viewHeader() - Text("Conditions are already accepted for following operator(s): **\(operatorsWithConditionsAccepted.map { $0.legalName_ }.joined(separator: ", "))**.") - Text("The same conditions will apply to operator **\(userServers[operatorIndex].operator_.legalName_)**.") - conditionsAppliedToOtherOperatorsText() - usageConditionsNavLinkButton() + viewHeader() + Text("Conditions are already accepted for these operator(s): **\(operatorsWithConditionsAccepted.map { $0.legalName_ }.joined(separator: ", "))**.") + Text("The same conditions will apply to operator **\(userServers[operatorIndex].operator_.legalName_)**.") + conditionsAppliedToOtherOperatorsText() + Spacer() - Spacer() - - acceptConditionsButton() - .padding(.bottom) - .padding(.bottom) - } - .padding(.horizontal) + acceptConditionsButton() + usageConditionsNavLinkButton() } + .padding(.bottom) + .padding(.bottom) + .padding(.horizontal) .frame(maxHeight: .infinity) } } else { VStack(alignment: .leading, spacing: 20) { - Group { - viewHeader() - Text("To use the servers of **\(userServers[operatorIndex].operator_.legalName_)**, accept conditions of use.") - conditionsAppliedToOtherOperatorsText() - ConditionsTextView() - acceptConditionsButton() - .padding(.bottom) - .padding(.bottom) - } - .padding(.horizontal) + viewHeader() + Text("To use the servers of **\(userServers[operatorIndex].operator_.legalName_)**, accept conditions of use.") + conditionsAppliedToOtherOperatorsText() + ConditionsTextView() + acceptConditionsButton() + .padding(.bottom) + .padding(.bottom) } + .padding(.horizontal) .frame(maxHeight: .infinity) } @@ -545,31 +540,16 @@ struct SingleOperatorUsageConditionsView: View { } private func usageConditionsNavLinkButton() -> some View { - ZStack { - Button { - usageConditionsNavLinkActive = true - } label: { - Text("View conditions") - } - - NavigationLink(isActive: $usageConditionsNavLinkActive) { - usageConditionsDestinationView() - } label: { - EmptyView() - } - .frame(width: 1, height: 1) - .hidden() + NavigationLink("View conditions") { + ConditionsTextView() + .padding() + .navigationTitle("Conditions of use") + .navigationBarTitleDisplayMode(.large) + .toolbar { ToolbarItem(placement: .navigationBarTrailing, content: conditionsLinkButton) } + .modifier(ThemedBackground(grouped: true)) } - } - - private func usageConditionsDestinationView() -> some View { - ConditionsTextView() - .padding() - .padding(.bottom) - .navigationTitle("Conditions of use") - .navigationBarTitleDisplayMode(.large) - .toolbar { ToolbarItem(placement: .navigationBarTrailing, content: conditionsLinkButton) } - .modifier(ThemedBackground(grouped: true)) + .font(.callout) + .frame(maxWidth: .infinity, alignment: .center) } } diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 4af0007eea..9260ac41c0 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -1537,8 +1537,8 @@ Conditions are accepted for the operator(s): **%@**. No comment provided by engineer. - - Conditions are already accepted for following operator(s): **%@**. + + Conditions are already accepted for these operator(s): **%@**. No comment provided by engineer. @@ -5766,7 +5766,7 @@ Enable in *Network & servers* settings. Save and notify contact Запази и уведоми контакта - No comment provided by engineer. + alert button Save and notify group members @@ -6380,6 +6380,10 @@ Enable in *Network & servers* settings. SimpleX Адрес No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. Сигурността на SimpleX Chat беше одитирана от Trail of Bits. diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index 7d92f62f12..d921471f7f 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -1492,8 +1492,8 @@ Conditions are accepted for the operator(s): **%@**. No comment provided by engineer. - - Conditions are already accepted for following operator(s): **%@**. + + Conditions are already accepted for these operator(s): **%@**. No comment provided by engineer. @@ -5576,7 +5576,7 @@ Enable in *Network & servers* settings. Save and notify contact Uložit a upozornit kontakt - No comment provided by engineer. + alert button Save and notify group members @@ -6178,6 +6178,10 @@ Enable in *Network & servers* settings. SimpleX Adresa No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. Zabezpečení SimpleX chatu bylo auditováno společností Trail of Bits. diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 516baf49b7..053a1faf73 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -1606,8 +1606,8 @@ Die Nutzungsbedingungen der/des Betreiber(s) werden akzeptiert: **%@**. No comment provided by engineer. - - Conditions are already accepted for following operator(s): **%@**. + + Conditions are already accepted for these operator(s): **%@**. Die Nutzungsbedingungen der/des folgenden Betreiber(s) wurden schon akzeptiert: **%@**. No comment provided by engineer. @@ -6034,7 +6034,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Save and notify contact Speichern und Kontakt benachrichtigen - No comment provided by engineer. + alert button Save and notify group members @@ -6692,6 +6692,11 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. SimpleX-Adresse No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + SimpleX-Chat und Flux haben vereinbart, die von Flux betriebenen Server in die App aufzunehmen. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. Die Sicherheit von SimpleX Chat wurde von Trail of Bits überprüft. diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 699091e2d8..004d7f0d31 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -1617,9 +1617,9 @@ Conditions are accepted for the operator(s): **%@**. No comment provided by engineer. - - Conditions are already accepted for following operator(s): **%@**. - Conditions are already accepted for following operator(s): **%@**. + + Conditions are already accepted for these operator(s): **%@**. + Conditions are already accepted for these operator(s): **%@**. No comment provided by engineer. @@ -6056,7 +6056,7 @@ Enable in *Network & servers* settings. Save and notify contact Save and notify contact - No comment provided by engineer. + alert button Save and notify group members @@ -6714,6 +6714,11 @@ Enable in *Network & servers* settings. SimpleX Address No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. SimpleX Chat security was audited by Trail of Bits. diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index ddb5d6b1a1..ea966ea63b 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -1606,8 +1606,8 @@ Las condiciones se han aceptado para el(los) operador(s): **%@**. No comment provided by engineer. - - Conditions are already accepted for following operator(s): **%@**. + + Conditions are already accepted for these operator(s): **%@**. Las condiciones ya se han aceptado para el/los siguiente(s) operador(s): **%@**. No comment provided by engineer. @@ -6034,7 +6034,7 @@ Actívalo en ajustes de *Servidores y Redes*. Save and notify contact Guardar y notificar contacto - No comment provided by engineer. + alert button Save and notify group members @@ -6692,6 +6692,11 @@ Actívalo en ajustes de *Servidores y Redes*. Dirección SimpleX No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + Simplex Chat y Flux han acordado incluir servidores operados por Flux en la aplicación + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. La seguridad de SimpleX Chat ha sido auditada por Trail of Bits. diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index 12c8d0e3ca..2f67ee9d7d 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -1485,8 +1485,8 @@ Conditions are accepted for the operator(s): **%@**. No comment provided by engineer. - - Conditions are already accepted for following operator(s): **%@**. + + Conditions are already accepted for these operator(s): **%@**. No comment provided by engineer. @@ -5564,7 +5564,7 @@ Enable in *Network & servers* settings. Save and notify contact Tallenna ja ilmoita kontaktille - No comment provided by engineer. + alert button Save and notify group members @@ -6165,6 +6165,10 @@ Enable in *Network & servers* settings. SimpleX-osoite No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. Trail of Bits on tarkastanut SimpleX Chatin tietoturvan. diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index e82f01e33b..74002293d7 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -1589,8 +1589,8 @@ Conditions are accepted for the operator(s): **%@**. No comment provided by engineer. - - Conditions are already accepted for following operator(s): **%@**. + + Conditions are already accepted for these operator(s): **%@**. No comment provided by engineer. @@ -5972,7 +5972,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Save and notify contact Enregistrer et en informer le contact - No comment provided by engineer. + alert button Save and notify group members @@ -6623,6 +6623,10 @@ Activez-le dans les paramètres *Réseau et serveurs*. Adresse SimpleX No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. La sécurité de SimpleX Chat a été auditée par Trail of Bits. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 5f05efaa4d..598bee5485 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -1616,8 +1616,8 @@ A következő üzemeltető(k) számára elfogadott feltételek: **%@**. No comment provided by engineer. - - Conditions are already accepted for following operator(s): **%@**. + + Conditions are already accepted for these operator(s): **%@**. A feltételek már el lettek fogadva a következő üzemeltető(k) számára: **%@**. No comment provided by engineer. @@ -6055,7 +6055,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. Save and notify contact Mentés és az ismerős értesítése - No comment provided by engineer. + alert button Save and notify group members @@ -6713,6 +6713,10 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. SimpleX-cím No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. A SimpleX Chat biztonsága a Trail of Bits által lett auditálva. diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index ebfba7e415..67633b7ae8 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -1616,8 +1616,8 @@ Le condizioni sono state accettate per gli operatori: **%@**. No comment provided by engineer. - - Conditions are already accepted for following operator(s): **%@**. + + Conditions are already accepted for these operator(s): **%@**. Le condizioni sono già state accettate per i seguenti operatori: **%@**. No comment provided by engineer. @@ -6055,7 +6055,7 @@ Attivalo nelle impostazioni *Rete e server*. Save and notify contact Salva e avvisa il contatto - No comment provided by engineer. + alert button Save and notify group members @@ -6713,6 +6713,10 @@ Attivalo nelle impostazioni *Rete e server*. Indirizzo SimpleX No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. La sicurezza di SimpleX Chat è stata verificata da Trail of Bits. diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index ba35db0c03..43e6f24cf7 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -1515,8 +1515,8 @@ Conditions are accepted for the operator(s): **%@**. No comment provided by engineer. - - Conditions are already accepted for following operator(s): **%@**. + + Conditions are already accepted for these operator(s): **%@**. No comment provided by engineer. @@ -5613,7 +5613,7 @@ Enable in *Network & servers* settings. Save and notify contact 保存して、連絡先にに知らせる - No comment provided by engineer. + alert button Save and notify group members @@ -6207,6 +6207,10 @@ Enable in *Network & servers* settings. SimpleXアドレス No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. SimpleX Chat のセキュリティは Trail of Bits によって監査されました。 diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 5631d9bd7d..c30370fc5a 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -1616,8 +1616,8 @@ Voorwaarden worden geaccepteerd voor de operator(s): **%@**. No comment provided by engineer. - - Conditions are already accepted for following operator(s): **%@**. + + Conditions are already accepted for these operator(s): **%@**. Voorwaarden zijn reeds geaccepteerd voor de volgende operator(s): **%@**. No comment provided by engineer. @@ -6055,7 +6055,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Save and notify contact Opslaan en Contact melden - No comment provided by engineer. + alert button Save and notify group members @@ -6713,6 +6713,11 @@ Schakel dit in in *Netwerk en servers*-instellingen. SimpleX adres No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + Simplex-chat en flux hebben een overeenkomst gemaakt om door flux geëxploiteerde servers in de app op te nemen. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. De beveiliging van SimpleX Chat is gecontroleerd door Trail of Bits. diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index dbc95b6527..e7c9863152 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -1584,8 +1584,8 @@ Conditions are accepted for the operator(s): **%@**. No comment provided by engineer. - - Conditions are already accepted for following operator(s): **%@**. + + Conditions are already accepted for these operator(s): **%@**. No comment provided by engineer. @@ -5962,7 +5962,7 @@ Włącz w ustawianiach *Sieć i serwery* . Save and notify contact Zapisz i powiadom kontakt - No comment provided by engineer. + alert button Save and notify group members @@ -6613,6 +6613,10 @@ Włącz w ustawianiach *Sieć i serwery* . Adres SimpleX No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. Bezpieczeństwo SimpleX Chat zostało zaudytowane przez Trail of Bits. diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 95bd74e484..943ea67ef4 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -1617,8 +1617,8 @@ Условия приняты для оператора(ов): **%@**. No comment provided by engineer. - - Conditions are already accepted for following operator(s): **%@**. + + Conditions are already accepted for these operator(s): **%@**. Условия уже приняты для следующих оператора(ов): **%@**. No comment provided by engineer. @@ -6055,7 +6055,7 @@ Enable in *Network & servers* settings. Save and notify contact Сохранить и уведомить контакт - No comment provided by engineer. + alert button Save and notify group members @@ -6713,6 +6713,11 @@ Enable in *Network & servers* settings. Адрес SimpleX No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + SimpleX Chat и Flux заключили соглашение добавить серверы под управлением Flux в приложение. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. Безопасность SimpleX Chat была проверена Trail of Bits. diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index 14827be1b5..177f426c1a 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -1477,8 +1477,8 @@ Conditions are accepted for the operator(s): **%@**. No comment provided by engineer. - - Conditions are already accepted for following operator(s): **%@**. + + Conditions are already accepted for these operator(s): **%@**. No comment provided by engineer. @@ -5541,7 +5541,7 @@ Enable in *Network & servers* settings. Save and notify contact บันทึกและแจ้งผู้ติดต่อ - No comment provided by engineer. + alert button Save and notify group members @@ -6139,6 +6139,10 @@ Enable in *Network & servers* settings. ที่อยู่ SimpleX No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. ความปลอดภัยของ SimpleX Chat ได้รับการตรวจสอบโดย Trail of Bits diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index d12ef93f69..d88adc3235 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -1589,8 +1589,8 @@ Conditions are accepted for the operator(s): **%@**. No comment provided by engineer. - - Conditions are already accepted for following operator(s): **%@**. + + Conditions are already accepted for these operator(s): **%@**. No comment provided by engineer. @@ -5972,7 +5972,7 @@ Enable in *Network & servers* settings. Save and notify contact Kaydet ve kişilere bildir - No comment provided by engineer. + alert button Save and notify group members @@ -6623,6 +6623,10 @@ Enable in *Network & servers* settings. SimpleX Adresi No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. SimpleX Chat güvenliği Trails of Bits tarafından denetlenmiştir. diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index e228fd01e6..d68b5abbe1 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -1606,8 +1606,8 @@ Для оператора(ів) приймаються умови: **%@**. No comment provided by engineer. - - Conditions are already accepted for following operator(s): **%@**. + + Conditions are already accepted for these operator(s): **%@**. Умови вже прийняті для наступних операторів: **%@**. No comment provided by engineer. @@ -6034,7 +6034,7 @@ Enable in *Network & servers* settings. Save and notify contact Зберегти та повідомити контакт - No comment provided by engineer. + alert button Save and notify group members @@ -6692,6 +6692,10 @@ Enable in *Network & servers* settings. Адреса SimpleX No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. Безпека SimpleX Chat була перевірена компанією Trail of Bits. diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index 0b1b568385..99d4a5077f 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -1575,8 +1575,8 @@ Conditions are accepted for the operator(s): **%@**. No comment provided by engineer. - - Conditions are already accepted for following operator(s): **%@**. + + Conditions are already accepted for these operator(s): **%@**. No comment provided by engineer. @@ -5925,7 +5925,7 @@ Enable in *Network & servers* settings. Save and notify contact 保存并通知联系人 - No comment provided by engineer. + alert button Save and notify group members @@ -6570,6 +6570,10 @@ Enable in *Network & servers* settings. SimpleX 地址 No comment provided by engineer. + + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + No comment provided by engineer. + SimpleX Chat security was audited by Trail of Bits. SimpleX Chat 的安全性 由 Trail of Bits 审核。 diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index a9cf2ee599..884993f542 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -1212,13 +1212,12 @@ public enum ServerProtocol: String, Decodable { public enum OperatorTag: String, Codable { case simplex = "simplex" case flux = "flux" - case xyz = "xyz" - case demo = "demo" } -public struct ServerOperatorInfo: Decodable { +public struct ServerOperatorInfo { public var description: [String] - public var website: String + public var website: URL + public var selfhost: (text: String, link: URL)? = nil public var logo: String public var largeLogo: String public var logoDarkMode: String @@ -1228,10 +1227,10 @@ public struct ServerOperatorInfo: Decodable { public let operatorsInfo: Dictionary = [ .simplex: ServerOperatorInfo( description: [ - "SimpleX Chat is the first communication network that has no user profile IDs of any kind, not even random numbers or keys that identify the users.", + "SimpleX Chat is the first communication network that has no user profile IDs of any kind, not even random numbers or identity keys.", "SimpleX Chat Ltd develops the communication software for SimpleX network." ], - website: "https://simplex.chat", + website: URL(string: "https://simplex.chat")!, logo: "decentralized", largeLogo: "logo", logoDarkMode: "decentralized-light", @@ -1239,31 +1238,17 @@ public let operatorsInfo: Dictionary = [ ), .flux: ServerOperatorInfo( description: [ - "Flux is the largest decentralized cloud infrastructure, leveraging a global network of user-operated computational nodes.", - "Flux offers a powerful, scalable, and affordable platform designed to support individuals, businesses, and cutting-edge technologies like AI. With high uptime and worldwide distribution, Flux ensures reliable, accessible cloud computing for all." + "Flux is the largest decentralized cloud, based on a global network of user-operated nodes.", + "Flux offers a powerful, scalable, and affordable cutting edge technology platform for all.", + "Flux operates servers in SimpleX network to improve its privacy and decentralization." ], - website: "https://runonflux.com", + website: URL(string: "https://runonflux.com")!, + selfhost: (text: "Self-host SimpleX servers on Flux", link: URL(string: "https://home.runonflux.io/apps/marketplace?q=simplex")!), logo: "flux_logo_symbol", largeLogo: "flux_logo", logoDarkMode: "flux_logo_symbol", largeLogoDarkMode: "flux_logo-light" ), - .xyz: ServerOperatorInfo( - description: ["XYZ servers"], - website: "XYZ website", - logo: "shield", - largeLogo: "logo", - logoDarkMode: "shield", - largeLogoDarkMode: "logo-light" - ), - .demo: ServerOperatorInfo( - description: ["Demo operator"], - website: "Demo website", - logo: "decentralized", - largeLogo: "logo", - logoDarkMode: "decentralized-light", - largeLogoDarkMode: "logo-light" - ) ] public struct UsageConditions: Decodable { @@ -1358,7 +1343,7 @@ public struct ServerOperator: Identifiable, Equatable, Codable { public static let dummyOperatorInfo = ServerOperatorInfo( description: ["Default"], - website: "Default", + website: URL(string: "https://simplex.chat")!, logo: "decentralized", largeLogo: "logo", logoDarkMode: "decentralized-light", @@ -1384,30 +1369,6 @@ public struct ServerOperator: Identifiable, Equatable, Codable { smpRoles: ServerRoles(storage: true, proxy: true), xftpRoles: ServerRoles(storage: true, proxy: true) ) - - public static var sampleData2 = ServerOperator( - operatorId: 2, - operatorTag: .xyz, - tradeName: "XYZ", - legalName: nil, - serverDomains: ["xyz.com"], - conditionsAcceptance: .required(deadline: nil), - enabled: false, - smpRoles: ServerRoles(storage: false, proxy: true), - xftpRoles: ServerRoles(storage: false, proxy: true) - ) - - public static var sampleData3 = ServerOperator( - operatorId: 3, - operatorTag: .demo, - tradeName: "Demo", - legalName: nil, - serverDomains: ["demo.com"], - conditionsAcceptance: .required(deadline: nil), - enabled: false, - smpRoles: ServerRoles(storage: true, proxy: false), - xftpRoles: ServerRoles(storage: true, proxy: false) - ) } public struct ServerRoles: Equatable, Codable { diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index 91606d8569..f2059d5627 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -3139,7 +3139,7 @@ /* alert button */ "Save (and notify contacts)" = "Запази (и уведоми контактите)"; -/* No comment provided by engineer. */ +/* alert button */ "Save and notify contact" = "Запази и уведоми контакта"; /* No comment provided by engineer. */ diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index 96b149a8d5..837e76ebbf 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -2538,7 +2538,7 @@ /* alert button */ "Save (and notify contacts)" = "Uložit (a informovat kontakty)"; -/* No comment provided by engineer. */ +/* alert button */ "Save and notify contact" = "Uložit a upozornit kontakt"; /* No comment provided by engineer. */ diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 25f9cf32c1..a510b30477 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -1030,7 +1030,7 @@ "Conditions are accepted for the operator(s): **%@**." = "Die Nutzungsbedingungen der/des Betreiber(s) werden akzeptiert: **%@**."; /* No comment provided by engineer. */ -"Conditions are already accepted for following operator(s): **%@**." = "Die Nutzungsbedingungen der/des folgenden Betreiber(s) wurden schon akzeptiert: **%@**."; +"Conditions are already accepted for these operator(s): **%@**." = "Die Nutzungsbedingungen der/des folgenden Betreiber(s) wurden schon akzeptiert: **%@**."; /* No comment provided by engineer. */ "Conditions of use" = "Nutzungsbedingungen"; @@ -3952,7 +3952,7 @@ /* alert button */ "Save (and notify contacts)" = "Speichern (und Kontakte benachrichtigen)"; -/* No comment provided by engineer. */ +/* alert button */ "Save and notify contact" = "Speichern und Kontakt benachrichtigen"; /* No comment provided by engineer. */ @@ -4391,6 +4391,9 @@ /* No comment provided by engineer. */ "SimpleX address or 1-time link?" = "SimpleX-Adresse oder Einmal-Link?"; +/* No comment provided by engineer. */ +"SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "SimpleX-Chat und Flux haben vereinbart, die von Flux betriebenen Server in die App aufzunehmen."; + /* No comment provided by engineer. */ "SimpleX Chat security was audited by Trail of Bits." = "Die Sicherheit von SimpleX Chat wurde von Trail of Bits überprüft."; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index a1665cd716..9c0b815ad4 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -1030,7 +1030,7 @@ "Conditions are accepted for the operator(s): **%@**." = "Las condiciones se han aceptado para el(los) operador(s): **%@**."; /* No comment provided by engineer. */ -"Conditions are already accepted for following operator(s): **%@**." = "Las condiciones ya se han aceptado para el/los siguiente(s) operador(s): **%@**."; +"Conditions are already accepted for these operator(s): **%@**." = "Las condiciones ya se han aceptado para el/los siguiente(s) operador(s): **%@**."; /* No comment provided by engineer. */ "Conditions of use" = "Condiciones de uso"; @@ -3952,7 +3952,7 @@ /* alert button */ "Save (and notify contacts)" = "Guardar (y notificar contactos)"; -/* No comment provided by engineer. */ +/* alert button */ "Save and notify contact" = "Guardar y notificar contacto"; /* No comment provided by engineer. */ @@ -4391,6 +4391,9 @@ /* No comment provided by engineer. */ "SimpleX address or 1-time link?" = "Dirección SimpleX o enlace de un uso?"; +/* No comment provided by engineer. */ +"SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "Simplex Chat y Flux han acordado incluir servidores operados por Flux en la aplicación"; + /* No comment provided by engineer. */ "SimpleX Chat security was audited by Trail of Bits." = "La seguridad de SimpleX Chat ha sido auditada por Trail of Bits."; diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index e4b56e76a4..f0987f3e1b 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -2508,7 +2508,7 @@ /* alert button */ "Save (and notify contacts)" = "Tallenna (ja ilmoita kontakteille)"; -/* No comment provided by engineer. */ +/* alert button */ "Save and notify contact" = "Tallenna ja ilmoita kontaktille"; /* No comment provided by engineer. */ diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index e50d2c0967..2de5997f07 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -3763,7 +3763,7 @@ /* alert button */ "Save (and notify contacts)" = "Enregistrer (et en informer les contacts)"; -/* No comment provided by engineer. */ +/* alert button */ "Save and notify contact" = "Enregistrer et en informer le contact"; /* No comment provided by engineer. */ diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index 8c0da0ed57..58d28cd8ed 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -1060,7 +1060,7 @@ "Conditions are accepted for the operator(s): **%@**." = "A következő üzemeltető(k) számára elfogadott feltételek: **%@**."; /* No comment provided by engineer. */ -"Conditions are already accepted for following operator(s): **%@**." = "A feltételek már el lettek fogadva a következő üzemeltető(k) számára: **%@**."; +"Conditions are already accepted for these operator(s): **%@**." = "A feltételek már el lettek fogadva a következő üzemeltető(k) számára: **%@**."; /* No comment provided by engineer. */ "Conditions of use" = "Használati feltételek"; @@ -4015,7 +4015,7 @@ /* alert button */ "Save (and notify contacts)" = "Mentés és az ismerősök értesítése"; -/* No comment provided by engineer. */ +/* alert button */ "Save and notify contact" = "Mentés és az ismerős értesítése"; /* No comment provided by engineer. */ diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index 1242b488ac..25a672da26 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -1060,7 +1060,7 @@ "Conditions are accepted for the operator(s): **%@**." = "Le condizioni sono state accettate per gli operatori: **%@**."; /* No comment provided by engineer. */ -"Conditions are already accepted for following operator(s): **%@**." = "Le condizioni sono già state accettate per i seguenti operatori: **%@**."; +"Conditions are already accepted for these operator(s): **%@**." = "Le condizioni sono già state accettate per i seguenti operatori: **%@**."; /* No comment provided by engineer. */ "Conditions of use" = "Condizioni d'uso"; @@ -4015,7 +4015,7 @@ /* alert button */ "Save (and notify contacts)" = "Salva (e avvisa i contatti)"; -/* No comment provided by engineer. */ +/* alert button */ "Save and notify contact" = "Salva e avvisa il contatto"; /* No comment provided by engineer. */ diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index 3aa64f9b55..da0ba42a86 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -2655,7 +2655,7 @@ /* alert button */ "Save (and notify contacts)" = "保存(連絡先に通知)"; -/* No comment provided by engineer. */ +/* alert button */ "Save and notify contact" = "保存して、連絡先にに知らせる"; /* No comment provided by engineer. */ diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index 4ead9a726d..ba28bd1f59 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -1060,7 +1060,7 @@ "Conditions are accepted for the operator(s): **%@**." = "Voorwaarden worden geaccepteerd voor de operator(s): **%@**."; /* No comment provided by engineer. */ -"Conditions are already accepted for following operator(s): **%@**." = "Voorwaarden zijn reeds geaccepteerd voor de volgende operator(s): **%@**."; +"Conditions are already accepted for these operator(s): **%@**." = "Voorwaarden zijn reeds geaccepteerd voor de volgende operator(s): **%@**."; /* No comment provided by engineer. */ "Conditions of use" = "Gebruiksvoorwaarden"; @@ -4015,7 +4015,7 @@ /* alert button */ "Save (and notify contacts)" = "Bewaar (en informeer contacten)"; -/* No comment provided by engineer. */ +/* alert button */ "Save and notify contact" = "Opslaan en Contact melden"; /* No comment provided by engineer. */ @@ -4454,6 +4454,9 @@ /* No comment provided by engineer. */ "SimpleX address or 1-time link?" = "SimpleX adres of eenmalige link?"; +/* No comment provided by engineer. */ +"SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "Simplex-chat en flux hebben een overeenkomst gemaakt om door flux geëxploiteerde servers in de app op te nemen."; + /* No comment provided by engineer. */ "SimpleX Chat security was audited by Trail of Bits." = "De beveiliging van SimpleX Chat is gecontroleerd door Trail of Bits."; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index e0bcedc965..e48e9f2ed8 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -3736,7 +3736,7 @@ /* alert button */ "Save (and notify contacts)" = "Zapisz (i powiadom kontakty)"; -/* No comment provided by engineer. */ +/* alert button */ "Save and notify contact" = "Zapisz i powiadom kontakt"; /* No comment provided by engineer. */ diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 09ee9a2e5a..09c95d4203 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -1066,7 +1066,7 @@ "Conditions are accepted for the operator(s): **%@**." = "Условия приняты для оператора(ов): **%@**."; /* No comment provided by engineer. */ -"Conditions are already accepted for following operator(s): **%@**." = "Условия уже приняты для следующих оператора(ов): **%@**."; +"Conditions are already accepted for these operator(s): **%@**." = "Условия уже приняты для следующих оператора(ов): **%@**."; /* No comment provided by engineer. */ "Conditions of use" = "Условия использования"; @@ -4024,7 +4024,7 @@ /* alert button */ "Save (and notify contacts)" = "Сохранить (и уведомить контакты)"; -/* No comment provided by engineer. */ +/* alert button */ "Save and notify contact" = "Сохранить и уведомить контакт"; /* No comment provided by engineer. */ @@ -4463,6 +4463,9 @@ /* No comment provided by engineer. */ "SimpleX address or 1-time link?" = "Адрес SimpleX или одноразовая ссылка?"; +/* No comment provided by engineer. */ +"SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "SimpleX Chat и Flux заключили соглашение добавить серверы под управлением Flux в приложение."; + /* No comment provided by engineer. */ "SimpleX Chat security was audited by Trail of Bits." = "Безопасность SimpleX Chat была проверена Trail of Bits."; diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index 3fee154931..4fdc49139a 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -2439,7 +2439,7 @@ /* alert button */ "Save (and notify contacts)" = "บันทึก (และแจ้งผู้ติดต่อ)"; -/* No comment provided by engineer. */ +/* alert button */ "Save and notify contact" = "บันทึกและแจ้งผู้ติดต่อ"; /* No comment provided by engineer. */ diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index a78faed4cd..3670e57955 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -3763,7 +3763,7 @@ /* alert button */ "Save (and notify contacts)" = "Kaydet (ve kişilere bildir)"; -/* No comment provided by engineer. */ +/* alert button */ "Save and notify contact" = "Kaydet ve kişilere bildir"; /* No comment provided by engineer. */ diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index ce607753fe..4e2b1680fd 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -1030,7 +1030,7 @@ "Conditions are accepted for the operator(s): **%@**." = "Для оператора(ів) приймаються умови: **%@**."; /* No comment provided by engineer. */ -"Conditions are already accepted for following operator(s): **%@**." = "Умови вже прийняті для наступних операторів: **%@**."; +"Conditions are already accepted for these operator(s): **%@**." = "Умови вже прийняті для наступних операторів: **%@**."; /* No comment provided by engineer. */ "Conditions of use" = "Умови використання"; @@ -3952,7 +3952,7 @@ /* alert button */ "Save (and notify contacts)" = "Зберегти (і повідомити контактам)"; -/* No comment provided by engineer. */ +/* alert button */ "Save and notify contact" = "Зберегти та повідомити контакт"; /* No comment provided by engineer. */ diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index 627bfd0c30..c40833b67b 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -3634,7 +3634,7 @@ /* alert button */ "Save (and notify contacts)" = "保存(并通知联系人)"; -/* No comment provided by engineer. */ +/* alert button */ "Save and notify contact" = "保存并通知联系人"; /* No comment provided by engineer. */ diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 94ce22d356..6d13ff191f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -3669,14 +3669,13 @@ enum class ServerProtocol { @Serializable enum class OperatorTag { @SerialName("simplex") SimpleX, - @SerialName("flux") Flux, - @SerialName("xyz") XYZ, - @SerialName("demo") Demo + @SerialName("flux") Flux } data class ServerOperatorInfo( val description: List, val website: String, + val selfhost: Pair? = null, val logo: ImageResource, val largeLogo: ImageResource, val logoDarkMode: ImageResource, @@ -3696,31 +3695,17 @@ val operatorsInfo: Map = mapOf( ), OperatorTag.Flux to ServerOperatorInfo( description = listOf( - "Flux is the largest decentralized cloud infrastructure, leveraging a global network of user-operated computational nodes.", - "Flux offers a powerful, scalable, and affordable platform designed to support individuals, businesses, and cutting-edge technologies like AI. With high uptime and worldwide distribution, Flux ensures reliable, accessible cloud computing for all." + "Flux is the largest decentralized cloud, based on a global network of user-operated nodes.", + "Flux offers a powerful, scalable, and affordable cutting edge technology platform for all.", + "Flux operates servers in SimpleX network to improve its privacy and decentralization." ), website = "https://runonflux.com", + selfhost = "Self-host SimpleX servers on Flux" to "https://home.runonflux.io/apps/marketplace?q=simplex", logo = MR.images.flux_logo_symbol, largeLogo = MR.images.flux_logo, logoDarkMode = MR.images.flux_logo_symbol, largeLogoDarkMode = MR.images.flux_logo_light ), - OperatorTag.XYZ to ServerOperatorInfo( - description = listOf("XYZ servers"), - website = "XYZ website", - logo = MR.images.shield, - largeLogo = MR.images.logo, - logoDarkMode = MR.images.shield, - largeLogoDarkMode = MR.images.logo_light - ), - OperatorTag.Demo to ServerOperatorInfo( - description = listOf("Demo operator"), - website = "Demo website", - logo = MR.images.decentralized, - largeLogo = MR.images.logo, - logoDarkMode = MR.images.decentralized_light, - largeLogoDarkMode = MR.images.logo_light - ) ) @Serializable @@ -3800,7 +3785,7 @@ data class ServerOperator( companion object { val dummyOperatorInfo = ServerOperatorInfo( description = listOf("Default"), - website = "Default", + website = "https://simplex.chat", logo = MR.images.decentralized, largeLogo = MR.images.logo, logoDarkMode = MR.images.decentralized_light, @@ -3818,30 +3803,6 @@ data class ServerOperator( smpRoles = ServerRoles(storage = true, proxy = true), xftpRoles = ServerRoles(storage = true, proxy = true) ) - - val sampleData2 = ServerOperator( - operatorId = 2, - operatorTag = OperatorTag.XYZ, - tradeName = "XYZ", - legalName = null, - serverDomains = listOf("xyz.com"), - conditionsAcceptance = ConditionsAcceptance.Required(deadline = null), - enabled = false, - smpRoles = ServerRoles(storage = false, proxy = true), - xftpRoles = ServerRoles(storage = false, proxy = true) - ) - - val sampleData3 = ServerOperator( - operatorId = 3, - operatorTag = OperatorTag.Demo, - tradeName = "Demo", - legalName = null, - serverDomains = listOf("demo.com"), - conditionsAcceptance = ConditionsAcceptance.Required(deadline = null), - enabled = false, - smpRoles = ServerRoles(storage = true, proxy = false), - xftpRoles = ServerRoles(storage = true, proxy = false) - ) } val id: Long diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt index dde1fb68ce..dcb7d7e133 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt @@ -14,8 +14,10 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* @@ -72,6 +74,7 @@ fun ModalData.ChooseServerOperators( } Spacer(Modifier.height(DEFAULT_PADDING_HALF)) + SectionTextFooter(annotatedStringResource(MR.strings.onboarding_network_operators_simplex_flux_agreement), textAlign = TextAlign.Center) SectionTextFooter(annotatedStringResource(MR.strings.onboarding_network_operators_configure_via_settings), textAlign = TextAlign.Center) } Spacer(Modifier.weight(1f)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt index 28ca88584d..dcb1bc9de1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt @@ -427,23 +427,27 @@ fun OperatorInfoView(serverOperator: ServerOperator) { SectionDividerSpaced(maxBottomPadding = false) + val uriHandler = LocalUriHandler.current SectionView { SectionItemView { Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { serverOperator.info.description.forEach { d -> Text(d) } + val website = serverOperator.info.website + Text(website, color = MaterialTheme.colors.primary, modifier = Modifier.clickable { uriHandler.openUriCatching(website) }) } } } - SectionDividerSpaced() - - SectionView(generalGetString(MR.strings.operator_website).uppercase()) { - SectionItemView { - val website = serverOperator.info.website - val uriHandler = LocalUriHandler.current - Text(website, color = MaterialTheme.colors.primary, modifier = Modifier.clickable { uriHandler.openUriCatching(website) }) + val selfhost = serverOperator.info.selfhost + if (selfhost != null) { + SectionDividerSpaced(maxBottomPadding = false) + SectionView { + SectionItemView { + val (text, link) = selfhost + Text(text, color = MaterialTheme.colors.primary, modifier = Modifier.clickable { uriHandler.openUriCatching(link) }) + } } } } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 29df338079..82bf5bc8dc 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1077,6 +1077,7 @@ Server operators Network operators + SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. The app protects your privacy by using different operators in each conversation. When more than one operator is enabled, none of them has metadata to learn who communicates with whom. For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml index 584377bc99..a0c5ccf01b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -2320,6 +2320,7 @@ только с одним контактом - поделитесь при встрече или через любой мессенджер.]]> Нет серверов для доставки сообщений. Вы можете настроить серверы позже. + SimpleX Chat и Flux заключили соглашение добавить серверы под управлением Flux в приложение. Приложение улучшает конфиденциальность используя разных операторов в каждом разговоре. Когда больше чем один оператор включен, ни один из них не видит метаданные, чтобы определить, кто соединен с кем. Ошибка сохранения серверов From ea4927c9b012744b38f8352d8dc1825d75c02ab4 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 7 Dec 2024 16:01:57 +0000 Subject: [PATCH 208/567] core: 6.2.0.7 updated version --- package.yaml | 2 +- simplex-chat.cabal | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.yaml b/package.yaml index b9c41ccdc0..b476741597 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.2.0.6 +version: 6.2.0.7 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/simplex-chat.cabal b/simplex-chat.cabal index ace5afd851..29e748c4e8 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.2.0.6 +version: 6.2.0.7 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From 93319d947ddb0f8b27e063097bae7dff3c447a4b Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 7 Dec 2024 16:11:30 +0000 Subject: [PATCH 209/567] website: translations (#5350) * Translated using Weblate (Arabic) Currently translated at 100.0% (258 of 258 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ar/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (258 of 258 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/uk/ * Translated using Weblate (Arabic) Currently translated at 100.0% (258 of 258 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ar/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (258 of 258 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/uk/ * Translated using Weblate (German) Currently translated at 100.0% (258 of 258 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/de/ --------- Co-authored-by: jonnysemon Co-authored-by: Bezruchenko Simon Co-authored-by: mlanp --- website/langs/ar.json | 3 ++- website/langs/de.json | 3 ++- website/langs/uk.json | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/website/langs/ar.json b/website/langs/ar.json index 9274e71850..f257c1c747 100644 --- a/website/langs/ar.json +++ b/website/langs/ar.json @@ -255,5 +255,6 @@ "docs-dropdown-10": "الشفافية", "docs-dropdown-11": "الأسئلة الأكثر شيوعًا", "docs-dropdown-12": "الأمان", - "hero-overlay-card-3-p-3": "قامت Trail of Bits بمراجعة التصميم التعموي لبروتوكولات شبكة SimpleX في يوليو 2024. اقرأ المزيد." + "hero-overlay-card-3-p-3": "قامت Trail of Bits بمراجعة التصميم التعموي لبروتوكولات شبكة SimpleX في يوليو 2024. اقرأ المزيد.", + "docs-dropdown-14": "SimpleX للأعمال التجارية" } diff --git a/website/langs/de.json b/website/langs/de.json index c5d1fceefa..3b1e9d34e8 100644 --- a/website/langs/de.json +++ b/website/langs/de.json @@ -255,5 +255,6 @@ "docs-dropdown-10": "Transparent", "docs-dropdown-11": "FAQ", "docs-dropdown-12": "Sicherheit", - "hero-overlay-card-3-p-3": "Trail of Bits hat das kryptografische Design des Netzwerk-Protokolls von SimpleX im Juli 2024 überprüft. Hier finden Sie weitere Informationen dazu." + "hero-overlay-card-3-p-3": "Trail of Bits hat das kryptografische Design des Netzwerk-Protokolls von SimpleX im Juli 2024 überprüft. Hier finden Sie weitere Informationen dazu.", + "docs-dropdown-14": "SimpleX für geschäftliche Anwendungen" } diff --git a/website/langs/uk.json b/website/langs/uk.json index d055aa68a2..794c65c956 100644 --- a/website/langs/uk.json +++ b/website/langs/uk.json @@ -255,5 +255,6 @@ "docs-dropdown-11": "ПОШИРЕНІ ЗАПИТАННЯ", "docs-dropdown-10": "Прозорість", "docs-dropdown-12": "Безпека", - "hero-overlay-card-3-p-3": "Trail of Bits переглянув криптографічний дизайн мережевих протоколів SimpleX в липні 2024 року. Детальніше." + "hero-overlay-card-3-p-3": "Trail of Bits переглянув криптографічний дизайн мережевих протоколів SimpleX в липні 2024 року. Детальніше.", + "docs-dropdown-14": "SimpleX для бізнесу" } From 7d6c7c58d7d9c1174f22318ba330684ee2bda512 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 7 Dec 2024 16:52:34 +0000 Subject: [PATCH 210/567] ui: translations (#5338) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2211 of 2211 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2210 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2210 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Russian) Currently translated at 99.8% (2206 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (Russian) Currently translated at 100.0% (1931 of 1931 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ru/ * Translated using Weblate (Italian) Currently translated at 100.0% (2210 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (1931 of 1931 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2210 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Arabic) Currently translated at 97.4% (2153 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Ukrainian) Currently translated at 99.8% (2206 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Arabic) Currently translated at 98.3% (2174 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2210 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Italian) Currently translated at 100.0% (2212 of 2212 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (1931 of 1931 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Arabic) Currently translated at 100.0% (2212 of 2212 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2212 of 2212 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2212 of 2212 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1931 of 1931 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2211 of 2211 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2210 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2210 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Russian) Currently translated at 99.8% (2206 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (Russian) Currently translated at 100.0% (1931 of 1931 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ru/ * Translated using Weblate (Italian) Currently translated at 100.0% (2210 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (1931 of 1931 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2210 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Arabic) Currently translated at 97.4% (2153 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Ukrainian) Currently translated at 99.8% (2206 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Arabic) Currently translated at 98.3% (2174 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2210 of 2210 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Italian) Currently translated at 100.0% (2212 of 2212 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (1931 of 1931 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Arabic) Currently translated at 100.0% (2212 of 2212 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2212 of 2212 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2212 of 2212 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1931 of 1931 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Arabic) Currently translated at 100.0% (2212 of 2212 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Dutch) Currently translated at 99.8% (2208 of 2212 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Arabic) Currently translated at 100.0% (2212 of 2212 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Dutch) Currently translated at 100.0% (2212 of 2212 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Arabic) Currently translated at 100.0% (2212 of 2212 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Arabic) Currently translated at 100.0% (2212 of 2212 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (German) Currently translated at 97.5% (2158 of 2212 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2212 of 2212 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1934 of 1934 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (German) Currently translated at 98.7% (2185 of 2213 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2213 of 2213 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2213 of 2213 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1934 of 1934 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (German) Currently translated at 100.0% (2213 of 2213 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (German) Currently translated at 100.0% (1934 of 1934 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2213 of 2213 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2213 of 2213 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1934 of 1934 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Dutch) Currently translated at 100.0% (2213 of 2213 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (1934 of 1934 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/ * Translated using Weblate (Arabic) Currently translated at 100.0% (2213 of 2213 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2213 of 2213 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1934 of 1934 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2214 of 2214 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1935 of 1935 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * process localizations --------- Co-authored-by: 大王叫我来巡山 Co-authored-by: summoner001 Co-authored-by: Ghost of Sparta Co-authored-by: J R Co-authored-by: Random Co-authored-by: jonnysemon Co-authored-by: Bezruchenko Simon Co-authored-by: M1K4 Co-authored-by: mlanp Co-authored-by: No name --- .../de.xcloc/Localized Contents/de.xliff | 31 ++- .../es.xcloc/Localized Contents/es.xliff | 49 +++- .../hu.xcloc/Localized Contents/hu.xliff | 18 +- .../it.xcloc/Localized Contents/it.xliff | 18 +- .../nl.xcloc/Localized Contents/nl.xliff | 3 + .../ru.xcloc/Localized Contents/ru.xliff | 6 +- apps/ios/de.lproj/Localizable.strings | 85 ++++++- apps/ios/es.lproj/Localizable.strings | 103 +++++++- apps/ios/hu.lproj/Localizable.strings | 26 +- apps/ios/it.lproj/Localizable.strings | 18 +- apps/ios/nl.lproj/Localizable.strings | 9 + apps/ios/ru.lproj/Localizable.strings | 6 +- .../commonMain/resources/MR/ar/strings.xml | 235 ++++++++++-------- .../commonMain/resources/MR/de/strings.xml | 120 +++++---- .../commonMain/resources/MR/es/strings.xml | 121 ++++++--- .../commonMain/resources/MR/hu/strings.xml | 23 +- .../commonMain/resources/MR/it/strings.xml | 32 +-- .../commonMain/resources/MR/nl/strings.xml | 8 +- .../commonMain/resources/MR/ru/strings.xml | 7 +- .../commonMain/resources/MR/uk/strings.xml | 101 +++++--- .../resources/MR/zh-rCN/strings.xml | 8 +- 21 files changed, 714 insertions(+), 313 deletions(-) diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 053a1faf73..1fc614becf 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -579,6 +579,7 @@ About operators + Über Betreiber No comment provided by engineer. @@ -641,6 +642,7 @@ Add friends + Freunde aufnehmen No comment provided by engineer. @@ -660,6 +662,7 @@ Add team members + Team-Mitglieder aufnehmen No comment provided by engineer. @@ -674,6 +677,7 @@ Add your team members to the conversations. + Nehmen Sie Team-Mitglieder in Ihre Unterhaltungen auf. No comment provided by engineer. @@ -1248,10 +1252,12 @@ Business address + Geschäftliche Adresse No comment provided by engineer. Business chats + Geschäftliche Chats No comment provided by engineer. @@ -1398,14 +1404,17 @@ Chat + Chat No comment provided by engineer. Chat already exists + Chat besteht bereits No comment provided by engineer. Chat already exists! + Chat besteht bereits! No comment provided by engineer. @@ -1485,10 +1494,12 @@ Chat will be deleted for all members - this cannot be undone! + Der Chat wird für alle Mitglieder gelöscht. Dies kann nicht rückgängig gemacht werden! No comment provided by engineer. Chat will be deleted for you - this cannot be undone! + Der Chat wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden! No comment provided by engineer. @@ -2247,6 +2258,7 @@ Das ist Ihr eigener Einmal-Link! Delete chat + Chat löschen No comment provided by engineer. @@ -2261,6 +2273,7 @@ Das ist Ihr eigener Einmal-Link! Delete chat? + Chat löschen? No comment provided by engineer. @@ -2530,6 +2543,7 @@ Das ist Ihr eigener Einmal-Link! Direct messages between members are prohibited in this chat. + In diesem Chat sind Direktnachrichten zwischen Mitgliedern nicht erlaubt. No comment provided by engineer. @@ -4105,6 +4119,7 @@ Weitere Verbesserungen sind bald verfügbar! Invite to chat + Zum Chat einladen No comment provided by engineer. @@ -4267,10 +4282,12 @@ Das ist Ihr Link für die Gruppe %@! Leave chat + Chat verlassen No comment provided by engineer. Leave chat? + Chat verlassen? No comment provided by engineer. @@ -4405,6 +4422,7 @@ Das ist Ihr Link für die Gruppe %@! Member role will be changed to "%@". All chat members will be notified. + Die Rolle des Mitglieds wird auf "%@" geändert. Alle Chat-Mitglieder werden darüber informiert. No comment provided by engineer. @@ -4419,6 +4437,7 @@ Das ist Ihr Link für die Gruppe %@! Member will be removed from chat - this cannot be undone! + Das Mitglied wird aus dem Chat entfernt. Dies kann nicht rückgängig gemacht werden! No comment provided by engineer. @@ -5036,6 +5055,7 @@ Dies erfordert die Aktivierung eines VPNs. Only chat owners can change preferences. + Nur Chat-Eigentümer können die Präferenzen ändern. No comment provided by engineer. @@ -5170,6 +5190,7 @@ Dies erfordert die Aktivierung eines VPNs. Or import archive file + Oder importieren Sie eine Archiv-Datei No comment provided by engineer. @@ -5435,6 +5456,7 @@ Fehler: %@ Privacy for your customers. + Schutz der Privatsphäre Ihrer Kunden. No comment provided by engineer. @@ -7011,6 +7033,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Tap Create SimpleX address in the menu to create it later. + Tippen Sie im Menü auf SimpleX-Adresse erstellen, um sie später zu erstellen. No comment provided by engineer. @@ -7212,12 +7235,12 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro The servers for new connections of your current chat profile **%@**. - Mögliche Server für neue Verbindungen von Ihrem aktuellen Chat-Profil **%@**. + Nachrichten-Server für neue Verbindungen über Ihr aktuelles Chat-Profil **%@**. No comment provided by engineer. The servers for new files of your current chat profile **%@**. - Die Server Deines aktuellen Chat-Profils für neue Dateien **%@**. + Medien- und Datei-Server für neue Daten über Ihr aktuelles Chat-Profil **%@**. No comment provided by engineer. @@ -8061,6 +8084,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s You are already connected with %@. + Sie sind bereits mit %@ verbunden. No comment provided by engineer. @@ -8339,6 +8363,7 @@ Verbindungsanfrage wiederholen? You will stop receiving messages from this chat. Chat history will be preserved. + Sie werden von diesem Chat keine Nachrichten mehr erhalten. Der Nachrichtenverlauf wird beibehalten. No comment provided by engineer. @@ -8528,6 +8553,7 @@ Verbindungsanfrage wiederholen? accepted invitation + Einladung akzeptiert chat list item title @@ -9214,6 +9240,7 @@ Verbindungsanfrage wiederholen? requested to connect + Zur Verbindung aufgefordert chat list item title diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index ea966ea63b..a96aebebae 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -579,6 +579,7 @@ About operators + Acerca de los operadores No comment provided by engineer. @@ -641,6 +642,7 @@ Add friends + Añadir amigos No comment provided by engineer. @@ -660,6 +662,7 @@ Add team members + Añadir miembros del equipo No comment provided by engineer. @@ -674,6 +677,7 @@ Add your team members to the conversations. + Añade a los miembros de tu equipo a las conversaciones. No comment provided by engineer. @@ -1248,10 +1252,12 @@ Business address + Dirección empresarial No comment provided by engineer. Business chats + Chats empresariales No comment provided by engineer. @@ -1398,14 +1404,17 @@ Chat + Chat No comment provided by engineer. Chat already exists + El chat ya existe No comment provided by engineer. Chat already exists! + ¡El chat ya existe! No comment provided by engineer. @@ -1485,10 +1494,12 @@ Chat will be deleted for all members - this cannot be undone! + El chat será eliminado para todos los miembros. ¡No podrá deshacerse! No comment provided by engineer. Chat will be deleted for you - this cannot be undone! + El chat será eliminado para tí. ¡No podrá deshacerse! No comment provided by engineer. @@ -2247,6 +2258,7 @@ This is your own one-time link! Delete chat + Eliminar chat No comment provided by engineer. @@ -2261,6 +2273,7 @@ This is your own one-time link! Delete chat? + ¿Eliminar chat? No comment provided by engineer. @@ -2295,7 +2308,7 @@ This is your own one-time link! Delete files and media? - Eliminar archivos y multimedia? + ¿Eliminar archivos y multimedia? No comment provided by engineer. @@ -2530,6 +2543,7 @@ This is your own one-time link! Direct messages between members are prohibited in this chat. + Mensajes directos no permitidos entre miembros de este chat. No comment provided by engineer. @@ -2740,7 +2754,7 @@ This is your own one-time link! Enable Flux - Habilitar Flux + Habilita Flux No comment provided by engineer. @@ -4105,6 +4119,7 @@ More improvements are coming soon! Invite to chat + Invitar al chat No comment provided by engineer. @@ -4267,10 +4282,12 @@ This is your link for group %@! Leave chat + Salir del chat No comment provided by engineer. Leave chat? + ¿Salir del chat? No comment provided by engineer. @@ -4405,6 +4422,7 @@ This is your link for group %@! Member role will be changed to "%@". All chat members will be notified. + El rol del miembro cambiará a "%@" y todos serán notificados. No comment provided by engineer. @@ -4419,6 +4437,7 @@ This is your link for group %@! Member will be removed from chat - this cannot be undone! + El miembro será eliminado del chat. ¡No podrá deshacerse! No comment provided by engineer. @@ -5036,6 +5055,7 @@ Requiere activación de la VPN. Only chat owners can change preferences. + Sólo los propietarios del chat pueden cambiar las preferencias. No comment provided by engineer. @@ -5170,6 +5190,7 @@ Requiere activación de la VPN. Or import archive file + O importa desde un archivo No comment provided by engineer. @@ -5410,7 +5431,7 @@ Error: %@ Preset server address - Dirección del servidor predefinida + Dirección predefinida del servidor No comment provided by engineer. @@ -5435,6 +5456,7 @@ Error: %@ Privacy for your customers. + Privacidad para tus clientes. No comment provided by engineer. @@ -5631,7 +5653,7 @@ Actívalo en ajustes de *Servidores y Redes*. Read more - Conoce más + Saber más No comment provided by engineer. @@ -6589,7 +6611,7 @@ Actívalo en ajustes de *Servidores y Redes*. Share SimpleX address on social media. - Compartir dirección SimpleX en redes sociales. + Comparte tu dirección SimpleX en redes sociales. No comment provided by engineer. @@ -6729,12 +6751,12 @@ Actívalo en ajustes de *Servidores y Redes*. SimpleX address and 1-time links are safe to share via any messenger. - Compartir enlaces de un uso y direcciones SimpleX es seguro a través de cualquier medio. + Compartir los enlaces de un uso y las direcciones SimpleX es seguro a través de cualquier medio. No comment provided by engineer. SimpleX address or 1-time link? - Dirección SimpleX o enlace de un uso? + ¿Dirección SimpleX o enlace de un uso? No comment provided by engineer. @@ -7011,6 +7033,7 @@ Actívalo en ajustes de *Servidores y Redes*. Tap Create SimpleX address in the menu to create it later. + Pulsa Crear dirección SimpleX en el menú para crearla más tarde. No comment provided by engineer. @@ -7197,7 +7220,7 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. The second preset operator in the app! - El segundo operador predefinido! + ¡Segundo operador predefinido! No comment provided by engineer. @@ -7212,7 +7235,7 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. The servers for new connections of your current chat profile **%@**. - Lista de servidores para las conexiones nuevas de tu perfil actual **%@**. + Lista de servidores para las conexiones nuevas del perfil **%@**. No comment provided by engineer. @@ -8056,11 +8079,12 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión You are already connected to %@. - Ya estás conectado a %@. + Ya estás conectado con %@. No comment provided by engineer. You are already connected with %@. + Ya estás conectado con %@. No comment provided by engineer. @@ -8339,6 +8363,7 @@ Repeat connection request? You will stop receiving messages from this chat. Chat history will be preserved. + Dejarás de recibir mensajes de este chat. El historial del chat se conserva. No comment provided by engineer. @@ -8528,6 +8553,7 @@ Repeat connection request? accepted invitation + invitación aceptada chat list item title @@ -8912,7 +8938,7 @@ Repeat connection request? for better metadata privacy. - para mayor privacidad de los metadatos. + para mejorar la privacidad de los metadatos. No comment provided by engineer. @@ -9214,6 +9240,7 @@ Repeat connection request? requested to connect + solicitado para conectar chat list item title diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 598bee5485..c682a02d8c 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -574,11 +574,12 @@ About SimpleX Chat - A SimpleX Chatről + SimpleX Chat névjegye No comment provided by engineer. About operators + Az üzemeltetőkről No comment provided by engineer. @@ -1352,7 +1353,7 @@ Change chat profiles - Felhasználói profilok megváltoztatása + Csevegési profilok megváltoztatása authentication reason @@ -3525,7 +3526,7 @@ Ez az Ön egyszer használható meghívó-hivatkozása! For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. - Ha például az ismerőse a SimpleX Chat kiszolgálón keresztül fogadja az üzeneteket, az Ön alkalmazása a Flux egyik kiszolgálóját használja a kézbesítéshez. + Például, ha az Ön ismerőse egy SimpleX Chat-kiszolgálón keresztül fogadja az üzeneteket, az Ön alkalmazása egy Flux-kiszolgálón keresztül fogja azokat kézbesíteni. No comment provided by engineer. @@ -5570,7 +5571,7 @@ Hiba: %@ Protect IP address - IP-cím védelem + IP-cím védelme No comment provided by engineer. @@ -6715,6 +6716,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben. SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + A SimpleX Chat és a Flux megállapodást kötött arról, hogy a Flux által üzemeltetett kiszolgálókat beépítik az alkalmazásba. No comment provided by engineer. @@ -7208,7 +7210,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. The same conditions will apply to operator **%@**. - Ugyanezek a feltételek vonatkoznak a következő üzemeltetőre is: **%@**. + Ugyanezek a feltételek lesznek elfogadva a következő üzemeltetőre is: **%@**. No comment provided by engineer. @@ -8154,7 +8156,7 @@ Csatlakozáskérés megismétlése? You can configure servers via settings. - A kiszolgálókat a beállításokon keresztül konfigurálhatja. + A kiszolgálókat a „Hálózat és kiszolgálók” menüben konfigurálhatja. No comment provided by engineer. @@ -8204,7 +8206,7 @@ Csatlakozáskérés megismétlése? You can set lock screen notification preview via settings. - A beállításokon keresztül beállíthatja a lezárási képernyő értesítési előnézetét. + A lezárási képernyő értesítési előnézetét az „Értesítések” menüben állíthatja be. No comment provided by engineer. @@ -8551,6 +8553,7 @@ Kapcsolatkérés megismétlése? accepted invitation + elfogadott meghívó chat list item title @@ -9237,6 +9240,7 @@ Kapcsolatkérés megismétlése? requested to connect + kérelmezve a kapcsolódáshoz chat list item title diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 67633b7ae8..a3aa28580c 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -1091,12 +1091,12 @@ Auto-accept contact requests - Auto-accetta richieste di contatto + Auto-accetta le richieste di contatto No comment provided by engineer. Auto-accept images - Auto-accetta immagini + Auto-accetta le immagini No comment provided by engineer. @@ -1216,7 +1216,7 @@ Blur media - Sfocatura file multimediali + Sfocatura dei file multimediali No comment provided by engineer. @@ -4501,7 +4501,7 @@ Questo è il tuo link per il gruppo %@! Message draft - Bozza dei messaggi + Bozza del messaggio No comment provided by engineer. @@ -5988,12 +5988,12 @@ Attivalo nelle impostazioni *Rete e server*. Review conditions - Esamina le condizioni + Leggi le condizioni No comment provided by engineer. Review later - Esamina più tardi + Leggi più tardi No comment provided by engineer. @@ -6289,7 +6289,7 @@ Attivalo nelle impostazioni *Rete e server*. Send link previews - Invia anteprime dei link + Invia le anteprime dei link No comment provided by engineer. @@ -6610,7 +6610,7 @@ Attivalo nelle impostazioni *Rete e server*. Share SimpleX address on social media. - Condividi indirizzo SimpleX sui social media. + Condividi l'indirizzo SimpleX sui social media. No comment provided by engineer. @@ -7031,7 +7031,7 @@ Attivalo nelle impostazioni *Rete e server*. Tap Create SimpleX address in the menu to create it later. - Tocca "Crea indirizzo SimpleX" nel menu per crearlo più tardi. + Tocca Crea indirizzo SimpleX nel menu per crearlo più tardi. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index c30370fc5a..73a9d05b73 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -579,6 +579,7 @@ About operators + Over operatoren No comment provided by engineer. @@ -8552,6 +8553,7 @@ Verbindingsverzoek herhalen? accepted invitation + geaccepteerde uitnodiging chat list item title @@ -9238,6 +9240,7 @@ Verbindingsverzoek herhalen? requested to connect + gevraagd om verbinding te maken chat list item title diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 943ea67ef4..814b878a03 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -384,7 +384,7 @@ **Scan / Paste link**: to connect via a link you received. - **Сканировать / Вставить ссылку**: чтобы соединится через полученную ссылку. + **Сканировать / Вставить ссылку**: чтобы соединиться через полученную ссылку. No comment provided by engineer. @@ -5582,7 +5582,7 @@ Error: %@ Protect your IP address from the messaging relays chosen by your contacts. Enable in *Network & servers* settings. Защитите ваш IP адрес от серверов сообщений, выбранных Вашими контактами. -Включите в настройках *Сеть и серверы*. +Включите в настройках *Сети и серверов*. No comment provided by engineer. @@ -8150,7 +8150,7 @@ Repeat join request? You can configure operators in Network & servers settings. - Вы можете настроить операторов в настройках Сеть и серверы. + Вы можете настроить операторов в настройках Сети и серверов. No comment provided by engineer. diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index a510b30477..7d69adc1d5 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -343,6 +343,9 @@ /* No comment provided by engineer. */ "Abort changing address?" = "Wechsel der Empfängeradresse beenden?"; +/* No comment provided by engineer. */ +"About operators" = "Über Betreiber"; + /* No comment provided by engineer. */ "About SimpleX Chat" = "Über SimpleX Chat"; @@ -376,6 +379,9 @@ /* No comment provided by engineer. */ "Accepted conditions" = "Akzeptierte Nutzungsbedingungen"; +/* chat list item title */ +"accepted invitation" = "Einladung akzeptiert"; + /* No comment provided by engineer. */ "Acknowledged" = "Bestätigt"; @@ -388,6 +394,9 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Fügen Sie die Adresse Ihrem Profil hinzu, damit Ihre Kontakte sie mit anderen Personen teilen können. Es wird eine Profilaktualisierung an Ihre Kontakte gesendet."; +/* No comment provided by engineer. */ +"Add friends" = "Freunde aufnehmen"; + /* No comment provided by engineer. */ "Add profile" = "Profil hinzufügen"; @@ -397,12 +406,18 @@ /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "Fügen Sie Server durch Scannen der QR Codes hinzu."; +/* No comment provided by engineer. */ +"Add team members" = "Team-Mitglieder aufnehmen"; + /* No comment provided by engineer. */ "Add to another device" = "Einem anderen Gerät hinzufügen"; /* No comment provided by engineer. */ "Add welcome message" = "Begrüßungsmeldung hinzufügen"; +/* No comment provided by engineer. */ +"Add your team members to the conversations." = "Nehmen Sie Team-Mitglieder in Ihre Unterhaltungen auf."; + /* No comment provided by engineer. */ "Added media & file servers" = "Medien- und Dateiserver hinzugefügt"; @@ -793,6 +808,12 @@ /* 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. */ +"Business address" = "Geschäftliche Adresse"; + +/* No comment provided by engineer. */ +"Business chats" = "Geschäftliche Chats"; + /* 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)."; @@ -909,6 +930,15 @@ /* chat item text */ "changing address…" = "Wechsel der Empfängeradresse wurde gestartet…"; +/* No comment provided by engineer. */ +"Chat" = "Chat"; + +/* No comment provided by engineer. */ +"Chat already exists" = "Chat besteht bereits"; + +/* No comment provided by engineer. */ +"Chat already exists!" = "Chat besteht bereits!"; + /* No comment provided by engineer. */ "Chat colors" = "Chat-Farben"; @@ -954,6 +984,12 @@ /* No comment provided by engineer. */ "Chat theme" = "Chat-Design"; +/* No comment provided by engineer. */ +"Chat will be deleted for all members - this cannot be undone!" = "Der Chat wird für alle Mitglieder gelöscht. Dies kann nicht rückgängig gemacht werden!"; + +/* No comment provided by engineer. */ +"Chat will be deleted for you - this cannot be undone!" = "Der Chat wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden!"; + /* No comment provided by engineer. */ "Chats" = "Chats"; @@ -1475,12 +1511,18 @@ /* No comment provided by engineer. */ "Delete and notify contact" = "Kontakt löschen und benachrichtigen"; +/* No comment provided by engineer. */ +"Delete chat" = "Chat löschen"; + /* No comment provided by engineer. */ "Delete chat profile" = "Chat-Profil löschen"; /* No comment provided by engineer. */ "Delete chat profile?" = "Chat-Profil löschen?"; +/* No comment provided by engineer. */ +"Delete chat?" = "Chat löschen?"; + /* No comment provided by engineer. */ "Delete connection" = "Verbindung löschen"; @@ -1655,6 +1697,9 @@ /* chat feature */ "Direct messages" = "Direkte Nachrichten"; +/* No comment provided by engineer. */ +"Direct messages between members are prohibited in this chat." = "In diesem Chat sind Direktnachrichten zwischen Mitgliedern nicht erlaubt."; + /* No comment provided by engineer. */ "Direct messages between members are prohibited." = "In dieser Gruppe sind Direktnachrichten zwischen Mitgliedern nicht erlaubt."; @@ -2694,6 +2739,9 @@ /* No comment provided by engineer. */ "Invite members" = "Mitglieder einladen"; +/* No comment provided by engineer. */ +"Invite to chat" = "Zum Chat einladen"; + /* No comment provided by engineer. */ "Invite to group" = "In Gruppe einladen"; @@ -2808,6 +2856,12 @@ /* swipe action */ "Leave" = "Verlassen"; +/* No comment provided by engineer. */ +"Leave chat" = "Chat verlassen"; + +/* No comment provided by engineer. */ +"Leave chat?" = "Chat verlassen?"; + /* No comment provided by engineer. */ "Leave group" = "Gruppe verlassen"; @@ -2904,12 +2958,18 @@ /* item status text */ "Member inactive" = "Mitglied inaktiv"; +/* No comment provided by engineer. */ +"Member role will be changed to \"%@\". All chat members will be notified." = "Die Rolle des Mitglieds wird auf \"%@\" geändert. Alle Chat-Mitglieder werden darüber informiert."; + /* No comment provided by engineer. */ "Member role will be changed to \"%@\". All group members will be notified." = "Die Mitgliederrolle wird auf \"%@\" geändert. Alle Mitglieder der Gruppe werden benachrichtigt."; /* No comment provided by engineer. */ "Member role will be changed to \"%@\". The member will receive a new invitation." = "Die Mitgliederrolle wird auf \"%@\" geändert. Das Mitglied wird eine neue Einladung erhalten."; +/* No comment provided by engineer. */ +"Member will be removed from chat - this cannot be undone!" = "Das Mitglied wird aus dem Chat entfernt. Dies kann nicht rückgängig gemacht werden!"; + /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Das Mitglied wird aus der Gruppe entfernt. Dies kann nicht rückgängig gemacht werden!"; @@ -3329,6 +3389,9 @@ /* No comment provided by engineer. */ "Onion hosts will not be used." = "Onion-Hosts werden nicht verwendet."; +/* No comment provided by engineer. */ +"Only chat owners can change preferences." = "Nur Chat-Eigentümer können die Präferenzen ändern."; + /* No comment provided by engineer. */ "Only client devices store user profiles, contacts, groups, and messages." = "Nur die Endgeräte speichern die Benutzerprofile, Kontakte, Gruppen und Nachrichten, welche über eine **2-Schichten Ende-zu-Ende-Verschlüsselung** gesendet werden."; @@ -3407,6 +3470,9 @@ /* alert title */ "Operator server" = "Betreiber-Server"; +/* No comment provided by engineer. */ +"Or import archive file" = "Oder importieren Sie eine Archiv-Datei"; + /* No comment provided by engineer. */ "Or paste archive link" = "Oder fügen Sie den Archiv-Link ein"; @@ -3575,6 +3641,9 @@ /* No comment provided by engineer. */ "Privacy & security" = "Datenschutz & Sicherheit"; +/* No comment provided by engineer. */ +"Privacy for your customers." = "Schutz der Privatsphäre Ihrer Kunden."; + /* No comment provided by engineer. */ "Privacy redefined" = "Datenschutz neu definiert"; @@ -3867,6 +3936,9 @@ /* chat item action */ "Reply" = "Antwort"; +/* chat list item title */ +"requested to connect" = "Zur Verbindung aufgefordert"; + /* No comment provided by engineer. */ "Required" = "Erforderlich"; @@ -4574,6 +4646,9 @@ /* No comment provided by engineer. */ "Tap button " = "Schaltfläche antippen "; +/* No comment provided by engineer. */ +"Tap Create SimpleX address in the menu to create it later." = "Tippen Sie im Menü auf SimpleX-Adresse erstellen, um sie später zu erstellen."; + /* No comment provided by engineer. */ "Tap to activate profile." = "Zum Aktivieren des Profils tippen."; @@ -4704,10 +4779,10 @@ "The sender will NOT be notified" = "Der Absender wird NICHT benachrichtigt"; /* No comment provided by engineer. */ -"The servers for new connections of your current chat profile **%@**." = "Mögliche Server für neue Verbindungen von Ihrem aktuellen Chat-Profil **%@**."; +"The servers for new connections of your current chat profile **%@**." = "Nachrichten-Server für neue Verbindungen über Ihr aktuelles Chat-Profil **%@**."; /* No comment provided by engineer. */ -"The servers for new files of your current chat profile **%@**." = "Die Server Deines aktuellen Chat-Profils für neue Dateien **%@**."; +"The servers for new files of your current chat profile **%@**." = "Medien- und Datei-Server für neue Daten über Ihr aktuelles Chat-Profil **%@**."; /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Der von Ihnen eingefügte Text ist kein SimpleX-Link."; @@ -5282,6 +5357,9 @@ /* No comment provided by engineer. */ "You are already connected to %@." = "Sie sind bereits mit %@ verbunden."; +/* No comment provided by engineer. */ +"You are already connected with %@." = "Sie sind bereits mit %@ verbunden."; + /* No comment provided by engineer. */ "You are already connecting to %@." = "Sie sind bereits mit %@ verbunden."; @@ -5480,6 +5558,9 @@ /* 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."; +/* No comment provided by engineer. */ +"You will stop receiving messages from this chat. Chat history will be preserved." = "Sie werden von diesem Chat keine Nachrichten mehr erhalten. Der Nachrichtenverlauf wird beibehalten."; + /* No comment provided by engineer. */ "You will stop receiving messages from this group. Chat history will be preserved." = "Sie werden von dieser Gruppe keine Nachrichten mehr erhalten. Der Nachrichtenverlauf wird beibehalten."; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 9c0b815ad4..ce36dec953 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -343,6 +343,9 @@ /* No comment provided by engineer. */ "Abort changing address?" = "¿Cancelar el cambio de servidor?"; +/* No comment provided by engineer. */ +"About operators" = "Acerca de los operadores"; + /* No comment provided by engineer. */ "About SimpleX Chat" = "Sobre SimpleX Chat"; @@ -376,6 +379,9 @@ /* No comment provided by engineer. */ "Accepted conditions" = "Condiciones aceptadas"; +/* chat list item title */ +"accepted invitation" = "invitación aceptada"; + /* No comment provided by engineer. */ "Acknowledged" = "Confirmaciones"; @@ -388,6 +394,9 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Añade la dirección a tu perfil para que tus contactos puedan compartirla con otros. La actualización del perfil se enviará a tus contactos."; +/* No comment provided by engineer. */ +"Add friends" = "Añadir amigos"; + /* No comment provided by engineer. */ "Add profile" = "Añadir perfil"; @@ -397,12 +406,18 @@ /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "Añadir servidores mediante el escaneo de códigos QR."; +/* No comment provided by engineer. */ +"Add team members" = "Añadir miembros del equipo"; + /* No comment provided by engineer. */ "Add to another device" = "Añadir a otro dispositivo"; /* No comment provided by engineer. */ "Add welcome message" = "Añadir mensaje de bienvenida"; +/* No comment provided by engineer. */ +"Add your team members to the conversations." = "Añade a los miembros de tu equipo a las conversaciones."; + /* No comment provided by engineer. */ "Added media & file servers" = "Servidores de archivos y multimedia añadidos"; @@ -793,6 +808,12 @@ /* 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. */ +"Business address" = "Dirección empresarial"; + +/* No comment provided by engineer. */ +"Business chats" = "Chats empresariales"; + /* 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 (predeterminado) o [por conexión](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; @@ -909,6 +930,15 @@ /* chat item text */ "changing address…" = "cambiando de servidor…"; +/* No comment provided by engineer. */ +"Chat" = "Chat"; + +/* No comment provided by engineer. */ +"Chat already exists" = "El chat ya existe"; + +/* No comment provided by engineer. */ +"Chat already exists!" = "¡El chat ya existe!"; + /* No comment provided by engineer. */ "Chat colors" = "Colores del chat"; @@ -954,6 +984,12 @@ /* No comment provided by engineer. */ "Chat theme" = "Tema de chat"; +/* No comment provided by engineer. */ +"Chat will be deleted for all members - this cannot be undone!" = "El chat será eliminado para todos los miembros. ¡No podrá deshacerse!"; + +/* No comment provided by engineer. */ +"Chat will be deleted for you - this cannot be undone!" = "El chat será eliminado para tí. ¡No podrá deshacerse!"; + /* No comment provided by engineer. */ "Chats" = "Chats"; @@ -1475,12 +1511,18 @@ /* No comment provided by engineer. */ "Delete and notify contact" = "Eliminar y notificar contacto"; +/* No comment provided by engineer. */ +"Delete chat" = "Eliminar chat"; + /* No comment provided by engineer. */ "Delete chat profile" = "Eliminar perfil"; /* No comment provided by engineer. */ "Delete chat profile?" = "¿Eliminar perfil?"; +/* No comment provided by engineer. */ +"Delete chat?" = "¿Eliminar chat?"; + /* No comment provided by engineer. */ "Delete connection" = "Eliminar conexión"; @@ -1500,7 +1542,7 @@ "Delete file" = "Eliminar archivo"; /* No comment provided by engineer. */ -"Delete files and media?" = "Eliminar archivos y multimedia?"; +"Delete files and media?" = "¿Eliminar archivos y multimedia?"; /* No comment provided by engineer. */ "Delete files for all chat profiles" = "Eliminar archivos de todos los perfiles"; @@ -1655,6 +1697,9 @@ /* chat feature */ "Direct messages" = "Mensajes directos"; +/* No comment provided by engineer. */ +"Direct messages between members are prohibited in this chat." = "Mensajes directos no permitidos entre miembros de este chat."; + /* No comment provided by engineer. */ "Direct messages between members are prohibited." = "Los mensajes directos entre miembros del grupo no están permitidos."; @@ -1798,7 +1843,7 @@ "Enable camera access" = "Permitir acceso a la cámara"; /* No comment provided by engineer. */ -"Enable Flux" = "Habilitar Flux"; +"Enable Flux" = "Habilita Flux"; /* No comment provided by engineer. */ "Enable for all" = "Activar para todos"; @@ -2296,7 +2341,7 @@ "Fix not supported by group member" = "Corrección no compatible con miembro del grupo"; /* No comment provided by engineer. */ -"for better metadata privacy." = "para mayor privacidad de los metadatos."; +"for better metadata privacy." = "para mejorar la privacidad de los metadatos."; /* servers error */ "For chat profile %@:" = "Para el perfil de chat %@:"; @@ -2694,6 +2739,9 @@ /* No comment provided by engineer. */ "Invite members" = "Invitar miembros"; +/* No comment provided by engineer. */ +"Invite to chat" = "Invitar al chat"; + /* No comment provided by engineer. */ "Invite to group" = "Invitar al grupo"; @@ -2808,6 +2856,12 @@ /* swipe action */ "Leave" = "Salir"; +/* No comment provided by engineer. */ +"Leave chat" = "Salir del chat"; + +/* No comment provided by engineer. */ +"Leave chat?" = "¿Salir del chat?"; + /* No comment provided by engineer. */ "Leave group" = "Salir del grupo"; @@ -2904,12 +2958,18 @@ /* item status text */ "Member inactive" = "Miembro inactivo"; +/* No comment provided by engineer. */ +"Member role will be changed to \"%@\". All chat members will be notified." = "El rol del miembro cambiará a \"%@\" y todos serán notificados."; + /* No comment provided by engineer. */ "Member role will be changed to \"%@\". All group members will be notified." = "El rol del miembro cambiará a \"%@\" y se notificará al grupo."; /* No comment provided by engineer. */ "Member role will be changed to \"%@\". The member will receive a new invitation." = "El rol del miembro cambiará a \"%@\" y recibirá una invitación nueva."; +/* No comment provided by engineer. */ +"Member will be removed from chat - this cannot be undone!" = "El miembro será eliminado del chat. ¡No podrá deshacerse!"; + /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "El miembro será expulsado del grupo. ¡No podrá deshacerse!"; @@ -3329,6 +3389,9 @@ /* No comment provided by engineer. */ "Onion hosts will not be used." = "No se usarán hosts .onion."; +/* No comment provided by engineer. */ +"Only chat owners can change preferences." = "Sólo los propietarios del chat pueden cambiar las preferencias."; + /* No comment provided by engineer. */ "Only client devices store user profiles, contacts, groups, and messages." = "Sólo los dispositivos cliente almacenan perfiles de usuario, contactos, grupos y mensajes enviados con **cifrado de extremo a extremo de 2 capas**."; @@ -3407,6 +3470,9 @@ /* alert title */ "Operator server" = "Servidor del operador"; +/* No comment provided by engineer. */ +"Or import archive file" = "O importa desde un archivo"; + /* No comment provided by engineer. */ "Or paste archive link" = "O pegar enlace del archivo"; @@ -3561,7 +3627,7 @@ "Preserve the last message draft, with attachments." = "Conserva el último borrador del mensaje con los datos adjuntos."; /* No comment provided by engineer. */ -"Preset server address" = "Dirección del servidor predefinida"; +"Preset server address" = "Dirección predefinida del servidor"; /* No comment provided by engineer. */ "Preset servers" = "Servidores predefinidos"; @@ -3575,6 +3641,9 @@ /* No comment provided by engineer. */ "Privacy & security" = "Seguridad y Privacidad"; +/* No comment provided by engineer. */ +"Privacy for your customers." = "Privacidad para tus clientes."; + /* No comment provided by engineer. */ "Privacy redefined" = "Privacidad redefinida"; @@ -3693,7 +3762,7 @@ "Read" = "Leer"; /* No comment provided by engineer. */ -"Read more" = "Conoce más"; +"Read more" = "Saber más"; /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Conoce más en la [Guía del Usuario](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; @@ -3867,6 +3936,9 @@ /* chat item action */ "Reply" = "Responder"; +/* chat list item title */ +"requested to connect" = "solicitado para conectar"; + /* No comment provided by engineer. */ "Required" = "Obligatorio"; @@ -4338,7 +4410,7 @@ "Share profile" = "Comparte perfil"; /* No comment provided by engineer. */ -"Share SimpleX address on social media." = "Compartir dirección SimpleX en redes sociales."; +"Share SimpleX address on social media." = "Comparte tu dirección SimpleX en redes sociales."; /* No comment provided by engineer. */ "Share this 1-time invite link" = "Comparte este enlace de un solo uso"; @@ -4386,10 +4458,10 @@ "SimpleX Address" = "Dirección SimpleX"; /* No comment provided by engineer. */ -"SimpleX address and 1-time links are safe to share via any messenger." = "Compartir enlaces de un uso y direcciones SimpleX es seguro a través de cualquier medio."; +"SimpleX address and 1-time links are safe to share via any messenger." = "Compartir los enlaces de un uso y las direcciones SimpleX es seguro a través de cualquier medio."; /* No comment provided by engineer. */ -"SimpleX address or 1-time link?" = "Dirección SimpleX o enlace de un uso?"; +"SimpleX address or 1-time link?" = "¿Dirección SimpleX o enlace de un uso?"; /* No comment provided by engineer. */ "SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "Simplex Chat y Flux han acordado incluir servidores operados por Flux en la aplicación"; @@ -4574,6 +4646,9 @@ /* No comment provided by engineer. */ "Tap button " = "Pulsa el botón "; +/* No comment provided by engineer. */ +"Tap Create SimpleX address in the menu to create it later." = "Pulsa Crear dirección SimpleX en el menú para crearla más tarde."; + /* No comment provided by engineer. */ "Tap to activate profile." = "Pulsa sobre un perfil para activarlo."; @@ -4695,7 +4770,7 @@ "The same conditions will apply to operator(s): **%@**." = "Las mismas condiciones se aplicarán a el/los operador(es) **%@**."; /* No comment provided by engineer. */ -"The second preset operator in the app!" = "El segundo operador predefinido!"; +"The second preset operator in the app!" = "¡Segundo operador predefinido!"; /* No comment provided by engineer. */ "The second tick we missed! ✅" = "¡El doble check que nos faltaba! ✅"; @@ -4704,7 +4779,7 @@ "The sender will NOT be notified" = "El remitente NO será notificado"; /* No comment provided by engineer. */ -"The servers for new connections of your current chat profile **%@**." = "Lista de servidores para las conexiones nuevas de tu perfil actual **%@**."; +"The servers for new connections of your current chat profile **%@**." = "Lista de servidores para las conexiones nuevas del perfil **%@**."; /* No comment provided by engineer. */ "The servers for new files of your current chat profile **%@**." = "Los servidores para archivos nuevos en tu perfil actual **%@**."; @@ -5280,7 +5355,10 @@ "You already have a chat profile with the same display name. Please choose another name." = "Ya tienes un perfil con este nombre mostrado. Por favor, elige otro nombre."; /* No comment provided by engineer. */ -"You are already connected to %@." = "Ya estás conectado a %@."; +"You are already connected to %@." = "Ya estás conectado con %@."; + +/* No comment provided by engineer. */ +"You are already connected with %@." = "Ya estás conectado con %@."; /* No comment provided by engineer. */ "You are already connecting to %@." = "Ya estás conectando con %@."; @@ -5480,6 +5558,9 @@ /* 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."; +/* No comment provided by engineer. */ +"You will stop receiving messages from this chat. Chat history will be preserved." = "Dejarás de recibir mensajes de este chat. El historial del chat se conserva."; + /* No comment provided by engineer. */ "You will stop receiving messages from this group. Chat history will be preserved." = "Dejarás de recibir mensajes de este grupo. El historial del chat se conservará."; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index 58d28cd8ed..594bd3a123 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -344,7 +344,10 @@ "Abort changing address?" = "Címváltoztatás megszakítása??"; /* No comment provided by engineer. */ -"About SimpleX Chat" = "A SimpleX Chatről"; +"About operators" = "Az üzemeltetőkről"; + +/* No comment provided by engineer. */ +"About SimpleX Chat" = "SimpleX Chat névjegye"; /* No comment provided by engineer. */ "above, then choose:" = "gombra fent, majd válassza ki:"; @@ -376,6 +379,9 @@ /* No comment provided by engineer. */ "Accepted conditions" = "Elfogadott feltételek"; +/* chat list item title */ +"accepted invitation" = "elfogadott meghívó"; + /* No comment provided by engineer. */ "Acknowledged" = "Nyugtázva"; @@ -879,7 +885,7 @@ "Change" = "Változtatás"; /* authentication reason */ -"Change chat profiles" = "Felhasználói profilok megváltoztatása"; +"Change chat profiles" = "Csevegési profilok megváltoztatása"; /* No comment provided by engineer. */ "Change database passphrase?" = "Adatbázis-jelmondat megváltoztatása?"; @@ -2344,7 +2350,7 @@ "For console" = "Konzolhoz"; /* No comment provided by engineer. */ -"For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server." = "Ha például az ismerőse a SimpleX Chat kiszolgálón keresztül fogadja az üzeneteket, az Ön alkalmazása a Flux egyik kiszolgálóját használja a kézbesítéshez."; +"For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server." = "Például, ha az Ön ismerőse egy SimpleX Chat-kiszolgálón keresztül fogadja az üzeneteket, az Ön alkalmazása egy Flux-kiszolgálón keresztül fogja azokat kézbesíteni."; /* No comment provided by engineer. */ "For private routing" = "A privát útválasztáshoz"; @@ -3708,7 +3714,7 @@ "Protect app screen" = "Alkalmazás képernyőjének védelme"; /* No comment provided by engineer. */ -"Protect IP address" = "IP-cím védelem"; +"Protect IP address" = "IP-cím védelme"; /* No comment provided by engineer. */ "Protect your chat profiles with a password!" = "Védje meg a csevegési profiljait egy jelszóval!"; @@ -3930,6 +3936,9 @@ /* chat item action */ "Reply" = "Válasz"; +/* chat list item title */ +"requested to connect" = "kérelmezve a kapcsolódáshoz"; + /* No comment provided by engineer. */ "Required" = "Szükséges"; @@ -4454,6 +4463,9 @@ /* No comment provided by engineer. */ "SimpleX address or 1-time link?" = "SimpleX-cím vagy egyszer használható meghívó-hivatkozás?"; +/* No comment provided by engineer. */ +"SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "A SimpleX Chat és a Flux megállapodást kötött arról, hogy a Flux által üzemeltetett kiszolgálókat beépítik az alkalmazásba."; + /* No comment provided by engineer. */ "SimpleX Chat security was audited by Trail of Bits." = "A SimpleX Chat biztonsága a Trail of Bits által lett auditálva."; @@ -4752,7 +4764,7 @@ "The profile is only shared with your contacts." = "A profilja csak az ismerőseivel kerül megosztásra."; /* No comment provided by engineer. */ -"The same conditions will apply to operator **%@**." = "Ugyanezek a feltételek vonatkoznak a következő üzemeltetőre is: **%@**."; +"The same conditions will apply to operator **%@**." = "Ugyanezek a feltételek lesznek elfogadva a következő üzemeltetőre is: **%@**."; /* No comment provided by engineer. */ "The same conditions will apply to operator(s): **%@**." = "Ugyanezek a feltételek lesznek elfogadva a következő üzemeltető(k)re is: **%@**."; @@ -5397,7 +5409,7 @@ "You can configure operators in Network & servers settings." = "Az üzemeltetőket a „Hálózat és kiszolgálók” beállításaban konfigurálhatja."; /* No comment provided by engineer. */ -"You can configure servers via settings." = "A kiszolgálókat a beállításokon keresztül konfigurálhatja."; +"You can configure servers via settings." = "A kiszolgálókat a „Hálózat és kiszolgálók” menüben konfigurálhatja."; /* No comment provided by engineer. */ "You can create it later" = "Létrehozás később"; @@ -5427,7 +5439,7 @@ "You can set connection name, to remember who the link was shared with." = "Beállíthatja az ismerős nevét, hogy emlékezzen arra, hogy kivel osztotta meg a hivatkozást."; /* No comment provided by engineer. */ -"You can set lock screen notification preview via settings." = "A beállításokon keresztül beállíthatja a lezárási képernyő értesítési előnézetét."; +"You can set lock screen notification preview via settings." = "A lezárási képernyő értesítési előnézetét az „Értesítések” menüben állíthatja be."; /* No comment provided by engineer. */ "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." = "Megoszthat egy hivatkozást vagy QR-kódot - így bárki csatlakozhat a csoporthoz. Ha a csoport később törlésre kerül, akkor nem fogja elveszíteni annak tagjait."; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index 25a672da26..80122f8535 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -689,10 +689,10 @@ "Auto-accept" = "Accetta automaticamente"; /* No comment provided by engineer. */ -"Auto-accept contact requests" = "Auto-accetta richieste di contatto"; +"Auto-accept contact requests" = "Auto-accetta le richieste di contatto"; /* No comment provided by engineer. */ -"Auto-accept images" = "Auto-accetta immagini"; +"Auto-accept images" = "Auto-accetta le immagini"; /* alert title */ "Auto-accept settings" = "Accetta automaticamente le impostazioni"; @@ -779,7 +779,7 @@ "Blur for better privacy." = "Sfoca per una privacy maggiore."; /* No comment provided by engineer. */ -"Blur media" = "Sfocatura file multimediali"; +"Blur media" = "Sfocatura dei file multimediali"; /* No comment provided by engineer. */ "bold" = "grassetto"; @@ -3004,7 +3004,7 @@ "Message delivery warning" = "Avviso di consegna del messaggio"; /* No comment provided by engineer. */ -"Message draft" = "Bozza dei messaggi"; +"Message draft" = "Bozza del messaggio"; /* item status text */ "Message forwarded" = "Messaggio inoltrato"; @@ -3982,10 +3982,10 @@ "Reveal" = "Rivela"; /* No comment provided by engineer. */ -"Review conditions" = "Esamina le condizioni"; +"Review conditions" = "Leggi le condizioni"; /* No comment provided by engineer. */ -"Review later" = "Esamina più tardi"; +"Review later" = "Leggi più tardi"; /* No comment provided by engineer. */ "Revoke" = "Revoca"; @@ -4181,7 +4181,7 @@ "Send errors" = "Errori di invio"; /* No comment provided by engineer. */ -"Send link previews" = "Invia anteprime dei link"; +"Send link previews" = "Invia le anteprime dei link"; /* No comment provided by engineer. */ "Send live message" = "Invia messaggio in diretta"; @@ -4401,7 +4401,7 @@ "Share profile" = "Condividi il profilo"; /* No comment provided by engineer. */ -"Share SimpleX address on social media." = "Condividi indirizzo SimpleX sui social media."; +"Share SimpleX address on social media." = "Condividi l'indirizzo SimpleX sui social media."; /* No comment provided by engineer. */ "Share this 1-time invite link" = "Condividi questo link di invito una tantum"; @@ -4635,7 +4635,7 @@ "Tap button " = "Tocca il pulsante "; /* No comment provided by engineer. */ -"Tap Create SimpleX address in the menu to create it later." = "Tocca \"Crea indirizzo SimpleX\" nel menu per crearlo più tardi."; +"Tap Create SimpleX address in the menu to create it later." = "Tocca Crea indirizzo SimpleX nel menu per crearlo più tardi."; /* No comment provided by engineer. */ "Tap to activate profile." = "Tocca per attivare il profilo."; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index ba28bd1f59..e5c3520898 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -343,6 +343,9 @@ /* No comment provided by engineer. */ "Abort changing address?" = "Adres wijziging afbreken?"; +/* No comment provided by engineer. */ +"About operators" = "Over operatoren"; + /* No comment provided by engineer. */ "About SimpleX Chat" = "Over SimpleX Chat"; @@ -376,6 +379,9 @@ /* No comment provided by engineer. */ "Accepted conditions" = "Geaccepteerde voorwaarden"; +/* chat list item title */ +"accepted invitation" = "geaccepteerde uitnodiging"; + /* No comment provided by engineer. */ "Acknowledged" = "Erkend"; @@ -3930,6 +3936,9 @@ /* chat item action */ "Reply" = "Antwoord"; +/* chat list item title */ +"requested to connect" = "gevraagd om verbinding te maken"; + /* No comment provided by engineer. */ "Required" = "Vereist"; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 09c95d4203..f22981f80a 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -83,7 +83,7 @@ "**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Рекомендовано**: токен устройства и уведомления отправляются на сервер SimpleX Chat, но сервер не получает сами сообщения, их размер или от кого они."; /* No comment provided by engineer. */ -"**Scan / Paste link**: to connect via a link you received." = "**Сканировать / Вставить ссылку**: чтобы соединится через полученную ссылку."; +"**Scan / Paste link**: to connect via a link you received." = "**Сканировать / Вставить ссылку**: чтобы соединиться через полученную ссылку."; /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Внимание**: для работы мгновенных уведомлений пароль должен быть сохранен в Keychain."; @@ -3720,7 +3720,7 @@ "Protect your chat profiles with a password!" = "Защитите Ваши профили чата паролем!"; /* No comment provided by engineer. */ -"Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Защитите ваш IP адрес от серверов сообщений, выбранных Вашими контактами.\nВключите в настройках *Сеть и серверы*."; +"Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Защитите ваш IP адрес от серверов сообщений, выбранных Вашими контактами.\nВключите в настройках *Сети и серверов*."; /* No comment provided by engineer. */ "Protocol timeout" = "Таймаут протокола"; @@ -5406,7 +5406,7 @@ "You can change it in Appearance settings." = "Вы можете изменить это в настройках Интерфейса."; /* No comment provided by engineer. */ -"You can configure operators in Network & servers settings." = "Вы можете настроить операторов в настройках Сеть и серверы."; +"You can configure operators in Network & servers settings." = "Вы можете настроить операторов в настройках Сети и серверов."; /* No comment provided by engineer. */ "You can configure servers via settings." = "Вы можете настроить серверы позже."; diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index c67b6258a2..4c781f0aab 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -12,10 +12,10 @@ عن SimpleX أعلاه، ثم: اقبل - لا يمكن التراجع عن هذا الإجراء - سيتم فقد ملف التعريف وجهات الاتصال والرسائل والملفات الخاصة بك بشكل نهائي. + لا يمكن التراجع عن هذا الإجراء - سيتم فقد ملف تعريفك وجهات اتصالك ورسائلك وملفاتك بشكل نهائي. هذه المجموعة لم تعد موجودة. رمز QR هذا ليس رابطًا! - الجيل القادم من \nالرسائل الخاصة + مستقبل المُراسلة لا يمكن التراجع عن هذا الإجراء - سيتم حذف جميع الملفات والوسائط المستلمة والمرسلة. ستبقى الصور منخفضة الدقة. لا يمكن التراجع عن هذا الإجراء - سيتم حذف الرسائل المرسلة والمستلمة قبل التحديد. قد تأخذ عدة دقائق. ينطبق هذا الإعداد على الرسائل الموجودة في ملف تعريف الدردشة الحالي الخاص بك @@ -30,7 +30,7 @@ أضِف خوادم مُعدة مسبقًا أضِف إلى جهاز آخر سيتم حذف جميع الدردشات والرسائل - لا يمكن التراجع عن هذا! - الوصول إلى الخوادم عبر وكيل SOCKS على المنفذ %d؟ يجب بدء تشغيل الوكيل قبل تمكين هذا الخيار. + الوصول إلى الخوادم عبر وكيل SOCKS على المنفذ %d؟ يجب بدء تشغيل الوكيل قبل تفعيل هذا الخيار. أضِف خادم إعدادات الشبكة المتقدمة سيبقى جميع أعضاء المجموعة على اتصال. @@ -42,7 +42,7 @@ قبول التخفي أضِف رسالة ترحيب أضف الخوادم عن طريق مسح رموز QR. - يمكّن للمشرفين إنشاء روابط للانضمام إلى المجموعات. + يمكن للمشرفين إنشاء روابط للانضمام إلى المجموعات. قبول طلب الاتصال؟ سيتم حذف جميع الرسائل - لا يمكن التراجع عن هذا! سيتم حذف الرسائل فقط من أجلك. مكالمة مقبولة @@ -67,17 +67,17 @@ دائِماً مُتاح يمكن للتطبيق استلام الإشعارات فقط عند تشغيله، ولن يتم بدء تشغيل أي خدمة في الخلفية السماح بالرسائل الصوتية؟ - ستبقى جميع جهات الاتصال الخاصة بك متصلة. + ستبقى جميع جهات اتصالك متصلة. استخدم التتابع دائمًا النسخ الاحتياطي لبيانات التطبيق حُذفت جميع بيانات التطبيق. السماح بحذف الرسائل المرسلة بشكل لا رجعة فيه. (24 ساعة) اسمح لجهات اتصالك بإرسال رسائل صوتية. - حول عنوان SimpleX + عن عنوان SimpleX بناء التطبيق: %s المظهر - أضف عنوانًا إلى ملف التعريف الخاص بك ، حتى تتمكن جهات الاتصال الخاصة بك من مشاركته مع أشخاص آخرين. سيتم إرسال تحديث الملف الشخصي إلى جهات الاتصال الخاصة بك. - ستبقى جميع جهات الاتصال الخاصة بك متصلة. سيتم إرسال تحديث الملف الشخصي إلى جهات الاتصال الخاصة بك. + أضف عنوانًا إلى ملف تعريفك، حتى تتمكن جهات اتصالك من مشاركته مع أشخاص آخرين. سيتم إرسال تحديث ملف التعريف إلى جهات اتصالك. + ستبقى جميع جهات اتصالك متصلة. سيتم إرسال تحديث ملف التعريف إلى جهات اتصالك. رمز التطبيق عنوان اسمح لجهات اتصالك بحذف الرسائل المرسلة بشكل لا رجعة فيه. (24 ساعة) @@ -96,7 +96,7 @@ يمكنك أنت وجهة اتصالك إضافة ردود فعل الرسائل. يمكنك أنت وجهة اتصالك إرسال رسائل تختفي. مكالمتك تحت الإجراء - لا يمكّن استلام الملف + لا يمكن استلام الملف جيد للبطارية. يتحقق التطبيق من الرسائل كل 10 دقائق. قد تفوتك مكالمات أو رسائل عاجلة.]]> عريض مكالمات الصوت (ليست مُعمّاة بين الطرفين) @@ -135,7 +135,7 @@ يتم استبدال رمز مرور التطبيق برمز مرور التدمير الذاتي. مكالمات الصوت والفيديو خطأ في الاتصال - تحسين البطارية نشط ، مما يؤدي إلى إيقاف تشغيل خدمة الخلفية والطلبات الدورية للرسائل الجديدة. يمكنك إعادة تمكينها عبر الإعدادات. + تحسين البطارية نشط، مما يؤدي إلى إيقاف تشغيل خدمة الخلفية والطلبات الدورية للرسائل الجديدة. يمكنك إعادة تفعيلها عبر الإعدادات. لا يمكن تهيئة قاعدة البيانات إرفاق طلب لاستلام الصورة @@ -182,7 +182,7 @@ قاعدة البيانات مُعمّاة غيرت دور %s إلى %s تغيير عنوان الاستلام - خطأ في إنشاء الملف الشخصي! + خطأ في إنشاء ملف التعريف! خطأ في الإتصال انتهت مهلة الاتصال جهة الاتصال موجودة بالفعل @@ -303,7 +303,7 @@ أدخل عبارة المرور الدردشات متصل - سيتم حذف جهة الاتصال وجميع الرسائل - لا يمكن التراجع عن هذا الإجراء! + سيتم حذف جهة الاتصال وجميع الرسائل - لا يمكن التراجع عن هذا! الحد الأقصى لحجم الملف المدعوم حاليًا هو %1$s. تواصل عبر الرابط / رمز QR إنشاء رابط دعوة لمرة واحدة @@ -314,7 +314,7 @@ ملون لدى جهة الاتصال التعمية بين الطريفين إنشاء - إنشاء ملف تعريف + أنشئ ملف تعريفك مكالمة جارية... تفعيل التدمير الذاتي الموافقة على التعمية… @@ -409,11 +409,11 @@ توسيع تحديد الدور انتهت صلاحية دعوة المجموعة المجموعة غير موجودة! - تصدير السمة + صدّر السمة الملفات والوسائط قلب الكاميرا سيتم حذف المجموعة لجميع الأعضاء - لا يمكن التراجع عن هذا! - يمكن لأعضاء المجموعة إرسال رسائل مباشرة. + يمكن للأعضاء إرسال رسائل مباشرة. فشل تحميل الدردشات أهلاً! \nتواصل معي عبر SimpleX Chat: %s @@ -422,8 +422,8 @@ الملف حُدّث ملف تعريف المجموعة أدخل اسم المجموعة: - يمكن لأعضاء المجموعة إرسال رسائل صوتية. - الملفات والوسائط ممنوعة في هذه المجموعة. + يمكن للأعضاء إرسال رسائل صوتية. + الملفات والوسائط ممنوعة. رسالة ترحيب المجموعة مزيد من تقليل استخدام البطارية المجموعة @@ -433,16 +433,16 @@ الواجهة الفرنسية المساعدة حُذِفت المجموعة - يمكن لأعضاء المجموعة إرسال رسائل تختفي. + يمكن للأعضاء إرسال رسائل تختفي. إشراف المجموعة أخيرا، لدينا منهم! 🚀 - تصدير قاعدة البيانات + صدّر قاعدة البيانات لوحدة التحكم الميزات التجريبية تجريبي المجموعة غير نشطة الملفات والوسائط - يمكن لأعضاء المجموعة حذف الرسائل المرسلة بشكل لا رجعة فيه. (24 ساعة) + يمكن للأعضاء حذف الرسائل المُرسلة بشكل لا رجعة فيه. (24 ساعة) الإصلاح غير مدعوم من قبل جهة الاتصال يُخزّن ملف تعريف المجموعة على أجهزة الأعضاء، وليس على الخوادم. روابط المجموعة @@ -450,25 +450,25 @@ الاسم الكامل: لم تعد دعوة المجموعة صالحة، تمت أُزيلت بواسطة المرسل. رابط المجموعة - سيتم استلام الملف عندما تكون جهة اتصالك متصلة بالإنترنت، يرجى الانتظار أو التحقق لاحقًا! + سيتم استلام الملف عندما تكون جهة اتصالك متصلة بالإنترنت، يُرجى الانتظار أو التحقق لاحقًا! الاسم الكامل للمجموعة: رابط كامل ملف سيتم حذف المجموعة لك - لا يمكن التراجع عن هذا! فشل تحميل الدردشة - يمكن لأعضاء المجموعة إضافة ردود فعل الرسالة. + يمكن للأعضاء إضافة ردود الفعل على الرسائل. المفضل مخفي حُفظ الملف سيتم حذف الملف من الخوادم. - سيتم استلام الملف عند اكتمال تحميل جهة الاتصال الخاصة بك. + سيتم استلام الملف عندما يكتمل جهة اتصالك من رفعِها. المساعدة الملف: %s إصلاح إصلاح الاتصال إصلاح الاتصال؟ الإصلاح غير مدعوم من قبل أعضاء المجموعة - يمكن لأعضاء المجموعة إرسال الملفات والوسائط. + يمكن للأعضاء إرسال الملفات والوسائط. تفضيلات المجموعة سريع ولا تنتظر حتى يصبح المرسل متصلاً بالإنترنت! إخفاء @@ -483,7 +483,7 @@ استيراد قاعدة بيانات ساعات السجل - سيتم استلام الصورة عند اكتمال تحميل جهة اتصالك. + سيتم استلام الصورة عندما يكتمل جهة اتصالك من رفعِها. اعرض رمز QR في مكالمة الفيديو، أو شارك الرابط.]]> ثبّت SimpleX Chat لطرفية إذا قمت بالتأكيد، فستتمكن خوادم المراسلة من رؤية عنوان IP الخاص بك ومزود الخدمة الخاص بك - أي الخوادم التي تتصل بها. @@ -511,7 +511,7 @@ التخفي عبر رابط لمرة واحدة أرسلت صورة صورة - سيتم استلام الصورة عندما تكون جهة اتصالك متصلة بالإنترنت، يرجى الانتظار أو التحقق لاحقًا! + سيتم استلام الصورة عندما تكون جهة اتصالك متصلة بالإنترنت، يُرجى الانتظار أو التحقق لاحقًا! حُفظت الصورة في المعرض صورة إذا لم تتمكن من الالتقاء شخصيًا، اعرض رمز QR في مكالمة الفيديو، أو شارك الرابط. @@ -526,7 +526,7 @@ فوري المضيف إخفاء - يُرجى السماح لSimpleX للتشغيل في الخلفية في مربع الحوار التالي. وإلا، سيتم تعطيل الإشعارات.]]> + السماح بذلك في مربع الحوار التالي لتلقي الإشعارات على الفور.]]> ردًا على إشعارات فورية خوادم ICE (واحد لكل سطر) @@ -534,7 +534,7 @@ إخفاء ملف التعريف كيفية استخدام ماركداون إذا أدخلت رمز مرور التدمير الذاتي أثناء فتح التطبيق: - يمكن تغييره لاحقًا عبر الإعدادات. + كيف يؤثر على البطارية انضمام فاتح مدعو للتواصل @@ -554,13 +554,13 @@ دعوة الأصدقاء خطأ في Keychain دعوة للمجموعة - يٌمنع حذف الرسائل بشكل لا رجعة فيه في هذه المجموعة. + يٌمنع حذف الرسائل بشكل لا رجعة فيه. تنسيق الرسالة غير صالح البيانات غير صالحة - بيانات الملف الشخصي المحلية فقط + بيانات ملف التعريف المحلية فقط يٌمنع حذف الرسائل بشكل لا رجعة فيه في هذه الدردشة. دعوة الأعضاء - مغادرة المجموعة + غادِر المجموعة الاسم المحلي: غادر يسمح بوجود العديد من الاتصالات المجهولة دون مشاركة أي بيانات بينهم في ملف تعريف دردشة واحد. @@ -603,7 +603,7 @@ نزّل الملف تعطيل قفل SimpleX تحرير - اسم الملف الشخصي: + اسم ملف التعريف: البريد الإلكتروني أدخل أسمك: كرر الرسالة @@ -613,7 +613,7 @@ حُرر الرجوع إلى إصدار سابق وفتح الدردشة رسائل مباشرة - الرسائل المختفية ممنوعة في هذه المجموعة. + الرسائل المختفية ممنوعة. تحرير ملف تعريف المجموعة لا تُظهر مرة أخرى الجهاز @@ -651,7 +651,7 @@ لا تنشئ عنوانًا خطأ في تحديث تضبيط الشبكة خطأ في استلام الملف - خطأ في تبديل الملف الشخصي! + خطأ في تبديل ملف التعريف! حافظ على اتصالاتك تأكد من أن عناوين خادم XFTP بالتنسيق الصحيح، وأن تكون مفصولة بأسطر وليست مكررة. عُلّم محذوف @@ -683,7 +683,7 @@ خطأ في بدء الدردشة خطأ في تصدير قاعدة بيانات الدردشة ستتم إزالة العضو من المجموعة - لا يمكن التراجع عن هذا! - اجعل الملف الشخصي خاصًا! + اجعل ملف التعريف خاصًا! تصفية الدردشات غير المقروءة والمفضلة. البحث عن الدردشات بشكل أسرع تفعيل @@ -734,7 +734,7 @@ \n- و اكثر! حالة الشبكة كتم - ردود الفعل الرسائل ممنوعة في هذه المجموعة. + ردود الفعل الرسائل ممنوعة. المزيد إعدادات متقدّمة مكالمة فائتة @@ -757,9 +757,7 @@ سيتم استخدام مضيفات البصل عند توفرها. لن يتم استخدام مضيفات البصل. لم تٌحدد جهات اتصال - يمكّن للمشرف الآن: -\n- حذف رسائل الأعضاء. -\n- تعطيل الأعضاء (دور "المراقب") + يمكن للمشرف الآن:\n- حذف رسائل الأعضاء.\n- تعطيل الأعضاء (دور المراقب) خدمة الإشعار غير مفعّل` مفعل @@ -793,7 +791,7 @@ تم تعيين كلمة المرور! المالك فقط جهة اتصالك يمكنها إرسال رسائل تختفي. - جهة اتصالك فقط يمكنها إضافة تفاعلات على الرسالة + جهة اتصالك فقط يمكنها إضافة ردود الفعل على الرسالة فقط مالكي المجموعة يمكنهم تغيير تفضيلات المجموعة. جهة اتصالك فقط يمكنها حذف الرسائل بشكل لا رجعة فيه (يمكنك تعليم الرسالة للحذف). (24 ساعة) أنت فقط يمكنك إرسال رسائل صوتية. @@ -806,7 +804,7 @@ كلمة المرور غير موجودة في مخزن المفاتيح، يرجى إدخالها يدوياً. قد يحدث هذا إذا قمت باستعادة ملفات التطبيق باستخدام أداة استرجاع بيانات. إذا لم يكن الأمر كذلك، تواصل مع المبرمجين رجاء افتح الدردشة فتح الرابط في المتصفح قد يقلل خصوصية وحماية اتصالك. الروابط غير الموثوقة من SimpleX ستكون باللون الأحمر - أنت فقط يمكنك إضافة تفاعل على الرسالة. + أنت فقط يمكنك إضافة ردود الفعل على الرسالة. أنت فقط يمكنك حذف الرسائل بشكل لا رجعة فيه (يمكن للمستلم تعليمها للحذف). (24 ساعة) أنت فقط يمكنك إرسال رسائل تختفي أنت فقط يمكنك إجراء المكالمات. @@ -819,7 +817,7 @@ ندّ لِندّ أنت تقرر من يمكنه الاتصال. مكالمة قيد الانتظار - تعمية ثنائية الطبقات من بين الطريفين.]]> + تقوم أجهزة العميل فقط بتخزين ملفات تعريف المستخدمين وجهات الاتصال والمجموعات والرسائل. صفّر الألوان حفظ عنوان الخادم المُعد مسبقًا @@ -829,7 +827,7 @@ الاستلام عبر يُرجى التحقق من استخدامك للرابط الصحيح أو اطلب من جهة اتصالك أن ترسل لك رابطًا آخر. الإشعارات الدورية مُعطَّلة - صورة الملف الشخصي + صورة ملف التعريف الإشعارات خاصة يرجى تخزين عبارة المرور بشكل آمن، فلن تتمكن من الوصول إلى الدردشة إذا فقدتها. يُرجى تحديث التطبيق والتواصل مع المطورين. @@ -862,7 +860,7 @@ الرجاء إدخال كلمة المرور السابقة بعد استعادة نسخة احتياطية لقاعدة البيانات. لا يمكن التراجع عن هذا الإجراء. استعادة النسخة الاحتياطية لقاعدة البيانات؟ حفظ - اتصالات الملف الشخصي والخادم + اتصالات ملف التعريف والخادم منع ردود فعل الرسالة. منع إرسال الرسائل الصوتية. منع ردود فعل الرسائل. @@ -884,7 +882,7 @@ يرى المستلمون التحديثات أثناء كتابتها. استلمت، ممنوع حفظ - سيتم إرسال تحديث الملف الشخصي إلى جهات الاتصال الخاصة بك. + سيتم إرسال تحديث ملف التعريف إلى جهات اتصالك. حفظ وإشعار جهات الاتصال حفظ وتحديث ملف تعريف المجموعة عدد البينج @@ -900,7 +898,7 @@ إزالة صفّر إلى الإعدادات الافتراضية بينج الفاصل الزمني - كلمة مرور الملف الشخصي + كلمة مرور ملف التعريف منع إرسال الرسائل التي تختفي. مهلة البروتوكول مهلة البروتوكول لكل كيلوبايت @@ -924,7 +922,7 @@ سحب وصول الملف؟ رٌفض الإذن! يرجى مطالبة جهة اتصالك بتفعيل إرسال الرسائل الصوتية. - العنصر النائب لصورة الملف الشخصي + العنصر النائب لصورة ملف التعريف رمز QR صفّر المنفذ %d @@ -1016,7 +1014,7 @@ خوادم SMP مشاركة الوسائط… رسائل SimpleX Chat - لم يتم تمكين قفل SimpleX! + قفل SimpleX غير مفعّل! إيقاف الدردشة التوقف عن استلام الملف؟ مشاركة الملف… @@ -1056,7 +1054,7 @@ عرض خيارات المطور simplexmq: v%s (%2s) يتطلب الخادم إذنًا لإنشاء قوائم انتظار، تحقق من كلمة المرور - يتطلب الخادم إذنًا للتحميل، تحقق من كلمة المرور + يتطلب الخادم إذنًا للرفع، تحقق من كلمة المرور عرض جهة الاتصال فقط مكالمات SimpleX Chat خدمة SimpleX Chat @@ -1084,14 +1082,13 @@ سيتم إلغاء الاتصال الذي قبلته! لن تتمكن جهة الاتصال التي شاركت هذا الرابط معها من الاتصال! هذا النص متاح في الإعدادات - لحماية الخصوصية، بدلاً من معرفات المستخدم التي تستخدمها جميع الأنظمة الأساسية الأخرى, يحتوي SimpleX على معرفات لقوائم انتظار الرسائل، منفصلة لكل جهة من جهات اتصالك. - لحماية معلوماتك، قم بتشغيل قفل SimpleX -\nسيُطلب منك إكمال المصادقة قبل تمكين هذه الميزة. + لحماية خصوصيتك، يستخدم SimpleX معرّفات منفصلة لكل جهة اتصال لديك. + لحماية معلوماتك، فعّل قفل SimpleX \nسيُطلب منك إكمال المصادقة قبل تفعيل هذه الميزة. عزل النقل بفضل المستخدمين - المساهمة عبر Weblate! دعم البلوتوث وتحسينات أخرى. بفضل المستخدمين - المساهمة عبر Weblate! - خدمة SimpleX تعمل في الخلفية – يستخدم نسبة قليلة من البطارية يوميًا.]]> + يتم تشغيل SimpleX في الخلفية بدلاً من استخدام إشعارات push.]]> انقر لبدء محادثة جديدة (للمشاركة مع جهة اتصالك) للتواصل عبر الرابط @@ -1103,17 +1100,17 @@ العنوان الرئيسي سيتم وضع علامة على الرسالة على أنها تحت الإشراف لجميع الأعضاء. انقر للانضمام - للكشف عن ملف التعريف المخفي الخاص بك، أدخل كلمة مرور كاملة في حقل البحث في صفحة ملفات تعريف الدردشة الخاصة بك. + للكشف عن ملف تعريفك المخفي، أدخل كلمة مرور كاملة في حقل البحث في صفحة ملفات تعريف الدردشة الخاصة بك. انقر للانضمام إلى وضع التخفي النظام السمات بفضل المستخدمين - المساهمة عبر Weblate! قاعدة البيانات لا تعمل بشكل صحيح. انقر لمعرفة المزيد ألوان الواجهة - انقر لتنشيط الملف الشخصي. + انقر لتنشيط ملف التعريف. عزل النقل هذه السلسلة ليست رابط اتصال! - هذه الإعدادات لملف التعريف الحالي الخاص بك + هذه الإعدادات لملف تعريفك الحالي يمكن تجاوزها في إعدادات الاتصال و المجموعة. انتهت مهلة اتصال TCP لحماية المنطقة الزمنية، تستخدم ملفات الصور / الصوت التوقيت العالمي المنسق (UTC). @@ -1140,7 +1137,7 @@ محاولة الاتصال بالخادم المستخدم لاستلام الرسائل من جهة الاتصال هذه (خطأ: %1$s). تشغيل خوادم WebRTC ICE - أنت تستخدم ملفًا شخصيًا متخفيًا لهذه المجموعة - لمنع مشاركة ملفك الشخصي الرئيسي الذي يدعو جهات الاتصال غير مسموح به + أنت تستخدم ملف تعريف متخفي لهذه المجموعة - لمنع مشاركة ملفك التعريفي الرئيسي الذي يدعو جهات الاتصال غير مسموح به غيّرتَ دور %s إلى %s نعم أنت متصل بالخادم المستخدم لاستلام الرسائل من جهة الاتصال هذه. @@ -1155,23 +1152,23 @@ عبر المُرحل لقد انضممت إلى هذه المجموعة لقد رفضت دعوة المجموعة - عندما تشارك ملفًا شخصيًا متخفيًا مع شخص ما، فسيتم استخدام هذا الملف الشخصي للمجموعات التي يدعوك إليها. + عندما تشارك ملف تعريف متخفي مع شخص ما، فسيتم استخدام هذا الملف التعريفي للمجموعات التي يدعوك إليها. لديك بالفعل ملف تعريف دردشة بنفس اسم العرض. الرجاء اختيار اسم آخر. أنت متصل بالفعل بـ%1$s. في انتظار الفيديو - سيتم استلام الفيديو عند اكتمال تحميل جهة اتصالك. + سيتم استلام الفيديو عند اكتمال رفع جهة اتصالك. تحقق من رمز الأمان رسائل صوتية عندما يطلب الأشخاص الاتصال، يمكنك قبوله أو رفضه. - سوف تكون متصلاً بالمجموعة عندما يكون جهاز مضيف المجموعة متصلاً بالإنترنت، يرجى الانتظار أو التحقق لاحقًا! - سوف تكون متصلاً عندما يتم قبول طلب الاتصال الخاص بك، يرجى الانتظار أو التحقق لاحقًا! + سوف تكون متصلاً بالمجموعة عندما يكون جهاز مضيف المجموعة متصلاً بالإنترنت، يُرجى الانتظار أو التحقق لاحقًا! + سوف تكون متصلاً عندما يتم قبول طلب اتصالك، يُرجى الانتظار أو التحقق لاحقًا! تستخدم خوادم SimpleX Chat. استخدم وكيل SOCKS استخدم مضيفي onion. استخدام وكيل SOCKS؟ عندما تكون متاحة ستبقى جهات اتصالك متصلة. - لا نقوم بتخزين أي من جهات الاتصال أو الرسائل الخاصة بك (بمجرد تسليمها) على الخوادم. + لا نقوم بتخزين أي من جهات اتصالك أو رسائلك (بمجرد تسليمها) على الخوادم. يمكنك استخدام تخفيض السعر لتنسيق الرسائل: استخدم الدردشة أنت @@ -1206,10 +1203,10 @@ عبر رابط لمرة واحدة مكالمة الفيديو ليست مُعمّاة بين الطريفين غيّرتَ العنوان - سوف تكون متصلاً عندما يكون جهاز جهة الاتصال الخاصة بك متصلاً بالإنترنت، يرجى الانتظار أو التحقق لاحقًا! + سوف تكون متصلاً عندما يكون جهاز جهة اتصالك متصلاً بالإنترنت، يُرجى الانتظار أو التحقق لاحقًا! غادرت يجب عليك استخدام أحدث إصدار من قاعدة بيانات الدردشة الخاصة بك على جهاز واحد فقط، وإلا فقد تتوقف عن تلقي الرسائل من بعض جهات الاتصال. - سيتم استلام الفيديو عندما تكون جهة اتصالك متصلة بالإنترنت، يرجى الانتظار أو التحقق لاحقًا! + سيتم استلام الفيديو عندما تكون جهة اتصالك متصلة بالإنترنت، يُرجى الانتظار أو التحقق لاحقًا! يمكنك مشاركة هذا العنوان مع جهات اتصالك للسماح لهم بالاتصال بـ%s. أُزيلت %1$s تحديث @@ -1238,11 +1235,11 @@ سيتم حذف قاعدة بيانات الدردشة الحالية واستبدالها بالقاعدة المستوردة. \nلا يمكن التراجع عن هذا الإجراء - سيتم فقد ملف التعريف وجهات الاتصال والرسائل والملفات الخاصة بك بشكل نهائي. تحديث عبارة مرور قاعدة البيانات - سوف تتوقف عن تلقي الرسائل من هذه المجموعة. سيتم الاحتفاظ سجل الدردشة. + سوف تتوقف عن تلقي الرسائل من هذه المجموعة. سيتم الاحتفاظ بسجل الدردشة. أسابيع يمكنك إخفاء أو كتم ملف تعريف المستخدم - اضغط مطولاً للقائمة. ما هو الجديد - ملفك الشخصي الحالي + ملف تعريفك الحالي عبر %1$s غير مقروءة مرحبًا! @@ -1252,7 +1249,7 @@ فيديو يمكنك مشاركة عنوانك كرابط أو رمز QR - يمكن لأي شخص الاتصال بك. يمكنك إنشاؤه لاحقًا - أنت تحاول دعوة جهة اتصال قمت بمشاركة ملف تعريف متخفي معها إلى المجموعة التي تستخدم فيها ملفك الشخصي الرئيسي + أنت تحاول دعوة جهة اتصال شاركت ملف تعريف متخفي معها إلى المجموعة التي تستخدم فيها ملف تعريفك الرئيسي ألغِ الكتم ألغِ الكتم لقد قبلت الاتصال @@ -1274,7 +1271,7 @@ \n- الوقت المخصص لتختفي. \n- تحرير التاريخ. يمكنك تفعيلة لاحقًا عبر الإعدادات - يمكنك تمكينها لاحقًا عبر إعدادات الخصوصية والأمان للتطبيق. + يمكنك تفعيلها لاحقًا عبر إعدادات الخصوصية والأمان للتطبيق. عبر رابط المجموعة لقد شاركت رابط لمرة واحدة متخفي عبر المتصفح @@ -1287,11 +1284,11 @@ سيتم إرسال ملف تعريف الدردشة الخاص بك \nإلى جهة اتصالك إلغاء الإخفاء - ملفك الشخصي العشوائي - ستستمر في استلام المكالمات والإشعارات من الملفات الشخصية المكتومة عندما تكون نشطة. + ملفك التعريفي العشوائي + ستستمر في استلام المكالمات والإشعارات من الملفات التعريفية المكتومة عندما تكون نشطة. انت تسمح بها مكالمة فيديو - الرسائل الصوتية ممنوعة في هذه الدردشة. + الرسائل الصوتية ممنوعة. فتح القفل رفع الملف لا يمكن التحقق منك؛ الرجاء المحاولة مرة اخرى. @@ -1299,7 +1296,7 @@ رسالة صوتية… أنت مدعو إلى المجموعة لا يمكنك إرسال رسائل! - تحتاج إلى السماح لجهة الاتصال الخاصة بك بإرسال رسائل صوتية لتتمكن من إرسالها. + تحتاج إلى السماح لجهة اتصالك بإرسال رسائل صوتية لتتمكن من إرسالها. أرسلت جهة اتصالك ملفًا أكبر من الحجم الأقصى المعتمد حاليًا (%1$s). الاتصال بمطوري SimpleX Chat لطرح أي أسئلة وتلقي التحديثات.]]> خادمك @@ -1334,7 +1331,7 @@ لا يمكن تشغيل SimpleX في الخلفية. ستستلم الإشعارات فقط عندما يكون التطبيق قيد التشغيل. سيتم مشاركة ملف تعريف عشوائي جديد. ألصق الرابط المُستلَم للتواصل مع جهة اتصالك… - ستتم مشاركة ملفك الشخصي %1$s. + ستتم مشاركة ملفك التعريفي %1$s. قد يغلق التطبيق بعد دقيقة واحدة في الخلفية. سماح لا مكالمات في الخلفية @@ -1388,9 +1385,9 @@ محظور حظر أعضاء المجموعة جهة الاتصال حُذفت - أنشِئ مجموعة باستخدام ملف تعريف عشوائي. - أنشِئ مجموعة - أنشِئ ملف تعريف + أنشئ مجموعة باستخدام ملف تعريف عشوائي. + أنشئ مجموعة + أنشئ ملف تعريف سطح المكتب متصل اتصل تلقائيًا عنوان سطح المكتب @@ -1485,9 +1482,7 @@ تحقق من الرمز مع سطح المكتب مسح رمز QR من سطح المكتب إلغاء الحظر - - إشعار اختياريًا جهات الاتصال المحذوفة. -\n- أسماء الملفات الشخصية بمسافات. -\n- و اكثر! + - إشعار اختياريًا جهات الاتصال المحذوفة. \n- أسماء الملفات التعريفية بمسافات. \n- و اكثر! مسار الملف غير صالح لقد طلبت بالفعل الاتصال عبر هذا العنوان! إظهار وحدة التحكم في نافذة جديدة @@ -1522,7 +1517,7 @@ يمكنك عرض رابط الدعوة مرة أخرى في تفاصيل الاتصال. أبقِ الدعوة غير المستخدمة؟ شارك رابط الدعوة هذا لمرة واحدة - أنشِئ مجموعة: لإنشاء مجموعة جديدة.]]> + أنشئ مجموعة: لإنشاء مجموعة جديدة.]]> التاريخ المرئي رمز مرور التطبيق دردشة جديدة @@ -1550,7 +1545,7 @@ %s غير نشط]]> أظهر مكالمات API البطيئة غير معروف - حدّثت الملف الشخصي + حدّثت ملف التعريف %s مفقود]]> %s لديه إصدار غير مدعوم. يُرجى التأكد من استخدام نفس الإصدار على كلا الجهازين]]> %s في حالة سيئة]]> @@ -1575,9 +1570,9 @@ خيارات المطور تغيّر العضو %1$s إلى %2$s أزلت عنوان الاتصال - أزلت الصورة الشخصية + أزلت صورة ملف التعريف عيّن عنوان جهة اتصال جديد - عيّن صورة شخصية جديدة + عيّن صورة تعريفية جديدة حالة غير معروفة تغيّر جهة الاتصال %1$s إلى %2$s يستغرق تنفيذ الوظيفة وقتًا طويلاً جدًا: %1$d ثانية: %2$s @@ -1624,7 +1619,7 @@ يمكن للمشرفين حظر عضو للجميع. ترحيل بيانات التطبيق جارِ أرشفة قاعدة البيانات - سيتم تعمية جميع جهات الاتصال والمحادثات والملفات الخاصة بك بشكل آمن وتحميلها في أجزاء إلى مُرحلات XFTP التي ضبطت. + سيتم تعمية جميع جهات الاتصال والمحادثات والملفات الخاصة بك بشكل آمن ورفعها في أجزاء إلى مُرحلات XFTP التي ضُبطت. طبّق يُرجى ملاحظة: استخدام نفس قاعدة البيانات على جهازين سيؤدي إلى كسر فك تعمية الرسائل من اتصالاتك، كحماية أمنية.]]> تحذير: سيتم حذف الأرشيف.]]> @@ -1658,7 +1653,7 @@ ألصق رابط الأرشيف يمكنك إعطاء محاولة أخرى. حدث خطأ أثناء تنزيل الأرشيف - الملف المُصدر غير موجود + الملف المُصدّر غير موجود تحقق من عبارة المرور تأكد من أنك تتذكر عبارة مرور قاعدة البيانات لترحيلها. التحقق من عبارة مرور قاعدة البيانات @@ -1718,8 +1713,8 @@ السماح بإرسال روابط SimpleX. منع إرسال روابط SimpleX كل الأعضاء - يمكن لأعضاء المجموعة إرسال روابط SimpleX. - روابط SimpleX محظورة في هذه المجموعة. + يمكن للأعضاء إرسال روابط SimpleX. + روابط SimpleX محظورة. المشرفين مفعّل لـ المالكون @@ -1746,8 +1741,8 @@ عند اتصال بمكالمات الصوت والفيديو. إدارة الشبكة اتصال شبكة أكثر موثوقية. - صور الملف الشخصي - شكل الصور الشخصية + صور ملف التعريف + شكل الصور التعريفية واجهة المستخدم الليتوانية مربع أو دائرة أو أي شيء بينهما. عنوان الخادم غير متوافق مع إعدادات الشبكة. @@ -1814,7 +1809,7 @@ صباح الخير! صورة خلفية الشاشة الوضع الفاتح - السمة الملف الشخصي + سمة ملف التعريف فاتح طبّق لِ ملء @@ -1833,8 +1828,7 @@ \nآخر رسالة تم استلامها: %2$s تسليم التصحيح معلومات قائمة انتظار الرسائل - احمِ عنوان IP الخاص بك من مُرحلات المُراسلة التي اختارتها جهات الاتصال الخاصة بك. -\nفعّل في إعدادات *الشبكة والخوادم*. + احمِ عنوان IP الخاص بك من مُرحلات المُراسلة التي اختارتها جهات اتصالك. \nفعّل في إعدادات *الشبكة والخوادم*. سمات دردشة جديدة حدث خطأ أثناء تهيئة WebView. حدّث نظامك إلى الإصدار الجديد. يُرجى التواصل بالمطورين. \nError: %s @@ -1999,7 +1993,7 @@ بإمكانك إرسال رسائل إلى %1$s من جهات الاتصال المؤرشفة. ألصق الرابط جهات اتصالك - شريط أدوات الدردشة القابل للوصول + شريط أدوات التطبيق القابلة للوصول حُذفت جهة الاتصال. السماح بالمكالمات؟ أرسل رسالة لتفعيل المكالمات. @@ -2040,7 +2034,7 @@ يحمي عنوان IP الخاص بك واتصالاتك. اتصال TCP حفظ وإعادة الاتصال - أنشِئ + أنشئ تجربة دردشة جديدة 🎉 تمويه من أجل خصوصية أفضل. كبّر حجم الخط @@ -2075,9 +2069,9 @@ لا يزال يتم تنزيل %1$d ملفًا. لا تستخدم بيانات الاعتماد مع الوكيل. خطأ في تحويل الرسائل - خطأ في تبديل الملف الشخصي + خطأ في تبديل ملف التعريف حدد ملف تعريف الدردشة - لقد تم نقل اتصالك إلى %s ولكن حدث خطأ غير متوقع أثناء إعادة توجيهك إلى الملف الشخصي. + لقد تم نقل اتصالك إلى %s ولكن حدث خطأ غير متوقع أثناء إعادة توجيهك إلى ملف التعريف. تحويل %1$s رسالة؟ لم يحوّل %1$s من الرسائل جارِ تحويل %1$s رسالة @@ -2117,7 +2111,7 @@ لملف تعريف الدردشة %s: لا يوجد وسائط أو خوادم ملفات. لا يوجد خوادم لإرسال الملفات. - لقد وصل الاتصال إلى الحد الأقصى من الرسائل غير المُسلمة، قد يكون جهة الاتصال الخاصة بك غير متصلة بالإنترنت. + لقد وصل الاتصال إلى الحد الأقصى من الرسائل غير المُسلمة، قد يكون جهة اتصالك غير متصلة بالإنترنت. الرسائل غير المُسلَّمة شارك رابطًا لمرة واحدة مع صديق أمان الاتصال @@ -2182,11 +2176,11 @@ عنوان أو رابط لمرة واحدة؟ مع جهة اتصال واحدة فقط - المشاركة شخصيًا أو عبر أي مُراسل.]]> سيتم قبول الشروط للمُشغلين المفعّلين بعد 30 يومًا. - اختر المُشغلين + مُشغلي الخادم لا يمكن تحميل نص الشروط الحالية، يمكنك مراجعة الشروط عبر هذا الرابط: خطأ في قبول الشروط خطأ في حفظ الخوادم - على سبيل المثال، إذا تلقيت رسائل عبر خادم SimpleX Chat، فسيستخدم التطبيق أحد خوادم Flux للتوجيه الخاص. + على سبيل المثال، إذا تلقى أحد جهات اتصالك رسائل عبر خادم SimpleX Chat، فسوف يقوم تطبيقك بتسليمها عبر خادم Flux. لا يوجد خوادم لتوجيه الرسائل الخاصة. لا يوجد خوادم رسائل. لا يوجد خوادم لاستقبال الملفات. @@ -2202,8 +2196,49 @@ انقر فوق أنشئ عنوان SimpleX في القائمة لإنشائه لاحقًا. حُذفت هذه الرسالة أو لم يتم استلامها بعد. استخدم للرسائل - عندما تفعّل أكثر من مُشغل شبكة واحد، سيستخدم التطبيق خوادم مُشغلين مختلفين لكل مُحادثة. + يحمي التطبيق خصوصيتك من خلال استخدام مُشغلين مختلفين في كل محادثة. %s.]]> %s.]]> %s.]]> + عنوان العمل التجاري + يتم تشغيل التطبيق دائمًا في الخلفية + دردشات العمل التجاري + أضف أعضاء الفريق + أضف أصدقاء + أضف أعضاء فريقك إلى المحادثات. + يُحظر إرسال الرسائل المباشرة بين الأعضاء في هذه الدردشة. + أجهزة Xiaomi: يُرجى تفعيل التشغيل التلقائي (Autostart) في إعدادات النظام لكي تعمل الإشعارات.]]> + مُعمَّاة بين الطرفين، مع أمان ما بعد الكم في الرسائل المباشرة.]]> + تحقق من الرسائل كل 10 دقائق + يُمنع إرسال الرسائل المباشرة بين الأعضاء. + الدردشة + كيف يساعد على الخصوصية + سيتم حذف الدردشة لجميع الأعضاء - لا يمكن التراجع عن هذا! + سيتم حذف الدردشة لديك - لا يمكن التراجع عن هذا! + احذف الدردشة + الدردشة موجودة بالفعل! + حذف الدردشة؟ + %1$s.]]> + أو استورد ملف الأرشيف + لا توجد خدمة خلفية + الإشعارات والبطارية + يمكن فقط لأصحاب الدردشة تغيير التفضيلات. + الخصوصية لعملائك. + الجوالات عن بُعد + ادعُ للدردشة + مغادرة المجموعة؟ + سيتم إزالة العضو من الدردشة - لا يمكن التراجع عن هذا! + غادِر الدردشة + الرسالة كبيرة جدًا! + يُرجى تقليل حجم الرسالة وإرسالها مرة أخرى. + شريط أداة الدردشة القابلة للوصول + الدعوة قُبلت + طلبت الاتصال + يُرجى تقليل حجم الرسالة أو إزالة الوسائط ثم إرسالها مرة أخرى. + يمكنك نسخ الرسالة وتقليل حجمها لإرسالها. + عندما يتم تفعيل أكثر من مُشغل واحد، لن يكون لدى أي منهم بيانات تعريفية لمعرفة من يتواصل مع من. + سيتم تغيير الدور إلى %s. وسيتم إشعار الجميع في الدردشة. + سيتم إرسال ملف تعريفك للدردشة إلى أعضاء الدردشة + سوف تتوقف عن تلقي الرسائل من هذه الدردشة. سيتم حفظ سجل الدردشة. + عن المُشغلين \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index 5ab956ae85..7c3ecd5ece 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -62,7 +62,7 @@ Der Absender hat die Dateiübertragung abgebrochen. Fehler beim Empfangen der Datei Fehler beim Erstellen der Adresse - Kontakt ist bereits vorhanden + Kontakt besteht bereits Sie sind bereits mit %1$s verbunden. Ungültiger Verbindungslink Überprüfen Sie bitte, ob Sie den richtigen Link genutzt haben, oder bitten Sie Ihren Kontakt darum, Ihnen nochmal einen Link zuzusenden. @@ -88,9 +88,9 @@ Sofortige Benachrichtigungen Sofortige Benachrichtigungen! Sofortige Benachrichtigungen sind deaktiviert! - SimpleX-Hintergrunddienst genutzt werden – dieser benötigt ein paar Prozent Akkuleistung am Tag.]]> + läuft SimpleX im Hintergrund ab, anstatt Push-Benachrichtigungen zu nutzen.]]> Diese können über die Einstellungen deaktiviert werden – solange die App läuft, werden Benachrichtigungen weiterhin angezeigt.]]> - Erlauben Sie SimpleX im Hintergrund abzulaufen. Ansonsten werden die Benachrichtigungen deaktiviert.]]> + Erlauben Sie es im nächsten Dialog.]]> Die Akkuoptimierung ist aktiv, der Hintergrunddienst und die periodische Nachfrage nach neuen Nachrichten ist abgeschaltet. Sie können diese Funktion in den Einstellungen wieder aktivieren. Periodische Benachrichtigungen Periodische Benachrichtigungen sind deaktiviert! @@ -462,7 +462,7 @@ Verbunden Beendet - Die nächste Generation \ndes privaten Messagings + Die Zukunft des Messagings Datenschutz neu definiert Keine Benutzerkennungen. Immun gegen Spam @@ -474,8 +474,8 @@ Wie es funktioniert Wie SimpleX funktioniert - Zum Schutz Ihrer Privatsphäre verwendet SimpleX anstelle von Benutzerkennungen, die von allen anderen Plattformen verwendet werden, Kennungen für Nachrichtenwarteschlangen, die für jeden Ihrer Kontakte individuell sind. - zweischichtige Ende-zu-Ende-Verschlüsselung gesendet werden.]]> + SimpleX nutzt individuelle Kennungen für jeden Ihrer Kontakte, um Ihre Privatsphäre zu schützen. + Nur die Endgeräte speichern Benutzerprofile, Kontakte, Gruppen und Nachrichten. GitHub-Repository mehr dazu.]]> Fügen Sie den erhaltenen Link ein @@ -683,7 +683,7 @@ Die Einladung ist abgelaufen! Die Gruppeneinladung ist nicht mehr gültig, da sie vom Absender entfernt wurde. Die Gruppe wurde nicht gefunden! - Diese Gruppe existiert nicht mehr. + Diese Gruppe ist nicht mehr vorhanden. Kontakte können nicht eingeladen werden! Sie verwenden ein Inkognito-Profil für diese Gruppe. Um zu verhindern, dass Sie Ihr Hauptprofil teilen, ist in diesem Fall das Einladen von Kontakten nicht erlaubt. @@ -781,8 +781,8 @@ Ändern Wechseln Die Mitgliederrolle ändern? - Die Mitgliederrolle wird auf "%s" geändert. Alle Mitglieder der Gruppe werden benachrichtigt. - Die Mitgliederrolle wird auf "%s" geändert. Das Mitglied wird eine neue Einladung erhalten. + Die Rolle wird auf %s geändert. Alle Mitglieder der Gruppe werden benachrichtigt. + Die Rolle wird auf %s geändert. Das Mitglied wird eine neue Einladung erhalten. Fehler beim Entfernen des Mitglieds Fehler beim Ändern der Rolle Gruppe @@ -872,12 +872,12 @@ Unwiederbringliches Löschen von Nachrichten nicht erlauben. Das Senden von Sprachnachrichten erlauben. Das Senden von Sprachnachrichten nicht erlauben. - Gruppenmitglieder können Direktnachrichten versenden. + Mitglieder können Direktnachrichten versenden. In dieser Gruppe sind Direktnachrichten zwischen Mitgliedern nicht erlaubt. - Gruppenmitglieder können gesendete Nachrichten unwiederbringlich löschen (bis zu 24 Stunden). - In dieser Gruppe ist das unwiederbringliche Löschen von Nachrichten nicht erlaubt. - Gruppenmitglieder können Sprachnachrichten versenden. - In dieser Gruppe sind Sprachnachrichten nicht erlaubt. + Mitglieder können gesendete Nachrichten unwiederbringlich löschen (bis zu 24 Stunden). + Das unwiederbringliche Löschen von Nachrichten ist nicht erlaubt. + Mitglieder können Sprachnachrichten versenden. + Sprachnachrichten sind nicht erlaubt. LIVE Schauen Sie sich den Sicherheitscode an Sofort @@ -887,7 +887,7 @@ %s wurde erfolgreich überprüft Verifikation zurücknehmen Solange die App abläuft - Kann später über die Einstellungen geändert werden. + Auswirkung auf den Akku Löschen nach %d Stunde %d Stunden @@ -922,8 +922,8 @@ Gruppenlink erstellen Erlauben Sie Ihren Kontakten das Senden von verschwindenden Nachrichten. Das Senden von verschwindenden Nachrichten nicht erlauben. - In dieser Gruppe sind verschwindende Nachrichten nicht erlaubt. - Gruppenmitglieder können verschwindende Nachrichten senden. + Verschwindende Nachrichten sind nicht erlaubt. + Mitglieder können verschwindende Nachrichten senden. Fügen Sie Server durch Scannen der QR-Codes hinzu. Verschwindende Nachrichten Übernehmen @@ -978,7 +978,7 @@ Chat-Profil löschen für PING-Zähler Transport-Isolations-Modus aktualisieren\? - Mögliche Server für neue Verbindungen über Ihr aktuelles Chat-Profil + Nachrichten-Server für neue Verbindungen über Ihr aktuelles Chat-Profil Dateien & Medien Transport-Isolation Chat-Profil löschen\? @@ -1248,7 +1248,7 @@ Wenn Sie diesen Zugangscode während des Öffnens der App eingeben, werden alle App-Daten unwiederbringlich gelöscht! Selbstzerstörungs-Zugangscode Zugangscode einstellen - In dieser Gruppe sind Reaktionen auf Nachrichten nicht erlaubt. + Reaktionen auf Nachrichten sind nicht erlaubt. Fehler beim Laden von Details Empfangene Nachricht Information @@ -1279,7 +1279,7 @@ Nur Ihr Kontakt kann Reaktionen auf Nachrichten geben. Reaktionen auf Nachrichten erlauben. Reaktionen auf Nachrichten nicht erlauben. - Gruppenmitglieder können eine Reaktion auf Nachrichten geben. + Mitglieder können eine Reaktion auf Nachrichten geben. Mehr erfahren Endlich haben wir sie! 🚀 Reaktionen auf Nachrichten @@ -1294,9 +1294,7 @@ Farbdesigns anpassen und weitergeben. Tage Stunden - - Bis zu 5 Minuten lange Sprachnachrichten -\n- Zeitdauer für verschwindende Nachrichten anpassen -\n- Nachrichtenverlauf bearbeiten + - Bis zu 5 Minuten lange Sprachnachrichten\n- Zeitdauer für verschwindende Nachrichten anpassen\n- Nachrichtenverlauf bearbeiten benutzerdefiniert Monate Auswählen @@ -1325,9 +1323,9 @@ Wechsel der Empfängeradresse beenden? Dateien und Medien sind nicht erlaubt! Nur Gruppenbesitzer können Dateien und Medien aktivieren. - Gruppenmitglieder können Dateien und Medien senden. + Mitglieder können Dateien und Medien senden. Der Wechsel der Empfängeradresse wird beendet. Die bisherige Adresse wird weiter verwendet. - In dieser Gruppe sind Dateien und Medien nicht erlaubt. + Dateien und Medien sind nicht erlaubt. Favorit entfernen Favorit Keine gefilterten Chats @@ -1385,9 +1383,7 @@ Reparatur der Verschlüsselung nach Wiedereinspielen von Backups. Ein paar weitere Dinge Auch wenn sie in den Unterhaltungen deaktiviert sind. - - stabilere Zustellung von Nachrichten. -\n- ein bisschen verbesserte Gruppen. -\n- und mehr! + - Stabilere Zustellung von Nachrichten.\n- Ein bisschen verbesserte Gruppen.\n- Und mehr! Nicht aktivieren Das Senden von Empfangsbestätigungen an alle Kontakte wird aktiviert. Sie können diese später in den Datenschutz- und Sicherheits-Einstellungen der App aktivieren. @@ -1457,9 +1453,7 @@ Arabisch, Bulgarisch, Finnisch, Hebräisch, Thailändisch und Ukrainisch - Dank der Nutzer und Weblate. Erstellen eines neuen Profils in der Desktop-App. 💻 Inkognito beim Verbinden einschalten. - - Verbindung mit dem Directory-Service (BETA)! -\n- Empfangsbestätigungen (für bis zu 20 Mitglieder). -\n- Schneller und stabiler. + - Verbindung mit dem Directory-Service (BETA)!\n- Empfangsbestätigungen (für bis zu 20 Mitglieder).\n- Schneller und stabiler. Direktnachricht senden Direkt miteinander verbunden Erweitern @@ -1568,9 +1562,7 @@ Desktop-Adresse einfügen Code mit dem Desktop überprüfen Den QR-Code vom Desktop scannen - - Optionale Benachrichtigung von gelöschten Kontakten. -\n- Profilnamen mit Leerzeichen. -\n- Und mehr! + - Optionale Benachrichtigung von gelöschten Kontakten.\n- Profilnamen mit Leerzeichen.\n- Und mehr! Vom Mobiltelefon scannen Verbindungen überprüfen Bitte warten Sie, solange die Datei von dem verknüpften Mobiltelefon geladen wird @@ -1799,12 +1791,12 @@ SimpleX-Links sind nicht erlaubt Sprachnachrichten sind nicht erlaubt SimpleX-Links - Gruppenmitglieder können SimpleX-Links senden. + Mitglieder können SimpleX-Links senden. Administratoren Alle Mitglieder Aktiviert für Eigentümer - In dieser Gruppe sind SimpleX-Links nicht erlaubt. + SimpleX-Links sind nicht erlaubt. Das Senden von SimpleX-Links nicht erlauben. Das Senden von SimpleX-Links erlauben. Lautsprecher @@ -2077,7 +2069,7 @@ Archivierte Kontakte Keine gefilterten Kontakte Ihre Kontakte - Chat-Symbolleiste unten + App-Symbolleiste unten Bitten Sie Ihren Kontakt darum, Anrufe zu aktivieren. Sie müssen Ihrem Kontakt Anrufe zu Ihnen erlauben, bevor Sie ihn selbst anrufen können. Anrufe erlauben? @@ -2157,8 +2149,7 @@ Verwenden Sie für jedes Profil unterschiedliche Proxy-Anmeldeinformationen. Verwenden Sie zufällige Anmeldeinformationen Benutzername - %1$d Datei-Fehler: -\n%2$s + %1$d Datei-Fehler:\n%2$s %1$d Datei(en) wird/werden immer noch heruntergeladen. Bei %1$d Datei(en) ist das Herunterladen fehlgeschlagen. Fehler beim Weiterleiten der Nachrichten @@ -2215,11 +2206,11 @@ Für soziale Medien Oder zum privaten Teilen SimpleX-Adresse oder Einmal-Link? - Betreiber auswählen + Server-Betreiber Netzwerk-Betreiber - Wenn mehr als ein Netzwerk-Betreiber aktiviert ist, verwendet die App für jede Unterhaltung Server der verschiedenen Betreiber. + Die App verwendet für jede Unterhaltung Server von unterschiedlichen Betreibern, um Ihre Privatsphäre zu schützen. Die Nutzungsbedingungen der aktivierten Betreiber werden nach 30 Tagen akzeptiert. - Wenn Sie beispielsweise Nachrichten über einen SimpleX-Chatserver empfangen, verwendet die App einen der Server von Flux für die private Weiterleitung. + Wenn Ihr Kontakt beispielsweise Nachrichten über einen SimpleX-Chat-Server empfängt, wird Ihre App diese über einen Flux-Server versenden. Später einsehen Wählen sie die zu nutzenden Netzwerk-Betreiber aus. Sie können die Betreiber in den Netzwerk- und Servereinstellungen konfigurieren. @@ -2246,7 +2237,7 @@ Für Nachrichten verwenden Nachrichtenserver hinzugefügt Für privates Routing - Die Server Deines aktuellen Chat-Profils für neue Dateien + Medien- und Datei-Server für neue Daten über Ihr aktuelles Chat-Profil Für das Senden Für Dateien verwenden Fehler beim Hinzufügen des Servers @@ -2271,7 +2262,7 @@ nur mit einem Kontakt genutzt werden - teilen Sie in nur persönlich oder über einen beliebigen Messenger.]]> %s.]]> %s.]]> - Die Nutzungsbedingungen werden akzeptiert am: %s. + Die Nutzungsbedingungen wurden akzeptiert am: %s %s.]]> %s zu nutzen, müssen Sie dessen Nutzungsbedingungen akzeptieren.]]> Fehler beim Akzeptieren der Nutzungsbedingungen @@ -2285,10 +2276,49 @@ Diese Verbindung hat das Limit der nicht ausgelieferten Nachrichten erreicht. Ihr Kontakt ist möglicherweise offline. Diese Nachricht wurde gelöscht oder bisher noch nicht empfangen. Zum Schutz vor dem Austausch Ihres Links können Sie die Sicherheitscodes Ihrer Kontakte vergleichen. - %s.]]> + %s.]]> %s.]]> Die Nutzungsbedingungen wurden akzeptiert am: %s. Der Text der aktuellen Nutzungsbedingungen konnte nicht geladen werden. Sie können die Nutzungsbedingungen unter diesem Link einsehen: Ferngesteuerte Mobiltelefone Oder importieren Sie eine Archiv-Datei + Hinweis für Geräte von Xiaomi: Bitte aktivieren Sie in den System-Einstellungen die Option "Autostart", damit Benachrichtigungen funktionieren.]]> + Ende-zu-Ende-verschlüsselt versendet. In Direktnachrichten sogar mit Post-Quantum-Security.]]> + Team-Mitglieder aufnehmen + Freunde aufnehmen + Einladung akzeptiert + Geschäftliche Adresse + Geschäftliche Chats + Nehmen Sie Team-Mitglieder in Ihre Unterhaltungen auf. + Die App läuft immer im Hintergrund ab + In diesem Chat sind Direktnachrichten zwischen Mitgliedern nicht erlaubt. + Kein Hintergrund-Service + Nachrichten alle 10 Minuten überprüfen + Benachrichtigungen und Akku + Zum Chat einladen + Chat besteht bereits! + Chat-Symbolleiste unten + Chat verlassen + Das Mitglied wird aus dem Chat entfernt. Dies kann nicht rückgängig gemacht werden! + Ihr Chat-Profil wird an die Chat-Mitglieder gesendet. + Direktnachrichten zwischen Mitgliedern sind nicht erlaubt. + Wie die Privatsphäre geschützt wird + Chat verlassen? + Chat löschen? + Der Chat wird für alle Mitglieder gelöscht. Dies kann nicht rückgängig gemacht werden! + Schutz der Privatsphäre Ihrer Kunden. + Zur Verbindung aufgefordert + Bitte verkleinern Sie die Nachrichten-Größe oder entfernen Sie Medien und versenden Sie diese erneut. + Nur Chat-Eigentümer können die Präferenzen ändern. + Bitte verkleinern Sie die Nachrichten-Größe und versenden Sie diese erneut. + Die Rolle wird auf %s geändert. Im Chat wird Jeder darüber informiert. + Sie werden von diesem Chat keine Nachrichten mehr erhalten. Der Nachrichtenverlauf wird beibehalten. + Sie können die Nachricht kopieren und verkleinern, um sie zu versenden. + Chat löschen + Der Chat wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden! + Die Nachricht ist zu umfangreich! + Wenn mehr als ein Betreiber aktiviert ist, hat keiner von ihnen Metadaten, um zu erfahren, wer mit wem kommuniziert. + Chat + %1$s verbunden.]]> + Über Betreiber \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index 0e53f59c06..434e05c174 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -94,7 +94,7 @@ Autenticación de dispositivo desactivada. Puedes habilitar Bloqueo SimpleX en Configuración, después de activar la autenticación de dispositivo. Desactivar Los mensajes temporales no están permitidos en este chat. - Los mensajes temporales no están permitidos en este grupo. + Mensajes temporales no permitidos. El nombre mostrado no puede contener espacios en blanco. Videollamada con cifrado de extremo a extremo conexión establecida @@ -337,7 +337,7 @@ Introduce la contraseña… Grupo inactivo grupo eliminado - Los miembros del grupo pueden enviar mensajes temporales. + Los miembros pueden enviar mensajes temporales. Enlaces de grupo Enlace de conexión no válido Error al aceptar solicitud del contacto @@ -357,7 +357,7 @@ Error al eliminar base de datos Base de datos cifrada Error al eliminar miembro - Los miembros del grupo pueden enviar mensajes de voz. + Los miembros pueden enviar mensajes de voz. en modo incógnito mediante enlace de dirección del contacto ¡Error al crear perfil! No se pudo cargar el chat @@ -407,8 +407,8 @@ SERVIDORES Nombre del grupo: Preferencias del grupo - Los miembros del grupo pueden enviar mensajes directos. - Los miembros del grupo pueden eliminar mensajes de forma irreversible. (24 horas) + Los miembros pueden enviar mensajes directos. + Los miembros pueden eliminar mensajes enviados de forma irreversible. (24 horas) Ocultar pantalla de aplicaciones en aplicaciones recientes. Cifrar Ampliar la selección de roles @@ -431,7 +431,7 @@ Cómo funciona El mensaje será eliminado. ¡No podrá deshacerse! El modo incógnito protege tu privacidad creando un perfil aleatorio por cada contacto. - permite que SimpleX se ejecute en segundo plano en el siguiente cuadro de diálogo. De lo contrario las notificaciones se desactivarán.]]> + Da permiso en el siguiente diálogo para recibir notificaciones instantáneas.]]> Instalar terminal de SimpleX Chat invitación al grupo %1$s ha invitado a %1$s @@ -443,7 +443,7 @@ Notificación instantánea Configuración avanzada Sólo los dispositivos cliente almacenan perfiles de usuario, contactos, grupos y mensajes. - Puedes cambiar estos ajustes más tarde en Configuración. + Cómo afecta a la batería Instantánea Unirte Unirte en modo incógnito @@ -451,7 +451,7 @@ Claro Activado La eliminación irreversible de mensajes no está permitida en este chat. - La eliminación irreversible de mensajes no está permitida en este grupo. + Eliminación irreversible no permitida. Configuración del servidor mejorada Esto puede ocurrir cuando: \n1. Los mensajes hayan caducado en el cliente saliente tras 2 días o en el servidor tras 30 días. @@ -555,7 +555,7 @@ has cambiado el servidor para %s ha salido Salir del grupo - Sólo los propietarios pueden modificar las preferencias del grupo. + Sólo los propietarios del grupo pueden cambiar las preferencias. Eliminar sólo el perfil no k @@ -642,7 +642,7 @@ Espacio reservado para la imagen del perfil Código QR Consultas y sugerencias - Dirección del servidor predefinida + Dirección predefinida del servidor Contacta vía email Valora la aplicación Guardar @@ -718,7 +718,7 @@ envío no autorizado Escribe un nombre para el contacto Error desconocido - El rol del miembro cambiará a "%s" y se notificará al grupo. + El rol cambiará a %s. Todos serán notificados. La seguridad de SimpleX Chat ha sido auditada por Trail of Bits. Los mensajes enviados se eliminarán una vez transcurrido el tiempo establecido. Mensajes de chat SimpleX @@ -736,12 +736,12 @@ ¡La conexión que has aceptado se cancelará! La base de datos no funciona correctamente. Pulsa para conocer más El mensaje será marcado como moderado para todos los miembros. - La nueva generación \nde mensajería privada + El futuro de la mensajería Esta acción es irreversible. Se eliminarán todos los archivos y multimedia recibidos y enviados. Las imágenes de baja resolución permanecerán. Esta acción es irreversible. Los mensajes enviados y recibidos anteriores a la selección serán eliminados. Podría tardar varios minutos. Esta configuración se aplica a los mensajes del perfil actual ¡Esta cadena no es un enlace de conexión! - servicio en segundo planoSimpleX, usa un pequeño porcentaje de la batería al día.]]> + SimpleX se ejecuta en segundo plano en lugar de usar notificaciones push.]]> Configuración Altavoz desactivado Inciar chat nuevo @@ -785,7 +785,7 @@ Probar servidor Probar servidores Estrella en GitHub - Lista de servidores para las conexiones nuevas de tu perfil actual + Lista de servidores para las conexiones nuevas del perfil ¿Usar conexión directa a Internet\? El perfil sólo se comparte con tus contactos. inicializando… @@ -804,7 +804,7 @@ Actualizar contraseña base de datos Pulsa para unirte en modo incógnito Cambiar - El rol del miembro cambiará a "%s" y recibirá una invitación nueva. + El rol cambiará a %s y el miembro recibirá una invitación nueva. Actualizar ¿Actualizar la configuración de red\? Intentando conectar con el servidor para recibir mensajes de este contacto. @@ -840,9 +840,9 @@ Mensajes de voz Tus contactos pueden permitir la eliminación completa de mensajes. Mensajes de voz - Los mensajes de voz no están permitidos en este grupo. + Mensajes de voz no permitidos. Comprobar la seguridad de la conexión - ¡Ya estás conectado a %1$s. + ¡Ya estás conectado con %1$s. ¡Bienvenido! Tu perfil será enviado \na tu contacto @@ -1028,7 +1028,7 @@ Servidores XFTP Puerto puerto %d - Usar hosts .onion como No si el proxy SOCKS no los admite.]]> + Usar hosts .onion debe estar a No si el proxy SOCKS no los admite.]]> Descargar archivo Usar proxy SOCKS Host @@ -1208,7 +1208,7 @@ semanas Error al cargar detalles Los miembros pueden añadir reacciones a los mensajes. - Las reacciones a los mensajes no están permitidas en este grupo. + Reacciones a los mensajes no permitidas. Sólo tu contacto puede añadir reacciones a los mensajes. 1 minuto Registro actualiz @@ -1228,12 +1228,12 @@ Personalizar y compartir temas de color. ¡Por fin los tenemos! 🚀 Reacciones a los mensajes - Conoce más + Saber más Interfaz en japonés y portugués sin texto Han ocurrido algunos errores no críticos durante la importación: ¿Cerrar\? - Aplicación + APLICACIÓN Reiniciar Cerrar Las notificaciones dejarán de funcionar hasta que reinicies la aplicación @@ -1248,8 +1248,8 @@ Cancelar cambio de dirección Archivos y multimedia No se permite el envío de archivos y multimedia. - Los archivos y multimedia no están permitidos en este grupo. - Los miembros del grupo pueden enviar archivos y multimedia. + Archivos y multimedia no permitidos. + Los miembros pueden enviar archivos y multimedia. Se permite enviar archivos y multimedia Favorito Sólo los propietarios del grupo pueden activar los archivos y multimedia. @@ -1356,7 +1356,7 @@ La contraseña aleatoria se almacenará en Configuración como texto plano. \nPuedes cambiarlo más tarde. La contraseña para el cifrado de la base de datos se actualizará y almacenará en Configuración - Eliminar contraseña de configuración\? + ¿Eliminar contraseña de configuración? Usar contraseña aleatoria Guardar contraseña en configuración Configuración contraseña base de datos @@ -1715,8 +1715,8 @@ Enlaces SimpleX no permitidos Mensajes de voz no permitidos Enlaces SimpleX - Los miembros del grupo pueden enviar enlaces SimpleX. - Los enlaces SimpleX no se permiten en este grupo. + Los miembros pueden enviar enlaces SimpleX. + Enlaces SimpleX no permitidos. propietarios Móvil Sin conexión de red @@ -1897,7 +1897,7 @@ errores de descifrado Eliminadas Errores de eliminación - desactivado + inactivo Mensaje reenviado El mensaje puede ser entregado más tarde si el miembro vuelve a estar activo. Miembro inactivo @@ -1990,7 +1990,7 @@ Medio Suave Barra de herramientas accesible - llamada + llamar conectar ¿Eliminar %d mensajes de miembros? mensaje @@ -2083,8 +2083,8 @@ Error guardando proxy Contraseña Autenticación proxy - Credenciales proxy diferentes para cada conexión. - Credenciales proxy diferentes para cada perfil. + Se usan credenciales proxy diferentes para cada conexión. + Se usan credenciales proxy diferentes para cada perfil. Credenciales aleatorias Nombre de usuario Tus credenciales podrían ser enviadas sin cifrar. @@ -2125,12 +2125,12 @@ Ningún servidor para enviar archivos. Seguridad de conexión Compartir enlace de un uso con un amigo - Compartir dirección SimpleX en redes sociales. + Comparte tu dirección SimpleX en redes sociales. Configuración de dirección Crear enlace de un uso Para redes sociales - Dirección SimpleX o enlace de un uso? - Selecciona operadores + ¿Dirección SimpleX o enlace de un uso? + Operadores de servidores Operadores de red Las condiciones de los operadores habilitados serán aceptadas después de 30 días. Revisar más tarde @@ -2162,10 +2162,10 @@ Las condiciones serán aceptadas automáticamente para los operadores habilitados el: %s. Continuar El texto con las condiciones actuales no se ha podido cargar, puedes revisar las condiciones en el siguiente enlace: - Habilitar Flux + Habilita Flux Error al aceptar las condiciones Error al actualizar el servidor - para mayor privacidad de los metadatos. + para mejorar la privacidad de los metadatos. Ningún mensaje Servidor nuevo Ningún servidor de archivos y multimedia. @@ -2175,7 +2175,7 @@ O para compartir en privado Selecciona los operadores de red a utilizar Campartir dirección públicamente - Compartir enlaces de un uso y direcciones SimpleX es seguro a través de cualquier medio. + Compartir los enlaces de un uso y las direcciones SimpleX es seguro a través de cualquier medio. Actualizar Sitio web Tus servidores @@ -2191,25 +2191,64 @@ Mensajes no entregados solamente con un contacto - comparte en persona o mediante cualquier aplicación de mensajería.]]> Puedes añadir un nombre a la conexión para recordar a quién corresponde. - Cuando está habilitado más de un operador de red, la aplicación usa servidores de diferentes operadores para cada conversación. + La aplicación protege tu privacidad mediante el uso de diferentes operadores en cada conversación. Puedes configurar los operadores desde Servidores y Redes. %s.]]> %s.]]> %s.]]> - %s.]]> + %s.]]> %s.]]> %s.]]> %s, acepta las condiciones de uso.]]> Los servidores para archivos nuevos en tu perfil actual - El segundo operador predefinido! + ¡Segundo operador predefinido! Puedes configurar los servidores a través de su configuración. Para protegerte contra una sustitución del enlace, puedes comparar los códigos de seguridad con tu contacto. - %s.]]> + %s.]]> %s.]]> - Si por ejemplo recibes los mensajes a través de un servidor de SimpleX Chat, la aplicación usará uno de Flux para el enrutamiento privado. + Por ejemplo, si tu contacto recibe a través de un servidor de SimpleX Chat, tu aplicación enviará a través de un servidor de Flux. Pulsa Crear dirección SimpleX en el menú para crearla más tarde. La conexión ha alcanzado el límite de mensajes no entregados. es posible que tu contacto esté desconectado. El mensaje ha sido borrado o aún no se ha recibido. Móvil remoto O importa desde un archivo + Mensajes directos entre miembros de este chat no permitidos. + En dispositivos Xiaomi: por favor, habilita el Autoinicio en los ajustes del sistema para que las notificaciones funcionen.]]> + Por favor, reduce el tamaño del mensaje y envíalo de nuevo. + Por favor, reduce el tamaño del mensaje o elimina los archivos y envíalo de nuevo. + Puedes copiar y reducir el tamaño del mensaje para enviarlo. + Añade a los miembros de tu equipo a las conversaciones. + Notificaciones y batería + Invitar al chat + Añadir amigos + Añadir miembros del equipo + El chat será eliminado para todos los miembros. ¡No podrá deshacerse! + Eliminar chat + ¿Eliminar chat? + Salir del chat + El chat será eliminado para tí. ¡No podrá deshacerse! + Sólo los propietarios del chat pueden cambiar las preferencias. + El miembro será eliminado del chat. ¡No podrá deshacerse! + El rol cambiará a %s. Todos serán notificados. + Dejarás de recibir mensajes de este chat. El historial del chat se conserva. + Cómo ayuda a la privacidad + Cuando está habilitado más de un operador, ninguno dispone de los metadatos para conocer quién se comunica con quién. + Tu perfil de chat será enviado a los miembros de chat + Chats empresariales + ¿Salir del chat? + Privacidad para tus clientes. + invitación aceptada + solicitado para conectar + Dirección empresarial + Comprobar mensajes cada 10 min. + Sin servicio en segundo plano + Chat + Barra de herramientas accesible + Mensajes directos entre miembros no permitidos. + %1$s.]]> + ¡El chat ya existe! + Acerca de los operadores + La aplicación siempre funciona en segundo plano + cifrados de extremo a extremo y con seguridad postcuántica en mensajes directos.]]> + ¡Mensaje demasiado largo! \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index 83f408054f..531e29de20 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -15,7 +15,7 @@ 30 másodperc Egyszer használható meghívó-hivatkozás %1$s szeretne kapcsolatba lépni Önnel ezen keresztül: - A SimpleX Chatről + SimpleX Chat névjegye 1 nap Címváltoztatás megszakítása A SimpleXről @@ -1754,7 +1754,7 @@ Ne küldjön üzeneteket közvetlenül, még akkor sem, ha az Ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. Tor vagy VPN nélkül az IP-címe látható lesz a fájlkiszolgálók számára. FÁJLOK - IP-cím védelem + IP-cím védelme Az alkalmazás kérni fogja az ismeretlen fájlkiszolgálókról történő letöltések megerősítését (kivéve, ha az .onion vagy a SOCKS proxy engedélyezve van). Ismeretlen kiszolgálók! Tor vagy VPN nélkül az IP-címe látható lesz az XFTP-közvetítő-kiszolgálók számára:\n%1$s. @@ -2109,13 +2109,13 @@ Egyszer használható meghívó-hivatkozás létrehozása Kiszolgáló-üzemeltetők Hálózati üzemeltetők - Amikor egynél több hálózati üzemeltető van engedélyezve, akkor az alkalmazás minden egyes beszélgetéshez a különböző üzemeltetők kiszolgálóit használja. - Ha például a SimpleX Chat kiszolgálón keresztül fogadja az üzeneteket, az alkalmazás a Flux egyik kiszolgálóját használja a privát útválasztáshoz. + Az alkalmazás úgy védi az adatait, hogy minden egyes beszélgetésben más-más üzemeltetőt használ. + Például, ha az Ön ismerőse egy SimpleX Chat-kiszolgálón keresztül fogadja az üzeneteket, az Ön alkalmazása egy Flux-kiszolgálón keresztül fogja azokat kézbesíteni. Válassza ki a használni kívánt hálózati üzemeltetőket. Felülvizsgálat később - A kiszolgálókat a beállításokon keresztül konfigurálhatja. + A kiszolgálókat a „Hálózat és kiszolgálók” menüben konfigurálhatja. A feltételek 30 nap elteltével lesznek elfogadva az engedélyezett üzemeltetők számára. - Az üzemeltetőket a „Hálózat és kiszolgálók” beállításaban konfigurálhatja. + Az üzemeltetőket a „Hálózat és kiszolgálók” menüben konfigurálhatja. Frissítés Folytatás Feltételek felülvizsgálata @@ -2133,14 +2133,14 @@ %s használata A jelenlegi feltételek szövegét nem lehetett betölteni, a feltételeket ezen a hivatkozáson keresztül vizsgálhatja felül: %s.]]> - %s.]]> - %s.]]> + %s.]]> + %s.]]> %s.]]> %s.]]> %s.]]> Feltételek elfogadása Használati feltételek - %s kiszolgálóinak használatához fogadja el a használati feltételeket.]]> + %s kiszolgálók használatához fogadja el a használati feltételeket.]]> Használat az üzenetekhez A fogadáshoz A privát útválasztáshoz @@ -2204,4 +2204,9 @@ Az üzenet túl nagy! Csökkentse az üzenet méretét vagy távolítsa el a médiát, és küldje el újra. A tagok közötti közvetlen üzenetek le vannak tiltva ebben a csevegésben. + Amikor egynél több üzemeltető van engedélyezve, akkor egyik sem rendelkezik olyan metaadatokkal, amelyekből megtudható, hogy ki kivel kommunikál. + elfogadott meghívó + kérelmezve a kapcsolódáshoz + Az üzemeltetőkről + A SimpleX Chat és a Flux megállapodást kötött arról, hogy a Flux által üzemeltetett kiszolgálókat beépítik az alkalmazásba. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index c342319dbb..11727beaed 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -363,7 +363,7 @@ Audio spento Audio acceso Chiamate audio e video - Auto-accetta immagini + Auto-accetta le immagini hash del messaggio errato ID messaggio errato Chiamata terminata @@ -470,7 +470,7 @@ attivato per il contatto attivato per te Preferenze del gruppo - Auto-accetta richieste di contatto + Auto-accetta le richieste di contatto %dg %d giorno %d giorni @@ -538,7 +538,7 @@ Codice QR non valido immagine di anteprima link Segna come già letto - Segna come non letto + Segna come non letta Altro Silenzia immagine del profilo @@ -704,7 +704,7 @@ Riavvia l\'app per creare un profilo di chat nuovo. Riavvia l\'app per usare il database della chat importato. AVVIA CHAT - Invia anteprime dei link + Invia le anteprime dei link Imposta la password per esportare IMPOSTAZIONI PROXY SOCKS @@ -918,7 +918,7 @@ Grazie agli utenti – contribuite via Weblate! Interfaccia francese Interfaccia italiana - Bozza dei messaggi + Bozza del messaggio Conserva la bozza dell\'ultimo messaggio, con gli allegati. Nomi di file privati Per profilo di chat (predefinito) o per connessione (BETA). @@ -1348,7 +1348,7 @@ %s e %s si sono connessi/e %s, %s e altri %d membri si sono connessi %s, %s e %s si sono connessi/e - Bozza + Bozza del messaggio Mostra gli ultimi messaggi Il database verrà crittografato e la password conservata nelle impostazioni. La password casuale viene conservata nelle impostazioni come testo normale. @@ -1615,8 +1615,7 @@ hai bloccato %s Il messaggio di benvenuto è troppo lungo Messaggio troppo grande - Migrazione database in corso. -\nPuò richiedere qualche minuto. + Migrazione del database in corso.\nPuò richiedere qualche minuto. Chiamata audio Termina chiamata Videochiamata @@ -1980,7 +1979,7 @@ Errore di connessione al server di inoltro %1$s. Riprova più tardi. La versione server di inoltro è incompatibile con le impostazioni di rete: %1$s. Off - Sfocatura file multimediali + Sfocatura dei file multimediali Leggera Media Forte @@ -2131,18 +2130,18 @@ Per i social media O per condividere in modo privato Operatori di rete - Quando più di un operatore di rete è attivato, l\'app userà i server di diversi operatori per ogni conversazione. + L\'app protegge la tua privacy usando diversi operatori per ogni conversazione. Puoi configurare gli operatori nelle impostazioni di rete e server. Operatori del server Seleziona gli operatori di rete da usare. Continua Aggiorna - Esamina più tardi + Leggi più tardi Server preimpostati Condizioni accettate Le condizioni verranno accettate automaticamente per gli operatori attivati il: %s. I tuoi server - Esamina le condizioni + Leggi le condizioni %s.]]> %s.]]> Il testo delle condizioni attuali testo non è stato caricato, puoi consultare le condizioni tramite questo link: @@ -2192,13 +2191,13 @@ Errore di accettazione delle condizioni Errore di salvataggio dei server per una migliore privacy dei metadati. - Ad esempio, se ricevi messaggi tramite il server di SimpleX Chat, l\'app userà uno dei server Flux per l\'instradamento privato. + Ad esempio, se il tuo contatto riceve i messaggi tramite un server di SimpleX Chat, la tua app li consegnerà tramite un server di Flux. Navigazione della chat migliorata Nuovo server Usa per i file Indirizzo SimpleX o link una tantum? Questo messaggio è stato eliminato o non ancora ricevuto. - Tocca \"Crea indirizzo SimpleX\" nel menu per crearlo più tardi. + Tocca Crea indirizzo SimpleX nel menu per crearlo più tardi. La connessione ha raggiunto il limite di messaggi non consegnati, il contatto potrebbe essere offline. Usa i server Puoi configurare i server nelle impostazioni. @@ -2208,7 +2207,7 @@ Nessun server per inviare file. - Apri la chat sul primo messaggio non letto.\n- Salta ai messaggi citati. Condividi indirizzo pubblicamente - Condividi indirizzo SimpleX sui social media. + Condividi l\'indirizzo SimpleX sui social media. O importa file archivio Telefoni remoti I messaggi diretti tra i membri sono vietati in questa chat. @@ -2246,4 +2245,7 @@ Il ruolo verrà cambiato in %s. Verrà notificato a tutti nella chat. Il tuo profilo di chat verrà inviato ai membri della chat Non riceverai più messaggi da questa chat. La cronologia della chat verrà conservata. + Quando più di un operatore è attivato, nessuno di essi ha metadati per capire chi comunica con chi. + invito accettato + richiesto di connettersi \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index f00182b469..57a6a18e90 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -2147,7 +2147,7 @@ Later beoordelen Selecteer welke netwerkoperators u wilt gebruiken. Update - Wanneer er meer dan één netwerkoperator is ingeschakeld, gebruikt de app voor elk gesprek de servers van verschillende operators. + De app beschermt uw privacy door in elk gesprek verschillende operators te gebruiken. U kunt operators configureren in Netwerk- en serverinstellingen. Doorgaan Voorwaarden bekijken @@ -2183,7 +2183,7 @@ Verbeterde chatnavigatie Netwerk decentralisatie De tweede vooraf ingestelde operator in de app! - Als u bijvoorbeeld berichten ontvangt via de SimpleX Chat-server, gebruikt de app een van de Flux-servers voor privéroutering. + Als uw contactpersoon bijvoorbeeld berichten ontvangt via een SimpleX Chat-server, worden deze door uw app via een Flux-server verzonden. Flux inschakelen Geen bericht App-werkbalken @@ -2242,4 +2242,8 @@ Geen achtergrondservice Alleen chateigenaren kunnen voorkeuren wijzigen. Verklein de berichtgrootte of verwijder de media en verzend het bericht opnieuw. + geaccepteerde uitnodiging + Wanneer er meer dan één operator is ingeschakeld, beschikt geen enkele operator over metagegevens om te achterhalen wie met wie communiceert. + gevraagd om verbinding te maken + Over operatoren \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml index a0c5ccf01b..f234a2cf0a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -1882,8 +1882,7 @@ Информация об очереди сообщений Персидский интерфейс Защитить IP адрес - Защитите ваш IP адрес от серверов сообщений, выбранных Вашими контактами. -\nВключите в настройках Сеть и серверы. + Защитите ваш IP адрес от серверов сообщений, выбранных Вашими контактами. \nВключите в настройках Сети и серверов. Отправьте сообщения напрямую, когда Ваш сервер или сервер получателя не поддерживает конфиденциальную доставку. Конфиденциальная доставка Использовать конфиденциальную доставку с неизвестными серверами. @@ -2265,7 +2264,7 @@ Как это улучшает конфиденциальность Операторы серверов Выберите операторов сети. - Вы можете настроить операторов в настройках Сеть и серверы. + Вы можете настроить операторов в настройках Сети и серверов. Продолжить Посмотреть позже Обновить @@ -2305,7 +2304,7 @@ Ошибка сохранения сервера Для доставки сообщений Открыть изменения - Оператор серверов изменен. + Оператор сервера изменен. Протокол сервера изменен. Серверы для новых файлов Вашего текущего профиля Для получения diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index 548e29f836..eff112717e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -219,7 +219,7 @@ OK Скопійовано в буфер обміну Для підключення через посилання - Відкрити у мобільному додатку, а потім торкніться Підключити в додатку.]]> + Відкрити у мобільному додатку, а потім торкніться Підключити в додатку.]]> Приглушити Скасувати приглушення Ви запросили контакт @@ -233,7 +233,7 @@ Одноразове запрошення Невірний код безпеки! Для перевірки end-to-end шифрування порівняйте (або скануйте) код на своїх пристроях. - Ваші налаштування + Налаштування Ваша SimpleX-адреса Допомога з Markdown Блокування SimpleX @@ -268,7 +268,7 @@ Ніяких ідентифікаторів користувачів. Децентралізована Використовувати чат - Це можна змінити пізніше в налаштуваннях. + Як це впливає на батарею Миттєво Виклик вже завершено! Ваші виклики @@ -496,7 +496,7 @@ Тільки ваш контакт може надсилати голосові повідомлення. Забороняйте надсилання повідомлень, які зникають. Забороняйте невідворотне видалення повідомлень. - Учасники групи можуть надсилати голосові повідомлення. + Учасники можуть надсилати голосові повідомлення. %dm Нове в %s Самознищуючий пароль @@ -544,7 +544,7 @@ Створити файл Помилка видалення користувача Помилка оновлення конфіденційності користувача - фоновий сервіс SimpleX – він використовує кілька відсотків батареї щодня.]]> + SimpleX працює у фоновому режимі замість використання пуш-повідомлень.]]> Періодичні сповіщення Служба чату SimpleX Перевіряє нові повідомлення кожні 10 хвилин протягом 1 хвилини @@ -649,7 +649,7 @@ Ви можете використовувати markdown для форматування повідомлень: Створіть свій профіль Створіть приватне підключення - шифрування на двох рівнях.]]> + Тільки клієнтські пристрої зберігають профілі, контакти, групи та повідомлення. Приватні сповіщення Споживає більше акумулятора! Додаток завжди працює у фоновому режимі – сповіщення відображаються миттєво.]]> Вставте отримане посилання @@ -746,14 +746,14 @@ Тільки ви можете надсилати голосові повідомлення. Тільки ви можете додавати реакції на повідомлення. Заборонити реакції на повідомлення. - Самознищувальні повідомлення заборонені в цій групі. - Учасники групи можуть надсилати приватні повідомлення. + Повідомлення, що зникають, заборонені. + Учасники можуть надсилати прямі повідомлення. Приватні повідомлення між учасниками заборонені в цій групі. - Учасники групи можуть назавжди видаляти відправлені повідомлення. (24 години) - Назавжди видалення повідомлень заборонене в цій групі. - Голосові повідомлення заборонені в цій групі. - Учасники групи можуть додавати реакції на повідомлення. - Реакції на повідомлення заборонені в цій групі. + Учасники можуть необоротно видаляти надіслані повідомлення (протягом 24 годин). + Заборонено необоротне видалення повідомлень. + Голосові повідомлення заборонені + Учасники можуть додавати реакції на повідомлення. + Реакції на повідомлення заборонені. %d година %d тиждень %d тижні @@ -805,7 +805,7 @@ Безпечна черга Видалити чергу Будь ласка, перевірте, що ви використали правильне посилання або попросіть вашого контакту вислати інше. - дозвольте SimpleX працювати в фоновому режимі в наступному діалозі. В іншому випадку сповіщення будуть вимкнені.]]> + Дозвольте це в наступному діалозі, щоб отримувати сповіщення миттєво.]]> Миттєві сповіщення Контакт прихований: нове повідомлення @@ -892,7 +892,7 @@ Дякуємо користувачам – приєднуйтеся через Weblate! Режим блокування SimpleX Системна аутентифікація - Для захисту приватності, замість ідентифікаторів користувачів, які використовуються всіма іншими платформами, у SimpleX є ідентифікатори черг повідомлень, окремі для кожного з ваших контактів. + Для захисту вашої конфіденційності SimpleX використовує окремі ID для кожного вашого контакту. Коли додаток запущено Періодично контакт не має зашифрування e2e @@ -919,7 +919,7 @@ ви видалили %1$s Торкніться для активації профілю. Забороняйте надсилання голосових повідомлень. - Учасники групи можуть надсилати самознищувальні повідомлення. + Учасники можуть надсилати повідомлення, що зникають. %d хв Зменшене споживання енергії батареї Редагувати зображення @@ -1012,7 +1012,7 @@ Очистити перевірку %s перевірено %s не перевірено - Надішліть нам електронного листа + Написати нам ел. листа Тестовий сервер Зберегти сервери\? Ваші сервери ICE @@ -1089,7 +1089,7 @@ ні вимк Встановити налаштування групи - Ваші налаштування + Налаштування Прямі повідомлення Помилка Одноразове запрошення @@ -1161,7 +1161,7 @@ кольоровий дзвінок завершено %1$s помилка дзвінка - Наступне покоління \nприватних повідомлень + Майбутнє обміну повідомленнями Кожен може хостити сервери. Інструменти розробника Експериментальні функції @@ -1178,7 +1178,7 @@ (щоб поділитися з вашим контактом) (сканувати або вставити з буферу обміну) підключитися до розробників SimpleX Chat, щоб задати будь-які питання і отримувати оновлення.]]> - Сканувати QR-код.]]> + Сканувати QR-код.]]> Адреса SimpleX Показати QR-код Приєднання до групи @@ -1249,7 +1249,7 @@ Помилка відміни зміни адреси Перервати зміну адреси Дозволити надсилання файлів та медіафайлів. - Файли та медіафайли заборонені в цій групі. + Файли та медіа заборонені. Підключити інкогніто Використовувати поточний профіль Дозволити @@ -1347,7 +1347,7 @@ Зміна адреси буде скасована. Буде використовуватися стара адреса для отримання. Повторно узгодити шифрування? Шифрування працює і нова угода про шифрування не потрібна. Це може призвести до помилок підключення! - Учасники групи можуть надсилати файли та медіафайли. + Учасники можуть надсилати файли та медіа. База даних буде зашифрована, і ключова фраза буде збережена в налаштуваннях. Розгорнути Повторити запит на підключення? @@ -1441,7 +1441,7 @@ Підключати автоматично Адреса робочого столу Одночасно може працювати лише один пристрій - Посилання на мобільний та комп\'ютерний додатки! 🔗 + Підключіть мобільний і десктопний додатки! 🔗 Через безпечний квантовостійкий протокол. Використовувати з робочого столу у мобільному додатку і скануйте QR-код.]]> Щоб приховати небажані повідомлення. @@ -1734,7 +1734,7 @@ Переслати Переслано Переслано з - Учасники групи можуть надсилати посилання SimpleX. + Учасники можуть надсилати посилання SimpleX. Звуки вхідного дзвінка Світлий режим Запасний варіант маршрутизації повідомлень @@ -1828,7 +1828,7 @@ Коли IP приховано Так Отримання паралелізму - У цій групі заборонені посилання на SimpleX. + Посилання SimpleX заборонені. Сформуйте зображення профілю При підключенні аудіо та відеодзвінків. Скинути колір @@ -2011,7 +2011,7 @@ Завантажити %s (%s) Відкрити розташування файлу Пропустити цю версію - Доступна панель чату + Доступні панелі додатка Не можна зателефонувати контакту Підключення до контакту, будь ласка, зачекайте або перевірте пізніше! Дзвінки заборонені! @@ -2129,10 +2129,10 @@ Щоб захиститися від заміни вашого посилання, ви можете порівняти коди безпеки контактів. Для соціальних мереж Або поділитися приватно - Обирайте операторів + Оператори серверів Мережеві оператори Умови будуть прийняті для ввімкнених операторів через 30 днів. - Наприклад, якщо ви отримуєте повідомлення через сервер SimpleX Chat, програма використовуватиме один із серверів Flux для приватної маршрутизації. + Наприклад, якщо ваш контакт отримує повідомлення через сервер SimpleX Chat, ваш додаток доставлятиме їх через сервер Flux. Виберіть мережевих операторів для використання. Ви можете налаштувати сервери за допомогою налаштувань. Перегляньте пізніше @@ -2145,7 +2145,7 @@ Ваші сервери Використовуйте %s Використовуйте сервери - %s.]]> + %s.]]> %s.]]> Прийняти умови Умови перегляду @@ -2177,7 +2177,7 @@ %s.]]> %s.]]> %s.]]> - %s.]]> + %s.]]> %s.]]> %s, прийміть умови використання.]]> Текст поточних умов не вдалося завантажити, ви можете переглянути умови за цим посиланням: @@ -2203,9 +2203,48 @@ SimpleX-адреси та одноразові посилання можна безпечно ділитися через будь-який месенджер. З\'єднання досягло ліміту недоставлених повідомлень, ваш контакт може бути офлайн. Натисніть Створити адресу SimpleX у меню, щоб створити її пізніше. - Якщо увімкнено більше одного оператора, програма використовуватиме сервери різних операторів для кожної розмови. + Додаток захищає вашу конфіденційність, використовуючи різних операторів у кожній розмові. Використовуйте для повідомлень Ви можете налаштувати операторів у налаштуваннях Мережі та серверів. Або імпортуйте архівний файл Віддалені мобільні + Пристрої Xiaomi: будь ласка, увімкніть Автозапуск у налаштуваннях системи, щоб сповіщення працювали.]]> + Повідомлення занадто велике! + Будь ласка, зменшіть розмір повідомлення або видаліть медіа та надішліть знову. + Додайте учасників команди до розмов. + Бізнес адреса + Перевіряти повідомлення кожні 10 хвилин. + Без фонової служби + Сповіщення та батарея + Додаток завжди працює у фоні. + зашифрованими end-to-end, з пост-квантовою безпекою в особистих повідомленнях.]]> + Покинути чат? + Учасник буде видалений з чату — це неможливо скасувати! + Бізнес чати + Конфіденційність для ваших клієнтів. + Доступна панель чату + Додати друзів + Додати учасників команди + Запросити до чату + Чат буде видалений для всіх учасників — це неможливо скасувати! + Видалити чат + Видалити чат? + Тільки власники чату можуть змінювати налаштування. + Роль буде змінена на %s. Усі учасники чату отримають повідомлення. + Прямі повідомлення між учасниками заборонені. + %1$s.]]> + Чат вже існує! + Як це допомагає зберігати конфіденційність + Прямі повідомлення між учасниками заборонені в цьому чаті. + Покинути чат + Чат + Чат буде видалений для вас — це неможливо скасувати! + Будь ласка, зменшіть розмір повідомлення та надішліть знову. + Скопіюйте та зменшіть розмір повідомлення для відправки. + Ви припините отримувати повідомлення з цього чату. Історія чату буде збережена. + Ваш профіль чату буде надіслано учасникам чату. + Коли увімкнено більше ніж одного оператора, жоден з них не має метаданих, щоб дізнатися, хто спілкується з ким. + прийнято запрошення + запит на підключення + Про операторів \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index ca42ccc902..0477307343 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -2157,8 +2157,8 @@ 应用中的第二个预设运营者! 改进了聊天导航 查看更新后的条款 - 比如,如果你通过 SimpleX 服务器收到消息,应用会使用 Flux 服务器中的一台进行私密路由。 - 启用了多于一个网络运营者时,应用会为每个对话使用不同运营者的服务器。 + 比如,如果你通过 SimpleX 服务器收到消息,应用会通过 Flux 服务器传送它们。 + 应用通过在每个对话中使用不同运营者保护你的隐私。 接受条款 模糊 地址或一次性链接? @@ -2229,4 +2229,8 @@ 聊天 将从聊天中删除成员 - 此操作无法撤销! 请减小消息尺寸并再次发送。 + 当启用了超过一个运营者时,没有一个运营者拥有了解谁和谁联络的元数据。 + 已接受邀请 + 被请求连接 + 关于运营者 \ No newline at end of file From 307211a47fee8fe7cdf0a7828219077615579caa Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Sun, 8 Dec 2024 00:09:00 +0700 Subject: [PATCH 211/567] android, desktop: landscape calls on Android and better local camera ratio management (#5124) * android, desktop: landscape calls on Android and better local camera ratio management The main thing is that now when exiting from CallActivity while in call audio devices are not reset to default. It allows to have landscape mode enabled * styles * fix changing calls --- .../android/src/main/AndroidManifest.xml | 1 - .../main/java/chat/simplex/app/SimplexApp.kt | 2 + .../views/call/CallAudioDeviceManager.kt | 8 +- .../common/views/call/CallView.android.kt | 113 ++++++++++-------- .../chat/simplex/common/platform/Platform.kt | 2 + .../simplex/common/views/call/CallManager.kt | 3 + .../chat/simplex/common/views/call/WebRTC.kt | 5 +- .../simplex/common/views/chat/ChatView.kt | 4 +- .../resources/assets/www/android/style.css | 105 +++++++++++----- .../commonMain/resources/assets/www/call.js | 31 +++-- .../resources/assets/www/desktop/style.css | 4 +- .../simplex-chat-webrtc/src/android/style.css | 105 +++++++++++----- packages/simplex-chat-webrtc/src/call.ts | 33 +++-- .../simplex-chat-webrtc/src/desktop/style.css | 4 +- 14 files changed, 283 insertions(+), 137 deletions(-) diff --git a/apps/multiplatform/android/src/main/AndroidManifest.xml b/apps/multiplatform/android/src/main/AndroidManifest.xml index deb5d83e5f..67bc0d70c8 100644 --- a/apps/multiplatform/android/src/main/AndroidManifest.xml +++ b/apps/multiplatform/android/src/main/AndroidManifest.xml @@ -115,7 +115,6 @@ android:launchMode="singleInstance" android:supportsPictureInPicture="true" android:autoRemoveFromRecents="true" - android:screenOrientation="portrait" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"/> = Build.VERSION_CODES.S) { + callAudioDeviceManager.start() + } + } + + override fun close() { + if (closed) return + closed = true + CallSoundsPlayer.stop() + if (wasConnected) { + CallSoundsPlayer.vibrate() + } + callAudioDeviceManager.stop() + dropAudioManagerOverrides() + if (proximityLock?.isHeld == true) { + proximityLock.release() + } + } + + private fun screenOffWakeLock(): WakeLock? { val pm = (androidAppContext.getSystemService(Context.POWER_SERVICE) as PowerManager) - if (pm.isWakeLockLevelSupported(PROXIMITY_SCREEN_OFF_WAKE_LOCK)) { + return if (pm.isWakeLockLevelSupported(PROXIMITY_SCREEN_OFF_WAKE_LOCK)) { pm.newWakeLock(PROXIMITY_SCREEN_OFF_WAKE_LOCK, androidAppContext.packageName + ":proximityLock") } else { null } } - val wasConnected = rememberSaveable { mutableStateOf(false) } +} + + +@SuppressLint("SourceLockedOrientationActivity") +@Composable +actual fun ActiveCallView() { + val call = remember { chatModel.activeCall }.value + val callState = call?.androidCallState as ActiveCallState? + val scope = rememberCoroutineScope() LaunchedEffect(call) { - if (call?.callState == CallState.Connected && !wasConnected.value) { + if (call?.callState == CallState.Connected && callState != null && !callState.wasConnected) { CallSoundsPlayer.vibrate(2) - wasConnected.value = true + callState.wasConnected = true } } - val callAudioDeviceManager = remember { CallAudioDeviceManagerInterface.new() } - DisposableEffect(Unit) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - callAudioDeviceManager.start() - } - onDispose { - CallSoundsPlayer.stop() - if (wasConnected.value) { - CallSoundsPlayer.vibrate() - } - callAudioDeviceManager.stop() - dropAudioManagerOverrides() - if (proximityLock?.isHeld == true) { - proximityLock.release() - } - } - } - LaunchedEffect(chatModel.activeCallViewIsCollapsed.value) { + LaunchedEffect(callState, chatModel.activeCallViewIsCollapsed.value) { + callState ?: return@LaunchedEffect if (chatModel.activeCallViewIsCollapsed.value) { - if (proximityLock?.isHeld == true) proximityLock.release() + if (callState.proximityLock?.isHeld == true) callState.proximityLock.release() } else { delay(1000) - if (proximityLock?.isHeld == false) proximityLock.acquire() + if (callState.proximityLock?.isHeld == false) callState.proximityLock.acquire() } } Box(Modifier.fillMaxSize()) { @@ -122,6 +134,7 @@ actual fun ActiveCallView() { Log.d(TAG, "received from WebRTCView: $apiMsg") val call = chatModel.activeCall.value if (call != null) { + val callState = call.androidCallState as ActiveCallState Log.d(TAG, "has active call $call") val callRh = call.remoteHostId when (val r = apiMsg.resp) { @@ -131,9 +144,9 @@ actual fun ActiveCallView() { updateActiveCall(call) { it.copy(callState = CallState.InvitationSent, localCapabilities = r.capabilities) } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { // Starting is delayed to make Android <= 11 working good with Bluetooth - callAudioDeviceManager.start() + callState.callAudioDeviceManager.start() } else { - callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.hasVideo, true) + callState.callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.hasVideo, true) } CallSoundsPlayer.startConnectingCallSound(scope) activeCallWaitDeliveryReceipt(scope) @@ -143,9 +156,9 @@ actual fun ActiveCallView() { updateActiveCall(call) { it.copy(callState = CallState.OfferSent, localCapabilities = r.capabilities) } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { // Starting is delayed to make Android <= 11 working good with Bluetooth - callAudioDeviceManager.start() + callState.callAudioDeviceManager.start() } else { - callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.hasVideo, true) + callState.callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.hasVideo, true) } } is WCallResponse.Answer -> withBGApi { @@ -228,14 +241,14 @@ actual fun ActiveCallView() { !chatModel.activeCallViewIsCollapsed.value -> true else -> false } - if (call != null && showOverlay) { - ActiveCallOverlay(call, chatModel, callAudioDeviceManager) + if (call != null && showOverlay && callState != null) { + ActiveCallOverlay(call, chatModel, callState.callAudioDeviceManager) } } - KeyChangeEffect(call?.localMediaSources?.hasVideo) { - if (call != null && call.hasVideo && callAudioDeviceManager.currentDevice.value?.type == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE) { + KeyChangeEffect(callState, call?.localMediaSources?.hasVideo) { + if (call != null && call.hasVideo && callState != null && callState.callAudioDeviceManager.currentDevice.value?.type == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE) { // enabling speaker on user action (peer action ignored) and not disabling it again - callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.hasVideo, true) + callState.callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.hasVideo, true) } } val context = LocalContext.current @@ -243,16 +256,12 @@ actual fun ActiveCallView() { val activity = context as? Activity ?: return@DisposableEffect onDispose {} val prevVolumeControlStream = activity.volumeControlStream activity.volumeControlStream = AudioManager.STREAM_VOICE_CALL - // Lock orientation to portrait in order to have good experience with calls - activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT chatModel.activeCallViewIsVisible.value = true // After the first call, End command gets added to the list which prevents making another calls chatModel.callCommand.removeAll { it is WCallCommand.End } keepScreenOn(true) onDispose { activity.volumeControlStream = prevVolumeControlStream - // Unlock orientation - activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED chatModel.activeCallViewIsVisible.value = false chatModel.callCommand.clear() keepScreenOn(false) @@ -264,8 +273,8 @@ actual fun ActiveCallView() { private fun ActiveCallOverlay(call: Call, chatModel: ChatModel, callAudioDeviceManager: CallAudioDeviceManagerInterface) { ActiveCallOverlayLayout( call = call, - devices = remember { callAudioDeviceManager.devices }.value, - currentDevice = remember { callAudioDeviceManager.currentDevice }, + devices = remember(callAudioDeviceManager) { callAudioDeviceManager.devices }.value, + currentDevice = remember(callAudioDeviceManager) { callAudioDeviceManager.currentDevice }, dismiss = { withBGApi { chatModel.callManager.endCall(call) } }, toggleAudio = { chatModel.callCommand.add(WCallCommand.Media(CallMediaSource.Mic, enable = !call.localMediaSources.mic)) }, selectDevice = { callAudioDeviceManager.selectDevice(it.id) }, @@ -832,7 +841,8 @@ fun PreviewActiveCallOverlayVideo() { connectionInfo = ConnectionInfo( RTCIceCandidate(RTCIceCandidateType.Host, "tcp"), RTCIceCandidate(RTCIceCandidateType.Host, "tcp") - ) + ), + androidCallState = {} ), devices = emptyList(), currentDevice = remember { mutableStateOf(null) }, @@ -841,7 +851,7 @@ fun PreviewActiveCallOverlayVideo() { selectDevice = {}, toggleVideo = {}, toggleSound = {}, - flipCamera = {} + flipCamera = {}, ) } } @@ -862,7 +872,8 @@ fun PreviewActiveCallOverlayAudio() { connectionInfo = ConnectionInfo( RTCIceCandidate(RTCIceCandidateType.Host, "udp"), RTCIceCandidate(RTCIceCandidateType.Host, "udp") - ) + ), + androidCallState = {} ), devices = emptyList(), currentDevice = remember { mutableStateOf(null) }, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt index e0a9e22f71..448100bc17 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt @@ -10,6 +10,7 @@ import chat.simplex.common.model.ChatId import chat.simplex.common.model.NotificationsMode import chat.simplex.common.ui.theme.CurrentColors import kotlinx.coroutines.Job +import java.io.Closeable interface PlatformInterface { suspend fun androidServiceStart() {} @@ -26,6 +27,7 @@ interface PlatformInterface { fun androidPictureInPictureAllowed(): Boolean = true fun androidCallEnded() {} fun androidRestartNetworkObserver() {} + fun androidCreateActiveCallState(): Closeable = Closeable { } fun androidIsXiaomiDevice(): Boolean = false val androidApiLevel: Int? get() = null @Composable fun androidLockPortraitOrientation() {} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt index 405094f72a..d6ab57a70d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt @@ -43,6 +43,7 @@ class CallManager(val chatModel: ChatModel) { private fun justAcceptIncomingCall(invitation: RcvCallInvitation, userProfile: Profile) { with (chatModel) { + activeCall.value?.androidCallState?.close() activeCall.value = Call( remoteHostId = invitation.remoteHostId, userProfile = userProfile, @@ -51,6 +52,7 @@ class CallManager(val chatModel: ChatModel) { callState = CallState.InvitationAccepted, initialCallType = invitation.callType.media, sharedKey = invitation.sharedKey, + androidCallState = platform.androidCreateActiveCallState() ) showCallView.value = true val useRelay = controller.appPrefs.webrtcPolicyRelay.get() @@ -78,6 +80,7 @@ class CallManager(val chatModel: ChatModel) { // Don't destroy WebView if you plan to accept next call right after this one if (!switchingCall.value) { showCallView.value = false + activeCall.value?.androidCallState?.close() activeCall.value = null activeCallViewIsCollapsed.value = false platform.androidCallEnded() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt index bbf860b39c..705fc6a28f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt @@ -7,6 +7,7 @@ import chat.simplex.res.MR import kotlinx.datetime.Instant import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import java.io.Closeable import java.net.URI import kotlin.collections.ArrayList @@ -27,7 +28,9 @@ data class Call( // When a user has audio call, and then he wants to enable camera but didn't grant permissions for using camera yet, // we show permissions view without enabling camera before permissions are granted. After they are granted, enabling camera - val wantsToEnableCamera: Boolean = false + val wantsToEnableCamera: Boolean = false, + + val androidCallState: Closeable ) { val encrypted: Boolean get() = localEncrypted && sharedKey != null private val localEncrypted: Boolean get() = localCapabilities?.encryption ?: false diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index ddf25a6e3b..913ea87c98 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.unit.* import chat.simplex.common.model.* import chat.simplex.common.model.CIDirection.GroupRcv import chat.simplex.common.model.ChatController.appPrefs +import chat.simplex.common.model.ChatModel.activeCall import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.model.ChatModel.withChats import chat.simplex.common.ui.theme.* @@ -573,7 +574,8 @@ fun startChatCall(remoteHostId: Long?, chatInfo: ChatInfo, media: CallMediaType) if (chatInfo is ChatInfo.Direct) { val contactInfo = chatModel.controller.apiContactInfo(remoteHostId, chatInfo.contact.contactId) val profile = contactInfo?.second ?: chatModel.currentUser.value?.profile?.toProfile() ?: return@withBGApi - chatModel.activeCall.value = Call(remoteHostId = remoteHostId, contact = chatInfo.contact, callUUID = null, callState = CallState.WaitCapabilities, initialCallType = media, userProfile = profile) + activeCall.value?.androidCallState?.close() + chatModel.activeCall.value = Call(remoteHostId = remoteHostId, contact = chatInfo.contact, callUUID = null, callState = CallState.WaitCapabilities, initialCallType = media, userProfile = profile, androidCallState = platform.androidCreateActiveCallState()) chatModel.showCallView.value = true chatModel.callCommand.add(WCallCommand.Capabilities(media)) } diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/www/android/style.css b/apps/multiplatform/common/src/commonMain/resources/assets/www/android/style.css index a9d1c3785a..377458c184 100644 --- a/apps/multiplatform/common/src/commonMain/resources/assets/www/android/style.css +++ b/apps/multiplatform/common/src/commonMain/resources/assets/www/android/style.css @@ -12,26 +12,60 @@ body { object-fit: cover; } -#remote-video-stream.collapsed { - position: absolute; - max-width: 30%; - max-height: 30%; - object-fit: cover; - margin: 16px; - border-radius: 16px; - bottom: 80px; - right: 0; +@media (orientation: portrait) { + #remote-video-stream.collapsed { + position: absolute; + width: 30%; + max-width: 30%; + height: 39.9vw; + object-fit: cover; + margin: 16px; + border-radius: 16px; + bottom: 80px; + right: 0; + } } -#remote-video-stream.collapsed-pip { - position: absolute; - max-width: 50%; - max-height: 50%; - object-fit: cover; - margin: 8px; - border-radius: 8px; - bottom: 0; - right: 0; +@media (orientation: landscape) { + #remote-video-stream.collapsed { + position: absolute; + width: 20%; + max-width: 20%; + height: 15.03vw; + object-fit: cover; + margin: 16px; + border-radius: 16px; + bottom: 80px; + right: 0; + } +} + +@media (orientation: portrait) { + #remote-video-stream.collapsed-pip { + position: absolute; + width: 50%; + max-width: 50%; + height: 66.5vw; + object-fit: cover; + margin: 8px; + border-radius: 8px; + bottom: 0; + right: 0; + } +} + +@media (orientation: landscape) { + #remote-video-stream.collapsed-pip { + position: absolute; + width: 50%; + max-width: 50%; + height: 37.59vw; + object-fit: cover; + margin: 8px; + border-radius: 8px; + bottom: 0; + right: 0; + } } #remote-screen-video-stream.inline { @@ -41,15 +75,32 @@ body { object-fit: cover; } -#local-video-stream.inline { - position: absolute; - width: 30%; - max-width: 30%; - object-fit: cover; - margin: 16px; - border-radius: 16px; - top: 0; - right: 0; +@media (orientation: portrait) { + #local-video-stream.inline { + position: absolute; + width: 30%; + max-width: 30%; + height: 39.9vw; + object-fit: cover; + margin: 16px; + border-radius: 16px; + top: 0; + right: 0; + } +} + +@media (orientation: landscape) { + #local-video-stream.inline { + position: absolute; + width: 20%; + max-width: 20%; + height: 15.03vw; + object-fit: cover; + margin: 16px; + border-radius: 16px; + top: 0; + right: 0; + } } #local-screen-video-stream.inline { diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/www/call.js b/apps/multiplatform/common/src/commonMain/resources/assets/www/call.js index 4dae487d03..7ab8d6fdd6 100644 --- a/apps/multiplatform/common/src/commonMain/resources/assets/www/call.js +++ b/apps/multiplatform/common/src/commonMain/resources/assets/www/call.js @@ -301,6 +301,7 @@ const processCommand = (function () { localStream = await getLocalMediaStream(true, command.media == CallMediaType.Video && (await browserHasCamera()), VideoCamera.User); const videos = getVideoElements(); if (videos) { + setupLocalVideoRatio(videos.local); videos.local.srcObject = localStream; videos.local.play().catch((e) => console.log(e)); } @@ -330,9 +331,12 @@ const processCommand = (function () { console.log("starting incoming call - create webrtc session"); if (activeCall) endCall(); + // It can be already defined on Android when switching calls (if the previous call was outgoing) + notConnectedCall = undefined; inactiveCallMediaSources.mic = true; inactiveCallMediaSources.camera = command.media == CallMediaType.Video; inactiveCallMediaSourcesChanged(inactiveCallMediaSources); + setupLocalVideoRatio(getVideoElements().local); const { media, iceServers, relay } = command; const encryption = supportsInsertableStreams(useWorker); const aesKey = encryption ? command.aesKey : undefined; @@ -547,13 +551,13 @@ const processCommand = (function () { } function endCall() { var _a; + shutdownCameraAndMic(); try { (_a = activeCall === null || activeCall === void 0 ? void 0 : activeCall.connection) === null || _a === void 0 ? void 0 : _a.close(); } catch (e) { console.log(e); } - shutdownCameraAndMic(); activeCall = undefined; resetVideoElements(); } @@ -642,27 +646,21 @@ const processCommand = (function () { } // Without doing it manually Firefox shows black screen but video can be played in Picture-in-Picture videos.local.play().catch((e) => console.log(e)); - setupLocalVideoRatio(videos.local); } function setupLocalVideoRatio(local) { - const ratio = isDesktop ? 1.33 : 1 / 1.33; - const currentRect = local.getBoundingClientRect(); - // better to get percents from here than to hardcode values from styles (the styles can be changed) - const screenWidth = currentRect.left + currentRect.width; - const percents = currentRect.width / screenWidth; - local.style.width = `${percents * 100}%`; - local.style.height = `${(percents / ratio) * 100}vw`; local.addEventListener("loadedmetadata", function () { console.log("Local video videoWidth: " + local.videoWidth + "px, videoHeight: " + local.videoHeight + "px"); if (local.videoWidth == 0 || local.videoHeight == 0) return; - local.style.height = `${(percents / (local.videoWidth / local.videoHeight)) * 100}vw`; + const ratio = local.videoWidth > local.videoHeight ? 0.2 : 0.3; + local.style.height = `${(ratio / (local.videoWidth / local.videoHeight)) * 100}vw`; }); local.onresize = function () { console.log("Local video size changed to " + local.videoWidth + "x" + local.videoHeight); if (local.videoWidth == 0 || local.videoHeight == 0) return; - local.style.height = `${(percents / (local.videoWidth / local.videoHeight)) * 100}vw`; + const ratio = local.videoWidth > local.videoHeight ? 0.2 : 0.3; + local.style.height = `${(ratio / (local.videoWidth / local.videoHeight)) * 100}vw`; }; } function setupEncryptionForLocalStream(call) { @@ -1128,8 +1126,9 @@ const processCommand = (function () { (!!useWorker && "RTCRtpScriptTransform" in window)); } function shutdownCameraAndMic() { - if (activeCall === null || activeCall === void 0 ? void 0 : activeCall.localStream) { + if (activeCall) { activeCall.localStream.getTracks().forEach((track) => track.stop()); + activeCall.localScreenStream.getTracks().forEach((track) => track.stop()); } } function resetVideoElements() { @@ -1295,6 +1294,9 @@ function changeLayout(layout) { break; } videos.localScreen.style.visibility = localSources.screenVideo ? "visible" : "hidden"; + if (!isDesktop && !localSources.camera) { + resetLocalVideoElementHeight(videos.local); + } } function getVideoElements() { const local = document.getElementById("local-video-stream"); @@ -1312,6 +1314,11 @@ function getVideoElements() { return; return { local, localScreen, remote, remoteScreen }; } +// Allow CSS to figure out the size of view by itself on Android because rotating to different orientation +// without dropping override will cause the view to have not normal proportion while no video is present +function resetLocalVideoElementHeight(local) { + local.style.height = ""; +} function desktopShowPermissionsAlert(mediaType) { if (!isDesktop) return; diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/www/desktop/style.css b/apps/multiplatform/common/src/commonMain/resources/assets/www/desktop/style.css index 99050bc94f..5110c7c7d6 100644 --- a/apps/multiplatform/common/src/commonMain/resources/assets/www/desktop/style.css +++ b/apps/multiplatform/common/src/commonMain/resources/assets/www/desktop/style.css @@ -15,8 +15,9 @@ body { #remote-video-stream.collapsed { position: absolute; + width: 20%; max-width: 20%; - max-height: 20%; + height: 15.03vw; object-fit: cover; margin: 16px; border-radius: 16px; @@ -47,6 +48,7 @@ body { position: absolute; width: 20%; max-width: 20%; + height: 15.03vw; object-fit: cover; margin: 16px; border-radius: 16px; diff --git a/packages/simplex-chat-webrtc/src/android/style.css b/packages/simplex-chat-webrtc/src/android/style.css index a9d1c3785a..377458c184 100644 --- a/packages/simplex-chat-webrtc/src/android/style.css +++ b/packages/simplex-chat-webrtc/src/android/style.css @@ -12,26 +12,60 @@ body { object-fit: cover; } -#remote-video-stream.collapsed { - position: absolute; - max-width: 30%; - max-height: 30%; - object-fit: cover; - margin: 16px; - border-radius: 16px; - bottom: 80px; - right: 0; +@media (orientation: portrait) { + #remote-video-stream.collapsed { + position: absolute; + width: 30%; + max-width: 30%; + height: 39.9vw; + object-fit: cover; + margin: 16px; + border-radius: 16px; + bottom: 80px; + right: 0; + } } -#remote-video-stream.collapsed-pip { - position: absolute; - max-width: 50%; - max-height: 50%; - object-fit: cover; - margin: 8px; - border-radius: 8px; - bottom: 0; - right: 0; +@media (orientation: landscape) { + #remote-video-stream.collapsed { + position: absolute; + width: 20%; + max-width: 20%; + height: 15.03vw; + object-fit: cover; + margin: 16px; + border-radius: 16px; + bottom: 80px; + right: 0; + } +} + +@media (orientation: portrait) { + #remote-video-stream.collapsed-pip { + position: absolute; + width: 50%; + max-width: 50%; + height: 66.5vw; + object-fit: cover; + margin: 8px; + border-radius: 8px; + bottom: 0; + right: 0; + } +} + +@media (orientation: landscape) { + #remote-video-stream.collapsed-pip { + position: absolute; + width: 50%; + max-width: 50%; + height: 37.59vw; + object-fit: cover; + margin: 8px; + border-radius: 8px; + bottom: 0; + right: 0; + } } #remote-screen-video-stream.inline { @@ -41,15 +75,32 @@ body { object-fit: cover; } -#local-video-stream.inline { - position: absolute; - width: 30%; - max-width: 30%; - object-fit: cover; - margin: 16px; - border-radius: 16px; - top: 0; - right: 0; +@media (orientation: portrait) { + #local-video-stream.inline { + position: absolute; + width: 30%; + max-width: 30%; + height: 39.9vw; + object-fit: cover; + margin: 16px; + border-radius: 16px; + top: 0; + right: 0; + } +} + +@media (orientation: landscape) { + #local-video-stream.inline { + position: absolute; + width: 20%; + max-width: 20%; + height: 15.03vw; + object-fit: cover; + margin: 16px; + border-radius: 16px; + top: 0; + right: 0; + } } #local-screen-video-stream.inline { diff --git a/packages/simplex-chat-webrtc/src/call.ts b/packages/simplex-chat-webrtc/src/call.ts index 693ad6bbe5..5f3d2bf332 100644 --- a/packages/simplex-chat-webrtc/src/call.ts +++ b/packages/simplex-chat-webrtc/src/call.ts @@ -593,6 +593,7 @@ const processCommand = (function () { ) const videos = getVideoElements() if (videos) { + setupLocalVideoRatio(videos.local) videos.local.srcObject = localStream videos.local.play().catch((e) => console.log(e)) } @@ -621,9 +622,12 @@ const processCommand = (function () { console.log("starting incoming call - create webrtc session") if (activeCall) endCall() + // It can be already defined on Android when switching calls (if the previous call was outgoing) + notConnectedCall = undefined inactiveCallMediaSources.mic = true inactiveCallMediaSources.camera = command.media == CallMediaType.Video inactiveCallMediaSourcesChanged(inactiveCallMediaSources) + setupLocalVideoRatio(getVideoElements()!.local) const {media, iceServers, relay} = command const encryption = supportsInsertableStreams(useWorker) @@ -827,12 +831,12 @@ const processCommand = (function () { } function endCall() { + shutdownCameraAndMic() try { activeCall?.connection?.close() } catch (e) { console.log(e) } - shutdownCameraAndMic() activeCall = undefined resetVideoElements() } @@ -925,28 +929,21 @@ const processCommand = (function () { } // Without doing it manually Firefox shows black screen but video can be played in Picture-in-Picture videos.local.play().catch((e) => console.log(e)) - setupLocalVideoRatio(videos.local) } function setupLocalVideoRatio(local: HTMLVideoElement) { - const ratio = isDesktop ? 1.33 : 1 / 1.33 - const currentRect = local.getBoundingClientRect() - // better to get percents from here than to hardcode values from styles (the styles can be changed) - const screenWidth = currentRect.left + currentRect.width - const percents = currentRect.width / screenWidth - local.style.width = `${percents * 100}%` - local.style.height = `${(percents / ratio) * 100}vw` - local.addEventListener("loadedmetadata", function () { console.log("Local video videoWidth: " + local.videoWidth + "px, videoHeight: " + local.videoHeight + "px") if (local.videoWidth == 0 || local.videoHeight == 0) return - local.style.height = `${(percents / (local.videoWidth / local.videoHeight)) * 100}vw` + const ratio = local.videoWidth > local.videoHeight ? 0.2 : 0.3 + local.style.height = `${(ratio / (local.videoWidth / local.videoHeight)) * 100}vw` }) local.onresize = function () { console.log("Local video size changed to " + local.videoWidth + "x" + local.videoHeight) if (local.videoWidth == 0 || local.videoHeight == 0) return - local.style.height = `${(percents / (local.videoWidth / local.videoHeight)) * 100}vw` + const ratio = local.videoWidth > local.videoHeight ? 0.2 : 0.3 + local.style.height = `${(ratio / (local.videoWidth / local.videoHeight)) * 100}vw` } } @@ -1441,8 +1438,9 @@ const processCommand = (function () { } function shutdownCameraAndMic() { - if (activeCall?.localStream) { + if (activeCall) { activeCall.localStream.getTracks().forEach((track) => track.stop()) + activeCall.localScreenStream.getTracks().forEach((track) => track.stop()) } } @@ -1614,6 +1612,9 @@ function changeLayout(layout: LayoutType) { break } videos.localScreen.style.visibility = localSources.screenVideo ? "visible" : "hidden" + if (!isDesktop && !localSources.camera) { + resetLocalVideoElementHeight(videos.local) + } } function getVideoElements(): VideoElements | undefined { @@ -1637,6 +1638,12 @@ function getVideoElements(): VideoElements | undefined { return {local, localScreen, remote, remoteScreen} } +// Allow CSS to figure out the size of view by itself on Android because rotating to different orientation +// without dropping override will cause the view to have not normal proportion while no video is present +function resetLocalVideoElementHeight(local: HTMLVideoElement) { + local.style.height = "" +} + function desktopShowPermissionsAlert(mediaType: CallMediaType) { if (!isDesktop) return diff --git a/packages/simplex-chat-webrtc/src/desktop/style.css b/packages/simplex-chat-webrtc/src/desktop/style.css index 99050bc94f..5110c7c7d6 100644 --- a/packages/simplex-chat-webrtc/src/desktop/style.css +++ b/packages/simplex-chat-webrtc/src/desktop/style.css @@ -15,8 +15,9 @@ body { #remote-video-stream.collapsed { position: absolute; + width: 20%; max-width: 20%; - max-height: 20%; + height: 15.03vw; object-fit: cover; margin: 16px; border-radius: 16px; @@ -47,6 +48,7 @@ body { position: absolute; width: 20%; max-width: 20%; + height: 15.03vw; object-fit: cover; margin: 16px; border-radius: 16px; From 7c86484978fd03e99964f6004cee78ff73ce98ee Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 7 Dec 2024 17:22:14 +0000 Subject: [PATCH 212/567] ios: update library --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 22d5ba971b..ac9993e367 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -167,9 +167,9 @@ 648010AB281ADD15009009B9 /* CIFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648010AA281ADD15009009B9 /* CIFileView.swift */; }; 648679AB2BC96A74006456E7 /* ChatItemForwardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648679AA2BC96A74006456E7 /* ChatItemForwardingView.swift */; }; 649B28DD2CFE07CF00536B68 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28D82CFE07CF00536B68 /* libffi.a */; }; - 649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.6-5lGV6gtq9gSDlEsE8DHXYo-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.6-5lGV6gtq9gSDlEsE8DHXYo-ghc9.6.3.a */; }; + 649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.7-3p784Fmu4gOAiEiFcsHj1E-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.7-3p784Fmu4gOAiEiFcsHj1E-ghc9.6.3.a */; }; 649B28DF2CFE07CF00536B68 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DA2CFE07CF00536B68 /* libgmpxx.a */; }; - 649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.6-5lGV6gtq9gSDlEsE8DHXYo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.6-5lGV6gtq9gSDlEsE8DHXYo.a */; }; + 649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.7-3p784Fmu4gOAiEiFcsHj1E.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.7-3p784Fmu4gOAiEiFcsHj1E.a */; }; 649B28E12CFE07CF00536B68 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DC2CFE07CF00536B68 /* libgmp.a */; }; 649BCDA0280460FD00C3A862 /* ComposeImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */; }; 649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCDA12805D6EF00C3A862 /* CIImageView.swift */; }; @@ -516,9 +516,9 @@ 648679AA2BC96A74006456E7 /* ChatItemForwardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemForwardingView.swift; sourceTree = ""; }; 6493D667280ED77F007A76FB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 649B28D82CFE07CF00536B68 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.6-5lGV6gtq9gSDlEsE8DHXYo-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.6-5lGV6gtq9gSDlEsE8DHXYo-ghc9.6.3.a"; sourceTree = ""; }; + 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.7-3p784Fmu4gOAiEiFcsHj1E-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.7-3p784Fmu4gOAiEiFcsHj1E-ghc9.6.3.a"; sourceTree = ""; }; 649B28DA2CFE07CF00536B68 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.6-5lGV6gtq9gSDlEsE8DHXYo.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.6-5lGV6gtq9gSDlEsE8DHXYo.a"; sourceTree = ""; }; + 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.7-3p784Fmu4gOAiEiFcsHj1E.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.7-3p784Fmu4gOAiEiFcsHj1E.a"; sourceTree = ""; }; 649B28DC2CFE07CF00536B68 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeImageView.swift; sourceTree = ""; }; 649BCDA12805D6EF00C3A862 /* CIImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIImageView.swift; sourceTree = ""; }; @@ -671,9 +671,9 @@ 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, 649B28E12CFE07CF00536B68 /* libgmp.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - 649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.6-5lGV6gtq9gSDlEsE8DHXYo.a in Frameworks */, + 649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.7-3p784Fmu4gOAiEiFcsHj1E.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - 649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.6-5lGV6gtq9gSDlEsE8DHXYo-ghc9.6.3.a in Frameworks */, + 649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.7-3p784Fmu4gOAiEiFcsHj1E-ghc9.6.3.a in Frameworks */, 649B28DD2CFE07CF00536B68 /* libffi.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -754,8 +754,8 @@ 649B28D82CFE07CF00536B68 /* libffi.a */, 649B28DC2CFE07CF00536B68 /* libgmp.a */, 649B28DA2CFE07CF00536B68 /* libgmpxx.a */, - 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.6-5lGV6gtq9gSDlEsE8DHXYo-ghc9.6.3.a */, - 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.6-5lGV6gtq9gSDlEsE8DHXYo.a */, + 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.7-3p784Fmu4gOAiEiFcsHj1E-ghc9.6.3.a */, + 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.7-3p784Fmu4gOAiEiFcsHj1E.a */, ); path = Libraries; sourceTree = ""; From df30bb99cb0ed722f82665bba608d1d18f3a54b7 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 7 Dec 2024 17:51:53 +0000 Subject: [PATCH 213/567] ui: add translations --- .../SimpleX Localizations/it.xcloc/Localized Contents/it.xliff | 1 + apps/ios/it.lproj/Localizable.strings | 3 +++ .../common/src/commonMain/resources/MR/de/strings.xml | 1 + .../common/src/commonMain/resources/MR/es/strings.xml | 1 + .../common/src/commonMain/resources/MR/it/strings.xml | 1 + .../common/src/commonMain/resources/MR/nl/strings.xml | 1 + 6 files changed, 8 insertions(+) diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index a3aa28580c..8a32fd3277 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -6715,6 +6715,7 @@ Attivalo nelle impostazioni *Rete e server*. SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + SimpleX Chat e Flux hanno concluso un accordo per includere server gestiti da Flux nell'app No comment provided by engineer. diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index 80122f8535..31ee4e9e18 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -4454,6 +4454,9 @@ /* No comment provided by engineer. */ "SimpleX address or 1-time link?" = "Indirizzo SimpleX o link una tantum?"; +/* No comment provided by engineer. */ +"SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "SimpleX Chat e Flux hanno concluso un accordo per includere server gestiti da Flux nell'app"; + /* No comment provided by engineer. */ "SimpleX Chat security was audited by Trail of Bits." = "La sicurezza di SimpleX Chat è stata verificata da Trail of Bits."; diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index 7c3ecd5ece..be6896d932 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -2321,4 +2321,5 @@ Chat %1$s verbunden.]]> Über Betreiber + SimpleX-Chat und Flux haben vereinbart, die von Flux betriebenen Server in die App aufzunehmen. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index 434e05c174..6163d7e873 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -2251,4 +2251,5 @@ La aplicación siempre funciona en segundo plano cifrados de extremo a extremo y con seguridad postcuántica en mensajes directos.]]> ¡Mensaje demasiado largo! + Simplex Chat y Flux han acordado incluir servidores operados por Flux en la aplicación. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index 11727beaed..ffdc377ceb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -2248,4 +2248,5 @@ Quando più di un operatore è attivato, nessuno di essi ha metadati per capire chi comunica con chi. invito accettato richiesto di connettersi + SimpleX Chat e Flux hanno concluso un accordo per includere server gestiti da Flux nell\'app \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index 57a6a18e90..ced3b9a3b0 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -2246,4 +2246,5 @@ Wanneer er meer dan één operator is ingeschakeld, beschikt geen enkele operator over metagegevens om te achterhalen wie met wie communiceert. gevraagd om verbinding te maken Over operatoren + Simplex-chat en flux hebben een overeenkomst gemaakt om door flux geëxploiteerde servers in de app op te nemen. \ No newline at end of file From f0781adbd3d4ec3449dd14a248f636950246688f Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Sat, 7 Dec 2024 22:51:23 +0400 Subject: [PATCH 214/567] desktop: fix opening operators on onboarding (#5351) --- .../views/onboarding/ChooseServerOperators.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt index dcb7d7e133..2f84166362 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt @@ -14,10 +14,8 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* @@ -55,7 +53,7 @@ fun ModalData.ChooseServerOperators( Column(Modifier.fillMaxWidth().padding(horizontal = DEFAULT_PADDING), horizontalAlignment = Alignment.CenterHorizontally) { OnboardingInformationButton( stringResource(MR.strings.how_it_helps_privacy), - onClick = { modalManager.showModal { ChooseServerOperatorsInfoView() } } + onClick = { modalManager.showModal { ChooseServerOperatorsInfoView(modalManager) } } ) } @@ -346,7 +344,9 @@ private fun enabledOperators(operators: List, selectedOperatorId } @Composable -private fun ChooseServerOperatorsInfoView() { +private fun ChooseServerOperatorsInfoView( + modalManager: ModalManager +) { ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.onboarding_network_operators)) @@ -362,7 +362,7 @@ private fun ChooseServerOperatorsInfoView() { SectionView(title = stringResource(MR.strings.onboarding_network_about_operators).uppercase()) { chatModel.conditions.value.serverOperators.forEach { op -> - ServerOperatorRow(op) + ServerOperatorRow(op, modalManager) } } SectionBottomSpacer() @@ -371,11 +371,12 @@ private fun ChooseServerOperatorsInfoView() { @Composable() private fun ServerOperatorRow( - operator: ServerOperator + operator: ServerOperator, + modalManager: ModalManager ) { SectionItemView( { - ModalManager.start.showModalCloseable { close -> + modalManager.showModalCloseable { close -> OperatorInfoView(operator) } } From febea096db302330543fc5a88253687b4dfa5f37 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 7 Dec 2024 18:53:18 +0000 Subject: [PATCH 215/567] android, desktop: remove duplicate translation key --- .../common/src/commonMain/resources/MR/base/strings.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 82bf5bc8dc..cfe56f88ee 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1752,7 +1752,6 @@ %s.]]> %s.]]> %s.]]> - %s.]]> %s.]]> %s.]]> View conditions From 33bc539e16004ac1126fca70e4ab8f2be41e192f Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 7 Dec 2024 20:53:01 +0000 Subject: [PATCH 216/567] 6.2: ios 254, android 259, desktop 82 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 20 ++++++++++---------- apps/multiplatform/gradle.properties | 8 ++++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index ac9993e367..30b9a27e0e 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -1931,7 +1931,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 253; + CURRENT_PROJECT_VERSION = 254; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1980,7 +1980,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 253; + CURRENT_PROJECT_VERSION = 254; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2021,7 +2021,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 253; + CURRENT_PROJECT_VERSION = 254; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2041,7 +2041,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 253; + CURRENT_PROJECT_VERSION = 254; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2066,7 +2066,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 253; + CURRENT_PROJECT_VERSION = 254; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2103,7 +2103,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 253; + CURRENT_PROJECT_VERSION = 254; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2140,7 +2140,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 253; + CURRENT_PROJECT_VERSION = 254; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2191,7 +2191,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 253; + CURRENT_PROJECT_VERSION = 254; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2242,7 +2242,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 253; + CURRENT_PROJECT_VERSION = 254; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2276,7 +2276,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 253; + CURRENT_PROJECT_VERSION = 254; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index e620b4992d..c392be6b0e 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -24,11 +24,11 @@ android.nonTransitiveRClass=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.2-beta.6 -android.version_code=258 +android.version_name=6.2 +android.version_code=259 -desktop.version_name=6.2-beta.6 -desktop.version_code=81 +desktop.version_name=6.2 +desktop.version_code=82 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From b06211bd4e266a6346904e936fc418271f1b852d Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Sun, 8 Dec 2024 10:39:31 +0000 Subject: [PATCH 217/567] flatpak: update metainfo (#5353) * flatpak: update metainfo * make notes like in release * simpler * space --------- Co-authored-by: Evgeny --- scripts/flatpak/chat.simplex.simplex.metainfo.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/scripts/flatpak/chat.simplex.simplex.metainfo.xml b/scripts/flatpak/chat.simplex.simplex.metainfo.xml index bc90e4e041..9570f4bbca 100644 --- a/scripts/flatpak/chat.simplex.simplex.metainfo.xml +++ b/scripts/flatpak/chat.simplex.simplex.metainfo.xml @@ -38,6 +38,17 @@ + + https://simplex.chat/blog/20241210-simplex-network-v6-2-servers-by-flux-business-chats.html + +

    New in v6.2:

    +
      +
    • SimpleX Chat and Flux made an agreement to include servers operated by Flux into the app – to improve metadata privacy.
    • +
    • Business chats – your customers privacy.
    • +
    • Improved user experience in chats: open on the first unread, jump to quoted messages, see who reacted.
    • +
    +
    +
    https://simplex.chat/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html From d64351b7603160dc27ca9e36ca1a3da999086b9e Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sun, 8 Dec 2024 15:17:18 +0000 Subject: [PATCH 218/567] ui: update label in server statistics --- apps/ios/Shared/Views/ChatList/ServersSummaryView.swift | 4 ++-- .../bg.xcloc/Localized Contents/bg.xliff | 4 ++-- .../cs.xcloc/Localized Contents/cs.xliff | 4 ++-- .../de.xcloc/Localized Contents/de.xliff | 4 ++-- .../en.xcloc/Localized Contents/en.xliff | 6 +++--- .../es.xcloc/Localized Contents/es.xliff | 4 ++-- .../fi.xcloc/Localized Contents/fi.xliff | 4 ++-- .../fr.xcloc/Localized Contents/fr.xliff | 4 ++-- .../hu.xcloc/Localized Contents/hu.xliff | 4 ++-- .../it.xcloc/Localized Contents/it.xliff | 4 ++-- .../ja.xcloc/Localized Contents/ja.xliff | 4 ++-- .../nl.xcloc/Localized Contents/nl.xliff | 4 ++-- .../pl.xcloc/Localized Contents/pl.xliff | 4 ++-- .../pt-BR.xcloc/Localized Contents/pt-BR.xliff | 4 ++-- .../ru.xcloc/Localized Contents/ru.xliff | 4 ++-- .../th.xcloc/Localized Contents/th.xliff | 4 ++-- .../tr.xcloc/Localized Contents/tr.xliff | 4 ++-- .../uk.xcloc/Localized Contents/uk.xliff | 4 ++-- .../zh-Hans.xcloc/Localized Contents/zh-Hans.xliff | 4 ++-- apps/ios/de.lproj/Localizable.strings | 2 +- apps/ios/es.lproj/Localizable.strings | 2 +- apps/ios/fr.lproj/Localizable.strings | 2 +- apps/ios/hu.lproj/Localizable.strings | 2 +- apps/ios/it.lproj/Localizable.strings | 2 +- apps/ios/nl.lproj/Localizable.strings | 2 +- apps/ios/pl.lproj/Localizable.strings | 2 +- apps/ios/ru.lproj/Localizable.strings | 2 +- apps/ios/tr.lproj/Localizable.strings | 2 +- apps/ios/uk.lproj/Localizable.strings | 2 +- apps/ios/zh-Hans.lproj/Localizable.strings | 2 +- .../common/src/commonMain/resources/MR/base/strings.xml | 2 +- 31 files changed, 51 insertions(+), 51 deletions(-) diff --git a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift index b87b84ebc0..aa802c1af9 100644 --- a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift +++ b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift @@ -587,7 +587,7 @@ struct SMPStatsView: View { } header: { Text("Statistics") } footer: { - Text("Starting from \(localTimestamp(statsStartedAt)).") + Text("\n") + Text("All data is private to your device.") + Text("Starting from \(localTimestamp(statsStartedAt)).") + Text("\n") + Text("All data is kept private on your device.") } } } @@ -703,7 +703,7 @@ struct XFTPStatsView: View { } header: { Text("Statistics") } footer: { - Text("Starting from \(localTimestamp(statsStartedAt)).") + Text("\n") + Text("All data is private to your device.") + Text("Starting from \(localTimestamp(statsStartedAt)).") + Text("\n") + Text("All data is kept private on your device.") } } } diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 9260ac41c0..4aa1f2213f 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -731,8 +731,8 @@ Всички данни се изтриват при въвеждане. No comment provided by engineer.
    - - All data is private to your device. + + All data is kept private on your device. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index d921471f7f..668888c20e 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -712,8 +712,8 @@ Všechna data se při zadání vymažou. No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 1fc614becf..e993740f1c 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -760,8 +760,8 @@ Alle Daten werden gelöscht, sobald dieser eingegeben wird. No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. Alle Daten werden nur auf Ihrem Gerät gespeichert. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 004d7f0d31..cebd6c90d1 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -760,9 +760,9 @@ All data is erased when it is entered. No comment provided by engineer. - - All data is private to your device. - All data is private to your device. + + All data is kept private on your device. + All data is kept private on your device. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index a96aebebae..08522cc617 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -760,8 +760,8 @@ Al introducirlo todos los datos son eliminados. No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. Todos los datos son privados y están en tu dispositivo. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index 2f67ee9d7d..2caa98e25b 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -707,8 +707,8 @@ Kaikki tiedot poistetaan, kun se syötetään. No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 74002293d7..148156b07c 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -745,8 +745,8 @@ Toutes les données sont effacées lorsqu'il est saisi. No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. Toutes les données restent confinées dans votre appareil. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index c682a02d8c..231c33523d 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -760,8 +760,8 @@ A jelkód megadása után az összes adat törlésre kerül. No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. Az összes adat biztonságban van az eszközén. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 8a32fd3277..d785acda81 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -759,8 +759,8 @@ Tutti i dati vengono cancellati quando inserito. No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. Tutti i dati sono privati, nel tuo dispositivo. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 43e6f24cf7..72e68cff48 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -724,8 +724,8 @@ 入力するとすべてのデータが消去されます。 No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 73a9d05b73..ab3499a4dc 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -760,8 +760,8 @@ Alle gegevens worden bij het invoeren gewist. No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. Alle gegevens zijn privé op uw apparaat. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index e7c9863152..8cfdf56f66 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -745,8 +745,8 @@ Wszystkie dane są usuwane po jego wprowadzeniu. No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. Wszystkie dane są prywatne na Twoim urządzeniu. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff b/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff index 9badf9c2e4..93ba6f357b 100644 --- a/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff +++ b/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff @@ -5425,8 +5425,8 @@ Isso pode acontecer por causa de algum bug ou quando a conexão está comprometi Advanced settings Configurações avançadas - - All data is private to your device. + + All data is kept private on your device. Toda informação é privada em seu dispositivo. diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 814b878a03..5809c65216 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -760,8 +760,8 @@ Все данные удаляются при его вводе. No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. Все данные хранятся только на вашем устройстве. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index 177f426c1a..4317787f67 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -699,8 +699,8 @@ ข้อมูลทั้งหมดจะถูกลบเมื่อถูกป้อน No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index d88adc3235..261752aefc 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -745,8 +745,8 @@ Kullanıldığında bütün veriler silinir. No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. Tüm veriler cihazınıza özeldir. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index d68b5abbe1..d7dcc58dcd 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -756,8 +756,8 @@ Всі дані стираються при введенні. No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. Всі дані є приватними для вашого пристрою. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index 99d4a5077f..d6e548c6be 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -739,8 +739,8 @@ 所有数据在输入后将被删除。 No comment provided by engineer. - - All data is private to your device. + + All data is kept private on your device. 所有数据都是您设备的私有数据. No comment provided by engineer. diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 7d69adc1d5..cad89ed29a 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -479,7 +479,7 @@ "All data is erased when it is entered." = "Alle Daten werden gelöscht, sobald dieser eingegeben wird."; /* No comment provided by engineer. */ -"All data is private to your device." = "Alle Daten werden nur auf Ihrem Gerät gespeichert."; +"All data is kept private on your device." = "Alle Daten werden nur auf Ihrem Gerät gespeichert."; /* No comment provided by engineer. */ "All group members will remain connected." = "Alle Gruppenmitglieder bleiben verbunden."; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index ce36dec953..e7570f177e 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -479,7 +479,7 @@ "All data is erased when it is entered." = "Al introducirlo todos los datos son eliminados."; /* No comment provided by engineer. */ -"All data is private to your device." = "Todos los datos son privados y están en tu dispositivo."; +"All data is kept private on your device." = "Todos los datos son privados y están en tu dispositivo."; /* No comment provided by engineer. */ "All group members will remain connected." = "Todos los miembros del grupo permanecerán conectados."; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index 2de5997f07..6b973e75d0 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -431,7 +431,7 @@ "All data is erased when it is entered." = "Toutes les données sont effacées lorsqu'il est saisi."; /* No comment provided by engineer. */ -"All data is private to your device." = "Toutes les données restent confinées dans votre appareil."; +"All data is kept private on your device." = "Toutes les données restent confinées dans votre appareil."; /* No comment provided by engineer. */ "All group members will remain connected." = "Tous les membres du groupe resteront connectés."; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index 594bd3a123..2ba51d1e13 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -479,7 +479,7 @@ "All data is erased when it is entered." = "A jelkód megadása után az összes adat törlésre kerül."; /* No comment provided by engineer. */ -"All data is private to your device." = "Az összes adat biztonságban van az eszközén."; +"All data is kept private on your device." = "Az összes adat biztonságban van az eszközén."; /* No comment provided by engineer. */ "All group members will remain connected." = "Az összes csoporttag kapcsolatban marad."; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index 31ee4e9e18..7c3a7e05de 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -473,7 +473,7 @@ "All data is erased when it is entered." = "Tutti i dati vengono cancellati quando inserito."; /* No comment provided by engineer. */ -"All data is private to your device." = "Tutti i dati sono privati, nel tuo dispositivo."; +"All data is kept private on your device." = "Tutti i dati sono privati, nel tuo dispositivo."; /* No comment provided by engineer. */ "All group members will remain connected." = "Tutti i membri del gruppo resteranno connessi."; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index e5c3520898..7004d0d124 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -479,7 +479,7 @@ "All data is erased when it is entered." = "Alle gegevens worden bij het invoeren gewist."; /* No comment provided by engineer. */ -"All data is private to your device." = "Alle gegevens zijn privé op uw apparaat."; +"All data is kept private on your device." = "Alle gegevens zijn privé op uw apparaat."; /* No comment provided by engineer. */ "All group members will remain connected." = "Alle groepsleden blijven verbonden."; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index e48e9f2ed8..cc3bd228f9 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -431,7 +431,7 @@ "All data is erased when it is entered." = "Wszystkie dane są usuwane po jego wprowadzeniu."; /* No comment provided by engineer. */ -"All data is private to your device." = "Wszystkie dane są prywatne na Twoim urządzeniu."; +"All data is kept private on your device." = "Wszystkie dane są prywatne na Twoim urządzeniu."; /* No comment provided by engineer. */ "All group members will remain connected." = "Wszyscy członkowie grupy pozostaną połączeni."; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index f22981f80a..dcd3de19d1 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -479,7 +479,7 @@ "All data is erased when it is entered." = "Все данные удаляются при его вводе."; /* No comment provided by engineer. */ -"All data is private to your device." = "Все данные хранятся только на вашем устройстве."; +"All data is kept private on your device." = "Все данные хранятся только на вашем устройстве."; /* No comment provided by engineer. */ "All group members will remain connected." = "Все члены группы, которые соединились через эту ссылку, останутся в группе."; diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index 3670e57955..b3eb5d426a 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -431,7 +431,7 @@ "All data is erased when it is entered." = "Kullanıldığında bütün veriler silinir."; /* No comment provided by engineer. */ -"All data is private to your device." = "Tüm veriler cihazınıza özeldir."; +"All data is kept private on your device." = "Tüm veriler cihazınıza özeldir."; /* No comment provided by engineer. */ "All group members will remain connected." = "Tüm grup üyeleri bağlı kalacaktır."; diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index 4e2b1680fd..ce8184272d 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -464,7 +464,7 @@ "All data is erased when it is entered." = "Всі дані стираються при введенні."; /* No comment provided by engineer. */ -"All data is private to your device." = "Всі дані є приватними для вашого пристрою."; +"All data is kept private on your device." = "Всі дані є приватними для вашого пристрою."; /* No comment provided by engineer. */ "All group members will remain connected." = "Всі учасники групи залишаться на зв'язку."; diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index c40833b67b..62ff2088c2 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -413,7 +413,7 @@ "All data is erased when it is entered." = "所有数据在输入后将被删除。"; /* No comment provided by engineer. */ -"All data is private to your device." = "所有数据都是您设备的私有数据."; +"All data is kept private on your device." = "所有数据都是您设备的私有数据."; /* No comment provided by engineer. */ "All group members will remain connected." = "所有群组成员将保持连接。"; diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index cfe56f88ee..ea824285e0 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -2402,7 +2402,7 @@ Messages sent Messages received Details - Starting from %s.\nAll data is private to your device. + Starting from %s.\nAll data is kept private on your device.. Message reception Active connections Pending From 4075c26dd20b536acf321b750878839da4a37da9 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Mon, 9 Dec 2024 21:03:56 +0400 Subject: [PATCH 219/567] ui: offer to fix connection on call and message buttons (#5358) --- apps/ios/Shared/Views/Chat/ChatInfoView.swift | 107 +++++++++++++----- .../Chat/Group/GroupMemberInfoView.swift | 83 ++++++++++---- .../simplex/common/views/chat/ChatInfoView.kt | 68 +++++++---- .../views/chat/group/GroupMemberInfoView.kt | 74 ++++++++---- .../commonMain/resources/MR/base/strings.xml | 4 + 5 files changed, 240 insertions(+), 96 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index ea9daa74bc..9b6b9b73e8 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -156,8 +156,8 @@ struct ChatInfoView: View { HStack(alignment: .center, spacing: 8) { let buttonWidth = g.size.width / 4 searchButton(width: buttonWidth) - AudioCallButton(chat: chat, contact: contact, width: buttonWidth) { alert = .someAlert(alert: $0) } - VideoButton(chat: chat, contact: contact, width: buttonWidth) { alert = .someAlert(alert: $0) } + AudioCallButton(chat: chat, contact: contact, connectionStats: $connectionStats, width: buttonWidth) { alert = .someAlert(alert: $0) } + VideoButton(chat: chat, contact: contact, connectionStats: $connectionStats, width: buttonWidth) { alert = .someAlert(alert: $0) } muteButton(width: buttonWidth) } } @@ -314,7 +314,15 @@ struct ChatInfoView: View { case .networkStatusAlert: return networkStatusAlert() case .switchAddressAlert: return switchAddressAlert(switchContactAddress) case .abortSwitchAddressAlert: return abortSwitchAddressAlert(abortSwitchContactAddress) - case .syncConnectionForceAlert: return syncConnectionForceAlert({ syncContactConnection(force: true) }) + case .syncConnectionForceAlert: + return syncConnectionForceAlert({ + Task { + if let stats = await syncContactConnection(contact, force: true, showAlert: { alert = .someAlert(alert: $0) }) { + connectionStats = stats + dismiss() + } + } + }) case let .queueInfo(info): return queueInfoAlert(info) case let .someAlert(a): return a.alert case let .error(title, error): return mkAlert(title: title, message: error) @@ -493,7 +501,12 @@ struct ChatInfoView: View { private func synchronizeConnectionButton() -> some View { Button { - syncContactConnection(force: false) + Task { + if let stats = await syncContactConnection(contact, force: false, showAlert: { alert = .someAlert(alert: $0) }) { + connectionStats = stats + dismiss() + } + } } label: { Label("Fix connection", systemImage: "exclamationmark.arrow.triangle.2.circlepath") .foregroundColor(.orange) @@ -612,25 +625,6 @@ struct ChatInfoView: View { } } - private func syncContactConnection(force: Bool) { - Task { - do { - let stats = try apiSyncContactRatchet(contact.apiId, force) - connectionStats = stats - await MainActor.run { - chatModel.updateContactConnectionStats(contact, stats) - dismiss() - } - } catch let error { - logger.error("syncContactConnection apiSyncContactRatchet error: \(responseError(error))") - let a = getErrorAlert(error, "Error synchronizing connection") - await MainActor.run { - alert = .error(title: a.title, error: a.message) - } - } - } - } - private func savePreferences() { Task { do { @@ -649,9 +643,32 @@ struct ChatInfoView: View { } } +func syncContactConnection(_ contact: Contact, force: Bool, showAlert: (SomeAlert) -> Void) async -> ConnectionStats? { + do { + let stats = try apiSyncContactRatchet(contact.apiId, force) + await MainActor.run { + ChatModel.shared.updateContactConnectionStats(contact, stats) + } + return stats + } catch let error { + logger.error("syncContactConnection apiSyncContactRatchet error: \(responseError(error))") + let a = getErrorAlert(error, "Error synchronizing connection") + await MainActor.run { + showAlert( + SomeAlert( + alert: mkAlert(title: a.title, message: a.message), + id: "syncContactConnection error" + ) + ) + } + return nil + } +} + struct AudioCallButton: View { var chat: Chat var contact: Contact + @Binding var connectionStats: ConnectionStats? var width: CGFloat var showAlert: (SomeAlert) -> Void @@ -659,6 +676,7 @@ struct AudioCallButton: View { CallButton( chat: chat, contact: contact, + connectionStats: $connectionStats, image: "phone.fill", title: "call", mediaType: .audio, @@ -671,6 +689,7 @@ struct AudioCallButton: View { struct VideoButton: View { var chat: Chat var contact: Contact + @Binding var connectionStats: ConnectionStats? var width: CGFloat var showAlert: (SomeAlert) -> Void @@ -678,6 +697,7 @@ struct VideoButton: View { CallButton( chat: chat, contact: contact, + connectionStats: $connectionStats, image: "video.fill", title: "video", mediaType: .video, @@ -690,6 +710,7 @@ struct VideoButton: View { private struct CallButton: View { var chat: Chat var contact: Contact + @Binding var connectionStats: ConnectionStats? var image: String var title: LocalizedStringKey var mediaType: CallMediaType @@ -701,12 +722,40 @@ private struct CallButton: View { InfoViewButton(image: image, title: title, disabledLook: !canCall, width: width) { if canCall { - if CallController.useCallKit() { - CallController.shared.startCall(contact, mediaType) - } else { - // When CallKit is not used, colorscheme will be changed and it will be visible if not hiding sheets first - dismissAllSheets(animated: true) { - CallController.shared.startCall(contact, mediaType) + if let connStats = connectionStats { + if connStats.ratchetSyncState == .ok { + if CallController.useCallKit() { + CallController.shared.startCall(contact, mediaType) + } else { + // When CallKit is not used, colorscheme will be changed and it will be visible if not hiding sheets first + dismissAllSheets(animated: true) { + CallController.shared.startCall(contact, mediaType) + } + } + } else if connStats.ratchetSyncAllowed { + showAlert(SomeAlert( + alert: Alert( + title: Text("Fix connection?"), + message: Text("Connection requires encryption renegotiation."), + primaryButton: .default(Text("Fix")) { + Task { + if let stats = await syncContactConnection(contact, force: false, showAlert: showAlert) { + connectionStats = stats + } + } + }, + secondaryButton: .cancel() + ), + id: "can't call contact, fix connection" + )) + } else { + showAlert(SomeAlert( + alert: mkAlert( + title: "Can't call contact", + message: "Encryption renegotiation in progress." + ), + id: "can't call contact, encryption renegotiation in progress" + )) } } } else if contact.nextSendGrpInv { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index b73c5e10f5..a18de1b349 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -20,6 +20,9 @@ struct GroupMemberInfoView: View { @State private var connectionStats: ConnectionStats? = nil @State private var connectionCode: String? = nil @State private var connectionLoaded: Bool = false + @State private var knownContactChat: Chat? = nil + @State private var knownContact: Contact? = nil + @State private var knownContactConnectionStats: ConnectionStats? = nil @State private var newRole: GroupMemberRole = .member @State private var alert: GroupMemberInfoViewAlert? @State private var sheet: PlanAndConnectActionSheet? @@ -119,8 +122,8 @@ struct GroupMemberInfoView: View { } label: { Label("Share address", systemImage: "square.and.arrow.up") } - if let contactId = member.memberContactId { - if knownDirectChat(contactId) == nil && !groupInfo.fullGroupPreferences.directMessages.on(for: groupInfo.membership) { + if member.memberContactId != nil { + if knownContactChat == nil && !groupInfo.fullGroupPreferences.directMessages.on(for: groupInfo.membership) { connectViaAddressButton(contactLink) } } else { @@ -229,6 +232,18 @@ struct GroupMemberInfoView: View { } logger.error("apiGroupMemberInfo or apiGetGroupMemberCode error: \(responseError(error))") } + if let contactId = member.memberContactId, let (contactChat, contact) = knownDirectChat(contactId) { + knownContactChat = contactChat + knownContact = contact + do { + let (stats, _) = try await apiContactInfo(contactChat.chatInfo.apiId) + await MainActor.run { + knownContactConnectionStats = stats + } + } catch let error { + logger.error("apiContactInfo error: \(responseError(error))") + } + } } .onChange(of: newRole) { newRole in if newRole != member.memberRole { @@ -274,10 +289,10 @@ struct GroupMemberInfoView: View { GeometryReader { g in let buttonWidth = g.size.width / 4 HStack(alignment: .center, spacing: 8) { - if let contactId = member.memberContactId, let (chat, contact) = knownDirectChat(contactId) { + if let chat = knownContactChat, let contact = knownContact { knownDirectChatButton(chat, width: buttonWidth) - AudioCallButton(chat: chat, contact: contact, width: buttonWidth) { alert = .someAlert(alert: $0) } - VideoButton(chat: chat, contact: contact, width: buttonWidth) { alert = .someAlert(alert: $0) } + AudioCallButton(chat: chat, contact: contact, connectionStats: $knownContactConnectionStats, width: buttonWidth) { alert = .someAlert(alert: $0) } + VideoButton(chat: chat, contact: contact, connectionStats: $knownContactConnectionStats, width: buttonWidth) { alert = .someAlert(alert: $0) } } else if groupInfo.fullGroupPreferences.directMessages.on(for: groupInfo.membership) { if let contactId = member.memberContactId { newDirectChatButton(contactId, width: buttonWidth) @@ -366,25 +381,49 @@ struct GroupMemberInfoView: View { func createMemberContactButton(width: CGFloat) -> some View { InfoViewButton(image: "message.fill", title: "message", width: width) { - progressIndicator = true - Task { - do { - let memberContact = try await apiCreateMemberContact(groupInfo.apiId, groupMember.groupMemberId) - await MainActor.run { - progressIndicator = false - chatModel.addChat(Chat(chatInfo: .direct(contact: memberContact))) - ItemsModel.shared.loadOpenChat(memberContact.id) { - dismissAllSheets(animated: true) + if let connStats = connectionStats { + if connStats.ratchetSyncState == .ok { + progressIndicator = true + Task { + do { + let memberContact = try await apiCreateMemberContact(groupInfo.apiId, groupMember.groupMemberId) + await MainActor.run { + progressIndicator = false + chatModel.addChat(Chat(chatInfo: .direct(contact: memberContact))) + ItemsModel.shared.loadOpenChat(memberContact.id) { + dismissAllSheets(animated: true) + } + NetworkModel.shared.setContactNetworkStatus(memberContact, .connected) + } + } catch let error { + logger.error("createMemberContactButton apiCreateMemberContact error: \(responseError(error))") + let a = getErrorAlert(error, "Error creating member contact") + await MainActor.run { + progressIndicator = false + alert = .error(title: a.title, error: a.message) + } } - NetworkModel.shared.setContactNetworkStatus(memberContact, .connected) - } - } catch let error { - logger.error("createMemberContactButton apiCreateMemberContact error: \(responseError(error))") - let a = getErrorAlert(error, "Error creating member contact") - await MainActor.run { - progressIndicator = false - alert = .error(title: a.title, error: a.message) } + } else if connStats.ratchetSyncAllowed { + alert = .someAlert(alert: SomeAlert( + alert: Alert( + title: Text("Fix connection?"), + message: Text("Connection requires encryption renegotiation."), + primaryButton: .default(Text("Fix")) { + syncMemberConnection(force: false) + }, + secondaryButton: .cancel() + ), + id: "can't message member, fix connection" + )) + } else { + alert = .someAlert(alert: SomeAlert( + alert: mkAlert( + title: "Can't message member", + message: "Encryption renegotiation in progress." + ), + id: "can't message contact, encryption renegotiation in progress" + )) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt index df13368900..ed661245a3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt @@ -131,26 +131,14 @@ fun ChatInfoView( }, syncContactConnection = { withBGApi { - val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = false) - connStats.value = cStats - if (cStats != null) { - withChats { - updateContactConnectionStats(chatRh, contact, cStats) - } - } + syncContactConnection(chatRh, contact, connStats, force = false) close.invoke() } }, syncContactConnectionForce = { showSyncConnectionForceAlert(syncConnectionForce = { withBGApi { - val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = true) - connStats.value = cStats - if (cStats != null) { - withChats { - updateContactConnectionStats(chatRh, contact, cStats) - } - } + syncContactConnection(chatRh, contact, connStats, force = true) close.invoke() } }) @@ -189,6 +177,16 @@ fun ChatInfoView( } } +suspend fun syncContactConnection(rhId: Long?, contact: Contact, connectionStats: MutableState, force: Boolean) { + val cStats = chatModel.controller.apiSyncContactRatchet(rhId, contact.contactId, force = force) + connectionStats.value = cStats + if (cStats != null) { + withChats { + updateContactConnectionStats(rhId, contact, cStats) + } + } +} + sealed class SendReceipts { object Yes: SendReceipts() object No: SendReceipts() @@ -505,7 +503,7 @@ fun ChatInfoLayout( currentUser: User, sendReceipts: State, setSendReceipts: (SendReceipts) -> Unit, - connStats: State, + connStats: MutableState, contactNetworkStatus: NetworkStatus, customUserProfile: Profile?, localAlias: String, @@ -553,8 +551,8 @@ fun ChatInfoLayout( verticalAlignment = Alignment.CenterVertically ) { SearchButton(modifier = Modifier.fillMaxWidth(0.25f), chat, contact, close, onSearchClicked) - AudioCallButton(modifier = Modifier.fillMaxWidth(0.33f), chat, contact) - VideoButton(modifier = Modifier.fillMaxWidth(0.5f), chat, contact) + AudioCallButton(modifier = Modifier.fillMaxWidth(0.33f), chat, contact, connStats) + VideoButton(modifier = Modifier.fillMaxWidth(0.5f), chat, contact, connStats) MuteButton(modifier = Modifier.fillMaxWidth(1f), chat, contact) } } @@ -825,12 +823,14 @@ fun MuteButton( fun AudioCallButton( modifier: Modifier, chat: Chat, - contact: Contact + contact: Contact, + connectionStats: MutableState ) { CallButton( modifier = modifier, chat, contact, + connectionStats, icon = painterResource(MR.images.ic_call), title = generalGetString(MR.strings.info_view_call_button), mediaType = CallMediaType.Audio @@ -841,12 +841,14 @@ fun AudioCallButton( fun VideoButton( modifier: Modifier, chat: Chat, - contact: Contact + contact: Contact, + connectionStats: MutableState ) { CallButton( modifier = modifier, chat, contact, + connectionStats, icon = painterResource(MR.images.ic_videocam), title = generalGetString(MR.strings.info_view_video_button), mediaType = CallMediaType.Video @@ -858,6 +860,7 @@ fun CallButton( modifier: Modifier, chat: Chat, contact: Contact, + connectionStats: MutableState, icon: Painter, title: String, mediaType: CallMediaType @@ -879,7 +882,23 @@ fun CallButton( disabledLook = !canCall, onClick = when { - canCall -> { { startChatCall(chat.remoteHostId, chat.chatInfo, mediaType) } } + canCall -> { { + val connStats = connectionStats.value + if (connStats != null) { + if (connStats.ratchetSyncState == RatchetSyncState.Ok) { + startChatCall(chat.remoteHostId, chat.chatInfo, mediaType) + } else if (connStats.ratchetSyncAllowed) { + showFixConnectionAlert(syncConnection = { + withBGApi { syncContactConnection(chat.remoteHostId, contact, connectionStats, force = false) } + }) + } else { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.cant_call_contact_alert_title), + generalGetString(MR.strings.encryption_renegotiation_in_progress) + ) + } + } + } } contact.nextSendGrpInv -> { { showCantCallContactSendMessageAlert() } } !contact.active -> { { showCantCallContactDeletedAlert() } } !contact.ready -> { { showCantCallContactConnectingAlert() } } @@ -1265,6 +1284,15 @@ fun showSyncConnectionForceAlert(syncConnectionForce: () -> Unit) { ) } +fun showFixConnectionAlert(syncConnection: () -> Unit) { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.sync_connection_question), + text = generalGetString(MR.strings.sync_connection_desc), + confirmText = generalGetString(MR.strings.sync_connection_confirm), + onConfirm = syncConnection, + ) +} + fun queueInfoText(info: Pair): String { val (rcvMsgInfo, qInfo) = info val msgInfo: String = if (rcvMsgInfo != null) json.encodeToString(rcvMsgInfo) else generalGetString(MR.strings.message_queue_info_none) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt index 7f0d5f088e..c9ac464438 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt @@ -8,8 +8,6 @@ import SectionSpacer import SectionTextFooter import SectionView import androidx.compose.desktop.ui.tooling.preview.Preview -import java.net.URI -import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.InlineTextContent import androidx.compose.foundation.text.appendInlineContent @@ -58,6 +56,19 @@ fun GroupMemberInfoView( val developerTools = chatModel.controller.appPrefs.developerTools.get() var progressIndicator by remember { mutableStateOf(false) } + fun syncMemberConnection() { + withBGApi { + val r = chatModel.controller.apiSyncGroupMemberRatchet(rhId, groupInfo.apiId, member.groupMemberId, force = false) + if (r != null) { + connStats.value = r.second + withChats { + updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) + } + close.invoke() + } + } + } + if (chat != null) { val newRole = remember { mutableStateOf(member.memberRole) } GroupMemberInfoLayout( @@ -78,19 +89,30 @@ fun GroupMemberInfoView( } }, createMemberContact = { - withBGApi { - progressIndicator = true - val memberContact = chatModel.controller.apiCreateMemberContact(rhId, groupInfo.apiId, member.groupMemberId) - if (memberContact != null) { - val memberChat = Chat(remoteHostId = rhId, ChatInfo.Direct(memberContact), chatItems = arrayListOf()) - withChats { - addChat(memberChat) - openLoadedChat(memberChat) + if (connectionStats != null) { + if (connectionStats.ratchetSyncState == RatchetSyncState.Ok) { + withBGApi { + progressIndicator = true + val memberContact = chatModel.controller.apiCreateMemberContact(rhId, groupInfo.apiId, member.groupMemberId) + if (memberContact != null) { + val memberChat = Chat(remoteHostId = rhId, ChatInfo.Direct(memberContact), chatItems = arrayListOf()) + withChats { + addChat(memberChat) + openLoadedChat(memberChat) + } + closeAll() + chatModel.setContactNetworkStatus(memberContact, NetworkStatus.Connected()) + } + progressIndicator = false } - closeAll() - chatModel.setContactNetworkStatus(memberContact, NetworkStatus.Connected()) + } else if (connectionStats.ratchetSyncAllowed) { + showFixConnectionAlert(syncConnection = { syncMemberConnection() }) + } else { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.cant_send_message_to_member_alert_title), + generalGetString(MR.strings.encryption_renegotiation_in_progress) + ) } - progressIndicator = false } }, connectViaAddress = { connReqUri -> @@ -149,16 +171,7 @@ fun GroupMemberInfoView( }) }, syncMemberConnection = { - withBGApi { - val r = chatModel.controller.apiSyncGroupMemberRatchet(rhId, groupInfo.apiId, member.groupMemberId, force = false) - if (r != null) { - connStats.value = r.second - withChats { - updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) - } - close.invoke() - } - } + syncMemberConnection() }, syncMemberConnectionForce = { showSyncConnectionForceAlert(syncConnectionForce = { @@ -335,9 +348,20 @@ fun GroupMemberInfoLayout( val knownChat = if (contactId != null) knownDirectChat(contactId) else null if (knownChat != null) { val (chat, contact) = knownChat + val knownContactConnectionStats: MutableState = remember { mutableStateOf(null) } + + LaunchedEffect(contact.contactId) { + withBGApi { + val contactInfo = chatModel.controller.apiContactInfo(chat.remoteHostId, chat.chatInfo.apiId) + if (contactInfo != null) { + knownContactConnectionStats.value = contactInfo.first + } + } + } + OpenChatButton(modifier = Modifier.fillMaxWidth(0.33f), onClick = { openDirectChat(contact.contactId) }) - AudioCallButton(modifier = Modifier.fillMaxWidth(0.5f), chat, contact) - VideoButton(modifier = Modifier.fillMaxWidth(1f), chat, contact) + AudioCallButton(modifier = Modifier.fillMaxWidth(0.5f), chat, contact, knownContactConnectionStats) + VideoButton(modifier = Modifier.fillMaxWidth(1f), chat, contact, knownContactConnectionStats) } else if (groupInfo.fullGroupPreferences.directMessages.on(groupInfo.membership)) { if (contactId != null) { OpenChatButton(modifier = Modifier.fillMaxWidth(0.33f), onClick = { openDirectChat(contactId) }) // legacy - only relevant for direct contacts created when joining group diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index ea824285e0..be885885d9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -524,6 +524,10 @@ Renegotiate encryption? The encryption is working and the new encryption agreement is not required. It may result in connection errors! Renegotiate + Fix connection? + Connection requires encryption renegotiation. + Fix + Encryption renegotiation in progress. View security code Verify security code From 2e573662d5ea486698d686c8b0cca77897c63871 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 10 Dec 2024 17:07:49 +0700 Subject: [PATCH 220/567] android, desktop: hiding counters while scrolling (#5363) * android, desktop: hiding counters while scrolling * change --- .../simplex/common/views/chat/ChatView.kt | 39 +++++++++++++++---- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 913ea87c98..983b5e9682 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -984,6 +984,7 @@ fun BoxScope.ChatItemsList( }) val maxHeight = remember { derivedStateOf { listState.value.layoutInfo.viewportEndOffset - topPaddingToContentPx.value } } val loadingMoreItems = remember { mutableStateOf(false) } + val animatedScrollingInProgress = remember { mutableStateOf(false) } val ignoreLoadingRequests = remember(remoteHostId) { mutableSetOf() } if (!loadingMoreItems.value) { PreloadItems(chatInfo.id, if (searchValueIsEmpty.value) ignoreLoadingRequests else mutableSetOf(), mergedItems, listState, ChatPagination.UNTIL_PRELOAD_COUNT) { chatId, pagination -> @@ -1004,7 +1005,7 @@ fun BoxScope.ChatItemsList( val chatInfoUpdated = rememberUpdatedState(chatInfo) val highlightedItems = remember { mutableStateOf(setOf()) } val scope = rememberCoroutineScope() - val scrollToItem: (Long) -> Unit = remember { scrollToItem(searchValue, loadingMoreItems, highlightedItems, chatInfoUpdated, maxHeight, scope, reversedChatItems, mergedItems, listState, loadMessages) } + val scrollToItem: (Long) -> Unit = remember { scrollToItem(searchValue, loadingMoreItems, animatedScrollingInProgress, highlightedItems, chatInfoUpdated, maxHeight, scope, reversedChatItems, mergedItems, listState, loadMessages) } val scrollToQuotedItemFromItem: (Long) -> Unit = remember { findQuotedItemFromItem(remoteHostIdUpdated, chatInfoUpdated, scope, scrollToItem) } LoadLastItems(loadingMoreItems, remoteHostId, chatInfo) @@ -1314,7 +1315,7 @@ fun BoxScope.ChatItemsList( } } } - FloatingButtons(loadingMoreItems, mergedItems, unreadCount, maxHeight, composeViewHeight, searchValue, markChatRead, listState) + FloatingButtons(loadingMoreItems, animatedScrollingInProgress, mergedItems, unreadCount, maxHeight, composeViewHeight, searchValue, markChatRead, listState) FloatingDate(Modifier.padding(top = 10.dp + topPaddingToContent(true)).align(Alignment.TopCenter), mergedItems, listState) LaunchedEffect(Unit) { @@ -1323,6 +1324,15 @@ fun BoxScope.ChatItemsList( chatViewScrollState.value = it } } + LaunchedEffect(Unit) { + snapshotFlow { listState.value.isScrollInProgress } + .filter { !it } + .collect { + if (animatedScrollingInProgress.value) { + animatedScrollingInProgress.value = false + } + } + } } @Composable @@ -1400,6 +1410,7 @@ private fun NotifyChatListOnFinishingComposition( @Composable fun BoxScope.FloatingButtons( loadingMoreItems: MutableState, + animatedScrollingInProgress: MutableState, mergedItems: State, unreadCount: State, maxHeight: State, @@ -1439,8 +1450,14 @@ fun BoxScope.FloatingButtons( bottomUnreadCount, showBottomButtonWithCounter, showBottomButtonWithArrow, + animatedScrollingInProgress, composeViewHeight, - onClick = { scope.launch { tryBlockAndSetLoadingMore(loadingMoreItems) { listState.value.animateScrollToItem(0) } } } + onClick = { + scope.launch { + animatedScrollingInProgress.value = true + tryBlockAndSetLoadingMore(loadingMoreItems) { listState.value.animateScrollToItem(0) } + } + } ) // Don't show top FAB if is in search if (searchValue.value.isNotEmpty()) return @@ -1451,11 +1468,15 @@ fun BoxScope.FloatingButtons( TopEndFloatingButton( Modifier.padding(end = DEFAULT_PADDING, top = 24.dp + topPaddingToContent(true)).align(Alignment.TopEnd), topUnreadCount, + animatedScrollingInProgress, onClick = { val index = mergedItems.value.items.indexOfLast { it.hasUnread() } if (index != -1) { // scroll to the top unread item - scope.launch { tryBlockAndSetLoadingMore(loadingMoreItems) { listState.value.animateScrollToItem(index + 1, -maxHeight.value) } } + scope.launch { + animatedScrollingInProgress.value = true + tryBlockAndSetLoadingMore(loadingMoreItems) { listState.value.animateScrollToItem(index + 1, -maxHeight.value) } + } } }, onLongClick = { showDropDown.value = true } @@ -1595,10 +1616,11 @@ fun MemberImage(member: GroupMember) { private fun TopEndFloatingButton( modifier: Modifier = Modifier, unreadCount: State, + animatedScrollingInProgress: State, onClick: () -> Unit, onLongClick: () -> Unit ) { - if (unreadCount.value > 0) { + if (remember { derivedStateOf { unreadCount.value > 0 && !animatedScrollingInProgress.value } }.value) { val interactionSource = interactionSourceWithDetection(onClick, onLongClick) FloatingActionButton( {}, // no action here @@ -1839,6 +1861,7 @@ private fun lastFullyVisibleIemInListState(topPaddingToContentPx: State, de private fun scrollToItem( searchValue: State, loadingMoreItems: MutableState, + animatedScrollingInProgress: MutableState, highlightedItems: MutableState>, chatInfo: State, maxHeight: State, @@ -1876,6 +1899,7 @@ private fun scrollToItem( highlightedItems.value = setOf(itemId) } else { withContext(scope.coroutineContext) { + animatedScrollingInProgress.value = true listState.value.animateScrollToItem(min(reversedChatItems.value.lastIndex, index + 1), -maxHeight.value) highlightedItems.value = setOf(itemId) } @@ -1937,10 +1961,11 @@ private fun BoxScope.BottomEndFloatingButton( unreadCount: State, showButtonWithCounter: State, showButtonWithArrow: State, + animatedScrollingInProgress: State, composeViewHeight: State, onClick: () -> Unit ) = when { - showButtonWithCounter.value -> { + showButtonWithCounter.value && !animatedScrollingInProgress.value -> { FloatingActionButton( onClick = onClick, elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp), @@ -1954,7 +1979,7 @@ private fun BoxScope.BottomEndFloatingButton( ) } } - showButtonWithArrow.value -> { + showButtonWithArrow.value && !animatedScrollingInProgress.value -> { FloatingActionButton( onClick = onClick, elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp), From 5e29cda27bc016f7b2087ca8ba78e334e0310603 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 10 Dec 2024 18:09:01 +0700 Subject: [PATCH 221/567] desktop: start using continuous runtime for AppImage again (#5366) --- scripts/desktop/make-appimage-linux.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/desktop/make-appimage-linux.sh b/scripts/desktop/make-appimage-linux.sh index 5084a0276d..6cc7aac011 100755 --- a/scripts/desktop/make-appimage-linux.sh +++ b/scripts/desktop/make-appimage-linux.sh @@ -40,10 +40,10 @@ if [ ! -f ../appimagetool-x86_64.AppImage ]; then wget --secure-protocol=TLSv1_3 https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage -O ../appimagetool-x86_64.AppImage chmod +x ../appimagetool-x86_64.AppImage fi -if [ ! -f ../runtime-fuse3-x86_64 ]; then - wget --secure-protocol=TLSv1_3 https://github.com/AppImage/type2-runtime/releases/download/old/runtime-fuse3-x86_64 -O ../runtime-fuse3-x86_64 - chmod +x ../runtime-fuse3-x86_64 +if [ ! -f ../runtime-x86_64 ]; then + wget --secure-protocol=TLSv1_3 https://github.com/AppImage/type2-runtime/releases/download/continuous/runtime-x86_64 -O ../runtime-x86_64 + chmod +x ../runtime-x86_64 fi -../appimagetool-x86_64.AppImage --runtime-file ../runtime-fuse3-x86_64 . +../appimagetool-x86_64.AppImage --runtime-file ../runtime-x86_64 . mv *imple*.AppImage ../../ From 6ff5f31bee2ca38b470430825e1ca086127ee6ac Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 10 Dec 2024 17:16:34 +0000 Subject: [PATCH 222/567] blog: v6.2 announcement (#5359) * blog: v6.2 announcement * update, images * update * readme * correction * update --- README.md | 12 +-- ...0404-simplex-chat-instant-notifications.md | 2 +- ...ork-v6-2-servers-by-flux-business-chats.md | 85 +++++++++++++++--- blog/README.md | 11 ++- blog/images/20241210-business.png | Bin 0 -> 371054 bytes blog/images/20241210-operators-1.png | Bin 0 -> 229344 bytes blog/images/20241210-operators-2.png | Bin 0 -> 263209 bytes blog/images/20241210-reactions.png | Bin 0 -> 481860 bytes .../src/_includes/blog_previews/20241125.html | 2 - .../src/_includes/blog_previews/20241210.html | 8 ++ 10 files changed, 98 insertions(+), 22 deletions(-) create mode 100644 blog/images/20241210-business.png create mode 100644 blog/images/20241210-operators-1.png create mode 100644 blog/images/20241210-operators-2.png create mode 100644 blog/images/20241210-reactions.png create mode 100644 website/src/_includes/blog_previews/20241210.html diff --git a/README.md b/README.md index 52f753a5ab..ad70c350e4 100644 --- a/README.md +++ b/README.md @@ -233,7 +233,7 @@ You can use SimpleX with your own servers and still communicate with people usin Recent and important updates: -[Nov 25, 2025. Servers operated by Flux - true privacy and decentralization for all users](./20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.md) +[Dec 10, 2024. SimpleX network: preset servers operated by Flux, business chats and more with v6.2 of the apps](./20241210-simplex-network-v6-2-servers-by-flux-business-chats.md) [Oct 14, 2024. SimpleX network: security review of protocols design by Trail of Bits, v6.1 released with better calls and user experience.](./blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md) @@ -243,20 +243,14 @@ Recent and important updates: [Mar 14, 2024. SimpleX Chat v5.6 beta: adding quantum resistance to Signal double ratchet algorithm.](./blog/20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md) -[Jan 24, 2024. SimpleX Chat: free infrastructure from Linode, v5.5 released with private notes, group history and a simpler UX to connect.](./blog/20240124-simplex-chat-infrastructure-costs-v5-5-simplex-ux-private-notes-group-history.md) - [Nov 25, 2023. SimpleX Chat v5.4 released: link mobile and desktop apps via quantum resistant protocol, and much better groups](./blog/20231125-simplex-chat-v5-4-link-mobile-desktop-quantum-resistant-better-groups.md). -[Sep 25, 2023. SimpleX Chat v5.3 released: desktop app, local file encryption, improved groups and directory service](./blog/20230925-simplex-chat-v5-3-desktop-app-local-file-encryption-directory-service.md). - [Apr 22, 2023. SimpleX Chat: vision and funding, v5.0 released with videos and files up to 1gb](./blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.md). [Mar 1, 2023. SimpleX File Transfer Protocol – send large files efficiently, privately and securely, soon to be integrated into SimpleX Chat apps.](./blog/20230301-simplex-file-transfer-protocol.md). [Nov 8, 2022. Security audit by Trail of Bits, the new website and v4.2 released](./blog/20221108-simplex-chat-v4.2-security-audit-new-website.md). -[Sep 28, 2022. v4.0: encrypted local chat database and many other changes](./blog/20220928-simplex-chat-v4-encrypted-database.md). - [All updates](./blog) ## :zap: Quick installation of a terminal app @@ -384,9 +378,11 @@ Please also join [#simplex-devs](https://simplex.chat/contact#/?v=1-2&smp=smp%3A - ✅ Improve sending videos (including encryption of locally stored videos). - ✅ Post-quantum resistant key exchange in double ratchet protocol. - ✅ Message delivery relay for senders (to conceal IP address from the recipients' servers and to reduce the traffic). +- ✅ Support multiple network operators in the app. +- 🏗 Large groups, communities and public channels. +- 🏗 Short links to connect and join groups. - 🏗 Improve stability and reduce battery usage. - 🏗 Improve experience for the new users. -- 🏗 Large groups, communities and public channels. - Privacy & security slider - a simple way to set all settings at once. - SMP queue redundancy and rotation (manual is supported). - Include optional message into connection request sent via contact address. diff --git a/blog/20220404-simplex-chat-instant-notifications.md b/blog/20220404-simplex-chat-instant-notifications.md index ce7dfd613c..7d88a47fa7 100644 --- a/blog/20220404-simplex-chat-instant-notifications.md +++ b/blog/20220404-simplex-chat-instant-notifications.md @@ -68,7 +68,7 @@ So, for Android we can now deliver instant message notifications without comprom Please let us know what needs to be improved - it's only the first version of instant notifications for Android! -## Our iOS approach has one trade-off +## iOS notifications require a server iOS is much more protective of what apps are allowed to run on the devices, and the solution that worked on Android is not viable on iOS. diff --git a/blog/20241210-simplex-network-v6-2-servers-by-flux-business-chats.md b/blog/20241210-simplex-network-v6-2-servers-by-flux-business-chats.md index 55de82df47..8f6f445414 100644 --- a/blog/20241210-simplex-network-v6-2-servers-by-flux-business-chats.md +++ b/blog/20241210-simplex-network-v6-2-servers-by-flux-business-chats.md @@ -1,23 +1,88 @@ --- layout: layouts/article.html -title: "Servers operated by Flux - true privacy and decentralization for all users" +title: "SimpleX network: preset servers operated by Flux, business chats and more with v6.2 of the apps" date: 2024-12-10 -# previewBody: blog_previews/20241210.html -# image: images/simplexonflux.png -# imageWide: true -draft: true +previewBody: blog_previews/20241210.html +image: images/20241210-operators.png permalink: "/blog/20241210-simplex-network-v6-2-servers-by-flux-business-chats.html" --- # SimpleX network: preset servers operated by Flux, business chats and more with v6.2 of the apps -**Will be published:** Dec 10, 2024 +**Published:** Dec 10, 2024 -This is a placeholder page for the upcoming v6.2 release announcement! +What's new in v6.2: -- Preset servers are now operated by two companies - SimpleX Chat and Flux. Read [this post](./20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.md). -- Business chats to provide support from your business to users of SimpleX network. Read [this page](../docs/BUSINESS.md). -- and more! +- [SimpleX Chat and Flux](#simplex-chat-and-flux-improve-metadata-privacy-in-simplex-network) improve metadata privacy in SimpleX network. +- [Business chats](#business-chats) to provide support from your business to users of SimpleX network. +- [Better user experience](#better-user-experience): open on the first unread, jump to quoted messages, see who reacted. +- [Improving notifications in iOS app](#improving-notifications-in-ios-app). + +## What's new in v6.2 + +### SimpleX Chat and Flux improve metadata privacy in SimpleX network + + + +SimpleX Chat and [Flux](https://runonflux.com) (Influx Technology Limited) made an agreement to include messaging and file servers operated by Flux into the app. + +SimpleX network is decentralized by design, but in the users of the previous app versions had to find other servers online or host servers themselves to use any other servers than operated by us. + +Now all users can choose between servers of two companies, use both of them, and continue using any other servers they host or available online. + +To use Flux servers enable them when the app offers it, or at any point later via Network & servers settings in the app. + +When both SimpleX Chat and Flux servers are enabled, the app will use servers of both operators in each connection to receive messages and for [private message routing](./20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md), increasing metadata privacy for all users. + +Read more about why SimpleX network benefits from multiple operators in [our previous post](./20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.md). + +You can also read about our plan [how network operators will make money](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/rfcs/2024-04-26-commercial-model.md), while continuing to protect users privacy, based on network design rather than on trust to operators, and without any cryptocurrency emission. + +### Business chats + + + +We use SimpleX Chat to provide support to SimpleX Chat users, and we also see some other companies offering SimpleX Chat as a support channel. + +One of the problem of providing support via general purpose messengers is that the customers don't see who they talk to, as they can in all dedicated support systems. + +It is not possible in most messengers, including SimpleX Chat prior to v6.2 - every new customer joins a one-to-one conversation, where the customers see that they talk to a company, not knowing who they talk to, and if it's a bot or a human. + +The new business chats in SimpleX Chat solve this problem: to use them enable the toggle under the contact address in your chat profile. It is safe to do, and you can always toggle it off, if needed - the address itself does not change. + +Once you do it, the app will be creating a new business chat with each connecting customer where multiple people can participate. Business chat is a hybrid of one-to-one and group conversation. In the list of chats you will see customer names and avatars, and the customer will see your business name and avatar, like with one-to-one conversations. But inside it works as a group, allowing customer to see who sent the message, and allowing you to add other participants from the business side, for delegation and escalation of customer questions. + +This can be done manually, or you can automate these conversations using bots that can answer some customer questions and then add a human to the conversation when appropriate or requested by the customer. We will be offering more bot-related features to the app and a simpler way to program bots very soon - watch our announcements. + +### Better user experience + + + +**Chat navigation** + +This has been a long-standing complaint from the users: *why does the app opens conversations on the last message, and not on the first unread message*? + +Android and desktop apps now open the chat on the first unread message. It will soon be done in the iOS app too. + +Also, the app can scroll to the replied message anywhere in the conversation (when you tap it), even if it was sent a very long time ago. + +**See who reacted!** + +This is a small but important change - you can now see who reacted to your messages! + +### Improving notifications in iOS app + +iOS notifications in a decentralized network is a complex problems. We [support iOS notifications](./20220404-simplex-chat-instant-notifications.md#ios-notifications-require-a-server) from early versions of the app, focussing on preserving privacy as much as possible. But the reliability of notifications was not good enough. + +We solved several problems of notification delivery in this release: +- messaging servers no longer lose notifications while notification servers are restarted. +- Apple can drop notifications while your device is offline - about 15-20% of notifications are dropped because of it. The servers and the new version of the app work around this problem by delivering several last notifications, to show notifications correctly even when Apple drops them. + +With these changes the iOS notifications remained as private and secure as before. The notifications only contain metadata, without the actual messages, and even the metadata is end-to-end encrypted between SimpleX notification servers and the client device, inaccessible to Apple push notification servers. + +There are two remaining problems we will solve soon: +- iOS only allows to use 25mb of device memory when processing notifications in the background. This limit didn't change for many years, and it is challenging for decentralized design. If the app uses more memory, iOS kills it and the notification is not shown – approximately 10% of notifications can be lost because of that. +- for notifications to work, the app communicates with the notification server. If the user puts the app in background too quickly, the app may fail to enable notification for the new contacts. We plan to change clients and servers to delegate this task to messaging servers, to remove the need for this additional communication entirely, without any impact on privacy and security. This will happen early next year. ## SimpleX network diff --git a/blog/README.md b/blog/README.md index 97ccffda9a..1432d95de5 100644 --- a/blog/README.md +++ b/blog/README.md @@ -1,6 +1,15 @@ # Blog -Nov 25, 2025 [Servers operated by Flux - true privacy and decentralization for all users](./20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.md) +Dec 10, 2024 [SimpleX network: preset servers operated by Flux, business chats and more with v6.2 of the apps](./20241210-simplex-network-v6-2-servers-by-flux-business-chats.md) + +- SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app to improve metadata privacy in SimpleX network. +- Business chats for better privacy and support of your customers. +- Better user experience: open on the first unread, jump to quoted messages, see who reacted. +- Improving notifications in iOS app. + +-- + +Nov 25, 2024 [Servers operated by Flux - true privacy and decentralization for all users](./20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.md) - Welcome, Flux - the new servers in v6.2-beta.1! - What's the problem? diff --git a/blog/images/20241210-business.png b/blog/images/20241210-business.png new file mode 100644 index 0000000000000000000000000000000000000000..835070e26459554cd87ba1b1ec3a542680edaf9b GIT binary patch literal 371054 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

    uJ~n2Jx)*(hM~;-MzLqG*BhU_CS|M2tm0})FQ7$ z>k!`x{!)!@INVHlC-~88F6ze@5=?jx$Rrw<-@d&IN@5g#1~;KD*0ReaK@R^p*v1Wk z1COzDxh~uk*hGrpE3K`qO`34Nw@_ah-?J=~vYNk!t;{L#V(SyqX5jnp=qek?3RHR6 z93E^<31=&9RUw#n-@bhJGgDQ4$s#JMpOu)Ro|us-P;N~XONgaRgtx*j2HHs zjc3mN5{~kng-0{e;Nj}K3az8i$>uw^;rLIH8?zurC}yXSLO)T#7BZZ8w93JDpKSsK zCLyZ|VkYN=FjY41y&nwj6E0YMn98m2Fg_% z2;j4jmB`4~BvWqUo-mgs#tK?|i@B<^9mu-vk}=T6idc!ZB6>dXz~X{L^a+_Gnz47V zpmsB{RJuZc$|vKw>J^tfiORHGtrsnzQsyhMRAv(S-c(%(<`c5@5Ob|#wouh7nP46e zGTw3GTi+yvqg4n6NfaR1Y$cF%HEKnxA)TXtT}4qXn_#u1C&8BCG3-M;aq&^A#zTmF zaze$Z#8RS;AAq%>W%d_nmh5O+(`DSI3k&*SmB5sMedJ$E$=~r1H6U$RNr&hM`M0SLR&;p}~(}y?_ zFBx$|)r|#+xDX5cxq)~XSXfx;L3ei&Z7s?HCT-h0JG?D^ex;S9VwJg6mknDNXX$tT zYw&Tv&;UaDh-cz0q@iahh#e#eYSeL04Co3D6YuWE3R+v$9?^e8oCC|E>QtnEX|*2C zl;lD@p5;frq0xF7UmbMj>U1lZ-(q#k>_T*?r-qisNlz@y!pf=?_pY8}6b*?z0?#-r zG1cEi|5-jpP8}M`<3=r@#vIo2+}ip^MsP3!H!|eAtZYMc3_knw6{uc=siIjOjktJ1 zS{}sJ6^eNO_7a{o(gwK#n9CgO^Xyr;0%?#iHij*$8+3^Po{}&a4PmOkK-n%|p@A5O ztcG~pB+vk=)c9(l?-KRv==HZYKC__bK=WfwY=O43He*{W&!$n?i>)r+0c0HN9C z(mb5a@(JF>QOVx%tDWL)jLK~c)(1}xi!PreSn1{~=1r8ik6oJ4KA=@&`BWA&dCI*U zvY}2%ud{kv%$=j+oq)mcG1S&v&&10 z$gA-uX{+%U5#OwEh|gEaG1#BZ%b(<;`|0n`t$}(A8eVM2e<2Kxq%qHiRb$KlH zc(LcmPGmDXNt6*bUW8p(ZX<&1sA8(m_wLJ_nl^}|a>9Y>=}-z~wO8W_(z9oDb%fK5 z?V3JR$SS;91$rcdk>OHA^R#Zb63zz)+}{w1CYCSGC(R$Ybgv)cC{rRcWdFOb1amL?;`B_;t2{} z7UI>_!J_xKv%Xy4V});n`;`&;i0Wz=I@7qTotp`W^Hif|iEo+W5eI!dFg(ITaS#id z>gxG<_=WlXn2|Qawma%rkIWndkq(1TO{XkvmXi#OgP7Tw`5tSQWtM9y6-}0cVp3C6 zWbu2yX<@9wqbMA{+iQd;Hm4#bc-rDpefl;r9sb(ag zz22&G20cs`6{OqFbvxRlpHuj}#wggch^r_S+B;rG_z8)GT70Frh6gnu@P)Rx=OM4Z?5P~&k}5n7q2II zjgP#xG3YGdoL;;DFyA#(%_Di0`b3Hlb-%5~7!dO(mD5&p~b&PCuO z+m%Dk3T8wMxxDTHuQgfy(eD&;%R$|bZfQ;E6g11urhPydrvAZB=)Om}_;3}C87-R} z?-mo$q>J$ac)l+`JnrU*GA|~zX(JeFW*KDB^M7oT0FEv)NqQ6St^-zc?nyR!b$aaF zV=R@QC|4ptimTfpIzRatG4>7}x*}s!*YZa6S@T&sxW^sq{6sFE3j6*0_b74o?=#=8 z_JJ6eo;|xcEP;boqvs;wo=Yn|dph5T6epKE!+Rs>eelnzg9Zr@>7p zqwuH>F~X?GjdY=i@9S&(`Y2lIlhe{R-@W%ROwD-EEXL1qGgtwucw8l%0so<Pz56v{RY|Q*I^={5>oRpz)%{ekpO6|Aj1#CWzj3-E2+^AI z(H@H-2wV%|qRpDYcpRS=NQ=>+hewx?4}PvwR$@cA%~7Umax`67|BK4=QB4{6b# z-ttjt^y*IEtVVrwHS(rnl|$f**wE?iJpx*462IO^xD2I$e<$v5YI2&Nk6KoTUJ^Zd z0zeB$B6yL=9NDY%ghQj&F7se9br{Y41wMb|qzcUdU(z?(no5y?53ymQc#dr6%v(9B z1y-ep#BJ9KG}W#m!E8@V7Eo^U-xJ3?sv+XJ9HjG)d%#K3J$M9dNkJuu2Yp?2r1#o?Z9YYb2ieK_Vdd}kI`QcUN9p^45H?sT9&UXhnOyfEbiylH+_(%I6f|Q z+}f=W&+R5F1F4$#H#Wr9mqFF%!wgWaU?{nkCn@}*+}y54l9D5i85!I4R+h!Xu+VLJ zCy&ygfvECy&PiCV(Zli_O^OWO4?+U9H7sgEj^rRM1ii~oD1as`xH_Yqy=}f(b>zI( zL%z>*1N?TAv2pXLKD-z!yUn-HpA36-7Q+o%y0!;)3`mfNl5VU{piB8Z^h)O=SLr*f zb&M=l{fb<;QioHjW|m&)Oiud2W`3!nEEb;}?J;2}a+<)RPxD9kh4|;|eaFwxyIAwo z1}N(;-W$@LOsKaCVS`&z_&}<%MWxv5N(#nC;lYzU%wOcvx((<%Ih2~@+N?FamO1SR zDxZfPUcx90#1WWqICev-GhX=LZ?IIOHyvHc4?Z%3jJoxk~}jsK#&W3S{0-x9Ux@9IazDpS3SbOTJJY|7YM$CBGlU3 z1#`c)&X|~(knryya!7o^9wT8IV2unx`|@zl5#`)w;)p37%85=Iby#o=GTQ5Sr+HEU zbm#-BlGQzu?>srirBXh`6}jVC(b$X_7dYdMPu@$}9DVKVpXaTDx*DO1V{hEEdOkF)|5m2DiSmnq*0rN!#xm^9Mb!xL3h#F# z<+hq+Z3hASiXW~qHv3A34HsTiNOKiQ83lnnfD2+v!rAzb7eb&WtlUPzYopJ>6wpxxmOut zI)KjUzH?cc6x$nn+GJGyfMJ{MO`~R~Q1wamR$Cy0*J z3Wb=OZdoVJ2K)FU?6ZxfAjEOR8}aA63$=`UV;vzuMSc3pkN~m`j&XLhZ37{pCgX$s z{Q#GN7s!zP+3KP%MX_sT369NLn>oCww|lDP?vh_cZU>fFq@ko1 zDYwVyWiN!x%8R=V$ec;QSb!Qah25=wPq=(4;%pDrNgho*HUVNSQ=H1YLW)c*d-5A z64l;6j%Yi&<%0>l7yN#rxyS{|$>WPv+Pm3O#HOjHN+Gbu5t^P;eUH8H;=y}^I()9w zF3g*1J-%)lk!;wy=lrMxWdWhfrI{+}dm@~0g6GFHj{46p?i6uGJO;_c10to2T$=%; z<$;NrJ#;*Bu6}2W*U_p45$AhKKw$nX`c&yPhXC&9p$<+1Y~_nW()_vP{2OfMn0iJ+rZYKHYu7e)Vw@z zM0;GFcUfEGu0%fMcRPIqOo4b?Hx_TNML5k~yIXzodz0^s z(b}DsZeAg|YK`~2C^eS>*cbP(M<=*zyoK5qZ?W3Z!1Q9@2pk(Lr_7)1*WyRpTH$c8 zD|EH9xZlt5eEZ9~g%pN&U)tATn{IgU6Px5ot7B-Oud-K2H=J5{%E#9+F__}1Sy_Il zFfG&S7gnZm<|J%}WX4Ty<$4Xa3RO+-`GD{0rYwtXI#nvZxS79u$F^o`Cu0Ln45O-x zEkZGL7^kUP80}!=s_tU&G{m2mHLVY!o7^h|u{%|tuhOPBsP&hrCXv|@lWn|mnNO~D zaN=GMP_oH>$6xQ&$eZ4?7ez@Eix@qTB$CY7N2rZzEw1^^%s^8+qrmI)Hy~G_Px+nK z@N&Eo4lZ_*Zf^S^?&>xVzh9G=udl|qKq+fBY79Ml=?h(S-C4~e8Qu1Zelt|t#tz&Q z{FrNU>i8Zt9JGKdj4q;1us;Qa`N-6{$m&LEtm;~3?REGtrH>O8`_Zjix9bhO3O*G& zoBHK4@9u>o=URQ6KS34|<0muiQT)a4*#wPcYNx!KTu29+f?+oLBB~W^GCj~|wDi|d z1r_?9V|t=4k%2LEI9KUNFTs$>Bnu=eao;;P^q3DVE4L!nO{9*?ExyGWK4LrO)(S|E zUp%K?*cUa;iZl^t_%zrxF2ey>4s!XVVHjF_c`2Np6x~<)HHV0qSdqm5?uDsE#tQ_P zU5tu68PQ(+Vdv42)yoSX_ztciZawQ|*kR_(PM8XEz?W%u3FBGYk0osXa0HPl>bnO3*9pj6fi z303r7QN}__Yqzj(bB&rA@2De=pJxH&DR~yuid`O(ZEo_UzqOW10t*vO4RE8669F;dsm)Kz*SleUd3i_-Mr#@GpRa5>yAF&_i;6l9XA zS~*I3c?$MY7@=TBU3p_yEcj4FP~uLV<^tR|EGsCe7cWKA#>mfD#wE-R;#nO^GojS? z-+tWDqm+tbtuuqvV2=R;skAfP+Cr1Sy`9geQ;WIyp_i5Zh^*GA~dSJ3OjjPKv!Ox}7Ebn$(>!*DH` zDq6_f$HiPWMmkoo@_KzlUY2}#^;O0UT<0!{Gfd|Vz&?86K)1tOKvofE+I&cIM3Q$r zaGgq3f}Brc=!>qnY}~})(?Brz2#t znQ$-*X}5+|H##WXIb#(qkxVVd$EsZ;s}vrF40>-p0K_SfZ>}Seg#z&y>Sz`SP^i0#o-lejnPRCS+u{Gmfb4Br)sb`Y%jV* znc>JK;lOo)OLmRw8#=f^Cc)Q09%d3#ByeSGyWP&7LAjRBq#RNXGJr`Xs3Of|J1Dj3 zu#ejr6{DHb7!RQu#1YT8aL%X29~RP|c$FaV??`oIVG3Ruaxo=HlWmHU23T7xGlDda zU0r(79QvRVgq9ddF+P+$LUG6gW_G#F&ic`IbWzOuDZ`3_<2c|Ot6Df% za#@0}d3@h-u(1(kHt?=4OQR0&8wo!$;D^LH_DN$jV6Y|(x8I}*TeR?^4+E0 zI916Uum{_Coa}ODum|blA#vx5QH`Gr zUM@po#RQI@93yDwTgj~9t%69zzL1!@%u7Z#ev6BAlq9jSyHA0$7B(!JhaCgHr4x4D zxz@>0hrWnpdLSa|M(${zi8Q)E-+D=-fl6%lb*fu89;IMJwAyM;gcbu~6tKRX zxRoDq8$s-iz+Sh-fo}7Zu8`QOy8SeFu0xmdv7vbm&-4j$Mjz`+z6E7Yoz=k;%qsND zSexVooz^$u@nju@s51`JGs?kJ(oq6hOyQGC1f#6?o5NL2ti|{zC)H^8rvkvr8kL3Q z(#aWDLQ{)%mcRt2xjGb1agu~JU5cr`vR0TX+wfiAoOP{tMg}FMsG1(o6%(0Qc9eVI z!iisxFj@A9EvNUUONf%TcA^Ul~TEcGB4J)FLCr*xt0rVdo3FzG+@EfFL3dK?k)citO~&oAEr4t zsMsU(Se%0tQ^I>)vTF3yxpy5sBO)l~%wp~tx_`G`kXW$qgRy+ltOu7(CPAOSV7TC5 z?DVuuihru-zrOo$@MO`?rfBY4$9~=PG#}H=^>EbN{WJrE`0o&hxmzhEB}`R@l2vnc zrMgc|dCRU6^Q+E553}n=)59q-xiRA&3ZGxyzhp*{M-rVbJiCQ`v%HA5_I zDMG{)Q*A#;1|qxKbskM)Xr{w&KR-rcznq;D=r>i8nJu|6kCAag?lCfG%9j-DuNXb6L+URSnuaMS}@l%ny?MTsqEM&{mIQR zOrR>^$WWjO+IPNHTK^@k`DCPbsi3Jz0@q<&)h*{Xf7^DaZNt*)8yZFzN<6hnh*)Ph zep=>}^dkhM&4oPM{xmkd7mYSEZ{?yDm0Gn8wbT%?8#9ca61QB>KTUjH*~lOeFuu}0 z^+JPkog&D(xr6f4ciP)$dX^n=mSHXn+H;bZBo&ZpUrlRD;U|npTN@iirRX{qn%q3A zB5ZJwQM&epZ%T8GYu&(Ii@}xwFu62^yQFZp^u)22?ley3E@4WSwQj(-&!-XLXP38_C{GAUo*?1B6{gXGnvADfyeTax7-$c~%-o|&3JD`W z2k>JpQ*N+~WuD-{cb2%axN(C}DXz)S`^p9nR(^Wok7sW@B1;7;G9A!|13!~5`B02F zMz}0hg&4qp!LJ%prQ*eMP(#zM3Sj0*;bw(lp?xh0WDOp`SY{H}g=%Lu&sJU(2OWpl z&IJ|0>0iE@<1MwDc9>f(p*C5^-CeNfYdy2J?7BW1-!rH$e%Phs=Q|+odb)KKfEC6S zV5+>EJf8kSb7$jL21lSnB$4_~*xrP|r2P<1S-Vy2U9qjPzf)@CWnoO$%x7n5)AbY` zB>PPmx88%f(vp(7-S5tM<$dPpRHt3_Dy@kVq zm#IDoBtNv20|_cSTPa!FecXNt7!BuZaaL^2;Khs}AIU@>zINk`lGF$uh)Fd=zQC(H znJaa87FPZ?2T?Qa0iGLOjjr3I@PW)sODjXO%WCiG`CF&S>BXq{?|?6j%S{w_C_6{C z-V$@%)+EY8*)h;|^bHqd+enduR705dl8J$q)*+}_eNoLY!G?rQ7q4}>Gz($oi_}Fbq_Rmxnqt2C{l)AN+ZJX-OcTQjJ#MFSFm35Vei9OOIueR=0~B zZsD=@5N((iN~g>Wn@JN|@HLwz*E?O0kR#0PWJqM%F}5mk8EcOd`w0nKsOYnj*$>oU z0SCSc0}Ai;lC;8w_l`=!I6aRl;NDZ(gKrMtzQ41qsY&IA9HkfJ62ejlr^_VX0z8d9 ztk3ybty4dXKNjzUy?KLO*}9(E{%Qw;He%8sudGI1u)X-fhM#YvvWr9+kpVF;V%Bs{ zmzk$_S%b#LMgXNYDJ0^n^FtD|?5y*XgaS2N37!?|-77fnmF|}`ZyU1ls5WCUf4znG z{5c{^v8IYb$NG@~FzDq`x=4R^Pwyv5!-<$5KX;|-5i@~8X$@75(BYUPSyu6h{ zk{pEfDX>+k^@!nY;V8+C``hY;Du1KXby8!rgV{#zZ%7Vf1zx@-uU~JD!rhhXAhzxD z)+mCcY#GxRA{50g#b5h278dRL#$;?v?&<8w2f*6NuD^GcqyINqqBtzpkZ7QGr3A2yAQINi|>kWm-*^#|DxGBf81<3lie&Rg zD+1DoAlru8p(%}QhgQ?X^3aDuDM?!9t=AJu%cOY$V$PTSiKaV8IMB<(M8@ZON7xp8 zE&+ppZpco0aqV;dNl7)#SRROG}0k&n~z_OIeLCd^tgJegBp#g1hO~4 zRMz&$M5NyDIS(64m=x(@Owg>_xGVWIafny9>LV^Hx*PsXQ>?vG%~ z&Tn<7P}whg3P@{AN19%zW-mjAUu7PBQB=f0gxcG8Qa5hqw#UAP;EcR_#mnfU5-l;p z-|A%hp|J~t)MGxWvw=8K)Hv;YS=e)*@56ruH zaKj8<&R-V?e_S90(h5lEh1JA570DMi@LGUCfV!=#8e1O31t5F6PRuYcwPVdH)4{MH zf?NGbA*LB2=1O@*rXvZPaj@UVnBnnMhS|I}D!=QQHgw2Bwdgl5InuS_>pUzhnbv4F zk$k526jUyTw);}VusQRW?>F8xzhwG;!_C6-7LcWH`PRtY=rY&m0A%1lfFVd%kAo>x z@8~^R_ntC@IofEdoPpKBLB{3QD{HRyLi%KJ!x3+Ga-HdOkz5vRW7r_U>7iN1=y#G= z;1N$s=U+;CB;{D;%~bH~54KmX+%1Jt6{y!YKEu&5X&F6AB$5&wVbdZY9NA*$N^&=h z-A{4&o6YHhCnTmfy*#x(eS9ad1AkKQvyW*Yi<4@a5&OBBiHURKEOl_De9Jk#kw7c;lp%ejls-H+aizFe+{IoPWZO`;mSL=lg&|X zZnL*igr6m7>^4Tr`O*ws@nJ?R-CPnWi7?hwgepl6KnOGJg_Sjfhcmd3R(5CREoYnG zSDk`kgZCx%@7*&IQNP}8-rp5$eGg8W%{6f@1(0T%zQy%Yd}7@zK3tyR9-weU1pWN# z>GF-3d%(PG+S|Zbud&^4ru=nl2U$*(o~nRGJ|>L$ZJ_kFkoSbcm+Hyp58&4WofI^W zFQg-IGFp5t6s4ci8dDU@G*SNDVtoX2EqKP!5G8F*l8@a5m@%#*I+3iggw;Tt!E$#R= zmV8w^4->TDMlt#KArY*stp4{SG5=aJlom#HJyEpnx%h2dLg9$V!Kh)Oxy3(65JQvb zyZ@Sl^C!O&Dul}uj#l$?Ki!BY={Ve}Bq!5ZkUB5`SI=$BIYv zhtH|Os}C*>%#C*>iHRv&u{>d@_1nbJOvsDqDVkKq6P^k)9U*9^Ms{<0=~7&9zjmXklqh2e2H2 zlQ)A8tbz=jj34@MjdPTkczneb=@-U}L`8Zq{=(DUJ%j(tysfcb zEj)tH`|a&*KKtnkPLaJy^tGh-CJoWQuAo>+KL&s?ys=?@vK(>aTw~ifFq&t+=m&Yd zZ7a?`f5ME6oKU?h`CPJ{3j&?QpNwxF56_00Zfa~)Z-`j;oZ zc3a{?Ot>nZ@akATWw%?<1D6!3^iKQi+Y1+ zlFeU#KHtb&D{bPnwY=1lcd6w(KecWBJxFu|gtopn>yv%H>gJ%vV{or-&YfupaOcac zWps2jNK>=O`((J@a-4B8M}mFVRs5&_|NaaSknoNsP09V7P>w?>VCq`U4(WZKVcoai zQhL;nuLp`8~3z?^bD+ z(~Hjr&wQF-0DdD#Dw`hL)2DjRZ?}47Bm+`Q=er#Nk7qxv@v>`@y-^1w|{du>BKaZ zJ=aG2iyPwQi^qH6u4oU}<2BgGT27ByK?>tqwLTYoXq)0VZO32j!2$_r3tBS)B&Onh zJ@w*tAjY0THPwr#ui4+@aKpb28C0JPYI3s<9=~ndZyGxgy)?a^C`rqVBi!4_;h)L6 zZW5|Ck=jW`QJG908Bwtvn8|9owyvNb2@7mh&?e!I%~|pNipJuWSI#V{c(}rZXr2*tmp4*aC=xg{D@#Sbwfyo&^*4*NJTgM)a|s8 zVDI;Cbe6MQ*(<4JeZOyocp`I9f*6Xd6u_f%Zf-6R=X=UEUlr2MC&Vp&Ex}pqFAv4c zyH^uBDn;`UZjS6LTS!XT{Yo2vqn|u>?6@B~M!M5ca)e{Td7>x3CZ2o^D`>(UZu_>Q z+V%Yk#tk0J&ceNz`6l-2sLlDxbBQ7%GuAi}{djb%Ky))*OVj_LLXZ2qMeixNV*&Dk z_jV4IN;yjyHAciBYj}ZTI%C?RubVzBn2)2ig|B^NyK&QmFNnr*=)-W6`%dU3mhB5v znq*Z=Ioj+HT4B};v=W-d$w^keBkA#B{*QAXYeCyn8siRXAJn5Okz{3Mtp}5tJ4x-v zqp}bLP?3)n_woB&-K)dX>+??x<>MST>2WfIo)Lv~M%=(~zptCX>?>RCj;ln8u3T2S zqcEc$zJ2>+_8=8Nt;lgETQ#pQ$mo>D6%Ebd-55Ogq9kQDHIk2mhT64%EDIcnG0FAb z77epo)RdfETx2%@p0HLJi!?iLj^s$!#B)eYb)QtE?C(1rMQOg9l(wcSlWBY0`BcX! z?6ZEU`S2GP=!KjUZwzfwg8y+Ml*L=-YNL>t6?C@SSXx~^a(goxK;`{a{O-~0>_qh5 zTiiSNS4ZDhGKo5`EVlqG$(ovXSTLQ5gQk!?2GnVTkF#CeK?%AX5DtcRxG6dGB}t?-Uhf~{9!eg654jouSi8h$J=>1$pc zBgc*5y!fOBJF7G3gRki(2^^RJfyiSGc!My97MqrVGD~s65d^gYN3(VRP;rMqo7@GK z#Zh3h_=dodJ?1g@w+eO_iyeGqW!-QMTT!PhF)!_%P(mK>f~d)8vl%zf!t&bSZgOX` z79fp_hOS3FoUI8qYp9+>2L9WM+@2!{&|R7r#G(>u1kAJ%3?!r=e$y$18=;p&g zXRhz+O(_y$zcw4nn@R{?M-Bu`*i+4^p#-AM^zhj={nY&BfP~%9mfgsuym75i2^JiN zDT8RNt<5lhP*LHqRt9RUC#DbWXMzD|+qW0QO)Z+yR!+aoI_i=u+}Z%!k;#iVSgzfd z_v*iJ%@{)8IER>U3g8L-zD>GM*q5j``5bg1ZyQz=$B{I<6w0KwEZ=;8@DUTrwz+62 z*BGiHFI@YhV12PL>i&Ed=0>m(nD@i!W ztSrCZMA})&>TMW#etzJ;w9HZZbfkhWVh7he{&)t&;gB{_WmD?6P;-|#Gn0XPP9^nd zn2)3RFj!3$J{{Y=`JwP*3g&lsyE3Tvw}5E2U%js6du^KJJ~uc7d!E6K<(LHymb%V} zXTR&+i(2IDSm0NCrYhEc1aBqexk0(K)EhGzv$s7We84lMJ%-{}h~;ZzESxs!(tck| zfv5sH7QRPQcWnpXLTAqBzf6u)b4v#-IpKru9Ugf`l)1yn5>xkQEX;T z0wi&_5kI?FwlD7W2+HaO`N8e@IvgtIagO8OQ6Dw`m+yBMJx+Q0Sew6}oH9_LHn2yn zF6gu#6Q6Vws&9GsP|zu; zieprBOOW2q#@$yHcliD0D4tGL^o*6$UeK%Rt|k4kA%YBcaCGZ zj@x&9#hidhN$UU7hzt3&kLJ0F=V$kt95q3{<o=9D`nS1kvyL}Z|KEm&mjdH-+>9qlg^d!*Kbza8&?A{LQdzt80JwJ zEGCT-Z8QLnw#)MP>c6<4ZhWN5DC&K08Q~|76WKj-PE9(`+#j7E_i)O#Q&F-7z3$Y?Aalf zXq!qLtw`2@Axp_ERsYM7Lx}&)LS6Wixy@T435Ra?MfAfYD|g9N{jGbf9GgF$CsFow$FF~Y*JA^i*V@77kw z4+UPTUzYAQm}5+#B@pt$be)=Q>=UI*W-L-Db;|BiRg#Td&v? ze|-jn^g(uZw&E5p$IQl5>enGYB0 zyRc5YPg)NA#IaP1=0^-n##8@oLbzm>i$(J@vK_Ig>+c(ujJemx(dMBG$;4O)YX5;dWeGfI@X zscFi~<}TYPwTdAPqq>@~*k)yKp^SlxivoGuY@&PTQzl=T&Q^ovEap_xVsm3_hQ8zin|pn?(XjH?(S0D-681B``vqgCNr7Iso>{w_QeA&*gUMFd)FkovGGaJ=f+ql?i9#} zJo1inX81;204AS~No=Zl=$)3KOo8w1=PaxE%mg|QH(v0!tF7UKkkYVgjMOAB_DKja z?MEF0&tw(md%}Xt2ELu=^b&>2(AM*U&)d*ix??WZy4@16N3G5Y-kO&?{h_%71JRBu ze`lOUQi7=J8A%E zUHNSeTPsFK#(A<~oaUs9J}&x#@4A?p&D4)0(8sDfKO|NZT*5&}Pw)Fe6Yyigso6h$ z?%K^|xzcRcqMOIa5Co2I&=XoTxHQC_a#1GLFS|c|?kerP$GtAB3yIFYMWc6sTkppCAtz3COP)z1o=8U>Lo%dJkL=l+xX!xyrM*-FH&Q#bnOXl#;@=7= z=dkF@coo$DUdWL+1(Dj-7}O}-$h;3Sp|(C56SFQ0D;DqNDIpy!?Y8 zrbNqc9i3#2Zd51Ac_%W~@U>!wow2842yHlmxq?cIJcyclz9U|4wNou{&KS%tsdEYf zqxlfisOIu<D{mG-Ixsh zbA>VoP9(t&i--0iHwv_(^R!?ABsS%hg)-&GW1yIyA*c?lnU1Dpuv+S{-SqwR=5{{T zMKdv0V+~z)Fzn#&K}<3BDr25wjHHUmMydwU1ZA8l-0e-a&wkGig>%r|>t%8?T#hun z_QIf$NXL|VRu1!V+&vLnh!%GGz6)uJR*ip7kRX10p)VK9t zZMb2m_r2OoQJm^c2AsCy=(E`nu4oby+;u-Zt{>@A7aw+&mC>5j-GZilH@%+Px7Y!E z*M1pc5Z$|@g*{~$YTm<8;!A2Z$+g~KUZyIb+!V<WS-i+W7`x7qfVr0SdRd&i!n!j9k_!O=lBnh6c+Ye-} zoY-`{63U?c2EaxdH2Y+FK`kI!)62NDP@%z7KbKw)*M^CZysJ8-C5=&a2>Q~d#BQs# zg+wGDpmG9+(`sE866(8H%vtrCd*#@a}tOH8Plk;lfiK`KR!;f zRnR+7h*s>kgyg-j8!taMx4v#%W~P>2wptIRMWlr957(Vv4`ONnWQeqA}60|XJh{e0#>q{hL8#a$XNg9S&)DMB!PzfF1yjuPHa|bok zZBA20J6G)5PAKPByf!+T%qm;on{rujp8omz*0k0#FMPh7 z&DR)$a7<0H%fX58;a~$VgwaI`)}eoLCZ)oHoL=~$r(U)a?iMg8^4-U~*j$Tciyfa+ z>ns?GGp6}F{mpp}xOdFV;*2o?5q9 z!NXXJtk=-EB_%hkv2gY1>u;`I(4G&3ua+?$E>m_vk03{q@u{iUvM>k-AB{03UnI2S z`h)M%x+ud^fjEufSkJf$kRiO9is<{egUN1(Es&o8HHzqs5@Aq_cDnYjlv^Nq>=k4_ChK2JI!0Ak%JvV-kmXt&qh3ADMP~3slLWE z8G}xL1a=y#tRd&}+11i$yLYB4^AkMuFC#9{k+=7lU~P--w{oTiXwk{p9XU{o?s92L zE@e0Hx|spDS#!pFxr&W=F*5PG?nsj}!b#%SQ98uOw2-jiAM&CEhp z+l*t}UoWB*J+==rT?{{mQfkeg2D`&nzO>yG{yV$%u;;#dU{(xe<^@EPYb_o z>^;ZJQwr*b5?m+~cBwc*SRb(}8vU^c3T32w0cUE1b+T_|p?m}moR0XP-VeUZ-qTR4 zL`<+X!G_QnVrJu0$?i5DDi-^C3S|rQH=_R3z5uZ= zYz5L3PlM1wKFK}xHY_xdX1AksZS&hZT4E9mPlk@*dy}({-|_uVsMnLzUz5QPXPPz_ zz`IF1AC-OZo97O@{o4?}gYE*ybly(8%pcSLCQW+RS$BwPUD%?6h2LgFmE9Z7aAj1v z!|Zfonw3;P!7KGLwq8%5*npo8ysSy(P*?cNe$WDs5 zfDv``*}pUtw#{9wb&0Ux(lwv!L=2A~8jb&V2*NR74i+EeIyYpcj3G*u<Mb?_yr0X^xz zXNZy}8=3F5(8kr;q>y2452=Xym|}JhBA^?=ZL7N&uK|K8fv+?i8Y`(g%E2Y^?6Dsr zs!7^LA)TDMQP!xS(Zt(%a1mMg5X!x%=N6|S925LKd-LvQO5a$7tVs_6vG>8PJ9tl! z?LKhEA<8Cb2idf&Htzxbjf`B3*4#4?y&oA;Da+l6(w@-)QfPzbpQ1(a1e#ECXIO6j!wFmeeg5}U>KTu-4I6>88IjbVKdlL0+D6!cpXHJAgheMM-0yPR+Lqh zp=>!_b)M9h9d4@@zaIu>uGg+$rlGOXW*X{ydK0Q+z{@$QkaOf$CnH8-E@`%8sbH;Vk4juLUn>w1<(qyem~ z6E2V%X2@ulO z{C=Wd>|8#hfV((Se_7zq_~l*-epr$RdmC%|hxhHIZz=@L9b{y!X>uCMxdrfGIjG?$ zYvr#tnz2+k;SOYE!CFRES@_p}rFqXfoFaxn%HjejWHC>_ae4PYch)Ba7T%P|UOYow zJWUMI4lj8;TBmNO9vNo(&8Oz_1CIgdRbW_m5UYtM0U3V)!Rx05R3Ekf&c9zg!j>40 zvLYanGAztus}_nn!JE~mZSmhr6{}?M@3^;$m{V3exVd{h_BT;5(POaD+zmEcQ5TrS zNlBwdalqzZV|_RA1c86QjztXy7amr{_^@53P*joL({% z7Y=rr9a+9!lrB~3mEV#@&j`R^OzUM5v@pbnb>A)CmIGp~Axdgf3#o7&jl&p8Ma=c^(I>ZX zo!pZBn^4%vVmn4gymKV`_T6PcNRRkLOW31HMxi` z`e}+J(@rP*#APNVuR0kkI~uqG^gf6nMlM>oxD-kh%#XDH+-oMz86zi9+=AS>KmTK1 z9$l98aJ0SGFZ?Sa>Cb!W5b-mYQo@mfZz(%yDt(Q~Ir^s)2{pM8uZ5!u^Dmxz1L0?% z1;|CfM3x>6Ce713uo`jbTvrNnTJyGVM}86#i3Ka@nA#+>(mt*Y3x{_Txb3tg zP;Cr=b-^y88BsPY?3r-xQwcNAu*^XeCDjPlZ)B#TCxrbugu!T}+Oy=SI#X0Li5ONB zM2h++xes$ZKWFk5ya1^b4`m*U(crr#+W2C1bOrexv*3*~mqwerf;K{VM^5#9%o3~e z#sHNlupyei6MdpKr;B&P8jWbn7T@#iS`NQ`56dj2w2@WL;ejn)%TKS}Q2SU)R{i*9 z=!f9cY~GznPyb^-hv@Ldt;DOAf-2D(kwvMB!|KpZ@;mlj^*@ZLKF+;3%Rbp448^In z$1g(F-GHftpgnrq_x-;Knjrf1?BA58B;9H$A%!-&?gxp756jDcGQ`}&9p^QIOANrMUPYqw;i|t=jO}jD*uPIfi35VF7fRNF}Q1CD-H5lle@MdqE<& zNZhBa`VLG0#;AOS4j+SY3VrOhM(e@ygOVIMuEJ0UAFs4zCfXLt{!_6Z26v7dYT9A( z9l7%zj!q$h(xJ~A@xZo9L(cPIPthXcc?;?di?+a(G%omjgO+tHwZ*i$U>s4dY@6Hh z+9)!!Bp>Val%!Yys9&1oj}zN5Z@*<`^XiH^ch#%|!0J{JOg6uiCd1PV0GDwK1kJ0=O_@R;lpBE9?seVz{=1I=JS5!$C#|KEoT{ zEl^c%$3VYdS$X-e_4fszx0f?3Sv*<{OD#PR^Dd2VR`q#!z#~9 zDV;8Bi;{{CXS}Z!0y~RteIxyt!RSqhUA6mastNr1nt!cq{foQ?50cZXE-}pr&@kq3 zq~#%ixEyw?mTk;T&B77-23sWfMBZHe7OLd_JLx!+>d%Ky?^nLlZztJr;lJ`dZXO52 zEyQdKDnC_6{3VNUcSVI>*tdohV6S$k@!+X*Wh*EBO0QU-#IfLD&+o#)R|7<=6RLB>gdmQjg$qSu#_HhSvw*M{VSd2Wb3e z?|ztdVl+kD!1FLTeG1kPSbu{WI=r zi%}(qx!IAq4dxN)x7h^qzIA_#-cg9OUhi)7O8No2WswPxPy}6Jm;rIx>KK)onPB%I zVekHH*w8`Yj;dzrOBNA$G!VmhmXivGwMXEK$?#Gm5>;6Lt2o?&cr^J5Q$6?dj(}4^ zZ7;o_)VSW07h5G3vM`;w{3p^_`xl%>x-t?X8pM1gL6K5N)$vDiOrALwmXET6-(%;A zbm~F2UGI;E^6CPvMyvF8JSIG;)7Z^XQF4c!le{xXd50HK8{LlQ(VB z-v;>JuF2<)#pazUANTc#uQfg*`~FCJKr!_w{5Eb@l2;TYKg@Ec^r;UMDo>7&w@Cuu zWnn8J8R;3o?T=32v-@mOx`0g$H|0s5f@Ko7>rI|SS|5X)FajNN;`Uuu05es+O7EPj z?hR%V6cZJ2mr6ebJVr&CkGr`;wBJw`{!>#RZjr;lb5{c#V$H0X=x4!|>;Q8(C->M% zPeH|o@;0=07$4Sz?!v__Va(_wOcws%<2M`r-8(;~H4*(9Fw&4$nKjfd;-1|wS*xY) z?uMp<;2>hU@Lbw~$P>99_E{wPu3v$}_+N!1oaX9crl0auKVF6|YAyU{z1k)>#MY#A zao6t3#BW{IdfC>)zN}C1{d-y*hpBWgC*Kz@c6`{pDfB_H-@GKKK<4V+^l?G;Novk) z-{YMgYzE34XSM z7K#Oe5vjgG$I4?FD_vgOzwiJ8c)pKOv2Q|$>-YB`&e2mF&&bcI_(>n)c*D>K+&k4+&tr zseMOJVzSnu6|3j#Q(`g~nA;vMe+~~kP&TBr4PHz}ILt-mwlE?}l95b%F!gu9+R)-S z(ZCOjxM?swLWXmQi=M?z4i9XR5+uCYB|fC#QY`J>6f-oBemAU98JDWK80ZQQLWG3i ztw(7vG7saos+B=L9`E6Z)%WQ`Cq9$t;^t&QJO2H6u=QEjy1y$UaY12iq8bTcqd;vU z=efx>D=JF0Q7%B2ixk#c>FtKO-~Y-eX!I+83hQ(5dDfBn#o}>HL@{naO4GJ8kFBqr zaG#>+EOQ3D!NEtmcn{*0*?&0Km%HGHcNhY`MyR#` z?yXQB&ok@qAe;^7*Q9B9_b~%*>gWgae>XB%wt;7KJ{?FhKv{F5T?KJYqm*PeTKaXa z;NOSjRkMx*h4)`L34H^xrr^`$a@zj|Sy)HwX+Y&+59=RNEWi`4> z)pwCZQDzoc)$ukF3%GR^yx$OiGP`b;B)BVOkDF9?`&z2`C)L}s6%vLKzbs^sF0`g~ z8)K2?h_n?7VtX?mtA^ijL6U#{=yms2g>>dKG|>4k<=Ad`CCt8F!5ZWw9n{< z<{1S}_o+z^O`z$N>-w=@uvLv#%c=aot&WzHYk!rSi|38=Spv5U^3$y3k@{N^sn+ROigCa*ENM9m-vxa|etz~;!6qM&{i7XsOe7`NUq5Q)<6B6uZ8Y}3P+$iE|h&yGII zNNfeU_D_sr*YCr6y`Tityy#IxTXYyQ`u8S-AwQ|eg4JXfq@9H{M%@-g*+9_GVMgox z6HT~-L5Xt%0k=z_if_SckJy^(HUxXL#`)*Q8Z*>c=;~r4Wi<(7joVX|qNn5$I9LB- zJ5z^-V`{7{`2D-UT;}U8$XdKCi>L8e0r|FWQjW7aTHkTiRP%b(!qs*?0G{yE3lFw_ z%iSFF>)pN~T}GzJ#jkD)8f36wI=C+*e%prU8hXIQl&7Z3Y#LnQRK+7+8GXh<96w@u z6ytS|oea!$Rpgi>X#(aO1KoL4yLwZUhf#vF*J-5~MYdJWC zgI0zGLR?>?fkdKFc~0@^(ALdZ`k4fJXva8+*i&jVNwMg6Fq4FY*!OShKbqY&$z)=-P>Y1;f3*94P=4!{zP`5~6Es(e=q)S?gCunv!SI@T z2e#*x@V>^)rI2aRPbvr+$ivN^MHuq#JpZ6L0(+Z?7LqZ1w_&A~rR=YOa|N{tykRAX z;`O(W*TXR|dqc4KdtG%;h|3m!+yfj%OZTV%r0KB^PZ-pUY&efs3a53ds>*s+)RsRH z8^t-GtV+ErS{q^*?+&Rj9sL@`JNM7a>pMG|%*5$(cwn%AZ52Q4l(S29UEwj?xyV2b z&i>{K2I3ztswKH57ZwSVIP+?202QUB7`boN_Hjq%f_GXOUqeIw46!{1P9v=Q-2>5N zmSQF3wNronb}mSM+ewt=H3CVDLi!zkT?s!myk$_kA1fQ!Wp5jQ zClct1?v+wEfKWE#U4JXJ&syg~5juXud){xiT%@H*>!1|W;`|@;0@e(V1V}-^Y%W*S zOUZMMEUX@ZL)gaYS&iI`rkkm#4CsB*Vdl28DxKiglu&u2tZOV@^5Nf)+`n^(*F|ux zkf?FtYI$R_B)FB&2l^viCZ?K-blcEKTRUqRW(z`Xl#XQj{z@hsG_a!)U39u{-tF(p zv8lWFUO&sAI$BwkH@Hp3OE3R8V+HFuarw9ft4@t?WitCm*i!}DHDdKx~R8|svU8S_3~_N0>ax(5o5LPRl&`vG?H zcm#L$4aEN6e>Gz*k0I$b%^;Uv&ZSCJ7graJriKQ7l-iGV>eMLjdpVX6rNWyp6QJil za6h(6CExIw9SrM!7?N&_M6cVi($LtP26(!}*zHi8+VptUV@vyTHy1H2)2NyP zlpqX6?epdYfKhFq&nMq4uak%vG6UT}KI?DhC|oVbfT*#+&|!%m{>hT_T~C{ybI+yN z+Auy#dA;TKRnYM8SeKMH0-KbGTQf5xffxYDmi;)Hi9sAaYw#kz;dAkK=O3j&KU@pX zTtTg0xe%NG=53Zb9*f*B8kbD~Dy}1~PDg2rN^T2(01z^qpjySTW_eb@YAumRqmN^yrQmokGSuK=;829r!%-jd@1 zCP!1q0cmoY^G!}iXel}wHDlkl$0x>^=8H8_%B$MITYt1_4ErdSrxjAGJj7h zmP%Dyy(q^(I`KF5D8satFM)JH#6$S@xIp7ljKB*uRwWp1B0FIGXU5JvocgcUJ1*3& z<0ho_{gHR#1(jfuFQw_a9N<0)n==_DIbphX&+D+e+*lp|I+2dkYPLwbO62gG#BliSYm$AnOqW`re{H*~ zx1WO9kn{GWxgiN3Wz&IfSdWBoZ|tkBV{-5Zk6!vb2Hjdxsg>YG>-N0@pB+qB1n5BxUNbY26KLWiHk1nkU zIINjIKdwG)vph~qb6Lp}*Qw?c<>^>I59kb--8S4qR}{=4G`gD+v?1<8<7f%PL+d>p z$4;6uZ*gCvRzC2kveE|?%N)*TohKUK$Z~D8S)!K!Binpa0)3dw>1k;PY+&-$>kIFU zx3a##id#hs;nUl_41-@NCBtZieAiR_BzJZTjk)*xnL)CP=6}Rs$F^cGm$p2}b^k|a zsyfF%Yp)|cr!xl5*JnH?;=UkLGt)EkZTH#MpTs zhXRAOEivyPjEvViFV<}P&+;pyHcV9YKaJmP<9Kqq=dwVE7a>;oSVpfM z3-!xK%nCI96nO*g@zd}OK#5Vr+1wPSz4s+GlC?%HrFHs0#CVzuuT|QlvAK6k)7;MQ1&>koX7uUirIIP{lqZ`4DxlL zDV$4;;K$ZPAi$@0B9(R6a{6X5GijGLUWa?y{1!@y5}H!j^XZbk`{SY7^oi4QyR`Nw z*;;+Qybep4FG1~1e!>uLkW%DVl`1He9rLaII4@$~3ak0KP&px8|9UW@Bbqmfs5l$$ zwjs8eT{e&2)W|^=d8JCP-K6bZtdzO?<;g&-`}N5ehf8sJWn*Qf^^39*|8Y$s5ko+Tzd0Y1@7Y8Bt1xeK7bXx zNXzG0_lFg$rz<<9;JK}z{D|hNoWquc!&F4K|TLtq2xjQ7x+rz_hZS! zB^|p&aS~D#DBT4gbx`nb_(OlwT1@y)9`(RGx`|@JcZ-gc1cEHt!whiVvS-!FFP@I^ zXebFj_77%i9MYz>u9Xd56Z)mb-r6fDzI8+2M5XU*NtRe9i8@X`& zUtAtxs5o|KofnwqH`5~c?OG4i-Ny;12kHAu2?+3EFTe}=hl3o~EZ=8|-(x|{kMQr0 z&8@b=gdq@pdzBgZ_Lg%eCG5V}z3}3{4+kVtKun&XQ;ja#0B%9lk5BMl=lGe5Cw>}K z8yvB5v*xDarT9ysDDC(u@EwLL0R9uk8Y9#>V3_S_r=M%$#agBb1{(*(Xc~=@Gdr#*ui}4bZzb^N&3Lax1Q;) z+V@1vp*6<)+!*}haYc>GgOZ2*`I}3Axf~qXj>HyIuE& z?yDMOWgpOROaQ&Edz+kHronu<{yXcX{p5_r187Ka7h4kP?L`F;RbzAyZO2W7`#yop z_?7{k!b&6M2VyEK2ucWoMuhh`5IU~@n-r6@kBdlUJZ#7V$eNGSw_~5d_iH)%8-shI zc`&u?E@e0LUhP~8d2n7RVkn-K&2@!&dG3~B6N1!d(RIJmA{Su*0g?A=`#vsF+Nc& zTu)+l|E*NjX70=J#iRSA6_96f|uf zWmldPHn6X3AX=>Yyy6b-PYjhE^vNiOW8^~sXo!4%_}IzY`gdu|xG~$j#PqZ@oE!F? zuR8$W{b-(3>iN~vUr(5Lb9>*^b<^j>MNs}y*&GiFvt|M^vFF@29Hns3c;D=GF2^|q zdVJi7yG@%yeU2wm6M#O?7?)=TpJ}|GZwfkkBo0!#eg6NQa|j(gF6(izqE10J!%N6C zuB~wj#H%~g5qCf#`pYy^2nm8^vN*sm0d_LbLEns_^jT8yR1v=O<+Pk35u6NMiWG#= z_0wkX*o4@pl#@`m6vrK9f#MHRjttd}?Z_f+-v}LBTS?UL8TWo&->SW@>i*z?KGA=s zuxMWfDQfLf-0$TjpCY}UnD%hzPPk(+Ur}FU`4m$yKlQb#KWz&VJ4cvQq5^k@?;x7l zo6Zb0mRF1KRF-RYQtJXFhoT7RT-Ie!Q&a#mUxZu`(x(&IdUzBBqs9~iJ$FlwHiRS8 z*DVoNV9)ENJTI53^!Ln0nm|gD#Ct@E5`D+Bj&&ST#2MqEzew^e6#19*GzX;w;O>Q{7-sj=Ht;_E~qH5 z^X3Qj2VYlvESCS9l{D&a+Mr}+vphxiAo9LfLLQ$EfJL@OjKLEm#)*D=UiqsC^8|j8 zRA{9fPte8cfzajyr$Di#GFtq?v|ER$@?H0egqbsYEzIAR|4#S|8VV=c zj3DHg5;NE9x!XNNFH&&|GfvHLOUxa-xO-ykU!JSLh<}H20AswPc#_EW^Sbf#u@B;f z?~sF$S_siTHxULVYT}|FH*~bbM_Ha#o|(?0O0f_sx*jK{U)s*kxMpv`u^yHhsSH&` zNW^8_hnao@YMqXbo41hNtXAmg+O)Rq9P)~Wn$uS#LG$w|X{x@vjC72ce@TuFg!Ajw)+aKpig<0DJaD9!qKWs|BxSJl^7M#2FxCz&) z$XviH>}7p9H8U$^@XAru8=|iX>iFC9v=4DZ9|hN}PswzW09vU%{<0yoR~-9x*3POP zBo;D@z1iQ+Y&?{9NtzxBaX{X9+H0g8F2nT~Tzh-$cY45erd}k_PFf?)r+j73@kI=V zCvs5OG*C^wM^jPnB#KxQipHb?tqkAqPNtC&iQjxkyrM(bVGtV*WfT%y>=@Fw<_|!x z;)P7^Gjze=`8BNX{o`r5C0?FqHb{y1xd%SX@|}P0=U>{;TrOSIzSH5DEgzJ|Fwa{m zvZ?tCL?g9rfA|lhveFd01Z9~d5$Ic#wp_*5bnBFlt-oP^{eFjB3cjxNP;WQ>1R_bi z2M7|)3j}zxg?qfLn$pe-+a`=jz9%L9T=ptlrzO2Pe?L;(j6O@aM|E5{e)H{K3n+s+T6Hcde6Y1(Qw zy}36#jJryWzZ=o#?x0Z*3qYyNaycJ9GeJM!6MHrVKg5baR{u%pC}w|BdXiRRltav# zn{D_N@~2f?h}*Ua4YIF-j1YiXShfEp#-mP)mDprK9hCBev1Ri#(jEvR01{__uyKi{ z+vf0ZaldO&W}?_y*A@2n8Wm?B%nnche}K=&kjvihqeQF>h3kc8s*f5bfmMIt^GC8k z_J{qty6+f8Pne+d$SfV8ydvmg5_@fnRU+0L{* zt(kkED9{%`zLFEp`+h&~{9_6rE6$b>%n~PwTh3tUdb}q=K@Fzwj<-L893ptwJk+X~ zE?o_GAS!PF*_tCVd+EU2a*S`xZijYp@M{zRWy}|7NVjq}QbD`Jn(4@n$Xx5cQ zxI@Q>DEkT)HIW3QrKj_jZ(LUBt{TsPai@tH!S@q|bS)BEGWueWX@*GplI<^g^;_5i zO#Dx9v%@z3nMo*pKW^|b)_KH##T*}-qGQ?Maq3s;Am@c}P3`^+$0?G4aT- z24*Ggk`}PY;qAQk2;>s?9@p(S{d~YySsFeW&l5so4QI48O;y7Jv8yQnt7opMctat{ zWb;X_OE-bv9o}~(@{h|`b!0Bm#5|W>Z)$k!$S{Ti6Qjrh>pfT;UBlm~8|o;-1>r3G z-&B$pS&~6LfX}p=>NIoxxkp6HW%8*XiiSs1n#YJ=c?cFn=-sIAM*- zRW_~lD66dWvT`wP<^6RdoO;3{6R6KEnV0wbDplFCSfO&OX0_*8UCL0&!Vpdk1;;s8 z3Yk@+fY6USYLYTwaOBsIxou2b&AS){`xFRvw&%&5>nu3W(^5MoZxKK4OPJIl5fQ(W zyd`yC5h^}W59RMJNePtd$0_~1_`KuUy$|Z<`}GX2^!%*GJZ8U)S*`4$!($d!X&LX4 zNMnuMgSF1D?U-Filg{LMFWAx>>%)ItXv;&&|9@?f1br_^z)`_zDBC^2e7FQSTC9cw zn^4hiqc2P9Dk>8x5I=YN#80mrS$bR0hlL!QRyxfm(x;5-W8u7vp@iiGG)D(?$t*(% zIIW$n;Y@!9oKmcia@`)Rcsv@tKMxDKt_VxMAAsqR?j77dm;8)e?W|dfZ>Ac)n_`jP|Az+;(8@ z^C4QR^NIcMP(%nV5OX|k!(v^iB9|Omk(i;|l}3((X;64((puwt%`~@ay=tdqHnf8vMm}Y{JDi;U`dm9q zbn(r?A8t2(8v1D~8+F(pX-0$yG>~06)u8J>%==`$+kZdQ%$Zs=7j(BDZ*XSYbzrtf zn&UMTiPxWS8dUM0deAQe=4|DD3Tt~u$oK9ihy}slm)~nZyAa|t{jDKOoIKN(x2P=d=iG!=Jr6h}CREm#zles_ zM-L$GRtt)pVAOjIuz}NUb58Yd{0aG;CmaX%#bLrZwQJ41T3YeZ@v(oB$&r6P=t@Wc zAmD{hK8&`N6VDwRQgeeY6e(IKY;1DG{vZ@OUts*l0m4mS1nCCg6ym(HtnXA=3iW;y z!;z(l)K8Yl^)TF;D^gD20ecNUYDqMfU zp!CMLZ0;fkZpG~-tqA!=x!vOu;N$a@!lFWQOMU%M@O7YY`@N99qrRh=xj_~TGid!3 zA#hPx;yfDv`r@fR=%BfawA8|3xB1gpc+30oQi40mPdJ#&0IGc;P+t`n;^;VaFLFS2FlgfIyR+5Oz%Tg)+)0gO(C z81%W)Q7^LunHa&jV35l;Iq+V^c1&4Y-O`vD1fZbi6v-owHKrTm3)q6nykUawEdJ-O zp+3&`)6+9ur`|Qc9|5){&!=QcPqHyM96qEC z-cG~gX{084xwg^gf(5s8`VAafltQxvEdYZ&Sh=oqI9nZjWT z1@g!!sA*Gctv&}<8l&A1t|_wA)s^kK7@KdgzBOT=E}`M4Jrq>C@W8jd^Ik{~NvQ4)QMm8#>2NUWHiB_h*kJ#3QeB z-l~Gnt;&4N>xiTPeW`V&R1fsCPGLycpD~-}e`bE}cZYItNvn;UWTTgFpu2yZ-5@!) zP4^?b9>IeIl?s(yVHMwaDh0>nl0e_B*G+Q|3*y%m$^UXT-L7NQk%n%(MFsfyZ@JjS zpU=Y=1xh?34Vg&c!~(RptL?zYqt5rGTI1msWC17t^H<^>flttM`)lECe*uD%-b!ky z!c9}VTOw;N$A8l^{!z%6A45VgRKSe>*_%)z$CCE@IvK(_!}~7e+O9d3)fZNQ2Q0wG z{PJ;8?ldKUf0$t+IUM?(zT@RogR$9LpXHtPQ?rWVc7OL0E8_2dk6+nCDywV zO6ZT8Xj0K}_!wk1)~FKBc2n9Ze!s&ucEL+=}wYCJJPFU31q{Vi+E{vqzyiTXC zGh21POXH^#6zX=*8DU};%_DF~h5lMRb$R5jN=C!+@$3A_#Y2`P%;gdrg)$KY{Wo5h z1ge>>jQ<2K&3Af739)yIcFxA^lWS~^jF9%*!tsDNXgs;q`^m=sY5}8$SMSmzVhe=J`totO6c14(X6_n{)Ujf+G|N z%UleYp;fNetbq40v9qV&A^Pn@FC1P0-65o(`rVK%Pgq-_W1q=y5Oy+2^s}e84``$d z8?8=qb#IT&-IIF1XT@EisQ1j0n)vGTiL+VVqUE`KS5)N`_-@29C45TJHbom3q_ZW* z2JZF({~Zvu7h(@#FyXvBH2?ER=Y2`Zx0$EgbL#La_@N9L_v(2!qi~P4Xmri6FTEM* zvfWhe^+0!NO#GT67agGONNT}Kn(H;Gysp=1R;(o@gk7;z3sgRFTnp|i^A!RadCDm7l zT*!%0Z3$HX=4*TD>S?Kt$7gFm#zO!`0nq4O!;VqV>(kwIa~^*V^q1!dUVQ%ItW2Mm zlmA22H855ZF4-hE_QbX(PA2xmwvCBxJDJ#?*tYFtV%xUuy-&OQ{y}y3*Hz~`b?THN z(@)#UZet;&r~LVm+14Tc)oW0uu}>h6W~TVS|GtBSaigW?LzCX&!q)EE@p177?>)r( zSr=hYj_4T#E-ZWE7el9iNQ>wKE?@=dVtS_gP^Pv*7E;&|(inu3(VRGEF_r;eHV*-S zsZ!sP{Y%qI0lW(`zH+#DDOrZ}(xrTIa5c0*{JHi$4kTpRfuRPw-;;jS z=tBCCtCbveW3nh{ZWuh(vs1syi`k7C0ee~f>F)6xQ7Knm|2Kyx13$PFM~$Ex674Cg zkC%KyD&UJ4=2g{?S&&9qK#u#uDIsr;{|?au);=+_D?RXVxqFbHCj0%KvJStyJ1Tgd z+&$-V7UmD5GB|u}dsJLJ-1JK{QRJR(s_4gEvy}CA@R6R~SPFw<2);|IR)?E?GYlG& z5~GOyL;qeZ=kbAnIG!6~S5LFxzTo$b|9Ao59Sl`!HLFrHCm0jlaRLtaA*Rd|)J-|< zcEG&ZBNW-q0#ub$JZB#(xx35Ef{krBpFV%2t~Wjsom@P(z5Wq1nOHXM8`NtdyE{eI zOZV9R^js%J;w_oXpXHMU+jP3Cv^n?_bxHWNb=Gt|?LTejLD}hRyFAkF@nGS4Gl0lk3SX0VMUzlDALL)npo0)t>a zp32l;uy5s1QB{T!wFypo6F|e_BW`Nj-DiY1OYb(^Gde`8O49XlaTPk1S;>|Q&`?sU z#M+!kBg*WazR|Q$_(NK#`OU?;XB-u6M5Qy^;lAR-gy2w_7Afj?oxD} zXV%pYjZiq_z~+igK-A{rVZAge2*2OgF_Y0oyjtclf$#awxJY&@(Zk<8xkg~8_eMZw zG)rMKkb?CpsxUZ_ec)isQmxqjw4pSiit`mc#FT#Vc0BTTS}CsWbD%Wh%N*Z{d@AV2 zCz3B(s-+XKG>7-m)%-2i%cJh$O|lzCaxkJOCV<7!wtzu;G~A<|vT-v7!5H!4!N zUC$RAna0U-67s%n0CJE^v;%0Ch%;n0FR~)c*)4V!06YE81BG8T?I!g8sXY?d8xZ{) zah$C6`J`3yq+U2=RnpcR6Q`S><&w@o(7RX4Ej;lz0?m|##Ir)CXb4$2LgR4+1^qut zcMt%iyq|Ru-{sgqvoOon(~Bbq;yahOeJso4;ul6jV5GtU`a#i;3FL!ejK|S^2Lm3& zhUu+u6qm5xgmN@np2uJv%vpvNL?vN|>AG?^eJx_{BLHIbShAAydo)v5ACL~L@^%O$ zuOkSf$f@=T3b2ld;foB6P(XIBeFMz1?^fqiD`^bZtg;qzi;Fc4pzt^I9WS2iy1i<# zEysayvu!_oKYwC&H+i**o?%G)d!5vp@6+2>v0BxJm#(7`sRf8x27LIwSB;9sv@2g* zN5#MeyJ>fH7%F8Urp^ejGd=9iT|{&G$P@UTu9v_IKdSW?sI{kYh4<4PP8 zS(6<&8l&^8;#z9c2?DxKXr~`Dx2LJ zwx)+%vkxzYoW2WQed9`4@@xU^I+6QqALF{F*4`ug_2H5t&Z7wWwDNu{9r)Zb9epcZ zbUagc0E=&K%ZZjYMZ6VDcd_-gl<5@ZKGj7FPUUAczP=#knxRz3nSt;CJH$hl&4c)!-E+RL`+C!G_TdvBg*H{-8~?5~oflTkI1 zo{qaZXW}5Iuyi3~9bL3%aTV+tuE+b((=y5CtxrcC8S6|>pBGX{H0p)YU-KXa;}Hy1 ziU&n?#RM4n`9&lr1tlD^s=tKDnpy*+rser8x#i1oak4goQalG$2;;t#&jGpXF6!~& zg63ZC?>ZeExsKzZU)X~%27$gjR(wy>JLWSX8460F;>WhH!9!CahdX$b*8m*QV<*K1 zW5~^sRi?>XYN;x5jpw3W-;FLxPz&z0zOVc0<#|FvseR?nuFOH6c5s-Va4i^|gCvwk zxXzK^HzZfd`{`pg0-pLw$!18u9+>~oJdlzH1kJx( zc3r76wwc}kemR&_&e~*XKCE>}T>8M}X8GvjxIVs8FU3*nwYR7fVyVcZv6bvsZcl>d z4#N3_?^NU0dO%GP4P2Z)mCj<2H`go3HlbY@73@S7NklvGBi-k*^`J_7=zOV4$J9s1 z^shH&?oqS&ZvW-oAQWBGXaqaambO>I9fAy>h^g&V1Nu@5b_Ih5R$~2Dsqrs8u`3N~ zJ9^>EswZfGcm6$`WYhFBpwRU>q&NWpYde0OGOJ^>NAdJS*V2^W|B-Ghyk#o5zR626>5M zEKYbGSurT$eK2O2z*%@tIt8BlX!fO4Y8sT6xh_``EUVxD3YInVZwrj-FI&j+DO`4& zILQYWEqkyKk26}_`zi^Oh~oCrxgz7_Ds|ST*47L8IJ?Tvo<@s`@tWDW#RPxW2aLW`!jS`3?>+Bu#?(X8n#{#zN-EGc1$KS_y-@XeFg1~%V|v| z1~%CWmGiVcbCKUGChD3_UuC-U^IWR^p5hPFDU0s6thZU##-tFNwN)@iLNs=cQAGtz z1r;S{r?li%ldERmVY9TEH8Lf4@xK7ZVw5E!@|25<9GXM`%1a7nY@aCTI@2(oQJV{6 z_R##oiU5tRrJ9Z&;@y!v4j=NH`gtJsp)ln>!gnY^T$p}#6z4biJj||$q?DJl(lt-H zDkQOWGM_&F#;}5q{C(?NqQKl*Fpb^9Dg4jqR>Zp46qRAe#6HA zMh*LDwI7sTX#8tT_cTxb5CLp<+9JlmJ6wi=cOIyc59|*h<4d;cwzGGb&S1QUg%T;< zE|rp%DH4OHf4Izj&X1_a=IW@`P6i(Xe~o!D`f=)ru4$NN7OME&$H%Q^)Lm7ikslPI z7YS#Fjdw7i{!B4_IBl#F^r!<`M0VWNY8f3fxEk%KZ}vz+U}zlTb1<~0k>CbF9;Rp8 zip|VV>E#hH;I{l_AC%NRQD+jDV}H~+ruorLl8{06Zyn6Px~d%m9LPIRlEGaZ&$)7; zD7q*Z-qgJLPc|d)pr4F6wCD*y?jg+>!;ZkM8d;39C!e9CrY}h%B2Y;HD7OqQ?a1Q1 zn*L!b$ZWLbEj1+m2k89CrI%h|X|Q&wo{VH22r<4P!@j}NSyAw7azJ7=mCaeROy>u( zOV_JVFi5;l%AJWu%yuj`mVcETlOJa~m>rO_NZ(kt_mK#Aw}aHaz+gKYTDIuQyrR1& zdKcC}&u|bQK7N82^C9}23C9RP&(MqiC$jkmUq}YLyIjxi`hl?O_uA4fSc4!CFgw$i zBc0YN;_-{z2%*J8`(~n2T}xo+7zP~VvY&uEX!m#q7r;Lc`HM7LZ+(~YPcN{zP^p@^ zUZ#&QwJJ!KY&!W) zto<6Q6o$MgWyb1_UM(t0K9@)`kjRZS1nsf3u%LypQmQ-uCdWScv)gPBS3YPBZp0&W zZ%8Vy;-zI^$qdEvhq|B>4C??3#mc|>asSjR1p)eF0Q!(IzFu>^1RxOgr-s}|}zgT14lh5U4R4Xi9 zY?9R6oSmikxSde#;_#bH>9wNLDK~~%KVPZGSEcbNZnGOyW_g*_1#hfbR#qk&l4DgTBwN!M-akI)VGTKGJ-tIU=z!PO z13C9&if7JgT;uV0<>j|2LY)>aJOcBYq^GQ5b5|g0H%)6Y+_AQ=OLnGlsKDJaU9N=R zzkZf~oUmBCZSrw{KPwPOtL6J=u`Y@mL(6Bu$3vGL(L;NFtkUdy_M-4U!@{jON(i4#k@jLh3VPi+ z?DRlp6eJ+MU;+%3tC&RVtk>A2FnuGF&sWL6hIDM^Q)fy?lpas0q*9dG8k-34j9COg zme}0`!N#R%xQmhsm=_W#WF{lAe=M?EJk|;gtp$EKRHd`oF7^t_%re=s!3IXlZS6mP zu=%VW-J8#m{0Ph3drr%G7pOHXTpER35Z&o(bZqe9IlyO{iZpt$$4v?m9iXGf=<_7& z{!hC4rz~PD9-tvD&-gh~wC_KJyoV&fLFp)qI@uqrBmh@B@AtELgi69vs~vopyG}p* zT~n(gH@>ayTiqmcVZOAg*67^H&)RWjs-BLz1_yo(Kc~0zAB~+&U3|uiYEA{_u_gcl zcH7N7K~l~wn-2K;Bta}Kjj*;DN7GE_B3yoh0H{M3Lks$a^Y7Vyx6r)~wRFgaJ_^OW ztyU4fnBJId_N-;Oym?!mm%PT``CQ|Hto^@!f`6VGEGV!;P`seGj_WCcAwtkpIpj$r zeq#FVZgqPoCtSwk1WlIK-70RROhqx*b|wKvYhro^ zArF&P+>Kun9VRGP%BL?EsM93Ycez;FbM5?dfdEP|m-^S*x-G~>;E+$Z;1P85bIzcN z(x(mP$76sY8QDLGt^Ymi zf&HL?fWaYtPh?-eD-P}B?Z8epi5}kx#StnPMR!1`M@sxoJl*Vl zPhAdpcd6!%jJf$ZQ0ymB3Wko(g@K!z4uf`~s-q5cvii6kp_bA#`PS)#w{0l}HcPWb zn;vVwOZ3jL}*Ig zJB}H~-|a;?BTedw-2@>H{V(y%zpET%=0D@c0mcn-m;n|J_gsxE3O3&R8~*~ouaaAO zflD0)JU0JOu*>XMBp#FEa^tUE01^*lTP%-fxR$iOO>C@@r17179DMEB{DeKZpNOoC zWNA+e3h4Gw5ycYi+re@eOZRam@BRy*<>(j1x;lQ^YC7EPFH%)M49p%6Tl?ST{2hbF zzCRCc`Ub8-EQwLt{#AMZ{c?XE2{7GE+4T~FUip9Z_z&9Y&t;*Whg$D#%W;Y0|aS~N=4Fa+$rKm?d_@Ao2F#+l&MSLJHN4N z88=X|O!#!GGxTUeVEerK8C%gwe;)N$^r1NXU&K8G+XC=wXb@lLobRAr{aG)G-<8H; z37?SwM8Pe7BA{DQT$Mic^ECjMgH>81@^&28wr`}O9E>*9bGpPoBKmPsasQ^Ie)T99 zcIl`Nj5+{sMM;0G_v<8{wu|~JLI?lGuti_D83y5k^9U~C=C&e zvPl%b7g>odDTv#+R45i`7weQp4Dc~7P!?8$-5?hYLSadMUJDTo4h^$%>Y{=5P{rQE zf~3Ju0$mcrXQ#UOK4sx$jYw$txQ zBB(hAngD7iRaEQq#vbfFr+HOKR={`Mz>0BIB#}ZJm`tZ$8KR2n~z8`Y# zcDcb^in7s+w*EXZ%oY(3hug-hOfnflN>XZA?8O->(;Y<-`11%SB^DK{NmL4_^DBv0 z%`iM!kaywmGH#jI$0Fc5;gl$A_tBD1F=HJgnMYqc&Shq0HCh=6h5 z?Zk7h$LXgvS1ea7&CQCy>ijxbNP*lY)flbrUZ86zA1)~=>8em#S!JnG7Cw}yyKoaa zRJ_rYK?KCOe+WKsZ+ z_2+1Sc>?tE`&)y+WYkem zS~@fSeK7`0L6P5qtjFsM1{Gr-xmmqRy=VRSr5DLZo?PK8Xtj-tT@YMJD@xHgH-~L) z8r>UzoV>}B-MSX9$E#k}9EaZqxCq&z0GM^wtK5SggWPx;q%5^|dP#y77g-Cv9O<|^G*URqde|Ff#8 zRIyx$ko7%UAzrD~N&~o)Lgh!?F6f;;iRn(BNL5$YX8EKOo%wx+wQSo>)I5V6h}lGN zdFRCMXkEm6W{w;0G7Uk51XF1wO7;~jszQn9{~zym;g~-y$;c)=-WNRkyFUhGi|tN8 zu&O;M(xFxabew%jO#_YfyvBJf@e5$p7-fCjZ)tuWi!lYg&p~1#Fv~r;{^d}v!w}Sw zR1|VkM7yl1s%vs#e!fh;PKCbK*9BVuB-0qwDjh1!U0;wm7#f|)`BXXNwmW6=ihgjK zDDIHPiqhqG2;TFCjsYB26;xAL|^(SeK-Sq#1mOwEF9;Y89>$2(g zbMcQ)d9P*6VM6=q+>}6=pU95^Pd(n-{MtQ%kFmpYJq7wIc#q?Iv9RaBM(KU{=;(ZC z94VE7yCp@vZK4WD%#4vwN*wH;YZhND6_#5moc;*n_Jw?d<=?@Lwz8W7tZ z;3^pQHNp&>7xf}ipvT8(W5L+7K`^M=ZS#P;Pip7s-)0=&O}sdaEPeetk+R>K^m7bR`UX?wB2f&Ioj`#jKNX^HC!bOH;JXlb)U3fBsvQ$zpHYeYms$+NM-$3 z;!gPnRhLTC2bXz`@7)-cLvUoU?HTi<6HZe1{` zEOxpOl#;PN6#6HV>|Ddhul_r3W++91^HgMp^}XOOP66wjRN?@fEMwxX(ncyeDa^lF zj>K}LL`OQ;_${^rp?GZ3dwpK0M?}O0873mgc;!K0zOkt(;?{pY64Aj9sb80=vh(nm zm6i3gBnyY3GsZ}yqLd{iNl#jiBQ0XmCJ@u)XyeZHa7FG8)Qa7d{Uizkt-1^ zgy7H6QK6Ei2xurPvrs%(bt}8pjTuU5;9t4NE&?V<3S&_cgr&nNc-R~DUtyz0k#+sS z;Nvj$M#G2<8;GEGwDf!d03^=k?*{t99ja!0f zsu7G{?s*scs$;x>twM4fKkWCA9~*W%eS+E`j|CBQjy0C+jSX)4x*94;Wfdg_Rz{rq zN(TBDAXG8Mf7%%$U$wcqvZNGQ4~&hg+S)`D3nvO9Ary9~sDg!(AeL;X=^^H9w|&kQ zE0ZZ)$PIq!m$@VE59hD?4=m>!2<<(NFEQM|LCLAT`@|^z`IYrw*+v2}Xu(NHw@u;BzY2@h z|Nit;GEJspTBqm}XolpjLF_)X_o|a%pZ$Ir)ybt@Qq|0U$^CcZ8e4#>?6p$U%I zQrg>*3fVA#(hoC3wuaWj{8-a_`seA_{=kv*>Ivq7m*n}g54;m9b%{`ac^wn5}U zb23DrLYr<_;0^cz;&d1dG0%J|L-2Js?xuG!CKHm5bg;YdIAfp*pqNt?etvMfA&4W#k$aZC^EeT88TA3> z2A)J6KnuG^VYJu}twfz(x#w>j>4a4jQ{o@O*ofm=Gto9f%$On2xOg%B^e_~L>1;6^ ziNu`=P%eV5%$@D$A?Cbbh63R?kv?_~JTob?|Gg4l;^lNB{tdPCJK&W1zOe^CZpxlI z!Yo!2G>&cFDz|YR@$IN|RAFUmJ>Th9HxH@ZfqqeI}84J>p z_qlJ71k(;z0CWs%hp(&{v_KMmOC6HH6=VgC(<~$ndbb@Sxp5VNcrK*?w|96MHNgU*n}6BM}kYj=`RGV(EbHDnBNmp(LPw zo=uNjefr^Dsa#hNS775Fff;MBtwM|!?gnVsRn(P8qgR$DsyR%RBWFs`;V|lTdAXO) zD_hX81p3L}+sdatonib*54*9p7GD_{{27}%h7x%&)9Uu&6;7Qzo!hHEE{}Ti%i}hp zduzix_eSVda`-O=?}mE2DoqSJd60CKClhc~jVh*|1@y??hV1EIG!Rx1UMNU_zmZ+) zb>LR$cdQvgtJ!QuZ*<-PsPci^VO;M!!RrX&axC1ZMG+FWdCc$Lop60dLwaO~@1S+? zZKtcbrk@!oz zaQ0s-J1Wr0Vu%un zyyKu9DnT(vY#D|gO{x%Bz<`*%M*e9k8zksmt1YirIyY0YpG3GE^NdB$Mpws0 zg^?B*E1S~R9222YGJFCA_<^zR@%zYjIjNpZzx~$DjGpB86Zhl(lWYM8Ao@$%n8@cp zO&<$w+O{LE%goef*;z{TTK)9$?g1_*4Co3RelL?$VFC1@VtEI>9On?f{Hfv7YKR*q zh_N6u;O3H@3KT(8F%#M2A|S`f5g6hcLuYSqnMu*~C-p!M4U&J(gu`$I4B+kEf)-%* zp{I8kYY9CT=}AD!jnt&4fO~wxb+(^7HQx7Z zQ_0MF1S6cq>5e-TN7{S?QKn3K^i8^{)TBD;B1YV(+KPX~OU|IgTQI|bgyLv|&cXk7 zU5_Y0#w z6E$a?OQZ=T%G0q2_!|5VGlRAg@V3)o>K}O}h6Qdnam@+DU*c>2P5+zQfmDDH#R$0L z$^V)O&J6<#U-Nu-bHSjF)^n&2^x46L@%}jma@V9>#({DtZ&pKX2TKIZ0?t+$DFsrB z(D|UVc>u>S5qsdtRy6)T*OMVw5rc+{kLTp*Ae+uls*(&c9s$au{4E&*#W7)W_@_Dj zozJEM0Q@1|)P`G4?CF;qLw#}?(UFZ?#k%+9Z%Nm)1ZH_uTlbB8^z*%w09oS!u0)CP z5mnpWGsQq@v0exXl;dDPyQO=l@Yzgy!@Vi5v{PfU&Nz?P(4buHFc^8(e`s8JXsCG& z&yE~nl6T~IT&rc|p|Hzm2w20`VH<#Tr%v$KYA|o$Ku5Da;1U)v^3Du#UEo3y+6A38_Dm)*61b_KhCD8xa4|xeSJ=n1%NZ!(Vkytjf2@qABw&y zt-Hbwpk(YajmE*35`G(RA>u>FS+4c%dIBXWB7e)B_TrM8dk^u?>?MvCuiaIaf*N57 z;zZ+FJ3$sgq!@}rXH%V4JIo2%KrjKbbjR;_Ktm-cH_3{xCG;Xig^qB7+AQgc)fiE# zm)VuU2>qG@BF7yS7|Q_V@0X&4f&Yb!KX?5IA$s;eGuBcA+4p+=92-MFLeYaG=s)-6 zMCKfAj*nR+AJR;qPwb0M($SWvrO330`f_f>_)99ck@*1QQe%EIY|$79a+svVMz!!Y zW2r`E;H=3gVKFl?!PhNt(&r4k z)|BWL76hwO|0td(Py|U+t{bUeFj7qGwCB44Hc;51QlaNecd^z{Q|$DqIFN_TEWJ+G zWRQi1a+yS}9kv_pNl5~cUKopnmlz3Nj{Jx_Y?BwUm^k@;Q=+OF$;k_?KCPl57@jeb z(1W^loK-RN zM_Qc$HzA+JW}g$4|NZdeqx!fA*@4)J{&Rc%XTDt)jZ}GUcvW&|f8W+Nqfw~h!&Wkz zb4{a=b$6$E3e84izqQXN#8Q`gJOnRr({ zEs*0gDBlq0I+ZEH_kiVQ?&6~GA`Q_1+*jDu6IeqVKtiJ+LrZ!FiSV2&>Y9fsFQMZM9bRd0$>CvMc3eyn48#J7Pj7`o1nqR4Hbl!-2Wq z{Cahk`}Etmo#i0E@yv$T{8>p-q=Qg6;Ba@ZH3=T`JXbA!2R*>9O1)UU7UwW!CEgyV zg5CV3uAF)+P8$|~AfU#WH(8SDz*noC}WNU+|IZ%w({$rMh$BfWu7c$JU_york){G!us9?3>Bmq5Ljb^!|K zakqy=Rm93%HB(GI8DHB%*Q9igD)4Ofn<7JrC*SAu-sN}iX2+vRXQ0oF?_=UQG96G; zOvMIO;W^$UhA?f_!XkGdoF2uG!Q_|&vf_4>-JwsEY>*s_EUQK`Xcy!g={^u3+KjWS zs7OG+9{ibLnQTmk+hzUDC|UEqPI)iZs-*sBgPB{K8BEwO3jNXsNYI4T^ftKx4Vp-- z(R^*Di5>{#Nf2S+}2+%zG8fR{FV?dgqqZ>fCG6&l0)hZH=_xv@sc(~c2lpA z@^7-1=7DA$D6E^ub#L%@NV#}2il5agg_rx;N$pBVr*>-DoBOF;eGG94Egc-wfW7DF zd6-0t!3y&ghvth#EuA?%8%O-9uH)H+p*A!MA<9T+awUqgFXUrE{%F|U{{6gcw=$oZ z!u7&4OP!Gc7>ugU+7)90uL0}UOVuNaNc%NC``B{cM-DY^+b*e?D%Xoj#Td}(i=A+h zfSGlYMqC;Kl)nyVt{qig9f|HtzSYs-wtZRY2%1dppTS2PkJj8)C+PAV-mgyV=CfpK zx*9v0e^__w_>8LQZ)-@hMxwn5UVE^Vb_01HZhymxEED*Q>NE;Uu6P%YhUHGNedWDa~TAU zl&a34K>h>D{)li|jGBY^gYsn5`4>p*YjIMsvUR2uA&21WiFMW9rf-|f($;wsfln;% zjcsw;st}@msYPk4xNoPDCZ{6QLv%C>oOJWLLj?CpLBcxj<~8I~2>*NU$1P^JooR-1 zk)^f4EMo~7{Mi7nFZIS~Qu#W<($==myB@y_67Rh{<)1mQXu|jDkaf-IrJxK!9sS67ULZwx!Q4N~qMd#gL>yHrdZJs{8XC?R+ z*qz5&QHyo)EsNZMzbh0aDm&8OYfgNedzDKRlg4OUM?o7n`YSkk4R)lfiCh5QI#7zN z_g%Lv$y4dC4hWP;DS`!&1+D!bv+iTLE0p7pQJJL6I$z-&?awKgL_HtnbndHX6ScDE zxM?l|-m5oeiIEMeqiC$Fb=%F^QyY%r!H=DD4A&3N%1bqmuIp46HQkGKXNJTUQXi$0 zEbUCT1!tg~tM*%_eC?A435?o&jdIs<$P=FXsXz8hWC!LwTJOz;bhFAwsfsLb z_3EFQiAUO^JzB2{f1VfGZa+-g<|8cE59NGED&JSxwZ|OCbTp69pKZO&Xc8A%ii#)z zduTUuzOM;M_hsc(4&1Ki_lkGwxaXsgp3&k>kLyPHmrSaYb7Hi&$O5+tUoAj`qC}^* ze7ys=<6%PIK78MIl&4%I-@nQfToAK>fVeB4r}j%^nK~b!u^sM|7yqiK&h1di9lK7# z`h_E#&r&S!uankkR(;>Bw~=n$vkoZ3JgfS9)aIpUzH8r)C*O9UQa%aj?4xjb*?#4c zl=SCs_|bJLdiY=jW;-yP*me`X^MY$xHgLaxEqmii0I?;|&t0J1Q2RW^mM) z&Qw4oVw{pEOF2~}!eWnA?DUv=DzZzn8p<(2cB0vn2A(XVCsD8VCl7?_Pd7&9$eL?v zx1p?$O2yMdHFzYeq`-j}AA3++2SP@Aqj(wThSVruCT%7Z=0^$3T1k8HYn@SE<1^1HWf~4e1BZ($qI|0rN{@n zc9&J3KJ&?Q=YRB#%5PeMxv#t7Hu-9=VENI!P?eVF+bh;~qyogU<(?7i6kk%e&$l>^Guagq;|dq^>YL+x3pXXy zQ8w94XkX~G5hvZBY9F|W2JFV4Qs*BeM6x+neNEBmtmPxM-vXW-@xh8)B2&v^CUFQm zF8AjRQf1K?8WQ7kXOps=6K`RylMJ@Fl4ax2m1p_pGWR+`JXZ4Jsy?z~T38jGu|Lw} z+S*7F>*uECt>gZD3fr=iw2^57F%wrA$XGI+BX9ulX??lY2)_&mI1}i?E*0>3KPQK~ zY-MG|(&jlzUDB2)3%po9Vwumf`4@XjT12kQ_&Dji*%Bi6y_nNti}q^g zn82>R*QF0p4}Sq_-1J#+jTMzJPP=|D7#usW8|2vqbkAF-(Y=Ayn$Yg_hyq%!NXcXx zPG)dkW?p#}@7XoY$#TnV*Kn>;i2J$tD?{V(n4vkJ@>hJRCe5+6waH_=?_e&%V>N-< z_P^OR+9~A2Y_le|(%7~i4*s}T5UKzZ;6YO`PR7_0O=L5$U-3f!yHvbO!}EroC~(;s z{J0agSGbwD^#-9S0T9D^WyNW-S=lA=Hy^(tx|QnIsjpxl?XTR!DU?B|Kk)E)O$wuHTM`3bd$Hsfd- zd~dA*%C!{FE-Ioq)w_*R@XtZY%2+3Prst;8qQ0QD)jI`I2MhzJXB&ReQZp# zx)6GP)h)^P#==41SR{X4Gf?N|_>XPvZ*N;)mx#bBoF9QX?X+$`+*a-LP>$hp%BQpe z)bk!B)9aiTNvFjxI8xyGRdwL--kaPZ`6Ob8uzsW794)wB{DSfcqe4t9PXs6IQb3%A zv%qh9FK^*^9w9sdlm7tky-R-L)udB+oi}$6=I;&|!7}fmyg&B#W?MTI&_>x{)icY( z|CTL*D`tgTE%aK3nsGD?WIMx)Lh+ERj08=OS9A!AgO1&XeM~P|Ejp> zSGMs=K8k)gbwhnLR{yD~HU#D&xVtT z;Y^RZ;)(d8ivE)#7VEvQ{%WRuX#6V;A#1;v9p`yZDIc#R416ZB#q%l4b!ad)RqGJH zG@6=1_*9@H6uj=Ez6uSbIDER3A@*ryu6z0%w6$fllSvDmcuG=3c!kJwAM>-e?_=NG z-{B;Y-Ez0VFvKdQyKW)V(Jg{n8e~C|&5b;_>qULOd-!&CL}?Vuk0(zws9X+_5YFq6 zfcHVPv->8F`6=DEaD(-1-ygBf#iUC&S!vTtzuj}ALsc!e#f)ycrIh+wH{HhOLoMxX zOH68mP%Whep$}hrZ*pFAj(x(?k&=KYHqTBy=wu&_pTkUGcT)J4(lw2XdxrXqn=~y_4;Zl}6g6 zUvz=)2s*K4QRPhMCHGEe0&4SsOEl8X`f2^_&)2P*8ka|b(@p_^#*(g7lrG!yP=e2z zIYJyjgIY6c_EM$OlY2Nmr?ap;L!aN((8bo>+lcfqQ|Eg-}nBH+mvo6TaUSPyifZYp%)N( zV=#TI4x!HN&$laApgxa9cLMc8jKIBs8@Rhu1|1yV00e#Zc}veRN`nbBv~v&t>K_A| zu1kMoBm^>@Zen;&#a(YL_0E9SXQqTmSGK_2qENi)#!V=V!Ai26gA=^^RcTAG=eayy zIDQVh>-KLKqH{$PTH3%xzJo$g*yZ@3Ent%#4$^tlB7~^P)!rADmpuR^r?1zg?)8Ty zy{l->LoDDWRQ86>m53nze#-ar?hS|X6v_iy&t}v<@@W$Rn(LC&erQsH#OWA=x%e1TIZ{>sa5sbF}&frrW|vj9)<0SeeYw8=PfOZ&o>5R2Z00$YbsmDB(}?^}ORys_!-TdM88F@6n2WH0 z#*v4@9#B>h-2Snec(V!hrpsJyb<*CrV%`3MsK!;dSzx`m00Zt^p>Z`T%ReH+bCg*n9r#A!pM8;{ zdotSat-*PaTJ3JLxwW?1H*idRS3_0dpb0jJ?eh@lh$7k%zkIPpL9gvEE8lCV<$Hg& zwn_1P0pA0mDMTu5|>K>?#m;)#_HmMMU$$kLN0m=Tl3=**iguXYbn!% zO~1<5UXj<0<7ly_syL?2t82sio|LfmdKlj|0~F`J zZtt>~|E*MG`S~vUiP*ns@+HO(O|o^x$h&6bp5$(6q82Tlbi(J^L-mf?0x&%h~x(7in`%_+acF#K3Cjk zn&r;b4l7?YvVj9SUajlX`UVuDA?F!aZ@F$YzkR)Re>qlrZk)~Qh11K7Hrb+f^Svr~ zy7s|PE*AuZ{j@_sK)C#o-P+^!mP0|G{W5NPzJPFtiRG(X4kK%NRQT0A`iks~lais7 z#s7H|Qxq`ADSLUMbJR7a<8&iJk=E358k@=(?De!}%l`y4TAA?PMa}*zy4vBM;C7&r z^l5j|*z_mX8@v7G)z@bO-+tg5@U47=$#A4TdtJS4HKW4@tWZaG!6;V+q}P9a+U)cN zvnjk@hw$TdY@8HZf>nf}SMeWkJ?UIIU`nAwhH;l;53Ce5y~f43ZT35=N~ESbIqStj zl~bE$dpE5nK!y%<7I5@nMTZ>#8JEwq7e%eKEOv<&fxQ-u@cf3zu^jy1!Yhzi2Grl` zRo&jAChOz1nhOeZp-+eR-1_)I-j!{wzQI9cnlk+QhV8idH+ZeGvOT2@l~XF<Kv`i)#%2ltG_~H;h(Vm~Pb>)Kpr__)nRQ#2u z=IN%VQER(A1!2m#jcW+|-z?|)Y|gL4UI*8iEvbhY<8|Mzy1~vof7B{3A+P7bhPU+z z$)~D@^dD8@of4>tvJY0yaz-i?uiIaG;GC zE}Dcs^GVRDYtr&Rf9zVPD}NUyuy3KMtcpv_pID&P-gc2DykbsoZksoNYMrrl>nzw+ zERPKrNhd49kAEyYEh~&BaSVNvc4&k!g@yoRnD-*sUHE~Bk&=?c0xy52&` z>wX}7eMBzlRajyAr1L*ANAX1IpAn+$MJg7MbxKg*?JX^T5V;EN7>5xZa7dTGozvI2 z><`DeZ@*kT$bESk1o+JswigC!3se4+A_TPVK*`?lU;4%AGDRFra()aFIrKtOTfGf& zcJE$vAKH2QPjMf%+O9XRiUmAYz&b&il%~{@)v9+^l$X1-Y9_CDB#!}s z{**`mIT54q^<4U1vg&NT9&kz~ZZi?Z@<1JJP}Rm13bcO0#r;{W9F@or-}Ml;%hj={ zVLp8Y>QSb4`9T}qzG45tI@}>H^M=uO4su4(0`SV1>3(Pp2!kgEAQm$}73u9nP!YhF zB7pB`V)mzV`5()yAI2gh5{V{d>Z4?{7O=nsvU|4?@4gGK--5aME#x5*;Zj9Kq*4(aXH8`u;!Rd zbJLO6wTXx4>uhKtkhIA9z?A)7^FmIKQaK{3FhdZ}$23pc&nyy>vm-UuI!TX`a^*xH zwFlO7zORlM+jn##?1sIbe6#L>>Y9N!vRSIk%N1b;y{9~O6DHa~(we zuq5IigXaDUKJ{`em&RcXM?bE-t!?0{y<>f7?!4}*Jm7bc7-qf%VPy1)Hsimh2|YotBT{xt5SC7go4aWyKnS$*6&kB|y*2ln42ev$a8|2`jXfx6?S67e5aHd?#b~H`N?MA%S$aHVNFUfJV&Rr6gXxY4w`Kd4~ znJ=A3Sy4Pfh1kWbHlWRVZEM~FODF`vW7G>BfKuk}aC0!tD#Z2kgWK*M3Nl-g*!6=k z3eUz8#OD*K}{RI~^V9a-u=6R)YADuPqUAzv*t-p)y}-LZ^0q2RAH=Qrqw)I7h> zbG;1&BcFQ*9iKU;bk)VNGsJQL4+P!qL=L~~Qt zhDi|}MRb8)$Q6awOO?zyrf}%+9%AwcN|C61d?mIoU)6tJLxBE66ffur{B#}gu1w8x zo@!Hp);7jG$bG*pxtNpfF|#VD2bU@Ojx9=&%Ww9*xL2Ugj!Ue`ceZqbs*2|ZWQOiI z^)9#Wn_Hy69Nl&NMf!><^wfcU*>&k#Q@HhlZ+lf&=a|-^d|l3dY`p-puc?w?Vjl3G zlTTtz?Q}al)xSkO3C-^+nDV^3b?*!G!3HZ?fq^m&)UB$&4WR`)-10(U zL_HWtM8~u_erGpd@Bs2ENwEfoCrqWC!aZ=5Aqlfu&1jsSoNWBZrfccZfQEQ){I-?t z|K4Mr?^t|vMJHc6a`iR?hxstK1EIc3zT-Q14!WrRO3Nv^5_)SL0uct$u+c;kZR`>oNRj=lFr zUZX6an9TXLEA7MJ`q-x8)Ixe8bFK>tk*lqy%a1Xh_9L>3d=5N)53G(D_%rP7J~2e6 z>8ij21?I>rA?;g~CuFDNYyDk_3peP*KcZ@663$|+v-tFy%RKZd+RrJ` zKa0?QJs><<$>b?;`{}~i)o3f{Z{AGVLL!&(_xQ?m@E!{_sWeH%D0XoW&W&*AT3mP< zxg&r_Ejuh9-%YI^V*HCYIZ0$X=e&5WvO7IFA7<>VpXl2y+iXb4!o=IW)mjW3sM(5iTI|0;$DCF63$ zUoeI8>m3VRT~I&VERV%xwOk)!gp!+s4V!Oo?)kinAoj1MCt_V=XY`%2ZF^BjtFCfdOoV9Zz?ILJ z?>C0FyqUw*8=ij8b?q!nTW#M|mr~InN1rF>U&9|P({Ai-I_)m39#SXrPCnJ@Rw39( z|B(GP<Xs_Bket7!p3_rhTp zuttg2^O%FNRTuF-^&Y(Y+fzUMU7VFc5JwPfZTWkUMldTq&xU7A4u2D0H<8@$BlRQgVNtugc(?dunKR$OTOzq1$>0u-Z27-4#Nm@L`1&NJV zmBUXH(xgAyy;nT=w#U}in<9(ZA>-4P2Ka9#8sJ!PKSf|C_&pgJI{PG8u|!{@ zfH>KVDURrjiX#nPR*_l_o4tC4if_bJfiYI@9mfB*(i_LN{oDI4GFdvSKModA)}CrV$#Hss zoNS-w%Int$w_9-P%(>6g0t~EAhZ@onB|8@(M&_YlU67H_2%nR_sb7{CMF#1pzEq!A z%ZyXlcIfD(aD=6H9k?vFJH^-<*rmBtzt@IVzrP61bVCx67Eb0|1o-NoSALs|v~n+k zA~izjHTr}9MK)~_e#)_Ib-oa4zEZlhQm?dS^329b7nahxM8UBMw`aPk%Ah8W^qi+u zaaNUD$u!T9#I5-pr%vQMXD=3eiN@rJgFvxFj@Vl*o(#jPQ*79?LpwUSf_{}}UGYg4Z!~Cz9*`!ut(27 z-KK2!Y0X2^0{l;YG@6bJ30f~zJK|=2d{c!KRza~LMfSQX{Z78qo&}FHLHHU z0f}w_#--ev!I%T$zh4bsPZZL~>3llOrGXQ3?XkMEp>DNj%a$W?Jm=zf{2K~SubtjN z0RLkRCTgUGX@(}+JVNh`jzok_|ywl9kv8AX{t`r!OKwiS)3R8@n~FO=~o8oo{tJ`CiRxJcQT&*cwXg8Qt~qB1m839DyX05i&B%+iI$sLZubwOAK6)M-nZ^{! znF2%I)7F@Pidi#fbZQ0=jIJ!D3XKaV*4$D;o?>5#DvPm{bC!tNE=b5n@4aiPTs|Cs zt`YGREo9f{Edqq4y?n=2%`Z;EWH#*$u>bLjl?k=8lS8_dI6+R#{tiG3IKef?Pk2aT z$FhKes-@}pF1z`}V}p6|!*GyY64qM$jAOFY|2~BLYg%pJvoxR8$QSrp$bti-o9PrN}+$Aspy>9^C4J$ z5VEg-To|Du{%v=td;h#zBvpW;`-8svS@tqzbKI-S#j<~8+ur^7+dQnD?B>)9u|vT$ zPrtfC)*hXKZ>M$pn}Yn644Vg%TrB=>d5S?>VFXqAA_}h{8p3Asc835=1OO$S!fj74_=)=cINjRd$`u z#_z)8CG9&Pyrq&-B+qjSnT|!WC5q-~l{;ioBrI@S;~0jInfJ}%S7M1}Z$Tvrt3_*% zc%Ubm)NkB>kXijO$i?w!8oy2!hJOBXg&yTXMdIcdrTiHAYasg6Z+OLNV7d7o`W4XX zZFUVyFA(5(j}kxCpV7IS?lWM8c->*>?CZAdF86zvz&tqapZPcy5c-t%Tk(2&bp0_zOr{8gy15_-2LAJ+&R)5v>dKJ2ev&bR9IK@7OWm}ARnf*NZe!av zqijbH(=wi7KZZilFE*e8F}Om7_)!PJXA?5zJI~#)863+Lld`*73GMd0-@E$UHKIu z2#>FR!jAm>bx8#AmiR&$iY4*vu1#5n^MKq+$55uV3uY3(ynforxJ^*Ad@XUhDmNnV)OqHMXm`on^hi{Y*Vy+(9vBsCD`)bnt5IcV~`JP?R|-`XA7KwM7trwO9ZFGfaWz2AdZMG%-v6f#>CxMF23mNWs{6rYh<|X_QUfe#m`NBqW-}izZWbj8%hP9kU)7o*W6(;x9_)I; zsSI8pjf%5|K6L{m<9^YsiUhceld5Hg%;Wb}|c$_oBfua@TJSm=i?ypmj8Q2V_BGrX$?8V)JswmHMX4cVi z>Bm{sD8X=9B5o1NDn*B0FD%oD6>FN%=}y;mjEhMVL69E9(H=uN?73Ou{TZW`n9=b!mZi)t~SfFdI4>O1LhI91zh6( zI!q!-^fk2%dcT-H;4g{eCUfB~|23dslWT|EHsLmj+jaN|?lO+hnnBxE?e->na*+by z$Jw+_U9881Oy|_m>XFfRBS$v*F^ zB(dOxE{tq5ShP1g%2~x%%ZnJkw1`P@lMT;N*u2Xc0dp@)sz+Trg zi_7k9kEM&$5zjTURoK!JIOQTc44NYVT%dX50$&XQxZC=55FfcEwqK$`M)q=1%FY?0 z4GoyTZjv@dH$?tj2y55lsyoK8y9P(-hv8iYd} zmYJtv-RRJ^aE$6C%yR;oqt{K`1X0^t7}5Yz>d(ks3j=Q=qW#F;>*?d_BMEInmI-@I z*gCfL`e*|--Uv!PxBl7LrvysrI!Kb-dVMQa#BSLox`UhdcUkqz!U9EQL8BizKk zC)J8=7%YwLe{f6YmZo00x^I)f4d%MtkPK?7OPwDT-~Ii>_ir;)*j9EOr`8(Zp%_mk zitnaA_zG2?nlR#O26D?b;|kt~8gGJnP`WzK3g1urdwR$i{QBwpe8>hnvD3XBpb$pB zzMobK$_H>&8TfWx+}!j+?`cr%#w|;Xf44({(Lm)sE-XQ;BT(W*j7cV0Dv^-D=lV?u zmdxvBLBq{7TW&tKGhJ1SH3*2oFwKugBHQwPy2!xVK78mr$a%-W&pTceu&uB}s@6OAdeUfBGwrAVq!e)~3E6Bez5Ihv6@hu~5q~I;@QMoH_p#H}Wrh>_WyZB!htFY0 zV>}<(wI8+!rU7kb&}SOq)9s-ahe?eXe~bdELZ9bR_vm9xm`(#jH&vD% zJ(b`-q{`0mUKqr*+|Lw|;oYd*AKF#vx}~XtbHIrdxK7sKWYMik%b*ynOxm9polKO2 zx3DhWKDJ9-=v(w1@=SVDywVOovG9E_q)6MxABVix6<&JFJ8ZW0L*?6WAxbcKim)(? zEw*WUoV#RtAdNk^vXiMH8xoe~-BS{_O-3j4KV%u2?iSk;Q7eQTDAY_KO5xtJ_r^*bHQ8 z4yw(I2)kHl@`}#pc+3?OjE(Lok$rnfJf3;ZC*?(8 z>E^+J44{fUjr8$wTyK+&=Sq5#ope*`9Z~@ypTD9C#T3G0AcYyMF1`064!G^SW%_6M z?mT;C_OTQO6!Q6}5D$vZ=>+7OULs~Tb~Z|XF@b&i3UCVl=sX+q?1Q3vH>v+^wPH-_ zb9K;t)gA^i#pjKr3bR%k3qS(IZ_%7~d?*PBgj9@2fCKZz$wixzqufB4bzqq-@x9#l zsifwR+^RnW0@Q$N;@X?6t6%?=mpW(~!lX%id$}1n_s)s^#k9O9_WQ0-ILB#pTae!N zrdYhtNO&=D1EC*Kca=pVmqw!?!DmitQlfD#0RkK9zz|e@2jkWk|85+w2Q8fEv~z4Y zKsV*a%N^2!e6f8BI;5lG?zlZaS`E@e_XCBBU7*jovVa>oi&wQikr#Xz&VUGTg`w+AM>}yHueGEerxSK>$}=B2~f5JNU6jA#1M^(PBrqaq6*zX-M9#CL!;Iq z7{eEh6WM2Qn&-5#R=BwmdTNY}K{oB#^Fu84JKwPt4ecmFf;Krc6BEOu+YOiqXdjAR z7(7^#^*%M6$~NM+q0s#yp%&?+p%*fh61PKG`vGYV1t z*I^N5s43!m&1*oB;F-L+XoMQmiaG>F>*g_0Lsr&Ifqh_>*5OXgWz!-A-~P1|0s&@C zU#W#Z>r|AlLDFT=o!a`Ky0PBzl#34eWyS7;m^80oJPF?Nh6S) z?)ubckspSjUDqD)_y#bZo1J?hF>rg%Nh<=TN-EpQwQ8}c{EjM>Q4X&x5bNyCMiYJIm=beR<)UQNv~EalMGao9b7+1tWLVqE6W zNRtC!Me9=!1;^mSFVYn74J$3Hm(@CqCJa-8t^nBJ4x=2K!4pV&7 z#K*RJUpk1`Vb7r6B}c2_ntgv?NDv$8nNei!P*z*&j+~ZL@!p@(e2YGWd!)X5qk-FY zq8u%`Y09uuh5RDzPR^fS(vYx70H}Oe$|ugHnJVWsiwoD6R&zP;s>)sD^*0AY`la9f{Rb(|grfnb73wAhuZ9C*3hGLS*2rLmA;!QJhL>4e z194q!Y~*xnk!Nc@jTpSt7+hE3Yr(WqQ!;-eDwQX{5Z>TUs0vzE_6#(ze6jYf>Go?% zRHM=w^w3C@<%Ul}**7;g3*p0ZnDiW#H6R!0U;Z5m7K5tbuja8Ko3uwy%hnuIi4%PM zhbG!1mztcnjJ*@8vLUTiop4w252=k|KFpu3-a|P>5P{8au5vKuwLgk|$MY%W-sG!* zyg{XY12yLKr$>3ziSA?)!cz1w*yk9O7W&Zn!r#t=SiTYx;7ieMzCKQLtY}7>{1Bce zD-CuDj(b9npB@7|I{Tlf=FV|RaT_Y?f0l4!>Q%(u{EzmtMA$=_qxi>+NvBbZ8e?&c zr7;*cRGSvg+S!vtWfZJRr!E%WO$!Nc|CVGH&i;5Ey7-^_1?USubtEI$^JxyM;xd4~ zd$(1Ro!4JTtg|X#q(e*f!ygn?u%>99|F4`?YwSEi%4XTBp6Pw6o`cc>NB+!R}8m_~)T^M?29Bfa_8mH_)h`Rf@V@os=j?#t*^FQ{J)4 zfuTny?P8o6{+UM5{0&pGHu}2sx+N6{&3bJS%=Tr(fpSru$-d66s$PwPuD2*HNp3uw z+i*s>1Xayb?`$NZEQaI>+fo(NIp1SH#(Ai*WhO499ia^IS)>$?vY*~;FoECfEHuj> zyw+W-%y4*VmTmV0h10#m(YCiS`zytE60m1R(NwITo=z99Ki#F(^C$5A=mFX|AztSZ zhsT(zN0ec?CpASwfR=v9h{*q+FvTvHqxNd2_>!R^59SnlF1Y4hd5I2JGm$lp5Sce3 zmvbn^P=A}mz}d>5OBLafW?&I!oYR#o350D(f12%glIpo@_}&8G>$I}tQa5-sQVCce z6W>dFSHvXI7?CB}tM4oEq%BcID&(B}x);mv!|3gX=7Jwvz}3gz)>@LxQOTFs8O9CeS=u3Anxc<8PB}9Mwso7zg`|=Ba%NG9gLoya=G17AcO0b^4 z(KOmMayY*W-qXA(=E|}H_B8h@`;XE{^Nstm7Wg>@X$kjhLwp>=!@nJuNJs|PbG2vD z^C7L%NoW}AW?}DHtU}=Ck#T#AMe+MjtM1~$Z=6x!-=x>^B;kj3!^9P`ufh`3FH4@g z3qP~VtsQ#KZSCi&o@#SMMe5pX^x9OUSJ0Rq1DOCVC9P5nNljC^F1NEX5M?BZ;`&s( zl_&CdCD9Wzi(hTFa(ercQ+8zsEFx&ki4@0|dsv^^s4Q2dsWsh&e2v zu;2mUmfuJEfp_^+(NWIQC4t#Sq6wHtLiD5=r0YbwV;5)q4-p)~;&mO-5@U~vALaETGnp2nZNWlDbyr^_GAC*Yu=KIWv+*)VL8IYbo zrb(V{k*#!TKg9Wt)jebUO{vf*CuS%c)w$~o7?aIxJ-TAcqh*$iAa}5@X zq){Mf)k3D2V#inte%@*h3()spX4b@^lgKYweqm z^=iw0A|G%@ARz(tnru-gR(nQHfF`^2;xnrZPC#}f@f?M9?z=Ft{hj6S*W}}=s3Gq8 zL7NGrii*r4fRVPBb2smqnAI{?KE&~ug|2UpF!CPyX-;~$f-j@I6@^I@gvY(@2~_gv z_A#^cuKQ*BWIzh@k_)QHUbaamnCdFmd1f%#__lS+%tb6 zt@o*xu$P|es@nH?Lk+@zPgN58zYm>oqKeilWl|_v&|*I$%kEPUx`>8R-~fh=Hs6M+NvXsB ziou>)52SZ22<-tI>F8%`0pQfW0dx4X;P2Yuau653TW5_S%m4l9mvPvP=w1fw)GE#~ zMf~h|s0|4~5nJmZA!1N>%gL>QT;}~B4g+c75e#GqHW-p%!w5mE=t&luGQ;U-_nG;i zXI@jiNnE@W)CDVyZ*q35E`FKxv-9w|dx-P&TVmy(cgfisM)jbx>pdEP@t#wE5(d>0 zsg{xZ=gNL^2%^31!;Rt3jR)UMy`*Y^(6H;LJ4C=J5DZx2GdEQA3|cRqR(K|H^w?OP z%RTRRsp2g)Nv6;J!vgor{2WpEpalQgN*2-HdIheV30BuAZ;T~}=YR5?TcwWHpf&P5 zB#@z8ZoHfCS%I=!gCG97YmR{Jjt?_g0bgNOtzXyE(zwaVC7 ztNzW>FngPAc%jM3>JO)79IZzhOI<@aC5q0uN(YLZz_l3nOybtmp#~ydHPh0(REy_0 zTH_}?>teEK=3Xh^CBG`BSPB&7?9zmobk=~$W5Yt$M>WYoS#ms=TSY}R8ahtEIxYFp zH_tYyxon9D7Xan)YgI*8 zDeehT5Bc3yAFCcSYEt7+Hae?m7`|3Ky^EBhB{Y6jAt5mdnovE2%iv42kf0gvZ zWmnwzaVt!uhry7Gj`N7r@HV79#zY<$Dvv1ahSae>Lc`@;&Iz?pl*Iw8s7Og9sc=-f zapuioix}*_h40m(i}wt)&Rfs*==2LOb>DR}*3z)1Rdk>);*|=vG?H&*AE|Q^<4z#m-$UCp3c&YRc2Pt=LjnKG(+*Nyn3mrGYwoP;q&%FghyaLIQG?v4zV$03 z&ffG~^jCx7$dBje+WKDEmr)o^-V4?m&q?b6@@`U}V_8FthfKrLcFFLf=afB~j|o*p z^`Ocm_~Tqru-VUuR;}xS-E`B~h?O^>i1rpr{WlkOVgsMhg+1c@6={;Nuq!oKrwOOv zjRrOqS#?39e`2T)SO`3)jaan-H)i|MtO{tk&$lIN9MyDml5^aGMlDW@GK0B(L!QI+ zQyKds6f^F*UVW*yw{`3OUdz983!~Kk#PUIJJKzHZOkwQ+@MbpO~Vt_=y zzRf(!aj!`QSZE#{)2Ed?wc0QF&EHdf#H8gM(dLi`6&YyCGQub}X;tRm%GJJ&PXbJ5 zyiLh!DuYkIZkh`T^L7_8+i!_H4au_~U#f-HSEIrRUoZo@MBW9!f&Tu>R~3dYzq{85 zo?ZUA_fQ2d=~?xLqZ(+wzK>m1$odi3bsxid4tPYNO0ZQY)&5$fdz;|MjUSRsmdw7( zyf*_$s_UGZZkh@CRQEUzW?kWKXJfgcM_tDT_5EU2BWyage5{PW=`thp+p;^jZWvQ9 zEON0*saX{DvK4_)>@)O{t8JiuN*0G@g-n&S12m$A}J94aEl>i6Z@1M6y}(}A?s!=?4UnH0_|35TO?WuN{*PIV?Y zZDQ8h4CCNC@=ha4yoxEK233Xyj6n^!y%pNssb6=L>vsXKSD)R|rco29)NB2DM=Z%;9QEi|V10+6UtnK#kGNmAHd&}>a99)}N zW<}h_Ort=csb=H*I}-;foudy5ojflKyP<7g_xu}eybQn!4OU z=gTxId5b6!A`gzhZm-RSGjk?>q09y(<)a;Dr?{jttO}nprP(C+0V{GfN4O4SZWKFa zh%^a-dv0taSk>e+mzB0lOu#_cx5i(~Omo%QnaxqoTLHf0^Cz=6gD<3B8bF=IoBiE{ z2nb(;wjhq&Ek+9SH<5G}Hf4oV6BcqQjv;fWVWo5J39!WQP`>!$(k_ z#;@j1RVd@C9nJP)dU1MDpYm9gmvet3uCpD<(ES0@f9ylPE(LfbsHyhhgkDGceYibW z$Ge9DmxBv;#82KyuXWoPvSDiO*s=WgD_8Tm?`&C)C#({mF~HRx`xVNkqDv8cpQlq} z2@^b|_l*)4U{DC{`*yIH0Hyr}mtW;2>>fX1tuswTIuDUS+U0}{+^VPW?|3_>zz}9r zbA#W{S5wjBxaHvY^v4smKAE``oUxrk0=W>=QDS|lT8XQrYmBRgy`U^)JF}+3YM8C0 z9eI7l+z7dMY9Fi-3y{sSv${5jO}YT(9s|;>FwoS%i3q#LX>Ed8!*z+1h91ON$}rMA zLQ%&QsI4qcT?s~UwGeY6FNzb&7{K3Z=+fc?t(|RqmedBf1|nZ} zifT0;(rJ4s3y@L{1*jp%%J%FFNFk^sI4QfM?~lbLsS8S5QLaJe z*lCe|wmCjEQ2XrOlJS^${-=k@9%l1X7}r#0P(g8AkbizxG^wr+v>gI*Ci6S9IiJx0 z4l0@5$#NW4mwcZ8(q8ZU%+K@B@7%776sSdfAR7nt*(+)#i#JS$j1)Pj(HjlbzO|f( za&6lLBaB$JFqP{T47IDsq~5EDM&&KTFB1}q<3;HM>O>=*#0qs{0TthM4t{^gNzT-% zUHpkMGY+LPIoo?4e*ARx^!c;$@Y|JOX~fL4VMaQTK69v%Bo_$msh8r5VM+ zFwq0{H^K)*@5&9?rN9)$pmEaXh&qRpdJ|<1q;HUfKcG=U0MX&MB3CH24c>w z5S$Bk2C3yYGL1E*QG4;$ErfXD_p`Nm4{2HHHj88u1Rdn#Kv^@x_t{?Zj5_P2Y~;L#>q?R<1tgTg>t{xzA0Q3i?U1y$op>DEyWS!c$R2>hkq{3>hnViJC$G;PC4j@UDs_$(l zj&J{otof>^YEbTpx*7gKo%?K4B@?~}(hIGXtBM_{vPQ=<3eaLS_>>TC!31M2 z@M%SgA?4k%HEm^1sNq`iX2PSZP=rZ33tIenH(#S(rZ%45vuMvC>Yt<2rol4Lmg8iX z46wN*y>ROY-__17WxWgs{I$xiyc$X3d8AnlG@9#8^KS!f8JfgJ^`+vGb_KlVs-^v zlD48)ReGHRjc<*Gu2^h6i{|SpQuU%b?WnCf0ms(JQK2BCIWEfdrgGM@oN7j6%osA< zN2}eTS7kBNEQ^Y_=rqM51*MJKET0Z)^wRT)FFF;!PM&w0V}%M4=1SV&vEOvV;Wvy` zUW1u_N4o{5EPE@d3*vrzpsF56^scjr@?^XTF<)EM#2-%|h??h%*jLmSf#suq!ozE} zIY7+Ds~^D2rgyPnuECB7(S;PTlAiH}OsFv`n|ksf|C?M5njztHjdTn+pd9`_SXKlT`eQ!WmR12a zw#`oJ{znJ8<*CZ94%S0zKJQWQ-)!OVmKi%((l!k~&YnitWpSJus|!wNh9n)-F_p#3RqiMlZJ+3TPgCuy zbmaE{zN%dBG(udSqQ&$K^A!AlaX3pyZ2o@$%L-(AmvLTkRWPDcPvb-^m^UXI#WERl zy(5O3mp>-gNBwlgiYt|-T~=vtPt^;X20D)L27kK`XjxpLlYzi9LT^$G@)1j*i&8E#dN?8wdNJ6j0)aBY)dn*-}!J?NB%MhA6&3+I4SWp$fh_ z=#ymalX=edFN^uAJcOTo*;=Uz5Tf`9=M&urHY;7DkAIynk(?Yctb}uYNP*r5Bz4HD zx79u=D#r6f{`&FDiV;eUN8>nz-1$vAg8NxhiM?+?`lpZr!9sdL1I4M@{-U7v@gPt> zxd>)%DCY`Kcc5W5{E?iB1TP4l9nYUZF+rW;*S)FlVDyggF)8>B%a z#$Hx)zYBR=WEy{rQAAa%h)i~J#m~sEOwA(_Jgj0}!s<|1$CGvC%QbCEI`1yq7aj{m z7~)0Uf7B6<(ujX13^mAaYr~v#l+XcE!9kS%pf}@8>mtq(j3W)0wL_95du3;^u#Jp6 zo8~eY*W4=K@us;@i|wkL?QhIm^nl*07Fo!vF=R0btOi zdBhEgZ9iAjFAlIa8vRO3GauvkV~iLHVI2d%y-3FT0*tx~U|yY)6Lv$Ks{h)#d{1HJ zJu7M2hS^M5Aeav`6Kyt%6yl>7}l}K-Vx5RA-QqMG41}UtZKbW7#b>)`qCrmf{#( z{gt>i9#&L{S$WZS^zO&l2NJC|iJ#4EHCue#UbLOw2q7hVF~SjgWp?_<#LO!6#3(4R zW;dmS*_$?g%_e^_=7Rw-!un&#htED%y+KFKDPlVSQcJv>Cn~m1e+VJvQpH}XuY{dB z9c9I&*K}Xwkf9eC8*K$S>=dAVIsLrvwIBr*M<;Bgi2s6&o8yEz|jmZC%xGb#S#<6XWK4I^kwVxrwh1gLaBFh#3T!uXQ z!VUGmru`088!P3xi*yz2QCQiz!d@Evcg6Aae8EW)tS_~Ye?(DtMT3~HqdDWe^hOeo z=4_jId0DC3qR<0_LOZ;}Cn zIRMm;1Xdqmmi)agl5^DpwD-8Q*I&wh6d;|X37Hz&Xiv9Bj4XrQA`rz}o`)HMhz*F4 z6kVH=Q|69C-A6QhnO83QME^y^gOVJgMj1|zIf1-*?|VX#i--($pbgp9BMucgC+53| znP+8(?s=HK_m$%WJh8`*4bK<-=RvcLI!@aS^k&0)Rjp$W!>|ND z4K(xgfoZoBD93m?`>;%VTY;7pNeXuTv1Rcp+h%h-5hNqaL^`P9}aN@EtnV0D*ld8N?@@CGL4KIe@ z7`^tueVUifp*9100z}xqn@1wlZ7CY5ddqEvDT|@*v30CW%C_!Hl=v*n?^8oCA1RqO zn_Sk5t=a){(#$hCoT)(E@Mv0&#qh>=sVU2_fy9}W!m`NwSN+HURP-YabQYXyP#Mi^nQ)1bsqxQFVd-RYa=FF8Q6>Vg(&j3}X^L*Rh`;n`QpOP4d9H&{$`4qI7AJ|gw@iPeYz_lQ-`Ej{bc?&|=Kwa#5V6mS z>8uTx@O264-nOt)O;`!uW6qf)FFDs(5@o$y4<6r5r+d@QQWA38gJ{rBq>!qSer4R& z8NnlMv>Bi7Q&M#d(sIAFoy{uK_&{PZG^ZNd+XY zPKU?-W|9HgqFc|8Kyk(8Yl%)W^(?68J9DAQrA!uCHSugwW4;-k@d*>Vnd`oC}Isi^69=sa!>pfm+rVK zWPTNC80>5+r{;^UFRBCs=56{+V!o|av@1(A6bxyLG!Q1RDo`8V%*mTJU^r_th!F08 z3Pc9!7>cy@u$h>XvL#5ei+Lt33w1_|B+h@aPys!f;o8~!Cxqv5IV`Nb_3Z!G?*E(5 z(}`iow=tF=>K2UF_oVh1sqLN^@5fjsWrkWcdZ=+_`DFPXu9nM%DjJ)Snlb6DuKp!r zMfzQ7$Nm#Pu_?}zk!aYMUC zxktwT9mW5x#xCEW#?T&VR{}9}J+zeI<;mDVXeWtsaIDXx-RY3yqLYOhv=?Px?(hKXJjt9hsIOvnU(y?&`;0SUwE*Qt^oKBBiaG-1JpuOnL=V z(HoH~C@v-hE~-ro-!-}{`wjp25{t&YJ9=eVkn}Pa=c}SJTdicA2l4tqwP@IGr^D9W zEB4`#N>wDDzRN(qkfU0vCSN32gK0Q*DfLdW!5_7bssI0dFkxUSLa(68(qf|Ezmod; z-np2T*}2siMqTawM2x3 zYP1iV?uM=B&AR=cN|sQB5`9Whs+q0d!;q>eWw!!*%9sBlN>6;|ZB1ud`#8n-8OUfg zL4=%+a&~e5ziuaKWCF8;^HuZ@mw$%nnCn2Uini$ka{%hZF|NuM?oyR*$u|k0Y*w3S zVTy{b^uUhmVhAP8%b3JOSQxyhqG2N&y(pk@LNSqGmlzLkCOq41jkZ#&PI9Hyu>kXC zPKS|(an9uV_jh##Z>1RohYiX21Ljs(SNcg7;ZAOw=s%bllVF@<=G;OneJjr|(I{Rf zPZ->F7j$T@!`M3We953DztMs&i8ZSBUm0T8D&l;x;QY#!`tK=I5-(ijU-iaP3$#6L zxGT;74_jXuP}R1zEl5g(bW2H>fOJWhbazNM!bVbBx;v!1yQRCkJEb<=-}2mZ?|l#V z`y+q$a>kf*{#Bxft z$r8vCr*H)=6Z1r3Mq!ia)z;3u?yWY6O|OxyO_RKx!jAo>H`gIuwL2CAlTfWn&8D_q zwst;{vy|LxNSeY)+iVV7<|x}S(@|qawtcC=fz9>l;nuA3g!^9}*I#C44bE~Cu3Z>@ z9&I9hyOe?t#LilQcg1nf6K+pHLgx;3sJ{T=*UOc{whOf>tau@BuhSWU90 z+SD*H9K!rvk-r?A|41qq*L#1>Q?dcUq5e$Tc&Ecgie*F=GzKCAw4f5MO!oTKc!-zq zL}0rC#K&?cutlk z8<=ra$&3H`U#0YyZpwv*$SKDR5B(NOt`a&HDota#sT#ifJ#lBo z-~UzafB7}iuOWN%=7svRhyw}D{0Op--{H;A%ds1bRQjo%PYW^J9X{dY;e#~W5}{kF z*j>#2tC#rz+TrOp@tZH^ zy(QG{V);v5ib1oFV->uVMgj=mwNfjSTKwD8Uikj%l4^0JNS52n&m0LMp-EL4EU>mI zY0IbJzvUhENex-Voidlw)m+HNUW5O3_Wu1x;49P{bR9Gsak{5%wM1Ge2Q#;kYymz4 zh8Zy5cp}Ux3fV0Vk@ZozRjz0~&da?ahQtgG-O}nSve&$2CK|MEc&DG11e}EXyVF-}2owlrR)hk-Zc1sDu#k-2W$~Hw>7>wvtSGnNRlgL7sNQzl z49wwcCX}%;^#31S^~Xf*z4hLB6j!q}suUgDyz^0Rrca+U^Q&-UH(&^^d{15W)=CML zn7~4_Sx9P+?xM`oK zc-hkYa=ygg_3wiFx^ub_hp0#WMsw+Seck{Ez!nF-*jV;GJ~~S9>QE9#-_`4v`$Gm9 zL^}U4Jp9v7LozHaa>ImdhxDTGyZJHWTRKdYBa93dc9O`0rhm)gkKOy@#KJ)JL@8^h ziuAc|q!5aV+F|!&v!FZ3OD}j$JWNImeHemhA?4NO){1&LQCM%7Hn8tlhf^?K;L$!B zO)9yw21wMPbb*?=j5dcgO@PFQE0`F2m6659H!h^x}AE={Saf3i*LwK1E zZrXKGA@N5E{&7>ZmxeFamO%)$whG2xR>=(I!5gPVE@D_j@>o1lno^@AV-80elkhdQ ze$Hp8N#Ia95qr77I#N{aDl&>*V+wTeUa3_@ChIuv;}GCUo-t$RTux?Z+GN_-ksjUs zt;PQHXOrwR>RG}saS-`l?VVo8HV1L)2$e((9&QxKm&IO7qsOeMm4Hh&N$j6m@FS$Q zw^yY4O^L0hqr#@c@Pp+%%U()k!XTYc&U8!(Ep-TQ)|1n|$4k}W81hPVC#EWi8#G|V z<_>wljVS`i!Oj@XEC(gboS3cVoB2uiQyP8HQDj81mH`13+aF*_@>m@0mAtkx(dYYjtLe)|_Xzm^^Hpb&{Hp5N4xe0Lh?bp#xlTQF71BF~x?mp8aO z>s3rH{1cc;+~uF!%K+{ z3JH;h{LaNlo53*q)wODTfSbq)eF^; zZZEIz9v0M;l?dIyP88wr$PSu>0V#0pL6=2aaTCsdNY4 ze-3F5_dedQj3!m)qg99g&mBy&GI-N-CVb}J-sU<=dWO(IlZiLC#Np9cH3ik!4lh=j zi{#zm4n3pcTKN`s?4r+PTh+OT0e7WThmB^r6o;B>NJ~Y?5>HJBW;jv#W-wib6fFJ^ zMc{2MM8X`C6&HC{AKK&iF#4_IBUF88?N&5-DcyERreLI!d(Q4mrN5TPU)JVtqw`nH z1r4bdyX_g89ABuc;t<4AsF_>a79E~o$ZS&>z(!pgmbC-bYNj9V_+rK;Sl&^^qTwhs zb?*&pCEbGP$hcI2cwsi4UkW=pKf86f5Q+MO%1)a6{m9}8`aiddz`FwOy|Le9E)P5N zCyVBY#htkQ3NR&FbZCO&tZ9Lo(uB^_6o*QQ{6sCwBKg}3x)%qrJ>=cz`byJONv+|f z6r8?IshFsoYH8D-vxQm^dqoD)e;=;N%&}+}sRwh; zloTqv`z=&y-bo#0B^hgc z;Uju8)>8p3>&fr3Q%N!=H6~PX4hc0!#N> zvQQgn-@KP*jx|i65ys%x5bdm`^iXVKL!Jg>vmu-2Tzwt`x7F($ne91LGv)E}0;wzX zmq%@gBqNO|XH=5BoFHyVMZzkPG+6-@1fr&=G3Vi7mraV&eYO;+C;7*1gb;fLrEn59 zL8uv6+%0T2PmEjA5bOx2E0Mz;lhsYTT$m_At(qwr+{k>zGVc8HZZHVGDlPOBY74D9 zkj;u!j|M!?gUL@rStjg730v88!9XtDRzgKpJ) zO$EkrtWpWYV``-oA9`Q-D|;+ZU0l|T-#XgJvW)RthGL^FjC53wn!B141g3t5wsJBv zv-c*cj_&uq+!Pg2lAncQNB4vV{*oe`Pk$P(nqOMWXkFus%= zz%VAHpT*q303>QA-VIu%73r8)_NoVgQonK(;t|8o8G85A?A^ZH5L}TAS@XexL-^B0 zmWs_(V3Y}j9NKO&T*fft=-IthW*CNl<+1)NgajxcN>MHXiFi=Sf8C%1EKT{;bk@We zP@OBxRJ0>bRNKR-K6#QcvK7S}ViCyCPdB9NzVc zl8<_?rO`Q$rrkS4V;W;pIdePn;)6W{D0D*ybC((S8*9lKZc9=2U{2SIAJzHaQUtRb z3#PTuB^b4}CJSqzcOSjDxGy>A*~i|2*d4Z^}fzdEz*gQD#G<%bw(cG61F82(Bp z|2vfl9DzPRf%RCz?nHccf0H2QP6z3s>kTS!C7OeBDaAfzg)t1~I{ynHw$FSLz_k$6 z#L#rmrzd{aq@9s%Ipp``?6bN3;$v@0t`?z3mTev zJd~$J--MiKpB6#ez$^rv?3n8W#NBUVkr|LcMYgqjqckwE9;Z(Q5 z3iJzCw>Xn})EB=d!GH|*dB z?V3I2^4*KowGX}1+lte$o7$NRv@XL*J0IV7i@KOhH`L!inz_>~HaqW$JAIk8(4&2q z5w;!arpmVi)+ZIW6f%n4r0h*K2TG!ygIUQk#^_~O($dqGM9Dj+?k{GmS428C^o;kz z!m}{Jb|EdKSjz~A=?l{jRH#jhDigS+QtXuWHF`W>UQ=r!0r8^5D5+>xJIT4hgxuB1 z-`Ti;COSP0Y+d5y0X$cy?zbV)C|QSChng?O%m_v;cP10bnaHNIEBIDRKn-G?WX(?p z@+E`$;=ymSGKX0+t<5DF)IVlpB{x_O){i8o#U>bDGCEDBx)=d^ zGj2ci@c4hcIWvKbpzh{kim0#lr^i(O7XY6Evue&1RUXM!&Tz`x54)aA=!)oHk$L_( z++QKO?YxO^Q%LZYBeDZtY8A$Le%V9M^Bb#eU4{T=VZ&yk+whl*gNAFbot{hCn^?m` zhqsd3fkr+@cQE_pKJ)qPG$9k~JUg`Ye>@vZ$mR!5Jl!mOn{^tmdKm3f4bHcGg1Hv#r4TwJjZvw1D? zQvydbyY$AyOm$XC{L4gOEF>ZKbiQSD0f^NtPO+D;QZzsKejJv|NY zUx;1r;Ew@na*q*uGsB1E%g)GL2eYT*@y1VTkP~U5-po`2KJMh@CW27CYDB3b#2ts_ zxl0Ygn#9vwBcfsxSZl=Ng)Nn;#uoa%8Du1XItx*@vm^Z$_sOcWCdFe916(@gwrI+w zQ}6CMV{H_r7~^muz6E4(T_@5e=-X9OSxweXl;Zl27yTz}GGT_qG%UHw<{Z;B-0m>H zMZ;X-D?z_S2O zN@}zA(tTk_*T!ZA)?$&%dWG*_((tx|^8UU~gU#qtVowx0t7c^?HvrtE&q~Ms`#63CS*u5h(R({lG#x`s~cOLIJk~Q26Vg?+okTg15CZ$ET*n zgRWDZS8$J8u2-0NcnGlQHQrtUOyf*8E6oK6sofNaD1=_-NA+7#QXR3XI=E>7F=>Dw z0yYiECaz3n2<6|IJ8yL#FMBn*5fLVQ2cufld2thjT#I?Gm0IDU@lVH#SuLJdUp#9~ zrwcoo@_5PJ+`Px_lxs;=lQ`w0@jWI+$h(~Z*b^%F`8NF-9);&rz-2Od!< z0Dhei*cTcfe)DxOETF94?Pfiov##~BDNqHmJDOq@%~enfJu#T;1I=d3{D2JJ`_Pe$lGVPmKR=T>p5fAqQwY0{00|Gh&_@`b`Hz@|k@IHA{bB0lI_uO<$xKQA2>%E8VC^cXETzmO)v`GplWZ zD2V+vq+cE2{YDzg%vF8qoXHFbpSYvp=15%5hdE&BhScKA+Me!gyU@fwD0hMv+a)N2 zEmShn!g;!K&MxgK8HWw+E}Oqv?gMQs5T7q~pE3V*im=^D{NnF^y{wuBd0DXi0K6x& zySes!zCLZ7ZISJ7h!yldlFJ=20Uf$ZljkKO2D7SPTG2#TfAHj`mucJ2P3{1i!aSY+ zL~$1nYUp>rn@lJAgCLKJNk)ve=q;e5wOB0Js$ppboFVUhvG&s8>ReYX*U-G%r0&^G z4Z_(&=J`vOVQ>y;!aaIjQ%r9F4sLgkjTNNval0<6Gype<{?oS!LF=9*T_WSQ2cQSV z1x9JxQ&NuV!#a4mOv`;3eBJWgxmY$&f{=xfC|}k3K1i5HU%H8fU2kIGTTe0ET)XqU zalD@}3hxI3=Ow#!)wa9X8GSP|zb=5d-YHS@W zp|Pz*6W*?mcb6I{(a^N2qq_W00QVejX~Ry^DTn7d$9jY?viwBQ{Tpk#KV3JRI?ndx zJh@1?r<2_+u(>cM9K*(aYSBjdwg81-GoM!kOK+jyDbJH&chFeYj29sgD zN>c#_eE_cS?Q&s*)mL|a+*CBu$9~&2n$GVjjkb~Z9U8ocQtc-IDl!bQ{Zni7HWO(t z~hsto%*CZf~crrk~D`9D29RWeMp z-Y=i{7nL?jUM^ZU9euCYq#h?iQCj#Fx&^!;ZWz!6UfK_k@tf^Qy62_X|7fhwpz@f` z9%oq6@wmt$hNe3v5Z+2Z3;D7aSK0jWHdyyT$h|W1tYOq$mg<$-Dhl7t+G$n#qx59j z{nD<#jpzO1I>5V;6?Ft?Jk7%;tGZ>pi(f5U59gz*xO?5J(diFcwnNOl?{)S2bKAa2%M(m*8%neRoo?>iYOl{tH;sj2f68W zfJ5C(Abk}eX;yVlC8Uc75#nK}@fUlANv?z4}h!HXkUv1mHHX_(w z#^@o3^i8UO;w%H`k7I{e*^hwtYg|vjNlu@oD&an^&kCcDsbLOh!TI1~5Tw=frsuxp zYJryL$V^VrC*r5WSvPh7qv!1)R}XXRx=v6^)hM54DBKL+;0bVxS>OcJ_qm~JhgEfg zrg(P=EhM|NYJ&N*oLB6`ZU$6aC+=&?JoCQve@ES6Tw$z!K;wbzGjJRELW=#Dyucav8ia?#g-E;|ifaOfe%uu+YGIf4e! z3Kn38Xmw?{i7Y}gz*{7h^e|lOk&G0w2^}b zX2qmaDbpbw;XY|T^`+twyhnuc!l%BpH-ynd0h}Ht2e>)5-fZI65dILQBFDQt@Bml7 z!?c2xA-3U3N!?0e7rp`#ZtJ=~K4e$w8Ez;1CXyk-Z5ZF3DPc5j9=v7SO*bP=zX@Wm zxSk=oKhXgC7%Ul*oN)nu)@uydp7KANNWXfouVtv#g5T&>$;?iY=}Kh#&Sc{gchf zAi~8W2vDl*dCT_MJKwj#Oe)9-YP@{l$Z%havKEyfPL>9E zrZq!OieiAup#L2f79DZ8`)@-wA`5P7s-;&rQ}9!RYY#tEn}t*rNUhV|ujCbw;J2Vf z=}w0seRwacAQ65od;uNJ-7)l5BY;Da2jQtw^weWRgfA*Gh11~GKrSCbM3N-L@P;6r zCM2l*@_4qXjVFNpCip`gT%)xZTB_6MN){S{5^bp-31NgP zJQK9hF$q&NXqzYdmmGQFYzZAo))hK1cuL)S*lMAAfQZw!C5 z55PIV5yO?TZhO3nDraS9x?=W6#o5HwCD{o{mL7rcyT9q>FNHxL(32NSPyg}LhYO_f zWs}uOuY&5x>oxM&8RjD{Ebgif&_yEN9|iN6KTMzr&ekfQvw=at#j<~b?91MMV$>HG zZM{yr#i)AFal`J9(>Wg^4CIL5k+^~A!z^R3xMYeJV^O)^l29Qb6)e5es=9-4AW zDU7J0i~%omZ^0x8{5C7d87(7$-#U*6szuO=E`3C|mfILGYLF@@#aj<(zk?7Nm{56w z4P#LQxjokFV+Ahq1Crlhsb5eeJh^^;NEqrz6{H;vg`4p@ z119aA?Kx@0=s`9?oM~r(%Yj#da5V_>7xNhp#4AH<-vUHR!ZoA%N#p}jN)Fo2l*VzC zhCTttg;(==$OE{?TgK@h>Gq*4nvNFFlq%M8n}FA$u|d{Q8~Fj}JAV?~CPWZvcr3Z# zEo24j*=xzfE?tTSRijV6CAL60e-(^8vk} z%Y<{p{_i-#lB8WwFzQPbLr9R2){ov+=G{9uhb+H{1T~%|1|zI{e+!co;L~zhlbd<9 zD~Qo{1dL1>GSaK377$z&OdjNDN9Zqz3g`}?l`9Fm^!vmHxHy(qn(170Dt8wPn&ENa z*yi#aZT3X3tVt6Y;X9Kg=jWq?4b_rXW*8`?)OzMJsl00^i7Z~*QLff@u^}jSLpCce zM>k+qq=XhyotkRVjkD~aCJ zkrj#!t}jEK<|FCQ2l~<^!#((c{D$bIHua5qM&)Ng%^}+R^&{@>J%*EuTlX_)rq~dN z;Yi!-b={9TT*U-i4u(I|YJ))Os16J>!^!Oa6jE`%+{ewdQx@tkq$9w z>|_Vn=eMKVyOl62DcnBJ6RfkhQ9<6Ja_qGWOGi{hd~X^(xPcdD-re&IG!|o!GLeI z=_I7@szIdVbq6%N`NyJQCoS*|`+3dAtV5Il(Jy5t3UTYE6rbmL+mTAWTZFiYS#mpe2{fBJ(NwRXfi@oH$ zvfpA7B_%&bC~br%<>M0n(CYG!WT#b142=Ft`#&4z>veIMEBKyXf|>2?jad@tATm-y zx+Ud3wEFv)ogM86^y-@6PghDH47uZu2y6M)R6jU@0n&E%H9Wg)>Mld-+vO9FJo-%$ z!nlKbptoOXDb^On+E*~j^C?PB_&W~RVv-r>577^{mNRIO`wb55p(KgvvPz6ezAU4Uj+ zO9Mf$x0rpf@Y!!44w#0mpHCo&({H|*kQ$8F>>mtPkVZ8n^KRw|x*2h9&BA~<*XI~2 zMoMb9SvQfoPk>X?L+>_+VYjPvaTKMZGcaSi&85ZoAzL9p$?@$k<5UGV_wkoY7{WVi z>e>r>EV~{`6)k-?pn<7z0l_^FMdvV7mKQDz$#}?Mh~f@d0V|5IC)Q+>bd$u;E0ne` zV2f-QaL&B%qFKM5p+E3HPV(@z9LHY|E^;ib2dLdq_SH} zzoW&|!s|DRorJaU1GeHl!8BVQs7-BRl8gJAG(Sa5{QaP6#RuZ9$qh41AM(fV@d#&^ zd}m!4PI^!0{Lc=Lg_LoQI%8cwIuzWB8NN$FLvYqdC1{3-4xseX)fvAo?-s@Z;r8EQ z*{jclPfL*Op_&%+>Dvn`DD8(%)sHHy!Hycp=HBm|YTS;0*WpAXjrH2WxnzZ;v zbqMcd_QQT3jH7jbD^ggjlrJNyk35=${|dha+j|6hAbJQwP;ow}@uyTooB`=}vq%e|X7-HY`35ZztZV3nMJ zK!#2WDu^@1xfYX;$dIJkpMWYAPIb>7XRdo5L5ITmKstpub*V?5C>VWNSK)<9(hsmi z1&1IGs40cT=e!>7kRpPypJCvr!-|Gx-ZJFb>9sm*w)L%<3!8u1Pk$;v*DB`z$|@-5 zmJc7NiV{V<(eUalwChK>A3+q{_>LX@VRE!62{8w3?NY*xDNI-D*Y(!#{1hO1luC7X zuVhTSO98Iz7<*dPQYH1JzJ#gI&e~tZ`K>ZT_j=J|2$E}(AC`Cy)zHI7BAh8{QV^DO z%Off#5L~?3jN!LLC~0@~paw9J9Q3Mr{et;Eswhqg-dF3p>70|VON!o2 z9CgbL`zE>-sXQBC-)FS6ktRUo*xPRCkM#EJ%uu6gkW925tbC_9Zg7W>7+)gN^TFWU z?`u%QevrQwbdIQ985gA>KB=ZbjXG#i0Fx*e7Xp9pl)1~wSI;L?*$PCW8rKVE&Nuu? z$jFpns9o{Eo*?}7wg{GN;POV<-e-*w$%%LJ!=jioxWW}Nn_nAd0-SA6$A45=Geu9l z_<<24vN1e+TLjrt+Ql|}-6oSs9{*77UAi8ZgYag7C|-Ho+4orJ!QgZ%Pb^bx4yh+P zwUw{s<&{ezNB)-ff0g=K0aB~rkk!jG2>J=;P(yH@6jAPe$ml+fj1HusT-90BSsK-M zGbVSE^WkGcGxz<3gq}RG+bUWD7bMG>)EmJm&Y6$v6`$1~H>rjbV$8nOX&Vq73gJR- zHN({g40W z`r!>u6aLsQpq@ny_v~b?=vismB>q_S(O|@<1FHC);tY7I5O;|EK{F%eN^eG`xADt!i6{!Dsr1$>dl_J%z}8+ zz#%fej)-gZiN#99i&VPW)>V~#7r=`Ff3J(%?8Q#A!S=^;@u^mIO2^ho24?J|H_-B2 z@;rrazu+brVOLp2e{}+|$q8mq2d@pP?vl4jtusFp=l8El$8~c=$InC`a||xhmFnbr zXfd+NbKUl{>hLXz*&0?jF3ucEgi}T5;l@$=fQOAjuvNp;B|$}#wIu^HhZX-y)g=n+ z(ah>|12Z*Lq*b4}Qvu~>*7cv4kd5Jai*{&SGOx4ZEQP?bs$0^VN){ZUpQ{C#cgPi@ zBTJfnK(7egPz+ z3>WA&-aIgd$4_gHu(Pj~84|&)aKmPF`r*&peL+fNAxmdg5?N4$$@>Lif(AIr|HSm{ z;dpbYhkESnT>UIosV^(-R~TIoTn5I?7ylVVW1^ApRYrQp7W6co4xuLh|3md;Rqe^+ zr3ZCMmPY0ra*8zwEp|1hFlP{&fCow$8}+eOlN%3kN{;G*Ml0p`s zHLTTRS(>Y5IaX+KQF<5GQiWy-iv}KzegraM>0yRjYIShrJ+2%j5)Ab{peG$q9Vr!oIo@x(lj>iLCu* z=4(s-B-!hfYQrI{6p|%3HS-zZoiCWS?X4W5ju_ugAQ)AEdZ?wVICxMdiZlqtw z-$ea4YB?^huKILgQSKdRepl9lpkj)Y9ND?t^$cV;VqS3&IKxOSc4;|%{z^pMsQoF@ zmd!&c|0hIL%e%`|YW~R_v&FHo&p1RxSRL^DA-FP*%{a##k8=M5pM2^0ykDkqsMR=?Vy26MwV@cM6KF=9U%bA}XR zSeZpeEy^oo96EE@tPs>%&Sz#MW1v*DLCd7HqSpeIpGslocl%SCp`uX0;_)y9Jv zE5R;5c$mht<)Z42s>-eE!di2-*nd_|8&#M$Rd$fe#ZIi~EQV0TMP9MwSU8ik9?q6AN`N?M2G*dMMddD13%s~Tfj2r%3IeI@;v-6rI{&GszJmPv z{7bysJ?K6fRxCpi>YYWmIZ@~U5;z7*EV+7OZKIx4E*TcM_!Mgoa~D77u0Ax!M4FmI zwUX>v9dKuLm&Z7GFN*MI#mfC;xex}13i5I?n9QZmiFKnqxZwJCIow+X&X%!jp#L%G zJ_Z=a0jev3PKyICHH%(jcu<6Zu1-uxoc+fl$epxWX2@_m*F5J9BjrK&&oEjEt_AD> z2}|Yj!K+j1Ui9Uo>?J*59FdcKL^bl8=!5^3leGh0T~jrwI;Gl|jui{G!?}>H7vL0+ zXB-sqf|6z+n6bC_9@+d&ez51OZWSac9#+Go5yfL&+wEyKEDT76dE7DzPE{x2?H`Dm!|vF82+OJ?89to%&zG4_TTkb zX>3T*kuTuKqJ8t=!Bh~GMsmdFebm_M%x!L|^OvrayP-l$WcF=4R^!k7&KOD4lj=*B z6x%Zg4|m}|f`_m$fCTd2*eAUi64VpjOM>k?9@FWlCm&cm{?^Ut!oTo^f;p z7m2=L6;x$ajVKcz{4UFM?cEvA!MLBe+GjZ{Ru6OkR;;Gj-Zxzw&(T|7FVF0&;2=mV zQSGXDvl{F*H|t&Xe)GyTajEq#RYxD;gyP3a^}Fa?;ve=qO`%m_(oM`OY2}EHYH>OX z%5@8R+-S9|&(cOG%X4oMe#j+zw77hSFq?2eFjIN+^WRndu^Kqr=MBD|fIzj34j(cm zT&Oj(lGS;P4tAvMT2=$1ny@3Pw7 zLQ*ovUnKA3NTN{S)bA<#4*LIH;}%eT*X|y2KM$ymMg@M=LT)rBS^VK5c`;+V>F}g6 zvZ}#Je?T8GpmZAj3N)->n+T1XL9Kt^?`jzdx=3fI#!wHeaCGQ!y@L@t@j|tjk&X@a z5a7FTIH9le9S@lG2hq{HjPmNAyI=c7hH7?16tk|dLQ0_gw0U|Mt6SbE*Xsr|HFsX= zJ3f6c-&`r`AkjTd;o!fxLh+5%kAyG4&RnVId$<`+6F)hV3X{<%!6%AqJR%4m-uMqR zO<{lLMmxj`o#NXl|B#kg^J{I>D z6W6!hY4w*1h85F$2c_o|iILRLNv%&XR4bv7*92%Ow{WqpBu$hYr<%k{bPkB4lHf&} z-m_03C3v);lXd|d96WgZvR0bn$L+F?o?s8$j%g0RG?2T#UI2x@i@=iNl@{8a>c#;2 zvX-q38bhfOUOeVpO~>nBy0wbc{+q4fQ-7w@wT1Gh3EEpOA+@H}75xzmWR&1AS0T0X zRSzLBcE6ThaBHCY29L^cCK;PVf&suYOL4&6omuscdv|_>&84=(QRPElEP$l;R`+|g ziLxo1j$>7yw{TW$-c_uif@opzHu}pCSA>1+#``V+fpl)W8teMQZgdHwz-F*ea;gISwGDMu)+0en= zZBcBEChEu?g_Ygb43I@$=`mlg!AZkF+P>mlrp0Zw(f_~#tsu( zHGE5ICLmlduCD_)SJaBP<|52^v`%Q@xa^1GW_`sZi z?lO5#Jc|3C@H)K>CwKEE;R#}y+cvF}uguglCyPDaST3_H8Kpdhk#jyU z>R3_vof+h--=Ur?^Mm4@3zR#Kznv^*{b7mR1c;GJfMR~ogz{VRAN+H)&n7acso(rw z@th+`s*oB`keydB3gH7K=Koi z&osOh7&mFTe>A3=jIldv}WS2dJ%D|kR|@aw|255+X2KLpIpw9pGp6! zS|=E$6+gP)8iIs$MI@J+uf2V;6>o8flidv3ZPhgD^U!jEbxXe?Jhjm7BL z(W^T=g?BVQU)$)9purZfC1a|&mMa8z2mQhT(}knNK<#A<4^;e+92IEek&mb*t;j!o zk2*>zei6vVlQth1hz$`V%AW~<6ZFC0-bqru&(&i6;oG53n|f<$r72*HVK;#x=GDx*~_ zI4`u_3qEzTJ@6o7{5ein2RjwdS&-b+P2l>_uWqOF)Dt)E6sH+=;~aqWRefFi#P!R3 z?|`q^7;+biLcNBLG(ZpY+;$4IVy!ScTzx!7y;^c$tXW92?SOYh`g|41^V95&QgURx z&RrW9+QvRH#KWIpnRnH}(YgN|@9AI?IO!6P5nH za4Y0bGx1vX5;v*EqV`$!&>#FOaq&txEUl!49q@u3$I(cx;VX!mpK{B0I*H*Q1Bg&X zFA);P!vj?iS6#l%fe%*$B4LT+EO>Obx~@-q>eLQl%34W%o$$AFhPyjX@5zR@vxRmz zemF_$3Q^T^eZgtC zs9R6F_GG#!1*xgHMRLZW5Au*0$nSl?;RNKDSD60?@I;@?LsWy0@@O!U;%)nQIcq@# zM+7lQlrCTE)5uiuXjTCYubix$oUo=?Bxrj4+SOwNt;Y-UnVyaX%K zi+S9$suv($9qMOZxO#+5vQ#%EE}3i>(?yR16?oPZ`+a zd5crxUI+#FX8_R~U7+CEDa(EImgs)27v^pbz+O>_v^7z($tS$Q+N$B7q@o}7;FXTi z86CCL$R$j9|C+k(?x3if_wW_PHmXpEAS_|_Ziqyg9}@n|VFS;fTK-ssjze6)k<#V6 zw#UR2B~iRt8e+3(q=JW_@+vMXDMB;Q)gh^j*prJ>f;8nP039Rw@u*s{GgkFU@SfZl zE@Js{E61bnB!KT?{SR1<@;3nPxHGEoKt8x+pQN9jK9D=lVVSsbpEzut2iQ1pb#4mYlG zf7+P8>JP$`Ox5OSc_StYI&%&ns6n5>mqeOkZ z+2xL;)hBjD_rq`CcY6~qWUekP^-aIg<5F%Pn7&M}=zACE+7Eg@d;!W=ji|MnIR3LG z*hSY*`g>PR9r-*aJ2BS=Ez1tqOqF+WV|4;?V$CN{OeZmU0Y_&P*ij*usWrp9^eMzM zX`X^}j=$uDPjs66B7+&>olnlE%YTb7e&$^8CV3|GC-7nR;p9jMATd(nN@Y2Y@VyGz;$^_iLQ!m(j>=m1}_54_+#V zug5>TAxzx+PVh9Lce&6&8v2o&gMec){^ksFJF=G>vR;5*w^fL^txThara1Z#VKxrH zg;Y`Z3sP>wNAuQIZ9m{J)VsADJC#kd8~C-H_273l?0QfWxL=IxM}~-{TpyM;$vz~F z!s`P4r8@v?MxgVsfekOe#KH4)CbnN=;-1}6SoIXbqU=eeJezoCn=?9DHvV(kDGMov z*fGSNRp+@2V;1le+`t-b6k+@q8yZ+oXx#2|J{RN=6gDkhH>MPjM3pjtEyee+A3g=%?bdIn5Ivl*MobIwEjO{9dc zP(6pwOM>+ZWYqC3XDqcp*JA=3ZYUQ5Nr|fnkP&u95x5!V1y!)nIFKutvi4jW&3?Hs zS_%*2GQ10IKj3xjP1V2IjrU*4;r@Xiq-JwVbw+m0gL5ax$4(d7>+W7D3R?ZK+@ZaC zAM}16Lv+Rpt|fehitgKyMQ8i5P>S)+;ZQ#y6^ZdMvNNAb6L++FArmJ8-Z53^hD!}Jc$J+Fpj;U?2Hiz(!0~Ox z8`hC1Oh-*89+FA*5Sxp40JcnEl4LLDzEnE!)M?4{`Ed~cW7k6+3w-6P_1%7$#)Z(+ z(x&4#9II|s=m;As#cDr2m#RajG;UQijTVsZj+;)Rf`nskpR1g)yzDV&pVO~(v`FET zAQ>Sg$~Vy8z#mtOdkOul7Wsjt@gB^m^F~CY_&e5nS-ba7!oiqGX=^IvsN&**t&i!W ziM&l!_gDRrQP}pB^Uf!!-1a-%4qsXwjuRXDYUd1s{d;-@8#9oPJP#&Z=D-SR1z>&Z zfgly^jX*2vGgIWKl(=|{6&Td?U*GyEV9lRwS}sjoo*z*hnQSaAhgwu9c?Y24)}Z{Q z7#gz8LmIyLzKcbgBsB6K@&LeWNvf-@?$bNzhA{;n3aAJNMvl>>i}~*M$Yk)M;~2Mw zkrRYJwj;h1SEJR9XVA{!Yn=|lO&Fsb8<6*G9>^QY*^)74u#{V25Q4h1 zgke?c=8w*eE)s!iLYO-Fs;(&kGN0m;KF}(@_Vu!{+lWs zH(=4TwSEm)^XLKh)n7pGAV-ljj``g0Ox4@ts;S8yG1rG5<)ryRaUlPMF#|raCOi)7 zW_06&&@TyOr7Cli@nhKEsUFex6=wu}@RVu85UF$H_*ur{-}3X{jGiD{E^|U236iWX zzpYJlW9&*?8vIsGHLubcBQg{n!$sdXx3S)57?#$stHVE>!5ckmdnxz(N>M%pOtKjz z^qJ*xK6Ojy;^4FAJWOR(g1@xO`4N*QAHh^PxO~i4-A{a;+~o)!KyK+D-zl9h^=D?I}PDy-ftro?IHbKS%j*a!aWXMztib>{YKPr zTwM>M10#}rY#FY}pD?6at2|dmdt88 zH#~oSQ`kQ^)bi_<`+OKo$i<|rMA5`18i3OB3k zsr~V^{q^MD`aw|{{2$yWI;_z>fM+b}`UOb{AXODsgdNc%qO=#*G30pAM);rIWM(@Y zx@vDSe30KHTh%rokBKr&Fx?2|GHc_rnZHhDVop%p0U{Pj8Pfc8X2TxeV1`CD2`=|n zw|tjV(dz(|wy4j;DJgqq6>wc}JRUZeW8S~T@crK2?}vs(i5;)&(XgU!mV?>N@bz28 zH&0TYo3&@ZHUQVD!Jkl@y^kIb4;3AJHARn83G@B5g8u8!9rOPq>n#J~=$duW1PJaB z2=4Cg48a|OySoH;3l72EArRc%-QC^Y-7RoB-`U?i`+e{K>6z)(Rkc=EJ>u$`ne3_U z^XKmP6BX#?)IkXjktlaOJiu;@^cb__Q6w_?{mjN^<{tbzFO8nEwa2>i%31d(Of6*k zrrlWE)2a@|rbHe0qDx>2PK+Fz_7aslWZ0KjUx7r7R`FI9EKl3N(swdZFx%SZA5a~ykqT|*946dazYB(Mbdu5PG%@{iZfzN9dE(hB5 zYq{Cj!W1vjX>E&!UPm8>rUlQys^x~)!`JZi71J!Q+;>i`gim@ucb zMxob{@9eq2V=&12eMpycm;SR7?zUqsTbpljZLpw>Ps99E-C*kcLdR}tZ>!m|;^b}c zca)R&?bN%YH-Ol5vREVSWoj0-$oxQZ%nPqENllg&H${XinA$9zLVckVC@qyT=`1wt z-)eDMdwn_SwY2A0GAL_st-#P*b27-*bLs*@Lg>L3%Ib>HPlW&AXrU_(_1dBPhu!h{ zbwqxl&C)FOQu&bI{!AX~3PW(oW+6jpVkc6*?8OR;*1fbNGTL=1MOfTZv|i9iEYY1P zucjjfJ(d5F)6;7}d+TJR0uBMZ&q|d0f_ti;e*3FMVH$ZHlu>C{YcOIn#<(~- zcpFy;8aOG$N8*=xO&jW?eSYvyySNX)Iks?q2y%M1-4n2@K@-Ght9^XGPI?*YbWw|p z*Tgn!S99ey8q}LxWL~UOVF!$XCb!ROXCj*w1(~SC*h40eyel&;$foj9qN)kugrQjc zwm+_JTQ_c*krBdsR#orDfB1LZVm&H2U7^>#`ufmDFnN`MUi zzPKR6S%dSU)j=7bWL(ZE<>yP_?3;MVOy8$o85?pR@d4J7qe`;$c1S8n$|YESFlFV;mD;?y4vZx5%qq{HKP zzP{g zrZr;GydYyYrA_BEbgzhEq@iGN6S`5FBjFzBAP*vz)l~9OPnC+7go|6r7=+J;7n}sw zpDPheZFFydWOCdKhdVa4v5k?4w6EW7YQOa7qWh{vE&oEW&2f-<5Sds=H+zSB!>KO} z_kj*xV@O%O_Z4{AQrKs-da8SOpCU^lXnubSPOpDwKzi%Z@i_a_G9&jsqa8-SD)MMa zGj0AgmxP-38=3vY6(`nFtEmqQ*Q|tCIXR^Q${lm`*xTdHq;=1oP{A8qa6Ur^G#b!u zQYx(xv~fFAZ@8Xhq+yW~Z8iH1o&A14Rj~Kx0MlTUx}|my@c^r;?}B)lZ}4^WXhzV~ zEn~xGCPL(dTMFteb0iAdX@otfnJP{QTqB%&Dilutgto%LqqJ7AFplGMjqV=->v05# zdx?u#Ru;!hy8Dr3KUj_MPiYilAs=>Qd_REw@Hom#y8F!5`@>^rprh!GcD)BOQFX8p zAi1c8g8M^O1Xkg&53uXB77@MmVd~NbY^$`@0o(}~J1=RNeuoJ@f=Hfo&M?$BfTeM0 zX#|;>$Zc}YWG47M%l(+jYfoCIVZ%H4H2u=k^?Im)G((Y1W)uP-eIZS`d*vEs`o8+X zy_L5FTuOTPhHOzM_3=FIZdmf#J`Tw_FI@NXE#2&sAO{2gg%a1c^DSrXEettcEMCdb znbCZDq1boDAZ$#e$Lm+@B@}@tCBQfP(@X<{_Oa4GT`0OM;$vopRk9I$JO?u;>vBcg zeRFS%9GhcL5W<3^Tg?xZw%OgjV>|VfXyTc@BweDolE=>)(gcLg?+-pm#rfFqBzE3x zr>!?%QW$9qp8hsXnkNx>JJGb<@**QRQ$3HF$s)0fdI_dY4aB_n=hY0I__TF02^;`} zI?JzKnpneSn&dkD1UwdhBwJ*PTiW4QT2g?$kbi&<%}=k;Jx2uWGyIf7$7mz;X`1prPfPj+0cwo>BYU%mhy z9i`C*%nb`V9z~&;6Ssh`9}U_%)U6C&oVv9AIPZo@6(a@J#=nnITNwyA06#IMIa>ky5xQn%q>9vU}8s<-H8wBsii8)nsj7M#YAK^TV z60_-D4Dj*JLsYW*5@|m~@HLuE8UGp%u{M%Zk4{0k=6gA*=&n^A2+Y4@heiN@4n9u{ zhgD?gARjeZh{tbpWW>4*BM6=_kwKC%DF4QQZGAf7fq)XxY}WH6NvS-N9e3&Wq@-AQ z(IJ0GwEQ6P-byE(#m?q9f+(rjNAFc_rujSwHuFs4gOb1gBv43DSvefw2~U`_aN)}N z(+d95>xDjdS=#xhD{u%l+Pc&<=x7Rvm4W2Ot+21E(sG)cyTt|!3OmFlDFO%ZU!tCG z3aYBUK~{4U-R=Bs`~8AI8pFk$C#N9mQTNb11LPT4EXoHAKSl+peJz6} zG<Op(DI^#gsukAobl98-) z^TSfdL8eC!aF{DIkApri@0?a*lh4~$*|-T(H0v6Qzx!n zy=`AlDY`p8zGXgJQ^7uAm>XPjcI2cn)#xdTku^J8wKTa0>HbM(8+bf%asI zKG2~4R{aB`febZNI@){Kem|vpA&YY~G+B%Bo0>_AC{x5!m4(^R`yffwbYMiXiva85 zL`!sE9uDlB~x9p zl0_>LBxZ%AAR6}d(xgRz2b4$mDq%eaBeZgWmE8tUz%OWQkTHazX_al=^2%OcbB-m8 zQ@3x;%&2bUC-@>Ly3A;^YNn~GXMEG=Fx{Tn)3%u-w* zJz8j0#=E=yVfFfPb{KL;KGjChc#gUH@j?fz3}3IX$nctB*<4R4g)VGdrU#MZ`La5u zmorYYmD=_riIB;Wul_t#R={|Pw=M{FR)YJi;_XE`nb@d2Zug-oI)r-(n0jVQ9g;6k zNf)dOUItE%v@Nli2H*<@JrB-1@^Nes-^L>qj=ss)8pH-|Qw3C&cX%2Vu)s+ZA>+a6 zLxbg7-yR}32>1YdfnfV~iH?rkj#}w}z-W#DBn2*9B4H-&F+g*Knu`j|HOn=f7!T<` zWxMnDtN~^n=@|&N(bMY+!DgGI*-oaNdtKCfW+l@2w$ax>Z(EJ5ZDTM;r?wXP%l6wg zrbjS9b5tJU$o-;e7Y2pG2l#e6Zmpp+1^>LtKl}mET2?HVX)#_FI=;{L>9Zr-`!B3D zB5xM ziA1#touch^$7W=Aj))Dp;yf*o?{}bbu5d2iYD!&?{q=1VDy5#lZs;LV^ZQgN#MWap zn$yqL&8~4O5>Z+P8`en7^o_~fb*2i51#9$ zuZ6Dl1hTbAH8N(FkTjpIv}jVi$cJM@sfO)B;AnUQY1WpnuO*@7HC5Cjh0{(N2$RxcK zEe1CoH93tS#&I9I@qB@9VCG3V9`}%#H#19q;83-zdH=4B;6;C)AO{ngSu~Q9Bac-D zKF2KAM@nqr$WXP z2F!r)(+BO2-RB{#x7nD__YQm2E@WVL@Z3^mFs)`GBlAVbv>fG$|{|q-Ri?<-UIlA%M*(xht5uFkmk3!XMw7AGAZpWgj zViY|4tBoaC9c(vmR+btgsI?2d2HXL-2ZbR>qu^Gi7$%oLcO2p`OH8+?ONuk}4c{eZ z$l(U(rcQ$GZ{`PZA(HCn5^EUe5qBKqwLS9rE+I|*5!mJFm=~v19@V|cF;6&?A^NnF zO?H>t^lkR71H=p?-+}kpRFAinm3}pNsds@DsVpDzg4(fLH!_CZTm<=tT61Xu!uiJ; z6*m=pkJE2nhQaooT5lVS zEe2DF>97h8wTv9+HGbuA06-PzU8Wp*O^M$UOu1e}>r}odOO8tIGVC8%_{t^;6p(b( zUPxqG(bWfAaGSrDDzdg?AS9{7A7CCq&yPnAq z-8{k(-GpiPgtPU{ey#1qYD$Xnc;|V}o%Bc>+v^dg2_{$1O^#X9Bs3eoYqzBS2{q!rzjo9t zkI*09IT_a$fob3k<*dZqj|w#m7fbDnFes6Z&aRj5q_pO?wGMB7qPQCYS!m;uc#&|;pRo?F;VS7^Bv?go{^66M_`<)rgy_NfEAU~8w1NtB4sAmBU zV9A%fK;x#kiNfYMhy&hAd4N4V8h138uxM|du4zC*OG6B=#hWmObD8YRWWFMCK1bM_ zFtgn6pZn;%!N}hn5^7DCDXqbIjE%A#a=b^EsIHm00tD{Q*Thx?u<7SLSbGTRHEtLC zcRf(l7uMM!)gvZ+bP!{Ijfr5<^{H(<-h@wMW_aCa&E@kE5c~885R>gc@tlEvr^kmq zKWBx1+^6o!JOCpTi^At~nzk@^=*@e~KX8s}kI1ODA}))0jUuW1K_tH@)!RK|BNCbB z_jT~~F@3#gw>79&3(~)|zf}#{>(bDiGNw_Xk1mo8sX|1>*;s$1n_ttKoB+$6(NS9l z!x*+JlV}CU+k2y@xb3`k^g(bk@+?8Mz0cqp)Wj(s- zHVFB7m^if$DnNQ8Q-9b^HQ+h)`fhxEO)%p$YTaA_R3vvKyti>$@*Nfpgr2ihl6Klm z?C}dOQ#akS`Lv#xU~s65ydoWnE}N%{Q7Y|jD4=q1sw1(l@Q)KkG9jM5n8Pf{pGeq( z#Es?ylW7-0zz~MxH8xf^Y>^-id{wtTT6n0xxfhd!QDdE{%NkPz#6RT!vS^e^{ z?Sk_wTcEp5torVK|gFS85%;?AScktiSUHDZF_>vwTj0c%1oUe0lFIVoOp@ z_q7MSShNvYYhwJFmYF^`VQzvdHxuwJOk3MV#6g6fQq~lA4eTn(?Rh9s_erh+FC)5d z0G>C9!x;25Bquz?s8pY-C2ZUVKqc%~6g5^8Bk39l41}YHlB{*cl>Pb67AmQfO|BVJ z29qw$hHV(-pb<;Ao?uM5Ol>GObXL*p7IC9(1wwN)^Qs%_X?*aSLlX_>VE985wdns;UE9OFRF6!4BKe!gez*Hx08ZQCEx zkMKotU{g#DT0NC~<%6Kc7)n3n>FsC6xSvEqkeZA{+<{kE>o#kiq>?_9-n7>mJGuBRQO` zcA|(d4_mT|JjtH69hWfdfXT)X)mMf~Jr zZ7yqCo6H!S8`8L8iDzDlR%3&-fxJHHm{8b&ga(IM-SpPEqS&roO8$KFe(or*#yKDC z%*tA?>Aka@Ca%!7J$AZiBXCk+6WEw>YZu}vlo zP9)zzTCO5ExNGg35maP&^h-E$#^G*IfFdXo+1c(VRJ6c!U}JA>pDwpBKXb0Pl?=rZ zi~H!?ZW!aAG#jCJhd1PBtJ-YjqIt|wbMl)8w}p z0=j%(a6;+W&qGKMlaN+=Kjl0wofE+8kr+t{I&iE%GI)(4!~}I-8U8X1js+U?N8Ue{HIxRK z^9iH)pOeo${a0hq%_n1hrn|5eNf?7KCquvCUmJI&Hx*>ME3nzl4}A;nHDoXrCqxwN zH!4AA;^_7>d04irn0nU3=>+r&=(XIB@|unJfsP#UfRxceij;9$TZtnR(^0iN`icQL zWls=YuxiW6eWu5`O4AU?xgERh801jw9gTJq$Ye--Z#KLH88<{{lT@gBosXYP$?{PO z^jf>-yi&A;_DRe8Nqql6)Z=_Quf$+@LDF2)IU;%>T#Q_JC2Yv-*yoY-WQ{gb>s{Hn2#_>4cv5KF zM!v6hu3tBh?1>)zp!4=dHFWuN2u;fgEzqfXPD5yYy+atl@Sdw0B_|Wg1-2h3HJ%&i zPkKlTo&tqW0EMbKchz>c`OZ_padN(rN)CXI4 zysQ!TEIrN-2KMnoY-30qJp2+e)=+`04hrp zqE9E-4fNpbesvAJ^XOvycKwnG&)g~Y*FY_SRmQeX{f;3%EkncKsBqK%@&I&BvdPx- zEQaBGDtQ{C*`_S-l`z&Q$0Pv{GGigme5%>K+$?A8{?D8xT`}m}_xRZipEoydffYSk z-6jOk`_sgoA?e~}&RWhfVIu8p_dKplTHkNp-oVH6nu+$Nt_;2ftjRK~BHB96DAKZE ztFH}VcZJ6{K z7kdr}1np2b_E9sAB318rgr`TrVlL?dlR?rH4i>Ye70m{J$h{4}#oX@c(tA#d6VsBi z`soHK+QL3o{S2r1dF=qgCcV*K_#4J$@ChhQqwRaZCWYv-t&aY{1ZZ>0+XNH5;rvOZ zu}L(eNY<;O+Xqsme4lerT=OFj`A9-tRkz$^oC%ef2a5y#`h6t>{{}>-T~sKU2|W_% zRo*P#o62cb%uayUK~?8T&Pz(DX}ZDIIg8}XGoR9WpZp$}C=2x-b&PZF+}e(FnVvWt z@2(>o52c(%c(OVSoduLB+D&%BqAhQ57M$4rPr@E zlc3OX5K4ZUjLXihSS+Dm6$1&9{tk(kVqH+-k5rMACB>sg)h&}x_MBI5#3e$B_*9=} zwG`saC!PGojSVibc{$t1jen5P3wsSlcX3_!Z_anTfA~{P!2{a72ON&!ZB)Seoh<-e zHP!Qyc&nv;_5ftGYZ3+rs&9R*n}8(QTxEb^$29;UQyd)8X#Rm5L5Ho6>w&-$BIp=0 zpejn#$hm#hClf;0lBwmgRtGdJp8tZ8T#~ZWcV$xCM8?lq@-r5Mt9^Fsho#%it{Ul` zj|Kz?V{&0ct%RO{qlp6O4V+8IuJ5zaOSAuN0-Nv-Fe)1&>+>EvS)kFFeI*as)cDM4*&`@Mu^lWOprvmDCG@;OoxPUc~IiySFbd(r+ifs>|*_w{)pX9RuFh;|4bN*P~MuBn-y; zOHTlf6h)T%NH_r}<$mQN$^G01*Y^{aZ>^8Ka}u6ESFZ;dV8L2$gXZP#yaZA&3+y94SI=ogl>d~bz;685iyJH`i1C#u|(muU!WV*{B;8^gF_v3 ztNjj@JrJ^$oh`>u<%NlcGw9cwk(F-VH2G`0lBzG;E|N6b86}jye-VC`7LVz617J+@#jEzB{WOOr16qa~I z#fsC)e@6C4ZBsw_2JRKH$v&E+2WyL<$C%f2;16JW6Z#yJ0}Lr7{9pGN4m9QY=ow7l zfZjnpYL0?=fp1q9YZtn~xG?xYaN;AAdX;As=naBw&&=2kXpgtypUF*RwkhEZ=fbT< zFfMvU#^Vw94S1izRXj#TAL!S&PEq&!S`K%f^TE;0s;=v_3fpQs^4e~Gp(Du`KP%JR z9+$i8A@zGURk#8stV3^a1BdJ>@!)Y7-bEfaV^@;>ix&jdlIjxv&znyrP9Cy1`{``3 zw7^%ptLEr>sM8~mKTI6}C4`w?j8zEJp+)#|HUfnL&} zb4n3V;=ScyjI8~P!+Dbf1O_RDtIDXIw@9Wk!PJFq7`0 zML?FKT@n1rtApDn1t!*!)roM=q{G;p&_seN{0!(1uQU#bU)o3HUeDXhv+&(YFq!}U zPM*Qp?+z|Zw5`P|t-vD&jxC{})*_W4* zV8GZbh`w6NuaK>mUwagSv~;mioSDu)gPj~^Ngvlf`^bO@8}6~OKND5)^URNw%1tDF zJ^+P%=R^$wFGJ<;+H44IE+|RIU_DDQXOPC$If?M>t3RZ9COk7TI}h=I498lj z5h*?8$jWit{^1l3b&<)Y=T3y(v$0^Ul;=Gj9c^!LEwp8_!#J2SIi6=5()9DX$Hj2K zuOw8F?Mwd;Oiz`Mv}SEPkvw@4gKziDRW%C(xo{W(roK1?5Ps3{xL-hN-=F+&^6`>k zV(1C@s;liI5>sJ5zVrwwJ;|f&6VjAc=A`ESniBF5<*VHrjI;Gzy97WwN;P=p6mprl z=7co*w7Ni6kFh^_x_NZ2pUqNexEvSNdD782cl~N#r1Psly=GIo7H1N0F7FEAKmtat z1*y0{h_K}bQOj4GGqR3@ucyi>Pvnn4^li5UfSe&WYYW=+NlsM;l?LXrxidtQTQRD{XXO<1=f+}qp}*a)>g%qHSRDX6 zcu3`5q9#?EgDeKC!wI#2@5MN_l^pFkN1m#4A0|J zAw-_mt(_=-_=gRzf_95YJf(um?Zu>?9XWo2b)Soj%W`B2ZkUsJ8al9L6=%~<$e8hT z#H6o9*aO}Jxr)QS9SV-$<5BL;fE|LbSrHy%B^$IXCnP$gB#zb`%gewt znKdN8H$1y`i*p?E6E7|O)>QX(4-+&-oDiH4cQVUjz>lt(5F~wC85!}nUE`RFj$xx= zNQ>cB1>RQ)OfgSZk3K{&JvSm6*F`pc8TxSq@D>|;)S!A9&(~-ec*#65?lLYK)7Q&y ztM93=;jgF-O6|Kgi$b#S3-#B6D#r`cbb;UOu&Kyt`RgkTh%uRK8@?Y5JTs+7^pLQtbr0g4FXoFQXKn*_<>!6y6!uly-?lpu9Ms=-@y~GF zxWrUWD1I83)<(q8-$l`Q>--$CvF=-Te#X7hBG$D$QUi&C{9kd0;$E8yyjt`OjB8&H z(C_bGW02eyYkIjJ3Xw#)cd5@!rR$&m3F*mgw#s&qw|}LCVT9qfxir{G%OiB ze<3)B{W-ugT=Rq3>`*=>prToD(f0QFdbB?(LI&+-=%S)tRLy*EuA5C&m;I%8C(=Bb z`S;c+66o;~dMMI!C#Mninp{$bZC?Q~Lt^lE`S$KgtJcovHoEAbqe7p;`0bA$)D2U1 zEAD#~2#>S^J^IRE_r6}R8riB@EoCdHs9#bfEUH3cYNt#@62#_z>7#^~F?e9XDQob| zulYLYm0=d?F*M+xy32BNx~)KQWoj~pa>ys0$eW)RBXk~lN*RpMNM3+o;hcg0)Lg)| zjg7c(I>4$u?GKDx?*}+vryJ8*7zI^qwF(}_Cc4#9A90H4WaB(Ir>mGXc#X{I@WI)f zkM~1VPZ#I9w5Z%>hLohM^$C;4b1qS+P1jV%MWVbV9-DcW5-?VAe}o!_IRb+^33qFUHoOx~ zHyGe6G3_bFI9qC_CWlhY_l%44uQfj*SCw)yuunZT_pp$|k&>v~Gd9l`JI}KY^f1>k z$FZcsI~iz{d!ogi=$b8}13d(ku-)gH?^W3^*BCu;ZcD4c9f$#AQelCkl5aF_ta^4a zLn3lm>eZ{2?1!`Q$(Uj2aE5uIEk#jL~|H0Z}pRnJSSkk!6FTL5zG_d}ISdrEg0n zXq#j#vX*eKF}^k5KF~2R9WRJ}7ve~q!rCdG%y^F2^gu@Bb36?w6?HC2%yT=OCM`AE*3)5dPgZh& zzSvacx*;`Z<3GkXzv`hU8KzaM*r-m>)DC<{#)~Y|QVRC}YB&gdhFVqc z(x8brL#5z(dFp66FCZN{l_Xx|bmf&y{v%P|54_PabpuO*(I&wO^{0dM)y!&u1Wo$| z8W2wx_D4Fb$Y-y6jh^xI%}@I4+prG-Hf$iHNQim+jZ4H*aPRVhG=H$p9%yD7 zVABgj6M$xd@hmGYaQs%NqjiiGvHqUWjs(R9XYl2QN{p&n@gZwcb`bhAu@z53@ zc31sBjP__ky?7P1ChDx;=3%yKO))JLTi;k`hv%Y`N5J}(sGBGo6FoGbskEwmu**o& zYiyzIc|eG$gVu0M++d0H{{NwFml1V#YdCxhHB|Nf&kE_S6YF`hSqw)+h)E)*?eVJ+ z%E(|;ySrHyc!(t`mG_qSNvq!0C(>XPyI&nh!@?t4imtsxxn!+bnQoO`h^zd7LudJX`8Ka-MfbknY|@kis7F1 zb=-zo8mtpHa9m=tyCUCR{!8WlzltVvM0&WBk4;qk4zG#3aP-9^QHyM3`{b^fo(0qW z>I1L2SX*&N$w6v;N=Ogs5+9$U(cAzy_W%Bl*$=+75y~Xdq)oKH$FP1XHMTg#8obr! z_okmU-zf3=Bu>z0e#WeP^cHI}(N~zr7SYXglvmG=JtiH4?OhrX&Ked*4+eI@> z=D{UM-)pG;%uB;}cqGd-8>*smxk-RP2RZ#ONtIua(7P~AWEfcATYnzJWaYhK)K?px zg)}WD9XRcNL&-Yt5ZqFFTprR<52XqJ^b`nDLx#AUj;HyU7r@0=!I)+;r7%6gh4#t2 z{zcNgL*3}%pK>dO@V>lNWBpP1F~ip!^g5UZD(cT<26K_gBMVW-_MZkb%o3lLgcB1c z3U+Z(N1+%WKeupw9i~H~*a!+B_^*)pU$c(n8|W`_b%v?GpT3&joKVjoCuA)|CX)!E zY>mUDd13y7s+vfNBdxcmEDk%`ZQ_3At^coLz+VX#yiXr%o+)$cE$~tQgzx=i6G8|q zSJya=hrWt*zG6=rlq}AisyLKI2-}wU=ab|FtNYqn=wfGKyW}c|;e%nB7Y3f>rp;m? zD|mTPh|7?v+bfqsoMxFgQc8sMKN>Cnqxr&^gc6d=L!BO1T7%AkYX4~ccG4$N_*2eO zbX3_7j6ZWBLTH|7_xQIDESL&ytJuE{v-ZD$(J9QoM%=!n&JBNKF(?<@GYarJps!}% z8|BS$d6TW%_VE0#n8SZH9mWZx?dMzlQdnN**f&y9Zk`E?J@pT&K3q4bJycv<#?Z=J zOKlhZ`xe>62(ML1j*_d4u*!Cg+UtgDO?r7Dmgv734Z~39(|m%wvUNh^$1G$Iyj1$H zOvnEisQ79SeNY~|AXXD^Z&E!}EYT8@adD-}CS@?%Y-Uty)zH|}Xfbe;{NKs(w+0$R z3$!|U8C87}x3ic09>z!0QJ&qe1?`fmQR8Pc_ogrnCgZ+W?IHcI`}?&IU1)(dt`X~+ zHjew8i9+Wh`J)6a&xES#u?GKXHnfurHfI}7-zB1HYggtw}GwZE3{y%^06P~Ylt2#l7Y0cFrrJFExw+@en&W^S^>QZ3B0bEXwp#%MUMm)oz z%-YER_RbL)5GAZuV=*>Q>FBn!G#yk>1WdS>X1@JY!i@?Bb3<<)Nf*%8 zvz%-yTn)HvB$i()B#SRBeJ3XkBb&;c0bKQDANf~d}?v0ZHCgZ4-xaa=FJ5IIybCO*|FZ%J}F znfJ0FZ-{d19wZYW-7(iM9-aIBZ#z&2?2mKQrcI@`m$S# z^Od)CgH&livSH;ktNj?8s@rzQdz;xzgjgWI7bYwqo9yNafKF>_TcX_*zW{PqbRxqT z)wGVhmH)e{0a?X}i7Y=%O??$VwLeyM)fb<4A5gzY-8z;NiNzvn?D^X9j^{YuG($wihx&6-`-eqMTmTSF# zeP^eZaHpA@4wH_cR3`HS_Mi$oQwIREv0su+Vdm0uxtrTk4SuWt-dov~t*TmOXO7zE zmTm$Kg9%2TH?;(Tam}T=qp&-DhxU)80bD}dA;LQ_hhs6& zY7&BEGkOfbn*+STF<=x6C|pFuO|a3aQ>*kezsDAk@`dgc{Odmoh<)u`Y#5Hyy`(x- z;iP{)aO-%S`Bi)}pQov2HPzP*(0VZ3Z;#VFpTwbEo!#}7cr8On>@wnUe*E8K&Hof) zz=YTpno@HtcCpof3-t;&A>rlnUc+K^gxy#oyQIVh+Uw;W!5;@I@Tf-8Cpz8_i!18D zIU#@d;NO2|K;V~hy#h3#hY~5ANYV-WhW-N|8=@gp>?c$piBx#`Rqk+IwA>_5u;&Q=gbU2B8yrFDP z%rqgH0n2bOX@5FCqY5^M*`@f;jocQ(BJ{^`RvJaf^wMJ)vVI*?!^wX3$bFH;6-Bkv z$vVmH1xSGP;Cg8SLz`9$VedN=MaMtQaW>U=LR8Qz%W=Iocv5}}a2I`OXsJ^TWVPMs{1Z!)_2eRfyv zGm}_`?gpVmNcg9HswJwRlxlR^`}j%`kz$FjymYS?%Ky~@K>1hf>Wve9=t{^+H5x!N zGuaJ$n2R|QG%t~qV%x7ya$^Z*Yi%@PLAZ+amK{se-~ZPt;C=C>^#SH3iY#yqXN>u@ z)c9#71+u0nBHs1{5!{YzwTI^Dpnt390TuY$Sh%oY57CZEJsrn(+hap}5cf?n>wbu` zbl2~YZJPIppO!JDjP^Z4ZekYB^kM%fI07sXN=xAtjH>9P&KYDK?&{J3X=DnSVhO#x za@8{Bh07B%WWuSgP~C_e6Q7HDx%g`TaXC=0RFOk|nv4vqeRrqx=E`$83s)~D-6z(Q zEiA3Ud+wCSXrnA0qm>?+7>ob0 zW24WFcuF7oub-O)1EUg3a$p;nmyr#h`)PWHzRy;v!l3c0UMj#qo1ffnWQrB|=Kc4@ z=YNNoBRoVrz`}B%AjizSN0UXoDqDZ+MXjJOQc@9C3NBb59H*{cAB+&;ruX3S7(j~b ze2w@}Qk!N(FtciL_5=`t;_#&(n=B)LG^Fe&H_Ginv$DAnC~>*t&!A972t+QJ*v#1c z)}+4;o*KSUyb`7$m0=TQAjeK3k;tYYq4a&uB8! z+KRMaR90e?K*M+KyOiJW*)mI&THccQJwQyuqA+6X42tB~7}X`$*N$1hDq5cvQyYBW zP8|Og5`@DNY?~0d*}kC<(d=?j4nATl{HlgL+x zaA^s;5vJYBv7RX6+=1-c)V1lFOe^6BAKTHO8EyaK&bRi{Y!D|v@2#lcUnRL(iJU81 zuO%A+T0NVm7)2Aunxo;ORgFv!Aicmd0b@obeUMLZ2aEN-@y?HkV^#T8&897;DO63$ ziJxOv$YZ0%RBldTBH~qJ*uQ=>EdglLaRBl%_xiJi-+&U#Z+VwuMn(7F0#V9`XV<%> z|2u*`e(2H?&XZubtQ)0}3@l%geYkt-l{QNw%32xKA$yVPh@Hw|fcDb@>JXQfG+YAQ zCdGyw#1~Gp+P^H;MY{pKZVk&atabMjIyMLKG0kg6qxfVHz?aq^P0r{ByrEJ$=Dtq2 z;`~Tk(AbIuM{;(pvczJuxaGwRW9M`}U9<%j?=T=LbfeXA(EkW6a>$QI?3V@vhOVcb zOis4`di|Nj$kr8}Z)DJqqS)-_+ckILw)DJ)K~2-rHqma?Cv+B@AJ$<)%d=E46YFR` zgX&%9%;svK_i)S3niCirX4%`CpB9$O%`}bq zztn}Bx~)_RbUJG(QA5J%H9G-w2o?^=P}S;kmL;=FA)|45!f2<(WQc#?^6t z=a;?8;BwHG@y2LC9isi7qAj_kzIQVe6?Y~R&{$*u9tT=YPi$(tcHIOf)*ao zl}PB_@?PmtXPw9LW}}^2AdBDYmfyRPvLsXGwIIEk5NAU0 z{q0ZB{mQ%ecv|nQsYbo`Wd9_h21ek0&}h+W)%TNHzHPasLbUWPgXG+Ys~vt&df-&p zDM9y+4W|a~a{HQ8kE+La^Tap;<|~$zY3EUc!TU!0>$=yFo(i%B1)C?PzCBX4E8G&S{swXkS2w(6bq9eV6yXW*-^`o!S#~n{@7tc5~kC+ z63_mJKv>Z@p3Kvk&ZOrn%e0E8pY#Bg4J_=QQC%H?xPgxuTBL?cJ zb|B%n5?1@;HeB0Og)XQMEnVGHlNRc#a%DerZy<-o=wdq0p~Ropjwa@F7hxArY`=MS zjvs@&4dK|wSO6}IOJD}9kLhUr$ArY+mNq1Z!4d~M!PJdD7-~tzN=XRmaBeFW?2Cta z)G8ha9BlU>fo2^6AbEuvT99d}!vXy?M!Ry0b2nY}AhEBr=^1C4>9jEWIZ9%^FTq){ zhs$x3;*jasF7<<&xwncpd()fKRClmCxX(lvp{>4kl7v8x`HvOqWE zV77iUfR*WadwFMOWv53=)b$qr!eze(*fog-1GFjn0=in(;!vni;|714idJHPD41M% zS}nvThjFg|KK{Y1clg@!EQD6Ct8;XFzU}_GD8FG^lIGYhiq9Jr5VV0L;$^8VJ?jd5 z6_4vH_)8C`ZPo&Xe_`T<$Kxi&^?9`eCRZTDET6X~TGmy2!ydL%egv#DDj7ey7!E1_?Ri4^Q?Ha$> zb*r0ZeE#cxHQZ~FZ3{3#SZUHAsdwa>8sk`ZQ}EOw;SUJ_fd##_`P|>u;VqbGqWb1p{Lh_+BVT7V4+E@Rfga^=k^zY_WkuEyk+w0%y>KC<~ z57gL8tNM?(04FYW*W;ps+Vo93z`QCn-`m65a`pzvr%}5R5HWJf0W+_4-w#Q@XuWX; zB)4k7nJ4chFjpryccREvM6o76L(p-6mI-%YcjK-B+NTEb3C|XQwvt;kNh_<wP!V(GcJ7-xRx(=d&sL7Pa2;|GA>H(7m|KAF^4wJi8F1sp+AiR3Nh zX9S)OO4QSt`HH8L#qeBl-K3_hXL4uMCu34sd|K{z{C)^nQ6M)1J0BSUy4#Zse*@Ik zjysnzz}PpLklz8QLh$b>evRK&fI39+-CwG1fI#~14&c#=g!HO#MWBEikX;dK1LJLF zJuo|fh;QMY_PtqM4bbW620oOR4-Ld>Ibyx_(1h1;C*jZg=TVl4K^b{~3C@Fs`*M^R z1_YMJrpqu@!C&olpFK!gtG()XOQu1ujaxLPLcN?Fj)v? zx$R?R%p%(iMrZ3s@!iYEIs>vL0e`pnU0y}#oiOGnb^Zc%7JIO-EDt*b7v!9yuz*vC z`Ev`f+x0rTf1|Qo?{8I5AM$TRhB5N9-3Bm-BH*Kupv5Q$nUpDroBI#yFZbHwMp5?B zL3HzE-g)Jz)dJqp2oFaES;i+MiKd}siO5gQL&7w%L*nBmFkL5@>j^M0q&5KfhTaSl zfO9Ii0#!Ko^&tqb@KX|h##VR-!8~mE=sp6BugMLc_ZMkbK)fr@+Xt0>-8HTf;4@Pt zBCC5NzXF+>B5h)F7!VW+hSmm%tMNch0YWy#$+{kAb-vCHuU7*!652Lx1ZJ<25*c&W zTu*&;syo;T0Bs8gkT}K!c=?D&n2`!@REAObcHf-KKZsE5?ep00y8lGbMBN7T8y>a_ zKx>NRT>kcexngoDgV=LPrhlUH?6}Gh+epLRNXW^HigSWbpH?1`jD3U)Kd!0o1O1l zdDC_5=(i3&+M2Gysg!=>#Hq^Z(8Czlgg2uLvj`d^(dS-iQIkLJx1a| z0o4)BwvKRKmrZgb`O1`(C*T}C)E6fa@k|14|8t(l0dUPc`~yh+sa{clwuf>_F)si+ z(;42uunkR{whR=6{B;6GJpq#lw)s)d`=tYo;9fdal`t9z&>5l5Gyq9^{j{1yuu7-1xuxA4(z$JeW-Y7q1TR)WUYUmSU;u{zU2l`)X1y+% zx%T{Y?6M~iV zhhUq;_9Dai*JpR;raxj#q^H9Nt&8eH!(yHbz`21_`1a%ND`@37IvDL+yTQj8`0xJDAkXeGzu&i+tyU;ej%a*GK3@2u(^2cP^JXD%hWTLD zPFkML`}dnS`%}wt*eI9!1EurIOtL9+>vdHs_{p@otcQV{t7&ZzsvaYU354BTR*2^i zWqs&P?ZxnCss832i5JK^=LYbk#NiVCLpJ`t$GGyZ4(k~jw()eo#C~^}H|%^(>UH90 zm6l(S{HieCqDmMj-yQZ#exvNvO+DfRMv*tyx=62olG~iFFXQ6k6xSnD<1%B~T56iv z&8eBCfJqPMD;f#qQ6z2^U(RU@J@9=QeowDr>3Ls#MxB}^o%)DMBA_v*5s(x7SzY%t z*QY;X1k%x~toMiJ!c+OI3KnChHiit|yP01tegZa-#G2605rn{4%jM+dQK9Hn32aCj;u7QQMb<(z5)o@^XMuJeXz!P_)j z`m3x&fMT;$oQie1!(+1j*}b6V`wBQG^Y1u~(OkM-A_M@#j89WkN8mOqdPY@c94)BE zRXm?tTg2xA*T55H!Ko_Tt}>8q7o;BhWkt0pj1dmU*4hWbQH8wDzdfCZ9*m9dK|5Nf5yK1rkLOiv1g*_eOPhgc)%9!m14Lv6> zvdXx;(7n<1(4r@GQXc=kJtEUQ@r$MP(5Xi9DC$-!;Qi}51`|%+iZwncB(EnxrWubA_*j0oXT>naTSHICZrnZGGn%zuL!tTS!;c7u zbo&I#BlJDQx=(NY?%oVp5nP}0e|rJDtjHg!B1XIT%b^<%k9NVpU;=@Rj?!PqAG>Np zKNcPR0jQWqf~{w#UIulCzQh-MiMiyuWXrV!9%h*dmatHZik$%B_VwxJv>G;h?JvHA z5Zg;mDzZ9rlOB2hp>DJER2k{J#S7r?MDE(R6fSs$NQ^=rn&9t;O=tz-0}OManY){A z_4W&N{Ppp&U0|T`;epk(dhRzambNSI)ltWt-M(aV7yi9u9uu{&cKF!I4(}OSZ~Thx zTSCHqAmXJ}FDC$Q`B=FGZaOD^-OfCw>Xf{8?4Qg`_+d zQ-+Y-RgGw)XMTCuqVe1sHhw2ts!D7|6A+xRZb2CHX|%-w8YFeleQdl6OZU``K09}p z+dO+cjeU951YD#LXT9r=r^O#*m{aTGTFUmLH820MwlhVWn z!D8bs5<lHXjg=;9)LO}mr)ZPVfCeXmQV`7VrYpH!Ontxngsvb8J-AN+>nLW z_3*4Fyzf=LGc(OoV9>{kl@FtV2gS-R-hBC7?a`t-N^dmTP zdeNUEnX`77@Kc~k$2Ys#Q-|)>edz)}(|D>MyyxUt+KcVe=WhT8a5Kp_{f+8z0&n9d zm$~lCct+M;RHOqPU>#9vpB_^!xR(g&X(#ggCjhfo`(t-fX=hH>Y`-Iwlf3qHATJo* z%7*B%A|h5v;6~!_Wb;(h++e%EC91#j9lsQk4i%fZ2_ym zR}>3rxIyToBp}fme&sYEQxOytt_wK?HC+0s*<-<9TkPu#qo4>{kfp(B)t{ zEv%Q+sI}1aGyY1ecOzL*J z;80hAj)38vWe!HOo$RNuO%aI+hYhk}o=LOxKES?KzD(?ta?QLc>d>4>r+SQmui+Rn zj3wzyuOJRLm7wksd*Kk{rIT=x9%fQd!Kz82&lv}<(8+}hi=?i*el{45vz2d`mne}_ za&HRSYeVp^qJ4D=CGc%s&)7A4!=+E8rF{A;6ZAX$qlmdQ@(d$gmK!yR0h=(_7IZtP z68!feDRJ^Bk&%&FR|RLZN$>QuSCX>V)R{I6wplWb>u(fMQmolh33#3Ise=g63Yp}+ zSd9&n35k}vWj{kqOq0@L*XLG+{sOm@7$_=>M;7uy^0}7N^vm(pk8(`JXuMk(GlzNg z&n6#S!v|G^L-#Kch-mGb;Qt2XW_VcB=>uG}!x<66VYBEsEcFz(-@Vr>bn7LxlIrCx1S-G8Rs*a87JMU<7BBi$X9O%*XZiUH<+E-aEK4`Up`Vd3#TOX>rbT zc}y&0#C!Sy7L^Pwl4Py`p85Xw&c-BlC5fbOOytW2?9Fn}AR@zCD9EHn=DpQI*holmP#p9#BMvS>@v%^^{$Wli z!1+4{QPYB9X%tnJpcx(M{-O$9)zfxZ@rEoy(BmBC`L5xV&rhT5WVGl;84DTy#j!ms z8@ClM5aZ2x{dB*ZDE1a_PDaWxGX_0?NGI%{N$Qe$jh+sqR16YN5jbsH)lPL!kalW+n z1vi+DAJr8)wqzFE)mUMBGvLxa8{z`)Yv9&@OOxhdU#4=H|2_L;w+8nkXd*e>JPhF| z0jYTivIW4U*~wjhCQ?TX7uOeJzpS9LXBW-@OAYw;#bv{2t=RlGrJRkaP6W#PHr~g` z+b4lbhX<$Lpy2*wors)LDpEm@FK!=Zr4u(xDRvf_%(m+l*=c-KvWHA7Nyp&mr9YwH z>56~z`&QBJ2G{=dh!6lO9n-UJ&oEwrTjeM|F&R{LU^<%T{z`itb6}BZ{liG*@57Cs zNWqYwh-h<xDxBDz$T6A8a!PSAQouk)Uo!BL4|%7ERA)-)w8P^D!jEK=uy=2 z8pDdm%1F%n)B+K63%Pe1K~*)1;_WIe%PnB2`z%$4-rZY`Ke-HU4;A@d00Tm<;Sb*E z^}Z#BX0fg#bIp*qZ4Vx}*ldxyZyC;HozWA;8O_IhJ(VCE7)xMcNcqF6O}>_>rlpBn zBUo4{eyF0bRKjpw>~9$s8vh$9ky`nrX!{pU11SzN9Oi3glwc>v#u}V=YF|Vj^T(sT z&p>C=xo5Nk>?x?j9gpgtN)QvYB_Kxaaf@We-&IPZELH1NSLJ`I!Bkk^CY1?l#J#uW znYDGlF_aZsmemEURK*Rbac&dn)62XC;#=}B&MS1fG|R0 zY0MO)5pHJO%DBc~gCt)+@;o->^}^>#`(;MGgO1`=uQwQ0h$`vRE5#OBIBb4wf5(&A z$uIrl71_6WoimT?=Qwv|V8;DvB{>}#UIZSWBuEl(Mzh%K+wN@_Gr~N+G2xgPU6h5} z%@DWcEytai>%XpgVUis@7uipt!2@w~cPT2Y6Q^WO65+_ah6wmN?4H5=KDea-PkkcWW>}l4msWezZ_y_P~YWY6^ya@O1;QcnLC`)4G5Xx>0{AJF- zkCkAa_+1UnFX6_&X?OUpPhC{&CTk8X+zu)lzx;DXedkLHYniJ%Se=v9(oLYbp0T@h zxP^)h;X>vbiA{7hbW!T4nV}py2zKiV@_5=I+!uaCj6T%&IxQgKa=45(w7dvzqb3xE z1m7AOO(&JT`f6f5LeP=Id-^+r*ma)mr}gVch;Ew$7qYkZP?Dptkhwwoz*}ppHqh%PF+EWNToQ@1log1I~)`LjxN|W zYZRUVvc36;ZDySJrl_lk9qe7u+z>r z{3xasTK-N<09O!7CBM8^^rSKZmfZbyHCeQW0o&@gmk&U9_#I2gL`pld5bFx)lOSsm ziW+YIFL9*|$ZqEk8`YxILheiI7;!tVKY09+)@5}EBe?`0M(f{_2!GRYhd=P|O7mDN z_3wT1B(6R#)F+_=3GMyw!tYC97wO{#7x75rdqC1e13k9J8KXG-Qn)GC|2?wXN`7RV zGtuH)#&Mxv4Gm^qcVWg`HDH*UQ%t!{eLYqCk~dqoW>-J6 z(KA8u>&F%F&-jDL`Z2`%;$`UK`QB7fh*Pc-zJP_Yh7NVlO3Ov@>yCTRqt|h4$HdWl zYbhyMPE2@L+c3ZW`6jY~Ure?J6~)zL z^l0~5KT$8O*qJ&7vk}$FQJ$bK*k<8~?Kq7!~+BeYCK#`BW1Uc?9&|`pIa@8;nP+F{p)s z6>V%9S${ozHF~|w3?LU3o1}DMxDYuN3?BZ>HEEg9_uqb0RwJ^n`rto=V^0&sxBs_m zsf_sRvM5)jL7;Irt9~IdziW9)&QX;;Z~_o**7y!8k;y*0Eo)FelZ2eW6= z%@G2>qsWbMzSH?p*iIIt?=^@K0xKCGXAu@;*5-E7MJkL-_G955SS-GSz0h(yaQ#k1 z@P`bAk;LnIUAhP8QOmR$7ZHLH{f&Q512RDIT?Pl4C?`YYVAt^DwwHv(vtTt%tFFGp z^YI2RplLNvPaZ}#ajb_!B=0l;Vx-rDV0>M044j!UmX600Q$~{MrnITXhoYg?x=R3h z!OrrzrK^7LD_Vaq4@0(ectfIKk(=2X8|N}dOt3Xl$Vc#O+5CDE$j*JqB{t@avRl62 zreBT^gQQKGzb44#_2%KlFxO(tD=6|yR6qEl^h-u8e5(yArI zMQ?tm?D0wQfZ`{O*?GImC3|72e|Lx2R~oyKFD@_YWR^CfF5xh1pjs{cgR%~doFQ`B z-N5jFP#}1jd_h}&B&oCy4|NjHqVtb%!)%{h%Zq`^ zRGw)zji;UWov|{uN}2g@=GZVb{y-$KnE2c@ACkhUZd4wfpuO^A)Rjz$oj=RRs_2K_ z+u|9(@xGs8US&KfH*B=c&(J|qwb91L~$91gA!jzY58mzR={1P-Lp%d6XrO+}eg zmnH9?iSmF_`Q-;;niuE!R_&ry zS?LPDKeM)wn9-Eq5-?40V*SJJ09LTzt>hRerl_SU}Lz_8~z|emL*4tA=WdFXDNi^kvB(_Ywk*q=BjAk(XzVr^X5Y~?aqeFVnpm^ z4DwF+{bjH}sT!^88kl@euqYUjh!HDS0vJ%`D~6$VKe)esHsv^SE~ue+uO;|%n+e`? zO`LJnZ4-56il{>1t?20MF3g^!2DYDMB(4phc?|Ek>H35)Cd}iYFQlZ%eY=kDAh&rC znJb)_)W1kk2n_ce{Ex8}y<jo=oD|mBO`)Ujlslpp?v3*l$IBc(_ip{x3#P%cy697`VqS9b zHI91BNw`%R_ZH}C9ZAFbpG?P?8HSR1b{K_*UkPPMrWc{ItNhR{i26D0=y_L}qEw#U zFf8rO*8O;UAOn@Mv#1ne^zF3-*N+)%FnNnL|MJFG*wUS`?U}$&CfXHZ6ZLzi*%sx^ z-79(VWJ?!v8>NrdbC#d`1cuY&TUdMh(xb@dGA##_TInM;)z@TR{k{J! zQ2R4Vz37Ns5XAt6CnoySl(ZIxVF-m8)v`ZI&bq&{<5-njR|zM{8!-?6K39~D?}gaS zWa$b1o+(l}>(b%s^#AHU#`?3O?0BgpRE)X_Z5WVz#R`3u+;1Fcsx-Bp;g&$~oljBz z!(V(>q;KWqlvhJ#=HnzSwJ44dJoZ3;0W$P=L<($%GSQ^?*uJ2xm|)d0p0aQH=*qf# z!Q;};l@i0EFJW2~2@1F3RET4qS{;^VEUfgZ6ijL#8HeSj(kf3SQv|?w!h?exfI}rc zwkeJ7B3|d$)zH2S;t$2r3`GU8B%$i4#|AUB#5}ETtP7%M_pBr9wlMvP6!m{0Ypnh9 z!CdeVxrYK%8X^}E4N|eTW|0@kDa3wKTcfRI5wK7hE8sZIf01FNpm&W~M8sGTqb5zX z6i<3u?!}b2HfeXHA=6PX*&CD&#k*3s)PTa&US8@R(PalNIP^}sK)fv#=m%OCB(vMt zjErnA(ue;(vrv^IzQrzF6ZO-)Zbd(fURMFe8(+y)d@c|<%oXSq^UDta!y}S7aHIb| z{YKNEIWr@PsvIH97xPadT~v3dzV@%4DXlPDGF#R_308bDA)errx+EX2(+}yQQTIGG zCsR=N+E=Z1Wj6gz4hrr}LhfwemR#ia$PwNV9kIA}H7(h9isN~Z)1blJcELUs>ECR6 z1QWX|JHPFHEj+?+C2Y=+gq!WO5oWs1*YDRvCxwB7`*8!MRtqBA!4uEhK9Jou8g1=- zZHN}1gm6QCet6QmIyHaE(JVtlhAS*M3-i^wD&LyD$j8#B;8%su#7e#?{pU;Bf7)98 z8bud2hB2|+%FIcY3yc+)JLZl{^x1(xXKC4C=>dtLWCFmJ{qC|qM)K6x`XVo;z#od& zrwzThiM7nZp;YXlbNA#`}CT2pZk^r z-gh@eWqzcqhW=Pi=#SBMh}uSbVH>D~AVQLK-oo{1${}%p^5Ihl@QrJm@+=|h-+Qwr zw^^{eHIo0W-wbH-qnJa0|A=j$iG?axy#o)>pI{X<(M^48naGW~RN+`f5on+Zg-dUP# zp6On0(U<)tTg^!2?C37LojkyRmc7cb+s2dG09BY`j!Iu;5K@}5RU)0vstfOpLBM`7 zI`r%&;gHIxxUCHjE?2q7){2BIQL2nZ`p@KgvKKC6lj>OvI$6E@Ik`R;Mwk=IYZyFQ zZtW`FfG_0EsXQc`F_sY}b^c`xu2=tYa3au=K4vNJnVLz~bvE!~gqhYK0e!M63|NZS z*Dx)5^Ln{1f;dA^A_B&^mvqd|bOTcw?Mh?AH8$g;=*ukmtH%pAxoW1hPWi1gwc_3F zmdjp^G?UMLPeHIf2b}epMp3{?sj-tB(I&q|rrwoe$Ca>Y8;ugtM5Of&Tr$gqE-y<8 zgyMh?EiYNN*#!9iJoX~w1VOrAka)#E3OJnxKDkNgMcxkvT2XR>z(fCk(I%XARUY3g~~vIy?L%`feDhYVwqZp-_S5qnH6lgofJF z0bqs!Ke7-CA1O;j8cf=)(TxB`0u}g7BUe{h&Fp(KxJK25h z^T~m$URlEY%Y!IEZ=ImF9frgGxZT{dZOLSQ!_cm|^Sn|NiDJp^QKDZ_YrKP#!g8w7 z6m=%?*#PfWFJ|EL_8b5A@@oBx4%K}`(~{4GrJR{shMJa-BvlwMDNzGE{zLZNroeC+ zr5Zp|720j$F_aBppzOWBL^hSE$x%VWCiCPZXHqUPu6$8o*OL8TjAB-MOjcP!nr2OC zDZ*xl=1mxLS!@fv5edhy{bA?H&F}!5&eaURIpxcg_NV_}XZI`&u!<02GiTb_8q>79 zfSLZ5VxD-Z$)D~}C3M&OsA3-pTMs7+w6}@&%*>oF|J{SR2=4IH=X3w};YHmiduQ2! zsHnjV`owVf+uUCtqMMLLvyJTzWp@4_6WS3Ba6*KN%jiIPd^vMgVPj>Laty6p37$fd1$M@e zwC-0(!n$1;1_mC0R@JjL6yNJK!h{Epf}bOSllurlD}q@%t|`g^pX7)6c$U~N@*~Od zX`%t-o9i*#3a*<-Qeh!45a3}5i6J-q)8!X7vdru&chd05egKUeTaM07bU^HP}6fDGT@DhhGgN3 zMly++l8u>jl!qz=vNU?72yO8}z2TxVu!z=k-%ez8+G;Tb@iR!EB@4ES+LhF7bv-JL zhvBSuUV1iNGJH;$0UyNX1vaIZvv$E4OsCOrWWc22zYvc=qzKcSE5+xCkDy zD3!Tkh7H3n@e&#;b=0xSk~Y)|*vt`}(|K`lb@ATbtCuO1yY`XRr4G~g~iP| zy&IzDZPc2SHLgJGMlu*hObEPL+F30oa#+2t9lTcr8cqIu7O2F{|EdVdnAPUmn?ew? zc#c9m0jRAt0x*Xv3tnya+zol{B8pFBuf&&Ttwg|=Ho9i&J}E>g_|MI>@JPD!cc<@D z6it@g7GhnHNY*g*k5P^??*@L&e-!$yOUK5a`*d~?;#80@F)HV^D?jhtXS*Wrz3lWv z{KTg@GH6_s4Rxq)Vp2dm5-MrHDf|?LTUF1KQvAnFJD~8(Vx6TS=rDRgP7+-qBX(bc zX+Bon$Gt(f+dqGgW}z~h={nbmVZGk>_MHz6AcTS@U`k0?+~n;K?BI{%!l=2*YxzZG_qgrhbehj67aDU_Gep*D5(_wt7 zOiwf0t*a|ogwN!94A>py4UoC&8((DziebHHL^&lB;t3z|CfZx_mRmwh4J0Spy+^bc z4j=S}(!YuJ#41Roj0B$ha?ncb++Q2xVs}o*@uco2o69DRp8cU4(Hzb4df|hD_YO(S zTNogeCR@ixM_Vgnvxz!thSpY~0~=gtjII;Fl2855J2wv76Y z6n&dniRsSq{l{F@y09E5Wm=&_5MKDPlRFW8bZM&X1(CEBz zN-5~W)ST3l$RI7Zmw(fMGjQLX;Du_vQFGGDWWHUy8+iU9+`OX67Q3O~F;4?+Kgamo zx8MMT4CN)Z3VO?FYutKE=UBbkx*2iSCRE@0z`?v9er~t7quYQF#2Qd+nNJWb_i*8! z{UaNElCE56LdajeHLh2W!k)V?EhHT;PG25sR5QG$Ew0XGt7e$jj*ux=!Imor+{@w{ zEKiouTsA9C*J+E~-x}Rma=F!bpOnLlq$O1_EU)-GIj#;fTmP+I*|~SYj(2&t@?VVl znwdX3UmljDww;Y~s9)b6RQ0Osd%qF7JLI79#}Jo)n^JefR+2(Ve3@_^AB9JC>fbfW z|IZ7HI~XG+sZ~8kg@Pm=lY2TJO!B{SRoSiJq8>JgW_;k>!7{q9x8ewGyxs^mr-rAq z^y5kOg7igSgERkkn_CI?o#8N*ofdkT(&IHCAX<1q#c-VqJe17m=fYWossvt{VQnkyp2YTCKHlu& zG47Vk!{@{LHhj-uHEipc4_OXptobmz`bRsOVS9lt0#pEy&ZV|w7c1Ffsh?X>A!ON+ zFYdFirz9+U_S4zHp9i3&@0xb9Nn*QA;N*r2vcI2h4XO4PtJs%iT*s0&ePi2edjG+{ zZ3hy5a{mCERL63qv?$D^yN|$qj<*<?@h5q}q+yYwx?G+vf5 zE7m%6fIe?uQ(icA3^Rv=kk|c>-(7E&&qdkKSuN`z08A=y29E^7qVbS{NRzMhbmV%6 z_puo4B@slt6gpm~l2$MiAQx(3A^$)8>|Q{(AbjRe0B_YMw!gvh<$y*5rchU)yFdcM zC$D+X8FH?)9QeFH?0oWB2aMp^FY?!bgOqJS=ZoWUc}N}|td1mnzpnUPb!F93ILQoS z@Fp2+AU*3X2bHQ!vw%#8;R*tdC-MfNFPnX!@!1ym@l@Cx?&?mU%Z9igRu5V&zF&1~ zr&W4~J?#m^P=;ME_S@SBG))r|>sLX8nsn^YWs+pn{|;sE9?ZT;E?2I0wX(TpHFTrz z4r{+Q9W^W^Xken}It&n)2XuJPy;j$CNepub_lTg(ua7@wPl5d!fI)wZdY z>@7&v%Y6vGhh#gEkAn@Ey)4{`VqIPxv?|O{wM;_<;wGG)?o}6EbFuh0rmiN10N-Ga zL8k2esu;D}w!pMG16~DXRT3J;GeR$8l%QbYwk-n&(l((kg zj$1!VH-22ld5oCEeG9Wc&L^5QB|pkqzSph0MY~b5=b3)vo$Oj%wL>Ca-yV=L4(tqMLCiP^@ROX3RdLmJWo~S=%#9-TgruaJ zKZ%eKCYz^}NxA3fk0l|pUW@@?7`H6dwrI^%}4>GZ64*J@EOA#{c9Zu6(4FfJuBCAoK0~N6} zu}ebCxyy6R7%@CLNkSq>0T~Yh%TgRNv|3}_l_VD9Lmz)D$M=K1gyIa~6GZ_bp%}L0 zXiP`I&<+Pp=$fn=LGS$`pyHMK1^f}dKM{|63TypSubvYaw0-X>v>rTVIh0ggNd3fjy~>56L}M)g}Wk$z#An)acMuesOdW4d$ds zSof_3=`#0{O`g&laP54f$*No42?8Wl@i6-O$Y5cLD!4!BIt%B_B-F0K!I+Ms2-gRH&OrAV?q=mm?}v_N5A+vGl}S0@TAMX2KLvE zrl(4k$6YMG*Smt`Y$y)QplYZrn&tuw7J}x0pE(elCG^SsY*Gy4l8E&A#r5lUUYgXq zd~O#0`sp;<7X=tGhux}n9DrOGR#98w?V`_oq6HRVs4*gamj!2mEZyZWv!RBouoj1by3LXN) z$V`7lYid%N#5)NuWzU>(LQSW-@qBM6%v#V1|MH^V9*Kq3V>>aq_ord+m*fI)JrvzG zvq`n@xQ?s!*m{hq%<5R_^!26M&L4>;HHq|#k2h)`oT)nrV<(2)XDmEuRHUUpsF}?;JDerctR+6yVBFVn^k6b zo@v;-n(!qaxZWoXl}%J=zemM8DlM%3A~rqKsa~b$@q8r51|pNwgYJM|8=EQ=p377yOf4-aqxDkF3h13P&+`5B~RDIA^nib&Da~h&bs{|6sK#-4}kSKizB1SRg$UnZ7 zuDhql-d5lDq#gmYl;YE`^M=7du3Tlg%+g6z~lw_XR~Hq;#J1}$qoch*Fj?=053qj^9_8+ZHlc$q4mRG|3%74cEPJ=hcxx3Tc< zl^wTM7e)V2-*WQw#(4p;-09(C^Yz9{kEaQtb4?h6)p!5T3!qmMmnH2H6pUG~&g+d4 zu*!Ts#RR6^f)~dM#H5t6TC5XNV~LvOo;hIEo0{`n``Bq^s&z9+{550vIRG{J0(+88 z56Ge3y!F{D@SVJ?jCG~S_E@^=MlimkD2n7a;x4So@Pg98{m!uVHOMGdlfG{rw>kpQ z`a{$u5_B}0w1kMm^Bz{|Ab1(!{2&`J(83U$W`!H%OTvC!#Ct?7+aH@DzbdDa9>TV% z6X_w`$?mECo$aVPFc!(Zntw7Pj?Q?0v~%zp*1oC5VMJk$w$b~sxd^V!GB6!sfY+g` z9}id0?O2$-vV;Eye9xRi=Pf+f6&Ll*QJvYXGYoU}^gaAnwwg80p4)Adj1J7^b7K=7}zI-wKDfJo(dYWY4cBuXKt2 zEDZGd29HW9>{@PsfPC+GwS2}hQ<*9i*zWvW;t5FVP&^N-i<10qhr2_N}@?t3jhGpi|hd-bm<&1r+k z^N`IjAte_>YS9WU(qKO(+ryvWtyf)+z-qlmt@qk<0`1}x&@gZqwD|ENE<&Hh0ng%v zp!zvI?E`VXt$%Y>omNkD;e47)ISSN7gRyNVwO_!|sgk4MAOJUS60g8XqG5P|*6^%o z30iaCCBFUQCmBLIH?xFmVIA#RAYp4y%s??iFK(@+=zlpT&Dw`c}=WuDC71T+ko&v@b)GtbjtpKSex~!G{_JMR@nK~BIe7M%KKqY2Nk8&4IkX7 zoVVv*hqv37VJU+fVg8~%0mCB$-)N9RGSN%*ZIsv;$huyPN`MFw6GO0}P=E{~R=ylN zkq7gaQr#z`xay9SLIFGp{<3@+j+ckO9cwnGlc!T+#zZAj4_z1cg7*X4S+MIYC@vO{ z2}TW4ipToCf00j`x83|XV`^0AFv>@^4bG0xAZOcnzwIHhT;C@PdP9~mO+nVw1u!b) zJOVS~L`=jBi%q4B9Gc>GDiz<`srG3iYOYif9|+OqdhQJ_;c17JlepHXmFwz_bjvpR zg`b2WwWyb#Yhd}N(BpMpc?Slm$)G5AQv<1OaLG8v4aVe4gnO~2sHQEXOZiIRd7tb> zwug{Lf{mre*e9_w_CC0z1R*>N+^#yy$ANUa4)9L4I=?bQgh3gS zoJ_lH-H1rV?jEq`-}!#fWV1T3{JJ)lthFKDtxi@!74^rO#`fRQ4)qwc!|#ss^5VFA z$lQJlu}H4Z~UV`$S`p%;y1O9?*;Df&Ld z-gMm_n`+xhQRD5ezG#MQ*bUUV#(#?~y`kH30&>W7506)K$i8<=Yo9=7I%WUQ**gLD zT3d=f_%SLw5O_IcqK>tEL`;%c4`$i;_}H_^R&(ztUv;yUR14@NAmz zI@*FE!Scp~J_99KMPSq>Ha;c1g-oz3$>Hu*)H=urvMDvwYn6YXLT%ApF}&*PEt;pC zrtXh2&g+nnd5A6t1e)goM`=4$YaDc@}YCaPiED`5U~bN`=oT6yME0=#V* zhq$@BFvA-p#rgfsu2QTd199E%oghLxcYxMZ&X>=fm)5QC;C_ex9QT@u=#v@>K zm%rwj<4u1N@AU}$ed>^5P~h&T_ggp>rGegoM}u3BQ^++R$}KZ02m6IMKu5u4?vWc5oy1-k6m0>7$*yrcubna?&YK+|yB!7sMF7rt=St z@z^N7N}#K*G`+Hm$hspO$y)_X4V$tHDLvLYyg!p3g69(kE7bvbFlD?B)+496bfzbg z%FBuBdOtcFCacK|*tk9F>TlRTFJ*up!IVNR0u{g)iGc{=ji6NNhukd4{w2R?0{^c$ zYV10#%c_A-E^Ilk7oQwJxF>y{n!0P`TYx&hgFR?eKKbCE5tDtqQnA(!+Mhefa)xt_ zt*L3uQ5MJ{)U0#2A^Hah?BiJrJ>>cr7kR6Mc1=IvfA0ub{`e(8h283W0ji7(C;IJ7 zQ8v5J=%{P7#!^#!m8yrX{Tj8;lK0eNi%yg*h!@s3p)5(@{)*6?Y(@NJBy1wjImi@v z9~r?^XPGTjeVl08@DVuW)#1TR8gAo$55fDFPciexHZF?@Zzr-P)qk-4e+M9e0bj~0~s-V6H87C z*}Fc~x8%Wg(Z)_%m3!k{*3WTiqAl{N6ybuBXi(??LqeziP8`-4gu@g{jiapP0Bf2$JHqnjXDZ8)lb>7eh68x@(h@(Mw{ZMIEzwmY5Lf_0Hb3$P1i?_Fiy+Ol0 z>zOBjN(CoR#F~2BOUj&(Da|}#i-a7=tZ2utaEIfoZD z6|5n!@u|a=TS^2NM0IsM@<WdBUda5-r7~A^CgA7YX0+O=LwbH?}z_w3qUpyvu-l1h7=ezpRr< z*1il|Nk^OYA#4~2dx{>}YQtr6_XRxTq?Jn#cSc9H$bJ%_ax(>vkU_3x27=i4leI z&cE>gxeRd~d;Q%WGm!*|QzM@yy#V7+{83v-urUJ8B~vgb>u)1y7bZ*y(qNb0`yu1K z&Jr`?3ew9Vo&{W%18Mn|`@i}an*cx4-L$Dwd1tt#zVG~Y+Rpb7ehXH-Eh1Qrd6v>D zyab_;=oR_ zuYQ>t!y)A?Z)5F$^$WQ{N$?^8*N%k{*S)d<_T?;f5X*(GQK*HleCvd(Ks%X|JR+k# zG`V8wJa`Z5D{nm0F$AbPO;i>sU!-^52w_MjK;<$!a?lOBk7MG(8>c zqP%PkM}qU3l>VC2CFvON`T`)&vgRB)J`q~iMkuIO3hM(|q+QLoJ?fzH?U_qN4_F5f zoFSL`y0Hk49vU3>1}6mq|B}eP%s33`+=n}>9{9)Usm6tiK-YR#i=3vQI1TsuC|`ja z<8HgXNwL#1UmHtHnADxU-Kef}5i+OQ*Jtmq47A9+)E_0+YGOFm6n4ijo!LU783K~T z%D$B}E{FV#!0)795SV0)`nG^oSu;#;sP8TljRtrzbF@D;P6hY_4UD?Uya5(PU%hdV z5JO0P4Wfe-G5_>(3)E0Eldg$!G=K!B12s=CPPoP?Md1nQYx!Z09z(vDZ2cy%?uP{& zLNEG^3<0+E%T?Mk(jmWwg5TY?)wcY-{s(}ZK3T3ZenQ+l{dcl%AL#SI9e+fDu8Q@6 zBq7?EpSMB0@7=)G24wa>8oPl|{C=ePA{?n)eNWqv06ZM+6KUj;ctqFM=X~&sKs8_+ zRpu_QutGi$e&;G1js=Cf*{34gP+dXWUn6ew(r026txJ8E;kHrSP!`{t@Q$1?OHLuB0MCeHpMfwfp?t?(u>&o!vBca`Uz5tJ)iybRBlXaIqm59fgFAS(eJ8lo^bW z_^mc8SP$^f_qly7uI6Xaor2+U2n{$-4!*bunt~i%?D5YJ(-8ElDYvA&nU9Qv0uPOk^~=v)6vnW7 zzTMhip?&BnUd1TT)fb-rHWC-9zT4h=8IZ{xOnV9^QFk?`bEfVsli2?Erj*v{le2+8 zcO3x9>pNoDU3agK?4Ohz_A0Q2wM8g|wT$(!0IJT#F~+Av_V!5HvXwj-)ASZE{LL-X z-D<4{dBlp~FM%_Z1yyhJhtk4WtTNdA=wyHW*s8)|Y5(&*3~y9-)pQ+C&3KRT_xkFV z{&}YV?$a+0%An+g{a6G|z8YULT^CDUlZI;y8?5`+ekHOvvqFoSI!g~OdDupy9}-y{XmBI%_BI&Dvza-GXxFCf+6Z%fj=N5+7CTYc=rFFZeG zqdipCqYbs}*LH^9c$aw*)oSu|n8zg9L*=titIC8DH_L|it_QD;A101raYrFNZQq;< zv#Q~2Yy~P_6nOH&OriQEWDN52GtpnrtzHpODsC#QITNn?LdOqBvZK|np}Qf=$-+;i z`j5+BYJCE;1U*>4<7~mtQ0_)Ps-3g=4(m*ID9RJpun^3iQPo>H-qaB5UwHz5B4n}q z(grU5U>tt2hZaK1EmQ7?mVA`U&r=z+`U75159plC`ro#!qrLpbZ?R2>am^Ogk zhS*WFS;-3uDcF=4AoI=LlK<<|`zrO>kB0iSJWhk_N_^$qFQb=Ugi+E;Z zI~v-Zepr?Vm2vCu?_f^d>21D1FnJ@U$E9=wQCW3Lr6(WcXvvMbW6w32na}bDJb0y0@B?`hlIp{ zGz>Z9ySRVP^Lg*BY2`$uOQ(zu4{oEc{zwjga^0yK7n6 z^U4)Jz*n&rOms-Zr{h#wjmy&^(&AGu?zBYEGZ~iUF79;mF>B`9bc)oeFqKq@UawZ~ z>Xw_S(5xgy9eeg-jUN{O@IiFY!}89ZeHU08=t&f&Julr_nz(%vT3*}U&@(^b&UI28 zJ;EZ`k~^W>e`p%_D8?Icj7EZA=$>sNM@_tQD)z>``VsPU+yrbelgPfxYJ~*>-z6ponD}Z_| zs>@Z%TxyhiXpUsL6>4)9p@;Teg9&r~eTTdWRcB1kCU!mg`el|Lp`stn4FGL?*D;{@ zn?|Y-%~R8wg4T4tbOq<^#_!584^7*wFeB5gJ9?xD=Lo5j7F1$(8(mv}$kSZSG7#nz zF;nhkj4Y}*fnIftGQS0Ifcge;-fDY=S0~ysKOT$og&`zM+%38>hykt(Y04Oj0AAE% z=sGp!a%aP_{ipYKTfpl~b@+9PGTyvhhU9MD?|q%kdylwb35VV<@vLAxCqvtxE7n<} z88rl2honeHsv#P-K=}L%Yw7Ug{;%)$1$kdDK$djfykhpz&F|$8xI`;cHE*UBtpvZU zMe_qoFi z=WuD7#!`4&-5FTn@x0C#Yf7_dl4&U?jjHyO*u|I9-)GwG2Fnm}?yFHpzk>;c|8^O5 z9mr~pi{igfj%VDh!I)(ro=7rF-YdI_#8Nm?6l?N(!Kx5=hVKo<=9w-g*1mRI_Q5I~ z?V)e&*C8!-nbIL)*;;uhTjM2gZb#i9OPw@gJf}8kYnEymqxT-_s&LNs8L}Mf1lio7sbZw~{MIdmi!gcw=WNq)qoqi_ee3>%dblQB1-Yu~)_;(yghuXX0Wu(B+@uc8{Z*pLn0e7%seQcK-ih^l?Ib!6^? z@+yeJD8;=Z5Xjfkcl?Lfky(wLAYVK-xO+1pT@Tr0iHXHi`@?L)16vjNn^0tUY35{C zbZy5xFTmI<+e%e`{g7aofM-pGX@j6E7=VS~kyOep{hi684KzX!p~>bn;^*e8;h)s~ z)@2N09ObcCpfWpVuH6R2+odnd#7&MZeT2SP@Qd%iQHG@*r4&A%siY9rD zC~sH-)_%+rOrDD5%``H{q1>(QJt=Um;T_xRejwOL{(xxUn?Vhc(fcs6&1+R?Tc!j? z4nYKg7%NW)2Wp9i0hGESBk9z203rbUwOP05rdtZJDrDblYv?yRqWhua+r`>zvCB34 z-lxAhw*-&YuSER`9?1!jX>E%d!xG|}LI1bFSwF2w2k_9>l9YX4gAvOEr*cU4`~DpdH8CVq~!$hXccVgBCXQQJ50Eu;o2E7SjA|*yAXw!+p+m+ zUy?%hcyR_J$Tq(1CvF4whCJPe9Co-lBJk;E%(*(=G1m|r%8pQThqPPyCiTFjf7357 zO^&X<8-!$%%~&-{&m=J>xu->WtIjcE-fpPMk2{P|jB_6=9hW7&>4}Ev=xUL!o1C^q zzf~kzvAP<69P@5~a%jDR!wzO~6hov*hZP<#B&zum$9Yb z4ggR~nHxc9w@Tw5$SA)?Qi}?YG}vI&P4CmF)tXRiA5{ysH*{C>7bR*Ax0bq0b)*yA zjq}F%wUnZcp6^U=+p=AbVW$|OCa~I3w@7?c6xROsc`~klk``pHf0+uDM=uPgOzK*d zNB7U#)h5x=pc?KRwJ<~(2h3MjL_nkJ-m&Ajw{=)%bo^*}t;2dkgP6?oqZDxmD1j}r zJ;u;Yp@spcpPT_HX>DwR?r?2Fq4+-B$cog|iuqaI?G`jm(0DxVbWq{8#1up>Ey@XA1sfdcRqB*UZ{+yknbA{uYz@;mE>9>+y2>xT?)$-{UAlW+8QWwd z6Q3kT0;TjPNSggsJolXM;~$SLyedpq+U04zmZ;jk*irUjh=9^y=|*Oc)eiq~c4^Dk z9);u54SUD!r_pR2T(`Bs?u1JkUrR9oWjc=!S)!=usyrQRzRkFYW++djV|+6b>x=%;)3hyN99Zf8?tOucvIq)@Ykw?K36%ux|}91 zf$}f*HZ2O+&OwZQ5C`ht5{9PFL?7(o(BRKj!zoBYKb~)C7e3OT z)_#eUu`1kB`OXihSYWxc)UJHey9lTgpyr*}Ym?pO3H2C=_DYb)IU3bTysKB$zkR`D zFV|oExF$lUJD<*2F+&X&2vLKX(M){lof4C4*RuR;oNcE3a%=%;@3l}5vq+*xTt%!I zHlwJsJgCL@j9&4TTVPdsy%tGifLcV zW5{D0*0+&3X6&HzwR+~Y8GDx=6+>S*UA#+iz5Qoq$8M*wE2(w_T5SLBhtDrdRRp6q z?r-Ve-X^NlQy3;B6>SxDXbJV(1uF(m(1Hvi!rq?j&YXY4!1#%u}Wrl|7x zn`s>S6~8mQC6YLeYU69iOckWfBGWg`hme2VQ9Mi(&K2i;ZTy<6o)6qc@irMm$*lF8 zd9BwvESbA*3_o{%r_-0*+~I(@=uc4+ z43`#Z9J_Lka7wBnTA13#m$`}voR)L?SGt9@__scziP<75X-*h|8?nRT7`X|qM3)tx zR*6n>DVd_T@I^us{}IJfcP{75wXqq8Ok@_6P!o&e>P%zn?jNaJqMir@)sVKx$G%~N?&KF6 z33_oC=KsCS|6VPdJSO#hx^Qf6y2e5j9$|E%&)}MRYf)xOGHi;DvHsUbqp6q4&$~Su zbsvFUs^iqR4JJLAM~G*Z&Xmbd|KJ0o%zv92+R#PYv~1OR^MiY4L?BS5n;jl||CD6) zjpHFc``Igtt4|@B-enHEj=do=ls3t4QcA;>oeUJ)$5JemP_|>o)_||&A6H~22#_9t zP)Bc?As1>sx~q~dHRQO4Z<-U8;F9{R$5!mJE#|+e5S~$7>+yNoA06?9ExZm^=;4ui zQ3D6N}PQ zf=ToDVsZ{{nw zJ(B_c)?KTqm6KGHWZAEcNXLs;fG`BQ_-ju30W_);e{wwqQ#0QS69 z;7j6ihIpC4`nEK5NcP4(if02AY{rw9vH_WH;?26ZMxl_;CoeqojW5oM+RE~1@iYqC z()xuo+PfKmLGlXM8fP5BgZ%Qjr4hSqKhZ02J1xgEda7 zknu#Xf+keM7G1QtdT>t2k1X#7q!-3OW5?VHK$tJ^bZuEvFY+fjwz%TmB&T2S{B+sM z%K3C$sFuHtGRhOr7jLF3o_<)S|FdYeNlX=uHO;;S!C?r$14W^^X<{uSL*^s=*1B!( z&POHRh37-b$_ywcG;}(M_O3WXmp-V(;ELY&s)Fu-vIlPJ7F+=zD4z7r)WFS|yCiC1 zH(s%L*08?`iFQQu?ax*q zHf}n1%3I6kU;jMPi7!a4DTV!BGMY3iYuLa9IPG^HK<3#23XhkMF^OO81K1rk&m87{3Nhz#}fy8s|!0s}8Q>2#~U1HclRy14(Be*e@d-3V!tzXtmoX&|n=#g>dD zoXTq|HRr~CsI6St_D7SM)t+guWZW-c$+USr4rvAak_zDR4D*&OjBQHOpLpg0LyEj(uqr4xzJxzH>e7?RGEmUUGp{67-TuJ(7J^H33XF2OAD0>O)Tc7!;c^r{=k=u zv7!yx4yVd<+-?0feO0y<3G0hMgG?upfPKwv-!T}rApla$ch5{O0UhDSSGMX=^W$T* zt1iudv%;s7ass=MnR`BgYcekY@5c(rOvdoJI@=>3Z+I0YY=dgbr~x6MV9u|I9c8CfURg>wR2ny3-ICecN#hP_KWV4b`##rb^gHo9GhoQg&t+ZX5 z!kGFO`J(SvABA;pDZD$`To}i1rYEJ(9hNHLEcfOuQ}|?kT7nlPlyiMAQno2y)RaMd z__>!yxZWN}_RJ44_%5LTW`Lm_J%rTL5?Je%bjO78)OCN}GogUojQZXtBvL1DNAboi zt2;>E07%gYeSvf*1mIe89?_N2xQEN{ocuD99A{Z70o~3tLOXU??Z$jue(B*fyzIra zhHsFV_!v6SKe-tJx$x9mQ5!`qo3hdxE4b{<52iW##6{^eSwxPnRmS@xkm4?Tc|fw# za3=!TDdiiFA$pz4_Gv1m8UWyDAk zQ^w@;EZa^;_oXx1$k5I@~FrN#XrhPFFAwLY

    x#z={lA@XYB`dCGrsmVaibqPcvW>;`I&mbn<+dK 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�kISG>ISk@*9Yb&Qz%Mdxy%GN2? zeKR)j_KI-0BqL&&srY5hJFhL8y{Z7d;sMVLGg@+=n-4d7zqr}o5Ci{9y1aI)NUTG) z2gwM04P0BYH|g51yx9QmN)U)wS<*@0V9cZr9U_bj(}j};r{`<=3+*WD$(L$@p~>3P zlrAU0k!T>M1ySk4yQA|A8FBYLyphF-zPwiFkLT~dUBcgIzYn}V>RPgd>S@dtL}29y z*XL@?ro_!s`jn~Xdh?V!PgNX>X}fG`p>WRX=rKIc8M9fTax&r)or22)6DDPajat-j zm{%zZGVD{D2|dTkD!-V+4Sp!iGS&#jflN7&jvGtef%FD!J@}jNyh4MvULwcJKj~cH z4PnGfFWa4QzexO&5IaH>al(^TjL@Z-e%NpSmV*kuim*z+EH~Qr@~B|gLz4WaOJDmH zkDCWWkF!A?(FfF~J?QR1osIP}gvyfIaP6r8nf`-P%r)ZNfd5Mdwoi&op0C0I=fF#< z8LS+%WD=GCCpR+x)Jzrv4JIJa`oY9NP!Ew~<4Fkd${h4#5U?f6D0aAt&$Z#aem!x= z1gE8N38oA2;QY~hFkb{G|8hpKWp*IZ<{ZL{b)AQ0XTDbc`66r+C&&ZT33s&@QK_6< z-C=9Kx!tvQg;csyit1pp>rv-W?|zP_VL%mo{ws#)v5dw1TUbRh!U?ZxQSsWZPs!9t zvuB}ZC_@_BxMtwMx8sI9)~1t~zu)7W7cGpOw0JkFTYd~OHXBAd=x5A)QHzPKR!oQHX=RzdZoB>Ha|K}UL1xfiXQ{j-#J(wwx45KajMPz( za-L3FqR(0G$eM_IHekgkFZjZ-bH@vrS+i@LCM1%6iUz6> z9Bjap(udqeC-WCmq48n8GtbTYw;BugMC%4?)zh~x4O6;YnXt?0LZb4tXo=>2OxCPM z5fpMjZ)n3QE3!KtOG+(~_27C>8aXl1p<*6i$qAx<&{e+ztyRNFt&?@Din%X+vfyKpq{wM+T_S;bd_VWfwu866_7I zM~QLut9|2!S*i(-_Z*C~+{I<@jJm;Z|E{dyJU~?Zg?e`m(;idL=Xj%sR1$7|;eBSq zZZkF&0u?b;*L!`rL4rwxvMiq;O!&eI{hV#jc0aO8&r)q1_gRIhu>haaawb(@V1wPe7RpYPSJd zy^)Gh&IrE&8yksN&RCV)S=NuO8}8+3rFN{T=c0-pmi2(ctr7*Y-rH*K)&>`*lU4ayBD zT;eWu)#O5_vqaoC(+9Fc0!ZXe%-!n`B!i?8B4Cu|zPC)Yvi|vj|DbJ(DHf=Y?}u2- z!KnJKPIYq#AIs)jNB^6Ww8fekevxQ556r`^EXy4yv?|k(Y*LpkY0e}x_L}`JcyzTW z!NVElZ*+`dSb$i{we-(eZhGP_x5Jyu0<6T7C5@w;?OubvjuP$JgR~o77Z2P6Z#D?C zcuCR1tyW}Td!IMi6R#HQ`Jq>|2^@>fi+7%p6GE?j+JaQ*Uu1}HS_xhwgK~RX-V`i| zO)aLcPUV9Pesg=dF+$m6Ap>s-;MB<%x^yBx(zw%$Z{YJP4DYMEe$vQ{n*jGXIHo@nRUi?Df!+Fh?r{c4cP&Zx50!5>%xTRv9hi7mnPN0%&iY0=s9jV)4WAtln)(D zoOt2_!+YJbOg` z&i2rkL%Hk~L8uJfsgbe*a_u%D+mT>SGOB~~pj?$#v<2yie1_;?`6-0Vzlq~a$+wzE z);cEx1$IJCJyJtAodiz62b!GMhLOGbp*w;3Ha~C1mScHot?Gr_ADql1sBf)4Ip%-Q z7L+Q?gRir3V;a;T+q0T(e%vl;`&te24Cb>b=_CoZ;ud}MC%t|0dD0>5^z-Upu$EsdTJ?)`6`xI%mw0#}?xb*^jGR$zEU3M@*wRj3gAiyN9= zF9x{y04s5#*D0GL>Gkf?icug=k|X;5fp+#v=UXLw6D}lobWKGd0K@I85ttrqad@&# zZCe&d`(pVd_cP7J%Mw3bywf*%WN@j}S3NNCdUqRsrwbpHdz_imk{*0*%*!harfwJ$ zxh2ej0KC4DJOxkcFY>xVDHzP;@ywwt5qnwhzU9WNWY6TKC|GN-D_V+J!_`j-er?NM z;@I!Tev_FvMeu%pYs@1(kE?-z1x%K;R0Lng~S}6P5y`#H8rHxOQG$$rpt=d^3Hk|Cla%`JU1mhH>)v=}9WIu`j zT}SQrc=pqb(1gjEv)Iq2iWrB6@O9MAOw@7@x~_ui38kEpV>F-D(m56L*NiRH(Ff@Xyg>^pJ}VBCz+*DLbCpqVYHoIKFw31Z6f-j6 zm*2#qK@_4eg+tr*>cbY8sJKm87|@uTZyY}hpbUG84DP=={CZu_vQViB~ zwRn&czkUeZCT`Zlmk)Ni{Ck?bvf0GJG_%TI)sV|9G7leU<;OntndX-nt@3QuaCPoW z7W*TSzbSy^Ik^RrBws^1Tj?+x-9^}n9uB1$T+Y^i+H0I~HFQg}#YH)&hvKlq$xo!b+jEHkE5|I?FTP7eZOZpfI5PNR&*4tF zp;Q@6O_wcIh=Z~Xi*5?Z2OF4ftD^3h&KH(vm3;M;x4ir6fKsv>T3xx{qqstOPHCTL zmGE(wHu`2H{7C*SjsF#2%dFsqV88MGkIo3?N1@%qjD$Qfn{33@jqgTa1j}N>Q1B^a zj#G()UpGb3jNV_;t_Ot-edG8E$uf>f#HJ!AtxuX@y|n*1fdgyk<^k)lWBo&QQc*?b zh}&YZT|0PNlfgpSznPmsHS{eWsg8_cjNtUc)-zk=zfgR8@rhTyup978P1VRoS{>C1 zV<_p`Uvz3SR8QffpW@TQ<@;o6NUol+mS+Qx>;;Q27V!4a#g=gn>JVEX}JTQC{g@!i z14`d$MqYe@u>Ig%4ZxJ}8wY1Ls6wBK0<_j6Hsp_IZGUkvVnc=o6}0j)r_jxWg-se3 zS>NZ_Q3>(nCntv#%?)tZ&8VZlCl(&1p*33`V3qi24JyLQqRToSmWcNL3~Ddcz_2EP zJv<67}v z+F@L{;)rC}oL+xeHj>S>$J~~anz1I#8Mg6eue5%XsfuU^#pbSRAqUAv} zlG7e?z=j5>XNK>WSIP+f(~9#B`HREHXyM=7%O4ORMXWAevuaW!Zimt)YxtIUWe$y3 zVVBi)<>yXwA8)&9AA!A5eBtLmF89v*Q8wsLGyF2l*i%>%gGvSOdxs#-&qG0!PTW#H59>9cf$dks4n< z&x}%5&Hg5i6#|##AEItC7rR?T67Mr1Ap{7{rNkmhWvRH(!MpX46aJnrwqV>X8{WfB zQXIv#t=Wx!W#LVa<-zpfLdOdRQevj7CeE*@vDo4sxI(NsV`;+EkFFWM>~9q0+Mzx~ z;x`23RWoPc#n=6Y4Ib2XX-~hQ$;zgrU8n%65{fRyVK;nYWn$w@Vt?W|RdRIU%2q={ zA|Y~TPU)nFAP<>yh!j4>bySpwZ~{q?Ojxw6!$6*+w+RyGU3beDVR!c>B>lfN2ex{@ zX_&!U0lcMUQ~63IPHTKpJc}ZzXI?|j^byY>{r$p>F2@r`%)t;wj&Zbil(Wpy7H+k z3RZ(U%(KDZvhf#=#1Vlv9)B&U7J1<6~#<()K zC$xzkQcy z$6^>8XJ`iiIM4oTwUp4wXzNpitDACjskh^Yf$`4EL6DfoK>VvSW+*6nUeg44sW@D( z;I>FC%-bzaJt%pJ48#b^>D*i*4r6j|qO7xxYAmsi_cXTVU}o&UmMqvY0sJ!gV@tIQCHJuh6*MoXQNzIJJ3SRQYKpVu$TH69jZ6ZF%p(BZ) zSXf$yb<=ervK)vh(rk>8it^EF%?X^B+~$@t~98)83K6 zkzd}prgUKxDF0NJ84%JN7gP8~!)r*0$HtSy#Za|60<^H~i!KaE=mOZFY$_-c;-1p- zm6?Q>S66>6Z{}=T)iVA3xkBsTnPUehNC%||j`EEN<#J}`4mdD5Bva)fzKto5ln8l7s;vI`2Fl1CIhZJE;2G}rl- zv;WKHEGfVzn(_X8E5kb~h3P(dwX{5=<3IR_l;XTXP>6_4m8Bd{oORnqp*J}YSG;Z! z5Y^dwz_osXuiPN3%*=|#B0Is?xm<4+saqY+&{kYZbEMU_&f3T&_pe@h4aEyvT9n7e z$LqhEq-~pHXO|Z1d|$b_xi|y)zVpdDY6g8lL6Lq-MQ=#ev@n{RoE$$PIHu9T0zRf^ zMvW`Qm*9nS^)Q05*XzWVx|cmc(uyJx^1&0InpPkhsM~RVpy-HED9uVtTB5T#`WK<# zl&l(FH8M9hBpIN5OEp|L)udT5sSRn26%+w8W;KKxSjd5z~ixT{GgBGdF>xsqFlCT1p3?u zw=9QH@5g0DcZs~=7Ux!5=(-%3X_0Cdc(QymIyuP&lN*-%HYNflujm6wF4yEY<$=`ybT0bMhFAK|sTel`+DGWrsmmPCYE*q>HHNsp(M`$&| zi!(CD!x4O1%Rw%8-~lIrssUC|c-t4FOS)uLjf5skjlc>KHVotNAG1Zhae#MBW%5W2 z*eDDHwcayrh{GHR?Lr(JjB-}XSMnLtweF2~eRnp0rF8!+G?-{uB=B=Uqkqrw(A5VbG+si!jnt5GCh{wgc{ zZ8rokoF}Gldjbf)PeBMl=DoB#emDMxrTk{WrRdgl!Cyr{fzD}Qn~?M}Arc^xlLfvJQ$G-w}u zZf-6CBaItS3#Z#-F)KX2477ek=bYJD5pLhHOH=zjGnkU1Bx92FXplj$X#u z1|>0*QvGLs)LI!Hsa6TR%!M&;FhW@GVgJG9-%kRL&c zgG)=Saudn%_+#P;EAbs zRL@(g(0Z?EJkW`Xp#UXN8#=!h{Uk9P7CJln3HlpWJBAVHr_G-r?Jq&doMB|O%;1yz zcdWXT2$A1>F(oO!Ar?^1=H>2hua4CD0ZRG1fxtgv{|+F!5=1j6lRF8ZR_TxikCxaThF(M#% z##J`SZ{OcALQh&`{R?3KHPtPQ3z!Mec7XD6*T@=|2-zWI!w=EWFP`E6o=oRP!FYk< zL&Lr`J}-1G$^TW1Fbq*iGa}h11{g$v*Z2UwH*@3;N%tEE`KXDRi)Iy)EIPQqX5d4k z3lkFfvS5JlhrbiF{~rHP5uR7xZWr5oZ^s6^7213bnWuLvxu1XE@R>3hGI`3blKwoi zLm@l=503wiN~VHEqbj5mmxmfG&bG0N5fU?f8~iA7TGh-A^0;GN#CD((0uyztb^i5C ze`_};C~wmyit#Iaqqc}jt00fghjfT&z7{H!K^(Q8@IJCRg9;hz_?#fMM#KM|=zDk_ z!Ygzr zoA?BS$J3z*K`Td+o|vN3Wmp(&C734AWK!ISR`y6lJdqlPiG5N2lOX(~sY5dG>kP{I z205D-b;};1AYL#Qj^h*8NS~tGe7-OdyD>Fa%OmUM>P?@ z@%F11;krYt1latFu^CDif8;nNvhb>Ru`+x--Ea{4UmWH^1!o$3kD8)<=PM!Mkf=Ze zi>nm_XU1+B1bA;k2b|?;+aOVLz5iXNMe}wM)_4==Z0$e9Nmgveb;Z}&Z}$w;kzzx+XaOqrr8oNv0ufdQ1!w+ zhO-gP(J_Ls_Hg~1Q83x+hj*U)Pp<`C#}IXubc?@k?d7<;c#;!?Zqm`e%t`h}29Y7E zK)&&h34sq;A`$GMD7-+o@eqaF(-pPBp+X>&rR}@_7W3Rwzf8t?ZzXOkuLZ!QT76h>GoIW%1MSh2x zz(GWcEpBVWI}pUlvZ(4%(LFB}?~U`PNZY+gNYWo~HGQp6ADh^uWIIRCTz00zO(Ekg znSuN;fMIn@fulU_BN;^12?R>%>FGhSJ1)kavzeknno6!|$``--L{+x_u~uX}FsAYx zPp|QFbQu7~xNFEXM8mC_?_Zl0?CefRym9Z+$>ms>n9>>=)Fl?o$Zx7>VTc$^c^9Jl z1#mw83>VRNU^^$8e#US$GtSR-9Y);MeI!EKB>kKc#ly{wFRrZQ&3+(lcP7>(vuf<+ zND7GcXJpg>+?{wgG<{e3SAJC(A81I0H%yxeS=mFpsXrSRGo7PYK!Tox0_x(p=`!>; zRqD06N5@DgVk|7ArU_kY*{}IB>_Y_9$1Q#>sDA0+3E76(_pQ;l(Ns=;6=Fw_Wv|k~ zBpkKN6uy<%e0+qp9?ek_l_~e$mi|hFkdca=WA}JDjd^K4S@HCIQ}f3-+_1pN_7d~a ziX9l)+2uSmI4XUp7XN6Hot-^lq^q0tUP~)TB=x4Y@3x^SqLRRraFq9wpw*9q*=b-+ z@tJe(k`^5cuJW}t+>{&%(QoQQ^AFTwBVoaxKYz~6%vh6fV>&!ujzmTWQtdX8YLxxi z-=VmWWpPEOQ$TWw6a3~qsvqw!E{{8QG0)mRe)!oF#V~K@uRZnJ(5o~=nQkU*#Np~HDGKG-m?(XiDsMik{j(53@ zA3_^X0QlXx>8~*L_va=I;;q*+A$#$J^-Kj@nGGWXKXK1kYg5x$ok{G zMZBsG`UP)a0$G4I8}8-U4J$q`RVpvi@eC(a{}QkY*}!?6D}i;3OnBS6rN%L%9?lgS zC)p*zlCM!O@6TX#P|IYZgNB-#Z)JUe5A-U@$~9tA#tsflBM++0LMjjo$VR+TkXF>; z)P_US0=9_`ow?x3H|FQ#;LOVE zQC!oeG3?!&6HMXU`)B&V#nQjUB*7^B{BbRWC^R=NH`LS zLitecW5W}w>cs=`rP`FjtIC5dVp)Js{`}8wlIl}M)O&;MreVZgw@-aGkbgXV;WW4} zBOegXVP;h7^IGvVQh_HVF?p+kdiz&}HIT|kP<1ZV12e}m!eLq_ZacdwPA)B?lN2XU zLy8B);7YSyMi#XaA&XTv=7%$Dh_{-tpZ|c@A6$Gyvsw^W?)y1Csy07(+#en9e)-z% zawR=U2^e`s#nN$GU}1$Hc^y8wM`6gd2qCTCfWAODBr8{>ufRha%Am5{wDo zYgHBVyqF;E`6)Yr^hW+#lyC|o>@7QuZCwN(xEX*qSGfVW#jgehhu4FZy@Vh49D*Fe7sJ)SEiaTe-Owb+(hE+Ib{=K>Sz-I1lx5 zq%7an3-CO}c;BJpdtKmyJGF40xnI?G%r zvcc%L?Uvp~wQOns@iKV1@QFoBa)lnvLV(P#`MH0){t;Qe@r{iQ3~p=XQaaAq+2K!fEG)c$lV0npepGT_MhQF; zm$|q(vuuL@3jd!e!6VQF>AfjZQLz1WwNELIggnEyk{qdP9EN;R~(0Tj~$#f)|;`%;FYQwkKXZow`r$kD$fmTMzwhlNZV__-Yhn z{W!RnheMg%A2Mh>r($}r#&uS?l7VN>2w)C{N-*{6Vew3EJ)7ZoQY#Oew+du zT*Y~0eO%QlD#h4>H#1QjJr)-2eQo9FkF%^T3S$b+rfLNLuEpWSQ%RG_jb3Cwa=A0_nPi#b5_F#m!cG2EGbur^4`CzjqBaq1^QN*$L zO3v<5J3*SiQ}s(dr+^h+P-IR8x+2uGLAh)#Tu#sN@;Ze(UT1JIX`^TT1d%rVIe(k{ zZJye@r%3|(v^^=4{<7$}Zxe)K|MW-w3ne~pgXyfI0a=D`KYuje$F53`%Q2$F#}!7% zY{!**!Ej~f$NZkCp-Nem_6yCpo`-ouqYl_o1&!6Lj`inX>A=j2EYF($gfO0 z@0o`{dw&xpo(0byAgdYto=C?w$`fhH(|FYM7|%rsQ;eU;Bpnsnroi+#(9A!wv%O6& zpuNzrxL8yXsfWhhp^Hboc@4r?Vx;6Uzpr3{F@OzhJd9VL~&ZuCkZCGe^d=1fD@h*9ai zLRWx7%JYwN{k?RN#j%|6IqRO#=SlPp=CbsA(D=z%SK>OV#i-vZa=2{U^& zad2hdrn{1|Hw|PyRF&JL#Gb7|C3h<^i?paSd)-+l-fXKwY2fm=5bqs8dingvNcceh zquLRlk5(pQNM{&k%)zpdt$W1R(@FX&n7&l|2K2MpRZFIIoalUXNnBP4vDV6Uc@J({=7vWWYjyv z7NTX}Htk=R>Lv%Q#{LAXe(TuVDWV5A4;dayf{SM@7UXxnAP+75eRlhgk*&aR?hF?k za><(i1h+ukzd(^1uY;$oKww^;*rki!yn71~%ww7$1u;42jV;K@JhC(sPtAJ3_<@-S{2O^P< z(dTW{3g*-Xj5k-utpt1BJ+-M$1x5_G&%-cToE}lYb6CuM@xVXZn@k0MfLB2E`tEp)V=dAmps4gvo!U_%E=}!>I0Im3&sEHB$)Ycjr_oLzVO1s zv~z(xZq8jRYM7y9quusM6{wQ}WF#E{!dQ2>%9VFz3c8HkWX7_t*U_p|c54l(C1$%5 z@HD9eSd%ymbW84}6tXFEgePryN%B5Mmn};8zHYEEgR>}tv|L&#MWO$sha&JNHd8xg z*vn;@yUi7Ig=z5RO)}i4Z13PF!*xGPd|47@V8j6WaLRg_K@{*Ro-Bq^72c>X7ZpFl zi5M|uyq@FmSr%Xx@zFl4yC(lchJY(-au>l&QQKTvppp5|qo`H2Tn*~9I^_IMfhLg} zs_9e9Nv}&5Bi51U?1=V<<@k%BU_3ltA`^g2HozA^67BuTjR3Mb)uo8h6T^Du1D{~p zCl%ThUU(PPo0ge`)K)R-*PjB~BX31EnPsM#lL!(&*pB)yMxWnA;eM+sI^|1uVC-XV zOB~U~X|ct(w4%2iw5Xznh>txtYk;)-8=>cr{SApOH!XfptrMl(6}pg+yj^&6n-uD^LK z)MBK|qMt_c^tJ6uJS!nj8twiH(R%7QE!S5<2kSqD;dY9;GN7V%^wlYr?gSMx1A9_= z64Lx0$#PTc9%lT+J1-K3lvY@y63jF&RnsD-P@UT6Hpq?B9z9C!*Qz1Kt70MGxeKP8 z7xec3CS3M-Ap5;yC-YoEAgF$hsv=~<22f5hOko8s|!Am%R* z^!il)Yb24yVdB*B&xgt#1>dvo3dxh0;-MH7`fHjgt8dLa&4y%RXsg0&v@%YT0ZgQB z0a?VlzOKv6e0MU07H8dqLw<`=VSbGBxMe#HlgL$4?&fbU9 zIUp=Fd!CX4ky2R#F+?D?X-&djq3-yn{#a0j#Ux-c)NaoeSH)9_brIAt;AHEVp^eKa z_e4(I02!Jiw5M}+$-3)(UCnLt=&ViFI^Q&A=EOOAQ!mwk6`CQb9RtG+-TD%VgV+Q0 zuSpvbfdV;I_!!(C3%`|rpqtF!VSk`gps~SOLP)KrGoj|!$e5wTp^4<^A#iPBJfHw% zkAd8s1M9SyKmB;r9f91xvxaqM{jsF?je-cTK9xc=V}*-<`8Q-(nEM2dvG+F}e$?B6ty4*N=Kj8|7}svD ztUZ*n9Zd02oifC=QC`8LH#)Uh?>}mrZs4nJP*+bBk~zjDMHdoq3&5N`t;2e|I8+kx zRV-Cm&uNs+XfRJDJjdsqs<(D%bU)|Yf?JwhHsGFg{K!q;SQc6hhOnP3(;7i3$bMHw zq)FK-(x7{0G*9*drigPUtxjz>SheEh70@y-!n-O zx-hSyIO)nzcp79BupB-FjBmk33zqJ%j+)`P$O$-H5-J>nR%Glfm`^%@gZ|8Q9L%R9 zo_%4v-?^%u}_w_c5%#zk|jq6-S z=EA$H%)rStOGtC7&F?EyJ6*PJ59BCU%s$;>d~EJ|xQuCn7EVj9aj9*tVxl1WMk?)D zzTv%iFZz4*Em*~Y&P&?k5-7?GgA<^x#oMU_KcyTZ?q(g5Uq{${XHjbE%Dy1c<=FR`GM3C+amHtf)aW<{ z0Vm$cER3k(!L&h&9qIVU{F_XUpg=y6G;x-rRwfHwr;mIz!hXPt0y@{AMYRUGDD^!T zvUi>neV|Bkg7DW1;Z@LzetvWa>wiHKonjF-sN0199MVa+0V6J}Twc?PE*tF8Yw_-l zitmm{mi%#2CayfBtfpGh2Y*V6)}-k8nA>Yc%b3rW^c@i@ArLZ6_EZL9phYCYJ!^Zj z>7QebUx}V|$3BG{)(x&C{W?r$1Ki4)Sxms+Fgj1!584ikBwTIIU$bn+A3MBfcQ5mp?*#V|M4rjvw$4=a%CQq6Q_$Ql_p(%3W zBz@{QH3#{%3x<773RmfZZ?ylZpd`XVeM%=X`BncOG(H{m?7+obh?k>f15`m7;n7Cf z2os4I4&1%3G4?b}AnoRmCPPir=rIWI8KgLv#sHpupny~2{t7LXDYUq0C<^gbatiZ6 z$L5(sgwt{~!ECSu9|*3Kut62SQ=oDghLyu6_U0&NB#@r&A{Vun^CQd=042z@j)Wmd zH4y5Ve+ytA38(aSL~Zi(jI4iCc1~o5kiaH{bwUK^5_`kSnYL2$n=2Bac^hk6+_0l}_WaNN?-qndjBF~3d?cBQu3532r8pU6YYTJUmtZxON zy*cD)GwK+z9NLU(i%zme3mm(5+nUB=JQ`0-+Fcq@hCK$^-4Ar+@8UdhYs!3qpzj(6 zl$0`1Mu<-Xg zVfBOPg0)Q@xt~s=oWfIiniLSj&VPibn`OZ|xCz+y%fBNlM(bK*lu>5kZrRI%5Er;w zpQ&Rp_)dR7q{m#);}}BZ((5cCuD+aPmE4VVtf;tjn799uDIqiV_38b1LBTq`_Ii($ z;VL*rVQUkO{rto0qvS}*I!rY%jxP17ULZA_>DE+aLq!U(D$xcxtsIoOco*7Pe_`;w4_w z8yH=|C6(w))rg=h-s@${8KqVe5qyLf1-Gkmb;b8D+Fg4-Z>(%6eD@X`U&*?OU6%wW zG=3yk3Q3m5eFNRuV+~CNI_LBV$_D%zG{TH7q0ff>_4$!Ii=$g7;ORl`>db7&+*~GK zTFb;rTo*_$i*d!daxJ<*Z;$Ra^={YVmArrNRm35^@>lIpIof^#oxHy9lm+>LChln7 zukwsv{2j`uP|Kbm5n_B-gnqW(Ri(VEDgQB056dc2DAeMus~nfR!J>)z(@zS`v6^pz z57l{BA2U41ROb~MG!52vC%%sOek6s~C~l}r&?w0}JiqdBkjoQ%5wAwBRLsZSRpa;` z#3Eis%pq^7*untmP-~aAX$)P>9yHyg-t03r=Du247YBk<&Vy(aAlxv?q|@GMwv$lM zUK}sLJxN3qWUU}!i$&7v4sO|5)2NF(oY{$Uk()581t%7T=VZNB1uq!jR}(#$x^-f8 z-?&CNzHgEcH=nI0Zse*_Z2Cc(cgcZD8-aMC{;kEUf!^3QGgO0~2(+H+KkyH{Y&z{# zjC#{l1o>+TuAN^BlY3cnE>tO;_}(bvq^}ne zMW9ZNVZtY;A$N1C#mG%`nrB|9hp`?0hs{XtQMpL8v~LNd3~9ORn4sa^0X7McD`^&u z6%mhoci=DBhf5?B07TQ)_I0o5I`97QdGPd$ZyEVM)vvBSgGLtmLVUG}xXRY6yA(79 zHL`K7@8fSt*Qo#moY#2HZZ2VI{&QN2okDdhn6C4f<9Ri%*A!Z{4v7pbl7?2EDY&Oc zoAwzJBvtGKxu>q23u7!2*AItM$5P7j7>fw~_o^1neM{Gr=Q&|W`pTXH-;++G9X#P+ zArAswOFEmd$LWooAeal5zmv0$^a!p|^xVIgQ}2C%R40`xG@>+qgcf}5m_hM7!cZ8q z60Po<;CSarR-d64*JLF@7N&XaHR*7nCwV`ge0%Mj5Mq4W#k-I7a+$4s=y zyF?4ttm1tBDj8GMIqWf#+_1&@n5$`@I=Ul2N@LRLo?n&HE3GXG4-=Sf1VWUP7=cJ5 zgLKxCfp;Y~4Zk)d;*IUCt5{@;U6Z(_&ydXf(`mf7>Xoj5o1$2#rq{cHC+Us(6=}!9 zoQ4U!&S9e;^YX_7QBS7N@B8fe_9`X;8(le|-WVtjw$id>7tF(4ws64a<}nDmta)t2 zaLr?b%a@jN{9Ej9h{2aH3I@vgX;|{0JaDJC-YkV^)bJAEQ)54W|GHr)2?Lw?$HkI~ z<9ST)#W2$fl?9I^G+NmglmcD(wk4L_l*8mk+VLTW>Z=l3k1>I=jLgyg8WMvcU6mLT4-M9e;~8ozPv3s44CA zznI9^-uhQkq@-UG?CxUajroIRgW@L7W^ z?YKeHuBQ7;R1u_GQreY-a?KABB!)W!z%@Z5M~#CWO?0sc5!IZ~70B{otk$lc$|}h| zG#5qg!a9+g#z!LRY6GsGj`>S%jjb*<~`*%F=9g^6BP zyy`goejV>v({m7e2Y^^8YMtuFU}-fndXKi@f(xxDNt%Lr1N-Sh^Fj#ct$yv_FOqRJ;>*6KB4@P}O_5Y2^~ayK7W9kU*msjqf7tGH;O zsT7-_OYLl>`_T~J5+&~w|KuzQ<{O>k$~|`6i{4ngh75urfL>)7z0Sm-2Hd4-h)pcF zsBXTC8BX(+{yHC)eZESnV6qGYxrFe2bZ#Fa`7{6*(0%;H zW{7z+;6aW&%IEhxksk4&X`X^>f=>0CkQM{hNsA(X2OoxEx?-bPu1TJ+9(lDO$wpYq zMRYgOP2ZsiHvf-22%MkZ!^70cI9^vn9*1NCWkH6wW#E$x%aR?AM(QnW9I4(wN^U(4 zBfsY&B-C5+Kv(Hr8#Eq!`^4(X)v$s&4t%kk74Z^h;J5*i0F#WQ&f>V%c{)RPrztSbGHNF$US!N;}UUG+KLWu zvKx1);tDEPt(nZ*QBs@*IYL;7nwLFw>G)M1@O2R#)J!48vtI+e-T_iM$O>$t7KEP{ zp6N0V)-F`51)m2jHR$e$fSB1CtScW*Yq%fonJO&=3mDdl13wons#aOwu+QrTKG#OG zyV{m`<0y70Ay@rQ9cOAW?m136jlQO)XQ|qp%g>RT>$kBT=C*{U7KnTRF|cZ6A?O2Y zY^EyM)P^;UEKO7|G^|f?nHxQme=5xK`sAZP&5pSrsNwXv>fuVhG>p;;D<`~X6lP;| zT>n|UH-_186UGbfnGIE=#%LIC@;nog>+v|Dg(ov2C&}$-m#!aJuXo$+leP9LF4VL= z$c|YYQ90o3aD`ikyn{p68|d=j1w>RpWcKU$&d4@G4N<=pG0{)3?~ZZ`7+PEyEip0z zzeZoTfF+5>&)7RIx;P4gTJ?%tBhQ1kYiMy`b8N=PwW||hGGz@=4 z>nN11z;8eW*y0OJ*`#8=ipAf6Fmz#&@*67$MlG&f#pu1=%zZE>te z>96dNvRO7o&^9GP@M2KV+?#wKpWCrW|LAL}dQ%vdYq!xc#&f;T%wVv~8leiCi*7u`Oq6V-m`4Bdh+tiORKzL3o*N&BW&02ynC)7W*e$||)14WE+C^A*P_^yN1s&}O(n zzOg1yQ`J4c7EQ@18bKr^bUi(H`}pw^>OSD+lyj6@!*@>7Yxy+7e%Qs>>k{GxrvP9?Wns-c*iT+0i8hW3~*`KAmi zH@H~pwXIR>)MH^Ovmx6lPnP-N(NaSg+u%z=9I&LH0fShkzTM{)Y^CBKU;OwrDJ@n1 z&kp!1^+qqiaT2D4Dhre}+*;O}pxoQ)35hD`4KB~Br?D;m>U|wR@4#`o59n*V`%pn1 zXyd0fZcV%ijd>FM@>SF?E7ZL9;(#dYE$jO%0fBZEs|M&g_|0?}n7~FQDl3@SMl6ZA z02_hWS=FXMK?3Qd@jhjyL_XU7(#I%6q%cJ8iIw0>o!jb!&qp9_B)f7ZD~pcGiRAN6 zh|ArTsr&|BS;8&cR;i7Q5SSo0 zM;k&m%d%d=&{aYC7-Ox4?^VPwhqp@bQnH(vrVzya#`<|a5$QILiGvt3FGy1ZZ*zEfS?{Q#MB49OVez;kH(RIkv18<>{?%wbyA?Q(%Ce zmLcRSrW^cPnHjUOX+hG_WSaVP*G#nA)Y$_AlRMktcb$Ls*kuX0lx&Tpz}|k(lIr6b zEcm@rDrT)hlFvVJbQ%>lP`?)ThiBaHRuO(U0gsr(&F*GE!WXPk%cEM!kqBPo#Jvv0 z%am8Xjt?D9ue2UlyB`2=mfSL$eo@(T()!bk-o#Y!~&%cC&?TD)QDGfh_CTy+NDp1$ot0|yT5 zltimht-(e{13&-$SA8CBr8I#NK?(cX#pUAJC&=$`5Cd5T>+W~tQdv|+oW6+xw-GT()IXdplE+OCzeS(jTGZ1Zk+4r?gX7u?k zH5Yh525l^LnWm6|PWDa3i?(ugH3mM)jw5k_pc@}zIGuo?!ZCYRU| zQmv>hAaL^J1h|R$4k&A=A#B+FD}VtykQnNAZ=oJr)Gz>2JGl25vm+6Ov=W->^Ie4m z-tPQ(HG7u7Z}>!D^=KAnX!C!3zjFu;^@hg+#uDJ2y_VGo@#13rj+cM za^^-%7@ZL_Zb<2VJePM^!?)q{TF*Cz%RHeSiOF%3mSM`teMRPYMHk5NNMjBnNl=yi zDrko?M=+RCjlhX08C>+V5nc=)tr!@c4mI@T;FAuSp-FhMm6Ybtfi1juQX*iLrl|7t zX%*{unde``TRJlYxnNh9l7%sU{slSKNJ)1H^^e{N%dVqT#y^9<(SmktVAN(M2-Ni)P7?z_d;b65&xucLEbXnM z+xc5w;ga;z^1{ni^b`zQd=civ-!u&DSq{Q6bf1BCDGl_4ZV`Ak^`}u8EKt_=%q4Xh zN(QPOHd?&n@<@SVe(N{oYH$1U8{~L+!pbBN5*U;rr68<5Jd0V6T56ho1?g@wBqU`#v`Cgl58PuBmco=7zgf)Fu$yM=9QvC5^MF=ooPxii|6@|fM` z2Z6(ruoX+YpfiP3hV9YhBx$iG8Gl5fe_ zCHE#+QYS@Fdil$sVh9lh^vWjDVJR$JcnUZXmgxmoxDe+Yn~GNQyPUH>v_xlkV$r&B z&X`Qya73rrxx1z$jy{{qHBdmEg6v_E?b06{1a;8K`0WDPKm}qQ_iEW=M5$i)5-IT} zE2c4Px;~enNogm~ z!0)yzcNA*I*_lZeFs3Zr#%eo^99UdD19?@xIz6uDfw@oZmIh76pP~&FwAMH3CxgSY%1Sf zOWJMpC>k!`DZD05My0;;8AU~P^^L2>uF|-@+9y73T0Ik~;%;z>z(F>Two6KhWq=!)?oy=z`X zb4fQO;d(r^Q(2GaOkenx4yjz&;6pM^RASvnBVT$8FL%>mqmILO{ zv~6sgzs(C~``WtMXsvbGz6g%zq=RT_zZWn(%LhMCo-q49?{RqyreSFzR2d;FT3{Uk zE|xrHIpyQFxblpE-iIvp<|#=br-KN5o|le=a=STa>) z=MGh!K{E`(j7wnp3Mpuw@+s|*VB4nAw#mQl%VtseaAFNmFvII*{o-I#yQX5JNM=db zy@`1-k9{K=vt9iu>7MjluDF%x(M&&xvaMLWirV7q2a;0)R?@o2@e&XS$t8D+do`vD}sd|== zZeyf51UAE>k|X!miix%+3%hmJFzh^oV;|_I;H&|G9+30Hf2yxe!jzxZ-dHR=DvU$+ z-c=z;z|bpvTz}?`{Sx0=rKjq!jVq{_*N(EEOrE(_y>vO&%7?;OklkTPCcgrO` zu4t-vX%c~MJ$|$4XD&*Wkcsp}WAEqWI1)G=ZVMW<97-C&J+1vM#%z071i~wLki0G zx0yWM5t0jSyq0dBzln|U1=W1(Km~x0&538+epXlhH7cm3&D87InKe1>S!=)rb2BSt zMVI>Nqc6DVN($lQj{H`<%{ z=pAQ3*sGs2lX?e{Y8*W8;F9Jt3)PrL$?3E$+*-p2yb*`oRfbYB-lgikfY3fv_14%b zw?ElUO5C5ilx63E2yd+$kJ-d0<5XF&bk@S+o=6Z_*CCDZp;9W>Z<-OBMv*|Al?8flE! zfq{DS$m>djx{vd-tptR=+vS>?v-W5EdEd7w!jIF{*g3qMQ_JM;N z!oreb6P7#zSHP5L=I6~(59&RP2?R^2Z9s;Bf|8r{+K*F8i1(?N_KQmi;PcvOvA*Aq z<$=&bP)`w(!h2_qW#nn6AAp*!1p>8++Y~bqW)u3Z=24u_!>KF3raGgqYu7-^=})(2 zo<{qVnsmpE${Nb;1$Byrsk}T%Y@ybU0wQ&X#h8S`;0Qs7yU+tTTUoyV~7d=#;5Q@wHfD$@IyRKW| z75ZV66qdZoakKEBJxzFPmdwbPISpTciw2S5n@IDBF;WML9RX!MR&tDuv2Xi7r_8|?xGrgODri5R00>7*^uf^_LaJ|^RCQuR zy6oiaxtiyx@vAl)QrOqevt>mTVzYnYqI|pw>|Q?*9X;ls_re_%dMkyWUH@GbE!VgG z0tUp97L$X=z_D9J3D>jLu87L!I`;FsvE8tS2G$^Vk?Ud6v4eN!-f7~%DXc!s@!_u!|{sZ9~h+c>OA)!ojP8VC;_*7-1T223S~8hJ$R!f92lVAV)(m}k=LU) z?u}0GI+jkJ5_A8yS8b8-p#fe5Qd+FRJEf&ceb|;bT|!aROq<7NYc@+0aOt*@7K*$3h1iS^MZ0b~A4lx6lPVZ`DsRGGj``UWjiil;SMhua$Y}&;<3dF7i z;bB(Z4x6n&iic^BjO5}G*-KHGPf}441Brz&Wu+|@?hM(?t?~A0x?xoCXhV(D%VJuR zH#pw_DD^677LQCjQB1asdv?akKWcPctFd6yql;Agfa9Fk5)~{09I$PFOUJMztg|jK zR9PwvKlrHy{P^Joq)?MCS$)t`O7@QKeVu@xAK_%oG+WP?`e2f*Kr76v8%&eD-M|5J z%e!UwnA|!igDiN=5mjzn)(NR`LBWApNIhTWL8?h2%konxF6Ukk=R0t1f%)-d%035J zM^TCStkzT)I5Jh|_RW~M@A;|q__sk4gG)`a5geglz?+XWj%=)$ZJA%}alDM$7qKtr zz(XbNG@paCbWXe&w#NrJf`Fk?jk(tz<*4*9{mY25^utF4mleh4POWOr5-bJj_2bb! zrHzW3uU0n99g*GX6Lg3T=9<>wBVf#+%NJyPOPvI1)Gqr6MIqerjL2W$RGUxw=7(HE(7!cJ<%ER z9Px_`Q`9y<`PgO4{+37KrjtQmM2mVTgkVU?DEm%fIELd(P$>wzJzYwju$U1V?ZN{d zEp-~ZF2@529FLz^^aG39$ILF%>pY|wd?(T*M82_fzYJFs2_L;9m@XLAiM?kmZIp0Lck0)EFK_Lz z2J3bG5eAM8`qcY|AZ~lcyr&Wd8*nQZn5BCCIWQ)MT8OBbSLWUr1*RGRo%zn_qoHm+ z8gphJw-O$VF8AVtD^sN3_T=i0$G23IRGgX1^ABUo`83R;y~hWV6;4&S0ipHq;Is?r z0*kNp)@Ak>UrvmFwom|vjgrB?C(sxa3kwYs)Kt)5#Cydz*ymQD;Eiu)q-yd%=;*w~RE6k9VXqG+dt3!Px>SX7B&Yl48(@l23m!(5)>w3(rCWg|l zrjD8ct6e*O1pUCxDjT!e2e?E7Wjn!C4l7-V*Et|UNV_HA53x=wT!CEdUo_vD(F_3w zUOb$f;%-#pz>zoa?!kvQ?W1lbCyJLTCwWR~YAYK@`eAUW9hk-ruxl1VIA1FfA$_Yu z$cXocX_T!4Sfq|UPA~ltrpAY{kS1RSz9T)&RK(@jWH^|z(j6_jUs+D@llPqRJ2~@& zyR{YSLvE*93`d6I;t5a3ovuTR^PSB5>pwn~Y;&&Yros3{5^UAKUFy}SeGe~-GCWDq zi?kb77Yq)Tw%PHsNx|pnL}qm56E~M}lBDOcen@XZH`3SAwSa>srnX&PWw|UNXASCx z$Q-$?ctKf*SH#pLEcz43*Dx`sr8qy+VRSk>PIM8Z3AQ@Q-|l^~m59dAA`AJ^c{B@` z?s|=Nf9Nnj99c6zvNS%f639wMJuE-RHME|y&SKE+4I=(}jZ`jCWJNw6HOl41HzNy2 zau`dh$;fJFnJoJ*_~#g0=Eg$Ff<2zCeIk)Co9=`#qRv6L_wjS$Zcuu0JzDEP35Si5 z4r_0j^Jj@rufsu`VnizBCda*+KV4#QJkVb4;P$@pfES?u5W!47AT)f~w#_%>;rG~^ zP5Ro#^gEq4y`67?brjQ&@xnNt#{^1^r%D=bTU}a7JLti`@)r{W>Q*?&bwY(&?)AXeXUlG zNN@F^(zq$@1d7ag)p=W@G|{qv#m_idLc%$;pu0*^m-8=WAx#;p3a!1tMzXc&uz=Fl zdnq@2e?p^*V}Ux&0dXp&&fr%5BGihSAsLi4+IIxZfK;^0WxCpSoFUfNTCt8R(JEr@ zQc3IS>cm~KpY88joryzgpmT#tvSOOfdMhG!;Zv2l4SZ;gH$+vlB?qsvAlVLs%G7)G zs$^wvOKoE|NNy6xA1XprW`aHXFqI4kbYd0H`hp$YzwoX4oXs$EbNe zl5D0~;U(~6Okh3monB0nL8i4QwCg~HV0-yfYq5|_zz zVq^kMLAC&uSg|Kr4tG`KZTeu`v%)hHs_13@G3u$j=OR=&t9zaiepzQr1f37+n69m@I#5a)cN8?K`hY=J znnxXd&ee_H`+km{S`xc>WJHXtmDY(ynyYkLszt{GYs8V520pj09&iS45i%#0_@95; z$o$#I>|lriWN-~BM`Ag-KQIi?E`DpFJ*x;sjg;ocrbpdkrGA#gu(v1PUkKf3GW2jR zw91W$WI#}hEXFVQ7jfcPYfd#xEHGhapi5OzA%)*0Z3$~fJtnYbuQPUv8>tvrL3w|S zE{W9NPcVSvu2Tn<(YX_aF5P4@F?Ib09sxgoGxRV+O{Wp(*==4=(aoB0Xd|MUdg_rhtDCi^ITLb9{2?F z@DBQTP1nGqN0?Gs0owzN8o&HX18zp8M|$AyZJQocl=77c_B)bZkTQZ|*9v7*BnP)< zVBqc*>?;JzlF)jP^1>sz>vDR3_{jpxclE>Rdw-P2L_`>(Q7TKA-u~B+z0JwGReb48 z+#esmc#a!f;;rx)T@4CA5Afh?cOD5x<@5aZ`vQ@2G_pVD`f=K4IUKG73Y1lm80yS_ z*Z(K9nZOMx?a!Pfr9f719I2=kVGR_dHVrk7`_dOg33}2H*g3Gl9c`L^%p{)d$a3DKEvmWTTwC zYs)?MVLBPf7AQ1?)fv(CxRlnMj`7R$ypglGX}U^VE+lqFB1=Xcour3O&>a#y4J_l{ z4&o4S!iQucpYWwR3!-r9$XJ&-j`jOv%f9`Y79cnwf~vZ+nPyzQ%Z-ToKo+?be3ngL z!NW5e-C2`t!uGZ$#5#wVgj=y!G~pFg^10(HoVvKyG&QSH-T*7pV%(A^nHF8M@b^)a zD?S5Ct!F3Le25eW0E6(HktNGmE>5Ar=A%957xk2ZTr z31OgQ{L{ni2f$u8H~0{_it4uiKP-UKa#zN|oBZi_{x2bZGdZfK@6%3605dP%ge9Jt zZbqS|abVhoXg3&xI^^!2_eP1rh$2pmbDtq229b#K7fLXe1e28$t{RA zS!zt)=KBfUV$?h?D}09{8NnoSTJF9fU)cUhIk^i3U)CY+jAZyxq0%cb7_kR1Fr=q~PSGAi^Q2zF?t z>o+o4CW`^Izt2q-ZugT+gM5jdReJFlUnQrj`RkKg?GAVpqy6BrE^}L{f2Pao;Jf+6nsUq_=7RcDY?5K5HyOaREqS^o%+txT^fwc6< z^UWh|N9rTro0$@mZH_k4dxE26tYxNN?|-7*4>Goz5{HpUr&QC_UOMhCQuf?6zVksN zoF~)gkW&)Gl9%cMK?Q)$uX|l1C*Cn4WVkGsdz?IIkoSr`d2M7>E$l4@qLlA4brIg) z`26r-Q@l}>FeJX8RUV6btR`lSY;qoo)Tu+EJ*`8to8-$od;!`7VF<$bO^tBIGW{fV6U zHMJ!!+I2TzJ8dBR_cm}UTz<}ParGQKu(7Jx;BYz9e|M~s&lPSK25W+p00 zy`xGflcLc8J2bi&s$H?_$}N-1&mBllP;H$)EOc8s2OXbUDwlD>wC&1B(F{#VUrNzE z0OFOpURVC%=0MDuV&j=X2t$@YOW?8sf-SKG@0x& z^gX8yM0bdA-B_#cLEf;8aNgjcd@v@9i=l3UA0nm}oOL^3=y{Em)AGF(gIUx>G~?$Ai#eZd|TA#m>-4@rs`?ur%yr zgtrH+v~u)ysZraC-l=eP8yh(#&qD|a|0 zN3A${M0#C?X71uMX*9p>o7R9ABmD)v1-wf$j;mDy9g|kVU7AMQc0pGk)hdwkGF!h9 zQ|L~uhxUsVr5T#EwteGtaDa9HQhFPZEfC(B?VS3#KuGdi<@@-V$;yQE!pL3fZ)S-t z(2cKfWeT_1>N%<1VhK#!ZJMPsbZGI&u~iAy7{h446!Ahusk?X6h_%LOR5`wQ4J@Kh zkEiQfHSVY8tWwvseWp|HXq4C2nWaCf%YGg$!=pg7c+s2>^csxR%WJ)zbFG~o_ ztIA;ui44{HDPP6?8naM}1#Zry5z&Lpnr|F$|EgfcTb}f~>Z4&Pfv9gUoOEc3xR*4c z;vsd%h;0Yj(`Yr1LpVE9Ri7X1g6lu!Jl~SO?*su01g~tXq6oBuxF_v?ICjEOwb~wj zMTyJN3>yb25fkA_vi`eK_JId_kGm&E#GN3%lRvrzWFAFt zE2$|=(_Xjy6wM)$_@dTSx0wYgdPr}N(F~Dv6T{pvjfG-D$OF&)mC*9;g14M$t2A&z z1vraD3aJW7A&lc-vSQg5n_D#nxl`A(0+umk@6Byd0%cF?&m%pDCF2CGxo5cLhp63B z_g3CY51rjK+Z5j7sFprpv_xPmE&cUKxunIsA=lV*r;NUrA`glNjw}?5^;_Z z_lE!;le2hdG;Th|QBO`<6Z3E4kPhWdPa$|-aw>Bsjl9c`7T>G2Nhu3DlflC-} z86HKIBJD=KKO2&071%3ES+exFV%XiHWZvms(F%KVoUJjH?)}9Q6s<;(e6%m&rhlAd z(WTupO0yTs3m_9yY4J?! zCz!jWhU|;j63QEgIM=;ThF7|OO|(zA=veiVW4e!46jKn7`8<>raD_o3gtb)=;Lw-l zl-J^90&kuf%p+^6_G!~&O=Hm@=Z%s=ll(3%+|Z>>S})?DVD0(#D12@92| z7wACBz==!GKfPM8xj@n9%cdph`Leug_aqT;6VIWN89fpC{ll|XiWg)dQRKL^PkDqC2hrf*VV?^9vpQcmn z4<%eXF8IuXOKT=mSaY}(UO+{&woek2S^4D-`beSzE+h2_nLoJ14my3jGrS%G%2oVl zCC4HNy~T#wl60{@|A!;=01eGPfi-FN5MXX6zzo^f|y#YVu*eFWK;=9QVt^s84N!<{Oh~? zVL8e8iwatVJEr4w!)LiAb<$>l@kwE0mKS9+F?|=@^^${a_*yZO3AFH`NgI@rY{qh* zj1s)>sXI6x=gNYxFPNs^0UoB}!EAF@K39j#;P0=?6g4KiIdGR{m z6!kM|Bv}?dG}c!*uPpx4FoPH~NPj1YT0Y7#6DdkBLuVki{xMNxaO+mdc|HxG8SagA znB6ur+>fb;wZ17KU(2{fy%{3u`?Aji+P)yX-`5#W^9auts6aNOKc3R*%GAM3nGKT| zh#3!%%RuW45PhyQ|KaQve% z!@#(MW*kaIBGL#RwqLdoWOshv{v?3(Qna`wxzkc?#Pn*(&F9*@=UjxabXc*RGzd$D z_J^#l!c;&TQQ^SfbtXH}#V+8h0&S4XrhPe$2dO&wHY1W5jtswsoKZVaLpi~PUt@9) zTEw=KOQ?H|!`$WFX-5kp5r$pw*L~CleZ9PR-}NjN(4mA$9n}t7dNc{mYD3)cX%vfy z(Esr5PZ1;m@=2pRIY3{TZua=J%9Qmk5o&(HLqZl@s!L8t)(xZeNldWRr#Q>MaxNBD z4C)I)uPKW|XVfSBMZ+NN!;WLyYpqM%rnXPBG{BY3!m;(@eV*Y8Nbpr&o^U6)`Dhn4 zE$iuWTwQ09{75PpmILNmCqlC22eSN90RLb}-cLV6l}NZAyCq1A2LmL7{kuiQV_)oZ z{aKWp$6#eXM-dm#ZZ{_IJI&c>m6RDI^Yh0_@rVBP*eE+3t@Qw!`rXQuR{y20%@s#I zr(o0@;!W;br^gEjd#>#(oqIB480Kh7Z-xW@weQEGE)m}8N9AKnlhcC@YX6S20+?(n zbL$y8qh*(u?5|leo;snG>q|`X8mBzk=QKwdyT$Jme?m)K;g(J6vR- zWmxIW%CPEc-F*}J#q}b#iy)K2WvraAG-Y$kmASG0rsR&;B)R`$s!@6hg&n17j~o?hm5V z0<{V1w!)G@wLKw0%og#F2GG+3W2k8l6`aJ&jVe0O(!V^+hWMNJQS`1>zWx;K|Go;1 z<>*etvBqtg7LoH`x)6khC-xVw$$U~~W*G#B^dZqMsZ4Tm*ybr7X#S4#_c*AHLM)1OS79hBvJ$k!m`i6PvMe^~U* z=-+26$iFULh(yT_lk*PS4oBd4anZ^rELBkxd*2Dh5m+Nc!=DgbX7R3F!qrDR!Zm^g zx+byZi0y-hb`r+S{}3_$TDN(zln{^^*PfJ=RDM8RY{Zcs)2J49A}kdh1NuFSQX#bv zyF2c@ez=FiAvyz=!MI}3iC6Sgh`8CA-oaXEp3bBDHhqa~Y9QBM zRK)j$@@r{{(1-N5JjXXC$i?8lCNLEQ!5_x#wfte~^&_;bF85nan(%2VEg!1tDYcmp z?6?ldLY-*#X1L01CU8KZ^IH2Rg}nqO=VGTOBw)Sepi|}IZQg-v8TeneH#s_ltJe~q z`xmNj(!UO_Xx4h{tqRfa?6*ph&sJ^mR(_BxzN!WF=97gkc^lPlv3uiNyE3s$9d%qz z3oC1DVy@k}fdAQ-ULm&gsbHn}HHq9!7S$p;?wqaPZ!2Uzr@WEZc;cN4Qa%O`r+fBP z_cXysX0Z0hp@n^a%{sVVEy&-Ulz)?sqT+a9{(|<0zg^n_9YS1YT;a!ZIYj_yG|%lL zzYdKPC6-~M=C8i(73E1TGBE#%`&K;@R|ao}vy%eT!bVF;wz|hX24`Vvt`s35;g5+^ z65@}R4F6>#N#(^-h)TT&g>$ZwTTr+WT*dgourwmE&rwi+_qn*pBn)a=a>OOq`Z+ft zs4l26*6qZ^%8G9L9cipD;^85rTh*Pp{O_5$(t@xakBgKmnCUDC8e_6DwavCy*sZTI z$Fh$bGuiHY7zjKkgzZLfae7c#C{eQJ&0+SACmSY<4F-3H8tUtRs_}HPd@)`6S%59} z=u-Uc{6EtUIgI-0_F0dDkYr0Y-v?^z*t=KI)KiG?x*W^!JMXuMsp96EE?H=&0Es>* z4QFrEr4Sm_ro9>pY?tO!NQ@{1*B`sMwDv4c2BIP}|B=D^_aZ;mf@y(91|g6P(?WD; zMhsm;65!RED!c&>nm?7QoXEbjhidvqr6di;a%{-#vogVR*Pe^ITTFi-tX_p~ z!e34UNNQ4#LS;TI=8NUdWyT;0rNBU6oXkes3feQn?+d~I)v4Qc(M{d7uUh;9uL6z&(TI4)l5%!d(!X=@@Sv(lHZJ^U)*xzj z$}mIhS@%dFcoI-pwH9gY51qZ`j|*{ag!PerEBDy1{3|YAE4DQe4bclUq^>sH@_Hrs zd};9^iK`^7)Es<5e8UB`wfr22joAAx=HGFvH&k>5@tb={vh2j<|I0YqLH2KIaxJf^ z8H5W}+>;kdE|rp+Yiw%dY7+410G}xzCQPVM*~7EA785f13QjZkePa^N7BN=ZzWvC< z0r6OXF2nv-%=CL=jFn5RxR)GUvarqGP@+GAD1DLWXsN9A6)h<2*c=a@fX_aEf4~Dh z7)uEyB{4++oo9kcT&z~&lF_#}3MwQa{e)V!90PNjrG1#? z9_XlDD%YAuS4^Z()&tT}`1T(fP*J1zXdSl$UcJ&}*6!2GA-mr~5xsDS{@*~|KVpiO zgv(1ywBzIBABOi4KoX_(^*xoJPjN~o3?N)(;>6+v09q-1{)y%758VO{h$PdTEcIc4 zM5XgwDj6)+(-lX=8^(@r-$pRLU;%|cI`<_P>b*G5aiwx@k;(GctdeaHa_3lXeXah_ z;($cZ;GhbxU_{6dT_LpRUvZbQKF%?CY=ZB+sCW5?8y?L?o8(xIY9oVV&NU@GsOIZL zwQ?Z`{_MDD7|dcrNAY|ntsNR!lX~$Gv1@8kKlHKl%~|s#c{_lj_DWtTI$_Pd^gbbK zY~MPm7JN!k=5GWz$+}wFlB)MPzx{^u@s)&+|G1fV#1?`KK zj)KJ_(BGp|lW%mG(zy@n(IRg@wY-;sq9s=m)7I299n&2q;By$)WbZ33!aJnGFsay) zi;?gzfyYU`l}O3-sTT^mf}9}3>F0rs{TnqxP8>QtcsS%Ovw^l>1t9G!Q*!e6!3EST zFhr=YOaQu1&8WjhWWJ_qA#Spgli9hXe>(~Cbp%Zst**Q=ikFqu{rI32{lqYV#Lq;+ z>p-;XD~SNpmINXMfIg9Nw}(&6Wk}9qE(Ant$)st&tm1?_fEyMax--c`$weO9V_|UY zPcS1~*~n>e?|gFUuQjUu{(n}KA`IU#`-rl61|M8NJc3&OS|2;kgtE1|gbktx@WD=o zKPcA9>UpQ~Pr>H@oCxTlJ;b`KQ(M0PGx20VdQSuLbC8QlwAL}*J1VV@VP#3?f>7!? zd<$VA(@uYn_Sdn=P?-|rcNc0djT@|IzlYkjf;=o|%}=V#n@lyR2u!n8vGPhirH@I} zLEg&HH6H)n=KnpqET*6MqUP7Qb=owq7Q}qnZL`C}_%xGL28;_nibCqu#q}VGI3}7o z%m4ed5u&7+VJZt{N(5X&Eb{^4H8b2s-dodBu(7m62`*T-LC{jYhyP;&f4%LL1;!hR z(t;JoMS7!Vjwa7{vyAMe7oL`$CUQn*-+A~_k{U$iCfeunX-ocI!H{t`-ue3l3^ESI z2Nv#iN3+~IVfPTR?{9e}F;lN5Ehi&VuUgMkS;(w8CF+^{^ZnK^5$>5(Vzh($p57)z zj%R!EJCuKix9KmUT%FTjyR7Y@!^%kc+f@I0P7JB8bBEexdV$!Lv*Uw?5++S1c1j%! z3$_Hm^SYiXR-U=ABzfh(jq6_#jgDZy2F*G`a~l+6`f634)6QPa(gEQ9`oim8l(4ki36sTx00r{*n$Wq$|rff#0E;}oQ$FzF? zpWuiWrJ;xI)qHWz&=&!=QI6@!;PEHO%>xg1v|jUbXYn2dL8xv>?PH?AWr8)zKTM7s z6(-6>+O)?aX=dpFsysSKmTHkS(4A`UBtAS>-C%+;K4vg0{e}R4a?tBq;ZMBhzl>@~ zt$_eSk^VQBmANUMQAgy5-@tJ3XaEm>eWVavkRV@Q_pyg5W@?lEKi7vahRipGkdhTG zzckeZ1K)|v0o^?I(yZZ@7VK0)XCEXPAtXnN>;GQ%{{8b~F&N4b)8_Y)+dCiL$LR~) z&*VnEgaLl-2Fp}6B+{NkNyW-3Z&J98!L1rlaMAqZJ-hIrKS?<~f!cv`=3wnyey7+z zVp*}d7_=~J1b|P}@${Bv3F-9(Y+lK~ggpPYqq`4X^%{>hvtwi3^^4H^Ck(2PpJo8j z2)afXzNOzG8m)%)l#K?8yR!t+(Q^^}V_fnNo#9fV-ETFGHRdFjnQ>3yxgkFf(9xnG zD06c^9f3lBY91Y@=e||-&WQMX&_D0&03&09U0yj(HQ zr2Iq?yX?PISpS|LQL+zRnBcfD_V zT2bmBmT(>|Dmt|70T`|vMW7o<>et)%xsn-UMvPsIQ(Z{&`<0Yfi3bd&Hrr!2y!yXZ zl&4H#SoYrcd_QR{-|rKt&Vog%d{J<8A{XBl2GFTce4mgxUs+RF=%4TSfCNpUgQ}cm zdLDKM*gHDi`+8Nz&dOzzh6hZ|ADW`X!S~sm$c=~I)qWHGKRD@stx?IYv2c{T_b#Jz zltngZ9q%UA!aOid``5%5o;mZjm=r$d)#5_|&}*(G|M8v&HV7~&q&TC8nalw6&lBp# z2?l@o*!$q$QZiM&EEC36+6DE?n;{Ikzm`q3xETIv4Hhs=t1L9!8?Rw5 zZSaY0q3ohk3?_u1mi&*W{nutF94^|W)on1Ww+*N&JQ4g_^i!s40S|YeCj^`x|2Cpa z<(&V^)s2wTY;o1#6{cZt1P{xlS?{>Dj7-R;ajrd<{Y{;`Ms?1JMBNOtDmYUi;mlU(#n)3cu^8wMBZ`Ob{iya zJf%?6j_D+Dk6j2S0VZdm;h|o|qL8qr>>pD5h?+|TUHw++1Iuc=B^q_H`Qd1p1#4F8 z1p7=|;jWxIX+#bSf%8S;@W!cBzNLc$uV!(k510Knr!6vO)o)+gPjX{)SBJC1J_*+z z*(q?0vo7=GSP2t$oKeM1T<*xl$GZ0J*YQ3)cnR&NZ|r|jaQWrK_>LoRaosA-C{58R zr3{Pnbfsos#l|}Ag#clqrv*AbciHAB@ncK3?tZ)44Sl-T<4N;wd1+1pU5>kWj`xjL zd3iaz%O1aX#MOB(`e-noKK*jD125Ln!)Q(IvEd;uvo>|A{?~FYF0Qnr9I)%{e2oDs zYv)t_ulf1VU3Nqm5tPAtR{b`<(?~Hclu3dZbh227_Un}H-@|mdN&7jTkUZ}SL8HTb ziPX~4(*NV?E92UHmbMFpLQ9KOkm3$Sg0!V*iffTlthfY9ao0eBBE^DBv0}x&#jRM- z;O>^-7D6D{o1XKW_xYbEAMzo;?7ef(&hF06b?wYXI9GX0+YPFMjguYH(7PHM6j-my z5c)Zs6hn-=ErIZ@ouOyZB)8CfjxM!dYSzOR2Qs+0KJ<5pbGn($OMRi*n(2~k zUwp%nDE959d~nWzl__^x=EvRP%+(rruLlr(7nu1xSnz3Tz}l?m=J|SJN=*Pkwa2r8 zry6b{zAyYK0NyG>w#AM`S2cY>PL0ZdMMS!F zWt(1&Wt7;>`MPPF%B>9IMA;p{&t2iMKgS9YI|(cO_`82WbxNr5<-0HTevL#FU|5r4 z4pe_s%yUW8zl%>?nBr#}N?$xt`S4Qm4kXjbR?(L0Ktv9-O5p$ECpH@Kle_19u@@99 zI4rHsAZ_+((QHs(SHyVDMns%(g>ZwXZh^`8a&CGfJCsh~YcsaYk#>a<&na*tMtbA- zw9%6|w#H$h3$~X$8afxK~!v+-Pkp2@I@_kx5hOAauV0ZQK^C0lbgNN24`@hBqO*X5bbny)? z2qi_uZ;qAD=tU3gp!HpRW6z{a{t_=*JnC#McUPqC(?xogsDRk`?SV&|P5-k;)b9o| znmoU)rneCxqQBjIn|_G6dOW@%zBZb{40pA)sj(VP!hS04Vj>6Ag~t~>bcOfgHbJ=X zZT70jW_R8f5k!cChTTt;OD;E~j$$6GG`t5}9oUo=haKa~7Y%$x(YyvCPJ;KThX8tF zOCC48+!fnnvEAK@4~K1%?M1dN9>3wK+UylH1hgTCqc2kXZA$aX&0Q{_{Lkzk|`%&_rP zdAi}vH){buGyeqcQsgc#5N&XV@+)Z)*lNLMVYgea5(*xIKmycw>g3Jr@wqvEc={>~ zInXE_v=n?qzR^8#UlqxHm-f2vk3~g_XxEzdMvsn^LIOTrPFSO zkuk3sv9Rdu-BJAcX-z8dmHXaIrJ$veew}^d$%!+Sh-=}o#|1K;xidL2F}0a0%g?+g zl7E{&-DT7T0cEBShr=zAk5_*L&e>pOJG0!T^m_0@jd3{!wnlT`J@!~|3KceT8F~d* z;AvzI?qNPbt^|=he}A(U0q8dkr6pflUD^DZX8+i%4?xTdPHyutotT_Vt$1?vIQIO+ z!t5dRs_4~b#1CxU*7F7*6^L&9NOgKMoBKgN`GcqFN38=*RaF`(z}<3e0qZX4>eDi5 zztkTMr2J+xg)Y|bVYgO?DtCB{v0@2}r@%oqt!dB-Q}Zi{Ofe^;PrCxi z4Jsb3jj%bqlNxya+|KkU|7Hz5(W2ieh~1^ZpJ!;tN^pIOMjCj;i(qqk4<(g}uJ9VN zaYxg+B0l2S{LVclRj!c5vJ_#rsHM&c5-{<0?uj2zdYY-Or6=MeK2Vd_z%d zUoH?%E%)r8`)jqBU^q#C$|XqGJu_iHkO%z$#)?oh=SDm^!8>_LL8<8l)=<@7$a-aj zlFpDtGxv?bM!cB(HFPI0rZPtz*E%F=R(!_4K#HYNRHV7IDF8pK5 zV`~p4$88jr!X0RTl#<5bH%sDw-ifqSyqI6UIs5f=m*@+CpAcVS+rf98x&}5`?*4nH z3Ieu8l{tV*FgrSJ9}N$^r%QS}PXeFO1!ns#U%`cRNW4daid}}q>qOSVn5%ClEBsJ9 zH|REhj4!j0O?2Tyu6rT|yC0|f0gn}4cS(UA$KlGW_yA8y;^W^HV7s2JNTOka4uW)v z*xh(%%um|Z5xVOwc^x%>%y@i4d<8J<;q~p!;jJGkp+Jna?auaFZ{+3)%%fHC+Zhf4 zO-w{QW81sa_6)*>3n?+|LRk#(T_q~wqOZq-)Yi$}Pa9j$y8#FN19$>kkFte#3UjOJ z%q?)bgwIDIeiN&mUQ)LZ-wkH#zMqHK9YcuckO)Lk9O;0?i0Ta5}#hcqWycqWPu^eztq!_)4FB%p{TNE0Wf$;DW*eIh z%h5x%di4Blk_ZxB@2ZpGC+1*uU%KadBvj&M*3HUUzRkA_*qV&@>HL%@`H)~=fOv!{ zpT)wUe9w^Z23^1Mb|5rtjmQ0k?Yg(8sp!3JzFl8 z^)N?qq$-)DOG4Gs^?^sl%^Kh)XF%RB^)l9VC#%55VEwr!l8rhOne78DUZ{f??&R>u zF;q;!8%~a;vnrQcyD$*>UnveY0A8>)5+tZmsG{baF{gxApG{V;x3+ft zg+Po!k=&k^luVDsf^%Z%xzus$uFH_8; zi{23Yf&|}K3N`7)$&^~H9A~1N3%q-!>EiI>0m5NUKn1H_AvHR}`k5n^0DMwZWf*W* zZ@6Ee^@uQCT45V1aaKix#JaQw*+Msa+?1#JedF=^%b2OjP>IuQ7Tqo;H!zXTYq6t0 zG|Z=Eb9ZwL$;p8W#ZM{Oak!pK8n2-1q2kWOVh2u;0g7pcmO*@S%Vxnp;}0y{ZmOLx z|4{HAs`F{{yc7;0J$lbZq-!*S*TxRK=zC_bG3XJ-W;_>hH04IX?|rpSZ%m8biyB5i zwqkCk-04)p-he&KTe zB#euI$OM+?nifjyCY%+SQ5ow)Ib(vSn@6&5qf%2YNXUjJE&mNFcbUt(O>Vo|^lCGg zXvMB_Lp~xAG`KELs$pQf$drO~kAn+DhKCWV86oa)B=Uoo(plmwiPzvT2yq`x5-D;X zQ&VV`>A2KzD-CO)Pe!Jn-S1GGc8j0i>J;`_xl5m-7fJhg_?ob;!wb!PpgfSyK17Y0 zh3Krd2CHuA-75zenuA)$^&{H5I?c`Arf+n6h5I{M$}RCZAB*y*%Uy$uZ*r4w zR`C&sSBl`N*@Ec`;OZo*$l*<^#6_O@E>IlRh1pgEn}K_?Lnn`e+apWN<3K9jx&BUf zdmBwNBE1Zzo#Oju=pW%aX%5PqD@;3??>hR;jc00yPF4|@C=(=B5}BnrJB<%kpT`ZZ z&+z1H`#i;)gtU5G%vCr`Qv<~zOz5kZB2HRTg#8JQD10J>ZrF?RqVKn7huF&yslgz%0{M*N${DUUwWNLa&Oh_WugwWh%fn)r4R zO{UVzv=HyQS=y-fjQ8qK6bvZ*>pr|JFz0Vcka#{` zy34&3#2FfI2Wx_nB@us*Xv`|g=6h-(YN8QR>1*XjNg$xV@A$)|^zwrjiSbJ{*PD=o z$?p@1#H-b>e7`g(UUF!&=`)*dws=PC@G|lfKXh>U>Q#T& z4%6doFS;a99xX2op0242aixig#^#Uo4+e87}Zj7w}41b%iQ_9u;ELZco+5y`%G0z+CWr zWn8;4Vu(@IsX0%_gJ?H=uIEY5a8aMADe!U0Md@xIzl;{6JXg=!jg=Znr?!cW?cPxQ=RSL1&DD96#)0RH~(Uz>Z_skXy{X?FQ>yeeglHg!F{ zm~zZBznlD-5^jGE0U1ELNADA{=)X17z<<*7Rl+;&?d1R-xApZGpND{L0lHPg{=zl^ zh^sMqhWl69MERwaLCxoI9A3=`TyvI%4K|^^lv-hrU(hM1LAxWpfAy?ffp1hd3+69lkGu z;6piHKb?@jLL7C)WSRKv=T^KozEL0)?LMmtvOC$3!neB1U#WR^JQed=Qt0h`a(_ z$|hYr1#QtX-B#}n$) z1YRZs==~^rSLRon=TdL)D0>d}?^%CtjWPVnu;tVm9{G+mXP*##CyN-^`J|JR`}XVBp4EnbD~0a|7d>~{6+au zHg^!q(aCnHFrz<^&TdU@Fj=trL^Jr#xCEXw9=Sl|z$5$n=T=Dk;HHp%&2IrD&uQ}pf)MQY@<01W{ejTn*0znNr zZA+IT+xD8J=)_Yz@G^8V)P@qdxg{os1=isPPh^e;oXlw$5_FwQlbtwY%07=eIRi0m{~waD$*JEvLI)TCy!{(7TS z^sr}-pom6>4tZjXI?omP?}~fmLr?nee>_ceNtgIwJe@qZs4#f$SQD>IXZOjsk)Bc7 zWT_M_2I@YOKHn?4BwxQHNRD!D;YM}xMB7MtCxKKQp5eyhr^Cg?UwzF;Q1GdDc6sxj zGBL%0h_^PWsXAfqoiHN}C2dVc!2e()XR`YWeVBBfX7E`GUZ8Xo+;r(yd~p&nF5b63O#HD6Y#N#NY(4oe14}$v_6th$Hep1cKx( zpe81#etq}oWK7J|8?Uyz%b090*0HiiniAv9B3jzyV{b&N!s{k0K6}h~!+Imr=_N(& z4ux2~(d<%!&wF9^Pefp^leeU>ngbH=k9iW$N-FfKHo`_j#k;JwgH*@l&G#XSlw8_v z*XEl5>XNn-Z&|Ck;6gJmLkmZ$9h+1yrae9GL>J#we$+A9(!?bAZc9iHJNOdK4103O zH6*ImOsSkwkc(-nJk)O_F;O3lXWxEcWT1~cg#}!gf53}#Cz(ywmnw&O-*W{yyrD}> z34H~$Z`|sTzv`AHzfEg?W80teX-{J$ztf)3k>AC99qM)-Ktm@^=eZJa(OpPH^FG3j zD6k-H-TslCK#&BXu6W1!S9TIAylUxINsjqPP=l!1wfUi5@@EV%1WU^MG%HL-80ZHq3VJ(`)T)>FK`5{unqOSm4~ zl{Rku9*VT!%q1Zto_Rph?YsK;YaNUkKoy9fzdfc$WR8ecLzUW8vZU)R+3(WF8In8b zsdzcPBa(2H1GxVgji+z?_!3Gmu?^=VdZKVq>XpA+Y%6WfiL}gTZQ84M@@J~-e56MD zQpdCEyN&Ynt-AjXQAa3^MU5*7=k6!_zR6Fc*V&~_nJ`a_fQ$oJ;DwGi3q z3w2Qt{mOuHrt~zI3#1(!UOD(GHgI2lRxP#S*MjxA(WsXWtsf^E3w{R)kHW-eitw5bm4S|P%J>iUxe580g!jDg;);onCb3NJ z4nCHs&j1Mic8WCQF#Wva6L%=7b8s^uQ|_Q<>Ux>5T2+ z1yPe+HjJxLrQk>d~QJYWtfQy)1lVXB3U2ND(P)JqhwZ#z~BTLWD@iT$FIqH zmb~+o^ok2WMa!o0nZnjP{nO?be%knq(pXUtcZ_&W307D}>6>eo@+TZuufrSL%LpIC z99SyfF_~Kuslc_YXSFUnLd=IG%ROqY2#{hO^kBVZKZ}#Z;9je-oNH-^3uy6q2$%{ z`l{3Za1*_vhsCr{W_8V(^gajg0A{=DP=C+l=Owu(KP2P^<9+UFL)P9!!=AG%k};nfhn|OvDIdE;o5xUq2slw}=tb(7L0} z_D09&crVGXjkE+4Y<87Ik5EB-&NF=*l9!BL`>?<(X1e1rkp=P{&*l`=?Fz}wk7z(k z`Ezp_RZ-HeWKdFfvI^}~j!3UlT@9hA00VUw6F)=QB3;K=7V3ks=nsY`5#i-P);ul* zTWQ3sy=|3l&93aZ;j}!h+XuKcMf{Dh3{Xn!w?e~FpU$|~(k$pp>w0&&2%*-`AazR` z06f3cRZEt(rt_{_r;dxd>IrzS*9uDD{_BSX^0cy(xTKTG2pY(R47_83@@#km8CbMf zJ9dC~cNEQAOM=OWYR;(+6UMLdR2sW7(KPO;*<(DznP(wyZ;?mQ!4Cb$f6j{rj7dX8 z=+IWBE_6gOs_siP)4uUH#d+&9bG(-+LKTIb#Qa-1PBz3_zQF*8*Ja4w0F3U-;eIF0 zEZQ{Jp{})h%eo{!T#MViFVb7XiaSNXvE#2?n;v^Jv1ks{z5^D#ZDA78MR^SPevmAh zubc!|muHMXc;6)7C!de55f)?Yn!(JBt!?q>jUzA$pR2ow8clc-L^^G1(EAcPXR2gp z5qv(vf6h>_Xv1Obp2j5lcW>jjlFzE#%#fg={iH32U8=H>k3}VlR;jDPd_mWz5LTTL8V%7?U7SsywFN-Nf3e&C79P z;cPg2CkKcOkl$G;@?D5LKPD+XOeb*cmg><>y{jw7%Ej$A*t~-Oq9+ujauKJw09AGQ zf+h~^+Ki@N>9ChbIOfGed@111^TKpqB~}1+gLquRDAH5orNM%2l}7$_tlKI0ImAww?p=Du5h}8#jP77O?YaWd5?L$B@E((JS<6znA1b!rB zQKQ~Cz}Sm!M6DWGDP0b{eKieV?DS+0*;RG<+G@GYERMP6$hrwBuK|@KozoYR8$!*< ztb`Iyu4!D?#RBosj~5&k2ioXz_Agh^vPYQ~MeL zua7pR+tQQ6IWd9EPkWIb^lN3%D10y!>i*4%XYohl@uLcTTIbMtEs8ENkkKu}fnnX7 zt=E%74o>QBJDlwMzCH1fh1P3l9sgNjtBir+i1EV%{nJwEM2&A3=(cN=c@{blxNuR= zE&;j}f{E67A2uzecBOywFRI5@8;>w9mcV=)*0Rd-l5?~4_4<6YJK`av@dUZZKs7ir z6e?w(KqLMA*@tr7i)-D@@XO83Dhp4szJZj5b}yL)k~mGVlIE=8WAzL_qg&6y41tOS z_D4q2x`}QI$tVm+->q3C3!Hl6WLzh^**hG!jX*;#!P~md;fcVfXpn2EXCX}PBxH`d zKW*|%w|dvwDv7t|My2NTMDc&nw8Tl$O2Z_itm&Bo*$l9@YyXARI4@qY2FPsv|1HV^DB(f+dlG-i*`a_KH@FND$=ZW2|@xeyGJp z6XGZ%w{HBwKNB|gds%z7_0(_za>~8g_oOz@>UTCj_E@2<|KbpOeb$cw`ClI=h!l~S z%`t!C_8Ph5*iDkXZp`P+L+MN<-~5vHo2}Tk;%xhQK|Xt!D9H~VugJ~|;b zskHxDvU3fL$nMDX-g_8e`lOrypkclb5B`ih<*-xI)*21+&l@-q;1OPHwcmU*aD2L( zj8Z^@to0F^AP_a^xn8i>MQ`EI`%G73f<&&L2Ilb+;jx^TJmbYcu7iPM$opZx<(<00 zX|H?<>%LPrE|mK6Mp5R;2V?$=YYBMYdq`WW%52+Muk}K4L3MWhvZWq9t(K}`R0*Jy zKY-E;*Nv9<0by6ez&HhhXIlm8D6iq?Y6fFr`K6JEF@BMNMi#DCP7-d(shBg}+nF;HGfLvh4cVC8*!;5UD1ny3EWbt2hB8&Y)%LuDf0F&@g2>D2 z8jLQRf09ZY3Cb9*nXC?1EFT11>ayrham_zhO5P_t}d*?&kiSoV2bopd_xD;A=9Wt|z5BmtaY`&)_#Z+`_=6vh@smc7< zCCp>JX;49FGwqpNiA}V09(Wwngu`rthkcwbKc44H$fSCbbn9KhO>{?ik*yo{XFl8} z<`^{ZoBadX4HhqwgZVeij|NJGJh1Zl1s&5BCpa8-W=KSS6R;l?jw$cD|L=#@zhPvM zD(6p)A-WHhYbP}LMf#=1O!rGVlDFg6+4dA}18r<7JSzoKd5286f@73U%3V|3q~+v7BfGNsW~ah9&W44o2xpP6g&Thgg! zr2Mbqi|1Ii&da5KK zZSNIh{Ozq{DI_apA)}VWBhVU=vEKNeayVkce%(3bA42`r7e3xfDbxBRaR+}?lV-W; zZ`8-;a$zPx`cbo`EA@B8QV+%C-aDS!bHtrWop&eixZ)*7v~J znX9)o($;>%#`cVvN~-eig7sE@{hvmRcoybztohk%O3IVJT_2^I9le-S7o+1}HqsIW ztmrh&C5sVE1f)X>FmDN90{_xr0FUHbh-+P*lhEh?8RV16;iTZ?o=xBTzZhz+LWWaJ zUz*W1H&-1MoMrO|aV9;xIIyBqYuglon=B+@1)u=qF?_jtJEZ-!%+`nN)G+b2Fg?

    G*4jpZ_5##}*v9v64=4;uTWbL74JS9N<$Js9zr(-q@o8NPJDC8)3V{ zpuaX(P1*|{oM()fbJlg9VWVBvW0n&9sK1I?iaGvJ{-J49JH(aH=VzL&|8pGXN?F3U zBB(kQih+J!F8M6!YH6>y;Pwpg+3aM~5~_U1XZg~oILa3|_}M%ueeV3MuFbFo%V?`$ za2)uGI;*iw8|WR1)6-_(A?YLD9NLMv^1ns2x_cg_wnjMs4{p9 zd}?Zq(*c_%J~WpM`69(@D+%($qYYh~yA#%7`N7=~Ys_%Q_GDq4xRN$6<0J5eUNu-4 zqfWOiz+rr?`8q)dUUK!fqmriO>x0SL>*d?Tca|Ror1z6LdXL!~_UFAObh|5_2sv?z zCK#y!Cv<>$TFI_d36MQGDLrF5j*8{XlhD)W{kziuNY;55yY}Bl7}7en+CZxe+qg`=EuSFJ8RFu>Wk1s}>N!P$IIQ>{i1kkx*xQTP!d3 zp>7ZW8t_HQf`3<0&H5SAV_%t%4)?-t&Nm-s`|==AaNzZOzUTaiU3)8TDmJ0-??)-M zuBIPZGlw@uV~sO{Yz4C}!hCgjd*k4C(16eeUagWe*{DskA4B}F)(sSx|5{X@$YC?G z7)#W<466PWiVX;3ht6ugUo(xy{F|Jdo!NELroMOfu>Td*-y%!eF6={JF^^D!Gd7*p zC`HR8uN@hSyabpTolAesF@UK~oxM~CR~JFbw{jYvJ8%l+K{i78aIAWQjh-79-E(vE zOHjL|{*%>8IOhLJWyhQY?rX5Eq_gA5q^m{gLfB}Hr%@{mSmpcmchd%nir46 zu0k^p@Z7C7Qw3$6cD39K)gT1 z667J zCh+O)rtuAEs$uGPd7>Qv%uTnt*Ir`@FRylUp?X%>yu!IO z;J+LoZGW_ffAXd;;ax&v^VW`ML>jXa}JH!#|=f);I}XE`~w=8p9o2 zCL&TL!u=t8_nMs``YV&i16VzCO*&jpIpk|08-~I5apZcuS+`dR9a1Kvq@XU}J#+dJ zx|^d=ofwPWmJTdQ_@nq~Hc8pdhT2VDV>gezhN)@uuHoVLo6f)UrzGoUJ4;n#XB85u zbuA0<)_9EAHNM3t?qHctCHaHcWpZgJT&LOWBk~YSWMR4RovO)9^lrJ@1Ps5%hij$mWEY!2=o|VBpNF|4!MkQ{E`5?Ifhh3iyqXoB{|;?tfTaM zx*W?}x%t^R-EyD>KC8(p^E;;SciKkkoT*)k$W3GSp3u_IQ@%fK#iwVzEv)DNV7J#Y zxjXEMA&yX<@&*Fgz|*I(QR{FI-`bB^oAYYUZMQdeqd+1((U-x$jM%gbQ`6yfot7OfO=m>9iz*zN!-~r03o~HP^??>hpo$VM&!NDj z#Mrlh{-@;IPk5O*z2~e8Kt5mtbhlX5Tb7AESo@{`zn#sx)|;1ll6`rfJ)JMn1G8OFuvcPyy;F2m zDK#ybx2v0?l0ki6kv%$eE=pDg94fkyO?+ZxBf&t;%fsov_Kn>+#dP$7$Rj?1>+*y5 zsIRtRCrt&dg~XeAb|} zp!N?E048CYUMI`_?6sL4jmnZA!EBlAb!#4kqXp6-z zp1`AuTTBI&IryP-LOIKHeb1}02QMOM4_Ll3EKv<4;&F-XuhvCqQ4qbEQZyj{zS~+w zthqf^*XlMUD{)|C&DaJNKhRkH7=)jSITsmt5S~?zJEedt@E|g3@dG7w5_rdK_NUzf zH{l+g6k$&MF)bErkEEi&tOmNAj>_x&U4WYWG=E&j@NOxz$3>K$zBykBD$!p$Nz$v0 zl;|Ld5X|n9@#H+j8sGc=qkzO@zj7?W%hHGx87UOl2IE8q@Hwb(@*UyyfMT?+aY#Jh zHlB|V2^Tt6qGBzWlYR-NC7W9Q^{AFtQBtt*3`F zTI{6v0ni>-cRdjKmm>8xhy8or>$$&YJd%p`y zDwZD42Ra62J;Ur5^YTZqEr~NY4$@Puj0M`$xZI?OBH{5cV`G9jCT_v3?Fha@5ozNN zfoj%Ty*}(sFG5>RT(i=B5HPpG$@qq56e_0u#Pg>LwNAL?FXONLjj2q*zv8ifD<#2+ zeP*Pj`9&ry?CAaCUl@SAhdR(xlF*z6b%5%ODUE~Vt95k7E?N-cqKA8x7oUrPiJVK157KG>B2 zDD>|@fb|`gM+H$J=vTiut!3nzGr!i^xI?ZO`MvJ|X@mk2LZN~Pu3l_4FcwNIAx^2* zA1J-(N*}w>q@5cP)_0e%X-1aURD&@o?8?Fc?vw6qe$1%}F zuSxz`x7zC)*-4uMJu2IYLmMcP^o+iaYT+$*MZaX_lp#2s@_Zc#6d4(A<`-Y58ZJBl zANn9X^XF6e)kL1Yw9H%~)jdr>go(Kin@V;o_aQsfn~)n?_t*vt^cQ!uT@djb`GQa- zfN0h$0=S`-_emw49?w)>?!Ts_{GFcS-Rj@7ry|yG>=xd~>?PV=Ilj~(|1k49-;3|m z9_LST5HZTcWL=41jl zE4c`9^xWM{4=~LIT&(q|kgAev% zB#qDQ$A1RyRMrp$$l~pf-C`NU?^`x`9Qwpgs>%gkmu^PJe*t9zOSAXVJL(Djuc!Sp zk`Ct=;IF+FZexhCeCy^aIM)g~y8Fg^y2&Uy@fbEvK=DjZB9@0=BW*)aJ50ztjN-}o zBqA2#nbjNyxz4BgJkhmbf}W|IUga&~m2gQ~zrv_xM;YH3T&>9+p>apu0X=~A45rel7flemC>4Golp&^J2-Bec)geUbr> zYPepdCQYe+AjG|RzsjR~E-I>d|+^K7wQzYcHP0SL17 zDFL^!iGe40!raK6c&Q-n>!UQQS24G@H_Nx0PpF`471Y!&n%9m-Zkgu%@0XF8=)v^@ z;Pti}cYSraMZt4hl79DD)8O{z+;^^QpB`>e>dQ`55tim33Lq+$+oCCktlSG32}}Nx zh6aq{dwb%4(pb^k^wUJPvp&RG?2=!`VrhH**m5O0(Sa_v~{d@D};Y!i+ypoV%!N@0yk+H+&y={P4NXUEiFl4KqgBdjlOAX7Uu= zXVvXvB%u0$6*(!e5qbBkui&3mIXP+E)xB$T?W!!g8&!b=`vHDO8hUg1Gclm*UVlxhe12(-NU6-M~^trEwtnQgZVK>-PIVoBK(ct_AOU z0v-nNV`q!-9HL-dSp`e3&PINjeCk1fnaW%ki*kP*4z%MIWka!FyAD3Nw?s4|+#URt z{xCLQ$DF}Z&8H#d)0J5FpT~nw2w?H99cAGcRQkf&e{cLD#Uzla!-E%JO1|wV(|7kD z)+q~%(hUcF&iV!#A zuis8xZSf-8;ba_JHVZ^-`_KCFTHHKSAlrTdlQ((uZutyoimpfKFnV9aE>r?hnCl^N zqo}rc6NQA8EnSv-gaDS8%G%)h#el2v`{jsk(46SRiJ3Svz2K4ML>5!8D~w zFm^_3;azeaDJ;Ny0ve2UD%^q}5(c9Ar|y0p`D?LKU#K78aM1`Q36u$Jl0kF*a_l2S zcOJ$PFBX1E{t`I`Y6AH^sHMNgj5#Q@Cgi6B12GzgCkfhAUUVDvJ0oKImVI zOlYNI7?KQ9c{h``HQlUjI6jHJ0L&Zw1oSEBOddjtmOa{x6H91+>%*1hH_74)M6iay zlfiLVS0ZxlS!jqFcaX0BjKzi6jjaPMw5iq_J zRkk&gmL`9aKPV<8mD)e>YmOx|aT&nn_C9T(R&$DH90W!cG{3hm1okB!SGy@S2$a>r zO_`|HvKv8OSUzDUt0tVw@T`iPSQ?zTUud_6f+yQ0Z&wd_ZD-$voFq-!X)iL;DuD;Q zHzHCh5Pv0}MaS5~1aaj|SnwXu4Y(2DWc^%;t*bR%zxol!{1~ZTXTFf_UVWsP#Vzt- zfqqJ+pA+k_)IJV?_q^Xs3b=akQ%aFL0WpK!Eut7JGk0yptEtC(e*cY7aNw?$O<4UY zll74QRI|FuoPbN)tEy|)bfk@Xj!gEv2@?a?6&#;g!TW0ZSdjhe`){fSrF@0 z9Ln%Zqq)}3KnI&D`{HbdoTv0canN+@u4F{`>R7;o{|TG_4<>?*ehc8v^iZ3-j8_@x z@rl<7*9WL9=qI%FdA>A*d9MZ{(~|u~JQ7c#0j0V5+vjY0h$j;s)fE zlFlf+G^njgL?F6sm(D8ndqRBZqEZm{z1hhs*F_p?!(uX)ojZ*=|8DXh1Z2hxZ>&Dj zP46E3XB&(wZ@-mpc1P?vKZ(AVIhn((hc!(yB)axBV4#*#Tgc(RbvzY%=U}Gg`OvYe zeO4j5xb)Id#*7!>{4|8PTmRFtjSE5fLl6y(wb?+#!asK}u8Oi`6k^qa6s8_-5 zH;n76p-WWx1JzLlSbN&+$e~m=zX4D@v4m3t&C9L-sin$pLcDF>Z~Ku;KYp3VZS&77 zDf@M{b$3wTTRKk^%t?wZ@$6y9u={5d6TGvhxawLLiOC|T4VVWG$@OJ(-5l@NkN6Sq zMm#mOvhz2*J{j?Tp!qNBDSeLHr%yn*+r^y66ZxsbT?iOX#shCE-scbR+rbSc5|Kz> zxFTE#{O{4Hqz*`GB7a)DmED(dw3PGdNgvs_i`th-4)f$q!k_WNrT_Gt0xM5vzMho- z3lsX^nbITo!=hA4&7g+8WpbNWKzPaaGZyA+yP_)FpZm?F;JIgR6yuyQu__RKnns(j z`(LtB1`IKbAWC(i)DlEm6oyt!UB?|^U1Hqu?Bt0(QE2Cc%Ud#K%VyJh)_)EHb^wWS zNgYRzs=GrvU*~Til{UjHyeDdlMaUmLnOiL7?Ek|d-X!xl8VeQuubh$XnNn|UwVZP0 z>D=^eB=gLX@np+4H1QjC2V9@#tqp$k`Na%n^T6@BRsU>7Ra!&(jpZlA&UjM!==2TN{E!u+e9 zVWOzdz7Uku6+p2*0aj0|L;T4*QqxT09~%0rE+3iz*Wr-)+K^54LWeK#&0+pd&oVm* zJ-N~l=7rEsM|U^$8AO=0?*A;aJyIt9P@3I~I|2LpBSQ0N&n{o9t4j4>Nv#`W#guOq zrw4w?{0(gUb1o%H{#dCh(tJE)O+KLfV?B0T!%*)d(vO#YE;gYto)@BX&yC9r>jZUE z;VkT)%bMLhJtgWEarZrzUs-d9y{Z7*SizWI;`%%e2pKkWg+-}gie-*x$dN4=ur2(} zvt@E#9-CvXAJ!*_Jg+G4T%>Jxxka^yGq$Jm(tDJpcqe~ybG64Htn?QO2w$|x1i9X) zy6WCew^=$vpjbCPc&Es%NJ-w&KOr^FFsmZme}F9=|2*mwm_&;1d!kUdou(g8HmdX) zH0-Xkrrb)0)E9s}SQ56Y>5Q~6sC`A~2*h9`Us~Y5p>iy+FbrbXRmI-ZQ`5#Wk8fkq zTrfyyH#H9I;%|5|qFZ=c9Pt-=Ky9n}iyTT)9M&L4sRG2qzO>RYO?h-%+@8o){GJ*S zOiIG;q)YvQk}B51RQ;|u2{9y*?f;LguYjtuYuXk>MNvW|r9rw=8l*UII z9&nkDij$0#()fn zbZ%AWfGHQDXFsZp(|LaBi#N~7yA)!0@Y1FC$-wu&5oncDZWuiK3)g~Jq-g*vqn z=e?gl@EMc79+h6hpwddYgcXw7eEa-pOgKJtN5-8ZvqIlLT2TcnzaXgBW_VcMyf1Qh zVytwUd`4aci;kEqUg(m~CWIXtXLi$Y+@CMSeL5wjhxk{PwYZJ%S; zu3389AWZ{_T0^7=R1y zZv|Vr>Y=}Ufye{Ek4mwPCt3NH?#GE5SpERez=d%G^k30}9uq=FV=syEUS&0$C}~n? zRSDGUi(};kH@72s)L2Z5i^L+oW9x$?i-@XEC_{YdZr|`TrnGa#fhjdLZ?VPlRngmj z)*YIOf|M9xUGQMshPVNwL>i2&^2WA$Yu??vPm_C$?vrQ6jRb^YubZ7f3e9cHqjKPEE8x>?pFCk&!M z#@~{^$TG_{1XeeP@40HwULmdZw=a+Lhv%w-@D<1{`p>aAcsP&vx`(Uhi{|)twaVEC z_M@m`Wzd$mb<=>H6e!8IrNEY!TRb_OZf3a#f3(pqYCdpvG<90(^`SdZGOWNq__87= zje$TQEy%gLrky75u0VV)L(ANhIv^|aGUH8_LUx@=s$Ms8zPfcTzC92Xx1+#VkN+-3 zQ?CwbH?i(U3@9eEt%&7y>~QdRF;8`6$X?^3-4k0hn8QPRnh*uJowOYA2hX0Bwn@S& z#c18EX%+3+7rQ7_|JpUTMZN<|^Xa}`^p4%7t4X}{<<^f=Z(X4P;D76Azv%Hok*(>X zFaNF@z6p+kY!Tra0IhO3fDpHiPN%1z=W1u`9~>Tlf-3G%*u=7HBsc$?f!n{*r+4+% zmpw|25n8iswcV0OvOK$@KFn)e&|Bco16v6b4F;-zhQt|p0U|`+pQhXEi>@TPmFlMJ z-Lbny1Y1Cs+X8wG8Tczf#wm_*)C3d&|JXYz*Xs;v+cZ(CR~^q%*VFPEV7wfhzpF?8 znDX`JVyx-*s)=l~7C>2L=5EfarK`W)VVz@dEUY|7y^p&l!`co1+~wgToM$zhrag9U zuJ%*)vUfoPp>}uat$5otb4wRB`&@C2yh{Mz+%-$BYJTRJ2z={pzB|Cya3{!ZoThnw_S*)Pccoa>A&a)^o=(_di6lg`jj8PPk$VS>1UdBD! z{n4xFKes}JM|Rv} zOEOFuWvF5r;9}~00iJMe{RNt@{H%6T4{^ni3helth$y_M>(A%H3o@&F;eC;Vo3~e) zi476)VCcRENcby$R9fkIS~q394U2!1d23dslgLyb|t!C(eVkEYQiHP*>%t zJ;tAM^wBlXv{EejPPUV0pW+>Sqs3kQEIEGCUWs*6z{1Fnw|xiVb~h}X2QU2ee3xBt zxkp*`@@U2QQ_l-P4KD|M9j@VWciO_$ypUP~Py8{s%lm-j@Z6L%HG#>^cm44@Hh#>r zm;H=$OkF{aPqx;%_rdY_aFvK`u@X%{Uy`2l)8emg<3a;M>tvce;!{?s-E~cWT$+>? z4nrIvc7Ul^qDxxS=^kh(fjY)L|6PzP66Z|;0tzBk$A8Vt?J0J;AjU(vp2=p`@pAG^NmZa{l&2;jW$ z9PNECUoFQoo(Oym?C>fnf!PLgbbO}-0V~XAgkd9O!}Z&&i-KC)AaD zjze$adI1FJHpZ=C>5ZkR${stMPU(iKki*nP$WBHn(F))h62SIh7;2<~0oeVIQ_)f{ zkh5wi2%qTuKUmSt*AwYK#l^hbYxdosrHam9XK|NIsW(oq!AfpD-DK)keffJD`;N_( z39)f`d0+_)-2Lvsx5sXwHM%uJH?m?7X>$JyF);`d`$>s&XUNmH^lx~g7U;i`LR}7Q ztR`@m1~IJ8X(t9;p-i?szGL+Q z9{FkNu=m&LfE8>{uNH1^AbRR>o8&N?$4G$3Lh&N}3tpQt0_r7+7Yoyx!4h#yiMToA-M;XlEo+na%6Kere@Y`KnPC2k-7WqBSDD zuK)%gex6y3{zV$xBVXozD{#&aiDMd7kkP`Xy68PiZ|^KmIW0(F@BJjB{0r!U@p+iy zmoY|$@a^FbzgCjc!xW*u7AopVcBzty41$!SA2|5ejTJKL1`^m!*dvFG9A)1G4WaR{ zfzOYmc&|?^EXoK7k#^u)`0d~EuCot_)t2}eqSyVsGc^f;ZNPOksEIwabvdx0ZW*mu`59J^ z$4U!-8jYsvDoQ;p7_PmCPA~e9oD1HtF7?1B@5s9Jw@8N}Ev zVt9w1=l=cY)}b!y{VGf}E*V3UpF{@@P57CyX;AU#iW2r97W;PbSMja(8 z^~LTBb1xz8f!Grf+7yDdJ#nSl9tAlY72x=x6>3#L`Kqr3R-DM1+jj@3#`_u+l(?o& zpeu;naWW4y7%djN1zAdE5uh2A@~4p$fjFKk@8j4yaq&2M2G72~%Q5u6 z_b0Knp0G9&adXkDbK@0LL=@r!?{*9w8nEYkJt(e?~L=6ywl z=v=nKxa(h1zkBy(AJMT!z`!~P56+K;vxl$$Kmk$Q2^vT)mim5fLpUBO9mT(y&dIOY z$TcWYgLD$>*{3{LUB6Xspb3guWMivbBXX)0%Z49NA!mds=W%l)B6ABK#EbNy{zR>U z!}t9&yX)r5AdsvKZv?8tc?hZ(?lu23!E%EqANLKwPwTb&@h6C>e{y&3DGIY|d}~1b zF}3Lp--mqmDD zFMtyi;M4lt5Y{~oLdFRve~!RU=tmN!h)W&VKu>BTFjYVY+{Xrr$m!%-h+`~29oT)m z8UTJ(aZ7RCa=VNl?&>F<_(r2*oSZGn@fxA?C_1-~q%^glgZP{YBDc}6yo!m)qd+47 zP@~nC0>;Wrz34wPi#YtR2t67k#M$i$<+vmlDV|Hq`@ou){dB$@N!9dTaBdgx_ye?X zkw2++->yCqgR>1QrLTsb{zeFdyA;;EG{M0s?<0c?0e@$~v+2~OQs*h7#ZRy`L;9S? zWA8t;j{m8c)&I(khLf~1KKb1>b!O%kxxxI=Fge}lwgI>wD+H97VN-?hzdkUK(_sh&T zmjprSmaMBt#3jq{Z5p(suH?1o(uQO_go)$Mg1YlRKUfq6+GP;3ZFh$QG5)g86D?iW zDgG~?-@=5q=(g`6-F4xznJhEVDP0lA&B!9yX4IOl$TT(H2>fFIE-=hj+JP2%!`I=Q zm`2X89zj14SNMGSL#q1}%6ELBX-I7T7Nf;9^w4=z*Mqi8J6gnt7ya4s0T^f#6C<#L z`Xbncxb#GSwr0?4z`-Ju{I79jvOWBUXj(`Qn~l5C)0V$lx2IbEVhxzn$y$}t1$_7d zgsy@4oO7E}V?d)hMek61)#)h(@8i&!cjJE!IExW^{~nmg;=8qE-!S`5;lg9kq!g5_ zyz*#If^|blx!y5UI)v5hXG+%VHbUHXB2QrQ6+l6kLI>1FMF5ebgLw81vqFTJO5r+YF$;<9^+zt zFA7)DTXQWhe>aiUAKKr|Sf|_pU%&|a0YtOg^O?%vb*tl6aIgiWswMzI&pns1M~9z~ z(zTB*yWAAZBUgJW$@i9=N@|VtVPKW7y6!+`kAC&zC}Vu z=}p21Dw@aeo8S?^+3H^x+rS|jf^xfMS}1t>B^Bjaq)wkYEur)e|v`hDd71z z@KDirurfC8bEPyxoql02tiwq((S5l5WRTeE}wQKTHU&dROy=Ldpbo(tukmk{F?XDj}cLS*}S7TO~; zWo6VK`Sc34%jvEO*o2 zLQMZoAdXJ;&I5y_l<2rCId#DY8HFVF#jbDTYw$ZHc9&(;RApS3ol>y?g7?bLcn>^~ zRyCnt(s>&<2wRa~tD}~@`vf7R+!82l{MpMx$W{&?xwWLp-~1JCxufPT9|ceV!mMkH zyE1r?@TXBWfdlDU52H306VXMdX;=&ZVIt65;MHhz-Chg~O-bCkI zJmE>g=x5TuXnu+WX@}Xvf(wqV*eVO`Cdj-LKV!W7kZS{8l7L3#RF{TivYdc+uk^dw8liORf-Hn568BOP}B>sPZcQVxR* zS5FIsYD!`dLxP)y-gvC${Z>JPswp3(`}RL0eh;vR6z>hzPN9WptIfT%yFh4W1xP1d&b8*9h3^vXlHm z94eF@a2uc(Q;-a!CzzFMqlW^;^*UYbDqSm-T+R!*?Y?SbGJ2QMkEb?<+gQ0Lub+wwKtn zz#$Cldrxml=@}71`rvqA#Q+AK`5W&t+=8&3gqBYpms?E*fn!pP4NhIBo|Q6ggDL97 zf+1u31*LgOHKQ+@OfL0A(3dig7*l))oKN{h;N`?Be5SQA-kor6qx8W6O0SfeDxy%21c&*B`$d-y)Sc&&qq|;w09vcc9489zoLFJ zXLlZc$dEUNVEjalL6mLqk&qh=HmW%U;nYl&>AIzwrP1GAc8u$0H#hJ; z-|od%s8z0GuJ4GjHUhF}qJ4#nB6Y|Ds&peB)rrO)zFTd6(@sHoznh7j zKvQdIdCaH|S0LC9vz{2;;ix%p6)`;J_lKA-W4glHrzl~SdBwT3cDmH!HcNaK8{$D+ zjT={5XJzN7GZUqkOL25(HnINhyf`CxF$%Dqu1m3QgPP!^@4KA^r7?`dA}h4LpWQBP z$`4eEc?39O%>9hYU0%+JEjxcpgx}-Z`EUbd0T~ryTwv{AZZvNEjk?y)TJ~fNZsJvJ z8E95ul8$<73IYp?@b#IW4@VIHg2&MR*&EA)p#Q9r|Mk*XOYjj-Oc!KWV*=(F zw-&U{`R8e{@x1cTi@A$PF7TcB2l2I@&$zh1_A>PybTuer5Bb%y(vzTX#>hg%5Gpcn zt*3(UOl@wZX4HZ!t5-3Z6&3>x&QA8YJ&P~pC|X29Q>%zNHCEB(8c1_$;EGFiQsPr7 z)?d3v6}#I}vmbTKH?DRB5#qtAGXu>1>b3dBQ1_F}dRnX2W^=eBZ_Q!vnqosG<8H6% zX;IGWIvlFv9Zm52%}g^xS!YGw9<48_Hr=0`hfB(E~pmv%6a)$?mE(^Bv4>HBUpbqV~~GMobXaH%L-Ou5H~ zXG&AAnQ4IycAQ&!ZIm+)n+cND^t2wIFf=Uv|n1p%tDj1|Y-KPw$)Ut0F z8HY_%b{ZJRv`Dj)Px~FGHAZhUjzD(hK`6>-aJm;$xt{_qxCz(kRc;c5OpZ-IB%f!$ z1&~Vz5QRdZjzmY)kA{!4QNu+5*XSF-*oCpEIe%6D#~I5<6s)pWfl@hHn~FH8bWKf!La9u8Iz z)MFkXY!hMXk`V2>BkTxkgOI-!#Q7CNHcN=qg1)U^p+DV$(hb8$6;a!?TRbPpNZes9 zJNGBdV&h#!+1)SIgqMy?lKAKL;x!K{$39m1#6+V&Mr95 z+m(Ll7lA>oDl)sPoxM+#_<;N^4^gGP-IDV|EWv*A&RhQ>Kd{VEMVuqg$4wcYLjI$} zIbmj+72-#`M1~?-H*KRRJ3&~_*x(4+eJer&1^f#fcoo^UTXi=hFWWYaM`gEXaIj*= zYNj-Sg1U|Q(;=B)AH`j{E0UFlC)n|z#=MWLun*>$|DA99nqd)c z*a$ugH(YFF<7eMmZ>qdDD`)8jXrLk{F;<#z_38a7dW;G4sXJtV^gtbZ4H0x-EiBn@ zDi~a+4WA`6R+)E2yE97UJYt)q2oo$Mnq4c^Ig)c`Wu(z;mkEmo888?s#Y{4go-ry# z^0!QJFdQZR4ui(>=Hy<%>sg^bn5D2bGf==gbD*;cc5XIJ_V(#qUsq8(p#Q22|QwfTLS3FMWXD2{Uahm4LpfjvZ;87M;61U;vZDvXr#vna+_xi z10RI_WjSj)0_F?odvtRijp`v799AH@hzrb@b}toSUoqkafIjtwm1=KQrD*@Ulssu+MN;S|3z$nPPmO!`md z@qcxU%8Tz{y#Jz3dqwPbz24i?pS-V_^V(gn&SJj(u`*Z0>N9EKfpS~98r>(%l*qx8 zIPSh)LUm6}YUstF*^j%-?xY7wokO%P1m(xE`kp@7oFt!H4Q6{%xd#x<{`)lGjDx(J zy(Xm23)QPOpR%g$CqxAg?K`z`%__^OZ03kOv7W6dmGJ4jYyk?Y?hh-Vgq=h8|6opI z)~j!c)Or%f16@I}m3aete~FvdGQ7fDw1!30uiw#Q){BF6e;}p@n&6Z1yjb4%r)_#_ zjhq?%fDM{#V>o^N;UzAywEH|%vI`vf52^p&VV0=EFFMm)a%ZWC+eRzBSilXJ2%T1} ztO_LDD3;6suY_g(45YAmMCr))X)cOs3P^A5MyR(<-y`s*>xq>P5jz@z@TyfRgZ0yw z*P<(aJS43Eh)^?&3mg7%zp7LQ=SCJ=&QeVqm!D%UG|Xhvvr*LOXU1J6>Afd4dw**V zO9$)HSf1f|owS&?#=BI|1J}FONs@TYI(i5z0ox;M!RpDETG``V#40pP^~#b2fps+z&Z3`ikJeU>c!rF<n(Qqvb$@UmvNcQlcFkek{n8?vOoE=Ff$Mz4QfYjuHouZ8u?W8Hm+!R<+V0* z;6;BjKpJz=VWD#{G-h`e=$i3-X^C$LXVLyp1A_dXM5#rgZOvu9r?OM(Su)b+xWpt|^z%DuK982TEl zLGd;rmM`i)PpV)GhM{nFrLvMK;nS^yP&k#p$DxG$S@&S_ zBV&^9!?*p5QI_h$JYqf#s>6V9-P8u$ftZl|6J6u6JzJaS(LY?Pha@{T9|^(a2z;}O zVdLZ!@^pvwdreMkpov6oa;b(y#6HP*rypwtr%GibqgY$|#1FH;3^Frb6?{39n)%jN^K*2x zI_d3=isbis@a~DY+dppNzk+^74J$xPX{Cm8bPh!&BLPX5OAyy4cY833X&vIog(;rrgUM(P9}+EU!`I7@Lhbd@egdHH8yr!;T_l8;=qJ^~qwz>f@! ze`ZSw>p@Ef9A?`gqL4&%&g)A^14~>^eqj6sd``dQy~P0~YR_N==)MjQc(n*q-b&ka z4C-;|HvW=j^#^Be?_K&AP|a(!us_-n5OUbai&v)I({e4K7u(bG}C?T9cREF9OX1{>7$WJEgb)-eUo0eP12MZ ztg9XfzBxh(sM@JtTW1dw?NkiKdTw-O^wdV+(Sn%Znd!a;)UZOL2Uv5M_yZd(vy032 zfXh_#rma)`l_8mRGr{nQG=FDTygSz+)dXrVK1#DZbdIuOr0q%2P?7;LN zhG?OylL0IN`)ZwsqR}BjU<~f<%Mk@9bkE#GlyuT9gic<^nJdgZSB>14L^N^MCq`_Q zd|;(9J*<(9%)2%8Y)s>k&wMIZp1pnV<7ks8`C4W(n5}jj_VqHuJK@a15W%NJ_0%zO zcSUFS%wGv5e3YG<4|KIQSG(0OQvG?oRF0Nq`Us`)dK)ua=+P}`1J9>({Hxm-!4?|h z1Ffta`e{eWs%l#Miz8F}3AWerP98UTFVn(ZvB5n#)3!(6C|LV#p0i;lEp&#B!xSV8 zk|K{K+i$2hM)%+p4hpD+TEO1h8T)>BTf(`^b4oB^*92uuX~; zPdlL>TQN$v*JlZG25giNjxzY24`h0XiAlf5e?Zs8^xzKSD^q~q_VP3=TPiV!0iEXU z>4ikHkGs-P$uwQy$D8|E^9`@xz=!KUCZuwI`=+iE;c{H61}}%ei!{%f&D86JZu`!{ zen(=NqyKjs|LfPW=ra&hXsWs>`VVvL|35`cTJ;MC!5?WBsM?`o< zI1e7&*iw}(7WLRQN1s6a+NLPP@KsmP(eRx)kr3HVs+tRh(yx=Yu0M zAsURBT&xPStT7X4N*t(Q^<5S%{1FQNjy=uN<^o$<6)P)+n1h>t^wh00P-S^gE6>us;kv~H>dKs}90O~$VS1t3KES9vFDwcA%Is@q!K;b9MJ+oJU z4*MZzml>5QRlm)BY3_G6DlzNW7NgEmxNOM@ef+v?e5LW}+&>5QuQ8eAfYUe1et{L6 zv$5x=5h@N5s#7z!?;=R>5R{5~bCDAIk(BKb6`j@h+xbq~W`jtt1##i_CgNcwz65)E^FA<>itjxL=cjE>Wg5f^$0Cy$gqg4zVj0DnM{a$-bPmej zaikb;1!Xe1lrm?jg5R=pZB4H(pN-Bv!${y)<>@7yF$M1tCMoX6$CLBt8)#@y zGwlXlAlJ@G>gr)-szCJQV@wLGHG&k;f=%AwrXv3drr1$^D8c~2{K=k1;}g|0wPL_L zbh8-oARdhbH4J@KbeG@yC++s{&Xw4}lX#nJl)h(TqO8()R2rcCCAKS49;bA8W|)&$ zVPQW``4j_VSfKwT5y$so?GdKO5K5foyJC@31pMH*aw>6EhzIdYc8A;NiLMyH z_v`ZC?WqrDm+#AGsucIP9R2XuxHbAmGSEhTpCJo9NQx9@G z?e}DP;GF}7fP-L1I~?^rf+zAn>W{3Sya#HPc%b#0%(6JQI4xEE1@kypkCI_`qO_87 zFy$Yz+P+YfoWI!<@!WQEt|P1WbH4lQ3`p|pfxF_edv>n`8{Tj9x$Ro}z<7)Ls!xLC z&F8wZC%Rke)bw?oPu_KirmYGw)c$W+6j!64V_kdNO$#=QIC&|LFLXPkuJR)|X~n)k zxriF4cR?NW&yHY4vJBuj~ z^SFe9HJC4KET%Z7W6izX*bHwd7R067Ka)C#>0mft8w)qF*2=9W{(3+n zR)L}^=b5v~&IU9b3y?^J^#f6-SDbr-bNk zoW65K)%n_hV*6R20$w6#3cBaX@Q{(}{~NQuxX#U;Ivy}obR8 zk#T?HCV$O+Qwi8jdP-QD)Z{6HqbUUk(91q7&yL?FdT$e=U+BAWd?i}?-#}I3-0TO? zaOan`nA+!eUmQPZs7xon9oeB9`TEiXYM)86^gthg7KunO61~CTTCwhE(DIw%D2E$b z;x{_XN--z;FB|7%EGRhBK=0yXkQSGz@2ipraI4v|E*l53Q&yR=7HrN$;qf02`LUxP z|6Lnk5hesBjtL(#Wrr)h3r?#~e02A#dGYl^0>^V^OEaC!XR53~QyN7-%ZgTw+PG+- zL#jKqj9E`a`Y4}fskP?_Wv{wDn(`SJENV+YIJ=5j_wUS_d6sP73%~j@ZH9%hn@h6# zVtw*x-x7KH=!qWo2PTFdmg+m-WkleBj}&G+`_9LrlUW*pXYT_$tK(iA?rGjoKZhe8 zXw7X1rr3RCb+z-6<7`wm9T|l>IzLna8F%Ak*)mzXK|Y>1_POtO-Lvj3u(^kDP$jM1 zp>xCvNY-$>swBkn$|6GNO7<;qQGA^<#~NH4P&Z;84C)Xf`0AMRVX+&eMg>l7Jqu;a zZwEF5E!+Y3ed^DNcP!83rJs-z{f~#h_XJ}nW>A>KB;P4RyFr_M`6?BdT{7ninV50# zm^VC!FKQoC^wTZFGyR7t(tm0S#S|P3m(r#59WKmunOV7)i>R3^17*|n@(sg-s+INT zP_Ve?8*>gEnjTnrVQp4eZ@LRX@-7QQZBhmzD*SSmDse9-M}ymsdnX~xA{-WA7wF~V zTb~iOd_K}^Bt)!F#552ev**c`1(uW8aCa~Mvp!wflk9+p7x5i>6ehg7oF&S|3om&i z*gJc!Z&R~53hWB^g1}6phB{Tycv1>+0?|$)-uK7O9dY%huN(7DQ!dC`8a)y2#fpmO|*995q$UGh)Tn{sQFn;;g$2k-wkRaKUB-E zY-KjoLkw-V>Z|UH8W1FLzzG^I6}utUvmODt8kA!Tr5M62TfvW2%;}38<{^*4oKNr- zZ7dqfsso|^4w8g71oa#~oD_3LCd|4aurm&Nvl+`#tj7CYu;>r3K&s#;4Xii%4EV$L z=8`QCMsUXg-JVEF1>w$+31%Q%De?^bL+bVLTt4#kJuFw_^M<0u9 zs*Gl;N5_C9nAC(B49(tiOZk&jomXcmT{E|CvUTPbGOD4FlPC3SziDfnYkjv`s-3UA zFVTOrn&KSt(gtE1uxVz_5H`T(l7L)1sm9TQb38hbCQV610Q>JpMgUf>OAhF^sS}=l1iat3e?Jl<`QGm4s5MhRDo_R{ zuzf&S-#_LInV+GPp?}Ars_nnRX{YSFKfsSq5?=6^Dnqu++Rt*ZQvDcO^sY-%9~nf4 zuiS4L1X(ncPGP?u%gbqwyA;fMa%%JfJR}pf(bvIAm6{A zErB#9znxFq-LppI=nXuvYe2W@UT;T~1!)d$mEPAF7IG@dy-1bB_x79sRt27Zw~n%| zUQ`{m0)m1?c_etrYp1D`;#K`#YuDUq8l3bw;m_Bt(eM9mw_mdva#~C!Ga4k$p%7Xg z7ZjXPJ!G^zmwI7m&Z^Uo@w#-dA`eX4wHbP$pEuWN=Hljm@38#%lHp}~Rli{|DM)Je zO#TF^IqkZ!G6d@xm>( zT!xgrqO=%X(e2YrFEU<*N=4(fGeE?1Y;d)9@1LL_A0q78f#Hx$u3jKz;%~+FUY41O zle#Dx-F`xduexQ9ap{IXbT)g#{lui~?4hvuhBluZ_N3crY#1;TC(`Fg#`MVjDEaXN z%!b}k-ozhl(S4#>oO^F;zrl|N^AR=#VkvJ79W%E-jj6<;9gQt5fSkmHiys&e-HIy0AC zgXnjY9dUPZeTZm{-4H#ks3TBf6ps#o3fv!ZXY9P zbTu(mF!va_R5~@E8JfFw)>ZhEKF~a?x|cNvRUgZPHRZ;NXonfvm6zDt?W(E(IT5%< ztk}^Z0U@pr+om=8XFnJx^By*8K1(y}i6es)s(3h_uQ!ol0!-aDg_+em(Zi;^wU+3C{b-zeXJ6WhDWj4%5eTLEnAO_Ks**! ze(?P@3N9{vXf_M|sWFohDTwxT_@d3Oqsj+`!7|0_oBh7tm#G!zTmTNbtS?_IVP-%ael{CA&?mOKwD|x2G)Q<|e(?+i+m~{JPRT2j0J8geB#ht?a3LW{1t?%(b z()orI1q*rUraGP_#Q0NR3`;XY`>BpVqgIJX zg*{kF=H5|O*V?^q8{o>RN>R^%jbJj_y6Ah_e$>4?jQ()ps_@Eb@hxcWr}+bFpv$s! zNyfa*Aj#iWKlgi#){s}$4j6~rYkY3S4>j#2j~@(`!GINuSAuMgmK5gZI%k&=v^MBE zBo$^?D!&mi+dMIv*35gP7$O8cV{#SFl!4->pH@E2NGUb{nn|Y9<>7kA;&>QkxcRz| zNn|#0es?V8!Gq}Y;UvmPDSB&c)>~6=98!ddQ=_s4E>w?mco8IIz3b6)EWN+b83Y8g zJhb7JM0~hvZAlCZ6go6EDGd54(s3hJm>n`rzE@&ZR^OQZ#`&se5O~ww^QksVVV2}l3e1r4$$au7i;V8g8Qf)6+kErFM<;*?Cy`HD+v6|NCoXC6 z9mDb${_p9sDy^CFSGK|sW3?PG!*{%2<$jkHG?(@QR}X}#c?Ve(#+Df|IuXt#DQoLA z;3hO#A+DYv^w%pbT{_}3N~5A+)A^>kM;DucH(#zU=hJ@LmKF3usQ&nhAu6=BHMeQ; zEaT7O1-k*m&U&874OmfnH12_b=@THYnO*E=meoK`jTM>=Kbpr_)-U=#*6J!5U+gf}?_u{yY~M}>Hg zz-ciLl$d}RMi>&Q+7L(VgT7gB%8gg>j^g0_XrxunY>sZ_RnDWk zE8rW|1SY;CH)o&VkHCZLgxb$le(d%aVU>y;Z=X3cv)sIHF71=up&H*-ySG5#W)RY@ zQ!XT*4al7{rFsuBVoju9msQ(f6x@_FJn$&=u5E_|?TmChSmZ&;TkQ~V^|EJzoz{$b zX#Ch38gz4#p&kce5IVm=|ANcKy{aKTet%l^!K1QKN@P_cG_zdSjw41Zg44+@G8@|! z<5>XLg=h9~TskzmGvyiT#?tGc0kd1?ut%zW1J73~d^$&~$*pe@EI)QBe2v@pryppP z7^ZIlV~wefw!k2&Ij)REQjD}0`0#=V^ZgcR^*lB+Ef{-3-+Z)cH?R7()c_UhO~E{C zmIYT#Y31yFh!^gsi8!pelZAE9=Y&D|`O-tKwT_4Gb-BX%_j&WSaOnVr>wbMSD1)w4 zq!GgvGKlxPZc@mbDdDX0YmT(*2iF7vna59U@n2i~AOwz1Z7>(kI#Wn&oI>14%ku95 zBMTI!(55GLb@8mjf)6%oOE7J8&07J~g{7cu$-f?C!l5TmAHwa!Xl5Z5wea49?!Zyz!=*Q)#?cmDLZPvPu>P+qavCCZ0Qw_>t?a zO~7W7dHbp(qN2s~6)G&e<6dIAn|Fges^ab;CbAD!WrIR4uR>rEDaRJCw&2I^!~esZ zdHl7MqU}(|>c~)(l$2Hu_Jgher~#Mfp+9i;ljkqinuVk-#Mf8vV2)^+LQeA;);5Cy zf{^o;NloO~E;HQ&OOg}G7sS*_P;Tfh(c2c%!<8RDC8x5}mr76bYJZP)*d1eI_7lqS zUeXt;d&8_Q;lJoXXuBu|3mfZ(6PhaP&v-CHGB_&nnzE4}5SY z0?qO&KU;gX%kqzY=%iZ%;Z~Bl3R^}w8A_!S^E?L{>9A?5y3a$W_B{;cgn@HOc^+s* zSPW>_>v*p3dT5AgT%IxDP2Mylnn2N^#UO;YO%t6puwvx+U{9Pr!q)@^cQVF@Op*8Z ziaO(omkC3z`%-i9Ww}AUYgz z*hW1jnt=NbOYPk-GZa$~16S)R`!oy(1^pxn0-?uaozqYrCeSmOXa~H{Cwj<)NeKfVxyuZ zKx(7J?lTytJ}v;FRnm?wrya>j*CjM1VX_Fiz4V7>K!yr7+d!kf%75Zh3~_W8@%QUmlp`G-0@aosduEm#=e*EEpEd#+af(arAvsYz`cA zE5-4Rg(eUFebEkC`?B&`B88?#ai}^&F^TMY#Eg0~@zm>4@U`$m+NatByrx+2AMC z#i7scYFn?6o{d?w%u%YO|Bt&Qsv0M3yzR_Nsb0jQl456>Ft8~6pTSnxXoU`ueG;~V z(BaRC6R{6=bTsr^ule&B?l+v{vb}Yag`0`IyGzn>Shl@$(Qj=~M60_lPEOZi5+25l zBjU~NP8F~NVh&6%>Z)Scp*IIPZ|FwGj#h(Vp>tCCTlbIKL@-TM%fq444t;W*R4>=j zyrvEHsKQ`wtc% zqcQRl;R;9_2vG)dMhv{DB)Mmx2w<^Zk{IuyqLxwUTG*d2R}|$^OD&%Z5RwiowjWj> zDl(%z2-!yj2zZ#VLX(-nN&-Gxmr!X{(;?S=p$fyLIHsWNky=`q)1v-7xd(@fX90^R z71G)6nJU!txjxLiD#7WTNvnHG5%aSqGj`Pc`8z4AaQjYh21_dMqfqLSNSj~(>Jt;X zatKj1wpeH_H4r|t?QDlt*#gNE=9!T|FHDBwwwp}q(C1{=^G&D!>uJ85{Yy&><&qKd zt0vDnrkE*c!5R8%lsKitkTCXcv%s=g*gN3i3HyE%oBI4MJ|_C@_%j;$3OAn6T_E$~ zSvaNa4-Em!aCivOS}CX|cRs6pC{!2laqfiy%FJ`LI+1aG1;&5_-OB%zyBgW6Ah-PO39t(_Td)z@0wlbx4Tw()%XlwqjKjGaNkAf06)Dj&O1T@6m z&)2VD-YZZmwMExfngLE$(9#%!qy^G9VN!4DI0m+oaCY(70NhJx&hrQJCP08eAaWtO z5Ol7b%G*cqp}y{DjKE?0pt-iv+EbG=W}krZK+>F14w`K$0>np4WqYUTp;&!==nQlz zVufdv+U1)k`X%01lI1f>!fc~a4^9t43!_w$a?RjX+H!%Ns{SF7p3QT1`W-S|dAB?% z;)Hxbj;1gz*=WdvL90ZI5mn{CNUQ_56q$0UkuHXH%}Ou>!(vHYEHg?P)fK)N3qKI) ztG9G4(_2*)wO^XUa-|cJT3aSVH<-7t7lr69~E6FT*wN`?tqId~XXN!?g0{ zgeH;`HX!wICIi5{sfE$~mE$Mo6 zf@tJ_^4}%1*bD>d3HC}C&j;aMTD;dWZeefg<&TJjF|nH%Hw17UXIb zi$k3BYM8g}c7BwBJZ4R^eELLhfNZvKz4I41S1+I(*&!Fvb*={pcH^}ZVo!qPPbzv< zE4unJ?j(gQ;mN*{iRH#wU&1OzBRidj{XcGj|0l2Q&IdjHFg$fI8}5y~ zS4}#(Je(n^YPz0x*Gl{2H(>0c;Ug=p2AA1E zTtfw%$!9%NVuzIDbF!4ZXDomv>EB^YeJms7Iaa4eum0HW6IMd(VdCPijQyl?2^Fp( zAhU>b-|GOt)I4RcS-W@yr)!+*%4zYdqp z=5-2WV|(ToY(?J0vP2ww?26`6)jyT~^u=jlIG1YGvm2Pn^>-p8VLy^~fFUJpA#`M;#u zPcuPvfL`BNNb@I()X>QBI-el($`}8)0<2dLxf}6+OW%zgc&uiUWrjTd)%XQJkN@6% zVPT&9`e?*JE!M5{q%m-|tr1WlKTl z?X3jAOaJScCf;K;0v;1J5)s2 zn*yIq2%;kuho5KKqxEgx-I(V)>f<5UTM9_xm`y|Xm7!$(y9 zt5U;KQv;Kc{?GnLxj8<8nuf*>2DGjIh8Q*Ndv9D$X*-BvpZtsKT6E`2HA>e{N_;BM z5prPv?`;Q1`6%K0h0MnlzwAFK{0Y6(I1@Y*d=ml_LVQ*B%Rd%_vlBkMXjEN160nS= zrh~R;3a4X|gp&87gKr<0zxK`t?HZWH9B<6X2cyGL{AYEpz@0H7v z1P`Vm;^a(*lBxCzq2c^81FcaeNe}omkNqUF=@PGR?|>B>X_4Yru(?(<<9~~f z&`@^yQN>+FGAkp3`YLCF;jtA7U2$XBOC0z-(Mg(sM>~PdYludMmVc7Ofd7_7i-dbi zKi7{@LNnoR-R+`9Fw=mRJmf1$lI7w}Ad?BfbYLsTACtqvQ*V>Uys-AT(4-{G?}TTE zy*jeI^t2V7;?)%lD&HGBnhbvRsbfkisAx8cpd?JeNKLj)YPJelxIX4O$m=4P2nn7{ zS`L$C&br7HjQ_`ZbT^PZCT%2rOcw8$(}MBXADy_D`mRo1rfu-!JKt$M4J!cS2v!S; zahltSVZ-ZVl3RZ`2OfJZcKYr`s;Md(;Qx@FyzG|Xj{dVG!3V6yzt&8!&k8;E_^PYk z-b4{4ka!+{GJV`EON_!GfpWCy)7{+H;?#x3Apt z_Bi;}{719`Fam7jj&sctC!aBm;%U?)9?XxDPVkk+T6M+T1YExndN;F#5NRa9?c4KyjVjNczrMroT~T!AjV1dLqg+5&g)TxtL2UsG3r~DI>9&l^FMWy zP|Tk#8_>I%DxD>5TN?Ge^Q@0`A({ToL!TY?OufGBV=vpbbvO8!Z>J>0QrUV1J4Z&E zpv0|HuWz6H5$;B%$)CiN?g`F>&;(|~#m)p{-^d}Bd8!o^1S9+=3;FM?yXnV2xl8%% z`wLeK**fk{LKjrt=A;(3@0Nt*jaHo{E$r7o0^BGB(}b$eh*G@&co66Vd5caiBGmbZ z;UAgnV`)}<_Hz@fDx;}LcZ$5DI82Wy#^;rjmFvB`jJuh@!uIDujD~&m(T2_C^jHix0+?vbWV?)$AeQET|@WuaEEiM39@DMG(*!- zkh=>E_0%e-a{;tBllLnYzb>cc?7nMsi9z5VI9XpC7EUscv?$!K!O~2eMY{U#qqRZG zuiy;2XZLd3eW-Wl6A4p%R>-_9o&k7iBWsYUi{E^wXzJz7iLd;LcfwJ9y+aJqW^-5X z_@inVmzlQxMx;{pdwcV$!u9}1@IFO`e zczE$`Xk(Md@gTYhc#q&}eMAvG#X`1Z%R;1+WIg#MhZ)D`5~?g5sg*)Y$b*KB;@wut zC-1XVkuUAP4)c$QDb{Xg&E+LNdyuhSBJ%bVid3A3-R&-58p64ECJ{{_P38hyeD~Ri zf4z8r0q0wE$v11Y_ZB#b)yeraq+=xh>8d?BzOH2&Y4y=7EB4vP&bvj7VOcF82A2 z&Dmjt*3uQRUVKCWT?P~ZijuYXMMUPWdl~tdi+jzCu2|Ah`q6Haaej+G#er$nVVmdM zYF18pQj6`vxvSuuzSMSr&Z**eWHfM~74G?_(3Qfkg=l&=M=THqOBW0NVf)odmH|sZ zUGU>q4VRB)2RE6O-&-=mt*Y{W`6vZmA1|ngQab#6$G+Am$@I`B3U5b&<%&qqjImcS zrZ_d@sGhiW?y92SmY+R`wvueA9HoSAU5{`>Wh=IT*mo43AoMSs^D)XC#uISv`T{Fw z*Z2VcFQGDB{%QbZ2Q5ncw7EIrK+ZKhKYSDnYN&@rG37!xd^5%D>I!XS$l+Dd8{TrR z3D9L>TMv^xrDADOt!U(RARb}y6XD%&qTMHl?_JsAk~Z1AN@b6nCzRd`+TsH0Z@-Z! zqPGyss3cI(?PjSGbYHq+dsVtk@b}ZXx7={l%d@lHO9>l69|snS3=JF~U@y{5)#u2G z`KX~u1M{;ry;zZz*9niwap(F~6)V}}JKFcxxZ4W~Gbl~rMoT%)sR(SxDi<>^8UY## z)Ylo#fG3Mo05p=%Gq_{El@hqK8>^Tbyr$F=ZW2;@-1=qtZiN(8 z(mBqz)yBrIjZQ~_{dRE00W^IMFp&qV*aBYMzk57GgLK3roauft4;(2?--5PU@&+W| z=Jbf#TCCjqC;@Jp@Z)PxvmMhcOk9_?c$ZbZS1)9}6MFqcJqYw>y~EBAb;S`*j<~bS zd(vXZp@L886`1DAijBuq@=_8r*aT_h%)mZj2(9;=2$H}R3GrSg$zjC`xs*#QCaG|A zmpI2c%U+$9Dy0!s=v&I}$0ko;{xYa!TY0rh2=8y-7wa@8O{s2q(g#S8?L+E?bQINm zggxpI{{GyG{J`y;ameb5uTfEKJ@!le6YbGF6`Y&q9o?)WbATpbG4=uOeZ4qwm z>tagf2l|U1(dd@t9MFQlKVbj*HDEhkF`;nJDJIn!qzk4Sf64J=itUM-20vScpJ>Fa zj}vq)!wh^D1gkk%G!RR;HcdFv+9I9U5BV{x@Oc<#^)129w31H5MtQ6Uaho;BJ+-Sx zx3({G--ywQbLZD4RQ}S@ol$FS=|YsD=7zKanUZ9CX=T=A@!9J3rIfP=Ik<)8a%aW8 zY-ouvay73nLv?ODO*M2OGi#-jY4LDF>U1@zt(9e%Cx;}?DqhkmQ!Kcco!)Ua!JBvN zRQ5P*XL=F-wS!P7Qp46Fcr93;L>c}3rBeycx+L0e3nIh4hj{0n-aFoZG*Ib2jOuqv z-yfeObT>SheYy&g$QMEL-%U78TM-HRfKtaEJ#&x_aqJ=9?H%xb=X@BBDKyuE4lD{b z!KGe=)6`Q`O*0XQj&_<&LVpjlBmDTrn&>GUpZaSF@QTYZxXCTxXkA!`+#_HQGtw$# zdTqwTE@Vq|kQ@o|*G4z*M$WLVmhvg{+hF0|u3y!|Ibrq-v3w$P!q3$%5WntjY)ZHK z5neD3w~FGVb#-t=Koz=$3!fMGaz26ng!JO(uPY9*dccozNLXQAM78)mkHegs6k1FJ zUjyV${9-6~x=7ih?MVaJEUV6CkEUJ27bD&k)%c&%vt3<`5>k=m<{3mfUb}vQIZ!7Z9@2@V%t(Pf4s}J)7y1RFYabjFcE`K7d%$!EJHZOD=!Z#(= zYOisp_kj?aUP{q{>^*%K;L?;!$Ti5oj>kz3hat~o_TZ$=t$o{qWwPEWdI-G#s%G;; z%T;2V5?as%C^tj2sYhqg#ZvK_tZU(z?g{uCF^($^BY9y*H8NbZlj+O^msy|=@w~GG ztLf%=<@;EVAi;dH{rzb3b+HztZw*UE1z~_nf&|TBN`858cKdf4|Bgjnw)PDeC}@eh zhSK>v;<{#i#JBs*b%`;QOp3X^(Y>E4X~L6qeMd%|7V#D@i3mA31`iByWA03v;K*ra zt8wBruz{|e)gW{Qy6-li5~Y;TsjiEXXo@QwT!`Q=gS|uX8uT%O^@yzPmLj)94%&G1 z+neo5FC7K5I;0K%wL_Qx>lV1aze%eATgl;5?*$V^+?o~UYB|NOJ4oeNe99@jI)mVF ztV6AW$z;1IWv{-W-j(Asr1hNLn_k1-xpg#C;>6Lp@iuQ~;G0^>Xmjh}!$AP0MIz#R z-0%(Z4Jp^ob_W}Af~NL+n@VkMF{9!&KI{doHHCs~j={s|oiUHEqg~quEjYnfnE^a4 zOzS+Oc96&VSu&L;loeS`yC&5&^^#M$@2=Uk@0ibGD@|Qx2Jy;Xd3NH3ZED7J@q?@c zV6;M(F>NCco!n?AuQ%(J(8+RmOTF*>8gnRnJz2;U0AuJZgd)i58ptZo5`fK~0aD}> z+XpPAcakdLj`cp_a%d5@}Y4yaYGfm%M+0Q!KP3oby{^4pwkI=O6BnE3&_kX7h7-P3xo)n2bQIM zciBm12CB^kI6dfuKWUp|ySmxJXS?FUTgF+~ml__&__N4^{efG;W!f-*`YW8Y8nVvY zO;6S}2sw8ASNIX>&RHEa>R>RrH}V%~@4%G-?|6{7nrQk|q&e!~Hea{l?)H~RXvZ5X zK=tK)_H}VVJ9hF+g6yuV3s8`_@KN9fN`l`A_ zj}P#qdF^l|8EksxcGzT!z7R0ziE>%@#|0ykR%G5jmIPP#w}#5+>4`4AZM##{C-2p@ zOESjd(@)23tJ)*=;*vb-pg=$3fWXA|XK&IhWzUN9RDNHG=UZl{rP3#^?Eog>8Y#*t zbzA=*{w;9#g@wyjP!M4*j?W&I#QvvGs}c$!!5ODe3C@b$xu+v6(Ot~^$+`>ujJSG( z1D(mMWL~W&m*tt7V;^Ur(m8v!qfYys4#kAyr?k0`|L;HB`(1r0XvZXTVpGUd|GWZ=?11lU93-Vh+z z&x}`?&Z!7E9Bjst@+94n0l5oCE=oqnQCSN<8QTXFih!h`tn>8e))K^_gD=T=EaHyN z^2(XyaIgVmBd0evX?FMK<^Z)&aJ;klAnw8bW(=3BVy@f^Pg%UxmBX1hZ(ob-$J3{b zd*Qua3BfI7BIf-jtMJ}q>f-RL84>6`z>R%0pU$J>PS~m{Ucsx^OR= z(>VfU(Eg@*LZInKWHa#c7sZ*?lk8QBZ6);jlm&q$SZ=%fL+eIo5UKaRzdO|`vPa_L z=G24fESMiYCMYd=^TCZRgBkZMxkD(<YMS?sxV`V&fb_H@TG0K=qBUME87p!x4M|QKt=#w#)}@#3zum0;ivsvgCJ-M z=ySwkjhypUB@)Yhz(XKnGdRNT&63%**^Lx?A_Pi`PsOuv9s#<6s^y2WHN7TUI_t%> zX#d46@cgqycH182iEq@(2C43&*4 zxm69`xaNg2A#P=v)t|IM6x*n5)T_lA6fc$S*6Z8cL4Yr|5E6xER^q1<(;JTw(wq_d z=4LlW2c!VsINdaU&_^?@0b#if@Omn0B{Q5Y*gGUT?K_GIYjvuT2ZS-`y^s#XOoaQ8VDd@(Sun8h zViyt>qf!!5ej!#3SnBG;$|a34pS8&S+R_?$>E0SLT8Vptx{WeZX3eV(q-5Vquenor zcdB>fhC*c9X>4b0L!eWxq)EjFHra%VUn`95QQlDWS!6RTx`pElETNm})j_5nmtj-e}KarSEB7h5|CJ2E=r zcePkL7&NyJjmG!6zGQfRV9j_lYNmz&y6m3JxZ8IjQuAvY(MQ@ZCXk)r>yxTt-XQKeI4HaC{ZHMI2i z;WSaJt*Vn7-Kh+(oaf$G}M4J0~0DTT>ATO4VZWExF|{gJc61HHi@$&M?4LFBzt=q1?u!tYaO_g zBSw62ZGMrGKHxdn*w(%2&Jx9ij5Q-#L~uO163ChBQiNJ?tl}Jn`BmQZ_3LOG_~N5k z_W7&U2$lDUuRgrTlS#frAj7zz(N{z7bDw#1h$+LrUFuiii5*lke*`ihYZ(f!R!X5K zenf)OnEdMIe27^sO$uSAKJ6|^IaNvLSWN9U z=wpC1`3raz&G3AW<~xqjpiJ`$ZvCE00jq+<(>50mHjVal9ly!*Y6TU_zlQ7>u`6OkR-U$|o(aU^2!sdPXk`SUxC)t>8&(EK2Ze#+pR61xSgB@fQ6GM#C7 ztK*@vJMq$4WdMlmHSNg{l)#fuG+{O@qU}edF%^l^1lD4B-&q!toLPgiOe2zhEt+pw zy{!$GT5$=u?hro9JeAqx0}F?dv4~besUm>_KaxjkzyjM`uPtt^;0ZZ&HRxrV358Pk zjXno9)#8nJKkubznFNz0JsS(8XFqA?c+kb{6tu?hv;Zsm^K@Rc1@S-;&*iV-Yu2qm zIkofCZ#_&<0RytZq6yJBh}6$AmgN@a-ufpV) zVkS{A*^{5VkdO_@G#2byt1ybNv)v6kx8GE0*?W=9{fEp%hh71+7A{@O3flZz1LVZo z)7P#1bcgs%WJzB-l9&i5?0 zk|}q^VEjy&FZJ|)*lbsAJ7wNTmS zK7zGn2-)0_&%J($043>8+C0^@#QMdFnVQrbBoJ93EoiH)O*ZNxOEN!gah39Z_abYZ<|>g*L4mL^z*{W@5^>YY=^XNo+I=}Sl^1cREE8B^V`!7JIbyXn_EF_2Tp9Bc zNztmkcqdZJo^s-VfuSr~+>^IBGJJwEaayR zL<=kNbLM4FkQ8a7QnePZkl2FMWleZds|n$fYf)!@2MITfEdoL?Ho$mh3d)6i+6b!z zhq!b1Z|TBS-qDac89TuVv0X#mWPo$o0M%UD*{5=T5SA#G*Ju#$wv%a2<126|**K5D zfGu@%HF}V2N1=Z>=r?>+e4(i;G-Z8^`HpgKtj1mjar0T&XocGrO0OqTyT=PCsOq*? ze(FKFEodGEG&_AYOCA)K=Emo<4EDTmw1O9foz`S<8_1f}eyh?!a=VDuyFLoykaQG8 zbEyor(yTK~h0AHT0Z!V<^SrsEK7%C|-dq&fWldpgR(v)b-Ff;KqCB4lZ<%RepI04m z8sJTia*H3*N5AQ0n5}%(c6Q>4yE9q(7e9-cjF;u-%N)1`l)q?4F3GEP3utkMHr@x@(FxI$3(urMkVp7=C ztu+jnZMlWfe~tU#I@Wfu@1RHbl(>_;z?TqDT-PQqYFB(uQdx3@X`VpzQCgbfoZe!* ztge$tb&sKYZek=k;R{M1GwYy}*A4Yk0c=-4vB+9n=Us7=dL%ba3xysA;PXfWbKkQh z)oKp7^qJ8ZHzB*eA=JDVJ_^Fe>6{KdXy9UrkA(AhGMhZfliHF*M?GroXsR+zkK+f! zs8tnO0Z^opl)p@C;G(C`Kygt*LU?e&hHyan0A}>%OK%{17wQLXoJKLw!9AGrgvANF zJ}pZ6;}wJbGN8e;%1_ze(&S~L#htpV1&n!^@SOgFZDPm|?W#rScdg^Yq|BystWzv2WNf|>E)?q<`9MHe-F>{T_JC}W}LFv`O3x3TeT zkX@3C_Rw`}eg5Y21DHpXF(j{g1b zPtuJ;$Ix^VL@#5KR2YH8$K{7ko|N#DpP)jpU;1YeNft+~8|R|^!j9A$Upc%- zo&!?xuPm;AiF2@Yn&6wIFJ}2aghRz#BZLMY*E6tBdmt^4{=c*1I%cSLXNgeH)!P`0 z>qOy3(OBynnsEV)Q$cz1#t;S0U6C`(%ylqPp0M8o$*Ocq<^jpH7G^)^?vHc>&5tkc zgi6SVobd*&DR2|;^o(|!t|8l#dwcb5>iPpBUB~3;7A!H%%j2EFIrlSu^^>=rJP0;N zB=IHB8?+= zrj#?^Nx01gtKEMM=s&v_Vm=0wL>Yh?*4TE=ltPZYPuCSd!QN8LlWNOnihu>Ap9dAx z36;`ZK;|^=Y~of?+k05W9VeII98lZ$=t0t<>sk8yEm|33CQ7e6bj~+BGz~!Pr|&O6 zS7sENV86e}x!;>Ha;0?;T6jX&w6CXLQzkQ+_o{cNp1}S?jka2{Ao}xF+d<>SGG^#arhI^`faIt_luyQOAZ}Imul9{atrCZ( zA-Sui?8mNA!F=;OCGIj~2;&|Lp!h<6yQCiEsQH#16>4h% z=!f*5T&&Oy-kFk<%2#v0Hmk!yiLddu6THam?MjuVQI;@yMG@Mn91?7B{?V#3&=*_t z-mWWkCyH4ak56`YSLN>9#%W@(<`LC`qonIYW!EB)m~!3Jt>N*xfp0lG?l~UW4_3sikaqi*s#f>>kkx6Qf{8hU0E<-C)Mt#bBPewIdLg&({ ziEH`>BwknZi!^u8@=A^n z@D|#0>#aZFeRU&SMfrM>OlU{_0Z;Rf9Kgg!teexFuhnJ#^$vRO<4dD%r^GH+^dwpa zpIaSV`&^46%~E#>TqfB*ZMGlyFKQMt9*tzM55*(uDakphJuP0(PI=#B>v)GM?6_ZWh{9{kk_m}za^d77fIX`uTGdY zSi!;(kbt~RFDpfXrBd} zYX20Pb#T4uf4rL&*1EW^__Tvoacjw-3Bx(Tu<8xj4p9ymw~{`4pd)leFIV9cMr5h>>&>4MSrx!FZCUxE0MnU z9y_m2N*f%px5c;MGck99mtVKrZ72<`EHK_%VGNCguDK-#qm{jS4`LC_1E==M)H%UP zgDV4TJs*lvE5d88ZELPil2_VSW~V*H^VE`N`^5Yo5=KZD=h{{*SBoF-(^f0p9eq5_ za(Z}o#r@lulagQz_?tiB8X`n!-QAzOWF|tg%CcuqF9ACl7%JKNvk?023|lTY(icsKS=?UeAf#m6g&kTqov2194(uB4@UQh{JZVM5b9+trfcVMUT^QdU(YFso|Nom`=|g8()a_Org*fypn01xaw>Ki%D{qfF<% z8fYzeTI5Ovxy80@3N@p6?XU9J5DK^h4U(#5n}iTDG#6iQjCaWaAhK9;HCZNI8UeNP z0i$cme$No5pws>2xt(=?s6v6QMviNL>p7p(#SWp8-zkAth@J|?^?R9VESyw|>*gzw zD#953y|*F=!X!10t_%Exa+Ec!sKd0q3go}W^?<5k+nwopX%wB0>qGA(OX%~xlSTfM zXWRCvj~s$A>F70hWBoQc@Z6t#$MX5;9maGfh+y}6;B+umlQ4T1p>zn1+~Y-ywP4(4 zK?`Ql-A@_^(V}BMw*hFwoa~?G{a>ZVp1pxO5?w1v$z43-GmCzDCD{;u3KQ$T*l>Y` zL!UEx-npDTiQ}REt3`0YNr79P!Hom;96Rg+=%8UevLj66l#>CwUdEVKTWOCkKo!hVty63=$u{2yis zRaLbPoem)Rz5dcp|2p>#T|U2aP$CnO^^xa=Y7(eHBU( zRg+1S=g8I>7q)AFIAQFp)|YDUaG1*#bz_LD7u0Pm{?UDC=y#>d{bpc(gQRDRIi@p= zI#=EprVQzCFH!JobM90~2=1pZxrPr0y;0YJ_g=TeF=adQo*vOvocUzJYnk$!QTOZ3 zGtU|)5<1wt$Rw>9?;fdv+Q643rjO9+t0pm4Hy5=jxut~)k3vQLOy{4x-#am2R%i>- zlo%#QmP^E)G+VYA&k!_O%r(Szy?BXGF8Otwa*3)eH;VuP+kVv6$VuV<9@$Q8H}o^{ zX>qBTjc_(x7q#&8+$HdN{UgW)DCOGT1XnGwAo1ZsTj|abFkuzj9cJtZmB)0O+&51b zxZzNrgDDeU)MR2$!k48jWxC(+&__xvfZyB#dL~?$yB7t5tUZeIMyChjvroXNwme%U zy%WU{%LpZu4kJo(ZHQgqW%;ho7dM7ZujskMzL+m0ek#dgR-xw@LcjxjAU^Sj{El``eqWdHtrQxS zm3=neM}voR-{PF>^SU${lT$ATQH@-}Yv)$iNdlJzTc6t16PD5NkIp)6dL;Kwcj$yJ zq{D!7*1uUahKui@2)GQgUrY7ca-F zbiA0|S~76LxzBylbZcXq^lzExnVXbFjSmX6;EbcWk0Dq-__xQj48TCYYaUtBWYSSZ1OouUF(+t1U95?|An0?l*Okxo|t+t$PLwGZ5aI!ubcs_#W#&u-ZiXYy+9^NNCFJ4Wl*v z^`dLQ`EyYV&HB<-weS)K`{j=vNt<5?>TIwhB>*CE;$~pxAxiF&|6*y)nJM$5YR#S$ z*kdlA2+c7B80^EJsVe%712&U#)ZFMzy?#6_XDY*x^Ze_#UL7oSK1DDAoM6^y@((xaD<>4_dYOOKI3J|)$C~!!cu&QQ}q*?sG zcv*@6;$_!H#MfSRY~_N6=L`^^c0B~LGgK~TW|w(u&X=PdJlZpN7u<3yA)ZrK2m(|j zJ?-+SJ7dEXbbGf|c=r#-;XeagdpbM%|J$qdpt>2oRTB&d_Z3HW_dz!5-|!k9ik?Ri z01l_4^ApFwlDbqdy|lP3h3wiCKnZj5l!9)CU@lM-IQ#6Q+@DLK)5nUXrlBOU@(qNW zT)b@e_&~jjBy?ej=QBzfw)=Z}rkrlhXRzi2`SOatg!30dryi~88QX5*cJCzD?U8^j z0@956Kcw(dCTs`VfE6K?ED?=x>nZU@5z%^uS90;Jp5@1WjwR;%QxQ%^?|}pj(^-_n(y$Ow((-cLXju>UeAmULJN!7!lk<_cL;KWEl{`G8@_b^H8^eQ;w|$+s4t z*;}oUW53)#Hcl&zW9IkJe1=t$8}bjnJnhe7f8E1jcl}v!bi?PeZ*Ns#{vX7*p1)ja z>KEia)s?>>3ZX_RrKJbS9)^JgkaWFku^<`*?fOdD6lU!9f4ui@#s~ig+gIZ6duaBC zlu|#7o8I<2o^eaM&_L+sj6vzpXnTDPXTXLlvm#_Vs)x=VhK5tEVbEI5rg zOAHJq*BgpvisRRdC$*;jSR1y`iM?InFcm9xlJct;{3?xMF@@)G9yRDmmh^b)GXIeS zBgMjYpmv~fpmoSN*ZcbHv5)$5dcO(FNhNo+b8eRj3*8HzB5`B`Q`8wLa$w+w4L#2Oy}GP8d@M28Ff0wmEn;5_2Ebj_Xub zxJY-SuFk{da4Wlw<&nPL0E8S%hmHxF_IVEQz%bqM?{$3v+9`jp3>%@M$$7{iPsvmf4iKYkaU$rqFWMIQ1u(^<}X)+i|t0>)_wl@oIW`bys^c z(91={EX_2>QytS!g~a7~(0H_VV(kN?-4;H82Hl&^7C&h%kNfMuXjP7M|7JR{%3%{f zR~bTd+hRW%)U!**k~u2COmnxOMdXU6h3&agnT_ii#)+{FY1SE@5?UB;+xuH)waoDs zM)vHKhHH_Eg8HLSi8#+w9W>!V6sKj*cMZ0y^Qm{^-jsH%7QuMI_rX{z02-iGn>+X* zJc7s@_CFW3n~3QCIh{)FPaHnJ0m8#BH$ksKpLTIBdE2XHDjJ5igN|CY|5fNA>uyNG`|msDZcz+d3l8oVn*bnzXp1DN*d*sh$HV_MZ+0J?0$QZL2;2AyC7DoiUk=Cklb&wppeHA`4Eo=MF zwO^CQPSHIG8($8mA5=LnU}hM2m^asOVg&-6Er~>JEGQ%&RFW@EVW0}Hl8Kgt{X#)! zMNy07agZ=29l!$*zLHO`x$#se(#kN=ul%hry^3wl@r zQ(dQ**Guv!dYyq02Pgb&CSCU;wDx;gCWXHjvUA$rlKlRdh$gJZH=Fb?g9qgPqoLs$ zE8=Ou7{^|`ELGt7G5Y1Px2*Gh zKs_Lx?@J-AmE%OV%_@?7zJ53Bj6`*`imBe z3$a3PZektvG%bDG`~}*&WSsymd3xRHumE$^?B2g&^_MLca$fGM-A|M&{wD-^#$)M5 zmQji`_1b2z#OzV1s|uR}9yilLtU*g(4h#v|f=)e1ePiCgqG5aNvqDTN6pFwhK6&mw zx^(wbox=h`<(Y9%^JmNKj8!6R4r0EX=)XwmB?|2Zu!9ZNM=cIzQ);>Tn0ztdb-M4H z7MR>8)ImL^TyCUMwz%_Ue=15DlhW6Q+WZMN4jSDjl!+CvwtUgCq_jp7#pY`lek><0H$YH3XYsx|6Tw_|Eh_TnctUcwE2u!#$x5I6o@4MEYJ^E? zKtT7n7mm6dCl%|bkj@O%k0SX{>wdfVv* z!!+@qd1|CW7YrFSYnL|<`E&yrLNyB(nqH9ni8$(!pEJvFGizYQsNR-*(cg+W+r*>$E?BMR{b9VKu!6O@Ovs6j$3VY zpLu^7$l?85aMJHeb+(nw*2ySYFnrvgMxoiJyXiH${rKNv6-E?7WJ!W}88VlBh2L%N z>vJ5w9%a}X53T5x#vd%nd?`*D=qXn0TtfU$5{Maymr`RtNxQ~ZqFPbzajKVig?LGm z)ZIrLq=)l+01V^Ra^zv43hOw~7WrqJ7=cf1Tpa4r=Q<+;FVd`%ywQ(DqFO~nUkPUr zcQ~}K)_<(>OY*1MX)5ddbBupIU`Wn|S&3^cK8=!K>hj4)F5V+IFa#%TJpW|uQI;{B zI$WBgO(wTfZv6k#OM;PyDuRp7A7+pWkK~{}c`|_Gt+>Z8MKSSfJ)2`QM8X0``fEyq z_+-CC5O3t?eN^$k#29Q+hyEnBN>}w5m z72DrzcKSTP9>=L+?eS5%E$Y&HFVsu$zR zA+vM3)n%jCONfUpj6ePFIkLwmyJhq^l3L|`r7-nwW$GDdl}cZ#aZvo1mDEwzh#xhW z*I{P-#Dkyem0M9wKS{wtjiD})>q{>K4<%pl1~8bs_>-#tcZV@|X^>TYDfX{q9Jgya z)5`cJ%|hH?U4B8y{w5VGot#>y8oKi@>ktpst5nI=Xx6eBNENwod4E*i;bJd*N3Hh7 zkNKH@iSKC(;&2#}{EBl*{+Mi9_AoqJF%c^4ngJ8kIQ>Yy_L0_q)nFI?bDL`D-}+n` zn)%;rneOG($l89CvNbLk;FK>LNfA9OG2G1aek85Er(5;6lEMG!EQH>rD1H8lrTu=E zzUc}Cn3{!gO)hQr)Q)PAywP?jV$ zTd%nJ*Sh3i;^;Yh3!2{3VdPC$W~0soJN8vg*&l zkx`k@`YGY!>7A3MUa=S8;de-~ z-z(7%I52GcC)>vyN%uD*R{hAjxq7)-AEDNfYM)GU@T)HVrpV z{YirV6I`~QbmPF8`K!whe+(6{P1u4jL@*`5m+8BnhCB~c#&jb;*S*wp(EFFEZ9UI_ z*C;zK@ZDI0&-Uf`y;kvrq@*bPc-msO;71FnZ-GW+*Xxp1v;VfWc|1NQ1_~RSK(stZ zv9)RT|KsW_1EOx1w3sJ& z@Bf^m=hN>K?(NLn6W7drjcJS1L;|j?fR#rUyx4SHYT#BRTNRg8m@v(Z=1ctju3Q*J>2zd7MdF`08LKbC1@+qcqT;_&xWpY|HheE+aP?|ZB%e55VN(d=&pK*Tu{E8vafpf<>v4VQ@Hh>uGQIQJ zh)?;1zj~hNi0QuUDR+G$Cq}^lsceR(ZPj_$%q&RGfv9J)lnw-8F^m1*BNus?aDN=3;xYhZe;x9=z#*QXXn(WX}{EydZ7Vk1( zN5NB!-k)svZl$wUR}F!AB&vebMSff}@9oV@${(A9J=jH<`N#jm8}Ki;GaZazI*iFw zOi~*Hv3DWDNE&eBzOb*=v72c8hvA!Xm|10I z5|(^uysVF7@bG!%6mgW-enTKN92FHqXp+JH%K7u?&4w|{9X59cQTtcYqvDbORD}No zN+Syt>NY$zi?pA(=cVn!1SS~)CKmoDKcsp+B8v9bCMHsOeFDqjOjtrkZ9*)%O#)^5 z?R;h0&5O^?#k|g~4iw-IpolS|qoWt%xbPR~1!#5uJVe8@fgj0tsRhF?t=ky@4Q>FfE;%okM%gakoaEVtP)fD z&1m)_UpZnlu7a2|Ue$jCcoSYZv{N~%>5{u0ra_?o|HE{YfpRUGTc}7Jg4$q@ z3C_r4#LgHDd!*r1JhuNdt6ot1QV<6%81ZPXjXSs6wjHGh3Y|YMhQ1cHcMrc7&6w zQe~$F-t;B|O+(!Uq_}k)0nmY$2M>V!_P<1*V~8}M+*+ii`l@lkF0CT-=XGFiho;W* zXK0+Nvhw=SbkPUbm^^%n?QbkDgIAM(drBMm9KC-~hq{;C+GAnjQry{9a7L~H*!Kx} z%@rcz0cTOE2=TwU`7F}K-&Y+9Hi-O+a=q_V43!safcs-2`s3MJtJ+avtI~--S?J-x zCqf*P(>n*i`ZjZku3z=Lk0ta2pK*+UXbYD4^fLa|lF%!nvqoVUL0QjKDe~5!``pTat#qZUaIePts$AfNk6;|E)OdyLMpvqq8S^Sb<-a1FxE2zTP3W&IA|*b1%9$qnpnRH|qgtP( zIAw5csnN}raxF=W(j6l8C6|>mAMEbZ5bN(hCktR>;ww2Pd26}ocDig9w7}x|4|Wfn z_sfaD=Gy;$yW~yIIk&8}{~w}?e?>VPC2HA$5%H-;%zS#FPxH_>mn=B56n~J8c{}Pn zR5HV&0gE|tz~O)R>}sW{pQn|2&HgBtn@Y!pj|W}U&RDIpO8xVbt9Cqe))_k`1(d;L zlu{2e)FQDve~}!EqgcST8RYFD{Oo75m5oSI&3tZpu5Sd+7uqVS+y7G3~6uXH0T^%ViX=um|RYtdx1lc)n($^Djq7 zQ$+pBn6v(y^SPc@9M3`4b6OZq9CI*2H>rJMm_PA7iGx2;?iDR+s@W)r%mb0R)%Nk1 z#^Lxd^3|*Oc9EjriTlq^U{kRI?@|$YiRY#(h(}EpYRq4U(wan)Z{dAu=l{p zw~K3!8kP5I)&l3v?v-Iib0N}%t1DG_7@-Pvq`Xu#TMCU>}$%9%B* z$eKE#3|%)}mQnG*RjTJZf-nC%$v0X!U!GJ?i(C1kKrpe5vy~H^sr*g!9L5YslDUV6 zM-3;>wDj@oy0|I){LMq8WfTB8XV`i!&F;)p*`KVC0hH)XzoPIqD9KJ&LQhZLesK31zMUt0||n`RcXUv6v~F!svR>5&KwP{+kN`oQlWA%R9sg;XK?|vlp^d zG$&0XNehJb=dj>He+d{}_f6uFCwztAxv&bGtXe3TkU zMA0=c?{C|*mnBTSN_rBU?dY5;_M&}Og3|0kJ!!2vjGEI64aD-D%F=(y%)7*KNkS?0GlUrvcyCYb&;m2^(PK7WtMSQ?y!$-5hsGsVg!`r# zTFBwZmv-R|+Z`YG6$LN8$|h-y*Wqw6AzWu1%1Dei76^+Lj_;r{>Zrtk(aObWf55Ij zwzvKBlB%g9;r?XnVT7dY)J;xR97?G2WNepVIdEZ$MCNwuZEcxi$zS-RT-jNZ!9xxP zF`JzvP&FzxHY{QYonY<2o&$9t65W})z-sJ+V2Edh+svPQj$IklaKe^1I=@ZY*?YsT z_21BJlberA1E4zJj8j*8a`6Y~=?=8ZXUD5Mu(cKd>5>N3CO-&eO#m;^e!pPjxl+l;ny1Qtc`0NSggg+*1dtf&{g2)2uLIL{6+AKxDWK3Ypc+btVRYh zSFRWKKKXf8H#vrq<;Hk8Xo|iS-{AU$UXc6;oJ@3WY+6%?gpB-71nXwnUdTlPdL8-w%r0jW(7MvZH^WT!8i4D29e=6<9IJ%)t}F) zPpCqgBX$TMcb%2)hY}u5N4=SbyMs{QLhd@qr~j4b{{P4Y!9pS&Ey0vaQBP4vz1ejWqEJu>C;feiZ=N3^gT%VMKZwSC6vPwX*A(& zBv#gw**LR!mVcpQ%Hq-ErRwqF`C`#u+jYM2BhGnOJbgpGdkI}Fl7ZOsND$XV@iz7# zS@`CVi*U^f_LIZLJxpaUmvxmC>NV|gXDqTPZoiztJk*r+&^HcCe8uCK-@<2?36=~ zT-vzsSHci=rrW9fY(T$_2$0`Win?$pQMZj{VW1lGTe2iQ13(W(cuqADC^1LW=Nss6 zfo4QRVc>k9`qzJ8Flu5*6<2P@F^gcYxR$E58naV;+rYG>0i*N`4NvZ0kS%gyC*0%1 zRJ6nq=%Y*9s~516k}xAd8YpV$$>kV)m-O|yjSEAXeJ`Y|`PsFn|Bu}M`gR}gjcoJX z(M-OPi6Y*DKZYi719b77+8a#=uxrOIj4J!RydyQVP*GWoU-3m+>zD;_{1WEnHC}j~ zsF{e!xP0d&V$k^DECVU`=31~i2$-Z4ayh}3RREZ%R=7d$Sb`I2X{GqE&G>`7N?-)l z@I{CniI3iZj>|ux6pw546JyoiVnlO654sY@qb@I108SCaMV6WaD%2Xj>s^ns1f*T7ow?pd2>=SD%;Z# z+3_9CUgPsDmo{B=nAud?11?k!+xUGlZ5VcaXwDrmdw*59lLN!fFBbi#uDl&3Yva#o zB>`DoQr?%6Ef05ZyL=%%6~NdEHb|z@F|f?LuI9fWJ>#F zo*v~Wa3X|HpQcvwQtVvKiZ`^M+R~PklfSjH?JR?q!{_+5_+n>v^NG`oUK-s#az&Rf z>=c_EJhZfMPN=ZEyMD41rGqu6UaY;Qhx+2bYQ^8G{$7c;wMH zg{1<9T}w`buY0Z#dc)upQ?ATs>bJ9x4<683&bpo5)sxU(K>P4ph;?H9Oop95uiy}>E1?*)Bi}&S0>fX`fe<)}F z0VlBWomS*1=8yHnKFw>EI-iSEb@kCe$I}#X?I?SCbHbDPNrPqMZi@ba4Lsr|d7Pf& zq0?#=Q%1kNLC!Y20$Exe@7A$r9l0wVoon*bTLvnksrN5Y_r^66jDeK?3Qbq1}8L+ynG~qiwWPrh#@N?HR z`XwLM_tfkR#}A0&xg9QY@`T!Rgf@3x)Gn8eHD{Et&_Uw8ftuVr{a4~(DiKgqJz9PZ zi+*^#4g!0!tad-DWW2O$!00B&=Iv_70=iN24KzJy>}m@5d*sF8pYRPwI*R_9`my>| zb<^ekRTJTwsfYK2voz#Ww#GVN+-eQz$BTW0^$=Mp)+mwi&r^*BTR+xaI#CB67ea2P z&vurKA0}2>-9jGjE?-}H$IFbMa|+ILS*CW@3bdbng*^~Sn)yz2-Vb-8_#waVwEwdR zfN!J2&sqKFp1}>TxCw1nEdsPQ`Yzn*7{SS<`uvR_J9!DZKd%J70EV81-MHA$@>HyKKIK-~?y3x)XlYkF zC%ql6<(CA5_Qp*FR{RAlje9z--k0TtRdRix3oh4d)WvvcSO_9r{1dt{B0_KK0JaZE zeQ-5m4|A&?Gj2~Y0{2~Cj)4z3d3i~brG{fnKrmFQ&X34^U7t-`aBMy$dUs=1yQ8Y zAw=`Tlk$_u)ik5Uq8Siq12RcSmOtc~GJBJqn65~g9E>a=5WvpHRM41CI_aDuBl=Pw zE$&Fr)O0p!`|@x}r-)p@oiv&m>5D&24jRM%|LC`%`L6}Y2OYmgdukoWlc41V ze4g6V1IoH)G!x+=;PTuqf2Pz>+)6LZC1?^jHC4{a$_ic&-+pUtiY?q^nwS_+WHfIo zxVQu|HO*IoNc8X6KW8Jrz`%f9U!w%Xg?Yhd!BU}+!5=TwwDVp{i0@E5Pp^2=+U|=9}$)vdit8K4qVZo7ldk2rw}ip&9X5aD|jI8xt%}ZDpBB zH_sv*U&PWC6mfHX?E+*tKzDO<^9p^A+5_H5Q|TBE0q1K=h(5QU*AZfR!4Pg})fKgz zkbn=H)pL}A=xiexd{Zhf{_7Z3Jh-?9bF;JkARK1j(K*^kr*S*yB($B+7DS#{b)l5P z{FACbcMcB7xmA8bM`Kiud2~SIW>a*ic*SNWCpx$$SqYo=`RtgV%uF3d&t1~L77$KG z?M_(+QWKnz-oA-b%|_wknw&3tCWWU4vch^WX z$ryws*ujCmZX$h1(ny~z*2IG7W;J~VU+S*lsiz~pGGqcUh>41%=`Cl)Wie0^bK>Ik zlaP=Q7yT;X5c|gZwX5K&$~#3xt2OY?pD*a3XGA>N>R329zzbHrhPc*#-TOXN@!qJ*(D;;!Fd%=H1y_aM@Om0c%_>iu|O%*2jF3ADJS=L^g?3UWBJKw6AYEphSGUuCe|V#2{q2Z+e>N@r}PEiJj<(??u9} zG|3WBCM8#dUcW0_oLXGEbMj!RQS}hQjv!W^_lMbZX-JEQ13aaSvrs!yzJ-K*8LBb; zB4DN-|MnM&*kW&IV2`Bk4!6;wuAWqWfeT$_pf@?FY(WmGTNlk_wB>2fx>iJ-PvEfJ zzT>fDmBS8b^qyUGm~)=oc{M974Pv0jL05g6I(AKMxP7o{KjMd<8WTviuq~7zrJ%5XEDV6@_o~|<|PD$&)M6zmkh{lnXbkDj>LC&(T zANBMmxVu}T;&B-`cB=FV5C@Adaeev!jpYAqg^dzv9eC1(l?>UsV1GSmrgl0s2vDdU`M2?AtztjTIdGg|-|6XP1&#zYcgqd^P_$b|AE8VB#oc|OsX90M`ru&9JgfSrp8@hhf$^c;^nMrZj} zkVZJamS<^1%C1V#61j(u#C-Eq$8-jBiTm(G)8AV9At&yUane#(SLfNPK$yC-^U}iE z=I8s5A5T%#JkkAo6DDh%`91?Y^M){Eqtb@N5!`t2Ea@X;Epo#FrI!(oPr`(kD!de8 z(EYum7rWcggioFZ(%dnPUlS5ckh&E`M}|^`o*`OyAXAW#7~!dkToAI)ylCEicd)zE zF#GY}m-%0MlE*|^cU=E$%5#d}hArW-3exQ|kwnwhlY?jnGY4h_-4iE1>tA2@w-#L8 zMn~zjursp@o3fB3CUQ1o3unYItIfPwtjDtFso3I)oZ-n9CRI6mo1Spy%q{iG9wADi zceJgo4y@z`qT_uFkX$SbB5wK7UHa`C$CxlI3INYpBk`)(-xtkl{&gsAeU#wNfU$tN zfVF_#5q@!rCNbC%Fai@E5bX;@9cH^@CTf@bMHTrQ2`hjnIM+j zRuiLPjFkm-846yoA%YEfNRV26ewFCgZ>o}>LdYqXt@A^Si>t*o0W?f5&BVt1cGf7F&@b;4mC>?s zGWfIXbj=yX*q6HI07!#gG)Nhv@X%QE4W2=rX+?O_#C;(BQ(7#-NIgqkTU|Ka`_R^J zxL&Nz8!tGOD%upSM`K7{g#CMuP0)D@%~pkY%8tY^O%1xHk`V~mCqOY31TU?@N+ z+(cY6w6@@}Y{7VtQ)t8yE4}9IU8$=3>N|}DO{Hh;CD(hJlBTA}$n1_>*a*aJd;9xa zEjS|QaikO!=B(}HQ5ZzuU;gcjhzJo+cuyow_)i2-HeB0)0e*diMPG%HC*Qy^vXove zG84m?=C~h)=5*0thqj~NPP^nq40>G;hGLce=n&?7wJq>qC~p!2&H2sWn@=!?6&sa~ ze0Q0in~PYpz!-fUAuWo)RLNk<>ZPE-m~bubWR~SOIQOQxvxE;Go9y~r^YCGwf!}BT zJe0N(N^oVj42=i*HMz$MD2TxiUgY!_bmn%rB?~!{_`KI*<<<;f+^q1HdUyyjo8-D6 zhpK2SIqf(r*gyivI@Og>f>cohbGy2c?|h0E7kd`WObxW(OL7|&r}}SU6*hef*QYB2 z6AnCiU4RfT}McDy#iF!s7EQ7J(d|3T-X>NlorS_nYeR zh(vl3&4AQ~)RF@GQ8e&C)|%C5`?(|m@I1J^F%~n{o-`d3EYj+RUuCHz^C2k!O1@6L zjxaqpllIjtn~}OR9__3oz#V#u>iCg>fPiJA8?yLYr5^bA+q5bb6yJ*8wmM)r`Ylwzme>~fFZvHVZ~!t zIx!^b*_J-0$*3cdS<}Ngky**cuZF zP8mS&sSIeAmiE@tqvUD6c~G$-6z_?L03p`an2aGh{l4R+7r26vv03O|ao^}r`_=66h3t%s|TM9TQZA=TwH zg`9+aVK|g8C`7#Gg*aad7-(r-ewkunPS3m!v?B@v=Wp4Ka&d6nZ~y9hlV0oMp3Nuo z_U$%p&7im*w)px|Ta^A>K@nXl$jekh{>|s3<70kaUS8-BeKt>x*L#{BVAw_M-#+u% zYw9*kVJr|<8`eE59-=-$Ztykz;N$nCVOo=1UR4zpzG0hv2FLM(F#$VSIXX%a z*WZK(xx{wrx$^W3HQId{YaB(In%S?&vJdE@n~#5O^lrd2e9aG(zpFK!aWZU`P(@^oPsG8+4ge-vSH0w{1FSn)h7 zl)BBnqul?v+oC-*T$J^@sTR*H*n~?5&(VjYoUQU(j%Q2=_?&dL9`@;J`W&)-a&oez zvV>8=Dkl^v12Hby%>Nj-8ee@X=k4V^(h46Xi;BWZHRpAu6JgD)-{~7skWw*Xb>v7S z*m^!(;7p&XiqpnDCgxO+tXD`tuL72%@^Q(UtZVs8a^f_22x7>p$c6+dbFd`#qKW!C zXDffLP{Vecw|chXoKeI`PUE33OXU@W?cI(SNo~Yjzi8cNfD#u)l*E44vvut*#o~PL z)b1mHKmhRf2KC}7Cmi=tEH&Pw$53GH?rf+3ZfW^NhS5GknD8;VzBGkfp_1-Oz@s;Q z3@46rZG3jtf{B@Vgt+rs2Da*bTy2+X#bLOh`*)7Fevcc64qi83w_LYgw~atsj~kCN zEgXxAQqt7TP+dHegrfAWh0#Q zS3A1cZANP$r9Y=kd!Yip-C-W;_I@{=m4TqYIxpU^J5`h{FBG^o78&jg>hL?UcymP1 zd6rap@?F(tlFRUYeaD}|+l}=HSH+=W>B5~>N z&RSYz6Q%VDn0+V(oyHaQ>v?Huw=DII;_G&-Y&j!53)-`e0-i|nLxLL|m!Q_gGGSk= zUXDBK-o8HoRk5zK>*M-d128tZ|$ z9`~mMsVM>3Z%sH~yC5w(8;m5jRhmt*v(BjAS6U>~Co83`X)D`b;+&_sudcol2jnFsClhJPU{I|1Jv~^!p>>-{liaz^zjBBdNG3f%limuaxZVrAB?7-WJkGNRYJUFT2uuua&yG%57nlldS;2Cc0084e*=FHXAdJQkltUOH(4V5^m z;b|wad~YCs(6!Sfs*C7yq~_ti4C~F`5@~fmJUd*d=RUab82R$S*5k6wstobqw%&Gd zce}t^zk{Oz`fGeBk$%XuaBw7&>Ruj^IXCc3>uuTnAvH+l8M%U&1Yv)+eQHlM&zid^+))ky4 zdVkPBJ&*%t+uA$**1bQO>7DnNR*t&3W^yHMXBPJC0RJeKORD5;7)Ic&mDxqeOZEUH5Jk+ z{aWaWsLWe>KAxX10i&s z@AM}h*OR#^>ngq&X1ImRH)s4Y3Y$-sUf7T;I*NGBfrNKo<>xV z>&OgQ^cVq`d7VDfM+1Z&%(dhy5y`G0=Pz15Jwvn)>zMzeb*EJ{G)B};q%lc(Wo7fV z&0W?5awZmf#mI;V&doS78t9PSrewp8?W{e=LaHaU{Zng?W5;6)Rl^Q4a?sN-UhA)h z9j=T>1qi3rb!fp*6SCnU83`YI=LzUrbMw&quOCwTUovBgau4?T9~{^LA6)0aLlFu< z*a-jW_IN?T8&bKRhVitNloZ&~lH+~9BlzgZ;q5NE)W>K4jm2F_;r?;XCrl^I*Nns+ zN%F!u^C7Yz7<7bAfPjnn)xK5I*k-t;n9?;16C>|MG_#fY7#r+2b7in#ON;VxYKL42 z)!iPd184&BmW_^c;Ior(otclHmB;zuk%;%bV|-~^$sT&G&4^bbd~GI4#zrGKDB)Xa zDX+{k0A1KY!1_D&i;R01DMefU+=7I zo_xUeeb(xHQZD`6i5D!so8T=?w-tbXrq>aW)V%JFuT}ZUQNwCyEK{fH6H-XBqu!@J)K>b|QZ%aX zdRPx(r}F)ko9cs#?YXol_!Loh5UUwxH1t{BZunVLp*Wf)Y?M>BM0O~cL~wh9Wba(A0rQj52C6q5;kP{1%j#WqZ%-aT%E#Q;1`a+>!m zT0NY(|tEg$|PTtpda(SJ_dapv85%rZ7el5Y&383# z&6;65nDOJx?))t)>xjPJuzkTVWiGueU#9cg;_SQPX{qSNF9X;9Z^5Xi-HY*ei4#KOrpkOI!e|hm`B;esVH6_ch8#5*< zRg>bApPYZx1djfB9s%&%L5gJ8J|@D#%+%DKBPpr%)5Q(z7=(w1@3u6BH%=&h#zL$d zEh!_R=lR1)hVvBG99&Y~KgwvYA`TA^F>DM16VV#fsv=r$f0r(d-RPc?)AFnH+UvY1 zj=S8D#O(q^!HC6{rE~4a|8cpIkq8xHpDn28wl^=u^)ylN!~C6S<2H~{v8ynCXJt`B z)iES~P5|pdEa#*7SFOB8&(oG17VTbOU?@}LIw@%vDO>9_>!^U7oxTx|%38!wiXG+G zRH|Q43FLQL5r}49dTxC_*57V|1hSTwF|4T9mbLx961!_2xAeVpZyQhOygZ96%05WE zk6C!(U~+u4Z0&XkkVXNRS=G}uNDN45e^ZmFcqJq#s4YzNy5-?=3BcwUf-zI^^8*SI z65?xw>&^3iGDq-kmiH*Rm~V!onLsh`Kfr7c4T9uRJ}-bn{jg!lft%{-dLXrJu)evf zywpX8`V_6{1~-ejCvo|naiTmBXA|Joi^U%YjMJYCw@hypbEe00Exqr**uL z_oTgS`@Tk`FXgZ+_-pTn4cb`Y9%e6-Q|fuwM8kt;q>b}Qy|!!p(R1LARR`Gql~2Mh zavl}srn!X1yu-3fX7lgw%FvUIc4+p~X=(Ri>*X~L72wo-Gt#gyrrXPG2*hTkhY=&)JZXr%Z_7LXB>%*TeH#$1AoPfGSj#Xq;ymv~>X;DsCA6 ztuj1xsDhfT5e=He&5c;z+MLFx8N;Yy z+}Ve^TOB{YPExX<)XaaN6Ky4woavIXGP9k@#?A+O82t5zayD(GCqiAh=WFU^)Ll;D zO3+2A;SP{Q82t`I=I2Bfg9&_~%5rgW2||B#!(lxFydGv3*yM>n`|_R1xw}1CSvza3 z#@u49Tk4V9^49HHP?8~k(KFM=TB+R-$xSA9UK2{vh4JxUe#c}aZJGCq$?#&F<~()n zrn9ZoI;m(i%VA1^09}0abT2O_r)$}BfE9X^V!+S8;!*2)bhE4de6Tu$1uqx8JahmPW19o6s77A~%31Pr9DFin^v~d=r*|vZFvrPhMJpBB? z-s1@n6WHu0(=>50|KfKVCkMFAj;e66hsK_@dtY&h33{Gw2cB}At9;G5S_a1Ez7!g& zxEGunS92^XDw-8%*9v0>AM4nN4J^A7hnG!_<+W^J4E{+^+ zf6?Eg*AO7mWcA}(vNlWjGJrrA`}ui~^v_CenGm@0R#%`yRAP97D^rSJW;FezQuX#4 zx8(sB5r;NapKK(QbRY8z3m<0dzHX0+p5_k(@#N2TwtbFn_L{nYW;U*Fi*AL6B7Yv~ zFI7?@)5_;l&`G;23s6>t}pg7Q`v zGvrZ65)BP8%sqE;QTKAn@Z-k;ex4yc$(5N32BZ{TBx(|m7~>Dp5WSvAF8OGK$dn7? z&NryLblW5dgo%bXKR-f}S@q_9F5u(Ou)Gj*a(O@pQ@5glgF{wo1_`G-=$;nT9{yZR zTo*R8KbzkFvJ|pc;Rc%~P(`t(%%ETqHWS!UOyfv*`?1Wy$Z04K0ATHVHqG6iogtejp_-7M6$A?E#d6F= zcF2Jz6|$mozFTB#9N9MzOH8^*n!%x(@dve=FI~+v)Hsj5VL0jRK6{GL1;k14C39$AHxM4iMhg2 zlV9>kc(LLgA62Np$g&>`hQ`KX=aYi!Qj${AmyS?$_@SqAkF@9OR~@_d`@de<*o(L# zUmSBhBo{Q0&$F7F#5|%q!ClCQP6NFUS?3FP$2bqWccO<_VZs*s>6K}Gf_Up| zBy?3Gr_*CfDh>evrBYW)M;l!p=NRl>CIHSLt2^Yv6;x-=vZ#@Lj;_TorX3>Qv{9S$T#`=`fAA--^* z@Vg)IgGiGnCZFDS&1HpPZw3^Wm3?KQbtvMsTZGVyovLenX*vA4few{&U$H+sgF(g& zeP7>K8a{;r+-iOR&wrtp9o?Wk1naT6q_E3BYS2STz9L-9*=n&r&3*}O8TWhGByOi_ zd0^uf7Peaj9h#pxt`0nooC0vuLSsf%V2RnHwm%0`@8OdMgL=bz&)oBW$x=e6q_ z8l2C3u2w8Nu79wnjimF#f>GXVO3MKr-tWn(Y!M?PzC|QPcD2{U3y<|Th&}y0G5tdZ zVfVE!*Bauz%~FFsG=1&9=;k!m50cp(`3B;t?vNW4cUD%W1lz#z>#IvVgXL3ojFjm% zPofMm^~h-vtbY*Ao?Di@chdP#zEEq~db9R)Kh`aBhZASpSLBzL_5u`f%b4Mu+Y0JF zo9Hg2bm}vntqEHF$s}Sh#{1B|()~Nl#?jTaeTeQZZ;GOrrq*dgkUrL~0|NsFIl8)> zfUL_p&rF%FD?CFbLUrKm86U@0-Rxd^qlM{n(v7;XFZ20xH6tUVaUiw^+k<*w2Z@}_ zQg|4`s7N#V{;X&eA?gp~>M=z9^d_PU9o%EKLhHy7|BNf@Z2{u$l6Cu?Qlk~yzG4tOUZ@SOfbz}+G zmhrHb4~*bYfjF9cc2)fJya)7XX-%I(zOXPhhF5QWeNYSl=U0v&gdqp)eYB9|b|0sY z{CzqxVbuHOxudyEw89vKgW|AU+WBya0-M^d6$1u@bUcKO{GKUQ7W1*_&J&MR82!A7 zjg4J%NPq@dW-@_t=}((mS^}wZH{1zmDOobj;I8zCv^ZLT11qNnIbm4jXKWTIZ>2pW zQlNj_=YwHS9CN79ahvJmDb>>i=nN|T?0z@HN`uB}7;jDJQ_R@cOgX3MR4_*b3UQ(wn~A!x+N1Z$P)DpTG$aBLCeLrbWh1kSexsID@PL=yKg5r zbr1=pt4)%uCSEJb3vRxf4ha0CbtGVjVsaB4_t_~Co}T}M8c%}`OsTlVZYN?P@Bwgb zshEf%>ix)eCZ5F*QmhPX(ESz_J?pi(C?3nja=s_Xj&km?0N=KqD>T|NLG&Z5nf+aiMHvRa5-Z5FS0v%c>kR?jxkb};b$2TD zjb1giHIf&@WWwez;p=FpXBH$J)kW!QBu6ofW}VmLtnKtwMA_+;#5}VR5R+BkOm>;r zh2->R6lUn(b`kUsvv=Bcz%q|d5+GnWQ$7A03dbnt(;UZY;#rgPk4ygsyJBflnC{80b1Wnw-L8Hu@DX|Al=<9EqihSE@`aPlKAoFVEYaFEjV`ZngPwuH% z-0c>;XU5>EbQXR_we%XEI<$9LD=s2yYu*2gF0f}s21>0`fq zDvzVLxs1da>>u>d5pOX{Kl1vvyXlx;1I>_KuSBlA?iCm<#zqgfw?LJrcqKAu6B0^A z$XM)*>}}^QLj^zl)2vJ8d$%dE;pHMbyM;KFQBX``d44|aWRj}ySt1mUq4SdQJMi$j zoNc`M@oWd%c|)PaZpECZ;=dia6n3O_B((WJ)IMj#0x5Jf!!7va=!PqEmhbHz8uiPU zFZEG@>uhxN_$peT+9CSDk@}*l8_Hqb=gIyLP{Jo0+pGb;D0D zTb^*927z-cRE4k!OE&_5nE$izgal7vk2^&Lg(6k16^mgQd+K?V-INR}d2O`0b3n#C zF$O}09fx&5KB%p56Dc0S*>_Cjs%ls!&ZUV_QfE2>izF;chD~LmKY7OIzkR+Y9^5#~ zR=ZQX*ej5nD7k@u_!jENuzyg@%4tU05J?UZk<2+iiG?40$4S0Mk(~aK5}F+rBd=z#_WKpyx#s~h?aeps z+#7tZ+EySBFx_x@)L2Gv3U-p!{mK8E?DwOYjL)ZEk7Bn{WA^UL3Ip?&mj{c+*hjP@ z&0{vBy~Zq(Um=dsa7_cxd`Rh9OVr&g(Ihq#P){Q%oB}rk1Cq(<-w?4lhow7qEZ8m9 zMMMbFS#&)!)zKks2Nm?a-9Ks(Xx_<_ZMn;H8(4AuQ{@;z{&%SdF+Llxtr5*8YMwcw51BM41 zneKOz4r8sG_$9cWWIqSx@4PEmzm;+>ZP;!JTTl` z1>4HE*>7(4zR|Q=GcLtG6x{Bcs#1KwS!{76(%>^l+uqaq_uNnuK3=$7(NkMDj1GBXQC7v5$`LU>!yM!xJim)Yk! z2^l=`zquBn;{4EOTICTJcP8#n=QTT%uFNyR9n>^_X|FeO)$wq+UeSJce^@M-y?;o7 zz?fZ>4HjZgGS2!a#^!1g(=EIDWpYaMvAw$b2!_SWoBMS4IOgf_bh47QUGN_0^yCd~ z&pbZ6!o=0g_$okp5@Na(lL>8a4@+%3qCfP~^A=KV>x-y)uRwo`orj$egMp3Muy>6@ z$gU6PDd5?wj~@~|7|i(;_K{>djGkmLj_OikeMb_(0M?w^w;x;dA5`=s6Vf_S-fLO% z3>3u`sXaQe1F8IIbntlK7HKT|uClT+bj5uw8iRr`(xabN99y4EG6r|8DTz&k#^YHN zrU%B?u*Ma)^i*&LSBXNX{EeJ45nw<(>m=65X_1G;P@>?TZqv%Q)_$Ko_3N>S$N1W$ z*YBjq$6UkQT&0QQ1iBpo@)Tsw0fFW1`}B*8`ls@W7N&cl&O1YhoTytRJvZ$DxtQDd z!D4mvDiJb>YdtTHVC!Uq7J6aWCiQC1Zc^GkerDrc{RAu~cHCY)N8h48nejii$cCs5g>b6VETZ9h05KpLTZ4 z;A2U38kV$UPkIyDrVi~pbXp`xLQZ6Ca_nrSbLIRWKh;KD%Yx~=Iw&6>k#Qj8!p^}w zJH?IX7uff&qKOTJ**rC3w%nR=fl+TIM>P`@pE`1@FQ3X;*wLS$jt_3vA4P9USCHqkg3Ef_hkT zULuPV8`s9}v^NB?CkBI*ANBg<+#h`qVjM*E(2de2;bIAm)1XGeh^-q|-K)sCC72`W zJ+i5I6KqsS{w76nxoie>PTQEC#H=NaCIpF<;*q6dCGni8pG%#wog*+z<*4kq*SHQ_ z^W$)7Yo3`~UcTLiLmQ594D(QZq5|?JeU(1)t%x`vO`)Mqb>8C>Gdmkk=jER2*nP3# z!v{m1J+8B`1@qOntpyH~Z-Tz{B$f5{9T2Wxv)vt)7nm|%n0b?S5<%G8VEZ6JI(%Q@55gm)VImx6l(bRi=uuc?STwI)LThSW8^P72O-6`ACcX&)P z_VGka*go@9R^kZj8W~yoec9Bixztb2tl?;I>j28jt7BfCX<;=j{a=L<@!hI$t=*FS z{+JqH{rb$cqcxjK*wSKgxHJD&J! zh1yWzOrwB2wlx0$e^pL~Qi*>%*MEtA-3~>3usi9bmz=N3^U|Zu!p>!W88#sV=`ZCw zA@&0CcFlR5uq*w)^3L)v%JyCNfTTzxNH<8Qz>tE#FvL)b53Mvx4c*cm!_YAUlA?eT z(kV4G%+ND*rxJpso8SKIwbrx#hJC-jU))#U*Li-A<2AGNhp^I&WB z=Lh9KXL<$azFxnxZ3e)1R2hG*rWpN}?0`dIYnxgl;?j;I;vK~i5idlee{9X%=PG%G z`M~&Sl%ZnAt5*oSSKhqxGBG!~7n5-k8fq*w3}kwUF8rbB=zx0^&(_owGe`cpx;ps) z9W$@N;^uSWM406C-;?D(8FVdRmnoZ{AFG+YJt5VJnGSzd9cp>j;l8Ho=ptK3Px|D> zE5}Kd{_gTZ@fgAVq*-%^>JC>)6Ujd#@@{{BD4caY=$2UuLVsv!ZTxBL$#gKt_K}Sk zfOp>ldJzZfN@)aofP&B%y}x2!CaP0?#2GQ8W}L_MdxwzfY#_3OxBocKLU z$W-&SpNaK;wkVMOekP^jF_mAY_jGvBLeIbe7x2q%o8}>3VcYIU6|(=@zW#&#n>!JQ zQmhh@$r8)ZkuH3D%Y}iJm;V_ZIO;%GTC~bpFK~L!d~>8!`0zupNXYgqE&x8Hz06!F>+h}?15W*r7oR_uXXZ19ueg_b6$RaKvSn1d%}~8q=6LX zlSgM4M5hl=+x}7i{JACCf`W_^P>`FuK|_=Q3GY^a6YP@2j~stiE(e~>vzV(i{T^Sx zV^UUEXG9_wiu%|7^O_lteNGkmamu{WRrssUQL#EJA0KeEENOq_K7nNG;pu52NsbcP zR4h@p=y>;iJ_ub^_N{PE3a(&{2s)_A0kVb6VNgq}E>13gAy&&P<-}9qE0xI>FJqu- zAy+UDrRQ^^QGik~^g-staC`6rB1NU;n8723;^tjHX#RAdfPkRlE87Lbmxfy4+kp|B zV2{;CfZVEk(Q1rLOZ1hTDMTRZ*;d6aLtF9h9abD1K^Bmj%1adQh~tX{2|^)WzY>-le)mpS;*yeO=VxhSJ4rxY zUBb@uz~sS4cD91kbNcVY(YWYI|AmdM&Ab541NTGp^zTsrJ;n8=63HFL^q9?lHjpd^_$sVZdHkdQT=6czmC#9rW5X zHZnC-$s}^w$|+EK9Iz9(e0m`7-ZeWv?b%jUb!ybRfM9PE!GI8+aMIV*;`EQY(vJZp zzCmqwsbSsgA9KEK&y*DdgAQF>R63Wp`2|JS<394Jbvf9FS1JyWXf9dI{RDu8vKyyf z(!_lgatL2Squ)zN)Qx`55OngNcb1z3*JZLKg%@%?1vxx9`xh`~u1XyG^2G}gI{clh zSFbJXq+PL^j3>+uQlErGrDc|za!}I0;#b2$Ei{R}4cv0mb4p9??uW?LUKk*h)QE18 zNV)kHv*EqTZ)G;%r1#!z+xO~LU-Vh3SBaj~xg+T$d0zSFmHcmL4t@V*b(7uXm5XqcIqrLHNMrDBq$ za?+=yd@5vRC-ka=laD_a;V|&=g-D36X%ScsRQ=b5&U}b?rTIg%AVy5%7fT{d8v$n9o0)cov z-2jL$)Ow@?IsR}uW zNWNCMi3!eDJ|pl~w}10#wypIR{)=>6N#@?BM*73wU!QiB7H^R&zjm@JwB&DXVTh+- zj=lJYujRzEBFHj@-0Wu`)6)mleG+r?gs5~hulDEJEBX9vUrL&FJ+UpBjEr8z<=tlA zNlQy}Rwhsd*J+v15zE<&CnhDO2KI?~uB=d``p+fp^>?{Jzh8H#p&h9u&9Fa=@ygQAGy{*9k=C)nz6BJ z<g(J~}2;T|)DVi>*0uYX;+XZ>Otc z>JbeSpDh9^x^hS>^#H$hpq zoeO69E>mVI$AfdfVuP7WlUYMEC=!}B_;3_0c^?Nlz@z$7*<&*tSGK;D)#+!mRk~ep z!K&1n3Hhl=b%jm8UHaZy=0$pYiGCi{TC^kuG+Vi1M`~dyW5BxbSBTj%@5TQB=Xx zTm5nEOYh&m%SuWjd7bSIEwkzH8D$up_|^Kgbspz@?Rv?>C!d#*ax^W+L0^lfh8#SU zsvNlJc=}$eG_JiRVE@MbPu$Yd-CvMy=k}S|_YHE& zS_5kl&5@;+@7u`cWIFn*>+tZl*4VbMJ^NMYBb(x#?JT?4~G6M_-QTMFT)%M zSuFt1vRSW-eDTFahmnWGLx0!nja(8e0#=1sm&BzY|E*2+5KOEcpjUO#` zgr$m0%E=vD0)gXyE$i$zd+tpfN`~GwtSKoSuW5~}fAsJ%9m#|LT!O)FI)}!_nv}A@ z_ZY4mf;(VcS0*s)>wT4t!I5WZNpx8r?*|08WR}sPO9=;?!`h=fVgIzpwTZzHP^6yf zHxo8yM(f%oEJ}m?dSJ&Tb4h2gT96+QcT+}mdFYc)e2YYWV<@U!luBK@4X5-qH%Eyy zPy-j|npzL82WJ%ir%z;k@qYy+k!hxRT^J*#Rnu+u2Sccu1N&<)+@-_fawhDr{p`jl zkK;*TEWD!pA$66Xf4n&|XRT_>fy3=!X;mkby{s;UiUwyY(x~umCqr6Zrplj&%T2KF z8%`xIYz53wWd45F7`K>)j^Cs|Q;UI$VkY-+o3hWA&hp=UM719_I}=o%;%fAEx53qh zQucZzmcOvIjxtuU;z&@0|0W{8aztJN1yjtheeHcqCPOIWvUFha-M*@^vhBy8c2Ef} zn^gINX@ z%j7N>r7inP9d-TwpWevF+5-dqqdRj?g_GnRd65cRK@YB0I}GNonjmUCpw43cI+6}~ zi}frDJXz5-mg`}MNIBRr1Fqr+(e2_^S)00T%KBM{#ux?PZ|EPDeS}AOfBMj_t^2(R z<&Glr zuzbS~*NCyLIs2MqFApphxxGm*LG{(#*ybVV3%FEAPmkXJlRWcMv(5ELLzUaL-b8>X z7Y**3;QCJ(+2sY&{x+r@49|7%rz*xK%E;-`;M*4{!aidzUf^VhktFz&p1X0-Cv+Ad z5a+vRQycZv0q-qZT`hL@-dB^k%{5J$%}(qL-ZQEyx$s2w7&&>cxWP|Fv`)8r1yZ|?}LC)zc7EV5>c`gRJB zaGr&jt&pY?D-bIy*{Qrpkv$mJry#}E0qeq|KZIXY#Q!VDp~;R1QY%9{!fBwkK_oFV zjP_sWhA2vy?0GCl^d2Y|vS}*P{pvnt;)vQSgMzf=28YInIfVU0s4gB^`qN;fkSW>d zAI8|+w&&#O0YX_Dp-iE%FXYK%X#MXM8R2c8U%nKPB#4UQ<>8N-@nrZ&0v}zy{dq`o z7<6GV9+EpT4|-ppNX=1@P3+8(hW$A+Gt+^cn&QrVu5*C@y4H<{cJfy@aVWvNCNca_ zcwXU)q4>(m#OuMdVZL4A@6v*beJ>|VV(%}XG~BQwi<);CjcskPC9k@XN4n4_J3nn; zOCU>ALdPP$5es+73YumqR0T=3LB<4u`rfGJC7rVPSe;wnW2d`ZB?fFvZ0mZjU%cQ_ zVDR=3=(2^2Ds_mtuv|+#_n4%!k~_a5S*;2UfhLetAlF0m@s%(DOq?ehJjArB9iAS4 zO^mnj+@Hce^PAt@dI_dUc=twK{bk{s{e=+O=4vv=Sfhe+0yT8$?H@afHFE=}SzZHq z?8}$-C7-^0O_hotsTKt^bS4rM(W({Iy{8%96m2BUUb!5w=xzw3MItgb1?Uc~>J?@` zYWZkNuT_u7*cwAH;q^b6CCZhw6Y7zNg9x-ZT~rf|b8Gzz1{j}Hn>v71)WgNSF)n>a z3GNwQ{e^s6e%gfz74}I72}uE6y^(+1M`Ytcg!JJ9Sy^w+b8$UWCme%JZCiruZJ87; zVYHq38)Hi?!-o03hfT-y3t#7hVGGiWnKe!|HtDchxFTVrxv#m_fOcBj&P+1PHHrq~ zYlbI5Y3@Y334_^@%Knj)-~N^pSlO_A6corJchQ{fGdXoo>w)+O;J}8V*vqe?7nCs- z%pJGe(R0qi-a^2!j=yDV<~PMz0agZ2#<}G+h%U4DLsJL~Fb>OKD=RhGIKwM-G*eud zoQ~Kp=f=oUu2LBZYD4O$ixNe%?)4M~By^zknppwkey?DYvPX9@!hiM2FO9>Kj1Z?n z^`=LkycFL1GoMoohXxss8L&KEa7OAy0&Xe?mJp@7}Ec7Keg-$W{3eWL_QIqLrF8*Rg&jy%egVMkTIXD?)RAIpO zH&ExXUny7N`%D-l8Ge%d?)j08h1%Mn>^hdRiPe%78oM?|CnJV`Mulq=ODNSFC&h~0 zIM9ChS+m{IBoOBxIW_l4K@8nfvfE@fXF}U(lLlc@@rILwmK~cXq5{klUmfo%WjEJ1 zqfM zfQyxi+=QDxC%MaH8;@kulkO1kyZP`R6DWvdLFDms6?~Bd<%Yl2WdQjlwrZ_cO_^vP zHRj=jicR7S2?@I0y7Vp9W41hr*Gee$%=FY?Wb$G7254TECtswntev#UPBWNR&Mf*% zPcm(KN@iW0G2zg{`!pk`ETjB>>~Cifu&@qt@xr47ZsP3(JYvFzy6~X*kQ&r`@jCJ+ z^S3@1-I)o9b)j$qfXCA?yC%7{N2Kb_EUqcO&L%G@n;Rsh(M!Wc9;IKwuUuo+!Pl{# zP!)ZGi(A!NyUK=Q*j_zXM5#-CwF|r9cof&RCJXhmz)GQBYaM@coqhp=Vs-m48UXK0 za&-nUFq$jw92)At!i|xT9^Y*>nnf@*bnjT15!v8hZq*p_YD%uT3-9p!NFpGqk>EZ5 z$TINSbrR8Y`~c~pr6u8}()Lj?M&AB9v2k3@zyH)z`e6~8cX|2=)6=O!G6yD)_!pKw z;AH-%0=yK$rOrio)(ExJmHzU6$7f^c)iRrbA3$=phd(i`vw>EV9AaD-`TAA-U7 zOUzH0{oVxy?U{s^CE?b_YdtSS7&AW?@v<2&CatRaZB5mcfv3|qLU8E~001uRY)ftf zJZe91<+Nnu)l!)mTeuj1HIb|fIFv(w3yNxNEBXgi+%czZGc=;?M7Q`w@lygATc4KNRFPECg);O*U`~EoBnOMJZP+l z_-RY4u@n-Fu;BCfIN7}&R4!U$w|)q`rtBR5v`&t-y7*@iQeHT9?|f|r zt{+byBCu#*PxCW&Du47iUEyAK)%p|sBL6x@_*Q>re%pp2b#+5qyb!qvS%3x)rJOSn z*eaV^=El_z*zKg4-LqZr7(eHg4lVg~euB#J57E7-I&t$>7Nb#`Hp-GipToUJ2}~v1S!-%ld8q3$3-GX8BK`G7IkwJ zrEf@)P;sSZ?#AeMbrX_F2O;>YDZbsT4I8y7+F^~{^zPaB*9W`^9fCJwc$l-Ba84t0 zEwy}Ne{x;gzDRJBEHevBSWo<~xb*>5A_``)dp*y^_xk#Yz5HAI#H~-d1Z8>s1H6bQ zPqO_+vb*jauLWVH{8ks&wvfeXRv6tc zZaDRKMa-;!{FagVV{D}YGTQ0^j~)YC~R;8gx2ed#)*qi`E^Td z(ooH9x03dcumpSs?gM{HP!aL0H?^~H>$;Mtvw8`z6K`%^55%2r=KcNIdm+e4g(0&^ zgEt0<$uw&S)G=1--(od-$(ypUt5;viBB))~_U8mDC}F^QUH70@tL053HI_LerOGIXfKHCZH7>G~ep&)|&}pELHL$zbuT84t^hNhPiY08gZ^=>rC<1Sz zN2e^(m{zcOx=)NlJ-nW*`OhQS=b~Iyqb@fLKUEjv`Cg;GsRy2>?q_=Q42&}X6%;o> z+jv4A*i;|Z?HNF$w)J><<(K!6efSq4em=8CtY_T#XW&Gi($2Vge{*`yG`YYCB!3GQ zbw!-|KF)WYZNJ$zU;E1BRv}AE+|Jsr_h^7td3&)=tT5Me2tjJ4)u`F>O^sqp;DboJ zr>#aZ*{q77>s7Ew_@@#7Gp?mMhtj^oS-^P@fBy;g)f(0eJ5NuYwY_vb4X=Cj%Y)#; z507wa2o3ZhvGI*Babw5_|Cs_;4ahO*H>`l~^)dFZULL!-1XdLqK;C1S!; zP00}Zsp;ULhW;M8t1chAcT(cSP2;J(nlJf1IdCdfCaADwBH1|>tGXvkh}2vk()lbG zt&>2e8%sB1t=jPo`|a$L6Azq1 zm!|OW4--ru!-kpjI!ZZJ6ANOhwbe`>WBn(`NF7s1j& z>dWovpRTUxAoyVYp-}I1$CpZ%P&3MTEbL64bG$)xTLrr=;;o;a>rV1L*i%hSx}n<< z(JDOU`=H;(vlL*Xu54bxoN`0u50*zcV4e}t2s|Qb0FsT%*o-FcHl9^y_X8Bd?pV@mu8Ys_)OL}E$H#hUVJ;UVE!YQP;fL<8{OrqOE9G=}EfOyYqUvFLN=N$rSjekR}5bw*MreQI54xKW{Fb&W78kn3;vZQ?rg zmA=~DtHDNr;g4r?IB4b|0@=m+z6DQB^*1&388lBSq?0=v9;rLWf?(#pwY@doq)Ec^ zf(4~U59N9&$QBm6o;=xt4|1A*%TXN}uLrDZ8|0 zQqF|oCQ^fNv&5GfoTz8M=;XONOp(?jqKC5UFr`7dj7pvSjH66-fxX($Pv5ewsZ6Q` z3}e`^=||N+*7BuTiS`U|`Fpr$6!qhQ3s7;DSR)6p5PO!yVGn?_+lOqLg~9yMb*#RK z`wB_9bl3o!5B-*h`P4~Je7x6uQLKD&Fo|cW=KypN*QP;pv_E1)&aULTR{fb-j~29( zDiz#uKza`4ldzu#;+Bg~L9(bA93%Fd1X`|RZjj!p2EJx6Z)1O3{aBZtMWPMh6&IB+ z=NhfGotnVB^HH9naz2C|z5y{i11_Ym)usslsI+-;J?YiTwP{jjTZAy00bu-;uLkI7 zD?d4MH*Lnzg4Rz`PZzgZ7~_4$Qh66utSD{SjvfDEcV)SXedFdNYDIt7WOR(i8+Dru zm=`TqIgGy|CQYmXC?$THao(3A&*?WyEx)mub>g#ZAj2w~C{7zj!?HL|u)&`TfgP z&(}AL{!8S=1ZPZpy94sOL{)J3SCr*4yDo51Z~XjreLYQnpdatr(*u=qetw>;_U0Sa zWa0zx#oKuPvR2ZtP|pS7NXHC+R^k=el?<7qdo?3>s|mBu!z5CW~lGtmN%Q*N1mn@xKe= zK^$J_!e=Rt1cr#0T6IYHhq0>j;Am?So%*hW;H3ofnfmSC<2OHa-#dOPz-suMCXq^% zv|(?4SwrD);QG@|5&>IvHkZ9hGqGBnQmLoqGUq(W60nped|$edvPy*AKJn_)03_&?`%kT2s}jrSJp+3OZ$LSvH4Yul#bqF_+UVM00$ zpjN%5chQVLr3$IqA>V{a^8%#_qe?rRIe@kx#-V03ce0l+WpfQ?PU0vS3G=aDf6C{7 zyr-#bjY*S(iF%jjBfYm_xG7Bpp6?NLAt#23(O6Va-C55H#B!nT&QEDxl8%nkV(@=I(I*llV8 zCqsHO({ARfrs(~V_ut!^j7-u^3xFMoY$t0gHNZs7278pk&&VL47g!c<7OVWVa!-q~ zx|a;4Me2uXXbaD3S%h~9%DR-6*61JRi5AgE(W{SL;yYaRPyw7Y>ojjopNc7H;WK{; zqBy$Kvcpyqa@?mhPQf zGOeA5p531Y5NDOo!{F!4hcEC5mTC;r8$`)C(Pfw-M>26|Dm9!n6r&-}qBQk%JV%SwUo-me>dvBV;G% z#L33>LQ&8#AURkN*?_D<70eH4zSCmB@D3TeUvc=SN+s(H+-A{B?WUngM8blmqI3X0 zNNDg88`;nn$gR|5x11$qs-}6&idBp;)nM+tiR@dr@5Bk@fg=CYg_htv=s%DUsU$J9ABAijtqw@>Eew!@4HFo+DM3}%{_b(c+QkQwP_SlR6YM-%6(lf@rR+&pt~*i1A_dM(UA?;S7& za6+XPzF6n&%pZrzcN8tYeKW#YTzV22W-cQ=ZvECQOX;6x6ylKO+HXcI|wDZ1pJ(uz*+D_dyJLN|rZEv95b5pk_QC>7n zcELnZ2;#U#T=c7ogmnKH%eOt5EohRpxC$ycC6z_C6%akzd^`#*3rR#2mpe(pOM3T6 z@EwwANR&ZVy=G~_g9E3hr-9}Pw`xRApr*n&+=mKB5{#Xi#3PV*mL*G$b;p!Yn@C|t z5s|_=WXsDEQt>=j>Pxl$!HXjPvJ{)?-Y~&W_PC3ZTKr$-q-ZKjS~aAnIZ6Yc#<@=P z?jBNigaB>EZ1ZCQ#(WR>2g+rVPnBXNf??}=o@Y5pNA5mUk)KF`j?1ZIjIMjlNjAh@ z>!svj8#Wn}(VIzS)eV_$=I0_VZZkn|UG@l<1X>MOtlUi7no zDU}`|vTe+2(LV)|2k$=ExM70h{)(yl{z~?o7-Komj@vWnNm&we&EUbbBm?I5myKdYII+97*()qvX zk4LvXpg^Zz;o(o11w)~v;*DGIdJ5GsM)!pu%;`FMmCa9c;!xZ=eq5xm`5mdv<4nyK zVZ9Poi5>()0J@;eZAj8~2%wd+4KA2PGoJg&g9;Y)4CUi+>tO(`*|T~#XeJ@!6UM7s z4x2l|g(E-QHkGD`^lnNiAp?Btu{NFs?L%WBJP+)*3|SGW<{j%{;Q2j-de?tFWu#a7 z`y5;r)x*W$1=$Wm#N+w0&Aq-yXXz6?~SqXKsHjlb5^5)bP2`>y`KLE1TBHPPG6Cn_A=pIpRE)^nx;`e?p|ItTXdeev)`?|1vP3PQGSS zI_4;0o=!EHiKrS^-BD+!j4mM&YqW0m>s;nU2T{8L=Cb=#Gwv&Lw>8EXM zmb!eB)Ou<&z|_eT{YV1ao#b*R3*slOZB1;gZcnrcWq1CugJ>;0Y?8M#RzL*h*$Z+e z6X@X(8Q?kGq$97zT|U}XN1N6?Ab!|h;mH|a$lmIvPR;YYqomGVE1H&W+u5l{JcOhC zy;b#Vewi$0v+)_b3M?g~I^(^)h25~mI3WMaq)oE9PzfDNj0+~%uw~sx4HccCc$r}fbfkP%xR8Jf`*m%=>V+{Ty_*?w zL~;N6PT9`Zw5dg9o>vlG8{={P=fd1mc@R?)gXk zydo~LtOy(KcZm%&-R^$zQby|>DGt!&${^<<4{X?UtAYLvZ zvLJIr5)i?L2NuAmKap1XE8>E`fg^Hg`R?b~V_X2@sHztNS>vPMZyvfS^x$0Pr*9|f z^_R})5&T>sEBM|Rk2w9mv$bz3*jEBVgwGy495>m1l?6;WFBzRXGz5_*@Ua8tYc*zxeCO6Fi=FBHmBIgQ`9b#p*B{aUUi-v0L4rCZxPM4#sA+v})}4Wi zov+tUL5*brkG*hv6fg8M=l?T<5^=&$e{O|}(Ubq*Muz_;J^y90{cm9V|L0fS$%$|N YdU{+>YMQY1C@0g+yDRo literal 0 HcmV?d00001 diff --git a/blog/images/20241210-operators-2.png b/blog/images/20241210-operators-2.png new file mode 100644 index 0000000000000000000000000000000000000000..85e599c8270ef2daf9d0c133b3c180ae96d0f4dc GIT binary patch literal 263209 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%0A$~(1Gg3%_G$|z-Jgn} zP$~>2Bx!;VDG5+som4dyz^2+ht6I{a9ow3;@rt?M#`|!2aF=*92=kumjtjH%{Z3IJ zt%ep3HbcS@)-p-Zr+Oe&h&~->XG|c90e)DPqGw9IJsT_6D$#0|@dbj%!xduD8BChf zo0}Wuv+6F?>MZ56k8z_GTWu5}d+3?5QGq|B&YC40$ zDw{hA80Q@yw#qIgKW#a`+q>_SOa#CcJ(W_wbwG4Cwzr62E|~-K#-ijNiDM``1%Xdk z95ChSl=)(UtMwNfD#}56>ROM9a4xy8$c7&7}h1lRBBMn}qwrBe-&EgdaLf*Lh zir^u%&k1XYwYp8d zU5z1ASh#BdXSou}eqfyuQb82^hCVb1_s}fqLgEWvz3m+%{3S_>9rKOZ2>U|i^X?kx z82y?`5K4W;b_b~9 zP7}3K@5FapF$@(*=+QdB3G{f2BKvnz)o&fD_J)tV^^!XhbWLnyeD~spjFDLHOf(8L zguVi&RYJgI3cN9@$8?vOQY^acTJ7Fu{XWc3`e8%>hX11g4W;Bt-61{}+U3ZN7_`_= zWOuxH1mNpe>Bq{9chn@$+phe1dLjj_2*V|k+!U<3H1qOH`-*nR!HRZYtV@p|bDD~T zdghQ=)`lAfoAR&CR^F)vcVsBrs$k>X4U}n`_;PZRS&QTFPDmIh0Y?xg@Y-EhSGPPP zgKlJM*a*kLF@Y+LoPsPy;1)M=qFK}PVg|t&SL^4`&6b$V zi{lk0NnQ4P6%C>fdV}Kyi$3_RrbTO6a_e?c6X^;$1#>0F4!+b8E85!MTv?Q5QbOPG z0sF-^-)cxt8EvNN#Roe@EtlSMN*f@cmm^`QXOW|&ml%DmmHp0=Vm$$O!J!dfxbVs5 z4GNetPrtr#oES~j@ks=_Td0xHTc3%wMCtmWYbuW$I zgyP(Pq7@`boY!y8nAr^4XgjcVc+a-BX>ZlM`r!)G$`AKJY_1-ke+adcvHYA$9Ei|E z8~Ap#b}E5&sM(4B3d#WHQO6IdB7G${kZYa>*W$<9vXT)D2YFyvmG-T8G6-rIoHZ)> z-^6dwnbrKj1CZ=XYS#L?Wg4E@cZYQaQ z^ry|0pjH2p$R%C*yKp3%pHEFS^TCBbwGG&VcmgOj?qz)v+ zO1TT8V`AeIYRQao;d%sUrg%>WLJNewEk6P6wMHTF@5#tai{2+OmpW30fR}}4j;ZW< z1Q#idX76lZY}%;BvhU!-hpU-~oVSzdpqX)r#>xpy&@BF>bwx!uIGhz$#Bv87Z!}zc zdU_f}+`#0k%Hi#rd)@pM+}G#<-AkD(iD_`7yM(A&YN^D=b-r%qBnT@3TYTH!*vLxr zY7s!cjbB7WG~is2Z3;6b1{x%$-0F@84a};Trk7(?-L+l2Jyd$mnh>3sSYMKv`Dt)= z_AFUYutc!NSm;Sys%861yT(w8$<{V`gc3Y4!r>TuSdgzGyqA;*s1~r&v&}!8K7)fY zQ<2t!N++D#oyQrkbovFD!GH7cElyAanKuZRvuOB3a2PpZCe+dP4bH2U=J|UPaeeP@ z`dmyOym1stI$6wirF$Yi!;zzrRp>vVYA&ZH6rzwC>F7Le(m5&g5Q2^&YqJc(Egr)W z?hig3b$R8p%toDSg*I2iMgooVF=Q%u>;iRT55sDPT_9^9m`It@>|4mMIB> zV)I;8Ukqe^AvQ6W&*BQDZH_0H5W|R)y@xYgt*Fx!Wpr-Z0Z&NotPv_uSEZVunenxJ zM7AzbFrp;CK76%5Ry>fY_gdyY#gJqVAUerRG>T(DT=*erB{s){WKK5aBHe0-XT*Yh zhH(uFvE>gh?{hFI3W%{Y_%xSD6a*EpeT*sVBW_>JQCL`(l6%C=5~77E71eub{NhUJ2wRRH$-EXmbM@v39KKj&)$oz6mMiNDq zixsfHhz(P-3ui9R9hqRP)ANB?HRzs{PqQ{3YloJ$bx5S<44us`9$IN1=6=|l=dbH0 z%YOLe1ygs|+yzjr*~ng!fh0Z{OT{b)}tJk%pIv?94<94T8=U1`tTT+oJ7IPC3 z(+!qd_Fa_);grIT?I#uGamsj0m$&_;w5bW)PD)4gv)ULX;X^k$|^~@F(r08_9Ku3LAS0Ko=9EOVD)VC^5_U2(_tJQIP3gTBStH( z*6;SH2+Y9PW^`80!^8xkY@M8wpC3#Zs~8D*vgC;OdNNh8Mb~FF7p`Pg<5D8GuW1%`Cy6uDSUNw_T5KPEZp zfwLz7Jsh=!p^d_~a$V`P5X72b;1I$9HccWeF#S7V6uyKs^qMmK%tWPx# z5UdO-3#S~}inbaJta3`3U1*Wzf{{)@h`GgHxP^W;GY zO6fp83lex5r@QlsjcZJ9`V|-xRHy8i>g1n2DSeIKex7hI{F(%rz8#Vf$~{ceUyTN^ zY;`sc!}fRxnnT6~K{vld?x`}V*Z#F#Z@fDeRG%Sn5!w6i$SVE8dVwx8^URE&)sj()|rvt#FHBLDWgHrmM=Pj+;)*h0X7Hf*} zw_@Ys%-VDc28_dST(m*1^s`KkCUp2P>c=i}n{w%sc>zOh@h;stK_aDTI6?Q5Mkq$B z+WCX7dy3ODY{@L#Zr)0^&AR*`63US*xDmz6N1!4HQhWF?a!$UcvbxqXkzy0A%C=cn z-3oByq%u>Lv=XNU_sb%`nM1`~jN!u-=jk|LP$Z6}4zPHPi6-2C`JuOvE|wF1>tMyw zh67fD(b06Nucl)nH^ekrlA}+I*+g%uQ0NMJSO>J5g)eSrPex>M=m~@*PU4PP8s8u% z|2X2;><@CuNn83 zi_l9dh?4@_Th?(;$ZpG|WGI<&BMPae);ajEw^`pbu8$nZVt}7TD%m;7eU2Ac;JR!&M3yJTTS&UGLsqw@Y8kq_^&PDvL*P5_>Xu@;KL^D`i0 zyjffrAPzV1`MmKAL2XXO#vOMjeJiCA@p#MC>6o!t|NZf=wzLaIYpNrNecMwjsP`RT zUGSEVL){vdn`=0hwpQn^CBwVX6(x6HmRLSBtJ@M3&s-Is<4{>eR-w5)(dD-$)2)Ik zftNe6OUB1Npbw)wB-`#VvRS%aTr>&uzPWnQ8El2&1@x*XczoOv`KWnbhADc~6q61m zbQ$_)1XZJ_cT2agPeEKyJMKkQT+Zu`$0>Ml!m{U!ttCO6jg#zp<)G;g9|_|Pbj+PI=nIVeLNPMI8G0ud<48i#WYQ2vWXNviV)kk<7hURi!%13q1T9Gehl*@N)?~pyE zmPvzF_it_!@0S`>Q*F)~dDHo*_z)8txnlc!Xx(-8pD{HlTVfXiV^e%t+~%bceb+jn zI7OJGvJ#b2u9$+*!H@1C^`YlV>RXG-M5NetykSaorSKBa+hM9W4;udwzVUq z&!OGtp62Tu>}OJCu2cn2nG>=74r}4E&n-D!tQ34Z?aP=V3hP@PB8yXMzw$w>Yo>_r zyM7jKV`olq(7fq!9m9Yn5zw~Cg9C?67As-kzd}3)yF*_%I*2r_2n)ui{x)Wi_%c*~Fl3KIKIOQ*LOk03l&Jx%)8nm3&Tiy)Jj)0ZRfbX`fi zTrxsOygrBbv9k6D=f*=_A<-<+*7PMN{>qLoxayofD_ZL{i7`BKwOH;vE}5%4jZ2vq z|@Xo65t$+d&A{&TY@{2?(E zQC_7pV@qs&Xxm4%7$YNI{Lo9peVOVZ>phIm8l9Ms`)yL%_i@%4Sb$sRiTldy!g4+#b9w^sQ19ZtcLi#i# z$4=T$Dn+$J%woH=YQSqIj{6g&lAu!J}^=n8g!&+ z^l`|ey|r^%s!Nc0AV%UDXy~k1!J#RpW}NrE6G)Vzk_eZUKGpheHd&F~GlIe*9KyoT zLO_IM*<^hoPJDzO?dS3|-hvF8d1JPN(A1?BcP;9Ipf^JH+cR$gB61TeFDI%GX~Hqc z(JA9^uHQ~p^{J$bLciEAd-(LQ=Aus?RHwAk6-`iRd^=B(l2>`AsN&-V0j^s?9Yits zs?IIcn*!T>UtMpsoImBFmUjXg`&gND@D_EV8C@EASkNrG#>1tY*N0AOQht;rpoW;x z`3m(>J)!>cMvS%u^aWz@b|=sKFZ<~>{7c2u6JkyUQRof>Hdxo*mmgxqib9PT=cCcZ zlMF0pIJ*UhLyPiodACM1%TQPIv_DDqgvBH;;@QLkg!CIT>}vGn7i|+VR;_i=#W@~r z!#1s=uH=?)oJi@)Ms(g1^i}i$=EM~w(nk~?qovv%*gj~*uQ|cQHML!FiinuRfOW|GQjVqG8Gmql$Y(aSz3Z^Lj_ zU(K`j+{oAXlm)<;XJ1*VU7>js1RN9$5rk^q5Iw(2=|{xuuN7l` z6V=Y0MX`8J`vw|JPnAbKKfRn|@ntRMPV`q4wxwd7s5$sV_das%0^g(fULjujCD44H?(`MxieTA|O z!GTr=DK0xzQspEeG5+_j3anq2092OrBieMA_dj1_kXY|Z9Sa*%@jpO87pq1EM_goWdv{ervGrHB9&gV;dTTO030d^cK!U> zqosx(vUEJ?lT(UY7$oYH)aGn%lPJL!Q5YAbqy+>yc)ZC{+EJM_SS^ZSJGaBV{V{z_saf_>^})z3J{SA|eJ)Dl;$pz>E4VD5VO2dHHRJxjTp= zU1CFqE?LP&p1rX3aVKl1rXKY1a}{f0D>?3s@z)rYc6eE8nJwt3a~Ayvui7k{85GG( zfR69=-7l3Z1lB@yvxlUNqA34LezUhHIx=_ z2Yxe!)TK4Ftu^RGt&%FHsa?=ZjOE;3>Xm^Fv~T9xV(Pj;qw&?Fx=CS=nQ=hQ>AVZm z`QS|7K&KM92_>b!)Rcg+>*ei~&l?+8mQ$tYIVUHMjf#OLCGYdfMUTb1f?hUIhs92g zPF?a8Jc#v;XF$iLaxX3|T^fD&u0xABaL)NSUuW$Qq7|&~ay&L16w4m+dak7SuArH><9kCpW-g1*sAR>IeY-6lS2l?uAs4CnLpYRT3P_q*Ku!r$T^4 zc+dMgb6(WWG#hK~EG7HZ)7-0G?VDlVyf?oZCNNLO9scl>Y+a~KLuT~{&((6hN`H2O z&jO9tDR=5BU!UJ)6k`6=dhEDdruY_UMASiH2#n_ROtj~!fcRJI*hlrEV+EKlZd^E6 zsH<^(sPhUD+ z#va;Ear*U$W$h@1n&0K$iutU@5fPK!S}OK-N{pR%mw5+AK`36tpDW%Q?~_Yf<+U?M zY}dN8s*sae>7_qB4VrC_oBVhlp7uFA4`&?{hhWSky?S(PH{eW_YFFdJdX)?B+fFsG zzz_LgE*+Loc4V456sN|rz!eYu+&LUowjlTn=HryNN?>3&bq-MJH_3&_CJ2s|_2=st z>vF7>?|j53=%C^DZM>7sVO~ZtmKl`FM$Okw)gTiU0=Ka6F>Ku26$DpWPg%t@9du6S z71dKr)jL?07rl@>QP{KI+u68+s;TvS6Gd#OI;ooaP#KieO({A`Ub&E=a>iw+fsAQ9 zcr-OM6eB&vsgnfDwt_l3&Pk$s@-=+ElvoxvaM&MT7KU(Wmv<8qMRk)@xq>xMIq6^_o}ijQ%02JBM91;B?Pp`(#LKsx13cop46LNy0RjM*A@^+qpl%-syB}c-^idKl z>h)_%Sq{pgy-n=S`Jj~G$bUwL)1?x%(B<;;q>}B)Jyj!p%jdV&p5v#P%M#3I_bm-> zMvoT6Bwi1g(B?~q9df9kooy1m%YrIb!~?ut-+hP<=)pvE;|?YfMUtGJqzKW7YXj=` znehPb)*cx-1$kA%^}*e`=ajltajUS#aQDw+TI!ZsMS+x2RTqbhFAQ~c*Q@XYW}A2z zucL>{Bh+Wst`9JrTiivc7I{n!BXpYg&AD{W55EMKx^(!utc@seP}Uu+CDLi>!5zxY zcSRy&BVp4sxN)CnDiaz?wY_n#f!ko2K=rgZ^uT!bgaf0g!1o0Ob34%?Ur%@EOmQ4e zd(->z1d^ltQ3wbXRnFRX#dIhVKJRR^FJ?cgib?b2ZxBEId3@{BmBd3|1kvn4IO>O& zroz0d#@?1yV41v7N!=9A=`SPZ9a)9SViww3ObN$2W%q{y54uN}n}GzN0{ zv6bK*A0d?(Lh^Omh>jUpwt>rLN6BhmY**dI?f$evDwp$xuP} zymS1WNYUz_r?fnheC{1Hn^5rjg@=w#j}}xnW=dX zr)A4`;tF@F&W}xLAIxHX3Jm?3pFZKKqG`N-N==}{Z--IGdLGluj+fs-_OMc+zlxeg zijCzfb#ryW4D02CWRJDT}{7kvFgsSWq!kDV~prpg_eP` z*l9mc4xiOoJ#5o{RiI)2UfrU*gXErv=owlqC4w!Z?=<#ImHFOv5moV zsp6jMJx@qX=(K6LKIz65n4?H;vJK~xFeg$vjTs<^n0%cpy*2v2CwxM3ws!# zV@2dHddCiUy>QNcr=%5>cybh;CT0B5qFumxp5BE!T1`UF#pQv>s}S5@UEdhv{hQDv z?(nA`U+Z16o;Zf}5eYj~YJ~QAkK~dPW#yC@gv#*1*Y1XU*zXkbbu;d(8<-8_F=r3G z=MI46FQH(4iC$Lmuc9jJ(YInx$;O zAJBw#(1o?Os~Kp~Qv~2lJz+X@U7rpdF@_KD?J3N6{Q%Bp0G8^#+x0priRKK0F7|ssL!D3u? z&%ZaNcO;DM+S{L?ZgLBu2)*rhyT7g!{bE$*%6TU`)-ZrF3Y!C`+`$TYj)ma!xzi0a zjsd^TV1Xf?_z>Oa#>9smi5m#L2hqYI>; z{^i7I5&;HH9?i@Kf1pNS0Vz3oqEwK96-mfiH-9kJ8eb3ezC!y7#BZ+pk@%obu#ZAF zBk_JtPJsXx$20cJ+}Om*lWXG_U!QFGFI>-0B6YW%Jm*2n^!a*ycz9*#F|n9x)I2sW z6-h#!>Mi*0{g2}m3VpqS-;5m{j*m4*`}&UsWB@XMi_llnrt1&NT>AKrc6<3~i@QE&BW9*g5~JC)$928ff3t4D`JFo~o3c7HL~7du&}3uN(pWin53sd+86noJ!(PuhIW( zX4wvg#7^okK++_o#4ySitR{Yz_t={~wYrCWMAs%?U z-YtSTIeN(2kToz?-!UMrPD8g3MN=kX;u+>^Y~&4+xK?%pAVfSw@ z8Y8u9ER^b(Rl}xAOioh)x|KUxPxFf_*GZDh`+OPWwr{1xL?#{yz{$6%-ZQ5{ZR!{O zO2Ry^ye1i9ISI%Q-cOa9XPEUY@Z3DCaVuYOm3&~<)p7S-QPV=ce${+^nQ=u=Bq>eA zWubmue+)o(CnWb#{rnsLk#B!Fm<+65Ql;Bnek>;$hI`-8%&4d0#V*j##U-Mkuu?Sd0UKS zrEUd7SFm}h-q9?K6RitPif>imwcphpyk3}Dei7wmJCiu+dHX9|84?a{TEmY!+5=L{T4-TJ%@!y6exZDf6$ zI$>*w;ay`OxBE)g!XmdxT{zqIVpSoQUhL>xcKOtOUot%KkhIicQRZkouPBb6OZv{9 z>>zg6E}NS)%8SFz_5N||x>d&DAX0@om!lAY9c4BH8u6l3yoG8k3{>8S^M5Qv%?A(e z5q}8xox~(VZP2dNVdqTcFyvo=jTai4J`M~5A8gzuh^E}aHy*?f7e_$;(Ul=&+Ia6Sg>hk62z;fQ{RdP#1)sl6YMThX=*r3$D1dZ4|aDGgEQPEKS!J7Ti zY*0}^a|-M>I(k3yjyDp?_SGNH0*CPK8VJ_#MnGOWKeCqs9BneX4Km0GaX2X zo)1+UFmkPR>-QQbj-)fBvbL~_xs^^L{Wv>1Zak);$649^KyGNiK~j=VMebU76$JA= zI?@DANT=|X5-p7$B_tNYZ1Pn^uHt3WPrns~$(kyrLMrzrt>*8g)hnD(b$L>BVf#+? ze){y*Wp!+#Kd-0=`OIn+`t9q}qfTWjZD929;ihlJ>lhfu?F9Cs#- zFd_Q$J5%uf+7MmE?M9ERXXTp=>7ud^a?9!H=&~G&+%@$ZiF@bQu}A=c{YhMOuFn@5 zecCmHNc(SIU%x7P?B%us;zY`=2GmNs3d|)PP0cAPif=+3NC(JLUF%())a{0>^eS3a z>?nNQJ*?V^&8;@CW{I16fY_TX85Q~3KAP2#p^J7xakRz8**75!xy@t_Y8nmnZTVX& zV!dH>ZqdYkGFxsQ881*Ybr**TY+C1}WM|~%u36sO`~1V!Aix^^nUW!_C|-@Pj~%%! z`Zzu&h3kNzcfE9d?7OCJJYD((6AIfpQU-}HpuvfHVR52vYh?}8hgswEWTr!z5t1M$KN~MT* z08SFundN8p0yd=G)pA0i1G?zh-K9Etzornn9?kA1#^+c0T;0=ur~a@lVTvcnrg1Z? z-Bj$f=ImXp0mRkSEnoCpSqDIY8(;XmbA+v|G&|K}K7XNnu9$i8bJap+Uf^&9oWM@f zW&N)&p4i%B(5U1%+z67?)jgfUCHxHAzDpeVH2%rdDTOZ!f?0a%WrlwFdWW&){3LLp zKY4*dDfn(kkMZ#PZnK@q5ylD~4s_wFB$8csy#C=Rx~K(1ErE4(cy8X|ZsYb~iNDxo z%~`9jLI?ZmOPA|a63Ez|r>P2u0J;Fiqw~h`r$^V+Vn(<9@zaT@`G&;5)sx=84-~YQ z3=WK${o8voUZby2dX;^4TUs8R1DBtw>x=fStPyn!7|nByX2%A6KF|O1Ol$+0-oR=b zVv&Y%oC(KJ9;f+PGkYuc8mH4jIUCRUhUDbZE<|p%~j^%R7Tyb|u1$S|yv)`@TyJ>-p5=SwQz z-!$r^dz;nTZNPrl8V2LRB4Otx=eH)PQQ8?K-zq32oWj2RvJyA5mlQ@gHpU;HwN`_N zHyKgqXcg4$|6p%ZdO>E|eYLQwQAWYiSp6(nDq;Pt}y8ctJ6(8Z6Yw5^pg zb}~!VD<$1~?&91HsVknv;H*Cj)M>B@y2c-iR1q_Gb#*QH6so$L{O6MVTUKh0mv|_! zxUsiK!mvqP=6oQX_4(6-GQ$&In1$8-DrOBS&{R^!nAE*NY4|)ZtZ*!eE-7g3>Xk9P zQPX0;TNT{SQuEL@7jum@iZgspE9H%E3PaVLV4l(Zz%`;bk?DG6AN`%7lx zAwKfepl#&of1OsBkGtde(M7Mv10GeT<%n^Am=LaX8rpr2u|pm1F_=osd)m#l>T{IC z7+1E$uVZ3?nGVTeBHOkk(eqg4#_JWQHl(jB+7T~S@i`<>dhzYywEL~JB>ktJXJ-Ui zFKjhhJ=Q8F49YfV(I|f8zvhpR9#*MKqDqPnNoWs|&ZZrBTz#x9Y^)^Vzrh-NAX3I$ zUSI!_kXVUld%S=E;M%~#!GXiOUaGB10R_}JN|=@9Smp;gIv|1r3S5R9RP46k%e7IRsjOv2HfVgVd;U{R~9q%T@ZLx@cnuB?rU{1%>pw zI?L$~`}+ful009h;!^NX2|9M8?hR%~JTK%)DdN-v?Jcf#*0!%pF*kk9I!!c~diga( z%Yc`XlCrYxsV>7wH^&9W;@Koq@E)2QFW1D)bM=N>p}blNje*U*0cxGy{LczYtg23e^W7k^N$bZHze zrM41&{+O*-$)rt0T`+^T)}wE&eSbslI8Cx{y)R~cA!eL5sxEYi0(PN+*I$CyT6op6 zw&o{Zm?+9~o}(g8*sz@9Mt7syW&1c5LVZZ=hN{U-O-jWTCVmjLNpKWxGv(%aez?l- z0rm-qbowLd0Hg0cM}fYPIeRLhw@pplSvDGFa735nc0?>acuD02Cr2e3)IIqe$ypY( zR-f5#HCf%#pEv_Xo_^1_9h6GeKNf=2bp(>~fXO?cJl4t;%Eou=5L0$w+Wt_@VpA3i!4^dP_J{jIR}qN96hn;aV(OWdtF*%{Wcg;eCEI@)W<@Q(GS4M9EzS z9pb`lyOr7l?-9*cDZ2#za9j`2Hz{7GR;_J^dUuvR=c5mM%9WA_kK8o7l`n_m%$f+G z>|C^%@tK;v^pWX!Dt9NH#!HgiUA&SN*E3SevDOEkvETjLMF(WKr@Bayc%;LV*UrJf z$jDKrbTrF2GjvEC?3t&cnCS0!Tgh7gRZc-s(3j(rEaX<1id5m9&FAgyZ`9`Oh8XV% zK&GDuh8$XwsKleG<-aPKn6v?64eCJ$`x&W0oN+HorfMwnbPmQ7M;1qPu)_5Si7)D} z2Zh%fG!qp=tW6a;?AUL!tB;?e#f@IX8L_+3%&&`_1w zI*3R_lg2Mb1T+w-q#$&a#_)n4uxdti_9**UqIgWi$Z$E0A#8257ChP-#*1^PC}Wrh znY`T_hPhfAP$D1vC86CmEr3{~JBP=vhb0M;K1#OA=5;4>!&2bjineF9^Z5XlN)+Go zmG80u`R=`akybQS1J6cQxI7-$l80X196T_CGLIOSgylmk>WE`Mk)vbm0jT4^PPdoG zB|f_Fw891Y?RhO--EW-9$;s|Y_J@3!On0_x*yHl_&zTYu$ULiT2J=@W!`%5DuS50( z^bd2Fl+JURiU1qWHJKO6&C6U4R;yMtWpi9EHnXkKl@s>dRStLer;v-M7ai@(H~uF6 zC}__w=*o*B5Qt;Y_{hi;H)XOL9A7Z@^5OCP)(P*_o(=lGz?iL`L^}p_k|4k+Uy6XgR9b^bMISN{kshXj2x9j4h|LEq)rQ&i*1sW z=rc;;U(`d6jbR2)@rfd1+Plrt9hb8Jhhzi$4n6RvmY#)}Yag-rPDiH3cwPYAoTa@+=;{`P<4>Ky~)YQOH`HcgX>lg73h+fEv*jct2k+iGky zwynmt?TKx_bKlSJ!TiOrviiuyrH(|?z`A%xo6+E(wu6Lf8rAu(TNSr&huHW(sSQUDEI zC+q;Ai4#35H;Cc&aFYsdB`b?y6fSEHdYdp8D>rqqWlr`9D97th zB8m~Y`~fJU16*5)l-&g}IPC*h=Hl;BRPsTz5ubX`zDM~$7uM@>)rl`$6$!;^kJ;Mffm!1cH)NsUzE>vlf-cS6t z+#k)Ul8$H^v6u7q9J*6DSYQ)E2vQ)Rrlx-em8-MBR<)hC_VE!u>ec60&y{O-jMSel z)1@IdEyW83Bf;`L-2w~i%h$>^8h%1`2=P2JrnvqWz{+tzaBo+Ylt{w4015!*%F4wZg>gMP+D89KuL@LB-j*upS;(?}+;*k1*c8|h5K#+_kqA<5Gmp`w+ zfD~-~`rK#lsh*FiToDz$LM^irxcY>kDI`M3M|I}n?O%xdJ#ZSw{?)SYmwh5nB!Q%V zN%shOslMc%hWjE{VP(SJCrswjTUd{X%NnpQqUm@B4b#vu_je48P3oGJ-r($)(cKqq z{kImtgn=nPuf-2zcr5-Y;)8*N1ucWPyz4m41aAGoJH6L`^RN^R0MY8z>&`~pIG=^3 zhLm|4|mCDU_>XLg_JNZ^%oXwNE`GCus0t7Ch|!mnaxlr1aN@+Rd=en z_!^K_6&z0be{#}XJ+DcIe(|@1-?buVqVH4gyAq@$dX}}O!7Xt`hT{* z`8OVD0H&&@4yKxzT(bF!RQ9@}Ot$^qC{&+i(KkHuV}t`Xh$!gxXGimRnrvlbGpgVv2!h&z{;S-7GqAPvYc4%&q|Gj2srrMhL>%cx<9vyQR&)zn+Y4C zfq`?c)JnL+WlzIzyALDcH`wrc;49<0b`J2*`6qGtn@^Nehzw{4ECZb3A@qrpyM@21 zm?=$}5DA5feL+C*Z-ZUQAPp^xV)xIRDZPwjtYqH?tWXgl>nKlA2-N2jpY{@NtFvuP zDPlfL)Sm)YrYyV~g^e!u;?WgJC4I-lJ5h;+4l%)n$UW^e)x2?YZ1)tT=G18j~Osg zNuo4P?vjU_)^^a-u$SxL=1GPGy33TcQqg~Ztiw*+f8yA1HQZU?xd`9e-%DkqQ5`B< zj;;@b6@GeeWWPB+8I`T^48L=0!Ged-p~6J#xB=If6!@PZRnYMoE`$K8`Zz)ue&`jn zM^mJIs3Dh8v2X~&m9QOCafozs4O^wgs-HDS6@_s1(?5s4PANd|M_kId0ag;^wTFsK z81t!hDKO`P9PTPet;qpz^DV8o%cOvkPuT(Y}=_F1~XlXMx-OgBaildXhET0XB^Oa zc1_PrQKEs8jH>hfm?o1YodqSGHm6xg-i-VnDw)eQUg^u7i7|kdMO1j&0A?cIn^QPu ziG-8}%f)QOFYyic7Yx#!VT+kT9`uDk@UMC9 zqd~g118?Ga_I4)0>7utbSnZeX7v~^kE`&5Q3XH==!B#HUb0hCq>SQ{az5InLayuKA zd^h7OpUm3(vl@;V`u3>M6FFp;hIPX9MRA#+LU3T7Nu5e_)uHAiPW z)|16bwu0G_@ec58OS5%vM|6H{g#6|H>_tN%3iyM7U-f&BOc1uTqLb5nRdNzq+N#*E z&ePH|nN7heby7kUSTqS0)Lx=Shj|MYBK#ZT=|30it8ty5ijKHF*#E5|^n@Xig;7rP zMdTR<`tX99s}&G32^dMry*YJuhpJe=W1aX0RQ2RPC#AlcWm)MS^?2rPYdrcrS-rI>PNF`6)$9bG*E0CFd`@^U-kC&|O#QG+u`P%m^0R-%PMW~?p;Ie#-?!Jhjw^qHENGB))L#@|LZ<0fT_tGwu{96EFPD0WMG%W zGV-2!*^r=4K4I9P@yCs|=6L4UY3|AbOuEdaR7R zr%KtjO8LGDvYM%n&P@&O(Sa%Fmqf5+KlPuklPC2w+9@2l? zJVEH4AkH>Cp`$X;qkCo`BQhYa^#50Ax)Oni0r^_|ViFy@>HTK?>mGuSR?Lcdv>$v4aUm?}=9 zs_SK)Utrv%eHlg(C=}x7`!<~IdaSLBtroRTu#JD`#Z&Tau}f^c12dlRZhN`S4*`l-M*A}ocM3dyj!ww z_Lqr1O!B|0%A-%&Bx_g**|=(dNq3c>C3vCcN&tWbv=k$oXr#v z^?jZQcQIb=dt)BD#cHG`m23EJiXf(24X$kLgh+gILiHkjqd$dBMNWmphzyZu1$4r? z%jU%x5_J!vR5?xsKT%bHlnJ@7|0dTa#9a4>sLVFdOW}gb>Mw~hc`vhzH>P5fxxdp1 za|!c4Z8^2#wbyrE4qV}iaT4gX}p*iIv+mTnDF+8$ySqQX>WumQXv|& zzF#oGEySI9m#_10E^CVU`I!a{`PReO7=2;Q?Y-T^>(%;Wj!rkGsCUM(UE0N> zcZKcuZ_%CWot-!zG_fzb_`uA5b&HYZ4U*t%w`5oG`N$)e@sdip4d+4pn$i)AG>p-k@JTm|vg~c!7}ak^+jt9gpje7_X*M`0H$DHf6Qk zJfY<@?gRp9+D6g)+(e0fObxY1r8mC$&z&IcZ5hZ5-yd{lDuS9uL&>vk*>!git^N zIezD!GSBLMs0E&wzeYqkU)GE+J7e9BabW_y72mXHJKh@I5Nel!aMhn-m&#ClX*`RMr=y?BM6by3kNA2^7d9&P z#boe|;)xym2;#0sseZ-B$ioK9qLxR6+u2>y-WQQO6@G|SHCmhda4qFc&c!j^>Y+7< z7ktGrozgN>&)f#vLrsTnBq@CGWP7u$3)O6H8Ewqz$MsQd?Mym|Z4%c5%-$H3f*l=e zquF59HaJP4q&dDz>}$Ql9a}i_5VQP-Dr6Ib&OgcVB8U!_hp7t^58S(i&x}bY^1VnF zlM{CUg2rmt^Ok(Gih0oA9qC(Xdasf^xVh)-)K5nvkvk0s5qWBe&vqghk}B91SFmqH zQ8Vql3IAcPOyENz$0B>XRvtYlA&<_*Ua?Hkl0-oZovB_bQw_3jwB101&2$d4Rx$*6 zIw)YdK+9A3XJ}iY$!wbBB}j)Nv`n?Ck%!qF$6H%FGxM@xSo5v-Vw`cmPybwYfb3XT zUETbNiAj&v`1IgC6TV2pAqz}h22%}|U_QHM!6wBkE~0LiJcy=SrMfCayUJAx1-FBa z%TApjW))kKa2KoC7B@X3POZHSZ|}hTt=n6Kx%wtTcTv^>AF}G9d}PRwL9+ z9z8iCnR|4Ad$77FdNNxTL|)pBzqZLwLwW&(t;K9SRRY;*0LExGcJ`GRt!an>foJd6 zT_-?)`?eTy;rX;UA)CvFTani)x7V{HO#*O}sgWs(q;IE-5@*@g=VcWX#j#PXjxwbl zyFN|)a!gpXOE7OA%DgR4E(+Uut7yTfgqbS0}TgO6;ZgqAT_?HYvxPWMrZ zo`UwGVuOf7Hd~#*D#o=GVhy|RF-44xjV<+TwMnz}!sp`!^o8)z*NZ6ZQ-G#TOMm<8 z_2`08P;_X>`}gJDH^TRA!ZCxI^t2&jt66m|pSL@9Y#=59Hy()L`7?6;$hJCv=G5?Y zkqlH%C=-f$qg85V$%!g-Od=&uCNwoHc!PneiiU>8 za(6PbtAwV8M!AAq8s>;Rq_zAB3a1OQ;1DPWX_}N);;Mc2K@i9vg?TW6&Ix3jhQS~Y z?#-Be*zGY48CVX4@lFWw3}1)sj(t7be6MKvcz>xd$pGTBHaGNnpf3jL*l*kZ8^yDDF1e*c9vRf zGl(kQ5}7LSeiego4SAmFvQc9#s2;30QJ3e6k2DF4f?5uE*S|Yv^Ea(f@3K!+USfX{>zEnRIu*3F3bBZAY3G zSp$XvJt-Qzk6Qspi}rxju_gQM=+705FYq5=FZIx%I!Ih0eC;O8A6FR?d8QP2lJp0q zSyt8*;(k2Fe3_(K&VReT}*qzBX-)$%z7Ku7l<8#xOfC zzgliadFd=ovr`aQ#7z7t-?itJNqPSp6OAN}En{=2kaD$KUss5CIl%XJJG&sBeqk+3 zhxX6-z!*i(?#}-qp~G`f+5XucT@Hn!S8o6Fog`dHZ#_;hp>5Q(Ox|DpSK~JnM4{*! z2m-u>!Ia=1nKj|81BiiU>-4M{F;@qh?`NB1U9Y2EOH5&`19_yW3ufBb7r(ubN2XUk zW*FXPn(fi}$Nlb^NdbfK)Vf zG&HVp#lE(Hg41oJ=n^LLoI@KuA12Ny#Or$i`(^^j8mjG0KV%TY!Ni+p22R<1Try8K zw@*quUYlMI%ORN7EJ(M&Nj}r}U4eH}8Jpf#=8Or^b&jqlS(=0!oM>ER;$(Fmd`Kh= ze3&#KB#;{o@+O49eaM;iQqLrsC|Z^qAK~2S$&fxk1h)bovFBnWk*0z|kZm`~%%9N9 zmqMaeaSq8ZgX7EhvDsA5+dRSs{0NINDsdha=AL9A_hRb=a9w(fK;rz9r9LeGNWaF%agI$^dETo)FWe}9N&H*DwW^kV zP|I24>X>n|w#iqonN+erDbKG50=Anvd3Qhj`dkI3^Mw3<|v4wQ@5GuiUpY51Eu$A+3ju8{V08C1+3tyWzW|fUh9IU`O4(d2`etWdcMbd-GhjyO z_V)`o=I7@MD_cQcH9fBeb-))4OAX{G;T@9>Ty~o{1{UB}sS9cms;*T*x7 zxfJG9c`s9`U>0})3YenvMGmIq2L?G@6VG*B_P~MU-}mHCArv7U(JUgn0dCJwAwd}l z-Oc$@G}op)D}i=n&12^bb1+3tX;#^_Sas&mP71tIQc@D!R%H^nT>aikP%9lC$>q9Z z$wer4*p2d{V)vVj)lit_U>XJ^cS zPw1m@2ATvXxQA?QeG_RN9DoTq)= z_x;zs$R-5x2ZJzk-*2iC{kusfi z)YZ>=#ZAW`w=H#F(qvWeYX6U|)gceFm8TFxN`lg=&D=T9jVxdY*QX;nM*m?SrE=J2 zDJ#6q3cSU$-N4@%3xV)m*7VJlmisjIWaP3uuO%(-yRjaQMxUho|09HL!jIK z)WBiw5=%8$6>XH>xJYWD*LnVHuV?}8`ZvqT>0x-M@BCQ()uT8fI#z|73` z5BfXKJdGO`>J(sd7`C*2LyFScFQ$qV3K&Zl+g*56IMeK!viMXH@iV7es>8LyH6t!K-0&Z9gH>VYe-O zW7&0Y301*(e(~{saV8Q31Grxro0!HcyT`(T)UE&2{A=45dnhJJ7I)M8#ahHP2%jen z_E?5Tt^9fN<8@MH>IZIRA@#_Y?A9p?3V5Ff7a!9Gq-S;^9%wVsRH2h zk+p1B{FHlUVwGuTIf3MLD6#+#ReQ zi#oVKu6Z8xNjN^oZ`Odk;UP=yH#Z$yc$I+#wZls6?MJQa#%61}UCk62N`rUL*_pYm z8f`YnFm*~vI3PF0u;)S z01>PyiFsj#K+>#`P<*R*FpjTw+WhKH4nbf+HW^q-C*|BlUb++FMSnLVuxsTb&ZLb{ zQ}Yeq)#A}JQ{U?e{M=?+kw*^ieaM^2_*dkAt<{H}xtMF~ni$rE9r`b-g*2oWk%8D(OZ85$rCT+YPM4@)s2 zRnn2aE-5Y|6Xe}G%e51KSb^5cmsgsmN=uF zxWAQ=mf^%C?C$OS{_`z{?_Ny_!;4>oHiEyZ<-S$o?v$(0_<~?|*?ww5>2vfV0*~`K zGro4mA3B%p?s1G}xk}gr6M6zs9iV-0wggX0w;gS$k%}CpAA5!?q#{Q6%+=P^57NjJ z5qX>*&NMU6jKXEqCoaWzv);rI3#=3}qJ%t2!RGg{>_?az92Syq~jk4$ox6e*NGunc@3uaG%F6;dU>q>)))T_8zQS_lP3%E(Y|4 zhnow$xb%1GFB(PTZv*0>f-us_w)pQ#vjN z^%QxL`)UtqUt20I;84R=qaK@-+;I_^*V zil>7vumEnP&sbJ}IIPwTq{OB?ZlV^gZ|**)8qrSRyu=>`I=+zW#J_>d%p#e~i5c;WNR+xD-C(R*jYvMA(cT(%2a$s5CgC)#M*n6avKp;&W|LP8J+#j ztOYn0w3tl`uhLf%YTWR(Ij22E&@*E)w1_s!Y4JL5=^cz^Y;U(2=gC3BSUxc=p9%c( zZ}Ix7pq244-1DjhT;5MEI5yVAIUPZ&Kd%dZKul=Xdi_%Pwna?C=sb!rA^Y3fg?%|E zqwE#s4><32+5!o?n$&HVd_SEm2W5HnP7_=TDo_v#?f|>;4bwv|%Xu1!LT9h23D04UM@9X;Mq)rl zclBNc5(L$sXU!Dj<9x6T8B6+ZQM+##-gg+v|3olY8fL|vDUfWRcRc94FcF`QWV;_z z^9&rj(+#xkIz|49)QnVrH0=!1=F2DDX;Sx zZGxVTrlsId`5bM>>GffK9P?A;nH0*#^FDE`ShmBjo%^8^!oMqZrcHd%f){uYj*d$M zL2O+qWyn<^d_c{`FhPbR!9R^7>mMc$j=ZgZuah)wn848`g$@7d{h`bDsQTc?RLH~d zwQBc6qN(R`WpYX?EbOxY{;iKejJeQ$DMAIW!Pbi{i&U>z!;)>I|837o^2Ep zjPY8ZRlswty?-l`=hhdM?mn#HJ%4-n8wteDf^K(eyxp;|5n66~CNBL`(yp`si>LTA z7XFjwvUoB^LH$7v<>sV=h&Uxm+0~}e3EE^jFrCtt5e{>#X%R2O{6pur04|6^1!XtUC2WbgUfU|S(|W4$%RRoz68Chvr$d^Zjhz^kDGC`Ok)VWFtnoHk z52q%wKPihAUYY>`D*?sMR7M#e7yd8;$C};|j*-e^FX8)?w~SxtuYCsH@!GO!^|@Z* z+3b&}>?VB19{$BRNysXArj}{MPu=N_TeV+paj6Alz9;DHau&UFo~;3T176H`2cn#> zXz=Odzx>xE%vW6R8r*pkP7(Pfr(3?u35&LaJmyrM_PTzO?1u8`^yDK z*K=8y1-dv@7Zy-0y&sX>Qxs6}O_&Ro9M5p|iW|QU>jSH7`DY48S7dsi6CRI_H>F6C zG59+}?U}a0i-*Uvt)0Zo&A9jFdC)JjifVQ=?>QBiGF=VL`0IreUyo=GjIoMXURpAu zZ@Hc6Ys_rhadVrFfguBcsM0CtGhw>5Za3S%QFpR1P?A9G7!RkVt(dg|q@Y(<%dz1& zp#4E|Pp&fQDK(LZNXtJ?qIgOGGE5*q`{{0!2m5?I* z*XJz06?=I2HN*cQ>+X@}cvVLW-F6?d%9*piYh5bp)RbZE!a`7=q&w2iKG!52fg6<# zJ*?qOjZ_2qN^BESF1-BBIo-y+Q25D;rAz}MOgscSlp=Y?)jSB!d-eFKzE|H+3Gswa zl^SWX0ho!nv3w&VBjfbTS-A0IBoNGM;U*LOoLaM#Gx6npMA_*3K$8D{ew@$9-yJK<(af6!4cQA>) zrl%mS##LWx+MXW?;of$2-%y5=f@N&&srmCGu_LLPNUt2F`^{BOT}TuCgb@=N1dCg> zz`+;M%O3&uwXdmohpOVsU&ZiofUJlzt+!j~#4mqKJ*yr)%EWGdETo*%+sYK&ucp+sKy_^}hy{Nss*rK~fY#8)(lNZn%V4$^W zuv6>8#X-qZE0>yiW@#O79y+Fk7{+I>T{>+{28ISri;q|T(#?2 zxh%|t*!U5h7-{9={rqC(HKmQHcqVshThF;C1jWwrNBcl;;i=KnNeG(v6{gR0br&~_ zLj7%f<6Z;dQ9Jz%pCa|c-J)(2`+0)bcZhm}e12Wy-s$C|vCSWkg#WqOBt;3bo>0Y4 zd5{H?AnF&+cZc%J;lgYgQtF7S*sBw1-RC&;pD0PnZIR9yHK;|lx$=d0*Hjqg@diA? zXckK?b$K}FBQ#Yqa2G`<2muC?%lhOv`!z*8s~QF(F{gj~gia)$pY|M44y)D7PFFW= zHN-r0C@q&$AoV944nIsU_&$+wmEK&XrDh@|-#biDX7)%@62}I{8YKW=$pQcrho*5+ zOx;+>l8uqH{*2vA6j;?Cec4fu>VVi!ei9Jy-z11&VD^7?@@PlnnwY+CK9uUp%U+BH zr>3=aw5hhSXlq|}RC%;^xxba-1W#0?H#L)kS=H6Ynn%$B8|zFBO(*r4tI`6{h}L&3 zwj4~DAAVWG>yszvw5^7Vm2{J-91LCNq^~Z{RzSB$t1Gf?n2#kxHF?}oN}E-Xc$xb( zo(yG=S?eKEi~y9=%~V)sxik$&7>6p0{(JhyEyWYl4J7IIJ|+AucH84lM8B;p-Nn=!erPOP z_O8^IFu-rm>DUJMI}33U)QcvqvWx}$Z>qs6QIcSMyWZ&cxB!ncG#?U*c*WL?Tm{JZ zZ*NReogw+P1s>Lb`_w!yIW!2Ezqa^(A!tJ9HE7i+X>r()A!!_fzd?Rt(4PJt`Ww-q zR;KZ|z2*AKZuF$~mr4I&zF%1lz?XBR+^9zWA7+$Hm>d{*r!DdB?pgdE_q5OvZnxVkuf`73|ry{Tjon7NBZV>Cex39ItOdEEmkBK z+T6uCC^Ff|by_rj80H%hM+dS$$6m;@-ETU_Kf_6qFooiWB5{2csQ(7EegelZ@eQ1b z&ZUd)vEE_<`wnmufBkx&25;l~C<_*TC@Mv~_X?^#^sv}4+DV+i%Q4H-;AEmr?&~Bs zUi&VAaG<>A58102_LyQMJP4%iKKG3oa=1>n6>3UVdbbZ_o2PK)mJ8h*x+ZJ^&a6n@ zM>1nouM>^29~&p$R}tBSb20L089y}REXmXLvEK#f8Nw;Y)#%~LdU>yIcLv<^z9!ig zcvVbxx^=_AxH$X%6%TS`+1C4$_&0)^;jn%Nm2{%CD6wBlO+#q@80TMW;XsgWJu?$q zm*}K{WasEElT zk{Wf;q&+yRPk|e#-pg80A2W15Ilg{guSP)4Yve>fS00kxc4H63phlKq)PHT?t@eM_o&hakx9YkZdrUDmy0 zc0BS;Bg(up3Q%K5>`*e^J&U`fXtjcWhl;~(MCoCxRWtY0_hf)O6CGS*gMNz>?B38` zxly<-6PoY|B7s=x-CV&dV$r1}lI@ij0+GV%h z1TrtA>H>BtLoGd$WUVlnl6T=w&N2*Y?8f}vZt!(z$)s8MgE>5AT%fx|Ytn$Ct`}Y> zTBbE$`aJXUOA=LaYN`}bwuZ&NtyNB0ytFUXzlh6Yn-?_w`-gpr^#FK704n`_MEzI6 z1#^-SIjfGrZUSs%>y37V0g(^LEZ(`QiCP#pFW2X;F*^0udWVnRnDO%qhD?B0U~ zd+WZ|kOS_gc<0=uqbDu5XQ}ox;Pei8%X*tj3!STx;0gWb?WBeAb&vZ(h5_>c+J$`Z zGye@J|M^h}_=FGw-?sg&4fsINqSAimw2;Boq8vh~avsR$b)8`I z?WQpZe70`)Gb}9!^#=SQgP+6`Onb)l;1(#0g0#Z82K-&|znAafi`m=eB(ZZ_t=2RK zBYYP5`(Ft0&(Al=gcO0ABbb^jV9;M?3Xf8rX$9<)6+_8>$@rq48^Z=L)^RqQX6MYW zem3F><&tze0`GV9{qpU>o~@w=jr6MQ^YFCxg?0YCSMjkg4`G(kP!X86C)gZE=65uA|Zf1VOSY9j&lT+!utdEMSTc!S3Bvbl7FulGBwuQEUsq807=r>QK2MlV57!2xZxIKVC7Tq`Lpx2cGEiR*zd;FBI zyhMUii-VzVxRZjGEC2hNU_2xe^W97L({db~SXZOAgTp8S2tI<|z4UlepObdu% z=o7{*i-$r47|`RK<72s6h>Lfc4$qM9a{&+bpYQKm@K+lRjC%IR-vB|jO<+~>*hFwl z=l>~@&=B?iJH;t>$T^}hJ*i=W z(+F$RxPdv>80nQ0(GB3+1CDBqjXV1-4&)*^bLuHUwcI2N^qYS3+)|eeo8Oben&NCts-+`*8$a=ky=Mobyz zrsR^e)Tb4g&IJV3PfSE7cFel3`-?^7NOfC#L&QM!d2PHT1J}VA z%PlQJ_dT=xlg0ec!#Qt}i5|$Hjj0e4*XU`}C7Lk4IY2$Q#fri5&V=sr^M7_G#_UGSCDp%5ZQXl_=EYbc5=>LrDOmAldmjPVPQr=0rFC ziy7|!J_ejR`N8=6;shT#j4P{y_v?wZ5|6a%jV1~5FV=TW#?n$Xo6YQ7)P`Ih5D@vf zP<(bY$9P&trEgkB^1s4isF;kWo!uMU*1$zHojmWa*(|rBGhiGC#JF`G7Vz_YjX~%J_cz#yvtq4-H4xPUcTcTAE{dR4?|BJl;<0LZ3 zkO=`ctDWQM)$Usu?O-`!?xE5?7osZgx=iTE+K9u7-HfyEF_2K(z-TsNxKH}HO?KAP)37I+|0cQr;~tEV_vjF} z$3(sKNQvE=E@5R2m1GjaavYc%(?VnGw;M-Wl}}GyPvf>G14#@NOqd5QorP$Y`~)#U zOY5gxz-#p4;Us_lD9tjx}=K`mdRGu|Htpa#}*OidC|Xm+|+}h*+SFw zrdDn=xv^f^)U^0}7xZ03Tg}8?7!?R!tiP!mRvuZePEswDNliNmacZ}{&{2#|n@u$4 z2<0Y(w#$?Vp6k}R|L+3fe{8qp0-$2r>@bZvp~C1lps%V80Wb@ED{Gjlj^ywKfyT2r z0_UNR!aHbx+X-uoiBVBG8+}Ien#gDo@$@>YxB2%KXLTT1@l z==3kTRmY$x7<2*;5EldHRh!+3D1E>C6mYg(A!b!h*l^fx_&a>A$6%P%iVfu=4r>{L zB0g|-a%xSjGZosB8|+_sq99iu(;RsSP4uLEb)o&gfccMuqki@qPiGHH9!#QJ#oPQ@ z^_oaicRKKG({p{Hj?8~o{M?V<;N z#=f%^qA7aE6J_&GwK;bi=uq$OYC*s1AgUNlP(uB)PS?}n{pQY9qsgPCvig6p($6dA z@9Wb|P?dFWzQnhM0H(*!RHs0!luLvhuM|oyw`(I?t`OpeD%&dBBF>5?4grMM%3A#! zlp9b*X1jiGh4+jRq=Eqo5M_Th``uB#Gd7*W_*iKX&d=Oy4Neu~`^axV6B$U0-X1_& z6~H!i9c4_q9*lsyHV}m?Hjd3=+DwW6LB;_meLB`>!Im-41M?-thFAOW^wgn$;!pGY zRN91oB;gQIcsE>#E*FysJO|e&l|4z!qvU-1m?MSP!#%3u=retPb}M*)2jS=f`ywEAbPa0duRQ&m#$wjUq@?uT-Uz-q9gj>) z{4IxALt347)jZBqe9JX^k^I;=OwJ&UD+CMQUaj4HwWJGvpSwO#{YjrFV2*W%xbZwy zGhhI_|D<_4?4Ut~cf=*EhQ<5{*Pd26CD}ceyf8_&c#3#ZPvO#a9{Ttw<^8Oz{$r*^ zqZF!;mCKy9Xc5)O)v_@}y|&=8^DBiCb>y$sW0)=DyEtPx*cN&cy5;Nk>&0-gxMG&; zJ6*Ko@Sk|P?Hkt|C0(nxT1M0LNEOM;6kbxL11{$_e&RR7EN;rFS`L zAC(}Jy9x&Dup08tza%M?XTfn%Mr!K&2fauID5 z*W9T?vx6!n>F3^o`1}>1_KK*-FuC{&hElvmTvt`6sLolr%GOmV>9r+`tgUj2#pJvD z_Cr|-%;$MXE_QEmM%n5DEOW1X<*El#tq*m|lB)3t?o(13EOUYML8w}D(lgVWALWxh z0QV2!dhP3z#Po+D*fp%?0`UMVeKv|KdQxi|!1Hd5z`0G=oAbjygU>MEnRdRtmo``m%;=mVpIri3DJ8V0RY00q~SbbAnAHugWjw{CPc)WJd zbf_^CraohB%IY$g)5x6pFXCG?F1E+&3Yum{R;35AkTZ4GALtg62PnOe(Sa17fzzoox(gW(C=bH92pUT$>2y_XatVaBvL2>GvRBhwtUw+%B5qqzpOotKSk3ykoz5fFEdKFQx+ z_R05yhORwf;f%=etsC;!bz;j~3&tUdAYCh)#>WgHDwhh-JF# zSwbQ9nDG#fcMDDZF@YU?on-3y=kjhZ*u!14Bvr~Y0krkOH>S97gf0c<@}+96yvQ$g zog}2IIB$G~?~jbVgYEb8u2{IMS0Zp=o$R}UO$+H6aVof-`P~b0tSim*nF_SZW$YSv zU@9_mf?NI%i;*kyvc(wXnexk~1d8Z6GoYL6J&o71rTUbQ^0!SpyRlkzwnd~7sbXx4 zJp$~-r?ik_5&2nbKV>ni?q`d zmX4ORR3blugaLVkDMq9x5N5}Z$x`&13z%^ zV|d;sb+IplufJLF64%gAs-q6HZ@Ke{4@+~8L7y=^u28x*CpJ81-6oKd5} zDWbApYk=diG z@R-^y+V{df^K$hXf7m~3^8zT<959&_d%x_Cy9it>rZhF-Wpz7L&BO7+(e*`bEK<^3fi9(Cy*z8rl2c=xL6#eAfc16I# zI*e}#)H)0-yFhn%GG}s%mKtNa0z!#n{VJ5Huw$J@0{UQg&ciSy!$96H9{t^u_;wpW ztcOfu;kgZ`Ye%C}j{I{%u^`BqIJSe83UCOHJKiwBe88d79}T(ke$y4BE<#8vb{| z_$<`L#`P-Agad2>ZTQluR`{ziA%Ykmb^?(c$4r{RFKLCESzPlsgGJYocRmj9c%?5%MG5bmBJxWCwRK%x5N{nCvGdT1JYgrtbXNrk&x@Va%CDYr z=j0H~ia`mkaoI*8>Ye=IAtPo6QyCNmt5!PB0(54-O^}HeiT$)|h!BO7{#y&cAcz#& zKtXO!#sjqkQJ1XGGN_zHf&gy&HY2~EH=BWTqUaQY33@OybBJJ{TtpF@Ov~EDFT+qQ zX*lI_>9^V-v1!kkIsRtQKI%>N8xSV#3tutZs2Ha9o*dUc+W$w^HwMSG_DM!VA*!x9AC;nx?&NY0th*%u5 zyr4(R`h?1C5wGul)|>Lk^AjIX@``E#kTq29EzMyfqPo<^v)%aN8&8g-yxyMdHAhUU z_}}V9jY%byi7P*~peahZ2tJ(N%I*Y^5GSfJ&$NGRSHUY?=dXrbB9m5VN&7$^wu}-D zRn2dQxP<{O*l8H9-L0l+01C0kL`@xUg|}-|R;SjRW64hU4M)UhCnF1WM_@a@&O*I_R_u|jDn`8(<*W=vsgzGkucb6|`;xp>YapuZj z)MlDB8;~RCr@Q0%lj^dJUXVkuTvlMvCS68*p86P9qNC8BFw}p2+BPeSUPN_-Pn2+| zl!Sk}7VU)_z2T6inAb2)C@T4qr)%ln;4Zk2$p&!;4i-5+us5zNkBfeU{oQ}QCklIk zNI}6&SEL>LB`Bf!;ZcoLzGa3_((EbOJp5d`x+!6Bdmr@b%#$yXqL5GWb5(M0)jC++ zyu8CVNcKWa%3xbwq!yz$HQ>~M=)y7odISKnw%TAI_F$w-C$ED4upD9cPwrat24n;m z{k7GZ_~Wyq{>S9>F)RzA^@~w+jaT5!tjES_e@Cv($!oe`=pplip(KuEu-(^$;i<$x z-x27tt2i4U&r9+hja{m2hg!`Bv&0H!VUDc5-isE(uk|TnW_c1w%xUKjK4pj2EMy@v z5-y7SiMV)ndhKkN;+XT+++NfCPX#XdW!}GJmUO1I znzJK0%980#o}Xbsd0)nyykm$qjA!AGzk2iYCGD98Xhw3_BWRSSJDpa$UbQCQ#fbr? zmLJUyvM{|S;vpLMvB-xd1a>!t=PuVZnyN( ztP7ubzvwh1{PY0<;Hxf?VR0Q6#0TIJ40h}d%O<8Mama2bg{NKIq_CFajZjV(cQBvQ z>OM*_%@3g@rWjnt4ta!Ah@-x64waBT{ILD{a~BOcsO#r0jvUt`IUmUdS((7Xk)C0cL{ve)|c zca@RUA6ksc^Z3b>YrD2O!sM?3FXKs9rgxq`WI6l6Ww}&7MaL@Fc$U4_Ws0f7JiITW zuFX8n$|0!RQC=acZ1#pXc^lRBDrXFA`V#w!PbJkss&z}9I+h`hr8ICps;mFzV^-SeWbm~Ct z)Nf&-T`+!^)CfzPkiL{+F6>~BGbYrE6;Y&FWr zf~k4mA#Pkk{fbukKq1TABjUw6cK#Hg!X2TN&D@AasrG6c9$y->NI-y733PW&MWpC# z!cAo(8BbIw<_{NGbs|(F% z$4Q3>i=NqBLFZ*u>JFHtpEl8Z^DR4;IZaFIFau>-Pj6)^2eR5tPKVcu9^+Xsq4z5>jBJ%u z7~tVqlb`pV&Nf;(?Q}Afer9qzi)ltt2L06XqRRXBQ+5|7ue=z~^kQt2Ru+9svWHX9 z2kuKQIHv;EEoWNTu3htbtrS@3K;S?&k+-Ylv=3GHhw0Gr*iIhEk5IS%v;H(R@{H(_ zH*Hr38|lyjl~iF-6C#li^rZ=uV%_~sDwZ?rR+GA8m*{S58s2WuJxShM$)Q$@bp?a4 zYKi#`KDWnpv%yhzq$#N@sKzsa&X8fp4%Mj|d@2lA>e06$8EwKPLTn(VX`nREIL z^)fgU73^n>SlR1y#7#Pd2!YBrQfG5wjF?B<2LZ7h{&2Q=UEI=F{rJ?Uxwp8yIVxPc{L%h=0J5>l{pjy z&mvgH51OB|xQiY2U#}RN>l8fDY!$^I0+|(JI*AA(MPj^HWEBWI^pnWAtsbdAJWvlB zewC9jO0`Kp4cN!kb|Q2?VHzFY{c%BM_*5=G{?o0vfWKl96YpFJlc)@fL$X_yjY6qY zk*beSv_TS(B*`rybM*^t*#zK8SZ}cU1tfBNRFzpl_J-+Ej1#jGR-ry^Z=3vnu0Cy= zFny_DsU76Q`PQH&xNyVI8u1~;n)ok%ogT_eNlTK;c6cTPoS&B(t8>~1F?AO^)ZADp z;;IS{YOX>NO1bp(#tbOnP|nQLfUcKG7knwkAlC>G&4a|lsYm}+WMk5;#RxSEMI-6? zL&SU6I0tQLT+y6IYsYtGWEFk&Svaoi2Pyx#BT3~_TJ?&7zE_3p)i7dGkPZmJGYN@Z z`N@(Za|?u9LQ}!ml^&Kfs5B~~UEv}rc zj3>`i_*Eo72RB5>{ze!X@{a5_>VUf=@t%5DzPLG6UKGWPIeV`UWg;9$9C`S{ZK;0! zF|*%1{|obAoPM6n7rAmXT-q9s<_T6ry{vvmO(ikBZ$(fAmmq;0CYaLIj*^_FjGF7` z@cU9n3#@?_m7BW@?UYZdw*HlOrQ8EI~kOYYOn^janF zTZxKgnYQ$091Pw2BIG)V3Q$gH-d*MgeE6IUxnD;2@Y4x#&u*>Lb*B!zK^%E1GEy#Giu!xc@!HB<4nZd%GwR90j zmVup_r@+jNeJP>j47vJCI&&Fq^k0p4JG0w1E&x&&#JO(W63B!X~|rK3F5*Z(`Ss zDV6AGIJP)evfW6f6+v*Kx$bevp~G{{HcJFUuc{IXQiuzHJZz|PlSm+x%#aZ(CtMQ%Z;VMrv)fq;)cq{isuruCN++D4<3Yhey!J86f7 zRK~=Fl<2yq&7b+G_H;D52g}Xn>DDg+HJtoUrQ?Q13u}o#iFnS+uOgagZR%O9*F5eI z@}s{MT|CvN&&YdoP45F1>rCMFvV#J*eo#{2Keie!5^40cGqjvUXT@6d5Lpj<_o0?w z9u#4b^&ai`)T4*jhX~}aClwtht$ycOVI~-$X&Yv{Rx*l5kko$PzL9jZHBQ2mwj;H! zbu+y#Q~34#xnRn8Q44#)^E7B#Y%J*Twhwk$VoOietyf>A1oD1hmKAQUbttl!j~nS( zf7LD5Dzzo_){NsIf9~nFwJe=0s;Xay-?Uz5Sr2y`VbMTlLzCA_Ll;SZRW2q4Lkx1L zC|X4qW6DlQWD26&s-Z2H7<=P7Z6te1n*JBwSi{4p;qOt4%%appB=w1zTv z;ovE=k?3n&EzS37;EXVZ@w_QiVxqkL?kRDC?W&F^+zkFr+sQKtD*(hM$eKM1NZu;r zdFy5#F%YQe#O-v_mp=wo1dJvInE>fS^P zbs_KOj}W>OP2sdJdMT0|#$f>Le7UbL&OF+i$Uetp&?3(#7#45Bc(w6H(qm3sd)AeD({+eT7LTJadPF7>Czmdf9qh7V(xFdQK@cl>{#4Ejb^~ z?6U!TMFd5?kI!1u2hF#VMXefLlcqqV%Ci5|%({F71ooUh$vBJ=B~5)4q%}O}U%b-e z+t!dD34BZoveaN5l}BfR1f-jC(6?kCL8BEZ+Nq-%x6}=Wd6>$*hS&*@-D(X_fEwaz zi$w8UeQz=zV>+v!k#nwg`B}bl-}$b6zsJ$3SQdcKXzJgmrAwVHl(56rFg15Kd|2jQ zpR%tfN~O*?@*?t(s(R6Xk54z_GNX`z5}DF#R1gwhEbwmE678Zdm+^8CQcQ}BjZApG zgafQcpAfq4px&RDWbeOS67g?D@wp*(-IVv7EY^u{opi2+q7EmHAz##MA7)DN2({#7bR9nit`?sZe$W5d6_lL|yhMu}I6j}LtEe7b>~KCa6DtYbPyBe!ms zdQy5#3a*N&F+(j5-7Zq2L%a3!@@}TYgH>M#8~4a3l}G&D4omrZI7+mzBtP*?SYiKCh|j*!ygz2UvOklJD#UZ`=6L{aGpD{!L({{?w^7v zkLJ29dwzP>kvAgz8kC|RJo&g`d0A7YYKK0O_!AzE5#vbV;G%DZkuhJ&2Hvl#mFsCT zjo!l5=E3P>5LVkpAX;&L2kJGZKttK@TPvdg@z04;%BJn;JoLa*8$mL<<3|j=wmApu z9%<@btnk zOOpblRUw0%Nt*? zR$82eM3j%S6+>j-H=CfKGW5qO=2!yp2iZ&pLEOg`b%oiI@omte5pn<(PT0!va0Ec1 zomPvLfu8vOh81DHd?PAQU>|aKq%@2dxwyI z;g2{HjZ)HfqZ=)mL}pItjIUMP%I71+ zMn?d4$ZR0Q_*}%3`V7x=YoDtGNZ<9L$##CAHr)NbfRtvKn1FcmEyN5`@^vKK@D$*T zb1JuaQV`OtPmZ_+hlpT0+`02i#WD=4QRtT$#`_|EY-V}YX=A{tiaJ?R0YwT z0dxvH>WQMLLlHzTCH|qQ2=vyPf9MzxM-%}Qqv_=6stWugr-6nPf0uCAwxs0ZHDdC! zIE=AT0uyCSWP&wXax!Qd-wkMNU&@dpoiBY0b=H6VQ4TD`AO|-$H^DmCxvF&N_ZJFh zBj`m+a3N_sLYBVm2bV=k$onfr37Q^D(i)2rSiUj^8QBW@KmNs67z#AAQ;p84`SBVn zWG!u;8RixSzz$b!33W)D3rL$gVkcxuPAZ#LBs%xs4&3Jxu8nnJy(nONX8r@t^Em;S zsy;dR*s`{!qsUF!^z|$aH+9kfLbGVn6R(O+?VN`&HR^Dfv`0uoV<A2!&ws1K&S=O#J8q|N76ruJ0wnw8~RcnPnr^H*O67^Qb;JYYTK zAdb3?m*M+T^WWM|0r7o@{zI4=^AFA0DA5GoQx${Sv)u56c&Y?m2F7R;&wJ^g0!tzS z!(-n+$DqJ-qLCgGL-%z2E{;Hb;)Q?YPuV5Iv*HR~! z#`*6r#g~GKahu7J8JBv~=nu|_UP&@_u%$LYyk355W}u;?Z>`O+?Z+XVgwOW&+O1x{ zFl%xd(r+?faQes;()4M}xo&^d4oHLl5AB(gQn8$^zdr@aeNt;AoKRgPHTC}I9e(fC zO`V6zyype}oz+KGtW~1<{Ywa6bm%n8)uZ1-l5a34$Pk0d(0UeG%s|dRzPg25U{0ZE z;sCNb6Y)HzZMnguH679GIjX6Bs{KK90v(mH?V=}1)H0QXmWls*w77MjXWzqlN3ndh zh-%(ImzqWSa!(bp~{p7baR_lB9j5`MV$DrMbq6KER|7Oo1AFGRTJ z=exoNS?&@1Mo)Das2NM!8@Q{);m{K9t!_hG{PddM#C3nS9lWBHl%)MmywGIvwnO>D zST`RkNsxa@6)TEn+aH@4A+qqTgh1^#+$&JMC?Gb5!I(vzF1N&W8PTVtfR5I zD+r?{IqeenB8Q-x_)TGa2Vr6&NCvkkcIv!TJ0!;+LkURH?9aJkF%$!fn1zU!b3`l@ za2v$$V@LN0>&qjNtE@FFzW=rgT#r*u!_xOMe zB}4mBe%Wl=sPR&_$v!J}OL}aC()p?{_lS4Ne`OnYqmVlKp@HkJGfiYj6=i zOqghx%rzLP5lH_DMa6~$oiLN^I=<2*XCK5VT}4CQQ{vK1~7km{#UlxD>=%iE1f z=$%+U4JseR;6HI7dTgiKC7--McR2GeMgVkjc}~V6CpVKCmUzSuqZE?jWt^=@5-)n& z$_>f(a2+O4Ae@+?U|v76Y(59@7|)DR@}fk@ZwMez2*m#Ap;Ufgi{g@GWIoX2>1UE! zHY`BAV)c(4HvG-Meuo5gqq*2)s4(aYa)??F3G7e7iA}Nh6o;8WR*No)`vmW3qNg7P z??ogaDESyAxX)*!UHrUrnw7xv3L0}2ouy(|&5v>1U_f*YwuQ#$7t$?|I|6L^D&28& zuSqiY=Q}Wn0yL6AQRGu^jwDbr8RIyW8{HU=Ai?yMRrI##wSVHrGvUN6s^;4VtK(ZP zwi-H&uOxjQ&FA@otSM*l(?h?s+%5=1nW1s4rJ@)f%vQf*j|KT1QCI_sLQO^3!u3}b zYjpLcem?W!{J#>ZPr9+dtXdtNFp~%059k__z&CA+1Lh&F_T<~1YV&`Q(AFxTjH?xP zA!s2pBY$CF#EVM^eHYDX{noIybiTsx(K+LJ7oC+5k5UCr{k}6JeKuG8E!Q=Mrzp0o zfh@8vXRaag#+Jk5{S0|Bs(gEK3im+2Kc&y zPi6b1nq)89Q-vV1Of6ee^dHHis~DKWVM2+q&z~ z^%}79_ylmoJd!!A8WhpoivOAYRLigrc~Mz$at=FNm=#`pIk2-!sSaK52p%uFCCXpl z+vuVbQWd+K`@)?44gc3H#uD1UN|8eWagf*Z%-ha4sOMN}eF-Lr`$#v##m(K#w6TOj z4cwl4-U}4EMhw-Hteq$xx5(vo^WxAru=>o{eF_*=)h zQw!tTwj`(IL2qm3^Nl*ieObH|c7RJ>5=G#T27_mhUjli9^6azH1~?Qw(R!qZ$DMPB zMScNq%l6PK6+>#3f#gI+CFaN>$JKc}X<5Usy$=b1sPEL<5gB1u2-y0R@<*)WXY{F*~Q#0XxkW6tHHzXt!&?o61Uv89!06yWWmI7FYmxnNZt3~f|g=%Gmz!T`mk>zCb; ze!6n3lDLXbclgi>Y(xR3cUcM9rtowkPm2??kQmtx7|b4xFJYo0}4nT zt-wp)cj#U3hq$>{-?_afGsC;VC0U+Lyhl6no)`4LU1ntq;>_~8L(LP4E?_jj;j?2Q z8dO|@BB1DmZUgXL$!|0|)he1>i5Qw0wj?7GhE z+M_k-3NqhW=;h?BZDjhtFu@lS1=|m@rnPm%an;z^Pa~uIq#8Vym-&C=|*5=#JKuhXCF5+MehqWug}`ZXwHOdh8YJmj{9Q4ea}~r z{5+JWGM_W^+&3JvYPLbcTaS{E?$@$0fUanVD2UFSNgzi#gG<+4JlLIWt>vlb9BcL5 zSak)Jc-ethF9o#Cjbz51_n$iJ~iXeTvPN?og)XsE%=E8ceGVZ5| zHc7(wlHhYYM!bjQAF5i7W5@Ds&g9AY+f)=aa<-O?FWv-)6+=sP1Zq0KV+)j5yaz8& z<6oESTZGa@kj>!ObICa{fuSl@S;Wj^gi#0DuIgN@aAh`fb!jphDU&}^DXgIcBUIKMz5!Th~ zRbfrGn|!h&8h}I|1?gL~>(~c!@>rWICRlq>C>}i!p0sGvGq;_IyuXU^u4$tSj^q8j z2!3`q0C(ode{N%ssl0Lw^qHC1yd@0)ooDiCPvRjCD*-`=i+$pj0S3C(Qx}?UfxMR$ zgP;{>!KCL6@$s1u%)8r*-FKAvaA)G5L;`V(C7Op`7{@z4Yd?%wy&aA)bn zci!%l(7vOA7`V%)YQ|SY0>kT2soXJw%*&w;TfYK*d(?vw0nuv-&iuZbBb+tON^<&A zDPG z7uQ{83^&nTo~edJ3xIjyc+13#J%TE^9ug?g`*j-A+Rx7rROsZ!?s^38lpzO(YQs&j zh5GLpv?FKg;>h}W#n}j5cF*KK<-8Ei;n&px)akVWzRWh0AX^W8x;L=h@O7F|`JQNo zaz38CgZJK`@y^?_#Z(OR%|t7qO3=r}@7`E0SeSCd_^{|2eUbvIa)J=c>ECksj2y?u zf_6Dnhg>@yU_9nyi3fU|N*~GiBB-qeJcW-=q>lU6ErTL@|;(ql}x26xJ%Xu|`OuZBvf7GrX5h4>h_KgZFA z*U$A~4Y6yBCCM?iPGl(B?RU@ZkUj|V!%5$)XY=j6L16B`DN1m*7XBi@Zpdtlpxp9n z1$*0RvN*NF>c;J~KpJG4*EkvQanAuloV5f$*)A@^F_v)kCTBQ#+ zJS?wmJ0!X$!QO`qZi!(K@ulnWpjc^+iV%=1SecdLiXV(p%4swoyN+Gst+*w`k>hy_`Lws<@8Ae}__osSdJkClBUr%mK|w~4gXzMg>r9zI zg{6GTVFFgT_H42P9!zPwCpXpJ^Pl1MwTq=uhTh&qv#qLnX_)GPggeno$PI#7wg%vU zLn@*pARrH!{fIwGJQ(xI8sMY$nVRC)#X`w;zP4M)HUQ(odq4JD<%` zQx?ndzL*~^oILbO;WzI+Sf;yV{AeSh99RQ|zwRLeL_PnCriKNoHv#}X2?@~3v9~I$ z7P$-6-0<>D=IV?E^FH9MYD2__$Ut3;)Fa-uMVYS~OhLPTRZ#2BeuU_*0b%5_G_?z* z-%O#FtwlLFufz-v10CjJeq{Er&u@>jqJHsp2hqla6AfL$+g~!fv!`ow3w^|@+o2P} z5PC(ks9rJahyHbPmodVTQGJ8w0gWK~ z3?f=X1Q2pmKBSfN;wAx5_Xe0ucP*dqDVwWe3OcWoYtZ5|dRv}J=%zRnjvUV2`hC*D z%-I*%tvNWUGM-GKa5?cO&Cy9;V>sNaY&&JVZzCAKL=*Utd*2qx-AD54VaELK zWtOf}rzZ*(Dc2TqTT%h9 zSQ`X1WIIh%#n zd44#gaOPKm$wg)zdK|EtXqE0nvmF$IJhO|G)^mT_r^@5{&(PZL` zJ2r}+oFeeEdqzhzueUZJz4Pb${`0gSkHH&~)aw!v^1f{j!}*d0YIV8htZ8{;#l%b9 z;T1n77by*wsfNF5rKcDn!4#R&;I3Oc1l#_RzoX``r*JSgg8QYE6RTq%iFVt@G2ynx z*nVqUF6Lo%QV*-q;m0EUbQ>h*3%y!qpe6KH^v7spyQ{WC~mTlM4_qjotw*>OY z&QmD1JhW>XMU>JR2BmOLrIJNZG#8KqQbD1+Cnf1K-A|8~pfUDxU*0)2SD!{1phD_ie3Er>|}Xio$_Ho;XY99Ttoh- zFVgCkrj0BITge4`;a2dAu@8g4y2F)$p$L5$A#!Y{Ag&?bf44$k35;v~DGHIy{i{4ww6!0MQrRVn|a$ld7tvffLuVnkD zi~X$pDWbrT`WezcxU)OwU|5`Mb=gbD2G7|EV1IEp#ZZ8}Vfxd0!_JP0@?iROr^i4V z+s8!}XLvpTGx^h}N}UZKsS9835vH6a%|gURMPp{)q568ED9H z_}qKEgLg8{Fv<6;9|eOoQ(~^G^`66_q~zG@!c*9wgV>K zLDjv^8z4ELt&cp0U{f4I9MGH?VqKVDG8j4a2DO7;I1FHaGQOL3q8z%>1|3MgxWzp3 z9o%+n!kt0tf`bO7hkb;?8T85yS61h<8P7=%6hxw*eZlW+KJmIK20uewz*-G{McZU0u$#075E@}tRF(h0pamk!4~Ck-jR4$ zTxDewTcpOD;QJ-d!2k$xBhQ$AybdFAC*;50BP0aGY8)eu5n`>F;(&c|m$DJSiOa+o zgBS}Uc|jKvK!u0&m6>D*fB37wtWHpqtewz7dDXFvy&#GyXXg{i1F!DtA>VbzKM9MD z-Ou5o`JQ(_VI#u7b-!g}b#X=H_hB7INR8X}OfEcU%Togg%=IF(0+acCZQ|5wc{Q@% z&r-q9+U6jg@hCa10*(jaVlMJ8<1&jYJ^}{xINzVbrK_;!Z8{v|8JAK)^W1i&rG%FD zHv*+Ink64y;^N-lIH72$M!Tgx5-%@rUh^WNQ=1Y@Fn^nZH7XJea`tu6^=YzLi&WM= z3()1(_D2E_%5adB=A;UWL#aaofA+xDxo}ur0lj|?zB==&K)8mHc+|Uq)%ClbTkuhs z@K2A@h<$9`JbYNKleS_!vg@HVn!YQT+K@IBe#2M0spj{dwDgscCV{t|r~>P){jnSS zmflCH(3|&fv0@Smg2&s(Qp*Llv+Z15NU1iVm;yEr^*WbCO{p8M-7$l1a0Hn3t|vc) zC^%-kI7trv2a2apM-+7TH-XDoelzI`w&%QVb7D?mV zlxBh&I*L(-{C|M&y8>KNkuOx8tfy8=!Gn-9QmFJ(S9-p{zN*nyryXj}(KAI4~mQM=%T6~T!fx~_8w@irF_5AC?tY9faTh%R$%PHLglf-Ai@C)pi4Ex$mmQAj!)qTWax3$i$ zr8Oi5657LcMCb630W&{RJ^g?0ggzuTH6%G_i)C1@^!_GJu)0(R3t)912k$F=08?B>2~C z0b@Jg*8m@zi?UEQ;UUqFOhV#24O1ZiiDWw3?bN2^T9tV&a!-Cef^tn>d;8VP@KHOUjez4B5Lah3HpF`$&h;ts|g}p+N?RQE# z=&9pg(2H=MAntpGbU+17v^)c-B77)A{yqAC2%)f1bGVsd0mxfnksoIU2YA)@lXjAq zKa=1x?Q_i=Zs+2zwVnUOvi1Lte@Uf6D>g_qfBDJuf6wBdn%H+Z(3sOBElK%r!nm|r z8M<^>#;kMd4PQQK*QNcEK`5tqLJg3@g-IPZOysGrS~ljY<@(py{7?Q>fE!hTFx_T8 zVT97N%o^YlCXzsg3&cujF(^V7@Lj=x7#qq;TfC$GY%2Qw#sBQ+APjkcR&#|$!f{G} zT|m1s^%mN71cgTmtu$q)@@D04V*Wq;6et9~0RgI^9FlaeBc^UD_HJ{;1m*88efaZs z9g(qc7xFKabzjN|TgLX5|9-S)9koOwo|hBaRWwd*GI1d8zK9mBAN4aX(?Xzrkmh4~ z*kQcS=R8f?|5_Z1PbyA95*_=u+Fwe3XZfQQW7e`sKw;ItkWI-gTtWUAcOLU&U^a*C zwX*S#v+*HO2nT_s2UD*wsg>$7Wx1E8d~(p{Qw(c*!H7&TP{btJ%>~PK$N(t|Qzp}c z4gVX4fDYoDP#?Drg=r+>PiGy2RI8cD1Kd|)VIvR7q|1toLoZxYWsh>}KjiBhOrLmH z^`M^a9QABLYhM@;oO@k}hb1EDXx6X}|0l8h| zLR?io;^>iwvLW3p`ZfN&KpEgqK`ym&p^?u8vpbj8dNj%r;>jIb{h&D7%-e@ z9?+Lmaq!lGT66C^^KcPVQ@|)Q=;4$2!?e`1PO4J|vk%r8{`IZjgotwtlU^`al9)p; z)d#JVh3HjeO}Q{*Qw(;9D~$ri!YIiiE%HwPOI)BqaZ`oIE6;W|pWPBF2I9DYSshw< zjhWD_wd$342ig3|Jb@A;I@yXp%^Hx#3lmJAfM`w4%#SkrC)FyEj>8HyFx%n68H^d~ zkmde-ZgWK?j<@;NOwkDUx6GuYmN3!HZ1>JA6)eyURLWY*mVEDP6Ck>V3m8>Wc)w&( zEmyfIE73_k>i92!36Kd8(%Q59R6-5f*^q43MJVlDNQ6s^=*JmsY+8YNc#gOI+T4QTFg*hallNeII$VpP4hA-(K($_}EepRNW4yBXWrkx*QwvPEHQ`RSlG!c*> zW89>`F+2D?oo)MZV{|j=bcYI**c4%M7y^v}(ebaS1nQsIq?Zgz4Nm`N)3HS%Hcyln zngtY_3YiF+8q_Wg_GRRmO)3VRQE8&x%EX=i$t(o~xj=zlC_6I9SMJKdt6K=U3*Rl5 zy(osdN;H=SV9oNwDF_B81w*p+HvYN3psoNxVqrkMcHxf|jA=r}HJ~PrP$Ypv`hBl# z8~mQ4syY2%68;+L(`-fsWk0PZKVJSr?Mjq<&*hqmv&pUN*v79X{BS*72E*dYq@vg4 zuXTxt`vkDTW;Ib({feui{+QFI3g6TmDKR?rRiHn3L^K(brYi##9;w=l`tOhI5F#!p zgT=Ymwns9GqqbTeL>OuOs0&o8a97INVOX}9jREXDy`uX6-WG6B+yu#9zJ@jzszDikX~r8uTI@g-TIqc27g?C`5JNM z-QF=!-XJj)D1B%s?n93Zbj$^#^L-lrKO^b?l^6TObCM?P7#$6At=U5>KV7@<{v)Od zvH@Qy`W}cRj%ErYFk$WJ&Hj3)%m;|s?OqleiA1*O_zAb@r0=A_=SUT|jO75r$Ibhj zd=xU#MS*OFe?2E6lp{4fG*E|WbY?QM<$xcXvX72>NM6l|B^7`mhJC6?hJ-yI_J=SK1=0eAu9F9`mWA(WY(~J|Nq_@VPacd5_4Hz z^*DlQhY90L`q)kBAP!oIASsSzgd^Wn1B$*D-;ogoyC@o<5NU_dP5pbl28ILQNJ&tz zhpCg|Y&&oJ9n{69*oeDi574S9nrjQl!f*wJE(5d5joAt<{zNGQKN{0_Z<5UeZlWwYPs(VLe~Oi6d-A5!oKl#ArgTo%RZpP3NX ztWW(AEeq~pp%Y}WF#k)GPOKpWRD^}?z6;8W_Hyk#giD%lD|*1CHi{X=6{5!CA5pX* zNXLBr3sU(Bf)WG_r&fb4Iox7&E*Yg%Ndh5PiN>h+WbS{t09MTmuKaQfP@{-&p^#|x zk?3)q3~~O_bAfmm7jFBxo(z)xmCA(exFw*0uA;-8Evc@e8oorWaEiHq@+uhLdO(A+m&t*>U3a)_9Dg-Tx#^3jr?l*O9?)aprF`) zCNGU!9+itxly=ud z!ZQJqJh`A zX#RAbt+-74HQ69$oIS+}({mP#W#ULAR?0*)#5i^UQK@CrMo%%C!T0enY%o8cOjRe+ z|2uCovK-7S8*Q7S!(y~cNMXg$M}?r|ppKo|1R3T&m>Kb)t*#*orYu%dG}Taq==76h zCWEHV&Q4RK`cpT=n$GTSG(LZ^@ncX!Q!-M8^9&sa1v0faGGQzl5lec$J9oe!I?wm7E6`=aDKT;jrJ6 zxi6sN#R=eLvBU8PHsYPHwV#>rKbPX+;+B!Dj~<_&+lv!4^waBhtPDP4U(}Nlf@X0G z3sc;06TJ`K-rfQ(LZkSrpH|b@%vYuhM^4&mz`R0zAB5vN7bp0n+U>Ng7Oj zzd!U*?3L1nV|{gPRz)i7OXkN#8Dke&l`xYSo#4GsMN9`A7HO{h7^or;^TSmUfLdnw z0B1Fk#e4R0ojq4`z|nCj>-?}84Dj{!#obz~>ERm&xi9D3TYU8-4{}t3292{u!p**| zs5{$*-4$6P*9MzmKhClm$?>){md#I8P)si6=W_*q?B>c<_aJxxK1kxypu96C5Lk}GLizSox#1C3z z`?LZPDG;|&$+LZACu`dc2AoJfyWgG2QD}8|0G>CItL=54msD5q)-L*4J$Yh*E#A%l z%#yvmu|QwIoTo_D|K2YuMkDq{x_EqY;^6(VO|%LW4Qe3rCP9E*qy8oGD$?4cE*(fq z*bMzK_GiQ1g+Nr50=@{}-7f$z&L#Z{-}%9L#u z&i1tn$?YV1&5IA$JRajs=3^p!A(ZJPpA=EMZXuf_U2i5zurY8@xm|lqjpF;r4p9?R zkds5<$cICmehJ1MP;dtKF94fU{~^9v1x``aT!p3LS>!!6ErtVF^{27K{@9j{2xU{KwXT^=tK6hKle!InNfMO333X$AGhrT*S)%vRhQW@BClQov- z+CzV5z;TzO6>tzL^l1T?Mme@hCD-Wb3P7nsEl?=dC;2PNTLZ*@_vCRk_3 zTN;a1DEp?gfrYDlJ?^vwi=1&1H{7ie3>BHb|`I@*X_w zSd^Y9LuY(lI@92F75|-Sz-^27Pr6oo9xr5O8D?*ZXMYvGBMGU%IOQ@Q0tq-q!_6k0 z5Q!G6Oo9}ymiMyvfMv`eU6yn%n(zH-LZEwjAT`@=!;LlPP|8DHt-c_TYoN)Q` zjS#{waM)gM@}BI#)RB80XkzzJcoe2$28PuOU_^3V93CI{JEeFwontA9-;M}`w;L%I z)5E@bcTC7}Jh~*Cv_>OdSm|8e@l@{nJGI% zZF}>guuQB{vF+(W$oEZM&O)d5FLQR72Fl?Yu%>gi=~Ud0;wg&|_tz)rOnH%}8#No8 z*h6*7tVbYCjf^y$j%N;Dm*p@bwx%UzZtB@G#8V}XO>PBUSuyxFZ^V~64u6>9{mQcZ z_ZaRI`66m*bOHg*^lbuK1&<=&T~r~0NYl0K!CP`3mM{36jG<_14@p@J9%-I#1oJ_mz6rqhFzhqT=9R@{L=2)aA4 z-n}K7LU1tJ@o>hv#_guf^9leBjPyN+3YsU}$qA}7ctdR)(WMSgPrLoM)IJ3XI<$AQ zKA88@z#TH0>dtouyC{whXG>qB7Oi+4o$a8EQLvo$5_oLZVt1Td_WvZa?wBZl?l`KD z7|#E(`nkS?>r;Rl+wx3_X2|>5xxc3?o_jRYH@o#6x9aYU9o&&M=Jv-BDf$tZ>=W&S@Z+q(hn>dNop9i&6d5sLu69Fb}sQ&*$x9+Tyk@$0{$ zn-(HAt#F^Mqr8T?YwL-{KIV6{iVl5rtk=dVVlmT*f@Kq-3rp?h|kOO-IkEVTk!cEc)=uW zL*F3g4wEv5#cajthy0;vlFU|8BUPuitsgs#^J|?#42K1XE90Q1Q0d9QXWpgrW8$r4_*pV-=W z>wUKW!#YzlkKLJnFB0 z12I&z6cZ>V;Ig9ZQvd=_WjCHF?36$M-cIqeu)pVKM6`n@ES^33q5aH&g1CfY@11z1 z*X&EY#fp&Un4V5)1XMcyPt2IKziv996-VU+?o!6r~5q+Ju=X3UF^aR*PeF2 zoC24{k$w4AW1Bv$sqA4$QvS^Jxzy}){dpfcAYL|d+j$XP*{aOxsh*nIzxDzjSXXXZ*+Z@4)~A zbnR^QrsA$?EJLta-4CXDdO5O2qm^=Bqza|AQWu&0^88j5{o1|LJG%99_@&>eT7pzd zFvDS#XP^a>(<6qmGCxM-tFPI{t5n;>pI42~PkZx*GWXEEgc|a14(t5;k!+y1G^%^I ze}RJO%Ne&Od8Z7BI>IvHwU@qaynvHo{JfAO)@B5 zT_{*5PpJCW+}l;!W^(2BF3>kBhM~{D71`d~e`YjJWWH(AZrl6<+`U#^ZCgzA{yGGd zM(VP7-(u0L(b~f}HhWd?^?=8nepQ=;Y;kYR9?7HkIe3#bo`)sHxD4`1Q5Do>>g|9q z+;BTv<+wTc+S@&Im3@b(qmqKw{daXT!a{9usl0LZaP?`oGhqk%=ka*a8JVksXm#z3 zqd|;#CIb?pCEh_BnbX*DqZ$(wQ#&rA7bByf@ZIsc^WE1KR=$z=q{-LT&#)U(N|)BP z=TqYUZrd}=hSS~TGChooo=J9=x4#dEgj_mL-o&`KsVWGgrs=zer=GWMyb4L)rg^zX zaeul{|8700NM)VIg8nkB-LbuTncS(ivCd<(AJNK37>n5s(9k}G_bdG)zH|! z(te<7-}lh`aro$g4V%&EII4wb^031eDiCp%*$H6Vw==hz9ROgqDff+&PMm#0F_+jl zD4fq;A(`@f|^I^2IMe!5k=6?jDoh+2&M0FcryMET(o+L+3F;uVEM`*qC z`4+l;m4^#{rB3)V3-GerrL*C9e9!F&)WZEguJ-fi2MJlfZajz%q6`mh^|6?j$+HPp zeiD;!GNQOWITND3t^c}^4K&HJaRC%KTtx8-$zuJxfXY0te(gPfrDqo2+aep|q+tKpL9&JMx$@ZWZc3At zo$xTz_x_4I5$pHag;{vlIMjo8ayFbXS@t?f zGkcDnb&obz6XipS!7c7-l+hEeEady8ZGlJH{Y&IE7;Z{HtW8bN7H|js{CY1;dN@|(J2nci{?5g)mlOtj zl7u8WpD{z6VSh{Es6j_5N@+Vn5=}}NsH~Lx4sm@))HjTll(wpwIRpoZq5*;SW^6|6FBX7QX|`NhG7t);03y zx8I!=Z7I3N-kE53gMMmNOV6_8^$}b1kYvkxK3vSJSxdLYvH}= z&^n(>q3O#p3g(C%@ZAa1fu3niPSExtnEGZl~ftAX{Ge9EMhjf&ROgbXqJ?PDXPJKf#g4Etn80))ALu{b~N zS`Or<>J7oG`v;Yy>*ZCMZ$%h+xuZcKF*uX=tHc@WkV|{dK4`!X)L&Y4!Xm6{1D+in`Va;I zuo?>8P8gM2Q=BnQhevi57SEn2f(MSXq(Y9{=E#@0k5i) z!c(o3I_qvVNa8pu0LleEh>{-GuFz-N!-rbL2e!h{3a$6H?NF-c-Db*Sxl**=Q+x;YH z+9h>RVIaCwLjJErkmhP6fwhKQ_72OP5cb3oE&sNUF&z2u&u(vXlxFzzC&C7LJ4}uMf)N_iGk59I{ zQD1C1Ul+vQ-vY{@!7O|+@b%T$k|j&zlcd(5`|c>2+f4xK=|pJYsOe&l!Rzky*hy3p@ZOg~s--S_dt{@|lq*+J64YYp^%wQ*oFAi;#5IdH)t zLTBs;^hisfIkjo5M0F!gfN+~txoR4ttSH~Sm)$yg657bQy=M`U`uio;?@3b_eoy%0 zBchysrJNYGkxScIM<+R_@kx*C>ify$)a}TT(I!X2QN%~sk#o|yt2RCHsdx`>Ng_<% z7xvG&+%OVY{CSy(kB87chU)hpc z!`~3pC^}+R!wm*Xvk!7rlv1Se03A>2#;a5JbGw;&0#sFSug?^Lq=MddiE!o)m>pj-iAfMODrl_3*jVxN^X?d z_fbS@jJK(5@2|?e3at`Vv~q}NB-_WH3guq^EA7MXjMjoL=yJF*o|m|rSuFLmwDh&l zDkT2Zrj#t6PYCN)04lXPa)<|-hqO;MB}1HK85r&0=1iiCIdD^;o3SO!eRZ+yKu4X< z(Z~D{D6YK9)DUDTc?D)*g{Ay~#GbBD2(}Pbg zO=lgbWzqf{fIzeXE4yT1_dk)%H`*I{>p2axuFXaKrm20*a&qS4>oRQQ#Htc*WIfy3 zJ#AI0-ZPwsGAYT@(oZg-A;aSKMIEh&6w@0`zC)IaSGV6-d`ZrIN5z4QyX;W)`^sLK zkXsUTl}5OI=GSRN?Q?Fldm9kD^ZP^8&*gJ+Qp}C{PG6;{^*qgdO`qMg;^<{*G|O?U zIcpJm_8b>ET+n@$N@i26Zf;vXqgnXeL9yRFGggiC@MK2(6+R zkQXEo#c6bu3$$Ky3G>0YJUQQHwY}2=>BxA$mq#Ok_(UQ7E!<6hn>j4Kzxa1(;co(u zaJ9;Ua72V;?sZc{bYjy~M%GPc{!9UA%kY2DO~3OXR93QPjS-Qa@K>Z}Lw(N($A3~V zIqyr_zgulGBSu`qerNgI*5))`jXis0nWcTXaWQr+ZFkM3`Y5msEa2l=De2usL)@q* z|4AF)4-9&eM%A|Ku)SzY(QJ_j`oELkBM~Os7zz^q>7m)21_e=qm(}1Nl4;=?ipGzMyz%YigS2JTJ)l)pir8-@y9iqB`y)~ z72C+i`em;T-(G%H#PMMjl}uoMRgP`bNzmxOq$slf_!r;tFo20I441~vMB!*$)V>jnvaL4ic^l4N zXQ6WkwKoFe1n+U(sF2Ti>b1qPDl{*cjVgc|v4igk@x##y!X#DKE9j)agQdYN=mlbw zVToREI2TNm{n7=pKF76)XD3E7-O3cFkQp!zYe-#YfIo^rT#p3O3(o-OqWKqUDeYc2 zs#9;No8bxww$ET$Qhu9vlr^zEl*mED!bBRE%ld#?XU0q(~XyER|+Pm>P!dL1wZQtbpsU4@|8w@XUNMJ=@t8EsJ%;ZG?dL)!(s9 z!eiskHv3F!XzbtYQ#PM6BGzD`Up$d2tS|X<-#a+|?z!kAd68DUMf*nqo(kizXY;z= z&m&y!ut?TZ5FX%cvZ9?gxRYma&aO1#YWU!%^&oZHdUmGN6D#oI7BiG6F?|8s^l|RV zEixOxu}lM9K)5B1C#4^ys)|hc`@-M}%D#nj+rAvkJI1nLnNvW(qA*;p( zGMy__H<`pv!UR+dKHX8C)lSGh0PIfFo&HW8o78NrSiKQkqY;fEJC8pLG1-%PJH?hgF1de;9O2~tIO9cmBLn~wGK>A=_TEN+_b z%4+<)KTgxs0Xt-~j9&FV<8eEyfxJ6?+cb26^3yxCX;}t7peH`ZY2_bMWlDHJ2R~ki zBkD13*K-DmPQ-t_(^qb>egLcbmTHw(I|LVYC`7ruRo82cWtDGU@XlaXOA42ir3E8< zpjfb$O2m2iT5E00Ut!t5dj3-goe6JdP2iB?sc%{^_cx7SniUp73P-Y0G;-|wvJ3Nf zT@uB=TYTCikthzfig7z+w@qQF+wqt37$gB@`@#Ova6~3rD+G4>T5>puXuBxEKgr|a ziE!zES~i7h8;vpGFDKD z#hD$(g5f?nJFL~l|3c4sPUkr8GYIEVHQMTbS41Fjs-kOomFF#)b_R7pjXWEQUjjyivIPSFBgyRqQ)X4|){` zxoodPbc$^caZhn)N|y`ok-)uY51lnHN*BARJIBI>Jt6_>{}fsYy|H@oxyO*su*~HW zbRWl%uAD(wE=d~Zsbq3GxXJ+2C)}(-Ut` zUY*C+s}AK@knQop4S?;-qg(yYokO~5~G^6IlZb=nS(A3Nu zdfPNf+8xVw<9Y^iB@*inp}bhlM_w1(=F$d0=;mA9aUoY-NI@gmqyi*d@v^I& z4-83BUleqhLIqJkp17UfIR&?qGO_z!)1nsWc81blDE(<%1+Axph@mSILdO1~YB`U~ zBsC{X7Y$u$Gy} zw$B7U%4@^Q)m0(&b`5!jZ|eVkIGO4-8CiQ{wRq??Jl~3v$}O9zqU&($Y?WjC9rJA@ zBCz<>SoCy}nFwHM*f`7tijg1tW%@+PE2?of< z_&a=cmzQd@17ZVxU&c(;HR|Rin;mvo7qg|O4kjJ9TF%?gp1YWXOce9Y0$tkRFiHlg zd+Jy!Q7v#@>?DpDl755 z>NIrALU4*A65>yFj?&M|4!t>}642_KHV0gt+Fcn{)1PpiLHxnDsh`;(H;wO9epnDu zPiK}ODIf>-=c2n805l(Y=vzXJpreoVp1T8>#Gh%9xfds1i+{sZq1}!~yTWZ#^&(x< zz;U4xc1p^rsmd&-x)9ioV4-gOVnCZ?mocDyb)p~n*b?g}aW`;}oO63X@bY+MY# zJLOXr81E+&jR^?(057{!72Fvp!+P8$H-;R!qR@J#vcc=yMMP(tZ53y?#Iv&v-?YW- zNA0;#F&%LZY4o0uc**Q5R1>EXor;twbnt6DHH-Ypaa)e2sUkv;hp#{z=VOO*7@W~S zzWqH=iVeT+aAhW3n0$NR|F3db;-@Io`iPvt%ry-`Jl4U{7|{;-6;-<$SHEfnk%w-k zfS40NqkZliBU9RJtkG`X*({v286Y7?--cWi_Xe#?k3N)s7Nc@!4bY$LX1@CKer$82 zcirOt96zw0+$o$55hfbYEkWA(<+5mNATXw?p^;E%6PS1pDe%s}Z6#b_$xi)I5a1%L z{=}+j$V20UhI{~%>_57;7J3Gafmey3@_L-p&NjA&D6c}?crK8#>t6fR;Ww7m zoWak1x@CiskLRUL{?nvYQD>2U7V_=Xa2(E>hvMWn8;?_ROl z=;21-P6LV1>6jcHI!L@z*93xD;pGk4BS4j--{8*Eqj-rryp& zD(^e$nyB;1Am<4aKz{vcm>-&iuHnJKe5!t^+s zEgB54%9L*?K(l27l>gMy38q6bVa3OE^okp#zx~;i_bLBGn?{6Q1&7UebbL0eIt=D_ z*USGW%QfA{3m6or6jG31$QtA6lW5A#7q69Ex`+FDBhZ4$YNFRWaqZ0~|JCkk$#r;s z87=s*xn&j8iYb_0`ARB#TUAKQGQh`F*yV`%K>81@ivq#g(vl=4U2TY9I9sEMungx|GZ(buX zh;2U9sAqi8F{1v7F7N$a`Oe0JbCSAudwdsaRF$Af%)WiM?3}`h4+}chwkq#}{pe&v|a+ z?FA(^`!s8sLe3+PaMi@E8W;((;fF?8D#aOfegCr7w&gkSN)g^K*kJ*o)EKvM775zN<9AX2wH~^G?7=o1mIGB^UPPT$uz`$*ugoW4&0Or5eaalx@;k+vJfPD6gMCxq`2DvKo?1EYVD>tSx$<2CM{PQmmSeI zWHu6n_%$?I-N^IXr5 zXuiKrc20;vOAs@KpcxFU8{V+I`~f>KH(AW2sxv44@x9Kk#eSm@+KIsxlHi`8!;jB` zdQ3?3I}8(B%h+eW+^%UMc#<}8BXu3cnkm{TLWyvQq+PaUU&QAgtrg_Fi^WjYX3=$n zWMnrUP2FCu>hhXTUromsJB{0f=4z{Ws}IBXygMK@sJJyxWzK>`7?>9Fb2Eq*O-Y!lGzI^66ywp{m8pYtlT%?}?ie#V{%Z*Z;bbd!>pa8E z$xOE;!C5EYR&44-d!lsJHrZDWy?=*U7JsZXW_ zbzDX#J*+%HO?V9B(~n$k1X5982FaHhsA#QeXnLp4Bh z{9Trkov_x_^mbAjG{a{PF9e*G*xGZ&G3L$}dzQ6?>QWyDxROwi^$8^LH_rnB0UGPO z2`}GRD&$^&Bv`*^{RHZ`?c(Qg%=6rZ#$Q5olo0mv| zHUi8S>D<@A#m(!I37N~>2yes<{hnP%f`st6ml1J<*8N9=$9^^Rd4>54CL5arj+k67 z?ev7$m60fF#BY@{ju^>X-RO`y{9z29-dHM=wLbLM=5Z|;O+k*1KFw-EXf&H0fSwG* z;&oV;{MXFS-GHU^4y#99EFT4a#fIH^A~umc_|{;r-_B4r7;?2mpx>!fj7J@;-=t@Bkuxh3&VgrS z+IK;CwI#r$wI&t*D>HxWD*0?9jAe++*RdLWlh{no$sUBpY8A1dsV=^-2<&1KKCbMzm*NiHmQRbLiui3{xr7P4w8(pexLI^v}bF%x% zvEm&9GF$oQCioj~Vph@VH&(E>#{~DtoBKWlXpc5&ym`#;-4@d;>xavW-3qUzOBbtC zmo>*!W%Ei8L~|p!9843*VF-odJ^p287bROVJPrDQlpGv@{x)7@bWpJ*P9K zeib+Z!cQhUQ{?7PA({8W@Ismmjs;;|lJhEr#u${Dt-Z>s4>y?O@)!+wc$uub|r?g%Y^A(uhWWQULbIA9*g3Qd4a68(Hv5Wk$~zf1UD%QHyQQ`3ths-pw)l765? zL>r+n<-fy4%A+WiT2BGJyON?Eq}bCwm%qm_7Dlf744TahE93px zd_8S+l)KN+(1J!2C8guY%nHBwJK?kY54KBRws_LQ()+75b{66H?vXzW>Yr03XQ-`T zwV;A3zBzZ$N1#XWYb;CQ90O;~9$}_nmR-$wYWB1bK@ozRu@t2ZNMmafLC(Zg_k5(+ zL~dwD$eHMaYI0E6^H&`?iK4Q)FXwq>;hoXH?b>{dVAG1XKejq{ViDNw>E1S)odww* zLMJ>y%l2u|Zgq>sWmDB%h!2ydf$ZBfR^9C1JSJdOy$2)x1q#OKnQW;RBWO zT1UzIPde29DZ|-(ko@+RjZ-CG-oALp_AMsYd>#|j{K0+6+R6qgt_aunoYuL1GhZW4 zm85K;jy3tE6+ruzoy5!4_fVj4_ohcrqKJ#50jZ<3%gFOtwTb{;lgFSz*MsRrlpmds zD?}m@ULUjlYH^1>7^GL?L-#%@**CW3xXz^sEHD|;{_H;Y$D#b^=CE=P#MrDAr;79? zu6MApdz7StS=JgnI@1#@o+P`vLwDp^F)gG?Gbfoz9X1<+9`S9nC3=W9PuB2gE7nkt z1zB9;V(fX#ilv|&TAjkn&^>S_g#J_>D289?jlsGITSeYi63WSYx>Bj{YOlVP;5#c% zx{R=rsyyqwmzJlJqXc%>%P#LSI4nC3VA?pyjAzPjhkb*E$NY&4CkVp%seM0aT@2P^ zu^Rh~NTJUhzU3E;S-EU;-77c`Rl?lMa#wR3F`rT9Y@R&)&r;0=EJ{it{e{%3!nvVT{pnDRHFT!Bo_Tv>3zh+J05vT$AZ42d9Ee{2mcRo zg`6*sYsEY1Hoo>dZg;pGG3cpM>;Jh0K>o?sWFu>m(4R;hFL^3>2APsC{zT~r{t*Om zw4{0JxbNeF7O1TD+#AQ*RCZE>B3-9GkPHGDGC{VC;_e%zU=b_shOL;h>iX%lnIj~o z!zq?wYWL;h(bs?EW9{T_#c((O73rG2ut*3`sA=NN5B%FhG_tawcQ$#vsY}oU{gHj# z$u|EutdITluSB^jz{PnKN}{C!gRY&{e);t@_Qv|+Hl+JvBaFt{UY~Xw?~9AK;Mu&n zFxv{M=wTR9!2+s!a5vu_!cCXa-uQ;Fl`5^!`W5K?+gdbaxd==gwJ!oUYpAJYmZGhU%nW*J}#tat| zQM4?RjkcGqB)l`#yq2OzTvBo8$wvmMl?Jn}-3An@Z>#)KW{sKres8 zCO!Y5pJaSLlRQb6CA$ueYbU0?o=S=&{v8H+2YUd?ovMjt0NL=eXiTp-tcbLN$J}n} z$v9q=WDZW?8i<2-2cg^swCB%%lolLG>2r>{1Ajbg?8fuFK&A5mQf@9MFO?&J{bqZzhMEtd<8Tq*NwQz` z!I{1i7``bA_M(To&Ob}Vh~E;n7~ZY8(Ok^=b4>vL2=2b|;fO#$2Ewn(;Z1AUtJ63c z(0AG8i@Z@_On&I~rS*gTu>XZ|FpeocN*&tXvv1q=wDKrg7kKn4#5+3x%>q8*M?Hmb znnxUVv{TV!hF`~G@-euTWD^D9_TplIV|U4>vKoQ%E=j_E;SPi$4m|>6;e7h9{u9&Y zP6(y2+s`_~pVrf<4`@0M6m?*9g}@ymKP-&=-?4nsn~Q22M>lxgO8_ z{B>g$US_q8egOg16eW=&it2!G%|9AfZX49@fl4zFZj+~nvF*Ixg|NFfwyh#!>>@=g za6fM3(ET8{O55pkkE%QmiFhQX8*4~Z38mc91NnFWn%aT-q393 z#^Uo70#dNuCImc7WE$x@y{ZjT zveJ7ah{PN&?uXRJ>pOw8+CMie#i#jBxQUi0ig6Q0vqg@WTBo)-UpDo~yX?CRv82`_JZxeDL&jz7awYB*K>hUz<2>DzboQ z!}Y6S97Qxc#q4ojD88{ESk)mhvwIAU#HW=K>M^-qjnQ>>*0>&Bkyn(!!-w;>$K0|r z#&^HFo=xE5`B3C@>ubVQ^7GKxJ@mU&LZ>AGJF~_$IEewyt$-~_%91e}yW>Uk-KzJs z?JNz4O*TTC;+C(7SQi`4q%##5{xbLHCTn_x&YEG_c&wbfZK1(nlYGFy$ zm!{R%kpi&j>cGhRg5+rlH~PjK=B_@dnH<$DE( zPL=GIpj#ArfknBhq4tFtUuUjLkPKNn5fmlRlSXVT&zW%Iv z&wei3K~!n3d4OvfCbC?d4Wp`!TXP(+)vAnBKx;~LVh%Sg;b7b;81TV=`oq<6PomTn zM((M)8=aj2^W|W-;vZ0pk$_G0Za-c5ncDj&*kjN_vE1kmuy!#lwg*3Hv3Q@<7vnUo zR_{g4>zXWR3@j_7wJ?TQAqUx@!R|WTvgX~lAaAH(1xf|9UA==&DhQscG@A5&0~*f-ULc?$VhD_%5z5(sk6LG3s! zAgk|psB05JK-}a*j?45=kH*h9IT7hWEYC^nO{AzDV(C79G}dI%Wdhor;Gixu*0A@! z_q{anXMQhXaxs`gTrI)~pv%F0l`r}P36-hB5ru@)ucyKwiI%^2bi`ih{HB9}yz?_n z)jtU9n{bkv3D6@M0@rZ?6{e8^l2{@nyh#VHgXnO*KP9(}CIc@Fe%}la;qn<;6_ISC zHO7-9L1TI_Rt)BrzU*=Xvhlizv8Tp7VAy*-zNbJ;&bk(RPQM8=O&f? z4Q=iIxh;VC0SA7g#?G%C)Zdn2;zDPLM`=}{_MJg3rBA(q()T`H!JK$Dt19bIRSFdM zssiu>%PIO^Fqww$&M$2MbKh-h@|-dqhCs#ZbMGMhe0{bALK2{H;FUDi;N{c+xpyNT z;M(RO@Yrra*FY;;FjoGBD}ff)Gwu1>rSf^gAj{=5(k9K-f*W!T9FUl{1Iv~$A28Cv zl+U7cnGCy5b5d&YV>9zzysR|=ky0}!Y=KHlHl(7ez z2FOOhkW6(u8r6Gbc9jLpQbNh^_`_c#CJ!br&YHHouU*fgot7PQH16WWPl&3De<`=# z-$y#&`S?h3HLcTUsN9v?gL`MXhLKA)fAMC`L+vYv2|g(Bng=^c{-vdi^I#8MyYFYU zY!7=+#2~QxY3tIzJ~85V*=se!CfeZo8NsFGS%B=WQ}GZslv#K#g(?CA;-2a$=dH4$ zYqrq~-X6mT+a;5oVq_>9=sXL6(JVlR&6`=5Dl__@MNjq0XL69`L1Zpl&FSc#cTfnh zUWe60Pkw;ujqLKGq5rYZI4&80;YA00<)Hng3oKJA(V=KK(tfi2sdzVjH(FMmJgN&_beP3^K7 zNBDz;z&uvCGk*|P6+-RvonL1M*i#C+IzPh;&n@YdOn5YVxZDX1;QA*|xt!kOW&xLW zw`p9DdIG%T8UfiXQL!n~nh2UIMs}pnt2ov4_|jCLxBySR z;qj-{SR|2aDCSA`O0Ms-jnePqy49~LcGh`9|B?kN zA-u}1gonNqeYuqi-=&j;KODN)6IMTwsgu22;2QCHDl)9l;(vf`%55aD>~o(7iCs_j zjva#gh*FritnMx9bGCAHfFUV+UGHJYrFQF2I6BtjJ_lD*&g(Hal%Ez!Q)ln&{_b1XBtCc zMq_KI`6C?Cxn1=oP6Q(t<&~8wKq&Vr^bDvK`JAg#+1XQK?+2#+mN_awsZ@ z6e%FyqxL#(+EQzgo>V=o^l8PpALpfcE}9NvKPp4PynH^(;Q-F%3(FEFjskLU zxJ+|e+XG<5E^M!cQF~2J+dS3wEDH+xJ?wPW#vVD~Ce!aw=ZK-zmYiy%3=CdPNbJ2c zZ!~zwiT?k)0DL*Te?wx9MrqGbC4}TzLe4nW9>yD$=i()_JFc?$PE=R-r+Q~4o)wj| z0vvfZRynk+GR@8#uEtyU?_OnVjXd6M16fn9mT? z#oPZpIwFJ?4sicA`oN-Qz~mfS|CI61F-*N3sTPP>iM?DNB`z)#Pn74c;fB6%Vv}@2 zVv|5R=aDa2>K&`5iAV`LuRp)5LCDdkM1d@{WPN5m>2(h#72kr1VAg3It)fjWA#Mh$ zvxU^RjRVx)?{wh}x?wb)+a|T)GAPxj)j*pUfJGtj+33NqOCNQT%7&1N7E(Z2gCPZR zg7tA_n-lN9!0%We3Fz5Uxfi<9L%cB#PFjvCip<`UkndqW;Ny@a0+-*6A}~3bg?~0H zpR>xjUt&?&-;NJ4%fWO?s-A!=8a!m|D_mdXJ0b~%s?t?U-q%DhxltN%Q;WJ~mUoY7 zgf(%A0XZbOj|wTgW@8pRBu|BRi&iwYB(15PHkF28tdyej!k;?xr)^i+Di5Cm8}NoB z$Jy({smCJB9M{0Oj3|h7<^D3MjM`;mPFZaZ=v~9Hemt_%(84`cp-q_tPnmVw$;y=0 zLbz@Po?tGa`qOWGvnudz+RM_br~s;Yx;mYqH2W^|$!fEK^j4q&XDC~}5Vos0NzNy9 zg;3;W*tN{MbVW*dUYhsDi~Pn;*hBDo0eY-{_JO2!VU1JIpbWFwr8_plA5-yrDQkX) z_HeEKoA2L`M6Txin|U5j8gmk!_iVve2mgxGGB8YZ7Q+OMnmV{8i$FkGHuJq}T=R9G zi_El{&r`j;&HLQSY>=&5;JM-|xFD7^lt}Y0R!ZDDNGIE-EOre@EHa(=3C^>42NS;; z9FYW-d(wC}Seu+3^w|QPZX7QnEO(uPT_NGqy>Dp5!Io0*{m`4w3Mr|g14G{igK!K! zt%f)Qn6SzH1st(+V_sN-URJ2UIVjnJ?dG2COs z@0Lf<-Z4hSVD^mC`V-5A4FL;4xRrJezUf`^79JtikZNKXfPfuNtc;ZP_6RsU3bwuH*?p-S2*ku=?5RjDi`lV!0oz{O zg|#WxIy-N)>3C9&loF4+RLq2q2(pz~ymbIezA_HeBOumu`Ec#B3i8CO_$)$4?ASva zGSr=oqE|pk2}JgVe=Kk_W%t%PhR1r4ALkP`2^JHYD5KjVn?^~VI`jqB1yTiaZc9nJ{m|FBuD-0$bh zZAkv&M=nvTV={EXfhy=yghLw4E4huNHiLg7XNcx1*?#_WgKRyBXcv{zYd^0Db>n?W znVRf}*rFvo&qS^u=iT2xxKmy3Ze2hgzOhLB1TX0{J?VC|FMmv}p|dul(TMK+PgeXz z+js8EL0@#a(M>q;JxOZ*VFerikxG18pft*3x`dR4-^2LD!O zdXVDQE>oz?;t#QM_HtYq`4}#9*_0`w7|KH*yYxT}h%g-<%dhIG(5$?=1QxbN^9RjTa zd7asL9;4`0j3E9*5>i>X}-npEaU8+z#n(l<}{cqRaRa*_Y;wkuBw+t&} zM*31F`4=K)b}^CMOEbOUIIIcUPqD*)$LU=9A(!ww2ZHhdU$mJ1yg5ssFK=w!<@I}t zy$_1%r#~=94!uaSE4PKpo%nwhA1!`PE1k+u7uwlh{G3`;QCP07>G@D^o*l!b7i`06 zi0YepD^P+uw=7MS0PH)n=ZZ>6xn5A;jh`uAexl7q9~_RJ`_WXu851S1WN34kX;f;S zp#rVGd}`JzN$Sr^lJ*)BV^i#`ud0*{BNYJ70AN?)iMA3*VBh(g%SBw3N#mm4C8C+6 zs{xkyX@TZvQC5~sNWy3RYtlTtfbSv|J@1FbSCc${m1^3AK?z2dmr}i8wg^zw^4_BH ziR#tm2J5XPH?Ite>`!aqUCH6E%J>)j5U0LqmUR2GMZt*n`^vc7qc#*5Arzloq_Zmx zLVt$UY#T+&|M&9)st6v?#r)?b;RiGX`)g4`sPP$gfMw{PE`R?D}6ng9M&xFm&~KMvg! z*I)B}d?)?JZ@KT(ij+tBlAk79YfTM|Tc(BWey;VLrfF%XTr-rRU6p25kZJwST4P{Xdz&r5TKrKqm? zpoB@$``;uOKR^TR{P>b6xJWf49lL*?9X}cTeneVR{W~U^-~YZnS>r2t+5sJB@2~Gj zn`@m4x1XS?B)Jw$_UJtB{Jc;ceup!5o{4wm7%e4=GP|nCwrQt4ZvXRXDPSXHtNs^u zTl9ND`e8j`-c$Bzd?1TJeQSHCiRS-4%oF?otX_W3?2Y<<=aaUqdX0HTVS7x+Kgp^2 z2_<=1x=2cMb8}_$XGIJ0v~5>C!XU!8QseyJi*h+;#O3}|hd}aS3DLfBj{DL8(knn9 za`TDg`jX-k;pBHgboWnUO~0NMmx`r#ug@FV%I<)3dcehYs)7#dRS14aBGRHfqO zMDq{D5uv>Y42R5fC=4VahX&5gHki%FNwo|OPJ+I4jt z6RZ_x${lRuzv4(w*&eM?@jU+LSpf*%A|0zr?gD^6bnj{n@cN?1)|AXqED*B^vS*iw zPUo*1O?Zp+v#W>uZZB55<@ugFER*>p{&}+fd@KsHu#@2mv$L)2K<-J-2@YaJU)%9L zn4e>&NP_|+%N(fJ_5U$(5DzQ-)^>C?!|JLW`5rdXkwO9>C4&%Mb?o8h?93E`#h@QG zmhrT*Vj^F-@Vdc3Rm%g29#YH?r#;#xQp{LwZ4`|9Uy1npY~xV)E`{{U6r2Xe3l4*~ z_Ovb(v>JDC=$|eHYH$uZ?h0Nz$jk98TAwyjv|7e26YjE#v=fwMX8HbnlZV`bemxcLf)j_0-SOVd%P=@>DScjHVnTNg zU@3N(?@O!P2GowzE!!Vx=5@wgNu?XA3xz)pBh)l{3k?_*Zpm?k{jCW8OVYacFl(12 z+Q~4Kfpa!P%%sIBNsIFf6!Zi%h-QnTualGV3;0#SNZG?=1SwE3z{f{kV;&u(ocS^T zsT}z4Ie+mp1yK~Ra`A{a zKb^Mryz?#RY@<_*bVB|F@Bcaj@Twd-mc>k(Ki``7P=3v4P>>#*pKkY!u|=D&D&Qxh zxMuY4HXyCV@j0{{h{E6L-~6Ai_@mkc-r%Rk<=L;@9S_8nPrQBxl2{-6-0wlZshJmr z2PCDStP1vVc3Ei*nm>uD|53Cn#L(yJ8rA7uxw+V>+aMPP@ny5E?!)*jQv%z%-gHt* z?AcqP&jJ#MefHGQkLyAN!?Q$J7!U*`)?xOc^a$7gIgFVVnl&cj%4KQKv~3i-rG9PN ztEhKrj4+)QoB0sj^vXj=!2!uc0z7mYLz??#=zNW7^}6}$ur zSLsOKDjGjRW$)cFYgQF${EJ+vq|n_W@I<3kwI5k7FU?uZWflt9JuF7SUR&bVtlmYo z<_Di~wraRNVxVkgOIAt~k^;wMVBa$(G=`2Meq9r7_7|149xV%hk#TrDKLUZSD)|Fm zyXP9|2v?jq1tEVcJXOK22c>4BmS2F#C_^IhhOx*>~dcz{H8h%p5IDI&#rI2bm2VN zQI;IDRWq^AQq&rUVOk%{Y>T7TV=omX2Wqz1g>mhjlv(Im|BK*5!f(Xs(+W9bZEr{> zZ8lyD$+^r{KQBAa_pRAkm4lDOB76KbyluW5!E9ayVuph^ugIO+eILYHJ@RW0b;4PN z{#e>S(lI6U$?Nq>H3Vtqxh_hDN(b^L-q9DZlpp!|MDJ z{JYii+;KwInr$`N0Z9P(y`q0hIpBl$uO##3xPE5EEQYvYYt?<(0`|LFQ(cSD-rGE> z`6@C-H~5hjH1Qfc@8VQ+&hCR)BIZH&knl=HP0f@2B9A%Ne-SK*Cltl&ZMXji>S1cJ zE6qp^5hdTu=2myC(s^hcYTA!0(CejS`@GR%WCclP+bihw$ww5f^iv(C6A38bnZdu} ztp8I#)QC_C^5)7rb_sk2PG&0J?MInkskfi;~y`?A}x4wyRAGdCOOvW6+6_ zE)EP6Emr~b)>I)qvir&`*Y9K+1bYlg0EX-e@MZ~x?EyeTSbhbTf8P!lstF}+XxPlv zi)8q0Y4ZG1V){emEQR$e?Ts#(8#IST71=p6KjEk@Nmu1xu5#(?J$%7yh!hLX|HlP< zea$UR9zvBd+s89a8KQZr^`hh8fS7HwQ==si?}jy44fvplW;@$`Yfre$RZ_)Q6yRb0 zog95(>%>1nz5&Utkon(F`N^9a*ZaCJIIF^HC7cQ6;v5r;bz%jZCS$LWV%{A=CEs-S zLot(vqEu1d_ahyd^jQyT85?dffh8(OoX&+Ms?bmI5&yPHQ@SuU_^8xUhwIXWRvgAQ zGihn7H=Mh59#K9k)P(!*sjCjzSD7C=X6?9(Qy5mJJp07@B(mYpEKrA8-kw#E(ck=! zGC#wIJ_$x^h7(EH{9g8v<3qm`>2Hf|h*;z+M-;$w3-#G9!>-#jfRE9sCE-u=I9*9k z|KlnD>y(v`(BAc+%9a|e14+{aR;UA}FKgd;7_1Ebq7%v1{C}LrO8Dy; zZz_ocI##br%IsI0i5Nmq^LJgCaYY1EAc29x_~-A1E*7-oJ$-dE{9a#HiuzVW?TbJf zC(LEYxgr6HM!mM;V4LD+6)=(j@kKk20EaUa_oS@C9zehWgMBTwsg?%ttMxwQV~v*RZ43|*{75No zuzQcl060}k<}ipa)2h{4U^0)hH)9!rE#Gw^-GxB#j^Dl|fwhmjV0Va@f-o`YTPb6= zODc0_z9?4dYxUO$8~w}2TGBu0u zfeBQo>8E?wXB$uaMs&aT%`@vduykQGRDLr$v7j=}^Tje*!qqjMgV{cP=u3HL240@t z&6mTHZ3u~Wj@z10>=Dx}EM6gCicRbO@q4s7Mrhf>`Rc-Owl7FO3>DJucudx6zR2Hz zzebv>tTS7zaD&D5uU8M^Nr6u?nV~4f^P8)sp)oN`5Gj4v$B}K&Q^?>iRsh1q=ZYba2p`nYbtExa8Krd^t|qV^Q4W2Gy48%(U#M7QS8Mf`h6}4 zuvRZMe8WZx-q2ds*^C-`oO3`soaljC#p-#29*~vrW>Exf%#xgPlRnK4%#Ee|N zJ1;f?iEFE^2wNG2F_{9FO*_HAO?t*N)3-WcBRvwEV@gLLP^ZD%c#3Fqe24&tBSR7* zj~At3AxHY#>n@i67asm6vLF+B1B|(8V#zn%^v3v{Ip_gYPBAUZtGt z2jZH>xuSBT9SO*_h+QI)tG-4>+U?o#GH>w&0caEmNytUU{r2Q?K=pgOT%ewH(CL5N zjS~JY*7DVC(S9e{)4`8PCRU`2Rj-C@w3klBYCmq7EuLSL7QQcR)Zv(1)3<3|AKr;` ztA)Y!^n3HgM&n-C5vKTAcRhoSKzo@|ZPUbsf@p0p6AivgnW9gJQm{+OF_(18@>BO0onD>k8%Vbn=e8kf>p_i@hjPC1n>JUzJ3-jp-5 zNc@M#Gt1>37)QHykC?R*WBz}_L7Pu>73mKYsE7%pov)1ps$U(72c1LR5mKjv* zoT89x0Ny0{8s)u>Sf{nDNN#9Rq5RtGgifm5cjU}!LIa5y% zaYM~WSa%td$Z{#wDWp%`U~AHnzj$Pq$-G?7a zHc{j)_D3KG|BN2bt8pybvz&C7*Yda!JY4L|Y`eHW3h-thVS)YnnUZrJ=Ky6A=Jg$< z6~$Te+!PYO9OfSdF%C0UTIavPB0vF^WxnVOF<5*n#gS$=wx(%nI4<=#DAaBgVKZax zxIP4suv*fW(El6Z5+jNt-7tMOW}{5y;}SKPf9140a7A{cC_T1V9I$+1#W+s@~{;6Q4@*9RG6rPP0@^~uMT>j;7Ls;4*+bJeewR}3Vu zx`CV69j)t2&>T86`*G#0o37!d>#ibp6V9@R~%${3xjG}$nTu5f2d!H}20qhkA zNP^HW^QuY^k9t|i*F5Xlyh;qdqLcp1HW+^D8YG##VVOk!c~$6FTwJ`u*cOfS8g2H6 zV*FM01@uqUii$T|9I0_kFbX303T5!>>5d(Sjk=K->;xwe@wgsl^_gwY*n@{N$74KJ zRPKu(K0L1on&y^_6|GCHJhM>@ySi1N+DN_x#KI2KtlB546C}|dY}6~p;?@eyxH6lvX)r?lL*LrNH<@L9K|tvZjFjS_pJ6?68R%sZ2= zd}WZjZ?rG_$elJGMU?G}$lTmAC2($3D|RFODy=|OVlv{(^ZXqmxIOZQWegr|-Rpai z7uJuzKfFN%CInLa2CCjW@pUh%Yd$n;6JK#q(&Xy|V$P`WBm3gM3QacgOs)2Op&PTg zu*jTzyV@X%=pVC$&wE6_!oSM1ceYzca9j+iVONe4)o?W{t7-XtM~VwgjeqoH3$~k| z3AZ`@)V}A}3p>Kh-=b#PYO%+tjGa6gE$JqtM7l);-RavACFn{Rh0 zi|wUJrIZhJjGtb0utc|s=&0$ka+zq@zXA(5@3c>$9*pFyTKySX zqv=EMN^B~Lbu)%?ExNjR!Ru$~&8Li+oH}|36X^!vJ#sb}lc-Ct$l#SA?? zNUf>S*euk7cm_VY%xWKuAX}>HiWBqc&KgtZP1kn_VNkNWpF&{o#{4R5MqUsTI2}PI zV!LF#*D6PY$1~y;oUW9tI$&tlOX*Afnt)BSpoaJ5FNzc@r`27vF88J}f>rqA}t}IObHyTQ%Qsx2lJ*iSOXLW3ea zLy){$%+@6j74;yqZWi?#Xh%lFPhqCrxODG$@Uu% zwBGNT=Fq#ll#fmZ#L!1W{;%De>Ohex@qMC{{@F0GI5~5NahUaF z)SX3{sP3r8#|P`A$b8TA3IT zKc5snXzXR@23rEezqA4giW^pqUj!EqT}0d zip1I%!c`?reg`6NOhA@CCP`dpQ>JnLmHhE^mA63=f!5;;Wz*Ep)yI2qDZ3sVo{BjN zH+U1BE(966*Hn%*4Ew9d>HmVBU@vEw&sGaoG~MIj4(NsL!*wI;l|M*x<;13x&0ss{ zIvF8nR8J4XuM@)To>COeUhzRmk{0w1q>(Cq{uF)3M}ak^;5Q34dzq26Q3Owdogmh% z2@%Wq;qLDI!27S0TFtWfsU1=i4_4C2&!Z>6KD_{JiomE?5}39)o`qN#_c`YEy&lW( zgh5YcjRB~R#CM6WJ@;~W-zvDGyB&f@C zx|IAi@+sIGMyk>z z8`%BS=7yQmf=b_Ht_e@;V4!6D!z#2|d1+dA0oWxSYt*XV@3i%)rXwkdxIHo}Y`~6i z!?)|M$syTDl$Y`WS?aa(_rjCbtv3lrjhkYpo=zQUF1v2dyyQv|is{y9#fGfH8fs+M z74fCd&3qA@R@u0871|3imSUDYnQVAF3jxrD8D`}L>$ysa>}7UX=`CdR-T#e3&9ULT zs=@8Z3>wTDbSS8;#olt%^qWOWmwTwlVT220}6*@8xCm ze|`I@_yc`LSS_dYU_PzFp&d8hN9^JsxPuu7rYgor!@m6BgL^DQfZ{&!t#Ex`8+@E; z$Cv~i0<-l=c1(KQP1F}gPZ+tc#ZDzuFjknV`L$>-o0}M7lh5@HAW_v%jNV)8QQ$#L z)BeLTuTkz<=?GS*tgEB-X}%@`XK_t|Fmcn6-{L7lSsm$nID>#vG|`6VPDZ`ooj+MIFbg< zh>eHEGZG6Gzh`M%ifZN0L>&CBSbohCZ%tn5S6sn`KXJh)6E+XSD;^h$jM+c6pjNJg zfAycyciGYHFXwxp{uvUxpIAs8dqkj3x+^?4%&;&@$q(OeN?8{EwC@7}r$=5zp+(|S ztpZkGH(fF(+!j0zjSi8eCKSG1HYC{5bv90QnjAW{>27R@<-gYambk$ted=OSf6SIA zh4rH-P)fBAK>gmaGP?6oynJ75Or0(2Tcf$(QOWr8oih&bG7u>$79SLovrF~LTm_BOC4QgnC!VY9}YRGWl40aI{~ zU;6EXea!2^m@EJ}3K%X^jcT&9MWTiB_6Pu@jEi8WS-~X-7k)y0hFu=(%T(6bzyDc~ zK#p_*@RC$WaAF$8hS!HN8w%e^m zoxNEa;|d;!xr}z2R-sZLq>sZcMO6`%Q$X{jnFP*SM10n1Ngj;)t&pY)t)7%Lo6HV1|!(-LwT=mAjkN3a1!2XfQu{5CA@Hlam z6nW%zl(L1R_)?C?D?ZkjfyTXO3bSwl&Bv`D&5yJ0a|VVo8KWPqTkJ~WrSTaAo+clL zSe4rgwCgme`CI0Gw4So6f?laLSK=tcl1g?S9l)w8~vv10lv^kwFWV@{R^Sz5T}5DXJP?$;w(+_ceu2R6baQS!rg%R zVXbzyp(I|sJxWiFZx88Qf~U_n9JZldx)a(}S(b4CSo?%^A^Z5oM1ahUyq2D7G4OZ( z+c;MO_3v@R!VTlw(JK(}oCJ02f=G}A!!Sk_1iGhD+Pp2Xq&^5e8g8sK@@fORA z6qf8NG#-V#Qujb}>^w^(B}x+Q3YX8id>MOBL!n1|Ba_GppVS=@P8rtT{AmDr zwVhd-#8XRc8ZFYruwJkA7Y+-{G{r#~LJ4&TW;xnr1s~NlaD(20*M2-z#8&K>-7(B4*Cj$@!5%qed$2$PMg zQTDc$3UaM|ckT|BZ1uPrhrR2^ydTm5y^XwWH~BxTY~>xa1xh}hp+3?NDy%ViGMr-6 z%Qk5QXXM0UR3uwX9g9lMxusSa__Cnb4DE`_*ZIh7R3Y&}u+n34*oU=VcpWW7 z*p+2Mz^r(NiB&ancu&>xt@O?_mC=}Ug)Icn&62{RPv6e_SD*bl&ipuxTG`T-CpoNxdDuco1%I5Ip4C9wUBH?e)^On0ai4jvy@%_}0 z6J3TSSlJgFPqdZLLovG1 z*NsDI^rR-2G?(@DXXpL-_}4=09%a6)VclV+&yH1;Mf`4$96=Y~3-{U|A14lbWhN6el>H^KhR# zNNu*mZGroRO2bkhhACd(bBbka5|F|E4y?0-hwsOQdG7C9Ky07Gi`VKMMIi{_f?R zXXt3h^}LlK0pf3JXfurKoN2lY*BhM?I=VR)l__s}k^jIKXJt0ly3OMs8E4A(sfo5D z2!8idt>2s6pP@z^TDq*kcr7fuS?5Hp9O2r;hXX60xh)I8Q zKR)5A7Hd!Br*Gz;+=t;{jQWv@umT?GHQArgTpgzKg?zQ0R0GreA|rPz~Mph0&UWL=|C zsPBvXX1z#!_{8SE6r8S(Ijqy7Hj?N8F5$QY%ZabNzyB)^>qT~!boouCt)w)~^0IF^ zx=2GXYjA{wy{`J~x-G)`kQZHZg2DxkwMDbSi(R;-NoO1SWGb6^b&0p9Ja`>Xz7L2h zj8|}^tnLXYlr=?oe^!uiLlL?->ZYBX4|4pFZ$sh1#i+#)qs$?|v8o2oQ)e<+M&vQi z(h!X;&K$yA9@CTZf@a6#{6s>z*U;HX%D7(^M?&K&zrmiyyzW#({{9f4dn)UDb)4L=e+?M+Sd={YttQ<5nS@(yVztuK2BclRZdnUtTzPv0+HgjK|XS${ulK#qQRGZngl6|bQwvjglCGK>kktfYS)XE zj(iQDL~ORR#%T0C2p{~s+xUw*JdT-dwr;s>Ab7UGolff56WB#DBLn{UF!rt<_!^HB z>u@zb$)AS++B8u};+|+GQ}ZV-rn9%$?9n~aoMm)kvG9J^b+Qhd)|7X%iEu;KolfFp zC86`En%}EYI3p54cbfI>FXs*KW)3<8YsxEr=WYhc)M#?xZ0v9_1JXI1x~*&nec}o= zC;M$9{>e~ot@Cub*av;c!TBj!6?bHvqqpsbCqAD4l>+s04E0Mgg9>e;ECb39YQYA= z<@C6aY6DXKUX@%6(ve6?3?;tk4!pf&icw&8Vd4<>B2^5)?p?@5F37+?<_RM9Gj4ID zqQ%3O+&-T5p;CPB7%pK%QyYi4^tMGpm^4#uJ_@`Tm!^Fm++lM}?cA(c8_Od3;Ao`5_7n+^CJVsdD3=1k>a2c3KT<8im6zPV?; zk69~C8Crz1f(c_HI_Q2zJeXDdJ4V3kbaPPp{_<`_n6 z09;9wjaQ}dm8a9KzG-fuQfqTcOgf1XJ<5!WMg58O$(c!=TChhoHme|lO32`EftL9e z&c_@UeWy;fWkqUG=@r&SYq``p$MZSSj8Q2Y$yHRmDST+G6FGzcYg0MH!`|QRpuxXE z4dE`HG9jVklS-uLI2pN)EuweyCZQGYCJr!TE&PP5qiv$zM%Vk3sz)8#R(If?!C zH5tl_kNK|7KQ>4hCe7SnbU!QsZTQLD3*+py7XI$HkWw9;oeNn5T7+65R>pKfZMpKo zuK;~4bBy2M5$VrsvV?o$^Wf*TW7s?JL+fZ_1nyVY)4F&`SWev;H`c>|yBY;<-5(Yr zJLZmcgK$C)O1=g!lr7(SpHk<_%V6@r^7Ge|Z?0sO_^;xNmAAj@P5FYSaRWhf{nsO{7;Kex?kJRplU zE-}FzQ*t#tvHIZ*SsB7pN4v|YeXTAD>w30HYf5achL|-aBdK zGr4Y^x2_=icOk+$ptPr55C;n&U*o4qbC|3$)dq?`Wj@+oalf!1erB~)#T|$TyHwm2 zY*jHJS!zdf@n7Tb48!naA%i~In;Fhc?{3>s{M@l!}tdd9}tpSQEx_g z-!QDHDs!vmAGff^$R=f=9jcLTFa8FhJHFV#-A#2o^_ty>IVaqAZ@LDlz3+7IX#lU$ z3XQT1I>h#~QO3RF`#xSP6S%-8M7!S*!e;Dv7=vxwplG0u;FnZE#KV3A8JB(Zs7ybCXeI2T~Ti$R8bjS!?qE z=C5vYC5xN@FCS*Y(U8mvg72!}=}3Y?KSjLaMQ=^};%}N7Lw}m&pkBv!Gn&Z|1k19- z%uG5NF2_BY!};6^k)NVXf>E$oYfwHfCl#xZs7q;V(pJz1OoX`uW<8Zk!bM56&+Yc2 z4$GQP2N(20HoMCU^H6U&DTnePi?XwzY?$eUB6H38RRBozm2qCTt{c8?mMNEvqhV-7 zLlbQM4Ob!c+((xx-Ay=8Fp{q~K>2r~dTImZ+{kkxD{W6hhkMjg{=1t?1=UKj|0kG~ zcO98u1wT*3NheU`^O=hd->M0>);arWtbZ;5;kQ?&GLrirCT(l(k()}r6 z4)gM~*6miyra&0QK4|j>+1j)aBiW185hS%kI0hDVWtr`8z9G7IQ6hGCu*=n%5kn`z zGHnqvRB}fLSu4=vWW(PGlKWe($YL1?k0jsIhO6v&#?!pJj}+b?HHyH+&^$|lrqT-i z^A0E2-a_lnKR>ykdN7_YQ{30&36DWah3|QX!t@=Fe+pAEr0dSQ=aQo~N)rCkA|HE4;<+<6VK0mmE#UBao3`TvOXhd8yDoPf%|)KDx+a# z6tWhBy2BhRD%&Ef4v#TzAfuULCH?&Z&`*3L0nZ-rRNV>*LE{ePN1yyp?%pBP>n6BE z5@T+1di`Kuk&IuVGt;6|h39o=k)ey_s~Ey47jhlM;C2hGZ?4#i<`;#I3n7cYy{AI#1y*|uDAs!W8F#qON@*l7 zG0S7apI7porgn^a^>{F7)Tmzv?)Q*$+m<-pexJN|x0$E5-Fq%J)7Pk634wicU&Ge| z8ljS(PyfAEda#s=lIbX$FGjESk85cN{}V!^km^CHfiO_eb41N56`mA}g6HxZ1>q1T zmfJT4r~*`z%c3T(&<)nUeQt$B`HL?cC6Xgt(7g4RaF6>m<0IoKAiCdIy=c~a7>6?< zYTs-M4P}XZ{Y8~_HS$!e@%KvL4p0ZTnXE6BDh3y? zhG=s5n(0n#h9(U|m6Fmj16#gL=_v;d)lmA2hkA$mIB{|j!Jt~n+a>)~0F6OUP2CPm zdZiVFWE2SM95bO2Ys3G?aDVXTX~BV5Cr^A;ncHqW?6{T;Pg2+1gSfGZy;BYRx{UoW zjIdNuVlDr9U;(rc=76`ZsHz|-Sem!gC3=*F1Y0nZh7iUgqU59Fk7DW3S1M1eoGCgO zTt~X%LqM(wp|Z_!j$4(hx{{iv1&0yLi{4+Y*`aaQ3Q{XioBK7?qQTDU)MrYWl3JMh zRDQD6EU^V&!8#khkoKGGF4vlWcwKrDQ zvcBmr2^5w!vVYzE6!yeNgyiSZ(*Oa-Z&Js-aI_D7MAnMvIluV%k0B(2?q@@xG(6># zhbx#SM8r8hBvWYP0RN2%-R2kfd zWePq|U+xcM?2n#&fO7YNl|M#$5W(5j?RIab88pQXW;!a*F$}Auez~hBJIkM#Fder^ zhp#=jTw^O~EU}N3hQDqQ;!#;o3l?)=T zqMM#=shKW|#|h8Eob@-F|In|u2rvUSX^%%Q+R5ku+EsGqzvJ%;{#_=WDo8l2TAn%d zTqR$2V5}-DN3xIi8@fIg>D|<3PsHfe8UoOkJNbLs?$C&-)2WzE1sL9ozLM+HbXGJ zXf&P!Q7+NH-SO09`F^V(X-B#iUIzK&>wuOe#RW=L13ZYFPrR%7q}ts$KjmJ9=Va}# z4Ue{y+k}R$Oj>!T@e|9T8w+*fUAVdAQjubg*R6>lqtz)O%XswQL0RnfI{WAJW6`Bk zv6W0Ef*S&YIr)h7_R>ek3s{naesKS0T0WliyKXW* zu_S0^_v>>p$75P+`8(37+4`Y#o%W~?O3A?HFD0lv|lF2NhVBA zsUKxZci$^d#O6-A2tME4`fwna+V;Kc!2p-gi|<8NBoVo0F!P^gG^jNwmU0oePzb9y z-rw~f0rM`_=XXkvT1%rW;=rHIuYb5zNu>c~8uJZvDij)89=6n2K_!iQ*Pauz-6=7O z0PYP3w^7z-r(N*TkIar&j!G-9^ay*0bot}Oa6Gs*^*}}I|9a4`3J7DnaOP50XT2I&Ls&PM2vC229bm&R>b0*rBiNpm{bobF)>)Ty3~sB_hCCf-J~Oe zGc54(0@d)8O&_h*7e^^CarU|h#u6-X+f+KPs(EW_LH;mcJowb~tu*G(R1tAHhhW&| zBkjvQMi!c>$`t4{@naX05($fgws!3o-ExPym%uF@OKfzXZM5t?%u1)v|aJ5kH z{gcwq|E7+7i2prH^m^<3kSu09g|@RzF>b9V0X9cb=+!K`FKB+JLvE4R&FtAU`FiC; z^KFG7xNps#VPW>#Pj1P@y^{lw>;}HMlwVXE{bv?{{@JwHh`7Xcp5nR9p=7-Wx79?$ zjOLSSi{l}&qnkk#?p-WPSvf5QYNFGq9UZ-gak?Ai20_1YdL8IR@7}ZOwX`5t)SvX9 zQWs-&`17^KyXbs2K4nZB@xr8-yuW%O6=ut2c^M2n=eZ|iGURw0`t%9EqGauI%}H0d z87J5+kZvy+QUwCHywRhse^{ilC6`-#CwSa8zq@LB2li1L%ye((eeYN<3DegST>n_@ zFGC+~DfOEsR!fsq+Bm6NOo4EQlYL>xPZk0$eN9YMJ~L|4uoX1oK1B1XA~c}c?X-rD z@k>)hWp4bCe!d&|;#^rEUs=K0lt;LR0)$_97;a%07kzMuU3XX-$+^wSPrId;;OT-L zZ()C}5xxU^aPD2PCoXk`Gs}rJ^_zUsYpjT+d~TGZc}cCPW5`n?GiLAmj@bRQss4qp ze3t#USE%=v&5A}bOzF?Fjw4H2HA-P^I`M{+k_x2~W%_bfVRrW`MLeerE|p>XNUCbC zowsj+$I41W&7Rud_3W|bR&?k&yY(6YM|(lq6VWf4Q>sEAPf{dvKS1>UY`1tdIL5i~ zD%?4E-Z&ND`MvM$FrKF764s=bU*jhxYkNpO*0c6Te?R7QC&rW4cD*#Fm-d?R(In5> zbvUCYm-zjimR8-8QbHl->)r>{8Ts%BUh_L%&NTd3Kn~jZ<@m;@$$nJh0W-b<+8BW zR6JStW~k}%vHno_jyA#8cG_L)j#? zY7iwHuWhq#?yL$I;oE}YY37&Fw(YF2dUO*BqR4z4`ZXHtAj&qR%*`sXQR*9*&vus- zuNLw?FEc&}t1QF%&YU_|s!b|SmP(2P^KZ8$l>+*H4EdBQJ9o$qQwU1~&eu7WZGARgXCeG)-Hg6`g9nw5L?5)dY_h15TX|bR9u@c_(l|X(yqiMQ5lv-0 z4M{IIFU#!6Z$v{%A{RHb_EFcOXV;^VP^&E7<4Po3Fjmwk=)#>vtPx_MI6i8yV}#hs*2B!1;A^Wo0)=CFs<%-9xcmAOfd? zjakFJC(Y_nHr0=sSxiDn^!RiE2IxsL(h0I~##_s^nS@`yp8?!XPJ;j)g{u70h`yC$FsLNhs}m=7*emeuR|2Qx=g$Gx%5|;*=J1 z(FgN2gH{$_FSHRaJWjLIGiL2z=u5DkXFcID6#_n%xI=5$rR*Y_?)Un7lr!Wud-vBt zI^FcD`f@$1KC_W3+-vt$R0Tp^(8Avq`zA^q0tsHsM1St?JRrz9N0Ds7|Ada{GUjsV*@Dzv7qVGFt8!y>za;*4L z5jUBZi)}t%y(=b>Q-+I|WKw;OJzHT>e^_TVQpken`7jco<7=|M4Z5odQee=L>z!H` ztvgo^O0WscJqXkk^!2!n%=oxEn$rz15XTHkv)D9_|6wig8K}tj26@v{b6U+NLxW24 z^RB2ZJeJ?tvg10P{Qc$oY5dwn$J`;%QdFPrdfx80%t+f2b*f5{XHqkCHPVAH?twjf z7EU~*dvB_5@yO!ywUmXybGC!A?c2wZ=2#1#p`|=p9fDZOZyMAm;0rrk6HY-4ie%x= zccP<9ddH{4WE_F%S5CL9_0sx#8BUH7UVq4x3-5i!8-;RZ+8Sm;YF_x3Y5#acZg~1J zB`_szX@W>9s|0TqDbwWndeb$XPU_e-F<40Y^I|?83?AYmr*v@posMJi-EjFOCgpqY zAs&m{3)z(Q#!PG)F2h=@{cN*9R$$My5pAq?DKSF@ zq2%IbBSV^VR~emA`|z*?b9!E!#jS1An|XYMRxQ^Bzw<$93#?NWLIi7nfyR3>vVa(m zR5Cr_y??uo{Pa(P_Jg@|%L~28jWBsR>fK1rE99T29aVQJdLR#8g`>D?oAJl^?c?4$>m!(zj92ev@qxN$L`qh^EVf^)@wr_}fm?w`5sbPo@o;S}{2kw)m$wT`SpR8bT zp@e_!kwjU2{`_P9!DXk^fEMNYdfGp1e4QDx%9Z60L@L4! zAKLaf%>q3U-t(H_I|`R7z8_R(KTT&L!0fn;?AO`&)EId7^80nE!tCg)rr|w{zE@6X zropY7Y}wAR1borrGV-B$?p4BMw-5TM29VGn`DvqPXcccBdAol?9>~(kP>pVpzMmLv zXt!^`VV&Ob>+*0rg?9Ws%W%g&OUU@`N1`SMX|rK^B&GBz^a_tSe+13#Iq5OP$`!$} z$bE!6K#t50a9A^tA)>=!!F~JMNZ-<3HV_j~KCIH6w_hUc+7qZn&(yYSe^gL|$-+lY z$`COqo{ z!jXmMB3bD^(bqXZSj0j@Vm}M68XUAhOw_oyQVDj@8iLne?c@Bqy8CopGuZ1cT>)PT z-`$-DHRx?K!uAu_)le_b?Km69Zy0T`XaQet-eQs3A4I;IPZMPDJm@2|ULqUeE91^T zKJ6mNwIQ_!thL?wH8(yqWIr-)_;4VJ+~{Gpdv2O+tm(WMa{jv}BxYSve}{Y?6j@Pd z{$MSI3#s{*wAn8HeLF;5$xpi7Nv-L?PqMeArhJF^She7qr1f)6$z#h*^*h3a$-+>; zO>iRh2U<18%l>BdPJh4WN+Yc(ZSs%ge!(S8M z2ghR*6x-&G&u1~*ZV4MoJQT=WBe#KO&G0a*R zNZ>Sz$02`IrK}OQn6j*2=jj;2?LHT7h1$VY#`EJ-w`l>-A3L#^Y+rH0Nxbjyr!CB|r{LzZ7v2!9bi)H!DM}bwF16 zkwx|6X*ZGOV=bI>X^YXGS;1vL()~qnd{xn};~qzrUj^p^l`+jQ>Dl+8JjWu$!pk%^ z9aLK=Xr}AyWVGG5LSbvVoX!2V|Te6OiEixbNk`GJxxrqxccZ+33` zO8-}-;4LMbb=^!XmUC55vHhtzT3AM16DQa?(xZUW1m4puHBOYeySiNp<1}Lip3ZRU zT2eiqJ?(0nRi{#V6CXb={o6vukAiab`D}jyoR-;v-Jaza^)D08$LrhqHSIh3IkM?2 zrR-(2s{}Cx6Y$2x>2dWbNqia}47l-HN^mVjSO zqGxV1-=xq<9WU@JXHKnXg(KpLL|L;Sx-2bYpgC@zjH{Y1tKQD<*}RmJ%+$X)#K4gn z$yus=+!@V$f41tFLbE}2j48TK4e-#Fm3A6eFZ4fYOqsq?$<}BAKUu1>a1cA3>tT(< zeBLVU5uF=tDDxAqP(x{meV1o8@&DNR%BZ-OrR^XgxF$ewcXubjo#5^+gWKRP!QCYU zcb8zn-Q9z`4X*hnIp^M!d*1KYUW>J-H{D(Rlyp@+cxT&uN9?zMfsH>}C_!r24c|Pb zm)vfbR7OK=$}p?+TP>A)D?&rIU^5(_Naci>#f(h)FQmV5OaXWLNl~%aSX_%B~@sMF}`g^-hI97JZe5g2iRg&oWv=I zeT#*Xsgxb65??Yd1DwD?2?`+H0AxAT&2ZSNAinhZ(Ua)|+V{bR@j z5)}fmjW^Z%?YhJ{r6hrZTQViS+LsUv+O+@*{`>h1ZsH8LnLe5paEO*Lv&YULuVRsh zKARB#7ed%U^MEyaLq(pp&GCbBn9cd1Qh^sJiF`LVIqpRL3h5i)&2)$vg)SLA;73A8 za!aS^_ksJ2Yc;r^yrwn_o2;&bl&2ou?x2fA7jIx}R*$tGX$v4hXF($nzB?$IP>14X zu%D2Hwbk|~65IPci>i>grD!a;*^dVLOdRCd0o1Ay?6xLNQT+&#+0-}7tU-bEj#n6* ztbN8WOJ?Y-1(J)4wH=3sKJ0Zm0!tB3tL}PMWen*#_0lkm{eP+494OQu21HSN>YLN7 zUSb3BLR(AMl=AuM^jM`v%v_0WVU%+24RK3El*;9r%AgNXxJr?Z>pzI}w>=&2i6(UP z*12RbjDA@4=r-ab2wkGHk{zY4?@n{L%cas)V9;jR^=8s`7l)LiA^SWAr)3_IQhbg~ zIyhJg_#jjhU$c+kozn7Qfm;)sdY^JCT6u5FI`1Z9_~e&@`;d=QaK#a5!fh?`MrCqnKgGf5PR(8; zJAR0N><;m{6LEV9HgfLNZ<9~CRoc!Gt4@gnU{-ocv}1l-YdQz>-Jv#QampS!DGC%# zcx?P_NP-_RBsmH1Oq2p8X363wjI}nXzR|B0{AwV4QplnUJgIk#`gz*e-7DB(d=JFZ zuG&<}eqr-Sb9Q#ca(oWVNN3>B^sdW?(;fH#OT~?)%BwP>A>i~Qc;i-YiHMJ(?K=96 zNu}eYOfBK99WUFR`QT#k#n7^9AY-e$`(3@Kqc^f;ynn&;UuOXZ7s8~ZE7PzK zp0HTaISfr#k^JZ+wkSoEQ~d@8*9J99r^b9`^yi~()8i>`0*8uaqv;@yaRv?dl19Kg zV9gZl!Wd4>a`G7qt#;$$jNL{hA-s6jV}iBw6U#5y(y=RU`QSvjuNY-;TXm_M$~ zMqsJF?ANw!bfA;6@*tLl^8W+vGWfunZ8l@p%a;s9D@;teoq&X@Ltj}f?a3znG z1y1Xzq^}lDn3yMYtM0GPb?)zmJ1adXGJZbxciDqdFRO|;%NX@|w6#Kco*R`Lw28e+ zMeu_-x7;$^n9?=KmM)IAtx`8q*Q(}KF}jj(_4r$5>h4&lQGTQ(weAXG8%DJ9{JGgI zH|3v1q|7$z&)+ryrd>9kziS4kxXR)QGM6>?rELJmmpNpzSomRQrF4cKwq-nv12R93 z-!!}qpdc34EL<&;UK_D~6L9!$e% zxeB1?pPC#F%Ev-;KGKnBvUV(Q8$G3w9}o{Ah>e~M3`%4&I;q|WXUxXDS@ITJaEP}6 zO=3^SuxiGw@(1G*R<8fE!dWe_RN0b})+*H*s234KQmxbs%R$QT4GF&WIfa0GD26oG z?gg6Ame%oMMg_kIv&Xq$7CQX2BlbPvM)r^LSzDog@aUGFhv!_YW?J#w+dQBDNr!mL z-$3$Y-|orzEJ-{sG!fvbZdzjuX``!@q_gQo!NG)+F-A~ zKM0>Q8&9|DfX1LywlLV)I2qe<&{w|h5_4Dl&t(oqfG}XX&J=a&bPU}8;J>szgz6Yu zfL9<8u;A5FE%P=frsYN_H+a zhmc^&>2#**<`Cmb>G)!y*Er}xk=2yv6#`c0AB(yS`(5pRy?-{)qC`M)2s(jySI6$8 zSg9~Ll4DI>^&)<)-b5byfF_^+J^*o>gxlwex`BpM}|{g8oE>1)CR0i4^j4AiCb^ z#K9!Ad5G73YMaS%x9oi2tNazI(o_@2(5k!+!7Kx`;pPZe*@vE9>f$6pqlKeSnfTL6 z$Z>Bkv9)vGsa3{bK*Rhz`VZi9LE=ZedP||eO99_6qI`$ELb1XBj@!w(-tnN2ibGl- zQV?OQFA@QRQWk!TGU7~0InqCSX6Y5D&OX!(ENo6?OIj+rRIvJ^0A$)e3C|xu6hrjt zLK4pH9Xj#OxrP#-RI7E6LZnSVCEjR=XJo8bWlM{w$I|U(EXTCOwoF~&Tnfj)spxM5 z>~AFd^$76yb#SESHmQ=tXQ%w+x`^2tS)WX@3(F^_d?)pNdQmvmI)!DQ76RuhYh6mnKmx?Jaqfd;3Y0jKi1{2`$-=9*+6VF-I`lK?9coLpB%+HL0u z)Ne29_YZ%YO4xo09YO=LqjW0`Mlu@8d>m@gnPVgl2I!T%j&{Zc8-IrZe!csb|D_LI z5}{(wWsKKiogqSIKe)~em`P2ZN+DIv@~0$`DteNaaKkZ-jD@A#lk9lV`=5W-4_Wj> z;^+Xyh{{vyV#9?al}#3GvbB_?p)UC%g<9?smL!HimSkCt@La7}Qo>3t?;rd0dzpPd zh#7(Npqo|E_CUi4V<)Vp)RH5yw3ODTY&MYI0zC0CuHhG7|D$96-k=F?*qN!tzygc{ zJk`XJuvv)~K_=y_l)@7Gbd4VPixgiB8w;K{F3COrnd9G6EeiNBGSOSPyYOu=tXdP! zI=ibCY3{JOHyilc9l+XqMV@Ig6a1L^@2|awr8)^tFe8W$|L{njLER&{I6HPk>; z!1u$69iy4jhvmgQXkz1uya@RBZ@FQOTRGl^;cHCL&j)v#)1D}&B$Mt+Rgb##D(^^? zDb6p&k$%Gf)ww?ZA$(vFn%IEFMv8VZMhlCd2%I%snu2LLM+{LkNo~@wV9&xs;xuJ| zZvW}Y9&{J>uTL@|!eS@CJ58g>A>NdvvR3sj$gzoc$`5UZH%|n3$pdx_)HPMn(8Kh9WCt9IvO^7RYE;eP zQ}rw$t{nD}{><7S;~i+ao71&phA5ebXLd|^T^lnZ@o^_rHyF+4!fpQ8H2aDTx$G3hZmfUY%y%i^s zP1ohrVbJ{s)1+7yt-p5g5zJ#l-sl7716GEt@@|^Os(%1zE0>^cm2#$m9?iM@ve zx~J28u^Qkq&^b{;P9)id_n~3qoOWjjt2JGy*0n^jh4H)VT#9-F)Ykp#@aasJbM-AI zgEltDvUP3Ml4a?^c4-Imr1_%B)O&*RA%pwjfeDE%XeDUm2zn%Gw+wH1r z$m3au9uCHLzx0WJlz5i4)^vY zUphIMzF~o+tmEZDcq%H9R?P*^``YddTH}ECA#Csmn8&9y_hnyP5FR`YAO7kPzKrVG zk@`V(^6|#@>+*9jrwMo2x5JVR@Qc$&!+q8*IFqp614i@iEr)=*jBti;RGv>;geb|{ z4w7!GN4lJ3^ZH+FfWNFRHye@|1>&Zl4>3%7_oZ*Cxvj8R@64Gu}D{b2r$!&6euUIynnIAIOJ9q>ySCZp;(vXOdXS;B5`6_|%}NXw%M9oKzo~ zC90^tcQRZd^ac|{{p@9+8{~(tK|!1j~^d0zgfydUgNW01=I{u80?OurW$q+MMOglsxh{sJ$ZxKheE)jHod{S zejkPIICO{-;O^WD!n_X$*=KHUHs!YdrKZ1sBJz#WwxIKwiiC*}9rRMLQPDA2xr3us z)HoiX9*lTfI0;24g?5?8LAs)visry8Z(GGC!HQ}tTT)=JX9fEq#{PzhmL}*89?I-f zz--qR-wo3Wa4!>gj!mgzCwv%3uA|<$8;KL~2{g7@9p=-GyA~78z}LtpqUo_!#ibMh z5Rk<)1D5geQp%Iw)A5VbeoghPriIAE)R8LnHc|YWCzpXL1(@IxPwrdmvS~V? zCZ~xWx^FKs>HR#`-e7XF%2IS;tkral5|;CL3NvZz!))c;4|Q+p{>iSRLC#N}+N%6d zF;JXi1AgfpiRS|SiYwJ`ErDh0 z4#R*)3BI%`YEX-~cXr}B`u#_@`1C9{9D6Wqq8)3dWzN~Y!4S9YB8elztZexx^qs)T z{F}W|f0PP66N(gD_;3+`uzFUwEfm| zmj^Kjkiy(~#L*T9M>}5-hw4KF4-UD9!tsGjSWL}D%S-BkLG>stm*BqlGJ2J@O|yq| zLl_}`oJ~q^Zthwab4Ivi=?FKtwmlyX3cFdy`M%!tq2}j$CP?G%lg7hE3f2gf+%dy^ z)}{*>l*D#GW)pazMbU_{n#>jbrVHV50Jfb?9eS=6GC zeBbwj>AY~kd=PV0VB(lS_IYikQF+~mspMdzC~gWq&;Y3vhuisXCXz_h^E2rFIOENw zFbeCL!yYCQhsQie=2)?OOhRbTC0B01$54yA2^d6Vy*^ZajUtE&1?1lsGAra&) zFi>1F5=$v3vq=`i8g4ekRmV=u?eP}zH=DMi5Il~nLkvTJkO)&?{vp2ApkOxp)jN7Z z(^rh(X+rLPG(VxN;S(WCX2g<8Q?M-%cz=`<2~?~z}(r*92~sU@7xI2dooOne_c87i$sro#d%&w z!|VR(-jNms^1fg2g}HT)|4hLVal7yYOm6Y%4*XK0>^DT#;r=KAvuv*hLohAqktMF~ z#b>p^^_QDQ&3C&Jb(dhmZYx!GV|?xJSn1|?O`j;d7G>5`@nk5*xc4&7)8<<#%D}X_ zmPVtwmWTJZ9X~T?S8bxN_rPpnt(z6(lcwQbW3oGvRPSd~LLqY+VE1dx#j+K*1y>dnskQ`ZhH z1fThqwR{{I7v84L&$N&(c`;OV)jSI%XvY~}`3Jt5d+qNMw%YS~OW<68CGXSQo>nDU@VAb^2<#9JR~)K~AF?j5Hz zfHC|oEuFE*%@a8<+mwQEVD8GZtEaoIXtl_O4N)E!zeUT^ayCMrHSjK4dBd&?{=6SQ zZyxtz&~{yQUW_8iW0ob~SZJ?3KmGY~_Oq^y?l^zs%d; zF{!at6iy)UX)~O0jxbz<9l2wL{$nZ(vB1|57j**F?Os1|qgwVTbPFg^M+144mBgHwx{jeB< zC8YLdvrw0U);igIYrxo|;7kUY;tk=$L`WBy$wlr0f(5}MU;ca6I#?_@@uO#y*j(6* z!UX38C`PNn{GqrJgcM#fYi=#);H}p*5ee`HQ^lKF37&r)+~qOr>Wgk?T6XP@cER3NP>P5Y&hIo*bXv8;Y9 zjZ9^#b}!3gjw&G(8_{gp=GX(wn5+uCi+$t%6F9n)SUbiS)MC5SWQ(VyFki2um%A z{-~Vq`D$~>|Fv}RcB9=!oRn=PpG)xIAXFI%nfUTgFeZ@13lWsOoBXUMf{||zLM?;+ zQU+^Z0t-M67@Mg<^Ovw+CaEST)Qn4c=;dL3lCCcjrbuE zt8HIYvml01);0w$b^ct(Wig+G_u2UV`wo}~sHG3kxa#6=terxtyQXX2)C#QD$7dY9 z$~2(HAfgXvIcvQflM!2@jAxF7LV3V8106}8%6rl;)>~X}#~l{;K7$RwIrqR;OF>Qi zxpAR5+8X@g8#rK$>>7zV0q|>%_(RAGSyr|T46Wnt$Rro=GDbx&kkCssTjS@nTEa)o zLl&U1v@kv6DeI=mrZ)G_r%QI^vL;o#1i!~l6z?4ST#d>BGV$SjfuTkYfivLvG#e;b z^io}y|KgW{JTVD-t{#kw^bRH7-94G%Bz8~C0%GR3>aIDI7 z7ft3@X$akZdeC6$*tzV8?`0`8pFh>om}R*b?tq=0SnvJv44<{sL|M~Hjnqa9hiLu> z$87D3LcyO=-#SP-s@A<9-<$2$5v!F!T2Ymf=f1SYFN!+*e~Hboj3JYMM|Cgby(R`- zwJdTJtoptdbL7>CK=S;kxv;$tnADfW!)k1&bNJ(J=n{A{e z;!bC&3FdQbYp2wg7XeQI2F5s-w)@1LJ5*rrt@I+RFS&rq|yo zmO%!Z=abRi%F~92zBg=@ZGB1Vu&v$+!CXV#3~7zztF8|vDH%D}&`PB! z9(ppIWpZm(S9=2?lChE0GCm8#Sl`+l=cW%)-b?_&h0X8Km+yuOw3lxCINKHKcfgL*+2cS?>e3UX zjl4FSmpfQXl%&EtkS-G#y7#2R_q|@SIWH6p+oOz6Bf`+2hhb$R{-zNNeA;L9P6+Ro ze}DDb;Oc$DEyqi7xInE_D_o}&k`f-CZL(cLC;wJA;Y(FkJPHQ=5ILjxNIR3+#!1Xy zH!i`r5If}#I-8m zuHjxh6bQRz&%ZQVVgiO-wtB~hZIROAL4ZHm(%^`0N;K%h=Q08G%|h+va+r8%88Z{f<+ih2R+U+ApT3|} zne?+yxpHV2uzU|h=r?|g5!2LBj74$}O774ewT=N0Y1CD5JXe4F-q{DqqTaEn?haNp z+D0XY7Yh$t*u243vF4a+PIc9x?-!9cS8_1uj;yv>-%}@F6LN)esBfwt`BUS>SKQ34yvkXzS^530y7U?QJ6M?!-v&w1n1_K z#EzI^IIv_lBXkR0z4ka0!GfqH60e*y$ir9N)QvciF1mx=$N6t(G$Y6wzYL|3eDA^R z{zwt?y2}h;!Y90aobft=3-pD*-{3JS+@wcdcgf@~HrOE|M}OMN;3C%fm1l0IAN&HIQSxZ=B)Li*kb zxfIYTn;>pjUodETrDycqOF+%Z!1jue(8iyW%v-NB7mQSkQboP~R`!}}J4U$cc(GQ+ z!)E!7KQSmZBFVKV>e#(Yo%y{CGB2p1yDu(3%%rx7t8Bn_=Uwj?=KjfSnUu?@>KbZ&=D=FiDLHrRg1j{?p>Ck28U~n(gS2A zqdqo38#%6}k+2I;3%Zh8lNZ9PU7KHHNd;QF zPe}{-Bd3259)}dfs@EkPPx%kR66*NTZIt1=PZbENQXm{MvHIz!6*P3vOjH^ALmG6> zgsd_#QByhm5aU->Bj|r>PJ|$s;}o?BrG=;Lh_yNFnG`S%9>Lw130)zSvAs!Mu3a>AkVIbPYM=kp=MuUKsV+QD=S zL5kUBxb&Ji1RK;{-Gas&YR8{LToF=BL9ipFvh%_dL!c{l9kbMf) z6G14~rbCQURwscm^4S79_1?#!{;H{*sF=3E$t;i<1U-3FBkDqeFi&{hQc|>0dl^?n zmS9WpA$bvo&3&0%U0jS!-CQeN(Ozix)vJ)XZ-V`6vs}ykJ%=@XE;bi8rr$$bEh^}B z1hQj!5Gq}*8_p&^;BMr}P>`5EGmrZDvQQh)3a#ZZeKe+w12fMD&|mBEr;1B8>BP^? zmj$_~InH2<%WQDb+&eUIvb-D{Tsuy5YFmtlOoR}Mz&BD`l++^Np zl#-M0!e57$q^k974;&Ws)%GL=@t0||_9gi&jMSRoX|jX*?lqIEI4H5OAM=Kzh#oZY z0eYm!5t_#!|C} zb($`Num%AXLSl2)iro!qbP+qk+%en_i7z{mDB;Q)WuLzulbAU$Z@}5*D-0qOWjS8)oRF8Ci_dcpjw&CTg~zzwu0G+ZRwtoae_E> zE72(QdH4997guEx-{1(fp9?8zUetNvA(i}2qjL*m5M2aIDOw)_)luP?`!imLz&J8V zqT!g-ayE!onDSF0Ox9*VAY4_0D!jH@`;(VJ%;3l* z(-QELMme)zNgp>$tr${*wQ@a0!|CqgUFcu60gnP^V*cZ2FJ8iEd1@W_@2UhN(#DC zdRO@!bkwM`f_~Togl#_rIeJOe;e2 z!ET9xj`G}w={HoulrkLhJH9dU5MpZ`iPQKIOiHN7)Kk4fuu(_;Qe6t)5}{8!-mhvW z=5}Qa`{LVHA5D+Oyc}vmmKFWzsUe^qC~Rd-#@*a*6(r&f%Rxw$#<)vpZ7Ycll&}Tf ze@ET&{dNK+6T`mjjsgNZx!!8B$rG)$I+RP6E_)F2+6vFrY%?YRtGqMo&7g8#j1P6& zbWeGB@!6osCEV1y-g5i4=ll09TtngZQO`Zj{%zF`l z?eHcnps1QE8dUXYYU?P$9ZnBt#!S4kyR0WKhtPO6x8})}W_z;}yuwV(wVPt(l+JVL zpHqE)lHBo>kKT88lp13Lib-yKZSJus`?`M_g5q5hqvr+!PBftg=ngH*)x2Ex$lU&V z$>0L7JTeJ0!-7yRCh)DW@!}f~Fl}-gk`gg2b5F`38rX^%9)_p5Vke8Brzd_y)uEt{ zU}P&y$z9N0x+0F}IO4=qtx-bg$>1ad_H*fb^eV}5=a0MXp1_RtW!9H3whPm?A62wi zW|MSJiW8QHM|L9S4tTFb7_AYg`)FkXfhQ~H!TYT3sh+!+cF4pgEyB|x@q~OvW+obX zefD?dEAAIbf`Y|VUD2SCW9SnOQ(kM7p?VH*KnZ~0g+#sQ0eKwDNceq)5CSV0O$bc2`5I=f=Mk!3w#KtS|p4ms&P~{40_E98c`SfmPn8 z;BixiYb%`7UMh_t`xV6=yn@UTJQ8BrA5^gS+_QQN8COQ{aD0{FrxaApeq|Pq*}@8Im{Z zN@?w*+`R~Y&yXE$T%rqVlovl)nvUE)>2#A;bIiBeaRIGSa|!uuhaP;ViI3m*(G^3& zP@pkR*jqsQ)7FI8H8scPgjLlS)&Q%zvwg~RqMTGS^v=9M=2Ta3fU?rNH3W{tLF?zv z^kOr;dOciN5ElLpLKDy036H76+j#>owaRQjXI^UX&QXj?f``xL!JXxvNEhtGp=<7K?7jl6m_a^G=IGmtK7cO1KP>+Z<7-1kPY!-8fFkZL&k zFHT*5$6%~9+3kAxXN~Jk^KY=2{vR_k(pMEjwL!@OEXM>K>qYys~3y6CQB9e|b zwTKVCTc1Pq#s1&|9H{;2Y&Bg4eyBi}-}<3`?&m!q&DO zfL+b_)C#=3D)-wE))Bo+&2Y(!1Yhc8vv|trK4daF*6cOq*Keo<1s)%;md0Bs)xkjUR=k$%v+s!J6Rs53bM2qOlwKoSh0O9q=bUTM0nig z-pi_!eKgL*2wNGFNnTFoy+F6{@BQ{nXPpsHT_^-k6A6xOJ#Iz5uYF<&0W27- zef6_wl=Tg)Q72nsDR)c^i>v)h@>%ZB)+YKChWB(4p1Wov4w)CGuU70_vO_OO`A>{m zFEC$6a#}&Tb@zt!+9d@$!!NvO>9L|gP-5OpAs^B>Xk#6BbsHxf1q`Acq$1k2u{N8@ zq&LUce<`9vas0itOlT5ZHi}TUeKUohcSG+A-n!`r$&%JI2*oy+)+(B`1V&fz@an@dJoHb!8Z47)RBG@PkSAlfwVs_D(%jm zTDm=f`pZb|XdjRAQ(4Vyz&V30+g34H!%){qB_AUQ&96cAq}o{qpjYWsC+6Z7JHtid z*r1L3OLun8G=^-!F|0>Pv!y}LEcd-%k#*3!pjq0=)T?`k%f0I%OEMh=ocQJrE@`F8 zQ?B*_!esnMdVm0(kIlhxO7wX4-Xkv|3fz|LD-5hq#=x13{9a5#06QU%f9O}y@nmTN z>FisqOst^Zd-^OR8N^GAP}}5EHJ3KK(nf1f1#YNB$t=$_&+P3j++xaXN*^1OyI)o$ z4+ccj(Pppo&I^+Uft-rYOvt4 zi$iM^!Q-?#oytOYiThezmv(+E+<0JEH=%E+*mWd% zQjcddhO(l6n{;d`WIZEDL!fJm=VWEdv5sZSee=!}XKagQP;Y#ew&fjn@mF17PcMtT zO}sJaZ9P&sJ>k_x`FM3dL8!zwZ*HpyQVHAb|CchAtjtvLC-u1k#%rWepfW=80p}2ol%8TqO@|;e7laDlY(N zvy<+Jnk4v)*=L{QX}`1}_^F*spn~^C3uv#25FRZG+`JQe8cm2zru5ORZ1?>3ayU{* zPg6@J>tmw%Ui6+W%1FRFhvo8MtL7YQhj9oBt-EZqZdy*MYt&!Rd!fh(FUGPP(bJ*g43VU}`+4PLyYJQe!qazo3Y_&n*C28Ej@ z_1j;$Ca}W3xcFqhtOU!gF$Tip$k9{0-HAA(zAn{>yl~0p{C`rJn6P2Dxl0cdzkctW zU~TkEp=8sZF{Y-#6IBNO(1b14_YBDS~w6>_53|h%k^PpD=A`e{PgN z;0?_RW6J@{W0$qlOy!3$E)$}0@5i7~A?WFVMQPg_@Brg$>%50oIkIicio|x!_Iee^1Fvz!Xigtc96tEk+ zu%K5_oZleL_EC9T=5Qu{$h-3|ou_8{GnoO%0obaxd|4O!+h=rXo*I$%H zq>eo3Ll=+WN4A;Li%YqE2MOT%Qaym`;K5%0LJ@Se{rj^0pCrTYF~q27i!_|c-`i)@ zxNXWL8Gp;xK$297R8K!KI=b#`2Ib*%UeN0kYSN!odw}`Ua0A;=g#Tu1frt742Q7v{ z=|ATZc@SN%y0x@;s@TIf87{5_e=x#8yxTSM=wg)?AqZDmFh}B}1Upqr)QtJaJupf{BI!wc!}@E{9{0g)cg&#Ju_>5>7YXyC+N8I62jl zSKOLa+wRXSGAWW6yIh5yaQJjXzv7!kCg}jjFPO{c)o+=T@BNgOi6Fan0(P`bDqbwq zb7vYp{-+nffSlZZPiwqKzsYYM`#TxZ?~9PWvVAVjWmJMQl(A`aCc^OL8sC((->FB{<4MVOVhrWYqIA>fKnn7-4E{jxTvnXpsVHq&z# zxXvY5i6xnld6=Jlen;ztwl8rilDzuQAev&YWVyAAWLN4;xR=t+o5u=l{W`6Y-GIYeafgq4tD>AM;4H$gl6a%0mQ2XFq_d^a1S zSJWX;ku(5qon+7)-*AxsEugF{+d973h1xL6dQqFiR(+T~vA+<=3)w!#th_a1NpJ)Z?H0ef*Bu-* zjXW$ruOMB|afsZ{Taiw#up0$ZI)@H#{s%F+AOYgwHOerX;VGi&s0gf0d*QBrkdUl+ zO#^$f(O@(Tfm40P15&R_tC9d2t5_veX1u~r^*ER^X5PQMFzQZW{@PNGzX-vQ@DI)) z!b8$s;nnZbvWLP21T`KCk}#=BMh)Jup108SOT+S7MBH0$7W_;g+123*mw+Ci9g#&B zX$a0?uLK0=eXXHw-u9~ z!51_BwfTb+bM^Kzzb-?i7oDBD1VyCEB#o?{ByjW%MlhCxcwJ@k+yaV(U9c8z>Ro>M zC^u+)Up}qf`hB>Nhq5Cww|GF7`Q+TSo zI8Rn(2y zO;eMlfgN zn7wdS<0s|+gD?Mq83WNAJIasy03DL_z4p=Ybr{2}{dxbtFp_-SPjEPmBb~E~6D@ zD>V^T6s8OuQe1WD(S|Om8ur{qM=LelPy*S2to+-N!am#mTdOj8!saQf+-ji-N|`FL zrJppPekekxXl*RXu+cgGHOk4sfuvOV-34^H6tX?Kz>$Kn@-^*U#W+SmJLVkZLru2lTbZuxx?`9RMf zm_X|)3oIv4>==ks6T7tkVrgyFl|;O&=t>UGj^(4%ZRMiV`owS@6|N)bGHEt7ntqak9bd1> zPNC^CYPs7eS}^r(&?vmSPrZ($KMSto%q~sK)u!nJ4v;T`K?EAhIL!Mdl`1 zDlo3F93&SgG^>EUjjX97bDl1sdla3$x~V{v=f5^?x@~{#Qj!7)YvQE9D<>(v3vCbMRVtn&|~E8kaq-1!WaX zfQP!OUNNgsL%t=Ts8(kIvjtQY=bjACLb!#r?0fG&^uq74BA}rodMTv#)D5v2jvFJa z{8H3Zc7GMP*b1KKobSCr|1U3~xF;}VSPr4sA}gwCTG*GNf9VUW&5|T z6uXMvjD|Yi_dAV%=ADAd0|L(bpj92CY){k9_)5vH+OWscxVRdh%%>pm-307-F;P8Y&v8bJEJp`1=ieUSptcK@_oB{bZr9wIDF zhh^q;PKf!J|MrJ?;`t$edZP_)(RDUteC?~*{it5D8w`91D+5xyLc zGb*(OY}cImY;DgkKka{p;CT%(V5O225mPAAKwC*bNk6x8e{P9}biX7%BI>HE5<}^q zU&og?X^I;tMh}pXk^i?$Bff)tA3h5|nO0i*rD`xtEPwKv?I2QWmBUa#Osa(ABXs$> z^m0xF|LQXHYz8My&i_>lcn}-apKPa-lzbvbfmvnUUeezh&>Zh~&BS_CC;*ZqIxNEy zT52X|LS^SKHQ*R29s0qW}7#W{GZ>jh4{u;Psc1XQ-4qLflnTtTy#j_ zqi*x+@sx2b+Aj>Aav`;*VX8$8WcMqVw2%_~@8teosmuW1qQOYlugs0?@nOE0O7D_U zXUo(AG(q*zDbm+EikcK9Rd(qH#*vbX!1`kP!Q<5bl@C0>k0L}0lxBDhMFq-Rh_@=X zlwSIwIvPZ~)keE9&B|_LI!0MvHvcH}1H%Bgk}D^rR>-onhvO3NEV9^^R(43?K=rc( zX#Y*HNSb=np=7;wOCBh_#h0?rbLVFe-f5RC-c$utq1D*Xcy z8(HZR|G$Lb12wcFB!h87(zpXmY@!~{q@3omm>~nLpwXa+`n{YYOc=TY($_EcU36g{ z|DGK32cpfYVm1|zOUn!~xN;R!+Ro;bg5^mL!!=q$EO-qI3ROMG3f{Vfl8J)eYs7zj z`8yHmt3jKT%7PMe(tUD~;UcFn7BxQi@xnd|~&$X2kaap&Uk}$}dOxIPrzk^jI7e zPT~c0iin3lLo>8U`}VE;sB(93`2Stp#>54CWl~y{f2}w`XvRnoxYn|5EQ0s?dRrhDN$N=aL7^Dp)>4j$gJ0;rh9dfN72= zq`M%$FU%zRLSssjMjFa;U$&8Or}|S@|8&cKAsr$bBz1ojuoT^ycrDET{Z#spq7jnf zcfWMh=HF7Re_~b-K>^^|is1r$WxBPL!H9EL{2!D98wR1M6V9>RQ)yr=HEgPo`li!T zE-k5!j+yeO`n#`YCa&dlgAouHZbpUISfRjG}k}pOv zvAQzXXbJVP^kf{ynp0m#r!pBp6wrm0j$zj1%xpb4bPq)T?`GqN`7(hbz*`q{Mgt*V zHdO0q^?`iMz;6^^WqDN_Q&x5~@dR8Zv{qv>tV{b61g=x}4ogWjGGegD(a|b%xTZ0v zjJ#s~*SvCsQBj3(TS|%>0>zSu(h7p3=QDal*|u$iQfQCXz>NXsJ8qV&7!x(;>eJ>@ zxD_-Nz@4-X%80F!+7TpzMj`3VjKtZ$jYG_;%gOKj3vJj!ea}_*EwgG4fmt4AaIEqH ziQo$1I_lwH{RV)63ASE$8zJQ$JBi7atsB8V%lNKm2(JVldZsFvjiyMHzHsl;3Bx%X zT4mW9>s5aierRQ(lH2|Azl##};Rx0kkO-4gWq|3_FC=0a87Wu{778$Q+BXr%FR$d537 z8z?EF(`sOQy4oZR31@gxaJyMF!C6}-4c_GrDOetG1H?PPiQikOZ0Y%!rSV**DMUqZ zj{g&B2!`@=$Lx}H>spej=D`CYxn=@ABgRS8Fvaz86q5EhWJg#v8Av{Kouvs1YP(G` zwlpkRUNRN85Z;OC1AL#TX`KY?owVNqf~})t>8-xCv|NW~6R@EZptvCnyEkoVZbSal zx#0VNIFW(T`{o3gR7hsH(!jN~?fL2HQtvG`(vB0Px(`J-Ph~^HaeOMRpLhx*8X|id zEDQbXi9S137%R=5Zy+)S%)3CZ zF~6^H`$iF&m{8EW%yK|n2deGkKVop6A@u4sX@~t*U+~Vb+E8U-XmS*fjA3c8K%}&J z1xz|cn`P@NrL!s(8xgb>5Ql+w5|o7VN;Z<}@tiC5*Y zI|OyW;rutA5XdJ@H|QfIU_o-6QB*BR)I}6ZT~RHo?U~`+`8_a9<6F2Rk*HF4DCb^d zVItf~t$Z`8h?+# z>oCfQJo>o-HlXBRqH;15OoTXc_yI@T(j8kv_1Sn-aZ>wd`k{i*;X3ry{|pFV#ixa_ ze-Bm8mnWT=hp%8O6%5~5hYX?B^8%$bG;EMKh_ordm)bYNS1(7ppaL#-muDgPYEl%` zn;7KKRloA)pRdsWz0G~a#4(%eOrxS0?3S%{EH|bm6Ta)1ddIFLx1MMck8#ZEEtF0H zV>xQ7$;;;dj!H3^_*4_58`}tC8^<*Zc|Q9Qx%Uq5Ulr@1*RP=%>C!lZoyxvVB}2P) z9MZJb)0GTVYYm!J_8nbdQe=yL?a*Xu&OD%qK>+ckzrelDcy}hB9uPU1J`MsKI;dt| zRs3rsvDz3el3mCkCC`s2Tyy&^F0HMYAgW_bz}!plqIN1CP&w?bZP0z(Nqa)D#As*w`No$Uc9q^d{TbE28z31eX_>argC_&;sB ziclNcZm}UgV5e4IW)sF+{_ZsOo2GF|zgA?jSThBxyQ(AIM@W1CH3{WKn&8?a;i!HAHon9{*w$x51rv+QGNXsd+_UcL$#kcNgZBa)wAP3W-+|L-GpXT&S{ zX0m-k`qYT!{L2P#zntX|CJm-+SKr6qpmj<|);u#)a&-H&mNE;DsHSWtFBvf42G-)G+RbX(?vrvU|nku1(Us|L8tjn}g$LK7ndb)GGKn zE4q6}ixJe}h3vGf_q8Hi|6}9rANg%R39IMJAoZV-#Xjp?G6*_#c%FbAt;7%(iQ9+T zn8M{{LwpTXLmB;4CahMfkSr8(m`f=oC8e=e!VV9jqEH!P$1(t3XZ8K^aq@lc-p6o0 znM5GE4-~dZxVB?!fw60s&a78&Z4p4DGN>GZcN|3l*4NE)WH=}-RrIn=u+hu8L-luA zowLXo-TOKY8ml!JIm<%d-XFopMOr9Z>%i159qLd|h|5F0) zedS;hv)yikZi1E9$%z(pu+)j@H| zztE*EUGl%qQH;%_#gMW$A+mZQ^?5GG#5&D|E>bE@`(rT|vq$i}e>gnPv^{gmr}MB9 z`#zqLIJL2LrWFC3-f&~()wP*W28!;0@5%fKqxc?8KF zgZTdx^tzri?<^|FB=i9&fAbmXaUKF&l(#w0zq{&$-NSl#tGy2NRwk^aNZn08cLW^N zw&E9`eQNr9-Ov`8^>k?dx|wfj2_Qx9(=8#T@n>l(kNG*7dSVg2{Aa{p=A_KLv(18< zdlW~U$O+|=1CzF(z8o*2>sRS-A&w%T34m zk4Bwk(2R0@&D<}yNk1z{siq{gU_)wmj`h9Ex{hYzj<^q}UjO7_@|N^0e*l8`}7wQh_= zwKj3DSL59UPg%b>z72VqF+oA6oh3+~!G_rU%;DL~%a^VDZvE>W8$(9_P#W3dq>^x% zREMQnj6mv-RV95gpZ&w*<7tY1R-BR@`Bt+YI52=6Pt3DNFD6#R+?N_iI8T*~cEERV zGA+!5t~RTxYx}|nvwHU|$(UNVd3v!;et&=CAzbN*g1ipzm`JT?en<41Z3J&HD$9|O zgxP_&YQ>Zl{u*Vu0bj_z4fCV#IzIL5XAr8n2*!#gsh zF9$gM^>oHECZyx%>du9w^XW1pHMyzuI3+rmQ1$v zXS0-6b{i9j8uBpRLkF0@5bdp3m$mZv`W{tYIuJN4XWx2QZg_zopxJ{qmBsnFq+7M-m28@na zenfJOq(#JoZp}&5O<-8rtRTG=j;=QO~t1p~<*sG9ISMmi3BlUOg zm7={UW*5WU=tMR^8lh9wW&9IB6tfFF-)_A4X835^wOn;9xvrGmx!E6AcB1jIR(c!; zFY)A4u$zxeacCNj$GCicXmvYVuLRbNAb>L$jDsa01AJHPCl$CY-7RFf7wyHK$fGb1 zvaiz^qJ-`(UTE@8fbGxylN0hVX!Hzd+B|J2R~h&sWL|NVZ}djZa2Wxs!xvl*z}Us+ z0WhUeqsXf3-S0TQ{KvxN^f$c5@zsox@Tr)lY3XEKyv8<^ZW&alSIKPSmSyO_y*^EXsjJkAt$#sUkJNCu8x4_Gb@q=vEbo z-KIexocXllGL$S%;dK7RXhzqnEblb>`o(7kwU`l2GI5Z(O&`zFw)w2b7?4?|2uLLB zdK2pmeoMjI`2K5qB%|bj^^lw}D$gs+n6Kvw<~W>Sh#@+GHO*C%dhHVDt}>}dGSef* zeYx3P$5*Gtq1A7{rdD4pxqciTFms8!o_PT-ovWi0?#)Is%Z@mB`xnT{7tz~;4*U+{ zs$J*6co`x_-dBHK$MN2u9NGCcEyKz`OZMjX#|W#AJl(8RfLt=1rxEp?E>S^o4@ff~ zs&`1LPdDZzMjl&?-!p8Sqi97AhbKY9KeQ#tWw9Ky7H!XP(*Zyb> zPai%x(fGMrF!BM;d(5^MKX-+f&8#TN{Pof19C+KD%6Ad>a4H7J8ehz+yfJ_I;kQTK zAI-se;+>zUuuDmR>ubyKA}r%y`&CQlhbja=({(d_Ew$X{dRaRxGT%7{W$CGVKAOU=bpeo$-3j194|*lx7HIY7-?8L( zjQY^ri3R$kH5T5+Jxw71CHeB;dlqI?h#m09370wy!Z8Zy$@OEju5`{{Tp|dc0g_X> zb2BKudyV{v-tY`b;Q}K3D7vxvvuNjXZO%Na%y_fr*Sgz&_txN__RFZ`WYevu4YI#u z%Jc`ooO6;O9nkR-uob!=kJ8-4Y0hV+TN$_o+;=j!ve>|~+YQyEo`k zBmnV=L++znEYNj%3;1SePLg?Fj^kYDEhzDB#mmF6UK9oQ)GE!Lua+!&-z?z}(N`lV zT)elU2PQ_fX7+iAl2^8pH`M~*HzZXCVWq)}WXOo_9#WbItS=bjsIBBTtL{y2vO*7` zs&O;>d^_n*bO5-W)^h{^+BoFQy5G0n$W>Nfo~axHB@jIRLC7IsY~pAvH1JA|+)~FN zv10t5p(VKn?2I5<;ROt_(Gif2?_~}d*hCc)9milms>PZxS=(n09-}6^>yxT>>Igiv z;A63JvEJ@&tdqj^vm!b1m}#m$dn^!sD#|yEE84Y4{-B+s{e@yPs$bcxSzG69d>|o2 zPGb5OcD;nW8y#E7v~^&sxFfkv^TLIpW)GiM&QafFa;~Cu zgtTk<{fid&6#Iu34^qR2J;zOF+(ml$6=T1VZ&ljr0dBxMGUUB%f!Ff+dQ322K8nTU zx!>QbRo;f?GoiAv+7a;$fD@>gGJc4o5|O|~b6*QPIFBKe?H zME$Y5oGYE(%pXa^M%}>$-3IA(_cV(wojE5fp=^4Ji#n)Sh{K#`vh)X~IBXczK|3tE zIx0yj0YO!&B0@OnD9=WCB_P}`Suy<+_g$Ew?-5`nvUM@D$D@DZcmp%0wCIJPF2O~y z!N0CNXPL;*r_t&YVH%Z&m_^x`b$=J@k3G-<3(IGRRifq7w+ZNFOy#7R0fYXAGpt@4 zL@Q&pY(G)bY=0GPi&ET2x;9gIo%)kav5-%uQayLN@vjF{aO{3ZW`je)5&*aH_5qd^s&dNRa5s(wUD%{Gqsi4XS3?5ML>=(>vVF0Cl)++w4|@T#~S zpZ^6uawSg*D3ZiLZNb<-?Pm5->1CgxLoVy5z=~~m$%9?HAJA%5U3oulqz`MJ;jAgL zfnIJQ@8l z)wJBGIDWb=E|YCX<+vW8g#-(Jw|%?ZgA^UyT^U*aeM1VT2iWd z1%q3;FFr%;Ma)PV(fJIcb_@FI1_g2aR`d%TaF+XEHLF>W+_9!uv|7n40KG&-F@NyvDS zbaHcWxX~o_!-O8;LiK>%FOST= zcXGq#&igW#kaPPWiYG9keTugFCXTlRDGN?5yZcRv(Ua^!G$RMSh4S#vwJIxAhBARL zK#OCXzsDS#^?bLuBb1aND#$ZGCcDrd_Sp9ks{;wTy#yR1OU^9PK(2rVoh7totqSUQ zian9uZwDGWcf|gl(d}PlITR8HDd_3%Ai067%V}ss16_-4@~>unGMYLN_M305h@S>H zBUw#?*hzQ#EF91BQFOo^u}huz>V$6)V*Xs3xWu%ieE3Dcu+eJe+?@OvW08_%^#GU- zWDWGE5s}yE5CcAX5wLbhhla|ztki;_fgbduA%2dFZ1r)DRZGPo5f1ra-=Ktk8|Unl z0&5jh(;Jr_Klxiuecb(-MGy_qaPty;zts&Q{p7ygriJ^uC)%d(z59|si&vd zhhxk>a;zF5cc|?lV?YZcl?we0Wo3;E-jZ40bCRW&c8Ye2};P9_&dvTq~~ z>N7k!REd&*EcMPO>3BQ285Smq{JebAs1(V@e`{KbOaxn zhkW;!rXo)$&UC&>PNa)cD+LyBBv|LmSG`8O7jQhXfWY%G<1KxSdZLeL)#qoX+T)ZfPDLE z>26^2bp}Far9Bp*4*BN&BOSAA>-5fH;}T6HYnFWnqDKv0gjOY4MSO@=FFa|eNPa~l ztpkNwm>4k`LI=m|Nm$$EXuZwSo8sESMIMw7#^P8Pu+nH@-ZWV2VIWqFfZrn@|a!IS4bM<(QW#}Rg3IjKUc4Q`PNDfUd{8~1yvQo)Ns)GKWE0Mpmg1dnKT8koS z>%PKoaokb0MQt%DE9a@4E3_2>(oTCT{5ZMA4r7 zyFXxbFC%@ zrjNxX!KlSCf!6hD!+lI6j(HPXG@cC|ldb=q4V$EJ3apm3hQsVZY!3%G@$GjDP`gXS znlTXAq+YNEm{4+Mrj{ugwpgje8*iqZS#1?lKY`ZV?>0ED9us?y1%q^iyh}jrMk69S zn!^KKID6uX8WuS9+Lf#$JqKFl!ey0^sU(g-$ zsw&&zt8^GpG@=QsJlg4d3bl$O$277i56b?8~z#+5+o>q%W5w;HL>iM`?P`PQz&jo*(Tinm zkihGR*ee3(cji(Zb0P5N=c{&gNAaR5@=SMU_i6C7^PWH!h3A+kd=bj*Qlf+WX*<$_ zCuvZANCR701cqJu6}r#4Nt@h)yOV#ZzU!TXq~2{xJ{lv{gYY1Xpm&anotG}Ji*=ol zX&u{X!$P+=X_7)3fp4-#t&??+bC*vE`loJjJQF(PxP)}w^!|CeM0rO&66UJXZSKZH z)ch2ose=lSwRm%faOUG0{R0Wm{QkBLCco-h_zxnXfNr0WVRH6JAck_9XYw+YfiU^` zr$AU#=iq%r*ED@^X5b~s&8JMc*?qBrx!)mGA?q*^JV$=JHOOfBijtLOWwk$39%7HI z|Li_xH8k@yxe^+Rg_sKx3kryeG+Lnqg@=fJb7?(HQO{h_cBo9LXB zj*dc{1wyDn$ot{t$~Ed)eiO)8TClXnA%Z5x@9)|w#n}Ace^SkzJdP~?U2cqP zxj8;wYHt4M6zl5hI*`KtHzkADDZlj7ERL{mq8@(L^OLF7kmnvsG3PbMu5v(i(l3m< zftuEKT2oFKkC?^@-X7ocibwq!XHQKGD@}>%u z=eg3nUtXV<7fDVktlM8fPAfIzZ-L zBG*@XYF+L>E7qQB%LjM+JB>xVYoBh*_+AyCM>8^yRimr6G4eNdWPnwi>z2%xoRkcy z_DX+@8H3etT{PoBR2<{HGLLn?php@Me}0mp!aX-yB5@q0yP}P*C}`gs8Q$Q*L~}lm zWlGwsMJvnbX?s1vP9?7Cgp`ofpa+FSgGVc;Jjb`Y+Qv0zX%3IQ({QnEGs1+ zI@?u!jBo3;vzzP6n2qnxTChP}rGp-fi-?1QzBH5C#&zcJH*?N^qNZhLx-i!*8D~rj z!Wy8^<8#$+2J(ff_BAolw{DJU^x!8jBkmcP8wEe&7-}tRFv7;_(Fi-9X>H*Jg`Myw zs=hYr`owY&Ux=zVRvvW|s1hCG z#Vh@#)y~}TR>|@a(M=b8|2}!^8fXr)7{*bSoD4Rh*`=r;H~Z zrm*c5fp0}qLlC`>ann)Hm}s+)yoW}y*5}#tKKWYxr1&v-P_Tv%|9vu}Dv^dpeSFeB zo~KvwK8Jv_BQm=siu(Jv2w(U?R%QD%HJTVFIi~9AfCujHvtA>gU)ds7yt)aC@mRhA z73UChX{1h>Plq5s_36{qC<1lG*Z$hFh?dvL^lfj&Pu~ge(?hUDYWXpmQ-Q3YexLbT zzpg*ueBTLBiv1eFhN&VY5^hKWv2~{p*!(p`X6}lH5u%BI!&#kmY)U5o8OJ;Owcl48 z82|O%;>h@@tg#;x?H5uY3IB#crY44N(l<$p{#$P`kU|pSPcadKowL5O<>my93I;tE zRwZzgbX{LNke9AopLXzx;xDd2K7h%lAzDmO`T9_9lts<`lkp;xcrM;@c)a@Z|K^}r**`SorI1~8()E;&nbrZ;i z`M1&vOM9)QV3caY$K3KmV#>GMFU!EGOn04RKwTs67!qBn{&Tt4gE zS(ydgNheEsAd{jRxXYvL_S3-)!OC=8kS9)x!S>pZ!YD~0^N@m3sOHu2*V?nUYTa5b z4zE3gKZ|8nZdOGSc4KY%Z7@rux1bmfKo*^CQeD?sSJ$O^T5mf1xx{vaf$0~&a={J^quJt#1BoqOO z&tkoUB9RoRv|`wv@wShJVY))%Qc1ADnZD|DN7( z@^V#F=n5?a{g}MpVC_>IoxaM5cCUg^-ISz22Xs+MmwUriaZg$3*G@dqNK{ZIPfdQ3 zA0{j;0*dl{WwfdyDXtPQH!}SO+JCdCy zYB12{)gfzWC0Z7xM6FZ0ewQ`&$-BMNNLoRIeu^Gkgu(k-+R@vXV-{8YhutJ!lB8=Q z($WfZ`!c7s6vQp$!b|iit!3)-pypboq4n(A8sebq%;s@l6-fMBwpz=i=r&8DLa&D3 zPWYPrEktmI5#S8bcCFVnW5lGQFlXDf-5;ap=5^VJ^BwZ; zB;hE&#~+WkAhf}!!K?-AG`s5lmhqX%B;lio^nlWF+BP(EHO~>G24hoHsAtOe)!Q_f zc)cA?j3{PdU5bo_sZ_|%^jm(w9{veA*zD-ayfp}Z?rWB>%E*ZKh)s%rQ%vM$Wu$P! zNcTo^_U?0zk9Df>j+|?*WrkgU6j3WsU~YbpVJdeCH}RR)F?XNk6OJ9uXK`vB>CxI; z1<9(}Cd!)|55zVF;)gV>r%}#yCZE$(u&qqHZ&PQwncU$h^jl3gq*sW2Xa8tO>Az~G z?v4pWi7?T(u~9$gf7~`JJFjNzW^O>6>T3~P;FbHq``_Fx-2i&88fY*9;;Z=lW6Dj#$o-t2Sm00GI}FU4lolpxcD&@ zl!ieKwH;~Aq(cJlUergzM5L;SYW5z!)&{o=Jo7LGH;56(bwwFYwoGqV+cu>`1ebUf%xB!&xt*V$WR#I`m`}W8ws~6F z2OD@oYQti~v6b@+>`Cm%ZlUf|r0IAXnMR%;E~Za}v6*D(VQK6C)s@;{LMv6@Qp^Tp z)yP28gX{fOh17xwU4v*v?-NiSJ2@{|-FXxfK2DhZ52lV8V za&780Lv@ZR>PA(0f?IABXNE9v?{4eYSik-#RP2+y6E2U(yYlCNtZ4+&S7St3-TDMI zi@7%yruDs6G$!(YgoXrUWIA_e+>hcQflI>j+#GDwj5==DylIdh(A*#~Av%Qnv56G{ zYrz%Olty1}?9n@meAXl%3Wqa|@z(>y8`(;F+3@!%&f3!j)ZRK6Ai9sYvhqtAnCJw} zqq(VS#%dhFv3oAqG-QnSvFd3;)X_ijt||#jc#7(&B#bI6ng#Qm0a={=XVuZ0C=2H- zq(^c~F0_$SMj8cA=o^2!YtU@jNrIS6);iM4ghsRn8viu$YHBgB0O2mJPV<%phqfnU zCPeC-n!%K;&f({?_WoBj4@-aj=5W#H85YczH%c=Z&Rvgti15s!W*zC%Xy@7Zk7p?W z>;H6}f++Asy^mM@_|kFfZr0z&n|(k-kfWV;i-zsQJ`rIAD(X6#pP_>3rD~{k6}y_K z{Oo~B;Rgw_i?v5OK#=aYf`N}vn*BjvPDh~?FYqj`u1a&EN)4rKThQzajBMtK!XM7n`V(3bJD~*p=0s~YHSK|spxFe8*VP(CyPUh)5 zw|fqA$co^WyFNeUJ3Xb=Bedr!?mWAKvgYzF;q?gA{Dit3KDM#gTZ2#hkKh`?p+!-m zTUb=4GbO1HhK)`*>hW!u@Tf9zg{)BE1wu;}i=lx)=k1=4&U*|d`Kkv$S5|d=CrGgT=YXSiCo6@bg%DA(53>NQ>r~$BA!XirqMPjB_Y%z%gb9nX#hP znCRS3f3|Y$*L&FoevS>3=spK{eXQ6-dD5-Eu~?sc@D zd{|zjg5~`;s6)Y8r3nl$IgR_U=iXoD+)V&?k?GL zz&FF^^!xq*K~D)0{Ijt@Ci&u~A@tJMK?-q(HFUB?^tST!DOqlXRxy~!)-ejSx{QnG z7PxmRz{J=gV%)6afIvS-S+KY{;*^~t{(ftf1mi1G5fmC`QbSV zYTqd*y}9`wB1&W*gY&>7w|j-*ta>(@7QX(<@PLTrdgdSLMPJ?lltsvptkg{MOyLgI zUVztpmlcIkve~Z_q}N2d38kS;CSxCOTDQa2LQ!`c?p52^R7=u;XUGeNwz&L&rl@J_ z<&}9#dIcD#RJK2r28;cof_n!uNc5Tyc3Gn|2so=XWx0I|Os^SC@i2Sh+&OZb4<4~1 z%Zq0|dCa}Uf6ZBsmVareCtQ@U%NOdd;B0xJFAoA(+mdJbBO?oNb^20`e z^xtoEQJdCt{TeY%4)wR3_}h5gHm87GF$y>Z`+{AO6=kga$4@t4zt|r{QOb^cHqCol zcM4^^Z)scp+$f1eD-AqFG=?<7N8pwb{a!9_hKZmttAe{#H$D7VdOuhJWow!qDQi31 z4#Yy_i!mV$r~E+f5$aLsr-v4oC-9wC@TG_U`Pcut(vTwDz01yXzT)NF^1XHQjJ6Zw zL-Z==9J9|8VIwrvz)-Oex1US=tt(*3;4UVavnlXnj4iukH1>2lKy=y=~F6{TT$+U2b=lWFJe9WP4KW?gJO>?fQAA#ytS9&KJzx6Tdj1l+EBQn!az1yijbK7Zl z*Z@?)mP&O9{N|)&HIYLWO6;N4;XeCWK@p2wZ5HU8|LCgrA@a|%)@N;5rgj<#Z_TtE zj;gok4@Meyvsa9oEXK3t7*=&%^-m)4QdNDdnJ3nLHa%Ri7{RoR+k$TvFN=e>1u}uQ6x6`Wqk9sO->j5UKu`}PW&VN` z^MCq@08Z8$heXq=X6a+Zxs@**4Q!*lzY=3tx-_en2O-tzA+CezuqVt740#c=l|u}5 zVpA`t!9O?CJ`nhgYp_DUQMU2F#uhz33-0VmqfHA{dX`1@7pmY!|=G5Q3N^m%I{DW3U_{c20Z4bSu^@BnniC&6+I|Sv5!Qv&Y3FNYdOO}j!-<3DE zKt}|t2G?BL8>5+1q46v@Va_tOq%k>0yM2WF0HM}XF64iJ3a9)5#GL&#Td z8uVR{$4JKZ14s?tG;TY?>&dL=DV@EH)c6zm?wN1vK$7ObO90v-V72Y)0_drS)z4`& zbfUh|R^^c{%;?D#WIh&48Gy|{bT0-TR4CD!#&*HV>3Br??3tIB$bS?<^uu^<7)$oJ z3O`3%kRJPUQ(o5KqJ#q7G0UNcU=BzOW4B_=Uv&iX7VnK_P}NAne<86GRlXHw*m{Y5 ztOi(}t2sc)8hY4A5jAw^G&1)RF{z^m|BKGQt3VKD@ItT|kYY7Q>}O}#M73|S+5x)g z!)P(I{+A@i_>=k2#e9WG>2>AzV>4Q;nKv?_<@#=sOPe!~z-kyt?4&<0@1YZeEZw_SpV;@ zi;r{i4q9w*>gI_%o=_lwlDF6cqNyINRinoF!*k_fnf?oI?BXDtpK3^*4^;ZcnAw$; zmJ{=Gx5#LQa$-NW)26-g1I*qgj#0yeD^pU{dpli_ka3ZQc!3E^L}3q~Ph<`T7mDzq zP^8w5PBRXdPon1%P^Uc2ygjRN`%?D)Sp&NV`J|*{9rr?w__4z?6QU9>_DJ00Po4W! ztgYCy|CN&T=vs@L$G1?jZWXBUdH>%;J=^{l2n+m@c(?ckZIH)1Fd`1?B>y>px_rHR z)Pe6jN#9sM5U0Xe8zO+M>CkI{lVs&a){C{`3&?1X>i~T&7~OkU%z(UY>BtrjzQPMP zKOu9w@G?+>m?^xXGL{~#WQRw;Pt5qMhIZblGqG~j%r3+Wpb^5c zmV4Q{MvZ504J|iFm*sq}zoJm&`L`Y8*y$4oB2N;m1t4CXp4}l8tWmSRv+QR-l!XMFAWy;Eqa6wddfZE2)!vkNlO&MkQ8IkW zKS$nxI1UPjceX5wWs;-cX$5vOqYzz`Ka_HOB9QDSg+yh^Xfba$6{59c$#|zs~s~gLAvC}iKBMGqj!VdkOuSdkGv2FBgdFp5IdmQAWb+sx_vUuK zaQBk}tI>lKzKcE#NBibpL)%l?P8hA4o3X-2qqe(YyJ~>2SiA^%ph21h+ibodSm6r7 ztD>1e$s9QBp7>CJ)gdCOFP7W_Hi>C0Z|*B0f(&|2!<_JyXp^P>gEV&~k6l@8=6kS? z--dy#;+b&N-1yg5Ky)luAL&`J6cnb6XT8Au&rcvCbe$!f`1i2T^3Y`SmDzkqO z$few1CWRegjs3=3M7;gCcelV=pZrp;x;S{*x>y6*X_uGM+*EmpL4bartU0r? zYqQ+7<=`6-LYNov0QHc>pLj_Zt4XtKom(+pY~LZhfkN!WH+})c%tkXdE#q{47QosW z3N=sanux#!8aIx=h&#!o1-DtF&MMEv-p-@%bNaeAZ;-Scl3o6Q2fH>cECuD4oa1Yn zucDtVhtq`>2yD?B3R8}%B5Lx`l$u(oQoC^%x=snN`*YvrqZD4$rZsc3(kd*{>Ln7Y z2*02?c22*;TorOpozKhfdpvBo$|kvDs5e)aKIJzpZZT~&u^bELhHqRKMh?Yh&~Hid z^@$0sNfC86i9du5DOkj|N@-6s+V$tT>C?K-Q=NnYUgE^ICmXwghB=p}MY?=Q zUFeC{dfii6=JM1=rhSLZ9b*GSe?^n6vCq>L_i*0|fCI%d>zGtbJASaj76e;yToeEr z@&x>s+|s)@c>1er*voubY^AijcQ1nLb6y6>MwD=?=17<8T1ViWl<@tytl{YskOG;q zVD|g9P7md0IZDSG1jg=3?AFEjzmI@+{;Tl9u1@ubh?mT}TD#{L z;)>R1pdn*BHV$3-?)wU|(e0-wCxF=eC)}BSqwNsPJg6|outDd8rT?+O#_)>61t>Tz z=4)k`|A}(`di*$S=QG*a2D~hW6+}zo?RrUEhZIC|w&mZ7!H7gSdQ zJ>a&d>%qX7&BqSB*41Vk<+Z&)?GoZr_K}2MK+Q>$$UGoHUf;%^=d>Bf zdlfIbBumJoB?1$x@dkr4q-(veeRG#0Hcx0K@fC22!ijuw#BY9GK zRzMP<8hRpoSRCGI5HVSyg^>;wZ>8G8(HQFK!f$cn3J>94d%(}$U7-g5 zD?!F}O=uv2L>&w}0vb3xM=j_I&vPULdik#YQD_|w z^R&j%?;@jqSNNuN2?$i&;0;JcZ|bO83>bpY9>)4Vn(f*m;1914;3&I}{csy6LmZ>L zf@i8gSKv^0kUzlMdjf94={+1d$XH>xd?kG}02uZOat7DQC~>-ix?QD+S3)%gBZpsB}P3OxIkIt>GE)C%C8A2J`;u9aIjoS$V}+&W#WkD z+8)O;u6zQt*{}>DzNT;A8fV?0b|jRnDfZ{o2Sy~)U}Kt;%-K9wbM|Z7N3_1mD7AU2Pet8#Jb+^1+KEhW!U-#9HCI& zQ8I^ni9I0BbB9B_fJE>6UuCn>YY^yr`IDL(tmuXgG(SaiEh5hLx(V)Otknnnm>if8 z%=hVMnDDlr-D9XsHHPZR>`k1s`rqMep@b3}&+_qbWnJ=r9(g>Z0BE)ZF!nnqudeQ4 ztp1+YZ3~@~5Acl_CWgO_hZR_^kTyw;@#DFZ-O2n&c2V&gD!mB$Kh8YaiRJ#Z*N^3j zkEE+w*^9k*RGy%X<)6xxLU3U3o(JuSh@40l4L+)a$5U56<6p>M^b_FXUld@7j4W@jpi{ieKc36Zpw!S2{G zLvV1idK{x?CMir`OZQDK#!|fharLEBkOqN5AjDq>4#s+r@0LqO=B|z(Sd_M>4qFA6)$Lw1X8r>AXy0u^Sk}|g}>$wcZQNmh|*B&(+ zLlewqAb}l*K2j6ZOP^X^OW$o93{E7qykrj>GBWRTJdfjt|DI2WSil*XlU|hwa@jwG z&P51aFZP4H7np4Qin^9W=P&Fdt(F`%W!vG%nmVYPVQvU#9&D1UPl^mr0)%)N32DNXS z_Pl8s9Eqtth2zxghx7Ega~Yp(`YqV$6o?PQtq=%yWixPaU{{&Df^Nj=NfPd?jt4H0Lm(;1uluJQ1MxTxQ z$PRx=g`x1bJg;=lC?mW+q`CfCaREEji~+PygW)UhrJARm2AxCau;r=n6*CYoJ(AKv zyug&3sYio1^}Z+CZDq=9zFx3`i=?QrMNZ<|wGNVvJp9UL7){v6+r)~x*Cq^uAJ8@D zNLwkHw0z8SMEtw-VyLOK&+h@aY@V6?G=XGA8{3(U^A)Mz?M7DC0$Zz&X~u z=~wmd>$YDefcADWjoa%$YcJDVLEK4)+E=E}eF1LXdGW_2UH?#?$WZk=D)pZec(ce2qFsNFUchIy#Lw}hot zg{4JeT(AtTfJn*0I%LT_L_gkd=g>%4;z63LAdq4_5va&{IT|&2?^#Y$FGBF)htbh< zJZPp84Tz_23G~$+jK-$2pPX{8NVw{Px1&~(?hrNl!tI6{o1+&lS688p9A>vLVzkz^9t9)a9zfp_-mR-a7DpI-E&v|$Fk zLG0l24oDae3~tcA&Vy0V#FbS9(F2&U$a@n6!+R^Z{|yn=dd^#t2xd-Sgt3ckBmbDW zDP^`I_5IM8DBgbvyVs=El#PSV6Qw8O0@ap^)npS%+eiUtZaVl zuq~avf=gB5mnA>vvi{%55a!fVdY{kP>ZEoq*4G=D{)<+Oe@lBuQ@-8`zd|38!TF&# z6`$x;-X^i4>W!6TRAGm9SxN_H<(q?#^lUY|EVy4R&aRzL_u@`0vF{o(Qgf^eEfdx)`UBeh{*HX*;=E$ z`bL>kGuSbb6v$g1_}lZ_?V8?`U!6($_ifg<+};t*ZR#z5k(HQKaG$Y z7x~csK3rEtaB{?(I#d^+uA@sT@AH*!4E-_QJ*JFWkL0dBEoVgF()vgehg)?^vrzvi zj_MSh)dyW2)4=4aHoBiqm1DwDwf0dlx4B$gm$2fLL)?_Xh#?QE(G`W(i@csXPxbcE z6i(AEm+AZ)ShzrK>RVXEHH zyppxYm*Yf4_)WV{cobx=A(*YR)B6g6rUPnZ;zon!%=XH7iE!+%#AzUTkO|@hp`d9t zik_RKC8D64xn+;I?oVEyK3Tc-?gS{GsKvCXhOW|QI}=#M{Cg(+QqUC zATE;-uj_V@_5NHe#k|gQ<=Xp>+~hAN;4&q`qaS=X!1@dHNtEA~I$^(5pkUuLJlpzd z)EFLzai>X(=_K~EJZ1b0qg7!B!PJg$T$z~Xv^x6h%MZ4`-$7sqU4fNOYQvi1nJqOJ zcX)*X2q&*rjHq5w!C}tS6I~9Slpmc*z2mzU$q=~Cq%~A!J1O+hbujB9jB@3N452Zz ziC6sPo~tQS?xyL6)!rNGH>6~0lZ8%9J|2UyLnLZ+#>B_`y)+6X<=*Fm{JP;nDBdTX z?ldvRsPkB)X{YW;4;y?#Mi#~Dq~=#U60enrRafVtk>)>FC}i5fDJm;JB$J2Ot4%E{ zVb?dqg+wKW^Ogm|y$*T@E5ZFdGC_T1_jYV(5~33x2vs?GecXagHb&rXI1#~fHXm{O^$4T6EM60?Ly1@loSvE*B{XuejIqD;4whNAUx<2Qy4bS2qd zKUn3lfxXb<@-=Z>38js^tO~07ZA~L~G@~NGqBn&lyEmjmTygnTNXhs|1l_sgAfi3C zpI-}eUH;$-KqcMa-sFg(`jXX`(r=6-nr3chnK}~quS*+I^R;~1+``QgQ*?{zSkqO>*uDq@wmX0&p6l%FMo>+Dbi9AaeqF-DbH z`O$;3I*&O4bmd495Fp^&3YVM?P3?aEmR~UpXgOU{VldK zCtf6#p;wg`rYZ)5GO!=WL0=lJ!(ZrC;Oc6jl5g?BIZ?sDC?lo$w-MW~gip*1$bZa6 znMF?VxdvH4CoPi&+h&?p5hU|2DUo6uL#ElDJa>uuEi}4aZ&mQC8WBI+vip2uX|Mn1 z*JT`Ls#KhoL18$G5J;0T$4nIwyRE^0SA0PIw$#kxi5!YMb4fYG zK&*?c>bMb=Tt1ASe_C_bT>9fZzwIcp$e;!E-KTW3{q&C?-v}_UOnMMKT7RRTsnWH4 zN7pYJ+L8A$DCk^i)hskv2Rc@^>YuzAi0W! z+=Q)RdQ&f+N@vg1dREgT|yzFf2#ukQRSCkci~l zd7zO$^k|@YLM+7UlORpOfv0}csi>0-xt!*BW4VjkE;|m&eZ*la0W%$f5mcV!shFy1_8|nwa^$!iccwie`QPr>oAhk^H&p!a=gATa`ElFN!_Y}%Ve_dmYAd`OX=)_D1LgrZ`g zCAcw_Jp}T;g2k_x7K8jQ4E)ek?j`m?)G`+AoNn$bk~{Ip3$pu=8u)iAn!P zeKG_m{c4RZx41H7wwVHeV@$XMb@0%qlVTa~Oq)+hqn`S#`XX45G-kS#2pRunA`aGu zjgzb+2@@*a_TUGR-IuhC<2K8BUL4ywk`weXPPUIcNE`melu0d0K=YvR6^!;dHxaS4 z*;@eLhh;$72545NNZ|jrJl5ye)X`7RX-0yC8g8a>Y-WNyzng#KJNJ?--dy#PhiKVr zN}i`iDJxBe_)&se$;3Q0bocHc2S3e==FyXF|P_?fsAiiG}2xHDYaV>`K-(o>!^l4y2>w&vz zM!Kj>|L41BnCLWBtbz`sgPuOMdsZOY9;44n?K)jTP9HSus>1Ghi>a1=aF+6XxYnaD zwd3=8o<|bv?y4DOAqdz&X45vkt)owIg~mcFeaEj3pH z)AIQ-vXlr4j_J3Nl{@Whjk7;+ zj@%uJ@QJ}hg;@8ED%q_rtC90|1{{515&?qBH9~Gny+kPYYc*ZP{pizolLgQ|oU*%_ zCM6lN)BLX5(FI|OogiOJDZ&9!dW(vJu<)QqdbL@z%~PcS2CH**!v<#Es<3y8LdAj> zg#pT$?q?QK-<-FE;PotH2|2*KXgZxm< z(7=&BdFWrp8>Z=gVzQO?{#JVGlMy4`==@r0^_e*SO%S$3hP?koJ2L){>f>}jOACpDs+J=N0ap-{v^lGnIXJb1jWsO3IG=5M|yC>IdDWgvYi6{NJf74&URui5*%- zyIxyYPXeb(yn+@Pkz@9rQVuz2`AO$UyF3Kjz~dWWTkAy`nL!)KFhCmokRRK)tj?%x-eK!IMHnH)WhtyYQ}~MNGL~fp=I88bCp(kzSh7K@&Cu`K*w!ixc%JWPK?1=Zg>LkB@kWEwVqb(<&C zrZ>*6Lgq}|G|p#R4O9gR8sg+{xZ@`5BMcno z_c^k4I#(w^#}@b+VN#tro1=5MYd(NYAB{2;l}}N2f~FZgK>j&WZ&#>-$t6ws_B&jE zw^{_~aVda`$6~d6$WE@RJ-ujDYT6KL z7PD6RXNm~_ukFXZ{Io`!VvTwmH$w8Vs4<1mX+6=vBXI1jR>Og=_j$+Uj-whKYt?8k zHBdW%ILN-}{(QH?b9vkQs8!GJu{I1QO%AE?zq^QI_50oR!MiFs)c5Y)XA1;|Nm(vyto2MR?H}96xxd;-=<#g)a4Jq)R<%oAoc$@wy}A0U z1jizx9OX|DF;%JCyv~@kn!&k!-`Hmouuh+;d*>fHLg?_T-Na86NyCUy~;qqs!_akMBk@1!B z`?$v75p+jbY(ot=;uD`v^XNx*2XIBh5+_=L$0ic1$EWpZO(G`L6g>M(l&!EEMJLKX z+c_O7Qd^};9;Zk-s%dO90dsMChV!GGidC2W9Tdv9Xu_FxuY`4}h@lk?VRJmAj2DmIw*&|L#JHUFJ?-MQpNSa$-VQTs3Y&@*Ckak5!bm z6Cr`YK|NS=PSbv`rYXSrt0P!>0|%;J(osIWLz){i^QE3d?vME^+h>aH5B92Q4#bDN zp()(SKEH-fh1Eh;Ml!7WOd-3h*ZuyI6m2^psE*usRtmpY_F*KpTP*(%W#D^mN^E>3 z@En@2^1R-icNLhi`VVeNocj+bgXaWdk>j)V4Ygj{p;hxbTw0h`;5w}zzx$%HI$gl( z2wfNggZWzsmAh_-mR$!a*Ch=youH%sOMTkF>>k5k({R-{FD;!ZX1kstYRPCle=qZ; z$VcVP2N(L#3e8zB$(2%lmBpW@f=WxpRH^y0w-8`CH=TDur-s1EqS(g-BBh>bvdcAz z)V6&ZNOT3?=a$Lv55|QS;Xy}75%_&YA<13YZs>OmXj#qtPJN?UA0TH z3##1unBYeb6x-KgOgJKc6$$0OHQkuSgOB6|}l@%f%PQ5$`MjkWb zh;5TItWw-3LWx22E4anHC=l%N0)8mo9&2{c&;>F)@RA)fnjK3*I{4j+F(x;ScHcvP z7Al4F*pqk$g;&>Kd8P>13>3Pyn)Qlr26)C}>|<}Zv=8(IL1_xUag;6gbMh0ywE zlXW0*LA`;9Zv|0vOgOMD3BP%#hF%Hf3ED%6(>;;EA4YTD-l)O~TwA#ZV|WR9Zc!sX zo^7#Gha~#{O+5zc>Mqk|evyAhZ-Hd$a%R4UI*Z3}{!6`iEQU&z(EOEyRNjq< z*rFV_020cfNssxhTEvu?S5c@&I?I4dwUB&8^@6U@`zr`8R2ICUsXfo;m(`-!ADif} zb1N^kp-N{I{B3K{LC{?iqMLyIoM)uHrwE*sQ_@j;LQ?+}+6EN3os*qcZc_U9F=1>t5&Zk%v9%I9ntvHOi20P9 z`tzHIm%|4aLTQ^VTKCq8SuTZUM)HDfl@&I_)een88s?R-WOowj4e27jibE+F(;c(k z>xw1<+ZM0U+=+FqzpcE#JLiI)IUN#J%?1SzYHW8ok6H=BIdLsakc#l?cRh`W;>`JFPY%J z;-KM+WcSh1FL~{Sv4fKZa#V4%!N-AV{Yl*+z50KKH#vve1O}Oks}G@vG4A_%Osmhi znI4vr73&&{v_sGo_=^0uKY8=a%_0^7*UzWF4500%be4x`yAcs|M?bxdAv?9V@v zs`Gyq+tIz&(s^BY=@cV8ELxW-=1>lzXV?Pt&pj%yKN&RoS6loRdb7ZC`)2=G#m`OX zJ=2a4+JF*TfWO~^+i~1c%ghDu81Fwoq$03u@Nk@3NoJoJNAWS>`@6p!2%9-ofb?H& zaum-h?P3_Bt$DzcsMh72>C zf|aKseW?1gw1h_3(Rnwb;AKkSz6&UbN;ECG9$EKFpKm z93_k8gzy^oK(-j^KBE@0+7}#nQc9J%Y}Wz)+86b+7US#1TLxR_!p!!s%X;?0NbRnN zf5^gzBJZ9bPELyD1knCjeIeTw==K|G&NdzWxA4r}e&E%-+(BoMn@|uD-bE{HR)Xwi~zKT|3Lo8Scr?g^`{N z!<%cX34>9n2QBlJNt6lvhM(dc{^MFpX86H8nnAS`Q5x25u4H^3pQCc7tereg2N!HM zLvzmDsGEg7*Y80Cp#RNwP*DD4haYS^@6uiCW&**U3KZMqTQ~=OYMFg^38nWo^9IQw zo|dvPkL_ch08#hYzrwe8n6UvlFa7Lww3Ym;xVbxjI>u z+Wza>ag5h}iaA9FOL$A0WhxW$IT0bpD(pNu@Gx(NQLhx&m3AEeGK~cB|4l~#Aj^l> zrg|R!HmJg#^|}hpjZ>@2NlGNGzxO`1wl!uUdStquQOS%=J|5_s=|@mbWEHP^UfG|GdR)s91037F1@EtwVY{ z1j;?bBJUDRu{IJCzWHgH6G`93FhBRv82lG*kwvktTX9gCH@(`u8}pRZD|e(ceWb!} z#H?q&nz2C>hdo&d4%kLe%V~{*9t~Q%jpPRfoA{eZvy=vmXC2=9{R9$ z({55ra6h#(I#@bafs#*{Qyp0$k?H^0w&0z&5?)L@XP;`hd{ZozJL@L}k_rR{eb~tf zIk4q3{MeyC)B3VJYr6f?FkBMAH2>L~+eBXtYRcKw9wHpL)VdC?szNmB`fmOhs+g@qPoQrSvmr=r}@gS`~~>%Nn}{;7FX4MtY`{FgU*wmB`Vs>r=O z19*+WD0|~yW1@iSlYFpP`B5(-{lkBK2uqZ}Pi8<$hRx*I?4bpEs-= znji}Xl1OZ+)FqjY{}>S7!LL$KnAN42rs`XGvXHiQ?y0cvDx|DY$D^+bQD zNow>l-jl24A5fM*RQZ8{cQIGj`d;!91WQmaK<8shdz|6=ua2#UC2A$<&zy9Ty|W() zEL-{r4Q?-2X_LFf2UmBjo&S#)fKNo$*tA~MmfH8YFErv7#a_)p1ZW`M;H zgu+}#6P~f9`}@YjYZ&735Cf|YqHilvuByj6l=^HFT9_0h?I}xtk89?!GzHK}>S$vB z-Kz}bUK7v19C6kg<0uj5bT5g#Q-jT`&#sy}+(1KJuZsHU!iL_KCYIr-qE}V9ZB~Sv zlsXoi)BAU+WzbWqAO%`zT4@#WFIpJVZE(r1@Vi+^qT@8B|2Sd#Y?!cu9LNIP918^c zlOeSM8#+sV_f9S7ky_wyKGz><4`raV%dE}l*Hb9dN;|krqPdE67l2th=L3MsBvm%A zc`YH3-}daw5fs+uub#hqdOGo+dT|7{0^E}IntBn5%>glWtCcwEr0Ijk(f?@D0URWD z_g^ojU(FWfKRo6s*)@x>fPIMtH^i=Pz_hp?*sY;#2NbC}t1uQ|)5*2`)6`j?cL|51Y<0wV7RIX!|@ssV;QJS@16+F6qv6kU5nRSh?O z?H@1PM#d)DjqFTQsiMFSO}ih$5Ip5EkFawSTi3s$*1fpLFX>oiC1<^88(@b1EobgK z=~iuZa-RtdJxs2Fz#U=lDetw!Ji7|eYqFm_fKJ}7;2Zb3pabwNmsr{l)pl@-!U9JD zx9QMBN=#3gCP1KPez7w##?topLo5^7bDW=+=j&3;u^*El-3EGZK<=6*bPS0d1N7~G zDrf(1>pzS+ZAQ>}dEZ{-{PQWQiemESD}Hnf6E#t4GSX&-&uY2>{o^a2I1@@SC|bz_ z=Ai0YhSx_{x&BsOe3+5}Rog~Icex*ZHc*X5Fhm|tb$K&gCi#{^i5=2Lp?(YPYmHhR zc$9It^p^11XVF$IBQFh74>Tjc9$-I>X!!;}+2VWwDgBMoU!X>G`7$i9_N!7tLYJsA znd#Hrgcgw|d5UIMF9-5OmVkTHr=RKi20L!=3=;>A(4uultbstpFJ<#!vu`@|2JaXs z*)HDZ*0ESH58pw;AZuh~u=%YM)UfoH@4b6-&8EK@Rk}%$Z zu+|t7Vf}Gdh=h zJxbbUi@y#Cv#XJoKbKePGsN~k1C2FS)p0I|*{>^C%1FA|E22P!c7PrBh!#Z*eRrXm zNeN=g!lT=fG``c6BH1c!VK>_N2x@!W0w7J(8xx=3ZSu#wmHc~54)g_^K)gbYR#q8P z^Yn*~=~7~Mecm&S3+!T?_9&y7y2O>!D9=#8O>!t!Tgb7&DT;+^c4!en`++B6X#XvP z;f!s#Iz5jMV8heZ|48%D!fWDHu&Hq->wp}ni5yt;o5uFSR2TY#49WU-CEYZ8N#QU- zaxe*0fYi2$az`AQ`gK7vKrseuo`RtaqR*eAs=foBi|D@JFULfe=Kr{Tx}iL!v9`!x zc_~Yt#3qHSM306HK|ueDd35vd@TC_gosS3TrU{;KiL5hPi|x=TMFJHePTW`H-Jfh z#zhOD*+1&oKwbrMN0xva@7O$0ml)V^BBC%vuBQUi0He`MQ#?iCia2>JKjzkuy`RZGbln8@3WJ+ArFsc8r^* z%O-Jmm2P%f6J!Xdim?*mFG&g!cA!^-;dL zv#n7pXW2i`c1;b=t6+4kg-E{1Dol-)-ByHzQBb@IJt*y_{I!}sA!1wEsC5mP0r%%@ zg}#4d4g@&dC_P(=Jfw$hXdvTG9X0=1Q|9Yc&7!$+r6s@r874R>OP8g=7=5c5&yTg~ z3ygbLGz`G+r@9Bg-@FUg>iO|*=>>QUq!Ado`X6z-X%urEV0@i8iO-jFo>b!D_ach6 z>;@vkIxqb0#*B`i7XUowx?@*})w>TTdp$#<8bF=gkp#?d0i)|wfKqViig5Mv;`bh) zB8xw&pAznZXn|45`P+nURye)?ij#kT0;JHRlfa*cFUsSsuPgj_Au~KNd^>4}I#XU+ zUt+yi`Y#&xiZkw|W&xWSZA~YQHsk_;tX>=ong{}pww{MA=K+MsfF4Hc*sl+njyH$@WCAcS*@dJGV%`-*FY@#< zNX%BgfMvpoQe>*xA#i);hdn_Yc^HZA`ye`^vnn4Tp6x;`VUqzv?(~IPebKF+`7(_Q zv!{9Xq8i4XWf#~nPS$jM>5X#x1>)jA?LpvF(#mJ{`WZyF?Rj?=>#t8sI-fVq-VzVe zKj?T6n;mMFD`yFvb(Fcf3a5%W6?}i_;qi|j{o5$nk!(4^0({{?3QDsn3TiZEiJJF9tX%AU{| zgF!q-6zm+LUWUI>X;9f^CHh@v1Z9supo6C!pz)7N}gMe#ZQLNN6wtP z=-sUIr09eq2TvPrgf|$UY7rb|JS{C>hai~d%IJikkU`JjkVw|$)-Nv`!U}x&=FEZA zzsa@x`DV*Z#z3_@pnm(49Fy94Xc5zlC?khD#_tNycNQ;o(o@n0wh$GyQYtLB@*k5U zg1$a+lXX~n}a>IEDv7g?}uhEMsi zK9JSaB@*g0Q=DM+NQfo0C#!sZgQ6-UzjtTDt{H*x@pG+1$rs$j`2qm2rCS-^WOHlR z^X?Sqw*M1DBHgYC4C7!wq7#f{9S!ZuBWRW%0t8543}K;hBX5^HDP-EJP+}HHWfbOJQq2OP_xg)>%FOr1jQwqHp(+9wFQ{H|KGi?$|-j(E-16B?-$twHy7_ z>K{u9Xh%9nPR57*V@via`#VJj0odgW6!c`b{ujU<(BoECmG(}VY}5_c#SF-`tu)B& z*N8XOtR|p3#owF~Cf-z@Q4!gtQ1Qq&U*sS6>vhnNy^u@CRw*vKj3RS3_1wF~i>aP; z!dFHo-x*qpKK`iY7Ef9Zn5;!)eO@=wvQ-|7ac17qfZ8Pae zogmpG_&qM=dKQxW(DRZ$=z8euGB@0kYH1#A-Y-YD6EfmHz2-o4KYy%^^&MaJbgXLK zd7mn^N9uFhdu+w&&u8Df&#Qe_gb|QBA%ihc8DEJ@O{wz1STUpVjI$u<%C(b*=TFDt z?~cbh(l3h;IJd=UC$LY4aC561&nn*5%uTIv$X@n=HGJG;EMUFn4}GbNfP$kq0T2ACHP@i z0S5SaK3>$6q#Ba1G_(q-2bc$W3?%7XvkrV@$=Oh;g&#F?%6{`@cWsX9QmYYVcAz+{ z`D1yOR%)~NS)BbFOiTqxZ~adDKM($vPB9e)$*;GFgC;A2w%cGSdToQa|6X?Qd0^O> z{sw2j0I3Xexjie{(_KA?)Yk=SwCSBB;@kBwizgsMr&e$Fv^geOUUno@GDQbr+v;Q7{g>4oSm_?9wV;@rPbhOm>`} z_rAjK@ZQOXP^2WNe8J*X`NxOzcvbD+SzR_1E0O}P0U?=MhQUNeNZ%bK)VA~kZdEa* z&!v7?hBq5{?k43-4(x*`iidx$b!@((`fqQ9u=?b6Gu_6sSlsO?`~M#Mc7w%}{8AJ- zD8H9yx2Wz=&nf%oRbBhtRw|%~WfpD?4s>yvpB+FX2qX+?^C>f55vA~KTaQI?V5Lv* z`Dhr`rqzRx(({0Ik(L-!^?>cgaTtdNN0pPts%?fNSj{Ls%j^r1-?~yZpqFwt`hwrZ zb31c=@%*X57UZtZ=h7(F>i;12$N4m1j}F*Fp~tHE;;NF~DDBSFfg0rn`7?K7jLC(q zZKk=uGH-poJVCb5muEema-K;K)8yljBOiEgJ3m}Ef65-~scqxLzYL-c$m*@pui_3TPXCNj2fKpPV` z7BqeF|x zpxjmmiV};}NtCzK(M4pPvoVh&In6y5qYvLIWoAXhz>?!TDAHtUDLby)MmOC<3{sN0 zI}ckg*=wIlps*(F4se6nFZ=W>c*C{O`HluSPxhc~c4D2`BW(}9qVOO5<))|ixm3Ng zuMRiXRq<^_JEop8~@evI3Yjo+&To#6HsRZ#tkF2ZU_SK(k3ncxllMpbAY zIqNEynCLh9l+VHkia{Wr+uYI`T5QzgI$Cp4n`NNCtTM`ZJuK|CP0g?+85)jwW(=#6#SoLUcXTNtqzd$XckVvbdG z{6J%Yc)*AY&24bC6z1eC?B#!Mg3%OVND`xpMB>}7q_3J72aAwLn#K)ty1hLRBxYU` zjLJu@ZNe`?F;x^=3<%n$`*%9e85Ex8!K0y6YxD>bZX0^$d;;--ji&72yy0$tuo7Fh zZYs6P#usI3W-|&P%Km8HEMYoAB9@i>p?o_te>!R0Ymtk1d^#^>*@+XW=Z1Hz=*fVI zR%)6+?iSw5WP4~smv$Hb2+1ry5q66gVzMt&?9!yQBhmJ0r9`@UpBn-|d?BHYWw&R3 zwWGvA;v+cSz;Tf#G2D#7(Z3#V?9#TXrF-RdUP@z9#vK-KiWua*K743zk97f^|Dwlh z>=}E6*XEQGtsAdV_}2naX17B0N`G?6nA)kF6}wpV6R8i=M}vP7fo5g3v&wMk_{n8R zOMmx#@`f-X^T+1H`CADDaVdSvlNs`C#MP!b6of zTz~NI(-1l+FE2SjeAHMg$*JXk{8^fSATN}~gMiBK-IZGkLvb6668j)=S8IbM4;rH< z#2WE>4nPv@={L^%W07j$6M-mCTXXyg{QS|{%xR#GIh&8toTRp4>{=^n{tskB`?dcZ z7q?D<&pF0tKEm@Lf*>M9{`bM_Rql#G>}S2K)N4lJ{CQrz=aeZwG_xuSS|Uq!ZyEjU zJtCSBNhlph!^ySGWyYNPoHm}QPr9a2@4mIuDP{Qdr*iv)zkha261Mn({!joV@;ldF zd3hA}-{SuGtZb6}$u>9m*l#>S;1YKYL-as{z1gyz^&JOd?DZPKQeCL2sO-tGj5`iR zE@JvYjZ}t|7cN6~6=V@|hFs0nv*H4MD+iZSWRfMkL2)O~K8yUi7UY#8V%rT-fBdRf zZ#1a+JJYb|V+;SkIARls)hp`qN^_Kc%~eNWqtKzzv=t+=2O#gWe2-mnT%uTQXLOJr z0S?!}{4O3JUv566j^(=r0$5h{bgxELrPi?Py0QAotyI%lqm==31J{EDBZ@x7Qznvq z7b?z(c?OYw_*MVNz{)0}mBIw!KuvK@f}dX$uPd%6d;f_N6Yt{Z*C-NDrx?q!$6?SL zL?XEbqD4##sWlaSGm#J*Z#)qLGvRG_op*Z)h@RqnR zfoAM^V4KPW@~&C|x-4rf384Z*7cKq)fdM~crD4^KZ8AKOQ~NP_j&FS_Z0A<^cDT$3 z?nXAKQ%!gHIVRdL?Pud20i-=r+%G5v<&RgHRX}eig>!5*h)ssqqj<-?CwV|d-%c7Z zpJgh%Fh6z;Vj9t$!@DB_&8xU(gkY$Ek-?>6TK#`$K+a!Xj8V4!;lgRi@k`(6| z>a@OdcD}5F9(3L95mC1WZW4%@K*3iU4iIp5qfekqQ=Y1cP)|5HOi#`$b9`F?IX>HhrNwX>0cvP$*&Z#m zq`(U3hYzwI;_HfEmU8+wDD|{3ny&F;Q9#^9Z_GD5U0x(a|96%tCbiHo&-|0xN~<)+ z{`(+NCm_&Z$dfwIKDF?E=CYHBF35?T0K?w|`ZPP6^shEL2)%h0FD7jp&3A2-71*>D zW}80eMvT<7(QK`C(3j&In{O!j>MA+DBifJavN2dZ(A1G+M0)n4p;gWyrNp|kb6i)|v#%BT9&z?krY?5d)Jojwr_;<1K&)J>uAasd2lxvoLeo6hy1XR*GU zZm>OpmEro{EkvjGp~A&M+|bTKVWDbT(T88`RTMFGIS=|r^rm_8dchDSGPSLVWDG)E z>MY^lxfsqwN+lFyA#PF|(hwOa{Je5Xq-dcszix5Rd3X_C!kq4Ti`f`?(Ztbm^(X)nNPtpZ~1&#(P3<3PQ^ry6xeXQK%P&|Mos4NXBi7Wh#YFFKm}n!G7izJ6-1k%sa{=u^w=C92o$wzBM<^}<) zP6Y^t*GmiJ#s*y7h6o??+vc|DF4IePo}xV7|I+*8R+2VT%~>jHoZ9kD3HJ1<2cb13*d1S9=c$(J*iC$i6DwIY^Npdq8w&ALf-%aDe7|aLi}S?FNCtQR_*3t>t$f~0G)+VN6otx zethtJQ%=$y+ovpnf`(v8!#L;2u)1Upje)4?;Ta4I#U)K{sV^!h@Hg#6wHomAWR7~ zdSUu%Jr8xGH_0jS;S0_Rn0>$3MTK|iEZNoGi1OD@5)vPv*P(FLn!(T)CfKXR{sfHB zIRmv(AjAsDd{Sr5h)hWFt<-|KZjx8+y2 z_bW5>$!g#{pJ8S~1paGfalg?NPN|Hd$@AFn$(nQ-G?GDutSjL*em(E~zZTg8v-A6o zSo?u3f19B%cm3)yWRUPI|IJeStn8pi`Whcu2E*kTUQnY}RO7EX1=7NaSx^hQivA}A zR%YT?@-8T@wGNl3ucT%qkigL*)Sq_63E1JKg_ke>3e7lIYcJ0JIbknd0ZrQG~6?5)+@CVuid zowVhiTnL?iiDPbe7_EXqo*+|GcWH@$&3u?5wc9_B?=DTOly_-hm^4*BM##XlRQG}{ z5Sk*xw6|Kqpu9o!2oXP$6OLwRp?EGs-moEtT~xY~8xUB_vve1H)M4GXid1zZj~TAR zTv)3tWa<_iSv-gbq)6<@TVm0>HUt-|$v-%&y*}q;W`dQ?oC(lsD)di8FiqQocdMC9 zCGgn%wKW3TGvRZy>b1i(y{b#{As+4E7fz(?$!1q65@sy9ekU`@C7ebhNxr1#yJ%#5B5ZgG&&#lN>M49>Tb758Y+e^ngQx+u$d-XZ&XUd z4Wkn2_INKBr^wL2C7dOC&st1*v))C#%fvJ&@PY1KNn5?Nyd*ue3d8w|t*Df58YwTT zHEj;N_&~ncpx!kkG1lNiyZ{fdN(L4w-j)`48*4gl_dSxKf!2`rx_eXaskqW$0}W*x zIh-JO?mACO2F8G^rWaHHrIik;WqY3gk8&j^J(Cq^{lsK0Qhga&imGwhY?_fv#2D35 z#cq$P9=HEZgw4IM5rO}b4hTGs%m=kga)P*O`8Nfnj!Ejtx##$j{^-a%N3ChLp=%ML z0Bj(ks3y%@z~b&}cpjX5-)=J+7<3;A@X~wx!Cd3k0D>YMaN-Hy{q*Z41pe4Z209i# z#He&XEM*L?(y~quHW@m!{8c)>p<{_ClqTcqr=VE+fd}u2S=)kv=}hWi zSk0_+HhHR|S3=k>5iPZu-tf4yxt@kT*F>Yhf>x!n-v_xPpFu_4%C^!Rk4(y0l)yG7 z7M1f4TyKgr8SuiGY?E}E8x=^SI7spJr($2K(5cZ==rF1j^xPH2=s64y^3f11BUX8&`f`0~Td&DJa3%Hi_;K4d^r8{^R{$$B7_X;-3*x$`LaiIlAKb+{w9T=wIUs z^>2Cz9OviPuY9q4zW9vYnX~7we?=&QY6|mCnO|9zMAInpivRH>*Dp(3mVMzk00~yM zS%Fe7qf}lnZ+(y_cJD!|7Hxp94#Cxi%qPz+fNG&%Nlkz5=IoGDogD{nK5Au2ccP4e z6fi$y6S!~qL&lQtpy{gXVsf{PN?zK64(w#Qrdt!0xLnMKSXzq-K}+n{M73Hmosx~i zbFWf=)d^1`u{?=xBK5MVOMcLn=}D_6df_P5+&mRwH>GCssky@azS^x90}_BkaQW9b za9#q4phPCqaTdc+Ws|z#^JpQri9;t49;M8rLlf!Rw zxUnN})qKZRcknYw>0oJED593*({Pgs^K#X5*8p0T$*mRnC#~@O#^F%Z$9~`KINxft zLboiFoyOk-{Vp+&Sx`!R(Y-b96Mj)!04J6*+b0aac)=534*T?;7^RPs9M2$`3y;+O z5HZp!GH;OHBhD)F5udGV0fUd#1V8U53nK1Kx^8=w)%YghNhe!eu)K8gufX((u*Ao z)4k@5zH@O(_sCYcC0l1wNy&N$Gv49fa`jSuaGvU)kvl~VB7$pg1|a)pLHv8>hW*uO zc2^~_RzE-;dpBuYnp59Fj7FF>U8|?L@>c(#gw(M%pm_;_cZvc8^KWoy4gj$&Nny2J z423RHQ+B5K%fqIZIAkfCZ^`dL)~BmYe0KvWX{Mu@5;NqTa1PN*<}I^y)lxPdf551i3@#eq!bzK zn{7g-1N1Pj52%CG2tqMx5@n%b0qtqR+YFw@6*CmWy29bbQ9^iQVzD)rgzkhFCq4eurP0joem+iK`d(7;XK(c@e7dlqA z*_lKVsFzO3Rm-RkaFtiqqqMfqExK2#*M_x;BgCWz7E2*hx&KHfRMh_{UfJgU=2csi z%3zK!;MF-DCb5@sbpXCcR34g0l^x6m}mX`{ip6od}wfZVElEgD9T~=Rza8OS6q{(w$3sALx2ml z9ag?g3Y`{tkVKosJPQx)&6)7o&vuPP1{~p+7(2r^+TJ9o9z-p+Ab!7end4HoK6aNK zl5nj4!GK7@uwIANhHdRojK|iv+2}k}>OnFlfdNh&%lHnVSXcxs%=_Ep2WnO{c+VVHzPQRSyPpbc_t>4T1B-_#WEkLZn>7+U|XhB#R8S7u`>e<2` zzBalMv>G7|V3O>BQ^@Yty&OS7ydXpNOLL+KMRsoQvIl+2;CPzU8lJu>mMymSpFnqu z($CLSuf5ENT0(AW*B^daDl4;YwnZ$YgPMe}g9URFaA*oBzQK(XdFmZzu?3;NobWS- zCy)IhY4_{PSJ~1U<8%@((ALes!?Mylb&n)hMY2?U_a^DKJw5`Dbf}zfG%BKC=@h#s9#7xMm#;*;y1h2EkQ}32mW9031y%( zhdD4pM;Wy?*7Z)k03QKPrEnX#)&UNB=o_`)b7k+;@HI)W4Yiy4YG~sYjatr>;+Ma>~SIJpFUKtHDT!JQpKUpzNC+CHT zQwDXJ*e%S{9^YEmwlnke>SwgxFIa^@ZfGHenDx z(|#F9rxL0#LyMS6^)s;h6@E_ZH3m&;Af9E3OC@A*$y}rbl`V&1FG`u2#ag1ooZ*#c zG-oSG0?uj5o7LC<8|TD<*D!WZ`M3qfTAbc_f%Xf!yrw}Qq(bhWHNh4d-D!$ypWE)h zU>BMCpEPSa9HNpP&stM0jbiYLaj@dWBLz&ARmUb0w%XF7G7I*QTwH^^+H`3Bet)-TRX(mO*> zU@KNB--5Lx2SNwh@t)k11R%?_sYm< z?URXc$WoH$KQRga#)n&Dz?XD|*&pWzU zWR^z9s*ojIiQ@iIqf+z1D!ivhbTr8FuEec-%z@amwbX^m{xW>B>tPg*jn>5nhAfmTK;AgD1s{OuWIBybj#=>Uj7myC?D*i0bl;pQ-k~q2e-rON^#x ze8PvR;ougeqrS#eBI*f&Q)drCfmdKp#6eI<^z*!&ui5jrzR$_=-Z@^U&{{iL;UX`u z$U8l#u0A-mEs)seF3xfM+t{CyUASA;cutx+BrjxdGG_R|$lGqZ4(6x^IW)~WL-cZI zZVr>I4Fyu^^le^uSOrDhrCTN3D4CObvmhY;bkIy8@c&-O)w=gnDvm^YnE>C_DlR>V zM$PQ}0$Fyt&)6E}2ZCX}rM$<*Afe5ov1g7U0q^q1!sDl#?(VU9w%EQj?KiBQe`b!K zJ?;wtbzpxrugg}o-#=2wY23*QIeelS@l<_^u1TAE8{t$(`HHoW18JeeVcm%^ z^42gZyWELQJogymB;W#7GM__Y4Q^JAk#6t?Wy&8@*YM)78_y4MF28-(qS!0F;h@RX z;%A^aj^h)%wBbsSN$SlJIX2qomMj^EEmlR%zVs1o;jlJ2L9pG>sgpAr0V)dr_#t!To()C01RhB7(hfc7Y&| zeDog ztqDUHoy+P4Gm`azD1NjZ1Mg{3Z{PY4yHDu8G?;sg!%Q(UvBu_;U4KGU*kF)<$u1SPWt z6=oG^x97LimGhv8Nk=u^q84AF`N$&lvx$iA$McQzzM~Sv45VYCj;&ir`_i3XvbMEm zdDluWoVp)9Of^xRb~^wNcTIcooJ!P7s&B<9k#b(den3(e{jqGg_CVDa*zvh$@{H>-)$BR)cZxkV3l=|Les9hU62o^jej7y-&8hTXg zUhKwBR+^KTdR4diUPybXOz88zNo!IXlw8MTrUda!;xK`G;3XjTc<27IOE{@qY7~+n zC|0&X<I>b|--Ue-H0zUH(p(=PZ87dm;_Q?4m+gLv_B)cRkLinR>~D6?rJGM! zR?RUQDD<1>)h0~yK%JJEdmPi2)Yqa#F~b;xEwSQ2#7MUrNgTQxuJ>Ph`J@BEPRq7><#sYvdeST1@?2 zN)VE)S-jKx^#Ah47tCci4X2kE`H@__6?4Na^ZIi-4?^2nne>u~x zp49Jgs+ph8{s)StV2@vB;r3Nc3t$RpeE6+Q-*YyKDAgi^asQ^oDx>^&;iJk1pbor^ zFRI6`?**{&x&C`xc^7c<2-Z9fK5PP?1EEQImNot-cE$nV+ty(bR$rh;A>k3Y=d&Hr zD6hD&$1E=-eWK>NjU z?hUAPY+zH8KkI`4&JG}ahnKI9^V_9f0zgWoIM2I-(tA|i-3CCB@56txX~pKNe;q<6 z{KgkxSdI1uBbb$lfV-kZ;FlqgV5dYLatQpf0`)Z@cn+#qp`6xLD3T_p&~%E z0N1s2t&(-$@?EA~^?{<}$8K~FL&vt_F&WaY$xeMGpm*rGtw1yS43OlSq4!U#2mtEt zTYV@{GAxXdGKGP4$w~pK_{3Qm7euHMG?{-{*se=zUoK0VgneOq)7S|(ws+Roy;$Q3 zm%LRNqCOv6YJvsjvWkVZ%{i4wOV}iPzXyIdwV1toj%MH#OR<^rpix3TnMyX*Hd(_O zt1-AXTjmF|ykCI`1OWPVQ_;VPvaGm_wL42fH><8|9c9da=7FhG+Rf421^|1S?oV(j z{u5Ttq9$r$f_8EJiBzJ0f ztB*(;yCa3~)+&^DDejctI|x zNUcUh{9f}d5EAwk8CK|}gG>V+Z~v66h=)Z>L{GN?;I;Lp58H%G&qv^K*2Y5#<&>Us z78qV;#Pj#}@Kme!r_UidCIO#!&UTYjJwkQeGoW%c_>*M<*#*=-5GK=uD+yz+1Ih2A zK(PXR{RJKU*9XHvMd4a=$+RUB=PfKuJv98G&UK9Ug&i zq6=$5Z&N#$`|OWFE2g3Dxi~>`0nnrFS>zGPbMo9FB(o+l$ZG-#_to<0CFj+)|_incQALNAc|5d))hqFu}RwqH(YAZkdjRXfz> zpl158`nDOjnYvEQ-*|04?m@!I{C;i=rL88I1`N``)Mfo>P2K)%U3=<1rR;R4cOT_A zO%OVE7>jZQiqec!X?||eEJ&y(WcF$;1#*YFGByJ4XCCg3oF;*orv+kI$3cnVWwGK3 zN2XbQ?1F1!c;~=4#qlcM@+wluDpK<*b9kHnxX3?v;%FN;-^&5xBYRiPMwbRjNb|z# zW2LKe?&CH#W`^VzXu>5iYbMihc;9;`b5NjVhfriS`D5G^mNyVqqB`nueqDsYp_F^SQs)E%0s}~g!+nBk7;Y(K1heeD{N~Fx z-ibl^>^?i{5UASZ1Ulxljv=d(zHn)<^hsBq1f7~W!ysuLc??<<$YK|oB2RaV-Pioq z+MdJ_QW-EV#g=gO@B!pNsHQ>IrdKOQbX%tkZP~E;EpRw#V(~$m?0pB+$unKfwo2H& z_`Nm4pVu1JHr5lp1mqExRw!?f`LcfPHrrPW^d$dXO=iS0t^FW@d0N8nE10qbOh;f? zhPsR!^e0-j%BXJov)2-^-di9Ct1a9CXdgRW-aOR^7U8Z0F_zM)0CUeWiWJOjTd7SI zp~B9DRI%kV0CV3pqkK9K&*_+nnXzW*RvyZxgk==i`om(Z0!BXJa$&W|ckk0F$=d2F z2q^^0nA${^kD2Rerb(w`j;Xpt&koIRWK9>oBAg$dmZdzYUoNVt$bBe*N{OV`pI9JX zeOS0$#ha?9_+jXqq_cqQAM*o~uutYBFKZb{gNT80lq?~-g41b@!mF^nf+cD$6x^#k zhSS@x77T?{b`@KP2rLm~mtXngZ#^to(v^0<9bb=6a9EPPpK!Si_WA0_t?82vY53E; zlKSg5#k9E}lZ~H@dE#ULahH8wzlkEBu|pGb25STRhU<;-B^#UIjQi3~oXA_T2iLrj zDS>z zmkurd8loIHkud(LEI|XLaiF1USO5(>2jzXbAo{&pIG+-dSHsT~{Q~sai1g4&p$N|W zq}+QGXSKWM_MrV3a|2nAE-#qck69TQ?@`OW9d=C#j~yXa$}kLwiO|8g_Vt}Ng{t6R zs$Zz>+s3I+B9_LEo9pe_>ip$YC{O32k{B^db%=ASSzL?rQSaP(Q@mISqFDb^$gM;W zKgG;ay{fxYICla}q$u`*`CT<~R`K+i`ht21)y3?WP)Rt`2EDY%HrYnXhHu`#08K9g zo97^cRdyr&9{Tdzi&XQc=fpKK@rc5p=tlH*?Rxhe0bJ3(j6V^_Ms7C^R?L&CPcFTG zf%vAJ;W`>D5q#Gav$=MX(i}8C@bJ6lSbDy#2kjNF6$eGA){#1q?b{EEgy7LqyjPa0 zDf0Jfy878>%avd`%DSCcA)hvNq_&#GAE}o z^!W)uxu-db^5%Yt(9F2(I_qkVx2(4prc-(r9n_nSyc&E+f&mG6woUcirf$bLQHy#G zgMLpBO@^A|QSPsr2c~k?` z^hMtjK_x~oB1(?0}?N?d?91h$c4`yfODcUt@Npa8#{Nz^))9ji6Pm@A_?X%z~`$_ZN=!YRJy-V>+*5E;yLR zidkJvM6nsxJVe|+$4j1tzfc3iQ3-Bj%4L}5f6VYII{oo5N92ssLl8R_!-8w6aEQRE*jHADYBw{w1Vf}uflGXgnED%Oq zqs0nISU)m=#u`v2OF5JanlP9e4Z;~VDbFwlvhciQN-Y<+<@cqdT6Q7#pdb<(Lxh{J zny-iC#cn0tY1Bm*8($b0TEw2LFLoIQ;~juWNn=)pcT4%VV#8iwgkj3%kQ^CLeA>j~ z3w~=>X_4}F`cIE4FUVS;+_`AisSCDO3}3|Gx;wc00s{VyYRCQGE&!9fyAqC8+#zu+ z026p{Ng)2TtxRNRzT_98k+mnpN^!kBv#633z8fUw=bV9^|4@IOC?A1`vR!JK`lkCO z0&kHv>bhH>y@}<7NEU%Un=#Gjccw`8yd4sDm33hqiE*UW5o1XDPs?x3RtDHG7I_>s z4J)AEnHdXbaKr5uYNfWNJAS57WwBFr^^GqNI+)HNRZAR3Ue(R9;D!fGy&x_<;l_V& zcWT@JNs6)Ch?ck!>zZc-i=uyIrNW^YTJ$h$xBc^)8^?;XuR;88f!CaBd#Y~MiCj1* zN|_3hZNs^CPDPxWiHktWOGu(TNZ&&w?vG8nhje>@dxWs^qkd*hbvT5MCbKKE>*c3D zW(>s@e$Im1dZ!>UbOepp@=`0IzgyB9Uz>XQh)=}fDe^K(+WO7Ykp6tLOfvPc=E)}h zNgtP`3C^n&c4)x5rClC|VMtktAHG?Ln!Q{AHl9fG<)we6JEMTvHWvp@35!bWbE7&F_zP;y6)P9p3S%4lNVA5Veh)Z{R6m)4J_Xo`3e?Nq_Xs=)g2{Ov)5{Smc2DmbYd&2TczG=GKQ19pR-=( z;ZfgepQRu>6bgPZ^>CZcJ*C)dj`Z6!tIYR#){wVye;*cS06JkR8a&*{4;|hT8-v83sN5Eh zHBj);`Qm3i>V>^aT`u>iG|5(f-8Y~n>uQ(c1&(rw(~iXbkWZVdgrY`b?{8ie`V-eP zU=!{vnOnR#z`-QRlxNBr7L~`o#r@q)U+3x%{5GzxlToeC&D%lBrfU8QN)4gp?0I4Q z+&jgLv_3WE3SS2MK6r{kFjHCR$_sPkU~Tb zl+TCrz?$j91iTaBKTX#sb~Bt2HZ}6B{w0;GjVZL{*Hg@AL>{1WyF6vsmZ~3^-QNFC z#fFKu%r9fCpwsC_O;J0;x5=I#8$%1XXHJ@bq!(I#gBAUR*3k{HNwKiu5Z`~DBq+{y zRTG$02reM92s$Xur$42tM&d!T`3Av11Pa1O_^l>o*O3+M`c+=aEZvMw$uBx->0&<` z3D-7rp>Wj6UoO0$kw%j?AP|DYFzFY0)sgk9l*$)!o0P&B5}|`G?OS1_cf_rF?7a;n zVx78z7iMKjYfOQCRctqmV+ z(-y4AY6r#_*A}u&5|Mw>`RC zuY&Mm@*Lkkw`K^mh)zguD7omgt=PdL`$keedTKwk7tAiOv5>fwX=j8PuogHizpC+P zB2o5*PU!;mkD1I6zd5YH-C;^$?}};ep8i*Tl2gOJR2cO}Nh*OO{fP)GTW3d#sKXiq zdq#(HN6gx+DH&XOZ`SUW_mS7JIi*%hFWi)u@N}!`K3*WBH*%z1mgqx^f=Fl(tgbl? zk$T$p)n?xpG0xPw(=dlQl-hRDo7&G=a8=}ZZA*-(l5b0TfU@#(rkXzQ`RI9<#hEzp zK=Kv}p_b;R4!*XA1QfT^sq;6{%hoAuE)#gDvu5`Vv@jV+_*-EWQbG^z(t9g(%lJsw&+ITf&pv`>SgH0|-sUndO>VtuT7R*=5aZZxw ze(452PlblQ?HMLgo78-ZIYyrEe7;G|^8q;(jGOBxHyGrAup7>l} zNil#V;H8ieLb$-9;xqk%UbJj%6VB8`Rm#r_KZ>NjI51nSSDwPdC_nHvtW|H*l4WKy zGxb%pd-+#9#+islI89$dtzk49+;aRAiDVl;emz79iz;+ z*Fw+Rw~58ZQB3(tE?{?p3;m*wlDW;+utda+$EyF$iEu`5tJo9Br_7J*<^Niosp&c=RxWZCtm_YN`pODJ;9ze5Jlld{5jjJzKAci(IImWH zxT55GNi|@rmrGN(J4A8MY`7x8TlUr%A!sK4xSdJRzF;7;%g|u1s>a@xMbTGLKb4yl z8a3Hm*tGM`s>4oeV>}h!Dn`W$D7{`r!?-yY(2@xRNqD#N&!@lF!F`JrZ~)p*2Cct1 z6qNAK(Hv|}m1r)o6pR$`bFy2!+{1qexIfvigh5Le)cTV6)fnSW83+jk7i0=hR@$l^ zon<(v$QLNPzIn~5%fj~aVSSl`3(n_i$SV6W^^x-ciGpxk1r z)@P;dJa-!?ZRZ*CZ^;ILL_U1pv-`4Hu>-pWlMQb;qZ8?{SrW(-uq^f@ev^*g8UOWD zh=XHGT_1N$L;*!$=19bSU|y8{id*4)2zN9Q^?Cul0(-}4r1|kkdx=LHp!tAp0{=$Ltyn_OBsbN zQ@!8p>Bm4}(rYvJGqL#a7K2Kg=7vlEncMVX;gWIdblwUJ-7+Vr5`>AEmQXm$$L@_& z6MhppthBlhFRh)dRICDwMq7AZ7@P0B!VlBr%|*C>`l6b?IhN2K-}hKDg*8RCV137$ zOP8!m+c}~jCU4pwW>4F^&6cWd6mQ#s0~`=1Ny4^ce-!7tko0BCurBOLYP3|0nrND; zxTEbvY?QYY@~(TeDV)3QvFWw71BIjb*5ou8k3C{sR6d&>zH#%2kE`bWcINyy=8Bz>T(c1`8rsr&dr}wd)m!Yp(#`dQIi6GzD0u5?7F!kvS188uCXT^lZT8@Ci z;TnCzwgr3SShxn2SMe#8vy%6S0~M=x1YNaz;?v9iRx;)9x~b+gKbgUP+Sj>+luI0AuCSD;PhGrk=m#4m#%fYL0LYq6>?budY`aw7 z?$ekVF7;n3xzXC;(A%IqQE`ow7~if38`8S$q|Z&Rm9AOB^WYC?+K#E#Sinl}RlBIh zOWdyP(P4!d&@B;C5%h>D#g?og-zOUgnO~ZPZc%SnkU?8e^$zw?1;qxE#s@;_(Nt&u<97@59w9-#OIHK!7jq5H?RI-my zx0h#7{6HpUQ9=PIG*weLplT5()u3gWug??A2<}%@SYw~xp5twpEi*^JYGb-M!N{&u z^BrE$d3nghB_m0bal~2eqkd9lA+(T;@#u$xk@pJepx?6`C;Rpj$K#8l&)o+XQcJgo zH2jwk3x7n}*0H7tE10{<==v){e-_;X2ycw;_EL>MYt)58Jx`NRXhj_Rj+}nCc+F?g z*C?2CNi2&8>P%y@@gh&2Wo&_tRcwJ2H-k`si-x6%MPZ=>KPf@x6{b)2y!!fz#Hr9S zx{kW=gd(mOGnnjyJmN9VRR(`eN1f6ITE^lXS<|3_SWq9j#{=@;nq~RHOVw)C+)j)V739w_%TlpCpfjNh zvnV!eps7)-BQyVQiHb_+daf`h`1g2afOa<|}d*THUQ#Mh1JuD*|iiVB+|Y zd|w@+V{I=ID}_~Bij=LlGV@XA`FG6YSFtprdKsI&d+Oi<-7!YjB&Gnp@3+5@e|^D&h?@ zO6ncYLcht*<2M&B%>Ovia#0g~qDQX(5j~fqk}f~SR}BK|vOeUu`^;srT5iRx{(ao| z3uBNv?$19l+p@qt4H4loSeG`s*!2OP{sj-mllj;>lnIP^FOs5+{@9gRrjAj6<5%eT zN*6dHXdk%n?&!xuyu_8*CsINgz4oQ!QO>d)kLA((phicsU!r0@0k!;>Ll_kTW6M7R z*Gq!<($$B{j@?`1@#`^G#43b4m+3%hgI+XCnaeqS`U-^sY5{!+X*SXO*v*ILn%|(z>nt4dwiyWSA^@nJ;-Swn1fTEXAJqtbrNN6_FBK7 zQ&x!~U6-WCI*icLCfnW717?-h{#uqA5=0SB02I;v;Z9!*m0o3C^9&N+F3)6qTGd(r zGu2p6J6Bb5;;u?mG}%PL z2N`qc0WqrbfHJ#$ObNRW7tgvFrFi20bb*;Ljy%_rn@76|#YUr{agrkgN{W=pDt^S9 z=^?-9dK;vp2$a2>s&2J`Q>RAQ-4vtdvS!G(-9}?Hyfj^lV{3Nt#a0=&68kL?x~Y~W zXUFHQzG^F!NIC(e6y1w(_X?Qy*%n{9sj=YbB9oGHCRC;WsJa4Fano;ZplsTCHmK@6 z?63n4me0}u;Gc(O*UOPgUx^u2PN{gR$ER}NFCGMCNUT)8fuCdLRnK%@Ehn3Q zsMwkZ9rhhP%((4@m4Z^!?7~I?Yv06OFmVz;A9XL;tUv39^DyDl?aGFzGhY^nt*+($ zU`aU6K)hr7@+U0t!p3I8(hzBN8q4Zlga@{yv(Ewx-(R*Bw0m~BsjU!YSFuX1E$hm|v%0u5{#wKX4`gjcB1k0o zf&sD`)vD9_@EMYKr(JVDsJ=r(k-WDt_a?$dv(-}0yFxiZ=fLMe9i&BZp**{lwkM_M zlNZKhI+_?3K^MTF?fnMJ@0}@_NqNz&>Q+G&^ob&pnxdT!aEM?o2dnU=|J*sHL0D#%kyCp6- zbc3iAeFd4K%78*U>%Nf;Z5aOP**M!4ds``}fOB+pq%tdaNiMTq z{%|1Tt$5Aom}tCpAQBM&ppgsJ6dcqvX;+=a*1bp(#n=~&=@+>eNUEh(nAP?!6_(4p zraZ46qEj(^2)Su>FyhPua+#}>FIiqDyU3XZaP`D(SFC=@)6T}rtv?QZWtOb_&_-B% zuuG?V6~zR?3#(`>Gxae}5t^V3=ubshXP9av1ADH1zCiU{decMVQt`VCKG|f!!`|L0 zY1t4w!^6iw!)n0(jo z-yLl+(o410rJnH($BvV|u?haI5NbAQdpB!+shn-Ki54F=is1I(qMFoFpN(W)9+MhS zx$}|N!r;N6rtskm1A0rWYz=p3U)UC%Xknvs6Gkd7bWnAjk)tm77d-|;><0R6zqRkT zMzu&~pOc^z%9PUWla~3~Kcb!0CFFFf7v($COj9OionDOSEv!>!eY363B9|!a54R|m zaY4B+E9N4pR&^g4)x%mHR5|$iJH@(k!ik782kfDglBeHDP!Qog0bQ$JtxU?S8GlNc zW{}$Z_sphd4uW(zW+^%(p;Jg{5+rSahjB9rRpoT6h**X6wHb{K@vBlq94FpXsrs-L z3i$GUvz69&n{Fk1od?QYl$$_qeOF%e;HdI;g;#KUqsVpK$2bHBK6>m{p*P}DvNaIm zK9*d#OX(^1(3apZ(^3lZzc@M9c&=T(?->%q`KG5kKbRzXHOp3(%MqZg`s2h)M!k$` zYFKgV?sQU`m;LdJf6&^3JdL-UlM@%Ef|-nGG$Eg?Z1gqKq>0TeBk4^egD-EI1#NA+ z5ahcDhdn70@nzaTXuGD0-^>G318|J>I2Rd{nC^4Wfp&uZFv_KLbmJ zgngiC)JrF2juzY;ha{VeWkYiG$kxbB!47;XdTe08g4D6&zXsJu*7uZ*y6wCZMh(VW zbh6%xyOT3g5uQeUSN7w3a_Prak?MIzz(U_xXp+SpF3mnUPeD-kEimnUe&IlSl0jAn zTGXd?iYCDO{BmCON#9_|j&Qs%O;OXS2ECU7c$-rhlp?TV`L~^#S_mBGXgOz5TfVdF+H`e)H{h zl&w%z)r(^#YmKdD7uVvb3h_~ev;cH!;eU2U2Jw^;9FE7*7V|RpO9wf8f8&MO_WH?} zXn16JYN|~iJ6Uh(w3EZa2?Vik^bX~FEKtVOdO0;hZ)gd#+D_IBHj@WnNW`iBd9nZc zHAWC*4ngM34<$saiXKm@5F78d8f+FBT#Z$H{{c0}V2EezT-PPJik`9<1MNRHw#J9A ze4ziMW818K)%4ScPHF|GTVybM=QU}OYv5moShUNp&D4RPQU7DJ5@fhV2hEp+NA;&s zoyrc5Pxh)DEh1&tYtgZ7rtq(*O`T6)M!0|Od$;;;pEPLc+Y3eg#_htnenp8W2xZrc zfoq*f2WJMksK~audIqUFg~0&aHDT?cG0D`>sHlJ6Sp35^6;)FGyK=)5jRi?_u@e3J;3#3i2bZl>*R?Q;`$ob*z++94Y(shqsNrX}KGIz~Vg02+{=5ca!+-2t zzA_?X*lro4J)LBfoxZ_6kTz3&M)BsyqdPu|^xBWcA1x6^=kRb{?TiY1t6-<*j>K+8 z-=x3xY^o!K&u7X1JrkB5GXbU7GM?Qv&pd1pWm3GMZc>^nWuTh_E+X-KC| zABC9TtG=-;C-muEPI`1)lADnndktXA;Wl}I9?+N}aAjpZwW=P>xYT}BeWZ?0r_5(q zqigE#PUw&uDB0|F|5bKAuo+D0^74NMx5pR(PD_9B`%l9YRV14SO>JI2U+=6}5y1N9 zba85CQSsYf2_#k^wMmEApgg9NJ*1e>FO?9$xNPSEH0cP|AYcjqtb&+V^0yCb_iwjX zGC3kq6_fMi)PGc%av_t0(FY`T_w{4^zu^@Bu#C-a#XE0|rrq4IEZijCLM5K15FiVe zrnN@m#o#G8p*Ht9Tt*yYcQ4Cn@>w*i%EsjF6B$8N8c?L|le}5}wvyWgKet3vkF6z> zRk@~?+aD+bD?iyTw_^V5+X!NdV`a=q(_R_s+A5c33gbFGtQrENsNjgCs@o-KeIY+h z^8+7lsthg?`}7OS2kE`WM1X>$>3DWJhl~FCP8>kLSvh867K#sI{U|qw>I`Tx|30NX z0t>05I>zqv$6Ys_PJo*ePg(pT`7y_H{UAoo@3eI%_81`gOE0~C^;0a|Af_khK|^ky z_0-3J)J-+b9?bP`7b9zA_|SSrU(R+d33B6RS3YkAMInurPe2h;(4>uieOK44U0S=h z-G9fC9nlgddVkztEU*r!G)1mg=ln3BXJO+fICSfzm!V^es8_(uqW!|NC_rMcpYL53_#*${<-5h$T zU+>qKOF#zjCJ4Wqu`L<;Y&%_gmNk+J($Ug#KK;tR16OJ5v^U)<6R5oUS?C1JAUITdMWbgtY+&u==mNACVIHA+l=vplQ z-%^u#g*d_gb1DGWnRI28{PNU0`362D7IqlnK7>EKCmx(KxqVB*-eG z_S;b*s`b1{UI*C2jU@n3hU^E_fS_Pvg)@Mt%&0?`R@74A-$K*{^8sxV$GCa7@0$U^ zTg!`DCJz*Q?iM-wW-tr&S#5B#kN&co_sR&zdvL!jv9@{ea7=l zjS;6G0IEn$H6pUMHYc~hd6-M?GV+F^3mDGSF;lf#)O`zS`jd075h;pouqeS@fvAZ7 z36|X|@X;u3#2yb+8=mEwi^)eQ0|1f7vH~=^3ao!<9M10yMJ-9mOve;Q1uSy_W1MhMdn$iu7;UcJi=m_3&wnK5^uBR^W3jR;PDZbsQ!D#>!p z%z2iP1FbY0JuD!Ef0@GQU8CeDcf{zUwN|u-c4e^{ua^n!4a=;diaML02p2CS}?r1FxFeJ7gMR}iA4P(3DSY#zhk zc2-PJj<;M{Kch!kaiz9!!nWtiQz3V7f~JCyBLUjc8?^wzm7(>x2iqqGE({+BVt+z_lkH(*=8JhK(YHm}1dXD~JL{&>n$3pmHN?*NHWy~fNwPwUls)N)4cC=K(RKW`5VAO@moLw+BJ3R*n4zxs`O zK|+D|OW8(KzDo}y;jQy{^x1#MrhaU}&iSWkr-FiBd%xkOvr?nj{!Lr+`~-bs2D)Lz z#8;#DY2;Hpg>&(~_>bSnKUp7=?znhb9q}TBh}aTbzw&=wx5#FnXGk=OD;IR(vmWA( zU&$^rr5i9(*YF5bIsRCwbkT3tOz;hA5}W^SG7}cm#2HIBmovxv{3=$r*h=o4bZ?Cu zpav4bd@PoE-cQ&iujV@0e4+iQIDmlHUtjvI?TFx#laz$iZ4HzI8Y_08qdhc#5J6k| zLhWkk*Mqa<0pQE5+%PUn)=ap;)N%~hAld=mFF~#3uy6^e&fJw3!0K(0kSLyRJv(rv zDY#v*tvttjo#lVMOLwxH2zm^Z5dXhz(X~tzDBo*rN;r5W`ZSdA?A=Y)8P#P;J^xnW zXUmImSgdb?=y(@1&Pv2=Q;(c?(3DgE#Ax#d^Y^y(#y=f5*9;>MF8CFM5;ya(i^|V6 z6}L(1%EJ#y>nZ~&63F)ik5$SAsbuA6ZP*s=8?#R50X5g%Ggt9X!Djcs$vO+ywM^Em z0TQ;;NED6K?uAhp&Vy8^bDSW9^9Px=xPY=s*Z(@&!U=Zu&pN>5@nf#nr~Eg?Q->+r z9?zGj-qYRG0r`XqA$Hl$dH4l@KaqiklAv&8t7tC;F$y@`$w(-PQj7`ylJyPI$+E{H zy9nSNeaAWKYGy{OU-wb90Z=V@wlgmS5~t1atH_<)*NS&q`u53y!Ko>U@pC^QAz(I{ zyz_I%Y1X{(e%Z$T5Z<`enD~udXUE;eqGG3T0Nz3VxO>z8Th@owt&8|liQo#=3^Gl} z_;k&-eA_(5w$)EEQ=luFrr{LfKq-4#Cp_5M@%!#55?n#{CqjZ*xze_AOa8pI1FHfQl8~vxEGl3K7~rn^-t1WTuH_=938d($u?zX z1kOezY#zAXSz_1YaTiY?VcixNW$*2eB@24}7^$7IY(Jl2g?CRFoilzD^~COSq3~3f zeV>Td8IkkP_k`?QYHoI7^zEtMN^JVivAcZsYJiVJa5^9I^9=ka{+5~OHrQ+^gHJM# zhFT_#jzB2ADk40rrfsjJ)as#h|Erne^FECTFKG>s4erfMvUX0?uF=Lc4l`s}+-jcE zG1r6-<%m|-MZH$fl7I-VF`ZZXpO+^8Z^@?j&LoxsO}x0;qnw%34MCezplG$N1|Cti zOtak|ktq~R4n6(BMKNkX&|{B-!ft9)L!>Aqmqs7E9v@q!kp}*-K2KQT*=}&6a;jJ< zpMh|M)Q;u2CCO=Mqlbi${qsfq0W)<$xY%;eU2gjq0=y2*DU?bxvRs(@Q061!05j{z zzd{u8xDtrCsJ;42_e&v-NaLiJf&zuPwf#*#R@M5s0)UC`-ZX1c?)rJNi@W;=^CQD} zI5vC!@d>O&@IGl;%tnK#~0lDcS?Iri*pu{3wew|8Sps8sr2a-aXtKYF-qMEJ3R zt-Iz-g;LtNg#2!a-S_FSti-Ynt*ciKwvkBDj@#xYo!$d0|MGt3bEnqT^q%HO@zAMw zzJHCMD)2bJk%7d--osMtq}Fw0F@J{el)hnL>DiV)Du%FEGkTHU7l8siqkryw;G?{Y zkLFuRwSo2@zhT8Fug1dER;7Zv)B)r^gUFWX|Dh}Y-EygL%&Myv*_7!m`IL?;55@B)8 z|M`UsOyL(PYDIc$w-<||#u9CtO!<;$0~DuuQs#t%+bATIBjuyyzvo#d{m7hW&-Rl= zgRSnbs6Pn&xeAtL7QvKu-r7{&A}0A0echf3)j4ZRncxrqd+lf2$G)Iu8fw3WM|r{^EM7(t7ZK>SqkoLXNBQ4j1G{*na;w6Kg>B`KGcJd*-_yA8 z9LGPdWSfb@$~K1C%pkYld7+PfpCy4ULqA)P|KkySSXVyk4?8s<*C;@3&tWNbCaR7q z84mH&&l4{w6QAFxotAi1xMX&vSQgcP?%_`;d1J7tEoaA34Ka2fhQ+CUF_Gj3FsfNO`L1(B3#6Mwi zW?Bj8Y`4=+{BW(yVEo|zv+soDmdou*7Z=i04L=Da{`I81A`i4`ub)%VCC``}pTe+S zd{qmS63J|?(Tjj{8cu;zbw&_bNc5NgZu|et!(w1+kgz)iMO;P0-B ze&YFD2snBP+o2RDIlz^^5eh_zCv^oJS9P zvVrMsPkVJXzArtE*nZ?k?@hp04;i6}n>W{hp# zY(+#d&S>eX=;d7cCNa?Y<-h(XkQ12iqX@t7;f=R_f_Z;6n$|1mOX%Y}udgvuogx+D z#PU8R9Fes6@|EDfpYK+;5b z&+{UQ<8_+D>n%$mA(>rVcD-|VcURDCMJK|H-2mb$2=lh}^Z_N~50 zo`V#hpPe;&o|wU|ZchP_1<;lr0P-(4z$nIt9$Oh$D{7fPb(6|aEj^opgI`j3Sr^t? zSVCyOfW)hGL#OU1^A8~u2gIFMG9EL!)|LQ{SgswN0AJRdTkvP@mpm@#6avmVz<{DYw>h0=zw9=sI zNJK0oT}u50p?F92fE(0YuJ~93Fl4=PQIHJ%{7~cZYn6rNhm@TIpk?IWa56w<{a`22 z05qEIpzMt!0uQLkLq&f9jMU4%n;*}{baE!IbPwPrRIzM>Zj90^lPVHoA}4(>9wf02 z`3J$%4aP;dgK86<7Gco%q=a=iP zs2tNg{eGN}vC7>cYD8?KGb)i~BHRcnuNXK0EN3WW!v)NlgkQgKQrnV85pkJ)0+Nj^ zl_bdBKms-9KDhWnygE;xVvz8k{GJE>7dQYY$bQ#AIXguGF9@i@lRuP~ObtX!fW-Gy z(&E=G?aB!4aRH~_FoqN1>+oyb79X0oS+LsK_&=V5B0SmpNi6`(H7WTv&>?XMJNqHL z?&2>{8bOT^2`Ls2cwa40eG5DUx9#w>tv=yAzTX6JFYAjTIdPr`+_Lo=KgpAAE5o6s znC$UFQl-5SV255(s}b;qB_Ns#m{L@yswaS1{Q*0H@AF}$gzgMzN)@@YfW`AX06WdS zbkrbOP;L~x6k|TJpRPVO%yw2K>7k%R;T6;Qv3DinCDABjD>PBxgyc6s#l6hcgzbo(w{Jwsy;m zbFw*`gdTuiCMn<6-{@@&&vUxv=USKZem~pt8J13UQjGYin6f_3iFb0SP3ieOOpxps z0NMQExF6C;&Vy+4oc9XHXv5b?a8J|$iNAvxuN~=$22$R*KcQ_?JEaowx(u-IPclMk zZp3Rj_lGqpgc&PDmL41VPP!Q+qW75j0qz{v((K7{Z4p78N0YX2#Z1W^{omJG7i)L{ zZ!%I-ofO3!m-5?s{yy{&R)AQm)`@#Sq|NxCtTyE6(E@7K1HejS1W3xcY^FlScW$nW zC&%my9}squrkpW9RZQjv6>)h^#a~NIOsPT?XCYx%28kFD{+T}yh>Vo851=SdI|SYf z^okR-y}-l3nX`)jXaMjk@SkT)j<@B|z=eZpcQV=A4N{M|DV_&I#-6&T$z&d!rHU5(y312Oya(O=GSRwQ&X zGABTUhe?lodTi~-^T1^tgQO7|_V_#}Q%!JiS(!7UZB2r{ESiuwkGAVe`1&1SqfXtc z@Zoas(S-Eg<6R}6X}q<_#6tPI3DNL8zNR2Gh(2QV0wTB8S6rz5io?`BI3PXt&D}(? zU7zBH7P81UVm7J~@-rgIfUPJW1h3p+O&g+%*-vBew`tS3XyH@P-;QOGN@w&{_u8}g z@MgEf#$AQHVt=B@(#+e;Lor77{%jFX{^)G?c*c}-OAP<;#$PUfMA;Bg9lF1dZ&|nXj&ga~p0VfR> z#eEosM{1?@Lyu)kmhcvxCON2$CRwoEf#o-sr98427GK&S=lt!v0<%?bu(oA>W{F&K zbA>N6hmCJRF(hRr${0e2Q)%=9ucJq*!ihj)!Z`~PWDMLPdbW=qbSBd_FF#83Zw!Lu zOi({SkoKQnc=-^y8XL!cdE&J(8RIh#Itk!WV{~}4K}QXJD~zwvT+;jV+Erz+o74^5 zeH`fxz$45eulmC-0c-cej7~~GPHGLrqk%gi3=|5zMEZ7X6e);!Xv-wvXTsyNibl2L z+*diQ+49iyxB&t)bMAn&$FsAxo`45fH?kYR;(x1&_cP+nG|9g*`40o z=MNatq0O2q{Yh62z2vGB;ECheJanlV=dIDO@Tt^`r~ip77(X8N0DNofJ(w^=!=+66 zAAv9`uF5Q*V9#KxGi>Srp-6@d3>tkw5Al=D?iv}}fE-ogYD+|5y}b3PrQ$IY$2mjh zJ#fN^7(<4s+@+fkVT~2>cy_v5eN-<*CO(WkbZMk1Qip;N2A*pnMk+d2+pwHJ0YhD@ z1-_;w`=*-?J?Y+NzbtrpJyl_BdAa4xIeaQyKVp9QXCDVMx(y42iyr@ItK(lFg6Bsw znQg{FCqf5-L`=*3UK6yxNgAOKVG^hM0M6>G^3+*Pi1MN;%re8SMqXAFBDYxJH*OVg z2~XDMfXgHI^^m`0naQN79tDcIV-nzbKZL#gU%s3R9*{}4gy$6wT}cKAP4lPpe>IfX z`k$w|!o+58o@a9=`9U5LD!DP}AoDDqnMPqVQFGR&es2t(ek0G=7K*!I%9 znex3Vq(#OO)I>wrCj*FbN54{)kGR4+xd+F+c$|z(577H^$(XA$`SB)+bcInq&xF8( zPSvN?l2%%XSrEo%X@RGj9LkP;l5tImV8pvd7eJ*GMC`m#T}zLUMpYAK|u3FbHUaCTy*+o9i@{n<AMFWBzyb>y z*h_0ia4-A{)O5C=coSpjjtu$D_O2`YP^dvEBp^}aEHI?TmPYh1d-f1d8q8C>Vo86y zWw4}>mBQs%GU|_ki=00)Ib}-ZzGlFqs0(duh<@2ddT z4_tzKPjx=QraZFgyr_5FYW&^7_mE_D3iM0dW$ztYc4vHd)qLzy2y`)$Isqe>b40+l z$Zu!nV_Vvgd0{yfb6av(5&7I`2Q98kiHkY&WteaUPc-(rIIw`1ZyGM9dX)%{efAv3 zf5;rk3B!{^6FD{D``KAkA!(@kTblM{Kzqd9e@tlGGm@|J!90Wm3K)C@F3n7BHwZK& zfkf@7hwQ(xO}cy+wKrFB2-;2J%deLN`i3h06Uk#qT|5=`kDw0^Dk7WjVsByN)I7+> zfg(V=$zs8&7spjSL5$;7#t-cQpR3-ei}EHpO0WDuDXk#}nPzr=rVUogit_VO``k0n zBvp8w-&5p+O~e9Uekpj8Wa>7Y%JZvjdSiwCu#`h(f6)-v&!tatcU6U1K#&@zD~X+X z?RHV!(tE9XXZ=s(FRed#1-CAwm-mmkm-9@9O81)N@fDZy%*iTnE&`qNJs9{lSrA_R zLjBWzSJ_H4bIF+2n|v=P_5k$}SM{HnFaS~1E2NRQC#+;{&*D>q96vZpM1vtn;APSl zO*i5ca9(Qcb(bFU9%kFxzyr`QGVtHHWM@Np9gwo93q>v^7 zNfxB`4W)j`##Ql{k}f;+2jUkV%aTBk@>A_ zHG{otqieDCg<_GhM!i9wg5Za{QY#ZZ=H^D)@3*A^e}vTE>-jO~J5< zMF;(aMLNHET#>d_r-c|2+BaV-%6^+x7G#Kf)=wkK8+B9vG~WPuSD-LMw=+}Z$slDs z)qS(d)^?^}q2~Mqu(RU~+6IEON48t{dj+%!N0f#b5s64FU_3%gr~Zz zlj}Z(#(5kSwMKY|B7|N`9dlpr#c<@nxIO+12V0oV&xa())|Zd5!|HYL20uNXH`y-| zc3OT-XM?$6@xXTo|2e;J{MY+;u&jTZ)!&Q8&MZHmTlRS$9ce~iC)v%;>FpOgz4Z|O zq+hDkJOqS++%(ZAzj)>Oq!YlgZ>k-2=F3hw%C!XESU7q?^+kU_;Ii|8A~DQdle<8~ z2cn+6PS$2;w(9&X`8@wG@SgSqad=ga5wha>hR}f}5MOlyZh+SGFuN6b%Ed!SXIyzc zIgq0oqwlclJP8s+3Twm;nr~1kVpWaJne6&jH5r-_F|eiJR}KnE2Ei5VFbe@sf+0DB zh6kNq0US5l-s!VfHBT)84wM$*Lb$STbSccqsHM%;%`Bd+x-ECFbK3oama0s9sEbtd zcrzH+=^at{zUt;t2>;-esl2Sm9?t&j^RN84YOU7~t^9{0&Q;<#IGi_9^pmXs%GvqU zfBaeuxtoR%P_hWg>hHeo?~%Lqq3cj8Y;MQ0Ip6Sp${xg!p7Vm()i1CGvSN$gUx%k! zX>!Ykant&RTxBC44m|jMkwi%6RX^v2M@+xkYauU@-d&Emm)TE&guOB(@2I!?M9p}T z`~De@Lek>)?UX~)1BSW%TJ2&aoOmcLI}_DOSr_MsWzEYnDG6Jz;` zc*rrUsfhmxbz^tov&PxdBgaZUSN#Ol>-`==alA&9{uPM9pA{YZgK&k*MiimOf@hH{ za0EJPv)#7?z=GYV=4_8WnD##=Om5ymGyw)qd<6@=pta)`<=*OvwDrTahRxh(K#VS% zfX$G7&xh;%^{b7z%j1U>7R27}0;^A)W6)KSr%v_ZxxYhV?Cpdi=eZLg#A&uszpHA9 zh=IOCs$o|zw6pgjj_a1ABk3GpPR%fhzzmZ^`wNfeza8kT48xSAc54iZT&C(dSEk-8 zXAFVeKO`snz#XB^5Wf6}Pn0b4>c-)8-5!TM1s3F{yqMUw5bWg4yHrMprKsFg+wHSG zv!pT=jnL$U!sF~(!L~IVN{2x{u+1*!U1yb>E{Q?f;L)t{0V zQoVI}--HBh%Q@zIPRcghc^M+n%Q^hh1=^Jt+u4JL?Q7!EeR2lKP!L@qjaPAMMBIO- zFTW5Y0ZqW2aT@XN&W629>Y{VHWz*G~*?EI}a@c(Sc#s|H&gHSx3d_9g#!|E-WtjEb zr1goZON3HMtUWR&0c@TODufC~PJ0;0Rv4W1SWZ1>^#|-iQyUQ*DtYz%sB?(Pj*6b(hT*?(F7`NPYRZjMs^Z?a@Qr zXU#+C`u!k(Kux~c@Hb6>j+U&?hQ|r;QkKm^`k(K=MoIyK{PMvWl;-{d4yhh$W#3%3 zGU9&J$n$AoP^15WRU(5a|YtI0okWR_rl82BJyiG3THbfr>MJZpQDJBuniSa zwj>?k_l@5TDK+7U`*RWp+*8l+nA@0v-dIkgYo1shw!~WAYzcPmI+DE)3&H4_xCinx z=PHxYQ@r_Pn0ir1{5aqFv)SM~Dof`}Dc^e1y`e}-khp%TkGgTMvlSBMqv5|gcuA3KdV^B^ z6{AnsWufibARDbcjkF>Ayde$313=H!?X+iUwRqB=W8y}~UW3?l5A>?MF*fUm9M?+0Vm)#;yDCWr) zhLCjoHClgsR2x2-({PSpwJ+J1cBah&Z?)SJ#Y_f+*ONcZQx=r?VNE$8%wvOn*g)bs z_h*#l+3Yl;6Cy>A0?emNiPlj zR~9>Ua|gWyHuz9tPn@w*-`cL0p2*BQVTp;FOtg30N$cSg@_GhWxpj&*tNb3x0MCn< zs_R%~(gn84ME^zm4nOjz?&7Upr7FqljuUc&RGf(spKO8}!qHD4qZ3@l!s`6$?JEOy zi7OWNN<$g}8p%zW7C!O&r476^{slvD#}tp=*Z!t==IRFjf47O2wc{%!P(iAKr9+AcNB&&`^HZzr6xzJ9ls zh2wLwbE>xz;;nx_mSl>lLwkQn-%eRXe<`$>C)~)a90ANIC_25 zIh})eL!%x0DO9)c}6if2jG4{ivoVK*D5ly2QN|BRoZA+xtwKk|$YC1aZ=n@%;I z*uTy`oD-%d8}5RY(sJKL=f3U?G5~Lzkeiwf=dS@@MxBlNYd`tiIh@m-R2Q|7p%kr2 z9UQ-pGuSZ+H!Aik3=rE8A&Co7Z>qWMvVOiAc7KLEL6;W`mPg0zq}lFQON%O`Gg>pB zwZ%zOb@1L>zYoh4-!aJ3fv^ZtyiwhDOFCE>6pnIF;}0l<4!je=S7W5)O$InJY zhu9tQbyL+}vw1V_U7wy_(wnm4%y6l!+SpX@)nKY5*EOz^rTjl3^_$-ftI*TV`)c%P zMZn&RZae;M+ypqRc-E?U)K6`7SNid^Yo0}cAm($nWn&OQP*l!^aQ=OJg}fBZEOWY&;Gvw<_@|i{OWM`53Fiw7QFa0__ zHTBs2RMa*y1F@biUyuX^DveDwaf3->9OtlxPi;d)ZuA-PD?d;F51Guh>_u*w-FoL=U#9W@&CgqYdxnkA^Bnj4ft1a> zTg>`s29^Ha`rWHL-h70>d3#?xd7FHqd$yQ(9g35t-g>M;f@@NBlxVX5)Ggie8SMwr z%1za?sEbFuIs*;X0Kj>p*UUw+2MlkBgYQ>nry?ezK&zpB@41L-*Zk0{~^6M$jP1jSd z_w{Tn{UmLyPRZp0y74R z6%cww9yC0NthyedQ#;8Qypt*&Zi2PaSf&fRpKuko5$?e&)i||_;{+n9r(Q?xRWr9m zG$!%3v;T}5JcZ<+@hmD0)#4N&cSvo8BhP-`J@RG3fIU}a@GR}hkV<-&BjuA>+8?ajT+=3 z08P#4$4{3}7?n9|{kcH}5wl}@NAZZCMx_Qd*EP5SmF*WSBC&kpsn4kr)@damLpk;d z=YkkYN*|_#)*kIM)wt5Wa0Ow^X^%kFey6Og7E$ZeE>yg8bS*Fo{7qGRV69( zMC+^ja%OH12~0;RBxW5JTq;^@{u5V%jDj=TS9LEexZ7Nua8w29j@ZBDT(V8?6TRw||42n|`_9dU8 zUWOT}*OCU)1=kIx102WXf`26~qTl4vaLwNo?6&$f=iv&a6|jFAy&BttEj5k5hn`Fh zsD`SMQr)!@fARE0uaEp9tYjcOx0lo7V_6a>vy+^;KCvTRgSA7cy;jC8%WdsSH8>*) zbS!f!_oXTOPrpFodZtcz=4_wmav4AZdBSA~6I@2?*Q+%i+bZo9aS>vo+q}1ZQEdSI z0eyrr{$2a;E4!}u3Kep8SE~LxA78bt!c4xH4M{&*IS#U@*Cef;^3(f_nA|sDJ7mJt z)NPhiE(^JR8ejlo^VEvym5cIhhvvFR>q2QPDl#%XO%QZOh#rLz6KJS5&^onM$a-7y z+jNuYk3WZH5EI@D>avSxdnsuJNf$kOj(*SN+CuxQ^^8u6T}z!as^kSrW=_(cN-UsS zSjUNYC>7@Zkl@S?IA<3P_*Pc9C%EK_9^gq7Tn$d!MZSHOET8C^WKrL#p(JLk#APG$ zi~8;n>=p7vZgIoQln3j3nHe{uU1@m(!cXw3RytImc$=V8K;H7Z3RwYLg3O!9oLhI5 zoE~wiZ1$CoUGtAh7c#;P34)H!(u@;gdL16@7BYRxiJ|ylqL{ z_5_O{aXVdYiWHdVX&(PE4<#5&T|gCgY%O!uW==1j?S?XGX=v?21&jJUx{q#yYn z)kI4xRLUv%zdFz{>IoHN5XvvY^(u!VH`FYKQv&fOWJ`@AgEr0Q@!Ev40xczv!%_$Z zSDtzX^B@!32 zPQov&BW!K(>~YMbsIi3i5ki3$EfG&ItNirzz%3neJf%uX0HNOH`VrgaZl5+x{0_&D zrw(YteD03k*5LU{VPB~iAU(6o)0Qli7PhwsLt~LBA#2uwKfo;BY%52_f68Cmww3qF z@Wcsw;OK#6ztfsr6TEku@yDKYu6U=E`q;Lg>ai0W8U*2Q z0ILD>bNdK{`n^k7x9oXYUHekuZTjunG^>&qGcJTkL8z~`hqpIh`4B4wr^>Fvl7c_q z9P4htUqfY`Ibcb+Q?e&nxnhEv?2r(i0mrH{-Ale0On1(uB)WU|QgPHZs}68XJcaRfHf2*_n#fjEQfXU(-j_;fj~#ZT z1~t-p`q++u$oH*(wHfNn_PiiC35gU!@07Hsrlwb=7^D{6eMYUDkrZl4L{8t|yrI)6 zWEe7*G8&}QE`;k0a2L){bVc7sh2SU>*j`PI)4~9_G~fvywy=UQ_yrjEY(M(dst#%o zsTLvI(OL+3jfblTpVrT$vq#kp6wp^hEFH(;^l1ClGbFqw)dM87I0!BiE}eu>9KB5Q zk!uS`HxH&?H7>ojmhr?M`5(f#B+f(%3oEr=1l9_5F5&5?lo++lb6k%hbvTVo!uht2 zkJt2Asf%vfYS_Ck3DV1fFKmyh-FQB{OA4t!q)qyGV-n5Ets|`3F_#~;-uZEyR(nbJ zGfu|`L&K-MTvmQe5ON5*BUx95{G-%vJk-w!{7Ti$P?Z-m!4XsFsgImoF!$xZo7+X; zUdf~P4^qfdo@g8HB42c>R@3-tM&PUbX9rEzC8#=2av3T*BpDFW?8V8y3}C8L^6=W| zbsbJy(qjo-m!(2+G*#DDf#}yT88`2B*7<_&U0y2Z&>JPB;RFs1>jO6G$Ltu8?%EPi zr&1)d6iX3=+Fwanb{B(*6Gj+}D9h&1Vr=;(E9H;F9^^7KC z;oMcq@(9-ceFMqh#^MC#Umh}J-&(rer8wU{m@sUqtS5V*tb}r9UvAf~YV`2j%0WRg zg}hK_tI+LQdvjIFH#v&)W*n?Ua>{u#Z<3ku+@P>A@63hlHKekgne(KZUq*1d)bB!} zLCIRn@p6OpD>F?V^HQ^}f{W9aJ`=NZjvZHVyfEfZI4j5|G+YO zwa>iC)t#2=0~s6f*x^|&Ml~f5m&y<8dI-}EL597SS_3CP1kH4zMmkiwe+0oRid03T$=*UF#8{i z^rg(PUf0ud!!}s{NPboQA6vIWifchv1EOL;aO5}?>0?TbhsOaPmKF0xc5q4%l`o$p4q9rCu za}0BAzha;lXAZM}K6y@W)sI08S)97;X*H(k?Y(%5)c$6_tLkur^q{L|8Ig^6kQJ-Nc2^B7M~0o3sow2az65Zs>tvEg*(`(sA&%cHQ|3H(};kp;d@}v8OuM`>!_P zK_SD!tc}ti%k?|$%FNH;uU7Nvar3Jwzt?%6D5QeagUv&xmA!S#s(ioY9F?}IkyItn z&>?GV-p_>bDSKx@OtUieTj$*XvQIUaP1eu#?u^^&s;XGRD2WVZnhA58L6@&jm<_iL zlcjFo%OQ0?y(5ty`N|hkvgi5Aj`UzJOb;Bp z(uZ6)oPLPwX7@iOoZgme+uJM7$?OeQ$3-wul_4`3`Vz=i9?|3hK3ALzb=hdcjM^?* z&hm>=o&H~%&baHi?^lxxwT+q(+5wnjIu{vE8hXR?Q-eRVdgc6rCRkLCZjxne)Ka{Q z_k9R4?uTagN$AK?$<{TQljARBJEN%X!UTK@os&AKIQrBx3x&}|rZJqa$b!?IWjha)god0lH@eyh1d-lOYdG%vot+b)|+`z;+)ya_E z2F@(BVg5QoJo^{Mip%NW8c?I}*Y@$>q9P9C^P&6?Ei2xnW!f^GVC!`@EjPO?(qC}OFTs>Bea@Wc5A(FwY#bxtbk_E%Vm4{v?sDuar(Wpr5AQt9%rF6gF5PYCS#l>P*p$gN;4RCJP0o%S{5dKlD# zdigdPp@3w@Sqp-Imi%>IFw!reod1rKjW60IQ8;~Wyd+sIKUp*jLh>VvY<%h~`LZh} zx5FZGJ4{NPnn8#871`IrLJn_uqCRZp|5_7_&g@g!FJHiWo_&GVSAY|h4&-b4h8^PV zA8YtGwJ$Uy`VO_hNsO~eA^*8`#2t6p)&794WMbt?vH0nvzwg>Op`6!OL-kCJ(6bg| zj&;Rb)rU@WQ#KAJp?91Zmh5g5({kRM*W)X4*8Ln69u^KN+{Rydf_G#j4Mc}4$c~;K zAuhYF(&#aNRlju;w+UnVoA#V{)bWEZ-y7RiMG^UAdbqwe5s-|;eDA({jpQHxahpED zO72Cy%AVjS~tU{qcWmoJxz_zVHT%#AdQ&?13(u+OJXiZF_zgd#blM-zIPWv?M58 z=Z$3p+4Spst*sYz7P@U*vBFHfn6fOU+pqF3`&4x?ReA{}mf$FXvFi)F#s+48J{=5d z6biX6YJLKA>D9}M$`$yviW-i&o*qbVd2AK! z+MV{T77ty`m}}7j$LoO+EhO{EZyEb5El>ReWx3UaDwXt2FOEBh#Kbv8Aasc^LMe^m8RN z)DE|Tyh?CM8NxY4`(jH=b9a5m8O{ra9;xV$^$Z3_y1#kSSkcq;gVGzR^G0s{e@XL} zVP6P6Qr>E9d-U0WM&&pgd1vginxD=QydsD?-wLTZvWIx>X$SW?{)d(R-;ud8@VQ2( z&u7-ZMZBx&F)(sKx7_t8xr$}>2Lx49>f}+D2zZOJ_M8Z*&yS`!=787#u$1`E3<^q|6bc%n5npZWW-nbj*yBn-S20Z^nYjkf7*+{FK6@X%HDPu6i?Dc ztjCtL;uL|qC8zQGYCe7$bo)G$>*ll`E^wO4gQRx;@b8gX@!#%)srzM$vp;i0YSI)` z(#F97>)?#~C5wF)DLJij-b>U}52FDR#k2SS*7S_vqgKqO6J=zYB{Y^z0}Sg$`3NhB z;5!jwcfR9c_pR><*y&FHl2I_C!V~P&4ad!qtY^-XuB~AIkr_aa3SQ*Nu#j&<$=caT zgdh*fzI2oQ zyQlsmHkj|ku(Ef%XbQ7MP8MeVA!~bf)xr|ncZNT`Ye1L{1v0(gmT)TB=D=9Q1G z?CAnYkpspV(m6P=)ZqGvPmV@saQE{j(KB{vP;~ov+^@H}RH*L4ME*?hpA+tDcb+q0 zfGAr<4?3(s^)BJ(PRwm{0wwtLdg0YC?I8I7pFSX4&lIgD@}oI5b(xCDVVzw;vK9na z?CjSbgUkF(~GCiK6nFyRr%L%wg&Oi7ceD{>zJR#_cj1JlV zCWY&41!#dkhyHl1DtEgK1iRfA37>YscjR2;^fR)OJ%Ga6LXO;7+5-sWxmx7S*T{;Y zJ9%K+4}&YIJ-_ReWgzSFI76yMho)T~{k_pV-_>t4{(X7m*SIp0C0Jy&a(EQE?W?(bH@!PKJ#OaT zg@O_M;8Ae;b2uvc>m$Lk;o;%qmf3a=Hp3P^UqJYP3nbsUzLq^32mbhT%9C(8AOLLb z=^DV>4+p7Q`C*CA474^fFig<<0>S}S7ABQ1n9=(k_>3p7Q*O*MqdI1+!KReOUN5a*NZoQmR*t@FAVeuQqb|M`|z znfN;9XwB$GlI>RIuI0Jv*v09Rma%?1ytd^{HV6ais9(AW;frIZTC^hbP|{t@0m<$S z?OW%#MAJ1qE-UQ~ZL^2C*tzKVXa&?BwXs>Ub*_z%I1Y}wjG9Adm-IR$xoX>{;Dg)jw)V=#vgXs~bE=gH{O zlZa*qo@p5cz1PmH>~F>sP}`oD)Kz$JuLNwxJ(_u0TlE23XPdZui<`Jn)R>zr>S_gCA{X_mi^(^{rXumvPLNd=Zku2Z4QX7L2#JoVxvI%V4vV5 zlXW*@`+WNb>^N6GZELSC{yV38{Mz!zSNF_6!1G5$bobFws(D;T^RGKVL4pZ{QU2-eCpIQ;SaN|ng^4WD%!Ju_nC7z^ zJ>Gc2Ya{@#E=|r8TQI6NlbHiDC)Zz9xlEZ_o`i{Qev?|4UI%0$*xNY){&jU?8zeAH zgWE%^zyMa8D_eCBAYvGYRlDLDkbkE=(u))2>;c#T$uxgnuqkqCY+a5iw0gXm%kFDA zp3zb!ipT^^6V?Fh2yAU5FH0pliji;hv!=5BkyU4kob7z;?LZt4`cmZm8NgTsY*82u z{E_hLS2JgTO|Z~6_;dlgS?Mq0DD{l=DWqa z0WAk!TzF`yf#-nR1J#(=<x_vLS>9EyCim(J^dC$ZX|4unJu`PQEz@C91lt3Eo&0f6)i+;;- zhP0v$b-xhYG(Y&gCD2&RzUoDC0V*_wQLxH&I*kR2G;zhglE}<3@L@AvA-XYmvEynH z@_3!60VaMUeej-9XWsOW{NTB7*=W{x1+RL{L5@n|(U5+C>F+;4*7-Qp&wGa7nxE zHR-t;vCL=R$0K7kdE|daq4NdJd=z*?a8X|Peh1;jzLkGWdpI_hrO`N6;3oY4k@eP5 zZGF%Ba0?Vdp=hz>{ood)V{2Y`)~hr>1?_n^e5+3JrTWak*8pX-E{Z8Lg!4f z&A%vYj;Ft6{MmD#rz@W%J3W{kLd!-l%dUjHbnzqQ&V^M%^4Q-#IZ%DV_;99u7kb8A z%QlaQnKN?m&EVBUly?myOSukPlM6R`;l=BP2na&oaa?e9vF5Nyy?F0da%;b;{ch!0 z5I$T!1$X%+9+yl(zUQ|PIbbh58CGAJS%BzyI0d29m1#oDSL=BZI-~8=wVnuEFM_&8 zkQN(afM8W}0haHo5!oDU5hoo{R=n4YGl=V)zeUh{yxv6Zr5r(@Gw|{H+pwmhmo;O( zglHG=BF8;SCTdl3jX4NehIq5``(JkySjY#0}21e}r4XlT|AA9Zyxk`Q$Li8OKm@NK1WI@RDS_^+a*MdPs znPc6NFZeewtce&7>N+WJX5_$G8D3Ev=FRsqWyZCEv+dm*N-u7ls}9p^t*1}>DJfx3 zJ3fy)k+mjps)B&O4<%Qc)Nx)x#9Kn?IVf={r;qUk!t~qq)?B+Twh#mge5vxZ2g3fR zRgEo9RY}iw?uf z?j^+Lz3wPvaC=`MB;#|rnZZW(P&mdYcG)>L?&I=?w%&1MB1^w|%4Jxn`KWEUy^uws zyxc03bgw?SH(i5=WJl9y1~MRh7b_|~h^l29a`h4ExjyiIPqY1v+ou7)o3ya_z&k{n z-umB;(i*Ty<23?cx<<8uzj~g}*PDd*SDZHJFT(seGd&?;OzHPDWh#pOV}#syIfx~Z zP@g?d8SaDVmkPcx-lf&03Y$eVnYK_(;*}QF7;%2OG{W`(H6X}Nx)7>sNArE3WXK6h ze>V}!SkpTPg984dWe-qeBEL{uP%5lfXI_+0WxtWU?Os~Fth){-Von4o=?=a(rPeMc zwyv?z0ct@1LQK~YXnn6`8I6)+=wF$R^~xwO8&I1kq=b~@*K@7TSHRLT(05q>Nnh88 zj@TQ2%6ls9e|5Xb6mR=H%=rW?tsJ>jvU+geBN_k%ml4qXD6=nN1?U6RoD|&RA_c^Zp)h#L?G$$b8=Ll#4}_fB({(cK#&LG*GhwmOx%j#$8NFv55Ia? zv{fOo66`Blk+B@_4KYJydmb>%#EF;yC86EK+h)3^o-@Vz1e(l~rtHG-n5dG{;n&&9 zCG~6f+k7VEKsA~7~cy&hG|c^+GY!Z`;+qiD-n#&Ni%t|s7g zQO~y1Y~HbdG52d}dxP??dQjVLIf9pJ|J1z-(flhj%%yVhHYhV4&F9M1hsaC-(e%?D zO2BOVM&%_5Bev&nyb~nei&FtQbZ;TS>G6*UId;-7L*TXy^t{$0Z={qP& z113u@k7smI1)(Un$B3@0D6h~m-D1cZbf00?p7f5xaX4zXtF{|y^8=K(ZLX_LFN>JX zaA-<}eNzxA5e1Gb7OWetwz%n;@e03W1;x>XgG7nK{&$;%VYO+ zA>+k4#@TibsZZYbQ|JC8v4*=ITuoIi{Og$p-G$Np-3C$n{1gLW!{0s4`IfKkG$PXp z7{TVTFl^ie5YLd-!FKCF-UDkl*C|(%MU7#>f68tXW=Q^`C%*1wVjyIG1&T-JitQrF>1|7xA5YbVx*-6(gwxLoWwQ^Ue^W z5DfXGypW8nK3{L7)-oJUBhH~`3XF=pi}yOupMx;?$ zqQRm~$y{R%_8M>e#)w^z!=aoaX&<4C*IA5+7$t;e8ne;PoQENb!L|1O=EfEV0UIS# z5vkAs7|LP8^&oSpP!j=QtU%uuGMbRs&$LIXkBmNvyd#ast8CtIgFy_JDbfs*m5TGI-mGd zR@atTtZhs3gkVo34g2IO7S^bzqmMWq=Qer27Tad+^Lky?BGh3SmfR>cSc^Sa6~QEj zxBZtd*xbb9Kg7kkc~#xc)5L`fap=WuL^2FIgCiYC18sN0c0rmOLi?D{U+wgoafl(f zrLsa+DV4%*cFZ`|AWKz*XMV>qd#W8^4p2XV4XTlgXdUM_Nsp%9-tfn2LxAB6ZiVlx zy5dB`9eZy;ZUE5xi)4-?9l_a`C1Fquvyfa&aSKSzc2(4itBP(c6F%)wCzBAJ5?Zit z$SG($r2ZRy@^_i739$4V+p0V}5v+(^a49-ytVDUqc%ti8vZLt>I7KSUIbG`AMnewf z^L--1fdsfpe^$&l0yJilwj_{NC0z=p&FS-AxcsjOmO2v?0qHTgKgE`rLI;h)@A=D# zl=nKIx9Hcp+Ei?i;O?e;nZ^m3`18gs_Hcy@Y)&|z4)JTB+xTzlho(16)kmU)0_U*t zMUF5E(HdcL>gX~#)#W5+g&IK}lA%hrt8ZTdgx~|TIcowbmG7U1+wT>C{Zr^jkXJIm zekg)3pzxGDNSKA;vsfbK{&q#x&c(@eFo#S<<+!NmoeF{qVZL;(fFVFFfK?-Sx!MWL+J!D5AXOvGKBy`RM~H(wt@{ zvBRt1x6029Oij}32c@)GlUhkloZnz=RX^}5ehCvsS?x4jhO+ipsBSf-PT$;D53#D{Fp81hta=n?|Bb*T@-ArF} zNTXS6pzXV^u76K|ATd6Qxglq0&8MR;Ok2OIWgEbUQwDi%3g^ zycb3;I{Uu(d8XU6SnTJKsi3BOE&0kvd3C};R~Ca-{NlMe5>c05t_(g{%N%0#W;a)?e z9{vu$kS8Y!*_?8K?I!KG^}IHjiuAd$>A8AeYy*NWx1Y{46C#f+{=<*Ob31vAd5v{&g4w~s&4>_#?%ctxL?+^&r6Af$YB_|f zeYbN;s8yC^EzJ-E702PR!3B3f$m$vXZCF9tNv<4T{)tK?v&792W4 zoy17={`*?M8f*N+TG(b_K)@DFE18*Tr=14mQ5Y);3kbxSTA&z=>r4HO z0t_Xj+C;gT>hG_OCUZ1dclg=rBu(Mr^eO~~x`2&L!Izd71*S*7o?=yDecTj#q;2B( zLhpBN%*r+cm+6276FF28BXrE;%zoS!7XNAEH8tSKfm8j&HX00*!8`tJ|Tl;%(R|m z2W2ZT@5p>=VN5E`=E}Nz3{pM0h$xK#S=g+1AbHIvAy0TKGeYQzrMjY9J zH*AD(r*P{KBNI*s#f%oo5$A#wyPL(j?k(~fVdreLS&vMt-(ZX9BryqcqMH%umgpoM zFFi*el$o1)I5MMlYFUT`xhiSq`8i39bG;AdCwDiEPKX)GYj<^I3_&O~KDMzSbtL-w zjrG*J0@lq3uY1^Y#pn=H{HV>-iy){;L_l(tDjNQ)N2)wS3G#Tv z8C>LZTWe8;=`Y60q{v?pxNBB3uc^4zL3Gpj!ynD0bFcqZNbTCiqy-uBLq)|sL;1Ry z_sKPIUuyi1KUNuvW2;qSt>FBdK@#c9mV5R)2DCWdK906a+a2hxIjf%Zue(~ao0NOJ zkx<)17;FIHjQffmEENv`vqnjXmI8)_SqDh)E8-TgvGzo!9z@+wvG12&$NB;`aMKirn+x> zhLm$*p!is897RknhIkZGnb~p7=`Tc zWfkdWdxV61BiL-jJnDS}F}~#;FsnBL8_zoGN(oiRJCanuxBn2j%$Q>0aURthnkKOI z;|6W86RF?8+dt=?z0;IS!Kk`q-SRXV7iirg+!vw29M?nVxLI)v^l`MlNZ89Vf3WSJ z<#SyB%5PPh33{c#u(5+}`P_n?5j*kIS!vyAXU|Im7r%&dGK{4irk5(>HXC1Hkd0^J z8!sf6nJd0nNrm&rRJ&3DzuM%Ua#1djs$+V70ka!%5s~hzbf*p}S}1VgEtgXdf8C_4 zXLIzb;$D*Yb{OK<`>8yE{BpT+{~7+}JyYtSJn22h>GlfJxunm&V>O&_TrHBiDIELK zQLFUGs9r;+-LcNVO9ShQG_#0(8x%@l;AGFG#q?E$qC~M>5G`bYH=*$jzgne1(ko2ICc>-XWZqtJXMs66D$1g^l7 z<_YPy1&N#_&O>L^rBk(1Z9S@QCz5G9UZs7{L*ERgkwytI$!hC+Bl&AG^fYj=E#}o_ znlI;ajlBSM1zxCDJxh75NinzKCm4gcR&g4UUS-J!o?knY2Hky7#EB01won*Gh}KLg z`4XqVcMAQ-nzYcoS5-VIRF9xQ>d?T9g#JmnhnAY5oG(N6_)@M&&>4OA{mc&wy$P0SX)~V?fMTrbyLTr^y@do%d zr8g~@Fo3Ysdaq?BH|#ohdfjJfY@yC2jfYg3mkIjijR5`VB*{}r=m%ocWuB!i5q{m>6T~4Y6nGb` z?tc2}_4A)aBxxinye>HeBF5Pq(pZgE@tCmS#?@UN)j9JbCzZap+!G5@MY%MjR(dYf zHP7QVe-ZM@HYHS-Civzq=72;cyIw?NQ1TIL82TQ-H*nVxL^AONVLEZujGRIhW~7z5 z@K3k{?=G}gbUSPtC4r@c(YPchp8}J`jTKSh6Rgf4QDCkMS4Z}XPyXj8GNSs~T}S~@ z0zD*t>|efY^h$Q#FP%Q*deh!6*EIY%d^*lMf5~g^h4~zK1F1kIZ#zv68782TA2bdT)JF5&lxc2>!L(aPObpDF zoYd@X#EihhK~q0N$cFgv_|GbxgzDOdLE*Fn9?jQQp*j)tnx+Lxq| zbFqghF{zY4K4(I_4>wf!bA-(d9|<>l`^)UrHhOn?ACxXZ4x0rA+U<^nBJf|pulYjC?xN_;x zO!aB4?nHy{K*XC4d6~(vnL)Zc!kG`|sTbIk_?wn{XXm)g`pb-2c|7|kCNhRND8wHu zTbvo^Lno*X67{q1AS#l4miivk8VrgK-4#Ve5NR((&&h1Yp{hHVRlvOV{7rBF<&vES zw<(b|ZOCXJO9n>kj2RJsi4%@AkZ0M(;ATiux@v5+4`{y}T`}^B_tl?|(N_P*Y3DXK*$W|DepaEmR z+p0kBEmX?+zoi-u(%}fB02Auk zyv=w>@)_Cn5bg6WOB8S&DIX*z$LZ-~c|cvIbm}@6;8C&T7xI3g=nZ)i44E;pZ*%u~ zqAtU`o3Q%j3TjtHJF5BX6Hd_zKQ*E_Vh_5pSO?Ii5v%oI6{%+no4YSSE-GS5#w#}&{)*hSKacOZyq zgUP8Pn0O>PH<5AyrcwdM0q>KJgH#Xu$2{yu6gq5}QfI)>^?m=}BTa z4&U+0Xlcrx2(yjxgu|-n)*`=s(~_v@&}Qd*c4u1EyEHp__^J5s#Q|f0?Ms~?3N$H< zneFXjKy;O-c^Y=GJ*{nE0<&hH+3CS{%-pc(ra#d=NH8jlmoAYM>p7+B05g#`Z6O3g zTOy1F+m6gbwa#u-0O?z!@ewl+C?Zk4xQ1$X$oG$i$vtPYc?JH&SL_~Hknn+G(ckR@ zRf8{r0bUU(v;32Kzu->*I|eXkp#KXZ@pGp&oxT*XM`_()TM~oFYZ>~{zp6(bp!5l+ zMuK8kvpir3Y)RW)ROJC3qJ>BjrcKFr!7r>9H!G}u*q1PKIKG!{w*jd#^hGB=^&c|x8gt=QEIHJbuGnfC zox+-3EEqrA2SQW!NmYbO#$|TJ#vG1(i9;J^IdN+W95u?7}pU4VXJUjfZNGJn59)4r@ z3$b{aOuX2&S@1@?IV=oqeBdAQ^Ro#ZMNjc+mTBeJNcCu+b?G`dFhHH z+FalnRkO>ZP5VmSI+p9Q&_VOK{<>%o1%fx4^lO7Cnf>q1^&>PgVo{$Fi~QA-#Uy2A zyQXdrHZ5nSs0GXVIX3HP>1yGXVhKWlEIqG&W%3xHS}9*pQQU^&_Z}xhQ3=*APTQ?6 z*1m^navl*zg=ksY(VDrIfmBReuy(5Lp{e1pJZjxbXx>%v(&WOM!m-WNyIHJL99$jL zG57@54`(u+e48a~1d@d1c86c1e~r)EIwc2}2f)#xh#SG_HB|z9e};L3 z5qv!7vea-qr{69JuY*5)+R6rkvBN?VK49IZ7m!^}NAQzcGmtt!y#))V&xO(+UC4P- z&cJQQ0L+MQta3AE9Lz-nx2k5Mf3)Di&Aq`l@!J4hwZ@N&=oVGIoATJKcU%cI>22f| zC?iE?M5_B1&Cl?@TAs~x53xzEG%C(wIn=1D1}!X7vwU5;R6h%xe>~ZQ8qUN+Sbx zIJVo)+WtLSExon2XTsgTf0dKjZds1V0Rw3=Nyo?oQR;6;b%h9_?Z-D@PW z^k4GHOuSIaEZ2>%HgtzMB+f2xL*80|qS;ie6G>ri#5dBG{lj(uzNqwt;|s_l_zOjR~v8*5=qi*u}DT86ehVczvQK4gnc`?#R9Kz=cn> zOa?ln#TK=5^LShP)`@wW%=zle%FgFIjQtSawHLqm%b(S!06F)EfD?q5Z03 z7}*Gr>MKkYQZW=apKl${oUI$L#;>V=8PN2VMJ+vEyM@yJ2Jj?!`SYu*zafpEEHV^5is zu$&cKn$IyEG&vS*?BVeL%n9LLBb=W~12d7Qb8a9;V^4jx&)@vYV?#GdF_MBalw$>% zIuwzylejkrstDBkm>vIq5dSkndlYFYD)7uXIJanTn(*vTc?O;dzMlX2PnLZ63zlta zMb_f`L0a^rX%S3e+zU=)50?Ks*T0`b7gaRK`qtLMJDg(eJ1<8X;H59bHo_SQsw}h ztwY=9{EzVxqWXr9<)a!)^0_q4Ntx17hvJ?MT9Nd#o*umnhjQqDLde6%PV3MA!<8U* zlpfvY+&Z=kiZvLXR$t}D1~_J}Q!!v8D8LP4+W#NJW6uXjj-?7E&E55x)COG58q5N7 zWI4hh+{p?Z6q<>O(rU-?K17X3CWGwtRg|73@GHkvsyKbfLYpx5f zC#^-XatkfM|F8$@`|sQ3L&M(EwkDQbl5AR>)Tp|w@wD4hcg5ZISZ=mHG?_j^UG%~UqI zG82bT?ytIaWhD$%i-YKP8AgvnFG3(v4JJ_oMtOLzvm+%YT9;o}9^S)Nzbp9d@XXyS zu`MZIWp&#A0yM(^_kk5IxZkc>(9>+dX}#S_oNbG+H31UM5`di?<(XfPaAOr+=Y*t( z9?RtX`^ON);aVYMLM3ZOm%26YW~rPg=8gZ|Il_>A5k%Ey4k8mowgIje6>|sQYGQ}! z3F)?HJjIbXO+tNAep@yS#-S2$dg=dpyCMP#*0e2e3qOwWP#qyr94s{*s~={{`XK(UD)m|91mpX9Xma zo;3_`g^~%3@0sbg%48_Zc|ZnXcv3?br#Y*?x;v&i!~37N(%%pKbBPrI=rABsrp&CX zwW$`_dCpzc!H78hv-@hT(qOl=^x3sU_QAs-5o6kC_At%>_v_mhopZncHRr#N7bI%K zRrJ2`%}OjzZy{=xnAjg*Y}Kk#KeRb+WE>~GUf%!b0w8?p+tZF;e%Ms^mN#~($6iRv z-~qThr0R83jarauHlOSF!J}YiW3Q!c^Z%IY?~Wagj$}V8*$R=VVu~mEk-%P720R_k znt0S-7+Bz5LrVSuQu`$4@{je_hvvIXE+qHDHfZ{dI2)t&Crv`f)(~FrEuLZl9o&mQ zCS!H!_y523_L2CxXe7yC$;3j}MZKnGe6kGM_4RY=*Png@w_~Ce8n4UcLd~UwwfnR8|NWovl|QKLiHz;ro3l)|37X9JwtiN zKtaS?xKd4^#pQGgkJJzyq?LZ1^Oi$~Fiyy>xkXs{E!E)!DWlpK$(z=Of9wz+DSSiw zjbDF#!on`$rEPve_f~d<5s6YPl<+~m!c;_MsOPQeamJU|A3KZx6=VFLBSoHuYEqjt z)(n)`+bBAcW~rThRs%Ue9A?ytdl(VmlnaK{u`nE&)pzKfaDu}lJ2oG?BzZ>dc@pBr zWdvHEbqa2g@QOx${|*g4ZKQQ)Qfi_2$A0)72T2;iENr7Rq((dS3+^}4Ld8*^Bo^1x zk{6n=tgEv@S~}@Fz38qF;4km{$E)84JbT>SZ5`~}3@12+3@g9BL}D5^MFgM9cGVc$L=#$~M8xFo@bKqw zR=biQO-=Qu8i8i#MkS6T&tiDwIhNYK+f~n*0A< zZzYU?_v(ita+17J(ZX1>qzs8MaX=}Ag;^I;xdPqusePUMqjFd3q2phu>Y{;>uGGnnXeN zPdO6Tiz#|P%oX;x7wxaY;|;vj09j9R0JiIKoTG<9oK;u(E~v`umMag@S>9#bjZ~q( zF<-?xwP8A;xm_Y$Uz5-%oAl>u6%bYV@2uDhal?Hy;{fp-3cBLfK33Mqk1tk_4RE@i z(kZ%T_^{AWBo8!F5^jo{ETv!I_)KoP=9uS38bDudQ$A8d)l{&I&h~`YSAyCsAL1#+ zE(jHAsD9tKcsm2i?~idZFJ^Ee1^J-GW!UQ~b9pc=fm)SDdv6olPL!+A|30yKj?ae<@Bw?sZ~x}=Y>*W0}(WL1JTuZm9t?|PYt6l#`& zDvbN(CHsupYfh((OBGBXytk#OJ=giD$r?DJTv?=Dl=|ddLX@i+)5|Ucm36ZOr>Ork zObNibN%3|fISE_Jtr_C~F!E%hxD^qt-?JIKOZimB-Usobb74msoQIz;T4R`Xc{jXk zd5Exz2i#$aJk;rKT7b0%2@QuseB%Y1J0dN`?i*^|ZjB#hLgale71BDS)3%W4&dcJ1 z(zHxZb&}%p+V@$qQ%`VfSQkykQMD3)`Rh_zBMVC%w)ar#umQG7FYUMurxy9B@aVOh zE|2LQ*%)C7R^5$vJLjiac}lpwqQH4bF+=4V;4*igZSL)QAP$9VKhEV6Jyb5^=MA)6 zVD_i^+mxz0Yk}$pU0}8e#o_s2tVQ`%Y5Fc1jP!e?Zj@(xSwp4Tut6{B*sOkLD^nb7 z^z6Fr-|Mu)jT%Q!;|+9XSt~X-`&aIM_0@|(Cy+K?AUz)I&Faa@xV~+Jz-bVnB`neH zw^#s159G~)+diY|>fYMZ{cE>oVW=?D&+A}4rHiYrpZ#+N5-?K~oNu#z z#f4z=uCy{84EPji-ZE&nFu&M_s8!^}k|b+WThp@TKD@O~WK3GQOgmJv!o;z|`+*I% zSTGrOBYrVbR76w#3SQWq+GHWHVaKZ0BULHlkdUb{2H(2SPKQ}8rRqZjw)n__B5Vq+ zc@AYu`)c(Or@RpG!ha72Lo%ES1k%3wNHjK&wj{0fG)Go1I#~s3DL`AW07bDG7%;lG z?unvlNh-y`X=WeZ9@iWk@M>%*)ZbWg;EDk@IxgGJv8rBUW;t2{k~Z72!3gD$`>nM= zdm&h!{}B^Ww4Lx)TW0XmC>*Cz55Qcn;sqHrkho4*Trz9Mxzr@( zv$DyA7y*jQUap(ZH1E$+A2icI$$2$JSbR^t@)iZ|HgH&PiWggWper-0M^>9|yCqgqYrOZ8RLW(kd31Nq}p=(@NE1EP@n+i|-H#|2;XPA*fyEaou* zt_S4HCg*)+)Lbgja3`-6&h1;#^tq(DT%PqK`OXsuRICV-a9>XB~IQwNe z6^hGMxznLJOnl?ycD0phpPh^k^NvA^m+!cAvu>Y&H&;5iU1O6Z2>Ie5?o5rM5%QU} z{2<%%8PMIr%`Q0_`=b6P5-y6Ibp@Eu5;HUJ)G5HeS<=M%PAL}a?uf7}k<@wV2Ojf> zytCdL!}XSsJrN~0j<#gXvX4G%j!`&?=#+bH;t!n=htsSN4a)FC228A9{0qvPd5J&N znWLQk9j<{206iWCw}$zHF>68WAJDvVEmG4ge|(HiE=bECWpE#Th^?EX3&|{2myLT> z^TzJL(4)9c=INs;JWP;~B*fqcjS}7Ohb#1T*p-Upx^B$&U8~9|_-tM`ci$ZbnXD8{ z#6X6W-}mF_`xGtfwiTPioQp_KuX-dtes;w%x;r11aMPDM7}F={%2}o8S~8#!Wt_aS znK|=g)nJZC>NA@K3Fd@}iONo1zROo^R{;LPAFZqX{Jf#q-)hnNP1(YM` zPQZyz`>%I&e?$t!V)6^f-h%8B!VM-*!AdUrX~9W@*e${KWx@B|Vr7Fpk2GC4oEr(x zM5DZA0AIZv_Og)0a-i*dDw3>UHeN#)a9v}Sqb9bb=XDHJFEw`ImKWW_-npJQ=Czch z_(rd2wvSsi0c?#~ndUz^;w)a>dg|@Y1z)%6#xY&=?ng~ko?uSWibmpC-oyR#(oSgK zUt-@zx_B40uSi|bh9Cj>*dGTQD6 zD4{3hGp>Fo{>g0kZA5^e9g4hV1g{kO-60Po>{gS2j2WVzd)~jMQrz=)+@|yVX@wwG zb92`%qPU~Sk?Xm|{_4_OE_*uT;y#Ed9T$ko=4&YRzsT#f_i%posB zSoc1^Qw)+*jP&}T&54_*INqiuVq}OywS^OX6TgLk%NhphzhM7}b9YnC^+ModukY%xy}z>mt(^}juz%yiyiX$=)C--K%IU{riH6XhrxYB z+x}=08U*xKMA^o{Er8Lm`ryXg{E3>H^z@A06NysDB^%xsQ&=4j<>uU-*Fu4dT9Iz_ zyZOzZaQ_Aw*?%`}X+0nPkXdJuAe|BvLS|5F2{;y&s#3_$$)pdf*$dn<84OHDD4PR{ zFX^qU=Yx&Pd9RXI2aKsVsx_<9Dc^doxE}0hL3DmYZo+rq82E7^B^`2H zq7wI*4S2RB&6?i*Y5kqnVU$J@1O50E$g6KfUHNb|J7i?ehrA1o+wSryo8i;=t92{_ z66qhG>>tDSe2VSc^ld$#cs8E8HvE{OS?u>6Bz{sR`?>0t`seFj zS=MHt(q3sQ>}j_*mH!C>n#AANC*8IYT{(|;T$d;j1gqm1{9aXm_r7%KMXOXIa66$w z!*SYA=D8eG*yZ6Lz&9p?DS=1TMm1sD#&}RE+HuM$v`th(z9ix$6%>X zl1cEZ#vC_TzJ`3_TE^6)01fu~BF+(ws`ClOijyt_)m$_^u#xgs3BdMR&ifc&N+Qwu zY@Gruq=mM2;-!VHg1$~Xh&D*$sb*GT<^2bD5Kc;agKOEPzAe3bt{0P@^vDAxpcx9k z@v7-ndf^&Y$_3d(&-sHPHRd<)XWccCo}^3mC!WV7ue{v*dTAfAyR>ED+^MeyRAATU zc=Pr@?zN9)F~ScLJ>t9dZw)AnTh1TO8&hq_81%5lRW<2|l03ZSEaiQCD^|Z2#C!L> zpedp$5wE(354L)_M-Ls=r`^JtLw<{VB&PH(_>85OBf3|;B%g>8`8sjwv*qINRd5)* zWrr~}JR^@0yScy{`W>H@z!I~|c#S-PZ~r(M0AitT!)a=IgTb;Z**lnn5{8OKes-Fj zS9EN4h0jZ5<$2Ez?WQBO-3aY+-xcn?zK-Lz#BDs38A@P|G$?$tDnk;H;M3v?_&S}u z2pv=CP_shD{{EJu*i2%c0?=tCGef&Nw)SP zRn3fL?$k$2=An|K@Fnx4r>wq1OHg|DamOTwovOAPRK`WjVd6zu*{@*uez&$n7#)!v$F+2`v+ z@avK$L9O>Dx!xy~48P3ku+iyzW`t^50|X`QL7f5b-48aLlD(O7ZVj%!OAZYp#~5Rv z`5gU#N8?mJ?FCdpLxXXCV);0AjnU{6{v^GM&8YwUXc2bZ@)4?4hE1sNMA;}w#w2E& zw~|Lw+0gLE2b1VgE{&9XVxFg$I_Ooea4E(4yN2|xDmU-j&X9PSd%9+$7%Pf(O@1*O zqIIeTbX-bywT2}-YGI`+35jy^>|FM|_D!E+%H7iikBdvDTdK7T$@w*h3i$H7ux1AS zc?m73b#1BOdc@cHd`xVtcsH3;OAT zd&id<&0xZqURpp{FkI!+kjeVRPb!!#G=C-2Anvd}b=vb@nBJDCD-2s9S9qg@cOGf5 z8fyaOJ=69ZZK9rWuMzQs_VLr$C5(gQvdnn8CAqd<@>AL=dhOi(mj_<^E1=;T{z zf_L0D-X61`|TiTf1rG^HwXk+)w|cyBx?28pH+1%u z#%P#NUbJJtoEiE%Xc;qYQ1IrrS6ZcXPmd=lq%LpPZAL62I)?$@WOQSQa0zjSs(cRb^F#{)i2Ea7rvx zp8R}?mSm-$55GjAj{UG({UHy#IKRicv=7;Y`p>a815#JG;R6LsJ)OM2bWuOp&{p_l zEY*~ZLE$Q-%AAt2tDg04op6%wO!r>Q?C!In7H#)z$qjiOkkCspiJIi z)vD0b@(#~#!t{3yWJ8g>LGYBdziy@{_H58;(KKa)M+1^tW2PX;X7x4a%+D1q`3vN~ zhq@R_!-JBhX~)qAo__iXKmA7_$xA%PBSpMd^bL+o^I_k2f8MIQCoJP1qMd!9GL~cW z6Zyd*N`>AWww1|3qx$BG|D!sahAb_;T=2PqM{srhq)~iHtkL>M+$swSs;6D93%@O* zt*GWW{XffkY-~sQx00sEz>TpYLd=3&=*RVj?Yeihm%st`VKRG0 z{emRMy2D~)%!j$P{v&10i~^bWL5!_thGQ**U^~IYcB`-KYE4=a576cHL`&Mh3Y(d8 zt9;X3PrCc`+V*%xlDd4TW|LN0h>m9G?UB^gt97S~l_hUhIcUG7N|H?6xs3Gw4ll_o z41`W&WoHKWSazFXG?Q|hV9{cuc0&di(%9bYd|(7A7Rz^{Bf)`R3}SMPo*HBI|49>V zLObwoA`8;c=bR-*t*bF^Vs9Irhq(62CmKKPhrlXlXC#>OCu#F>*Bt$Uli8Za08`>mKYbz#f@mMKvL9mlE!Ln$v;Mu^guHxwdncpqld~E&`WYswd_ztH-=^u6V^c z#s~7KYn3C`V*t7g4mj(q;2f9odq~NS&J64gobhdL^yZPI5D|lYaAxjcm2E2s6f64$ zDN%TXuKC4zo`C@V#ALad?yljKReEiqjJ{Jnw{GDhKhoH2-QeW%Sfc2Vn`PZ*|9>oj z;s4BhD-!o-QHT@vZflo`)DE*~o&<++Bt}<_Y!@_HZ9hWBF9!!@#V#^0{>1&3mVNHy z4>svN?29{ss`=2(5y0%F8Hj-ZPiazm?>Af*vuQbVV;VxF!M;)h_?1^)DyM0BC>qX)ROE3`ypi;Op!V(Cj4 zSY;i|R3=lEISxp`0eSlT6mC$k$>T&RrX?@USaTcA_POOoBhk-T&#_0}>7eSi% zrMm)h%yDd4=yUT{MfK_}NN(#5ZsdnJp~>=%%Eu{=H4H~R7EK$=yGeD^8g7|SX$jI^ zrcBC=cv9N;c;ZPIVLYfCM@!MLR*wz>OxaeUk5kzujNof7 z;7V&+Wk;iR2m9JbFlaj1UIUTm#bZ+pTkdt_e?4AZpuiIDzK{&6UBn(QFjemk$INJL z>Pp$r*Hh^Fj5T~-=?IYRyzRRjl-FtC`NYUy-qG$O(n%un-=a&22#S>tn8)&Nxp>pN z<>{n*&Gb;I#6QO0uF%Y{L4iQ=Bl(-93cAQNhs5or+R$fg-)(;=Zx%!AHB9pvgcIl- z0Lx({c%z)%JJ%C zRSK=33|sZE70KjFKW@3YKWb%q)_3JqZPmFH{nBk^pO#%EH;yYZ7JnH~EKZ`n@l(%d z;rTJI-~&8#yO}98XZ{)&cKoP++C_a!uoHn@ZJ-_+Hq?EoDsJ>xTb*4XKTg-TDEv4# zMB7T~es?q(9XP>08kx$+THUDFcJu}rRH1~r?~}NE-?SWFaiHTfk#FQS9kpff*o-3z zX%Ka)Y1U1~`jj1f+1-}oipPEWtwl#*h+Ttv4#mUwW~}+kMdngrM%eiV(CddG*nZUX ziKv+S=I-0xQ)pjQ9|3;Z1^P~I!js^?%km}GC=En2&xeXhGetT}H)ZCTq(=&RiW;7C z#=Rdi4~}{c{2^0puKYIx+JtP;8PAhr&G1>ht+b@Xl~Ai|@{Tr!1eJgl%_-(pMTR{t z&bqLE&n;ez>g2{d28hm(r$-}|@VQ$I4GjNlD6Al1XD-Q-W#>r^Dtp*`slpbr_tyL7 z^iextQ~U|Ht3WxSRxf}`D_VK+@yFJU%1rMNosg|ch-8^Ezs&49_C7MF!E8TttIlw1 z-?On&H@h7z_`9uEqjRn*TqKJ=LU^K0A|H$)!U$0-mV8|?snSdcX>W14@@3m#9l{s5*^I?*eJ962M z`Fs$-SWI>|l-W>ztI(e2*qmlLD&Fk!9A7F^GE$Uw|A2x9MCl&x_W!YU)&Ws&+uv72 zML{V6k#1>`Zbd>`y1QFq7={!9>Fyk*yE_J@8|fapbLbgh=ACYQ;rs={ z!BXv>*LS^*E`Hk{dhd{;;|y2P;w5G2i?f>E0l+5qDZA#)a_gr}b9i?cXr4$n{;px5 z!+z}5PPwa35f-(PxA)I5NrACZrG)xgq1e4wv#*Pv zS5a!5w+5V>?p0X8XS!AvZ1=Do^@$CdWvU+O$-Ah&_U6}} zld3l}WT=Gla)=i^0eex9bu9bH8%c}m8LchyD$+uLMWyW|%sGr0OceMXVGaa>k&>Ur zYIburcphO|4#`LouXRbBZXMC-xL8N=o*p*HV>v-u#AN-7AxK0!jfi1Lczo}rl*6+cag%+n>q#fm=dKp#X-KD4XX+2p*!Rj^h!Gslh$t6iq8trDulK$ zF#G0&LVHtUtjQs}=XjLm&M#+RS}(egx8fyjYU*3BFJgNxDfM=lgXcrnPi&ip7WKOH z*T4M~(Epf02kB77tNZq9a9aJ(L%o-eF|&(Em40|7!c(vGX8>ngUYE@PB5e8aDM_gs zS>i-hN*k*IA_uPrW%e?$3Dg)EMGn}KRIN>vsPG6ZL%*%n3^_p8!L@I`Z5O#-@q6O2M48JLAgmpX1lB@dWr9gb__4HbOq85x)c z+aw7ev(0JygDcagP5jg^;JjuFu74LBA>E_Tgzb zAzyr@@6LQ<^*b2%24^T~lW}5a+89VuXbOUS5!%H1DemgxWiim3OrN+0h5u%`08f zoC!k0-A--L@q-JVmHk&_uc#stm!vMQpej^&)av&8LIrte?jNK_!qAB%NOl@rSgU)SxpB3n7f<$Z-{J$~>l;!0s>j}{)2gu(elG;+D&3Z;P)Um- z*BQ-I$qh1)+v`#OkX!>WTU|^wh2sRnfNir?V?0m|-vEnv^_3#yb7Wv&Ro{gUXHt3q z7HyJ#2uM#C%CY%Oys|138|H9d3oOt3{ugH!pdJ|Gd&0rC=50c#5MiuFiT#H7bD#Ud zjig%m~p3v^?)4cMu=hk9w_Zs<$%So z#ZMHK7M0s(NmjZRVVtGlz==QA$w;rcoma=VXnNA}2u`TVN~Z>>Z*%40Z}EOrq*mm! zMa`GNe1@mq38T^SYxY7{WLimCW~uw@H~)?cOQW$qE4HnEOJA=8u=g+PK)m4uWmt`S zp~~Kiz-ZwkKf}s7%XGyhbfsQ)VAU`B4i6muC3HQ>4vOPi;OzE@7dqR_c1)!%zp^G} zD>{W>hv;(=fyKKo4?4G_;X{*ThF9bL7U>5kP>m7}pG@8F21`L(RMb;a9l1wac3mrs z3_RR87sEr}$*LTK4sEzyy@u{#y)@ZP<|U{M>)HZ^MCVGzPF5*`W8YDh_BTRKWWIb< zg<@>*TsHW6fMRi<>wFiX8FlKd@|k*&y2Xi`l*CII&kk-9UFp3Qz7qv5AIZ3vX4Ti` zle?~30cdau;}WYExBNXyvmKIDusHl`fnTNug1HTTF# zg1rNMG$o?}mmR$>czaUK#JYcimdU1S@LFW!KyF-vECq|rym@5>FH*>Hx*~IX%RO~F z!4Knuyg?zG>fan>Gt#TK9je8(Jh|Jc{(U8l<_{e(S4tK^Lm}}ZV&e|<-RY9qQr!|% zLVWFrDtiWO0ZRU-sf!0Lt_mtT9k*$(!c}3GRJ^u}hrJTbO+kIKa*Fz|y4jQuxb9}T z=CXry=tn;?+ayJ^_kxhpQvh9{{%aKZrmY}rAL?9-C}I7i^OXS#40g$dtzbNB zJ{`7a@Lp1@uCxC`fIR4^S;#o^*V#9}kEd$&4(TZo2h>#?zm^`-Neue3AlEGD%6}j? zz`_5H68~hh(}Tu%BNwb*)%$9xr`mOuw&_`)#m}#scII^((+=b$Wz5`kic0A8_J{+g zb2|QLgH0KQpBfKX0eY`y!b~>8i1>WJ#n!2~L4(hJxj`c%Kj8_M`AHXAj${Yaa)Ety zjXq})Qjh+5e)AS%7#|%Z!zuE8u1E0L6DLE|&zm%C$eJTf>j{T0B9?@P=wv(U2gC42xy$um*pR2R zl_{m0V-foJ!Pc{bm#%%^zc4s#p>fsefol`-9i$+oyU**IYC>A%FchKt>T$(rq5gj>0P9LHPGW{{5H8MrtnbNBI${x#DDT zyH!^(9nhQ#yT@>bjB;i$15C*wLGo^z?BoE^Vsw20*YBidEGP18J z>dqf+7Jk_JCqD8oVg-z^t&q|8QSdHSZ^&z>?0M1-?AGmw_O0pTB)Y8&V3a~4$Tk#f+d;h|WimE~^zxykwf z{+aaPm_fIIr^5aj?{Dd*i^CYeg6LnlyUHJQl{*3|^y{H93xPSo^8uZbZe9r@E520E19Uqpm#h={j=gAK<|M@yGkb{#~JOPa7P&Nt-Uh;$|+) zADs(vu%#Xxk<1Pjo%c-v9+qo^c*{bJZiWyxRjs%ElD4)_fk{yKMBh8lhkyN+QW>nD zK`W9zB4X2YigAT+TKbOOBIoz2-ws>-9t=X~DG4JYT9MgPAK2)s14kn$|RZHEgG~n`dA3UyzY+v}xaVS(nl!c84{9>m<^3a&_!qB?_e*DH~3eoi2x#R-ih07{03qJl9Ymb2_3w#6zsCaQ&S8d&y+sTC_5p=Z%&O zFb5Vpz~KBBtPl-w|EyczO4s6)>oj*W^LJ`+aZIr{t+6Tl<%n~Qok@chqX?8MAj3I< zQ9=Sz0)>4@K4O5#Zc>L?C&4g3o?s~3$WZz2nA`PpA2vg%eJIu~KPj0k1n z%x(@Qf3p~@jZn&{iU<$n2>!3}5Wwi9`?A1MQnBc{pZ4(+Ny!f)b6c&Xr}?$&rV53o zwM8Mvl8PQJvS&-Iazi=&BA0QKT&k1|>&DKAq=x%W&C4!>w$E00Pusva^2tip*c`hR zWf^}4jQ?I({~}qrhyv)QVMRqnJIan61M9Tg5`FlJLy0C$b{a{iu#Ya#abI!LR!+5KrDf{iQ-Gww;4sxc0*!ih|wbyy#3h zj+p;`4jTq0fpP%&y`C@uzX-ph!oX|{qUz>xjVJ|?B8oIWvf0V zq-^{0HNACiFOlV1ok{N0Wt{tCQS<-5QR48aB4tLd_E>mTgN07{LT1u_s5#kYUSN5)oR@Z-dLt;XV zJMzl(gZiA&`SlT1mKXYoQl_#T=_(t!@dAW`j)&h9hz+Cv`|f}D;U@zBa)!FR>98Sp zv_{^L13h~PSaeTy7Vww%Hj>S=M~c%ePRsuBP-PT;U#6Pp)nQ*fgH4l@@cb1g?37NJ*y+dUav@cNqe?e>RpIoTc7*z1Dy#rnqXy%&>eFooMT|*4slZ=mvAX zyM@XtBnRa`pW>3bhXDnQDwWTZUyTEvh>L2d2mA{Ij01S;mIzAIK7VpNFrYvedx4&v zzpno0X$flllf4Gh_0Mx0b&9c-^Iy%I%?(_9W#$)CTUB#=QrAXYKku_EWC@qe~*5VKlK~ z-}G3x-X9!49XCQo@GMIvTytZ2gjJF^nW8mtfArlSKM}YX20imfW?2<>hSGaqiCt3% z8T^1^V;YZC)>+sQyOHcUfir)>>i_N0*f23*7k!$~2b14{?rQX3>u7lvadVw)Xo*dj zQ!Z&+z8oLiUH^-R_&;AtNArcI37&0L{Kl+m{?JWJjI>CURsUb)83Vy*iEu%8AD35` zFa;9ApMPG4{OLY_&e9mS@)Df58LOyFe{}72YwCK#jBjT%_T9y(NG0j~^(Tit>*c@N z|9`>HJe4KVwcEYDpv&u3$*^Q0;#1AguhOE#)7BURjS< zdBv2|Ei%3swyzYP2z+wVx0X<1Yb&InFmT^c(*px7iv?O))^uQFIitaR9@NkDyOJc$75v=%rI^QgOLDwuRq9s$^j#grFs*Ljrx!{uYllnvA3(-H9 z$sfGBF%EuTs2zk;MOgPP*Z8&Ekd*?X@ft5#a4ku~V#)s~6)^od?vEC-J*Uz5=%pJk z)w_x1sA4*zC9Aly6F|pkn2t2rOtKb&Pyg3y|Jz`_wUD^Hsjy(Px0qvq(~Oy=)#aoAI&w-$RJGuwfj%+ z|6x+)=#hU3Bi=&0@E|(@98C?n}%gi~C8Q{uUnoeaRcL{m&(hkMTAh zShmsBt^YRdkhHjj(=UmBf?+={_q4HpNrxrN(V6!>72Tr2i+^Kff8mEGilB5tcbg<$hMDBAu&rJWC_kWP~KP?g=hT@<=HLtY; z14lXAX2fWBh~T)Qthu*??HIS9vsG1ReclV!L+`&E^?$1bBkCBrh7uVl-jBi8y+j+U zYt=3jfQ`#&!NOV4Jblz##NY17uwhyGIl@a6?ZY`Ny6?Q0lf{@R{LRHGLkr&0a29hcSC zZ|zZh(V^SXg9ph;hKX`TYZw1qszG>#0uICdW_6Y(<+G)2S~`XO=+^SND3aRSNOH#l ztw^s|=`9rUB!oP8|IyxeT$pLn=IfP~hDQ)Vozi}76}0Qqe7+vFK?d;D7+~LQW40hV zo&TqQR>2EN;4ArwGis?no$O_5D1MQRq)C`v1%<^u>>- zfp#3vQe&4TQ<6edNi?WB7WmLHn0tgXBefx5z3T66+$X<(UEEZ*fi4I-$Hm*7%ux5R z?07eGa@VHJlEYz$gh?y^(ouFAl|}!*a*X@;cj}$z-1jn*P)9ot2f6Nk#t>^e>2T(6 zt(fi~$ZXE@k_Y>KCjIBqLGjUje4*D+84V=oyA$eYM|e1EH+0 z4G%WMGX-4`2geRmXrs~U5&_8XFE?(hF!HoqGv51lBE>X`go?TCQg9p;= z6mORA&G8t;)K2Jf$MRCd6GWQkE7Ey>5FWBJ#IJq!?B2{@yhG0me~$j4w)kH4*@uLz z*)jKjn|WxKSei#oYC@_1r4GuHl;eJ?X=L+BX;C|uA#AE9LRB{cBwEA`%HbbwWMrD~ zJ(#w=Er4v_+hFqmi)TT-8}`E<_P9YB-=ai@MqXLq}S)e*w+?gCU9IU0^7V*eT z6T8`jblGFwVxEtRCL*gJV1k&)>(g>L*l+HC3YL3~#q{ntjlTxm$XhwMz=YN-H_ zeXe)BVv<#SJb*d2;qWis3$1+qLPI=KlhWTrnjZNb7^^TuF5P1O^)9Rk$UWp$fp$ga z6t4Xi9YFI1tNQmLVHXNT#8iUYu;)jt;g_*W2ePQY27%Nx_qP3Se)?3LN%FR^b6tN&hX_&vcOXk(H)o<4m6G;o&Ib8MLfpnf1`7r z*j@Dr=aro|uj)MI16M_7J32S!AL00}n0p)nAx|TzgI0EL`P}Tx_1mA1Jv1uGliwU% z|J%O*8P0tHJbh~}n6P#k>5xM@&@Opb>4gDD)oU(X-R-xyd=nwgKs`kH#OQjA70+vD3EsCf>xjF{y0CM+<{~GNcdCwOG9Esh3lE+D!AitocW| z(U+aQ29&EOYXOh{YVi2fF;^tQ`)5R4X2W7Rko7KF1c1KK>|IYR+}s+(f^X zzD=okX_{~7vs^u+&(Y)w2MYd>QEM{a9m!6kNRsUNi`LZt8MZ(ADGR@SRt#&CAzrXO zeYl{Ftg@y>EYOFG7acG+vdB9ORs@!HMGP|7E_de$MAtVy@erSw?7R>VWm?=0eMk#m3r&Svv;SLG2m4*BLGXY-yU`+kI`aoZxtbRN~r z;K=76|22@tnY6|nRQu{7s*y6|X)FPCJ!ndj#=do-&&|qKU&|u3aK=k=@ilj~R#>-L z?CGS9LD2M-=!BEcOy}m``sf8FF%eT(MxE)(6r)MNRBGFcRIxWN=%oG2InX|-JXxG> zUkLzQ(%4{hvTs#oyP7IufKmK)pwNcR7zgi5uX{e>>bHiG47XgKGhR*x7eUkVpv{83 zpBr7~?A~juQ*0gjx^l8&j2A-(gPmZ5{E>s=KZ4WOk@xl!fxE!=c_~{9B~I!5DTSuy{6a5)7qtNCSvC%3c8_#I!zz6b#d_9ZgrH|BQmVg zW=rWNinJ&8i?eyS$-z30K3}brn&s*(aBCZyPInUGexQEC;lm%;mo@cgM_5z;l=J3p z`ban7>1wSB^Tn9iXf^r<>Pn{DYOFRt=uo(8duyW1*;c9GgiW@FUl}0E^nvw>)+>?7RPB6~Q`oc!(3R8P!Qq=Wl;hSTy=HDh4pK;|#(uVc-7GV__e zm3nwMt>fLnKbv38gyYK2^x?lW!fXsi=PTM){_PH%zw83BPWO~-p(S_K#>|TRA40=+ z`C7PXJqRGZa~6gUpdTC9%f6Y@RI62^=5Mk{pG;Y+=e;Ku{5%(m z{E_U#8+dGh-$8c7*hV#5?r-9j#TYP(N@FbK5BiK){~++W+hc8_JgAq02ZK*(1g?e^ zjT*(%+*@qZ*>(o7T=BH)H`Jav;;b^<1f4WL&dXKl1t>r8{ns^^S?Ie~lzCu3G}#`y zo!*Ju6QJ45Pu$Tuj9&;(LEPPvU32TqtiUx5oL z)~CaawtV_w%Xm(G+4L^Ydea(a$aGizA&|LdzV}n9)pe#Eg37 z{z+SUXmhA-j`f*t?@1ee>#a_V0!gWzYQ+0$rC;Q3CY_v2?U469@Ni5~hiB_c0w_eLeIJ9IozpDjxm39DM-;#OaNdWk@7|z4zt@aS3tGWRmK#dT>ByGkFbkiTd zor%i_f^R|U98O(_AQb#(GeZ15{IgQzR?G{f;p?+eBTFB6qN03~BA%t)Q`$|N!oexq z97q<+E_gtDYG)?}60RPuD8XFGA$?VHU4hzK;ukh|F zhbDV-hHneQ$Ge@0_FGwLANj>Ow4bV+*e;u9+lF!HskWj<9^dC!xt8*UX|b@oT>9S( z=9!?*-%{s(x{9e(Z)>UO8+=Ta_n_Mc9)Xrun%G9B-}jGP!huQ9QUP2)SlPk*7I5U0 zb`L5mPTOM@gdN#B=m)uIrc>RwFXX3qNnc7p3JDKOFwS?z49DnYSkyzt7x3&3NSgUaJ`=KBrsWp%e7k|}|v_mKP+ewj{8o2`*v#?A#$Eb)b zEdAR_MRk%>UX`^CGZ1AIPbEsHIX{xOj4nUxLYDQd1)ytR)6vpiD=U3Q&f`riK^dQZ z_-1y9(MwIqjjwHvK%u}b!Po4jBY!|OdQQMMRO{=7nW5vfPsHPa%E&%A@heU}y8|AS zV@cz1$ytk$LEpmU4|b=r4-6%Vq3iD+rg`Q>Cz|a4D=eLUjv*>i-~5JmRGFUHM>nY! z5^R$j&KUHxmeNKrMvkkT>Bqo9`g{D!^qZ;bPev}8a{U7}1#DZGAJg$W1-_iS7EnlB zf4jBkM7Uxt>}`DrX?qW>%xT9)($0{?W`=L3RfTHbjpzkejvn>Xl4951t~6IG0Bc#x z6DmT<0?1ng6ne3}9b73#44Pk*Iz4Q!HsLBW z=ka#bXU70KV`&X+t`oq!kJ*-hpPGDt*sFZa{kmddbC9&~T&F}VD9dhLP~UO+>BO>( zg$vc4_!FQTg#0#dwyruW^Hxt$j!#+gsHe26w|=f`>DyF(f@2lR7C^V*wnY%a{jW+wwvP18*p0_MiOT z^=!Jnq*>HzmeBsp>(Top?w#2NVRRD59``(H;82Q)>xExAIFVHS@gQ#2`O?us@aGwW z4e1|9cv;Wx@e9<6Pu$eyjL@UwmM6#c)O8&;elf+bW`MC-EjHv|lqWT`aa_bk8Ah94 znc+g>N0yf+1YSN$=SyWDnde;`L78Fs-RgN_H0&qQlrC}wwgpB`7LLjfY^~oI?wu`I zv)!I!7&LHbEz36=G+?cwYOWYh;>;~lf#%TSJ)&1>d5L8tNslB|@Fg*&-eZz<`KU`f z8#%+fHgBZ2AISJ|1Vjnta61aT3HTcF6z6r}!N8gX-Pf=!AHD(O?U9+}DooSUe zhV=kWTTL1|Qz$h6H?2~J04&Q-Qd(qtm|sah+ktbYenD|-x00A?fkZeu@6ycBunDrX zVPp}024+H-!woZZqP~E@bfbI4{OzvNvV9Nte3f+W z>K9n9qqSw>wU@l?h0$v-s9#%GuY7GcF9Ka^Ecl!$*2gF`jlZZ9_OW~FX3<6O?EKDl zFd9;BW5{VT|FN|zzL?+&3N<~Nfk=K`e}5;q>%gAmxBKkrGtY(r*#v%qC&T!6Aiw0- zZW0Ta*zCTavd{b5OG%$(pB?8JSfh-t-m-g9%^xL!l{!Qgau(m~p3iPZ&D@C3^B5sJ z^5OLr_G!xyfjbFP6Bx)Ze`zNlHGS-m)_yC~t&3_(>SmdD=I^!=*L0ZVn<*L6y8V$Q zeEnrw7OTti$^K2q?y|4Jgoxx?&xIh{-rX8<6D12M8u605^$wqhC;5%@3GqdCwME0O z{X<7d^r75SX-&-BbJI-)>+7xy3%Z|CSEDnX9<~d(vLWs7O%KGX?>2=T{is_YTPliY ziqYVf{if8ptTIZ!Stspvkq|Q~gddN_TX6BEe#QEok8H}a%Xb@OjW%lS352XE zvOhZ#?62gG__4k0Q<6n?O_m4)SVbcfsFrytMoIRreU9T7>h0)ijz*3b08z*<(%|tb zBuvYOEOW*gi5Mvy8KM#^vq5H#oH;NZw<~tr57=kO^x0lMT~uEW6(~^k zmbiy8g|+hvk7lm;jltt_tEWI%x;Vc+FEO;vv8XQ26Q`Nm{5>V`(}IIuCvq}G*QrvuHA3Shf=v!>>5#H4hG(6Mx{;N6&b(0x&FT8pT!jy`m~4u(9gTq^bVIwe8Xx-J+CmQ<Tl~PUnMa0}Gv8phh954Ie9g!yL<% zCq|%NVtKE zlhgCy^peZBsnIX4+c|yme2df+T{0T+wLNKCICnie9W}S+ktaE=-1dP+nLraarV+Vs zH>orJ$m!<24egTv<;CpBfpy3wuyxw@{!56qLw|c~7sTz>DaqrOg(AELpnyW$O!b`Q z=5(DaGsO66_!(UX&m#p-2aMuc)5VB;wEW(Ey*1dQ7tOYT zX!I72ae4ikm?Tk27s5S-Mf?`xUfP5CRRk8dEh0It^%phCY9sk%%|^u)+Krd!^-zZH zEp$IzCzlh0^>Zt-N6M^-rR^&_r)k0OXXr|^=SyPQ`(H+az@Un9!U{Rct6u%qNY{5QS5?jf z%T=|tZKEywEz>)^gNgpP@53DZ9ZVkqPJ_7HU5ibHiQ6Zvi>Cfz9r`_6tf9ao zRv84>uPFv z+>dyx#jWN7ivjG{B=kJU`rIU%u%*=4()V)>?9u#kiTS#+-#7b{XU(rspF+nuKNYMV zi?dW@4~WQ0^g7h^o&;6Ttm25@z+bg(kP>7Wj^&SP2x3oAjYZyVBa3a2+cles6Adc? z1eopAW4>8?KC40}DE(ir-ub)o&LhMz=^tNe%m@N}`>J28R4#g|`wyidN2g z4rBX_zSRi91xxzmXFW(QwC&h;N+AvqpU#joR@R6 zp3~*1$!E*n>(&@HDyF!mQ7?9NY76#O+t-Wf zhnDcYqDNY!AmYE&ybW(nxO}ojI*kgc*uz{W>wOX-y%kA)x4|XxZ$?X&7=1R=_`Un` z&MEM2J`NZK7Bz^*iXt6v$8VHxmv(09!}+{D#p_W^T5<{PiY4N*I1GFgl!+5oM%l+R z0d83k5x@(yQTO@PxllK6Sm1qjwfEWRHI|lGfeptLF9%a_1G#3Zk$13A|K4jMOe@yy zFAX!6u=VHy$TysFDUR>8{mhw~^Rj~Pi6ep@>)px=yc6K)@-GwBC`fNzoFivq+@c-i z_l7g&nN)8b(9o@}kE9hu(k7?5Xi?1df!lXhDh%Ko#W4?gPc=S#FsyiXp1dQ0q6IDa za<=VkN1|xA(?sMTvY>SD8qYGv`pCe|3VHI`i6a&LaV~YpxR#$~@l~YOzz3%LBI)WH zoNX;;3{R|Qi=m5k0&JCClgLc6!c7?xfbBs+POqiC*TKP7#v zy}SG_3?hq`I1_G8T{?rDx`;D}p0;Kdh^@A7x^AkpgB_t#Etd_KV)|bvvKRVqs;nj_ zoNnz`@0>yW;24D=erQ1OO*II5BWS3*HA~T8SQeoaVvisV6%DsQ-GUE6aUP=YMcAME zp1@=ilJ~{;O@sCp)%J12iX@Myn-uq~LF889r*XR+WeC2^46g z%&FK3T7|QEi2Km|x@+fd%m_&VVHLdovZadj=BWAXzDEApzLd~o_JKPJ*M&9ZGh7rs zpjk}D!c76Cp80D!qA@CCle4nkXIE{?H}n)xcJ5w3wY{1wDo14sNPG!@sKzActW-1- zZEGZcxt9<3*V^-3Zx>hc6EB1xgMxe=JR|oAgT0Yig;1$!zZ?(myPWhb{^dLvqI*Z_ z@VKc$OYyW4Rls@V_i$d8jJPV@f%)TgSs%g9+H1q=`SBtNCDULw>$CQGOD>%@4mi2F?uRH0lfhw#X_5H$lo5;zEy5 zCo0`MPN&;|8~WE~Zc(jtq~|X~38PI8H-3ldS0r;LA0FjF-TLI2eHPuJEce-4YJQF^ zfNTxRkM}8yT?o!+2FP~Ik54uUs!`t^{8l;R9xt^&+EC$Kd=%`~6=RcjRjp>@RL`!g z777Pj*7sFm({Qy9^hGxM)g42D%0c$vg~JUM{i8M+ZbXamh-Wb4k~~hwdO=?YlI(G){NDy7>Wzb>B^6Zy$|HjkFCdCT_q3#B7$t=DdFC*y=7! zw?ZwVlTVv&j6++4oi46RPNf!%^-RG8U!$k5rnlpNVJgpMMNb7Bv!YKm&dr9Vo(?xd z^%(B&*|^1B7$8?=c0G2zj%o=nyR?^zy-MusPrtGQzaTGlHHy!U$4C}j7+~i>MhO3* z|5-q6q)@2S1i++1$WTzKS8Dr83VFmTnDM&aA^HZs^`pJ*hG&>51Dlf527Kd&yv-8Q zZ3hKGyH5;7JAWhZ^7GqoC=CcV7C%4!&U@@p=irq&`grEh=*W=z{!t2?1ftQd+Nz10 z?I+oeA^PF@(@a+6k<;dUwZntidl+~r^2VQ^qs-dsI<37)75C$A&na=}xlEh-sIsru zt6!^{O9x_XqlHwrUeJ9v+ywqf{$LIpC@vDzMdW3Vyx=RFvG+plhh>Vx)a%-!w6c>!6iXo!)U>}k0XM~etcxLQ+tLd~od@wT3*`;2d} zbXJV^98XcEn6#r4v4@2Ym5 z=fa$xaC)G4oUUrqt{&gjgpXZbbi?Qp>u8Vk>i94|Xq|;9aPeNw4$gxXfJeo}gVOZb z^Hw}Qx4-P8Y!GmNs7zJj3W#?N2{0=)yBjiL4;#%K=I6o&JE0=5$Z+Wl8sbaNmVK9S ztjuQHpf)uITSBTu=b&JbSm;XY<&b--%Z;&m>GHOFgGCk%&G~!K$nuGQK0G}7HRR3= zp;o{s&EC|$Hg<8=OeN$d(}_`3suhmSa0Wv*xEvtYz?)J&<1nBrZF*FL7*$D=S4^y> zXX_!iD>pBA4m)vSccb!$ow~8`w&DC+=jsqxfd8mhfD2k{av~4#mD}*!9K0C(ZB*>)*>p|Mh*3lww?}1wpKXh>;miUFWRplkv zX=ht>>uW)SdQXmRpEp7GFq4>K+T$%0iC1Bw(cwArJ89p+BtD}hiMnvr9HH)%ecI;T zAm)mXhlN$0E#4H;yW-335&X-Nykrui{LJAgy+*~4v+2Y9Rsg4R%Ng}Bk1cR<`uYv} zFmi)z!z77X^zHUF{M(9n_??(K_Z*)&-a8d%uI6KAa6LHtk^ltV7aedeDj-9p4Gd8-Q_$57Q(g{V*6AQM59ez$o{wCd>Lb?7(gzc`%=51ZC+6OAfCrc4q5aBQ zK3Z(fM|a2tqOFtF7xfn!(X!jyz#Rd>gB2}kVJr|L1o%Egcid15a)&Mxfw!52kmUz@ zZnvE3WRzumtw1t|lwR}c=bYQFm%dN0JRlt!StvfhOQMmrF&z<(_F$6Rt%5?coRWr7 zyN`m?=w{f@u4QfpmVhx@S&~dTb6Jvcb zt4?jV?#pgbBew>1(jY#D7$@>$M97+_5o7eccy9Ixza`T$y-#Np*tgC*^#iqAPR-** zi`%4uU_s>{EI@(V5B*v1dP9@^AS!}18a7XBs%yG3GWb0p`Eue=8nJ&r?&%N*Om}Lzf2gw-oF^V zY$C4~RH>?$T$MG_GDr6tS}olXI}A4J!Ne+YemBP{v-2HK4L6~ta4t5x5hd#6(oM^V;VsHO)TJ87BA)Ln?*wHZ6Jq3?VB ztCs+it=Z_3BS@=NHs|S%^foroGP9&^w$j=&(P^id*|#__JUU8r#pv|S@W%uc)e=s| zTB}{O&VbFUaMa9g<~PRp20rZMR!5?OY z>z5qWZWeQAa2*$HyH0^XnnR+!zr+~k3ME=Ain2Y;^7cI^eVC^R5^ zpe*Tg$31LzR0Wk2iyQPFc|wB7fduyr_zXmu^}J+yf+`zXS+1v)Dyyqorp(K(ij&-| zR|P|q@9^x}k1P{Lzq+t$PD1ErRM5dnf=ATA6-IeRyy^11LWAq$gF{&bMplOG#GF1> z>Z6Y_sXvcD*%{o;<+K-n8-98&ZZ{_;PGoSY){rbG%l|DFmHq6{f&~ZY?r3iWNljJx%>RUR`c|0Bsk;_(H>wte2M-}(j57;H;;hHkNIu+{Xl{;*cpb(TJqR|ABQBm6b)wtSp zEis{Z8M_~LM`Cds9_jZAMb%t1H?fhN(7N566}+(3kRWzET4xAtMA5o~$TvzHZPGaJ zDqi^>S=gMvI_|_+!u=_f1=m1*$9WRO@w`s7`?&0pct^+k1EjR+^5S8Ula`%LiY-OU z7o%Fy<8BFuBYzw04C^+2a7)|#IbM*a$X(qljdi*Y`C`XeS0wX}U|)ml^YfU(Zs}w> zVYA1@R=-@2fzbS5nu>Ad+xbx~@%*KLeW!h&X)C!#-{RAY%@ zr_P&R6*RbWz<8EC<96}nhOHyKB#Kiz_E@>$%^le2Ns}Heo_CQ5iq@zz>tkejne7x@&?zFqIg5Inc)#G4HpVU3SWXY9=+q!T1gq?$K zYInR-fGbym$H{{>b7nIX56GzT2|MYw1`>fkZ=)SX#HYhk`N7H0;VC2bR3!Or1f9yV z&cooF63^eKT&J4yfs54n?I?T)x_sqirYqs6V&&qO+^*%Pp?_Nbv;7PbWj%A&%~ynPF;lTW39!oADAzk64aSK22xbi$(a zn=7*b;tOt$Egv6f=Q)fxQ;+KRxkRL?$`=XTBH?hIf%*sy@+>2ihA*np>3yx zL1D2Gp7J1;cCF>~*-HV7K@F99k&1##$3*Wi- z_rBMe_xpd&?7h}r@vLX9ea<;YYkBWmzsS+#zuO3c1QW~*NCG`-n;uEsAnhA zNj8RVJiqfxPWapIiOO%urzEB8g$s#1Yf=x@V<*mk`Y0H$7yk0UeE8Ha-y>r?zl>E*<44rl4C4KIMacBB zhwEjeA0AZAH?*mJdA6E>B{``05lXUyD;s3amKY5%YEef0OUI zVA#0psR%VLSvq8-Kij%0kic!XVBvQhs3wAMYuae$Ke!y5Rt-z1!33%!KC*52HvilP zoeYc{mWoNgz{7}3%eT#~4ehn3#nff_&;>fz!@LJ2eXa!kcG1ptyi6!67_@yn(Oh9% zA!~TC9Km9Bq1Cgun~w6}etB|6yzTDChOXk8mb@Ttm~f_T=3$d=`+0oav_2zd+CQH= z;H7ra&Wut15he)5mS^#N&<9y71=I+BvsHgz7g>1c2clh7i=4K@Wbb5z-2j>oFXax3x$jkAu#H zJhq;aG+z4bwyaoQE=jlkTt()bF-tcdeu{Zhc2e5r>jZH?I?uKj!n0?aGB_^Q1K~h1 z{=x%3t|y2z_K4!P2zoR|WGG<*TDOJ+-JZ$j^OXw6Y)n~&BQb--SJDrM;ZWaNqp6WQ z3&49${9TtOMnb6wlNP@--#cC%c^~E(uEH|txpY+N#&9M^T%$Qex93^S`d$Khg7JB% z=_7qpBW8PBJ9)2K(kZ$H>iTPh3aYa*F`MGKcEY;r#`GI7b43MyZUBEPGC;^cR=&6p%l(NiRCvuEc= z1z$SoAa-WXKq@Qi$!hrKqk-(T8IO;M=WnPv7#&T%-PCd}op#G}QZk~JykN7J6{@r3 z_t@G$^&5TN*6JgL=xJ(B+Wu;-Zm{Y-0dtTa_@HEr5a-N@LPgvj&E)0v@b&CYaO ze8)>K4#bDVcbhT4&yF@}YxVo54qA^Ie9)C)($#)r%nTzE>z~wLN7IWV%Isc$|Lg2X zIL2>gDPt%AW|?t`iN6EBTt>ILIBm~FnEK8N-v7&dl={uKVhlF@YNCIlnnw?yiX4Pi`KREkCJHDe#4z zw5A=q2sM;Tg4eQ4>SkBqhnk2bTBFCk=*HIxcfNO-Vs7;1Z?T?S#@l9s686t-Sl7@R$*z|6H7!f(~^fvrzd=}xCOIB(t^r<>s{~tgSKrh%jloybbOOJ{a%P`)7ZYl=Dz2ve3g;M&59n0C zVxp|xmNup)cueUdxPQ}9S?rKYEl0>SXP{>Cy`pJKUCc}P^w&fe<08v`r^;=c5qj1- zhXkRl*BPopv}1S9TVlAL4M439S8U2VH$BqaXZtc^JY+1`E)Sgj9bE#Df(G5)Gtilf zpD3ZyxrD%-l|cDXt5~OgzhSwG)CApPA|V&Htv)(H&fS+~NBvG;Mh16`M-IP%;%?Pd z?PhVN8#sv+sV@m_v+!(4jS^70P19x>QFNQKgS(ke0H2Z% z!~z)G%^-9Q=8xOx(?`Rd|SBb^rI+TiqD)loiGk1lm=cF92LrGgzX9l`G5 z6_rvJ=}%Hp%MH)&h+c3YuhYl880uM~?|(5tS0`0b=F3g*V#ek%Tarb>2HD;s#IwB} zjF4U0MWc#$h786(_~DNoTS5pO*KKZMTKy>0iv*n!YBT3*Y}Y41Hsrmlm?8-c*|y(p zAl-$$V^B_7RDllZ+bh8Qia_u^+x-zt*8Z)s-U4*BBSv7zcDhjK_sXf4n4Yn9iJEMU zdH2T0C(=Pa`TaY^_;{pW<=Z=E?o8gWJ664ndvN6{0nknLp~5Rjj5520;ZyG)954#i z)G^;7qd3*nwNcG7G3|DQ0_2g}^-N-`wrJ3V-%?E_c_s<3QlWhG$#u~|6iH@y{tvv&IT& z=AJ-}zOg6OMJ9ENn>!RHPz4^p{)J6cfaSC2ZEz}YIat+oqu z^u7NaCGan=O7Y7=%05*ePH%XZ8V+u!Rp=F*EWRoIO~r+R3UWz#mcT>y9DG zpjb5CjQ{9F<=+*WSyD11F5z8;FN1OTvG{WTEjRl#??3tJ`U|IC3RVD{`)60f;Thm48LlZ zAjS2*ZZPH&<~o~KMazx*FNZ8DEi|X~ESe|> z`QwmuZmr}u@R|R6Vj{)(Wx)s}o2|;-h=qbxR-?#xFrva>u5hMi**zgma9fv9wdAJ9 zt|XqATyCqlWvFE2vaPCMJazvP z_TWHLjtXwxig5DWcv*w;VV{vCTp?CeqJ`7ER;w3Uy(>fq6kE?XADjFWwJCcq_wHXo zi=JE!+XfvZG0R84@O-}hMlx%?<2=h2MuDBIh?T^nF!a?(51@;{?ovhKEgHC4Ex`ouT1IG`%y=$*E zt~~MRW-AwGre2kgDNLB~JN#TkA*Md-$M^nr#gO!qz8~!W)wCLG@D?Y0ikXWf>nrtw zV&?P+XRp7Y5Bs+CrByR`rmCJ`nf9C8H?~>d|}re>~+sVK<5CRTs@-Q`VqZ79=mLpV>rR#X42GutMnd8VX1o7csRm(t4JC-3!B#D;0kZxYR&%XfBsr8tg+GH(8RazHHBjcdQYZMdf2 zW&*J&LGc}BNIUs}3N-lZZyWZhD&%rf8Oi)#>i*9$eN}ix>dhtZA(Fj1Uf~8S{bV<8 z1}m)o~=Yw1c=GgjvA!_7~J+ z`WMGufWLI@j7ky-JBU4A0oA47!;$KC@D?kH69<_?o5%i_uskC11gi>LcflAxD7SFO zz{=1S#mXYxBE`fR0m6yC|Go<#rXxPuQR3J7PUyuH z^b6JB#KXT5zUm_Q;L$*_RN$u7Sn%G@@JUBAsxya}KgEf$*>=o_e507k+Nj@#AAqQT z5@`}6FNJ@Wz?R0cnqYK)ecp6Kc8@XTY}H!GQJIhNfZ;mIk!G=)Umi~uYr8uo_(ESN zgf^HRCM0U>!Rt>d*kDxs{7DtyOZ^`lG{RTmm)uuGskEB0`{2QjjmC z>bCB(Y8tlQevVP}2H!Urg;G0dS5iox8}N*manP;AuT&4dvDb7?^w@fg%vm`>wZwvm z@*n=u9gwM?eU5lMKgOKRa_ZX*Z6tXU&5AzD{_w&#hsk?x?m(4xBE_2c&jDS*D@v5% zu!n>a88KCuzcrBhp zcqlBY*2Jn;i8$r!Fu81MSc~k7>~u$+@}%2Isjk0n{&}o&1Ha5nb~ZFOP{fC{UYPDB4r1_qTa!7A0sjI|DZU>#+a8ToXJp@7WOl ziIW+*fJ7QZwYQEX3<=C6$Qr;31R9sdLLrn2 zANpzgc1%6hiA++M`RpioS}1`C%Ml*9x)bPk9qug~K4O*5id*ItU__Mz+v5=NKfQKov*;>U z0?rOWWU(a}-%pq^&z2H|agt75lCSR46HjI%>wWdVo|GDV_!n#dDo`R_qw7P=J3Sik z>?U&R@pxfkT3h}x;(o*GtKf3&Tdu;7!d7caUorw5)E|{sKEv-A%DjI(CJl1nM1-Wdxx(pu@x)d78S%d!*D7-Z%c?B;C@$%6R^b0=lI~qNQJ;3!?4(f{Icb z?~hbhf*G%hC!g7}f<3{OMqIQBphjpqZyk4CR9}s0whseZibJ@|C>C4Sm0I{p#oKI%LuN(CWf;@2MxNlw+JLiGg|?~u+x3fU{W zPA)P`YM=i#L5Ur?1aE#wbmFLKh0mc)zbnBuF2`iabydbcG+4qCP->yjjb{I8Thr*! zBrtg5ZV8TCRpWPr8A~wV%dh4%TBn9qJcPAW#F+*r3it@H(Jc)Z^0I_fE!U&DCgjNe z#GSQ9}B$-62A^AoeHNwgIlh@#Xcv*H|HsVnspo9gXBq z2flYxDM5D;U9dZ1EvY8`zC!;6>3@g9_Gr@HcRHg|i7)*7PZnGmlrYxYUx6a70M^qa z1nRtz%VGasAC@t+8! zo7~?1$?@^Dq`>6n)UT2l`TbNRMLozEqGp)DuXpaRfR~CwhI<3y?a0 z(u@wlgpq0QWJl9X#IGP{4fo>TdW6{^!;GOHIUvnC44n(+qMYAquVHbo3l z%$ah4X+uuq2d2zaQ^Od|>!gILco>Yg3Ar7_r)9ChnJs}Q2pSNlLA|cHtH$cA=U)F@ z`fPG?esO)tV@l39@0m&J;NYt-y4Z_wsys+^;4rhS-`#oI_r-hZn+Dj2MFD;n-N=Bx zH%u>gBYt_qCxa=Gwf0kGa)|mJA*@(F@fZ)RJzH}cjBZ%;{q&EcHjN$fD&1+k)*!jk zqK^zfo~=Qxu48KgfG0f7X9_U_Z~Gz*XnKsRILEcRN~~T|FK78#zmY|oMk=PwQ`z7= z!y)r|986sYcAVvX`)OZ3>V1PS!OxKG3J%!pb=pax&jMp7aa_{_roA1>5Jv1n^A49; z+g2OvHf2%m zGWKpP*H4bK3gY?h|Guelnz-v>V08Ii9IPvngb#Y-&hZMuXLy^d6t7m$NiwD(5~C3v zxIs&1G230o#gbT1^qua{V(sh{8e?G+OKObP7&7IH-bEn<_ob$4YQ>5DGHSbAM%&fYW(>9u0_qh%+>|Gb#;k?&Bn1a{RJh z;kb&&r=2A&uu z)Eb1P7)nGO+-0va)*RGxysd*>pM8E9tZH4VtCB$lkmgtG<@q*1l-u;NThn-=a43K27gUmD}*&J-2E|9z&;YE(}$2%q%l@c2>I|KR*&FOqlv>fb+&% zFt#Wf2FxNF^Wo%_<(XUjH^y&E*0R(w~F)?s>eHJcs|y4MYvNIqB=O&**4n|5c6TufB#K_ZA#>Q|4R*%^A{-YbVEM zVuP=^==dFoG~y{wt6#&4exzTO9<<0j(lHQAm+gqQ32|f`OR33A=hb6T{!j@^7aMA& z^Otg=_rrb!Fj5ptkgJCks|OG#skh4{fP>TF#K6kqeHn~tCTTiP*Ud-JAAQtap= z{_Md?`}9z-ti<}{M>5R|mc{zbG!4K|C#`p^+~#f_XuUnNeM=etzD+`8@9G~7R>tES zKBk3+gmOJm$~En7(0&ahOa|z4r-GH1kl*iftQ}4lG6G*0F%w7 zy8U;3IuLIfz#{Ee+JgSUKdEM0)LfnG$Mq7&CvF9^yoM-GZU~|Tu)7Wh;8x~60EDB}ES&Cy>2k0CBJ9@s~$FNz0ds!QC?mv*#H z-b$y9(RQ4 z!(RAL|6q-@!Lg4mx?oO=+OTInVKQ}4r&K0RW@2_7gvXk z!yo_IFhGul$k47ZdWdnAo&*z!q?lJ$D1QyY2TDG^VTOC!uvY({R;mu*=)4`9uVls<{ z6onm|2eEY;)z)DyQX5JY(ojY`V0H0xJE!61sbc%w{sHA$eEkF+wn2-(c(92y|1C<+ zvNC186UMn&V1g;pQjI@(Z}a-wGl~5JGEMHeh0hu~gkNuCKc`Gyxu&M$I}%o%IAZJ| z=M^}Eu{TQKt^;SCA_5%zRTevQIfD);th0#)bFlPTD)*qKq}HL-+W!nhM7zL!i?@QU z(4qCjBQSpXFjMZsU6*M-ZiWlko>-t|+I#G*Ud;Mq-A%m9x<3GJABH|H=}wK2O_lfc zn@>y4ExB^?D|N^ULhvX0TnVOPeD!$xlLvfNgZf73^74shTAUsyA9yuT?~$?=%0Be| zFFJu9gP>m?LOwSKCUN{v{P~0L^)(KNMMi}3r_Gc8;%r`|Y%!FFhO8xQCX%JUw&SuT z*3+NcsNt#h4eT|o2|l0>-)Dg{iEemkr;gX_ekcdC6_^B$PZ>@N*LP*zhx*4DeD=@A zB5eWUgXPEL>1E&ST0Vz^$#d4^(pzXXRG5Q=!MOhqBaUr8vyN9nH~>eDoS?|P)OK|f zAykLl(Sa2UMf-h7^^e4m&h_al|2n5ET^dltQaY&!J%QD_*w~z*{gY>_-TDg{!^oE z()~X*3OKGmZ>;;jqO%rNVin-M?ee`u)8N){tDB@KwkGwj;VIUIilhX=X`lyOMP7$# zJ;qe|K)Darr&yBI6$s)u`+`HaLS-LFliYjP{A0GuQ6#@8er3tLY{1(alIsW*oNh2l zFXONgp}4v=$S(_jeK-<(a4c>{z-db}fULrjZiiny(%a~&5h*zLk+`cN?b zGw8YdgE4EPI~)9l!(q}$ssa9LSRH2l^1cu=H zb)J@;F=oR!XRGY;)p_6^K=(#hriuLs_(_JO02e$qbd9luA~mVa8L$r=$iBsO2_Am= z$I<*tQ6~cvCP4K@Rt@hiB3V6n&o&j8tu=dn*${^&{!AVGML(?3t3GjNIANc0fYrwo zz%F3}&6qWk-^=C}^Q4at)V}c8NoL^GeK!bHI!eo~I4UC%oe~`8MZ|ZC3kx}PsorIQCcB)AaV&_b08FJJxuv1sUTjxI`87Vq=VO7+0?^N=0_Q`-y&Nhx~iw1w>Nm&(b zUg1W-Ka`nZgx^f{A{HHe@mK4gK4w-ImT#xQ#%7r`cni-5#dJ@{7n?D0b=Un?9+_qu zj1HJUxTo(t?pmzz_FugHdpG05Q1j6Ugi3DFl&oQg$)v#J(WTNJ zkr?w*DNx2}b4o-vnDmXxpWZSsT%rDesE2$2QAf*KCb9l9Vp{9hVFnMs-niusLGtp3 z3)S)GYnVvWFS(95yz`5j2TIWKhVs|OFdjpyVf;BUQ}(}qCbd5aa>+X7w*XyNOZ0Y=kr#ksE3}Yx>(ZC zz+e<`E@%UUE2f`+G}bxL%)Jml0_V@i@mYExOhcoh4^>NBO%>w!JPxZg5RGzfx0_@G zhg_caV&F}fyh#K&hT5qrK@)fQLO@4FCxdW+M044d6GB4gJ zZrO)l91LUR6s?GOVODs}5r>w{dJy}fl+tgX%_#jSZF17s0SWM}hU)<&{x3QG<{_1R zJ9@s>&Kh7FT;^_rl;HPNxW0I@ziqW(ncne`p%2rw8S1jiO`yDo*0U`vkc%&XCxK?1 zQ7tz(h7}kA|C2FY4N2FG2SSMNgAJ6B9wLAD6}U!P<>UC{r`xi24acZ$aF|^xW?prB zACNJ}OZ<;?Bk2{QuPlI)Lc-ek`;=$$QDT1ZAku8xH#3L_r>^+S5G=*SV&$lvSzAUw zBG~0w+b9v;@WQ7#O+ee%B_OO2?!snrxMxEUD9S*Oz^X{L2!hLuZgkWhNQKq;2}+`f zt?x~yNE5K%x42bz`Pr=#gHE=QwL~Q`l3*W$&P__1W@LUyY%Pk*63VgFoZd47Rmoe5 z%{r5f%AQIFQ8#4yc5=`?E&0P`{awwKP8xv4en}hsu-8r&KK8gc!%-fK9c>!Ntrqs}4F9uSa{yPzfLqz<2|kfPQThCsfw;NUpIi zZu-Y6>LDKvj9@4Gks)Q|Q5}j|yiU@%U-D_a-)nLXVZ;FGjDhNS7p^kegkICf0jI@m zvDp!h1r0TuGO@N@&QFSZzQB~%TQj!>Uj%JFN~!hOPCz!pcYm)A+#5n*TlEAPowakl z^+U6JB-&0#6(Btl>cX$ek#C7~60nlrm0;GJk#g~U$z`-w4mtO<>hcDfvw4T2c)t7I zGElr1NKrN&@yt&gml3{-r&}_+77TXWekW}c0(v26QpJAL8A*MsnQF=YY0wWy!XhNf z;OcQImepPfz8cJFYP7vUker#cTA0tD>|cp_MhGArcg*zi*t@5&X4+Bc!w5_Wrwuc9 zG~D71=IjWQTFK1tmI?T9Ar4o_}LA%s(_elXx3an zRJN1=r>YDR^UALpu7CZr+e?t<89la^zdn6%&GJrvb7BQw4x(YkH<}itNej&P$WMF+ z;44I+wU#riiY4YG6E6b#7Kb|4hl`z#eqTBZm?@xlCwrUxGze@Rc(CA3G3bL{BG+Fr{x0vqA`^ZtL6-MtKh4N#Rw*L9IGYA7lS)F zZGy_oBoⓈ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 HTYUas#YQ^T literal 0 HcmV?d00001 diff --git a/blog/images/20241210-reactions.png b/blog/images/20241210-reactions.png new file mode 100644 index 0000000000000000000000000000000000000000..6de8ba8f07433706d0a3a6ea3fb2524b8b31f274 GIT binary patch literal 481860 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

    d%{kH$Be_%tp?#rYky@3S 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�W$vbj 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

    4K?OEh*Lh`=4Ko<&|EUuVt)B6FNlNw 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�-^I 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 z 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 L%Zpct>iYi|N+@;! literal 0 HcmV?d00001 diff --git a/website/src/_includes/blog_previews/20241125.html b/website/src/_includes/blog_previews/20241125.html index 2a73e9ec9b..8e71807c0b 100644 --- a/website/src/_includes/blog_previews/20241125.html +++ b/website/src/_includes/blog_previews/20241125.html @@ -1,5 +1,3 @@ -

    -
  • - - - - - - No comment provided by engineer. - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (може да се копира) @@ -323,11 +307,6 @@ %u пропуснати съобщения. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (ново) @@ -338,11 +317,6 @@ (това устройство v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - **Create 1-time link**: to create and share a new invitation link. **Добави контакт**: за създаване на нов линк. @@ -407,11 +381,6 @@ \*удебелен* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -448,11 +417,6 @@ - история на редактиране. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 сек @@ -519,11 +483,6 @@ 30 секунди No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -531,14 +490,6 @@ <p><a href="%@">Свържете се с мен чрез SimpleX Chat</a></p> email text - - @%@ - No comment provided by engineer. - - - @'%@' - No comment provided by engineer. - A few more things Още няколко неща @@ -780,8 +731,8 @@ Всички чатове и съобщения ще бъдат изтрити - това не може да бъде отменено! No comment provided by engineer. - - All chats will be removed from the list (text), and the list deleted. + + All chats will be removed from the list %@, and the list deleted. alert message @@ -3495,9 +3446,9 @@ This is your own one-time link! %@ alert message - + File is blocked by server operator: -(info.reason.text). +%@. file error text @@ -6106,8 +6057,8 @@ Enable in *Network & servers* settings. Report violation: only group moderators will see it. report reason - - Report: (text.isEmpty ? reason.text : text) + + Report: %@ report in notification diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index 54dc3734c3..e8ea11fb5f 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -5,22 +5,6 @@
    - - - - - - No comment provided by engineer. - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (lze kopírovat) @@ -321,11 +305,6 @@ %u zpráv přeskočeno. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) No comment provided by engineer. @@ -335,11 +314,6 @@ (toto zařízení v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - **Create 1-time link**: to create and share a new invitation link. No comment provided by engineer. @@ -400,11 +374,6 @@ \*tučně* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -438,11 +407,6 @@ - historie úprav. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec time to disappear @@ -507,11 +471,6 @@ 30 vteřin No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -519,14 +478,6 @@ <p><a href="%@"> Připojte se ke mne přes SimpleX Chat</a></p> email text - - @%@ - No comment provided by engineer. - - - @'%@' - No comment provided by engineer. - A few more things Ještě pár věcí @@ -749,8 +700,8 @@ Všechny chaty a zprávy budou smazány – tuto akci nelze vrátit zpět! No comment provided by engineer. - - All chats will be removed from the list (text), and the list deleted. + + All chats will be removed from the list %@, and the list deleted. alert message @@ -3352,9 +3303,9 @@ This is your own one-time link! %@ alert message - + File is blocked by server operator: -(info.reason.text). +%@. file error text @@ -5877,8 +5828,8 @@ Enable in *Network & servers* settings. Report violation: only group moderators will see it. report reason - - Report: (text.isEmpty ? reason.text : text) + + Report: %@ report in notification diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 1c016f4614..4c391f97d3 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -5,23 +5,6 @@
    - - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (kann kopiert werden) @@ -332,11 +315,6 @@ %u übersprungene Nachrichten. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (Neu) @@ -347,11 +325,6 @@ (Dieses Gerät hat v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - **Create 1-time link**: to create and share a new invitation link. **Kontakt hinzufügen**: Um einen neuen Einladungslink zu erstellen. @@ -417,11 +390,6 @@ \*fett* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -458,11 +426,6 @@ - Nachrichtenverlauf bearbeiten No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 sek @@ -531,11 +494,6 @@ 30 Sekunden No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -543,16 +501,6 @@ <p><a href="%@">Verbinden Sie sich per SimpleX Chat mit mir</a></p> email text - - @%@ - @%@ - No comment provided by engineer. - - - @'%@' - @'%@' - No comment provided by engineer. - A few more things Ein paar weitere Dinge @@ -798,9 +746,9 @@ Es werden alle Chats und Nachrichten gelöscht. Dies kann nicht rückgängig gemacht werden! No comment provided by engineer. - - All chats will be removed from the list (text), and the list deleted. - Alle Chats werden von der Liste (text) entfernt und danach wird die Liste gelöscht. + + All chats will be removed from the list %@, and the list deleted. + Alle Chats werden von der Liste %@ entfernt und danach wird die Liste gelöscht. alert message @@ -3663,11 +3611,11 @@ Das ist Ihr eigener Einmal-Link! %@ alert message - + File is blocked by server operator: -(info.reason.text). +%@. Datei wurde vom Server-Betreiber blockiert: -(info.reason.text). +%@. file error text @@ -6448,9 +6396,9 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Verstoß melden: Nur Gruppenmoderatoren werden es sehen. report reason - - Report: (text.isEmpty ? reason.text : text) - Meldung: (text.isEmpty ? reason.text : text) + + Report: %@ + Meldung: %@ report in notification diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 39d229c5fa..a205080107 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -5,23 +5,6 @@
    - - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (can be copied) @@ -332,11 +315,6 @@ %u messages skipped. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (new) @@ -347,11 +325,6 @@ (this device v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - **Create 1-time link**: to create and share a new invitation link. **Create 1-time link**: to create and share a new invitation link. @@ -417,11 +390,6 @@ \*bold* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -458,11 +426,6 @@ - editing history. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 sec @@ -531,11 +494,6 @@ 30 seconds No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -543,16 +501,6 @@ <p><a href="%@">Connect to me via SimpleX Chat</a></p> email text - - @%@ - @%@ - No comment provided by engineer. - - - @'%@' - @'%@' - No comment provided by engineer. - A few more things A few more things @@ -798,9 +746,9 @@ All chats and messages will be deleted - this cannot be undone! No comment provided by engineer. - - All chats will be removed from the list (text), and the list deleted. - All chats will be removed from the list (text), and the list deleted. + + All chats will be removed from the list %@, and the list deleted. + All chats will be removed from the list %@, and the list deleted. alert message @@ -3663,11 +3611,11 @@ This is your own one-time link! %@ alert message - + File is blocked by server operator: -(info.reason.text). +%@. File is blocked by server operator: -(info.reason.text). +%@. file error text @@ -6448,9 +6396,9 @@ Enable in *Network & servers* settings. Report violation: only group moderators will see it. report reason - - Report: (text.isEmpty ? reason.text : text) - Report: (text.isEmpty ? reason.text : text) + + Report: %@ + Report: %@ report in notification diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 07a1447735..fd2ae881ce 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -5,23 +5,6 @@
    - - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (puede copiarse) @@ -332,11 +315,6 @@ %u mensaje(s) omitido(s). No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (nuevo) @@ -347,11 +325,6 @@ (este dispositivo v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - **Create 1-time link**: to create and share a new invitation link. **Añadir contacto**: crea un enlace de invitación nuevo. @@ -417,11 +390,6 @@ \*bold* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -458,11 +426,6 @@ - historial de edición. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 seg @@ -531,11 +494,6 @@ 30 segundos No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -543,16 +501,6 @@ <p><a href="%@"> Conecta conmigo a través de SimpleX Chat</a></p> email text - - @%@ - @%@ - No comment provided by engineer. - - - @'%@' - @'%@' - No comment provided by engineer. - A few more things Algunas cosas más @@ -798,9 +746,9 @@ Se eliminarán todos los chats y mensajes. ¡No podrá deshacerse! No comment provided by engineer. - - All chats will be removed from the list (text), and the list deleted. - Todos los chats serán quitados de la lista (text) y esta será eliminada. + + All chats will be removed from the list %@, and the list deleted. + Todos los chats serán quitados de la lista %@ y esta será eliminada. alert message @@ -3663,11 +3611,11 @@ This is your own one-time link! %@ alert message - + File is blocked by server operator: -(info.reason.text). +%@. Archivo bloqueado por el operador del servidor -(info.reason.text). +%@. file error text @@ -6448,9 +6396,9 @@ Actívalo en ajustes de *Servidores y Redes*. Informar de violación: sólo los moderadores del grupo lo verán. report reason - - Report: (text.isEmpty ? reason.text : text) - Informe: (text.isEmpty ? reason.text : text) + + Report: %@ + Informe: %@ report in notification diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index 9d7c9c4bbb..baa5fc7c48 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -5,22 +5,6 @@
    - - - - - - No comment provided by engineer. - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (voidaan kopioida) @@ -312,11 +296,6 @@ %u viestit ohitettu. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) No comment provided by engineer. @@ -325,11 +304,6 @@ (this device v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - **Create 1-time link**: to create and share a new invitation link. No comment provided by engineer. @@ -390,11 +364,6 @@ \*bold* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -425,11 +394,6 @@ - historian muokkaaminen. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec time to disappear @@ -494,11 +458,6 @@ 30 sekuntia No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -506,14 +465,6 @@ <p> <a href="%@"> Ollaan yhteydessä SimpleX Chatin kautta</a></p> email text - - @%@ - No comment provided by engineer. - - - @'%@' - No comment provided by engineer. - A few more things Muutama asia lisää @@ -736,8 +687,8 @@ Kaikki keskustelut ja viestit poistetaan - tätä ei voi kumota! No comment provided by engineer. - - All chats will be removed from the list (text), and the list deleted. + + All chats will be removed from the list %@, and the list deleted. alert message @@ -3334,9 +3285,9 @@ This is your own one-time link! %@ alert message - + File is blocked by server operator: -(info.reason.text). +%@. file error text @@ -5857,8 +5808,8 @@ Enable in *Network & servers* settings. Report violation: only group moderators will see it. report reason - - Report: (text.isEmpty ? reason.text : text) + + Report: %@ report in notification diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 31eef4b3b1..d36e7c2bd4 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -5,22 +5,6 @@
    - - - - - - No comment provided by engineer. - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (peut être copié) @@ -330,11 +314,6 @@ %u messages sautés. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (nouveau) @@ -345,11 +324,6 @@ (cet appareil v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - **Create 1-time link**: to create and share a new invitation link. **Ajouter un contact** : pour créer un nouveau lien d'invitation. @@ -415,11 +389,6 @@ \*gras* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -456,11 +425,6 @@ - l'historique de modification. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 sec @@ -528,11 +492,6 @@ 30 secondes No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -540,14 +499,6 @@ <p><a href="%@">Contactez-moi via SimpleX Chat</a></p> email text - - @%@ - No comment provided by engineer. - - - @'%@' - No comment provided by engineer. - A few more things Encore quelques points @@ -789,8 +740,8 @@ Toutes les discussions et tous les messages seront supprimés - il est impossible de revenir en arrière ! No comment provided by engineer. - - All chats will be removed from the list (text), and the list deleted. + + All chats will be removed from the list %@, and the list deleted. alert message @@ -3607,9 +3558,9 @@ Il s'agit de votre propre lien unique ! %@ alert message - + File is blocked by server operator: -(info.reason.text). +%@. file error text @@ -6337,8 +6288,8 @@ Activez-le dans les paramètres *Réseau et serveurs*. Report violation: only group moderators will see it. report reason - - Report: (text.isEmpty ? reason.text : text) + + Report: %@ report in notification diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 1ff79e2697..7c25a74d3f 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -5,23 +5,6 @@
    - - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (másolható) @@ -332,11 +315,6 @@ %u üzenet kihagyva. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (új) @@ -347,11 +325,6 @@ (ez az eszköz: v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - **Create 1-time link**: to create and share a new invitation link. **Partner hozzáadása:** új meghívási hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz. @@ -417,11 +390,6 @@ \*félkövér* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -458,11 +426,6 @@ - előzmények szerkesztése. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 mp @@ -531,11 +494,6 @@ 30 másodperc No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -543,16 +501,6 @@ <p><a href="%@">Csatlakozzon hozzám a SimpleX Chaten keresztül</a></p> email text - - @%@ - @%@ - No comment provided by engineer. - - - @'%@' - @'%@' - No comment provided by engineer. - A few more things Néhány további dolog @@ -798,9 +746,9 @@ Az összes csevegés és üzenet törölve lesz – ez a művelet nem vonható vissza! No comment provided by engineer. - - All chats will be removed from the list (text), and the list deleted. - Az összes csevegés el lesz távolítva a(z) (text) nevű listáról, és a lista is törölve lesz. + + All chats will be removed from the list %@, and the list deleted. + Az összes csevegés el lesz távolítva a(z) %@ nevű listáról, és a lista is törölve lesz. alert message @@ -3663,11 +3611,11 @@ Ez az Ön egyszer használható meghívási hivatkozása! %@ alert message - + File is blocked by server operator: -(info.reason.text). +%@. A kiszolgáló üzemeltetője letiltotta a fájlt: -(info.reason.text). +%@. file error text @@ -6448,9 +6396,9 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. Szabálysértés jelentése: csak a csoport moderátorai látják. report reason - - Report: (text.isEmpty ? reason.text : text) - Jelentés: (text.isEmpty ? reason.text : text) + + Report: %@ + Jelentés: %@ report in notification diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 2855e1bd77..18ee99deda 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -5,23 +5,6 @@
    - - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (può essere copiato) @@ -332,11 +315,6 @@ %u messaggi saltati. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (nuovo) @@ -347,11 +325,6 @@ (questo dispositivo v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - **Create 1-time link**: to create and share a new invitation link. **Aggiungi contatto**: per creare un nuovo link di invito. @@ -417,11 +390,6 @@ \*grassetto* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -458,11 +426,6 @@ - cronologia delle modifiche. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 sec @@ -531,11 +494,6 @@ 30 secondi No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -543,16 +501,6 @@ <p><a href="%@">Connettiti a me via SimpleX Chat</a></p> email text - - @%@ - @%@ - No comment provided by engineer. - - - @'%@' - @'%@' - No comment provided by engineer. - A few more things Qualche altra cosa @@ -798,9 +746,9 @@ Tutte le chat e i messaggi verranno eliminati. Non è reversibile! No comment provided by engineer. - - All chats will be removed from the list (text), and the list deleted. - Tutte le chat verranno rimosse dall'elenco (testo) e l'elenco eliminato. + + All chats will be removed from the list %@, and the list deleted. + Tutte le chat verranno rimosse dall'elenco %@ e l'elenco eliminato. alert message @@ -3663,11 +3611,11 @@ Questo è il tuo link una tantum! %@ alert message - + File is blocked by server operator: -(info.reason.text). +%@. Il file è bloccato dall'operatore del server: -(info.reason.text). +%@. file error text @@ -6448,9 +6396,9 @@ Attivalo nelle impostazioni *Rete e server*. Segnala violazione: solo i moderatori del gruppo lo vedranno. report reason - - Report: (text.isEmpty ? reason.text : text) - Segnalazione: (text.isEmpty ? reason.text : text) + + Report: %@ + Segnalazione: %@ report in notification diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 1a79d251eb..fdca2d5aca 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -5,22 +5,6 @@
    - - - - - - No comment provided by engineer. - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (コピー可能) @@ -330,11 +314,6 @@ %u 件のメッセージがスキップされました。 No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (新規) @@ -345,11 +324,6 @@ (このデバイス v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - **Create 1-time link**: to create and share a new invitation link. **コンタクトの追加**: 新しい招待リンクを作成するか、受け取ったリンクから接続します。 @@ -415,11 +389,6 @@ \*太字* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -456,11 +425,6 @@ - 編集履歴。 No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 秒 @@ -528,11 +492,6 @@ 30秒 No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -540,14 +499,6 @@ <p><a href="%@">SimpleX Chatでつながろう</a></p> email text - - @%@ - No comment provided by engineer. - - - @'%@' - No comment provided by engineer. - A few more things その他 @@ -773,8 +724,8 @@ 全チャットとメッセージが削除されます(※元に戻せません※)! No comment provided by engineer. - - All chats will be removed from the list (text), and the list deleted. + + All chats will be removed from the list %@, and the list deleted. alert message @@ -3407,9 +3358,9 @@ This is your own one-time link! %@ alert message - + File is blocked by server operator: -(info.reason.text). +%@. file error text @@ -5934,8 +5885,8 @@ Enable in *Network & servers* settings. Report violation: only group moderators will see it. report reason - - Report: (text.isEmpty ? reason.text : text) + + Report: %@ report in notification diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index aeb9c96696..d8d0b3c712 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -5,23 +5,6 @@
    - - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (kan gekopieerd worden) @@ -332,11 +315,6 @@ %u berichten zijn overgeslagen. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (nieuw) @@ -347,11 +325,6 @@ (dit apparaat v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - **Create 1-time link**: to create and share a new invitation link. **Contact toevoegen**: om een nieuwe uitnodigingslink aan te maken, of verbinding te maken via een link die u heeft ontvangen. @@ -417,11 +390,6 @@ \*vetgedrukt* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -458,11 +426,6 @@ - bewerkingsgeschiedenis. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 sec @@ -531,11 +494,6 @@ 30 seconden No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -543,16 +501,6 @@ <p><a href="%@">Maak verbinding met mij via SimpleX Chat</a></p> email text - - @%@ - @%@ - No comment provided by engineer. - - - @'%@' - @'%@' - No comment provided by engineer. - A few more things Nog een paar dingen @@ -798,9 +746,9 @@ Alle chats en berichten worden verwijderd, dit kan niet ongedaan worden gemaakt! No comment provided by engineer. - - All chats will be removed from the list (text), and the list deleted. - Alle chats worden uit de lijst (tekst) verwijderd en de lijst wordt verwijderd. + + All chats will be removed from the list %@, and the list deleted. + Alle chats worden uit de lijst %@ verwijderd en de lijst wordt verwijderd. alert message @@ -3663,11 +3611,11 @@ Dit is uw eigen eenmalige link! %@ alert message - + File is blocked by server operator: -(info.reason.text). +%@. Bestand is geblokkeerd door serveroperator: -(info.reason.text). +%@. file error text @@ -6448,9 +6396,9 @@ Schakel dit in in *Netwerk en servers*-instellingen. Rapporteer overtreding: alleen groepsmoderators kunnen dit zien. report reason - - Report: (text.isEmpty ? reason.text : text) - Rapport: (text.isEmpty ? reason.text : text) + + Report: %@ + Rapport: %@ report in notification diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index aa257a2322..6a2d6e0d83 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -5,22 +5,6 @@
    - - - - - - No comment provided by engineer. - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (można skopiować) @@ -331,11 +315,6 @@ %u pominiętych wiadomości. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (nowy) @@ -346,11 +325,6 @@ (to urządzenie v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - **Create 1-time link**: to create and share a new invitation link. **Dodaj kontakt**: aby utworzyć nowy link z zaproszeniem lub połączyć się za pomocą otrzymanego linku. @@ -416,11 +390,6 @@ \*pogrubiony* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -457,11 +426,6 @@ - historia edycji. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 sek @@ -530,11 +494,6 @@ 30 sekund No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -542,16 +501,6 @@ <p><a href="%@">Połącz się ze mną poprzez SimpleX Chat.</a></p> email text - - @%@ - @%@ - No comment provided by engineer. - - - @'%@' - @'%@' - No comment provided by engineer. - A few more things Jeszcze kilka rzeczy @@ -797,9 +746,9 @@ Wszystkie czaty i wiadomości zostaną usunięte - nie można tego cofnąć! No comment provided by engineer. - - All chats will be removed from the list (text), and the list deleted. - Wszystkie rozmowy zostaną usunięte z listy (text), a lista usunięta. + + All chats will be removed from the list %@, and the list deleted. + Wszystkie rozmowy zostaną usunięte z listy %@, a lista usunięta. alert message @@ -3607,9 +3556,9 @@ To jest twój jednorazowy link! %@ alert message - + File is blocked by server operator: -(info.reason.text). +%@. file error text @@ -6302,8 +6251,8 @@ Włącz w ustawianiach *Sieć i serwery* . Report violation: only group moderators will see it. report reason - - Report: (text.isEmpty ? reason.text : text) + + Report: %@ report in notification diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index edb7ca86e2..0e14a85c19 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -5,23 +5,6 @@
    - - - - - - No comment provided by engineer. - - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (можно скопировать) @@ -332,11 +315,6 @@ %u сообщений пропущено. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (новое) @@ -347,11 +325,6 @@ (это устройство v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - **Create 1-time link**: to create and share a new invitation link. **Добавить контакт**: создать и поделиться новой ссылкой-приглашением. @@ -417,11 +390,6 @@ \*жирный* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -458,11 +426,6 @@ - история редактирования. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 сек @@ -531,11 +494,6 @@ 30 секунд No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -543,16 +501,6 @@ <p><a href="%@">Соединитесь со мной в SimpleX Chat.</a></p> email text - - @%@ - @%@ - No comment provided by engineer. - - - @'%@' - @'%@' - No comment provided by engineer. - A few more things Еще несколько изменений @@ -798,9 +746,9 @@ Все чаты и сообщения будут удалены - это нельзя отменить! No comment provided by engineer. - - All chats will be removed from the list (text), and the list deleted. - Все чаты будут удалены из списка, а сам список удален. + + All chats will be removed from the list %@, and the list deleted. + Все чаты будут удалены из списка %@, и список удален. alert message @@ -845,7 +793,7 @@ All reports will be archived for you. - Все отчеты будут заархивированы для вас. + Все сообщения о нарушениях будут заархивированы для вас. No comment provided by engineer. @@ -925,7 +873,7 @@ Allow to report messsages to moderators. - Разрешить отправлять жалобы на сообщения модераторам. + Разрешить отправлять сообщения о нарушениях модераторам. No comment provided by engineer. @@ -1095,12 +1043,12 @@ Archive %lld reports? - Архивировать %lld отчёт(ов)? + Архивировать %lld сообщений о нарушениях? No comment provided by engineer. Archive all reports? - Архивировать все отчеты? + Архивировать все сообщения о нарушениях? No comment provided by engineer. @@ -1115,17 +1063,17 @@ Archive report - Архивировать отчет + Архивировать сообщение о нарушении No comment provided by engineer. Archive report? - Архивировать отчет? + Архивировать сообщение о нарушении? No comment provided by engineer. Archive reports - Архивировать отчёты + Архивировать сообщения о нарушениях swipe action @@ -1140,7 +1088,7 @@ Ask - спросить + Спросить No comment provided by engineer. @@ -1270,7 +1218,7 @@ Better privacy and security - Улучшенная приватность и безопасность + Улучшенная конфиденциальность и безопасность No comment provided by engineer. @@ -1375,7 +1323,7 @@ Businesses - Предприятия + Бизнесы No comment provided by engineer. @@ -1727,7 +1675,7 @@ Community guidelines violation - Нарушение принципов Сообщества + Нарушение правил группы report reason @@ -2587,7 +2535,7 @@ This is your own one-time link! Delete report - удалить отчёт + Удалить сообщение о нарушении No comment provided by engineer. @@ -3273,7 +3221,7 @@ This is your own one-time link! Error creating report - Ошибка создания отчета + Ошибка создания сообщения о нарушении No comment provided by engineer. @@ -3398,7 +3346,7 @@ This is your own one-time link! Error reordering lists - Ошибка переупорядочивания списков + Ошибка сортировки списков alert title @@ -3663,10 +3611,11 @@ This is your own one-time link! %@ alert message - + File is blocked by server operator: -(info.reason.text). - Файл заблокирован оператором сервера +%@. + Файл заблокирован оператором сервера: +%@. file error text @@ -3945,7 +3894,7 @@ Error: %2$@ Get notified when mentioned. - Получайте уведомления от упоминаний. + Уведомления, когда Вас упомянули. No comment provided by engineer. @@ -4276,12 +4225,12 @@ More improvements are coming soon! Inappropriate content - Нежелательный контент + Неприемлемый контент report reason Inappropriate profile - нежелательный профиль + Неприемлемый профиль report reason @@ -4781,7 +4730,7 @@ This is your link for group %@! Member reports - Жалобы на участника + Сообщения о нарушениях chat feature @@ -4821,7 +4770,7 @@ This is your link for group %@! Members can report messsages to moderators. - Участники могут пожаловаться на сообщения администраторам. + Члены группы могут пожаловаться модераторам. No comment provided by engineer. @@ -4851,7 +4800,7 @@ This is your link for group %@! Mention members 👋 - Упоминайте участников 👋 + Упоминайте членов группы 👋 No comment provided by engineer. @@ -5106,7 +5055,7 @@ This is your link for group %@! Mute all - Выключить уведомления для всех + Все без звука notification label action @@ -5256,7 +5205,7 @@ This is your link for group %@! No chats - Никаких чатов + Нет чатов No comment provided by engineer. @@ -5266,7 +5215,7 @@ This is your link for group %@! No chats in list %@ - Никаких чатов в списке %@ + Нет чатов в списке %@ No comment provided by engineer. @@ -5386,7 +5335,7 @@ This is your link for group %@! No unread chats - Без непрочитанных чатов + Нет непрочитанных чатов No comment provided by engineer. @@ -5401,7 +5350,7 @@ This is your link for group %@! Notes - Примечания + Заметки No comment provided by engineer. @@ -5771,7 +5720,7 @@ Requires compatible VPN. Pending - В ожидании + Ожидает No comment provided by engineer. @@ -5880,7 +5829,7 @@ Error: %@ Please try to disable and re-enable notfications. - Попробуйте отключить и снова включить уведомления. + Попробуйте выключить и снова включить уведомления. token info @@ -5955,7 +5904,7 @@ Error: %@ Private media file names. - Приватные названия медиафайлов. + Конфиденциальные названия медиафайлов. No comment provided by engineer. @@ -6035,7 +5984,7 @@ Error: %@ Prohibit reporting messages to moderators. - Запретить отправлять жалобы на сообщения модераторам. + Запретить жаловаться модераторам группы. No comment provided by engineer. @@ -6418,47 +6367,47 @@ Enable in *Network & servers* settings. Report content: only group moderators will see it. - Содержание жалобы: его увидят только групповые модераторы. + Пожаловаться на сообщение: увидят только модераторы группы. report reason Report member profile: only group moderators will see it. - Жалоба на профиль участника: его увидят только групповые модераторы. + Пожаловаться на профиль: увидят только модераторы группы. report reason Report other: only group moderators will see it. - Другая жалоба: её увидят только модераторы. + Пожаловаться: увидят только модераторы группы. report reason Report reason? - Причина жалобы? + Причина сообщения? No comment provided by engineer. Report spam: only group moderators will see it. - Пожаловаться на спам: это увидит только модератор группы. + Пожаловаться на спам: увидят только модераторы группы. report reason Report violation: only group moderators will see it. - Пожаловаться на нарушение: это увидит только модератор группы. + Пожаловаться на нарушение: увидят только модераторы группы. report reason - - Report: (text.isEmpty ? reason.text : text) - Жалоба + + Report: %@ + Сообщение о нарушении: %@ report in notification Reporting messages to moderators is prohibited. - Жалобы на сообщения запрещены в этой группе. + Сообщения о нарушениях запрещены в этой группе. No comment provided by engineer. Reports - Жалобы + Сообщения о нарушениях No comment provided by engineer. @@ -6884,7 +6833,7 @@ Enable in *Network & servers* settings. Send private reports - Отправляйте приватные жалобы + Вы можете сообщить о нарушениях No comment provided by engineer. @@ -7104,7 +7053,7 @@ Enable in *Network & servers* settings. Set chat name… - Установить имя чата… + Имя чата… No comment provided by engineer. @@ -7129,7 +7078,7 @@ Enable in *Network & servers* settings. Set message expiration in chats. - Установите время исчезания сообщений в чатах. + Установите срок хранения сообщений в чатах. No comment provided by engineer. @@ -7598,7 +7547,7 @@ Enable in *Network & servers* settings. TCP port for messaging - TCP-порт для обмена сообщениями + TCP-порт для отправки сообщений No comment provided by engineer. @@ -7885,7 +7834,7 @@ It can happen because of some bug or when the connection is compromised. This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted. - Это действие не может быть отмененено - сообщения, отправленные и полученные в этом чате ранее чем выбранное, будут удалены. + Это действие нельзя отменить - сообщения в этом чате, отправленные или полученные раньше чем выбрано, будут удалены. alert message @@ -8324,7 +8273,7 @@ To connect, please ask your contact to create another connection link and check Use TCP port %@ when no port is specified. - Использовать TCP-порт %@, когда не указано ни одного порта. + Использовать TCP-порт %@, когда порт не указан. No comment provided by engineer. @@ -9233,7 +9182,7 @@ Repeat connection request? archived report - архивная жалоба + заархивированное сообщение о нарушении No comment provided by engineer. @@ -9841,7 +9790,7 @@ Repeat connection request? pending - на рассмотрении + ожидает No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index 05ebf4337d..74bb020cd6 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -5,22 +5,6 @@
    - - - - - - No comment provided by engineer. - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (สามารถคัดลอกได้) @@ -306,11 +290,6 @@ %u ข้อความที่ถูกข้าม No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) No comment provided by engineer. @@ -319,11 +298,6 @@ (this device v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - **Create 1-time link**: to create and share a new invitation link. No comment provided by engineer. @@ -384,11 +358,6 @@ \*ตัวหนา* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -419,11 +388,6 @@ - ประวัติการแก้ไข No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec time to disappear @@ -488,11 +452,6 @@ 30 วินาที No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -500,14 +459,6 @@ <p><a href="%@">เชื่อมต่อกับฉันผ่าน SimpleX Chat</a></p> email text - - @%@ - No comment provided by engineer. - - - @'%@' - No comment provided by engineer. - A few more things อีกสองสามอย่าง @@ -728,8 +679,8 @@ แชทและข้อความทั้งหมดจะถูกลบ - การดำเนินการนี้ไม่สามารถยกเลิกได้! No comment provided by engineer. - - All chats will be removed from the list (text), and the list deleted. + + All chats will be removed from the list %@, and the list deleted. alert message @@ -3319,9 +3270,9 @@ This is your own one-time link! %@ alert message - + File is blocked by server operator: -(info.reason.text). +%@. file error text @@ -5834,8 +5785,8 @@ Enable in *Network & servers* settings. Report violation: only group moderators will see it. report reason - - Report: (text.isEmpty ? reason.text : text) + + Report: %@ report in notification diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index 9b832b61fc..d3d81c2674 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -5,22 +5,6 @@
    - - - - - - No comment provided by engineer. - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (kopyalanabilir) @@ -330,11 +314,6 @@ %u mesajlar atlandı. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (yeni) @@ -345,11 +324,6 @@ (bu cihaz v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - **Create 1-time link**: to create and share a new invitation link. **Kişi ekle**: yeni bir davet bağlantısı oluşturmak için, ya da aldığın bağlantıyla bağlan. @@ -415,11 +389,6 @@ \*kalın* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -456,11 +425,6 @@ - düzenleme geçmişi. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 saniye @@ -528,11 +492,6 @@ 30 saniye No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -540,14 +499,6 @@ <p><a href="%@">SimpleX Chat ile bana bağlanın</a></p> email text - - @%@ - No comment provided by engineer. - - - @'%@' - No comment provided by engineer. - A few more things Birkaç şey daha @@ -789,8 +740,8 @@ Tüm konuşmalar ve mesajlar silinecektir. Bu, geri alınamaz! No comment provided by engineer. - - All chats will be removed from the list (text), and the list deleted. + + All chats will be removed from the list %@, and the list deleted. alert message @@ -3606,9 +3557,9 @@ Bu senin kendi tek kullanımlık bağlantın! %@ alert message - + File is blocked by server operator: -(info.reason.text). +%@. file error text @@ -6314,8 +6265,8 @@ Enable in *Network & servers* settings. Report violation: only group moderators will see it. report reason - - Report: (text.isEmpty ? reason.text : text) + + Report: %@ report in notification diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index bb9ae7d3cd..767867685d 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -5,22 +5,6 @@
    - - - - - - No comment provided by engineer. - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (можна скопіювати) @@ -330,11 +314,6 @@ %u повідомлень пропущено. No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (новий) @@ -345,11 +324,6 @@ (цей пристрій v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - **Create 1-time link**: to create and share a new invitation link. **Додати контакт**: створити нове посилання-запрошення. @@ -415,11 +389,6 @@ \*жирний* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -456,11 +425,6 @@ - історія редагування. No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 сек @@ -528,11 +492,6 @@ 30 секунд No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -540,14 +499,6 @@ <p><a href="%@"> Зв'яжіться зі мною через SimpleX Chat</a></p> email text - - @%@ - No comment provided by engineer. - - - @'%@' - No comment provided by engineer. - A few more things Ще кілька речей @@ -789,8 +740,8 @@ Всі чати та повідомлення будуть видалені - це неможливо скасувати! No comment provided by engineer. - - All chats will be removed from the list (text), and the list deleted. + + All chats will be removed from the list %@, and the list deleted. alert message @@ -3607,9 +3558,9 @@ This is your own one-time link! %@ alert message - + File is blocked by server operator: -(info.reason.text). +%@. file error text @@ -6337,8 +6288,8 @@ Enable in *Network & servers* settings. Report violation: only group moderators will see it. report reason - - Report: (text.isEmpty ? reason.text : text) + + Report: %@ report in notification diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index ef62814aff..d464b2f0ed 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -5,22 +5,6 @@
    - - - - - - No comment provided by engineer. - - - - No comment provided by engineer. - - - ( - ( - No comment provided by engineer. - (can be copied) (可复制) @@ -322,11 +306,6 @@ 已跳过 %u 条消息。 No comment provided by engineer. - - ( - ( - No comment provided by engineer. - (new) (新) @@ -337,11 +316,6 @@ (此设备 v%@) No comment provided by engineer. - - ) - ) - No comment provided by engineer. - **Create 1-time link**: to create and share a new invitation link. **添加联系人**: 创建新的邀请链接,或通过您收到的链接进行连接. @@ -406,11 +380,6 @@ \*加粗* No comment provided by engineer. - - , - , - 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)! - delivery receipts (up to 20 members). @@ -447,11 +416,6 @@ - 编辑消息历史。 No comment provided by engineer. - - . - . - No comment provided by engineer. - 0 sec 0 秒 @@ -517,11 +481,6 @@ 30秒 No comment provided by engineer. - - : - : - No comment provided by engineer. - <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> @@ -529,14 +488,6 @@ <p><a href="%@">通过 SimpleX Chat </a></p>与我联系 email text - - @%@ - No comment provided by engineer. - - - @'%@' - No comment provided by engineer. - A few more things 一些杂项 @@ -768,8 +719,8 @@ 所有聊天记录和消息将被删除——这一行为无法撤销! No comment provided by engineer. - - All chats will be removed from the list (text), and the list deleted. + + All chats will be removed from the list %@, and the list deleted. alert message @@ -3532,9 +3483,9 @@ This is your own one-time link! %@ alert message - + File is blocked by server operator: -(info.reason.text). +%@. file error text @@ -6209,8 +6160,8 @@ Enable in *Network & servers* settings. Report violation: only group moderators will see it. report reason - - Report: (text.isEmpty ? reason.text : text) + + Report: %@ report in notification diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 468bc2ea8f..51feb623e2 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -3827,7 +3827,7 @@ public enum FileError: Decodable, Equatable, Hashable { public var errorInfo: String { switch self { case .auth: NSLocalizedString("Wrong key or unknown file chunk address - most likely file is deleted.", comment: "file error text") - case let .blocked(_, info): NSLocalizedString("File is blocked by server operator:\n\(info.reason.text).", comment: "file error text") + case let .blocked(_, info): String.localizedStringWithFormat(NSLocalizedString("File is blocked by server operator:\n%@.", comment: "file error text"), info.reason.text) case .noFile: NSLocalizedString("File not found - most likely file was deleted or cancelled.", comment: "file error text") case let .relay(srvError): String.localizedStringWithFormat(NSLocalizedString("File server error: %@", comment: "file error text"), srvError.errorInfo) case let .other(fileError): String.localizedStringWithFormat(NSLocalizedString("Error: %@", comment: "file error text"), fileError) diff --git a/apps/ios/SimpleXChat/Notifications.swift b/apps/ios/SimpleXChat/Notifications.swift index 97cc633115..5579449caa 100644 --- a/apps/ios/SimpleXChat/Notifications.swift +++ b/apps/ios/SimpleXChat/Notifications.swift @@ -205,7 +205,7 @@ func hideSecrets(_ cItem: ChatItem) -> String { } else { let mc = cItem.content.msgContent if case let .report(text, reason) = mc { - return NSLocalizedString("Report: \(text.isEmpty ? reason.text : text)", comment: "report in notification") + return String.localizedStringWithFormat(NSLocalizedString("Report: %@", comment: "report in notification"), text.isEmpty ? reason.text : text) } else { return cItem.text } diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index b697b58222..631e0e7628 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -1,9 +1,3 @@ -/* No comment provided by engineer. */ -"\n" = "\n"; - -/* No comment provided by engineer. */ -" (" = " ("; - /* No comment provided by engineer. */ " (can be copied)" = " (може да се копира)"; @@ -22,30 +16,15 @@ /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- гласови съобщения до 5 минути.\n- персонализирано време за изчезване.\n- история на редактиране."; -/* No comment provided by engineer. */ -", " = ", "; - -/* No comment provided by engineer. */ -": " = ": "; - /* No comment provided by engineer. */ "!1 colored!" = "!1 цветно!"; -/* No comment provided by engineer. */ -"." = "."; - -/* No comment provided by engineer. */ -"(" = "("; - /* No comment provided by engineer. */ "(new)" = "(ново)"; /* No comment provided by engineer. */ "(this device v%@)" = "(това устройство v%@)"; -/* No comment provided by engineer. */ -")" = ")"; - /* No comment provided by engineer. */ "[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Допринеси](https://github.com/simplex-chat/simplex-chat#contribute)"; diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index eef7930c6f..b6baf5e951 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -1,9 +1,3 @@ -/* No comment provided by engineer. */ -"\n" = "\n"; - -/* No comment provided by engineer. */ -" (" = " ("; - /* No comment provided by engineer. */ " (can be copied)" = " (lze kopírovat)"; @@ -19,27 +13,12 @@ /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- 5 minutové hlasové zprávy.\n- vlastní čas mizení.\n- historie úprav."; -/* No comment provided by engineer. */ -", " = ", "; - -/* No comment provided by engineer. */ -": " = ": "; - /* No comment provided by engineer. */ "!1 colored!" = "!1 barevný!"; -/* No comment provided by engineer. */ -"." = "."; - -/* No comment provided by engineer. */ -"(" = "("; - /* No comment provided by engineer. */ "(this device v%@)" = "(toto zařízení v%@)"; -/* No comment provided by engineer. */ -")" = ")"; - /* No comment provided by engineer. */ "[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Přispějte](https://github.com/simplex-chat/simplex-chat#contribute)"; diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index da88d2cb77..a85506f2e3 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -1,12 +1,3 @@ -/* No comment provided by engineer. */ -"\n" = "\n"; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" (" = " ("; - /* No comment provided by engineer. */ " (can be copied)" = " (kann kopiert werden)"; @@ -25,30 +16,15 @@ /* 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- Nachrichtenverlauf bearbeiten"; -/* No comment provided by engineer. */ -", " = ", "; - -/* No comment provided by engineer. */ -": " = ": "; - /* No comment provided by engineer. */ "!1 colored!" = "!1 farbig!"; -/* No comment provided by engineer. */ -"." = "."; - -/* 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. */ -")" = ")"; - /* No comment provided by engineer. */ "[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Unterstützen Sie uns](https://github.com/simplex-chat/simplex-chat#contribute)"; @@ -58,12 +34,6 @@ /* No comment provided by engineer. */ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Stern auf GitHub vergeben](https://github.com/simplex-chat/simplex-chat)"; -/* No comment provided by engineer. */ -"@'%@'" = "@'%@'"; - -/* No comment provided by engineer. */ -"@%@" = "@%@"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Kontakt hinzufügen**: Um einen neuen Einladungslink zu erstellen."; @@ -503,7 +473,7 @@ "All chats and messages will be deleted - this cannot be undone!" = "Es werden alle Chats und Nachrichten gelöscht. Dies kann nicht rückgängig gemacht werden!"; /* alert message */ -"All chats will be removed from the list (text), and the list deleted." = "Alle Chats werden von der Liste (text) entfernt und danach wird die Liste gelöscht."; +"All chats will be removed from the list %@, and the list deleted." = "Alle Chats werden von der Liste %@ entfernt und danach wird die Liste gelöscht."; /* No comment provided by engineer. */ "All data is erased when it is entered." = "Alle Daten werden gelöscht, sobald dieser eingegeben wird."; @@ -2434,7 +2404,7 @@ "File errors:\n%@" = "Datei-Fehler:\n%@"; /* file error text */ -"File is blocked by server operator:\n(info.reason.text)." = "Datei wurde vom Server-Betreiber blockiert:\n(info.reason.text)."; +"File is blocked by server operator:\n%@." = "Datei wurde vom Server-Betreiber blockiert:\n%@."; /* file error text */ "File not found - most likely file was deleted or cancelled." = "Datei nicht gefunden - höchstwahrscheinlich wurde die Datei gelöscht oder der Transfer abgebrochen."; @@ -4276,7 +4246,7 @@ "Report violation: only group moderators will see it." = "Verstoß melden: Nur Gruppenmoderatoren werden es sehen."; /* report in notification */ -"Report: (text.isEmpty ? reason.text : text)" = "Meldung: (text.isEmpty ? reason.text : text)"; +"Report: %@" = "Meldung: %@"; /* No comment provided by engineer. */ "Reporting messages to moderators is prohibited." = "Melden von Nachrichten an Moderatoren ist nicht erlaubt."; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index f69e9a1356..09e7ea5b8e 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -1,12 +1,3 @@ -/* No comment provided by engineer. */ -"\n" = "\n"; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" (" = " ("; - /* No comment provided by engineer. */ " (can be copied)" = " (puede copiarse)"; @@ -25,30 +16,15 @@ /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- mensajes de voz de hasta 5 minutos.\n- tiempo personalizado para mensajes temporales.\n- historial de edición."; -/* No comment provided by engineer. */ -", " = ", "; - -/* No comment provided by engineer. */ -": " = ": "; - /* No comment provided by engineer. */ "!1 colored!" = "!1 coloreado!"; -/* No comment provided by engineer. */ -"." = "."; - -/* No comment provided by engineer. */ -"(" = "("; - /* No comment provided by engineer. */ "(new)" = "(nuevo)"; /* No comment provided by engineer. */ "(this device v%@)" = "(este dispositivo v%@)"; -/* No comment provided by engineer. */ -")" = ")"; - /* No comment provided by engineer. */ "[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Contribuye](https://github.com/simplex-chat/simplex-chat#contribute)"; @@ -58,12 +34,6 @@ /* No comment provided by engineer. */ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Estrella en GitHub](https://github.com/simplex-chat/simplex-chat)"; -/* No comment provided by engineer. */ -"@'%@'" = "@'%@'"; - -/* No comment provided by engineer. */ -"@%@" = "@%@"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Añadir contacto**: crea un enlace de invitación nuevo."; @@ -503,7 +473,7 @@ "All chats and messages will be deleted - this cannot be undone!" = "Se eliminarán todos los chats y mensajes. ¡No podrá deshacerse!"; /* alert message */ -"All chats will be removed from the list (text), and the list deleted." = "Todos los chats serán quitados de la lista (text) y esta será eliminada."; +"All chats will be removed from the list %@, and the list deleted." = "Todos los chats serán quitados de la lista %@ y esta será eliminada."; /* No comment provided by engineer. */ "All data is erased when it is entered." = "Al introducirlo todos los datos son eliminados."; @@ -2434,7 +2404,7 @@ "File errors:\n%@" = "Error(es) de archivo\n%@"; /* file error text */ -"File is blocked by server operator:\n(info.reason.text)." = "Archivo bloqueado por el operador del servidor\n(info.reason.text)."; +"File is blocked by server operator:\n%@." = "Archivo bloqueado por el operador del servidor\n%@."; /* file error text */ "File not found - most likely file was deleted or cancelled." = "Archivo no encontrado, probablemente haya sido borrado o cancelado."; @@ -4276,7 +4246,7 @@ "Report violation: only group moderators will see it." = "Informar de violación: sólo los moderadores del grupo lo verán."; /* report in notification */ -"Report: (text.isEmpty ? reason.text : text)" = "Informe: (text.isEmpty ? reason.text : text)"; +"Report: %@" = "Informe: %@"; /* No comment provided by engineer. */ "Reporting messages to moderators is prohibited." = "No se permite informar de mensajes a los moderadores."; diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index e860e56426..33a2acff78 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -1,9 +1,3 @@ -/* No comment provided by engineer. */ -"\n" = "\n"; - -/* No comment provided by engineer. */ -" (" = " ("; - /* No comment provided by engineer. */ " (can be copied)" = " (voidaan kopioida)"; @@ -16,24 +10,9 @@ /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- ääniviestit enintään 5 minuuttia.\n- mukautettu katoamisaika.\n- historian muokkaaminen."; -/* No comment provided by engineer. */ -", " = ", "; - -/* No comment provided by engineer. */ -": " = ": "; - /* No comment provided by engineer. */ "!1 colored!" = "!1 värillinen!"; -/* No comment provided by engineer. */ -"." = "."; - -/* No comment provided by engineer. */ -"(" = "("; - -/* No comment provided by engineer. */ -")" = ")"; - /* No comment provided by engineer. */ "[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Osallistu](https://github.com/simplex-chat/simplex-chat#contribute)"; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index 05dcfa9cd3..ad33963572 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -1,9 +1,3 @@ -/* No comment provided by engineer. */ -"\n" = "\n"; - -/* No comment provided by engineer. */ -" (" = " ("; - /* No comment provided by engineer. */ " (can be copied)" = " (peut être copié)"; @@ -22,30 +16,15 @@ /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- messages vocaux pouvant durer jusqu'à 5 minutes.\n- délai personnalisé de disparition.\n- l'historique de modification."; -/* No comment provided by engineer. */ -", " = ", "; - -/* No comment provided by engineer. */ -": " = ": "; - /* No comment provided by engineer. */ "!1 colored!" = "!1 coloré!"; -/* No comment provided by engineer. */ -"." = "."; - -/* No comment provided by engineer. */ -"(" = "("; - /* No comment provided by engineer. */ "(new)" = "(nouveau)"; /* No comment provided by engineer. */ "(this device v%@)" = "(cet appareil v%@)"; -/* No comment provided by engineer. */ -")" = ")"; - /* No comment provided by engineer. */ "[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Contribuer](https://github.com/simplex-chat/simplex-chat#contribute)"; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index 0fb32210f5..64b087aa59 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -1,12 +1,3 @@ -/* No comment provided by engineer. */ -"\n" = "\n"; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" (" = " ("; - /* No comment provided by engineer. */ " (can be copied)" = " (másolható)"; @@ -25,30 +16,15 @@ /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- legfeljebb 5 perc hosszúságú hangüzenetek.\n- egyéni üzenet-eltűnési időkorlát.\n- előzmények szerkesztése."; -/* No comment provided by engineer. */ -", " = ", "; - -/* No comment provided by engineer. */ -": " = ": "; - /* No comment provided by engineer. */ "!1 colored!" = "!1 színezett!"; -/* No comment provided by engineer. */ -"." = "."; - -/* No comment provided by engineer. */ -"(" = "("; - /* No comment provided by engineer. */ "(new)" = "(új)"; /* No comment provided by engineer. */ "(this device v%@)" = "(ez az eszköz: v%@)"; -/* No comment provided by engineer. */ -")" = ")"; - /* No comment provided by engineer. */ "[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Hozzájárulás](https://github.com/simplex-chat/simplex-chat#contribute)"; @@ -58,12 +34,6 @@ /* No comment provided by engineer. */ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Csillagozás a GitHubon](https://github.com/simplex-chat/simplex-chat)"; -/* No comment provided by engineer. */ -"@'%@'" = "@'%@'"; - -/* No comment provided by engineer. */ -"@%@" = "@%@"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Partner hozzáadása:** új meghívási hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz."; @@ -503,7 +473,7 @@ "All chats and messages will be deleted - this cannot be undone!" = "Az összes csevegés és üzenet törölve lesz – ez a művelet nem vonható vissza!"; /* alert message */ -"All chats will be removed from the list (text), and the list deleted." = "Az összes csevegés el lesz távolítva a(z) (text) nevű listáról, és a lista is törölve lesz."; +"All chats will be removed from the list %@, and the list deleted." = "Az összes csevegés el lesz távolítva a(z) %@ nevű listáról, és a lista is törölve lesz."; /* No comment provided by engineer. */ "All data is erased when it is entered." = "A jelkód megadása után az összes adat törölve lesz."; @@ -2434,7 +2404,7 @@ "File errors:\n%@" = "Fájlhiba:\n%@"; /* file error text */ -"File is blocked by server operator:\n(info.reason.text)." = "A kiszolgáló üzemeltetője letiltotta a fájlt:\n(info.reason.text)."; +"File is blocked by server operator:\n%@." = "A kiszolgáló üzemeltetője letiltotta a fájlt:\n%@."; /* file error text */ "File not found - most likely file was deleted or cancelled." = "A fájl nem található – valószínűleg a fájlt törölték vagy visszavonták."; @@ -4276,7 +4246,7 @@ "Report violation: only group moderators will see it." = "Szabálysértés jelentése: csak a csoport moderátorai látják."; /* report in notification */ -"Report: (text.isEmpty ? reason.text : text)" = "Jelentés: (text.isEmpty ? reason.text : text)"; +"Report: %@" = "Jelentés: %@"; /* No comment provided by engineer. */ "Reporting messages to moderators is prohibited." = "Az üzenetek jelentése a moderátorok felé le van tiltva."; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index 6474459604..d2fe0811ca 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -1,12 +1,3 @@ -/* No comment provided by engineer. */ -"\n" = "\n"; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" (" = " ("; - /* No comment provided by engineer. */ " (can be copied)" = " (può essere copiato)"; @@ -25,30 +16,15 @@ /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- messaggi vocali fino a 5 minuti.\n- tempo di scomparsa personalizzato.\n- cronologia delle modifiche."; -/* No comment provided by engineer. */ -", " = ", "; - -/* No comment provided by engineer. */ -": " = ": "; - /* No comment provided by engineer. */ "!1 colored!" = "!1 colorato!"; -/* No comment provided by engineer. */ -"." = "."; - -/* No comment provided by engineer. */ -"(" = "("; - /* No comment provided by engineer. */ "(new)" = "(nuovo)"; /* No comment provided by engineer. */ "(this device v%@)" = "(questo dispositivo v%@)"; -/* No comment provided by engineer. */ -")" = ")"; - /* No comment provided by engineer. */ "[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Contribuisci](https://github.com/simplex-chat/simplex-chat#contribute)"; @@ -58,12 +34,6 @@ /* No comment provided by engineer. */ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Dai una stella su GitHub](https://github.com/simplex-chat/simplex-chat)"; -/* No comment provided by engineer. */ -"@'%@'" = "@'%@'"; - -/* No comment provided by engineer. */ -"@%@" = "@%@"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Aggiungi contatto**: per creare un nuovo link di invito."; @@ -503,7 +473,7 @@ "All chats and messages will be deleted - this cannot be undone!" = "Tutte le chat e i messaggi verranno eliminati. Non è reversibile!"; /* alert message */ -"All chats will be removed from the list (text), and the list deleted." = "Tutte le chat verranno rimosse dall'elenco (testo) e l'elenco eliminato."; +"All chats will be removed from the list %@, and the list deleted." = "Tutte le chat verranno rimosse dall'elenco %@ e l'elenco eliminato."; /* No comment provided by engineer. */ "All data is erased when it is entered." = "Tutti i dati vengono cancellati quando inserito."; @@ -2434,7 +2404,7 @@ "File errors:\n%@" = "Errori di file:\n%@"; /* file error text */ -"File is blocked by server operator:\n(info.reason.text)." = "Il file è bloccato dall'operatore del server:\n(info.reason.text)."; +"File is blocked by server operator:\n%@." = "Il file è bloccato dall'operatore del server:\n%@."; /* file error text */ "File not found - most likely file was deleted or cancelled." = "File non trovato - probabilmente è stato eliminato o annullato."; @@ -4276,7 +4246,7 @@ "Report violation: only group moderators will see it." = "Segnala violazione: solo i moderatori del gruppo lo vedranno."; /* report in notification */ -"Report: (text.isEmpty ? reason.text : text)" = "Segnalazione: (text.isEmpty ? reason.text : text)"; +"Report: %@" = "Segnalazione: %@"; /* No comment provided by engineer. */ "Reporting messages to moderators is prohibited." = "È vietato segnalare messaggi ai moderatori."; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index b97da471b2..9ef8c02a0e 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -1,9 +1,3 @@ -/* No comment provided by engineer. */ -"\n" = "\n"; - -/* No comment provided by engineer. */ -" (" = " ("; - /* No comment provided by engineer. */ " (can be copied)" = " (コピー可能)"; @@ -22,30 +16,15 @@ /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- 最長 5 分間の音声メッセージ。\n- 消えるまでのカスタム時間。\n- 編集履歴。"; -/* No comment provided by engineer. */ -", " = ", "; - -/* No comment provided by engineer. */ -": " = ": "; - /* No comment provided by engineer. */ "!1 colored!" = "!1 色付き!"; -/* No comment provided by engineer. */ -"." = "."; - -/* No comment provided by engineer. */ -"(" = "("; - /* No comment provided by engineer. */ "(new)" = "(新規)"; /* No comment provided by engineer. */ "(this device v%@)" = "(このデバイス v%@)"; -/* No comment provided by engineer. */ -")" = ")"; - /* No comment provided by engineer. */ "[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[貢献する](https://github.com/simplex-chat/simplex-chat#contribute)"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index 1b2ab30025..e2bdd06018 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -1,12 +1,3 @@ -/* No comment provided by engineer. */ -"\n" = "\n"; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" (" = " ("; - /* No comment provided by engineer. */ " (can be copied)" = " (kan gekopieerd worden)"; @@ -25,30 +16,15 @@ /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- spraakberichten tot 5 minuten.\n- aangepaste tijd om te verdwijnen.\n- bewerkingsgeschiedenis."; -/* No comment provided by engineer. */ -", " = ", "; - -/* No comment provided by engineer. */ -": " = ": "; - /* No comment provided by engineer. */ "!1 colored!" = "!1 gekleurd!"; -/* No comment provided by engineer. */ -"." = "."; - -/* No comment provided by engineer. */ -"(" = "("; - /* No comment provided by engineer. */ "(new)" = "(nieuw)"; /* No comment provided by engineer. */ "(this device v%@)" = "(dit apparaat v%@)"; -/* No comment provided by engineer. */ -")" = ")"; - /* No comment provided by engineer. */ "[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Bijdragen](https://github.com/simplex-chat/simplex-chat#contribute)"; @@ -58,12 +34,6 @@ /* No comment provided by engineer. */ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)"; -/* No comment provided by engineer. */ -"@'%@'" = "@'%@'"; - -/* No comment provided by engineer. */ -"@%@" = "@%@"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Contact toevoegen**: om een nieuwe uitnodigingslink aan te maken, of verbinding te maken via een link die u heeft ontvangen."; @@ -503,7 +473,7 @@ "All chats and messages will be deleted - this cannot be undone!" = "Alle chats en berichten worden verwijderd, dit kan niet ongedaan worden gemaakt!"; /* alert message */ -"All chats will be removed from the list (text), and the list deleted." = "Alle chats worden uit de lijst (tekst) verwijderd en de lijst wordt verwijderd."; +"All chats will be removed from the list %@, and the list deleted." = "Alle chats worden uit de lijst %@ verwijderd en de lijst wordt verwijderd."; /* No comment provided by engineer. */ "All data is erased when it is entered." = "Alle gegevens worden bij het invoeren gewist."; @@ -2434,7 +2404,7 @@ "File errors:\n%@" = "Bestandsfouten:\n%@"; /* file error text */ -"File is blocked by server operator:\n(info.reason.text)." = "Bestand is geblokkeerd door serveroperator:\n(info.reason.text)."; +"File is blocked by server operator:\n%@." = "Bestand is geblokkeerd door serveroperator:\n%@."; /* file error text */ "File not found - most likely file was deleted or cancelled." = "Bestand niet gevonden - hoogstwaarschijnlijk is het bestand verwijderd of geannuleerd."; @@ -4267,7 +4237,7 @@ "Report violation: only group moderators will see it." = "Rapporteer overtreding: alleen groepsmoderators kunnen dit zien."; /* report in notification */ -"Report: (text.isEmpty ? reason.text : text)" = "Rapport: (text.isEmpty ? reason.text : text)"; +"Report: %@" = "Rapport: %@"; /* No comment provided by engineer. */ "Reporting messages to moderators is prohibited." = "Het is niet toegestaan om berichten aan moderators te melden."; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index 4c88e3dcaf..72a9a538c6 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -1,9 +1,3 @@ -/* No comment provided by engineer. */ -"\n" = "\n"; - -/* No comment provided by engineer. */ -" (" = " ("; - /* No comment provided by engineer. */ " (can be copied)" = " (można skopiować)"; @@ -22,30 +16,15 @@ /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- wiadomości głosowe do 5 minut.\n- niestandardowy czas zniknięcia.\n- historia edycji."; -/* No comment provided by engineer. */ -", " = ", "; - -/* No comment provided by engineer. */ -": " = ": "; - /* No comment provided by engineer. */ "!1 colored!" = "!1 pokolorowany!"; -/* No comment provided by engineer. */ -"." = "."; - -/* No comment provided by engineer. */ -"(" = "("; - /* No comment provided by engineer. */ "(new)" = "(nowy)"; /* No comment provided by engineer. */ "(this device v%@)" = "(to urządzenie v%@)"; -/* No comment provided by engineer. */ -")" = ")"; - /* No comment provided by engineer. */ "[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Przyczyń się](https://github.com/simplex-chat/simplex-chat#contribute)"; @@ -55,12 +34,6 @@ /* No comment provided by engineer. */ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Daj gwiazdkę na GitHub](https://github.com/simplex-chat/simplex-chat)"; -/* No comment provided by engineer. */ -"@'%@'" = "@'%@'"; - -/* No comment provided by engineer. */ -"@%@" = "@%@"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Dodaj kontakt**: aby utworzyć nowy link z zaproszeniem lub połączyć się za pomocą otrzymanego linku."; @@ -497,7 +470,7 @@ "All chats and messages will be deleted - this cannot be undone!" = "Wszystkie czaty i wiadomości zostaną usunięte - nie można tego cofnąć!"; /* alert message */ -"All chats will be removed from the list (text), and the list deleted." = "Wszystkie rozmowy zostaną usunięte z listy (text), a lista usunięta."; +"All chats will be removed from the list %@, and the list deleted." = "Wszystkie rozmowy zostaną usunięte z listy %@, a lista usunięta."; /* No comment provided by engineer. */ "All data is erased when it is entered." = "Wszystkie dane są usuwane po jego wprowadzeniu."; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index b63d59c644..c8b971e21d 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -1,12 +1,3 @@ -/* No comment provided by engineer. */ -"\n" = "\n"; - -/* No comment provided by engineer. */ -" " = " "; - -/* No comment provided by engineer. */ -" (" = " ("; - /* No comment provided by engineer. */ " (can be copied)" = " (можно скопировать)"; @@ -25,30 +16,15 @@ /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- голосовые сообщения до 5 минут.\n- настройка времени исчезающих сообщений.\n- история редактирования."; -/* No comment provided by engineer. */ -", " = ", "; - -/* No comment provided by engineer. */ -": " = ": "; - /* No comment provided by engineer. */ "!1 colored!" = "!1 цвет!"; -/* No comment provided by engineer. */ -"." = "."; - -/* No comment provided by engineer. */ -"(" = "("; - /* No comment provided by engineer. */ "(new)" = "(новое)"; /* No comment provided by engineer. */ "(this device v%@)" = "(это устройство v%@)"; -/* No comment provided by engineer. */ -")" = ")"; - /* No comment provided by engineer. */ "[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Внести свой вклад](https://github.com/simplex-chat/simplex-chat#contribute)"; @@ -58,12 +34,6 @@ /* No comment provided by engineer. */ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Поставить звездочку в GitHub](https://github.com/simplex-chat/simplex-chat)"; -/* No comment provided by engineer. */ -"@'%@'" = "@'%@'"; - -/* No comment provided by engineer. */ -"@%@" = "@%@"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Добавить контакт**: создать и поделиться новой ссылкой-приглашением."; @@ -503,7 +473,7 @@ "All chats and messages will be deleted - this cannot be undone!" = "Все чаты и сообщения будут удалены - это нельзя отменить!"; /* alert message */ -"All chats will be removed from the list (text), and the list deleted." = "Все чаты будут удалены из списка, а сам список удален."; +"All chats will be removed from the list %@, and the list deleted." = "Все чаты будут удалены из списка %@, и список удален."; /* No comment provided by engineer. */ "All data is erased when it is entered." = "Все данные удаляются при его вводе."; @@ -533,7 +503,7 @@ "All profiles" = "Все профили"; /* No comment provided by engineer. */ -"All reports will be archived for you." = "Все отчеты будут заархивированы для вас."; +"All reports will be archived for you." = "Все сообщения о нарушениях будут заархивированы для вас."; /* No comment provided by engineer. */ "All your contacts will remain connected." = "Все контакты, которые соединились через этот адрес, сохранятся."; @@ -581,7 +551,7 @@ "Allow to irreversibly delete sent messages. (24 hours)" = "Разрешить необратимо удалять отправленные сообщения. (24 часа)"; /* No comment provided by engineer. */ -"Allow to report messsages to moderators." = "Разрешить отправлять жалобы на сообщения модераторам."; +"Allow to report messsages to moderators." = "Разрешить отправлять сообщения о нарушениях модераторам."; /* No comment provided by engineer. */ "Allow to send files and media." = "Разрешить посылать файлы и медиа."; @@ -689,10 +659,10 @@ "Archive" = "Архивировать"; /* No comment provided by engineer. */ -"Archive %lld reports?" = "Архивировать %lld отчёт(ов)?"; +"Archive %lld reports?" = "Архивировать %lld сообщений о нарушениях?"; /* No comment provided by engineer. */ -"Archive all reports?" = "Архивировать все отчеты?"; +"Archive all reports?" = "Архивировать все сообщения о нарушениях?"; /* No comment provided by engineer. */ "Archive and upload" = "Архивировать и загрузить"; @@ -701,25 +671,25 @@ "Archive contacts to chat later." = "Архивируйте контакты чтобы продолжить переписку."; /* No comment provided by engineer. */ -"Archive report" = "Архивировать отчет"; +"Archive report" = "Архивировать сообщение о нарушении"; /* No comment provided by engineer. */ -"Archive report?" = "Архивировать отчет?"; +"Archive report?" = "Архивировать сообщение о нарушении?"; /* swipe action */ -"Archive reports" = "Архивировать отчёты"; +"Archive reports" = "Архивировать сообщения о нарушениях"; /* No comment provided by engineer. */ "Archived contacts" = "Архивированные контакты"; /* No comment provided by engineer. */ -"archived report" = "архивная жалоба"; +"archived report" = "заархивированное сообщение о нарушении"; /* No comment provided by engineer. */ "Archiving database" = "Подготовка архива"; /* No comment provided by engineer. */ -"Ask" = "спросить"; +"Ask" = "Спросить"; /* No comment provided by engineer. */ "Attach" = "Прикрепить"; @@ -812,7 +782,7 @@ "Better notifications" = "Улучшенные уведомления"; /* No comment provided by engineer. */ -"Better privacy and security" = "Улучшенная приватность и безопасность"; +"Better privacy and security" = "Улучшенная конфиденциальность и безопасность"; /* No comment provided by engineer. */ "Better security ✅" = "Улучшенная безопасность ✅"; @@ -887,7 +857,7 @@ "Business chats" = "Бизнес разговоры"; /* No comment provided by engineer. */ -"Businesses" = "Предприятия"; +"Businesses" = "Бизнесы"; /* 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)." = "По профилю чата или [по соединению](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (БЕТА)."; @@ -1132,7 +1102,7 @@ "colored" = "цвет"; /* report reason */ -"Community guidelines violation" = "Нарушение принципов Сообщества"; +"Community guidelines violation" = "Нарушение правил группы"; /* server test step */ "Compare file" = "Сравнение файла"; @@ -1713,7 +1683,7 @@ "Delete queue" = "Удаление очереди"; /* No comment provided by engineer. */ -"Delete report" = "удалить отчёт"; +"Delete report" = "Удалить сообщение о нарушении"; /* No comment provided by engineer. */ "Delete up to 20 messages at once." = "Удаляйте до 20 сообщений за раз."; @@ -2194,7 +2164,7 @@ "Error creating profile!" = "Ошибка создания профиля!"; /* No comment provided by engineer. */ -"Error creating report" = "Ошибка создания отчета"; +"Error creating report" = "Ошибка создания сообщения о нарушении"; /* No comment provided by engineer. */ "Error decrypting file" = "Ошибка расшифровки файла"; @@ -2269,7 +2239,7 @@ "Error removing member" = "Ошибка при удалении члена группы"; /* alert title */ -"Error reordering lists" = "Ошибка переупорядочивания списков"; +"Error reordering lists" = "Ошибка сортировки списков"; /* No comment provided by engineer. */ "Error resetting statistics" = "Ошибка сброса статистики"; @@ -2434,7 +2404,7 @@ "File errors:\n%@" = "Ошибки файлов:\n%@"; /* file error text */ -"File is blocked by server operator:\n(info.reason.text)." = "Файл заблокирован оператором сервера"; +"File is blocked by server operator:\n%@." = "Файл заблокирован оператором сервера:\n%@."; /* file error text */ "File not found - most likely file was deleted or cancelled." = "Файл не найден - скорее всего, файл был удален или отменен."; @@ -2602,7 +2572,7 @@ "Further reduced battery usage" = "Уменьшенное потребление батареи"; /* No comment provided by engineer. */ -"Get notified when mentioned." = "Получайте уведомления от упоминаний."; +"Get notified when mentioned." = "Уведомления, когда Вас упомянули."; /* No comment provided by engineer. */ "GIFs and stickers" = "ГИФ файлы и стикеры"; @@ -2812,10 +2782,10 @@ "inactive" = "неактивен"; /* report reason */ -"Inappropriate content" = "Нежелательный контент"; +"Inappropriate content" = "Неприемлемый контент"; /* report reason */ -"Inappropriate profile" = "нежелательный профиль"; +"Inappropriate profile" = "Неприемлемый профиль"; /* No comment provided by engineer. */ "Incognito" = "Инкогнито"; @@ -3178,7 +3148,7 @@ "Member inactive" = "Член неактивен"; /* chat feature */ -"Member reports" = "Жалобы на участника"; +"Member reports" = "Сообщения о нарушениях"; /* No comment provided by engineer. */ "Member role will be changed to \"%@\". All chat members will be notified." = "Роль участника будет изменена на \"%@\". Все участники разговора получат уведомление."; @@ -3202,7 +3172,7 @@ "Members can irreversibly delete sent messages. (24 hours)" = "Члены группы могут необратимо удалять отправленные сообщения. (24 часа)"; /* No comment provided by engineer. */ -"Members can report messsages to moderators." = "Участники могут пожаловаться на сообщения администраторам."; +"Members can report messsages to moderators." = "Члены группы могут пожаловаться модераторам."; /* No comment provided by engineer. */ "Members can send direct messages." = "Члены группы могут посылать прямые сообщения."; @@ -3220,7 +3190,7 @@ "Members can send voice messages." = "Члены группы могут отправлять голосовые сообщения."; /* No comment provided by engineer. */ -"Mention members 👋" = "Упоминайте участников 👋"; +"Mention members 👋" = "Упоминайте членов группы 👋"; /* No comment provided by engineer. */ "Menus" = "Меню"; @@ -3397,7 +3367,7 @@ "Mute" = "Без звука"; /* notification label action */ -"Mute all" = "Выключить уведомления для всех"; +"Mute all" = "Все без звука"; /* No comment provided by engineer. */ "Muted when inactive!" = "Без звука, когда не активный!"; @@ -3496,13 +3466,13 @@ "No app password" = "Нет кода доступа"; /* No comment provided by engineer. */ -"No chats" = "Никаких чатов"; +"No chats" = "Нет чатов"; /* No comment provided by engineer. */ "No chats found" = "Чаты не найдены"; /* No comment provided by engineer. */ -"No chats in list %@" = "Никаких чатов в списке %@"; +"No chats in list %@" = "Нет чатов в списке %@"; /* No comment provided by engineer. */ "No contacts selected" = "Контакты не выбраны"; @@ -3580,7 +3550,7 @@ "No token!" = "Нет токена!"; /* No comment provided by engineer. */ -"No unread chats" = "Без непрочитанных чатов"; +"No unread chats" = "Нет непрочитанных чатов"; /* No comment provided by engineer. */ "No user identifiers." = "Без идентификаторов пользователей."; @@ -3589,7 +3559,7 @@ "Not compatible!" = "Несовместимая версия!"; /* No comment provided by engineer. */ -"Notes" = "Примечания"; +"Notes" = "Заметки"; /* No comment provided by engineer. */ "Nothing selected" = "Ничего не выбрано"; @@ -3831,10 +3801,10 @@ "peer-to-peer" = "peer-to-peer"; /* No comment provided by engineer. */ -"pending" = "на рассмотрении"; +"pending" = "ожидает"; /* No comment provided by engineer. */ -"Pending" = "В ожидании"; +"Pending" = "Ожидает"; /* No comment provided by engineer. */ "pending approval" = "ожидает утверждения"; @@ -3906,7 +3876,7 @@ "Please store passphrase securely, you will NOT be able to change it if you lose it." = "Пожалуйста, надежно сохраните пароль, Вы НЕ сможете его поменять, если потеряете."; /* token info */ -"Please try to disable and re-enable notfications." = "Попробуйте отключить и снова включить уведомления."; +"Please try to disable and re-enable notfications." = "Попробуйте выключить и снова включить уведомления."; /* token info */ "Please wait for token activation to complete." = "Пожалуйста, дождитесь завершения активации токена."; @@ -3951,7 +3921,7 @@ "Private filenames" = "Защищенные имена файлов"; /* No comment provided by engineer. */ -"Private media file names." = "Приватные названия медиафайлов."; +"Private media file names." = "Конфиденциальные названия медиафайлов."; /* No comment provided by engineer. */ "Private message routing" = "Конфиденциальная доставка сообщений"; @@ -3999,7 +3969,7 @@ "Prohibit messages reactions." = "Запретить реакции на сообщения."; /* No comment provided by engineer. */ -"Prohibit reporting messages to moderators." = "Запретить отправлять жалобы на сообщения модераторам."; +"Prohibit reporting messages to moderators." = "Запретить жаловаться модераторам группы."; /* No comment provided by engineer. */ "Prohibit sending direct messages to members." = "Запретить посылать прямые сообщения членам группы."; @@ -4258,31 +4228,31 @@ "Report" = "Пожаловаться"; /* report reason */ -"Report content: only group moderators will see it." = "Содержание жалобы: его увидят только групповые модераторы."; +"Report content: only group moderators will see it." = "Пожаловаться на сообщение: увидят только модераторы группы."; /* report reason */ -"Report member profile: only group moderators will see it." = "Жалоба на профиль участника: его увидят только групповые модераторы."; +"Report member profile: only group moderators will see it." = "Пожаловаться на профиль: увидят только модераторы группы."; /* report reason */ -"Report other: only group moderators will see it." = "Другая жалоба: её увидят только модераторы."; +"Report other: only group moderators will see it." = "Пожаловаться: увидят только модераторы группы."; /* No comment provided by engineer. */ -"Report reason?" = "Причина жалобы?"; +"Report reason?" = "Причина сообщения?"; /* report reason */ -"Report spam: only group moderators will see it." = "Пожаловаться на спам: это увидит только модератор группы."; +"Report spam: only group moderators will see it." = "Пожаловаться на спам: увидят только модераторы группы."; /* report reason */ -"Report violation: only group moderators will see it." = "Пожаловаться на нарушение: это увидит только модератор группы."; +"Report violation: only group moderators will see it." = "Пожаловаться на нарушение: увидят только модераторы группы."; /* report in notification */ -"Report: (text.isEmpty ? reason.text : text)" = "Жалоба"; +"Report: %@" = "Сообщение о нарушении: %@"; /* No comment provided by engineer. */ -"Reporting messages to moderators is prohibited." = "Жалобы на сообщения запрещены в этой группе."; +"Reporting messages to moderators is prohibited." = "Сообщения о нарушениях запрещены в этой группе."; /* No comment provided by engineer. */ -"Reports" = "Жалобы"; +"Reports" = "Сообщения о нарушениях"; /* chat list item title */ "requested to connect" = "запрошено соединение"; @@ -4559,7 +4529,7 @@ "Send notifications" = "Отправлять уведомления"; /* No comment provided by engineer. */ -"Send private reports" = "Отправляйте приватные жалобы"; +"Send private reports" = "Вы можете сообщить о нарушениях"; /* No comment provided by engineer. */ "Send questions and ideas" = "Отправьте вопросы и идеи"; @@ -4694,7 +4664,7 @@ "Set 1 day" = "Установить 1 день"; /* No comment provided by engineer. */ -"Set chat name…" = "Установить имя чата…"; +"Set chat name…" = "Имя чата…"; /* No comment provided by engineer. */ "Set contact name…" = "Имя контакта…"; @@ -4709,7 +4679,7 @@ "Set it instead of system authentication." = "Установите код вместо системной аутентификации."; /* No comment provided by engineer. */ -"Set message expiration in chats." = "Установите время исчезания сообщений в чатах."; +"Set message expiration in chats." = "Установите срок хранения сообщений в чатах."; /* profile update event chat item */ "set new contact address" = "установлен новый адрес контакта"; @@ -5041,7 +5011,7 @@ "TCP connection timeout" = "Таймаут TCP соединения"; /* No comment provided by engineer. */ -"TCP port for messaging" = "TCP-порт для обмена сообщениями"; +"TCP port for messaging" = "TCP-порт для отправки сообщений"; /* No comment provided by engineer. */ "TCP_KEEPCNT" = "TCP_KEEPCNT"; @@ -5182,7 +5152,7 @@ "This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "Это действие нельзя отменить — все сообщения, отправленные или полученные раньше чем выбрано, будут удалены. Это может занять несколько минут."; /* alert message */ -"This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted." = "Это действие не может быть отмененено - сообщения, отправленные и полученные в этом чате ранее чем выбранное, будут удалены."; +"This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted." = "Это действие нельзя отменить - сообщения в этом чате, отправленные или полученные раньше чем выбрано, будут удалены."; /* No comment provided by engineer. */ "This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Это действие нельзя отменить — Ваш профиль, контакты, сообщения и файлы будут безвозвратно утеряны."; @@ -5506,7 +5476,7 @@ "Use SOCKS proxy" = "Использовать SOCKS прокси"; /* No comment provided by engineer. */ -"Use TCP port %@ when no port is specified." = "Использовать TCP-порт %@, когда не указано ни одного порта."; +"Use TCP port %@ when no port is specified." = "Использовать TCP-порт %@, когда порт не указан."; /* No comment provided by engineer. */ "Use the app while in the call." = "Используйте приложение во время звонка."; diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index 0a3564fe91..abe1632645 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -1,9 +1,3 @@ -/* No comment provided by engineer. */ -"\n" = "\n"; - -/* No comment provided by engineer. */ -" (" = " ("; - /* No comment provided by engineer. */ " (can be copied)" = " (สามารถคัดลอกได้)"; @@ -16,24 +10,9 @@ /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- ข้อความเสียงนานสุด 5 นาที\n- เวลาที่กำหนดเองที่จะหายไป\n- ประวัติการแก้ไข"; -/* No comment provided by engineer. */ -", " = ", "; - -/* No comment provided by engineer. */ -": " = ": "; - /* No comment provided by engineer. */ "!1 colored!" = "!1 มีสี!"; -/* No comment provided by engineer. */ -"." = "."; - -/* No comment provided by engineer. */ -"(" = "("; - -/* No comment provided by engineer. */ -")" = ")"; - /* No comment provided by engineer. */ "[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[มีส่วนร่วม](https://github.com/simplex-chat/simplex-chat#contribute)"; diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index dd0d7c6217..88b2ac401e 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -1,9 +1,3 @@ -/* No comment provided by engineer. */ -"\n" = "\n"; - -/* No comment provided by engineer. */ -" (" = " ("; - /* No comment provided by engineer. */ " (can be copied)" = " (kopyalanabilir)"; @@ -22,30 +16,15 @@ /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- 5 dakikaya kadar süren sesli mesajlar.\n- mesaj kaybolması için özel zaman.\n- düzenleme geçmişi."; -/* No comment provided by engineer. */ -", " = ", "; - -/* No comment provided by engineer. */ -": " = ": "; - /* No comment provided by engineer. */ "!1 colored!" = "!1 renklendirilmiş!"; -/* No comment provided by engineer. */ -"." = "."; - -/* No comment provided by engineer. */ -"(" = "("; - /* No comment provided by engineer. */ "(new)" = "(yeni)"; /* No comment provided by engineer. */ "(this device v%@)" = "(bu cihaz v%@)"; -/* No comment provided by engineer. */ -")" = ")"; - /* No comment provided by engineer. */ "[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Katkıda bulun](https://github.com/simplex-chat/simplex-chat#contribute)"; diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index f750d1ac8b..ceb8ee0bfa 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -1,9 +1,3 @@ -/* No comment provided by engineer. */ -"\n" = "\n"; - -/* No comment provided by engineer. */ -" (" = " ("; - /* No comment provided by engineer. */ " (can be copied)" = " (можна скопіювати)"; @@ -22,30 +16,15 @@ /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- голосові повідомлення до 5 хвилин.\n- користувальницький час зникнення.\n- історія редагування."; -/* No comment provided by engineer. */ -", " = ", "; - -/* No comment provided by engineer. */ -": " = ": "; - /* No comment provided by engineer. */ "!1 colored!" = "!1 кольоровий!"; -/* No comment provided by engineer. */ -"." = "."; - -/* No comment provided by engineer. */ -"(" = "("; - /* No comment provided by engineer. */ "(new)" = "(новий)"; /* No comment provided by engineer. */ "(this device v%@)" = "(цей пристрій v%@)"; -/* No comment provided by engineer. */ -")" = ")"; - /* No comment provided by engineer. */ "[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Внесок](https://github.com/simplex-chat/simplex-chat#contribute)"; diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index 68ca7a839f..3ed0d0fa14 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -1,9 +1,3 @@ -/* No comment provided by engineer. */ -"\n" = "\n"; - -/* No comment provided by engineer. */ -" (" = " ("; - /* No comment provided by engineer. */ " (can be copied)" = " (可复制)"; @@ -22,30 +16,15 @@ /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- 语音消息最长5分钟。\n- 自定义限时消息。\n- 编辑消息历史。"; -/* No comment provided by engineer. */ -", " = ", "; - -/* No comment provided by engineer. */ -": " = ": "; - /* No comment provided by engineer. */ "!1 colored!" = "!1 种彩色!"; -/* No comment provided by engineer. */ -"." = "."; - -/* No comment provided by engineer. */ -"(" = "("; - /* No comment provided by engineer. */ "(new)" = "(新)"; /* No comment provided by engineer. */ "(this device v%@)" = "(此设备 v%@)"; -/* No comment provided by engineer. */ -")" = ")"; - /* No comment provided by engineer. */ "[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[贡献](https://github.com/simplex-chat/simplex-chat#contribute)"; diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index a9d5a4b60b..20b851bad7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -2282,7 +2282,6 @@ سبب آخر البلاغات 1 بلاغ - بلاغات الأعضاء المؤرشفة %d بلاغات بلاغات الأعضاء بلّغ عن المحتوى: سيراه مشرفو المجموعة فقط. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index e5d3384f9b..5e8d22cb99 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -460,7 +460,6 @@ 1 report %d reports Member reports - Archived member reports Share message… diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml index 0d071e4cce..f2a18050c3 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml @@ -2269,7 +2269,6 @@ El fitxer està blocat per l\'operador del servidor:\n%1$s. Motiu de l\'informe? 1 informe - Informes de membres arxivats %d informes Informes de membres Informes diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml index 19cf9c440d..9d13f4f262 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml @@ -2175,7 +2175,6 @@ Chyba aktualizace serveru Operátor serveru Decentralizace sítě - Archivována hlášení členů Archivovat hlášení Opustit chat? Nastavení adres diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index a28477d524..551b6caa7f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -2382,7 +2382,6 @@ %d Meldungen Mitglieder-Meldungen Meldungen - Archivierte Mitglieder-Meldungen Inhalt verletzt Nutzungsbedingungen Spam Verbindung blockiert diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index 3c9ed3e5bf..ad90b97d38 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -2304,7 +2304,6 @@ Archivar Archivar informe Informes de miembro - Informes de miembros archivados %d informes Informar del perfil de un miembro: sólo los moderadores del grupo lo verán. Otro motivo diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml index 6e23eca6b6..dd5ce1dc6a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml @@ -2124,7 +2124,6 @@ invitation acceptée Archiver le signalement Demander - Rapports du membre archivés Ajouter à la liste Toutes les discussions seront supprimées de la liste %s, et la liste sera supprimée Ajouter des membres à l\'équipe diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index ddcc232717..4449fbed58 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -2274,7 +2274,6 @@ Jelentések %s által archivált jelentés %d jelentés - Tagok archivált jelentései Kéretlen tartalom A tartalom sérti a használati feltételeket A kapcsolat le van tiltva diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml index 14fb1ad156..835b21db45 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml @@ -2250,7 +2250,6 @@ Semua 1 laporan Arsip - Laporan anggota arsip Alasan lain laporan arsip oleh %s Arsip laporan diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index b474e6c130..d9b207f27a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -2307,7 +2307,6 @@ Archivia la segnalazione Elimina la segnalazione Segnalazioni - Segnalazioni dei membri archiviate 1 segnalazione segnalazione archiviata da %s %d segnalazioni diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml index f69ec96d7c..651d32518f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml @@ -1496,7 +1496,6 @@ 1 보고서 리스트 추가 전부 - 보관된 멤버 리포트 목록 %s의 모든 차트가 제거되었고, 목록도 삭제되었습니다. 1 년 리스트에 추가하기 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index 997979d039..686b1959d5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -2305,7 +2305,6 @@ Rapport verwijderen gearchiveerd rapport door %s 1 rapport - Gearchiveerde ledenrapporten %d rapporten Ledenrapporten Inhoud schendt de gebruiksvoorwaarden diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml index 8d31873b2d..5102c98563 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml @@ -2131,7 +2131,6 @@ Wszystkie Biznesy raport - Zarchiwizowane raporty członków Zapytaj %s.]]> %s.]]> diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml index 63b6b202d9..184548e0da 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml @@ -2123,7 +2123,6 @@ Arquivar Perguntar Desfoque - Denúncias arquivadas de membros Endereço comercial denúncia arquivada Deletar chat diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml index 881b726a51..f8f356614b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -754,7 +754,7 @@ Вы пытаетесь пригласить инкогнито контакт в группу, где Вы используете свой основной профиль Пригласить членов группы - %1$s УЧАСНИКОВ + %1$s ЧЛЕНОВ ГРУППЫ Вы: %1$s Удалить группу Удалить группу? @@ -2296,8 +2296,8 @@ Серверы по умолчанию Роль будет изменена на %s. Все участники разговора получат уведомление. Ваш профиль будет отправлен участникам разговора. - %s.]]> - %s.]]> + %s.]]> + %s.]]> Условия использования Дополнительные серверы файлов и медиа Ошибка сохранения сервера @@ -2367,16 +2367,16 @@ Название списка и эмодзи должны быть разными для всех списков. Пожаловаться Спам - Пожаловаться на спам: это увидит только модератор группы + Пожаловаться на спам: увидят только модераторы группы. Это действие не может быть отмененено - сообщения, отправленные и полученные в этом чате ранее чем выбранное, будут удалены Получайте уведомления от упоминаний. - Жалобы на сообщения запрещены в этой группе. - Пожаловаться на нарушение: это увидит только модератор группы + Сообщения о нарушениях запрещены в этой группе. + Пожаловаться на нарушение: увидят только модераторы группы. Установить имя чата… Улучшенная производительность групп Приватные названия медиафайлов. Спам - Жалобы + Сообщения о нарушениях Непрочитанные упоминания Да Упоминайте участников 👋 @@ -2385,78 +2385,77 @@ Ускорена отправка сообщений. Помогайте администраторам модерировать их группы. Организуйте чаты в списки - Отправляйте приватные жалобы + Вы можете сообщить о нарушениях Установите время исчезания сообщений в чатах. Вы можете упомянуть до %1$s пользователей в одном сообщении! - Причина жалобы? + Причина сообщения? Эта жалоба будет архивирована для вас. - Разрешить отправлять жалобы на сообщения модераторам. + Разрешить отправлять сообщения о нарушениях модераторам. Содержание нарушает условия использования Ошибка чтения пароля базы данных - архивная жалоба %s - Нарушение принципов Сообщества - Нежелательный контент - Еще одна причина - нежелательный профиль - Архивные отчеты участников - %d отчёты - Ошибка создания отчета + сообщение о нарушении заархивировано %s + Нарушение правил группы + Неприемлемое сообщение + Другая причина + Неприемлемый профиль + %d сообщений о нарушениях + Ошибка создания сообщения о нарушении Соединение заблокировано Соединение заблокировано сервером оператора:\n%1$s. - спросить + Спросить Отключить автоматическое удаление сообщений? Удалить сообщения с вашего устройства. Отключить удаление сообщений по умолчанию (%s) - Все отчеты будут заархивированы для вас. - Архивировать все отчеты? - Архивировать %d отчёт? + Все сообщения о нарушениях будут заархивированы для вас. + Архивировать все сообщения о нарушениях? + Архивировать %d сообщений о нарушениях? Для меня - Архивировать жалобу - Архивировать отчёты - удалить отчёт + Архивировать сообщение о нарушении + Архивировать сообщения о нарушениях + Удалить сообщение о нарушении Файл заблокирован оператором сервера:\n%1$s. Для всех модераторов - 1 жалоба + 1 сообщение о нарушении Измененить автоматическое удаление сообщений? 1 год Не пропустите важные сообщения. Ошибка сохранения настроек - архивная жалоба - архив - Архивировать жалобу? - Парольная фраза в хранилище ключей не может быть прочитана. Это могло произойти после обновления системы, несовместимого с приложением. Если это не так, обратитесь к разработчикам. - Парольная фраза в хранилище не читается, пожалуйста, введите ее вручную. Это могло произойти после обновления системы, несовместимого с приложением. Если это не так, обратитесь к разработчикам. + заархивированное сообщение о нарушении + архивировать + Архивировать сообщение о нарушении? + Пароль не может быть прочитан из Keystore. Это могло произойти после обновления системы, несовместимого с приложением. Если это не так, обратитесь к разработчикам. + Пароль не может быть прочитан из Keystore, пожалуйста, введите его. Это могло произойти после обновления системы, несовместимого с приложением. Если это не так, обратитесь к разработчикам. модератор ожидает утверждения - на рассмотрении + ожидает Обновленные условия - Запретить отправлять жалобы на сообщения модераторам. - Участники могут пожаловаться на сообщения администраторам. + Запретить жаловаться модераторам группы. + Члены группы могут пожаловаться модераторам. Сообщения в этом чате никогда не будут удалены. Открыть ссылку из списка чатов Открыть веб-ссылку? - Жалоба на профиль участника: его увидят только групповые модераторы. - Жалобы на участника - Другая жалоба: её увидят только модераторы. + Пожаловаться на профиль: увидят только модераторы группы. + Сообщения о нарушениях + Пожаловаться: увидят только модераторы группы. Выключить уведомления для всех - Использовать TCP-порт %1$s, когда не указано ни одного порта. + Использовать TCP-порт %1$s, когда порт не указан. Использовать веб-порт Нет - Содержание жалобы: его увидят только групповые модераторы. + Пожаловаться на сообщение: увидят только модераторы группы. отклонён - Жалоба: %s - TCP-порт для обмена сообщениями + Сообщение о нарушении: %s + TCP-порт для отправки сообщений Открыть ссылку отклонён Только отправитель и модераторы видят это Только вы и модераторы видят это Разблокировать членов для всех? - Сообщения от этих членов будут показаны! + Сообщения от этих членов группы будут показаны! Все новые сообщения от этих членов группы будут скрыты! Заблокировать членов для всех? Члены группы будут удалены - это действие нельзя отменить! - Члены будут удалены из разговора - это действие нельзя отменить! + Участники будут удалены из разговора - это действие нельзя отменить! модераторы Удалить членов группы? diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml index 62d3764940..c721c95663 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml @@ -2143,7 +2143,6 @@ Raporu arşivle %s tarafından arşivlenen rapor 1 rapor - Arşivlenmiş üye raporları %s.]]> Sor 1 yıl diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index 5fd75918a9..ce6d5fbfa8 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -2308,7 +2308,6 @@ %d повідомлень Повідомлення учасників Повідомлення - Архівовані повідомлення учасників Вміст порушує умови використання Спам Файл заблоковано оператором сервера: \n%1$s. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml index cdc9721282..b7b8248b10 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml @@ -1869,7 +1869,6 @@ 1 báo cáo %d báo cáo Các báo cáo của thành viên - Các báo cáo thành viên đã được lưu trữ Lỗi đăng ký dài hạn Các báo cáo Bắt đầu từ %s.\nTất cả dữ liệu được lưu trữ một cách riêng tư trên thiết bị của bạn. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index ba5a4d3555..67faae4943 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -2294,7 +2294,6 @@ 举报 1 个举报 成员举报 - 已存档的成员举报 %d 个举报 垃圾信息 连接被阻止 From 430e212a9eb5a54991b2595dc614a8604856028d Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 7 Mar 2025 18:38:06 +0400 Subject: [PATCH 462/567] core: name limit (#5724) * core: name limit * ios * trim spaces, test --- apps/ios/Shared/Views/Onboarding/CreateProfile.swift | 1 + src/Simplex/Chat/Library/Commands.hs | 4 ++-- tests/ValidNames.hs | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift index 409cb859ea..53cf73f1c9 100644 --- a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift +++ b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift @@ -145,6 +145,7 @@ struct CreateFirstProfile: View { TextField("Enter your name…", text: $displayName) .focused($focusDisplayName) .padding(.horizontal) + .padding(.trailing, 20) .padding(.vertical, 10) .background( RoundedRectangle(cornerRadius: 10, style: .continuous) diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 6a5775beb7..54d3cd9143 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -37,7 +37,7 @@ import Data.Either (fromRight, partitionEithers, rights) import Data.Foldable (foldr') import Data.Functor (($>)) import Data.Int (Int64) -import Data.List (find, foldl', isSuffixOf, partition, sortOn, zipWith4) +import Data.List (dropWhileEnd, find, foldl', isSuffixOf, partition, sortOn, zipWith4) import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.List.NonEmpty as L import Data.Map.Strict (Map) @@ -4334,7 +4334,7 @@ displayNameP = safeDecodeUtf8 <$> (quoted '\'' <|> takeNameTill (\c -> isSpace c refChar c = c > ' ' && c /= '#' && c /= '@' && c /= '\'' mkValidName :: String -> String -mkValidName = reverse . dropWhile isSpace . fst3 . foldl' addChar ("", '\NUL', 0 :: Int) +mkValidName = dropWhileEnd isSpace . take 50 . reverse . fst3 . foldl' addChar ("", '\NUL', 0 :: Int) where fst3 (x, _, _) = x addChar (r, prev, punct) c = if validChar then (c' : r, c', punct') else (r, prev, punct) diff --git a/tests/ValidNames.hs b/tests/ValidNames.hs index 03089b1721..22ac4a695d 100644 --- a/tests/ValidNames.hs +++ b/tests/ValidNames.hs @@ -38,3 +38,5 @@ testMkValidName = do mkValidName "alice@example.com" `shouldBe` "alice@example.com" mkValidName "alice <> bob" `shouldBe` "alice <> bob" mkValidName "alice -> bob" `shouldBe` "alice -> bob" + mkValidName "01234567890123456789012345678901234567890123456789extra" `shouldBe` "01234567890123456789012345678901234567890123456789" + mkValidName "0123456789012345678901234567890123456789012345678 extra" `shouldBe` "0123456789012345678901234567890123456789012345678" From e2d488266c51c8d68d2ba6bada0a88a872421b07 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 7 Mar 2025 14:59:00 +0000 Subject: [PATCH 463/567] core: 6.3.0.8 (simplexmq 6.3.0.8) --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- src/Simplex/Chat/Remote.hs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cabal.project b/cabal.project index 477ba9c601..2e1b43d41f 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 9fece9ce3df24d1b006d98e44a6b4e654861428b + tag: a491a1d8780054432542611f540317a6090b9360 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 1a9de205f3..53ccda920f 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."9fece9ce3df24d1b006d98e44a6b4e654861428b" = "0vf4kkj2jc4bg3bc9vsl0gv2z0l7z48626sqlk5wh7il6k14pn17"; + "https://github.com/simplex-chat/simplexmq.git"."a491a1d8780054432542611f540317a6090b9360" = "183wmraa25rxcf3b07apimsdvamccc3qx3p5rr726qzvpkvrxpab"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 950b689234..bbdd766a7b 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.3.0.7 +version: 6.3.0.8 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index 3e64b2bcea..a7f44eb465 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -75,11 +75,11 @@ remoteFilesFolder = "simplex_v1_files" -- when acting as host minRemoteCtrlVersion :: AppVersion -minRemoteCtrlVersion = AppVersion [6, 3, 0, 7] +minRemoteCtrlVersion = AppVersion [6, 3, 0, 8] -- when acting as controller minRemoteHostVersion :: AppVersion -minRemoteHostVersion = AppVersion [6, 3, 0, 7] +minRemoteHostVersion = AppVersion [6, 3, 0, 8] currentAppVersion :: AppVersion currentAppVersion = AppVersion SC.version From 27f63dafaa8ca1e232d5def60954bbccecf6d5cc Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 7 Mar 2025 22:08:00 +0700 Subject: [PATCH 464/567] ui: option to remove messages of removed members (#5717) * ui: removing messages of removed members * android * fix android * fix ios and refactor * refactor android * update * update2 * remove ts * android new logic * refactor * remove spaghetti * if * android --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Model/ChatModel.swift | 39 ++++++++++++++++ apps/ios/Shared/Model/SimpleXAPI.swift | 16 +++++-- apps/ios/SimpleXChat/APITypes.swift | 16 +++---- .../chat/simplex/common/model/ChatModel.kt | 46 ++++++++++++++++++- .../chat/simplex/common/model/SimpleXAPI.kt | 34 ++++++++++---- 5 files changed, 126 insertions(+), 25 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index a1c5a55c3b..23b167a9ff 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -624,6 +624,45 @@ final class ChatModel: ObservableObject { VoiceItemState.stopVoiceInChatView(cInfo, cItem) } + func removeMemberItems(_ removedMember: GroupMember, byMember: GroupMember, _ groupInfo: GroupInfo) { + // this should not happen, only another member can "remove" user, user can only "leave" (another event). + if byMember.groupMemberId == groupInfo.membership.groupMemberId { + logger.debug("exiting removeMemberItems") + return + } + if chatId == groupInfo.id { + for i in 0.. 0, + let updatedItem = removedUpdatedItem(chat.chatItems[0]) { + chat.chatItems = [updatedItem] + } + + func removedUpdatedItem(_ item: ChatItem) -> ChatItem? { + let newContent: CIContent + if case .groupSnd = item.chatDir, removedMember.groupMemberId == groupInfo.membership.groupMemberId { + newContent = .sndModerated + } else if case let .groupRcv(groupMember) = item.chatDir, groupMember.groupMemberId == removedMember.groupMemberId { + newContent = .rcvModerated + } else { + return nil + } + var updatedItem = item + updatedItem.meta.itemDeleted = .moderated(deletedTs: Date.now, byGroupMember: byMember) + if groupInfo.fullGroupPreferences.fullDelete.on { + updatedItem.content = newContent + } + if item.isActiveReport { + decreaseGroupReportsCounter(groupInfo.id) + } + return updatedItem + } + } + func nextChatItemData(_ chatItemId: Int64, previous: Bool, map: @escaping (ChatItem) -> T?) -> T? { guard var i = im.reversedChatItems.firstIndex(where: { $0.id == chatItemId }) else { return nil } if previous { diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 875a1f27fa..495209499c 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1588,9 +1588,9 @@ func apiJoinGroup(_ groupId: Int64) async throws -> JoinGroupResult { } } -func apiRemoveMembers(_ groupId: Int64, _ memberIds: [Int64]) async throws -> [GroupMember] { - let r = await chatSendCmd(.apiRemoveMembers(groupId: groupId, memberIds: memberIds), bgTask: false) - if case let .userDeletedMembers(_, _, members) = r { return members } +func apiRemoveMembers(_ groupId: Int64, _ memberIds: [Int64], _ withMessages: Bool = false) async throws -> [GroupMember] { + let r = await chatSendCmd(.apiRemoveMembers(groupId: groupId, memberIds: memberIds, withMessages: withMessages), bgTask: false) + if case let .userDeletedMembers(_, _, members, withMessages) = r { return members } throw r } @@ -2187,16 +2187,22 @@ func processReceivedMsg(_ res: ChatResponse) async { _ = m.upsertGroupMember(groupInfo, member) } } - case let .deletedMemberUser(user, groupInfo, _): // TODO update user member + case let .deletedMemberUser(user, groupInfo, member, withMessages): // TODO update user member if active(user) { await MainActor.run { m.updateGroup(groupInfo) + if withMessages { + m.removeMemberItems(groupInfo.membership, byMember: member, groupInfo) + } } } - case let .deletedMember(user, groupInfo, _, deletedMember): + case let .deletedMember(user, groupInfo, byMember, deletedMember, withMessages): if active(user) { await MainActor.run { _ = m.upsertGroupMember(groupInfo, deletedMember) + if withMessages { + m.removeMemberItems(deletedMember, byMember: byMember, groupInfo) + } } } case let .leftMember(user, groupInfo, member): diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 20c8785b3a..6db0478ab3 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -73,7 +73,7 @@ public enum ChatCommand { case apiJoinGroup(groupId: Int64) case apiMembersRole(groupId: Int64, memberIds: [Int64], memberRole: GroupMemberRole) case apiBlockMembersForAll(groupId: Int64, memberIds: [Int64], blocked: Bool) - case apiRemoveMembers(groupId: Int64, memberIds: [Int64]) + case apiRemoveMembers(groupId: Int64, memberIds: [Int64], withMessages: Bool) case apiLeaveGroup(groupId: Int64) case apiListMembers(groupId: Int64) case apiUpdateGroupProfile(groupId: Int64, groupProfile: GroupProfile) @@ -252,7 +252,7 @@ public enum ChatCommand { case let .apiJoinGroup(groupId): return "/_join #\(groupId)" case let .apiMembersRole(groupId, memberIds, memberRole): return "/_member role #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ",")) \(memberRole.rawValue)" case let .apiBlockMembersForAll(groupId, memberIds, blocked): return "/_block #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ",")) blocked=\(onOff(blocked))" - case let .apiRemoveMembers(groupId, memberIds): return "/_remove #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ","))" + case let .apiRemoveMembers(groupId, memberIds, withMessages): return "/_remove #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ",")) messages=\(onOff(withMessages))" case let .apiLeaveGroup(groupId): return "/_leave #\(groupId)" case let .apiListMembers(groupId): return "/_members #\(groupId)" case let .apiUpdateGroupProfile(groupId, groupProfile): return "/_group_profile #\(groupId) \(encodeJSON(groupProfile))" @@ -681,7 +681,7 @@ public enum ChatResponse: Decodable, Error { case userAcceptedGroupSent(user: UserRef, groupInfo: GroupInfo, hostContact: Contact?) case groupLinkConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember) case businessLinkConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember, fromContact: Contact) - case userDeletedMembers(user: UserRef, groupInfo: GroupInfo, members: [GroupMember]) + case userDeletedMembers(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], withMessages: Bool) case leftMemberUser(user: UserRef, groupInfo: GroupInfo) case groupMembers(user: UserRef, group: Group) case receivedGroupInvitation(user: UserRef, groupInfo: GroupInfo, contact: Contact, memberRole: GroupMemberRole) @@ -691,8 +691,8 @@ public enum ChatResponse: Decodable, Error { case membersRoleUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], toRole: GroupMemberRole) case memberBlockedForAll(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, member: GroupMember, blocked: Bool) case membersBlockedForAllUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], blocked: Bool) - case deletedMemberUser(user: UserRef, groupInfo: GroupInfo, member: GroupMember) - case deletedMember(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, deletedMember: GroupMember) + case deletedMemberUser(user: UserRef, groupInfo: GroupInfo, member: GroupMember, withMessages: Bool) + case deletedMember(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, deletedMember: GroupMember, withMessages: Bool) case leftMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember) case groupDeleted(user: UserRef, groupInfo: GroupInfo, member: GroupMember) case contactsMerged(user: UserRef, intoContact: Contact, mergedContact: Contact) @@ -1048,7 +1048,7 @@ public enum ChatResponse: Decodable, Error { 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 .businessLinkConnecting(u, groupInfo, hostMember, fromContact): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(String(describing: hostMember))\nfromContact: \(String(describing: fromContact))") - case let .userDeletedMembers(u, groupInfo, members): return withUser(u, "groupInfo: \(groupInfo)\nmembers: \(members)") + case let .userDeletedMembers(u, groupInfo, members, withMessages): return withUser(u, "groupInfo: \(groupInfo)\nmembers: \(members)\nwithMessages: \(withMessages)") case let .leftMemberUser(u, groupInfo): return withUser(u, String(describing: groupInfo)) case let .groupMembers(u, group): return withUser(u, String(describing: group)) case let .receivedGroupInvitation(u, groupInfo, contact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\ncontact: \(contact)\nmemberRole: \(memberRole)") @@ -1058,8 +1058,8 @@ public enum ChatResponse: Decodable, Error { case let .membersRoleUser(u, groupInfo, members, toRole): return withUser(u, "groupInfo: \(groupInfo)\nmembers: \(members)\ntoRole: \(toRole)") case let .memberBlockedForAll(u, groupInfo, byMember, member, blocked): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\nmember: \(member)\nblocked: \(blocked)") case let .membersBlockedForAllUser(u, groupInfo, members, blocked): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(members)\nblocked: \(blocked)") - case let .deletedMemberUser(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") - case let .deletedMember(u, groupInfo, byMember, deletedMember): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\ndeletedMember: \(deletedMember)") + case let .deletedMemberUser(u, groupInfo, member, withMessages): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)\nwithMessages: \(withMessages)") + case let .deletedMember(u, groupInfo, byMember, deletedMember, withMessages): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\ndeletedMember: \(deletedMember)\nwithMessages: \(withMessages)") case let .leftMember(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") case let .groupDeleted(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") case let .contactsMerged(u, intoContact, mergedContact): return withUser(u, "intoContact: \(intoContact)\nmergedContact: \(mergedContact)") diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index c69301423e..85db407a20 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -549,11 +549,11 @@ object ChatModel { } } - suspend fun updateChatItem(cInfo: ChatInfo, cItem: ChatItem, status: CIStatus? = null) { + suspend fun updateChatItem(cInfo: ChatInfo, cItem: ChatItem, status: CIStatus? = null, atIndex: Int? = null) { withContext(Dispatchers.Main) { if (chatId.value == cInfo.id) { val items = chatItems.value - val itemIndex = items.indexOfFirst { it.id == cItem.id } + val itemIndex = atIndex ?: items.indexOfFirst { it.id == cItem.id } if (itemIndex >= 0) { items[itemIndex] = cItem } @@ -589,6 +589,48 @@ object ChatModel { } } + suspend fun removeMemberItems(rhId: Long?, removedMember: GroupMember, byMember: GroupMember, groupInfo: GroupInfo) { + fun removedUpdatedItem(item: ChatItem): ChatItem? { + val newContent = when { + item.chatDir is CIDirection.GroupSnd && removedMember.groupMemberId == groupInfo.membership.groupMemberId -> CIContent.SndModerated + item.chatDir is CIDirection.GroupRcv && item.chatDir.groupMember.groupMemberId == removedMember.groupMemberId -> CIContent.RcvModerated + else -> return null + } + val updatedItem = item.copy( + meta = item.meta.copy(itemDeleted = CIDeleted.Moderated(Clock.System.now(), byGroupMember = byMember)), + content = if (groupInfo.fullGroupPreferences.fullDelete.on) newContent else item.content + ) + if (item.isActiveReport) { + decreaseGroupReportsCounter(rhId, groupInfo.id) + } + return updatedItem + } + + // this should not happen, only another member can "remove" user, user can only "leave" (another event). + if (byMember.groupMemberId == groupInfo.membership.groupMemberId) { + Log.d(TAG, "exiting removeMemberItems") + return + } + val cInfo = ChatInfo.Group(groupInfo) + if (chatId.value == groupInfo.id) { + for (i in 0 until chatItems.value.size) { + val updatedItem = removedUpdatedItem(chatItems.value[i]) + if (updatedItem != null) { + updateChatItem(cInfo, updatedItem, atIndex = i) + } + } + } else { + val i = getChatIndex(rhId, groupInfo.id) + val chat = chats[i] + if (chat.chatItems.isNotEmpty()) { + val updatedItem = removedUpdatedItem(chat.chatItems[0]) + if (updatedItem != null) { + chats.value[i] = chat.copy(chatItems = listOf(updatedItem)) + } + } + } + } + fun clearChat(rhId: Long?, cInfo: ChatInfo) { // clear preview val i = getChatIndex(rhId, cInfo.id) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index c221e3c15e..b04a08e0e1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -1995,8 +1995,8 @@ object ChatController { } } - suspend fun apiRemoveMembers(rh: Long?, groupId: Long, memberIds: List): List? = - when (val r = sendCmd(rh, CC.ApiRemoveMembers(groupId, memberIds))) { + suspend fun apiRemoveMembers(rh: Long?, groupId: Long, memberIds: List, withMessages: Boolean = false): List? = + when (val r = sendCmd(rh, CC.ApiRemoveMembers(groupId, memberIds, withMessages))) { is CR.UserDeletedMembers -> r.members else -> { if (!(networkErrorAlert(r))) { @@ -2694,15 +2694,29 @@ object ChatController { if (active(r.user)) { withChats { updateGroup(rhId, r.groupInfo) + if (r.withMessages) { + removeMemberItems(rhId, r.groupInfo.membership, byMember = r.member, r.groupInfo) + } + } + withReportsChatsIfOpen { + if (r.withMessages) { + removeMemberItems(rhId, r.groupInfo.membership, byMember = r.member, r.groupInfo) + } } } is CR.DeletedMember -> if (active(r.user)) { withChats { upsertGroupMember(rhId, r.groupInfo, r.deletedMember) + if (r.withMessages) { + removeMemberItems(rhId, r.deletedMember, byMember = r.byMember, r.groupInfo) + } } withReportsChatsIfOpen { upsertGroupMember(rhId, r.groupInfo, r.deletedMember) + if (r.withMessages) { + removeMemberItems(rhId, r.deletedMember, byMember = r.byMember, r.groupInfo) + } } } is CR.LeftMember -> @@ -3412,7 +3426,7 @@ sealed class CC { class ApiJoinGroup(val groupId: Long): CC() class ApiMembersRole(val groupId: Long, val memberIds: List, val memberRole: GroupMemberRole): CC() class ApiBlockMembersForAll(val groupId: Long, val memberIds: List, val blocked: Boolean): CC() - class ApiRemoveMembers(val groupId: Long, val memberIds: List): CC() + class ApiRemoveMembers(val groupId: Long, val memberIds: List, val withMessages: Boolean): CC() class ApiLeaveGroup(val groupId: Long): CC() class ApiListMembers(val groupId: Long): CC() class ApiUpdateGroupProfile(val groupId: Long, val groupProfile: GroupProfile): CC() @@ -3597,7 +3611,7 @@ sealed class CC { is ApiJoinGroup -> "/_join #$groupId" is ApiMembersRole -> "/_member role #$groupId ${memberIds.joinToString(",")} ${memberRole.memberRole}" is ApiBlockMembersForAll -> "/_block #$groupId ${memberIds.joinToString(",")} blocked=${onOff(blocked)}" - is ApiRemoveMembers -> "/_remove #$groupId ${memberIds.joinToString(",")}" + is ApiRemoveMembers -> "/_remove #$groupId ${memberIds.joinToString(",")} messages=${onOff(withMessages)}" is ApiLeaveGroup -> "/_leave #$groupId" is ApiListMembers -> "/_members #$groupId" is ApiUpdateGroupProfile -> "/_group_profile #$groupId ${json.encodeToString(groupProfile)}" @@ -5805,7 +5819,7 @@ sealed class CR { @Serializable @SerialName("userAcceptedGroupSent") class UserAcceptedGroupSent (val user: UserRef, val groupInfo: GroupInfo, val hostContact: Contact? = null): CR() @Serializable @SerialName("groupLinkConnecting") class GroupLinkConnecting (val user: UserRef, val groupInfo: GroupInfo, val hostMember: GroupMember): CR() @Serializable @SerialName("businessLinkConnecting") class BusinessLinkConnecting (val user: UserRef, val groupInfo: GroupInfo, val hostMember: GroupMember, val fromContact: Contact): CR() - @Serializable @SerialName("userDeletedMembers") class UserDeletedMembers(val user: UserRef, val groupInfo: GroupInfo, val members: List): CR() + @Serializable @SerialName("userDeletedMembers") class UserDeletedMembers(val user: UserRef, val groupInfo: GroupInfo, val members: List, val withMessages: Boolean): CR() @Serializable @SerialName("leftMemberUser") class LeftMemberUser(val user: UserRef, val groupInfo: GroupInfo): CR() @Serializable @SerialName("groupMembers") class GroupMembers(val user: UserRef, val group: Group): CR() @Serializable @SerialName("receivedGroupInvitation") class ReceivedGroupInvitation(val user: UserRef, val groupInfo: GroupInfo, val contact: Contact, val memberRole: GroupMemberRole): CR() @@ -5815,8 +5829,8 @@ sealed class CR { @Serializable @SerialName("membersRoleUser") class MembersRoleUser(val user: UserRef, val groupInfo: GroupInfo, val members: List, val toRole: GroupMemberRole): CR() @Serializable @SerialName("memberBlockedForAll") class MemberBlockedForAll(val user: UserRef, val groupInfo: GroupInfo, val byMember: GroupMember, val member: GroupMember, val blocked: Boolean): CR() @Serializable @SerialName("membersBlockedForAllUser") class MembersBlockedForAllUser(val user: UserRef, val groupInfo: GroupInfo, val members: List, val blocked: Boolean): CR() - @Serializable @SerialName("deletedMemberUser") class DeletedMemberUser(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember): CR() - @Serializable @SerialName("deletedMember") class DeletedMember(val user: UserRef, val groupInfo: GroupInfo, val byMember: GroupMember, val deletedMember: GroupMember): CR() + @Serializable @SerialName("deletedMemberUser") class DeletedMemberUser(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val withMessages: Boolean): CR() + @Serializable @SerialName("deletedMember") class DeletedMember(val user: UserRef, val groupInfo: GroupInfo, val byMember: GroupMember, val deletedMember: GroupMember, val withMessages: Boolean): CR() @Serializable @SerialName("leftMember") class LeftMember(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember): CR() @Serializable @SerialName("groupDeleted") class GroupDeleted(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember): CR() @Serializable @SerialName("contactsMerged") class ContactsMerged(val user: UserRef, val intoContact: Contact, val mergedContact: Contact): CR() @@ -6168,7 +6182,7 @@ sealed class CR { is UserAcceptedGroupSent -> json.encodeToString(groupInfo) is GroupLinkConnecting -> withUser(user, "groupInfo: $groupInfo\nhostMember: $hostMember") is BusinessLinkConnecting -> withUser(user, "groupInfo: $groupInfo\nhostMember: $hostMember\nfromContact: $fromContact") - is UserDeletedMembers -> withUser(user, "groupInfo: $groupInfo\nmembers: $members") + is UserDeletedMembers -> withUser(user, "groupInfo: $groupInfo\nmembers: $members\nwithMessages: $withMessages") is LeftMemberUser -> withUser(user, json.encodeToString(groupInfo)) is GroupMembers -> withUser(user, json.encodeToString(group)) is ReceivedGroupInvitation -> withUser(user, "groupInfo: $groupInfo\ncontact: $contact\nmemberRole: $memberRole") @@ -6178,8 +6192,8 @@ sealed class CR { is MembersRoleUser -> withUser(user, "groupInfo: $groupInfo\nmembers: $members\ntoRole: $toRole") is MemberBlockedForAll -> withUser(user, "groupInfo: $groupInfo\nbyMember: $byMember\nmember: $member\nblocked: $blocked") is MembersBlockedForAllUser -> withUser(user, "groupInfo: $groupInfo\nmembers: $members\nblocked: $blocked") - is DeletedMemberUser -> withUser(user, "groupInfo: $groupInfo\nmember: $member") - is DeletedMember -> withUser(user, "groupInfo: $groupInfo\nbyMember: $byMember\ndeletedMember: $deletedMember") + is DeletedMemberUser -> withUser(user, "groupInfo: $groupInfo\nmember: $member\nwithMessages: ${withMessages}") + is DeletedMember -> withUser(user, "groupInfo: $groupInfo\nbyMember: $byMember\ndeletedMember: $deletedMember\nwithMessages: ${withMessages}") is LeftMember -> withUser(user, "groupInfo: $groupInfo\nmember: $member") is GroupDeleted -> withUser(user, "groupInfo: $groupInfo\nmember: $member") is ContactsMerged -> withUser(user, "intoContact: $intoContact\nmergedContact: $mergedContact") From 3412ceba01c64aa2c2584254d10ad6ef14b9d83b Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 7 Mar 2025 23:49:55 +0700 Subject: [PATCH 465/567] android, desktop: fix group members duplicates (#5727) * android, desktop: fix group members duplicates * optimization * use groupMemberId as key --------- Co-authored-by: Evgeny Poberezkin --- .../commonMain/kotlin/chat/simplex/common/model/ChatModel.kt | 5 +++++ .../chat/simplex/common/views/chat/group/GroupMentions.kt | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 85db407a20..13cdc9f19a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -789,6 +789,11 @@ object ChatModel { } // update current chat return if (chatId.value == groupInfo.id) { + if (groupMembers.value.isNotEmpty() && groupMembers.value.firstOrNull()?.groupId != groupInfo.groupId) { + // stale data, should be cleared at that point, otherwise, duplicated items will be here which will produce crashes in LazyColumn + groupMembers.value = emptyList() + groupMembersIndexes.value = emptyMap() + } val memberIndex = groupMembersIndexes.value[member.groupMemberId] val updated = chatItems.value.map { // Take into account only specific changes, not all. Other member updates are not important and can be skipped diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMentions.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMentions.kt index 89ef19dcda..1a63375432 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMentions.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMentions.kt @@ -196,7 +196,7 @@ fun GroupMentions( MaxMentionsReached() } } - itemsIndexed(filteredMembers.value, key = { _, item -> item.memberId }) { i, member -> + itemsIndexed(filteredMembers.value, key = { _, item -> item.groupMemberId }) { i, member -> if (i != 0 || !showMaxReachedBox) { Divider() } From f31372a771e53c8a554e71b04e88f3a249be435e Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Sat, 8 Mar 2025 00:10:57 +0700 Subject: [PATCH 466/567] android, desktop: fix link preview (#5725) Co-authored-by: Evgeny Poberezkin --- .../simplex/common/views/chat/item/CIImageView.kt | 12 ++++++------ .../simplex/common/views/helpers/LinkPreviews.kt | 10 ++++++++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt index 0863c28007..1be2110b1f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt @@ -92,12 +92,6 @@ fun CIImageView( } } - @Composable - fun imageViewFullWidth(): Dp { - val approximatePadding = 100.dp - return with(LocalDensity.current) { minOf(DEFAULT_MAX_IMAGE_WIDTH, LocalWindowWidth() - approximatePadding) } - } - @Composable fun imageView(imageBitmap: ImageBitmap, onClick: () -> Unit) { Image( @@ -265,6 +259,12 @@ fun CIImageView( } } +@Composable +fun imageViewFullWidth(): Dp { + val approximatePadding = 100.dp + return with(LocalDensity.current) { minOf(DEFAULT_MAX_IMAGE_WIDTH, LocalWindowWidth() - approximatePadding) } +} + private fun showDownloadButton(status: CIFileStatus?): Boolean = status is CIFileStatus.RcvInvitation || status is CIFileStatus.RcvAborted diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LinkPreviews.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LinkPreviews.kt index de4e9fe281..9c529e547a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LinkPreviews.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LinkPreviews.kt @@ -21,6 +21,7 @@ import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chat.chatViewScrollState import chat.simplex.common.views.chat.item.CHAT_IMAGE_LAYOUT_ID +import chat.simplex.common.views.chat.item.imageViewFullWidth import chat.simplex.res.MR import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -135,10 +136,15 @@ fun ComposeLinkView(linkPreview: LinkPreview?, cancelPreview: () -> Unit, cancel @Composable fun ChatItemLinkView(linkPreview: LinkPreview, showMenu: State, onLongClick: () -> Unit) { - Column(Modifier.layoutId(CHAT_IMAGE_LAYOUT_ID).widthIn(max = DEFAULT_MAX_IMAGE_WIDTH)) { + val image = base64ToBitmap(linkPreview.image) + Column( + Modifier + .layoutId(CHAT_IMAGE_LAYOUT_ID) + .width(if (image.width * 0.97 <= image.height) imageViewFullWidth() * 0.75f else DEFAULT_MAX_IMAGE_WIDTH) + ) { val blurred = remember { mutableStateOf(appPrefs.privacyMediaBlurRadius.get() > 0) } Image( - base64ToBitmap(linkPreview.image), + image, stringResource(MR.strings.image_descr_link_preview), modifier = Modifier .fillMaxWidth() From 89dddab06082e5b43fb9f212de1c60a07307779f Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 7 Mar 2025 18:18:43 +0000 Subject: [PATCH 467/567] 6.3: ios 268, android 279, desktop 96 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 36 +++++++++++----------- apps/multiplatform/gradle.properties | 8 ++--- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 957f58d5f5..b4c7cbf592 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -174,8 +174,8 @@ 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; }; 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; }; - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.7-Er4xsZkxTrnBI7tHHcaHrY-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.7-Er4xsZkxTrnBI7tHHcaHrY-ghc9.6.3.a */; }; - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.7-Er4xsZkxTrnBI7tHHcaHrY.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.7-Er4xsZkxTrnBI7tHHcaHrY.a */; }; + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3RDt4h0Fq4hJV00CU7V8H-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3RDt4h0Fq4hJV00CU7V8H-ghc9.6.3.a */; }; + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3RDt4h0Fq4hJV00CU7V8H.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3RDt4h0Fq4hJV00CU7V8H.a */; }; 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; @@ -531,8 +531,8 @@ 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.7-Er4xsZkxTrnBI7tHHcaHrY-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.0.7-Er4xsZkxTrnBI7tHHcaHrY-ghc9.6.3.a"; sourceTree = ""; }; - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.7-Er4xsZkxTrnBI7tHHcaHrY.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.0.7-Er4xsZkxTrnBI7tHHcaHrY.a"; sourceTree = ""; }; + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3RDt4h0Fq4hJV00CU7V8H-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.0.8-3RDt4h0Fq4hJV00CU7V8H-ghc9.6.3.a"; sourceTree = ""; }; + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3RDt4h0Fq4hJV00CU7V8H.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.0.8-3RDt4h0Fq4hJV00CU7V8H.a"; sourceTree = ""; }; 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; @@ -688,8 +688,8 @@ 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */, 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */, 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */, - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.7-Er4xsZkxTrnBI7tHHcaHrY-ghc9.6.3.a in Frameworks */, - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.7-Er4xsZkxTrnBI7tHHcaHrY.a in Frameworks */, + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3RDt4h0Fq4hJV00CU7V8H-ghc9.6.3.a in Frameworks */, + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3RDt4h0Fq4hJV00CU7V8H.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -774,8 +774,8 @@ 64C829992D54AEEE006B9E89 /* libffi.a */, 64C829982D54AEED006B9E89 /* libgmp.a */, 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */, - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.7-Er4xsZkxTrnBI7tHHcaHrY-ghc9.6.3.a */, - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.7-Er4xsZkxTrnBI7tHHcaHrY.a */, + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3RDt4h0Fq4hJV00CU7V8H-ghc9.6.3.a */, + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3RDt4h0Fq4hJV00CU7V8H.a */, ); path = Libraries; sourceTree = ""; @@ -1963,7 +1963,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 267; + CURRENT_PROJECT_VERSION = 268; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2012,7 +2012,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 267; + CURRENT_PROJECT_VERSION = 268; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2053,7 +2053,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 267; + CURRENT_PROJECT_VERSION = 268; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2073,7 +2073,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 267; + CURRENT_PROJECT_VERSION = 268; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2098,7 +2098,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 267; + CURRENT_PROJECT_VERSION = 268; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2135,7 +2135,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 267; + CURRENT_PROJECT_VERSION = 268; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2172,7 +2172,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 267; + CURRENT_PROJECT_VERSION = 268; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2223,7 +2223,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 267; + CURRENT_PROJECT_VERSION = 268; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2274,7 +2274,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 267; + CURRENT_PROJECT_VERSION = 268; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2308,7 +2308,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 267; + CURRENT_PROJECT_VERSION = 268; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 7bc1da0b97..09095de72e 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -24,11 +24,11 @@ android.nonTransitiveRClass=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.3-beta.7 -android.version_code=278 +android.version_name=6.3 +android.version_code=279 -desktop.version_name=6.3-beta.7 -desktop.version_code=95 +desktop.version_name=6.3 +desktop.version_code=96 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From 3188d9f08724446693a1a1dedc9bec2754e6f07e Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Sat, 8 Mar 2025 20:53:27 +0000 Subject: [PATCH 468/567] docs: add reproducibility section (#5732) --- docs/SERVER.md | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/docs/SERVER.md b/docs/SERVER.md index 88ef74c058..91e5543553 100644 --- a/docs/SERVER.md +++ b/docs/SERVER.md @@ -1564,6 +1564,78 @@ To update your smp-server to latest version, choose your installation method and docker image prune ``` +## Reproduce builds + +You can locally reproduce server binaries, following these instructions. + +You must have: + +- Linux machine +- `x86-64` architecture +- Installed `docker`, `curl` and `git` + +1. Download script: + + ```sh + curl -LO 'https://raw.githubusercontent.com/simplex-chat/simplexmq/refs/heads/master/scripts/reproduce-builds.sh' + ``` + +2. Make it executable: + + ```sh + chmod +x reproduce-builds.sh + ``` + +3. Execute the script with the required tag: + + ```sh + ./reproduce-builds.sh 'v6.3.0' + ``` + + This will take a while. + +4. After compilation, you should see the following folders: + + ```sh + ls out* + ``` + + ```sh + out-20.04: + ntf-server smp-server xftp xftp-server + + out-20.04-github: + ntf-server smp-server xftp xftp-server + + out-22.04: + ntf-server smp-server xftp xftp-server + + out-22.04-github: + ntf-server smp-server xftp xftp-server + + out-24.04: + ntf-server smp-server xftp xftp-server + + out-24.04-github: + ntf-server smp-server xftp xftp-server + ``` + +5. Compare the hashes from github release with locally build binaries: + + ```sh + sha256sum out*-github/* + ``` + + ```sh + sha256sum out*[0-9]/* + ``` + + You can safely delete cloned repository: + + ```sh + cd ../ && rm -rf simplexmq + ``` + ## Configuring the app to use the server To configure the app to use your messaging server copy it's full address, including password, and add it to the app. You have an option to use your server together with preset servers or without them - you can remove or disable them. From 9dfa68bf57f31e6c3663df2c76ea69df608bfbae Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 8 Mar 2025 23:28:33 +0000 Subject: [PATCH 469/567] blog: update v6.3 release post (#5733) * blog: update v6.3 release post * update post, server page * update * headers --- README.md | 3 +- ...user-experience-safety-in-public-groups.md | 148 ++++++++++++++++-- docs/SERVER.md | 24 +++ 3 files changed, 163 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 936667da8c..ac0868d214 100644 --- a/README.md +++ b/README.md @@ -305,12 +305,13 @@ What is already implemented: 15. Manual messaging queue rotations to move conversation to another SMP relay. 16. Sending end-to-end encrypted files using [XFTP protocol](https://simplex.chat/blog/20230301-simplex-file-transfer-protocol.html). 17. Local files encryption. +18. [Reproducible server builds](./docs/SERVER.md#reproduce-builds). We plan to add: 1. Automatic message queue rotation and redundancy. Currently the queues created between two users are used until the queue is manually changed by the user or contact is deleted. We are planning to add automatic queue rotation to make these identifiers temporary and rotate based on some schedule TBC (e.g., every X messages, or every X hours/days). 2. Message "mixing" - adding latency to message delivery, to protect against traffic correlation by message time. -3. Reproducible builds – this is the limitation of the development stack, but we will be investing into solving this problem. Users can still build all applications and services from the source code. +3. Reproducible clients builds – this is a complex problem, but we are aiming to have it in 2025 at least partially. 4. Recipients' XFTP relays to reduce traffic and conceal IP addresses from the relays chosen, and potentially controlled, by another party. ## For developers diff --git a/blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.md b/blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.md index 1e201825ce..755849afde 100644 --- a/blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.md +++ b/blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.md @@ -10,23 +10,149 @@ permalink: "/blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-publi # SimpleX Chat v6.3: new user experience and safety in public groups -**Will be published:** Mar 8, 2025 +**Published:** Mar 8, 2025 -This is a permalink for release announcement. +**Please note**: v6.3 release for iOS is delayed, we are currently fixing the crashes on iOS 18 reported by the users - thank you! -What's new in v6.3: +If you installed TestFlight release and it crashes for you, please install the current latest beta build 6.3 (265) - it is more stable than the removed builds 266-268. -- improved groups: - - [mention other members](#mention-members). - - [private reports](#private-reports) to group admins. - - [preventing spam and abuse](#preventing-spam-and-abuse). - - [better performance](#better-privacy-and-security). -- [chat lists](#chat-lists) to keep track of what's important. -- [jump to found and forwarded messages](#jump-to-found-and-forwarded-messages) -- [privacy and security improvements](#privacy-and-security-improvements). +This post will be updated once iOS v6.3 is released. + +**What's new in v6.3**: +- [preventing spam and abuse in public groups](#preventing-spam-and-abuse-in-public-groups). +- [group improvements](#group-improvements): [mention other members](#mention-other-members-and-get-notified-when-mentioned), [improved performance](#better-group-performance). +- [better-chat-navigation](#better-chat-navigation): [organize chats into lists](#organize-chats-into-lists) and [jump to found and forwarded messages](#jump-to-found-and-forwarded-messages). +- [privacy and security improvements](#pri): + +The last but not the least - from v6.3 [server builds are now reproducible](#reproducible-server-builds). ## What's new in v6.3 +## Preventing spam and abuse in public groups + +[We wrote before](./20250114-simplex-network-large-groups-privacy-preserving-content-moderation.md): as the network grows, it becomes more attractive to attackers. This release adds several features that reduce the possibility of attacks and abuse. + +### Spam in groups that are listed in our group directory + +There is no built-in group discovery in SimpleX Chat apps. Instead, we offer an experimental chat bot that allows to submit and to discover public groups. Not so long ago, spammers started sending messages via bots attempting to disrupt these groups. + +We released several changes to the groups directory to protect from spam attacks: + +**Optional captcha verification** + +Group owners can enable the requirement to pass captcha challenge before joining the group. Captcha is generated in the directory bot itself, without any 3rd party servers, and is sent to the joining member. The new member must reply with the text in the image to be accepted to the group. While not a perfect protection, this basic measure complicates programming automatic bots to join public groups. It also provides a foundation to implement "knocking" - a conversation with dedicated group admins prior to joining the group. We plan to release support for knocking in March. + +**Profanity filter for member names** + +While group settings support giving all joining member an "observer" role - that is, without the right to send messages - the attackers tried spaming groups by joining and leaving. We added an optional filter for member names that group owners can enable for groups listed in directory - if a member name contains profanity, they will be rejected. Further improvements will be released in March as well. + +The current SimpleX directory chatbot is a hybrid of [future chat relays](./20250114-simplex-network-large-groups-privacy-preserving-content-moderation.md#can-large-groups-scale) (a.k.a. super-peers) we are now developing to support large groups, and of a directory service that will be embedded in the app UI later this year, allowing to search and to discover public groups. Anybody is able to run their own directory bots now, and there will be possibility to use third party directories via the app UI in the future too. + +Read more about [SimpleX group directory](../docs/DIRECTORY.md), how to submit your groups, and which groups we now accept. Currently we accept groups related to a limited list of topics that will be expanded once we have better moderation functionality for the groups. + +### More power to group owners and moderators + +This release includes two new features to help group moderators. + +**Private reports** + +Members in groups can bring specific messages and members to group moderators privately, even if the group does not allow direct messages. The simply need to choose report button on the message and choose the report reason. This report will be visible to all group owners and moderators, but not to other members. + +Group moderators can see all member reports in a separate view, and quickly find the problematic messages, making moderation much easier in public groups. These reports are private to group, they are not sent to server operators. + +Please note, that for group listed in our group directory, the directory bot acts as admin, so it will receive all reports as well. + +**Acting on multiple members at once** + +When attackers come, they often use multiple profiles. This version allows selecting multiple members at once and perform these actions on all selected members: +- switch members role between "observer" and "member". +- block and unblock members - this is a "shadow" block, so when you block multiple members who you believe are attackers, their messages will be blocked for all other members but not for them. +- remove members from the group. + +The next version will also allow to remove members together with all messages they sent - for example, if a spam bot joined and sent a lot of spam, but nothing of value. + +## Group improvements + +Abuse and attacks were not the only focus of this release – there are several features that increase value of the groups for the "good" users. + +### Mention other members and get notified when mentioned + +This feature allows you to mention other members in the group in the usual way - type @ character, and choose the member you want to mention from the menu. Even that there is no user accounts and persistent identities we made it work by referencing members in additional message data by their random group ID that is also used for replies and all other interactions in the group. + +You can also now switch message notifications in the group to "mentions only" mode. You will be notified only when you are mentioned in a message or when somebody replies to your message (by including your message as a quote). Simply choose "Mute" in the context menu of the group in the list of chats to switch group notifications to "mentions only" mode. After that you can choose "Mute all" to disable all notifications, including mentions. + +### Better group performance + +**Send messages faster** + +While we didn't reduce the required network traffic to send messages to large groups - your client still has to send message to each member individually - we fully redesigned the process of preparing the message to send, by reducing both storage and time required to put the message in the queue. + +Previously, while preparing to send a message the device had to store a fully encrypted message block to a database for each member, using ~17-20kb of storage for each member, which means that to send 1 message to a group with 1000 active members you devise was temporarily reserving 17-20 Mb (!) of storage. With this release the original message is stored only once, consuming exactly as much (or as little) space as its content, and the actual encryption happens right before sending. While preparing to send a message only headers and keys are prepared and stored, consuming ~200 bytes per message, or about 200kb for 1000 active members - reducing temporary storage required to send the message ~100x, and also reducing the time to prepare it. + +**Faster group deletion** + +When you leave the group, the app preserves a copy of all your communications in the group. You can choose to keep it or to delete it completely. This final group deletion was very slow prior to this release - depending on the number of groups on your device it could sometimes take many minutes. + +This release solved this problem reducing the time it takes to delete the group to seconds, and even in cases when the app is terminated half way - it is either rolled back or completes, but it cannot leave the group in a partially deleted state. It improves both user experience and privacy, as gives you better control over your data. + +## Better chat navigation + +### Organize chats into lists + +It is a common feature in many messengers – it helps organizing your conversations. + +The lists also show when any chat in the list has messages. + +There are several preset lists: contacts, groups, private notes, favourite chats and also groups with member reports - the last list is automatically shown if members of any groups where you are the moderator or the owner sent private reports, until these reports are acted on or archived. + +### Jump to found and forwarded messages + +This version allows to quickly navigate from message in the search results to the point in the conversation when it was sent. + +You can also navigate from the forwarded message (or from the message saved to private notes) to the original message in the chat where it was forwarded or saved from. + +## Privacy and security improvements + +### Set message retention period in chats + +Before this version, you could enable message retention period for all chats in your profile. While helpful in some cases, many of us have conversations that we want to keep for a long time, and some other conversations that are pure entertainment and you want to remove them quicker. + +This version allows it - you can set different retention periods in different conversations. It can be 1 day, 1 week, 1 month or 1 year. We can add more or allow custom retention time in the future too, if users need it. + +### Private media file names + +Previously there were scenarios, when original file names were preserved - specifically, when sending a video or when forwarding any media file. The latter was even worse, as media file usually is generated automatically, to include timestamp, and using the same name could have been used to correlate files between conversations, as one of our users pointed out. + +This version fixes this problem - media file name is now changed when forwarding it to reflect the sending time, so no additional metadata is revealed. + +Please also note: +- the apps remove metadata from all static images, +- only iOS app removes metadata from videos, but android and desktop apps do not do it yet, +- animated images are sent as is, please pre-process them if you need to reduce metadata, +- other file types are sent as is, and their names are left unchanged - we believe that for ordinary files their name is part of their content. + +We plan further improvements to reduce metadata in files in the near future – please let us know what you believe is the most important to reduce first. + +## Reproducible server builds + +Starting from v6.3 server releases are reproducible! + +**Why it is important** + +With reproducible builds anybody can build servers from our code following the same process, and the build would produce identical binaries. + +This also allows us to sign releases, as we reproduce GitHub builds ourselves and by signing them we attest that our builds resulted in identical binaries. + +**How to reproduce builds** + +You can reproduce our builds on Linux with x86 CPU in docker container - please follow the instructions [here](../docs/SERVER.md#reproduce-builds). + +We are looking for support from open-source contributors or security researchers who would agree to publish their signature of our releases, having reproduced the builds. + +**How to verify release signature** + +Please see the instructions [here](../docs/SERVER.md#verifying-server-binaries). + ## SimpleX network Some links to answer the most common questions: diff --git a/docs/SERVER.md b/docs/SERVER.md index 91e5543553..3bfd064c4e 100644 --- a/docs/SERVER.md +++ b/docs/SERVER.md @@ -15,6 +15,7 @@ revision: 12.10.2024 - [systemd service](#systemd-service) with [installation script](#installation-script) or [manually](#manual-deployment) - [docker container](#docker-container) - [Linode marketplace](#linode-marketplace) +- [Verifying server binaries] - [Configuration](#configuration) - [Interactively](#interactively) - [Via command line options](#via-command-line-options) @@ -32,6 +33,7 @@ revision: 12.10.2024 - [Systemd commands](#systemd-commands) - [Control port](#control-port) - [Daily statistics](#daily-statistics) +- [Reproduce builds](#reproduce-builds) - [Updating your SMP server](#updating-your-smp-server) - [Configuring the app to use the server](#configuring-the-app-to-use-the-server) @@ -515,6 +517,28 @@ This configuration allows you to retain the ability to manage 80 and 443 ports y You can deploy smp-server upon creating new Linode VM. Please refer to: [Linode Marketplace](https://www.linode.com/marketplace/apps/simplex-chat/simplex-chat/) +## Verifying server binaries + +Starting from v6.3 server builds are [reproducible](#reproduce-builds). + +That also allows us to sign server releases, confirming the integrity of GitHub builds. + +To verify server binaries after you downloaded them: + +1. Download `_sha256sums` (hashes of all server binaries) and `_sha256sums.asc` (signature). + +2. Download our key FB44AF81A45BDE327319797C85107E357D4A17FC from [openpgp.org](https://keys.openpgp.org/search?q=chat%40simplex.chat) + +3. Import the key with `gpg --import FB44AF81A45BDE327319797C85107E357D4A17FC`. Key filename should be the same as its fingerprint, but please change it if necessary. + +4. Run `gpg --verify --trusted-key _sha256sums.asc _sha256sums`. It should print: + +> Good signature from "SimpleX Chat " + +5. Compute the hashes of the binaries you plan to use with `shu256sum ` or with `openssl sha256 ` and compare them with the hashes in the file `_sha256sums` - they must be the same. + +That is it - you now verified authenticity of our GitHub server binaries. + ## Configuration To see which options are available, execute `smp-server` without flags: From 4bd95c8e4e1576dc30c3c0ae83d68c3434400839 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Sun, 9 Mar 2025 18:22:47 +0700 Subject: [PATCH 470/567] ios: fix random crashes in chat on iOS 18 (#5734) --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index b4c7cbf592..e60616d533 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -1989,6 +1989,7 @@ ); LLVM_LTO = YES_THIN; MARKETING_VERSION = 6.3; + OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000"; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -2038,6 +2039,7 @@ ); LLVM_LTO = YES; MARKETING_VERSION = 6.3; + OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000"; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; From ed625347bdc0f2a07d60961a089a4c7e76a9f291 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sun, 9 Mar 2025 16:08:49 +0000 Subject: [PATCH 471/567] ios: v6.3, build 269 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index e60616d533..9396845831 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -1963,7 +1963,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 268; + CURRENT_PROJECT_VERSION = 269; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2013,7 +2013,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 268; + CURRENT_PROJECT_VERSION = 269; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2055,7 +2055,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 268; + CURRENT_PROJECT_VERSION = 269; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2075,7 +2075,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 268; + CURRENT_PROJECT_VERSION = 269; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2100,7 +2100,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 268; + CURRENT_PROJECT_VERSION = 269; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2137,7 +2137,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 268; + CURRENT_PROJECT_VERSION = 269; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2174,7 +2174,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 268; + CURRENT_PROJECT_VERSION = 269; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2225,7 +2225,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 268; + CURRENT_PROJECT_VERSION = 269; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2276,7 +2276,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 268; + CURRENT_PROJECT_VERSION = 269; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2310,7 +2310,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 268; + CURRENT_PROJECT_VERSION = 269; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; From 16cf91902caf246f4472f362774958909797d9ee Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 9 Mar 2025 20:18:58 +0000 Subject: [PATCH 472/567] blog: update v6.3 blog post (#5735) --- README.md | 4 ++ ...user-experience-safety-in-public-groups.md | 68 +++++++++--------- blog/README.md | 22 +++++- blog/images/20250308-captcha.png | Bin 0 -> 476245 bytes blog/images/20250308-lists.png | Bin 0 -> 252142 bytes blog/images/20250308-mentions.png | Bin 0 -> 511264 bytes blog/images/20250308-reports.png | Bin 0 -> 435663 bytes .../src/_includes/blog_previews/20250308.html | 12 ++++ 8 files changed, 71 insertions(+), 35 deletions(-) create mode 100644 blog/images/20250308-captcha.png create mode 100644 blog/images/20250308-lists.png create mode 100644 blog/images/20250308-mentions.png create mode 100644 blog/images/20250308-reports.png create mode 100644 website/src/_includes/blog_previews/20250308.html diff --git a/README.md b/README.md index ac0868d214..1830228370 100644 --- a/README.md +++ b/README.md @@ -234,6 +234,10 @@ You can use SimpleX with your own servers and still communicate with people usin Recent and important updates: +[Mar 8, 2025. SimpleX Chat v6.3: new user experience and safety in public groups](./blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.md) + +[Jan 14, 2025. SimpleX network: large groups and privacy-preserving content moderation](./blog/20250114-simplex-network-large-groups-privacy-preserving-content-moderation.md) + [Dec 10, 2024. SimpleX network: preset servers operated by Flux, business chats and more with v6.2 of the apps](./20241210-simplex-network-v6-2-servers-by-flux-business-chats.md) [Oct 14, 2024. SimpleX network: security review of protocols design by Trail of Bits, v6.1 released with better calls and user experience.](./blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md) diff --git a/blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.md b/blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.md index 755849afde..f1aa3a243f 100644 --- a/blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.md +++ b/blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.md @@ -2,9 +2,9 @@ layout: layouts/article.html title: "SimpleX Chat v6.3: new user experience and safety in public groups" date: 2025-03-08 -# previewBody: blog_previews/20241210.html -# image: images/20241210-operators-1.png -draft: true +previewBody: blog_previews/20250308.html +image: images/20250308-captcha.png +imageBottom: true permalink: "/blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.html" --- @@ -12,19 +12,15 @@ permalink: "/blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-publi **Published:** Mar 8, 2025 -**Please note**: v6.3 release for iOS is delayed, we are currently fixing the crashes on iOS 18 reported by the users - thank you! - -If you installed TestFlight release and it crashes for you, please install the current latest beta build 6.3 (265) - it is more stable than the removed builds 266-268. - -This post will be updated once iOS v6.3 is released. - **What's new in v6.3**: - [preventing spam and abuse in public groups](#preventing-spam-and-abuse-in-public-groups). - [group improvements](#group-improvements): [mention other members](#mention-other-members-and-get-notified-when-mentioned), [improved performance](#better-group-performance). -- [better-chat-navigation](#better-chat-navigation): [organize chats into lists](#organize-chats-into-lists) and [jump to found and forwarded messages](#jump-to-found-and-forwarded-messages). -- [privacy and security improvements](#pri): +- [better chat navigation](#better-chat-navigation): [organize chats into lists](#organize-chats-into-lists) and [jump to found and forwarded messages](#jump-to-found-and-forwarded-messages). +- [privacy and security improvements](#privacy-and-security-improvements): [chat retention period](#set-message-retention-period-in-chats) and [private media file names](#private-media-file-names). -The last but not the least - from v6.3 [server builds are now reproducible](#reproducible-server-builds). +Also, we added Catalan interface language to Android and desktop apps, thanks to [our users and Weblate](https://github.com/simplex-chat/simplex-chat#help-translating-simplex-chat). + +The last but not the least - [server builds are now reproducible](#reproducible-server-builds). ## What's new in v6.3 @@ -36,10 +32,12 @@ The last but not the least - from v6.3 [server builds are now reproducible](#rep There is no built-in group discovery in SimpleX Chat apps. Instead, we offer an experimental chat bot that allows to submit and to discover public groups. Not so long ago, spammers started sending messages via bots attempting to disrupt these groups. -We released several changes to the groups directory to protect from spam attacks: +We released several changes to the groups directory to protect from spam attacks. **Optional captcha verification** + + Group owners can enable the requirement to pass captcha challenge before joining the group. Captcha is generated in the directory bot itself, without any 3rd party servers, and is sent to the joining member. The new member must reply with the text in the image to be accepted to the group. While not a perfect protection, this basic measure complicates programming automatic bots to join public groups. It also provides a foundation to implement "knocking" - a conversation with dedicated group admins prior to joining the group. We plan to release support for knocking in March. **Profanity filter for member names** @@ -54,13 +52,15 @@ Read more about [SimpleX group directory](../docs/DIRECTORY.md), how to submit y This release includes two new features to help group moderators. + + **Private reports** -Members in groups can bring specific messages and members to group moderators privately, even if the group does not allow direct messages. The simply need to choose report button on the message and choose the report reason. This report will be visible to all group owners and moderators, but not to other members. +Group members can privately bring to group moderators attention specific messages and members, even if the group does not allow direct messages. The simply need to choose report in the message context menu and choose the report reason. This report will be visible to all group owners and moderators, but not to other members. -Group moderators can see all member reports in a separate view, and quickly find the problematic messages, making moderation much easier in public groups. These reports are private to group, they are not sent to server operators. +Group moderators can see all member reports in a separate view, and quickly find the problematic messages, making moderation much easier in public groups. These reports are private to groups, they are not sent to server operators. -Please note, that for group listed in our group directory, the directory bot acts as admin, so it will receive all reports as well. +Please note: in the groups listed in our directory, the directory bot acts as admin, so it will receive all reports as well. **Acting on multiple members at once** @@ -73,37 +73,37 @@ The next version will also allow to remove members together with all messages th ## Group improvements -Abuse and attacks were not the only focus of this release – there are several features that increase value of the groups for the "good" users. - ### Mention other members and get notified when mentioned -This feature allows you to mention other members in the group in the usual way - type @ character, and choose the member you want to mention from the menu. Even that there is no user accounts and persistent identities we made it work by referencing members in additional message data by their random group ID that is also used for replies and all other interactions in the group. + -You can also now switch message notifications in the group to "mentions only" mode. You will be notified only when you are mentioned in a message or when somebody replies to your message (by including your message as a quote). Simply choose "Mute" in the context menu of the group in the list of chats to switch group notifications to "mentions only" mode. After that you can choose "Mute all" to disable all notifications, including mentions. +This feature allows you to mention other members in the group in the usual way - type `@` character, and choose the member you want to mention from the menu. Even that there is no user accounts and persistent identities we made it work by referencing members by their random group ID that is also used for replies and all other interactions in the group. + +You can also now switch message notifications in the group to "mentions only" mode. You will be notified only when you are mentioned in a message, or when somebody replies to your message. Simply choose "Mute" in the context menu of the group in the list of chats to switch group notifications to "mentions only" mode. After that you can choose "Mute all" to disable all notifications, including mentions. ### Better group performance **Send messages faster** -While we didn't reduce the required network traffic to send messages to large groups - your client still has to send message to each member individually - we fully redesigned the process of preparing the message to send, by reducing both storage and time required to put the message in the queue. - -Previously, while preparing to send a message the device had to store a fully encrypted message block to a database for each member, using ~17-20kb of storage for each member, which means that to send 1 message to a group with 1000 active members you devise was temporarily reserving 17-20 Mb (!) of storage. With this release the original message is stored only once, consuming exactly as much (or as little) space as its content, and the actual encryption happens right before sending. While preparing to send a message only headers and keys are prepared and stored, consuming ~200 bytes per message, or about 200kb for 1000 active members - reducing temporary storage required to send the message ~100x, and also reducing the time to prepare it. +We didn't reduce the required network traffic to send messages to large groups yet - your client still has to send message to each member individually. But we redesigned the process of sending a message, reducing temporary storage required to schedule the message for delivery by about 100x. This creates a significant storage saving - e.g, to send one message to a group of 1,000 members previously required ~20Mb, and now it is reduced to ~200kb. It also reduces the time and battery used to send a message. **Faster group deletion** -When you leave the group, the app preserves a copy of all your communications in the group. You can choose to keep it or to delete it completely. This final group deletion was very slow prior to this release - depending on the number of groups on your device it could sometimes take many minutes. +When you leave the group, the app preserves a copy of all your communications in the group. You can choose to keep it or to delete it completely. This final group deletion was very slow prior to this release - depending on the number of groups on your device it could sometimes take several minutes. -This release solved this problem reducing the time it takes to delete the group to seconds, and even in cases when the app is terminated half way - it is either rolled back or completes, but it cannot leave the group in a partially deleted state. It improves both user experience and privacy, as gives you better control over your data. +This release solved this problem – the time it takes to delete the group is reduced to seconds, and even in cases when the app is terminated half-way, it either rolls back or completes, but it cannot leave the group in a partially deleted state. It improves both user experience and privacy, as gives you better control over your data. ## Better chat navigation ### Organize chats into lists + + It is a common feature in many messengers – it helps organizing your conversations. -The lists also show when any chat in the list has messages. +The lists also show a blue mark when any chat in the list has new messages. -There are several preset lists: contacts, groups, private notes, favourite chats and also groups with member reports - the last list is automatically shown if members of any groups where you are the moderator or the owner sent private reports, until these reports are acted on or archived. +There are several preset lists: contacts, groups, private notes, business chats, favourite chats and also groups with member reports - the last list is automatically shown if members of any groups where you are the moderator or the owner sent private reports, until these reports are acted on or archived. ### Jump to found and forwarded messages @@ -115,20 +115,20 @@ You can also navigate from the forwarded message (or from the message saved to p ### Set message retention period in chats -Before this version, you could enable message retention period for all chats in your profile. While helpful in some cases, many of us have conversations that we want to keep for a long time, and some other conversations that are pure entertainment and you want to remove them quicker. +Before this version, you could enable message retention period for all chats in your profile. While helpful in some cases, many of us have conversations that we want to keep for a long time, and some other conversations that we want to remove quicker. -This version allows it - you can set different retention periods in different conversations. It can be 1 day, 1 week, 1 month or 1 year. We can add more or allow custom retention time in the future too, if users need it. +This version allows it - you can set different retention periods in different conversations. It can be 1 day, 1 week, 1 month or 1 year. We may allow custom retention time in the future. ### Private media file names -Previously there were scenarios, when original file names were preserved - specifically, when sending a video or when forwarding any media file. The latter was even worse, as media file usually is generated automatically, to include timestamp, and using the same name could have been used to correlate files between conversations, as one of our users pointed out. +Previously there were scenarios when original media file names were preserved - e.g., when sending a video file or when forwarding any media file. The latter problem was worse, as media file name is generated automatically, and includes timestamp. So the same name could have been used to correlate files between conversations, as one of our users pointed out. -This version fixes this problem - media file name is now changed when forwarding it to reflect the sending time, so no additional metadata is revealed. +This version fixes this problem - media file name is now changed when forwarding it to match the time of forwarding, so no additional metadata is revealed. Please also note: - the apps remove metadata from all static images, -- only iOS app removes metadata from videos, but android and desktop apps do not do it yet, -- animated images are sent as is, please pre-process them if you need to reduce metadata, +- iOS app removes metadata from videos, but android and desktop apps do not do it yet, +- animated images are sent as is, - other file types are sent as is, and their names are left unchanged - we believe that for ordinary files their name is part of their content. We plan further improvements to reduce metadata in files in the near future – please let us know what you believe is the most important to reduce first. @@ -147,7 +147,7 @@ This also allows us to sign releases, as we reproduce GitHub builds ourselves an You can reproduce our builds on Linux with x86 CPU in docker container - please follow the instructions [here](../docs/SERVER.md#reproduce-builds). -We are looking for support from open-source contributors or security researchers who would agree to publish their signature of our releases, having reproduced the builds. +We are looking for support from open-source contributors or security researchers who would also reproduce and sign our releases. **How to verify release signature** diff --git a/blog/README.md b/blog/README.md index 1432d95de5..00a84eca6c 100644 --- a/blog/README.md +++ b/blog/README.md @@ -1,5 +1,25 @@ # Blog +Mar 3, 2025 [SimpleX Chat v6.3: new user experience and safety in public groups](./20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md) + +What's new in v6.3: +- preventing spam and abuse in public groups. +- group improvements: mention other members and improved performance. +- better chat navigation: organize chats into lists and jump to found and forwarded messages. +- privacy and security improvements: chat retention period and private media file names. + +Also, we added Catalan interface language to Android and desktop apps, thanks to our users and Weblate. + +The last but not the least - server builds are now reproducible! + +-- + +Jan 14, 2025 [SimpleX network: large groups and privacy-preserving content moderation](./20250114-simplex-network-large-groups-privacy-preserving-content-moderation.md) + +This post explains how server operators can moderate end-to-end encrypted conversations without compromising user privacy or end-to-end encryption. + +-- + Dec 10, 2024 [SimpleX network: preset servers operated by Flux, business chats and more with v6.2 of the apps](./20241210-simplex-network-v6-2-servers-by-flux-business-chats.md) - SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app to improve metadata privacy in SimpleX network. @@ -19,7 +39,7 @@ Nov 25, 2024 [Servers operated by Flux - true privacy and decentralization for a --- -Oct 14, 2024 [SimpleX network: security review of protocols design by Trail of Bits, v6.1 released with better calls and user experience.](./20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md) +Oct 14, 2024 [SimpleX network: security review of protocols design by Trail of Bits, v6.1 released with better calls and user experience](./20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md) New security audit: Trail of Bits reviewed the cryptographic design of protocols used in SimpleX network and apps. diff --git a/blog/images/20250308-captcha.png b/blog/images/20250308-captcha.png new file mode 100644 index 0000000000000000000000000000000000000000..7f43b47bd135606eaaae39e7ab494e1974f0daa8 GIT binary patch literal 476245 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∨8>%CFK@5IEF9|wZH0v>1jbU@zM5ZmV?lb{Ae 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 literal 0 HcmV?d00001 diff --git a/blog/images/20250308-mentions.png b/blog/images/20250308-mentions.png new file mode 100644 index 0000000000000000000000000000000000000000..658c7e0011fd82b62a98cea0ba4b2b4a8f6f5cfa GIT binary patch literal 511264 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%0Af2^ccFsAs#zT!1xVTP_G%xZ1m-7rVxTm08PY2%c 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 z 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 ZWqnp&R3E~m-aUArqNt%zE%!e7{{WcJiFE(~ literal 0 HcmV?d00001 diff --git a/blog/images/20250308-lists.png b/blog/images/20250308-lists.png new file mode 100644 index 0000000000000000000000000000000000000000..cd15adbcbc3a5e020f2619b6685b199cb45de5cf GIT binary patch literal 252142 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

    |NZ`ZweN^$5hRoCDUHh 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�Z#OY|2ZpV!%tbGLaOKW>@f2W|AFMme**5 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

    Gz^qBy~dLG;k)8YJns5Gm6pDtr1ZQJP@ZCBa} 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%0AoSlHN8Wd?ArYRLr4C{4hJTWT9+1+|g+#g(*j z;Q~@)I%dqUb&+V7DX$9tXJ?TvgK(;Bu%eU=s707a-38@Dp6gE{+AyM_BOefKFLju~h<1W({C=b9jrW5E=E_Kf5Q6JFWx)weL zH1sz4u9b}rgMjla*j&aY`zwoAQgvZ24VlH*VzZUISeB_H@qPo*3;3FlhP{n`I;bJXC(Hij-cM4kJCnZj z%~#WxE`AM5B)8en=TTa&?WHQVvCZSH{T&8wJAkZJtX*A1P<55D&oUNl$d~axecpZa z(f2W*2$*Nl==(SNfczed2uU0KImV*U;#vG`-Bc&MN`BSo$7F7{@gC5i0@$rGm?={y zl<*1$8+4}X_tzLK^jM7AW+7-Ffe~u;9aFys+^u0x0eIew?=Q41-Y zR;~^=$P096j~?xjb{1Qri@AIb^(J)u>!@%kr5?Aj*g`w*GV|q3g?jN~lQNQ{x%sT3 z=7QQb_1*zw6H7TG+T0i(>tMx&(Ju7YeCmMA9#X&S3=UnRP+~!lQu?;`?RU2#&j#wP zfAD_#@ehAU9By_cpC(U8xZ%uDAKi6T5HBx1qmXFg z7b*s$wIUg|^b{rn1(gWDiXlP)eKWv)=f=MP+k6gU=icInx)lMUdfNiI9;R zR#+S$Jp$Yl5GezM!SgI1#^rT(8B}`jQypg+M%)#?;>DT#*v>7dh|A!)Fxw(ZV=!Yp z`R64+K_iXJbAVbtidTH=qA_Oylz=&ii-01C9FAcbc21HXOvCX7&M+f?ig#V~)rP&u#m%qzl6wjVKAIA+jQdE5IwTEHHA}4yhBS6weAAArCKxf(Y^AZT5q4Zg# zAkYM~hBMH}IPYzlgcO-b281m`>V#k#0AR-s62OOfMqMyc{gd7xChiRbf6+VnoDSB` zSD?aK8ME7hm(c@N&-HM3KeZ-vvH6GXtb%I8%K6J7vF;phq&+}c2Z@&rx52fYlqaOeuer>3u;+ z2m0!&uq@$+omQJGt29Jt257j8P6xm+X49G>qTqUu&24&^oWop-phd8@0I*l;7t&N_ zGz1x2+q*2L<2!Si2CYhh*^im-5F9v=76`e~l2&|R2kG`-y~ALH-h#CT^XJym3U;Kg z0j!pq=hFcF;>yiO=^|<%Jeh1$HD|e03{5Jh}zo zCQX|YEVv9222n2cWj0B=nyN@a+rvjJ{A3Gy1`)T>egEZ+Ui#_JQG?kfUegOta||ul zvKq?1g4Ibny=F1x~Zp|Q*W`)G6E zWK&u_uZmbdVbC)zudzAUp5Wh(le&(-3KGaJvl%YDjJ#kVtc9lmqr3rwX2JA`{z-GH z6TvvdOpu^y$w=LB^NYqDlJ*(f0fP@M+|00$dx#$S!+S^M2QzLp%zu&(T3jj9iMROH znU{I$Po1{esJ7DL`Ev~F89X{m_Wu$0o=pEg~c0p67=bk-;oh6^qR70U7x!z~OK>0EffjkYTPcsN4%* zKx}DiTl9Lh!J2$SC>1>az-W|yVgBMhZ`u*!U7?_bW-096y?ZQwWicv)V4z*5P>3e_ z<=?yJ_)F4RQQ9RlR-m;+lL7C#Q9$8WZKrIaP^jkI&4LQsYBEREwyw)GEwMv%FcglB zM@CRh%w&+mp|Hgs_j-tQaH{ENr__2 zpA5oq(j{MrPGE8sKV38G>Q5Y80=KUG*?2b4iYt7ZpMt$tAVROCE~?jajZZ2!iiOabW?EkyLoa= zxhz}z<89uXPiPQ|zRn*mA-e+L@z!qWv<%|;^y%Z~U;gQTvWagCJJ1tO{=C7dpV#BV zd&Zzvgmf4y(ha5>3hUx#dILg@u;Z>wKH^6tKBJX|QR4}R0aLeZ3Xm-Uxfa@UQ#vc) zZC*oLkC3;6ZSnr$4iij;aLhtZ@m0){cn|yGp_)2)S#8|SyC0!d-f13!(nAK_1Ei{D zw4Hidnd0wxiU4I?`P-8kkI(>$^^i@J%V=CDPDB`>m;5o9FJmgu=e~nVaIDD*s{s=> zIrivmxBl)Qi}K_5|H|Y6aY;Kd%b<4Ja>)Z!(1cDRrYm&7dXHHLmOkB2{^j4}A!djT zY#(7D-5X?elOhblyhb;8+)7&~MtaCWfN&K9FN1U%3hJ+#qn8i4>@>&EUor7OtEjNH zv(JQq1BlQ{_K^fvIYqPITg5*$dCg};Y)W`t}{{d zRL#TY_zw@8M}K;Vu(aCTx_PtN!qjD|pLm60%m6e)IE~g5e!}m)uJ2B<6+S|!bC1nA z<IjO`UYOue~%EvSG0?U=HX50O=DKTW-fyeNOj`B`~*)m&c86fZw4FqeTR12 zxWHUyC+HHc8i#o5IYpR7pB%y7Ds!HP912djp`>h1o@UfXmwiYy0iNks(ZKXE)9519 zcWH|%G(ZLR(YHOk1TAel1B6!v@-ZFuAtpN~n0Squu=UWId#qLmIbfppIDHR1hiQu~ zKKx@pVfk%awjn2+z&=NK*OQDP8%Y_Y^bq4}Qr^-AJxQ0=OdaWJ(0-A)q!+hL6DY`t zNxS=HxLD;)4W2#ynUH3 zZ}&%vFcC&5FCChvS=zB#sp6)|Ol;rD(9Xd8-V}sWL&ZX7wOk7I3JAV0g0ApoAUb;B z8i%%=j|$)S_1^b}8u#-1^7ZZhCM|!;Re=_$^%)S5B?Cc4(0G^I zieVyjAG%c}7Fwn3QIaV7~qECgxe`!`L*+$fA=h+>*UXP5pNLS}O!1HNoFsFAu-v7d?d0WCXi+{V&X4`E-Ti=QZCXIS zR@>SUNo%fb2vXk@{H~!gfLU!`ALrcDXG2sFV#`lG3yiYl(V53PBu zMxIUo_F&-HQ*Km2AffQkqWnl$bPvJ~QphN*JG&xDD4Dm{- zgA(l{6L+##3#DeybYhesF$psS88v&J!nlNXdWGfgDK=qaOjPu@jeJTP1WjDuY5&mc zG5{;-P#bqX{|%Zh1=3Y~g#QH_K=9DRO3I~OJw9HPtd_p``?zM@`SZUuoAgUd8wkC?%#xywp5<}G zc?tp?IxDr(%v-=+O)|n06N8?D{?=+}V6EhkE$hVm5DqkhF-*~lTB8au9lg3B&^M%8 z!Kd?zQY6Q;pMCeVIr#Q_mh}UaO2Ls|a)xct&q7N|BmpoFmZKuBi;} zsZs2EQ5B=?pJ3OG?K>LFWy+I7?+{--B~mSCmx;zylMLdWqLrTY*m&W+>%v*zm; zJI&*N-f5QB>1)1Zvh-j`nea5miq7Hr7Jk~7u|~qoVzkdBfpRe+*gc#yUwwPreDkPi z$p^Z&0;gi$SIl)pZy7-}n>(F7lE0G_H*t>HOnQ1$e7l2o-h*1^TnhN^?Q(AvtIe?C(R9TSL6Zg{*Z05|vPLL({LrJ7eRei+ z()p^f9U=Trp!@#r5w0bVn!`Ql1P*%JQu&b#1T4V(f!FvB)bz!^N}p`o)W7BGMduI^FL6egQe#o2bEsSdHYGb*XkTsx6~0uv@I zaiPiz%NYXu(Fq=c_JAjn@S^nSP={T#>YA_Fji@l_(pa4L`>YFI+fNC!1ltj9$jKz7 zU?S?lMNC|2FGJfK_+W@fq)u~_Jt!lzs4|IBb1&iR-qQ`SO?!X{&LVf3Rea9^shMy^-{3$pG5#d#sCH>F;Ett3p;6Pd^ zb3qqzZ|lcwjNAsXv7MT^^|zRohkVJ`HjzSsb#uTbsz3gIAHnv|2tcbWLEmm}-noyh z=$&Q@0sYp8_nWQjA7F=$-)aWE)hT8r42CY73#!AWNSC0?p-2TOr#;2wM_iZ&F~|pn zim)_N8s~?y5CH(8f`M`cNlu^Q%N#rZ9J|%SgO|K#(4Jkx))~z$>Bef^G^KD5UAx|F z-XAqz4mYq@-EJO#^+)VoA2mD2uh`3qKtrZyta+;|*L#v>RseTde2I;k+N!Fwad6iY zj&eA1BM3n+CLHF$)R~Thj`==I;BNzUhGNy4h;xuR(lAw&Omb9I(%aVdJMgM)n_%J!*7ZSj7OfkPoUX= z9{7>I#^%)Vi{~7;#3?uk<@?yMfB)S#&GxCLENocB48#?e8Tjj=B^%s;HDmIkMz#-a z2IMtD-K#eo_uHs6Q0&s=sjsBUrr@4hZ^~3OoJHuJ$luhNc)I!4!H>>t0MF0>Z8Am1+yJ-D;W} z*L%$;p9BAY-=mnj&2SC-a$H94vX|iD<7drZzQrf_E?LsS^;szxPDL+D}q^Lx<^-yQvs1A?u&oN9hAT|a-6UxWxMi))-GSdFm zkVABknmw#3D_f*jh_r994(;+}`HW>iwvULAOPxE`xrUGfrVuRk*R4i;%#!&LLis)? zy`FLEsOKtJA1=~le*@{#$B=~+1_L!oKE^VR`8a+WxCR&7V2P_>OU;x6x%>!{o(x+~ z4b9X)?5{T6wxat<{pJf#g^Xr5Lk3g-skK&sx6JwjH>-^K>}u5;9=kM{G$>3vmZtzr zSRf7jalXr}2w0jD0V2P&b!e{bzrN6&3`B5;Z(>i+v$EV8lXGb?V6r}d*zOV0IVag* z|IS9?wQW4{ebU@uk4Oj8sE#WT9z=vI#vW!kZf=e3xfM>(C8aBWMCQqoUQMuxOLY}m z-jy1-$e}*JcW%(9jx(>pw7n&ix3UYRyt4210-!QChhQ!I~Z_T&jgIPDRml;_N$xJ%uaB2F)Fer3uX2_{SEE zFx8rB@2)nLQ>ODSc^H8p>-tT<={ELtr9BWRe@p)2tM)&dD!$~zMVM2KxMQ9_c@>m@ z5bV5Uo>!RAnD4dNI2R4NWs@EX_-btQ@Z^1gW$c3gW#$t%^K>%f%7oAF8qCGg#>TW{ z`PQILnN%rMJ6X~!NzXar-uACQ3&*(nyn3rLS578O@(I@Y~7` zye}GZTT|%_A^_R}@KeCmhi?RPaLuX+@fHQk08fWuCeV?eb!tfG;=s~=PkSVq1DzeZ zgKZ6Y*M2j3eXgLr8eO4GK6B>v#MeiLP*Ikp2~h_ogdp3qOlr%3?eKyV6n80Mi=- zpv*_EEkdhYQsC$NTmP}mgR=q^`2?zYiUe1MX@i4d4Zcy%wG@_9`iYmXaxYHYXG5#_ z^4(|cY#p2Q^}UOU#H$z2num|RYX-mnBSJFXIBwr5uW zEE{L(odS0}dOLpPT*4OheQQ{ftoHH{X+CDiW3*xGv{+5$=YYz($Fp zCzZ+B^Jeb__>iX8xf!}*(GnUg;p(t!2Y{E3)ek;y9-tK)p%r_Ckoe^5e??380)Yze zEDE0yE?m+t8nzJv^Z-Gkk1!+ufe3;p`C%-qfFqqIX!{gs;T$uh(?e?w9k$U}AOG=R z&}KeshVc6OIs-qkCfLO)q<5WZud06&_;Do0ve9!&nRRE^xq-$WH^74%+fDb`)273*=9BL^Nf95;C$Dx9zS-Q0 zE0QrnxIVpOequTJov~4}CvP!xRv7GazlM9T_@8%0g`Zw4y4XK!^KWbL^ie0~h_yUA z$x#T_M!n#xUHO!~1}wv+4H{Li&q8%$l$MebT)8!_#IB^P;==@Y2HS!C`Nwd4!wEZ@&2+J9E;~Nogi= zOumpFe63r|pP((&VneMP6){mc)Gs#zH-fkuI2F)m2-+TQ<3U%`-U+KWCrp4SCp>{g z3@4D{FP%x;5OWK=e=PZP9#-Tt{r|(XKqZd!puW|B+h>&tLq-lBctHfG(jL%hzMvlg z9TEV7)(SoJm7g*S7n-`-Ho%l%r%1j9 zC2H~JWqRkFe|bXD%ceR=tl;UUOD?HcnO=cdEw=9#E5Un(DjSI%gMzt2l)}0$`rJVJ zw!xUXWGoKxf0)Ye9>9?esPMFHmP<@D)U>A!!iY``G^uh^`~W#i*BqbaCRMAg{gro8nwf^QPy@7jKh(9)}x+YJH81 zonBe&kgT5y?@f7Ke&@=A^b~HTUs)AFYXH5ak+ia(l?ROXOG1TtIgM~MnF?%jj#dUW zhgUa5W=XzOe1s-%#T9mFJ0p=%1TqKjg?RBxhrt^=m)zHDTLQ^e1`uxAP@i+hc$ygT6GA#ZQ;TQff z@04YZj6GS46Ys+NCF@j--wKwAl>7su%{LPY1f?V~Jp*+W=0#dF6@|FN6B^O!9_^Wd z4lgIWXk%AbpEPUu`)1uAO)>VyX!mB?sEQlH*ldc25rmldz}6)#?-k)3q_aw)u^4FV?8$)$dI7FybScoQ424!?kvjj|*+ch_USJe@A z7|es0{U=J2W*Y(EOUk7X%L>E(bG)eFK}8L49|587COpf^L?E=8H$u{q)eNEDO=^}p z8faC8qR$)g$jTE@!)sjqrX7fixK_ zB(ZVO;t!d~NDBoSp#qh4iUp)ptZWabY z4wDls;iI%M-djgbreJxRAB1H!!FOu81B+LR#N-e`HT{4}@!r=fz5xUfnD zb8Y0w4ECgrh=#liBu~fHexDpNk*kx3G;IrDfEdPbLeb0WzquMLm$Djl=){mnX{Y3w zdzi3?CccXXUSwA3j|Sj5b@%NNxKRFOHt7zTWOX!8azpC!8T$({X~Eqm4G&@egno_o zkn&RmCo2l;@JunyEc6LFSMUeYq~2=LE6Dm>%3a^S|Fyy`kbL4-Uz!>9lj?c@tOCKF z?43g@+ZINsIF^U2#ytZdX%yQ_b4J1aMZDty|=-xMUc zOFEt;sfN=wD=hX^#Dv0*>PM2!!TDVQKlW_%w?DkGp*ex@1T}5lO(z|hxJJwGcmxXb z;CFcctpmwZSAd^~+%7yDM?uu9!SPO)fb_v{3^m@N5;}G4YDD3Ri|94Z#hcgfWQ}6G zSSA}jFA{YDJ;y5=KAj~I?3}ZT&~{2fbwVSZ-Zoe)Cn`8}m=yB@aenM-dFGohOYP9kfhbTqI_(VUS~J)d?Q$!`!U+!Cw!;!klSC%W&u>2v z%3&DaN3bIreaa%Kb1(jvf97%V4&DTgyq9a6k9bCCcUa=1d{*2eD)6>HN1{0_0TdkP z+H%wQookB2z4&^cpNSX3Yh_$`|EAkYivqjgZ~G#vQVu@&ZQd7fA8e%)9~SfSIS6sp z^MV7AKS}Sm#0Tyv4213pyXy@!)4+oFoi)SZivD_OCFj<6y%|pf@=hI_{9P#@I`PljfHAw*K}B$Xo1&O z)-X5F)f!qf1jar#xU$^|_R$JcZV2nDRpgMNB{c1~(YH;{_mM%4o2P&FaG?EYWaEww zCX$EM?G#>pWI`3{^nsA*u_!e#Q>Em(p@LwM{TOVR3bc^`7z;dj^VG1k5Oc(%)d2#x_W$ydr zGiJ&72p26UXp40U;#`7x6Xq(Cntog%lgWpsKMJW`H!2fJk1ZZ*q_+Gz+VTOKm1Bgx z9(*`t{&mhkv%|qlxE*O$Z@5hdm?u+Rf$jzGQy-K024fT);4^^ z30WSzwL*KJjo?$xG(=wVfL0N?l!HhVsoj2a&N8d_73Dg1Fyn zFkXr2yNmbr``_ZKu(r)CeGgL(J8p$?QMFMknA`us^FP4OhO5oFHt4lX#*?8Hpf!d3 zL82ci&`*_IQhK0K^pI?=Gjy zd;YjlRx?OxEzZ)}Z^o}Y5SD@Pek)(feGUks9u$Zb==?c2n!eooT%ZXNE1uJf=?KjN>?Ge$wW$R()||lVX~H zP~n%S^4IS!l}Zmg?`UA{G}Eb)!vExv$w=<`!#6uTrN&%|R5}St&bNB6w!@Gm)d1o_ z;t9fQODP`PAmxP7{&=g_UNA=YSH6{Ngl*zbP3f4-S>Cc?`Hy1Jt{6CYXI$&cfeSID zedo~7PBa8!UVi6iFfK(5?AFqgmqfGN;*vNf6JAMUy4M2{$xpnzbEvZ%;^04@>vs!M zPE~Co@X4=9!<{f3m@Qj?rlb%j17q%Ffu@bP`RMJ_C!rD0!*KcoNFu%TUH*aneu?4n4Pc@6Crxa zk(V<&O6btX^h6UP1rH}L@udzib-PBX)3^4(r^kUaqX(a}N%n5D_VKsP_+S3H+5h^_ z2#UCNWSM;!Vcre`0U9`G!UMN+`2-=VxO_7&YmMv>4iu<7=%q+y#)CG=6#Vm4h3B6(e|84kQ!e#ik#oEc83#2p>2o5SeBnZ#Z1D3U+|Zr<{RJ5 znw1PEEBJw?J`u84H!#_l>D}TL<}g$z$Jx)A80n^OqP;)D-9YUl9oiXupb&nH=MaUf zbxeeMth5{e-xULeI7EDeBJAWTXK_hMp8%Z|I(HT z?kGp78^^tKaNpcsZf;=SHNgGh2|~H2m)f~2K&q^8ob+HE**s;+~t!HZA+rHAWyl;?}mdKuXG1v|N^+FlD<~io3JY99R?WYi$EQd!SYqkXFtk47& zY3)c%E1LZ5E261_}H3Z@Q>YB{rXOnyBEnuMSRO?Yt0w`_*AsKDe`1?&jT z#xXv)c&(EX=_00%?%vJ+@!s71SIP-I>(BDG@Evr8?O>gO!X!v_{HmrCVFQVvT=Bgvf6(JXVd_MI5&ps#HS|Kj>~Qko5P^2erp*~;R2yGB zv1sm7Cht@fnnbnD6voC5PGn5J-11Hn7MJeTx)K%cWTGTLYwmzBI^*Z0qJ!|!;CUq- zL)sfc3x_+)CwseWG{hqdn!`Tk7n;}T5ksxGn?U6%;hl>Mx3=8~ zcM6EK#c0pPg@2ylsGoS=95O-MetQbB{_c;3V|W=3}R!;!#J0fIQ!}do=_Z^ z(=!Dxc$V@tozwK`SkvP&a?(8hANVx4+OOl%k-G14V&tBhP&j0Ef&h6y{p@(UE&PpE ze;w~HTduT_w{5o%FFO&(;UFpa3yun)_ETLZDV=jBBM6W#zfKV74mnBm@C7SH*s{C8 zl6Gld4i3u*kgIqFT4R!Q*xSPd1tAp?`<${(poM#GoJ4h)AoSp$-VsOCGkA=FdAh^Q z2ltF4s>`ukPioRPEXT97CpG;kbcB}D)jF`RNrmv$J2x+zEGN!#?`F*r+T?)~C4}tb zqm#7tY`{klRmist)|Ky0;3-|jYSZmL3{TwE^h-SAQ3nBC;W`^tY0t@(`kx}7#Ks-| zRcVv@GYvQmv=1kb2s}4}EK&dmn!}F@ z>-KCxIKKb@KmbWZK~%Bl^dmX!jtR~L)0R8;?l&LY?loV1{S_`h;cE=kU8w-QtfF)o z{Fb2067Ce2PFGn))2}{xv~8>Gy>U_^S$TO?K~VK){qS=fkUm!(%2*-PMf}Oo30vN# zHIw(%<*uCi9>Jzc`SYHsuH4q7W!%9*DxSCR3A#U*O0UQx>iM7XM zOZeHOV!cLCD1y5uyvBz<-(8Bo2yF*VT=9v3>6Lt?tIw#@O6TVrD}!&2$%ax6V{#9G zawo>(?azAyX%I-&$f=zXg)H0i-htKurNo!^08jD@7@>UdF7Je2FjQ!8Koc)urbG6g z7+(06_1!kM*cc_zytg4(9uv2i!n7GUosc%{eX4$p27w$=BB7r&rWum?!j|928F^$jI{%Q{#^t`1^OxO&Ga9B?s4k~`$+x> z{cJcT`PmsKS2ml-O4yh7*AYB6zzG2e?fQ71fgY{*%q1x_Y7;hlP06>niid`+51X|w zX3aH(|1lclo$tQmxc5Ic&tE=jwpbF^mbrJ1#}`ZrX0(GLn@Y7)pK?Tfm(66J;OWj| zNx6i!+{vNGg1ZLLS8yk8=NhY4_#VH`5%$f$|5LN`@Gtn?e$_m{W@3mC-{UB7Y1mx< zFdjY>O!WmF{_!56NB_*(KuOKGNzi_(G#29V96MfxA1fGSD&O%n0woe5b?hd}B!tHB z0sZBp>o?GpbGQrI!-k2=`U?b>ofpjHkD&$LP~h(se5}%w z;GPgC0!*AV2htyQJv~@WzYQYzQa%O3KJQL?3VQTMl*_}L^mVQvL|2q@YA^3b-$U}D z*x=7MG?;4PHFZ!>@-#|UCwO42p>CkLo^>SoaNsk;Tn8yL!sAda7q~noC)n1zB5+Q6Nz`HDgviUa&*}oED=%2N zadQy3E(61jwF7oH%47Di3b^SPybl{Fm>1y~wx(^w=PcgWasAT#3fU508jcC(rB-(n?dR1 zq{Brr?Y_Z^ScU@E`V`?PGU%9`klHUeDRQNVY})cD=ZGiph-Z`Wo9VoFbya!!TLMkUM*L&_T}|k{Z+_WX**Ei0xE-__3WyOlep?R z#YvPaB?{YCkNIYd76$K~terqqg>^OY?n`i@VO(Kr^70C0j`w`dgv{~FgBmVEZ6OST zyi6Un>BY{@!C+g9kWQY7!yjpqkQd%9!Y!ZXe~n8#V?;2WM=e02RU?lGH+Q;<|+)_1AgQ&fX%i%|alQCNI8G?#^Sl*H0NF2Zq>uCB0pmDOO3;M@s-SrngV-LU)<^g)C&Z&$r zs)D5abx66L1RlfBo|?MJDvKwBKF4jNURm_~A+KpJFrveBk{6FN@RI!MD%!*q1@Q3= z!wk>z7Mm$Ctx`BY#oT7nnjP3g<$m|J>_0*XKf-+B1!glVtN>x4J|8z_c-6jR0;x2$xAa0C)rU_K#FWoR+B_L;HMlQx~advKOy zMfOJH&nf|Yxc{6(pWywaE$Z0KpwI?#M<~x}!9;cy!-b4f;!jv<8Zw?@&2I)NQxSZn z1FyiX)FBVv@oYOT9hLFzok}GZ+kI=Ae+tFKm8&M0FP$GNPdfF+6^l0|uCgB-2`D)zNc01|FJD$xqrb@IqH_z6MpSF3& zf|QSI?t!aGitTMm{b#QOG%php<1>j_h&xwQP-gLT!beh`3LSJ|I%E&WHV34w;q(8= z^JmSImpk}SKW%pKP_%X9kds?!%bFL32+)u=<89iKZO%3XT)FjN=eEvnjbegWXk)(DAO__s=hd$W}Xzv^EExea+%vEgzW}->wVM2CF zigNVFOswco>8XY3giB#vQy7a_g?sHJx6qm?!2dAFRi(qV;Tt350j9!4TzK)mY&axF z8=>TAt|kDHB5joC0;Rndo;Vwi-vSD?8vJgA(wUd|;!--Hg#>emt@zvJjAT_H(0vH<{1+heN-=V_{#tv(@G_7#B(Ar!BoTc(?_$tgdO;A zSU!C6r+;f6J^sEqd$EU5x74g7gf4-1mrm5>{Fn^Lmo`>-Lb{?#WK$U)Mi4-qocBh! z15`rEx;c{+o|k_6kIl#Uet-VEe**r)=GAz=xpnOt?g5u#-hdHDsZeW|qzVj3bl~R| zftB{AEp$AEM0iu!Auf>>UJ47@Cgca-I}r=0l)0ny$uH(~(g6I2xH_4*$rFL?3_pQm zX~k-ThZAXz(b3F^?_FrsA&wqqrv8*L@lm-1Zu*y=(3uS34^0>l;fg>*lmWuq8iKdp ze2#MHlx9JtpAmP<5}G2A#f$_2muXZceVV-}cTSm{q;H~J3i@_j_Mc-17Jk~pho8_u zkI*)qVOXxG85GgchU;Qafvv~kLqp2#L@leNq*M0~n&D+wI1>-eZ>&;^5}}=WLdGox za{ETjB@{Mg99wosfw`1Is4Ft3_+A$VJ{oBcpa}2HNNiQvEYqclI(4ZHy;}4x^|IDs zlP>jPSrqhhTs#7;-JpG#{YC^?g=cxMeP>^)09meO3TD4lH0YECT+9Qo=}+vhVoqZ( zMxBL8+J3^xJ8K|Jwk!|yXAsg0$N(E1^TN63k2_(5p^xO9BY$r`Q(Qy3Dn;Eg$&$_AQljmdFLUuWIY*S zA<{g;-}Xk#)P$<-_ViB$V+C(*E7g+f!fpZkASc!e@6oE;4tQ2$YD{g6g?E`*{CYW< zMB8*%+%^HAkY;8ImgZ?5!u37-EE?RwMnJS9rLDwgxcDXO7P#sBDc=c4EwJ~cGcMl} zhd;t0N;Ya*R$&^vRG4Y(JS&VEJXpxV=!D2>eL-m+Y8qdZ*z-?syjhIDm`0pjQhl2s z45}?!X%02aMob%txV;q;q!)(uCQE9{vY4}4CQVHY($NW0wC7A@jB6)jN3JQOT6=k2 z{MBMfUz@8mQ@~dscd{p(*ryZE4wRML07F+~gvrJAt6UBG+ zDy?cN?*f;1;-v;PSN?dt2&51%2|=)gC0@Zx;KHep>s>V5;aP+*L9|e7{*{(3*ucL0 zCp34^AyPEfRGx9{9-9(?jybMFBMa3GZH(PJHNDkBEqA*Kro<+4=v z2(*K=f}$I~W}2hu@=q-$1GG4fkf_ecbBv~S#z~XS0iIdN%QgCrZYB&7TuF~BK80ro z^i@t2-CSL5R#}49tN|`09VP~9%RJl%>1lH0K+Uo`yYI$hwUKB-Imxfz*#IAe!reV+ zi6$5=uo^vG-RWCnj4DviBdJRS2Idgdg7&GDj4~k=HKRM&)N^$GaQ#v9`03;3>&Gve zB~GpU@WT(AoA=h6J{$U?Qk27Nuhs_w3{nTQMWjK1jd=zbcG2!?LeK&K8D2n; z&20`i+5F3&ny>%xyXG4_dfdUzc_XuOI4gGc)_z(f4cLapiKVJB$*yQ95qzvvVJnnS z57yRrKb+tcd^}u)TbMc2b1UyVNUcQVu(S!+;f;wty z>X|D7&^g+De7NA*!if`| zlepW{OhK)(W-gZA{zA8pQzma|dtljPT$w^6ZS-CEK!+p9~SIPdQ;$Ay$(%&6nba6S|0>eN=qRBke0x z>xD%8SC~YCCHSKC2Dbbr-kP9HIWF5dk@c?#>75gN(`jd}eSKdu6gMb1Fpvl9B=IH` z0hPSd4?qM3P0c)FD$@2DQ4pA4%Li3hrD2ARfJopkS(`hkP`HARvjo4(KlVT8)K~FT z#8{31?p_iP1=4KA9&6bXXCok&)trvBVsMCw4n)sMnfWfCDdHDibV8HOsq$+kDex@i z(nh?Pr-Bdp^NPtnuo@nD5@^-{V^4{Jd6HDsZy0Fr%Atxm1C*WyegU^))%D6DXT`A~AV3y`0 zSjz^leBn~R*Dv{e1%KX)T0zRzz8DrB9bD!i@fJSLKm5ntz~|Ydr}ops$b42iUA5>7 z*au0m_q)Qp0=yfEFlVZKeIkm%$YyE=<T9%OsDNqD<=$M0B z_%4l%*M6qH%0$AtJfqWmQ?N`F3BkcZn*oj7?Sw_JXv~2}$7ir(^qcVb&#%;*-+V5? zJM$`UiXh7q9kKEKC+^lqjcGbl1jF)~`-|3{_eNR>ru7#-;znMQw-hdlt-R0tbI?dS z{+ciUQh>bU4}}pI^Dt()3fojAt?w4YyiB}|palTiTgPQ!M|Xojg;FxltCEX&w2$qL zQ8X*arYeM5wP#JPd+egET6F<-gg+`a!X%f5K)z%^o@4Pq;6*leRk)*V_p zTZF`v0X~Vz3WN9w+Eh>Y?4c=kL#EpJ;Kd)Y^+20rwA`y4Rqp{fVMy%=Y_tgT=X=`K zauhrpIhQ&1eZa4SSxJXeEa3>Ww`hrBJQaH#%u+^^HOfGKXqR19Kj#pD$&AH+mYy_; zSVkKS8+T*Xuf4LXu1aDlBLY@5OD zo^_!4&pEaaQ(V5;qT+T?1Z+SOo}YDq*ccl7okYCmnRg0?;V}fVv^>mGCG>4!b1q#k3_@223^gv1TJJaO+Z0AxV!f z9`SD9;U>vwCdCULaB_z@n6_402yqm~!PY}?rqC{^2X9CLjcf)Z>VA#7ptdM4e4v?$ zrRL)q{9tWEr)<+L#>?=uY8hvcia zSHW{iUeYldVeujT0N#tnpZe)hrgQR{BBa|OGdV;%F5S~d@K^JfvfYz*js2fp229Tyyx9H`Y^(j9o7}H@e#yRl<7TTYf(U7o~$+08#K-`j^XKx7F&wDEl zhnQ-8^UYVy2lpQ|cOHDwtY2GhUSN~Chg&jhQiXtd#&k!R{)A~>p=v04+$R@!r3UNQGQFNZTmr8^<|_*sE8ZDexVWvv7WvevuXtWWz7(y*Eq~uR z*DFgXP71=uAavQ*ygpx4Ny%2)Yi6Qw%Ze;4DGW8+bWSunh0M!oO3I7Bepk?z4mGZw zpc_lIclUjsqX|F&BrYDQl9y)y?64_A^NK_IEZM#ar}0ceV*BfiCBR7#puxr9pZs16BajrT4n`IHw=0LsqVCdZRb{h=3j+&1!qVTFD7>0j5A5! zq7z|Sfm(Q`t9gpUPlX7!#JBUzrc7W!p0^H`K~E=6juwN$wm#W4JF%|CpK&?muJL#k z!Q9D_WpiNiYKYdJ>lSi{lO;D9o0lbHm2$qa;>wHVrkz7aA=Y8F1w_fr*Yep}Vx|}R zlB~cp8ovz8(jE{F5Vk=fIS3gbYvSc0f6DvZT46yBsuMq7j8CY^yao>dmqEd9&Jn`SV2HTkV_-vCVab^*h^T?~@S6eJHtoUB z*f2T3ed36vZx!I|=7j0cWTCBcfY-g;Dt6>Ue79=5uJ$=Ida(xP8N#4i`Fy9-8)Bm{ z6r}hPezv-ZHK^`gm74=Aa#y+D{QShvE;Ps@;n{GZKN*4Rov|;ouR= zg$jKYlO6q4cQD~u*}mC)eE;KSyo+t><8PZ?%JA^v!{$00LY+j!Z6bo86M`HNM44ii zgh1%2jZ@1(9Vm$IV$QR&w2mg8LvV0Wh_ul3fPWn?F&npTHQ)Zze}^u($U8V`mJr5Q znJ^5b7o_jFB4Kq>4_Q^BT!5r)BgUZBo$1}h2zE}=!H--Owlm@)BsnotF!d@Ai)U9e z6@_rpTOTvep=TncW_-d5#yKZh_Myp=8d}o0LHLx7nYyf;Ah~jiIZj?M(}AyIXO0l7 zfT*pps|xb3e=L9DcW5C!ps%YKmM5&mck7c>`8g@H-IymRlY)QJ!L%*!uR!CN;<%bZ z9h@=|i}sm}#tP?}R3JdRF_UR1Q4mV;;-ZHhh3?YlDX4-2d8ssTUNL}o70@HZf`OX_ zC%l6>zy+a}xN4{!yPWWP2kFS0fdpiwvF)4ez!`k0hXGQL)nTZ@uaDM&-z8TH$h~B- z`ljqr&}>szl+sRE5lFe3)+6YBb|pYzR)H&hmWgSP(#`!J7yCavr*YJ zO5cExp)j7+QwGdzN|g`9!G42NLFFe}73EN2N86;bG>c1a;Ah-Qiy}lj_CjO%h@TsG zHL;WT#;l&aVsC?oV|ik44=*`ecuHDb<3v~XoGAF8(*`DErqs08vOzu^*Oo&t=6rQQ zs!N|50-i@GP+wi))w~Raf9g|tSN=lME6thK^6tS7Y-E6i((Qnlu4RP7)XI#trVO;`l$ecC5ig+PPNQor8P z&CX7I3<}hCwx*HRYpnT+X9hDPRMHY59U#xfGrk?T;BD;b%y~N(#!UN$U^;QIjFwxi zaY-xOQj=;u)vDN0i>vu6klGEnRB8Uk^nLIHR^YbzNjJ62F`FSXzZvzXg5S+koRSwX zoMgOhFb@YkDOm;Sn)Vn(i`IZcq!`cbzzoLIyhWa{tmZ2{eYW1bFJ(10{KN}+E1f;@ zEvK|{#oe4y6op=H291{uzRl~=D1_uJP~tmrDTA&2QF*{Z)caC5eBu>@5kKpw;vazX zKf*L*STRX{!gA1jEe(Y7SzHP}(xlv*F7LP+L^Y7jn=cu>xxxnJo`mv28O};-id6!S z+I^OPfx>+$n_O?Sf(JOJ%)Cjo@@(D}n{rzLx6%A;I*}(R$cts}8I&$U`Cf81E#__t zwhW}}cn7(A_Z}|9?!-oP8%?sdSQj1lV8;I@OG8Zr;F zF@8=KL^|;#NhcwFw8=|&cIn~4rHeM)D5hMHnH;loTKd(Nc8sMQIH*$(}!!UD*S4_x$)`+ zN25PSyM4rojJuSZ{Lu8TuC81Wu#ZeZu;$*Sv^_UJi6dwl(!cf*)ul4`fyG(rU< zI|a(52Me#MGa9-!;nK>9uXSc0C}&L0@H37VoGz0pBthyATBzxCCBu!oOeUn~z;p_w zNN(iDIDr`o6OTjO?VWP^szQ|C%$8OTEcsMX*C)*lSb>B86y~z}Ls?v1I0whL4+I+z zgOcY)+K|)8;#EnQ2$_Lxy%JdSh@eWvgkM=gAz!_409%%k&cTCzkod*Lr~;o{9tL3-P7 z5fR=t@(<&IWQ*tzzkz65p2`S`Bf~Eg>xb$7wr@l+cH^L{;H29_wcZ{&qz$lwSX+!a zVhX9+3@gmLitDr9K*lg)xO*P+?QhN;pvXyB<_8+-OYCPHL)4IuXqalb)v{{uEPmo@ zT&uy(hR1YndJj=>6eW6x(y;lcA9B+h-wr+rjPp|=alxm>Eil{o=I6mYp^5Ko-2`#* zCnJkH${29qg}+N7ulOTe&4@G`snL>VF+Oob*Ydjp?nI!JrA!=5=l8b>(pB0y$ksH$ z8+vJ~;=S5)@69Kg?`#;RGo;LF>@!HVL{!7B-+Ad|Fuj0%mOjc;(xc|J4P1(l`qEa^ zfk#~GfKas4@_|^I7D1Ph%T#K;s&-X7PCIAD3I=d00HUddwIdZ)I_$=yMBXmt%C{MWzwb@SWb{uUeOTUl;iLr~w`LNkWexrcX&A*X4^ z$F>6~8gKJIgDztvGrk+MYYpzZ+ZlDL{tgLR7BNEuv$XK>S3H|8s6=Xl6KTXlxX z$`X|@Kmh47>@Cp*paXK3y@26BsXYg(hPd^hqr zB{{<;T|0EmSrC=zdYBC0LQkQV(HnD^)n@rVo>VCJn2mjIE;~kJe2jpto5$YshuGvV z#V>dtje8e?Nt!8G$Q@lK8nM+y;E!OUMx70p`de?bo4>R%wSgNEad|mifo^ydVK^UAVnhe9oj(|!}dMHrq&e&h#IZ88eJz)nwJNWM()J0)7Z=KE`vzVgprS87q%SlF;O1mm`)!3m>yR-T%-eoRGmG*I zKyXQaMY&cysnwUHvA@f0{z_MQM!H~HlCqR>Gj`;`fn+4M+i_({&G;(bmz>Pl_7%hx z%!O+|?!;~azd2c#7AjhfSotaT`?Oo)*;aLf8cjKawdOc($Q4egyiwoH5s;u)7~YdwAoEiQwckpCTEs6(bcMwz2H%8Djrsaj7>rls!F!TPGtP1ExnjIc|cx47Ql>$`|6(2iIVB7dqnF&Y}AZ9n3~UAv(9@>S%iz?tKh(W z4H3@MejF@7kHo^tJ@sapP3un_9UMH)I|Fa(sHC&AEba~qmv7jEm*ZNh#VN;;xz*qH@1lg>D=oc&`y;o^Y4n?3CWCm3uB;M!Vk~!=ujsO zjV9J!lZ5*k3VlkM+K7qedzWUF-xRhzYC1NN3cvvMo!TS^_-GdatdJg+9S}bK{EOz- zUwqO0>dRj>pM3HOf-r8*m^H7uWR39b1lqy7hc;Plxpa@p{{6NeMEqu zBK6J@!Brd-uvm(2&}dINkY$ADl-Vh^<$-yAkJ-wNZEV@GsdKP) zA~s_a;&i6lJ_K3@XbBcKgw711fm-tu`VA+M(=MkZ!XG`%ScV-0^AVfxIH2Sd0eZ>- zKM}0KeV|DV8vMc93ML~ro3ow6=Gnu?2-9pxr9PK%r*_I9eC~3g8$hAK7-2*WzLTsm zTKyA*?g5?Z(gyjU9o6i@W5>5SO>|p2yLy0&#+R?Y&%r$VulAc|wCJmt1@)NpjIob( z6Rm;|Opd@z1C$w^05IWD5aIlGOk7?9hvVdLe$@Pm(?h3wPnu`ne$~9(=fEHQx36)U zEPq!Mu9rHkv~KTT}8FzXPyO@;N@$6erxYSd&?s)r5^Z{&q-(LYTTqGPDxql zQoh!98oHPcydgo-Du6nX5LYK41}E@taN*>q5lpp}HoOJ(iH`Iv-b&7 zI@Fd*ZvfPOGZvJTn1+kD<@qZ9n!9v)6Ba)QU%V8a^o(I&s4y&H6xP*}!uPKsE&h&k z3T$d_)r2Zs9U@SwHCL#wW&OFlFTT^HprLG~u;<{Z{j%m6b}*dh5TV)u*x)-mh{Xb1 zc1~9j_$j33$Gvdp0pdvq zV_wdy*0I8|afGV?Y|JVk^YF7lxQ>(3*wn)E>ZCwe)~|N`dd0Dh)h78xAy^HHJmSR6 zRTG7D>*<*%5%h!lCm{?%`MsGWy#_>|}D#wV`r(C_A(!Hc{qFDW21gjoCp2NNQtuFPgJ zM2lV{^1<)HiL|5wrf|`mIY9E&va6*NzR!L)8_DIh{#npdhtX+HO0LIUtU>x|FY~vI_bW?3!JwRv1>xsOD9{uIqru&n5D* zEH8dg1zL%VLUjjshtqS8pr38xx{kwv@X6`sJOzp+1oBn9p6DXa4Us7ya68!4x@pb< zJFS%igy6tN7vz#ap?`)}RgJud`z)cE?k%C!hO!ePb@7evmQas+k0_>=U%`=Da^sn9 z6+KUVhyZ+dW4U?y`#)ipveO*AdKSF3xnFmaD1-eulZXRmwmGd;6ARoW>bC6|(~p@p zzf4Ak;Ga_l>D2TZp`df)&I9bSckso2K)KP-A3qMic!J^v6Chnq+ILv93KcOsuxCOr zck^inpY2m7HckZmHr&3~++MrZy!!qj$Fl<$njD?(q9tY1DzIE(lKL}frckZP3Kc^J zQU#7cK-VT6Lh$q6jfwCIal`Ox%c)nETevFF^Nss*QYgF*kLdn#0{+Uhni#}RKUcG| z-i2kW;Msl+7_MYkjb@vJacoDnDRJeWGOXHi+o$Prh1DaF%kr`S@l!Ux6XKH6w5H(~ z4=$RmXfFak_qpmnT;Jp75ux0oCs*;1A_U>W7x7D;${;Qv)L8qi2=l}P=iFO1;1o=4 z`$Xe~<*_dkzTbieSII5jzVn*=dicq#v^#^Wdt{0N?(unn@!TEWm~HDt2~LM&ldwHe)c2 z0<0Q)>sZK6jMD!3u1V(%MzHj1%}2EN0Rl=k&(e3P#dk7S`<`Wla^10xxY}=w+0@)) zqx160dUNagEjHt>Hs3#blD#NSDs{=3u`=|KKk3Ip585car|>TiW_3v7F*$TnHluxI z4+7o{PCI+e-pgmrD(55&X>V?}u5M)0w^7Cj_NjX-=4yi^U4&ZeMz+7oU>rVmgXHEYOWEI;CtCw^NyPL z+k#XuQW#TG!^QtfvvUbs`kGZN<*Q7(iPC@jS2CG(q+=+k;p!tN8 zh4e){E8zLvJo!%tn!YpwV$IE~jay+?MbMiFEop?SNlU!C)Wp31=}_=8{aYtl0O$&X z8!1g2Jh=)h9jo!)1k?zZLz`GHCUTyWBRe_|RLJu4o8VlA!LtLL!+{-?dDn8$)kc6k zPzd6s!elnL5?{aOnjEDgC10{^PWj`+%puDGN0X+k4uF$>&9e$4y(zoAWIk4n6N@c1 zisY~FeCw=)QULQdq*(>U^4`Bvu~u)Qkue}Bkl0I8aV&W9$h*=;TabmP5>uSZPocFe zT?@S+lL0At3k7%p5Vl{-eHs7jdyy3baTYFOPUt7D(pup>bTVV8 zfy>kPq;(P0Yq`v)mPqVMi0O&>_^Y5CT!JKZ1|Uz+(_e7xMy*fdLOX@(lWfYHVxF?y z+`IRn`2sEZZ+`oC&4ULIq7hRomwPpHU;tB_>)NcTb!w$f+&o zo4-~-n3uQ3J^dtj=Hye2Y|-c;J|ajmLFDu-%?{S}o`T&p7$E2&GVh_?Mku%LCa6$* zx&`brso*q5cj$Fi!7cXR2&v>Vp^SY7@1E*b1Y$k7P%)+M5is3IHl%Ex)|pL>(Sico zZwl-K;G$XApWU@PTe$iIKZNLAPXB!Q;!*Qr=W(-wY0cL5E!@P7@P!TDXf~Fy@wUFy zBEl7zmOw9PHaVnGbls$fhMM0Jb=B*$xprd-o97d>+-Uj8+l`944Ah**x^(iR9q%g3 z<}qKQKHZ?Yx{7aTI=BPvrFmY1j-3vh)jt2v%%bt}{`gU|_xwro3c;w)Ns+pw%;q_1 zfp$`k)?K3;Zgq9qMJPf_hDOqpMyupZoksvy$a6yEGj$W87(A?F1@iEfd=4MTZ#GiI zs?go)RUTHDJ=6ON|C~6fZFB+#h?LQ`Xgid*c+<8Q^5R|-7-?ZEauPfv4|0YNuyd9l zP2yJ@NBNUC3gz}y<~g8zP8`RYEResGJzyR@$H*K!H+s4s$l$CH{!37sY4qbaf1B0^_P6It^4lqfx--~hpT+TORcv8E4LBU6@(R{F#CE9f3tN0pg`qjLHlpxdXlF&Dde}P{VW3Z4KlFW zR<*R+Serku0zxCJjs7;lbixY!#RPo#QJ6pSp>~SWNv$+PO0()@=ttr&X6L;)Xdmv? z`dduqo&tsF8ioktPSzAGt=~N=*ZSSpc`qp)033W=gwVBQIkc*HbCGZS?!ExOE95U> z{-Orn0iy`?@G5!AFG7rfo$O2f_%4t6$~`d3Gasl}Y2h2=X@7o!Q~TWuk>r7-;<|2ecs2jiH=;a}bo zFW-dE+V8$ITWiU-5IQFo4{a#rPRjZU*bFKah7Cx#fl}~BdWts41lp=9;3-g%kO2i- zX@tm|H@BN#{pRnQ|NOuG7c}G_b9mzxn&1sIP-w8Txeno#K`dTG+~h}k%UXovB8V|I zmBEdh2#(@HylgH^iRCEqE9v;lXX7fiiZv*u4mf}WS4_wFah@P-y0H-Pk`4e^X!MUy z_p^D>Jqlg4WFrRV5zE*e?z>BvxnQR~!ev{34b3_Oxvu#-Z0c;#qD9NA*$jiY_S&f2 z8MuLqR=eXyPc;7W+c};%bfKr4NDO!oMA4*+3loHO+(NEzFLNmA2>bJA%??_^mruWC z1L$G1eeDK)nBWj2&T@$;Aen z*;X(UP$4R~OAc9#!;Q17B?6aYCV4-{}hFy3T`f_!S?3lYH2G7wRlU5`P3$%fw8%kwQ9!S@mNk<#AP8xj-Wwk~vj!t@z* zTotg4C1(X``I&E^8(4|&SMY6PDS&6`opf2gw;elz{c&kSk zNL0!X(DX0DCQXzhmN=P}zNKIdv>;Sq1{V4H4L}9`7E2!!)o;e|GqBr8xdMf9mp&pZWpgM+&~b<@e=GQz}&@5NG|JoyK> zXu69zo&q`MD`@CPn2}{eC=&z^q8hMQWM!2-C$!6D`YPMsl=gA^_TAU6Z zFQ4+Bl^uA6-pf`EehB_7{TEh;Us&Ry$%W3+Pm>%rFhFe5A*Y=84-xt~Wte<>$E(dT z40*&p3->>SXJ{*$V`WT`kKZBuAeHRU)FBvw5|FCh=y%1`xO{S+Q)AFkkn zNbo*E1Y2x>f=Z=PwfRt}(nY*az87ijAPj1luQSHHgl`m@CJ3kN2tk+VS0caRucc7? z>cI&Ld)kdle}y!);F^ah5Vxfb1Yxg`haOWDI25#Ga|JJj#FsFUBix%LKvAtHu*AiiGe`uI6>j%Qwa$ZwIJA zY3HW4*khd)>KDW7Pm5o!{$6xTS=2HYT8mCJ=f5C31xI07zQv$`?1XEvOipGh#XO}| z&p0R_LSN%?Sb93_c;ta_YRM&_W=#tI)+eV$gJYqm0=uR!x_C7Bz2INIJ66!22M6z? zu@GA;rM(uRje&`G3WVj8gMv{Ipc#CHn{RgP{sWiNVcKyj2-0zy-mA~RDMC72EI{5H za!Cf}?%*3}+!d63l0L?hs@_LS2=aFL`H?19A_>(&Gv(wVeKx>qKM}z_c|aRY3?PPW z!)0H~WDe%!U#}&!PbpU^xA+tio0pZSW*KcVwCQosju6h+L>SK?3@-QX-fzDA)o+{M zAe7&_bC=DH*AdD$@D{R-$p?Zinq=)evl*8`ZK_6zLU@F{!k_|~Gz=Zc+j2xuQKJf8 zMYywKt)wz|iGJ!N*gzrTp1HXW0a3rhQ%=}B!p`~Z6pfeWEV~EI!BbrDX?r|7#rYGxY(a99Rir-!}k>Ic|^-&krsIJnj9Ki+Bn@b6zY z&%S%u+`02HKG1JM8*JC1y>|GnYHB*vqahDiO4I#YJdnVkN1X7ekSwictO)289vVh2)6GVBfKwjD9X7LC^p7UnPhap zx5EH2#5AWrLXdz5&e(IX_0flPp7_#!@?G=zyRVrDL<$jzs1zNLPIUNzV{E$l7J&x-_OKSc@|>Va7p^>9g~GDe zM8TRiCi4(q__n+H&wGVKz9s};o@}=Si!z{S8^%w77VEWQV)5`3swKss(iFFry7pq8JIyf>W_i#!w8wq@B12yjVg zTYsCNK<6E00OLefB#D`gbtW`kM2M&ShO|C}Nc*VJhW-E)65309FGcMoD!sv5x)aH( z6La07DvTc;aR?YRgUEbCnTLjOmqWU&Cy`JI;7XK}#})R6sO?oSPCI}wu_Jd0h1Is4 z1d2P;Rn9+9U|&-E4(RwhryaSO()t)WDWEN?DbLvoPR!X?a`XBfe5GHD?fNd}TPIAS zzxeXk&HWEQKseuQ{@efYUz`8?zx`MCfl&Xzb6i@&!;;!~D zF*HMqKcmml^Uw*KKzqnXC*bJnkEUBznyW@3mNW;>kA+ez?N=U1nmY2z|2%s&cy)Yr z{FWoE>%V4r9k=#LJ|f?F*17Qy1h}H`mg3`^bCYdr)0!7xe&d(&P%HWZ%0i@zm+y2} zAxRP|YMp)*MMQCPNB#=uZySW==FgZPv@{l`8g#vLRD0wKPsn;GGo2eTogAob(SDeP z+Zyd>uf`N_>6F8Rc%Ex*TQF64s~Y)jmXha{2LiT#SzpD?7+f@U3Eg=oex@%%A_GQn zYXS4kU>8>6+YyxxQCJ2|Gi)So9IXcoNgf4b1=(nOxyrBn568X^-IrGv9B`d9$X^P` znuAzRza&J^SZLMatN5CNa&K8IW6HB|bxhOOypuHP=Y&U(>ADa&#qK``$KtK3WXK=P#!+iOtw-j38n1nVykE$_A|dEXA7 z#4YhnQ`7m*v_AW2z!b3dElZ7uY?#0v2{c@OBhu`_A zAmM;--6|ydy%eqBX?`^XZ{nL5QOhS0@s<~b=ecKubJJG`?XNBn)jHnB-QoR@K5ahx z{MXHAU;G*Y{Q<5I)sU~nAMp^|&6qS$S)v^Q9PN?E*=q}01nV+bXX7Z@nsX*PWRP~n zz@*(~?8}3oa7sKkr|DtCGwnd^oVZ*4;aGGWvm$j z18S?4mDIY%12ir_GRuR{!zjqm=DluIPjmaX`_d*fw&E9h)sL3JOjMWj44aTcZ6{T zqfo#KBoED2*693D$z$ES2rSB$u8s|t*3eXAhb|rAfg!$_XJh=KA7BTJ)~}By zzl)Y#tz!=w4HayF`IL!}o>eqYw6E_Xm{eDau`@@~o;NpWVE<6+6~5(R=4t?Gw4t{Z zp2Gu~yg|DYG$-en@+c5%;&2SDEe>e1(N}XQ`<5bv*w;|H!rSr|b?t-@slII#e5DPm zp*$BScq+`Anjtaq)2g@?59Mvi8*e8FrrXzV2S*8yxK}tgoj7YF4fd>{ z*cNTw0LTxZyf49&7r_`sxF@DTD7A1}#`K6`v z-MoBdVZAGHxic+)z&3P;%-~(#r9ZU3;46jm)C&onh-Bizz3o{odx0&eWNJov=c%dY z9>46wmv#!p)(0FGlOYA=BfRhkU+uMRIDG=9D`^wd?+R^7L3@eGrDNCi8{3p&+B|yn z4J$)aa4$$NW-GKLi%kJ+Ymz{U$g#O*at6Q9t}Js% z4kv(hbKKK?D_VKl?-@ABdz#<~L0HnHkdzxj6iQqxxA@L`K&PF$`e>T^9a0l6>c=PP zZ?LWXwL{6Nno0ZckjFXpYlSlo~K zolcg2icE3SMZAd}B?XVGHAWWea7xGQ)pipYX3{>Jg9orU2spU8LC8%?T#CR9Ob5jz zA+8e?>qcQhi(~~SJ7W0<66D7%FTs_;E1H)iFQu_84yqJ`0R;THm&OC$JNXJ7{2mZ? z`laJ9lOx**WdUD>AAf2u#ozLD7$nPvsZ|D11Uy{mDO`U1>3wX-{{c<;Z<>1_q20v` z$T~KfIYE z9GXtS$3sSX2-yQ%vW?KT=^4Ta{0>LYuaj>3)^f9XYc0GsrU9J6kwK<38N~$zKg~1soWp=Q!?;$*&;yuK}THJ6;tw&R* zzzj!3V2%HC7)Vor6IUAC4*(hp+HQ4qstQJ5b;QKljZTJXReBp#xwQfTrAqSI+frGl9;U1PtIE z%L7iSKH4?42w%t>B%|XP(Mga@Xj~P7X%{qriGs4irTjvC={3PE9xcNnp&h1fXq3@L z+g7METeFzWdC_~~*p_YYcF>x^oH6-;N^Bm)*E@%1d5Dw#z_oi;U==y-=QVTkB-Ze& zO^UkCgx~M{ToF-V%6ItQe?@=-FY?r621I!$&x3D`YfQ5tufX6N$t@{ug>S?&LNwIC zq1Q?~@paV5ToQO8J@W%bT33DH3Y2*Ws>Tr?>Y(;d!B0Ts0eN##47DF~^5_Y-_F2JQ z+yFLZm1Y0Ycco0c(*u!SdK9Yct8^T+#){Qiya{O^K0!v9vZ4Gsal6g)7dy>40=b@B zI`l90jrKR|XvuHgVAJ^*pEkESI7^clH|i?XZy|_h6E`I^?htbpl?>8HO})MTDeY|K zdY?_|E1cZ>9PRx9@wVt6Q82<+PhY%5(V)4TG;;$xX;{*op&0PgT>IQEcuwKjsr(0g zr^*veUCy|7LI~?|W-*h?on}DBeNMg|V=C5Ta%%u$K%Kw+F_Q_;mKuRpwqvk!Po5fh z6=T+i@5G{16?f*%&ptmZ_S|P`XawVNE%JwBlWBRIZ+`zD6^lgxVct-D0aGc*udRpD*cKCA@x9qA6l=Sy0AvN?0<`4+GkHl-1t^6n z#=9_270}dzUf$SgnH0jbao6NU^AZIZH*pTp;1nwSOs0IUHR#6EOb&phCe|BD+?fj@!l5XPl4O#qLBbo1``J{ySFTnX0R_BB=NI7SUwdQ$(O61AtIRb!L$@L zd77o=G^nw*-4?jQ_O1l)+q=Y(p69%S?F%muGq$vrgLmN-5{OF%W%2QWG||!so48a# z@Q8O23Mp^rXZeV4t`l%4p(Jpa26xIr;mjiko@5fCtt1<+(4cBYvV;JLY>q$hG0XNF z%{2zc&p!RE`869mKmGj6oZhMb;So#COA4G$l&q6d!wgys?kwE~cLein{_`sRz{^cj z5h6%ou?+Rf9Z!lH;2a^JQt--p%K?HGYC!K~xK^+zuukduUxL5ojA`A=xC0Uon5tB^sUXEzrvB z$QO_e_B~zX9#$;gJKd*t5bGcO350 z7&(~I*Zv@;A(>W(OV%s63I8w+LRedRivhT80A2<#oZW?1P0(0^NBhl;$vnKAQ*f5c z%Jqw^Q(Xn5QPz9w@kg#{Q~|UPXd;D1S;d4K7}^zb5@VCPpp@29IrCfI zso+X`)r&ug$PWS-O9u6!!S=@ndcOjiGx&_XI1(uih}oic;?{S*t?gBkOByscu@BF4 zz7?RWjh>yr1ch$~nbDclZkz?0luWsK^+H)$TOkcLtm|!l<=K^Zo(;P$TtGxI zM>~4_2)oR#m1_$Nv>DEPP%6lovs|`AZsO#^P>MhN>o}k!jBNrJ3`CIz+L8`u2Z*{< zbnB z?-I?O!9It(+o=&bdD>5$3*;=Xsh2&(hUnCTfVV^To*n1}iyr={j4|!I53Au_M=Gk% z8g}0G{t<6YFTcKoo5g`@pw(jZ>iTD z?5DQg?|uHDITp|j0m4nUITTp4mZ?qh!z^%_q|J69VEW363HeFiXWTvdVf=zifq6eo zb~y6KW0bS+Y_J{C3To}(1fGqj89Oa2X8Hue3%HEC7e(Uvq(A0K9;jW;h?&+yP!ijF zc-~LfC&ke`w-dBscF^F#Z5m+tDerNBk%8S9Wnd?-`Oq$W7L@OjFGup`%Qze`xnR^o zt-JY=PyKoFS#$W@pLs{261nn$G?|IC!;mE)$&rHP-haIGTRW<3pDaM-eLAj`x-w%( zX(NpB_|Io~PW&X~weR=O2~(51-(Tq`mHk)Nqok4Nm7ain;*2(q=K-MvN;=;6zR9_l zwsF`}gf{YK`Fd?bZMKv3Fwce~DU9sQESEM}t;nC2due`iTz z{(sr;!dMpouyA*p-A<33KVL@AKS@J3QAW{VAHg1f_ZC_-fwhQ5 zITkcg%Z8|66CGR>a=mp64-PkPum}^+C}`wHh|_Ju*+LWm2v{fYb>InLaDV_`T4+i} z9XRP3hjMjV-9@G7!AXmm1Avf&ofeB_1?Jm;<(PF)+1&WV^$G%58_`{CrL~83(6)m~ z(-w<&#sNw0pt?m_FEf4S;9I~lF8F67mDbb|1}*Rm!Q9(pM^kLm1?HU=<}h4qtF9*- zHOzZ922q~iVv=%I74BeekoIE)AmbW=fEd8~Hu7n^nRN)1ljR?KW!MKCq79Pign(AT zoI9xo{F0A=Cg`jS8>uYB&;OowTbbg3H)eZ)fry)PIS_#qWF4A#F$9oQ4%`T53trfY zDF>RvSoYTGT?RoJ9m3yey?I~F-;_wwF)3uv`Ya%BG4K(L-3~*zgsH8`{-B?*KDT^r zuxu}sZ$WV6n>rZUZ7`+FeAqBhmNi}5kIEO{)t_yg^!Y9MMGhUnH#3V|h~bG1plMwE zw;hQ&8R=@vt=!np*xnn5;nZ4Z)(zRtMm)%)avEF)P<4Yc>-i|_9A+>H8z)@cYx2F{ zZn2Pfi$UB5eBRtGgpqEK3z_pIfugFaDehC+Bw_7RdD zWwhVR6Xc^V47E)qZvRjglqhxILCQ`UB3)OEtdpLfj10MN0`09HpWuVwy_O@9sV3)J zO^cmaQ10Y=4&&|}URrj)X@_#Xf80eMWv-;t+zrqF@gM*3(Tf)^9wO|*pZv+6EO7S2 zyS~_u=@Tz{km3{HALzxMsor(RZ8{~$v+(@WWKv;oI;;aF^cjKdw^nI<)P$;aR@Sup zR=X^y5~zPxp-X*6b$l%NYIgo4%4klrIR|m}AKvM0!!|9zoxp-K{dNl+AN0r<{AonN zvlVX*dQ4wGrvk=F()69-KPz7kO2ggZD<4AH9k2GE^e*JNgOSWEK%9Um4K450bURql zoWsKgO%nYbQ#(RjINp| zL5Jzrd>X>TFJOOoC18l#L&~epKvd5NUVCT^K%#WQlm(sS@xw>UH@@*re3HLdo_ONP za)fE(DYm?i4Y{2hTJ`kUBpNUen9_b!vjVl{af_EUx#ui!NM1p2hgNubBT?W;D>(R z1*&fKzNNh#pkNO$6uWF?ib0MVQ5TDvjy&0ACSe<3rN4G7hO!IfPJMFm>6qYr6gPAL z^F0A^2YFz#qCvpq-Y6R_0m?Curx@8G5zsQKNdsxDv$0DT-tDqbcN;Sr)78Jd>j{P^ zZ`;4dlxT;0%?%WC+e`;rk#*?zF&LgQtJ-ecpKc4j0;F`amkoi4JDN}eI|G*i1m6jc zHFq&B|7l}wTVUO_s_dZw)o;9uPzC1h4jET`@H+9}g`>mZNm^94R4+v2n{@p7yk2ub z9*7d2lagt3S;09x1LRlx<46bT*K-X+9{JX|Qnz@Rb_zOcdu@Jr$B&{R3?aQBS7~QR zFZZVFzr2$So-@FZhO~XuWoII?xfiKv*5_iy9pv2sy|afB-WvyC=HyHQ^$#=|zv1v` zI0Hl$Uh+%|{Np0&ZNRo|!WaX*ab}&c*&-~9kM(bkOf0gVVU1lrXXlXH;ts(q)I4^A z^)2VhiPLAOLd;Y_&juS|%}!!cLf_%KlnrOI@OA=6SPWV>@!2wN!ta+ce9-(zTBX_i zUB38n=s9)b7z67mtmw$^U3eg?W|_J(<;yWz0ZM8ivj4gBJ!Rr=mf`v%Je z<(j~>hA0;_=Ib5YBcd&9JE$)66pZ^Sa1gf3;7qau?p!(7*YA&0cfd zML4{n3J#TDzWLUF{^px+{ig)80l)U;7hn9&U;pf9?{IAcuV^3mctl`cDZGo$S}}Q; z=l)5i`a<-pV!XEJzE<-uIB#s?G+AL{ib zFTC(VdExoz%d21hN;!WX7l_zU2Yd(?=*ZL*)rbLXL}-U1C={%zsS=dOQ;3O6N$;GS zyY7A0yw4fH5$cl-uE>j@*TH8mYd#}XG{Zp!q=ET~{*XI(ad6knE{nN<5qtosLiVza zHl4G!^kZ!9Gs&V#+Xg*C2sX4G?d+(LMPtZroFf3)UHH0(hCE(Uh-Z+|r`qZfz=N|e z8vp=z>fGCOtD*W&-nj5<4g2vWG}L8cql_InSw?lghNe8DZ3d(rgxdv_bxILJ1nUlV zaIc8}L^N~o0YE-H;DSdq(G#t5cp5i_*t~9_p&q59XCHe2u?t$a@d=IHx4z2(#wrVK ziMyn?imA-n-~|RN?z(yi6B~9$Mbq8JoTkNKCfkz(#u{+1MmR14i9s;a8m=wPptB*A zGde;v`N+Y#rYFz<-?V+_qts{Z)VBcAE{4>sr_;jiBfF;Vp)JR!OIaNRdSgb zK4CP>(QsjO`gnF~-D3gm&MFIf;p^5an;fx&YnOq`HbBjak*R$a@pdvOfeuWCcC3e# zNefe-$YZp)44R-(rG_$vkVG~?lxYVzWx%CtN!O>4o1Cp`hPUOZ_1XS}GNUor#1{T0 zi#`Y8oxpyCT_<(J=fcNTTwSh1b7qU-6%ZurI0D3xS(-nSh9|UD{LA8J>Y;-ml7uD! zF=wt7`lhY&7AVd?-sf62Q1`qiJ_lB|lUZZKv-B0jWt}m69mLi=%OlrWMB5H8ZA;CU zKe$s?kXygEZPH#`S+dQrO_Bl+SOsuZT=l$!cN4VU0&v};>Y`8+Ar%JlP5>OB9>6aT zp_}V5>X`LiAM&mpa(7+|j(l*9c3gUNmDmojpD3r!o-3#M&9GQgbG&tYfE#X-g`O@N zzH;?SS-y!`7&N-mup6VfD{s~#pp}lvBX6@;g6HG~I;jtuw9DDYtjAT4}Ng+zyBZpr@zk!|1&?kHO2P8hj{X#k5s)LubUC79gY>S z9{o9Kp83A_`Z1jB>^*d0XYS`moodk&M%7*l#>c1(YRq*{Ctx~A@-jvcC7`b!Q|f&+ zS-C$T7Pzm_0toaBr^a;+Ks^LtdzeG+se#Xe32x>_0CPRMLK}_CuL_ty4E@bF`?=?$ zkqGVK{W<-agKNvuyM0h|S4mkBtl-LB@2mRl1N`;7ln)V^eA3IaYN}!yHom^yfS|a^ zgz26QYul1Alr8OSI!NPl+S?fn7m#X`?(B>pU9j(EEgedZzExi5QE zge-vI?Q-B1allPk7oOLAP|wtUkzWT&nT;Wzqu6S$yA?L(7i<0 zNq(*z#RJPE_RoTH7f;dKlC;brKXhDzIj5wnriad*d$s5BsM2FoBcBkJJb1qQFmE+n zxK_(6fk=;hH1ES=|Cr&#pBu7_0P)i$TU)I-KW(LVuwj$$4(#;wp?$0xG8cvkK-~&k z`qxVivm1#F@Dw7qDv*p&#v&T)26v$NNZb#~Upu4qRQmYZnBNAO3!u?mo+@!29S zy_{%XvWHe^6K1dCx^QU;S8$>kfGR!;5&AK>&tQlF!6=&ZY+X-&VxECoPS=2HUz2Zz zQLP&bYFdRwqG;Jhup95-60(cdd;}Mc@?vSJU9PsVe_y2$#wPw2e#X^&w}Buq2D1?~ zerVk{Hx>b2Kw)gc8NmRkTo}uM1X?!HQdmg63Ai5BCU5d^6@QnZ|up|ojJuL*><#%kufm_?8b^}x1<`eZe+ zNXU->RMQNkL{^dz4*^$dKZhyD{dOf~LRf+byd31XgQX^28w`$?7)0p3$Z}(Nnt#?| z!#fBJC|A}=R~oj+tD@|nCKtVTssBymX#jo#ngBou_gs|{#$#JX3Y__G%f>sd(^^1- z-*YcDHD124j&kF`ys5j85<1slmt{)ukYu&j3s4wF+SVIw?3ImS>X|#r3cPI^9JJXV zI8ew;9%(CE^&a_vK#NUT8N}<5N92ufMtC-ayG{2#NZB#aw0)VR9?l&;0$@H}<}m$A zD-Rty05IHzkFJ#W-u^iQVO)g5v-v}FxIn~|Y?j$Q7iVYiQ0dhq&0MA7;_vCX`EugeQGoijyi=xaH%1hGcn2rLR}~r*V=Wi1Q*S98+Z_F@J9J{T zXX!BNT-&=0Q6d|tx`l;>K`@vg?4Ydh^^OY)O9Q}#HnNDOE0}rzWzI3E6+do9G(ykWB!0oa_pj0NsZi|*AAV|P;V=OpKa`mT`(Fz zF0JyHp8}B0UZA0Ah(l2CPxH+DdEK1lReeXt&2yrGKHvA^k)H@z9q?3+q_hKi0pjuw zAT@-hSPe1uEXWxdCW1^6C(nX2%ni`Et?*j~1g@|Ub5!lvas9DR*2RrC+GT}la&htT zQ^(7bbR<(ual4(jzPQ!mswLeiVMI&v6qO))5rcv{pHGnN9Lv2ha z;0S{Slp<)=U6f|!n?jqo#~@d)A=*Rl;BjVc3l9^NcQ#GJFMG!gjL=wWzpdLkcN*1H zrwtevytD!2ZIGE~fs3X}YGzk>XPOxZq0w~VtvQ~r|NV(r(P!qE>)+)HI`4$W5 z1`)nVK&%5LgoMA!pWswLCotB|7GLzv-l$O|F9PtaUGTk|weX#@9;wUlPHk|sp$v$C zh-J!p@3bS}vCCrBvWIOl9n2(a&D?d?JP7D?xu_|GvNy@hKsB3jMhPGV{rs22?2H<1 zI%TOWYMU&ttOxF7xrSeS-A(GU(b*TZ=+>3gPp+M*lBfQZ=a`&up^3;g&r)}lY4T(> ztM!*~NnYvpq5R_9JG?bq;)77>X=Xff9Zs5o%AeHX+_>#zo|J& z$GUDW4sSyjC1&2ek2V}0v;nD_g4Oydo!|y#A>{+#)RY^ync}HuvJ1~EXpd&yba+ zbZD9}7y~lAA+~#YXv6bcdPHU$+7(@7qdBZw$7cu1|MAcMS^4vS{lkIZ zd*h9PW5-YI2m0=JzgvFz!yh)m4|JyGe3V1tdIYATMJ9PhLENT01#2q7DPDX|he=xW zC}XlHGQfvx+cZ6=s71E}Z_EcijSS-w@CwYdIo1W9&jezEy4acv@_pDllt0a81k6@6 z!)a3E0AU1R&Fcdn#^s_(j~UW2-6ko|g2^@Bo1bW&4fr3>(d6}UXUR>HXfA38+lyTO zxx;7D3SJ_KySg!WONF}G(Zr*y$fJgD+ojY zR8_6rCJQh3c)o(?m37Z(IwubuD@RYADMwBm0iZ6E_fEO8a49nd$4;GK;pSNc8&ecz z1J`Yv=gk2$z8IEQ;Q=6Pf2_81bPihFJrG*71)n@L&Za_u zZ5C?Ey$L31JR&1;ABgNa7~H`8MBrsZ;KEf#BYY#Mvpgs{7rJQ| z4nW_+gh$PFfG;h93-%lWY=P)@c<;i*ZOT%4i`@BnuS{prrUqoOs9UMCAP=A|ICp1MQFsU2c}=;t9mFswa6nMg!DNX}Y6E$<{z#j%3(hDD@mgqP z)jSH=Rlmn9h03Pcidti9b$HBoYRql3oB^}$WzbJ!;jBVH8u3}9t@iTYL1k^v?rb+~ z60B!dM8jvHCfD+-It_5>V?0E}IsdtKMlKrXUXz!Fi-fTL7|8P;^a#u|0|CFZ2R7cA z!@z52PmDGiaoUiW(Gg$PKpAhquX(EVUm7xqA~(|E4xS!1QSRiLfgiLFF_W|kpuTqf zIX$^k+fGjXmlu z%7dNlX_Si4P8!`klnb+XUkcIk!@)!)KvSY+z+BsRZQ#9@#)BM& zlV)A`5zRgHYWM7BMhy)IiSz&OVPw6XkJ{~~EbFt%8QN0&1)8}!5Q@z9m}WR-st$r~ zyHzxvNkF}Oz*zpqtl5h)fT|h2ks!4 z2Ga(VYw44ow9DKG7Q}thk_Ou7I6^-WRGUjXU7J`MOj^pC7P2J>AWT3sGzEm)p!z*O zuX4>F)6K6(fDNymvfmqDe$e5)hwr@<8#yqjQ`I#sV-I~icRJsW!Ob`XIBKt3YBXV* z+)Mh<>~r&=W?Wlf!T35IkTX;Rmv7|>jl_&DnuHVkjjMpQbV&8SjtMA@R!&GP>KWePJ znlB49gDwFTv}AUJ#ZyD0ET*JG9MU`lSlj}rF92}$4L*TM&M*UwZA=Ze0Ab5)Mx+Tw z-F<*qM)FzTv+vn-U#jpwreZ5^0vPnb<}TVN?IOrMO?YSo_4uS6EGpD^2iZg`xT8lE z%5QuGTW-MmCK~BB?Ych7`4S+CT%hq4ENWZ31<&+e;bK3Fq6;XR8JY#WU_OBC>g8&f zRjDJB0A1o#J_d%d=N$o9Z!VXc*O$u?ZGqv18xO590CKm)(eXLl#bNu)_XAXDi?;$1 zXJA~MNtzV6mbU`%9c+Y^ixNMAn!@7K5gLLH<}NLG)y0fq2i|Vtp`$~7oEg|8j$7E{ zWgD6{V2i~WMFKMg)>#OM7FC&&9&P=zORM#gOu7TC`J;8>EkKl?WtTe7b=nn5-1`J^ z-~z84)F|tw5fGmD@Pud9Zxa(p*E&XixVH`q_yotcr+$({5C|`-Ukt+_Ti*M!_q?N7 z4BrPZM|&*YwPkbGq=HlEQe)?Tc-Yi^X9e)KQQJx6*_kQnYEyRBQQHpf+bJRd@Fwj! zzQA1|kIv`cRKqg*=Rd;i^!%D?y*|GZqivQ3?3CI+zJ zuAHpRWYEQ?gDdPoz+iV8^Ak1jOMq(^>bhA_=%rtfUk<3XQ74JC%XRR&S3Z3IZ2Pc-Td{FFUsS2k(?9E^>)ZpFF?9b$14>A=4&7Cg7wduYlRnMJx#w%V+d zAT4=5%&ZZt%c0!rQZWl``MwTfD1%zMy->eJCoIDT&o?eK&>`*YdjO= z8}^{b-9FR%zq6gvOee1VsGpBmko{5r2YC&xch*x~b_cM#pa4Ewx!lPP4w7`_Q>JiR>h9`(}e-SIvh=l&N-n*7>v`OXact1+g_ zqZexKnmFrM`67?@JKV(Kqxvo#9@q8z1Wnx0C?_40vvYSa!jJlD-w;3Iymv5@K@FP9 z4S>fArV3LmLVOwR_%~nwZh7fTua?ti&f>j5KnuvC^T}a?fCi_9GbjO&3%&x(Ij3he zn*qtWkPr%#8l5)7GN@7Y|82^pIrqQwy2)wHeZ8}dkOBPd9ljM{^gSW5E?Q>{!!tk` z0hqT1xcu7y3@?)pr>f=WxEq)VTFoEK($vF=>gDB|8GJa{*MSD8-Vf@dy3(zHgAI9y=eo2^xgdWX zH0>Zx;$yHva5o*=Msv4=tHa$vW;O<0?w0uzl=grn(j28C3RW?a(HvLEG^ssC>@2Cz4HEB z@1h+9P%)-hT3=w0w302!Hw5hPA8dq{(#41jqo7G|6asWL^J>$z<)37rq~{LTIk>UU z47S5+@ST=&;4OGli)+~mnuY=A`mNTc-Fh{D<_$n`=Mp=w2G$x~Y}<&|D{OCXLsIo$o451hbFk znMqqesyrKy&4+_61}PFu00F2oSK4)tvN6v%i#=mvg-cQg0T(Y_l#ML5#6LOvCd|$; zAZ7u!rYkOrRC9mf(&h56e)t2FMT6zc>9OoIoOKBZyoQkD6oM zOu7)*w3U~p6Y}h$V!Eo7Tl7v8X55MOm}TeYOU)Hh&?ag^3SeJpr;{>KHEWy#(2dJ~ zgm=>rJBZ$s{V(l|s#eP=P0h%ggKq@jMv!;)x!tXuq*VLLPbB9yMNJ$v&5%kkG&ny+rJmbY0b>!NQ54=TdVHP#?(GJ_f7BpyC!Kt`Zx6rS$EOLx%(B~wpn0Jz+C zXHv`!1W5txEX*Bo5v#xmxwqVBaX~q9WVC9eNuh;ibo}%zcp7at<=9om@d)EURL?M7 zz&eFdM!F18kuBY0b^va2OL8W&^}lN~CR%CJNG~ zHr4eIP2*?njqK-4u4zwV;?mb1q+PH|@y{WY190Q?yXIT#jwUEE4pfIc77KX`4QH|Gj9?XXob1(WA!z=D7Dn zm&xyXi~ND!;%3>&(G+L;@X@d(6h z==Dpk8B7kI3D{#l4zP2ERL~|v#R4dsG6;|^HT9;uL521kpFQkh>VEGzvjFf@fT@P} z4hC@h>pG;*>Y=tx^99>DdHCx*LJxoRH?LeMuItE!qlo8j0cFm6j=JI=o z@?Qq1JM$6Sa%M>E7W~dQHNDUtlFJ-^dI3tobyI--9ckFj6L~{YnRL4{Pf@9b`AgAEHWGi zh@C(8QaOAakOy*H2bi{99H|x%&^$3QgP&zG3lLmINaqh9DrdfM9&PM&SzNkSKK$T= zas#gp+be9WgBERV>3aF`U;cTSf%h+*d!h^g4Dg*Mv&f?jUA^|!vd;_%bUNi8*#cY* zbloV3I>rNrT*xvr8F0EI5FyW9@}{P}#rEKqzjo8wZX3NW#|5~$*(|)5t(qqZQq@Kd zvEavnObgJf8#wKe)dmZS)bI~avWSlB4P?e$5Lb}}&7G#m_qBy97 zr^<^@KVQy2`((LsYl(piU>^-4qZ2@~3l1F=qqzknb&*v`vPhL%wV|4TWYZ%yCerk3 z0v=1S_U9ee9|1G*cdf>B0vrT@htBHy6O$nZPP@oN8zA4t4PJJoMMJvT>0L1|YbwdF05!Nn@VT-QM zc1(1+R%?qg!;fcs%nnFQz4y7MCuqK*O`Cik@Uxxl#UU^kX_B6<4+Yu+ zb)QwcX~Ua_;wsQ)!0rGNURB$2UQ#a=G4s(ZMD@MxM$9xgTfW-uYZBb1};l+UWzghO*y;%j^jzE&EoLEVfPcjji)n9^i{*-EOtku?ssk!Wm&2vud@}Y zkr{Bc@$GE?vcrJx*7|Y=KJr8}9_wFxztqiZbQ(*yC>^Pi~Ce2emiw3TaHbhYz!P-50*V zKxmGK`Dp)3zj}~EIQP&;O;xVnKJL8hJvU(Uf>Nqky8X@Lx{3gVO*KXZ-A+CYA*tGV zd0$=CH3S_jb%C$u2%89>@vLxdfe5r*WNPJ|Y_!%yEud3FDhSi$1oO}N{7S)x*P9m8pKtBc|F$>wi6>V!rDn0N8JUKuLABp=Ou4y zm;`RZ`%UjU(a_NZjP8fcXDK)OkL1%lR`4%n{ZDa80KTDDC{MVcXW>7)G^%>X1 zDIxh%dU=zBXD&Xnqi8y6JLT<|1JDryJdXkdfB!%FyX7li{he~|$*0+5hApb2`K8mN z^clIgR7bFo2LQ8ap-Qberr>xrjEG zO@WZJ7FxCGDYVsu+rj_&_`*P0Ky!ETBQ$rLGufK^)X8(@+%r$IF%Ft`H}$!;#*ToO z(X4gLjfGWQ#j!2;tahr?_$XH0J5@%G&X?))r_00^KFe*?K%A?$Zb9BI>(8)v-C8R@ z`|*#;aa>Z)f9VzYiMCBU5Atjt==sSEc{9tL-4!#o=VlQBi}n-W=R;Nuiw`?{R<+ZLeQI;L@J2LP*Wj8p0Zn6>P<5sr&4Yhu(3>Lx%)TkuT)r{+;@ zz>f80Xo7Z>Le=HwIxZiF*nnvgAT>3GNedeDx!L)0?eeAaqrdz~dFvPNmF?HI%GX}~ zYB_WMeEI7i|D@akkWVq?WtmOTjxf{+UHXK_0EqnJ>lr$CarM}x0}_-AB0V-(kaCU9 zkd^`2vxjEOS{9eK*a3379HNn!#fSI=&*kA53vGu9Cm8OcMb|EUKutP8S-_S4j83M_ zHv;Z{eeW(3zb!0~hU?)q>VK!8`6XACg|{flaONl>OW%*S5DbkZ>onV1zYZ zTRg$8?PWkVaoLVp9|Y`%u^s3RFhCJ#bHv<6x(Ms$V8h6&rX!ZSbxAr>H+k>jUI2-k z=YtLkrI%|0=DlIv0B8?j8gDrc4Q&^B?LdAII`(ii=>T4yYpxiRMcR*D_-1_06uGeS z{rBHvV6zf)qr+@qHlax!VA$O?M=)Db9@iNNU1!$o2EVv1`5q{^VFn40t%@zFc-&(y6zg&!XBqnaobhx9hUW8OgSSH$) z>q*2m(ODVI{`A?tg7>9mfVt(f1GraF0pbRT)V9*K&&E&DhR2p?+%Bf?m4e^0Lt)NQ<)upYy?Kz2$8g8%rgggPeUziN z6b~6gL+-tw-}R>d*2>eX5chuf@J|Fkf?&a*Ac_+TGZ#lzFfMPE6W_VOB~vsHkep3| zCJzG6hJHbp@oM^Dk)=)GY<3#}=E6}mshYu55PheOGeHk@7^i6n0G$EApa1^hbAP`H zhnT_vjP&T@QEjr#f&ScYGBBC#`C|W7(yANgJZ zvdy-)98Q<2{TZO)G^{>+k;Jmbn&;g}%WJoBx5&48idEoYzA zZw!C$u@B6{qeMk?MxF%f_gqI?ZhRyxNzrsd>zZJR4pNZ5so8^_CT-E$`VtKSvNtpb zD96|F$=UF;(^=&nu&|`!V5iIhw9cG9j%Mx<8d3l^Q`I||wS0Wx<8tLPi^AA2XO~WP z3{W>gCwu(V=`xPyb@B+BcC_X0JUR$yJu!HmY4TM7>t#$mE(0WPL`(P6fB$dG>i;%S z{@#E6-;_g7e?eiP&I9DgVO&8XG)~Qf1Zw83TWHlnyg**l3(YLt<{n{lgQ!e>119!1 zY3MLz(d1~Dg`$JlB<^*{12xbcPL&rmom+!wgooJq6L8wA0fYcNxF`+vp8lL2at+7N zIg8&wFUqhQ)LHEK*E*x+5VrC&ZFn@mj*OV_TGxJLjz zmsYN_dm@Ydn7x_g2P~%Ch#!&$Skx+pbTI28OV>>3Mo`y=4w}_O>hHWeaI=ivV%Jcr z!nzF+;C2En{Pf&pIfVA@{8LZz4m))GULQVkyqr9JE~XPZ48o>Z890MHxQUi)4#okf zdU;t{+hSoaAnwpfJP{o#LxOH+jL_yP&%2bLzPj7HD|q+WEaPr`1c-0}=q~0g))N@?-wsW;ikHfcqZNL^DvRn(Hdny(TIWPRa?VMmFIoCws*F}?vi*ZKh_Ewvwat>=DZrTR(|hl|SJ;X@ z^7PcxPqEPRI5ycV(uH5P?L%&Q1`W5c6JK0hDp#&tW!7pKdw*=k87%I??>Tnp)Lkp8 z@_=Tw+%X}7-|;>JAK(xJqdIdkf<}CD?npU)<}98-ju3{m70CIv-j33CBA@V68+Y?^ z^~z;<1P&oy%TG6((Ub!!c9@;gU%lnvqEy$Z=zej7f!o!UOXb4SN4SZ`3(EitK#^_3 zjUeZGAFLZgO<9(?w<8ras}1)s1=F<($7eS0rA3Xo&nksnJiuZ81aszvj}x>&S$-(vE^Ii#FA=c#X#!zUuwVOOqeF1L?P)6K(~fL$K5JwWe2kt7n87r}5Uf z6Kz0CjlX`8bO_;HFsf7lY|OtNHIifUy-nE+p)q{=+2_i)zw^EF{A*t?ht5A+hNj)S zAxx_YLF^tKi`vj#Ivjs`?a=f=Kilqrq#Pnwp2Tse+0=LH#-RLAbixBjiD_{2r|e2|?CjNMVz$vD z&#^=ImA2@Vou)PeXtp)m*>#Wt&~Qgi>y4m8KsJT_?m8gea*c0u5Ja_|hkgcd&W17U zi*|V!ExI0Bw8?Id&H|`MXy4V!s_h>sn`pE*m)6Q0Znwr~hsp_jPP z^34KY0AgeaemW!6q5&4TtF<2nc*X+^ZKeDcw41(iU>MCtymnBwMgEkhm^WG84BqNs zSTXiqMvG!H6#J6XrT1;whHO8wJczI=uC99LsMevG=K zt?@X6sV=hYY=@q9I+Xc310KEktntk>Hs8i+dzzViTn}kWK7pxPz&Y>aiAoB!?Dio7 z?-AA(bg2j9le2htIfg4y77mY3k!RK+un2yXLDw&S@sj{@XM-FVtI2nN0JY=0$cU~R z1?KYBLB<;GLDt)lZ@tcJGjP|3|0;_-Z`@ia*VeAaEn*jsEU*iDkx9HNjp1o&j7_~9 zFn8z=>zbgjCV5k9ZZT6L;i0ogB>A}>jZ{6}L~Mv^jEyY!$8V45p?9CUaNAPteb689 z0j{kDk9JTm!`|Mv-c_ookZ-^6F1B!C)3BO5Ir_kyi-zND`vJ>Z?# z(`ayh&_XmVUE`tb2xt?GkE6LVSK7S_ZdDCq2+IaEL2x*9oi1szvPuvrAHP+upz+lm;sgNh?SJ|Il-KZzQ(k-xfW-`rJR(nSTBOErPk=*% z7<*T=af5tUT6N&$c>plh!3Eo9hm}-NAGrod=vr`8lN~CUnqT^Nz7vgNXHVtWqaNtP zevrY7<_<30m6^4?1QBw_ehTU2z2HS}D4PO5`9YhmYsO7Fh-){mm7_FND&?1MT%j%7 zLpxo{=|f}X3JX6A{+qjtC3Tf8)dw&c*mN-rUU}5W-UKxNoxk_@%h3GMvhdC?kpqAm zgR>i#KMpWhXIgG{9?u-s2`eMz(8dS;An`c!pqbcC3$vuoRDirb<2!A@{RC+M99>jA z1V4w=7^{uvy_DNtxUf@=gYBL5Xb9TX8b>P%5RS!)8h1+drXp~01Ke@hP1M@mHt79YBf=j;unY@`! zlojxP4Djs^uMQUccniAK^v|+;=M;Y27I~4jzQ^b+0{&x^tZy%BBCb7HD@~k!*YB8%Biy+8K zEGVD1f$SwMc+ykLgB;Q>NKq5&Dtyfzs7C?I;E8}38!^K*$L;2aw0+1+-7X;Ki7N>~SIOUQs=*m#_c)|-pZ zl?&QN8oVlhjob;4>vhe$>7_HKQM5D8zh32+5<{iYv(uvCQ_~?}(au(IY&x1eq*3QP zYhME(`M6jAZgW|n=n*+3e1JS>Cx#tt9>SnN~dSOViO86hV|2$`;g*gp8GV7E%8@i?33ml zfuIX`Qp0j>G~{MqUgSk0^Ryphy$X15n8cR}nsB{862{Y}v2W-wuPs2sAPWl}?NB;& z)&N_;ZM1M`eYffOwg6*W3>0A_1;q}R-4S-gggzE#wQ;2wzM85H78?(BZqo6h4IUWM zwiFkL!)RAAH50VzAAUD>;v`7AZUnThxdryYBWVAQpFV?T?pT>Ueue>Oo5i1hQ+Bbb zJvu#ERzG~d{LMfAlk)PBQ)Phn)-yGeO16;31(sL_XNHJ`${4`fybN@m=0w8K8V+#3 zCr|+B3jlQ4*1|<%7x3x8Vt`7iy=nj%z@^Lj4`oX?E!pG*If~3tv1GZX2Tk1&P`NO8 z$jVAwi3cDvfcunTrl4Dx8z{G1Y+>H!y)N+7G#;6qK&yPKte{c2dHn|3$!U?reZN8iGIi+6wHxdSumsK2iBp)8pd}wwh6L@DnI^Q_a4%fGkO7r;=lZuk!*3Kh)4V9Y z!pW0IJn-TMYQCL%eY0vUR_Y$byrWwkd_vI z+N@z|w@1q{+kt7!WEk|t8xLV>ky^L7k%yd}?QlaSO?k|YhjCFvkM%G*#Ow1pB)lQM zmqL|d9-0}CUVa?JHub%}c<{fE1k7*K(noP~$yqvOzI3M`di3?^e{gE=^F@=KuQU-@ z0Q9*k-RU90xTXk#^q7zkSg<590wxz^_J4n$=+mShL~W0*uH(%SBo7Cw=mV|N94QV_`3U(1tZx(v-=W z59PrrUHPnBxUh4X_T4xH^JY#{qo%w`Hs#0peH2T4WH8C!ET8#JG^v}#uC z!a18$DKxx|pdApr?==YN5dDVu4E1S^MDoL*+KG7L}Y8fBie(Ex-51AC{-s#dC;_ zUE&klf|7yqA-pjET+^9VyK+DX_wjx1b116mOJ@i?(4U51e(QDed7v*JEl`tIbGCfd zlnD?9>D1aC7T%#v8%0yq*_dT>m=#8ww=l)pE~{wbMln~=dx?wqT%ajp*;u8cWpkV* z1~n6SR#<0y`2}_&Tww4sfGNY+%oV;FVIkUbS-yUOE#?7fXx=~{lnLx!TrI0tu15nm zeH!l%`28L}GE&A5pTT9~jq;a&@m9I9xL!^iM!OD>yKwR2^2EiDuz6*7!r0XsO@snI zQVhr_{{bMhgG`-UF9)Ctdl>QwC?Sq9y=d&J#RNnECt}Y`Pw4pC4fUUi#KlIE|FlhQq$i=!&7C&I@r`hg|5*ITQ58;TYnyUyVw(q z6V5@GvmKGOyhgTR*buhQgJ?%LnL%JVY5Df={eBrmGrRTv+bp=-EIGNznDGSb;t{9F8B0Cf{uT7tN z#j{ZwerMYDXiHslqih;^$^^3!Mk>fEiy*PVzp-=+k3Bb$^DFG3uu)F1agc^y*KROa zyS7MJ+A-V}USC)&i#%JPUM((a7Q<{JK>dkxPh>Evdqug@(Cos_J?IW6NpGA%FjPV} zr0^5C4`P-hZT1oInu7*k`OSgvcR}=3MXYyzK?)y?QJsE2)4>0CY%mmcDc~ZPE7JK3!#r zg{Lb&T8i6TeE=#1U8DTbL_zZeZI?gmu<-oOpWZ+;KlwZlNkWMh&9rHX5gnj-Sl6_* zmsjZ+^Yo!u(fhKVWT$az_wGX3n%W&fBE*7MwdI<$sCiKfy)WH&27jz~(qkX0i#h$t z+pW8h)}Ll5nz|?-YKfywC#?7U-zU#FEIFUqz$oT4-2vskQ?@iOvW(TB+aW}|EvV;1 z(-h?6Q-4n5swPJeq6#Ffv@zJ^kv9JfOAu%QQqv%$Q{r7ZJvui# zs!uzbyfxL;vt^>pNVgh4rKaf)54G*{lOG7WT z5%C&%+hg&Q8?QX|)YIi3{KNmGy!w@|mSg9i#C@GWi4V+s5+D&e0X5?Bvck7c2W!R> zt-0YEKEjxIk1%!VHHuF;nl?<{-F@Bg=Tkm^tgpf=c;*ZS=p2BmMiOv}7JPW}5DWHJ zadWp;+JJEv^Z{YZDvJPhqoz&mHlTedo)egzx`B5VTqO*m{knyV!$qdLS8%yDi1(E( z3gSBM-L755OT@M7WkwA<-Wk?e+}XO`W|7)Q*w6y<=J7Z{M~rmP_@RlXqE7NUsJtJ-&2aqtIDO~SJX zzMiQsWrOc&R5}h!SeP1fnA8Ivqtyg52B@pWqq+sK)H1H+SAhTwr0}d`?k1}(a<%&w z(BPyHQ-fVJlbUZhDBXmnZD=0E?)dGG@CA)FdjzfEWo8*V*ydWlMxp(g7oI9tu?4@r z2uKIGSM!Kk|IDY;*^La5DXwd0?Xx_$T&*wEEf-T-kLl7=Z_+PuryRDh3BP`Gg>Aj@ zjx#us9c`8WHUMjNd7=F1-~0u$C0pgWFMYYRXOERVw76~rwYYG-eE5qWXA$YCFTRXf z5*s8f;jdj&3S8(7qm|mkRAy%r&_YJuA-Y^Ll9ucq&qv71RqEQa z&pb;x<9`TV%7^4b+M&jsLj~GHXrzrt#RHE*>N<;!H>tM{)-%vRP6PM>KBm03$~ppO zxH7{={LTW%_bg_m{IZV&1FtrwVJ+%`3MB^!8)(Yc0Oi`6ujr$k*^^N=OTywEg_APL zAcBr?gn>3mQs-FoIWdRAiM1wpg^5pm7tYdY$meRx!GMw(u?cML=dgXB#(RqC3^51(l!qIrp6*fRN~uy=p^sQ3Nk*tJaXM<8u~ucE}V z(W46328o3*_+z;_n5hGr$|s-kHb)buXP@bI+JP5QB~HEe2-1D)AjEGy3@e|M%?iqU z{NmmQpmtuhb4mlvvvl8%fVft3nC8K2|Mk8xZm*l)e#7F6yAIQq4$HuLA`p)U7Ow3u z0?fG%FOr57_>VMrPBQz4)58JeK9NN778;U5l|Nr)>XnXNdr_xXmsx0b4BPS7zxTcJ z{qKLje3|Kb7w4&g3ljl$xi%g;DtePFQj1ot*KupIpw_%;nUk(z!>Y;)(>}-{zx=-8 zo7caJqe*A~oJ7rlY#{G|;w}pmTVr^8m_!4GW_)ZGQxiHoJJ3}YCuXpM`NbYuyVj07 zMgoReC~=d8ii2IWu5{F+c=Q-xT3#;-i`OrfE0->oHGEr-(BV&Fwz177A`9>1j&XH` z?ZV%TPwyw5I#-SZ%x7kgP!@nV;_RSFb>9*c_+@=_HGYmK(7Ipxt3NNNzVJes^ChMw zDHGC-hYGTh4V{29BUU`j2fEvMm%;vfV48+>1#k{X9$}Zfqg=bFt^=U+-dPEE`|Q92H*^|j z;pDTge5HKzo8Kxw`?r5yE`Rt|nWCXwL0hiN#c9k~j-Pt2T)uiK8#%eKu+9eb03u+I zO!43P;)%R)9T3Jjpw)NCmcTp%3-6N$zp(y8C$j`v8J&Ojh4Rdozg1@OL{x54mhzF! zjW#g5(L`eUu#ML)+M4 z4M%1Hp+jCstviA0fhfwBUY=`vOHRm$8hJsqVXWtMwxJGya!niqv5*+qkS^CK>T}wC``i0upAA&ec$!oJoE{< z&7nI!0jG+V{p5SG+PqJp;SqFMn1Um1wC@hr3x1%Jnqkc_Zc?c=q0yA(AcxAK9ni5F z92?iq<1kuj^04nSpFVR?;7{OaXS7Xkpmuwm4%#I?f@(F%=FOwW+In+@#K7?{9ZJ^|=kLT9w+k0y+q57jJ!1hSPBo zrfC%F_jWY(ndi?snm7%qCaapn*cl4Ql}{VvyiHV6FL0=|A;a)RG4nol@SIK7FPgLE zn|yONJ>$?0)ed(B4UjVeFTV2S^8G*hqw%i2C(`l$> z1Dtg*d&weCdg;cTC5u4qwM{1kNRKHV?C9sE*9{Nr^Ml56XIjZKG&FJD`L^Emr)9V% ztmj^CU+$7n-o6Y%7)jMjApU(2ftE9FZ2 z!}2aRu1~=CnG5A!6|my#S2p zvFXsU+f_=?!tG$M{qYBHQidDl-0{l3o_`!8d0WoGGoY=imux==<8~{J5;+-4L$#wwOL!qo;rV z<*$?(8l@F%d1qKad+nol$}slXV*oJCAaXV8{M#D=_)fMmp&>}XW2R(6r*Pj%_auX^p8(Xph*Sr z@eJZIN18y?T;Rto#Wt)NhyxlHh`Km5K-t+T1~P~zWiJ@7d=#LY?+m=DyMja8k!VtB zM=7z?#VYTWEwZK_$U5O0`L6>Q`GuG$GpW}VNZX1eEd5r(IjzYv2kN!1Iy+O9Z@#Z^ zk%%BTR4{-bw6X9L6zj@zk3p;(5mC0#s<)M^*B8s%A6zVNzWV`#uSL>S^y=)3GcP0p zf0!M{L@FjEwAaomIB=`B?DVNz+fI$b>oaFhmg6iEbz?9WiJG=;yaN>TE$!I=NYD(A zwKsRL5m}PigtzT?12J7s3gXp@`!MNJ$CX31-)hNiJB(Yb;igW`icBIWYRXsESX@u% zV;yMgU!C$Ca=?vw)VA`$M`H}$$C$C~0M6r`hT3^-{sh1rPb@7wJfS5=tTX5nO?M&l3vR7Swr-F!aXc42ZWRNkG|5}V_8ES9k<=NtW@ysIec}W z$M=Lg;DaVA3w3|wbl0~gsrQuL06Xb7-`y4Tz_)&{8H-*w1i^x|&(iw(jX;%VHI*ZP z?7%4=ETDPSghjxoJHXmx)o=UrY`AQEL<21T*`lK8MiLKe(Pj&-1?YzB|E|XL$$u4t z25@Uf?jT65^e4sRLRM!m+@z_62~tMWpB(+?_U7(9$~Qr}Gb6sU9kGG4vv+_eXqU(4 zES(2(FIcya(36RS5}!BB^RoZ&UJbo=jVM=WY-nb<&yRPWV^^*wipJSS+xYJAC*Bt+ z!4pRfS6}pB_oscwH6PlT@jnAa)9{*Rmb0-MmyK#eTkXzl6w|e(qv9C>Vm3m!s@?Tl ze;&34#^c%SFd(FJ(nROg*S=Ezv;XQJm2Z9Lb+nLk@D%_~7~oVujvb4P>e6xQ6~Ruf z0`qz;un&;!5hzHvE%2z6_gW!uJQ|Yf-F^Scx2B_P0`#J6ZXf6z`ab20=6(Np|C;<* zCLnCIS(;&>$#Qf*1_%M14~(ys;VBkNVp5J88~LNN1su(~5DlAD-R+H_jkbfGW7C?$ z$7j&8V`mL8n89_MZVy-C>)gzI`NFd=mLo?`u(=O5>wvxqe4fwEi~!s(;3a3NeDwZ1 zXxVqld9<#YcO0IbEWi8Jm&=6<^X1*Qep#+;-ij&6GcP<_4xc&+%@A&RMV-jC4KQ^_ zpU#nmJvP8`5gg(4T5;v-<#L=xtA$;)<^?G#WMzP9ea&w6>jrhpv0GPrXcnyjw}}og zN?kF(0;WAG>aMbZoQc&(FuNHNWD=i%-+{>v5jyzP%~^}@V`4)bu4h>1d>A|0Z9wa* zf9ns+06QN3@Bi!nf|r~5a_;m|q;rpL*WZQyHMU!y3AlHvKCS~fd(BPqnF1s|pZOEO zS5PkSlor^+Pup&{Gaqn=7IN<*$8}~V@WF!#5+MBj_sY=O7ntopqrD9%B46$-c?i!t zYQGDM_O|h&GlN^pBhNisCQf~q28_mq0h4mG%YwcgG_ls(?KR4f0fvijN05b0fSfWn z%wonJX@vp{OB#mZr}DQ8-LYqGyEs?ih$hv%&_0nV_{_i%-l@6W#vG!8={gW`0kYEq zwqK^>fJMO0hfrf%$vYZP%doz$?O_HnhBAJ>HO_dPp=^!PL4gW|TCr(^kT-AJ4uCRP zB7Pd~x&S-Lks5I*h;oAP+H3of0DYB(oi`UXS-Dc)dH+Ir>ti<6VHSrmA=-55$NY(~ zinaY_7B6$3_8zX8u4Y@Lj?8*ba|N#su^Z&mPd&v96ZZb_%YlW9-s^6t#K*mK+t4~Q z0W}#cDA$9SQ|VAhk1~Tq=r<0EoiTL*CoJGVA`=Iod-Y~*1H%Ib3Jf^ftlJnzeLs8T z5Nmtjle}JIkY;&EKj3_b^?B>A8mCTblV0l^g=4+wM6;<}-EHYl4UUiT@Gw=kJC8?%|Ar}%pZLd>;w-SiG}7_2ITh?*P0 zJyqLQz^*dKe)RSB;s3%L<+C2f+PiBW^~Id>>$`uH%SQs{CM?-^9+h}Aa3^yJ$NxK@ z-TCFi+$s1Tg3x)|Ah*5$q*M-C@QLsse@IIAi3&zPu>7=7twS~_*>zI&zT`f}!|2>` z(nYD7i+o!2dB-2yN5Qjp;o6~VcCkq%b^H2<(V$x-Q+#U!rm2eC1Is6YD}CxcG{xSW zZv^sgO7!aj=XV!F!ylhpZt_V?)V16=Y-$AP6|A$+4%u^Izny?Pgz7TVJo}xEr*Zi6 zYuk@CBCs-N_NMo|sUS8y{sPcAM*~D)Whe0w-fJT;PLJB5)lTjppxn9}4FmB@v)!?P zTsUEziJo`4;wE&HUo~R2K{OxrI+{mm@W-EgW-J0GdN4b9wJ=U+y4AP9>9yCsTKUY=<#*V-NzKfUk1myhmxMIiL;^#Ray-AF-vgLDW@VNWjBdWf< z&))`+_rLO4J$el2f64Xj_f2Yd1>iPic<GI00gd283m-AKYgM+dH!s< zgl2XaPcC!gAC?g|wt4*x79yTM$AAjmEd#0n(uv(E`BE0*uO864vjM1}(|_XZQ-F}A zvUv4IHaQsv+zH?^V+8sX=(Je$s`sHmHGu$fB1cmOFhGN>%B;gES%51@%Zv+cvVNTP z2%;Pf`y9=yz)2a?y1mL7@(wM-@Q>P1me^3m-7_~Cecy279=;jIWMmxqJ$m{~S-JiZ z;C83H`!1#}0N^uc&!b@ksib16DHp_0i=f%cm3)&X5A!U~d9SNe363Y0TvFd1LBu17 z4TEjTF5qSkQv+;sw}#5k-~4%b`Kg!69t(Q6vmwoy@|CY+P6?geWxU0h$GtVc{8pI; z1matd@&iD#iO|~i8g!Fq>g4z&CNF@(b->aDVf5}I&uz<`P0)=AwJYrbgrJQDTrqNh zbKLTtQ`#m73xN>lIW<%K1p>442 zJ&2Y=zM4ApL$kov_M@6ZAWzUFS$yQzd;|sD3*^<{YVS?P_%E36h3NrWy%4Fv#>Rs| zcd zw?~g2E?;>5*>djO*(?m@QD_Xnrmb^8qB~XF)GqRFJK17j;{eArTa=sasKW`pCn;Ll zMHLO)Ue9zzM7qJOm3-R~w3E6EN=NX0-$F$#SRP@pJ`6Z_##a-r7GPbMw5Bo4PP$`M ztMN%a!!U~?pLuR`L`-}$TuvW9&n8^Q%M!Z;Z|tF!ryX*`F83|4PqMF5@z}Hv&dx9t zW>$=)%gCe>HGb$sct%n6%9cb-h$BT^FF*CK;ga6|2lTVFM-Fc7pFOJF)ayt6duJQp zOLRYNMXTP_KJQUetKUB|Fu#|K9+x@QtJ_3--~9Z}3^yInz482ZJh2VXq(#lS;Cv2X zsiwK+{oXvt4Q;xsf9!(`~A9VoZ{w@r`>!z7d4jpnEh8 zNOP`FKmD(rm$Zoq#1^(Of+Ew*Kl8_dSmnrofh(s&ZMtogoKj6gYsUy}>8$A~?Uany z&dddKODvvJd$WZv?q{BPzWkH_?w^z|fA@8?KaOSu<#3ev0>n&>_|hqvL4In_ojMPG zflk4IgM)4AV6dJDBXOA^VuJFhH2V6l4gmBPnhHBK5QdI2WMvC)4|ES4x@mzZZ0 zg;hV)gS(m)t(S{$yG?)*K1$vP(X6YD_MN~r zazMPwdNnl>P*Ykq9=#)V%J#%^;h!^c`WSB%6V|KL72Z|fCZ<^gOTIT5^nd*ErSkprEW z=g&ced34h*q9B}N;XA7oJF;R1wnKhRuXc|lFH#P&sHN^pp#pQ_i5CbjZ*qF*Ker{OM&Z3m zS_cOnj~IQ8#J?7XM=!$A?V~)d@0zq8HBU*W_vaCTd5>r#yPK*|>z^L+TsA?PvlZ-d z9ZgiGn56F$@#9PF&d8O8Spb$AUjb;d?ffUDH$;cHhLDOk1gW1C?#@&``_7I?ZTAQj z+)C2$rL9l~$qpPF*bII0GHfR0Vm9n4aY82gZe(ro_Zhh`s|{1~60ThQLjb{Lmw z2Ap%{B3(7-2Lb1PcfaGH^xf~<2YJ??j`^`dIq*`e?mMTO`vRvHt@*!_SOOMY@ z8&*NlAZp)pH7!WlyHcPqj2CW)` zL?HqK0gVGJ4xOB&^TcGqO?n(iDR-IHQC{ez#{sE_SdrDipScSkvw0IXT%FB@05!ql zEuu;trQ<_j-t^v%C0lY@oz-9wY{s1l|J1kn=*}=u7CIi$9 zJ>^`$`^Vta#iB=+ku&GZ$d!-F4sP&v*cj>hjjPmSHig2hU=-lC2Y~DFbMU{XPkZPQ zm^<4*;#^~M%o%nz)iLyHQ#`=nBBm?>@{j-sZ_GK$HZ* zAb$k;EB^)w5Fi;KNPvuEAeuoGjc1c3Te2gI5+&M_C@vz|>~6C6y=&jMSM@5N&$;hc z^}4!xtrA7$cE9>|xM0zChZQ7wIhXK6l^H zM!dPdpj?^osJkBa#<^;L-ji6JWV+WUr1B6#HIr%3kS8adY`Shheav>*jM`Hpt4;1L z=^Gd%U!EfB*vl$5C-}CgKb~7BG-y_{JYjxn0b*+d&19lS8;8jxq zayL9X;q>?}XoaqMCVJCsl5OKBZ@PMkhk8B}Eu(PYLfV;^cAuWS9Qn=0T1Ipl$cTkEX@ zxY9g9xz-Wv{H;Gm=m21bh1Nv^xvuB1#eHqEoA0B2LZeaUpTm|v9efQU1vU&=JclX; zAjL^#0ClN=ZaaIIV8c5h!;Bh(v{nw3GA+~5d;EDbpzWid_(Xc{3!hJoN^SuXK|A?y zV_B465s4%iBg9Bk{F4soRc2KD^Ltc-s`nyzagAtlOnBV8Zlahj0g7Z4e={2E$Z3Bv>ae}YVPHAOX zstRyy01PYxoaRtz=vc%WhxHLZ#^J=T9dNdYrG-`Mpdvp@e~I^=eruxMud>p?9?-OM zevHFZ&eC^fsjw;;yY}v4iJGIqk;XT1L^z_$pj=|jdz@Iylr>goqVDpUf);RxdV_!_ z^u$pCKpKv@7U)xg2WY`k1PBO00{}7J3DlvR(-pC~NM6xp4uIrD5-{n+*7%GPp2!JZ z@RoR#cxxt6>8kp|f(50*@t*vfW!qXk@q{%I;tpseRww2+te0P}kCDL4HK2DxP1fhz~11iJXWF{r|5=Anh!YN+o#)it^arz`peLp zrP27YF5o}ZogyS-Fg!MX`ySPnRPmB#tpJ6tJZ)i_JWT@<$0sLHuUN#w3YLd1UP|wr z;grh>`c}ubc48rYZI@fWjguGg*Z8uP8PcE~<$jssV^zVrMk&VRz*VJ3j~z|@9IVqu zKeqtSRY#%t1El8-ntAd7=Yrp51QYJPS&fNe2JS1Nhn}Ys(F0RIoVbL*hWy4!qo}Fa z&#Le4#Il5HNwX7nB;~FMXdbhJy5b(5Ya_$-1GFi|+yM4~dDŸMX>4Wn&akp$Ki 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%0D1oj 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

    BiQ-hAKw2Dq^BPHm*WZx@O1<4 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 X`@?ah^#uVE;-w_7E>|gI`QiTn4dE%? literal 0 HcmV?d00001 diff --git a/website/src/_includes/blog_previews/20250308.html b/website/src/_includes/blog_previews/20250308.html new file mode 100644 index 0000000000..ab55ecdbca --- /dev/null +++ b/website/src/_includes/blog_previews/20250308.html @@ -0,0 +1,12 @@ +

    v6.3 is released:

    + +
      +
    • Preventing spam and abuse in public groups.
    • +
    • Group improvements: mention other members and improved performance.
    • +
    • Better chat navigation: organize chats into lists and jump to found and forwarded messages.
    • +
    • Privacy and security improvements: chat retention period and private media file names.
    • +
    + +

    Also, we added Catalan interface language, thanks to our users and Weblate.

    + +

    The last but not the least - server builds are now reproducible!

    From 2317cee3ebc8c8f6feefabab82f16a29566ece74 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Mon, 10 Mar 2025 14:54:55 +0400 Subject: [PATCH 473/567] desktop: fix postgres migration (#5739) --- .../commonMain/kotlin/chat/simplex/common/platform/Core.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt index 276d3e3909..a9f2dcaffc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt @@ -79,7 +79,11 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat } if (rerunMigration) { chatModel.dbMigrationInProgress.value = true - migrated = chatMigrateInit(dbAbsolutePrefixPath, dbKey, confirm.value) + migrated = if (databaseBackend == "postgres") { + chatMigrateInit("simplex_v1", "postgresql://simplex@/simplex_v1", confirm.value) + } else { + chatMigrateInit(dbAbsolutePrefixPath, dbKey, confirm.value) + } res = runCatching { json.decodeFromString(migrated[0] as String) }.getOrElse { DBMigrationResult.Unknown(migrated[0] as String) } From 5050d6082579f6791a5dc77ca5bf04dfb9ebe966 Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Tue, 11 Mar 2025 08:37:48 +0000 Subject: [PATCH 474/567] flatpak: update metainfo (#5740) --- .../flatpak/chat.simplex.simplex.metainfo.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/scripts/flatpak/chat.simplex.simplex.metainfo.xml b/scripts/flatpak/chat.simplex.simplex.metainfo.xml index db84b67e63..45e0f9a418 100644 --- a/scripts/flatpak/chat.simplex.simplex.metainfo.xml +++ b/scripts/flatpak/chat.simplex.simplex.metainfo.xml @@ -38,6 +38,22 @@ + + https://simplex.chat/blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.html + +

    New in v6.3.0:

    +
      +
    • Mention members and get notified when mentioned.
    • +
    • Send private reports to moderators.
    • +
    • Delete, block and change role for multiple members at once
    • +
    • Faster sending messages and faster deletion.
    • +
    • Organize chats into lists to keep track of what's important.
    • +
    • Jump to found and forwarded messages.
    • +
    • Private media file names.
    • +
    • Message expiration in chats.
    • +
    +
    +
    https://simplex.chat/blog/20241210-simplex-network-v6-2-servers-by-flux-business-chats.html From aba09939e2a3d748fe922df902321d4a87f52b8b Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 11 Mar 2025 10:32:02 +0000 Subject: [PATCH 475/567] directory: more permissive captcha rules (#5741) --- .../src/Directory/Captcha.hs | 40 +++++++++++++++++++ .../src/Directory/Service.hs | 10 +---- simplex-chat.cabal | 1 + tests/Bots/DirectoryTests.hs | 14 +++++++ 4 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 apps/simplex-directory-service/src/Directory/Captcha.hs diff --git a/apps/simplex-directory-service/src/Directory/Captcha.hs b/apps/simplex-directory-service/src/Directory/Captcha.hs new file mode 100644 index 0000000000..54d595e96f --- /dev/null +++ b/apps/simplex-directory-service/src/Directory/Captcha.hs @@ -0,0 +1,40 @@ +module Directory.Captcha (getCaptchaStr, matchCaptchaStr) where + +import qualified Data.Map.Strict as M +import Data.Maybe (fromMaybe) +import qualified Data.Text as T +import System.Random (randomRIO) + +getCaptchaStr :: Int -> String -> IO String +getCaptchaStr 0 s = pure s +getCaptchaStr n s = do + i <- randomRIO (0, length captchaChars - 1) + let c = captchaChars !! i + getCaptchaStr (n - 1) (c : s) + +matchCaptchaStr :: T.Text -> T.Text -> Bool +matchCaptchaStr captcha guess = T.length captcha == T.length guess && matchChars (T.zip captcha guess) + where + matchChars [] = True + matchChars ((c, g) : cs) = + let g' = fromMaybe g $ M.lookup g captchaMatches + in c == g' && matchChars cs + +captchaChars :: String +captchaChars = "23456789ABCDEFGHIJKLMNOPQRSTUVWXYZabdefghijkmnpqrty" + +captchaMatches :: M.Map Char Char +captchaMatches = + M.fromList + [ ('0', 'O'), + ('1', 'I'), + ('c', 'C'), + ('l', 'I'), + ('o', 'O'), + ('s', 'S'), + ('u', 'U'), + ('v', 'V'), + ('w', 'W'), + ('x', 'X'), + ('z', 'Z') + ] diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs index 79654b4da6..575c7ca738 100644 --- a/apps/simplex-directory-service/src/Directory/Service.hs +++ b/apps/simplex-directory-service/src/Directory/Service.hs @@ -39,6 +39,7 @@ import qualified Data.Text.IO as T import Data.Time.Clock (NominalDiffTime, UTCTime, diffUTCTime, getCurrentTime) import Data.Time.LocalTime (getCurrentTimeZone) import Directory.BlockedWords +import Directory.Captcha import Directory.Events import Directory.Options import Directory.Search @@ -67,7 +68,6 @@ import qualified Simplex.Messaging.TMap as TM import Simplex.Messaging.Util (safeDecodeUtf8, tshow, ($>>=), (<$$>)) import System.Directory (getAppUserDataDirectory) import System.Process (readProcess) -import System.Random (randomRIO) data GroupProfileUpdate = GPNoServiceLink | GPServiceLinkAdded | GPServiceLinkRemoved | GPHasServiceLink | GPServiceLinkError @@ -455,12 +455,6 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName atomically $ TM.insert gmId captcha $ pendingCaptchas env sendCaptcha mc where - getCaptchaStr 0 s = pure s - getCaptchaStr n s = do - i <- randomRIO (0, length chars - 1) - let c = chars !! i - getCaptchaStr (n - 1) (c : s) - chars = "23456789ABCDEFGHIJKLMNOPQRSTUVWXYZabdefghijkmnpqrsty" getCaptcha s = case captchaGenerator opts of Nothing -> pure textMsg Just script -> content <$> readProcess script [s] "" @@ -491,7 +485,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName atomically (TM.lookup (groupMemberId' m) $ pendingCaptchas env) >>= \case Just PendingCaptcha {captchaText, sentAt, attempts} | ts `diffUTCTime` sentAt > captchaTTL -> sendMemberCaptcha g m (Just ciId) captchaExpired $ attempts - 1 - | captchaText == msgText -> do + | matchCaptchaStr captchaText msgText -> do sendComposedMessages_ cc (SRGroup groupId $ Just $ groupMemberId' m) [(Just ciId, MCText $ "Correct, you joined the group " <> n)] approvePendingMember a g m | attempts >= maxCaptchaAttempts -> rejectPendingMember tooManyAttempts diff --git a/simplex-chat.cabal b/simplex-chat.cabal index bbdd766a7b..377fb41d81 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -408,6 +408,7 @@ executable simplex-directory-service StrictData other-modules: Directory.BlockedWords + Directory.Captcha Directory.Events Directory.Options Directory.Search diff --git a/tests/Bots/DirectoryTests.hs b/tests/Bots/DirectoryTests.hs index 31b36159fd..1e22b1854e 100644 --- a/tests/Bots/DirectoryTests.hs +++ b/tests/Bots/DirectoryTests.hs @@ -13,6 +13,7 @@ import Control.Concurrent (forkIO, killThread, threadDelay) import Control.Exception (finally) import Control.Monad (forM_, when) import qualified Data.Text as T +import Directory.Captcha import qualified Directory.Events as DE import Directory.Options import Directory.Service @@ -65,6 +66,8 @@ directoryServiceTests = do it "should list user's groups" testListUserGroups describe "store log" $ do it "should restore directory service state" testRestoreDirectory + describe "captcha" $ do + it "should accept some incorrect spellings" testCaptcha directoryProfile :: Profile directoryProfile = Profile {displayName = "SimpleX-Directory", fullName = "", image = Nothing, contactLink = Nothing, preferences = Nothing} @@ -974,6 +977,17 @@ testRestoreDirectory ps = do cath #> "@SimpleX-Directory security" groupFoundN' 2 cath "security" +testCaptcha :: HasCallStack => TestParams -> IO () +testCaptcha _ps = do + let captcha = "23456789ABCDEFGHIJKLMNOPQRSTUVWXYZabdefghijkmnpqrty" + matchCaptchaStr captcha captcha `shouldBe` True + matchCaptchaStr captcha "23456789ABcDEFGH1JKLMNoPQRsTuvwxYzabdefghijkmnpqrty" `shouldBe` True + matchCaptchaStr "OOIICSUVWXZ" "OOIICSUVWXZ" `shouldBe` True + matchCaptchaStr "OOIICSUVWXZ" "0o1lcsuvwxz" `shouldBe` True + matchCaptchaStr "OOIICSUVWXZ" "" `shouldBe` False + matchCaptchaStr "OOIICSUVWXZ" "0o1lcsuvwx" `shouldBe` False + matchCaptchaStr "OOIICSUVWXZ" "0o1lcsuvwxzz" `shouldBe` False + listGroups :: HasCallStack => TestCC -> TestCC -> TestCC -> IO () listGroups superUser bob cath = do bob #> "@SimpleX-Directory /list" From 45c7c6bc6e0494996ba29331a275449dd948e416 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 12 Mar 2025 10:30:04 +0000 Subject: [PATCH 476/567] directory: use lowercase letters in captcha, accept any case for same-looking letters (#5744) --- .../src/Directory/Captcha.hs | 41 +++++++++---------- simplex-chat.cabal | 1 + tests/Bots/DirectoryTests.hs | 12 +++--- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/apps/simplex-directory-service/src/Directory/Captcha.hs b/apps/simplex-directory-service/src/Directory/Captcha.hs index 54d595e96f..d60b09df83 100644 --- a/apps/simplex-directory-service/src/Directory/Captcha.hs +++ b/apps/simplex-directory-service/src/Directory/Captcha.hs @@ -11,30 +11,27 @@ getCaptchaStr n s = do i <- randomRIO (0, length captchaChars - 1) let c = captchaChars !! i getCaptchaStr (n - 1) (c : s) + where + captchaChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" matchCaptchaStr :: T.Text -> T.Text -> Bool matchCaptchaStr captcha guess = T.length captcha == T.length guess && matchChars (T.zip captcha guess) where matchChars [] = True - matchChars ((c, g) : cs) = - let g' = fromMaybe g $ M.lookup g captchaMatches - in c == g' && matchChars cs - -captchaChars :: String -captchaChars = "23456789ABCDEFGHIJKLMNOPQRSTUVWXYZabdefghijkmnpqrty" - -captchaMatches :: M.Map Char Char -captchaMatches = - M.fromList - [ ('0', 'O'), - ('1', 'I'), - ('c', 'C'), - ('l', 'I'), - ('o', 'O'), - ('s', 'S'), - ('u', 'U'), - ('v', 'V'), - ('w', 'W'), - ('x', 'X'), - ('z', 'Z') - ] + matchChars ((c, g) : cs) = matchChar c == matchChar g && matchChars cs + matchChar c = fromMaybe c $ M.lookup c captchaMatches + captchaMatches = + M.fromList + [ ('0', 'O'), + ('1', 'I'), + ('c', 'C'), + ('l', 'I'), + ('o', 'O'), + ('p', 'P'), + ('s', 'S'), + ('u', 'U'), + ('v', 'V'), + ('w', 'W'), + ('x', 'X'), + ('z', 'Z') + ] diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 377fb41d81..f8370e4391 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -476,6 +476,7 @@ test-suite simplex-chat-test Broadcast.Bot Broadcast.Options Directory.BlockedWords + Directory.Captcha Directory.Events Directory.Options Directory.Search diff --git a/tests/Bots/DirectoryTests.hs b/tests/Bots/DirectoryTests.hs index 1e22b1854e..e9db100e8d 100644 --- a/tests/Bots/DirectoryTests.hs +++ b/tests/Bots/DirectoryTests.hs @@ -982,11 +982,13 @@ testCaptcha _ps = do let captcha = "23456789ABCDEFGHIJKLMNOPQRSTUVWXYZabdefghijkmnpqrty" matchCaptchaStr captcha captcha `shouldBe` True matchCaptchaStr captcha "23456789ABcDEFGH1JKLMNoPQRsTuvwxYzabdefghijkmnpqrty" `shouldBe` True - matchCaptchaStr "OOIICSUVWXZ" "OOIICSUVWXZ" `shouldBe` True - matchCaptchaStr "OOIICSUVWXZ" "0o1lcsuvwxz" `shouldBe` True - matchCaptchaStr "OOIICSUVWXZ" "" `shouldBe` False - matchCaptchaStr "OOIICSUVWXZ" "0o1lcsuvwx" `shouldBe` False - matchCaptchaStr "OOIICSUVWXZ" "0o1lcsuvwxzz" `shouldBe` False + matchCaptchaStr "23456789ABcDEFGH1JKLMNoPQRsTuvwxYzabdefghijkmnpqrty" captcha `shouldBe` True + matchCaptchaStr "OOIICPSUVWXZ" "OOIICPSUVWXZ" `shouldBe` True + matchCaptchaStr "OOIICPSUVWXZ" "0o1lcpsuvwxz" `shouldBe` True + matchCaptchaStr "0o1lcpsuvwxz" "OOIICPSUVWXZ" `shouldBe` True + matchCaptchaStr "OOIICPSUVWXZ" "" `shouldBe` False + matchCaptchaStr "OOIICPSUVWXZ" "0o1lcpsuvwx" `shouldBe` False + matchCaptchaStr "OOIICPSUVWXZ" "0o1lcpsuvwxzz" `shouldBe` False listGroups :: HasCallStack => TestCC -> TestCC -> TestCC -> IO () listGroups superUser bob cath = do From 364aa667ad832c50ec0f79a9fd9b8e783b900129 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 14 Mar 2025 05:36:45 +0700 Subject: [PATCH 477/567] ios: scrolling improvements (#5746) * ios: scrolling improvements * changes * fixes * fix * private --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Model/ChatModel.swift | 4 + .../Shared/Views/Chat/ChatItemsLoader.swift | 189 +++++++++++++++++- .../Shared/Views/Chat/ChatScrollHelpers.swift | 70 ++++--- apps/ios/Shared/Views/Chat/ChatView.swift | 53 +++-- .../Shared/Views/Chat/EndlessScrollView.swift | 17 +- 5 files changed, 274 insertions(+), 59 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 23b167a9ff..00260cc05e 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -66,6 +66,10 @@ class ItemsModel: ObservableObject { private var navigationTimeoutTask: Task? = nil private var loadChatTask: Task? = nil + var lastItemsLoaded: Bool { + chatState.splits.isEmpty || chatState.splits.first != reversedChatItems.first?.id + } + init() { publisher .throttle(for: 0.2, scheduler: DispatchQueue.main, latest: true) diff --git a/apps/ios/Shared/Views/Chat/ChatItemsLoader.swift b/apps/ios/Shared/Views/Chat/ChatItemsLoader.swift index add28cd7f9..07034cf8ec 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemsLoader.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemsLoader.swift @@ -60,6 +60,8 @@ func apiLoadMessages( chatState.unreadTotal = chat.chatStats.unreadCount chatState.unreadAfter = navInfo.afterUnread chatState.unreadAfterNewestLoaded = navInfo.afterUnread + + PreloadState.shared.clear() } case let .before(paginationChatItemId, _): newItems.append(contentsOf: oldItems) @@ -104,19 +106,22 @@ func apiLoadMessages( } } case .around: - let newSplits: [Int64] + var newSplits: [Int64] if openAroundItemId == nil { newItems.append(contentsOf: oldItems) newSplits = await removeDuplicatesAndUpperSplits(&newItems, chat, chatState.splits, visibleItemIndexesNonReversed) } else { newSplits = [] } - // currently, items will always be added on top, which is index 0 - newItems.insert(contentsOf: chat.chatItems, at: 0) + let (itemIndex, splitIndex) = indexToInsertAround(chat.chatInfo.chatType, chat.chatItems.last, to: newItems, Set(newSplits)) + //indexToInsertAroundTest() + newItems.insert(contentsOf: chat.chatItems, at: itemIndex) + newSplits.insert(chat.chatItems.last!.id, at: splitIndex) let newReversed: [ChatItem] = newItems.reversed() + let orderedSplits = newSplits await MainActor.run { ItemsModel.shared.reversedChatItems = newReversed - chatState.splits = [chat.chatItems.last!.id] + newSplits + chatState.splits = orderedSplits chatState.unreadAfterItemId = chat.chatItems.last!.id chatState.totalAfter = navInfo.afterTotal chatState.unreadTotal = chat.chatStats.unreadCount @@ -130,14 +135,16 @@ func apiLoadMessages( // no need to set it, count will be wrong // chatState.unreadAfterNewestLoaded = navInfo.afterUnread } + PreloadState.shared.clear() } case .last: newItems.append(contentsOf: oldItems) - removeDuplicates(&newItems, chat) + let newSplits = await removeDuplicatesAndUnusedSplits(&newItems, chat, chatState.splits) newItems.append(contentsOf: chat.chatItems) let items = newItems await MainActor.run { ItemsModel.shared.reversedChatItems = items.reversed() + chatState.splits = newSplits chatModel.updateChatInfo(chat.chatInfo) chatState.unreadAfterNewestLoaded = 0 } @@ -234,10 +241,14 @@ private func removeDuplicatesAndModifySplitsOnAfterPagination( let indexInSplitRanges = splits.firstIndex(of: paginationChatItemId) // Currently, it should always load from split range let loadingFromSplitRange = indexInSplitRanges != nil - var splitsToMerge: [Int64] = if let indexInSplitRanges, loadingFromSplitRange && indexInSplitRanges + 1 <= splits.count { - Array(splits[indexInSplitRanges + 1 ..< splits.count]) + let topSplits: [Int64] + var splitsToMerge: [Int64] + if let indexInSplitRanges, loadingFromSplitRange && indexInSplitRanges + 1 <= splits.count { + splitsToMerge = Array(splits[indexInSplitRanges + 1 ..< splits.count]) + topSplits = Array(splits[0 ..< indexInSplitRanges + 1]) } else { - [] + splitsToMerge = [] + topSplits = [] } newItems.removeAll(where: { new in let duplicate = newIds.contains(new.id) @@ -257,8 +268,8 @@ private func removeDuplicatesAndModifySplitsOnAfterPagination( }) var newSplits: [Int64] = [] if firstItemIdBelowAllSplits != nil { - // no splits anymore, all were merged with bottom items - newSplits = [] + // no splits below anymore, all were merged with bottom items + newSplits = topSplits } else { if !splitsToRemove.isEmpty { var new = splits @@ -320,6 +331,28 @@ private func removeDuplicatesAndUpperSplits( return newSplits } +private func removeDuplicatesAndUnusedSplits( + _ newItems: inout [ChatItem], + _ chat: Chat, + _ splits: [Int64] +) async -> [Int64] { + if splits.isEmpty { + removeDuplicates(&newItems, chat) + return splits + } + + var newSplits = splits + let (newIds, _) = mapItemsToIds(chat.chatItems) + newItems.removeAll(where: { + let duplicate = newIds.contains($0.id) + if duplicate, let firstIndex = newSplits.firstIndex(of: $0.id) { + newSplits.remove(at: firstIndex) + } + return duplicate + }) + return newSplits +} + // ids, number of unread items private func mapItemsToIds(_ items: [ChatItem]) -> (Set, Int) { var unreadInLoaded = 0 @@ -340,3 +373,139 @@ private func removeDuplicates(_ newItems: inout [ChatItem], _ chat: Chat) { let (newIds, _) = mapItemsToIds(chat.chatItems) newItems.removeAll { newIds.contains($0.id) } } + +private typealias SameTimeItem = (index: Int, item: ChatItem) + +// return (item index, split index) +private func indexToInsertAround(_ chatType: ChatType, _ lastNew: ChatItem?, to: [ChatItem], _ splits: Set) -> (Int, Int) { + guard to.count > 0, let lastNew = lastNew else { return (0, 0) } + // group sorting: item_ts, item_id + // everything else: created_at, item_id + let compareByTimeTs = chatType == .group + // in case several items have the same time as another item in the `to` array + var sameTime: [SameTimeItem] = [] + + // trying to find new split index for item looks difficult but allows to not use one more loop. + // The idea is to memorize how many splits were till any index (map number of splits until index) + // and use resulting itemIndex to decide new split index position. + // Because of the possibility to have many items with the same timestamp, it's possible to see `itemIndex < || == || > i`. + var splitsTillIndex: [Int] = [] + var splitsPerPrevIndex = 0 + + for i in 0 ..< to.count { + let item = to[i] + + splitsPerPrevIndex = splits.contains(item.id) ? splitsPerPrevIndex + 1 : splitsPerPrevIndex + splitsTillIndex.append(splitsPerPrevIndex) + + let itemIsNewer = (compareByTimeTs ? item.meta.itemTs > lastNew.meta.itemTs : item.meta.createdAt > lastNew.meta.createdAt) + if itemIsNewer || i + 1 == to.count { + if (compareByTimeTs ? lastNew.meta.itemTs == item.meta.itemTs : lastNew.meta.createdAt == item.meta.createdAt) { + sameTime.append((i, item)) + } + // time to stop the loop. Item is newer or it's the last item in `to` array, taking previous items and checking position inside them + let itemIndex: Int + if sameTime.count > 1, let first = sameTime.sorted(by: { prev, next in prev.item.meta.itemId < next.item.id }).first(where: { same in same.item.id > lastNew.id }) { + itemIndex = first.index + } else if sameTime.count == 1 { + itemIndex = sameTime[0].item.id > lastNew.id ? sameTime[0].index : sameTime[0].index + 1 + } else { + itemIndex = itemIsNewer ? i : i + 1 + } + let splitIndex = splitsTillIndex[min(itemIndex, splitsTillIndex.count - 1)] + let prevItemSplitIndex = itemIndex == 0 ? 0 : splitsTillIndex[min(itemIndex - 1, splitsTillIndex.count - 1)] + return (itemIndex, splitIndex == prevItemSplitIndex ? splitIndex : prevItemSplitIndex) + } + + if (compareByTimeTs ? lastNew.meta.itemTs == item.meta.itemTs : lastNew.meta.createdAt == item.meta.createdAt) { + sameTime.append(SameTimeItem(index: i, item: item)) + } else { + sameTime = [] + } + } + // shouldn't be here + return (to.count, splits.count) +} + +private func indexToInsertAroundTest() { + func assert(_ one: (Int, Int), _ two: (Int, Int)) { + if one != two { + logger.debug("\(String(describing: one)) != \(String(describing: two))") + fatalError() + } + } + + let itemsToInsert = [ChatItem.getSample(3, .groupSnd, Date.init(timeIntervalSince1970: 3), "")] + let items1 = [ + ChatItem.getSample(0, .groupSnd, Date.init(timeIntervalSince1970: 0), ""), + ChatItem.getSample(1, .groupSnd, Date.init(timeIntervalSince1970: 1), ""), + ChatItem.getSample(2, .groupSnd, Date.init(timeIntervalSince1970: 2), "") + ] + assert(indexToInsertAround(.group, itemsToInsert.last, to: items1, Set([1])), (3, 1)) + + let items2 = [ + ChatItem.getSample(0, .groupSnd, Date.init(timeIntervalSince1970: 0), ""), + ChatItem.getSample(1, .groupSnd, Date.init(timeIntervalSince1970: 1), ""), + ChatItem.getSample(2, .groupSnd, Date.init(timeIntervalSince1970: 3), "") + ] + assert(indexToInsertAround(.group, itemsToInsert.last, to: items2, Set([2])), (3, 1)) + + let items3 = [ + ChatItem.getSample(0, .groupSnd, Date.init(timeIntervalSince1970: 0), ""), + ChatItem.getSample(1, .groupSnd, Date.init(timeIntervalSince1970: 3), ""), + ChatItem.getSample(2, .groupSnd, Date.init(timeIntervalSince1970: 3), "") + ] + assert(indexToInsertAround(.group, itemsToInsert.last, to: items3, Set([1])), (3, 1)) + + let items4 = [ + ChatItem.getSample(0, .groupSnd, Date.init(timeIntervalSince1970: 0), ""), + ChatItem.getSample(4, .groupSnd, Date.init(timeIntervalSince1970: 3), ""), + ChatItem.getSample(5, .groupSnd, Date.init(timeIntervalSince1970: 3), "") + ] + assert(indexToInsertAround(.group, itemsToInsert.last, to: items4, Set([4])), (1, 0)) + + let items5 = [ + ChatItem.getSample(0, .groupSnd, Date.init(timeIntervalSince1970: 0), ""), + ChatItem.getSample(2, .groupSnd, Date.init(timeIntervalSince1970: 3), ""), + ChatItem.getSample(4, .groupSnd, Date.init(timeIntervalSince1970: 3), "") + ] + assert(indexToInsertAround(.group, itemsToInsert.last, to: items5, Set([2])), (2, 1)) + + let items6 = [ + ChatItem.getSample(4, .groupSnd, Date.init(timeIntervalSince1970: 4), ""), + ChatItem.getSample(5, .groupSnd, Date.init(timeIntervalSince1970: 4), ""), + ChatItem.getSample(6, .groupSnd, Date.init(timeIntervalSince1970: 4), "") + ] + assert(indexToInsertAround(.group, itemsToInsert.last, to: items6, Set([5])), (0, 0)) + + let items7 = [ + ChatItem.getSample(4, .groupSnd, Date.init(timeIntervalSince1970: 4), ""), + ChatItem.getSample(5, .groupSnd, Date.init(timeIntervalSince1970: 4), ""), + ChatItem.getSample(6, .groupSnd, Date.init(timeIntervalSince1970: 4), "") + ] + assert(indexToInsertAround(.group, nil, to: items7, Set([6])), (0, 0)) + + let items8 = [ + ChatItem.getSample(2, .groupSnd, Date.init(timeIntervalSince1970: 4), ""), + ChatItem.getSample(4, .groupSnd, Date.init(timeIntervalSince1970: 3), ""), + ChatItem.getSample(5, .groupSnd, Date.init(timeIntervalSince1970: 4), "") + ] + assert(indexToInsertAround(.group, itemsToInsert.last, to: items8, Set([2])), (0, 0)) + + let items9 = [ + ChatItem.getSample(2, .groupSnd, Date.init(timeIntervalSince1970: 3), ""), + ChatItem.getSample(4, .groupSnd, Date.init(timeIntervalSince1970: 3), ""), + ChatItem.getSample(5, .groupSnd, Date.init(timeIntervalSince1970: 4), "") + ] + assert(indexToInsertAround(.group, itemsToInsert.last, to: items9, Set([5])), (1, 0)) + + let items10 = [ + ChatItem.getSample(4, .groupSnd, Date.init(timeIntervalSince1970: 3), ""), + ChatItem.getSample(5, .groupSnd, Date.init(timeIntervalSince1970: 3), ""), + ChatItem.getSample(6, .groupSnd, Date.init(timeIntervalSince1970: 4), "") + ] + assert(indexToInsertAround(.group, itemsToInsert.last, to: items10, Set([4])), (0, 0)) + + let items11: [ChatItem] = [] + assert(indexToInsertAround(.group, itemsToInsert.last, to: items11, Set([])), (0, 0)) +} diff --git a/apps/ios/Shared/Views/Chat/ChatScrollHelpers.swift b/apps/ios/Shared/Views/Chat/ChatScrollHelpers.swift index 873f24d5c3..c1a1eec7d2 100644 --- a/apps/ios/Shared/Views/Chat/ChatScrollHelpers.swift +++ b/apps/ios/Shared/Views/Chat/ChatScrollHelpers.swift @@ -9,25 +9,23 @@ import SwiftUI import SimpleXChat -func loadLastItems(_ loadingMoreItems: Binding, loadingBottomItems: Binding, _ chat: Chat) { - if ItemsModel.shared.chatState.totalAfter == 0 { - return +func loadLastItems(_ loadingMoreItems: Binding, loadingBottomItems: Binding, _ chat: Chat) async { + await MainActor.run { + loadingMoreItems.wrappedValue = true + loadingBottomItems.wrappedValue = true } - loadingMoreItems.wrappedValue = true - loadingBottomItems.wrappedValue = true - Task { - try? await Task.sleep(nanoseconds: 500_000000) - if ChatModel.shared.chatId != chat.chatInfo.id { - await MainActor.run { - loadingMoreItems.wrappedValue = false - } - return - } - await apiLoadMessages(chat.chatInfo.id, ChatPagination.last(count: 50), ItemsModel.shared.chatState) + try? await Task.sleep(nanoseconds: 500_000000) + if ChatModel.shared.chatId != chat.chatInfo.id { await MainActor.run { loadingMoreItems.wrappedValue = false loadingBottomItems.wrappedValue = false } + return + } + await apiLoadMessages(chat.chatInfo.id, ChatPagination.last(count: 50), ItemsModel.shared.chatState) + await MainActor.run { + loadingMoreItems.wrappedValue = false + loadingBottomItems.wrappedValue = false } } @@ -36,6 +34,12 @@ class PreloadState { var prevFirstVisible: Int64 = Int64.min var prevItemsCount: Int = 0 var preloading: Bool = false + + func clear() { + prevFirstVisible = Int64.min + prevItemsCount = 0 + preloading = false + } } func preloadIfNeeded( @@ -43,26 +47,41 @@ func preloadIfNeeded( _ ignoreLoadingRequests: Binding, _ listState: EndlessScrollView.ListState, _ mergedItems: BoxedValue, - loadItems: @escaping (Bool, ChatPagination) async -> Bool + loadItems: @escaping (Bool, ChatPagination) async -> Bool, + loadLastItems: @escaping () async -> Void ) { let state = PreloadState.shared guard !listState.isScrolling && !listState.isAnimatedScrolling, - state.prevFirstVisible != listState.firstVisibleItemIndex || state.prevItemsCount != mergedItems.boxedValue.indexInParentItems.count, !state.preloading, listState.totalItemsCount > 0 else { return } - state.prevFirstVisible = listState.firstVisibleItemId as! Int64 - state.prevItemsCount = mergedItems.boxedValue.indexInParentItems.count - state.preloading = true - let allowLoadMore = allowLoadMoreItems.wrappedValue - Task { - defer { - state.preloading = false + if state.prevFirstVisible != listState.firstVisibleItemId as! Int64 || state.prevItemsCount != mergedItems.boxedValue.indexInParentItems.count { + state.preloading = true + let allowLoadMore = allowLoadMoreItems.wrappedValue + Task { + defer { state.preloading = false } + var triedToLoad = true + await preloadItems(mergedItems.boxedValue, allowLoadMore, listState, ignoreLoadingRequests) { pagination in + triedToLoad = await loadItems(false, pagination) + return triedToLoad + } + if triedToLoad { + state.prevFirstVisible = listState.firstVisibleItemId as! Int64 + state.prevItemsCount = mergedItems.boxedValue.indexInParentItems.count + } + // it's important to ask last items when the view is fully covered with items. Otherwise, visible items from one + // split will be merged with last items and position of scroll will change unexpectedly. + if listState.itemsCanCoverScreen && !ItemsModel.shared.lastItemsLoaded { + await loadLastItems() + } } - await preloadItems(mergedItems.boxedValue, allowLoadMore, listState, ignoreLoadingRequests) { pagination in - await loadItems(false, pagination) + } else if listState.itemsCanCoverScreen && !ItemsModel.shared.lastItemsLoaded { + state.preloading = true + Task { + defer { state.preloading = false } + await loadLastItems() } } } @@ -105,6 +124,7 @@ async { let triedToLoad = await loadItems(ChatPagination.before(chatItemId: loadFromItemId, count: ChatPagination.PRELOAD_COUNT)) if triedToLoad && sizeWas == ItemsModel.shared.reversedChatItems.count && firstItemIdWas == ItemsModel.shared.reversedChatItems.last?.id { ignoreLoadingRequests.wrappedValue = loadFromItemId + return false } return triedToLoad } diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 1e4c16b036..693efcfbb5 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -91,7 +91,11 @@ struct ChatView: View { if let groupInfo = chat.chatInfo.groupInfo, !composeState.message.isEmpty { GroupMentionsView(groupInfo: groupInfo, composeState: $composeState, selectedRange: $selectedRange, keyboardVisible: $keyboardVisible) } - FloatingButtons(theme: theme, scrollView: scrollView, chat: chat, loadingTopItems: $loadingTopItems, requestedTopScroll: $requestedTopScroll, loadingBottomItems: $loadingBottomItems, requestedBottomScroll: $requestedBottomScroll, animatedScrollingInProgress: $animatedScrollingInProgress, listState: scrollView.listState, model: floatingButtonModel) + FloatingButtons(theme: theme, scrollView: scrollView, chat: chat, loadingMoreItems: $loadingMoreItems, loadingTopItems: $loadingTopItems, requestedTopScroll: $requestedTopScroll, loadingBottomItems: $loadingBottomItems, requestedBottomScroll: $requestedBottomScroll, animatedScrollingInProgress: $animatedScrollingInProgress, listState: scrollView.listState, model: floatingButtonModel, reloadItems: { + mergedItems.boxedValue = MergedItems.create(im.reversedChatItems, revealedItems, im.chatState) + scrollView.updateItems(mergedItems.boxedValue.items) + } + ) } connectingText() if selectedChatItems == nil { @@ -262,7 +266,6 @@ struct ChatView: View { // this may already being loading because of changed chat id (see .onChange(of: chat.id) if !loadingBottomItems { - loadLastItems($loadingMoreItems, loadingBottomItems: $loadingBottomItems, chat) allowLoadMoreItems = false DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { allowLoadMoreItems = true @@ -584,7 +587,6 @@ struct ChatView: View { scrollView.updateItems(mergedItems.boxedValue.items) } .onChange(of: chat.id) { _ in - loadLastItems($loadingMoreItems, loadingBottomItems: $loadingBottomItems, chat) allowLoadMoreItems = false DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { allowLoadMoreItems = true @@ -629,7 +631,6 @@ struct ChatView: View { if let unreadIndex { scrollView.scrollToItem(unreadIndex) } - loadLastItems($loadingMoreItems, loadingBottomItems: $loadingBottomItems, chat) DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { allowLoadMoreItems = true } @@ -647,10 +648,8 @@ struct ChatView: View { } else if let index = scrollView.listState.items.lastIndex(where: { $0.hasUnread() }) { // scroll to the top unread item scrollView.scrollToItem(index) - loadLastItems($loadingMoreItems, loadingBottomItems: $loadingBottomItems, chat) } else { scrollView.scrollToBottom() - loadLastItems($loadingMoreItems, loadingBottomItems: $loadingBottomItems, chat) } } } @@ -731,6 +730,7 @@ struct ChatView: View { let theme: AppTheme let scrollView: EndlessScrollView let chat: Chat + @Binding var loadingMoreItems: Bool @Binding var loadingTopItems: Bool @Binding var requestedTopScroll: Bool @Binding var loadingBottomItems: Bool @@ -738,6 +738,7 @@ struct ChatView: View { @Binding var animatedScrollingInProgress: Bool let listState: EndlessScrollView.ListState @ObservedObject var model: FloatingButtonModel + let reloadItems: () -> Void var body: some View { ZStack(alignment: .top) { @@ -795,7 +796,7 @@ struct ChatView: View { } } .onTapGesture { - if loadingBottomItems { + if loadingBottomItems || !ItemsModel.shared.lastItemsLoaded { requestedTopScroll = false requestedBottomScroll = true } else { @@ -815,7 +816,7 @@ struct ChatView: View { } } .onChange(of: loadingBottomItems) { loading in - if !loading && requestedBottomScroll { + if !loading && requestedBottomScroll && ItemsModel.shared.lastItemsLoaded { requestedBottomScroll = false scrollToBottom() } @@ -824,15 +825,25 @@ struct ChatView: View { } private func scrollToTopUnread() { - if let index = listState.items.lastIndex(where: { $0.hasUnread() }) { - animatedScrollingInProgress = true - // scroll to the top unread item - Task { + Task { + if !ItemsModel.shared.chatState.splits.isEmpty { + await MainActor.run { loadingMoreItems = true } + await loadChat(chatId: chat.id, openAroundItemId: nil, clearItems: false) + await MainActor.run { reloadItems() } + if let index = listState.items.lastIndex(where: { $0.hasUnread() }) { + await MainActor.run { animatedScrollingInProgress = true } + await scrollView.scrollToItemAnimated(index) + await MainActor.run { animatedScrollingInProgress = false } + } + await MainActor.run { loadingMoreItems = false } + } else if let index = listState.items.lastIndex(where: { $0.hasUnread() }) { + await MainActor.run { animatedScrollingInProgress = true } + // scroll to the top unread item await scrollView.scrollToItemAnimated(index) await MainActor.run { animatedScrollingInProgress = false } + } else { + logger.debug("No more unread items, total: \(listState.items.count)") } - } else { - logger.debug("No more unread items, total: \(listState.items.count)") } } @@ -1147,6 +1158,11 @@ struct ChatView: View { } else { await loadChatItems(chat, pagination) } + }, + loadLastItems: { + if !loadingMoreItems { + await loadLastItems($loadingMoreItems, loadingBottomItems: $loadingBottomItems, chat) + } } ) } @@ -1247,18 +1263,11 @@ struct ChatView: View { nil } let showAvatar = shouldShowAvatar(item, listItem.nextItem) - let itemSeparation: ItemSeparation let single = switch merged { case .single: true default: false } - if single || revealed { - let prev = listItem.prevItem - itemSeparation = getItemSeparation(item, prev) - let nextForGap = (item.mergeCategory != nil && item.mergeCategory == prev?.mergeCategory) || isLastItem ? nil : listItem.nextItem - } else { - itemSeparation = getItemSeparation(item, nil) - } + let itemSeparation = getItemSeparation(item, single || revealed ? listItem.prevItem: nil) return VStack(spacing: 0) { if let last { DateSeparator(date: last.meta.itemTs).padding(8) diff --git a/apps/ios/Shared/Views/Chat/EndlessScrollView.swift b/apps/ios/Shared/Views/Chat/EndlessScrollView.swift index 9756489628..670707c0c1 100644 --- a/apps/ios/Shared/Views/Chat/EndlessScrollView.swift +++ b/apps/ios/Shared/Views/Chat/EndlessScrollView.swift @@ -171,6 +171,9 @@ class EndlessScrollView: UIScrollView, UIScrollViewDelegate, UIGestu visibleItems.last?.index ?? 0 } + /// Specifies if visible items cover the whole screen or can cover it (if overscrolled) + var itemsCanCoverScreen: Bool = false + /// Whether there is a non-animated scroll to item in progress or not var isScrolling: Bool = false /// Whether there is an animated scroll to item in progress or not @@ -284,7 +287,8 @@ class EndlessScrollView: UIScrollView, UIScrollViewDelegate, UIGestu func updateItems(_ items: [ScrollItem], _ forceReloadVisible: Bool = false) { if !Thread.isMainThread { - fatalError("Use main thread to update items") + logger.error("Use main thread to update items") + return } if bounds.height == 0 { self.listState.items = items @@ -302,6 +306,7 @@ class EndlessScrollView: UIScrollView, UIScrollViewDelegate, UIGestu if items.isEmpty { listState.visibleItems.forEach { item in item.view.removeFromSuperview() } listState.visibleItems = [] + listState.itemsCanCoverScreen = false listState.firstVisibleItemId = EndlessScrollView.DEFAULT_ITEM_ID listState.firstVisibleItemIndex = 0 listState.firstVisibleItemOffset = -insetTop @@ -322,6 +327,7 @@ class EndlessScrollView: UIScrollView, UIScrollViewDelegate, UIGestu var oldVisible = listState.visibleItems var newVisible: [VisibleItem] = [] + var visibleItemsHeight: CGFloat = 0 let offsetsDiff = contentOffsetY - prevProcessedOffset var shouldBeFirstVisible = items.firstIndex(where: { item in item.id == listState.firstVisibleItemId as! ScrollItem.ID }) ?? 0 @@ -389,6 +395,7 @@ class EndlessScrollView: UIScrollView, UIScrollViewDelegate, UIGestu addSubview(vis.view) } newVisible.append(vis) + visibleItemsHeight += vis.view.frame.height nextOffsetY = vis.view.frame.origin.y } else { let vis: VisibleItem @@ -406,6 +413,7 @@ class EndlessScrollView: UIScrollView, UIScrollViewDelegate, UIGestu addSubview(vis.view) } newVisible.append(vis) + visibleItemsHeight += vis.view.frame.height } if abs(nextOffsetY) < contentOffsetY && !allowOneMore { break @@ -435,6 +443,7 @@ class EndlessScrollView: UIScrollView, UIScrollViewDelegate, UIGestu } offset += vis.view.frame.height newVisible.insert(vis, at: 0) + visibleItemsHeight += vis.view.frame.height if offset >= contentOffsetY + bounds.height { break } @@ -450,11 +459,15 @@ class EndlessScrollView: UIScrollView, UIScrollViewDelegate, UIGestu prevProcessedOffset = contentOffsetY listState.visibleItems = newVisible - listState.items = items + // bottom drawing starts from 0 until top visible area at least (bound.height - insetTop) or above top bar (bounds.height). + // For visible items to preserve offset after adding more items having such height is enough + listState.itemsCanCoverScreen = visibleItemsHeight >= bounds.height - insetTop listState.firstVisibleItemId = listState.visibleItems.first?.item.id ?? EndlessScrollView.DEFAULT_ITEM_ID listState.firstVisibleItemIndex = listState.visibleItems.first?.index ?? 0 listState.firstVisibleItemOffset = listState.visibleItems.first?.offset ?? -insetTop + // updating the items with the last step in order to call listener with fully updated state + listState.items = items estimatedContentHeight.update(contentOffset, listState, averageItemHeight, itemsCountChanged) scrollBarView.contentSize = CGSizeMake(bounds.width, estimatedContentHeight.virtualOverscrolledHeight) From ae24da090c8eda0b572bc59d03a7e5f1183a6839 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Sun, 16 Mar 2025 23:48:36 +0700 Subject: [PATCH 478/567] android, desktop: fix crash on very long quoted message (#5751) --- .../chat/simplex/common/views/chat/item/FramedItemView.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt index 689bf195f5..fd8a32af64 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt @@ -66,7 +66,11 @@ fun FramedItemView( @Composable fun ciQuotedMsgView(qi: CIQuote) { Box( - Modifier.padding(vertical = 6.dp, horizontal = 12.dp), + Modifier + // this width limitation prevents crash on calculating constraints that may happen if you post veeeery long message and then quote it. + // Top level layout wants `IntrinsicWidth.Max` and very long layout makes the crash in this case + .widthIn(max = 50000.dp) + .padding(vertical = 6.dp, horizontal = 12.dp), contentAlignment = Alignment.TopStart ) { val sender = qi.sender(membership()) From b8e2e71a60f14128dd8575691253cb031da037a2 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 16 Mar 2025 19:30:31 +0000 Subject: [PATCH 479/567] core: exclude CLI modules from client library (#5758) * core: exclude CLI modules from client library * client_library flag in nix builds * use client_library in builds, update iOS library --- Dockerfile | 2 +- apps/ios/SimpleX.xcodeproj/project.pbxproj | 16 ++++----- flake.nix | 7 ++++ scripts/desktop/build-lib-linux.sh | 2 +- scripts/desktop/build-lib-mac.sh | 4 +-- scripts/desktop/build-lib-windows.sh | 2 +- simplex-chat.cabal | 39 ++++++++++++++++------ 7 files changed, 49 insertions(+), 23 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7b9641777a..cdcbc40d7d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ RUN cp ./scripts/cabal.project.local.linux ./cabal.project.local # Compile simplex-chat RUN cabal update -RUN cabal build exe:simplex-chat --constraint 'simplexmq +client_library' +RUN cabal build exe:simplex-chat --constraint 'simplexmq +client_library' --constraint 'simplex-chat +client_library' # Strip the binary from debug symbols to reduce size RUN bin=$(find /project/dist-newstyle -name "simplex-chat" -type f -executable) && \ diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 9396845831..76684f15e4 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -174,8 +174,8 @@ 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; }; 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; }; - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3RDt4h0Fq4hJV00CU7V8H-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3RDt4h0Fq4hJV00CU7V8H-ghc9.6.3.a */; }; - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3RDt4h0Fq4hJV00CU7V8H.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3RDt4h0Fq4hJV00CU7V8H.a */; }; + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3t1toqktTLqKKdcXhaf4EO-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3t1toqktTLqKKdcXhaf4EO-ghc9.6.3.a */; }; + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3t1toqktTLqKKdcXhaf4EO.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3t1toqktTLqKKdcXhaf4EO.a */; }; 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; @@ -531,8 +531,8 @@ 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3RDt4h0Fq4hJV00CU7V8H-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.0.8-3RDt4h0Fq4hJV00CU7V8H-ghc9.6.3.a"; sourceTree = ""; }; - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3RDt4h0Fq4hJV00CU7V8H.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.0.8-3RDt4h0Fq4hJV00CU7V8H.a"; sourceTree = ""; }; + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3t1toqktTLqKKdcXhaf4EO-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.0.8-3t1toqktTLqKKdcXhaf4EO-ghc9.6.3.a"; sourceTree = ""; }; + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3t1toqktTLqKKdcXhaf4EO.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.0.8-3t1toqktTLqKKdcXhaf4EO.a"; sourceTree = ""; }; 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; @@ -688,8 +688,8 @@ 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */, 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */, 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */, - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3RDt4h0Fq4hJV00CU7V8H-ghc9.6.3.a in Frameworks */, - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3RDt4h0Fq4hJV00CU7V8H.a in Frameworks */, + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3t1toqktTLqKKdcXhaf4EO-ghc9.6.3.a in Frameworks */, + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3t1toqktTLqKKdcXhaf4EO.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -774,8 +774,8 @@ 64C829992D54AEEE006B9E89 /* libffi.a */, 64C829982D54AEED006B9E89 /* libgmp.a */, 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */, - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3RDt4h0Fq4hJV00CU7V8H-ghc9.6.3.a */, - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3RDt4h0Fq4hJV00CU7V8H.a */, + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3t1toqktTLqKKdcXhaf4EO-ghc9.6.3.a */, + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3t1toqktTLqKKdcXhaf4EO.a */, ); path = Libraries; sourceTree = ""; diff --git a/flake.nix b/flake.nix index 1a1043c5f2..9ee0c52020 100644 --- a/flake.nix +++ b/flake.nix @@ -198,6 +198,7 @@ packages.direct-sqlcipher.components.library.libs = pkgs.lib.mkForce [ pkgs.pkgsCross.mingwW64.openssl ]; + packages.simplex-chat.flags.client_library = true; packages.simplexmq.flags.client_library = true; packages.simplexmq.components.library.libs = pkgs.lib.mkForce [ pkgs.pkgsCross.mingwW64.openssl @@ -336,6 +337,7 @@ packages.direct-sqlcipher.patches = [ ./scripts/nix/direct-sqlcipher-android-log.patch ]; + packages.simplex-chat.flags.client_library = true; packages.simplexmq.flags.client_library = true; packages.simplexmq.components.library.libs = pkgs.lib.mkForce [ (android32Pkgs.openssl.override { static = true; enableKTLS = false; }) @@ -445,6 +447,7 @@ packages.direct-sqlcipher.patches = [ ./scripts/nix/direct-sqlcipher-android-log.patch ]; + packages.simplex-chat.flags.client_library = true; packages.simplexmq.flags.client_library = true; packages.simplexmq.components.library.libs = pkgs.lib.mkForce [ (androidPkgs.openssl.override { static = true; }) @@ -550,6 +553,7 @@ packages.simplexmq.flags.swift = true; packages.direct-sqlcipher.flags.commoncrypto = true; packages.entropy.flags.DoNotGetEntropy = true; + packages.simplex-chat.flags.client_library = true; packages.simplexmq.flags.client_library = true; packages.simplexmq.components.library.libs = pkgs.lib.mkForce [ # TODO: have a cross override for iOS, that sets this. @@ -565,6 +569,7 @@ extra-modules = [{ packages.direct-sqlcipher.flags.commoncrypto = true; packages.entropy.flags.DoNotGetEntropy = true; + packages.simplex-chat.flags.client_library = true; packages.simplexmq.flags.client_library = true; packages.simplexmq.components.library.libs = pkgs.lib.mkForce [ ((pkgs.openssl.override { static = true; }).overrideDerivation (old: { CFLAGS = "-mcpu=apple-a7 -march=armv8-a+norcpc" ;})) @@ -583,6 +588,7 @@ packages.simplexmq.flags.swift = true; packages.direct-sqlcipher.flags.commoncrypto = true; packages.entropy.flags.DoNotGetEntropy = true; + packages.simplex-chat.flags.client_library = true; packages.simplexmq.flags.client_library = true; packages.simplexmq.components.library.libs = pkgs.lib.mkForce [ (pkgs.openssl.override { static = true; }) @@ -597,6 +603,7 @@ extra-modules = [{ packages.direct-sqlcipher.flags.commoncrypto = true; packages.entropy.flags.DoNotGetEntropy = true; + packages.simplex-chat.flags.client_library = true; packages.simplexmq.flags.client_library = true; packages.simplexmq.components.library.libs = pkgs.lib.mkForce [ (pkgs.openssl.override { static = true; }) diff --git a/scripts/desktop/build-lib-linux.sh b/scripts/desktop/build-lib-linux.sh index 80ae9fa82e..1db2755926 100755 --- a/scripts/desktop/build-lib-linux.sh +++ b/scripts/desktop/build-lib-linux.sh @@ -25,7 +25,7 @@ for elem in "${exports[@]}"; do count=$(grep -R "$elem$" libsimplex.dll.def | wc for elem in "${exports[@]}"; do count=$(grep -R "\"$elem\"" flake.nix | wc -l); if [ $count -ne 2 ]; then echo Wrong exports in flake.nix. Add \"$elem\" in two places of the file; exit 1; fi ; done rm -rf $BUILD_DIR -cabal build lib:simplex-chat --ghc-options='-optl-Wl,-rpath,$ORIGIN -flink-rts -threaded' --constraint 'simplexmq +client_library' +cabal build lib:simplex-chat --ghc-options='-optl-Wl,-rpath,$ORIGIN -flink-rts -threaded' --constraint 'simplexmq +client_library' --constraint 'simplex-chat +client_library' cd $BUILD_DIR/build #patchelf --add-needed libHSrts_thr-ghc${GHC_VERSION}.so libHSsimplex-chat-*-inplace-ghc${GHC_VERSION}.so #patchelf --add-rpath '$ORIGIN' libHSsimplex-chat-*-inplace-ghc${GHC_VERSION}.so diff --git a/scripts/desktop/build-lib-mac.sh b/scripts/desktop/build-lib-mac.sh index 66af5cbb0c..934d9b8d7d 100755 --- a/scripts/desktop/build-lib-mac.sh +++ b/scripts/desktop/build-lib-mac.sh @@ -28,10 +28,10 @@ rm -rf $BUILD_DIR if [[ "$DATABASE_BACKEND" == "postgres" ]]; then echo "Building with postgres backend..." - cabal build -f client_postgres lib:simplex-chat lib:simplex-chat --ghc-options="-optl-Wl,-rpath,@loader_path -optl-Wl,-L$GHC_LIBS_DIR/$ARCH-osx-ghc-$GHC_VERSION -optl-lHSrts_thr-ghc$GHC_VERSION -optl-lffi" --constraint 'simplexmq +client_library' + cabal build -f client_postgres lib:simplex-chat lib:simplex-chat --ghc-options="-optl-Wl,-rpath,@loader_path -optl-Wl,-L$GHC_LIBS_DIR/$ARCH-osx-ghc-$GHC_VERSION -optl-lHSrts_thr-ghc$GHC_VERSION -optl-lffi" --constraint 'simplexmq +client_library' --constraint 'simplex-chat +client_library' else echo "Building with sqlite backend..." - cabal build lib:simplex-chat lib:simplex-chat --ghc-options="-optl-Wl,-rpath,@loader_path -optl-Wl,-L$GHC_LIBS_DIR/$ARCH-osx-ghc-$GHC_VERSION -optl-lHSrts_thr-ghc$GHC_VERSION -optl-lffi" --constraint 'simplexmq +client_library' + cabal build lib:simplex-chat lib:simplex-chat --ghc-options="-optl-Wl,-rpath,@loader_path -optl-Wl,-L$GHC_LIBS_DIR/$ARCH-osx-ghc-$GHC_VERSION -optl-lHSrts_thr-ghc$GHC_VERSION -optl-lffi" --constraint 'simplexmq +client_library' --constraint 'simplex-chat +client_library' fi cd $BUILD_DIR/build diff --git a/scripts/desktop/build-lib-windows.sh b/scripts/desktop/build-lib-windows.sh index cbb886ccb3..af408d4054 100755 --- a/scripts/desktop/build-lib-windows.sh +++ b/scripts/desktop/build-lib-windows.sh @@ -51,7 +51,7 @@ echo " ghc-options: -shared -threaded -optl-L$openssl_windows_style_path -opt # Very important! Without it the build fails on linking step since the linker can't find exported symbols. # It looks like GHC bug because with such random path the build ends successfully sed -i "s/ld.lld.exe/abracadabra.exe/" `ghc --print-libdir`/settings -cabal build lib:simplex-chat --constraint 'simplexmq +client_library' +cabal build lib:simplex-chat --constraint 'simplexmq +client_library' --constraint 'simplex-chat +client_library' rm -rf apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/ rm -rf apps/multiplatform/desktop/build/cmake diff --git a/simplex-chat.cabal b/simplex-chat.cabal index f8370e4391..c9dc9f6afe 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -24,6 +24,11 @@ flag swift manual: True default: False +flag client_library + description: Don't build server- and CLI-related code. + manual: True + default: False + flag client_postgres description: Build with PostgreSQL instead of SQLite. manual: True @@ -33,13 +38,9 @@ library exposed-modules: Simplex.Chat Simplex.Chat.AppSettings - Simplex.Chat.Bot - Simplex.Chat.Bot.KnownContacts Simplex.Chat.Call Simplex.Chat.Controller - Simplex.Chat.Core Simplex.Chat.Files - Simplex.Chat.Help Simplex.Chat.Library.Commands Simplex.Chat.Library.Internal Simplex.Chat.Library.Subscriber @@ -78,18 +79,24 @@ library Simplex.Chat.Store.Remote Simplex.Chat.Store.Shared Simplex.Chat.Styled - Simplex.Chat.Terminal - Simplex.Chat.Terminal.Input - Simplex.Chat.Terminal.Main - Simplex.Chat.Terminal.Notification - Simplex.Chat.Terminal.Output Simplex.Chat.Types Simplex.Chat.Types.Preferences Simplex.Chat.Types.Shared Simplex.Chat.Types.UITheme Simplex.Chat.Types.Util Simplex.Chat.Util - Simplex.Chat.View + if !flag(client_library) + exposed-modules: + Simplex.Chat.Bot + Simplex.Chat.Bot.KnownContacts + Simplex.Chat.Core + Simplex.Chat.Help + Simplex.Chat.Terminal + Simplex.Chat.Terminal.Input + Simplex.Chat.Terminal.Main + Simplex.Chat.Terminal.Notification + Simplex.Chat.Terminal.Output + Simplex.Chat.View if flag(client_postgres) exposed-modules: Simplex.Chat.Options.Postgres @@ -296,6 +303,8 @@ library , text >=1.2.4.0 && <1.3 executable simplex-bot + if flag(client_library) + buildable: False main-is: Main.hs other-modules: Paths_simplex_chat @@ -313,6 +322,8 @@ executable simplex-bot cpp-options: -DdbPostgres executable simplex-bot-advanced + if flag(client_library) + buildable: False main-is: Main.hs other-modules: Paths_simplex_chat @@ -339,6 +350,8 @@ executable simplex-bot-advanced text >=1.2.4.0 && <1.3 executable simplex-broadcast-bot + if flag(client_library) + buildable: False main-is: Main.hs hs-source-dirs: apps/simplex-broadcast-bot @@ -369,6 +382,8 @@ executable simplex-broadcast-bot text >=1.2.4.0 && <1.3 executable simplex-chat + if flag(client_library) + buildable: False main-is: Main.hs other-modules: Server @@ -400,6 +415,8 @@ executable simplex-chat text >=1.2.4.0 && <1.3 executable simplex-directory-service + if flag(client_library) + buildable: False main-is: Main.hs hs-source-dirs: apps/simplex-directory-service @@ -447,6 +464,8 @@ executable simplex-directory-service , text >=1.2.4.0 && <1.3 test-suite simplex-chat-test + if flag(client_library) + buildable: False type: exitcode-stdio-1.0 main-is: Test.hs other-modules: From e58d09ce785f3e6faff32a3e87f402ba5af7f13f Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 19 Mar 2025 04:18:58 +0700 Subject: [PATCH 480/567] android, desktop: fix negative content offset on some Android devices (#5752) --- .../simplex/common/views/chat/group/GroupChatInfoView.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index a94c787eec..f38cd972f3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -379,10 +379,11 @@ fun ModalData.GroupChatInfoLayout( } else { PaddingValues( top = topPaddingToContent(false), - bottom = navBarPadding + - imePadding + - selectedItemsBarHeight + - (if (navBarPadding > 0.dp && imePadding > 0.dp) -AppBarHeight * fontSizeSqrtMultiplier else 0.dp) + bottom = if (imePadding > 0.dp) { + imePadding + selectedItemsBarHeight + } else { + navBarPadding + selectedItemsBarHeight + } ) } ) { From 745372dd7a564c744a3e7182ddb186d62d4c9ad8 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 19 Mar 2025 04:20:11 +0700 Subject: [PATCH 481/567] android, desktop: open links from notes (#5761) --- .../simplex/common/views/chatlist/ChatListNavLinkView.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt index 89dc84a1fc..5e33117e62 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt @@ -105,13 +105,14 @@ fun ChatListNavLinkView(chat: Chat, nextChatSelected: State) { ) } is ChatInfo.Local -> { + val defaultClickAction = { if (chatModel.chatId.value != chat.id) scope.launch { noteFolderChatAction(chat.remoteHostId, chat.chatInfo.noteFolder) } } ChatListNavLinkLayout( chatLinkPreview = { tryOrShowError("${chat.id}ChatListNavLink", error = { ErrorChatListItem() }) { - ChatPreviewView(chat, showChatPreviews, chatModel.draft.value, chatModel.draftChatId.value, chatModel.currentUser.value?.profile?.displayName, null, disabled, linkMode, inProgress = false, progressByTimeout = false, {}) + ChatPreviewView(chat, showChatPreviews, chatModel.draft.value, chatModel.draftChatId.value, chatModel.currentUser.value?.profile?.displayName, null, disabled, linkMode, inProgress = false, progressByTimeout = false, defaultClickAction) } }, - click = { if (chatModel.chatId.value != chat.id) scope.launch { noteFolderChatAction(chat.remoteHostId, chat.chatInfo.noteFolder) } }, + click = defaultClickAction, dropdownMenuItems = { tryOrShowError("${chat.id}ChatListNavLinkDropdown", error = {}) { NoteFolderMenuItems(chat, showMenu, showMarkRead) From 6556e09a33ac1a61a9f4ba0076adc239bfcc8c75 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 19 Mar 2025 07:16:31 +0000 Subject: [PATCH 482/567] core: update simplexmq to support PostgreSQL servers (#5760) * core: update simplexmq to support postgres * update simplexmq * update ios --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 16 ++++++++-------- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat/Call.hs | 4 ++-- src/Simplex/Chat/Messages.hs | 3 ++- src/Simplex/Chat/Operators.hs | 3 ++- src/Simplex/Chat/Protocol.hs | 4 ++-- src/Simplex/Chat/Types.hs | 4 ++-- src/Simplex/Chat/Types/Preferences.hs | 3 ++- src/Simplex/Chat/Types/Shared.hs | 2 +- src/Simplex/Chat/Types/UITheme.hs | 3 ++- tests/ChatClient.hs | 13 +++++++------ tests/ChatTests/Direct.hs | 6 ++---- tests/ChatTests/Groups.hs | 6 ++---- tests/ChatTests/Profiles.hs | 3 +-- 15 files changed, 37 insertions(+), 37 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 76684f15e4..6727643022 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -174,8 +174,8 @@ 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; }; 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; }; - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3t1toqktTLqKKdcXhaf4EO-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3t1toqktTLqKKdcXhaf4EO-ghc9.6.3.a */; }; - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3t1toqktTLqKKdcXhaf4EO.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3t1toqktTLqKKdcXhaf4EO.a */; }; + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-CNSVk2Y5c4gAUnxeodOxfm-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-CNSVk2Y5c4gAUnxeodOxfm-ghc9.6.3.a */; }; + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-CNSVk2Y5c4gAUnxeodOxfm.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-CNSVk2Y5c4gAUnxeodOxfm.a */; }; 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; @@ -531,8 +531,8 @@ 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3t1toqktTLqKKdcXhaf4EO-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.0.8-3t1toqktTLqKKdcXhaf4EO-ghc9.6.3.a"; sourceTree = ""; }; - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3t1toqktTLqKKdcXhaf4EO.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.0.8-3t1toqktTLqKKdcXhaf4EO.a"; sourceTree = ""; }; + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-CNSVk2Y5c4gAUnxeodOxfm-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.0.8-CNSVk2Y5c4gAUnxeodOxfm-ghc9.6.3.a"; sourceTree = ""; }; + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-CNSVk2Y5c4gAUnxeodOxfm.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.0.8-CNSVk2Y5c4gAUnxeodOxfm.a"; sourceTree = ""; }; 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; @@ -688,8 +688,8 @@ 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */, 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */, 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */, - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3t1toqktTLqKKdcXhaf4EO-ghc9.6.3.a in Frameworks */, - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3t1toqktTLqKKdcXhaf4EO.a in Frameworks */, + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-CNSVk2Y5c4gAUnxeodOxfm-ghc9.6.3.a in Frameworks */, + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-CNSVk2Y5c4gAUnxeodOxfm.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -774,8 +774,8 @@ 64C829992D54AEEE006B9E89 /* libffi.a */, 64C829982D54AEED006B9E89 /* libgmp.a */, 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */, - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3t1toqktTLqKKdcXhaf4EO-ghc9.6.3.a */, - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-3t1toqktTLqKKdcXhaf4EO.a */, + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-CNSVk2Y5c4gAUnxeodOxfm-ghc9.6.3.a */, + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-CNSVk2Y5c4gAUnxeodOxfm.a */, ); path = Libraries; sourceTree = ""; diff --git a/cabal.project b/cabal.project index 2e1b43d41f..a40bcc43bc 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: a491a1d8780054432542611f540317a6090b9360 + tag: 5c0adcbbff873bf9e58d8939f8e8178692aec384 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 53ccda920f..317ba843a8 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."a491a1d8780054432542611f540317a6090b9360" = "183wmraa25rxcf3b07apimsdvamccc3qx3p5rr726qzvpkvrxpab"; + "https://github.com/simplex-chat/simplexmq.git"."5c0adcbbff873bf9e58d8939f8e8178692aec384" = "0qxyv4fn5kh5dwx03n8d09x72w15vg19s1ikq2q5cgpq404z799x"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat/Call.hs b/src/Simplex/Chat/Call.hs index 3b1f28dd27..18398ecb3e 100644 --- a/src/Simplex/Chat/Call.hs +++ b/src/Simplex/Chat/Call.hs @@ -23,10 +23,10 @@ import Data.Text (Text) import Data.Time.Clock (UTCTime) import Simplex.Chat.Options.DB (FromField (..), ToField (..)) import Simplex.Chat.Types (Contact, ContactId, User) -import Simplex.Messaging.Agent.Store.DB (Binary (..)) +import Simplex.Messaging.Agent.Store.DB (Binary (..), fromTextField_) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Encoding.String -import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, fstToLower, singleFieldJSON) +import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fstToLower, singleFieldJSON) import Simplex.Messaging.Util (decodeJSON, encodeJSON) data Call = Call diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index ae88bc796b..79d416dee5 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -50,10 +50,11 @@ import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Shared import Simplex.Chat.Types.Util (textParseJSON) import Simplex.Messaging.Agent.Protocol (AgentMsgId, MsgMeta (..), MsgReceiptStatus (..)) +import Simplex.Messaging.Agent.Store.DB (fromTextField_) import Simplex.Messaging.Crypto.File (CryptoFile (..)) import qualified Simplex.Messaging.Crypto.File as CF import Simplex.Messaging.Encoding.String -import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, parseAll, sumTypeJSON) +import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, parseAll, sumTypeJSON) import Simplex.Messaging.Protocol (BlockingInfo, MsgBody) import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8, (<$?>)) diff --git a/src/Simplex/Chat/Operators.hs b/src/Simplex/Chat/Operators.hs index 15135d4e9e..5240460c9c 100644 --- a/src/Simplex/Chat/Operators.hs +++ b/src/Simplex/Chat/Operators.hs @@ -50,8 +50,9 @@ import Simplex.Chat.Options.DB (FromField (..), ToField (..)) import Simplex.Chat.Types (User) import Simplex.Chat.Types.Util (textParseJSON) import Simplex.Messaging.Agent.Env.SQLite (ServerCfg (..), ServerRoles (..), allRoles) +import Simplex.Messaging.Agent.Store.DB (fromTextField_) import Simplex.Messaging.Encoding.String -import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, fromTextField_, sumTypeJSON) +import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, sumTypeJSON) import Simplex.Messaging.Protocol (AProtocolType (..), ProtoServerWithAuth (..), ProtocolServer (..), ProtocolType (..), ProtocolTypeI, SProtocolType (..), UserProtocol) import Simplex.Messaging.Transport.Client (TransportHost (..)) import Simplex.Messaging.Util (atomicModifyIORef'_, safeDecodeUtf8) diff --git a/src/Simplex/Chat/Protocol.hs b/src/Simplex/Chat/Protocol.hs index 566cd04003..2281c1aefa 100644 --- a/src/Simplex/Chat/Protocol.hs +++ b/src/Simplex/Chat/Protocol.hs @@ -29,7 +29,6 @@ import qualified Data.Aeson.KeyMap as JM import qualified Data.Aeson.TH as JQ import qualified Data.Aeson.Types as JT import qualified Data.Attoparsec.ByteString.Char8 as A -import Data.Bifunctor (first) import Data.ByteString.Char8 (ByteString) import qualified Data.ByteString.Char8 as B import Data.ByteString.Internal (c2w, w2c) @@ -54,11 +53,12 @@ import Simplex.Chat.Types import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Shared import Simplex.Messaging.Agent.Protocol (VersionSMPA, pqdrSMPAgentVersion) +import Simplex.Messaging.Agent.Store.DB (fromTextField_) import qualified Simplex.Messaging.Agent.Store.DB as DB import Simplex.Messaging.Compression (Compressed, compress1, decompress1) import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding.String -import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, fromTextField_, fstToLower, parseAll, sumTypeJSON, taggedObjectJSON) +import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, fstToLower, parseAll, sumTypeJSON, taggedObjectJSON) import Simplex.Messaging.Protocol (MsgBody) import Simplex.Messaging.Util (decodeJSON, eitherToMaybe, encodeJSON, safeDecodeUtf8, (<$?>)) import Simplex.Messaging.Version hiding (version) diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index 5ba7565611..2bc9e725e5 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -52,11 +52,11 @@ import Simplex.Chat.Types.Util import Simplex.FileTransfer.Description (FileDigest) import Simplex.FileTransfer.Types (RcvFileId, SndFileId) import Simplex.Messaging.Agent.Protocol (ACorrId, AEventTag (..), AEvtTag (..), ConnId, ConnectionMode (..), ConnectionRequestUri, InvitationId, SAEntity (..), UserId) -import Simplex.Messaging.Agent.Store.DB (Binary (..)) +import Simplex.Messaging.Agent.Store.DB (Binary (..), blobFieldDecoder, fromTextField_) import Simplex.Messaging.Crypto.File (CryptoFileArgs (..)) import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport, pattern PQEncOff) import Simplex.Messaging.Encoding.String -import Simplex.Messaging.Parsers (blobFieldDecoder, defaultJSON, dropPrefix, enumJSON, fromTextField_, sumTypeJSON) +import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, sumTypeJSON) import Simplex.Messaging.Util (safeDecodeUtf8, (<$?>)) import Simplex.Messaging.Version import Simplex.Messaging.Version.Internal diff --git a/src/Simplex/Chat/Types/Preferences.hs b/src/Simplex/Chat/Types/Preferences.hs index 2e704c5cf5..63d80657dc 100644 --- a/src/Simplex/Chat/Types/Preferences.hs +++ b/src/Simplex/Chat/Types/Preferences.hs @@ -32,8 +32,9 @@ import qualified Data.Text as T import GHC.Records.Compat import Simplex.Chat.Options.DB (FromField (..), ToField (..)) import Simplex.Chat.Types.Shared +import Simplex.Messaging.Agent.Store.DB (blobFieldDecoder, fromTextField_) import Simplex.Messaging.Encoding.String -import Simplex.Messaging.Parsers (blobFieldDecoder, defaultJSON, dropPrefix, enumJSON, fromTextField_, sumTypeJSON) +import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, sumTypeJSON) import Simplex.Messaging.Util (decodeJSON, encodeJSON, safeDecodeUtf8, (<$?>)) data ChatFeature diff --git a/src/Simplex/Chat/Types/Shared.hs b/src/Simplex/Chat/Types/Shared.hs index e22610cfe5..4c6adae4e9 100644 --- a/src/Simplex/Chat/Types/Shared.hs +++ b/src/Simplex/Chat/Types/Shared.hs @@ -7,8 +7,8 @@ import Data.Aeson (FromJSON (..), ToJSON (..)) import qualified Data.Attoparsec.ByteString.Char8 as A import qualified Data.ByteString.Char8 as B import Simplex.Chat.Options.DB (FromField (..), ToField (..)) +import Simplex.Messaging.Agent.Store.DB (blobFieldDecoder) import Simplex.Messaging.Encoding.String -import Simplex.Messaging.Parsers (blobFieldDecoder) import Simplex.Messaging.Util ((<$?>)) data GroupMemberRole diff --git a/src/Simplex/Chat/Types/UITheme.hs b/src/Simplex/Chat/Types/UITheme.hs index f2512a3a5a..d0f23a7307 100644 --- a/src/Simplex/Chat/Types/UITheme.hs +++ b/src/Simplex/Chat/Types/UITheme.hs @@ -15,8 +15,9 @@ import Data.Maybe (fromMaybe) import Data.Text (Text) import Simplex.Chat.Options.DB (FromField (..), ToField (..)) import Simplex.Chat.Types.Util +import Simplex.Messaging.Agent.Store.DB (fromTextField_) import Simplex.Messaging.Encoding.String -import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_) +import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON) import Simplex.Messaging.Util (decodeJSON, encodeJSON) data UITheme = UITheme diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index 28f7befebe..c41c155697 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -54,8 +54,8 @@ import Simplex.Messaging.Crypto.Ratchet (supportedE2EEncryptVRange) import qualified Simplex.Messaging.Crypto.Ratchet as CR import Simplex.Messaging.Protocol (srvHostnamesSMPClientVersion) import Simplex.Messaging.Server (runSMPServerBlocking) -import Simplex.Messaging.Server.Env.STM (ServerConfig (..), StartOptions (..), defaultMessageExpiration, defaultIdleQueueInterval, defaultNtfExpiration, defaultInactiveClientExpiration) -import Simplex.Messaging.Server.MsgStore.Types (AMSType (..), SMSType (..)) +import Simplex.Messaging.Server.Env.STM (AServerStoreCfg (..), ServerConfig (..), ServerStoreCfg (..), StartOptions (..), StorePaths (..), defaultMessageExpiration, defaultIdleQueueInterval, defaultNtfExpiration, defaultInactiveClientExpiration) +import Simplex.Messaging.Server.MsgStore.Types (SQSType (..), SMSType (..)) import Simplex.Messaging.Transport import Simplex.Messaging.Transport.Server (ServerCredentials (..), defaultTransportServerConfig) import Simplex.Messaging.Version @@ -476,14 +476,12 @@ smpServerCfg = ServerConfig { transports = [(serverPort, transport @TLS, False)], tbqSize = 1, - msgStoreType = AMSType SMSMemory, msgQueueQuota = 16, maxJournalMsgCount = 24, maxJournalStateLines = 4, queueIdBytes = 12, msgIdBytes = 6, - storeLogFile = Nothing, - storeMsgsFile = Nothing, + serverStoreCfg = ASSCfg SQSMemory SMSMemory $ SSCMemory Nothing, storeNtfsFile = Nothing, allowNewQueues = True, -- server password is disabled as otherwise v1 tests fail @@ -518,9 +516,12 @@ smpServerCfg = allowSMPProxy = True, serverClientConcurrency = 16, information = Nothing, - startOptions = StartOptions False False + startOptions = StartOptions {maintenance = False, compactLog = False, skipWarnings = False, confirmMigrations = MCYesUp} } +persistentServerStoreCfg :: FilePath -> AServerStoreCfg +persistentServerStoreCfg tmp = ASSCfg SQSMemory SMSMemory $ SSCMemory $ Just StorePaths {storeLogFile = tmp <> "/smp-server-store.log", storeMsgsFile = Just $ tmp <> "/smp-server-messages.log"} + withSmpServer :: IO () -> IO () withSmpServer = withSmpServer' smpServerCfg diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index 998835b867..5c18088b76 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -270,8 +270,7 @@ testRetryConnecting ps = testChatCfgOpts2 cfg' opts' aliceProfile bobProfile tes smpServerCfg { transports = [("7003", transport @TLS, False)], msgQueueQuota = 2, - storeLogFile = Just $ tmp <> "/smp-server-store.log", - storeMsgsFile = Just $ tmp <> "/smp-server-messages.log" + serverStoreCfg = persistentServerStoreCfg tmp } fastRetryInterval = defaultReconnectInterval {initialInterval = 50000} -- same as in agent tests cfg' = @@ -329,8 +328,7 @@ testRetryConnectingClientTimeout ps = do smpServerCfg { transports = [("7003", transport @TLS, False)], msgQueueQuota = 2, - storeLogFile = Just $ tmp <> "/smp-server-store.log", - storeMsgsFile = Just $ tmp <> "/smp-server-messages.log" + serverStoreCfg = persistentServerStoreCfg tmp } fastRetryInterval = defaultReconnectInterval {initialInterval = 50000} -- same as in agent tests cfg' = diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index 2e99de4a82..73a8735f63 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -1988,8 +1988,7 @@ testSharedMessageBody ps = serverCfg' = smpServerCfg { transports = [("7003", transport @TLS, False)], - storeLogFile = Just $ tmp <> "/smp-server-store.log", - storeMsgsFile = Just $ tmp <> "/smp-server-messages.log" + serverStoreCfg = persistentServerStoreCfg tmp } opts' = testOpts @@ -2045,8 +2044,7 @@ testSharedBatchBody ps = serverCfg' = smpServerCfg { transports = [("7003", transport @TLS, False)], - storeLogFile = Just $ tmp <> "/smp-server-store.log", - storeMsgsFile = Just $ tmp <> "/smp-server-messages.log" + serverStoreCfg = persistentServerStoreCfg tmp } opts' = testOpts diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index 5d2b9f5ba8..72d502392e 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -311,8 +311,7 @@ testRetryAcceptingViaContactLink ps = testChatCfgOpts2 cfg' opts' aliceProfile b smpServerCfg { transports = [("7003", transport @TLS, False)], msgQueueQuota = 2, - storeLogFile = Just $ tmp <> "/smp-server-store.log", - storeMsgsFile = Just $ tmp <> "/smp-server-messages.log" + serverStoreCfg = persistentServerStoreCfg tmp } fastRetryInterval = defaultReconnectInterval {initialInterval = 50000} -- same as in agent tests cfg' = From 6e7df9c72d848460c9e23cf866976c45366970f3 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 19 Mar 2025 22:06:08 +0700 Subject: [PATCH 483/567] android, desktop: menu near top floating button (#5764) --- .../kotlin/chat/simplex/common/views/chat/ChatView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 1c3f4b7749..78e97ffbf2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -1754,7 +1754,7 @@ fun BoxScope.FloatingButtons( onLongClick = { showDropDown.value = true } ) - Box(Modifier.fillMaxWidth().wrapContentSize(Alignment.TopEnd)) { + Box(Modifier.fillMaxWidth().wrapContentSize(Alignment.TopEnd).align(Alignment.TopEnd)) { val density = LocalDensity.current val width = remember { mutableStateOf(250.dp) } DefaultDropdownMenu( From 6b75f61537de98fd7cdf7fa731ee9016e9a35d92 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 19 Mar 2025 22:07:05 +0700 Subject: [PATCH 484/567] android, desktop: scrolling improvements (#5753) * android, desktop: scrolling improvements * more changes * fixes * search * fix concurrency --------- Co-authored-by: Evgeny Poberezkin --- .../common/views/chat/ChatItemsLoader.kt | 194 ++++++++++++++- .../simplex/common/views/chat/ChatView.kt | 231 +++++++++--------- .../views/chatlist/ChatListNavLinkView.kt | 2 +- 3 files changed, 305 insertions(+), 122 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt index 9050804db4..385bf42397 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt @@ -6,6 +6,7 @@ import chat.simplex.common.model.ChatModel.withChats import chat.simplex.common.platform.chatModel import kotlinx.coroutines.* import kotlinx.coroutines.flow.StateFlow +import kotlinx.datetime.Instant import kotlin.math.min const val TRIM_KEEP_COUNT = 200 @@ -122,18 +123,20 @@ suspend fun processLoadedChat( } } is ChatPagination.Around -> { - val newSplits = if (openAroundItemId == null) { + val newSplits: ArrayList = if (openAroundItemId == null) { newItems.addAll(oldItems) - removeDuplicatesAndUpperSplits(newItems, chat, splits, visibleItemIndexesNonReversed) + ArrayList(removeDuplicatesAndUpperSplits(newItems, chat, splits, visibleItemIndexesNonReversed)) } else { - emptyList() + arrayListOf() } - // currently, items will always be added on top, which is index 0 - newItems.addAll(0, chat.chatItems) + val (itemIndex, splitIndex) = indexToInsertAround(chat.chatInfo.chatType, chat.chatItems.lastOrNull(), to = newItems, newSplits.toSet()) + //indexToInsertAroundTest() + newItems.addAll(itemIndex, chat.chatItems) + newSplits.add(splitIndex, chat.chatItems.last().id) withChats(contentTag) { chatItems.replaceAll(newItems) - splits.value = listOf(chat.chatItems.last().id) + newSplits + splits.value = newSplits unreadAfterItemId.value = chat.chatItems.last().id totalAfter.value = navInfo.afterTotal unreadTotal.value = chat.chatStats.unreadCount @@ -151,10 +154,12 @@ suspend fun processLoadedChat( } is ChatPagination.Last -> { newItems.addAll(oldItems) + val newSplits = removeDuplicatesAndUnusedSplits(newItems, chat, chatState.splits.value) removeDuplicates(newItems, chat) newItems.addAll(chat.chatItems) withChats(contentTag) { chatItems.replaceAll(newItems) + chatState.splits.value = newSplits unreadAfterNewestLoaded.value = 0 } } @@ -240,7 +245,15 @@ private fun removeDuplicatesAndModifySplitsOnAfterPagination( val indexInSplitRanges = splits.value.indexOf(paginationChatItemId) // Currently, it should always load from split range val loadingFromSplitRange = indexInSplitRanges != -1 - val splitsToMerge = if (loadingFromSplitRange && indexInSplitRanges + 1 <= splits.value.size) ArrayList(splits.value.subList(indexInSplitRanges + 1, splits.value.size)) else ArrayList() + val topSplits: List + val splitsToMerge: ArrayList + if (loadingFromSplitRange && indexInSplitRanges + 1 <= splits.value.size) { + splitsToMerge = ArrayList(splits.value.subList(indexInSplitRanges + 1, splits.value.size)) + topSplits = splits.value.take(indexInSplitRanges + 1) + } else { + splitsToMerge = ArrayList() + topSplits = emptyList() + } newItems.removeAll { val duplicate = newIds.contains(it.id) if (loadingFromSplitRange && duplicate) { @@ -259,8 +272,8 @@ private fun removeDuplicatesAndModifySplitsOnAfterPagination( } var newSplits: List = emptyList() if (firstItemIdBelowAllSplits != null) { - // no splits anymore, all were merged with bottom items - newSplits = emptyList() + // no splits below anymore, all were merged with bottom items + newSplits = topSplits } else { if (splitsToRemove.isNotEmpty()) { val new = ArrayList(splits.value) @@ -323,6 +336,31 @@ private fun removeDuplicatesAndUpperSplits( return newSplits } +private fun removeDuplicatesAndUnusedSplits( + newItems: SnapshotStateList, + chat: Chat, + splits: List +): List { + if (splits.isEmpty()) { + removeDuplicates(newItems, chat) + return splits + } + + val newSplits = splits.toMutableList() + val (newIds, _) = mapItemsToIds(chat.chatItems) + newItems.removeAll { + val duplicate = newIds.contains(it.id) + if (duplicate) { + val firstIndex = newSplits.indexOf(it.id) + if (firstIndex != -1) { + newSplits.removeAt(firstIndex) + } + } + duplicate + } + return newSplits +} + // ids, number of unread items private fun mapItemsToIds(items: List): Pair, Int> { var unreadInLoaded = 0 @@ -343,3 +381,141 @@ private fun removeDuplicates(newItems: SnapshotStateList, chat: Chat) val (newIds, _) = mapItemsToIds(chat.chatItems) newItems.removeAll { newIds.contains(it.id) } } + +private data class SameTimeItem(val index: Int, val item: ChatItem) + +// return (item index, split index) +private fun indexToInsertAround(chatType: ChatType, lastNew: ChatItem?, to: List, splits: Set): Pair { + if (to.size <= 0 || lastNew == null) { + return 0 to 0 + } + // group sorting: item_ts, item_id + // everything else: created_at, item_id + val compareByTimeTs = chatType == ChatType.Group + // in case several items have the same time as another item in the `to` array + var sameTime: ArrayList = arrayListOf() + + // trying to find new split index for item looks difficult but allows to not use one more loop. + // The idea is to memorize how many splits were till any index (map number of splits until index) + // and use resulting itemIndex to decide new split index position. + // Because of the possibility to have many items with the same timestamp, it's possible to see `itemIndex < || == || > i`. + val splitsTillIndex: ArrayList = arrayListOf() + var splitsPerPrevIndex = 0 + + for (i in to.indices) { + val item = to[i] + + splitsPerPrevIndex = if (splits.contains(item.id)) splitsPerPrevIndex + 1 else splitsPerPrevIndex + splitsTillIndex.add(splitsPerPrevIndex) + val itemIsNewer = (if (compareByTimeTs) item.meta.itemTs > lastNew.meta.itemTs else item.meta.createdAt > lastNew.meta.createdAt) + if (itemIsNewer || i + 1 == to.size) { + val same = if (compareByTimeTs) lastNew.meta.itemTs == item.meta.itemTs else lastNew.meta.createdAt == item.meta.createdAt + if (same) { + sameTime.add(SameTimeItem(i, item)) + } + // time to stop the loop. Item is newer, or it's the last item in `to` array, taking previous items and checking position inside them + val itemIndex: Int + val first = if (sameTime.size > 1) sameTime.sortedWith { prev, next -> prev.item.meta.itemId.compareTo(next.item.id) }.firstOrNull { same -> same.item.id > lastNew.id } else null + if (sameTime.size > 1 && first != null) { + itemIndex = first.index + } else if (sameTime.size == 1) { + itemIndex = if (sameTime[0].item.id > lastNew.id) sameTime[0].index else sameTime[0].index + 1 + } else { + itemIndex = if (itemIsNewer) i else i + 1 + } + val splitIndex = splitsTillIndex[min(itemIndex, splitsTillIndex.size - 1)] + val prevItemSplitIndex = if (itemIndex == 0) 0 else splitsTillIndex[min(itemIndex - 1, splitsTillIndex.size - 1)] + return Pair(itemIndex, if (splitIndex == prevItemSplitIndex) splitIndex else prevItemSplitIndex) + } + val same = if (compareByTimeTs) lastNew.meta.itemTs == item.meta.itemTs else lastNew.meta.createdAt == item.meta.createdAt + if (same) { + sameTime.add(SameTimeItem(index = i, item = item)) + } else { + sameTime = arrayListOf() + } + } + // shouldn't be here + return Pair(to.size, splits.size) +} + +private fun indexToInsertAroundTest() { + fun assert(one: Pair, two: Pair) { + if (one != two) { + throw Exception("$one != $two") + } + } + + val itemsToInsert = listOf(ChatItem.getSampleData(3, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds( 3), "")) + val items1 = listOf( + ChatItem.getSampleData(0, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds( 0), ""), + ChatItem.getSampleData(1, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds( 1), ""), + ChatItem.getSampleData(2, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds( 2), "") + ) + assert(indexToInsertAround(ChatType.Group, itemsToInsert.lastOrNull(), to = items1, setOf(1)), Pair(3, 1)) + + val items2 = listOf( + ChatItem.getSampleData(0, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(0), ""), + ChatItem.getSampleData(1, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(1), ""), + ChatItem.getSampleData(2, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(3), "") + ) + assert(indexToInsertAround(ChatType.Group, itemsToInsert.lastOrNull(), to = items2, setOf(2)), Pair(3, 1)) + + val items3 = listOf( + ChatItem.getSampleData(0, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(0), ""), + ChatItem.getSampleData(1, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(3), ""), + ChatItem.getSampleData(2, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(3), "") + ) + assert(indexToInsertAround(ChatType.Group, itemsToInsert.lastOrNull(), to = items3, setOf(1)), Pair(3, 1)) + + val items4 = listOf( + ChatItem.getSampleData(0, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(0), ""), + ChatItem.getSampleData(4, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(3), ""), + ChatItem.getSampleData(5, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(3), "") + ) + assert(indexToInsertAround(ChatType.Group, itemsToInsert.lastOrNull(), to = items4, setOf(4)), Pair(1, 0)) + + val items5 = listOf( + ChatItem.getSampleData(0, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(0), ""), + ChatItem.getSampleData(2, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(3), ""), + ChatItem.getSampleData(4, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(3), "") + ) + assert(indexToInsertAround(ChatType.Group, itemsToInsert.lastOrNull(), to = items5, setOf(2)), Pair(2, 1)) + + val items6 = listOf( + ChatItem.getSampleData(4, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(4), ""), + ChatItem.getSampleData(5, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(4), ""), + ChatItem.getSampleData(6, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(4), "") + ) + assert(indexToInsertAround(ChatType.Group, itemsToInsert.lastOrNull(), to = items6, setOf(5)), Pair(0, 0)) + + val items7 = listOf( + ChatItem.getSampleData(4, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(4), ""), + ChatItem.getSampleData(5, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(4), ""), + ChatItem.getSampleData(6, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(4), "") + ) + assert(indexToInsertAround(ChatType.Group, null, to = items7, setOf(6)), Pair(0, 0)) + + val items8 = listOf( + ChatItem.getSampleData(2, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(4), ""), + ChatItem.getSampleData(4, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(3), ""), + ChatItem.getSampleData(5, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(4), "") + ) + assert(indexToInsertAround(ChatType.Group, itemsToInsert.lastOrNull(), to = items8, setOf(2)), Pair(0, 0)) + + val items9 = listOf( + ChatItem.getSampleData(2, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(3), ""), + ChatItem.getSampleData(4, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(3), ""), + ChatItem.getSampleData(5, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(4), "") + ) + assert(indexToInsertAround(ChatType.Group, itemsToInsert.lastOrNull(), to = items9, setOf(5)), Pair(1, 0)) + + val items10 = listOf( + ChatItem.getSampleData(4, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(3), ""), + ChatItem.getSampleData(5, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(3), ""), + ChatItem.getSampleData(6, CIDirection.GroupSnd(), Instant.fromEpochMilliseconds(4), "") + ) + assert(indexToInsertAround(ChatType.Group, itemsToInsert.lastOrNull(), to = items10, setOf(4)), Pair(0, 0)) + + val items11: List = listOf() + assert(indexToInsertAround(ChatType.Group, itemsToInsert.lastOrNull(), to = items11, emptySet()), Pair(0, 0)) +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 78e97ffbf2..46965d07fc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -1227,7 +1227,7 @@ fun BoxScope.ChatItemsList( if (reportsState != null) { reportsListState = null reportsState - } else if (index <= 0) { + } else if (index <= 0 || !searchValueIsEmpty.value) { LazyListState(0, 0) } else { LazyListState(index + 1, -maxHeightForList.value) @@ -1242,19 +1242,19 @@ fun BoxScope.ChatItemsList( if (searchValueIsEmpty.value && reversedChatItems.value.size < ChatPagination.INITIAL_COUNT) ignoreLoadingRequests.add(reversedChatItems.value.lastOrNull()?.id ?: return@LaunchedEffect) } - if (!loadingMoreItems.value) { - PreloadItems(chatInfo.id, if (searchValueIsEmpty.value) ignoreLoadingRequests else mutableSetOf(), contentTag, mergedItems, listState, ChatPagination.UNTIL_PRELOAD_COUNT) { chatId, pagination -> - if (loadingMoreItems.value) return@PreloadItems false + PreloadItems(chatInfo.id, if (searchValueIsEmpty.value) ignoreLoadingRequests else mutableSetOf(), loadingMoreItems, resetListState, contentTag, mergedItems, listState, ChatPagination.UNTIL_PRELOAD_COUNT) { chatId, pagination -> + if (loadingMoreItems.value || chatId != chatModel.chatId.value) return@PreloadItems false + loadingMoreItems.value = true + withContext(NonCancellable) { try { - loadingMoreItems.value = true loadMessages(chatId, pagination) { visibleItemIndexesNonReversed(mergedItems, reversedChatItems.value.size, listState.value) } } finally { loadingMoreItems.value = false } - true } + true } val remoteHostIdUpdated = rememberUpdatedState(remoteHostId) @@ -1275,7 +1275,6 @@ fun BoxScope.ChatItemsList( scrollToItemId.value = null } } } - LoadLastItems(loadingMoreItems, resetListState, remoteHostId, chatInfo) SmallScrollOnNewMessage(listState, reversedChatItems) val finishedInitialComposition = remember { mutableStateOf(false) } NotifyChatListOnFinishingComposition(finishedInitialComposition, chatInfo, revealedItems, listState, onComposed) @@ -1583,7 +1582,7 @@ fun BoxScope.ChatItemsList( } } } - FloatingButtons(topPaddingToContent, topPaddingToContentPx, loadingMoreItems, animatedScrollingInProgress, mergedItems, unreadCount, maxHeight, composeViewHeight, searchValue, markChatRead, listState) + FloatingButtons(reversedChatItems, chatInfoUpdated, topPaddingToContent, topPaddingToContentPx, contentTag, loadingMoreItems, animatedScrollingInProgress, mergedItems, unreadCount, maxHeight, composeViewHeight, searchValue, markChatRead, listState, loadMessages) FloatingDate(Modifier.padding(top = 10.dp + topPaddingToContent).align(Alignment.TopCenter), topPaddingToContentPx, mergedItems, listState) LaunchedEffect(Unit) { @@ -1603,21 +1602,17 @@ fun BoxScope.ChatItemsList( } } -@Composable -private fun LoadLastItems(loadingMoreItems: MutableState, resetListState: State, remoteHostId: Long?, chatInfo: ChatInfo) { - val contentTag = LocalContentTag.current - LaunchedEffect(remoteHostId, chatInfo.id, resetListState.value) { - try { - loadingMoreItems.value = true - if (chatModel.chatStateForContent(contentTag).totalAfter.value <= 0) return@LaunchedEffect - delay(500) - withContext(Dispatchers.Default) { - apiLoadMessages(remoteHostId, chatInfo.chatType, chatInfo.apiId, contentTag, ChatPagination.Last(ChatPagination.INITIAL_COUNT)) - } - } finally { - loadingMoreItems.value = false - } - } +private suspend fun loadLastItems(chatId: State, contentTag: MsgContentTag?, listState: State, loadItems: State Boolean>) { + val lastVisible = listState.value.layoutInfo.visibleItemsInfo.lastOrNull() + val itemsCanCoverScreen = lastVisible != null && listState.value.layoutInfo.viewportEndOffset - listState.value.layoutInfo.afterContentPadding <= lastVisible.offset + lastVisible.size + if (!itemsCanCoverScreen) return + + val chatState = chatModel.chatStateForContent(contentTag) + val lastItemsLoaded = chatState.splits.value.isEmpty() || chatState.splits.value.firstOrNull() != chatModel.chatItemsForContent(contentTag).value.lastOrNull()?.id + if (lastItemsLoaded) return + + delay(500) + loadItems.value(chatId.value, ChatPagination.Last(ChatPagination.INITIAL_COUNT)) } // TODO: in extra rare case when after loading last items only 1 item is loaded, the view will jump like when receiving new message @@ -1680,8 +1675,11 @@ private fun NotifyChatListOnFinishingComposition( @Composable fun BoxScope.FloatingButtons( + reversedChatItems: State>, + chatInfo: State, topPaddingToContent: Dp, topPaddingToContentPx: State, + contentTag: MsgContentTag?, loadingMoreItems: MutableState, animatedScrollingInProgress: MutableState, mergedItems: State, @@ -1690,7 +1688,8 @@ fun BoxScope.FloatingButtons( composeViewHeight: State, searchValue: State, markChatRead: () -> Unit, - listState: State + listState: State, + loadMessages: suspend (ChatId, ChatPagination, visibleItemIndexesNonReversed: () -> IntRange) -> Unit ) { val scope = rememberCoroutineScope() val bottomUnreadCount = remember { @@ -1734,7 +1733,9 @@ fun BoxScope.FloatingButtons( // Don't show top FAB if is in search if (searchValue.value.isNotEmpty()) return val fabSize = 56.dp - val topUnreadCount = remember { derivedStateOf { if (bottomUnreadCount.value >= 0) (unreadCount.value - bottomUnreadCount.value).coerceAtLeast(0) else 0 } } + val topUnreadCount = remember { derivedStateOf { + if (bottomUnreadCount.value >= 0) (unreadCount.value - bottomUnreadCount.value).coerceAtLeast(0) else 0 } + } val showDropDown = remember { mutableStateOf(false) } TopEndFloatingButton( @@ -1742,14 +1743,31 @@ fun BoxScope.FloatingButtons( topUnreadCount, animatedScrollingInProgress, onClick = { - val index = mergedItems.value.items.indexOfLast { it.hasUnread() } - if (index != -1) { - // scroll to the top unread item scope.launch { - animatedScrollingInProgress.value = true - tryBlockAndSetLoadingMore(loadingMoreItems) { listState.value.animateScrollToItem(index + 1, -maxHeight.value) } + tryBlockAndSetLoadingMore(loadingMoreItems) { + if (chatModel.chatStateForContent(contentTag).splits.value.isNotEmpty()) { + val pagination = ChatPagination.Initial(ChatPagination.INITIAL_COUNT) + val oldSize = reversedChatItems.value.size + loadMessages(chatInfo.value.id, pagination) { + visibleItemIndexesNonReversed(mergedItems, reversedChatItems.value.size, listState.value) + } + var repeatsLeft = 100 + while (oldSize == reversedChatItems.value.size && repeatsLeft > 0) { + delay(10) + repeatsLeft-- + } + if (oldSize == reversedChatItems.value.size) { + return@tryBlockAndSetLoadingMore + } + } + val index = mergedItems.value.items.indexOfLast { it.hasUnread() } + if (index != -1) { + // scroll to the top unread item + animatedScrollingInProgress.value = true + listState.value.animateScrollToItem(index + 1, -maxHeight.value) + } + } } - } }, onLongClick = { showDropDown.value = true } ) @@ -1777,6 +1795,8 @@ fun BoxScope.FloatingButtons( fun PreloadItems( chatId: String, ignoreLoadingRequests: MutableSet, + loadingMoreItems: State, + resetListState: State, contentTag: MsgContentTag?, mergedItems: State, listState: State, @@ -1788,13 +1808,32 @@ fun PreloadItems( val chatId = rememberUpdatedState(chatId) val loadItems = rememberUpdatedState(loadItems) val ignoreLoadingRequests = rememberUpdatedState(ignoreLoadingRequests) - PreloadItemsBefore(allowLoad, chatId, ignoreLoadingRequests, contentTag, mergedItems, listState, remaining, loadItems) - PreloadItemsAfter(allowLoad, chatId, contentTag, mergedItems, listState, remaining, loadItems) + LaunchedEffect(Unit) { + snapshotFlow { chatId.value } + .distinctUntilChanged() + .filterNotNull() + .collect { + allowLoad.value = false + delay(500) + allowLoad.value = true + } + } + if (allowLoad.value && !loadingMoreItems.value) { + LaunchedEffect(chatId.value, resetListState.value) { + snapshotFlow { listState.value.firstVisibleItemIndex } + .distinctUntilChanged() + .collect { firstVisibleIndex -> + if (!preloadItemsBefore(firstVisibleIndex, chatId, ignoreLoadingRequests, contentTag, mergedItems, listState, remaining, loadItems)) { + preloadItemsAfter(firstVisibleIndex, chatId, contentTag, mergedItems, remaining, loadItems) + } + loadLastItems(chatId, contentTag, listState, loadItems) + } + } + } } -@Composable -private fun PreloadItemsBefore( - allowLoad: State, +private suspend fun preloadItemsBefore( + firstVisibleIndex: Int, chatId: State, ignoreLoadingRequests: State>, contentTag: MsgContentTag?, @@ -1802,83 +1841,47 @@ private fun PreloadItemsBefore( listState: State, remaining: Int, loadItems: State Boolean>, -) { - KeyChangeEffect(allowLoad.value, chatId.value) { - snapshotFlow { listState.value.firstVisibleItemIndex } - .distinctUntilChanged() - .map { firstVisibleIndex -> - val splits = mergedItems.value.splits - val lastVisibleIndex = (listState.value.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0) - var lastIndexToLoadFrom: Int? = findLastIndexToLoadFromInSplits(firstVisibleIndex, lastVisibleIndex, remaining, splits) - val items = reversedChatItemsStatic(contentTag) - if (splits.isEmpty() && items.isNotEmpty() && lastVisibleIndex > mergedItems.value.items.size - remaining) { - lastIndexToLoadFrom = items.lastIndex - } - if (allowLoad.value && lastIndexToLoadFrom != null) { - items.getOrNull(lastIndexToLoadFrom)?.id - } else { - null - } - } - .filterNotNull() - .filter { !ignoreLoadingRequests.value.contains(it) } - .collect { loadFromItemId -> - withBGApi { - val items = reversedChatItemsStatic(contentTag) - val sizeWas = items.size - val oldestItemIdWas = items.lastOrNull()?.id - val triedToLoad = loadItems.value(chatId.value, ChatPagination.Before(loadFromItemId, ChatPagination.PRELOAD_COUNT)) - val itemsUpdated = reversedChatItemsStatic(contentTag) - if (triedToLoad && sizeWas == itemsUpdated.size && oldestItemIdWas == itemsUpdated.lastOrNull()?.id) { - ignoreLoadingRequests.value.add(loadFromItemId) - } - } - } +): Boolean { + val splits = mergedItems.value.splits + val lastVisibleIndex = (listState.value.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0) + var lastIndexToLoadFrom: Int? = findLastIndexToLoadFromInSplits(firstVisibleIndex, lastVisibleIndex, remaining, splits) + val items = reversedChatItemsStatic(contentTag) + if (splits.isEmpty() && items.isNotEmpty() && lastVisibleIndex > mergedItems.value.items.size - remaining) { + lastIndexToLoadFrom = items.lastIndex } + if (lastIndexToLoadFrom != null) { + val loadFromItemId = items.getOrNull(lastIndexToLoadFrom)?.id ?: return false + if (!ignoreLoadingRequests.value.contains(loadFromItemId)) { + val items = reversedChatItemsStatic(contentTag) + val sizeWas = items.size + val oldestItemIdWas = items.lastOrNull()?.id + val triedToLoad = loadItems.value(chatId.value, ChatPagination.Before(loadFromItemId, ChatPagination.PRELOAD_COUNT)) + val itemsUpdated = reversedChatItemsStatic(contentTag) + if (triedToLoad && sizeWas == itemsUpdated.size && oldestItemIdWas == itemsUpdated.lastOrNull()?.id) { + ignoreLoadingRequests.value.add(loadFromItemId) + return false + } + return triedToLoad + } + } + return false } -@Composable -private fun PreloadItemsAfter( - allowLoad: MutableState, +private suspend fun preloadItemsAfter( + firstVisibleIndex: Int, chatId: State, contentTag: MsgContentTag?, mergedItems: State, - listState: State, remaining: Int, loadItems: State Boolean>, ) { - LaunchedEffect(Unit) { - snapshotFlow { chatId.value } - .distinctUntilChanged() - .filterNotNull() - .collect { - allowLoad.value = listState.value.layoutInfo.totalItemsCount == listState.value.layoutInfo.visibleItemsInfo.size - delay(500) - allowLoad.value = true - } - } - LaunchedEffect(chatId.value) { - launch { - snapshotFlow { listState.value.firstVisibleItemIndex } - .distinctUntilChanged() - .map { firstVisibleIndex -> - val items = reversedChatItemsStatic(contentTag) - val splits = mergedItems.value.splits - val split = splits.lastOrNull { it.indexRangeInParentItems.contains(firstVisibleIndex) } - // we're inside a splitRange (top --- [end of the splitRange --- we're here --- start of the splitRange] --- bottom) - if (split != null && split.indexRangeInParentItems.first + remaining > firstVisibleIndex) { - items.getOrNull(split.indexRangeInReversed.first)?.id - } else { - null - } - } - .filterNotNull() - .collect { loadFromItemId -> - withBGApi { - loadItems.value(chatId.value, ChatPagination.After(loadFromItemId, ChatPagination.PRELOAD_COUNT)) - } - } - } + val items = reversedChatItemsStatic(contentTag) + val splits = mergedItems.value.splits + val split = splits.lastOrNull { it.indexRangeInParentItems.contains(firstVisibleIndex) } + // we're inside a splitRange (top --- [end of the splitRange --- we're here --- start of the splitRange] --- bottom) + if (split != null && split.indexRangeInParentItems.first + remaining > firstVisibleIndex) { + val loadFromItemId = items.getOrNull(split.indexRangeInReversed.first)?.id ?: return + loadItems.value(chatId.value, ChatPagination.After(loadFromItemId, ChatPagination.PRELOAD_COUNT)) } } @@ -2115,7 +2118,7 @@ private fun DateSeparator(date: Instant) { @Composable private fun MarkItemsReadAfterDelay( - itemKey: String, + itemKey: ChatViewItemKey, itemIds: List, finishedInitialComposition: State, chatId: ChatId, @@ -2153,18 +2156,20 @@ private fun reversedChatItemsStatic(contentTag: MsgContentTag?): List private fun oldestPartiallyVisibleListItemInListStateOrNull(topPaddingToContentPx: State, mergedItems: State, listState: State): ListItem? { val lastFullyVisibleOffset = listState.value.layoutInfo.viewportEndOffset - topPaddingToContentPx.value - return mergedItems.value.items.getOrNull((listState.value.layoutInfo.visibleItemsInfo.lastOrNull { item -> + val visibleKey: ChatViewItemKey? = listState.value.layoutInfo.visibleItemsInfo.lastOrNull { item -> item.offset <= lastFullyVisibleOffset - }?.index ?: listState.value.layoutInfo.visibleItemsInfo.lastOrNull()?.index) ?: -1)?.oldest() + }?.key as? ChatViewItemKey + return mergedItems.value.items.getOrNull((mergedItems.value.indexInParentItems[visibleKey?.first] ?: listState.value.layoutInfo.visibleItemsInfo.lastOrNull()?.index) ?: -1)?.oldest() } private fun lastFullyVisibleIemInListState(topPaddingToContentPx: State, density: Float, fontSizeSqrtMultiplier: Float, mergedItems: State, listState: State): ChatItem? { val lastFullyVisibleOffsetMinusFloatingHeight = listState.value.layoutInfo.viewportEndOffset - topPaddingToContentPx.value - 50 * density * fontSizeSqrtMultiplier + val visibleKey: ChatViewItemKey? = listState.value.layoutInfo.visibleItemsInfo.lastOrNull { item -> + item.offset <= lastFullyVisibleOffsetMinusFloatingHeight && item.size > 0 + }?.key as? ChatViewItemKey + return mergedItems.value.items.getOrNull( - (listState.value.layoutInfo.visibleItemsInfo.lastOrNull { item -> - item.offset <= lastFullyVisibleOffsetMinusFloatingHeight && item.size > 0 - } - ?.index + (mergedItems.value.indexInParentItems[visibleKey?.first] ?: listState.value.layoutInfo.visibleItemsInfo.lastOrNull()?.index) ?: -1)?.newest()?.item } @@ -2652,7 +2657,9 @@ fun providerForGallery( } } -private fun keyForItem(item: ChatItem): String = (item.id to item.meta.createdAt.toEpochMilliseconds()).toString() +typealias ChatViewItemKey = Pair + +private fun keyForItem(item: ChatItem): ChatViewItemKey = ChatViewItemKey(item.id, item.meta.createdAt.toEpochMilliseconds()) private fun ViewConfiguration.bigTouchSlop(slop: Float = 50f) = object: ViewConfiguration { override val longPressTimeoutMillis diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt index 5e33117e62..8b4b2bb1d2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt @@ -245,7 +245,7 @@ suspend fun apiFindMessages(ch: Chat, search: String, contentTag: MsgContentTag? withChats(contentTag) { chatItems.clearAndNotify() } - apiLoadMessages(ch.remoteHostId, ch.chatInfo.chatType, ch.chatInfo.apiId, contentTag, pagination = ChatPagination.Last(ChatPagination.INITIAL_COUNT), search = search) + apiLoadMessages(ch.remoteHostId, ch.chatInfo.chatType, ch.chatInfo.apiId, contentTag, pagination = if (search.isNotEmpty()) ChatPagination.Last(ChatPagination.INITIAL_COUNT) else ChatPagination.Initial(ChatPagination.INITIAL_COUNT), search = search) } suspend fun setGroupMembers(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel) = coroutineScope { From cd20dc0a04336e5ea6e44c890fa9139ee625031a Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 19 Mar 2025 22:11:53 +0700 Subject: [PATCH 485/567] android, desktop: enhancements to floating buttons (#5763) * android, desktop: enhancements to floating buttons * size * size --- .../simplex/common/views/chat/ChatView.kt | 242 +++++++++++++----- 1 file changed, 178 insertions(+), 64 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 46965d07fc..94a5fd3549 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -1185,6 +1185,22 @@ fun BoxScope.ChatItemsList( developerTools: Boolean, showViaProxy: Boolean ) { + val loadingTopItems = remember { mutableStateOf(false) } + val loadingBottomItems = remember { mutableStateOf(false) } + // just for changing local var here based on request + val loadMessages: suspend (ChatId, ChatPagination, visibleItemIndexesNonReversed: () -> IntRange) -> Unit = { chatId, pagination, visibleItemIndexesNonReversed -> + val loadingSide = when (pagination) { + is ChatPagination.Before -> loadingTopItems + is ChatPagination.Last -> loadingBottomItems + is ChatPagination.After, is ChatPagination.Around, is ChatPagination.Initial -> null + } + loadingSide?.value = true + try { + loadMessages(chatId, pagination, visibleItemIndexesNonReversed) + } finally { + loadingSide?.value = false + } + } val searchValueIsEmpty = remember { derivedStateOf { searchValue.value.isEmpty() } } val searchValueIsNotBlank = remember { derivedStateOf { searchValue.value.isNotBlank() } } val revealedItems = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(setOf()) } @@ -1582,7 +1598,25 @@ fun BoxScope.ChatItemsList( } } } - FloatingButtons(reversedChatItems, chatInfoUpdated, topPaddingToContent, topPaddingToContentPx, contentTag, loadingMoreItems, animatedScrollingInProgress, mergedItems, unreadCount, maxHeight, composeViewHeight, searchValue, markChatRead, listState, loadMessages) + FloatingButtons( + reversedChatItems, + chatInfoUpdated, + topPaddingToContent, + topPaddingToContentPx, + contentTag, + loadingMoreItems, + loadingTopItems, + loadingBottomItems, + animatedScrollingInProgress, + mergedItems, + unreadCount, + maxHeight, + composeViewHeight, + searchValue, + markChatRead, + listState, + loadMessages + ) FloatingDate(Modifier.padding(top = 10.dp + topPaddingToContent).align(Alignment.TopCenter), topPaddingToContentPx, mergedItems, listState) LaunchedEffect(Unit) { @@ -1607,14 +1641,17 @@ private suspend fun loadLastItems(chatId: State, contentTag: MsgContentT val itemsCanCoverScreen = lastVisible != null && listState.value.layoutInfo.viewportEndOffset - listState.value.layoutInfo.afterContentPadding <= lastVisible.offset + lastVisible.size if (!itemsCanCoverScreen) return - val chatState = chatModel.chatStateForContent(contentTag) - val lastItemsLoaded = chatState.splits.value.isEmpty() || chatState.splits.value.firstOrNull() != chatModel.chatItemsForContent(contentTag).value.lastOrNull()?.id - if (lastItemsLoaded) return + if (lastItemsLoaded(contentTag)) return delay(500) loadItems.value(chatId.value, ChatPagination.Last(ChatPagination.INITIAL_COUNT)) } +private fun lastItemsLoaded(contentTag: MsgContentTag?): Boolean { + val chatState = chatModel.chatStateForContent(contentTag) + return chatState.splits.value.isEmpty() || chatState.splits.value.firstOrNull() != chatModel.chatItemsForContent(contentTag).value.lastOrNull()?.id +} + // TODO: in extra rare case when after loading last items only 1 item is loaded, the view will jump like when receiving new message // can be reproduced by forwarding a message to notes that is (ChatPagination.INITIAL_COUNT - 1) away from bottom and going to that message @Composable @@ -1681,6 +1718,8 @@ fun BoxScope.FloatingButtons( topPaddingToContentPx: State, contentTag: MsgContentTag?, loadingMoreItems: MutableState, + loadingTopItems: MutableState, + loadingBottomItems: MutableState, animatedScrollingInProgress: MutableState, mergedItems: State, unreadCount: State, @@ -1692,6 +1731,40 @@ fun BoxScope.FloatingButtons( loadMessages: suspend (ChatId, ChatPagination, visibleItemIndexesNonReversed: () -> IntRange) -> Unit ) { val scope = rememberCoroutineScope() + fun scrollToBottom() { + scope.launch { + animatedScrollingInProgress.value = true + tryBlockAndSetLoadingMore(loadingMoreItems) { listState.value.animateScrollToItem(0) } + } + } + fun scrollToTopUnread() { + scope.launch { + tryBlockAndSetLoadingMore(loadingMoreItems) { + if (chatModel.chatStateForContent(contentTag).splits.value.isNotEmpty()) { + val pagination = ChatPagination.Initial(ChatPagination.INITIAL_COUNT) + val oldSize = reversedChatItems.value.size + loadMessages(chatInfo.value.id, pagination) { + visibleItemIndexesNonReversed(mergedItems, reversedChatItems.value.size, listState.value) + } + var repeatsLeft = 100 + while (oldSize == reversedChatItems.value.size && repeatsLeft > 0) { + delay(10) + repeatsLeft-- + } + if (oldSize == reversedChatItems.value.size) { + return@tryBlockAndSetLoadingMore + } + } + val index = mergedItems.value.items.indexOfLast { it.hasUnread() } + if (index != -1) { + // scroll to the top unread item + animatedScrollingInProgress.value = true + listState.value.animateScrollToItem(index + 1, -maxHeight.value) + } + } + } + } + val bottomUnreadCount = remember { derivedStateOf { if (unreadCount.value == 0) return@derivedStateOf 0 @@ -1717,19 +1790,48 @@ fun BoxScope.FloatingButtons( allowToShowBottomWithArrow.value = shouldShow shouldShow && allow } } + + val requestedTopScroll = remember { mutableStateOf(false) } + val requestedBottomScroll = remember { mutableStateOf(false) } + BottomEndFloatingButton( bottomUnreadCount, showBottomButtonWithCounter, showBottomButtonWithArrow, + requestedBottomScroll, animatedScrollingInProgress, composeViewHeight, onClick = { - scope.launch { - animatedScrollingInProgress.value = true - tryBlockAndSetLoadingMore(loadingMoreItems) { listState.value.animateScrollToItem(0) } + if (loadingBottomItems.value || !lastItemsLoaded(contentTag)) { + requestedTopScroll.value = false + requestedBottomScroll.value = true + } else { + scrollToBottom() } } ) + LaunchedEffect(Unit) { + launch { + snapshotFlow { loadingTopItems.value } + .drop(1) + .collect { top -> + if (!top && requestedTopScroll.value) { + requestedTopScroll.value = false + scrollToTopUnread() + } + } + } + launch { + snapshotFlow { loadingBottomItems.value } + .drop(1) + .collect { bottom -> + if (!bottom && requestedBottomScroll.value) { + requestedBottomScroll.value = false + scrollToBottom() + } + } + } + } // Don't show top FAB if is in search if (searchValue.value.isNotEmpty()) return val fabSize = 56.dp @@ -1741,33 +1843,15 @@ fun BoxScope.FloatingButtons( TopEndFloatingButton( Modifier.padding(end = DEFAULT_PADDING, top = 24.dp + topPaddingToContent).align(Alignment.TopEnd), topUnreadCount, + requestedTopScroll, animatedScrollingInProgress, onClick = { - scope.launch { - tryBlockAndSetLoadingMore(loadingMoreItems) { - if (chatModel.chatStateForContent(contentTag).splits.value.isNotEmpty()) { - val pagination = ChatPagination.Initial(ChatPagination.INITIAL_COUNT) - val oldSize = reversedChatItems.value.size - loadMessages(chatInfo.value.id, pagination) { - visibleItemIndexesNonReversed(mergedItems, reversedChatItems.value.size, listState.value) - } - var repeatsLeft = 100 - while (oldSize == reversedChatItems.value.size && repeatsLeft > 0) { - delay(10) - repeatsLeft-- - } - if (oldSize == reversedChatItems.value.size) { - return@tryBlockAndSetLoadingMore - } - } - val index = mergedItems.value.items.indexOfLast { it.hasUnread() } - if (index != -1) { - // scroll to the top unread item - animatedScrollingInProgress.value = true - listState.value.animateScrollToItem(index + 1, -maxHeight.value) - } - } - } + if (loadingTopItems.value) { + requestedBottomScroll.value = false + requestedTopScroll.value = true + } else { + scrollToTopUnread() + } }, onLongClick = { showDropDown.value = true } ) @@ -1896,6 +1980,7 @@ fun MemberImage(member: GroupMember) { private fun TopEndFloatingButton( modifier: Modifier = Modifier, unreadCount: State, + requestedTopScroll: State, animatedScrollingInProgress: State, onClick: () -> Unit, onLongClick: () -> Unit @@ -1909,11 +1994,15 @@ private fun TopEndFloatingButton( elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp), interactionSource = interactionSource, ) { - Text( - unreadCountStr(unreadCount.value), - color = MaterialTheme.colors.primary, - fontSize = 14.sp, - ) + if (requestedTopScroll.value) { + LoadingProgressIndicator() + } else { + Text( + unreadCountStr(unreadCount.value), + color = MaterialTheme.colors.primary, + fontSize = 14.sp, + ) + } } } } @@ -2281,39 +2370,50 @@ private fun BoxScope.BottomEndFloatingButton( unreadCount: State, showButtonWithCounter: State, showButtonWithArrow: State, + requestedBottomScroll: State, animatedScrollingInProgress: State, composeViewHeight: State, onClick: () -> Unit -) = when { - showButtonWithCounter.value && !animatedScrollingInProgress.value -> { - FloatingActionButton( - onClick = onClick, - elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp), - modifier = Modifier.padding(end = DEFAULT_PADDING, bottom = DEFAULT_PADDING + composeViewHeight.value).align(Alignment.BottomEnd).size(48.dp), - backgroundColor = MaterialTheme.colors.secondaryVariant, - ) { - Text( - unreadCountStr(unreadCount.value), - color = MaterialTheme.colors.primary, - fontSize = 14.sp, - ) +) { + when { + showButtonWithCounter.value && !animatedScrollingInProgress.value -> { + FloatingActionButton( + onClick = onClick, + elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp), + modifier = Modifier.padding(end = DEFAULT_PADDING, bottom = DEFAULT_PADDING + composeViewHeight.value).align(Alignment.BottomEnd).size(48.dp), + backgroundColor = MaterialTheme.colors.secondaryVariant, + ) { + if (requestedBottomScroll.value) { + LoadingProgressIndicator() + } else { + Text( + unreadCountStr(unreadCount.value), + color = MaterialTheme.colors.primary, + fontSize = 14.sp, + ) + } + } } - } - showButtonWithArrow.value && !animatedScrollingInProgress.value -> { - FloatingActionButton( - onClick = onClick, - elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp), - modifier = Modifier.padding(end = DEFAULT_PADDING, bottom = DEFAULT_PADDING + composeViewHeight.value).align(Alignment.BottomEnd).size(48.dp), - backgroundColor = MaterialTheme.colors.secondaryVariant, - ) { - Icon( - painter = painterResource(MR.images.ic_keyboard_arrow_down), - contentDescription = null, - tint = MaterialTheme.colors.primary - ) + showButtonWithArrow.value && !animatedScrollingInProgress.value -> { + FloatingActionButton( + onClick = onClick, + elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp, 0.dp, 0.dp), + modifier = Modifier.padding(end = DEFAULT_PADDING, bottom = DEFAULT_PADDING + composeViewHeight.value).align(Alignment.BottomEnd).size(48.dp), + backgroundColor = MaterialTheme.colors.secondaryVariant, + ) { + if (requestedBottomScroll.value) { + LoadingProgressIndicator() + } else { + Icon( + painter = painterResource(MR.images.ic_keyboard_arrow_down), + contentDescription = null, + tint = MaterialTheme.colors.primary + ) + } + } } + else -> {} } - else -> {} } @Composable @@ -2339,6 +2439,20 @@ fun SelectedListItem( ) } +@Composable +private fun LoadingProgressIndicator() { + Box( + Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator( + Modifier.size(30.dp), + color = MaterialTheme.colors.secondary, + strokeWidth = 2.dp + ) + } +} + private fun selectUnselectChatItem( select: Boolean, ci: ChatItem, From 6020c6010d23f87f8abf162fc61859b4bbebeeb8 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 21 Mar 2025 06:06:52 +0700 Subject: [PATCH 486/567] ios: moving content up when setting emoji on the first message (#5766) --- apps/ios/Shared/Views/Chat/EndlessScrollView.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/ios/Shared/Views/Chat/EndlessScrollView.swift b/apps/ios/Shared/Views/Chat/EndlessScrollView.swift index 670707c0c1..cc61754b26 100644 --- a/apps/ios/Shared/Views/Chat/EndlessScrollView.swift +++ b/apps/ios/Shared/Views/Chat/EndlessScrollView.swift @@ -345,7 +345,11 @@ class EndlessScrollView: UIScrollView, UIScrollViewDelegate, UIGestu if let visibleIndex { let v = oldVisible.remove(at: visibleIndex) if forceReloadVisible || v.view.bounds.width != bounds.width || v.item.hashValue != item.hashValue { + let wasHeight = v.view.bounds.height updateCell(v.view, i, items) + if wasHeight < v.view.bounds.height && i == 0 && shouldBeFirstVisible == i { + v.view.frame.origin.y -= v.view.bounds.height - wasHeight + } } visible = v } else { From 15742aee3072091f52b5d65102617c656f46402f Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 21 Mar 2025 11:49:59 +0000 Subject: [PATCH 487/567] ios: XCode 16 workaround to prevent stack overflow (#5771) * ios: Workaround for stackoverflow with Xcode 16 - Increased stack size to 4MiB - Fix: https://github.com/simplex-chat/simplex-chat/issues/4837 * Remove Main Thread Stack Size Linker Setting Removed the linker setting for the main thread stack size as the main thread is no longer used. * Set Thread Stack Size to 2MiB Set the thread stack size to 2MiB. In my environment, 992KiB worked fine, so increasing the size to more than double should provide sufficient margin. * ios: moving content up when setting emoji on the first message (#5766) * simplify --------- Co-authored-by: ISHIHARA Kazuto Co-authored-by: Avently <7953703+avently@users.noreply.github.com> --- apps/ios/SimpleXChat/API.swift | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/apps/ios/SimpleXChat/API.swift b/apps/ios/SimpleXChat/API.swift index 5e5f047611..e439cd337b 100644 --- a/apps/ios/SimpleXChat/API.swift +++ b/apps/ios/SimpleXChat/API.swift @@ -183,7 +183,9 @@ public func chatResponse(_ s: String) -> ChatResponse { // let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson)) // let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free) do { - let r = try jsonDecoder.decode(APIResponse.self, from: d) + let r = try callWithLargeStack { + try jsonDecoder.decode(APIResponse.self, from: d) + } return r.resp } catch { logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)") @@ -231,6 +233,32 @@ public func chatResponse(_ s: String) -> ChatResponse { return ChatResponse.response(type: type ?? "invalid", json: json ?? s) } +private let largeStackSize: Int = 2 * 1024 * 1024 + +private func callWithLargeStack(_ f: @escaping () throws -> T) throws -> T { + let semaphore = DispatchSemaphore(value: 0) + var result: Result? + let thread = Thread { + do { + result = .success(try f()) + } catch { + result = .failure(error) + } + semaphore.signal() + } + + thread.stackSize = largeStackSize + thread.qualityOfService = Thread.current.qualityOfService + thread.start() + + semaphore.wait() + + switch result! { + case let .success(r): return r + case let .failure(e): throw e + } +} + private func decodeUser_(_ jDict: NSDictionary) -> UserRef? { if let user_ = jDict["user_"] { try? decodeObject(user_ as Any) From 9f853e2e84fa48787a12550a4f8221bee9cdc5d5 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 22 Mar 2025 14:20:29 +0000 Subject: [PATCH 488/567] core: 6.3.1.0 (simplexmq 6.3.1.0) --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cabal.project b/cabal.project index a40bcc43bc..0554d14ec8 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 5c0adcbbff873bf9e58d8939f8e8178692aec384 + tag: aace3fd2fb146097304b09ef07ff613a8b255f67 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 317ba843a8..41b45e9ddc 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."5c0adcbbff873bf9e58d8939f8e8178692aec384" = "0qxyv4fn5kh5dwx03n8d09x72w15vg19s1ikq2q5cgpq404z799x"; + "https://github.com/simplex-chat/simplexmq.git"."aace3fd2fb146097304b09ef07ff613a8b255f67" = "0iqdarkvlakk4xmrqsg62z0vhs3kwm02l8vpr383vf8q2hd7ky75"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index c9dc9f6afe..f2b3582722 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.3.0.8 +version: 6.3.1.0 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From 99dcaa34bac4be3dad9910aecd6bc98e90db3aa5 Mon Sep 17 00:00:00 2001 From: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com> Date: Tue, 25 Mar 2025 12:32:12 +0000 Subject: [PATCH 489/567] readme: update SimpleX users group link (#5772) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1830228370..7e6e5d975d 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ You must: Messages not following these rules will be deleted, the right to send messages may be revoked, and the access to the new members to the group may be temporarily restricted, to prevent re-joining under a different name - our imperfect group moderation does not have a better solution at the moment. -You can join an English-speaking users group if you want to ask any questions: [#SimpleX users group](https://simplex.chat/contact#/?v=2-4&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2Fos8FftfoV8zjb2T89fUEjJtF7y64p5av%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAQqMgh0fw2lPhjn3PDIEfAKA_E0-gf8Hr8zzhYnDivRs%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22lBPiveK2mjfUH43SN77R0w%3D%3D%22%7D) +You can join an English-speaking users group if you want to ask any questions: [#SimpleX users group](https://simplex.chat/contact#/?v=2-7&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FiBkJE72asZX1NUZaYFIeKRVk6oVjb-iv%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAinqu3j74AMjODLoIRR487ZW6ysip_dlpD6Zxk18SPFY%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22groupLinkId%22%3A%223wAFGCLygQHR5AwynZOHlQ%3D%3D%22%7D) There is also a group [#simplex-devs](https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FvYCRjIflKNMGYlfTkuHe4B40qSlQ0439%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAHNdcqNbzXZhyMoSBjT2R0-Eb1EPaLyUg3KZjn-kmM1w%253D%26srv%3Dbylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22PD20tcXjw7IpkkMCfR6HLA%3D%3D%22%7D) for developers who build on SimpleX platform: From f8fddb1daf949cfbe84613ae6d984286ff7a4228 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 25 Mar 2025 12:52:49 +0000 Subject: [PATCH 490/567] docs: update server doc about reproducing builds (#5779) --- docs/SERVER.md | 52 +++++++++++++++----------------------------------- 1 file changed, 15 insertions(+), 37 deletions(-) diff --git a/docs/SERVER.md b/docs/SERVER.md index 3bfd064c4e..4ddfb68e63 100644 --- a/docs/SERVER.md +++ b/docs/SERVER.md @@ -15,7 +15,7 @@ revision: 12.10.2024 - [systemd service](#systemd-service) with [installation script](#installation-script) or [manually](#manual-deployment) - [docker container](#docker-container) - [Linode marketplace](#linode-marketplace) -- [Verifying server binaries] +- [Verifying server binaries](#verifying-server-binaries) - [Configuration](#configuration) - [Interactively](#interactively) - [Via command line options](#via-command-line-options) @@ -1592,7 +1592,9 @@ To update your smp-server to latest version, choose your installation method and You can locally reproduce server binaries, following these instructions. -You must have: +If you are a security expert or researcher, you can help SimpleX network and users community by signing the release checksums – we will [publish your signature](https://github.com/simplex-chat/simplexmq/releases/tag/v6.3.1). Please reach out to us! + +To reproduce the build you must have: - Linux machine - `x86-64` architecture @@ -1613,52 +1615,28 @@ You must have: 3. Execute the script with the required tag: ```sh - ./reproduce-builds.sh 'v6.3.0' + ./reproduce-builds.sh 'v6.3.1' ``` + The script executes these steps (please review the script to confirm): + + 1) builds all server binaries for the release in docker container. + 2) downloads binaries from the same GitHub release and compares them with the built binaries. + 3) if they all match, generates _sha256sums file with their checksums. + This will take a while. -4. After compilation, you should see the following folders: +4. After compilation, you should see the folder named as the tag (e.g., `v6.3.1`) with two subfolders: ```sh - ls out* + ls v6.3.1 ``` ```sh - out-20.04: - ntf-server smp-server xftp xftp-server - - out-20.04-github: - ntf-server smp-server xftp xftp-server - - out-22.04: - ntf-server smp-server xftp xftp-server - - out-22.04-github: - ntf-server smp-server xftp xftp-server - - out-24.04: - ntf-server smp-server xftp xftp-server - - out-24.04-github: - ntf-server smp-server xftp xftp-server + from-source prebuilt _sha256sums ``` -5. Compare the hashes from github release with locally build binaries: - - ```sh - sha256sum out*-github/* - ``` - - ```sh - sha256sum out*[0-9]/* - ``` - - You can safely delete cloned repository: - - ```sh - cd ../ && rm -rf simplexmq - ``` + The file _sha256sums contains the hashes of all builds - you can compare it with the same file in GitHub release. ## Configuring the app to use the server From 444378647468126bcadd1fdbff0d53fcf4ad8a35 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 28 Mar 2025 15:37:39 +0000 Subject: [PATCH 491/567] ui: move operators selection to sheet on onboarding (#5783) * ios: show updated conditions always on what's new screen * rework onboarding * update text * android whatsnew * android wip * layout * improve what's new layout * remove * fix desktop --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/ContentView.swift | 13 +- .../Onboarding/ChooseServerOperators.swift | 438 ++++++++---------- .../Views/Onboarding/CreateProfile.swift | 2 +- .../Views/Onboarding/OnboardingView.swift | 4 +- .../Shared/Views/Onboarding/SimpleXInfo.swift | 1 + .../Views/Onboarding/WhatsNewView.swift | 13 +- apps/ios/SimpleX.xcodeproj/project.pbxproj | 16 +- .../kotlin/chat/simplex/common/App.kt | 2 +- .../common/views/chatlist/ChatListView.kt | 20 +- .../views/onboarding/ChooseServerOperators.kt | 230 ++++----- .../common/views/onboarding/WhatsNewView.kt | 70 +-- .../commonMain/resources/MR/base/strings.xml | 7 +- 12 files changed, 348 insertions(+), 468 deletions(-) diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift index 65631954e5..305ad0a601 100644 --- a/apps/ios/Shared/ContentView.swift +++ b/apps/ios/Shared/ContentView.swift @@ -11,12 +11,10 @@ import SimpleXChat private enum NoticesSheet: Identifiable { case whatsNew(updatedConditions: Bool) - case updatedConditions var id: String { switch self { case .whatsNew: return "whatsNew" - case .updatedConditions: return "updatedConditions" } } } @@ -278,10 +276,8 @@ struct ContentView: View { let showWhatsNew = shouldShowWhatsNew() let showUpdatedConditions = chatModel.conditions.conditionsAction?.showNotice ?? false noticesShown = showWhatsNew || showUpdatedConditions - if showWhatsNew { + if showWhatsNew || showUpdatedConditions { noticesSheetItem = .whatsNew(updatedConditions: showUpdatedConditions) - } else if showUpdatedConditions { - noticesSheetItem = .updatedConditions } } } @@ -300,13 +296,6 @@ struct ContentView: View { .if(updatedConditions) { v in v.task { await setConditionsNotified_() } } - case .updatedConditions: - UsageConditionsView( - currUserServers: Binding.constant([]), - userServers: Binding.constant([]) - ) - .modifier(ThemedBackground(grouped: true)) - .task { await setConditionsNotified_() } } } if chatModel.setDeliveryReceipts { diff --git a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift index 8523336d2b..45ef186671 100644 --- a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift +++ b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift @@ -43,26 +43,23 @@ struct OnboardingButtonStyle: ButtonStyle { } } -private enum ChooseServerOperatorsSheet: Identifiable { - case showInfo +private enum OnboardingConditionsViewSheet: Identifiable { case showConditions + case configureOperators var id: String { switch self { - case .showInfo: return "showInfo" case .showConditions: return "showConditions" + case .configureOperators: return "configureOperators" } } } -struct ChooseServerOperators: View { - @Environment(\.dismiss) var dismiss: DismissAction - @Environment(\.colorScheme) var colorScheme: ColorScheme +struct OnboardingConditionsView: View { @EnvironmentObject var theme: AppTheme - var onboarding: Bool @State private var serverOperators: [ServerOperator] = [] @State private var selectedOperatorIds = Set() - @State private var sheetItem: ChooseServerOperatorsSheet? = nil + @State private var sheetItem: OnboardingConditionsViewSheet? = nil @State private var notificationsModeNavLinkActive = false @State private var justOpened = true @@ -72,16 +69,192 @@ struct ChooseServerOperators: View { GeometryReader { g in ScrollView { VStack(alignment: .leading, spacing: 20) { - let title = Text("Server operators") + Text("Conditions of use") .font(.largeTitle) .bold() .frame(maxWidth: .infinity, alignment: .center) - - if onboarding { - title.padding(.top, 25) - } else { - title + .padding(.top, 25) + + Spacer() + + VStack(alignment: .leading, spacing: 20) { + Text("Private chats, groups and your contacts are not accessible to server operators.") + .lineSpacing(2) + .frame(maxWidth: .infinity, alignment: .leading) + Text(""" + By using SimpleX Chat you agree to: + - send only legal content in public groups. + - respect other users – no spam. + """) + .lineSpacing(2) + .frame(maxWidth: .infinity, alignment: .leading) + + Button("Privacy policy and conditions of use.") { + sheetItem = .showConditions + } + .frame(maxWidth: .infinity, alignment: .leading) } + .padding(.horizontal, 4) + + Spacer() + + VStack(spacing: 12) { + acceptConditionsButton() + + Button("Configure server operators") { + sheetItem = .configureOperators + } + .frame(minHeight: 40) + } + } + .frame(minHeight: g.size.height) + } + .onAppear { + if justOpened { + serverOperators = ChatModel.shared.conditions.serverOperators + selectedOperatorIds = Set(serverOperators.filter { $0.enabled }.map { $0.operatorId }) + justOpened = false + } + } + .sheet(item: $sheetItem) { item in + switch item { + case .showConditions: + SimpleConditionsView() + .modifier(ThemedBackground(grouped: true)) + case .configureOperators: + ChooseServerOperators(serverOperators: serverOperators, selectedOperatorIds: $selectedOperatorIds) + .modifier(ThemedBackground()) + } + } + .frame(maxHeight: .infinity, alignment: .top) + } + .frame(maxHeight: .infinity, alignment: .top) + .padding(25) + } + + private func continueToNextStep() { + onboardingStageDefault.set(.step4_SetNotificationsMode) + notificationsModeNavLinkActive = true + } + + func notificationsModeNavLinkButton(_ button: @escaping (() -> some View)) -> some View { + ZStack { + button() + + NavigationLink(isActive: $notificationsModeNavLinkActive) { + notificationsModeDestinationView() + } label: { + EmptyView() + } + .frame(width: 1, height: 1) + .hidden() + } + } + + private func notificationsModeDestinationView() -> some View { + SetNotificationsMode() + .navigationBarBackButtonHidden(true) + .modifier(ThemedBackground()) + } + + private func acceptConditionsButton() -> some View { + notificationsModeNavLinkButton { + Button { + Task { + do { + let conditionsId = ChatModel.shared.conditions.currentConditions.conditionsId + let acceptForOperators = selectedOperators.filter { !$0.conditionsAcceptance.conditionsAccepted } + let operatorIds = acceptForOperators.map { $0.operatorId } + let r = try await acceptConditions(conditionsId: conditionsId, operatorIds: operatorIds) + await MainActor.run { + ChatModel.shared.conditions = r + } + if let enabledOperators = enabledOperators(r.serverOperators) { + let r2 = try await setServerOperators(operators: enabledOperators) + await MainActor.run { + ChatModel.shared.conditions = r2 + continueToNextStep() + } + } else { + await MainActor.run { + continueToNextStep() + } + } + } catch let error { + await MainActor.run { + showAlert( + NSLocalizedString("Error accepting conditions", comment: "alert title"), + message: responseError(error) + ) + } + } + } + } label: { + Text("Accept") + } + .buttonStyle(OnboardingButtonStyle(isDisabled: selectedOperatorIds.isEmpty)) + .disabled(selectedOperatorIds.isEmpty) + } + } + + private func enabledOperators(_ operators: [ServerOperator]) -> [ServerOperator]? { + var ops = operators + if !ops.isEmpty { + for i in 0.. + @State private var sheetItem: ChooseServerOperatorsSheet? = nil + + var body: some View { + GeometryReader { g in + ScrollView { + VStack(alignment: .leading, spacing: 20) { + Text("Server operators") + .font(.largeTitle) + .bold() + .frame(maxWidth: .infinity, alignment: .center) + .padding(.top, 25) infoText() .frame(maxWidth: .infinity, alignment: .center) @@ -101,74 +274,25 @@ struct ChooseServerOperators: View { .padding(.horizontal, 16) Spacer() - - let reviewForOperators = selectedOperators.filter { !$0.conditionsAcceptance.conditionsAccepted } - let canReviewLater = reviewForOperators.allSatisfy { $0.conditionsAcceptance.usageAllowed } - let currEnabledOperatorIds = Set(serverOperators.filter { $0.enabled }.map { $0.operatorId }) - + VStack(spacing: 8) { - if !reviewForOperators.isEmpty { - reviewConditionsButton() - } else if selectedOperatorIds != currEnabledOperatorIds && !selectedOperatorIds.isEmpty { - setOperatorsButton() - } else { - continueButton() - } - if onboarding { - Group { - if reviewForOperators.isEmpty { - Button("Conditions of use") { - sheetItem = .showConditions - } - } else { - Text("Conditions of use") - .foregroundColor(.clear) - } - } - .font(.system(size: 17, weight: .semibold)) - .frame(minHeight: 40) - } - } - - if !onboarding && !reviewForOperators.isEmpty { - VStack(spacing: 8) { - reviewLaterButton() - ( - Text("Conditions will be accepted for enabled operators after 30 days.") - + textSpace - + Text("You can configure operators in Network & servers settings.") - ) - .multilineTextAlignment(.center) - .font(.footnote) - .padding(.horizontal, 32) - } - .frame(maxWidth: .infinity) - .disabled(!canReviewLater) - .padding(.bottom) + setOperatorsButton() + onboardingButtonPlaceholder() } } .frame(minHeight: g.size.height) } - .onAppear { - if justOpened { - serverOperators = ChatModel.shared.conditions.serverOperators - selectedOperatorIds = Set(serverOperators.filter { $0.enabled }.map { $0.operatorId }) - justOpened = false - } - } .sheet(item: $sheetItem) { item in switch item { case .showInfo: ChooseServerOperatorsInfoView() - case .showConditions: - SimpleConditionsView() - .modifier(ThemedBackground(grouped: true)) } } .frame(maxHeight: .infinity, alignment: .top) } .frame(maxHeight: .infinity, alignment: .top) - .padding(onboarding ? 25 : 16) + .padding(25) + .interactiveDismissDisabled(selectedOperatorIds.isEmpty) } private func infoText() -> some View { @@ -213,181 +337,15 @@ struct ChooseServerOperators: View { } } - private func reviewConditionsButton() -> some View { - NavigationLink("Review conditions") { - reviewConditionsView() - .navigationTitle("Conditions of use") - .navigationBarTitleDisplayMode(.large) - .toolbar { ToolbarItem(placement: .navigationBarTrailing, content: conditionsLinkButton) } - .modifier(ThemedBackground(grouped: true)) + private func setOperatorsButton() -> some View { + Button { + dismiss() + } label: { + Text("OK") } .buttonStyle(OnboardingButtonStyle(isDisabled: selectedOperatorIds.isEmpty)) .disabled(selectedOperatorIds.isEmpty) } - - private func setOperatorsButton() -> some View { - notificationsModeNavLinkButton { - Button { - Task { - if let enabledOperators = enabledOperators(serverOperators) { - let r = try await setServerOperators(operators: enabledOperators) - await MainActor.run { - ChatModel.shared.conditions = r - continueToNextStep() - } - } else { - await MainActor.run { - continueToNextStep() - } - } - } - } label: { - Text("Update") - } - .buttonStyle(OnboardingButtonStyle(isDisabled: selectedOperatorIds.isEmpty)) - .disabled(selectedOperatorIds.isEmpty) - } - } - - private func continueButton() -> some View { - notificationsModeNavLinkButton { - Button { - continueToNextStep() - } label: { - Text("Continue") - } - .buttonStyle(OnboardingButtonStyle(isDisabled: selectedOperatorIds.isEmpty)) - .disabled(selectedOperatorIds.isEmpty) - } - } - - private func reviewLaterButton() -> some View { - notificationsModeNavLinkButton { - Button { - continueToNextStep() - } label: { - Text("Review later") - } - .buttonStyle(.borderless) - } - } - - private func continueToNextStep() { - if onboarding { - onboardingStageDefault.set(.step4_SetNotificationsMode) - notificationsModeNavLinkActive = true - } else { - dismiss() - } - } - - func notificationsModeNavLinkButton(_ button: @escaping (() -> some View)) -> some View { - ZStack { - button() - - NavigationLink(isActive: $notificationsModeNavLinkActive) { - notificationsModeDestinationView() - } label: { - EmptyView() - } - .frame(width: 1, height: 1) - .hidden() - } - } - - private func notificationsModeDestinationView() -> some View { - SetNotificationsMode() - .navigationBarBackButtonHidden(true) - .modifier(ThemedBackground()) - } - - @ViewBuilder private func reviewConditionsView() -> some View { - let operatorsWithConditionsAccepted = ChatModel.shared.conditions.serverOperators.filter { $0.conditionsAcceptance.conditionsAccepted } - let acceptForOperators = selectedOperators.filter { !$0.conditionsAcceptance.conditionsAccepted } - VStack(alignment: .leading, spacing: 20) { - if !operatorsWithConditionsAccepted.isEmpty { - Text("Conditions are already accepted for these operator(s): **\(operatorsWithConditionsAccepted.map { $0.legalName_ }.joined(separator: ", "))**.") - Text("The same conditions will apply to operator(s): **\(acceptForOperators.map { $0.legalName_ }.joined(separator: ", "))**.") - } else { - Text("Conditions will be accepted for operator(s): **\(acceptForOperators.map { $0.legalName_ }.joined(separator: ", "))**.") - } - ConditionsTextView() - .frame(maxHeight: .infinity) - acceptConditionsButton() - .padding(.bottom) - .padding(.bottom) - } - .padding(.horizontal, 25) - } - - private func acceptConditionsButton() -> some View { - notificationsModeNavLinkButton { - Button { - Task { - do { - let conditionsId = ChatModel.shared.conditions.currentConditions.conditionsId - let acceptForOperators = selectedOperators.filter { !$0.conditionsAcceptance.conditionsAccepted } - let operatorIds = acceptForOperators.map { $0.operatorId } - let r = try await acceptConditions(conditionsId: conditionsId, operatorIds: operatorIds) - await MainActor.run { - ChatModel.shared.conditions = r - } - if let enabledOperators = enabledOperators(r.serverOperators) { - let r2 = try await setServerOperators(operators: enabledOperators) - await MainActor.run { - ChatModel.shared.conditions = r2 - continueToNextStep() - } - } else { - await MainActor.run { - continueToNextStep() - } - } - } catch let error { - await MainActor.run { - showAlert( - NSLocalizedString("Error accepting conditions", comment: "alert title"), - message: responseError(error) - ) - } - } - } - } label: { - Text("Accept conditions") - } - .buttonStyle(OnboardingButtonStyle()) - } - } - - private func enabledOperators(_ operators: [ServerOperator]) -> [ServerOperator]? { - var ops = operators - if !ops.isEmpty { - for i in 0.. some View { - ChooseServerOperators(onboarding: true) + OnboardingConditionsView() .navigationBarBackButtonHidden(true) .modifier(ThemedBackground()) } diff --git a/apps/ios/Shared/Views/Onboarding/OnboardingView.swift b/apps/ios/Shared/Views/Onboarding/OnboardingView.swift index b2b1b8fa68..8f448dc508 100644 --- a/apps/ios/Shared/Views/Onboarding/OnboardingView.swift +++ b/apps/ios/Shared/Views/Onboarding/OnboardingView.swift @@ -23,7 +23,7 @@ struct OnboardingView: View { case .step3_CreateSimpleXAddress: // deprecated CreateSimpleXAddress() case .step3_ChooseServerOperators: - ChooseServerOperators(onboarding: true) + OnboardingConditionsView() .navigationBarBackButtonHidden(true) .modifier(ThemedBackground()) case .step4_SetNotificationsMode: @@ -44,7 +44,7 @@ enum OnboardingStage: String, Identifiable { case step1_SimpleXInfo case step2_CreateProfile // deprecated case step3_CreateSimpleXAddress // deprecated - case step3_ChooseServerOperators + case step3_ChooseServerOperators // changed to simplified conditions case step4_SetNotificationsMode case onboardingComplete diff --git a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift index dbae3e9fb3..e55cc4037a 100644 --- a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift +++ b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift @@ -129,6 +129,7 @@ struct SimpleXInfo: View { NavigationLink(isActive: $createProfileNavLinkActive) { CreateFirstProfile() + .modifier(ThemedBackground()) } label: { EmptyView() } diff --git a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift index f7c7145dcc..f65a21623a 100644 --- a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift +++ b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift @@ -594,8 +594,6 @@ func shouldShowWhatsNew() -> Bool { } fileprivate struct NewOperatorsView: View { - @State private var showOperatorsSheet = false - var body: some View { VStack(alignment: .leading) { Image((operatorsInfo[.flux] ?? ServerOperator.dummyOperatorInfo).largeLogo) @@ -606,16 +604,7 @@ fileprivate struct NewOperatorsView: View { .multilineTextAlignment(.leading) .lineLimit(10) HStack { - Button("Enable Flux") { - showOperatorsSheet = true - } - Text("for better metadata privacy.") - } - } - .sheet(isPresented: $showOperatorsSheet) { - NavigationView { - ChooseServerOperators(onboarding: false) - .modifier(ThemedBackground()) + Text("Enable Flux in Network & servers settings for better metadata privacy.") } } } diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 6727643022..b7db01ec2b 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -174,8 +174,8 @@ 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; }; 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; }; - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-CNSVk2Y5c4gAUnxeodOxfm-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-CNSVk2Y5c4gAUnxeodOxfm-ghc9.6.3.a */; }; - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-CNSVk2Y5c4gAUnxeodOxfm.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-CNSVk2Y5c4gAUnxeodOxfm.a */; }; + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.0-IjBs5IcA9K0E6bK6RlYtK-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.0-IjBs5IcA9K0E6bK6RlYtK-ghc9.6.3.a */; }; + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.0-IjBs5IcA9K0E6bK6RlYtK.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.0-IjBs5IcA9K0E6bK6RlYtK.a */; }; 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; @@ -531,8 +531,8 @@ 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-CNSVk2Y5c4gAUnxeodOxfm-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.0.8-CNSVk2Y5c4gAUnxeodOxfm-ghc9.6.3.a"; sourceTree = ""; }; - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-CNSVk2Y5c4gAUnxeodOxfm.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.0.8-CNSVk2Y5c4gAUnxeodOxfm.a"; sourceTree = ""; }; + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.0-IjBs5IcA9K0E6bK6RlYtK-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.1.0-IjBs5IcA9K0E6bK6RlYtK-ghc9.6.3.a"; sourceTree = ""; }; + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.0-IjBs5IcA9K0E6bK6RlYtK.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.1.0-IjBs5IcA9K0E6bK6RlYtK.a"; sourceTree = ""; }; 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; @@ -688,8 +688,8 @@ 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */, 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */, 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */, - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-CNSVk2Y5c4gAUnxeodOxfm-ghc9.6.3.a in Frameworks */, - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-CNSVk2Y5c4gAUnxeodOxfm.a in Frameworks */, + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.0-IjBs5IcA9K0E6bK6RlYtK-ghc9.6.3.a in Frameworks */, + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.0-IjBs5IcA9K0E6bK6RlYtK.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -774,8 +774,8 @@ 64C829992D54AEEE006B9E89 /* libffi.a */, 64C829982D54AEED006B9E89 /* libgmp.a */, 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */, - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-CNSVk2Y5c4gAUnxeodOxfm-ghc9.6.3.a */, - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.0.8-CNSVk2Y5c4gAUnxeodOxfm.a */, + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.0-IjBs5IcA9K0E6bK6RlYtK-ghc9.6.3.a */, + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.0-IjBs5IcA9K0E6bK6RlYtK.a */, ); path = Libraries; sourceTree = ""; diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index 2456463910..600804a763 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -194,7 +194,7 @@ fun MainScreen() { OnboardingStage.Step2_5_SetupDatabasePassphrase -> SetupDatabasePassphrase(chatModel) OnboardingStage.Step3_ChooseServerOperators -> { val modalData = remember { ModalData() } - modalData.ChooseServerOperators(true) + modalData.OnboardingConditionsView() if (appPlatform.isDesktop) { ModalManager.fullscreen.showInView() } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index a6774c6870..3538d41f01 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -127,31 +127,13 @@ fun ToggleChatListCard() { @Composable fun ChatListView(chatModel: ChatModel, userPickerState: MutableStateFlow, setPerformLA: (Boolean) -> Unit, stopped: Boolean) { val oneHandUI = remember { appPrefs.oneHandUI.state } - val rhId = chatModel.remoteHostId() LaunchedEffect(Unit) { val showWhatsNew = shouldShowWhatsNew(chatModel) val showUpdatedConditions = chatModel.conditions.value.conditionsAction?.shouldShowNotice ?: false - if (showWhatsNew) { + if (showWhatsNew || showUpdatedConditions) { delay(1000L) ModalManager.center.showCustomModal { close -> WhatsNewView(close = close, updatedConditions = showUpdatedConditions) } - } else if (showUpdatedConditions) { - ModalManager.center.showModalCloseable(endButtons = { ConditionsLinkButton() }) { close -> - LaunchedEffect(Unit) { - val conditionsId = chatModel.conditions.value.currentConditions.conditionsId - try { - setConditionsNotified(rh = rhId, conditionsId = conditionsId) - } catch (e: Exception) { - Log.d(TAG, "UsageConditionsView setConditionsNotified error: ${e.message}") - } - } - UsageConditionsView( - userServers = mutableStateOf(emptyList()), - currUserServers = mutableStateOf(emptyList()), - close = close, - rhId = rhId - ) - } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt index 80cc977602..a14f163a91 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt @@ -7,15 +7,18 @@ import SectionTextFooter import SectionView import TextIconSpaced import androidx.compose.foundation.* +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.* import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* @@ -27,11 +30,7 @@ import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource @Composable -fun ModalData.ChooseServerOperators( - onboarding: Boolean, - close: (() -> Unit) = { ModalManager.fullscreen.closeModals() }, - modalManager: ModalManager = ModalManager.fullscreen -) { +fun ModalData.OnboardingConditionsView() { LaunchedEffect(Unit) { prepareChatBeforeFinishingOnboarding() } @@ -41,6 +40,73 @@ fun ModalData.ChooseServerOperators( val selectedOperatorIds = remember { stateGetOrPut("selectedOperatorIds") { serverOperators.value.filter { it.enabled }.map { it.operatorId }.toSet() } } val selectedOperators = remember { derivedStateOf { serverOperators.value.filter { selectedOperatorIds.value.contains(it.operatorId) } } } + ColumnWithScrollBar( + Modifier + .themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer), + maxIntrinsicSize = true + ) { + Box(Modifier.align(Alignment.CenterHorizontally)) { + AppBarTitle(stringResource(MR.strings.operator_conditions_of_use), bottomPadding = DEFAULT_PADDING) + } + + Spacer(Modifier.weight(1f)) + Column( + (if (appPlatform.isDesktop) Modifier.width(450.dp).align(Alignment.CenterHorizontally) else Modifier) + .fillMaxWidth() + .padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING), + horizontalAlignment = Alignment.Start + ) { + Text( + stringResource(MR.strings.onboarding_conditions_private_chats_not_accessible), + style = TextStyle(fontSize = 17.sp, lineHeight = 23.sp) + ) + Spacer(Modifier.height(DEFAULT_PADDING)) + Text( + stringResource(MR.strings.onboarding_conditions_by_using_you_agree), + style = TextStyle(fontSize = 17.sp, lineHeight = 23.sp) + ) + Spacer(Modifier.height(DEFAULT_PADDING)) + Text( + stringResource(MR.strings.onboarding_conditions_privacy_policy_and_conditions_of_use), + style = TextStyle(fontSize = 17.sp), + color = MaterialTheme.colors.primary, + modifier = Modifier + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) { + ModalManager.fullscreen.showModal(endButtons = { ConditionsLinkButton() }) { + SimpleConditionsView(rhId = null) + } + } + ) + } + Spacer(Modifier.weight(1f)) + + Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + AcceptConditionsButton(enabled = selectedOperatorIds.value.isNotEmpty(), selectedOperators, selectedOperatorIds) + TextButtonBelowOnboardingButton(stringResource(MR.strings.onboarding_conditions_configure_server_operators)) { + ModalManager.fullscreen.showModalCloseable { close -> + ChooseServerOperators(serverOperators, selectedOperatorIds, close) + } + } + } + } + } + } +} + +@Composable +fun ModalData.ChooseServerOperators( + serverOperators: State>, + selectedOperatorIds: MutableState>, + close: (() -> Unit) +) { + LaunchedEffect(Unit) { + prepareChatBeforeFinishingOnboarding() + } + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { + ModalView({}, showClose = false) { ColumnWithScrollBar( Modifier .themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer), @@ -53,7 +119,7 @@ fun ModalData.ChooseServerOperators( Column(Modifier.fillMaxWidth().padding(horizontal = DEFAULT_PADDING), horizontalAlignment = Alignment.CenterHorizontally) { OnboardingInformationButton( stringResource(MR.strings.how_it_helps_privacy), - onClick = { modalManager.showModal { ChooseServerOperatorsInfoView(modalManager) } } + onClick = { ModalManager.fullscreen.showModal { ChooseServerOperatorsInfoView() } } ) } @@ -77,37 +143,11 @@ fun ModalData.ChooseServerOperators( } Spacer(Modifier.weight(1f)) - val reviewForOperators = selectedOperators.value.filter { !it.conditionsAcceptance.conditionsAccepted } - val canReviewLater = reviewForOperators.all { it.conditionsAcceptance.usageAllowed } - val currEnabledOperatorIds = serverOperators.value.filter { it.enabled }.map { it.operatorId }.toSet() - Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { val enabled = selectedOperatorIds.value.isNotEmpty() - when { - reviewForOperators.isNotEmpty() -> ReviewConditionsButton(enabled, onboarding, selectedOperators, selectedOperatorIds, modalManager) - selectedOperatorIds.value != currEnabledOperatorIds && enabled -> SetOperatorsButton(true, onboarding, serverOperators, selectedOperatorIds, close) - else -> ContinueButton(enabled, onboarding, close) - } - if (onboarding && reviewForOperators.isEmpty()) { - TextButtonBelowOnboardingButton(stringResource(MR.strings.operator_conditions_of_use)) { - modalManager.showModalCloseable(endButtons = { ConditionsLinkButton() }) { close -> - SimpleConditionsView(rhId = null) - } - } - } else if (onboarding || reviewForOperators.isEmpty()) { - // Reserve space - TextButtonBelowOnboardingButton("", null) - } - if (!onboarding && reviewForOperators.isNotEmpty()) { - ReviewLaterButton(canReviewLater, close) - SectionTextFooter( - annotatedStringResource(MR.strings.onboarding_network_operators_conditions_will_be_accepted) + - AnnotatedString(" ") + - annotatedStringResource(MR.strings.onboarding_network_operators_conditions_you_can_configure), - textAlign = TextAlign.Center - ) - SectionBottomSpacer() - } + SetOperatorsButton(enabled, close) + // Reserve space + TextButtonBelowOnboardingButton("", null) } } } @@ -162,115 +202,36 @@ private fun CircleCheckbox(checked: Boolean) { } @Composable -private fun ReviewConditionsButton( - enabled: Boolean, - onboarding: Boolean, - selectedOperators: State>, - selectedOperatorIds: State>, - modalManager: ModalManager -) { +private fun SetOperatorsButton(enabled: Boolean, close: () -> Unit) { OnboardingActionButton( modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth() else Modifier.widthIn(min = 300.dp), - labelId = MR.strings.operator_review_conditions, + labelId = MR.strings.ok, onboarding = null, enabled = enabled, onclick = { - modalManager.showModalCloseable(endButtons = { ConditionsLinkButton() }) { close -> - ReviewConditionsView(onboarding, selectedOperators, selectedOperatorIds, close) - } + close() } ) } -@Composable -private fun SetOperatorsButton(enabled: Boolean, onboarding: Boolean, serverOperators: State>, selectedOperatorIds: State>, close: () -> Unit) { - OnboardingActionButton( - modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth() else Modifier.widthIn(min = 300.dp), - labelId = MR.strings.onboarding_network_operators_update, - onboarding = null, - enabled = enabled, - onclick = { - withBGApi { - val enabledOperators = enabledOperators(serverOperators.value, selectedOperatorIds.value) - if (enabledOperators != null) { - val r = chatController.setServerOperators(rh = chatModel.remoteHostId(), operators = enabledOperators) - if (r != null) { - chatModel.conditions.value = r - } - continueToNextStep(onboarding, close) - } - } - } - ) -} - -@Composable -private fun ContinueButton(enabled: Boolean, onboarding: Boolean, close: () -> Unit) { - OnboardingActionButton( - modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth() else Modifier.widthIn(min = 300.dp), - labelId = MR.strings.onboarding_network_operators_continue, - onboarding = null, - enabled = enabled, - onclick = { - continueToNextStep(onboarding, close) - } - ) -} - -@Composable -private fun ReviewLaterButton(enabled: Boolean, close: () -> Unit) { - TextButtonBelowOnboardingButton( - stringResource(MR.strings.onboarding_network_operators_review_later), - onClick = if (!enabled) null else {{ continueToNextStep(false, close) }} - ) -} - -@Composable -private fun ReviewConditionsView( - onboarding: Boolean, - selectedOperators: State>, - selectedOperatorIds: State>, - close: () -> Unit -) { - // remembering both since we don't want to reload the view after the user accepts conditions - val operatorsWithConditionsAccepted = remember { chatModel.conditions.value.serverOperators.filter { it.conditionsAcceptance.conditionsAccepted } } - val acceptForOperators = remember { selectedOperators.value.filter { !it.conditionsAcceptance.conditionsAccepted } } - ColumnWithScrollBar(modifier = Modifier.fillMaxSize().padding(horizontal = if (onboarding) DEFAULT_ONBOARDING_HORIZONTAL_PADDING else DEFAULT_PADDING)) { - AppBarTitle(stringResource(MR.strings.operator_conditions_of_use), withPadding = false, enableAlphaChanges = false, bottomPadding = DEFAULT_PADDING) - if (operatorsWithConditionsAccepted.isNotEmpty()) { - ReadableText(MR.strings.operator_conditions_accepted_for_some, args = operatorsWithConditionsAccepted.joinToString(", ") { it.legalName_ }) - ReadableText(MR.strings.operator_same_conditions_will_apply_to_operators, args = acceptForOperators.joinToString(", ") { it.legalName_ }) - } else { - ReadableText(MR.strings.operator_conditions_will_be_accepted_for_some, args = acceptForOperators.joinToString(", ") { it.legalName_ }) - } - Column(modifier = Modifier.weight(1f).padding(top = DEFAULT_PADDING_HALF)) { - ConditionsTextView(chatModel.remoteHostId()) - } - Column(Modifier.padding(vertical = DEFAULT_PADDING).widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { - AcceptConditionsButton(onboarding, selectedOperators, selectedOperatorIds, close) - } - } -} - @Composable private fun AcceptConditionsButton( - onboarding: Boolean, + enabled: Boolean, selectedOperators: State>, - selectedOperatorIds: State>, - close: () -> Unit + selectedOperatorIds: State> ) { fun continueOnAccept() { - if (appPlatform.isDesktop || !onboarding) { - if (onboarding) { close() } - continueToNextStep(onboarding, close) + if (appPlatform.isDesktop) { + continueToNextStep() } else { continueToSetNotificationsAfterAccept() } } OnboardingActionButton( - modifier = if (appPlatform.isAndroid) Modifier.fillMaxWidth() else Modifier, - labelId = MR.strings.accept_conditions, + modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth() else Modifier.widthIn(min = 300.dp), + labelId = MR.strings.onboarding_conditions_accept, onboarding = null, + enabled = enabled, onclick = { withBGApi { val conditionsId = chatModel.conditions.value.currentConditions.conditionsId @@ -295,12 +256,8 @@ private fun AcceptConditionsButton( ) } -private fun continueToNextStep(onboarding: Boolean, close: () -> Unit) { - if (onboarding) { +private fun continueToNextStep() { appPrefs.onboardingStage.set(if (appPlatform.isAndroid) OnboardingStage.Step4_SetNotificationsMode else OnboardingStage.OnboardingComplete) - } else { - close() - } } private fun continueToSetNotificationsAfterAccept() { @@ -339,9 +296,7 @@ private fun enabledOperators(operators: List, selectedOperatorId } @Composable -private fun ChooseServerOperatorsInfoView( - modalManager: ModalManager -) { +private fun ChooseServerOperatorsInfoView() { ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.onboarding_network_operators)) @@ -357,21 +312,20 @@ private fun ChooseServerOperatorsInfoView( SectionView(title = stringResource(MR.strings.onboarding_network_about_operators).uppercase()) { chatModel.conditions.value.serverOperators.forEach { op -> - ServerOperatorRow(op, modalManager) + ServerOperatorRow(op) } } SectionBottomSpacer() } } -@Composable() +@Composable private fun ServerOperatorRow( - operator: ServerOperator, - modalManager: ModalManager + operator: ServerOperator ) { SectionItemView( { - modalManager.showModalCloseable { close -> + ModalManager.fullscreen.showModalCloseable { close -> OperatorInfoView(operator) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt index de9f909150..52eea3dd9d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt @@ -14,7 +14,7 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.ChatController.appPrefs @@ -161,10 +161,14 @@ fun ModalData.WhatsNewView(updatedConditions: Boolean = false, viaSettings: Bool } if (updatedConditions) { - Row( + Text( + stringResource(MR.strings.view_updated_conditions), + color = MaterialTheme.colors.primary, modifier = Modifier - .clip(shape = CircleShape) - .clickable { + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) { modalManager.showModalCloseable { close -> UsageConditionsView( userServers = mutableStateOf(emptyList()), @@ -174,15 +178,7 @@ fun ModalData.WhatsNewView(updatedConditions: Boolean = false, viaSettings: Bool ) } } - .padding(horizontal = 6.dp, vertical = 4.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center - ) { - Text( - stringResource(MR.strings.view_updated_conditions), - color = MaterialTheme.colors.primary - ) - } + ) } if (!viaSettings) { @@ -190,14 +186,21 @@ fun ModalData.WhatsNewView(updatedConditions: Boolean = false, viaSettings: Bool Box( Modifier.fillMaxWidth(), contentAlignment = Alignment.Center ) { - Text( - generalGetString(MR.strings.ok), - modifier = Modifier.clickable(onClick = { - close() - }), - style = MaterialTheme.typography.h3, - color = MaterialTheme.colors.primary - ) + Box(Modifier.clip(RoundedCornerShape(20.dp))) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = Modifier + .clickable { close() } + .padding(8.dp) + ) { + Text( + generalGetString(MR.strings.ok), + style = MaterialTheme.typography.h3, + color = MaterialTheme.colors.primary + ) + } + } } Spacer(Modifier.fillMaxHeight().weight(1f)) } @@ -213,8 +216,17 @@ fun ModalData.WhatsNewView(updatedConditions: Boolean = false, viaSettings: Bool fun ReadMoreButton(url: String) { val uriHandler = LocalUriHandler.current Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(top = DEFAULT_PADDING.div(4))) { - Text(stringResource(MR.strings.whats_new_read_more), color = MaterialTheme.colors.primary, - modifier = Modifier.clickable { uriHandler.openUriCatching(url) }) + Text( + stringResource(MR.strings.whats_new_read_more), + color = MaterialTheme.colors.primary, + modifier = Modifier + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) { + uriHandler.openUriCatching(url) + } + ) Icon(painterResource(MR.images.ic_open_in_new), stringResource(MR.strings.whats_new_read_more), tint = MaterialTheme.colors.primary) } } @@ -751,17 +763,7 @@ private val versionDescriptions: List = listOf( val src = (operatorsInfo[OperatorTag.Flux] ?: dummyOperatorInfo).largeLogo Image(painterResource(src), null, modifier = Modifier.height(48.dp)) Text(stringResource(MR.strings.v6_2_network_decentralization_descr), modifier = Modifier.padding(top = 8.dp)) - Row { - Text( - stringResource(MR.strings.v6_2_network_decentralization_enable_flux), - color = MaterialTheme.colors.primary, - modifier = Modifier.clickable { - modalManager.showModalCloseable { close -> ChooseServerOperators(onboarding = false, close, modalManager) } - } - ) - Text(" ") - Text(stringResource(MR.strings.v6_2_network_decentralization_enable_flux_reason)) - } + Text(stringResource(MR.strings.v6_2_network_decentralization_enable_flux)) } } ), diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 5e8d22cb99..d905ab71ea 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1162,6 +1162,11 @@ Use random passphrase + Private chats, groups and your contacts are not accessible to server operators. + By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam. + Privacy policy and conditions of use. + Accept + Configure server operators Server operators Network operators SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. @@ -2291,7 +2296,7 @@ Delete or moderate up to 200 messages. Network decentralization The second preset operator in the app! - Enable flux + Enable Flux in Network & servers settings for better metadata privacy. for better metadata privacy. Improved chat navigation - Open chat on the first unread message.\n- Jump to quoted messages. From 27f2926aed8bfe3869290b33fbf807c5fdd5bae6 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 28 Mar 2025 18:48:54 +0000 Subject: [PATCH 492/567] directory: joining groups with enabled captcha screening and observer role (#5784) * directory: joining groups with enabled captcha screen (test) * fix directory, test * query plans --- .../src/Directory/Service.hs | 22 ++--- src/Simplex/Chat/Store/Profiles.hs | 26 +++--- .../SQLite/Migrations/chat_query_plans.txt | 16 ++-- tests/Bots/DirectoryTests.hs | 89 ++++++++++++++++++- 4 files changed, 122 insertions(+), 31 deletions(-) diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs index 575c7ca738..054f261b4e 100644 --- a/apps/simplex-directory-service/src/Directory/Service.hs +++ b/apps/simplex-directory-service/src/Directory/Service.hs @@ -469,7 +469,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName approvePendingMember :: DirectoryMemberAcceptance -> GroupInfo -> GroupMember -> IO () approvePendingMember a g@GroupInfo {groupId} m@GroupMember {memberProfile = LocalProfile {displayName, image}} = do - gli_ <- join <$> withDB' cc (\db -> getGroupLinkInfo db userId groupId) + gli_ <- join <$> withDB' "getGroupLinkInfo" cc (\db -> getGroupLinkInfo db userId groupId) let role = if useMemberFilter image (makeObserver a) then GRObserver else maybe GRMember (\GroupLinkInfo {memberRole} -> memberRole) gli_ gmId = groupMemberId' m sendChatCmd cc (APIAcceptMember groupId gmId role) >>= \case @@ -698,7 +698,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName case acceptance_ of Just a' | a /= a' -> do let d = toCustomData $ DirectoryGroupData a' - withDB' cc (\db -> setGroupCustomData db user g $ Just d) >>= \case + withDB' "setGroupCustomData" cc (\db -> setGroupCustomData db user g $ Just d) >>= \case Just () -> sendSettigns n a' " set to" Nothing -> sendReply $ "Error changing spam filter settings for group " <> n _ -> sendSettigns n a "" @@ -977,24 +977,24 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName sendComposedMessage cc ct Nothing $ MCText text getContact' :: ChatController -> User -> ContactId -> IO (Maybe Contact) -getContact' cc user ctId = withDB cc $ \db -> getContact db (vr cc) user ctId +getContact' cc user ctId = withDB "getContact" cc $ \db -> getContact db (vr cc) user ctId getGroup :: ChatController -> User -> GroupId -> IO (Maybe GroupInfo) -getGroup cc user gId = withDB cc $ \db -> getGroupInfo db (vr cc) user gId +getGroup cc user gId = withDB "getGroupInfo" cc $ \db -> getGroupInfo db (vr cc) user gId -withDB' :: ChatController -> (DB.Connection -> IO a) -> IO (Maybe a) -withDB' cc a = withDB cc $ ExceptT . fmap Right . a +withDB' :: Text -> ChatController -> (DB.Connection -> IO a) -> IO (Maybe a) +withDB' cxt cc a = withDB cxt cc $ ExceptT . fmap Right . a -withDB :: ChatController -> (DB.Connection -> ExceptT StoreError IO a) -> IO (Maybe a) -withDB ChatController {chatStore} action = do +withDB :: Text -> ChatController -> (DB.Connection -> ExceptT StoreError IO a) -> IO (Maybe a) +withDB cxt ChatController {chatStore} action = do r_ :: Either ChatError a <- withTransaction chatStore (runExceptT . withExceptT ChatErrorStore . action) `E.catches` handleDBErrors case r_ of Right r -> pure $ Just r - Left e -> Nothing <$ logError ("Database error: " <> tshow e) + Left e -> Nothing <$ logError ("Database error: " <> cxt <> " " <> tshow e) getGroupAndSummary :: ChatController -> User -> GroupId -> IO (Maybe (GroupInfo, GroupSummary)) getGroupAndSummary cc user gId = - withDB cc $ \db -> (,) <$> getGroupInfo db (vr cc) user gId <*> liftIO (getGroupSummary db user gId) + withDB "getGroupAndSummary" cc $ \db -> (,) <$> getGroupInfo db (vr cc) user gId <*> liftIO (getGroupSummary db user gId) vr :: ChatController -> VersionRangeChat vr ChatController {config = ChatConfig {chatVRange}} = chatVRange @@ -1002,7 +1002,7 @@ vr ChatController {config = ChatConfig {chatVRange}} = chatVRange getGroupLinkRole :: ChatController -> User -> GroupInfo -> IO (Maybe (Int64, ConnReqContact, GroupMemberRole)) getGroupLinkRole cc user gInfo = - withDB cc $ \db -> getGroupLink db user gInfo + withDB "getGroupLink" cc $ \db -> getGroupLink db user gInfo setGroupLinkRole :: ChatController -> GroupInfo -> GroupMemberRole -> IO (Maybe ConnReqContact) setGroupLinkRole cc GroupInfo {groupId} mRole = resp <$> sendChatCmd cc (APIGroupLinkMemberRole groupId mRole) diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index 22d2a7b1f5..bdd54c3f1e 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -492,15 +492,14 @@ getUserAddress db User {userId} = getUserContactLinkById :: DB.Connection -> UserId -> Int64 -> ExceptT StoreError IO (UserContactLink, Maybe GroupLinkInfo) getUserContactLinkById db userId userContactLinkId = ExceptT . firstRow (\(ucl :. gli) -> (toUserContactLink ucl, toGroupLinkInfo gli)) SEUserContactLinkNotFound $ - DB.query db (groupLinkInfoQuery <> " AND user_contact_link_id = ?") (userId, userContactLinkId) - -groupLinkInfoQuery :: Query -groupLinkInfoQuery = - [sql| - SELECT conn_req_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content, group_id, group_link_member_role - FROM user_contact_links - WHERE user_id = ? - |] + DB.query + db + [sql| + SELECT conn_req_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content, group_id, group_link_member_role + FROM user_contact_links + WHERE user_id = ? AND user_contact_link_id = ? + |] + (userId, userContactLinkId) toGroupLinkInfo :: (Maybe GroupId, Maybe GroupMemberRole) -> Maybe GroupLinkInfo toGroupLinkInfo (groupId_, mRole_) = @@ -510,7 +509,14 @@ toGroupLinkInfo (groupId_, mRole_) = getGroupLinkInfo :: DB.Connection -> UserId -> GroupId -> IO (Maybe GroupLinkInfo) getGroupLinkInfo db userId groupId = fmap join $ maybeFirstRow toGroupLinkInfo $ - DB.query db (groupLinkInfoQuery <> " AND group_id = ?") (userId, groupId) + DB.query + db + [sql| + SELECT group_id, group_link_member_role + FROM user_contact_links + WHERE user_id = ? AND group_id = ? + |] + (userId, groupId) getUserContactLinkByConnReq :: DB.Connection -> User -> (ConnReqContact, ConnReqContact) -> IO (Maybe UserContactLink) getUserContactLinkByConnReq db User {userId} (cReqSchema1, cReqSchema2) = diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt index 9cbd5965b7..6f1d243cf3 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt @@ -2950,6 +2950,14 @@ Query: Plan: SEARCH user_contact_links USING INDEX sqlite_autoindex_user_contact_links_1 (user_id=? AND local_display_name=?) +Query: + SELECT conn_req_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content, group_id, group_link_member_role + FROM user_contact_links + WHERE user_id = ? AND user_contact_link_id = ? + +Plan: +SEARCH user_contact_links USING INTEGER PRIMARY KEY (rowid=?) + Query: SELECT connection_id, agent_conn_id, conn_level, via_contact, via_user_contact_link, via_group_link, group_link_id, custom_user_profile_id, conn_status, conn_type, contact_conn_initiated, local_alias, contact_id, group_member_id, snd_file_id, rcv_file_id, user_contact_link_id, @@ -4642,14 +4650,6 @@ SEARCH c USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN CORRELATED SCALAR SUBQUERY 1 SEARCH cc USING COVERING INDEX idx_connections_group_member (user_id=? AND group_member_id=?) -Query: - SELECT conn_req_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content, group_id, group_link_member_role - FROM user_contact_links - WHERE user_id = ? - AND user_contact_link_id = ? -Plan: -SEARCH user_contact_links USING INTEGER PRIMARY KEY (rowid=?) - Query: SELECT f.file_id, f.ci_file_status, f.file_path FROM chat_items i diff --git a/tests/Bots/DirectoryTests.hs b/tests/Bots/DirectoryTests.hs index e9db100e8d..6601032a79 100644 --- a/tests/Bots/DirectoryTests.hs +++ b/tests/Bots/DirectoryTests.hs @@ -20,7 +20,7 @@ import Directory.Service import Directory.Store import GHC.IO.Handle (hClose) import Simplex.Chat.Bot.KnownContacts -import Simplex.Chat.Controller (ChatConfig (..)) +import Simplex.Chat.Controller (ChatConfig (..), ChatHooks (..), defaultChatHooks) import Simplex.Chat.Core import Simplex.Chat.Options (CoreChatOpts (..)) import Simplex.Chat.Options.DB @@ -64,6 +64,8 @@ directoryServiceTests = do it "should prohibit approval if a duplicate group is listed" testDuplicateProhibitApproval describe "list groups" $ do it "should list user's groups" testListUserGroups + describe "member admission" $ do + it "should ask member to pass captcha screen" testCapthaScreening describe "store log" $ do it "should restore directory service state" testRestoreDirectory describe "captcha" $ do @@ -954,6 +956,88 @@ testListUserGroups ps = groupNotFound cath "anonymity" listGroups superUser bob cath +testCapthaScreening :: HasCallStack => TestParams -> IO () +testCapthaScreening ps = + withDirectoryService ps $ \superUser dsLink -> + withNewTestChat ps "bob" bobProfile $ \bob -> + withNewTestChat ps "cath" cathProfile $ \cath -> do + bob `connectVia` dsLink + registerGroup superUser bob "privacy" "Privacy" + -- check default role + bob #> "@SimpleX-Directory /role 1" + bob <# "SimpleX-Directory> > /role 1" + bob <## " The initial member role for the group privacy is set to member" + bob <## "Send /role 1 observer to change it." + bob <## "" + note <- getTermLine bob + let groupLink = dropStrPrefix "Please note: it applies only to members joining via this link: " note + -- enable captcha + bob #> "@SimpleX-Directory /filter 1 captcha" + bob <# "SimpleX-Directory> > /filter 1 captcha" + bob <## " Spam filter settings for group privacy set to:" + bob <## "- reject long/inappropriate names: disabled" + bob <## "- pass captcha to join: enabled" + bob <## "" + bob <## "Use /filter 1 [name] [captcha] to enable and /filter 1 off to disable filter." + -- connect with captcha screen + _ <- join cath groupLink + cath #> "#privacy 123" -- sending incorrect captcha + cath <# "#privacy SimpleX-Directory!> > cath 123" + cath <## " Incorrect text, please try again." + captcha <- dropStrPrefix "#privacy SimpleX-Directory> " . dropTime <$> getTermLine cath + sendCaptcha cath captcha + cath <#. "#privacy SimpleX-Directory> Link to join the group privacy: https://" + cath <## "#privacy: member bob (Bob) is connected" + bob <## "#privacy: SimpleX-Directory added cath (Catherine) to the group (connecting...)" + bob <## "#privacy: new member cath is connected" + cath #> "#privacy hello" + bob <# "#privacy cath> hello" + cath ##> "/l privacy" + cath <## "#privacy: you left the group" + cath <## "use /d #privacy to delete the group" + bob <## "#privacy: cath left the group" + cath ##> "/d #privacy" + cath <## "#privacy: you deleted the group" + -- change default role to observer + bob #> "@SimpleX-Directory /role 1 observer" + bob <# "SimpleX-Directory> > /role 1 observer" + bob <## " The initial member role for the group privacy is set to observer" + bob <## "" + bob <##. "Please note: it applies only to members joining via this link: https://" + -- connect with captcha screen again, as observer + captcha' <- join cath groupLink + sendCaptcha cath captcha' + -- message from cath that left + pastMember <- dropStrPrefix "#privacy: SimpleX-Directory forwarded a message from an unknown member, creating unknown member record " <$> getTermLine cath + cath <# ("#privacy " <> pastMember <> "> hello [>>]") + cath <#. "#privacy SimpleX-Directory> Link to join the group privacy: https://" + cath <## "#privacy: member bob (Bob) is connected" + bob <## "#privacy: SimpleX-Directory added cath_1 (Catherine) to the group (connecting...)" + bob <## "#privacy: new member cath_1 is connected" + cath ##> "#privacy hello" + cath <## "#privacy: you don't have permission to send messages" + (bob "/ms privacy" + cath <## "cath (Catherine): observer, you, connected" + cath <## "SimpleX-Directory: admin, host, connected" + cath <## "bob (Bob): owner, connected" + cath <## (pastMember <> ": author, status unknown") + where + join cath groupLink = do + cath ##> ("/c " <> groupLink) + cath <## "connection request sent!" + cath <## "#privacy: joining the group..." + cath <## "#privacy: you joined the group, pending approval" + cath <# "#privacy SimpleX-Directory> Captcha is generated by SimpleX Directory service." + cath <## "" + cath <## "Send captcha text to join the group privacy." + dropStrPrefix "#privacy SimpleX-Directory> " . dropTime <$> getTermLine cath + sendCaptcha cath captcha = do + cath #> ("#privacy " <> captcha) + cath <# ("#privacy SimpleX-Directory!> > cath " <> captcha) + cath <## " Correct, you joined the group privacy" + cath <## "#privacy: you joined the group" + testRestoreDirectory :: HasCallStack => TestParams -> IO () testRestoreDirectory ps = do testListUserGroups ps @@ -1137,7 +1221,8 @@ runDirectory cfg opts@DirectoryOpts {directoryLog} action = do where bot st = do env <- newServiceState opts - simplexChatCore cfg (mkChatOpts opts) $ directoryService st opts env + let cfg' = cfg {chatHooks = defaultChatHooks {acceptMember = Just $ acceptMemberHook opts env}} + simplexChatCore cfg' (mkChatOpts opts) $ directoryService st opts env registerGroup :: TestCC -> TestCC -> String -> String -> IO () registerGroup su u n fn = registerGroupId su u n fn 1 1 From 7c1d900e1f1fe65f3e1148845594b1000dd0baaa Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 29 Mar 2025 20:47:15 +0000 Subject: [PATCH 493/567] core: 6.3.1.1 --- simplex-chat.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simplex-chat.cabal b/simplex-chat.cabal index f2b3582722..f3095e7fce 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.3.1.0 +version: 6.3.1.1 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From af56b3fed006b42d0b6740ed8817551fbd9f39fa Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 31 Mar 2025 17:27:12 +0100 Subject: [PATCH 494/567] ios: update core library --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index b7db01ec2b..060f0effe8 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -174,8 +174,8 @@ 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; }; 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; }; - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.0-IjBs5IcA9K0E6bK6RlYtK-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.0-IjBs5IcA9K0E6bK6RlYtK-ghc9.6.3.a */; }; - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.0-IjBs5IcA9K0E6bK6RlYtK.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.0-IjBs5IcA9K0E6bK6RlYtK.a */; }; + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.1-AtbMdJukKDu9yojMiV9pEU-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.1-AtbMdJukKDu9yojMiV9pEU-ghc9.6.3.a */; }; + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.1-AtbMdJukKDu9yojMiV9pEU.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.1-AtbMdJukKDu9yojMiV9pEU.a */; }; 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; @@ -531,8 +531,8 @@ 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.0-IjBs5IcA9K0E6bK6RlYtK-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.1.0-IjBs5IcA9K0E6bK6RlYtK-ghc9.6.3.a"; sourceTree = ""; }; - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.0-IjBs5IcA9K0E6bK6RlYtK.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.1.0-IjBs5IcA9K0E6bK6RlYtK.a"; sourceTree = ""; }; + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.1-AtbMdJukKDu9yojMiV9pEU-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.1.1-AtbMdJukKDu9yojMiV9pEU-ghc9.6.3.a"; sourceTree = ""; }; + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.1-AtbMdJukKDu9yojMiV9pEU.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.1.1-AtbMdJukKDu9yojMiV9pEU.a"; sourceTree = ""; }; 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; @@ -688,8 +688,8 @@ 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */, 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */, 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */, - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.0-IjBs5IcA9K0E6bK6RlYtK-ghc9.6.3.a in Frameworks */, - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.0-IjBs5IcA9K0E6bK6RlYtK.a in Frameworks */, + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.1-AtbMdJukKDu9yojMiV9pEU-ghc9.6.3.a in Frameworks */, + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.1-AtbMdJukKDu9yojMiV9pEU.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -774,8 +774,8 @@ 64C829992D54AEEE006B9E89 /* libffi.a */, 64C829982D54AEED006B9E89 /* libgmp.a */, 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */, - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.0-IjBs5IcA9K0E6bK6RlYtK-ghc9.6.3.a */, - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.0-IjBs5IcA9K0E6bK6RlYtK.a */, + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.1-AtbMdJukKDu9yojMiV9pEU-ghc9.6.3.a */, + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.1-AtbMdJukKDu9yojMiV9pEU.a */, ); path = Libraries; sourceTree = ""; From a32ed2ec1f794011b659b10576ba054150a7145a Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 31 Mar 2025 19:29:45 +0100 Subject: [PATCH 495/567] ui: translations (#5791) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (Spanish) Currently translated at 100.0% (2341 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Catalan) Currently translated at 100.0% (2341 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ca/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2341 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2055 of 2055 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2341 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Japanese) Currently translated at 82.8% (1939 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/ * Translated using Weblate (Dutch) Currently translated at 99.3% (2326 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Dutch) Currently translated at 99.8% (2051 of 2055 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 96.1% (2252 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2341 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2055 of 2055 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 96.8% (2267 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 96.8% (2267 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/ * Translated using Weblate (Dutch) Currently translated at 100.0% (2341 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (2055 of 2055 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/ * Translated using Weblate (Japanese) Currently translated at 83.2% (1950 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 98.1% (2298 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/ * Translated using Weblate (Japanese) Currently translated at 83.3% (1951 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 98.1% (2298 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2341 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2055 of 2055 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Czech) Currently translated at 100.0% (2341 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Vietnamese) Currently translated at 99.7% (2334 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Vietnamese) Currently translated at 100.0% (2341 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2341 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2055 of 2055 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2341 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2055 of 2055 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2055 of 2055 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2341 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2055 of 2055 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Vietnamese) Currently translated at 100.0% (2341 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2341 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2055 of 2055 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2341 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2055 of 2055 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Indonesian) Currently translated at 99.1% (2321 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (Indonesian) Currently translated at 99.7% (2334 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2341 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2055 of 2055 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2341 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Dutch) Currently translated at 100.0% (2341 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (2055 of 2055 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/ * Translated using Weblate (Catalan) Currently translated at 100.0% (2341 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ca/ * Translated using Weblate (Korean) Currently translated at 21.9% (451 of 2055 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ko/ * Translated using Weblate (Vietnamese) Currently translated at 100.0% (2341 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2341 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Arabic) Currently translated at 30.8% (633 of 2055 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ar/ * Translated using Weblate (Italian) Currently translated at 100.0% (2341 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (2055 of 2055 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Russian) Currently translated at 100.0% (2341 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2341 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2055 of 2055 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (German) Currently translated at 100.0% (2341 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (German) Currently translated at 100.0% (2055 of 2055 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 99.9% (2340 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/ * Translated using Weblate (Turkish) Currently translated at 93.1% (2181 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/tr/ * Translated using Weblate (Russian) Currently translated at 96.6% (1987 of 2055 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ru/ * Translated using Weblate (French) Currently translated at 98.6% (2309 of 2341 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 84.3% (1734 of 2055 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hans/ * Translated using Weblate (Hungarian) Currently translated at 99.7% (2341 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2346 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2055 of 2055 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (German) Currently translated at 100.0% (2346 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2346 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Spanish) Currently translated at 99.8% (2343 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Arabic) Currently translated at 100.0% (2346 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Catalan) Currently translated at 100.0% (2346 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ca/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2346 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2346 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Vietnamese) Currently translated at 100.0% (2346 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Spanish) Currently translated at 99.8% (2343 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2346 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2055 of 2055 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2346 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/ * Translated using Weblate (Italian) Currently translated at 100.0% (2346 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Russian) Currently translated at 100.0% (2346 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * export/import localizations * add translations for onboarding * translation --------- Co-authored-by: No name Co-authored-by: fran secs Co-authored-by: summoner001 Co-authored-by: k-kozika Co-authored-by: M1K4 Co-authored-by: João Moreira Co-authored-by: Igor Julliano Co-authored-by: Miyu Sakatsuki Co-authored-by: zenobit Co-authored-by: tuananh-ng <158744840+tuananh-ng@users.noreply.github.com> Co-authored-by: Rafi Co-authored-by: dtalens Co-authored-by: jaeone Co-authored-by: Muhammad Co-authored-by: Random Co-authored-by: mlanp Co-authored-by: Volkan Yıldırım Co-authored-by: Near Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com> Co-authored-by: Ross Li Co-authored-by: 大王叫我来巡山 Co-authored-by: Mihai Pantazi Co-authored-by: jonnysemon --- .../ar.xcloc/Localized Contents/ar.xliff | 100 ++++- .../bg.xcloc/Localized Contents/bg.xliff | 109 +++--- .../bg.xcloc/contents.json | 4 +- .../cs.xcloc/Localized Contents/cs.xliff | 108 +++--- .../cs.xcloc/contents.json | 4 +- .../de.xcloc/Localized Contents/de.xliff | 154 ++++---- .../de.xcloc/contents.json | 4 +- .../en.xcloc/Localized Contents/en.xliff | 123 +++--- .../en.xcloc/contents.json | 4 +- .../es.xcloc/Localized Contents/es.xliff | 174 ++++----- .../es.xcloc/contents.json | 4 +- .../fi.xcloc/Localized Contents/fi.xliff | 109 +++--- .../fi.xcloc/contents.json | 4 +- .../fr.xcloc/Localized Contents/fr.xliff | 116 +++--- .../fr.xcloc/contents.json | 4 +- .../hu.xcloc/Localized Contents/hu.xliff | 364 +++++++++--------- .../hu.xcloc/contents.json | 4 +- .../it.xcloc/Localized Contents/it.xliff | 124 +++--- .../it.xcloc/contents.json | 4 +- .../ja.xcloc/Localized Contents/ja.xliff | 109 +++--- .../ja.xcloc/contents.json | 4 +- .../ko.xcloc/Localized Contents/ko.xliff | 204 +++++++++- .../nl.xcloc/Localized Contents/nl.xliff | 130 +++---- .../nl.xcloc/contents.json | 4 +- .../pl.xcloc/Localized Contents/pl.xliff | 110 +++--- .../pl.xcloc/contents.json | 4 +- .../ru.xcloc/Localized Contents/ru.xliff | 193 +++------- .../ru.xcloc/contents.json | 4 +- .../th.xcloc/Localized Contents/th.xliff | 108 +++--- .../th.xcloc/contents.json | 4 +- .../tr.xcloc/Localized Contents/tr.xliff | 112 +++--- .../tr.xcloc/contents.json | 4 +- .../uk.xcloc/Localized Contents/uk.xliff | 116 +++--- .../uk.xcloc/contents.json | 4 +- .../Localized Contents/zh-Hans.xliff | 118 +++--- .../zh-Hans.xcloc/contents.json | 4 +- .../SimpleX SE/hu.lproj/Localizable.strings | 4 +- apps/ios/bg.lproj/Localizable.strings | 45 +-- apps/ios/cs.lproj/Localizable.strings | 37 +- apps/ios/de.lproj/Localizable.strings | 110 +++--- .../de.lproj/SimpleX--iOS--InfoPlist.strings | 2 +- apps/ios/es.lproj/Localizable.strings | 132 +++---- apps/ios/fi.lproj/Localizable.strings | 40 +- apps/ios/fr.lproj/Localizable.strings | 66 +--- apps/ios/hu.lproj/Localizable.strings | 316 ++++++++------- apps/ios/it.lproj/Localizable.strings | 82 ++-- apps/ios/ja.lproj/Localizable.strings | 40 +- apps/ios/nl.lproj/Localizable.strings | 90 ++--- apps/ios/pl.lproj/Localizable.strings | 48 +-- apps/ios/ru.lproj/Localizable.strings | 284 ++------------ apps/ios/th.lproj/Localizable.strings | 37 +- apps/ios/tr.lproj/Localizable.strings | 54 +-- apps/ios/uk.lproj/Localizable.strings | 66 +--- apps/ios/zh-Hans.lproj/Localizable.strings | 66 ++-- .../commonMain/resources/MR/ar/strings.xml | 7 +- .../commonMain/resources/MR/ca/strings.xml | 107 ++--- .../commonMain/resources/MR/cs/strings.xml | 8 + .../commonMain/resources/MR/de/strings.xml | 45 ++- .../commonMain/resources/MR/es/strings.xml | 165 ++++---- .../commonMain/resources/MR/fr/strings.xml | 19 + .../commonMain/resources/MR/hu/strings.xml | 275 ++++++------- .../commonMain/resources/MR/in/strings.xml | 23 +- .../commonMain/resources/MR/it/strings.xml | 9 +- .../commonMain/resources/MR/ja/strings.xml | 15 + .../commonMain/resources/MR/nl/strings.xml | 24 +- .../resources/MR/pt-rBR/strings.xml | 196 +++++++++- .../commonMain/resources/MR/ru/strings.xml | 9 +- .../commonMain/resources/MR/tr/strings.xml | 95 ++++- .../commonMain/resources/MR/vi/strings.xml | 105 ++--- .../resources/MR/zh-rCN/strings.xml | 55 +-- 70 files changed, 2712 insertions(+), 2713 deletions(-) diff --git a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff index aba5e1384e..613a5e08ad 100644 --- a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff +++ b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff @@ -553,8 +553,9 @@ يمكنك أنت وجهة اتصالك إرسال رسائل صوتية. 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). + حسب ملف تعريف الدردشة (افتراضي) أو [حسب الاتصال] (https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. @@ -582,8 +583,9 @@ إلغاء No comment provided by engineer. - + Cannot access keychain to save database password + لا يمكن الوصول إلى سلسلة المفاتيح لحفظ كلمة مرور قاعدة البيانات No comment provided by engineer. @@ -601,8 +603,9 @@ تغيير عبارة مرور قاعدة البيانات؟ No comment provided by engineer. - + Change member role? + تغيير دور العضو؟ No comment provided by engineer. @@ -669,12 +672,14 @@ تحقق من عنوان الخادم وحاول مرة أخرى. No comment provided by engineer. - + Choose file + اختر الملف No comment provided by engineer. - + Choose from library + اختر من المكتبة No comment provided by engineer. @@ -756,8 +761,9 @@ جارِ الاتصال بالخادم… No comment provided by engineer. - + Connecting to server… (error: %@) + الاتصال بالخادم... (الخطأ: %@) No comment provided by engineer. @@ -5389,6 +5395,88 @@ This is your own one-time link! Enable self-destruct passcode تفعيل رمز التدمير الذاتي + + Can't message member + لا يمكن الاتصال بالعضو + + + Color chats with the new themes. + محادثات ملونة مع السمات الجديدة. + + + All chats will be removed from the list %@, and the list deleted. + ستتم إزالة جميع الدردشات من القائمة %@، وسيتم حذف القائمة. + + + Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! + البلغارية والفنلندية والتايلاندية والأوكرانية - شكرًا للمستخدمين و[Weblate] (https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! + + + Choose _Migrate from another device_ on the new device and scan QR code. + اختر _الترحيل من جهاز آخر_ على الجهاز الجديد وامسح رمز الاستجابة السريعة. + + + Conditions will be accepted for the operator(s): **%@**. + سيتم قبول شروط المشغل (المشغلين): **%@**. + + + Conditions will be accepted on: %@. + سيتم قبول الشروط على: %@. + + + Confirmed + تم التأكيد + + + Connection is blocked by server operator: +%@ + تم حظر الاتصال من قبل مشغل الخادم: +%@ + + + Can't call member + لا يمكن الاتصال بالعضو + + + Chat already exists + الدردشة موجودة بالفعل + + + Check messages every 20 min. + تحقق من الرسائل كل 20 دقيقة. + + + Check messages when allowed. + تحقق من الرسائل عندما يُسمح بذلك. + + + Cannot forward message + لا يمكن إعادة توجيه الرسالة + + + Chat preferences were changed. + تم تغيير تفضيلات المحادثة. + + + Conditions are already accepted for these operator(s): **%@**. + الشروط مقبولة بالفعل لهذا المشغل (المشغلين): **%@**. + + + Conditions will be accepted for operator(s): **%@**. + سيتم قبول شروط المشغل (المشغلين): **%@**. + + + Conditions accepted on: %@. + الشروط المقبولة على: %@. + + + Conditions are accepted for the operator(s): **%@**. + يتم قبول شروط المشغل (المشغلين): **%@**. + + + Conditions will be automatically accepted for enabled operators on: %@. + سيتم قبول الشروط تلقائيًا للمشغلين الممكّنين على: %@. + diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 7c118a877f..6fb6729a9b 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -2,7 +2,7 @@
    - +
    @@ -431,7 +431,7 @@ 1 day 1 ден delete after time - time interval +time interval 1 hour @@ -447,13 +447,13 @@ 1 month 1 месец delete after time - time interval +time interval 1 week 1 седмица delete after time - time interval +time interval 1 year @@ -551,8 +551,8 @@ Accept Приеми accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action Accept conditions @@ -573,7 +573,7 @@ Accept incognito Приеми инкогнито accept contact request via notification - swipe action +swipe action Accepted conditions @@ -1300,6 +1300,12 @@ Чрез чат профил (по подразбиране) или [чрез връзка](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (БЕТА). No comment provided by engineer. + + By using SimpleX Chat you agree to: +- send only legal content in public groups. +- respect other users – no spam. + No comment provided by engineer. + Call already ended! Разговорът вече приключи! @@ -1348,7 +1354,7 @@ Cancel Отказ alert action - alert button +alert button Cancel migration @@ -1437,7 +1443,7 @@ Change self-destruct passcode Промени кода за достъп за самоунищожение authentication reason - set passcode view +set passcode view Chat @@ -1654,14 +1660,6 @@ Conditions of use No comment provided by engineer. - - Conditions will be accepted for enabled operators after 30 days. - No comment provided by engineer. - - - Conditions will be accepted for operator(s): **%@**. - No comment provided by engineer. - Conditions will be accepted for the operator(s): **%@**. No comment provided by engineer. @@ -1679,6 +1677,10 @@ Конфигурирай ICE сървъри No comment provided by engineer. + + Configure server operators + No comment provided by engineer. + Confirm Потвърди @@ -2244,7 +2246,7 @@ This is your own one-time link! Delete Изтрий alert action - swipe action +swipe action Delete %lld messages of members? @@ -2711,7 +2713,7 @@ This is your own one-time link! Download Изтегли alert button - chat item action +chat item action Download errors @@ -2783,8 +2785,8 @@ This is your own one-time link! Активиране (запазване на промените) No comment provided by engineer. - - Enable Flux + + Enable Flux in Network & servers settings for better metadata privacy. No comment provided by engineer. @@ -3337,7 +3339,9 @@ This is your own one-time link! Error: %@ Грешка: %@ - alert message + alert message +file error text +snd error text Error: URL is invalid @@ -5589,11 +5593,19 @@ Error: %@ Privacy for your customers. No comment provided by engineer. + + Privacy policy and conditions of use. + No comment provided by engineer. + Privacy redefined Поверителността преосмислена No comment provided by engineer. + + Private chats, groups and your contacts are not accessible to server operators. + No comment provided by engineer. + Private filenames Поверителни имена на файлове @@ -5934,7 +5946,7 @@ Enable in *Network & servers* settings. Reject Отхвърляне reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -6153,10 +6165,6 @@ Enable in *Network & servers* settings. Review conditions No comment provided by engineer. - - Review later - No comment provided by engineer. - Revoke Отзови @@ -6203,7 +6211,7 @@ Enable in *Network & servers* settings. Save Запази alert button - chat item action +chat item action Save (and notify contacts) @@ -6736,7 +6744,7 @@ Enable in *Network & servers* settings. Share Сподели alert action - chat item action +chat item action Share 1-time link @@ -6982,7 +6990,7 @@ Enable in *Network & servers* settings. Spam blocking reason - report reason +report reason Square, circle, or anything in between. @@ -7330,10 +7338,6 @@ It can happen because of some bug or when the connection is compromised.The same conditions will apply to operator **%@**. No comment provided by engineer. - - The same conditions will apply to operator(s): **%@**. - No comment provided by engineer. - The second preset operator in the app! No comment provided by engineer. @@ -8246,10 +8250,6 @@ Repeat join request? You can change it in Appearance settings. No comment provided by engineer. - - You can configure operators in Network & servers settings. - No comment provided by engineer. - You can configure servers via settings. No comment provided by engineer. @@ -8483,11 +8483,6 @@ Repeat connection request? Вашите ICE сървъри No comment provided by engineer. - - Your SMP servers - Вашите SMP сървъри - No comment provided by engineer. - Your SimpleX address Вашият адрес в SimpleX @@ -8718,7 +8713,8 @@ Repeat connection request? blocked by admin блокиран от админ - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -8887,7 +8883,7 @@ Repeat connection request? default (%@) по подразбиране (%@) delete after time - pref value +pref value default (no) @@ -9013,19 +9009,10 @@ Repeat connection request? грешка No comment provided by engineer. - - event happened - събитие се случи - No comment provided by engineer. - expired No comment provided by engineer. - - for better metadata privacy. - No comment provided by engineer. - forwarded препратено @@ -9231,8 +9218,8 @@ Repeat connection request? off изключено enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -9588,7 +9575,7 @@ last received msg: %2$@
    - +
    @@ -9625,7 +9612,7 @@ last received msg: %2$@
    - +
    @@ -9647,7 +9634,7 @@ last received msg: %2$@
    - +
    @@ -9674,7 +9661,7 @@ last received msg: %2$@
    - +
    @@ -9693,7 +9680,7 @@ last received msg: %2$@
    - +
    diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/contents.json b/apps/ios/SimpleX Localizations/bg.xcloc/contents.json index 5356e25a2e..66d64e6539 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/bg.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "bg", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index e8ea11fb5f..5fd894b226 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -2,7 +2,7 @@
    - +
    @@ -420,7 +420,7 @@ 1 day 1 den delete after time - time interval +time interval 1 hour @@ -436,13 +436,13 @@ 1 month 1 měsíc delete after time - time interval +time interval 1 week 1 týden delete after time - time interval +time interval 1 year @@ -537,8 +537,8 @@ Accept Přijmout accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action Accept conditions @@ -558,7 +558,7 @@ Accept incognito Přijmout inkognito accept contact request via notification - swipe action +swipe action Accepted conditions @@ -1227,6 +1227,12 @@ 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). No comment provided by engineer. + + By using SimpleX Chat you agree to: +- send only legal content in public groups. +- respect other users – no spam. + No comment provided by engineer. + Call already ended! Hovor již skončil! @@ -1271,7 +1277,7 @@ Cancel Zrušit alert action - alert button +alert button Cancel migration @@ -1356,7 +1362,7 @@ Change self-destruct passcode Změnit sebedestrukční heslo authentication reason - set passcode view +set passcode view Chat @@ -1569,14 +1575,6 @@ Conditions of use No comment provided by engineer. - - Conditions will be accepted for enabled operators after 30 days. - No comment provided by engineer. - - - Conditions will be accepted for operator(s): **%@**. - No comment provided by engineer. - Conditions will be accepted for the operator(s): **%@**. No comment provided by engineer. @@ -1594,6 +1592,10 @@ Konfigurace serverů ICE No comment provided by engineer. + + Configure server operators + No comment provided by engineer. + Confirm Potvrdit @@ -2134,7 +2136,7 @@ This is your own one-time link! Delete Smazat alert action - swipe action +swipe action Delete %lld messages of members? @@ -2591,7 +2593,7 @@ This is your own one-time link! Download alert button - chat item action +chat item action Download errors @@ -2660,8 +2662,8 @@ This is your own one-time link! Povolit (zachovat přepsání) No comment provided by engineer. - - Enable Flux + + Enable Flux in Network & servers settings for better metadata privacy. No comment provided by engineer. @@ -3197,7 +3199,9 @@ This is your own one-time link! Error: %@ Chyba: %@ - alert message + alert message +file error text +snd error text Error: URL is invalid @@ -5373,11 +5377,19 @@ Error: %@ Privacy for your customers. No comment provided by engineer. + + Privacy policy and conditions of use. + No comment provided by engineer. + Privacy redefined Nové vymezení soukromí No comment provided by engineer. + + Private chats, groups and your contacts are not accessible to server operators. + No comment provided by engineer. + Private filenames Soukromé názvy souborů @@ -5710,7 +5722,7 @@ Enable in *Network & servers* settings. Reject Odmítnout reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -5923,10 +5935,6 @@ Enable in *Network & servers* settings. Review conditions No comment provided by engineer. - - Review later - No comment provided by engineer. - Revoke Odvolat @@ -5972,7 +5980,7 @@ Enable in *Network & servers* settings. Save Uložit alert button - chat item action +chat item action Save (and notify contacts) @@ -6495,7 +6503,7 @@ Enable in *Network & servers* settings. Share Sdílet alert action - chat item action +chat item action Share 1-time link @@ -6737,7 +6745,7 @@ Enable in *Network & servers* settings. Spam blocking reason - report reason +report reason Square, circle, or anything in between. @@ -7077,10 +7085,6 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován The same conditions will apply to operator **%@**. No comment provided by engineer. - - The same conditions will apply to operator(s): **%@**. - No comment provided by engineer. - The second preset operator in the app! No comment provided by engineer. @@ -7945,10 +7949,6 @@ Repeat join request? You can change it in Appearance settings. No comment provided by engineer. - - You can configure operators in Network & servers settings. - No comment provided by engineer. - You can configure servers via settings. No comment provided by engineer. @@ -8174,11 +8174,6 @@ Repeat connection request? Vaše servery ICE No comment provided by engineer. - - Your SMP servers - Vaše servery SMP - No comment provided by engineer. - Your SimpleX address Vaše SimpleX adresa @@ -8401,7 +8396,8 @@ Repeat connection request? blocked by admin - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -8569,7 +8565,7 @@ Repeat connection request? default (%@) výchozí (%@) delete after time - pref value +pref value default (no) @@ -8694,18 +8690,10 @@ Repeat connection request? chyba No comment provided by engineer. - - event happened - No comment provided by engineer. - expired No comment provided by engineer. - - for better metadata privacy. - No comment provided by engineer. - forwarded No comment provided by engineer. @@ -8909,8 +8897,8 @@ Repeat connection request? off vypnuto enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -9250,7 +9238,7 @@ last received msg: %2$@
    - +
    @@ -9286,7 +9274,7 @@ last received msg: %2$@
    - +
    @@ -9308,7 +9296,7 @@ last received msg: %2$@
    - +
    @@ -9335,7 +9323,7 @@ last received msg: %2$@
    - +
    @@ -9354,7 +9342,7 @@ last received msg: %2$@
    - +
    diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/contents.json b/apps/ios/SimpleX Localizations/cs.xcloc/contents.json index aaa2ed1ee0..9cd5922c24 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/cs.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "cs", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 4c391f97d3..1603a67df1 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -2,7 +2,7 @@
    - +
    @@ -440,7 +440,7 @@ 1 day Älter als ein Tag delete after time - time interval +time interval 1 hour @@ -456,13 +456,13 @@ 1 month Älter als ein Monat delete after time - time interval +time interval 1 week Älter als eine Woche delete after time - time interval +time interval 1 year @@ -562,8 +562,8 @@ Accept Annehmen accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action Accept conditions @@ -584,7 +584,7 @@ Accept incognito Inkognito akzeptieren accept contact request via notification - swipe action +swipe action Accepted conditions @@ -1331,6 +1331,15 @@ Per Chat-Profil (Voreinstellung) oder [per Verbindung](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. + + By using SimpleX Chat you agree to: +- send only legal content in public groups. +- respect other users – no spam. + Durch die Nutzung von SimpleX Chat erklären Sie sich damit einverstanden: +- nur legale Inhalte in öffentlichen Gruppen zu versenden. +- andere Nutzer zu respektieren - kein Spam. + No comment provided by engineer. + Call already ended! Anruf ist bereits beendet! @@ -1380,7 +1389,7 @@ Cancel Abbrechen alert action - alert button +alert button Cancel migration @@ -1471,7 +1480,7 @@ Change self-destruct passcode Selbstzerstörungs-Zugangscode ändern authentication reason - set passcode view +set passcode view Chat @@ -1713,16 +1722,6 @@ Nutzungsbedingungen No comment provided by engineer. - - Conditions will be accepted for enabled operators after 30 days. - Die Nutzungsbedingungen der aktivierten Betreiber werden nach 30 Tagen akzeptiert. - No comment provided by engineer. - - - Conditions will be accepted for operator(s): **%@**. - Die Nutzungsbedingungen der/des Betreiber(s) werden akzeptiert: **%@**. - No comment provided by engineer. - Conditions will be accepted for the operator(s): **%@**. Die Nutzungsbedingungen der/des Betreiber(s) werden akzeptiert: **%@**. @@ -1743,6 +1742,11 @@ ICE-Server konfigurieren No comment provided by engineer. + + Configure server operators + Server-Betreiber konfigurieren + No comment provided by engineer. + Confirm Bestätigen @@ -2341,7 +2345,7 @@ Das ist Ihr eigener Einmal-Link! Delete Löschen alert action - swipe action +swipe action Delete %lld messages of members? @@ -2837,7 +2841,7 @@ Das ist Ihr eigener Einmal-Link! Download Herunterladen alert button - chat item action +chat item action Download errors @@ -2914,9 +2918,8 @@ Das ist Ihr eigener Einmal-Link! Aktivieren (vorgenommene Einstellungen bleiben erhalten) No comment provided by engineer. - - Enable Flux - Flux aktivieren + + Enable Flux in Network & servers settings for better metadata privacy. No comment provided by engineer. @@ -3321,7 +3324,7 @@ Das ist Ihr eigener Einmal-Link! Error receiving file - Fehler beim Empfangen der Datei + Fehler beim Herunterladen der Datei alert title @@ -3492,7 +3495,9 @@ Das ist Ihr eigener Einmal-Link! Error: %@ Fehler: %@ - alert message + alert message +file error text +snd error text Error: URL is invalid @@ -3645,12 +3650,12 @@ Das ist Ihr eigener Einmal-Link! File will be received when your contact completes uploading it. - Die Datei wird empfangen, sobald das Hochladen durch ihren Kontakt abgeschlossen ist. + Die Datei wird heruntergeladen, sobald das Hochladen durch ihren Kontakt abgeschlossen ist. No comment provided by engineer. File will be received when your contact is online, please wait or check later! - Die Datei wird empfangen, sobald Ihr Kontakt online ist. Bitte warten oder schauen Sie später nochmal nach! + Die Datei wird heruntergeladen, sobald Ihr Kontakt online ist. Bitte warten oder schauen Sie später nochmal nach! No comment provided by engineer. @@ -4139,12 +4144,12 @@ Fehler: %2$@ Image will be received when your contact completes uploading it. - Das Bild wird empfangen, sobald das Hochladen durch ihren Kontakt abgeschlossen ist. + Das Bild wird heruntergeladen, sobald das Hochladen durch ihren Kontakt abgeschlossen ist. No comment provided by engineer. Image will be received when your contact is online, please wait or check later! - Das Bild wird empfangen, sobald Ihr Kontakt online ist. Bitte warten oder schauen Sie später nochmal nach! + Das Bild wird heruntergeladen, sobald Ihr Kontakt online ist. Bitte warten oder schauen Sie später nochmal nach! No comment provided by engineer. @@ -5306,7 +5311,7 @@ Das ist Ihr Link für die Gruppe %@! No received or sent files - Keine empfangenen oder gesendeten Dateien + Keine herunter- oder hochgeladene Dateien No comment provided by engineer. @@ -5316,7 +5321,7 @@ Das ist Ihr Link für die Gruppe %@! No servers to receive files. - Keine Server für den Empfang von Dateien. + Keine Server für das Herunterladen von Dateien. servers error @@ -5893,11 +5898,21 @@ Fehler: %@ Schutz der Privatsphäre Ihrer Kunden. No comment provided by engineer. + + Privacy policy and conditions of use. + Datenschutzbestimmungen und Nutzungsbedingungen. + No comment provided by engineer. + Privacy redefined Datenschutz neu definiert No comment provided by engineer. + + Private chats, groups and your contacts are not accessible to server operators. + Private Chats, Gruppen und Ihre Kontakte sind für Server-Betreiber nicht zugänglich. + No comment provided by engineer. + Private filenames Neutrale Dateinamen @@ -6172,7 +6187,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Receiving file will be stopped. - Der Empfang der Datei wird beendet. + Das Herunterladen der Datei wird beendet. No comment provided by engineer. @@ -6264,7 +6279,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Reject Ablehnen reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -6501,11 +6516,6 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Nutzungsbedingungen einsehen No comment provided by engineer. - - Review later - Später einsehen - No comment provided by engineer. - Revoke Widerrufen @@ -6543,7 +6553,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Safely receive files - Dateien sicher empfangen + Dateien sicher herunterladen No comment provided by engineer. @@ -6555,7 +6565,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Save Speichern alert button - chat item action +chat item action Save (and notify contacts) @@ -7126,7 +7136,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Share Teilen alert action - chat item action +chat item action Share 1-time link @@ -7394,7 +7404,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Spam Spam blocking reason - report reason +report reason Square, circle, or anything in between. @@ -7453,17 +7463,17 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Stop file - Datei beenden + Herunterladen beenden cancel file action Stop receiving file? - Den Empfang der Datei beenden? + Das Herunterladen der Datei beenden? No comment provided by engineer. Stop sending file? - Das Senden der Datei beenden? + Das Hochladen der Datei beenden? No comment provided by engineer. @@ -7763,11 +7773,6 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Dieselben Nutzungsbedingungen gelten auch für den Betreiber **%@**. No comment provided by engineer. - - The same conditions will apply to operator(s): **%@**. - Dieselben Nutzungsbedingungen gelten auch für den/die Betreiber: **%@**. - No comment provided by engineer. - The second preset operator in the app! Der zweite voreingestellte Netzwerk-Betreiber in der App! @@ -7825,7 +7830,7 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain. - Es werden alle empfangenen und gesendeten Dateien und Medien gelöscht. Bilder mit niedriger Auflösung bleiben erhalten. Diese Aktion kann nicht rückgängig gemacht werden! + Es werden alle herunter- und hochgeladene Dateien und Medien gelöscht. Bilder mit niedriger Auflösung bleiben erhalten. Diese Aktion kann nicht rückgängig gemacht werden! No comment provided by engineer. @@ -8424,12 +8429,12 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Video will be received when your contact completes uploading it. - Das Video wird empfangen, sobald Ihr Kontakt das Hochladen beendet hat. + Das Video wird heruntergeladen, sobald Ihr Kontakt das Hochladen beendet hat. No comment provided by engineer. Video will be received when your contact is online, please wait or check later! - Das Video wird empfangen, wenn Ihr Kontakt online ist. Bitte warten oder überprüfen Sie es später! + Das Video wird heruntergeladen, sobald Ihr Kontakt online ist. Bitte warten oder überprüfen Sie es später! No comment provided by engineer. @@ -8734,11 +8739,6 @@ Verbindungsanfrage wiederholen? Kann von Ihnen in den Erscheinungsbild-Einstellungen geändert werden. No comment provided by engineer. - - You can configure operators in Network & servers settings. - Sie können die Betreiber in den Netzwerk- und Servereinstellungen konfigurieren. - No comment provided by engineer. - You can configure servers via settings. Sie können die Server über die Einstellungen konfigurieren. @@ -8981,11 +8981,6 @@ Verbindungsanfrage wiederholen? Ihre ICE-Server No comment provided by engineer. - - Your SMP servers - Ihre SMP-Server - No comment provided by engineer. - Your SimpleX address Ihre SimpleX-Adresse @@ -9224,7 +9219,8 @@ Verbindungsanfrage wiederholen? blocked by admin wurde vom Administrator blockiert - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -9395,7 +9391,7 @@ Verbindungsanfrage wiederholen? default (%@) Default (%@) delete after time - pref value +pref value default (no) @@ -9522,21 +9518,11 @@ Verbindungsanfrage wiederholen? Fehler No comment provided by engineer. - - event happened - event happened - No comment provided by engineer. - expired Abgelaufen No comment provided by engineer. - - for better metadata privacy. - für einen besseren Metadatenschutz. - No comment provided by engineer. - forwarded weitergeleitet @@ -9746,8 +9732,8 @@ Verbindungsanfrage wiederholen? off Aus enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -10117,7 +10103,7 @@ Zuletzt empfangene Nachricht: %2$@
    - +
    @@ -10147,14 +10133,14 @@ Zuletzt empfangene Nachricht: %2$@ SimpleX needs access to Photo Library for saving captured and received media - SimpleX benötigt Zugriff auf das Fotoalbum, um selbst gemachte oder empfangene Bilder zu speichern + SimpleX benötigt Zugriff auf das Fotoalbum, um selbst gemachte oder heruntergeladene Bilder zu speichern Privacy - Photo Library Additions Usage Description
    - +
    @@ -10176,7 +10162,7 @@ Zuletzt empfangene Nachricht: %2$@
    - +
    @@ -10208,7 +10194,7 @@ Zuletzt empfangene Nachricht: %2$@
    - +
    @@ -10230,7 +10216,7 @@ Zuletzt empfangene Nachricht: %2$@
    - +
    diff --git a/apps/ios/SimpleX Localizations/de.xcloc/contents.json b/apps/ios/SimpleX Localizations/de.xcloc/contents.json index 18b517d802..e8d71cf38c 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/de.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "de", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index a205080107..8d040ff99a 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -2,7 +2,7 @@
    - +
    @@ -440,7 +440,7 @@ 1 day 1 day delete after time - time interval +time interval 1 hour @@ -456,13 +456,13 @@ 1 month 1 month delete after time - time interval +time interval 1 week 1 week delete after time - time interval +time interval 1 year @@ -562,8 +562,8 @@ Accept Accept accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action Accept conditions @@ -584,7 +584,7 @@ Accept incognito Accept incognito accept contact request via notification - swipe action +swipe action Accepted conditions @@ -1331,6 +1331,15 @@ By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. + + By using SimpleX Chat you agree to: +- send only legal content in public groups. +- respect other users – no spam. + By using SimpleX Chat you agree to: +- send only legal content in public groups. +- respect other users – no spam. + No comment provided by engineer. + Call already ended! Call already ended! @@ -1380,7 +1389,7 @@ Cancel Cancel alert action - alert button +alert button Cancel migration @@ -1471,7 +1480,7 @@ Change self-destruct passcode Change self-destruct passcode authentication reason - set passcode view +set passcode view Chat @@ -1713,16 +1722,6 @@ Conditions of use No comment provided by engineer. - - Conditions will be accepted for enabled operators after 30 days. - Conditions will be accepted for enabled operators after 30 days. - No comment provided by engineer. - - - Conditions will be accepted for operator(s): **%@**. - Conditions will be accepted for operator(s): **%@**. - No comment provided by engineer. - Conditions will be accepted for the operator(s): **%@**. Conditions will be accepted for the operator(s): **%@**. @@ -1743,6 +1742,11 @@ Configure ICE servers No comment provided by engineer. + + Configure server operators + Configure server operators + No comment provided by engineer. + Confirm Confirm @@ -2341,7 +2345,7 @@ This is your own one-time link! Delete Delete alert action - swipe action +swipe action Delete %lld messages of members? @@ -2837,7 +2841,7 @@ This is your own one-time link! Download Download alert button - chat item action +chat item action Download errors @@ -2914,9 +2918,9 @@ This is your own one-time link! Enable (keep overrides) No comment provided by engineer. - - Enable Flux - Enable Flux + + Enable Flux in Network & servers settings for better metadata privacy. + Enable Flux in Network & servers settings for better metadata privacy. No comment provided by engineer. @@ -3492,7 +3496,9 @@ This is your own one-time link! Error: %@ Error: %@ - alert message + alert message +file error text +snd error text Error: URL is invalid @@ -5893,11 +5899,21 @@ Error: %@ Privacy for your customers. No comment provided by engineer. + + Privacy policy and conditions of use. + Privacy policy and conditions of use. + No comment provided by engineer. + Privacy redefined Privacy redefined No comment provided by engineer. + + Private chats, groups and your contacts are not accessible to server operators. + Private chats, groups and your contacts are not accessible to server operators. + No comment provided by engineer. + Private filenames Private filenames @@ -6264,7 +6280,7 @@ Enable in *Network & servers* settings. Reject Reject reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -6501,11 +6517,6 @@ Enable in *Network & servers* settings. Review conditions No comment provided by engineer. - - Review later - Review later - No comment provided by engineer. - Revoke Revoke @@ -6555,7 +6566,7 @@ Enable in *Network & servers* settings. Save Save alert button - chat item action +chat item action Save (and notify contacts) @@ -7126,7 +7137,7 @@ Enable in *Network & servers* settings. Share Share alert action - chat item action +chat item action Share 1-time link @@ -7394,7 +7405,7 @@ Enable in *Network & servers* settings. Spam Spam blocking reason - report reason +report reason Square, circle, or anything in between. @@ -7763,11 +7774,6 @@ It can happen because of some bug or when the connection is compromised.The same conditions will apply to operator **%@**. No comment provided by engineer. - - The same conditions will apply to operator(s): **%@**. - The same conditions will apply to operator(s): **%@**. - No comment provided by engineer. - The second preset operator in the app! The second preset operator in the app! @@ -8734,11 +8740,6 @@ Repeat join request? You can change it in Appearance settings. No comment provided by engineer. - - You can configure operators in Network & servers settings. - You can configure operators in Network & servers settings. - No comment provided by engineer. - You can configure servers via settings. You can configure servers via settings. @@ -8981,11 +8982,6 @@ Repeat connection request? Your ICE servers No comment provided by engineer. - - Your SMP servers - Your SMP servers - No comment provided by engineer. - Your SimpleX address Your SimpleX address @@ -9224,7 +9220,8 @@ Repeat connection request? blocked by admin blocked by admin - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -9395,7 +9392,7 @@ Repeat connection request? default (%@) default (%@) delete after time - pref value +pref value default (no) @@ -9522,21 +9519,11 @@ Repeat connection request? error No comment provided by engineer. - - event happened - event happened - No comment provided by engineer. - expired expired No comment provided by engineer. - - for better metadata privacy. - for better metadata privacy. - No comment provided by engineer. - forwarded forwarded @@ -9746,8 +9733,8 @@ Repeat connection request? off off enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -10117,7 +10104,7 @@ last received msg: %2$@
    - +
    @@ -10154,7 +10141,7 @@ last received msg: %2$@
    - +
    @@ -10176,7 +10163,7 @@ last received msg: %2$@
    - +
    @@ -10208,7 +10195,7 @@ last received msg: %2$@
    - +
    @@ -10230,7 +10217,7 @@ last received msg: %2$@
    - +
    diff --git a/apps/ios/SimpleX Localizations/en.xcloc/contents.json b/apps/ios/SimpleX Localizations/en.xcloc/contents.json index 2f39a1f1ee..ec2accf27e 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/en.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "en", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index fd2ae881ce..4f33d923af 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -2,7 +2,7 @@
    - +
    @@ -440,7 +440,7 @@ 1 day un dia delete after time - time interval +time interval 1 hour @@ -456,13 +456,13 @@ 1 month un mes delete after time - time interval +time interval 1 week una semana delete after time - time interval +time interval 1 year @@ -562,8 +562,8 @@ Accept Aceptar accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action Accept conditions @@ -584,7 +584,7 @@ Accept incognito Aceptar incógnito accept contact request via notification - swipe action +swipe action Accepted conditions @@ -743,12 +743,12 @@ All chats and messages will be deleted - this cannot be undone! - Se eliminarán todos los chats y mensajes. ¡No podrá deshacerse! + Se eliminarán todos los chats y mensajes. ¡No puede deshacerse! No comment provided by engineer. All chats will be removed from the list %@, and the list deleted. - Todos los chats serán quitados de la lista %@ y esta será eliminada. + Todos los chats se quitarán de la lista %@ y esta será eliminada. alert message @@ -773,12 +773,12 @@ All messages will be deleted - this cannot be undone! - Todos los mensajes serán borrados. ¡No podrá deshacerse! + Todos los mensajes serán eliminados. ¡No puede deshacerse! No comment provided by engineer. All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you. - Se eliminarán todos los mensajes SOLO para tí. ¡No podrá deshacerse! + Se eliminarán todos los mensajes SOLO para tí. ¡No puede deshacerse! No comment provided by engineer. @@ -993,7 +993,7 @@ App icon - Icono aplicación + Icono de la aplicación No comment provided by engineer. @@ -1331,6 +1331,15 @@ Mediante perfil (predeterminado) o [por conexión](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. + + By using SimpleX Chat you agree to: +- send only legal content in public groups. +- respect other users – no spam. + Al usar SimpleX Chat, aceptas: +- enviar únicamente contenido legal en los grupos públicos. +- respetar a los demás usuarios – spam prohibido. + No comment provided by engineer. + Call already ended! ¡La llamada ha terminado! @@ -1380,7 +1389,7 @@ Cancel Cancelar alert action - alert button +alert button Cancel migration @@ -1471,7 +1480,7 @@ Change self-destruct passcode Cambiar código autodestrucción authentication reason - set passcode view +set passcode view Chat @@ -1565,12 +1574,12 @@ Chat will be deleted for all members - this cannot be undone! - El chat será eliminado para todos los miembros. ¡No podrá deshacerse! + El chat será eliminado para todos los miembros. ¡No puede deshacerse! No comment provided by engineer. Chat will be deleted for you - this cannot be undone! - El chat será eliminado para tí. ¡No podrá deshacerse! + El chat será eliminado para tí. ¡No puede deshacerse! No comment provided by engineer. @@ -1713,16 +1722,6 @@ Condiciones de uso No comment provided by engineer. - - Conditions will be accepted for enabled operators after 30 days. - Las condiciones de los operadores habilitados serán aceptadas después de 30 días. - No comment provided by engineer. - - - Conditions will be accepted for operator(s): **%@**. - Las condiciones serán aceptadas para el/los operador(es): **%@**. - No comment provided by engineer. - Conditions will be accepted for the operator(s): **%@**. Las condiciones serán aceptadas para el/los operador(es): **%@**. @@ -1743,6 +1742,11 @@ Configure servidores ICE No comment provided by engineer. + + Configure server operators + Configurar operadores de servidores + No comment provided by engineer. + Confirm Confirmar @@ -2026,7 +2030,7 @@ This is your own one-time link! Contact will be deleted - this cannot be undone! - El contacto será eliminado. ¡No podrá deshacerse! + El contacto será eliminado. ¡No puede deshacerse! No comment provided by engineer. @@ -2341,7 +2345,7 @@ This is your own one-time link! Delete Eliminar alert action - swipe action +swipe action Delete %lld messages of members? @@ -2515,7 +2519,7 @@ This is your own one-time link! Delete or moderate up to 200 messages. - Borra o modera hasta 200 mensajes a la vez. + Elimina o modera hasta 200 mensajes a la vez. No comment provided by engineer. @@ -2535,7 +2539,7 @@ This is your own one-time link! Delete report - Borrar informe + Eliminar informe No comment provided by engineer. @@ -2837,7 +2841,7 @@ This is your own one-time link! Download Descargar alert button - chat item action +chat item action Download errors @@ -2914,9 +2918,8 @@ This is your own one-time link! Activar (conservar anulaciones) No comment provided by engineer. - - Enable Flux - Habilita Flux + + Enable Flux in Network & servers settings for better metadata privacy. No comment provided by engineer. @@ -3492,7 +3495,9 @@ This is your own one-time link! Error: %@ Error: %@ - alert message + alert message +file error text +snd error text Error: URL is invalid @@ -3620,7 +3625,7 @@ This is your own one-time link! File not found - most likely file was deleted or cancelled. - Archivo no encontrado, probablemente haya sido borrado o cancelado. + Archivo no encontrado, probablemente haya sido eliminado o cancelado. file error text @@ -3994,12 +3999,12 @@ Error: %2$@ Group will be deleted for all members - this cannot be undone! - El grupo será eliminado para todos los miembros. ¡No podrá deshacerse! + El grupo será eliminado para todos los miembros. ¡No puede deshacerse! No comment provided by engineer. Group will be deleted for you - this cannot be undone! - El grupo será eliminado para tí. ¡No podrá deshacerse! + El grupo será eliminado para tí. ¡No puede deshacerse! No comment provided by engineer. @@ -4731,7 +4736,7 @@ This is your link for group %@! Member reports - Informes de miembro + Informes de miembros chat feature @@ -4751,12 +4756,12 @@ This is your link for group %@! Member will be removed from chat - this cannot be undone! - El miembro será eliminado del chat. ¡No podrá deshacerse! + El miembro será eliminado del chat. ¡No puede deshacerse! No comment provided by engineer. Member will be removed from group - this cannot be undone! - El miembro será expulsado del grupo. ¡No podrá deshacerse! + El miembro será expulsado del grupo. ¡No puede deshacerse! No comment provided by engineer. @@ -4916,7 +4921,7 @@ This is your link for group %@! Messages in this chat will never be deleted. - Los mensajes de esta conversación nunca se borran. + Los mensajes de esta conversación nunca se eliminan. alert message @@ -4931,7 +4936,7 @@ This is your link for group %@! Messages were deleted after you selected them. - Los mensajes han sido borrados después de seleccionarlos. + Los mensajes han sido eliminados después de seleccionarlos. alert message @@ -5893,11 +5898,21 @@ Error: %@ Privacidad para tus clientes. No comment provided by engineer. + + Privacy policy and conditions of use. + Política de privacidad y condiciones de uso. + No comment provided by engineer. + Privacy redefined Privacidad redefinida No comment provided by engineer. + + Private chats, groups and your contacts are not accessible to server operators. + Los chats privados, los grupos y tus contactos no son accesibles para los operadores de servidores. + No comment provided by engineer. + Private filenames Nombres de archivos privados @@ -6082,7 +6097,7 @@ Actívalo en ajustes de *Servidores y Redes*. Reachable chat toolbar - Barra de chat accesible + Barra de menú accesible No comment provided by engineer. @@ -6264,7 +6279,7 @@ Actívalo en ajustes de *Servidores y Redes*. Reject Rechazar reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -6501,11 +6516,6 @@ Actívalo en ajustes de *Servidores y Redes*. Revisar condiciones No comment provided by engineer. - - Review later - Revisar más tarde - No comment provided by engineer. - Revoke Revocar @@ -6555,7 +6565,7 @@ Actívalo en ajustes de *Servidores y Redes*. Save Guardar alert button - chat item action +chat item action Save (and notify contacts) @@ -7039,7 +7049,7 @@ Actívalo en ajustes de *Servidores y Redes*. Servers statistics will be reset - this cannot be undone! - Las estadísticas de los servidores serán restablecidas. ¡No podrá deshacerse! + Las estadísticas de los servidores serán restablecidas. ¡No puede deshacerse! No comment provided by engineer. @@ -7126,7 +7136,7 @@ Actívalo en ajustes de *Servidores y Redes*. Share Compartir alert action - chat item action +chat item action Share 1-time link @@ -7280,7 +7290,7 @@ Actívalo en ajustes de *Servidores y Redes*. SimpleX address and 1-time links are safe to share via any messenger. - Compartir los enlaces de un uso y las direcciones SimpleX es seguro a través de cualquier medio. + Compartir enlaces de un solo uso y direcciones SimpleX es seguro a través de cualquier medio. No comment provided by engineer. @@ -7394,7 +7404,7 @@ Actívalo en ajustes de *Servidores y Redes*. Spam Spam blocking reason - report reason +report reason Square, circle, or anything in between. @@ -7690,7 +7700,7 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. The code you scanned is not a SimpleX link QR code. - El código QR escaneado no es un enlace SimpleX. + El código QR escaneado no es un enlace de SimpleX. No comment provided by engineer. @@ -7763,11 +7773,6 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. Las mismas condiciones se aplicarán al operador **%@**. No comment provided by engineer. - - The same conditions will apply to operator(s): **%@**. - Las mismas condiciones se aplicarán a el/los operador(es) **%@**. - No comment provided by engineer. - The second preset operator in the app! ¡Segundo operador predefinido! @@ -7795,7 +7800,7 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. The text you pasted is not a SimpleX link. - El texto pegado no es un enlace SimpleX. + El texto pegado no es un enlace de SimpleX. No comment provided by engineer. @@ -7835,7 +7840,7 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted. - Todos los mensajes previos al período seleccionado serán eliminados del chat. ¡No podrá deshacerse! + Todos los mensajes previos al período seleccionado serán eliminados del chat. ¡No puede deshacerse! alert message @@ -7890,7 +7895,7 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. This message was deleted or not received yet. - El mensaje ha sido borrado o aún no se ha recibido. + El mensaje ha sido eliminado o aún no se ha recibido. No comment provided by engineer. @@ -7947,7 +7952,7 @@ Se te pedirá que completes la autenticación antes de activar esta función.
    To protect your privacy, SimpleX uses separate IDs for each of your contacts. - Para proteger tu privacidad, en lugar de los identificadores de usuario que usan el resto de plataformas, SimpleX dispone de identificadores para las colas de mensajes, independientes para cada uno de tus contactos. + Para proteger tu privacidad, SimpleX usa identificadores distintos para cada uno de tus contactos. No comment provided by engineer. @@ -8734,11 +8739,6 @@ Repeat join request? Puedes cambiar la posición de la barra desde el menú Apariencia. No comment provided by engineer. - - You can configure operators in Network & servers settings. - Puedes configurar los operadores desde Servidores y Redes. - No comment provided by engineer. - You can configure servers via settings. Puedes configurar los servidores a través de su configuración. @@ -8981,11 +8981,6 @@ Repeat connection request? Servidores ICE No comment provided by engineer. - - Your SMP servers - Servidores SMP - No comment provided by engineer. - Your SimpleX address Mi dirección SimpleX @@ -9224,7 +9219,8 @@ Repeat connection request? blocked by admin bloqueado por administrador - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -9395,7 +9391,7 @@ Repeat connection request? default (%@) predeterminado (%@) delete after time - pref value +pref value default (no) @@ -9522,21 +9518,11 @@ Repeat connection request? error No comment provided by engineer. - - event happened - evento ocurrido - No comment provided by engineer. - expired expirados No comment provided by engineer. - - for better metadata privacy. - para mejorar la privacidad de los metadatos. - No comment provided by engineer. - forwarded reenviado @@ -9746,8 +9732,8 @@ Repeat connection request? off desactivado enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -10117,7 +10103,7 @@ last received msg: %2$@
    - +
    @@ -10154,7 +10140,7 @@ last received msg: %2$@
    - +
    @@ -10176,7 +10162,7 @@ last received msg: %2$@
    - +
    @@ -10208,7 +10194,7 @@ last received msg: %2$@
    - +
    @@ -10230,7 +10216,7 @@ last received msg: %2$@
    - +
    diff --git a/apps/ios/SimpleX Localizations/es.xcloc/contents.json b/apps/ios/SimpleX Localizations/es.xcloc/contents.json index 340591e607..80cffac8d2 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/es.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "es", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index baa5fc7c48..c5393e97a1 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -2,7 +2,7 @@
    - +
    @@ -407,7 +407,7 @@ 1 day 1 päivä delete after time - time interval +time interval 1 hour @@ -423,13 +423,13 @@ 1 month 1 kuukausi delete after time - time interval +time interval 1 week 1 viikko delete after time - time interval +time interval 1 year @@ -524,8 +524,8 @@ Accept Hyväksy accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action Accept conditions @@ -545,7 +545,7 @@ Accept incognito Hyväksy tuntematon accept contact request via notification - swipe action +swipe action Accepted conditions @@ -1212,6 +1212,12 @@ Chat-profiilin mukaan (oletus) tai [yhteyden mukaan](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. + + By using SimpleX Chat you agree to: +- send only legal content in public groups. +- respect other users – no spam. + No comment provided by engineer. + Call already ended! Puhelu on jo päättynyt! @@ -1256,7 +1262,7 @@ Cancel Peruuta alert action - alert button +alert button Cancel migration @@ -1341,7 +1347,7 @@ Change self-destruct passcode Vaihda itsetuhoutuva pääsykoodi authentication reason - set passcode view +set passcode view Chat @@ -1554,14 +1560,6 @@ Conditions of use No comment provided by engineer. - - Conditions will be accepted for enabled operators after 30 days. - No comment provided by engineer. - - - Conditions will be accepted for operator(s): **%@**. - No comment provided by engineer. - Conditions will be accepted for the operator(s): **%@**. No comment provided by engineer. @@ -1579,6 +1577,10 @@ Määritä ICE-palvelimet No comment provided by engineer. + + Configure server operators + No comment provided by engineer. + Confirm Vahvista @@ -2119,7 +2121,7 @@ This is your own one-time link! Delete Poista alert action - swipe action +swipe action Delete %lld messages of members? @@ -2576,7 +2578,7 @@ This is your own one-time link! Download alert button - chat item action +chat item action Download errors @@ -2645,8 +2647,8 @@ This is your own one-time link! Salli (pidä ohitukset) No comment provided by engineer. - - Enable Flux + + Enable Flux in Network & servers settings for better metadata privacy. No comment provided by engineer. @@ -3179,7 +3181,9 @@ This is your own one-time link! Error: %@ Virhe: %@ - alert message + alert message +file error text +snd error text Error: URL is invalid @@ -5353,11 +5357,19 @@ Error: %@ Privacy for your customers. No comment provided by engineer. + + Privacy policy and conditions of use. + No comment provided by engineer. + Privacy redefined Yksityisyys uudelleen määritettynä No comment provided by engineer. + + Private chats, groups and your contacts are not accessible to server operators. + No comment provided by engineer. + Private filenames Yksityiset tiedostonimet @@ -5690,7 +5702,7 @@ Enable in *Network & servers* settings. Reject Hylkää reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -5903,10 +5915,6 @@ Enable in *Network & servers* settings. Review conditions No comment provided by engineer. - - Review later - No comment provided by engineer. - Revoke Peruuta @@ -5952,7 +5960,7 @@ Enable in *Network & servers* settings. Save Tallenna alert button - chat item action +chat item action Save (and notify contacts) @@ -6474,7 +6482,7 @@ Enable in *Network & servers* settings. Share Jaa alert action - chat item action +chat item action Share 1-time link @@ -6715,7 +6723,7 @@ Enable in *Network & servers* settings. Spam blocking reason - report reason +report reason Square, circle, or anything in between. @@ -7055,10 +7063,6 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.The same conditions will apply to operator **%@**. No comment provided by engineer. - - The same conditions will apply to operator(s): **%@**. - No comment provided by engineer. - The second preset operator in the app! No comment provided by engineer. @@ -7922,10 +7926,6 @@ Repeat join request? You can change it in Appearance settings. No comment provided by engineer. - - You can configure operators in Network & servers settings. - No comment provided by engineer. - You can configure servers via settings. No comment provided by engineer. @@ -8151,11 +8151,6 @@ Repeat connection request? ICE-palvelimesi No comment provided by engineer. - - Your SMP servers - SMP-palvelimesi - No comment provided by engineer. - Your SimpleX address SimpleX-osoitteesi @@ -8378,7 +8373,8 @@ Repeat connection request? blocked by admin - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -8545,7 +8541,7 @@ Repeat connection request? default (%@) oletusarvo (%@) delete after time - pref value +pref value default (no) @@ -8670,19 +8666,10 @@ Repeat connection request? virhe No comment provided by engineer. - - event happened - tapahtuma tapahtui - No comment provided by engineer. - expired No comment provided by engineer. - - for better metadata privacy. - No comment provided by engineer. - forwarded No comment provided by engineer. @@ -8886,8 +8873,8 @@ Repeat connection request? off pois enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -9226,7 +9213,7 @@ last received msg: %2$@
    - +
    @@ -9262,7 +9249,7 @@ last received msg: %2$@
    - +
    @@ -9284,7 +9271,7 @@ last received msg: %2$@
    - +
    @@ -9311,7 +9298,7 @@ last received msg: %2$@
    - +
    @@ -9330,7 +9317,7 @@ last received msg: %2$@
    - +
    diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/contents.json b/apps/ios/SimpleX Localizations/fi.xcloc/contents.json index 78ce40cec5..11f7a4861c 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/fi.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "fi", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index d36e7c2bd4..823491dc72 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -2,7 +2,7 @@
    - +
    @@ -439,7 +439,7 @@ 1 day 1 jour delete after time - time interval +time interval 1 hour @@ -455,13 +455,13 @@ 1 month 1 mois delete after time - time interval +time interval 1 week 1 semaine delete after time - time interval +time interval 1 year @@ -560,8 +560,8 @@ Accept Accepter accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action Accept conditions @@ -582,7 +582,7 @@ Accept incognito Accepter en incognito accept contact request via notification - swipe action +swipe action Accepted conditions @@ -1310,6 +1310,12 @@ Par profil de chat (par défaut) ou [par connexion](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. + + By using SimpleX Chat you agree to: +- send only legal content in public groups. +- respect other users – no spam. + No comment provided by engineer. + Call already ended! Appel déjà terminé ! @@ -1359,7 +1365,7 @@ Cancel Annuler alert action - alert button +alert button Cancel migration @@ -1449,7 +1455,7 @@ Change self-destruct passcode Modifier le code d'autodestruction authentication reason - set passcode view +set passcode view Chat @@ -1688,16 +1694,6 @@ Conditions d'utilisation No comment provided by engineer. - - Conditions will be accepted for enabled operators after 30 days. - Les conditions seront acceptées pour les opérateurs activés après 30 jours. - No comment provided by engineer. - - - Conditions will be accepted for operator(s): **%@**. - Les conditions seront acceptées pour le(s) opérateur(s) : **%@**. - No comment provided by engineer. - Conditions will be accepted for the operator(s): **%@**. Les conditions seront acceptées pour le(s) opérateur(s) : **%@**. @@ -1718,6 +1714,10 @@ Configurer les serveurs ICE No comment provided by engineer. + + Configure server operators + No comment provided by engineer. + Confirm Confirmer @@ -2308,7 +2308,7 @@ Il s'agit de votre propre lien unique ! Delete Supprimer alert action - swipe action +swipe action Delete %lld messages of members? @@ -2796,7 +2796,7 @@ Il s'agit de votre propre lien unique ! Download Télécharger alert button - chat item action +chat item action Download errors @@ -2873,9 +2873,8 @@ Il s'agit de votre propre lien unique ! Activer (conserver les remplacements) No comment provided by engineer. - - Enable Flux - Activer Flux + + Enable Flux in Network & servers settings for better metadata privacy. No comment provided by engineer. @@ -3443,7 +3442,9 @@ Il s'agit de votre propre lien unique ! Error: %@ Erreur : %@ - alert message + alert message +file error text +snd error text Error: URL is invalid @@ -5797,11 +5798,19 @@ Erreur : %@ Respect de la vie privée de vos clients. No comment provided by engineer. + + Privacy policy and conditions of use. + No comment provided by engineer. + Privacy redefined La vie privée redéfinie No comment provided by engineer. + + Private chats, groups and your contacts are not accessible to server operators. + No comment provided by engineer. + Private filenames Noms de fichiers privés @@ -6163,7 +6172,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Reject Rejeter reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -6390,11 +6399,6 @@ Activez-le dans les paramètres *Réseau et serveurs*. Vérifier les conditions No comment provided by engineer. - - Review later - Vérifier plus tard - No comment provided by engineer. - Revoke Révoquer @@ -6444,7 +6448,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Save Enregistrer alert button - chat item action +chat item action Save (and notify contacts) @@ -7011,7 +7015,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Share Partager alert action - chat item action +chat item action Share 1-time link @@ -7278,7 +7282,7 @@ Activez-le dans les paramètres *Réseau et serveurs*. Spam blocking reason - report reason +report reason Square, circle, or anything in between. @@ -7644,11 +7648,6 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Les mêmes conditions s'appliquent à l'opérateur **%@**. No comment provided by engineer. - - The same conditions will apply to operator(s): **%@**. - Les mêmes conditions s'appliquent à(aux) l'opérateur(s) : **%@**. - No comment provided by engineer. - The second preset operator in the app! Le deuxième opérateur prédéfini de l'application ! @@ -8608,11 +8607,6 @@ Répéter la demande d'adhésion ? Vous pouvez choisir de le modifier dans les paramètres d'apparence. No comment provided by engineer. - - You can configure operators in Network & servers settings. - Vous pouvez définir les opérateurs dans les paramètres Réseau et serveurs. - No comment provided by engineer. - You can configure servers via settings. Vous pouvez configurer les serveurs via les paramètres. @@ -8854,11 +8848,6 @@ Répéter la demande de connexion ? Vos serveurs ICE No comment provided by engineer. - - Your SMP servers - Vos serveurs SMP - No comment provided by engineer. - Your SimpleX address Votre adresse SimpleX @@ -9096,7 +9085,8 @@ Répéter la demande de connexion ? blocked by admin bloqué par l'administrateur - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -9267,7 +9257,7 @@ Répéter la demande de connexion ? default (%@) défaut (%@) delete after time - pref value +pref value default (no) @@ -9394,21 +9384,11 @@ Répéter la demande de connexion ? erreur No comment provided by engineer. - - event happened - event happened - No comment provided by engineer. - expired expiré No comment provided by engineer. - - for better metadata privacy. - pour une meilleure protection des métadonnées. - No comment provided by engineer. - forwarded transféré @@ -9617,8 +9597,8 @@ Répéter la demande de connexion ? off off enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -9985,7 +9965,7 @@ dernier message reçu : %2$@
    - +
    @@ -10022,7 +10002,7 @@ dernier message reçu : %2$@
    - +
    @@ -10044,7 +10024,7 @@ dernier message reçu : %2$@
    - +
    @@ -10076,7 +10056,7 @@ dernier message reçu : %2$@
    - +
    @@ -10098,7 +10078,7 @@ dernier message reçu : %2$@
    - +
    diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/contents.json b/apps/ios/SimpleX Localizations/fr.xcloc/contents.json index 22d271b92e..d026c874ec 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/fr.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "fr", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 7c25a74d3f..76157f29ac 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -2,7 +2,7 @@
    - +
    @@ -152,7 +152,7 @@ %d file(s) failed to download. - %d fájlt nem sikerült letölteni. + Nem sikerült letölteni %d fájlt. forward confirmation reason @@ -307,7 +307,7 @@ %u messages failed to decrypt. - %u üzenet visszafejtése sikertelen. + Nem sikerült visszafejteni %u üzenetet. No comment provided by engineer. @@ -440,7 +440,7 @@ 1 day 1 nap delete after time - time interval +time interval 1 hour @@ -456,13 +456,13 @@ 1 month 1 hónap delete after time - time interval +time interval 1 week 1 hét delete after time - time interval +time interval 1 year @@ -471,12 +471,12 @@ 1-time link - Egyszer használható meghívási hivatkozás + Egyszer használható meghívó No comment provided by engineer. 1-time link can be used *with one contact only* - share in person or via any messenger. - Az egyszer használható meghívási hivatkozás csak *egyetlen partnerrel használható* – személyesen vagy bármilyen üzenetváltón keresztül megosztható. + Az egyszer használható meghívó egy hivatkozás és *csak egyetlen partnerrel használható* – személyesen vagy bármilyen üzenetváltó-alkalmazáson keresztül megosztható. No comment provided by engineer. @@ -562,8 +562,8 @@ Accept Elfogadás accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action Accept conditions @@ -572,19 +572,19 @@ Accept connection request? - Elfogadja a kapcsolódási kérést? + Elfogadja a meghívási kérést? No comment provided by engineer. Accept contact request from %@? - Elfogadja %@ kapcsolódási kérését? + Elfogadja %@ meghívási kérését? notification body Accept incognito Elfogadás inkognitóban accept contact request via notification - swipe action +swipe action Accepted conditions @@ -703,7 +703,7 @@ Address or 1-time link? - Cím vagy egyszer használható meghívási hivatkozás? + Cím vagy egyszer használható meghívó? No comment provided by engineer. @@ -1143,7 +1143,7 @@ Auto-accept contact requests - Kapcsolatkérések automatikus elfogadása + Meghívási kérések automatikus elfogadása No comment provided by engineer. @@ -1331,6 +1331,15 @@ A csevegési profillal (alapértelmezett), vagy a [kapcsolattal] (https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BÉTA). No comment provided by engineer. + + By using SimpleX Chat you agree to: +- send only legal content in public groups. +- respect other users – no spam. + A SimpleX Chat használatával Ön elfogadja, hogy: +- csak elfogadott tartalmakat tesz közzé a nyilvános csoportokban. +- tiszteletben tartja a többi felhasználót, és nem küld kéretlen tartalmat senkinek. + No comment provided by engineer. + Call already ended! A hívás már befejeződött! @@ -1380,7 +1389,7 @@ Cancel Mégse alert action - alert button +alert button Cancel migration @@ -1471,7 +1480,7 @@ Change self-destruct passcode Önmegsemmisítő-jelkód módosítása authentication reason - set passcode view +set passcode view Chat @@ -1713,16 +1722,6 @@ Használati feltételek No comment provided by engineer. - - Conditions will be accepted for enabled operators after 30 days. - A feltételek 30 nap elteltével lesznek elfogadva az engedélyezett üzemeltetők számára. - No comment provided by engineer. - - - Conditions will be accepted for operator(s): **%@**. - A feltételek el lesznek fogadva a következő üzemeltető(k) számára: **%@**. - No comment provided by engineer. - Conditions will be accepted for the operator(s): **%@**. A feltételek el lesznek fogadva a következő üzemeltető(k) számára: **%@**. @@ -1743,6 +1742,11 @@ ICE-kiszolgálók beállítása No comment provided by engineer. + + Configure server operators + Kiszolgálóüzemeltetők beállítása + No comment provided by engineer. + Confirm Megerősítés @@ -1839,7 +1843,7 @@ Ez az Ön SimpleX-címe! Connect to yourself? This is your own one-time link! Kapcsolódik saját magához? -Ez az Ön egyszer használható meghívási hivatkozása! +Ez az Ön egyszer használható meghívója! No comment provided by engineer. @@ -1854,7 +1858,7 @@ Ez az Ön egyszer használható meghívási hivatkozása! Connect via one-time link - Kapcsolódás egyszer használható meghívási hivatkozáson keresztül + Kapcsolódás egyszer használható meghívón keresztül No comment provided by engineer. @@ -1951,7 +1955,7 @@ Ez az Ön egyszer használható meghívási hivatkozása! Connection request sent! - Kapcsolatkérés elküldve! + Meghívási kérés elküldve! No comment provided by engineer. @@ -2086,7 +2090,7 @@ Ez az Ön egyszer használható meghívási hivatkozása! Create 1-time link - Egyszer használható meghívási hivatkozás létrehozása + Egyszer használható meghívó létrehozása No comment provided by engineer. @@ -2206,7 +2210,7 @@ Ez az Ön egyszer használható meghívási hivatkozása! Customizable message shape. - Testre szabható üzenetbuborékok. + Személyre szabható üzenetbuborékok. No comment provided by engineer. @@ -2341,7 +2345,7 @@ Ez az Ön egyszer használható meghívási hivatkozása! Delete Törlés alert action - swipe action +swipe action Delete %lld messages of members? @@ -2837,7 +2841,7 @@ Ez az Ön egyszer használható meghívási hivatkozása! Download Letöltés alert button - chat item action +chat item action Download errors @@ -2914,9 +2918,8 @@ Ez az Ön egyszer használható meghívási hivatkozása! Engedélyezés (felülírások megtartásával) No comment provided by engineer. - - Enable Flux - Flux engedélyezése + + Enable Flux in Network & servers settings for better metadata privacy. No comment provided by engineer. @@ -3051,7 +3054,7 @@ Ez az Ön egyszer használható meghívási hivatkozása! Encryption re-negotiation error - Hiba a titkosítás újraegyeztetésekor + Hiba történt a titkosítás újraegyeztetésekor message decrypt error item @@ -3126,282 +3129,282 @@ Ez az Ön egyszer használható meghívási hivatkozása! Error aborting address change - Hiba a cím módosításának megszakításakor + Hiba történt a cím módosításának megszakításakor No comment provided by engineer. Error accepting conditions - Hiba a feltételek elfogadásakor + Hiba történt a feltételek elfogadásakor alert title Error accepting contact request - Hiba történt a kapcsolatkérés elfogadásakor + Hiba történt a meghívási kérés elfogadásakor No comment provided by engineer. Error adding member(s) - Hiba a tag(ok) hozzáadásakor + Hiba történt a tag(ok) hozzáadásakor No comment provided by engineer. Error adding server - Hiba a kiszolgáló hozzáadásakor + Hiba történt a kiszolgáló hozzáadásakor alert title Error changing address - Hiba a cím módosításakor + Hiba történt a cím módosításakor No comment provided by engineer. Error changing connection profile - Hiba a kapcsolati profilra való váltáskor + Hiba történt a kapcsolati profilra való váltáskor No comment provided by engineer. Error changing role - Hiba a szerepkör módosításakor + Hiba történt a szerepkör módosításakor No comment provided by engineer. Error changing setting - Hiba a beállítás módosításakor + Hiba történt a beállítás módosításakor No comment provided by engineer. Error changing to incognito! - Hiba az inkognitóprofilra való váltáskor! + Hiba történt az inkognitóprofilra való váltáskor! No comment provided by engineer. Error checking token status - Hiba a token állapotának ellenőrzésekor + Hiba történt a token állapotának ellenőrzésekor No comment provided by engineer. Error connecting to forwarding server %@. Please try later. - Hiba a(z) %@ továbbítókiszolgálóhoz való kapcsolódáskor. Próbálja meg később. + Hiba történt a(z) %@ továbbítókiszolgálóhoz való kapcsolódáskor. Próbálja meg később. No comment provided by engineer. Error creating address - Hiba a cím létrehozásakor + Hiba történt a cím létrehozásakor No comment provided by engineer. Error creating group - Hiba a csoport létrehozásakor + Hiba történt a csoport létrehozásakor No comment provided by engineer. Error creating group link - Hiba a csoporthivatkozás létrehozásakor + Hiba történt a csoporthivatkozás létrehozásakor No comment provided by engineer. Error creating list - Hiba a lista létrehozásakor + Hiba történt a lista létrehozásakor alert title Error creating member contact - Hiba a partnerrel történő kapcsolat létrehozásában + Hiba történt a partnerrel történő kapcsolat létrehozásában No comment provided by engineer. Error creating message - Hiba az üzenet létrehozásakor + Hiba történt az üzenet létrehozásakor No comment provided by engineer. Error creating profile! - Hiba a profil létrehozásakor! + Hiba történt a profil létrehozásakor! No comment provided by engineer. Error creating report - Hiba a jelentés létrehozásakor + Hiba történt a jelentés létrehozásakor No comment provided by engineer. Error decrypting file - Hiba a fájl visszafejtésekor + Hiba történt a fájl visszafejtésekor No comment provided by engineer. Error deleting chat database - Hiba a csevegési adatbázis törlésekor + Hiba történt a csevegési adatbázis törlésekor No comment provided by engineer. Error deleting chat! - Hiba a csevegés törlésekor! + Hiba történt a csevegés törlésekor! No comment provided by engineer. Error deleting connection - Hiba a kapcsolat törlésekor + Hiba történt a kapcsolat törlésekor No comment provided by engineer. Error deleting database - Hiba az adatbázis törlésekor + Hiba történt az adatbázis törlésekor No comment provided by engineer. Error deleting old database - Hiba a régi adatbázis törlésekor + Hiba történt a régi adatbázis törlésekor No comment provided by engineer. Error deleting token - Hiba a token törlésekor + Hiba történt a token törlésekor No comment provided by engineer. Error deleting user profile - Hiba a felhasználó-profil törlésekor + Hiba történt a felhasználó-profil törlésekor No comment provided by engineer. Error downloading the archive - Hiba az archívum letöltésekor + Hiba történt az archívum letöltésekor No comment provided by engineer. Error enabling delivery receipts! - Hiba a kézbesítési jelentések engedélyezésekor! + Hiba történt a kézbesítési jelentések engedélyezésekor! No comment provided by engineer. Error enabling notifications - Hiba az értesítések engedélyezésekor + Hiba történt az értesítések engedélyezésekor No comment provided by engineer. Error encrypting database - Hiba az adatbázis titkosításakor + Hiba történt az adatbázis titkosításakor No comment provided by engineer. Error exporting chat database - Hiba a csevegési adatbázis exportálásakor + Hiba történt a csevegési adatbázis exportálásakor No comment provided by engineer. Error exporting theme: %@ - Hiba a téma exportálásakor: %@ + Hiba történt a téma exportálásakor: %@ No comment provided by engineer. Error importing chat database - Hiba a csevegési adatbázis importálásakor + Hiba történt a csevegési adatbázis importálásakor No comment provided by engineer. Error joining group - Hiba a csoporthoz való csatlakozáskor + Hiba történt a csoporthoz való csatlakozáskor No comment provided by engineer. Error loading servers - Hiba a kiszolgálók betöltésekor + Hiba történt a kiszolgálók betöltésekor alert title Error migrating settings - Hiba a beállítások átköltöztetésekor + Hiba történt a beállítások átköltöztetésekor No comment provided by engineer. Error opening chat - Hiba a csevegés megnyitásakor + Hiba történt a csevegés megnyitásakor No comment provided by engineer. Error receiving file - Hiba a fájl fogadásakor + Hiba történt a fájl fogadásakor alert title Error reconnecting server - Hiba a kiszolgálóhoz való újrakapcsolódáskor + Hiba történt a kiszolgálóhoz való újrakapcsolódáskor No comment provided by engineer. Error reconnecting servers - Hiba a kiszolgálókhoz való újrakapcsolódáskor + Hiba történt a kiszolgálókhoz való újrakapcsolódáskor No comment provided by engineer. Error registering for notifications - Hiba az értesítések regisztrálásakor + Hiba történt az értesítések regisztrálásakor alert title Error removing member - Hiba a tag eltávolításakor + Hiba történt a tag eltávolításakor No comment provided by engineer. Error reordering lists - Hiba a listák újrarendezésekor + Hiba történt a listák újrarendezésekor alert title Error resetting statistics - Hiba a statisztikák visszaállításakor + Hiba történt a statisztikák visszaállításakor No comment provided by engineer. Error saving ICE servers - Hiba az ICE-kiszolgálók mentésekor + Hiba történt az ICE-kiszolgálók mentésekor No comment provided by engineer. Error saving chat list - Hiba a csevegési lista mentésekor + Hiba történt a csevegési lista mentésekor alert title Error saving group profile - Hiba a csoportprofil mentésekor + Hiba történt a csoportprofil mentésekor No comment provided by engineer. Error saving passcode - Hiba a jelkód mentésekor + Hiba történt a jelkód mentésekor No comment provided by engineer. Error saving passphrase to keychain - Hiba a jelmondat kulcstartóba történő mentésekor + Hiba történt a jelmondat kulcstartóba történő mentésekor No comment provided by engineer. Error saving servers - Hiba a kiszolgálók mentésekor + Hiba történt a kiszolgálók mentésekor alert title Error saving settings - Hiba a beállítások mentésekor + Hiba történt a beállítások mentésekor when migrating Error saving user password - Hiba a felhasználó jelszavának mentésekor + Hiba történt a felhasználó jelszavának mentésekor No comment provided by engineer. Error scanning code: %@ - Hiba a kód beolvasásakor: %@ + Hiba történt a kód beolvasásakor: %@ No comment provided by engineer. Error sending email - Hiba az e-mail küldésekor + Hiba történt az e-mail elküldésekor No comment provided by engineer. @@ -3411,7 +3414,7 @@ Ez az Ön egyszer használható meghívási hivatkozása! Error sending message - Hiba az üzenet küldésekor + Hiba történt az üzenet elküldésekor No comment provided by engineer. @@ -3421,47 +3424,47 @@ Ez az Ön egyszer használható meghívási hivatkozása! Error starting chat - Hiba a csevegés elindításakor + Hiba történt a csevegés elindításakor No comment provided by engineer. Error stopping chat - Hiba a csevegés megállításakor + Hiba történt a csevegés megállításakor No comment provided by engineer. Error switching profile - Hiba a profilváltáskor + Hiba történt a profilváltáskor No comment provided by engineer. Error switching profile! - Hiba a profilváltáskor! + Hiba történt a profilváltáskor! alertTitle Error synchronizing connection - Hiba a kapcsolat szinkronizálásakor + Hiba történt a kapcsolat szinkronizálásakor No comment provided by engineer. Error testing server connection - Hiba a kiszolgáló kapcsolatának tesztelésekor + Hiba történt a kiszolgáló kapcsolatának tesztelésekor No comment provided by engineer. Error updating group link - Hiba a csoporthivatkozás frissítésekor + Hiba történt a csoporthivatkozás frissítésekor No comment provided by engineer. Error updating message - Hiba az üzenet frissítésekor + Hiba történt az üzenet frissítésekor No comment provided by engineer. Error updating server - Hiba a kiszolgáló frissítésekor + Hiba történt a kiszolgáló frissítésekor alert title @@ -3471,17 +3474,17 @@ Ez az Ön egyszer használható meghívási hivatkozása! Error updating user privacy - Hiba a felhasználói adatvédelem frissítésekor + Hiba történt a felhasználói adatvédelem frissítésekor No comment provided by engineer. Error uploading the archive - Hiba az archívum feltöltésekor + Hiba történt az archívum feltöltésekor No comment provided by engineer. Error verifying passphrase: - Hiba a jelmondat hitelesítésekor: + Hiba történt a jelmondat hitelesítésekor: No comment provided by engineer. @@ -3492,7 +3495,9 @@ Ez az Ön egyszer használható meghívási hivatkozása! Error: %@ Hiba: %@ - alert message + alert message +file error text +snd error text Error: URL is invalid @@ -3825,7 +3830,7 @@ Ez az Ön egyszer használható meghívási hivatkozása! Forwarding server %@ failed to connect to destination server %@. Please try later. - A(z) %@ továbbítókiszolgáló nem tudott csatlakozni a(z) %@ célkiszolgálóhoz. Próbálja meg később. + A(z) %@ továbbítókiszolgáló nem tudott kapcsolódni a(z) %@ célkiszolgálóhoz. Próbálja meg később. No comment provided by engineer. @@ -4453,7 +4458,7 @@ További fejlesztések hamarosan! 3. The connection was compromised. Ez akkor fordulhat elő, ha: 1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak. -2. Az üzenet visszafejtése sikertelen volt, mert Ön, vagy a partnere régebbi adatbázis biztonsági mentést használt. +2. Nem sikerült az üzenetet visszafejteni, mert Ön, vagy a partnere régebbi adatbázis biztonsági mentést használt. 3. A kapcsolat sérült. No comment provided by engineer. @@ -4986,7 +4991,7 @@ Ez az Ön hivatkozása a(z) %@ nevű csoporthoz! Migration error: - Átköltöztetés hiba: + Átköltöztetési hiba: No comment provided by engineer. @@ -5141,7 +5146,7 @@ Ez az Ön hivatkozása a(z) %@ nevű csoporthoz! New contact request - Új kapcsolatkérés + Új meghívási kérés notification @@ -5420,7 +5425,7 @@ Ez az Ön hivatkozása a(z) %@ nevű csoporthoz! One-time invitation link - Egyszer használható meghívási hivatkozás + Egyszer használható meghívó No comment provided by engineer. @@ -5893,11 +5898,21 @@ Hiba: %@ Az Ön ügyfeleinek adatvédelme. No comment provided by engineer. + + Privacy policy and conditions of use. + Adatvédelmi szabályzat és felhasználási feltételek. + No comment provided by engineer. + Privacy redefined Adatvédelem újraértelmezve No comment provided by engineer. + + Private chats, groups and your contacts are not accessible to server operators. + A privát csevegések, a csoportok és a partnerek nem érhetők el a szerver üzemeltetői számára. + No comment provided by engineer. + Private filenames Privát fájlnevek @@ -6264,7 +6279,7 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. Reject Elutasítás reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -6273,7 +6288,7 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. Reject contact request - Kapcsolatkérés elutasítása + Meghívási kérés elutasítása No comment provided by engineer. @@ -6333,7 +6348,7 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. Repeat connection request? - Megismétli a kapcsolódási kérést? + Megismétli a meghívási kérést? No comment provided by engineer. @@ -6348,7 +6363,7 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. Repeat join request? - Megismétli a csatlakozási kérést? + Megismétli a meghívási kérést? No comment provided by engineer. @@ -6483,7 +6498,7 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. Restore database error - Hiba az adatbázis visszaállításakor + Hiba történt az adatbázis visszaállításakor No comment provided by engineer. @@ -6501,11 +6516,6 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. Feltételek felülvizsgálata No comment provided by engineer. - - Review later - Felülvizsgálat később - No comment provided by engineer. - Revoke Visszavonás @@ -6555,7 +6565,7 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. Save Mentés alert button - chat item action +chat item action Save (and notify contacts) @@ -6849,7 +6859,7 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. Send them from gallery or custom keyboards. - Küldje el őket galériából vagy egyedi billentyűzetekről. + Küldje el őket a galériából vagy az egyéni billentyűzetekről. No comment provided by engineer. @@ -6864,7 +6874,7 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. Sender may have deleted the connection request. - A küldő törölhette a kapcsolatkérést. + A küldője törölhette a meghívási kérést. No comment provided by engineer. @@ -6989,7 +6999,7 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. Server operators - Kiszolgáló-üzemeltetők + Kiszolgálóüzemeltetők No comment provided by engineer. @@ -7126,16 +7136,16 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. Share Megosztás alert action - chat item action +chat item action Share 1-time link - Egyszer használható meghívási hivatkozás megosztása + Egyszer használható meghívó megosztása No comment provided by engineer. Share 1-time link with a friend - Egyszer használható meghívási hivatkozás megosztása egy baráttal + Egyszer használható meghívó megosztása egy baráttal No comment provided by engineer. @@ -7175,7 +7185,7 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. Share this 1-time invite link - Ennek az egyszer használható meghívási hivatkozásnak a megosztása + Ennek az egyszer használható meghívónak a megosztása No comment provided by engineer. @@ -7280,12 +7290,12 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. SimpleX address and 1-time links are safe to share via any messenger. - A SimpleX-cím és az egyszer használható meghívási hivatkozás biztonságosan megosztható bármilyen üzenetváltón keresztül. + A SimpleX-cím és az egyszer használható meghívó biztonságosan megosztható bármilyen üzenetváltó-alkalmazáson keresztül. No comment provided by engineer. SimpleX address or 1-time link? - SimpleX-cím vagy egyszer használható meghívási hivatkozás? + SimpleX-cím vagy egyszer használható meghívó? No comment provided by engineer. @@ -7320,7 +7330,7 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. SimpleX one-time invitation - Egyszer használható SimpleX-meghívási hivatkozás + Egyszer használható SimpleX-meghívó simplex link type @@ -7394,7 +7404,7 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. Spam Kéretlen tartalom blocking reason - report reason +report reason Square, circle, or anything in between. @@ -7523,7 +7533,7 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. Switch chat profile for 1-time invitations. - Csevegési profilváltás az egyszer használható meghívási hivatkozásokhoz. + Csevegési profilváltás az egyszer használható meghívókhoz. No comment provided by engineer. @@ -7653,12 +7663,12 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! - Köszönet a felhasználóknak – [hozzájárulás a Weblate-en keresztül](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! + Köszönet a felhasználóknak [a Weblate-en való közreműködésért](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! No comment provided by engineer. Thanks to the users – contribute via Weblate! - Köszönet a felhasználóknak – hozzájárulás a Weblate-en! + Köszönet a felhasználóknak a Weblate-en való közreműködésért! No comment provided by engineer. @@ -7670,7 +7680,7 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. The app can notify you when you receive messages or contact requests - please open settings to enable. - Az alkalmazás értesíteni fogja, amikor üzeneteket vagy kapcsolatkéréseket kap – ezt a beállítások menüben engedélyezheti. + Az alkalmazás értesíteni fogja, amikor üzeneteket vagy meghívási kéréseket kap – ezt a beállítások menüben engedélyezheti. No comment provided by engineer. @@ -7763,11 +7773,6 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. Ugyanezek a feltételek lesznek elfogadva a következő üzemeltető számára is: **%@**. No comment provided by engineer. - - The same conditions will apply to operator(s): **%@**. - Ugyanezek a feltételek lesznek elfogadva a következő üzemeltető(k) számára is: **%@**. - No comment provided by engineer. - The second preset operator in the app! A második előre beállított üzemeltető az alkalmazásban! @@ -7880,7 +7885,7 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. This is your own one-time link! - Ez az Ön egyszer használható meghívási hivatkozása! + Ez az Ön egyszer használható meghívója! No comment provided by engineer. @@ -8679,7 +8684,7 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso You are already connecting via this one-time link! - A kapcsolódás már folyamatban van ezen az egyszer használható meghívási hivatkozáson keresztül! + A kapcsolódás már folyamatban van ezen az egyszer használható meghívón keresztül! No comment provided by engineer. @@ -8706,7 +8711,7 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso You are already joining the group! Repeat join request? A csatlakozás már folyamatban van a csoporthoz! -Megismétli a csatlakozási kérést? +Megismétli a meghívási kérést? No comment provided by engineer. @@ -8734,11 +8739,6 @@ Megismétli a csatlakozási kérést? Ezt a „Megjelenés” menüben módosíthatja. No comment provided by engineer. - - You can configure operators in Network & servers settings. - Az üzemeltetőket a „Hálózat és kiszolgálók” menüben konfigurálhatja. - No comment provided by engineer. - You can configure servers via settings. A kiszolgálókat a „Hálózat és kiszolgálók” menüben konfigurálhatja. @@ -8846,14 +8846,14 @@ Megismétli a csatlakozási kérést? You have already requested connection via this address! - Már küldött egy kapcsolatkérést ezen a címen keresztül! + Már küldött egy meghívási kérést ezen a címen keresztül! No comment provided by engineer. You have already requested connection! Repeat connection request? - Ön már küldött egy kapcsolódási kérést! -Megismétli a kapcsolódási kérést? + Ön már küldött egy meghívási kérést! +Megismétli a meghívási kérést? No comment provided by engineer. @@ -8928,7 +8928,7 @@ Megismétli a kapcsolódási kérést? You will be connected when your connection request is accepted, please wait or check later! - Akkor lesz kapcsolódva, ha a kapcsolatkérése el lesz fogadva, várjon, vagy ellenőrizze később! + Akkor lesz kapcsolódva, ha a meghívási kérése el lesz fogadva, várjon, vagy ellenőrizze később! No comment provided by engineer. @@ -8981,11 +8981,6 @@ Megismétli a kapcsolódási kérést? Saját ICE-kiszolgálók No comment provided by engineer. - - Your SMP servers - Saját SMP-kiszolgálók - No comment provided by engineer. - Your SimpleX address Profil SimpleX-címe @@ -9108,7 +9103,7 @@ Megismétli a kapcsolódási kérést? [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Hozzájárulás](https://github.com/simplex-chat/simplex-chat#contribute) + [Közreműködés](https://github.com/simplex-chat/simplex-chat#contribute) No comment provided by engineer. @@ -9224,7 +9219,8 @@ Megismétli a kapcsolódási kérést? blocked by admin letiltva az adminisztrátor által - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -9395,7 +9391,7 @@ Megismétli a kapcsolódási kérést? default (%@) alapértelmezett (%@) delete after time - pref value +pref value default (no) @@ -9522,21 +9518,11 @@ Megismétli a kapcsolódási kérést? hiba No comment provided by engineer. - - event happened - esemény történt - No comment provided by engineer. - expired lejárt No comment provided by engineer. - - for better metadata privacy. - a metaadatok jobb védelme érdekében. - No comment provided by engineer. - forwarded továbbított @@ -9584,7 +9570,7 @@ Megismétli a kapcsolódási kérést? incognito via one-time link - inkognitó egy egyszer használható meghívási hivatkozáson keresztül + inkognitó egy egyszer használható meghívón keresztül chat list item description @@ -9629,7 +9615,7 @@ Megismétli a kapcsolódási kérést? invited to connect - meghívta egy partnerét + Függőben lévő meghívó chat list item title @@ -9746,8 +9732,8 @@ Megismétli a kapcsolódási kérést? off kikapcsolva enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -9851,7 +9837,7 @@ Megismétli a kapcsolódási kérést? requested to connect - kérelmezve a kapcsolódáshoz + Függőben lévő meghívási kérelem chat list item title @@ -9990,7 +9976,7 @@ utoljára fogadott üzenet: %2$@ via one-time link - egy egyszer használható meghívási hivatkozáson keresztül + egy egyszer használható meghívón keresztül chat list item description @@ -10090,12 +10076,12 @@ utoljára fogadott üzenet: %2$@ you shared one-time link - Ön egy egyszer használható meghívási hivatkozást osztott meg + Ön egy egyszer használható meghívót osztott meg chat list item description you shared one-time link incognito - Ön egy egyszer használható meghívási hivatkozást osztott meg inkognitóban + Ön egy egyszer használható meghívót osztott meg inkognitóban chat list item description @@ -10117,7 +10103,7 @@ utoljára fogadott üzenet: %2$@
    - +
    @@ -10154,7 +10140,7 @@ utoljára fogadott üzenet: %2$@
    - +
    @@ -10176,7 +10162,7 @@ utoljára fogadott üzenet: %2$@
    - +
    @@ -10208,7 +10194,7 @@ utoljára fogadott üzenet: %2$@
    - +
    @@ -10230,7 +10216,7 @@ utoljára fogadott üzenet: %2$@
    - +
    @@ -10300,12 +10286,12 @@ utoljára fogadott üzenet: %2$@ Error preparing file - Hiba a fájl előkészítésekor + Hiba történt a fájl előkészítésekor No comment provided by engineer. Error preparing message - Hiba az üzenet előkészítésekor + Hiba történt az üzenet előkészítésekor No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/contents.json b/apps/ios/SimpleX Localizations/hu.xcloc/contents.json index 0b16198498..c07ec0f900 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/hu.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "hu", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 18ee99deda..9d6aa9f4be 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -2,7 +2,7 @@
    - +
    @@ -440,7 +440,7 @@ 1 day 1 giorno delete after time - time interval +time interval 1 hour @@ -456,13 +456,13 @@ 1 month 1 mese delete after time - time interval +time interval 1 week 1 settimana delete after time - time interval +time interval 1 year @@ -562,8 +562,8 @@ Accept Accetta accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action Accept conditions @@ -584,7 +584,7 @@ Accept incognito Accetta in incognito accept contact request via notification - swipe action +swipe action Accepted conditions @@ -1331,6 +1331,15 @@ Per profilo di chat (predefinito) o [per connessione](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. + + By using SimpleX Chat you agree to: +- send only legal content in public groups. +- respect other users – no spam. + Usando SimpleX Chat accetti di: +- inviare solo contenuto legale nei gruppi pubblici. +- rispettare gli altri utenti - niente spam. + No comment provided by engineer. + Call already ended! Chiamata già terminata! @@ -1380,7 +1389,7 @@ Cancel Annulla alert action - alert button +alert button Cancel migration @@ -1471,7 +1480,7 @@ Change self-destruct passcode Cambia codice di autodistruzione authentication reason - set passcode view +set passcode view Chat @@ -1713,16 +1722,6 @@ Condizioni d'uso No comment provided by engineer. - - Conditions will be accepted for enabled operators after 30 days. - Le condizioni verranno accettate per gli operatori attivati dopo 30 giorni. - No comment provided by engineer. - - - Conditions will be accepted for operator(s): **%@**. - Le condizioni verranno accettate per gli operatori: **%@**. - No comment provided by engineer. - Conditions will be accepted for the operator(s): **%@**. Le condizioni verranno accettate per gli operatori: **%@**. @@ -1743,6 +1742,11 @@ Configura server ICE No comment provided by engineer. + + Configure server operators + Configura gli operatori dei server + No comment provided by engineer. + Confirm Conferma @@ -2341,7 +2345,7 @@ Questo è il tuo link una tantum! Delete Elimina alert action - swipe action +swipe action Delete %lld messages of members? @@ -2837,7 +2841,7 @@ Questo è il tuo link una tantum! Download Scarica alert button - chat item action +chat item action Download errors @@ -2914,9 +2918,8 @@ Questo è il tuo link una tantum! Attiva (mantieni sostituzioni) No comment provided by engineer. - - Enable Flux - Attiva Flux + + Enable Flux in Network & servers settings for better metadata privacy. No comment provided by engineer. @@ -3492,7 +3495,9 @@ Questo è il tuo link una tantum! Error: %@ Errore: %@ - alert message + alert message +file error text +snd error text Error: URL is invalid @@ -5893,11 +5898,21 @@ Errore: %@ Privacy per i tuoi clienti. No comment provided by engineer. + + Privacy policy and conditions of use. + Informativa sulla privacy e condizioni d'uso. + No comment provided by engineer. + Privacy redefined Privacy ridefinita No comment provided by engineer. + + Private chats, groups and your contacts are not accessible to server operators. + Le chat private, i gruppi e i tuoi contatti non sono accessibili agli operatori dei server. + No comment provided by engineer. + Private filenames Nomi di file privati @@ -6264,7 +6279,7 @@ Attivalo nelle impostazioni *Rete e server*. Reject Rifiuta reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -6501,11 +6516,6 @@ Attivalo nelle impostazioni *Rete e server*. Leggi le condizioni No comment provided by engineer. - - Review later - Leggi più tardi - No comment provided by engineer. - Revoke Revoca @@ -6555,7 +6565,7 @@ Attivalo nelle impostazioni *Rete e server*. Save Salva alert button - chat item action +chat item action Save (and notify contacts) @@ -7126,7 +7136,7 @@ Attivalo nelle impostazioni *Rete e server*. Share Condividi alert action - chat item action +chat item action Share 1-time link @@ -7394,7 +7404,7 @@ Attivalo nelle impostazioni *Rete e server*. Spam Spam blocking reason - report reason +report reason Square, circle, or anything in between. @@ -7763,11 +7773,6 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Le stesse condizioni si applicheranno all'operatore **%@**. No comment provided by engineer. - - The same conditions will apply to operator(s): **%@**. - Le stesse condizioni si applicheranno agli operatori **%@**. - No comment provided by engineer. - The second preset operator in the app! Il secondo operatore preimpostato nell'app! @@ -8734,11 +8739,6 @@ Ripetere la richiesta di ingresso? Puoi cambiarlo nelle impostazioni dell'aspetto. No comment provided by engineer. - - You can configure operators in Network & servers settings. - Puoi configurare gli operatori nelle impostazioni di rete e server. - No comment provided by engineer. - You can configure servers via settings. Puoi configurare i server nelle impostazioni. @@ -8981,11 +8981,6 @@ Ripetere la richiesta di connessione? I tuoi server ICE No comment provided by engineer. - - Your SMP servers - I tuoi server SMP - No comment provided by engineer. - Your SimpleX address Il tuo indirizzo SimpleX @@ -9224,7 +9219,8 @@ Ripetere la richiesta di connessione? blocked by admin bloccato dall'amministratore - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -9395,7 +9391,7 @@ Ripetere la richiesta di connessione? default (%@) predefinito (%@) delete after time - pref value +pref value default (no) @@ -9522,21 +9518,11 @@ Ripetere la richiesta di connessione? errore No comment provided by engineer. - - event happened - evento accaduto - No comment provided by engineer. - expired scaduto No comment provided by engineer. - - for better metadata privacy. - per una migliore privacy dei metadati. - No comment provided by engineer. - forwarded inoltrato @@ -9664,7 +9650,7 @@ Ripetere la richiesta di connessione? member %1$@ changed to %2$@ - membro %1$@ cambiato in %2$@ + il membro %1$@ è diventato %2$@ profile update event chat item @@ -9746,8 +9732,8 @@ Ripetere la richiesta di connessione? off off enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -10117,7 +10103,7 @@ ultimo msg ricevuto: %2$@
    - +
    @@ -10154,7 +10140,7 @@ ultimo msg ricevuto: %2$@
    - +
    @@ -10176,7 +10162,7 @@ ultimo msg ricevuto: %2$@
    - +
    @@ -10208,7 +10194,7 @@ ultimo msg ricevuto: %2$@
    - +
    @@ -10230,7 +10216,7 @@ ultimo msg ricevuto: %2$@
    - +
    diff --git a/apps/ios/SimpleX Localizations/it.xcloc/contents.json b/apps/ios/SimpleX Localizations/it.xcloc/contents.json index 13870ab8dd..a42f254bd9 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/it.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "it", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index fdca2d5aca..8e7c0ae206 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -2,7 +2,7 @@
    - +
    @@ -439,7 +439,7 @@ 1 day 1日 delete after time - time interval +time interval 1 hour @@ -455,13 +455,13 @@ 1 month 1ヶ月 delete after time - time interval +time interval 1 week 1週間 delete after time - time interval +time interval 1 year @@ -558,8 +558,8 @@ Accept 承諾 accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action Accept conditions @@ -579,7 +579,7 @@ Accept incognito シークレットモードで承諾 accept contact request via notification - swipe action +swipe action Accepted conditions @@ -1262,6 +1262,12 @@ チャット プロファイル経由 (デフォルト) または [接続経由](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. + + By using SimpleX Chat you agree to: +- send only legal content in public groups. +- respect other users – no spam. + No comment provided by engineer. + Call already ended! 通話は既に終了してます! @@ -1306,7 +1312,7 @@ Cancel 中止 alert action - alert button +alert button Cancel migration @@ -1391,7 +1397,7 @@ Change self-destruct passcode 自己破壊パスコードを変更する authentication reason - set passcode view +set passcode view Chat @@ -1610,14 +1616,6 @@ Conditions of use No comment provided by engineer. - - Conditions will be accepted for enabled operators after 30 days. - No comment provided by engineer. - - - Conditions will be accepted for operator(s): **%@**. - No comment provided by engineer. - Conditions will be accepted for the operator(s): **%@**. No comment provided by engineer. @@ -1635,6 +1633,10 @@ ICEサーバを設定 No comment provided by engineer. + + Configure server operators + No comment provided by engineer. + Confirm 確認 @@ -2189,7 +2191,7 @@ This is your own one-time link! Delete 削除 alert action - swipe action +swipe action Delete %lld messages of members? @@ -2648,7 +2650,7 @@ This is your own one-time link! Download alert button - chat item action +chat item action Download errors @@ -2717,8 +2719,8 @@ This is your own one-time link! 有効にする(設定の優先を維持) No comment provided by engineer. - - Enable Flux + + Enable Flux in Network & servers settings for better metadata privacy. No comment provided by engineer. @@ -3252,7 +3254,9 @@ This is your own one-time link! Error: %@ エラー : %@ - alert message + alert message +file error text +snd error text Error: URL is invalid @@ -5430,11 +5434,19 @@ Error: %@ Privacy for your customers. No comment provided by engineer. + + Privacy policy and conditions of use. + No comment provided by engineer. + Privacy redefined プライバシーの基準を新境地に No comment provided by engineer. + + Private chats, groups and your contacts are not accessible to server operators. + No comment provided by engineer. + Private filenames プライベートなファイル名 @@ -5767,7 +5779,7 @@ Enable in *Network & servers* settings. Reject 拒否 reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -5980,10 +5992,6 @@ Enable in *Network & servers* settings. Review conditions No comment provided by engineer. - - Review later - No comment provided by engineer. - Revoke 取り消す @@ -6029,7 +6037,7 @@ Enable in *Network & servers* settings. Save 保存 alert button - chat item action +chat item action Save (and notify contacts) @@ -6544,7 +6552,7 @@ Enable in *Network & servers* settings. Share 共有する alert action - chat item action +chat item action Share 1-time link @@ -6786,7 +6794,7 @@ Enable in *Network & servers* settings. Spam blocking reason - report reason +report reason Square, circle, or anything in between. @@ -7126,10 +7134,6 @@ It can happen because of some bug or when the connection is compromised.The same conditions will apply to operator **%@**. No comment provided by engineer. - - The same conditions will apply to operator(s): **%@**. - No comment provided by engineer. - The second preset operator in the app! No comment provided by engineer. @@ -7992,10 +7996,6 @@ Repeat join request? You can change it in Appearance settings. No comment provided by engineer. - - You can configure operators in Network & servers settings. - No comment provided by engineer. - You can configure servers via settings. No comment provided by engineer. @@ -8222,11 +8222,6 @@ Repeat connection request? あなたのICEサーバ No comment provided by engineer. - - Your SMP servers - あなたのSMPサーバ - No comment provided by engineer. - Your SimpleX address あなたのSimpleXアドレス @@ -8449,7 +8444,8 @@ Repeat connection request? blocked by admin - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -8616,7 +8612,7 @@ Repeat connection request? default (%@) デフォルト (%@) delete after time - pref value +pref value default (no) @@ -8741,19 +8737,10 @@ Repeat connection request? エラー No comment provided by engineer. - - event happened - イベント発生 - No comment provided by engineer. - expired No comment provided by engineer. - - for better metadata privacy. - No comment provided by engineer. - forwarded No comment provided by engineer. @@ -8957,8 +8944,8 @@ Repeat connection request? off オフ enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -9297,7 +9284,7 @@ last received msg: %2$@
    - +
    @@ -9333,7 +9320,7 @@ last received msg: %2$@
    - +
    @@ -9355,7 +9342,7 @@ last received msg: %2$@
    - +
    @@ -9382,7 +9369,7 @@ last received msg: %2$@
    - +
    @@ -9401,7 +9388,7 @@ last received msg: %2$@
    - +
    diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/contents.json b/apps/ios/SimpleX Localizations/ja.xcloc/contents.json index 604a21be97..ce6052fc44 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/ja.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "ja", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file diff --git a/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff b/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff index d643600bc9..e35732f046 100644 --- a/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff +++ b/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff @@ -36,7 +36,7 @@ !1 colored! - !1 색상! + !1 색상 적용됨! No comment provided by engineer. @@ -166,6 +166,7 @@ %llds + No comment provided by engineer. @@ -188,7 +189,7 @@ **More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata. - **비공개**: 매 20분마다 새 메시지를 확인합니다. 푸시 서버에는 장치 토큰만 공유됩니다. 연락처 수나 메세지 메타데이터가 표시되지 않습니다. + **비공개**: 20분마다 새로운 메시지를 확인합니다. 푸시 서버에는 장치 토큰만 공유됩니다. 연락처 수나 메세지 메타데이터가 표시되지 않습니다. No comment provided by engineer. @@ -231,6 +232,7 @@ \*bold* + No comment provided by engineer. @@ -923,46 +925,58 @@ 현재 지원되는 최대 파일 크기는 %@입니다. No comment provided by engineer. - + Dark + 다크 No comment provided by engineer. - + Database ID + 데이터베이스 아이디 No comment provided by engineer. - + Database encrypted! + 데이터베이스 암호화됨! No comment provided by engineer. - + Database encryption passphrase will be updated and stored in the keychain. + 데이터베이스 암호화 키가 키체인에 저장됩니다. + No comment provided by engineer. - + Database encryption passphrase will be updated. + 데이터베이스 암호화 키가 업데이트됩니다. + No comment provided by engineer. - + Database error + 데이터베이스 오류 No comment provided by engineer. - + Database is encrypted using a random passphrase, you can change it. + 데이터베이스는 임의의 암호를 사용하여 암호화되므로 변경할 수 있습니다. No comment provided by engineer. - + Database is encrypted using a random passphrase. Please change it before exporting. + 데이터베이스는 임의의 암호를 사용하여 암호화됩니다. 내보내기 전에 변경하십시오. No comment provided by engineer. - + Database passphrase + 데이터베이스 암호화 키 No comment provided by engineer. - + Database passphrase & export + 데이터베이스 암호화 키 & 내보내기 No comment provided by engineer. @@ -4941,6 +4955,172 @@ This is your own SimpleX address! Customizable message shape. 사용자 지정 가능한 메세지 형태. + + %d seconds(s) + %d 초 + + + 1 year + 1년 + + + Add list + 리스트 추가 + + + Add to list + 리스트에 추가 + + + All + 모두 + + + Allow to report messsages to moderators. + 메시지를 신고하는것을 허용합니다. + + + Another reason + 다른 이유 + + + App group: + 앱 그룹: + + + Archive + 아카이브 + + + Archive report + 신고 아카이브 + + + Archive report? + 신고를 아카이브할까요? + + + Archive reports + 신고 아카이브 + + + Ask + 묻기 + + + Clear group? + 그룹을 비울까요? + + + Clear or delete group? + 그룹을 비우거나 삭제할까요? + + + Community guidelines violation + 커뮤니티 지침 위반 + + + Connection blocked + 연결 차단됨 + + + Connection is blocked by server operator: +%@ + 서버 관리자에 의해 연결이 차단되었습니다: +%@ + + + Connection not ready. + 연결 준비되지 않음. + + + Connection requires encryption renegotiation. + 연결에는 암호화 재협상이 필요합니다. + + + Content violates conditions of use + 내용은 사용 규정을 위반합니다 + + + Create list + 리스트 추가 + + + Database ID: %d + 데이터베이스 아이디: %d + + + Database IDs and Transport isolation option. + 데이터베이스 ID 및 전송 격리 옵션. + + + Database downgrade + 데이터베이스 다운그레이드 + + + Better groups performance + 더 나은 그룹 성능 + + + Confirmed + 확인함 + + + Active + 활성화됨 + + + Archive all reports? + 모든 신고를 아카이브할까요? + + + Businesses + 비즈니스 + + + Better privacy and security + 더 나은 프라이버시 및 보안 + + + Change automatic message deletion? + 자동 메시지 삭제를 변경할까요? + + + All chats will be removed from the list %@, and the list deleted. + 모든 채팅은 %@ 리스트에서 제거되고 리스트는 삭제됩니다. + + + All reports will be archived for you. + 모든 보고서는 사용자를 위해 보관됩니다. + + + Accent + 강조 + + + Archive %lld reports? + %lld 신고를 아카이브할까요? + + + - connect to [directory service](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)! +- delivery receipts (up to 20 members). +- faster and more stable. + - [경로 서비스](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) 에 연결중! +- 전달 확인 (최대 20 명의 멤버). +- 더 빠르고 안정적입니다. + + + All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + 모든 메시지와 파일은 **종간단 암호화 (E2EE)**되며, 개인 메시지는 양자 보안이 적용됩니다. + + + Customize theme + 테마 사용자 지정 + + + Dark mode colors + 다크 모드 색상들 +
    diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index d8d0b3c712..2ab0c75ff1 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -2,7 +2,7 @@
    - +
    @@ -440,7 +440,7 @@ 1 day 1 dag delete after time - time interval +time interval 1 hour @@ -456,13 +456,13 @@ 1 month 1 maand delete after time - time interval +time interval 1 week 1 week delete after time - time interval +time interval 1 year @@ -562,8 +562,8 @@ Accept Accepteer accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action Accept conditions @@ -584,7 +584,7 @@ Accept incognito Accepteer incognito accept contact request via notification - swipe action +swipe action Accepted conditions @@ -1331,6 +1331,12 @@ Via chatprofiel (standaard) of [via verbinding](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. + + By using SimpleX Chat you agree to: +- send only legal content in public groups. +- respect other users – no spam. + No comment provided by engineer. + Call already ended! Oproep al beëindigd! @@ -1380,7 +1386,7 @@ Cancel Annuleren alert action - alert button +alert button Cancel migration @@ -1471,7 +1477,7 @@ Change self-destruct passcode Zelfvernietigings code wijzigen authentication reason - set passcode view +set passcode view Chat @@ -1713,16 +1719,6 @@ Gebruiksvoorwaarden No comment provided by engineer. - - Conditions will be accepted for enabled operators after 30 days. - Voor ingeschakelde operators worden de voorwaarden na 30 dagen geaccepteerd. - No comment provided by engineer. - - - Conditions will be accepted for operator(s): **%@**. - Voorwaarden worden geaccepteerd voor operator(s): **%@**. - No comment provided by engineer. - Conditions will be accepted for the operator(s): **%@**. Voorwaarden worden geaccepteerd voor de operator(s): **%@**. @@ -1743,6 +1739,10 @@ ICE servers configureren No comment provided by engineer. + + Configure server operators + No comment provided by engineer. + Confirm Bevestigen @@ -2341,7 +2341,7 @@ Dit is uw eigen eenmalige link! Delete Verwijderen alert action - swipe action +swipe action Delete %lld messages of members? @@ -2815,7 +2815,7 @@ Dit is uw eigen eenmalige link! Don't miss important messages. - ‐Mis geen belangrijke berichten. + Mis geen belangrijke berichten. No comment provided by engineer. @@ -2837,7 +2837,7 @@ Dit is uw eigen eenmalige link! Download Downloaden alert button - chat item action +chat item action Download errors @@ -2914,9 +2914,8 @@ Dit is uw eigen eenmalige link! Inschakelen (overschrijvingen behouden) No comment provided by engineer. - - Enable Flux - Flux inschakelen + + Enable Flux in Network & servers settings for better metadata privacy. No comment provided by engineer. @@ -3221,7 +3220,7 @@ Dit is uw eigen eenmalige link! Error creating report - Fout bij maken van rapport + Fout bij het rapporteren No comment provided by engineer. @@ -3492,7 +3491,9 @@ Dit is uw eigen eenmalige link! Error: %@ Fout: %@ - alert message + alert message +file error text +snd error text Error: URL is invalid @@ -5893,11 +5894,19 @@ Fout: %@ Privacy voor uw klanten. No comment provided by engineer. + + Privacy policy and conditions of use. + No comment provided by engineer. + Privacy redefined Privacy opnieuw gedefinieerd No comment provided by engineer. + + Private chats, groups and your contacts are not accessible to server operators. + No comment provided by engineer. + Private filenames Privé bestandsnamen @@ -6264,7 +6273,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Reject Afwijzen reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -6363,7 +6372,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Report - Rapport + rapporteren chat item action @@ -6398,7 +6407,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Report: %@ - Rapport: %@ + rapporteer: %@ report in notification @@ -6501,11 +6510,6 @@ Schakel dit in in *Netwerk en servers*-instellingen. Voorwaarden bekijken No comment provided by engineer. - - Review later - Later beoordelen - No comment provided by engineer. - Revoke Intrekken @@ -6555,7 +6559,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Save Opslaan alert button - chat item action +chat item action Save (and notify contacts) @@ -6834,7 +6838,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Send private reports - Verstuur rapporten privé + Rapporteer privé No comment provided by engineer. @@ -7126,7 +7130,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Share Deel alert action - chat item action +chat item action Share 1-time link @@ -7394,7 +7398,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Spam Spam blocking reason - report reason +report reason Square, circle, or anything in between. @@ -7763,11 +7767,6 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. Dezelfde voorwaarden gelden voor operator **%@**. No comment provided by engineer. - - The same conditions will apply to operator(s): **%@**. - Dezelfde voorwaarden gelden voor operator(s): **%@**. - No comment provided by engineer. - The second preset operator in the app! De tweede vooraf ingestelde operator in de app! @@ -8209,6 +8208,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Updated conditions + Bijgewerkte voorwaarden No comment provided by engineer. @@ -8733,11 +8733,6 @@ Deelnameverzoek herhalen? U kunt dit wijzigen in de instellingen onder uiterlijk. No comment provided by engineer. - - You can configure operators in Network & servers settings. - U kunt operators configureren in Netwerk- en serverinstellingen. - No comment provided by engineer. - You can configure servers via settings. U kunt servers configureren via instellingen. @@ -8980,11 +8975,6 @@ Verbindingsverzoek herhalen? Uw ICE servers No comment provided by engineer. - - Your SMP servers - Uw SMP servers - No comment provided by engineer. - Your SimpleX address Uw SimpleX adres @@ -9223,7 +9213,8 @@ Verbindingsverzoek herhalen? blocked by admin geblokkeerd door beheerder - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -9394,7 +9385,7 @@ Verbindingsverzoek herhalen? default (%@) standaard (%@) delete after time - pref value +pref value default (no) @@ -9521,21 +9512,11 @@ Verbindingsverzoek herhalen? fout No comment provided by engineer. - - event happened - gebeurtenis gebeurd - No comment provided by engineer. - expired verlopen No comment provided by engineer. - - for better metadata privacy. - voor betere privacy van metagegevens. - No comment provided by engineer. - forwarded doorgestuurd @@ -9745,8 +9726,8 @@ Verbindingsverzoek herhalen? off uit enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -9790,10 +9771,12 @@ Verbindingsverzoek herhalen? pending + In behandeling No comment provided by engineer. pending approval + in afwachting van goedkeuring No comment provided by engineer. @@ -9813,6 +9796,7 @@ Verbindingsverzoek herhalen? rejected + afgewezen No comment provided by engineer. @@ -10113,7 +10097,7 @@ laatst ontvangen bericht: %2$@
    - +
    @@ -10150,7 +10134,7 @@ laatst ontvangen bericht: %2$@
    - +
    @@ -10172,7 +10156,7 @@ laatst ontvangen bericht: %2$@
    - +
    @@ -10204,7 +10188,7 @@ laatst ontvangen bericht: %2$@
    - +
    @@ -10226,7 +10210,7 @@ laatst ontvangen bericht: %2$@
    - +
    diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/contents.json b/apps/ios/SimpleX Localizations/nl.xcloc/contents.json index 4c631c367e..4b8d468de2 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/nl.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "nl", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 6a2d6e0d83..dce57ccc9e 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -2,7 +2,7 @@
    - +
    @@ -440,7 +440,7 @@ 1 day 1 dzień delete after time - time interval +time interval 1 hour @@ -456,13 +456,13 @@ 1 month 1 miesiąc delete after time - time interval +time interval 1 week 1 tydzień delete after time - time interval +time interval 1 year @@ -562,8 +562,8 @@ Accept Akceptuj accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action Accept conditions @@ -584,7 +584,7 @@ Accept incognito Akceptuj incognito accept contact request via notification - swipe action +swipe action Accepted conditions @@ -1329,6 +1329,12 @@ Według profilu czatu (domyślnie) lub [według połączenia](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. + + By using SimpleX Chat you agree to: +- send only legal content in public groups. +- respect other users – no spam. + No comment provided by engineer. + Call already ended! Połączenie już zakończone! @@ -1378,7 +1384,7 @@ Cancel Anuluj alert action - alert button +alert button Cancel migration @@ -1468,7 +1474,7 @@ Change self-destruct passcode Zmień pin samozniszczenia authentication reason - set passcode view +set passcode view Chat @@ -1707,15 +1713,6 @@ Warunki użytkowania No comment provided by engineer. - - Conditions will be accepted for enabled operators after 30 days. - Warunki zostaną zaakceptowane dla aktywowanych operatorów po 30 dniach. - No comment provided by engineer. - - - Conditions will be accepted for operator(s): **%@**. - No comment provided by engineer. - Conditions will be accepted for the operator(s): **%@**. No comment provided by engineer. @@ -1733,6 +1730,10 @@ Skonfiguruj serwery ICE No comment provided by engineer. + + Configure server operators + No comment provided by engineer. + Confirm Potwierdź @@ -2319,7 +2320,7 @@ To jest twój jednorazowy link! Delete Usuń alert action - swipe action +swipe action Delete %lld messages of members? @@ -2802,7 +2803,7 @@ To jest twój jednorazowy link! Download Pobierz alert button - chat item action +chat item action Download errors @@ -2878,8 +2879,8 @@ To jest twój jednorazowy link! Włącz (zachowaj nadpisania) No comment provided by engineer. - - Enable Flux + + Enable Flux in Network & servers settings for better metadata privacy. No comment provided by engineer. @@ -3442,7 +3443,9 @@ To jest twój jednorazowy link! Error: %@ Błąd: %@ - alert message + alert message +file error text +snd error text Error: URL is invalid @@ -5760,11 +5763,19 @@ Błąd: %@ Privacy for your customers. No comment provided by engineer. + + Privacy policy and conditions of use. + No comment provided by engineer. + Privacy redefined Redefinicja prywatności No comment provided by engineer. + + Private chats, groups and your contacts are not accessible to server operators. + No comment provided by engineer. + Private filenames Prywatne nazwy plików @@ -6126,7 +6137,7 @@ Włącz w ustawianiach *Sieć i serwery* . Reject Odrzuć reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -6352,10 +6363,6 @@ Włącz w ustawianiach *Sieć i serwery* . Review conditions No comment provided by engineer. - - Review later - No comment provided by engineer. - Revoke Odwołaj @@ -6405,7 +6412,7 @@ Włącz w ustawianiach *Sieć i serwery* . Save Zapisz alert button - chat item action +chat item action Save (and notify contacts) @@ -6968,7 +6975,7 @@ Włącz w ustawianiach *Sieć i serwery* . Share Udostępnij alert action - chat item action +chat item action Share 1-time link @@ -7226,7 +7233,7 @@ Włącz w ustawianiach *Sieć i serwery* . Spam blocking reason - report reason +report reason Square, circle, or anything in between. @@ -7586,10 +7593,6 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom The same conditions will apply to operator **%@**. No comment provided by engineer. - - The same conditions will apply to operator(s): **%@**. - No comment provided by engineer. - The second preset operator in the app! No comment provided by engineer. @@ -8533,10 +8536,6 @@ Powtórzyć prośbę dołączenia? Możesz to zmienić w ustawieniach wyglądu. No comment provided by engineer. - - You can configure operators in Network & servers settings. - No comment provided by engineer. - You can configure servers via settings. No comment provided by engineer. @@ -8775,11 +8774,6 @@ Powtórzyć prośbę połączenia? Twoje serwery ICE No comment provided by engineer. - - Your SMP servers - Twoje serwery SMP - No comment provided by engineer. - Your SimpleX address Twój adres SimpleX @@ -9016,7 +9010,8 @@ Powtórzyć prośbę połączenia? blocked by admin zablokowany przez admina - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -9187,7 +9182,7 @@ Powtórzyć prośbę połączenia? default (%@) domyślne (%@) delete after time - pref value +pref value default (no) @@ -9314,20 +9309,11 @@ Powtórzyć prośbę połączenia? błąd No comment provided by engineer. - - event happened - nowe wydarzenie - No comment provided by engineer. - expired wygasły No comment provided by engineer. - - for better metadata privacy. - No comment provided by engineer. - forwarded przekazane dalej @@ -9536,8 +9522,8 @@ Powtórzyć prośbę połączenia? off wyłączony enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -9903,7 +9889,7 @@ ostatnia otrzymana wiadomość: %2$@
    - +
    @@ -9940,7 +9926,7 @@ ostatnia otrzymana wiadomość: %2$@
    - +
    @@ -9962,7 +9948,7 @@ ostatnia otrzymana wiadomość: %2$@
    - +
    @@ -9990,7 +9976,7 @@ ostatnia otrzymana wiadomość: %2$@
    - +
    @@ -10012,7 +9998,7 @@ ostatnia otrzymana wiadomość: %2$@
    - +
    diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/contents.json b/apps/ios/SimpleX Localizations/pl.xcloc/contents.json index 0074d85662..c79fba1c1e 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/pl.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "pl", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 0e14a85c19..09e0400ec9 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -2,7 +2,7 @@
    - +
    @@ -127,12 +127,10 @@ %@, %@ and %lld members - %@, %@ и %lld членов группы No comment provided by engineer. %@, %@ and %lld other members connected - %@, %@ и %lld других членов соединены No comment provided by engineer. @@ -232,7 +230,6 @@ %lld members - Членов группы: %lld No comment provided by engineer. @@ -440,7 +437,7 @@ 1 day 1 день delete after time - time interval +time interval 1 hour @@ -456,13 +453,13 @@ 1 month 1 месяц delete after time - time interval +time interval 1 week 1 неделю delete after time - time interval +time interval 1 year @@ -524,8 +521,6 @@ A separate TCP connection will be used **for each contact and group member**. **Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail. - Отдельное TCP-соединение (и авторизация SOCKS) будет использоваться **для каждого контакта и члена группы**. -**Обратите внимание**: если у Вас много контактов, потребление батареи и трафика может быть значительно выше, и некоторые соединения могут не работать. No comment provided by engineer. @@ -562,8 +557,8 @@ Accept Принять accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action Accept conditions @@ -584,7 +579,7 @@ Accept incognito Принять инкогнито accept contact request via notification - swipe action +swipe action Accepted conditions @@ -713,7 +708,6 @@ Admins can block a member for all. - Админы могут заблокировать члена группы. No comment provided by engineer. @@ -763,7 +757,6 @@ All group members will remain connected. - Все члены группы, которые соединились через эту ссылку, останутся в группе. No comment provided by engineer. @@ -853,7 +846,6 @@ Allow sending direct messages to members. - Разрешить посылать прямые сообщения членам группы. No comment provided by engineer. @@ -1248,22 +1240,18 @@ Block group members - Блокируйте членов группы No comment provided by engineer. Block member - Заблокировать члена группы No comment provided by engineer. Block member for all? - Заблокировать члена для всех? No comment provided by engineer. Block member? - Заблокировать члена группы? No comment provided by engineer. @@ -1331,6 +1319,15 @@ По профилю чата или [по соединению](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (БЕТА). No comment provided by engineer. + + By using SimpleX Chat you agree to: +- send only legal content in public groups. +- respect other users – no spam. + Используя SimpleX Chat, Вы согласны: +- отправлять только законные сообщения в публичных группах. +- уважать других пользователей – не отправлять спам. + No comment provided by engineer. + Call already ended! Звонок уже завершен! @@ -1358,7 +1355,6 @@ Can't call member - Не удается позвонить члену группы No comment provided by engineer. @@ -1373,14 +1369,13 @@ Can't message member - Не удается написать члену группы No comment provided by engineer. Cancel Отменить alert action - alert button +alert button Cancel migration @@ -1439,7 +1434,6 @@ Change member role? - Поменять роль члена группы? No comment provided by engineer. @@ -1471,7 +1465,7 @@ Change self-destruct passcode Изменить код самоуничтожения authentication reason - set passcode view +set passcode view Chat @@ -1713,16 +1707,6 @@ Условия использования No comment provided by engineer. - - Conditions will be accepted for enabled operators after 30 days. - Условия будут приняты для включенных операторов через 30 дней. - No comment provided by engineer. - - - Conditions will be accepted for operator(s): **%@**. - Условия будут приняты для оператора(ов): **%@**. - No comment provided by engineer. - Conditions will be accepted for the operator(s): **%@**. Условия будут приняты для оператора(ов): **%@**. @@ -1743,6 +1727,11 @@ Настройка ICE серверов No comment provided by engineer. + + Configure server operators + Настроить операторов серверов + No comment provided by engineer. + Confirm Подтвердить @@ -2341,11 +2330,10 @@ This is your own one-time link! Delete Удалить alert action - swipe action +swipe action Delete %lld messages of members? - Удалить %lld сообщений членов группы? No comment provided by engineer. @@ -2680,12 +2668,10 @@ This is your own one-time link! Direct messages between members are prohibited in this chat. - Прямые сообщения между членами запрещены в этом разговоре. No comment provided by engineer. Direct messages between members are prohibited. - Прямые сообщения между членами группы запрещены. No comment provided by engineer. @@ -2790,7 +2776,6 @@ This is your own one-time link! Do not send history to new members. - Не отправлять историю новым членам. No comment provided by engineer. @@ -2837,7 +2822,7 @@ This is your own one-time link! Download Загрузить alert button - chat item action +chat item action Download errors @@ -2914,9 +2899,8 @@ This is your own one-time link! Включить (кроме исключений) No comment provided by engineer. - - Enable Flux - Включить Flux + + Enable Flux in Network & servers settings for better metadata privacy. No comment provided by engineer. @@ -3141,7 +3125,6 @@ This is your own one-time link! Error adding member(s) - Ошибка при добавлении членов группы No comment provided by engineer. @@ -3206,7 +3189,6 @@ This is your own one-time link! Error creating member contact - Ошибка создания контакта с членом группы No comment provided by engineer. @@ -3341,7 +3323,6 @@ This is your own one-time link! Error removing member - Ошибка при удалении члена группы No comment provided by engineer. @@ -3406,7 +3387,6 @@ This is your own one-time link! Error sending member contact invitation - Ошибка отправки приглашения члену группы No comment provided by engineer. @@ -3492,7 +3472,9 @@ This is your own one-time link! Error: %@ Ошибка: %@ - alert message + alert message +file error text +snd error text Error: URL is invalid @@ -3740,7 +3722,6 @@ This is your own one-time link! Fix not supported by group member - Починка не поддерживается членом группы No comment provided by engineer. @@ -3874,7 +3855,6 @@ Error: %2$@ Fully decentralized – visible only to members. - Группа полностью децентрализована – она видна только членам. No comment provided by engineer. @@ -3984,7 +3964,6 @@ Error: %2$@ Group profile is stored on members' devices, not on the servers. - Профиль группы хранится на устройствах членов, а не на серверах. No comment provided by engineer. @@ -3994,7 +3973,6 @@ Error: %2$@ Group will be deleted for all members - this cannot be undone! - Группа будет удалена для всех членов - это действие нельзя отменить! No comment provided by engineer. @@ -4059,7 +4037,6 @@ Error: %2$@ History is not sent to new members. - История не отправляется новым членам. No comment provided by engineer. @@ -4407,7 +4384,6 @@ More improvements are coming soon! Invite members - Пригласить членов группы No comment provided by engineer. @@ -4720,12 +4696,10 @@ This is your link for group %@! Member - Член группы No comment provided by engineer. Member inactive - Член неактивен item status text @@ -4740,67 +4714,54 @@ This is your link for group %@! Member role will be changed to "%@". All group members will be notified. - Роль члена группы будет изменена на "%@". Все члены группы получат сообщение. No comment provided by engineer. Member role will be changed to "%@". The member will receive a new invitation. - Роль члена группы будет изменена на "%@". Будет отправлено новое приглашение. No comment provided by engineer. Member will be removed from chat - this cannot be undone! - Член будет удален из разговора - это действие нельзя отменить! No comment provided by engineer. Member will be removed from group - this cannot be undone! - Член группы будет удален - это действие нельзя отменить! No comment provided by engineer. Members can add message reactions. - Члены группы могут добавлять реакции на сообщения. No comment provided by engineer. Members can irreversibly delete sent messages. (24 hours) - Члены группы могут необратимо удалять отправленные сообщения. (24 часа) No comment provided by engineer. Members can report messsages to moderators. - Члены группы могут пожаловаться модераторам. No comment provided by engineer. Members can send SimpleX links. - Члены группы могут отправлять ссылки SimpleX. No comment provided by engineer. Members can send direct messages. - Члены группы могут посылать прямые сообщения. No comment provided by engineer. Members can send disappearing messages. - Члены группы могут посылать исчезающие сообщения. No comment provided by engineer. Members can send files and media. - Члены группы могут слать файлы и медиа. No comment provided by engineer. Members can send voice messages. - Члены группы могут отправлять голосовые сообщения. No comment provided by engineer. Mention members 👋 - Упоминайте членов группы 👋 No comment provided by engineer. @@ -4835,7 +4796,6 @@ This is your link for group %@! Message may be delivered later if member becomes active. - Сообщение может быть доставлено позже, если член группы станет активным. item status description @@ -5175,7 +5135,6 @@ This is your link for group %@! New member role - Роль члена группы No comment provided by engineer. @@ -5392,9 +5351,6 @@ This is your link for group %@! Now admins can: - delete members' messages. - disable members ("observer" role) - Теперь админы могут: -- удалять сообщения членов. -- приостанавливать членов (роль "наблюдатель") No comment provided by engineer. @@ -5695,7 +5651,6 @@ Requires compatible VPN. Past member %@ - Бывший член %@ past/unknown group member @@ -5892,11 +5847,21 @@ Error: %@ Конфиденциальность для ваших покупателей. No comment provided by engineer. + + Privacy policy and conditions of use. + Политика конфиденциальности и условия использования. + No comment provided by engineer. + Privacy redefined Более конфиденциальный No comment provided by engineer. + + Private chats, groups and your contacts are not accessible to server operators. + Частные разговоры, группы и Ваши контакты недоступны для операторов серверов. + No comment provided by engineer. + Private filenames Защищенные имена файлов @@ -5994,7 +5959,6 @@ Error: %@ Prohibit sending direct messages to members. - Запретить посылать прямые сообщения членам группы. No comment provided by engineer. @@ -6263,7 +6227,7 @@ Enable in *Network & servers* settings. Reject Отклонить reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -6302,12 +6266,10 @@ Enable in *Network & servers* settings. Remove member - Удалить члена группы No comment provided by engineer. Remove member? - Удалить члена группы? No comment provided by engineer. @@ -6500,11 +6462,6 @@ Enable in *Network & servers* settings. Посмотреть условия No comment provided by engineer. - - Review later - Посмотреть позже - No comment provided by engineer. - Revoke Отозвать @@ -6554,7 +6511,7 @@ Enable in *Network & servers* settings. Save Сохранить alert button - chat item action +chat item action Save (and notify contacts) @@ -6568,7 +6525,6 @@ Enable in *Network & servers* settings. Save and notify group members - Сохранить и уведомить членов группы No comment provided by engineer. @@ -6853,7 +6809,6 @@ Enable in *Network & servers* settings. Send up to 100 last messages to new members. - Отправить до 100 последних сообщений новым членам. No comment provided by engineer. @@ -7098,7 +7053,6 @@ Enable in *Network & servers* settings. Set the message shown to new members! - Установить сообщение для новых членов группы! No comment provided by engineer. @@ -7125,7 +7079,7 @@ Enable in *Network & servers* settings. Share Поделиться alert action - chat item action +chat item action Share 1-time link @@ -7393,7 +7347,7 @@ Enable in *Network & servers* settings. Spam Спам blocking reason - report reason +report reason Square, circle, or anything in between. @@ -7729,22 +7683,18 @@ It can happen because of some bug or when the connection is compromised. The message will be deleted for all members. - Сообщение будет удалено для всех членов группы. No comment provided by engineer. The message will be marked as moderated for all members. - Сообщение будет помечено как удаленное для всех членов группы. No comment provided by engineer. The messages will be deleted for all members. - Сообщения будут удалены для всех членов группы. No comment provided by engineer. The messages will be marked as moderated for all members. - Сообщения будут помечены как удаленные для всех членов группы. No comment provided by engineer. @@ -7762,11 +7712,6 @@ It can happen because of some bug or when the connection is compromised.Те же самые условия будут приняты для оператора **%@**. No comment provided by engineer. - - The same conditions will apply to operator(s): **%@**. - Те же самые условия будут приняты для оператора(ов): **%@**. - No comment provided by engineer. - The second preset operator in the app! Второй оператор серверов в приложении! @@ -7864,7 +7809,6 @@ It can happen because of some bug or when the connection is compromised. This group has over %lld members, delivery receipts are not sent. - В группе более %lld членов, отчёты о доставке выключены. No comment provided by engineer. @@ -8071,17 +8015,14 @@ You will be prompted to complete authentication before this feature is enabled.< Unblock member - Разблокировать члена группы No comment provided by engineer. Unblock member for all? - Разблокировать члена для всех? No comment provided by engineer. Unblock member? - Разблокировать члена группы? No comment provided by engineer. @@ -8183,7 +8124,6 @@ To connect, please ask your contact to create another connection link and check Up to 100 last messages are sent to new members. - До 100 последних сообщений отправляются новым членам. No comment provided by engineer. @@ -8733,11 +8673,6 @@ Repeat join request? Вы можете изменить это в настройках Интерфейса. No comment provided by engineer. - - You can configure operators in Network & servers settings. - Вы можете настроить операторов в настройках Сети и серверов. - No comment provided by engineer. - You can configure servers via settings. Вы можете настроить серверы позже. @@ -8795,7 +8730,6 @@ Repeat join request? 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. - Вы можете поделиться ссылкой или QR кодом - через них можно присоединиться к группе. Вы сможете удалить ссылку, сохранив членов группы, которые через нее соединились. No comment provided by engineer. @@ -8872,7 +8806,6 @@ Repeat connection request? You joined this group. Connecting to inviting group member. - Вы вступили в эту группу. Устанавливается соединение с пригласившим членом группы. No comment provided by engineer. @@ -8942,7 +8875,6 @@ Repeat connection request? You will connect to all group members. - Вы соединитесь со всеми членами группы. No comment provided by engineer. @@ -8980,11 +8912,6 @@ Repeat connection request? Ваши ICE серверы No comment provided by engineer. - - Your SMP servers - Ваши SMP серверы - No comment provided by engineer. - Your SimpleX address Ваш адрес SimpleX @@ -9167,7 +9094,6 @@ Repeat connection request? all members - все члены feature role @@ -9223,7 +9149,8 @@ Repeat connection request? blocked by admin заблокировано администратором - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -9394,7 +9321,7 @@ Repeat connection request? default (%@) по умолчанию (%@) delete after time - pref value +pref value default (no) @@ -9521,21 +9448,11 @@ Repeat connection request? ошибка No comment provided by engineer. - - event happened - событие произошло - No comment provided by engineer. - expired истекло No comment provided by engineer. - - for better metadata privacy. - для лучшей конфиденциальности метаданных. - No comment provided by engineer. - forwarded переслано @@ -9658,12 +9575,10 @@ Repeat connection request? member - член группы member role member %1$@ changed to %2$@ - член %1$@ изменился на %2$@ profile update event chat item @@ -9745,8 +9660,8 @@ Repeat connection request? off нет enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -10116,7 +10031,7 @@ last received msg: %2$@
    - +
    @@ -10153,7 +10068,7 @@ last received msg: %2$@
    - +
    @@ -10175,7 +10090,7 @@ last received msg: %2$@
    - +
    @@ -10207,7 +10122,7 @@ last received msg: %2$@
    - +
    @@ -10229,7 +10144,7 @@ last received msg: %2$@
    - +
    diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/contents.json b/apps/ios/SimpleX Localizations/ru.xcloc/contents.json index a28b0ed489..b49b25d653 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/ru.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "ru", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index 74bb020cd6..a3162e0bec 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -2,7 +2,7 @@
    - +
    @@ -401,7 +401,7 @@ 1 day 1 วัน delete after time - time interval +time interval 1 hour @@ -417,13 +417,13 @@ 1 month 1 เดือน delete after time - time interval +time interval 1 week 1 สัปดาห์ delete after time - time interval +time interval 1 year @@ -517,8 +517,8 @@ Accept รับ accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action Accept conditions @@ -537,7 +537,7 @@ Accept incognito ยอมรับโหมดไม่ระบุตัวตน accept contact request via notification - swipe action +swipe action Accepted conditions @@ -1204,6 +1204,12 @@ ตามโปรไฟล์แชท (ค่าเริ่มต้น) หรือ [โดยการเชื่อมต่อ](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (เบต้า) No comment provided by engineer. + + By using SimpleX Chat you agree to: +- send only legal content in public groups. +- respect other users – no spam. + No comment provided by engineer. + Call already ended! สิ้นสุดการโทรแล้ว! @@ -1248,7 +1254,7 @@ Cancel ยกเลิก alert action - alert button +alert button Cancel migration @@ -1333,7 +1339,7 @@ Change self-destruct passcode เปลี่ยนรหัสผ่านแบบทำลายตัวเอง authentication reason - set passcode view +set passcode view Chat @@ -1546,14 +1552,6 @@ Conditions of use No comment provided by engineer. - - Conditions will be accepted for enabled operators after 30 days. - No comment provided by engineer. - - - Conditions will be accepted for operator(s): **%@**. - No comment provided by engineer. - Conditions will be accepted for the operator(s): **%@**. No comment provided by engineer. @@ -1571,6 +1569,10 @@ กำหนดค่าเซิร์ฟเวอร์ ICE No comment provided by engineer. + + Configure server operators + No comment provided by engineer. + Confirm ยืนยัน @@ -2108,7 +2110,7 @@ This is your own one-time link! Delete ลบ alert action - swipe action +swipe action Delete %lld messages of members? @@ -2563,7 +2565,7 @@ This is your own one-time link! Download alert button - chat item action +chat item action Download errors @@ -2632,8 +2634,8 @@ This is your own one-time link! เปิดใช้งาน (เก็บการแทนที่) No comment provided by engineer. - - Enable Flux + + Enable Flux in Network & servers settings for better metadata privacy. No comment provided by engineer. @@ -3164,7 +3166,9 @@ This is your own one-time link! Error: %@ ข้อผิดพลาด: % @ - alert message + alert message +file error text +snd error text Error: URL is invalid @@ -5332,11 +5336,19 @@ Error: %@ Privacy for your customers. No comment provided by engineer. + + Privacy policy and conditions of use. + No comment provided by engineer. + Privacy redefined นิยามความเป็นส่วนตัวใหม่ No comment provided by engineer. + + Private chats, groups and your contacts are not accessible to server operators. + No comment provided by engineer. + Private filenames ชื่อไฟล์ส่วนตัว @@ -5668,7 +5680,7 @@ Enable in *Network & servers* settings. Reject ปฏิเสธ reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -5880,10 +5892,6 @@ Enable in *Network & servers* settings. Review conditions No comment provided by engineer. - - Review later - No comment provided by engineer. - Revoke ถอน @@ -5929,7 +5937,7 @@ Enable in *Network & servers* settings. Save บันทึก alert button - chat item action +chat item action Save (and notify contacts) @@ -6449,7 +6457,7 @@ Enable in *Network & servers* settings. Share แชร์ alert action - chat item action +chat item action Share 1-time link @@ -6688,7 +6696,7 @@ Enable in *Network & servers* settings. Spam blocking reason - report reason +report reason Square, circle, or anything in between. @@ -7029,10 +7037,6 @@ It can happen because of some bug or when the connection is compromised.The same conditions will apply to operator **%@**. No comment provided by engineer. - - The same conditions will apply to operator(s): **%@**. - No comment provided by engineer. - The second preset operator in the app! No comment provided by engineer. @@ -7892,10 +7896,6 @@ Repeat join request? You can change it in Appearance settings. No comment provided by engineer. - - You can configure operators in Network & servers settings. - No comment provided by engineer. - You can configure servers via settings. No comment provided by engineer. @@ -8120,11 +8120,6 @@ Repeat connection request? เซิร์ฟเวอร์ ICE ของคุณ No comment provided by engineer. - - Your SMP servers - เซิร์ฟเวอร์ SMP ของคุณ - No comment provided by engineer. - Your SimpleX address ที่อยู่ SimpleX ของคุณ @@ -8346,7 +8341,8 @@ Repeat connection request? blocked by admin - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -8513,7 +8509,7 @@ Repeat connection request? default (%@) ค่าเริ่มต้น (%@) delete after time - pref value +pref value default (no) @@ -8637,18 +8633,10 @@ Repeat connection request? ผิดพลาด No comment provided by engineer. - - event happened - No comment provided by engineer. - expired No comment provided by engineer. - - for better metadata privacy. - No comment provided by engineer. - forwarded No comment provided by engineer. @@ -8852,8 +8840,8 @@ Repeat connection request? off ปิด enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -9192,7 +9180,7 @@ last received msg: %2$@
    - +
    @@ -9228,7 +9216,7 @@ last received msg: %2$@
    - +
    @@ -9250,7 +9238,7 @@ last received msg: %2$@
    - +
    @@ -9277,7 +9265,7 @@ last received msg: %2$@
    - +
    @@ -9296,7 +9284,7 @@ last received msg: %2$@
    - +
    diff --git a/apps/ios/SimpleX Localizations/th.xcloc/contents.json b/apps/ios/SimpleX Localizations/th.xcloc/contents.json index 4562ab8385..ee6ee63ea9 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/th.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "th", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index d3d81c2674..bce3cdde08 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -2,7 +2,7 @@
    - +
    @@ -439,7 +439,7 @@ 1 day 1 gün delete after time - time interval +time interval 1 hour @@ -455,13 +455,13 @@ 1 month 1 ay delete after time - time interval +time interval 1 week 1 hafta delete after time - time interval +time interval 1 year @@ -560,8 +560,8 @@ Accept Kabul et accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action Accept conditions @@ -582,7 +582,7 @@ Accept incognito Takma adla kabul et accept contact request via notification - swipe action +swipe action Accepted conditions @@ -1310,6 +1310,12 @@ Sohbet profiline göre (varsayılan) veya [bağlantıya göre](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. + + By using SimpleX Chat you agree to: +- send only legal content in public groups. +- respect other users – no spam. + No comment provided by engineer. + Call already ended! Arama çoktan bitti! @@ -1359,7 +1365,7 @@ Cancel İptal et alert action - alert button +alert button Cancel migration @@ -1449,7 +1455,7 @@ Change self-destruct passcode Kendini yok eden parolayı değiştir authentication reason - set passcode view +set passcode view Chat @@ -1688,16 +1694,6 @@ Kullanım koşulları No comment provided by engineer. - - Conditions will be accepted for enabled operators after 30 days. - Koşullar 30 gün sonra etkin operatörler tarafından kabul edilecektir. - No comment provided by engineer. - - - Conditions will be accepted for operator(s): **%@**. - Koşullar operatör(ler) için kabul edilecektir: **%@**. - No comment provided by engineer. - Conditions will be accepted for the operator(s): **%@**. Koşullar bu operatör(ler) için kabul edilecektir: **%@**. @@ -1718,6 +1714,10 @@ ICE sunucularını ayarla No comment provided by engineer. + + Configure server operators + No comment provided by engineer. + Confirm Onayla @@ -2308,7 +2308,7 @@ Bu senin kendi tek kullanımlık bağlantın! Delete Sil alert action - swipe action +swipe action Delete %lld messages of members? @@ -2795,7 +2795,7 @@ Bu senin kendi tek kullanımlık bağlantın! Download İndir alert button - chat item action +chat item action Download errors @@ -2872,9 +2872,8 @@ Bu senin kendi tek kullanımlık bağlantın! Etkinleştir (geçersiz kılmaları koru) No comment provided by engineer. - - Enable Flux - Flux'u Etkinleştir + + Enable Flux in Network & servers settings for better metadata privacy. No comment provided by engineer. @@ -3442,7 +3441,9 @@ Bu senin kendi tek kullanımlık bağlantın! Error: %@ Hata: %@ - alert message + alert message +file error text +snd error text Error: URL is invalid @@ -5774,11 +5775,19 @@ Hata: %@ Privacy for your customers. No comment provided by engineer. + + Privacy policy and conditions of use. + No comment provided by engineer. + Privacy redefined Gizlilik yeniden tanımlandı No comment provided by engineer. + + Private chats, groups and your contacts are not accessible to server operators. + No comment provided by engineer. + Private filenames Gizli dosya adları @@ -6140,7 +6149,7 @@ Enable in *Network & servers* settings. Reject Reddet reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -6366,10 +6375,6 @@ Enable in *Network & servers* settings. Review conditions No comment provided by engineer. - - Review later - No comment provided by engineer. - Revoke İptal et @@ -6419,7 +6424,7 @@ Enable in *Network & servers* settings. Save Kaydet alert button - chat item action +chat item action Save (and notify contacts) @@ -6982,7 +6987,7 @@ Enable in *Network & servers* settings. Share Paylaş alert action - chat item action +chat item action Share 1-time link @@ -7241,7 +7246,7 @@ Enable in *Network & servers* settings. Spam blocking reason - report reason +report reason Square, circle, or anything in between. @@ -7603,10 +7608,6 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. The same conditions will apply to operator **%@**. No comment provided by engineer. - - The same conditions will apply to operator(s): **%@**. - No comment provided by engineer. - The second preset operator in the app! No comment provided by engineer. @@ -8550,10 +8551,6 @@ Katılma isteği tekrarlansın mı? Görünüm ayarlarından değiştirebilirsiniz. No comment provided by engineer. - - You can configure operators in Network & servers settings. - No comment provided by engineer. - You can configure servers via settings. No comment provided by engineer. @@ -8792,11 +8789,6 @@ Bağlantı isteği tekrarlansın mı? ICE sunucularınız No comment provided by engineer. - - Your SMP servers - SMP sunucularınız - No comment provided by engineer. - Your SimpleX address SimpleX adresin @@ -9032,7 +9024,8 @@ Bağlantı isteği tekrarlansın mı? blocked by admin yönetici tarafından engellendi - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -9203,7 +9196,7 @@ Bağlantı isteği tekrarlansın mı? default (%@) varsayılan (%@) delete after time - pref value +pref value default (no) @@ -9330,20 +9323,11 @@ Bağlantı isteği tekrarlansın mı? hata No comment provided by engineer. - - event happened - etkinlik yaşandı - No comment provided by engineer. - expired Süresi dolmuş No comment provided by engineer. - - for better metadata privacy. - No comment provided by engineer. - forwarded iletildi @@ -9552,8 +9536,8 @@ Bağlantı isteği tekrarlansın mı? off kapalı enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -9919,7 +9903,7 @@ son alınan msj: %2$@
    - +
    @@ -9956,7 +9940,7 @@ son alınan msj: %2$@
    - +
    @@ -9978,7 +9962,7 @@ son alınan msj: %2$@
    - +
    @@ -10005,7 +9989,7 @@ son alınan msj: %2$@
    - +
    @@ -10027,7 +10011,7 @@ son alınan msj: %2$@
    - +
    diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/contents.json b/apps/ios/SimpleX Localizations/tr.xcloc/contents.json index 6f74640a6b..2e32ea2080 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/tr.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "tr", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 767867685d..c95be26929 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -2,7 +2,7 @@
    - +
    @@ -439,7 +439,7 @@ 1 day 1 день delete after time - time interval +time interval 1 hour @@ -455,13 +455,13 @@ 1 month 1 місяць delete after time - time interval +time interval 1 week 1 тиждень delete after time - time interval +time interval 1 year @@ -560,8 +560,8 @@ Accept Прийняти accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action Accept conditions @@ -582,7 +582,7 @@ Accept incognito Прийняти інкогніто accept contact request via notification - swipe action +swipe action Accepted conditions @@ -1310,6 +1310,12 @@ Через профіль чату (за замовчуванням) або [за з'єднанням](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. + + By using SimpleX Chat you agree to: +- send only legal content in public groups. +- respect other users – no spam. + No comment provided by engineer. + Call already ended! Дзвінок вже закінчився! @@ -1359,7 +1365,7 @@ Cancel Скасувати alert action - alert button +alert button Cancel migration @@ -1449,7 +1455,7 @@ Change self-destruct passcode Змінити пароль самознищення authentication reason - set passcode view +set passcode view Chat @@ -1688,16 +1694,6 @@ Умови використання No comment provided by engineer. - - Conditions will be accepted for enabled operators after 30 days. - Умови будуть прийняті для ввімкнених операторів через 30 днів. - No comment provided by engineer. - - - Conditions will be accepted for operator(s): **%@**. - Умови приймаються для оператора(ів): **%@**. - No comment provided by engineer. - Conditions will be accepted for the operator(s): **%@**. Для оператора(ів) приймаються умови: **%@**. @@ -1718,6 +1714,10 @@ Налаштування серверів ICE No comment provided by engineer. + + Configure server operators + No comment provided by engineer. + Confirm Підтвердити @@ -2308,7 +2308,7 @@ This is your own one-time link! Delete Видалити alert action - swipe action +swipe action Delete %lld messages of members? @@ -2796,7 +2796,7 @@ This is your own one-time link! Download Завантажити alert button - chat item action +chat item action Download errors @@ -2873,9 +2873,8 @@ This is your own one-time link! Увімкнути (зберегти перевизначення) No comment provided by engineer. - - Enable Flux - Увімкнути Flux + + Enable Flux in Network & servers settings for better metadata privacy. No comment provided by engineer. @@ -3443,7 +3442,9 @@ This is your own one-time link! Error: %@ Помилка: %@ - alert message + alert message +file error text +snd error text Error: URL is invalid @@ -5797,11 +5798,19 @@ Error: %@ Конфіденційність для ваших клієнтів. No comment provided by engineer. + + Privacy policy and conditions of use. + No comment provided by engineer. + Privacy redefined Конфіденційність переглянута No comment provided by engineer. + + Private chats, groups and your contacts are not accessible to server operators. + No comment provided by engineer. + Private filenames Приватні імена файлів @@ -6163,7 +6172,7 @@ Enable in *Network & servers* settings. Reject Відхилити reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -6390,11 +6399,6 @@ Enable in *Network & servers* settings. Умови перегляду No comment provided by engineer. - - Review later - Перегляньте пізніше - No comment provided by engineer. - Revoke Відкликати @@ -6444,7 +6448,7 @@ Enable in *Network & servers* settings. Save Зберегти alert button - chat item action +chat item action Save (and notify contacts) @@ -7011,7 +7015,7 @@ Enable in *Network & servers* settings. Share Поділіться alert action - chat item action +chat item action Share 1-time link @@ -7278,7 +7282,7 @@ Enable in *Network & servers* settings. Spam blocking reason - report reason +report reason Square, circle, or anything in between. @@ -7644,11 +7648,6 @@ It can happen because of some bug or when the connection is compromised.Такі ж умови діятимуть і для оператора **%@**. No comment provided by engineer. - - The same conditions will apply to operator(s): **%@**. - Такі ж умови будуть застосовуватися до оператора(ів): **%@**. - No comment provided by engineer. - The second preset operator in the app! Другий попередньо встановлений оператор у застосунку! @@ -8608,11 +8607,6 @@ Repeat join request? Ви можете змінити його в налаштуваннях зовнішнього вигляду. No comment provided by engineer. - - You can configure operators in Network & servers settings. - Ви можете налаштувати операторів у налаштуваннях Мережі та серверів. - No comment provided by engineer. - You can configure servers via settings. Ви можете налаштувати сервери за допомогою налаштувань. @@ -8854,11 +8848,6 @@ Repeat connection request? Ваші сервери ICE No comment provided by engineer. - - Your SMP servers - Ваші SMP-сервери - No comment provided by engineer. - Your SimpleX address Ваша адреса SimpleX @@ -9096,7 +9085,8 @@ Repeat connection request? blocked by admin заблоковано адміністратором - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -9267,7 +9257,7 @@ Repeat connection request? default (%@) за замовчуванням (%@) delete after time - pref value +pref value default (no) @@ -9394,21 +9384,11 @@ Repeat connection request? помилка No comment provided by engineer. - - event happened - відбулася подія - No comment provided by engineer. - expired закінчився No comment provided by engineer. - - for better metadata privacy. - для кращої конфіденційності метаданих. - No comment provided by engineer. - forwarded переслано @@ -9617,8 +9597,8 @@ Repeat connection request? off вимкнено enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -9985,7 +9965,7 @@ last received msg: %2$@
    - +
    @@ -10022,7 +10002,7 @@ last received msg: %2$@
    - +
    @@ -10044,7 +10024,7 @@ last received msg: %2$@
    - +
    @@ -10076,7 +10056,7 @@ last received msg: %2$@
    - +
    @@ -10098,7 +10078,7 @@ last received msg: %2$@
    - +
    diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/contents.json b/apps/ios/SimpleX Localizations/uk.xcloc/contents.json index 38238e7802..a93c702952 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/uk.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "uk", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index d464b2f0ed..1e4c1a72f6 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -2,7 +2,7 @@
    - +
    @@ -102,10 +102,12 @@ %@ server + 服务器 No comment provided by engineer. %@ servers + 服务器 No comment provided by engineer. @@ -430,7 +432,7 @@ 1 day 1天 delete after time - time interval +time interval 1 hour @@ -446,13 +448,13 @@ 1 month 1月 delete after time - time interval +time interval 1 week 1周 delete after time - time interval +time interval 1 year @@ -548,8 +550,8 @@ Accept 接受 accept contact request via notification - accept incoming call via notification - swipe action +accept incoming call via notification +swipe action Accept conditions @@ -569,7 +571,7 @@ Accept incognito 接受隐身聊天 accept contact request via notification - swipe action +swipe action Accepted conditions @@ -1279,6 +1281,15 @@ 通过聊天资料(默认)或者[通过连接](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)。 No comment provided by engineer. + + By using SimpleX Chat you agree to: +- send only legal content in public groups. +- respect other users – no spam. + 使用 SimpleX Chat 代表您同意: +- 在公开群中只发送合法内容 +- 尊重其他用户 – 没有垃圾信息。 + No comment provided by engineer. + Call already ended! 通话已结束! @@ -1328,7 +1339,7 @@ Cancel 取消 alert action - alert button +alert button Cancel migration @@ -1417,7 +1428,7 @@ Change self-destruct passcode 更改自毁密码 authentication reason - set passcode view +set passcode view Chat @@ -1642,14 +1653,7 @@ Conditions of use - No comment provided by engineer. - - - Conditions will be accepted for enabled operators after 30 days. - No comment provided by engineer. - - - Conditions will be accepted for operator(s): **%@**. + 使用条款 No comment provided by engineer. @@ -1669,6 +1673,11 @@ 配置 ICE 服务器 No comment provided by engineer. + + Configure server operators + 配置服务器运营方 + No comment provided by engineer. + Confirm 确认 @@ -2254,7 +2263,7 @@ This is your own one-time link! Delete 删除 alert action - swipe action +swipe action Delete %lld messages of members? @@ -2736,7 +2745,7 @@ This is your own one-time link! Download 下载 alert button - chat item action +chat item action Download errors @@ -2811,8 +2820,8 @@ This is your own one-time link! 启用(保持覆盖) No comment provided by engineer. - - Enable Flux + + Enable Flux in Network & servers settings for better metadata privacy. No comment provided by engineer. @@ -3371,7 +3380,9 @@ This is your own one-time link! Error: %@ 错误: %@ - alert message + alert message +file error text +snd error text Error: URL is invalid @@ -5671,11 +5682,21 @@ Error: %@ Privacy for your customers. No comment provided by engineer. + + Privacy policy and conditions of use. + 隐私政策和使用条款。 + No comment provided by engineer. + Privacy redefined 重新定义隐私 No comment provided by engineer. + + Private chats, groups and your contacts are not accessible to server operators. + 服务器运营方无法访问私密聊天、群组和你的联系人。 + No comment provided by engineer. + Private filenames 私密文件名 @@ -6036,7 +6057,7 @@ Enable in *Network & servers* settings. Reject 拒绝 reject incoming call via notification - swipe action +swipe action Reject (sender NOT notified) @@ -6261,10 +6282,6 @@ Enable in *Network & servers* settings. Review conditions No comment provided by engineer. - - Review later - No comment provided by engineer. - Revoke 撤销 @@ -6313,7 +6330,7 @@ Enable in *Network & servers* settings. Save 保存 alert button - chat item action +chat item action Save (and notify contacts) @@ -6871,7 +6888,7 @@ Enable in *Network & servers* settings. Share 分享 alert action - chat item action +chat item action Share 1-time link @@ -7127,7 +7144,7 @@ Enable in *Network & servers* settings. Spam blocking reason - report reason +report reason Square, circle, or anything in between. @@ -7486,10 +7503,6 @@ It can happen because of some bug or when the connection is compromised.The same conditions will apply to operator **%@**. No comment provided by engineer. - - The same conditions will apply to operator(s): **%@**. - No comment provided by engineer. - The second preset operator in the app! No comment provided by engineer. @@ -8428,10 +8441,6 @@ Repeat join request? 您可以在外观设置中更改它。 No comment provided by engineer. - - You can configure operators in Network & servers settings. - No comment provided by engineer. - You can configure servers via settings. No comment provided by engineer. @@ -8670,11 +8679,6 @@ Repeat connection request? 您的 ICE 服务器 No comment provided by engineer. - - Your SMP servers - 您的 SMP 服务器 - No comment provided by engineer. - Your SimpleX address 您的 SimpleX 地址 @@ -8906,7 +8910,8 @@ Repeat connection request? blocked by admin 由管理员封禁 - marked deleted chat item preview text + blocked chat item +marked deleted chat item preview text bold @@ -9077,7 +9082,7 @@ Repeat connection request? default (%@) 默认 (%@) delete after time - pref value +pref value default (no) @@ -9204,20 +9209,11 @@ Repeat connection request? 错误 No comment provided by engineer. - - event happened - 发生的事 - No comment provided by engineer. - expired 过期 No comment provided by engineer. - - for better metadata privacy. - No comment provided by engineer. - forwarded 已转发 @@ -9426,8 +9422,8 @@ Repeat connection request? off 关闭 enabled status - group pref value - time to disappear +group pref value +time to disappear offered %@ @@ -9793,7 +9789,7 @@ last received msg: %2$@
    - +
    @@ -9830,7 +9826,7 @@ last received msg: %2$@
    - +
    @@ -9852,7 +9848,7 @@ last received msg: %2$@
    - +
    @@ -9879,7 +9875,7 @@ last received msg: %2$@
    - +
    @@ -9901,7 +9897,7 @@ last received msg: %2$@
    - +
    diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/contents.json b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/contents.json index 6416a2d8fa..91977b0744 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/contents.json +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/contents.json @@ -3,10 +3,10 @@ "project" : "SimpleX.xcodeproj", "targetLocale" : "zh-Hans", "toolInfo" : { - "toolBuildNumber" : "15F31d", + "toolBuildNumber" : "16C5032a", "toolID" : "com.apple.dt.xcode", "toolName" : "Xcode", - "toolVersion" : "15.4" + "toolVersion" : "16.2" }, "version" : "1.0" } \ No newline at end of file diff --git a/apps/ios/SimpleX SE/hu.lproj/Localizable.strings b/apps/ios/SimpleX SE/hu.lproj/Localizable.strings index 2ba56bb2af..2fedf0e6f1 100644 --- a/apps/ios/SimpleX SE/hu.lproj/Localizable.strings +++ b/apps/ios/SimpleX SE/hu.lproj/Localizable.strings @@ -38,10 +38,10 @@ "Database upgrade required" = "Adatbázis fejlesztése szükséges"; /* No comment provided by engineer. */ -"Error preparing file" = "Hiba a fájl előkészítésekor"; +"Error preparing file" = "Hiba történt a fájl előkészítésekor"; /* No comment provided by engineer. */ -"Error preparing message" = "Hiba az üzenet előkészítésekor"; +"Error preparing message" = "Hiba történt az üzenet előkészítésekor"; /* No comment provided by engineer. */ "Error: %@" = "Hiba: %@"; diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index 631e0e7628..432bc75894 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -242,7 +242,7 @@ "0s" = "0s"; /* delete after time - time interval */ +time interval */ "1 day" = "1 ден"; /* time interval */ @@ -252,11 +252,11 @@ "1 minute" = "1 минута"; /* delete after time - time interval */ +time interval */ "1 month" = "1 месец"; /* delete after time - time interval */ +time interval */ "1 week" = "1 седмица"; /* No comment provided by engineer. */ @@ -308,8 +308,8 @@ "Accent" = "Акцент"; /* accept contact request via notification - accept incoming call via notification - swipe action */ +accept incoming call via notification +swipe action */ "Accept" = "Приеми"; /* No comment provided by engineer. */ @@ -322,7 +322,7 @@ "Accept contact request from %@?" = "Приемане на заявка за контакт от %@?"; /* accept contact request via notification - swipe action */ +swipe action */ "Accept incognito" = "Приеми инкогнито"; /* call status */ @@ -718,7 +718,8 @@ /* rcv group event chat item */ "blocked %@" = "блокиран %@"; -/* marked deleted chat item preview text */ +/* blocked chat item +marked deleted chat item preview text */ "blocked by admin" = "блокиран от админ"; /* No comment provided by engineer. */ @@ -794,7 +795,7 @@ "Can't invite contacts!" = "Не може да поканят контактите!"; /* alert action - alert button */ +alert button */ "Cancel" = "Отказ"; /* No comment provided by engineer. */ @@ -846,7 +847,7 @@ "Change self-destruct mode" = "Промени режима на самоунищожение"; /* authentication reason - set passcode view */ +set passcode view */ "Change self-destruct passcode" = "Промени кода за достъп за самоунищожение"; /* chat item text */ @@ -1252,7 +1253,7 @@ "Decryption error" = "Грешка при декриптиране"; /* delete after time - pref value */ +pref value */ "default (%@)" = "по подразбиране (%@)"; /* No comment provided by engineer. */ @@ -1262,7 +1263,7 @@ "default (yes)" = "по подразбиране (да)"; /* alert action - swipe action */ +swipe action */ "Delete" = "Изтрий"; /* No comment provided by engineer. */ @@ -1491,7 +1492,7 @@ "Downgrade and open chat" = "Понижи версията и отвори чата"; /* alert button - chat item action */ +chat item action */ "Download" = "Изтегли"; /* No comment provided by engineer. */ @@ -1845,7 +1846,9 @@ /* No comment provided by engineer. */ "Error: " = "Грешка: "; -/* alert message */ +/* alert message +file error text +snd error text */ "Error: %@" = "Грешка: %@"; /* No comment provided by engineer. */ @@ -1857,9 +1860,6 @@ /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Дори когато е деактивиран в разговора."; -/* No comment provided by engineer. */ -"event happened" = "събитие се случи"; - /* No comment provided by engineer. */ "Exit without saving" = "Изход без запазване"; @@ -2746,8 +2746,8 @@ "observer" = "наблюдател"; /* enabled status - group pref value - time to disappear */ +group pref value +time to disappear */ "off" = "изключено"; /* blur media */ @@ -3132,7 +3132,7 @@ "Reduced battery usage" = "Намалена консумация на батерията"; /* reject incoming call via notification - swipe action */ +swipe action */ "Reject" = "Отхвърляне"; /* No comment provided by engineer. */ @@ -3259,7 +3259,7 @@ "Safer groups" = "По-безопасни групи"; /* alert button - chat item action */ +chat item action */ "Save" = "Запази"; /* alert button */ @@ -3515,7 +3515,7 @@ "Shape profile images" = "Променете формата на профилните изображения"; /* alert action - chat item action */ +chat item action */ "Share" = "Сподели"; /* No comment provided by engineer. */ @@ -4463,6 +4463,3 @@ /* No comment provided by engineer. */ "Your SimpleX address" = "Вашият адрес в SimpleX"; -/* No comment provided by engineer. */ -"Your SMP servers" = "Вашите SMP сървъри"; - diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index b6baf5e951..25fb66aa12 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -215,7 +215,7 @@ "0s" = "0s"; /* delete after time - time interval */ +time interval */ "1 day" = "1 den"; /* time interval */ @@ -225,11 +225,11 @@ "1 minute" = "1 minutu"; /* delete after time - time interval */ +time interval */ "1 month" = "1 měsíc"; /* delete after time - time interval */ +time interval */ "1 week" = "1 týden"; /* No comment provided by engineer. */ @@ -272,8 +272,8 @@ "above, then choose:" = "výše, pak vyberte:"; /* accept contact request via notification - accept incoming call via notification - swipe action */ +accept incoming call via notification +swipe action */ "Accept" = "Přijmout"; /* No comment provided by engineer. */ @@ -283,7 +283,7 @@ "Accept contact request from %@?" = "Přijmout žádost o kontakt od %@?"; /* accept contact request via notification - swipe action */ +swipe action */ "Accept incognito" = "Přijmout inkognito"; /* call status */ @@ -548,7 +548,7 @@ "Can't invite contacts!" = "Nelze pozvat kontakty!"; /* alert action - alert button */ +alert button */ "Cancel" = "Zrušit"; /* feature offered item */ @@ -588,7 +588,7 @@ "Change self-destruct mode" = "Změnit režim sebedestrukce"; /* authentication reason - set passcode view */ +set passcode view */ "Change self-destruct passcode" = "Změnit sebedestrukční heslo"; /* chat item text */ @@ -910,7 +910,7 @@ "Decryption error" = "Chyba dešifrování"; /* delete after time - pref value */ +pref value */ "default (%@)" = "výchozí (%@)"; /* No comment provided by engineer. */ @@ -920,7 +920,7 @@ "default (yes)" = "výchozí (ano)"; /* alert action - swipe action */ +swipe action */ "Delete" = "Smazat"; /* No comment provided by engineer. */ @@ -1409,7 +1409,9 @@ /* No comment provided by engineer. */ "Error: " = "Chyba: "; -/* alert message */ +/* alert message +file error text +snd error text */ "Error: %@" = "Chyba: %@"; /* No comment provided by engineer. */ @@ -2127,8 +2129,8 @@ "observer" = "pozorovatel"; /* enabled status - group pref value - time to disappear */ +group pref value +time to disappear */ "off" = "vypnuto"; /* blur media */ @@ -2435,7 +2437,7 @@ "Reduced battery usage" = "Snížení spotřeby baterie"; /* reject incoming call via notification - swipe action */ +swipe action */ "Reject" = "Odmítnout"; /* No comment provided by engineer. */ @@ -2535,7 +2537,7 @@ "Run chat" = "Spustit chat"; /* alert button - chat item action */ +chat item action */ "Save" = "Uložit"; /* alert button */ @@ -2749,7 +2751,7 @@ "Settings" = "Nastavení"; /* alert action - chat item action */ +chat item action */ "Share" = "Sdílet"; /* No comment provided by engineer. */ @@ -3472,6 +3474,3 @@ /* No comment provided by engineer. */ "Your SimpleX address" = "Vaše SimpleX adresa"; -/* No comment provided by engineer. */ -"Your SMP servers" = "Vaše servery SMP"; - diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index a85506f2e3..d92ad24117 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -269,7 +269,7 @@ "0s" = "0s"; /* delete after time - time interval */ +time interval */ "1 day" = "Älter als ein Tag"; /* time interval */ @@ -279,11 +279,11 @@ "1 minute" = "1 Minute"; /* delete after time - time interval */ +time interval */ "1 month" = "Älter als ein Monat"; /* delete after time - time interval */ +time interval */ "1 week" = "Älter als eine Woche"; /* delete after time */ @@ -341,8 +341,8 @@ "Accent" = "Akzent"; /* accept contact request via notification - accept incoming call via notification - swipe action */ +accept incoming call via notification +swipe action */ "Accept" = "Annehmen"; /* No comment provided by engineer. */ @@ -355,7 +355,7 @@ "Accept contact request from %@?" = "Die Kontaktanfrage von %@ annehmen?"; /* accept contact request via notification - swipe action */ +swipe action */ "Accept incognito" = "Inkognito akzeptieren"; /* call status */ @@ -817,7 +817,8 @@ /* rcv group event chat item */ "blocked %@" = "%@ wurde blockiert"; -/* marked deleted chat item preview text */ +/* blocked chat item +marked deleted chat item preview text */ "blocked by admin" = "wurde vom Administrator blockiert"; /* No comment provided by engineer. */ @@ -862,6 +863,9 @@ /* 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)."; +/* No comment provided by engineer. */ +"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "Durch die Nutzung von SimpleX Chat erklären Sie sich damit einverstanden:\n- nur legale Inhalte in öffentlichen Gruppen zu versenden.\n- andere Nutzer zu respektieren - kein Spam."; + /* No comment provided by engineer. */ "call" = "Anrufen"; @@ -902,7 +906,7 @@ "Can't message member" = "Mitglied kann nicht benachrichtigt werden"; /* alert action - alert button */ +alert button */ "Cancel" = "Abbrechen"; /* No comment provided by engineer. */ @@ -960,7 +964,7 @@ "Change self-destruct mode" = "Selbstzerstörungs-Modus ändern"; /* authentication reason - set passcode view */ +set passcode view */ "Change self-destruct passcode" = "Selbstzerstörungs-Zugangscode ändern"; /* chat item text */ @@ -1128,12 +1132,6 @@ /* No comment provided by engineer. */ "Conditions of use" = "Nutzungsbedingungen"; -/* No comment provided by engineer. */ -"Conditions will be accepted for enabled operators after 30 days." = "Die Nutzungsbedingungen der aktivierten Betreiber werden nach 30 Tagen akzeptiert."; - -/* No comment provided by engineer. */ -"Conditions will be accepted for operator(s): **%@**." = "Die Nutzungsbedingungen der/des Betreiber(s) werden akzeptiert: **%@**."; - /* No comment provided by engineer. */ "Conditions will be accepted for the operator(s): **%@**." = "Die Nutzungsbedingungen der/des Betreiber(s) werden akzeptiert: **%@**."; @@ -1146,6 +1144,9 @@ /* No comment provided by engineer. */ "Configure ICE servers" = "ICE-Server konfigurieren"; +/* No comment provided by engineer. */ +"Configure server operators" = "Server-Betreiber konfigurieren"; + /* No comment provided by engineer. */ "Confirm" = "Bestätigen"; @@ -1555,7 +1556,7 @@ "decryption errors" = "Entschlüsselungs-Fehler"; /* delete after time - pref value */ +pref value */ "default (%@)" = "Default (%@)"; /* No comment provided by engineer. */ @@ -1565,7 +1566,7 @@ "default (yes)" = "Voreinstellung (Ja)"; /* alert action - swipe action */ +swipe action */ "Delete" = "Löschen"; /* No comment provided by engineer. */ @@ -1881,7 +1882,7 @@ "Downgrade and open chat" = "Datenbank herabstufen und den Chat öffnen"; /* alert button - chat item action */ +chat item action */ "Download" = "Herunterladen"; /* No comment provided by engineer. */ @@ -1944,9 +1945,6 @@ /* No comment provided by engineer. */ "Enable camera access" = "Kamera-Zugriff aktivieren"; -/* No comment provided by engineer. */ -"Enable Flux" = "Flux aktivieren"; - /* No comment provided by engineer. */ "Enable for all" = "Für Alle aktivieren"; @@ -2224,7 +2222,7 @@ "Error opening chat" = "Fehler beim Öffnen des Chats"; /* alert title */ -"Error receiving file" = "Fehler beim Empfangen der Datei"; +"Error receiving file" = "Fehler beim Herunterladen der Datei"; /* No comment provided by engineer. */ "Error reconnecting server" = "Fehler beim Wiederherstellen der Verbindung zum Server"; @@ -2325,7 +2323,9 @@ /* No comment provided by engineer. */ "Error: " = "Fehler: "; -/* alert message */ +/* alert message +file error text +snd error text */ "Error: %@" = "Fehler: %@"; /* No comment provided by engineer. */ @@ -2343,9 +2343,6 @@ /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Auch wenn sie im Chat deaktiviert sind."; -/* No comment provided by engineer. */ -"event happened" = "event happened"; - /* No comment provided by engineer. */ "Exit without saving" = "Beenden ohne Speichern"; @@ -2422,10 +2419,10 @@ "File will be deleted from servers." = "Die Datei wird von den Servern gelöscht."; /* No comment provided by engineer. */ -"File will be received when your contact completes uploading it." = "Die Datei wird empfangen, sobald das Hochladen durch ihren Kontakt abgeschlossen ist."; +"File will be received when your contact completes uploading it." = "Die Datei wird heruntergeladen, sobald das Hochladen durch ihren Kontakt abgeschlossen ist."; /* No comment provided by engineer. */ -"File will be received when your contact is online, please wait or check later!" = "Die Datei wird empfangen, sobald Ihr Kontakt online ist. Bitte warten oder schauen Sie später nochmal nach!"; +"File will be received when your contact is online, please wait or check later!" = "Die Datei wird heruntergeladen, sobald Ihr Kontakt online ist. Bitte warten oder schauen Sie später nochmal nach!"; /* No comment provided by engineer. */ "File: %@" = "Datei: %@"; @@ -2484,9 +2481,6 @@ /* No comment provided by engineer. */ "For all moderators" = "Für alle Moderatoren"; -/* No comment provided by engineer. */ -"for better metadata privacy." = "für einen besseren Metadatenschutz."; - /* servers error */ "For chat profile %@:" = "Für das Chat-Profil %@:"; @@ -2728,10 +2722,10 @@ "Ignore" = "Ignorieren"; /* No comment provided by engineer. */ -"Image will be received when your contact completes uploading it." = "Das Bild wird empfangen, sobald das Hochladen durch ihren Kontakt abgeschlossen ist."; +"Image will be received when your contact completes uploading it." = "Das Bild wird heruntergeladen, sobald das Hochladen durch ihren Kontakt abgeschlossen ist."; /* No comment provided by engineer. */ -"Image will be received when your contact is online, please wait or check later!" = "Das Bild wird empfangen, sobald Ihr Kontakt online ist. Bitte warten oder schauen Sie später nochmal nach!"; +"Image will be received when your contact is online, please wait or check later!" = "Das Bild wird heruntergeladen, sobald Ihr Kontakt online ist. Bitte warten oder schauen Sie später nochmal nach!"; /* No comment provided by engineer. */ "Immediately" = "Sofort"; @@ -3529,13 +3523,13 @@ "No push server" = "Lokal"; /* No comment provided by engineer. */ -"No received or sent files" = "Keine empfangenen oder gesendeten Dateien"; +"No received or sent files" = "Keine herunter- oder hochgeladene Dateien"; /* servers error */ "No servers for private message routing." = "Keine Server für privates Nachrichten-Routing."; /* servers error */ -"No servers to receive files." = "Keine Server für den Empfang von Dateien."; +"No servers to receive files." = "Keine Server für das Herunterladen von Dateien."; /* servers error */ "No servers to receive messages." = "Keine Server für den Empfang von Nachrichten."; @@ -3589,8 +3583,8 @@ "observer" = "Beobachter"; /* enabled status - group pref value - time to disappear */ +group pref value +time to disappear */ "off" = "Aus"; /* blur media */ @@ -3914,9 +3908,15 @@ /* No comment provided by engineer. */ "Privacy for your customers." = "Schutz der Privatsphäre Ihrer Kunden."; +/* No comment provided by engineer. */ +"Privacy policy and conditions of use." = "Datenschutzbestimmungen und Nutzungsbedingungen."; + /* No comment provided by engineer. */ "Privacy redefined" = "Datenschutz neu definiert"; +/* No comment provided by engineer. */ +"Private chats, groups and your contacts are not accessible to server operators." = "Private Chats, Gruppen und Ihre Kontakte sind für Server-Betreiber nicht zugänglich."; + /* No comment provided by engineer. */ "Private filenames" = "Neutrale Dateinamen"; @@ -4089,7 +4089,7 @@ "Receiving address will be changed to a different server. Address change will complete after sender comes online." = "Die Empfängeradresse wird auf einen anderen Server geändert. Der Adresswechsel wird abgeschlossen, wenn der Absender wieder online ist."; /* No comment provided by engineer. */ -"Receiving file will be stopped." = "Der Empfang der Datei wird beendet."; +"Receiving file will be stopped." = "Das Herunterladen der Datei wird beendet."; /* No comment provided by engineer. */ "Receiving via" = "Empfangen über"; @@ -4143,7 +4143,7 @@ "Registered" = "Registriert"; /* reject incoming call via notification - swipe action */ +swipe action */ "Reject" = "Ablehnen"; /* No comment provided by engineer. */ @@ -4311,9 +4311,6 @@ /* No comment provided by engineer. */ "Review conditions" = "Nutzungsbedingungen einsehen"; -/* No comment provided by engineer. */ -"Review later" = "Später einsehen"; - /* No comment provided by engineer. */ "Revoke" = "Widerrufen"; @@ -4330,13 +4327,13 @@ "Run chat" = "Chat starten"; /* No comment provided by engineer. */ -"Safely receive files" = "Dateien sicher empfangen"; +"Safely receive files" = "Dateien sicher herunterladen"; /* No comment provided by engineer. */ "Safer groups" = "Sicherere Gruppen"; /* alert button - chat item action */ +chat item action */ "Save" = "Speichern"; /* alert button */ @@ -4712,7 +4709,7 @@ "Shape profile images" = "Form der Profil-Bilder"; /* alert action - chat item action */ +chat item action */ "Share" = "Teilen"; /* No comment provided by engineer. */ @@ -4878,7 +4875,7 @@ "Somebody" = "Jemand"; /* blocking reason - report reason */ +report reason */ "Spam" = "Spam"; /* No comment provided by engineer. */ @@ -4918,13 +4915,13 @@ "Stop chat?" = "Chat beenden?"; /* cancel file action */ -"Stop file" = "Datei beenden"; +"Stop file" = "Herunterladen beenden"; /* No comment provided by engineer. */ -"Stop receiving file?" = "Den Empfang der Datei beenden?"; +"Stop receiving file?" = "Das Herunterladen der Datei beenden?"; /* No comment provided by engineer. */ -"Stop sending file?" = "Das Senden der Datei beenden?"; +"Stop sending file?" = "Das Hochladen der Datei beenden?"; /* alert action */ "Stop sharing" = "Teilen beenden"; @@ -5109,9 +5106,6 @@ /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Dieselben Nutzungsbedingungen gelten auch für den Betreiber **%@**."; -/* No comment provided by engineer. */ -"The same conditions will apply to operator(s): **%@**." = "Dieselben Nutzungsbedingungen gelten auch für den/die Betreiber: **%@**."; - /* No comment provided by engineer. */ "The second preset operator in the app!" = "Der zweite voreingestellte Netzwerk-Betreiber in der App!"; @@ -5146,7 +5140,7 @@ "They can be overridden in contact and group settings." = "Sie können in den Kontakteinstellungen überschrieben werden."; /* No comment provided by engineer. */ -"This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "Es werden alle empfangenen und gesendeten Dateien und Medien gelöscht. Bilder mit niedriger Auflösung bleiben erhalten. Diese Aktion kann nicht rückgängig gemacht werden!"; +"This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "Es werden alle herunter- und hochgeladene Dateien und Medien gelöscht. Bilder mit niedriger Auflösung bleiben erhalten. Diese Aktion kann nicht rückgängig gemacht werden!"; /* No comment provided by engineer. */ "This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "Es werden alle empfangenen und gesendeten Nachrichten, die über den ausgewählten Zeitraum hinaus gehen, gelöscht. Dieser Vorgang kann mehrere Minuten dauern. Diese Aktion kann nicht rückgängig gemacht werden!"; @@ -5551,10 +5545,10 @@ "video call (not e2e encrypted)" = "Videoanruf (nicht E2E verschlüsselt)"; /* No comment provided by engineer. */ -"Video will be received when your contact completes uploading it." = "Das Video wird empfangen, sobald Ihr Kontakt das Hochladen beendet hat."; +"Video will be received when your contact completes uploading it." = "Das Video wird heruntergeladen, sobald Ihr Kontakt das Hochladen beendet hat."; /* No comment provided by engineer. */ -"Video will be received when your contact is online, please wait or check later!" = "Das Video wird empfangen, wenn Ihr Kontakt online ist. Bitte warten oder überprüfen Sie es später!"; +"Video will be received when your contact is online, please wait or check later!" = "Das Video wird heruntergeladen, sobald Ihr Kontakt online ist. Bitte warten oder überprüfen Sie es später!"; /* No comment provided by engineer. */ "Videos and files up to 1gb" = "Videos und Dateien bis zu 1GB"; @@ -5766,9 +5760,6 @@ /* No comment provided by engineer. */ "You can change it in Appearance settings." = "Kann von Ihnen in den Erscheinungsbild-Einstellungen geändert werden."; -/* No comment provided by engineer. */ -"You can configure operators in Network & servers settings." = "Sie können die Betreiber in den Netzwerk- und Servereinstellungen konfigurieren."; - /* No comment provided by engineer. */ "You can configure servers via settings." = "Sie können die Server über die Einstellungen konfigurieren."; @@ -6015,6 +6006,3 @@ /* No comment provided by engineer. */ "Your SimpleX address" = "Ihre SimpleX-Adresse"; -/* No comment provided by engineer. */ -"Your SMP servers" = "Ihre SMP-Server"; - diff --git a/apps/ios/de.lproj/SimpleX--iOS--InfoPlist.strings b/apps/ios/de.lproj/SimpleX--iOS--InfoPlist.strings index 0dee85ad95..e0554c9fb6 100644 --- a/apps/ios/de.lproj/SimpleX--iOS--InfoPlist.strings +++ b/apps/ios/de.lproj/SimpleX--iOS--InfoPlist.strings @@ -14,5 +14,5 @@ "NSMicrophoneUsageDescription" = "SimpleX benötigt Zugriff auf das Mikrofon, um Audio- und Videoanrufe und die Aufnahme von Sprachnachrichten zu ermöglichen."; /* Privacy - Photo Library Additions Usage Description */ -"NSPhotoLibraryAddUsageDescription" = "SimpleX benötigt Zugriff auf das Fotoalbum, um selbst gemachte oder empfangene Bilder zu speichern"; +"NSPhotoLibraryAddUsageDescription" = "SimpleX benötigt Zugriff auf das Fotoalbum, um selbst gemachte oder heruntergeladene Bilder zu speichern"; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 09e7ea5b8e..b6580836e7 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -269,7 +269,7 @@ "0s" = "0s"; /* delete after time - time interval */ +time interval */ "1 day" = "un dia"; /* time interval */ @@ -279,11 +279,11 @@ "1 minute" = "1 minuto"; /* delete after time - time interval */ +time interval */ "1 month" = "un mes"; /* delete after time - time interval */ +time interval */ "1 week" = "una semana"; /* delete after time */ @@ -341,8 +341,8 @@ "Accent" = "Color"; /* accept contact request via notification - accept incoming call via notification - swipe action */ +accept incoming call via notification +swipe action */ "Accept" = "Aceptar"; /* No comment provided by engineer. */ @@ -355,7 +355,7 @@ "Accept contact request from %@?" = "¿Aceptar solicitud de contacto de %@?"; /* accept contact request via notification - swipe action */ +swipe action */ "Accept incognito" = "Aceptar incógnito"; /* call status */ @@ -470,10 +470,10 @@ "All app data is deleted." = "Todos los datos de la aplicación se eliminarán."; /* No comment provided by engineer. */ -"All chats and messages will be deleted - this cannot be undone!" = "Se eliminarán todos los chats y mensajes. ¡No podrá deshacerse!"; +"All chats and messages will be deleted - this cannot be undone!" = "Se eliminarán todos los chats y mensajes. ¡No puede deshacerse!"; /* alert message */ -"All chats will be removed from the list %@, and the list deleted." = "Todos los chats serán quitados de la lista %@ y esta será eliminada."; +"All chats will be removed from the list %@, and the list deleted." = "Todos los chats se quitarán de la lista %@ y esta será eliminada."; /* No comment provided by engineer. */ "All data is erased when it is entered." = "Al introducirlo todos los datos son eliminados."; @@ -491,10 +491,10 @@ "All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Todos los mensajes y archivos son enviados **cifrados de extremo a extremo** y con seguridad de cifrado postcuántico en mensajes directos."; /* No comment provided by engineer. */ -"All messages will be deleted - this cannot be undone!" = "Todos los mensajes serán borrados. ¡No podrá deshacerse!"; +"All messages will be deleted - this cannot be undone!" = "Todos los mensajes serán eliminados. ¡No puede deshacerse!"; /* No comment provided by engineer. */ -"All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "Se eliminarán todos los mensajes SOLO para tí. ¡No podrá deshacerse!"; +"All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "Se eliminarán todos los mensajes SOLO para tí. ¡No puede deshacerse!"; /* No comment provided by engineer. */ "All new messages from %@ will be hidden!" = "¡Los mensajes nuevos de %@ estarán ocultos!"; @@ -629,7 +629,7 @@ "App group:" = "Grupo app:"; /* No comment provided by engineer. */ -"App icon" = "Icono aplicación"; +"App icon" = "Icono de la aplicación"; /* No comment provided by engineer. */ "App passcode" = "Código de acceso de la aplicación"; @@ -817,7 +817,8 @@ /* rcv group event chat item */ "blocked %@" = "ha bloqueado a %@"; -/* marked deleted chat item preview text */ +/* blocked chat item +marked deleted chat item preview text */ "blocked by admin" = "bloqueado por administrador"; /* No comment provided by engineer. */ @@ -862,6 +863,9 @@ /* 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 (predeterminado) o [por conexión](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; +/* No comment provided by engineer. */ +"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "Al usar SimpleX Chat, aceptas:\n- enviar únicamente contenido legal en los grupos públicos.\n- respetar a los demás usuarios – spam prohibido."; + /* No comment provided by engineer. */ "call" = "llamada"; @@ -902,7 +906,7 @@ "Can't message member" = "No se pueden enviar mensajes al miembro"; /* alert action - alert button */ +alert button */ "Cancel" = "Cancelar"; /* No comment provided by engineer. */ @@ -960,7 +964,7 @@ "Change self-destruct mode" = "Cambiar el modo de autodestrucción"; /* authentication reason - set passcode view */ +set passcode view */ "Change self-destruct passcode" = "Cambiar código autodestrucción"; /* chat item text */ @@ -1033,10 +1037,10 @@ "Chat theme" = "Tema de chat"; /* No comment provided by engineer. */ -"Chat will be deleted for all members - this cannot be undone!" = "El chat será eliminado para todos los miembros. ¡No podrá deshacerse!"; +"Chat will be deleted for all members - this cannot be undone!" = "El chat será eliminado para todos los miembros. ¡No puede deshacerse!"; /* No comment provided by engineer. */ -"Chat will be deleted for you - this cannot be undone!" = "El chat será eliminado para tí. ¡No podrá deshacerse!"; +"Chat will be deleted for you - this cannot be undone!" = "El chat será eliminado para tí. ¡No puede deshacerse!"; /* No comment provided by engineer. */ "Chats" = "Chats"; @@ -1128,12 +1132,6 @@ /* No comment provided by engineer. */ "Conditions of use" = "Condiciones de uso"; -/* No comment provided by engineer. */ -"Conditions will be accepted for enabled operators after 30 days." = "Las condiciones de los operadores habilitados serán aceptadas después de 30 días."; - -/* No comment provided by engineer. */ -"Conditions will be accepted for operator(s): **%@**." = "Las condiciones serán aceptadas para el/los operador(es): **%@**."; - /* No comment provided by engineer. */ "Conditions will be accepted for the operator(s): **%@**." = "Las condiciones serán aceptadas para el/los operador(es): **%@**."; @@ -1146,6 +1144,9 @@ /* No comment provided by engineer. */ "Configure ICE servers" = "Configure servidores ICE"; +/* No comment provided by engineer. */ +"Configure server operators" = "Configurar operadores de servidores"; + /* No comment provided by engineer. */ "Confirm" = "Confirmar"; @@ -1357,7 +1358,7 @@ "Contact preferences" = "Preferencias de contacto"; /* No comment provided by engineer. */ -"Contact will be deleted - this cannot be undone!" = "El contacto será eliminado. ¡No podrá deshacerse!"; +"Contact will be deleted - this cannot be undone!" = "El contacto será eliminado. ¡No puede deshacerse!"; /* No comment provided by engineer. */ "Contacts" = "Contactos"; @@ -1555,7 +1556,7 @@ "decryption errors" = "errores de descifrado"; /* delete after time - pref value */ +pref value */ "default (%@)" = "predeterminado (%@)"; /* No comment provided by engineer. */ @@ -1565,7 +1566,7 @@ "default (yes)" = "predeterminado (sí)"; /* alert action - swipe action */ +swipe action */ "Delete" = "Eliminar"; /* No comment provided by engineer. */ @@ -1671,7 +1672,7 @@ "Delete old database?" = "¿Eliminar base de datos antigua?"; /* No comment provided by engineer. */ -"Delete or moderate up to 200 messages." = "Borra o modera hasta 200 mensajes a la vez."; +"Delete or moderate up to 200 messages." = "Elimina o modera hasta 200 mensajes a la vez."; /* No comment provided by engineer. */ "Delete pending connection?" = "¿Eliminar conexión pendiente?"; @@ -1683,7 +1684,7 @@ "Delete queue" = "Eliminar cola"; /* No comment provided by engineer. */ -"Delete report" = "Borrar informe"; +"Delete report" = "Eliminar informe"; /* No comment provided by engineer. */ "Delete up to 20 messages at once." = "Elimina hasta 20 mensajes a la vez."; @@ -1881,7 +1882,7 @@ "Downgrade and open chat" = "Degradar y abrir Chat"; /* alert button - chat item action */ +chat item action */ "Download" = "Descargar"; /* No comment provided by engineer. */ @@ -1944,9 +1945,6 @@ /* No comment provided by engineer. */ "Enable camera access" = "Permitir acceso a la cámara"; -/* No comment provided by engineer. */ -"Enable Flux" = "Habilita Flux"; - /* No comment provided by engineer. */ "Enable for all" = "Activar para todos"; @@ -2325,7 +2323,9 @@ /* No comment provided by engineer. */ "Error: " = "Error: "; -/* alert message */ +/* alert message +file error text +snd error text */ "Error: %@" = "Error: %@"; /* No comment provided by engineer. */ @@ -2343,9 +2343,6 @@ /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Incluso si está desactivado para la conversación."; -/* No comment provided by engineer. */ -"event happened" = "evento ocurrido"; - /* No comment provided by engineer. */ "Exit without saving" = "Salir sin guardar"; @@ -2407,7 +2404,7 @@ "File is blocked by server operator:\n%@." = "Archivo bloqueado por el operador del servidor\n%@."; /* file error text */ -"File not found - most likely file was deleted or cancelled." = "Archivo no encontrado, probablemente haya sido borrado o cancelado."; +"File not found - most likely file was deleted or cancelled." = "Archivo no encontrado, probablemente haya sido eliminado o cancelado."; /* file error text */ "File server error: %@" = "Error del servidor de archivos: %@"; @@ -2484,9 +2481,6 @@ /* No comment provided by engineer. */ "For all moderators" = "Para todos los moderadores"; -/* No comment provided by engineer. */ -"for better metadata privacy." = "para mejorar la privacidad de los metadatos."; - /* servers error */ "For chat profile %@:" = "Para el perfil de chat %@:"; @@ -2641,10 +2635,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 será eliminado 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 puede deshacerse!"; /* No comment provided by engineer. */ -"Group will be deleted for you - this cannot be undone!" = "El grupo será eliminado para tí. ¡No podrá deshacerse!"; +"Group will be deleted for you - this cannot be undone!" = "El grupo será eliminado para tí. ¡No puede deshacerse!"; /* No comment provided by engineer. */ "Groups" = "Grupos"; @@ -3148,7 +3142,7 @@ "Member inactive" = "Miembro inactivo"; /* chat feature */ -"Member reports" = "Informes de miembro"; +"Member reports" = "Informes de miembros"; /* No comment provided by engineer. */ "Member role will be changed to \"%@\". All chat members will be notified." = "El rol del miembro cambiará a \"%@\" y todos serán notificados."; @@ -3160,10 +3154,10 @@ "Member role will be changed to \"%@\". The member will receive a new invitation." = "El rol del miembro cambiará a \"%@\" y recibirá una invitación nueva."; /* No comment provided by engineer. */ -"Member will be removed from chat - this cannot be undone!" = "El miembro será eliminado del chat. ¡No podrá deshacerse!"; +"Member will be removed from chat - this cannot be undone!" = "El miembro será eliminado del chat. ¡No puede deshacerse!"; /* No comment provided by engineer. */ -"Member will be removed from group - this cannot be undone!" = "El miembro será expulsado del grupo. ¡No podrá deshacerse!"; +"Member will be removed from group - this cannot be undone!" = "El miembro será expulsado del grupo. ¡No puede deshacerse!"; /* No comment provided by engineer. */ "Members can add message reactions." = "Los miembros pueden añadir reacciones a los mensajes."; @@ -3265,7 +3259,7 @@ "Messages from %@ will be shown!" = "¡Los mensajes de %@ serán mostrados!"; /* alert message */ -"Messages in this chat will never be deleted." = "Los mensajes de esta conversación nunca se borran."; +"Messages in this chat will never be deleted." = "Los mensajes de esta conversación nunca se eliminan."; /* No comment provided by engineer. */ "Messages received" = "Mensajes recibidos"; @@ -3274,7 +3268,7 @@ "Messages sent" = "Mensajes enviados"; /* alert message */ -"Messages were deleted after you selected them." = "Los mensajes han sido borrados después de seleccionarlos."; +"Messages were deleted after you selected them." = "Los mensajes han sido eliminados después de seleccionarlos."; /* No comment provided by engineer. */ "Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Los mensajes, archivos y llamadas están protegidos mediante **cifrado de extremo a extremo** con secreto perfecto hacía adelante, repudio y recuperación tras ataque."; @@ -3589,8 +3583,8 @@ "observer" = "observador"; /* enabled status - group pref value - time to disappear */ +group pref value +time to disappear */ "off" = "desactivado"; /* blur media */ @@ -3914,9 +3908,15 @@ /* No comment provided by engineer. */ "Privacy for your customers." = "Privacidad para tus clientes."; +/* No comment provided by engineer. */ +"Privacy policy and conditions of use." = "Política de privacidad y condiciones de uso."; + /* No comment provided by engineer. */ "Privacy redefined" = "Privacidad redefinida"; +/* No comment provided by engineer. */ +"Private chats, groups and your contacts are not accessible to server operators." = "Los chats privados, los grupos y tus contactos no son accesibles para los operadores de servidores."; + /* No comment provided by engineer. */ "Private filenames" = "Nombres de archivos privados"; @@ -4029,7 +4029,7 @@ "Rate the app" = "Valora la aplicación"; /* No comment provided by engineer. */ -"Reachable chat toolbar" = "Barra de chat accesible"; +"Reachable chat toolbar" = "Barra de menú accesible"; /* chat item menu */ "React…" = "Reacciona…"; @@ -4143,7 +4143,7 @@ "Registered" = "Registrado"; /* reject incoming call via notification - swipe action */ +swipe action */ "Reject" = "Rechazar"; /* No comment provided by engineer. */ @@ -4311,9 +4311,6 @@ /* No comment provided by engineer. */ "Review conditions" = "Revisar condiciones"; -/* No comment provided by engineer. */ -"Review later" = "Revisar más tarde"; - /* No comment provided by engineer. */ "Revoke" = "Revocar"; @@ -4336,7 +4333,7 @@ "Safer groups" = "Grupos más seguros"; /* alert button - chat item action */ +chat item action */ "Save" = "Guardar"; /* alert button */ @@ -4655,7 +4652,7 @@ "Servers info" = "Info servidores"; /* No comment provided by engineer. */ -"Servers statistics will be reset - this cannot be undone!" = "Las estadísticas de los servidores serán restablecidas. ¡No podrá deshacerse!"; +"Servers statistics will be reset - this cannot be undone!" = "Las estadísticas de los servidores serán restablecidas. ¡No puede deshacerse!"; /* No comment provided by engineer. */ "Session code" = "Código de sesión"; @@ -4712,7 +4709,7 @@ "Shape profile images" = "Dar forma a las imágenes de perfil"; /* alert action - chat item action */ +chat item action */ "Share" = "Compartir"; /* No comment provided by engineer. */ @@ -4788,7 +4785,7 @@ "SimpleX Address" = "Dirección SimpleX"; /* No comment provided by engineer. */ -"SimpleX address and 1-time links are safe to share via any messenger." = "Compartir los enlaces de un uso y las direcciones SimpleX es seguro a través de cualquier medio."; +"SimpleX address and 1-time links are safe to share via any messenger." = "Compartir enlaces de un solo uso y direcciones SimpleX es seguro a través de cualquier medio."; /* No comment provided by engineer. */ "SimpleX address or 1-time link?" = "¿Dirección SimpleX o enlace de un uso?"; @@ -4878,7 +4875,7 @@ "Somebody" = "Alguien"; /* blocking reason - report reason */ +report reason */ "Spam" = "Spam"; /* No comment provided by engineer. */ @@ -5062,7 +5059,7 @@ "The attempt to change database passphrase was not completed." = "El intento de cambiar la contraseña de la base de datos no se ha completado."; /* No comment provided by engineer. */ -"The code you scanned is not a SimpleX link QR code." = "El código QR escaneado no es un enlace SimpleX."; +"The code you scanned is not a SimpleX link QR code." = "El código QR escaneado no es un enlace de SimpleX."; /* No comment provided by engineer. */ "The connection reached the limit of undelivered messages, your contact may be offline." = "La conexión ha alcanzado el límite de mensajes no entregados. es posible que tu contacto esté desconectado."; @@ -5109,9 +5106,6 @@ /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Las mismas condiciones se aplicarán al operador **%@**."; -/* No comment provided by engineer. */ -"The same conditions will apply to operator(s): **%@**." = "Las mismas condiciones se aplicarán a el/los operador(es) **%@**."; - /* No comment provided by engineer. */ "The second preset operator in the app!" = "¡Segundo operador predefinido!"; @@ -5128,7 +5122,7 @@ "The servers for new files of your current chat profile **%@**." = "Servidores para enviar archivos en tu perfil **%@**."; /* No comment provided by engineer. */ -"The text you pasted is not a SimpleX link." = "El texto pegado no es un enlace SimpleX."; +"The text you pasted is not a SimpleX link." = "El texto pegado no es un enlace de SimpleX."; /* No comment provided by engineer. */ "The uploaded database archive will be permanently removed from the servers." = "El archivo de bases de datos subido será eliminado permanentemente de los servidores."; @@ -5152,7 +5146,7 @@ "This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "Esta acción es irreversible. Se eliminarán los mensajes enviados y recibidos anteriores a la selección. Podría tardar varios minutos."; /* alert message */ -"This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted." = "Todos los mensajes previos al período seleccionado serán eliminados del chat. ¡No podrá deshacerse!"; +"This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted." = "Todos los mensajes previos al período seleccionado serán eliminados del chat. ¡No puede deshacerse!"; /* No comment provided by engineer. */ "This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Esta acción es irreversible. Tu perfil, contactos, mensajes y archivos se perderán irreversiblemente."; @@ -5188,7 +5182,7 @@ "This link was used with another mobile device, please create a new link on the desktop." = "Este enlace ha sido usado en otro dispositivo móvil, por favor crea un enlace nuevo en el ordenador."; /* No comment provided by engineer. */ -"This message was deleted or not received yet." = "El mensaje ha sido borrado o aún no se ha recibido."; +"This message was deleted or not received yet." = "El mensaje ha sido eliminado o aún no se ha recibido."; /* No comment provided by engineer. */ "This setting applies to messages in your current chat profile **%@**." = "Esta configuración se aplica a los mensajes del perfil actual **%@**."; @@ -5221,7 +5215,7 @@ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "Para proteger tu dirección IP, el enrutamiento privado usa tu lista de servidores SMP para enviar mensajes."; /* No comment provided by engineer. */ -"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Para proteger tu privacidad, en lugar de los identificadores de usuario que usan el resto de plataformas, SimpleX dispone de identificadores para las colas de mensajes, independientes para cada uno de tus contactos."; +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Para proteger tu privacidad, SimpleX usa identificadores distintos para cada uno de tus contactos."; /* No comment provided by engineer. */ "To receive" = "Para recibir"; @@ -5766,9 +5760,6 @@ /* No comment provided by engineer. */ "You can change it in Appearance settings." = "Puedes cambiar la posición de la barra desde el menú Apariencia."; -/* No comment provided by engineer. */ -"You can configure operators in Network & servers settings." = "Puedes configurar los operadores desde Servidores y Redes."; - /* No comment provided by engineer. */ "You can configure servers via settings." = "Puedes configurar los servidores a través de su configuración."; @@ -6015,6 +6006,3 @@ /* No comment provided by engineer. */ "Your SimpleX address" = "Mi dirección SimpleX"; -/* No comment provided by engineer. */ -"Your SMP servers" = "Servidores SMP"; - diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index 33a2acff78..c4031adf9a 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -182,7 +182,7 @@ "0s" = "0s"; /* delete after time - time interval */ +time interval */ "1 day" = "1 päivä"; /* time interval */ @@ -192,11 +192,11 @@ "1 minute" = "1 minuutti"; /* delete after time - time interval */ +time interval */ "1 month" = "1 kuukausi"; /* delete after time - time interval */ +time interval */ "1 week" = "1 viikko"; /* No comment provided by engineer. */ @@ -239,8 +239,8 @@ "above, then choose:" = "edellä, valitse sitten:"; /* accept contact request via notification - accept incoming call via notification - swipe action */ +accept incoming call via notification +swipe action */ "Accept" = "Hyväksy"; /* No comment provided by engineer. */ @@ -250,7 +250,7 @@ "Accept contact request from %@?" = "Hyväksy kontaktipyyntö %@:ltä?"; /* accept contact request via notification - swipe action */ +swipe action */ "Accept incognito" = "Hyväksy tuntematon"; /* call status */ @@ -509,7 +509,7 @@ "Can't invite contacts!" = "Kontakteja ei voi kutsua!"; /* alert action - alert button */ +alert button */ "Cancel" = "Peruuta"; /* feature offered item */ @@ -549,7 +549,7 @@ "Change self-destruct mode" = "Vaihda itsetuhotilaa"; /* authentication reason - set passcode view */ +set passcode view */ "Change self-destruct passcode" = "Vaihda itsetuhoutuva pääsykoodi"; /* chat item text */ @@ -868,7 +868,7 @@ "Decryption error" = "Salauksen purkuvirhe"; /* delete after time - pref value */ +pref value */ "default (%@)" = "oletusarvo (%@)"; /* No comment provided by engineer. */ @@ -878,7 +878,7 @@ "default (yes)" = "oletusarvo (kyllä)"; /* alert action - swipe action */ +swipe action */ "Delete" = "Poista"; /* No comment provided by engineer. */ @@ -1358,7 +1358,9 @@ /* No comment provided by engineer. */ "Error: " = "Virhe: "; -/* alert message */ +/* alert message +file error text +snd error text */ "Error: %@" = "Virhe: %@"; /* No comment provided by engineer. */ @@ -1370,9 +1372,6 @@ /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Jopa kun ei käytössä keskustelussa."; -/* No comment provided by engineer. */ -"event happened" = "tapahtuma tapahtui"; - /* No comment provided by engineer. */ "Exit without saving" = "Poistu tallentamatta"; @@ -2076,8 +2075,8 @@ "observer" = "tarkkailija"; /* enabled status - group pref value - time to disappear */ +group pref value +time to disappear */ "off" = "pois"; /* blur media */ @@ -2381,7 +2380,7 @@ "Reduced battery usage" = "Pienempi akun käyttö"; /* reject incoming call via notification - swipe action */ +swipe action */ "Reject" = "Hylkää"; /* No comment provided by engineer. */ @@ -2481,7 +2480,7 @@ "Run chat" = "Käynnistä chat"; /* alert button - chat item action */ +chat item action */ "Save" = "Tallenna"; /* alert button */ @@ -2689,7 +2688,7 @@ "Settings" = "Asetukset"; /* alert action - chat item action */ +chat item action */ "Share" = "Jaa"; /* No comment provided by engineer. */ @@ -3406,6 +3405,3 @@ /* No comment provided by engineer. */ "Your SimpleX address" = "SimpleX-osoitteesi"; -/* No comment provided by engineer. */ -"Your SMP servers" = "SMP-palvelimesi"; - diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index ad33963572..9990bd4a3e 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -266,7 +266,7 @@ "0s" = "0s"; /* delete after time - time interval */ +time interval */ "1 day" = "1 jour"; /* time interval */ @@ -276,11 +276,11 @@ "1 minute" = "1 minute"; /* delete after time - time interval */ +time interval */ "1 month" = "1 mois"; /* delete after time - time interval */ +time interval */ "1 week" = "1 semaine"; /* No comment provided by engineer. */ @@ -335,8 +335,8 @@ "Accent" = "Principale"; /* accept contact request via notification - accept incoming call via notification - swipe action */ +accept incoming call via notification +swipe action */ "Accept" = "Accepter"; /* No comment provided by engineer. */ @@ -349,7 +349,7 @@ "Accept contact request from %@?" = "Accepter la demande de contact de %@ ?"; /* accept contact request via notification - swipe action */ +swipe action */ "Accept incognito" = "Accepter en incognito"; /* call status */ @@ -754,7 +754,8 @@ /* rcv group event chat item */ "blocked %@" = "%@ bloqué"; -/* marked deleted chat item preview text */ +/* blocked chat item +marked deleted chat item preview text */ "blocked by admin" = "bloqué par l'administrateur"; /* No comment provided by engineer. */ @@ -836,7 +837,7 @@ "Can't message member" = "Impossible d'envoyer un message à ce membre"; /* alert action - alert button */ +alert button */ "Cancel" = "Annuler"; /* No comment provided by engineer. */ @@ -891,7 +892,7 @@ "Change self-destruct mode" = "Modifier le mode d'autodestruction"; /* authentication reason - set passcode view */ +set passcode view */ "Change self-destruct passcode" = "Modifier le code d'autodestruction"; /* chat item text */ @@ -1050,12 +1051,6 @@ /* No comment provided by engineer. */ "Conditions of use" = "Conditions d'utilisation"; -/* No comment provided by engineer. */ -"Conditions will be accepted for enabled operators after 30 days." = "Les conditions seront acceptées pour les opérateurs activés après 30 jours."; - -/* No comment provided by engineer. */ -"Conditions will be accepted for operator(s): **%@**." = "Les conditions seront acceptées pour le(s) opérateur(s) : **%@**."; - /* No comment provided by engineer. */ "Conditions will be accepted for the operator(s): **%@**." = "Les conditions seront acceptées pour le(s) opérateur(s) : **%@**."; @@ -1456,7 +1451,7 @@ "decryption errors" = "Erreurs de déchiffrement"; /* delete after time - pref value */ +pref value */ "default (%@)" = "défaut (%@)"; /* No comment provided by engineer. */ @@ -1466,7 +1461,7 @@ "default (yes)" = "par défaut (oui)"; /* alert action - swipe action */ +swipe action */ "Delete" = "Supprimer"; /* No comment provided by engineer. */ @@ -1758,7 +1753,7 @@ "Downgrade and open chat" = "Rétrograder et ouvrir le chat"; /* alert button - chat item action */ +chat item action */ "Download" = "Télécharger"; /* No comment provided by engineer. */ @@ -1821,9 +1816,6 @@ /* No comment provided by engineer. */ "Enable camera access" = "Autoriser l'accès à la caméra"; -/* No comment provided by engineer. */ -"Enable Flux" = "Activer Flux"; - /* No comment provided by engineer. */ "Enable for all" = "Activer pour tous"; @@ -2178,7 +2170,9 @@ /* No comment provided by engineer. */ "Error: " = "Erreur : "; -/* alert message */ +/* alert message +file error text +snd error text */ "Error: %@" = "Erreur : %@"; /* No comment provided by engineer. */ @@ -2196,9 +2190,6 @@ /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Même s'il est désactivé dans la conversation."; -/* No comment provided by engineer. */ -"event happened" = "event happened"; - /* No comment provided by engineer. */ "Exit without saving" = "Quitter sans enregistrer"; @@ -2319,9 +2310,6 @@ /* No comment provided by engineer. */ "Fix not supported by group member" = "Correction non prise en charge par un membre du groupe"; -/* No comment provided by engineer. */ -"for better metadata privacy." = "pour une meilleure protection des métadonnées."; - /* servers error */ "For chat profile %@:" = "Pour le profil de discussion %@ :"; @@ -3328,8 +3316,8 @@ "observer" = "observateur"; /* enabled status - group pref value - time to disappear */ +group pref value +time to disappear */ "off" = "off"; /* blur media */ @@ -3834,7 +3822,7 @@ "Reduced battery usage" = "Réduction de la consommation de batterie"; /* reject incoming call via notification - swipe action */ +swipe action */ "Reject" = "Rejeter"; /* No comment provided by engineer. */ @@ -3969,9 +3957,6 @@ /* No comment provided by engineer. */ "Review conditions" = "Vérifier les conditions"; -/* No comment provided by engineer. */ -"Review later" = "Vérifier plus tard"; - /* No comment provided by engineer. */ "Revoke" = "Révoquer"; @@ -3994,7 +3979,7 @@ "Safer groups" = "Groupes plus sûrs"; /* alert button - chat item action */ +chat item action */ "Save" = "Enregistrer"; /* alert button */ @@ -4358,7 +4343,7 @@ "Shape profile images" = "Images de profil modelable"; /* alert action - chat item action */ +chat item action */ "Share" = "Partager"; /* No comment provided by engineer. */ @@ -4742,9 +4727,6 @@ /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Les mêmes conditions s'appliquent à l'opérateur **%@**."; -/* No comment provided by engineer. */ -"The same conditions will apply to operator(s): **%@**." = "Les mêmes conditions s'appliquent à(aux) l'opérateur(s) : **%@**."; - /* No comment provided by engineer. */ "The second preset operator in the app!" = "Le deuxième opérateur prédéfini de l'application !"; @@ -5378,9 +5360,6 @@ /* No comment provided by engineer. */ "You can change it in Appearance settings." = "Vous pouvez choisir de le modifier dans les paramètres d'apparence."; -/* No comment provided by engineer. */ -"You can configure operators in Network & servers settings." = "Vous pouvez définir les opérateurs dans les paramètres Réseau et serveurs."; - /* No comment provided by engineer. */ "You can configure servers via settings." = "Vous pouvez configurer les serveurs via les paramètres."; @@ -5624,6 +5603,3 @@ /* No comment provided by engineer. */ "Your SimpleX address" = "Votre adresse SimpleX"; -/* No comment provided by engineer. */ -"Your SMP servers" = "Vos serveurs SMP"; - diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index 64b087aa59..f21ba8f6b3 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -26,7 +26,7 @@ "(this device v%@)" = "(ez az eszköz: v%@)"; /* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Hozzájárulás](https://github.com/simplex-chat/simplex-chat#contribute)"; +"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Közreműködés](https://github.com/simplex-chat/simplex-chat#contribute)"; /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Küldjön nekünk e-mailt](mailto:chat@simplex.chat)"; @@ -155,7 +155,7 @@ "%d file(s) are still being downloaded." = "%d fájl letöltése még folyamatban van."; /* forward confirmation reason */ -"%d file(s) failed to download." = "%d fájlt nem sikerült letölteni."; +"%d file(s) failed to download." = "Nem sikerült letölteni %d fájlt."; /* forward confirmation reason */ "%d file(s) were deleted." = "%d fájl törölve lett."; @@ -248,7 +248,7 @@ "%lldw" = "%lldhét"; /* No comment provided by engineer. */ -"%u messages failed to decrypt." = "%u üzenet visszafejtése sikertelen."; +"%u messages failed to decrypt." = "Nem sikerült visszafejteni %u üzenetet."; /* No comment provided by engineer. */ "%u messages skipped." = "%u üzenet kihagyva."; @@ -269,7 +269,7 @@ "0s" = "0s"; /* delete after time - time interval */ +time interval */ "1 day" = "1 nap"; /* time interval */ @@ -279,21 +279,21 @@ "1 minute" = "1 perc"; /* delete after time - time interval */ +time interval */ "1 month" = "1 hónap"; /* delete after time - time interval */ +time interval */ "1 week" = "1 hét"; /* delete after time */ "1 year" = "1 év"; /* No comment provided by engineer. */ -"1-time link" = "Egyszer használható meghívási hivatkozás"; +"1-time link" = "Egyszer használható meghívó"; /* No comment provided by engineer. */ -"1-time link can be used *with one contact only* - share in person or via any messenger." = "Az egyszer használható meghívási hivatkozás csak *egyetlen partnerrel használható* – személyesen vagy bármilyen üzenetváltón keresztül megosztható."; +"1-time link can be used *with one contact only* - share in person or via any messenger." = "Az egyszer használható meghívó egy hivatkozás és *csak egyetlen partnerrel használható* – személyesen vagy bármilyen üzenetváltó-alkalmazáson keresztül megosztható."; /* No comment provided by engineer. */ "5 minutes" = "5 perc"; @@ -341,21 +341,21 @@ "Accent" = "Kiemelőszín"; /* accept contact request via notification - accept incoming call via notification - swipe action */ +accept incoming call via notification +swipe action */ "Accept" = "Elfogadás"; /* No comment provided by engineer. */ "Accept conditions" = "Feltételek elfogadása"; /* No comment provided by engineer. */ -"Accept connection request?" = "Elfogadja a kapcsolódási kérést?"; +"Accept connection request?" = "Elfogadja a meghívási kérést?"; /* notification body */ -"Accept contact request from %@?" = "Elfogadja %@ kapcsolódási kérését?"; +"Accept contact request from %@?" = "Elfogadja %@ meghívási kérését?"; /* accept contact request via notification - swipe action */ +swipe action */ "Accept incognito" = "Elfogadás inkognitóban"; /* call status */ @@ -434,7 +434,7 @@ "Address change will be aborted. Old receiving address will be used." = "A cím módosítása meg fog szakadni. A régi fogadási cím lesz használva."; /* No comment provided by engineer. */ -"Address or 1-time link?" = "Cím vagy egyszer használható meghívási hivatkozás?"; +"Address or 1-time link?" = "Cím vagy egyszer használható meghívó?"; /* No comment provided by engineer. */ "Address settings" = "Címbeállítások"; @@ -731,7 +731,7 @@ "Auto-accept" = "Automatikus elfogadás"; /* No comment provided by engineer. */ -"Auto-accept contact requests" = "Kapcsolatkérések automatikus elfogadása"; +"Auto-accept contact requests" = "Meghívási kérések automatikus elfogadása"; /* No comment provided by engineer. */ "Auto-accept images" = "Képek automatikus elfogadása"; @@ -817,7 +817,8 @@ /* rcv group event chat item */ "blocked %@" = "letiltotta őt: %@"; -/* marked deleted chat item preview text */ +/* blocked chat item +marked deleted chat item preview text */ "blocked by admin" = "letiltva az adminisztrátor által"; /* No comment provided by engineer. */ @@ -862,6 +863,9 @@ /* 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)." = "A csevegési profillal (alapértelmezett), vagy a [kapcsolattal] (https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BÉTA)."; +/* No comment provided by engineer. */ +"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "A SimpleX Chat használatával Ön elfogadja, hogy:\n- csak elfogadott tartalmakat tesz közzé a nyilvános csoportokban.\n- tiszteletben tartja a többi felhasználót, és nem küld kéretlen tartalmat senkinek."; + /* No comment provided by engineer. */ "call" = "hívás"; @@ -902,7 +906,7 @@ "Can't message member" = "Nem lehet üzenetet küldeni a tagnak"; /* alert action - alert button */ +alert button */ "Cancel" = "Mégse"; /* No comment provided by engineer. */ @@ -960,7 +964,7 @@ "Change self-destruct mode" = "Önmegsemmisítő-mód módosítása"; /* authentication reason - set passcode view */ +set passcode view */ "Change self-destruct passcode" = "Önmegsemmisítő-jelkód módosítása"; /* chat item text */ @@ -1128,12 +1132,6 @@ /* No comment provided by engineer. */ "Conditions of use" = "Használati feltételek"; -/* No comment provided by engineer. */ -"Conditions will be accepted for enabled operators after 30 days." = "A feltételek 30 nap elteltével lesznek elfogadva az engedélyezett üzemeltetők számára."; - -/* No comment provided by engineer. */ -"Conditions will be accepted for operator(s): **%@**." = "A feltételek el lesznek fogadva a következő üzemeltető(k) számára: **%@**."; - /* No comment provided by engineer. */ "Conditions will be accepted for the operator(s): **%@**." = "A feltételek el lesznek fogadva a következő üzemeltető(k) számára: **%@**."; @@ -1146,6 +1144,9 @@ /* No comment provided by engineer. */ "Configure ICE servers" = "ICE-kiszolgálók beállítása"; +/* No comment provided by engineer. */ +"Configure server operators" = "Kiszolgálóüzemeltetők beállítása"; + /* No comment provided by engineer. */ "Confirm" = "Megerősítés"; @@ -1201,7 +1202,7 @@ "Connect to yourself?" = "Kapcsolódik saját magához?"; /* No comment provided by engineer. */ -"Connect to yourself?\nThis is your own one-time link!" = "Kapcsolódik saját magához?\nEz az Ön egyszer használható meghívási hivatkozása!"; +"Connect to yourself?\nThis is your own one-time link!" = "Kapcsolódik saját magához?\nEz az Ön egyszer használható meghívója!"; /* No comment provided by engineer. */ "Connect to yourself?\nThis is your own SimpleX address!" = "Kapcsolódik saját magához?\nEz az Ön SimpleX-címe!"; @@ -1213,7 +1214,7 @@ "Connect via link" = "Kapcsolódás egy hivatkozáson keresztül"; /* No comment provided by engineer. */ -"Connect via one-time link" = "Kapcsolódás egyszer használható meghívási hivatkozáson keresztül"; +"Connect via one-time link" = "Kapcsolódás egyszer használható meghívón keresztül"; /* No comment provided by engineer. */ "Connect with %@" = "Kapcsolódás a következővel: %@"; @@ -1300,7 +1301,7 @@ "Connection notifications" = "Kapcsolódási értesítések"; /* No comment provided by engineer. */ -"Connection request sent!" = "Kapcsolatkérés elküldve!"; +"Connection request sent!" = "Meghívási kérés elküldve!"; /* No comment provided by engineer. */ "Connection requires encryption renegotiation." = "A kapcsolat titkosítása újraegyeztetést igényel."; @@ -1393,7 +1394,7 @@ "Create" = "Létrehozás"; /* No comment provided by engineer. */ -"Create 1-time link" = "Egyszer használható meghívási hivatkozás létrehozása"; +"Create 1-time link" = "Egyszer használható meghívó létrehozása"; /* No comment provided by engineer. */ "Create a group using a random profile." = "Csoport létrehozása véletlenszerű profillal."; @@ -1471,7 +1472,7 @@ "Custom time" = "Egyéni időköz"; /* No comment provided by engineer. */ -"Customizable message shape." = "Testre szabható üzenetbuborékok."; +"Customizable message shape." = "Személyre szabható üzenetbuborékok."; /* No comment provided by engineer. */ "Customize theme" = "Téma személyre szabása"; @@ -1555,7 +1556,7 @@ "decryption errors" = "visszafejtési hibák"; /* delete after time - pref value */ +pref value */ "default (%@)" = "alapértelmezett (%@)"; /* No comment provided by engineer. */ @@ -1565,7 +1566,7 @@ "default (yes)" = "alapértelmezett (igen)"; /* alert action - swipe action */ +swipe action */ "Delete" = "Törlés"; /* No comment provided by engineer. */ @@ -1881,7 +1882,7 @@ "Downgrade and open chat" = "Visszafejlesztés és a csevegés megnyitása"; /* alert button - chat item action */ +chat item action */ "Download" = "Letöltés"; /* No comment provided by engineer. */ @@ -1944,9 +1945,6 @@ /* No comment provided by engineer. */ "Enable camera access" = "Kamera hozzáférés engedélyezése"; -/* No comment provided by engineer. */ -"Enable Flux" = "Flux engedélyezése"; - /* No comment provided by engineer. */ "Enable for all" = "Engedélyezés az összes tag számára"; @@ -2047,7 +2045,7 @@ "encryption re-negotiation allowed for %@" = "a titkosítás újraegyeztetése engedélyezve van %@ számára"; /* message decrypt error item */ -"Encryption re-negotiation error" = "Hiba a titkosítás újraegyeztetésekor"; +"Encryption re-negotiation error" = "Hiba történt a titkosítás újraegyeztetésekor"; /* No comment provided by engineer. */ "Encryption re-negotiation failed." = "Nem sikerült a titkosítást újraegyeztetni."; @@ -2107,225 +2105,227 @@ "Error" = "Hiba"; /* No comment provided by engineer. */ -"Error aborting address change" = "Hiba a cím módosításának megszakításakor"; +"Error aborting address change" = "Hiba történt a cím módosításának megszakításakor"; /* alert title */ -"Error accepting conditions" = "Hiba a feltételek elfogadásakor"; +"Error accepting conditions" = "Hiba történt a feltételek elfogadásakor"; /* No comment provided by engineer. */ -"Error accepting contact request" = "Hiba történt a kapcsolatkérés elfogadásakor"; +"Error accepting contact request" = "Hiba történt a meghívási kérés elfogadásakor"; /* No comment provided by engineer. */ -"Error adding member(s)" = "Hiba a tag(ok) hozzáadásakor"; +"Error adding member(s)" = "Hiba történt a tag(ok) hozzáadásakor"; /* alert title */ -"Error adding server" = "Hiba a kiszolgáló hozzáadásakor"; +"Error adding server" = "Hiba történt a kiszolgáló hozzáadásakor"; /* No comment provided by engineer. */ -"Error changing address" = "Hiba a cím módosításakor"; +"Error changing address" = "Hiba történt a cím módosításakor"; /* No comment provided by engineer. */ -"Error changing connection profile" = "Hiba a kapcsolati profilra való váltáskor"; +"Error changing connection profile" = "Hiba történt a kapcsolati profilra való váltáskor"; /* No comment provided by engineer. */ -"Error changing role" = "Hiba a szerepkör módosításakor"; +"Error changing role" = "Hiba történt a szerepkör módosításakor"; /* No comment provided by engineer. */ -"Error changing setting" = "Hiba a beállítás módosításakor"; +"Error changing setting" = "Hiba történt a beállítás módosításakor"; /* No comment provided by engineer. */ -"Error changing to incognito!" = "Hiba az inkognitóprofilra való váltáskor!"; +"Error changing to incognito!" = "Hiba történt az inkognitóprofilra való váltáskor!"; /* No comment provided by engineer. */ -"Error checking token status" = "Hiba a token állapotának ellenőrzésekor"; +"Error checking token status" = "Hiba történt a token állapotának ellenőrzésekor"; /* No comment provided by engineer. */ -"Error connecting to forwarding server %@. Please try later." = "Hiba a(z) %@ továbbítókiszolgálóhoz való kapcsolódáskor. Próbálja meg később."; +"Error connecting to forwarding server %@. Please try later." = "Hiba történt a(z) %@ továbbítókiszolgálóhoz való kapcsolódáskor. Próbálja meg később."; /* No comment provided by engineer. */ -"Error creating address" = "Hiba a cím létrehozásakor"; +"Error creating address" = "Hiba történt a cím létrehozásakor"; /* No comment provided by engineer. */ -"Error creating group" = "Hiba a csoport létrehozásakor"; +"Error creating group" = "Hiba történt a csoport létrehozásakor"; /* No comment provided by engineer. */ -"Error creating group link" = "Hiba a csoporthivatkozás létrehozásakor"; +"Error creating group link" = "Hiba történt a csoporthivatkozás létrehozásakor"; /* alert title */ -"Error creating list" = "Hiba a lista létrehozásakor"; +"Error creating list" = "Hiba történt a lista létrehozásakor"; /* No comment provided by engineer. */ -"Error creating member contact" = "Hiba a partnerrel történő kapcsolat létrehozásában"; +"Error creating member contact" = "Hiba történt a partnerrel történő kapcsolat létrehozásában"; /* No comment provided by engineer. */ -"Error creating message" = "Hiba az üzenet létrehozásakor"; +"Error creating message" = "Hiba történt az üzenet létrehozásakor"; /* No comment provided by engineer. */ -"Error creating profile!" = "Hiba a profil létrehozásakor!"; +"Error creating profile!" = "Hiba történt a profil létrehozásakor!"; /* No comment provided by engineer. */ -"Error creating report" = "Hiba a jelentés létrehozásakor"; +"Error creating report" = "Hiba történt a jelentés létrehozásakor"; /* No comment provided by engineer. */ -"Error decrypting file" = "Hiba a fájl visszafejtésekor"; +"Error decrypting file" = "Hiba történt a fájl visszafejtésekor"; /* No comment provided by engineer. */ -"Error deleting chat database" = "Hiba a csevegési adatbázis törlésekor"; +"Error deleting chat database" = "Hiba történt a csevegési adatbázis törlésekor"; /* No comment provided by engineer. */ -"Error deleting chat!" = "Hiba a csevegés törlésekor!"; +"Error deleting chat!" = "Hiba történt a csevegés törlésekor!"; /* No comment provided by engineer. */ -"Error deleting connection" = "Hiba a kapcsolat törlésekor"; +"Error deleting connection" = "Hiba történt a kapcsolat törlésekor"; /* No comment provided by engineer. */ -"Error deleting database" = "Hiba az adatbázis törlésekor"; +"Error deleting database" = "Hiba történt az adatbázis törlésekor"; /* No comment provided by engineer. */ -"Error deleting old database" = "Hiba a régi adatbázis törlésekor"; +"Error deleting old database" = "Hiba történt a régi adatbázis törlésekor"; /* No comment provided by engineer. */ -"Error deleting token" = "Hiba a token törlésekor"; +"Error deleting token" = "Hiba történt a token törlésekor"; /* No comment provided by engineer. */ -"Error deleting user profile" = "Hiba a felhasználó-profil törlésekor"; +"Error deleting user profile" = "Hiba történt a felhasználó-profil törlésekor"; /* No comment provided by engineer. */ -"Error downloading the archive" = "Hiba az archívum letöltésekor"; +"Error downloading the archive" = "Hiba történt az archívum letöltésekor"; /* No comment provided by engineer. */ -"Error enabling delivery receipts!" = "Hiba a kézbesítési jelentések engedélyezésekor!"; +"Error enabling delivery receipts!" = "Hiba történt a kézbesítési jelentések engedélyezésekor!"; /* No comment provided by engineer. */ -"Error enabling notifications" = "Hiba az értesítések engedélyezésekor"; +"Error enabling notifications" = "Hiba történt az értesítések engedélyezésekor"; /* No comment provided by engineer. */ -"Error encrypting database" = "Hiba az adatbázis titkosításakor"; +"Error encrypting database" = "Hiba történt az adatbázis titkosításakor"; /* No comment provided by engineer. */ -"Error exporting chat database" = "Hiba a csevegési adatbázis exportálásakor"; +"Error exporting chat database" = "Hiba történt a csevegési adatbázis exportálásakor"; /* No comment provided by engineer. */ -"Error exporting theme: %@" = "Hiba a téma exportálásakor: %@"; +"Error exporting theme: %@" = "Hiba történt a téma exportálásakor: %@"; /* No comment provided by engineer. */ -"Error importing chat database" = "Hiba a csevegési adatbázis importálásakor"; +"Error importing chat database" = "Hiba történt a csevegési adatbázis importálásakor"; /* No comment provided by engineer. */ -"Error joining group" = "Hiba a csoporthoz való csatlakozáskor"; +"Error joining group" = "Hiba történt a csoporthoz való csatlakozáskor"; /* alert title */ -"Error loading servers" = "Hiba a kiszolgálók betöltésekor"; +"Error loading servers" = "Hiba történt a kiszolgálók betöltésekor"; /* No comment provided by engineer. */ -"Error migrating settings" = "Hiba a beállítások átköltöztetésekor"; +"Error migrating settings" = "Hiba történt a beállítások átköltöztetésekor"; /* No comment provided by engineer. */ -"Error opening chat" = "Hiba a csevegés megnyitásakor"; +"Error opening chat" = "Hiba történt a csevegés megnyitásakor"; /* alert title */ -"Error receiving file" = "Hiba a fájl fogadásakor"; +"Error receiving file" = "Hiba történt a fájl fogadásakor"; /* No comment provided by engineer. */ -"Error reconnecting server" = "Hiba a kiszolgálóhoz való újrakapcsolódáskor"; +"Error reconnecting server" = "Hiba történt a kiszolgálóhoz való újrakapcsolódáskor"; /* No comment provided by engineer. */ -"Error reconnecting servers" = "Hiba a kiszolgálókhoz való újrakapcsolódáskor"; +"Error reconnecting servers" = "Hiba történt a kiszolgálókhoz való újrakapcsolódáskor"; /* alert title */ -"Error registering for notifications" = "Hiba az értesítések regisztrálásakor"; +"Error registering for notifications" = "Hiba történt az értesítések regisztrálásakor"; /* No comment provided by engineer. */ -"Error removing member" = "Hiba a tag eltávolításakor"; +"Error removing member" = "Hiba történt a tag eltávolításakor"; /* alert title */ -"Error reordering lists" = "Hiba a listák újrarendezésekor"; +"Error reordering lists" = "Hiba történt a listák újrarendezésekor"; /* No comment provided by engineer. */ -"Error resetting statistics" = "Hiba a statisztikák visszaállításakor"; +"Error resetting statistics" = "Hiba történt a statisztikák visszaállításakor"; /* alert title */ -"Error saving chat list" = "Hiba a csevegési lista mentésekor"; +"Error saving chat list" = "Hiba történt a csevegési lista mentésekor"; /* No comment provided by engineer. */ -"Error saving group profile" = "Hiba a csoportprofil mentésekor"; +"Error saving group profile" = "Hiba történt a csoportprofil mentésekor"; /* No comment provided by engineer. */ -"Error saving ICE servers" = "Hiba az ICE-kiszolgálók mentésekor"; +"Error saving ICE servers" = "Hiba történt az ICE-kiszolgálók mentésekor"; /* No comment provided by engineer. */ -"Error saving passcode" = "Hiba a jelkód mentésekor"; +"Error saving passcode" = "Hiba történt a jelkód mentésekor"; /* No comment provided by engineer. */ -"Error saving passphrase to keychain" = "Hiba a jelmondat kulcstartóba történő mentésekor"; +"Error saving passphrase to keychain" = "Hiba történt a jelmondat kulcstartóba történő mentésekor"; /* alert title */ -"Error saving servers" = "Hiba a kiszolgálók mentésekor"; +"Error saving servers" = "Hiba történt a kiszolgálók mentésekor"; /* when migrating */ -"Error saving settings" = "Hiba a beállítások mentésekor"; +"Error saving settings" = "Hiba történt a beállítások mentésekor"; /* No comment provided by engineer. */ -"Error saving user password" = "Hiba a felhasználó jelszavának mentésekor"; +"Error saving user password" = "Hiba történt a felhasználó jelszavának mentésekor"; /* No comment provided by engineer. */ -"Error scanning code: %@" = "Hiba a kód beolvasásakor: %@"; +"Error scanning code: %@" = "Hiba történt a kód beolvasásakor: %@"; /* No comment provided by engineer. */ -"Error sending email" = "Hiba az e-mail küldésekor"; +"Error sending email" = "Hiba történt az e-mail elküldésekor"; /* No comment provided by engineer. */ "Error sending member contact invitation" = "Hiba történt a tag kapcsolatfelvételi meghívójának elküldésekor"; /* No comment provided by engineer. */ -"Error sending message" = "Hiba az üzenet küldésekor"; +"Error sending message" = "Hiba történt az üzenet elküldésekor"; /* No comment provided by engineer. */ "Error setting delivery receipts!" = "Hiba történt a kézbesítési jelentések beállításakor!"; /* No comment provided by engineer. */ -"Error starting chat" = "Hiba a csevegés elindításakor"; +"Error starting chat" = "Hiba történt a csevegés elindításakor"; /* No comment provided by engineer. */ -"Error stopping chat" = "Hiba a csevegés megállításakor"; +"Error stopping chat" = "Hiba történt a csevegés megállításakor"; /* No comment provided by engineer. */ -"Error switching profile" = "Hiba a profilváltáskor"; +"Error switching profile" = "Hiba történt a profilváltáskor"; /* alertTitle */ -"Error switching profile!" = "Hiba a profilváltáskor!"; +"Error switching profile!" = "Hiba történt a profilváltáskor!"; /* No comment provided by engineer. */ -"Error synchronizing connection" = "Hiba a kapcsolat szinkronizálásakor"; +"Error synchronizing connection" = "Hiba történt a kapcsolat szinkronizálásakor"; /* No comment provided by engineer. */ -"Error testing server connection" = "Hiba a kiszolgáló kapcsolatának tesztelésekor"; +"Error testing server connection" = "Hiba történt a kiszolgáló kapcsolatának tesztelésekor"; /* No comment provided by engineer. */ -"Error updating group link" = "Hiba a csoporthivatkozás frissítésekor"; +"Error updating group link" = "Hiba történt a csoporthivatkozás frissítésekor"; /* No comment provided by engineer. */ -"Error updating message" = "Hiba az üzenet frissítésekor"; +"Error updating message" = "Hiba történt az üzenet frissítésekor"; /* alert title */ -"Error updating server" = "Hiba a kiszolgáló frissítésekor"; +"Error updating server" = "Hiba történt a kiszolgáló frissítésekor"; /* No comment provided by engineer. */ "Error updating settings" = "Hiba történt a beállítások frissítésekor"; /* No comment provided by engineer. */ -"Error updating user privacy" = "Hiba a felhasználói adatvédelem frissítésekor"; +"Error updating user privacy" = "Hiba történt a felhasználói adatvédelem frissítésekor"; /* No comment provided by engineer. */ -"Error uploading the archive" = "Hiba az archívum feltöltésekor"; +"Error uploading the archive" = "Hiba történt az archívum feltöltésekor"; /* No comment provided by engineer. */ -"Error verifying passphrase:" = "Hiba a jelmondat hitelesítésekor:"; +"Error verifying passphrase:" = "Hiba történt a jelmondat hitelesítésekor:"; /* No comment provided by engineer. */ "Error: " = "Hiba: "; -/* alert message */ +/* alert message +file error text +snd error text */ "Error: %@" = "Hiba: %@"; /* No comment provided by engineer. */ @@ -2343,9 +2343,6 @@ /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Akkor is, ha le van tiltva a beszélgetésben."; -/* No comment provided by engineer. */ -"event happened" = "esemény történt"; - /* No comment provided by engineer. */ "Exit without saving" = "Kilépés mentés nélkül"; @@ -2484,9 +2481,6 @@ /* No comment provided by engineer. */ "For all moderators" = "Az összes moderátor számára"; -/* No comment provided by engineer. */ -"for better metadata privacy." = "a metaadatok jobb védelme érdekében."; - /* servers error */ "For chat profile %@:" = "A(z) %@ nevű csevegési profilhoz:"; @@ -2536,7 +2530,7 @@ "Forwarding %lld messages" = "%lld üzenet továbbítása"; /* No comment provided by engineer. */ -"Forwarding server %@ failed to connect to destination server %@. Please try later." = "A(z) %@ továbbítókiszolgáló nem tudott csatlakozni a(z) %@ célkiszolgálóhoz. Próbálja meg később."; +"Forwarding server %@ failed to connect to destination server %@. Please try later." = "A(z) %@ továbbítókiszolgáló nem tudott kapcsolódni a(z) %@ célkiszolgálóhoz. Próbálja meg később."; /* No comment provided by engineer. */ "Forwarding server address is incompatible with network settings: %@." = "A továbbítókiszolgáló címe nem kompatibilis a hálózati beállításokkal: %@."; @@ -2806,7 +2800,7 @@ "incognito via group link" = "inkognitó a csoporthivatkozáson keresztül"; /* chat list item description */ -"incognito via one-time link" = "inkognitó egy egyszer használható meghívási hivatkozáson keresztül"; +"incognito via one-time link" = "inkognitó egy egyszer használható meghívón keresztül"; /* notification */ "Incoming audio call" = "Bejövő hanghívás"; @@ -2932,7 +2926,7 @@ "invited %@" = "meghívta őt: %@"; /* chat list item title */ -"invited to connect" = "meghívta egy partnerét"; +"invited to connect" = "Függőben lévő meghívó"; /* rcv group event chat item */ "invited via your group link" = "meghíva az Ön csoporthivatkozásán keresztül"; @@ -2962,7 +2956,7 @@ "It can happen when you or your connection used the old database backup." = "Ez akkor fordulhat elő, ha Ön vagy a partnere régi adatbázis biztonsági mentést használt."; /* No comment provided by engineer. */ -"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." = "Ez akkor fordulhat elő, ha:\n1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak.\n2. Az üzenet visszafejtése sikertelen volt, mert Ön, vagy a partnere régebbi adatbázis biztonsági mentést használt.\n3. A kapcsolat sérült."; +"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." = "Ez akkor fordulhat elő, ha:\n1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak.\n2. Nem sikerült az üzenetet visszafejteni, mert Ön, vagy a partnere régebbi adatbázis biztonsági mentést használt.\n3. A kapcsolat sérült."; /* No comment provided by engineer. */ "It protects your IP address and connections." = "Védi az IP-címét és a kapcsolatait."; @@ -3307,7 +3301,7 @@ "Migration complete" = "Átköltöztetés befejezve"; /* No comment provided by engineer. */ -"Migration error:" = "Átköltöztetés hiba:"; +"Migration error:" = "Átköltöztetési hiba:"; /* No comment provided by engineer. */ "Migration failed. Tap **Skip** below to continue using the current database. Please report the issue to the app developers via chat or email [chat@simplex.chat](mailto:chat@simplex.chat)." = "Sikertelen átköltöztetés. Koppintson a **Kihagyás** lehetőségre a jelenlegi adatbázis használatának folytatásához. Jelentse a problémát az alkalmazás fejlesztőinek csevegésben vagy e-mailben [chat@simplex.chat](mailto:chat@simplex.chat)."; @@ -3412,7 +3406,7 @@ "New chat experience 🎉" = "Új csevegési élmény 🎉"; /* notification */ -"New contact request" = "Új kapcsolatkérés"; +"New contact request" = "Új meghívási kérés"; /* notification */ "New contact:" = "Új kapcsolat:"; @@ -3589,8 +3583,8 @@ "observer" = "megfigyelő"; /* enabled status - group pref value - time to disappear */ +group pref value +time to disappear */ "off" = "kikapcsolva"; /* blur media */ @@ -3615,7 +3609,7 @@ "on" = "bekapcsolva"; /* No comment provided by engineer. */ -"One-time invitation link" = "Egyszer használható meghívási hivatkozás"; +"One-time invitation link" = "Egyszer használható meghívó"; /* No comment provided by engineer. */ "Onion hosts will be **required** for connection.\nRequires compatible VPN." = "Onion-kiszolgálók **szükségesek** a kapcsolódáshoz.\nKompatibilis VPN szükséges."; @@ -3914,9 +3908,15 @@ /* No comment provided by engineer. */ "Privacy for your customers." = "Az Ön ügyfeleinek adatvédelme."; +/* No comment provided by engineer. */ +"Privacy policy and conditions of use." = "Adatvédelmi szabályzat és felhasználási feltételek."; + /* No comment provided by engineer. */ "Privacy redefined" = "Adatvédelem újraértelmezve"; +/* No comment provided by engineer. */ +"Private chats, groups and your contacts are not accessible to server operators." = "A privát csevegések, a csoportok és a partnerek nem érhetők el a szerver üzemeltetői számára."; + /* No comment provided by engineer. */ "Private filenames" = "Privát fájlnevek"; @@ -4143,14 +4143,14 @@ "Registered" = "Regisztrálva"; /* reject incoming call via notification - swipe action */ +swipe action */ "Reject" = "Elutasítás"; /* No comment provided by engineer. */ "Reject (sender NOT notified)" = "Elutasítás (a feladó NEM kap értesítést)"; /* No comment provided by engineer. */ -"Reject contact request" = "Kapcsolatkérés elutasítása"; +"Reject contact request" = "Meghívási kérés elutasítása"; /* No comment provided by engineer. */ "rejected" = "elutasítva"; @@ -4207,7 +4207,7 @@ "Renegotiate encryption?" = "Újraegyezteti a titkosítást?"; /* No comment provided by engineer. */ -"Repeat connection request?" = "Megismétli a kapcsolódási kérést?"; +"Repeat connection request?" = "Megismétli a meghívási kérést?"; /* No comment provided by engineer. */ "Repeat download" = "Letöltés ismét"; @@ -4216,7 +4216,7 @@ "Repeat import" = "Importálás ismét"; /* No comment provided by engineer. */ -"Repeat join request?" = "Megismétli a csatlakozási kérést?"; +"Repeat join request?" = "Megismétli a meghívási kérést?"; /* No comment provided by engineer. */ "Repeat upload" = "Feltöltés ismét"; @@ -4255,7 +4255,7 @@ "Reports" = "Jelentések"; /* chat list item title */ -"requested to connect" = "kérelmezve a kapcsolódáshoz"; +"requested to connect" = "Függőben lévő meghívási kérelem"; /* No comment provided by engineer. */ "Required" = "Szükséges"; @@ -4300,7 +4300,7 @@ "Restore database backup?" = "Visszaállítja az adatbázismentést?"; /* No comment provided by engineer. */ -"Restore database error" = "Hiba az adatbázis visszaállításakor"; +"Restore database error" = "Hiba történt az adatbázis visszaállításakor"; /* No comment provided by engineer. */ "Retry" = "Újrapróbálkozás"; @@ -4311,9 +4311,6 @@ /* No comment provided by engineer. */ "Review conditions" = "Feltételek felülvizsgálata"; -/* No comment provided by engineer. */ -"Review later" = "Felülvizsgálat később"; - /* No comment provided by engineer. */ "Revoke" = "Visszavonás"; @@ -4336,7 +4333,7 @@ "Safer groups" = "Biztonságosabb csoportok"; /* alert button - chat item action */ +chat item action */ "Save" = "Mentés"; /* alert button */ @@ -4538,7 +4535,7 @@ "Send receipts" = "Kézbesítési jelentések küldése"; /* No comment provided by engineer. */ -"Send them from gallery or custom keyboards." = "Küldje el őket galériából vagy egyedi billentyűzetekről."; +"Send them from gallery or custom keyboards." = "Küldje el őket a galériából vagy az egyéni billentyűzetekről."; /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Legfeljebb az utolsó 100 üzenet elküldése az új tagok számára."; @@ -4547,7 +4544,7 @@ "Sender cancelled file transfer." = "A fájl küldője visszavonta az átvitelt."; /* No comment provided by engineer. */ -"Sender may have deleted the connection request." = "A küldő törölhette a kapcsolatkérést."; +"Sender may have deleted the connection request." = "A küldője törölhette a meghívási kérést."; /* No comment provided by engineer. */ "Sending delivery receipts will be enabled for all contacts in all visible chat profiles." = "A kézbesítési jelentések küldése engedélyezve lesz az összes látható csevegési profilban lévő összes partnere számára."; @@ -4622,7 +4619,7 @@ "Server operator changed." = "A kiszolgáló üzemeltetője módosult."; /* No comment provided by engineer. */ -"Server operators" = "Kiszolgáló-üzemeltetők"; +"Server operators" = "Kiszolgálóüzemeltetők"; /* alert title */ "Server protocol changed." = "A kiszolgáló-protokoll módosult."; @@ -4712,14 +4709,14 @@ "Shape profile images" = "Profilkép alakzata"; /* alert action - chat item action */ +chat item action */ "Share" = "Megosztás"; /* No comment provided by engineer. */ -"Share 1-time link" = "Egyszer használható meghívási hivatkozás megosztása"; +"Share 1-time link" = "Egyszer használható meghívó megosztása"; /* No comment provided by engineer. */ -"Share 1-time link with a friend" = "Egyszer használható meghívási hivatkozás megosztása egy baráttal"; +"Share 1-time link with a friend" = "Egyszer használható meghívó megosztása egy baráttal"; /* No comment provided by engineer. */ "Share address" = "Cím megosztása"; @@ -4743,7 +4740,7 @@ "Share SimpleX address on social media." = "SimpleX-cím megosztása a közösségi médiában."; /* No comment provided by engineer. */ -"Share this 1-time invite link" = "Ennek az egyszer használható meghívási hivatkozásnak a megosztása"; +"Share this 1-time invite link" = "Ennek az egyszer használható meghívónak a megosztása"; /* No comment provided by engineer. */ "Share to SimpleX" = "Megosztás a SimpleXben"; @@ -4788,10 +4785,10 @@ "SimpleX Address" = "SimpleX-cím"; /* No comment provided by engineer. */ -"SimpleX address and 1-time links are safe to share via any messenger." = "A SimpleX-cím és az egyszer használható meghívási hivatkozás biztonságosan megosztható bármilyen üzenetváltón keresztül."; +"SimpleX address and 1-time links are safe to share via any messenger." = "A SimpleX-cím és az egyszer használható meghívó biztonságosan megosztható bármilyen üzenetváltó-alkalmazáson keresztül."; /* No comment provided by engineer. */ -"SimpleX address or 1-time link?" = "SimpleX-cím vagy egyszer használható meghívási hivatkozás?"; +"SimpleX address or 1-time link?" = "SimpleX-cím vagy egyszer használható meghívó?"; /* No comment provided by engineer. */ "SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "A SimpleX Chat és a Flux megállapodást kötött arról, hogy a Flux által üzemeltetett kiszolgálókat beépítik az alkalmazásba."; @@ -4830,7 +4827,7 @@ "SimpleX Lock turned on" = "SimpleX-zár bekapcsolva"; /* simplex link type */ -"SimpleX one-time invitation" = "Egyszer használható SimpleX-meghívási hivatkozás"; +"SimpleX one-time invitation" = "Egyszer használható SimpleX-meghívó"; /* No comment provided by engineer. */ "SimpleX protocols reviewed by Trail of Bits." = "A SimpleX Chat biztonsága a Trail of Bits által lett felülvizsgálva."; @@ -4878,7 +4875,7 @@ "Somebody" = "Valaki"; /* blocking reason - report reason */ +report reason */ "Spam" = "Kéretlen tartalom"; /* No comment provided by engineer. */ @@ -4966,7 +4963,7 @@ "Switch audio and video during the call." = "Hang/Videó váltása hívás közben."; /* No comment provided by engineer. */ -"Switch chat profile for 1-time invitations." = "Csevegési profilváltás az egyszer használható meghívási hivatkozásokhoz."; +"Switch chat profile for 1-time invitations." = "Csevegési profilváltás az egyszer használható meghívókhoz."; /* No comment provided by engineer. */ "System" = "Rendszer"; @@ -5044,13 +5041,13 @@ "Thank you for installing SimpleX Chat!" = "Köszönjük, hogy telepítette a SimpleX Chatet!"; /* No comment provided by engineer. */ -"Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Köszönet a felhasználóknak – [hozzájárulás a Weblate-en keresztül](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; +"Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Köszönet a felhasználóknak [a Weblate-en való közreműködésért](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; /* No comment provided by engineer. */ -"Thanks to the users – contribute via Weblate!" = "Köszönet a felhasználóknak – hozzájárulás a Weblate-en!"; +"Thanks to the users – contribute via Weblate!" = "Köszönet a felhasználóknak a Weblate-en való közreműködésért!"; /* No comment provided by engineer. */ -"The app can notify you when you receive messages or contact requests - please open settings to enable." = "Az alkalmazás értesíteni fogja, amikor üzeneteket vagy kapcsolatkéréseket kap – ezt a beállítások menüben engedélyezheti."; +"The app can notify you when you receive messages or contact requests - please open settings to enable." = "Az alkalmazás értesíteni fogja, amikor üzeneteket vagy meghívási kéréseket kap – ezt a beállítások menüben engedélyezheti."; /* No comment provided by engineer. */ "The app protects your privacy by using different operators in each conversation." = "Az alkalmazás úgy védi az adatait, hogy minden egyes beszélgetéshez más-más üzemeltetőt használ."; @@ -5109,9 +5106,6 @@ /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Ugyanezek a feltételek lesznek elfogadva a következő üzemeltető számára is: **%@**."; -/* No comment provided by engineer. */ -"The same conditions will apply to operator(s): **%@**." = "Ugyanezek a feltételek lesznek elfogadva a következő üzemeltető(k) számára is: **%@**."; - /* No comment provided by engineer. */ "The second preset operator in the app!" = "A második előre beállított üzemeltető az alkalmazásban!"; @@ -5179,7 +5173,7 @@ "This group no longer exists." = "Ez a csoport már nem létezik."; /* No comment provided by engineer. */ -"This is your own one-time link!" = "Ez az Ön egyszer használható meghívási hivatkozása!"; +"This is your own one-time link!" = "Ez az Ön egyszer használható meghívója!"; /* No comment provided by engineer. */ "This is your own SimpleX address!" = "Ez az Ön SimpleX-címe!"; @@ -5533,7 +5527,7 @@ "via group link" = "a csoporthivatkozáson keresztül"; /* chat list item description */ -"via one-time link" = "egy egyszer használható meghívási hivatkozáson keresztül"; +"via one-time link" = "egy egyszer használható meghívón keresztül"; /* No comment provided by engineer. */ "via relay" = "egy továbbítókiszolgálón keresztül"; @@ -5725,7 +5719,7 @@ "You are already connecting to %@." = "A kapcsolódás már folyamatban van a következőhöz: %@."; /* No comment provided by engineer. */ -"You are already connecting via this one-time link!" = "A kapcsolódás már folyamatban van ezen az egyszer használható meghívási hivatkozáson keresztül!"; +"You are already connecting via this one-time link!" = "A kapcsolódás már folyamatban van ezen az egyszer használható meghívón keresztül!"; /* No comment provided by engineer. */ "You are already in group %@." = "Ön már a(z) %@ nevű csoport tagja."; @@ -5740,7 +5734,7 @@ "You are already joining the group via this link." = "A csatlakozás már folyamatban van a csoporthoz ezen a hivatkozáson keresztül."; /* No comment provided by engineer. */ -"You are already joining the group!\nRepeat join request?" = "A csatlakozás már folyamatban van a csoporthoz!\nMegismétli a csatlakozási kérést?"; +"You are already joining the group!\nRepeat join request?" = "A csatlakozás már folyamatban van a csoporthoz!\nMegismétli a meghívási kérést?"; /* No comment provided by engineer. */ "You are connected to the server used to receive messages from this contact." = "Ön már kapcsolódott ahhoz a kiszolgálóhoz, amely az adott partnerétől érkező üzenetek fogadására szolgál."; @@ -5766,9 +5760,6 @@ /* No comment provided by engineer. */ "You can change it in Appearance settings." = "Ezt a „Megjelenés” menüben módosíthatja."; -/* No comment provided by engineer. */ -"You can configure operators in Network & servers settings." = "Az üzemeltetőket a „Hálózat és kiszolgálók” menüben konfigurálhatja."; - /* No comment provided by engineer. */ "You can configure servers via settings." = "A kiszolgálókat a „Hálózat és kiszolgálók” menüben konfigurálhatja."; @@ -5845,10 +5836,10 @@ "You decide who can connect." = "Ön dönti el, hogy kivel beszélget."; /* No comment provided by engineer. */ -"You have already requested connection via this address!" = "Már küldött egy kapcsolatkérést ezen a címen keresztül!"; +"You have already requested connection via this address!" = "Már küldött egy meghívási kérést ezen a címen keresztül!"; /* No comment provided by engineer. */ -"You have already requested connection!\nRepeat connection request?" = "Ön már küldött egy kapcsolódási kérést!\nMegismétli a kapcsolódási kérést?"; +"You have already requested connection!\nRepeat connection request?" = "Ön már küldött egy meghívási kérést!\nMegismétli a meghívási kérést?"; /* No comment provided by engineer. */ "You have to enter passphrase every time the app starts - it is not stored on the device." = "A jelmondatot minden alkalommal meg kell adnia, amikor az alkalmazás elindul – nem az eszközön van tárolva."; @@ -5890,10 +5881,10 @@ "You sent group invitation" = "Csoportmeghívó elküldve"; /* chat list item description */ -"you shared one-time link" = "Ön egy egyszer használható meghívási hivatkozást osztott meg"; +"you shared one-time link" = "Ön egy egyszer használható meghívót osztott meg"; /* chat list item description */ -"you shared one-time link incognito" = "Ön egy egyszer használható meghívási hivatkozást osztott meg inkognitóban"; +"you shared one-time link incognito" = "Ön egy egyszer használható meghívót osztott meg inkognitóban"; /* token info */ "You should receive notifications." = "Ön megkapja az értesítéseket."; @@ -5908,7 +5899,7 @@ "You will be connected when group link host's device is online, please wait or check later!" = "Akkor lesz kapcsolódva, amikor a csoporthivatkozás tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később!"; /* No comment provided by engineer. */ -"You will be connected when your connection request is accepted, please wait or check later!" = "Akkor lesz kapcsolódva, ha a kapcsolatkérése el lesz fogadva, várjon, vagy ellenőrizze később!"; +"You will be connected when your connection request is accepted, please wait or check later!" = "Akkor lesz kapcsolódva, ha a meghívási kérése el lesz fogadva, várjon, vagy ellenőrizze később!"; /* No comment provided by engineer. */ "You will be connected when your contact's device is online, please wait or check later!" = "Akkor lesz kapcsolódva, amikor a partnerének az eszköze online lesz, várjon, vagy ellenőrizze később!"; @@ -6015,6 +6006,3 @@ /* No comment provided by engineer. */ "Your SimpleX address" = "Profil SimpleX-címe"; -/* No comment provided by engineer. */ -"Your SMP servers" = "Saját SMP-kiszolgálók"; - diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index d2fe0811ca..53798fe0eb 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -269,7 +269,7 @@ "0s" = "0s"; /* delete after time - time interval */ +time interval */ "1 day" = "1 giorno"; /* time interval */ @@ -279,11 +279,11 @@ "1 minute" = "1 minuto"; /* delete after time - time interval */ +time interval */ "1 month" = "1 mese"; /* delete after time - time interval */ +time interval */ "1 week" = "1 settimana"; /* delete after time */ @@ -341,8 +341,8 @@ "Accent" = "Principale"; /* accept contact request via notification - accept incoming call via notification - swipe action */ +accept incoming call via notification +swipe action */ "Accept" = "Accetta"; /* No comment provided by engineer. */ @@ -355,7 +355,7 @@ "Accept contact request from %@?" = "Accettare la richiesta di contatto da %@?"; /* accept contact request via notification - swipe action */ +swipe action */ "Accept incognito" = "Accetta in incognito"; /* call status */ @@ -817,7 +817,8 @@ /* rcv group event chat item */ "blocked %@" = "ha bloccato %@"; -/* marked deleted chat item preview text */ +/* blocked chat item +marked deleted chat item preview text */ "blocked by admin" = "bloccato dall'amministratore"; /* No comment provided by engineer. */ @@ -862,6 +863,9 @@ /* 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 profilo di chat (predefinito) o [per connessione](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; +/* No comment provided by engineer. */ +"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "Usando SimpleX Chat accetti di:\n- inviare solo contenuto legale nei gruppi pubblici.\n- rispettare gli altri utenti - niente spam."; + /* No comment provided by engineer. */ "call" = "chiama"; @@ -902,7 +906,7 @@ "Can't message member" = "Impossibile inviare un messaggio al membro"; /* alert action - alert button */ +alert button */ "Cancel" = "Annulla"; /* No comment provided by engineer. */ @@ -960,7 +964,7 @@ "Change self-destruct mode" = "Cambia modalità di autodistruzione"; /* authentication reason - set passcode view */ +set passcode view */ "Change self-destruct passcode" = "Cambia codice di autodistruzione"; /* chat item text */ @@ -1128,12 +1132,6 @@ /* No comment provided by engineer. */ "Conditions of use" = "Condizioni d'uso"; -/* No comment provided by engineer. */ -"Conditions will be accepted for enabled operators after 30 days." = "Le condizioni verranno accettate per gli operatori attivati dopo 30 giorni."; - -/* No comment provided by engineer. */ -"Conditions will be accepted for operator(s): **%@**." = "Le condizioni verranno accettate per gli operatori: **%@**."; - /* No comment provided by engineer. */ "Conditions will be accepted for the operator(s): **%@**." = "Le condizioni verranno accettate per gli operatori: **%@**."; @@ -1146,6 +1144,9 @@ /* No comment provided by engineer. */ "Configure ICE servers" = "Configura server ICE"; +/* No comment provided by engineer. */ +"Configure server operators" = "Configura gli operatori dei server"; + /* No comment provided by engineer. */ "Confirm" = "Conferma"; @@ -1555,7 +1556,7 @@ "decryption errors" = "errori di decifrazione"; /* delete after time - pref value */ +pref value */ "default (%@)" = "predefinito (%@)"; /* No comment provided by engineer. */ @@ -1565,7 +1566,7 @@ "default (yes)" = "predefinito (sì)"; /* alert action - swipe action */ +swipe action */ "Delete" = "Elimina"; /* No comment provided by engineer. */ @@ -1881,7 +1882,7 @@ "Downgrade and open chat" = "Esegui downgrade e apri chat"; /* alert button - chat item action */ +chat item action */ "Download" = "Scarica"; /* No comment provided by engineer. */ @@ -1944,9 +1945,6 @@ /* No comment provided by engineer. */ "Enable camera access" = "Attiva l'accesso alla fotocamera"; -/* No comment provided by engineer. */ -"Enable Flux" = "Attiva Flux"; - /* No comment provided by engineer. */ "Enable for all" = "Attiva per tutti"; @@ -2325,7 +2323,9 @@ /* No comment provided by engineer. */ "Error: " = "Errore: "; -/* alert message */ +/* alert message +file error text +snd error text */ "Error: %@" = "Errore: %@"; /* No comment provided by engineer. */ @@ -2343,9 +2343,6 @@ /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Anche quando disattivato nella conversazione."; -/* No comment provided by engineer. */ -"event happened" = "evento accaduto"; - /* No comment provided by engineer. */ "Exit without saving" = "Esci senza salvare"; @@ -2484,9 +2481,6 @@ /* No comment provided by engineer. */ "For all moderators" = "Per tutti i moderatori"; -/* No comment provided by engineer. */ -"for better metadata privacy." = "per una migliore privacy dei metadati."; - /* servers error */ "For chat profile %@:" = "Per il profilo di chat %@:"; @@ -3139,7 +3133,7 @@ "Member" = "Membro"; /* profile update event chat item */ -"member %@ changed to %@" = "membro %1$@ cambiato in %2$@"; +"member %@ changed to %@" = "il membro %1$@ è diventato %2$@"; /* rcv group event chat item */ "member connected" = "si è connesso/a"; @@ -3589,8 +3583,8 @@ "observer" = "osservatore"; /* enabled status - group pref value - time to disappear */ +group pref value +time to disappear */ "off" = "off"; /* blur media */ @@ -3914,9 +3908,15 @@ /* No comment provided by engineer. */ "Privacy for your customers." = "Privacy per i tuoi clienti."; +/* No comment provided by engineer. */ +"Privacy policy and conditions of use." = "Informativa sulla privacy e condizioni d'uso."; + /* No comment provided by engineer. */ "Privacy redefined" = "Privacy ridefinita"; +/* No comment provided by engineer. */ +"Private chats, groups and your contacts are not accessible to server operators." = "Le chat private, i gruppi e i tuoi contatti non sono accessibili agli operatori dei server."; + /* No comment provided by engineer. */ "Private filenames" = "Nomi di file privati"; @@ -4143,7 +4143,7 @@ "Registered" = "Registrato"; /* reject incoming call via notification - swipe action */ +swipe action */ "Reject" = "Rifiuta"; /* No comment provided by engineer. */ @@ -4311,9 +4311,6 @@ /* No comment provided by engineer. */ "Review conditions" = "Leggi le condizioni"; -/* No comment provided by engineer. */ -"Review later" = "Leggi più tardi"; - /* No comment provided by engineer. */ "Revoke" = "Revoca"; @@ -4336,7 +4333,7 @@ "Safer groups" = "Gruppi più sicuri"; /* alert button - chat item action */ +chat item action */ "Save" = "Salva"; /* alert button */ @@ -4712,7 +4709,7 @@ "Shape profile images" = "Forma delle immagini del profilo"; /* alert action - chat item action */ +chat item action */ "Share" = "Condividi"; /* No comment provided by engineer. */ @@ -4878,7 +4875,7 @@ "Somebody" = "Qualcuno"; /* blocking reason - report reason */ +report reason */ "Spam" = "Spam"; /* No comment provided by engineer. */ @@ -5109,9 +5106,6 @@ /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Le stesse condizioni si applicheranno all'operatore **%@**."; -/* No comment provided by engineer. */ -"The same conditions will apply to operator(s): **%@**." = "Le stesse condizioni si applicheranno agli operatori **%@**."; - /* No comment provided by engineer. */ "The second preset operator in the app!" = "Il secondo operatore preimpostato nell'app!"; @@ -5766,9 +5760,6 @@ /* No comment provided by engineer. */ "You can change it in Appearance settings." = "Puoi cambiarlo nelle impostazioni dell'aspetto."; -/* No comment provided by engineer. */ -"You can configure operators in Network & servers settings." = "Puoi configurare gli operatori nelle impostazioni di rete e server."; - /* No comment provided by engineer. */ "You can configure servers via settings." = "Puoi configurare i server nelle impostazioni."; @@ -6015,6 +6006,3 @@ /* No comment provided by engineer. */ "Your SimpleX address" = "Il tuo indirizzo SimpleX"; -/* No comment provided by engineer. */ -"Your SMP servers" = "I tuoi server SMP"; - diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index 9ef8c02a0e..89934d67ce 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -266,7 +266,7 @@ "0s" = "0秒"; /* delete after time - time interval */ +time interval */ "1 day" = "1日"; /* time interval */ @@ -276,11 +276,11 @@ "1 minute" = "1分"; /* delete after time - time interval */ +time interval */ "1 month" = "1ヶ月"; /* delete after time - time interval */ +time interval */ "1 week" = "1週間"; /* No comment provided by engineer. */ @@ -329,8 +329,8 @@ "above, then choose:" = "上で選んでください:"; /* accept contact request via notification - accept incoming call via notification - swipe action */ +accept incoming call via notification +swipe action */ "Accept" = "承諾"; /* No comment provided by engineer. */ @@ -340,7 +340,7 @@ "Accept contact request from %@?" = "%@ からの連絡要求を受け入れますか?"; /* accept contact request via notification - swipe action */ +swipe action */ "Accept incognito" = "シークレットモードで承諾"; /* call status */ @@ -647,7 +647,7 @@ "Can't invite contacts!" = "連絡先を招待できません!"; /* alert action - alert button */ +alert button */ "Cancel" = "中止"; /* feature offered item */ @@ -687,7 +687,7 @@ "Change self-destruct mode" = "自己破壊モードの変更"; /* authentication reason - set passcode view */ +set passcode view */ "Change self-destruct passcode" = "自己破壊パスコードを変更する"; /* chat item text */ @@ -1066,7 +1066,7 @@ "Decryption error" = "復号化エラー"; /* delete after time - pref value */ +pref value */ "default (%@)" = "デフォルト (%@)"; /* No comment provided by engineer. */ @@ -1076,7 +1076,7 @@ "default (yes)" = "デフォルト(はい)"; /* alert action - swipe action */ +swipe action */ "Delete" = "削除"; /* No comment provided by engineer. */ @@ -1565,7 +1565,9 @@ /* No comment provided by engineer. */ "Error: " = "エラー : "; -/* alert message */ +/* alert message +file error text +snd error text */ "Error: %@" = "エラー : %@"; /* No comment provided by engineer. */ @@ -1577,9 +1579,6 @@ /* No comment provided by engineer. */ "Even when disabled in the conversation." = "会話中に無効になっている場合でも。"; -/* No comment provided by engineer. */ -"event happened" = "イベント発生"; - /* No comment provided by engineer. */ "Exit without saving" = "保存せずに閉じる"; @@ -2292,8 +2291,8 @@ "observer" = "オブザーバー"; /* enabled status - group pref value - time to disappear */ +group pref value +time to disappear */ "off" = "オフ"; /* blur media */ @@ -2600,7 +2599,7 @@ "Reduced battery usage" = "電池使用量低減"; /* reject incoming call via notification - swipe action */ +swipe action */ "Reject" = "拒否"; /* No comment provided by engineer. */ @@ -2700,7 +2699,7 @@ "Run chat" = "チャット起動"; /* alert button - chat item action */ +chat item action */ "Save" = "保存"; /* alert button */ @@ -2887,7 +2886,7 @@ "Settings" = "設定"; /* alert action - chat item action */ +chat item action */ "Share" = "共有する"; /* No comment provided by engineer. */ @@ -3607,6 +3606,3 @@ /* No comment provided by engineer. */ "Your SimpleX address" = "あなたのSimpleXアドレス"; -/* No comment provided by engineer. */ -"Your SMP servers" = "あなたのSMPサーバ"; - diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index e2bdd06018..f9e289369e 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -269,7 +269,7 @@ "0s" = "0s"; /* delete after time - time interval */ +time interval */ "1 day" = "1 dag"; /* time interval */ @@ -279,11 +279,11 @@ "1 minute" = "1 minuut"; /* delete after time - time interval */ +time interval */ "1 month" = "1 maand"; /* delete after time - time interval */ +time interval */ "1 week" = "1 week"; /* delete after time */ @@ -341,8 +341,8 @@ "Accent" = "Accent"; /* accept contact request via notification - accept incoming call via notification - swipe action */ +accept incoming call via notification +swipe action */ "Accept" = "Accepteer"; /* No comment provided by engineer. */ @@ -355,7 +355,7 @@ "Accept contact request from %@?" = "Accepteer contactverzoek van %@?"; /* accept contact request via notification - swipe action */ +swipe action */ "Accept incognito" = "Accepteer incognito"; /* call status */ @@ -817,7 +817,8 @@ /* rcv group event chat item */ "blocked %@" = "blokkeerde %@"; -/* marked deleted chat item preview text */ +/* blocked chat item +marked deleted chat item preview text */ "blocked by admin" = "geblokkeerd door beheerder"; /* No comment provided by engineer. */ @@ -902,7 +903,7 @@ "Can't message member" = "Kan geen bericht sturen naar lid"; /* alert action - alert button */ +alert button */ "Cancel" = "Annuleren"; /* No comment provided by engineer. */ @@ -960,7 +961,7 @@ "Change self-destruct mode" = "Zelfvernietigings modus wijzigen"; /* authentication reason - set passcode view */ +set passcode view */ "Change self-destruct passcode" = "Zelfvernietigings code wijzigen"; /* chat item text */ @@ -1128,12 +1129,6 @@ /* No comment provided by engineer. */ "Conditions of use" = "Gebruiksvoorwaarden"; -/* No comment provided by engineer. */ -"Conditions will be accepted for enabled operators after 30 days." = "Voor ingeschakelde operators worden de voorwaarden na 30 dagen geaccepteerd."; - -/* No comment provided by engineer. */ -"Conditions will be accepted for operator(s): **%@**." = "Voorwaarden worden geaccepteerd voor operator(s): **%@**."; - /* No comment provided by engineer. */ "Conditions will be accepted for the operator(s): **%@**." = "Voorwaarden worden geaccepteerd voor de operator(s): **%@**."; @@ -1555,7 +1550,7 @@ "decryption errors" = "decoderingsfouten"; /* delete after time - pref value */ +pref value */ "default (%@)" = "standaard (%@)"; /* No comment provided by engineer. */ @@ -1565,7 +1560,7 @@ "default (yes)" = "standaard (ja)"; /* alert action - swipe action */ +swipe action */ "Delete" = "Verwijderen"; /* No comment provided by engineer. */ @@ -1869,7 +1864,7 @@ "Don't enable" = "Niet inschakelen"; /* No comment provided by engineer. */ -"Don't miss important messages." = "‐Mis geen belangrijke berichten."; +"Don't miss important messages." = "Mis geen belangrijke berichten."; /* No comment provided by engineer. */ "Don't show again" = "Niet meer weergeven"; @@ -1881,7 +1876,7 @@ "Downgrade and open chat" = "Downgraden en chat openen"; /* alert button - chat item action */ +chat item action */ "Download" = "Downloaden"; /* No comment provided by engineer. */ @@ -1944,9 +1939,6 @@ /* No comment provided by engineer. */ "Enable camera access" = "Schakel cameratoegang in"; -/* No comment provided by engineer. */ -"Enable Flux" = "Flux inschakelen"; - /* No comment provided by engineer. */ "Enable for all" = "Inschakelen voor iedereen"; @@ -2164,7 +2156,7 @@ "Error creating profile!" = "Fout bij aanmaken van profiel!"; /* No comment provided by engineer. */ -"Error creating report" = "Fout bij maken van rapport"; +"Error creating report" = "Fout bij het rapporteren"; /* No comment provided by engineer. */ "Error decrypting file" = "Fout bij het ontsleutelen van bestand"; @@ -2325,7 +2317,9 @@ /* No comment provided by engineer. */ "Error: " = "Fout: "; -/* alert message */ +/* alert message +file error text +snd error text */ "Error: %@" = "Fout: %@"; /* No comment provided by engineer. */ @@ -2343,9 +2337,6 @@ /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Zelfs wanneer uitgeschakeld in het gesprek."; -/* No comment provided by engineer. */ -"event happened" = "gebeurtenis gebeurd"; - /* No comment provided by engineer. */ "Exit without saving" = "Afsluiten zonder opslaan"; @@ -2484,9 +2475,6 @@ /* No comment provided by engineer. */ "For all moderators" = "Voor alle moderators"; -/* No comment provided by engineer. */ -"for better metadata privacy." = "voor betere privacy van metagegevens."; - /* servers error */ "For chat profile %@:" = "Voor chatprofiel %@:"; @@ -3589,8 +3577,8 @@ "observer" = "Waarnemer"; /* enabled status - group pref value - time to disappear */ +group pref value +time to disappear */ "off" = "uit"; /* blur media */ @@ -3800,9 +3788,15 @@ /* No comment provided by engineer. */ "peer-to-peer" = "peer-to-peer"; +/* No comment provided by engineer. */ +"pending" = "In behandeling"; + /* No comment provided by engineer. */ "Pending" = "in behandeling"; +/* No comment provided by engineer. */ +"pending approval" = "in afwachting van goedkeuring"; + /* No comment provided by engineer. */ "Periodic" = "Periodiek"; @@ -4137,7 +4131,7 @@ "Registered" = "Geregistreerd"; /* reject incoming call via notification - swipe action */ +swipe action */ "Reject" = "Afwijzen"; /* No comment provided by engineer. */ @@ -4146,6 +4140,9 @@ /* No comment provided by engineer. */ "Reject contact request" = "Contactverzoek afwijzen"; +/* No comment provided by engineer. */ +"rejected" = "afgewezen"; + /* call status */ "rejected call" = "geweigerde oproep"; @@ -4216,7 +4213,7 @@ "Reply" = "Antwoord"; /* chat item action */ -"Report" = "Rapport"; +"Report" = "rapporteren"; /* report reason */ "Report content: only group moderators will see it." = "Inhoud melden: alleen groepsmoderators kunnen dit zien."; @@ -4237,7 +4234,7 @@ "Report violation: only group moderators will see it." = "Rapporteer overtreding: alleen groepsmoderators kunnen dit zien."; /* report in notification */ -"Report: %@" = "Rapport: %@"; +"Report: %@" = "rapporteer: %@"; /* No comment provided by engineer. */ "Reporting messages to moderators is prohibited." = "Het is niet toegestaan om berichten aan moderators te melden."; @@ -4302,9 +4299,6 @@ /* No comment provided by engineer. */ "Review conditions" = "Voorwaarden bekijken"; -/* No comment provided by engineer. */ -"Review later" = "Later beoordelen"; - /* No comment provided by engineer. */ "Revoke" = "Intrekken"; @@ -4327,7 +4321,7 @@ "Safer groups" = "Veiligere groepen"; /* alert button - chat item action */ +chat item action */ "Save" = "Opslaan"; /* alert button */ @@ -4520,7 +4514,7 @@ "Send notifications" = "Meldingen verzenden"; /* No comment provided by engineer. */ -"Send private reports" = "Verstuur rapporten privé"; +"Send private reports" = "Rapporteer privé"; /* No comment provided by engineer. */ "Send questions and ideas" = "Stuur vragen en ideeën"; @@ -4703,7 +4697,7 @@ "Shape profile images" = "Vorm profiel afbeeldingen"; /* alert action - chat item action */ +chat item action */ "Share" = "Deel"; /* No comment provided by engineer. */ @@ -4869,7 +4863,7 @@ "Somebody" = "Iemand"; /* blocking reason - report reason */ +report reason */ "Spam" = "Spam"; /* No comment provided by engineer. */ @@ -5100,9 +5094,6 @@ /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Dezelfde voorwaarden gelden voor operator **%@**."; -/* No comment provided by engineer. */ -"The same conditions will apply to operator(s): **%@**." = "Dezelfde voorwaarden gelden voor operator(s): **%@**."; - /* No comment provided by engineer. */ "The second preset operator in the app!" = "De tweede vooraf ingestelde operator in de app!"; @@ -5382,6 +5373,9 @@ /* No comment provided by engineer. */ "Update settings?" = "Instellingen actualiseren?"; +/* No comment provided by engineer. */ +"Updated conditions" = "Bijgewerkte voorwaarden"; + /* rcv group event chat item */ "updated group profile" = "bijgewerkt groep profiel"; @@ -5754,9 +5748,6 @@ /* No comment provided by engineer. */ "You can change it in Appearance settings." = "U kunt dit wijzigen in de instellingen onder uiterlijk."; -/* No comment provided by engineer. */ -"You can configure operators in Network & servers settings." = "U kunt operators configureren in Netwerk- en serverinstellingen."; - /* No comment provided by engineer. */ "You can configure servers via settings." = "U kunt servers configureren via instellingen."; @@ -6003,6 +5994,3 @@ /* No comment provided by engineer. */ "Your SimpleX address" = "Uw SimpleX adres"; -/* No comment provided by engineer. */ -"Your SMP servers" = "Uw SMP servers"; - diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index 72a9a538c6..82730db52a 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -269,7 +269,7 @@ "0s" = "0s"; /* delete after time - time interval */ +time interval */ "1 day" = "1 dzień"; /* time interval */ @@ -279,11 +279,11 @@ "1 minute" = "1 minuta"; /* delete after time - time interval */ +time interval */ "1 month" = "1 miesiąc"; /* delete after time - time interval */ +time interval */ "1 week" = "1 tydzień"; /* delete after time */ @@ -341,8 +341,8 @@ "Accent" = "Akcent"; /* accept contact request via notification - accept incoming call via notification - swipe action */ +accept incoming call via notification +swipe action */ "Accept" = "Akceptuj"; /* No comment provided by engineer. */ @@ -355,7 +355,7 @@ "Accept contact request from %@?" = "Zaakceptuj prośbę o kontakt od %@?"; /* accept contact request via notification - swipe action */ +swipe action */ "Accept incognito" = "Akceptuj incognito"; /* call status */ @@ -805,7 +805,8 @@ /* rcv group event chat item */ "blocked %@" = "zablokowany %@"; -/* marked deleted chat item preview text */ +/* blocked chat item +marked deleted chat item preview text */ "blocked by admin" = "zablokowany przez admina"; /* No comment provided by engineer. */ @@ -890,7 +891,7 @@ "Can't message member" = "Nie można wysłać wiadomości do członka"; /* alert action - alert button */ +alert button */ "Cancel" = "Anuluj"; /* No comment provided by engineer. */ @@ -945,7 +946,7 @@ "Change self-destruct mode" = "Zmień tryb samozniszczenia"; /* authentication reason - set passcode view */ +set passcode view */ "Change self-destruct passcode" = "Zmień pin samozniszczenia"; /* chat item text */ @@ -1104,9 +1105,6 @@ /* No comment provided by engineer. */ "Conditions of use" = "Warunki użytkowania"; -/* No comment provided by engineer. */ -"Conditions will be accepted for enabled operators after 30 days." = "Warunki zostaną zaakceptowane dla aktywowanych operatorów po 30 dniach."; - /* No comment provided by engineer. */ "Configure ICE servers" = "Skonfiguruj serwery ICE"; @@ -1486,7 +1484,7 @@ "decryption errors" = "błąd odszyfrowywania"; /* delete after time - pref value */ +pref value */ "default (%@)" = "domyślne (%@)"; /* No comment provided by engineer. */ @@ -1496,7 +1494,7 @@ "default (yes)" = "domyślnie (tak)"; /* alert action - swipe action */ +swipe action */ "Delete" = "Usuń"; /* No comment provided by engineer. */ @@ -1773,7 +1771,7 @@ "Downgrade and open chat" = "Obniż wersję i otwórz czat"; /* alert button - chat item action */ +chat item action */ "Download" = "Pobierz"; /* No comment provided by engineer. */ @@ -2172,7 +2170,9 @@ /* No comment provided by engineer. */ "Error: " = "Błąd: "; -/* alert message */ +/* alert message +file error text +snd error text */ "Error: %@" = "Błąd: %@"; /* No comment provided by engineer. */ @@ -2187,9 +2187,6 @@ /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Nawet po wyłączeniu w rozmowie."; -/* No comment provided by engineer. */ -"event happened" = "nowe wydarzenie"; - /* No comment provided by engineer. */ "Exit without saving" = "Wyjdź bez zapisywania"; @@ -3241,8 +3238,8 @@ "observer" = "obserwator"; /* enabled status - group pref value - time to disappear */ +group pref value +time to disappear */ "off" = "wyłączony"; /* blur media */ @@ -3720,7 +3717,7 @@ "Reduced battery usage" = "Zmniejszone zużycie baterii"; /* reject incoming call via notification - swipe action */ +swipe action */ "Reject" = "Odrzuć"; /* No comment provided by engineer. */ @@ -3871,7 +3868,7 @@ "Safer groups" = "Bezpieczniejsze grupy"; /* alert button - chat item action */ +chat item action */ "Save" = "Zapisz"; /* alert button */ @@ -4223,7 +4220,7 @@ "Shape profile images" = "Kształtuj obrazy profilowe"; /* alert action - chat item action */ +chat item action */ "Share" = "Udostępnij"; /* No comment provided by engineer. */ @@ -5384,6 +5381,3 @@ /* No comment provided by engineer. */ "Your SimpleX address" = "Twój adres SimpleX"; -/* No comment provided by engineer. */ -"Your SMP servers" = "Twoje serwery SMP"; - diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index c8b971e21d..f86b3aa4fd 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -139,12 +139,6 @@ /* format for date separator in chat */ "%@, %@" = "%1$@, %2$@"; -/* No comment provided by engineer. */ -"%@, %@ and %lld members" = "%@, %@ и %lld членов группы"; - -/* No comment provided by engineer. */ -"%@, %@ and %lld other members connected" = "%@, %@ и %lld других членов соединены"; - /* copied message info */ "%@:" = "%@:"; @@ -202,9 +196,6 @@ /* No comment provided by engineer. */ "%lld group events" = "%lld событий"; -/* No comment provided by engineer. */ -"%lld members" = "Членов группы: %lld"; - /* No comment provided by engineer. */ "%lld messages blocked" = "%lld сообщений заблокировано"; @@ -269,7 +260,7 @@ "0s" = "0с"; /* delete after time - time interval */ +time interval */ "1 day" = "1 день"; /* time interval */ @@ -279,11 +270,11 @@ "1 minute" = "1 минута"; /* delete after time - time interval */ +time interval */ "1 month" = "1 месяц"; /* delete after time - time interval */ +time interval */ "1 week" = "1 неделю"; /* delete after time */ @@ -316,9 +307,6 @@ /* No comment provided by engineer. */ "A separate TCP connection will be used **for each chat profile you have in the app**." = "Отдельное TCP-соединение будет использоваться **для каждого профиля чата, который Вы имеете в приложении**."; -/* No comment provided by engineer. */ -"A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail." = "Отдельное TCP-соединение (и авторизация SOCKS) будет использоваться **для каждого контакта и члена группы**.\n**Обратите внимание**: если у Вас много контактов, потребление батареи и трафика может быть значительно выше, и некоторые соединения могут не работать."; - /* No comment provided by engineer. */ "Abort" = "Прекратить"; @@ -341,8 +329,8 @@ "Accent" = "Акцент"; /* accept contact request via notification - accept incoming call via notification - swipe action */ +accept incoming call via notification +swipe action */ "Accept" = "Принять"; /* No comment provided by engineer. */ @@ -355,7 +343,7 @@ "Accept contact request from %@?" = "Принять запрос на соединение от %@?"; /* accept contact request via notification - swipe action */ +swipe action */ "Accept incognito" = "Принять инкогнито"; /* call status */ @@ -445,9 +433,6 @@ /* feature role */ "admins" = "админы"; -/* No comment provided by engineer. */ -"Admins can block a member for all." = "Админы могут заблокировать члена группы."; - /* No comment provided by engineer. */ "Admins can create the links to join groups." = "Админы могут создать ссылки для вступления в группу."; @@ -481,12 +466,6 @@ /* No comment provided by engineer. */ "All data is kept private on your device." = "Все данные хранятся только на вашем устройстве."; -/* No comment provided by engineer. */ -"All group members will remain connected." = "Все члены группы, которые соединились через эту ссылку, останутся в группе."; - -/* feature role */ -"all members" = "все члены"; - /* No comment provided by engineer. */ "All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Все сообщения и файлы отправляются с **end-to-end шифрованием**, с постквантовой безопасностью в прямых разговорах."; @@ -538,9 +517,6 @@ /* No comment provided by engineer. */ "Allow message reactions." = "Разрешить реакции на сообщения."; -/* No comment provided by engineer. */ -"Allow sending direct messages to members." = "Разрешить посылать прямые сообщения членам группы."; - /* No comment provided by engineer. */ "Allow sending disappearing messages." = "Разрешить посылать исчезающие сообщения."; @@ -799,25 +775,14 @@ /* No comment provided by engineer. */ "Block for all" = "Заблокировать для всех"; -/* No comment provided by engineer. */ -"Block group members" = "Блокируйте членов группы"; - -/* No comment provided by engineer. */ -"Block member" = "Заблокировать члена группы"; - -/* No comment provided by engineer. */ -"Block member for all?" = "Заблокировать члена для всех?"; - -/* No comment provided by engineer. */ -"Block member?" = "Заблокировать члена группы?"; - /* marked deleted chat item preview text */ "blocked" = "заблокировано"; /* rcv group event chat item */ "blocked %@" = "%@ заблокирован"; -/* marked deleted chat item preview text */ +/* blocked chat item +marked deleted chat item preview text */ "blocked by admin" = "заблокировано администратором"; /* No comment provided by engineer. */ @@ -862,6 +827,9 @@ /* 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)." = "По профилю чата или [по соединению](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (БЕТА)."; +/* No comment provided by engineer. */ +"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "Используя SimpleX Chat, Вы согласны:\n- отправлять только законные сообщения в публичных группах.\n- уважать других пользователей – не отправлять спам."; + /* No comment provided by engineer. */ "call" = "звонок"; @@ -889,20 +857,14 @@ /* No comment provided by engineer. */ "Can't call contact" = "Не удается позвонить контакту"; -/* No comment provided by engineer. */ -"Can't call member" = "Не удается позвонить члену группы"; - /* No comment provided by engineer. */ "Can't invite contact!" = "Нельзя пригласить контакт!"; /* No comment provided by engineer. */ "Can't invite contacts!" = "Нельзя пригласить контакты!"; -/* No comment provided by engineer. */ -"Can't message member" = "Не удается написать члену группы"; - /* alert action - alert button */ +alert button */ "Cancel" = "Отменить"; /* No comment provided by engineer. */ @@ -941,9 +903,6 @@ /* authentication reason */ "Change lock mode" = "Изменить режим блокировки"; -/* No comment provided by engineer. */ -"Change member role?" = "Поменять роль члена группы?"; - /* authentication reason */ "Change passcode" = "Изменить код доступа"; @@ -960,7 +919,7 @@ "Change self-destruct mode" = "Изменить режим самоуничтожения"; /* authentication reason - set passcode view */ +set passcode view */ "Change self-destruct passcode" = "Изменить код самоуничтожения"; /* chat item text */ @@ -1128,12 +1087,6 @@ /* No comment provided by engineer. */ "Conditions of use" = "Условия использования"; -/* No comment provided by engineer. */ -"Conditions will be accepted for enabled operators after 30 days." = "Условия будут приняты для включенных операторов через 30 дней."; - -/* No comment provided by engineer. */ -"Conditions will be accepted for operator(s): **%@**." = "Условия будут приняты для оператора(ов): **%@**."; - /* No comment provided by engineer. */ "Conditions will be accepted for the operator(s): **%@**." = "Условия будут приняты для оператора(ов): **%@**."; @@ -1146,6 +1099,9 @@ /* No comment provided by engineer. */ "Configure ICE servers" = "Настройка ICE серверов"; +/* No comment provided by engineer. */ +"Configure server operators" = "Настроить операторов серверов"; + /* No comment provided by engineer. */ "Confirm" = "Подтвердить"; @@ -1555,7 +1511,7 @@ "decryption errors" = "ошибки расшифровки"; /* delete after time - pref value */ +pref value */ "default (%@)" = "по умолчанию (%@)"; /* No comment provided by engineer. */ @@ -1565,12 +1521,9 @@ "default (yes)" = "по умолчанию (да)"; /* alert action - swipe action */ +swipe action */ "Delete" = "Удалить"; -/* No comment provided by engineer. */ -"Delete %lld messages of members?" = "Удалить %lld сообщений членов группы?"; - /* No comment provided by engineer. */ "Delete %lld messages?" = "Удалить %lld сообщений?"; @@ -1784,12 +1737,6 @@ /* chat feature */ "Direct messages" = "Прямые сообщения"; -/* No comment provided by engineer. */ -"Direct messages between members are prohibited in this chat." = "Прямые сообщения между членами запрещены в этом разговоре."; - -/* No comment provided by engineer. */ -"Direct messages between members are prohibited." = "Прямые сообщения между членами группы запрещены."; - /* No comment provided by engineer. */ "Disable (keep overrides)" = "Выключить (кроме исключений)"; @@ -1844,9 +1791,6 @@ /* No comment provided by engineer. */ "Do it later" = "Отложить"; -/* No comment provided by engineer. */ -"Do not send history to new members." = "Не отправлять историю новым членам."; - /* No comment provided by engineer. */ "Do NOT send messages directly, even if your or destination server does not support private routing." = "Не отправлять сообщения напрямую, даже если сервер получателя не поддерживает конфиденциальную доставку."; @@ -1881,7 +1825,7 @@ "Downgrade and open chat" = "Откатить версию и открыть чат"; /* alert button - chat item action */ +chat item action */ "Download" = "Загрузить"; /* No comment provided by engineer. */ @@ -1944,9 +1888,6 @@ /* No comment provided by engineer. */ "Enable camera access" = "Включить доступ к камере"; -/* No comment provided by engineer. */ -"Enable Flux" = "Включить Flux"; - /* No comment provided by engineer. */ "Enable for all" = "Включить для всех"; @@ -2115,9 +2056,6 @@ /* No comment provided by engineer. */ "Error accepting contact request" = "Ошибка при принятии запроса на соединение"; -/* No comment provided by engineer. */ -"Error adding member(s)" = "Ошибка при добавлении членов группы"; - /* alert title */ "Error adding server" = "Ошибка добавления сервера"; @@ -2154,9 +2092,6 @@ /* alert title */ "Error creating list" = "Ошибка создания списка"; -/* No comment provided by engineer. */ -"Error creating member contact" = "Ошибка создания контакта с членом группы"; - /* No comment provided by engineer. */ "Error creating message" = "Ошибка создания сообщения"; @@ -2235,9 +2170,6 @@ /* alert title */ "Error registering for notifications" = "Ошибка регистрации для уведомлений"; -/* No comment provided by engineer. */ -"Error removing member" = "Ошибка при удалении члена группы"; - /* alert title */ "Error reordering lists" = "Ошибка сортировки списков"; @@ -2274,9 +2206,6 @@ /* No comment provided by engineer. */ "Error sending email" = "Ошибка отправки email"; -/* No comment provided by engineer. */ -"Error sending member contact invitation" = "Ошибка отправки приглашения члену группы"; - /* No comment provided by engineer. */ "Error sending message" = "Ошибка при отправке сообщения"; @@ -2325,7 +2254,9 @@ /* No comment provided by engineer. */ "Error: " = "Ошибка: "; -/* alert message */ +/* alert message +file error text +snd error text */ "Error: %@" = "Ошибка: %@"; /* No comment provided by engineer. */ @@ -2343,9 +2274,6 @@ /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Даже когда они выключены в разговоре."; -/* No comment provided by engineer. */ -"event happened" = "событие произошло"; - /* No comment provided by engineer. */ "Exit without saving" = "Выйти без сохранения"; @@ -2478,15 +2406,9 @@ /* No comment provided by engineer. */ "Fix not supported by contact" = "Починка не поддерживается контактом"; -/* No comment provided by engineer. */ -"Fix not supported by group member" = "Починка не поддерживается членом группы"; - /* No comment provided by engineer. */ "For all moderators" = "Для всех модераторов"; -/* No comment provided by engineer. */ -"for better metadata privacy." = "для лучшей конфиденциальности метаданных."; - /* servers error */ "For chat profile %@:" = "Для профиля чата %@:"; @@ -2562,9 +2484,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Полное имя (не обязательно)"; -/* No comment provided by engineer. */ -"Fully decentralized – visible only to members." = "Группа полностью децентрализована – она видна только членам."; - /* No comment provided by engineer. */ "Fully re-implemented - work in background!" = "Полностью обновлены - работают в фоне!"; @@ -2631,18 +2550,12 @@ /* No comment provided by engineer. */ "Group profile" = "Профиль группы"; -/* No comment provided by engineer. */ -"Group profile is stored on members' devices, not on the servers." = "Профиль группы хранится на устройствах членов, а не на серверах."; - /* snd group event chat item */ "group profile updated" = "профиль группы обновлен"; /* No comment provided by engineer. */ "Group welcome message" = "Приветственное сообщение группы"; -/* No comment provided by engineer. */ -"Group will be deleted for all members - this cannot be undone!" = "Группа будет удалена для всех членов - это действие нельзя отменить!"; - /* No comment provided by engineer. */ "Group will be deleted for you - this cannot be undone!" = "Группа будет удалена для Вас - это действие нельзя отменить!"; @@ -2679,9 +2592,6 @@ /* No comment provided by engineer. */ "History" = "История"; -/* No comment provided by engineer. */ -"History is not sent to new members." = "История не отправляется новым членам."; - /* time unit */ "hours" = "часов"; @@ -2916,9 +2826,6 @@ /* No comment provided by engineer. */ "Invite friends" = "Пригласить друзей"; -/* No comment provided by engineer. */ -"Invite members" = "Пригласить членов группы"; - /* No comment provided by engineer. */ "Invite to chat" = "Пригласить в разговор"; @@ -3132,66 +3039,15 @@ /* blur media */ "Medium" = "Среднее"; -/* member role */ -"member" = "член группы"; - -/* No comment provided by engineer. */ -"Member" = "Член группы"; - -/* profile update event chat item */ -"member %@ changed to %@" = "член %1$@ изменился на %2$@"; - /* rcv group event chat item */ "member connected" = "соединен(а)"; -/* item status text */ -"Member inactive" = "Член неактивен"; - /* chat feature */ "Member reports" = "Сообщения о нарушениях"; /* No comment provided by engineer. */ "Member role will be changed to \"%@\". All chat members will be notified." = "Роль участника будет изменена на \"%@\". Все участники разговора получат уведомление."; -/* No comment provided by engineer. */ -"Member role will be changed to \"%@\". All group members will be notified." = "Роль члена группы будет изменена на \"%@\". Все члены группы получат сообщение."; - -/* No comment provided by engineer. */ -"Member role will be changed to \"%@\". The member will receive a new invitation." = "Роль члена группы будет изменена на \"%@\". Будет отправлено новое приглашение."; - -/* No comment provided by engineer. */ -"Member will be removed from chat - this cannot be undone!" = "Член будет удален из разговора - это действие нельзя отменить!"; - -/* No comment provided by engineer. */ -"Member will be removed from group - this cannot be undone!" = "Член группы будет удален - это действие нельзя отменить!"; - -/* No comment provided by engineer. */ -"Members can add message reactions." = "Члены группы могут добавлять реакции на сообщения."; - -/* No comment provided by engineer. */ -"Members can irreversibly delete sent messages. (24 hours)" = "Члены группы могут необратимо удалять отправленные сообщения. (24 часа)"; - -/* No comment provided by engineer. */ -"Members can report messsages to moderators." = "Члены группы могут пожаловаться модераторам."; - -/* No comment provided by engineer. */ -"Members can send direct messages." = "Члены группы могут посылать прямые сообщения."; - -/* No comment provided by engineer. */ -"Members can send disappearing messages." = "Члены группы могут посылать исчезающие сообщения."; - -/* No comment provided by engineer. */ -"Members can send files and media." = "Члены группы могут слать файлы и медиа."; - -/* No comment provided by engineer. */ -"Members can send SimpleX links." = "Члены группы могут отправлять ссылки SimpleX."; - -/* No comment provided by engineer. */ -"Members can send voice messages." = "Члены группы могут отправлять голосовые сообщения."; - -/* No comment provided by engineer. */ -"Mention members 👋" = "Упоминайте членов группы 👋"; - /* No comment provided by engineer. */ "Menus" = "Меню"; @@ -3213,9 +3069,6 @@ /* item status text */ "Message forwarded" = "Сообщение переслано"; -/* item status description */ -"Message may be delivered later if member becomes active." = "Сообщение может быть доставлено позже, если член группы станет активным."; - /* No comment provided by engineer. */ "Message queue info" = "Информация об очереди сообщений"; @@ -3432,9 +3285,6 @@ /* No comment provided by engineer. */ "New media options" = "Новые медиа-опции"; -/* No comment provided by engineer. */ -"New member role" = "Роль члена группы"; - /* notification */ "new message" = "новое сообщение"; @@ -3582,15 +3432,12 @@ /* alert title */ "Notifications status" = "Статус уведомлений"; -/* No comment provided by engineer. */ -"Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "Теперь админы могут:\n- удалять сообщения членов.\n- приостанавливать членов (роль \"наблюдатель\")"; - /* member role */ "observer" = "читатель"; /* enabled status - group pref value - time to disappear */ +group pref value +time to disappear */ "off" = "нет"; /* blur media */ @@ -3782,9 +3629,6 @@ /* No comment provided by engineer. */ "Password to show" = "Пароль чтобы раскрыть"; -/* past/unknown group member */ -"Past member %@" = "Бывший член %@"; - /* No comment provided by engineer. */ "Paste desktop address" = "Вставить адрес компьютера"; @@ -3914,9 +3758,15 @@ /* No comment provided by engineer. */ "Privacy for your customers." = "Конфиденциальность для ваших покупателей."; +/* No comment provided by engineer. */ +"Privacy policy and conditions of use." = "Политика конфиденциальности и условия использования."; + /* No comment provided by engineer. */ "Privacy redefined" = "Более конфиденциальный"; +/* No comment provided by engineer. */ +"Private chats, groups and your contacts are not accessible to server operators." = "Частные разговоры, группы и Ваши контакты недоступны для операторов серверов."; + /* No comment provided by engineer. */ "Private filenames" = "Защищенные имена файлов"; @@ -3971,9 +3821,6 @@ /* No comment provided by engineer. */ "Prohibit reporting messages to moderators." = "Запретить жаловаться модераторам группы."; -/* No comment provided by engineer. */ -"Prohibit sending direct messages to members." = "Запретить посылать прямые сообщения членам группы."; - /* No comment provided by engineer. */ "Prohibit sending disappearing messages." = "Запретить посылать исчезающие сообщения."; @@ -4143,7 +3990,7 @@ "Registered" = "Зарегистрирован"; /* reject incoming call via notification - swipe action */ +swipe action */ "Reject" = "Отклонить"; /* No comment provided by engineer. */ @@ -4173,12 +4020,6 @@ /* No comment provided by engineer. */ "Remove image" = "Удалить изображение"; -/* No comment provided by engineer. */ -"Remove member" = "Удалить члена группы"; - -/* No comment provided by engineer. */ -"Remove member?" = "Удалить члена группы?"; - /* No comment provided by engineer. */ "Remove passphrase from keychain?" = "Удалить пароль из Keychain?"; @@ -4311,9 +4152,6 @@ /* No comment provided by engineer. */ "Review conditions" = "Посмотреть условия"; -/* No comment provided by engineer. */ -"Review later" = "Посмотреть позже"; - /* No comment provided by engineer. */ "Revoke" = "Отозвать"; @@ -4336,7 +4174,7 @@ "Safer groups" = "Более безопасные группы"; /* alert button - chat item action */ +chat item action */ "Save" = "Сохранить"; /* alert button */ @@ -4345,9 +4183,6 @@ /* alert button */ "Save and notify contact" = "Сохранить и уведомить контакт"; -/* No comment provided by engineer. */ -"Save and notify group members" = "Сохранить и уведомить членов группы"; - /* No comment provided by engineer. */ "Save and reconnect" = "Сохранить и переподключиться"; @@ -4540,9 +4375,6 @@ /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "Отправьте из галереи или из дополнительных клавиатур."; -/* No comment provided by engineer. */ -"Send up to 100 last messages to new members." = "Отправить до 100 последних сообщений новым членам."; - /* alert message */ "Sender cancelled file transfer." = "Отправитель отменил передачу файла."; @@ -4696,9 +4528,6 @@ /* No comment provided by engineer. */ "Set passphrase to export" = "Установите пароль"; -/* No comment provided by engineer. */ -"Set the message shown to new members!" = "Установить сообщение для новых членов группы!"; - /* No comment provided by engineer. */ "Set timeouts for proxy/VPN" = "Установить таймауты для прокси/VPN"; @@ -4712,7 +4541,7 @@ "Shape profile images" = "Форма картинок профилей"; /* alert action - chat item action */ +chat item action */ "Share" = "Поделиться"; /* No comment provided by engineer. */ @@ -4878,7 +4707,7 @@ "Somebody" = "Контакт"; /* blocking reason - report reason */ +report reason */ "Spam" = "Спам"; /* No comment provided by engineer. */ @@ -5088,18 +4917,6 @@ /* No comment provided by engineer. */ "The ID of the next message is incorrect (less or equal to the previous).\nIt can happen because of some bug or when the connection is compromised." = "Неправильный ID предыдущего сообщения (меньше или равен предыдущему).\nЭто может произойти из-за ошибки программы, или когда соединение компроментировано."; -/* No comment provided by engineer. */ -"The message will be deleted for all members." = "Сообщение будет удалено для всех членов группы."; - -/* No comment provided by engineer. */ -"The message will be marked as moderated for all members." = "Сообщение будет помечено как удаленное для всех членов группы."; - -/* No comment provided by engineer. */ -"The messages will be deleted for all members." = "Сообщения будут удалены для всех членов группы."; - -/* No comment provided by engineer. */ -"The messages will be marked as moderated for all members." = "Сообщения будут помечены как удаленные для всех членов группы."; - /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Предыдущая версия данных чата не удалена при перемещении, её можно удалить."; @@ -5109,9 +4926,6 @@ /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Те же самые условия будут приняты для оператора **%@**."; -/* No comment provided by engineer. */ -"The same conditions will apply to operator(s): **%@**." = "Те же самые условия будут приняты для оператора(ов): **%@**."; - /* No comment provided by engineer. */ "The second preset operator in the app!" = "Второй оператор серверов в приложении!"; @@ -5172,9 +4986,6 @@ /* No comment provided by engineer. */ "This display name is invalid. Please choose another name." = "Ошибка имени профиля. Пожалуйста, выберите другое имя."; -/* No comment provided by engineer. */ -"This group has over %lld members, delivery receipts are not sent." = "В группе более %lld членов, отчёты о доставке выключены."; - /* No comment provided by engineer. */ "This group no longer exists." = "Эта группа больше не существует."; @@ -5295,15 +5106,6 @@ /* No comment provided by engineer. */ "Unblock for all" = "Разблокировать для всех"; -/* No comment provided by engineer. */ -"Unblock member" = "Разблокировать члена группы"; - -/* No comment provided by engineer. */ -"Unblock member for all?" = "Разблокировать члена для всех?"; - -/* No comment provided by engineer. */ -"Unblock member?" = "Разблокировать члена группы?"; - /* rcv group event chat item */ "unblocked %@" = "%@ разблокирован"; @@ -5376,9 +5178,6 @@ /* swipe action */ "Unread" = "Не прочитано"; -/* No comment provided by engineer. */ -"Up to 100 last messages are sent to new members." = "До 100 последних сообщений отправляются новым членам."; - /* No comment provided by engineer. */ "Update" = "Обновить"; @@ -5766,9 +5565,6 @@ /* No comment provided by engineer. */ "You can change it in Appearance settings." = "Вы можете изменить это в настройках Интерфейса."; -/* No comment provided by engineer. */ -"You can configure operators in Network & servers settings." = "Вы можете настроить операторов в настройках Сети и серверов."; - /* No comment provided by engineer. */ "You can configure servers via settings." = "Вы можете настроить серверы позже."; @@ -5802,9 +5598,6 @@ /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Вы можете установить просмотр уведомлений на экране блокировки в настройках."; -/* No comment provided by engineer. */ -"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." = "Вы можете поделиться ссылкой или QR кодом - через них можно присоединиться к группе. Вы сможете удалить ссылку, сохранив членов группы, которые через нее соединились."; - /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "Вы можете поделиться этим адресом с Вашими контактами, чтобы они могли соединиться с **%@**."; @@ -5859,9 +5652,6 @@ /* No comment provided by engineer. */ "You joined this group" = "Вы вступили в эту группу"; -/* No comment provided by engineer. */ -"You joined this group. Connecting to inviting group member." = "Вы вступили в эту группу. Устанавливается соединение с пригласившим членом группы."; - /* snd group event chat item */ "you left" = "Вы покинули группу"; @@ -5916,9 +5706,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 connect to all group members." = "Вы соединитесь со всеми членами группы."; - /* No comment provided by engineer. */ "You will still receive calls and notifications from muted profiles when they are active." = "Вы все равно получите звонки и уведомления в профилях без звука, когда они активные."; @@ -6015,6 +5802,3 @@ /* No comment provided by engineer. */ "Your SimpleX address" = "Ваш адрес SimpleX"; -/* No comment provided by engineer. */ -"Your SMP servers" = "Ваши SMP серверы"; - diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index abe1632645..6b3381922a 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -164,7 +164,7 @@ "0s" = "0s"; /* delete after time - time interval */ +time interval */ "1 day" = "1 วัน"; /* time interval */ @@ -174,11 +174,11 @@ "1 minute" = "1 นาที"; /* delete after time - time interval */ +time interval */ "1 month" = "1 เดือน"; /* delete after time - time interval */ +time interval */ "1 week" = "1 สัปดาห์"; /* No comment provided by engineer. */ @@ -218,15 +218,15 @@ "above, then choose:" = "ด้านบน จากนั้นเลือก:"; /* accept contact request via notification - accept incoming call via notification - swipe action */ +accept incoming call via notification +swipe action */ "Accept" = "รับ"; /* notification body */ "Accept contact request from %@?" = "รับการขอติดต่อจาก %@?"; /* accept contact request via notification - swipe action */ +swipe action */ "Accept incognito" = "ยอมรับโหมดไม่ระบุตัวตน"; /* call status */ @@ -485,7 +485,7 @@ "Can't invite contacts!" = "ไม่สามารถเชิญผู้ติดต่อได้!"; /* alert action - alert button */ +alert button */ "Cancel" = "ยกเลิก"; /* feature offered item */ @@ -525,7 +525,7 @@ "Change self-destruct mode" = "เปลี่ยนโหมดทําลายตัวเอง"; /* authentication reason - set passcode view */ +set passcode view */ "Change self-destruct passcode" = "เปลี่ยนรหัสผ่านแบบทำลายตัวเอง"; /* chat item text */ @@ -835,7 +835,7 @@ "Decryption error" = "ข้อผิดพลาดในการ decrypt"; /* delete after time - pref value */ +pref value */ "default (%@)" = "ค่าเริ่มต้น (%@)"; /* No comment provided by engineer. */ @@ -845,7 +845,7 @@ "default (yes)" = "ค่าเริ่มต้น (ใช่)"; /* alert action - swipe action */ +swipe action */ "Delete" = "ลบ"; /* No comment provided by engineer. */ @@ -1310,7 +1310,9 @@ /* No comment provided by engineer. */ "Error: " = "ผิดพลาด: "; -/* alert message */ +/* alert message +file error text +snd error text */ "Error: %@" = "ข้อผิดพลาด: % @"; /* No comment provided by engineer. */ @@ -2013,8 +2015,8 @@ "observer" = "ผู้สังเกตการณ์"; /* enabled status - group pref value - time to disappear */ +group pref value +time to disappear */ "off" = "ปิด"; /* blur media */ @@ -2315,7 +2317,7 @@ "Reduced battery usage" = "ลดการใช้แบตเตอรี่"; /* reject incoming call via notification - swipe action */ +swipe action */ "Reject" = "ปฏิเสธ"; /* No comment provided by engineer. */ @@ -2412,7 +2414,7 @@ "Run chat" = "เรียกใช้แชท"; /* alert button - chat item action */ +chat item action */ "Save" = "บันทึก"; /* alert button */ @@ -2614,7 +2616,7 @@ "Settings" = "การตั้งค่า"; /* alert action - chat item action */ +chat item action */ "Share" = "แชร์"; /* No comment provided by engineer. */ @@ -3307,6 +3309,3 @@ /* No comment provided by engineer. */ "Your SimpleX address" = "ที่อยู่ SimpleX ของคุณ"; -/* No comment provided by engineer. */ -"Your SMP servers" = "เซิร์ฟเวอร์ SMP ของคุณ"; - diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index 88b2ac401e..718d547c67 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -266,7 +266,7 @@ "0s" = "0sn"; /* delete after time - time interval */ +time interval */ "1 day" = "1 gün"; /* time interval */ @@ -276,11 +276,11 @@ "1 minute" = "1 dakika"; /* delete after time - time interval */ +time interval */ "1 month" = "1 ay"; /* delete after time - time interval */ +time interval */ "1 week" = "1 hafta"; /* No comment provided by engineer. */ @@ -335,8 +335,8 @@ "Accent" = "Ana renk"; /* accept contact request via notification - accept incoming call via notification - swipe action */ +accept incoming call via notification +swipe action */ "Accept" = "Kabul et"; /* No comment provided by engineer. */ @@ -349,7 +349,7 @@ "Accept contact request from %@?" = "%@ 'den gelen iletişim isteği kabul edilsin mi?"; /* accept contact request via notification - swipe action */ +swipe action */ "Accept incognito" = "Takma adla kabul et"; /* call status */ @@ -751,7 +751,8 @@ /* rcv group event chat item */ "blocked %@" = "engellendi %@"; -/* marked deleted chat item preview text */ +/* blocked chat item +marked deleted chat item preview text */ "blocked by admin" = "yönetici tarafından engellendi"; /* No comment provided by engineer. */ @@ -833,7 +834,7 @@ "Can't message member" = "Üyeye mesaj gönderilemiyor"; /* alert action - alert button */ +alert button */ "Cancel" = "İptal et"; /* No comment provided by engineer. */ @@ -888,7 +889,7 @@ "Change self-destruct mode" = "Kendini yok etme modunu değiştir"; /* authentication reason - set passcode view */ +set passcode view */ "Change self-destruct passcode" = "Kendini yok eden parolayı değiştir"; /* chat item text */ @@ -1047,12 +1048,6 @@ /* No comment provided by engineer. */ "Conditions of use" = "Kullanım koşulları"; -/* No comment provided by engineer. */ -"Conditions will be accepted for enabled operators after 30 days." = "Koşullar 30 gün sonra etkin operatörler tarafından kabul edilecektir."; - -/* No comment provided by engineer. */ -"Conditions will be accepted for operator(s): **%@**." = "Koşullar operatör(ler) için kabul edilecektir: **%@**."; - /* No comment provided by engineer. */ "Conditions will be accepted for the operator(s): **%@**." = "Koşullar bu operatör(ler) için kabul edilecektir: **%@**."; @@ -1453,7 +1448,7 @@ "decryption errors" = "Şifre çözme hataları"; /* delete after time - pref value */ +pref value */ "default (%@)" = "varsayılan (%@)"; /* No comment provided by engineer. */ @@ -1463,7 +1458,7 @@ "default (yes)" = "varsayılan (evet)"; /* alert action - swipe action */ +swipe action */ "Delete" = "Sil"; /* No comment provided by engineer. */ @@ -1752,7 +1747,7 @@ "Downgrade and open chat" = "Sürüm düşür ve sohbeti aç"; /* alert button - chat item action */ +chat item action */ "Download" = "İndir"; /* No comment provided by engineer. */ @@ -1815,9 +1810,6 @@ /* No comment provided by engineer. */ "Enable camera access" = "Kamera erişimini etkinleştir"; -/* No comment provided by engineer. */ -"Enable Flux" = "Flux'u Etkinleştir"; - /* No comment provided by engineer. */ "Enable for all" = "Herkes için etkinleştir"; @@ -2172,7 +2164,9 @@ /* No comment provided by engineer. */ "Error: " = "Hata: "; -/* alert message */ +/* alert message +file error text +snd error text */ "Error: %@" = "Hata: %@"; /* No comment provided by engineer. */ @@ -2190,9 +2184,6 @@ /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Konuşma sırasında devre dışı bırakılsa bile."; -/* No comment provided by engineer. */ -"event happened" = "etkinlik yaşandı"; - /* No comment provided by engineer. */ "Exit without saving" = "Kaydetmeden çık"; @@ -3280,8 +3271,8 @@ "observer" = "gözlemci"; /* enabled status - group pref value - time to disappear */ +group pref value +time to disappear */ "off" = "kapalı"; /* blur media */ @@ -3759,7 +3750,7 @@ "Reduced battery usage" = "Azaltılmış pil kullanımı"; /* reject incoming call via notification - swipe action */ +swipe action */ "Reject" = "Reddet"; /* No comment provided by engineer. */ @@ -3910,7 +3901,7 @@ "Safer groups" = "Daha güvenli gruplar"; /* alert button - chat item action */ +chat item action */ "Save" = "Kaydet"; /* alert button */ @@ -4262,7 +4253,7 @@ "Shape profile images" = "Profil resimlerini şekillendir"; /* alert action - chat item action */ +chat item action */ "Share" = "Paylaş"; /* No comment provided by engineer. */ @@ -5429,6 +5420,3 @@ /* No comment provided by engineer. */ "Your SimpleX address" = "SimpleX adresin"; -/* No comment provided by engineer. */ -"Your SMP servers" = "SMP sunucularınız"; - diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index ceb8ee0bfa..f54ecec21d 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -266,7 +266,7 @@ "0s" = "0с"; /* delete after time - time interval */ +time interval */ "1 day" = "1 день"; /* time interval */ @@ -276,11 +276,11 @@ "1 minute" = "1 хвилина"; /* delete after time - time interval */ +time interval */ "1 month" = "1 місяць"; /* delete after time - time interval */ +time interval */ "1 week" = "1 тиждень"; /* No comment provided by engineer. */ @@ -335,8 +335,8 @@ "Accent" = "Акцент"; /* accept contact request via notification - accept incoming call via notification - swipe action */ +accept incoming call via notification +swipe action */ "Accept" = "Прийняти"; /* No comment provided by engineer. */ @@ -349,7 +349,7 @@ "Accept contact request from %@?" = "Прийняти запит на контакт від %@?"; /* accept contact request via notification - swipe action */ +swipe action */ "Accept incognito" = "Прийняти інкогніто"; /* call status */ @@ -754,7 +754,8 @@ /* rcv group event chat item */ "blocked %@" = "заблоковано %@"; -/* marked deleted chat item preview text */ +/* blocked chat item +marked deleted chat item preview text */ "blocked by admin" = "заблоковано адміністратором"; /* No comment provided by engineer. */ @@ -836,7 +837,7 @@ "Can't message member" = "Не можу надіслати повідомлення користувачеві"; /* alert action - alert button */ +alert button */ "Cancel" = "Скасувати"; /* No comment provided by engineer. */ @@ -891,7 +892,7 @@ "Change self-destruct mode" = "Змінити режим самознищення"; /* authentication reason - set passcode view */ +set passcode view */ "Change self-destruct passcode" = "Змінити пароль самознищення"; /* chat item text */ @@ -1050,12 +1051,6 @@ /* No comment provided by engineer. */ "Conditions of use" = "Умови використання"; -/* No comment provided by engineer. */ -"Conditions will be accepted for enabled operators after 30 days." = "Умови будуть прийняті для ввімкнених операторів через 30 днів."; - -/* No comment provided by engineer. */ -"Conditions will be accepted for operator(s): **%@**." = "Умови приймаються для оператора(ів): **%@**."; - /* No comment provided by engineer. */ "Conditions will be accepted for the operator(s): **%@**." = "Для оператора(ів) приймаються умови: **%@**."; @@ -1456,7 +1451,7 @@ "decryption errors" = "помилки розшифровки"; /* delete after time - pref value */ +pref value */ "default (%@)" = "за замовчуванням (%@)"; /* No comment provided by engineer. */ @@ -1466,7 +1461,7 @@ "default (yes)" = "за замовчуванням (так)"; /* alert action - swipe action */ +swipe action */ "Delete" = "Видалити"; /* No comment provided by engineer. */ @@ -1758,7 +1753,7 @@ "Downgrade and open chat" = "Пониження та відкритий чат"; /* alert button - chat item action */ +chat item action */ "Download" = "Завантажити"; /* No comment provided by engineer. */ @@ -1821,9 +1816,6 @@ /* No comment provided by engineer. */ "Enable camera access" = "Увімкніть доступ до камери"; -/* No comment provided by engineer. */ -"Enable Flux" = "Увімкнути Flux"; - /* No comment provided by engineer. */ "Enable for all" = "Увімкнути для всіх"; @@ -2178,7 +2170,9 @@ /* No comment provided by engineer. */ "Error: " = "Помилка: "; -/* alert message */ +/* alert message +file error text +snd error text */ "Error: %@" = "Помилка: %@"; /* No comment provided by engineer. */ @@ -2196,9 +2190,6 @@ /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Навіть коли вимкнений у розмові."; -/* No comment provided by engineer. */ -"event happened" = "відбулася подія"; - /* No comment provided by engineer. */ "Exit without saving" = "Вихід без збереження"; @@ -2319,9 +2310,6 @@ /* No comment provided by engineer. */ "Fix not supported by group member" = "Виправлення не підтримується учасником групи"; -/* No comment provided by engineer. */ -"for better metadata privacy." = "для кращої конфіденційності метаданих."; - /* servers error */ "For chat profile %@:" = "Для профілю чату %@:"; @@ -3328,8 +3316,8 @@ "observer" = "спостерігач"; /* enabled status - group pref value - time to disappear */ +group pref value +time to disappear */ "off" = "вимкнено"; /* blur media */ @@ -3834,7 +3822,7 @@ "Reduced battery usage" = "Зменшення використання акумулятора"; /* reject incoming call via notification - swipe action */ +swipe action */ "Reject" = "Відхилити"; /* No comment provided by engineer. */ @@ -3969,9 +3957,6 @@ /* No comment provided by engineer. */ "Review conditions" = "Умови перегляду"; -/* No comment provided by engineer. */ -"Review later" = "Перегляньте пізніше"; - /* No comment provided by engineer. */ "Revoke" = "Відкликати"; @@ -3994,7 +3979,7 @@ "Safer groups" = "Безпечніші групи"; /* alert button - chat item action */ +chat item action */ "Save" = "Зберегти"; /* alert button */ @@ -4358,7 +4343,7 @@ "Shape profile images" = "Сформуйте зображення профілю"; /* alert action - chat item action */ +chat item action */ "Share" = "Поділіться"; /* No comment provided by engineer. */ @@ -4742,9 +4727,6 @@ /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Такі ж умови діятимуть і для оператора **%@**."; -/* No comment provided by engineer. */ -"The same conditions will apply to operator(s): **%@**." = "Такі ж умови будуть застосовуватися до оператора(ів): **%@**."; - /* No comment provided by engineer. */ "The second preset operator in the app!" = "Другий попередньо встановлений оператор у застосунку!"; @@ -5378,9 +5360,6 @@ /* No comment provided by engineer. */ "You can change it in Appearance settings." = "Ви можете змінити його в налаштуваннях зовнішнього вигляду."; -/* No comment provided by engineer. */ -"You can configure operators in Network & servers settings." = "Ви можете налаштувати операторів у налаштуваннях Мережі та серверів."; - /* No comment provided by engineer. */ "You can configure servers via settings." = "Ви можете налаштувати сервери за допомогою налаштувань."; @@ -5624,6 +5603,3 @@ /* No comment provided by engineer. */ "Your SimpleX address" = "Ваша адреса SimpleX"; -/* No comment provided by engineer. */ -"Your SMP servers" = "Ваші SMP-сервери"; - diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index 3ed0d0fa14..b363d25a4c 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -121,6 +121,12 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ 已认证"; +/* No comment provided by engineer. */ +"%@ server" = "服务器"; + +/* No comment provided by engineer. */ +"%@ servers" = "服务器"; + /* No comment provided by engineer. */ "%@ uploaded" = "%@ 已上传"; @@ -239,7 +245,7 @@ "0s" = "0秒"; /* delete after time - time interval */ +time interval */ "1 day" = "1天"; /* time interval */ @@ -249,11 +255,11 @@ "1 minute" = "1分钟"; /* delete after time - time interval */ +time interval */ "1 month" = "1月"; /* delete after time - time interval */ +time interval */ "1 week" = "1周"; /* No comment provided by engineer. */ @@ -299,8 +305,8 @@ "Accent" = "强调"; /* accept contact request via notification - accept incoming call via notification - swipe action */ +accept incoming call via notification +swipe action */ "Accept" = "接受"; /* No comment provided by engineer. */ @@ -310,7 +316,7 @@ "Accept contact request from %@?" = "接受来自 %@ 的联系人请求?"; /* accept contact request via notification - swipe action */ +swipe action */ "Accept incognito" = "接受隐身聊天"; /* call status */ @@ -664,7 +670,8 @@ /* rcv group event chat item */ "blocked %@" = "已封禁 %@"; -/* marked deleted chat item preview text */ +/* blocked chat item +marked deleted chat item preview text */ "blocked by admin" = "由管理员封禁"; /* No comment provided by engineer. */ @@ -700,6 +707,9 @@ /* 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)." = "通过聊天资料(默认)或者[通过连接](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)。"; +/* No comment provided by engineer. */ +"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "使用 SimpleX Chat 代表您同意:\n- 在公开群中只发送合法内容\n- 尊重其他用户 – 没有垃圾信息。"; + /* No comment provided by engineer. */ "call" = "呼叫"; @@ -740,7 +750,7 @@ "Can't message member" = "无法向成员发送消息"; /* alert action - alert button */ +alert button */ "Cancel" = "取消"; /* No comment provided by engineer. */ @@ -792,7 +802,7 @@ "Change self-destruct mode" = "更改自毁模式"; /* authentication reason - set passcode view */ +set passcode view */ "Change self-destruct passcode" = "更改自毁密码"; /* chat item text */ @@ -915,9 +925,15 @@ /* No comment provided by engineer. */ "Completed" = "已完成"; +/* No comment provided by engineer. */ +"Conditions of use" = "使用条款"; + /* No comment provided by engineer. */ "Configure ICE servers" = "配置 ICE 服务器"; +/* No comment provided by engineer. */ +"Configure server operators" = "配置服务器运营方"; + /* No comment provided by engineer. */ "Confirm" = "确认"; @@ -1291,7 +1307,7 @@ "decryption errors" = "解密错误"; /* delete after time - pref value */ +pref value */ "default (%@)" = "默认 (%@)"; /* No comment provided by engineer. */ @@ -1301,7 +1317,7 @@ "default (yes)" = "默认 (是)"; /* alert action - swipe action */ +swipe action */ "Delete" = "删除"; /* No comment provided by engineer. */ @@ -1575,7 +1591,7 @@ "Downgrade and open chat" = "降级并打开聊天"; /* alert button - chat item action */ +chat item action */ "Download" = "下载"; /* No comment provided by engineer. */ @@ -1959,7 +1975,9 @@ /* No comment provided by engineer. */ "Error: " = "错误: "; -/* alert message */ +/* alert message +file error text +snd error text */ "Error: %@" = "错误: %@"; /* No comment provided by engineer. */ @@ -1974,9 +1992,6 @@ /* No comment provided by engineer. */ "Even when disabled in the conversation." = "即使在对话中被禁用。"; -/* No comment provided by engineer. */ -"event happened" = "发生的事"; - /* No comment provided by engineer. */ "Exit without saving" = "退出而不保存"; @@ -2989,8 +3004,8 @@ "observer" = "观察者"; /* enabled status - group pref value - time to disappear */ +group pref value +time to disappear */ "off" = "关闭"; /* blur media */ @@ -3245,9 +3260,15 @@ /* No comment provided by engineer. */ "Privacy & security" = "隐私和安全"; +/* No comment provided by engineer. */ +"Privacy policy and conditions of use." = "隐私政策和使用条款。"; + /* No comment provided by engineer. */ "Privacy redefined" = "重新定义隐私"; +/* No comment provided by engineer. */ +"Private chats, groups and your contacts are not accessible to server operators." = "服务器运营方无法访问私密聊天、群组和你的联系人。"; + /* No comment provided by engineer. */ "Private filenames" = "私密文件名"; @@ -3456,7 +3477,7 @@ "Reduced battery usage" = "减少电池使用量"; /* reject incoming call via notification - swipe action */ +swipe action */ "Reject" = "拒绝"; /* No comment provided by engineer. */ @@ -3604,7 +3625,7 @@ "Safer groups" = "更安全的群组"; /* alert button - chat item action */ +chat item action */ "Save" = "保存"; /* alert button */ @@ -3941,7 +3962,7 @@ "Shape profile images" = "改变个人资料图形状"; /* alert action - chat item action */ +chat item action */ "Share" = "分享"; /* No comment provided by engineer. */ @@ -5060,6 +5081,3 @@ /* No comment provided by engineer. */ "Your SimpleX address" = "您的 SimpleX 地址"; -/* No comment provided by engineer. */ -"Your SMP servers" = "您的 SMP 服务器"; - diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index 20b851bad7..160e1322b7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -2156,7 +2156,7 @@ أشرطة أدوات التطبيق تمويه الشفافية - فعّل flux + فعّل flux في إعدادات الشبكة والخوادم لتحسين خصوصية البيانات الوصفية. اللامركزية الشبكية المُشغل المُعد مسبقًا الثاني في التطبيق! لتحسين خصوصية البيانات الوصفية. @@ -2361,4 +2361,9 @@ المشرفين لا يمكن قراءة عبارة المرور في Keystore. قد يكون هذا قد حدث بعد تحديث النظام غير متوافق مع التطبيق. إذا لم يكن الأمر كذلك، فيُرجى التواصل مع المطورين. موافقة الانتظار + ضبّط مُشغلي الخادم + سياسة الخصوصية وشروط الاستخدام. + لا يمكن الوصول إلى الدردشات الخاصة والمجموعات وجهات اتصالك لمشغلي الخادم. + باستخدام SimpleX Chat، توافق على:\n- إرسال المحتوى القانوني فقط في المجموعات العامة.\n- احترام المستخدمين الآخرين – لا سبام. + اقبل diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml index f2a18050c3..b0c6e32c70 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml @@ -182,12 +182,12 @@ Permet als teus contactes eliminar de manera irreversible els missatges enviats. (24 hores) Permet que els teus contactes enviïn missatges que desapareixen. Permet que els teus contactes enviïn missatges de veu. - Tant tu com el teu contacte pots suprimir de manera irreversible els missatges enviats. (24 hores) + Tant vos com els vostres contactes podeu suprimir de manera irreversible els missatges enviats. (24 hores) Tant tu com el teu contacte podeu enviar missatges que desapareguin. Tant tu com el teu contacte podeu afegir reaccions als missatges. Permet la supressió irreversible del missatge només si el teu contacte t\'ho permet. (24 hores) Permet enviar missatges directes als membres. - Permet suprimir de manera irreversible els missatges enviats. (24 hores) + Permeteu suprimir de manera irreversible els missatges enviats. (24 hores) Permet enviar missatges que desapareixen. Permet enviar fitxers i mitjans. Permet enviar missatges de veu. @@ -307,7 +307,7 @@ Cancel·la el canvi d\'adreça Accepta les condicions Condicions acceptades - "Afegiu perfil" + Afegiu un perfil Voleu cancel·lar el canvi d\'adreça? 1 minut Accepta @@ -323,7 +323,7 @@ Configuració d\'adreça Afegeix a un altre dispositiu administradors - Suprimir contacte? + Voleu suprimir el contacte? Desapareixerà a: %s L\'autenticació del dispositiu està desactivada. S\'està desactivant el bloqueig SimpleX. L\'autenticació del dispositiu no està activada. Podeu activar SimpleX Lock mitjançant Configuració, un cop hàgiu activat l\'autenticació del dispositiu. @@ -331,7 +331,7 @@ %1$s
    .]]> %1$s
    .]]> L\'adreça del servidor de destinació de %1$s és incompatible amb la configuració del servidor de reenviament %2$s. - Suprimir perfil + Suprimeix el perfil %d hores Esborrar per mi Els missatges directes entre membres estan prohibits en aquest xat. @@ -341,7 +341,7 @@ %d missatge(s) bloquejat(s) per l\'administrador %d missatge(s) marcat(s) eliminat(s) Error de desxifrat - Suprimir cua + Suprimeix la cua Esborrar fitxer SimpleX s\'executa en segon pla
    en lloc d\'utilitzar notificacions push.]]> Desactivar notificacions @@ -350,29 +350,29 @@ %d minuts Desactiva el bloqueig SimpleX Error del servidor de destinació: %1$s - Suprimir el missatge? - Suprimir %d missatges? - Suprimir %d missatges dels membres? - Suprimir el missatge del membre? + Voleu suprimir el missatge? + Voleu suprimir els %d missatges? + Voleu suprimir %d missatges dels membres? + Vols suprimir el missatge del membre? Error de descodificació Eliminar i notificar el contacte - Suprimir contacte - Suprimir sense notificació + Suprimeix el contacte + Suprimeix sense notificació Desconnectat Missatge que desapareix - Suprimir - Suprimir - Suprimir la connexió pendent? + Suprimeix + Suprimeix + Voleu suprimir la connexió pendent? Contrasenya i exportació de la base de dades - Suprimir servidor + Suprimeix el servidor Utilitza els hosts .onion a No si el servidor SOCKS no els admet.]]> Desactivat NO envieu missatges directament, encara que el vostre servidor de destinació no admeti l\'encaminament privat. - Suprimir l\'adreça? + Voleu suprimir l\'adreça? Opcions de desenvolupador Desactivar Esborrar adreça - Suprimir imatge + Suprimeix la imatge El nom mostrat no pot contenir espais en blanc. Descentralitzada Desactivar @@ -386,11 +386,11 @@ DISPOSITIU La base de dades es xifra amb una contrasenya aleatòria. Si us plau, canvieu-la abans d\'exportar. Contrasenya de la base de dades - Suprimir perfil? + Voleu suprimir el perfil? Esborrar la base de dades - Suprimir tots els fitxers - Suprimir fitxers i mitjans? - Suprimir fitxers de tots els perfils + Suprimeix tots els fitxers + Voleu suprimir els fitxers i els mitjans? + Suprimeix els fitxers de tots els perfils %d fitxer(s) amb una mida total de %s Base de dades xifrada! Suprimir missatges @@ -427,17 +427,17 @@ %s
    .]]> %s
    .]]> %s
    , accepteu les condicions d\'ús.]]> - Suprimir perfil? - Suprimir perfil + Voleu suprimir el perfil? + Suprimeix el perfil predeterminat (%s) - Suprimir per a tothom + Suprimeix per a tothom Missatges directes Missatges que desapareixen Els missatges que desapareixen estan prohibits en aquest xat. %dd %d dia %d dies - Suprimir en + Suprimeix després %dh %d hora Els missatges directes entre membres estan prohibits. @@ -638,7 +638,7 @@ Introduïu el vostre nom: Error en obrir el navegador Error en exportar la base de dades de xat - Error en suprimir la base de dades de xat + S\'ha produït un error en suprimir la base de dades de xat Error en importar la base de dades de xat Error en canviar la configuració Error en eliminar el membre @@ -667,14 +667,14 @@ Error en carregar els detalls Error en rebre el fitxer Error en acceptar la sol·licitud de contacte - Error en suprimir el contacte - Error en suprimir el grup + S\'ha produït un error en suprimir el contacte + S\'ha produït un error en suprimir el grup Error en cancel·lar el canvi d\'adreça Error en canviar l\'adreça - Error en suprimir la sol·licitud de contacte - Error en suprimir la connexió de contacte pendent - Error en suprimir les notes privades - Error en suprimir el perfil d\'usuari + S\'ha produït un error en suprimir la sol·licitud de contacte + S\'ha produït un error en suprimir la connexió de contacte pendent + S\'ha produït un error en suprimir les notes privades + S\'ha produït un error en suprimir el perfil d\'usuari Comparar el fitxer Error: %1$s Error @@ -875,7 +875,7 @@ %ds %d setmanes Baixa noves versions de GitHub. - Habilita Flux + Habiliteu Flux a la configuració de la xarxa i dels servidors per obtenir una millor privadesa de les metadades. Connectar amb l\'ordinador Connectant Errors de descàrrega @@ -939,7 +939,7 @@ habilitat per al contacte habilitat habilitat per a tu - Els contactes poden marcar missatges per suprimir-los; els podreu veure. + Els contactes poden marcar missatges per suprimir-los. Encara els podreu veure. Habilitat per Personalitza i comparteix temes de color. Temes personalitzats @@ -1149,7 +1149,7 @@ Pot passar quan:\n1. Els missatges van caducar al client d\'enviament al cap de 2 dies o al servidor després de 30 dies.\n2. No s\'ha pogut desxifrar el missatge, perquè tu o el teu contacte feien servir una còpia de seguretat de la base de dades antiga.\n3. La connexió s\'ha compromès. Només dades de perfil local Unir-se al teu grup? - El missatge es marcarà per suprimir-lo. Els destinataris podran revelar aquest missatge. + El missatge es marcarà per suprimir-lo. Els destinataris podran visualitzar aquest missatge. Nova experiència de xat 🎉 Unir-te Enganxar enllaç per connectar! @@ -1160,7 +1160,7 @@ el membre %1$s ha canviat a %2$s Canvia l\'aspecte dels teus xats! Màxim 40 segons, rebut a l\'instant. - Només tu pots suprimir missatges de manera irreversible (el teu contacte pot marcar-los per suprimir-los). (24 hores) + Només vos podreu suprimir els missatges de manera irreversible (el vostre contacte pot marcar-los per suprimir-los). (24 hores) Obrir configuració del servidor altres errors - Notificació opcional als contactes suprimits.\n- Noms de perfil amb espais.\n- I més! @@ -1282,7 +1282,7 @@ Nou arxiu de bases de dades Obrir la carpeta de la base de dades Aturar SimpleX? - Atura SimpleX per exportar, importar o suprimir la base de dades de xat. No podreu rebre ni enviar missatges mentre el xat estigui aturat. + Atura SimpleX per poder exportar, importar o suprimir la base de dades de xat. No podreu rebre ni enviar missatges mentre el xat estigui aturat. Estableix contrasenya per a exportar Aturar Reinicieu l\'aplicació per utilitzar la base de dades de xat importada. @@ -1315,7 +1315,7 @@ Toqueu per activar el perfil. Silenciat quan està inactiu! Encara rebràs trucades i notificacions de perfils silenciats quan estiguin actius. - Només el vostre contacte pot suprimir missatges de manera irreversible (pots marcar-los per suprimir-los). (24 hores) + Només el vostre contacte pot suprimir missatges de manera irreversible (podeu marcar-los per suprimir-los). (24 hores) La supressió de missatges irreversible està prohibida en aquest xat. Els vostres contactes poden permetre la supressió completa del missatge. Eliminació irreversible del missatge @@ -1959,7 +1959,7 @@ mai No s\'han rebut ni enviats fitxers Reinicieu l\'aplicació per crear un perfil de xat nou. - Aquesta acció no es pot desfer: se suprimiran tots els fitxers i mitjans rebuts i enviats. Les imatges de baixa resolució es mantindran. + Aquesta acció no es pot desfer: se suprimiran tots els fitxers i els mitjans rebuts i enviats. Les imatges de baixa resolució es mantindran. Heu d\'utilitzar la versió més recent de la vostra base de dades de xat NOMÉS en un dispositiu, en cas contrari, podeu deixar de rebre els missatges d\'alguns contactes. Aquesta configuració s\'aplica als missatges del vostre perfil de xat actual La vostra base de dades de xat no està xifrada; definiu una contrasenya per protegir-la. @@ -2215,7 +2215,7 @@ L\'actualització del perfil s\'enviarà als vostres contactes. a + b ratllat - Suprimir en + Suprimeix els missatges després Eliminar el perfil de xat per La connexió no està preparada. Error en crear la llista de xat @@ -2258,7 +2258,7 @@ Violació de les normes de la comunitat Contingut inadequat Perfil inadequat - Suprimir informe + Suprimeix l\'informe L\'informe s\'arxivarà. Informar Spam @@ -2285,13 +2285,13 @@ Només ho veieu vosaltres i moderació Error en crear informe Establir nom del xat… - Suprimir els missatges de xat del teu dispositiu. + Suprimiu els missatges de xat del vostre dispositiu. Voleu canviar la supressió automàtica de missatges? predeterminat (%s) 1 any Desactivar la supressió de missatges Desactivar la supressió automàtica de missatges? - Aquesta acció no es pot desfer; els missatges enviats i rebuts en aquest xat abans del seleccionat se suprimiran. + Aquesta acció no es pot desfer; els missatges enviats i rebuts en aquest xat anteriors al seleccionat se suprimiran. Els missatges d\'aquest xat no se suprimiran mai. Port TCP per a missatgeria Emprar port web @@ -2324,4 +2324,23 @@ Ajudar els administradors a moderar els seus grups. rebutjat rebutjat + pendent + pendent d\'aprovació + Tots els missatges nous d\'aquests/es membres s\'amagaran! + Blocar membres per a tots/es? + Condicions actualitzades + Error en llegir la contrasenya de la base de dades + Els/les membres s\'eliminaran del xat; això no es pot desfer! + Els missatges d\'aquests/es membres es mostraran! + Desblocar membres per a tots/es? + moderació + La frase de contrasenya a Keystore no es pot llegir. Això pot haver passat després que l\'actualització del sistema sigui incompatible amb l\'aplicació. Si no és el cas, poseu-vos en contacte amb els desenvolupadors. + Els/les membres s\'eliminaran del grup; això no es pot desfer! + La frase de contrasenya a Keystore no es pot llegir, introduïu-la manualment. Això pot haver passat després que l\'actualització del sistema sigui incompatible amb l\'aplicació. Si no és el cas, poseu-vos en contacte amb els desenvolupadors. + Expulsar membres? + Política de privadesa i condicions d\'ús. + Els xats privats, els grups i els vostres contactes no són accessibles per als operadors de servidor. + Acceptar + En utilitzar SimpleX Chat accepteu:\n- enviar només contingut legal en grups públics.\n- Respectar els altres usuaris, sense correu brossa. + Configurar els operadors de servidor diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml index 9d13f4f262..3b8db5044b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml @@ -2359,4 +2359,12 @@ Aktualizované podmínky čekám na schválení čekám + Blokovat členy všem? + Všechny nové zprávy od těchto členů budou skryty! + Odblokovat členy všem? + moderátoři + Zprávy od těchto členů budou zobrazeny! + Členové budou odstraněny ze skupiny - toto nelze zvrátit! + Odebrat členy? + Členové budou odstraněny z chatu - toto nelze zvrátit! diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index 551b6caa7f..145d4142a7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -20,7 +20,7 @@ Gelöscht als gelöscht markiert Das Senden von Dateien wird noch nicht unterstützt - Der Empfang von Dateien wird noch nicht unterstützt + Das Herunterladen von Dateien wird noch nicht unterstützt Profil Unbekanntes Nachrichtenformat Ungültiges Nachrichtenformat @@ -58,9 +58,9 @@ Fehler beim Senden der Nachricht Fehler beim Hinzufügen von Mitgliedern Fehler beim Beitritt zur Gruppe - Datei kann nicht empfangen werden + Datei kann nicht heruntergeladen werden Der Absender hat die Dateiübertragung abgebrochen. - Fehler beim Empfangen der Datei + Fehler beim Herunterladen der Datei Fehler beim Erstellen der Adresse Kontakt besteht bereits Sie sind bereits mit %1$s verbunden. @@ -195,10 +195,10 @@ Bild Warten auf ein Bild - Es wird um den Empfang eines Bildes gebeten + Es wird um das Herunterladen eines Bildes gebeten Bild gesendet Warten auf ein Bild - Das Bild wird empfangen, sobald Ihr Kontakt online ist. Bitte warten oder schauen Sie später nochmal nach! + Das Bild wird heruntergeladen, sobald Ihr Kontakt online ist. Bitte warten oder schauen Sie später nochmal nach! Bild wurde im Fotoalbum gespeichert Datei @@ -206,7 +206,7 @@ Ihr Kontakt hat eine Datei gesendet, die größer ist als die derzeit unterstützte maximale Größe (%1$s). Die derzeit maximal unterstützte Dateigröße beträgt %1$s. Warte auf Datei - Die Datei wird empfangen, sobald Ihr Kontakt online ist. Bitte warten oder schauen Sie später noch mal nach! + Die Datei wird heruntergeladen, sobald Ihr Kontakt online ist. Bitte warten oder schauen Sie später noch mal nach! Datei gespeichert Datei nicht gefunden Fehler beim Speichern der Datei @@ -597,8 +597,8 @@ Starten Sie die App neu, um ein neues Chat-Profil zu erstellen. Sie dürfen die neueste Version Ihrer Chat-Datenbank NUR auf einem Gerät verwenden, andernfalls erhalten Sie möglicherweise keine Nachrichten mehr von einigen Ihrer Kontakte. Dateien und Medien löschen? - Es werden alle empfangenen und gesendeten Dateien und Medien gelöscht. Bilder mit niedriger Auflösung bleiben erhalten. Diese Aktion kann nicht rückgängig gemacht werden! - Keine empfangenen oder gesendeten Dateien + Es werden alle herunter- und hochgeladene Dateien und Medien gelöscht. Bilder mit niedriger Auflösung bleiben erhalten. Diese Aktion kann nicht rückgängig gemacht werden! + Keine herunter- oder hochgeladene Dateien %d Datei(en) mit einem Gesamtspeicherverbrauch von %s nie Älter als ein Tag @@ -1077,22 +1077,22 @@ Die Datenbank-Version ist neuer als die App, keine Abwärts-Migration für: %s Verberge: Migrationen: %s - Das Bild wird empfangen, sobald das Hochladen durch ihren Kontakt abgeschlossen ist. - Die Datei wird empfangen, sobald das Hochladen durch ihren Kontakt abgeschlossen ist. + Das Bild wird heruntergeladen, sobald das Hochladen durch ihren Kontakt abgeschlossen ist. + Die Datei wird heruntergeladen, sobald das Hochladen durch ihren Kontakt abgeschlossen ist. Chat-Profil löschen Profil löschen Verbergen des Profils aufheben Passwort für Profil Verbergen des Chat-Profils aufheben - Aufforderung zum Empfang des Videos + Aufforderung zum Herunterladen des Videos Es können nur 10 Videos zur gleichen Zeit versendet werden Zu viele Videos auf einmal! Video Video gesendet - Das Video wird empfangen, sobald Ihr Kontakt das Hochladen beendet hat. + Das Video wird heruntergeladen, sobald Ihr Kontakt das Hochladen beendet hat. Auf das Video warten Auf das Video warten - Das Video wird empfangen, wenn Ihr Kontakt online ist. Bitte warten oder überprüfen Sie es später! + Das Video wird heruntergeladen, wenn Ihr Kontakt online ist. Bitte warten oder überprüfen Sie es später! Ihre XFTP-Server Host Fehler beim Speichern der XFTP-Server @@ -1159,11 +1159,11 @@ Sowohl Sie als auch Ihr Kontakt können Anrufe tätigen. Nur Sie können Anrufe tätigen. Audio-/Video-Anrufe nicht erlauben. - Den Empfang der Datei beenden\? - Das Senden der Datei beenden\? - Der Empfang der Datei wird beendet. + Herunterladen der Datei beenden? + Das Hochladen der Datei beenden? + Das Herunterladen der Datei wird beendet. Das Senden der Datei wird beendet. - Datei beenden + Download beenden Die Datei wird von den Servern gelöscht. Widerrufen Datei widerrufen @@ -1906,7 +1906,7 @@ Privates Nachrichten-Routing 🚀 Schützen Sie Ihre IP-Adresse vor den Nachrichten-Relais , die Ihr Kontakt ausgewählt hat. \nAktivieren Sie es in den *Netzwerk & Server* Einstellungen. - Dateien sicher empfangen + Dateien sicher herunterladen Mit reduziertem Akkuverbrauch. Keine Information Debugging-Zustellung @@ -2191,7 +2191,7 @@ Fehler in der Server-Konfiguration. Für das Chat-Profil %s: Keine Medien- und Dateiserver. - Keine Server für den Empfang von Dateien. + Keine Server für das Herunterladen von Dateien. Keine Server für das Versenden von Dateien. Nicht ausgelieferte Nachrichten Die SimpleX-Adresse auf sozialen Medien teilen. @@ -2246,7 +2246,7 @@ Der Server-Betreiber wurde geändert. Das Server-Protokoll wurde geändert. Transparenz - Flux aktivieren + Für einen besseren Metadatenschutz Flux in den Netzwerk- und Servereinstellungen aktivieren. Dezentralisiertes Netzwerk Der zweite voreingestellte Netzwerk-Betreiber in der App! Verbesserte Chat-Navigation @@ -2447,4 +2447,9 @@ Moderatoren Mitglieder für Alle blockieren? Alle neuen Nachrichten dieser Mitglieder werden nicht angezeigt! + Durch die Nutzung von SimpleX Chat erklären Sie sich damit einverstanden:\n- nur legale Inhalte in öffentlichen Gruppen zu versenden.\n- andere Nutzer zu respektieren - kein Spam. + Datenschutzbestimmungen und Nutzungsbedingungen. + Akzeptieren + Server-Betreiber konfigurieren + Private Chats, Gruppen und Ihre Kontakte sind für Server-Betreiber nicht zugänglich. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index ad90b97d38..be71a7a927 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -7,9 +7,9 @@ Bueno para la batería. La aplicación comprueba si hay mensajes cada 10 minutos. Podrías perderte llamadas o mensajes urgentes.]]> Aceptar Copia de seguridad de los datos de la aplicación - un dia - un mes - una semana + 1 día + 1 mes + 1 semana Se permiten los mensajes temporales pero sólo si tu contacto también los permite. Añadir servidores mediante el escaneo de códigos QR. Añadir servidores predefinidos @@ -20,7 +20,7 @@ Permites a tus contactos enviar mensajes de voz. siempre La aplicación sólo puede recibir notificaciones cuando se está ejecutando. No se iniciará ningún servicio en segundo plano. - ICONO APLICACIÓN + ICONO DE LA APLICACIÓN La optimización de la batería está activa, desactivando el servicio en segundo plano y las solicitudes periódicas de nuevos mensajes. Puedes volver a activarlos en Configuración. El servicio está siempre en funcionamiento en segundo plano. Las notificaciones se muestran en cuanto haya mensajes nuevos. Se puede desactivar en Configuración – las notificaciones se seguirán mostrando mientras la app esté en funcionamiento.]]> @@ -29,7 +29,7 @@ y después: ¿Aceptar solicitud de conexión\? Aceptar incógnito - Se eliminarán todos los mensajes SOLO para tí. ¡No podrá deshacerse! + Se eliminarán todos los mensajes SOLO para tí. ¡No puede deshacerse! Añadir servidor ¿Acceder a los servidores a través del proxy SOCKS en el puerto %d\? El proxy debe iniciarse antes de activar esta opción. Todos tus contactos permanecerán conectados. @@ -49,7 +49,7 @@ Audio activado ID de mensaje erróneo Auto aceptar imágenes - Se eliminarán todos los chats y mensajes. ¡No podrá deshacerse! + Se eliminarán todos los chats y mensajes. ¡No puede deshacerse! Aceptar Se permiten mensajes temporales. Android Keystore se usará para almacenar de forma segura la frase de contraseña - permite que el servicio de notificación funcione. @@ -108,7 +108,7 @@ Llamada con cifrado de extremo a extremo cifrado de extremo a extremo mensaje duplicado - Herramientas desarrollo + Herramientas para desarrolladores Eliminar los archivos de todos los perfiles Activar ¡Base de datos cifrada! @@ -136,7 +136,7 @@ conectando (anunciado) conexión %1$d Conecta vía enlace / Código QR - El contacto y todos los mensajes serán eliminados. ¡No podrá deshacerse! + El contacto y todos los mensajes serán eliminados. ¡No puede deshacerse! Contacto verificado el contacto dispone de cifrado de extremo a extremo Desconectar @@ -158,7 +158,7 @@ Conectado Copiado en portapapeles Crea enlace de invitación de un uso. - Escanear código QR ]]> + Escanear código QR ]]> Eliminar Eliminar ¡El contacto aun no se ha conectado! @@ -238,8 +238,7 @@ Email Conectar Conectar mediante enlace - Base de Datos y -\nContraseña + Base de Datos y Contraseña Contribuye Core versión: v%s Eliminar imagen @@ -272,7 +271,7 @@ Cambiar rol Mediante perfil (predeterminado) o por conexión (BETA) cambiando de servidor… - Preferencias de Chat + Preferencias generales cancelado %s SimpleX está parado LLAMADAS @@ -312,12 +311,12 @@ Girar la cámara Invitación de grupo caducada La invitación al grupo ya no es válida, ha sido eliminada por el remitente. - El grupo será eliminado para tí. ¡No podrá deshacerse! + El grupo será eliminado para tí. ¡No puede deshacerse! Cómo usar la sintaxis markdown en modo incógnito mediante enlace de un solo uso Dirección de contacto SimpleX Error al guardar servidores SMP - 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. + Abrir el enlace en el navegador puede reducir la privacidad y seguridad de la conexión. Los enlaces de SimpleX que no son de confianza aparecerán en rojo. Error al actualizar la configuración de red Error al crear dirección Error al eliminar perfil @@ -343,7 +342,7 @@ Error al cambiar dirección Error al guardar archivo Error - De la Galería + De la galería Imagen Vídeo Si has recibido un enlace de invitación a SimpleX Chat puedes abrirlo en tu navegador: @@ -411,7 +410,7 @@ Ocultar pantalla de aplicaciones en aplicaciones recientes. Cifrar Ampliar la selección de roles - El grupo será eliminado para todos los miembros. ¡No podrá deshacerse! + El grupo será eliminado para todos los miembros. ¡No puede deshacerse! Activar TCP keep-alive activado para tí error @@ -428,7 +427,7 @@ ayuda Compartir enlace Cómo funciona - El mensaje será eliminado. ¡No podrá deshacerse! + El mensaje será eliminado. ¡No puede deshacerse! El modo incógnito protege tu privacidad creando un perfil aleatorio por cada contacto. Da permiso en el siguiente diálogo para recibir notificaciones instantáneas.]]> Instalar terminal de SimpleX Chat @@ -490,7 +489,7 @@ eliminado por el moderador invitación a conectarse ¡Las notificaciones instantáneas están desactivadas! - mensaje nuevo + nuevo mensaje Nueva solicitud de contacto Inicie sesión con sus credenciales Error en la entrega del mensaje @@ -519,7 +518,7 @@ OK (sólo almacenado por miembros del grupo) Ayuda sintaxis markdown - Servidores y Redes + Servidores y Red Se usarán hosts .onion si están disponibles. cursiva Llamada audio entrante @@ -564,7 +563,7 @@ Salir ¿Salir del grupo\? propietario - El miembro será expulsado del grupo. ¡No podrá deshacerse! + El miembro será expulsado del grupo. ¡No puede deshacerse! Sólo los propietarios del grupo pueden activar los mensajes de voz. Más Marcar como verificado @@ -578,7 +577,7 @@ Error al restaurar base de datos Seleccionar contactos Guardar perfil de grupo - Restablecer colores + Reiniciar colores Sólo tú puedes enviar mensajes temporales. Sólo tu contacto puede enviar mensajes temporales. No se permiten mensajes de voz. @@ -596,7 +595,7 @@ llamada rechazada secreto Abrir SimpleX Chat para aceptar llamada - Restablecer valores predetarminados + Reiniciar a valores predetarminados Pendiente Notificaciones periódicas Guarda la contraseña de forma segura, NO podrás cambiarla si la pierdes. @@ -605,7 +604,7 @@ Notificaciones privadas imagen del perfil No se permiten mensajes de voz. - Proteger la pantalla + Proteger pantalla de la aplicación repositorio GitHub .]]> Grabar mensaje de voz ha expulsado a %1$s @@ -684,7 +683,7 @@ Expulsar Expulsar miembro Enviar mensaje directo - Restablecer + Reiniciar Pegar Código de seguridad Escanea el código de seguridad desde la aplicación de tu contacto. @@ -744,14 +743,14 @@ Altavoz desactivado Inciar chat nuevo Para exportar, importar o eliminar la base de datos debes parar SimpleX. Mientra tanto no podrás enviar ni recibir mensajes. - Gracias por instalar SimpleX Chat! - Para proteger tu privacidad, SimpleX dispone de identificadores para las colas de mensajes, independientes para cada uno de tus contactos. + ¡Gracias por instalar SimpleX Chat! + Para proteger tu privacidad, SimpleX usa identificadores distintos para cada uno de tus contactos. Para proteger tu información, activa el Bloqueo SimpleX. \nSe te pedirá que completes la autenticación antes de activar esta función. Para actualizar la configuración el cliente se reconectará a todos los servidores. ¿Usar servidores SimpleX Chat\? Enlace de grupo SimpleX - Invitación única SimpleX + Invitación SimpleX de un uso Enlaces SimpleX El servidor requiere autorización para crear colas, comprueba la contraseña Para recibir notificaciones, introduce la contraseña de la base de datos @@ -782,7 +781,7 @@ %s no está verificado Probar servidor Probar servidores - Estrella en GitHub + Califica en GitHub Servidores para conexiones nuevas en tu perfil ¿Usar conexión directa a Internet\? El perfil sólo se comparte con tus contactos. @@ -871,7 +870,7 @@ Mediante navegador mediante %1$s Servicio SimpleX Chat - ¡Bienvenido %1$s ! + ¡Bienvenido %1$s! has sido invitado al grupo Esperando archivo Esperando imagen @@ -936,7 +935,7 @@ Mis perfiles Mi dirección SimpleX Tu servidor - Dirección del servidor + Dirección de tu servidor Tu perfil actual Tu perfil es almacenado en tu dispositivo y solamente se comparte con tus contactos. Los servidores SimpleX no pueden ver tu perfil. Sistema @@ -993,10 +992,10 @@ Confirmar actualizaciones de la bases de datos la versión de la base de datos es más reciente que la aplicación, pero no hay migración hacia versión anterior para: %s EXPERIMENTAL - IDs de la base de datos y opciónes de aislamiento de transporte. + IDs de la base de datos y opciones de aislamiento de transporte. El archivo se recibirá cuando el contacto termine de subirlo. La imagen se recibirá cuando el contacto termine de subirla. - Mostrar opciones de desarrollador + Mostrar opciones para desarrolladores Ocultar: Mostrar: Eliminar perfil @@ -1107,7 +1106,7 @@ Abriendo base de datos… Error al introducir dirección Guía de Usuario.]]> - Enlace de un uso + Enlace de un solo uso Dirección SimpleX Cuando alguien solicite conectarse podrás aceptar o rechazar su solicitud. Compartir dirección @@ -1144,7 +1143,7 @@ Mensaje enviado Dejar de compartir ¿Dejar de compartir la dirección\? - COLORES DEL INTERFAZ + COLORES DE LA INTERFAZ Puedes crearla más tarde ¿Compartir la dirección con los contactos\? Compartir con contactos @@ -1234,7 +1233,7 @@ APLICACIÓN Reiniciar Cerrar - Las notificaciones dejarán de funcionar hasta que reinicies la aplicación + Las notificaciones dejarán de funcionar hasta que vuelvas a iniciar la aplicación Desactivado Error al cancelar cambio de dirección Sin chats filtrados @@ -1319,7 +1318,7 @@ ¿Permitir confirmaciones para grupos\? Las confirmaciones están deshabilitadas para los grupos %d Las confirmaciones están activadas para %d grupos - Grupos pequeños (max. 20) + Grupos pequeños (máx. 20) Activar para todos los grupos %s: %s desactivado @@ -1368,7 +1367,7 @@ Recuerda: los servidores están conectados mediante proxy SOCKS, pero las llamadas y las previsualizaciones de enlaces usan conexión directa.]]> Cifrar archivos locales Nueva aplicación para ordenador! - 6 idiomas nuevos para el interfaz + 6 nuevos idiomas para la interfaz Cifrado de los nuevos archivos locales (excepto vídeos). Envía un mensaje para conectar Descubre y únete a grupos @@ -1518,8 +1517,8 @@ No se envía el historial a los miembros nuevos. O muestra el código QR Hasta 100 últimos mensajes son enviados a los miembros nuevos. - El código QR escaneado no es un enlace SimpleX. - El texto pegado no es un enlace SimpleX. + El código QR escaneado no es un enlace de SimpleX. + El texto pegado no es un enlace de SimpleX. Permitir acceso a la cámara Podrás ver el enlace de invitación en detalles de conexión. ¿Guardar invitación no usada? @@ -1543,7 +1542,7 @@ Error al crear mensaje Error al eliminar notas privadas ¿Eliminar notas privadas? - Opciones desarrollador + Opciones para desarrolladores ha bloqueado a %s ha desbloqueado a %s has bloqueado a %s @@ -1571,7 +1570,7 @@ El ordenador está inactivo El ordenador está ocupado Error crítico - Todos los mensajes serán borrados. ¡No podrá deshacerse! + Todos los mensajes serán eliminados. ¡No puede deshacerse! ¿Iniciar chat? Mensaje de bienvenida demasiado largo Tiempo de espera para conectar con el ordenador agotado @@ -1684,8 +1683,8 @@ Error al guardar ajustes El archivo exportado no existe Para continuar, SimpleX debe estar parado. - cifrado de extremo a extremo con secreto perfecto hacía adelante, repudio y recuperación tras ataque.]]> - cifrado de extremo a extremo resistente a tecnología cuántica con secreto perfecto hacía adelante, repudio y recuperación tras ataque.]]> + cifrado de extremo a extremo con secreto perfecto hacia adelante, repudio y recuperación tras ataque.]]> + cifrado de extremo a extremo resistente a tecnología cuántica con secreto perfecto hacia adelante, repudio y recuperación tras ataque.]]> Migrar aquí Migrar a otro dispositivo Migrar a otro dispositivo mediante código QR. @@ -1712,7 +1711,7 @@ Enlaces SimpleX no permitidos Mensajes de voz no permitidos Enlaces SimpleX - Los miembros pueden enviar enlaces SimpleX. + Los miembros pueden enviar enlaces de SimpleX. Enlaces SimpleX no permitidos. propietarios Móvil @@ -1808,7 +1807,7 @@ Respuesta recibida Mosaico Quitar imagen - Restablecer color + Reiniciar color Escala Respuesta enviada Establecer tema predefinido @@ -1823,7 +1822,7 @@ información cola del servidor: %1$s \n \núltimo mensaje recibido: %2$s - Restablecer al tema de la aplicación + Reiniciar al tema de la aplicación Enrutamiento privado de mensajes 🚀 Recibe archivos de forma segura Mejora del envío de mensajes @@ -1835,14 +1834,13 @@ Nuevos temas de chat Información cola de mensajes ninguno - Protege tu dirección IP de los servidores de retransmisión elegidos por tus contactos. -\nActívalo en ajustes de *Servidores y Redes*. - Restablecer al tema del usuario + Protege tu dirección IP de los servidores elegidos por tus contactos.\nActívalo en *Servidores y Red*. + Reiniciar al tema del usuario Error al inicializar WebView. Actualiza tu sistema a la última versión. Por favor, ponte en contacto con los desarrolladores. \nError: %s Interfaz en persa Clave incorrecta o dirección del bloque del archivo desconocida. Es probable que el archivo se haya eliminado. - Archivo no encontrado, probablemente haya sido borrado o cancelado. + Archivo no encontrado, probablemente haya sido eliminado o cancelado. Error del servidor de archivos: %1$s Error de archivo Error en archivo temporal @@ -1856,7 +1854,7 @@ Este enlace ha sido usado en otro dispositivo móvil, por favor crea un enlace nuevo en el ordenador. No se puede enviar el mensaje Las preferencias seleccionadas no permiten este mensaje. - Info servidores + Estadísticas servidores Archivos Mostrando Suscritas @@ -1903,9 +1901,9 @@ La dirección del servidor es incompatible con la configuración de red: %1$s. La versión del servidor es incompatible con tu aplicación: %1$s. Tamaño de la fuente - Error al restablecer las estadísticas - Restablecer - Las estadísticas de los servidores serán restablecidas. ¡No podrá deshacerse! + Error al reiniciar las estadísticas + Reiniciar + Las estadísticas de los servidores serán restablecidas. ¡No puede deshacerse! Descargado Servidor SMP Aún no hay conexión directa, el mensaje es reenviado por el administrador. @@ -1944,8 +1942,8 @@ ¿Reconectar servidores? Reconectar con el servidor para forzar la entrega de mensajes. Se usa tráfico adicional. Reconectar todos los servidores para forzar la entrega de mensajes. Usa tráfico adicional. - Restablecer todas las estadísticas - ¿Restablecer todas las estadísticas? + Reiniciar estadísticas + ¿Reiniciar todas las estadísticas? Mensajes enviados Total enviados Archivos descargados @@ -1965,7 +1963,7 @@ Tamaño Conexiones activas Iniciado el %s. - Iniciado el %s \nLa información es privada en tu dispositivo. + Iniciado el %s \nLos datos son privados en tu dispositivo. Bloques eliminados Bloques descargados Bloques subidos @@ -1999,7 +1997,7 @@ Configuración ¿Confirmas la eliminación del contacto? ¡Contacto eliminado! - El contacto será eliminado. ¡No podrá deshacerse! + El contacto será eliminado. ¡No puede deshacerse! ¡Conversación eliminada! Elimina sin notificar Eliminar sólo la conversación @@ -2020,7 +2018,7 @@ Por favor, pide a tu contacto que active las llamadas. Enviar mensaje para activar llamadas. Elimina hasta 20 mensajes a la vez. - Barra de herramientas accesible + Barra de menú accesible Archiva contactos para charlar más tarde. Puedes guardar el archivo exportado. Fuerte @@ -2071,7 +2069,7 @@ %1$s mensajes no enviados Descargar Reenviando %1$s mensajes - Los mensajes han sido borrados después de seleccionarlos. + Los mensajes han sido eliminados después de seleccionarlos. ¡Nada para reenviar! Guardando %1$s mensajes No uses credenciales con proxy. @@ -2085,7 +2083,7 @@ Tus credenciales podrían ser enviadas sin cifrar. ¿Eliminar archivo? El archivo de bases de datos subido será eliminado permanentemente de los servidores. - Los mensajes serán eliminados. ¡No podrá deshacerse! + Los mensajes serán eliminados. ¡No puede deshacerse! Error al cambiar perfil Selecciona perfil de chat Perfil a compartir @@ -2110,7 +2108,7 @@ Protocolos de SimpleX auditados por Trail of Bits. Intercambia audio y video durante la llamada. Seguridad mejorada ✅ - Borra o modera hasta 200 mensajes a la vez. + Elimina o modera hasta 200 mensajes a la vez. Cambia el perfil de chat para invitaciones de un solo uso. Error al guardar servidores Error en la configuración del servidor. @@ -2121,7 +2119,7 @@ Seguridad de conexión Compartir enlace de un uso con un amigo Comparte tu dirección SimpleX en redes sociales. - Configurar dirección + Ajustes de dirección Crear enlace de un uso Para redes sociales ¿Dirección SimpleX o enlace de un uso? @@ -2153,11 +2151,11 @@ Condiciones aceptadas Servidores de archivos y multimedia añadidos Servidores de mensajes añadidos - ¿Dirección o enlace de un uso? + ¿Dirección o enlace de un solo uso? Las condiciones serán aceptadas automáticamente para los operadores habilitados el: %s. Continuar El texto con las condiciones actuales no se ha podido cargar, puedes revisar las condiciones en el siguiente enlace: - Habilita Flux + Activa Flux en Servidores y Red para mejorar la privacidad de los metadatos. Error al aceptar las condiciones Error al actualizar el servidor para mejorar la privacidad de los metadatos. @@ -2169,8 +2167,8 @@ Servidor del operador O para compartir en privado Selecciona los operadores de red a utilizar - Campartir dirección públicamente - Compartir los enlaces de un uso y las direcciones SimpleX es seguro a través de cualquier medio. + Compartir dirección públicamente + Compartir enlaces de un solo uso y direcciones SimpleX es seguro a través de cualquier medio. Actualizar Sitio web Tus servidores @@ -2187,7 +2185,7 @@ solamente con un contacto - comparte en persona o mediante cualquier aplicación de mensajería.]]> Puedes añadir un nombre a la conexión para recordar a quién corresponde. La aplicación protege tu privacidad mediante el uso de diferentes operadores en cada conversación. - Puedes configurar los operadores desde Servidores y Redes. + Puedes configurar los operadores desde los ajustes de Servidores y Red. %s.]]> %s.]]> %s.]]> @@ -2204,7 +2202,7 @@ Por ejemplo, si tu contacto recibe a través de un servidor de SimpleX Chat, tu aplicación enviará a través de un servidor de Flux. Pulsa Crear dirección SimpleX en el menú para crearla más tarde. La conexión ha alcanzado el límite de mensajes no entregados. es posible que tu contacto esté desconectado. - El mensaje ha sido borrado o aún no se ha recibido. + El mensaje ha sido eliminado o aún no se ha recibido. Móvil remoto O importa desde un archivo Mensajes directos entre miembros de este chat no permitidos. @@ -2217,13 +2215,13 @@ Invitar al chat Añadir amigos Añadir miembros del equipo - El chat será eliminado para todos los miembros. ¡No podrá deshacerse! + El chat será eliminado para todos los miembros. ¡No puede deshacerse! Eliminar chat ¿Eliminar chat? Salir del chat - El chat será eliminado para tí. ¡No podrá deshacerse! + El chat será eliminado para tí. ¡No puede deshacerse! Sólo los propietarios del chat pueden cambiar las preferencias. - El miembro será eliminado del chat. ¡No podrá deshacerse! + El miembro será eliminado del chat. ¡No puede deshacerse! El rol cambiará a %s. Todos serán notificados. Dejarás de recibir mensajes de este chat. El historial del chat se conserva. Cómo ayuda a la privacidad @@ -2268,7 +2266,7 @@ Grupos Abrir con %s Añadir a la lista - Todos los chats serán quitados de la lista %s y esta será eliminada + Todos los chats se quitarán de la lista %s y esta será eliminada Crear lista Eliminar ¿Eliminar lista? @@ -2286,8 +2284,8 @@ informe archivado por %s ¿Archivar informe? El informe será archivado para ti. - Informe - Borrar informe + Informar + Eliminar informe 1 informe Informes Informar de spam: sólo los moderadores del grupo lo verán. @@ -2303,7 +2301,7 @@ Archivo bloqueado por el operador del servidor:\n%1$s Archivar Archivar informe - Informes de miembro + Informes de miembros %d informes Informar del perfil de un miembro: sólo los moderadores del grupo lo verán. Otro motivo @@ -2325,9 +2323,9 @@ Desactivar ¿Modificar la eliminación automática de mensajes? Elimina los mensajes del dispositivo - Los mensajes de esta conversación nunca se borran. - Todos los mensajes previos al período seleccionado serán eliminados del chat. ¡No podrá deshacerse! - Un año + Los mensajes de esta conversación nunca se eliminan. + Todos los mensajes previos al período seleccionado serán eliminados del chat. ¡No puede deshacerse! + 1 año predefinido (%s) Puerto TCP para mensajes Se usa el puerto TCP %1$s cuando no se ha especificado otro. @@ -2365,13 +2363,18 @@ ¿Desbloquear miembros para todos? ¡Todos los mensajes nuevos de estos miembros estarán ocultos! ¿Bloquear miembros para todos? - Los miembros serán expulsados del chat. ¡No podrá deshacerse! + Los miembros serán expulsados del chat. ¡No puede deshacerse! Condiciones actualizadas moderadores - Los miembros serán expulsados del grupo. ¡No podrá deshacerse! + Los miembros serán expulsados del grupo. ¡No puede deshacerse! pendiente de aprobación pendiente Error al leer la frase de contraseña de la base de datos La frase de contraseña no se ha podido leer en Keystore. Puede deberse a alguna actualización del sistema incompatible con la aplicación. Si no es así, por favor, ponte en contacto con los desarrolladores. La frase de contraseña no se ha podido leer en Keystore. Por favor, introdúcela manualmente. Puede deberse a alguna actualización del sistema incompatible con la aplicación. Si no es así, por favor, ponte en contacto con los desarrolladores. + Aceptar + Política de privacidad y condiciones de uso. + Los chats privados, los grupos y tus contactos no son accesibles para los operadores de servidores. + Al usar SimpleX Chat, aceptas:\n- enviar únicamente contenido legal en los grupos públicos.\n- respetar a los demás usuarios - spam prohibido. + Configurar operadores de servidores diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml index dd5ce1dc6a..43fcb2f5f7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml @@ -2325,4 +2325,23 @@ Tous les signalements seront archivés pour vous. Archiver tous les signalements ? Archiver %d signalements ? + Modifier la suppression automatique des messages ? + Supprimer les messages de discussion de votre appareil. + par défaut (%s) + Bloquer ces membres pour tous ? + Désactiver la suppression automatique des messages ? + Désactiver la suppression des messages + Ne manquez pas les messages importants. + Recevoir une notification en cas de mention. + Vie privée et sécurité renforcées + Suppression plus rapide des groupes. + Envoi plus rapide des messages. + Pour moi + Archiver les rapports + Pour tous les modérateurs + Les membres peuvent signaler des messages aux modérateurs. + Groupes plus performants + Erreur lors de la lecture de la phase secrète de la base de données + Aider les administrateurs à modérer leurs groupes. + Tous les nouveaux messages de ces membres seront cachés ! diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index 4449fbed58..5ab5dd8a63 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -1,6 +1,6 @@ - %1$d üzenet visszafejtése sikertelen. + Nem sikerült visszafejteni %1$d üzenetet. %1$d üzenet kihagyva. %1$d üzenet kihagyva %1$s TAG @@ -13,7 +13,7 @@ Megszakítja a cím módosítását? Megszakítás 30 másodperc - Egyszer használható meghívási hivatkozás + Egyszer használható meghívó %1$s szeretne kapcsolatba lépni Önnel a következőn keresztül: A SimpleX Chat névjegye 1 nap @@ -26,7 +26,7 @@ Elfogadás gombra fent, majd: Elfogadás inkognitóban - Elfogadja a kapcsolódási kérést? + Elfogadja a meghívási kérést? Elfogadás Elfogadás Cím hozzáadása a profilhoz, hogy a partnerei megoszthassák másokkal. A profilfrissítés el lesz küldve az Ön partnerei számára. @@ -90,7 +90,7 @@ titkosítás elfogadása… Nem lehet meghívni a partnert! téves üzenet ID - Kapcsolatkérések automatikus elfogadása + Meghívási kérések automatikus elfogadása Megjegyzés: NEM fogja tudni helyreállítani, vagy módosítani a jelmondatot abban az esetben, ha elveszíti.]]> hívás… További másodlagos szín @@ -128,7 +128,7 @@ Az összes alkalmazásadat törölve. Legjobb akkumulátoridő. Csak akkor kap értesítéseket, amikor az alkalmazás meg van nyitva. (NINCS háttérszolgáltatás.)]]> Megjelenés - Az akkumulátor-optimalizálás aktív, ez kikapcsolja a háttérszolgáltatást és az új üzenetek időszakos lekérdezését. A beállításokban újraengedélyezheti. + Az akkumulátor-optimalizálás aktív, ez kikapcsolja a háttérszolgáltatást és az új üzenetek időszakos lekérdezését. Ezt a beállításokban újraengedélyezheti. Letiltja a tagot? %1$s hívása befejeződött Jó akkumulátoridő. Az alkalmazás 10 percenként ellenőrzi az új üzeneteket. Előfordulhat, hogy hívásokról, vagy a sürgős üzenetekről marad le.]]> @@ -199,7 +199,7 @@ Csoport létrehozása véletlenszerű profillal. A partner és az összes üzenet törölve lesz – ez a művelet nem vonható vissza! A partnerei törlésre jelölhetnek üzeneteket; Ön majd meg tudja nézni azokat. - Kapcsolódik az egyszer használható meghívási hivatkozással? + Kapcsolódik az egyszer használható meghívóval? Kapcsolódás egy hivatkozáson vagy QR-kódon keresztül Kapcsolódási hiba (AUTH) Csak név @@ -213,7 +213,7 @@ Partner ellenőrizve Kapcsolódik saját magához? Kimásolva a vágólapra - Kapcsolatkérés elküldve! + Meghívási kérés elküldve! Kapcsolódás a számítógéphez Kapcsolat Helyesbíti a nevet a következőre: %s? @@ -234,13 +234,13 @@ Kapcsolódási hiba A partnere még nem kapcsolódott! - kapcsolódás könyvtár szolgáltatáshoz (BÉTA)!\n- kézbesítési jelentések (legfeljebb 20 tagig).\n- gyorsabb és stabilabb. - Hozzájárulás + Közreműködés kapcsolódás (bemutatkozó meghívó) SimpleX-cím létrehozása törölt partner Törli a tag üzenetét? A csevegés fut - Egyszer használható meghívási hivatkozás létrehozása + Egyszer használható meghívó létrehozása Törlés Új üzenetek ellenőrzése 10 percenként, legfeljebb 1 percen keresztül Adatbázis törlése @@ -407,7 +407,7 @@ Letiltja a kézbesítési jelentéseket a csoportok számára? nap %d nap - Duplikált megjelenített név! + Duplikált megjelenítendő név! Letiltás (felülírások megtartásával) Adatbázis fejlesztése %d üzenet letiltva @@ -456,58 +456,58 @@ Engedélyezés az összes tag számára A kézbesítési jelentések le vannak tiltva! Kibontás - Hiba az üzenet küldésekor + Hiba történt az üzenet elküldésekor Adja meg a jelkódot Mindenkinél - Hiba a titkosítás újraegyeztetésekor - Hiba az adatbázis titkosításakor - Hiba a csoport törlésekor + Hiba történt a titkosítás újraegyeztetésekor + Hiba történt az adatbázis titkosításakor + Hiba történt a csoport törlésekor Kilépés mentés nélkül A tárolt fájlok- és a médiatartalmak titkosítása - Hiba a cím beállításakor + Hiba történt a cím beállításakor A csoportmeghívó lejárt - Hiba az ICE-kiszolgálók mentésekor + Hiba történt az ICE-kiszolgálók mentésekor Hiba Hiba - Hiba az XFTP-kiszolgálók betöltésekor - Hiba az SMP-kiszolgálók betöltésekor - Hiba a hálózat konfigurációjának frissítésekor + Hiba történt az XFTP-kiszolgálók betöltésekor + Hiba történt az SMP-kiszolgálók betöltésekor + Hiba történt a hálózat konfigurációjának frissítésekor TCP életben tartása Kamera váltás Üdvözlöm!\nCsatlakozzon hozzám a SimpleX Chaten keresztül: %s A megjelenített név nem tartalmazhat szóközöket. Csoport Adja meg az üdvözlőüzenetet… (nem kötelező) - Hiba a csevegési adatbázis exportálásakor - Hiba a fájl mentésekor + Hiba történt a csevegési adatbázis exportálásakor + Hiba történt a fájl mentésekor Helyi fájlok titkosítása titkosítás elfogadva %s számára %d üzenet megjelölve törlésre a titkosítás újraegyeztetése engedélyezve van Önmegsemmisítés engedélyezése Olvasatlan és kedvenc csevegésekre való szűrés. - A csevegések betöltése sikertelen + Nem sikerült betölteni a csevegéseket A csoport már létezik! Francia kezelőfelület Csoporthivatkozások Végre, megvannak! 🚀 - Hiba a csevegés elindításakor + Hiba történt a csevegés elindításakor A csoport profilja a tagok eszközein tárolódik, nem a kiszolgálókon. Adja meg a jelmondatot… - Hiba a felhasználói adatvédelem frissítésekor + Hiba történt a felhasználói adatvédelem frissítésekor Titkosít Csoport nem található! - Hiba az SMP-kiszolgálók mentésekor + Hiba történt az SMP-kiszolgálók mentésekor Visszafejlesztés és a csevegés megnyitása A csoport inaktív Gyors és nem kell várni, amíg a feladó online lesz! - Hiba a csoporthoz való csatlakozáskor + Hiba történt a csoporthoz való csatlakozáskor Kedvenc Csoport moderálása Fájl Csoporthivatkozás a titkosítás újraegyeztetése szükséges %s számára - Hiba a profilváltáskor! + Hiba történt a profilváltáskor! Kísérleti funkciók Engedélyezés (felülírások megtartásával) Adja meg a helyes jelmondatot. @@ -516,7 +516,7 @@ A zárolási képernyőn megjelenő hívások engedélyezése a Beállításokban. titkosítás elfogadva Engedélyezi a kézbesítési jelentéseket? - Hiba a csoportprofil mentésekor + Hiba történt a csoportprofil mentésekor hiba A fájl törölve lesz a kiszolgálókról. Akkor is, ha le van tiltva a beszélgetésben. @@ -526,52 +526,52 @@ Teljesen decentralizált – csak a tagok számára látható. Fájl: %s Hívás befejezése - Hiba a csoporthivatkozás törlésekor + Hiba történt a csoporthivatkozás törlésekor Fájl elmentve Kapcsolat javítása? Fájlok és médiatartalmak KONZOLHOZ Nem sikerült a titkosítást újraegyeztetni. - Hiba a felhasználó-profil törlésekor + Hiba történt a felhasználó-profil törlésekor Csoporttag általi javítás nem támogatott Adja meg az üdvözlőüzenetet… Titkosított adatbázis Adja meg a jelszót a keresőben A fájl akkor érkezik meg, amikor a küldője befejezte annak feltöltését. Fájl letöltése - A csevegés betöltése sikertelen + Nem sikerült betölteni a csevegést Adja meg a kiszolgálót kézzel A fájl akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később! - Hiba a csoporthivatkozás létrehozásakor + Hiba történt a csoporthivatkozás létrehozásakor A galériából Engedélyezés (csoport felülírások megtartásával) - Hiba a partner törlésekor + Hiba történt a partner törlésekor A tagok véglegesen törölhetik az elküldött üzeneteiket. (24 óra) - Hiba a szerepkör módosításakor + Hiba történt a szerepkör módosításakor Javítás A tagok küldhetnek eltűnő üzeneteket. Kapcsolat javítása - Hiba a profil létrehozásakor! - Hiba a tag(ok) hozzáadásakor + Hiba történt a profil létrehozásakor! + Hiba történt a tag(ok) hozzáadásakor Fájl A tagok küldhetnek fájlokat és médiatartalmakat. Törlés ennyi idő után - Hiba a beállítás módosításakor - Hiba a csoporthivatkozás frissítésekor + Hiba történt a beállítás módosításakor + Hiba történt a csoporthivatkozás frissítésekor a csoport törölve csoportprofil frissítve - Hiba a függőben lévő meghívó törlésekor - Hiba a csevegési adatbázis importálásakor - Hiba a kézbesítési jelentések engedélyezésekor! - Hiba az XFTP-kiszolgálók mentésekor + Hiba történt a függőben lévő meghívó törlésekor + Hiba történt a csevegési adatbázis importálásakor + Hiba történt a kézbesítési jelentések engedélyezésekor! + Hiba történt az XFTP-kiszolgálók mentésekor A tagok küldhetnek egymásnak közvetlen üzeneteket. - Hiba a tag eltávolításakor + Hiba történt a tag eltávolításakor befejeződött A csoport üdvözlőüzenete Adja meg a csoport nevét: - Hiba a meghívó küldésekor + Hiba történt a meghívó elküldésekor Adjon meg egy nevet: - Hiba a felhasználó jelszavának mentésekor + Hiba történt a felhasználó jelszavának mentésekor Téma exportálása Adja meg ennek az eszköznek a nevét… Hiba @@ -580,10 +580,10 @@ súgó Önmegsemmisítő-jelkód engedélyezése KÍSÉRLETI - Hiba a cím módosításának megszakításakor - Hiba a fájl fogadásakor + Hiba történt a cím módosításának megszakításakor + Hiba történt a fájl fogadásakor titkosítás rendben - Hiba a kapcsolatkérés törlésekor + Hiba történt a meghívási kérés törlésekor Engedélyezi a kézbesítési jelentéseket a csoportok számára? Partner általi javítás nem támogatott Fájl nem található @@ -592,23 +592,23 @@ Adatbázis exportálása Teljes név: Tovább csökkentett akkumulátor-használat - Hiba a csevegés megállításakor + Hiba történt a csevegés megállításakor titkosítás rendben %s számára A csoport törölve lesz az összes tag számára – ez a művelet nem vonható vissza! Titkosítás javítása az adatmentések helyreállítása után. - Hiba a csevegési adatbázis törlésekor + Hiba történt a csevegési adatbázis törlésekor Teljes hivatkozás - Hiba a cím módosításakor + Hiba történt a cím módosításakor A tagok küldhetnek hangüzeneteket. Csoportbeállítások Hiba: %s Eltűnő üzenetek SimpleX-zár bekapcsolása - Hiba a kapcsolat szinkronizálásakor - Hiba a cím létrehozásakor + Hiba történt a kapcsolat szinkronizálásakor + Hiba történt a cím létrehozásakor engedélyezve - Hiba a részletek betöltésekor - Hiba történt a kapcsolatkérés elfogadásakor + Hiba történt a részletek betöltésekor + Hiba történt a meghívási kérés elfogadásakor a titkosítás újraegyeztetése engedélyezve van %s számára a titkosítás újraegyeztetése szükséges Rejtett csevegési profilok @@ -621,7 +621,7 @@ Hogyan használja a saját kiszolgálóit Csevegési üzenetek gyorsabb megtalálása Téma importálása - Hiba a téma importálásakor + Hiba történt a téma importálásakor Partner nevének és az üzenet tartalmának elrejtése Nem kompatibilis adatbázis-verzió Hogyan működik a SimpleX @@ -659,7 +659,7 @@ A fájlok- és a médiatartalmak küldése le van tiltva. Hogyan működik Elrejtés: - Hiba a partnerrel történő kapcsolat létrehozásában + Hiba történt a partnerrel történő kapcsolat létrehozásában ICE-kiszolgálók (soronként egy) beolvashatja a QR-kódot a videohívásban, vagy a partnere megoszthat egy meghívási hivatkozást.]]> Ha az alkalmazás megnyitásakor megadja ezt a jelkódot, az összes alkalmazásadat véglegesen el lesz távolítva! @@ -737,7 +737,7 @@ Az onion-kiszolgálók nem lesznek használva. perc Tudjon meg többet - Új kapcsolatkérés + Új meghívási kérés Csatlakozás a csoporthoz Társított számítógép beállítások meghíva az Ön csoporthivatkozásán keresztül @@ -775,11 +775,11 @@ érvénytelen adat Győződjön meg arról, hogy a megadott WebRTC ICE-kiszolgálók címei megfelelő formátumúak, soronként elkülönítettek, és nincsenek duplikálva. Csak a csoport tulajdonosai engedélyezhetik a fájlok- és a médiatartalmak küldését. - A fájl betöltése… + Fájl betöltése… Nincs hozzáadandó partner Üzenetvázlat - meghívta egy partnerét - Egyszer használható meghívási hivatkozás + Függőben lévő meghívó + Egyszer használható meghívó Értesítések Egyszerre csak 10 kép küldhető el ajánlotta: %s, ekkor: %2s @@ -810,7 +810,7 @@ %s ajánlotta Csoport elhagyása %s összes üzenete meg fog jelenni! - Ez akkor fordulhat elő, ha:\n1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak.\n2. Az üzenet visszafejtése sikertelen volt, mert Ön, vagy a partnere régebbi adatbázis biztonsági mentést használt.\n3. A kapcsolat sérült. + Ez akkor fordulhat elő, ha:\n1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak.\n2. Nem sikerült visszafejteni az üzenetet, mert Ön, vagy a partnere régebbi adatbázis biztonsági mentést használt.\n3. A kapcsolat sérült. megfigyelő inkognitó a csoporthivatkozáson keresztül Onion-kiszolgálók használata, ha azok rendelkezésre állnak. @@ -820,9 +820,9 @@ csatlakozás mint %s Nincs csevegés kijelölve Csak helyi profiladatok - inkognitó egy egyszer használható meghívási hivatkozáson keresztül + inkognitó egy egyszer használható meghívón keresztül Moderálva: %s - Egyszer használható meghívási hivatkozás + Egyszer használható meghívó Érvénytelen név! Beszélgessünk a SimpleX Chatben Moderálva @@ -871,7 +871,7 @@ elutasított hívás Időszakos fogadott, tiltott - Megismétli a kapcsolódási kérést? + Megismétli a meghívási kérést? Véglegesen csak Ön törölhet üzeneteket (partnere csak törlésre jelölheti meg őket ). (24 óra) Szerepkör SimpleX kapcsolattartási cím @@ -906,8 +906,8 @@ Csak Ön tud hívásokat indítani. Biztonságos sorba állítás Értékelje az alkalmazást - Egyszer használható meghívási hivatkozás megosztása - Hiba az adatbázis visszaállításakor + Egyszer használható meghívó megosztása + Hiba történt az adatbázis visszaállításakor %s és %s Ön engedélyezi Csökkentett akkumulátor-használat @@ -926,7 +926,7 @@ (beolvasás, vagy beillesztés a vágólapról) Várakozás a videóra Válasz - Ez az Ön egyszer használható meghívási hivatkozása! + Ez az Ön egyszer használható meghívója! SimpleX Chat hívások Új inkognitóprofil használata Frissítse az alkalmazást, és lépjen kapcsolatba a fejlesztőkkel. @@ -958,7 +958,7 @@ Adatbázismentés visszaállítása Visszavonás Kérje meg a partnerét, hogy engedélyezze a hangüzenetek küldését. - Ön egy egyszer használható meghívási hivatkozást osztott meg + Ön egy egyszer használható meghívót osztott meg A hivatkozás megnyitása a böngészőben gyengítheti az adatvédelmet és a biztonságot. A megbízhatatlan SimpleX-hivatkozások pirossal vannak kiemelve. Saját ICE-kiszolgálók Kapcsolat létrehozása @@ -1070,7 +1070,7 @@ tulajdonos Bekapcsolás %s, %s és %s kapcsolódott - Egyszer használható SimpleX-meghívási hivatkozás + Egyszer használható SimpleX-meghívó Hívások nem sikerült elküldeni KEZELŐFELÜLET SZÍNEI @@ -1096,12 +1096,12 @@ Adatbázis-jelmondat beállítása Biztonsági kód megtekintése Feloldja a tag letiltását? - A küldő törölhette a kapcsolatkérést. + A küldője törölhette a meghívási kérést. Érvénytelen adatbázis-jelmondat Saját SMP-kiszolgálók A kézbesítési jelentések le vannak tiltva Adatbázismappa megnyitása - egy egyszer használható meghívási hivatkozáson keresztül + egy egyszer használható meghívón keresztül Csoportbeállítások megadása a következőn keresztül: %1$s igen @@ -1115,7 +1115,7 @@ Megállítás Megállítja a címmegosztást? Csevegési profilok módosítása - Megismétli a csatlakozási kérést? + Megismétli a meghívási kérést? Várakozás a képre Hangüzenetek Eltávolítja a tagot? @@ -1316,12 +1316,12 @@ Ellenőrizze, hogy a megfelelő hivatkozást használta-e, vagy kérje meg a partnerét, hogy küldjön egy másikat. A kép nem dekódolható. Próbálja meg egy másik képpel, vagy lépjen kapcsolatba a fejlesztőkkel. Érvénytelen fájlelérési útvonalat osztott meg. Jelentse a problémát az alkalmazás fejlesztőinek. - Már van egy csevegési profil ugyanezzel a megjelenített névvel. Válasszon egy másik nevet. + Már van egy csevegési profil ugyanezzel a megjelenítendő névvel. Válasszon egy másik nevet. Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott partnerétől érkező üzenetek fogadására szolgál (hiba: %1$s). A fájl fogadása le fog állni. Ne felejtse el, vagy tárolja biztonságosan – az elveszett jelszót nem lehet visszaállítani! A videó akkor érkezik meg, amikor a küldője befejezte annak feltöltését. - Ön egy egyszer használható meghívási hivatkozást osztott meg inkognitóban + Ön egy egyszer használható meghívót osztott meg inkognitóban Ön már kapcsolódott ahhoz a kiszolgálóhoz, amely az adott partnerétől érkező üzenetek fogadására szolgál. Később engedélyezheti a „Beállításokban” Akkor lesz kapcsolódva a csoporthoz, amikor a csoport tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később! @@ -1346,7 +1346,7 @@ Amikor az alkalmazás fut Inkognitóprofilt használ ehhez a csoporthoz – fő profilja megosztásának elkerülése érdekében a meghívók küldése le van tiltva Átvitel-izoláció - Akkor lesz kapcsolódva, ha a kapcsolatkérése el lesz fogadva, várjon, vagy ellenőrizze később! + Akkor lesz kapcsolódva, ha a meghívási kérése el lesz fogadva, várjon, vagy ellenőrizze később! A hangüzenetek küldése le van tiltva. Alkalmazás akkumulátor-használata / Korlátlan módot az alkalmazás beállításaiban.]]> Biztonságos kvantumálló-protokollon keresztül. @@ -1384,7 +1384,7 @@ Alkalmazás akkumulátor-használata / Korlátlan módot az alkalmazás beállításaiban.]]> Ez a karakterlánc nem egy meghívási hivatkozás! Új csevegés indításához - A kapcsolódás már folyamatban van ezen az egyszer használható meghívási hivatkozáson keresztül! + A kapcsolódás már folyamatban van ezen az egyszer használható meghívón keresztül! Nem veszíti el a partnereit, ha később törli a címét. A beállítások frissítése a kiszolgálókhoz való újra kapcsolódással jár. kapcsolatba akar lépni Önnel! @@ -1404,13 +1404,13 @@ Ön irányítja csevegését! Kód hitelesítése a számítógépen Az időzóna védelmének érdekében a kép-/hangfájlok UTC-t használnak. - A kapcsolatkérés el lesz küldve ezen csoporttag számára. + A meghívási kérés el lesz küldve ezen csoporttag számára. Inkognitóprofil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott. - Már küldött egy kapcsolatkérést ezen a címen keresztül! + Már küldött egy meghívási kérést ezen a címen keresztül! Megoszthatja ezt a SimpleX-címet a partnereivel, hogy kapcsolatba léphessenek vele: %s. - Amikor az emberek kapcsolatot kérnek, Ön elfogadhatja vagy elutasíthatja azokat. + Amikor az emberek meghívót küldenek, Ön elfogadhatja vagy elutasíthatja azokat. Megjelenítendő üzenet beállítása az új tagok számára! - Köszönet a felhasználóknak – hozzájárulás a Weblate-en! + Köszönet a felhasználóknak a Weblate-en való közreműködésért! A kézbesítési jelentések küldése az összes partnere számára engedélyezve lesz. Protokoll időtúllépése kB-onként Az adatbázis jelmondatának módosítására tett kísérlet nem fejeződött be. @@ -1421,7 +1421,7 @@ Ez a művelet nem vonható vissza – az összes fogadott és küldött fájl a médiatartalmakkal együtt törölve lesznek. Az alacsony felbontású képek viszont megmaradnak. A kézbesítési jelentések engedélyezve vannak %d partnernél Küldés a következőn keresztül: - Köszönet a felhasználóknak – hozzájárulás a Weblate-en! + Köszönet a felhasználóknak a Weblate-en való közreműködésért! A kézbesítési jelentések küldése engedélyezve lesz az összes látható csevegési profilban lévő összes partnere számára. Bluetooth támogatás és további fejlesztések. Ez a funkció még nem támogatott. Próbálja meg a következő kiadásban. @@ -1434,13 +1434,13 @@ Jelmondat beállítása az exportáláshoz A kézbesítési jelentések le vannak tiltva %d csoportban Néhány nem végzetes hiba történt az importáláskor: - Köszönet a felhasználóknak – hozzájárulás a Weblate-en! + Köszönet a felhasználóknak a Weblate-en való közreműködésért! A továbbítókiszolgáló csak szükség esetén lesz használva. Egy másik fél megfigyelheti az IP-címet. Beállítás a rendszer-hitelesítés helyett. A fogadási cím egy másik kiszolgálóra fog módosulni. A cím módosítása a feladó online állapotba kerülése után fejeződik be. A csevegés megállítása a csevegési adatbázis exportálásához, importálásához vagy törléséhez. A csevegés megállításakor nem tud üzeneteket fogadni és küldeni. Jelmondat mentése a Keystore-ba - Köszönet a felhasználóknak – hozzájárulás a Weblate-en! + Köszönet a felhasználóknak a Weblate-en való közreműködésért! Jelmondat mentése a beállításokban Ennek a csoportnak több mint %1$d tagja van, a kézbesítési jelentések nem lesznek elküldve. A második jelölés, amit kihagytunk! ✅ @@ -1454,7 +1454,7 @@ A profil aktiválásához koppintson az ikonra. A kézbesítési jelentések le vannak tiltva %d partnernél Munkamenet kód - Köszönet a felhasználóknak – hozzájárulás a Weblate-en! + Köszönet a felhasználóknak a Weblate-en való közreműködésért! Kis csoportok (max. 20 tag) Az Ön által elfogadott kérelem vissza lesz vonva! Élő üzenet küldése – az üzenet a címzett(ek) számára valós időben frissül, ahogy Ön beírja az üzenetet @@ -1479,8 +1479,8 @@ Ezek a beállítások csak a jelenlegi csevegési profiljára vonatkoznak Várjon, amíg a fájl betöltődik a társított hordozható eszközről GitHub tárolónkban.]]> - hiba a tartalom megjelenítésekor - hiba az üzenet megjelenítésekor + Hiba történt a tartalom megjelenítésekor + Hiba történt az üzenet megjelenítésekor Láthatóvá teheti a SimpleXbeli partnerei számára a „Beállításokban”. Legfeljebb az utolsó 100 üzenet lesz elküldve az új tagok számára. A beolvasott QR-kód nem egy SimpleX-QR-kód-hivatkozás. @@ -1503,9 +1503,9 @@ Vagy mutassa meg ezt a kódot Kamera hozzáférés engedélyezése Megtartja a fel nem használt meghívót? - Ennek az egyszer használható meghívási hivatkozásnak a megosztása + Ennek az egyszer használható meghívónak a megosztása Új csevegés - A csevegések betöltése… + Csevegések betöltése… Hivatkozás létrehozása… Vagy QR-kód beolvasása Érvénytelen QR-kód @@ -1553,8 +1553,8 @@ frissített profil %1$s a következőre módosította a nevét: %2$s Privát jegyzetek - Hiba a privát jegyzetek törlésekor - Hiba az üzenet létrehozásakor + Hiba történt a privát jegyzetek törlésekor + Hiba történt az üzenet létrehozásakor Kiüríti a privát jegyzeteket? Létrehozva Mentett üzenet @@ -1581,21 +1581,21 @@ Feloldás Az összes tag számára feloldja a tag letiltását? Ön letiltotta őt: %s - Hiba a tag az összes csoporttag számára való letiltásakor + Hiba történt a tag az összes csoporttag számára való letiltásakor Az üzenet túl nagy Az üdvözlőüzenet túl hosszú Az adatbázis átköltöztetése folyamatban van.\nEz eltarthat néhány percig. Hanghívás A hívás befejeződött Videóhívás - Hiba a böngésző megnyitásakor + Hiba történt a böngésző megnyitásakor A hívásokhoz egy alapértelmezett webböngésző szükséges. Állítson be egy alapértelmezett webböngészőt az eszközön, és osszon meg további információkat a SimpleX Chat fejlesztőivel. Hálózati beállítások megerősítése - Hiba a csevegési adatbázis exportálásakor + Hiba történt a csevegési adatbázis exportálásakor Alkalmaz Archiválás és feltöltés Feltöltés megerősítése - Hiba az adatbázis törlésekor + Hiba történt az adatbázis törlésekor Az adminisztrátorok egy tagot a csoport összes tagja számára letilthatnak. Az összes partnere, -beszélgetése és -fájlja biztonságosan titkosítva lesz, majd töredékekre bontva feltöltődnek a beállított XFTP-továbbítókiszolgálókra. Alkalmazásadatok átköltöztetése @@ -1610,10 +1610,10 @@ Letöltési hivatkozás részletei Engedélyezés a közvetlen csevegésekben (BÉTA)! Adja meg a jelmondatot - Hiba a beállítások mentésekor - Hiba az archívum letöltésekor - Hiba az archívum feltöltésekor - Hiba a jelmondat hitelesítésekor: + Hiba történt a beállítások mentésekor + Hiba történt az archívum letöltésekor + Hiba történt az archívum feltöltésekor + Hiba történt a jelmondat hitelesítésekor: Az exportált fájl nem létezik A fájl törölve lett, vagy érvénytelen a hivatkozás %s letöltve @@ -1628,7 +1628,7 @@ Vagy az archívum hivatkozásának beillesztése Archívum hivatkozásának beillesztése Letöltés ismét - Sikertelen importálás + Nem sikerült az importálás Ellenőrizze, hogy a hálózati beállítások megfelelők-e ehhez az eszközhöz. A folytatáshoz a csevegést meg kell szakítani. Csevegés megállítása folyamatban @@ -1666,7 +1666,7 @@ Ez a csevegés végpontok közötti kvantumálló titkosítással védett. végpontok közötti titkosítással, sérülés utáni titkosság-védelemmel és -helyreállítással, továbbá visszautasítással védi.]]> végpontok közötti kvantumálló titkosítással sérülés utáni titkosságvédelemmel, visszautasítással és feltörés utáni helyreállítással vannak védve.]]> - Hiba az értesítés megjelenítésekor, lépjen kapcsolatba a fejlesztőkkel. + Hiba történt az értesítés megjelenítésekor, lépjen kapcsolatba a fejlesztőkkel. Keresse meg ezt az engedélyt az Android beállításaiban, és adja meg kézzel. Engedélyezés a beállításokban Engedély(ek) megadása a hívások kezdeményezéséhez @@ -1797,7 +1797,7 @@ Privát üzenet-útválasztás 🚀 Fájlok biztonságos fogadása Csökkentett akkumulátor-használattal. - Hiba a WebView előkészítésekor. Frissítse rendszerét az új verzióra. Lépjen kapcsolatba a fejlesztőkkel.\nHiba: %s + Hiba történt a WebView előkészítésekor. Frissítse rendszerét az új verzióra. Lépjen kapcsolatba a fejlesztőkkel.\nHiba: %s Felhasználó által létrehozott téma visszaállítása Üzenetsorbaállítási információ nincs @@ -1839,9 +1839,9 @@ Proxyzott kiszolgálók Újrakapcsolódik a kiszolgálókhoz? Újrakapcsolódik a kiszolgálóhoz? - Hiba a kiszolgálóhoz való újrakapcsolódáskor + Hiba történt a kiszolgálóhoz való újrakapcsolódáskor Újrakapcsolódás az összes kiszolgálóhoz - Hiba a statisztikák visszaállításakor + Hiba történt a statisztikák visszaállításakor Visszaállítás Az összes statisztika visszaállítása Visszaállítja az összes statisztikát? @@ -1892,7 +1892,7 @@ Fogadott üzenetek Letöltési hibák Hiba - Hiba a kiszolgálókhoz való újrakapcsolódáskor + Hiba történt a kiszolgálókhoz való újrakapcsolódáskor Fájlok Betűméret Nincs információ, próbálja meg újratölteni @@ -1932,9 +1932,9 @@ Letiltás Letiltva Stabil - Hiba a(z) %1$s továbbítókiszolgálóhoz való kapcsolódáskor. Próbálja meg később. + Hiba történt a(z) %1$s továbbítókiszolgálóhoz való kapcsolódáskor. Próbálja meg később. A(z) %1$s célkiszolgáló verziója nem kompatibilis a(z) %2$s továbbítókiszolgálóval. - A(z) %1$s továbbítókiszolgáló nem tudott csatlakozni a(z) %2$s célkiszolgálóhoz. Próbálja meg később. + A(z) %1$s továbbítókiszolgáló nem tudott kapcsolódni a(z) %2$s célkiszolgálóhoz. Próbálja meg később. A(z) %1$s célkiszolgáló címe nem kompatibilis a(z) %2$s továbbítókiszolgáló beállításaival. Médiatartalom elhomályosítása Közepes @@ -2016,7 +2016,7 @@ Új üzenet Érvénytelen hivatkozás Ellenőrizze, hogy a SimpleX-hivatkozás helyes-e. - Hiba a profilváltáskor + Hiba történt a profilváltáskor A kapcsolata át lett helyezve ide: %s, de egy váratlan hiba történt a profilra való átirányításkor. Az üzenetek törölve lesznek – ez a művelet nem vonható vissza! Eltávolítja az archívumot? @@ -2031,14 +2031,14 @@ Jelszó Felhasználónév A hitelesítőadatai titkosítatlanul is elküldhetők. - Hiba a proxy mentésekor + Hiba történt a proxy mentésekor Győződjön meg arról, hogy a proxy konfigurációja helyes. Proxyhitelesítés Véletlenszerű hitelesítőadatok használata %1$d egyéb fájlhiba. Nincs mit továbbítani! %1$d fájl letöltése még folyamatban van. - %1$d fájlt nem sikerült letölteni. + Nem sikerült letölteni %1$d fájlt. %1$d fájl nem lett letöltve. Letöltés %1$d fájl törölve lett. @@ -2050,9 +2050,9 @@ Továbbítja az üzeneteket fájlok nélkül? Az üzeneteket törölték miután kijelölte őket. %1$s üzenet mentése - Hiba az üzenetek továbbításakor + Hiba történt az üzenetek továbbításakor Hang elnémítva - Hiba a WebView előkészítésekor. Győződjön meg arról, hogy a WebView telepítve van-e, és támogatja-e az arm64 architektúrát.\nHiba: %s + Hiba történt a WebView előkészítésekor. Győződjön meg arról, hogy a WebView telepítve van-e, és támogatja-e az arm64 architektúrát.\nHiba: %s Sarok Üzenetbuborék alakja Farok @@ -2066,19 +2066,19 @@ Továbbfejlesztett hívásélmény Továbbfejlesztett üzenetdátumok. Továbbfejlesztett felhasználói élmény - Testre szabható üzenetbuborékok. + Személyre szabható üzenetbuborékok. Legfeljebb 200 üzenet egyszerre való törlése, vagy moderálása. Legfeljebb 20 üzenet egyszerre való továbbítása. Hang/Videó váltása hívás közben. - Csevegési profilváltás az egyszer használható meghívási hivatkozásokhoz. + Csevegési profilváltás az egyszer használható meghívókhoz. Továbbfejlesztett biztonság ✅ A SimpleX Chat biztonsága a Trail of Bits által lett felülvizsgálva. - Hiba a kiszolgálók mentésekor + Hiba történt a kiszolgálók mentésekor Nincsenek üzenet-kiszolgálók. Nincsenek üzenetfogadási kiszolgálók. Nincsenek média- és fájlkiszolgálók. A(z) %s nevű csevegési profilhoz: - Cím vagy egyszer használható meghívási hivatkozás? + Cím vagy egyszer használható meghívó? Új kiszolgáló Címbeállítások Előre beállított kiszolgálók @@ -2088,7 +2088,7 @@ Nincsenek fájlküldő-kiszolgálók. Nincsenek fájlfogadási kiszolgálók. Hibák a kiszolgálók konfigurációjában. - Hiba a feltételek elfogadásakor + Hiba történt a feltételek elfogadásakor Kézbesítetlen üzenetek A kapcsolat elérte a kézbesítetlen üzenetek számának határát, az Ön partnere lehet, hogy offline állapotban van. Nincs üzenet @@ -2096,17 +2096,17 @@ Koppintson a SimpleX-cím létrehozása menüpontra a későbbi létrehozáshoz. Cím nyilvános megosztása SimpleX-cím megosztása a közösségi médiában. - Egyszer használható meghívási hivatkozás megosztása egy baráttal - egyetlen partnerrel használható – személyesen vagy bármilyen üzenetváltón keresztül megosztható.]]> + Egyszer használható meghívó megosztása egy baráttal + csak egyetlen partnerrel használható – személyesen vagy bármilyen üzenetváltó-alkalmazáson keresztül megosztható.]]> Beállíthatja a partner nevét, hogy emlékezzen arra, hogy kivel osztotta meg a hivatkozást. Kapcsolatbiztonság - A SimpleX-cím és az egyszer használható meghívási hivatkozás biztonságosan megosztható bármilyen üzenetváltón keresztül. + A SimpleX-cím és az egyszer használható meghívó biztonságosan megosztható bármilyen üzenetváltó-alkalmazáson keresztül. A hivatkozás cseréje elleni védelem érdekében összehasonlíthatja a biztonsági kódokat a partnerével. A közösségi médiához Vagy a privát megosztáshoz - SimpleX-cím vagy egyszer használható meghívási hivatkozás? - Egyszer használható meghívási hivatkozás létrehozása - Kiszolgáló-üzemeltetők + SimpleX-cím vagy egyszer használható meghívó? + Egyszer használható meghívó létrehozása + Kiszolgálóüzemeltetők Hálózatüzemeltetők Az alkalmazás úgy védi az adatait, hogy minden egyes beszélgetéshez más-más üzemeltetőt használ. Például, ha az Ön partnere egy SimpleX Chat-kiszolgálón keresztül fogadja az üzeneteket, az Ön alkalmazása egy Flux-kiszolgálón keresztül fogja azokat kézbesíteni. @@ -2130,7 +2130,7 @@ A feltételek el lesznek elfogadva a következő időpontban: %s. Kiszolgálók használata %s használata - A jelenlegi feltételek szövegét nem lehetett betölteni, a feltételeket a következő hivatkozáson keresztül vizsgálhatja felül: + A jelenlegi feltételek szövegét nem sikerült betölteni, a feltételeket a következő hivatkozáson keresztül vizsgálhatja felül: %s.]]> %s.]]> %s.]]> @@ -2149,17 +2149,17 @@ Hozzáadott média- és fájlkiszolgálók Feltételek megnyitása Módosítások megtekintése - Hiba a kiszolgáló frissítésekor + Hiba történt a kiszolgáló frissítésekor A kiszolgáló-protokoll módosult. A kiszolgáló üzemeltetője módosult. Kiszolgáló-üzemeltető Kiszolgáló hozzáadva a következő üzemeltetőhöz: %s. - Hiba a kiszolgáló hozzáadásakor + Hiba történt a kiszolgáló hozzáadásakor Átlátszóság Elhomályosítás Hálózati decentralizáció A második előre beállított üzemeltető az alkalmazásban! - Flux engedélyezése + A Flux kiszolgálókat engedélyezheti a beállításokban, a „Hálózat és kiszolgálók” menüben, a jobb metaadat-adatvédelem érdekében. Alkalmazás-eszköztárak a metaadatok jobb védelme érdekében. Javított csevegési navigáció @@ -2205,7 +2205,7 @@ A tagok közötti közvetlen üzenetek le vannak tiltva ebben a csevegésben. Amikor egynél több üzemeltető van engedélyezve, akkor egyik sem rendelkezik olyan metaadatokkal, amelyekből megtudható, hogy ki kivel kommunikál. elfogadott meghívó - kérelmezve a kapcsolódáshoz + Függőben lévő meghívási kérelem Az üzemeltetőkről A SimpleX Chat és a Flux megállapodást kötött arról, hogy a Flux által üzemeltetett kiszolgálókat beépítik az alkalmazásba. A titkosítás újraegyeztetése folyamatban van. @@ -2213,7 +2213,7 @@ Javítás Kapcsolat javítása? Naplózás engedélyezése - Hiba az adatbázis mentésekor + Hiba történt az adatbázis mentésekor áthúzott A következő csevegési profil törlése Üzenetek törlése ennyi idő után @@ -2225,9 +2225,9 @@ Lista hozzáadása Összes Hozzáadás listához - Hiba a csevegési lista létrehozásakor - Hiba a csevegési lista betöltésekor - Hiba a csevegési lista frissítésekor + Hiba történt a csevegési lista létrehozásakor + Hiba történt a csevegési lista betöltésekor + Hiba történt a csevegési lista frissítésekor Üzleti Partnerek Kedvencek @@ -2246,8 +2246,8 @@ Jegyzetek Lista módosítása Elrendezés módosítása - Hiba a jelentés létrehozásakor - Hiba a beállítások mentésekor + Hiba történt a jelentés létrehozásakor + Hiba történt a beállítások mentésekor A jelentés archiválva lesz az Ön számára. Tartalom jelentése: csak a csoport moderátorai látják. Archívum @@ -2328,7 +2328,7 @@ A jelmondat nem olvasható a Keystore-ban, ezért kézzel szükséges megadni. Ez az alkalmazással nem kompatibilis rendszerfrissítés után történhetett meg. Ha nem így történt, akkor lépjen kapcsolatba a fejlesztőkkel. függőben jóváhagyásra vár - Hiba az adatbázis-jelmondat olvasásakor + Hiba történt az adatbázis-jelmondat olvasásakor A jelmondat nem olvasható a Keystore-ban. Ez az alkalmazással nem kompatibilis rendszerfrissítés után történhetett meg. Ha nem így történt, akkor lépjen kapcsolatba a fejlesztőkkel. Frissített feltételek A tagok el lesznek távolítva a csoportból – ez a művelet nem vonható vissza! @@ -2339,4 +2339,9 @@ Ezen tagok összes új üzenete el lesz rejtve! A tagok összes üzenete meg fog jelenni! moderátorok + Elfogadás + A SimpleX Chat használatával Ön elfogadja, hogy:\n- csak elfogadott tartalmakat tesz közzé a nyilvános csoportokban.\n- tiszteletben tartja a többi felhasználót, és nem küld kéretlen tartalmat senkinek. + Adatvédelmi szabályzat és felhasználási feltételek. + A privát csevegések, a csoportok és a partnerek nem érhetők el a szerver üzemeltetői számára. + Kiszolgálóüzemeltetők beállítása diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml index 835b21db45..278b94feb3 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml @@ -1788,7 +1788,7 @@ Kesalahan Keychain Simpan frasa sandi dan buka obrolan Buka obrolan - Frasa sandi tidak ditemukan di Keystore, silakan masukkan secara manual. Hal ini mungkin terjadi jika Anda pulihkan data aplikasi menggunakan alat cadangan. Jika tidak, silakan hubungi pengembang. + Frasa sandi tidak ditemukan di Keystore, silakan masukkan secara manual. Hal ini mungkin terjadi jika Anda memulihkan data aplikasi menggunakan alat cadangan. Jika tidak demikian, silakan hubungi pengembang. Toolbar aplikasi yang dijangkau Anggota akan dihapus dari grup - ini tidak dapat dibatalkan! Batas waktu protokol per KB @@ -2316,4 +2316,25 @@ Laporkan pesan dilarang di grup ini. Dilarang laporkan pesan ke moderator. Arsipkan %d laporan? + Gagal membaca frasa sandi basis data + Privasi dan keamanan lebih baik + Semua pesan baru dari anggota ini akan disembunyikan! + Blokir anggota untuk semua? + Dapat notifikasi saat disebut. + Kirim pesan lebih cepat. + Jangan lewatkan pesan penting. + Kinerja grup yang lebih baik + Hapus grup lebih cepat. + Syarat diperbarui + Anggota akan dihapus dari obrolan - hal ini tidak dapat dibatalkan! + Hapus anggota? + ditolak + tertunda + menunggu persetujuan + Buka blokir anggota untuk semua? + Kirim laporan pribadi + Bantu admin memoderasi grup. + Atur pesan kedaluwarsa obrolan. + Nama berkas media pribadi. + ditolak diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index d9b207f27a..08a8f9b87e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -1586,7 +1586,7 @@ Errore di eliminazione delle note private Consegna dei messaggi migliorata Entra in conversazioni di gruppo - membro %1$s cambiato in %2$s + il membro %1$s è diventato %2$s Incolla un link per connettere! indirizzo di contatto rimosso contatto %1$s cambiato in %2$s @@ -2177,7 +2177,7 @@ Trasparenza Decentralizzazione della rete Il secondo operatore preimpostato nell\'app! - Attiva Flux + Attiva Flux nelle impostazioni \"Rete e server\" per una migliore privacy dei metadati. Vedi le condizioni aggiornate Sfocatura Server dei messaggi aggiunti @@ -2376,4 +2376,9 @@ Bloccare i membri per tutti? moderatori Tutti i nuovi messaggi di questi membri verranno nascosti! + Usando SimpleX Chat accetti di:\n- inviare solo contenuto legale nei gruppi pubblici.\n- rispettare gli altri utenti - niente spam. + Le chat private, i gruppi e i tuoi contatti non sono accessibili agli operatori dei server. + Accetta + Configura gli operatori dei server + Informativa sulla privacy e condizioni d\'uso. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml index 8a92168d26..21c04b8473 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml @@ -1993,4 +1993,19 @@ 利用条件の承諾 %s の利用条件に承諾しています。]]> 後で作成する場合はメニューから「SimpleXのアドレスを作成」を選択してください。 + 運営者について + 1年 + 1 件のレポート + モデレーターにメッセージを報告することを許可する + これらのメンバーからの新しいメッセージはすべて非表示になります! + リストに追加 + アドレス設定 + チームのメンバーを会話に追加する + 追加されたメッセージサーバー + アドレスか使い捨てのリンク? + 友達を追加 + チームメンバーを追加 + リストを追加 + すべて + ワンタイムリンクを生成 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index 686b1959d5..d0b3cd5f53 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -2281,7 +2281,7 @@ Lijst wijzigen Wijzig volgorde ‐Fout bij het opslaan van instellingen - Fout bij maken van rapport + Fout bij het rapporteren Archief Schending van de communityrichtlijnen Een andere reden @@ -2295,7 +2295,7 @@ Alleen jij en moderators zien het Spam Rapport archiveren? - Rapport + rapporteren Reden melding? Het rapport wordt voor u gearchiveerd. Anders melden: alleen groepsmoderators kunnen het zien. @@ -2341,22 +2341,36 @@ Voor mij Leden kunnen berichten melden bij moderators. Het melden van berichten in deze groep is niet toegestaan - Rapport: %s + Rapporteer: %s Het melden van berichten aan moderators is niet toegestaan. Voor alle moderators Rapporten archiveren Betere prestaties van groepen Stel de berichtvervaldatum in chats in. Betere privacy en veiligheid - ‐Mis geen belangrijke berichten. + Mis geen belangrijke berichten. Sneller verwijderen van groepen. Ontvang een melding als u vermeld wordt. Help beheerders bij het modereren van hun groepen. Vermeld leden 👋 Organiseer chats in lijsten Namen van persoonlijke mediabestanden. - Verstuur rapporten privé + Rapporteer privé Sneller verzenden van berichten. afgewezen afgewezen + Fout bij het lezen van database wachtwoord + Alle nieuwe berichten van deze leden worden verborgen! + Leden voor iedereen blokkeren? + Leden worden uit de chat verwijderd. Dit kan niet ongedaan worden gemaakt! + Leden worden uit de groep verwijderd. Dit kan niet ongedaan worden gemaakt! + Leden voor iedereen deblokkeren? + Berichten van deze leden worden getoond! + moderatoren + Wachtwoord in Keystore kan niet worden gelezen, voer deze handmatig in. Dit kan zijn gebeurd na een systeemupdate die niet compatibel is met de app. Als dit niet het geval is, neem dan contact op met de ontwikkelaars. + in afwachting van goedkeuring + Leden verwijderen? + Bijgewerkte voorwaarden + Wachtwoord in Keystore kan niet worden gelezen. Dit kan zijn gebeurd na een systeemupdate die niet compatibel is met de app. Als dit niet het geval is, neem dan contact op met de ontwikkelaars. + in behandeling diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml index 184548e0da..a0cce488af 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml @@ -341,7 +341,7 @@ Ocultar Da Galeria Vídeo - Os membros do grupo podem enviar mensagens temporárias. + Os membros podem enviar mensagens temporárias. Arquivo Nome completo: Chamada de áudio recebida @@ -360,7 +360,7 @@ Mensagens que desaparecem Preferências do grupo Mensagens temporárias são proibidas nesse bate-papo. - Os membros do grupo podem enviar mensagens diretas. + Os membros podem enviar mensagens diretas. %dmês Link completo Ocultar @@ -377,7 +377,7 @@ Falha ao carregar as conversas Arquivo: %s Arquivo salvo - Os membros do grupo podem enviar mensagens de voz. + Os membros podem enviar mensagens de voz. O grupo será excluído para todos os membros - isso não pode ser desfeito! AJUDA Ocultar contato e mensagem @@ -406,7 +406,7 @@ perfil do grupo atualizado Grupo excluído O modo Incognito protege sua privacidade usando um novo perfil aleatório para cada contato. - Os membros do grupo podem excluir mensagens enviadas de forma irreversível. (24 horas) + Os membros grupo podem excluir mensagens enviadas de forma irreversível. (24 horas) %dsemana Configuração de servidor aprimorada Interface francesa @@ -534,7 +534,7 @@ Suas preferências Definir preferências de grupo Somente você pode excluir irreversivelmente as mensagens (seu contato pode marcá-las para exclusão). (24 horas) - A exclusão irreversível de mensagens é proibida neste grupo. + A exclusão irreversível de mensagens é proibida. Os destinatários vêem as atualizações conforme você as digita. Uso da bateria reduzido Mais melhorias chegarão em breve! @@ -641,7 +641,7 @@ \nAtenção: você não será capaz de se conectar aos servidores sem um endereço .onion Versão principal: v%s repositório do GitHub.]]> - Pode ser mudado mais tarde via configurações. + Como isso afeta a bateria %1$s quer se conectar com você via sem criptografia ponta-a-ponta Abrir @@ -849,7 +849,7 @@ Para começar um novo bate-papo Ligar Bem-vindo(a)! - A próxima geração \nde mensageiros privados + O futuro da transmissão de mensagens PROXY SOCKS A tentativa de alterar a senha do banco de dados não foi concluída. Pare o bate-papo para exportar, importar ou excluir o banco de dados do chat. Você não poderá receber e enviar mensagens enquanto o chat estiver interrompido. @@ -862,7 +862,7 @@ A atualização das configurações reconectará o cliente a todos os servidores. Atualizar Sistema - Mensagens de voz são proibidas neste grupo. + Mensagens de voz são proibidas. Verificar a segurança da conexão Para proteger o fuso horário, os arquivos de imagem/voz usam UTC. formato de mensagem desconhecido @@ -1061,7 +1061,7 @@ Senha alterada! Você pode ativar o bloqueio SimpleX via configurações. Hash de mensagem incorreta - O hash da mensagem anterior é diferente. + O hash da mensagem anterior é diferente.\" %1$d mensagens falharam em serem descriptografadas. ID de mensagem incorreta A ID da próxima mensagem está incorreta (menor ou igual à anterior). @@ -1171,7 +1171,7 @@ Permitir reações à mensagens. Somente você pode adicionar reações à mensagens. Somente seu contato pode adicionar reações à mensagens. - Reações à mensagens são proibidas neste grupo. + Reações a mensagens são proibidas. horas minutos segundos @@ -1199,7 +1199,7 @@ %s (atual) Permitir que seus contatos adicionem reações à mensagens. Você e seu contato podem adicionar reações à mensagens. - Os membros do grupo podem adicionar reações às mensagens. + Os membros podem adicionar reações. Reações à mensagens são proibidas neste bate-papo. Proibir reações à mensagens. personalizado @@ -1265,7 +1265,7 @@ Erro ao sincronizar conexão Favorito Arquivos e mídia proibidos! - Os membros do grupo podem enviar arquivos e mídia. + Os membros podem enviar arquivos e mídias. Corrigir Correção não suportada pelo contato Desligar @@ -1800,7 +1800,7 @@ Aviso: iniciar conversa em múltiplos dispositivos não é suportado e pode causar falhas na entrega de mensagens Internet cabeada não deve usar a mesma base de dados em dois dispositivos.]]> - Membros do grupo podem enviar link SimpleX + Membros podem enviar links SimpleX. Importando arquivo Modo claro Ativado para @@ -1897,7 +1897,7 @@ Escala Preencher Ajustar - Links SimpleX são proibidos neste grupo. + Links SimpleX são proibidos. Migrar para outro dispositivo via QR code. Chamadas picture-in-picture Use o aplicativo enquanto está em chamada. @@ -1982,7 +1982,7 @@ Sessões de transporte Recepção de mensagem Pendente - Começando de %s. \nTodos os dados são privados do seu dispositivo. + Começando em %s.\nTodos os dados são mantidos privados em seu dispositivo. Total Servidores proxiados Servidores conectados anteriormente @@ -2055,7 +2055,7 @@ Por favor reinicie o aplicativo. Me lembre mais tarde Para ser notificado sobre os novos lançamentos, habilite a checagem periódica de versões Estáveis e Beta. - Barra de ferramentas de conversa acessível + Barras de ferramentas de aplicativos acessível Falha no baixar de %1$d arquivo(s). %1$s mensagens não encaminhadas. DADOS DO BATE-PAPO @@ -2196,7 +2196,7 @@ Criar lista Editar Canto - Ativar flux + Ativar o Flux nas Configurações de rede e servidores para melhor privacidade de metadados. Todas denúncias serão arquivadas para você. Arquivar todas denúncias? Arquivar %d denúncias? @@ -2220,4 +2220,166 @@ Erro ao criar denúncia Chat Seus servidores + aprovação pendente + pendente + Os membros podem denunciar mensagens aos moderadores. + Operadores da rede + Operador + Nenhum serviço de segundo plano + Abrir mudanças + moderadores + Descentralização da rede + Privacidade para seus clientes. + Mencione membros 👋 + Seja notificado quando mencionado. + Ajude os administradores a moderar seus grupos. + Organize os chats em listas + Nomes de arquivos de mídia privados. + Conteúdo inapropriado + Perfil inapropriado + Nenhuma mensagem de servidores. + Nenhuma mensagem + Denúncias de membros + Ou compartilhe em particular + Nenhum chat não lido + Nenhum chat + Notas + Abrir com %s + O nome da lista e o emoji devem ser diferentes para todas as listas. + Novas credenciais SOCKS serão usadas para cada servidor. + Notificações e bateria + As mensagens desses membros serão exibidas! + Silenciar tudo + Para redes sociais + Servidores predefinidos + Abrir links da lista de bate-papo + Abrir web link? + Convidar ao chat + Abrir condições + Nome da lista... + Novas credenciais SOCKS serão usadas toda vez que você iniciar o aplicativo. + Abrir link + Forma da mensagem + moderador + A mensagem é muito grande! + Por favor, reduza o tamanho da mensagem e a envie novamente. + Operador da rede + Para roteamento privado + Aprimorada a navegação de bate-papo + - Abra o chat na primeira mensagem não lida.\n- Pule para mensagens citadas. + Os membros serão removidos do chat. Essa ação não pode ser desfeita! + Said do chat + Os membros serão removidos do grupo. Essa ação não pode ser desfeita! + Nove servidor + Nenhum chat encontrado + As mensagens neste chat nunca serão excluídas. + A frase-senha na Keystore não pôde ser lida. Isso pode ter acontecido após uma atualização do sistema incompatível com o aplicativo. Se não for o caso, entre em contato com os desenvolvedores. + Somente os proprietários do chat podem alterar as preferências. + A frase-senha na Keystore não pôde ser lida, insira-a manualmente. Isso pode ter acontecido após uma atualização do sistema incompatível com o aplicativo. Se não for o caso, entre em contato com os desenvolvedores. + Grupos + Ou importar arquivo compactado + Lista + Reduza o tamanho da mensagem ou remova a mídia e envie novamente. + Como isso ajuda na privacidade + Não + Sair do chat? + O membro será removido do chat - essa ação não pode ser desfeita! + Encaminhe até 20 mensagens de uma vez. + Nenhuma mídia & nenhum arquivo de servidores. + Nenhum servidor para enviar arquivos. + Nenhum servidor para roteamento de mensagens privadas. + Nenhum servidor para receber arquivos. + Nenhum servidor para receber mensagens. + Abra Configurações do Safari / Websites / Microfone, e escolha Permitir para localhost. + Servidor do operador + Nenhum chat na lista %s. + Somente o remetente e os moderadores podem vê-lo. + Somente você e os moderadores podem ver isso + rejeitado + Denunciar + Proibir a denúncia de mensagens aos moderadores. + Denunciar conteúdo: somente os moderadores do grupo poderão ver. + Denunciar perfil de membro: somente moderadores do grupo poderão ver. + Dispositivos móveis remotos + Remover membros? + Denunciar outro: somente os moderadores do grupo poderão ver. + Qual é a razão da denúncia? + Denúncia: %s + rejeitado + É proibido denunciar mensagens neste grupo. + Barra de ferramentas de chat acessível + Denúncias + Operador do servidor alterado. + Definir nome do chat… + Compartilhar o endereço publicamente + Denunciar violação: somente os moderadores do grupo poderão ver. + %s servidores + Protocolos SimpleX analisados pela Trail of Bits. + Enviar denúncias privadas + Defina a expiração de mensagens em chats. + Spam + Spam + Salvar lista + Compartilhe um link único com um amigo + Endereço SimpleX ou link único? + Som silenciado + Alterne entre áudio e vídeo durante a chamada. + Selecione as operadoras de rede a serem utilizadas. + Revisar condições + Endereços SimpleX e links únicos são seguros para compartilhar por meio de qualquer mensageiro. + Operadores do servidor + Servidor adicionado ao operador %s. + riscar + Denunciar spam: somente os moderadores do grupo poderão ver. + Protocolo do servidor alterado. + Compartilhe o endereço do SimpleX nas redes sociais. + Revisar depois + Servidor + O SimpleX Chat e o Flux fizeram um acordo para incluir servidores operados pelo Flux no aplicativo. + conexão solicitada + The role will be changed to %s. Everyone in the chat will be notified. + Transparência + Alterne o perfil de chat para convites únicos. + Desbloquear membros para todos? + Para enviar + Condições atualizadas + O segundo operador predefinido no aplicativo! + Esta mensagem foi excluída ou ainda não foi recebida. + Ver condições atualizadas + Toque em Criar endereço SimpleX no menu para criá-lo mais tarde. + Usar porta TCP %1$s quando nenhuma porta for especificada. + A denúncia será arquivado para você. + Para receber + Esta ação não pode ser desfeita - as mensagens enviadas e recebidas neste chat antes da selecionada serão excluídas. + Para se proteger contra a substituição do seu link, você pode comparar os códigos de segurança dos contatos. + Menções não lidas + Porta TCP para mensagens + Usar porta web + O aplicativo protege sua privacidade usando diferentes operadores em cada conversa. + Quando mais de um operador está ativado, nenhum deles têm metadados para saber quem se comunica com quem. + Sim + Seu perfil de chat será enviado aos membros do chat + Ver condições + Usar para mensagens + Você pode definir o nome da conexão para lembrar com quem o link foi compartilhado. + Você pode configurar servidores nas configurações. + Usar %s + Usar servidores + Website + Você pode mencionar até %1$s membros por mensagem! + Final + Você pode copiar e reduzir o tamanho da mensagem para enviá-la. + Atualização + Você pode configurar operadores em Configurações de rede & servidores. + Usar para arquivos + Você deixará de receber mensagens deste chat. O histórico do chat será preservado. + Para fazer chamadas, permita usar seu microfone. Encerre a chamada e tente ligar novamente. + Os servidores para novos arquivos do seu perfil de chat atual + A conexão atingiu o limite de mensagens não entregues, seu contato pode estar offline. + Mensagens não entregues + Configurar operadores de servidor + Chats privados, grupos e seus contatos não são acessíveis aos operadores de servidor. + Aceitar + Ao usar o SimpleX Chat, você concorda em:\n- enviar apenas conteúdo legal em grupos públicos.\n- respeitar outros usuários – sem spam. + Política de privacidade e condições de uso. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml index f8f356614b..dafa22dfa9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -2231,7 +2231,7 @@ Прозрачность Децентрализация сети Второй оператор серверов в приложении! - Включить Flux + Включите Flux в настройках Сеть и серверы для лучшей конфиденциальности метаданных. для лучшей конфиденциальности метаданных. Улучшенная навигация в разговоре Посмотреть измененные условия @@ -2354,7 +2354,7 @@ Открыто с %s Создать список Добавить в список - Список изменений + Изменить список Сохранить список Имя списка... Исправить соединение? @@ -2458,4 +2458,9 @@ Участники будут удалены из разговора - это действие нельзя отменить! модераторы Удалить членов группы? + Принять + Используя SimpleX Chat, Вы согласны:\n- отправлять только законные сообщения в публичных группах.\n- уважать других пользователей – не отправлять спам. + Частные разговоры, группы и Ваши контакты недоступны для операторов серверов. + Настроить операторов серверов + Политика конфиденциальности и условия использования. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml index c721c95663..257daec596 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml @@ -264,7 +264,7 @@ Kullanıldığında bütün veriler silinir. Kendiliğinden yok olan mesajlar Kişilerinin sana, kendiğinden yok olan mesajlar göndermesine izin ver. - Bu grupta kendiliğinden yok olan mesajlara izin verilmiyor. + Kendiliğinden yok olan mesajlara izin verilmiyor. %1$d mesajlar deşifrelenemedi. %1$s ÜYELER %1$d atlanılmış mesaj(lar) @@ -605,7 +605,7 @@ Yedekleri geri yükledikten sonra şifrelemeyi onar. Fransız arayüzü Daha da azaltılmış pil kullanımı - Grup üyeleri, mesajlara tepki ekleyebilir. + Üyeler, mesajlara tepki ekleyebilir. Grup profili, üyelerinin aygıtlarında barındırılmaktadır, sunucularda değil. Gizle Gizle @@ -648,9 +648,9 @@ Grup tam adı: Dosya ve medya Grup üyeleri doğrudan mesaj gönderebilir. - Grup üyeleri, gönderilen mesajları kalıcı olarak silebilir. (24 saat içinde) + Üyeler, gönderilen mesajları kalıcı olarak silebilir. (24 saat içinde) Grup üyeleri sesli mesaj gönderebilirler. - Bu toplu konuşmada, dosya ve medya yasaklanmıştır. + Dosya ve medya yasaklanmıştır. Grup üyeleri dosya ve medya paylaşabilir. Grup bağlantıları Konuşmada devre dışı bırakıldığında bile @@ -690,7 +690,7 @@ Android Keystore parolayı güvenli bir şekilde saklamak için kullanılır - bildirim hizmetinin çalışmasını sağlar. Karşılama mesajı Karşılama mesajı - Bu grupta sesli mesajlar yasaktır. + Sesli mesajlar yasaktır. Neler yeni %s sürümünde yeni Daha fazla bilgi edinin @@ -730,7 +730,7 @@ Arkadaşlarınızı davet edin kalın italik - Daha sonra ayarlardan değiştirebilirsiniz. + Pili nasıl etkiler kişi uçtan uca şifrelemeye sahiptir kişi uçtan uca şifrelemeye sahip değildir Sohbet durduruldu @@ -758,7 +758,7 @@ Japonca ve Portekizce kullanıcı arayüzü Kişiler davet edilemiyor! Davetin süresi dolmuş! - Geri alınamaz mesaj silme bu grupta yasaktır + Geri alınamaz mesaj silme yasaktır Mesaj gönderildi bilgisi! Gruba katılınıyor tek seferlik gizli bağlantı paylaştınız @@ -835,7 +835,7 @@ Alıcı adresini değiştir Mesaj tepkileri Tercihleriniz - Mesaj tepkileri bu grupta yasaklıdır + Mesaj tepkileri yasaklıdır. Bu kişiden mesaj almak için kullanılan sunucuya bağlısınız. Zaten %1$s e bağlısınız Doğrulanamadınız; lütfen tekrar deneyin. @@ -931,7 +931,7 @@ - daha stabil mesaj iletimi. \n- biraz daha iyi gruplar. \n- ve daha fazlası! - 2 katmanlı uçtan uca şifreleme ile kullanıcı profillerini, kişileri, grupları ve gönderilen mesajları depolar.]]> + Sadece istemci cihazlar kullanıcı profillerini, kişileri, grupları ve gönderilen mesajları depolar. Grup tercihlerini sadece grup sahipleri değiştirebilir. metin yok Ağ durumu @@ -940,7 +940,7 @@ Kilit modunu değiştir Bu kişiden mesaj almak için kullanılan sunucuya bağlanılmaya çalışılıyor. Lütfen doğru bağlantıyı kullandığınızı kontrol edin veya irtibat kişinizden size başka bir bağlantı göndermesini isteyin. - SimpleX arka plan hizmeti kullanılır - günde pilin yüzde birkaçını kullanır.]]> + SimpleX arka planda çalışır.]]> Periyodik bildirimler Periyodik bildirimler devre dışı Bildirimleri almak için lütfen veri tabanı parolasını girin @@ -949,7 +949,7 @@ Hatırlayın veya güvenli bir şekilde saklayın - kaybolan bir parolayı kurtarmanın bir yolu yoktur! Sohbet konsolunu aç - Sohbet profillerini aö + Sohbet profillerini değiştir. Bu metin ayarlarda mevcut Filtrelenmiş sohbet yok Çok fazla görsel! @@ -1022,7 +1022,7 @@ Mesaj taslağı Sohbet profillerini parola ile koru! Daha az pil kullanımı - Gizliliği korumak için, diğer tüm platformlar gibi kullanıcı kimliği kullanmak yerine, SimpleX mesaj kuyrukları için kişilerinizin her biri için ayrı tanımlayıcılara sahiptir. + Gizliliği korumak için, SimpleX her bir konuşma için farklı bir ID kullanır. Bu kişiden mesaj almak için kullanılan sunucuya bağlanılmaya çalışılıyor (hata: %1$s). Alıcılar güncellemeleri siz yazdıkça görürler. Bilgilerinizi kullanarak giriş yapın @@ -1713,7 +1713,7 @@ SimpleX bağlantısı gönderimini yasakla Eşzamanlılık alınıyor SimpleX bağlantıları - SimpleX bağlantıları bu grupta yasaklandı + SimpleX bağlantıları yasaklandı Kulaklık Hoparlör Kulaklıklar @@ -2053,14 +2053,14 @@ Ses kapatıldı Yüklendi Sunucu istatistikleri sıfırlanacaktır - bu geri alınamaz! - Erişilebilir sohbet araç çubuğu + Erişilebilir uygulama araç çubukları Görünüm ayarlarından değiştirebilirsiniz. Sohbet listesini değiştir: Sistem modu Erişilebilir sohbet araç çubuğu İçin bilgi gösteriliyor İstatistikler - %s\'den başlayarak.\nTüm veriler cihazınıza özeldir. + %s\'den başlayarak.\nTüm veriler cihazınızda gizli tutulur. Bir proxy aracılığıyla gönderildi Sunucu adresi Yükleme hataları @@ -2158,4 +2158,69 @@ Arşiv Raporu arşivleyelim mi? uçtan uca şifreli olarak gönderilir ve doğrudan mesajlarda kuantum sonrası güvenlik sağlanır.]]> + Veritabanı şifresini okurken hata oluştu + Sohbeti sil + Sohbet silinsin mi? + Bağlantı hazır değil. + Kullanım şartları + daha iyi üstveri gizliliği için. + Sunucuları kaydederken hata oluştu + Şartlar 30 gün sonra etkin operatörler için kabul edilecektir. + Raporu sil + Listeyi değiştir + %s için de geçerli olacaktır.]]> + Üyeler herkes için engellensin mi? + Topluluk kurallarının ihlali + Şartları kabul ederken hata oluştu + Sil + Liste silinsin mi? + Bağlantı güvenliği + İçerik kullanım şartlarını ihlal ediyor + Rapor oluşturulurken hata oluştu + Bağlantı engellendi + Bağlantı sunucu operatörü tarafından engellendi:\n%1$s. + Dosya sunucu operatörü tarafından engellendi:\n%1$s. + %d rapor + Mesaj çok büyük! + Otomatik silinen mesajlar değiştirilsin mi? + Otomatik silinen mesajlar devre dışı bırakılsın mı? + Liste oluştur + Sırayı değiştir + Düzenle + Sosyal medya için + Tek kullanımlık bağlantı oluştur + Örneğin, eğer kişiniz SimpleX Sohbet sunucusundan mesajları alıyorsa, uygulamanız bu mesajları Flux sunucusundan iletecektir. + varsayılan (%s) + Bu üyelerden gelen yeni mesajların hepsi gizlenecektir. + İyileştirilmiş grup performansı + Sohbet zaten var! + %1$s ile bağlısınız.]]> + Konuşma profili %s için: + Üyeler arası doğrudan mesajlaşma yasaklıdır. + Şartlar %s tarihinde etkin operatörler için otomatik olarak kabul edilecektir. + Sohbet mesajlarını cihazınızdan silin. + Her 10 dakikada mesajları kontrol et + Devam et + İyileştirilmiş gizlilik ve güvenlik + Daha hızlı mesaj gönderme. + Bu sohbette üyeler arası doğrudan mesajlaşma yasaklıdır. + Sohbet listesi yüklenirken hata oluştu + Sohbet listesi oluşturulurken hata oluştu + Sohbet listesini güncellerken hata oluştu + Sık kullanılanlar + Konuşmalar + Veritabanını kaydederken hata oluştu + Sohbet sizin için silinecek - bu geri alınamaz! + %s için de geçerli olacaktır.]]> + Bütün moderatörler için + Benim için + Ayarlar kaydedilirken hata oluştu + Günlükleri etkinleştir + Sohbet + Grupların daha hızlı silinmesi. + Sohbet bütün üyeler için silinecek - bu geri alınamaz! + Sunucu eklerken hata oluştu + Onar + Bağlantı onarılsın mı? + Sunucuyu güncellerken hata oluştu diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml index b7b8248b10..311d492892 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml @@ -226,16 +226,16 @@ đang thay đổi địa chỉ… đang thay đổi địa chỉ… Bảng điều khiển trò chuyện - Ứng dụng SimpleX Chat đang hoạt động - Cơ sở dữ liệu SimpleX Chat đã bị xóa - Ứng dụng SimpleX Chat đã được dừng lại. Nếu bạn đã sử dụng cơ sở dữ liệu này trên một thiết bị khác, bạn nên chuyển nó trở lại trước khi bắt đầu ứng dụng. - Cơ sở dữ liệu SimpleX Chat đã được nhập - Ứng dụng SimpleX Chat đã được dừng lại + Kết nối trò chuyện đang hoạt động + Cơ sở dữ liệu trò chuyện đã bị xóa + Kết nối trò chuyện đã được dừng lại. Nếu bạn đã sử dụng cơ sở dữ liệu này trên một thiết bị khác, bạn nên chuyển nó trở lại trước khi khởi động kết nối. + Cơ sở dữ liệu trò chuyện đã được nhập + Kết nối trò chuyện đã được dừng lại đang thay đổi địa chỉ cho %s… Tùy chọn trò chuyện Màu trò chuyện - CƠ SỞ DỮ LIỆU SIMPLEX CHAT - Ứng dụng SimpleX Chat đã được dừng lại + CƠ SỞ DỮ LIỆU TRÒ CHUYỆN + Kết nối trò chuyện đã được dừng lại Cơ sở dữ liệu đã được di chuyển! Các cuộc trò chuyện CÁC CUỘC TRÒ CHUYỆN @@ -248,17 +248,17 @@ Kiểm tra kết nối internet của bạn và thử lại Chủ đề trò chuyện Chế độ màu - Xác minh xóa - Xóa + Xác minh dọn dẹp + Dọn dẹp Nút đóng - Xóa ghi chú riêng tư? + Dọn dẹp ghi chú riêng tư? có màu - Xóa cuộc trò chuyện - Xóa + Dọn dẹp cuộc trò chuyện + Dọn dẹp Sắp ra mắt! Di chuyển từ một thiết bị kháctrên thiết bị mới và quét mã QR.]]> - Xóa - Xóa cuộc trò chuyện? + Dọn dẹp + Dọn dẹp cuộc trò chuyện? Cài đặt cấu hình cho các máy chủ ICE Xác nhận các cài đặt mạng Xác nhận mã truy cập @@ -302,10 +302,10 @@ đang kết nối (lời mời giới thiệu) Kết nối tới máy tính đang ở trong tình trạng không tốt Kết nối đã bị ngắt - Kết nối thông qua đường dẫn / mã QR + Kết nối qua đường dẫn / mã QR Kết nối tới chính bạn? Yêu cầu kết nối đã được gửi! - Kết nối thông qua đường dẫn + Kết nối qua đường dẫn Đang kết nối cuộc gọi Kết nối đã bị ngắt Đang kết nối tới máy tính @@ -318,18 +318,18 @@ Thời gian chờ kết nối Lỗi kết nối Kết nối - Kết nối thông qua đường dẫn? + Kết nối qua đường dẫn? kết nối %1$d kết nối đã được tạo lập Kết nối với %1$s? Lỗi kết nối (AUTH) Kết nối đang kết nối cuộc gọi… - Kết nối thông qua địa chỉ liên lạc? - Kết nối thông qua đường dẫn dùng một lần? + Kết nối qua địa chỉ liên lạc? + Kết nối qua đường dẫn dùng một lần? Liên hệ đã được kiểm tra Các liên hệ - Liên hệ có cho phép + Liên hệ cho phép Liên hệ đã tồn tại liện hệ %1$s đã thay đổi thành %2$s Liên hệ này vẫn chưa được kết nối! @@ -412,7 +412,7 @@ \nQuá trình này có thể mất một vài phút. Mật khẩu cơ sở dữ liệu và xuất dữ liệu Mật khẩu cơ sở dữ liệu khác với mật khẩu được lưu trong Keystore. - Mật khẩu cơ sở dữ liệu là cần thiết để mở ứng dụng SimpleX Chat. + Mật khẩu cơ sở dữ liệu là cần thiết để mở kết nối trò chuyện. Lỗi cơ sở dữ liệu Xóa Xóa địa chỉ? @@ -559,7 +559,7 @@ Bất kỳ ai cũng có thể tạo máy chủ. Tiếp tục Kết nối nhanh hơn với bạn bè. - Cơ sở dữ liệu SimpleX Chat đã được xuất + Cơ sở dữ liệu trò chuyện đã được xuất Lưu trữ các liên hệ để trò chuyện sau. Ngắt kết nối máy tính? Xóa tối đa 20 tin nhắn cùng một lúc. @@ -592,7 +592,7 @@ Không hiển thị lại Tải về tệp tin Đang tải về kho lưu trữ - Hạ cấp và mở SimpleX Chat + Hạ cấp và mở kết nối trò chuyện Tải về Không bật Đã tải về @@ -682,7 +682,7 @@ Lỗi tạo địa chỉ Lỗi chấp nhận yêu cầu liên hệ Lỗi thay đổi địa chỉ - Lỗi xóa cơ sở dữ liệu SimpleX Chat + Lỗi xóa cơ sở dữ liệu trò chuyện Lỗi tạo liên hệ thành viên Lỗi hủy bỏ thay đổi địa chỉ Lỗi tạo đường dẫn nhóm @@ -694,13 +694,13 @@ Lỗi xóa yêu cầu liên hệ Lỗi xóa cơ sở dữ liệu Lỗi xóa kết nối liên hệ đang chờ xử lý - Lỗi xuất cơ sở dữ liệu SimpleX Chat + Lỗi xuất cơ sở dữ liệu trò chuyện Lỗi mã hóa cơ sở dữ liệu Lỗi bật chỉ báo đã nhận! Lỗi xóa ghi chú riêng tư Lỗi xóa hồ sơ người dùng - Lỗi nhập cơ sở dữ liệu SimpleX Chat - Lỗi xuất cơ sở dữ liệu SimpleX Chat + Lỗi nhập cơ sở dữ liệu trò chuyện + Lỗi xuất cơ sở dữ liệu trò chuyện Lỗi tải xuống kho lưu trữ Lỗi khởi tạo WebView. Cập nhật hệ điều hành của bạn lên phiên bản mới. Vui lòng liên hệ với nhà phát triển. \nLỗi: %s @@ -725,8 +725,8 @@ Lỗi lưu máy chủ XFTP Lỗi lưu máy chủ SMP Lỗi gửi tin nhắn - Lỗi khởi động ứng dụng - Lỗi dừng ứng dụng + Lỗi khởi động kết nối trò chuyện + Lỗi dừng kết nối trò chuyện Lỗi hiển thị thông báo, liên hệ với nhà phát triển. Lỗi lưu mật khẩu người dùng Lỗi lưu cài đặt @@ -749,11 +749,11 @@ Lỗi tải lên kho lưu trữ Tập tin đã xuất không tồn tại TẬP TIN - Không thể tải tin nhắn + Không thể tải các cuộc trò chuyện Không tìm thấy tệp - có thể tập tin đã bị xóa và hủy bỏ. Lỗi tệp Xuất chủ đề - Không thể tải tin nhắn + Không thể tải cuộc trò chuyện Tập tin Nhanh chóng và không cần phải đợi người gửi hoạt động! Không tìm thấy tệp @@ -776,7 +776,7 @@ Tệp sẽ được nhận khi liên hệ của bạn hoạt động, vui lòng chờ hoặc kiểm tra lại sau! Trạng thái tệp: %s Lấp đầy - CƠ SỞ DỮ LIỆU SIMPLEX CHAT + CƠ SỞ DỮ LIỆU TRÒ CHUYỆN Lỗi chuyển đổi hồ sơ Lọc các cuộc hội thoại chưa đọc và các cuộc hội thoại yêu thích. Cuối cùng, chúng ta đã có chúng! 🚀 @@ -956,7 +956,7 @@ MÀU SẮC GIAO DIỆN đã được mời Đường dẫn không hợp lệ - tác vụ trò chuyện không hợp lệ + cuộc trò chuyện không hợp lệ dữ liệu không hợp lệ định dạng tin nhắn không hợp lệ Mời @@ -1066,7 +1066,7 @@ Tin nhắn Lỗi keychain đã rời - đã được mời thông qua đường dẫn nhóm của bạn + đã được mời qua đường dẫn nhóm của bạn thành viên đã rời Nó cho phép việc có các kết nối ẩn danh mà không có bất kỳ dữ liệu chung nào giữa chúng trong một hồ sơ trò chuyện @@ -1244,7 +1244,7 @@ Mở SimpleX Chat để chấp nhận cuộc gọi Mở Cài đặt Safari / Trang Web / Mic, rồi chọn Cho phép với localhost. Mã truy cập - Mở kết nối trò chuyện + Mở cuộc trò chuyện Mở vị trí tệp Sử dụng từ máy tính trong ứng dụng di động và quét mã QR.]]> Hoặc dán đường dẫn lưu trữ @@ -1393,7 +1393,7 @@ Tiếp tục Không thể tải văn bản về các điều kiện hiện tại, bạn có thể xem xét các điều kiện thông qua đường dẫn này: Các điều kiện sử dụng - Cho phép flux + Sử dụng Flux trong cài đặt Mạng & máy chủ để bảo mật siêu dữ liệu tốt hơn. Các điều kiện sẽ được chấp nhận vào: %s. Lỗi thêm máy chủ Lỗi cập nhật máy chủ @@ -2007,7 +2007,7 @@ Đây là đường dẫn dùng một lần của riêng bạn! Cài đặt này áp dụng cho các tin nhắn trong hồ sơ trò chuyện hiện tại của bạn Đây là địa chỉ SimpleX của riêng bạn! - Để kết nối thông qua đường dẫn + Để kết nối qua đường dẫn Để bảo vệ đường dẫn của bạn khỏi bị thay thế, bạn có thể so sánh các mã bảo mật liên lạc. Để bảo vệ địa chỉ IP của bạn, định tuyến riêng tư sử dụng các máy chủ SMP của bạn để gửi tin nhắn. Cách ly truyền tải @@ -2090,7 +2090,7 @@ Sử dụng các dịch vụ .onion Xác minh mã bảo mật Sử dụng hồ sơ ẩn danh mới - thông qua %1$s + qua %1$s Sử dụng proxy SOCKS Sử dụng SimpleX Chat Sử dụng cho các kết nối mới @@ -2103,7 +2103,7 @@ Sử dụng từ máy tính Sử dụng định tuyến riêng tư với các máy chủ không xác định khi địa chỉ IP không được bảo vệ. Xem các điều kiện - thông qua đường dẫn dùng một lần + qua đường dẫn dùng một lần Các tin nhắn thoại bị cấm. Lịch sử hữu hình Cuộc gọi video @@ -2116,9 +2116,9 @@ Video bật Các tin nhắn thoại bị cấm trong cuộc trò chuyện này. Tin nhắn thoại - Thông qua giao thức kháng lượng tử an toàn. - thông qua đường dẫn nhóm - Thông qua trình duyệt + Qua giao thức kháng lượng tử an toàn. + qua đường dẫn nhóm + Qua trình duyệt video Xem các điều kiện đã được cập nhật Xem sự cố @@ -2131,7 +2131,7 @@ thông qua relay Tin nhắn thoại cuộc gọi video (không được mã hóa đầu cuối) - thông qua đường dẫn địa chỉ liên lạc + qua đường dẫn địa chỉ liên lạc Video Chúng tôi không lưu bất kỳ liên hệ hay tin nhắn nào của bạn (một khi đã được gửi) trên các máy chủ. Website @@ -2333,4 +2333,23 @@ đã từ chối Thiết lập giờ hết hạn cho tin nhắn trong các cuộc trò chuyện. Tên các tệp tin đa phương tiện riêng tư. + Lỗi đọc mật khẩu cơ sở dữ liệu + Tất cả các tin nhắn mới từ những thành viên này sẽ bị ẩn! + Chặn các thành viên cho tất cả? + các kiểm duyệt viên + Các tin nhắn từ những thành viên này sẽ được hiển thị! + Các thành viên sẽ bị xóa khỏi nhóm - việc này không thể được hoàn tác! + Các thành viên sẽ bị xóa khỏi cuộc trò chuyện - việc này không thể được hoàn tác! + Không thể đọc mật khẩu trong Keystore. Điều này có thể xảy ra sau bản cập nhật hệ thống không tương thích với ứng dụng. Nếu không phải như vậy, xin vui lòng liên hệ với các nhà phát triển. + đang chờ xử lý + Các điều kiện đã được cập nhật + Không thể đọc mật khẩu trong Keystore, xin vui lòng nhập thủ công. Điều này có thể xảy ra sau khi bản cập nhật hệ thống không tương thích với ứng dụng. Nếu không phải như vậy, vui lòng liên hệ với các nhà phát triển. + đang chờ phê duyệt + Bỏ chặn các thành viên cho tất cả? + Xóa các thành viên? + Chính sách quyền riêng tư và các điều kiện sử dụng. + Bằng việc sử dụng SimpleX Chat, bạn đồng ý:\n- chỉ gửi nội dung hợp pháp trong các nhóm công khai.\n- tôn trọng những người dùng khác - không gửi tin rác. + Các cuộc trò chuyện riêng tư, nhóm và liên hệ của bạn không thể truy cập được đối với các bên vận hành máy chủ. + Chấp nhận + Định cấu hình các bên vận hành máy chủ diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index 67faae4943..24abd90a31 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -2116,21 +2116,21 @@ 创建一次性链接 用于社交媒体 或者私下分享 - 服务器运营者 - 网络运营者 - 30 天后将接受已启用的运营者的条款。 + 服务器运营方 + 网络运营方 + 30 天后将接受已启用的运营方的条款。 继续 稍后审阅 - 选择要使用的网络运营者。 + 选择要使用的网络运营方。 更新 你可以通过设置配置服务器。 - %s.]]> - 将于下列日期自动接受已启用的运营者的条款:%s。 + %s.]]> + 将于下列日期自动接受已启用的运营方的条款:%s。 预设服务器 你的服务器 接受条款的将来日期为:%s。 - 网络运营者 - 运营者 + 网络运营方 + 运营方 %s 台服务器 网站 无法加载当前条款文本,你可以通过此链接审阅条款: @@ -2147,17 +2147,17 @@ 用于消息 打开更改 打开条款 - 运营者服务器 - 已添加服务器到运营者 %s - 服务器运营者已更改。 + 运营方服务器 + 已添加服务器到运营方 %s + 服务器运营方已更改。 服务器协议已更改。 透明度 网络去中心化 - 应用中的第二个预设运营者! + 应用中的第二个预设运营方! 改进了聊天导航 查看更新后的条款 比如,如果你通过 SimpleX 服务器收到消息,应用会通过 Flux 服务器传送它们。 - 应用通过在每个对话中使用不同运营者保护你的隐私。 + 应用通过在每个对话中使用不同运营方保护你的隐私。 接受条款 模糊 地址或一次性链接? @@ -2167,13 +2167,13 @@ 已接受条款 应用工具栏 仅用于一名联系人
    - 面对面或通过任何消息应用分享.]]> - %s.]]> - %s.]]> - %s.]]> - %s.]]> + %s.]]> + %s.]]> + %s.]]> + %s.]]> %s的服务器,请接受使用条款。]]> - %s.]]> - 开启 flux + %s.]]> + 在“网络&服务器”设置中启用 Flux,更好地保护元数据隐私。 接受条款出错 为了更好的元数据隐私。 添加服务器出错 @@ -2189,8 +2189,8 @@ 此消息被删除或尚未收到。 连接达到了未送达消息上限,你的联系人可能处于离线状态。 为了防止链接被替换,你可以比较联系人安全代码。 - 你可以在“网络和服务器”设置中配置运营者。 - 接受运营者条款的日期:%s + 你可以在“网络和服务器”设置中配置运营方。 + 接受运营方条款的日期:%s 远程移动设备 或者导入压缩文件 小米设备:请在系统设置中开启“自动启动”让通知正常工作。]]> @@ -2228,10 +2228,10 @@ 聊天 将从聊天中删除成员 - 此操作无法撤销! 请减小消息尺寸并再次发送。 - 当启用了超过一个运营者时,没有一个运营者拥有了解谁和谁联络的元数据。 + 当启用了超过一个运营方时,没有一个运营方拥有了解谁和谁联络的元数据。 已接受邀请 被请求连接 - 关于运营者 + 关于运营方 SimpleX Chat 和 Flux 达成协议将 Flux 运营的服务器包括在应用中。 修复 修复连接? @@ -2297,9 +2297,9 @@ %d 个举报 垃圾信息 连接被阻止 - 连接被服务器运营者阻止:\n%1$s. + 连接被服务器运营方阻止:\n%1$s. 内容违反使用条款 - 文件被服务器运营者阻止:\n%1$s. + 文件被服务器运营方阻止:\n%1$s. 询问 @@ -2360,4 +2360,9 @@ 为所有其他成员解封这些成员吗? moderators 将从聊天中移除这些成员 — 此操作无法撤销! + 隐私政策和使用条款。 + 接受 + 使用 SimpleX Chat 代表您同意:\n- 在公开群中只发送合法内容\n- 尊重其他用户 – 没有垃圾信息。 + 服务器运营方无法访问私密聊天、群组和你的联系人。 + 配置服务器运营方 From 66273790e626b0f9141ab2e80d725941a76ba0a6 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 31 Mar 2025 19:38:54 +0100 Subject: [PATCH 496/567] website: translations (#5792) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (Russian) Currently translated at 100.0% (258 of 258 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ru/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (258 of 258 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ * Translated using Weblate (Japanese) Currently translated at 98.8% (255 of 258 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ja/ * Translated using Weblate (French) Currently translated at 100.0% (258 of 258 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/fr/ * Translated using Weblate (Spanish) Currently translated at 100.0% (258 of 258 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/es/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (258 of 258 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/pt_BR/ * Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 10.8% (28 of 258 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hant/ * Translated using Weblate (Japanese) Currently translated at 100.0% (258 of 258 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ja/ --------- Co-authored-by: noname Co-authored-by: summoner001 Co-authored-by: Miyu Sakatsuki Co-authored-by: Farias França --- website/langs/es.json | 8 +++++--- website/langs/fr.json | 8 +++++--- website/langs/hu.json | 28 ++++++++++++++-------------- website/langs/ja.json | 8 +++++--- website/langs/pt_BR.json | 12 +++++++----- website/langs/zh_Hant.json | 31 ++++++++++++++++++++++++++++++- 6 files changed, 66 insertions(+), 29 deletions(-) diff --git a/website/langs/es.json b/website/langs/es.json index b88a592ac4..56055abf67 100644 --- a/website/langs/es.json +++ b/website/langs/es.json @@ -244,15 +244,17 @@ "signing-key-fingerprint": "Huella digital de la clave de firma (SHA-256)", "releases-to-this-repo-are-done-1-2-days-later": "Las versiones aparecen varios días más tarde en este repositorio", "comparison-section-list-point-4a": "Los servidores de retransmisión no pueden comprometer la encriptación e2e. Para evitar posibles ataques, verifique el código de seguridad mediante un canal alternativo", - "hero-overlay-3-title": "Evaluación de la seguridad", + "hero-overlay-3-title": "Evaluaciones de seguridad", "hero-overlay-card-3-p-2": "Trail of Bits revisó la criptografía y los componentes de red de la plataforma SimpleX en noviembre de 2022. Más información.", "jobs": "Únete al equipo", - "hero-overlay-3-textlink": "Evaluación de la seguridad", + "hero-overlay-3-textlink": "Evaluaciones de seguridad", "hero-overlay-card-3-p-1": "Trail of Bits es una consultora de seguridad y tecnología líder cuyos clientes incluyen grandes tecnológicas, agencias gubernamentales e importantes proyectos de blockchain.", "docs-dropdown-9": "Descargas", "please-enable-javascript": "Habilita JavaScript para ver el código QR.", "please-use-link-in-mobile-app": "Usa el enlace en la apliación móvil", "docs-dropdown-10": "Transparencia", "docs-dropdown-11": "FAQ", - "docs-dropdown-12": "Seguridad" + "docs-dropdown-12": "Seguridad", + "docs-dropdown-14": "SimpleX para empresas", + "hero-overlay-card-3-p-3": "Trail of Bits revisó el diseño criptográfico de los protocolos de red SimpleX en julio de 2024." } diff --git a/website/langs/fr.json b/website/langs/fr.json index 61be2c8621..ba2c4fb00c 100644 --- a/website/langs/fr.json +++ b/website/langs/fr.json @@ -245,15 +245,17 @@ "f-droid-org-repo": "Dépot F-Droid.org", "stable-versions-built-by-f-droid-org": "Versions stables créées par F-Droid.org", "comparison-section-list-point-4a": "Les relais SimpleX ne peuvent pas compromettre le chiffrement e2e. Vérifier le code de sécurité pour limiter les attaques sur le canal hors bande", - "hero-overlay-3-title": "Évaluation de sécurité", + "hero-overlay-3-title": "Évaluation de la sécurité", "hero-overlay-card-3-p-2": "Trail of Bits a examiné les composants cryptographiques et réseau de la plateforme SimpleX en novembre 2022. En savoir plus.", "jobs": "Rejoignez notre équipe", - "hero-overlay-3-textlink": "Évaluation de sécurité", + "hero-overlay-3-textlink": "Évaluations de la sécurité", "hero-overlay-card-3-p-1": "Trail of Bits est un cabinet leader dans le secteur de la sécurité et des technologies qui compte parmi ses clients des grandes entreprises de la tech, des agences gouvernementales et d'importants projets de blockchain.", "docs-dropdown-9": "Téléchargements", "please-enable-javascript": "Veuillez activer JavaScript pour voir le code QR.", "please-use-link-in-mobile-app": "Veuillez utiliser le lien dans l'application mobile", "docs-dropdown-10": "Transparence", "docs-dropdown-12": "Sécurité", - "docs-dropdown-11": "FAQ" + "docs-dropdown-11": "FAQ", + "hero-overlay-card-3-p-3": "Trail of Bits a examiné la conception cryptographique des protocoles réseau SimpleX en juillet 2024.", + "docs-dropdown-14": "SimpleX pour les entreprises" } diff --git a/website/langs/hu.json b/website/langs/hu.json index ae9ee1d12b..46ad9668cb 100644 --- a/website/langs/hu.json +++ b/website/langs/hu.json @@ -11,7 +11,7 @@ "simplex-explained-tab-1-text": "1. Felhasználói élmény", "simplex-explained-tab-2-text": "2. Hogyan működik", "simplex-explained-tab-3-text": "3. Mit látnak a kiszolgálók", - "simplex-explained-tab-1-p-1": "Létrehozhat kapcsolatokat és csoportokat, valamint kétirányú beszélgetéseket folytathat, ugyanúgy mint bármely más üzenetváltón.", + "simplex-explained-tab-1-p-1": "Létrehozhat kapcsolatokat és csoportokat, valamint kétirányú beszélgetéseket folytathat, ugyanúgy mint bármely más üzenetváltó-alkalmazásban.", "simplex-explained-tab-1-p-2": "Hogyan működhet egyirányú üzenet-sorballítással és felhasználói profilazonosítók nélkül?", "simplex-explained-tab-2-p-1": "Minden kapcsolathoz két különböző üzenetküldési sorbaállítást használ a különböző kiszolgálókon keresztül történő üzenetküldéshez és -fogadáshoz.", "simplex-explained-tab-2-p-2": "A kiszolgálók csak egyirányú üzeneteket továbbítanak, anélkül, hogy teljes képet kapnának a felhasználók beszélgetéseiről vagy kapcsolatairól.", @@ -20,12 +20,12 @@ "smp-protocol": "SMP-protokoll", "chat-protocol": "Csevegésprotokoll", "donate": "Támogatás", - "copyright-label": "© 2020-2024 SimpleX | Nyílt forráskódú projekt", + "copyright-label": "© 2020-2025 SimpleX | Nyílt forráskódú projekt", "simplex-chat-protocol": "A SimpleX Chat-protokoll", "terminal-cli": "Terminál CLI", "terms-and-privacy-policy": "Adatvédelmi irányelvek", "hero-header": "Újradefiniált adatvédelem", - "hero-subheader": "Az első üzenetváltó
    felhasználói azonosítók nélkül", + "hero-subheader": "Az első üzenetváltó-alkalmazás
    felhasználói azonosítók nélkül", "hero-p-1": "Más alkalmazások felhasználói azonosítókkal rendelkeznek: Signal, Matrix, Session, Briar, Jami, Cwtch, stb.
    A SimpleX nem, még véletlenszerű számokkal sem.
    Ez radikálisan javítja az adatvédelmet.", "hero-overlay-1-textlink": "Miért ártanak a felhasználói azonosítók az adatvédelemnek?", "hero-overlay-2-textlink": "Hogyan működik a SimpleX?", @@ -111,10 +111,10 @@ "privacy-matters-overlay-card-3-p-1": "Mindenkinek törődnie kell a magánélet és a kommunikáció biztonságával — az ártalmatlan beszélgetések veszélybe sodorhatják, még akkor is, ha nincs semmi rejtegetnivalója.", "privacy-matters-overlay-card-3-p-2": "Az egyik legmegdöbbentőbb a Mohamedou Ould Salahi memoárjában leírt és az „A mauritániai” c. filmben bemutatott történet. Őt bírósági tárgyalás nélkül a guantánamói táborba zárták, és ott kínozták 15 éven át, miután egy afganisztáni rokonát telefonon felhívta, akit azzal gyanúsítottak a hatóságok, hogy köze van a 9/11-es merényletekhez, holott Salahi az előző 10 évben Németországban élt.", "privacy-matters-overlay-card-3-p-3": "Átlagos embereket letartóztatnak azért, amit online megosztanak, még „névtelen” fiókjaikon keresztül is, még demokratikus országokban is.", - "privacy-matters-overlay-card-3-p-4": "Nem elég csak egy végpontok között titkosított üzenetváltó alkalmazást használnunk, mindannyiunknak olyan üzenetváltó alkalmazásokat kell használnunk, amelyek védik a személyes partnereink magánéletét — akikkel kapcsolatban állunk.", + "privacy-matters-overlay-card-3-p-4": "Nem elég csak egy végpontok között titkosított üzenetváltó-alkalmazást használnunk, mindannyiunknak olyan üzenetváltó-alkalmazásokat kell használnunk, amelyek védik a személyes partnereink magánéletét — akikkel kapcsolatban állunk.", "simplex-unique-overlay-card-1-p-1": "Más üzenetküldő platformoktól eltérően a SimpleX nem rendel azonosítókat a felhasználókhoz. Nem támaszkodik telefonszámokra, tartomány-alapú címekre (mint az e-mail, XMPP vagy a Matrix), felhasználónevekre, nyilvános kulcsokra vagy akár véletlenszerű számokra a felhasználók azonosításához — nem tudjuk, hogy hányan használják a SimpleX-kiszolgálóinkat.", "simplex-unique-overlay-card-1-p-2": "Az üzenetek kézbesítéséhez a SimpleX az egyirányú üzenet várakoztatást használ páronkénti névtelen címekkel, külön a fogadott és külön az elküldött üzenetek számára, általában különböző kiszolgálókon keresztül. A SimpleX használata olyan, mintha minden egyes kapcsolatnak más-más “eldobható” e-mail-címe vagy telefonja lenne és nem kell ezeket gondosan kezelni.", - "simplex-unique-overlay-card-1-p-3": "Ez a kialakítás megvédi annak titkosságát, hogy kivel kommunikál, elrejtve azt a SimpleX platform kiszolgálói és a megfigyelők elől. IP-címének a kiszolgálók elől való elrejtéséhez azt teheti meg, hogy Toron keresztül kapcsolódik a SimpleX-kiszolgálókhoz.", + "simplex-unique-overlay-card-1-p-3": "Ez a kialakítás megvédi annak titkosságát, hogy kivel kommunikál, elrejtve azt a SimpleX-platform kiszolgálói és a megfigyelők elől. IP-címének a kiszolgálók elől való elrejtéséhez azt teheti meg, hogy Toron keresztül kapcsolódik a SimpleX-kiszolgálókhoz.", "simplex-unique-overlay-card-2-p-1": "Mivel ön nem rendelkezik azonosítóval a SimpleX-platformon, senki sem tud kapcsolatba lépni önnel, hacsak nem oszt meg egy egyszeri vagy ideiglenes felhasználói címet, például QR-kódot vagy hivatkozást.", "simplex-unique-overlay-card-2-p-2": "Még a nem kötelező felhasználói cím esetében is, bár spam kapcsolatfelvételi kérések küldésére használható, megváltoztathatja vagy teljesen törölheti azt anélkül, hogy elveszítené a meglévő kapcsolatait.", "simplex-unique-overlay-card-3-p-1": "A SimpleX Chat az összes felhasználói adatot kizárólag a klienseken tárolja egy hordozható titkosított adatbázis-formátumban, amely exportálható és átvihető bármely más támogatott eszközre.", @@ -122,21 +122,21 @@ "simplex-unique-overlay-card-3-p-3": "A föderált hálózatok kiszolgálóitól (e-mail, XMPP vagy Matrix) eltérően a SimpleX-kiszolgálók nem tárolják a felhasználói fiókokat, csak továbbítják az üzeneteket, így védve mindkét fél magánéletét.", "simplex-unique-overlay-card-3-p-4": "A küldött és a fogadott kiszolgálóforgalom között nincsenek közös azonosítók vagy titkosított szövegek — ha bárki megfigyeli, nem tudja könnyen megállapítani, hogy ki kivel kommunikál, még akkor sem, ha a TLS-t kompromittálják.", "simplex-unique-overlay-card-4-p-1": "Használhatja a SimpleXet a saját kiszolgálóival, és továbbra is kommunikálhat azokkal, akik az általunk biztosított, előre konfigurált kiszolgálókat használják.", - "simplex-unique-overlay-card-4-p-2": "A SimpleX platform nyitott protokollt használ és SDK-t biztosít a chatbotok létrehozásához, lehetővé téve olyan szolgáltatások megvalósítását, amelyekkel a felhasználók a SimpleX Chat alkalmazásokon keresztül léphetnek kapcsolatba — mi már nagyon várjuk, hogy milyen SimpleX szolgáltatásokat készítenek a lelkes közreműködők.", + "simplex-unique-overlay-card-4-p-2": "A SimpleX-platform nyitott protokollt használ és SDK-t biztosít a chatbotok létrehozásához, lehetővé téve olyan szolgáltatások megvalósítását, amelyekkel a felhasználók a SimpleX Chat alkalmazásokon keresztül léphetnek kapcsolatba — mi már nagyon várjuk, hogy milyen SimpleX szolgáltatásokat készítenek a lelkes közreműködők.", "simplex-unique-overlay-card-4-p-3": "Ha a SimpleX-platformra való fejlesztést fontolgatja, például a SimpleX-alkalmazások felhasználóinak szánt chatbotot, vagy a SimpleX Chat könyvtárbotjának integrálását más mobilalkalmazásba, lépjen velünk kapcsolatba, ha bármilyen tanácsot vagy támogatást szeretne kapni.", - "simplex-unique-card-1-p-1": "A SimpleX védi az ön profiljához tartozó kapcsolatait és metaadatait, elrejtve azokat a SimpleX platform kiszolgálói és a megfigyelők elől.", + "simplex-unique-card-1-p-1": "A SimpleX védi az ön profiljához tartozó kapcsolatokat és metaadatokat, elrejtve azokat a SimpleX-platform kiszolgálói és a megfigyelők elől.", "simplex-unique-card-1-p-2": "Minden más létező üzenetküldő platformtól eltérően a SimpleX nem rendelkezik a felhasználókhoz rendelt azonosítókkal — még véletlenszerű számokkal sem.", - "simplex-unique-card-2-p-1": "Mivel a SimpleX platformon nincs azonosítója vagy állandó címe, senki sem tud kapcsolatba lépni önnel, hacsak nem oszt meg egy egyszeri vagy ideiglenes felhasználói címet, például QR-kódot vagy hivatkozást.", + "simplex-unique-card-2-p-1": "Mivel a SimpleX-platformon nincs azonosítója vagy állandó címe, senki sem tud kapcsolatba lépni önnel, hacsak nem oszt meg egy egyszeri vagy ideiglenes felhasználói címet, például QR-kódot vagy hivatkozást.", "simplex-unique-card-3-p-1": "A SimpleX Chat az összes felhasználói adatot kizárólag a klienseken tárolja egy hordozható titkosított adatbázis-formátumban —, amely exportálható és átvihető bármely más támogatott eszközre.", "simplex-unique-card-3-p-2": "A végpontok között titkosított üzenetek átmenetileg a SimpleX továbbítókiszolgálóin tartózkodnak, amíg be nem érkeznek a címzetthez, majd automatikusan véglegesen törlődnek onnan.", "simplex-unique-card-4-p-1": "A SimpleX hálózat teljesen decentralizált és független bármely kriptopénztől vagy bármely más platformtól, kivéve az internetet.", "simplex-unique-card-4-p-2": "Használhatja a SimpleXet a saját kiszolgálóival vagy az általunk biztosított kiszolgálókkal, és továbbra is kapcsolódhat bármely felhasználóhoz.", - "join": "Csatlakozás", - "we-invite-you-to-join-the-conversation": "Meghívjuk Önt, hogy csatlakozzon a beszélgetéshez", + "join": "Csatlakozzon a közösségeinkhez", + "we-invite-you-to-join-the-conversation": "Meghívjuk Önt, hogy csatlakozzon a beszélgetésekhez", "join-the-REDDIT-community": "Csatlakozzon a REDDIT közösséghez", "join-us-on-GitHub": "Csatlakozzon hozzánk a GitHubon", "donate-here-to-help-us": "Adományozzon és segítsen nekünk", - "sign-up-to-receive-our-updates": "Regisztráljon az oldalra, hogy megkapja frissítéseinket", + "sign-up-to-receive-our-updates": "Regisztráljon a hírleveleinkre, hogy ne maradjon le semmiről", "enter-your-email-address": "Adja meg az e-mail-címét", "get-simplex": "A SimpleX számítógép-alkalmazásának letöltése", "why-simplex-is": "A SimpleX mitől", @@ -166,7 +166,7 @@ "copy-the-command-below-text": "másolja be az alábbi parancsot, és használja a csevegésben:", "privacy-matters-section-header": "Miért számít az adatvédelem", "privacy-matters-section-subheader": "A metaadatok védelmének megőrzése — kivel beszélget — megvédi a következőktől:", - "privacy-matters-section-label": "Győződjön meg arról, hogy az üzenetváltó amit használ nem fér hozzá az adataidhoz!", + "privacy-matters-section-label": "Győződjön meg arról, hogy az üzenetváltó-alkalmazás amit használ nem fér hozzá az adataidhoz!", "simplex-private-section-header": "Mitől lesz a SimpleX privát", "simplex-network-section-header": "SimpleX hálózat", "simplex-network-section-desc": "A Simplex Chat a P2P és a föderált hálózatok előnyeinek kombinálásával biztosítja a legjobb adatvédelmet.", @@ -197,7 +197,7 @@ "comparison-section-list-point-5": "Nem védi a felhasználók metaadatait", "comparison-section-list-point-6": "Bár a P2P elosztott, de nem föderált - egyetlen hálózatként működnek", "comparison-section-list-point-7": "A P2P-hálózatoknak vagy van egy központi hitelesítője, vagy az egész hálózat kompromittálódhat", - "see-here": "lásd itt", + "see-here": "tekintse meg itt", "guide-dropdown-1": "Gyors indítás", "guide-dropdown-2": "Üzenetek küldése", "guide-dropdown-3": "Titkos csoportok", @@ -207,7 +207,7 @@ "guide-dropdown-7": "Adatvédelem és biztonság", "guide-dropdown-8": "Alkalmazás beállításai", "guide": "Útmutató", - "docs-dropdown-1": "SimpleX platform", + "docs-dropdown-1": "SimpleX-platform", "docs-dropdown-2": "Android fájlok elérése", "docs-dropdown-3": "Hozzáférés a csevegési adatbázishoz", "docs-dropdown-8": "SimpleX jegyzékszolgáltatás", diff --git a/website/langs/ja.json b/website/langs/ja.json index 6f994b59da..c24883fb00 100644 --- a/website/langs/ja.json +++ b/website/langs/ja.json @@ -246,13 +246,15 @@ "simplex-private-5-title": "何レイヤーもの
    コンテンツパディング", "hero-overlay-card-3-p-1": "Trail of Bitsは、大手ハイテク企業、政府機関、主要なブロックチェーン・プロジェクトなどを顧客に持つ、セキュリティとテクノロジーの大手コンサルタント会社です。", "jobs": "チームに参加する", - "hero-overlay-3-textlink": "セキュリティ監査", - "hero-overlay-3-title": "セキュリティ監査", + "hero-overlay-3-textlink": "セキュリティ評価", + "hero-overlay-3-title": "セキュリティ評価", "hero-overlay-card-3-p-2": "Trail of Bitsは2022年11月にSimpleXプラットフォームの暗号とネットワークのコンポーネントを検証しました。詳しくは お知らせをご覧ください。", "docs-dropdown-9": "ダウンロード", "please-enable-javascript": "QRコードを表示するためにJavaScriptを有効にしてください。", "please-use-link-in-mobile-app": "このリンクをモバイルアプリで使用してください", "docs-dropdown-10": "透明性", "docs-dropdown-11": "よくある質問", - "docs-dropdown-12": "セキュリティ" + "docs-dropdown-12": "セキュリティ", + "docs-dropdown-14": "ビジネス向けSimpleX", + "hero-overlay-card-3-p-3": "Trail of Bits は 2024 年 7 月に SimpleX ネットワーク プロトコルの暗号設計をレビューしました。" } diff --git a/website/langs/pt_BR.json b/website/langs/pt_BR.json index 73095c6db2..b25888591e 100644 --- a/website/langs/pt_BR.json +++ b/website/langs/pt_BR.json @@ -11,7 +11,7 @@ "simplex-explained-tab-1-p-1": "Você pode criar contatos e grupos e ter conversas bidirecionais, como em qualquer outro mensageiro.", "simplex-explained-tab-2-p-1": "Para cada conexão, são usadas duas filas de mensagens separadas para enviar e receber mensagens por meio de servidores diferentes.", "hero-p-1": "Outros aplicativos possuem IDs de usuário: Signal, Matrix, Session, Briar, Jami, Cwtch, etc.
    O SimpleX não tem, nem mesmo números aleatórios.
    Isso melhora radicalmente a sua privacidade.", - "terms-and-privacy-policy": "Termos e Política de Privacidade", + "terms-and-privacy-policy": "Termos de serviço & Política de Privacidade", "hero-2-header-desc": "O vídeo mostra como você se conecta com seu amigo através do QR code de uso único dele, pessoalmente ou via link de vídeo. Você também pode se conectar compartilhando um link de convite.", "feature-7-title": "Armazenamento do aplicativo criptografado portátil — mova o perfil para outro dispositivo", "simplex-explained": "Explicação do SimpleX", @@ -242,9 +242,9 @@ "simplex-chat-repo": "Repositório Simplex Chat", "f-droid-org-repo": "Repositório F-Droid.org", "stable-versions-built-by-f-droid-org": "Versões estáveis criadas por F-Droid.org", - "releases-to-this-repo-are-done-1-2-days-later": "Os lançamentos para este repositório são feitos 1-2 dias depois", - "hero-overlay-3-textlink": "Avaliação Segura", - "hero-overlay-3-title": "Avaliação Segura", + "releases-to-this-repo-are-done-1-2-days-later": "Os lançamentos para este repositório são feitos 1 ou 2 dias depois", + "hero-overlay-3-textlink": "Avaliações de segurança", + "hero-overlay-3-title": "Avaliações de segurança", "hero-overlay-card-3-p-1": "Trail of Bits é uma consultoria líder em segurança e tecnologia cujos clientes incluem grandes empresas de tecnologia, agências governamentais e grandes projetos de blockchain.", "hero-overlay-card-3-p-2": "Trail of Bits analisou a criptografia da plataforma SimpleX e os componentes de rede em novembro de 2022. Leia mais em o anúncio.", "f-droid-page-f-droid-org-repo-section-text": "Os repositórios SimpleX Chat e F-Droid.org assinam compilações com chaves diferentes. Para mudar, exporte o banco de dados de bate-papo e reinstale o aplicativo.", @@ -254,5 +254,7 @@ "docs-dropdown-9": "Baixar", "docs-dropdown-11": "FAQ", "docs-dropdown-10": "Transparência", - "docs-dropdown-12": "Segurança" + "docs-dropdown-12": "Segurança", + "hero-overlay-card-3-p-3": "Trail of Bits revisou o design criptografico das redes utilizadas pelo SimpleX em julho de 2024", + "docs-dropdown-14": "SimpleX para negócios" } diff --git a/website/langs/zh_Hant.json b/website/langs/zh_Hant.json index 0967ef424b..d95c6259c7 100644 --- a/website/langs/zh_Hant.json +++ b/website/langs/zh_Hant.json @@ -1 +1,30 @@ -{} +{ + "home": "家", + "developers": "開發人員", + "reference": "參考", + "blog": "博客", + "features": "特徵", + "why-simplex": "為什麼選擇SimpleX", + "simplex-privacy": "SimpleX 隱私", + "simplex-network": "SimpleX 網路", + "simplex-explained": "Simplex 解釋", + "simplex-explained-tab-1-text": "1. 用戶體驗", + "simplex-explained-tab-2-text": "2. 它是如何工作的", + "simplex-explained-tab-3-text": "3. 伺服器可以看到什麼", + "terminal-cli": "終端 CLI", + "hero-header": "重新定義隱私", + "hero-subheader": "第一個沒有 User ID 的 Messenger", + "simplex-explained-tab-3-p-2": "用戶可以通過使用 Tor 訪問伺服器來進一步提高元數據隱私,防止按 IP 位址進行序列化。", + "smp-protocol": "SMP 協定", + "simplex-explained-tab-2-p-2": "伺服器僅以一種方式傳遞消息,而無法全面瞭解使用者的對話或連接。", + "simplex-explained-tab-2-p-1": "對於每個連接,您可以使用兩個單獨的消息佇列通過不同的伺服器發送和接收消息。", + "chat-protocol": "聊天協定", + "copyright-label": "© 2020-2024 單工 |開源專案", + "donate": "捐", + "simplex-explained-tab-1-p-1": "您可以創建聯繫人和群組,並進行雙向對話,就像在任何其他 Messenger 中一樣。", + "simplex-explained-tab-1-p-2": "它如何在沒有使用者配置檔標識符的情況下使用單向佇列?", + "simplex-explained-tab-3-p-1": "伺服器對每個佇列都有單獨的匿名憑證,並且不知道它們屬於哪些使用者。", + "chat-bot-example": "聊天機器人示例", + "simplex-chat-protocol": "SimpleX Chat 協定", + "terms-and-privacy-policy": "隱私策略" +} From 4b6d1d4585c36034385b2ea4f82a191daa1fce8c Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 31 Mar 2025 20:09:46 +0100 Subject: [PATCH 497/567] 6.3.1: ios 270, android 281, desktop 97 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++++----------- apps/multiplatform/gradle.properties | 8 ++--- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 060f0effe8..366bfd167a 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -1963,7 +1963,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 269; + CURRENT_PROJECT_VERSION = 270; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1988,7 +1988,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES_THIN; - MARKETING_VERSION = 6.3; + MARKETING_VERSION = 6.3.1; OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000"; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; @@ -2013,7 +2013,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 269; + CURRENT_PROJECT_VERSION = 270; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2038,7 +2038,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3; + MARKETING_VERSION = 6.3.1; OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000"; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; @@ -2055,11 +2055,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 269; + CURRENT_PROJECT_VERSION = 270; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.3; + MARKETING_VERSION = 6.3.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2075,11 +2075,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 269; + CURRENT_PROJECT_VERSION = 270; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.3; + MARKETING_VERSION = 6.3.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2100,7 +2100,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 269; + CURRENT_PROJECT_VERSION = 270; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2115,7 +2115,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3; + MARKETING_VERSION = 6.3.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2137,7 +2137,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 269; + CURRENT_PROJECT_VERSION = 270; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2152,7 +2152,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3; + MARKETING_VERSION = 6.3.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2174,7 +2174,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 269; + CURRENT_PROJECT_VERSION = 270; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2200,7 +2200,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3; + MARKETING_VERSION = 6.3.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2225,7 +2225,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 269; + CURRENT_PROJECT_VERSION = 270; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2251,7 +2251,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3; + MARKETING_VERSION = 6.3.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2276,7 +2276,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 269; + CURRENT_PROJECT_VERSION = 270; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2291,7 +2291,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.3; + MARKETING_VERSION = 6.3.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2310,7 +2310,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 269; + CURRENT_PROJECT_VERSION = 270; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2325,7 +2325,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.3; + MARKETING_VERSION = 6.3.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 09095de72e..50e49c7b26 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -24,11 +24,11 @@ android.nonTransitiveRClass=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.3 -android.version_code=279 +android.version_name=6.3.1 +android.version_code=281 -desktop.version_name=6.3 -desktop.version_code=96 +desktop.version_name=6.3.1 +desktop.version_code=97 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From d45ecff13ab95646728f48142e0034bc07af3f60 Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Tue, 1 Apr 2025 11:44:22 +0000 Subject: [PATCH 498/567] flatpak: update metainfo (#5794) --- .../flatpak/chat.simplex.simplex.metainfo.xml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/scripts/flatpak/chat.simplex.simplex.metainfo.xml b/scripts/flatpak/chat.simplex.simplex.metainfo.xml index 45e0f9a418..6ad4fda03e 100644 --- a/scripts/flatpak/chat.simplex.simplex.metainfo.xml +++ b/scripts/flatpak/chat.simplex.simplex.metainfo.xml @@ -38,6 +38,27 @@ + + https://simplex.chat/blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.html + +

    New in v6.3.1:

    +
      +
    • scrolling/navigation improvements.
    • +
    • faster onboarding (conditions and operators are combined to one screen).
    • +
    +

    New in v6.3.0:

    +
      +
    • Mention members and get notified when mentioned.
    • +
    • Send private reports to moderators.
    • +
    • Delete, block and change role for multiple members at once
    • +
    • Faster sending messages and faster deletion.
    • +
    • Organize chats into lists to keep track of what's important.
    • +
    • Jump to found and forwarded messages.
    • +
    • Private media file names.
    • +
    • Message expiration in chats.
    • +
    +
    +
    https://simplex.chat/blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.html From e7f8533112b32794db93d1f73e5605037cd987b1 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Thu, 3 Apr 2025 10:55:54 +0100 Subject: [PATCH 499/567] ios: v6.3.1, build 271 using XCode 15 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 366bfd167a..5e6148758c 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -1963,7 +1963,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 270; + CURRENT_PROJECT_VERSION = 271; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2013,7 +2013,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 270; + CURRENT_PROJECT_VERSION = 271; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2055,7 +2055,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 270; + CURRENT_PROJECT_VERSION = 271; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2075,7 +2075,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 270; + CURRENT_PROJECT_VERSION = 271; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2100,7 +2100,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 270; + CURRENT_PROJECT_VERSION = 271; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2137,7 +2137,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 270; + CURRENT_PROJECT_VERSION = 271; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2174,7 +2174,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 270; + CURRENT_PROJECT_VERSION = 271; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2225,7 +2225,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 270; + CURRENT_PROJECT_VERSION = 271; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2276,7 +2276,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 270; + CURRENT_PROJECT_VERSION = 271; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2310,7 +2310,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 270; + CURRENT_PROJECT_VERSION = 271; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; From 3fb09d3def3d6ed369509939626c12985a54f0ac Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 3 Apr 2025 16:27:40 +0100 Subject: [PATCH 500/567] core: fix types for PostgreSQL database (#5800) * core: fix types for PostgreSQL database * option to create schema * use action forks --- .github/workflows/build.yml | 34 ++++++++++++++-------------- .github/workflows/web.yml | 2 +- src/Simplex/Chat/Options/Postgres.hs | 33 ++++++++++++++++++++++----- tests/ChatClient.hs | 4 +++- 4 files changed, 48 insertions(+), 25 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2338258d82..39973dc017 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,7 +32,7 @@ jobs: - name: Build changelog id: build_changelog - uses: mikepenz/release-changelog-builder-action@v4 + uses: simplex-chat/release-changelog-builder-action@v4 with: configuration: .github/changelog_conf.json failOnError: true @@ -42,7 +42,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Create release - uses: softprops/action-gh-release@v1 + uses: simplex-chat/action-gh-release@v1 with: body: ${{ steps.build_changelog.outputs.changelog }} prerelease: true @@ -97,7 +97,7 @@ jobs: - name: Configure pagefile (Windows) if: matrix.os == 'windows-latest' - uses: al-cheb/configure-pagefile-action@v1.3 + uses: simplex-chat/configure-pagefile-action@v1.3 with: minimum-size: 16GB maximum-size: 16GB @@ -107,7 +107,7 @@ jobs: uses: actions/checkout@v3 - name: Setup Haskell - uses: haskell-actions/setup@v2 + uses: simplex-chat/setup-haskell-action@v2 with: ghc-version: ${{ matrix.ghc }} cabal-version: "3.10.1.0" @@ -179,7 +179,7 @@ jobs: - name: Unix upload CLI binary to release if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os != 'windows-latest' - uses: svenstaro/upload-release-action@v2 + uses: simplex-chat/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: ${{ steps.unix_cli_build.outputs.bin_path }} @@ -188,7 +188,7 @@ jobs: - name: Unix update CLI binary hash if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os != 'windows-latest' - uses: softprops/action-gh-release@v1 + uses: simplex-chat/action-gh-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -242,7 +242,7 @@ jobs: - name: Linux upload desktop package to release if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04') - uses: svenstaro/upload-release-action@v2 + uses: simplex-chat/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: ${{ steps.linux_desktop_build.outputs.package_path }} @@ -251,7 +251,7 @@ jobs: - name: Linux update desktop package hash if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04') - uses: softprops/action-gh-release@v1 + uses: simplex-chat/action-gh-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -261,7 +261,7 @@ jobs: - name: Linux upload AppImage to release if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04' - uses: svenstaro/upload-release-action@v2 + uses: simplex-chat/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: ${{ steps.linux_appimage_build.outputs.appimage_path }} @@ -270,7 +270,7 @@ jobs: - name: Linux update AppImage hash if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04' - uses: softprops/action-gh-release@v1 + uses: simplex-chat/action-gh-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -280,7 +280,7 @@ jobs: - name: Mac upload desktop package to release if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'macos-latest' || matrix.os == 'macos-13') - uses: svenstaro/upload-release-action@v2 + uses: simplex-chat/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: ${{ steps.mac_desktop_build.outputs.package_path }} @@ -289,7 +289,7 @@ jobs: - name: Mac update desktop package hash if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'macos-latest' || matrix.os == 'macos-13') - uses: softprops/action-gh-release@v1 + uses: simplex-chat/action-gh-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -319,7 +319,7 @@ jobs: - name: 'Setup MSYS2' if: matrix.os == 'windows-latest' - uses: msys2/setup-msys2@v2 + uses: simplex-chat/setup-msys2@v2 with: msystem: ucrt64 update: true @@ -357,7 +357,7 @@ jobs: - name: Windows upload CLI binary to release if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest' - uses: svenstaro/upload-release-action@v2 + uses: simplex-chat/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: ${{ steps.windows_build.outputs.bin_path }} @@ -366,7 +366,7 @@ jobs: - name: Windows update CLI binary hash if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest' - uses: softprops/action-gh-release@v1 + uses: simplex-chat/action-gh-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -389,7 +389,7 @@ jobs: - name: Windows upload desktop package to release if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest' - uses: svenstaro/upload-release-action@v2 + uses: simplex-chat/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: ${{ steps.windows_desktop_build.outputs.package_path }} @@ -398,7 +398,7 @@ jobs: - name: Windows update desktop package hash if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest' - uses: softprops/action-gh-release@v1 + uses: simplex-chat/action-gh-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml index 6839d48aeb..5fbe8293bc 100644 --- a/.github/workflows/web.yml +++ b/.github/workflows/web.yml @@ -33,7 +33,7 @@ jobs: ./website/web.sh - name: Deploy - uses: peaceiris/actions-gh-pages@v3 + uses: simplex-chat/actions-gh-pages@v3 with: publish_dir: ./website/_site github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/src/Simplex/Chat/Options/Postgres.hs b/src/Simplex/Chat/Options/Postgres.hs index b174ecd02e..f7c429e93e 100644 --- a/src/Simplex/Chat/Options/Postgres.hs +++ b/src/Simplex/Chat/Options/Postgres.hs @@ -7,11 +7,14 @@ module Simplex.Chat.Options.Postgres where import qualified Data.ByteString.Char8 as B import Foreign.C.String import Options.Applicative +import Numeric.Natural (Natural) import Simplex.Messaging.Agent.Store.Interface (DBOpts (..)) data ChatDbOpts = ChatDbOpts { dbConnstr :: String, - dbSchemaPrefix :: String + dbSchemaPrefix :: String, + dbPoolSize :: Natural, + dbCreateSchema :: Bool } chatDbOptsP :: FilePath -> String -> Parser ChatDbOpts @@ -33,16 +36,32 @@ chatDbOptsP _appDir defaultDbName = do <> value "simplex_v1" <> showDefault ) - pure ChatDbOpts {dbConnstr, dbSchemaPrefix} + dbPoolSize <- + option + auto + ( long "pool-size" + <> metavar "DB_POOL_SIZE" + <> help "Database connection pool size" + <> value 1 + <> showDefault + ) + dbCreateSchema <- + switch + ( long "create-schema" + <> help "Create database schema when it does not exist" + ) + pure ChatDbOpts {dbConnstr, dbSchemaPrefix, dbPoolSize, dbCreateSchema} dbString :: ChatDbOpts -> String dbString ChatDbOpts {dbConnstr} = dbConnstr toDBOpts :: ChatDbOpts -> String -> Bool -> DBOpts -toDBOpts ChatDbOpts {dbConnstr, dbSchemaPrefix} dbSuffix _keepKey = +toDBOpts ChatDbOpts {dbConnstr, dbSchemaPrefix, dbPoolSize, dbCreateSchema} dbSuffix _keepKey = DBOpts { connstr = B.pack dbConnstr, - schema = if null dbSchemaPrefix then "simplex_v1" <> dbSuffix else dbSchemaPrefix <> dbSuffix + schema = B.pack $ if null dbSchemaPrefix then "simplex_v1" <> dbSuffix else dbSchemaPrefix <> dbSuffix, + poolSize = dbPoolSize, + createSchema = dbCreateSchema } chatSuffix :: String @@ -58,11 +77,13 @@ mobileDbOpts schemaPrefix connstr = do pure $ ChatDbOpts { dbConnstr, - dbSchemaPrefix + dbSchemaPrefix, + dbPoolSize = 1, + dbCreateSchema = True } removeDbKey :: ChatDbOpts -> ChatDbOpts removeDbKey = id errorDbStr :: DBOpts -> String -errorDbStr DBOpts {schema} = schema +errorDbStr DBOpts {schema} = B.unpack schema diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index c41c155697..f16bc84cfd 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -117,7 +117,9 @@ testCoreOpts = { dbConnstr = testDBConnstr, -- dbSchemaPrefix is not used in tests (except bot tests where it's redefined), -- instead different schema prefix is passed per client so that single test database is used - dbSchemaPrefix = "" + dbSchemaPrefix = "", + dbPoolSize = 3, + dbCreateSchema = True #else { dbFilePrefix = "./simplex_v1", -- dbFilePrefix is not used in tests (except bot tests where it's redefined) dbKey = "", -- dbKey = "this is a pass-phrase to encrypt the database", From 48b1ef764bea7ebe8cd1dbf3144153f87efa633f Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Fri, 11 Apr 2025 22:19:24 +0000 Subject: [PATCH 501/567] ci: reproducible builds/refactor (#5808) * ci: reproducible builds/refactor * ci: fix mac desktop upload * ci: docker shell abort on error * scripts: add reproduce script * ci: add new reproduce workflow * scripts/reproduce-builds: change repo back to official --- .github/actions/prepare-build/action.yml | 52 ++ .github/actions/prepare-release/action.yml | 39 ++ .github/workflows/build.yml | 580 ++++++++++++--------- .github/workflows/reproduce-schedule.yml | 45 ++ Dockerfile.build | 84 +++ scripts/ci/linux_util_free_space.sh | 96 ++++ scripts/reproduce-builds.sh | 120 +++++ 7 files changed, 757 insertions(+), 259 deletions(-) create mode 100644 .github/actions/prepare-build/action.yml create mode 100644 .github/actions/prepare-release/action.yml create mode 100644 .github/workflows/reproduce-schedule.yml create mode 100644 Dockerfile.build create mode 100755 scripts/ci/linux_util_free_space.sh create mode 100644 scripts/reproduce-builds.sh diff --git a/.github/actions/prepare-build/action.yml b/.github/actions/prepare-build/action.yml new file mode 100644 index 0000000000..6682641419 --- /dev/null +++ b/.github/actions/prepare-build/action.yml @@ -0,0 +1,52 @@ +name: "Prebuilt steps for build" +description: "Reusable steps for multiple jobs" +inputs: + java_ver: + required: true + description: "Java version to install" + ghc_ver: + required: true + description: "GHC version to install" + github_ref: + required: true + description: "Git reference" + os: + required: true + description: "Target OS" + cache_path: + required: false + default: "~/.cabal/store" + description: "Cache path" + cabal_ver: + required: false + default: 3.10.1.0 + description: "GHC version to install" +runs: + using: "composite" + steps: + - name: Skip unreliable ghc 8.10.7 build on stable branch + shell: bash + if: inputs.ghc_ver == '8.10.7' && inputs.github_ref == 'refs/heads/stable' + run: exit 0 + + - name: Setup Haskell + uses: simplex-chat/setup-haskell-action@v2 + with: + ghc-version: ${{ inputs.ghc_ver }} + cabal-version: ${{ inputs.cabal_ver }} + + - name: Setup Java + if: startsWith(inputs.github_ref, 'refs/tags/v') + uses: actions/setup-java@v3 + with: + distribution: 'corretto' + java-version: ${{ inputs.java_ver }} + cache: 'gradle' + + - name: Restore cached build + uses: actions/cache@v4 + with: + path: | + ${{ inputs.cache_path }} + dist-newstyle + key: ${{ inputs.os }}-ghc${{ inputs.ghc_ver }}-${{ hashFiles('cabal.project', 'simplex-chat.cabal') }} diff --git a/.github/actions/prepare-release/action.yml b/.github/actions/prepare-release/action.yml new file mode 100644 index 0000000000..e44e6ef0f2 --- /dev/null +++ b/.github/actions/prepare-release/action.yml @@ -0,0 +1,39 @@ +name: "Upload binary and update hash" +description: "Reusable steps for multiple jobs" +inputs: + bin_path: + required: true + description: "Path to binary to upload" + bin_name: + required: true + description: "Name of uploaded binary" + bin_hash: + required: true + description: "Message with SHA to include in release" + github_ref: + required: true + description: "Github reference" + github_token: + required: true + description: "Github token" +runs: + using: "composite" + steps: + - name: Linux upload AppImage to release + if: startsWith(inputs.github_ref, 'refs/tags/v') + uses: simplex-chat/upload-release-action@v2 + with: + repo_token: ${{ inputs.github_token }} + file: ${{ inputs.bin_path }} + asset_name: ${{ inputs.bin_name }} + tag: ${{ inputs.github_ref }} + + - name: Linux update AppImage hash + if: startsWith(inputs.github_ref, 'refs/tags/v') + uses: simplex-chat/action-gh-release@v2 + env: + GITHUB_TOKEN: ${{ inputs.github_token }} + with: + append_body: true + body: | + ${{ inputs.bin_hash }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 39973dc017..de0b976bcc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,16 +22,57 @@ on: - "README.md" - "PRIVACY.md" +# This workflow uses custom actions (prepare-build and prepare-release) defined in: +# +# .github/actions/ +# ├── prepare-build +# │ └── action.yml +# └── prepare-release +# └── action.yml + +# Important! +# Do not use always(), it makes build unskippable. +# See: https://github.com/actions/runner/issues/1846#issuecomment-1246102753 + jobs: - prepare-release: - if: startsWith(github.ref, 'refs/tags/v') + +# ============================= +# Global variables +# ============================= + +# That is the only and less hacky way to setup global variables +# to use in strategy matrix (env:/YAML anchors doesn't work). +# See: https://github.com/orgs/community/discussions/56787#discussioncomment-6041789 +# https://github.com/actions/runner/issues/1182 +# https://stackoverflow.com/a/77549656 + + variables: + runs-on: ubuntu-latest + outputs: + GHC_VER: 9.6.3 + JAVA_VER: 17 + steps: + - name: Dummy job when we have just simple variables + if: false + run: echo + +# ============================= +# Create release +# ============================= + +# Create release, but only if it's triggered by tag push. +# On pull requests/commits push, this job will always complete. + + maybe-release: runs-on: ubuntu-latest steps: - name: Clone project + if: startsWith(github.ref, 'refs/tags/v') uses: actions/checkout@v3 - name: Build changelog id: build_changelog + if: startsWith(github.ref, 'refs/tags/v') uses: simplex-chat/release-changelog-builder-action@v4 with: configuration: .github/changelog_conf.json @@ -42,6 +83,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Create release + if: startsWith(github.ref, 'refs/tags/v') uses: simplex-chat/action-gh-release@v1 with: body: ${{ steps.build_changelog.outputs.changelog }} @@ -52,183 +94,259 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - build: - name: build-${{ matrix.os }}-${{ matrix.ghc }} - if: always() - needs: prepare-release - runs-on: ${{ matrix.os }} +# ========================= +# Linux Build +# ========================= + + build-linux: + name: "ubuntu-${{ matrix.os }} (CLI,Desktop), GHC: ${{ matrix.ghc }}" + needs: [maybe-release, variables] + runs-on: ubuntu-${{ matrix.os }} strategy: fail-fast: false matrix: include: - - os: ubuntu-20.04 + - os: 20.04 ghc: "8.10.7" - cache_path: ~/.cabal/store - - os: ubuntu-20.04 - ghc: "9.6.3" - cache_path: ~/.cabal/store - asset_name: simplex-chat-ubuntu-20_04-x86-64 + - os: 20.04 + ghc: ${{ needs.variables.outputs.GHC_VER }} + cli_asset_name: simplex-chat-ubuntu-20_04-x86-64 desktop_asset_name: simplex-desktop-ubuntu-20_04-x86_64.deb - - os: ubuntu-22.04 - ghc: "9.6.3" - cache_path: ~/.cabal/store - asset_name: simplex-chat-ubuntu-22_04-x86-64 + - os: 22.04 + ghc: ${{ needs.variables.outputs.GHC_VER }} + cli_asset_name: simplex-chat-ubuntu-22_04-x86-64 desktop_asset_name: simplex-desktop-ubuntu-22_04-x86_64.deb - - os: macos-latest - ghc: "9.6.3" - cache_path: ~/.cabal/store - asset_name: simplex-chat-macos-aarch64 - desktop_asset_name: simplex-desktop-macos-aarch64.dmg - - os: macos-13 - ghc: "9.6.3" - cache_path: ~/.cabal/store - asset_name: simplex-chat-macos-x86-64 - desktop_asset_name: simplex-desktop-macos-x86_64.dmg - - os: windows-latest - ghc: "9.6.3" - cache_path: C:/cabal - asset_name: simplex-chat-windows-x86-64 - desktop_asset_name: simplex-desktop-windows-x86_64.msi - steps: - - name: Skip unreliable ghc 8.10.7 build on stable branch - if: matrix.ghc == '8.10.7' && github.ref == 'refs/heads/stable' - run: exit 0 - - - name: Configure pagefile (Windows) - if: matrix.os == 'windows-latest' - uses: simplex-chat/configure-pagefile-action@v1.3 - with: - minimum-size: 16GB - maximum-size: 16GB - disk-root: "C:" - - - name: Clone project + - name: Checkout Code uses: actions/checkout@v3 - - name: Setup Haskell - uses: simplex-chat/setup-haskell-action@v2 - with: - ghc-version: ${{ matrix.ghc }} - cabal-version: "3.10.1.0" + # Otherwise we run out of disk space with Docker build + - name: Free disk space + shell: bash + run: ./scripts/ci/linux_util_free_space.sh - name: Restore cached build - id: restore_cache - uses: actions/cache/restore@v3 + uses: actions/cache@v4 with: path: | - ${{ matrix.cache_path }} + ~/.cabal/store dist-newstyle - key: ${{ matrix.os }}-ghc${{ matrix.ghc }}-${{ hashFiles('cabal.project', 'simplex-chat.cabal') }} + key: ubuntu-${{ matrix.os }}-ghc${{ matrix.ghc }}-${{ hashFiles('cabal.project', 'simplex-chat.cabal') }} - # / Unix + - name: Set up Docker Buildx + uses: simplex-chat/docker-setup-buildx-action@v3 - - name: Unix prepare cabal.project.local for Mac - if: matrix.os == 'macos-latest' + - name: Build and cache Docker image + uses: simplex-chat/docker-build-push-action@v6 + with: + context: . + load: true + file: Dockerfile.build + tags: build/${{ matrix.os }}:latest + build-args: | + TAG=${{ matrix.os }} + GHC=${{ matrix.ghc }} + + # Docker needs these flags for AppImage build: + # --device /dev/fuse + # --cap-add SYS_ADMIN + # --security-opt apparmor:unconfined + - name: Start container shell: bash run: | - echo "ignore-project: False" >> cabal.project.local - echo "package simplexmq" >> cabal.project.local - echo " extra-include-dirs: /opt/homebrew/opt/openssl@3.0/include" >> cabal.project.local - echo " extra-lib-dirs: /opt/homebrew/opt/openssl@3.0/lib" >> cabal.project.local - echo "" >> cabal.project.local - echo "package direct-sqlcipher" >> cabal.project.local - echo " extra-include-dirs: /opt/homebrew/opt/openssl@3.0/include" >> cabal.project.local - echo " extra-lib-dirs: /opt/homebrew/opt/openssl@3.0/lib" >> cabal.project.local - echo " flags: +openssl" >> cabal.project.local + docker run -t -d \ + --device /dev/fuse \ + --cap-add SYS_ADMIN \ + --security-opt apparmor:unconfined \ + --name builder \ + -v ~/.cabal:/root/.cabal \ + -v /home/runner/work/_temp:/home/runner/work/_temp \ + -v ${{ github.workspace }}:/project \ + build/${{ matrix.os }}:latest - - name: Unix prepare cabal.project.local for Mac - if: matrix.os == 'macos-13' - shell: bash - run: | - echo "ignore-project: False" >> cabal.project.local - echo "package simplexmq" >> cabal.project.local - echo " extra-include-dirs: /usr/local/opt/openssl@3.0/include" >> cabal.project.local - echo " extra-lib-dirs: /usr/local/opt/openssl@3.0/lib" >> cabal.project.local - echo "" >> cabal.project.local - echo "package direct-sqlcipher" >> cabal.project.local - echo " extra-include-dirs: /usr/local/opt/openssl@3.0/include" >> cabal.project.local - echo " extra-lib-dirs: /usr/local/opt/openssl@3.0/lib" >> cabal.project.local - echo " flags: +openssl" >> cabal.project.local - - - name: Install AppImage dependencies - if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04' - run: sudo apt install -y desktop-file-utils - - - name: Install openssl for Mac - if: matrix.os == 'macos-latest' || matrix.os == 'macos-13' - run: brew install openssl@3.0 - - - name: Unix prepare cabal.project.local for Ubuntu - if: matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04' + - name: Prepare cabal.project.local shell: bash run: | echo "ignore-project: False" >> cabal.project.local echo "package direct-sqlcipher" >> cabal.project.local echo " flags: +openssl" >> cabal.project.local - - name: Unix build CLI - id: unix_cli_build - if: matrix.os != 'windows-latest' + # chmod/git commands are used to workaround permission issues when cache is restored + - name: Build CLI + shell: docker exec -t builder sh -eu {0} + run: | + chmod -R 777 dist-newstyle ~/.cabal && git config --global --add safe.directory '*' + cabal clean + cabal update + cabal build -j --enable-tests + mkdir -p /out + for i in simplex-chat simplex-chat-test; do + bin=$(find /project/dist-newstyle -name "$i" -type f -executable) + chmod +x "$bin" + mv "$bin" /out/ + done + strip /out/simplex-chat + + - name: Copy tests from container shell: bash run: | - cabal build --enable-tests - path=$(cabal list-bin simplex-chat) - echo "bin_path=$path" >> $GITHUB_OUTPUT - echo "bin_hash=$(echo SHA2-512\(${{ matrix.asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT + docker cp builder:/out/simplex-chat-test . - - name: Unix upload CLI binary to release - if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os != 'windows-latest' - uses: simplex-chat/upload-release-action@v2 - with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: ${{ steps.unix_cli_build.outputs.bin_path }} - asset_name: ${{ matrix.asset_name }} - tag: ${{ github.ref }} - - - name: Unix update CLI binary hash - if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os != 'windows-latest' - uses: simplex-chat/action-gh-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - append_body: true - body: | - ${{ steps.unix_cli_build.outputs.bin_hash }} - - - name: Setup Java - if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name - uses: actions/setup-java@v3 - with: - distribution: 'corretto' - java-version: '17' - cache: 'gradle' - - - name: Linux build desktop - id: linux_desktop_build - if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04') + - name: Copy CLI from container and prepare it + id: linux_cli_prepare + if: startsWith(github.ref, 'refs/tags/v') && matrix.cli_asset_name shell: bash + run: | + docker cp builder:/out/simplex-chat ./${{ matrix.cli_asset_name }} + path="${{ github.workspace }}/${{ matrix.cli_asset_name }}" + echo "bin_path=$path" >> $GITHUB_OUTPUT + echo "bin_hash=$(echo SHA2-512\(${{ matrix.cli_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT + + - name: Upload CLI + if: startsWith(github.ref, 'refs/tags/v') && matrix.cli_asset_name + uses: ./.github/actions/prepare-release + with: + bin_path: ${{ steps.linux_cli_prepare.outputs.bin_path }} + bin_name: ${{ matrix.cli_asset_name }} + bin_hash: ${{ steps.linux_cli_prepare.outputs.bin_hash }} + github_ref: ${{ github.ref }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Build Desktop + if: startsWith(github.ref, 'refs/tags/v') && matrix.desktop_asset_name + shell: docker exec -t builder sh -eu {0} run: | scripts/desktop/build-lib-linux.sh cd apps/multiplatform ./gradlew packageDeb - path=$(echo $PWD/release/main/deb/simplex_*_amd64.deb) + + - name: Prepare Desktop + id: linux_desktop_build + if: startsWith(github.ref, 'refs/tags/v') && matrix.desktop_asset_name + shell: bash + run: | + path=$(echo ${{ github.workspace }}/apps/multiplatform/release/main/deb/simplex_*_amd64.deb ) echo "package_path=$path" >> $GITHUB_OUTPUT echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - - name: Linux make AppImage - id: linux_appimage_build - if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04' - shell: bash + - name: Upload Desktop + uses: ./.github/actions/prepare-release + if: startsWith(github.ref, 'refs/tags/v') && matrix.desktop_asset_name + with: + bin_path: ${{ steps.linux_desktop_build.outputs.package_path }} + bin_name: ${{ matrix.desktop_asset_name }} + bin_hash: ${{ steps.linux_desktop_build.outputs.package_hash }} + github_ref: ${{ github.ref }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Build AppImage + if: startsWith(github.ref, 'refs/tags/v') && matrix.desktop_asset_name && matrix.os == '20.04' + shell: docker exec -t builder sh -eu {0} run: | scripts/desktop/make-appimage-linux.sh - path=$(echo $PWD/apps/multiplatform/release/main/*imple*.AppImage) + + - name: Prepare AppImage + id: linux_appimage_build + if: startsWith(github.ref, 'refs/tags/v') && matrix.desktop_asset_name && matrix.os == '20.04' + shell: bash + run: | + path=$(echo ${{ github.workspace }}/apps/multiplatform/release/main/*imple*.AppImage) echo "appimage_path=$path" >> $GITHUB_OUTPUT echo "appimage_hash=$(echo SHA2-512\(simplex-desktop-x86_64.AppImage\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - - name: Mac build desktop + - name: Upload AppImage + if: startsWith(github.ref, 'refs/tags/v') && matrix.desktop_asset_name && matrix.os == '20.04' + uses: ./.github/actions/prepare-release + with: + bin_path: ${{ steps.linux_appimage_build.outputs.appimage_path }} + bin_name: "simplex-desktop-x86_64.AppImage" + bin_hash: ${{ steps.linux_appimage_build.outputs.appimage_hash }} + github_ref: ${{ github.ref }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Fix permissions for cache + shell: bash + run: | + sudo chmod -R 777 dist-newstyle ~/.cabal + sudo chown -R $(id -u):$(id -g) dist-newstyle ~/.cabal + + - name: Run tests + shell: bash + run: | + ./simplex-chat-test + +# ========================= +# MacOS Build +# ========================= + + build-macos: + name: "${{ matrix.os }} (CLI,Desktop), GHC: ${{ matrix.ghc }}" + needs: [maybe-release, variables] + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: macos-latest + ghc: ${{ needs.variables.outputs.GHC_VER }} + cli_asset_name: simplex-chat-macos-aarch64 + desktop_asset_name: simplex-desktop-macos-aarch64.dmg + openssl_dir: "/opt/homebrew/opt" + - os: macos-13 + ghc: ${{ needs.variables.outputs.GHC_VER }} + cli_asset_name: simplex-chat-macos-x86-64 + desktop_asset_name: simplex-desktop-macos-x86_64.dmg + openssl_dir: "/usr/local/opt" + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Prepare build + uses: ./.github/actions/prepare-build + with: + java_ver: ${{ needs.variables.outputs.JAVA_VER }} + ghc_ver: ${{ matrix.ghc }} + os: ${{ matrix.os }} + github_ref: ${{ github.ref }} + + - name: Install OpenSSL + run: brew install openssl@3.0 + + - name: Prepare cabal.project.local + shell: bash + run: | + echo "ignore-project: False" >> cabal.project.local + echo "package simplexmq" >> cabal.project.local + echo " extra-include-dirs: ${{ matrix.opnessl_dir }}/openssl@3.0/include" >> cabal.project.local + echo " extra-lib-dirs: ${{ matrix.openssl_dir}}/openssl@3.0/lib" >> cabal.project.local + echo "" >> cabal.project.local + echo "package direct-sqlcipher" >> cabal.project.local + echo " extra-include-dirs: ${{ matrix.openssl_dir }}/openssl@3.0/include" >> cabal.project.local + echo " extra-lib-dirs: ${{ matrix.openssl_dir }}/openssl@3.0/lib" >> cabal.project.local + echo " flags: +openssl" >> cabal.project.local + + - name: Build CLI + id: mac_cli_build + shell: bash + run: | + cabal build -j --enable-tests + path=$(cabal list-bin simplex-chat) + echo "bin_path=$path" >> $GITHUB_OUTPUT + echo "bin_hash=$(echo SHA2-512\(${{ matrix.cli_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT + + - name: Upload CLI + if: startsWith(github.ref, 'refs/tags/v') + uses: ./.github/actions/prepare-release + with: + bin_path: ${{ steps.mac_cli_build.outputs.bin_path }} + bin_name: ${{ matrix.cli_asset_name }} + bin_hash: ${{ steps.mac_cli_build.outputs.bin_hash }} + github_ref: ${{ github.ref }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Build Desktop id: mac_desktop_build - if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'macos-latest' || matrix.os == 'macos-13') + if: startsWith(github.ref, 'refs/tags/v') shell: bash env: APPLE_SIMPLEX_SIGNING_KEYCHAIN: ${{ secrets.APPLE_SIMPLEX_SIGNING_KEYCHAIN }} @@ -240,85 +358,58 @@ jobs: echo "package_path=$path" >> $GITHUB_OUTPUT echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - - name: Linux upload desktop package to release - if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04') - uses: simplex-chat/upload-release-action@v2 + - name: Upload Desktop + if: startsWith(github.ref, 'refs/tags/v') + uses: ./.github/actions/prepare-release with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: ${{ steps.linux_desktop_build.outputs.package_path }} - asset_name: ${{ matrix.desktop_asset_name }} - tag: ${{ github.ref }} + bin_path: ${{ steps.mac_desktop_build.outputs.package_path }} + bin_name: ${{ matrix.desktop_asset_name }} + bin_hash: ${{ steps.mac_desktop_build.outputs.package_hash }} + github_ref: ${{ github.ref }} + github_token: ${{ secrets.GITHUB_TOKEN }} - - name: Linux update desktop package hash - if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04') - uses: simplex-chat/action-gh-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - append_body: true - body: | - ${{ steps.linux_desktop_build.outputs.package_hash }} - - - name: Linux upload AppImage to release - if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04' - uses: simplex-chat/upload-release-action@v2 - with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: ${{ steps.linux_appimage_build.outputs.appimage_path }} - asset_name: simplex-desktop-x86_64.AppImage - tag: ${{ github.ref }} - - - name: Linux update AppImage hash - if: startsWith(github.ref, 'refs/tags/v') && matrix.asset_name && matrix.os == 'ubuntu-20.04' - uses: simplex-chat/action-gh-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - append_body: true - body: | - ${{ steps.linux_appimage_build.outputs.appimage_hash }} - - - name: Mac upload desktop package to release - if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'macos-latest' || matrix.os == 'macos-13') - uses: simplex-chat/upload-release-action@v2 - with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: ${{ steps.mac_desktop_build.outputs.package_path }} - asset_name: ${{ matrix.desktop_asset_name }} - tag: ${{ github.ref }} - - - name: Mac update desktop package hash - if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'macos-latest' || matrix.os == 'macos-13') - uses: simplex-chat/action-gh-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - append_body: true - body: | - ${{ steps.mac_desktop_build.outputs.package_hash }} - - - name: Cache unix build - uses: actions/cache/save@v3 - if: matrix.os != 'windows-latest' - with: - path: | - ${{ matrix.cache_path }} - dist-newstyle - key: ${{ steps.restore_cache.outputs.cache-primary-key }} - - - name: Unix test - if: matrix.os != 'windows-latest' + - name: Run tests timeout-minutes: 40 shell: bash run: cabal test --test-show-details=direct - # Unix / +# ========================= +# Windows Build +# ========================= - # / Windows - # rm -rf dist-newstyle/src/direct-sq* is here because of the bug in cabal's dependency which prevents second build from finishing + build-windows: + name: "${{ matrix.os }} (CLI,Desktop), GHC: ${{ matrix.ghc }}" + needs: [maybe-release, variables] + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: windows-latest + ghc: ${{ needs.variables.outputs.GHC_VER }} + cli_asset_name: simplex-chat-windows-x86-64 + desktop_asset_name: simplex-desktop-windows-x86_64.msi + steps: + - name: Checkout Code + uses: actions/checkout@v3 + - name: Prepare build + uses: ./.github/actions/prepare-build + with: + java_ver: ${{ needs.variables.outputs.JAVA_VER }} + ghc_ver: ${{ matrix.ghc }} + os: ${{ matrix.os }} + cache_path: "C:/cabal" + github_ref: ${{ github.ref }} + + - name: Configure pagefile (Windows) + uses: simplex-chat/configure-pagefile-action@v1.4 + with: + minimum-size: 16GB + maximum-size: 16GB + disk-root: "C:" + - name: 'Setup MSYS2' - if: matrix.os == 'windows-latest' uses: simplex-chat/setup-msys2@v2 with: msystem: ucrt64 @@ -331,10 +422,9 @@ jobs: toolchain:p cmake:p - - - name: Windows build - id: windows_build - if: matrix.os == 'windows-latest' + # rm -rf dist-newstyle/src/direct-sq* is here because of the bug in cabal's dependency which prevents second build from finishing + - name: Build CLI + id: windows_cli_build shell: msys2 {0} run: | export PATH=$PATH:/c/ghcup/bin:$(echo /c/tools/ghc-*/bin || echo) @@ -349,70 +439,42 @@ jobs: rm -rf dist-newstyle/src/direct-sq* sed -i "s/, unix /--, unix /" simplex-chat.cabal - cabal build --enable-tests + cabal build -j --enable-tests rm -rf dist-newstyle/src/direct-sq* path=$(cabal list-bin simplex-chat | tail -n 1) echo "bin_path=$path" >> $GITHUB_OUTPUT - echo "bin_hash=$(echo SHA2-512\(${{ matrix.asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT + echo "bin_hash=$(echo SHA2-512\(${{ matrix.cli_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - - name: Windows upload CLI binary to release - if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest' - uses: simplex-chat/upload-release-action@v2 + - name: Upload CLI + if: startsWith(github.ref, 'refs/tags/v') + uses: ./.github/actions/prepare-release with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: ${{ steps.windows_build.outputs.bin_path }} - asset_name: ${{ matrix.asset_name }} - tag: ${{ github.ref }} + bin_path: ${{ steps.windows_cli_build.outputs.bin_path }} + bin_name: ${{ matrix.cli_asset_name }} + bin_hash: ${{ steps.windows_cli_build.outputs.bin_hash }} + github_ref: ${{ github.ref }} + github_token: ${{ secrets.GITHUB_TOKEN }} - - name: Windows update CLI binary hash - if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest' - uses: simplex-chat/action-gh-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - append_body: true - body: | - ${{ steps.windows_build.outputs.bin_hash }} - - - name: Windows build desktop + - name: Build Desktop id: windows_desktop_build - if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest' + if: startsWith(github.ref, 'refs/tags/v') shell: msys2 {0} run: | export PATH=$PATH:/c/ghcup/bin:$(echo /c/tools/ghc-*/bin || echo) scripts/desktop/build-lib-windows.sh cd apps/multiplatform ./gradlew packageMsi + rm -rf dist-newstyle/src/direct-sq* path=$(echo $PWD/release/main/msi/*imple*.msi | sed 's#/\([a-z]\)#\1:#' | sed 's#/#\\#g') echo "package_path=$path" >> $GITHUB_OUTPUT echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - - name: Windows upload desktop package to release - if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest' - uses: simplex-chat/upload-release-action@v2 + - name: Upload Desktop + if: startsWith(github.ref, 'refs/tags/v') + uses: ./.github/actions/prepare-release with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: ${{ steps.windows_desktop_build.outputs.package_path }} - asset_name: ${{ matrix.desktop_asset_name }} - tag: ${{ github.ref }} - - - name: Windows update desktop package hash - if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest' - uses: simplex-chat/action-gh-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - append_body: true - body: | - ${{ steps.windows_desktop_build.outputs.package_hash }} - - - name: Cache windows build - uses: actions/cache/save@v3 - if: matrix.os == 'windows-latest' - with: - path: | - ${{ matrix.cache_path }} - dist-newstyle - key: ${{ steps.restore_cache.outputs.cache-primary-key }} - - # Windows / + bin_path: ${{ steps.windows_desktop_build.outputs.package_path }} + bin_name: ${{ matrix.desktop_asset_name }} + bin_hash: ${{ steps.windows_desktop_build.outputs.package_hash }} + github_ref: ${{ github.ref }} + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/reproduce-schedule.yml b/.github/workflows/reproduce-schedule.yml new file mode 100644 index 0000000000..7de44addc7 --- /dev/null +++ b/.github/workflows/reproduce-schedule.yml @@ -0,0 +1,45 @@ +name: Reproduce latest release + +on: + workflow_dispatch: + schedule: + - cron: '0 2 * * *' # every day at 02:00 night + +jobs: + reproduce: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Get latest release + shell: bash + run: | + curl --proto '=https' \ + --tlsv1.2 \ + -sSf -L \ + 'https://api.github.com/repos/simplex-chat/simplex-chat/releases/latest' \ + 2>/dev/null | \ + grep -i "tag_name" | \ + awk -F \" '{print "TAG="$4}' >> $GITHUB_ENV + + - name: Execute reproduce script + run: | + ${GITHUB_WORKSPACE}/scripts/reproduce-builds.sh "$TAG" + + - name: Check if build has been reproduced + env: + url: ${{ secrets.STATUS_SIMPLEX_WEBHOOK_URL }} + user: ${{ secrets.STATUS_SIMPLEX_WEBHOOK_USER }} + pass: ${{ secrets.STATUS_SIMPLEX_WEBHOOK_PASS }} + run: | + if [ -f "${GITHUB_WORKSPACE}/$TAG/_sha256sums" ]; then + exit 0 + else + curl --proto '=https' --tlsv1.2 -sSf \ + -u "${user}:${pass}" \ + -H 'Content-Type: application/json' \ + -d '{"title": "👾 GitHub: Runner", "description": "⛔️ '"$TAG"' did not reproduce."}' \ + "$url" + exit 1 + fi diff --git a/Dockerfile.build b/Dockerfile.build new file mode 100644 index 0000000000..cd0fd22aad --- /dev/null +++ b/Dockerfile.build @@ -0,0 +1,84 @@ +# syntax=docker/dockerfile:1.7.0-labs +ARG TAG=24.04 +FROM ubuntu:${TAG} AS build + +### Build stage + +ARG GHC=9.6.3 +ARG CABAL=3.10.1.0 +ARG JAVA=17 + +ENV TZ=Etc/UTC \ + DEBIAN_FRONTEND=noninteractive + +# Install curl, git and and simplexmq dependencies +RUN apt-get update && \ + apt-get install -y curl \ + libpq-dev \ + git \ + sqlite3 \ + libsqlite3-dev \ + build-essential \ + libgmp3-dev \ + zlib1g-dev \ + llvm \ + cmake \ + llvm-dev \ + libnuma-dev \ + libssl-dev \ + desktop-file-utils \ + openjdk-${JAVA}-jdk-headless \ + patchelf \ + ca-certificates \ + zip \ + wget \ + fuse3 \ + file \ + appstream \ + gpg \ + unzip &&\ + export JAVA_HOME=$(update-java-alternatives -l | head -n 1 | awk -F ' ' '{print $NF}') &&\ + ln -s /bin/fusermount /bin/fusermount3 || : + +# Specify bootstrap Haskell versions +ENV BOOTSTRAP_HASKELL_GHC_VERSION=${GHC} +ENV BOOTSTRAP_HASKELL_CABAL_VERSION=${CABAL} + +# Do not install Stack +ENV BOOTSTRAP_HASKELL_INSTALL_NO_STACK=true +ENV BOOTSTRAP_HASKELL_INSTALL_NO_STACK_HOOK=true + +# Install ghcup +RUN curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | BOOTSTRAP_HASKELL_NONINTERACTIVE=1 sh + +# Adjust PATH +ENV PATH="/root/.cabal/bin:/root/.ghcup/bin:$PATH" + +# Set both as default +RUN ghcup set ghc "${GHC}" && \ + ghcup set cabal "${CABAL}" + +#===================== +# Install Android SDK +#===================== +ARG SDK_VERSION=13114758 + +ENV SDK_VERSION=$SDK_VERSION \ + ANDROID_HOME=/root + +RUN curl -L -o tools.zip "https://dl.google.com/android/repository/commandlinetools-linux-${SDK_VERSION}_latest.zip" && \ + unzip tools.zip && rm tools.zip && \ + mv cmdline-tools tools && mkdir "$ANDROID_HOME/cmdline-tools" && mv tools "$ANDROID_HOME/cmdline-tools/" && \ + ln -s "$ANDROID_HOME/cmdline-tools/tools" "$ANDROID_HOME/cmdline-tools/latest" + +ENV PATH="$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/cmdline-tools/tools/bin" + +# https://askubuntu.com/questions/885658/android-sdk-repositories-cfg-could-not-be-loaded +RUN mkdir -p ~/.android ~/.gradle && \ + touch ~/.android/repositories.cfg && \ + echo 'org.gradle.console=plain' > ~/.gradle/gradle.properties &&\ + yes | sdkmanager --licenses >/dev/null + +ENV PATH=$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/build-tools + +WORKDIR /project diff --git a/scripts/ci/linux_util_free_space.sh b/scripts/ci/linux_util_free_space.sh new file mode 100755 index 0000000000..ef00eb886e --- /dev/null +++ b/scripts/ci/linux_util_free_space.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# Taken from: https://github.com/apache/arrow/blob/main/ci/scripts/util_free_space.sh + +set -eux + +df -h +echo "::group::/usr/local/*" +du -hsc /usr/local/* +echo "::endgroup::" +# ~1GB +sudo rm -rf \ + /usr/local/aws-sam-cil \ + /usr/local/julia* || : +echo "::group::/usr/local/bin/*" +du -hsc /usr/local/bin/* +echo "::endgroup::" +# ~1GB (From 1.2GB to 214MB) +sudo rm -rf \ + /usr/local/bin/aliyun \ + /usr/local/bin/azcopy \ + /usr/local/bin/bicep \ + /usr/local/bin/cmake-gui \ + /usr/local/bin/cpack \ + /usr/local/bin/helm \ + /usr/local/bin/hub \ + /usr/local/bin/kubectl \ + /usr/local/bin/minikube \ + /usr/local/bin/node \ + /usr/local/bin/packer \ + /usr/local/bin/pulumi* \ + /usr/local/bin/sam \ + /usr/local/bin/stack \ + /usr/local/bin/terraform || : +# 142M +sudo rm -rf /usr/local/bin/oc || : \ +echo "::group::/usr/local/share/*" +du -hsc /usr/local/share/* +echo "::endgroup::" +# 506MB +sudo rm -rf /usr/local/share/chromium || : +# 1.3GB +sudo rm -rf /usr/local/share/powershell || : +echo "::group::/usr/local/lib/*" +du -hsc /usr/local/lib/* +echo "::endgroup::" +# 15GB +sudo rm -rf /usr/local/lib/android || : +# 341MB +sudo rm -rf /usr/local/lib/heroku || : +# 1.2GB +sudo rm -rf /usr/local/lib/node_modules || : +echo "::group::/opt/*" +du -hsc /opt/* +echo "::endgroup::" +# 679MB +sudo rm -rf /opt/az || : +echo "::group::/opt/microsoft/*" +du -hsc /opt/microsoft/* +echo "::endgroup::" +# 197MB +sudo rm -rf /opt/microsoft/powershell || : +echo "::group::/opt/hostedtoolcache/*" +du -hsc /opt/hostedtoolcache/* +echo "::endgroup::" +# 5.3GB +sudo rm -rf /opt/hostedtoolcache/CodeQL || : +# 1.4GB +sudo rm -rf /opt/hostedtoolcache/go || : +# 489MB +sudo rm -rf /opt/hostedtoolcache/PyPy || : +# 376MB +sudo rm -rf /opt/hostedtoolcache/node || : +# Remove Web browser packages +sudo apt purge -y \ + firefox \ + google-chrome-stable \ + microsoft-edge-stable +df -h diff --git a/scripts/reproduce-builds.sh b/scripts/reproduce-builds.sh new file mode 100644 index 0000000000..1334fec0ec --- /dev/null +++ b/scripts/reproduce-builds.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env sh +set -eu + +TAG="$1" + +tempdir="$(mktemp -d)" +init_dir="$PWD" + +repo_name="simplex-chat" +repo="https://github.com/simplex-chat/${repo_name}" + +cabal_local='ignore-project: False +package direct-sqlcipher + flags: +openssl' + +export DOCKER_BUILDKIT=1 + +cleanup() { + docker exec -t builder sh -c 'rm -rf ./dist-newstyle' 2>/dev/null || : + rm -rf -- "$tempdir" + docker rm --force builder 2>/dev/null || : + docker image rm local 2>/dev/null || : + cd "$init_dir" +} +trap 'cleanup' EXIT INT + +mkdir -p "$init_dir/$TAG/from-source" "$init_dir/$TAG/prebuilt" + +git -C "$tempdir" clone "$repo.git" &&\ + cd "$tempdir/${repo_name}" &&\ + git checkout "$TAG" + +for os in 20.04 22.04; do + os_url="$(printf '%s' "$os" | tr '.' '_')" + + # Build image + docker build \ + --no-cache \ + --build-arg TAG=${os} \ + --build-arg GHC=9.6.3 \ + -f "$tempdir/${repo_name}/Dockerfile.build" \ + -t local \ + . + + printf '%s' "$cabal_local" > "$tempdir/${repo_name}/cabal.project.local" + + # Run container in background + docker run -t -d \ + --name builder \ + -v "$tempdir/${repo_name}:/project" \ + local + + docker exec \ + -t \ + builder \ + sh -c 'cabal clean && cabal update && cabal build -j --enable-tests && mkdir -p /out && for i in simplex-chat; do bin=$(find /project/dist-newstyle -name "$i" -type f -executable) && chmod +x "$bin" && mv "$bin" /out/; done && strip /out/simplex-chat' + + docker cp \ + builder:/out/simplex-chat \ + "$init_dir/$TAG/from-source/simplex-chat-ubuntu-${os_url}-x86-64" + + # Download prebuilt postgresql binary + curl -L \ + --output-dir "$init_dir/$TAG/prebuilt/" \ + -O \ + "$repo/releases/download/${TAG}/simplex-chat-ubuntu-${os_url}-x86-64" + + # Important! Remove dist-newstyle for the next interation + docker exec \ + -t \ + builder \ + sh -c 'rm -rf ./dist-newstyle' + + # Also restore git to previous state + git reset --hard && git clean -dfx + + # Stop containers, delete images + docker stop builder + docker rm --force builder + docker image rm local +done + +# Cleanup +rm -rf -- "$tempdir" +cd "$init_dir" + +# Final stage: compare hashes + +# Path to binaries +path_bin="$init_dir/$TAG" + +# Assume everything is okay for now +bad=0 + +# Check hashes for all binaries +for file in "$path_bin"/from-source/*; do + # Extract binary name + app="$(basename $file)" + + # Compute hash for compiled binary + compiled=$(sha256sum "$path_bin/from-source/$app" | awk '{print $1}') + # Compute hash for prebuilt binary + prebuilt=$(sha256sum "$path_bin/prebuilt/$app" | awk '{print $1}') + + # Compare + if [ "$compiled" != "$prebuilt" ]; then + # If hashes doesn't match, set bad... + bad=1 + + # ... and print affected binary + printf "%s - sha256sum hash doesn't match\n" "$app" + fi +done + +# If everything is still okay, compute checksums file +if [ "$bad" = 0 ]; then + sha256sum "$path_bin"/from-source/* | sed -e "s|$PWD/||g" -e 's|from-source/||g' > "$path_bin/_sha256sums" + + printf 'Checksums computed - %s\n' "$path_bin/_sha256sums" +fi From 090f576b65b9be64cc04267177763b8c4b506ae2 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 12 Apr 2025 19:34:30 +0100 Subject: [PATCH 502/567] directory: allow admins deleting groups and registering groups with the same name as deleted one; /help commands; better support of other group owners; support link encoding and version changes (#5829) * allow admins deleting groups from directory and registering groups with same name as deleted one; /help commands * support profile changes by other owners, with/without connection to directory * profile check will succeed when group link encoding or versions change, but the link queues remain the same --- .../src/Directory/Events.hs | 35 ++- .../src/Directory/Service.hs | 121 ++++++--- .../src/Directory/Store.hs | 15 +- src/Simplex/Chat/Markdown.hs | 14 +- tests/Bots/DirectoryTests.hs | 240 ++++++++++++------ tests/MarkdownTests.hs | 11 +- 6 files changed, 296 insertions(+), 140 deletions(-) diff --git a/apps/simplex-directory-service/src/Directory/Events.hs b/apps/simplex-directory-service/src/Directory/Events.hs index ed4204abb7..802221f976 100644 --- a/apps/simplex-directory-service/src/Directory/Events.hs +++ b/apps/simplex-directory-service/src/Directory/Events.hs @@ -11,6 +11,7 @@ module Directory.Events ( DirectoryEvent (..), DirectoryCmd (..), ADirectoryCmd (..), + DirectoryHelpSection (..), DirectoryRole (..), SDirectoryRole (..), crDirectoryEvent, @@ -25,6 +26,7 @@ import qualified Data.Attoparsec.Text as A import Data.Char (isSpace) import Data.Either (fromRight) import Data.Functor (($>)) +import Data.Maybe (fromMaybe) import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) @@ -45,7 +47,7 @@ data DirectoryEvent = DEContactConnected Contact | DEGroupInvitation {contact :: Contact, groupInfo :: GroupInfo, fromMemberRole :: GroupMemberRole, memberRole :: GroupMemberRole} | DEServiceJoinedGroup {contactId :: ContactId, groupInfo :: GroupInfo, hostMember :: GroupMember} - | DEGroupUpdated {contactId :: ContactId, fromGroup :: GroupInfo, toGroup :: GroupInfo} + | DEGroupUpdated {member :: GroupMember, fromGroup :: GroupInfo, toGroup :: GroupInfo} | DEPendingMember GroupInfo GroupMember | DEPendingMemberMsg GroupInfo GroupMember ChatItemId Text | DEContactRoleChanged GroupInfo ContactId GroupMemberRole -- contactId here is the contact whose role changed @@ -66,7 +68,7 @@ crDirectoryEvent = \case CRContactConnected {contact} -> Just $ DEContactConnected contact CRReceivedGroupInvitation {contact, groupInfo, fromMemberRole, memberRole} -> Just $ DEGroupInvitation {contact, groupInfo, fromMemberRole, memberRole} CRUserJoinedGroup {groupInfo, hostMember} -> (\contactId -> DEServiceJoinedGroup {contactId, groupInfo, hostMember}) <$> memberContactId hostMember - CRGroupUpdated {fromGroup, toGroup, member_} -> (\contactId -> DEGroupUpdated {contactId, fromGroup, toGroup}) <$> (memberContactId =<< member_) + CRGroupUpdated {fromGroup, toGroup, member_} -> (\member -> DEGroupUpdated {member, fromGroup, toGroup}) <$> member_ CRJoinedGroupMember {groupInfo, member = m} | pending m -> Just $ DEPendingMember groupInfo m | otherwise -> Nothing @@ -137,8 +139,11 @@ deriving instance Show (DirectoryCmdTag r) data ADirectoryCmdTag = forall r. ADCT (SDirectoryRole r) (DirectoryCmdTag r) +data DirectoryHelpSection = DHSRegistration | DHSCommands + deriving (Show) + data DirectoryCmd (r :: DirectoryRole) where - DCHelp :: DirectoryCmd 'DRUser + DCHelp :: DirectoryHelpSection -> DirectoryCmd 'DRUser DCSearchGroup :: Text -> DirectoryCmd 'DRUser DCSearchNext :: DirectoryCmd 'DRUser DCAllGroups :: DirectoryCmd 'DRUser @@ -180,7 +185,7 @@ directoryCmdP = (tagP >>= \(ADCT u t) -> ADC u <$> (cmdP t <|> pure (DCCommandError t))) <|> pure (ADC SDRUser DCUnknownCommand) tagP = - A.takeTill (== ' ') >>= \case + A.takeTill isSpace >>= \case "help" -> u DCHelp_ "h" -> u DCHelp_ "next" -> u DCSearchNext_ @@ -213,11 +218,19 @@ directoryCmdP = su = pure . ADCT SDRSuperUser cmdP :: DirectoryCmdTag r -> Parser (DirectoryCmd r) cmdP = \case - DCHelp_ -> pure DCHelp + DCHelp_ -> DCHelp . fromMaybe DHSRegistration <$> optional (A.takeWhile isSpace *> helpSectionP) + where + helpSectionP = + A.takeText >>= \case + "registration" -> pure DHSRegistration + "r" -> pure DHSRegistration + "commands" -> pure DHSCommands + "c" -> pure DHSCommands + _ -> fail "bad help section" DCSearchNext_ -> pure DCSearchNext DCAllGroups_ -> pure DCAllGroups DCRecentGroups_ -> pure DCRecentGroups - DCSubmitGroup_ -> fmap DCSubmitGroup . strDecode . encodeUtf8 <$?> (A.takeWhile1 isSpace *> A.takeText) + DCSubmitGroup_ -> fmap DCSubmitGroup . strDecode . encodeUtf8 <$?> (spacesP *> A.takeText) DCConfirmDuplicateGroup_ -> gc DCConfirmDuplicateGroup DCListUserGroups_ -> pure DCListUserGroups DCDeleteGroup_ -> gc DCDeleteGroup @@ -228,7 +241,7 @@ directoryCmdP = DCGroupFilter_ -> do (groupId, displayName_) <- gc_ (,) acceptance_ <- - (A.takeWhile (== ' ') >> A.endOfInput) $> Nothing + (A.takeWhile isSpace >> A.endOfInput) $> Nothing <|> Just <$> (acceptancePresetsP <|> acceptanceFiltersP) pure $ DCGroupFilter groupId displayName_ acceptance_ where @@ -272,15 +285,15 @@ directoryCmdP = where gc f = f <$> (spacesP *> A.decimal) <*> (A.char ':' *> displayNameTextP) gc_ f = f <$> (spacesP *> A.decimal) <*> optional (A.char ':' *> displayNameTextP) - -- wordP = spacesP *> A.takeTill (== ' ') - spacesP = A.takeWhile1 (== ' ') + -- wordP = spacesP *> A.takeTill isSpace + spacesP = A.takeWhile1 isSpace viewName :: Text -> Text -viewName n = if T.any (== ' ') n then "'" <> n <> "'" else n +viewName n = if T.any isSpace n then "'" <> n <> "'" else n directoryCmdTag :: DirectoryCmd r -> Text directoryCmdTag = \case - DCHelp -> "help" + DCHelp _ -> "help" DCSearchGroup _ -> "search" DCSearchNext -> "next" DCAllGroups -> "all" diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs index 054f261b4e..bf998840f6 100644 --- a/apps/simplex-directory-service/src/Directory/Service.hs +++ b/apps/simplex-directory-service/src/Directory/Service.hs @@ -48,6 +48,7 @@ import Simplex.Chat.Bot import Simplex.Chat.Bot.KnownContacts import Simplex.Chat.Controller import Simplex.Chat.Core +import Simplex.Chat.Markdown (FormattedText (..), Format (..), parseMaybeMarkdownList) import Simplex.Chat.Messages import Simplex.Chat.Options import Simplex.Chat.Protocol (MsgContent (..)) @@ -61,6 +62,7 @@ import Simplex.Chat.Types import Simplex.Chat.Types.Shared import Simplex.Chat.View (serializeChatResponse, simplexChatContact, viewContactName, viewGroupName) import Simplex.Messaging.Agent.Store.Common (withTransaction) +import Simplex.Messaging.Agent.Protocol (AConnectionRequestUri (..), SConnectionMode (..), sameConnReqContact) import qualified Simplex.Messaging.Agent.Store.DB as DB import Simplex.Messaging.Encoding.String import Simplex.Messaging.TMap (TMap) @@ -185,13 +187,13 @@ useMemberFilter img_ = \case Nothing -> False readBlockedWordsConfig :: DirectoryOpts -> IO BlockedWordsConfig -readBlockedWordsConfig DirectoryOpts {blockedFragmentsFile, blockedWordsFile, nameSpellingFile, blockedExtensionRules} = do +readBlockedWordsConfig DirectoryOpts {blockedFragmentsFile, blockedWordsFile, nameSpellingFile, blockedExtensionRules, testing} = do extensionRules <- maybe (pure []) (fmap read . readFile) blockedExtensionRules spelling <- maybe (pure M.empty) (fmap (M.fromList . read) . readFile) nameSpellingFile blockedFragments <- S.fromList <$> maybe (pure []) (fmap T.lines . T.readFile) blockedFragmentsFile bws <- maybe (pure []) (fmap lines . readFile) blockedWordsFile let blockedWords = S.fromList $ concatMap (wordVariants extensionRules) bws - putStrLn $ "Blocked fragments: " <> show (length blockedFragments) <> ", blocked words: " <> show (length blockedWords) <> ", spelling rules: " <> show (M.size spelling) + unless testing $ putStrLn $ "Blocked fragments: " <> show (length blockedFragments) <> ", blocked words: " <> show (length blockedWords) <> ", spelling rules: " <> show (M.size spelling) pure BlockedWordsConfig {blockedFragments, blockedWords, extensionRules, spelling} directoryServiceEvent :: DirectoryStore -> DirectoryOpts -> ServiceState -> User -> ChatController -> ChatResponse -> IO () @@ -200,7 +202,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName DEContactConnected ct -> deContactConnected ct DEGroupInvitation {contact = ct, groupInfo = g, fromMemberRole, memberRole} -> deGroupInvitation ct g fromMemberRole memberRole DEServiceJoinedGroup ctId g owner -> deServiceJoinedGroup ctId g owner - DEGroupUpdated {contactId, fromGroup, toGroup} -> deGroupUpdated contactId fromGroup toGroup + DEGroupUpdated {member, fromGroup, toGroup} -> deGroupUpdated member fromGroup toGroup DEPendingMember g m -> dePendingMember g m DEPendingMemberMsg g m ciId t -> dePendingMemberMsg g m ciId t DEContactRoleChanged g ctId role -> deContactRoleChanged g ctId role @@ -253,17 +255,25 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName getDuplicateGroup GroupInfo {groupId, groupProfile = GroupProfile {displayName, fullName}} = getGroups fullName >>= mapM duplicateGroup where - sameGroup (GroupInfo {groupId = gId, groupProfile = GroupProfile {displayName = n, fullName = fn}}, _) = - gId /= groupId && n == displayName && fn == fullName + sameGroupNotRemoved (g@GroupInfo {groupId = gId, groupProfile = GroupProfile {displayName = n, fullName = fn}}, _) = + gId /= groupId && n == displayName && fn == fullName && not (memberRemoved $ membership g) duplicateGroup [] = pure DGUnique duplicateGroup groups = do - let gs = filter sameGroup groups + let gs = filter sameGroupNotRemoved groups if null gs then pure DGUnique else do (lgs, rgs) <- atomically $ (,) <$> readTVar (listedGroups st) <*> readTVar (reservedGroups st) let reserved = any (\(GroupInfo {groupId = gId}, _) -> gId `S.member` lgs || gId `S.member` rgs) gs - pure $ if reserved then DGReserved else DGRegistered + if reserved + then pure DGReserved + else do + removed <- foldM (\r -> fmap (r &&) . isGroupRemoved) True gs + pure $ if removed then DGUnique else DGRegistered + isGroupRemoved (GroupInfo {groupId = gId}, _) = + getGroupReg st gId >>= \case + Just GroupReg {groupRegStatus} -> groupRemoved <$> readTVarIO groupRegStatus + Nothing -> pure True processInvitation :: Contact -> GroupInfo -> IO () processInvitation ct g@GroupInfo {groupId, groupProfile = GroupProfile {displayName}} = do @@ -354,78 +364,95 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName _ -> notifyOwner gr $ unexpectedError "can't create group link" _ -> notifyOwner gr $ unexpectedError "can't create group link" - deGroupUpdated :: ContactId -> GroupInfo -> GroupInfo -> IO () - deGroupUpdated ctId fromGroup toGroup = do + deGroupUpdated :: GroupMember -> GroupInfo -> GroupInfo -> IO () + deGroupUpdated m@GroupMember {memberProfile = LocalProfile {displayName = mName}} fromGroup toGroup = do logInfo $ "group updated " <> viewGroupName toGroup unless (sameProfile p p') $ do withGroupReg toGroup "group updated" $ \gr -> do let userGroupRef = userGroupReference gr toGroup + byMember = case memberContactId m of + Just ctId | ctId `isOwner` gr -> "" -- group registration owner, not any group owner. + _ -> " by " <> mName -- owner notification from directory will include the name. readTVarIO (groupRegStatus gr) >>= \case GRSPendingConfirmation -> pure () GRSProposed -> pure () GRSPendingUpdate -> groupProfileUpdate >>= \case GPNoServiceLink -> - when (ctId `isOwner` gr) $ notifyOwner gr $ "The profile updated for " <> userGroupRef <> ", but the group link is not added to the welcome message." - GPServiceLinkAdded - | ctId `isOwner` gr -> groupLinkAdded gr - | otherwise -> notifyOwner gr "The group link is added by another group member, your registration will not be processed.\n\nPlease update the group profile yourself." - GPServiceLinkRemoved -> when (ctId `isOwner` gr) $ notifyOwner gr $ "The group link of " <> userGroupRef <> " is removed from the welcome message, please add it." - GPHasServiceLink -> when (ctId `isOwner` gr) $ groupLinkAdded gr + notifyOwner gr $ "The profile updated for " <> userGroupRef <> byMember <> ", but the group link is not added to the welcome message." + GPServiceLinkAdded -> groupLinkAdded gr byMember + GPServiceLinkRemoved -> + notifyOwner gr $ + "The group link of " <> userGroupRef <> " is removed from the welcome message" <> byMember <> ", please add it." + GPHasServiceLink -> groupLinkAdded gr byMember GPServiceLinkError -> do - when (ctId `isOwner` gr) $ notifyOwner gr $ "Error: " <> serviceName <> " has no group link for " <> userGroupRef <> ". Please report the error to the developers." + notifyOwner gr $ + ("Error: " <> serviceName <> " has no group link for " <> userGroupRef) + <> " after profile was updated" <> byMember <> ". Please report the error to the developers." logError $ "Error: no group link for " <> userGroupRef - GRSPendingApproval n -> processProfileChange gr $ n + 1 - GRSActive -> processProfileChange gr 1 - GRSSuspended -> processProfileChange gr 1 - GRSSuspendedBadRoles -> processProfileChange gr 1 + GRSPendingApproval n -> processProfileChange gr byMember $ n + 1 + GRSActive -> processProfileChange gr byMember 1 + GRSSuspended -> processProfileChange gr byMember 1 + GRSSuspendedBadRoles -> processProfileChange gr byMember 1 GRSRemoved -> pure () where - isInfix l d_ = l `T.isInfixOf` fromMaybe "" d_ GroupInfo {groupId, groupProfile = p} = fromGroup GroupInfo {groupProfile = p'} = toGroup sameProfile GroupProfile {displayName = n, fullName = fn, image = i, description = d} GroupProfile {displayName = n', fullName = fn', image = i', description = d'} = n == n' && fn == fn' && i == i' && d == d' - groupLinkAdded gr = do + groupLinkAdded gr byMember = do getDuplicateGroup toGroup >>= \case Nothing -> notifyOwner gr "Error: getDuplicateGroup. Please notify the developers." Just DGReserved -> notifyOwner gr $ groupAlreadyListed toGroup _ -> do let gaId = 1 setGroupStatus st gr $ GRSPendingApproval gaId - notifyOwner gr $ "Thank you! The group link for " <> userGroupReference gr toGroup <> " is added to the welcome message.\nYou will be notified once the group is added to the directory - it may take up to 48 hours." + notifyOwner gr $ + ("Thank you! The group link for " <> userGroupReference gr toGroup <> " is added to the welcome message" <> byMember) + <> ".\nYou will be notified once the group is added to the directory - it may take up to 48 hours." checkRolesSendToApprove gr gaId - processProfileChange gr n' = do + processProfileChange gr byMember n' = do setGroupStatus st gr GRSPendingUpdate let userGroupRef = userGroupReference gr toGroup groupRef = groupReference toGroup groupProfileUpdate >>= \case GPNoServiceLink -> do - notifyOwner gr $ "The group profile is updated " <> userGroupRef <> ", but no link is added to the welcome message.\n\nThe group will remain hidden from the directory until the group link is added and the group is re-approved." + notifyOwner gr $ + ("The group profile is updated for " <> userGroupRef <> byMember <> ", but no link is added to the welcome message.\n\n") + <> "The group will remain hidden from the directory until the group link is added and the group is re-approved." GPServiceLinkRemoved -> do - notifyOwner gr $ "The group link for " <> userGroupRef <> " is removed from the welcome message.\n\nThe group is hidden from the directory until the group link is added and the group is re-approved." + notifyOwner gr $ + ("The group link for " <> userGroupRef <> " is removed from the welcome message" <> byMember) + <> ".\n\nThe group is hidden from the directory until the group link is added and the group is re-approved." notifyAdminUsers $ "The group link is removed from " <> groupRef <> ", de-listed." GPServiceLinkAdded -> do setGroupStatus st gr $ GRSPendingApproval n' - notifyOwner gr $ "The group link is added to " <> userGroupRef <> "!\nIt is hidden from the directory until approved." - notifyAdminUsers $ "The group link is added to " <> groupRef <> "." + notifyOwner gr $ + ("The group link is added to " <> userGroupRef <> byMember) + <> "!\nIt is hidden from the directory until approved." + notifyAdminUsers $ "The group link is added to " <> groupRef <> byMember <> "." checkRolesSendToApprove gr n' GPHasServiceLink -> do setGroupStatus st gr $ GRSPendingApproval n' - notifyOwner gr $ "The group " <> userGroupRef <> " is updated!\nIt is hidden from the directory until approved." - notifyAdminUsers $ "The group " <> groupRef <> " is updated." + notifyOwner gr $ + ("The group " <> userGroupRef <> " is updated" <> byMember) + <> "!\nIt is hidden from the directory until approved." + notifyAdminUsers $ "The group " <> groupRef <> " is updated" <> byMember <> "." checkRolesSendToApprove gr n' GPServiceLinkError -> logError $ "Error: no group link for " <> groupRef <> " pending approval." groupProfileUpdate = profileUpdate <$> sendChatCmd cc (APIGetGroupLink groupId) where profileUpdate = \case CRGroupLink {connReqContact} -> - let groupLink1 = strEncodeTxt connReqContact - groupLink2 = strEncodeTxt $ simplexChatContact connReqContact - hadLinkBefore = groupLink1 `isInfix` description p || groupLink2 `isInfix` description p - hasLinkNow = groupLink1 `isInfix` description p' || groupLink2 `isInfix` description p' + let hadLinkBefore = profileHasGroupLink fromGroup + hasLinkNow = profileHasGroupLink toGroup + profileHasGroupLink GroupInfo {groupProfile = gp} = + maybe False (any ftHasLink) $ parseMaybeMarkdownList =<< description gp + ftHasLink = \case + FormattedText (Just SimplexLink {simplexUri = ACR SCMContact cr'}) _ -> sameConnReqContact connReqContact cr' + _ -> False in if | hadLinkBefore && hasLinkNow -> GPHasServiceLink | hadLinkBefore -> GPServiceLinkRemoved @@ -617,7 +644,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName deUserCommand :: Contact -> ChatItemId -> DirectoryCmd 'DRUser -> IO () deUserCommand ct ciId = \case - DCHelp -> + DCHelp DHSRegistration -> sendMessage cc ct $ "You must be the owner to add the group to the directory:\n\ \1. Invite " @@ -628,7 +655,16 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName <> " bot will create a public group link for the new members to join even when you are offline.\n\ \3. You will then need to add this link to the group welcome message.\n\ \4. Once the link is added, service admins will approve the group (it can take up to 48 hours), and everybody will be able to find it in directory.\n\n\ - \Start from inviting the bot to your group as admin - it will guide you through the process" + \Start from inviting the bot to your group as admin - it will guide you through the process." + DCHelp DHSCommands -> + sendMessage cc ct $ + "*/help commands* - receive this help message.\n\ + \*/help* - how to register your group to be added to directory.\n\ + \*/list* - list the groups you registered.\n\ + \*/delete :* - remove the group you submitted from directory, with _ID_ and _name_ as shown by */list* command.\n\ + \*/role * - view and set default member role for your group.\n\ + \*/filter * - view and set spam filter settings for group.\n\n\ + \To search for groups, send the search text." DCSearchGroup s -> withFoundListedGroups (Just s) $ sendSearchResults s DCSearchNext -> atomically (TM.lookup (contactId' ct) searchRequests) >>= \case @@ -667,10 +703,10 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName "0 registered groups for " <> localDisplayName' ct <> " (" <> tshow (contactId' ct) <> ") out of " <> tshow total <> " registrations" void . forkIO $ forM_ (reverse grs) $ \gr@GroupReg {userGroupRegId} -> sendGroupInfo ct gr userGroupRegId Nothing - DCDeleteGroup ugrId gName -> - withUserGroupReg ugrId gName $ \GroupInfo {groupProfile = GroupProfile {displayName}} gr -> do + DCDeleteGroup gId gName -> + (if isAdmin then withGroupAndReg sendReply else withUserGroupReg) gId gName $ \GroupInfo {groupProfile = GroupProfile {displayName}} gr -> do delGroupReg st gr - sendReply $ "Your group " <> displayName <> " is deleted from the directory" + sendReply $ (if isAdmin then "The group " else "Your group ") <> displayName <> " is deleted from the directory" DCMemberRole gId gName_ mRole_ -> (if isAdmin then withGroupAndReg_ sendReply else withUserGroupReg_) gId gName_ $ \g _gr -> do let GroupInfo {groupProfile = GroupProfile {displayName = n}} = g @@ -802,9 +838,12 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName setGroupStatus st gr GRSActive let approved = "The group " <> userGroupReference' gr n <> " is approved" notifyOwner gr $ - (approved <> " and listed in directory!\n") + (approved <> " and listed in directory - please moderate it!\n") <> "Please note: if you change the group profile it will be hidden from directory until it is re-approved.\n\n" - <> ("Use */filter " <> tshow ugrId <> "* to configure anti-spam filter and */role " <> tshow ugrId <> "* to set default member role.") + <> "Supported commands:\n" + <> ("- */filter " <> tshow ugrId <> "* - to configure anti-spam filter.\n") + <> ("- */role " <> tshow ugrId <> "* - to set default member role.\n") + <> "- */help commands* - other commands." invited <- forM ownersGroup $ \og@KnownGroup {localDisplayName = ogName} -> do inviteToOwnersGroup og gr $ \case diff --git a/apps/simplex-directory-service/src/Directory/Store.hs b/apps/simplex-directory-service/src/Directory/Store.hs index fed52f494f..031d05fd49 100644 --- a/apps/simplex-directory-service/src/Directory/Store.hs +++ b/apps/simplex-directory-service/src/Directory/Store.hs @@ -25,6 +25,7 @@ module Directory.Store filterListedGroups, groupRegStatusText, pendingApproval, + groupRemoved, fromCustomData, toCustomData, noJoinFilter, @@ -139,13 +140,19 @@ data GroupRegStatus | GRSSuspended | GRSSuspendedBadRoles | GRSRemoved + deriving (Show) pendingApproval :: GroupRegStatus -> Bool pendingApproval = \case GRSPendingApproval _ -> True _ -> False -data DirectoryStatus = DSListed | DSReserved | DSRegistered +groupRemoved :: GroupRegStatus -> Bool +groupRemoved = \case + GRSRemoved -> True + _ -> False + +data DirectoryStatus = DSListed | DSReserved | DSRegistered | DSRemoved groupRegStatusText :: GroupRegStatus -> Text groupRegStatusText = \case @@ -163,6 +170,7 @@ grDirectoryStatus = \case GRSActive -> DSListed GRSSuspended -> DSReserved GRSSuspendedBadRoles -> DSReserved + GRSRemoved -> DSRemoved _ -> DSRegistered $(JQ.deriveJSON (enumJSON $ dropPrefix "PC") ''ProfileCondition) @@ -200,8 +208,9 @@ addGroupReg st ct GroupInfo {groupId} grStatus = do | otherwise = mx delGroupReg :: DirectoryStore -> GroupReg -> IO () -delGroupReg st GroupReg {dbGroupId = gId} = do +delGroupReg st GroupReg {dbGroupId = gId, groupRegStatus} = do logGDelete st gId + atomically $ writeTVar groupRegStatus GRSRemoved atomically $ unlistGroup st gId atomically $ modifyTVar' (groupRegs st) $ filter ((gId ==) . dbGroupId) @@ -216,6 +225,7 @@ setGroupStatus st gr grStatus = do DSListed -> listGroup DSReserved -> reserveGroup DSRegistered -> unlistGroup + DSRemoved -> unlistGroup setGroupRegOwner :: DirectoryStore -> GroupReg -> GroupMember -> IO () setGroupRegOwner st gr owner = do @@ -390,6 +400,7 @@ mkDirectoryStore h groups = DSListed -> (grs', S.insert gId listed, reserved) DSReserved -> (grs', listed, S.insert gId reserved) DSRegistered -> (grs', listed, reserved) + DSRemoved -> (grs, listed, reserved) mkDirectoryStore_ :: Maybe Handle -> ([GroupReg], Set GroupId, Set GroupId) -> IO DirectoryStore mkDirectoryStore_ h (grs, listed, reserved) = do diff --git a/src/Simplex/Chat/Markdown.hs b/src/Simplex/Chat/Markdown.hs index 0d1432c7e5..3ed2a8fa0b 100644 --- a/src/Simplex/Chat/Markdown.hs +++ b/src/Simplex/Chat/Markdown.hs @@ -49,7 +49,7 @@ data Format | Secret | Colored {color :: FormatColor} | Uri - | SimplexLink {linkType :: SimplexLinkType, simplexUri :: Text, smpHosts :: NonEmpty Text} + | SimplexLink {linkType :: SimplexLinkType, simplexUri :: AConnectionRequestUri, smpHosts :: NonEmpty Text} | Mention {memberName :: Text} | Email | Phone @@ -255,12 +255,12 @@ markdownP = mconcat <$> A.many' fragmentP noFormat = pure . unmarked simplexUriFormat :: AConnectionRequestUri -> Format simplexUriFormat = \case - ACR _ (CRContactUri crData) -> - let uri = safeDecodeUtf8 . strEncode $ CRContactUri crData {crScheme = SSSimplex} - in SimplexLink (linkType' crData) uri $ uriHosts crData - ACR _ (CRInvitationUri crData e2e) -> - let uri = safeDecodeUtf8 . strEncode $ CRInvitationUri crData {crScheme = SSSimplex} e2e - in SimplexLink XLInvitation uri $ uriHosts crData + ACR m (CRContactUri crData) -> + let cReq = ACR m $ CRContactUri crData {crScheme = SSSimplex} + in SimplexLink (linkType' crData) cReq $ uriHosts crData + ACR m (CRInvitationUri crData e2e) -> + let cReq = ACR m $ CRInvitationUri crData {crScheme = SSSimplex} e2e + in SimplexLink XLInvitation cReq $ uriHosts crData where uriHosts ConnReqUriData {crSmpQueues} = L.map (safeDecodeUtf8 . strEncode) $ sconcat $ L.map (host . qServer) crSmpQueues linkType' ConnReqUriData {crClientData} = case crClientData >>= decodeJSON of diff --git a/tests/Bots/DirectoryTests.hs b/tests/Bots/DirectoryTests.hs index 6601032a79..92d70727e3 100644 --- a/tests/Bots/DirectoryTests.hs +++ b/tests/Bots/DirectoryTests.hs @@ -34,6 +34,7 @@ directoryServiceTests = do it "should register group" testDirectoryService it "should suspend and resume group, send message to owner" testSuspendResume it "should delete group registration" testDeleteGroup + it "admin should delete group registration" testDeleteGroupAdmin it "should change initial member role" testSetRole it "should join found group via link" testJoinGroup it "should support group names with spaces" testGroupNameWithSpaces @@ -52,10 +53,12 @@ directoryServiceTests = do it "should NOT allow approving if roles are incorrect" testNotApprovedBadRoles describe "should require re-approval if profile is changed by" $ do it "the registration owner" testRegOwnerChangedProfile - it "another owner" testAnotherOwnerChangedProfile -- TODO fix - doesn't work if another owner is not connected as contact + it "another owner" testAnotherOwnerChangedProfile + it "another owner not connected to directory" testNotConnectedOwnerChangedProfile describe "should require profile update if group link is removed by " $ do it "the registration owner" testRegOwnerRemovedLink - it "another owner" testAnotherOwnerRemovedLink -- TODO fix - doesn't work if another owner is not connected as contact + it "another owner" testAnotherOwnerRemovedLink + it "another owner not connected to directory" testNotConnectedOwnerRemovedLink describe "duplicate groups (same display name and full name)" $ do it "should ask for confirmation if a duplicate group is submitted" testDuplicateAskConfirmation it "should prohibit registration if a duplicate group is listed" testDuplicateProhibitRegistration @@ -186,10 +189,13 @@ testDirectoryService ps = superUser #> "@SimpleX-Directory /approve 1:PSA 1" superUser <# "SimpleX-Directory> > /approve 1:PSA 1" superUser <## " Group approved!" - bob <# "SimpleX-Directory> The group ID 1 (PSA) is approved and listed in directory!" + bob <# "SimpleX-Directory> The group ID 1 (PSA) is approved and listed in directory - please moderate it!" bob <## "Please note: if you change the group profile it will be hidden from directory until it is re-approved." bob <## "" - bob <## "Use /filter 1 to configure anti-spam filter and /role 1 to set default member role." + bob <## "Supported commands:" + bob <## "- /filter 1 - to configure anti-spam filter." + bob <## "- /role 1 - to set default member role." + bob <## "- /help commands - other commands." search bob "privacy" welcomeWithLink' search bob "security" welcomeWithLink' cath `connectVia` dsLink @@ -266,6 +272,38 @@ testDeleteGroup ps = bob <## " Your group privacy is deleted from the directory" groupNotFound bob "privacy" +testDeleteGroupAdmin :: HasCallStack => TestParams -> IO () +testDeleteGroupAdmin ps = + withDirectoryService ps $ \superUser dsLink -> + withNewTestChat ps "bob" bobProfile $ \bob -> do + withNewTestChat ps "cath" cathProfile $ \cath -> do + bob `connectVia` dsLink + registerGroup superUser bob "privacy" "Privacy" + cath `connectVia` dsLink + registerGroupId superUser cath "security" "Security" 2 1 + groupFound bob "privacy" + groupFound bob "security" + listUserGroup bob "privacy" "Privacy" + listUserGroup cath "security" "Security" + superUser #> "@SimpleX-Directory /last" + superUser <# "SimpleX-Directory> > /last" + superUser <## " 2 registered group(s)" + memberGroupListing superUser bob 1 "privacy" "Privacy" 2 "active" + memberGroupListing superUser cath 2 "security" "Security" 2 "active" + -- trying to register group with the same name + submitGroup bob "security" "Security" + bob <# "SimpleX-Directory> The group security (Security) is already listed in the directory, please choose another name." + bob ##> "/d #security" + bob <## "#security: you deleted the group" + -- admin can delete the group + superUser #> "@SimpleX-Directory /delete 2:security" + superUser <# "SimpleX-Directory> > /delete 2:security" + superUser <## " The group security is deleted from the directory" + groupFound bob "privacy" + groupNotFound bob "security" + -- another user can register the group with the same name + registerGroupId superUser bob "security" "Security" 4 1 + testSetRole :: HasCallStack => TestParams -> IO () testSetRole ps = withDirectoryService ps $ \superUser dsLink -> @@ -726,13 +764,34 @@ testAnotherOwnerChangedProfile ps = cath <## "full name changed to: Privacy and Security" bob <## "cath updated group #privacy:" bob <## "full name changed to: Privacy and Security" - bob <# "SimpleX-Directory> The group ID 1 (privacy) is updated!" + bob <# "SimpleX-Directory> The group ID 1 (privacy) is updated by cath!" bob <## "It is hidden from the directory until approved." groupNotFound cath "privacy" - superUser <# "SimpleX-Directory> The group ID 1 (privacy) is updated." + superUser <# "SimpleX-Directory> The group ID 1 (privacy) is updated by cath." reapproveGroup 3 superUser bob groupFoundN 3 cath "privacy" +testNotConnectedOwnerChangedProfile :: HasCallStack => TestParams -> IO () +testNotConnectedOwnerChangedProfile ps = + withDirectoryService ps $ \superUser dsLink -> + withNewTestChat ps "bob" bobProfile $ \bob -> + withNewTestChat ps "cath" cathProfile $ \cath -> do + withNewTestChat ps "dan" danProfile $ \dan -> do + bob `connectVia` dsLink + dan `connectVia` dsLink + registerGroup superUser bob "privacy" "Privacy" + addCathAsOwner bob cath + cath ##> "/gp privacy privacy Privacy and Security" + cath <## "full name changed to: Privacy and Security" + bob <## "cath updated group #privacy:" + bob <## "full name changed to: Privacy and Security" + bob <# "SimpleX-Directory> The group ID 1 (privacy) is updated by cath!" + bob <## "It is hidden from the directory until approved." + groupNotFound dan "privacy" + superUser <# "SimpleX-Directory> The group ID 1 (privacy) is updated by cath." + reapproveGroup 3 superUser bob + groupFoundN 3 dan "privacy" + testRegOwnerRemovedLink :: HasCallStack => TestParams -> IO () testRegOwnerRemovedLink ps = withDirectoryService ps $ \superUser dsLink -> @@ -758,14 +817,15 @@ testRegOwnerRemovedLink ps = cath <## "contact and member are merged: SimpleX-Directory_1, #privacy SimpleX-Directory" cath <## "use @SimpleX-Directory to send messages" groupNotFound cath "privacy" - bob ##> ("/set welcome #privacy " <> welcomeWithLink) + let withChangedLink = T.unpack $ T.replace "contact#/?v=2-7&" "contact#/?v=3-7&" $ T.pack welcomeWithLink + bob ##> ("/set welcome #privacy " <> withChangedLink) bob <## "description changed to:" - bob <## welcomeWithLink + bob <## withChangedLink bob <# "SimpleX-Directory> Thank you! The group link for ID 1 (privacy) is added to the welcome message." bob <## "You will be notified once the group is added to the directory - it may take up to 48 hours." cath <## "bob updated group #privacy:" cath <## "description changed to:" - cath <## welcomeWithLink + cath <## withChangedLink reapproveGroup 3 superUser bob groupFoundN 3 cath "privacy" @@ -789,7 +849,7 @@ testAnotherOwnerRemovedLink ps = bob <## "cath updated group #privacy:" bob <## "description changed to:" bob <## "Welcome!" - bob <# "SimpleX-Directory> The group link for ID 1 (privacy) is removed from the welcome message." + bob <# "SimpleX-Directory> The group link for ID 1 (privacy) is removed from the welcome message by cath." bob <## "" bob <## "The group is hidden from the directory until the group link is added and the group is re-approved." superUser <# "SimpleX-Directory> The group link is removed from ID 1 (privacy), de-listed." @@ -800,20 +860,55 @@ testAnotherOwnerRemovedLink ps = bob <## "cath updated group #privacy:" bob <## "description changed to:" bob <## welcomeWithLink - bob <# "SimpleX-Directory> The group link is added by another group member, your registration will not be processed." - bob <## "" - bob <## "Please update the group profile yourself." - bob ##> ("/set welcome #privacy " <> welcomeWithLink <> " - welcome!") - bob <## "description changed to:" - bob <## (welcomeWithLink <> " - welcome!") - bob <# "SimpleX-Directory> Thank you! The group link for ID 1 (privacy) is added to the welcome message." + bob <# "SimpleX-Directory> Thank you! The group link for ID 1 (privacy) is added to the welcome message by cath." bob <## "You will be notified once the group is added to the directory - it may take up to 48 hours." - cath <## "bob updated group #privacy:" - cath <## "description changed to:" - cath <## (welcomeWithLink <> " - welcome!") reapproveGroup 3 superUser bob groupFoundN 3 cath "privacy" +testNotConnectedOwnerRemovedLink :: HasCallStack => TestParams -> IO () +testNotConnectedOwnerRemovedLink ps = + withDirectoryService ps $ \superUser dsLink -> + withNewTestChat ps "bob" bobProfile $ \bob -> + withNewTestChat ps "cath" cathProfile $ \cath -> do + withNewTestChat ps "dan" danProfile $ \dan -> do + bob `connectVia` dsLink + dan `connectVia` dsLink + registerGroup superUser bob "privacy" "Privacy" + addCathAsOwner bob cath + bob ##> "/show welcome #privacy" + bob <## "Welcome message:" + welcomeWithLink <- getTermLine bob + cath ##> "/set welcome #privacy Welcome!" + cath <## "description changed to:" + cath <## "Welcome!" + bob <## "cath updated group #privacy:" + bob <## "description changed to:" + bob <## "Welcome!" + bob <# "SimpleX-Directory> The group link for ID 1 (privacy) is removed from the welcome message by cath." + bob <## "" + bob <## "The group is hidden from the directory until the group link is added and the group is re-approved." + superUser <# "SimpleX-Directory> The group link is removed from ID 1 (privacy), de-listed." + groupNotFound dan "privacy" + cath ##> ("/set welcome #privacy " <> welcomeWithLink) + cath <## "description changed to:" + cath <## welcomeWithLink + bob <## "cath updated group #privacy:" + bob <## "description changed to:" + bob <## welcomeWithLink + -- bob <# "SimpleX-Directory> The group link is added by another group member, your registration will not be processed." + -- bob <## "" + -- bob <## "Please update the group profile yourself." + -- bob ##> ("/set welcome #privacy " <> welcomeWithLink <> " - welcome!") + -- bob <## "description changed to:" + -- bob <## (welcomeWithLink <> " - welcome!") + bob <# "SimpleX-Directory> Thank you! The group link for ID 1 (privacy) is added to the welcome message by cath." + bob <## "You will be notified once the group is added to the directory - it may take up to 48 hours." + -- cath <## "bob updated group #privacy:" + -- cath <## "description changed to:" + -- cath <## (welcomeWithLink <> " - welcome!") + reapproveGroup 3 superUser bob + groupFoundN 3 dan "privacy" + testDuplicateAskConfirmation :: HasCallStack => TestParams -> IO () testDuplicateAskConfirmation ps = withDirectoryService ps $ \superUser dsLink -> @@ -937,14 +1032,7 @@ testListUserGroups ps = cath <## "use @SimpleX-Directory to send messages" registerGroupId superUser bob "security" "Security" 2 2 registerGroupId superUser cath "anonymity" "Anonymity" 3 1 - cath #> "@SimpleX-Directory /list" - cath <# "SimpleX-Directory> > /list" - cath <## " 1 registered group(s)" - cath <# "SimpleX-Directory> 1. anonymity (Anonymity)" - cath <## "Welcome message:" - cath <##. "Link to join the group anonymity: " - cath <## "2 members" - cath <## "Status: active" + listUserGroup cath "anonymity" "Anonymity" -- with de-listed group groupFound cath "anonymity" cath ##> "/mr anonymity SimpleX-Directory member" @@ -1076,27 +1164,11 @@ testCaptcha _ps = do listGroups :: HasCallStack => TestCC -> TestCC -> TestCC -> IO () listGroups superUser bob cath = do - bob #> "@SimpleX-Directory /list" - bob <# "SimpleX-Directory> > /list" - bob <## " 2 registered group(s)" - bob <# "SimpleX-Directory> 1. privacy (Privacy)" - bob <## "Welcome message:" - bob <##. "Link to join the group privacy: " - bob <## "3 members" - bob <## "Status: active" - bob <# "SimpleX-Directory> 2. security (Security)" - bob <## "Welcome message:" - bob <##. "Link to join the group security: " - bob <## "2 members" - bob <## "Status: active" - cath #> "@SimpleX-Directory /list" - cath <# "SimpleX-Directory> > /list" - cath <## " 1 registered group(s)" - cath <# "SimpleX-Directory> 1. anonymity (Anonymity)" - cath <## "Welcome message:" - cath <##. "Link to join the group anonymity: " - cath <## "2 members" - cath <## "Status: suspended because roles changed" + sendListCommand bob 2 + groupListing bob 1 "privacy" "Privacy" 3 "active" + groupListing bob 2 "security" "Security" 2 "active" + sendListCommand cath 1 + groupListing cath 1 "anonymity" "Anonymity" 2 "suspended because roles changed" -- superuser lists all groups bob #> "@SimpleX-Directory /last" bob <# "SimpleX-Directory> > /last" @@ -1104,34 +1176,42 @@ listGroups superUser bob cath = do superUser #> "@SimpleX-Directory /last" superUser <# "SimpleX-Directory> > /last" superUser <## " 3 registered group(s)" - superUser <# "SimpleX-Directory> 1. privacy (Privacy)" - superUser <## "Welcome message:" - superUser <##. "Link to join the group privacy: " - superUser <## "Owner: bob" - superUser <## "3 members" - superUser <## "Status: active" - superUser <# "SimpleX-Directory> 2. security (Security)" - superUser <## "Welcome message:" - superUser <##. "Link to join the group security: " - superUser <## "Owner: bob" - superUser <## "2 members" - superUser <## "Status: active" - superUser <# "SimpleX-Directory> 3. anonymity (Anonymity)" - superUser <## "Welcome message:" - superUser <##. "Link to join the group anonymity: " - superUser <## "Owner: cath" - superUser <## "2 members" - superUser <## "Status: suspended because roles changed" + memberGroupListing superUser bob 1 "privacy" "Privacy" 3 "active" + memberGroupListing superUser bob 2 "security" "Security" 2 "active" + memberGroupListing superUser cath 3 "anonymity" "Anonymity" 2 "suspended because roles changed" -- showing last 1 group superUser #> "@SimpleX-Directory /last 1" superUser <# "SimpleX-Directory> > /last 1" superUser <## " 3 registered group(s), showing the last 1" - superUser <# "SimpleX-Directory> 3. anonymity (Anonymity)" - superUser <## "Welcome message:" - superUser <##. "Link to join the group anonymity: " - superUser <## "Owner: cath" - superUser <## "2 members" - superUser <## "Status: suspended because roles changed" + memberGroupListing superUser cath 3 "anonymity" "Anonymity" 2 "suspended because roles changed" + +listUserGroup :: HasCallStack => TestCC -> String -> String -> IO () +listUserGroup u n fn = do + sendListCommand u 1 + groupListing u 1 n fn 2 "active" + +sendListCommand :: HasCallStack => TestCC -> Int -> IO () +sendListCommand u count = do + u #> "@SimpleX-Directory /list" + u <# "SimpleX-Directory> > /list" + u <## (" " <> show count <> " registered group(s)") + +groupListing :: HasCallStack => TestCC -> Int -> String -> String -> Int -> String -> IO () +groupListing u = groupListing_ u Nothing + +memberGroupListing :: HasCallStack => TestCC -> TestCC -> Int -> String -> String -> Int -> String -> IO () +memberGroupListing su owner = groupListing_ su (Just owner) + +groupListing_ :: HasCallStack => TestCC -> Maybe TestCC -> Int -> String -> String -> Int -> String -> IO () +groupListing_ su owner_ gId n fn count status = do + su <# ("SimpleX-Directory> " <> show gId <> ". " <> n <> " (" <> fn <> ")") + su <## "Welcome message:" + su <##. ("Link to join the group " <> n <> ": ") + forM_ owner_ $ \owner -> do + ownerName <- userName owner + su <## ("Owner: " <> ownerName) + su <## (show count <> " members") + su <## ("Status: " <> status) reapproveGroup :: HasCallStack => Int -> TestCC -> TestCC -> IO () reapproveGroup count superUser bob = do @@ -1146,10 +1226,13 @@ reapproveGroup count superUser bob = do superUser #> "@SimpleX-Directory /approve 1:privacy 1" superUser <# "SimpleX-Directory> > /approve 1:privacy 1" superUser <## " Group approved!" - bob <# "SimpleX-Directory> The group ID 1 (privacy) is approved and listed in directory!" + bob <# "SimpleX-Directory> The group ID 1 (privacy) is approved and listed in directory - please moderate it!" bob <## "Please note: if you change the group profile it will be hidden from directory until it is re-approved." bob <## "" - bob <## "Use /filter 1 to configure anti-spam filter and /role 1 to set default member role." + bob <## "Supported commands:" + bob <## "- /filter 1 - to configure anti-spam filter." + bob <## "- /role 1 - to set default member role." + bob <## "- /help commands - other commands." addCathAsOwner :: HasCallStack => TestCC -> TestCC -> IO () addCathAsOwner bob cath = do @@ -1293,10 +1376,13 @@ approveRegistrationId su u n gId ugId = do su #> ("@SimpleX-Directory " <> approve) su <# ("SimpleX-Directory> > " <> approve) su <## " Group approved!" - u <# ("SimpleX-Directory> The group ID " <> show ugId <> " (" <> n <> ") is approved and listed in directory!") + u <# ("SimpleX-Directory> The group ID " <> show ugId <> " (" <> n <> ") is approved and listed in directory - please moderate it!") u <## "Please note: if you change the group profile it will be hidden from directory until it is re-approved." u <## "" - u <## ("Use /filter " <> show ugId <> " to configure anti-spam filter and /role " <> show ugId <> " to set default member role.") + u <## "Supported commands:" + u <## ("- /filter " <> show ugId <> " - to configure anti-spam filter.") + u <## ("- /role " <> show ugId <> " - to set default member role.") + u <## "- /help commands - other commands." connectVia :: TestCC -> String -> IO () u `connectVia` dsLink = do diff --git a/tests/MarkdownTests.hs b/tests/MarkdownTests.hs index ec4e336fe9..fc872f05b1 100644 --- a/tests/MarkdownTests.hs +++ b/tests/MarkdownTests.hs @@ -8,7 +8,9 @@ module MarkdownTests where import Data.List.NonEmpty (NonEmpty) import Data.Text (Text) import qualified Data.Text as T +import Data.Text.Encoding (encodeUtf8) import Simplex.Chat.Markdown +import Simplex.Messaging.Encoding.String import System.Console.ANSI.Types import Test.Hspec @@ -169,7 +171,12 @@ uri :: Text -> Markdown uri = Markdown $ Just Uri simplexLink :: SimplexLinkType -> Text -> NonEmpty Text -> Text -> Markdown -simplexLink linkType simplexUri smpHosts = Markdown $ Just SimplexLink {linkType, simplexUri, smpHosts} +simplexLink linkType uriText smpHosts t = Markdown (simplexLinkFormat linkType uriText smpHosts) t + +simplexLinkFormat :: SimplexLinkType -> Text -> NonEmpty Text -> Maybe Format +simplexLinkFormat linkType uriText smpHosts = case strDecode $ encodeUtf8 uriText of + Right simplexUri -> Just SimplexLink {linkType, simplexUri, smpHosts} + Left e -> error e textWithUri :: Spec textWithUri = describe "text with Uri" do @@ -275,6 +282,6 @@ multilineMarkdownList = describe "multiline markdown" do it "multiline with simplex link" do ("https://simplex.chat" <> inv <> "\ntext") <<==>> - [ FormattedText (Just $ SimplexLink XLInvitation ("simplex:" <> inv) ["smp.simplex.im"]) ("https://simplex.chat" <> inv), + [ FormattedText (simplexLinkFormat XLInvitation ("simplex:" <> inv) ["smp.simplex.im"]) ("https://simplex.chat" <> inv), "\ntext" ] From 8766891124a23269359c725b509b4c201218ea7f Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 12 Apr 2025 20:07:27 +0100 Subject: [PATCH 503/567] core: 6.3.2.0 (simplexmq 6.3.2.0) --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cabal.project b/cabal.project index 0554d14ec8..14cccb4f1f 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: aace3fd2fb146097304b09ef07ff613a8b255f67 + tag: 9abc0fa88dd70a7e30a041697335bb663c1140b7 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 41b45e9ddc..326306cf85 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."aace3fd2fb146097304b09ef07ff613a8b255f67" = "0iqdarkvlakk4xmrqsg62z0vhs3kwm02l8vpr383vf8q2hd7ky75"; + "https://github.com/simplex-chat/simplexmq.git"."9abc0fa88dd70a7e30a041697335bb663c1140b7" = "0gaqqvhb5s9xw5mq2iy8swp7w34zrkwkbjlyhggz2q9nr5680z84"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index f3095e7fce..ae7294a39d 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.3.1.1 +version: 6.3.2.0 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From eae281df60a8d92ea774c769304cbb9da2dc5efe Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 12 Apr 2025 21:53:53 +0100 Subject: [PATCH 504/567] 6.3.2: ios 272, android 283, desktop 98 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 56 +++++++++++----------- apps/multiplatform/gradle.properties | 8 ++-- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 5e6148758c..096d65cd64 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -174,8 +174,8 @@ 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; }; 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; }; - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.1-AtbMdJukKDu9yojMiV9pEU-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.1-AtbMdJukKDu9yojMiV9pEU-ghc9.6.3.a */; }; - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.1-AtbMdJukKDu9yojMiV9pEU.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.1-AtbMdJukKDu9yojMiV9pEU.a */; }; + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.2.0-m8oPCo81q3CiwhMXvT868-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.2.0-m8oPCo81q3CiwhMXvT868-ghc9.6.3.a */; }; + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.2.0-m8oPCo81q3CiwhMXvT868.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.2.0-m8oPCo81q3CiwhMXvT868.a */; }; 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; @@ -531,8 +531,8 @@ 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.1-AtbMdJukKDu9yojMiV9pEU-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.1.1-AtbMdJukKDu9yojMiV9pEU-ghc9.6.3.a"; sourceTree = ""; }; - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.1-AtbMdJukKDu9yojMiV9pEU.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.1.1-AtbMdJukKDu9yojMiV9pEU.a"; sourceTree = ""; }; + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.2.0-m8oPCo81q3CiwhMXvT868-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.2.0-m8oPCo81q3CiwhMXvT868-ghc9.6.3.a"; sourceTree = ""; }; + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.2.0-m8oPCo81q3CiwhMXvT868.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.2.0-m8oPCo81q3CiwhMXvT868.a"; sourceTree = ""; }; 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; @@ -688,8 +688,8 @@ 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */, 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */, 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */, - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.1-AtbMdJukKDu9yojMiV9pEU-ghc9.6.3.a in Frameworks */, - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.1-AtbMdJukKDu9yojMiV9pEU.a in Frameworks */, + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.2.0-m8oPCo81q3CiwhMXvT868-ghc9.6.3.a in Frameworks */, + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.2.0-m8oPCo81q3CiwhMXvT868.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -774,8 +774,8 @@ 64C829992D54AEEE006B9E89 /* libffi.a */, 64C829982D54AEED006B9E89 /* libgmp.a */, 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */, - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.1-AtbMdJukKDu9yojMiV9pEU-ghc9.6.3.a */, - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.1.1-AtbMdJukKDu9yojMiV9pEU.a */, + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.2.0-m8oPCo81q3CiwhMXvT868-ghc9.6.3.a */, + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.2.0-m8oPCo81q3CiwhMXvT868.a */, ); path = Libraries; sourceTree = ""; @@ -1963,7 +1963,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 271; + CURRENT_PROJECT_VERSION = 272; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1988,7 +1988,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES_THIN; - MARKETING_VERSION = 6.3.1; + MARKETING_VERSION = 6.3.2; OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000"; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; @@ -2013,7 +2013,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 271; + CURRENT_PROJECT_VERSION = 272; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2038,7 +2038,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.1; + MARKETING_VERSION = 6.3.2; OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000"; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; @@ -2055,11 +2055,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 271; + CURRENT_PROJECT_VERSION = 272; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.3.1; + MARKETING_VERSION = 6.3.2; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2075,11 +2075,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 271; + CURRENT_PROJECT_VERSION = 272; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.3.1; + MARKETING_VERSION = 6.3.2; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2100,7 +2100,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 271; + CURRENT_PROJECT_VERSION = 272; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2115,7 +2115,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.1; + MARKETING_VERSION = 6.3.2; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2137,7 +2137,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 271; + CURRENT_PROJECT_VERSION = 272; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2152,7 +2152,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.1; + MARKETING_VERSION = 6.3.2; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2174,7 +2174,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 271; + CURRENT_PROJECT_VERSION = 272; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2200,7 +2200,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.1; + MARKETING_VERSION = 6.3.2; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2225,7 +2225,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 271; + CURRENT_PROJECT_VERSION = 272; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2251,7 +2251,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.1; + MARKETING_VERSION = 6.3.2; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2276,7 +2276,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 271; + CURRENT_PROJECT_VERSION = 272; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2291,7 +2291,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.3.1; + MARKETING_VERSION = 6.3.2; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2310,7 +2310,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 271; + CURRENT_PROJECT_VERSION = 272; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2325,7 +2325,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.3.1; + MARKETING_VERSION = 6.3.2; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 50e49c7b26..1e320972be 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -24,11 +24,11 @@ android.nonTransitiveRClass=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.3.1 -android.version_code=281 +android.version_name=6.3.2 +android.version_code=283 -desktop.version_name=6.3.1 -desktop.version_code=97 +desktop.version_name=6.3.2 +desktop.version_code=98 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From 14d9240995b8d1ea3990cc2f9660d5098e648f05 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sun, 13 Apr 2025 10:50:06 +0100 Subject: [PATCH 505/567] docs: correction to command --- docs/SERVER.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/SERVER.md b/docs/SERVER.md index 4ddfb68e63..57f49b5588 100644 --- a/docs/SERVER.md +++ b/docs/SERVER.md @@ -531,7 +531,7 @@ To verify server binaries after you downloaded them: 3. Import the key with `gpg --import FB44AF81A45BDE327319797C85107E357D4A17FC`. Key filename should be the same as its fingerprint, but please change it if necessary. -4. Run `gpg --verify --trusted-key _sha256sums.asc _sha256sums`. It should print: +4. Run `gpg --verify _sha256sums.asc _sha256sums`. It should print: > Good signature from "SimpleX Chat " From 38c2529d8b2919f7966f593dbdbf5d7c35d54ee2 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Mon, 14 Apr 2025 16:01:22 +0000 Subject: [PATCH 506/567] kotlin: refactor chat contexts 1 (remove functions creating indirection) (#5827) * kotlin: refactor chat contexts 1 * remove withChats * comment * remove withReportChatsIfOpen * remove comment * fix desktop --- .../main/java/chat/simplex/app/SimplexApp.kt | 6 +- .../simplex/common/platform/UI.android.kt | 11 +- .../chat/simplex/common/model/ChatModel.kt | 86 ++-- .../chat/simplex/common/model/SimpleXAPI.kt | 382 ++++++++++-------- .../simplex/common/views/chat/ChatInfoView.kt | 44 +- .../common/views/chat/ChatItemsLoader.kt | 38 +- .../simplex/common/views/chat/ChatView.kt | 186 +++++---- .../simplex/common/views/chat/ComposeView.kt | 25 +- .../common/views/chat/ContactPreferences.kt | 8 +- .../views/chat/SelectableChatItemToolbars.kt | 4 +- .../views/chat/group/AddGroupMembersView.kt | 13 +- .../views/chat/group/GroupChatInfoView.kt | 22 +- .../views/chat/group/GroupMemberInfoView.kt | 103 +++-- .../views/chat/group/GroupPreferences.kt | 11 +- .../views/chat/group/GroupProfileView.kt | 9 +- .../views/chat/group/GroupReportsView.kt | 2 +- .../views/chat/group/WelcomeMessageView.kt | 11 +- .../views/chat/item/CIChatFeatureView.kt | 4 +- .../common/views/chat/item/ChatItemView.kt | 9 +- .../views/chat/item/MarkedDeletedItemView.kt | 3 +- .../views/chatlist/ChatListNavLinkView.kt | 70 ++-- .../common/views/chatlist/ChatListView.kt | 5 +- .../common/views/chatlist/TagListView.kt | 19 +- .../views/contacts/ContactListNavView.kt | 1 - .../common/views/database/DatabaseView.kt | 25 +- .../simplex/common/views/helpers/ModalView.kt | 2 +- .../common/views/newchat/AddGroupView.kt | 10 +- .../common/views/newchat/ConnectPlan.kt | 6 +- .../newchat/ContactConnectionInfoView.kt | 7 +- .../common/views/newchat/NewChatView.kt | 18 +- .../common/views/usersettings/Preferences.kt | 8 +- .../views/usersettings/PrivacySettings.kt | 18 +- .../kotlin/chat/simplex/common/DesktopApp.kt | 12 +- 33 files changed, 598 insertions(+), 580 deletions(-) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt index 1c8209334d..5545595dc6 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt @@ -24,7 +24,6 @@ import chat.simplex.app.views.call.CallActivity import chat.simplex.common.helpers.* import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs -import chat.simplex.common.model.ChatModel.withChats import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.call.* @@ -33,7 +32,6 @@ import chat.simplex.common.views.helpers.* import chat.simplex.common.views.onboarding.OnboardingStage import com.jakewharton.processphoenix.ProcessPhoenix import kotlinx.coroutines.* -import kotlinx.coroutines.flow.map import java.io.* import java.util.* import java.util.concurrent.TimeUnit @@ -94,7 +92,7 @@ class SimplexApp: Application(), LifecycleEventObserver { Lifecycle.Event.ON_START -> { isAppOnForeground = true if (chatModel.chatRunning.value == true) { - withChats { + withContext(Dispatchers.Main) { kotlin.runCatching { val currentUserId = chatModel.currentUser.value?.userId val chats = ArrayList(chatController.apiGetChats(chatModel.remoteHostId())) @@ -107,7 +105,7 @@ class SimplexApp: Application(), LifecycleEventObserver { /** Pass old chatStats because unreadCounter can be changed already while [ChatController.apiGetChats] is executing */ if (indexOfCurrentChat >= 0) chats[indexOfCurrentChat] = chats[indexOfCurrentChat].copy(chatStats = oldStats) } - updateChats(chats) + chatModel.chatsContext.updateChats(chats) } }.onFailure { Log.e(TAG, it.stackTraceToString()) } } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/UI.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/UI.android.kt index a1698ae28a..f56563a1cb 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/UI.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/UI.android.kt @@ -14,12 +14,11 @@ import androidx.compose.runtime.* import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalView import chat.simplex.common.AppScreen -import chat.simplex.common.model.ChatModel.withChats -import chat.simplex.common.model.clear import chat.simplex.common.model.clearAndNotify import chat.simplex.common.views.helpers.* import androidx.compose.ui.platform.LocalContext as LocalContext1 import chat.simplex.res.MR +import kotlinx.coroutines.* actual fun showToast(text: String, timeout: Long) = Toast.makeText(androidAppContext, text, Toast.LENGTH_SHORT).show() @@ -76,13 +75,13 @@ actual class GlobalExceptionsHandler: Thread.UncaughtExceptionHandler { ModalManager.start.closeModal() } else if (chatModel.chatId.value != null) { withApi { - withChats { + withContext(Dispatchers.Main) { // Since no modals are open, the problem is probably in ChatView chatModel.chatId.value = null - chatItems.clearAndNotify() + chatModel.chatsContext.chatItems.clearAndNotify() } - withChats { - chatItems.clearAndNotify() + withContext(Dispatchers.Main) { + chatModel.chatsContext.chatItems.clearAndNotify() } } } else { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 13cdc9f19a..59343cd326 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -67,7 +67,7 @@ object ChatModel { val chatId = mutableStateOf(null) val openAroundItemId: MutableState = mutableStateOf(null) val chatsContext = ChatsContext(null) - val reportsChatsContext = ChatsContext(MsgContentTag.Report) + val secondaryChatsContext = ChatsContext(MsgContentTag.Report) // declaration of chatsContext should be before any other variable that is taken from ChatsContext class and used in the model, otherwise, strange crash with NullPointerException for "this" parameter in random functions val chats: State> = chatsContext.chats // rhId, chatId @@ -170,36 +170,6 @@ object ChatModel { // return true if you handled the click var centerPanelBackgroundClickHandler: (() -> Boolean)? = null - fun chatsForContent(contentTag: MsgContentTag?): State> = when(contentTag) { - null -> chatsContext.chats - MsgContentTag.Report -> reportsChatsContext.chats - else -> TODO() - } - - fun chatItemsForContent(contentTag: MsgContentTag?): State> = when(contentTag) { - null -> chatsContext.chatItems - MsgContentTag.Report -> reportsChatsContext.chatItems - else -> TODO() - } - - fun chatStateForContent(contentTag: MsgContentTag?): ActiveChatState = when(contentTag) { - null -> chatsContext.chatState - MsgContentTag.Report -> reportsChatsContext.chatState - else -> TODO() - } - - fun chatItemsChangesListenerForContent(contentTag: MsgContentTag?): ChatItemsChangesListener? = when(contentTag) { - null -> chatsContext.chatItemsChangesListener - MsgContentTag.Report -> reportsChatsContext.chatItemsChangesListener - else -> TODO() - } - - fun setChatItemsChangeListenerForContent(listener: ChatItemsChangesListener?, contentTag: MsgContentTag?) = when(contentTag) { - null -> chatsContext.chatItemsChangesListener = listener - MsgContentTag.Report -> reportsChatsContext.chatItemsChangesListener = listener - else -> TODO() - } - fun getUser(userId: Long): User? = if (currentUser.value?.userId == userId) { currentUser.value } else { @@ -324,21 +294,6 @@ object ChatModel { } } - // running everything inside the block on main thread. Make sure any heavy computation is moved to a background thread - suspend fun withChats(contentTag: MsgContentTag? = null, action: suspend ChatsContext.() -> T): T = withContext(Dispatchers.Main) { - when { - contentTag == null -> chatsContext.action() - contentTag == MsgContentTag.Report -> reportsChatsContext.action() - else -> TODO() - } - } - - suspend fun withReportsChatsIfOpen(action: suspend ChatsContext.() -> T) = withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.GROUP_REPORTS)) { - reportsChatsContext.action() - } - } - class ChatsContext(private val contentTag: MsgContentTag?) { val chats = mutableStateOf(SnapshotStateList()) /** if you modify the items by adding/removing them, use helpers methods like [addAndNotify], [removeLastAndNotify], [removeAllAndNotify], [clearAndNotify] and so on. @@ -659,8 +614,9 @@ object ChatModel { subject .throttleLatest(2000) .collect { - withChats(contentTag) { - chats.replaceAll(popCollectedChats()) + withContext(Dispatchers.Main) { + val chatsCtx = if (contentTag == null) chatsContext else secondaryChatsContext + chatsCtx.chats.replaceAll(popCollectedChats()) } } } @@ -960,17 +916,17 @@ object ChatModel { suspend fun addLiveDummy(chatInfo: ChatInfo): ChatItem { val cItem = ChatItem.liveDummy(chatInfo is ChatInfo.Direct) - withChats { - chatItems.addAndNotify(cItem, contentTag = null) + withContext(Dispatchers.Main) { + chatsContext.chatItems.addAndNotify(cItem, contentTag = null) } return cItem } fun removeLiveDummy() { - if (chatItemsForContent(null).value.lastOrNull()?.id == ChatItem.TEMP_LIVE_CHAT_ITEM_ID) { + if (chatsContext.chatItems.value.lastOrNull()?.id == ChatItem.TEMP_LIVE_CHAT_ITEM_ID) { withApi { - withChats { - chatItems.removeLastAndNotify(contentTag = null) + withContext(Dispatchers.Main) { + chatsContext.chatItems.removeLastAndNotify(contentTag = null) } } } @@ -1042,9 +998,11 @@ object ChatModel { fun replaceConnReqView(id: String, withId: String) { if (id == showingInvitation.value?.connId) { withApi { - withChats { + withContext(Dispatchers.Main) { showingInvitation.value = null - chatItems.clearAndNotify() + // TODO [contexts] - why does clearAndNotify operates with listeners for both contexts? + // TODO - should it be called for both contexts here instead? + chatsContext.chatItems.clearAndNotify() chatModel.chatId.value = withId } } @@ -1055,9 +1013,10 @@ object ChatModel { fun dismissConnReqView(id: String) = withApi { if (id == showingInvitation.value?.connId) { - withChats { + withContext(Dispatchers.Main) { showingInvitation.value = null - chatItems.clearAndNotify() + // TODO [contexts] see replaceConnReqView + chatsContext.chatItems.clearAndNotify() chatModel.chatId.value = null } // Close NewChatView @@ -2739,7 +2698,8 @@ fun MutableState>.add(index: Int, elem: Chat) { } fun MutableState>.addAndNotify(index: Int, elem: ChatItem, contentTag: MsgContentTag?) { - value = SnapshotStateList().apply { addAll(value); add(index, elem); chatModel.chatItemsChangesListenerForContent(contentTag)?.added(elem.id to elem.isRcvNew, index) } + val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext + value = SnapshotStateList().apply { addAll(value); add(index, elem); chatsCtx.chatItemsChangesListener?.added(elem.id to elem.isRcvNew, index) } } fun MutableState>.add(elem: Chat) { @@ -2751,7 +2711,8 @@ fun MutableList.removeAll(predicate: (T) -> Boolean): Boolean = if (isEmp // Adds item to chatItems and notifies a listener about newly added item fun MutableState>.addAndNotify(elem: ChatItem, contentTag: MsgContentTag?) { - value = SnapshotStateList().apply { addAll(value); add(elem); chatModel.chatItemsChangesListenerForContent(contentTag)?.added(elem.id to elem.isRcvNew, lastIndex) } + val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext + value = SnapshotStateList().apply { addAll(value); add(elem); chatsCtx.chatItemsChangesListener?.added(elem.id to elem.isRcvNew, lastIndex) } } fun MutableState>.addAll(index: Int, elems: List) { @@ -2781,7 +2742,7 @@ fun MutableState>.removeAllAndNotify(block: (ChatIte } if (toRemove.isNotEmpty()) { chatModel.chatsContext.chatItemsChangesListener?.removed(toRemove, value) - chatModel.reportsChatsContext.chatItemsChangesListener?.removed(toRemove, value) + chatModel.secondaryChatsContext.chatItemsChangesListener?.removed(toRemove, value) } } @@ -2801,7 +2762,8 @@ fun MutableState>.removeLastAndNotify(contentTag: Ms val rem = removeLast() removed = Triple(rem.id, remIndex, rem.isRcvNew) } - chatModel.chatItemsChangesListenerForContent(contentTag)?.removed(listOf(removed), value) + val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext + chatsCtx.chatItemsChangesListener?.removed(listOf(removed), value) } fun MutableState>.replaceAll(elems: List) { @@ -2816,7 +2778,7 @@ fun MutableState>.clear() { fun MutableState>.clearAndNotify() { value = SnapshotStateList() chatModel.chatsContext.chatItemsChangesListener?.cleared() - chatModel.reportsChatsContext.chatItemsChangesListener?.cleared() + chatModel.secondaryChatsContext.chatItemsChangesListener?.cleared() } fun State>.asReversed(): MutableList = value.asReversed() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index b04a08e0e1..a090919e6d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -17,8 +17,6 @@ import androidx.compose.ui.unit.dp import chat.simplex.common.model.ChatController.getNetCfg import chat.simplex.common.model.ChatController.setNetCfg import chat.simplex.common.model.ChatModel.changingActiveUserMutex -import chat.simplex.common.model.ChatModel.withChats -import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen import chat.simplex.common.model.MsgContent.MCUnknown import dev.icerock.moko.resources.compose.painterResource import chat.simplex.common.platform.* @@ -544,9 +542,9 @@ object ChatController { } Log.d(TAG, "startChat: started") } else { - withChats { + withContext(Dispatchers.Main) { val chats = apiGetChats(null) - updateChats(chats) + chatModel.chatsContext.updateChats(chats) } Log.d(TAG, "startChat: running") } @@ -627,9 +625,9 @@ object ChatController { val hasUser = chatModel.currentUser.value != null chatModel.userAddress.value = if (hasUser) apiGetUserAddress(rhId) else null chatModel.chatItemTTL.value = if (hasUser) getChatItemTTL(rhId) else ChatItemTTL.None - withChats { + withContext(Dispatchers.Main) { val chats = apiGetChats(rhId) - updateChats(chats) + chatModel.chatsContext.updateChats(chats) } chatModel.userTags.value = apiGetChatTags(rhId).takeIf { hasUser } ?: emptyList() chatModel.activeChatTagFilter.value = null @@ -1490,8 +1488,8 @@ object ChatController { suspend fun deleteChat(chat: Chat, chatDeleteMode: ChatDeleteMode = ChatDeleteMode.Full(notify = true)) { val cInfo = chat.chatInfo if (apiDeleteChat(rh = chat.remoteHostId, type = cInfo.chatType, id = cInfo.apiId, chatDeleteMode = chatDeleteMode)) { - withChats { - removeChat(chat.remoteHostId, cInfo.id) + withContext(Dispatchers.Main) { + chatModel.chatsContext.removeChat(chat.remoteHostId, cInfo.id) } } } @@ -1539,11 +1537,11 @@ object ChatController { withBGApi { val updatedChatInfo = apiClearChat(chat.remoteHostId, chat.chatInfo.chatType, chat.chatInfo.apiId) if (updatedChatInfo != null) { - withChats { - clearChat(chat.remoteHostId, updatedChatInfo) + withContext(Dispatchers.Main) { + chatModel.chatsContext.clearChat(chat.remoteHostId, updatedChatInfo) } - withChats(MsgContentTag.Report) { - clearChat(chat.remoteHostId, updatedChatInfo) + withContext(Dispatchers.Main) { + chatModel.secondaryChatsContext.clearChat(chat.remoteHostId, updatedChatInfo) } ntfManager.cancelNotificationsForChat(chat.chatInfo.id) close?.invoke() @@ -1975,12 +1973,14 @@ object ChatController { val r = sendCmd(rh, CC.ApiJoinGroup(groupId)) when (r) { is CR.UserAcceptedGroupSent -> - withChats { - updateGroup(rh, r.groupInfo) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroup(rh, r.groupInfo) } is CR.ChatCmdError -> { val e = r.chatError - suspend fun deleteGroup() { if (apiDeleteChat(rh, ChatType.Group, groupId)) { withChats { removeChat(rh, "#$groupId") } } } + suspend fun deleteGroup() { if (apiDeleteChat(rh, ChatType.Group, groupId)) { + withContext(Dispatchers.Main) { chatModel.chatsContext.removeChat(rh, "#$groupId") } } + } if (e is ChatError.ChatErrorAgent && e.agentError is AgentErrorType.SMP && e.agentError.smpErr is SMPErrorType.AUTH) { deleteGroup() AlertManager.shared.showAlertMsg(generalGetString(MR.strings.alert_title_group_invitation_expired), generalGetString(MR.strings.alert_message_group_invitation_expired)) @@ -2134,8 +2134,8 @@ object ChatController { val prefs = contact.mergedPreferences.toPreferences().setAllowed(feature, param = param) val toContact = apiSetContactPrefs(rh, contact.contactId, prefs) if (toContact != null) { - withChats { - updateContact(rh, toContact) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContact(rh, toContact) } } } @@ -2406,19 +2406,19 @@ object ChatController { when (r) { is CR.ContactDeletedByContact -> { if (active(r.user) && r.contact.directOrUsed) { - withChats { - updateContact(rhId, r.contact) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContact(rhId, r.contact) } } } is CR.ContactConnected -> { if (active(r.user) && r.contact.directOrUsed) { - withChats { - updateContact(rhId, r.contact) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContact(rhId, r.contact) val conn = r.contact.activeConn if (conn != null) { chatModel.replaceConnReqView(conn.id, "@${r.contact.contactId}") - removeChat(rhId, conn.id) + chatModel.chatsContext.removeChat(rhId, conn.id) } } } @@ -2429,24 +2429,24 @@ object ChatController { } is CR.ContactConnecting -> { if (active(r.user) && r.contact.directOrUsed) { - withChats { - updateContact(rhId, r.contact) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContact(rhId, r.contact) val conn = r.contact.activeConn if (conn != null) { chatModel.replaceConnReqView(conn.id, "@${r.contact.contactId}") - removeChat(rhId, conn.id) + chatModel.chatsContext.removeChat(rhId, conn.id) } } } } is CR.ContactSndReady -> { if (active(r.user) && r.contact.directOrUsed) { - withChats { - updateContact(rhId, r.contact) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContact(rhId, r.contact) val conn = r.contact.activeConn if (conn != null) { chatModel.replaceConnReqView(conn.id, "@${r.contact.contactId}") - removeChat(rhId, conn.id) + chatModel.chatsContext.removeChat(rhId, conn.id) } } } @@ -2456,11 +2456,11 @@ object ChatController { val contactRequest = r.contactRequest val cInfo = ChatInfo.ContactRequest(contactRequest) if (active(r.user)) { - withChats { - if (hasChat(rhId, contactRequest.id)) { - updateChatInfo(rhId, cInfo) + withContext(Dispatchers.Main) { + if (chatModel.chatsContext.hasChat(rhId, contactRequest.id)) { + chatModel.chatsContext.updateChatInfo(rhId, cInfo) } else { - addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = listOf())) + chatModel.chatsContext.addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = listOf())) } } } @@ -2469,18 +2469,20 @@ object ChatController { is CR.ContactUpdated -> { if (active(r.user) && chatModel.chatsContext.hasChat(rhId, r.toContact.id)) { val cInfo = ChatInfo.Direct(r.toContact) - withChats { - updateChatInfo(rhId, cInfo) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateChatInfo(rhId, cInfo) } } } is CR.GroupMemberUpdated -> { if (active(r.user)) { - withChats { - upsertGroupMember(rhId, r.groupInfo, r.toMember) + withContext(Dispatchers.Main) { + chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.toMember) } - withReportsChatsIfOpen { - upsertGroupMember(rhId, r.groupInfo, r.toMember) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + chatModel.secondaryChatsContext.upsertGroupMember(rhId, r.groupInfo, r.toMember) + } } } } @@ -2489,8 +2491,8 @@ object ChatController { if (chatModel.chatId.value == r.mergedContact.id) { chatModel.chatId.value = r.intoContact.id } - withChats { - removeChat(rhId, r.mergedContact.id) + withContext(Dispatchers.Main) { + chatModel.chatsContext.removeChat(rhId, r.mergedContact.id) } } } @@ -2501,8 +2503,8 @@ object ChatController { is CR.ContactSubSummary -> { for (sub in r.contactSubscriptions) { if (active(r.user)) { - withChats { - updateContact(rhId, sub.contact) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContact(rhId, sub.contact) } } val err = sub.contactError @@ -2528,20 +2530,22 @@ object ChatController { val cInfo = chatItem.chatInfo val cItem = chatItem.chatItem if (active(r.user)) { - withChats { - addChatItem(rhId, cInfo, cItem) + withContext(Dispatchers.Main) { + chatModel.chatsContext.addChatItem(rhId, cInfo, cItem) if (cItem.isActiveReport) { - increaseGroupReportsCounter(rhId, cInfo.id) + chatModel.chatsContext.increaseGroupReportsCounter(rhId, cInfo.id) } } - withReportsChatsIfOpen { - if (cItem.isReport) { - addChatItem(rhId, cInfo, cItem) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + if (cItem.isReport) { + chatModel.secondaryChatsContext.addChatItem(rhId, cInfo, cItem) + } } } } else if (cItem.isRcvNew && cInfo.ntfsEnabled(cItem)) { - withChats { - increaseUnreadCounter(rhId, r.user) + withContext(Dispatchers.Main) { + chatModel.chatsContext.increaseUnreadCounter(rhId, r.user) } } val file = cItem.file @@ -2562,12 +2566,14 @@ object ChatController { val cInfo = chatItem.chatInfo val cItem = chatItem.chatItem if (!cItem.isDeletedContent && active(r.user)) { - withChats { - updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus) } - withReportsChatsIfOpen { - if (cItem.isReport) { - updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + if (cItem.isReport) { + chatModel.secondaryChatsContext.updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus) + } } } } @@ -2576,12 +2582,14 @@ object ChatController { chatItemUpdateNotify(rhId, r.user, r.chatItem) is CR.ChatItemReaction -> { if (active(r.user)) { - withChats { - updateChatItem(r.reaction.chatInfo, r.reaction.chatReaction.chatItem) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateChatItem(r.reaction.chatInfo, r.reaction.chatReaction.chatItem) } - withReportsChatsIfOpen { - if (r.reaction.chatReaction.chatItem.isReport) { - updateChatItem(r.reaction.chatInfo, r.reaction.chatReaction.chatItem) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + if (r.reaction.chatReaction.chatItem.isReport) { + chatModel.secondaryChatsContext.updateChatItem(r.reaction.chatInfo, r.reaction.chatReaction.chatItem) + } } } } @@ -2590,8 +2598,8 @@ object ChatController { if (!active(r.user)) { r.chatItemDeletions.forEach { (deletedChatItem, toChatItem) -> if (toChatItem == null && deletedChatItem.chatItem.isRcvNew && deletedChatItem.chatInfo.ntfsEnabled(deletedChatItem.chatItem)) { - withChats { - decreaseUnreadCounter(rhId, r.user) + withContext(Dispatchers.Main) { + chatModel.chatsContext.decreaseUnreadCounter(rhId, r.user) } } } @@ -2614,22 +2622,24 @@ object ChatController { generalGetString(if (toChatItem != null) MR.strings.marked_deleted_description else MR.strings.deleted_description) ) } - withChats { + withContext(Dispatchers.Main) { if (toChatItem == null) { - removeChatItem(rhId, cInfo, cItem) + chatModel.chatsContext.removeChatItem(rhId, cInfo, cItem) } else { - upsertChatItem(rhId, cInfo, toChatItem.chatItem) + chatModel.chatsContext.upsertChatItem(rhId, cInfo, toChatItem.chatItem) } if (cItem.isActiveReport) { - decreaseGroupReportsCounter(rhId, cInfo.id) + chatModel.chatsContext.decreaseGroupReportsCounter(rhId, cInfo.id) } } - withReportsChatsIfOpen { - if (cItem.isReport) { - if (toChatItem == null) { - removeChatItem(rhId, cInfo, cItem) - } else { - upsertChatItem(rhId, cInfo, toChatItem.chatItem) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + if (cItem.isReport) { + if (toChatItem == null) { + chatModel.secondaryChatsContext.removeChatItem(rhId, cInfo, cItem) + } else { + chatModel.secondaryChatsContext.upsertChatItem(rhId, cInfo, toChatItem.chatItem) + } } } } @@ -2640,9 +2650,9 @@ object ChatController { } is CR.ReceivedGroupInvitation -> { if (active(r.user)) { - withChats { + withContext(Dispatchers.Main) { // update so that repeat group invitations are not duplicated - updateGroup(rhId, r.groupInfo) + chatModel.chatsContext.updateGroup(rhId, r.groupInfo) } // TODO NtfManager.shared.notifyGroupInvitation } @@ -2650,137 +2660,149 @@ object ChatController { is CR.UserAcceptedGroupSent -> { if (!active(r.user)) return - withChats { - updateGroup(rhId, r.groupInfo) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroup(rhId, r.groupInfo) val conn = r.hostContact?.activeConn if (conn != null) { chatModel.replaceConnReqView(conn.id, "#${r.groupInfo.groupId}") - removeChat(rhId, conn.id) + chatModel.chatsContext.removeChat(rhId, conn.id) } } } is CR.GroupLinkConnecting -> { if (!active(r.user)) return - withChats { - updateGroup(rhId, r.groupInfo) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroup(rhId, r.groupInfo) val hostConn = r.hostMember.activeConn if (hostConn != null) { chatModel.replaceConnReqView(hostConn.id, "#${r.groupInfo.groupId}") - removeChat(rhId, hostConn.id) + chatModel.chatsContext.removeChat(rhId, hostConn.id) } } } is CR.BusinessLinkConnecting -> { if (!active(r.user)) return - withChats { - updateGroup(rhId, r.groupInfo) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroup(rhId, r.groupInfo) } if (chatModel.chatId.value == r.fromContact.id) { openGroupChat(rhId, r.groupInfo.groupId) } - withChats { - removeChat(rhId, r.fromContact.id) + withContext(Dispatchers.Main) { + chatModel.chatsContext.removeChat(rhId, r.fromContact.id) } } is CR.JoinedGroupMemberConnecting -> if (active(r.user)) { - withChats { - upsertGroupMember(rhId, r.groupInfo, r.member) + withContext(Dispatchers.Main) { + chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member) } } is CR.DeletedMemberUser -> // TODO update user member if (active(r.user)) { - withChats { - updateGroup(rhId, r.groupInfo) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroup(rhId, r.groupInfo) if (r.withMessages) { - removeMemberItems(rhId, r.groupInfo.membership, byMember = r.member, r.groupInfo) + chatModel.chatsContext.removeMemberItems(rhId, r.groupInfo.membership, byMember = r.member, r.groupInfo) } } - withReportsChatsIfOpen { - if (r.withMessages) { - removeMemberItems(rhId, r.groupInfo.membership, byMember = r.member, r.groupInfo) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + if (r.withMessages) { + chatModel.secondaryChatsContext.removeMemberItems(rhId, r.groupInfo.membership, byMember = r.member, r.groupInfo) + } } } } is CR.DeletedMember -> if (active(r.user)) { - withChats { - upsertGroupMember(rhId, r.groupInfo, r.deletedMember) + withContext(Dispatchers.Main) { + chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.deletedMember) if (r.withMessages) { - removeMemberItems(rhId, r.deletedMember, byMember = r.byMember, r.groupInfo) + chatModel.chatsContext.removeMemberItems(rhId, r.deletedMember, byMember = r.byMember, r.groupInfo) } } - withReportsChatsIfOpen { - upsertGroupMember(rhId, r.groupInfo, r.deletedMember) - if (r.withMessages) { - removeMemberItems(rhId, r.deletedMember, byMember = r.byMember, r.groupInfo) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + chatModel.secondaryChatsContext.upsertGroupMember(rhId, r.groupInfo, r.deletedMember) + if (r.withMessages) { + chatModel.secondaryChatsContext.removeMemberItems(rhId, r.deletedMember, byMember = r.byMember, r.groupInfo) + } } } } is CR.LeftMember -> if (active(r.user)) { - withChats { - upsertGroupMember(rhId, r.groupInfo, r.member) + withContext(Dispatchers.Main) { + chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member) } - withReportsChatsIfOpen { - upsertGroupMember(rhId, r.groupInfo, r.member) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + chatModel.secondaryChatsContext.upsertGroupMember(rhId, r.groupInfo, r.member) + } } } is CR.MemberRole -> if (active(r.user)) { - withChats { - upsertGroupMember(rhId, r.groupInfo, r.member) + withContext(Dispatchers.Main) { + chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member) } - withReportsChatsIfOpen { - upsertGroupMember(rhId, r.groupInfo, r.member) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + chatModel.secondaryChatsContext.upsertGroupMember(rhId, r.groupInfo, r.member) + } } } is CR.MembersRoleUser -> if (active(r.user)) { - withChats { + withContext(Dispatchers.Main) { r.members.forEach { member -> - upsertGroupMember(rhId, r.groupInfo, member) + chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, member) } } - withReportsChatsIfOpen { - r.members.forEach { member -> - upsertGroupMember(rhId, r.groupInfo, member) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + r.members.forEach { member -> + chatModel.secondaryChatsContext.upsertGroupMember(rhId, r.groupInfo, member) + } } } } is CR.MemberBlockedForAll -> if (active(r.user)) { - withChats { - upsertGroupMember(rhId, r.groupInfo, r.member) + withContext(Dispatchers.Main) { + chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member) } - withReportsChatsIfOpen { - upsertGroupMember(rhId, r.groupInfo, r.member) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + chatModel.secondaryChatsContext.upsertGroupMember(rhId, r.groupInfo, r.member) + } } } is CR.GroupDeleted -> // TODO update user member if (active(r.user)) { - withChats { - updateGroup(rhId, r.groupInfo) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroup(rhId, r.groupInfo) } } is CR.UserJoinedGroup -> if (active(r.user)) { - withChats { - updateGroup(rhId, r.groupInfo) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroup(rhId, r.groupInfo) } } is CR.JoinedGroupMember -> if (active(r.user)) { - withChats { - upsertGroupMember(rhId, r.groupInfo, r.member) + withContext(Dispatchers.Main) { + chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member) } } is CR.ConnectedToGroupMember -> { if (active(r.user)) { - withChats { - upsertGroupMember(rhId, r.groupInfo, r.member) + withContext(Dispatchers.Main) { + chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member) } } if (r.memberContact != null) { @@ -2789,14 +2811,14 @@ object ChatController { } is CR.GroupUpdated -> if (active(r.user)) { - withChats { - updateGroup(rhId, r.toGroup) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroup(rhId, r.toGroup) } } is CR.NewMemberContactReceivedInv -> if (active(r.user)) { - withChats { - updateContact(rhId, r.contact) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContact(rhId, r.contact) } } is CR.RcvFileStart -> @@ -2897,26 +2919,26 @@ object ChatController { } is CR.ContactSwitch -> if (active(r.user)) { - withChats { - updateContactConnectionStats(rhId, r.contact, r.switchProgress.connectionStats) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContactConnectionStats(rhId, r.contact, r.switchProgress.connectionStats) } } is CR.GroupMemberSwitch -> if (active(r.user)) { - withChats { - updateGroupMemberConnectionStats(rhId, r.groupInfo, r.member, r.switchProgress.connectionStats) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroupMemberConnectionStats(rhId, r.groupInfo, r.member, r.switchProgress.connectionStats) } } is CR.ContactRatchetSync -> if (active(r.user)) { - withChats { - updateContactConnectionStats(rhId, r.contact, r.ratchetSyncProgress.connectionStats) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContactConnectionStats(rhId, r.contact, r.ratchetSyncProgress.connectionStats) } } is CR.GroupMemberRatchetSync -> if (active(r.user)) { - withChats { - updateGroupMemberConnectionStats(rhId, r.groupInfo, r.member, r.ratchetSyncProgress.connectionStats) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroupMemberConnectionStats(rhId, r.groupInfo, r.member, r.ratchetSyncProgress.connectionStats) } } is CR.RemoteHostSessionCode -> { @@ -2930,8 +2952,8 @@ object ChatController { } is CR.ContactDisabled -> { if (active(r.user)) { - withChats { - updateContact(rhId, r.contact) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContact(rhId, r.contact) } } } @@ -3055,8 +3077,8 @@ object ChatController { } is CR.ContactPQEnabled -> if (active(r.user)) { - withChats { - updateContact(rhId, r.contact) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContact(rhId, r.contact) } } is CR.ChatRespError -> when { @@ -3120,8 +3142,8 @@ object ChatController { suspend fun leaveGroup(rh: Long?, groupId: Long) { val groupInfo = apiLeaveGroup(rh, groupId) if (groupInfo != null) { - withChats { - updateGroup(rh, groupInfo) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroup(rh, groupInfo) } } } @@ -3130,10 +3152,12 @@ object ChatController { if (activeUser(rh, user)) { val cInfo = aChatItem.chatInfo val cItem = aChatItem.chatItem - withChats { upsertChatItem(rh, cInfo, cItem) } - withReportsChatsIfOpen { - if (cItem.isReport) { - upsertChatItem(rh, cInfo, cItem) + withContext(Dispatchers.Main) { chatModel.chatsContext.upsertChatItem(rh, cInfo, cItem) } + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + if (cItem.isReport) { + chatModel.secondaryChatsContext.upsertChatItem(rh, cInfo, cItem) + } } } } @@ -3147,15 +3171,16 @@ object ChatController { return } val cInfo = ChatInfo.Group(r.groupInfo) - withChats { + withContext(Dispatchers.Main) { + val chatsCtx = chatModel.chatsContext r.chatItemIDs.forEach { itemId -> - decreaseGroupReportsCounter(rhId, cInfo.id) - val cItem = chatItems.value.lastOrNull { it.id == itemId } ?: return@forEach + chatsCtx.decreaseGroupReportsCounter(rhId, cInfo.id) + val cItem = chatsCtx.chatItems.value.lastOrNull { it.id == itemId } ?: return@forEach if (chatModel.chatId.value != null) { // Stop voice playback only inside a chat, allow to play in a chat list AudioPlayer.stop(cItem) } - val isLastChatItem = getChat(cInfo.id)?.chatItems?.lastOrNull()?.id == cItem.id + val isLastChatItem = chatsCtx.getChat(cInfo.id)?.chatItems?.lastOrNull()?.id == cItem.id if (isLastChatItem && ntfManager.hasNotificationsForChat(cInfo.id)) { ntfManager.cancelNotificationsForChat(cInfo.id) ntfManager.displayNotification( @@ -3170,22 +3195,25 @@ object ChatController { } else { CIDeleted.Deleted(Clock.System.now()) } - upsertChatItem(rhId, cInfo, cItem.copy(meta = cItem.meta.copy(itemDeleted = deleted))) + chatsCtx.upsertChatItem(rhId, cInfo, cItem.copy(meta = cItem.meta.copy(itemDeleted = deleted))) } } - withReportsChatsIfOpen { - r.chatItemIDs.forEach { itemId -> - val cItem = chatItems.value.lastOrNull { it.id == itemId } ?: return@forEach - if (chatModel.chatId.value != null) { - // Stop voice playback only inside a chat, allow to play in a chat list - AudioPlayer.stop(cItem) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + val chatsCtx = chatModel.secondaryChatsContext + r.chatItemIDs.forEach { itemId -> + val cItem = chatsCtx.chatItems.value.lastOrNull { it.id == itemId } ?: return@forEach + if (chatModel.chatId.value != null) { + // Stop voice playback only inside a chat, allow to play in a chat list + AudioPlayer.stop(cItem) + } + val deleted = if (r.member_ != null && (cItem.chatDir as CIDirection.GroupRcv?)?.groupMember?.groupMemberId != r.member_.groupMemberId) { + CIDeleted.Moderated(Clock.System.now(), r.member_) + } else { + CIDeleted.Deleted(Clock.System.now()) + } + chatsCtx.upsertChatItem(rhId, cInfo, cItem.copy(meta = cItem.meta.copy(itemDeleted = deleted))) } - val deleted = if (r.member_ != null && (cItem.chatDir as CIDirection.GroupRcv?)?.groupMember?.groupMemberId != r.member_.groupMemberId) { - CIDeleted.Moderated(Clock.System.now(), r.member_) - } else { - CIDeleted.Deleted(Clock.System.now()) - } - upsertChatItem(rhId, cInfo, cItem.copy(meta = cItem.meta.copy(itemDeleted = deleted))) } } } @@ -3197,8 +3225,14 @@ object ChatController { if (!activeUser(rh, user)) { notify() } else { - val createdChat = withChats { upsertChatItem(rh, cInfo, cItem) } - withReportsChatsIfOpen { if (cItem.content.msgContent is MsgContent.MCReport) { upsertChatItem(rh, cInfo, cItem) } } + val createdChat = withContext(Dispatchers.Main) { chatModel.chatsContext.upsertChatItem(rh, cInfo, cItem) } + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + if (cItem.content.msgContent is MsgContent.MCReport) { + chatModel.secondaryChatsContext.upsertChatItem(rh, cInfo, cItem) + } + } + } if (createdChat) { notify() } else if (cItem.content is CIContent.RcvCall && cItem.content.status == CICallStatus.Missed) { @@ -3243,15 +3277,17 @@ object ChatController { chatModel.users.addAll(users) chatModel.currentUser.value = user if (user == null) { - withChats { - chatItems.clearAndNotify() - chats.clear() - popChatCollector.clear() + withContext(Dispatchers.Main) { + chatModel.chatsContext.chatItems.clearAndNotify() + chatModel.chatsContext.chats.clear() + chatModel.chatsContext.popChatCollector.clear() } - withReportsChatsIfOpen { - chatItems.clearAndNotify() - chats.clear() - popChatCollector.clear() + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + chatModel.secondaryChatsContext.chatItems.clearAndNotify() + chatModel.secondaryChatsContext.chats.clear() + chatModel.secondaryChatsContext.popChatCollector.clear() + } } } val statuses = apiGetNetworkStatuses(rhId) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt index a730bd1b71..d5a99d9acb 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt @@ -19,7 +19,6 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.* @@ -37,17 +36,16 @@ import androidx.compose.ui.unit.* import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatModel.controller -import chat.simplex.common.model.ChatModel.withChats import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.* import chat.simplex.common.platform.* import chat.simplex.common.views.chat.group.ChatTTLSection -import chat.simplex.common.views.chat.group.ProgressIndicator import chat.simplex.common.views.chatlist.updateChatSettings import chat.simplex.common.views.database.* import chat.simplex.common.views.newchat.* import chat.simplex.res.MR +import kotlinx.coroutines.* import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch @@ -126,8 +124,8 @@ fun ChatInfoView( val cStats = chatModel.controller.apiSwitchContact(chatRh, contact.contactId) connStats.value = cStats if (cStats != null) { - withChats { - updateContactConnectionStats(chatRh, contact, cStats) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContactConnectionStats(chatRh, contact, cStats) } } close.invoke() @@ -140,8 +138,8 @@ fun ChatInfoView( val cStats = chatModel.controller.apiAbortSwitchContact(chatRh, contact.contactId) connStats.value = cStats if (cStats != null) { - withChats { - updateContactConnectionStats(chatRh, contact, cStats) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContactConnectionStats(chatRh, contact, cStats) } } } @@ -171,8 +169,8 @@ fun ChatInfoView( verify = { code -> chatModel.controller.apiVerifyContact(chatRh, ct.contactId, code)?.let { r -> val (verified, existingCode) = r - withChats { - updateContact( + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContact( chatRh, ct.copy( activeConn = ct.activeConn?.copy( @@ -200,8 +198,8 @@ suspend fun syncContactConnection(rhId: Long?, contact: Contact, connectionStats val cStats = chatModel.controller.apiSyncContactRatchet(rhId, contact.contactId, force = force) connectionStats.value = cStats if (cStats != null) { - withChats { - updateContactConnectionStats(rhId, contact, cStats) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContactConnectionStats(rhId, contact, cStats) } } } @@ -475,14 +473,14 @@ fun deleteContact(chat: Chat, chatModel: ChatModel, close: (() -> Unit)?, chatDe val chatRh = chat.remoteHostId val ct = chatModel.controller.apiDeleteContact(chatRh, chatInfo.apiId, chatDeleteMode) if (ct != null) { - withChats { + withContext(Dispatchers.Main) { when (chatDeleteMode) { is ChatDeleteMode.Full -> - removeChat(chatRh, chatInfo.id) + chatModel.chatsContext.removeChat(chatRh, chatInfo.id) is ChatDeleteMode.Entity -> - updateContact(chatRh, ct) + chatModel.chatsContext.updateContact(chatRh, ct) is ChatDeleteMode.Messages -> - clearChat(chatRh, ChatInfo.Direct(ct)) + chatModel.chatsContext.clearChat(chatRh, ChatInfo.Direct(ct)) } } if (chatModel.chatId.value == chatInfo.id) { @@ -1270,11 +1268,11 @@ suspend fun save(applyToMode: DefaultThemeMode?, newTheme: ThemeModeOverride?, c wallpaperFilesToDelete.forEach(::removeWallpaperFile) if (controller.apiSetChatUIThemes(chat.remoteHostId, chat.id, changedThemes)) { - withChats { + withContext(Dispatchers.Main) { if (chat.chatInfo is ChatInfo.Direct) { - updateChatInfo(chat.remoteHostId, chat.chatInfo.copy(contact = chat.chatInfo.contact.copy(uiThemes = changedThemes))) + chatModel.chatsContext.updateChatInfo(chat.remoteHostId, chat.chatInfo.copy(contact = chat.chatInfo.contact.copy(uiThemes = changedThemes))) } else if (chat.chatInfo is ChatInfo.Group) { - updateChatInfo(chat.remoteHostId, chat.chatInfo.copy(groupInfo = chat.chatInfo.groupInfo.copy(uiThemes = changedThemes))) + chatModel.chatsContext.updateChatInfo(chat.remoteHostId, chat.chatInfo.copy(groupInfo = chat.chatInfo.groupInfo.copy(uiThemes = changedThemes))) } } } @@ -1283,8 +1281,8 @@ suspend fun save(applyToMode: DefaultThemeMode?, newTheme: ThemeModeOverride?, c private fun setContactAlias(chat: Chat, localAlias: String, chatModel: ChatModel) = withBGApi { val chatRh = chat.remoteHostId chatModel.controller.apiSetContactAlias(chatRh, chat.chatInfo.apiId, localAlias)?.let { - withChats { - updateContact(chatRh, it) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContact(chatRh, it) } } } @@ -1386,10 +1384,10 @@ private suspend fun afterSetChatTTL(rhId: Long?, chatInfo: ChatInfo, progressInd val (chat, navInfo) = controller.apiGetChat(rhId, chatInfo.chatType, chatInfo.apiId, null, pagination) ?: return if (chat.chatItems.isEmpty()) { // replacing old chat with the same old chat but without items. Less intrusive way of clearing a preview - withChats { - val oldChat = getChat(chat.id) + withContext(Dispatchers.Main) { + val oldChat = chatModel.chatsContext.getChat(chat.id) if (oldChat != null) { - replaceChat(oldChat.remoteHostId, oldChat.id, oldChat.copy(chatItems = emptyList())) + chatModel.chatsContext.replaceChat(oldChat.remoteHostId, oldChat.id, oldChat.copy(chatItems = emptyList())) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt index 385bf42397..51a6d24e21 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt @@ -2,7 +2,6 @@ package chat.simplex.common.views.chat import androidx.compose.runtime.snapshots.SnapshotStateList import chat.simplex.common.model.* -import chat.simplex.common.model.ChatModel.withChats import chat.simplex.common.platform.chatModel import kotlinx.coroutines.* import kotlinx.coroutines.flow.StateFlow @@ -48,29 +47,30 @@ suspend fun processLoadedChat( openAroundItemId: Long?, visibleItemIndexesNonReversed: () -> IntRange = { 0 .. 0 } ) { - val chatState = chatModel.chatStateForContent(contentTag) + val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext + val chatState = chatsCtx.chatState val (splits, unreadAfterItemId, totalAfter, unreadTotal, unreadAfter, unreadAfterNewestLoaded) = chatState - val oldItems = chatModel.chatItemsForContent(contentTag).value + val oldItems = chatsCtx.chatItems.value val newItems = SnapshotStateList() when (pagination) { is ChatPagination.Initial -> { val newSplits = if (chat.chatItems.isNotEmpty() && navInfo.afterTotal > 0) listOf(chat.chatItems.last().id) else emptyList() if (contentTag == null) { // update main chats, not content tagged - withChats { - val oldChat = getChat(chat.id) + withContext(Dispatchers.Main) { + val oldChat = chatModel.chatsContext.getChat(chat.id) if (oldChat == null) { - addChat(chat) + chatModel.chatsContext.addChat(chat) } else { - updateChatInfo(chat.remoteHostId, chat.chatInfo) + chatModel.chatsContext.updateChatInfo(chat.remoteHostId, chat.chatInfo) // unreadChat is currently not actual in getChat query (always false) - updateChatStats(chat.remoteHostId, chat.id, chat.chatStats.copy(unreadChat = oldChat.chatStats.unreadChat)) + chatModel.chatsContext.updateChatStats(chat.remoteHostId, chat.id, chat.chatStats.copy(unreadChat = oldChat.chatStats.unreadChat)) } } } - withChats(contentTag) { - chatItemStatuses.clear() - chatItems.replaceAll(chat.chatItems) + withContext(Dispatchers.Main) { + chatsCtx.chatItemStatuses.clear() + chatsCtx.chatItems.replaceAll(chat.chatItems) chatModel.chatId.value = chat.id splits.value = newSplits if (chat.chatItems.isNotEmpty()) { @@ -93,8 +93,8 @@ suspend fun processLoadedChat( ) val insertAt = (indexInCurrentItems - (wasSize - newItems.size) + trimmedIds.size).coerceAtLeast(0) newItems.addAll(insertAt, chat.chatItems) - withChats(contentTag) { - chatItems.replaceAll(newItems) + withContext(Dispatchers.Main) { + chatsCtx.chatItems.replaceAll(newItems) splits.value = newSplits chatState.moveUnreadAfterItem(oldUnreadSplitIndex, newUnreadSplitIndex, oldItems) } @@ -112,8 +112,8 @@ suspend fun processLoadedChat( val indexToAdd = min(indexInCurrentItems + 1, newItems.size) val indexToAddIsLast = indexToAdd == newItems.size newItems.addAll(indexToAdd, chat.chatItems) - withChats(contentTag) { - chatItems.replaceAll(newItems) + withContext(Dispatchers.Main) { + chatsCtx.chatItems.replaceAll(newItems) splits.value = newSplits chatState.moveUnreadAfterItem(splits.value.firstOrNull() ?: newItems.last().id, newItems) // loading clear bottom area, updating number of unread items after the newest loaded item @@ -134,8 +134,8 @@ suspend fun processLoadedChat( newItems.addAll(itemIndex, chat.chatItems) newSplits.add(splitIndex, chat.chatItems.last().id) - withChats(contentTag) { - chatItems.replaceAll(newItems) + withContext(Dispatchers.Main) { + chatsCtx.chatItems.replaceAll(newItems) splits.value = newSplits unreadAfterItemId.value = chat.chatItems.last().id totalAfter.value = navInfo.afterTotal @@ -157,8 +157,8 @@ suspend fun processLoadedChat( val newSplits = removeDuplicatesAndUnusedSplits(newItems, chat, chatState.splits.value) removeDuplicates(newItems, chat) newItems.addAll(chat.chatItems) - withChats(contentTag) { - chatItems.replaceAll(newItems) + withContext(Dispatchers.Main) { + chatsCtx.chatItems.replaceAll(newItems) chatState.splits.value = newSplits unreadAfterNewestLoaded.value = 0 } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 94a5fd3549..c948437ac3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -32,8 +32,6 @@ import chat.simplex.common.model.CIDirection.GroupRcv import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatModel.activeCall import chat.simplex.common.model.ChatModel.controller -import chat.simplex.common.model.ChatModel.withChats -import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen import chat.simplex.common.ui.theme.* import chat.simplex.common.views.call.* import chat.simplex.common.views.chat.group.* @@ -114,9 +112,10 @@ fun ChatView( val chatRh = remoteHostId.value // We need to have real unreadCount value for displaying it inside top right button // Having activeChat reloaded on every change in it is inefficient (UI lags) + val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext val unreadCount = remember { derivedStateOf { - chatModel.chatsForContent(contentTag).value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.chatStats?.unreadCount ?: 0 + chatsCtx.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.chatStats?.unreadCount ?: 0 } } val clipboard = LocalClipboardManager.current @@ -389,23 +388,25 @@ fun ChatView( if (deleted != null) { deletedChatItem = deleted.deletedChatItem.chatItem toChatItem = deleted.toChatItem?.chatItem - withChats { + withContext(Dispatchers.Main) { if (toChatItem != null) { - upsertChatItem(chatRh, chatInfo, toChatItem) + chatModel.chatsContext.upsertChatItem(chatRh, chatInfo, toChatItem) } else { - removeChatItem(chatRh, chatInfo, deletedChatItem) + chatModel.chatsContext.removeChatItem(chatRh, chatInfo, deletedChatItem) } val deletedItem = deleted.deletedChatItem.chatItem if (deletedItem.isActiveReport) { - decreaseGroupReportsCounter(chatRh, chatInfo.id) + chatModel.chatsContext.decreaseGroupReportsCounter(chatRh, chatInfo.id) } } - withReportsChatsIfOpen { - if (deletedChatItem.isReport) { - if (toChatItem != null) { - upsertChatItem(chatRh, chatInfo, toChatItem) - } else { - removeChatItem(chatRh, chatInfo, deletedChatItem) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + if (deletedChatItem.isReport) { + if (toChatItem != null) { + chatModel.secondaryChatsContext.upsertChatItem(chatRh, chatInfo, toChatItem) + } else { + chatModel.secondaryChatsContext.removeChatItem(chatRh, chatInfo, deletedChatItem) + } } } } @@ -463,8 +464,8 @@ fun ChatView( if (r != null) { val contactStats = r.first if (contactStats != null) - withChats { - updateContactConnectionStats(chatRh, contact, contactStats) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContactConnectionStats(chatRh, contact, contactStats) } } } @@ -475,8 +476,8 @@ fun ChatView( if (r != null) { val memStats = r.second if (memStats != null) { - withChats { - updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, memStats) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, memStats) } } } @@ -486,8 +487,8 @@ fun ChatView( withBGApi { val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = false) if (cStats != null) { - withChats { - updateContactConnectionStats(chatRh, contact, cStats) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContactConnectionStats(chatRh, contact, cStats) } } } @@ -496,8 +497,8 @@ fun ChatView( withBGApi { val r = chatModel.controller.apiSyncGroupMemberRatchet(chatRh, groupInfo.apiId, member.groupMemberId, force = false) if (r != null) { - withChats { - updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, r.second) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, r.second) } } } @@ -519,12 +520,14 @@ fun ChatView( reaction = reaction ) if (updatedCI != null) { - withChats { - updateChatItem(cInfo, updatedCI) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateChatItem(cInfo, updatedCI) } - withReportsChatsIfOpen { - if (cItem.isReport) { - updateChatItem(cInfo, updatedCI) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + if (cItem.isReport) { + chatModel.secondaryChatsContext.updateChatItem(cInfo, updatedCI) + } } } } @@ -544,7 +547,7 @@ fun ChatView( groupMembersJob.cancel() groupMembersJob = scope.launch(Dispatchers.Default) { var initialCiInfo = loadChatItemInfo() ?: return@launch - if (!ModalManager.end.hasModalOpen(ModalViewId.GROUP_REPORTS)) { + if (!ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { ModalManager.end.closeModals() } ModalManager.end.showModalCloseable(endButtons = { @@ -578,11 +581,8 @@ fun ChatView( openGroupLink = { groupInfo -> openGroupLink(view = view, groupInfo = groupInfo, rhId = chatRh, close = { ModalManager.end.closeModals() }) }, markItemsRead = { itemsIds -> withBGApi { - withChats { - // It's important to call it on Main thread. Otherwise, composable crash occurs from time-to-time without useful stacktrace - withContext(Dispatchers.Main) { - markChatItemsRead(chatRh, chatInfo.id, itemsIds) - } + withContext(Dispatchers.Main) { + chatModel.chatsContext.markChatItemsRead(chatRh, chatInfo.id, itemsIds) ntfManager.cancelNotificationsForChat(chatInfo.id) chatModel.controller.apiChatItemsRead( chatRh, @@ -591,18 +591,17 @@ fun ChatView( itemsIds ) } - withReportsChatsIfOpen { - markChatItemsRead(chatRh, chatInfo.id, itemsIds) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + chatModel.secondaryChatsContext.markChatItemsRead(chatRh, chatInfo.id, itemsIds) + } } } }, markChatRead = { withBGApi { - withChats { - // It's important to call it on Main thread. Otherwise, composable crash occurs from time-to-time without useful stacktrace - withContext(Dispatchers.Main) { - markChatItemsRead(chatRh, chatInfo.id) - } + withContext(Dispatchers.Main) { + chatModel.chatsContext.markChatItemsRead(chatRh, chatInfo.id) ntfManager.cancelNotificationsForChat(chatInfo.id) chatModel.controller.apiChatRead( chatRh, @@ -610,8 +609,10 @@ fun ChatView( chatInfo.apiId ) } - withReportsChatsIfOpen { - markChatItemsRead(chatRh, chatInfo.id) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + chatModel.secondaryChatsContext.markChatItemsRead(chatRh, chatInfo.id) + } } } }, @@ -636,8 +637,8 @@ fun ChatView( LaunchedEffect(chatInfo.id) { onComposed(chatInfo.id) ModalManager.end.closeModals() - withChats { - chatItems.clearAndNotify() + withContext(Dispatchers.Main) { + chatModel.chatsContext.chatItems.clearAndNotify() } } } @@ -649,8 +650,8 @@ fun ChatView( LaunchedEffect(chatInfo.id) { onComposed(chatInfo.id) ModalManager.end.closeModals() - withChats { - chatItems.clearAndNotify() + withContext(Dispatchers.Main) { + chatModel.chatsContext.chatItems.clearAndNotify() } } } @@ -1206,12 +1207,13 @@ fun BoxScope.ChatItemsList( val revealedItems = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(setOf()) } val contentTag = LocalContentTag.current // not using reversedChatItems inside to prevent possible derivedState bug in Compose when one derived state access can cause crash asking another derived state + val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext val mergedItems = remember { derivedStateOf { - MergedItems.create(chatModel.chatItemsForContent(contentTag).value.asReversed(), unreadCount, revealedItems.value, chatModel.chatStateForContent(contentTag)) + MergedItems.create(chatsCtx.chatItems.value.asReversed(), unreadCount, revealedItems.value, chatsCtx.chatState) } } - val reversedChatItems = remember { derivedStateOf { chatModel.chatItemsForContent(contentTag).value.asReversed() } } + val reversedChatItems = remember { derivedStateOf { chatsCtx.chatItems.value.asReversed() } } val reportsCount = reportsCount(chatInfo.id) val topPaddingToContent = topPaddingToContent(chatView = contentTag == null, contentTag == null && reportsCount > 0) val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent.roundToPx() }) @@ -1297,11 +1299,11 @@ fun BoxScope.ChatItemsList( DisposableEffectOnGone( always = { - chatModel.setChatItemsChangeListenerForContent(recalculateChatStatePositions(chatModel.chatStateForContent(contentTag)), contentTag) + chatsCtx.chatItemsChangesListener = recalculateChatStatePositions(chatsCtx.chatState) }, whenGone = { VideoPlayerHolder.releaseAll() - chatModel.setChatItemsChangeListenerForContent(recalculateChatStatePositions(chatModel.chatStateForContent(contentTag)), contentTag) + chatsCtx.chatItemsChangesListener = recalculateChatStatePositions(chatsCtx.chatState) } ) @@ -1648,8 +1650,9 @@ private suspend fun loadLastItems(chatId: State, contentTag: MsgContentT } private fun lastItemsLoaded(contentTag: MsgContentTag?): Boolean { - val chatState = chatModel.chatStateForContent(contentTag) - return chatState.splits.value.isEmpty() || chatState.splits.value.firstOrNull() != chatModel.chatItemsForContent(contentTag).value.lastOrNull()?.id + val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext + val chatState = chatsCtx.chatState + return chatState.splits.value.isEmpty() || chatState.splits.value.firstOrNull() != chatsCtx.chatItems.value.lastOrNull()?.id } // TODO: in extra rare case when after loading last items only 1 item is loaded, the view will jump like when receiving new message @@ -1740,7 +1743,8 @@ fun BoxScope.FloatingButtons( fun scrollToTopUnread() { scope.launch { tryBlockAndSetLoadingMore(loadingMoreItems) { - if (chatModel.chatStateForContent(contentTag).splits.value.isNotEmpty()) { + val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext + if (chatsCtx.chatState.splits.value.isNotEmpty()) { val pagination = ChatPagination.Initial(ChatPagination.INITIAL_COUNT) val oldSize = reversedChatItems.value.size loadMessages(chatInfo.value.id, pagination) { @@ -2129,7 +2133,7 @@ private fun SaveReportsStateOnDispose(listState: State) { val contentTag = LocalContentTag.current DisposableEffect(Unit) { onDispose { - reportsListState = if (contentTag == MsgContentTag.Report && ModalManager.end.hasModalOpen(ModalViewId.GROUP_REPORTS)) listState.value else null + reportsListState = if (contentTag == MsgContentTag.Report && ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) listState.value else null } } } @@ -2240,8 +2244,10 @@ fun reportsCount(staleChatId: String?): Int { } } -private fun reversedChatItemsStatic(contentTag: MsgContentTag?): List = - chatModel.chatItemsForContent(contentTag).value.asReversed() +private fun reversedChatItemsStatic(contentTag: MsgContentTag?): List { + val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext + return chatsCtx.chatItems.value.asReversed() +} private fun oldestPartiallyVisibleListItemInListStateOrNull(topPaddingToContentPx: State, mergedItems: State, listState: State): ListItem? { val lastFullyVisibleOffset = listState.value.layoutInfo.viewportEndOffset - topPaddingToContentPx.value @@ -2326,11 +2332,13 @@ private fun findQuotedItemFromItem( scope.launch(Dispatchers.Default) { val item = apiLoadSingleMessage(rhId.value, chatInfo.value.chatType, chatInfo.value.apiId, itemId, contentTag) if (item != null) { - withChats { - updateChatItem(chatInfo.value, item) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateChatItem(chatInfo.value, item) } - withReportsChatsIfOpen { - updateChatItem(chatInfo.value, item) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + chatModel.secondaryChatsContext.updateChatItem(chatInfo.value, item) + } } if (item.quotedItem?.itemId != null) { scrollToItem(item.quotedItem.itemId) @@ -2510,28 +2518,30 @@ private fun deleteMessages(chatRh: Long?, chatInfo: ChatInfo, itemIds: List, chatInfo: ChatInfo) { chatModel.chatId.value = null chatModel.sharedContent.value = SharedContent.Forward( - chatModel.chatItemsForContent(null).value.filter { chatItemsIds.contains(it.id) }, + chatModel.chatsContext.chatItems.value.filter { chatItemsIds.contains(it.id) }, chatInfo ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index b48b32030f..de9fc26905 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -25,7 +25,6 @@ import androidx.compose.ui.util.* import chat.simplex.common.model.* import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.model.ChatModel.filesToDelete -import chat.simplex.common.model.ChatModel.withChats import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chat.item.* @@ -473,8 +472,8 @@ fun ComposeView( ) if (!chatItems.isNullOrEmpty()) { chatItems.forEach { aChatItem -> - withChats { - addChatItem(chat.remoteHostId, cInfo, aChatItem.chatItem) + withContext(Dispatchers.Main) { + chatModel.chatsContext.addChatItem(chat.remoteHostId, cInfo, aChatItem.chatItem) } } return chatItems.first().chatItem @@ -505,9 +504,9 @@ fun ComposeView( ttl = ttl ) - withChats { + withContext(Dispatchers.Main) { chatItems?.forEach { chatItem -> - addChatItem(rhId, chat.chatInfo, chatItem) + chatModel.chatsContext.addChatItem(rhId, chat.chatInfo, chatItem) } } @@ -567,9 +566,9 @@ fun ComposeView( suspend fun sendReport(reportReason: ReportReason, chatItemId: Long): List? { val cItems = chatModel.controller.apiReportMessage(chat.remoteHostId, chat.chatInfo.apiId, chatItemId, reportReason, msgText) if (cItems != null) { - withChats { + withContext(Dispatchers.Main) { cItems.forEach { chatItem -> - addChatItem(chat.remoteHostId, chat.chatInfo, chatItem.chatItem) + chatModel.chatsContext.addChatItem(chat.remoteHostId, chat.chatInfo, chatItem.chatItem) } } } @@ -581,8 +580,8 @@ fun ComposeView( val mc = checkLinkPreview() val contact = chatModel.controller.apiSendMemberContactInvitation(chat.remoteHostId, chat.chatInfo.apiId, mc) if (contact != null) { - withChats { - updateContact(chat.remoteHostId, contact) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContact(chat.remoteHostId, contact) } } } @@ -599,8 +598,10 @@ fun ComposeView( updatedMessage = UpdatedMessage(updateMsgContent(oldMsgContent), cs.memberMentions), live = live ) - if (updatedItem != null) withChats { - upsertChatItem(chat.remoteHostId, cInfo, updatedItem.chatItem) + if (updatedItem != null) { + withContext(Dispatchers.Main) { + chatModel.chatsContext.upsertChatItem(chat.remoteHostId, cInfo, updatedItem.chatItem) + } } return updatedItem?.chatItem } @@ -890,7 +891,7 @@ fun ComposeView( fun editPrevMessage() { if (composeState.value.contextItem != ComposeContextItem.NoContextItem || composeState.value.preview != ComposePreview.NoPreview) return - val lastEditable = chatModel.chatItemsForContent(null).value.findLast { it.meta.editable } + val lastEditable = chatModel.chatsContext.chatItems.value.findLast { it.meta.editable } if (lastEditable != null) { composeState.value = ComposeState(editingItem = lastEditable, useLinkPreviews = useLinkPreviews) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt index b1e9bf750e..7c04c30f67 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt @@ -12,16 +12,16 @@ import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import dev.icerock.moko.resources.compose.stringResource import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.PreferenceToggle import chat.simplex.common.model.* -import chat.simplex.common.model.ChatModel.withChats import chat.simplex.common.platform.ColumnWithScrollBar +import chat.simplex.common.platform.chatModel import chat.simplex.res.MR +import kotlinx.coroutines.* @Composable fun ContactPreferencesView( @@ -41,8 +41,8 @@ fun ContactPreferencesView( val prefs = contactFeaturesAllowedToPrefs(featuresAllowed) val toContact = m.controller.apiSetContactPrefs(rhId, ct.contactId, prefs) if (toContact != null) { - withChats { - updateContact(rhId, toContact) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContact(rhId, toContact) currentFeaturesAllowed = featuresAllowed } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt index 50e6f73bca..70210778ac 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.unit.dp import chat.simplex.common.model.* import chat.simplex.common.platform.BackHandler import chat.simplex.common.platform.chatModel +import chat.simplex.common.views.chat.group.LocalContentTag import chat.simplex.common.views.helpers.* import dev.icerock.moko.resources.compose.stringResource import chat.simplex.res.MR @@ -121,7 +122,8 @@ fun SelectedItemsButtonsToolbar( } Divider(Modifier.align(Alignment.TopStart)) } - val chatItems = remember { derivedStateOf { chatModel.chatItemsForContent(contentTag).value } } + val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext + val chatItems = remember { derivedStateOf { chatsCtx.chatItems.value } } LaunchedEffect(chatInfo, chatItems.value, selectedChatItems.value) { recheckItems(chatInfo, chatItems.value, selectedChatItems, deleteEnabled, deleteForEveryoneEnabled, canArchiveReports, canModerate, moderateEnabled, forwardEnabled, deleteCountProhibited, forwardCountProhibited) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt index abfb3895d9..a6d009f76a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt @@ -25,8 +25,6 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* -import chat.simplex.common.model.ChatModel.withChats -import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chat.ChatInfoToolbarTitle import chat.simplex.common.views.helpers.* @@ -35,6 +33,7 @@ import chat.simplex.common.model.GroupInfo import chat.simplex.common.platform.* import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource +import kotlinx.coroutines.* @Composable fun AddGroupMembersView(rhId: Long?, groupInfo: GroupInfo, creatingGroup: Boolean = false, chatModel: ChatModel, close: () -> Unit) { @@ -62,11 +61,13 @@ fun AddGroupMembersView(rhId: Long?, groupInfo: GroupInfo, creatingGroup: Boolea for (contactId in selectedContacts) { val member = chatModel.controller.apiAddMember(rhId, groupInfo.groupId, contactId, selectedRole.value) if (member != null) { - withChats { - upsertGroupMember(rhId, groupInfo, member) + withContext(Dispatchers.Main) { + chatModel.chatsContext.upsertGroupMember(rhId, groupInfo, member) } - withReportsChatsIfOpen { - upsertGroupMember(rhId, groupInfo, member) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + chatModel.secondaryChatsContext.upsertGroupMember(rhId, groupInfo, member) + } } } else { break diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index f38cd972f3..b80e46eeb6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -32,8 +32,6 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs -import chat.simplex.common.model.ChatModel.withChats -import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.* @@ -182,8 +180,8 @@ fun deleteGroupDialog(chat: Chat, groupInfo: GroupInfo, chatModel: ChatModel, cl withBGApi { val r = chatModel.controller.apiDeleteChat(chat.remoteHostId, chatInfo.chatType, chatInfo.apiId) if (r) { - withChats { - removeChat(chat.remoteHostId, chatInfo.id) + withContext(Dispatchers.Main) { + chatModel.chatsContext.removeChat(chat.remoteHostId, chatInfo.id) if (chatModel.chatId.value == chatInfo.id) { chatModel.chatId.value = null ModalManager.end.closeModals() @@ -957,8 +955,8 @@ private fun SearchRowView( private fun setGroupAlias(chat: Chat, localAlias: String, chatModel: ChatModel) = withBGApi { val chatRh = chat.remoteHostId chatModel.controller.apiSetGroupAlias(chatRh, chat.chatInfo.apiId, localAlias)?.let { - withChats { - updateGroup(chatRh, it) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroup(chatRh, it) } } } @@ -967,14 +965,16 @@ fun removeMembers(rhId: Long?, groupInfo: GroupInfo, memberIds: List, onSu withBGApi { val updatedMembers = chatModel.controller.apiRemoveMembers(rhId, groupInfo.groupId, memberIds) if (updatedMembers != null) { - withChats { + withContext(Dispatchers.Main) { updatedMembers.forEach { updatedMember -> - upsertGroupMember(rhId, groupInfo, updatedMember) + chatModel.chatsContext.upsertGroupMember(rhId, groupInfo, updatedMember) } } - withReportsChatsIfOpen { - updatedMembers.forEach { updatedMember -> - upsertGroupMember(rhId, groupInfo, updatedMember) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + updatedMembers.forEach { updatedMember -> + chatModel.secondaryChatsContext.upsertGroupMember(rhId, groupInfo, updatedMember) + } } } onSuccess() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt index 38163f9b6e..638722463d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt @@ -27,8 +27,6 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* import chat.simplex.common.model.* import chat.simplex.common.model.ChatModel.controller -import chat.simplex.common.model.ChatModel.withChats -import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chat.* import chat.simplex.common.views.helpers.* @@ -40,6 +38,7 @@ import chat.simplex.common.views.chatlist.openLoadedChat import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource import kotlinx.datetime.Clock +import kotlinx.coroutines.* @Composable fun GroupMemberInfoView( @@ -63,11 +62,13 @@ fun GroupMemberInfoView( val r = chatModel.controller.apiSyncGroupMemberRatchet(rhId, groupInfo.apiId, member.groupMemberId, force = false) if (r != null) { connStats.value = r.second - withChats { - updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) } - withReportsChatsIfOpen { - updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + chatModel.secondaryChatsContext.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) + } } close.invoke() } @@ -100,8 +101,8 @@ fun GroupMemberInfoView( val memberContact = chatModel.controller.apiCreateMemberContact(rhId, groupInfo.apiId, member.groupMemberId) if (memberContact != null) { val memberChat = Chat(remoteHostId = rhId, ChatInfo.Direct(memberContact), chatItems = arrayListOf()) - withChats { - addChat(memberChat) + withContext(Dispatchers.Main) { + chatModel.chatsContext.addChat(memberChat) } openLoadedChat(memberChat) closeAll() @@ -149,11 +150,13 @@ fun GroupMemberInfoView( val r = chatModel.controller.apiSwitchGroupMember(rhId, groupInfo.apiId, member.groupMemberId) if (r != null) { connStats.value = r.second - withChats { - updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) } - withReportsChatsIfOpen { - updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + chatModel.secondaryChatsContext.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) + } } close.invoke() } @@ -166,11 +169,13 @@ fun GroupMemberInfoView( val r = chatModel.controller.apiAbortSwitchGroupMember(rhId, groupInfo.apiId, member.groupMemberId) if (r != null) { connStats.value = r.second - withChats { - updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) } - withReportsChatsIfOpen { - updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + chatModel.secondaryChatsContext.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) + } } close.invoke() } @@ -186,11 +191,13 @@ fun GroupMemberInfoView( val r = chatModel.controller.apiSyncGroupMemberRatchet(rhId, groupInfo.apiId, member.groupMemberId, force = true) if (r != null) { connStats.value = r.second - withChats { - updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) } - withReportsChatsIfOpen { - updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + chatModel.secondaryChatsContext.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) + } } close.invoke() } @@ -212,11 +219,13 @@ fun GroupMemberInfoView( connectionCode = if (verified) SecurityCode(existingCode, Clock.System.now()) else null ) ) - withChats { - upsertGroupMember(rhId, groupInfo, copy) + withContext(Dispatchers.Main) { + chatModel.chatsContext.upsertGroupMember(rhId, groupInfo, copy) } - withReportsChatsIfOpen { - upsertGroupMember(rhId, groupInfo, copy) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + chatModel.secondaryChatsContext.upsertGroupMember(rhId, groupInfo, copy) + } } r } @@ -247,14 +256,16 @@ fun removeMemberDialog(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, c withBGApi { val removedMembers = chatModel.controller.apiRemoveMembers(rhId, member.groupId, listOf(member.groupMemberId)) if (removedMembers != null) { - withChats { + withContext(Dispatchers.Main) { removedMembers.forEach { removedMember -> - upsertGroupMember(rhId, groupInfo, removedMember) + chatModel.chatsContext.upsertGroupMember(rhId, groupInfo, removedMember) } } - withReportsChatsIfOpen { - removedMembers.forEach { removedMember -> - upsertGroupMember(rhId, groupInfo, removedMember) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + removedMembers.forEach { removedMember -> + chatModel.secondaryChatsContext.upsertGroupMember(rhId, groupInfo, removedMember) + } } } } @@ -697,14 +708,16 @@ fun updateMembersRole(newRole: GroupMemberRole, rhId: Long?, groupInfo: GroupInf withBGApi { kotlin.runCatching { val members = chatModel.controller.apiMembersRole(rhId, groupInfo.groupId, memberIds, newRole) - withChats { + withContext(Dispatchers.Main) { members.forEach { member -> - upsertGroupMember(rhId, groupInfo, member) + chatModel.chatsContext.upsertGroupMember(rhId, groupInfo, member) } } - withReportsChatsIfOpen { - members.forEach { member -> - upsertGroupMember(rhId, groupInfo, member) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + members.forEach { member -> + chatModel.secondaryChatsContext.upsertGroupMember(rhId, groupInfo, member) + } } } onSuccess() @@ -798,11 +811,13 @@ fun updateMemberSettings(rhId: Long?, gInfo: GroupInfo, member: GroupMember, mem withBGApi { val success = ChatController.apiSetMemberSettings(rhId, gInfo.groupId, member.groupMemberId, memberSettings) if (success) { - withChats { - upsertGroupMember(rhId, gInfo, member.copy(memberSettings = memberSettings)) + withContext(Dispatchers.Main) { + chatModel.chatsContext.upsertGroupMember(rhId, gInfo, member.copy(memberSettings = memberSettings)) } - withReportsChatsIfOpen { - upsertGroupMember(rhId, gInfo, member.copy(memberSettings = memberSettings)) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + chatModel.secondaryChatsContext.upsertGroupMember(rhId, gInfo, member.copy(memberSettings = memberSettings)) + } } } } @@ -857,14 +872,16 @@ fun unblockForAllAlert(rhId: Long?, gInfo: GroupInfo, memberIds: List, onS fun blockMemberForAll(rhId: Long?, gInfo: GroupInfo, memberIds: List, blocked: Boolean, onSuccess: () -> Unit = {}) { withBGApi { val updatedMembers = ChatController.apiBlockMembersForAll(rhId, gInfo.groupId, memberIds, blocked) - withChats { + withContext(Dispatchers.Main) { updatedMembers.forEach { updatedMember -> - upsertGroupMember(rhId, gInfo, updatedMember) + chatModel.chatsContext.upsertGroupMember(rhId, gInfo, updatedMember) } } - withReportsChatsIfOpen { - updatedMembers.forEach { updatedMember -> - upsertGroupMember(rhId, gInfo, updatedMember) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + updatedMembers.forEach { updatedMember -> + chatModel.secondaryChatsContext.upsertGroupMember(rhId, gInfo, updatedMember) + } } } onSuccess() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt index e1b0f30423..12c5b65769 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt @@ -15,9 +15,10 @@ import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.PreferenceToggleWithIcon import chat.simplex.common.model.* -import chat.simplex.common.model.ChatModel.withChats import chat.simplex.common.platform.ColumnWithScrollBar +import chat.simplex.common.platform.chatModel import chat.simplex.res.MR +import kotlinx.coroutines.* private val featureRoles: List> = listOf( null to generalGetString(MR.strings.feature_roles_all_members), @@ -42,12 +43,12 @@ fun GroupPreferencesView(m: ChatModel, rhId: Long?, chatId: String, close: () -> val gp = gInfo.groupProfile.copy(groupPreferences = preferences.toGroupPreferences()) val g = m.controller.apiUpdateGroup(rhId, gInfo.groupId, gp) if (g != null) { - withChats { - updateGroup(rhId, g) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroup(rhId, g) currentPreferences = preferences } - withChats { - updateGroup(rhId, g) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroup(rhId, g) } } afterSave() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt index 3163c109e6..fb24c028b2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt @@ -17,8 +17,6 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* -import chat.simplex.common.model.ChatModel.withChats -import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.* @@ -27,8 +25,7 @@ import chat.simplex.common.views.onboarding.ReadableText import chat.simplex.common.views.usersettings.* import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import java.net.URI @Composable @@ -40,8 +37,8 @@ fun GroupProfileView(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, cl withBGApi { val gInfo = chatModel.controller.apiUpdateGroup(rhId, groupInfo.groupId, p) if (gInfo != null) { - withChats { - updateGroup(rhId, gInfo) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroup(rhId, gInfo) } close.invoke() } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupReportsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupReportsView.kt index 058ee59a3b..b41d190ffe 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupReportsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupReportsView.kt @@ -72,7 +72,7 @@ private fun ItemsReload(contentTag: MsgContentTag?) { suspend fun showGroupReportsView(staleChatId: State, scrollToItemId: MutableState, chatInfo: ChatInfo) { openChat(chatModel.remoteHostId(), chatInfo, MsgContentTag.Report) - ModalManager.end.showCustomModal(true, id = ModalViewId.GROUP_REPORTS) { close -> + ModalManager.end.showCustomModal(true, id = ModalViewId.SECONDARY_CHAT) { close -> ModalView({}, showAppBar = false) { val chatInfo = remember { derivedStateOf { chatModel.chats.value.firstOrNull { it.id == chatModel.chatId.value }?.chatInfo } }.value if (chatInfo is ChatInfo.Group && chatInfo.groupInfo.canModerate) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt index 703d74f225..1e99c7f527 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt @@ -26,13 +26,10 @@ import chat.simplex.common.ui.theme.DEFAULT_PADDING import chat.simplex.common.views.chat.item.MarkdownText import chat.simplex.common.views.helpers.* import chat.simplex.common.model.ChatModel -import chat.simplex.common.model.ChatModel.withChats -import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen import chat.simplex.common.model.GroupInfo -import chat.simplex.common.platform.ColumnWithScrollBar -import chat.simplex.common.platform.chatJsonLength +import chat.simplex.common.platform.* import chat.simplex.res.MR -import kotlinx.coroutines.delay +import kotlinx.coroutines.* private const val maxByteCount = 1200 @@ -51,8 +48,8 @@ fun GroupWelcomeView(m: ChatModel, rhId: Long?, groupInfo: GroupInfo, close: () val res = m.controller.apiUpdateGroup(rhId, gInfo.groupId, groupProfileUpdated) if (res != null) { gInfo = res - withChats { - updateGroup(rhId, res) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroup(rhId, res) } welcomeText.value = welcome ?: "" } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIChatFeatureView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIChatFeatureView.kt index 7711ee73af..6a57912296 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIChatFeatureView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIChatFeatureView.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* import chat.simplex.common.model.ChatModel.getChatItemIndexOrNull +import chat.simplex.common.platform.chatModel import chat.simplex.common.platform.onRightClick import chat.simplex.common.views.chat.group.LocalContentTag @@ -76,7 +77,8 @@ private fun mergedFeatures(chatItem: ChatItem, chatInfo: ChatInfo): List = arrayListOf() val icons: MutableSet = mutableSetOf() - val reversedChatItems = m.chatItemsForContent(LocalContentTag.current).value.asReversed() + val chatsCtx = if (LocalContentTag.current == null) m.chatsContext else m.secondaryChatsContext + val reversedChatItems = chatsCtx.chatItems.value.asReversed() var i = getChatItemIndexOrNull(chatItem, reversedChatItems) if (i != null) { while (i < reversedChatItems.size) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index 4eb7f56837..d08fa574ef 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -10,7 +10,6 @@ import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.* import androidx.compose.ui.graphics.* @@ -632,7 +631,8 @@ fun ChatItemView( } @Composable fun EventItemView() { - val reversedChatItems = chatModel.chatItemsForContent(LocalContentTag.current).value.asReversed() + val chatsCtx = if (LocalContentTag.current == null) chatModel.chatsContext else chatModel.secondaryChatsContext + val reversedChatItems = chatsCtx.chatItems.value.asReversed() CIEventView(eventItemViewText(reversedChatItems)) } @@ -839,13 +839,14 @@ fun DeleteItemAction( buttonText: String = stringResource(MR.strings.delete_verb), ) { val contentTag = LocalContentTag.current + val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext ItemAction( buttonText, painterResource(MR.images.ic_delete), onClick = { showMenu.value = false if (!revealed.value) { - val reversedChatItems = chatModel.chatItemsForContent(contentTag).value.asReversed() + val reversedChatItems = chatsCtx.chatItems.value.asReversed() val currIndex = chatModel.getChatItemIndexOrNull(cItem, reversedChatItems) val ciCategory = cItem.mergeCategory if (currIndex != null && ciCategory != null) { @@ -1314,7 +1315,7 @@ fun shapeStyle(chatItem: ChatItem? = null, tailEnabled: Boolean, tailVisible: Bo } private fun closeReportsIfNeeded() { - if (appPlatform.isAndroid && ModalManager.end.isLastModalOpen(ModalViewId.GROUP_REPORTS)) { + if (appPlatform.isAndroid && ModalManager.end.isLastModalOpen(ModalViewId.SECONDARY_CHAT)) { ModalManager.end.closeModals() } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/MarkedDeletedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/MarkedDeletedItemView.kt index f731db2df9..af9df2cb9a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/MarkedDeletedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/MarkedDeletedItemView.kt @@ -44,7 +44,8 @@ fun MarkedDeletedItemView(ci: ChatItem, chatInfo: ChatInfo, timedMessagesTTL: In @Composable private fun MergedMarkedDeletedText(chatItem: ChatItem, chatInfo: ChatInfo, revealed: State) { - val reversedChatItems = chatModel.chatItemsForContent(LocalContentTag.current).value.asReversed() + val chatsCtx = if (LocalContentTag.current == null) chatModel.chatsContext else chatModel.secondaryChatsContext + val reversedChatItems = chatsCtx.chatItems.value.asReversed() var i = getChatItemIndexOrNull(chatItem, reversedChatItems) val ciCategory = chatItem.mergeCategory val text = if (!revealed.value && ciCategory != null && i != null) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt index 8b4b2bb1d2..fb2349d2b8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt @@ -20,8 +20,6 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* import chat.simplex.common.model.ChatModel.controller -import chat.simplex.common.model.ChatModel.withChats -import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chat.* @@ -233,17 +231,19 @@ suspend fun openChat( ) suspend fun openLoadedChat(chat: Chat, contentTag: MsgContentTag? = null) { - withChats(contentTag) { - chatItemStatuses.clear() - chatItems.replaceAll(chat.chatItems) + withContext(Dispatchers.Main) { + val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext + chatsCtx.chatItemStatuses.clear() + chatsCtx.chatItems.replaceAll(chat.chatItems) chatModel.chatId.value = chat.chatInfo.id - chatModel.chatStateForContent(contentTag).clear() + chatsCtx.chatState.clear() } } suspend fun apiFindMessages(ch: Chat, search: String, contentTag: MsgContentTag?) { - withChats(contentTag) { - chatItems.clearAndNotify() + withContext(Dispatchers.Main) { + val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext + chatsCtx.chatItems.clearAndNotify() } apiLoadMessages(ch.remoteHostId, ch.chatInfo.chatType, ch.chatInfo.apiId, contentTag, pagination = if (search.isNotEmpty()) ChatPagination.Last(ChatPagination.INITIAL_COUNT) else ChatPagination.Initial(ChatPagination.INITIAL_COUNT), search = search) } @@ -604,11 +604,13 @@ fun markChatRead(c: Chat) { var chat = c withApi { if (chat.chatStats.unreadCount > 0) { - withChats { - markChatItemsRead(chat.remoteHostId, chat.chatInfo.id) + withContext(Dispatchers.Main) { + chatModel.chatsContext.markChatItemsRead(chat.remoteHostId, chat.chatInfo.id) } - withReportsChatsIfOpen { - markChatItemsRead(chat.remoteHostId, chat.chatInfo.id) + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + chatModel.secondaryChatsContext.markChatItemsRead(chat.remoteHostId, chat.chatInfo.id) + } } chatModel.controller.apiChatRead( chat.remoteHostId, @@ -625,9 +627,9 @@ fun markChatRead(c: Chat) { false ) if (success) { - withChats { - replaceChat(chat.remoteHostId, chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = false))) - markChatTagRead(chat) + withContext(Dispatchers.Main) { + chatModel.chatsContext.replaceChat(chat.remoteHostId, chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = false))) + chatModel.chatsContext.markChatTagRead(chat) } } } @@ -647,9 +649,9 @@ fun markChatUnread(chat: Chat, chatModel: ChatModel) { true ) if (success) { - withChats { - replaceChat(chat.remoteHostId, chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = true))) - updateChatTagReadNoContentTag(chat, wasUnread) + withContext(Dispatchers.Main) { + chatModel.chatsContext.replaceChat(chat.remoteHostId, chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = true))) + chatModel.chatsContext.updateChatTagReadNoContentTag(chat, wasUnread) } } } @@ -690,8 +692,8 @@ fun acceptContactRequest(rhId: Long?, incognito: Boolean, apiId: Long, contactRe val contact = chatModel.controller.apiAcceptContactRequest(rhId, incognito, apiId) if (contact != null && isCurrentUser && contactRequest != null) { val chat = Chat(remoteHostId = rhId, ChatInfo.Direct(contact), listOf()) - withChats { - replaceChat(rhId, contactRequest.id, chat) + withContext(Dispatchers.Main) { + chatModel.chatsContext.replaceChat(rhId, contactRequest.id, chat) } chatModel.setContactNetworkStatus(contact, NetworkStatus.Connected()) close?.invoke(chat) @@ -702,8 +704,8 @@ fun acceptContactRequest(rhId: Long?, incognito: Boolean, apiId: Long, contactRe fun rejectContactRequest(rhId: Long?, contactRequest: ChatInfo.ContactRequest, chatModel: ChatModel) { withBGApi { chatModel.controller.apiRejectContactRequest(rhId, contactRequest.apiId) - withChats { - removeChat(rhId, contactRequest.id) + withContext(Dispatchers.Main) { + chatModel.chatsContext.removeChat(rhId, contactRequest.id) } } } @@ -720,8 +722,8 @@ fun deleteContactConnectionAlert(rhId: Long?, connection: PendingContactConnecti withBGApi { AlertManager.shared.hideAlert() if (chatModel.controller.apiDeleteChat(rhId, ChatType.ContactConnection, connection.apiId)) { - withChats { - removeChat(rhId, connection.id) + withContext(Dispatchers.Main) { + chatModel.chatsContext.removeChat(rhId, connection.id) } onSuccess() } @@ -741,8 +743,8 @@ fun pendingContactAlertDialog(rhId: Long?, chatInfo: ChatInfo, chatModel: ChatMo withBGApi { val r = chatModel.controller.apiDeleteChat(rhId, chatInfo.chatType, chatInfo.apiId) if (r) { - withChats { - removeChat(rhId, chatInfo.id) + withContext(Dispatchers.Main) { + chatModel.chatsContext.removeChat(rhId, chatInfo.id) } if (chatModel.chatId.value == chatInfo.id) { chatModel.chatId.value = null @@ -805,8 +807,8 @@ fun askCurrentOrIncognitoProfileConnectContactViaAddress( suspend fun connectContactViaAddress(chatModel: ChatModel, rhId: Long?, contactId: Long, incognito: Boolean): Boolean { val contact = chatModel.controller.apiConnectContactViaAddress(rhId, incognito, contactId) if (contact != null) { - withChats { - updateContact(rhId, contact) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContact(rhId, contact) } AlertManager.privacySensitive.showAlertMsg( title = generalGetString(MR.strings.connection_request_sent), @@ -848,8 +850,8 @@ fun deleteGroup(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel) { withBGApi { val r = chatModel.controller.apiDeleteChat(rhId, ChatType.Group, groupInfo.apiId) if (r) { - withChats { - removeChat(rhId, groupInfo.id) + withContext(Dispatchers.Main) { + chatModel.chatsContext.removeChat(rhId, groupInfo.id) } if (chatModel.chatId.value == groupInfo.id) { chatModel.chatId.value = null @@ -903,16 +905,16 @@ fun updateChatSettings(remoteHostId: Long?, chatInfo: ChatInfo, chatSettings: Ch val wasUnread = chat?.unreadTag ?: false val wasFavorite = chatInfo.chatSettings?.favorite ?: false chatModel.updateChatFavorite(favorite = chatSettings.favorite, wasFavorite) - withChats { - updateChatInfo(remoteHostId, newChatInfo) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateChatInfo(remoteHostId, newChatInfo) } if (chatSettings.enableNtfs == MsgFilter.None) { ntfManager.cancelNotificationsForChat(chatInfo.id) } val updatedChat = chatModel.getChat(chatInfo.id) if (updatedChat != null) { - withChats { - updateChatTagReadNoContentTag(updatedChat, wasUnread) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateChatTagReadNoContentTag(updatedChat, wasUnread) } } val current = currentState?.value diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 3538d41f01..87c02f038c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -27,7 +27,6 @@ import androidx.compose.ui.unit.* import chat.simplex.common.AppLock import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs -import chat.simplex.common.model.ChatController.setConditionsNotified import chat.simplex.common.model.ChatController.stopRemoteHostAndReloadHosts import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* @@ -38,8 +37,6 @@ import chat.simplex.common.views.chat.topPaddingToContent import chat.simplex.common.views.newchat.* import chat.simplex.common.views.onboarding.* import chat.simplex.common.views.usersettings.* -import chat.simplex.common.views.usersettings.networkAndServers.ConditionsLinkButton -import chat.simplex.common.views.usersettings.networkAndServers.UsageConditionsView import chat.simplex.res.MR import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.StringResource @@ -139,7 +136,7 @@ fun ChatListView(chatModel: ChatModel, userPickerState: MutableStateFlow Unit, reorderMode: Boolean) { @@ -417,15 +416,15 @@ private fun setTag(rhId: Long?, tagId: Long?, chat: Chat, close: () -> Unit) { when (val cInfo = chat.chatInfo) { is ChatInfo.Direct -> { val contact = cInfo.contact.copy(chatTags = result.second) - withChats { - updateContact(rhId, contact) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContact(rhId, contact) } } is ChatInfo.Group -> { val group = cInfo.groupInfo.copy(chatTags = result.second) - withChats { - updateGroup(rhId, group) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroup(rhId, group) } } @@ -453,14 +452,14 @@ private fun deleteTag(rhId: Long?, tag: ChatTag, saving: MutableState) when (val cInfo = c.chatInfo) { is ChatInfo.Direct -> { val contact = cInfo.contact.copy(chatTags = cInfo.contact.chatTags.filter { it != tagId }) - withChats { - updateContact(rhId, contact) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContact(rhId, contact) } } is ChatInfo.Group -> { val group = cInfo.groupInfo.copy(chatTags = cInfo.groupInfo.chatTags.filter { it != tagId }) - withChats { - updateGroup(rhId, group) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroup(rhId, group) } } else -> {} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/contacts/ContactListNavView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/contacts/ContactListNavView.kt index da70aef621..6ea7e9fc02 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/contacts/ContactListNavView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/contacts/ContactListNavView.kt @@ -5,7 +5,6 @@ import androidx.compose.ui.graphics.Color import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import chat.simplex.common.model.* -import chat.simplex.common.model.ChatModel.withChats import chat.simplex.common.platform.* import chat.simplex.common.views.chat.* import chat.simplex.common.views.chat.item.ItemAction diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index 4a3e1cda54..a2fcae9d7d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -21,8 +21,6 @@ import androidx.compose.ui.unit.dp import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatModel.controller -import chat.simplex.common.model.ChatModel.withChats -import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.* @@ -36,6 +34,7 @@ import java.nio.file.StandardCopyOption import java.text.SimpleDateFormat import java.util.* import kotlin.collections.ArrayList +import kotlinx.coroutines.* @Composable fun DatabaseView() { @@ -538,15 +537,17 @@ fun deleteChatDatabaseFilesAndState() { // Clear sensitive data on screen just in case ModalManager will fail to prevent hiding its modals while database encrypts itself chatModel.chatId.value = null withLongRunningApi { - withChats { - chatItems.clearAndNotify() - chats.clear() - popChatCollector.clear() + withContext(Dispatchers.Main) { + chatModel.chatsContext.chatItems.clearAndNotify() + chatModel.chatsContext.chats.clear() + chatModel.chatsContext.popChatCollector.clear() } - withReportsChatsIfOpen { - chatItems.clearAndNotify() - chats.clear() - popChatCollector.clear() + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + chatModel.secondaryChatsContext.chatItems.clearAndNotify() + chatModel.secondaryChatsContext.chats.clear() + chatModel.secondaryChatsContext.popChatCollector.clear() + } } } chatModel.users.clear() @@ -785,10 +786,10 @@ private fun afterSetCiTTL( appFilesCountAndSize.value = directoryFileCountAndSize(appFilesDir.absolutePath) withApi { try { - withChats { + withContext(Dispatchers.Main) { // this is using current remote host on purpose - if it changes during update, it will load correct chats val chats = m.controller.apiGetChats(m.remoteHostId()) - updateChats(chats) + chatModel.chatsContext.updateChats(chats) } } catch (e: Exception) { Log.e(TAG, "apiGetChats error: ${e.message}") diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt index 564e96945c..4848e791e1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt @@ -85,7 +85,7 @@ class ModalData(val keyboardCoversBar: Boolean = true) { } enum class ModalViewId { - GROUP_REPORTS + SECONDARY_CHAT } class ModalManager(private val placement: ModalPlacement? = null) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt index 2380c64a4c..8205299583 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt @@ -18,7 +18,6 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* -import chat.simplex.common.model.ChatModel.withChats import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chat.group.AddGroupMembersView import chat.simplex.common.views.chatlist.setGroupMembers @@ -30,6 +29,7 @@ import chat.simplex.common.views.usersettings.* import chat.simplex.res.MR import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlinx.coroutines.* import java.net.URI @Composable @@ -42,10 +42,10 @@ fun AddGroupView(chatModel: ChatModel, rh: RemoteHostInfo?, close: () -> Unit, c withBGApi { val groupInfo = chatModel.controller.apiNewGroup(rhId, incognito, groupProfile) if (groupInfo != null) { - withChats { - updateGroup(rhId = rhId, groupInfo) - chatItems.clearAndNotify() - chatItemStatuses.clear() + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateGroup(rhId = rhId, groupInfo) + chatModel.chatsContext.chatItems.clearAndNotify() + chatModel.chatsContext.chatItemStatuses.clear() chatModel.chatId.value = groupInfo.id } setGroupMembers(rhId, groupInfo, chatModel) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt index 1b5b475b35..6af7ec8134 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt @@ -7,13 +7,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import dev.icerock.moko.resources.compose.stringResource import chat.simplex.common.model.* -import chat.simplex.common.model.ChatModel.withChats import chat.simplex.common.platform.* import chat.simplex.common.views.chatlist.* import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import kotlinx.coroutines.* -import java.net.URI enum class ConnectionLinkType { INVITATION, CONTACT, GROUP @@ -359,8 +357,8 @@ suspend fun connectViaUri( val pcc = chatModel.controller.apiConnect(rhId, incognito, uri) val connLinkType = if (connectionPlan != null) planToConnectionLinkType(connectionPlan) else ConnectionLinkType.INVITATION if (pcc != null) { - withChats { - updateContactConnection(rhId, pcc) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContactConnection(rhId, pcc) } close?.invoke() AlertManager.privacySensitive.showAlertMsg( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt index 1623f8510d..1328523033 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt @@ -5,7 +5,6 @@ import SectionDividerSpaced import SectionTextFooter import SectionView import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* @@ -22,11 +21,11 @@ import chat.simplex.common.views.chat.LocalAliasEditor import chat.simplex.common.views.chatlist.deleteContactConnectionAlert import chat.simplex.common.views.helpers.* import chat.simplex.common.model.ChatModel -import chat.simplex.common.model.ChatModel.withChats import chat.simplex.common.model.PendingContactConnection import chat.simplex.common.platform.* import chat.simplex.common.views.usersettings.* import chat.simplex.res.MR +import kotlinx.coroutines.* @Composable fun ContactConnectionInfoView( @@ -185,8 +184,8 @@ fun DeleteButton(onClick: () -> Unit) { private fun setContactAlias(rhId: Long?, contactConnection: PendingContactConnection, localAlias: String, chatModel: ChatModel) = withBGApi { chatModel.controller.apiSetConnectionAlias(rhId, contactConnection.pccConnId, localAlias)?.let { - withChats { - updateContactConnection(rhId, it) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContactConnection(rhId, it) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index 923c0256a8..edc54a8d2e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -31,7 +31,6 @@ import androidx.compose.ui.unit.sp import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatModel.controller -import chat.simplex.common.model.ChatModel.withChats import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chat.topPaddingToContent @@ -39,7 +38,6 @@ import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.* import chat.simplex.res.MR import kotlinx.coroutines.* -import java.net.URI enum class NewChatOption { INVITE, CONNECT @@ -315,8 +313,8 @@ fun ActiveProfilePicker( if (contactConnection != null) { updatedConn = controller.apiChangeConnectionUser(rhId, contactConnection.pccConnId, user.userId) if (updatedConn != null) { - withChats { - updateContactConnection(rhId, updatedConn) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContactConnection(rhId, updatedConn) updateShownConnection(updatedConn) } } @@ -338,8 +336,8 @@ fun ActiveProfilePicker( } if (updatedConn != null) { - withChats { - updateContactConnection(user.remoteHostId, updatedConn) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContactConnection(user.remoteHostId, updatedConn) } } @@ -368,8 +366,8 @@ fun ActiveProfilePicker( appPreferences.incognito.set(true) val conn = controller.apiSetConnectionIncognito(rhId, contactConnection.pccConnId, true) if (conn != null) { - withChats { - updateContactConnection(rhId, conn) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContactConnection(rhId, conn) updateShownConnection(conn) } close() @@ -685,8 +683,8 @@ private fun createInvitation( withBGApi { val (r, alert) = controller.apiAddContact(rhId, incognito = controller.appPrefs.incognito.get()) if (r != null) { - withChats { - updateContactConnection(rhId, r.second) + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateContactConnection(rhId, r.second) chatModel.showingInvitation.value = ShowingInvitation(connId = r.second.id, connReq = simplexChatLink(r.first), connChatUsed = false, conn = r.second) contactConnection.value = r.second } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt index 5132516669..72fa45b936 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt @@ -11,13 +11,13 @@ import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.stringResource import chat.simplex.common.views.helpers.* import chat.simplex.common.model.* -import chat.simplex.common.model.ChatModel.withChats import chat.simplex.common.platform.ColumnWithScrollBar +import chat.simplex.common.platform.chatModel import chat.simplex.res.MR +import kotlinx.coroutines.* @Composable fun PreferencesView(m: ChatModel, user: User, close: () -> Unit,) { @@ -34,8 +34,8 @@ fun PreferencesView(m: ChatModel, user: User, close: () -> Unit,) { if (updated != null) { val (updatedProfile, updatedContacts) = updated m.updateCurrentUser(user.remoteHostId, updatedProfile, preferences) - withChats { - updatedContacts.forEach { updateContact(user.remoteHostId, it) } + withContext(Dispatchers.Main) { + updatedContacts.forEach { chatModel.chatsContext.updateContact(user.remoteHostId, it) } } currentPreferences = preferences } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt index c411eb0d78..02446ae982 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt @@ -30,8 +30,8 @@ import chat.simplex.common.views.isValidDisplayName import chat.simplex.common.views.localauth.SetAppPasscodeView import chat.simplex.common.views.onboarding.ReadableText import chat.simplex.common.model.ChatModel -import chat.simplex.common.model.ChatModel.withChats import chat.simplex.common.platform.* +import kotlinx.coroutines.* enum class LAMode { SYSTEM, @@ -119,15 +119,15 @@ fun PrivacySettingsView( chatModel.currentUser.value = currentUser.copy(sendRcptsContacts = enable) if (clearOverrides) { // For loop here is to prevent ConcurrentModificationException that happens with forEach - withChats { - for (i in 0 until chats.size) { - val chat = chats[i] + withContext(Dispatchers.Main) { + for (i in 0 until chatModel.chatsContext.chats.size) { + val chat = chatModel.chatsContext.chats[i] if (chat.chatInfo is ChatInfo.Direct) { var contact = chat.chatInfo.contact val sendRcpts = contact.chatSettings.sendRcpts if (sendRcpts != null && sendRcpts != enable) { contact = contact.copy(chatSettings = contact.chatSettings.copy(sendRcpts = null)) - updateContact(currentUser.remoteHostId, contact) + chatModel.chatsContext.updateContact(currentUser.remoteHostId, contact) } } } @@ -143,16 +143,16 @@ fun PrivacySettingsView( chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true) chatModel.currentUser.value = currentUser.copy(sendRcptsSmallGroups = enable) if (clearOverrides) { - withChats { + withContext(Dispatchers.Main) { // For loop here is to prevent ConcurrentModificationException that happens with forEach - for (i in 0 until chats.size) { - val chat = chats[i] + for (i in 0 until chatModel.chatsContext.chats.size) { + val chat = chatModel.chatsContext.chats[i] if (chat.chatInfo is ChatInfo.Group) { var groupInfo = chat.chatInfo.groupInfo val sendRcpts = groupInfo.chatSettings.sendRcpts if (sendRcpts != null && sendRcpts != enable) { groupInfo = groupInfo.copy(chatSettings = groupInfo.chatSettings.copy(sendRcpts = null)) - updateGroup(currentUser.remoteHostId, groupInfo) + chatModel.chatsContext.updateGroup(currentUser.remoteHostId, groupInfo) } } } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt index 9d747206ab..1d0a873c7d 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt @@ -14,8 +14,6 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.compose.ui.window.* import chat.simplex.common.model.* -import chat.simplex.common.model.ChatModel.withChats -import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.DEFAULT_START_MODAL_WIDTH import chat.simplex.common.ui.theme.SimpleXTheme @@ -58,12 +56,14 @@ fun showApp() { } else { // The last possible cause that can be closed withApi { - withChats { + withContext(Dispatchers.Main) { chatModel.chatId.value = null - chatItems.clearAndNotify() + chatModel.chatsContext.chatItems.clearAndNotify() } - withReportsChatsIfOpen { - chatItems.clearAndNotify() + withContext(Dispatchers.Main) { + if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { + chatModel.secondaryChatsContext.chatItems.clearAndNotify() + } } } } From 45e395d35a09344c93e6adbcac55283d29b42ab3 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 14 Apr 2025 21:25:32 +0100 Subject: [PATCH 507/567] core, ui: short connection links with stored data (#5824) * core, ui: optionally use short links (#5799) * core: optionally use short links * update test * update simplexmq, short group links * fix query * fix parser for _connect * ios: use short links * shorten links to remove fingerprint and onion hosts from known servers * fix parser * tests * nix * update query plans * update simplexmq, simplex: schema for short links * simplexmq * update ios * fix short links in ios * android: use short links * fix short group links, test short link connection plans * core: fix connection plan to recognize own short links * update simplexmq * space * all tests * relative symlinks in simplexmq to fix windows build * core: improve connection plan for short links (#5825) * core: improve connection plan for short links * improve connection plans * update UI * update simplexmq * ios: add preset server domains to entitlements, add short link paths to .well-known/apple-app-site-association * update simplexmq * fix group short link in iOS, fix simplex:/ scheme saved to database or used for connection plans * update simplexmq * ios: delay opening URI from outside until the app is started * update simplexmq --- apps/ios/Shared/ContentView.swift | 10 +- apps/ios/Shared/Model/SimpleXAPI.swift | 89 +++--- apps/ios/Shared/SimpleXApp.swift | 16 +- apps/ios/Shared/Views/Chat/ChatInfoView.swift | 2 +- apps/ios/Shared/Views/Chat/ChatView.swift | 2 +- .../Views/Chat/Group/GroupChatInfoView.swift | 2 +- .../Views/Chat/Group/GroupLinkView.swift | 18 +- .../ChatList/ContactConnectionInfo.swift | 30 +- .../Shared/Views/NewChat/AddGroupView.swift | 2 +- .../Shared/Views/NewChat/NewChatView.swift | 298 ++++++++++-------- apps/ios/Shared/Views/NewChat/QRCode.swift | 17 +- .../Onboarding/CreateSimpleXAddress.swift | 10 +- .../Views/UserSettings/PrivacySettings.swift | 7 + .../Views/UserSettings/SettingsView.swift | 2 + .../Views/UserSettings/UserAddressView.swift | 41 ++- apps/ios/SimpleX (iOS).entitlements | 4 + apps/ios/SimpleXChat/API.swift | 2 +- apps/ios/SimpleXChat/APITypes.swift | 74 +++-- apps/ios/SimpleXChat/ChatTypes.swift | 4 +- .../android/src/main/AndroidManifest.xml | 25 ++ .../chat/simplex/common/model/ChatModel.kt | 8 +- .../chat/simplex/common/model/SimpleXAPI.kt | 138 +++++--- .../simplex/common/views/chat/ChatView.kt | 6 +- .../views/chat/group/GroupChatInfoView.kt | 6 +- .../common/views/chat/group/GroupLinkView.kt | 25 +- .../views/chatlist/ChatListNavLinkView.kt | 2 +- .../simplex/common/views/helpers/Section.kt | 18 ++ .../common/views/newchat/AddGroupView.kt | 2 +- .../common/views/newchat/ConnectPlan.kt | 109 +++---- .../newchat/ContactConnectionInfoView.kt | 43 +-- .../common/views/newchat/NewChatView.kt | 59 ++-- .../simplex/common/views/newchat/QRCode.kt | 30 +- .../views/usersettings/PrivacySettings.kt | 7 + .../views/usersettings/UserAddressView.kt | 18 +- .../commonMain/resources/MR/base/strings.xml | 6 + .../src/Directory/Service.hs | 25 +- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 3 + src/Simplex/Chat.hs | 82 +---- src/Simplex/Chat/Bot.hs | 14 +- src/Simplex/Chat/Controller.hs | 37 ++- src/Simplex/Chat/Library/Commands.hs | 254 +++++++++------ src/Simplex/Chat/Library/Internal.hs | 4 +- src/Simplex/Chat/Library/Subscriber.hs | 4 +- src/Simplex/Chat/Markdown.hs | 43 ++- src/Simplex/Chat/Operators.hs | 7 + src/Simplex/Chat/Operators/Presets.hs | 117 +++++++ src/Simplex/Chat/Store/Connections.hs | 24 +- src/Simplex/Chat/Store/Direct.hs | 38 +-- src/Simplex/Chat/Store/Groups.hs | 44 ++- src/Simplex/Chat/Store/Messages.hs | 6 +- src/Simplex/Chat/Store/Postgres/Migrations.hs | 4 +- .../Migrations/M20250402_short_links.hs | 23 ++ src/Simplex/Chat/Store/Profiles.hs | 63 ++-- src/Simplex/Chat/Store/SQLite/Migrations.hs | 4 +- .../Migrations/M20250402_short_links.hs | 23 ++ .../SQLite/Migrations/agent_query_plans.txt | 77 ++++- .../SQLite/Migrations/chat_query_plans.txt | 81 +++-- .../Store/SQLite/Migrations/chat_schema.sql | 3 + src/Simplex/Chat/Store/Shared.hs | 20 +- src/Simplex/Chat/Terminal.hs | 3 +- src/Simplex/Chat/Types.hs | 18 +- src/Simplex/Chat/View.hs | 74 +++-- tests/ChatClient.hs | 21 +- tests/ChatTests/Direct.hs | 2 + tests/ChatTests/Profiles.hs | 164 ++++++++++ tests/ChatTests/Utils.hs | 30 +- tests/OperatorTests.hs | 1 + tests/ProtocolTests.hs | 22 +- .../apple-app-site-association/index.json | 24 ++ 71 files changed, 1676 insertions(+), 819 deletions(-) create mode 100644 src/Simplex/Chat/Operators/Presets.hs create mode 100644 src/Simplex/Chat/Store/Postgres/Migrations/M20250402_short_links.hs create mode 100644 src/Simplex/Chat/Store/SQLite/Migrations/M20250402_short_links.hs diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift index 305ad0a601..e8b494724a 100644 --- a/apps/ios/Shared/ContentView.swift +++ b/apps/ios/Shared/ContentView.swift @@ -443,12 +443,12 @@ struct ContentView: View { } func connectViaUrl() { - dismissAllSheets() { - let m = ChatModel.shared - if let url = m.appOpenUrl { - m.appOpenUrl = nil + let m = ChatModel.shared + if let url = m.appOpenUrl { + m.appOpenUrl = nil + dismissAllSheets() { var path = url.path - if (path == "/contact" || path == "/invitation") { + if (path == "/contact" || path == "/invitation" || path == "/a" || path == "/c" || path == "/g" || path == "/i") { path.removeFirst() let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)") planAndConnect( diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 495209499c..2de818abb2 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -839,13 +839,14 @@ func apiVerifyGroupMember(_ groupId: Int64, _ groupMemberId: Int64, connectionCo return nil } -func apiAddContact(incognito: Bool) async -> ((String, PendingContactConnection)?, Alert?) { +func apiAddContact(incognito: Bool) async -> ((CreatedConnLink, PendingContactConnection)?, Alert?) { guard let userId = ChatModel.shared.currentUser?.userId else { logger.error("apiAddContact: no current user") return (nil, nil) } - let r = await chatSendCmd(.apiAddContact(userId: userId, incognito: incognito), bgTask: false) - if case let .invitation(_, connReqInvitation, connection) = r { return ((connReqInvitation, connection), nil) } + let short = UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_SHORT_LINKS) + let r = await chatSendCmd(.apiAddContact(userId: userId, short: short, incognito: incognito), bgTask: false) + if case let .invitation(_, connLinkInv, connection) = r { return ((connLinkInv, connection), nil) } let alert = connectionErrorAlert(r) return (nil, alert) } @@ -856,23 +857,26 @@ func apiSetConnectionIncognito(connId: Int64, incognito: Bool) async throws -> P throw r } -func apiChangeConnectionUser(connId: Int64, userId: Int64) async throws -> PendingContactConnection? { +func apiChangeConnectionUser(connId: Int64, userId: Int64) async throws -> PendingContactConnection { let r = await chatSendCmd(.apiChangeConnectionUser(connId: connId, userId: userId)) if case let .connectionUserChanged(_, _, toConnection, _) = r {return toConnection} throw r } -func apiConnectPlan(connReq: String) async throws -> ConnectionPlan { - let userId = try currentUserId("apiConnectPlan") - let r = await chatSendCmd(.apiConnectPlan(userId: userId, connReq: connReq)) - if case let .connectionPlan(_, connectionPlan) = r { return connectionPlan } - logger.error("apiConnectPlan error: \(responseError(r))") - throw r +func apiConnectPlan(connLink: String) async -> ((CreatedConnLink, ConnectionPlan)?, Alert?) { + guard let userId = ChatModel.shared.currentUser?.userId else { + logger.error("apiConnectPlan: no current user") + return (nil, nil) + } + let r = await chatSendCmd(.apiConnectPlan(userId: userId, connLink: connLink)) + if case let .connectionPlan(_, connLink, connPlan) = r { return ((connLink, connPlan), nil) } + let alert = apiConnectResponseAlert(r) ?? connectionErrorAlert(r) + return (nil, alert) } -func apiConnect(incognito: Bool, connReq: String) async -> (ConnReqType, PendingContactConnection)? { - let (r, alert) = await apiConnect_(incognito: incognito, connReq: connReq) +func apiConnect(incognito: Bool, connLink: CreatedConnLink) async -> (ConnReqType, PendingContactConnection)? { + let (r, alert) = await apiConnect_(incognito: incognito, connLink: connLink) if let alert = alert { AlertManager.shared.showAlert(alert) return nil @@ -881,12 +885,12 @@ func apiConnect(incognito: Bool, connReq: String) async -> (ConnReqType, Pending } } -func apiConnect_(incognito: Bool, connReq: String) async -> ((ConnReqType, PendingContactConnection)?, Alert?) { +func apiConnect_(incognito: Bool, connLink: CreatedConnLink) async -> ((ConnReqType, PendingContactConnection)?, Alert?) { guard let userId = ChatModel.shared.currentUser?.userId else { logger.error("apiConnect: no current user") return (nil, nil) } - let r = await chatSendCmd(.apiConnect(userId: userId, incognito: incognito, connReq: connReq)) + let r = await chatSendCmd(.apiConnect(userId: userId, incognito: incognito, connLink: connLink)) let m = ChatModel.shared switch r { case let .sentConfirmation(_, connection): @@ -899,20 +903,31 @@ func apiConnect_(incognito: Bool, connReq: String) async -> ((ConnReqType, Pendi } let alert = contactAlreadyExistsAlert(contact) return (nil, alert) + default: () + } + let alert = apiConnectResponseAlert(r) ?? connectionErrorAlert(r) + return (nil, alert) +} + +private func apiConnectResponseAlert(_ r: ChatResponse) -> Alert? { + switch r { case .chatCmdError(_, .error(.invalidConnReq)): - let alert = mkAlert( + mkAlert( title: "Invalid connection link", message: "Please check that you used the correct link or ask your contact to send you another one." ) - return (nil, alert) + case .chatCmdError(_, .error(.unsupportedConnReq)): + mkAlert( + title: "Unsupported connection link", + message: "This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." + ) case .chatCmdError(_, .errorAgent(.SMP(_, .AUTH))): - let alert = mkAlert( + mkAlert( title: "Connection error (AUTH)", message: "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." ) - return (nil, alert) case let .chatCmdError(_, .errorAgent(.SMP(_, .BLOCKED(info)))): - let alert = Alert( + Alert( title: Text("Connection blocked"), message: Text("Connection is blocked by server operator:\n\(info.reason.text)"), primaryButton: .default(Text("Ok")), @@ -922,25 +937,22 @@ func apiConnect_(incognito: Bool, connReq: String) async -> ((ConnReqType, Pendi } } ) - return (nil, alert) case .chatCmdError(_, .errorAgent(.SMP(_, .QUOTA))): - let alert = mkAlert( + mkAlert( title: "Undelivered messages", message: "The connection reached the limit of undelivered messages, your contact may be offline." ) - return (nil, alert) case let .chatCmdError(_, .errorAgent(.INTERNAL(internalErr))): if internalErr == "SEUniqueID" { - let alert = mkAlert( + mkAlert( title: "Already connected?", message: "It seems like you are already connected via this link. If it is not the case, there was an error (\(responseError(r)))." ) - return (nil, alert) + } else { + nil } - default: () + default: nil } - let alert = connectionErrorAlert(r) - return (nil, alert) } func contactAlreadyExistsAlert(_ contact: Contact) -> Alert { @@ -1130,10 +1142,10 @@ func apiSetChatUIThemes(chatId: ChatId, themes: ThemeModeOverrides?) async -> Bo } -func apiCreateUserAddress() async throws -> String { +func apiCreateUserAddress(short: Bool) async throws -> CreatedConnLink { let userId = try currentUserId("apiCreateUserAddress") - let r = await chatSendCmd(.apiCreateMyAddress(userId: userId)) - if case let .userContactLinkCreated(_, connReq) = r { return connReq } + let r = await chatSendCmd(.apiCreateMyAddress(userId: userId, short: short)) + if case let .userContactLinkCreated(_, connLink) = r { return connLink } throw r } @@ -1642,15 +1654,16 @@ func apiUpdateGroup(_ groupId: Int64, _ groupProfile: GroupProfile) async throws throw r } -func apiCreateGroupLink(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> (String, GroupMemberRole) { - let r = await chatSendCmd(.apiCreateGroupLink(groupId: groupId, memberRole: memberRole)) - if case let .groupLinkCreated(_, _, connReq, memberRole) = r { return (connReq, memberRole) } +func apiCreateGroupLink(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> (CreatedConnLink, GroupMemberRole) { + let short = UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_SHORT_LINKS) + let r = await chatSendCmd(.apiCreateGroupLink(groupId: groupId, memberRole: memberRole, short: short)) + if case let .groupLinkCreated(_, _, connLink, memberRole) = r { return (connLink, memberRole) } throw r } -func apiGroupLinkMemberRole(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> (String, GroupMemberRole) { +func apiGroupLinkMemberRole(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> (CreatedConnLink, GroupMemberRole) { let r = await chatSendCmd(.apiGroupLinkMemberRole(groupId: groupId, memberRole: memberRole)) - if case let .groupLink(_, _, connReq, memberRole) = r { return (connReq, memberRole) } + if case let .groupLink(_, _, connLink, memberRole) = r { return (connLink, memberRole) } throw r } @@ -1660,11 +1673,11 @@ func apiDeleteGroupLink(_ groupId: Int64) async throws { throw r } -func apiGetGroupLink(_ groupId: Int64) throws -> (String, GroupMemberRole)? { +func apiGetGroupLink(_ groupId: Int64) throws -> (CreatedConnLink, GroupMemberRole)? { let r = chatSendCmdSync(.apiGetGroupLink(groupId: groupId)) switch r { - case let .groupLink(_, _, connReq, memberRole): - return (connReq, memberRole) + case let .groupLink(_, _, connLink, memberRole): + return (connLink, memberRole) case .chatCmdError(_, chatError: .errorStore(storeError: .groupLinkNotFound)): return nil default: throw r diff --git a/apps/ios/Shared/SimpleXApp.swift b/apps/ios/Shared/SimpleXApp.swift index 10120db185..f8d69c5fc8 100644 --- a/apps/ios/Shared/SimpleXApp.swift +++ b/apps/ios/Shared/SimpleXApp.swift @@ -19,6 +19,7 @@ struct SimpleXApp: App { @Environment(\.scenePhase) var scenePhase @State private var enteredBackgroundAuthenticated: TimeInterval? = nil + @State private var appOpenUrlLater: URL? init() { DispatchQueue.global(qos: .background).sync { @@ -42,7 +43,11 @@ struct SimpleXApp: App { .environmentObject(AppTheme.shared) .onOpenURL { url in logger.debug("ContentView.onOpenURL: \(url)") - chatModel.appOpenUrl = url + if AppChatState.shared.value == .active { + chatModel.appOpenUrl = url + } else { + appOpenUrlLater = url + } } .onAppear() { // Present screen for continue migration if it wasn't finished yet @@ -93,7 +98,16 @@ struct SimpleXApp: App { if !chatModel.showCallView && !CallController.shared.hasActiveCalls() { await updateCallInvitations() } + if let url = appOpenUrlLater { + await MainActor.run { + appOpenUrlLater = nil + chatModel.appOpenUrl = url + } + } } + } else if let url = appOpenUrlLater { + appOpenUrlLater = nil + chatModel.appOpenUrl = url } } } diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index 8fe4260a1e..8194c8fe6f 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -7,7 +7,7 @@ // import SwiftUI -import SimpleXChat +@preconcurrency import SimpleXChat func infoRow(_ title: LocalizedStringKey, _ value: String) -> some View { HStack { diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 693efcfbb5..eae28b76be 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -45,7 +45,7 @@ struct ChatView: View { @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 groupLink: CreatedConnLink? @State private var groupLinkMemberRole: GroupMemberRole = .member @State private var forwardedChatItems: [ChatItem] = [] @State private var selectedChatItems: Set? = nil diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index 56d994b397..9fa07bc391 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -21,7 +21,7 @@ struct GroupChatInfoView: View { @State var localAlias: String @FocusState private var aliasTextFieldFocused: Bool @State private var alert: GroupChatInfoViewAlert? = nil - @State private var groupLink: String? + @State private var groupLink: CreatedConnLink? @State private var groupLinkMemberRole: GroupMemberRole = .member @State private var groupLinkNavLinkActive: Bool = false @State private var addMembersNavLinkActive: Bool = false diff --git a/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift b/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift index 39288e2d52..a11c073a42 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift @@ -10,12 +10,14 @@ import SwiftUI import SimpleXChat struct GroupLinkView: View { + @EnvironmentObject var theme: AppTheme var groupId: Int64 - @Binding var groupLink: String? + @Binding var groupLink: CreatedConnLink? @Binding var groupLinkMemberRole: GroupMemberRole var showTitle: Bool = false var creatingGroup: Bool = false var linkCreatedCb: (() -> Void)? = nil + @State private var showShortLink = true @State private var creatingLink = false @State private var alert: GroupLinkAlert? @State private var shouldCreate = true @@ -69,10 +71,10 @@ struct GroupLinkView: View { } } .frame(height: 36) - SimpleXLinkQRCode(uri: groupLink) - .id("simplex-qrcode-view-for-\(groupLink)") + SimpleXCreatedLinkQRCode(link: groupLink, short: $showShortLink) + .id("simplex-qrcode-view-for-\(groupLink.simplexChatUri(short: showShortLink))") Button { - showShareSheet(items: [simplexChatLink(groupLink)]) + showShareSheet(items: [groupLink.simplexChatUri(short: showShortLink)]) } label: { Label("Share link", systemImage: "square.and.arrow.up") } @@ -93,6 +95,10 @@ struct GroupLinkView: View { .frame(maxWidth: .infinity) } } + } header: { + if let groupLink, groupLink.connShortLink != nil { + ToggleShortLinkHeader(text: Text(""), link: groupLink, short: $showShortLink) + } } .alert(item: $alert) { alert in switch alert { @@ -158,8 +164,8 @@ struct GroupLinkView: View { struct GroupLinkView_Previews: PreviewProvider { static var previews: some View { - @State var groupLink: String? = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D" - @State var noGroupLink: String? = nil + @State var groupLink: CreatedConnLink? = CreatedConnLink(connFullLink: "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", connShortLink: nil) + @State var noGroupLink: CreatedConnLink? = nil return Group { GroupLinkView(groupId: 1, groupLink: $groupLink, groupLinkMemberRole: Binding.constant(.member)) diff --git a/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift b/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift index 0f64b632dc..b9f5b984e1 100644 --- a/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift +++ b/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift @@ -14,6 +14,7 @@ struct ContactConnectionInfo: View { @EnvironmentObject var theme: AppTheme @Environment(\.dismiss) var dismiss: DismissAction @State var contactConnection: PendingContactConnection + @State private var showShortLink: Bool = true @State private var alert: CCInfoAlert? @State private var localAlias = "" @State private var showIncognitoSheet = false @@ -61,14 +62,19 @@ struct ContactConnectionInfo: View { } if contactConnection.initiated, - let connReqInv = contactConnection.connReqInv { - SimpleXLinkQRCode(uri: simplexChatLink(connReqInv)) + let connLinkInv = contactConnection.connLinkInv { + SimpleXCreatedLinkQRCode(link: connLinkInv, short: $showShortLink) + .id("simplex-invitation-qrcode-\(connLinkInv.simplexChatUri(short: showShortLink))") incognitoEnabled() - shareLinkButton(connReqInv, theme.colors.secondary) - oneTimeLinkLearnMoreButton(theme.colors.secondary) + shareLinkButton(connLinkInv, short: showShortLink) + oneTimeLinkLearnMoreButton() } else { incognitoEnabled() - oneTimeLinkLearnMoreButton(theme.colors.secondary) + oneTimeLinkLearnMoreButton() + } + } header: { + if let connLinkInv = contactConnection.connLinkInv, connLinkInv.connShortLink != nil { + ToggleShortLinkHeader(text: Text(""), link: connLinkInv, short: $showShortLink) } } footer: { sharedProfileInfo(contactConnection.incognito) @@ -167,26 +173,22 @@ struct ContactConnectionInfo: View { } } -private func shareLinkButton(_ connReqInvitation: String, _ secondaryColor: Color) -> some View { +private func shareLinkButton(_ connLinkInvitation: CreatedConnLink, short: Bool) -> some View { Button { - showShareSheet(items: [simplexChatLink(connReqInvitation)]) + showShareSheet(items: [connLinkInvitation.simplexChatUri(short: short)]) } label: { - settingsRow("square.and.arrow.up", color: secondaryColor) { - Text("Share 1-time link") - } + Label("Share 1-time link", systemImage: "square.and.arrow.up") } } -private func oneTimeLinkLearnMoreButton(_ secondaryColor: Color) -> some View { +private func oneTimeLinkLearnMoreButton() -> some View { NavigationLink { AddContactLearnMore(showTitle: false) .navigationTitle("One-time invitation link") .modifier(ThemedBackground()) .navigationBarTitleDisplayMode(.large) } label: { - settingsRow("info.circle", color: secondaryColor) { - Text("Learn more") - } + Label("Learn more", systemImage: "info.circle") } } diff --git a/apps/ios/Shared/Views/NewChat/AddGroupView.swift b/apps/ios/Shared/Views/NewChat/AddGroupView.swift index 0fe0f2644d..87c0b80372 100644 --- a/apps/ios/Shared/Views/NewChat/AddGroupView.swift +++ b/apps/ios/Shared/Views/NewChat/AddGroupView.swift @@ -23,7 +23,7 @@ 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 groupLink: CreatedConnLink? @State private var groupLinkMemberRole: GroupMemberRole = .member var body: some View { diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift index 7a7b91880c..2524b5e682 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatView.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -81,7 +81,8 @@ struct NewChatView: View { @State var selection: NewChatOption @State var showQRCodeScanner = false @State private var invitationUsed: Bool = false - @State private var connReqInvitation: String = "" + @State private var connLinkInvitation: CreatedConnLink = CreatedConnLink(connFullLink: "", connShortLink: nil) + @State private var showShortLink = true @State private var creatingConnReq = false @State var choosingProfile = false @State private var pastedLink: String = "" @@ -174,11 +175,12 @@ struct NewChatView: View { private func prepareAndInviteView() -> some View { ZStack { // ZStack is needed for views to not make transitions between each other - if connReqInvitation != "" { + if connLinkInvitation.connFullLink != "" { InviteView( invitationUsed: $invitationUsed, contactConnection: $contactConnection, - connReqInvitation: $connReqInvitation, + connLinkInvitation: $connLinkInvitation, + showShortLink: $showShortLink, choosingProfile: $choosingProfile ) } else if creatingConnReq { @@ -190,16 +192,16 @@ struct NewChatView: View { } private func createInvitation() { - if connReqInvitation == "" && contactConnection == nil && !creatingConnReq { + if connLinkInvitation.connFullLink == "" && contactConnection == nil && !creatingConnReq { creatingConnReq = true Task { _ = try? await Task.sleep(nanoseconds: 250_000000) let (r, apiAlert) = await apiAddContact(incognito: incognitoGroupDefault.get()) - if let (connReq, pcc) = r { + if let (connLink, pcc) = r { await MainActor.run { m.updateContactConnection(pcc) m.showingInvitation = ShowingInvitation(pcc: pcc, connChatUsed: false) - connReqInvitation = connReq + connLinkInvitation = connLink contactConnection = pcc } } else { @@ -243,7 +245,8 @@ private struct InviteView: View { @EnvironmentObject var theme: AppTheme @Binding var invitationUsed: Bool @Binding var contactConnection: PendingContactConnection? - @Binding var connReqInvitation: String + @Binding var connLinkInvitation: CreatedConnLink + @Binding var showShortLink: Bool @Binding var choosingProfile: Bool @AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false @@ -261,7 +264,7 @@ private struct InviteView: View { NavigationLink { ActiveProfilePicker( contactConnection: $contactConnection, - connReqInvitation: $connReqInvitation, + connLinkInvitation: $connLinkInvitation, incognitoEnabled: $incognitoDefault, choosingProfile: $choosingProfile, selectedProfile: selectedProfile @@ -296,7 +299,7 @@ private struct InviteView: View { private func shareLinkView() -> some View { HStack { - let link = simplexChatLink(connReqInvitation) + let link = connLinkInvitation.simplexChatUri(short: showShortLink) linkTextView(link) Button { showShareSheet(items: [link]) @@ -310,9 +313,9 @@ private struct InviteView: View { } private func qrCodeView() -> some View { - Section(header: Text("Or show this code").foregroundColor(theme.colors.secondary)) { - SimpleXLinkQRCode(uri: connReqInvitation, onShare: setInvitationUsed) - .id("simplex-qrcode-view-for-\(connReqInvitation)") + Section { + SimpleXCreatedLinkQRCode(link: connLinkInvitation, short: $showShortLink, onShare: setInvitationUsed) + .id("simplex-qrcode-view-for-\(connLinkInvitation.simplexChatUri(short: showShortLink))") .padding() .background( RoundedRectangle(cornerRadius: 12, style: .continuous) @@ -322,6 +325,8 @@ private struct InviteView: View { .listRowBackground(Color.clear) .listRowSeparator(.hidden) .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + } header: { + ToggleShortLinkHeader(text: Text("Or show this code"), link: connLinkInvitation, short: $showShortLink) } } @@ -343,7 +348,7 @@ private struct ActiveProfilePicker: View { @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var theme: AppTheme @Binding var contactConnection: PendingContactConnection? - @Binding var connReqInvitation: String + @Binding var connLinkInvitation: CreatedConnLink @Binding var incognitoEnabled: Bool @Binding var choosingProfile: Bool @State private var alert: SomeAlert? @@ -415,12 +420,11 @@ private struct ActiveProfilePicker: View { } Task { do { - if let contactConn = contactConnection, - let conn = try await apiChangeConnectionUser(connId: contactConn.pccConnId, userId: profile.userId) { - + if let contactConn = contactConnection { + let conn = try await apiChangeConnectionUser(connId: contactConn.pccConnId, userId: profile.userId) await MainActor.run { contactConnection = conn - connReqInvitation = conn.connReqInv ?? "" + connLinkInvitation = conn.connLinkInv ?? CreatedConnLink(connFullLink: "", connShortLink: nil) incognitoEnabled = false chatModel.updateContactConnection(conn) } @@ -836,23 +840,25 @@ func sharedProfileInfo(_ incognito: Bool) -> Text { } enum PlanAndConnectAlert: Identifiable { - case ownInvitationLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool) - case invitationLinkConnecting(connectionLink: String) - case ownContactAddressConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool) - case contactAddressConnectingConfirmReconnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool) - case groupLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool) - case groupLinkConnectingConfirmReconnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool) - case groupLinkConnecting(connectionLink: String, groupInfo: GroupInfo?) + case ownInvitationLinkConfirmConnect(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, incognito: Bool) + case invitationLinkConnecting(connectionLink: CreatedConnLink) + case ownContactAddressConfirmConnect(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, incognito: Bool) + case contactAddressConnectingConfirmReconnect(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, incognito: Bool) + case groupLinkConfirmConnect(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, incognito: Bool) + case groupLinkConnectingConfirmReconnect(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, incognito: Bool) + case groupLinkConnecting(connectionLink: CreatedConnLink, groupInfo: GroupInfo?) + case error(shortOrFullLink: String, alert: Alert) var id: String { switch self { - case let .ownInvitationLinkConfirmConnect(connectionLink, _, _): return "ownInvitationLinkConfirmConnect \(connectionLink)" - case let .invitationLinkConnecting(connectionLink): return "invitationLinkConnecting \(connectionLink)" - case let .ownContactAddressConfirmConnect(connectionLink, _, _): return "ownContactAddressConfirmConnect \(connectionLink)" - case let .contactAddressConnectingConfirmReconnect(connectionLink, _, _): return "contactAddressConnectingConfirmReconnect \(connectionLink)" - case let .groupLinkConfirmConnect(connectionLink, _, _): return "groupLinkConfirmConnect \(connectionLink)" - case let .groupLinkConnectingConfirmReconnect(connectionLink, _, _): return "groupLinkConnectingConfirmReconnect \(connectionLink)" - case let .groupLinkConnecting(connectionLink, _): return "groupLinkConnecting \(connectionLink)" + case let .ownInvitationLinkConfirmConnect(connectionLink, _, _): return "ownInvitationLinkConfirmConnect \(connectionLink.connFullLink)" + case let .invitationLinkConnecting(connectionLink): return "invitationLinkConnecting \(connectionLink.connFullLink)" + case let .ownContactAddressConfirmConnect(connectionLink, _, _): return "ownContactAddressConfirmConnect \(connectionLink.connFullLink)" + case let .contactAddressConnectingConfirmReconnect(connectionLink, _, _): return "contactAddressConnectingConfirmReconnect \(connectionLink.connFullLink)" + case let .groupLinkConfirmConnect(connectionLink, _, _): return "groupLinkConfirmConnect \(connectionLink.connFullLink)" + case let .groupLinkConnectingConfirmReconnect(connectionLink, _, _): return "groupLinkConnectingConfirmReconnect \(connectionLink.connFullLink)" + case let .groupLinkConnecting(connectionLink, _): return "groupLinkConnecting \(connectionLink.connFullLink)" + case let .error(shortOrFullLink, alert): return "error \(shortOrFullLink)" } } } @@ -935,21 +941,22 @@ func planAndConnectAlert(_ alert: PlanAndConnectAlert, dismiss: Bool, cleanup: ( dismissButton: .default(Text("OK")) { cleanup?() } ) } + case let .error(_, alert): return alert } } enum PlanAndConnectActionSheet: Identifiable { - case askCurrentOrIncognitoProfile(connectionLink: String, connectionPlan: ConnectionPlan?, title: LocalizedStringKey) - case askCurrentOrIncognitoProfileDestructive(connectionLink: String, connectionPlan: ConnectionPlan, title: LocalizedStringKey) + case askCurrentOrIncognitoProfile(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan?, title: LocalizedStringKey) + case askCurrentOrIncognitoProfileDestructive(connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan, title: LocalizedStringKey) case askCurrentOrIncognitoProfileConnectContactViaAddress(contact: Contact) - case ownGroupLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool?, groupInfo: GroupInfo) + case ownGroupLinkConfirmConnect(connectionLink: CreatedConnLink, 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 .askCurrentOrIncognitoProfile(connectionLink, _, _): return "askCurrentOrIncognitoProfile \(connectionLink.connFullLink)" + case let .askCurrentOrIncognitoProfileDestructive(connectionLink, _, _): return "askCurrentOrIncognitoProfileDestructive \(connectionLink.connFullLink)" case let .askCurrentOrIncognitoProfileConnectContactViaAddress(contact): return "askCurrentOrIncognitoProfileConnectContactViaAddress \(contact.contactId)" - case let .ownGroupLinkConfirmConnect(connectionLink, _, _, _): return "ownGroupLinkConfirmConnect \(connectionLink)" + case let .ownGroupLinkConfirmConnect(connectionLink, _, _, _): return "ownGroupLinkConfirmConnect \(connectionLink.connFullLink)" } } } @@ -1008,7 +1015,7 @@ func planAndConnectActionSheet(_ sheet: PlanAndConnectActionSheet, dismiss: Bool } func planAndConnect( - _ connectionLink: String, + _ shortOrFullLink: String, showAlert: @escaping (PlanAndConnectAlert) -> Void, showActionSheet: @escaping (PlanAndConnectActionSheet) -> Void, dismiss: Bool, @@ -1018,8 +1025,8 @@ func planAndConnect( filterKnownGroup: ((GroupInfo) -> Void)? = nil ) { Task { - do { - let connectionPlan = try await apiConnectPlan(connReq: connectionLink) + let (result, alert) = await apiConnectPlan(connLink: shortOrFullLink) + if let (connectionLink, connectionPlan) = result { switch connectionPlan { case let .invitationLink(ilp): switch ilp { @@ -1028,32 +1035,40 @@ func planAndConnect( if let incognito = incognito { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) } else { - showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect via one-time link")) + await MainActor.run { + showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect via one-time link")) + } } case .ownLink: logger.debug("planAndConnect, .invitationLink, .ownLink, incognito=\(incognito?.description ?? "nil")") - if let incognito = incognito { - showAlert(.ownInvitationLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) - } else { - showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own one-time link!")) + await MainActor.run { + if let incognito = incognito { + showAlert(.ownInvitationLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) + } else { + showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own one-time link!")) + } } case let .connecting(contact_): logger.debug("planAndConnect, .invitationLink, .connecting, incognito=\(incognito?.description ?? "nil")") - if let contact = contact_ { - if let f = filterKnownContact { - f(contact) + await MainActor.run { + if let contact = contact_ { + if let f = filterKnownContact { + f(contact) + } else { + openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) } + } } else { - openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) } + showAlert(.invitationLinkConnecting(connectionLink: connectionLink)) } - } else { - showAlert(.invitationLinkConnecting(connectionLink: connectionLink)) } case let .known(contact): logger.debug("planAndConnect, .invitationLink, .known, incognito=\(incognito?.description ?? "nil")") - if let f = filterKnownContact { - f(contact) - } else { - openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyExistsAlert(contact)) } + await MainActor.run { + if let f = filterKnownContact { + f(contact) + } else { + openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyExistsAlert(contact)) } + } } } case let .contactAddress(cap): @@ -1063,83 +1078,109 @@ func planAndConnect( if let incognito = incognito { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito, cleanup: cleanup) } else { - showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect via contact address")) + await MainActor.run { + showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect via contact address")) + } } case .ownLink: logger.debug("planAndConnect, .contactAddress, .ownLink, incognito=\(incognito?.description ?? "nil")") - if let incognito = incognito { - showAlert(.ownContactAddressConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) - } else { - showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own SimpleX address!")) + await MainActor.run { + if let incognito = incognito { + showAlert(.ownContactAddressConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) + } else { + showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own SimpleX address!")) + } } case .connectingConfirmReconnect: logger.debug("planAndConnect, .contactAddress, .connectingConfirmReconnect, incognito=\(incognito?.description ?? "nil")") - if let incognito = incognito { - showAlert(.contactAddressConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) - } else { - showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You have already requested connection!\nRepeat connection request?")) + await MainActor.run { + if let incognito = incognito { + showAlert(.contactAddressConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) + } else { + showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You have already requested connection!\nRepeat connection request?")) + } } case let .connectingProhibit(contact): logger.debug("planAndConnect, .contactAddress, .connectingProhibit, incognito=\(incognito?.description ?? "nil")") - if let f = filterKnownContact { - f(contact) - } else { - openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) } + await MainActor.run { + if let f = filterKnownContact { + f(contact) + } else { + openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) } + } } case let .known(contact): logger.debug("planAndConnect, .contactAddress, .known, incognito=\(incognito?.description ?? "nil")") - if let f = filterKnownContact { - f(contact) - } else { - openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyExistsAlert(contact)) } + await MainActor.run { + if let f = filterKnownContact { + f(contact) + } else { + openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyExistsAlert(contact)) } + } } case let .contactViaAddress(contact): logger.debug("planAndConnect, .contactAddress, .contactViaAddress, incognito=\(incognito?.description ?? "nil")") if let incognito = incognito { connectContactViaAddress_(contact, dismiss: dismiss, incognito: incognito, cleanup: cleanup) } else { - showActionSheet(.askCurrentOrIncognitoProfileConnectContactViaAddress(contact: contact)) + await MainActor.run { + showActionSheet(.askCurrentOrIncognitoProfileConnectContactViaAddress(contact: contact)) + } } } case let .groupLink(glp): switch glp { case .ok: - if let incognito = incognito { - showAlert(.groupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) - } else { - showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Join group")) + await MainActor.run { + if let incognito = incognito { + showAlert(.groupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) + } else { + showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Join group")) + } } case let .ownLink(groupInfo): logger.debug("planAndConnect, .groupLink, .ownLink, incognito=\(incognito?.description ?? "nil")") - if let f = filterKnownGroup { - f(groupInfo) + await MainActor.run { + if let f = filterKnownGroup { + f(groupInfo) + } + showActionSheet(.ownGroupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito, groupInfo: groupInfo)) } - showActionSheet(.ownGroupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito, groupInfo: groupInfo)) case .connectingConfirmReconnect: logger.debug("planAndConnect, .groupLink, .connectingConfirmReconnect, incognito=\(incognito?.description ?? "nil")") - if let incognito = incognito { - showAlert(.groupLinkConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) - } else { - showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You are already joining the group!\nRepeat join request?")) + await MainActor.run { + if let incognito = incognito { + showAlert(.groupLinkConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) + } else { + showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You are already joining the group!\nRepeat join request?")) + } } case let .connectingProhibit(groupInfo_): logger.debug("planAndConnect, .groupLink, .connectingProhibit, incognito=\(incognito?.description ?? "nil")") - showAlert(.groupLinkConnecting(connectionLink: connectionLink, groupInfo: groupInfo_)) + await MainActor.run { + showAlert(.groupLinkConnecting(connectionLink: connectionLink, groupInfo: groupInfo_)) + } case let .known(groupInfo): logger.debug("planAndConnect, .groupLink, .known, incognito=\(incognito?.description ?? "nil")") - if let f = filterKnownGroup { - f(groupInfo) - } else { - openKnownGroup(groupInfo, dismiss: dismiss) { AlertManager.shared.showAlert(groupAlreadyExistsAlert(groupInfo)) } + await MainActor.run { + if let f = filterKnownGroup { + f(groupInfo) + } else { + openKnownGroup(groupInfo, dismiss: dismiss) { AlertManager.shared.showAlert(groupAlreadyExistsAlert(groupInfo)) } + } } } + case let .error(chatError): + logger.debug("planAndConnect, .error \(chatErrorString(chatError))") + if let incognito = incognito { + connectViaLink(connectionLink, connectionPlan: nil, dismiss: dismiss, incognito: incognito, cleanup: cleanup) + } else { + showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: nil, title: "Connect via link")) + } } - } catch { - logger.debug("planAndConnect, plan error") - if let incognito = incognito { - connectViaLink(connectionLink, connectionPlan: nil, dismiss: dismiss, incognito: incognito, cleanup: cleanup) - } else { - showActionSheet(.askCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: nil, title: "Connect via link")) + } else if let alert { + await MainActor.run { + showAlert(.error(shortOrFullLink: shortOrFullLink, alert: alert)) } } } @@ -1161,22 +1202,22 @@ private func connectContactViaAddress_(_ contact: Contact, dismiss: Bool, incogn } private func connectViaLink( - _ connectionLink: String, + _ connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan?, dismiss: Bool, incognito: Bool, cleanup: (() -> Void)? ) { Task { - if let (connReqType, pcc) = await apiConnect(incognito: incognito, connReq: connectionLink) { + if let (connReqType, pcc) = await apiConnect(incognito: incognito, connLink: connectionLink) { await MainActor.run { ChatModel.shared.updateContactConnection(pcc) } let crt: ConnReqType - if let plan = connectionPlan { - crt = planToConnReqType(plan) + crt = if let plan = connectionPlan { + planToConnReqType(plan) ?? connReqType } else { - crt = connReqType + connReqType } DispatchQueue.main.async { if dismiss { @@ -1199,43 +1240,35 @@ private func connectViaLink( } func openKnownContact(_ contact: Contact, dismiss: Bool, showAlreadyExistsAlert: (() -> Void)?) { - Task { - let m = ChatModel.shared - if let c = m.getContactChat(contact.contactId) { - DispatchQueue.main.async { - if dismiss { - dismissAllSheets(animated: true) { - ItemsModel.shared.loadOpenChat(c.id) { - showAlreadyExistsAlert?() - } - } - } else { - ItemsModel.shared.loadOpenChat(c.id) { - showAlreadyExistsAlert?() - } + let m = ChatModel.shared + if let c = m.getContactChat(contact.contactId) { + if dismiss { + dismissAllSheets(animated: true) { + ItemsModel.shared.loadOpenChat(c.id) { + showAlreadyExistsAlert?() } } + } else { + ItemsModel.shared.loadOpenChat(c.id) { + showAlreadyExistsAlert?() + } } } } func openKnownGroup(_ groupInfo: GroupInfo, dismiss: Bool, showAlreadyExistsAlert: (() -> Void)?) { - Task { - let m = ChatModel.shared - if let g = m.getGroupChat(groupInfo.groupId) { - DispatchQueue.main.async { - if dismiss { - dismissAllSheets(animated: true) { - ItemsModel.shared.loadOpenChat(g.id) { - showAlreadyExistsAlert?() - } - } - } else { - ItemsModel.shared.loadOpenChat(g.id) { - showAlreadyExistsAlert?() - } + let m = ChatModel.shared + if let g = m.getGroupChat(groupInfo.groupId) { + if dismiss { + dismissAllSheets(animated: true) { + ItemsModel.shared.loadOpenChat(g.id) { + showAlreadyExistsAlert?() } } + } else { + ItemsModel.shared.loadOpenChat(g.id) { + showAlreadyExistsAlert?() + } } } } @@ -1273,11 +1306,12 @@ enum ConnReqType: Equatable { } } -private func planToConnReqType(_ connectionPlan: ConnectionPlan) -> ConnReqType { +private func planToConnReqType(_ connectionPlan: ConnectionPlan) -> ConnReqType? { switch connectionPlan { - case .invitationLink: return .invitation - case .contactAddress: return .contact - case .groupLink: return .groupLink + case .invitationLink: .invitation + case .contactAddress: .contact + case .groupLink: .groupLink + case .error: nil } } diff --git a/apps/ios/Shared/Views/NewChat/QRCode.swift b/apps/ios/Shared/Views/NewChat/QRCode.swift index bc1dc4b5bc..453149198b 100644 --- a/apps/ios/Shared/Views/NewChat/QRCode.swift +++ b/apps/ios/Shared/Views/NewChat/QRCode.swift @@ -8,6 +8,7 @@ import SwiftUI import CoreImage.CIFilterBuiltins +import SimpleXChat struct MutableQRCode: View { @Binding var uri: String @@ -20,6 +21,16 @@ struct MutableQRCode: View { } } +struct SimpleXCreatedLinkQRCode: View { + let link: CreatedConnLink + @Binding var short: Bool + var onShare: (() -> Void)? = nil + + var body: some View { + QRCode(uri: link.simplexChatUri(short: short), onShare: onShare) + } +} + struct SimpleXLinkQRCode: View { let uri: String var withLogo: Bool = true @@ -31,12 +42,6 @@ struct SimpleXLinkQRCode: View { } } -func simplexChatLink(_ uri: String) -> String { - uri.starts(with: "simplex:/") - ? uri.replacingOccurrences(of: "simplex:/", with: "https://simplex.chat/") - : uri -} - struct QRCode: View { let uri: String var withLogo: Bool = true diff --git a/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift b/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift index befb34b318..a2f5db7f03 100644 --- a/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift +++ b/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift @@ -31,7 +31,7 @@ struct CreateSimpleXAddress: View { Spacer() if let userAddress = m.userAddress { - SimpleXLinkQRCode(uri: userAddress.connReqContact) + SimpleXCreatedLinkQRCode(link: userAddress.connLinkContact, short: Binding.constant(false)) .frame(maxHeight: g.size.width) shareQRCodeButton(userAddress) .frame(maxWidth: .infinity) @@ -77,9 +77,9 @@ struct CreateSimpleXAddress: View { progressIndicator = true Task { do { - let connReqContact = try await apiCreateUserAddress() + let connLinkContact = try await apiCreateUserAddress(short: false) DispatchQueue.main.async { - m.userAddress = UserContactLink(connReqContact: connReqContact) + m.userAddress = UserContactLink(connLinkContact: connLinkContact) } await MainActor.run { progressIndicator = false } } catch let error { @@ -121,7 +121,7 @@ struct CreateSimpleXAddress: View { private func shareQRCodeButton(_ userAddress: UserContactLink) -> some View { Button { - showShareSheet(items: [simplexChatLink(userAddress.connReqContact)]) + showShareSheet(items: [simplexChatLink(userAddress.connLinkContact.simplexChatUri(short: false))]) } label: { Label("Share", systemImage: "square.and.arrow.up") } @@ -189,7 +189,7 @@ struct SendAddressMailView: View { let messageBody = String(format: NSLocalizedString("""

    Hi!

    Connect to me via SimpleX Chat

    - """, comment: "email text"), simplexChatLink(userAddress.connReqContact)) + """, comment: "email text"), simplexChatLink(userAddress.connLinkContact.simplexChatUri(short: false))) MailView( isShowing: self.$showMailView, result: $mailViewResult, diff --git a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift index 0b9d1ef76c..1a17b9d661 100644 --- a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift +++ b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift @@ -20,6 +20,8 @@ struct PrivacySettings: View { @AppStorage(GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES, store: groupDefaults) private var encryptLocalFiles = true @AppStorage(GROUP_DEFAULT_PRIVACY_ASK_TO_APPROVE_RELAYS, store: groupDefaults) private var askToApproveRelays = true @State private var simplexLinkMode = privacySimplexLinkModeDefault.get() + @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false + @AppStorage(DEFAULT_PRIVACY_SHORT_LINKS) private var shortSimplexLinks = false @AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = false @AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false @State private var currentLAMode = privacyLocalAuthModeDefault.get() @@ -111,6 +113,11 @@ struct PrivacySettings: View { .onChange(of: simplexLinkMode) { mode in privacySimplexLinkModeDefault.set(mode) } + if developerTools { + settingsRow("link.badge.plus", color: theme.colors.secondary) { + Toggle("Use short links (BETA)", isOn: $shortSimplexLinks) + } + } } header: { Text("Chats") .foregroundColor(theme.colors.secondary) diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index 80e2a537da..961cad128f 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -33,6 +33,7 @@ let DEFAULT_PRIVACY_CHAT_LIST_OPEN_LINKS = "privacyChatListOpenLinks" let DEFAULT_PRIVACY_SIMPLEX_LINK_MODE = "privacySimplexLinkMode" let DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS = "privacyShowChatPreviews" let DEFAULT_PRIVACY_SAVE_LAST_DRAFT = "privacySaveLastDraft" +let DEFAULT_PRIVACY_SHORT_LINKS = "privacyShortLinks" let DEFAULT_PRIVACY_PROTECT_SCREEN = "privacyProtectScreen" let DEFAULT_PRIVACY_DELIVERY_RECEIPTS_SET = "privacyDeliveryReceiptsSet" let DEFAULT_PRIVACY_MEDIA_BLUR_RADIUS = "privacyMediaBlurRadius" @@ -99,6 +100,7 @@ let appDefaults: [String: Any] = [ DEFAULT_PRIVACY_SIMPLEX_LINK_MODE: SimpleXLinkMode.description.rawValue, DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS: true, DEFAULT_PRIVACY_SAVE_LAST_DRAFT: true, + DEFAULT_PRIVACY_SHORT_LINKS: false, DEFAULT_PRIVACY_PROTECT_SCREEN: false, DEFAULT_PRIVACY_DELIVERY_RECEIPTS_SET: false, DEFAULT_PRIVACY_MEDIA_BLUR_RADIUS: 0, diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift index 7965215b49..4813edf96c 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift @@ -8,7 +8,7 @@ import SwiftUI import MessageUI -import SimpleXChat +@preconcurrency import SimpleXChat struct UserAddressView: View { @Environment(\.dismiss) var dismiss: DismissAction @@ -16,6 +16,7 @@ struct UserAddressView: View { @EnvironmentObject var theme: AppTheme @State var shareViaProfile = false @State var autoCreate = false + @State private var showShortLink = true @State private var aas = AutoAcceptState() @State private var savedAAS = AutoAcceptState() @State private var showMailView = false @@ -135,8 +136,8 @@ struct UserAddressView: View { @ViewBuilder private func existingAddressView(_ userAddress: UserContactLink) -> some View { Section { - SimpleXLinkQRCode(uri: userAddress.connReqContact) - .id("simplex-contact-address-qrcode-\(userAddress.connReqContact)") + SimpleXCreatedLinkQRCode(link: userAddress.connLinkContact, short: $showShortLink) + .id("simplex-contact-address-qrcode-\(userAddress.connLinkContact.simplexChatUri(short: showShortLink))") shareQRCodeButton(userAddress) // if MFMailComposeViewController.canSendMail() { // shareViaEmailButton(userAddress) @@ -153,8 +154,7 @@ struct UserAddressView: View { } addressSettingsButton(userAddress) } header: { - Text("For social media") - .foregroundColor(theme.colors.secondary) + ToggleShortLinkHeader(text: Text("For social media"), link: userAddress.connLinkContact, short: $showShortLink) } footer: { if aas.business { Text("Add your team members to the conversations.") @@ -193,9 +193,10 @@ struct UserAddressView: View { progressIndicator = true Task { do { - let connReqContact = try await apiCreateUserAddress() + let short = UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_SHORT_LINKS) + let connLinkContact = try await apiCreateUserAddress(short: short) DispatchQueue.main.async { - chatModel.userAddress = UserContactLink(connReqContact: connReqContact) + chatModel.userAddress = UserContactLink(connLinkContact: connLinkContact) alert = .shareOnCreate progressIndicator = false } @@ -231,7 +232,7 @@ struct UserAddressView: View { private func shareQRCodeButton(_ userAddress: UserContactLink) -> some View { Button { - showShareSheet(items: [simplexChatLink(userAddress.connReqContact)]) + showShareSheet(items: [simplexChatLink(userAddress.connLinkContact.simplexChatUri(short: showShortLink))]) } label: { settingsRow("square.and.arrow.up", color: theme.colors.secondary) { Text("Share address") @@ -294,6 +295,28 @@ struct UserAddressView: View { } } +struct ToggleShortLinkHeader: View { + @EnvironmentObject var theme: AppTheme + let text: Text + var link: CreatedConnLink + @Binding var short: Bool + + var body: some View { + if link.connShortLink == nil { + text.foregroundColor(theme.colors.secondary) + } else { + HStack { + text.foregroundColor(theme.colors.secondary) + Spacer() + Text(short ? "Full link" : "Short link") + .textCase(.none) + .foregroundColor(theme.colors.primary) + .onTapGesture { short.toggle() } + } + } + } +} + private struct AutoAcceptState: Equatable { var enable = false var incognito = false @@ -542,7 +565,7 @@ private func saveAAS(_ aas: Binding, _ savedAAS: Bindingapplinks:simplex.chat applinks:www.simplex.chat applinks:simplex.chat?mode=developer + applinks:*.simplex.im + applinks:*.simplex.im?mode=developer + applinks:*.simplexonflux.com + applinks:*.simplexonflux.com?mode=developer com.apple.security.application-groups diff --git a/apps/ios/SimpleXChat/API.swift b/apps/ios/SimpleXChat/API.swift index e439cd337b..869dffea31 100644 --- a/apps/ios/SimpleXChat/API.swift +++ b/apps/ios/SimpleXChat/API.swift @@ -324,7 +324,7 @@ public func responseError(_ err: Error) -> String { } } -func chatErrorString(_ err: ChatError) -> String { +public func chatErrorString(_ err: ChatError) -> String { if case let .invalidJSON(json) = err { return json } return String(describing: err) } diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 6db0478ab3..a9de0df01b 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -77,7 +77,7 @@ public enum ChatCommand { case apiLeaveGroup(groupId: Int64) case apiListMembers(groupId: Int64) case apiUpdateGroupProfile(groupId: Int64, groupProfile: GroupProfile) - case apiCreateGroupLink(groupId: Int64, memberRole: GroupMemberRole) + case apiCreateGroupLink(groupId: Int64, memberRole: GroupMemberRole, short: Bool) case apiGroupLinkMemberRole(groupId: Int64, memberRole: GroupMemberRole) case apiDeleteGroupLink(groupId: Int64) case apiGetGroupLink(groupId: Int64) @@ -116,11 +116,11 @@ public enum ChatCommand { case apiGetGroupMemberCode(groupId: Int64, groupMemberId: Int64) case apiVerifyContact(contactId: Int64, connectionCode: String?) case apiVerifyGroupMember(groupId: Int64, groupMemberId: Int64, connectionCode: String?) - case apiAddContact(userId: Int64, incognito: Bool) + case apiAddContact(userId: Int64, short: Bool, incognito: Bool) case apiSetConnectionIncognito(connId: Int64, incognito: Bool) case apiChangeConnectionUser(connId: Int64, userId: Int64) - case apiConnectPlan(userId: Int64, connReq: String) - case apiConnect(userId: Int64, incognito: Bool, connReq: String) + case apiConnectPlan(userId: Int64, connLink: String) + case apiConnect(userId: Int64, incognito: Bool, connLink: CreatedConnLink) case apiConnectContactViaAddress(userId: Int64, incognito: Bool, contactId: Int64) case apiDeleteChat(type: ChatType, id: Int64, chatDeleteMode: ChatDeleteMode) case apiClearChat(type: ChatType, id: Int64) @@ -132,7 +132,7 @@ public enum ChatCommand { case apiSetConnectionAlias(connId: Int64, localAlias: String) case apiSetUserUIThemes(userId: Int64, themes: ThemeModeOverrides?) case apiSetChatUIThemes(chatId: String, themes: ThemeModeOverrides?) - case apiCreateMyAddress(userId: Int64) + case apiCreateMyAddress(userId: Int64, short: Bool) case apiDeleteMyAddress(userId: Int64) case apiShowMyAddress(userId: Int64) case apiSetProfileAddress(userId: Int64, on: Bool) @@ -256,7 +256,7 @@ public enum ChatCommand { case let .apiLeaveGroup(groupId): return "/_leave #\(groupId)" case let .apiListMembers(groupId): return "/_members #\(groupId)" case let .apiUpdateGroupProfile(groupId, groupProfile): return "/_group_profile #\(groupId) \(encodeJSON(groupProfile))" - case let .apiCreateGroupLink(groupId, memberRole): return "/_create link #\(groupId) \(memberRole)" + case let .apiCreateGroupLink(groupId, memberRole, short): return "/_create link #\(groupId) \(memberRole) short=\(onOff(short))" case let .apiGroupLinkMemberRole(groupId, memberRole): return "/_set link role #\(groupId) \(memberRole)" case let .apiDeleteGroupLink(groupId): return "/_delete link #\(groupId)" case let .apiGetGroupLink(groupId): return "/_get link #\(groupId)" @@ -305,11 +305,11 @@ public enum ChatCommand { case let .apiVerifyContact(contactId, .none): return "/_verify code @\(contactId)" case let .apiVerifyGroupMember(groupId, groupMemberId, .some(connectionCode)): return "/_verify code #\(groupId) \(groupMemberId) \(connectionCode)" case let .apiVerifyGroupMember(groupId, groupMemberId, .none): return "/_verify code #\(groupId) \(groupMemberId)" - case let .apiAddContact(userId, incognito): return "/_connect \(userId) incognito=\(onOff(incognito))" + case let .apiAddContact(userId, short, incognito): return "/_connect \(userId) short=\(onOff(short)) incognito=\(onOff(incognito))" case let .apiSetConnectionIncognito(connId, incognito): return "/_set incognito :\(connId) \(onOff(incognito))" case let .apiChangeConnectionUser(connId, userId): return "/_set conn user :\(connId) \(userId)" - 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 .apiConnectPlan(userId, connLink): return "/_connect plan \(userId) \(connLink)" + case let .apiConnect(userId, incognito, connLink): return "/_connect \(userId) incognito=\(onOff(incognito)) \(connLink.connFullLink) \(connLink.connShortLink ?? "")" case let .apiConnectContactViaAddress(userId, incognito, contactId): return "/_connect contact \(userId) incognito=\(onOff(incognito)) \(contactId)" case let .apiDeleteChat(type, id, chatDeleteMode): return "/_delete \(ref(type, id)) \(chatDeleteMode.cmdString)" case let .apiClearChat(type, id): return "/_clear chat \(ref(type, id))" @@ -321,7 +321,7 @@ public enum ChatCommand { case let .apiSetConnectionAlias(connId, localAlias): return "/_set alias :\(connId) \(localAlias.trimmingCharacters(in: .whitespaces))" case let .apiSetUserUIThemes(userId, themes): return "/_set theme user \(userId) \(themes != nil ? encodeJSON(themes) : "")" case let .apiSetChatUIThemes(chatId, themes): return "/_set theme \(chatId) \(themes != nil ? encodeJSON(themes) : "")" - case let .apiCreateMyAddress(userId): return "/_address \(userId)" + case let .apiCreateMyAddress(userId, short): return "/_address \(userId) short=\(onOff(short))" case let .apiDeleteMyAddress(userId): return "/_delete_address \(userId)" case let .apiShowMyAddress(userId): return "/_show_address \(userId)" case let .apiSetProfileAddress(userId, on): return "/_profile_address \(userId) \(onOff(on))" @@ -629,10 +629,10 @@ public enum ChatResponse: Decodable, Error { case groupMemberCode(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionCode: String) case connectionVerified(user: UserRef, verified: Bool, expectedCode: String) case tagsUpdated(user: UserRef, userTags: [ChatTag], chatTags: [Int64]) - case invitation(user: UserRef, connReqInvitation: String, connection: PendingContactConnection) + case invitation(user: UserRef, connLinkInvitation: CreatedConnLink, connection: PendingContactConnection) case connectionIncognitoUpdated(user: UserRef, toConnection: PendingContactConnection) case connectionUserChanged(user: UserRef, fromConnection: PendingContactConnection, toConnection: PendingContactConnection, newUser: UserRef) - case connectionPlan(user: UserRef, connectionPlan: ConnectionPlan) + case connectionPlan(user: UserRef, connLink: CreatedConnLink, connectionPlan: ConnectionPlan) case sentConfirmation(user: UserRef, connection: PendingContactConnection) case sentInvitation(user: UserRef, connection: PendingContactConnection) case sentInvitationToContact(user: UserRef, contact: Contact, customUserProfile: Profile?) @@ -649,7 +649,7 @@ public enum ChatResponse: Decodable, Error { case contactPrefsUpdated(user: User, fromContact: Contact, toContact: Contact) case userContactLink(user: User, contactLink: UserContactLink) case userContactLinkUpdated(user: User, contactLink: UserContactLink) - case userContactLinkCreated(user: User, connReqContact: String) + case userContactLinkCreated(user: User, connLinkContact: CreatedConnLink) case userContactLinkDeleted(user: User) case contactConnected(user: UserRef, contact: Contact, userCustomProfile: Profile?) case contactConnecting(user: UserRef, contact: Contact) @@ -702,8 +702,8 @@ public enum ChatResponse: Decodable, Error { case connectedToGroupMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember, memberContact: Contact?) case groupRemoved(user: UserRef, groupInfo: GroupInfo) // unused case groupUpdated(user: UserRef, toGroup: GroupInfo) - case groupLinkCreated(user: UserRef, groupInfo: GroupInfo, connReqContact: String, memberRole: GroupMemberRole) - case groupLink(user: UserRef, groupInfo: GroupInfo, connReqContact: String, memberRole: GroupMemberRole) + case groupLinkCreated(user: UserRef, groupInfo: GroupInfo, connLinkContact: CreatedConnLink, memberRole: GroupMemberRole) + case groupLink(user: UserRef, groupInfo: GroupInfo, connLinkContact: CreatedConnLink, memberRole: GroupMemberRole) case groupLinkDeleted(user: UserRef, groupInfo: GroupInfo) case newMemberContact(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember) case newMemberContactSentInv(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember) @@ -989,10 +989,10 @@ public enum ChatResponse: Decodable, Error { case let .groupMemberCode(u, groupInfo, member, connectionCode): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionCode: \(connectionCode)") case let .connectionVerified(u, verified, expectedCode): return withUser(u, "verified: \(verified)\nconnectionCode: \(expectedCode)") case let .tagsUpdated(u, userTags, chatTags): return withUser(u, "userTags: \(String(describing: userTags))\nchatTags: \(String(describing: chatTags))") - case let .invitation(u, connReqInvitation, connection): return withUser(u, "connReqInvitation: \(connReqInvitation)\nconnection: \(connection)") + case let .invitation(u, connLinkInvitation, connection): return withUser(u, "connLinkInvitation: \(connLinkInvitation)\nconnection: \(connection)") case let .connectionIncognitoUpdated(u, toConnection): return withUser(u, String(describing: toConnection)) - case let .connectionUserChanged(u, fromConnection, toConnection, newUser): return withUser(u, "fromConnection: \(String(describing: fromConnection))\ntoConnection: \(String(describing: toConnection))\newUserId: \(String(describing: newUser.userId))") - case let .connectionPlan(u, connectionPlan): return withUser(u, String(describing: connectionPlan)) + case let .connectionUserChanged(u, fromConnection, toConnection, newUser): return withUser(u, "fromConnection: \(String(describing: fromConnection))\ntoConnection: \(String(describing: toConnection))\nnewUserId: \(String(describing: newUser.userId))") + case let .connectionPlan(u, connLink, connectionPlan): return withUser(u, "connLink: \(String(describing: connLink))\nconnectionPlan: \(String(describing: connectionPlan))") case let .sentConfirmation(u, connection): return withUser(u, String(describing: connection)) case let .sentInvitation(u, connection): return withUser(u, String(describing: connection)) case let .sentInvitationToContact(u, contact, _): return withUser(u, String(describing: contact)) @@ -1009,7 +1009,7 @@ public enum ChatResponse: Decodable, Error { case let .contactPrefsUpdated(u, fromContact, toContact): return withUser(u, "fromContact: \(String(describing: fromContact))\ntoContact: \(String(describing: toContact))") case let .userContactLink(u, contactLink): return withUser(u, contactLink.responseDetails) case let .userContactLinkUpdated(u, contactLink): return withUser(u, contactLink.responseDetails) - case let .userContactLinkCreated(u, connReq): return withUser(u, connReq) + case let .userContactLinkCreated(u, connLink): return withUser(u, String(describing: connLink)) case .userContactLinkDeleted: return noDetails case let .contactConnected(u, contact, _): return withUser(u, String(describing: contact)) case let .contactConnecting(u, contact): return withUser(u, String(describing: contact)) @@ -1069,8 +1069,8 @@ public enum ChatResponse: Decodable, Error { case let .connectedToGroupMember(u, groupInfo, member, memberContact): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)\nmemberContact: \(String(describing: memberContact))") case let .groupRemoved(u, groupInfo): return withUser(u, String(describing: groupInfo)) case let .groupUpdated(u, toGroup): return withUser(u, String(describing: toGroup)) - case let .groupLinkCreated(u, groupInfo, connReqContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnReqContact: \(connReqContact)\nmemberRole: \(memberRole)") - case let .groupLink(u, groupInfo, connReqContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnReqContact: \(connReqContact)\nmemberRole: \(memberRole)") + case let .groupLinkCreated(u, groupInfo, connLinkContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnLinkContact: \(connLinkContact)\nmemberRole: \(memberRole)") + case let .groupLink(u, groupInfo, connLinkContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnLinkContact: \(connLinkContact)\nmemberRole: \(memberRole)") case let .groupLinkDeleted(u, groupInfo): return withUser(u, String(describing: groupInfo)) case let .newMemberContact(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") case let .newMemberContactSentInv(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") @@ -1173,10 +1173,31 @@ public enum ChatDeleteMode: Codable { } } +public struct CreatedConnLink: Decodable, Hashable { + public var connFullLink: String + public var connShortLink: String? + + public init(connFullLink: String, connShortLink: String?) { + self.connFullLink = connFullLink + self.connShortLink = connShortLink + } + + public func simplexChatUri(short: Bool = true) -> String { + short ? (connShortLink ?? simplexChatLink(connFullLink)) : simplexChatLink(connFullLink) + } +} + +public func simplexChatLink(_ uri: String) -> String { + uri.starts(with: "simplex:/") + ? uri.replacingOccurrences(of: "simplex:/", with: "https://simplex.chat/") + : uri +} + public enum ConnectionPlan: Decodable, Hashable { case invitationLink(invitationLinkPlan: InvitationLinkPlan) case contactAddress(contactAddressPlan: ContactAddressPlan) case groupLink(groupLinkPlan: GroupLinkPlan) + case error(chatError: ChatError) } public enum InvitationLinkPlan: Decodable, Hashable { @@ -2183,16 +2204,16 @@ public enum RatchetSyncState: String, Decodable { } public struct UserContactLink: Decodable, Hashable { - public var connReqContact: String + public var connLinkContact: CreatedConnLink public var autoAccept: AutoAccept? - public init(connReqContact: String, autoAccept: AutoAccept? = nil) { - self.connReqContact = connReqContact + public init(connLinkContact: CreatedConnLink, autoAccept: AutoAccept? = nil) { + self.connLinkContact = connLinkContact self.autoAccept = autoAccept } var responseDetails: String { - "connReqContact: \(connReqContact)\nautoAccept: \(AutoAccept.cmdString(autoAccept))" + "connLinkContact: \(connLinkContact)\nautoAccept: \(AutoAccept.cmdString(autoAccept))" } } @@ -2404,8 +2425,8 @@ public enum ChatErrorType: Decodable, Hashable { case chatNotStarted case chatNotStopped case chatStoreChanged - case connectionPlan(connectionPlan: ConnectionPlan) case invalidConnReq + case unsupportedConnReq case invalidChatMessage(connection: Connection, message: String) case contactNotReady(contact: Contact) case contactNotActive(contact: Contact) @@ -2521,6 +2542,7 @@ public enum StoreError: Decodable, Hashable { case hostMemberIdNotFound(groupId: Int64) case contactNotFoundByFileId(fileId: Int64) case noGroupSndStatus(itemId: Int64, groupMemberId: Int64) + case dBException(message: String) } public enum DatabaseError: Decodable, Hashable { diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 51feb623e2..0c47442987 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -1853,7 +1853,7 @@ public struct PendingContactConnection: Decodable, NamedChat, Hashable { public var viaContactUri: Bool public var groupLinkId: String? public var customUserProfileId: Int64? - public var connReqInv: String? + public var connLinkInv: CreatedConnLink? public var localAlias: String var createdAt: Date public var updatedAt: Date @@ -4063,12 +4063,14 @@ public enum SimplexLinkType: String, Decodable, Hashable { case contact case invitation case group + case channel public var description: String { switch self { case .contact: return NSLocalizedString("SimpleX contact address", comment: "simplex link type") case .invitation: return NSLocalizedString("SimpleX one-time invitation", comment: "simplex link type") case .group: return NSLocalizedString("SimpleX group link", comment: "simplex link type") + case .channel: return NSLocalizedString("SimpleX channel link", comment: "simplex link type") } } } diff --git a/apps/multiplatform/android/src/main/AndroidManifest.xml b/apps/multiplatform/android/src/main/AndroidManifest.xml index 48dfba11cc..0470977bcd 100644 --- a/apps/multiplatform/android/src/main/AndroidManifest.xml +++ b/apps/multiplatform/android/src/main/AndroidManifest.xml @@ -77,8 +77,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 59343cd326..7ad26c3726 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -1078,7 +1078,7 @@ interface ChatItemsChangesListener { data class ShowingInvitation( val connId: String, - val connReq: String, + val connLink: CreatedConnLink, val connChatUsed: Boolean, val conn: PendingContactConnection ) @@ -2198,7 +2198,7 @@ class PendingContactConnection( val viaContactUri: Boolean, val groupLinkId: String? = null, val customUserProfileId: Long? = null, - val connReqInv: String? = null, + val connLinkInv: CreatedConnLink? = null, override val localAlias: String, override val createdAt: Instant, override val updatedAt: Instant @@ -3968,12 +3968,14 @@ sealed class Format { enum class SimplexLinkType(val linkType: String) { contact("contact"), invitation("invitation"), - group("group"); + group("group"), + channel("channel"); val description: String get() = generalGetString(when (this) { contact -> MR.strings.simplex_link_contact invitation -> MR.strings.simplex_link_invitation group -> MR.strings.simplex_link_group + channel -> MR.strings.simplex_link_channel }) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index a090919e6d..b5f44fdebc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -5,6 +5,8 @@ import androidx.compose.foundation.layout.* import androidx.compose.material.* import chat.simplex.common.views.helpers.* import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.listSaver import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester @@ -119,6 +121,7 @@ class AppPreferences { ) val privacyShowChatPreviews = mkBoolPreference(SHARED_PREFS_PRIVACY_SHOW_CHAT_PREVIEWS, true) val privacySaveLastDraft = mkBoolPreference(SHARED_PREFS_PRIVACY_SAVE_LAST_DRAFT, true) + val privacyShortLinks = mkBoolPreference(SHARED_PREFS_PRIVACY_SHORT_LINKS, false) val privacyDeliveryReceiptsSet = mkBoolPreference(SHARED_PREFS_PRIVACY_DELIVERY_RECEIPTS_SET, false) val privacyEncryptLocalFiles = mkBoolPreference(SHARED_PREFS_PRIVACY_ENCRYPT_LOCAL_FILES, true) val privacyAskToApproveRelays = mkBoolPreference(SHARED_PREFS_PRIVACY_ASK_TO_APPROVE_RELAYS, true) @@ -378,6 +381,7 @@ class AppPreferences { private const val SHARED_PREFS_PRIVACY_SIMPLEX_LINK_MODE = "PrivacySimplexLinkMode" private const val SHARED_PREFS_PRIVACY_SHOW_CHAT_PREVIEWS = "PrivacyShowChatPreviews" private const val SHARED_PREFS_PRIVACY_SAVE_LAST_DRAFT = "PrivacySaveLastDraft" + private const val SHARED_PREFS_PRIVACY_SHORT_LINKS = "PrivacyShortLinks" private const val SHARED_PREFS_PRIVACY_DELIVERY_RECEIPTS_SET = "PrivacyDeliveryReceiptsSet" private const val SHARED_PREFS_PRIVACY_ENCRYPT_LOCAL_FILES = "PrivacyEncryptLocalFiles" private const val SHARED_PREFS_PRIVACY_ASK_TO_APPROVE_RELAYS = "PrivacyAskToApproveRelays" @@ -1364,11 +1368,12 @@ object ChatController { - suspend fun apiAddContact(rh: Long?, incognito: Boolean): Pair?, (() -> Unit)?> { + suspend fun apiAddContact(rh: Long?, incognito: Boolean): Pair?, (() -> Unit)?> { val userId = try { currentUserId("apiAddContact") } catch (e: Exception) { return null to null } - val r = sendCmd(rh, CC.APIAddContact(userId, incognito)) + val short = appPrefs.privacyShortLinks.get() + val r = sendCmd(rh, CC.APIAddContact(userId, short = short, incognito = incognito)) return when (r) { - is CR.Invitation -> (r.connReqInvitation to r.connection) to null + is CR.Invitation -> (r.connLinkInvitation to r.connection) to null else -> { if (!(networkErrorAlert(r))) { return null to { apiErrorAlert("apiAddContact", generalGetString(MR.strings.connection_error), r) } @@ -1406,34 +1411,45 @@ object ChatController { } } - suspend fun apiConnectPlan(rh: Long?, connReq: String): ConnectionPlan? { + suspend fun apiConnectPlan(rh: Long?, connLink: String): Pair? { val userId = kotlin.runCatching { currentUserId("apiConnectPlan") }.getOrElse { return null } - val r = sendCmd(rh, CC.APIConnectPlan(userId, connReq)) - if (r is CR.CRConnectionPlan) return r.connectionPlan - Log.e(TAG, "apiConnectPlan bad response: ${r.responseType} ${r.details}") + val r = sendCmd(rh, CC.APIConnectPlan(userId, connLink)) + if (r is CR.CRConnectionPlan) return r.connLink to r.connectionPlan + apiConnectResponseAlert(r) return null } - suspend fun apiConnect(rh: Long?, incognito: Boolean, connReq: String): PendingContactConnection? { + suspend fun apiConnect(rh: Long?, incognito: Boolean, connLink: CreatedConnLink): PendingContactConnection? { val userId = try { currentUserId("apiConnect") } catch (e: Exception) { return null } - val r = sendCmd(rh, CC.APIConnect(userId, incognito, connReq)) + val r = sendCmd(rh, CC.APIConnect(userId, incognito, connLink)) when { r is CR.SentConfirmation -> return r.connection r is CR.SentInvitation -> return r.connection - r is CR.ContactAlreadyExists -> { + r is CR.ContactAlreadyExists -> AlertManager.shared.showAlertMsg( generalGetString(MR.strings.contact_already_exists), String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), r.contact.displayName) ) - return null - } + else -> apiConnectResponseAlert(r) + } + return null + } + + private fun apiConnectResponseAlert(r: CR) { + when { r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorChat && r.chatError.errorType is ChatErrorType.InvalidConnReq -> { AlertManager.shared.showAlertMsg( generalGetString(MR.strings.invalid_connection_link), generalGetString(MR.strings.please_check_correct_link_and_maybe_ask_for_a_new_one) ) - return null + } + r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorChat + && r.chatError.errorType is ChatErrorType.UnsupportedConnReq -> { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.unsupported_connection_link), + generalGetString(MR.strings.link_requires_newer_app_version_please_upgrade) + ) } r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent && r.chatError.agentError is AgentErrorType.SMP @@ -1442,7 +1458,6 @@ object ChatController { generalGetString(MR.strings.connection_error_auth), generalGetString(MR.strings.connection_error_auth_desc) ) - return null } r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent && r.chatError.agentError is AgentErrorType.SMP @@ -1451,7 +1466,6 @@ object ChatController { generalGetString(MR.strings.connection_error_blocked), generalGetString(MR.strings.connection_error_blocked_desc).format(r.chatError.agentError.smpErr.blockInfo.reason.text), ) - return null } r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent && r.chatError.agentError is AgentErrorType.SMP @@ -1460,13 +1474,11 @@ object ChatController { generalGetString(MR.strings.connection_error_quota), generalGetString(MR.strings.connection_error_quota_desc) ) - return null } else -> { if (!(networkErrorAlert(r))) { apiErrorAlert("apiConnect", generalGetString(MR.strings.connection_error), r) } - return null } } } @@ -1619,11 +1631,11 @@ object ChatController { return false } - suspend fun apiCreateUserAddress(rh: Long?): String? { + suspend fun apiCreateUserAddress(rh: Long?, short: Boolean): CreatedConnLink? { val userId = kotlin.runCatching { currentUserId("apiCreateUserAddress") }.getOrElse { return null } - val r = sendCmd(rh, CC.ApiCreateMyAddress(userId)) + val r = sendCmd(rh, CC.ApiCreateMyAddress(userId, short)) return when (r) { - is CR.UserContactLinkCreated -> r.connReqContact + is CR.UserContactLinkCreated -> r.connLinkContact else -> { if (!(networkErrorAlert(r))) { apiErrorAlert("apiCreateUserAddress", generalGetString(MR.strings.error_creating_address), r) @@ -2060,9 +2072,10 @@ object ChatController { } } - suspend fun apiCreateGroupLink(rh: Long?, groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): Pair? { - return when (val r = sendCmd(rh, CC.APICreateGroupLink(groupId, memberRole))) { - is CR.GroupLinkCreated -> r.connReqContact to r.memberRole + suspend fun apiCreateGroupLink(rh: Long?, groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): Pair? { + val short = appPrefs.privacyShortLinks.get() + return when (val r = sendCmd(rh, CC.APICreateGroupLink(groupId, memberRole, short))) { + is CR.GroupLinkCreated -> r.connLinkContact to r.memberRole else -> { if (!(networkErrorAlert(r))) { apiErrorAlert("apiCreateGroupLink", generalGetString(MR.strings.error_creating_link_for_group), r) @@ -2072,9 +2085,9 @@ object ChatController { } } - suspend fun apiGroupLinkMemberRole(rh: Long?, groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): Pair? { + suspend fun apiGroupLinkMemberRole(rh: Long?, groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): Pair? { return when (val r = sendCmd(rh, CC.APIGroupLinkMemberRole(groupId, memberRole))) { - is CR.GroupLink -> r.connReqContact to r.memberRole + is CR.GroupLink -> r.connLinkContact to r.memberRole else -> { if (!(networkErrorAlert(r))) { apiErrorAlert("apiGroupLinkMemberRole", generalGetString(MR.strings.error_updating_link_for_group), r) @@ -2096,9 +2109,9 @@ object ChatController { } } - suspend fun apiGetGroupLink(rh: Long?, groupId: Long): Pair? { + suspend fun apiGetGroupLink(rh: Long?, groupId: Long): Pair? { return when (val r = sendCmd(rh, CC.APIGetGroupLink(groupId))) { - is CR.GroupLink -> r.connReqContact to r.memberRole + is CR.GroupLink -> r.connLinkContact to r.memberRole else -> { Log.e(TAG, "apiGetGroupLink bad response: ${r.responseType} ${r.details}") null @@ -3466,7 +3479,7 @@ sealed class CC { class ApiLeaveGroup(val groupId: Long): CC() class ApiListMembers(val groupId: Long): CC() class ApiUpdateGroupProfile(val groupId: Long, val groupProfile: GroupProfile): CC() - class APICreateGroupLink(val groupId: Long, val memberRole: GroupMemberRole): CC() + class APICreateGroupLink(val groupId: Long, val memberRole: GroupMemberRole, val short: Boolean): CC() class APIGroupLinkMemberRole(val groupId: Long, val memberRole: GroupMemberRole): CC() class APIDeleteGroupLink(val groupId: Long): CC() class APIGetGroupLink(val groupId: Long): CC() @@ -3505,11 +3518,11 @@ sealed class CC { class APIGetGroupMemberCode(val groupId: Long, val groupMemberId: Long): CC() class APIVerifyContact(val contactId: Long, val connectionCode: String?): CC() class APIVerifyGroupMember(val groupId: Long, val groupMemberId: Long, val connectionCode: String?): CC() - class APIAddContact(val userId: Long, val incognito: Boolean): CC() + class APIAddContact(val userId: Long, val short: Boolean, val incognito: Boolean): CC() class ApiSetConnectionIncognito(val connId: Long, val incognito: Boolean): CC() class ApiChangeConnectionUser(val connId: Long, val userId: Long): CC() - class APIConnectPlan(val userId: Long, val connReq: String): CC() - class APIConnect(val userId: Long, val incognito: Boolean, val connReq: String): CC() + class APIConnectPlan(val userId: Long, val connLink: String): CC() + class APIConnect(val userId: Long, val incognito: Boolean, val connLink: CreatedConnLink): CC() class ApiConnectContactViaAddress(val userId: Long, val incognito: Boolean, val contactId: Long): CC() class ApiDeleteChat(val type: ChatType, val id: Long, val chatDeleteMode: ChatDeleteMode): CC() class ApiClearChat(val type: ChatType, val id: Long): CC() @@ -3521,7 +3534,7 @@ sealed class CC { class ApiSetConnectionAlias(val connId: Long, val localAlias: String): CC() class ApiSetUserUIThemes(val userId: Long, val themes: ThemeModeOverrides?): CC() class ApiSetChatUIThemes(val chatId: String, val themes: ThemeModeOverrides?): CC() - class ApiCreateMyAddress(val userId: Long): CC() + class ApiCreateMyAddress(val userId: Long, val short: Boolean): CC() class ApiDeleteMyAddress(val userId: Long): CC() class ApiShowMyAddress(val userId: Long): CC() class ApiSetProfileAddress(val userId: Long, val on: Boolean): CC() @@ -3651,7 +3664,7 @@ sealed class CC { is ApiLeaveGroup -> "/_leave #$groupId" is ApiListMembers -> "/_members #$groupId" is ApiUpdateGroupProfile -> "/_group_profile #$groupId ${json.encodeToString(groupProfile)}" - is APICreateGroupLink -> "/_create link #$groupId ${memberRole.name.lowercase()}" + is APICreateGroupLink -> "/_create link #$groupId ${memberRole.name.lowercase()} short=${onOff(short)}" is APIGroupLinkMemberRole -> "/_set link role #$groupId ${memberRole.name.lowercase()}" is APIDeleteGroupLink -> "/_delete link #$groupId" is APIGetGroupLink -> "/_get link #$groupId" @@ -3690,11 +3703,11 @@ sealed class CC { is APIGetGroupMemberCode -> "/_get code #$groupId $groupMemberId" is APIVerifyContact -> "/_verify code @$contactId" + if (connectionCode != null) " $connectionCode" else "" is APIVerifyGroupMember -> "/_verify code #$groupId $groupMemberId" + if (connectionCode != null) " $connectionCode" else "" - is APIAddContact -> "/_connect $userId incognito=${onOff(incognito)}" + is APIAddContact -> "/_connect $userId short=${onOff(short)} incognito=${onOff(incognito)}" is ApiSetConnectionIncognito -> "/_set incognito :$connId ${onOff(incognito)}" is ApiChangeConnectionUser -> "/_set conn user :$connId $userId" - is APIConnectPlan -> "/_connect plan $userId $connReq" - is APIConnect -> "/_connect $userId incognito=${onOff(incognito)} $connReq" + is APIConnectPlan -> "/_connect plan $userId $connLink" + is APIConnect -> "/_connect $userId incognito=${onOff(incognito)} ${connLink.connFullLink} ${connLink.connShortLink ?: ""}" is ApiConnectContactViaAddress -> "/_connect contact $userId incognito=${onOff(incognito)} $contactId" is ApiDeleteChat -> "/_delete ${chatRef(type, id)} ${chatDeleteMode.cmdString}" is ApiClearChat -> "/_clear chat ${chatRef(type, id)}" @@ -3706,7 +3719,7 @@ sealed class CC { is ApiSetConnectionAlias -> "/_set alias :$connId ${localAlias.trim()}" is ApiSetUserUIThemes -> "/_set theme user $userId ${if (themes != null) json.encodeToString(themes) else ""}" is ApiSetChatUIThemes -> "/_set theme $chatId ${if (themes != null) json.encodeToString(themes) else ""}" - is ApiCreateMyAddress -> "/_address $userId" + is ApiCreateMyAddress -> "/_address $userId short=${onOff(short)}" is ApiDeleteMyAddress -> "/_delete_address $userId" is ApiShowMyAddress -> "/_show_address $userId" is ApiSetProfileAddress -> "/_profile_address $userId ${onOff(on)}" @@ -5799,10 +5812,10 @@ sealed class CR { @Serializable @SerialName("groupMemberCode") class GroupMemberCode(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val connectionCode: String): CR() @Serializable @SerialName("connectionVerified") class ConnectionVerified(val user: UserRef, val verified: Boolean, val expectedCode: String): CR() @Serializable @SerialName("tagsUpdated") class TagsUpdated(val user: UserRef, val userTags: List, val chatTags: List): CR() - @Serializable @SerialName("invitation") class Invitation(val user: UserRef, val connReqInvitation: String, val connection: PendingContactConnection): CR() + @Serializable @SerialName("invitation") class Invitation(val user: UserRef, val connLinkInvitation: CreatedConnLink, val connection: PendingContactConnection): CR() @Serializable @SerialName("connectionIncognitoUpdated") class ConnectionIncognitoUpdated(val user: UserRef, val toConnection: PendingContactConnection): CR() @Serializable @SerialName("connectionUserChanged") class ConnectionUserChanged(val user: UserRef, val fromConnection: PendingContactConnection, val toConnection: PendingContactConnection, val newUser: UserRef): CR() - @Serializable @SerialName("connectionPlan") class CRConnectionPlan(val user: UserRef, val connectionPlan: ConnectionPlan): CR() + @Serializable @SerialName("connectionPlan") class CRConnectionPlan(val user: UserRef, val connLink: CreatedConnLink, val connectionPlan: ConnectionPlan): CR() @Serializable @SerialName("sentConfirmation") class SentConfirmation(val user: UserRef, val connection: PendingContactConnection): CR() @Serializable @SerialName("sentInvitation") class SentInvitation(val user: UserRef, val connection: PendingContactConnection): CR() @Serializable @SerialName("sentInvitationToContact") class SentInvitationToContact(val user: UserRef, val contact: Contact, val customUserProfile: Profile?): CR() @@ -5819,7 +5832,7 @@ sealed class CR { @Serializable @SerialName("contactPrefsUpdated") class ContactPrefsUpdated(val user: UserRef, val fromContact: Contact, val toContact: Contact): CR() @Serializable @SerialName("userContactLink") class UserContactLink(val user: User, val contactLink: UserContactLinkRec): CR() @Serializable @SerialName("userContactLinkUpdated") class UserContactLinkUpdated(val user: User, val contactLink: UserContactLinkRec): CR() - @Serializable @SerialName("userContactLinkCreated") class UserContactLinkCreated(val user: User, val connReqContact: String): CR() + @Serializable @SerialName("userContactLinkCreated") class UserContactLinkCreated(val user: User, val connLinkContact: CreatedConnLink): CR() @Serializable @SerialName("userContactLinkDeleted") class UserContactLinkDeleted(val user: User): CR() @Serializable @SerialName("contactConnected") class ContactConnected(val user: UserRef, val contact: Contact, val userCustomProfile: Profile? = null): CR() @Serializable @SerialName("contactConnecting") class ContactConnecting(val user: UserRef, val contact: Contact): CR() @@ -5876,8 +5889,8 @@ sealed class CR { @Serializable @SerialName("connectedToGroupMember") class ConnectedToGroupMember(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val memberContact: Contact? = null): CR() @Serializable @SerialName("groupRemoved") class GroupRemoved(val user: UserRef, val groupInfo: GroupInfo): CR() // unused @Serializable @SerialName("groupUpdated") class GroupUpdated(val user: UserRef, val toGroup: GroupInfo): CR() - @Serializable @SerialName("groupLinkCreated") class GroupLinkCreated(val user: UserRef, val groupInfo: GroupInfo, val connReqContact: String, val memberRole: GroupMemberRole): CR() - @Serializable @SerialName("groupLink") class GroupLink(val user: UserRef, val groupInfo: GroupInfo, val connReqContact: String, val memberRole: GroupMemberRole): CR() + @Serializable @SerialName("groupLinkCreated") class GroupLinkCreated(val user: UserRef, val groupInfo: GroupInfo, val connLinkContact: CreatedConnLink, val memberRole: GroupMemberRole): CR() + @Serializable @SerialName("groupLink") class GroupLink(val user: UserRef, val groupInfo: GroupInfo, val connLinkContact: CreatedConnLink, val memberRole: GroupMemberRole): CR() @Serializable @SerialName("groupLinkDeleted") class GroupLinkDeleted(val user: UserRef, val groupInfo: GroupInfo): CR() @Serializable @SerialName("newMemberContact") class NewMemberContact(val user: UserRef, val contact: Contact, val groupInfo: GroupInfo, val member: GroupMember): CR() @Serializable @SerialName("newMemberContactSentInv") class NewMemberContactSentInv(val user: UserRef, val contact: Contact, val groupInfo: GroupInfo, val member: GroupMember): CR() @@ -6165,10 +6178,10 @@ sealed class CR { is GroupMemberCode -> withUser(user, "groupInfo: ${json.encodeToString(groupInfo)}\nmember: ${json.encodeToString(member)}\nconnectionCode: $connectionCode") is ConnectionVerified -> withUser(user, "verified: $verified\nconnectionCode: $expectedCode") is TagsUpdated -> withUser(user, "userTags: ${json.encodeToString(userTags)}\nchatTags: ${json.encodeToString(chatTags)}") - is Invitation -> withUser(user, "connReqInvitation: $connReqInvitation\nconnection: $connection") + is Invitation -> withUser(user, "connLinkInvitation: ${json.encodeToString(connLinkInvitation)}\nconnection: $connection") is ConnectionIncognitoUpdated -> withUser(user, json.encodeToString(toConnection)) is ConnectionUserChanged -> withUser(user, "fromConnection: ${json.encodeToString(fromConnection)}\ntoConnection: ${json.encodeToString(toConnection)}\nnewUser: ${json.encodeToString(newUser)}" ) - is CRConnectionPlan -> withUser(user, json.encodeToString(connectionPlan)) + is CRConnectionPlan -> withUser(user, "connLink: ${json.encodeToString(connLink)}\nconnectionPlan: ${json.encodeToString(connectionPlan)}") is SentConfirmation -> withUser(user, json.encodeToString(connection)) is SentInvitation -> withUser(user, json.encodeToString(connection)) is SentInvitationToContact -> withUser(user, json.encodeToString(contact)) @@ -6185,7 +6198,7 @@ sealed class CR { is ContactPrefsUpdated -> withUser(user, "fromContact: $fromContact\ntoContact: \n${json.encodeToString(toContact)}") is UserContactLink -> withUser(user, contactLink.responseDetails) is UserContactLinkUpdated -> withUser(user, contactLink.responseDetails) - is UserContactLinkCreated -> withUser(user, connReqContact) + is UserContactLinkCreated -> withUser(user, json.encodeToString(connLinkContact)) is UserContactLinkDeleted -> withUser(user, noDetails()) is ContactConnected -> withUser(user, json.encodeToString(contact)) is ContactConnecting -> withUser(user, json.encodeToString(contact)) @@ -6239,8 +6252,8 @@ sealed class CR { is ConnectedToGroupMember -> withUser(user, "groupInfo: $groupInfo\nmember: $member\nmemberContact: $memberContact") is GroupRemoved -> withUser(user, json.encodeToString(groupInfo)) is GroupUpdated -> withUser(user, json.encodeToString(toGroup)) - is GroupLinkCreated -> withUser(user, "groupInfo: $groupInfo\nconnReqContact: $connReqContact\nmemberRole: $memberRole") - is GroupLink -> withUser(user, "groupInfo: $groupInfo\nconnReqContact: $connReqContact\nmemberRole: $memberRole") + is GroupLinkCreated -> withUser(user, "groupInfo: $groupInfo\nconnLinkContact: $connLinkContact\nmemberRole: $memberRole") + is GroupLink -> withUser(user, "groupInfo: $groupInfo\nconnLinkContact: $connLinkContact\nmemberRole: $memberRole") is GroupLinkDeleted -> withUser(user, json.encodeToString(groupInfo)) is NewMemberContact -> withUser(user, "contact: $contact\ngroupInfo: $groupInfo\nmember: $member") is NewMemberContactSentInv -> withUser(user, "contact: $contact\ngroupInfo: $groupInfo\nmember: $member") @@ -6350,11 +6363,34 @@ sealed class ChatDeleteMode { } } +@Serializable +data class CreatedConnLink(val connFullLink: String, val connShortLink: String?) { + fun simplexChatUri(short: Boolean): String = + if (short) connShortLink ?: simplexChatLink(connFullLink) + else simplexChatLink(connFullLink) + + companion object { + val nullableStateSaver: Saver> = Saver( + save = { link -> link?.connFullLink to link?.connShortLink }, + restore = { saved -> + val connFullLink = saved.first + if (connFullLink == null) null + else CreatedConnLink(connFullLink = connFullLink, connShortLink = saved.second) + } + ) + } +} + +fun simplexChatLink(uri: String): String = + if (uri.startsWith("simplex:/")) uri.replace("simplex:/", "https://simplex.chat/") + else uri + @Serializable sealed class ConnectionPlan { @Serializable @SerialName("invitationLink") class InvitationLink(val invitationLinkPlan: InvitationLinkPlan): ConnectionPlan() @Serializable @SerialName("contactAddress") class ContactAddress(val contactAddressPlan: ContactAddressPlan): ConnectionPlan() @Serializable @SerialName("groupLink") class GroupLink(val groupLinkPlan: GroupLinkPlan): ConnectionPlan() + @Serializable @SerialName("error") class Error(val chatError: ChatError): ConnectionPlan() } @Serializable @@ -6487,8 +6523,8 @@ enum class RatchetSyncState { } @Serializable -class UserContactLinkRec(val connReqContact: String, val autoAccept: AutoAccept? = null) { - val responseDetails: String get() = "connReqContact: ${connReqContact}\nautoAccept: ${AutoAccept.cmdString(autoAccept)}" +class UserContactLinkRec(val connLinkContact: CreatedConnLink, val autoAccept: AutoAccept? = null) { + val responseDetails: String get() = "connLinkContact: ${connLinkContact}\nautoAccept: ${AutoAccept.cmdString(autoAccept)}" } @Serializable @@ -6580,6 +6616,7 @@ sealed class ChatErrorType { is ChatStoreChanged -> "chatStoreChanged" is ConnectionPlanChatError -> "connectionPlan" is InvalidConnReq -> "invalidConnReq" + is UnsupportedConnReq -> "unsupportedConnReq" is InvalidChatMessage -> "invalidChatMessage" is ContactNotReady -> "contactNotReady" is ContactNotActive -> "contactNotActive" @@ -6658,6 +6695,7 @@ sealed class ChatErrorType { @Serializable @SerialName("chatStoreChanged") object ChatStoreChanged: ChatErrorType() @Serializable @SerialName("connectionPlan") class ConnectionPlanChatError(val connectionPlan: ConnectionPlan): ChatErrorType() @Serializable @SerialName("invalidConnReq") object InvalidConnReq: ChatErrorType() + @Serializable @SerialName("unsupportedConnReq") object UnsupportedConnReq: ChatErrorType() @Serializable @SerialName("invalidChatMessage") class InvalidChatMessage(val connection: Connection, val message: String): ChatErrorType() @Serializable @SerialName("contactNotReady") class ContactNotReady(val contact: Contact): ChatErrorType() @Serializable @SerialName("contactNotActive") class ContactNotActive(val contact: Contact): ChatErrorType() @@ -6777,6 +6815,7 @@ sealed class StoreError { is ContactNotFoundByFileId -> "contactNotFoundByFileId" is NoGroupSndStatus -> "noGroupSndStatus" is LargeMsg -> "largeMsg" + is DBException -> "dBException" } @Serializable @SerialName("duplicateName") object DuplicateName: StoreError() @@ -6837,6 +6876,7 @@ sealed class StoreError { @Serializable @SerialName("contactNotFoundByFileId") class ContactNotFoundByFileId(val fileId: Long): StoreError() @Serializable @SerialName("noGroupSndStatus") class NoGroupSndStatus(val itemId: Long, val groupMemberId: Long): StoreError() @Serializable @SerialName("largeMsg") object LargeMsg: StoreError() + @Serializable @SerialName("dBException") class DBException(val message: String): StoreError() } @Serializable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index c948437ac3..2a18419746 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -263,7 +263,7 @@ fun ChatView( // The idea is to preload information before showing a modal because large groups can take time to load all members var preloadedContactInfo: Pair? = null var preloadedCode: String? = null - var preloadedLink: Pair? = null + var preloadedLink: Pair? = null if (chatInfo is ChatInfo.Direct) { preloadedContactInfo = chatModel.controller.apiContactInfo(chatRh, chatInfo.apiId) preloadedCode = chatModel.controller.apiGetContactCode(chatRh, chatInfo.apiId)?.second @@ -291,7 +291,7 @@ fun ChatView( showSearch.value = true } } else if (chatInfo is ChatInfo.Group) { - var link: Pair? by remember(chatInfo.id) { mutableStateOf(preloadedLink) } + var link: Pair? by remember(chatInfo.id) { mutableStateOf(preloadedLink) } KeyChangeEffect(chatInfo.id) { setGroupMembers(chatRh, chatInfo.groupInfo, chatModel) link = chatModel.controller.apiGetGroupLink(chatRh, chatInfo.groupInfo.groupId) @@ -632,7 +632,7 @@ fun ChatView( is ChatInfo.ContactConnection -> { val close = { chatModel.chatId.value = null } ModalView(close, showClose = appPlatform.isAndroid, content = { - ContactConnectionInfoView(chatModel, chatRh, chatInfo.contactConnection.connReqInv, chatInfo.contactConnection, false, close) + ContactConnectionInfoView(chatModel, chatRh, chatInfo.contactConnection.connLinkInv, chatInfo.contactConnection, false, close) }) LaunchedEffect(chatInfo.id) { onComposed(chatInfo.id) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index b80e46eeb6..1551c49b47 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -53,12 +53,12 @@ val MEMBER_ROW_VERTICAL_PADDING = 8.dp fun ModalData.GroupChatInfoView( rhId: Long?, chatId: String, - groupLink: String?, + groupLink: CreatedConnLink?, groupLinkMemberRole: GroupMemberRole?, selectedItems: MutableState?>, appBar: MutableState<@Composable (BoxScope.() -> Unit)?>, scrollToItemId: MutableState, - onGroupLinkUpdated: (Pair?) -> Unit, + onGroupLinkUpdated: (Pair?) -> Unit, close: () -> Unit, onSearchClicked: () -> Unit ) { @@ -328,7 +328,7 @@ fun ModalData.GroupChatInfoLayout( activeSortedMembers: List, developerTools: Boolean, onLocalAliasChanged: (String) -> Unit, - groupLink: String?, + groupLink: CreatedConnLink?, selectedItems: MutableState?>, appBar: MutableState<@Composable (BoxScope.() -> Unit)?>, scrollToItemId: MutableState, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt index 987a80e7c0..6e1b9a731d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt @@ -1,6 +1,7 @@ package chat.simplex.common.views.chat.group import SectionBottomSpacer +import SectionViewWithButton import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -15,11 +16,11 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* -import chat.simplex.common.platform.ColumnWithScrollBar -import chat.simplex.common.platform.shareText +import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.newchat.* +import chat.simplex.common.views.usersettings.SettingsActionItem import chat.simplex.res.MR @Composable @@ -27,13 +28,13 @@ fun GroupLinkView( chatModel: ChatModel, rhId: Long?, groupInfo: GroupInfo, - connReqContact: String?, + connLinkContact: CreatedConnLink?, memberRole: GroupMemberRole?, - onGroupLinkUpdated: ((Pair?) -> Unit)?, + onGroupLinkUpdated: ((Pair?) -> Unit)?, creatingGroup: Boolean = false, close: (() -> Unit)? = null ) { - var groupLink by rememberSaveable { mutableStateOf(connReqContact) } + var groupLink by rememberSaveable(stateSaver = CreatedConnLink.nullableStateSaver) { mutableStateOf(connLinkContact) } val groupLinkMemberRole = rememberSaveable { mutableStateOf(memberRole) } var creatingLink by rememberSaveable { mutableStateOf(false) } fun createLink() { @@ -99,7 +100,7 @@ fun GroupLinkView( @Composable fun GroupLinkLayout( - groupLink: String?, + groupLink: CreatedConnLink?, groupInfo: GroupInfo, groupLinkMemberRole: MutableState, creatingLink: Boolean, @@ -150,7 +151,15 @@ fun GroupLinkLayout( } initialLaunch = false } - SimpleXLinkQRCode(groupLink) + val showShortLink = remember { mutableStateOf(true) } + Spacer(Modifier.height(DEFAULT_PADDING_HALF)) + if (groupLink.connShortLink == null) { + SimpleXCreatedLinkQRCode(groupLink, short = false) + } else { + SectionViewWithButton(titleButton = { ToggleShortLinkButton(showShortLink) }) { + SimpleXCreatedLinkQRCode(groupLink, short = showShortLink.value) + } + } Row( horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.CenterVertically, @@ -160,7 +169,7 @@ fun GroupLinkLayout( SimpleButton( stringResource(MR.strings.share_link), icon = painterResource(MR.images.ic_share), - click = { clipboard.shareText(simplexChatLink(groupLink)) } + click = { clipboard.shareText(groupLink.simplexChatUri(short = showShortLink.value)) } ) if (creatingGroup && close != null) { ContinueButton(close) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt index fb2349d2b8..e8350e606a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt @@ -547,7 +547,7 @@ fun ContactConnectionMenuItems(rhId: Long?, chatInfo: ChatInfo.ContactConnection ModalManager.center.closeModals() ModalManager.end.closeModals() ModalManager.center.showModalCloseable(true, showClose = appPlatform.isAndroid) { close -> - ContactConnectionInfoView(chatModel, rhId, chatInfo.contactConnection.connReqInv, chatInfo.contactConnection, true, close) + ContactConnectionInfoView(chatModel, rhId, chatInfo.contactConnection.connLinkInv, chatInfo.contactConnection, true, close) } showMenu.value = false }, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt index 37bf5b10b1..0d188bb73c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt @@ -53,6 +53,24 @@ fun SectionView( } } +@Composable +fun SectionViewWithButton(title: String? = null, titleButton: (@Composable () -> Unit)?, contentPadding: PaddingValues = PaddingValues(), headerBottomPadding: Dp = DEFAULT_PADDING, content: (@Composable ColumnScope.() -> Unit)) { + Column { + if (title != null || titleButton != null) { + Row(modifier = Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = headerBottomPadding).fillMaxWidth()) { + if (title != null) { + Text(title, color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2, fontSize = 12.sp) + } + if (titleButton != null) { + Spacer(modifier = Modifier.weight(1f)) + titleButton() + } + } + } + Column(Modifier.padding(contentPadding).fillMaxWidth()) { content() } + } +} + @Composable fun SectionViewSelectable( title: String?, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt index 8205299583..3d913cf957 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt @@ -57,7 +57,7 @@ fun AddGroupView(chatModel: ChatModel, rh: RemoteHostInfo?, close: () -> Unit, c } } else { ModalManager.end.showModalCloseable(true) { close -> - GroupLinkView(chatModel, rhId, groupInfo, connReqContact = null, memberRole = null, onGroupLinkUpdated = null, creatingGroup = true, close) + GroupLinkView(chatModel, rhId, groupInfo, connLinkContact = null, memberRole = null, onGroupLinkUpdated = null, creatingGroup = true, close) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt index 6af7ec8134..330c80b7a2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt @@ -19,7 +19,7 @@ enum class ConnectionLinkType { suspend fun planAndConnect( rhId: Long?, - uri: String, + shortOrFullLink: String, incognito: Boolean?, close: (() -> Unit)?, cleanup: (() -> Unit)? = null, @@ -27,18 +27,19 @@ suspend fun planAndConnect( filterKnownGroup: ((GroupInfo) -> Unit)? = null, ): CompletableDeferred { val completable = CompletableDeferred() - val close: (() -> Unit)? = { + val close: (() -> Unit) = { close?.invoke() // if close was called, it means the connection was created completable.complete(true) } - val cleanup: (() -> Unit)? = { + val cleanup: (() -> Unit) = { cleanup?.invoke() completable.complete(!completable.isActive) } - val connectionPlan = chatModel.controller.apiConnectPlan(rhId, uri) - if (connectionPlan != null) { - val link = strHasSingleSimplexLink(uri.trim()) + val result = chatModel.controller.apiConnectPlan(rhId, shortOrFullLink) + if (result != null) { + val (connectionLink, connectionPlan) = result + val link = strHasSingleSimplexLink(shortOrFullLink.trim()) val linkText = if (link?.format is Format.SimplexLink) "

    ${link.simplexLinkText(link.format.linkType, link.format.smpHosts)}" else @@ -48,10 +49,10 @@ suspend fun planAndConnect( InvitationLinkPlan.Ok -> { Log.d(TAG, "planAndConnect, .InvitationLink, .Ok, incognito=$incognito") if (incognito != null) { - connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) + connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) } else { askCurrentOrIncognitoProfileAlert( - chatModel, rhId, uri, connectionPlan, close, + chatModel, rhId, connectionLink, connectionPlan, close, title = generalGetString(MR.strings.connect_via_invitation_link), text = generalGetString(MR.strings.profile_will_be_sent_to_contact_sending_link) + linkText, connectDestructive = false, @@ -66,7 +67,7 @@ suspend fun planAndConnect( title = generalGetString(MR.strings.connect_plan_connect_to_yourself), text = generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link) + linkText, confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb), - onConfirm = { withBGApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } }, + onConfirm = { withBGApi { connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) } }, onDismiss = cleanup, onDismissRequest = cleanup, destructive = true, @@ -74,7 +75,7 @@ suspend fun planAndConnect( ) } else { askCurrentOrIncognitoProfileAlert( - chatModel, rhId, uri, connectionPlan, close, + chatModel, rhId, connectionLink, connectionPlan, close, title = generalGetString(MR.strings.connect_plan_connect_to_yourself), text = generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link) + linkText, connectDestructive = true, @@ -95,7 +96,7 @@ suspend fun planAndConnect( String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName) + linkText, hostDevice = hostDevice(rhId), ) - cleanup?.invoke() + cleanup() } } else { AlertManager.privacySensitive.showAlertMsg( @@ -103,7 +104,7 @@ suspend fun planAndConnect( generalGetString(MR.strings.connect_plan_you_are_already_connecting_via_this_one_time_link) + linkText, hostDevice = hostDevice(rhId), ) - cleanup?.invoke() + cleanup() } } is InvitationLinkPlan.Known -> { @@ -118,7 +119,7 @@ suspend fun planAndConnect( String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName) + linkText, hostDevice = hostDevice(rhId), ) - cleanup?.invoke() + cleanup() } } } @@ -126,10 +127,10 @@ suspend fun planAndConnect( ContactAddressPlan.Ok -> { Log.d(TAG, "planAndConnect, .ContactAddress, .Ok, incognito=$incognito") if (incognito != null) { - connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) + connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) } else { askCurrentOrIncognitoProfileAlert( - chatModel, rhId, uri, connectionPlan, close, + chatModel, rhId, connectionLink, connectionPlan, close, title = generalGetString(MR.strings.connect_via_contact_link), text = generalGetString(MR.strings.profile_will_be_sent_to_contact_sending_link) + linkText, connectDestructive = false, @@ -144,7 +145,7 @@ suspend fun planAndConnect( title = generalGetString(MR.strings.connect_plan_connect_to_yourself), text = generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address) + linkText, confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb), - onConfirm = { withBGApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } }, + onConfirm = { withBGApi { connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) } }, destructive = true, onDismiss = cleanup, onDismissRequest = cleanup, @@ -152,7 +153,7 @@ suspend fun planAndConnect( ) } else { askCurrentOrIncognitoProfileAlert( - chatModel, rhId, uri, connectionPlan, close, + chatModel, rhId, connectionLink, connectionPlan, close, title = generalGetString(MR.strings.connect_plan_connect_to_yourself), text = generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address) + linkText, connectDestructive = true, @@ -167,7 +168,7 @@ suspend fun planAndConnect( title = generalGetString(MR.strings.connect_plan_repeat_connection_request), text = generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address) + linkText, confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb), - onConfirm = { withBGApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } }, + onConfirm = { withBGApi { connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) } }, onDismiss = cleanup, onDismissRequest = cleanup, destructive = true, @@ -175,7 +176,7 @@ suspend fun planAndConnect( ) } else { askCurrentOrIncognitoProfileAlert( - chatModel, rhId, uri, connectionPlan, close, + chatModel, rhId, connectionLink, connectionPlan, close, title = generalGetString(MR.strings.connect_plan_repeat_connection_request), text = generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address) + linkText, connectDestructive = true, @@ -195,7 +196,7 @@ suspend fun planAndConnect( String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName) + linkText, hostDevice = hostDevice(rhId), ) - cleanup?.invoke() + cleanup() } } is ContactAddressPlan.Known -> { @@ -210,19 +211,19 @@ suspend fun planAndConnect( String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName) + linkText, hostDevice = hostDevice(rhId), ) - cleanup?.invoke() + cleanup() } } is ContactAddressPlan.ContactViaAddress -> { Log.d(TAG, "planAndConnect, .ContactAddress, .ContactViaAddress, incognito=$incognito") val contact = connectionPlan.contactAddressPlan.contact if (incognito != null) { - close?.invoke() + close() connectContactViaAddress(chatModel, rhId, contact.contactId, incognito) } else { askCurrentOrIncognitoProfileConnectContactViaAddress(chatModel, rhId, contact, close, openChat = false) } - cleanup?.invoke() + cleanup() } } is ConnectionPlan.GroupLink -> when (connectionPlan.groupLinkPlan) { @@ -233,14 +234,14 @@ suspend fun planAndConnect( title = generalGetString(MR.strings.connect_via_group_link), text = generalGetString(MR.strings.you_will_join_group) + linkText, confirmText = if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button), - onConfirm = { withBGApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } }, + onConfirm = { withBGApi { connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) } }, onDismiss = cleanup, onDismissRequest = cleanup, hostDevice = hostDevice(rhId), ) } else { askCurrentOrIncognitoProfileAlert( - chatModel, rhId, uri, connectionPlan, close, + chatModel, rhId, connectionLink, connectionPlan, close, title = generalGetString(MR.strings.connect_via_group_link), text = generalGetString(MR.strings.you_will_join_group) + linkText, connectDestructive = false, @@ -254,7 +255,7 @@ suspend fun planAndConnect( if (filterKnownGroup != null) { filterKnownGroup(groupInfo) } else { - ownGroupLinkConfirmConnect(chatModel, rhId, uri, linkText, incognito, connectionPlan, groupInfo, close, cleanup) + ownGroupLinkConfirmConnect(chatModel, rhId, connectionLink, linkText, incognito, connectionPlan, groupInfo, close, cleanup) } } GroupLinkPlan.ConnectingConfirmReconnect -> { @@ -264,7 +265,7 @@ suspend fun planAndConnect( title = generalGetString(MR.strings.connect_plan_repeat_join_request), text = generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link) + linkText, confirmText = if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button), - onConfirm = { withBGApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } }, + onConfirm = { withBGApi { connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) } }, onDismiss = cleanup, onDismissRequest = cleanup, destructive = true, @@ -272,7 +273,7 @@ suspend fun planAndConnect( ) } else { askCurrentOrIncognitoProfileAlert( - chatModel, rhId, uri, connectionPlan, close, + chatModel, rhId, connectionLink, connectionPlan, close, title = generalGetString(MR.strings.connect_plan_repeat_join_request), text = generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link) + linkText, connectDestructive = true, @@ -302,7 +303,7 @@ suspend fun planAndConnect( hostDevice = hostDevice(rhId), ) } - cleanup?.invoke() + cleanup() } is GroupLinkPlan.Known -> { Log.d(TAG, "planAndConnect, .GroupLink, .Known, incognito=$incognito") @@ -324,22 +325,23 @@ suspend fun planAndConnect( hostDevice = hostDevice(rhId), ) } - cleanup?.invoke() + cleanup() } } } - } - } else { - Log.d(TAG, "planAndConnect, plan error") - if (incognito != null) { - connectViaUri(chatModel, rhId, uri, incognito, connectionPlan = null, close, cleanup) - } else { - askCurrentOrIncognitoProfileAlert( - chatModel, rhId, uri, connectionPlan = null, close, - title = generalGetString(MR.strings.connect_plan_connect_via_link), - connectDestructive = false, - cleanup = cleanup, - ) + is ConnectionPlan.Error -> { + Log.d(TAG, "planAndConnect, error ${connectionPlan.chatError}") + if (incognito != null) { + connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan = null, close, cleanup) + } else { + askCurrentOrIncognitoProfileAlert( + chatModel, rhId, connectionLink, connectionPlan = null, close, + title = generalGetString(MR.strings.connect_plan_connect_via_link), + connectDestructive = false, + cleanup = cleanup, + ) + } + } } } return completable @@ -348,14 +350,14 @@ suspend fun planAndConnect( suspend fun connectViaUri( chatModel: ChatModel, rhId: Long?, - uri: String, + connLink: CreatedConnLink, incognito: Boolean, connectionPlan: ConnectionPlan?, close: (() -> Unit)?, cleanup: (() -> Unit)?, ): Boolean { - val pcc = chatModel.controller.apiConnect(rhId, incognito, uri) - val connLinkType = if (connectionPlan != null) planToConnectionLinkType(connectionPlan) else ConnectionLinkType.INVITATION + val pcc = chatModel.controller.apiConnect(rhId, incognito, connLink) + val connLinkType = if (connectionPlan != null) planToConnectionLinkType(connectionPlan) ?: ConnectionLinkType.INVITATION else ConnectionLinkType.INVITATION if (pcc != null) { withContext(Dispatchers.Main) { chatModel.chatsContext.updateContactConnection(rhId, pcc) @@ -376,18 +378,19 @@ suspend fun connectViaUri( return pcc != null } -fun planToConnectionLinkType(connectionPlan: ConnectionPlan): ConnectionLinkType { +fun planToConnectionLinkType(connectionPlan: ConnectionPlan): ConnectionLinkType? { return when(connectionPlan) { is ConnectionPlan.InvitationLink -> ConnectionLinkType.INVITATION is ConnectionPlan.ContactAddress -> ConnectionLinkType.CONTACT is ConnectionPlan.GroupLink -> ConnectionLinkType.GROUP + is ConnectionPlan.Error -> null } } fun askCurrentOrIncognitoProfileAlert( chatModel: ChatModel, rhId: Long?, - uri: String, + connectionLink: CreatedConnLink, connectionPlan: ConnectionPlan?, close: (() -> Unit)?, title: String, @@ -404,7 +407,7 @@ fun askCurrentOrIncognitoProfileAlert( SectionItemView({ AlertManager.privacySensitive.hideAlert() withBGApi { - connectViaUri(chatModel, rhId, uri, incognito = false, connectionPlan, close, cleanup) + connectViaUri(chatModel, rhId, connectionLink, incognito = false, connectionPlan, close, cleanup) } }) { Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = connectColor) @@ -412,7 +415,7 @@ fun askCurrentOrIncognitoProfileAlert( SectionItemView({ AlertManager.privacySensitive.hideAlert() withBGApi { - connectViaUri(chatModel, rhId, uri, incognito = true, connectionPlan, close, cleanup) + connectViaUri(chatModel, rhId, connectionLink, incognito = true, connectionPlan, close, cleanup) } }) { Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = connectColor) @@ -443,7 +446,7 @@ fun openKnownContact(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, co fun ownGroupLinkConfirmConnect( chatModel: ChatModel, rhId: Long?, - uri: String, + connectionLink: CreatedConnLink, linkText: String, incognito: Boolean?, connectionPlan: ConnectionPlan?, @@ -469,7 +472,7 @@ fun ownGroupLinkConfirmConnect( SectionItemView({ AlertManager.privacySensitive.hideAlert() withBGApi { - connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) + connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) } }) { Text( @@ -482,7 +485,7 @@ fun ownGroupLinkConfirmConnect( SectionItemView({ AlertManager.privacySensitive.hideAlert() withBGApi { - connectViaUri(chatModel, rhId, uri, incognito = false, connectionPlan, close, cleanup) + connectViaUri(chatModel, rhId, connectionLink, incognito = false, connectionPlan, close, cleanup) } }) { Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error) @@ -491,7 +494,7 @@ fun ownGroupLinkConfirmConnect( SectionItemView({ AlertManager.privacySensitive.hideAlert() withBGApi { - connectViaUri(chatModel, rhId, uri, incognito = true, connectionPlan, close, cleanup) + connectViaUri(chatModel, rhId, connectionLink, incognito = true, connectionPlan, close, cleanup) } }) { Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt index 1328523033..0f299b5187 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt @@ -4,6 +4,7 @@ import SectionBottomSpacer import SectionDividerSpaced import SectionTextFooter import SectionView +import SectionViewWithButton import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -31,14 +32,14 @@ import kotlinx.coroutines.* fun ContactConnectionInfoView( chatModel: ChatModel, rhId: Long?, - connReqInvitation: String?, + connLinkInvitation: CreatedConnLink?, contactConnection: PendingContactConnection, focusAlias: Boolean, close: () -> Unit ) { - LaunchedEffect(connReqInvitation) { - if (connReqInvitation != null) { - chatModel.showingInvitation.value = ShowingInvitation(contactConnection.id, connReqInvitation, false, conn = contactConnection) + LaunchedEffect(connLinkInvitation) { + if (connLinkInvitation != null) { + chatModel.showingInvitation.value = ShowingInvitation(contactConnection.id, connLinkInvitation, false, conn = contactConnection) } } /** When [AddContactLearnMore] is open, we don't need to drop [ChatModel.showingInvitation]. @@ -53,16 +54,16 @@ fun ContactConnectionInfoView( } } } - val clipboard = LocalClipboardManager.current + val showShortLink = remember { mutableStateOf(true) } ContactConnectionInfoLayout( chatModel = chatModel, - connReq = connReqInvitation, + connLink = connLinkInvitation, + showShortLink = showShortLink, contactConnection = contactConnection, focusAlias = focusAlias, rhId = rhId, deleteConnection = { deleteContactConnectionAlert(rhId, contactConnection, chatModel, close) }, onLocalAliasChanged = { setContactAlias(rhId, contactConnection, it, chatModel) }, - share = { if (connReqInvitation != null) clipboard.shareText(connReqInvitation) }, learnMore = { ModalManager.end.showModalCloseable { close -> AddContactLearnMore(close) @@ -74,13 +75,13 @@ fun ContactConnectionInfoView( @Composable private fun ContactConnectionInfoLayout( chatModel: ChatModel, - connReq: String?, + connLink: CreatedConnLink?, + showShortLink: MutableState, contactConnection: PendingContactConnection, focusAlias: Boolean, rhId: Long?, deleteConnection: () -> Unit, onLocalAliasChanged: (String) -> Unit, - share: () -> Unit, learnMore: () -> Unit, ) { @Composable fun incognitoEnabled() { @@ -126,13 +127,19 @@ private fun ContactConnectionInfoLayout( LocalAliasEditor(contactConnection.id, contactConnection.localAlias, center = false, leadingIcon = true, focus = focusAlias, updateValue = onLocalAliasChanged) } - SectionView { - if (!connReq.isNullOrEmpty() && contactConnection.initiated) { - SimpleXLinkQRCode(connReq) + if (connLink != null && connLink.connFullLink.isNotEmpty() && contactConnection.initiated) { + Spacer(Modifier.height(DEFAULT_PADDING)) + SectionViewWithButton( + stringResource(MR.strings.one_time_link).uppercase(), + titleButton = if (connLink.connShortLink == null) null else {{ ToggleShortLinkButton(showShortLink) }} + ) { + SimpleXCreatedLinkQRCode(connLink, short = showShortLink.value) incognitoEnabled() - ShareLinkButton(connReq) + ShareLinkButton(connLink.simplexChatUri(short = showShortLink.value)) OneTimeLinkLearnMoreButton(learnMore) - } else { + } + } else { + SectionView { incognitoEnabled() OneTimeLinkLearnMoreButton(learnMore) } @@ -148,14 +155,14 @@ private fun ContactConnectionInfoLayout( } @Composable -fun ShareLinkButton(connReqInvitation: String) { +fun ShareLinkButton(linkUri: String) { val clipboard = LocalClipboardManager.current SettingsActionItem( painterResource(MR.images.ic_share), stringResource(MR.strings.share_invitation_link), click = { chatModel.showingInvitation.value = chatModel.showingInvitation.value?.copy(connChatUsed = true) - clipboard.shareText(simplexChatLink(connReqInvitation)) + clipboard.shareText(simplexChatLink(linkUri)) }, iconColor = MaterialTheme.colors.primary, textColor = MaterialTheme.colors.primary, @@ -200,13 +207,13 @@ private fun PreviewContactConnectionInfoView() { SimpleXTheme { ContactConnectionInfoLayout( chatModel = ChatModel, - connReq = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", + connLink = CreatedConnLink("https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", null), + showShortLink = remember { mutableStateOf(true) }, contactConnection = PendingContactConnection.getSampleData(), focusAlias = false, rhId = null, deleteConnection = {}, onLocalAliasChanged = {}, - share = {}, learnMore = {} ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index edc54a8d2e..1b3138d21c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -4,6 +4,7 @@ import SectionBottomSpacer import SectionItemView import SectionTextFooter import SectionView +import SectionViewWithButton import TextIconSpaced import androidx.compose.foundation.* import androidx.compose.foundation.interaction.MutableInteractionSource @@ -48,17 +49,17 @@ fun ModalData.NewChatView(rh: RemoteHostInfo?, selection: NewChatOption, showQRC val selection = remember { stateGetOrPut("selection") { selection } } val showQRCodeScanner = remember { stateGetOrPut("showQRCodeScanner") { showQRCodeScanner } } val contactConnection: MutableState = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(chatModel.showingInvitation.value?.conn) } - val connReqInvitation by remember { derivedStateOf { chatModel.showingInvitation.value?.connReq ?: "" } } + val connLinkInvitation by remember { derivedStateOf { chatModel.showingInvitation.value?.connLink ?: CreatedConnLink("", null) } } val creatingConnReq = rememberSaveable { mutableStateOf(false) } val pastedLink = rememberSaveable { mutableStateOf("") } LaunchedEffect(selection.value) { if ( selection.value == NewChatOption.INVITE - && connReqInvitation.isEmpty() + && connLinkInvitation.connFullLink.isEmpty() && contactConnection.value == null && !creatingConnReq.value ) { - createInvitation(rh?.remoteHostId, creatingConnReq, connReqInvitation, contactConnection) + createInvitation(rh?.remoteHostId, creatingConnReq, connLinkInvitation, contactConnection) } } DisposableEffect(Unit) { @@ -143,12 +144,12 @@ fun ModalData.NewChatView(rh: RemoteHostInfo?, selection: NewChatOption, showQRC Modifier .fillMaxWidth() .heightIn(min = this@BoxWithConstraints.maxHeight - 150.dp), - verticalArrangement = if (index == NewChatOption.INVITE.ordinal && connReqInvitation.isEmpty()) Arrangement.Center else Arrangement.Top + verticalArrangement = if (index == NewChatOption.INVITE.ordinal && connLinkInvitation.connFullLink.isEmpty()) Arrangement.Center else Arrangement.Top ) { Spacer(Modifier.height(DEFAULT_PADDING)) when (index) { NewChatOption.INVITE.ordinal -> { - PrepareAndInviteView(rh?.remoteHostId, contactConnection, connReqInvitation, creatingConnReq) + PrepareAndInviteView(rh?.remoteHostId, contactConnection, connLinkInvitation, creatingConnReq) } NewChatOption.CONNECT.ordinal -> { ConnectView(rh?.remoteHostId, showQRCodeScanner, pastedLink, close) @@ -162,17 +163,17 @@ fun ModalData.NewChatView(rh: RemoteHostInfo?, selection: NewChatOption, showQRC } @Composable -private fun PrepareAndInviteView(rhId: Long?, contactConnection: MutableState, connReqInvitation: String, creatingConnReq: MutableState) { - if (connReqInvitation.isNotEmpty()) { +private fun PrepareAndInviteView(rhId: Long?, contactConnection: MutableState, connLinkInvitation: CreatedConnLink, creatingConnReq: MutableState) { + if (connLinkInvitation.connFullLink.isNotEmpty()) { InviteView( rhId, - connReqInvitation = connReqInvitation, + connLinkInvitation = connLinkInvitation, contactConnection = contactConnection, ) } else if (creatingConnReq.value) { CreatingLinkProgressView() } else { - RetryButton { createInvitation(rhId, creatingConnReq, connReqInvitation, contactConnection) } + RetryButton { createInvitation(rhId, creatingConnReq, connLinkInvitation, contactConnection) } } } @@ -185,7 +186,7 @@ private fun updateShownConnection(conn: PendingContactConnection) { chatModel.showingInvitation.value = chatModel.showingInvitation.value?.copy( conn = conn, connId = conn.id, - connReq = conn.connReqInv ?: "", + connLink = conn.connLinkInv ?: CreatedConnLink("", null), connChatUsed = true ) } @@ -449,15 +450,21 @@ fun ActiveProfilePicker( } @Composable -private fun InviteView(rhId: Long?, connReqInvitation: String, contactConnection: MutableState) { - SectionView(stringResource(MR.strings.share_this_1_time_link).uppercase(), headerBottomPadding = 5.dp) { - LinkTextView(connReqInvitation, true) - } - +private fun InviteView(rhId: Long?, connLinkInvitation: CreatedConnLink, contactConnection: MutableState) { + val showShortLink = remember { mutableStateOf(true) } Spacer(Modifier.height(10.dp)) - SectionView(stringResource(MR.strings.or_show_this_qr_code).uppercase(), headerBottomPadding = 5.dp) { - SimpleXLinkQRCode(connReqInvitation, onShare = { chatModel.markShowingInvitationUsed() }) + SectionView(stringResource(MR.strings.share_this_1_time_link).uppercase(), headerBottomPadding = 5.dp) { + LinkTextView(connLinkInvitation.simplexChatUri(short = showShortLink.value), true) + } + + Spacer(Modifier.height(DEFAULT_PADDING)) + + SectionViewWithButton( + stringResource(MR.strings.or_show_this_qr_code).uppercase(), + titleButton = if (connLinkInvitation.connShortLink != null) {{ ToggleShortLinkButton(showShortLink) }} else null + ) { + SimpleXCreatedLinkQRCode(connLinkInvitation, short = showShortLink.value, onShare = { chatModel.markShowingInvitationUsed() }) } Spacer(Modifier.height(DEFAULT_PADDING)) @@ -528,6 +535,18 @@ private fun InviteView(rhId: Long?, connReqInvitation: String, contactConnection } } +@Composable +fun ToggleShortLinkButton(short: MutableState) { + Text( + stringResource(if (short.value) MR.strings.full_link_button_text else MR.strings.short_link_button_text), + modifier = Modifier.clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) { short.value = !short.value }, + style = MaterialTheme.typography.body2, fontSize = 14.sp, color = MaterialTheme.colors.primary + ) +} + @Composable fun AddContactLearnMoreButton() { IconButton( @@ -675,17 +694,17 @@ private suspend fun connect(rhId: Long?, link: String, close: () -> Unit, cleanu private fun createInvitation( rhId: Long?, creatingConnReq: MutableState, - connReqInvitation: String, + connLinkInvitation: CreatedConnLink, contactConnection: MutableState ) { - if (connReqInvitation.isNotEmpty() || contactConnection.value != null || creatingConnReq.value) return + if (connLinkInvitation.connFullLink.isNotEmpty() || contactConnection.value != null || creatingConnReq.value) return creatingConnReq.value = true withBGApi { val (r, alert) = controller.apiAddContact(rhId, incognito = controller.appPrefs.incognito.get()) if (r != null) { withContext(Dispatchers.Main) { chatModel.chatsContext.updateContactConnection(rhId, r.second) - chatModel.showingInvitation.value = ShowingInvitation(connId = r.second.id, connReq = simplexChatLink(r.first), connChatUsed = false, conn = r.second) + chatModel.showingInvitation.value = ShowingInvitation(connId = r.second.id, connLink = r.first, connChatUsed = false, conn = r.second) contactConnection.value = r.second } } else { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt index e38c983487..bacb5ab802 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt @@ -12,13 +12,33 @@ import androidx.compose.ui.unit.dp import dev.icerock.moko.resources.compose.stringResource import boofcv.alg.drawing.FiducialImageEngine import boofcv.alg.fiducial.qrcode.* -import chat.simplex.common.model.CryptoFile +import chat.simplex.common.model.* import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import kotlinx.coroutines.launch +@Composable +fun SimpleXCreatedLinkQRCode( + connLink: CreatedConnLink, + short: Boolean, + modifier: Modifier = Modifier, + padding: PaddingValues = PaddingValues(horizontal = DEFAULT_PADDING * 2f, vertical = DEFAULT_PADDING_HALF), + tintColor: Color = Color(0xff062d56), + withLogo: Boolean = true, + onShare: (() -> Unit)? = null, +) { + QRCode( + connLink.simplexChatUri(short), + modifier, + padding, + tintColor, + withLogo, + onShare, + ) +} + @Composable fun SimpleXLinkQRCode( connReq: String, @@ -38,14 +58,6 @@ fun SimpleXLinkQRCode( ) } -fun simplexChatLink(uri: String): String { - return if (uri.startsWith("simplex:/")) { - uri.replace("simplex:/", "https://simplex.chat/") - } else { - uri - } -} - @Composable fun QRCode( connReq: String, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt index 02446ae982..24978ecf7c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt @@ -88,6 +88,13 @@ fun PrivacySettingsView( simplexLinkMode.set(it) chatModel.simplexLinkMode.value = it }) + if (appPrefs.developerTools.get()) { + SettingsPreferenceItem( + null, + stringResource(MR.strings.privacy_short_links), + chatModel.controller.appPrefs.privacyShortLinks + ) + } } SectionDividerSpaced() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt index 7bc35bc0de..8c7c2d8416 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt @@ -5,6 +5,7 @@ import SectionDividerSpaced import SectionItemView import SectionTextFooter import SectionView +import SectionViewWithButton import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape @@ -61,7 +62,8 @@ fun UserAddressView( fun createAddress() { withBGApi { progressIndicator = true - val connReqContact = chatModel.controller.apiCreateUserAddress(user?.value?.remoteHostId) + val short = appPreferences.privacyShortLinks.get() + val connReqContact = chatModel.controller.apiCreateUserAddress(user.value?.remoteHostId, short = short) if (connReqContact != null) { chatModel.userAddress.value = UserContactLinkRec(connReqContact) @@ -102,7 +104,7 @@ fun UserAddressView( sendEmail = { userAddress -> uriHandler.sendEmail( generalGetString(MR.strings.email_invite_subject), - generalGetString(MR.strings.email_invite_body).format(simplexChatLink(userAddress.connReqContact)) + generalGetString(MR.strings.email_invite_body).format(simplexChatLink(userAddress.connLinkContact.connFullLink)) // TODO [short links] replace with short link ) }, setProfileAddress = ::setProfileAddress, @@ -198,10 +200,14 @@ private fun UserAddressLayout( } else { val autoAcceptState = remember { mutableStateOf(AutoAcceptState(userAddress)) } val autoAcceptStateSaved = remember { mutableStateOf(autoAcceptState.value) } + val showShortLink = remember { mutableStateOf(true) } - SectionView(stringResource(MR.strings.for_social_media).uppercase()) { - SimpleXLinkQRCode(userAddress.connReqContact) - ShareAddressButton { share(simplexChatLink(userAddress.connReqContact)) } + SectionViewWithButton( + stringResource(MR.strings.for_social_media).uppercase(), + titleButton = if (userAddress.connLinkContact.connShortLink != null) {{ ToggleShortLinkButton(showShortLink) }} else null + ) { + SimpleXCreatedLinkQRCode(userAddress.connLinkContact, short = showShortLink.value) + ShareAddressButton { share(userAddress.connLinkContact.simplexChatUri(short = showShortLink.value)) } // ShareViaEmailButton { sendEmail(userAddress) } BusinessAddressToggle(autoAcceptState) { saveAas(autoAcceptState.value, autoAcceptStateSaved) } AddressSettingsButton(user, userAddress, shareViaProfile, setProfileAddress, saveAas) @@ -584,7 +590,7 @@ fun PreviewUserAddressLayoutAddressCreated() { SimpleXTheme { UserAddressLayout( user = User.sampleData, - userAddress = UserContactLinkRec("https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D"), + userAddress = UserContactLinkRec(CreatedConnLink("https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", null)), createAddress = {}, share = { _ -> }, deleteAddress = {}, diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index d905ab71ea..90a176658d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -91,12 +91,14 @@ SimpleX contact address SimpleX one-time invitation SimpleX group link + SimpleX channel link via %1$s SimpleX links Description Full link Via browser Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red. + Use short links (BETA) Spam @@ -168,6 +170,8 @@ You are already connected to %1$s. Invalid connection link Please check that you used the correct link or ask your contact to send you another one. + Unsupported connection link + This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. Connection error (AUTH) 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. Connection blocked @@ -795,6 +799,8 @@ 1-time link SimpleX address Or show this code + Full link + Short link Share profile Select chat profile Error switching profile diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs index bf998840f6..c5c41e39be 100644 --- a/apps/simplex-directory-service/src/Directory/Service.hs +++ b/apps/simplex-directory-service/src/Directory/Service.hs @@ -61,8 +61,9 @@ import Simplex.Chat.Terminal.Main (simplexChatCLI') import Simplex.Chat.Types import Simplex.Chat.Types.Shared import Simplex.Chat.View (serializeChatResponse, simplexChatContact, viewContactName, viewGroupName) +import Simplex.Messaging.Agent.Protocol (AConnectionLink (..), ConnectionLink (..), CreatedConnLink (..)) import Simplex.Messaging.Agent.Store.Common (withTransaction) -import Simplex.Messaging.Agent.Protocol (AConnectionRequestUri (..), SConnectionMode (..), sameConnReqContact) +import Simplex.Messaging.Agent.Protocol (SConnectionMode (..), sameConnReqContact, sameShortLinkContact) import qualified Simplex.Messaging.Agent.Store.DB as DB import Simplex.Messaging.Encoding.String import Simplex.Messaging.TMap (TMap) @@ -347,15 +348,15 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName setGroupRegOwner st gr owner let GroupInfo {groupId, groupProfile = GroupProfile {displayName}} = g notifyOwner gr $ "Joined the group " <> displayName <> ", creating the link…" - sendChatCmd cc (APICreateGroupLink groupId GRMember) >>= \case - CRGroupLinkCreated {connReqContact} -> do + sendChatCmd cc (APICreateGroupLink groupId GRMember False) >>= \case + CRGroupLinkCreated {connLinkContact = CCLink gLink _} -> do setGroupStatus st gr GRSPendingUpdate notifyOwner gr "Created the public link to join the group via this directory service that is always online.\n\n\ \Please add it to the group welcome message.\n\ \For example, add:" - notifyOwner gr $ "Link to join the group " <> displayName <> ": " <> strEncodeTxt (simplexChatContact connReqContact) + notifyOwner gr $ "Link to join the group " <> displayName <> ": " <> strEncodeTxt (simplexChatContact gLink) CRChatCmdError _ (ChatError e) -> case e of CEGroupUserRole {} -> notifyOwner gr "Failed creating group link, as service is no longer an admin." CEGroupMemberUserRemoved -> notifyOwner gr "Failed creating group link, as service is removed from the group." @@ -445,13 +446,15 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName groupProfileUpdate = profileUpdate <$> sendChatCmd cc (APIGetGroupLink groupId) where profileUpdate = \case - CRGroupLink {connReqContact} -> + CRGroupLink {connLinkContact = CCLink cr sl_} -> let hadLinkBefore = profileHasGroupLink fromGroup hasLinkNow = profileHasGroupLink toGroup profileHasGroupLink GroupInfo {groupProfile = gp} = maybe False (any ftHasLink) $ parseMaybeMarkdownList =<< description gp ftHasLink = \case - FormattedText (Just SimplexLink {simplexUri = ACR SCMContact cr'}) _ -> sameConnReqContact connReqContact cr' + FormattedText (Just SimplexLink {simplexUri = ACL SCMContact cLink}) _ -> case cLink of + CLFull cr' -> sameConnReqContact cr' cr + CLShort sl' -> maybe False (sameShortLinkContact sl') sl_ _ -> False in if | hadLinkBefore && hasLinkNow -> GPHasServiceLink @@ -713,7 +716,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName case mRole_ of Nothing -> getGroupLinkRole cc user g >>= \case - Just (_, gLink, mRole) -> do + Just (_, CCLink gLink _, mRole) -> do let anotherRole = case mRole of GRObserver -> GRMember; _ -> GRObserver sendReply $ initialRole n mRole @@ -893,10 +896,10 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName let groupRef = groupReference' groupId gName withGroupAndReg sendReply groupId gName $ \_ _ -> sendChatCmd cc (APIGetGroupLink groupId) >>= \case - CRGroupLink {connReqContact, memberRole} -> + CRGroupLink {connLinkContact = CCLink cReq _, memberRole} -> sendReply $ T.unlines [ "The link to join the group " <> groupRef <> ":", - strEncodeTxt $ simplexChatContact connReqContact, + strEncodeTxt $ simplexChatContact cReq, "New member role: " <> strEncodeTxt memberRole ] CRChatCmdError _ (ChatErrorStore (SEGroupLinkNotFound _)) -> @@ -1039,7 +1042,7 @@ vr :: ChatController -> VersionRangeChat vr ChatController {config = ChatConfig {chatVRange}} = chatVRange {-# INLINE vr #-} -getGroupLinkRole :: ChatController -> User -> GroupInfo -> IO (Maybe (Int64, ConnReqContact, GroupMemberRole)) +getGroupLinkRole :: ChatController -> User -> GroupInfo -> IO (Maybe (Int64, CreatedLinkContact, GroupMemberRole)) getGroupLinkRole cc user gInfo = withDB "getGroupLink" cc $ \db -> getGroupLink db user gInfo @@ -1047,7 +1050,7 @@ setGroupLinkRole :: ChatController -> GroupInfo -> GroupMemberRole -> IO (Maybe setGroupLinkRole cc GroupInfo {groupId} mRole = resp <$> sendChatCmd cc (APIGroupLinkMemberRole groupId mRole) where resp = \case - CRGroupLink _ _ gLink _ -> Just gLink + CRGroupLink _ _ (CCLink gLink _) _ -> Just gLink _ -> Nothing unexpectedError :: Text -> Text diff --git a/cabal.project b/cabal.project index 14cccb4f1f..1bf1289900 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 9abc0fa88dd70a7e30a041697335bb663c1140b7 + tag: 305f79d2a66a8d122bf457e023988200bb7fe00c source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 326306cf85..e2df71ae0f 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."9abc0fa88dd70a7e30a041697335bb663c1140b7" = "0gaqqvhb5s9xw5mq2iy8swp7w34zrkwkbjlyhggz2q9nr5680z84"; + "https://github.com/simplex-chat/simplexmq.git"."305f79d2a66a8d122bf457e023988200bb7fe00c" = "1lawc5pf4hgc6wym2xz8gi92izi1vk98ppv3ldrpajz1mq62ifpc"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index ae7294a39d..3eb75d5cf6 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -55,6 +55,7 @@ library Simplex.Chat.Mobile.WebRTC Simplex.Chat.Operators Simplex.Chat.Operators.Conditions + Simplex.Chat.Operators.Presets Simplex.Chat.Options Simplex.Chat.Options.DB Simplex.Chat.ProfileGenerator @@ -102,6 +103,7 @@ library Simplex.Chat.Options.Postgres Simplex.Chat.Store.Postgres.Migrations Simplex.Chat.Store.Postgres.Migrations.M20241220_initial + Simplex.Chat.Store.Postgres.Migrations.M20250402_short_links else exposed-modules: Simplex.Chat.Archive @@ -231,6 +233,7 @@ library Simplex.Chat.Store.SQLite.Migrations.M20250126_mentions Simplex.Chat.Store.SQLite.Migrations.M20250129_delete_unused_contacts Simplex.Chat.Store.SQLite.Migrations.M20250130_indexes + Simplex.Chat.Store.SQLite.Migrations.M20250402_short_links other-modules: Paths_simplex_chat hs-source-dirs: diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 001e2fde1b..02a765bb19 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -30,6 +30,7 @@ import Data.Time.Clock (getCurrentTime) import Simplex.Chat.Controller import Simplex.Chat.Library.Commands import Simplex.Chat.Operators +import Simplex.Chat.Operators.Presets import Simplex.Chat.Options import Simplex.Chat.Options.DB import Simplex.Chat.Protocol @@ -39,7 +40,7 @@ import Simplex.Chat.Types import Simplex.Chat.Util (shuffle) import Simplex.FileTransfer.Client.Presets (defaultXFTPServers) import Simplex.Messaging.Agent as Agent -import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), InitialAgentServers (..), ServerCfg (..), ServerRoles (..), allRoles, createAgentStore, defaultAgentConfig, presetServerCfg) +import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), InitialAgentServers (..), ServerCfg (..), allRoles, createAgentStore, defaultAgentConfig, presetServerCfg) import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Store.Common (DBStore (dbNew)) import qualified Simplex.Messaging.Agent.Store.DB as DB @@ -51,34 +52,6 @@ import qualified Simplex.Messaging.TMap as TM import qualified UnliftIO.Exception as E import UnliftIO.STM -operatorSimpleXChat :: NewServerOperator -operatorSimpleXChat = - ServerOperator - { operatorId = DBNewEntity, - operatorTag = Just OTSimplex, - tradeName = "SimpleX Chat", - legalName = Just "SimpleX Chat Ltd", - serverDomains = ["simplex.im"], - conditionsAcceptance = CARequired Nothing, - enabled = True, - smpRoles = allRoles, - xftpRoles = allRoles - } - -operatorFlux :: NewServerOperator -operatorFlux = - ServerOperator - { operatorId = DBNewEntity, - operatorTag = Just OTFlux, - tradeName = "Flux", - legalName = Just "InFlux Technologies Limited", - serverDomains = ["simplexonflux.com"], - conditionsAcceptance = CARequired Nothing, - enabled = False, - smpRoles = ServerRoles {storage = False, proxy = True}, - xftpRoles = ServerRoles {storage = False, proxy = True} - } - defaultChatConfig :: ChatConfig defaultChatConfig = ChatConfig @@ -112,6 +85,10 @@ defaultChatConfig = ntf = _defaultNtfServers, netCfg = defaultNetworkConfig }, + -- please note: if these servers are changed, this option needs to be split to two, + -- to have a different set of servers on the receiving end and on the sending end. + -- To preserve backward compatibility receiving end should update before the sending. + shortLinkPresetServers = allPresetServers, tbqSize = 1024, fileChunkSize = 15780, -- do not change xftpDescrPartSize = 14000, @@ -133,53 +110,6 @@ defaultChatConfig = chatHooks = defaultChatHooks } -simplexChatSMPServers :: [NewUserServer 'PSMP] -simplexChatSMPServers = - map - (presetServer True) - [ "smp://0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU=@smp8.simplex.im,beccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion", - "smp://SkIkI6EPd2D63F4xFKfHk7I1UGZVNn6k1QWZ5rcyr6w=@smp9.simplex.im,jssqzccmrcws6bhmn77vgmhfjmhwlyr3u7puw4erkyoosywgl67slqqd.onion", - "smp://6iIcWT_dF2zN_w5xzZEY7HI2Prbh3ldP07YTyDexPjE=@smp10.simplex.im,rb2pbttocvnbrngnwziclp2f4ckjq65kebafws6g4hy22cdaiv5dwjqd.onion", - "smp://1OwYGt-yqOfe2IyVHhxz3ohqo3aCCMjtB-8wn4X_aoY=@smp11.simplex.im,6ioorbm6i3yxmuoezrhjk6f6qgkc4syabh7m3so74xunb5nzr4pwgfqd.onion", - "smp://UkMFNAXLXeAAe0beCa4w6X_zp18PwxSaSjY17BKUGXQ=@smp12.simplex.im,ie42b5weq7zdkghocs3mgxdjeuycheeqqmksntj57rmejagmg4eor5yd.onion", - "smp://enEkec4hlR3UtKx2NMpOUK_K4ZuDxjWBO1d9Y4YXVaA=@smp14.simplex.im,aspkyu2sopsnizbyfabtsicikr2s4r3ti35jogbcekhm3fsoeyjvgrid.onion", - "smp://h--vW7ZSkXPeOUpfxlFGgauQmXNFOzGoizak7Ult7cw=@smp15.simplex.im,oauu4bgijybyhczbnxtlggo6hiubahmeutaqineuyy23aojpih3dajad.onion", - "smp://hejn2gVIqNU6xjtGM3OwQeuk8ZEbDXVJXAlnSBJBWUA=@smp16.simplex.im,p3ktngodzi6qrf7w64mmde3syuzrv57y55hxabqcq3l5p6oi7yzze6qd.onion", - "smp://ZKe4uxF4Z_aLJJOEsC-Y6hSkXgQS5-oc442JQGkyP8M=@smp17.simplex.im,ogtwfxyi3h2h5weftjjpjmxclhb5ugufa5rcyrmg7j4xlch7qsr5nuqd.onion", - "smp://PtsqghzQKU83kYTlQ1VKg996dW4Cw4x_bvpKmiv8uns=@smp18.simplex.im,lyqpnwbs2zqfr45jqkncwpywpbtq7jrhxnib5qddtr6npjyezuwd3nqd.onion", - "smp://N_McQS3F9TGoh4ER0QstUf55kGnNSd-wXfNPZ7HukcM=@smp19.simplex.im,i53bbtoqhlc365k6kxzwdp5w3cdt433s7bwh3y32rcbml2vztiyyz5id.onion" - ] - <> map - (presetServer False) - [ "smp://u2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU=@smp4.simplex.im,o5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion", - "smp://hpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg=@smp5.simplex.im,jjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion", - "smp://PQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo=@smp6.simplex.im,bylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion" - ] - -fluxSMPServers :: [NewUserServer 'PSMP] -fluxSMPServers = - map - (presetServer True) - [ "smp://xQW_ufMkGE20UrTlBl8QqceG1tbuylXhr9VOLPyRJmw=@smp1.simplexonflux.com,qb4yoanyl4p7o33yrknv4rs6qo7ugeb2tu2zo66sbebezs4cpyosarid.onion", - "smp://LDnWZVlAUInmjmdpQQoIo6FUinRXGe0q3zi5okXDE4s=@smp2.simplexonflux.com,yiqtuh3q4x7hgovkomafsod52wvfjucdljqbbipg5sdssnklgongxbqd.onion", - "smp://1jne379u7IDJSxAvXbWb_JgoE7iabcslX0LBF22Rej0=@smp3.simplexonflux.com,a5lm4k7ufei66cdck6fy63r4lmkqy3dekmmb7jkfdm5ivi6kfaojshad.onion", - "smp://xmAmqj75I9mWrUihLUlI0ZuNLXlIwFIlHRq5Pb6cHAU=@smp4.simplexonflux.com,qpcz2axyy66u26hfdd2e23uohcf3y6c36mn7dcuilcgnwjasnrvnxjqd.onion", - "smp://rWvBYyTamuRCBYb_KAn-nsejg879ndhiTg5Sq3k0xWA=@smp5.simplexonflux.com,4ao347qwiuluyd45xunmii4skjigzuuox53hpdsgbwxqafd4yrticead.onion", - "smp://PN7-uqLBToqlf1NxHEaiL35lV2vBpXq8Nj8BW11bU48=@smp6.simplexonflux.com,hury6ot3ymebbr2535mlp7gcxzrjpc6oujhtfxcfh2m4fal4xw5fq6qd.onion" - ] - -fluxXFTPServers :: [NewUserServer 'PXFTP] -fluxXFTPServers = - map - (presetServer True) - [ "xftp://92Sctlc09vHl_nAqF2min88zKyjdYJ9mgxRCJns5K2U=@xftp1.simplexonflux.com,apl3pumq3emwqtrztykyyoomdx4dg6ysql5zek2bi3rgznz7ai3odkid.onion", - "xftp://YBXy4f5zU1CEhnbbCzVWTNVNsaETcAGmYqGNxHntiE8=@xftp2.simplexonflux.com,c5jjecisncnngysah3cz2mppediutfelco4asx65mi75d44njvua3xid.onion", - "xftp://ARQO74ZSvv2OrulRF3CdgwPz_AMy27r0phtLSq5b664=@xftp3.simplexonflux.com,dc4mohiubvbnsdfqqn7xhlhpqs5u4tjzp7xpz6v6corwvzvqjtaqqiqd.onion", - "xftp://ub2jmAa9U0uQCy90O-fSUNaYCj6sdhl49Jh3VpNXP58=@xftp4.simplexonflux.com,4qq5pzier3i4yhpuhcrhfbl6j25udc4czoyascrj4yswhodhfwev3nyd.onion", - "xftp://Rh19D5e4Eez37DEE9hAlXDB3gZa1BdFYJTPgJWPO9OI=@xftp5.simplexonflux.com,q7itltdn32hjmgcqwhow4tay5ijetng3ur32bolssw32fvc5jrwvozad.onion", - "xftp://0AznwoyfX8Od9T_acp1QeeKtxUi676IBIiQjXVwbdyU=@xftp6.simplexonflux.com,upvzf23ou6nrmaf3qgnhd6cn3d74tvivlmz3p7wdfwq6fhthjrjiiqid.onion" - ] - logCfg :: LogConfig logCfg = LogConfig {lc_file = Nothing, lc_stderr = True} diff --git a/src/Simplex/Chat/Bot.hs b/src/Simplex/Chat/Bot.hs index 54e7baa194..727d7f9ac5 100644 --- a/src/Simplex/Chat/Bot.hs +++ b/src/Simplex/Chat/Bot.hs @@ -15,6 +15,7 @@ import qualified Data.ByteString.Char8 as B import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as L import qualified Data.Map.Strict as M +import Data.Maybe (isJust) import Data.Text (Text) import qualified Data.Text as T import Simplex.Chat.Controller @@ -24,6 +25,7 @@ import Simplex.Chat.Messages.CIContent import Simplex.Chat.Protocol (MsgContent (..)) import Simplex.Chat.Store import Simplex.Chat.Types (Contact (..), ContactId, IsContact (..), User (..)) +import Simplex.Messaging.Agent.Protocol (CreatedConnLink (..)) import Simplex.Messaging.Encoding.String (strEncode) import System.Exit (exitFailure) @@ -49,16 +51,18 @@ initializeBotAddress = initializeBotAddress' True initializeBotAddress' :: Bool -> ChatController -> IO () initializeBotAddress' logAddress cc = do sendChatCmd cc ShowMyAddress >>= \case - CRUserContactLink _ UserContactLink {connReqContact} -> showBotAddress connReqContact + CRUserContactLink _ UserContactLink {connLinkContact} -> showBotAddress connLinkContact CRChatCmdError _ (ChatErrorStore SEUserContactLinkNotFound) -> do when logAddress $ putStrLn "No bot address, creating..." - sendChatCmd cc CreateMyAddress >>= \case - CRUserContactLinkCreated _ uri -> showBotAddress uri + -- TODO [short links] create short link by default + sendChatCmd cc (CreateMyAddress False) >>= \case + CRUserContactLinkCreated _ ccLink -> showBotAddress ccLink _ -> putStrLn "can't create bot address" >> exitFailure _ -> putStrLn "unexpected response" >> exitFailure where - showBotAddress uri = do - when logAddress $ putStrLn $ "Bot's contact address is: " <> B.unpack (strEncode uri) + showBotAddress (CCLink uri shortUri) = do + when logAddress $ putStrLn $ "Bot's contact address is: " <> B.unpack (maybe (strEncode uri) strEncode shortUri) + when (isJust shortUri) $ putStrLn $ "Full contact address for old clients: " <> B.unpack (strEncode uri) void $ sendChatCmd cc $ AddressAutoAccept $ Just AutoAccept {businessAddress = False, acceptIncognito = False, autoReply = Nothing} sendMessage :: ChatController -> Contact -> Text -> IO () diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 49ff263f6f..21d5f1041b 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -138,6 +138,7 @@ data ChatConfig = ChatConfig chatVRange :: VersionRangeChat, confirmMigrations :: MigrationConfirmation, presetServers :: PresetServers, + shortLinkPresetServers :: NonEmpty SMPServer, tbqSize :: Natural, fileChunkSize :: Integer, xftpDescrPartSize :: Int, @@ -364,7 +365,7 @@ data ChatCommand | APILeaveGroup GroupId | APIListMembers GroupId | APIUpdateGroupProfile GroupId GroupProfile - | APICreateGroupLink GroupId GroupMemberRole + | APICreateGroupLink GroupId GroupMemberRole CreateShortLink | APIGroupLinkMemberRole GroupId GroupMemberRole | APIDeleteGroupLink GroupId | APIGetGroupLink GroupId @@ -437,21 +438,21 @@ data ChatCommand | EnableGroupMember GroupName ContactName | ChatHelp HelpSection | Welcome - | APIAddContact UserId IncognitoEnabled - | AddContact IncognitoEnabled + | APIAddContact UserId CreateShortLink IncognitoEnabled + | AddContact CreateShortLink IncognitoEnabled | APISetConnectionIncognito Int64 IncognitoEnabled | APIChangeConnectionUser Int64 UserId -- new user id to switch connection to - | APIConnectPlan UserId AConnectionRequestUri - | APIConnect UserId IncognitoEnabled (Maybe AConnectionRequestUri) - | Connect IncognitoEnabled (Maybe AConnectionRequestUri) + | APIConnectPlan UserId AConnectionLink + | APIConnect UserId IncognitoEnabled (Maybe ACreatedConnLink) + | Connect IncognitoEnabled (Maybe AConnectionLink) | APIConnectContactViaAddress UserId IncognitoEnabled ContactId | ConnectSimplex IncognitoEnabled -- UserId (not used in UI) | DeleteContact ContactName ChatDeleteMode | ClearContact ContactName | APIListContacts UserId | ListContacts - | APICreateMyAddress UserId - | CreateMyAddress + | APICreateMyAddress UserId CreateShortLink + | CreateMyAddress CreateShortLink | APIDeleteMyAddress UserId | DeleteMyAddress | APIShowMyAddress UserId @@ -492,7 +493,7 @@ data ChatCommand | ShowGroupProfile GroupName | UpdateGroupDescription GroupName (Maybe Text) | ShowGroupDescription GroupName - | CreateGroupLink GroupName GroupMemberRole + | CreateGroupLink GroupName GroupMemberRole CreateShortLink | GroupLinkMemberRole GroupName GroupMemberRole | DeleteGroupLink GroupName | ShowGroupLink GroupName @@ -674,10 +675,10 @@ data ChatResponse | CRUserProfileNoChange {user :: User} | CRUserPrivacy {user :: User, updatedUser :: User} | CRVersionInfo {versionInfo :: CoreVersionInfo, chatMigrations :: [UpMigration], agentMigrations :: [UpMigration]} - | CRInvitation {user :: User, connReqInvitation :: ConnReqInvitation, connection :: PendingContactConnection} + | CRInvitation {user :: User, connLinkInvitation :: CreatedLinkInvitation, connection :: PendingContactConnection} | CRConnectionIncognitoUpdated {user :: User, toConnection :: PendingContactConnection} | CRConnectionUserChanged {user :: User, fromConnection :: PendingContactConnection, toConnection :: PendingContactConnection, newUser :: User} - | CRConnectionPlan {user :: User, connectionPlan :: ConnectionPlan} + | CRConnectionPlan {user :: User, connLink :: ACreatedConnLink, connectionPlan :: ConnectionPlan} | CRSentConfirmation {user :: User, connection :: PendingContactConnection} | CRSentInvitation {user :: User, connection :: PendingContactConnection, customUserProfile :: Maybe Profile} | CRSentInvitationToContact {user :: User, contact :: Contact, customUserProfile :: Maybe Profile} @@ -687,7 +688,7 @@ data ChatResponse | CRContactDeleted {user :: User, contact :: Contact} | CRContactDeletedByContact {user :: User, contact :: Contact} | CRChatCleared {user :: User, chatInfo :: AChatInfo} - | CRUserContactLinkCreated {user :: User, connReqContact :: ConnReqContact} + | CRUserContactLinkCreated {user :: User, connLinkContact :: CreatedLinkContact} | CRUserContactLinkDeleted {user :: User} | CRReceivedContactRequest {user :: User, contactRequest :: UserContactRequest} | CRAcceptingContactRequest {user :: User, contact :: Contact} @@ -765,8 +766,8 @@ data ChatResponse | CRGroupUpdated {user :: User, fromGroup :: GroupInfo, toGroup :: GroupInfo, member_ :: Maybe GroupMember} | CRGroupProfile {user :: User, groupInfo :: GroupInfo} | CRGroupDescription {user :: User, groupInfo :: GroupInfo} -- only used in CLI - | CRGroupLinkCreated {user :: User, groupInfo :: GroupInfo, connReqContact :: ConnReqContact, memberRole :: GroupMemberRole} - | CRGroupLink {user :: User, groupInfo :: GroupInfo, connReqContact :: ConnReqContact, memberRole :: GroupMemberRole} + | CRGroupLinkCreated {user :: User, groupInfo :: GroupInfo, connLinkContact :: CreatedLinkContact, memberRole :: GroupMemberRole} + | CRGroupLink {user :: User, groupInfo :: GroupInfo, connLinkContact :: CreatedLinkContact, memberRole :: GroupMemberRole} | CRGroupLinkDeleted {user :: User, groupInfo :: GroupInfo} | CRAcceptingGroupJoinRequestMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember} | CRNoMemberContactCreating {user :: User, groupInfo :: GroupInfo, member :: GroupMember} -- only used in CLI @@ -941,6 +942,7 @@ data ConnectionPlan = CPInvitationLink {invitationLinkPlan :: InvitationLinkPlan} | CPContactAddress {contactAddressPlan :: ContactAddressPlan} | CPGroupLink {groupLinkPlan :: GroupLinkPlan} + | CPError {chatError :: ChatError} deriving (Show) data InvitationLinkPlan @@ -984,6 +986,7 @@ connectionPlanProceed = \case GLPOwnLink _ -> True GLPConnectingConfirmReconnect -> True _ -> False + CPError _ -> True data ForwardConfirmation = FCFilesNotAccepted {fileIds :: [FileTransferId]} @@ -1247,8 +1250,8 @@ data ChatErrorType | CEChatNotStarted | CEChatNotStopped | CEChatStoreChanged - | CEConnectionPlan {connectionPlan :: ConnectionPlan} | CEInvalidConnReq + | CEUnsupportedConnReq | CEInvalidChatMessage {connection :: Connection, msgMeta :: Maybe MsgMetaJSON, messageData :: Text, message :: String} | CEContactNotFound {contactName :: ContactName, suspectedMember :: Maybe (GroupInfo, GroupMember)} | CEContactNotReady {contact :: Contact} @@ -1583,8 +1586,6 @@ $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CAP") ''ContactAddressPlan) $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "GLP") ''GroupLinkPlan) -$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CP") ''ConnectionPlan) - $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "FC") ''ForwardConfirmation) $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CE") ''ChatErrorType) @@ -1599,6 +1600,8 @@ $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "DB") ''DatabaseError) $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "Chat") ''ChatError) +$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CP") ''ConnectionPlan) + $(JQ.deriveJSON defaultJSON ''AppFilePathsConfig) $(JQ.deriveJSON defaultJSON ''ContactSubStatus) diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 54d3cd9143..890a27f573 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -1647,16 +1647,18 @@ processChatCommand' vr = \case EnableGroupMember gName mName -> withMemberName gName mName $ \gId mId -> APIEnableGroupMember gId mId ChatHelp section -> pure $ CRChatHelp section Welcome -> withUser $ pure . CRWelcome - APIAddContact userId incognito -> withUserId userId $ \user -> procCmd $ do + APIAddContact userId short incognito -> withUserId userId $ \user -> procCmd $ do -- [incognito] generate profile for connection incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing subMode <- chatReadVar subscriptionMode - (connId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing IKPQOn subMode + let userData = shortLinkUserData short + (connId, ccLink) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation userData Nothing IKPQOn subMode + ccLink' <- shortenCreatedLink ccLink -- TODO PQ pass minVersion from the current range - conn <- withFastStore' $ \db -> createDirectConnection db user connId cReq ConnNew incognitoProfile subMode initialChatVersion PQSupportOn - pure $ CRInvitation user cReq conn - AddContact incognito -> withUser $ \User {userId} -> - processChatCommand $ APIAddContact userId incognito + conn <- withFastStore' $ \db -> createDirectConnection db user connId ccLink' ConnNew incognitoProfile subMode initialChatVersion PQSupportOn + pure $ CRInvitation user ccLink' conn + AddContact short incognito -> withUser $ \User {userId} -> + processChatCommand $ APIAddContact userId short incognito APISetConnectionIncognito connId incognito -> withUser $ \user@User {userId} -> do conn'_ <- withFastStore $ \db -> do conn@PendingContactConnection {pccConnStatus, customUserProfileId} <- getPendingContactConnection db userId connId @@ -1674,9 +1676,9 @@ processChatCommand' vr = \case Nothing -> throwChatError CEConnectionIncognitoChangeProhibited APIChangeConnectionUser connId newUserId -> withUser $ \user@User {userId} -> do conn <- withFastStore $ \db -> getPendingContactConnection db userId connId - let PendingContactConnection {pccConnStatus, connReqInv} = conn - case (pccConnStatus, connReqInv) of - (ConnNew, Just cReqInv) -> do + let PendingContactConnection {pccConnStatus, connLinkInv} = conn + case (pccConnStatus, connLinkInv) of + (ConnNew, Just (CCLink cReqInv _)) -> do newUser <- privateGetUser newUserId conn' <- ifM (canKeepLink cReqInv newUser) (updateConnRecord user conn newUser) (recreateConn user conn newUser) pure $ CRConnectionUserChanged user conn conn' newUser @@ -1697,19 +1699,21 @@ processChatCommand' vr = \case forM_ customUserProfileId $ \profileId -> deletePCCIncognitoProfile db user profileId pure conn' - recreateConn user conn@PendingContactConnection {customUserProfileId} newUser = do + recreateConn user conn@PendingContactConnection {customUserProfileId, connLinkInv} newUser = do subMode <- chatReadVar subscriptionMode - (agConnId, cReq) <- withAgent $ \a -> createConnection a (aUserId newUser) True SCMInvitation Nothing IKPQOn subMode + let userData = shortLinkUserData $ isJust $ connShortLink =<< connLinkInv + (agConnId, ccLink) <- withAgent $ \a -> createConnection a (aUserId newUser) True SCMInvitation userData Nothing IKPQOn subMode + ccLink' <- shortenCreatedLink ccLink conn' <- withFastStore' $ \db -> do deleteConnectionRecord db user connId forM_ customUserProfileId $ \profileId -> deletePCCIncognitoProfile db user profileId - createDirectConnection db newUser agConnId cReq ConnNew Nothing subMode initialChatVersion PQSupportOn + createDirectConnection db newUser agConnId ccLink' ConnNew Nothing subMode initialChatVersion PQSupportOn deleteAgentConnectionAsync user (aConnId' conn) pure conn' - APIConnectPlan userId cReqUri -> withUserId userId $ \user -> - CRConnectionPlan user <$> connectPlan user cReqUri - APIConnect userId incognito (Just (ACR SCMInvitation cReq@(CRInvitationUri crData e2e))) -> withUserId userId $ \user -> withInvitationLock "connect" (strEncode cReq) . procCmd $ do + APIConnectPlan userId cLink -> withUserId userId $ \user -> + uncurry (CRConnectionPlan user) <$> connectPlan user cLink + APIConnect userId incognito (Just (ACCL SCMInvitation (CCLink cReq@(CRInvitationUri crData e2e) sLnk_))) -> withUserId userId $ \user -> withInvitationLock "connect" (strEncode cReq) . procCmd $ do subMode <- chatReadVar subscriptionMode -- [incognito] generate profile to send incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing @@ -1732,7 +1736,8 @@ processChatCommand' vr = \case where joinNewConn chatV dm = do connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq pqSup' - pcc <- withFastStore' $ \db -> createDirectConnection db user connId cReq ConnPrepared (incognitoProfile $> profileToSend) subMode chatV pqSup' + let ccLink = CCLink cReq $ serverShortLink <$> sLnk_ + pcc <- withFastStore' $ \db -> createDirectConnection db user connId ccLink ConnPrepared (incognitoProfile $> profileToSend) subMode chatV pqSup' joinPreparedConn connId pcc dm joinPreparedConn connId pcc@PendingContactConnection {pccConnId} dm = do void $ withAgent $ \a -> joinConnection a (aUserId user) connId True cReq dm pqSup' subMode @@ -1742,43 +1747,40 @@ processChatCommand' vr = \case ( CRInvitationUri crData {crScheme = SSSimplex} e2e, CRInvitationUri crData {crScheme = simplexChat} e2e ) - APIConnect userId incognito (Just (ACR SCMContact cReq)) -> withUserId userId $ \user -> connectViaContact user incognito cReq + APIConnect userId incognito (Just (ACCL SCMContact ccLink)) -> withUserId userId $ \user -> connectViaContact user incognito ccLink APIConnect _ _ Nothing -> throwChatError CEInvalidConnReq - Connect incognito aCReqUri@(Just cReqUri) -> withUser $ \user@User {userId} -> do - plan <- connectPlan user cReqUri `catchChatError` const (pure $ CPInvitationLink ILPOk) - unless (connectionPlanProceed plan) $ throwChatError (CEConnectionPlan plan) - case plan of - CPContactAddress (CAPContactViaAddress Contact {contactId}) -> - processChatCommand $ APIConnectContactViaAddress userId incognito contactId - _ -> processChatCommand $ APIConnect userId incognito aCReqUri + Connect incognito (Just cLink@(ACL m cLink')) -> withUser $ \user -> do + (ccLink, plan) <- connectPlan user cLink `catchChatError` \e -> case cLink' of CLFull cReq -> pure (ACCL m (CCLink cReq Nothing), CPInvitationLink ILPOk); _ -> throwError e + connectWithPlan user incognito ccLink plan Connect _ Nothing -> throwChatError CEInvalidConnReq APIConnectContactViaAddress userId incognito contactId -> withUserId userId $ \user -> do ct@Contact {activeConn, profile = LocalProfile {contactLink}} <- withFastStore $ \db -> getContact db vr user contactId when (isJust activeConn) $ throwChatError (CECommandError "contact already has connection") - case contactLink of - Just cReq -> connectContactViaAddress user incognito ct cReq + ccLink <- case contactLink of + Just (CLFull cReq) -> pure $ CCLink cReq Nothing + Just (CLShort sLnk) -> do + cReq <- getShortLinkConnReq user sLnk + pure $ CCLink cReq $ Just sLnk Nothing -> throwChatError (CECommandError "no address in contact profile") - ConnectSimplex incognito -> withUser $ \user@User {userId} -> do - let cReqUri = ACR SCMContact adminContactReq - plan <- connectPlan user cReqUri `catchChatError` const (pure $ CPInvitationLink ILPOk) - unless (connectionPlanProceed plan) $ throwChatError (CEConnectionPlan plan) - case plan of - CPContactAddress (CAPContactViaAddress Contact {contactId}) -> - processChatCommand $ APIConnectContactViaAddress userId incognito contactId - _ -> processChatCommand $ APIConnect userId incognito (Just cReqUri) + connectContactViaAddress user incognito ct ccLink + ConnectSimplex incognito -> withUser $ \user -> do + plan <- contactRequestPlan user adminContactReq `catchChatError` const (pure $ CPContactAddress CAPOk) + connectWithPlan user incognito (ACCL SCMContact (CCLink adminContactReq Nothing)) plan DeleteContact cName cdm -> withContactName cName $ \ctId -> APIDeleteChat (ChatRef CTDirect ctId) cdm ClearContact cName -> withContactName cName $ APIClearChat . ChatRef CTDirect APIListContacts userId -> withUserId userId $ \user -> CRContactsList user <$> withFastStore' (\db -> getUserContacts db vr user) ListContacts -> withUser $ \User {userId} -> processChatCommand $ APIListContacts userId - APICreateMyAddress userId -> withUserId userId $ \user -> procCmd $ do + APICreateMyAddress userId short -> withUserId userId $ \user -> procCmd $ do subMode <- chatReadVar subscriptionMode - (connId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMContact Nothing IKPQOn subMode - withFastStore $ \db -> createUserContactLink db user connId cReq subMode - pure $ CRUserContactLinkCreated user cReq - CreateMyAddress -> withUser $ \User {userId} -> - processChatCommand $ APICreateMyAddress userId + let userData = shortLinkUserData short + (connId, ccLink) <- withAgent $ \a -> createConnection a (aUserId user) True SCMContact userData Nothing IKPQOn subMode + ccLink' <- shortenCreatedLink ccLink + withFastStore $ \db -> createUserContactLink db user connId ccLink' subMode + pure $ CRUserContactLinkCreated user ccLink' + CreateMyAddress short -> withUser $ \User {userId} -> + processChatCommand $ APICreateMyAddress userId short APIDeleteMyAddress userId -> withUserId userId $ \user@User {profile = p} -> do conns <- withFastStore $ \db -> getUserAddressConnections db vr user withChatLock "deleteMyAddress" $ do @@ -1800,8 +1802,9 @@ processChatCommand' vr = \case let p' = (fromLocalProfile p :: Profile) {contactLink = Nothing} updateProfile_ user p' $ withFastStore' $ \db -> setUserProfileContactLink db user Nothing APISetProfileAddress userId True -> withUserId userId $ \user@User {profile = p} -> do - ucl@UserContactLink {connReqContact} <- withFastStore (`getUserAddress` user) - let p' = (fromLocalProfile p :: Profile) {contactLink = Just connReqContact} + ucl@UserContactLink {connLinkContact = CCLink cReq _} <- withFastStore (`getUserAddress` user) + -- TODO [short links] replace with short links + let p' = (fromLocalProfile p :: Profile) {contactLink = Just $ CLFull cReq} updateProfile_ user p' $ withFastStore' $ \db -> setUserProfileContactLink db user $ Just ucl SetProfileAddress onOff -> withUser $ \User {userId} -> processChatCommand $ APISetProfileAddress userId onOff @@ -1979,7 +1982,7 @@ processChatCommand' vr = \case Nothing -> do gVar <- asks random subMode <- chatReadVar subscriptionMode - (agentConnId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing IKPQOff subMode + (agentConnId, CCLink cReq _) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing Nothing IKPQOff subMode member <- withFastStore $ \db -> createNewContactMember db gVar user gInfo contact memRole agentConnId cReq subMode sendInvitation member cReq pure $ CRSentGroupInvitation user gInfo contact member @@ -2276,16 +2279,18 @@ processChatCommand' vr = \case updateGroupProfileByName gName $ \p -> p {description} ShowGroupDescription gName -> withUser $ \user -> CRGroupDescription user <$> withFastStore (\db -> getGroupInfoByName db vr user gName) - APICreateGroupLink groupId mRole -> withUser $ \user -> withGroupLock "createGroupLink" groupId $ do + APICreateGroupLink groupId mRole short -> withUser $ \user -> withGroupLock "createGroupLink" groupId $ do gInfo <- withFastStore $ \db -> getGroupInfo db vr user groupId assertUserGroupRole gInfo GRAdmin when (mRole > GRMember) $ throwChatError $ CEGroupMemberInitialRole gInfo mRole groupLinkId <- GroupLinkId <$> drgRandomBytes 16 subMode <- chatReadVar subscriptionMode let crClientData = encodeJSON $ CRDataGroup groupLinkId - (connId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMContact (Just crClientData) IKPQOff subMode - withFastStore $ \db -> createGroupLink db user gInfo connId cReq groupLinkId mRole subMode - pure $ CRGroupLinkCreated user gInfo cReq mRole + userData = shortLinkUserData short + (connId, ccLink) <- withAgent $ \a -> createConnection a (aUserId user) True SCMContact userData (Just crClientData) IKPQOff subMode + ccLink' <- createdGroupLink <$> shortenCreatedLink ccLink + withFastStore $ \db -> createGroupLink db user gInfo connId ccLink' groupLinkId mRole subMode + pure $ CRGroupLinkCreated user gInfo ccLink' mRole APIGroupLinkMemberRole groupId mRole' -> withUser $ \user -> withGroupLock "groupLinkMemberRole" groupId $ do gInfo <- withFastStore $ \db -> getGroupInfo db vr user groupId (groupLinkId, groupLink, mRole) <- withFastStore $ \db -> getGroupLink db user gInfo @@ -2311,7 +2316,7 @@ processChatCommand' vr = \case when (isJust $ memberContactId m) $ throwChatError $ CECommandError "member contact already exists" subMode <- chatReadVar subscriptionMode -- TODO PQ should negotitate contact connection with PQSupportOn? - (connId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing IKPQOff subMode + (connId, CCLink cReq _) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing Nothing IKPQOff subMode -- [incognito] reuse membership incognito profile ct <- withFastStore' $ \db -> createMemberContact db user connId cReq g m mConn subMode -- TODO not sure it is correct to set connections status here? @@ -2332,9 +2337,9 @@ processChatCommand' vr = \case toView $ CRNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct') ci] pure $ CRNewMemberContactSentInv user ct' g m _ -> throwChatError CEGroupMemberNotActive - CreateGroupLink gName mRole -> withUser $ \user -> do + CreateGroupLink gName mRole short -> withUser $ \user -> do groupId <- withFastStore $ \db -> getGroupIdByName db user gName - processChatCommand $ APICreateGroupLink groupId mRole + processChatCommand $ APICreateGroupLink groupId mRole short GroupLinkMemberRole gName mRole -> withUser $ \user -> do groupId <- withFastStore $ \db -> getGroupIdByName db user gName processChatCommand $ APIGroupLinkMemberRole groupId mRole @@ -2671,8 +2676,8 @@ processChatCommand' vr = \case CTGroup -> withFastStore $ \db -> getGroupChatItemIdByText' db user cId msg CTLocal -> withFastStore $ \db -> getLocalChatItemIdByText' db user cId msg _ -> throwChatError $ CECommandError "not supported" - connectViaContact :: User -> IncognitoEnabled -> ConnectionRequestUri 'CMContact -> CM ChatResponse - connectViaContact user@User {userId} incognito cReq@(CRContactUri ConnReqUriData {crClientData}) = withInvitationLock "connectViaContact" (strEncode cReq) $ do + connectViaContact :: User -> IncognitoEnabled -> CreatedLinkContact -> CM ChatResponse + connectViaContact user@User {userId} incognito (CCLink cReq@(CRContactUri ConnReqUriData {crClientData}) sLnk) = withInvitationLock "connectViaContact" (strEncode cReq) $ do let groupLinkId = crClientData >>= decodeJSON >>= \(CRDataGroup gli) -> Just gli cReqHash = ConnReqUriHash . C.sha256Hash $ strEncode cReq case groupLinkId of @@ -2702,11 +2707,12 @@ processChatCommand' vr = \case -- [incognito] generate profile to send incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing subMode <- chatReadVar subscriptionMode - conn@PendingContactConnection {pccConnId} <- withFastStore' $ \db -> createConnReqConnection db userId connId cReqHash xContactId incognitoProfile groupLinkId subMode chatV pqSup + let sLnk' = serverShortLink <$> sLnk + conn@PendingContactConnection {pccConnId} <- withFastStore' $ \db -> createConnReqConnection db userId connId cReqHash sLnk' xContactId incognitoProfile groupLinkId subMode chatV pqSup joinContact user pccConnId connId cReq incognitoProfile xContactId inGroup pqSup chatV pure $ CRSentInvitation user conn incognitoProfile - connectContactViaAddress :: User -> IncognitoEnabled -> Contact -> ConnectionRequestUri 'CMContact -> CM ChatResponse - connectContactViaAddress user incognito ct cReq = + connectContactViaAddress :: User -> IncognitoEnabled -> Contact -> CreatedLinkContact -> CM ChatResponse + connectContactViaAddress user incognito ct (CCLink cReq shortLink) = withInvitationLock "connectContactViaAddress" (strEncode cReq) $ do newXContactId <- XContactId <$> drgRandomBytes 16 let pqSup = PQSupportOn @@ -2715,10 +2721,10 @@ processChatCommand' vr = \case -- [incognito] generate profile to send incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing subMode <- chatReadVar subscriptionMode - (pccConnId, ct') <- withFastStore $ \db -> createAddressContactConnection db vr user ct connId cReqHash newXContactId incognitoProfile subMode chatV pqSup + (pccConnId, ct') <- withFastStore $ \db -> createAddressContactConnection db vr user ct connId cReqHash shortLink newXContactId incognitoProfile subMode chatV pqSup joinContact user pccConnId connId cReq incognitoProfile newXContactId False pqSup chatV pure $ CRSentInvitationToContact user ct' incognitoProfile - prepareContact :: User -> ConnectionRequestUri 'CMContact -> PQSupport -> CM (ConnId, VersionChat) + prepareContact :: User -> ConnReqContact -> PQSupport -> CM (ConnId, VersionChat) prepareContact user cReq pqSup = do -- 0) toggle disabled - PQSupportOff -- 1) toggle enabled, address supports PQ (connRequestPQSupport returns Just True) - PQSupportOn, enable support with compression @@ -2729,7 +2735,7 @@ processChatCommand' vr = \case let chatV = agentToChatVersion agentV connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq pqSup pure (connId, chatV) - joinContact :: User -> Int64 -> ConnId -> ConnectionRequestUri 'CMContact -> Maybe Profile -> XContactId -> Bool -> PQSupport -> VersionChat -> CM () + joinContact :: User -> Int64 -> ConnId -> ConnReqContact -> Maybe Profile -> XContactId -> Bool -> PQSupport -> VersionChat -> CM () joinContact user pccConnId connId cReq incognitoProfile xContactId inGroup pqSup chatV = do let profileToSend = userProfileToSend user incognitoProfile Nothing inGroup dm <- encodeConnInfoPQ pqSup chatV (XContact profileToSend $ Just xContactId) @@ -3034,32 +3040,77 @@ processChatCommand' vr = \case pure (gId, chatSettings) _ -> throwChatError $ CECommandError "not supported" processChatCommand $ APISetChatSettings (ChatRef cType chatId) $ updateSettings chatSettings - connectPlan :: User -> AConnectionRequestUri -> CM ConnectionPlan - connectPlan user (ACR SCMInvitation (CRInvitationUri crData e2e)) = do - withFastStore' (\db -> getConnectionEntityByConnReq db vr user cReqSchemas) >>= \case - Nothing -> pure $ CPInvitationLink ILPOk - Just (RcvDirectMsgConnection Connection {connStatus = ConnPrepared} Nothing) -> - pure $ CPInvitationLink ILPOk - Just (RcvDirectMsgConnection conn ct_) -> do - let Connection {connStatus, contactConnInitiated} = conn - if - | connStatus == ConnNew && contactConnInitiated -> - pure $ CPInvitationLink ILPOwnLink - | not (connReady conn) -> - pure $ CPInvitationLink (ILPConnecting ct_) - | otherwise -> case ct_ of - Just ct -> pure $ CPInvitationLink (ILPKnown ct) - Nothing -> throwChatError $ CEInternalError "ready RcvDirectMsgConnection connection should have associated contact" - Just _ -> throwChatError $ CECommandError "found connection entity is not RcvDirectMsgConnection" + connectPlan :: User -> AConnectionLink -> CM (ACreatedConnLink, ConnectionPlan) + connectPlan user (ACL SCMInvitation cLink) = case cLink of + CLFull cReq -> invitationReqAndPlan cReq Nothing + CLShort l -> do + let l' = serverShortLink l + withFastStore' (\db -> getConnectionEntityViaShortLink db vr user l') >>= \case + Just (cReq, ent) -> + (ACCL SCMInvitation (CCLink cReq (Just l')),) <$> (invitationEntityPlan ent `catchChatError` (pure . CPError)) + Nothing -> getShortLinkConnReq user l' >>= (`invitationReqAndPlan` Just l') where - cReqSchemas :: (ConnReqInvitation, ConnReqInvitation) - cReqSchemas = + invitationReqAndPlan cReq sLnk_ = do + plan <- inviationRequestPlan user cReq `catchChatError` (pure . CPError) + pure (ACCL SCMInvitation (CCLink cReq sLnk_), plan) + connectPlan user (ACL SCMContact cLink) = case cLink of + CLFull cReq -> contactReqAndPlan cReq Nothing + CLShort l@(CSLContact _ ct _ _) -> do + let l' = serverShortLink l + case ct of + CCTContact -> + withFastStore' (\db -> getUserContactLinkViaShortLink db user l') >>= \case + Just (UserContactLink (CCLink cReq _) _) -> pure (ACCL SCMContact $ CCLink cReq (Just l'), CPContactAddress CAPOwnLink) + Nothing -> getShortLinkConnReq user l' >>= (`contactReqAndPlan` Just l') + CCTGroup -> + withFastStore' (\db -> getGroupInfoViaUserShortLink db vr user l') >>= \case + Just (cReq, g) -> pure (ACCL SCMContact $ CCLink cReq (Just l'), CPGroupLink (GLPOwnLink g)) + Nothing -> getShortLinkConnReq user l' >>= (`contactReqAndPlan` Just l') + CCTChannel -> throwChatError $ CECommandError "channel links are not supported in this version" + where + contactReqAndPlan cReq sLnk_ = do + plan <- contactRequestPlan user cReq `catchChatError` (pure . CPError) + pure (ACCL SCMContact $ CCLink cReq sLnk_, plan) + connectWithPlan :: User -> IncognitoEnabled -> ACreatedConnLink -> ConnectionPlan -> CM ChatResponse + connectWithPlan user@User {userId} incognito ccLink plan + | connectionPlanProceed plan = do + case plan of CPError e -> toView $ CRChatError (Just user) e; _ -> pure () + case plan of + CPContactAddress (CAPContactViaAddress Contact {contactId}) -> + processChatCommand $ APIConnectContactViaAddress userId incognito contactId + _ -> processChatCommand $ APIConnect userId incognito (Just ccLink) + | otherwise = pure $ CRConnectionPlan user ccLink plan + inviationRequestPlan :: User -> ConnReqInvitation -> CM ConnectionPlan + inviationRequestPlan user cReq = do + withFastStore' (\db -> getConnectionEntityByConnReq db vr user $ cReqSchemas cReq) >>= \case + Nothing -> pure $ CPInvitationLink ILPOk + Just ent -> invitationEntityPlan ent + where + cReqSchemas :: ConnReqInvitation -> (ConnReqInvitation, ConnReqInvitation) + cReqSchemas (CRInvitationUri crData e2e) = ( CRInvitationUri crData {crScheme = SSSimplex} e2e, CRInvitationUri crData {crScheme = simplexChat} e2e ) - connectPlan user (ACR SCMContact (CRContactUri crData)) = do + invitationEntityPlan :: ConnectionEntity -> CM ConnectionPlan + invitationEntityPlan = \case + RcvDirectMsgConnection Connection {connStatus = ConnPrepared} Nothing -> + pure $ CPInvitationLink ILPOk + RcvDirectMsgConnection conn ct_ -> do + let Connection {connStatus, contactConnInitiated} = conn + if + | connStatus == ConnNew && contactConnInitiated -> + pure $ CPInvitationLink ILPOwnLink + | not (connReady conn) -> + pure $ CPInvitationLink (ILPConnecting ct_) + | otherwise -> case ct_ of + Just ct -> pure $ CPInvitationLink (ILPKnown ct) + Nothing -> throwChatError $ CEInternalError "ready RcvDirectMsgConnection connection should have associated contact" + _ -> throwChatError $ CECommandError "found connection entity is not RcvDirectMsgConnection" + contactRequestPlan :: User -> ConnReqContact -> CM ConnectionPlan + contactRequestPlan user (CRContactUri crData) = do let ConnReqUriData {crClientData} = crData groupLinkId = crClientData >>= decodeJSON >>= \(CRDataGroup gli) -> Just gli + cReqHashes = bimap hash hash cReqSchemas case groupLinkId of -- contact address Nothing -> @@ -3105,9 +3156,31 @@ processChatCommand' vr = \case ( CRContactUri crData {crScheme = SSSimplex}, CRContactUri crData {crScheme = simplexChat} ) - cReqHashes :: (ConnReqUriHash, ConnReqUriHash) - cReqHashes = bimap hash hash cReqSchemas + hash :: ConnReqContact -> ConnReqUriHash hash = ConnReqUriHash . C.sha256Hash . strEncode + getShortLinkConnReq :: User -> ConnShortLink m -> CM (ConnectionRequestUri m) + getShortLinkConnReq User {userId} l = do + l' <- restoreShortLink' l + (cReq, cData) <- withAgent (\a -> getConnShortLink a userId l') + case cData of + ContactLinkData {direct} | not direct -> throwChatError CEUnsupportedConnReq + _ -> pure () + pure cReq + -- This function is needed, as UI uses simplex:/ schema in message view, so that the links can be handled without browser, + -- and short links are stored with server hostname schema, so they wouldn't match without it. + serverShortLink :: ConnShortLink m -> ConnShortLink m + serverShortLink = \case + CSLInvitation _ srv lnkId linkKey -> CSLInvitation SLSServer srv lnkId linkKey + CSLContact _ ct srv linkKey -> CSLContact SLSServer ct srv linkKey + restoreShortLink' l = (`restoreShortLink` l) <$> asks (shortLinkPresetServers . config) + shortLinkUserData short = if short then Just "" else Nothing + shortenCreatedLink :: CreatedConnLink m -> CM (CreatedConnLink m) + shortenCreatedLink (CCLink cReq sLnk) = CCLink cReq <$> mapM (\l -> (`shortenShortLink` l) <$> asks (shortLinkPresetServers . config)) sLnk + createdGroupLink :: CreatedLinkContact -> CreatedLinkContact + createdGroupLink (CCLink cReq shortLink) = CCLink cReq (toGroupLink <$> shortLink) + where + toGroupLink :: ShortLinkContact -> ShortLinkContact + toGroupLink (CSLContact sch _ srv k) = CSLContact sch CCTGroup srv k updateCIGroupInvitationStatus :: User -> GroupInfo -> CIGroupInvitationStatus -> CM () updateCIGroupInvitationStatus user GroupInfo {groupId} newStatus = do AChatItem _ _ cInfo ChatItem {content, meta = CIMeta {itemId}} <- withFastStore $ \db -> getChatItemByGroupId db vr user groupId @@ -3574,7 +3647,7 @@ subscribeUserConnections vr onlyNeeded agentBatchSubscribe user = do viaUserContactLink, groupLinkId, customUserProfileId, - connReqInv = Nothing, + connLinkInv = Nothing, localAlias, createdAt, updatedAt = createdAt @@ -4042,11 +4115,11 @@ chatCommandP = "/set welcome " *> char_ '#' *> (UpdateGroupDescription <$> displayNameP <* A.space <*> (Just <$> msgTextP)), "/delete welcome " *> char_ '#' *> (UpdateGroupDescription <$> displayNameP <*> pure Nothing), "/show welcome " *> char_ '#' *> (ShowGroupDescription <$> displayNameP), - "/_create link #" *> (APICreateGroupLink <$> A.decimal <*> (memberRole <|> pure GRMember)), + "/_create link #" *> (APICreateGroupLink <$> A.decimal <*> (memberRole <|> pure GRMember) <*> shortOnOffP), "/_set link role #" *> (APIGroupLinkMemberRole <$> A.decimal <*> memberRole), "/_delete link #" *> (APIDeleteGroupLink <$> A.decimal), "/_get link #" *> (APIGetGroupLink <$> A.decimal), - "/create link #" *> (CreateGroupLink <$> displayNameP <*> (memberRole <|> pure GRMember)), + "/create link #" *> (CreateGroupLink <$> displayNameP <*> (memberRole <|> pure GRMember) <*> shortP), "/set link role #" *> (GroupLinkMemberRole <$> displayNameP <*> memberRole), "/delete link #" *> (DeleteGroupLink <$> displayNameP), "/show link #" *> (ShowGroupLink <$> displayNameP), @@ -4057,12 +4130,12 @@ chatCommandP = "/_contacts " *> (APIListContacts <$> A.decimal), "/contacts" $> ListContacts, "/_connect plan " *> (APIConnectPlan <$> A.decimal <* A.space <*> strP), - "/_connect " *> (APIConnect <$> A.decimal <*> incognitoOnOffP <* A.space <*> ((Just <$> strP) <|> A.takeByteString $> Nothing)), - "/_connect " *> (APIAddContact <$> A.decimal <*> incognitoOnOffP), + "/_connect " *> (APIAddContact <$> A.decimal <*> shortOnOffP <*> incognitoOnOffP), + "/_connect " *> (APIConnect <$> A.decimal <*> incognitoOnOffP <* A.space <*> connLinkP), "/_set incognito :" *> (APISetConnectionIncognito <$> A.decimal <* A.space <*> onOffP), "/_set conn user :" *> (APIChangeConnectionUser <$> A.decimal <* A.space <*> A.decimal), + ("/connect" <|> "/c") *> (AddContact <$> shortP <*> incognitoP), ("/connect" <|> "/c") *> (Connect <$> incognitoP <* A.space <*> ((Just <$> strP) <|> A.takeTill isSpace $> Nothing)), - ("/connect" <|> "/c") *> (AddContact <$> incognitoP), ForwardMessage <$> chatNameP <* " <- @" <*> displayNameP <* A.space <*> msgTextP, ForwardGroupMessage <$> chatNameP <* " <- #" <*> displayNameP <* A.space <* A.char '@' <*> (Just <$> displayNameP) <* A.space <*> msgTextP, ForwardGroupMessage <$> chatNameP <* " <- #" <*> displayNameP <*> pure Nothing <* A.space <*> msgTextP, @@ -4096,8 +4169,8 @@ chatCommandP = ("/fstatus " <|> "/fs ") *> (FileStatus <$> A.decimal), "/_connect contact " *> (APIConnectContactViaAddress <$> A.decimal <*> incognitoOnOffP <* A.space <*> A.decimal), "/simplex" *> (ConnectSimplex <$> incognitoP), - "/_address " *> (APICreateMyAddress <$> A.decimal), - ("/address" <|> "/ad") $> CreateMyAddress, + "/_address " *> (APICreateMyAddress <$> A.decimal <*> shortOnOffP), + ("/address" <|> "/ad") *> (CreateMyAddress <$> shortP), "/_delete_address " *> (APIDeleteMyAddress <$> A.decimal), ("/delete_address" <|> "/da") $> DeleteMyAddress, "/_show_address " *> (APIShowMyAddress <$> A.decimal), @@ -4167,7 +4240,12 @@ chatCommandP = ] where choice = A.choice . map (\p -> p <* A.takeWhile (== ' ') <* A.endOfInput) + connLinkP = do + ((Just <$> strP) <|> A.takeTill (== ' ') $> Nothing) + >>= mapM (\(ACR m cReq) -> ACCL m . CCLink cReq <$> optional (A.space *> strP)) + shortP = (A.space *> ("short" <|> "s")) $> True <|> pure False incognitoP = (A.space *> ("incognito" <|> "i")) $> True <|> pure False + shortOnOffP = (A.space *> "short=" *> onOffP) <|> pure False incognitoOnOffP = (A.space *> "incognito=" *> onOffP) <|> pure False imagePrefix = (<>) <$> "data:" <*> ("image/png;base64," <|> "image/jpg;base64,") imageP = safeDecodeUtf8 <$> ((<>) <$> imagePrefix <*> (B64.encode <$> base64P)) diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs index 0d7de70098..dca3a7f678 100644 --- a/src/Simplex/Chat/Library/Internal.hs +++ b/src/Simplex/Chat/Library/Internal.hs @@ -2234,7 +2234,7 @@ simplexTeamContactProfile = { displayName = "SimpleX Chat team", fullName = "", image = Just (ImageData "data:image/jpg;base64,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"), - contactLink = Just adminContactReq, + contactLink = Just $ CLFull adminContactReq, preferences = Nothing } @@ -2244,7 +2244,7 @@ simplexStatusContactProfile = { displayName = "SimpleX-Status", fullName = "", image = Just (ImageData "data:image/jpg;base64,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"), - contactLink = Just (either error id $ strDecode "simplex:/contact/#/?v=1-2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FShQuD-rPokbDvkyotKx5NwM8P3oUXHxA%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEA6fSx1k9zrOmF0BJpCaTarZvnZpMTAVQhd3RkDQ35KT0%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion"), + contactLink = Just (either error CLFull $ strDecode "simplex:/contact/#/?v=1-2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FShQuD-rPokbDvkyotKx5NwM8P3oUXHxA%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEA6fSx1k9zrOmF0BJpCaTarZvnZpMTAVQhd3RkDQ35KT0%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion"), preferences = Nothing } diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index f9fcecc5a1..38d67aa150 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -1186,8 +1186,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = CORGroup gInfo -> toView $ CRBusinessRequestAlreadyAccepted user gInfo CORRequest cReq -> do ucl <- withStore $ \db -> getUserContactLinkById db userId userContactLinkId - let (UserContactLink {connReqContact, autoAccept}, gLinkInfo_) = ucl - isSimplexTeam = sameConnReqContact connReqContact adminContactReq + let (UserContactLink {connLinkContact = CCLink connReq _, autoAccept}, gLinkInfo_) = ucl + isSimplexTeam = sameConnReqContact connReq adminContactReq v = maxVersion chatVRange case autoAccept of Just AutoAccept {acceptIncognito, businessAddress} diff --git a/src/Simplex/Chat/Markdown.hs b/src/Simplex/Chat/Markdown.hs index 3ed2a8fa0b..e5de9c408c 100644 --- a/src/Simplex/Chat/Markdown.hs +++ b/src/Simplex/Chat/Markdown.hs @@ -29,11 +29,10 @@ import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) import Simplex.Chat.Types -import Simplex.Messaging.Agent.Protocol (AConnectionRequestUri (..), ConnReqUriData (..), ConnectionRequestUri (..), SMPQueue (..)) +import Simplex.Messaging.Agent.Protocol (AConnectionLink (..), ConnReqUriData (..), ConnShortLink (..), ConnectionLink (..), ConnectionRequestUri (..), ContactConnType (..), SMPQueue (..), simplexConnReqUri, simplexShortLink) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fstToLower, sumTypeJSON) import Simplex.Messaging.Protocol (ProtocolServer (..)) -import Simplex.Messaging.ServiceScheme (ServiceScheme (..)) import Simplex.Messaging.Util (decodeJSON, safeDecodeUtf8) import System.Console.ANSI.Types import qualified Text.Email.Validate as Email @@ -49,7 +48,7 @@ data Format | Secret | Colored {color :: FormatColor} | Uri - | SimplexLink {linkType :: SimplexLinkType, simplexUri :: AConnectionRequestUri, smpHosts :: NonEmpty Text} + | SimplexLink {linkType :: SimplexLinkType, simplexUri :: AConnectionLink, smpHosts :: NonEmpty Text} | Mention {memberName :: Text} | Email | Phone @@ -62,7 +61,7 @@ mentionedNames = mapMaybe (\(FormattedText f _) -> mentionedName =<< f) Mention name -> Just name _ -> Nothing -data SimplexLinkType = XLContact | XLInvitation | XLGroup +data SimplexLinkType = XLContact | XLInvitation | XLGroup | XLChannel deriving (Eq, Show) colored :: Color -> Format @@ -248,24 +247,34 @@ markdownP = mconcat <$> A.many' fragmentP ')' -> False c -> isPunctuation c uriMarkdown s = case strDecode $ encodeUtf8 s of - Right cReq -> markdown (simplexUriFormat cReq) s + Right cLink -> markdown (simplexUriFormat cLink) s _ -> markdown Uri s isUri s = T.length s >= 10 && any (`T.isPrefixOf` s) ["http://", "https://", "simplex:/"] isEmail s = T.any (== '@') s && Email.isValid (encodeUtf8 s) noFormat = pure . unmarked - simplexUriFormat :: AConnectionRequestUri -> Format + simplexUriFormat :: AConnectionLink -> Format simplexUriFormat = \case - ACR m (CRContactUri crData) -> - let cReq = ACR m $ CRContactUri crData {crScheme = SSSimplex} - in SimplexLink (linkType' crData) cReq $ uriHosts crData - ACR m (CRInvitationUri crData e2e) -> - let cReq = ACR m $ CRInvitationUri crData {crScheme = SSSimplex} e2e - in SimplexLink XLInvitation cReq $ uriHosts crData - where - uriHosts ConnReqUriData {crSmpQueues} = L.map (safeDecodeUtf8 . strEncode) $ sconcat $ L.map (host . qServer) crSmpQueues - linkType' ConnReqUriData {crClientData} = case crClientData >>= decodeJSON of - Just (CRDataGroup _) -> XLGroup - Nothing -> XLContact + ACL m (CLFull cReq) -> case cReq of + CRContactUri crData -> SimplexLink (linkType' crData) cLink $ uriHosts crData + CRInvitationUri crData _ -> SimplexLink XLInvitation cLink $ uriHosts crData + where + cLink = ACL m $ CLFull $ simplexConnReqUri cReq + uriHosts ConnReqUriData {crSmpQueues} = L.map strEncodeText $ sconcat $ L.map (host . qServer) crSmpQueues + linkType' ConnReqUriData {crClientData} = case crClientData >>= decodeJSON of + Just (CRDataGroup _) -> XLGroup + Nothing -> XLContact + ACL m (CLShort sLnk) -> case sLnk of + CSLContact _ ct srv _ -> SimplexLink (linkType' ct) cLink $ uriHosts srv + CSLInvitation _ srv _ _ -> SimplexLink XLInvitation cLink $ uriHosts srv + where + cLink = ACL m $ CLShort $ simplexShortLink sLnk + uriHosts srv = L.map strEncodeText $ host srv + linkType' = \case + CCTGroup -> XLGroup + CCTChannel -> XLChannel + CCTContact -> XLContact + strEncodeText :: StrEncoding a => a -> Text + strEncodeText = safeDecodeUtf8 . strEncode markdownText :: FormattedText -> Text markdownText (FormattedText f_ t) = case f_ of diff --git a/src/Simplex/Chat/Operators.hs b/src/Simplex/Chat/Operators.hs index 5240460c9c..8c4490a2c4 100644 --- a/src/Simplex/Chat/Operators.hs +++ b/src/Simplex/Chat/Operators.hs @@ -275,6 +275,10 @@ data UserServer' s (p :: ProtocolType) = UserServer } deriving (Show) +presetServerAddress :: UserServer' s p -> ProtocolServer p +presetServerAddress UserServer {server = ProtoServerWithAuth srv _} = srv +{-# INLINE presetServerAddress #-} + data PresetOperator = PresetOperator { operator :: Maybe NewServerOperator, smp :: [NewUserServer 'PSMP], @@ -297,6 +301,9 @@ operatorServersToUse p PresetOperator {useSMP, useXFTP} = case p of SPSMP -> useSMP SPXFTP -> useXFTP +presetServer' :: Bool -> ProtocolServer p -> NewUserServer p +presetServer' enabled = presetServer enabled . (`ProtoServerWithAuth` Nothing) + presetServer :: Bool -> ProtoServerWithAuth p -> NewUserServer p presetServer = newUserServer_ True diff --git a/src/Simplex/Chat/Operators/Presets.hs b/src/Simplex/Chat/Operators/Presets.hs new file mode 100644 index 0000000000..4aa0903d3c --- /dev/null +++ b/src/Simplex/Chat/Operators/Presets.hs @@ -0,0 +1,117 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE OverloadedLists #-} +{-# LANGUAGE OverloadedStrings #-} + +module Simplex.Chat.Operators.Presets where + +import Data.List.NonEmpty (NonEmpty) +import qualified Data.List.NonEmpty as L +import Simplex.Chat.Operators +import Simplex.Messaging.Agent.Env.SQLite (ServerRoles (..), allRoles) +import Simplex.Messaging.Protocol (ProtocolType (..), SMPServer) + +operatorSimpleXChat :: NewServerOperator +operatorSimpleXChat = + ServerOperator + { operatorId = DBNewEntity, + operatorTag = Just OTSimplex, + tradeName = "SimpleX Chat", + legalName = Just "SimpleX Chat Ltd", + serverDomains = ["simplex.im"], + conditionsAcceptance = CARequired Nothing, + enabled = True, + smpRoles = allRoles, + xftpRoles = allRoles + } + +operatorFlux :: NewServerOperator +operatorFlux = + ServerOperator + { operatorId = DBNewEntity, + operatorTag = Just OTFlux, + tradeName = "Flux", + legalName = Just "InFlux Technologies Limited", + serverDomains = ["simplexonflux.com"], + conditionsAcceptance = CARequired Nothing, + enabled = False, + smpRoles = ServerRoles {storage = False, proxy = True}, + xftpRoles = ServerRoles {storage = False, proxy = True} + } + +-- Please note: if any servers are removed from the lists below, they MUST be added here. +-- Otherwise previously created short links won't work. +-- +-- !!! Also, if any servers need to be added, shortLinkPresetServers will need to be be split to two, +-- so that option used for restoring links is updated earlier, for backward/forward compatibility. +allPresetServers :: NonEmpty SMPServer +allPresetServers = enabledSimplexChatSMPServers <> disabledSimplexChatSMPServers <> fluxSMPServers_ + -- TODO [short links] remove, added for testing + <> ["smp://8Af90NX2TTkKEJAF1RCg69P_Odg2Z-6_J6DOKUqK3rQ=@smp7.simplex.im,dbxqutskmmbkbrs7ofi7pmopeyhgi5cxbjbh4ummgmep4r6bz4cbrcid.onion"] + +simplexChatSMPServers :: [NewUserServer 'PSMP] +simplexChatSMPServers = + map (presetServer' True) (L.toList enabledSimplexChatSMPServers) + <> map (presetServer' False) (L.toList disabledSimplexChatSMPServers) + +-- Please note: if any servers are removed from this list, they MUST be added to allPresetServers. +-- Otherwise previously created short links won't work. +-- +-- !!! Also, if any servers need to be added, shortLinkPresetServers will need to be be split to two, +-- so that option used for restoring links is updated earlier, for backward/forward compatibility. +enabledSimplexChatSMPServers :: NonEmpty SMPServer +enabledSimplexChatSMPServers = + [ "smp://0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU=@smp8.simplex.im,beccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion", + "smp://SkIkI6EPd2D63F4xFKfHk7I1UGZVNn6k1QWZ5rcyr6w=@smp9.simplex.im,jssqzccmrcws6bhmn77vgmhfjmhwlyr3u7puw4erkyoosywgl67slqqd.onion", + "smp://6iIcWT_dF2zN_w5xzZEY7HI2Prbh3ldP07YTyDexPjE=@smp10.simplex.im,rb2pbttocvnbrngnwziclp2f4ckjq65kebafws6g4hy22cdaiv5dwjqd.onion", + "smp://1OwYGt-yqOfe2IyVHhxz3ohqo3aCCMjtB-8wn4X_aoY=@smp11.simplex.im,6ioorbm6i3yxmuoezrhjk6f6qgkc4syabh7m3so74xunb5nzr4pwgfqd.onion", + "smp://UkMFNAXLXeAAe0beCa4w6X_zp18PwxSaSjY17BKUGXQ=@smp12.simplex.im,ie42b5weq7zdkghocs3mgxdjeuycheeqqmksntj57rmejagmg4eor5yd.onion", + "smp://enEkec4hlR3UtKx2NMpOUK_K4ZuDxjWBO1d9Y4YXVaA=@smp14.simplex.im,aspkyu2sopsnizbyfabtsicikr2s4r3ti35jogbcekhm3fsoeyjvgrid.onion", + "smp://h--vW7ZSkXPeOUpfxlFGgauQmXNFOzGoizak7Ult7cw=@smp15.simplex.im,oauu4bgijybyhczbnxtlggo6hiubahmeutaqineuyy23aojpih3dajad.onion", + "smp://hejn2gVIqNU6xjtGM3OwQeuk8ZEbDXVJXAlnSBJBWUA=@smp16.simplex.im,p3ktngodzi6qrf7w64mmde3syuzrv57y55hxabqcq3l5p6oi7yzze6qd.onion", + "smp://ZKe4uxF4Z_aLJJOEsC-Y6hSkXgQS5-oc442JQGkyP8M=@smp17.simplex.im,ogtwfxyi3h2h5weftjjpjmxclhb5ugufa5rcyrmg7j4xlch7qsr5nuqd.onion", + "smp://PtsqghzQKU83kYTlQ1VKg996dW4Cw4x_bvpKmiv8uns=@smp18.simplex.im,lyqpnwbs2zqfr45jqkncwpywpbtq7jrhxnib5qddtr6npjyezuwd3nqd.onion", + "smp://N_McQS3F9TGoh4ER0QstUf55kGnNSd-wXfNPZ7HukcM=@smp19.simplex.im,i53bbtoqhlc365k6kxzwdp5w3cdt433s7bwh3y32rcbml2vztiyyz5id.onion" + ] + +-- Please note: if any servers are removed from this list, they MUST be added to allPresetServers. +-- Otherwise previously created short links won't work. +-- +-- !!! Also, if any servers need to be added, shortLinkPresetServers will need to be be split to two, +-- so that option used for restoring links is updated earlier, for backward/forward compatibility. +disabledSimplexChatSMPServers :: NonEmpty SMPServer +disabledSimplexChatSMPServers = + [ "smp://u2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU=@smp4.simplex.im,o5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion", + "smp://hpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg=@smp5.simplex.im,jjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion", + "smp://PQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo=@smp6.simplex.im,bylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion" + ] + +fluxSMPServers :: [NewUserServer 'PSMP] +fluxSMPServers = map (presetServer' True) $ L.toList fluxSMPServers_ + +-- Please note: if any servers are removed from this list, they MUST be added to allPresetServers. +-- Otherwise previously created short links won't work. +-- +-- !!! Also, if any servers need to be added, shortLinkPresetServers will need to be be split to two, +-- so that option used for restoring links is updated earlier, for backward/forward compatibility. +fluxSMPServers_ :: NonEmpty SMPServer +fluxSMPServers_ = + [ "smp://xQW_ufMkGE20UrTlBl8QqceG1tbuylXhr9VOLPyRJmw=@smp1.simplexonflux.com,qb4yoanyl4p7o33yrknv4rs6qo7ugeb2tu2zo66sbebezs4cpyosarid.onion", + "smp://LDnWZVlAUInmjmdpQQoIo6FUinRXGe0q3zi5okXDE4s=@smp2.simplexonflux.com,yiqtuh3q4x7hgovkomafsod52wvfjucdljqbbipg5sdssnklgongxbqd.onion", + "smp://1jne379u7IDJSxAvXbWb_JgoE7iabcslX0LBF22Rej0=@smp3.simplexonflux.com,a5lm4k7ufei66cdck6fy63r4lmkqy3dekmmb7jkfdm5ivi6kfaojshad.onion", + "smp://xmAmqj75I9mWrUihLUlI0ZuNLXlIwFIlHRq5Pb6cHAU=@smp4.simplexonflux.com,qpcz2axyy66u26hfdd2e23uohcf3y6c36mn7dcuilcgnwjasnrvnxjqd.onion", + "smp://rWvBYyTamuRCBYb_KAn-nsejg879ndhiTg5Sq3k0xWA=@smp5.simplexonflux.com,4ao347qwiuluyd45xunmii4skjigzuuox53hpdsgbwxqafd4yrticead.onion", + "smp://PN7-uqLBToqlf1NxHEaiL35lV2vBpXq8Nj8BW11bU48=@smp6.simplexonflux.com,hury6ot3ymebbr2535mlp7gcxzrjpc6oujhtfxcfh2m4fal4xw5fq6qd.onion" + ] + +fluxXFTPServers :: [NewUserServer 'PXFTP] +fluxXFTPServers = + map + (presetServer True) + [ "xftp://92Sctlc09vHl_nAqF2min88zKyjdYJ9mgxRCJns5K2U=@xftp1.simplexonflux.com,apl3pumq3emwqtrztykyyoomdx4dg6ysql5zek2bi3rgznz7ai3odkid.onion", + "xftp://YBXy4f5zU1CEhnbbCzVWTNVNsaETcAGmYqGNxHntiE8=@xftp2.simplexonflux.com,c5jjecisncnngysah3cz2mppediutfelco4asx65mi75d44njvua3xid.onion", + "xftp://ARQO74ZSvv2OrulRF3CdgwPz_AMy27r0phtLSq5b664=@xftp3.simplexonflux.com,dc4mohiubvbnsdfqqn7xhlhpqs5u4tjzp7xpz6v6corwvzvqjtaqqiqd.onion", + "xftp://ub2jmAa9U0uQCy90O-fSUNaYCj6sdhl49Jh3VpNXP58=@xftp4.simplexonflux.com,4qq5pzier3i4yhpuhcrhfbl6j25udc4czoyascrj4yswhodhfwev3nyd.onion", + "xftp://Rh19D5e4Eez37DEE9hAlXDB3gZa1BdFYJTPgJWPO9OI=@xftp5.simplexonflux.com,q7itltdn32hjmgcqwhow4tay5ijetng3ur32bolssw32fvc5jrwvozad.onion", + "xftp://0AznwoyfX8Od9T_acp1QeeKtxUi676IBIiQjXVwbdyU=@xftp6.simplexonflux.com,upvzf23ou6nrmaf3qgnhd6cn3d74tvivlmz3p7wdfwq6fhthjrjiiqid.onion" + ] diff --git a/src/Simplex/Chat/Store/Connections.hs b/src/Simplex/Chat/Store/Connections.hs index d43f7169ea..0a4f5392c0 100644 --- a/src/Simplex/Chat/Store/Connections.hs +++ b/src/Simplex/Chat/Store/Connections.hs @@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-} +{-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} @@ -12,6 +13,7 @@ module Simplex.Chat.Store.Connections ( getChatLockEntity, getConnectionEntity, getConnectionEntityByConnReq, + getConnectionEntityViaShortLink, getContactConnEntityByConnReqHash, getConnectionsToSubscribe, unsetConnectionToSubscribe, @@ -33,7 +35,7 @@ import Simplex.Chat.Store.Groups import Simplex.Chat.Store.Profiles import Simplex.Chat.Store.Shared import Simplex.Chat.Types -import Simplex.Messaging.Agent.Protocol (ConnId) +import Simplex.Messaging.Agent.Protocol (ConnId, ConnShortLink, ConnectionMode (..)) import Simplex.Messaging.Agent.Store.AgentStore (firstRow, firstRow', maybeFirstRow) import Simplex.Messaging.Agent.Store.DB (BoolInt (..)) import qualified Simplex.Messaging.Agent.Store.DB as DB @@ -204,6 +206,26 @@ getConnectionEntityByConnReq db vr user@User {userId} (cReqSchema1, cReqSchema2) DB.query db "SELECT agent_conn_id FROM connections WHERE user_id = ? AND conn_req_inv IN (?,?) LIMIT 1" (userId, cReqSchema1, cReqSchema2) maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getConnectionEntity db vr user) connId_ +getConnectionEntityViaShortLink :: DB.Connection -> VersionRangeChat -> User -> ConnShortLink 'CMInvitation -> IO (Maybe (ConnReqInvitation, ConnectionEntity)) +getConnectionEntityViaShortLink db vr user@User {userId} shortLink = fmap eitherToMaybe $ runExceptT $ do + (cReq, connId) <- ExceptT getConnReqConnId + (cReq,) <$> getConnectionEntity db vr user connId + where + getConnReqConnId = + firstRow' toConnReqConnId (SEInternalError "connection not found") $ + DB.query + db + [sql| + SELECT conn_req_inv, agent_conn_id + FROM connections + WHERE user_id = ? AND short_link_inv = ? LIMIT 1 + |] + (userId, shortLink) + -- cReq is Maybe - it is removed when connection is established + toConnReqConnId = \case + (Just cReq, connId) -> Right (cReq, connId) + _ -> Left $ SEInternalError "no connection request" + -- search connection for connection plan: -- multiple connections can have same via_contact_uri_hash if request was repeated; -- this function searches for latest connection with contact so that "known contact" plan would be chosen; diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs index d473c81758..4de832a8b1 100644 --- a/src/Simplex/Chat/Store/Direct.hs +++ b/src/Simplex/Chat/Store/Direct.hs @@ -100,7 +100,7 @@ import Simplex.Chat.Store.Shared import Simplex.Chat.Types import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.UITheme -import Simplex.Messaging.Agent.Protocol (ConnId, InvitationId, UserId) +import Simplex.Messaging.Agent.Protocol (ConnId, CreatedConnLink (..), InvitationId, UserId) import Simplex.Messaging.Agent.Store.AgentStore (firstRow, maybeFirstRow) import Simplex.Messaging.Agent.Store.DB (Binary (..), BoolInt (..)) import qualified Simplex.Messaging.Agent.Store.DB as DB @@ -122,7 +122,7 @@ getPendingContactConnection db userId connId = do DB.query db [sql| - SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at + SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, custom_user_profile_id, conn_req_inv, short_link_inv, local_alias, created_at, updated_at FROM connections WHERE user_id = ? AND connection_id = ? @@ -148,14 +148,14 @@ deletePendingContactConnection db userId connId = |] (userId, connId, ConnContact) -createAddressContactConnection :: DB.Connection -> VersionRangeChat -> User -> Contact -> ConnId -> ConnReqUriHash -> XContactId -> Maybe Profile -> SubscriptionMode -> VersionChat -> PQSupport -> ExceptT StoreError IO (Int64, Contact) -createAddressContactConnection db vr user@User {userId} Contact {contactId} acId cReqHash xContactId incognitoProfile subMode chatV pqSup = do - PendingContactConnection {pccConnId} <- liftIO $ createConnReqConnection db userId acId cReqHash xContactId incognitoProfile Nothing subMode chatV pqSup +createAddressContactConnection :: DB.Connection -> VersionRangeChat -> User -> Contact -> ConnId -> ConnReqUriHash -> Maybe ShortLinkContact -> XContactId -> Maybe Profile -> SubscriptionMode -> VersionChat -> PQSupport -> ExceptT StoreError IO (Int64, Contact) +createAddressContactConnection db vr user@User {userId} Contact {contactId} acId cReqHash sLnk xContactId incognitoProfile subMode chatV pqSup = do + PendingContactConnection {pccConnId} <- liftIO $ createConnReqConnection db userId acId cReqHash sLnk xContactId incognitoProfile Nothing subMode chatV pqSup liftIO $ DB.execute db "UPDATE connections SET contact_id = ? WHERE connection_id = ?" (contactId, pccConnId) (pccConnId,) <$> getContact db vr user contactId -createConnReqConnection :: DB.Connection -> UserId -> ConnId -> ConnReqUriHash -> XContactId -> Maybe Profile -> Maybe GroupLinkId -> SubscriptionMode -> VersionChat -> PQSupport -> IO PendingContactConnection -createConnReqConnection db userId acId cReqHash xContactId incognitoProfile groupLinkId subMode chatV pqSup = do +createConnReqConnection :: DB.Connection -> UserId -> ConnId -> ConnReqUriHash -> Maybe ShortLinkContact -> XContactId -> Maybe Profile -> Maybe GroupLinkId -> SubscriptionMode -> VersionChat -> PQSupport -> IO PendingContactConnection +createConnReqConnection db userId acId cReqHash sLnk xContactId incognitoProfile groupLinkId subMode chatV pqSup = do createdAt <- getCurrentTime customUserProfileId <- mapM (createIncognitoProfile_ db userId createdAt) incognitoProfile let pccConnStatus = ConnJoined @@ -164,16 +164,16 @@ createConnReqConnection db userId acId cReqHash xContactId incognitoProfile grou [sql| INSERT INTO connections ( user_id, agent_conn_id, conn_status, conn_type, contact_conn_initiated, - via_contact_uri_hash, xcontact_id, custom_user_profile_id, via_group_link, group_link_id, + via_contact_uri_hash, via_short_link_contact, xcontact_id, custom_user_profile_id, via_group_link, group_link_id, created_at, updated_at, to_subscribe, conn_chat_version, pq_support, pq_encryption - ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) + ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) |] - ( (userId, acId, pccConnStatus, ConnContact, BI True, cReqHash, xContactId) + ( (userId, acId, pccConnStatus, ConnContact, BI True, cReqHash, sLnk, xContactId) :. (customUserProfileId, BI (isJust groupLinkId), groupLinkId) :. (createdAt, createdAt, BI (subMode == SMOnlyCreate), chatV, pqSup, pqSup) ) pccConnId <- insertedRowId db - pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = True, viaUserContactLink = Nothing, groupLinkId, customUserProfileId, connReqInv = Nothing, localAlias = "", createdAt, updatedAt = createdAt} + pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = True, viaUserContactLink = Nothing, groupLinkId, customUserProfileId, connLinkInv = Nothing, localAlias = "", createdAt, updatedAt = createdAt} getConnReqContactXContactId :: DB.Connection -> VersionRangeChat -> User -> ConnReqUriHash -> IO (Maybe Contact, Maybe XContactId) getConnReqContactXContactId db vr user@User {userId} cReqHash = do @@ -214,8 +214,8 @@ getContactByConnReqHash db vr user@User {userId} cReqHash = do (userId, cReqHash, CSActive) mapM (addDirectChatTags db) ct_ -createDirectConnection :: DB.Connection -> User -> ConnId -> ConnReqInvitation -> ConnStatus -> Maybe Profile -> SubscriptionMode -> VersionChat -> PQSupport -> IO PendingContactConnection -createDirectConnection db User {userId} acId cReq pccConnStatus incognitoProfile subMode chatV pqSup = do +createDirectConnection :: DB.Connection -> User -> ConnId -> CreatedLinkInvitation -> ConnStatus -> Maybe Profile -> SubscriptionMode -> VersionChat -> PQSupport -> IO PendingContactConnection +createDirectConnection db User {userId} acId ccLink@(CCLink cReq shortLinkInv) pccConnStatus incognitoProfile subMode chatV pqSup = do createdAt <- getCurrentTime customUserProfileId <- mapM (createIncognitoProfile_ db userId createdAt) incognitoProfile let contactConnInitiated = pccConnStatus == ConnNew @@ -223,15 +223,15 @@ createDirectConnection db User {userId} acId cReq pccConnStatus incognitoProfile db [sql| INSERT INTO connections - (user_id, agent_conn_id, conn_req_inv, conn_status, conn_type, contact_conn_initiated, custom_user_profile_id, + (user_id, agent_conn_id, conn_req_inv, short_link_inv, conn_status, conn_type, contact_conn_initiated, custom_user_profile_id, created_at, updated_at, to_subscribe, conn_chat_version, pq_support, pq_encryption) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?) |] - ( (userId, acId, cReq, pccConnStatus, ConnContact, BI contactConnInitiated, customUserProfileId) + ( (userId, acId, cReq, shortLinkInv, pccConnStatus, ConnContact, BI contactConnInitiated, customUserProfileId) :. (createdAt, createdAt, BI (subMode == SMOnlyCreate), chatV, pqSup, pqSup) ) pccConnId <- insertedRowId db - pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = False, viaUserContactLink = Nothing, groupLinkId = Nothing, customUserProfileId, connReqInv = Just cReq, localAlias = "", createdAt, updatedAt = createdAt} + pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = False, viaUserContactLink = Nothing, groupLinkId = Nothing, customUserProfileId, connLinkInv = Just ccLink, localAlias = "", createdAt, updatedAt = createdAt} createIncognitoProfile :: DB.Connection -> User -> Profile -> IO Int64 createIncognitoProfile db User {userId} p = do @@ -904,7 +904,7 @@ getPendingContactConnections db User {userId} = do <$> DB.query db [sql| - SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at + SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, custom_user_profile_id, conn_req_inv, short_link_inv, local_alias, created_at, updated_at FROM connections WHERE user_id = ? AND conn_type = ? @@ -989,7 +989,7 @@ updateConnectionStatus_ :: DB.Connection -> Int64 -> ConnStatus -> IO () updateConnectionStatus_ db connId connStatus = do currentTs <- getCurrentTime if connStatus == ConnReady - then DB.execute db "UPDATE connections SET conn_status = ?, updated_at = ?, conn_req_inv = NULL WHERE connection_id = ?" (connStatus, currentTs, connId) + then DB.execute db "UPDATE connections SET conn_status = ?, updated_at = ?, conn_req_inv = NULL, short_link_inv = NULL WHERE connection_id = ?" (connStatus, currentTs, connId) else DB.execute db "UPDATE connections SET conn_status = ?, updated_at = ? WHERE connection_id = ?" (connStatus, currentTs, connId) updateContactSettings :: DB.Connection -> User -> Int64 -> ChatSettings -> IO () diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 0094e20cb8..0c49338a2e 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -39,6 +39,7 @@ module Simplex.Chat.Store.Groups getGroup, getGroupInfo, getGroupInfoByUserContactLinkConnReq, + getGroupInfoViaUserShortLink, getGroupInfoByGroupLinkHash, updateGroupProfile, updateGroupPreferences, @@ -157,14 +158,14 @@ import Simplex.Chat.Types import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Shared import Simplex.Chat.Types.UITheme -import Simplex.Messaging.Agent.Protocol (ConnId, UserId) +import Simplex.Messaging.Agent.Protocol (ConnId, CreatedConnLink (..), UserId) import Simplex.Messaging.Agent.Store.AgentStore (firstRow, fromOnlyBI, maybeFirstRow) import Simplex.Messaging.Agent.Store.DB (Binary (..), BoolInt (..)) import qualified Simplex.Messaging.Agent.Store.DB as DB import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.Ratchet (pattern PQEncOff, pattern PQSupportOff) import Simplex.Messaging.Protocol (SubscriptionMode (..)) -import Simplex.Messaging.Util (eitherToMaybe, ($>>=), (<$$>)) +import Simplex.Messaging.Util (eitherToMaybe, firstRow', ($>>=), (<$$>)) import Simplex.Messaging.Version import UnliftIO.STM #if defined(dbPostgres) @@ -175,21 +176,21 @@ import Database.SQLite.Simple (Only (..), Query, (:.) (..)) import Database.SQLite.Simple.QQ (sql) #endif -type MaybeGroupMemberRow = (Maybe Int64, Maybe Int64, Maybe MemberId, Maybe VersionChat, Maybe VersionChat, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId, Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe ImageData, Maybe ConnReqContact, Maybe LocalAlias, Maybe Preferences) :. (Maybe UTCTime, Maybe UTCTime) +type MaybeGroupMemberRow = (Maybe Int64, Maybe Int64, Maybe MemberId, Maybe VersionChat, Maybe VersionChat, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId, Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe LocalAlias, Maybe Preferences) :. (Maybe UTCTime, Maybe UTCTime) toMaybeGroupMember :: Int64 -> MaybeGroupMemberRow -> Maybe GroupMember toMaybeGroupMember userContactId ((Just groupMemberId, Just groupId, Just memberId, Just minVer, Just maxVer, Just memberRole, Just memberCategory, Just memberStatus, Just showMessages, memberBlocked) :. (invitedById, invitedByGroupMemberId, Just localDisplayName, memberContactId, Just memberContactProfileId, Just profileId, Just displayName, Just fullName, image, contactLink, Just localAlias, contactPreferences) :. (Just createdAt, Just updatedAt)) = Just $ toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, showMessages, memberBlocked) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, contactPreferences) :. (createdAt, updatedAt)) toMaybeGroupMember _ _ = Nothing -createGroupLink :: DB.Connection -> User -> GroupInfo -> ConnId -> ConnReqContact -> GroupLinkId -> GroupMemberRole -> SubscriptionMode -> ExceptT StoreError IO () -createGroupLink db User {userId} groupInfo@GroupInfo {groupId, localDisplayName} agentConnId cReq groupLinkId memberRole subMode = +createGroupLink :: DB.Connection -> User -> GroupInfo -> ConnId -> CreatedLinkContact -> GroupLinkId -> GroupMemberRole -> SubscriptionMode -> ExceptT StoreError IO () +createGroupLink db User {userId} groupInfo@GroupInfo {groupId, localDisplayName} agentConnId (CCLink cReq shortLink) groupLinkId memberRole subMode = checkConstraint (SEDuplicateGroupLink groupInfo) . liftIO $ do currentTs <- getCurrentTime DB.execute db - "INSERT INTO user_contact_links (user_id, group_id, group_link_id, local_display_name, conn_req_contact, group_link_member_role, auto_accept, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)" - (userId, groupId, groupLinkId, "group_link_" <> localDisplayName, cReq, memberRole, BI True, currentTs, currentTs) + "INSERT INTO user_contact_links (user_id, group_id, group_link_id, local_display_name, conn_req_contact, short_link_contact, group_link_member_role, auto_accept, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?)" + (userId, groupId, groupLinkId, "group_link_" <> localDisplayName, cReq, shortLink, memberRole, BI True, currentTs, currentTs) userContactLinkId <- insertedRowId db void $ createConnection_ db userId ConnUserContact (Just userContactLinkId) agentConnId ConnNew initialChatVersion chatInitialVRange Nothing Nothing Nothing 0 currentTs subMode PQSupportOff @@ -250,12 +251,12 @@ deleteGroupLink db User {userId} GroupInfo {groupId} = do (userId, groupId) DB.execute db "DELETE FROM user_contact_links WHERE user_id = ? AND group_id = ?" (userId, groupId) -getGroupLink :: DB.Connection -> User -> GroupInfo -> ExceptT StoreError IO (Int64, ConnReqContact, GroupMemberRole) +getGroupLink :: DB.Connection -> User -> GroupInfo -> ExceptT StoreError IO (Int64, CreatedLinkContact, GroupMemberRole) getGroupLink db User {userId} gInfo@GroupInfo {groupId} = ExceptT . firstRow groupLink (SEGroupLinkNotFound gInfo) $ - DB.query db "SELECT user_contact_link_id, conn_req_contact, group_link_member_role FROM user_contact_links WHERE user_id = ? AND group_id = ? LIMIT 1" (userId, groupId) + DB.query db "SELECT user_contact_link_id, conn_req_contact, short_link_contact, group_link_member_role FROM user_contact_links WHERE user_id = ? AND group_id = ? LIMIT 1" (userId, groupId) where - groupLink (linkId, cReq, mRole_) = (linkId, cReq, fromMaybe GRMember mRole_) + groupLink (linkId, cReq, shortLink, mRole_) = (linkId, CCLink cReq shortLink, fromMaybe GRMember mRole_) getGroupLinkId :: DB.Connection -> User -> GroupInfo -> IO (Maybe GroupLinkId) getGroupLinkId db User {userId} GroupInfo {groupId} = @@ -1683,8 +1684,9 @@ getGroupInfo db vr User {userId, userContactId} groupId = ExceptT $ do getGroupInfoByUserContactLinkConnReq :: DB.Connection -> VersionRangeChat -> User -> (ConnReqContact, ConnReqContact) -> IO (Maybe GroupInfo) getGroupInfoByUserContactLinkConnReq db vr user@User {userId} (cReqSchema1, cReqSchema2) = do + -- fmap join is to support group_id = NULL if non-group contact request is sent to this function (e.g., if client data is appended). groupId_ <- - maybeFirstRow fromOnly $ + fmap join . maybeFirstRow fromOnly $ DB.query db [sql| @@ -1695,6 +1697,26 @@ getGroupInfoByUserContactLinkConnReq db vr user@User {userId} (cReqSchema1, cReq (userId, cReqSchema1, cReqSchema2) maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getGroupInfo db vr user) groupId_ +getGroupInfoViaUserShortLink :: DB.Connection -> VersionRangeChat -> User -> ShortLinkContact -> IO (Maybe (ConnReqContact, GroupInfo)) +getGroupInfoViaUserShortLink db vr user@User {userId} shortLink = fmap eitherToMaybe $ runExceptT $ do + (cReq, groupId) <- ExceptT getConnReqGroup + (cReq,) <$> getGroupInfo db vr user groupId + where + getConnReqGroup = + firstRow' toConnReqGroupId (SEInternalError "group link not found") $ + DB.query + db + [sql| + SELECT conn_req_contact, group_id + FROM user_contact_links + WHERE user_id = ? AND short_link_contact = ? + |] + (userId, shortLink) + toConnReqGroupId = \case + -- cReq is "not null", group_id is nullable + (cReq, Just groupId) -> Right (cReq, groupId) + _ -> Left $ SEInternalError "no conn req or group ID" + getGroupInfoByGroupLinkHash :: DB.Connection -> VersionRangeChat -> User -> (ConnReqUriHash, ConnReqUriHash) -> IO (Maybe GroupInfo) getGroupInfoByGroupLinkHash db vr user@User {userId, userContactId} (groupLinkHash1, groupLinkHash2) = do groupId_ <- diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index ab7d866349..bdbb3fe67d 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -162,7 +162,7 @@ import Simplex.Chat.Store.NoteFolders import Simplex.Chat.Store.Shared import Simplex.Chat.Types import Simplex.Chat.Types.Shared -import Simplex.Messaging.Agent.Protocol (AgentMsgId, ConnId, MsgMeta (..), UserId) +import Simplex.Messaging.Agent.Protocol (AgentMsgId, ConnId, ConnShortLink, ConnectionMode (..), MsgMeta (..), UserId) import Simplex.Messaging.Agent.Store.AgentStore (firstRow, firstRow', maybeFirstRow) import Simplex.Messaging.Agent.Store.DB (BoolInt (..)) import qualified Simplex.Messaging.Agent.Store.DB as DB @@ -966,7 +966,7 @@ getContactConnectionChatPreviews_ db User {userId} pagination clq = case clq of [sql| SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, - custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at + custom_user_profile_id, conn_req_inv, short_link_inv, local_alias, created_at, updated_at FROM connections WHERE user_id = ? AND conn_type = ? @@ -982,7 +982,7 @@ getContactConnectionChatPreviews_ db User {userId} pagination clq = case clq of PTLast count -> DB.query db (query <> " ORDER BY updated_at DESC LIMIT ?") (params search :. Only count) PTAfter ts count -> DB.query db (query <> " AND updated_at > ? ORDER BY updated_at ASC LIMIT ?") (params search :. (ts, count)) PTBefore ts count -> DB.query db (query <> " AND updated_at < ? ORDER BY updated_at DESC LIMIT ?") (params search :. (ts, count)) - toPreview :: (Int64, ConnId, ConnStatus, Maybe ByteString, Maybe Int64, Maybe GroupLinkId, Maybe Int64, Maybe ConnReqInvitation, LocalAlias, UTCTime, UTCTime) -> AChatPreviewData + toPreview :: (Int64, ConnId, ConnStatus, Maybe ByteString, Maybe Int64, Maybe GroupLinkId, Maybe Int64, Maybe ConnReqInvitation, Maybe (ConnShortLink 'CMInvitation), LocalAlias, UTCTime, UTCTime) -> AChatPreviewData toPreview connRow = let conn@PendingContactConnection {updatedAt} = toPendingContactConnection connRow aChat = AChat SCTContactConnection $ Chat (ContactConnection conn) [] emptyChatStats diff --git a/src/Simplex/Chat/Store/Postgres/Migrations.hs b/src/Simplex/Chat/Store/Postgres/Migrations.hs index 285a952279..dc7202edc8 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations.hs +++ b/src/Simplex/Chat/Store/Postgres/Migrations.hs @@ -5,11 +5,13 @@ module Simplex.Chat.Store.Postgres.Migrations (migrations) where import Data.List (sortOn) import Data.Text (Text) import Simplex.Chat.Store.Postgres.Migrations.M20241220_initial +import Simplex.Chat.Store.Postgres.Migrations.M20250402_short_links import Simplex.Messaging.Agent.Store.Shared (Migration (..)) schemaMigrations :: [(String, Text, Maybe Text)] schemaMigrations = - [ ("20241220_initial", m20241220_initial, Nothing) + [ ("20241220_initial", m20241220_initial, Nothing), + ("20250402_short_links", m20250402_short_links, Just down_m20250402_short_links) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/M20250402_short_links.hs b/src/Simplex/Chat/Store/Postgres/Migrations/M20250402_short_links.hs new file mode 100644 index 0000000000..de4f699377 --- /dev/null +++ b/src/Simplex/Chat/Store/Postgres/Migrations/M20250402_short_links.hs @@ -0,0 +1,23 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Store.Postgres.Migrations.M20250402_short_links where + +import Data.Text (Text) +import qualified Data.Text as T +import Text.RawString.QQ (r) + +m20250402_short_links :: Text +m20250402_short_links = + T.pack + [r| +ALTER TABLE user_contact_links ADD COLUMN short_link_contact BYTEA; +ALTER TABLE connections ADD COLUMN short_link_inv BYTEA; +|] + +down_m20250402_short_links :: Text +down_m20250402_short_links = + T.pack + [r| +ALTER TABLE user_contact_links DROP COLUMN short_link_contact; +ALTER TABLE connections DROP COLUMN short_link_inv; +|] diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index bdd54c3f1e..a7dc154d9d 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -50,6 +50,7 @@ module Simplex.Chat.Store.Profiles getUserContactLinkById, getGroupLinkInfo, getUserContactLinkByConnReq, + getUserContactLinkViaShortLink, getContactWithoutConnViaAddress, updateUserAddressAutoAccept, getProtocolServers, @@ -100,7 +101,7 @@ import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Shared import Simplex.Chat.Types.UITheme import Simplex.Messaging.Agent.Env.SQLite (ServerRoles (..)) -import Simplex.Messaging.Agent.Protocol (ACorrId, ConnId, UserId) +import Simplex.Messaging.Agent.Protocol (ACorrId, ConnId, ConnectionLink (..), CreatedConnLink (..), UserId) import Simplex.Messaging.Agent.Store.AgentStore (firstRow, maybeFirstRow) import Simplex.Messaging.Agent.Store.DB (BoolInt (..)) import qualified Simplex.Messaging.Agent.Store.DB as DB @@ -326,11 +327,13 @@ setUserProfileContactLink db user@User {userId, profile = p@LocalProfile {profil SET contact_link = ?, updated_at = ? WHERE user_id = ? AND contact_profile_id = ? |] - (connReqContact_, ts, userId, profileId) - pure (user :: User) {profile = p {contactLink = connReqContact_}} + (contactLink, ts, userId, profileId) + pure (user :: User) {profile = p {contactLink}} where - connReqContact_ = case ucl_ of - Just UserContactLink {connReqContact} -> Just connReqContact + -- TODO [short links] this should be replaced with short links once they are supported by all clients. + -- Or, maybe, we want to allow both, when both are optional. + contactLink = case ucl_ of + Just UserContactLink {connLinkContact = CCLink cReq _} -> Just $ CLFull cReq _ -> Nothing -- only used in tests @@ -346,17 +349,17 @@ getUserContactProfiles db User {userId} = |] (Only userId) where - toContactProfile :: (ContactName, Text, Maybe ImageData, Maybe ConnReqContact, Maybe Preferences) -> Profile + toContactProfile :: (ContactName, Text, Maybe ImageData, Maybe ConnLinkContact, Maybe Preferences) -> Profile toContactProfile (displayName, fullName, image, contactLink, preferences) = Profile {displayName, fullName, image, contactLink, preferences} -createUserContactLink :: DB.Connection -> User -> ConnId -> ConnReqContact -> SubscriptionMode -> ExceptT StoreError IO () -createUserContactLink db User {userId} agentConnId cReq subMode = +createUserContactLink :: DB.Connection -> User -> ConnId -> CreatedLinkContact -> SubscriptionMode -> ExceptT StoreError IO () +createUserContactLink db User {userId} agentConnId (CCLink cReq shortLink) subMode = checkConstraint SEDuplicateContactLink . liftIO $ do currentTs <- getCurrentTime DB.execute db - "INSERT INTO user_contact_links (user_id, conn_req_contact, created_at, updated_at) VALUES (?,?,?,?)" - (userId, cReq, currentTs, currentTs) + "INSERT INTO user_contact_links (user_id, conn_req_contact, short_link_contact, created_at, updated_at) VALUES (?,?,?,?,?)" + (userId, cReq, shortLink, currentTs, currentTs) userContactLinkId <- insertedRowId db void $ createConnection_ db userId ConnUserContact (Just userContactLinkId) agentConnId ConnNew initialChatVersion chatInitialVRange Nothing Nothing Nothing 0 currentTs subMode CR.PQSupportOff @@ -450,7 +453,7 @@ data UserMsgReceiptSettings = UserMsgReceiptSettings deriving (Show) data UserContactLink = UserContactLink - { connReqContact :: ConnReqContact, + { connLinkContact :: CreatedLinkContact, autoAccept :: Maybe AutoAccept } deriving (Show) @@ -472,22 +475,15 @@ $(J.deriveJSON defaultJSON ''AutoAccept) $(J.deriveJSON defaultJSON ''UserContactLink) -toUserContactLink :: (ConnReqContact, BoolInt, BoolInt, BoolInt, Maybe MsgContent) -> UserContactLink -toUserContactLink (connReq, BI autoAccept, BI businessAddress, BI acceptIncognito, autoReply) = - UserContactLink connReq $ +toUserContactLink :: (ConnReqContact, Maybe ShortLinkContact, BoolInt, BoolInt, BoolInt, Maybe MsgContent) -> UserContactLink +toUserContactLink (connReq, shortLink, BI autoAccept, BI businessAddress, BI acceptIncognito, autoReply) = + UserContactLink (CCLink connReq shortLink) $ if autoAccept then Just AutoAccept {businessAddress, acceptIncognito, autoReply} else Nothing getUserAddress :: DB.Connection -> User -> ExceptT StoreError IO UserContactLink getUserAddress db User {userId} = ExceptT . firstRow toUserContactLink SEUserContactLinkNotFound $ - DB.query - db - [sql| - SELECT conn_req_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content - FROM user_contact_links - WHERE user_id = ? AND local_display_name = '' AND group_id IS NULL - |] - (Only userId) + DB.query db (userContactLinkQuery <> " WHERE user_id = ? AND local_display_name = '' AND group_id IS NULL") (Only userId) getUserContactLinkById :: DB.Connection -> UserId -> Int64 -> ExceptT StoreError IO (UserContactLink, Maybe GroupLinkInfo) getUserContactLinkById db userId userContactLinkId = @@ -495,7 +491,7 @@ getUserContactLinkById db userId userContactLinkId = DB.query db [sql| - SELECT conn_req_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content, group_id, group_link_member_role + SELECT conn_req_contact, short_link_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content, group_id, group_link_member_role FROM user_contact_links WHERE user_id = ? AND user_contact_link_id = ? |] @@ -521,14 +517,19 @@ getGroupLinkInfo db userId groupId = getUserContactLinkByConnReq :: DB.Connection -> User -> (ConnReqContact, ConnReqContact) -> IO (Maybe UserContactLink) getUserContactLinkByConnReq db User {userId} (cReqSchema1, cReqSchema2) = maybeFirstRow toUserContactLink $ - DB.query - db - [sql| - SELECT conn_req_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content - FROM user_contact_links - WHERE user_id = ? AND conn_req_contact IN (?,?) - |] - (userId, cReqSchema1, cReqSchema2) + DB.query db (userContactLinkQuery <> " WHERE user_id = ? AND conn_req_contact IN (?,?)") (userId, cReqSchema1, cReqSchema2) + +getUserContactLinkViaShortLink :: DB.Connection -> User -> ShortLinkContact -> IO (Maybe UserContactLink) +getUserContactLinkViaShortLink db User {userId} shortLink = + maybeFirstRow toUserContactLink $ + DB.query db (userContactLinkQuery <> " WHERE user_id = ? AND short_link_contact = ?") (userId, shortLink) + +userContactLinkQuery :: Query +userContactLinkQuery = + [sql| + SELECT conn_req_contact, short_link_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content + FROM user_contact_links + |] getContactWithoutConnViaAddress :: DB.Connection -> VersionRangeChat -> User -> (ConnReqContact, ConnReqContact) -> IO (Maybe Contact) getContactWithoutConnViaAddress db vr user@User {userId} (cReqSchema1, cReqSchema2) = do diff --git a/src/Simplex/Chat/Store/SQLite/Migrations.hs b/src/Simplex/Chat/Store/SQLite/Migrations.hs index 5865cd180e..81253c5b87 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations.hs @@ -128,6 +128,7 @@ import Simplex.Chat.Store.SQLite.Migrations.M20250122_chat_items_include_in_hist import Simplex.Chat.Store.SQLite.Migrations.M20250126_mentions import Simplex.Chat.Store.SQLite.Migrations.M20250129_delete_unused_contacts import Simplex.Chat.Store.SQLite.Migrations.M20250130_indexes +import Simplex.Chat.Store.SQLite.Migrations.M20250402_short_links import Simplex.Messaging.Agent.Store.Shared (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -255,7 +256,8 @@ schemaMigrations = ("20250122_chat_items_include_in_history", m20250122_chat_items_include_in_history, Just down_m20250122_chat_items_include_in_history), ("20250126_mentions", m20250126_mentions, Just down_m20250126_mentions), ("20250129_delete_unused_contacts", m20250129_delete_unused_contacts, Just down_m20250129_delete_unused_contacts), - ("20250130_indexes", m20250130_indexes, Just down_m20250130_indexes) + ("20250130_indexes", m20250130_indexes, Just down_m20250130_indexes), + ("20250402_short_links", m20250402_short_links, Just down_m20250402_short_links) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20250402_short_links.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20250402_short_links.hs new file mode 100644 index 0000000000..62637c0782 --- /dev/null +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20250402_short_links.hs @@ -0,0 +1,23 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Store.SQLite.Migrations.M20250402_short_links where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20250402_short_links :: Query +m20250402_short_links = + [sql| +ALTER TABLE user_contact_links ADD COLUMN short_link_contact BLOB; +ALTER TABLE connections ADD COLUMN short_link_inv BLOB; +ALTER TABLE connections ADD COLUMN via_short_link_contact BLOB; + +|] + +down_m20250402_short_links :: Query +down_m20250402_short_links = + [sql| +ALTER TABLE user_contact_links DROP COLUMN short_link_contact; +ALTER TABLE connections DROP COLUMN short_link_inv; +ALTER TABLE connections DROP COLUMN via_short_link_contact; +|] diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt index 31bc2feb81..e6a567035e 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt @@ -443,6 +443,22 @@ Query: Plan: SEARCH connections USING PRIMARY KEY (conn_id=?) +Query: + SELECT link_id, snd_private_key + FROM inv_short_links + WHERE host = ? AND port = ? AND snd_id = ? + +Plan: +SEARCH inv_short_links USING INDEX idx_inv_short_links_link_id (host=? AND port=?) + +Query: + SELECT link_key, snd_private_key, snd_id + FROM inv_short_links + WHERE host = ? AND port = ? AND link_id = ? + +Plan: +SEARCH inv_short_links USING INDEX idx_inv_short_links_link_id (host=? AND port=? AND link_id=?) + Query: SELECT s.internal_id, m.msg_type, s.internal_hash, s.rcpt_internal_id, s.rcpt_status FROM snd_messages s @@ -466,6 +482,19 @@ Query: Plan: +Query: + INSERT INTO inv_short_links + (host, port, server_key_hash, link_id, link_key, snd_private_key, snd_id) + VALUES (?,?,?,?,?,?,?) + ON CONFLICT (host, port, link_id) + DO UPDATE SET + server_key_hash = EXCLUDED.server_key_hash, + link_key = EXCLUDED.link_key, + snd_private_key = EXCLUDED.snd_private_key, + snd_id = EXCLUDED.snd_id + +Plan: + Query: INSERT INTO messages (conn_id, internal_id, internal_ts, internal_rcv_id, internal_snd_id, msg_type, msg_flags, msg_body, pq_encryption) @@ -524,7 +553,10 @@ SEARCH messages USING COVERING INDEX idx_messages_conn_id_internal_rcv_id (conn_ Query: INSERT INTO rcv_queues - (host, port, rcv_id, conn_id, rcv_private_key, rcv_dh_secret, e2e_priv_key, e2e_dh_secret, snd_id, snd_secure, status, rcv_queue_id, rcv_primary, replace_rcv_queue_id, smp_client_version, server_key_hash) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?); + ( host, port, rcv_id, conn_id, rcv_private_key, rcv_dh_secret, e2e_priv_key, e2e_dh_secret, + snd_id, queue_mode, status, rcv_queue_id, rcv_primary, replace_rcv_queue_id, smp_client_version, server_key_hash, + link_id, link_key, link_priv_sig_key, link_enc_fixed_data + ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?); Plan: @@ -546,14 +578,14 @@ SEARCH messages USING COVERING INDEX idx_messages_conn_id_internal_snd_id (conn_ Query: INSERT INTO snd_queues - (host, port, snd_id, snd_secure, conn_id, snd_public_key, snd_private_key, e2e_pub_key, e2e_dh_secret, + (host, port, snd_id, queue_mode, conn_id, snd_public_key, snd_private_key, e2e_pub_key, e2e_dh_secret, status, snd_queue_id, snd_primary, replace_snd_queue_id, smp_client_version, server_key_hash) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ON CONFLICT (host, port, snd_id) DO UPDATE SET host=EXCLUDED.host, port=EXCLUDED.port, snd_id=EXCLUDED.snd_id, - snd_secure=EXCLUDED.snd_secure, + queue_mode=EXCLUDED.queue_mode, conn_id=EXCLUDED.conn_id, snd_public_key=EXCLUDED.snd_public_key, snd_private_key=EXCLUDED.snd_private_key, @@ -631,6 +663,14 @@ Query: Plan: SEARCH connections USING PRIMARY KEY (conn_id=?) +Query: + UPDATE inv_short_links + SET snd_id = ? + WHERE host = ? AND port = ? AND link_id = ? + +Plan: +SEARCH inv_short_links USING INDEX idx_inv_short_links_link_id (host=? AND port=? AND link_id=?) + Query: UPDATE ratchets SET x3dh_priv_key_1 = ?, x3dh_priv_key_2 = ?, pq_priv_kem = ? @@ -691,7 +731,7 @@ SEARCH snd_queues USING PRIMARY KEY (host=? AND port=? AND snd_id=?) Query: SELECT - c.user_id, COALESCE(q.server_key_hash, s.key_hash), q.conn_id, q.host, q.port, q.snd_id, q.snd_secure, + c.user_id, COALESCE(q.server_key_hash, s.key_hash), q.conn_id, q.host, q.port, q.snd_id, q.queue_mode, q.snd_public_key, q.snd_private_key, q.e2e_pub_key, q.e2e_dh_secret, q.status, q.snd_queue_id, q.snd_primary, q.replace_snd_queue_id, q.switch_status, q.smp_client_version FROM snd_queues q @@ -705,9 +745,10 @@ SEARCH s USING PRIMARY KEY (host=? AND port=?) Query: SELECT c.user_id, COALESCE(q.server_key_hash, s.key_hash), q.conn_id, q.host, q.port, q.rcv_id, q.rcv_private_key, q.rcv_dh_secret, - q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.snd_secure, q.status, + q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.queue_mode, q.status, q.rcv_queue_id, q.rcv_primary, q.replace_rcv_queue_id, q.switch_status, q.smp_client_version, q.delete_errors, - q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret + q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret, + q.link_id, q.link_key, q.link_priv_sig_key, q.link_enc_fixed_data FROM rcv_queues q JOIN servers s ON q.host = s.host AND q.port = s.port JOIN connections c ON q.conn_id = c.conn_id @@ -719,9 +760,10 @@ SEARCH s USING PRIMARY KEY (host=? AND port=?) Query: SELECT c.user_id, COALESCE(q.server_key_hash, s.key_hash), q.conn_id, q.host, q.port, q.rcv_id, q.rcv_private_key, q.rcv_dh_secret, - q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.snd_secure, q.status, + q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.queue_mode, q.status, q.rcv_queue_id, q.rcv_primary, q.replace_rcv_queue_id, q.switch_status, q.smp_client_version, q.delete_errors, - q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret + q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret, + q.link_id, q.link_key, q.link_priv_sig_key, q.link_enc_fixed_data FROM rcv_queues q JOIN servers s ON q.host = s.host AND q.port = s.port JOIN connections c ON q.conn_id = c.conn_id @@ -733,9 +775,10 @@ SEARCH c USING PRIMARY KEY (conn_id=?) Query: SELECT c.user_id, COALESCE(q.server_key_hash, s.key_hash), q.conn_id, q.host, q.port, q.rcv_id, q.rcv_private_key, q.rcv_dh_secret, - q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.snd_secure, q.status, + q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.queue_mode, q.status, q.rcv_queue_id, q.rcv_primary, q.replace_rcv_queue_id, q.switch_status, q.smp_client_version, q.delete_errors, - q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret + q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret, + q.link_id, q.link_key, q.link_priv_sig_key, q.link_enc_fixed_data FROM rcv_queues q JOIN servers s ON q.host = s.host AND q.port = s.port JOIN connections c ON q.conn_id = c.conn_id @@ -747,9 +790,10 @@ SEARCH c USING PRIMARY KEY (conn_id=?) Query: SELECT c.user_id, COALESCE(q.server_key_hash, s.key_hash), q.conn_id, q.host, q.port, q.rcv_id, q.rcv_private_key, q.rcv_dh_secret, - q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.snd_secure, q.status, + q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.queue_mode, q.status, q.rcv_queue_id, q.rcv_primary, q.replace_rcv_queue_id, q.switch_status, q.smp_client_version, q.delete_errors, - q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret + q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret, + q.link_id, q.link_key, q.link_priv_sig_key, q.link_enc_fixed_data FROM rcv_queues q JOIN servers s ON q.host = s.host AND q.port = s.port JOIN connections c ON q.conn_id = c.conn_id @@ -761,9 +805,10 @@ SEARCH s USING PRIMARY KEY (host=? AND port=?) Query: SELECT c.user_id, COALESCE(q.server_key_hash, s.key_hash), q.conn_id, q.host, q.port, q.rcv_id, q.rcv_private_key, q.rcv_dh_secret, - q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.snd_secure, q.status, + q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.queue_mode, q.status, q.rcv_queue_id, q.rcv_primary, q.replace_rcv_queue_id, q.switch_status, q.smp_client_version, q.delete_errors, - q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret + q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret, + q.link_id, q.link_key, q.link_priv_sig_key, q.link_enc_fixed_data FROM rcv_queues q JOIN servers s ON q.host = s.host AND q.port = s.port JOIN connections c ON q.conn_id = c.conn_id @@ -799,6 +844,10 @@ Query: DELETE FROM deleted_snd_chunk_replicas WHERE deleted_snd_chunk_replica_id Plan: SEARCH deleted_snd_chunk_replicas USING INTEGER PRIMARY KEY (rowid=?) +Query: DELETE FROM inv_short_links WHERE host = ? AND port = ? AND link_id = ? +Plan: +SEARCH inv_short_links USING INDEX idx_inv_short_links_link_id (host=? AND port=? AND link_id=?) + Query: DELETE FROM messages WHERE conn_id = ? AND internal_id = ?; Plan: SEARCH messages USING PRIMARY KEY (conn_id=? AND internal_id=?) diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt index 6f1d243cf3..e3eff0f6f1 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt @@ -497,6 +497,14 @@ Plan: SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=?) USE TEMP B-TREE FOR ORDER BY +Query: + SELECT conn_req_contact, group_id + FROM user_contact_links + WHERE user_id = ? AND short_link_contact = ? + +Plan: +SEARCH user_contact_links USING INDEX sqlite_autoindex_user_contact_links_1 (user_id=?) + Query: SELECT conn_req_contact, group_id FROM user_contact_links @@ -505,6 +513,14 @@ Query: Plan: SEARCH user_contact_links USING INTEGER PRIMARY KEY (rowid=?) +Query: + SELECT conn_req_inv, agent_conn_id + FROM connections + WHERE user_id = ? AND short_link_inv = ? LIMIT 1 + +Plan: +SEARCH connections USING INDEX idx_connections_updated_at (user_id=?) + Query: SELECT connection_id, agent_conn_id, conn_level, via_contact, via_user_contact_link, via_group_link, group_link_id, custom_user_profile_id, conn_status, conn_type, contact_conn_initiated, local_alias, contact_id, group_member_id, snd_file_id, rcv_file_id, user_contact_link_id, @@ -1347,7 +1363,7 @@ SCAN cc Query: SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, - custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at + custom_user_profile_id, conn_req_inv, short_link_inv, local_alias, created_at, updated_at FROM connections WHERE user_id = ? AND conn_type = ? @@ -1364,7 +1380,7 @@ SEARCH connections USING INDEX idx_connections_updated_at (user_id=? AND updated Query: SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, - custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at + custom_user_profile_id, conn_req_inv, short_link_inv, local_alias, created_at, updated_at FROM connections WHERE user_id = ? AND conn_type = ? @@ -1381,7 +1397,7 @@ SEARCH connections USING INDEX idx_connections_updated_at (user_id=? AND updated Query: SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, - custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at + custom_user_profile_id, conn_req_inv, short_link_inv, local_alias, created_at, updated_at FROM connections WHERE user_id = ? AND conn_type = ? @@ -2935,23 +2951,7 @@ Plan: SEARCH commands USING INTEGER PRIMARY KEY (rowid=?) Query: - SELECT conn_req_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content - FROM user_contact_links - WHERE user_id = ? AND conn_req_contact IN (?,?) - -Plan: -SEARCH user_contact_links USING INDEX sqlite_autoindex_user_contact_links_1 (user_id=?) - -Query: - SELECT conn_req_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content - FROM user_contact_links - WHERE user_id = ? AND local_display_name = '' AND group_id IS NULL - -Plan: -SEARCH user_contact_links USING INDEX sqlite_autoindex_user_contact_links_1 (user_id=? AND local_display_name=?) - -Query: - SELECT conn_req_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content, group_id, group_link_member_role + SELECT conn_req_contact, short_link_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content, group_id, group_link_member_role FROM user_contact_links WHERE user_id = ? AND user_contact_link_id = ? @@ -2970,7 +2970,7 @@ Plan: SEARCH connections USING INTEGER PRIMARY KEY (rowid=?) Query: - SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at + SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, custom_user_profile_id, conn_req_inv, short_link_inv, local_alias, created_at, updated_at FROM connections WHERE user_id = ? AND conn_type = ? @@ -2980,7 +2980,7 @@ Plan: SEARCH connections USING INDEX idx_connections_updated_at (user_id=?) Query: - SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at + SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, custom_user_profile_id, conn_req_inv, short_link_inv, local_alias, created_at, updated_at FROM connections WHERE user_id = ? AND connection_id = ? @@ -3944,9 +3944,9 @@ Plan: Query: INSERT INTO connections - (user_id, agent_conn_id, conn_req_inv, conn_status, conn_type, contact_conn_initiated, custom_user_profile_id, + (user_id, agent_conn_id, conn_req_inv, short_link_inv, conn_status, conn_type, contact_conn_initiated, custom_user_profile_id, created_at, updated_at, to_subscribe, conn_chat_version, pq_support, pq_encryption) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?) Plan: @@ -3962,9 +3962,9 @@ Plan: Query: INSERT INTO connections ( user_id, agent_conn_id, conn_status, conn_type, contact_conn_initiated, - via_contact_uri_hash, xcontact_id, custom_user_profile_id, via_group_link, group_link_id, + via_contact_uri_hash, via_short_link_contact, xcontact_id, custom_user_profile_id, via_group_link, group_link_id, created_at, updated_at, to_subscribe, conn_chat_version, pq_support, pq_encryption - ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) + ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) Plan: @@ -4650,6 +4650,27 @@ SEARCH c USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN CORRELATED SCALAR SUBQUERY 1 SEARCH cc USING COVERING INDEX idx_connections_group_member (user_id=? AND group_member_id=?) +Query: + SELECT conn_req_contact, short_link_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content + FROM user_contact_links + WHERE user_id = ? AND conn_req_contact IN (?,?) +Plan: +SEARCH user_contact_links USING INDEX sqlite_autoindex_user_contact_links_1 (user_id=?) + +Query: + SELECT conn_req_contact, short_link_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content + FROM user_contact_links + WHERE user_id = ? AND local_display_name = '' AND group_id IS NULL +Plan: +SEARCH user_contact_links USING INDEX sqlite_autoindex_user_contact_links_1 (user_id=? AND local_display_name=?) + +Query: + SELECT conn_req_contact, short_link_contact, auto_accept, business_address, auto_accept_incognito, auto_reply_msg_content + FROM user_contact_links + WHERE user_id = ? AND short_link_contact = ? +Plan: +SEARCH user_contact_links USING INDEX sqlite_autoindex_user_contact_links_1 (user_id=?) + Query: SELECT f.file_id, f.ci_file_status, f.file_path FROM chat_items i @@ -5409,10 +5430,10 @@ SEARCH connections USING INTEGER PRIMARY KEY (rowid=?) Query: INSERT INTO temp_conn_ids (conn_id) VALUES (?) Plan: -Query: INSERT INTO user_contact_links (user_id, conn_req_contact, created_at, updated_at) VALUES (?,?,?,?) +Query: INSERT INTO user_contact_links (user_id, conn_req_contact, short_link_contact, created_at, updated_at) VALUES (?,?,?,?,?) Plan: -Query: INSERT INTO user_contact_links (user_id, group_id, group_link_id, local_display_name, conn_req_contact, group_link_member_role, auto_accept, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?) +Query: INSERT INTO user_contact_links (user_id, group_id, group_link_id, local_display_name, conn_req_contact, short_link_contact, group_link_member_role, auto_accept, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?) Plan: Query: INSERT INTO users (agent_user_id, local_display_name, active_user, active_order, contact_id, show_ntfs, send_rcpts_contacts, send_rcpts_small_groups, created_at, updated_at) VALUES (?,?,?,?,0,?,?,?,?,?) @@ -5637,7 +5658,7 @@ Query: SELECT user_contact_link_id FROM contact_requests WHERE contact_request_i Plan: SEARCH contact_requests USING INTEGER PRIMARY KEY (rowid=?) -Query: SELECT user_contact_link_id, conn_req_contact, group_link_member_role FROM user_contact_links WHERE user_id = ? AND group_id = ? LIMIT 1 +Query: SELECT user_contact_link_id, conn_req_contact, short_link_contact, group_link_member_role FROM user_contact_links WHERE user_id = ? AND group_id = ? LIMIT 1 Plan: SEARCH user_contact_links USING INDEX idx_user_contact_links_group_id (group_id=?) @@ -5685,7 +5706,7 @@ Query: UPDATE connections SET conn_status = ?, updated_at = ? WHERE connection_i Plan: SEARCH connections USING INTEGER PRIMARY KEY (rowid=?) -Query: UPDATE connections SET conn_status = ?, updated_at = ?, conn_req_inv = NULL WHERE connection_id = ? +Query: UPDATE connections SET conn_status = ?, updated_at = ?, conn_req_inv = NULL, short_link_inv = NULL WHERE connection_id = ? Plan: SEARCH connections USING INTEGER PRIMARY KEY (rowid=?) diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql index f4cf2bccb8..33b800f4ef 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql @@ -297,6 +297,8 @@ CREATE TABLE connections( pq_snd_enabled INTEGER, pq_rcv_enabled INTEGER, quota_err_counter INTEGER NOT NULL DEFAULT 0, + short_link_inv BLOB, + via_short_link_contact BLOB, FOREIGN KEY(snd_file_id, connection_id) REFERENCES snd_files(file_id, connection_id) ON DELETE CASCADE @@ -316,6 +318,7 @@ CREATE TABLE user_contact_links( group_link_id BLOB, group_link_member_role TEXT NULL, business_address INTEGER DEFAULT 0, + short_link_contact BLOB, UNIQUE(user_id, local_display_name) ); CREATE TABLE contact_requests( diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index a556fd0a49..c681180759 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-} +{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE LambdaCase #-} @@ -35,7 +36,7 @@ import Simplex.Chat.Types import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Shared import Simplex.Chat.Types.UITheme -import Simplex.Messaging.Agent.Protocol (ConnId, UserId) +import Simplex.Messaging.Agent.Protocol (ConnId, ConnShortLink, ConnectionMode (..), CreatedConnLink (..), UserId) import Simplex.Messaging.Agent.Store.AgentStore (firstRow, maybeFirstRow) import Simplex.Messaging.Agent.Store.DB (BoolInt (..)) import qualified Simplex.Messaging.Agent.Store.DB as DB @@ -416,7 +417,7 @@ deleteUnusedIncognitoProfileById_ db User {userId} profileId = |] (userId, profileId, userId, profileId, userId, profileId) -type ContactRow' = (ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, BoolInt, ContactStatus) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime) :. (Maybe GroupMemberId, BoolInt, Maybe UIThemeEntityOverrides, BoolInt, Maybe CustomData, Maybe Int64) +type ContactRow' = (ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe ImageData, Maybe ConnLinkContact, LocalAlias, BoolInt, ContactStatus) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime) :. (Maybe GroupMemberId, BoolInt, Maybe UIThemeEntityOverrides, BoolInt, Maybe CustomData, Maybe Int64) type ContactRow = Only ContactId :. ContactRow' @@ -441,10 +442,10 @@ getProfileById db userId profileId = |] (userId, profileId) where - toProfile :: (ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences) -> LocalProfile + toProfile :: (ContactName, Text, Maybe ImageData, Maybe ConnLinkContact, LocalAlias, Maybe Preferences) -> LocalProfile toProfile (displayName, fullName, image, contactLink, localAlias, preferences) = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} -type ContactRequestRow = (Int64, ContactName, AgentInvId, Maybe ContactId, Int64, AgentConnId, Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact) :. (Maybe XContactId, PQSupport, Maybe Preferences, UTCTime, UTCTime, VersionChat, VersionChat) +type ContactRequestRow = (Int64, ContactName, AgentInvId, Maybe ContactId, Int64, AgentConnId, Int64, ContactName, Text, Maybe ImageData, Maybe ConnLinkContact) :. (Maybe XContactId, PQSupport, Maybe Preferences, UTCTime, UTCTime, VersionChat, VersionChat) toContactRequest :: ContactRequestRow -> UserContactRequest toContactRequest ((contactRequestId, localDisplayName, agentInvitationId, contactId_, userContactLinkId, agentContactConnId, profileId, displayName, fullName, image, contactLink) :. (xContactId, pqSupport, preferences, createdAt, updatedAt, minVer, maxVer)) = do @@ -462,7 +463,7 @@ userQuery = JOIN contact_profiles ucp ON ucp.contact_profile_id = uct.contact_profile_id |] -toUser :: (UserId, UserId, ContactId, ProfileId, BoolInt, Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, Maybe Preferences) :. (BoolInt, BoolInt, BoolInt, Maybe B64UrlByteString, Maybe B64UrlByteString, Maybe UTCTime, Maybe UIThemeEntityOverrides) -> User +toUser :: (UserId, UserId, ContactId, ProfileId, BoolInt, Int64, ContactName, Text, Maybe ImageData, Maybe ConnLinkContact, Maybe Preferences) :. (BoolInt, BoolInt, BoolInt, Maybe B64UrlByteString, Maybe B64UrlByteString, Maybe UTCTime, Maybe UIThemeEntityOverrides) -> User toUser ((userId, auId, userContactId, profileId, BI activeUser, activeOrder, displayName, fullName, image, contactLink, userPreferences) :. (BI showNtfs, BI sendRcptsContacts, BI sendRcptsSmallGroups, viewPwdHash_, viewPwdSalt_, userMemberProfileUpdatedAt, uiThemes)) = User {userId, agentUserId = AgentUserId auId, userContactId, localDisplayName = displayName, profile, activeUser, activeOrder, fullPreferences, showNtfs, sendRcptsContacts, sendRcptsSmallGroups, viewPwdHash, userMemberProfileUpdatedAt, uiThemes} where @@ -470,9 +471,10 @@ toUser ((userId, auId, userContactId, profileId, BI activeUser, activeOrder, dis fullPreferences = mergePreferences Nothing userPreferences viewPwdHash = UserPwdHash <$> viewPwdHash_ <*> viewPwdSalt_ -toPendingContactConnection :: (Int64, ConnId, ConnStatus, Maybe ByteString, Maybe Int64, Maybe GroupLinkId, Maybe Int64, Maybe ConnReqInvitation, LocalAlias, UTCTime, UTCTime) -> PendingContactConnection -toPendingContactConnection (pccConnId, acId, pccConnStatus, connReqHash, viaUserContactLink, groupLinkId, customUserProfileId, connReqInv, localAlias, createdAt, updatedAt) = - PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = isJust connReqHash, viaUserContactLink, groupLinkId, customUserProfileId, connReqInv, localAlias, createdAt, updatedAt} +toPendingContactConnection :: (Int64, ConnId, ConnStatus, Maybe ByteString, Maybe Int64, Maybe GroupLinkId, Maybe Int64, Maybe ConnReqInvitation, Maybe (ConnShortLink 'CMInvitation), LocalAlias, UTCTime, UTCTime) -> PendingContactConnection +toPendingContactConnection (pccConnId, acId, pccConnStatus, connReqHash, viaUserContactLink, groupLinkId, customUserProfileId, connReqInv, shortLinkInv, localAlias, createdAt, updatedAt) = + let connLinkInv = (`CCLink` shortLinkInv) <$> connReqInv + in PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = isJust connReqHash, viaUserContactLink, groupLinkId, customUserProfileId, connLinkInv, localAlias, createdAt, updatedAt} getConnReqInv :: DB.Connection -> Int64 -> ExceptT StoreError IO ConnReqInvitation getConnReqInv db connId = @@ -579,7 +581,7 @@ type BusinessChatInfoRow = (Maybe BusinessChatType, Maybe MemberId, Maybe Member type GroupInfoRow = (Int64, GroupName, GroupName, Text, Text, Maybe Text, Maybe ImageData, Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. BusinessChatInfoRow :. (Maybe UIThemeEntityOverrides, Maybe CustomData, Maybe Int64) :. GroupMemberRow -type GroupMemberRow = (Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences) :. (UTCTime, UTCTime) +type GroupMemberRow = (Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnLinkContact, LocalAlias, Maybe Preferences) :. (UTCTime, UTCTime) toGroupInfo :: VersionRangeChat -> Int64 -> [ChatTagId] -> GroupInfoRow -> GroupInfo toGroupInfo vr userContactId chatTags ((groupId, localDisplayName, displayName, fullName, localAlias, description, image, enableNtfs_, sendRcpts, BI favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. businessRow :. (uiThemes, customData, chatItemTTL) :. userMemberRow) = diff --git a/src/Simplex/Chat/Terminal.hs b/src/Simplex/Chat/Terminal.hs index 958cd9d75f..e432343839 100644 --- a/src/Simplex/Chat/Terminal.hs +++ b/src/Simplex/Chat/Terminal.hs @@ -9,12 +9,13 @@ module Simplex.Chat.Terminal where import Control.Monad import qualified Data.List.NonEmpty as L -import Simplex.Chat (defaultChatConfig, operatorSimpleXChat) +import Simplex.Chat (defaultChatConfig) import Simplex.Chat.Controller import Simplex.Chat.Core import Simplex.Chat.Help (chatWelcome) import Simplex.Chat.Library.Commands (_defaultNtfServers) import Simplex.Chat.Operators +import Simplex.Chat.Operators.Presets (operatorSimpleXChat) import Simplex.Chat.Options import Simplex.Chat.Terminal.Input import Simplex.Chat.Terminal.Output diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index 2bc9e725e5..9d875f5bf4 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -51,7 +51,7 @@ import Simplex.Chat.Types.UITheme import Simplex.Chat.Types.Util import Simplex.FileTransfer.Description (FileDigest) import Simplex.FileTransfer.Types (RcvFileId, SndFileId) -import Simplex.Messaging.Agent.Protocol (ACorrId, AEventTag (..), AEvtTag (..), ConnId, ConnectionMode (..), ConnectionRequestUri, InvitationId, SAEntity (..), UserId) +import Simplex.Messaging.Agent.Protocol (ACorrId, AEventTag (..), AEvtTag (..), ConnId, ConnShortLink, ConnectionLink, ConnectionMode (..), ConnectionRequestUri, CreatedConnLink, InvitationId, SAEntity (..), UserId) import Simplex.Messaging.Agent.Store.DB (Binary (..), blobFieldDecoder, fromTextField_) import Simplex.Messaging.Crypto.File (CryptoFileArgs (..)) import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport, pattern PQEncOff) @@ -220,6 +220,8 @@ contactConnId c = aConnId <$> contactConn c type IncognitoEnabled = Bool +type CreateShortLink = Bool + contactConnIncognito :: Contact -> IncognitoEnabled contactConnIncognito = maybe False connIncognito . contactConn @@ -559,7 +561,7 @@ data Profile = Profile { displayName :: ContactName, fullName :: Text, image :: Maybe ImageData, - contactLink :: Maybe ConnReqContact, + contactLink :: Maybe ConnLinkContact, preferences :: Maybe Preferences -- fields that should not be read into this data type to prevent sending them as part of profile to contacts: -- - contact_profile_id @@ -592,7 +594,7 @@ data LocalProfile = LocalProfile displayName :: ContactName, fullName :: Text, image :: Maybe ImageData, - contactLink :: Maybe ConnReqContact, + contactLink :: Maybe ConnLinkContact, preferences :: Maybe Preferences, localAlias :: LocalAlias } @@ -1407,6 +1409,14 @@ type ConnReqInvitation = ConnectionRequestUri 'CMInvitation type ConnReqContact = ConnectionRequestUri 'CMContact +type CreatedLinkInvitation = CreatedConnLink 'CMInvitation + +type CreatedLinkContact = CreatedConnLink 'CMContact + +type ConnLinkContact = ConnectionLink 'CMContact + +type ShortLinkContact = ConnShortLink 'CMContact + data Connection = Connection { connId :: Int64, agentConnId :: AgentConnId, @@ -1484,7 +1494,7 @@ data PendingContactConnection = PendingContactConnection viaUserContactLink :: Maybe Int64, groupLinkId :: Maybe GroupLinkId, customUserProfileId :: Maybe Int64, - connReqInv :: Maybe ConnReqInvitation, + connLinkInv :: Maybe CreatedLinkInvitation, localAlias :: Text, createdAt :: UTCTime, updatedAt :: UTCTime diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 5053f24ec6..f145262862 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -90,7 +90,7 @@ serializeChatResponse :: (Maybe RemoteHostId, Maybe User) -> CurrentTime -> Time serializeChatResponse user_ ts tz remoteHost_ = unlines . map unStyle . responseToView user_ defaultChatConfig False ts tz remoteHost_ responseToView :: (Maybe RemoteHostId, Maybe User) -> ChatConfig -> Bool -> CurrentTime -> TimeZone -> Maybe RemoteHostId -> ChatResponse -> [StyledString] -responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showReceipts, testView} liveItems ts tz outputRH = \case +responseToView hu@(currentRH, user_) cfg@ChatConfig {logLevel, showReactions, showReceipts, testView} liveItems ts tz outputRH = \case CRActiveUser User {profile, uiThemes} -> viewUserProfile (fromLocalProfile profile) <> viewUITheme uiThemes CRUsersList users -> viewUsersList users CRChatStarted -> ["chat started"] @@ -108,7 +108,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRUserServersValidation {} -> [] CRUsageConditions current _ accepted_ -> viewUsageConditions current accepted_ CRChatItemTTL u ttl -> ttyUser u $ viewChatItemTTL ttl - CRNetworkConfig cfg -> viewNetworkConfig cfg + CRNetworkConfig netCfg -> viewNetworkConfig netCfg CRContactInfo u ct cStats customUserProfile -> ttyUser u $ viewContactInfo ct cStats customUserProfile CRGroupInfo u g s -> ttyUser u $ viewGroupInfo g s CRGroupMemberInfo u g m cStats -> ttyUser u $ viewGroupMemberInfo g m cStats @@ -181,7 +181,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe HSDatabase -> databaseHelpInfo CRWelcome user -> chatWelcome user CRContactsList u cs -> ttyUser u $ viewContactsList cs - CRUserContactLink u UserContactLink {connReqContact, autoAccept} -> ttyUser u $ connReqContact_ "Your chat address:" connReqContact <> autoAcceptStatus_ autoAccept + CRUserContactLink u UserContactLink {connLinkContact, autoAccept} -> ttyUser u $ connReqContact_ "Your chat address:" connLinkContact <> autoAcceptStatus_ autoAccept CRUserContactLinkUpdated u UserContactLink {autoAccept} -> ttyUser u $ autoAcceptStatus_ autoAccept CRContactRequestRejected u UserContactRequest {localDisplayName = c} -> ttyUser u [ttyContact c <> ": contact request rejected"] CRGroupCreated u g -> ttyUser u $ viewGroupCreated g testView @@ -200,10 +200,10 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRUserProfileNoChange u -> ttyUser u ["user profile did not change"] CRUserPrivacy u u' -> ttyUserPrefix u $ viewUserPrivacy u u' CRVersionInfo info _ _ -> viewVersionInfo logLevel info - CRInvitation u cReq _ -> ttyUser u $ viewConnReqInvitation cReq + CRInvitation u ccLink _ -> ttyUser u $ viewConnReqInvitation ccLink CRConnectionIncognitoUpdated u c -> ttyUser u $ viewConnectionIncognitoUpdated c CRConnectionUserChanged u c c' nu -> ttyUser u $ viewConnectionUserChanged u c nu c' - CRConnectionPlan u connectionPlan -> ttyUser u $ viewConnectionPlan connectionPlan + CRConnectionPlan u _ connectionPlan -> ttyUser u $ viewConnectionPlan cfg connectionPlan CRSentConfirmation u _ -> ttyUser u ["confirmation sent!"] CRSentInvitation u _ customUserProfile -> ttyUser u $ viewSentInvitation customUserProfile testView CRSentInvitationToContact u _c customUserProfile -> ttyUser u $ viewSentInvitation customUserProfile testView @@ -215,7 +215,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRContactAlreadyExists u c -> ttyUser u [ttyFullContact c <> ": contact already exists"] CRContactRequestAlreadyAccepted u c -> ttyUser u [ttyFullContact c <> ": sent you a duplicate contact request, but you are already connected, no action needed"] CRBusinessRequestAlreadyAccepted u g -> ttyUser u [ttyFullGroup g <> ": sent you a duplicate connection request, but you are already connected, no action needed"] - CRUserContactLinkCreated u cReq -> ttyUser u $ connReqContact_ "Your new chat address is created!" cReq + CRUserContactLinkCreated u ccLink -> ttyUser u $ connReqContact_ "Your new chat address is created!" ccLink CRUserContactLinkDeleted u -> ttyUser u viewUserContactLinkDeleted CRUserAcceptedGroupSent u _g _ -> ttyUser u [] -- [ttyGroup' g <> ": joining the group..."] CRGroupLinkConnecting u g _ -> ttyUser u [ttyGroup' g <> ": joining the group..."] @@ -314,8 +314,8 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRGroupUpdated u g g' m -> ttyUser u $ viewGroupUpdated g g' m CRGroupProfile u g -> ttyUser u $ viewGroupProfile g CRGroupDescription u g -> ttyUser u $ viewGroupDescription g - CRGroupLinkCreated u g cReq mRole -> ttyUser u $ groupLink_ "Group link is created!" g cReq mRole - CRGroupLink u g cReq mRole -> ttyUser u $ groupLink_ "Group link:" g cReq mRole + CRGroupLinkCreated u g ccLink mRole -> ttyUser u $ groupLink_ "Group link is created!" g ccLink mRole + CRGroupLink u g ccLink mRole -> ttyUser u $ groupLink_ "Group link:" g ccLink mRole CRGroupLinkDeleted u g -> ttyUser u $ viewGroupLinkDeleted g CRAcceptingGroupJoinRequestMember _ g m -> [ttyFullMember m <> ": accepting request to join group " <> ttyGroup' g <> "..."] CRNoMemberContactCreating u g m -> ttyUser u ["member " <> ttyGroup' g <> " " <> ttyMember m <> " does not have direct connection, creating"] @@ -911,14 +911,17 @@ viewInvalidConnReq = plain updateStr ] -viewConnReqInvitation :: ConnReqInvitation -> [StyledString] -viewConnReqInvitation cReq = +viewConnReqInvitation :: CreatedLinkInvitation -> [StyledString] +viewConnReqInvitation (CCLink cReq shortLink) = [ "pass this invitation link to your contact (via another channel): ", "", - (plain . strEncode) (simplexChatInvitation cReq), + plain $ maybe cReqStr strEncode shortLink, "", "and ask them to connect: " <> highlight' "/c " ] + <> ["The invitation link for old clients: " <> plain cReqStr | isJust shortLink] + where + cReqStr = strEncode $ simplexChatInvitation cReq simplexChatInvitation :: ConnReqInvitation -> ConnReqInvitation simplexChatInvitation (CRInvitationUri crData e2e) = CRInvitationUri crData {crScheme = simplexChat} e2e @@ -973,21 +976,29 @@ viewForwardPlan count itemIds = maybe [forwardCount] $ \fc -> [confirmation fc, | otherwise = plain $ show len <> " message(s) out of " <> show count <> " can be forwarded" len = length itemIds -connReqContact_ :: StyledString -> ConnReqContact -> [StyledString] -connReqContact_ intro cReq = +connReqContact_ :: StyledString -> CreatedLinkContact -> [StyledString] +connReqContact_ intro (CCLink cReq shortLink) = [ intro, "", - (plain . strEncode) (simplexChatContact cReq), + plain $ maybe cReqStr strEncode shortLink, "", "Anybody can send you contact requests with: " <> highlight' "/c ", "to show it again: " <> highlight' "/sa", "to share with your contacts: " <> highlight' "/profile_address on", "to delete it: " <> highlight' "/da" <> " (accepted contacts will remain connected)" ] + <> ["The contact link for old clients: " <> plain cReqStr | isJust shortLink] + where + cReqStr = strEncode $ simplexChatContact cReq simplexChatContact :: ConnReqContact -> ConnReqContact simplexChatContact (CRContactUri crData) = CRContactUri crData {crScheme = simplexChat} +simplexChatContact' :: ConnLinkContact -> ConnLinkContact +simplexChatContact' = \case + CLFull (CRContactUri crData) -> CLFull $ CRContactUri crData {crScheme = simplexChat} + l@(CLShort _) -> l + autoAcceptStatus_ :: Maybe AutoAccept -> [StyledString] autoAcceptStatus_ = \case Just AutoAccept {businessAddress, acceptIncognito, autoReply} -> @@ -1000,16 +1011,19 @@ autoAcceptStatus_ = \case | otherwise = "" _ -> ["auto_accept off"] -groupLink_ :: StyledString -> GroupInfo -> ConnReqContact -> GroupMemberRole -> [StyledString] -groupLink_ intro g cReq mRole = +groupLink_ :: StyledString -> GroupInfo -> CreatedLinkContact -> GroupMemberRole -> [StyledString] +groupLink_ intro g (CCLink cReq shortLink) mRole = [ intro, "", - (plain . strEncode) (simplexChatContact cReq), + plain $ maybe cReqStr strEncode shortLink, "", "Anybody can connect to you and join group as " <> showRole mRole <> " with: " <> highlight' "/c ", "to show it again: " <> highlight ("/show link #" <> viewGroupName g), "to delete it: " <> highlight ("/delete link #" <> viewGroupName g) <> " (joined members will remain connected to you)" ] + <> ["The group link for old clients: " <> plain cReqStr | isJust shortLink] + where + cReqStr = strEncode $ simplexChatContact cReq viewGroupLinkDeleted :: GroupInfo -> [StyledString] viewGroupLinkDeleted g = @@ -1405,7 +1419,7 @@ viewContactInfo :: Contact -> Maybe ConnectionStats -> Maybe Profile -> [StyledS viewContactInfo ct@Contact {contactId, profile = LocalProfile {localAlias, contactLink}, activeConn, uiThemes, customData} stats incognitoProfile = ["contact ID: " <> sShow contactId] <> maybe [] viewConnectionStats stats - <> maybe [] (\l -> ["contact address: " <> (plain . strEncode) (simplexChatContact l)]) contactLink + <> maybe [] (\l -> ["contact address: " <> (plain . strEncode) (simplexChatContact' l)]) contactLink <> maybe ["you've shared main profile with this contact"] (\p -> ["you've shared incognito profile with this contact: " <> incognitoProfile' p]) @@ -1437,7 +1451,7 @@ viewGroupMemberInfo GroupInfo {groupId} m@GroupMember {groupMemberId, memberProf "member ID: " <> sShow groupMemberId ] <> maybe ["member not connected"] viewConnectionStats stats - <> maybe [] (\l -> ["contact address: " <> (plain . strEncode) (simplexChatContact l)]) contactLink + <> maybe [] (\l -> ["contact address: " <> (plain . strEncode) (simplexChatContact' l)]) contactLink <> ["alias: " <> plain localAlias | localAlias /= ""] <> [viewConnectionVerified (memberSecurityCode m) | isJust stats] <> maybe [] (\ac -> [viewPeerChatVRange (peerChatVRange ac)]) activeConn @@ -1663,21 +1677,24 @@ viewConnectionIncognitoUpdated PendingContactConnection {pccConnId, customUserPr | otherwise = ["connection " <> sShow pccConnId <> " changed to non incognito"] viewConnectionUserChanged :: User -> PendingContactConnection -> User -> PendingContactConnection -> [StyledString] -viewConnectionUserChanged User {localDisplayName = n} PendingContactConnection {pccConnId, connReqInv} User {localDisplayName = n'} PendingContactConnection {connReqInv = connReqInv'} = - case (connReqInv, connReqInv') of - (Just cReqInv, Just cReqInv') - | cReqInv /= cReqInv' -> [userChangedStr <> ", new link:"] <> newLink cReqInv' +viewConnectionUserChanged User {localDisplayName = n} PendingContactConnection {pccConnId, connLinkInv} User {localDisplayName = n'} PendingContactConnection {connLinkInv = connLinkInv'} = + case (connLinkInv, connLinkInv') of + (Just ccLink, Just ccLink') + | ccLink /= ccLink' -> [userChangedStr <> ", new link:"] <> newLink ccLink' _ -> [userChangedStr] where userChangedStr = "connection " <> sShow pccConnId <> " changed from user " <> plain n <> " to user " <> plain n' - newLink cReqInv = + newLink (CCLink cReq shortLink) = [ "", - (plain . strEncode) (simplexChatInvitation cReqInv), + plain $ maybe cReqStr strEncode shortLink, "" ] + <> ["The invitation link for old clients: " <> plain cReqStr | isJust shortLink] + where + cReqStr = strEncode $ simplexChatInvitation cReq -viewConnectionPlan :: ConnectionPlan -> [StyledString] -viewConnectionPlan = \case +viewConnectionPlan :: ChatConfig -> ConnectionPlan -> [StyledString] +viewConnectionPlan ChatConfig {logLevel, testView} = \case CPInvitationLink ilp -> case ilp of ILPOk -> [invLink "ok to connect"] ILPOwnLink -> [invLink "own link"] @@ -1716,6 +1733,7 @@ viewConnectionPlan = \case grpOrBiz GroupInfo {businessChat} = case businessChat of Just _ -> "business" Nothing -> "group" + CPError e -> viewChatError False logLevel testView e viewContactUpdated :: Contact -> Contact -> [StyledString] viewContactUpdated @@ -2138,8 +2156,8 @@ viewChatError isCmd logLevel testView = \case CEChatNotStarted -> ["error: chat not started"] CEChatNotStopped -> ["error: chat not stopped"] CEChatStoreChanged -> ["error: chat store changed, please restart chat"] - CEConnectionPlan connectionPlan -> viewConnectionPlan connectionPlan CEInvalidConnReq -> viewInvalidConnReq + CEUnsupportedConnReq -> [ "", "Connection link is not supported by the your app version, please ugrade it.", plain updateStr] CEInvalidChatMessage Connection {connId} msgMeta_ msg e -> [ plain $ ("chat message error: " <> e <> " (" <> T.unpack (T.take 120 msg) <> ")") diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index f16bc84cfd..2d0bde5058 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -186,6 +186,7 @@ testCfg = defaultChatConfig { agentConfig = testAgentCfg, showReceipts = False, + shortLinkPresetServers = ["smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=@localhost:7001"], testView = True, tbqSize = 16 } @@ -290,8 +291,8 @@ startTestChat_ TestParams {printOutput} db cfg opts@ChatOpts {maintenance} user ct <- newChatTerminal t opts cc <- newChatController db (Just user) cfg opts False void $ execChatCommand' (SetTempFolder "tests/tmp/tmp") `runReaderT` cc - chatAsync <- async . runSimplexChat opts user cc $ \_u cc' -> runChatTerminal ct cc' opts - atomically . unless maintenance $ readTVar (agentAsync cc) >>= \a -> when (isNothing a) retry + chatAsync <- async $ runSimplexChat opts user cc $ \_u cc' -> runChatTerminal ct cc' opts + unless maintenance $ atomically $ readTVar (agentAsync cc) >>= \a -> when (isNothing a) retry termQ <- newTQueueIO termAsync <- async $ readTerminalOutput t termQ pure TestCC {chatController = cc, virtualTerminal = t, chatAsync, termAsync, termQ, printOutput} @@ -393,14 +394,14 @@ withTmpFiles = testChatN :: HasCallStack => ChatConfig -> ChatOpts -> [Profile] -> (HasCallStack => [TestCC] -> IO ()) -> TestParams -> IO () testChatN cfg opts ps test params = - bracket (getTestCCs (zip ps [1 ..]) []) entTests test + bracket (getTestCCs $ zip ps [1 ..]) endTests test where - getTestCCs :: [(Profile, Int)] -> [TestCC] -> IO [TestCC] - getTestCCs [] tcs = pure tcs - getTestCCs ((p, db) : envs') tcs = (:) <$> createTestChat params cfg opts (show db) p <*> getTestCCs envs' tcs - entTests tcs = do - concurrentlyN_ $ map ( IO [TestCC] + getTestCCs [] = pure [] + getTestCCs ((p, db) : envs') = (:) <$> createTestChat params cfg opts (show db) p <*> getTestCCs envs' + endTests tcs = do + mapConcurrently_ ( TestCC -> Int -> Expectation ( ?" (Only msgIdBob) :: IO [[Int]] bobItemsCount `shouldBe` [[300]] + threadDelay 1000000 + testGetSetSMPServers :: HasCallStack => TestParams -> IO () testGetSetSMPServers = testChat aliceProfile $ diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index 72d502392e..433615e62a 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -101,6 +101,11 @@ chatProfileTests = do it "files & media" testGroupPrefsFilesForRole it "SimpleX links" testGroupPrefsSimplexLinksForRole it "set user, contact and group UI theme" testSetUITheme + describe "short links" $ do + it "should connect via one-time inviation" testShortLinkInvitation + it "should plan and connect via one-time inviation" testPlanShortLinkInvitation + it "should connect via contact address" testShortLinkContactAddress + it "should join group" testShortLinkJoinGroup testUpdateProfile :: HasCallStack => TestParams -> IO () testUpdateProfile = @@ -2583,3 +2588,162 @@ testSetUITheme = groupInfo a = do a <## "group ID: 1" a <## "current members: 1" + +testShortLinkInvitation :: HasCallStack => TestParams -> IO () +testShortLinkInvitation = + testChat2 aliceProfile bobProfile $ \alice bob -> do + alice ##> "/c short" + inv <- getShortInvitation alice + bob ##> ("/c " <> inv) + bob <## "confirmation sent!" + concurrently_ + (alice <## "bob (Bob): contact is connected") + (bob <## "alice (Alice): contact is connected") + alice #> "@bob hi" + bob <# "alice> hi" + bob #> "@alice hey" + alice <# "bob> hey" + +testPlanShortLinkInvitation :: HasCallStack => TestParams -> IO () +testPlanShortLinkInvitation = + testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do + alice ##> "/c short" + inv <- getShortInvitation alice + alice ##> ("/_connect plan 1 " <> inv) + alice <## "invitation link: own link" + alice ##> ("/_connect plan 1 " <> slSimplexScheme inv) + alice <## "invitation link: own link" + bob ##> ("/_connect plan 1 " <> inv) + bob <## "invitation link: ok to connect" + -- nobody else can connect + cath ##> ("/_connect plan 1 " <> inv) + cath <##. "error: connection authorization failed" + cath ##> ("/c " <> inv) + cath <##. "error: connection authorization failed" + -- bob can retry "plan" + bob ##> ("/_connect plan 1 " <> inv) + bob <## "invitation link: ok to connect" + -- with simplex: scheme too + bob ##> ("/_connect plan 1 " <> slSimplexScheme inv) + bob <## "invitation link: ok to connect" + bob ##> ("/c " <> inv) + bob <## "confirmation sent!" + concurrently_ + (alice <## "bob (Bob): contact is connected") + (bob <## "alice (Alice): contact is connected") + alice #> "@bob hi" + bob <# "alice> hi" + bob #> "@alice hey" + alice <# "bob> hey" + bob ##> ("/_connect plan 1 " <> inv) + bob <##. "error: connection authorization failed" + alice ##> ("/_connect plan 1 " <> inv) + alice <##. "error: connection authorization failed" -- short_link_inv and conn_req_inv are removed after connection + +slSimplexScheme :: String -> String +slSimplexScheme sl = T.unpack $ T.replace "https://localhost/" "simplex:/" (T.pack sl) <> "?h=localhost" + +testShortLinkContactAddress :: HasCallStack => TestParams -> IO () +testShortLinkContactAddress = + testChat4 aliceProfile bobProfile cathProfile danProfile $ \alice bob cath dan -> do + alice ##> "/ad short" + (shortLink, fullLink) <- getShortContactLink alice True + alice ##> ("/_connect plan 1 " <> shortLink) + alice <## "contact address: own address" + alice ##> ("/_connect plan 1 " <> slSimplexScheme shortLink) + alice <## "contact address: own address" + alice ##> ("/_connect plan 1 " <> fullLink) + alice <## "contact address: own address" + (alice, bob) `connectVia` shortLink + bob ##> ("/_connect plan 1 " <> slSimplexScheme shortLink) + bob <## "contact address: known contact alice" + bob <## "use @alice to send messages" + (alice, cath) `connectVia` slSimplexScheme shortLink + cath ##> ("/_connect plan 1 " <> shortLink) + cath <## "contact address: known contact alice" + cath <## "use @alice to send messages" + (alice, dan) `connectVia` fullLink + where + (alice, cc) `connectVia` cLink = do + name <- userName cc + sName <- showName cc + cc ##> ("/_connect plan 1 " <> cLink) + cc <## "contact address: ok to connect" + cc ##> ("/c " <> cLink) + alice <#? cc + alice ##> ("/ac " <> name) + alice <## (sName <> ": accepting contact request, you can send messages to contact") + concurrently_ + (cc <## "alice (Alice): contact is connected") + (alice <## (sName <> ": contact is connected")) + cc ##> ("/_connect plan 1 " <> cLink) + cc <## "contact address: known contact alice" + cc <## "use @alice to send messages" + +testShortLinkJoinGroup :: HasCallStack => TestParams -> IO () +testShortLinkJoinGroup = + testChat4 aliceProfile bobProfile cathProfile danProfile $ \alice bob cath dan -> do + threadDelay 100000 + alice ##> "/ad short" -- create the address to test that it can co-exist with group link + _ <- getShortContactLink alice True + alice ##> "/g team" + alice <## "group #team is created" + alice <## "to add members use /a team or /create link #team" + alice ##> "/create link #team short" + (shortLink, fullLink) <- getShortGroupLink alice "team" GRMember True + alice ##> ("/_connect plan 1 " <> shortLink) + alice <## "group link: own link for group #team" + alice ##> ("/_connect plan 1 " <> slSimplexScheme shortLink) + alice <## "group link: own link for group #team" + alice ##> ("/_connect plan 1 " <> fullLink) + alice <## "group link: own link for group #team" + joinGroup alice bob shortLink + bob ##> ("/_connect plan 1 " <> shortLink) + bob <## "group link: known group #team" + bob <## "use #team to send messages" + bob ##> ("/_connect plan 1 " <> slSimplexScheme shortLink) + bob <## "group link: known group #team" + bob <## "use #team to send messages" + joinGroup alice cath $ slSimplexScheme shortLink + concurrentlyN_ + [ do + bob <## "#team: alice added cath (Catherine) to the group (connecting...)" + bob <## "#team: new member cath is connected", + cath <## "#team: member bob (Bob) is connected" + ] + cath ##> ("/_connect plan 1 " <> slSimplexScheme shortLink) + cath <## "group link: known group #team" + cath <## "use #team to send messages" + cath ##> ("/_connect plan 1 " <> shortLink) + cath <## "group link: known group #team" + cath <## "use #team to send messages" + joinGroup alice dan fullLink + concurrentlyN_ + [ do + bob <## "#team: alice added dan (Daniel) to the group (connecting...)" + bob <## "#team: new member dan is connected", + do + cath <## "#team: alice added dan (Daniel) to the group (connecting...)" + cath <## "#team: new member dan is connected", + do + dan <## "#team: member bob (Bob) is connected" + dan <## "#team: member cath (Catherine) is connected" + ] + dan ##> ("/_connect plan 1 " <> fullLink) + dan <## "group link: known group #team" + dan <## "use #team to send messages" + where + joinGroup alice cc link = do + name <- userName cc + sName <- showName cc + cc ##> ("/_connect plan 1 " <> link) + cc <## "group link: ok to connect" + cc ##> ("/c " <> link) + cc <## "connection request sent!" + alice <## (sName <> ": accepting request to join group #team...") + concurrentlyN_ + [ alice <## ("#team: " <> name <> " joined the group"), + do + cc <## "#team: joining the group..." + cc <## "#team: you joined the group" + ] diff --git a/tests/ChatTests/Utils.hs b/tests/ChatTests/Utils.hs index 9a3b560b29..3ae3f61ca7 100644 --- a/tests/ChatTests/Utils.hs +++ b/tests/ChatTests/Utils.hs @@ -11,7 +11,7 @@ module ChatTests.Utils where import ChatClient import ChatTests.DBUtils import Control.Concurrent (threadDelay) -import Control.Concurrent.Async (concurrently_) +import Control.Concurrent.Async (concurrently_, mapConcurrently_) import Control.Concurrent.STM import Control.Monad (unless, when) import Control.Monad.Except (runExceptT) @@ -424,7 +424,7 @@ getInAnyOrder f cc ls = do cc <# line = (dropTime <$> getTermLine cc) `shouldReturn` line (*<#) :: HasCallStack => [TestCC] -> String -> Expectation -ccs *<# line = concurrentlyN_ $ map (<# line) ccs +ccs *<# line = mapConcurrently_ (<# line) ccs (?<#) :: HasCallStack => TestCC -> String -> Expectation cc ?<# line = (dropTime <$> getTermLine cc) `shouldReturn` "i " <> line @@ -502,14 +502,27 @@ dropPartialReceipt_ msg = case splitAt 2 msg of _ -> Nothing getInvitation :: HasCallStack => TestCC -> IO String -getInvitation cc = do +getInvitation = getInvitation_ False + +getShortInvitation :: HasCallStack => TestCC -> IO String +getShortInvitation = getInvitation_ True + +getInvitation_ :: HasCallStack => Bool -> TestCC -> IO String +getInvitation_ short cc = do cc <## "pass this invitation link to your contact (via another channel):" cc <## "" inv <- getTermLine cc cc <## "" cc <## "and ask them to connect: /c " + when short $ cc <##. "The invitation link for old clients: https://simplex.chat/invitation#" pure inv +getShortContactLink :: HasCallStack => TestCC -> Bool -> IO (String, String) +getShortContactLink cc created = do + shortLink <- getContactLink cc created + fullLink <- dropLinePrefix "The contact link for old clients: " =<< getTermLine cc + pure (shortLink, fullLink) + getContactLink :: HasCallStack => TestCC -> Bool -> IO String getContactLink cc created = do cc <## if created then "Your new chat address is created!" else "Your chat address:" @@ -522,6 +535,17 @@ getContactLink cc created = do cc <## "to delete it: /da (accepted contacts will remain connected)" pure link +dropLinePrefix :: String -> String -> IO String +dropLinePrefix line s + | line `isPrefixOf` s = pure $ drop (length line) s + | otherwise = error $ "expected to start from: " <> line <> ", got: " <> s + +getShortGroupLink :: HasCallStack => TestCC -> String -> GroupMemberRole -> Bool -> IO (String, String) +getShortGroupLink cc gName mRole created = do + shortLink <- getGroupLink cc gName mRole created + fullLink <- dropLinePrefix "The group link for old clients: " =<< getTermLine cc + pure (shortLink, fullLink) + getGroupLink :: HasCallStack => TestCC -> String -> GroupMemberRole -> Bool -> IO String getGroupLink cc gName mRole created = do cc <## if created then "Group link is created!" else "Group link:" diff --git a/tests/OperatorTests.hs b/tests/OperatorTests.hs index 0a00d7b83c..dbfde6a03d 100644 --- a/tests/OperatorTests.hs +++ b/tests/OperatorTests.hs @@ -19,6 +19,7 @@ import qualified Data.List.NonEmpty as L import Simplex.Chat import Simplex.Chat.Controller (ChatConfig (..), PresetServers (..)) import Simplex.Chat.Operators +import Simplex.Chat.Operators.Presets import Simplex.Chat.Types import Simplex.FileTransfer.Client.Presets (defaultXFTPServers) import Simplex.Messaging.Agent.Env.SQLite (ServerRoles (..), allRoles) diff --git a/tests/ProtocolTests.hs b/tests/ProtocolTests.hs index 5c672b2858..50d2c1eef0 100644 --- a/tests/ProtocolTests.hs +++ b/tests/ProtocolTests.hs @@ -35,7 +35,7 @@ queue = { smpServer = srv, senderId = EntityId "\223\142z\251", dhPublicKey = "MCowBQYDK2VuAyEAjiswwI3O/NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o=", - sndSecure = False + queueMode = Nothing } connReqData :: ConnReqUriData @@ -201,7 +201,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do "{\"v\":\"1\",\"event\":\"x.msg.deleted\",\"params\":{}}" #==# XMsgDeleted it "x.file" $ - "{\"v\":\"1\",\"event\":\"x.file\",\"params\":{\"file\":{\"fileConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}" + "{\"v\":\"1\",\"event\":\"x.file\",\"params\":{\"file\":{\"fileConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}" #==# XFile FileInvitation {fileName = "photo.jpg", fileSize = 12345, fileDigest = Nothing, fileConnReq = Just testConnReq, fileInline = Nothing, fileDescr = Nothing} it "x.file without file invitation" $ "{\"v\":\"1\",\"event\":\"x.file\",\"params\":{\"file\":{\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}" @@ -210,7 +210,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do "{\"v\":\"1\",\"event\":\"x.file.acpt\",\"params\":{\"fileName\":\"photo.jpg\"}}" #==# XFileAcpt "photo.jpg" it "x.file.acpt.inv" $ - "{\"v\":\"1\",\"event\":\"x.file.acpt.inv\",\"params\":{\"msgId\":\"AQIDBA==\",\"fileName\":\"photo.jpg\",\"fileConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}" + "{\"v\":\"1\",\"event\":\"x.file.acpt.inv\",\"params\":{\"msgId\":\"AQIDBA==\",\"fileName\":\"photo.jpg\",\"fileConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}" #==# XFileAcptInv (SharedMsgId "\1\2\3\4") (Just testConnReq) "photo.jpg" it "x.file.acpt.inv" $ "{\"v\":\"1\",\"event\":\"x.file.acpt.inv\",\"params\":{\"msgId\":\"AQIDBA==\",\"fileName\":\"photo.jpg\"}}" @@ -237,10 +237,10 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do "{\"v\":\"1\",\"event\":\"x.contact\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" ==# XContact testProfile Nothing it "x.grp.inv" $ - "{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}}}}" + "{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}}}}" #==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, business = Nothing, groupLinkId = Nothing, groupSize = Nothing} it "x.grp.inv with group link id" $ - "{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}, \"groupLinkId\":\"AQIDBA==\"}}}" + "{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}, \"groupLinkId\":\"AQIDBA==\"}}}" #==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, business = Nothing, groupLinkId = Just $ GroupLinkId "\1\2\3\4", groupSize = Nothing} it "x.grp.acpt without incognito profile" $ "{\"v\":\"1\",\"event\":\"x.grp.acpt\",\"params\":{\"memberId\":\"AQIDBA==\"}}" @@ -261,16 +261,16 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do "{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberRestrictions\":{\"restriction\":\"blocked\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} (Just MemberRestrictions {restriction = MRSBlocked}) it "x.grp.mem.inv" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}}" + "{\"v\":\"1\",\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}}" #==# XGrpMemInv (MemberId "\1\2\3\4") IntroInvitation {groupConnReq = testConnReq, directConnReq = Just testConnReq} it "x.grp.mem.inv w/t directConnReq" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}}" + "{\"v\":\"1\",\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}}" #==# XGrpMemInv (MemberId "\1\2\3\4") IntroInvitation {groupConnReq = testConnReq, directConnReq = Nothing} it "x.grp.mem.fwd" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" + "{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} IntroInvitation {groupConnReq = testConnReq, directConnReq = Just testConnReq} it "x.grp.mem.fwd with member chat version range and w/t directConnReq" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-14\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" + "{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-14\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile} IntroInvitation {groupConnReq = testConnReq, directConnReq = Nothing} it "x.grp.mem.info" $ "{\"v\":\"1\",\"event\":\"x.grp.mem.info\",\"params\":{\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" @@ -291,10 +291,10 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do "{\"v\":\"1\",\"event\":\"x.grp.del\",\"params\":{}}" ==# XGrpDel it "x.grp.direct.inv" $ - "{\"v\":\"1\",\"event\":\"x.grp.direct.inv\",\"params\":{\"connReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\", \"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" + "{\"v\":\"1\",\"event\":\"x.grp.direct.inv\",\"params\":{\"connReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\", \"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" #==# XGrpDirectInv testConnReq (Just $ MCText "hello") it "x.grp.direct.inv without content" $ - "{\"v\":\"1\",\"event\":\"x.grp.direct.inv\",\"params\":{\"connReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}" + "{\"v\":\"1\",\"event\":\"x.grp.direct.inv\",\"params\":{\"connReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}" #==# XGrpDirectInv testConnReq Nothing -- it "x.grp.msg.forward" -- $ "{\"v\":\"1\",\"event\":\"x.grp.msg.forward\",\"params\":{\"msgForward\":{\"memberId\":\"AQIDBA==\",\"msg\":\"{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}\",\"msgTs\":\"1970-01-01T00:00:01.000000001Z\"}}}" diff --git a/website/src/.well-known/apple-app-site-association/index.json b/website/src/.well-known/apple-app-site-association/index.json index 3cd3fdd043..3b513fe61e 100644 --- a/website/src/.well-known/apple-app-site-association/index.json +++ b/website/src/.well-known/apple-app-site-association/index.json @@ -17,6 +17,30 @@ }, { "/": "/invitation" + }, + { + "/": "/a/*" + }, + { + "/": "/a" + }, + { + "/": "/c/*" + }, + { + "/": "/c" + }, + { + "/": "/g/*" + }, + { + "/": "/g" + }, + { + "/": "/i/*" + }, + { + "/": "/i" } ] } From df99ed495ca55b18117aafefb62cb7a0e3cdf4af Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Tue, 15 Apr 2025 09:02:50 +0000 Subject: [PATCH 508/567] ci/docker: use Java Corretto (#5832) --- Dockerfile.build | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Dockerfile.build b/Dockerfile.build index cd0fd22aad..76bb1127f2 100644 --- a/Dockerfile.build +++ b/Dockerfile.build @@ -11,7 +11,7 @@ ARG JAVA=17 ENV TZ=Etc/UTC \ DEBIAN_FRONTEND=noninteractive -# Install curl, git and and simplexmq dependencies +# Install curl, git and and simplex-chat dependencies RUN apt-get update && \ apt-get install -y curl \ libpq-dev \ @@ -27,7 +27,6 @@ RUN apt-get update && \ libnuma-dev \ libssl-dev \ desktop-file-utils \ - openjdk-${JAVA}-jdk-headless \ patchelf \ ca-certificates \ zip \ @@ -37,9 +36,18 @@ RUN apt-get update && \ appstream \ gpg \ unzip &&\ - export JAVA_HOME=$(update-java-alternatives -l | head -n 1 | awk -F ' ' '{print $NF}') &&\ ln -s /bin/fusermount /bin/fusermount3 || : +# Install Java Coretto +# Required, because official Java in Ubuntu +# depends on libjpeg.so.8 and liblcms2.so.2 which are NOT copied into final +# /usr/lib/runtime/lib directory and I do not have time to figure out gradle.kotlin +# to fix this :( +RUN curl --proto '=https' --tlsv1.2 -sSf 'https://apt.corretto.aws/corretto.key' | gpg --dearmor -o /usr/share/keyrings/corretto-keyring.gpg &&\ + echo "deb [signed-by=/usr/share/keyrings/corretto-keyring.gpg] https://apt.corretto.aws stable main" > /etc/apt/sources.list.d/corretto.list &&\ + apt update &&\ + apt install -y java-${JAVA}-amazon-corretto-jdk + # Specify bootstrap Haskell versions ENV BOOTSTRAP_HASKELL_GHC_VERSION=${GHC} ENV BOOTSTRAP_HASKELL_CABAL_VERSION=${CABAL} From f52d06af3ad160490787bebe8f0e15f8f0d927d6 Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Tue, 15 Apr 2025 09:24:24 +0000 Subject: [PATCH 509/567] flatpak: update metainfo (#5836) * flatpak: update metainfo * include previous changes --- .../flatpak/chat.simplex.simplex.metainfo.xml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/scripts/flatpak/chat.simplex.simplex.metainfo.xml b/scripts/flatpak/chat.simplex.simplex.metainfo.xml index 6ad4fda03e..823b8562fc 100644 --- a/scripts/flatpak/chat.simplex.simplex.metainfo.xml +++ b/scripts/flatpak/chat.simplex.simplex.metainfo.xml @@ -38,6 +38,28 @@
    + + https://simplex.chat/blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.html + +

    New in v6.3.1-2:

    +
      +
    • fix related to backward/forward compatibility of the app in some rare cases.
    • +
    • scrolling/navigation improvements.
    • +
    • faster onboarding (conditions and operators are combined to one screen).
    • +
    +

    New in v6.3.0:

    +
      +
    • Mention members and get notified when mentioned.
    • +
    • Send private reports to moderators.
    • +
    • Delete, block and change role for multiple members at once
    • +
    • Faster sending messages and faster deletion.
    • +
    • Organize chats into lists to keep track of what's important.
    • +
    • Jump to found and forwarded messages.
    • +
    • Private media file names.
    • +
    • Message expiration in chats.
    • +
    +
    +
    https://simplex.chat/blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.html From 0f3e546e369845372346504fd7e82afe3002e371 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 15 Apr 2025 13:50:06 +0000 Subject: [PATCH 510/567] kotlin: refactor chat contexts 2 (null secondary context, pass context instead of content tag, straighten chat state code) (#5830) --- .../simplex/common/platform/UI.android.kt | 3 - .../kotlin/chat/simplex/common/App.kt | 4 +- .../chat/simplex/common/model/ChatModel.kt | 92 ++-- .../chat/simplex/common/model/SimpleXAPI.kt | 92 ++-- .../simplex/common/platform/NtfManager.kt | 2 +- .../simplex/common/views/chat/ChatInfoView.kt | 15 +- .../common/views/chat/ChatItemInfoView.kt | 2 +- .../common/views/chat/ChatItemsLoader.kt | 17 +- .../common/views/chat/ChatItemsMerger.kt | 43 +- .../simplex/common/views/chat/ChatView.kt | 520 +++++++++--------- .../views/chat/SelectableChatItemToolbars.kt | 4 +- .../views/chat/group/AddGroupMembersView.kt | 4 +- .../views/chat/group/GroupChatInfoView.kt | 9 +- .../views/chat/group/GroupMemberInfoView.kt | 44 +- .../views/chat/group/GroupReportsView.kt | 23 +- .../views/chat/item/CIChatFeatureView.kt | 9 +- .../common/views/chat/item/ChatItemView.kt | 50 +- .../views/chat/item/MarkedDeletedItemView.kt | 9 +- .../views/chatlist/ChatListNavLinkView.kt | 40 +- .../views/contacts/ContactListNavView.kt | 4 +- .../common/views/database/DatabaseView.kt | 8 +- .../simplex/common/views/helpers/ModalView.kt | 11 +- .../kotlin/chat/simplex/common/DesktopApp.kt | 4 +- .../views/chatlist/ChatListView.desktop.kt | 4 +- 24 files changed, 462 insertions(+), 551 deletions(-) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/UI.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/UI.android.kt index f56563a1cb..f6066d1624 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/UI.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/UI.android.kt @@ -80,9 +80,6 @@ actual class GlobalExceptionsHandler: Thread.UncaughtExceptionHandler { chatModel.chatId.value = null chatModel.chatsContext.chatItems.clearAndNotify() } - withContext(Dispatchers.Main) { - chatModel.chatsContext.chatItems.clearAndNotify() - } } } else { // ChatList, nothing to do. Maybe to show other view except ChatList diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index 600804a763..d88a450fd1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -339,7 +339,7 @@ fun AndroidScreen(userPickerState: MutableStateFlow) { .graphicsLayer { translationX = maxWidth.toPx() - minOf(offset.value.dp, maxWidth).toPx() } ) Box2@{ currentChatId.value?.let { - ChatView(currentChatId, contentTag = null, onComposed = onComposed) + ChatView(chatsCtx = chatModel.chatsContext, currentChatId, onComposed = onComposed) } } } @@ -393,7 +393,7 @@ fun CenterPartOfScreen() { ModalManager.center.showInView() } } - else -> ChatView(currentChatId, contentTag = null) {} + else -> ChatView(chatsCtx = chatModel.chatsContext, currentChatId) {} } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 7ad26c3726..7e5eed3c42 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -67,7 +67,7 @@ object ChatModel { val chatId = mutableStateOf(null) val openAroundItemId: MutableState = mutableStateOf(null) val chatsContext = ChatsContext(null) - val secondaryChatsContext = ChatsContext(MsgContentTag.Report) + val secondaryChatsContext = mutableStateOf(null) // declaration of chatsContext should be before any other variable that is taken from ChatsContext class and used in the model, otherwise, strange crash with NullPointerException for "this" parameter in random functions val chats: State> = chatsContext.chats // rhId, chatId @@ -294,16 +294,15 @@ object ChatModel { } } - class ChatsContext(private val contentTag: MsgContentTag?) { + class ChatsContext(val contentTag: MsgContentTag?) { val chats = mutableStateOf(SnapshotStateList()) - /** if you modify the items by adding/removing them, use helpers methods like [addAndNotify], [removeLastAndNotify], [removeAllAndNotify], [clearAndNotify] and so on. + /** if you modify the items by adding/removing them, use helpers methods like [addToChatItems], [removeLastChatItems], [removeAllAndNotify], [clearAndNotify] and so on. * If some helper is missing, create it. Notify is needed to track state of items that we added manually (not via api call). See [apiLoadMessages]. - * If you use api call to get the items, use just [add] instead of [addAndNotify]. + * If you use api call to get the items, use just [add] instead of [addToChatItems]. * Never modify underlying list directly because it produces unexpected results in ChatView's LazyColumn (setting by index is ok) */ val chatItems = mutableStateOf(SnapshotStateList()) val chatItemStatuses = mutableMapOf() // set listener here that will be notified on every add/delete of a chat item - var chatItemsChangesListener: ChatItemsChangesListener? = null val chatState = ActiveChatState() fun hasChat(rhId: Long?, id: String): Boolean = chats.value.firstOrNull { it.id == id && it.remoteHostId == rhId } != null @@ -395,6 +394,26 @@ object ChatModel { addChat(chat) } } + + fun addToChatItems(index: Int, elem: ChatItem) { + chatItems.value = SnapshotStateList().apply { addAll(chatItems.value); add(index, elem); chatState.itemAdded(elem.id to elem.isRcvNew) } + } + + fun addToChatItems(elem: ChatItem) { + chatItems.value = SnapshotStateList().apply { addAll(chatItems.value); add(elem); chatState.itemAdded(elem.id to elem.isRcvNew) } + } + + fun removeLastChatItems() { + val removed: Triple + chatItems.value = SnapshotStateList().apply { + addAll(chatItems.value) + val remIndex = lastIndex + val rem = removeLast() + removed = Triple(rem.id, remIndex, rem.isRcvNew) + } + chatState.itemsRemoved(listOf(removed), chatItems.value) + } + suspend fun addChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem) { // mark chat non deleted if (cInfo is ChatInfo.Direct && cInfo.chatDeleted) { @@ -448,9 +467,9 @@ object ChatModel { // Prevent situation when chat item already in the list received from backend if (chatItems.value.none { it.id == cItem.id }) { if (chatItems.value.lastOrNull()?.id == ChatItem.TEMP_LIVE_CHAT_ITEM_ID) { - chatItems.addAndNotify(kotlin.math.max(0, chatItems.value.lastIndex), cItem, contentTag) + addToChatItems(kotlin.math.max(0, chatItems.value.lastIndex), cItem) } else { - chatItems.addAndNotify(cItem, contentTag) + addToChatItems(cItem) } } } @@ -495,7 +514,7 @@ object ChatModel { } else { cItem } - chatItems.addAndNotify(ci, contentTag) + addToChatItems(ci) true } } else { @@ -602,9 +621,10 @@ object ChatModel { } } - val popChatCollector = PopChatCollector(contentTag) + val popChatCollector = PopChatCollector(this) - class PopChatCollector(contentTag: MsgContentTag?) { + // TODO [contexts] no reason for this to be nested? + class PopChatCollector(chatsCtx: ChatsContext) { private val subject = MutableSharedFlow() private var remoteHostId: Long? = null private val chatsToPop = mutableMapOf() @@ -615,7 +635,6 @@ object ChatModel { .throttleLatest(2000) .collect { withContext(Dispatchers.Main) { - val chatsCtx = if (contentTag == null) chatsContext else secondaryChatsContext chatsCtx.chats.replaceAll(popCollectedChats()) } } @@ -704,7 +723,7 @@ object ChatModel { } i-- } - chatItemsChangesListener?.read(if (itemIds != null) markedReadIds else null, items) + chatState.itemsRead(if (itemIds != null) markedReadIds else null, items) } return markedRead to mentionsMarkedRead } @@ -917,7 +936,7 @@ object ChatModel { suspend fun addLiveDummy(chatInfo: ChatInfo): ChatItem { val cItem = ChatItem.liveDummy(chatInfo is ChatInfo.Direct) withContext(Dispatchers.Main) { - chatsContext.chatItems.addAndNotify(cItem, contentTag = null) + chatsContext.addToChatItems(cItem) } return cItem } @@ -926,7 +945,7 @@ object ChatModel { if (chatsContext.chatItems.value.lastOrNull()?.id == ChatItem.TEMP_LIVE_CHAT_ITEM_ID) { withApi { withContext(Dispatchers.Main) { - chatsContext.chatItems.removeLastAndNotify(contentTag = null) + chatsContext.removeLastChatItems() } } } @@ -1000,8 +1019,6 @@ object ChatModel { withApi { withContext(Dispatchers.Main) { showingInvitation.value = null - // TODO [contexts] - why does clearAndNotify operates with listeners for both contexts? - // TODO - should it be called for both contexts here instead? chatsContext.chatItems.clearAndNotify() chatModel.chatId.value = withId } @@ -1015,7 +1032,6 @@ object ChatModel { if (id == showingInvitation.value?.connId) { withContext(Dispatchers.Main) { showingInvitation.value = null - // TODO [contexts] see replaceConnReqView chatsContext.chatItems.clearAndNotify() chatModel.chatId.value = null } @@ -1067,15 +1083,6 @@ object ChatModel { fun connectedToRemote(): Boolean = currentRemoteHost.value != null || remoteCtrlSession.value?.active == true } -interface ChatItemsChangesListener { - // pass null itemIds if the whole chat now read - fun read(itemIds: Set?, newItems: List) - fun added(item: Pair, index: Int) - // itemId, index in old chatModel.chatItems (before the update), isRcvNew (is item unread or not) - fun removed(itemIds: List>, newItems: List) - fun cleared() -} - data class ShowingInvitation( val connId: String, val connLink: CreatedConnLink, @@ -2697,11 +2704,6 @@ fun MutableState>.add(index: Int, elem: Chat) { value = SnapshotStateList().apply { addAll(value); add(index, elem) } } -fun MutableState>.addAndNotify(index: Int, elem: ChatItem, contentTag: MsgContentTag?) { - val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext - value = SnapshotStateList().apply { addAll(value); add(index, elem); chatsCtx.chatItemsChangesListener?.added(elem.id to elem.isRcvNew, index) } -} - fun MutableState>.add(elem: Chat) { value = SnapshotStateList().apply { addAll(value); add(elem) } } @@ -2709,12 +2711,6 @@ fun MutableState>.add(elem: Chat) { // For some reason, Kotlin version crashes if the list is empty fun MutableList.removeAll(predicate: (T) -> Boolean): Boolean = if (isEmpty()) false else remAll(predicate) -// Adds item to chatItems and notifies a listener about newly added item -fun MutableState>.addAndNotify(elem: ChatItem, contentTag: MsgContentTag?) { - val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext - value = SnapshotStateList().apply { addAll(value); add(elem); chatsCtx.chatItemsChangesListener?.added(elem.id to elem.isRcvNew, lastIndex) } -} - fun MutableState>.addAll(index: Int, elems: List) { value = SnapshotStateList().apply { addAll(value); addAll(index, elems) } } @@ -2727,6 +2723,7 @@ fun MutableState>.removeAll(block: (Chat) -> Boolean) { value = SnapshotStateList().apply { addAll(value); removeAll(block) } } +// TODO [contexts] operates with both contexts? // Removes item(s) from chatItems and notifies a listener about removed item(s) fun MutableState>.removeAllAndNotify(block: (ChatItem) -> Boolean) { val toRemove = ArrayList>() @@ -2741,8 +2738,8 @@ fun MutableState>.removeAllAndNotify(block: (ChatIte } } if (toRemove.isNotEmpty()) { - chatModel.chatsContext.chatItemsChangesListener?.removed(toRemove, value) - chatModel.secondaryChatsContext.chatItemsChangesListener?.removed(toRemove, value) + chatModel.chatsContext.chatState.itemsRemoved(toRemove, value) + chatModel.secondaryChatsContext.value?.chatState?.itemsRemoved(toRemove, value) } } @@ -2754,18 +2751,6 @@ fun MutableState>.removeAt(index: Int): Chat { return res } -fun MutableState>.removeLastAndNotify(contentTag: MsgContentTag?) { - val removed: Triple - value = SnapshotStateList().apply { - addAll(value) - val remIndex = lastIndex - val rem = removeLast() - removed = Triple(rem.id, remIndex, rem.isRcvNew) - } - val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext - chatsCtx.chatItemsChangesListener?.removed(listOf(removed), value) -} - fun MutableState>.replaceAll(elems: List) { value = SnapshotStateList().apply { addAll(elems) } } @@ -2774,11 +2759,12 @@ fun MutableState>.clear() { value = SnapshotStateList() } +// TODO [contexts] operates with both contexts? // Removes all chatItems and notifies a listener about it fun MutableState>.clearAndNotify() { value = SnapshotStateList() - chatModel.chatsContext.chatItemsChangesListener?.cleared() - chatModel.secondaryChatsContext.chatItemsChangesListener?.cleared() + chatModel.chatsContext.chatState.clear() + chatModel.secondaryChatsContext.value?.chatState?.clear() } fun State>.asReversed(): MutableList = value.asReversed() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index b5f44fdebc..a557cf93cf 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -1553,7 +1553,7 @@ object ChatController { chatModel.chatsContext.clearChat(chat.remoteHostId, updatedChatInfo) } withContext(Dispatchers.Main) { - chatModel.secondaryChatsContext.clearChat(chat.remoteHostId, updatedChatInfo) + chatModel.secondaryChatsContext.value?.clearChat(chat.remoteHostId, updatedChatInfo) } ntfManager.cancelNotificationsForChat(chat.chatInfo.id) close?.invoke() @@ -2493,9 +2493,7 @@ object ChatController { chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.toMember) } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - chatModel.secondaryChatsContext.upsertGroupMember(rhId, r.groupInfo, r.toMember) - } + chatModel.secondaryChatsContext.value?.upsertGroupMember(rhId, r.groupInfo, r.toMember) } } } @@ -2550,10 +2548,8 @@ object ChatController { } } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - if (cItem.isReport) { - chatModel.secondaryChatsContext.addChatItem(rhId, cInfo, cItem) - } + if (cItem.isReport) { + chatModel.secondaryChatsContext.value?.addChatItem(rhId, cInfo, cItem) } } } else if (cItem.isRcvNew && cInfo.ntfsEnabled(cItem)) { @@ -2583,10 +2579,8 @@ object ChatController { chatModel.chatsContext.updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus) } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - if (cItem.isReport) { - chatModel.secondaryChatsContext.updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus) - } + if (cItem.isReport) { + chatModel.secondaryChatsContext.value?.updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus) } } } @@ -2599,10 +2593,8 @@ object ChatController { chatModel.chatsContext.updateChatItem(r.reaction.chatInfo, r.reaction.chatReaction.chatItem) } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - if (r.reaction.chatReaction.chatItem.isReport) { - chatModel.secondaryChatsContext.updateChatItem(r.reaction.chatInfo, r.reaction.chatReaction.chatItem) - } + if (r.reaction.chatReaction.chatItem.isReport) { + chatModel.secondaryChatsContext.value?.updateChatItem(r.reaction.chatInfo, r.reaction.chatReaction.chatItem) } } } @@ -2646,13 +2638,11 @@ object ChatController { } } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - if (cItem.isReport) { - if (toChatItem == null) { - chatModel.secondaryChatsContext.removeChatItem(rhId, cInfo, cItem) - } else { - chatModel.secondaryChatsContext.upsertChatItem(rhId, cInfo, toChatItem.chatItem) - } + if (cItem.isReport) { + if (toChatItem == null) { + chatModel.secondaryChatsContext.value?.removeChatItem(rhId, cInfo, cItem) + } else { + chatModel.secondaryChatsContext.value?.upsertChatItem(rhId, cInfo, toChatItem.chatItem) } } } @@ -2722,10 +2712,8 @@ object ChatController { } } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - if (r.withMessages) { - chatModel.secondaryChatsContext.removeMemberItems(rhId, r.groupInfo.membership, byMember = r.member, r.groupInfo) - } + if (r.withMessages) { + chatModel.secondaryChatsContext.value?.removeMemberItems(rhId, r.groupInfo.membership, byMember = r.member, r.groupInfo) } } } @@ -2738,11 +2726,9 @@ object ChatController { } } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - chatModel.secondaryChatsContext.upsertGroupMember(rhId, r.groupInfo, r.deletedMember) - if (r.withMessages) { - chatModel.secondaryChatsContext.removeMemberItems(rhId, r.deletedMember, byMember = r.byMember, r.groupInfo) - } + chatModel.secondaryChatsContext.value?.upsertGroupMember(rhId, r.groupInfo, r.deletedMember) + if (r.withMessages) { + chatModel.secondaryChatsContext.value?.removeMemberItems(rhId, r.deletedMember, byMember = r.byMember, r.groupInfo) } } } @@ -2752,9 +2738,7 @@ object ChatController { chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member) } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - chatModel.secondaryChatsContext.upsertGroupMember(rhId, r.groupInfo, r.member) - } + chatModel.secondaryChatsContext.value?.upsertGroupMember(rhId, r.groupInfo, r.member) } } is CR.MemberRole -> @@ -2763,9 +2747,7 @@ object ChatController { chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member) } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - chatModel.secondaryChatsContext.upsertGroupMember(rhId, r.groupInfo, r.member) - } + chatModel.secondaryChatsContext.value?.upsertGroupMember(rhId, r.groupInfo, r.member) } } is CR.MembersRoleUser -> @@ -2776,10 +2758,8 @@ object ChatController { } } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - r.members.forEach { member -> - chatModel.secondaryChatsContext.upsertGroupMember(rhId, r.groupInfo, member) - } + r.members.forEach { member -> + chatModel.secondaryChatsContext.value?.upsertGroupMember(rhId, r.groupInfo, member) } } } @@ -2789,9 +2769,7 @@ object ChatController { chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member) } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - chatModel.secondaryChatsContext.upsertGroupMember(rhId, r.groupInfo, r.member) - } + chatModel.secondaryChatsContext.value?.upsertGroupMember(rhId, r.groupInfo, r.member) } } is CR.GroupDeleted -> // TODO update user member @@ -3167,10 +3145,8 @@ object ChatController { val cItem = aChatItem.chatItem withContext(Dispatchers.Main) { chatModel.chatsContext.upsertChatItem(rh, cInfo, cItem) } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - if (cItem.isReport) { - chatModel.secondaryChatsContext.upsertChatItem(rh, cInfo, cItem) - } + if (cItem.isReport) { + chatModel.secondaryChatsContext.value?.upsertChatItem(rh, cInfo, cItem) } } } @@ -3212,8 +3188,8 @@ object ChatController { } } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - val chatsCtx = chatModel.secondaryChatsContext + val chatsCtx = chatModel.secondaryChatsContext.value + if (chatsCtx != null) { r.chatItemIDs.forEach { itemId -> val cItem = chatsCtx.chatItems.value.lastOrNull { it.id == itemId } ?: return@forEach if (chatModel.chatId.value != null) { @@ -3240,10 +3216,8 @@ object ChatController { } else { val createdChat = withContext(Dispatchers.Main) { chatModel.chatsContext.upsertChatItem(rh, cInfo, cItem) } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - if (cItem.content.msgContent is MsgContent.MCReport) { - chatModel.secondaryChatsContext.upsertChatItem(rh, cInfo, cItem) - } + if (cItem.content.msgContent is MsgContent.MCReport) { + chatModel.secondaryChatsContext.value?.upsertChatItem(rh, cInfo, cItem) } } if (createdChat) { @@ -3296,11 +3270,9 @@ object ChatController { chatModel.chatsContext.popChatCollector.clear() } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - chatModel.secondaryChatsContext.chatItems.clearAndNotify() - chatModel.secondaryChatsContext.chats.clear() - chatModel.secondaryChatsContext.popChatCollector.clear() - } + chatModel.secondaryChatsContext.value?.chatItems?.clearAndNotify() + chatModel.secondaryChatsContext.value?.chats?.clear() + chatModel.secondaryChatsContext.value?.popChatCollector?.clear() } } val statuses = apiGetNetworkStatuses(rhId) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt index 5efd3747a3..5b9e63963c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt @@ -73,7 +73,7 @@ abstract class NtfManager { } val cInfo = chatModel.getChat(chatId)?.chatInfo chatModel.clearOverlays.value = true - if (cInfo != null && (cInfo is ChatInfo.Direct || cInfo is ChatInfo.Group)) openChat(null, cInfo) + if (cInfo != null && (cInfo is ChatInfo.Direct || cInfo is ChatInfo.Group)) openChat(secondaryChatsCtx = null, rhId = null, cInfo) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt index d5a99d9acb..2a77d0a6dc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt @@ -55,6 +55,7 @@ import java.io.File @Composable fun ChatInfoView( + chatsCtx: ChatModel.ChatsContext, chatModel: ChatModel, contact: Contact, connectionStats: ConnectionStats?, @@ -97,7 +98,7 @@ fun ChatInfoView( val previousChatTTL = chatItemTTL.value chatItemTTL.value = it - setChatTTLAlert(chat.remoteHostId, chat.chatInfo, chatItemTTL, previousChatTTL, deletingItems) + setChatTTLAlert(chatsCtx, chat.remoteHostId, chat.chatInfo, chatItemTTL, previousChatTTL, deletingItems) }, connStats = connStats, contactNetworkStatus.value, @@ -1332,6 +1333,7 @@ fun queueInfoText(info: Pair): String { } fun setChatTTLAlert( + chatsCtx: ChatModel.ChatsContext, rhId: Long?, chatInfo: ChatInfo, selectedChatTTL: MutableState, @@ -1351,7 +1353,7 @@ fun setChatTTLAlert( } else MR.strings.enable_automatic_deletion_question), text = generalGetString(if (newTTLToUse.neverExpires) MR.strings.disable_automatic_deletion_message else MR.strings.change_automatic_chat_deletion_message), confirmText = generalGetString(if (newTTLToUse.neverExpires) MR.strings.disable_automatic_deletion else MR.strings.delete_messages), - onConfirm = { setChatTTL(rhId, chatInfo, selectedChatTTL, progressIndicator, previousChatTTL) }, + onConfirm = { setChatTTL(chatsCtx, rhId, chatInfo, selectedChatTTL, progressIndicator, previousChatTTL) }, onDismiss = { selectedChatTTL.value = previousChatTTL }, onDismissRequest = { selectedChatTTL.value = previousChatTTL }, destructive = true, @@ -1359,6 +1361,7 @@ fun setChatTTLAlert( } private fun setChatTTL( + chatsCtx: ChatModel.ChatsContext, rhId: Long?, chatInfo: ChatInfo, chatTTL: MutableState, @@ -1369,16 +1372,16 @@ private fun setChatTTL( withBGApi { try { chatModel.controller.setChatTTL(rhId, chatInfo.chatType, chatInfo.apiId, chatTTL.value) - afterSetChatTTL(rhId, chatInfo, progressIndicator) + afterSetChatTTL(chatsCtx, rhId, chatInfo, progressIndicator) } catch (e: Exception) { chatTTL.value = previousChatTTL - afterSetChatTTL(rhId, chatInfo, progressIndicator) + afterSetChatTTL(chatsCtx, rhId, chatInfo, progressIndicator) AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_changing_message_deletion), e.stackTraceToString()) } } } -private suspend fun afterSetChatTTL(rhId: Long?, chatInfo: ChatInfo, progressIndicator: MutableState) { +private suspend fun afterSetChatTTL(chatsCtx: ChatModel.ChatsContext, rhId: Long?, chatInfo: ChatInfo, progressIndicator: MutableState) { try { val pagination = ChatPagination.Initial(ChatPagination.INITIAL_COUNT) val (chat, navInfo) = controller.apiGetChat(rhId, chatInfo.chatType, chatInfo.apiId, null, pagination) ?: return @@ -1393,9 +1396,9 @@ private suspend fun afterSetChatTTL(rhId: Long?, chatInfo: ChatInfo, progressInd } if (chat.remoteHostId != chatModel.remoteHostId() || chat.id != chatModel.chatId.value) return processLoadedChat( + chatsCtx, chat, navInfo, - contentTag = null, pagination = pagination, openAroundItemId = null ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt index c13eb8f139..9c36f4896b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt @@ -209,7 +209,7 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools SectionItemView( click = { withBGApi { - openChat(chatRh, forwardedFromItem.chatInfo) + openChat(secondaryChatsCtx = null, chatRh, forwardedFromItem.chatInfo) ModalManager.end.closeModals() } }, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt index 51a6d24e21..eabe9cb60a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt @@ -11,43 +11,42 @@ import kotlin.math.min const val TRIM_KEEP_COUNT = 200 suspend fun apiLoadSingleMessage( + chatsCtx: ChatModel.ChatsContext, rhId: Long?, chatType: ChatType, apiId: Long, - itemId: Long, - contentTag: MsgContentTag?, + itemId: Long ): ChatItem? = coroutineScope { - val (chat, _) = chatModel.controller.apiGetChat(rhId, chatType, apiId, contentTag, ChatPagination.Around(itemId, 0), "") ?: return@coroutineScope null + val (chat, _) = chatModel.controller.apiGetChat(rhId, chatType, apiId, chatsCtx.contentTag, ChatPagination.Around(itemId, 0), "") ?: return@coroutineScope null chat.chatItems.firstOrNull() } suspend fun apiLoadMessages( + chatsCtx: ChatModel.ChatsContext, rhId: Long?, chatType: ChatType, apiId: Long, - contentTag: MsgContentTag?, pagination: ChatPagination, search: String = "", openAroundItemId: Long? = null, visibleItemIndexesNonReversed: () -> IntRange = { 0 .. 0 } ) = coroutineScope { - val (chat, navInfo) = chatModel.controller.apiGetChat(rhId, chatType, apiId, contentTag, pagination, search) ?: return@coroutineScope + val (chat, navInfo) = chatModel.controller.apiGetChat(rhId, chatType, apiId, chatsCtx.contentTag, pagination, search) ?: return@coroutineScope // For .initial allow the chatItems to be empty as well as chatModel.chatId to not match this chat because these values become set after .initial finishes /** When [openAroundItemId] is provided, chatId can be different too */ if (((chatModel.chatId.value != chat.id || chat.chatItems.isEmpty()) && pagination !is ChatPagination.Initial && pagination !is ChatPagination.Last && openAroundItemId == null) || !isActive) return@coroutineScope - processLoadedChat(chat, navInfo, contentTag, pagination, openAroundItemId, visibleItemIndexesNonReversed) + processLoadedChat(chatsCtx, chat, navInfo, pagination, openAroundItemId, visibleItemIndexesNonReversed) } suspend fun processLoadedChat( + chatsCtx: ChatModel.ChatsContext, chat: Chat, navInfo: NavigationInfo, - contentTag: MsgContentTag?, pagination: ChatPagination, openAroundItemId: Long?, visibleItemIndexesNonReversed: () -> IntRange = { 0 .. 0 } ) { - val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext val chatState = chatsCtx.chatState val (splits, unreadAfterItemId, totalAfter, unreadTotal, unreadAfter, unreadAfterNewestLoaded) = chatState val oldItems = chatsCtx.chatItems.value @@ -55,7 +54,7 @@ suspend fun processLoadedChat( when (pagination) { is ChatPagination.Initial -> { val newSplits = if (chat.chatItems.isNotEmpty() && navInfo.afterTotal > 0) listOf(chat.chatItems.last().id) else emptyList() - if (contentTag == null) { + if (chatsCtx.contentTag == null) { // update main chats, not content tagged withContext(Dispatchers.Main) { val oldChat = chatModel.chatsContext.getChat(chat.id) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsMerger.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsMerger.kt index d318cf05fd..d98c041478 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsMerger.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsMerger.kt @@ -237,24 +237,8 @@ data class ActiveChatState ( unreadAfter.value = 0 unreadAfterNewestLoaded.value = 0 } -} -fun visibleItemIndexesNonReversed(mergedItems: State, reversedItemsSize: Int, listState: LazyListState): IntRange { - val zero = 0 .. 0 - if (listState.layoutInfo.totalItemsCount == 0) return zero - val newest = mergedItems.value.items.getOrNull(listState.firstVisibleItemIndex)?.startIndexInReversedItems - val oldest = mergedItems.value.items.getOrNull(listState.layoutInfo.visibleItemsInfo.last().index)?.lastIndexInReversed() - if (newest == null || oldest == null) return zero - val range = reversedItemsSize - oldest .. reversedItemsSize - newest - if (range.first < 0 || range.last < 0) return zero - - // visible items mapped to their underlying data structure which is chatModel.chatItems - return range -} - -fun recalculateChatStatePositions(chatState: ActiveChatState) = object: ChatItemsChangesListener { - override fun read(itemIds: Set?, newItems: List) { - val (_, unreadAfterItemId, _, unreadTotal, unreadAfter) = chatState + fun itemsRead(itemIds: Set?, newItems: List) { if (itemIds == null) { // special case when the whole chat became read unreadTotal.value = 0 @@ -287,14 +271,15 @@ fun recalculateChatStatePositions(chatState: ActiveChatState) = object: ChatItem unreadTotal.value = newUnreadTotal unreadAfter.value = newUnreadAfter } - override fun added(item: Pair, index: Int) { + + fun itemAdded(item: Pair) { if (item.second) { - chatState.unreadAfter.value++ - chatState.unreadTotal.value++ + unreadAfter.value++ + unreadTotal.value++ } } - override fun removed(itemIds: List>, newItems: List) { - val (splits, unreadAfterItemId, totalAfter, unreadTotal, unreadAfter) = chatState + + fun itemsRemoved(itemIds: List>, newItems: List) { val newSplits = ArrayList() for (split in splits.value) { val index = itemIds.indexOfFirst { it.first == split } @@ -343,7 +328,19 @@ fun recalculateChatStatePositions(chatState: ActiveChatState) = object: ChatItem totalAfter.value -= itemIds.size } } - override fun cleared() { chatState.clear() } +} + +fun visibleItemIndexesNonReversed(mergedItems: State, reversedItemsSize: Int, listState: LazyListState): IntRange { + val zero = 0 .. 0 + if (listState.layoutInfo.totalItemsCount == 0) return zero + val newest = mergedItems.value.items.getOrNull(listState.firstVisibleItemIndex)?.startIndexInReversedItems + val oldest = mergedItems.value.items.getOrNull(listState.layoutInfo.visibleItemsInfo.last().index)?.lastIndexInReversed() + if (newest == null || oldest == null) return zero + val range = reversedItemsSize - oldest .. reversedItemsSize - newest + if (range.first < 0 || range.last < 0) return zero + + // visible items mapped to their underlying data structure which is chatModel.chatItems + return range } /** Helps in debugging */ diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 2a18419746..ef82b9a35b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -57,8 +57,8 @@ data class ItemSeparation(val timestamp: Boolean, val largeGap: Boolean, val dat // staleChatId means the id that was before chatModel.chatId becomes null. It's needed for Android only to make transition from chat // to chat list smooth. Otherwise, chat view will become blank right before the transition starts fun ChatView( + chatsCtx: ChatModel.ChatsContext, staleChatId: State, - contentTag: MsgContentTag?, scrollToItemId: MutableState = remember { mutableStateOf(null) }, onComposed: suspend (chatId: String) -> Unit ) { @@ -99,7 +99,7 @@ fun ChatView( .distinctUntilChanged() .filterNotNull() .collect { chatId -> - if (contentTag == null) { + if (chatsCtx.contentTag == null) { markUnreadChatAsRead(chatId) } showSearch.value = false @@ -112,7 +112,6 @@ fun ChatView( val chatRh = remoteHostId.value // We need to have real unreadCount value for displaying it inside top right button // Having activeChat reloaded on every change in it is inefficient (UI lags) - val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext val unreadCount = remember { derivedStateOf { chatsCtx.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.chatStats?.unreadCount ?: 0 @@ -121,7 +120,6 @@ fun ChatView( val clipboard = LocalClipboardManager.current CompositionLocalProvider( LocalAppBarHandler provides rememberAppBarHandler(chatInfo.id, keyboardCoversBar = false), - LocalContentTag provides contentTag ) { when (chatInfo) { is ChatInfo.Direct, is ChatInfo.Group, is ChatInfo.Local -> { @@ -135,15 +133,16 @@ fun ChatView( val sameText = searchText.value == value // showSearch can be false with empty text when it was closed manually after clicking on message from search to load .around it // (required on Android to have this check to prevent call to search with old text) - val emptyAndClosedSearch = searchText.value.isEmpty() && !showSearch.value && contentTag == null + val emptyAndClosedSearch = searchText.value.isEmpty() && !showSearch.value && chatsCtx.contentTag == null val c = chatModel.getChat(chatInfo.id) if (sameText || emptyAndClosedSearch || c == null || chatModel.chatId.value != chatInfo.id) return@onSearchValueChanged withBGApi { - apiFindMessages(c, value, contentTag) + apiFindMessages(chatsCtx, c, value) searchText.value = value } } ChatLayout( + chatsCtx = chatsCtx, remoteHostId = remoteHostId, chatInfo = activeChatInfo, unreadCount, @@ -175,7 +174,7 @@ fun ChatView( } } else { SelectedItemsButtonsToolbar( - contentTag = contentTag, + chatsCtx = chatsCtx, selectedChatItems = selectedChatItems, chatInfo = chatInfo, deleteItems = { canDeleteForAll -> @@ -287,7 +286,7 @@ fun ChatView( code = chatModel.controller.apiGetContactCode(chatRh, chatInfo.apiId)?.second preloadedCode = code } - ChatInfoView(chatModel, chatInfo.contact, contactInfo?.first, contactInfo?.second, chatInfo.localAlias, code, close) { + ChatInfoView(chatsCtx, chatModel, chatInfo.contact, contactInfo?.first, contactInfo?.second, chatInfo.localAlias, code, close) { showSearch.value = true } } else if (chatInfo is ChatInfo.Group) { @@ -297,7 +296,7 @@ fun ChatView( link = chatModel.controller.apiGetGroupLink(chatRh, chatInfo.groupInfo.groupId) preloadedLink = link } - GroupChatInfoView(chatRh, chatInfo.id, link?.first, link?.second, selectedItems, appBar, scrollToItemId, { + GroupChatInfoView(chatsCtx, chatRh, chatInfo.id, link?.first, link?.second, selectedItems, appBar, scrollToItemId, { link = it preloadedLink = it }, close, { showSearch.value = true }) @@ -344,7 +343,7 @@ fun ChatView( setGroupMembers(chatRh, groupInfo, chatModel) if (!isActive) return@launch - if (contentTag == null) { + if (chatsCtx.contentTag == null) { ModalManager.end.closeModals() } ModalManager.end.showModalCloseable(true) { close -> @@ -358,12 +357,12 @@ fun ChatView( val c = chatModel.getChat(chatId) if (chatModel.chatId.value != chatId) return@ChatLayout if (c != null) { - apiLoadMessages(c.remoteHostId, c.chatInfo.chatType, c.chatInfo.apiId, contentTag, pagination, searchText.value, null, visibleItemIndexes) + apiLoadMessages(chatsCtx, c.remoteHostId, c.chatInfo.chatType, c.chatInfo.apiId, pagination, searchText.value, null, visibleItemIndexes) } }, deleteMessage = { itemId, mode -> withBGApi { - val toDeleteItem = reversedChatItemsStatic(contentTag).lastOrNull { it.id == itemId } + val toDeleteItem = reversedChatItemsStatic(chatsCtx).lastOrNull { it.id == itemId } val toModerate = toDeleteItem?.memberToModerate(chatInfo) val groupInfo = toModerate?.first val groupMember = toModerate?.second @@ -400,13 +399,11 @@ fun ChatView( } } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - if (deletedChatItem.isReport) { - if (toChatItem != null) { - chatModel.secondaryChatsContext.upsertChatItem(chatRh, chatInfo, toChatItem) - } else { - chatModel.secondaryChatsContext.removeChatItem(chatRh, chatInfo, deletedChatItem) - } + if (deletedChatItem.isReport) { + if (toChatItem != null) { + chatModel.secondaryChatsContext.value?.upsertChatItem(chatRh, chatInfo, toChatItem) + } else { + chatModel.secondaryChatsContext.value?.removeChatItem(chatRh, chatInfo, deletedChatItem) } } } @@ -524,10 +521,8 @@ fun ChatView( chatModel.chatsContext.updateChatItem(cInfo, updatedCI) } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - if (cItem.isReport) { - chatModel.secondaryChatsContext.updateChatItem(cInfo, updatedCI) - } + if (cItem.isReport) { + chatModel.secondaryChatsContext.value?.updateChatItem(cInfo, updatedCI) } } } @@ -592,9 +587,7 @@ fun ChatView( ) } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - chatModel.secondaryChatsContext.markChatItemsRead(chatRh, chatInfo.id, itemsIds) - } + chatModel.secondaryChatsContext.value?.markChatItemsRead(chatRh, chatInfo.id, itemsIds) } } }, @@ -610,9 +603,7 @@ fun ChatView( ) } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - chatModel.secondaryChatsContext.markChatItemsRead(chatRh, chatInfo.id) - } + chatModel.secondaryChatsContext.value?.markChatItemsRead(chatRh, chatInfo.id) } } }, @@ -676,6 +667,7 @@ fun startChatCall(remoteHostId: Long?, chatInfo: ChatInfo, media: CallMediaType) @Composable fun ChatLayout( + chatsCtx: ChatModel.ChatsContext, remoteHostId: State, chatInfo: State, unreadCount: State, @@ -753,8 +745,7 @@ fun ChatLayout( sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp) ) { val composeViewHeight = remember { mutableStateOf(0.dp) } - val contentTag = LocalContentTag.current - Box(Modifier.fillMaxSize().chatViewBackgroundModifier(MaterialTheme.colors, MaterialTheme.wallpaper, LocalAppBarHandler.current?.backgroundGraphicsLayerSize, LocalAppBarHandler.current?.backgroundGraphicsLayer, contentTag == null)) { + Box(Modifier.fillMaxSize().chatViewBackgroundModifier(MaterialTheme.colors, MaterialTheme.wallpaper, LocalAppBarHandler.current?.backgroundGraphicsLayerSize, LocalAppBarHandler.current?.backgroundGraphicsLayer, drawWallpaper = chatsCtx.contentTag == null)) { val remoteHostId = remember { remoteHostId }.value val chatInfo = remember { chatInfo }.value val oneHandUI = remember { appPrefs.oneHandUI.state } @@ -768,7 +759,7 @@ fun ChatLayout( override fun calculateScrollDistance(offset: Float, size: Float, containerSize: Float): Float = 0f }) { ChatItemsList( - remoteHostId, chatInfo, unreadCount, composeState, composeViewHeight, searchValue, + chatsCtx, remoteHostId, chatInfo, unreadCount, composeState, composeViewHeight, searchValue, useLinkPreviews, linkMode, scrollToItemId, selectedChatItems, showMemberInfo, showChatInfo = info, loadMessages, deleteMessage, deleteMessages, archiveReports, receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat, forwardItem, updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember, @@ -791,7 +782,7 @@ fun ChatLayout( } } } - if (contentTag == MsgContentTag.Report) { + if (chatsCtx.contentTag == MsgContentTag.Report) { Column( Modifier .layoutId(CHAT_COMPOSE_LAYOUT_ID) @@ -802,7 +793,7 @@ fun ChatLayout( AnimatedVisibility(selectedChatItems.value != null) { if (chatInfo != null) { SelectedItemsButtonsToolbar( - contentTag = contentTag, + chatsCtx = chatsCtx, selectedChatItems = selectedChatItems, chatInfo = chatInfo, deleteItems = { _ -> @@ -842,7 +833,7 @@ fun ChatLayout( } val reportsCount = reportsCount(chatInfo?.id) if (oneHandUI.value && chatBottomBar.value) { - if (contentTag == null && reportsCount > 0) { + if (chatsCtx.contentTag == null && reportsCount > 0) { ReportedCountToolbar(reportsCount, withStatusBar = true, showGroupReports) } else { StatusBarBackground() @@ -850,14 +841,14 @@ fun ChatLayout( } else { NavigationBarBackground(true, oneHandUI.value, noAlpha = true) } - if (contentTag == MsgContentTag.Report) { + if (chatsCtx.contentTag == MsgContentTag.Report) { if (oneHandUI.value) { StatusBarBackground() } Column(if (oneHandUI.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) { Box { if (selectedChatItems.value == null) { - GroupReportsAppBar(contentTag, { ModalManager.end.closeModal() }, onSearchValueChanged) + GroupReportsAppBar(chatsCtx, { ModalManager.end.closeModal() }, onSearchValueChanged) } else { SelectedItemsCounterToolbar(selectedChatItems, !oneHandUI.value) } @@ -868,13 +859,13 @@ fun ChatLayout( Box { if (selectedChatItems.value == null) { if (chatInfo != null) { - ChatInfoToolbar(chatInfo, contentTag, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch) + ChatInfoToolbar(chatsCtx, chatInfo, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch) } } else { SelectedItemsCounterToolbar(selectedChatItems, !oneHandUI.value || !chatBottomBar.value) } } - if (contentTag == null && reportsCount > 0 && (!oneHandUI.value || !chatBottomBar.value)) { + if (chatsCtx.contentTag == null && reportsCount > 0 && (!oneHandUI.value || !chatBottomBar.value)) { ReportedCountToolbar(reportsCount, withStatusBar = false, showGroupReports) } } @@ -886,8 +877,8 @@ fun ChatLayout( @Composable fun BoxScope.ChatInfoToolbar( + chatsCtx: ChatModel.ChatsContext, chatInfo: ChatInfo, - contentTag: MsgContentTag?, back: () -> Unit, info: () -> Unit, startCall: (CallMediaType) -> Unit, @@ -909,7 +900,7 @@ fun BoxScope.ChatInfoToolbar( showSearch.value = false } } - if (appPlatform.isAndroid && contentTag == null) { + if (appPlatform.isAndroid && chatsCtx.contentTag == null) { BackHandler(onBack = onBackClicked) } val barButtons = arrayListOf<@Composable RowScope.() -> Unit>() @@ -1148,6 +1139,7 @@ private var reportsListState: LazyListState? = null @Composable fun BoxScope.ChatItemsList( + chatsCtx: ChatModel.ChatsContext, remoteHostId: Long?, chatInfo: ChatInfo, unreadCount: State, @@ -1205,107 +1197,107 @@ fun BoxScope.ChatItemsList( val searchValueIsEmpty = remember { derivedStateOf { searchValue.value.isEmpty() } } val searchValueIsNotBlank = remember { derivedStateOf { searchValue.value.isNotBlank() } } val revealedItems = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(setOf()) } - val contentTag = LocalContentTag.current // not using reversedChatItems inside to prevent possible derivedState bug in Compose when one derived state access can cause crash asking another derived state - val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext - val mergedItems = remember { - derivedStateOf { - MergedItems.create(chatsCtx.chatItems.value.asReversed(), unreadCount, revealedItems.value, chatsCtx.chatState) + if (chatsCtx != null) { + val mergedItems = remember { + derivedStateOf { + MergedItems.create(chatsCtx.chatItems.value.asReversed(), unreadCount, revealedItems.value, chatsCtx.chatState) + } } - } - val reversedChatItems = remember { derivedStateOf { chatsCtx.chatItems.value.asReversed() } } - val reportsCount = reportsCount(chatInfo.id) - val topPaddingToContent = topPaddingToContent(chatView = contentTag == null, contentTag == null && reportsCount > 0) - val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent.roundToPx() }) - val numberOfBottomAppBars = numberOfBottomAppBars() - /** determines height based on window info and static height of two AppBars. It's needed because in the first graphic frame height of - * [composeViewHeight] is unknown, but we need to set scroll position for unread messages already so it will be correct before the first frame appears - * */ - val maxHeightForList = rememberUpdatedState( - with(LocalDensity.current) { LocalWindowHeight().roundToPx() - topPaddingToContentPx.value - (AppBarHeight * fontSizeSqrtMultiplier * numberOfBottomAppBars).roundToPx() } - ) - val resetListState = remember { mutableStateOf(false) } - remember(chatModel.openAroundItemId.value) { - if (chatModel.openAroundItemId.value != null) { - closeSearch() - resetListState.value = !resetListState.value + val reversedChatItems = remember { derivedStateOf { chatsCtx.chatItems.value.asReversed() } } + val reportsCount = reportsCount(chatInfo.id) + val topPaddingToContent = topPaddingToContent( + chatView = chatsCtx.contentTag == null, + additionalTopBar = chatsCtx.contentTag == null && reportsCount > 0 + ) + val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent.roundToPx() }) + val numberOfBottomAppBars = numberOfBottomAppBars() + + /** determines height based on window info and static height of two AppBars. It's needed because in the first graphic frame height of + * [composeViewHeight] is unknown, but we need to set scroll position for unread messages already so it will be correct before the first frame appears + * */ + val maxHeightForList = rememberUpdatedState( + with(LocalDensity.current) { LocalWindowHeight().roundToPx() - topPaddingToContentPx.value - (AppBarHeight * fontSizeSqrtMultiplier * numberOfBottomAppBars).roundToPx() } + ) + val resetListState = remember { mutableStateOf(false) } + remember(chatModel.openAroundItemId.value) { + if (chatModel.openAroundItemId.value != null) { + closeSearch() + resetListState.value = !resetListState.value + } } - } - val highlightedItems = remember { mutableStateOf(setOf()) } - val hoveredItemId = remember { mutableStateOf(null as Long?) } - val listState = rememberUpdatedState(rememberSaveable(chatInfo.id, searchValueIsEmpty.value, resetListState.value, saver = LazyListState.Saver) { - val openAroundItemId = chatModel.openAroundItemId.value - val index = mergedItems.value.indexInParentItems[openAroundItemId] ?: mergedItems.value.items.indexOfLast { it.hasUnread() } - val reportsState = reportsListState - if (openAroundItemId != null) { - highlightedItems.value += openAroundItemId - chatModel.openAroundItemId.value = null + val highlightedItems = remember { mutableStateOf(setOf()) } + val hoveredItemId = remember { mutableStateOf(null as Long?) } + val listState = rememberUpdatedState(rememberSaveable(chatInfo.id, searchValueIsEmpty.value, resetListState.value, saver = LazyListState.Saver) { + val openAroundItemId = chatModel.openAroundItemId.value + val index = mergedItems.value.indexInParentItems[openAroundItemId] ?: mergedItems.value.items.indexOfLast { it.hasUnread() } + val reportsState = reportsListState + if (openAroundItemId != null) { + highlightedItems.value += openAroundItemId + chatModel.openAroundItemId.value = null + } + hoveredItemId.value = null + if (reportsState != null) { + reportsListState = null + reportsState + } else if (index <= 0 || !searchValueIsEmpty.value) { + LazyListState(0, 0) + } else { + LazyListState(index + 1, -maxHeightForList.value) + } + }) + SaveReportsStateOnDispose(chatsCtx, listState) + val maxHeight = remember { derivedStateOf { listState.value.layoutInfo.viewportEndOffset - topPaddingToContentPx.value } } + val loadingMoreItems = remember { mutableStateOf(false) } + val animatedScrollingInProgress = remember { mutableStateOf(false) } + val ignoreLoadingRequests = remember(remoteHostId) { mutableSetOf() } + LaunchedEffect(chatInfo.id, searchValueIsEmpty.value) { + if (searchValueIsEmpty.value && reversedChatItems.value.size < ChatPagination.INITIAL_COUNT) + ignoreLoadingRequests.add(reversedChatItems.value.lastOrNull()?.id ?: return@LaunchedEffect) } - hoveredItemId.value = null - if (reportsState != null) { - reportsListState = null - reportsState - } else if (index <= 0 || !searchValueIsEmpty.value) { - LazyListState(0, 0) - } else { - LazyListState(index + 1, -maxHeightForList.value) - } - }) - SaveReportsStateOnDispose(listState) - val maxHeight = remember { derivedStateOf { listState.value.layoutInfo.viewportEndOffset - topPaddingToContentPx.value } } - val loadingMoreItems = remember { mutableStateOf(false) } - val animatedScrollingInProgress = remember { mutableStateOf(false) } - val ignoreLoadingRequests = remember(remoteHostId) { mutableSetOf() } - LaunchedEffect(chatInfo.id, searchValueIsEmpty.value) { - if (searchValueIsEmpty.value && reversedChatItems.value.size < ChatPagination.INITIAL_COUNT) - ignoreLoadingRequests.add(reversedChatItems.value.lastOrNull()?.id ?: return@LaunchedEffect) - } - PreloadItems(chatInfo.id, if (searchValueIsEmpty.value) ignoreLoadingRequests else mutableSetOf(), loadingMoreItems, resetListState, contentTag, mergedItems, listState, ChatPagination.UNTIL_PRELOAD_COUNT) { chatId, pagination -> - if (loadingMoreItems.value || chatId != chatModel.chatId.value) return@PreloadItems false - loadingMoreItems.value = true - withContext(NonCancellable) { - try { - loadMessages(chatId, pagination) { - visibleItemIndexesNonReversed(mergedItems, reversedChatItems.value.size, listState.value) + PreloadItems(chatsCtx, chatInfo.id, if (searchValueIsEmpty.value) ignoreLoadingRequests else mutableSetOf(), loadingMoreItems, resetListState, mergedItems, listState, ChatPagination.UNTIL_PRELOAD_COUNT) { chatId, pagination -> + if (loadingMoreItems.value || chatId != chatModel.chatId.value) return@PreloadItems false + loadingMoreItems.value = true + withContext(NonCancellable) { + try { + loadMessages(chatId, pagination) { + visibleItemIndexesNonReversed(mergedItems, reversedChatItems.value.size, listState.value) + } + } finally { + loadingMoreItems.value = false + } + } + true + } + val remoteHostIdUpdated = rememberUpdatedState(remoteHostId) + val chatInfoUpdated = rememberUpdatedState(chatInfo) + val scope = rememberCoroutineScope() + val scrollToItem: (Long) -> Unit = remember { + // In group reports just set the itemId to scroll to so the main ChatView will handle scrolling + if (chatsCtx.contentTag == MsgContentTag.Report) return@remember { scrollToItemId.value = it } + scrollToItem(searchValue, loadingMoreItems, animatedScrollingInProgress, highlightedItems, chatInfoUpdated, maxHeight, scope, reversedChatItems, mergedItems, listState, loadMessages) + } + val scrollToQuotedItemFromItem: (Long) -> Unit = remember { findQuotedItemFromItem(chatsCtx, remoteHostIdUpdated, chatInfoUpdated, scope, scrollToItem) } + if (chatsCtx.contentTag == null) { + LaunchedEffect(Unit) { + snapshotFlow { scrollToItemId.value }.filterNotNull().collect { + if (appPlatform.isAndroid) { + ModalManager.end.closeModals() + } + scrollToItem(it) + scrollToItemId.value = null } - } finally { - loadingMoreItems.value = false } } - true - } + SmallScrollOnNewMessage(listState, reversedChatItems) + val finishedInitialComposition = remember { mutableStateOf(false) } + NotifyChatListOnFinishingComposition(finishedInitialComposition, chatInfo, revealedItems, listState, onComposed) - val remoteHostIdUpdated = rememberUpdatedState(remoteHostId) - val chatInfoUpdated = rememberUpdatedState(chatInfo) - val scope = rememberCoroutineScope() - val scrollToItem: (Long) -> Unit = remember { - // In group reports just set the itemId to scroll to so the main ChatView will handle scrolling - if (contentTag == MsgContentTag.Report) return@remember { scrollToItemId.value = it } - scrollToItem(searchValue, loadingMoreItems, animatedScrollingInProgress, highlightedItems, chatInfoUpdated, maxHeight, scope, reversedChatItems, mergedItems, listState, loadMessages) - } - val scrollToQuotedItemFromItem: (Long) -> Unit = remember { findQuotedItemFromItem(remoteHostIdUpdated, chatInfoUpdated, scope, scrollToItem, contentTag) } - if (contentTag == null) { - LaunchedEffect(Unit) { snapshotFlow { scrollToItemId.value }.filterNotNull().collect { - if (appPlatform.isAndroid) { - ModalManager.end.closeModals() + DisposableEffectOnGone( + whenGone = { + VideoPlayerHolder.releaseAll() } - scrollToItem(it) - scrollToItemId.value = null } - } - } - SmallScrollOnNewMessage(listState, reversedChatItems) - val finishedInitialComposition = remember { mutableStateOf(false) } - NotifyChatListOnFinishingComposition(finishedInitialComposition, chatInfo, revealedItems, listState, onComposed) - - DisposableEffectOnGone( - always = { - chatsCtx.chatItemsChangesListener = recalculateChatStatePositions(chatsCtx.chatState) - }, - whenGone = { - VideoPlayerHolder.releaseAll() - chatsCtx.chatItemsChangesListener = recalculateChatStatePositions(chatsCtx.chatState) - } - ) + ) @Composable fun ChatViewListItem( @@ -1350,7 +1342,7 @@ fun BoxScope.ChatItemsList( highlightedItems.value = setOf() } } - ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, highlighted = highlighted, hoveredItemId = hoveredItemId, range = range, searchIsNotBlank = searchValueIsNotBlank, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems, reversedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, archiveReports = archiveReports, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, scrollToQuotedItemFromItem = scrollToQuotedItemFromItem, setReaction = setReaction, showItemDetails = showItemDetails, reveal = reveal, showMemberInfo = showMemberInfo, showChatInfo = showChatInfo, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp) + ChatItemView(chatsCtx, remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, highlighted = highlighted, hoveredItemId = hoveredItemId, range = range, searchIsNotBlank = searchValueIsNotBlank, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems, reversedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, archiveReports = archiveReports, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, scrollToQuotedItemFromItem = scrollToQuotedItemFromItem, setReaction = setReaction, showItemDetails = showItemDetails, reveal = reveal, showMemberInfo = showMemberInfo, showChatInfo = showChatInfo, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp) } } @@ -1487,7 +1479,7 @@ fun BoxScope.ChatItemsList( } } else { ChatItemBox { - AnimatedVisibility (selectionVisible, enter = fadeIn(), exit = fadeOut()) { + AnimatedVisibility(selectionVisible, enter = fadeIn(), exit = fadeOut()) { SelectedListItem(Modifier.padding(start = 8.dp), cItem.id, selectedChatItems) } Row( @@ -1502,7 +1494,7 @@ fun BoxScope.ChatItemsList( } } else { ChatItemBox { - AnimatedVisibility (selectionVisible, enter = fadeIn(), exit = fadeOut()) { + AnimatedVisibility(selectionVisible, enter = fadeIn(), exit = fadeOut()) { SelectedListItem(Modifier.padding(start = 8.dp), cItem.id, selectedChatItems) } Box( @@ -1517,7 +1509,7 @@ fun BoxScope.ChatItemsList( } } else { // direct message ChatItemBox { - AnimatedVisibility (selectionVisible, enter = fadeIn(), exit = fadeOut()) { + AnimatedVisibility(selectionVisible, enter = fadeIn(), exit = fadeOut()) { SelectedListItem(Modifier.padding(start = 8.dp), cItem.id, selectedChatItems) } @@ -1547,110 +1539,110 @@ fun BoxScope.ChatItemsList( ChatItemView(cItem, range, itemSeparation, previousItemSeparationLargeGap) } } - LazyColumnWithScrollBar( - Modifier.align(Alignment.BottomCenter), - state = listState.value, - contentPadding = PaddingValues( - top = topPaddingToContent, - bottom = composeViewHeight.value - ), - reverseLayout = true, - additionalBarOffset = composeViewHeight, - additionalTopBar = rememberUpdatedState(contentTag == null && reportsCount > 0), - chatBottomBar = remember { appPrefs.chatBottomBar.state } - ) { - val mergedItemsValue = mergedItems.value - itemsIndexed(mergedItemsValue.items, key = { _, merged -> keyForItem(merged.newest().item) }) { index, merged -> - val isLastItem = index == mergedItemsValue.items.lastIndex - val last = if (isLastItem) reversedChatItems.value.lastOrNull() else null - val listItem = merged.newest() - val item = listItem.item - val range = if (merged is MergedItem.Grouped) { - merged.rangeInReversed.value - } else { - null - } - val showAvatar = shouldShowAvatar(item, listItem.nextItem) - val isRevealed = remember { derivedStateOf { revealedItems.value.contains(item.id) } } - val itemSeparation: ItemSeparation - val prevItemSeparationLargeGap: Boolean - if (merged is MergedItem.Single || isRevealed.value) { - val prev = listItem.prevItem - itemSeparation = getItemSeparation(item, prev) - val nextForGap = if ((item.mergeCategory != null && item.mergeCategory == prev?.mergeCategory) || isLastItem) null else listItem.nextItem - prevItemSeparationLargeGap = if (nextForGap == null) false else getItemSeparationLargeGap(nextForGap, item) - } else { - itemSeparation = getItemSeparation(item, null) - prevItemSeparationLargeGap = false - } - ChatViewListItem(index == 0, rememberUpdatedState(range), showAvatar, item, itemSeparation, prevItemSeparationLargeGap, isRevealed) { - if (merged is MergedItem.Grouped) merged.reveal(it, revealedItems) - } - - if (last != null) { - // no using separate item(){} block in order to have total number of items in LazyColumn match number of merged items - DateSeparator(last.meta.itemTs) - } - if (item.isRcvNew) { - val itemIds = when (merged) { - is MergedItem.Single -> listOf(merged.item.item.id) - is MergedItem.Grouped -> merged.items.map { it.item.id } + LazyColumnWithScrollBar( + Modifier.align(Alignment.BottomCenter), + state = listState.value, + contentPadding = PaddingValues( + top = topPaddingToContent, + bottom = composeViewHeight.value + ), + reverseLayout = true, + additionalBarOffset = composeViewHeight, + additionalTopBar = rememberUpdatedState(chatsCtx.contentTag == null && reportsCount > 0), + chatBottomBar = remember { appPrefs.chatBottomBar.state } + ) { + val mergedItemsValue = mergedItems.value + itemsIndexed(mergedItemsValue.items, key = { _, merged -> keyForItem(merged.newest().item) }) { index, merged -> + val isLastItem = index == mergedItemsValue.items.lastIndex + val last = if (isLastItem) reversedChatItems.value.lastOrNull() else null + val listItem = merged.newest() + val item = listItem.item + val range = if (merged is MergedItem.Grouped) { + merged.rangeInReversed.value + } else { + null + } + val showAvatar = shouldShowAvatar(item, listItem.nextItem) + val isRevealed = remember { derivedStateOf { revealedItems.value.contains(item.id) } } + val itemSeparation: ItemSeparation + val prevItemSeparationLargeGap: Boolean + if (merged is MergedItem.Single || isRevealed.value) { + val prev = listItem.prevItem + itemSeparation = getItemSeparation(item, prev) + val nextForGap = if ((item.mergeCategory != null && item.mergeCategory == prev?.mergeCategory) || isLastItem) null else listItem.nextItem + prevItemSeparationLargeGap = if (nextForGap == null) false else getItemSeparationLargeGap(nextForGap, item) + } else { + itemSeparation = getItemSeparation(item, null) + prevItemSeparationLargeGap = false + } + ChatViewListItem(index == 0, rememberUpdatedState(range), showAvatar, item, itemSeparation, prevItemSeparationLargeGap, isRevealed) { + if (merged is MergedItem.Grouped) merged.reveal(it, revealedItems) + } + + if (last != null) { + // no using separate item(){} block in order to have total number of items in LazyColumn match number of merged items + DateSeparator(last.meta.itemTs) + } + if (item.isRcvNew) { + val itemIds = when (merged) { + is MergedItem.Single -> listOf(merged.item.item.id) + is MergedItem.Grouped -> merged.items.map { it.item.id } + } + MarkItemsReadAfterDelay(keyForItem(item), itemIds, finishedInitialComposition, chatInfo.id, listState, markItemsRead) } - MarkItemsReadAfterDelay(keyForItem(item), itemIds, finishedInitialComposition, chatInfo.id, listState, markItemsRead) } } - } - FloatingButtons( - reversedChatItems, - chatInfoUpdated, - topPaddingToContent, - topPaddingToContentPx, - contentTag, - loadingMoreItems, - loadingTopItems, - loadingBottomItems, - animatedScrollingInProgress, - mergedItems, - unreadCount, - maxHeight, - composeViewHeight, - searchValue, - markChatRead, - listState, - loadMessages - ) - FloatingDate(Modifier.padding(top = 10.dp + topPaddingToContent).align(Alignment.TopCenter), topPaddingToContentPx, mergedItems, listState) + FloatingButtons( + chatsCtx, + reversedChatItems, + chatInfoUpdated, + topPaddingToContent, + topPaddingToContentPx, + loadingMoreItems, + loadingTopItems, + loadingBottomItems, + animatedScrollingInProgress, + mergedItems, + unreadCount, + maxHeight, + composeViewHeight, + searchValue, + markChatRead, + listState, + loadMessages + ) + FloatingDate(Modifier.padding(top = 10.dp + topPaddingToContent).align(Alignment.TopCenter), topPaddingToContentPx, mergedItems, listState) - LaunchedEffect(Unit) { - snapshotFlow { listState.value.isScrollInProgress } - .collect { - chatViewScrollState.value = it - } - } - LaunchedEffect(Unit) { - snapshotFlow { listState.value.isScrollInProgress } - .filter { !it } - .collect { - if (animatedScrollingInProgress.value) { - animatedScrollingInProgress.value = false + LaunchedEffect(Unit) { + snapshotFlow { listState.value.isScrollInProgress } + .collect { + chatViewScrollState.value = it } - } + } + LaunchedEffect(Unit) { + snapshotFlow { listState.value.isScrollInProgress } + .filter { !it } + .collect { + if (animatedScrollingInProgress.value) { + animatedScrollingInProgress.value = false + } + } + } } } -private suspend fun loadLastItems(chatId: State, contentTag: MsgContentTag?, listState: State, loadItems: State Boolean>) { +private suspend fun loadLastItems(chatsCtx: ChatModel.ChatsContext, chatId: State, listState: State, loadItems: State Boolean>) { val lastVisible = listState.value.layoutInfo.visibleItemsInfo.lastOrNull() val itemsCanCoverScreen = lastVisible != null && listState.value.layoutInfo.viewportEndOffset - listState.value.layoutInfo.afterContentPadding <= lastVisible.offset + lastVisible.size if (!itemsCanCoverScreen) return - if (lastItemsLoaded(contentTag)) return + if (lastItemsLoaded(chatsCtx)) return delay(500) loadItems.value(chatId.value, ChatPagination.Last(ChatPagination.INITIAL_COUNT)) } -private fun lastItemsLoaded(contentTag: MsgContentTag?): Boolean { - val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext +private fun lastItemsLoaded(chatsCtx: ChatModel.ChatsContext): Boolean { val chatState = chatsCtx.chatState return chatState.splits.value.isEmpty() || chatState.splits.value.firstOrNull() != chatsCtx.chatItems.value.lastOrNull()?.id } @@ -1715,11 +1707,11 @@ private fun NotifyChatListOnFinishingComposition( @Composable fun BoxScope.FloatingButtons( + chatsCtx: ChatModel.ChatsContext, reversedChatItems: State>, chatInfo: State, topPaddingToContent: Dp, topPaddingToContentPx: State, - contentTag: MsgContentTag?, loadingMoreItems: MutableState, loadingTopItems: MutableState, loadingBottomItems: MutableState, @@ -1743,7 +1735,6 @@ fun BoxScope.FloatingButtons( fun scrollToTopUnread() { scope.launch { tryBlockAndSetLoadingMore(loadingMoreItems) { - val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext if (chatsCtx.chatState.splits.value.isNotEmpty()) { val pagination = ChatPagination.Initial(ChatPagination.INITIAL_COUNT) val oldSize = reversedChatItems.value.size @@ -1806,7 +1797,7 @@ fun BoxScope.FloatingButtons( animatedScrollingInProgress, composeViewHeight, onClick = { - if (loadingBottomItems.value || !lastItemsLoaded(contentTag)) { + if (loadingBottomItems.value || !lastItemsLoaded(chatsCtx)) { requestedTopScroll.value = false requestedBottomScroll.value = true } else { @@ -1881,11 +1872,11 @@ fun BoxScope.FloatingButtons( @Composable fun PreloadItems( + chatsCtx: ChatModel.ChatsContext, chatId: String, ignoreLoadingRequests: MutableSet, loadingMoreItems: State, resetListState: State, - contentTag: MsgContentTag?, mergedItems: State, listState: State, remaining: Int, @@ -1911,20 +1902,20 @@ fun PreloadItems( snapshotFlow { listState.value.firstVisibleItemIndex } .distinctUntilChanged() .collect { firstVisibleIndex -> - if (!preloadItemsBefore(firstVisibleIndex, chatId, ignoreLoadingRequests, contentTag, mergedItems, listState, remaining, loadItems)) { - preloadItemsAfter(firstVisibleIndex, chatId, contentTag, mergedItems, remaining, loadItems) + if (!preloadItemsBefore(chatsCtx, firstVisibleIndex, chatId, ignoreLoadingRequests, mergedItems, listState, remaining, loadItems)) { + preloadItemsAfter(chatsCtx, firstVisibleIndex, chatId, mergedItems, remaining, loadItems) } - loadLastItems(chatId, contentTag, listState, loadItems) + loadLastItems(chatsCtx, chatId, listState, loadItems) } } } } private suspend fun preloadItemsBefore( + chatsCtx: ChatModel.ChatsContext, firstVisibleIndex: Int, chatId: State, ignoreLoadingRequests: State>, - contentTag: MsgContentTag?, mergedItems: State, listState: State, remaining: Int, @@ -1933,18 +1924,18 @@ private suspend fun preloadItemsBefore( val splits = mergedItems.value.splits val lastVisibleIndex = (listState.value.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0) var lastIndexToLoadFrom: Int? = findLastIndexToLoadFromInSplits(firstVisibleIndex, lastVisibleIndex, remaining, splits) - val items = reversedChatItemsStatic(contentTag) + val items = reversedChatItemsStatic(chatsCtx) if (splits.isEmpty() && items.isNotEmpty() && lastVisibleIndex > mergedItems.value.items.size - remaining) { lastIndexToLoadFrom = items.lastIndex } if (lastIndexToLoadFrom != null) { val loadFromItemId = items.getOrNull(lastIndexToLoadFrom)?.id ?: return false if (!ignoreLoadingRequests.value.contains(loadFromItemId)) { - val items = reversedChatItemsStatic(contentTag) + val items = reversedChatItemsStatic(chatsCtx) val sizeWas = items.size val oldestItemIdWas = items.lastOrNull()?.id val triedToLoad = loadItems.value(chatId.value, ChatPagination.Before(loadFromItemId, ChatPagination.PRELOAD_COUNT)) - val itemsUpdated = reversedChatItemsStatic(contentTag) + val itemsUpdated = reversedChatItemsStatic(chatsCtx) if (triedToLoad && sizeWas == itemsUpdated.size && oldestItemIdWas == itemsUpdated.lastOrNull()?.id) { ignoreLoadingRequests.value.add(loadFromItemId) return false @@ -1956,14 +1947,14 @@ private suspend fun preloadItemsBefore( } private suspend fun preloadItemsAfter( + chatsCtx: ChatModel.ChatsContext, firstVisibleIndex: Int, chatId: State, - contentTag: MsgContentTag?, mergedItems: State, remaining: Int, loadItems: State Boolean>, ) { - val items = reversedChatItemsStatic(contentTag) + val items = reversedChatItemsStatic(chatsCtx) val splits = mergedItems.value.splits val split = splits.lastOrNull { it.indexRangeInParentItems.contains(firstVisibleIndex) } // we're inside a splitRange (top --- [end of the splitRange --- we're here --- start of the splitRange] --- bottom) @@ -2129,11 +2120,10 @@ private fun FloatingDate( } @Composable -private fun SaveReportsStateOnDispose(listState: State) { - val contentTag = LocalContentTag.current +private fun SaveReportsStateOnDispose(chatsCtx: ChatModel.ChatsContext, listState: State) { DisposableEffect(Unit) { onDispose { - reportsListState = if (contentTag == MsgContentTag.Report && ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) listState.value else null + reportsListState = if (chatsCtx.contentTag == MsgContentTag.Report && ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) listState.value else null } } } @@ -2244,10 +2234,8 @@ fun reportsCount(staleChatId: String?): Int { } } -private fun reversedChatItemsStatic(contentTag: MsgContentTag?): List { - val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext - return chatsCtx.chatItems.value.asReversed() -} +private fun reversedChatItemsStatic(chatsCtx: ChatModel.ChatsContext): List = + chatsCtx.chatItems.value.asReversed() private fun oldestPartiallyVisibleListItemInListStateOrNull(topPaddingToContentPx: State, mergedItems: State, listState: State): ListItem? { val lastFullyVisibleOffset = listState.value.layoutInfo.viewportEndOffset - topPaddingToContentPx.value @@ -2323,22 +2311,20 @@ private fun scrollToItem( } private fun findQuotedItemFromItem( + chatsCtx: ChatModel.ChatsContext, rhId: State, chatInfo: State, scope: CoroutineScope, - scrollToItem: (Long) -> Unit, - contentTag: MsgContentTag? + scrollToItem: (Long) -> Unit ): (Long) -> Unit = { itemId: Long -> scope.launch(Dispatchers.Default) { - val item = apiLoadSingleMessage(rhId.value, chatInfo.value.chatType, chatInfo.value.apiId, itemId, contentTag) + val item = apiLoadSingleMessage(chatsCtx, rhId.value, chatInfo.value.chatType, chatInfo.value.apiId, itemId) if (item != null) { withContext(Dispatchers.Main) { chatModel.chatsContext.updateChatItem(chatInfo.value, item) } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - chatModel.secondaryChatsContext.updateChatItem(chatInfo.value, item) - } + chatModel.secondaryChatsContext.value?.updateChatItem(chatInfo.value, item) } if (item.quotedItem?.itemId != null) { scrollToItem(item.quotedItem.itemId) @@ -2533,15 +2519,13 @@ private fun deleteMessages(chatRh: Long?, chatInfo: ChatInfo, itemIds: List Unit) { @Composable fun SelectedItemsButtonsToolbar( + chatsCtx: ChatModel.ChatsContext, chatInfo: ChatInfo, - contentTag: MsgContentTag?, selectedChatItems: MutableState?>, deleteItems: (Boolean) -> Unit, // Boolean - delete for everyone is possible archiveItems: () -> Unit, @@ -122,7 +121,6 @@ fun SelectedItemsButtonsToolbar( } Divider(Modifier.align(Alignment.TopStart)) } - val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext val chatItems = remember { derivedStateOf { chatsCtx.chatItems.value } } LaunchedEffect(chatInfo, chatItems.value, selectedChatItems.value) { recheckItems(chatInfo, chatItems.value, selectedChatItems, deleteEnabled, deleteForEveryoneEnabled, canArchiveReports, canModerate, moderateEnabled, forwardEnabled, deleteCountProhibited, forwardCountProhibited) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt index a6d009f76a..10694d13bf 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt @@ -65,9 +65,7 @@ fun AddGroupMembersView(rhId: Long?, groupInfo: GroupInfo, creatingGroup: Boolea chatModel.chatsContext.upsertGroupMember(rhId, groupInfo, member) } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - chatModel.secondaryChatsContext.upsertGroupMember(rhId, groupInfo, member) - } + chatModel.secondaryChatsContext.value?.upsertGroupMember(rhId, groupInfo, member) } } else { break diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index 1551c49b47..22956738e7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -51,6 +51,7 @@ val MEMBER_ROW_VERTICAL_PADDING = 8.dp @Composable fun ModalData.GroupChatInfoView( + chatsCtx: ChatModel.ChatsContext, rhId: Long?, chatId: String, groupLink: CreatedConnLink?, @@ -92,7 +93,7 @@ fun ModalData.GroupChatInfoView( val previousChatTTL = chatItemTTL.value chatItemTTL.value = it - setChatTTLAlert(chat.remoteHostId, chat.chatInfo, chatItemTTL, previousChatTTL, deletingItems) + setChatTTLAlert(chatsCtx, chat.remoteHostId, chat.chatInfo, chatItemTTL, previousChatTTL, deletingItems) }, activeSortedMembers = remember { chatModel.groupMembers }.value .filter { it.memberStatus != GroupMemberStatus.MemLeft && it.memberStatus != GroupMemberStatus.MemRemoved } @@ -971,10 +972,8 @@ fun removeMembers(rhId: Long?, groupInfo: GroupInfo, memberIds: List, onSu } } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - updatedMembers.forEach { updatedMember -> - chatModel.secondaryChatsContext.upsertGroupMember(rhId, groupInfo, updatedMember) - } + updatedMembers.forEach { updatedMember -> + chatModel.secondaryChatsContext.value?.upsertGroupMember(rhId, groupInfo, updatedMember) } } onSuccess() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt index 638722463d..285c96165c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt @@ -66,9 +66,7 @@ fun GroupMemberInfoView( chatModel.chatsContext.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - chatModel.secondaryChatsContext.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) - } + chatModel.secondaryChatsContext.value?.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) } close.invoke() } @@ -88,7 +86,7 @@ fun GroupMemberInfoView( getContactChat = { chatModel.getContactChat(it) }, openDirectChat = { withBGApi { - apiLoadMessages(rhId, ChatType.Direct, it, null, ChatPagination.Initial(ChatPagination.INITIAL_COUNT)) + apiLoadMessages(chatModel.chatsContext, rhId, ChatType.Direct, it, ChatPagination.Initial(ChatPagination.INITIAL_COUNT)) if (chatModel.getContactChat(it) != null) { closeAll() } @@ -154,9 +152,7 @@ fun GroupMemberInfoView( chatModel.chatsContext.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - chatModel.secondaryChatsContext.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) - } + chatModel.secondaryChatsContext.value?.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) } close.invoke() } @@ -173,9 +169,7 @@ fun GroupMemberInfoView( chatModel.chatsContext.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - chatModel.secondaryChatsContext.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) - } + chatModel.secondaryChatsContext.value?.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) } close.invoke() } @@ -195,9 +189,7 @@ fun GroupMemberInfoView( chatModel.chatsContext.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - chatModel.secondaryChatsContext.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) - } + chatModel.secondaryChatsContext.value?.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) } close.invoke() } @@ -223,9 +215,7 @@ fun GroupMemberInfoView( chatModel.chatsContext.upsertGroupMember(rhId, groupInfo, copy) } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - chatModel.secondaryChatsContext.upsertGroupMember(rhId, groupInfo, copy) - } + chatModel.secondaryChatsContext.value?.upsertGroupMember(rhId, groupInfo, copy) } r } @@ -262,10 +252,8 @@ fun removeMemberDialog(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, c } } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - removedMembers.forEach { removedMember -> - chatModel.secondaryChatsContext.upsertGroupMember(rhId, groupInfo, removedMember) - } + removedMembers.forEach { removedMember -> + chatModel.secondaryChatsContext.value?.upsertGroupMember(rhId, groupInfo, removedMember) } } } @@ -714,10 +702,8 @@ fun updateMembersRole(newRole: GroupMemberRole, rhId: Long?, groupInfo: GroupInf } } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - members.forEach { member -> - chatModel.secondaryChatsContext.upsertGroupMember(rhId, groupInfo, member) - } + members.forEach { member -> + chatModel.secondaryChatsContext.value?.upsertGroupMember(rhId, groupInfo, member) } } onSuccess() @@ -815,9 +801,7 @@ fun updateMemberSettings(rhId: Long?, gInfo: GroupInfo, member: GroupMember, mem chatModel.chatsContext.upsertGroupMember(rhId, gInfo, member.copy(memberSettings = memberSettings)) } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - chatModel.secondaryChatsContext.upsertGroupMember(rhId, gInfo, member.copy(memberSettings = memberSettings)) - } + chatModel.secondaryChatsContext.value?.upsertGroupMember(rhId, gInfo, member.copy(memberSettings = memberSettings)) } } } @@ -878,10 +862,8 @@ fun blockMemberForAll(rhId: Long?, gInfo: GroupInfo, memberIds: List, bloc } } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - updatedMembers.forEach { updatedMember -> - chatModel.secondaryChatsContext.upsertGroupMember(rhId, gInfo, updatedMember) - } + updatedMembers.forEach { updatedMember -> + chatModel.secondaryChatsContext.value?.upsertGroupMember(rhId, gInfo, updatedMember) } } onSuccess() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupReportsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupReportsView.kt index b41d190ffe..1eeeb99c93 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupReportsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupReportsView.kt @@ -14,16 +14,14 @@ import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.flow.* -val LocalContentTag: ProvidableCompositionLocal = staticCompositionLocalOf { null } - @Composable -private fun GroupReportsView(staleChatId: State, scrollToItemId: MutableState) { - ChatView(staleChatId, contentTag = MsgContentTag.Report, scrollToItemId, onComposed = {}) +private fun GroupReportsView(reportsChatsCtx: ChatModel.ChatsContext, staleChatId: State, scrollToItemId: MutableState) { + ChatView(reportsChatsCtx, staleChatId, scrollToItemId, onComposed = {}) } @Composable fun GroupReportsAppBar( - contentTag: MsgContentTag?, + chatsCtx: ChatModel.ChatsContext, close: () -> Unit, onSearchValueChanged: (String) -> Unit ) { @@ -51,11 +49,11 @@ fun GroupReportsAppBar( } } ) - ItemsReload(contentTag) + ItemsReload(chatsCtx) } @Composable -private fun ItemsReload(contentTag: MsgContentTag?) { +private fun ItemsReload(chatsCtx: ChatModel.ChatsContext,) { LaunchedEffect(Unit) { snapshotFlow { chatModel.chatId.value } .distinctUntilChanged() @@ -65,18 +63,19 @@ private fun ItemsReload(contentTag: MsgContentTag?) { .filterNotNull() .filter { it.chatInfo is ChatInfo.Group } .collect { chat -> - reloadItems(chat, contentTag) + reloadItems(chatsCtx, chat) } } } suspend fun showGroupReportsView(staleChatId: State, scrollToItemId: MutableState, chatInfo: ChatInfo) { - openChat(chatModel.remoteHostId(), chatInfo, MsgContentTag.Report) + val reportsChatsCtx = ChatModel.ChatsContext(contentTag = MsgContentTag.Report) + openChat(secondaryChatsCtx = reportsChatsCtx, chatModel.remoteHostId(), chatInfo) ModalManager.end.showCustomModal(true, id = ModalViewId.SECONDARY_CHAT) { close -> ModalView({}, showAppBar = false) { val chatInfo = remember { derivedStateOf { chatModel.chats.value.firstOrNull { it.id == chatModel.chatId.value }?.chatInfo } }.value if (chatInfo is ChatInfo.Group && chatInfo.groupInfo.canModerate) { - GroupReportsView(staleChatId, scrollToItemId) + GroupReportsView(reportsChatsCtx, staleChatId, scrollToItemId) } else { LaunchedEffect(Unit) { close() @@ -86,6 +85,6 @@ suspend fun showGroupReportsView(staleChatId: State, scrollToItemId: Mu } } -private suspend fun reloadItems(chat: Chat, contentTag: MsgContentTag?) { - apiLoadMessages(chat.remoteHostId, chat.chatInfo.chatType, chat.chatInfo.apiId, contentTag, ChatPagination.Initial(ChatPagination.INITIAL_COUNT)) +private suspend fun reloadItems(chatsCtx: ChatModel.ChatsContext, chat: Chat) { + apiLoadMessages(chatsCtx, chat.remoteHostId, chat.chatInfo.chatType, chat.chatInfo.apiId, ChatPagination.Initial(ChatPagination.INITIAL_COUNT)) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIChatFeatureView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIChatFeatureView.kt index 6a57912296..c743d78d1c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIChatFeatureView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIChatFeatureView.kt @@ -12,12 +12,11 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* import chat.simplex.common.model.ChatModel.getChatItemIndexOrNull -import chat.simplex.common.platform.chatModel import chat.simplex.common.platform.onRightClick -import chat.simplex.common.views.chat.group.LocalContentTag @Composable fun CIChatFeatureView( + chatsCtx: ChatModel.ChatsContext, chatInfo: ChatInfo, chatItem: ChatItem, feature: Feature, @@ -26,7 +25,7 @@ fun CIChatFeatureView( revealed: State, showMenu: MutableState, ) { - val merged = if (!revealed.value) mergedFeatures(chatItem, chatInfo) else emptyList() + val merged = if (!revealed.value) mergedFeatures(chatsCtx, chatItem, chatInfo) else emptyList() Box( Modifier .combinedClickable( @@ -73,11 +72,9 @@ private fun Feature.toFeatureInfo(color: Color, param: Int?, type: String): Feat ) @Composable -private fun mergedFeatures(chatItem: ChatItem, chatInfo: ChatInfo): List? { - val m = ChatModel +private fun mergedFeatures(chatsCtx: ChatModel.ChatsContext, chatItem: ChatItem, chatInfo: ChatInfo): List? { val fs: ArrayList = arrayListOf() val icons: MutableSet = mutableSetOf() - val chatsCtx = if (LocalContentTag.current == null) m.chatsContext else m.secondaryChatsContext val reversedChatItems = chatsCtx.chatItems.value.asReversed() var i = getChatItemIndexOrNull(chatItem, reversedChatItems) if (i != null) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index d08fa574ef..5bbbc33741 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -29,7 +29,6 @@ import chat.simplex.common.model.ChatModel.currentUser import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chat.* -import chat.simplex.common.views.chat.group.LocalContentTag import chat.simplex.common.views.chatlist.openChat import chat.simplex.common.views.helpers.* import chat.simplex.res.MR @@ -63,6 +62,7 @@ data class ChatItemReactionMenuItem ( @Composable fun ChatItemView( + chatsCtx: ChatModel.ChatsContext, rhId: Long?, cInfo: ChatInfo, cItem: ChatItem, @@ -277,7 +277,7 @@ fun ChatItemView( if (searchIsNotBlank.value) { GoToItemInnerButton(alignStart, MR.images.ic_search, 17.dp, parentActivated) { withBGApi { - openChat(rhId, cInfo.chatType, cInfo.apiId, null, cItem.id) + openChat(secondaryChatsCtx = null, rhId, cInfo.chatType, cInfo.apiId, cItem.id) closeReportsIfNeeded() } } @@ -285,7 +285,7 @@ fun ChatItemView( GoToItemInnerButton(alignStart, MR.images.ic_arrow_forward, 22.dp, parentActivated) { val (chatType, apiId, msgId) = chatTypeApiIdMsgId withBGApi { - openChat(rhId, chatType, apiId, null, msgId) + openChat(secondaryChatsCtx = null, rhId, chatType, apiId, msgId) closeReportsIfNeeded() } } @@ -364,7 +364,7 @@ fun ChatItemView( @Composable fun DeleteItemMenu() { DefaultDropdownMenu(showMenu) { - DeleteItemAction(cItem, revealed, showMenu, questionText = deleteMessageQuestionText(), deleteMessage, deleteMessages) + DeleteItemAction(chatsCtx, cItem, revealed, showMenu, questionText = deleteMessageQuestionText(), deleteMessage, deleteMessages) if (cItem.canBeDeletedForSelf) { Divider() SelectItemAction(showMenu, selectChatItem) @@ -382,7 +382,7 @@ fun ChatItemView( if (cItem.chatDir !is CIDirection.GroupSnd && cInfo.groupInfo.membership.memberRole >= GroupMemberRole.Moderator) { ArchiveReportItemAction(cItem.id, cInfo.groupInfo.membership.memberActive, showMenu, archiveReports) } - DeleteItemAction(cItem, revealed, showMenu, questionText = deleteMessageQuestionText(), deleteMessage, deleteMessages, buttonText = stringResource(MR.strings.delete_report)) + DeleteItemAction(chatsCtx, cItem, revealed, showMenu, questionText = deleteMessageQuestionText(), deleteMessage, deleteMessages, buttonText = stringResource(MR.strings.delete_report)) Divider() SelectItemAction(showMenu, selectChatItem) } @@ -472,7 +472,7 @@ fun ChatItemView( CancelFileItemAction(cItem.file.fileId, showMenu, cancelFile = cancelFile, cancelAction = cItem.file.cancelAction) } if (!(live && cItem.meta.isLive) && !preview) { - DeleteItemAction(cItem, revealed, showMenu, questionText = deleteMessageQuestionText(), deleteMessage, deleteMessages) + DeleteItemAction(chatsCtx, cItem, revealed, showMenu, questionText = deleteMessageQuestionText(), deleteMessage, deleteMessages) } if (cItem.chatDir !is CIDirection.GroupSnd) { val groupInfo = cItem.memberToModerate(cInfo)?.first @@ -498,7 +498,7 @@ fun ChatItemView( ExpandItemAction(revealed, showMenu, reveal) } ItemInfoAction(cInfo, cItem, showItemDetails, showMenu) - DeleteItemAction(cItem, revealed, showMenu, questionText = deleteMessageQuestionText(), deleteMessage, deleteMessages) + DeleteItemAction(chatsCtx, cItem, revealed, showMenu, questionText = deleteMessageQuestionText(), deleteMessage, deleteMessages) if (cItem.canBeDeletedForSelf) { Divider() SelectItemAction(showMenu, selectChatItem) @@ -508,7 +508,7 @@ fun ChatItemView( cItem.isDeletedContent -> { DefaultDropdownMenu(showMenu) { ItemInfoAction(cInfo, cItem, showItemDetails, showMenu) - DeleteItemAction(cItem, revealed, showMenu, questionText = deleteMessageQuestionText(), deleteMessage, deleteMessages) + DeleteItemAction(chatsCtx, cItem, revealed, showMenu, questionText = deleteMessageQuestionText(), deleteMessage, deleteMessages) if (cItem.canBeDeletedForSelf) { Divider() SelectItemAction(showMenu, selectChatItem) @@ -522,7 +522,7 @@ fun ChatItemView( } else { ExpandItemAction(revealed, showMenu, reveal) } - DeleteItemAction(cItem, revealed, showMenu, questionText = deleteMessageQuestionText(), deleteMessage, deleteMessages) + DeleteItemAction(chatsCtx, cItem, revealed, showMenu, questionText = deleteMessageQuestionText(), deleteMessage, deleteMessages) if (cItem.canBeDeletedForSelf) { Divider() SelectItemAction(showMenu, selectChatItem) @@ -531,7 +531,7 @@ fun ChatItemView( } else -> { DefaultDropdownMenu(showMenu) { - DeleteItemAction(cItem, revealed, showMenu, questionText = deleteMessageQuestionText(), deleteMessage, deleteMessages) + DeleteItemAction(chatsCtx, cItem, revealed, showMenu, questionText = deleteMessageQuestionText(), deleteMessage, deleteMessages) if (selectedChatItems.value == null) { Divider() SelectItemAction(showMenu, selectChatItem) @@ -548,7 +548,7 @@ fun ChatItemView( RevealItemAction(revealed, showMenu, reveal) } ItemInfoAction(cInfo, cItem, showItemDetails, showMenu) - DeleteItemAction(cItem, revealed, showMenu, questionText = deleteMessageQuestionText(), deleteMessage, deleteMessages) + DeleteItemAction(chatsCtx, cItem, revealed, showMenu, questionText = deleteMessageQuestionText(), deleteMessage, deleteMessages) if (cItem.canBeDeletedForSelf) { Divider() SelectItemAction(showMenu, selectChatItem) @@ -560,7 +560,7 @@ fun ChatItemView( fun ContentItem() { val mc = cItem.content.msgContent if (cItem.meta.itemDeleted != null && (!revealed.value || cItem.isDeletedContent)) { - MarkedDeletedItemView(cItem, cInfo, cInfo.timedMessagesTTL, revealed, showViaProxy = showViaProxy, showTimestamp = showTimestamp) + MarkedDeletedItemView(chatsCtx, cItem, cInfo, cInfo.timedMessagesTTL, revealed, showViaProxy = showViaProxy, showTimestamp = showTimestamp) MarkedDeletedItemDropdownMenu() } else { if (cItem.quotedItem == null && cItem.meta.itemForwarded == null && cItem.meta.itemDeleted == null && !cItem.meta.isLive) { @@ -582,7 +582,7 @@ fun ChatItemView( DeletedItemView(cItem, cInfo.timedMessagesTTL, showViaProxy = showViaProxy, showTimestamp = showTimestamp) DefaultDropdownMenu(showMenu) { ItemInfoAction(cInfo, cItem, showItemDetails, showMenu) - DeleteItemAction(cItem, revealed, showMenu, questionText = deleteMessageQuestionText(), deleteMessage, deleteMessages) + DeleteItemAction(chatsCtx, cItem, revealed, showMenu, questionText = deleteMessageQuestionText(), deleteMessage, deleteMessages) if (cItem.canBeDeletedForSelf) { Divider() SelectItemAction(showMenu, selectChatItem) @@ -631,14 +631,13 @@ fun ChatItemView( } @Composable fun EventItemView() { - val chatsCtx = if (LocalContentTag.current == null) chatModel.chatsContext else chatModel.secondaryChatsContext val reversedChatItems = chatsCtx.chatItems.value.asReversed() CIEventView(eventItemViewText(reversedChatItems)) } @Composable fun DeletedItem() { - MarkedDeletedItemView(cItem, cInfo, cInfo.timedMessagesTTL, revealed, showViaProxy = showViaProxy, showTimestamp = showTimestamp) + MarkedDeletedItemView(chatsCtx, cItem, cInfo, cInfo.timedMessagesTTL, revealed, showViaProxy = showViaProxy, showTimestamp = showTimestamp) DefaultDropdownMenu(showMenu) { if (revealed.value) { HideItemAction(revealed, showMenu, reveal) @@ -648,7 +647,7 @@ fun ChatItemView( ExpandItemAction(revealed, showMenu, reveal) } ItemInfoAction(cInfo, cItem, showItemDetails, showMenu) - DeleteItemAction(cItem, revealed, showMenu, questionText = generalGetString(MR.strings.delete_message_cannot_be_undone_warning), deleteMessage, deleteMessages) + DeleteItemAction(chatsCtx, cItem, revealed, showMenu, questionText = generalGetString(MR.strings.delete_message_cannot_be_undone_warning), deleteMessage, deleteMessages) if (cItem.canBeDeletedForSelf) { Divider() SelectItemAction(showMenu, selectChatItem) @@ -729,11 +728,11 @@ fun ChatItemView( MsgContentItemDropdownMenu() } is CIContent.RcvChatFeature -> { - CIChatFeatureView(cInfo, cItem, c.feature, c.enabled.iconColor, revealed = revealed, showMenu = showMenu) + CIChatFeatureView(chatsCtx, cInfo, cItem, c.feature, c.enabled.iconColor, revealed = revealed, showMenu = showMenu) MsgContentItemDropdownMenu() } is CIContent.SndChatFeature -> { - CIChatFeatureView(cInfo, cItem, c.feature, c.enabled.iconColor, revealed = revealed, showMenu = showMenu) + CIChatFeatureView(chatsCtx, cInfo, cItem, c.feature, c.enabled.iconColor, revealed = revealed, showMenu = showMenu) MsgContentItemDropdownMenu() } is CIContent.RcvChatPreference -> { @@ -742,23 +741,23 @@ fun ChatItemView( DeleteItemMenu() } is CIContent.SndChatPreference -> { - CIChatFeatureView(cInfo, cItem, c.feature, MaterialTheme.colors.secondary, icon = c.feature.icon, revealed, showMenu = showMenu) + CIChatFeatureView(chatsCtx, cInfo, cItem, c.feature, MaterialTheme.colors.secondary, icon = c.feature.icon, revealed, showMenu = showMenu) MsgContentItemDropdownMenu() } is CIContent.RcvGroupFeature -> { - CIChatFeatureView(cInfo, cItem, c.groupFeature, c.preference.enabled(c.memberRole_, (cInfo as? ChatInfo.Group)?.groupInfo?.membership).iconColor, revealed = revealed, showMenu = showMenu) + CIChatFeatureView(chatsCtx, cInfo, cItem, c.groupFeature, c.preference.enabled(c.memberRole_, (cInfo as? ChatInfo.Group)?.groupInfo?.membership).iconColor, revealed = revealed, showMenu = showMenu) MsgContentItemDropdownMenu() } is CIContent.SndGroupFeature -> { - CIChatFeatureView(cInfo, cItem, c.groupFeature, c.preference.enabled(c.memberRole_, (cInfo as? ChatInfo.Group)?.groupInfo?.membership).iconColor, revealed = revealed, showMenu = showMenu) + CIChatFeatureView(chatsCtx, cInfo, cItem, c.groupFeature, c.preference.enabled(c.memberRole_, (cInfo as? ChatInfo.Group)?.groupInfo?.membership).iconColor, revealed = revealed, showMenu = showMenu) MsgContentItemDropdownMenu() } is CIContent.RcvChatFeatureRejected -> { - CIChatFeatureView(cInfo, cItem, c.feature, Color.Red, revealed = revealed, showMenu = showMenu) + CIChatFeatureView(chatsCtx, cInfo, cItem, c.feature, Color.Red, revealed = revealed, showMenu = showMenu) MsgContentItemDropdownMenu() } is CIContent.RcvGroupFeatureRejected -> { - CIChatFeatureView(cInfo, cItem, c.groupFeature, Color.Red, revealed = revealed, showMenu = showMenu) + CIChatFeatureView(chatsCtx, cInfo, cItem, c.groupFeature, Color.Red, revealed = revealed, showMenu = showMenu) MsgContentItemDropdownMenu() } is CIContent.SndModerated -> DeletedItem() @@ -830,6 +829,7 @@ fun ItemInfoAction( @Composable fun DeleteItemAction( + chatsCtx: ChatModel.ChatsContext, cItem: ChatItem, revealed: State, showMenu: MutableState, @@ -838,8 +838,6 @@ fun DeleteItemAction( deleteMessages: (List) -> Unit, buttonText: String = stringResource(MR.strings.delete_verb), ) { - val contentTag = LocalContentTag.current - val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext ItemAction( buttonText, painterResource(MR.images.ic_delete), @@ -1424,6 +1422,7 @@ fun PreviewChatItemView( chatItem: ChatItem = ChatItem.getSampleData(1, CIDirection.DirectSnd(), Clock.System.now(), "hello") ) { ChatItemView( + chatsCtx = ChatModel.ChatsContext(contentTag = null), rhId = null, ChatInfo.Direct.sampleData, chatItem, @@ -1473,6 +1472,7 @@ fun PreviewChatItemView( fun PreviewChatItemViewDeletedContent() { SimpleXTheme { ChatItemView( + chatsCtx = ChatModel.ChatsContext(contentTag = null), rhId = null, ChatInfo.Direct.sampleData, ChatItem.getDeletedContentSampleData(), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/MarkedDeletedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/MarkedDeletedItemView.kt index af9df2cb9a..84bc14fee3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/MarkedDeletedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/MarkedDeletedItemView.kt @@ -12,17 +12,15 @@ import androidx.compose.runtime.* import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* -import chat.simplex.common.model.ChatController.chatModel import chat.simplex.common.model.ChatModel.getChatItemIndexOrNull import chat.simplex.common.ui.theme.* -import chat.simplex.common.views.chat.group.LocalContentTag import chat.simplex.common.views.helpers.generalGetString import chat.simplex.res.MR import dev.icerock.moko.resources.compose.stringResource import kotlinx.datetime.Clock @Composable -fun MarkedDeletedItemView(ci: ChatItem, chatInfo: ChatInfo, timedMessagesTTL: Int?, revealed: State, showViaProxy: Boolean, showTimestamp: Boolean) { +fun MarkedDeletedItemView(chatsCtx: ChatModel.ChatsContext, ci: ChatItem, chatInfo: ChatInfo, timedMessagesTTL: Int?, revealed: State, showViaProxy: Boolean, showTimestamp: Boolean) { val sentColor = MaterialTheme.appColors.sentMessage val receivedColor = MaterialTheme.appColors.receivedMessage Surface( @@ -35,7 +33,7 @@ fun MarkedDeletedItemView(ci: ChatItem, chatInfo: ChatInfo, timedMessagesTTL: In verticalAlignment = Alignment.CenterVertically ) { Box(Modifier.weight(1f, false)) { - MergedMarkedDeletedText(ci, chatInfo, revealed) + MergedMarkedDeletedText(chatsCtx, ci, chatInfo, revealed) } CIMetaView(ci, timedMessagesTTL, showViaProxy = showViaProxy, showTimestamp = showTimestamp) } @@ -43,8 +41,7 @@ fun MarkedDeletedItemView(ci: ChatItem, chatInfo: ChatInfo, timedMessagesTTL: In } @Composable -private fun MergedMarkedDeletedText(chatItem: ChatItem, chatInfo: ChatInfo, revealed: State) { - val chatsCtx = if (LocalContentTag.current == null) chatModel.chatsContext else chatModel.secondaryChatsContext +private fun MergedMarkedDeletedText(chatsCtx: ChatModel.ChatsContext, chatItem: ChatItem, chatInfo: ChatInfo, revealed: State) { val reversedChatItems = chatsCtx.chatItems.value.asReversed() var i = getChatItemIndexOrNull(chatItem, reversedChatItems) val ciCategory = chatItem.mergeCategory diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt index e8350e606a..958b794bd7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt @@ -189,7 +189,7 @@ fun ErrorChatListItem() { suspend fun directChatAction(rhId: Long?, contact: Contact, chatModel: ChatModel) { when { contact.activeConn == null && contact.profile.contactLink != null && contact.active -> askCurrentOrIncognitoProfileConnectContactViaAddress(chatModel, rhId, contact, close = null, openChat = true) - else -> openChat(rhId, ChatInfo.Direct(contact)) + else -> openDirectChat(rhId, contact.contactId) } } @@ -197,30 +197,33 @@ suspend fun groupChatAction(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatMo when (groupInfo.membership.memberStatus) { GroupMemberStatus.MemInvited -> acceptGroupInvitationAlertDialog(rhId, groupInfo, chatModel, inProgress) GroupMemberStatus.MemAccepted -> groupInvitationAcceptedAlert(rhId) - else -> openChat(rhId, ChatInfo.Group(groupInfo)) + else -> openGroupChat(rhId, groupInfo.groupId) } } -suspend fun noteFolderChatAction(rhId: Long?, noteFolder: NoteFolder) = openChat(rhId, ChatInfo.Local(noteFolder)) +suspend fun noteFolderChatAction(rhId: Long?, noteFolder: NoteFolder) = openChat(secondaryChatsCtx = null, rhId, ChatInfo.Local(noteFolder)) -suspend fun openDirectChat(rhId: Long?, contactId: Long) = openChat(rhId, ChatType.Direct, contactId) +suspend fun openDirectChat(rhId: Long?, contactId: Long) = openChat(secondaryChatsCtx = null, rhId, ChatType.Direct, contactId) -suspend fun openGroupChat(rhId: Long?, groupId: Long, contentTag: MsgContentTag? = null) = openChat(rhId, ChatType.Group, groupId, contentTag) +suspend fun openGroupChat(rhId: Long?, groupId: Long) = openChat(secondaryChatsCtx = null, rhId, ChatType.Group, groupId) -suspend fun openChat(rhId: Long?, chatInfo: ChatInfo, contentTag: MsgContentTag? = null) = openChat(rhId, chatInfo.chatType, chatInfo.apiId, contentTag) +suspend fun openChat(secondaryChatsCtx: ChatModel.ChatsContext?, rhId: Long?, chatInfo: ChatInfo) = openChat(secondaryChatsCtx, rhId, chatInfo.chatType, chatInfo.apiId) suspend fun openChat( + secondaryChatsCtx: ChatModel.ChatsContext?, rhId: Long?, chatType: ChatType, apiId: Long, - contentTag: MsgContentTag? = null, openAroundItemId: Long? = null -) = +) { + if (secondaryChatsCtx != null) { + chatModel.secondaryChatsContext.value = secondaryChatsCtx + } apiLoadMessages( + chatsCtx = secondaryChatsCtx ?: chatModel.chatsContext, rhId, chatType, apiId, - contentTag, if (openAroundItemId != null) { ChatPagination.Around(openAroundItemId, ChatPagination.INITIAL_COUNT) } else { @@ -229,23 +232,22 @@ suspend fun openChat( "", openAroundItemId ) +} -suspend fun openLoadedChat(chat: Chat, contentTag: MsgContentTag? = null) { +suspend fun openLoadedChat(chat: Chat) { withContext(Dispatchers.Main) { - val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext - chatsCtx.chatItemStatuses.clear() - chatsCtx.chatItems.replaceAll(chat.chatItems) + chatModel.chatsContext.chatItemStatuses.clear() + chatModel.chatsContext.chatItems.replaceAll(chat.chatItems) chatModel.chatId.value = chat.chatInfo.id - chatsCtx.chatState.clear() + chatModel.chatsContext.chatState.clear() } } -suspend fun apiFindMessages(ch: Chat, search: String, contentTag: MsgContentTag?) { +suspend fun apiFindMessages(chatsCtx: ChatModel.ChatsContext, ch: Chat, search: String) { withContext(Dispatchers.Main) { - val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext chatsCtx.chatItems.clearAndNotify() } - apiLoadMessages(ch.remoteHostId, ch.chatInfo.chatType, ch.chatInfo.apiId, contentTag, pagination = if (search.isNotEmpty()) ChatPagination.Last(ChatPagination.INITIAL_COUNT) else ChatPagination.Initial(ChatPagination.INITIAL_COUNT), search = search) + apiLoadMessages(chatsCtx, ch.remoteHostId, ch.chatInfo.chatType, ch.chatInfo.apiId, pagination = if (search.isNotEmpty()) ChatPagination.Last(ChatPagination.INITIAL_COUNT) else ChatPagination.Initial(ChatPagination.INITIAL_COUNT), search = search) } suspend fun setGroupMembers(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel) = coroutineScope { @@ -608,9 +610,7 @@ fun markChatRead(c: Chat) { chatModel.chatsContext.markChatItemsRead(chat.remoteHostId, chat.chatInfo.id) } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - chatModel.secondaryChatsContext.markChatItemsRead(chat.remoteHostId, chat.chatInfo.id) - } + chatModel.secondaryChatsContext.value?.markChatItemsRead(chat.remoteHostId, chat.chatInfo.id) } chatModel.controller.apiChatRead( chat.remoteHostId, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/contacts/ContactListNavView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/contacts/ContactListNavView.kt index 6ea7e9fc02..4e65a3649e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/contacts/ContactListNavView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/contacts/ContactListNavView.kt @@ -55,13 +55,13 @@ fun ContactListNavLinkView(chat: Chat, nextChatSelected: State, showDel when (contactType) { ContactType.RECENT -> { withApi { - openChat(rhId, chat.chatInfo) + openChat(secondaryChatsCtx = null, rhId, chat.chatInfo) ModalManager.start.closeModals() } } ContactType.CHAT_DELETED -> { withApi { - openChat(rhId, chat.chatInfo) + openChat(secondaryChatsCtx = null, rhId, chat.chatInfo) ModalManager.start.closeModals() } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index a2fcae9d7d..4a911fa6f0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -543,11 +543,9 @@ fun deleteChatDatabaseFilesAndState() { chatModel.chatsContext.popChatCollector.clear() } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - chatModel.secondaryChatsContext.chatItems.clearAndNotify() - chatModel.secondaryChatsContext.chats.clear() - chatModel.secondaryChatsContext.popChatCollector.clear() - } + chatModel.secondaryChatsContext.value?.chatItems?.clearAndNotify() + chatModel.secondaryChatsContext.value?.chats?.clear() + chatModel.secondaryChatsContext.value?.popChatCollector?.clear() } } chatModel.users.clear() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt index 4848e791e1..af207d1381 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt @@ -161,13 +161,20 @@ class ModalManager(private val placement: ModalPlacement? = null) { fun closeModal() { if (modalViews.isNotEmpty()) { - if (modalViews.lastOrNull()?.animated == false) modalViews.removeAt(modalViews.lastIndex) - else runAtomically { toRemove.add(modalViews.lastIndex - min(toRemove.size, modalViews.lastIndex)) } + val lastModal = modalViews.lastOrNull() + if (lastModal != null) { + if (lastModal.id == ModalViewId.SECONDARY_CHAT) chatModel.secondaryChatsContext.value = null + if (!lastModal.animated) + modalViews.removeAt(modalViews.lastIndex) + else + runAtomically { toRemove.add(modalViews.lastIndex - min(toRemove.size, modalViews.lastIndex)) } + } } _modalCount.value = modalViews.size - toRemove.size } fun closeModals() { + chatModel.secondaryChatsContext.value = null modalViews.clear() toRemove.clear() _modalCount.value = 0 diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt index 1d0a873c7d..dfffb826f5 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt @@ -61,9 +61,7 @@ fun showApp() { chatModel.chatsContext.chatItems.clearAndNotify() } withContext(Dispatchers.Main) { - if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) { - chatModel.secondaryChatsContext.chatItems.clearAndNotify() - } + chatModel.secondaryChatsContext.value = null } } } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt index e295144191..9fd65ec995 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt @@ -51,7 +51,7 @@ private fun ActiveCallInteractiveAreaOneHand(call: Call, showMenu: MutableState< val chat = chatModel.getChat(call.contact.id) if (chat != null) { withBGApi { - openChat(chat.remoteHostId, chat.chatInfo) + openChat(secondaryChatsCtx = null, chat.remoteHostId, chat.chatInfo) } } }, @@ -116,7 +116,7 @@ private fun ActiveCallInteractiveAreaNonOneHand(call: Call, showMenu: MutableSta val chat = chatModel.getChat(call.contact.id) if (chat != null) { withBGApi { - openChat(chat.remoteHostId, chat.chatInfo) + openChat(secondaryChatsCtx = null, chat.remoteHostId, chat.chatInfo) } } }, From 82f9fecccf6e970d7765f25dfb4916e9c91bb4b2 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 16 Apr 2025 19:21:42 +0100 Subject: [PATCH 511/567] =?UTF-8?q?android,=20desktop:=20enable=20reaction?= =?UTF-8?q?s=20=F0=9F=98=82=20and=20=E2=9C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/chat/simplex/common/model/ChatModel.kt | 8 +++++--- .../chat/simplex/common/views/chat/item/ChatItemView.kt | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 7e5eed3c42..47d9563ee3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -3336,14 +3336,16 @@ sealed class MsgReaction { } companion object { - val values: List get() = MREmojiChar.values().map(::Emoji) - val old: List get() = listOf( + val values: List get() = MREmojiChar.entries.map(::Emoji) + val supported: List get() = listOf( MREmojiChar.ThumbsUp, MREmojiChar.ThumbsDown, MREmojiChar.Smile, + MREmojiChar.Laugh, MREmojiChar.Sad, MREmojiChar.Heart, - MREmojiChar.Launch + MREmojiChar.Launch, + MREmojiChar.Check ).map(::Emoji) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index 5bbbc33741..2e789df7bc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -337,7 +337,7 @@ fun ChatItemView( @Composable fun MsgReactionsMenu() { - val rs = MsgReaction.old.mapNotNull { r -> + val rs = MsgReaction.supported.mapNotNull { r -> if (null == cItem.reactions.find { it.userReacted && it.reaction.text == r.text }) { r } else { @@ -348,7 +348,7 @@ fun ChatItemView( Row(modifier = Modifier.padding(horizontal = DEFAULT_PADDING).horizontalScroll(rememberScrollState()), verticalAlignment = Alignment.CenterVertically) { rs.forEach() { r -> Box( - Modifier.size(36.dp).clickable { + Modifier.size(36.dp).clip(CircleShape).clickable { setReaction(cInfo, cItem, true, r) showMenu.value = false }, From b637d370f3aa9a010f32d1ac723412a8df500d3e Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 23 Apr 2025 08:33:17 +0100 Subject: [PATCH 512/567] core: 6.3.3.0 (simplexmq 6.4.0.1) --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- src/Simplex/Chat/Remote.hs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cabal.project b/cabal.project index 1bf1289900..28403c9fff 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 305f79d2a66a8d122bf457e023988200bb7fe00c + tag: ec5a60430d6e2b1b4f33fa1790effbb6060bf7b8 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index e2df71ae0f..e71a13b22a 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."305f79d2a66a8d122bf457e023988200bb7fe00c" = "1lawc5pf4hgc6wym2xz8gi92izi1vk98ppv3ldrpajz1mq62ifpc"; + "https://github.com/simplex-chat/simplexmq.git"."ec5a60430d6e2b1b4f33fa1790effbb6060bf7b8" = "0q238j7w976f9nx7r3gd61yhj557zwcxvrbci5lq7fib0v4ja7aw"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 3eb75d5cf6..a01345a910 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.3.2.0 +version: 6.3.3.0 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index a7f44eb465..8c87851af7 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -75,11 +75,11 @@ remoteFilesFolder = "simplex_v1_files" -- when acting as host minRemoteCtrlVersion :: AppVersion -minRemoteCtrlVersion = AppVersion [6, 3, 0, 8] +minRemoteCtrlVersion = AppVersion [6, 3, 3, 0] -- when acting as controller minRemoteHostVersion :: AppVersion -minRemoteHostVersion = AppVersion [6, 3, 0, 8] +minRemoteHostVersion = AppVersion [6, 3, 3, 0] currentAppVersion :: AppVersion currentAppVersion = AppVersion SC.version From a6fd5ce9020006d11fdfbbb718515d6ec2ccb55b Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 23 Apr 2025 10:30:37 +0100 Subject: [PATCH 513/567] website: translations (#5845) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (Hebrew) Currently translated at 100.0% (258 of 258 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/he/ * Translated using Weblate (Hebrew) Currently translated at 100.0% (258 of 258 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/he/ * Translated using Weblate (German) Currently translated at 100.0% (258 of 258 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/de/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (258 of 258 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (258 of 258 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ * Translated using Weblate (German) Currently translated at 100.0% (258 of 258 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/de/ --------- Co-authored-by: מילקי צבעוני Co-authored-by: mlanp Co-authored-by: Ghost of Sparta Co-authored-by: summoner001 --- website/langs/de.json | 2 +- website/langs/he.json | 8 +++++--- website/langs/hu.json | 42 +++++++++++++++++++++--------------------- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/website/langs/de.json b/website/langs/de.json index 3b1e9d34e8..00c34478e7 100644 --- a/website/langs/de.json +++ b/website/langs/de.json @@ -54,7 +54,7 @@ "simplex-private-6-title": "Out-of-Band-
    Schlüsselaustausch", "simplex-private-9-title": "Unidirektionale
    Nachrichten-Warteschlangen", "simplex-private-10-title": "Temporäre, anonyme paarweise Kennungen", - "simplex-private-card-1-point-1": "Double-Ratchet-Protokoll —
    Off-the-Record-Nachrichten mit Perfect Forward Secrecy und Einbruchsresistenz.", + "simplex-private-card-1-point-1": "Double-Ratchet-Protokoll —
    Off-the-Record-Nachrichten mit Perfect Forward Secrecy, Abstreitbarkeit und Wiederherstellung nach einer Kompromittierung.", "simplex-private-card-1-point-2": "NaCL-Kryptobox in jeder Warteschlange, um eine Korrelation des Datenverkehrs zwischen Nachrichtenwarteschlangen zu verhindern, falls TLS kompromittiert wurde.", "simplex-private-card-3-point-1": "Für Client-Server-Verbindungen wird nur TLS 1.2/1.3 mit starken Algorithmen verwendet.", "simplex-private-card-2-point-1": "Zusätzliche Server-Verschlüsselungs-Schicht für die Zustellung an den Empfänger, um eine Korrelation zwischen empfangenen und gesendeten Server-Daten zu vermeiden, falls TLS kompromittiert wurde.", diff --git a/website/langs/he.json b/website/langs/he.json index 4fd966f05d..878f0e51b2 100644 --- a/website/langs/he.json +++ b/website/langs/he.json @@ -75,12 +75,12 @@ "privacy-matters-2-overlay-1-linkText": "פרטיות מעניקה לכם עוצמה", "privacy-matters-3-overlay-1-linkText": "פרטיות מגנה על החופש שלכם", "simplex-explained-tab-1-p-1": "אתם יכולים ליצור אנשי קשר וקבוצות, ולנהל שיחות דו-כיווניות, כמו בכל תוכנה אחרת לשליחת הודעות.", - "hero-overlay-3-title": "הערכת אבטחה", + "hero-overlay-3-title": "הערכות אבטחה", "simplex-unique-1-overlay-1-title": "פרטיות מלאה של הזהות, הפרופיל, אנשי הקשר והמטא נתונים שלך", "simplex-unique-3-overlay-1-title": "בעלות, שליטה ואבטחה של הנתונים שלך", "simplex-unique-2-overlay-1-title": "ההגנה הטובה ביותר מפני ספאם וניצול לרעה", "simplex-unique-3-title": "אתה שולט בנתונים שלך", - "hero-overlay-3-textlink": "הערכת אבטחה", + "hero-overlay-3-textlink": "הערכות אבטחה", "simplex-unique-4-overlay-1-title": "מבוזר לחלוטין — המשתמשים הם הבעלים של רשת SimpleX", "simplex-unique-2-title": "אתה מוגן
    מפני ספאם וניצול לרעה", "simplex-unique-4-title": "רשת SimpleX בבעלותך", @@ -254,5 +254,7 @@ "please-use-link-in-mobile-app": "אנא השתמש בקישור באפליקציה במכשיר נייד", "docs-dropdown-10": "שקיפות", "docs-dropdown-11": "שאלות ותשובות", - "docs-dropdown-12": "אבטחה" + "docs-dropdown-12": "אבטחה", + "hero-overlay-card-3-p-3": "Trail of bits סקר את הקוד הקריפטוגרפי של פרוטוקולי רשת SimpleX ביולי 2024. קרא עוד.", + "docs-dropdown-14": "SimpleX לעסקים" } diff --git a/website/langs/hu.json b/website/langs/hu.json index 46ad9668cb..60eeeafe13 100644 --- a/website/langs/hu.json +++ b/website/langs/hu.json @@ -1,7 +1,7 @@ { "home": "Kezdőoldal", "developers": "Fejlesztők", - "reference": "Referencia", + "reference": "Hivatkozás", "blog": "Blog", "features": "Funkciók", "why-simplex": "Miért válassza a SimpleXet", @@ -18,15 +18,15 @@ "simplex-explained-tab-3-p-1": "A kiszolgálók minden egyes üzenetsorbaállításhoz külön névtelen hitelesítő-adatokkal rendelkeznek, és nem tudják, hogy melyik felhasználóhoz tartoznak.", "simplex-explained-tab-3-p-2": "A felhasználók tovább fokozhatják a metaadatok adatvédelmét, ha a Tor segítségével férnek hozzá a kiszolgálókhoz, így megakadályozva az IP-cím szerinti korrelációt.", "smp-protocol": "SMP-protokoll", - "chat-protocol": "Csevegésprotokoll", - "donate": "Támogatás", + "chat-protocol": "Csevegési protokoll", + "donate": "Adományozás", "copyright-label": "© 2020-2025 SimpleX | Nyílt forráskódú projekt", "simplex-chat-protocol": "A SimpleX Chat-protokoll", "terminal-cli": "Terminál CLI", "terms-and-privacy-policy": "Adatvédelmi irányelvek", "hero-header": "Újradefiniált adatvédelem", "hero-subheader": "Az első üzenetváltó-alkalmazás
    felhasználói azonosítók nélkül", - "hero-p-1": "Más alkalmazások felhasználói azonosítókkal rendelkeznek: Signal, Matrix, Session, Briar, Jami, Cwtch, stb.
    A SimpleX nem, még véletlenszerű számokkal sem.
    Ez radikálisan javítja az adatvédelmet.", + "hero-p-1": "Más alkalmazások felhasználói azonosítókkal rendelkeznek: Signal, Matrix, Session, Briar, Jami, Cwtch, stb.
    A SimpleX azonban nem, még véletlenszerű számokkal sem.
    Ez radikálisan javítja az adatvédelmet.", "hero-overlay-1-textlink": "Miért ártanak a felhasználói azonosítók az adatvédelemnek?", "hero-overlay-2-textlink": "Hogyan működik a SimpleX?", "hero-overlay-3-textlink": "Biztonsági felmérések", @@ -43,7 +43,7 @@ "feature-6-title": "E2E-titkosított
    hang- és videohívások", "feature-7-title": "Hordozható titkosított alkalmazás-adattárolás — profil átköltöztetése egy másik eszközre", "feature-8-title": "Az inkognitómód —
    egyedülálló a SimpleX Chatben", - "simplex-network-overlay-1-title": "Összehasonlítás más P2P üzenetküldő protokollokkal", + "simplex-network-overlay-1-title": "Összehasonlítás más P2P-üzenetküldő protokollokkal", "simplex-private-1-title": "2 rétegű végpontok közötti titkosítás", "simplex-private-2-title": "További rétege a
    kiszolgáló-titkosítás", "simplex-private-4-title": "Nem kötelező
    hozzáférés Tor-on keresztül", @@ -59,7 +59,7 @@ "simplex-private-card-3-point-1": "A kliens és a kiszolgálók közötti kapcsolatokhoz csak az erős algoritmusokkal rendelkező TLS 1.2/1.3 protokollt használja.", "simplex-private-card-3-point-2": "A kiszolgáló ujjlenyomata és a csatornakötés megakadályozza a MITM- és a visszajátszási támadásokat.", "simplex-private-card-3-point-3": "Az újrakapcsolódás le van tiltva a munkamenet elleni támadások megelőzése érdekében.", - "simplex-private-card-4-point-1": "Az IP-címe védelme érdekében a kiszolgálókat a TORon vagy más átvitel-átfedő-hálózaton keresztül is elérheti.", + "simplex-private-card-4-point-1": "Az IP-címe védelme érdekében a kiszolgálókat a Tor-on vagy más átvitel-átfedő-hálózaton keresztül is elérheti.", "simplex-private-card-6-point-1": "Számos kommunikációs platform sebezhető a kiszolgálók vagy a hálózat-szolgáltatók MITM-támadásaival szemben.", "simplex-private-card-6-point-2": "Ennek megakadályozása érdekében a SimpleX-alkalmazások egyszeri kulcsokat adnak át sávon kívül, amikor egy címet hivatkozásként vagy QR-kódként oszt meg.", "simplex-private-card-7-point-1": "Az integritás garantálása érdekében az üzenetek sorszámozással vannak ellátva, és tartalmazzák az előző üzenet hasítóértékét.", @@ -79,20 +79,20 @@ "privacy-matters-3-overlay-1-linkText": "Az adatvédelem szabaddá tesz", "simplex-unique-1-title": "Teljes magánéletet élvezhet", "simplex-unique-1-overlay-1-title": "Személyazonosságának, profiljának, kapcsolatainak és metaadatainak teljes körű védelme", - "simplex-unique-2-title": "Véd
    a kéretlen üzenetektől és a visszaélésektől", - "simplex-unique-2-overlay-1-title": "A legjobb védelem a kéretlen üzenetek és a visszaélések ellen", + "simplex-unique-2-title": "Véd
    a kéretlen tartalmaktól és a visszaélésektől", + "simplex-unique-2-overlay-1-title": "A legjobb védelem a kéretlen tartalmak és a visszaélések ellen", "simplex-unique-3-title": "Ön kezeli az adatait", "simplex-unique-3-overlay-1-title": "Az adatok biztonsága és kezelése az Ön kezében van", "simplex-unique-4-title": "Öné a SimpleX-hálózat", "simplex-unique-4-overlay-1-title": "Teljesen decentralizált — a SimpleX-hálózat a felhasználóké", "hero-overlay-card-1-p-1": "Sok felhasználó kérdezte: ha a SimpleXnek nincsenek felhasználói azonosítói, honnan tudja, hogy hová kell eljuttatni az üzeneteket?", "hero-overlay-card-1-p-2": "Az üzenetek kézbesítéséhez az összes többi platform által használt felhasználói azonosítók helyett a SimpleX az üzenetek sorbaállításához ideiglenes, névtelen, páros azonosítókat használ, külön-külön minden egyes kapcsolathoz — nincsenek hosszú távú azonosítók.", - "hero-overlay-card-1-p-4": "Ez a kialakítás megakadályozza a felhasználók metaadatainak kiszivárgását az alkalmazás szintjén. Az adatvédelem további javítása és az IP-cím védelme érdekében az üzenetküldő kiszolgálókhoz TOR hálózaton keresztül is kapcsolódhat.", + "hero-overlay-card-1-p-4": "Ez a kialakítás megakadályozza a felhasználók metaadatainak kiszivárgását az alkalmazás szintjén. Az adatvédelem további javítása és az IP-cím védelme érdekében az üzenetküldő kiszolgálókhoz Tor hálózaton keresztül is kapcsolódhat.", "hero-overlay-card-1-p-5": "Csak a kliensek tárolják a felhasználói profilokat, kapcsolatokat és csoportokat; az üzenetek küldése 2 rétegű végpontok közötti titkosítással történik.", "hero-overlay-card-1-p-6": "További leírást a SimpleX ismertetőben olvashat.", "hero-overlay-card-2-p-1": "Ha a felhasználók állandó azonosítóval rendelkeznek, még akkor is, ha ez csak egy véletlenszerű szám, például egy munkamenet-azonosító, fennáll annak a veszélye, hogy a szolgáltató vagy egy támadó megfigyelheti, azt hogy hogyan kapcsolódnak a felhasználók egymáshoz, és hány üzenetet küldenek egymásnak.", "hero-overlay-card-2-p-2": "Ezt az információt aztán összefüggésbe hozhatják a meglévő nyilvános közösségi hálózatokkal, és meghatározhatnak néhány valódi személyazonosságot.", - "hero-overlay-card-2-p-3": "Még a TOR v3 szolgáltatásokat használó, legprivátabb alkalmazások esetében is, ha két különböző kapcsolattartóval beszél ugyanazon a profilon keresztül, bizonyítani tudják, hogy ugyanahhoz a személyhez kapcsolódnak.", + "hero-overlay-card-2-p-3": "Még a Tor v3 szolgáltatásokat használó, legprivátabb alkalmazások esetében is, ha két különböző kapcsolattartóval beszél ugyanazon a profilon keresztül, bizonyítani tudják, hogy ugyanahhoz a személyhez kapcsolódnak.", "hero-overlay-card-2-p-4": "A SimpleX úgy védekezik ezen támadások ellen, hogy nem tartalmaz felhasználói azonosítókat. Ha pedig használja az inkognitómódot, akkor minden egyes létrejött kapcsolatban más-más felhasználó név jelenik meg, így elkerülhető a közöttük lévő összefüggések teljes bizonyítása.", "hero-overlay-card-3-p-1": "Trail of Bits egy vezető biztonsági és technológiai tanácsadó cég, amelynek az ügyfelei közé tartoznak nagy technológiai cégek, kormányzati ügynökségek és jelentős blokklánc projektek.", "hero-overlay-card-3-p-2": "A Trail of Bits 2022 novemberében áttekintette a SimpleX-platform kriptográfiai és hálózati komponenseit. További információk.", @@ -116,7 +116,7 @@ "simplex-unique-overlay-card-1-p-2": "Az üzenetek kézbesítéséhez a SimpleX az egyirányú üzenet várakoztatást használ páronkénti névtelen címekkel, külön a fogadott és külön az elküldött üzenetek számára, általában különböző kiszolgálókon keresztül. A SimpleX használata olyan, mintha minden egyes kapcsolatnak más-más “eldobható” e-mail-címe vagy telefonja lenne és nem kell ezeket gondosan kezelni.", "simplex-unique-overlay-card-1-p-3": "Ez a kialakítás megvédi annak titkosságát, hogy kivel kommunikál, elrejtve azt a SimpleX-platform kiszolgálói és a megfigyelők elől. IP-címének a kiszolgálók elől való elrejtéséhez azt teheti meg, hogy Toron keresztül kapcsolódik a SimpleX-kiszolgálókhoz.", "simplex-unique-overlay-card-2-p-1": "Mivel ön nem rendelkezik azonosítóval a SimpleX-platformon, senki sem tud kapcsolatba lépni önnel, hacsak nem oszt meg egy egyszeri vagy ideiglenes felhasználói címet, például QR-kódot vagy hivatkozást.", - "simplex-unique-overlay-card-2-p-2": "Még a nem kötelező felhasználói cím esetében is, bár spam kapcsolatfelvételi kérések küldésére használható, megváltoztathatja vagy teljesen törölheti azt anélkül, hogy elveszítené a meglévő kapcsolatait.", + "simplex-unique-overlay-card-2-p-2": "Még a felhasználói cím használata esetén is, aminek használata nem kötelező – ugyanakkor ez a kéretlen kapcsolatkérelmek küldésére is használható – módosíthatja vagy teljesen törölheti anélkül, hogy elveszítené a meglévő kapcsolatait.", "simplex-unique-overlay-card-3-p-1": "A SimpleX Chat az összes felhasználói adatot kizárólag a klienseken tárolja egy hordozható titkosított adatbázis-formátumban, amely exportálható és átvihető bármely más támogatott eszközre.", "simplex-unique-overlay-card-3-p-2": "A végpontok között titkosított üzenetek átmenetileg a SimpleX továbbítókiszolgálóin tartózkodnak, amíg be nem érkeznek a címzetthez, majd automatikusan véglegesen törlődnek onnan.", "simplex-unique-overlay-card-3-p-3": "A föderált hálózatok kiszolgálóitól (e-mail, XMPP vagy Matrix) eltérően a SimpleX-kiszolgálók nem tárolják a felhasználói fiókokat, csak továbbítják az üzeneteket, így védve mindkét fél magánéletét.", @@ -129,7 +129,7 @@ "simplex-unique-card-2-p-1": "Mivel a SimpleX-platformon nincs azonosítója vagy állandó címe, senki sem tud kapcsolatba lépni önnel, hacsak nem oszt meg egy egyszeri vagy ideiglenes felhasználói címet, például QR-kódot vagy hivatkozást.", "simplex-unique-card-3-p-1": "A SimpleX Chat az összes felhasználói adatot kizárólag a klienseken tárolja egy hordozható titkosított adatbázis-formátumban —, amely exportálható és átvihető bármely más támogatott eszközre.", "simplex-unique-card-3-p-2": "A végpontok között titkosított üzenetek átmenetileg a SimpleX továbbítókiszolgálóin tartózkodnak, amíg be nem érkeznek a címzetthez, majd automatikusan véglegesen törlődnek onnan.", - "simplex-unique-card-4-p-1": "A SimpleX hálózat teljesen decentralizált és független bármely kriptopénztől vagy bármely más platformtól, kivéve az internetet.", + "simplex-unique-card-4-p-1": "A SimpleX-hálózat teljesen decentralizált és független bármely kriptopénztől vagy bármely más platformtól, kivéve az internetet.", "simplex-unique-card-4-p-2": "Használhatja a SimpleXet a saját kiszolgálóival vagy az általunk biztosított kiszolgálókkal, és továbbra is kapcsolódhat bármely felhasználóhoz.", "join": "Csatlakozzon a közösségeinkhez", "we-invite-you-to-join-the-conversation": "Meghívjuk Önt, hogy csatlakozzon a beszélgetésekhez", @@ -168,17 +168,17 @@ "privacy-matters-section-subheader": "A metaadatok védelmének megőrzése — kivel beszélget — megvédi a következőktől:", "privacy-matters-section-label": "Győződjön meg arról, hogy az üzenetváltó-alkalmazás amit használ nem fér hozzá az adataidhoz!", "simplex-private-section-header": "Mitől lesz a SimpleX privát", - "simplex-network-section-header": "SimpleX hálózat", - "simplex-network-section-desc": "A Simplex Chat a P2P és a föderált hálózatok előnyeinek kombinálásával biztosítja a legjobb adatvédelmet.", + "simplex-network-section-header": "SimpleX-hálózat", + "simplex-network-section-desc": "A Simplex Chat a P2P- és a föderált hálózatok előnyeinek kombinálásával biztosítja a legjobb adatvédelmet.", "simplex-network-1-desc": "Minden üzenet a kiszolgálókon keresztül kerül elküldésre, ami jobb metaadat-védelmet és megbízható aszinkron üzenetkézbesítést biztosít, miközben elkerülhető a sok", "simplex-network-2-header": "A föderált hálózatokkal ellentétben", "simplex-network-2-desc": "A SimpleX továbbítókiszolgálói NEM tárolnak felhasználói profilokat, kapcsolatokat és kézbesített üzeneteket, NEM kapcsolódnak egymáshoz, és NINCS kiszolgálókönyvtár.", - "simplex-network-3-header": "SimpleX hálózat", + "simplex-network-3-header": "SimpleX-hálózat", "simplex-network-3-desc": "a kiszolgálók egyirányú üzenet várakoztatásokat biztosítanak a felhasználók összekapcsolásához, de nem látják a hálózati kapcsolati gráfot; azt csak a felhasználók látják.", "comparison-section-header": "Összehasonlítás más protokollokkal", "protocol-1-text": "Signal, nagy platformok", "protocol-2-text": "XMPP, Matrix", - "protocol-3-text": "P2P protokollok", + "protocol-3-text": "P2P-protokollok", "comparison-point-1-text": "Globális személyazonosságot igényel", "comparison-point-2-text": "MITM lehetősége", "comparison-point-4-text": "Egyetlen vagy központosított hálózat", @@ -195,7 +195,7 @@ "comparison-section-list-point-4a": "A SimpleX továbbítókiszolgálói nem veszélyeztethetik az e2e titkosítást. Hitelesítse a biztonsági kódot a sávon kívüli csatorna elleni támadások veszélyeinek csökkentésére", "comparison-section-list-point-4": "Ha az üzemeltetett kiszolgálók veszélybe kerülnek. Hitelesítse a biztonsági kódot a Signal vagy más biztonságos üzenetküldő alkalmazás segítségével a támadások veszélyeinek csökkentésére", "comparison-section-list-point-5": "Nem védi a felhasználók metaadatait", - "comparison-section-list-point-6": "Bár a P2P elosztott, de nem föderált - egyetlen hálózatként működnek", + "comparison-section-list-point-6": "Bár a P2P elosztott, de nem föderált – egyetlen hálózatként működnek", "comparison-section-list-point-7": "A P2P-hálózatoknak vagy van egy központi hitelesítője, vagy az egész hálózat kompromittálódhat", "see-here": "tekintse meg itt", "guide-dropdown-1": "Gyors indítás", @@ -224,18 +224,18 @@ "contact-hero-header": "Kapott egy meghívót a SimpleX Chaten való beszélgetéshez", "invitation-hero-header": "Kapott egy egyszer használható meghívót a SimpleX Chaten való beszélgetéshez", "simplex-network-overlay-card-1-li-4": "A P2P-megvalósításokat egyes internetszolgáltatók blokkolhatják (mint például a BitTorrent). A SimpleX átvitel-független - a szabványos webes protokollokon, pl. WebSocketsen keresztül is működik.", - "simplex-private-card-4-point-2": "A SimpleX TORon keresztüli használatához telepítse az Orbot alkalmazást és engedélyezze a SOCKS5 proxyt (vagy a VPN-t az iOS-ban).", + "simplex-private-card-4-point-2": "A SimpleX Tor-on keresztüli használatához telepítse az Orbot alkalmazást és engedélyezze a SOCKS5 proxyt (vagy a VPN-t az iOS-ban).", "simplex-private-card-5-point-1": "A SimpleX minden titkosítási réteghez tartalomkitöltést használ, hogy meghiúsítsa az üzenetméret ellen irányuló támadásokat.", "simplex-private-card-5-point-2": "A kiszolgálók és a hálózatot megfigyelők számára a különböző méretű üzenetek egyformának tűnnek.", "privacy-matters-1-title": "Hirdetés és árdiszkrimináció", "hero-overlay-card-1-p-3": "Ön határozza meg, hogy melyik kiszolgáló(ka)t használja az üzenetek fogadására, a kapcsolatokhoz — azokat a kiszolgálókat, amelyeket az üzenetek küldésére használ. Minden beszélgetés két különböző kiszolgálót használ.", "simplex-network-overlay-card-1-p-1": "A P2P üzenetküldő protokollok és alkalmazások számos problémával küzdenek, amelyek miatt kevésbé megbízhatóak, mint a SimpleX, bonyolultabb az elemzésük és többféle támadással szemben sebezhetőek.", - "chat-bot-example": "Chat bot példa", + "chat-bot-example": "Példa csevegési botra", "simplex-private-3-title": "Biztonságos, hitelesített
    TLS adatátvitel", "github-repository": "GitHub tárolójában", "tap-to-close": "Koppintson a bezáráshoz", - "simplex-network-1-header": "A P2P hálózatokkal ellentétben", - "simplex-network-1-overlay-linktext": "a P2P hálózat problémái", + "simplex-network-1-header": "A P2P-hálózatokkal ellentétben", + "simplex-network-1-overlay-linktext": "a P2P-hálózatok problémái", "comparison-point-3-text": "Függés a DNS-től", "yes": "Igen", "guide-dropdown-9": "Kapcsolatok létrehozása", From 52e2af6e3250d267c1c8aa00505bc13606305f00 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 23 Apr 2025 10:53:55 +0100 Subject: [PATCH 514/567] ui: translations (#5843) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (Italian) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Hebrew) Currently translated at 81.9% (1923 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2346 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hebrew) Currently translated at 82.6% (1938 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/ * Translated using Weblate (Hebrew) Currently translated at 85.8% (2013 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/ * Translated using Weblate (German) Currently translated at 100.0% (2346 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (German) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (German) Currently translated at 100.0% (2346 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Hebrew) Currently translated at 86.5% (2031 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/ * Translated using Weblate (Hebrew) Currently translated at 86.7% (2034 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/ * Translated using Weblate (Hebrew) Currently translated at 86.7% (2035 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2346 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2346 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (German) Currently translated at 100.0% (2346 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (German) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (Czech) Currently translated at 100.0% (2346 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Russian) Currently translated at 97.4% (1998 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ru/ * Translated using Weblate (Italian) Currently translated at 100.0% (2346 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Hebrew) Currently translated at 86.7% (2035 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2346 of 2346 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (French) Currently translated at 96.0% (1971 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (German) Currently translated at 100.0% (2352 of 2352 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2352 of 2352 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Arabic) Currently translated at 100.0% (2352 of 2352 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2352 of 2352 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Italian) Currently translated at 100.0% (2352 of 2352 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2352 of 2352 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Vietnamese) Currently translated at 100.0% (2352 of 2352 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Catalan) Currently translated at 100.0% (2352 of 2352 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ca/ * Translated using Weblate (German) Currently translated at 100.0% (2352 of 2352 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (German) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (Dutch) Currently translated at 100.0% (2352 of 2352 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2352 of 2352 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2352 of 2352 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2352 of 2352 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2352 of 2352 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * process localizations * update ru translations * correct ru case * export --------- Co-authored-by: Random Co-authored-by: מילקי צבעוני Co-authored-by: summoner001 Co-authored-by: mlanp Co-authored-by: gacarel Co-authored-by: Bezruchenko Simon Co-authored-by: zenobit Co-authored-by: thedmdim Co-authored-by: Rafi Co-authored-by: khalidbelk Co-authored-by: 大王叫我来巡山 Co-authored-by: jonnysemon Co-authored-by: No name Co-authored-by: tuananh-ng <158744840+tuananh-ng@users.noreply.github.com> Co-authored-by: fran secs Co-authored-by: M1K4 Co-authored-by: Ghost of Sparta --- .../bg.xcloc/Localized Contents/bg.xliff | 20 ++ .../cs.xcloc/Localized Contents/cs.xliff | 20 ++ .../de.xcloc/Localized Contents/de.xliff | 31 ++- .../en.xcloc/Localized Contents/en.xliff | 25 ++ .../es.xcloc/Localized Contents/es.xliff | 25 +- .../fi.xcloc/Localized Contents/fi.xliff | 20 ++ .../fr.xcloc/Localized Contents/fr.xliff | 79 ++++++ .../hu.xcloc/Localized Contents/hu.xliff | 79 +++--- .../it.xcloc/Localized Contents/it.xliff | 23 +- .../ja.xcloc/Localized Contents/ja.xliff | 20 ++ .../nl.xcloc/Localized Contents/nl.xliff | 27 ++ .../pl.xcloc/Localized Contents/pl.xliff | 20 ++ .../ru.xcloc/Localized Contents/ru.xliff | 37 +++ .../th.xcloc/Localized Contents/th.xliff | 20 ++ .../tr.xcloc/Localized Contents/tr.xliff | 20 ++ .../uk.xcloc/Localized Contents/uk.xliff | 20 ++ .../Localized Contents/zh-Hans.xliff | 20 ++ apps/ios/de.lproj/Localizable.strings | 13 +- apps/ios/es.lproj/Localizable.strings | 7 +- apps/ios/fr.lproj/Localizable.strings | 165 +++++++++++++ apps/ios/hu.lproj/Localizable.strings | 61 ++--- apps/ios/it.lproj/Localizable.strings | 5 +- apps/ios/nl.lproj/Localizable.strings | 15 ++ apps/ios/ru.lproj/Localizable.strings | 48 ++++ .../commonMain/resources/MR/ar/strings.xml | 6 + .../commonMain/resources/MR/ca/strings.xml | 6 + .../commonMain/resources/MR/cs/strings.xml | 11 +- .../commonMain/resources/MR/de/strings.xml | 16 +- .../commonMain/resources/MR/es/strings.xml | 6 + .../commonMain/resources/MR/hu/strings.xml | 60 +++-- .../commonMain/resources/MR/in/strings.xml | 14 +- .../commonMain/resources/MR/it/strings.xml | 8 +- .../commonMain/resources/MR/iw/strings.xml | 231 +++++++++++++++--- .../commonMain/resources/MR/nl/strings.xml | 13 +- .../commonMain/resources/MR/uk/strings.xml | 25 +- .../commonMain/resources/MR/vi/strings.xml | 8 +- .../resources/MR/zh-rCN/strings.xml | 6 + 37 files changed, 1080 insertions(+), 150 deletions(-) diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 6fb6729a9b..213394aa14 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -6800,6 +6800,10 @@ chat item action Сподели с контактите No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code Покажи QR код @@ -6893,6 +6897,10 @@ chat item action SimpleX address or 1-time link? No comment provided by engineer. + + SimpleX channel link + simplex link type + SimpleX contact address SimpleX адрес за контакт @@ -7447,6 +7455,10 @@ It can happen because of some bug or when the connection is compromised.Това е вашят еднократен линк за връзка! No comment provided by engineer. + + This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + No comment provided by engineer. + This link was used with another mobile device, please create a new link on the desktop. No comment provided by engineer. @@ -7729,6 +7741,10 @@ To connect, please ask your contact to create another connection link and check Непрочетено swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. На новите членове се изпращат до последните 100 съобщения. @@ -7876,6 +7892,10 @@ To connect, please ask your contact to create another connection link and check Use servers No comment provided by engineer. + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. Използвайте приложението по време на разговора. diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index 5fd894b226..826ad0171b 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -6558,6 +6558,10 @@ chat item action Sdílet s kontakty No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code No comment provided by engineer. @@ -6650,6 +6654,10 @@ chat item action SimpleX address or 1-time link? No comment provided by engineer. + + SimpleX channel link + simplex link type + SimpleX contact address SimpleX kontaktní adresa @@ -7187,6 +7195,10 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován This is your own one-time link! No comment provided by engineer. + + This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + No comment provided by engineer. + This link was used with another mobile device, please create a new link on the desktop. No comment provided by engineer. @@ -7460,6 +7472,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Nepřečtený swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. No comment provided by engineer. @@ -7602,6 +7618,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Use servers No comment provided by engineer. + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 1603a67df1..facf6d5a9e 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -2920,6 +2920,7 @@ chat item action Enable Flux in Network & servers settings for better metadata privacy. + Für einen besseren Metadatenschutz Flux in den Netzwerk- und Servereinstellungen aktivieren. No comment provided by engineer. @@ -4941,12 +4942,12 @@ Das ist Ihr Link für die Gruppe %@! Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. - Nachrichten, Dateien und Anrufe sind durch **Ende-zu-Ende-Verschlüsselung** mit Perfect Forward Secrecy, Ablehnung und Einbruchs-Wiederherstellung geschützt. + Nachrichten, Dateien und Anrufe sind durch **Ende-zu-Ende-Verschlüsselung** mit Perfect Forward Secrecy, Abstreitbarkeit und Wiederherstellung nach einer Kompromittierung geschützt. No comment provided by engineer. Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. - Nachrichten, Dateien und Anrufe sind durch **Quantum-resistente E2E-Verschlüsselung** mit Perfect Forward Secrecy, Ablehnung und Einbruchs-Wiederherstellung geschützt. + Nachrichten, Dateien und Anrufe sind durch **Quantum-resistente E2E-Verschlüsselung** mit Perfect Forward Secrecy, Abstreitbarkeit und Wiederherstellung nach einer Kompromittierung geschützt. No comment provided by engineer. @@ -5311,7 +5312,7 @@ Das ist Ihr Link für die Gruppe %@! No received or sent files - Keine herunter- oder hochgeladene Dateien + Keine herunter- oder hochgeladenen Dateien No comment provided by engineer. @@ -5900,7 +5901,7 @@ Fehler: %@ Privacy policy and conditions of use. - Datenschutzbestimmungen und Nutzungsbedingungen. + Datenschutz- und Nutzungsbedingungen. No comment provided by engineer. @@ -7198,6 +7199,10 @@ chat item action Mit Kontakten teilen No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code QR-Code anzeigen @@ -7298,6 +7303,10 @@ chat item action SimpleX-Adresse oder Einmal-Link? No comment provided by engineer. + + SimpleX channel link + simplex link type + SimpleX contact address SimpleX-Kontaktadressen-Link @@ -7830,7 +7839,7 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain. - Es werden alle herunter- und hochgeladene Dateien und Medien gelöscht. Bilder mit niedriger Auflösung bleiben erhalten. Diese Aktion kann nicht rückgängig gemacht werden! + Es werden alle herunter- und hochgeladenen Dateien und Medien gelöscht. Bilder mit niedriger Auflösung bleiben erhalten. Diese Aktion kann nicht rückgängig gemacht werden! No comment provided by engineer. @@ -7888,6 +7897,10 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Das ist Ihr eigener Einmal-Link! No comment provided by engineer. + + This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + No comment provided by engineer. + This link was used with another mobile device, please create a new link on the desktop. Dieser Link wurde schon mit einem anderen Mobiltelefon genutzt. Bitte erstellen sie einen neuen Link in der Desktop-App. @@ -8187,6 +8200,10 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Ungelesen swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. Bis zu 100 der letzten Nachrichten werden an neue Mitglieder gesendet. @@ -8347,6 +8364,10 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Verwende Server No comment provided by engineer. + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. Die App kann während eines Anrufs genutzt werden. diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 8d040ff99a..9eb60c2cbc 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -7199,6 +7199,11 @@ chat item action Share with contacts No comment provided by engineer. + + Short link + Short link + No comment provided by engineer. + Show QR code Show QR code @@ -7299,6 +7304,11 @@ chat item action SimpleX address or 1-time link? No comment provided by engineer. + + SimpleX channel link + SimpleX channel link + simplex link type + SimpleX contact address SimpleX contact address @@ -7889,6 +7899,11 @@ It can happen because of some bug or when the connection is compromised.This is your own one-time link! No comment provided by engineer. + + This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + No comment provided by engineer. + This link was used with another mobile device, please create a new link on the desktop. This link was used with another mobile device, please create a new link on the desktop. @@ -8188,6 +8203,11 @@ To connect, please ask your contact to create another connection link and check Unread swipe action + + Unsupported connection link + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. Up to 100 last messages are sent to new members. @@ -8348,6 +8368,11 @@ To connect, please ask your contact to create another connection link and check Use servers No comment provided by engineer. + + Use short links (BETA) + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. Use the app while in the call. diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 4f33d923af..5250aa0de7 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -466,7 +466,7 @@ time interval 1 year - Un año + 1 año delete after time @@ -873,7 +873,7 @@ swipe action Allow to report messsages to moderators. - Se permite informar de mensajes a los moderadores. + Permitir informar de mensajes a los moderadores. No comment provided by engineer. @@ -2920,6 +2920,7 @@ chat item action Enable Flux in Network & servers settings for better metadata privacy. + Habilitar Flux en la configuración de Red y servidores para mejorar la privacidad de los metadatos. No comment provided by engineer. @@ -7198,6 +7199,10 @@ chat item action Compartir con contactos No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code Mostrar código QR @@ -7298,6 +7303,10 @@ chat item action ¿Dirección SimpleX o enlace de un uso? No comment provided by engineer. + + SimpleX channel link + simplex link type + SimpleX contact address Dirección de contacto SimpleX @@ -7888,6 +7897,10 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. ¡Este es tu propio enlace de un solo uso! No comment provided by engineer. + + This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + No comment provided by engineer. + This link was used with another mobile device, please create a new link on the desktop. Este enlace ha sido usado en otro dispositivo móvil, por favor crea un enlace nuevo en el ordenador. @@ -8187,6 +8200,10 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión No leído swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. Hasta 100 últimos mensajes son enviados a los miembros nuevos. @@ -8347,6 +8364,10 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Usar servidores No comment provided by engineer. + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. Usar la aplicación durante la llamada. diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index c5393e97a1..8dc4250e20 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -6537,6 +6537,10 @@ chat item action Jaa kontaktien kanssa No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code No comment provided by engineer. @@ -6629,6 +6633,10 @@ chat item action SimpleX address or 1-time link? No comment provided by engineer. + + SimpleX channel link + simplex link type + SimpleX contact address SimpleX-yhteystiedot @@ -7165,6 +7173,10 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.
    This is your own one-time link! No comment provided by engineer. + + This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + No comment provided by engineer. + This link was used with another mobile device, please create a new link on the desktop. No comment provided by engineer. @@ -7437,6 +7449,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Lukematon swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. No comment provided by engineer. @@ -7579,6 +7595,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Use servers No comment provided by engineer. + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 823491dc72..304b9e2084 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -192,6 +192,7 @@ %d seconds(s) + %d seconde(s) delete after time @@ -465,6 +466,7 @@ time interval 1 year + 1 an delete after time @@ -601,6 +603,7 @@ swipe action Active + Actif token status text @@ -620,6 +623,7 @@ swipe action Add list + Ajouter une liste No comment provided by engineer. @@ -649,6 +653,7 @@ swipe action Add to list + Ajouter à la liste No comment provided by engineer. @@ -728,6 +733,7 @@ swipe action All + Tout No comment provided by engineer. @@ -742,6 +748,7 @@ swipe action All chats will be removed from the list %@, and the list deleted. + Tous les chats seront supprimés de la liste %@, et la liste sera supprimée. alert message @@ -786,6 +793,7 @@ swipe action All reports will be archived for you. + Tous les rapports seront archivés pour vous. No comment provided by engineer. @@ -865,6 +873,7 @@ swipe action Allow to report messsages to moderators. + Permettre de signaler des messages aux modérateurs. No comment provided by engineer. @@ -949,6 +958,7 @@ swipe action Another reason + Autre raison report reason @@ -1027,14 +1037,17 @@ swipe action Archive + Archiver No comment provided by engineer. Archive %lld reports? + Archiver les rapports %lld ? No comment provided by engineer. Archive all reports? + Archiver tous les rapports ? No comment provided by engineer. @@ -1049,14 +1062,17 @@ swipe action Archive report + Archiver le rapport No comment provided by engineer. Archive report? + Archiver le rapport ? No comment provided by engineer. Archive reports + Archiver les rapports swipe action @@ -1071,6 +1087,7 @@ swipe action Ask + Demander No comment provided by engineer. @@ -1175,6 +1192,7 @@ swipe action Better groups performance + Meilleure performance des groupes No comment provided by engineer. @@ -1199,6 +1217,7 @@ swipe action Better privacy and security + Meilleure protection de la privacité et de la sécurité No comment provided by engineer. @@ -1303,6 +1322,7 @@ swipe action Businesses + Entreprises No comment provided by engineer. @@ -1314,6 +1334,9 @@ swipe action By using SimpleX Chat you agree to: - send only legal content in public groups. - respect other users – no spam. + En utilisant SimpleX Chat, vous acceptez de : +- n'envoyer que du contenu légal dans les groupes publics. +- respecter les autres utilisateurs - pas de spam. No comment provided by engineer. @@ -1404,6 +1427,7 @@ alert button Change automatic message deletion? + Modifier la suppression automatique des messages ? alert title @@ -1629,10 +1653,12 @@ set passcode view Clear group? + Vider le groupe ? No comment provided by engineer. Clear or delete group? + Vider ou supprimer le groupe ? No comment provided by engineer. @@ -1657,6 +1683,7 @@ set passcode view Community guidelines violation + Infraction aux règles communautaires report reason @@ -1716,6 +1743,7 @@ set passcode view Configure server operators + Configurer les opérateurs de serveur No comment provided by engineer. @@ -1770,6 +1798,7 @@ set passcode view Confirmed + Confirmé token status text @@ -1893,6 +1922,7 @@ Il s'agit de votre propre lien unique ! Connection blocked + Connexion bloquée No comment provided by engineer. @@ -1908,10 +1938,13 @@ Il s'agit de votre propre lien unique ! Connection is blocked by server operator: %@ + La connexion est bloquée par l'opérateur du serveur : +%@ No comment provided by engineer. Connection not ready. + La connexion n'est pas prête. No comment provided by engineer. @@ -1926,6 +1959,7 @@ Il s'agit de votre propre lien unique ! Connection requires encryption renegotiation. + La connexion nécessite une renégociation du cryptage. No comment provided by engineer. @@ -2010,6 +2044,7 @@ Il s'agit de votre propre lien unique ! Content violates conditions of use + Le contenu enfreint les conditions d'utilisation blocking reason @@ -2089,6 +2124,7 @@ Il s'agit de votre propre lien unique ! Create list + Créer une liste No comment provided by engineer. @@ -2352,6 +2388,7 @@ swipe action Delete chat messages from your device. + Supprimer les messages de chat de votre appareil. No comment provided by engineer. @@ -2446,6 +2483,7 @@ swipe action Delete list? + Supprimer la liste ? alert title @@ -2500,6 +2538,7 @@ swipe action Delete report + Supprimer le rapport No comment provided by engineer. @@ -2664,10 +2703,12 @@ swipe action Disable automatic message deletion? + Désactiver la suppression automatique des messages ? alert title Disable delete messages + Désactiver la suppression des messages alert button @@ -2762,6 +2803,7 @@ swipe action Documents: + Documents: No comment provided by engineer. @@ -2776,6 +2818,7 @@ swipe action Don't miss important messages. + Ne manquez pas les messages importants. No comment provided by engineer. @@ -2785,6 +2828,7 @@ swipe action Done + Terminé No comment provided by engineer. @@ -2875,6 +2919,7 @@ chat item action Enable Flux in Network & servers settings for better metadata privacy. + Activez Flux dans les paramètres du réseau et des serveurs pour une meilleure confidentialité des métadonnées. No comment provided by engineer. @@ -3019,6 +3064,7 @@ chat item action Encryption renegotiation in progress. + Renégociation du chiffrement en cours. No comment provided by engineer. @@ -3133,6 +3179,7 @@ chat item action Error checking token status + Erreur lors de la vérification de l'état du jeton (token) No comment provided by engineer. @@ -3157,6 +3204,7 @@ chat item action Error creating list + Erreur lors de la création de la liste alert title @@ -3176,6 +3224,7 @@ chat item action Error creating report + Erreur lors de la création du rapport No comment provided by engineer. @@ -3290,6 +3339,7 @@ chat item action Error registering for notifications + Erreur lors de l'inscription aux notifications alert title @@ -3299,6 +3349,7 @@ chat item action Error reordering lists + Erreur lors de la réorganisation des listes alert title @@ -3313,6 +3364,7 @@ chat item action Error saving chat list + Erreur lors de l'enregistrement de la liste des chats alert title @@ -3397,6 +3449,7 @@ chat item action Error testing server connection + Erreur lors du test de connexion au serveur No comment provided by engineer. @@ -3483,6 +3536,7 @@ snd error text Expired + Expiré token status text @@ -3527,6 +3581,7 @@ snd error text Faster deletion of groups. + Suppression plus rapide des groupes. No comment provided by engineer. @@ -3536,6 +3591,7 @@ snd error text Faster sending messages. + Envoi plus rapide des messages. No comment provided by engineer. @@ -3545,6 +3601,7 @@ snd error text Favorites + Favoris No comment provided by engineer. @@ -3562,6 +3619,8 @@ snd error text File is blocked by server operator: %@. + Le fichier est bloqué par l'opérateur du serveur : +%@. file error text @@ -7077,6 +7136,10 @@ chat item action Partager avec vos contacts No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code Afficher le code QR @@ -7177,6 +7240,10 @@ chat item action Adresse SimpleX ou lien unique ? No comment provided by engineer. + + SimpleX channel link + simplex link type + SimpleX contact address Adresse de contact SimpleX @@ -7762,6 +7829,10 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Voici votre propre lien unique ! No comment provided by engineer. + + This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + No comment provided by engineer. + This link was used with another mobile device, please create a new link on the desktop. Ce lien a été utilisé avec un autre appareil mobile, veuillez créer un nouveau lien sur le bureau. @@ -8059,6 +8130,10 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Non lu swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. Les 100 derniers messages sont envoyés aux nouveaux membres. @@ -8217,6 +8292,10 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Utiliser les serveurs No comment provided by engineer. + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. Utiliser l'application pendant l'appel. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 76157f29ac..f24ba2b781 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -613,7 +613,7 @@ swipe action Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. - Cím hozzáadása a profilhoz, hogy a partnerei megoszthassák másokkal. A profilfrissítés el lesz küldve az Ön partnerei számára. + Cím hozzáadása a profilhoz, hogy a partnerei megoszthassák másokkal. A profilfrissítés el lesz küldve partnerei számára. No comment provided by engineer. @@ -758,7 +758,7 @@ swipe action All data is kept private on your device. - Az összes adat privát módon van tárolva az Ön eszközén. + Az összes adat privát módon van tárolva az eszközén. No comment provided by engineer. @@ -803,7 +803,7 @@ swipe action All your contacts will remain connected. Profile update will be sent to your contacts. - A partnereivel kapcsolatban marad. A profilfrissítés el lesz küldve az Ön partnerei számára. + A partnereivel kapcsolatban marad. A profilfrissítés el lesz küldve a partnerei számára. No comment provided by engineer. @@ -1836,14 +1836,14 @@ set passcode view Connect to yourself? This is your own SimpleX address! Kapcsolódik saját magához? -Ez az Ön SimpleX-címe! +Ez a saját SimpleX-címe! No comment provided by engineer. Connect to yourself? This is your own one-time link! Kapcsolódik saját magához? -Ez az Ön egyszer használható meghívója! +Ez a saját egyszer használható meghívója! No comment provided by engineer. @@ -2389,7 +2389,7 @@ swipe action Delete chat messages from your device. - Csevegési üzenetek törlése az Ön eszközéről. + Csevegési üzenetek törlése a saját eszközéről. No comment provided by engineer. @@ -2454,7 +2454,7 @@ swipe action Delete for me - Csak számomra + Csak nálam No comment provided by engineer. @@ -2774,7 +2774,7 @@ swipe action Do NOT send messages directly, even if your or destination server does not support private routing. - NE küldjön üzeneteket közvetlenül, még akkor sem, ha az Ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. + NE küldjön üzeneteket közvetlenül, még akkor sem, ha a saját kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. No comment provided by engineer. @@ -2885,7 +2885,7 @@ chat item action Duplicate display name! - Duplikált megjelenített név! + Duplikált megjelenítendő név! No comment provided by engineer. @@ -2920,6 +2920,7 @@ chat item action Enable Flux in Network & servers settings for better metadata privacy. + A Flux kiszolgálókat engedélyezheti a beállításokban, a „Hálózat és kiszolgálók” menüben, a metaadatok jobb védelme érdekében. No comment provided by engineer. @@ -3765,7 +3766,7 @@ snd error text For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. - Például, ha az Ön partnere egy SimpleX Chat-kiszolgálón keresztül fogadja az üzeneteket, az Ön alkalmazása egy Flux-kiszolgálón keresztül fogja azokat kézbesíteni. + Például, ha a partnere egy SimpleX Chat-kiszolgálón keresztül fogadja az üzeneteket, akkor az Ön alkalmazása egy Flux-kiszolgálón keresztül fogja azokat kézbesíteni. No comment provided by engineer. @@ -4516,7 +4517,7 @@ További fejlesztések hamarosan! Join your group? This is your link for group %@! Csatlakozik a csoportjához? -Ez az Ön hivatkozása a(z) %@ nevű csoporthoz! +Ez a saját hivatkozása a(z) %@ nevű csoporthoz! No comment provided by engineer. @@ -4641,7 +4642,7 @@ Ez az Ön hivatkozása a(z) %@ nevű csoporthoz! List name... - Listanév… + Lista neve… No comment provided by engineer. @@ -5161,7 +5162,7 @@ Ez az Ön hivatkozása a(z) %@ nevű csoporthoz! New display name - Új megjelenítési név + Új megjelenítendő név No comment provided by engineer. @@ -5895,7 +5896,7 @@ Hiba: %@ Privacy for your customers. - Az Ön ügyfeleinek adatvédelme. + Saját ügyfeleinek adatvédelme. No comment provided by engineer. @@ -5975,7 +5976,7 @@ Hiba: %@ Profile update will be sent to your contacts. - A profilfrissítés el lesz küldve az Ön partnerei számára. + A profilfrissítés el lesz küldve a partnerei számára. alert message @@ -6684,7 +6685,7 @@ chat item action Scan QR code from desktop - QR-kód beolvasása számítógépről + QR-kód beolvasása a számítógépről No comment provided by engineer. @@ -6829,12 +6830,12 @@ chat item action Send messages directly when IP address is protected and your or destination server does not support private routing. - Közvetlen üzenetküldés, ha az IP-cím védett és az Ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. + Közvetlen üzenetküldés, ha az IP-cím védett és a saját kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. No comment provided by engineer. Send messages directly when your or destination server does not support private routing. - Közvetlen üzenetküldés, ha az Ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. + Közvetlen üzenetküldés, ha a saját kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. No comment provided by engineer. @@ -7198,6 +7199,10 @@ chat item action Megosztás a partnerekkel No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code QR-kód megjelenítése @@ -7298,6 +7303,10 @@ chat item action SimpleX-cím vagy egyszer használható meghívó? No comment provided by engineer. + + SimpleX channel link + simplex link type + SimpleX contact address SimpleX kapcsolattartási cím @@ -7548,12 +7557,12 @@ report reason TCP connection - TCP kapcsolat + TCP-kapcsolat No comment provided by engineer. TCP connection timeout - TCP kapcsolat időtúllépése + TCP-kapcsolat időtúllépése No comment provided by engineer. @@ -7705,7 +7714,7 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. The connection reached the limit of undelivered messages, your contact may be offline. - A kapcsolat elérte a kézbesítetlen üzenetek számának határát, az Ön partnere lehet, hogy offline állapotban van. + A kapcsolat elérte a kézbesítetlen üzenetek számának határát, a partnere lehet, hogy offline állapotban van. No comment provided by engineer. @@ -7880,12 +7889,16 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. This is your own SimpleX address! - Ez az Ön SimpleX-címe! + Ez a saját SimpleX-címe! No comment provided by engineer. This is your own one-time link! - Ez az Ön egyszer használható meghívója! + Ez a saját egyszer használható meghívója! + No comment provided by engineer. + + + This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. No comment provided by engineer. @@ -7952,7 +7965,7 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll To protect your privacy, SimpleX uses separate IDs for each of your contacts. - Az Ön adatainak védelme érdekében a SimpleX külön üzenet-azonosítókat használ minden egyes kapcsolatához. + Adatainak védelme érdekében a SimpleX külön üzenet-azonosítókat használ minden egyes kapcsolatához. No comment provided by engineer. @@ -8187,6 +8200,10 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Olvasatlan swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. Legfeljebb az utolsó 100 üzenet lesz elküldve az új tagok számára. @@ -8347,6 +8364,10 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Kiszolgálók használata No comment provided by engineer. + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. Használja az alkalmazást hívás közben. @@ -8664,7 +8685,7 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso You already have a chat profile with the same display name. Please choose another name. - Már van egy csevegési profil ugyanezzel a megjelenített névvel. Válasszon egy másik nevet. + Már van egy csevegési profil ugyanezzel a megjelenítendő névvel. Válasszon egy másik nevet. No comment provided by engineer. @@ -8968,7 +8989,7 @@ Megismétli a meghívási kérést? You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile - Egy olyan partnerét próbálja meghívni, akivel inkognitóprofilt osztott meg abban a csoportban, amelyben a saját fő profilja van használatban + Egy olyan partnerét próbálja meghívni, akivel inkognitóprofilt osztott meg abban a csoportban, amelyben a fő profilja van használatban No comment provided by engineer. @@ -9073,12 +9094,12 @@ Megismétli a meghívási kérést? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. - A profilja módosult. Ha elmenti, a profilfrissítés el lesz küldve az Ön partnerei számára. + A profilja módosult. Ha elmenti, a profilfrissítés el lesz küldve a partnerei számára. alert message Your profile, contacts and delivered messages are stored on your device. - A profilja, a partnerei és az elküldött üzenetei az Ön eszközén vannak tárolva. + A profilja, a partnerei és az elküldött üzenetei a saját eszközén vannak tárolva. No comment provided by engineer. @@ -9620,7 +9641,7 @@ pref value invited via your group link - meghíva az Ön csoporthivatkozásán keresztül + meghíva a saját csoporthivatkozásán keresztül rcv group event chat item diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 9d6aa9f4be..353d420e27 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -1218,7 +1218,7 @@ swipe action Better privacy and security - Privacy e sicurezza migliorate + Privacy e sicurezza migliori No comment provided by engineer. @@ -2920,6 +2920,7 @@ chat item action Enable Flux in Network & servers settings for better metadata privacy. + Attiva Flux nelle impostazioni "Rete e server" per una migliore privacy dei metadati. No comment provided by engineer. @@ -7198,6 +7199,10 @@ chat item action Condividi con i contatti No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code Mostra codice QR @@ -7298,6 +7303,10 @@ chat item action Indirizzo SimpleX o link una tantum? No comment provided by engineer. + + SimpleX channel link + simplex link type + SimpleX contact address Indirizzo di contatto SimpleX @@ -7888,6 +7897,10 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Questo è il tuo link una tantum! No comment provided by engineer. + + This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + No comment provided by engineer. + This link was used with another mobile device, please create a new link on the desktop. Questo link è stato usato con un altro dispositivo mobile, creane uno nuovo sul desktop. @@ -8187,6 +8200,10 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Non letto swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. Vengono inviati ai nuovi membri fino a 100 ultimi messaggi. @@ -8347,6 +8364,10 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Usa i server No comment provided by engineer. + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. Usa l'app mentre sei in chiamata. diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 8e7c0ae206..34856e2e2b 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -6607,6 +6607,10 @@ chat item action 連絡先と共有する No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code No comment provided by engineer. @@ -6699,6 +6703,10 @@ chat item action SimpleX address or 1-time link? No comment provided by engineer. + + SimpleX channel link + simplex link type + SimpleX contact address SimpleX連絡先アドレス @@ -7235,6 +7243,10 @@ It can happen because of some bug or when the connection is compromised.This is your own one-time link! No comment provided by engineer. + + This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + No comment provided by engineer. + This link was used with another mobile device, please create a new link on the desktop. No comment provided by engineer. @@ -7507,6 +7519,10 @@ To connect, please ask your contact to create another connection link and check 未読 swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. No comment provided by engineer. @@ -7649,6 +7665,10 @@ To connect, please ask your contact to create another connection link and check Use servers No comment provided by engineer. + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 2ab0c75ff1..4d9df505db 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -1335,6 +1335,9 @@ swipe action By using SimpleX Chat you agree to: - send only legal content in public groups. - respect other users – no spam. + Door SimpleX Chat te gebruiken, gaat u ermee akkoord: +- alleen legale content te versturen in openbare groepen. +- andere gebruikers te respecteren – geen spam. No comment provided by engineer. @@ -1741,6 +1744,7 @@ set passcode view Configure server operators + Serveroperators configureren No comment provided by engineer. @@ -2916,6 +2920,7 @@ chat item action Enable Flux in Network & servers settings for better metadata privacy. + Schakel Flux in bij Netwerk- en serverinstellingen voor betere privacy van metagegevens. No comment provided by engineer. @@ -5896,6 +5901,7 @@ Fout: %@ Privacy policy and conditions of use. + Privacybeleid en gebruiksvoorwaarden. No comment provided by engineer. @@ -5905,6 +5911,7 @@ Fout: %@ Private chats, groups and your contacts are not accessible to server operators. + Privéchats, groepen en uw contacten zijn niet toegankelijk voor serverbeheerders. No comment provided by engineer. @@ -7192,6 +7199,10 @@ chat item action Delen met contacten No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code Toon QR-code @@ -7292,6 +7303,10 @@ chat item action SimpleX adres of eenmalige link? No comment provided by engineer. + + SimpleX channel link + simplex link type + SimpleX contact address SimpleX contact adres @@ -7882,6 +7897,10 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. Dit is uw eigen eenmalige link! No comment provided by engineer. + + This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + No comment provided by engineer. + This link was used with another mobile device, please create a new link on the desktop. Deze link is gebruikt met een ander mobiel apparaat. Maak een nieuwe link op de desktop. @@ -8181,6 +8200,10 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Ongelezen swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. Er worden maximaal 100 laatste berichten naar nieuwe leden verzonden. @@ -8341,6 +8364,10 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Gebruik servers No comment provided by engineer. + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. Gebruik de app tijdens het gesprek. diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index dce57ccc9e..0a548dc227 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -7034,6 +7034,10 @@ chat item action Udostępnij kontaktom No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code Pokaż kod QR @@ -7131,6 +7135,10 @@ chat item action SimpleX address or 1-time link? No comment provided by engineer. + + SimpleX channel link + simplex link type + SimpleX contact address Adres kontaktowy SimpleX @@ -7704,6 +7712,10 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom To jest twój jednorazowy link! No comment provided by engineer. + + This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + No comment provided by engineer. + This link was used with another mobile device, please create a new link on the desktop. Ten link dostał użyty z innym urządzeniem mobilnym, proszę stworzyć nowy link na komputerze. @@ -7996,6 +8008,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Nieprzeczytane swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. Do nowych członków wysyłanych jest do 100 ostatnich wiadomości. @@ -8150,6 +8166,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Use servers No comment provided by engineer. + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. Używaj aplikacji podczas połączenia. diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 09e0400ec9..61495069c7 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -127,10 +127,12 @@ %@, %@ and %lld members + %@, %@ и %lld членов группы No comment provided by engineer. %@, %@ and %lld other members connected + установлено соединение с %@, %@ и %lld другими членами группы No comment provided by engineer. @@ -230,6 +232,7 @@ %lld members + %lld членов No comment provided by engineer. @@ -521,6 +524,8 @@ time interval A separate TCP connection will be used **for each contact and group member**. **Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail. + Будет использовано отдельное TCP соединение **для каждого контакта и члена группы**. +**Примечание**: Чем больше подключений, тем быстрее разряжается батарея и расходуется трафик, а некоторые соединения могут отваливаться. No comment provided by engineer. @@ -708,6 +713,7 @@ swipe action Admins can block a member for all. + Админы могут заблокировать члена группы. No comment provided by engineer. @@ -757,6 +763,7 @@ swipe action All group members will remain connected. + Все члены группы останутся соединены. No comment provided by engineer. @@ -846,6 +853,7 @@ swipe action Allow sending direct messages to members. + Разрешить личные сообщения членам группы. No comment provided by engineer. @@ -1240,18 +1248,22 @@ swipe action Block group members + Заблокировать членов группы No comment provided by engineer. Block member + Заблокировать члена группы No comment provided by engineer. Block member for all? + Заблокировать для всех? No comment provided by engineer. Block member? + Заблокировать члена группы? No comment provided by engineer. @@ -1355,6 +1367,7 @@ swipe action Can't call member + Не удаётся позвонить члену группы No comment provided by engineer. @@ -1369,6 +1382,7 @@ swipe action Can't message member + Не удаётся отправить сообщение члену группы No comment provided by engineer. @@ -1434,6 +1448,7 @@ alert button Change member role? + Поменять роль члена группы? No comment provided by engineer. @@ -2334,6 +2349,7 @@ swipe action Delete %lld messages of members? + Удалить %lld сообщений членов группы? No comment provided by engineer. @@ -2668,6 +2684,7 @@ swipe action Direct messages between members are prohibited in this chat. + Личные сообщения запрещены в этой группе. No comment provided by engineer. @@ -7141,6 +7158,10 @@ chat item action Поделиться с контактами No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code Показать QR код @@ -7241,6 +7262,10 @@ chat item action Адрес SimpleX или одноразовая ссылка? No comment provided by engineer. + + SimpleX channel link + simplex link type + SimpleX contact address SimpleX ссылка-контакт @@ -7826,6 +7851,10 @@ It can happen because of some bug or when the connection is compromised.Это ваша собственная одноразовая ссылка! No comment provided by engineer. + + This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + No comment provided by engineer. + This link was used with another mobile device, please create a new link on the desktop. Эта ссылка была использована на другом мобильном, пожалуйста, создайте новую ссылку на компьютере. @@ -8122,6 +8151,10 @@ To connect, please ask your contact to create another connection link and check Не прочитано swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. No comment provided by engineer. @@ -8281,6 +8314,10 @@ To connect, please ask your contact to create another connection link and check Использовать серверы No comment provided by engineer. + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. Используйте приложение во время звонка. diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index a3162e0bec..7431c13969 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -6512,6 +6512,10 @@ chat item action แชร์กับผู้ติดต่อ No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code No comment provided by engineer. @@ -6603,6 +6607,10 @@ chat item action SimpleX address or 1-time link? No comment provided by engineer. + + SimpleX channel link + simplex link type + SimpleX contact address ที่อยู่ติดต่อ SimpleX @@ -7137,6 +7145,10 @@ It can happen because of some bug or when the connection is compromised.This is your own one-time link! No comment provided by engineer. + + This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + No comment provided by engineer. + This link was used with another mobile device, please create a new link on the desktop. No comment provided by engineer. @@ -7409,6 +7421,10 @@ To connect, please ask your contact to create another connection link and check เปลี่ยนเป็นยังไม่ได้อ่าน swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. No comment provided by engineer. @@ -7549,6 +7565,10 @@ To connect, please ask your contact to create another connection link and check Use servers No comment provided by engineer. + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index bce3cdde08..935c4885b5 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -7046,6 +7046,10 @@ chat item action Kişilerle paylaş No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code QR kodunu göster @@ -7143,6 +7147,10 @@ chat item action SimpleX address or 1-time link? No comment provided by engineer. + + SimpleX channel link + simplex link type + SimpleX contact address SimpleX kişi adresi @@ -7719,6 +7727,10 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Bu senin kendi tek kullanımlık bağlantın! No comment provided by engineer. + + This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + No comment provided by engineer. + This link was used with another mobile device, please create a new link on the desktop. Bu bağlantı başka bir mobil cihazda kullanıldı, lütfen masaüstünde yeni bir bağlantı oluşturun. @@ -8011,6 +8023,10 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Okunmamış swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. Yeni üyelere 100e kadar en son mesajlar gönderildi. @@ -8165,6 +8181,10 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Use servers No comment provided by engineer. + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. Görüşme sırasında uygulamayı kullanın. diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index c95be26929..9ea65c4b11 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -7077,6 +7077,10 @@ chat item action Поділіться з контактами No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code Показати QR-код @@ -7177,6 +7181,10 @@ chat item action SimpleX адреса або одноразове посилання? No comment provided by engineer. + + SimpleX channel link + simplex link type + SimpleX contact address Контактна адреса SimpleX @@ -7762,6 +7770,10 @@ It can happen because of some bug or when the connection is compromised.Це ваше власне одноразове посилання! No comment provided by engineer. + + This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + No comment provided by engineer. + This link was used with another mobile device, please create a new link on the desktop. Це посилання було використано з іншого мобільного пристрою, будь ласка, створіть нове посилання на робочому столі. @@ -8059,6 +8071,10 @@ To connect, please ask your contact to create another connection link and check Непрочитане swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. Новим користувачам надсилається до 100 останніх повідомлень. @@ -8217,6 +8233,10 @@ To connect, please ask your contact to create another connection link and check Використовуйте сервери No comment provided by engineer. + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. Використовуйте додаток під час розмови. diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index 1e4c1a72f6..30a6567414 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -6946,6 +6946,10 @@ chat item action 与联系人分享 No comment provided by engineer. + + Short link + No comment provided by engineer. + Show QR code 显示二维码 @@ -7043,6 +7047,10 @@ chat item action SimpleX address or 1-time link? No comment provided by engineer. + + SimpleX channel link + simplex link type + SimpleX contact address SimpleX 联系地址 @@ -7613,6 +7621,10 @@ It can happen because of some bug or when the connection is compromised.这是你自己的一次性链接! No comment provided by engineer. + + This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + No comment provided by engineer. + This link was used with another mobile device, please create a new link on the desktop. 此链接已在其他移动设备上使用,请在桌面上创建新链接。 @@ -7903,6 +7915,10 @@ To connect, please ask your contact to create another connection link and check 未读 swipe action + + Unsupported connection link + No comment provided by engineer. + Up to 100 last messages are sent to new members. 给新成员发送了最多 100 条历史消息。 @@ -8056,6 +8072,10 @@ To connect, please ask your contact to create another connection link and check Use servers No comment provided by engineer. + + Use short links (BETA) + No comment provided by engineer. + Use the app while in the call. 通话时使用本应用. diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index d92ad24117..ef03de17f0 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -1945,6 +1945,9 @@ chat item action */ /* No comment provided by engineer. */ "Enable camera access" = "Kamera-Zugriff aktivieren"; +/* No comment provided by engineer. */ +"Enable Flux in Network & servers settings for better metadata privacy." = "Für einen besseren Metadatenschutz Flux in den Netzwerk- und Servereinstellungen aktivieren."; + /* No comment provided by engineer. */ "Enable for all" = "Für Alle aktivieren"; @@ -3271,10 +3274,10 @@ snd error text */ "Messages were deleted after you selected them." = "Die Nachrichten wurden gelöscht, nachdem Sie sie ausgewählt hatten."; /* No comment provided by engineer. */ -"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Nachrichten, Dateien und Anrufe sind durch **Ende-zu-Ende-Verschlüsselung** mit Perfect Forward Secrecy, Ablehnung und Einbruchs-Wiederherstellung geschützt."; +"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Nachrichten, Dateien und Anrufe sind durch **Ende-zu-Ende-Verschlüsselung** mit Perfect Forward Secrecy, Abstreitbarkeit und Wiederherstellung nach einer Kompromittierung geschützt."; /* No comment provided by engineer. */ -"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Nachrichten, Dateien und Anrufe sind durch **Quantum-resistente E2E-Verschlüsselung** mit Perfect Forward Secrecy, Ablehnung und Einbruchs-Wiederherstellung geschützt."; +"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Nachrichten, Dateien und Anrufe sind durch **Quantum-resistente E2E-Verschlüsselung** mit Perfect Forward Secrecy, Abstreitbarkeit und Wiederherstellung nach einer Kompromittierung geschützt."; /* No comment provided by engineer. */ "Migrate device" = "Gerät migrieren"; @@ -3523,7 +3526,7 @@ snd error text */ "No push server" = "Lokal"; /* No comment provided by engineer. */ -"No received or sent files" = "Keine herunter- oder hochgeladene Dateien"; +"No received or sent files" = "Keine herunter- oder hochgeladenen Dateien"; /* servers error */ "No servers for private message routing." = "Keine Server für privates Nachrichten-Routing."; @@ -3909,7 +3912,7 @@ time to disappear */ "Privacy for your customers." = "Schutz der Privatsphäre Ihrer Kunden."; /* No comment provided by engineer. */ -"Privacy policy and conditions of use." = "Datenschutzbestimmungen und Nutzungsbedingungen."; +"Privacy policy and conditions of use." = "Datenschutz- und Nutzungsbedingungen."; /* No comment provided by engineer. */ "Privacy redefined" = "Datenschutz neu definiert"; @@ -5140,7 +5143,7 @@ report reason */ "They can be overridden in contact and group settings." = "Sie können in den Kontakteinstellungen überschrieben werden."; /* No comment provided by engineer. */ -"This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "Es werden alle herunter- und hochgeladene Dateien und Medien gelöscht. Bilder mit niedriger Auflösung bleiben erhalten. Diese Aktion kann nicht rückgängig gemacht werden!"; +"This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "Es werden alle herunter- und hochgeladenen Dateien und Medien gelöscht. Bilder mit niedriger Auflösung bleiben erhalten. Diese Aktion kann nicht rückgängig gemacht werden!"; /* No comment provided by engineer. */ "This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "Es werden alle empfangenen und gesendeten Nachrichten, die über den ausgewählten Zeitraum hinaus gehen, gelöscht. Dieser Vorgang kann mehrere Minuten dauern. Diese Aktion kann nicht rückgängig gemacht werden!"; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index b6580836e7..f9fe85dba7 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -287,7 +287,7 @@ time interval */ "1 week" = "una semana"; /* delete after time */ -"1 year" = "Un año"; +"1 year" = "1 año"; /* No comment provided by engineer. */ "1-time link" = "Enlace de un uso"; @@ -551,7 +551,7 @@ swipe action */ "Allow to irreversibly delete sent messages. (24 hours)" = "Se permite la eliminación irreversible de mensajes. (24 horas)"; /* No comment provided by engineer. */ -"Allow to report messsages to moderators." = "Se permite informar de mensajes a los moderadores."; +"Allow to report messsages to moderators." = "Permitir informar de mensajes a los moderadores."; /* No comment provided by engineer. */ "Allow to send files and media." = "Se permite enviar archivos y multimedia."; @@ -1945,6 +1945,9 @@ chat item action */ /* No comment provided by engineer. */ "Enable camera access" = "Permitir acceso a la cámara"; +/* No comment provided by engineer. */ +"Enable Flux in Network & servers settings for better metadata privacy." = "Habilitar Flux en la configuración de Red y servidores para mejorar la privacidad de los metadatos."; + /* No comment provided by engineer. */ "Enable for all" = "Activar para todos"; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index 9990bd4a3e..a0145a7f47 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -178,6 +178,9 @@ /* time interval */ "%d sec" = "%d sec"; +/* delete after time */ +"%d seconds(s)" = "%d seconde(s)"; + /* integrity error chat item */ "%d skipped message(s)" = "%d message·s sauté·s"; @@ -283,6 +286,9 @@ time interval */ time interval */ "1 week" = "1 semaine"; +/* delete after time */ +"1 year" = "1 an"; + /* No comment provided by engineer. */ "1-time link" = "Lien unique"; @@ -367,6 +373,9 @@ swipe action */ /* No comment provided by engineer. */ "Acknowledgement errors" = "Erreur d'accusé de réception"; +/* token status text */ +"Active" = "Actif"; + /* No comment provided by engineer. */ "Active connections" = "Connections actives"; @@ -376,6 +385,9 @@ swipe action */ /* No comment provided by engineer. */ "Add friends" = "Ajouter des amis"; +/* No comment provided by engineer. */ +"Add list" = "Ajouter une liste"; + /* No comment provided by engineer. */ "Add profile" = "Ajouter un profil"; @@ -391,6 +403,9 @@ swipe action */ /* No comment provided by engineer. */ "Add to another device" = "Ajouter à un autre appareil"; +/* No comment provided by engineer. */ +"Add to list" = "Ajouter à la liste"; + /* No comment provided by engineer. */ "Add welcome message" = "Ajouter un message d'accueil"; @@ -448,12 +463,18 @@ swipe action */ /* chat item text */ "agreeing encryption…" = "négociation du chiffrement…"; +/* No comment provided by engineer. */ +"All" = "Tout"; + /* No comment provided by engineer. */ "All app data is deleted." = "Toutes les données de l'application sont supprimées."; /* No comment provided by engineer. */ "All chats and messages will be deleted - this cannot be undone!" = "Toutes les discussions et tous les messages seront supprimés - il est impossible de revenir en arrière !"; +/* alert message */ +"All chats will be removed from the list %@, and the list deleted." = "Tous les chats seront supprimés de la liste %@, et la liste sera supprimée."; + /* No comment provided by engineer. */ "All data is erased when it is entered." = "Toutes les données sont effacées lorsqu'il est saisi."; @@ -481,6 +502,9 @@ swipe action */ /* profile dropdown */ "All profiles" = "Tous les profiles"; +/* No comment provided by engineer. */ +"All reports will be archived for you." = "Tous les rapports seront archivés pour vous."; + /* No comment provided by engineer. */ "All your contacts will remain connected." = "Tous vos contacts resteront connectés."; @@ -526,6 +550,9 @@ swipe action */ /* No comment provided by engineer. */ "Allow to irreversibly delete sent messages. (24 hours)" = "Autoriser la suppression irréversible de messages envoyés. (24 heures)"; +/* No comment provided by engineer. */ +"Allow to report messsages to moderators." = "Permettre de signaler des messages aux modérateurs."; + /* No comment provided by engineer. */ "Allow to send files and media." = "Permet l'envoi de fichiers et de médias."; @@ -580,6 +607,9 @@ swipe action */ /* No comment provided by engineer. */ "and %lld other events" = "et %lld autres événements"; +/* report reason */ +"Another reason" = "Autre raison"; + /* No comment provided by engineer. */ "Answer call" = "Répondre à l'appel"; @@ -622,18 +652,39 @@ swipe action */ /* No comment provided by engineer. */ "Apply to" = "Appliquer à"; +/* No comment provided by engineer. */ +"Archive" = "Archiver"; + +/* No comment provided by engineer. */ +"Archive %lld reports?" = "Archiver les rapports %lld ?"; + +/* No comment provided by engineer. */ +"Archive all reports?" = "Archiver tous les rapports ?"; + /* No comment provided by engineer. */ "Archive and upload" = "Archiver et téléverser"; /* No comment provided by engineer. */ "Archive contacts to chat later." = "Archiver les contacts pour discuter plus tard."; +/* No comment provided by engineer. */ +"Archive report" = "Archiver le rapport"; + +/* No comment provided by engineer. */ +"Archive report?" = "Archiver le rapport ?"; + +/* swipe action */ +"Archive reports" = "Archiver les rapports"; + /* No comment provided by engineer. */ "Archived contacts" = "Contacts archivés"; /* No comment provided by engineer. */ "Archiving database" = "Archivage de la base de données"; +/* No comment provided by engineer. */ +"Ask" = "Demander"; + /* No comment provided by engineer. */ "Attach" = "Attacher"; @@ -709,6 +760,9 @@ swipe action */ /* No comment provided by engineer. */ "Better groups" = "Des groupes plus performants"; +/* No comment provided by engineer. */ +"Better groups performance" = "Meilleure performance des groupes"; + /* No comment provided by engineer. */ "Better message dates." = "Meilleures dates de messages."; @@ -721,6 +775,9 @@ swipe action */ /* No comment provided by engineer. */ "Better notifications" = "Notifications améliorées"; +/* No comment provided by engineer. */ +"Better privacy and security" = "Meilleure protection de la privacité et de la sécurité"; + /* No comment provided by engineer. */ "Better security ✅" = "Sécurité accrue ✅"; @@ -794,9 +851,15 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Business chats" = "Discussions professionnelles"; +/* No comment provided by engineer. */ +"Businesses" = "Entreprises"; + /* 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)." = "Par profil de chat (par défaut) ou [par connexion](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; +/* No comment provided by engineer. */ +"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "En utilisant SimpleX Chat, vous acceptez de :\n- n'envoyer que du contenu légal dans les groupes publics.\n- respecter les autres utilisateurs - pas de spam."; + /* No comment provided by engineer. */ "call" = "appeler"; @@ -864,6 +927,9 @@ alert button */ /* No comment provided by engineer. */ "Change" = "Changer"; +/* alert title */ +"Change automatic message deletion?" = "Modifier la suppression automatique des messages ?"; + /* authentication reason */ "Change chat profiles" = "Changer de profil de discussion"; @@ -1012,6 +1078,12 @@ set passcode view */ /* No comment provided by engineer. */ "Clear conversation?" = "Effacer la conversation ?"; +/* No comment provided by engineer. */ +"Clear group?" = "Vider le groupe ?"; + +/* No comment provided by engineer. */ +"Clear or delete group?" = "Vider ou supprimer le groupe ?"; + /* No comment provided by engineer. */ "Clear private notes?" = "Effacer les notes privées ?"; @@ -1027,6 +1099,9 @@ set passcode view */ /* No comment provided by engineer. */ "colored" = "coloré"; +/* report reason */ +"Community guidelines violation" = "Infraction aux règles communautaires"; + /* server test step */ "Compare file" = "Comparer le fichier"; @@ -1063,6 +1138,9 @@ set passcode view */ /* No comment provided by engineer. */ "Configure ICE servers" = "Configurer les serveurs ICE"; +/* No comment provided by engineer. */ +"Configure server operators" = "Configurer les opérateurs de serveur"; + /* No comment provided by engineer. */ "Confirm" = "Confirmer"; @@ -1093,6 +1171,9 @@ set passcode view */ /* No comment provided by engineer. */ "Confirm upload" = "Confirmer la transmission"; +/* token status text */ +"Confirmed" = "Confirmé"; + /* server test step */ "Connect" = "Se connecter"; @@ -1192,6 +1273,9 @@ set passcode view */ /* No comment provided by engineer. */ "Connection and servers status." = "État de la connexion et des serveurs."; +/* No comment provided by engineer. */ +"Connection blocked" = "Connexion bloquée"; + /* No comment provided by engineer. */ "Connection error" = "Erreur de connexion"; @@ -1201,12 +1285,21 @@ set passcode view */ /* chat list item title (it should not be shown */ "connection established" = "connexion établie"; +/* No comment provided by engineer. */ +"Connection is blocked by server operator:\n%@" = "La connexion est bloquée par l'opérateur du serveur :\n%@"; + +/* No comment provided by engineer. */ +"Connection not ready." = "La connexion n'est pas prête."; + /* No comment provided by engineer. */ "Connection notifications" = "Notifications de connexion"; /* No comment provided by engineer. */ "Connection request sent!" = "Demande de connexion envoyée !"; +/* No comment provided by engineer. */ +"Connection requires encryption renegotiation." = "La connexion nécessite une renégociation du cryptage."; + /* No comment provided by engineer. */ "Connection security" = "Sécurité des connexions"; @@ -1267,6 +1360,9 @@ set passcode view */ /* No comment provided by engineer. */ "Contacts can mark messages for deletion; you will be able to view them." = "Vos contacts peuvent marquer les messages pour les supprimer ; vous pourrez les consulter."; +/* blocking reason */ +"Content violates conditions of use" = "Le contenu enfreint les conditions d'utilisation"; + /* No comment provided by engineer. */ "Continue" = "Continuer"; @@ -1309,6 +1405,9 @@ set passcode view */ /* No comment provided by engineer. */ "Create link" = "Créer un lien"; +/* No comment provided by engineer. */ +"Create list" = "Créer une liste"; + /* No comment provided by engineer. */ "Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" = "Créer un nouveau profil sur [l'application de bureau](https://simplex.chat/downloads/). 💻"; @@ -1488,6 +1587,9 @@ swipe action */ /* No comment provided by engineer. */ "Delete chat" = "Supprimer la discussion"; +/* No comment provided by engineer. */ +"Delete chat messages from your device." = "Supprimer les messages de chat de votre appareil."; + /* No comment provided by engineer. */ "Delete chat profile" = "Supprimer le profil de chat"; @@ -1542,6 +1644,9 @@ swipe action */ /* No comment provided by engineer. */ "Delete link?" = "Supprimer le lien ?"; +/* alert title */ +"Delete list?" = "Supprimer la liste ?"; + /* No comment provided by engineer. */ "Delete member message?" = "Supprimer le message de ce membre ?"; @@ -1572,6 +1677,9 @@ swipe action */ /* server test step */ "Delete queue" = "Supprimer la file d'attente"; +/* No comment provided by engineer. */ +"Delete report" = "Supprimer le rapport"; + /* No comment provided by engineer. */ "Delete up to 20 messages at once." = "Supprimez jusqu'à 20 messages à la fois."; @@ -1680,6 +1788,12 @@ swipe action */ /* No comment provided by engineer. */ "Disable (keep overrides)" = "Désactiver (conserver les remplacements)"; +/* alert title */ +"Disable automatic message deletion?" = "Désactiver la suppression automatique des messages ?"; + +/* alert button */ +"Disable delete messages" = "Désactiver la suppression des messages"; + /* No comment provided by engineer. */ "Disable for all" = "Désactiver pour tous"; @@ -1740,15 +1854,24 @@ swipe action */ /* No comment provided by engineer. */ "Do NOT use SimpleX for emergency calls." = "N'utilisez PAS SimpleX pour les appels d'urgence."; +/* No comment provided by engineer. */ +"Documents:" = "Documents:"; + /* No comment provided by engineer. */ "Don't create address" = "Ne pas créer d'adresse"; /* No comment provided by engineer. */ "Don't enable" = "Ne pas activer"; +/* No comment provided by engineer. */ +"Don't miss important messages." = "Ne manquez pas les messages importants."; + /* No comment provided by engineer. */ "Don't show again" = "Ne plus afficher"; +/* No comment provided by engineer. */ +"Done" = "Terminé"; + /* No comment provided by engineer. */ "Downgrade and open chat" = "Rétrograder et ouvrir le chat"; @@ -1816,6 +1939,9 @@ chat item action */ /* No comment provided by engineer. */ "Enable camera access" = "Autoriser l'accès à la caméra"; +/* No comment provided by engineer. */ +"Enable Flux in Network & servers settings for better metadata privacy." = "Activez Flux dans les paramètres du réseau et des serveurs pour une meilleure confidentialité des métadonnées."; + /* No comment provided by engineer. */ "Enable for all" = "Activer pour tous"; @@ -1927,6 +2053,9 @@ chat item action */ /* chat item text */ "encryption re-negotiation required for %@" = "renégociation de chiffrement requise pour %@"; +/* No comment provided by engineer. */ +"Encryption renegotiation in progress." = "Renégociation du chiffrement en cours."; + /* No comment provided by engineer. */ "ended" = "terminé"; @@ -2002,6 +2131,9 @@ chat item action */ /* No comment provided by engineer. */ "Error changing to incognito!" = "Erreur lors du passage en mode incognito !"; +/* No comment provided by engineer. */ +"Error checking token status" = "Erreur lors de la vérification de l'état du jeton (token)"; + /* No comment provided by engineer. */ "Error connecting to forwarding server %@. Please try later." = "Erreur de connexion au serveur de redirection %@. Veuillez réessayer plus tard."; @@ -2014,6 +2146,9 @@ chat item action */ /* No comment provided by engineer. */ "Error creating group link" = "Erreur lors de la création du lien du groupe"; +/* alert title */ +"Error creating list" = "Erreur lors de la création de la liste"; + /* No comment provided by engineer. */ "Error creating member contact" = "Erreur lors de la création du contact du membre"; @@ -2023,6 +2158,9 @@ chat item action */ /* No comment provided by engineer. */ "Error creating profile!" = "Erreur lors de la création du profil !"; +/* No comment provided by engineer. */ +"Error creating report" = "Erreur lors de la création du rapport"; + /* No comment provided by engineer. */ "Error decrypting file" = "Erreur lors du déchiffrement du fichier"; @@ -2089,12 +2227,21 @@ chat item action */ /* No comment provided by engineer. */ "Error reconnecting servers" = "Erreur de reconnexion des serveurs"; +/* alert title */ +"Error registering for notifications" = "Erreur lors de l'inscription aux notifications"; + /* No comment provided by engineer. */ "Error removing member" = "Erreur lors de la suppression d'un membre"; +/* alert title */ +"Error reordering lists" = "Erreur lors de la réorganisation des listes"; + /* No comment provided by engineer. */ "Error resetting statistics" = "Erreur de réinitialisation des statistiques"; +/* alert title */ +"Error saving chat list" = "Erreur lors de l'enregistrement de la liste des chats"; + /* No comment provided by engineer. */ "Error saving group profile" = "Erreur lors de la sauvegarde du profil de groupe"; @@ -2146,6 +2293,9 @@ chat item action */ /* No comment provided by engineer. */ "Error synchronizing connection" = "Erreur de synchronisation de connexion"; +/* No comment provided by engineer. */ +"Error testing server connection" = "Erreur lors du test de connexion au serveur"; + /* No comment provided by engineer. */ "Error updating group link" = "Erreur lors de la mise à jour du lien de groupe"; @@ -2199,6 +2349,9 @@ snd error text */ /* No comment provided by engineer. */ "expired" = "expiré"; +/* token status text */ +"Expired" = "Expiré"; + /* No comment provided by engineer. */ "Export database" = "Exporter la base de données"; @@ -2223,18 +2376,30 @@ snd error text */ /* No comment provided by engineer. */ "Fast and no wait until the sender is online!" = "Rapide et ne nécessitant pas d'attendre que l'expéditeur soit en ligne !"; +/* No comment provided by engineer. */ +"Faster deletion of groups." = "Suppression plus rapide des groupes."; + /* No comment provided by engineer. */ "Faster joining and more reliable messages." = "Connexion plus rapide et messages plus fiables."; +/* No comment provided by engineer. */ +"Faster sending messages." = "Envoi plus rapide des messages."; + /* swipe action */ "Favorite" = "Favoris"; +/* No comment provided by engineer. */ +"Favorites" = "Favoris"; + /* file error alert title */ "File error" = "Erreur de fichier"; /* alert message */ "File errors:\n%@" = "Erreurs de fichier :\n%@"; +/* file error text */ +"File is blocked by server operator:\n%@." = "Le fichier est bloqué par l'opérateur du serveur :\n%@."; + /* file error text */ "File not found - most likely file was deleted or cancelled." = "Fichier introuvable - le fichier a probablement été supprimé ou annulé."; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index f21ba8f6b3..2d2fbb2ad1 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -380,7 +380,7 @@ swipe action */ "Active connections" = "Aktív kapcsolatok száma"; /* No comment provided by engineer. */ -"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Cím hozzáadása a profilhoz, hogy a partnerei megoszthassák másokkal. A profilfrissítés el lesz küldve az Ön partnerei számára."; +"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Cím hozzáadása a profilhoz, hogy a partnerei megoszthassák másokkal. A profilfrissítés el lesz küldve partnerei számára."; /* No comment provided by engineer. */ "Add friends" = "Barátok hozzáadása"; @@ -479,7 +479,7 @@ swipe action */ "All data is erased when it is entered." = "A jelkód megadása után az összes adat törölve lesz."; /* No comment provided by engineer. */ -"All data is kept private on your device." = "Az összes adat privát módon van tárolva az Ön eszközén."; +"All data is kept private on your device." = "Az összes adat privát módon van tárolva az eszközén."; /* No comment provided by engineer. */ "All group members will remain connected." = "Az összes csoporttag kapcsolatban marad."; @@ -509,7 +509,7 @@ swipe action */ "All your contacts will remain connected." = "Az összes partnerével kapcsolatban marad."; /* No comment provided by engineer. */ -"All your contacts will remain connected. Profile update will be sent to your contacts." = "A partnereivel kapcsolatban marad. A profilfrissítés el lesz küldve az Ön partnerei számára."; +"All your contacts will remain connected. Profile update will be sent to your contacts." = "A partnereivel kapcsolatban marad. A profilfrissítés el lesz küldve a partnerei számára."; /* No comment provided by engineer. */ "All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Az összes partnere, -beszélgetése és -fájlja biztonságosan titkosítva lesz, majd töredékekre bontva feltöltődnek a beállított XFTP-továbbítókiszolgálókra."; @@ -1202,10 +1202,10 @@ set passcode view */ "Connect to yourself?" = "Kapcsolódik saját magához?"; /* No comment provided by engineer. */ -"Connect to yourself?\nThis is your own one-time link!" = "Kapcsolódik saját magához?\nEz az Ön egyszer használható meghívója!"; +"Connect to yourself?\nThis is your own one-time link!" = "Kapcsolódik saját magához?\nEz a saját egyszer használható meghívója!"; /* No comment provided by engineer. */ -"Connect to yourself?\nThis is your own SimpleX address!" = "Kapcsolódik saját magához?\nEz az Ön SimpleX-címe!"; +"Connect to yourself?\nThis is your own SimpleX address!" = "Kapcsolódik saját magához?\nEz a saját SimpleX-címe!"; /* No comment provided by engineer. */ "Connect via contact address" = "Kapcsolódás a kapcsolattartási címen keresztül"; @@ -1594,7 +1594,7 @@ swipe action */ "Delete chat" = "Csevegés törlése"; /* No comment provided by engineer. */ -"Delete chat messages from your device." = "Csevegési üzenetek törlése az Ön eszközéről."; +"Delete chat messages from your device." = "Csevegési üzenetek törlése a saját eszközéről."; /* No comment provided by engineer. */ "Delete chat profile" = "Csevegési profil törlése"; @@ -1633,7 +1633,7 @@ swipe action */ "Delete for everyone" = "Törlés az összes tagnál"; /* No comment provided by engineer. */ -"Delete for me" = "Csak számomra"; +"Delete for me" = "Csak nálam"; /* No comment provided by engineer. */ "Delete group" = "Csoport törlése"; @@ -1849,7 +1849,7 @@ swipe action */ "Do not send history to new members." = "Az előzmények ne legyenek elküldve az új tagok számára."; /* No comment provided by engineer. */ -"Do NOT send messages directly, even if your or destination server does not support private routing." = "NE küldjön üzeneteket közvetlenül, még akkor sem, ha az Ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást."; +"Do NOT send messages directly, even if your or destination server does not support private routing." = "NE küldjön üzeneteket közvetlenül, még akkor sem, ha a saját kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást."; /* No comment provided by engineer. */ "Do not use credentials with proxy." = "Ne használja a hitelesítőadatokat proxyval."; @@ -1910,7 +1910,7 @@ chat item action */ "Downloading link details" = "Letöltési hivatkozás részletei"; /* No comment provided by engineer. */ -"Duplicate display name!" = "Duplikált megjelenített név!"; +"Duplicate display name!" = "Duplikált megjelenítendő név!"; /* integrity error chat item */ "duplicate message" = "duplikált üzenet"; @@ -1945,6 +1945,9 @@ chat item action */ /* No comment provided by engineer. */ "Enable camera access" = "Kamera hozzáférés engedélyezése"; +/* No comment provided by engineer. */ +"Enable Flux in Network & servers settings for better metadata privacy." = "A Flux kiszolgálókat engedélyezheti a beállításokban, a „Hálózat és kiszolgálók” menüben, a metaadatok jobb védelme érdekében."; + /* No comment provided by engineer. */ "Enable for all" = "Engedélyezés az összes tag számára"; @@ -2488,7 +2491,7 @@ snd error text */ "For console" = "Konzolhoz"; /* No comment provided by engineer. */ -"For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server." = "Például, ha az Ön partnere egy SimpleX Chat-kiszolgálón keresztül fogadja az üzeneteket, az Ön alkalmazása egy Flux-kiszolgálón keresztül fogja azokat kézbesíteni."; +"For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server." = "Például, ha a partnere egy SimpleX Chat-kiszolgálón keresztül fogadja az üzeneteket, akkor az Ön alkalmazása egy Flux-kiszolgálón keresztül fogja azokat kézbesíteni."; /* No comment provided by engineer. */ "For me" = "Csak magamnak"; @@ -2929,7 +2932,7 @@ snd error text */ "invited to connect" = "Függőben lévő meghívó"; /* rcv group event chat item */ -"invited via your group link" = "meghíva az Ön csoporthivatkozásán keresztül"; +"invited via your group link" = "meghíva a saját csoporthivatkozásán keresztül"; /* No comment provided by engineer. */ "iOS Keychain is used to securely store passphrase - it allows receiving push notifications." = "Az iOS kulcstartó a jelmondat biztonságos tárolására szolgál – lehetővé teszi a push-értesítések fogadását."; @@ -2995,7 +2998,7 @@ snd error text */ "Join with current profile" = "Csatlakozás a jelenlegi profillal"; /* No comment provided by engineer. */ -"Join your group?\nThis is your link for group %@!" = "Csatlakozik a csoportjához?\nEz az Ön hivatkozása a(z) %@ nevű csoporthoz!"; +"Join your group?\nThis is your link for group %@!" = "Csatlakozik a csoportjához?\nEz a saját hivatkozása a(z) %@ nevű csoporthoz!"; /* No comment provided by engineer. */ "Joining group" = "Csatlakozás a csoporthoz"; @@ -3070,7 +3073,7 @@ snd error text */ "List name and emoji should be different for all lists." = "Az összes lista nevének és emodzsijának különbözőnek kell lennie."; /* No comment provided by engineer. */ -"List name..." = "Listanév…"; +"List name..." = "Lista neve…"; /* No comment provided by engineer. */ "LIVE" = "ÉLŐ"; @@ -3415,7 +3418,7 @@ snd error text */ "New desktop app!" = "Új számítógép-alkalmazás!"; /* No comment provided by engineer. */ -"New display name" = "Új megjelenítési név"; +"New display name" = "Új megjelenítendő név"; /* notification */ "New events" = "Új események"; @@ -3906,7 +3909,7 @@ time to disappear */ "Privacy & security" = "Adatvédelem és biztonság"; /* No comment provided by engineer. */ -"Privacy for your customers." = "Az Ön ügyfeleinek adatvédelme."; +"Privacy for your customers." = "Saját ügyfeleinek adatvédelme."; /* No comment provided by engineer. */ "Privacy policy and conditions of use." = "Adatvédelmi szabályzat és felhasználási feltételek."; @@ -3954,7 +3957,7 @@ time to disappear */ "Profile theme" = "Profiltéma"; /* alert message */ -"Profile update will be sent to your contacts." = "A profilfrissítés el lesz küldve az Ön partnerei számára."; +"Profile update will be sent to your contacts." = "A profilfrissítés el lesz küldve a partnerei számára."; /* No comment provided by engineer. */ "Prohibit audio/video calls." = "A hívások kezdeményezése le van tiltva."; @@ -4415,7 +4418,7 @@ chat item action */ "Scan QR code" = "QR-kód beolvasása"; /* No comment provided by engineer. */ -"Scan QR code from desktop" = "QR-kód beolvasása számítógépről"; +"Scan QR code from desktop" = "QR-kód beolvasása a számítógépről"; /* No comment provided by engineer. */ "Scan security code from your contact's app." = "Biztonsági kód beolvasása a partnere alkalmazásából."; @@ -4517,10 +4520,10 @@ chat item action */ "Send message to enable calls." = "Üzenet küldése a hívások engedélyezéséhez."; /* No comment provided by engineer. */ -"Send messages directly when IP address is protected and your or destination server does not support private routing." = "Közvetlen üzenetküldés, ha az IP-cím védett és az Ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást."; +"Send messages directly when IP address is protected and your or destination server does not support private routing." = "Közvetlen üzenetküldés, ha az IP-cím védett és a saját kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást."; /* No comment provided by engineer. */ -"Send messages directly when your or destination server does not support private routing." = "Közvetlen üzenetküldés, ha az Ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást."; +"Send messages directly when your or destination server does not support private routing." = "Közvetlen üzenetküldés, ha a saját kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást."; /* No comment provided by engineer. */ "Send notifications" = "Értesítések küldése"; @@ -5002,10 +5005,10 @@ report reason */ "Tap to scan" = "Koppintson ide a QR-kód beolvasásához"; /* No comment provided by engineer. */ -"TCP connection" = "TCP kapcsolat"; +"TCP connection" = "TCP-kapcsolat"; /* No comment provided by engineer. */ -"TCP connection timeout" = "TCP kapcsolat időtúllépése"; +"TCP connection timeout" = "TCP-kapcsolat időtúllépése"; /* No comment provided by engineer. */ "TCP port for messaging" = "TCP-port az üzenetváltáshoz"; @@ -5062,7 +5065,7 @@ report reason */ "The code you scanned is not a SimpleX link QR code." = "A beolvasott QR-kód nem egy SimpleX-QR-kód-hivatkozás."; /* No comment provided by engineer. */ -"The connection reached the limit of undelivered messages, your contact may be offline." = "A kapcsolat elérte a kézbesítetlen üzenetek számának határát, az Ön partnere lehet, hogy offline állapotban van."; +"The connection reached the limit of undelivered messages, your contact may be offline." = "A kapcsolat elérte a kézbesítetlen üzenetek számának határát, a partnere lehet, hogy offline állapotban van."; /* No comment provided by engineer. */ "The connection you accepted will be cancelled!" = "Az Ön által elfogadott kérelem vissza lesz vonva!"; @@ -5173,10 +5176,10 @@ report reason */ "This group no longer exists." = "Ez a csoport már nem létezik."; /* No comment provided by engineer. */ -"This is your own one-time link!" = "Ez az Ön egyszer használható meghívója!"; +"This is your own one-time link!" = "Ez a saját egyszer használható meghívója!"; /* No comment provided by engineer. */ -"This is your own SimpleX address!" = "Ez az Ön SimpleX-címe!"; +"This is your own SimpleX address!" = "Ez a saját SimpleX-címe!"; /* No comment provided by engineer. */ "This link was used with another mobile device, please create a new link on the desktop." = "Ezt a hivatkozást egy másik hordozható eszközön már használták, hozzon létre egy új hivatkozást a számítógépén."; @@ -5215,7 +5218,7 @@ report reason */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "Az IP-cím védelmének érdekében a privát útválasztás az SMP-kiszolgálókat használja az üzenetek kézbesítéséhez."; /* No comment provided by engineer. */ -"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Az Ön adatainak védelme érdekében a SimpleX külön üzenet-azonosítókat használ minden egyes kapcsolatához."; +"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Adatainak védelme érdekében a SimpleX külön üzenet-azonosítókat használ minden egyes kapcsolatához."; /* No comment provided by engineer. */ "To receive" = "A fogadáshoz"; @@ -5707,7 +5710,7 @@ report reason */ "You allow" = "Ön engedélyezi"; /* No comment provided by engineer. */ -"You already have a chat profile with the same display name. Please choose another name." = "Már van egy csevegési profil ugyanezzel a megjelenített névvel. Válasszon egy másik nevet."; +"You already have a chat profile with the same display name. Please choose another name." = "Már van egy csevegési profil ugyanezzel a megjelenítendő névvel. Válasszon egy másik nevet."; /* No comment provided by engineer. */ "You are already connected to %@." = "Ön már kapcsolódott a következőhöz: %@."; @@ -5926,7 +5929,7 @@ report reason */ "you: " = "Ön: "; /* No comment provided by engineer. */ -"You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile" = "Egy olyan partnerét próbálja meghívni, akivel inkognitóprofilt osztott meg abban a csoportban, amelyben a saját fő profilja van használatban"; +"You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile" = "Egy olyan partnerét próbálja meghívni, akivel inkognitóprofilt osztott meg abban a csoportban, amelyben a fő profilja van használatban"; /* No comment provided by engineer. */ "You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Inkognitóprofilt használ ehhez a csoporthoz – fő profilja megosztásának elkerülése érdekében a meghívók küldése le van tiltva"; @@ -5986,10 +5989,10 @@ report reason */ "Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "A profilja az eszközén van tárolva és csak a partnereivel van megosztva. A SimpleX-kiszolgálók nem láthatják a profilját."; /* alert message */ -"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "A profilja módosult. Ha elmenti, a profilfrissítés el lesz küldve az Ön partnerei számára."; +"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "A profilja módosult. Ha elmenti, a profilfrissítés el lesz küldve a partnerei számára."; /* No comment provided by engineer. */ -"Your profile, contacts and delivered messages are stored on your device." = "A profilja, a partnerei és az elküldött üzenetei az Ön eszközén vannak tárolva."; +"Your profile, contacts and delivered messages are stored on your device." = "A profilja, a partnerei és az elküldött üzenetei a saját eszközén vannak tárolva."; /* No comment provided by engineer. */ "Your random profile" = "Véletlenszerű profil"; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index 53798fe0eb..3988b33531 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -782,7 +782,7 @@ swipe action */ "Better notifications" = "Notifiche migliorate"; /* No comment provided by engineer. */ -"Better privacy and security" = "Privacy e sicurezza migliorate"; +"Better privacy and security" = "Privacy e sicurezza migliori"; /* No comment provided by engineer. */ "Better security ✅" = "Sicurezza migliorata ✅"; @@ -1945,6 +1945,9 @@ chat item action */ /* No comment provided by engineer. */ "Enable camera access" = "Attiva l'accesso alla fotocamera"; +/* No comment provided by engineer. */ +"Enable Flux in Network & servers settings for better metadata privacy." = "Attiva Flux nelle impostazioni \"Rete e server\" per una migliore privacy dei metadati."; + /* No comment provided by engineer. */ "Enable for all" = "Attiva per tutti"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index f9e289369e..cd026361e0 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -863,6 +863,9 @@ marked deleted chat item preview text */ /* 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)." = "Via chatprofiel (standaard) of [via verbinding](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; +/* No comment provided by engineer. */ +"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "Door SimpleX Chat te gebruiken, gaat u ermee akkoord:\n- alleen legale content te versturen in openbare groepen.\n- andere gebruikers te respecteren – geen spam."; + /* No comment provided by engineer. */ "call" = "bellen"; @@ -1141,6 +1144,9 @@ set passcode view */ /* No comment provided by engineer. */ "Configure ICE servers" = "ICE servers configureren"; +/* No comment provided by engineer. */ +"Configure server operators" = "Serveroperators configureren"; + /* No comment provided by engineer. */ "Confirm" = "Bevestigen"; @@ -1939,6 +1945,9 @@ chat item action */ /* No comment provided by engineer. */ "Enable camera access" = "Schakel cameratoegang in"; +/* No comment provided by engineer. */ +"Enable Flux in Network & servers settings for better metadata privacy." = "Schakel Flux in bij Netwerk- en serverinstellingen voor betere privacy van metagegevens."; + /* No comment provided by engineer. */ "Enable for all" = "Inschakelen voor iedereen"; @@ -3902,9 +3911,15 @@ time to disappear */ /* No comment provided by engineer. */ "Privacy for your customers." = "Privacy voor uw klanten."; +/* No comment provided by engineer. */ +"Privacy policy and conditions of use." = "Privacybeleid en gebruiksvoorwaarden."; + /* No comment provided by engineer. */ "Privacy redefined" = "Privacy opnieuw gedefinieerd"; +/* No comment provided by engineer. */ +"Private chats, groups and your contacts are not accessible to server operators." = "Privéchats, groepen en uw contacten zijn niet toegankelijk voor serverbeheerders."; + /* No comment provided by engineer. */ "Private filenames" = "Privé bestandsnamen"; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index f86b3aa4fd..45a2d6db4f 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -139,6 +139,12 @@ /* format for date separator in chat */ "%@, %@" = "%1$@, %2$@"; +/* No comment provided by engineer. */ +"%@, %@ and %lld members" = "%@, %@ и %lld членов группы"; + +/* No comment provided by engineer. */ +"%@, %@ and %lld other members connected" = "установлено соединение с %@, %@ и %lld другими членами группы"; + /* copied message info */ "%@:" = "%@:"; @@ -196,6 +202,9 @@ /* No comment provided by engineer. */ "%lld group events" = "%lld событий"; +/* No comment provided by engineer. */ +"%lld members" = "%lld членов"; + /* No comment provided by engineer. */ "%lld messages blocked" = "%lld сообщений заблокировано"; @@ -307,6 +316,9 @@ time interval */ /* No comment provided by engineer. */ "A separate TCP connection will be used **for each chat profile you have in the app**." = "Отдельное TCP-соединение будет использоваться **для каждого профиля чата, который Вы имеете в приложении**."; +/* No comment provided by engineer. */ +"A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail." = "Будет использовано отдельное TCP соединение **для каждого контакта и члена группы**.\n**Примечание**: Чем больше подключений, тем быстрее разряжается батарея и расходуется трафик, а некоторые соединения могут отваливаться."; + /* No comment provided by engineer. */ "Abort" = "Прекратить"; @@ -433,6 +445,9 @@ swipe action */ /* feature role */ "admins" = "админы"; +/* No comment provided by engineer. */ +"Admins can block a member for all." = "Админы могут заблокировать члена группы."; + /* No comment provided by engineer. */ "Admins can create the links to join groups." = "Админы могут создать ссылки для вступления в группу."; @@ -466,6 +481,9 @@ swipe action */ /* No comment provided by engineer. */ "All data is kept private on your device." = "Все данные хранятся только на вашем устройстве."; +/* No comment provided by engineer. */ +"All group members will remain connected." = "Все члены группы останутся соединены."; + /* No comment provided by engineer. */ "All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Все сообщения и файлы отправляются с **end-to-end шифрованием**, с постквантовой безопасностью в прямых разговорах."; @@ -517,6 +535,9 @@ swipe action */ /* No comment provided by engineer. */ "Allow message reactions." = "Разрешить реакции на сообщения."; +/* No comment provided by engineer. */ +"Allow sending direct messages to members." = "Разрешить личные сообщения членам группы."; + /* No comment provided by engineer. */ "Allow sending disappearing messages." = "Разрешить посылать исчезающие сообщения."; @@ -775,6 +796,18 @@ swipe action */ /* No comment provided by engineer. */ "Block for all" = "Заблокировать для всех"; +/* No comment provided by engineer. */ +"Block group members" = "Заблокировать членов группы"; + +/* No comment provided by engineer. */ +"Block member" = "Заблокировать члена группы"; + +/* No comment provided by engineer. */ +"Block member for all?" = "Заблокировать для всех?"; + +/* No comment provided by engineer. */ +"Block member?" = "Заблокировать члена группы?"; + /* marked deleted chat item preview text */ "blocked" = "заблокировано"; @@ -857,12 +890,18 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Can't call contact" = "Не удается позвонить контакту"; +/* No comment provided by engineer. */ +"Can't call member" = "Не удаётся позвонить члену группы"; + /* No comment provided by engineer. */ "Can't invite contact!" = "Нельзя пригласить контакт!"; /* No comment provided by engineer. */ "Can't invite contacts!" = "Нельзя пригласить контакты!"; +/* No comment provided by engineer. */ +"Can't message member" = "Не удаётся отправить сообщение члену группы"; + /* alert action alert button */ "Cancel" = "Отменить"; @@ -903,6 +942,9 @@ alert button */ /* authentication reason */ "Change lock mode" = "Изменить режим блокировки"; +/* No comment provided by engineer. */ +"Change member role?" = "Поменять роль члена группы?"; + /* authentication reason */ "Change passcode" = "Изменить код доступа"; @@ -1524,6 +1566,9 @@ pref value */ swipe action */ "Delete" = "Удалить"; +/* No comment provided by engineer. */ +"Delete %lld messages of members?" = "Удалить %lld сообщений членов группы?"; + /* No comment provided by engineer. */ "Delete %lld messages?" = "Удалить %lld сообщений?"; @@ -1737,6 +1782,9 @@ swipe action */ /* chat feature */ "Direct messages" = "Прямые сообщения"; +/* No comment provided by engineer. */ +"Direct messages between members are prohibited in this chat." = "Личные сообщения запрещены в этой группе."; + /* No comment provided by engineer. */ "Disable (keep overrides)" = "Выключить (кроме исключений)"; diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index 160e1322b7..a804d48dfb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -2366,4 +2366,10 @@ لا يمكن الوصول إلى الدردشات الخاصة والمجموعات وجهات اتصالك لمشغلي الخادم. باستخدام SimpleX Chat، توافق على:\n- إرسال المحتوى القانوني فقط في المجموعات العامة.\n- احترام المستخدمين الآخرين – لا سبام. اقبل + استخدم روابط قصيرة (تجريبي) + يتطلب هذا الرابط إصدار تطبيق أحدث. يُرجى ترقية التطبيق أو اطلب من جهة اتصالك إرسال رابط متوافق. + رابط كامل + رابط قصير + رابط قناة SimpleX + رابط اتصال غير مدعوم diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml index b0c6e32c70..77c2a6ef65 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml @@ -2343,4 +2343,10 @@ Acceptar En utilitzar SimpleX Chat accepteu:\n- enviar només contingut legal en grups públics.\n- Respectar els altres usuaris, sense correu brossa. Configurar els operadors de servidor + Enllaç al canal SimpleX + Aquest enllaç requereix una versió de l\'aplicació més recent. Actualitzeu l\'aplicació o demaneu al vostre contacte que enviï un enllaç compatible. + Enllaç de connexió no compatible + Emprar enllaços curts (BETA) + Enllaç complet + Enllaç curt diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml index 3b8db5044b..e262a59214 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml @@ -1373,9 +1373,7 @@ Zjednodušený režim inkognito Vytvořit nový profil v desktopové aplikaci. 💻 Změnit inkognito při připojování. - - připojit k adresáři skupin (BETA)! -\n- doručenky (až 20 členů). -\n- rychlejší a stabilnější. + - připojení k adresáři skupin (BETA)!\n- doručenky (až 20 členů).\n- rychlejší a stabilnější. odeslat přímou zprávu smazaný kontakt Chyba @@ -2113,7 +2111,7 @@ %s.]]> %s.]]> Přidány servery pro média & soubory - Povolit flux + Povolte Flux v nastavení sítě a serverů pro lepší ochranu metadat. Servery přes proxy Soukromí pro vaše zákazníky. moderátor @@ -2367,4 +2365,9 @@ Členové budou odstraněny ze skupiny - toto nelze zvrátit! Odebrat členy? Členové budou odstraněny z chatu - toto nelze zvrátit! + Použitím SimpleX chatu souhlasíte že:\n- ve veřejných skupinách budete zasílat pouze legální obsah.\n- budete respektovat ostatní uživatele – žádný spam. + Přijmout + Nastavit operátora serveru + Zásady ochrany soukromí a podmínky používání. + Soukromé konverzace, skupiny a kontakty nejsou přístupné provozovatelům serverů. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index 145d4142a7..089ef06827 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -597,8 +597,8 @@ Starten Sie die App neu, um ein neues Chat-Profil zu erstellen. Sie dürfen die neueste Version Ihrer Chat-Datenbank NUR auf einem Gerät verwenden, andernfalls erhalten Sie möglicherweise keine Nachrichten mehr von einigen Ihrer Kontakte. Dateien und Medien löschen? - Es werden alle herunter- und hochgeladene Dateien und Medien gelöscht. Bilder mit niedriger Auflösung bleiben erhalten. Diese Aktion kann nicht rückgängig gemacht werden! - Keine herunter- oder hochgeladene Dateien + Es werden alle herunter- und hochgeladenen Dateien und Medien gelöscht. Bilder mit niedriger Auflösung bleiben erhalten. Diese Aktion kann nicht rückgängig gemacht werden! + Keine herunter- oder hochgeladenen Dateien %d Datei(en) mit einem Gesamtspeicherverbrauch von %s nie Älter als ein Tag @@ -1714,8 +1714,8 @@ Bitte bestätigen Sie für die Migration, dass Sie sich an Ihr Datenbank-Passwort erinnern. Hochladen bestätigen Herunterladen fehlgeschlagen - Ende-zu-Ende-Verschlüsselung mit Perfect Forward Secrecy, Ablehnung und Einbruchs-Wiederherstellung geschützt.]]> - Quantum-resistente E2E-Verschlüsselung mit Perfect Forward Secrecy, Ablehnung und Einbruchs-Wiederherstellung geschützt.]]> + Ende-zu-Ende-Verschlüsselung mit Perfect Forward Secrecy, Abstreitbarkeit und Wiederherstellung nach einer Kompromittierung geschützt.]]> + Quantum-resistente E2E-Verschlüsselung mit Perfect Forward Secrecy, Abstreitbarkeit und Wiederherstellung nach einer Kompromittierung geschützt.]]> Dieser Chat ist durch Ende-zu-Ende-Verschlüsselung geschützt. Dieser Chat ist durch Quantum-resistente Ende-zu-Ende-Verschlüsselung geschützt. Migrationsansicht öffnen @@ -2448,8 +2448,14 @@ Mitglieder für Alle blockieren? Alle neuen Nachrichten dieser Mitglieder werden nicht angezeigt! Durch die Nutzung von SimpleX Chat erklären Sie sich damit einverstanden:\n- nur legale Inhalte in öffentlichen Gruppen zu versenden.\n- andere Nutzer zu respektieren - kein Spam. - Datenschutzbestimmungen und Nutzungsbedingungen. + Datenschutz- und Nutzungsbedingungen. Akzeptieren Server-Betreiber konfigurieren Private Chats, Gruppen und Ihre Kontakte sind für Server-Betreiber nicht zugänglich. + Verbindungs-Link wird nicht unterstützt + Verkürzte Links verwenden (BETA) + Verkürzter Link + Vollständiger Link + SimpleX-Kanal-Link + Für diesen Link wird eine neuere App-Version benötigt. Bitte aktualisieren Sie die App oder bitten Sie Ihren Kontakt einen kompatiblen Link zu senden. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index be71a7a927..6ba4c15afb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -2377,4 +2377,10 @@ Los chats privados, los grupos y tus contactos no son accesibles para los operadores de servidores. Al usar SimpleX Chat, aceptas:\n- enviar únicamente contenido legal en los grupos públicos.\n- respetar a los demás usuarios - spam prohibido. Configurar operadores de servidores + Enlace de canal SimpleX + Enlace completo + Enlace corto + Este enlace requiere una versión más reciente de la aplicación. Por favor, actualiza la aplicación o pide a tu contacto un enlace compatible. + Enlace de conexión no compatible + Usar enlaces cortos (BETA) diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index 5ab5dd8a63..6d62d3f5b8 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -29,7 +29,7 @@ Elfogadja a meghívási kérést? Elfogadás Elfogadás - Cím hozzáadása a profilhoz, hogy a partnerei megoszthassák másokkal. A profilfrissítés el lesz küldve az Ön partnerei számára. + Cím hozzáadása a profilhoz, hogy a partnerei megoszthassák másokkal. A profilfrissítés el lesz küldve partnerei számára. További kiemelőszín híváshiba Csoporttagok letiltása @@ -48,7 +48,7 @@ Megjegyzés: az üzenet- és fájltovábbító kiszolgálók SOCKS-proxyn keresztül kapcsolódnak. A hívások és a hivatkozások előnézetének elküldése közvetlen kapcsolatot használnak.]]> Alkalmazásadatok biztonsági mentése Az adatbázis előkészítése sikertelen - A partnereivel kapcsolatban marad. A profilfrissítés el lesz küldve az Ön partnerei számára. + A partnereivel kapcsolatban marad. A profilfrissítés el lesz küldve a partnerei számára. A csevegési profillal (alapértelmezett), vagy a kapcsolattal (BÉTA). Egy új, véletlenszerű profil lesz megosztva. A hangüzenetek küldése csak abban az esetben van engedélyezve, ha a partnere is engedélyezi. @@ -287,7 +287,7 @@ Hitelesítés törlése készítő Megerősítés - Csak számomra + Csak nálam Töröl %d üzenetet? Egyéni témák kapcsolódás (elfogadva) @@ -475,7 +475,7 @@ TCP életben tartása Kamera váltás Üdvözlöm!\nCsatlakozzon hozzám a SimpleX Chaten keresztül: %s - A megjelenített név nem tartalmazhat szóközöket. + A megjelenítendő név nem tartalmazhat szóközöket. Csoport Adja meg az üdvözlőüzenetet… (nem kötelező) Hiba történt a csevegési adatbázis exportálásakor @@ -740,7 +740,7 @@ Új meghívási kérés Csatlakozás a csoporthoz Társított számítógép beállítások - meghíva az Ön csoporthivatkozásán keresztül + meghíva a saját csoporthivatkozásán keresztül elhagyta a csoportot Társított számítógépek Nincs alkalmazás jelkód @@ -835,7 +835,7 @@ Hamarosan további fejlesztések érkeznek! kikapcsolva SimpleX Chat telepítése a terminálhoz - Új megjelenített név: + Új megjelenítendő név: Új jelmondat… nem fogadott hívás Átköltöztetés: %s @@ -926,7 +926,7 @@ (beolvasás, vagy beillesztés a vágólapról) Várakozás a videóra Válasz - Ez az Ön egyszer használható meghívója! + Ez a saját egyszer használható meghívója! SimpleX Chat hívások Új inkognitóprofil használata Frissítse az alkalmazást, és lépjen kapcsolatba a fejlesztőkkel. @@ -1038,14 +1038,14 @@ Kiszolgálók mentése Üdvözlőüzenet mp - A profilfrissítés el lesz küldve az Ön partnerei számára. + A profilfrissítés el lesz küldve a partnerei számára. Egyszerűsített inkognitómód Menti az üdvözlőüzenetet? Új csevegési fiók létrehozásához indítsa újra az alkalmazást. Engedély megtagadva! Függőben lévő hívás Adatbázis megnyitása… - Leállítás? + Leállítja az alkalmazást? Jelmondat szükséges Privát értesítések Ön meghívta egy partnerét @@ -1061,7 +1061,7 @@ Adatbázis-jelmondat beállítása Üzenetbuborék színe Időszakosan indul - Ez az Ön SimpleX-címe! + Ez a saját SimpleX-címe! eltávolítva Megosztás SimpleX csapat @@ -1219,7 +1219,7 @@ Védje meg a csevegési profiljait egy jelszóval! Csak a partnere tud eltűnő üzeneteket küldeni. Saját ICE-kiszolgálók - QR-kód beolvasása számítógépről + QR-kód beolvasása a számítógépről SimpleX logó Feloldás Némítás megszüntetése @@ -1262,7 +1262,7 @@ SimpleX Chat-kiszolgálók használata? Csevegési profil felfedése Videók és fájlok legfeljebb 1GB méretig - TCP kapcsolat időtúllépése + TCP-kapcsolat időtúllépése A(z) %1$s nevű profilja meg lesz osztva. Ön már kapcsolódott a következőhöz: %1$s. A jelenlegi csevegési adatbázis TÖRÖLVE és CSERÉLVE lesz az importáltra!\nEz a művelet nem vonható vissza – profiljai, partnerei, csevegési üzenetei és fájljai véglegesen törölve lesznek. @@ -1341,7 +1341,7 @@ Üzenetek formázása a szövegbe szúrt speciális karakterekkel: Megnyitás az alkalmazásban gombra.]]> A csevegési profilja el lesz küldve\na partnere számára - Egy olyan partnerét próbálja meghívni, akivel inkognitóprofilt osztott meg abban a csoportban, amelyben a saját fő profilja van használatban + Egy olyan partnerét próbálja meghívni, akivel inkognitóprofilt osztott meg abban a csoportban, amelyben a fő profilja van használatban %1$s nevű csoporthoz.]]> Amikor az alkalmazás fut Inkognitóprofilt használ ehhez a csoporthoz – fő profilja megosztásának elkerülése érdekében a meghívók küldése le van tiltva @@ -1361,7 +1361,7 @@ A profilja az eszközén van tárolva és csak a partnereivel van megosztva. A SimpleX-kiszolgálók nem láthatják a profilját. Ön a következőre módosította %s szerepkörét: „%s” Csoportmeghívó elutasítva - Az Ön adatainak védelme érdekében a SimpleX külön üzenet-azonosítókat használ minden egyes kapcsolatához. + Adatainak védelme érdekében a SimpleX külön üzenet-azonosítókat használ minden egyes kapcsolatához. (a megosztáshoz a partnerével) Csoportmeghívó elküldve Frissíti az átvitel-izoláció módját? @@ -1380,7 +1380,7 @@ A partnerei engedélyezhetik a teljes üzenet törlését. A jelmondatot minden alkalommal meg kell adnia, amikor az alkalmazás elindul – nem az eszközön van tárolva. Ha engedélyezni szeretné a hordozható eszköz-alkalmazás társítását a számítógéphez, akkor nyissa meg ezt a portot a tűzfalában, miután engedélyezte azt - A profilja, a partnerei és az elküldött üzenetei az Ön eszközén vannak tárolva. + A profilja, a partnerei és az elküldött üzenetei a saját eszközén vannak tárolva. Alkalmazás akkumulátor-használata / Korlátlan módot az alkalmazás beállításaiban.]]> Ez a karakterlánc nem egy meghívási hivatkozás! Új csevegés indításához @@ -1399,7 +1399,7 @@ Inkognitóra váltás kapcsolódáskor. Megoszthat egy hivatkozást vagy QR-kódot – így bárki csatlakozhat a csoporthoz. Ha a csoportot Ön később törli, akkor nem fogja elveszíteni annak tagjait. Ön csatlakozott ehhez a csoporthoz - %1$s nevű csoporthoz!]]> + %1$s nevű csoporthoz!]]> A hangüzenetek küldése le van tiltva ebben a csevegésben. Ön irányítja csevegését! Kód hitelesítése a számítógépen @@ -1744,13 +1744,13 @@ Használjon privát útválasztást ismeretlen kiszolgálókkal. Mindig használjon privát útválasztást. Üzenet-útválasztási mód - Közvetlen üzenetküldés, ha az IP-cím védett és az Ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. - Közvetlen üzenetküldés, ha az Ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. + Közvetlen üzenetküldés, ha az IP-cím védett és a saját kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. + Közvetlen üzenetküldés, ha a saját kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. Az IP-cím védelmének érdekében a privát útválasztás az SMP-kiszolgálókat használja az üzenetek kézbesítéséhez. Üzenet-útválasztási tartalék PRIVÁT ÜZENET-ÚTVÁLASZTÁS Használjon privát útválasztást ismeretlen kiszolgálókkal, ha az IP-cím nem védett. - NE küldjön üzeneteket közvetlenül, még akkor sem, ha az Ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. + NE küldjön üzeneteket közvetlenül, még akkor sem, ha a saját kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. Tor vagy VPN nélkül az Ön IP-címe látható lesz a fájlkiszolgálók számára. FÁJLOK IP-cím védelme @@ -1834,7 +1834,7 @@ Kapcsolódás Hibák Függőben - Statisztikagyűjtés kezdete: %s.\nAz összes adat privát módon van tárolva az Ön eszközén. + Statisztikagyűjtés kezdete: %s.\nAz összes adat privát módon van tárolva az eszközén. Elküldött üzenetek Proxyzott kiszolgálók Újrakapcsolódik a kiszolgálókhoz? @@ -1999,7 +1999,7 @@ Mentés és újrakapcsolódás Használja az alkalmazást egy kézzel. A partnerek archiválása a későbbi csevegéshez. - TCP kapcsolat + TCP-kapcsolat Az exportált archívumot elmentheti. Tippek visszaállítása Csevegési lista átváltása: @@ -2090,7 +2090,7 @@ Hibák a kiszolgálók konfigurációjában. Hiba történt a feltételek elfogadásakor Kézbesítetlen üzenetek - A kapcsolat elérte a kézbesítetlen üzenetek számának határát, az Ön partnere lehet, hogy offline állapotban van. + A kapcsolat elérte a kézbesítetlen üzenetek számának határát, a partnere lehet, hogy offline állapotban van. Nincs üzenet Ez az üzenet törölve lett vagy még nem érkezett meg. Koppintson a SimpleX-cím létrehozása menüpontra a későbbi létrehozáshoz. @@ -2109,7 +2109,7 @@ Kiszolgálóüzemeltetők Hálózatüzemeltetők Az alkalmazás úgy védi az adatait, hogy minden egyes beszélgetéshez más-más üzemeltetőt használ. - Például, ha az Ön partnere egy SimpleX Chat-kiszolgálón keresztül fogadja az üzeneteket, az Ön alkalmazása egy Flux-kiszolgálón keresztül fogja azokat kézbesíteni. + Például, ha a partnere egy SimpleX Chat-kiszolgálón keresztül fogadja az üzeneteket, akkor az Ön alkalmazása egy Flux-kiszolgálón keresztül fogja azokat kézbesíteni. Jelölje ki a használni kívánt hálózatüzemeltetőket. Felülvizsgálat később A kiszolgálókat a „Hálózat és kiszolgálók” menüben konfigurálhatja. @@ -2159,7 +2159,7 @@ Elhomályosítás Hálózati decentralizáció A második előre beállított üzemeltető az alkalmazásban! - A Flux kiszolgálókat engedélyezheti a beállításokban, a „Hálózat és kiszolgálók” menüben, a jobb metaadat-adatvédelem érdekében. + A Flux kiszolgálókat engedélyezheti a beállításokban, a „Hálózat és kiszolgálók” menüben, a metaadatok jobb védelme érdekében. Alkalmazás-eszköztárak a metaadatok jobb védelme érdekében. Javított csevegési navigáció @@ -2195,7 +2195,7 @@ A csevegési profilja el lesz küldve a csevegésben résztvevő tagok számára A tagok közötti közvetlen üzenetek le vannak tiltva. Üzleti csevegések - Az Ön ügyfeleinek adatvédelme. + Saját ügyfeleinek adatvédelme. %1$s.]]> A csevegés már létezik! Csökkentse az üzenet méretét, és küldje el újra. @@ -2240,7 +2240,7 @@ Törlés Törli a listát? Szerkesztés - Listanév… + Lista neve… Az összes lista nevének és emodzsijának különbözőnek kell lennie. Nincsenek csevegések a(z) %s nevű listában. Jegyzetek @@ -2291,7 +2291,7 @@ Az ebben a csevegésben lévő üzenetek soha nem lesznek törölve. 1 év alapértelmezett (%s) - Csevegési üzenetek törlése az Ön eszközéről. + Csevegési üzenetek törlése a saját eszközéről. Módosítja az automatikus üzenettörlést? Ez a művelet nem vonható vissza – a kijelölt üzenettől korábban küldött és fogadott üzenetek törölve lesznek a csevegésből. A következő TCP-port használata, amikor nincs port megadva: %1$s. @@ -2344,4 +2344,10 @@ Adatvédelmi szabályzat és felhasználási feltételek. A privát csevegések, a csoportok és a partnerek nem érhetők el a szerver üzemeltetői számára. Kiszolgálóüzemeltetők beállítása + Nem támogatott kapcsolattartási hivatkozás + Rövid hivatkozások használata (béta) + Rövid hivatkozás + Teljes hivatkozás + Ez a hivatkozás újabb alkalmazásverziót igényel. Frissítse az alkalmazást vagy kérjen egy kompatibilis hivatkozást a partnerétől. + SimpleX-csatornahivatkozás diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml index 278b94feb3..95b60a3394 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml @@ -1696,7 +1696,7 @@ negosiasi ulang enkripsi diperbolehkan negosiasi ulang enkripsi diperlukan negosiasi ulang enkripsi diizinkan untuk %s - Aktifkan flux + Aktifkan Flux di pengaturan Jaringan dan server untuk privasi metadata yang lebih baik. untuk privasi metadata lebih baik. Navigasi obrolan ditingkatkan Galat @@ -2337,4 +2337,16 @@ Atur pesan kedaluwarsa obrolan. Nama berkas media pribadi. ditolak + Anggota akan dihapus dari grup - tindakan ini tidak dapat dibatalkan! + moderator + Pesan dari anggota ini akan ditampilkan! + Sebutkan anggota 👋 + Atur obrolan ke dalam daftar + Frasa sandi di Keystore tidak dapat dibaca. Hal ini mungkin terjadi setelah pembaruan sistem yang tidak kompatibel dengan aplikasi. Jika tidak demikian, silakan hubungi pengembang. + Terima + Dengan menggunakan SimpleX Chat, Anda setuju untuk:\n- hanya mengirim konten legal di grup publik.\n- hormati pengguna lain – tidak ada spam. + Konfigurasikan operator server + Kebijakan privasi dan ketentuan penggunaan. + Obrolan pribadi, grup, dan kontak Anda tidak dapat diakses oleh operator server. + Frasa sandi di Keystore tidak dapat dibaca, silakan masukkan secara manual. Hal ini mungkin terjadi setelah pembaruan sistem yang tidak kompatibel dengan aplikasi. Jika tidak demikian, silakan hubungi pengembang. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index 08a8f9b87e..4373091266 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -2353,7 +2353,7 @@ Ricevi una notifica quando menzionato. Aiuta gli amministratori a moderare i loro gruppi. Menziona i membri 👋 - Privacy e sicurezza migliorate + Privacy e sicurezza migliori Invio dei messaggi più veloce. Prestazioni dei gruppi migliorate Organizza le chat in elenchi @@ -2381,4 +2381,10 @@ Accetta Configura gli operatori dei server Informativa sulla privacy e condizioni d\'uso. + Questo link richiede una versione più recente dell\'app. Aggiornala o chiedi al tuo contatto di inviare un link compatibile. + Link completo + Link breve + Link del canale SimpleX + Link di connessione non supportato + Usa link brevi (BETA) diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml index ab78cf51f3..1103e3a0e6 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml @@ -371,7 +371,7 @@ מופעל מופעל עבור איש הקשר מופעל עבורך - הודעות נעלמות אסורות בקבוצה זו. + הודעות נעלמות אסורות. %d דקה %d שנ׳ הודעות נעלמות @@ -489,10 +489,10 @@ הקבוצה תימחק עבורך – לא ניתן לבטל זאת! הסתר העדפות קבוצה - חברי קבוצה יכולים למחוק הודעות שנשלחו באופן בלתי הפיך. (24 שעות) - חברי הקבוצה יכולים לשלוח הודעות נעלמות. - חברי הקבוצה יכולים לשלוח הודעות ישירות. - חברי הקבוצה יכולים לשלוח הודעות קוליות. + משתמשים יכולים למחוק הודעות שנשלחו באופן בלתי הפיך. (24 שעות) + משתמשים יכולים לשלוח הודעות נעלמות. + משתמשים יכולים לשלוח הודעות ישירות. + יכולים לשלוח הודעות קוליות. אפשר השמדה עצמית אם תבחרו לדחות השולח לא יקבל התראה על כך. אם תאשרו, שרתי העברת ההודעות יוכלו לראות את ה־IP שלכם, וספק האינטרנט שלכם – את השרתים אליהם אתם מחוברים. @@ -505,7 +505,7 @@ התעלם מיד ייבא מסד נתונים - חסין מפני ספאם ושימוש לרעה + חסין מפני ספאם לייבא מסד נתונים של צ׳אט\? תמונה נשלחה התמונה תתקבל כאשר איש הקשר יסיים להעלות אותה. @@ -521,10 +521,10 @@ זהות נסתרת באמצעות קישור קבוצה זהות נסתרת באמצעות קישור חד־פעמי קישור חיבור לא תקין - אפשרו ל-SimpleX לפעול ברקע בתיבת הדו-שיח הבאה. אחרת, ההתראות יושבתו.]]> + אפשר זאת בתיבת הדו-שיח הבאה כדי לקבל התראות על הודעות חדשות באופן מיידי.]]> התראות מיידיות מושבתות! הזמן חברי קבוצה - הוזמן + הזמין את עקיף (%1$s) מצב זהות נסתרת מגן על הפרטיות שלך על ידי שימוש בפרופיל אקראי חדש עבור כל איש קשר. גרסת מסד נתונים לא תואמת @@ -556,14 +556,14 @@ הזמן לקבוצה הזמן חברי קבוצה מחיקה בלתי הפיכה של הודעות אסורה בצ׳אט זה. - מחיקה בלתי הפיכה של הודעות אסורה בקבוצה זו. + מחיקת הודעות בלתי הפיכה אסורה. להצטרף בתור %s זה מאפשר חיבורים אנונימיים רבים ללא שום נתונים משותפים ביניהם בפרופיל צ׳אט יחיד. זה יכול לקרות כאשר: \n1. פג תוקפן של ההודעות בלקוח השולח לאחר 2 ימים או בשרת לאחר 30 ימים. \n2. פיענוח הצפנת הודעה נכשל, מכיוון שאתם או איש הקשר שלכם השתמשתם בגיבוי ישן של מסד הנתונים. \n3. החיבור נפגע. - ניתן לשנות זאת מאוחר יותר באמצעות ההגדרות. + איך זה משפיע על הסוללה זה יכול לקרות כאשר אתם או איש הקשר שלכם השתמשתם בגיבוי ישן של מסד הנתונים. להצטרף לקבוצה\? הצטרף @@ -608,7 +608,7 @@ קישור הזמנה חד־פעמי מרקדאון בהודעות רשת ושרתים - הגדרות רשת + הגדרות מתקדמות ארכיון מסד נתונים חדש הודעות חבר קבוצה @@ -690,7 +690,7 @@ תגובות אמוג׳י להודעות אסורות בקבוצה זו. אפשר לאנשי הקשר להוסיף תגובות אמוג׳י להודעות. אפשר תגובות אמוג׳י להודעות רק אם איש הקשר מאפשר אותן. - חברי הקבוצה יכולים להוסיף תגובות אמוג׳י להודעות. + משתמשים יכולים להוסיף תגובות אמוג׳י להודעות. רק אתם יכולים להוסיף תגובות אמוג׳י להודעות. רק איש הקשר שלכם יכול להוסיף תגובות אמוג׳י להודעות. פתח @@ -737,7 +737,7 @@ שרת מוגדר מראש פרטיות מוגדרת מחדש אנשים יכולים להתחבר אליכם רק דרך הקישורים שאתם משתפים. - פרוטוקול וקוד פתוחים – כל אחד יכול להריץ את השרתים. + כל אחד יכול לארח שרתים. תקופתי נא להזין את הסיסמה הקודמת לאחר שחזור גיבוי מסד הנתונים, לא ניתן לבטל פעולה זו. לאסור מחיקה בלתי הפיכה של הודעות. @@ -746,7 +746,7 @@ אנא בידקו את חיבור האינטרנט שלכם עם %1$s ונסו שוב. ייתכן שטביעת האצבע של התעודה בכתובת השרת שגויה פתיחת מסוף צ׳אט - פתיחת פרופילי צ׳אט + שנה פרופילי צ׳אט ממתין כתובת שרת מוגדר מראש סיסמה להצגה @@ -1016,7 +1016,7 @@ יותר מדי תמונות! תודה שהתקנתם את SimpleX Chat! קישור זה אינו קישור חיבור תקין! - צבעי ערכת נושא + צבעי ממשק התפקיד ישתנה ל־"%s". החבר יקבל הזמנה חדשה. השרתים לחיבורים חדשים של פרופיל הצ׳אט הנוכחי שלך הפלטפורמה הראשונה ללא כל מזהי משתמש - פרטית בעיצובה. @@ -1054,7 +1054,7 @@ שדרג ופתח צ׳אט כדי להגן על אזור הזמן, קובצי תמונה/קול משתמשים ב־UTC. העלה קובץ - שירות רקע SimpleX – הוא משתמש בכמה אחוזים מהסוללה ביום.]]> + SimpleX רץ ברקע במקום להשתמש בpush notifications.]]> כדי לקבל התראות, יש להזין את סיסמת מסד הנתונים בטל נעילה שליחה לא מורשית @@ -1239,15 +1239,15 @@ כיבוי אפליקציה אפשר לשלוח קבצים ומדיה. - מועדף + הוסף למועדפים קבצים ומדיה אין צ\'אטים מסוננים לכבות\? קבצים ומדיה אסורים! - קבצים ומדיה אסורים בקבוצה זו. - חברי הקבוצה יכולים לשלוח קבצים ומדיה. + קבצים ומדיה אסורים. + משתמשים יכולים לשלוח קבצים ומדיה. איתחול - שנוא + הסר מהמועדפים כבוי קו חוצה לאסור שליחת קבצים ומדיה. @@ -1528,8 +1528,8 @@ הצג קריאות API איטיות אפשרויות למפתח צור פרופיל - ו %d שאר האירועים - הגדר כתובת איש קשר חדש + בנוסף ל- %d אירועים אחרים + איש הקשר הגדיר כתובת חדשה לחץ לחיבור דפדפן האינטרנט המוגדר כברירת מחדל נדרש לשיחות. אנא הגדר דפדפן ברירת מחדל במערכת, ושתף מידע נוסף עם המפתחים. השיחה הזו מוגנת באמצעות הצפנה קצה-אל-קצה. @@ -1609,7 +1609,7 @@ החיבור עצר נתיב קובץ לא חוקי שיתפת נתיב קובץ לא חוקי. דווח על הבעיה למפתחי האפליקציה. - %1$d הודעות שנערכו על ידי %2$s + %1$d הודעות נחסמו על ידי %2$s %d הודעות סומנו כנמחקות האם לחזור על בקשת החיבור? חסום @@ -1632,13 +1632,13 @@ טעינה של הקובץ שימוש ממחשב שולחני חסומים %s - מחק איש קשר + איש קשר נמחק %d אירועי קבוצה %s, %s ו-%d חברים איש הקשר %1$s השתנה ל-%2$s כתובת איש קשר הוסרה תמונת פרופיל הוסרה - הגדר תמונת פרופיל חדשה + הגדיר תמונת פרופיל חדשה עדכן פרופיל מצב לא ידוע נוצר ב @@ -1722,7 +1722,7 @@ מרובע, עיגול, או כל דבר ביניהם העבר ושמור הודעות מתי שמתחבר שחיות קוליות ווידאו. - לא מצליח לשלוח הודעה + לא ניתן לשלוח הודעה הודעות קוליות לא מאופשרות שגיאת קובץ זמני קבצים ומדיה לא מאופשרים @@ -1748,7 +1748,7 @@ מצב הקובץ:%s ריק כהה - מצב צבעוני + ערכת נושא שחור בהיר אפס צבע @@ -1767,8 +1767,8 @@ הגדר ערכת נושא ברירת מחדל אפס ערכת נושא למשתמש החל ל - מצב כל הצבעים - חברי הקבוצה יכולים לשלוח קישורי SimpleX + ערכת נושא + משתמשים יכולים לשלוח קישורי SimpleXצ עשה שהצאט\'ים שלך יראו אחרת! הגדרות רשת הקישור הזה שומש כבר במכשיר אחר, אנא צור קישור חדש במחשב. @@ -1918,4 +1918,177 @@ שרת XFTP חלש אנשי קשר בארכיון + דיווח בארכיון + הפרה של הנחיות קהילתיות + %1$d שגיאת קבצים:\n%2$s + %1$d הקבצים עדיין בהורדה. + הסכם לתנאים + %1$d ההורדה של הקובץ/ים עדיין לא הסתיימה. + התנאים המקובלים עלי + שנה + סיבה אחרת + כתובת עסקית + שיפור בסידור של הודעות לפי תאריכים. + שיפור ביצועים לקבוצות + התקשר + לא ניתן להתקשר לחבר קבוצה + %s.]]> + להעביר דיווח לארכיון? + מסד נתונים של הצא\'טים + בדוק עבור הודעות חדשות כל 10 דקות + אישרת את תנאי השימוש ב:%s. + הצ\'אט יימחק עבור כל החברים - לא ניתן לבטל את זה! + לחץ על כפתור מידע ליד שדה כתובת כדי לאפשר שימוש במיקרופון. + %s.]]> + %s.]]> + העבר אנשי קשר לארכיון לשוחח מאוחר יותר + לשנות את מחיקת ההודעה האוטומטית? + הצ\'אט כבר קיים! + הכל + %1$d שגיאה/ות קובץ אחר/ות. + %1$d הקובץ/ים נכשל/ו בהורדה. + כל ההודעות החדשות מחברים אלו יוסתרו! + לחסום את חברי הקבוצה לכולם? + הדיווח הועבר לארכיון ע\"י %s + עם איש קשר אחד בלבד - שתף באופן אישי או באמצעות כל מסנג\'ר.]]> + טשטוש בשביל שיפור הפרטיות. + דיווח 1 + כל הצ\'אטים יוסרו מהרשימת %s, והרשימה תימחק + שנה רשימה + לאפשר שיחות? + הצ\'אט יימחק עבורך - אי אפשר לבטל את זה! + שיחות לא מורשות! + שאל + %s.]]> + מכשירי שיואמי: אנא תאפשר הפעלה אוטומטית בהגדרות הטלפון שלך כדי שההתראות על הודעות חדשות יפעלו.]]> + %1$s ההודעות לא הועברו. + מוצפנים מקצה לקצה, עם אבטחה פוסט-קוונטית בהודעות ישירות.]]> + טישטוש + עסקי + %s, קבל את תנאי השימוש.]]> + כל הדיווחים אצלך יועברו לארכיון. + להעביר לארכיון %d דיווחים? + להעביר לארכיון את כל הדיווחים? + העבר דיווח לארכיון + דיווחים בארכיון + אפשר לדווח על הודעות למנהלים. + %1$d הקובץ/ים נמחקו. + %s.]]> + המסד נתונים יוצא בהצלחה + %1$s כבר באנשי קשר.]]> + לא ניתן לשלוח הודעה לחבר קבוצה + קבל הזמנה + על המפעילים + לא ניתן להתקשר לאיש קשר + שיפור לשיחות + שנה את הסדר + ארכיון + אני מסכים + שיפור בפרטיות ובאבטחה + אבטחה יותר טובה✅ + ממשק משתמש יותר נוח + "גרסאת שרת היעד %1$s אינה תואמת עם שרת ההעברה %2$s." + מחק עד 20 הודעות בבת אחת. + הודעות ישירות בין חברים חסומות. + השבת מחיקת הודעות + אל תשתמש בתעודות עם פרוקסי. + ירד + %d דיווחים + יורד %s (%s) + השבת מחיקת הודעות אוטומטית? + כפילויות + תנאי שימוש + מחק בלי להתריע + הורדה + הודעות ישירות בין חברים אסורות בצ\'אט זה. + אל תחמיץ הודעות חשובות. + הורד גרסאות חדשות מ GitHub. + פרופיל לא הולם + לפרטיות מטא דאטא טובה יותר. + אפשר Flux בהגדרות רשת ושרתים בשביל לשפר את הפרטיות של המטא דאטא + מעביר %1$s הודעות + שגיאה בקריאת משפט-סיסמה של מסד נתונים + משתמשים יכולים לדווח על הודעות לאחראי תוכן + שגיאת אתחול ב WebView, וודא שיש לך WebView מותקן והוא תותך בארכיטקטורה arm64\nשגיאה: %s + שגיאה בשמירת פרוקסי + לפרופיל צ\'אט %s: + למסלול פרטי + שגיאה בשמירת ההגדרות + שגיאה בהעברת ההודעות + שגיאה בהחלפת פרופיל + אפשר לוגים + איך זה משפר את הפרטיות + שיפור בגלילה בצ\'אט + תוכן לא הולם + שגיאה בקבלת תנאי שימוש + שגיאות בתצורת השרתים. + שרת ההעברות %1$s לא הצליח להתחבר לשרת היעד %2$s.נסה שוב במועד מאוחר יותר. + דיווחים מחברי הקבוצה + שרתי קבצים ומדיה + קישור לא תקין + שגיאה ביצירת רשימת צא\'טים חדשה + שגיאת עדכון רשימת צ\'אטים + שגיאה בטעינת רשימות הצא\'טים + ערוך + ודא שתצורת ה- proxy נכונה. + הזמן + זה מגן על כתובת ה- IP והחיבורים שלך. + עזוב צ\'אט + תקן + לתקן חיבור? + מחיקה מהירה יותר של קבוצות. + שליחת הודעות מהירה יותר. + העבר הודעות ללא קבצים? + הזמן לצ\'אט + שרת ההעברות: %1$s\nשגיאת שרת יעד: %2$s + שרת ההעברות: %1$s\nשגיאה: %2$s + למדיה חברתית + הגדל את גודל הגופן. + רשימה + מעודפים + קבוצות + שם הרשימה.. + שם הרשימה והאמוג\'י צריכים להיות שונים משאר הרשימות. + שמור שיחה + הזמן + ממשק בשפה הליטאית + הקובץ נחסם ע\"י מפעיל השרת:\n%1$s. + בשבילי + לכל האחראי תוכן + העבר הודעות… + שגיאה בשמירת המסד נתונים + שגיאה בהוספת שרת + שגיאת עדכון שם שרת + להעביר %1$s הודעה/ות? + שגיאה בשליחת הדיווח + שגיאה בשמירת שרתים + לעזוב את הצ\'אט? + העבר עד 20 הודעות בבת אחת. + קבל התראה כאשר מתייגים אותך + עזור לאחראי תוכן בדיווחים על תוכן בעייתי בקבוצות + הוסף רשימה + הדגשה נוספת 2 + בשימוש שלך ב- SimpleX Chat אתה מסכים ל:\n\n- לשלוח רק תוכן חוקי בקבוצות ציבוריות.\n\n- לכבד את שאר המשתמשים - לא לשלוח ספאם. + %s.]]> + %s.]]> + הוסף חברים + שרתי הודעות שנוספו + שרתי מדיה וקבצים שנוספו + הוסף לרשימה + חיבורים פעילים + הוסף את חברי הצוות שלך לשיחות. + האפליקציה תמיד רצה ברקע + הגדרות כתובת + סרגל הכלים של האפליקציה + סשן לאפליקציה + כתובת או קישור חד פעמי? + הוסף חברי צוות + %s.]]> + צ\'אט + צא\'טים עסקיים + "הזבל נמחק" + אנשי קשר + איש הקשר יימחק - לא ניתן לבטל זאת! + איש הקשר נמחק. + ספאם diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index d0b3cd5f53..d07cb6db39 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -2183,7 +2183,7 @@ Netwerk decentralisatie De tweede vooraf ingestelde operator in de app! Als uw contactpersoon bijvoorbeeld berichten ontvangt via een SimpleX Chat-server, worden deze door uw app via een Flux-server verzonden. - Flux inschakelen + Schakel Flux in bij Netwerk- en serverinstellingen voor betere privacy van metagegevens. Geen bericht App-werkbalken Vervagen @@ -2373,4 +2373,15 @@ Bijgewerkte voorwaarden Wachtwoord in Keystore kan niet worden gelezen. Dit kan zijn gebeurd na een systeemupdate die niet compatibel is met de app. Als dit niet het geval is, neem dan contact op met de ontwikkelaars. in behandeling + Accepteer + Door SimpleX Chat te gebruiken, gaat u ermee akkoord:\n- alleen legale content te versturen in openbare groepen.\n- andere gebruikers te respecteren – geen spam. + SimpleX channel link + Voor deze link is een nieuwere app-versie vereist. Werk de app bij of vraag je contactpersoon om een compatibele link te sturen. + Volledige link + Niet-ondersteunde verbindingslink + Gebruik korte links (BETA) + Korte link + Serveroperators configureren + Privacybeleid en gebruiksvoorwaarden. + Privéchats, groepen en uw contacten zijn niet toegankelijk voor serverbeheerders. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index ce6d5fbfa8..ad2a7e67be 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -496,7 +496,7 @@ Забороняйте надсилання повідомлень, які зникають. Забороняйте невідворотне видалення повідомлень. Учасники можуть надсилати голосові повідомлення. - %dm + %dм Нове в %s Самознищуючий пароль Італійський інтерфейс @@ -528,7 +528,7 @@ Встановити на 1 день Забороняйте реакції на повідомлення. Реакції на повідомлення заборонені в цьому чаті. - %ds + %dс хвилини Китайський та іспанський інтерфейс підключення %1$d @@ -756,7 +756,7 @@ %d година %d тиждень %d тижні - %dw + %dтиж запропоновано %s: %2s З опційним вітанням. Приховуйте екран додатка в останніх програмах. @@ -876,7 +876,7 @@ Надіслано о: %s Видалено о: %s %s (поточне) - %dh + %dч %d день %d днів скасовано %s @@ -985,7 +985,7 @@ Змінити роль Ви все ще отримуватимете дзвінки та сповіщення від приглушених профілів, коли вони активні. %d місяці - %dmth + %dміс Надіслані повідомлення будуть видалені після встановленого часу. Відкриття бази даних… Помилка встановлення адреси @@ -1133,7 +1133,7 @@ Підключитися Щоб показати ваш схований профіль, введіть повний пароль у поле пошуку на сторінці Ваші профілі. Підтвердити пароль - %dd + %dд Захистіть свої чат-профілі паролем! Помилка дешифрування Сервер вимагає авторизації для завантаження, перевірте пароль @@ -2179,7 +2179,7 @@ Текст поточних умов не вдалося завантажити, ви можете переглянути умови за цим посиланням: Помилки в конфігурації серверів. Умови приймаються з: %s. - Увімкнути flux + Увімкніть Flux у налаштуваннях мережі та серверів для кращої конфіденційності метаданих Умови приймаються до: %s. Продовжити Створити одноразове посилання @@ -2373,4 +2373,15 @@ Учасників буде видалено з чату – це неможливо скасувати! Розблокувати учасників для всіх? Оновлені умови + Приватні чати, групи та ваші контакти недоступні для операторів сервера. + Прийняти + Використовуючи SimpleX Chat, ви погоджуєтесь на:\n- надсилати тільки легальний контент у публічних групах.\n- поважати інших користувачів – без спаму. + Налаштувати операторів сервера + Політика конфіденційності та умови використання + Використовувати короткі посилання (BETA) + Це посилання вимагає новішої версії додатку. Будь ласка, оновіть додаток або попросіть вашого контакту надіслати сумісне посилання. + Повне посилання + Коротке посилання + Посилання на канал SimpleX + Несумісне посилання для підключення diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml index 311d492892..99931215e7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml @@ -828,7 +828,7 @@ Chào buổi chiều! Giao diện tiếng Pháp Đã tìm thấy máy tính - Đầy đủ đường dẫn + Toàn bộ đường dẫn Từ Thư viện Tên đầy đủ: Tuyệt đối phi tập trung - chỉ hiển thị cho thành viên. @@ -2352,4 +2352,10 @@ Các cuộc trò chuyện riêng tư, nhóm và liên hệ của bạn không thể truy cập được đối với các bên vận hành máy chủ. Chấp nhận Định cấu hình các bên vận hành máy chủ + Đường dẫn này yêu cầu một phiên bản ứng dụng mới hơn. Vui lòng nâng cấp ứng dụng hoặc yêu cầu liên hệ của một gửi cho một đường dẫn tương thích. + Đường dẫn kênh SimpleX + Đường dẫn kết nối không được hỗ trợ + Sử dụng đường dẫn ngắn (BETA) + Toàn bộ đường dẫn + Đường dẫn ngắn diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index 24abd90a31..1958885843 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -2365,4 +2365,10 @@ 使用 SimpleX Chat 代表您同意:\n- 在公开群中只发送合法内容\n- 尊重其他用户 – 没有垃圾信息。 服务器运营方无法访问私密聊天、群组和你的联系人。 配置服务器运营方 + 不支持的连接链接 + 使用短链接(测试) + SimpleX 频道链接 + 短链接 + 此链接需要更新的应用版本。请升级应用或请求你的联系人发送相容的链接。 + 完整链接 From 96b962809fb7f8f6916e7332fe05cf32dceb4e39 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 23 Apr 2025 13:09:16 +0100 Subject: [PATCH 515/567] core: fix connecting via short links --- src/Simplex/Chat/Library/Commands.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 890a27f573..5c544e3397 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -3159,9 +3159,9 @@ processChatCommand' vr = \case hash :: ConnReqContact -> ConnReqUriHash hash = ConnReqUriHash . C.sha256Hash . strEncode getShortLinkConnReq :: User -> ConnShortLink m -> CM (ConnectionRequestUri m) - getShortLinkConnReq User {userId} l = do + getShortLinkConnReq user l = do l' <- restoreShortLink' l - (cReq, cData) <- withAgent (\a -> getConnShortLink a userId l') + (cReq, cData) <- withAgent (\a -> getConnShortLink a (aUserId user) l') case cData of ContactLinkData {direct} | not direct -> throwChatError CEUnsupportedConnReq _ -> pure () From 5351fa68d06bf24f2910b4393b72da4f6983e6e1 Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Wed, 23 Apr 2025 12:27:30 +0000 Subject: [PATCH 516/567] ci: switch to sha256 and skip 8.10.7 on release (#5837) * ci: skip 8.10.7 on release * ci: switch to sha256 * script/reproduce-builds: make it executable * scripts/reproduce-builds: rename to simplex-chat-reproduce-builds * ci: bump actions * ci: 20.04 is deprecated * scripts/reproduce-builds: remove Ubuntu 20.04 * docs: adjust reproduce script * ci: skip 8.10.7 in stable or release for Linux * ci: really skup 8.10.7 in stable or release * ci: remove useless linux checks * ci: remove timeout from mac tests * ci: fix action names * ci: setup swap for 8.10.7 * ci: bump swap to 30gb * ci: simplify * ci: 10 -> 3 retries * ci: retry only in stable or release --- .github/actions/prepare-build/action.yml | 5 - .github/actions/prepare-release/action.yml | 4 +- .github/actions/swap/action.yml | 44 ++++++++ .github/workflows/build.yml | 102 +++++++++++++----- docs/SERVER.md | 6 +- ...ds.sh => simplex-chat-reproduce-builds.sh} | 12 +-- 6 files changed, 132 insertions(+), 41 deletions(-) create mode 100644 .github/actions/swap/action.yml rename scripts/{reproduce-builds.sh => simplex-chat-reproduce-builds.sh} (88%) mode change 100644 => 100755 diff --git a/.github/actions/prepare-build/action.yml b/.github/actions/prepare-build/action.yml index 6682641419..ce75b7a57c 100644 --- a/.github/actions/prepare-build/action.yml +++ b/.github/actions/prepare-build/action.yml @@ -24,11 +24,6 @@ inputs: runs: using: "composite" steps: - - name: Skip unreliable ghc 8.10.7 build on stable branch - shell: bash - if: inputs.ghc_ver == '8.10.7' && inputs.github_ref == 'refs/heads/stable' - run: exit 0 - - name: Setup Haskell uses: simplex-chat/setup-haskell-action@v2 with: diff --git a/.github/actions/prepare-release/action.yml b/.github/actions/prepare-release/action.yml index e44e6ef0f2..e0d32bd596 100644 --- a/.github/actions/prepare-release/action.yml +++ b/.github/actions/prepare-release/action.yml @@ -19,7 +19,7 @@ inputs: runs: using: "composite" steps: - - name: Linux upload AppImage to release + - name: Upload file with specific name if: startsWith(inputs.github_ref, 'refs/tags/v') uses: simplex-chat/upload-release-action@v2 with: @@ -28,7 +28,7 @@ runs: asset_name: ${{ inputs.bin_name }} tag: ${{ inputs.github_ref }} - - name: Linux update AppImage hash + - name: Add hash to release notes if: startsWith(inputs.github_ref, 'refs/tags/v') uses: simplex-chat/action-gh-release@v2 env: diff --git a/.github/actions/swap/action.yml b/.github/actions/swap/action.yml new file mode 100644 index 0000000000..87d670b147 --- /dev/null +++ b/.github/actions/swap/action.yml @@ -0,0 +1,44 @@ +name: 'Set Swap Space' +description: 'Add moar swap' +branding: + icon: 'crop' + color: 'orange' +inputs: + swap-size-gb: + description: 'Swap space to create, in Gigabytes.' + required: false + default: '10' +runs: + using: "composite" + steps: + - name: Swap space report before modification + shell: bash + run: | + echo "Memory and swap:" + free -h + echo + swapon --show + echo + - name: Set Swap + shell: bash + run: | + export SWAP_FILE=$(swapon --show=NAME | tail -n 1) + echo "Swap file: $SWAP_FILE" + if [ -z "$SWAP_FILE" ]; then + SWAP_FILE=/opt/swapfile + else + sudo swapoff $SWAP_FILE + sudo rm $SWAP_FILE + fi + sudo fallocate -l ${{ inputs.swap-size-gb }}G $SWAP_FILE + sudo chmod 600 $SWAP_FILE + sudo mkswap $SWAP_FILE + sudo swapon $SWAP_FILE + - name: Swap space report after modification + shell: bash + run: | + echo "Memory and swap:" + free -h + echo + swapon --show + echo diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index de0b976bcc..ca1bc79510 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -73,7 +73,7 @@ jobs: - name: Build changelog id: build_changelog if: startsWith(github.ref, 'refs/tags/v') - uses: simplex-chat/release-changelog-builder-action@v4 + uses: simplex-chat/release-changelog-builder-action@v5 with: configuration: .github/changelog_conf.json failOnError: true @@ -84,7 +84,7 @@ jobs: - name: Create release if: startsWith(github.ref, 'refs/tags/v') - uses: simplex-chat/action-gh-release@v1 + uses: simplex-chat/action-gh-release@v2 with: body: ${{ steps.build_changelog.outputs.changelog }} prerelease: true @@ -106,26 +106,38 @@ jobs: fail-fast: false matrix: include: - - os: 20.04 + - os: 22.04 ghc: "8.10.7" - - os: 20.04 - ghc: ${{ needs.variables.outputs.GHC_VER }} - cli_asset_name: simplex-chat-ubuntu-20_04-x86-64 - desktop_asset_name: simplex-desktop-ubuntu-20_04-x86_64.deb + should_run: ${{ !(github.ref == 'refs/heads/stable' || startsWith(github.ref, 'refs/tags/v')) }} - os: 22.04 ghc: ${{ needs.variables.outputs.GHC_VER }} cli_asset_name: simplex-chat-ubuntu-22_04-x86-64 desktop_asset_name: simplex-desktop-ubuntu-22_04-x86_64.deb + should_run: true + - os: 24.04 + ghc: ${{ needs.variables.outputs.GHC_VER }} + cli_asset_name: simplex-chat-ubuntu-24_04-x86-64 + desktop_asset_name: simplex-desktop-ubuntu-24_04-x86_64.deb + should_run: true steps: - name: Checkout Code + if: matrix.should_run == true uses: actions/checkout@v3 + - name: Setup swap + if: matrix.ghc == '8.10.7' && matrix.should_run == true + uses: ./.github/actions/swap + with: + swap-size-gb: 30 + # Otherwise we run out of disk space with Docker build - name: Free disk space + if: matrix.should_run == true shell: bash run: ./scripts/ci/linux_util_free_space.sh - name: Restore cached build + if: matrix.should_run == true uses: actions/cache@v4 with: path: | @@ -134,9 +146,11 @@ jobs: key: ubuntu-${{ matrix.os }}-ghc${{ matrix.ghc }}-${{ hashFiles('cabal.project', 'simplex-chat.cabal') }} - name: Set up Docker Buildx + if: matrix.should_run == true uses: simplex-chat/docker-setup-buildx-action@v3 - name: Build and cache Docker image + if: matrix.should_run == true uses: simplex-chat/docker-build-push-action@v6 with: context: . @@ -152,6 +166,7 @@ jobs: # --cap-add SYS_ADMIN # --security-opt apparmor:unconfined - name: Start container + if: matrix.should_run == true shell: bash run: | docker run -t -d \ @@ -165,6 +180,7 @@ jobs: build/${{ matrix.os }}:latest - name: Prepare cabal.project.local + if: matrix.should_run == true shell: bash run: | echo "ignore-project: False" >> cabal.project.local @@ -173,6 +189,7 @@ jobs: # chmod/git commands are used to workaround permission issues when cache is restored - name: Build CLI + if: matrix.should_run == true shell: docker exec -t builder sh -eu {0} run: | chmod -R 777 dist-newstyle ~/.cabal && git config --global --add safe.directory '*' @@ -188,22 +205,23 @@ jobs: strip /out/simplex-chat - name: Copy tests from container + if: matrix.should_run == true shell: bash run: | docker cp builder:/out/simplex-chat-test . - name: Copy CLI from container and prepare it id: linux_cli_prepare - if: startsWith(github.ref, 'refs/tags/v') && matrix.cli_asset_name + if: startsWith(github.ref, 'refs/tags/v') && matrix.should_run == true shell: bash run: | docker cp builder:/out/simplex-chat ./${{ matrix.cli_asset_name }} path="${{ github.workspace }}/${{ matrix.cli_asset_name }}" echo "bin_path=$path" >> $GITHUB_OUTPUT - echo "bin_hash=$(echo SHA2-512\(${{ matrix.cli_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT + echo "bin_hash=$(echo SHA2-256\(${{ matrix.cli_asset_name }}\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - name: Upload CLI - if: startsWith(github.ref, 'refs/tags/v') && matrix.cli_asset_name + if: startsWith(github.ref, 'refs/tags/v') && matrix.should_run == true uses: ./.github/actions/prepare-release with: bin_path: ${{ steps.linux_cli_prepare.outputs.bin_path }} @@ -213,7 +231,7 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} - name: Build Desktop - if: startsWith(github.ref, 'refs/tags/v') && matrix.desktop_asset_name + if: startsWith(github.ref, 'refs/tags/v') && matrix.should_run == true shell: docker exec -t builder sh -eu {0} run: | scripts/desktop/build-lib-linux.sh @@ -222,16 +240,16 @@ jobs: - name: Prepare Desktop id: linux_desktop_build - if: startsWith(github.ref, 'refs/tags/v') && matrix.desktop_asset_name + if: startsWith(github.ref, 'refs/tags/v') && matrix.should_run == true shell: bash run: | path=$(echo ${{ github.workspace }}/apps/multiplatform/release/main/deb/simplex_*_amd64.deb ) echo "package_path=$path" >> $GITHUB_OUTPUT - echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT + echo "package_hash=$(echo SHA2-256\(${{ matrix.desktop_asset_name }}\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - name: Upload Desktop uses: ./.github/actions/prepare-release - if: startsWith(github.ref, 'refs/tags/v') && matrix.desktop_asset_name + if: startsWith(github.ref, 'refs/tags/v') && matrix.should_run == true with: bin_path: ${{ steps.linux_desktop_build.outputs.package_path }} bin_name: ${{ matrix.desktop_asset_name }} @@ -240,22 +258,22 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} - name: Build AppImage - if: startsWith(github.ref, 'refs/tags/v') && matrix.desktop_asset_name && matrix.os == '20.04' + if: startsWith(github.ref, 'refs/tags/v') && matrix.os == '22.04' && matrix.should_run == true shell: docker exec -t builder sh -eu {0} run: | scripts/desktop/make-appimage-linux.sh - name: Prepare AppImage id: linux_appimage_build - if: startsWith(github.ref, 'refs/tags/v') && matrix.desktop_asset_name && matrix.os == '20.04' + if: startsWith(github.ref, 'refs/tags/v') && matrix.os == '22.04' && matrix.should_run == true shell: bash run: | path=$(echo ${{ github.workspace }}/apps/multiplatform/release/main/*imple*.AppImage) echo "appimage_path=$path" >> $GITHUB_OUTPUT - echo "appimage_hash=$(echo SHA2-512\(simplex-desktop-x86_64.AppImage\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT + echo "appimage_hash=$(echo SHA2-256\(simplex-desktop-x86_64.AppImage\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - name: Upload AppImage - if: startsWith(github.ref, 'refs/tags/v') && matrix.desktop_asset_name && matrix.os == '20.04' + if: startsWith(github.ref, 'refs/tags/v') && matrix.os == '22.04' && matrix.should_run == true uses: ./.github/actions/prepare-release with: bin_path: ${{ steps.linux_appimage_build.outputs.appimage_path }} @@ -265,15 +283,33 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} - name: Fix permissions for cache + if: matrix.should_run == true shell: bash run: | sudo chmod -R 777 dist-newstyle ~/.cabal sudo chown -R $(id -u):$(id -g) dist-newstyle ~/.cabal - name: Run tests + if: matrix.should_run == true + timeout-minutes: 120 shell: bash run: | - ./simplex-chat-test + i=1 + attempts=1 + ${{ (github.ref == 'refs/heads/stable' || startsWith(github.ref, 'refs/tags/v')) }} && attempts=3 + while [ "$i" -le "$attempts" ]; do + if ./simplex-chat-test; then + break + else + echo "Attempt $i failed, retrying..." + i=$((i + 1)) + sleep 1 + fi + done + if [ "$i" -gt "$attempts" ]; then + echo "All "$attempts" attempts failed." + exit 1 + fi # ========================= # MacOS Build @@ -332,7 +368,7 @@ jobs: cabal build -j --enable-tests path=$(cabal list-bin simplex-chat) echo "bin_path=$path" >> $GITHUB_OUTPUT - echo "bin_hash=$(echo SHA2-512\(${{ matrix.cli_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT + echo "bin_hash=$(echo SHA2-256\(${{ matrix.cli_asset_name }}\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - name: Upload CLI if: startsWith(github.ref, 'refs/tags/v') @@ -356,7 +392,7 @@ jobs: scripts/ci/build-desktop-mac.sh path=$(echo $PWD/apps/multiplatform/release/main/dmg/SimpleX-*.dmg) echo "package_path=$path" >> $GITHUB_OUTPUT - echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT + echo "package_hash=$(echo SHA2-256\(${{ matrix.desktop_asset_name }}\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - name: Upload Desktop if: startsWith(github.ref, 'refs/tags/v') @@ -369,9 +405,25 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} - name: Run tests - timeout-minutes: 40 + timeout-minutes: 120 shell: bash - run: cabal test --test-show-details=direct + run: | + i=1 + attempts=1 + ${{ (github.ref == 'refs/heads/stable' || startsWith(github.ref, 'refs/tags/v')) }} && attempts=3 + while [ "$i" -le "$attempts" ]; do + if cabal test --test-show-details=direct; then + break + else + echo "Attempt $i failed, retrying..." + i=$((i + 1)) + sleep 1 + fi + done + if [ "$i" -gt "$attempts" ]; then + echo "All "$attempts" attempts failed." + exit 1 + fi # ========================= # Windows Build @@ -443,7 +495,7 @@ jobs: rm -rf dist-newstyle/src/direct-sq* path=$(cabal list-bin simplex-chat | tail -n 1) echo "bin_path=$path" >> $GITHUB_OUTPUT - echo "bin_hash=$(echo SHA2-512\(${{ matrix.cli_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT + echo "bin_hash=$(echo SHA2-256\(${{ matrix.cli_asset_name }}\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - name: Upload CLI if: startsWith(github.ref, 'refs/tags/v') @@ -467,7 +519,7 @@ jobs: rm -rf dist-newstyle/src/direct-sq* path=$(echo $PWD/release/main/msi/*imple*.msi | sed 's#/\([a-z]\)#\1:#' | sed 's#/#\\#g') echo "package_path=$path" >> $GITHUB_OUTPUT - echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT + echo "package_hash=$(echo SHA2-256\(${{ matrix.desktop_asset_name }}\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - name: Upload Desktop if: startsWith(github.ref, 'refs/tags/v') diff --git a/docs/SERVER.md b/docs/SERVER.md index 57f49b5588..f45403be8a 100644 --- a/docs/SERVER.md +++ b/docs/SERVER.md @@ -1603,19 +1603,19 @@ To reproduce the build you must have: 1. Download script: ```sh - curl -LO 'https://raw.githubusercontent.com/simplex-chat/simplexmq/refs/heads/master/scripts/reproduce-builds.sh' + curl -LO 'https://raw.githubusercontent.com/simplex-chat/simplexmq/refs/heads/master/scripts/simplexmq-reproduce-builds.sh' ``` 2. Make it executable: ```sh - chmod +x reproduce-builds.sh + chmod +x simplexmq-reproduce-builds.sh ``` 3. Execute the script with the required tag: ```sh - ./reproduce-builds.sh 'v6.3.1' + ./simplexmq-reproduce-builds.sh 'v6.3.1' ``` The script executes these steps (please review the script to confirm): diff --git a/scripts/reproduce-builds.sh b/scripts/simplex-chat-reproduce-builds.sh old mode 100644 new mode 100755 similarity index 88% rename from scripts/reproduce-builds.sh rename to scripts/simplex-chat-reproduce-builds.sh index 1334fec0ec..408d1d4a53 --- a/scripts/reproduce-builds.sh +++ b/scripts/simplex-chat-reproduce-builds.sh @@ -24,13 +24,13 @@ cleanup() { } trap 'cleanup' EXIT INT -mkdir -p "$init_dir/$TAG/from-source" "$init_dir/$TAG/prebuilt" +mkdir -p "$init_dir/$TAG-$repo_name/from-source" "$init_dir/$TAG-$repo_name/prebuilt" git -C "$tempdir" clone "$repo.git" &&\ cd "$tempdir/${repo_name}" &&\ git checkout "$TAG" -for os in 20.04 22.04; do +for os in 22.04 24.04; do os_url="$(printf '%s' "$os" | tr '.' '_')" # Build image @@ -57,11 +57,11 @@ for os in 20.04 22.04; do docker cp \ builder:/out/simplex-chat \ - "$init_dir/$TAG/from-source/simplex-chat-ubuntu-${os_url}-x86-64" + "$init_dir/$TAG-$repo_name/from-source/simplex-chat-ubuntu-${os_url}-x86-64" # Download prebuilt postgresql binary curl -L \ - --output-dir "$init_dir/$TAG/prebuilt/" \ + --output-dir "$init_dir/$TAG-$repo_name/prebuilt/" \ -O \ "$repo/releases/download/${TAG}/simplex-chat-ubuntu-${os_url}-x86-64" @@ -87,7 +87,7 @@ cd "$init_dir" # Final stage: compare hashes # Path to binaries -path_bin="$init_dir/$TAG" +path_bin="$init_dir/$TAG-$repo_name" # Assume everything is okay for now bad=0 @@ -114,7 +114,7 @@ done # If everything is still okay, compute checksums file if [ "$bad" = 0 ]; then - sha256sum "$path_bin"/from-source/* | sed -e "s|$PWD/||g" -e 's|from-source/||g' > "$path_bin/_sha256sums" + sha256sum "$path_bin"/from-source/* | sed -e "s|$PWD/||g" -e 's|from-source/||g' -e "s|-$repo_name||g" > "$path_bin/_sha256sums" printf 'Checksums computed - %s\n' "$path_bin/_sha256sums" fi From 3257b60b703adc047e4fe1ad3e7aac55b7626612 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 23 Apr 2025 13:27:58 +0100 Subject: [PATCH 517/567] core: 6.3.3.1 --- simplex-chat.cabal | 2 +- src/Simplex/Chat/Remote.hs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/simplex-chat.cabal b/simplex-chat.cabal index a01345a910..291083368e 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.3.3.0 +version: 6.3.3.1 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index 8c87851af7..d971656a26 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -75,11 +75,11 @@ remoteFilesFolder = "simplex_v1_files" -- when acting as host minRemoteCtrlVersion :: AppVersion -minRemoteCtrlVersion = AppVersion [6, 3, 3, 0] +minRemoteCtrlVersion = AppVersion [6, 3, 3, 1] -- when acting as controller minRemoteHostVersion :: AppVersion -minRemoteHostVersion = AppVersion [6, 3, 3, 0] +minRemoteHostVersion = AppVersion [6, 3, 3, 1] currentAppVersion :: AppVersion currentAppVersion = AppVersion SC.version From 83b3d631f5e529d87a6b7ebac0bb73d8578c577e Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 23 Apr 2025 18:14:43 +0100 Subject: [PATCH 518/567] 6.3.3: ios 273, android 285, desktop 99 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 56 +++++++++++----------- apps/multiplatform/gradle.properties | 8 ++-- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 096d65cd64..5346e706a2 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -174,8 +174,8 @@ 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; }; 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; }; - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.2.0-m8oPCo81q3CiwhMXvT868-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.2.0-m8oPCo81q3CiwhMXvT868-ghc9.6.3.a */; }; - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.2.0-m8oPCo81q3CiwhMXvT868.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.2.0-m8oPCo81q3CiwhMXvT868.a */; }; + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.3.1-5KR5yzeCZIzIubYi5BDCKe-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.3.1-5KR5yzeCZIzIubYi5BDCKe-ghc9.6.3.a */; }; + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.3.1-5KR5yzeCZIzIubYi5BDCKe.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.3.1-5KR5yzeCZIzIubYi5BDCKe.a */; }; 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; @@ -531,8 +531,8 @@ 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.2.0-m8oPCo81q3CiwhMXvT868-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.2.0-m8oPCo81q3CiwhMXvT868-ghc9.6.3.a"; sourceTree = ""; }; - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.2.0-m8oPCo81q3CiwhMXvT868.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.2.0-m8oPCo81q3CiwhMXvT868.a"; sourceTree = ""; }; + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.3.1-5KR5yzeCZIzIubYi5BDCKe-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.3.1-5KR5yzeCZIzIubYi5BDCKe-ghc9.6.3.a"; sourceTree = ""; }; + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.3.1-5KR5yzeCZIzIubYi5BDCKe.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.3.1-5KR5yzeCZIzIubYi5BDCKe.a"; sourceTree = ""; }; 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; @@ -688,8 +688,8 @@ 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */, 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */, 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */, - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.2.0-m8oPCo81q3CiwhMXvT868-ghc9.6.3.a in Frameworks */, - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.2.0-m8oPCo81q3CiwhMXvT868.a in Frameworks */, + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.3.1-5KR5yzeCZIzIubYi5BDCKe-ghc9.6.3.a in Frameworks */, + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.3.1-5KR5yzeCZIzIubYi5BDCKe.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -774,8 +774,8 @@ 64C829992D54AEEE006B9E89 /* libffi.a */, 64C829982D54AEED006B9E89 /* libgmp.a */, 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */, - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.2.0-m8oPCo81q3CiwhMXvT868-ghc9.6.3.a */, - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.2.0-m8oPCo81q3CiwhMXvT868.a */, + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.3.1-5KR5yzeCZIzIubYi5BDCKe-ghc9.6.3.a */, + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.3.1-5KR5yzeCZIzIubYi5BDCKe.a */, ); path = Libraries; sourceTree = ""; @@ -1963,7 +1963,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 272; + CURRENT_PROJECT_VERSION = 273; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1988,7 +1988,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES_THIN; - MARKETING_VERSION = 6.3.2; + MARKETING_VERSION = 6.3.3; OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000"; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; @@ -2013,7 +2013,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 272; + CURRENT_PROJECT_VERSION = 273; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2038,7 +2038,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.2; + MARKETING_VERSION = 6.3.3; OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000"; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; @@ -2055,11 +2055,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 272; + CURRENT_PROJECT_VERSION = 273; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.3.2; + MARKETING_VERSION = 6.3.3; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2075,11 +2075,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 272; + CURRENT_PROJECT_VERSION = 273; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.3.2; + MARKETING_VERSION = 6.3.3; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2100,7 +2100,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 272; + CURRENT_PROJECT_VERSION = 273; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2115,7 +2115,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.2; + MARKETING_VERSION = 6.3.3; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2137,7 +2137,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 272; + CURRENT_PROJECT_VERSION = 273; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2152,7 +2152,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.2; + MARKETING_VERSION = 6.3.3; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2174,7 +2174,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 272; + CURRENT_PROJECT_VERSION = 273; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2200,7 +2200,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.2; + MARKETING_VERSION = 6.3.3; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2225,7 +2225,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 272; + CURRENT_PROJECT_VERSION = 273; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2251,7 +2251,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.2; + MARKETING_VERSION = 6.3.3; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2276,7 +2276,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 272; + CURRENT_PROJECT_VERSION = 273; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2291,7 +2291,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.3.2; + MARKETING_VERSION = 6.3.3; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2310,7 +2310,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 272; + CURRENT_PROJECT_VERSION = 273; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2325,7 +2325,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.3.2; + MARKETING_VERSION = 6.3.3; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 1e320972be..2dc5be4210 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -24,11 +24,11 @@ android.nonTransitiveRClass=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.3.2 -android.version_code=283 +android.version_name=6.3.3 +android.version_code=285 -desktop.version_name=6.3.2 -desktop.version_code=98 +desktop.version_name=6.3.3 +desktop.version_code=99 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From 36252339315cffcf8f2e271e0dfbf5c67d2e7d2d Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Thu, 24 Apr 2025 08:11:43 +0000 Subject: [PATCH 519/567] flatpak: update metainfo (#5846) --- .../flatpak/chat.simplex.simplex.metainfo.xml | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/scripts/flatpak/chat.simplex.simplex.metainfo.xml b/scripts/flatpak/chat.simplex.simplex.metainfo.xml index 823b8562fc..82987e211a 100644 --- a/scripts/flatpak/chat.simplex.simplex.metainfo.xml +++ b/scripts/flatpak/chat.simplex.simplex.metainfo.xml @@ -38,6 +38,29 @@
    + + https://simplex.chat/blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.html + +

    New in v6.3.1-3:

    +
      +
    • support for connecting via short connection links.
    • +
    • fix related to backward/forward compatibility of the app in some rare cases.
    • +
    • scrolling/navigation improvements.
    • +
    • faster onboarding (conditions and operators are combined to one screen).
    • +
    +

    New in v6.3.0:

    +
      +
    • Mention members and get notified when mentioned.
    • +
    • Send private reports to moderators.
    • +
    • Delete, block and change role for multiple members at once
    • +
    • Faster sending messages and faster deletion.
    • +
    • Organize chats into lists to keep track of what's important.
    • +
    • Jump to found and forwarded messages.
    • +
    • Private media file names.
    • +
    • Message expiration in chats.
    • +
    +
    +
    https://simplex.chat/blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.html From 623a46e41853902235d533e25ca2e7ddb2ae2da8 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 25 Apr 2025 11:16:41 +0100 Subject: [PATCH 520/567] website: new languages, update some texts (#5849) * website: new languages, update some texts * update --- website/langs/ar.json | 5 +- website/langs/bg.json | 2 +- website/langs/cs.json | 9 +- website/langs/de.json | 7 +- website/langs/en.json | 49 ++- website/langs/es.json | 9 +- website/langs/fi.json | 7 +- website/langs/fr.json | 9 +- website/langs/he.json | 5 +- website/langs/hu.json | 7 +- website/langs/it.json | 7 +- website/langs/ja.json | 5 +- website/langs/nl.json | 9 +- website/langs/pl.json | 7 +- website/langs/pt_BR.json | 7 +- website/langs/ru.json | 285 +++++++++--------- website/langs/uk.json | 7 +- website/langs/zh_Hans.json | 5 +- website/src/_data/languages.json | 25 +- .../_includes/sections/simplex_unique.html | 2 +- website/src/img/flags/fi.svg | 5 + website/src/img/flags/hu.svg | 7 + website/src/img/flags/il.svg | 14 + 23 files changed, 261 insertions(+), 233 deletions(-) create mode 100644 website/src/img/flags/fi.svg create mode 100644 website/src/img/flags/hu.svg create mode 100644 website/src/img/flags/il.svg diff --git a/website/langs/ar.json b/website/langs/ar.json index f257c1c747..fd759d5491 100644 --- a/website/langs/ar.json +++ b/website/langs/ar.json @@ -31,7 +31,7 @@ "simplex-explained-tab-2-p-1": "لكل اتصال، تستخدم قائمتي انتظار منفصلتين للمُراسلة لإرسال واستلام الرسائل عبر خوادم مختلفة.", "simplex-explained-tab-2-p-2": "تقوم الخوادم بتمرير الرسائل في اتجاه واحد فقط، دون الحصول على الصورة الكاملة لمُحادثات المستخدم أو اتصالاته.", "simplex-explained-tab-3-p-1": "تحتوي الخوادم على بيانات اعتماد مجهولة منفصلة لكل قائمة انتظار، ولا تعرف المستخدمين الذين ينتمون إليهم.", - "copyright-label": "مشروع مفتوح المصدر © SimpleX 2020-2024", + "copyright-label": "مشروع مفتوح المصدر © SimpleX 2020-2025", "simplex-chat-protocol": "بروتوكول دردشة SimpleX", "developers": "المطورين", "hero-subheader": "أول نظام مُراسلة
    دون معرّفات مُستخدم", @@ -133,8 +133,7 @@ "sign-up-to-receive-our-updates": "اشترك للحصول على آخر مستجداتنا", "enter-your-email-address": "أدخل عنوان بريدك الإلكتروني", "get-simplex": "احصل على تطبيق سطح المكتب SimpleX", - "why-simplex-is": "لماذا SimpleX", - "unique": "فريد من نوعه", + "why-simplex-is-unique": "لماذا SimpleX فريد من نوعه", "learn-more": "اقرأ أكثر", "more-info": "معلومات أكثر", "hide-info": "معلومات أقل", diff --git a/website/langs/bg.json b/website/langs/bg.json index 963f850e53..21fb93c2cb 100644 --- a/website/langs/bg.json +++ b/website/langs/bg.json @@ -21,7 +21,7 @@ "smp-protocol": "СМП Протокол", "chat-protocol": "Чат протокол", "donate": "Дарете", - "copyright-label": "© 2020-2024 SimpleX | Проект с отворен код", + "copyright-label": "© 2020-2025 SimpleX | Проект с отворен код", "simplex-chat-protocol": "SimpleX Чат протокол", "terminal-cli": "Системна конзола", "terms-and-privacy-policy": "Условия и политика за поверителност", diff --git a/website/langs/cs.json b/website/langs/cs.json index 5d7727ba0a..47ab2c99d0 100644 --- a/website/langs/cs.json +++ b/website/langs/cs.json @@ -14,7 +14,7 @@ "features": "Funkce", "why-simplex": "Proč SimpleX", "simplex-privacy": "SimpleX soukromí", - "simplex-explained": "Simplex vysvětlení", + "simplex-explained": "SimpleX vysvětlení", "simplex-explained-tab-1-text": "1. Co vidí uživatel", "simplex-explained-tab-2-text": "2. Jak to funguje", "simplex-explained-tab-3-text": "3. Co vidí servery", @@ -25,7 +25,7 @@ "smp-protocol": "SMP protokol", "chat-protocol": "Chat protokol", "donate": "Darovat", - "copyright-label": "© 2020-2024 SimpleX | Projekt s otevřeným zdrojovým kódem", + "copyright-label": "© 2020-2025 SimpleX | Projekt s otevřeným zdrojovým kódem", "simplex-chat-protocol": "SimpleX Chat protokol", "terminal-cli": "Terminálové rozhraní příkazového řádku", "terms-and-privacy-policy": "Ochrana soukromí", @@ -115,8 +115,7 @@ "sign-up-to-receive-our-updates": "Přihlaste se k odběru novinek", "enter-your-email-address": "vložte svou e-mailovou adresu", "get-simplex": "Získat SimpleX desktop app", - "why-simplex-is": "Proč je SimpleX", - "unique": "jedinečný", + "why-simplex-is-unique": "Proč je SimpleX jedinečný", "learn-more": "Další informace", "more-info": "Více informací", "hide-info": "Skrýt informace", @@ -180,7 +179,7 @@ "privacy-matters-overlay-card-1-p-2": "Internetoví prodejci vědí, že lidé s nižšími příjmy častěji provádějí urgentní nákupy, takže mohou účtovat vyšší ceny nebo odebírat slevy.", "privacy-matters-overlay-card-1-p-4": "Platforma SimpleX chrání soukromí vašich připojení lépe než jakákoli jiná alternativa a plně zabraňuje tomu, aby byl váš sociální graf dostupný všem společnostem nebo organizacím. I když lidé používají servery poskytované SimpleX Chat, neznáme počet uživatelů ani jejich připojení.", "privacy-matters-overlay-card-2-p-2": "Chcete-li být objektivní a činit nezávislá rozhodnutí, musíte mít svůj informační prostor pod kontrolou. Je to možné pouze v případě, že používáte soukromou komunikační platformu, která nemá přístup k vašemu sociálnímu grafu.", - "simplex-unique-overlay-card-1-p-2": "K doručování zpráv SimpleX používá párové anonymní adresy jednosměrných front zpráv, oddělených pro přijaté a odeslané zprávy, obvykle přes různé servery. Používání SimpleX je jako mít jinou “ vypalovačku” e-mail nebo telefon pro každý kontakt a žádné potíže s jejich správou.", + "simplex-unique-overlay-card-1-p-2": "K doručování zpráv SimpleX používá párové anonymní adresy jednosměrných front zpráv, oddělených pro přijaté a odeslané zprávy, obvykle přes různé servery.", "privacy-matters-overlay-card-3-p-2": "Jedním z nejvíce šokujících příběhů je zkušenost Mohamedoua Oulda Salahiho popsaná v jeho pamětech a zobrazená v Mauritánském filmu. Byl umístěn do tábora na Guantánamu bez soudu a byl tam 15 let mučen po telefonátu svému příbuznému v Afghánistánu pro podezření z účasti na útocích z 11. září, i když předchozích 10 let žil v Německu.", "simplex-unique-overlay-card-1-p-1": "Na rozdíl od jiných platforem pro zasílání zpráv nemá SimpleX žádné identifikátory přiřazené uživatelům. Nespoléhá se na telefonní čísla, adresy založené na doméně (jako je e-mail nebo XMPP), uživatelská jména, veřejné klíče nebo dokonce náhodná čísla k identifikaci svých uživatelů —. Nevíme', kolik lidí používá naše servery SimpleX.", "invitation-hero-header": "Byl vám zaslán odkaz pro připojení na SimpleX Chat", diff --git a/website/langs/de.json b/website/langs/de.json index 00c34478e7..f2ec261ab7 100644 --- a/website/langs/de.json +++ b/website/langs/de.json @@ -21,7 +21,7 @@ "smp-protocol": "SMP-Protokoll", "chat-bot-example": "Beispiel für einen Chatbot", "donate": "Spenden", - "copyright-label": "© 2020-2024 SimpleX | Open-Source-Projekt", + "copyright-label": "© 2020-2025 SimpleX | Open-Source-Projekt", "chat-protocol": "Chat-Protokoll", "simplex-chat-protocol": "SimpleX-Chat-Protokoll", "terminal-cli": "Terminal-Kommandozeilen-Schnittstelle", @@ -175,11 +175,10 @@ "comparison-section-list-point-7": "P2P-Netzwerke haben entweder eine zentrale Verwaltung oder das gesamte Netzwerk kann kompromittiert werden", "see-here": "Siehe hier", "simplex-unique-card-4-p-1": "Das SimpleX-Netzwerk ist vollständig dezentralisiert und unabhängig von Kryptowährungen oder anderen Plattformen außer dem Internet.", - "unique": "einmalig", "privacy-matters-overlay-card-2-p-1": "Vor nicht allzu langer Zeit beobachteten wir, wie große Wahlen von einem angesehenen Beratungsunternehmen manipuliert wurden, welches unsere sozialen Graphen nutzte, um unsere Sicht auf die reale Welt zu verzerren und unsere Stimmen zu manipulieren.", "privacy-matters-overlay-card-3-p-2": "Eine der schockierendsten Geschichten ist die Erfahrung von Mohamedou Ould Salahi, welche in seinen Memoiren beschrieben und im Film „The Mauritanian“ gezeigt wird. Er kam nach einem Anruf bei seinen Verwandten in Afghanistan und ohne Gerichtsverfahren in das Guantanamo-Lager und wurde dort einige Jahre lang gefoltert, weil er verdächtigt wurde, an den 9/11-Angriffen beteiligt gewesen zu sein, obwohl er die vorhergehenden 10 Jahre in Deutschland gelebt hatte.", "simplex-unique-overlay-card-1-p-1": "Im Gegensatz zu anderen Nachrichten-Plattformen weist SimpleX den Benutzern keine Kennungen zu. Es verlässt sich nicht auf Telefonnummern, domänenbasierte Adressen (wie E-Mail oder XMPP), Benutzernamen, öffentliche Schlüssel oder sogar Zufallszahlen, um seine Benutzer zu identifizieren — Wir wissen nicht, wie viele Personen unsere SimpleX-Server verwenden.", - "simplex-unique-overlay-card-1-p-2": "Um Nachrichten auszuliefern nutzt SimpleX paarweise anonyme Adressen aus unidirektionalen Nachrichten-Warteschlangen, die für empfangene und gesendete Nachrichten separiert sind und gewöhnlich über verschiedene Server gesendet werden. Die Nutzung von SimpleX entspricht der Nutzung von unterschiedlichen Mailservern oder Telefonen für jeden einzelnen Kontakt und vermeidet dabei eine mühsame Verwaltung.", + "simplex-unique-overlay-card-1-p-2": "Um Nachrichten auszuliefern nutzt SimpleX paarweise anonyme Adressen aus unidirektionalen Nachrichten-Warteschlangen, die für empfangene und gesendete Nachrichten separiert sind und gewöhnlich über verschiedene Server gesendet werden.", "simplex-network-overlay-card-1-li-5": "Alle bekannten P2P-Netzwerke können anfällig für Sybil-Angriffe sein, da jeder Knoten ermittelbar ist und das Netzwerk als Ganzes funktioniert. Bekannte Maßnahmen zur Verhinderung erfordern entweder eine zentralisierte Komponente oder einen teuren Ausführungsnachweis. Das SimpleX-Netzwerk bietet keine Ermittlung der Server, ist fragmentiert und arbeitet mit mehreren isolierten Subnetzwerken, wodurch netzwerkweite Angriffe unmöglich werden.", "simplex-network-overlay-card-1-li-3": "P2P löst nicht das Problem des MITM-Angriffs und die meisten bestehenden Implementierungen nutzen für den initialen Schlüsselaustausch keine Out-of-Band-Nachrichten. Im Gegensatz hierzu nutzt SimpleX für den initialen Schlüsselaustausch Out-of-Band-Nachrichten oder zum Teil schon bestehende sichere und vertrauenswürdige Verbindungen.", "tap-the-connect-button-in-the-app": "Drücken Sie die „Verbinden“-Taste in der Applikation", @@ -206,7 +205,7 @@ "simplex-unique-overlay-card-2-p-2": "Auch wenn die optionale Benutzeradresse zum Versenden von Spam-Kontaktanfragen verwendet werden kann, können Sie sie ändern oder ganz löschen, ohne dass Ihre Verbindungen verloren gehen.", "simplex-unique-overlay-card-4-p-2": "Die SimpleX-Plattform verwendet ein offenes Protokoll und bietet ein SDK zur Erstellung von Chatbots an, welches die Erstellung von Diensten ermöglicht, mit denen Nutzer über SimpleX-Chat interagieren können — Wir sind gespannt, welche SimpleX-Dienste Sie erstellen können.", "simplex-unique-card-4-p-2": "Sie können SimpleX mit Ihren eigenen Servern oder mit den von uns zur Verfügung gestellten Servern verwenden — und sich trotzdem mit jedem Benutzer verbinden.", - "why-simplex-is": "Warum ist SimpleX", + "why-simplex-is-unique": "Warum ist SimpleX einmalig", "contact-hero-p-1": "Die öffentlichen Schlüssel und die Adresse der Nachrichtenwarteschlange in diesem Link werden NICHT über das Netzwerk gesendet, wenn Sie diese Seite aufrufen — sie sind in dem Hash-Fragment der Link-URL enthalten.", "if-you-already-installed-simplex-chat-for-the-terminal": "Wenn Sie SimpleX schon für das Terminal installiert haben", "simplex-network-3-desc": "Die Server stellen unidirektionale Warteschlangen zur Verfügung, um die Benutzer miteinander zu verbinden. Sie haben aber keinen Einblick in den Verbindungs-Graphen des Netzwerks — Diesen haben nur die Benutzer selbst.", diff --git a/website/langs/en.json b/website/langs/en.json index e57b3375de..f9691e2594 100644 --- a/website/langs/en.json +++ b/website/langs/en.json @@ -7,21 +7,21 @@ "why-simplex": "Why SimpleX", "simplex-privacy": "SimpleX privacy", "simplex-network": "SimpleX network", - "simplex-explained": "Simplex explained", + "simplex-explained": "SimpleX explained", "simplex-explained-tab-1-text": "1. What users experience", "simplex-explained-tab-2-text": "2. How does it work", "simplex-explained-tab-3-text": "3. What servers see", "simplex-explained-tab-1-p-1": "You can create contacts and groups, and have two-way conversations, as in any other messenger.", "simplex-explained-tab-1-p-2": "How can it work with unidirectional queues and without user profile identifiers?", "simplex-explained-tab-2-p-1": "For each connection you use two separate messaging queues to send and receive messages via different servers.", - "simplex-explained-tab-2-p-2": "Servers only pass messages one way, without having the full picture of user's conversation or connections.", + "simplex-explained-tab-2-p-2": "Servers only pass messages one way, without having the full picture of user's conversations or connections.", "simplex-explained-tab-3-p-1": "The servers have separate anonymous credentials for each queue, and do not know which users they belong to.", "simplex-explained-tab-3-p-2": "Users can further improve metadata privacy by using Tor to access servers, preventing corellation by IP address.", "chat-bot-example": "Chat bot example", "smp-protocol": "SMP protocol", "chat-protocol": "Chat protocol", "donate": "Donate", - "copyright-label": "© 2020-2024 SimpleX | Open-Source Project", + "copyright-label": "© 2020-2025 SimpleX | Open-Source Project", "simplex-chat-protocol": "SimpleX Chat protocol", "terminal-cli": "Terminal CLI", "terms-and-privacy-policy": "Privacy Policy", @@ -65,7 +65,7 @@ "simplex-private-card-4-point-2": "To use SimpleX via Tor please install Orbot app and enable SOCKS5 proxy (or VPN on iOS).", "simplex-private-card-5-point-1": "SimpleX uses content padding for each encryption layer to frustrate message size attacks.", "simplex-private-card-5-point-2": "It makes messages of different sizes look the same to the servers and network observers.", - "simplex-private-card-6-point-1": "Many communication platforms are vulnerable to MITM attacks by servers or network providers.", + "simplex-private-card-6-point-1": "Many communication networks are vulnerable to MITM attacks by servers or network providers.", "simplex-private-card-6-point-2": "To prevent it SimpleX apps pass one-time keys out-of-band, when you share an address as a link or a QR code.", "simplex-private-card-7-point-1": "To guarantee integrity the messages are sequentially numbered and include the hash of the previous message.", "simplex-private-card-7-point-2": "If any message is added, removed or changed the recipient will be alerted.", @@ -92,7 +92,7 @@ "simplex-unique-4-title": "You own SimpleX network", "simplex-unique-4-overlay-1-title": "Fully decentralised — users own the SimpleX network", "hero-overlay-card-1-p-1": "Many users asked: if SimpleX has no user identifiers, how can it know where to deliver messages?", - "hero-overlay-card-1-p-2": "To deliver messages, instead of user IDs used by all other platforms, SimpleX uses temporary anonymous pairwise identifiers of message queues, separate for each of your connections — there are no long term identifiers.", + "hero-overlay-card-1-p-2": "To deliver messages, instead of user IDs used by all other networks, SimpleX uses temporary anonymous pairwise identifiers of message queues, separate for each of your connections — there are no long term identifiers.", "hero-overlay-card-1-p-3": "You define which server(s) to use to receive the messages, your contacts — the servers you use to send the messages to them. Every conversation is likely to use two different servers.", "hero-overlay-card-1-p-4": "This design prevents leaking any users' metadata on the application level. To further improve privacy and protect your IP address you can connect to messaging servers via Tor.", "hero-overlay-card-1-p-5": "Only client devices store user profiles, contacts and groups; the messages are sent with 2-layer end-to-end encryption.", @@ -108,38 +108,38 @@ "simplex-network-overlay-card-1-li-1": "P2P networks rely on some variant of DHT to route messages. DHT designs have to balance delivery guarantee and latency. SimpleX has both better delivery guarantee and lower latency than P2P, because the message can be redundantly passed via several servers in parallel, using the servers chosen by the recipient. In P2P networks the message is passed through O(log N) nodes sequentially, using nodes chosen by the algorithm.", "simplex-network-overlay-card-1-li-2": "SimpleX design, unlike most P2P networks, has no global user identifiers of any kind, even temporary, and only uses temporary pairwise identifiers, providing better anonymity and metadata protection.", "simplex-network-overlay-card-1-li-3": "P2P does not solve MITM attack problem, and most existing implementations do not use out-of-band messages for the initial key exchange. SimpleX uses out-of-band messages or, in some cases, pre-existing secure and trusted connections for the initial key exchange.", - "simplex-network-overlay-card-1-li-4": "P2P implementations can be blocked by some Internet providers (like BitTorrent). SimpleX is transport agnostic - it can work over standard web protocols, e.g. WebSockets.", + "simplex-network-overlay-card-1-li-4": "P2P implementations can be blocked by some Internet providers (like BitTorrent). SimpleX is transport agnostic — it can work over standard web protocols, e.g. WebSockets.", "simplex-network-overlay-card-1-li-5": "All known P2P networks may be vulnerable to Sybil attack, because each node is discoverable, and the network operates as a whole. Known measures to mitigate it require either a centralized component or expensive proof of work. SimpleX network has no server discoverability, it is fragmented and operates as multiple isolated sub-networks, making network-wide attacks impossible.", "simplex-network-overlay-card-1-li-6": "P2P networks may be vulnerable to DRDoS attack, when the clients can rebroadcast and amplify traffic, resulting in network-wide denial of service. SimpleX clients only relay traffic from known connection and cannot be used by an attacker to amplify the traffic in the whole network.", "privacy-matters-overlay-card-1-p-1": "Many large companies use information about who you are connected with to estimate your income, sell you the products you don't really need, and to determine the prices.", "privacy-matters-overlay-card-1-p-2": "Online retailers know that people with lower incomes are more likely to make urgent purchases, so they may charge higher prices or remove discounts.", "privacy-matters-overlay-card-1-p-3": "Some financial and insurance companies use social graphs to determine interest rates and premiums. It often makes people with lower incomes pay more — it is known as 'poverty premium'.", - "privacy-matters-overlay-card-1-p-4": "SimpleX platform protects the privacy of your connections better than any alternative, fully preventing your social graph becoming available to any companies or organizations. Even when people use servers provided by SimpleX Chat, we do not know the number of users or their connections.", + "privacy-matters-overlay-card-1-p-4": "SimpleX network protects the privacy of your connections better than any alternative, fully preventing your social graph becoming available to any companies or organizations. Even when people use servers preconfigured in SimpleX Chat apps, server operators do not know the number of users or their connections.", "privacy-matters-overlay-card-2-p-1": "Not so long ago we observed the major elections being manipulated by a reputable consulting company that used our social graphs to distort our view of the real world and manipulate our votes.", - "privacy-matters-overlay-card-2-p-2": "To be objective and to make independent decisions you need to be in control of your information space. It is only possible if you use private communication platform that does not have access to your social graph.", - "privacy-matters-overlay-card-2-p-3": "SimpleX is the first platform that doesn't have any user identifiers by design, in this way protecting your connections graph better than any known alternative.", + "privacy-matters-overlay-card-2-p-2": "To be objective and to make independent decisions you need to be in control of your information space. It is only possible if you use private communication network that does not have access to your social graph.", + "privacy-matters-overlay-card-2-p-3": "SimpleX is the first network that doesn't have any user identifiers by design, in this way protecting your connections graph better than any known alternative.", "privacy-matters-overlay-card-3-p-1": "Everyone should care about privacy and security of their communications — harmless conversations can put you in danger, even if you have nothing to hide.", "privacy-matters-overlay-card-3-p-2": "One of the most shocking stories is the experience of Mohamedou Ould Salahi described in his memoir and shown in The Mauritanian movie. He was put into Guantanamo camp, without trial, and was tortured there for 15 years after a phone call to his relative in Afghanistan, under suspicion of being involved in 9/11 attacks, even though he lived in Germany for the previous 10 years.", "privacy-matters-overlay-card-3-p-3": "Ordinary people get arrested for what they share online, even via their 'anonymous' accounts, even in democratic countries.", "privacy-matters-overlay-card-3-p-4": "It is not enough to use an end-to-end encrypted messenger, we all should use the messengers that protect the privacy of our personal networks — who we are connected with.", - "simplex-unique-overlay-card-1-p-1": "Unlike other messaging platforms, SimpleX has no identifiers assigned to the users. It does not rely on phone numbers, domain-based addresses (like email or XMPP), usernames, public keys or even random numbers to identify its users — we don't know how many people use our SimpleX servers.", - "simplex-unique-overlay-card-1-p-2": "To deliver messages SimpleX uses pairwise anonymous addresses of unidirectional message queues, separate for received and sent messages, usually via different servers. Using SimpleX is like having a different “burner” email or phone for each contact, and no hassle to manage them.", - "simplex-unique-overlay-card-1-p-3": "This design protects the privacy of who you are communicating with, hiding it from SimpleX platform servers and from any observers. To hide your IP address from the servers, you can connect to SimpleX servers via Tor.", - "simplex-unique-overlay-card-2-p-1": "Because you have no identifier on the SimpleX platform, nobody can contact you unless you share a one-time or temporary user address, as a QR code or a link.", + "simplex-unique-overlay-card-1-p-1": "Unlike other messaging networks, SimpleX has no identifiers assigned to the users. It does not rely on phone numbers, domain-based addresses (like email or XMPP), usernames, public keys or even random numbers to identify its users — SimpleX server operators don't know how many people use their servers.", + "simplex-unique-overlay-card-1-p-2": "To deliver messages SimpleX uses pairwise anonymous addresses of unidirectional message queues, separate for received and sent messages, usually via different servers.", + "simplex-unique-overlay-card-1-p-3": "This design protects the privacy of who you are communicating with, hiding it from SimpleX network servers and from any observers. To hide your IP address from the servers, you can connect to SimpleX servers via Tor.", + "simplex-unique-overlay-card-2-p-1": "Because you have no identifier on the SimpleX network, nobody can contact you unless you share a one-time or temporary user address, as a QR code or a link.", "simplex-unique-overlay-card-2-p-2": "Even with the optional user address, while it can be used to send spam contact requests, you can change or completely delete it without losing any of your connections.", "simplex-unique-overlay-card-3-p-1": "SimpleX Chat stores all user data only on client devices using a portable encrypted database format that can be exported and transferred to any supported device.", "simplex-unique-overlay-card-3-p-2": "The end-to-end encrypted messages are held temporarily on SimpleX relay servers until received, then they are permanently deleted.", "simplex-unique-overlay-card-3-p-3": "Unlike federated networks servers (email, XMPP or Matrix), SimpleX servers don't store user accounts, they only relay messages, protecting the privacy of both parties.", "simplex-unique-overlay-card-3-p-4": "There are no identifiers or ciphertext in common between sent and received server traffic — if anybody is observing it, they cannot easily determine who communicates with whom, even if TLS is compromised.", - "simplex-unique-overlay-card-4-p-1": "You can use SimpleX with your own servers and still communicate with people who use the pre-configured servers provided by us.", - "simplex-unique-overlay-card-4-p-2": "SimpleX platform uses an open protocol and provides SDK to create chat bots, allowing implementation of services that users can interact with via SimpleX Chat apps — we're really looking forward to see what SimpleX services you can build.", - "simplex-unique-overlay-card-4-p-3": "If you are considering developing for the SimpleX platform, for example, the chat bot for SimpleX app users, or the integration of the SimpleX Chat library into your mobile apps, please get in touch for any advice and support.", - "simplex-unique-card-1-p-1": "SimpleX protects the privacy of your profile, contacts and metadata, hiding it from SimpleX platform servers and any observers.", - "simplex-unique-card-1-p-2": "Unlike any other existing messaging platform, SimpleX has no identifiers assigned to the users — not even random numbers.", - "simplex-unique-card-2-p-1": "Because you have no identifier or fixed address on the SimpleX platform, nobody can contact you unless you share a one-time or temporary user address, as a QR code or a link.", + "simplex-unique-overlay-card-4-p-1": "You can use SimpleX with your own servers and still communicate with people who use the servers preconfigured in the apps.", + "simplex-unique-overlay-card-4-p-2": "SimpleX network uses an open protocol and provides SDK to create chat bots, allowing implementation of services that users can interact with via SimpleX Chat apps — we're really looking forward to see what SimpleX services you will build.", + "simplex-unique-overlay-card-4-p-3": "If you are considering developing for the SimpleX network, for example, the chat bot for SimpleX app users, or the integration of the SimpleX Chat library into your mobile apps, please get in touch for any advice and support.", + "simplex-unique-card-1-p-1": "SimpleX protects the privacy of your profile, contacts and metadata, hiding it from SimpleX network servers and any observers.", + "simplex-unique-card-1-p-2": "Unlike any other existing messaging network, SimpleX has no identifiers assigned to the users — not even random numbers.", + "simplex-unique-card-2-p-1": "Because you have no identifier or fixed address on the SimpleX network, nobody can contact you unless you share a one-time or temporary user address, as a QR code or a link.", "simplex-unique-card-3-p-1": "SimpleX stores all user data on client devices in a portable encrypted database format — it can be transferred to another device.", "simplex-unique-card-3-p-2": "The end-to-end encrypted messages are held temporarily on SimpleX relay servers until received, then they are permanently deleted.", - "simplex-unique-card-4-p-1": "The SimpleX network is fully decentralised and independent of any crypto-currency or any other platform, other than the Internet.", + "simplex-unique-card-4-p-1": "The SimpleX network is fully decentralised and independent of any crypto-currency or any other network, other than the Internet.", "simplex-unique-card-4-p-2": "You can use SimpleX with your own servers or with the servers provided by us — and still connect to any user.", "join": "Join", "we-invite-you-to-join-the-conversation": "We invite you to join the conversation", @@ -149,8 +149,7 @@ "sign-up-to-receive-our-updates": "Sign up to receive our updates", "enter-your-email-address": "Enter your email address", "get-simplex": "Get SimpleX desktop app", - "why-simplex-is": "Why SimpleX is", - "unique": "unique", + "why-simplex-is-unique": "Why SimpleX is unique", "learn-more": "Learn more", "more-info": "More info", "hide-info": "Hide info", @@ -172,7 +171,7 @@ "use-this-command": "Use this command:", "see-simplex-chat": "See SimpleX Chat", "github-repository": "GitHub repository", - "the-instructions--source-code": "the instructions how to download or compile it from the source code.", + "the-instructions--source-code": "for the instructions how to download or compile it from the source code.", "if-you-already-installed-simplex-chat-for-the-terminal": "If you already installed SimpleX Chat for the terminal", "if-you-already-installed": "If you already installed", "simplex-chat-for-the-terminal": "SimpleX Chat for the terminal", @@ -213,7 +212,7 @@ "comparison-section-list-point-4a": "SimpleX relays cannot compromise e2e encryption. Verify security code to mitigate attack on out-of-band channel", "comparison-section-list-point-4": "If operator’s servers are compromised. Verify security code in Signal and some other apps to mitigate it", "comparison-section-list-point-5": "Does not protect users' metadata privacy", - "comparison-section-list-point-6": "While P2P are distributed, they are not federated - they operate as a single network", + "comparison-section-list-point-6": "While P2P are distributed, they are not federated — they operate as a single network", "comparison-section-list-point-7": "P2P networks either have a central authority or the whole network can be compromised", "see-here": "see here", "guide-dropdown-1": "Quick start", @@ -226,7 +225,7 @@ "guide-dropdown-8": "App settings", "guide-dropdown-9": "Making connections", "guide": "Guide", - "docs-dropdown-1": "SimpleX platform", + "docs-dropdown-1": "SimpleX network", "docs-dropdown-2": "Accessing Android files", "docs-dropdown-3": "Accessing chat database", "docs-dropdown-4": "Host SMP Server", diff --git a/website/langs/es.json b/website/langs/es.json index 56055abf67..b3cfc83969 100644 --- a/website/langs/es.json +++ b/website/langs/es.json @@ -10,7 +10,7 @@ "simplex-explained-tab-3-p-2": "El usuario puede mejorar aún más la privacidad de sus metadatos haciendo uso de la red Tor para acceder a los servidores, evitando así la correlación por dirección IP.", "smp-protocol": "Protocolo SMP", "donate": "Donación", - "copyright-label": "© 2020-2024 SimpleX | Proyecto de Código Abierto", + "copyright-label": "© 2020-2025 SimpleX | Proyecto de Código Abierto", "simplex-chat-protocol": "Protocolo de SimpleX Chat", "terms-and-privacy-policy": "Política de Privacidad", "hero-header": "Privacidad redefinida", @@ -40,7 +40,7 @@ "simplex-private-card-7-point-2": "Si se añade, elimina o modifica algún mensaje, el destinatario es avisado.", "simplex-private-card-8-point-1": "Los servidores SimpleX actúan como nodos de mezcla de baja latencia — los mensajes entrantes y salientes siguen un orden diferente.", "simplex-private-card-9-point-1": "Cada cola de mensajes transmite los mensajes en un solo sentido, con las distintas direcciones de envío y recepción.", - "simplex-explained": "Simplex explicado", + "simplex-explained": "SimpleX explicado", "simplex-explained-tab-1-p-1": "Puedes crear contactos y grupos, y mantener conversaciones bidireccionales como en cualquier aplicación de mensajería.", "simplex-explained-tab-1-p-2": "¿Cómo puede funcionar con colas unidireccionales y sin identificadores de usuario?", "simplex-explained-tab-2-p-1": "Por cada conexión se usan dos colas de mensajes separadas para que el envío y recepción se hagan a través de servidores diferentes.", @@ -122,7 +122,7 @@ "privacy-matters-overlay-card-3-p-2": "Una de las historias más estremecedoras es la experiencia de Mohamedou Ould Salahi descrita en sus memorias y plasmada en la película The Mauritanian. Fue internado en la prisión militar de Guantánamo sin juicio previo, donde fue torturado durante 15 años tras una llamada telefónica a su pariente en Afganistán, bajo sospecha de estar implicado en los atentados del 11-S a pesar de que vivía en Alemania desde hacía 10 años.", "privacy-matters-overlay-card-3-p-4": "No basta con usar mensajería cifrada de extremo a extremo, todos deberíamos utilizar las aplicaciones de mensajería que protegen la privacidad de nuestras redes personales y con quién estamos conectados.", "privacy-matters-overlay-card-3-p-3": "Personas corrientes son detenidas por lo que comparten en Internet, incluso a través de sus cuentas \"anónimas\" e incluso en países democráticos.", - "simplex-unique-overlay-card-1-p-2": "Para enviar mensajes, SimpleX utiliza direcciones anónimas por pares de colas de mensajes unidireccionales, separadas para mensajes recibidos y enviados, y normalmente a través de servidores diferentes. Utilizar SimpleX es como tener un correo electrónico “desechable” o un teléfono diferente para cada contacto, pero sin las complicaciones que implicaría gestionarlos.", + "simplex-unique-overlay-card-1-p-2": "Para enviar mensajes, SimpleX utiliza direcciones anónimas por pares de colas de mensajes unidireccionales, separadas para mensajes recibidos y enviados, y normalmente a través de servidores diferentes.", "simplex-unique-overlay-card-3-p-1": "SimpleX Chat almacena todos los datos de usuario únicamente en los dispositivos cliente usando un formato cifrado y portable de la base de datos, la cual puede ser exportada y transferida a cualquier dispositivo compatible.", "simplex-unique-overlay-card-3-p-3": "A diferencia de los servidores de redes federadas (correo electrónico, XMPP o Matrix), los servidores SimpleX no almacenan cuentas de usuario, sólo retransmiten mensajes, protegiendo así la privacidad de ambas partes.", "simplex-unique-overlay-card-4-p-1": "Puede usar SimpleX con sus propios servidores y aún así comunicarte con personas conectadas a los servidores preconfigurados y proporcionados por nosotros.", @@ -138,8 +138,7 @@ "donate-here-to-help-us": "Para ayudarnos haga una donación aquí", "enter-your-email-address": "Escriba su dirección de correo electrónico", "get-simplex": "Obtenga SimpleX desktop app", - "why-simplex-is": "Por qué SimpleX es", - "unique": "único", + "why-simplex-is-unique": "Por qué SimpleX es único", "learn-more": "Descubra más", "more-info": "Más información", "hide-info": "Ocultar información", diff --git a/website/langs/fi.json b/website/langs/fi.json index 68c0d4f1b4..460aa7989b 100644 --- a/website/langs/fi.json +++ b/website/langs/fi.json @@ -112,7 +112,7 @@ "simplex-explained-tab-1-p-1": "Voit luoda yhteyshenkilöitä ja ryhmiä sekä käydä kaksisuuntaisia keskusteluja kuten missä tahansa muussa viestisovelluksessa.", "simplex-explained-tab-3-p-1": "Palvelimilla on erilliset anonyymit tunnistetiedot kullekin jonolle, eivätkä ne tiedä, mille käyttäjille ne kuuluvat.", "donate": "Lahjoita", - "copyright-label": "© 2020-2023 SimpleX | Avoin projekti", + "copyright-label": "© 2020-2025 SimpleX | Avoin projekti", "hero-p-1": "Muissa sovelluksissa on käyttäjätunnuksia: Signal, Matrix, Session, Briar, Jami, Cwtch, jne.
    SimpleX ei käytä niitä, ei edes satunnaisia numeroita.
    Tämä parantaa yksityisyyttäsi radikaalisti.", "simplex-private-1-title": "2 kerrosta päästä päähän salattua viestintää", "simplex-private-2-title": "Lisäkerros palvelimen salaukselle", @@ -168,7 +168,7 @@ "privacy-matters-overlay-card-3-p-3": "Tavalliset ihmiset pidätetään siitä, mitä he jakavat verkossa, jopa 'anonyymien' tiliensä kautta, jopa demokraattisissa maissa.", "privacy-matters-overlay-card-3-p-4": "Ei riitä, että käytät päästä päähän salattua viestintäsovellusta, meidän kaikkien pitäisi käyttää viestintäsovelluksia, jotka suojelevat henkilökohtaisten verkostojemme yksityisyyttä — keiden kanssa olemme yhteydessä.", "simplex-unique-overlay-card-1-p-1": "Toisin kuin muut viestintäalustat, SimpleX:llä ei ole mitään tunnisteita käyttäjille. Se ei luota puhelinnumeroihin, verkkotunnuksiin perustuviin osoitteisiin (kuten sähköposti tai XMPP), käyttäjänimiin, julkisiin avaimiin tai edes satunnaisiin numeroihin tunnistaakseen käyttäjänsä — emme tiedä kuinka monta ihmistä käyttää SimpleX-palvelimiamme.", - "simplex-unique-overlay-card-1-p-2": "Viestien toimittamiseksi SimpleX käyttää parittaisia nimettömiä osoitteita kaksisuuntaisille viestijonoille, jotka ovat erilliset vastaanotetuille ja lähetetyille viesteille, yleensä eri palvelimien kautta. SimpleX:n käyttö on kuin eri “kertakäyttöinen” sähköposti tai puhelin jokaiselle yhteydelle, eikä sinun tarvitse vaivautua niiden hallitsemiseen.", + "simplex-unique-overlay-card-1-p-2": "Viestien toimittamiseksi SimpleX käyttää parittaisia nimettömiä osoitteita kaksisuuntaisille viestijonoille, jotka ovat erilliset vastaanotetuille ja lähetetyille viesteille, yleensä eri palvelimien kautta.", "simplex-unique-overlay-card-1-p-3": "Tämä suunnittelu suojaa sitä, kenen kanssa kommunikoit, piilottamalla sen SimpleX-alustan palvelimilta ja kaikilta havainnoijilta. Piilottaaksesi IP-osoitteesi palvelimilta, voit yhdistää SimpleX-palvelimiin Tor-verkon kautta.", "simplex-unique-overlay-card-2-p-1": "Koska sinulla ei ole tunnistetta SimpleX-alustalla, kukaan ei voi ottaa sinuun yhteyttä, ellei jaa kertakäyttöistä tai väliaikaista käyttäjäosoitetta, kuten QR-koodia tai linkkiä.", "simplex-unique-overlay-card-2-p-2": "Jopa valinnaisen käyttäjäosoitteen kanssa, vaikka sitä voitaisiin käyttää roskapostiyhteyspyyntöjen lähettämiseen, voit vaihtaa sen tai poistaa sen kokonaan menettämättä mitään yhteyksiäsi.", @@ -187,8 +187,7 @@ "sign-up-to-receive-our-updates": "Tilaa päivityksemme", "enter-your-email-address": "Syötä sähköpostiosoitteesi", "get-simplex": "Hanki SimpleX desktop app", - "why-simplex-is": "Miksi SimpleX on", - "unique": "ainutlaatuinen", + "why-simplex-is-unique": "Miksi SimpleX on ainutlaatuinen", "learn-more": "Lue lisää", "more-info": "Lisätietoja", "contact-hero-subheader": "Skannaa QR-koodi SimpleX Chat -sovelluksella puhelimessasi tai tabletissasi.", diff --git a/website/langs/fr.json b/website/langs/fr.json index ba2c4fb00c..efc8d6c9c6 100644 --- a/website/langs/fr.json +++ b/website/langs/fr.json @@ -7,7 +7,7 @@ "why-simplex": "Pourquoi SimpleX", "simplex-privacy": "Confidentialité SimpleX", "simplex-network": "Réseau SimpleX", - "simplex-explained": "Simplex expliqué", + "simplex-explained": "SimpleX expliqué", "simplex-explained-tab-1-text": "1. L'expérience des utilisateurs", "simplex-explained-tab-2-text": "2. Comment ça marche", "simplex-explained-tab-3-text": "3. Ce que voient les serveurs", @@ -21,7 +21,7 @@ "smp-protocol": "Protocole SMP", "chat-protocol": "Protocole de chat", "donate": "Faire un don", - "copyright-label": "© 2020-2024 SimpleX | Projet Open-Source", + "copyright-label": "© 2020-2025 SimpleX | Projet Open-Source", "simplex-chat-protocol": "Protocole SimpleX Chat", "terminal-cli": "Terminal CLI", "terms-and-privacy-policy": "Politique de confidentialité", @@ -118,7 +118,7 @@ "privacy-matters-overlay-card-3-p-3": "Des personnes lambda se font arrêter pour ce qu'elles partagent en ligne, même via leurs comptes \"anonymes\", même dans les pays démocratiques.", "privacy-matters-overlay-card-3-p-4": "Il ne suffit pas d'utiliser une messagerie chiffrée de bout en bout, nous devrions tous utiliser des messageries qui protègent la vie privée de nos réseaux personnels — les personnes avec lesquelles nous sommes connectés.", "simplex-unique-overlay-card-1-p-1": "Contrairement aux autres plateformes de messagerie, SimpleX n'a pas d'identifiant attribué aux utilisateurs. Il ne s'appuie pas sur des numéros de téléphone, des adresses basées sur des domaines (comme l'e-mail ou XMPP), des noms d'utilisateur, des clés publiques ou même des nombres aléatoires pour identifier ses utilisateurs — nous ne savons pas combien de personnes utilisent nos serveurs SimpleX.", - "simplex-unique-overlay-card-1-p-2": "Pour envoyer des messages, SimpleX utilise des adresses anonymes par paires de files d'attente de messages unidirectionnelles, séparées pour les messages reçus et envoyés, généralement via des serveurs différents. Utiliser SimpleX, c'est comme avoir un e-mail ou un téléphone jetable différent pour chaque contact, sans aucune difficulté pour les gérer.", + "simplex-unique-overlay-card-1-p-2": "Pour envoyer des messages, SimpleX utilise des adresses anonymes par paires de files d'attente de messages unidirectionnelles, séparées pour les messages reçus et envoyés, généralement via des serveurs différents.", "simplex-unique-overlay-card-1-p-3": "Cette approche protège la confidentialité des personnes avec lesquelles vous communiquez, en la cachant des serveurs de la plateforme SimpleX et de tout observateur. Pour cacher votre adresse IP aux serveurs, vous pouvez vous connecter aux serveurs SimpleX via Tor.", "simplex-unique-overlay-card-2-p-1": "Parce que vous n'avez pas d'identifiant sur la plateforme SimpleX, personne ne peut vous contacter sauf si vous partagez une adresse d'utilisateur unique ou temporaire, sous forme de code QR ou de lien .", "simplex-unique-overlay-card-2-p-2": "Même avec l'adresse utilisateur facultative, bien qu'elle puisse être utilisée pour envoyer des demandes de contact de spam, vous pouvez la modifier ou la supprimer complètement sans perdre aucune de vos connexions.", @@ -144,8 +144,7 @@ "sign-up-to-receive-our-updates": "Inscrivez-vous pour recevoir nos mises à jour", "enter-your-email-address": "Entrez votre adresse e-mail", "get-simplex": "Obtenir SimpleX desktop app", - "why-simplex-is": "Pourquoi SimpleX est", - "unique": "unique", + "why-simplex-is-unique": "Pourquoi SimpleX est unique", "learn-more": "En savoir plus", "more-info": "Plus d'infos", "hide-info": "Masquer les infos", diff --git a/website/langs/he.json b/website/langs/he.json index 878f0e51b2..4a61999896 100644 --- a/website/langs/he.json +++ b/website/langs/he.json @@ -53,7 +53,7 @@ "smp-protocol": "פרוטוקול SMP", "chat-protocol": "פרוטוקול צ'אט", "donate": "תרומה", - "copyright-label": "© 2020-2024 SimpleX | פרויקט קוד פתוח", + "copyright-label": "© 2020-2025 SimpleX | פרויקט קוד פתוח", "hero-p-1": "לאפליקציות אחרות יש מזהי משתמש: Signal, Matrix, Session, Briar, Jami, Cwtch וכו'.
    ל-SimpleX אין, אפילו לא מספרים אקראיים.
    זה משפר באופן קיצוני את הפרטיות שלך.", "hero-overlay-2-title": "מדוע מזהי משתמש מזיקים לפרטיות?", "feature-6-title": "שיחות שמע ווידאו
    מוצפנות מקצה לקצה", @@ -163,10 +163,9 @@ "simplex-unique-overlay-card-3-p-2": "ההודעות המוצפנות מקצה לקצה מוחזקות באופן זמני בשרתי ממסר של SimpleX עד שמתקבלות, ואז הן נמחקות לצמיתות.", "simplex-unique-overlay-card-4-p-2": "פלטפורמת SimpleX משתמשת בפרוטוקול פתוח ומספקת SDK ליצירת צ'אט בוטים, המאפשר הטמעה של שירותים שמשתמשים יכולים לתקשר איתם באמצעות אפליקציות SimpleX Chat — אנחנו ממש מצפים לראות אילו שירותי SimpleX אתם יכולים לבנות.", "contact-hero-p-2": "עדיין לא הורדתם את ה-SimpleX Chat?", - "why-simplex-is": "מדוע SimpleX הוא", + "why-simplex-is-unique": "מדוע SimpleX הוא ייחודי", "simplex-network-section-header": "רשת SimpleX", "tap-the-connect-button-in-the-app": "הקישו על הלחצן 'התחבר' באפליקציה", - "unique": "ייחודי", "simplex-network-1-overlay-linktext": "בעיות של רשתות P2P", "protocol-2-text": "XMPP, Matrix", "simplex-network-overlay-card-1-li-4": "יישומי P2P יכולים להיחסם על ידי ספקי אינטרנט מסוימים (כמו BitTorrent). SimpleX הוא אגנוסטי לתעבורה - הוא יכול לעבוד על פרוטוקולי אינטרנט סטנדרטיים, למשל WebSockets.", diff --git a/website/langs/hu.json b/website/langs/hu.json index 60eeeafe13..f502291e52 100644 --- a/website/langs/hu.json +++ b/website/langs/hu.json @@ -7,7 +7,7 @@ "why-simplex": "Miért válassza a SimpleXet", "simplex-privacy": "A SimpleX adatvédelme", "simplex-network": "A SimpleX-hálózat", - "simplex-explained": "A Simplex bemutatása", + "simplex-explained": "A SimpleX bemutatása", "simplex-explained-tab-1-text": "1. Felhasználói élmény", "simplex-explained-tab-2-text": "2. Hogyan működik", "simplex-explained-tab-3-text": "3. Mit látnak a kiszolgálók", @@ -113,7 +113,7 @@ "privacy-matters-overlay-card-3-p-3": "Átlagos embereket letartóztatnak azért, amit online megosztanak, még „névtelen” fiókjaikon keresztül is, még demokratikus országokban is.", "privacy-matters-overlay-card-3-p-4": "Nem elég csak egy végpontok között titkosított üzenetváltó-alkalmazást használnunk, mindannyiunknak olyan üzenetváltó-alkalmazásokat kell használnunk, amelyek védik a személyes partnereink magánéletét — akikkel kapcsolatban állunk.", "simplex-unique-overlay-card-1-p-1": "Más üzenetküldő platformoktól eltérően a SimpleX nem rendel azonosítókat a felhasználókhoz. Nem támaszkodik telefonszámokra, tartomány-alapú címekre (mint az e-mail, XMPP vagy a Matrix), felhasználónevekre, nyilvános kulcsokra vagy akár véletlenszerű számokra a felhasználók azonosításához — nem tudjuk, hogy hányan használják a SimpleX-kiszolgálóinkat.", - "simplex-unique-overlay-card-1-p-2": "Az üzenetek kézbesítéséhez a SimpleX az egyirányú üzenet várakoztatást használ páronkénti névtelen címekkel, külön a fogadott és külön az elküldött üzenetek számára, általában különböző kiszolgálókon keresztül. A SimpleX használata olyan, mintha minden egyes kapcsolatnak más-más “eldobható” e-mail-címe vagy telefonja lenne és nem kell ezeket gondosan kezelni.", + "simplex-unique-overlay-card-1-p-2": "Az üzenetek kézbesítéséhez a SimpleX az egyirányú üzenet várakoztatást használ páronkénti névtelen címekkel, külön a fogadott és külön az elküldött üzenetek számára, általában különböző kiszolgálókon keresztül.", "simplex-unique-overlay-card-1-p-3": "Ez a kialakítás megvédi annak titkosságát, hogy kivel kommunikál, elrejtve azt a SimpleX-platform kiszolgálói és a megfigyelők elől. IP-címének a kiszolgálók elől való elrejtéséhez azt teheti meg, hogy Toron keresztül kapcsolódik a SimpleX-kiszolgálókhoz.", "simplex-unique-overlay-card-2-p-1": "Mivel ön nem rendelkezik azonosítóval a SimpleX-platformon, senki sem tud kapcsolatba lépni önnel, hacsak nem oszt meg egy egyszeri vagy ideiglenes felhasználói címet, például QR-kódot vagy hivatkozást.", "simplex-unique-overlay-card-2-p-2": "Még a felhasználói cím használata esetén is, aminek használata nem kötelező – ugyanakkor ez a kéretlen kapcsolatkérelmek küldésére is használható – módosíthatja vagy teljesen törölheti anélkül, hogy elveszítené a meglévő kapcsolatait.", @@ -139,8 +139,7 @@ "sign-up-to-receive-our-updates": "Regisztráljon a hírleveleinkre, hogy ne maradjon le semmiről", "enter-your-email-address": "Adja meg az e-mail-címét", "get-simplex": "A SimpleX számítógép-alkalmazásának letöltése", - "why-simplex-is": "A SimpleX mitől", - "unique": "egyedülálló", + "why-simplex-is-unique": "A SimpleX mitől egyedülálló", "learn-more": "Tudjon meg többet", "more-info": "További információ", "hide-info": "Információ elrejtése", diff --git a/website/langs/it.json b/website/langs/it.json index 502ab6d886..032706ebab 100644 --- a/website/langs/it.json +++ b/website/langs/it.json @@ -10,7 +10,7 @@ "simplex-explained-tab-3-p-1": "I server hanno credenziali anonime separate per ogni coda e non sanno a quali utenti appartengano.", "chat-protocol": "Protocollo di chat", "donate": "Dona", - "copyright-label": "© 2020-2024 SimpleX | Progetto Open-Source", + "copyright-label": "© 2020-2025 SimpleX | Progetto Open-Source", "simplex-chat-protocol": "Protocollo di SimpleX Chat", "terminal-cli": "Terminale CLI", "terms-and-privacy-policy": "Informativa sulla privacy", @@ -82,8 +82,7 @@ "we-invite-you-to-join-the-conversation": "Ti invitiamo a unirti alla conversazione", "enter-your-email-address": "Inserisci il tuo indirizzo email", "get-simplex": "Ottieni SimpleX desktop app", - "why-simplex-is": "Perché SimpleX è", - "unique": "unico", + "why-simplex-is-unique": "Perché SimpleX è unico", "learn-more": "Maggiori informazioni", "more-info": "Mostra info", "hide-info": "Nascondi info", @@ -187,7 +186,7 @@ "join-us-on-GitHub": "Unisciti a noi su GitHub", "simplex-chat-for-the-terminal": "SimpleX Chat per il terminale", "privacy-matters-overlay-card-3-p-4": "Non è sufficiente usare un messenger crittografato end-to-end, tutti dovremmo usare i messenger che proteggono la privacy delle nostre reti personali — con chi siamo connessi.", - "simplex-unique-overlay-card-1-p-2": "Per recapitare i messaggi, SimpleX usa indirizzi anonimi a coppie di code di messaggi unidirezionali, separate per i messaggi ricevuti e inviati, di solito tramite server diversi. Usare SimpleX è come avere un'email o telefono “temporanei” per ogni contatto, e nessuna seccatura per gestirli.", + "simplex-unique-overlay-card-1-p-2": "Per recapitare i messaggi, SimpleX usa indirizzi anonimi a coppie di code di messaggi unidirezionali, separate per i messaggi ricevuti e inviati, di solito tramite server diversi.", "simplex-unique-overlay-card-1-p-1": "A differenza di altre piattaforme di messaggistica, SimpleX non ha alcun identificatore assegnato agli utenti. Non si basa su numeri di telefono, indirizzi basati su domini (come email o XMPP), nomi utente, chiavi pubbliche o persino numeri casuali per identificare i suoi utenti — non sappiamo quante persone usano i nostri server SimpleX.", "simplex-unique-overlay-card-3-p-3": "A differenza dei server di reti federate (email, XMPP o Matrix), i server SimpleX non conservano gli account utente, ma trasmettono solo i messaggi, proteggendo la privacy di entrambe le parti.", "simplex-unique-card-3-p-2": "I messaggi crittografati end-to-end vengono conservati temporaneamente sui server di inoltro SimpleX fino alla ricezione, quindi vengono eliminati definitivamente.", diff --git a/website/langs/ja.json b/website/langs/ja.json index c24883fb00..05365e2271 100644 --- a/website/langs/ja.json +++ b/website/langs/ja.json @@ -52,7 +52,7 @@ "chat-protocol": "チャットプロトコル", "chat-bot-example": "チャットボットの例", "donate": "寄付", - "copyright-label": "© 2020-2024 SimpleX | Open-Source Project", + "copyright-label": "© 2020-2025 SimpleX | Open-Source Project", "hero-p-1": "他のアプリにはユーザー ID があります: Signal、Matrix、Session、Briar、Jami、Cwtch など。
    SimpleX にはありません。乱数さえもありません
    これにより、プライバシーが大幅に向上します。", "copy-the-command-below-text": "以下のコマンドをコピーしてチャットで使用します:", "simplex-private-card-9-point-1": "各メッセージ キューは、異なる送信アドレスと受信アドレスを使用してメッセージを一方向に渡します。", @@ -193,13 +193,12 @@ "simplex-unique-overlay-card-3-p-2": "エンドツーエンドで暗号化されたメッセージは、SimpleXのリレーサーバーで受信するまで一時的に保持され、その後永久に削除されます。", "simplex-private-card-7-point-1": "整合性を保証するために、メッセージには連続した番号が付けられ、前のメッセージのハッシュが含まれます。", "contact-hero-p-2": "SimpleX Chat をまだダウンロードしていませんか?", - "why-simplex-is": "なぜSimpleXなのか", + "why-simplex-is-unique": "なぜSimpleXなのか唯一", "simplex-network-section-header": "SimpleX ネットワーク", "simplex-private-10-title": "一時的な匿名のペア識別子", "privacy-matters-1-overlay-1-linkText": "プライバシーの保護はコストを削減します", "tap-the-connect-button-in-the-app": "アプリの 「接続」 ボタンをタップします", "comparison-section-list-point-4a": "SimpleX リレーは e2e 暗号化を侵害できません。 セキュリティ コードを検証して帯域外チャネルへの攻撃を軽減します", - "unique": "唯一", "simplex-network-1-overlay-linktext": "P2Pネットワークの問題点", "no-private": "いいえ - プライベート", "simplex-unique-1-title": "プライバシーが完全に守られます", diff --git a/website/langs/nl.json b/website/langs/nl.json index 4db6126c2c..30e751c9f9 100644 --- a/website/langs/nl.json +++ b/website/langs/nl.json @@ -7,7 +7,7 @@ "why-simplex": "Waarom SimpleX", "simplex-privacy": "SimpleX privacy", "simplex-network": "SimpleX netwerk", - "simplex-explained": "Simplex uitgelegd", + "simplex-explained": "SimpleX uitgelegd", "simplex-explained-tab-1-text": "1. Wat gebruikers ervaren", "simplex-explained-tab-3-text": "3. Wat servers zien", "simplex-explained-tab-1-p-1": "U kunt contacten en groepen maken en tweerichtings gesprekken voeren, zoals in elke andere messenger.", @@ -17,7 +17,7 @@ "chat-bot-example": "Chatbot voorbeeld", "smp-protocol": "SMP protocol", "donate": "Doneer", - "copyright-label": "© 2020-2024 SimpleX | Open-sourceproject", + "copyright-label": "© 2020-2025 SimpleX | Open-sourceproject", "simplex-chat-protocol": "SimpleX Chat protocol", "terminal-cli": "Terminal CLI", "terms-and-privacy-policy": "Privacybeleid", @@ -116,8 +116,7 @@ "join-us-on-GitHub": "Volg ons op GitHub", "donate-here-to-help-us": "Doneer hier om ons te helpen", "sign-up-to-receive-our-updates": "Schrijf u in om onze updates te ontvangen", - "unique": "uniek", - "why-simplex-is": "Waarom is SimpleX", + "why-simplex-is-unique": "Waarom is SimpleX uniek", "learn-more": "Kom meer te weten", "more-info": "Meer informatie", "hide-info": "Info verbergen", @@ -183,7 +182,7 @@ "privacy-matters-overlay-card-3-p-2": "Een van de meest schokkende verhalen is de ervaring van Mohamedou Ould Salahi beschreven in zijn memoires en getoond in The Mauritanian movie. Hij werd zonder proces in het Guantanamo kamp geplaatst en werd daar 15 jaar lang gemarteld na een telefoontje naar zijn familielid in Afghanistan, op verdenking van betrokkenheid bij aanslagen van 9/11, ook al woonde hij de afgelopen 10 jaar in Duitsland.", "privacy-matters-overlay-card-3-p-3": "Gewone mensen worden gearresteerd voor wat ze online delen, zelfs via hun 'anonieme' accounts, zelfs in democratische landen.", "privacy-matters-overlay-card-3-p-4": "Het is niet genoeg om een end-to-end versleutelde messenger te gebruiken, we zouden allemaal de messengers moeten gebruiken die de privacy van onze persoonlijke netwerken beschermen — met wie we verbonden zijn.", - "simplex-unique-overlay-card-1-p-2": "Om berichten af te leveren gebruikt SimpleX paarsgewijze anonieme adressen van unidirectionele berichten wachtrijen, gescheiden voor ontvangen en verzonden berichten, meestal via verschillende servers. Het gebruik van SimpleX is als het hebben van een andere “brander” e-mail of telefoon voor elk contact, en geen gedoe om ze te beheren.", + "simplex-unique-overlay-card-1-p-2": "Om berichten af te leveren gebruikt SimpleX paarsgewijze anonieme adressen van unidirectionele berichten wachtrijen, gescheiden voor ontvangen en verzonden berichten, meestal via verschillende servers.", "simplex-unique-overlay-card-1-p-3": "Dit ontwerp beschermt de privacy van met wie u communiceert en verbergt deze voor SimpleX platform servers en voor waarnemers. Om uw IP-adres voor de servers te verbergen, kunt u verbinding maken met SimpleX servers via Tor .", "simplex-unique-overlay-card-2-p-1": "Omdat je geen identificatie hebt op het SimpleX platform, kan niemand contact met je opnemen, tenzij je een eenmalig of tijdelijk gebruikers adres deelt, als een QR-code of een link.", "simplex-unique-overlay-card-2-p-2": "Zelfs met het optionele gebruikers adres, hoewel het kan worden gebruikt om spam-contact verzoeken te verzenden, kunt u het wijzigen of volledig verwijderen zonder uw verbindingen te verliezen.", diff --git a/website/langs/pl.json b/website/langs/pl.json index a25155e018..d45207a709 100644 --- a/website/langs/pl.json +++ b/website/langs/pl.json @@ -15,7 +15,7 @@ "smp-protocol": "Protokół SMP", "chat-protocol": "Protokół czatu", "donate": "Darowizna", - "copyright-label": "© 2020-2024 SimpleX | Projekt Open-Source", + "copyright-label": "© 2020-2025 SimpleX | Projekt Open-Source", "simplex-chat-protocol": "Protokół SimpleX Chat", "terminal-cli": "Terminal CLI", "terms-and-privacy-policy": "Polityka prywatności", @@ -140,7 +140,7 @@ "simplex-network-overlay-card-1-li-6": "Sieci P2P mogą być podatne na atak DRDoS, kiedy klienci mogą rozgłaszać i wzmacniać ruch, co powoduje odmowę usługi w całej sieci. Klienci SimpleX przekazują jedynie ruch ze znanego połączenia i nie mogą być wykorzystani przez atakującego do wzmocnienia ruchu w całej sieci.", "privacy-matters-overlay-card-1-p-1": "Wiele dużych firm wykorzystuje informacje o tym, z kim jesteś połączony, aby oszacować Twoje dochody, sprzedać Ci produkty, których tak naprawdę nie potrzebujesz, oraz ustalić ceny.", "simplex-unique-overlay-card-2-p-2": "Nawet w przypadku opcjonalnego adresu użytkownika, podczas gdy może on być używany do wysyłania spamowych zapytań o kontakt, możesz go zmienić lub całkowicie usunąć bez utraty jakichkolwiek połączeń.", - "simplex-unique-overlay-card-1-p-2": "Do dostarczania wiadomości SimpleX używa parami anonimowych adresów jednokierunkowych kolejek z wiadomościami, oddzielnych dla wiadomości odbieranych i wysyłanych, zwykle przez różne serwery. Korzystanie z SimpleX jest jak posiadanie wielu “jednorazowych” emailów lub osobnego telefonu dla każdego kontaktu i braku problemów z zarządzaniem nimi.", + "simplex-unique-overlay-card-1-p-2": "Do dostarczania wiadomości SimpleX używa parami anonimowych adresów jednokierunkowych kolejek z wiadomościami, oddzielnych dla wiadomości odbieranych i wysyłanych, zwykle przez różne serwery.", "simplex-unique-overlay-card-1-p-3": "Taka konstrukcja chroni prywatność tego, z kim się komunikujesz, ukrywając ją przed serwerami platformy SimpleX i przed wszelkimi obserwatorami. Aby ukryć swój adres IP przed serwerami, możesz połączyć się z serwerami SimpleX za pośrednictwem sieci Tor.", "simplex-unique-overlay-card-3-p-4": "Nie ma żadnych identyfikatorów ani szyfrogramów wspólnych między wysyłanym i odbieranym ruchem serwera — jeśli ktokolwiek to obserwuje, nie może łatwo określić, kto komunikuje się z kim, nawet jeśli bezpieczeństwo protokołu TLS zostało zagrożone.", "simplex-unique-overlay-card-2-p-1": "Ponieważ nie masz identyfikatora na platformie SimpleX, nikt nie może się z Tobą skontaktować, chyba że udostępnisz jednorazowy lub tymczasowy adres użytkownika, w postaci kodu QR lub linku.", @@ -154,13 +154,12 @@ "simplex-unique-card-1-p-2": "W przeciwieństwie do każdej innej istniejącej platformy komunikacyjnej, SimpleX nie ma żadnych identyfikatorów przypisanych do użytkowników — nawet losowych liczb.", "tap-the-connect-button-in-the-app": "Stuknij przycisk 'połącz się' w aplikacji", "join-the-REDDIT-community": "Dołącz do społeczności REDDIT", - "unique": "unikalny", "hide-info": "Ukryj informacje", "simplex-unique-card-4-p-2": "Możesz używać SimpleX z własnymi serwerami lub z serwerami dostarczonymi przez nas — i nadal łączyć się z dowolnym użytkownikiem.", "we-invite-you-to-join-the-conversation": "Zapraszamy do udziału w rozmowie", "enter-your-email-address": "Wpisz swój adres e-mail", "get-simplex": "Pobierz SimpleX desktop app", - "why-simplex-is": "Dlaczego SimpleX jest", + "why-simplex-is-unique": "Dlaczego SimpleX jest unikalny", "join": "Dołącz do", "join-us-on-GitHub": "Dołącz do nas na GitHubie", "sign-up-to-receive-our-updates": "Zapisz się, aby otrzymywać nasze aktualizacje", diff --git a/website/langs/pt_BR.json b/website/langs/pt_BR.json index b25888591e..77854f5be1 100644 --- a/website/langs/pt_BR.json +++ b/website/langs/pt_BR.json @@ -25,7 +25,7 @@ "smp-protocol": "Protocolo SMP", "chat-protocol": "Protocolo de bate-papo", "donate": "Doar", - "copyright-label": "© 2020-2024 SimpleX | Projeto de Código Livre", + "copyright-label": "© 2020-2025 SimpleX | Projeto de Código Livre", "simplex-chat-protocol": "Protocolo Chat SimpleX", "terminal-cli": "CLI Terminal", "hero-header": "Privacidade redefinida", @@ -127,7 +127,7 @@ "privacy-matters-overlay-card-2-p-3": "O SimpleX é a primeira plataforma que não tem nenhum identificador de usuário por design, protegendo assim seu gráfico de conexões melhor do que qualquer alternativa conhecida.", "privacy-matters-overlay-card-3-p-3": "Pessoas comuns são presas pelo que compartilham online, mesmo por meio de suas contas \"anônimas\", mesmo em países democráticos.", "privacy-matters-overlay-card-3-p-4": "Não basta usar um mensageiro criptografado de ponta-a-ponta, todos nós devemos usar os mensageiros que protegem a privacidade de nossas redes pessoais — com quem estamos conectados.", - "simplex-unique-overlay-card-1-p-2": "Para entregar mensagens, o SimpleX usa endereços anônimos em pares de filas de mensagens unidirecionais, separadas para mensagens recebidas e enviadas, geralmente por meio de servidores diferentes. Usar o SimpleX é como ter um email ou telefone de “gravação” diferente para cada contato, sem a necessidade de gerenciá-los.", + "simplex-unique-overlay-card-1-p-2": "Para entregar mensagens, o SimpleX usa endereços anônimos em pares de filas de mensagens unidirecionais, separadas para mensagens recebidas e enviadas, geralmente por meio de servidores diferentes.", "simplex-unique-overlay-card-1-p-3": "Esse design protege a privacidade de quem está se comunicando com você, ocultando-a dos servidores da plataforma SimpleX e de quaisquer observadores. Para ocultar seu endereço IP dos servidores, você pode se conectar aos servidores do SimpleX via Tor.", "simplex-unique-overlay-card-2-p-2": "Mesmo com o endereço de usuário opcional, embora ele possa ser usado para enviar solicitações de contato de spam, você pode alterá-lo ou excluí-lo completamente sem perder nenhuma das suas conexões.", "simplex-unique-overlay-card-2-p-1": "Como você não tem um identificador na plataforma SimpleX, ninguém pode entrar em contato com você, a menos que compartilhe um endereço de usuário único ou temporário, como um QR code ou um link.", @@ -147,8 +147,7 @@ "sign-up-to-receive-our-updates": "Inscreva-se para receber nossas atualizações", "enter-your-email-address": "Digite seu endereço de email", "get-simplex": "Obtenha o SimpleX aplicativo desktop", - "why-simplex-is": "Por que o SimpleX é", - "unique": "único", + "why-simplex-is-unique": "Por que o SimpleX é único", "learn-more": "Saiba mais", "more-info": "Mais informações", "contact-hero-header": "Você recebeu um endereço para se conectar no SimpleX Chat", diff --git a/website/langs/ru.json b/website/langs/ru.json index c584d93355..335b19ab11 100644 --- a/website/langs/ru.json +++ b/website/langs/ru.json @@ -1,259 +1,258 @@ { "copy-the-command-below-text": "скопируйте приведенную ниже команду и используйте ее в чате:", - "copyright-label": "© 2020-2024 SimpleX | Проект с открытым исходным кодом", + "copyright-label": "© 2020-2025 SimpleX | Проект с открытым исходным кодом", "chat-bot-example": "Пример Чат бота", "simplex-private-card-9-point-1": "Каждая очередь сообщений передает сообщения в одном направлении с разными адресами отправки и получения.", - "simplex-private-card-1-point-2": "Криптобокс NaCL в каждой очереди для предотвращения корреляции трафика между очередями сообщений, в случае компрометации TLS.", - "contact-hero-p-1": "Открытые ключи и адрес очереди сообщений по этой ссылке НЕ отправляются по сети при просмотре этой страницы — они содержатся в хэш-фрагменте URL-адреса ссылки.", + "simplex-private-card-1-point-2": "NaCL cryptobox в каждой очереди для предотвращения корреляции трафика между очередями сообщений, в случае компрометированного TLS.", + "contact-hero-p-1": "Публичные ключи и адрес очереди сообщений в этой ссылке НЕ отправляются по сети при просмотре этой страницы — они содержатся в хэш-фрагменте URL-адреса ссылки.", "guide-dropdown-5": "Управление данными", "scan-the-qr-code-with-the-simplex-chat-app": "Отсканируйте QR-код с помощью приложения SimpleX Chat", - "simplex-private-card-9-point-2": "Это уменьшает векторы атак и доступные метаданные, по сравнению с традиционными посредниками для доставки сообщений.", + "simplex-private-card-9-point-2": "Это уменьшает векторы атак и доступные метаданные, по сравнению с традиционными серверами доставки сообщений.", "simplex-unique-card-3-p-2": "Сквозные зашифрованные сообщения временно хранятся на серверах SimpleX до получения, после чего они удаляются безвозвратно.", - "feature-7-title": "Портативное, зашифрованное хранилище в приложении — можно перенести профиль на другое устройство", - "no-federated": "Нет - федеративный", - "hero-2-header": "Как начать общаться приватно", + "feature-7-title": "Зашифрованное база данных — Вы можете перенести профиль на другое устройство", + "no-federated": "Нет - федеративные", + "hero-2-header": "Установите конфиденциальное соединение", "simplex-unique-overlay-card-3-p-3": "В отличие от серверов федеративных сетей (электронной почты, XMPP или Matrix), серверы SimpleX не хранят учетные записи пользователей, они только ретранслируют сообщения, защищая конфиденциальность обеих сторон.", - "hero-subheader": "Первый мессенджер
    не нуждающийся в идентификаторах
    пользователя", - "privacy-matters-overlay-card-3-p-2": "Одна из самых шокирующих историй - это опыт Слахи, Мохаммеда Ульда, описанный в его мемуарах и показанный в фильме Мавританец. Он был помещен в лагерь Гуантанамо без суда и следствия и подвергался там пыткам в течение 15 лет после телефонного звонка своему родственнику в Афганистан под подозревается в причастности к терактам 11 сентября, хотя предыдущие 10 лет он жил в Германии.", - "signing-key-fingerprint": "Отпечаток ключа подписи (SHA-256)", + "hero-subheader": "Первый мессенджер
    без идентификаторов пользователей", + "privacy-matters-overlay-card-3-p-2": "Одна из самых шокирующих историй — это опыт Слахи, Мохаммеда Ульда, описанный в его мемуарах и показанный в фильме Мавританец. Он был помещен в лагерь Гуантанамо без суда и следствия и подвергался там пыткам в течение 15 лет после телефонного звонка своему родственнику в Афганистане, из за подозрения в причастности к терактам 11 сентября, хотя предыдущие 10 лет он жил в Германии.", + "signing-key-fingerprint": "Идентификатор ключа подписи (SHA-256)", "simplex-network-2-desc": "Серверные узлы SimpleX НЕ хранят профили пользователей, контакты и доставленные сообщения, НЕ подключаются друг к другу, и НЕ имеют каталога серверов.", "simplex-privacy": "Конфиденциальность SimpleX", "docs-dropdown-5": "Свой XFTP Сервер", - "simplex-private-card-3-point-2": "Отпечаток сервера и привязка канала предотвращают MITM атаки и Атаки повторного воспроизведения.", + "simplex-private-card-3-point-2": "Идентификатор сервера и привязка к TLS сессии предотвращают атаки перехвата (MITM) и повторного использования.", "docs-dropdown-3": "Доступ к в базе данных чата", "installing-simplex-chat-to-terminal": "Установка SimpleX Chat в терминале", "use-this-command": "Используйте эту команду:", - "simplex-explained": "Простое объяснение вкратце", + "simplex-explained": "Как SimpleX работает", "to-make-a-connection": "Чтобы установить соединение:", - "comparison-section-list-point-6": "Хотя P2P распределены, они не являются федеративными, то есть P2P - работают как единая сеть", - "hero-overlay-2-textlink": "Как работает SimpleX?", + "comparison-section-list-point-6": "Хотя P2P распределены, они не являются федеративными, то есть P2P — работают как единая сеть", + "hero-overlay-2-textlink": "Как SimpleX работает?", "simplex-chat-via-f-droid": "SimpleX Chat в F-Droid", - "privacy-matters-overlay-card-1-p-1": "Многие крупные компании используют информацию о том, с кем вы связаны, чтобы оценить ваш доход, продавать вам больше товаров, которые вам на самом деле не нужны, и определять из этой информации, выгодные для них цены.", - "privacy-matters-1-overlay-1-title": "Конфиденциальность экономит ваши деньги", - "simplex-private-card-5-point-1": "SimpleX использует заполнение содержимого для каждого уровня шифрования, чтобы предотвратить атаки на размер сообщения.", - "privacy-matters-2-overlay-1-linkText": "Конфиденциальность дает вам власть", - "hero-overlay-3-title": "Оценки безопасности", - "enter-your-email-address": "Email адрес", - "simplex-explained-tab-1-text": "1. Как это видят пользователи", + "privacy-matters-overlay-card-1-p-1": "Многие крупные компании используют информацию о том, с кем Вы связаны, чтобы оценить Ваш доход, продавать Вам больше товаров, которые Вам на самом деле не нужны, и определять из этой информации, выгодные для них цены.", + "privacy-matters-1-overlay-1-title": "Конфиденциальность экономит Ваши деньги", + "simplex-private-card-5-point-1": "SimpleX использует дополнение содержания для каждого уровня шифрования, чтобы скрыть размер сообщений.", + "privacy-matters-2-overlay-1-linkText": "Конфиденциальность дает Вам власть", + "hero-overlay-3-title": "Аудит безопасности", + "enter-your-email-address": "Введите Ваш адрес Email", + "simplex-explained-tab-1-text": "1. Что видят пользователи", "tap-to-close": "Нажмите, чтобы закрыть", "simplex-unique-card-4-p-2": "Вы можете использовать SimpleX со своими собственными серверами или с серверами, предоставленными нами — и при этом подключаться к любому пользователю SimpleX.", - "hero-overlay-card-3-p-2": "В ноябре 2022 года Trail of Bits провела обзор криптографии и сетевых компонентов SimpleX. Дополнительная информация.", - "hero-overlay-card-3-p-3": "В июле 2024 года Trail of Bits провела обзор криптографического дизайна протоколов SimpleX. Дополнительная информация.", - "feature-1-title": "Сообщения зашифрованные E2E-шифрованием
    с поддержкой markdown и редактированием", + "hero-overlay-card-3-p-2": "В ноябре 2022 года Trail of Bits провела аудит криптографии и сетевых компонентов SimpleX. Дополнительная информация.", + "hero-overlay-card-3-p-3": "В июле 2024 года Trail of Bits провела аудит криптографического дизайна протоколов SimpleX. Дополнительная информация.", + "feature-1-title": "Сообщения с E2E-шифрованием
    с форматированием и редактированием", "comparison-point-4-text": "Единая или Централизованная сеть", "guide-dropdown-9": "Установление соединений", - "simplex-unique-1-overlay-1-title": "Полная конфиденциальность вашей личности, профиля, контактов и других метаданных", - "hero-overlay-card-2-p-4": "SimpleX защищает от этих атак, поскольку в его конструкции нет никаких идентификаторов пользователей. И, если вы используете режим инкогнито, у вас будет другое отображаемое имя для каждого контакта, что позволит избежать какого-либо пересечения между ними.", - "privacy-matters-overlay-card-2-p-2": "Чтобы быть объективным и принимать независимые решения, вам необходимо контролировать свое информационное пространство. Это возможно только в том случае, если вы используете приватную, коммуникационную платформу, которая не имеет доступа к вашему социальному графу.", - "hero-overlay-card-2-p-1": "Когда у пользователей есть постоянные идентификаторы, даже если это просто случайное число, например идентификатор сеанса, существует риск того, что провайдер или злоумышленник могут наблюдайте за тем, как подключены пользователи и сколько сообщений они отправляют.", - "feature-3-title": "Децентрализованные группы — только
    их участники знают, что они существуют", + "simplex-unique-1-overlay-1-title": "Полная конфиденциальность Вашей личности, профиля, контактов и метаданных", + "hero-overlay-card-2-p-4": "SimpleX защищает от этих атак, поскольку он не использует никакие идентификаторы профилей пользователей. И, если Вы используете режим инкогнито, у Вас будет другое отображаемое имя для каждого контакта, что позволит избежать какого-либо пересечения между ними.", + "privacy-matters-overlay-card-2-p-2": "Чтобы быть объективным и принимать независимые решения, необходимо контролировать свое информационное пространство. Это возможно только, если Вы используете конфиденциальную коммуникационную сеть, которая не имеет доступа к контактам Вашей социальной сети.", + "hero-overlay-card-2-p-1": "Когда у пользователя есть постоянный идентификатор, даже если это просто случайное число, например Session ID, существует риск того, что провайдер или злоумышленник могут наблюдать за тем, как пользователи соединены и сколько сообщений они отправляют.", + "feature-3-title": "Децентрализованные группы — известные только участникам", "glossary": "Глоссарий", "simplex-network-overlay-1-title": "Сравнение с протоколами обмена сообщениями P2P", - "comparison-section-list-point-7": "Сети P2P либо имеют центральный орган управления, либо вся сеть может быть скомпрометирована", - "simplex-unique-overlay-card-4-p-1": "Вы можете использовать SimpleX со своими собственными серверами или предоставленными нами серверами, при этом имея возможность общаться с любым пользователем.", - "simplex-explained-tab-3-p-1": "Серверы имеют отдельные, Анонимные учётные данные для каждой очереди и не знают, к каким пользователям они принадлежат.", - "docs-dropdown-1": "Платформа SimpleX", - "hero-overlay-card-1-p-5": "Только клиентские устройства хранят профили пользователей, контакты и группы; сообщения отправляются с двухуровневым, Сквозным шифрованием.", + "comparison-section-list-point-7": "Сети P2P либо имеют центральный компонент, либо вся сеть может быть атакована", + "simplex-unique-overlay-card-4-p-1": "Вы можете использовать SimpleX со своими собственными серверами или с серверами предустановленными в приложении, при этом имея возможность общаться с любым пользователем.", + "simplex-explained-tab-3-p-1": "Серверы имеют отдельные, анонимные учётные данные для каждой очереди и не знают, каким пользователям они принадлежат.", + "docs-dropdown-1": "Сеть SimpleX", + "hero-overlay-card-1-p-5": "Только клиентские устройства хранят профили пользователей, контакты и группы; сообщения отправляются с двухуровневым сквозным шифрованием.", "simplex-chat-for-the-terminal": "SimpleX Chat для терминала", - "simplex-network-overlay-card-1-li-3": "P2P не решает проблему MITM-атаки (Атака посредника), и большинство существующих реализаций не используют внеполосные сообщения для первоначального обмена ключами. SimpleX использует внеполосные сообщения или, в некоторых случаях, ранее существовавшие защищенные и доверенные соединения для первоначального обмена ключами.", + "simplex-network-overlay-card-1-li-3": "P2P не решает проблему MITM-атаки (Атака посредника), и многие сети не используют сообщения вне протокола для первоначального обмена ключами. SimpleX использует сообщения вне протокола или, в некоторых случаях, ранее существовавшие защищенные и доверенные соединения для первоначального обмена ключами.", "the-instructions--source-code": "SimpleX Chat.", "simplex-network-section-desc": "SimpleX Chat обеспечивает наилучшую конфиденциальность, сочетая преимущества P2P и федеративных сетей.", - "privacy-matters-section-subheader": "Сохранение конфиденциальности ваших метаданных — с кем вы общаетесь — защищает вас от:", - "if-you-already-installed": "Если вы уже установили", - "simplex-explained-tab-3-p-2": "Пользователи могут еще больше повысить свою конфиденциальность скрыв свой IP-адрес, например используя сеть Tor для доступа к серверам.", - "join": "Присоединяйся к", - "privacy-matters-section-header": "Почему приватность важна", - "hero-overlay-1-textlink": "Почему идентификаторы пользователя - вредны для приватности?", + "privacy-matters-section-subheader": "Сохранение конфиденциальности Ваших метаданных — с кем Вы общаетесь — защищает Вас от:", + "if-you-already-installed": "Если Вы уже установили", + "simplex-explained-tab-3-p-2": "Пользователи могут повысить свою конфиденциальность используя сеть Tor для доступа к серверам.", + "join": "Присоединяйтесь к", + "privacy-matters-section-header": "Почему конфиденциальность важна", + "hero-overlay-1-textlink": "Почему идентификаторы пользователя уменьшают конфиденциальность?", "on-this-page": "На этой странице", "privacy-matters-overlay-card-1-p-2": "Интернет-магазины знают, что люди с более низкими доходами с большей вероятностью совершают срочные покупки, поэтому они могут устанавливать более высокие цены или отменять скидки.", - "simplex-unique-3-overlay-1-title": "Контроль и безопасность ваших данных", + "simplex-unique-3-overlay-1-title": "Контроль и безопасность Ваших данных", "protocol-3-text": "Протоколы P2P", - "simplex-private-card-6-point-2": "Чтобы предотвратить это, приложения SimpleX передают одноразовые ключи внеполосно, когда вы делитесь адресом в виде ссылки или QR-кода.", + "simplex-private-card-6-point-2": "Чтобы предотвратить это, приложения SimpleX передают одноразовые ключи вне протокола, когда Вы делитесь адресом в виде ссылки или QR-кода.", "no": "Нет", - "contact-hero-header": "Вы получили адрес для подключения в SimpleX Chat", + "contact-hero-header": "Вы получили адрес контакта для соединения в SimpleX Chat", "feature-8-title": "Режим инкогнито —
    уникальный для SimpleX Chat", - "why-simplex": "Что делает SimpleX уникальным", + "why-simplex": "Почему SimpleX", "simplex-private-card-4-point-2": "Чтобы использовать SimpleX через сеть Tor, пожалуйста, установите приложение Orbot и включите прокси (или режим VPN на iOS).", - "contact-hero-subheader": "Отсканируйте QR-код с помощью приложения SimpleX Chat на вашем телефоне или планшете.", + "contact-hero-subheader": "Отсканируйте QR-код с помощью приложения SimpleX Chat на Вашем телефоне или планшете.", "simplex-unique-2-overlay-1-title": "Лучшая защита от спама и злоупотреблений", - "simplex-private-6-title": "Внеполосный
    Обмен ключами", - "join-us-on-GitHub": "Присоединяйтесь к нам на GitHub", + "simplex-private-6-title": "Обмен ключами
    вне протокола", + "join-us-on-GitHub": "Присоединяйтесь на GitHub", "comparison-section-header": "Сравнение с другими протоколами", - "invitation-hero-header": "Вы получили одноразовую ссылку для подключения в SimpleX Chat", - "no-secure": "Нет - безопасно", - "hero-overlay-card-1-p-2": "Для доставки сообщений вместо идентификаторов пользователей, используемых всеми другими платформами, SimpleX использует временные, анонимные, попарные идентификаторы очередей сообщений, отдельно для каждого из ваших контактов — нет долгосрочных идентификаторов.", + "invitation-hero-header": "Вы получили одноразовую ссылку для соединения в SimpleX Chat", + "no-secure": "Нет - безопасный", + "hero-overlay-card-1-p-2": "Для доставки сообщений вместо идентификаторов пользователей, используемых другими сетями, SimpleX использует временные, анонимные, парные идентификаторы очередей сообщений, отдельные для каждого Вашего контакта — без долгосрочных идентификаторов.", "simplex-explained-tab-1-p-2": "Как это может работать с однонаправленными очередями и без идентификаторов профиля пользователя?", "simplex-network-1-header": "В отличие от P2P-сетей", "jobs": "Присоединиться к команде", "simplex-private-card-7-point-2": "Если какое-либо сообщение будет добавлено, удалено или изменено, получатель будет предупрежден об этом.", - "simplex-unique-3-title": "Только вы контролируете
    свои данные", + "simplex-unique-3-title": "Только Вы контролируете
    Ваши данные", "guide-dropdown-3": "Секретные группы", "no-resilient": "Нет - устойчив", - "hide-info": "Спрятать информацию", + "hide-info": "Скрыть информацию", "privacy-matters-overlay-card-3-p-4": "Недостаточно просто использовать мессенджер со сквозным шифрованием, мы все должны использовать мессенджеры, которые защищают конфиденциальность наших личных сетей — с какими людьми мы связаны.", - "releases-to-this-repo-are-done-1-2-days-later": "Выпуск новых версий в этом репозитории выходит с задержкой в несколько дней", + "releases-to-this-repo-are-done-1-2-days-later": "Новые версии в этом репозитории публикуются с задержкой в несколько дней", "comparison-point-1-text": "Требуется глобальный идентификатор", "comparison-section-list-point-5": "Не защищает конфиденциальность пользовательских метаданных", - "hero-overlay-card-2-p-2": "Затем они могли бы сопоставить эту информацию с существующими общедоступными социальными сетями и определить некоторые реальные личности.", - "privacy-matters-overlay-card-1-p-3": "Некоторые финансовые и страховые компании используют социальные графики для определения процентных ставок и премий. Это часто заставляет людей с более низкими доходами платить больше — это известно как \"премия за бедность\".", + "hero-overlay-card-2-p-2": "Эта информация может быть сопоставлена с социальными сетями, чтобы определить реальные личности пользователей.", + "privacy-matters-overlay-card-1-p-3": "Некоторые финансовые и страховые компании используют социальные сети для определения процентных ставок и премий. Это часто заставляет людей с более низкими доходами платить больше — это известно как \"наценка за бедность\".", "comparison-point-3-text": "Зависимость от DNS", "yes": "Да", "docs-dropdown-6": "Сервера WebRTC", "newer-version-of-eng-msg": "Существует более новая версия этой страницы на английском языке.", "install-simplex-app": "Установите приложение SimpleX", - "hero-overlay-3-textlink": "Оценки безопасности", + "hero-overlay-3-textlink": "Аудит безопасности", "comparison-point-2-text": "Возможность MITM", - "scan-the-qr-code-with-the-simplex-chat-app-description": "Открытые ключи и адрес очереди сообщений, указанные в этой ссылке, НЕ отправляются по сети при просмотре этой страницы —
    они содержатся в хэш-фрагменте URL-адреса ссылки.", + "scan-the-qr-code-with-the-simplex-chat-app-description": "Публичные ключи и адрес очереди сообщений, указанные в этой ссылке, НЕ отправляются по сети при просмотре этой страницы —
    они содержатся в хэш-фрагменте URL-адреса ссылки.", "guide-dropdown-8": "Настройки приложения", - "simplex-explained-tab-2-p-2": "Серверы передают сообщения только в одну сторону, не имея полной картины общения пользователя или подключений.", + "simplex-explained-tab-2-p-2": "Серверы передают сообщения только в одну сторону, не имея полной информации о разговорах и контактах пользователя.", "smp-protocol": "Протокол SMP", "open-simplex-app": "Откройте приложение SimpleX", "see-simplex-chat": "Инструкции по загрузке или компиляции SimpleX Chat из исходного кода приведены в", "terminal-cli": "Приложение для терминала (CLI)", "comparison-section-list-point-1": "Обычно требуется номера телефона, в некоторых случаях — имя пользователя", - "simplex-explained-tab-3-text": "3. Что видят сервера", - "github-repository": "репозиторий Github", + "simplex-explained-tab-3-text": "3. Что видят серверы", + "github-repository": "репозитории GitHub", "feature-5-title": "Исчезающие сообщения", "connect-in-app": "Подключитесь в приложении", "menu": "Меню", - "simplex-private-card-4-point-1": "Чтобы защитить свой IP-адрес вы можете подключаться к серверам через сеть Tor или какую-либо другую транспортную оверлейную сеть.", - "privacy-matters-3-title": "Судебное преследование не виновных", - "comparison-point-5-text": "Атака на центральный компонент или другая сетевая атака", + "simplex-private-card-4-point-1": "Чтобы защитить свой IP-адрес Вы можете подключаться к серверам через сеть Tor или какую-либо другую транспортную оверлейную сеть.", + "privacy-matters-3-title": "Преследование из-за невиновного общения", + "comparison-point-5-text": "Центральный компонент или атака на всю сеть", "click-to-see": "Нажать здесь, чтобы увидеть", - "donate-here-to-help-us": "Пожертвуйте здесь, чтобы помочь нам", + "donate-here-to-help-us": "Пожертвовать", "simplex-private-1-title": "2-уровневое
    сквозное шифрование", - "simplex-unique-card-1-p-2": "В отличие от любой другой существующей платформы обмена сообщениями, SimpleX не имеет идентификаторов пользователей — нету даже случайных цифр.", - "privacy-matters-2-overlay-1-title": "Конфиденциальность дает вам власть", - "simplex-unique-overlay-card-2-p-2": "Хоть злоумышленники и могут использовать постоянный адрес для отправки нежелательных запросов или спама, вы можете легко его изменить или просто удалить, не теряя связи с уже установленными контактами.", + "simplex-unique-card-1-p-2": "В отличие от любой другой существующей сети обмена сообщениями, SimpleX не имеет идентификаторов пользователей — нет даже случайных цифр.", + "privacy-matters-2-overlay-1-title": "Конфиденциальность дает Вам власть", + "simplex-unique-overlay-card-2-p-2": "Хоть злоумышленники и могут использовать постоянный адрес для отправки нежелательных запросов или спама, Вы можете легко его изменить или просто удалить, не теряя связи с уже установленными контактами.", "simplex-unique-4-overlay-1-title": "Полностью децентрализованная — пользователи владеют сетью SimpleX", "guide-dropdown-2": "Отправка сообщений", - "simplex-network-overlay-card-1-li-5": "Все известные P2P-сети могут быть уязвимы для Атаки Сивиллы, поскольку каждый узел доступен для обнаружения, и сеть работает как единое целое. Известные меры по его смягчению требуют либо централизованного компонента, либо дорогостоящего Proof-of-work. Сеть SimpleX не имеет функционала по обмену серверами, она фрагментирована и работает как множество изолированных подсетей, из-за чего провести атаку по всей сети - невозможно.", - "simplex-private-2-title": "Дополнительный уровень
    шифрования сервера", - "hero-overlay-card-1-p-4": "Такая конструкция предотвращает утечку любых пользовательских метаданных на уровне приложения. Для дальнейшего улучшения конфиденциальности и защиты вашего IP-адреса вы можете подключиться к серверам обмена сообщениями через сеть Tor.", + "simplex-network-overlay-card-1-li-5": "Все известные P2P-сети могут быть уязвимы для Атаки Сивиллы, поскольку каждый узел доступен для обнаружения, и сеть работает как единое целое. Известные меры по уменьшению риска требуют либо централизованного компонента, либо дорогостоящего Proof-of-work. Сеть SimpleX не позволяет обнаруживать серверы, она фрагментирована и работает как множество изолированных подсетей, из-за чего провести атаку на всю сеть невозможно.", + "simplex-private-2-title": "Дополнительный уровень
    шифрования с сервером", + "hero-overlay-card-1-p-4": "Этот подход предотвращает утечку любых пользовательских метаданных на уровне приложения. Для дальнейшего улучшения конфиденциальности и защиты Вашего IP-адреса Вы можете подключиться к серверам обмена сообщениями через сеть Tor.", "f-droid-org-repo": "Репозиторий F-Droid.org", "guide-dropdown-4": "Профили чата", "simplex-network-2-header": "В отличие от федеративных сетей", - "see-here": "подробней тут", - "simplex-private-3-title": "Безопасный аутентифицированный
    протокол TLS", - "comparison-section-list-point-3": "Открытый ключ или какой-либо другой глобально уникальный идентификатор", - "hero-overlay-card-2-p-3": "Даже в самых приватных приложениях, использующих скрытые сервисы Tor v3, если вы общаетесь с двумя разными контактами через один и тот же профиль, они могут доказать, что они являются связаны с одним и тем же человеком.", - "simplex-private-4-title": "Вариант доступа
    через сеть Tor", + "see-here": "подробнее здесь", + "simplex-private-3-title": "Безопасное аутентифицированное
    соединение TLS", + "comparison-section-list-point-3": "Публичный ключ или какой-либо другой глобально уникальный идентификатор", + "hero-overlay-card-2-p-3": "Даже в самых конфиденциальных приложениях, использующих скрытые адреса Tor, если Вы общаетесь с двумя разными контактами через один и тот же профиль, они могут доказать, что они связаны с одним и тем же человеком.", + "simplex-private-4-title": "Возможен доступ
    через сеть Tor", "privacy-matters-1-title": "Реклама и ценовая дискриминация", "simplex-unique-card-3-p-1": "SimpleX хранит все пользовательские данные на клиентских устройствах в портативном формате зашифрованной базы данных — их можно перенести на другое устройство.", - "hero-overlay-1-title": "Как работает SimpleX?", + "hero-overlay-1-title": "Как SimpleX работает?", "stable-versions-built-by-f-droid-org": "Стабильные версии, созданные F-Droid.org", - "contact-hero-p-3": "Воспользуйтесь ссылками ниже, чтобы загрузить приложение.", + "contact-hero-p-3": "Используйте ссылки ниже, чтобы загрузить приложение.", "simplex-network": "Сеть SimpleX", - "privacy-matters-3-overlay-1-title": "Конфиденциальность защищает вашу свободу", + "privacy-matters-3-overlay-1-title": "Конфиденциальность защищает Вашу свободу", "docs-dropdown-7": "Перевести SimpleX Chat", "back-to-top": "Вернуться к началу", "simplex-network-1-desc": "Все сообщения отправляются через серверы, что обеспечивает лучшую конфиденциальность метаданных и надежную асинхронную доставку сообщений, избегая при этом многих", "simplex-chat-repo": "Репозиторий SimpleX Chat", "simplex-private-card-6-point-1": "Многие коммуникационные платформы уязвимы для MITM-атак со стороны серверов или сетевых провайдеров.", - "privacy-matters-3-overlay-1-linkText": "Конфиденциальность защищает вашу свободу", - "simplex-unique-overlay-card-1-p-2": "Для доставки сообщений SimpleX использует попарные, анонимные адреса однонаправленных очередей сообщений, раздельные для полученных и отправленных сообщений, обычно через разные серверы. Использование SimpleX это как иметь отдельный “одноразовый” адрес электронной почты или номер телефона для каждого контакта, при это, не обременяя вас управлять эти вручную.", - "simplex-unique-overlay-card-3-p-4": "Со стороны не видно разницу между отправлением или получением сообщений — если кто-то наблюдает за этим, он не cможет легко определить, кто с кем общается, даже если протокол TLS будет скомпрометирован.", + "privacy-matters-3-overlay-1-linkText": "Конфиденциальность защищает Вашу свободу", + "simplex-unique-overlay-card-1-p-2": "Для доставки сообщений SimpleX использует попарные, анонимные адреса однонаправленных очередей сообщений, раздельные для полученных и отправленных сообщений, обычно через разные серверы.", + "simplex-unique-overlay-card-3-p-4": "Со стороны не видно разницы между отправлением или получением сообщений — если кто-то наблюдает за этим, он не cможет легко определить, кто с кем общается, даже если протокол TLS будет скомпрометирован.", "docs-dropdown-2": "Доступ к файлам в версии для Android", - "get-simplex": "Скачать SimpleX для ПК", - "privacy-matters-overlay-card-3-p-1": "Каждый должен заботиться о конфиденциальности и безопасности своих коммуникаций — безобидные разговоры могут подвергнуть вас опасности, например за ваши политические взгляды, даже если кажется, что вам \"нечего скрывать\".", + "get-simplex": "Скачать SimpleX для компьютера", + "privacy-matters-overlay-card-3-p-1": "Каждый должен заботиться о конфиденциальности и безопасности своих коммуникаций — безобидные разговоры могут подвергнуть Вас опасности, например за Ваши политические взгляды, даже если кажется, что Вам \"нечего скрывать\".", "simplex-unique-2-title": "Вы защищены от
    спама и злоупотреблений", - "simplex-unique-overlay-card-4-p-3": "Если вы рассматриваете возможность разработки платформе SimpleX, например, чат-бота для пользователей SimpleX или интеграции библиотеки SimpleX Chat в ваше мобильное приложение, пожалуйста, обращайтесь за любыми советами и поддержкой.", + "simplex-unique-overlay-card-4-p-3": "Если Вы рассматриваете возможность разработки для сети SimpleX, например, чат-бота для пользователей SimpleX или интеграции библиотеки SimpleX Chat в Ваше мобильное приложение, пожалуйста, обращайтесь за любыми советами и поддержкой.", "comparison-section-list-point-2": "Адреса на основе DNS", "stable-and-beta-versions-built-by-developers": "Стабильные и бета-версии, созданные разработчиками", "simplex-network-3-header": "Сеть SimpleX", - "simplex-unique-card-2-p-1": "Поскольку у вас нет идентификатора или фиксированного адреса на платформе SimpleX, никто не сможет связаться с вами, без вашего явного согласия, только если вы сами поделитесь адресом в виде QR-кода или ссылки.", - "hero-overlay-card-3-p-1": "Trail of Bits - ведущая консалтинговая компания в области безопасности и технологий, клиентами которой являются крупные технологические компании, правительственные агентства и крупные блокчейн проекты.", - "hero-header": "Иной взгляд на приватность", + "simplex-unique-card-2-p-1": "Поскольку у Вас нет идентификатора или фиксированного адреса в сети SimpleX, никто не сможет связаться с Вами без Вашего явного согласия mdash; это возможно, только если Вы сами поделитесь адресом в виде QR-кода или ссылки.", + "hero-overlay-card-3-p-1": "Trail of Bits — ведущая консалтинговая компания в области безопасности, клиентами которой являются крупные технологические компании, правительственные агентства и крупные блокчейн проекты.", + "hero-header": "Более конфиденциальный", "comparison-section-list-point-4": "Если операторы серверов скомпрометированы. В Signal, и некоторых других приложениях, есть возможность подтвердить код безопасности", - "simplex-private-card-2-point-1": "Дополнительный уровень серверного шифрования для доставки получателю, чтобы предотвратить корреляцию между полученным и отправленным трафиком сервера, если используемый протокол TLS скомпрометированный.", - "f-droid-page-simplex-chat-repo-section-text": "Чтобы добавить его в свой клиент F-Droid, отсканируйте QR-код или воспользуйтесь этим URL-адресом:", - "guide-dropdown-7": "Приватность и безопасность", - "join-the-REDDIT-community": "Присоединяйтесь к нам в сообществе REDDIT", - "simplex-private-card-10-point-2": "Это позволяет доставлять сообщения без идентификаторов профиля пользователя, обеспечивая лучшую конфиденциальность метаданных, чем другие альтернативы.", + "simplex-private-card-2-point-1": "Дополнительный уровень шифрования между сервером и получателем для предотвращения корреляции полученного и отправленного трафика сервера, в случае компрометированного TLS.", + "f-droid-page-simplex-chat-repo-section-text": "Чтобы добавить его в свой клиент F-Droid, отсканируйте QR-код или используйте этот адрес:", + "guide-dropdown-7": "Конфиденциальность и безопасность", + "join-the-REDDIT-community": "Присоединяйтесь в REDDIT", + "simplex-private-card-10-point-2": "Это позволяет доставлять сообщения без идентификаторов профиля пользователя, и обеспечивает лучшую конфиденциальность метаданных, чем альтернативы.", "privacy-matters-2-title": "Манипулирование выборами", - "home": "Домашняя страница", + "home": "Главная", "chat-protocol": "Протокол чата", - "simplex-private-card-5-point-2": "Это позволяет сообщениям разного размера выглядеть одинаково для серверов и сетевых наблюдателей.", - "hero-overlay-card-1-p-1": "Многие спрашивают:Если у SimpleX нету никаких идентификаторов пользователя, то как приложение знает, куда доставлять сообщения?", - "feature-6-title": "Зашифрованные E2E-шифрованием аудио и видео звонки", - "hero-p-1": "Другие приложения имеют ID своих пользователей: Signal, Matrix, Session, Briar, Jami, Cwtch и т. п.
    SimpleX не имет, нету даже случайных цифр.
    Это значительно повышает вашу приватность.", - "simplex-network-overlay-card-1-li-2": "В отличие от многих P2P сетей, SimpleX спроектирован так, чтобы не нуждаться в глобальных идентификаторов его пользователей, даже временных, используя только временные попарные идентификаторы, обеспечивая лучшую анонимность и защиту метаданных пользователя.", - "simplex-unique-4-title": "Только вы владеете
    сетью SimpleX", + "simplex-private-card-5-point-2": "Это делает сообщениям разного размера одинаковыми для серверов и сети.", + "hero-overlay-card-1-p-1": "Многие спрашивают: Если у SimpleX нет никаких идентификаторов пользователя, то как приложение знает, куда доставлять сообщения?", + "feature-6-title": "Аудио и видео звонки с E2E-шифрованием", + "hero-p-1": "Другие приложения используют ID пользователей: Signal, Matrix, Session, Briar, Jami, Cwtch и т. п.
    SimpleX не использует, даже случайных цифр.
    Это значительно повышает Вашу конфиденциальность.", + "simplex-network-overlay-card-1-li-2": "В отличие от многих P2P сетей, SimpleX спроектирован так, чтобы не нуждаться в глобальных идентификаторах пользователей, даже временных, используя только временные попарные идентификаторы, обеспечивая лучшую анонимность и защиту метаданных пользователей.", + "simplex-unique-4-title": "Вы владеете
    сетью SimpleX", "privacy-matters-overlay-card-3-p-3": "Обычных людей арестовывают за то, чем они делятся в Интернете, даже через свои \"анонимные\" аккаунты, даже в демократических странах.", "simplex-unique-overlay-card-3-p-2": "Сквозные зашифрованные сообщения временно хранятся на серверах SimpleX до получения, после чего они удаляются безвозвратно.", - "blog": "Новости", - "simplex-private-card-7-point-1": "Чтобы гарантировать целостность, сообщения последовательно нумеруются и включают в себя хэш предыдущего сообщения.", - "simplex-unique-overlay-card-4-p-2": "Платформа SimpleX использует открытый протокол и предоставляет SDK для создания чат-ботов, позволяя внедрять сервисы, с которыми пользователи могут взаимодействовать через приложение SimpleX Chat — мы с нетерпением ждем возможности увидеть, какие сервисы SimpleX вы сможете создать.", + "blog": "Блог", + "simplex-private-card-7-point-1": "Для обеспечения неизменности, сообщения нумеруются по порядку и содержат хэш предыдущего сообщения.", + "simplex-unique-overlay-card-4-p-2": "Сеть SimpleX использует открытый протокол и предоставляет SDK для создания чат-ботов, позволяя внедрять сервисы, с которыми пользователи могут взаимодействовать через приложение SimpleX Chat — мы с нетерпением ждем сервисы SimpleX, которые Вы создадите.", "simplex-explained-tab-1-p-1": "Вы можете создавать контакты и группы, а также вести двусторонние беседы, как и в любом другом мессенджере.", "contact-hero-p-2": "Еще не скачали SimpleX Chat?", - "why-simplex-is": "Почему SimpleX", + "why-simplex-is-unique": "Почему SimpleX уникальный", "simplex-network-section-header": "Сеть SimpleX", "simplex-private-10-title": "Временные анонимные парные идентификаторы", - "privacy-matters-1-overlay-1-linkText": "Конфиденциальность экономит ваши деньги", + "privacy-matters-1-overlay-1-linkText": "Конфиденциальность экономит Ваши деньги", "tap-the-connect-button-in-the-app": "Нажмите на кнопку ’подключиться’ в приложении", "comparison-section-list-point-4a": "Сервера SimpleX не могут скомпрометировать сквозное шифрование", - "unique": "уникальный", "simplex-network-1-overlay-linktext": "проблем P2P сетей", - "no-private": "Нет - приватно", - "simplex-unique-1-title": "У вас есть полная
    конфиденциальность", + "no-private": "Нет - конфиденциальный", + "simplex-unique-1-title": "У Вас есть полная
    конфиденциальность", "protocol-2-text": "XMPP, Matrix", "guide": "Руководство", - "simplex-network-overlay-card-1-li-4": "Реализации P2P могут быть заблокированы некоторыми интернет-провайдерами (например, BitTorrent). SimpleX не зависит от транспорта - он может работать по стандартным веб-протоколам, например WebSockets.", - "hero-overlay-2-title": "Почему идентификаторы пользователя - вредны для приватности?", + "simplex-network-overlay-card-1-li-4": "Реализации P2P могут быть заблокированы некоторыми интернет-провайдерами (например, BitTorrent). SimpleX протокол не зависит от транспорта — он может быть реализован через стандартные веб-протоколы, например WebSockets.", + "hero-overlay-2-title": "Почему идентификаторы пользователя уменьшают конфиденциальность?", "simplex-explained-tab-2-text": "2. Как это работает", "docs-dropdown-4": "Свой SMP Сервер", - "feature-4-title": "Зашифрованные E2E-шифрованием голосовые сообщения", - "privacy-matters-overlay-card-2-p-1": "Не так давно мы наблюдали, как авторитетная консалтинговая компания манипулировала крупными выборами, используя наши социальные графики для искажения нашего мнения из реального мира и манипулируют нашими голосами.", - "privacy-matters-overlay-card-2-p-3": "SimpleX - это первая платформа, которая по своей конструкции не имеет никаких идентификаторов пользователей, таким образом защищая график ваших контактов лучше, чем любая известная альтернатива.", - "learn-more": "Учить больше", + "feature-4-title": "Голосовые сообщения с E2E-шифрованием", + "privacy-matters-overlay-card-2-p-1": "Не так давно мы наблюдали, как авторитетная консалтинговая компания манипулировала крупными выборами, используя наши контакты в социальных сетях, чтобы изменять наши мнения и манипулировать нашими голосами.", + "privacy-matters-overlay-card-2-p-3": "SimpleX — это первая сеть, которая не имеет никаких идентификаторов пользователей, таким образом защищая Ваши контакты лучше, чем любая известная альтернатива.", + "learn-more": "Узнать больше", "donate": "Пожертвовать", "simplex-private-8-title": "Смешивание сообщений
    для уменьшения корреляции", "scan-qr-code-from-mobile-app": "Отсканируйте QR-код в мобильном приложении", "simplex-private-card-3-point-3": "Возобновление соединения отключено для предотвращения сеансовых атак.", - "simplex-private-card-10-point-1": "SimpleX использует временные анонимные попарные адреса и учетные данные для каждого контакта пользователя или члена группы.", + "simplex-private-card-10-point-1": "SimpleX использует временные анонимные парные адреса и учетные данные для каждого контакта пользователя или члена группы.", "guide-dropdown-6": "Аудио и видео Звонки", - "more-info": "Больше информации", + "more-info": "Дополнительная информации", "no-decentralized": "Нет - децентрализованный", "protocol-1-text": "Signal, большие платформы", - "hero-2-header-desc": "В видео показано, как подключиться к своему другу с помощью его одноразового QR-кода, лично или по видеосвязи. Вы также можете подключиться, поделившись ссылкой-приглашением.", - "simplex-network-overlay-card-1-li-6": "Сети P2P могут быть уязвимы для DRDoS атаки, когда клиенты могут ретранслировать и усиливать/увеличивать объём трафика, что приводит к отказу в обслуживании по всей сети. Клиенты SimpleX ретранслируют трафик только из известного соединения и не могут быть использованы злоумышленником для нагрузки трафика всей сети.", - "if-you-already-installed-simplex-chat-for-the-terminal": "Если вы уже установили SimpleX Chat для терминала", + "hero-2-header-desc": "В видео показано, как подключиться к Вашему другу через одноразовый QR-код, при встрече или во время видеосвязи. Вы также можете соединится, поделившись ссылкой-приглашением.", + "simplex-network-overlay-card-1-li-6": "Сети P2P могут быть уязвимы для DRDoS атаки, когда клиенты могут ретранслировать и увеличивать трафик, что приводит к отказу всей сети. Клиенты SimpleX ретранслируют трафик только из известного соединения и не могут быть использованы злоумышленником для создания трафика во всей сети.", + "if-you-already-installed-simplex-chat-for-the-terminal": "Если Вы уже установили SimpleX Chat для терминала", "docs-dropdown-8": "Служба Каталогов SimpleX", - "simplex-private-card-1-point-1": "Протокол с двойным храповым механизмом —
    обмен сообщениями OTR с идеальной секретностью пересылки и восстановлением после взлома.", + "simplex-private-card-1-point-1": "Протокол двойного обновления ключей —
    \"отрицаемые\" сообщения с идеальной прямой секретностью и восстановлением после взлома", "simplex-private-card-8-point-1": "Серверы SimpleX действуют как узлы-миксеры с низкой задержкой — входящие и исходящие сообщения имеют разный порядок.", - "simplex-unique-overlay-card-2-p-1": "Поскольку у вас нет идентификатора на платформе SimpleX, никто не сможет связаться с вами, если вы сами не предоставите одноразовый или временный адрес в виде QR-кода или ссылки.", - "sign-up-to-receive-our-updates": "Введите ваш Email, чтобы получать рассылку обновлений от нас", + "simplex-unique-overlay-card-2-p-1": "Поскольку у Вас нет идентификатора в сети SimpleX, никто не сможет связаться с Вами, если Вы сами не предоставите одноразовый или временный адрес в виде QR-кода или ссылки.", + "sign-up-to-receive-our-updates": "Подпишитесь на нашу рассылку новостей", "guide-dropdown-1": "Быстрый старт", - "simplex-explained-tab-2-p-1": "Для каждого подключения вы используете две отдельные очереди обмена сообщениями, то есть отправка и получения сообщений происходит через разные серверы.", - "simplex-private-section-header": "Что делает SimpleX приватным", - "we-invite-you-to-join-the-conversation": "Мы приглашаем вас присоединиться к беседе", - "feature-2-title": "Изображения, видео и файлы
    зашифрованные E2E-шифрованием", + "simplex-explained-tab-2-p-1": "Для каждого контакта Вы используете две отдельные очереди сообщений, отправляя и получая сообщения через разные серверы.", + "simplex-private-section-header": "Что делает SimpleX конфиденциальным", + "we-invite-you-to-join-the-conversation": "Мы приглашаем Вас присоединиться к разговору", + "feature-2-title": "Изображения, видео и файлы
    с E2E-шифрованием", "simplex-private-9-title": "Однонаправленные
    очереди сообщений", - "simplex-unique-overlay-card-1-p-3": "Этот дизайн защищает конфиденциальность того, с кем вы общаетесь, скрывая это от серверов SimpleX и от любых наблюдателей из вне. Чтобы скрыть свой IP-адрес от серверов, вы можете подключиться к серверам SimpleX через сеть Tor.", + "simplex-unique-overlay-card-1-p-3": "Этот дизайн защищает конфиденциальность Ваших контактов, скрывая их от серверов SimpleX и от любых внешних наблюдателей. Чтобы скрыть свой IP-адрес от серверов, Вы можете подключиться к серверам SimpleX через сеть Tor.", "developers": "Разработчики", - "simplex-private-7-title": "Проверка целостности
    сообщения", - "privacy-matters-overlay-card-1-p-4": "Платформа SimpleX защищает конфиденциальность ваших контактов лучше, чем любая другая альтернатива, полностью предотвращая доступ к вашему социальному графику каким-либо компаниям или организациям. Даже когда люди используют серверы, предоставляемые SimpleX Chat, мы не знаем точное количество пользователей или с кем они общаются.", - "hero-overlay-card-1-p-6": "Подробнее читайте в техническом документе SimpleX.", + "simplex-private-7-title": "Проверка неизменности
    сообщений", + "privacy-matters-overlay-card-1-p-4": "Сеть SimpleX защищает конфиденциальность Ваших контактов лучше, чем альтернативы, предотвращая доступ к Вашей социальной сети каким-либо компаниям или организациям. Даже когда люди используют серверы, предоставляемые SimpleX Chat, мы не знаем точное количество пользователей или с кем они общаются.", + "hero-overlay-card-1-p-6": "Подробнее читайте в техническом описании SimpleX.", "simplex-network-overlay-card-1-p-1": "Протоколы и приложения для обмена сообщениями P2P имеют различные проблемы, которые делают их менее надежными, чем SimpleX, более сложными для анализа и уязвимыми для нескольких типов атак.", "terms-and-privacy-policy": "Политика Конфиденциальности", - "simplex-network-overlay-card-1-li-1": "Сети P2P полагаются на тот или иной вариант DHT для маршрутизации сообщений. Проекты DHT должны обеспечивать баланс между гарантией доставки и задержкой. SimpleX имеет как лучшую гарантию доставки, так и меньшую задержку, чем P2P. В сетях P2P сообщение передается через нескольких узлов, последовательно, кол-во узлов-посредников будет расти параллельно размеру сети - O(log N).", - "privacy-matters-section-label": "Убедитесь, что ваш мессенджер не может получить доступ к вашим данным!", + "simplex-network-overlay-card-1-li-1": "Сети P2P используют DHT (распределенные хэш-таблицы) для маршрутизации сообщений. DHT должны обеспечивать баланс между гарантией доставки и задержкой. SimpleX имеет как лучшую гарантию доставки, так и меньшую задержку, чем P2P. В сетях P2P сообщение передается через нескольких узлов, последовательно, кол-во узлов-посредников будет расти параллельно размеру сети — O(log N).", + "privacy-matters-section-label": "Убедитесь, что Ваш мессенджер не может получить доступ к Вашим данным!", "simplex-unique-overlay-card-3-p-1": "SimpleX Chat хранит все пользовательские данные на клиентских устройствах в портативном формате зашифрованной базы данных которую можно перенести на другое устройство.", "simplex-network-3-desc": "серверы предоставляют однонаправленные очереди для подключения пользователей, но у них нет видимости графика сетевых подключений — это делают только пользователи.", - "simplex-unique-card-1-p-1": "SimpleX защищает конфиденциальность вашего профиля, контактов и метаданных, скрывая их от серверов платформы SimpleX и любых наблюдателей.", - "simplex-private-card-3-point-1": "Для соединений клиент-сервер используется только протокол TLS 1.2/1.3 с надежными алгоритмами.", - "simplex-unique-card-4-p-1": "Сеть SimpleX полностью децентрализована и независима от любой криптовалюты/блокчейна или любой другой платформы, кроме Интернета.", - "features": "Возможности", - "hero-overlay-card-1-p-3": "Вы определяете, какие серверы будете использовать для получения сообщений, а ваши контакты — серверы, которые вы используете для отправки им сообщений. Каждый новый чат, скорее всего, будет вестись на двух разных серверах.", + "simplex-unique-card-1-p-1": "SimpleX защищает конфиденциальность Вашего профиля, контактов и метаданных, скрывая их от серверов сети SimpleX и любых наблюдателей.", + "simplex-private-card-3-point-1": "Для соединений клиента и сервера используется только протокол TLS 1.2/1.3 с безопасными алгоритмами.", + "simplex-unique-card-4-p-1": "Сеть SimpleX полностью децентрализована и независима от любой криптовалюты/блокчейна или любой другой сети, кроме Интернета.", + "features": "Функции", + "hero-overlay-card-1-p-3": "Вы определяете, какие серверы использовать для получения сообщений, а Ваши контакты — серверы, которые Вы используете для отправки им сообщений. Каждый новый чат, скорее всего, будет вестись на двух разных серверах.", "docs-dropdown-9": "Скачать", "simplex-chat-protocol": "Протокол SimpleX Chat", - "simplex-unique-overlay-card-1-p-1": "В отличие от других платформ обмена сообщениями, SimpleX не имеет идентификаторов, присвоенных пользователям. Он не полагается на номера телефонов, доменные адреса (например, электронную почту или XMPP), имена пользователей, открытые ключи или даже случайные числа для идентификации своих пользователей — мы не знаем, сколько людей пользуются нашими SimpleX серверами.", - "reference": "Ссылки", - "f-droid-page-f-droid-org-repo-section-text": "Приложение SimpleX Chat от разработчиков и от репозитория F-Droid.org имеют разные ключи подписи. Если вы хотите сменить одно на другое, вам сначала нужно будет экспортировать базу данных и только потом скачать другое приложение.", - "simplex-private-5-title": "Многоуровневое
    Заполнения содержимого", - "please-use-link-in-mobile-app": "Пожалуйста, воспользуйтесь ссылкой в мобильном приложении", + "simplex-unique-overlay-card-1-p-1": "В отличие от других сетей обмена сообщениями, SimpleX не имеет идентификаторов, присвоенных пользователям. Он не полагается на номера телефонов, доменные адреса (например, электронную почту или XMPP), имена пользователей, публичные ключи или даже случайные числа для идентификации своих пользователей — операторы серверов SimpleX не знают, сколько людей пользуются их серверами.", + "reference": "Документы", + "f-droid-page-f-droid-org-repo-section-text": "Приложение SimpleX Chat от разработчиков и от репозитория F-Droid.org имеют разные ключи подписи. Если Вы хотите сменить одно на другое, Вам сначала нужно будет экспортировать базу данных и только потом скачать другое приложение.", + "simplex-private-5-title": "Многоуровневое
    дополнение сообщений", + "please-use-link-in-mobile-app": "Пожалуйста, используйте ссылку в мобильном приложении", "please-enable-javascript": "Пожалуйста, включите JavaScript, чтобы увидеть QR-код.", - "docs-dropdown-10": "Прозрачность", + "docs-dropdown-10": "Запросы данных", "docs-dropdown-12": "Безопасность", "docs-dropdown-11": "Часто задаваемые вопросы", "docs-dropdown-14": "SimpleX для бизнеса" diff --git a/website/langs/uk.json b/website/langs/uk.json index 794c65c956..71b3254e63 100644 --- a/website/langs/uk.json +++ b/website/langs/uk.json @@ -78,7 +78,7 @@ "smp-protocol": "Протокол SMP", "chat-protocol": "Протокол чату", "donate": "Пожертвувати", - "copyright-label": "© 2020-2024 SimpleX | Проект з відкритим кодом", + "copyright-label": "© 2020-2025 SimpleX | Проект з відкритим кодом", "simplex-chat-protocol": "Протокол чату SimpleX", "terminal-cli": "Термінал CLI", "hero-header": "Приватність переосмислена", @@ -152,7 +152,7 @@ "simplex-network-overlay-card-1-li-2": "У дизайні SimpleX, на відміну від більшості P2P-мереж, немає жодних глобальних ідентифікаторів користувачів будь-якого виду, навіть тимчасових, та використовуються лише тимчасові парні ідентифікатори, що забезпечує кращу анонімність та захист метаданих.", "privacy-matters-overlay-card-1-p-1": "Багато великих компаній використовують інформацію про те, з ким ви з'єднані, щоб оцінити ваш дохід, продавати вам продукти, які вам дійсно не потрібні, і визначати ціни.", "privacy-matters-overlay-card-3-p-3": "Звичайних людей арештовують за те, що вони публікують онлайн, навіть через свої 'анонімні' облікові записи, навіть у демократичних країнах.", - "simplex-unique-overlay-card-1-p-2": "Для доставки повідомлень SimpleX використовує парні анонімні адреси однобічних черг повідомлень, окремо для отриманих та відправлених повідомлень, зазвичай через різні сервери. Використання SimpleX схоже на наявність різної “витратної” електронної пошти або телефону для кожного контакту, і немає неприємностей у їх управлінні.", + "simplex-unique-overlay-card-1-p-2": "Для доставки повідомлень SimpleX використовує парні анонімні адреси однобічних черг повідомлень, окремо для отриманих та відправлених повідомлень, зазвичай через різні сервери.", "simplex-unique-overlay-card-1-p-3": "Цей дизайн захищає конфіденційність осіб, з якими ви спілкуєтеся, приховуючи це від серверів платформи SimpleX та будь-яких спостерігачів. Щоб сховати свою IP-адресу від серверів, ви можете підключитися до серверів SimpleX через Tor.", "simplex-unique-overlay-card-2-p-1": "Оскільки у вас немає ідентифікатора на платформі SimpleX, ніхто не може з вами зв'язатися, якщо ви не поділитеся одноразовою або тимчасовою адресою користувача, у вигляді QR-коду або посилання.", "simplex-unique-overlay-card-2-p-2": "Навіть з необов'язковою адресою користувача, яка може бути використана для відправки спамових запитань на зв'язок, ви можете змінити або повністю видалити її, не втрачаючи жодного з ваших з'єднань.", @@ -174,8 +174,7 @@ "sign-up-to-receive-our-updates": "Підпишіться, щоб отримувати наші оновлення", "enter-your-email-address": "Введіть свою електронну адресу", "get-simplex": "Отримати SimpleX додаток для настільних комп'ютерів", - "why-simplex-is": "Чому SimpleX є", - "unique": "унікальним", + "why-simplex-is-unique": "Чому SimpleX є унікальним", "learn-more": "Дізнатися більше", "more-info": "Додаткова інформація", "hide-info": "Приховати інформацію", diff --git a/website/langs/zh_Hans.json b/website/langs/zh_Hans.json index c32bc2f123..c6712e7bbf 100644 --- a/website/langs/zh_Hans.json +++ b/website/langs/zh_Hans.json @@ -57,7 +57,7 @@ "simplex-chat-protocol": "SimpleX 聊天协议", "smp-protocol": "SMP协议", "chat-protocol": "聊天协议", - "copyright-label": "© 2020-2024 SimpleX | 开源项目", + "copyright-label": "© 2020-2025 SimpleX | 开源项目", "terminal-cli": "命令行程式", "simplex-explained-tab-1-p-1": "您可以创建联系人和群组,并进行双向对话,就像是任何其他即时通讯软件一样。", "hero-p-1": "其他应用——如Signal、Matrix、Session、Briar、Jami、Cwtch 等——都需要用户 ID。
    而SimpleX 不需要用户ID,连随机生成的也不需要。
    这从根本上改善了您的隐私。", @@ -143,7 +143,7 @@ "we-invite-you-to-join-the-conversation": "我们邀请您加入对话", "enter-your-email-address": "输入您的电子邮箱地址", "join-the-REDDIT-community": "加入 REDDIT 社区", - "why-simplex-is": "为什么 SimpleX 是", + "why-simplex-is-unique": "为什么 SimpleX 是 独特的", "learn-more": "了解更多", "join-us-on-GitHub": "在 GitHub 上加入我们", "more-info": "更多信息", @@ -205,7 +205,6 @@ "simplex-unique-overlay-card-2-p-2": "即使使用可选的用户地址,当它被用于发送垃圾邮件联系请求,您可以更改或完全删除它而不会丢失任何连接。", "simplex-unique-overlay-card-3-p-1": "SimpleX Chat 使用便携式加密数据库格式仅将所有用户数据存储在客户端设备上,该格式可以导出并传输到任何支持的设备。", "donate-here-to-help-us": "在这里捐款来帮助我们", - "unique": "独特的", "simplex-unique-card-1-p-2": "与任何其他现有的消息传递平台不同,SimpleX 没有分配给用户的标识符—— 甚至随机数也没有。", "simplex-unique-overlay-card-2-p-1": "因为您在 SimpleX 平台上没有标识符,所以除非您以二维码或链接的形式分享一次性或临时用户地址,没有人可以联系您。", "simplex-unique-overlay-card-3-p-2": "端到端加密的消息在被收到前会暂时保存在 SimpleX 中继服务器上,传送完成后它们会被永久删除。", diff --git a/website/src/_data/languages.json b/website/src/_data/languages.json index 0ac05063cc..340cc59da9 100644 --- a/website/src/_data/languages.json +++ b/website/src/_data/languages.json @@ -33,12 +33,31 @@ "flag": "/img/flags/es.svg", "enabled": true }, + { + "label": "fi", + "name": "Suomi", + "flag": "/img/flags/fi.svg", + "enabled": true + }, { "label": "fr", "name": "Français", "flag": "/img/flags/fr.svg", "enabled": true }, + { + "label": "he", + "name": "עִברִית", + "flag": "/img/flags/il.svg", + "enabled": true, + "rtl": true + }, + { + "label": "hu", + "name": "Magyar", + "flag": "/img/flags/hu.svg", + "enabled": true + }, { "label": "it", "name": "Italiano", @@ -89,9 +108,9 @@ }, { "label": "ru", - "name": "Russian", + "name": "Русский", "flag": "/img/flags/ru.svg", - "enabled": false + "enabled": true } - ] + ] } \ No newline at end of file diff --git a/website/src/_includes/sections/simplex_unique.html b/website/src/_includes/sections/simplex_unique.html index 6759e3a5cb..cdccb3dd5a 100644 --- a/website/src/_includes/sections/simplex_unique.html +++ b/website/src/_includes/sections/simplex_unique.html @@ -1,6 +1,6 @@
    -

    {{ "why-simplex-is" | i18n({}, lang ) | safe }} {{ "unique" | i18n({}, lang ) | safe }}

    +

    {{ "why-simplex-is-unique" | i18n({}, lang ) | safe }}

    diff --git a/website/src/img/flags/fi.svg b/website/src/img/flags/fi.svg new file mode 100644 index 0000000000..470be2d07c --- /dev/null +++ b/website/src/img/flags/fi.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/website/src/img/flags/hu.svg b/website/src/img/flags/hu.svg new file mode 100644 index 0000000000..baddf7f5ea --- /dev/null +++ b/website/src/img/flags/hu.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/website/src/img/flags/il.svg b/website/src/img/flags/il.svg new file mode 100644 index 0000000000..f43be7e8ed --- /dev/null +++ b/website/src/img/flags/il.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + From d53c13f8be83b1c88d44400164dd8ded323ac859 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 25 Apr 2025 11:17:09 +0100 Subject: [PATCH 521/567] docs: dependencies (#5850) --- docs/dependencies/HASKELL.md | 177 ++++++ docs/dependencies/README.md | 9 + .../licences/apps/sqlcipher/LICENSE.md | 24 + .../licences/apps/vlc/COPYING.LIB | 502 ++++++++++++++++++ .../dependencies/licences/apps/webrtc/LICENSE | 24 + .../licences/haskell/OneTuple-0.4.1.1/LICENSE | 34 ++ .../licences/haskell/Only-0.1/LICENSE | 30 ++ .../haskell/QuickCheck-2.14.3/LICENSE | 29 + .../licences/haskell/SHA-1.6.4.4/LICENSE | 29 + .../licences/haskell/StateVar-1.2.2/LICENSE | 29 + .../licences/haskell/aeson-2.2.1.0/LICENSE | 0 .../haskell/ansi-terminal-1.0/LICENSE | 22 + .../ansi-terminal-types-0.11.5/LICENSE | 25 + .../licences/haskell/appar-0.1.8/LICENSE | 29 + .../haskell/asn1-encoding-0.9.6/LICENSE | 27 + .../licences/haskell/asn1-parse-0.9.5/LICENSE | 27 + .../licences/haskell/asn1-types-0.3.4/LICENSE | 27 + .../licences/haskell/assoc-1.1/LICENSE | 30 ++ .../licences/haskell/async-2.2.5/LICENSE | 30 ++ .../haskell/attoparsec-0.14.4/LICENSE | 30 ++ .../haskell/auto-update-0.1.6/LICENSE | 20 + .../haskell/base-orphans-0.9.1/LICENSE | 20 + .../haskell/base64-bytestring-1.2.1.0/LICENSE | 30 ++ .../licences/haskell/basement-0.0.16/LICENSE | 28 + .../licences/haskell/bifunctors-5.6.1/LICENSE | 26 + .../licences/haskell/bitvec-1.1.5.0/LICENSE | 0 .../haskell/blaze-builder-0.4.2.3/LICENSE | 30 ++ .../haskell/blaze-textual-0.2.3.1/LICENSE | 0 .../licences/haskell/boring-0.2.1/LICENSE | 30 ++ .../licences/haskell/byteorder-1.0.4/LICENSE | 30 ++ .../bytestring-builder-0.10.8.2.0/LICENSE | 30 ++ .../haskell/case-insensitive-1.2.1.0/LICENSE | 31 ++ .../licences/haskell/cereal-0.5.8.3/LICENSE | 30 ++ .../licences/haskell/clock-0.8.4/LICENSE | 31 ++ .../licences/haskell/colour-2.3.6/LICENSE | 20 + .../licences/haskell/comonad-5.0.8/LICENSE | 27 + .../haskell/composition-1.0.2.2/LICENSE | 30 ++ .../licences/haskell/conduit-1.3.5/LICENSE | 0 .../haskell/conduit-extra-1.3.6/LICENSE | 0 .../licences/haskell/constraints-0.14/LICENSE | 0 .../haskell/contravariant-1.5.5/LICENSE | 30 ++ .../haskell/cryptohash-md5-0.11.101.0/LICENSE | 28 + .../cryptohash-sha1-0.11.101.0/LICENSE | 28 + .../licences/haskell/crypton-0.34/LICENSE | 28 + .../haskell/crypton-x509-1.7.6/LICENSE | 27 + .../haskell/crypton-x509-store-1.6.9/LICENSE | 27 + .../crypton-x509-validation-1.6.12/LICENSE | 27 + .../haskell/cryptostore-0.3.0.1/LICENSE | 30 ++ .../haskell/data-default-0.7.1.1/LICENSE | 26 + .../data-default-class-0.1.2.0/LICENSE | 26 + .../LICENSE | 26 + .../LICENSE | 26 + .../LICENSE | 26 + .../licences/haskell/data-fix-0.3.2/LICENSE | 30 ++ .../licences/haskell/digest-0.0.1.7/LICENSE | 23 + .../haskell/distributive-0.6.2.1/LICENSE | 26 + .../licences/haskell/dlist-1.0/license.md | 28 + .../licences/haskell/easy-file-0.2.5/LICENSE | 29 + .../haskell/email-validate-2.3.2.19/LICENSE | 0 .../licences/haskell/entropy-0.4.1.10/LICENSE | 30 ++ .../haskell/fast-logger-3.2.2/LICENSE | 29 + .../haskell/file-embed-0.0.15.0/LICENSE | 25 + .../haskell/generically-0.1.1/LICENSE | 28 + .../licences/haskell/hashable-1.4.3.0/LICENSE | 30 ++ .../licences/haskell/hourglass-0.2.12/LICENSE | 27 + .../haskell/http-types-0.12.4/LICENSE | 31 ++ .../licences/haskell/http2-5.0.0/LICENSE | 0 .../haskell/indexed-traversable-0.1.3/LICENSE | 26 + .../LICENSE | 26 + .../licences/haskell/ini-0.4.2/LICENSE | 0 .../integer-conversion-0.1.0.1/LICENSE | 30 ++ .../integer-logarithms-1.0.3.1/LICENSE | 16 + .../licences/haskell/iproute-1.7.12/LICENSE | 29 + .../licences/haskell/libyaml-0.1.2/LICENSE | 0 .../haskell/lifted-base-0.2.3.12/LICENSE | 29 + .../licences/haskell/memory-0.18.0/LICENSE | 29 + .../haskell/monad-control-1.0.3.1/LICENSE | 29 + .../haskell/monad-logger-0.3.40/LICENSE | 0 .../haskell/mono-traversable-1.0.15.3/LICENSE | 0 .../licences/haskell/network-3.1.4.0/LICENSE | 29 + .../haskell/network-byte-order-0.1.7/LICENSE | 30 ++ .../haskell/network-control-0.0.2/LICENSE | 0 .../haskell/network-info-0.2.1/LICENSE | 30 ++ .../haskell/network-transport-0.5.6/LICENSE | 31 ++ .../haskell/network-udp-0.0.0/LICENSE | 29 + .../haskell/network-uri-2.6.4.2/LICENSE | 29 + .../haskell/old-locale-1.0.0.7/LICENSE | 63 +++ .../licences/haskell/old-time-1.1.0.4/LICENSE | 63 +++ .../optparse-applicative-0.18.1.0/LICENSE | 30 ++ .../licences/haskell/pem-0.2.4/LICENSE | 27 + .../haskell/prettyprinter-1.7.1/LICENSE.md | 23 + .../LICENSE.md | 23 + .../haskell/primitive-0.9.0.0/LICENSE | 30 ++ .../licences/haskell/psqueues-0.2.8.0/LICENSE | 31 ++ .../licences/haskell/random-1.2.1.1/LICENSE | 63 +++ .../haskell/record-hasfield-1.0/LICENSE | 30 ++ .../licences/haskell/resourcet-1.3.0/LICENSE | 30 ++ .../haskell/safe-exceptions-0.1.7.4/LICENSE | 20 + .../haskell/scientific-0.3.7.0/LICENSE | 30 ++ .../licences/haskell/semialign-1.3/LICENSE | 30 ++ .../haskell/semigroupoids-6.0.0.1/LICENSE | 26 + .../haskell/simple-logger-0.1.1/LICENSE | 0 .../licences/haskell/socks-0.6.1/LICENSE | 27 + .../licences/haskell/split-0.2.4/LICENSE | 27 + .../licences/haskell/splitmix-0.1.0.5/LICENSE | 30 ++ .../haskell/stm-chans-3.0.0.9/LICENSE | 35 ++ .../haskell/streaming-commons-0.2.2.6/LICENSE | 21 + .../licences/haskell/strict-0.5/LICENSE | 26 + .../licences/haskell/tagged-0.8.8/LICENSE | 30 ++ .../licences/haskell/temporary-1.3/LICENSE | 27 + .../licences/haskell/terminal-0.2.0.0/LICENSE | 30 ++ .../licences/haskell/text-iso8601-0.1/LICENSE | 30 ++ .../licences/haskell/text-short-0.1.5/LICENSE | 30 ++ .../haskell/th-abstraction-0.6.0.0/LICENSE | 13 + .../licences/haskell/th-compat-0.1.4/LICENSE | 30 ++ .../licences/haskell/these-1.2/LICENSE | 30 ++ .../haskell/time-compat-1.9.6.1/LICENSE | 30 ++ .../haskell/time-manager-0.0.1/LICENSE | 20 + .../licences/haskell/tls-1.9.0/LICENSE | 27 + .../haskell/transformers-base-0.4.6/LICENSE | 27 + .../haskell/transformers-compat-0.7.2/LICENSE | 30 ++ .../licences/haskell/type-equality-1/LICENSE | 30 ++ .../haskell/typed-process-0.2.11.1/LICENSE | 20 + .../haskell/unix-compat-0.7.1/LICENSE | 31 ++ .../licences/haskell/unix-time-0.4.11/LICENSE | 29 + .../haskell/unliftio-0.2.25.0/LICENSE | 20 + .../haskell/unliftio-core-0.2.1.0/LICENSE | 20 + .../unordered-containers-0.2.19.1/LICENSE | 30 ++ .../licences/haskell/uuid-1.3.15/LICENSE | 28 + .../haskell/uuid-types-1.0.5.1/LICENSE | 28 + .../licences/haskell/vector-0.13.1.0/LICENSE | 32 ++ .../haskell/vector-algorithms-0.9.0.1/LICENSE | 0 .../haskell/vector-stream-0.1.0.0/LICENSE | 32 ++ .../haskell/websockets-0.12.7.3/LICENCE | 0 .../licences/haskell/witherable-0.4.2/LICENSE | 30 ++ .../licences/haskell/yaml-0.11.11.2/LICENSE | 0 .../licences/haskell/zip-2.0.0/LICENSE.md | 0 .../licences/haskell/zlib-0.6.3.0/LICENSE | 24 + .../licences/haskell/zstd-0.1.3.0/LICENSE | 30 ++ 139 files changed, 4053 insertions(+) create mode 100644 docs/dependencies/HASKELL.md create mode 100644 docs/dependencies/README.md create mode 100644 docs/dependencies/licences/apps/sqlcipher/LICENSE.md create mode 100644 docs/dependencies/licences/apps/vlc/COPYING.LIB create mode 100644 docs/dependencies/licences/apps/webrtc/LICENSE create mode 100644 docs/dependencies/licences/haskell/OneTuple-0.4.1.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/Only-0.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/QuickCheck-2.14.3/LICENSE create mode 100644 docs/dependencies/licences/haskell/SHA-1.6.4.4/LICENSE create mode 100644 docs/dependencies/licences/haskell/StateVar-1.2.2/LICENSE create mode 100644 docs/dependencies/licences/haskell/aeson-2.2.1.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/ansi-terminal-1.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/ansi-terminal-types-0.11.5/LICENSE create mode 100644 docs/dependencies/licences/haskell/appar-0.1.8/LICENSE create mode 100644 docs/dependencies/licences/haskell/asn1-encoding-0.9.6/LICENSE create mode 100644 docs/dependencies/licences/haskell/asn1-parse-0.9.5/LICENSE create mode 100644 docs/dependencies/licences/haskell/asn1-types-0.3.4/LICENSE create mode 100644 docs/dependencies/licences/haskell/assoc-1.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/async-2.2.5/LICENSE create mode 100644 docs/dependencies/licences/haskell/attoparsec-0.14.4/LICENSE create mode 100644 docs/dependencies/licences/haskell/auto-update-0.1.6/LICENSE create mode 100644 docs/dependencies/licences/haskell/base-orphans-0.9.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/base64-bytestring-1.2.1.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/basement-0.0.16/LICENSE create mode 100644 docs/dependencies/licences/haskell/bifunctors-5.6.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/bitvec-1.1.5.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/blaze-builder-0.4.2.3/LICENSE create mode 100644 docs/dependencies/licences/haskell/blaze-textual-0.2.3.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/boring-0.2.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/byteorder-1.0.4/LICENSE create mode 100644 docs/dependencies/licences/haskell/bytestring-builder-0.10.8.2.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/case-insensitive-1.2.1.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/cereal-0.5.8.3/LICENSE create mode 100644 docs/dependencies/licences/haskell/clock-0.8.4/LICENSE create mode 100644 docs/dependencies/licences/haskell/colour-2.3.6/LICENSE create mode 100644 docs/dependencies/licences/haskell/comonad-5.0.8/LICENSE create mode 100644 docs/dependencies/licences/haskell/composition-1.0.2.2/LICENSE create mode 100644 docs/dependencies/licences/haskell/conduit-1.3.5/LICENSE create mode 100644 docs/dependencies/licences/haskell/conduit-extra-1.3.6/LICENSE create mode 100644 docs/dependencies/licences/haskell/constraints-0.14/LICENSE create mode 100644 docs/dependencies/licences/haskell/contravariant-1.5.5/LICENSE create mode 100644 docs/dependencies/licences/haskell/cryptohash-md5-0.11.101.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/cryptohash-sha1-0.11.101.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/crypton-0.34/LICENSE create mode 100644 docs/dependencies/licences/haskell/crypton-x509-1.7.6/LICENSE create mode 100644 docs/dependencies/licences/haskell/crypton-x509-store-1.6.9/LICENSE create mode 100644 docs/dependencies/licences/haskell/crypton-x509-validation-1.6.12/LICENSE create mode 100644 docs/dependencies/licences/haskell/cryptostore-0.3.0.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/data-default-0.7.1.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/data-default-class-0.1.2.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/data-default-instances-containers-0.0.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/data-default-instances-dlist-0.0.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/data-default-instances-old-locale-0.0.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/data-fix-0.3.2/LICENSE create mode 100644 docs/dependencies/licences/haskell/digest-0.0.1.7/LICENSE create mode 100644 docs/dependencies/licences/haskell/distributive-0.6.2.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/dlist-1.0/license.md create mode 100644 docs/dependencies/licences/haskell/easy-file-0.2.5/LICENSE create mode 100644 docs/dependencies/licences/haskell/email-validate-2.3.2.19/LICENSE create mode 100644 docs/dependencies/licences/haskell/entropy-0.4.1.10/LICENSE create mode 100644 docs/dependencies/licences/haskell/fast-logger-3.2.2/LICENSE create mode 100644 docs/dependencies/licences/haskell/file-embed-0.0.15.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/generically-0.1.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/hashable-1.4.3.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/hourglass-0.2.12/LICENSE create mode 100644 docs/dependencies/licences/haskell/http-types-0.12.4/LICENSE create mode 100644 docs/dependencies/licences/haskell/http2-5.0.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/indexed-traversable-0.1.3/LICENSE create mode 100644 docs/dependencies/licences/haskell/indexed-traversable-instances-0.1.1.2/LICENSE create mode 100644 docs/dependencies/licences/haskell/ini-0.4.2/LICENSE create mode 100644 docs/dependencies/licences/haskell/integer-conversion-0.1.0.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/integer-logarithms-1.0.3.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/iproute-1.7.12/LICENSE create mode 100644 docs/dependencies/licences/haskell/libyaml-0.1.2/LICENSE create mode 100644 docs/dependencies/licences/haskell/lifted-base-0.2.3.12/LICENSE create mode 100644 docs/dependencies/licences/haskell/memory-0.18.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/monad-control-1.0.3.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/monad-logger-0.3.40/LICENSE create mode 100644 docs/dependencies/licences/haskell/mono-traversable-1.0.15.3/LICENSE create mode 100644 docs/dependencies/licences/haskell/network-3.1.4.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/network-byte-order-0.1.7/LICENSE create mode 100644 docs/dependencies/licences/haskell/network-control-0.0.2/LICENSE create mode 100644 docs/dependencies/licences/haskell/network-info-0.2.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/network-transport-0.5.6/LICENSE create mode 100644 docs/dependencies/licences/haskell/network-udp-0.0.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/network-uri-2.6.4.2/LICENSE create mode 100644 docs/dependencies/licences/haskell/old-locale-1.0.0.7/LICENSE create mode 100644 docs/dependencies/licences/haskell/old-time-1.1.0.4/LICENSE create mode 100644 docs/dependencies/licences/haskell/optparse-applicative-0.18.1.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/pem-0.2.4/LICENSE create mode 100644 docs/dependencies/licences/haskell/prettyprinter-1.7.1/LICENSE.md create mode 100644 docs/dependencies/licences/haskell/prettyprinter-ansi-terminal-1.1.3/LICENSE.md create mode 100644 docs/dependencies/licences/haskell/primitive-0.9.0.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/psqueues-0.2.8.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/random-1.2.1.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/record-hasfield-1.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/resourcet-1.3.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/safe-exceptions-0.1.7.4/LICENSE create mode 100644 docs/dependencies/licences/haskell/scientific-0.3.7.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/semialign-1.3/LICENSE create mode 100644 docs/dependencies/licences/haskell/semigroupoids-6.0.0.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/simple-logger-0.1.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/socks-0.6.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/split-0.2.4/LICENSE create mode 100644 docs/dependencies/licences/haskell/splitmix-0.1.0.5/LICENSE create mode 100644 docs/dependencies/licences/haskell/stm-chans-3.0.0.9/LICENSE create mode 100644 docs/dependencies/licences/haskell/streaming-commons-0.2.2.6/LICENSE create mode 100644 docs/dependencies/licences/haskell/strict-0.5/LICENSE create mode 100644 docs/dependencies/licences/haskell/tagged-0.8.8/LICENSE create mode 100644 docs/dependencies/licences/haskell/temporary-1.3/LICENSE create mode 100644 docs/dependencies/licences/haskell/terminal-0.2.0.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/text-iso8601-0.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/text-short-0.1.5/LICENSE create mode 100644 docs/dependencies/licences/haskell/th-abstraction-0.6.0.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/th-compat-0.1.4/LICENSE create mode 100644 docs/dependencies/licences/haskell/these-1.2/LICENSE create mode 100644 docs/dependencies/licences/haskell/time-compat-1.9.6.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/time-manager-0.0.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/tls-1.9.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/transformers-base-0.4.6/LICENSE create mode 100644 docs/dependencies/licences/haskell/transformers-compat-0.7.2/LICENSE create mode 100644 docs/dependencies/licences/haskell/type-equality-1/LICENSE create mode 100644 docs/dependencies/licences/haskell/typed-process-0.2.11.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/unix-compat-0.7.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/unix-time-0.4.11/LICENSE create mode 100644 docs/dependencies/licences/haskell/unliftio-0.2.25.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/unliftio-core-0.2.1.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/unordered-containers-0.2.19.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/uuid-1.3.15/LICENSE create mode 100644 docs/dependencies/licences/haskell/uuid-types-1.0.5.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/vector-0.13.1.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/vector-algorithms-0.9.0.1/LICENSE create mode 100644 docs/dependencies/licences/haskell/vector-stream-0.1.0.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/websockets-0.12.7.3/LICENCE create mode 100644 docs/dependencies/licences/haskell/witherable-0.4.2/LICENSE create mode 100644 docs/dependencies/licences/haskell/yaml-0.11.11.2/LICENSE create mode 100644 docs/dependencies/licences/haskell/zip-2.0.0/LICENSE.md create mode 100644 docs/dependencies/licences/haskell/zlib-0.6.3.0/LICENSE create mode 100644 docs/dependencies/licences/haskell/zstd-0.1.3.0/LICENSE diff --git a/docs/dependencies/HASKELL.md b/docs/dependencies/HASKELL.md new file mode 100644 index 0000000000..e6b180e33b --- /dev/null +++ b/docs/dependencies/HASKELL.md @@ -0,0 +1,177 @@ +# Dependency License Report + +Bold-faced **`package-name`**s denote standard libraries bundled with `ghc-9.6.3`. + +## Direct dependencies of `simplex-chat:exe:simplex-chat` + +| Name | Version | [SPDX](https://spdx.org/licenses/) License Id | Description | Also depended upon by | +| --- | --- | --- | --- | --- | +| **`base`** | [`4.18.1.0`](http://hackage.haskell.org/package/base-4.18.1.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/base-4.18.1.0/src/LICENSE) | Basic libraries | *(core library)* | +| `base64-bytestring` | [`1.2.1.0`](http://hackage.haskell.org/package/base64-bytestring-1.2.1.0) | [`BSD-3-Clause`](./licences/haskell/base64-bytestring-1.2.1.0/LICENSE) | Fast base64 encoding and decoding for ByteStrings | `simplexmq`, `websockets` | +| **`bytestring`** | [`0.11.5.2`](http://hackage.haskell.org/package/bytestring-0.11.5.2) | [`BSD-3-Clause`](http://hackage.haskell.org/package/bytestring-0.11.5.2/src/LICENSE) | Fast, compact, strict and lazy byte strings with a list interface | `SHA`, `aeson`, `appar`, `asn1-encoding`, `asn1-parse`, `asn1-types`, `attoparsec`, `base64-bytestring`, `binary`, `bitvec`, `blaze-builder`, `blaze-textual`, `bytestring-builder`, `case-insensitive`, `cereal`, `conduit`, `conduit-extra`, `cryptohash-md5`, `cryptohash-sha1`, `crypton`, `crypton-x509`, `crypton-x509-store`, `crypton-x509-validation`, `cryptostore`, `digest`, `direct-sqlcipher`, `email-validate`, `entropy`, `fast-logger`, `file-embed`, `filepath`, `hashable`, `http-types`, `http2`, `integer-conversion`, `iproute`, `libyaml`, `memory`, `monad-logger`, `mono-traversable`, `network`, `network-byte-order`, `network-transport`, `network-udp`, `parsec`, `pem`, `random`, `scientific`, `simplexmq`, `socks`, `sqlcipher-simple`, `streaming-commons`, `strict`, `terminal`, `text`, `text-short`, `tls`, `typed-process`, `unix`, `unix-time`, `unliftio`, `uuid`, `uuid-types`, `vector-algorithms`, `websockets`, `yaml`, `zip`, `zlib`, `zstd` | +| `composition` | [`1.0.2.2`](http://hackage.haskell.org/package/composition-1.0.2.2) | [`BSD-3-Clause`](./licences/haskell/composition-1.0.2.2/LICENSE) | Combinators for unorthodox function composition | `simplexmq` | +| `constraints` | [`0.14`](http://hackage.haskell.org/package/constraints-0.14) | [`BSD-2-Clause`](./licences/haskell/constraints-0.14/LICENSE) | Constraint manipulation | `simplexmq` | +| **`containers`** | [`0.6.7`](http://hackage.haskell.org/package/containers-0.6.7) | [`BSD-3-Clause`](http://hackage.haskell.org/package/containers-0.6.7/src/LICENSE) | Assorted concrete container types | `QuickCheck`, `aeson`, `attoparsec`, `bifunctors`, `binary`, `cereal`, `comonad`, `crypton-x509`, `crypton-x509-store`, `crypton-x509-validation`, `data-default-instances-containers`, `hashable`, `http2`, `indexed-traversable`, `iproute`, `mono-traversable`, `network-udp`, `resourcet`, `scientific`, `semialign`, `semigroupoids`, `simplexmq`, `sqlcipher-simple`, `th-abstraction`, `websockets`, `witherable`, `yaml`, `zip` | +| `crypton` | [`0.34`](http://hackage.haskell.org/package/crypton-0.34) | [`BSD-3-Clause`](./licences/haskell/crypton-0.34/LICENSE) | Cryptography Primitives sink | `crypton-x509`, `crypton-x509-store`, `crypton-x509-validation`, `cryptostore`, `simplexmq`, `tls` | +| `uuid` | [`1.3.15`](http://hackage.haskell.org/package/uuid-1.3.15) | [`BSD-3-Clause`](./licences/haskell/uuid-1.3.15/LICENSE) | For creating, comparing, parsing and printing Universally Unique Identifiers | | +| **`directory`** | [`1.3.8.1`](http://hackage.haskell.org/package/directory-1.3.8.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/directory-1.3.8.1/src/LICENSE) | Platform-agnostic library for filesystem operations | `conduit`, `conduit-extra`, `crypton-x509-store`, `easy-file`, `fast-logger`, `file-embed`, `network`, `process`, `simplexmq`, `streaming-commons`, `temporary`, `unliftio`, `yaml`, `zip` | +| `direct-sqlcipher` | [`2.3.28`](http://hackage.haskell.org/package/direct-sqlcipher-2.3.28) | *MISSING* | *MISSING* | `simplexmq`, `sqlcipher-simple` | +| `data-default` | [`0.7.1.1`](http://hackage.haskell.org/package/data-default-0.7.1.1) | [`BSD-3-Clause`](./licences/haskell/data-default-0.7.1.1/LICENSE) | A class for types with a default value | `simplexmq` | +| **`exceptions`** | [`0.10.7`](http://hackage.haskell.org/package/exceptions-0.10.7) | [`BSD-3-Clause`](http://hackage.haskell.org/package/exceptions-0.10.7/src/LICENSE) | Extensible optionally-pure exceptions | `aeson`, `conduit`, `filepath`, `monad-logger`, `resourcet`, `safe-exceptions`, `sqlcipher-simple`, `temporary`, `terminal`, `zip` | +| **`filepath`** | [`1.4.100.4`](http://hackage.haskell.org/package/filepath-1.4.100.4) | [`BSD-3-Clause`](http://hackage.haskell.org/package/filepath-1.4.100.4/src/LICENSE) | Library for manipulating FilePaths in a cross platform way. | `conduit`, `conduit-extra`, `crypton-x509-store`, `directory`, `easy-file`, `fast-logger`, `file-embed`, `hashable`, `process`, `simplexmq`, `temporary`, `unix`, `unliftio`, `yaml`, `zip` | +| `file-embed` | [`0.0.15.0`](http://hackage.haskell.org/package/file-embed-0.0.15.0) | [`BSD-2-Clause`](./licences/haskell/file-embed-0.0.15.0/LICENSE) | Use Template Haskell to embed file contents directly. | | +| `http-types` | [`0.12.4`](http://hackage.haskell.org/package/http-types-0.12.4) | [`BSD-3-Clause`](./licences/haskell/http-types-0.12.4/LICENSE) | Generic HTTP types for Haskell (for both client and server code). | `http2`, `simplexmq` | +| `http2` | [`5.0.0`](http://hackage.haskell.org/package/http2-5.0.0) | [`BSD-3-Clause`](./licences/haskell/http2-5.0.0/LICENSE) | HTTP/2 library | `simplexmq` | +| `email-validate` | [`2.3.2.19`](http://hackage.haskell.org/package/email-validate-2.3.2.19) | [`BSD-3-Clause`](./licences/haskell/email-validate-2.3.2.19/LICENSE) | Email address validation | | +| `memory` | [`0.18.0`](http://hackage.haskell.org/package/memory-0.18.0) | [`BSD-3-Clause`](./licences/haskell/memory-0.18.0/LICENSE) | memory and related abstraction stuff | `asn1-types`, `crypton`, `crypton-x509`, `crypton-x509-validation`, `cryptostore`, `pem`, `simplexmq`, `tls` | +| **`mtl`** | [`2.3.1`](http://hackage.haskell.org/package/mtl-2.3.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/mtl-2.3.1/src/LICENSE) | Monad classes for transformers, using functional dependencies | `conduit`, `constraints`, `crypton-x509-store`, `crypton-x509-validation`, `exceptions`, `monad-logger`, `parsec`, `random`, `resourcet`, `simple-logger`, `simplexmq`, `tls`, `yaml`, `zip` | +| `unliftio` | [`0.2.25.0`](http://hackage.haskell.org/package/unliftio-0.2.25.0) | [`MIT`](./licences/haskell/unliftio-0.2.25.0/LICENSE) | The MonadUnliftIO typeclass for unlifting monads to IO (batteries included) | `http2`, `simplexmq`, `time-manager` | +| `unliftio-core` | [`0.2.1.0`](http://hackage.haskell.org/package/unliftio-core-0.2.1.0) | [`MIT`](./licences/haskell/unliftio-core-0.2.1.0/LICENSE) | The MonadUnliftIO typeclass for unlifting monads to IO | `conduit`, `conduit-extra`, `monad-logger`, `resourcet`, `simplexmq`, `typed-process`, `unliftio` | +| `ansi-terminal` | [`1.0`](http://hackage.haskell.org/package/ansi-terminal-1.0) | [`BSD-3-Clause`](./licences/haskell/ansi-terminal-1.0/LICENSE) | Simple ANSI terminal support | `prettyprinter-ansi-terminal` | +| `network` | [`3.1.4.0`](http://hackage.haskell.org/package/network-3.1.4.0) | [`BSD-3-Clause`](./licences/haskell/network-3.1.4.0/LICENSE) | Low-level networking interface | `conduit-extra`, `http2`, `iproute`, `network-udp`, `simplexmq`, `socks`, `streaming-commons`, `tls`, `websockets` | +| `network-transport` | [`0.5.6`](http://hackage.haskell.org/package/network-transport-0.5.6) | [`BSD-3-Clause`](./licences/haskell/network-transport-0.5.6/LICENSE) | Network abstraction layer | `simplexmq` | +| **`process`** | [`1.6.17.0`](http://hackage.haskell.org/package/process-1.6.17.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/process-1.6.17.0/src/LICENSE) | Process libraries | `conduit-extra`, `optparse-applicative`, `simplexmq`, `streaming-commons`, `typed-process`, `unliftio` | +| `optparse-applicative` | [`0.18.1.0`](http://hackage.haskell.org/package/optparse-applicative-0.18.1.0) | [`BSD-3-Clause`](./licences/haskell/optparse-applicative-0.18.1.0/LICENSE) | Utilities and combinators for parsing command line options | `simplexmq` | +| `record-hasfield` | [`1.0`](http://hackage.haskell.org/package/record-hasfield-1.0) | [`BSD-3-Clause`](./licences/haskell/record-hasfield-1.0/LICENSE) | A version of GHC.Records as available in future GHCs. | | +| `random` | [`1.2.1.1`](http://hackage.haskell.org/package/random-1.2.1.1) | [`BSD-3-Clause`](./licences/haskell/random-1.2.1.1/LICENSE) | Pseudo-random number generation | `QuickCheck`, `simplexmq`, `streaming-commons`, `temporary`, `uuid`, `uuid-types`, `websockets` | +| `socks` | [`0.6.1`](http://hackage.haskell.org/package/socks-0.6.1) | [`BSD-3-Clause`](./licences/haskell/socks-0.6.1/LICENSE) | Socks proxy (ver 5) | `simplexmq` | +| `scientific` | [`0.3.7.0`](http://hackage.haskell.org/package/scientific-0.3.7.0) | [`BSD-3-Clause`](./licences/haskell/scientific-0.3.7.0/LICENSE) | Numbers represented using scientific notation | `aeson`, `attoparsec`, `yaml` | +| `simple-logger` | [`0.1.1`](http://hackage.haskell.org/package/simple-logger-0.1.1) | [`MIT`](./licences/haskell/simple-logger-0.1.1/LICENSE) | A very simple but efficient logging framework | `simplexmq` | +| `simplexmq` | [`6.4.0.1`](http://hackage.haskell.org/package/simplexmq-6.4.0.1) | *MISSING* | *MISSING* | | +| `aeson` | [`2.2.1.0`](http://hackage.haskell.org/package/aeson-2.2.1.0) | [`BSD-3-Clause`](./licences/haskell/aeson-2.2.1.0/LICENSE) | Fast JSON parsing and encoding | `simplexmq`, `yaml` | +| `sqlcipher-simple` | [`0.4.18.1`](http://hackage.haskell.org/package/sqlcipher-simple-0.4.18.1) | *MISSING* | *MISSING* | `simplexmq` | +| **`stm`** | [`2.5.1.0`](http://hackage.haskell.org/package/stm-2.5.1.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/stm-2.5.1.0/src/LICENSE) | Software Transactional Memory | `StateVar`, `async`, `conduit-extra`, `exceptions`, `fast-logger`, `http2`, `monad-control`, `monad-logger`, `simplexmq`, `stm-chans`, `streaming-commons`, `terminal`, `transformers-base`, `typed-process`, `unliftio` | +| `async` | [`2.2.5`](http://hackage.haskell.org/package/async-2.2.5) | [`BSD-3-Clause`](./licences/haskell/async-2.2.5/LICENSE) | Run IO operations asynchronously and wait for their results | `conduit-extra`, `http2`, `simplexmq`, `streaming-commons`, `terminal`, `tls`, `typed-process`, `unliftio`, `websockets` | +| **`template-haskell`** | [`2.20.0.0`](http://hackage.haskell.org/package/template-haskell-2.20.0.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/template-haskell-2.20.0.0/src/LICENSE) | Support library for Template Haskell | `OneTuple`, `QuickCheck`, `aeson`, `bifunctors`, `bytestring`, `containers`, `email-validate`, `exceptions`, `file-embed`, `filepath`, `monad-logger`, `network-uri`, `primitive`, `scientific`, `semigroupoids`, `sqlcipher-simple`, `tagged`, `text`, `text-short`, `th-abstraction`, `th-compat`, `unordered-containers`, `uuid-types`, `yaml` | +| **`text`** | [`2.0.2`](http://hackage.haskell.org/package/text-2.0.2) | [`BSD-2-Clause`](http://hackage.haskell.org/package/text-2.0.2/src/LICENSE) | An efficient packed Unicode text type. | `aeson`, `attoparsec`, `blaze-builder`, `blaze-textual`, `case-insensitive`, `conduit`, `conduit-extra`, `direct-sqlcipher`, `fast-logger`, `hashable`, `http-types`, `ini`, `integer-conversion`, `monad-logger`, `mono-traversable`, `optparse-applicative`, `parsec`, `prettyprinter`, `prettyprinter-ansi-terminal`, `scientific`, `simple-logger`, `simplexmq`, `sqlcipher-simple`, `streaming-commons`, `strict`, `terminal`, `text-iso8601`, `text-short`, `uuid`, `uuid-types`, `websockets`, `yaml`, `zip` | +| **`time`** | [`1.12.2`](http://hackage.haskell.org/package/time-1.12.2) | [`BSD-2-Clause`](http://hackage.haskell.org/package/time-1.12.2/src/LICENSE) | A time library | `aeson`, `directory`, `easy-file`, `iso8601-time`, `simplexmq`, `sqlcipher-simple`, `text-iso8601`, `time-compat`, `unix`, `unliftio`, `uuid`, `zip` | +| `tls` | [`1.9.0`](http://hackage.haskell.org/package/tls-1.9.0) | [`BSD-3-Clause`](./licences/haskell/tls-1.9.0/LICENSE) | TLS/SSL protocol native implementation (Server and Client) | `simplexmq` | +| `terminal` | [`0.2.0.0`](http://hackage.haskell.org/package/terminal-0.2.0.0) | [`BSD-3-Clause`](./licences/haskell/terminal-0.2.0.0/LICENSE) | Portable terminal interaction library | | +| `attoparsec` | [`0.14.4`](http://hackage.haskell.org/package/attoparsec-0.14.4) | [`BSD-3-Clause`](./licences/haskell/attoparsec-0.14.4/LICENSE) | Fast combinator parsing for bytestrings and text | `conduit-extra`, `email-validate`, `ini`, `simplexmq`, `sqlcipher-simple`, `websockets`, `yaml` | +| `websockets` | [`0.12.7.3`](http://hackage.haskell.org/package/websockets-0.12.7.3) | [`BSD-3-Clause`](./licences/haskell/websockets-0.12.7.3/LICENCE) | A sensible and clean way to write WebSocket-capable servers in Haskell. | `simplexmq` | +| `zip` | [`2.0.0`](http://hackage.haskell.org/package/zip-2.0.0) | [`BSD-3-Clause`](./licences/haskell/zip-2.0.0/LICENSE.md) | Operations on zip archives | | + +## Indirect transitive dependencies + +| Name | Version | [SPDX](https://spdx.org/licenses/) License Id | Description | Depended upon by | +| --- | --- | --- | --- | --- | +| `OneTuple` | [`0.4.1.1`](http://hackage.haskell.org/package/OneTuple-0.4.1.1) | [`BSD-3-Clause`](./licences/haskell/OneTuple-0.4.1.1/LICENSE) | Singleton Tuple | `aeson`, `indexed-traversable-instances` | +| `Only` | [`0.1`](http://hackage.haskell.org/package/Only-0.1) | [`BSD-3-Clause`](./licences/haskell/Only-0.1/LICENSE) | The 1-tuple type or single-value "collection" | `sqlcipher-simple` | +| `QuickCheck` | [`2.14.3`](http://hackage.haskell.org/package/QuickCheck-2.14.3) | [`BSD-3-Clause`](./licences/haskell/QuickCheck-2.14.3/LICENSE) | Automatic testing of Haskell programs | `aeson` | +| `SHA` | [`1.6.4.4`](http://hackage.haskell.org/package/SHA-1.6.4.4) | [`BSD-3-Clause`](./licences/haskell/SHA-1.6.4.4/LICENSE) | Implementations of the SHA suite of message digest functions | `websockets` | +| `StateVar` | [`1.2.2`](http://hackage.haskell.org/package/StateVar-1.2.2) | [`BSD-3-Clause`](./licences/haskell/StateVar-1.2.2/LICENSE) | State variables | `contravariant` | +| **`array`** | [`0.5.5.0`](http://hackage.haskell.org/package/array-0.5.5.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/array-0.5.5.0/src/LICENSE) | Mutable and immutable arrays | `SHA`, `attoparsec`, `binary`, `cereal`, `containers`, `deepseq`, `fast-logger`, `http-types`, `http2`, `indexed-traversable`, `integer-logarithms`, `stm`, `streaming-commons`, `text` | +| `bifunctors` | [`5.6.1`](http://hackage.haskell.org/package/bifunctors-5.6.1) | [`BSD-3-Clause`](./licences/haskell/bifunctors-5.6.1/LICENSE) | Bifunctors | `semigroupoids` | +| **`binary`** | [`0.8.9.1`](http://hackage.haskell.org/package/binary-0.8.9.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/binary-0.8.9.1/src/LICENSE) | Binary serialisation for Haskell values using lazy ByteStrings | `SHA`, `constraints`, `network-transport`, `scientific`, `strict`, `text`, `text-short`, `these`, `unix-time`, `uuid`, `uuid-types`, `websockets` | +| `blaze-builder` | [`0.4.2.3`](http://hackage.haskell.org/package/blaze-builder-0.4.2.3) | [`BSD-3-Clause`](./licences/haskell/blaze-builder-0.4.2.3/LICENSE) | Efficient buffered output. | `blaze-textual`, `sqlcipher-simple` | +| `blaze-textual` | [`0.2.3.1`](http://hackage.haskell.org/package/blaze-textual-0.2.3.1) | [`BSD-3-Clause`](./licences/haskell/blaze-textual-0.2.3.1/LICENSE) | Fast rendering of common datatypes | `sqlcipher-simple` | +| `boring` | [`0.2.1`](http://hackage.haskell.org/package/boring-0.2.1) | [`BSD-3-Clause`](./licences/haskell/boring-0.2.1/LICENSE) | Boring and Absurd types | `constraints` | +| `base-orphans` | [`0.9.1`](http://hackage.haskell.org/package/base-orphans-0.9.1) | [`MIT`](./licences/haskell/base-orphans-0.9.1/LICENSE) | Backwards-compatible orphan instances for base | `distributive`, `semigroupoids`, `time-compat`, `transformers-base`, `witherable` | +| `basement` | [`0.0.16`](http://hackage.haskell.org/package/basement-0.0.16) | [`BSD-3-Clause`](./licences/haskell/basement-0.0.16/LICENSE) | Foundation scrap box of array & string | `crypton`, `cryptostore`, `memory`, `pem`, `socks` | +| `bitvec` | [`1.1.5.0`](http://hackage.haskell.org/package/bitvec-1.1.5.0) | [`BSD-3-Clause`](./licences/haskell/bitvec-1.1.5.0/LICENSE) | Space-efficient bit vectors | `vector-algorithms` | +| `byteorder` | [`1.0.4`](http://hackage.haskell.org/package/byteorder-1.0.4) | [`BSD-3-Clause`](./licences/haskell/byteorder-1.0.4/LICENSE) | Exposes the native endianness or byte ordering of the system. | `iproute` | +| `bytestring-builder` | [`0.10.8.2.0`](http://hackage.haskell.org/package/bytestring-builder-0.10.8.2.0) | [`BSD-3-Clause`](./licences/haskell/bytestring-builder-0.10.8.2.0/LICENSE) | The new bytestring builder, packaged outside of GHC | `websockets` | +| `clock` | [`0.8.4`](http://hackage.haskell.org/package/clock-0.8.4) | [`BSD-3-Clause`](./licences/haskell/clock-0.8.4/LICENSE) | High-resolution clock functions: monotonic, realtime, cputime. | `websockets` | +| `colour` | [`2.3.6`](http://hackage.haskell.org/package/colour-2.3.6) | [`MIT`](./licences/haskell/colour-2.3.6/LICENSE) | A model for human colour/color perception | `ansi-terminal`, `ansi-terminal-types` | +| `comonad` | [`5.0.8`](http://hackage.haskell.org/package/comonad-5.0.8) | [`BSD-3-Clause`](./licences/haskell/comonad-5.0.8/LICENSE) | Comonads | `bifunctors`, `semigroupoids` | +| `conduit` | [`1.3.5`](http://hackage.haskell.org/package/conduit-1.3.5) | [`MIT`](./licences/haskell/conduit-1.3.5/LICENSE) | Streaming data processing library. | `conduit-extra`, `libyaml`, `monad-logger`, `yaml`, `zip` | +| `conduit-extra` | [`1.3.6`](http://hackage.haskell.org/package/conduit-extra-1.3.6) | [`MIT`](./licences/haskell/conduit-extra-1.3.6/LICENSE) | Batteries included conduit: adapters for common libraries. | `monad-logger`, `zip` | +| `contravariant` | [`1.5.5`](http://hackage.haskell.org/package/contravariant-1.5.5) | [`BSD-3-Clause`](./licences/haskell/contravariant-1.5.5/LICENSE) | Contravariant functors | `semigroupoids` | +| `cereal` | [`0.5.8.3`](http://hackage.haskell.org/package/cereal-0.5.8.3) | [`BSD-3-Clause`](./licences/haskell/cereal-0.5.8.3/LICENSE) | A binary serialization library | `socks`, `tls`, `zip` | +| `cryptohash-md5` | [`0.11.101.0`](http://hackage.haskell.org/package/cryptohash-md5-0.11.101.0) | [`BSD-3-Clause`](./licences/haskell/cryptohash-md5-0.11.101.0/LICENSE) | Fast, pure and practical MD5 implementation | `uuid` | +| `cryptohash-sha1` | [`0.11.101.0`](http://hackage.haskell.org/package/cryptohash-sha1-0.11.101.0) | [`BSD-3-Clause`](./licences/haskell/cryptohash-sha1-0.11.101.0/LICENSE) | Fast, pure and practical SHA-1 implementation | `uuid` | +| `crypton-x509` | [`1.7.6`](http://hackage.haskell.org/package/crypton-x509-1.7.6) | [`BSD-3-Clause`](./licences/haskell/crypton-x509-1.7.6/LICENSE) | X509 reader and writer | `crypton-x509-store`, `crypton-x509-validation`, `cryptostore`, `simplexmq`, `tls` | +| `crypton-x509-store` | [`1.6.9`](http://hackage.haskell.org/package/crypton-x509-store-1.6.9) | [`BSD-3-Clause`](./licences/haskell/crypton-x509-store-1.6.9/LICENSE) | X.509 collection accessing and storing methods | `crypton-x509-validation`, `simplexmq`, `tls` | +| `crypton-x509-validation` | [`1.6.12`](http://hackage.haskell.org/package/crypton-x509-validation-1.6.12) | [`BSD-3-Clause`](./licences/haskell/crypton-x509-validation-1.6.12/LICENSE) | X.509 Certificate and CRL validation | `cryptostore`, `simplexmq`, `tls` | +| `cryptostore` | [`0.3.0.1`](http://hackage.haskell.org/package/cryptostore-0.3.0.1) | [`BSD-3-Clause`](./licences/haskell/cryptostore-0.3.0.1/LICENSE) | Serialization of cryptographic data types | `simplexmq` | +| `case-insensitive` | [`1.2.1.0`](http://hackage.haskell.org/package/case-insensitive-1.2.1.0) | [`BSD-3-Clause`](./licences/haskell/case-insensitive-1.2.1.0/LICENSE) | Case insensitive string comparison | `http-types`, `http2`, `simplexmq`, `websockets`, `zip` | +| `uuid-types` | [`1.0.5.1`](http://hackage.haskell.org/package/uuid-types-1.0.5.1) | [`BSD-3-Clause`](./licences/haskell/uuid-types-1.0.5.1/LICENSE) | Type definitions for Universally Unique Identifiers | `aeson`, `uuid` | +| **`deepseq`** | [`1.4.8.1`](http://hackage.haskell.org/package/deepseq-1.4.8.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/deepseq-1.4.8.1/src/LICENSE) | Deep evaluation of data structures | `Only`, `QuickCheck`, `aeson`, `attoparsec`, `bitvec`, `blaze-builder`, `bytestring`, `bytestring-builder`, `case-insensitive`, `constraints`, `containers`, `crypton`, `data-fix`, `dlist`, `filepath`, `hashable`, `hourglass`, `memory`, `network`, `network-transport`, `network-uri`, `pretty`, `primitive`, `process`, `psqueues`, `random`, `safe-exceptions`, `scientific`, `splitmix`, `strict`, `tagged`, `text`, `text-short`, `these`, `time`, `time-compat`, `unliftio`, `unordered-containers`, `uuid-types`, `vector`, `zstd` | +| `digest` | [`0.0.1.7`](http://hackage.haskell.org/package/digest-0.0.1.7) | [`BSD-2-Clause`](./licences/haskell/digest-0.0.1.7/LICENSE) | Various hashes for bytestrings; CRC32 and Adler32 for now. | `zip` | +| `dlist` | [`1.0`](http://hackage.haskell.org/package/dlist-1.0) | [`BSD-3-Clause`](./licences/haskell/dlist-1.0/license.md) | Difference lists | `aeson`, `data-default-instances-dlist`, `zip` | +| `distributive` | [`0.6.2.1`](http://hackage.haskell.org/package/distributive-0.6.2.1) | [`BSD-3-Clause`](./licences/haskell/distributive-0.6.2.1/LICENSE) | Distributive functors -- Dual to Traversable | `comonad`, `semigroupoids` | +| `data-default-class` | [`0.1.2.0`](http://hackage.haskell.org/package/data-default-class-0.1.2.0) | [`BSD-3-Clause`](./licences/haskell/data-default-class-0.1.2.0/LICENSE) | A class for types with a default value | `crypton-x509-validation`, `data-default`, `data-default-instances-containers`, `data-default-instances-dlist`, `data-default-instances-old-locale`, `tls` | +| `data-default-instances-containers` | [`0.0.1`](http://hackage.haskell.org/package/data-default-instances-containers-0.0.1) | [`BSD-3-Clause`](./licences/haskell/data-default-instances-containers-0.0.1/LICENSE) | Default instances for types in containers | `data-default` | +| `data-default-instances-dlist` | [`0.0.1`](http://hackage.haskell.org/package/data-default-instances-dlist-0.0.1) | [`BSD-3-Clause`](./licences/haskell/data-default-instances-dlist-0.0.1/LICENSE) | Default instances for types in dlist | `data-default` | +| `data-default-instances-old-locale` | [`0.0.1`](http://hackage.haskell.org/package/data-default-instances-old-locale-0.0.1) | [`BSD-3-Clause`](./licences/haskell/data-default-instances-old-locale-0.0.1/LICENSE) | Default instances for types in old-locale | `data-default` | +| `data-fix` | [`0.3.2`](http://hackage.haskell.org/package/data-fix-0.3.2) | [`BSD-3-Clause`](./licences/haskell/data-fix-0.3.2/LICENSE) | Fixpoint data types | `aeson` | +| `fast-logger` | [`3.2.2`](http://hackage.haskell.org/package/fast-logger-3.2.2) | [`BSD-3-Clause`](./licences/haskell/fast-logger-3.2.2/LICENSE) | A fast logging system | `monad-logger`, `simple-logger` | +| **`ghc-bignum`** | [`1.3`](http://hackage.haskell.org/package/ghc-bignum-1.3) | [`BSD-3-Clause`](http://hackage.haskell.org/package/ghc-bignum-1.3/src/LICENSE) | GHC BigNum library | `base`, `bitvec`, `hashable`, `integer-gmp`, `integer-logarithms` | +| **`ghc-boot-th`** | [`9.6.3`](http://hackage.haskell.org/package/ghc-boot-th-9.6.3) | [`BSD-3-Clause`](http://hackage.haskell.org/package/ghc-boot-th-9.6.3/src/LICENSE) | Shared functionality between GHC and the @template-haskell@ library | `template-haskell` | +| **`ghc-prim`** | [`0.10.0`](http://hackage.haskell.org/package/ghc-prim-0.10.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/ghc-prim-0.10.0/src/LICENSE) | GHC primitives | *(core library)* | +| `generically` | [`0.1.1`](http://hackage.haskell.org/package/generically-0.1.1) | [`BSD-3-Clause`](./licences/haskell/generically-0.1.1/LICENSE) | Generically newtype to use with DerivingVia | `aeson` | +| `hourglass` | [`0.2.12`](http://hackage.haskell.org/package/hourglass-0.2.12) | [`BSD-3-Clause`](./licences/haskell/hourglass-0.2.12/LICENSE) | simple performant time related library | `asn1-encoding`, `asn1-types`, `crypton-x509`, `crypton-x509-validation`, `cryptostore`, `simplexmq` | +| `hashable` | [`1.4.3.0`](http://hackage.haskell.org/package/hashable-1.4.3.0) | [`BSD-3-Clause`](./licences/haskell/hashable-1.4.3.0/LICENSE) | A class for types that can be converted to a hash value | `aeson`, `async`, `case-insensitive`, `constraints`, `data-fix`, `mono-traversable`, `network-transport`, `psqueues`, `scientific`, `semialign`, `semigroupoids`, `simplexmq`, `strict`, `text-short`, `these`, `time-compat`, `unordered-containers`, `uuid-types`, `witherable` | +| **`integer-gmp`** | [`1.1`](http://hackage.haskell.org/package/integer-gmp-1.1) | [`BSD-3-Clause`](http://hackage.haskell.org/package/integer-gmp-1.1/src/LICENSE) | Integer library based on GMP | *(core library)* | +| `libyaml` | [`0.1.2`](http://hackage.haskell.org/package/libyaml-0.1.2) | [`BSD-3-Clause`](./licences/haskell/libyaml-0.1.2/LICENSE) | Low-level, streaming YAML interface. | `yaml` | +| `old-locale` | [`1.0.0.7`](http://hackage.haskell.org/package/old-locale-1.0.0.7) | [`BSD-3-Clause`](./licences/haskell/old-locale-1.0.0.7/LICENSE) | locale library | `blaze-textual`, `data-default-instances-old-locale`, `old-time` | +| `old-time` | [`1.1.0.4`](http://hackage.haskell.org/package/old-time-1.1.0.4) | [`BSD-3-Clause`](./licences/haskell/old-time-1.1.0.4/LICENSE) | Time library | `unix-time` | +| `lifted-base` | [`0.2.3.12`](http://hackage.haskell.org/package/lifted-base-0.2.3.12) | [`BSD-3-Clause`](./licences/haskell/lifted-base-0.2.3.12/LICENSE) | lifted IO operations from the base library | `monad-logger` | +| `mono-traversable` | [`1.0.15.3`](http://hackage.haskell.org/package/mono-traversable-1.0.15.3) | [`MIT`](./licences/haskell/mono-traversable-1.0.15.3/LICENSE) | Type classes for mapping, folding, and traversing monomorphic containers | `conduit` | +| `monad-control` | [`1.0.3.1`](http://hackage.haskell.org/package/monad-control-1.0.3.1) | [`BSD-3-Clause`](./licences/haskell/monad-control-1.0.3.1/LICENSE) | Lift control operations, like exception catching, through monad transformers | `lifted-base`, `monad-logger`, `zip` | +| `monad-logger` | [`0.3.40`](http://hackage.haskell.org/package/monad-logger-0.3.40) | [`MIT`](./licences/haskell/monad-logger-0.3.40/LICENSE) | A class of monads which can log messages. | `simple-logger` | +| `monad-loops` | [`0.4.3`](http://hackage.haskell.org/package/monad-loops-0.4.3) | [`LicenseRef-PublicDomain`](http://hackage.haskell.org/package/monad-loops-0.4.3) | Monadic loops | `monad-logger` | +| `ini` | [`0.4.2`](http://hackage.haskell.org/package/ini-0.4.2) | [`BSD-3-Clause`](./licences/haskell/ini-0.4.2/LICENSE) | Configuration files in the INI format. | `simplexmq` | +| `indexed-traversable` | [`0.1.3`](http://hackage.haskell.org/package/indexed-traversable-0.1.3) | [`BSD-2-Clause`](./licences/haskell/indexed-traversable-0.1.3/LICENSE) | FunctorWithIndex, FoldableWithIndex, TraversableWithIndex | `aeson`, `comonad`, `indexed-traversable-instances`, `semialign`, `witherable` | +| `indexed-traversable-instances` | [`0.1.1.2`](http://hackage.haskell.org/package/indexed-traversable-instances-0.1.1.2) | [`BSD-2-Clause`](./licences/haskell/indexed-traversable-instances-0.1.1.2/LICENSE) | More instances of FunctorWithIndex, FoldableWithIndex, TraversableWithIndex | `semialign`, `witherable` | +| `unordered-containers` | [`0.2.19.1`](http://hackage.haskell.org/package/unordered-containers-0.2.19.1) | [`BSD-3-Clause`](./licences/haskell/unordered-containers-0.2.19.1/LICENSE) | Efficient hashing-based container types | `aeson`, `indexed-traversable-instances`, `ini`, `mono-traversable`, `semialign`, `semigroupoids`, `witherable`, `yaml` | +| `ansi-terminal-types` | [`0.11.5`](http://hackage.haskell.org/package/ansi-terminal-types-0.11.5) | [`BSD-3-Clause`](./licences/haskell/ansi-terminal-types-0.11.5/LICENSE) | Types and functions used to represent SGR aspects | `ansi-terminal` | +| `integer-conversion` | [`0.1.0.1`](http://hackage.haskell.org/package/integer-conversion-0.1.0.1) | [`BSD-3-Clause`](./licences/haskell/integer-conversion-0.1.0.1/LICENSE) | Conversion from strings to Integer | `aeson`, `text-iso8601` | +| `integer-logarithms` | [`1.0.3.1`](http://hackage.haskell.org/package/integer-logarithms-1.0.3.1) | [`MIT`](./licences/haskell/integer-logarithms-1.0.3.1/LICENSE) | Integer logarithms. | `aeson`, `scientific` | +| `entropy` | [`0.4.1.10`](http://hackage.haskell.org/package/entropy-0.4.1.10) | [`BSD-3-Clause`](./licences/haskell/entropy-0.4.1.10/LICENSE) | A platform independent entropy source | `uuid`, `websockets` | +| `network-byte-order` | [`0.1.7`](http://hackage.haskell.org/package/network-byte-order-0.1.7) | [`BSD-3-Clause`](./licences/haskell/network-byte-order-0.1.7/LICENSE) | Network byte order utilities | `http2` | +| `network-control` | [`0.0.2`](http://hackage.haskell.org/package/network-control-0.0.2) | [`BSD-3-Clause`](./licences/haskell/network-control-0.0.2/LICENSE) | Library to control network protocols | `http2` | +| `network-udp` | [`0.0.0`](http://hackage.haskell.org/package/network-udp-0.0.0) | [`BSD-3-Clause`](./licences/haskell/network-udp-0.0.0/LICENSE) | UDP library | `simplexmq` | +| `network-info` | [`0.2.1`](http://hackage.haskell.org/package/network-info-0.2.1) | [`BSD-3-Clause`](./licences/haskell/network-info-0.2.1/LICENSE) | Access the local computer's basic network configuration | `simplexmq`, `uuid` | +| `network-uri` | [`2.6.4.2`](http://hackage.haskell.org/package/network-uri-2.6.4.2) | [`BSD-3-Clause`](./licences/haskell/network-uri-2.6.4.2/LICENSE) | URI manipulation | `aeson` | +| `unix-compat` | [`0.7.1`](http://hackage.haskell.org/package/unix-compat-0.7.1) | [`BSD-3-Clause`](./licences/haskell/unix-compat-0.7.1/LICENSE) | Portable POSIX-compatibility layer. | `fast-logger` | +| `unix-time` | [`0.4.11`](http://hackage.haskell.org/package/unix-time-0.4.11) | [`BSD-3-Clause`](./licences/haskell/unix-time-0.4.11/LICENSE) | Unix time parser/formatter and utilities | `fast-logger`, `http2`, `network-control`, `tls` | +| **`parsec`** | [`3.1.16.1`](http://hackage.haskell.org/package/parsec-3.1.16.1) | [`BSD-2-Clause`](http://hackage.haskell.org/package/parsec-3.1.16.1/src/LICENSE) | Monadic parser combinators | `network-uri` | +| `pem` | [`0.2.4`](http://hackage.haskell.org/package/pem-0.2.4) | [`BSD-3-Clause`](./licences/haskell/pem-0.2.4/LICENSE) | Privacy Enhanced Mail (PEM) format reader and writer. | `crypton-x509`, `crypton-x509-store`, `crypton-x509-validation`, `cryptostore` | +| `appar` | [`0.1.8`](http://hackage.haskell.org/package/appar-0.1.8) | [`BSD-3-Clause`](./licences/haskell/appar-0.1.8/LICENSE) | A simple applicative parser | `iproute` | +| **`pretty`** | [`1.1.3.6`](http://hackage.haskell.org/package/pretty-1.1.3.6) | [`BSD-3-Clause`](http://hackage.haskell.org/package/pretty-1.1.3.6/src/LICENSE) | Pretty-printing library | `template-haskell` | +| `primitive` | [`0.9.0.0`](http://hackage.haskell.org/package/primitive-0.9.0.0) | [`BSD-3-Clause`](./licences/haskell/primitive-0.9.0.0/LICENSE) | Primitive memory-related operations | `aeson`, `bitvec`, `conduit`, `conduit-extra`, `integer-conversion`, `resourcet`, `scientific`, `vector`, `vector-algorithms` | +| `iproute` | [`1.7.12`](http://hackage.haskell.org/package/iproute-1.7.12) | [`BSD-3-Clause`](./licences/haskell/iproute-1.7.12/LICENSE) | IP Routing Table | `network-udp`, `simplexmq` | +| `prettyprinter` | [`1.7.1`](http://hackage.haskell.org/package/prettyprinter-1.7.1) | [`BSD-2-Clause`](./licences/haskell/prettyprinter-1.7.1/LICENSE.md) | A modern, easy to use, well-documented, extensible pretty-printer. | `optparse-applicative`, `prettyprinter-ansi-terminal`, `terminal` | +| `prettyprinter-ansi-terminal` | [`1.1.3`](http://hackage.haskell.org/package/prettyprinter-ansi-terminal-1.1.3) | [`BSD-2-Clause`](./licences/haskell/prettyprinter-ansi-terminal-1.1.3/LICENSE.md) | ANSI terminal backend for the »prettyprinter« package. | `optparse-applicative` | +| `psqueues` | [`0.2.8.0`](http://hackage.haskell.org/package/psqueues-0.2.8.0) | [`BSD-3-Clause`](./licences/haskell/psqueues-0.2.8.0/LICENSE) | Pure priority search queues | `network-control` | +| `resourcet` | [`1.3.0`](http://hackage.haskell.org/package/resourcet-1.3.0) | [`BSD-3-Clause`](./licences/haskell/resourcet-1.3.0/LICENSE) | Deterministic allocation and freeing of scarce resources. | `conduit`, `conduit-extra`, `libyaml`, `monad-logger`, `yaml`, `zip` | +| `iso8601-time` | [`0.1.5`](http://hackage.haskell.org/package/iso8601-time-0.1.5) | [`MIT`](http://hackage.haskell.org/package/iso8601-time-0.1.5) | Convert to/from the ISO 8601 time format | `simplexmq` | +| `safe-exceptions` | [`0.1.7.4`](http://hackage.haskell.org/package/safe-exceptions-0.1.7.4) | [`MIT`](./licences/haskell/safe-exceptions-0.1.7.4/LICENSE) | Safe, consistent, and easy exception handling | `unliftio` | +| `semigroupoids` | [`6.0.0.1`](http://hackage.haskell.org/package/semigroupoids-6.0.0.1) | [`BSD-2-Clause`](./licences/haskell/semigroupoids-6.0.0.1/LICENSE) | Semigroupoids: Category sans id | `semialign` | +| `semialign` | [`1.3`](http://hackage.haskell.org/package/semialign-1.3) | [`BSD-3-Clause`](./licences/haskell/semialign-1.3/LICENSE) | Align and Zip type-classes from the common Semialign ancestor. | `aeson` | +| `asn1-encoding` | [`0.9.6`](http://hackage.haskell.org/package/asn1-encoding-0.9.6) | [`BSD-3-Clause`](./licences/haskell/asn1-encoding-0.9.6/LICENSE) | ASN1 data reader and writer in RAW, BER and DER forms | `asn1-parse`, `crypton-x509`, `crypton-x509-store`, `crypton-x509-validation`, `cryptostore`, `simplexmq`, `tls` | +| `asn1-parse` | [`0.9.5`](http://hackage.haskell.org/package/asn1-parse-0.9.5) | [`BSD-3-Clause`](./licences/haskell/asn1-parse-0.9.5/LICENSE) | Simple monadic parser for ASN1 stream types. | `crypton-x509` | +| `asn1-types` | [`0.3.4`](http://hackage.haskell.org/package/asn1-types-0.3.4) | [`BSD-3-Clause`](./licences/haskell/asn1-types-0.3.4/LICENSE) | ASN.1 types | `asn1-encoding`, `asn1-parse`, `crypton-x509`, `crypton-x509-store`, `crypton-x509-validation`, `cryptostore`, `simplexmq`, `tls` | +| `split` | [`0.2.4`](http://hackage.haskell.org/package/split-0.2.4) | [`BSD-3-Clause`](./licences/haskell/split-0.2.4/LICENSE) | Combinator library for splitting lists. | `mono-traversable` | +| `splitmix` | [`0.1.0.5`](http://hackage.haskell.org/package/splitmix-0.1.0.5) | [`BSD-3-Clause`](./licences/haskell/splitmix-0.1.0.5/LICENSE) | Fast Splittable PRNG | `QuickCheck`, `random` | +| `assoc` | [`1.1`](http://hackage.haskell.org/package/assoc-1.1) | [`BSD-3-Clause`](./licences/haskell/assoc-1.1/LICENSE) | swap and assoc: Symmetric and Semigroupy Bifunctors | `bifunctors`, `strict`, `these` | +| `stm-chans` | [`3.0.0.9`](http://hackage.haskell.org/package/stm-chans-3.0.0.9) | [`BSD-3-Clause`](./licences/haskell/stm-chans-3.0.0.9/LICENSE) | Additional types of channels for STM. | `monad-logger` | +| `strict` | [`0.5`](http://hackage.haskell.org/package/strict-0.5) | [`BSD-3-Clause`](./licences/haskell/strict-0.5/LICENSE) | Strict data types and String IO. | `aeson` | +| `streaming-commons` | [`0.2.2.6`](http://hackage.haskell.org/package/streaming-commons-0.2.2.6) | [`MIT`](./licences/haskell/streaming-commons-0.2.2.6/LICENSE) | Common lower-level functions needed by various streaming data libraries | `conduit-extra`, `websockets` | +| `easy-file` | [`0.2.5`](http://hackage.haskell.org/package/easy-file-0.2.5) | [`BSD-3-Clause`](./licences/haskell/easy-file-0.2.5/LICENSE) | Cross-platform File handling | `fast-logger` | +| `auto-update` | [`0.1.6`](http://hackage.haskell.org/package/auto-update-0.1.6) | [`MIT`](./licences/haskell/auto-update-0.1.6/LICENSE) | Efficiently run periodic, on-demand actions | `fast-logger`, `time-manager` | +| `tagged` | [`0.8.8`](http://hackage.haskell.org/package/tagged-0.8.8) | [`BSD-3-Clause`](./licences/haskell/tagged-0.8.8/LICENSE) | Haskell 98 phantom types to avoid unsafely passing dummy arguments | `aeson`, `assoc`, `bifunctors`, `boring`, `comonad`, `distributive`, `indexed-traversable-instances`, `semialign`, `semigroupoids` | +| `th-abstraction` | [`0.6.0.0`](http://hackage.haskell.org/package/th-abstraction-0.6.0.0) | [`ISC`](./licences/haskell/th-abstraction-0.6.0.0/LICENSE) | Nicer interface for reified information about data types | `aeson`, `bifunctors` | +| `th-compat` | [`0.1.4`](http://hackage.haskell.org/package/th-compat-0.1.4) | [`BSD-3-Clause`](./licences/haskell/th-compat-0.1.4/LICENSE) | Backward- (and forward-)compatible Quote and Code types | `network-uri` | +| `these` | [`1.2`](http://hackage.haskell.org/package/these-1.2) | [`BSD-3-Clause`](./licences/haskell/these-1.2/LICENSE) | An either-or-both data type. | `aeson`, `semialign`, `strict` | +| `time-compat` | [`1.9.6.1`](http://hackage.haskell.org/package/time-compat-1.9.6.1) | [`BSD-3-Clause`](./licences/haskell/time-compat-1.9.6.1/LICENSE) | Compatibility package for time | `aeson`, `text-iso8601` | +| `time-manager` | [`0.0.1`](http://hackage.haskell.org/package/time-manager-0.0.1) | [`MIT`](./licences/haskell/time-manager-0.0.1/LICENSE) | Scalable timer | `http2`, `simplexmq` | +| `temporary` | [`1.3`](http://hackage.haskell.org/package/temporary-1.3) | [`BSD-3-Clause`](./licences/haskell/temporary-1.3/LICENSE) | Portable temporary file and directory support | `simplexmq` | +| **`transformers`** | [`0.6.1.0`](http://hackage.haskell.org/package/transformers-0.6.1.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/transformers-0.6.1.0/src/LICENSE) | Concrete functor and monad transformers | `QuickCheck`, `StateVar`, `attoparsec`, `bifunctors`, `boring`, `comonad`, `conduit`, `conduit-extra`, `constraints`, `contravariant`, `crypton-x509`, `distributive`, `exceptions`, `indexed-traversable`, `monad-control`, `monad-logger`, `mono-traversable`, `mtl`, `network-transport`, `optparse-applicative`, `primitive`, `resourcet`, `safe-exceptions`, `semialign`, `semigroupoids`, `simplexmq`, `sqlcipher-simple`, `streaming-commons`, `strict`, `tagged`, `temporary`, `terminal`, `tls`, `transformers-base`, `transformers-compat`, `typed-process`, `unliftio`, `unliftio-core`, `witherable`, `yaml`, `zip` | +| `transformers-base` | [`0.4.6`](http://hackage.haskell.org/package/transformers-base-0.4.6) | [`BSD-3-Clause`](./licences/haskell/transformers-base-0.4.6/LICENSE) | Lift computations from the bottom of a transformer stack | `lifted-base`, `monad-control`, `monad-logger`, `zip` | +| `transformers-compat` | [`0.7.2`](http://hackage.haskell.org/package/transformers-compat-0.7.2) | [`BSD-3-Clause`](./licences/haskell/transformers-compat-0.7.2/LICENSE) | A small compatibility shim for the transformers library | `comonad`, `monad-control`, `monad-logger`, `optparse-applicative`, `semigroupoids`, `transformers-base` | +| `attoparsec` | [`0.14.4`](http://hackage.haskell.org/package/attoparsec-0.14.4) | [`BSD-3-Clause`](./licences/haskell/attoparsec-0.14.4/LICENSE) | Fast combinator parsing for bytestrings and text | `attoparsec` | +| `text-iso8601` | [`0.1`](http://hackage.haskell.org/package/text-iso8601-0.1) | [`BSD-3-Clause`](./licences/haskell/text-iso8601-0.1/LICENSE) | Converting time to and from ISO 8601 text. | `aeson` | +| `text-short` | [`0.1.5`](http://hackage.haskell.org/package/text-short-0.1.5) | [`BSD-3-Clause`](./licences/haskell/text-short-0.1.5/LICENSE) | Memory-efficient representation of Unicode text strings | `aeson` | +| `type-equality` | [`1`](http://hackage.haskell.org/package/type-equality-1) | [`BSD-3-Clause`](./licences/haskell/type-equality-1/LICENSE) | Data.Type.Equality compat package | `constraints` | +| `typed-process` | [`0.2.11.1`](http://hackage.haskell.org/package/typed-process-0.2.11.1) | [`MIT`](./licences/haskell/typed-process-0.2.11.1/LICENSE) | Run external processes, with strong typing of streams | `conduit-extra` | +| **`unix`** | [`2.8.1.0`](http://hackage.haskell.org/package/unix-2.8.1.0) | [`BSD-3-Clause`](http://hackage.haskell.org/package/unix-2.8.1.0/src/LICENSE) | POSIX functionality | `conduit`, `directory`, `easy-file`, `entropy`, `process`, `streaming-commons`, `temporary`, `unix-compat`, `unliftio`, `zip` | +| `vector` | [`0.13.1.0`](http://hackage.haskell.org/package/vector-0.13.1.0) | [`BSD-3-Clause`](./licences/haskell/vector-0.13.1.0/LICENSE) | Efficient Arrays | `aeson`, `bitvec`, `blaze-textual`, `conduit`, `indexed-traversable-instances`, `mono-traversable`, `semialign`, `vector-algorithms`, `witherable`, `yaml` | +| `vector-algorithms` | [`0.9.0.1`](http://hackage.haskell.org/package/vector-algorithms-0.9.0.1) | [`BSD-3-Clause`](./licences/haskell/vector-algorithms-0.9.0.1/LICENSE) | Efficient algorithms for vector arrays | `mono-traversable` | +| `vector-stream` | [`0.1.0.0`](http://hackage.haskell.org/package/vector-stream-0.1.0.0) | [`BSD-3-Clause`](./licences/haskell/vector-stream-0.1.0.0/LICENSE) | Efficient Streams | `vector` | +| `witherable` | [`0.4.2`](http://hackage.haskell.org/package/witherable-0.4.2) | [`BSD-3-Clause`](./licences/haskell/witherable-0.4.2/LICENSE) | filterable traversable | `aeson` | +| `yaml` | [`0.11.11.2`](http://hackage.haskell.org/package/yaml-0.11.11.2) | [`BSD-3-Clause`](./licences/haskell/yaml-0.11.11.2/LICENSE) | Support for parsing and rendering YAML documents. | `simplexmq` | +| `zlib` | [`0.6.3.0`](http://hackage.haskell.org/package/zlib-0.6.3.0) | [`BSD-3-Clause`](./licences/haskell/zlib-0.6.3.0/LICENSE) | Compression and decompression in the gzip and zlib formats | `streaming-commons` | +| `zstd` | [`0.1.3.0`](http://hackage.haskell.org/package/zstd-0.1.3.0) | [`BSD-3-Clause`](./licences/haskell/zstd-0.1.3.0/LICENSE) | Haskell bindings to the Zstandard compression algorithm | `simplexmq` | + diff --git a/docs/dependencies/README.md b/docs/dependencies/README.md new file mode 100644 index 0000000000..02c0623988 --- /dev/null +++ b/docs/dependencies/README.md @@ -0,0 +1,9 @@ +# SimpleX Chat and SimpleX servers dependencies + +[SQLCipher](https://github.com/sqlcipher/sqlcipher): Extension of [SQLite](https://sqlite.org) with encryption ([BSD-style](./licences/build/sqlcipher/LICENSE.md)) + +[vlc](https://github.com/videolan/vlc): VLC media player library ([LGPLv2](./licences/build/vlc/COPYING.LIB)) + +[WebRTC](https://webrtc.googlesource.com/src/): RTC for calls ([BSD-3-clause](./licences/build/webrtc/LICENSE)) + +[Haskell dependencies](./HASKELL.md). diff --git a/docs/dependencies/licences/apps/sqlcipher/LICENSE.md b/docs/dependencies/licences/apps/sqlcipher/LICENSE.md new file mode 100644 index 0000000000..3f71443161 --- /dev/null +++ b/docs/dependencies/licences/apps/sqlcipher/LICENSE.md @@ -0,0 +1,24 @@ +Copyright (c) 2025, ZETETIC LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the ZETETIC LLC nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/apps/vlc/COPYING.LIB b/docs/dependencies/licences/apps/vlc/COPYING.LIB new file mode 100644 index 0000000000..4362b49151 --- /dev/null +++ b/docs/dependencies/licences/apps/vlc/COPYING.LIB @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/docs/dependencies/licences/apps/webrtc/LICENSE b/docs/dependencies/licences/apps/webrtc/LICENSE new file mode 100644 index 0000000000..8868dcdf45 --- /dev/null +++ b/docs/dependencies/licences/apps/webrtc/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2011, The WebRTC project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/OneTuple-0.4.1.1/LICENSE b/docs/dependencies/licences/haskell/OneTuple-0.4.1.1/LICENSE new file mode 100644 index 0000000000..405f9161e9 --- /dev/null +++ b/docs/dependencies/licences/haskell/OneTuple-0.4.1.1/LICENSE @@ -0,0 +1,34 @@ + +Copyright (c) 2008, John A. Dorsey. +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of John Dorsey nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/docs/dependencies/licences/haskell/Only-0.1/LICENSE b/docs/dependencies/licences/haskell/Only-0.1/LICENSE new file mode 100644 index 0000000000..98d4b1ad78 --- /dev/null +++ b/docs/dependencies/licences/haskell/Only-0.1/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2017, Herbert Valerio Riedel + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Herbert Valerio Riedel nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/QuickCheck-2.14.3/LICENSE b/docs/dependencies/licences/haskell/QuickCheck-2.14.3/LICENSE new file mode 100644 index 0000000000..8b0fdbfe4e --- /dev/null +++ b/docs/dependencies/licences/haskell/QuickCheck-2.14.3/LICENSE @@ -0,0 +1,29 @@ +(The following is the 3-clause BSD license.) + +Copyright (c) 2000-2019, Koen Claessen +Copyright (c) 2006-2008, Björn Bringert +Copyright (c) 2009-2019, Nick Smallbone + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +- Neither the names of the copyright owners nor the names of the + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/SHA-1.6.4.4/LICENSE b/docs/dependencies/licences/haskell/SHA-1.6.4.4/LICENSE new file mode 100644 index 0000000000..8e49100207 --- /dev/null +++ b/docs/dependencies/licences/haskell/SHA-1.6.4.4/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2008, Galois, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of the Galois, Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/StateVar-1.2.2/LICENSE b/docs/dependencies/licences/haskell/StateVar-1.2.2/LICENSE new file mode 100644 index 0000000000..e71299ba0e --- /dev/null +++ b/docs/dependencies/licences/haskell/StateVar-1.2.2/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2014-2015, Edward Kmett +Copyright (c) 2009-2021, Sven Panne +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the author nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/aeson-2.2.1.0/LICENSE b/docs/dependencies/licences/haskell/aeson-2.2.1.0/LICENSE new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/dependencies/licences/haskell/ansi-terminal-1.0/LICENSE b/docs/dependencies/licences/haskell/ansi-terminal-1.0/LICENSE new file mode 100644 index 0000000000..814a83d4fc --- /dev/null +++ b/docs/dependencies/licences/haskell/ansi-terminal-1.0/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2008, Maximilian Bolingbroke +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted +provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of + conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + * Neither the name of Maximilian Bolingbroke nor the names of other contributors may be used to + endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/ansi-terminal-types-0.11.5/LICENSE b/docs/dependencies/licences/haskell/ansi-terminal-types-0.11.5/LICENSE new file mode 100644 index 0000000000..413047009d --- /dev/null +++ b/docs/dependencies/licences/haskell/ansi-terminal-types-0.11.5/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2008, Maximilian Bolingbroke +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of Maximilian Bolingbroke nor the names of other contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/appar-0.1.8/LICENSE b/docs/dependencies/licences/haskell/appar-0.1.8/LICENSE new file mode 100644 index 0000000000..5422193088 --- /dev/null +++ b/docs/dependencies/licences/haskell/appar-0.1.8/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2009, IIJ Innovation Institute Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/asn1-encoding-0.9.6/LICENSE b/docs/dependencies/licences/haskell/asn1-encoding-0.9.6/LICENSE new file mode 100644 index 0000000000..e68cc61f14 --- /dev/null +++ b/docs/dependencies/licences/haskell/asn1-encoding-0.9.6/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2010-2013 Vincent Hanquez + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/asn1-parse-0.9.5/LICENSE b/docs/dependencies/licences/haskell/asn1-parse-0.9.5/LICENSE new file mode 100644 index 0000000000..e68cc61f14 --- /dev/null +++ b/docs/dependencies/licences/haskell/asn1-parse-0.9.5/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2010-2013 Vincent Hanquez + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/asn1-types-0.3.4/LICENSE b/docs/dependencies/licences/haskell/asn1-types-0.3.4/LICENSE new file mode 100644 index 0000000000..e68cc61f14 --- /dev/null +++ b/docs/dependencies/licences/haskell/asn1-types-0.3.4/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2010-2013 Vincent Hanquez + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/assoc-1.1/LICENSE b/docs/dependencies/licences/haskell/assoc-1.1/LICENSE new file mode 100644 index 0000000000..96445e5484 --- /dev/null +++ b/docs/dependencies/licences/haskell/assoc-1.1/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2017, Oleg Grenrus + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Oleg Grenrus nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/async-2.2.5/LICENSE b/docs/dependencies/licences/haskell/async-2.2.5/LICENSE new file mode 100644 index 0000000000..0acbb121f4 --- /dev/null +++ b/docs/dependencies/licences/haskell/async-2.2.5/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2012, Simon Marlow + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Simon Marlow nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/attoparsec-0.14.4/LICENSE b/docs/dependencies/licences/haskell/attoparsec-0.14.4/LICENSE new file mode 100644 index 0000000000..97392a6264 --- /dev/null +++ b/docs/dependencies/licences/haskell/attoparsec-0.14.4/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) Lennart Kolmodin + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/auto-update-0.1.6/LICENSE b/docs/dependencies/licences/haskell/auto-update-0.1.6/LICENSE new file mode 100644 index 0000000000..d2a7d05797 --- /dev/null +++ b/docs/dependencies/licences/haskell/auto-update-0.1.6/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2014 Michael Snoyman + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/docs/dependencies/licences/haskell/base-orphans-0.9.1/LICENSE b/docs/dependencies/licences/haskell/base-orphans-0.9.1/LICENSE new file mode 100644 index 0000000000..4751ca3f63 --- /dev/null +++ b/docs/dependencies/licences/haskell/base-orphans-0.9.1/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2015-2017 Simon Hengel , João Cristóvão , Ryan Scott + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/docs/dependencies/licences/haskell/base64-bytestring-1.2.1.0/LICENSE b/docs/dependencies/licences/haskell/base64-bytestring-1.2.1.0/LICENSE new file mode 100644 index 0000000000..89d2a1b35f --- /dev/null +++ b/docs/dependencies/licences/haskell/base64-bytestring-1.2.1.0/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2010 Bryan O'Sullivan + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/basement-0.0.16/LICENSE b/docs/dependencies/licences/haskell/basement-0.0.16/LICENSE new file mode 100644 index 0000000000..75e0288c26 --- /dev/null +++ b/docs/dependencies/licences/haskell/basement-0.0.16/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2015-2017 Vincent Hanquez +Copyright (c) 2017-2019 Foundation Maintainers + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/bifunctors-5.6.1/LICENSE b/docs/dependencies/licences/haskell/bifunctors-5.6.1/LICENSE new file mode 100644 index 0000000000..531684e87f --- /dev/null +++ b/docs/dependencies/licences/haskell/bifunctors-5.6.1/LICENSE @@ -0,0 +1,26 @@ +Copyright 2008-2016 Edward Kmett + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/bitvec-1.1.5.0/LICENSE b/docs/dependencies/licences/haskell/bitvec-1.1.5.0/LICENSE new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/dependencies/licences/haskell/blaze-builder-0.4.2.3/LICENSE b/docs/dependencies/licences/haskell/blaze-builder-0.4.2.3/LICENSE new file mode 100644 index 0000000000..a8bcabfd42 --- /dev/null +++ b/docs/dependencies/licences/haskell/blaze-builder-0.4.2.3/LICENSE @@ -0,0 +1,30 @@ +Copyright Jasper Van der Jeugt 2010, Simon Meier 2010 & 2011 + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Jasper Van der Jeugt nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/blaze-textual-0.2.3.1/LICENSE b/docs/dependencies/licences/haskell/blaze-textual-0.2.3.1/LICENSE new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/dependencies/licences/haskell/boring-0.2.1/LICENSE b/docs/dependencies/licences/haskell/boring-0.2.1/LICENSE new file mode 100644 index 0000000000..96445e5484 --- /dev/null +++ b/docs/dependencies/licences/haskell/boring-0.2.1/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2017, Oleg Grenrus + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Oleg Grenrus nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/byteorder-1.0.4/LICENSE b/docs/dependencies/licences/haskell/byteorder-1.0.4/LICENSE new file mode 100644 index 0000000000..81b771a3b8 --- /dev/null +++ b/docs/dependencies/licences/haskell/byteorder-1.0.4/LICENSE @@ -0,0 +1,30 @@ +Copyright 2009, Antoine Latter + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of the author nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/bytestring-builder-0.10.8.2.0/LICENSE b/docs/dependencies/licences/haskell/bytestring-builder-0.10.8.2.0/LICENSE new file mode 100644 index 0000000000..5106f1309a --- /dev/null +++ b/docs/dependencies/licences/haskell/bytestring-builder-0.10.8.2.0/LICENSE @@ -0,0 +1,30 @@ +Copyright Jasper Van der Jeugt 2010, Simon Meier 2010-2013 + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Jasper Van der Jeugt nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/case-insensitive-1.2.1.0/LICENSE b/docs/dependencies/licences/haskell/case-insensitive-1.2.1.0/LICENSE new file mode 100644 index 0000000000..c81fb8bf9d --- /dev/null +++ b/docs/dependencies/licences/haskell/case-insensitive-1.2.1.0/LICENSE @@ -0,0 +1,31 @@ +Copyright (c) 2011-2013 Bas van Dijk + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * The name of Bas van Dijk and the names of contributors may NOT + be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/cereal-0.5.8.3/LICENSE b/docs/dependencies/licences/haskell/cereal-0.5.8.3/LICENSE new file mode 100644 index 0000000000..6dfb133efb --- /dev/null +++ b/docs/dependencies/licences/haskell/cereal-0.5.8.3/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) Lennart Kolmodin, Galois, Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/clock-0.8.4/LICENSE b/docs/dependencies/licences/haskell/clock-0.8.4/LICENSE new file mode 100644 index 0000000000..c2451c8c0a --- /dev/null +++ b/docs/dependencies/licences/haskell/clock-0.8.4/LICENSE @@ -0,0 +1,31 @@ +Copyright (c) 2009-2022, Clock Contributors + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * The names of contributors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/colour-2.3.6/LICENSE b/docs/dependencies/licences/haskell/colour-2.3.6/LICENSE new file mode 100644 index 0000000000..61aede888a --- /dev/null +++ b/docs/dependencies/licences/haskell/colour-2.3.6/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2008, 2009 +Russell O'Connor + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/docs/dependencies/licences/haskell/comonad-5.0.8/LICENSE b/docs/dependencies/licences/haskell/comonad-5.0.8/LICENSE new file mode 100644 index 0000000000..f8e92e04df --- /dev/null +++ b/docs/dependencies/licences/haskell/comonad-5.0.8/LICENSE @@ -0,0 +1,27 @@ +Copyright 2008-2014 Edward Kmett +Copyright 2004-2008 Dave Menendez + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/composition-1.0.2.2/LICENSE b/docs/dependencies/licences/haskell/composition-1.0.2.2/LICENSE new file mode 100644 index 0000000000..e191e7ab64 --- /dev/null +++ b/docs/dependencies/licences/haskell/composition-1.0.2.2/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2011-2012, Dan Burton + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Dan Burton nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/conduit-1.3.5/LICENSE b/docs/dependencies/licences/haskell/conduit-1.3.5/LICENSE new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/dependencies/licences/haskell/conduit-extra-1.3.6/LICENSE b/docs/dependencies/licences/haskell/conduit-extra-1.3.6/LICENSE new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/dependencies/licences/haskell/constraints-0.14/LICENSE b/docs/dependencies/licences/haskell/constraints-0.14/LICENSE new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/dependencies/licences/haskell/contravariant-1.5.5/LICENSE b/docs/dependencies/licences/haskell/contravariant-1.5.5/LICENSE new file mode 100644 index 0000000000..f357c7c429 --- /dev/null +++ b/docs/dependencies/licences/haskell/contravariant-1.5.5/LICENSE @@ -0,0 +1,30 @@ +Copyright 2007-2015 Edward Kmett + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/cryptohash-md5-0.11.101.0/LICENSE b/docs/dependencies/licences/haskell/cryptohash-md5-0.11.101.0/LICENSE new file mode 100644 index 0000000000..467bf18f27 --- /dev/null +++ b/docs/dependencies/licences/haskell/cryptohash-md5-0.11.101.0/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2010-2014 Vincent Hanquez + 2016 Herbert Valerio Riedel + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/cryptohash-sha1-0.11.101.0/LICENSE b/docs/dependencies/licences/haskell/cryptohash-sha1-0.11.101.0/LICENSE new file mode 100644 index 0000000000..467bf18f27 --- /dev/null +++ b/docs/dependencies/licences/haskell/cryptohash-sha1-0.11.101.0/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2010-2014 Vincent Hanquez + 2016 Herbert Valerio Riedel + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/crypton-0.34/LICENSE b/docs/dependencies/licences/haskell/crypton-0.34/LICENSE new file mode 100644 index 0000000000..a61c84945b --- /dev/null +++ b/docs/dependencies/licences/haskell/crypton-0.34/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2006-2015 Vincent Hanquez + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/crypton-x509-1.7.6/LICENSE b/docs/dependencies/licences/haskell/crypton-x509-1.7.6/LICENSE new file mode 100644 index 0000000000..e68cc61f14 --- /dev/null +++ b/docs/dependencies/licences/haskell/crypton-x509-1.7.6/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2010-2013 Vincent Hanquez + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/crypton-x509-store-1.6.9/LICENSE b/docs/dependencies/licences/haskell/crypton-x509-store-1.6.9/LICENSE new file mode 100644 index 0000000000..e68cc61f14 --- /dev/null +++ b/docs/dependencies/licences/haskell/crypton-x509-store-1.6.9/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2010-2013 Vincent Hanquez + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/crypton-x509-validation-1.6.12/LICENSE b/docs/dependencies/licences/haskell/crypton-x509-validation-1.6.12/LICENSE new file mode 100644 index 0000000000..e68cc61f14 --- /dev/null +++ b/docs/dependencies/licences/haskell/crypton-x509-validation-1.6.12/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2010-2013 Vincent Hanquez + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/cryptostore-0.3.0.1/LICENSE b/docs/dependencies/licences/haskell/cryptostore-0.3.0.1/LICENSE new file mode 100644 index 0000000000..b85926a265 --- /dev/null +++ b/docs/dependencies/licences/haskell/cryptostore-0.3.0.1/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2018-2023, Olivier Chéron + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Olivier Chéron nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/data-default-0.7.1.1/LICENSE b/docs/dependencies/licences/haskell/data-default-0.7.1.1/LICENSE new file mode 100644 index 0000000000..cd44640be9 --- /dev/null +++ b/docs/dependencies/licences/haskell/data-default-0.7.1.1/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2013 Lukas Mai + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/data-default-class-0.1.2.0/LICENSE b/docs/dependencies/licences/haskell/data-default-class-0.1.2.0/LICENSE new file mode 100644 index 0000000000..cd44640be9 --- /dev/null +++ b/docs/dependencies/licences/haskell/data-default-class-0.1.2.0/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2013 Lukas Mai + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/data-default-instances-containers-0.0.1/LICENSE b/docs/dependencies/licences/haskell/data-default-instances-containers-0.0.1/LICENSE new file mode 100644 index 0000000000..4f9075d76b --- /dev/null +++ b/docs/dependencies/licences/haskell/data-default-instances-containers-0.0.1/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2013 Lukas Mai + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY LUKAS MAI AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/data-default-instances-dlist-0.0.1/LICENSE b/docs/dependencies/licences/haskell/data-default-instances-dlist-0.0.1/LICENSE new file mode 100644 index 0000000000..4f9075d76b --- /dev/null +++ b/docs/dependencies/licences/haskell/data-default-instances-dlist-0.0.1/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2013 Lukas Mai + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY LUKAS MAI AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/data-default-instances-old-locale-0.0.1/LICENSE b/docs/dependencies/licences/haskell/data-default-instances-old-locale-0.0.1/LICENSE new file mode 100644 index 0000000000..4f9075d76b --- /dev/null +++ b/docs/dependencies/licences/haskell/data-default-instances-old-locale-0.0.1/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2013 Lukas Mai + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY LUKAS MAI AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/data-fix-0.3.2/LICENSE b/docs/dependencies/licences/haskell/data-fix-0.3.2/LICENSE new file mode 100644 index 0000000000..0576170924 --- /dev/null +++ b/docs/dependencies/licences/haskell/data-fix-0.3.2/LICENSE @@ -0,0 +1,30 @@ +Copyright Anton Kholomiov 2010 + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Anton Kholomiov nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/digest-0.0.1.7/LICENSE b/docs/dependencies/licences/haskell/digest-0.0.1.7/LICENSE new file mode 100644 index 0000000000..48326b0815 --- /dev/null +++ b/docs/dependencies/licences/haskell/digest-0.0.1.7/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2008-2009, Eugene Kirpichov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/distributive-0.6.2.1/LICENSE b/docs/dependencies/licences/haskell/distributive-0.6.2.1/LICENSE new file mode 100644 index 0000000000..d6c9cbe4a4 --- /dev/null +++ b/docs/dependencies/licences/haskell/distributive-0.6.2.1/LICENSE @@ -0,0 +1,26 @@ +Copyright 2011-2016 Edward Kmett + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/dlist-1.0/license.md b/docs/dependencies/licences/haskell/dlist-1.0/license.md new file mode 100644 index 0000000000..32ac978e90 --- /dev/null +++ b/docs/dependencies/licences/haskell/dlist-1.0/license.md @@ -0,0 +1,28 @@ +Copyright © 2006-2009 Don Stewart, 2013-2020 Sean Leather, contributors + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holders nor the names of other contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/easy-file-0.2.5/LICENSE b/docs/dependencies/licences/haskell/easy-file-0.2.5/LICENSE new file mode 100644 index 0000000000..5422193088 --- /dev/null +++ b/docs/dependencies/licences/haskell/easy-file-0.2.5/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2009, IIJ Innovation Institute Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/email-validate-2.3.2.19/LICENSE b/docs/dependencies/licences/haskell/email-validate-2.3.2.19/LICENSE new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/dependencies/licences/haskell/entropy-0.4.1.10/LICENSE b/docs/dependencies/licences/haskell/entropy-0.4.1.10/LICENSE new file mode 100644 index 0000000000..6d24e89d0b --- /dev/null +++ b/docs/dependencies/licences/haskell/entropy-0.4.1.10/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) Thomas DuBuisson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/fast-logger-3.2.2/LICENSE b/docs/dependencies/licences/haskell/fast-logger-3.2.2/LICENSE new file mode 100644 index 0000000000..5422193088 --- /dev/null +++ b/docs/dependencies/licences/haskell/fast-logger-3.2.2/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2009, IIJ Innovation Institute Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/file-embed-0.0.15.0/LICENSE b/docs/dependencies/licences/haskell/file-embed-0.0.15.0/LICENSE new file mode 100644 index 0000000000..11dc17a16c --- /dev/null +++ b/docs/dependencies/licences/haskell/file-embed-0.0.15.0/LICENSE @@ -0,0 +1,25 @@ +The following license covers this documentation, and the source code, except +where otherwise indicated. + +Copyright 2008, Michael Snoyman. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/generically-0.1.1/LICENSE b/docs/dependencies/licences/haskell/generically-0.1.1/LICENSE new file mode 100644 index 0000000000..58c573a1e2 --- /dev/null +++ b/docs/dependencies/licences/haskell/generically-0.1.1/LICENSE @@ -0,0 +1,28 @@ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Herbert Valerio Riedel nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/hashable-1.4.3.0/LICENSE b/docs/dependencies/licences/haskell/hashable-1.4.3.0/LICENSE new file mode 100644 index 0000000000..7130957b4d --- /dev/null +++ b/docs/dependencies/licences/haskell/hashable-1.4.3.0/LICENSE @@ -0,0 +1,30 @@ +Copyright Milan Straka 2010 + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Milan Straka nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/hourglass-0.2.12/LICENSE b/docs/dependencies/licences/haskell/hourglass-0.2.12/LICENSE new file mode 100644 index 0000000000..36c942d4a8 --- /dev/null +++ b/docs/dependencies/licences/haskell/hourglass-0.2.12/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2014 Vincent Hanquez + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/http-types-0.12.4/LICENSE b/docs/dependencies/licences/haskell/http-types-0.12.4/LICENSE new file mode 100644 index 0000000000..f77a7b24dd --- /dev/null +++ b/docs/dependencies/licences/haskell/http-types-0.12.4/LICENSE @@ -0,0 +1,31 @@ +Copyright (c) 2011, Aristid Breitkreuz +Copyright (c) 2011, Michael Snoyman + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Aristid Breitkreuz nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/http2-5.0.0/LICENSE b/docs/dependencies/licences/haskell/http2-5.0.0/LICENSE new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/dependencies/licences/haskell/indexed-traversable-0.1.3/LICENSE b/docs/dependencies/licences/haskell/indexed-traversable-0.1.3/LICENSE new file mode 100644 index 0000000000..476166addc --- /dev/null +++ b/docs/dependencies/licences/haskell/indexed-traversable-0.1.3/LICENSE @@ -0,0 +1,26 @@ +Copyright 2012-2016 Edward Kmett + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/indexed-traversable-instances-0.1.1.2/LICENSE b/docs/dependencies/licences/haskell/indexed-traversable-instances-0.1.1.2/LICENSE new file mode 100644 index 0000000000..476166addc --- /dev/null +++ b/docs/dependencies/licences/haskell/indexed-traversable-instances-0.1.1.2/LICENSE @@ -0,0 +1,26 @@ +Copyright 2012-2016 Edward Kmett + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/ini-0.4.2/LICENSE b/docs/dependencies/licences/haskell/ini-0.4.2/LICENSE new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/dependencies/licences/haskell/integer-conversion-0.1.0.1/LICENSE b/docs/dependencies/licences/haskell/integer-conversion-0.1.0.1/LICENSE new file mode 100644 index 0000000000..3f74492595 --- /dev/null +++ b/docs/dependencies/licences/haskell/integer-conversion-0.1.0.1/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2023, Oleg Grenrus + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Oleg Grenrus nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/integer-logarithms-1.0.3.1/LICENSE b/docs/dependencies/licences/haskell/integer-logarithms-1.0.3.1/LICENSE new file mode 100644 index 0000000000..9430f484d0 --- /dev/null +++ b/docs/dependencies/licences/haskell/integer-logarithms-1.0.3.1/LICENSE @@ -0,0 +1,16 @@ +Copyright (c) 2011 Daniel Fischer, 2017 Oleg Grenrus + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, + sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/docs/dependencies/licences/haskell/iproute-1.7.12/LICENSE b/docs/dependencies/licences/haskell/iproute-1.7.12/LICENSE new file mode 100644 index 0000000000..5422193088 --- /dev/null +++ b/docs/dependencies/licences/haskell/iproute-1.7.12/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2009, IIJ Innovation Institute Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/libyaml-0.1.2/LICENSE b/docs/dependencies/licences/haskell/libyaml-0.1.2/LICENSE new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/dependencies/licences/haskell/lifted-base-0.2.3.12/LICENSE b/docs/dependencies/licences/haskell/lifted-base-0.2.3.12/LICENSE new file mode 100644 index 0000000000..b8b5ed95c0 --- /dev/null +++ b/docs/dependencies/licences/haskell/lifted-base-0.2.3.12/LICENSE @@ -0,0 +1,29 @@ +Copyright © 2010-2012, Bas van Dijk, Anders Kaseorg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +• Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +• Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +• Neither the name of the author nor the names of other contributors + may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +“AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/memory-0.18.0/LICENSE b/docs/dependencies/licences/haskell/memory-0.18.0/LICENSE new file mode 100644 index 0000000000..8eafd52555 --- /dev/null +++ b/docs/dependencies/licences/haskell/memory-0.18.0/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2015-2018 Vincent Hanquez +Copyright (c) 2017-2018 Nicolas Di Prima + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/monad-control-1.0.3.1/LICENSE b/docs/dependencies/licences/haskell/monad-control-1.0.3.1/LICENSE new file mode 100644 index 0000000000..f4e1bfab44 --- /dev/null +++ b/docs/dependencies/licences/haskell/monad-control-1.0.3.1/LICENSE @@ -0,0 +1,29 @@ +Copyright © 2010, Bas van Dijk, Anders Kaseorg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +• Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +• Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +• Neither the name of the author nor the names of other contributors + may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +“AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/monad-logger-0.3.40/LICENSE b/docs/dependencies/licences/haskell/monad-logger-0.3.40/LICENSE new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/dependencies/licences/haskell/mono-traversable-1.0.15.3/LICENSE b/docs/dependencies/licences/haskell/mono-traversable-1.0.15.3/LICENSE new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/dependencies/licences/haskell/network-3.1.4.0/LICENSE b/docs/dependencies/licences/haskell/network-3.1.4.0/LICENSE new file mode 100644 index 0000000000..40bba626f8 --- /dev/null +++ b/docs/dependencies/licences/haskell/network-3.1.4.0/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2002-2010, The University Court of the University of Glasgow. +Copyright (c) 2007-2010, Johan Tibell + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +- Neither name of the University nor the names of its contributors may be +used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF +GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +UNIVERSITY COURT OF THE UNIVERSITY OF GLASGOW OR THE CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. diff --git a/docs/dependencies/licences/haskell/network-byte-order-0.1.7/LICENSE b/docs/dependencies/licences/haskell/network-byte-order-0.1.7/LICENSE new file mode 100644 index 0000000000..ed342e54a0 --- /dev/null +++ b/docs/dependencies/licences/haskell/network-byte-order-0.1.7/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2017, Kazu Yamamoto + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Kazu Yamamoto nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/network-control-0.0.2/LICENSE b/docs/dependencies/licences/haskell/network-control-0.0.2/LICENSE new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/dependencies/licences/haskell/network-info-0.2.1/LICENSE b/docs/dependencies/licences/haskell/network-info-0.2.1/LICENSE new file mode 100644 index 0000000000..be005bd412 --- /dev/null +++ b/docs/dependencies/licences/haskell/network-info-0.2.1/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2010, Jacob Stanley + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Jacob Stanley nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/network-transport-0.5.6/LICENSE b/docs/dependencies/licences/haskell/network-transport-0.5.6/LICENSE new file mode 100644 index 0000000000..f3459e449d --- /dev/null +++ b/docs/dependencies/licences/haskell/network-transport-0.5.6/LICENSE @@ -0,0 +1,31 @@ +Copyright Well-Typed LLP, 2011-2012 + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of the owner nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/docs/dependencies/licences/haskell/network-udp-0.0.0/LICENSE b/docs/dependencies/licences/haskell/network-udp-0.0.0/LICENSE new file mode 100644 index 0000000000..73957a7a32 --- /dev/null +++ b/docs/dependencies/licences/haskell/network-udp-0.0.0/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2022, Internet Initiative Japan Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/network-uri-2.6.4.2/LICENSE b/docs/dependencies/licences/haskell/network-uri-2.6.4.2/LICENSE new file mode 100644 index 0000000000..40bba626f8 --- /dev/null +++ b/docs/dependencies/licences/haskell/network-uri-2.6.4.2/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2002-2010, The University Court of the University of Glasgow. +Copyright (c) 2007-2010, Johan Tibell + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +- Neither name of the University nor the names of its contributors may be +used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF +GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +UNIVERSITY COURT OF THE UNIVERSITY OF GLASGOW OR THE CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. diff --git a/docs/dependencies/licences/haskell/old-locale-1.0.0.7/LICENSE b/docs/dependencies/licences/haskell/old-locale-1.0.0.7/LICENSE new file mode 100644 index 0000000000..06bb641487 --- /dev/null +++ b/docs/dependencies/licences/haskell/old-locale-1.0.0.7/LICENSE @@ -0,0 +1,63 @@ +This library (libraries/base) is derived from code from two +sources: + + * Code from the GHC project which is largely (c) The University of + Glasgow, and distributable under a BSD-style license (see below), + + * Code from the Haskell 98 Report which is (c) Simon Peyton Jones + and freely redistributable (but see the full license for + restrictions). + +The full text of these licenses is reproduced below. Both of the +licenses are BSD-style or compatible. + +----------------------------------------------------------------------------- + +The Glasgow Haskell Compiler License + +Copyright 2004, The University Court of the University of Glasgow. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +- Neither name of the University nor the names of its contributors may be +used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF +GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +UNIVERSITY COURT OF THE UNIVERSITY OF GLASGOW OR THE CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +----------------------------------------------------------------------------- + +Code derived from the document "Report on the Programming Language +Haskell 98", is distributed under the following license: + + Copyright (c) 2002 Simon Peyton Jones + + The authors intend this Report to belong to the entire Haskell + community, and so we grant permission to copy and distribute it for + any purpose, provided that it is reproduced in its entirety, + including this Notice. Modified versions of this Report may also be + copied and distributed for any purpose, provided that the modified + version is clearly presented as such, and that it does not claim to + be a definition of the Haskell 98 Language. + +----------------------------------------------------------------------------- diff --git a/docs/dependencies/licences/haskell/old-time-1.1.0.4/LICENSE b/docs/dependencies/licences/haskell/old-time-1.1.0.4/LICENSE new file mode 100644 index 0000000000..06bb641487 --- /dev/null +++ b/docs/dependencies/licences/haskell/old-time-1.1.0.4/LICENSE @@ -0,0 +1,63 @@ +This library (libraries/base) is derived from code from two +sources: + + * Code from the GHC project which is largely (c) The University of + Glasgow, and distributable under a BSD-style license (see below), + + * Code from the Haskell 98 Report which is (c) Simon Peyton Jones + and freely redistributable (but see the full license for + restrictions). + +The full text of these licenses is reproduced below. Both of the +licenses are BSD-style or compatible. + +----------------------------------------------------------------------------- + +The Glasgow Haskell Compiler License + +Copyright 2004, The University Court of the University of Glasgow. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +- Neither name of the University nor the names of its contributors may be +used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF +GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +UNIVERSITY COURT OF THE UNIVERSITY OF GLASGOW OR THE CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +----------------------------------------------------------------------------- + +Code derived from the document "Report on the Programming Language +Haskell 98", is distributed under the following license: + + Copyright (c) 2002 Simon Peyton Jones + + The authors intend this Report to belong to the entire Haskell + community, and so we grant permission to copy and distribute it for + any purpose, provided that it is reproduced in its entirety, + including this Notice. Modified versions of this Report may also be + copied and distributed for any purpose, provided that the modified + version is clearly presented as such, and that it does not claim to + be a definition of the Haskell 98 Language. + +----------------------------------------------------------------------------- diff --git a/docs/dependencies/licences/haskell/optparse-applicative-0.18.1.0/LICENSE b/docs/dependencies/licences/haskell/optparse-applicative-0.18.1.0/LICENSE new file mode 100644 index 0000000000..0507cc2a81 --- /dev/null +++ b/docs/dependencies/licences/haskell/optparse-applicative-0.18.1.0/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2012, Paolo Capriotti + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Paolo Capriotti nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/pem-0.2.4/LICENSE b/docs/dependencies/licences/haskell/pem-0.2.4/LICENSE new file mode 100644 index 0000000000..15c41788af --- /dev/null +++ b/docs/dependencies/licences/haskell/pem-0.2.4/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2010-2018 Vincent Hanquez + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/prettyprinter-1.7.1/LICENSE.md b/docs/dependencies/licences/haskell/prettyprinter-1.7.1/LICENSE.md new file mode 100644 index 0000000000..39592102dc --- /dev/null +++ b/docs/dependencies/licences/haskell/prettyprinter-1.7.1/LICENSE.md @@ -0,0 +1,23 @@ +Copyright 2008, Daan Leijen and Max Bolingbroke, 2016 David Luposchainsky. All +rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + - Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +This software is provided by the copyright holders "as is" and any express or +implied warranties, including, but not limited to, the implied warranties of +merchantability and fitness for a particular purpose are disclaimed. In no event +shall the copyright holders be liable for any direct, indirect, incidental, +special, exemplary, or consequential damages (including, but not limited to, +procurement of substitute goods or services; loss of use, data, or profits; or +business interruption) however caused and on any theory of liability, whether in +contract, strict liability, or tort (including negligence or otherwise) arising +in any way out of the use of this software, even if advised of the possibility +of such damage. diff --git a/docs/dependencies/licences/haskell/prettyprinter-ansi-terminal-1.1.3/LICENSE.md b/docs/dependencies/licences/haskell/prettyprinter-ansi-terminal-1.1.3/LICENSE.md new file mode 100644 index 0000000000..39592102dc --- /dev/null +++ b/docs/dependencies/licences/haskell/prettyprinter-ansi-terminal-1.1.3/LICENSE.md @@ -0,0 +1,23 @@ +Copyright 2008, Daan Leijen and Max Bolingbroke, 2016 David Luposchainsky. All +rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + - Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +This software is provided by the copyright holders "as is" and any express or +implied warranties, including, but not limited to, the implied warranties of +merchantability and fitness for a particular purpose are disclaimed. In no event +shall the copyright holders be liable for any direct, indirect, incidental, +special, exemplary, or consequential damages (including, but not limited to, +procurement of substitute goods or services; loss of use, data, or profits; or +business interruption) however caused and on any theory of liability, whether in +contract, strict liability, or tort (including negligence or otherwise) arising +in any way out of the use of this software, even if advised of the possibility +of such damage. diff --git a/docs/dependencies/licences/haskell/primitive-0.9.0.0/LICENSE b/docs/dependencies/licences/haskell/primitive-0.9.0.0/LICENSE new file mode 100644 index 0000000000..fc213a6ffb --- /dev/null +++ b/docs/dependencies/licences/haskell/primitive-0.9.0.0/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2008-2009, Roman Leshchinskiy +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +- Neither name of the University nor the names of its contributors may be +used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF +GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +UNIVERSITY COURT OF THE UNIVERSITY OF GLASGOW OR THE CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + diff --git a/docs/dependencies/licences/haskell/psqueues-0.2.8.0/LICENSE b/docs/dependencies/licences/haskell/psqueues-0.2.8.0/LICENSE new file mode 100644 index 0000000000..92337b951e --- /dev/null +++ b/docs/dependencies/licences/haskell/psqueues-0.2.8.0/LICENSE @@ -0,0 +1,31 @@ +The Glasgow Haskell Compiler License + +Copyright 2004, The University Court of the University of Glasgow. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +- Neither name of the University nor the names of its contributors may be +used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF +GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +UNIVERSITY COURT OF THE UNIVERSITY OF GLASGOW OR THE CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. diff --git a/docs/dependencies/licences/haskell/random-1.2.1.1/LICENSE b/docs/dependencies/licences/haskell/random-1.2.1.1/LICENSE new file mode 100644 index 0000000000..06bb641487 --- /dev/null +++ b/docs/dependencies/licences/haskell/random-1.2.1.1/LICENSE @@ -0,0 +1,63 @@ +This library (libraries/base) is derived from code from two +sources: + + * Code from the GHC project which is largely (c) The University of + Glasgow, and distributable under a BSD-style license (see below), + + * Code from the Haskell 98 Report which is (c) Simon Peyton Jones + and freely redistributable (but see the full license for + restrictions). + +The full text of these licenses is reproduced below. Both of the +licenses are BSD-style or compatible. + +----------------------------------------------------------------------------- + +The Glasgow Haskell Compiler License + +Copyright 2004, The University Court of the University of Glasgow. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +- Neither name of the University nor the names of its contributors may be +used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF +GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +UNIVERSITY COURT OF THE UNIVERSITY OF GLASGOW OR THE CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +----------------------------------------------------------------------------- + +Code derived from the document "Report on the Programming Language +Haskell 98", is distributed under the following license: + + Copyright (c) 2002 Simon Peyton Jones + + The authors intend this Report to belong to the entire Haskell + community, and so we grant permission to copy and distribute it for + any purpose, provided that it is reproduced in its entirety, + including this Notice. Modified versions of this Report may also be + copied and distributed for any purpose, provided that the modified + version is clearly presented as such, and that it does not claim to + be a definition of the Haskell 98 Language. + +----------------------------------------------------------------------------- diff --git a/docs/dependencies/licences/haskell/record-hasfield-1.0/LICENSE b/docs/dependencies/licences/haskell/record-hasfield-1.0/LICENSE new file mode 100644 index 0000000000..f6a9dc60f7 --- /dev/null +++ b/docs/dependencies/licences/haskell/record-hasfield-1.0/LICENSE @@ -0,0 +1,30 @@ +Copyright Adam Gundry and Neil Mitchell 2018-2019. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Neil Mitchell nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/resourcet-1.3.0/LICENSE b/docs/dependencies/licences/haskell/resourcet-1.3.0/LICENSE new file mode 100644 index 0000000000..fe6d4e9b3f --- /dev/null +++ b/docs/dependencies/licences/haskell/resourcet-1.3.0/LICENSE @@ -0,0 +1,30 @@ +Copyright (c)2011, Michael Snoyman + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Michael Snoyman nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/safe-exceptions-0.1.7.4/LICENSE b/docs/dependencies/licences/haskell/safe-exceptions-0.1.7.4/LICENSE new file mode 100644 index 0000000000..9a69c0901b --- /dev/null +++ b/docs/dependencies/licences/haskell/safe-exceptions-0.1.7.4/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2016 FP Complete + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/docs/dependencies/licences/haskell/scientific-0.3.7.0/LICENSE b/docs/dependencies/licences/haskell/scientific-0.3.7.0/LICENSE new file mode 100644 index 0000000000..c1015843aa --- /dev/null +++ b/docs/dependencies/licences/haskell/scientific-0.3.7.0/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2013, Bas van Dijk + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Bas van Dijk nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/semialign-1.3/LICENSE b/docs/dependencies/licences/haskell/semialign-1.3/LICENSE new file mode 100644 index 0000000000..9d6d650165 --- /dev/null +++ b/docs/dependencies/licences/haskell/semialign-1.3/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2012, C. McCann, 2015-2019 Oleg Grenrus + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of C. McCann nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/semigroupoids-6.0.0.1/LICENSE b/docs/dependencies/licences/haskell/semigroupoids-6.0.0.1/LICENSE new file mode 100644 index 0000000000..1812ac961c --- /dev/null +++ b/docs/dependencies/licences/haskell/semigroupoids-6.0.0.1/LICENSE @@ -0,0 +1,26 @@ +Copyright 2011-2015 Edward Kmett + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/simple-logger-0.1.1/LICENSE b/docs/dependencies/licences/haskell/simple-logger-0.1.1/LICENSE new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/dependencies/licences/haskell/socks-0.6.1/LICENSE b/docs/dependencies/licences/haskell/socks-0.6.1/LICENSE new file mode 100644 index 0000000000..ed6f548a98 --- /dev/null +++ b/docs/dependencies/licences/haskell/socks-0.6.1/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2010-2019 Vincent Hanquez + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/split-0.2.4/LICENSE b/docs/dependencies/licences/haskell/split-0.2.4/LICENSE new file mode 100644 index 0000000000..648167ba61 --- /dev/null +++ b/docs/dependencies/licences/haskell/split-0.2.4/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2008 Brent Yorgey, Louis Wasserman + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the author nor the names of other contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/splitmix-0.1.0.5/LICENSE b/docs/dependencies/licences/haskell/splitmix-0.1.0.5/LICENSE new file mode 100644 index 0000000000..96445e5484 --- /dev/null +++ b/docs/dependencies/licences/haskell/splitmix-0.1.0.5/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2017, Oleg Grenrus + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Oleg Grenrus nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/stm-chans-3.0.0.9/LICENSE b/docs/dependencies/licences/haskell/stm-chans-3.0.0.9/LICENSE new file mode 100644 index 0000000000..55157e8352 --- /dev/null +++ b/docs/dependencies/licences/haskell/stm-chans-3.0.0.9/LICENSE @@ -0,0 +1,35 @@ +=== stm-chans license === + +Copyright (c) 2011--2013, wren gayle romano. +ALL RIGHTS RESERVED. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of the copyright holders nor the names of + other contributors may be used to endorse or promote products + derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + diff --git a/docs/dependencies/licences/haskell/streaming-commons-0.2.2.6/LICENSE b/docs/dependencies/licences/haskell/streaming-commons-0.2.2.6/LICENSE new file mode 100644 index 0000000000..7198785aaf --- /dev/null +++ b/docs/dependencies/licences/haskell/streaming-commons-0.2.2.6/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 FP Complete + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/docs/dependencies/licences/haskell/strict-0.5/LICENSE b/docs/dependencies/licences/haskell/strict-0.5/LICENSE new file mode 100644 index 0000000000..4782550806 --- /dev/null +++ b/docs/dependencies/licences/haskell/strict-0.5/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) Roman Leshchinskiy 2006-2007 + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/tagged-0.8.8/LICENSE b/docs/dependencies/licences/haskell/tagged-0.8.8/LICENSE new file mode 100644 index 0000000000..db046152dc --- /dev/null +++ b/docs/dependencies/licences/haskell/tagged-0.8.8/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2009-2015 Edward Kmett +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Edward Kmett nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/temporary-1.3/LICENSE b/docs/dependencies/licences/haskell/temporary-1.3/LICENSE new file mode 100644 index 0000000000..db55f34db3 --- /dev/null +++ b/docs/dependencies/licences/haskell/temporary-1.3/LICENSE @@ -0,0 +1,27 @@ +Copyright + (c) 2003-2006, Isaac Jones + (c) 2005-2009, Duncan Coutts + (c) 2008, Maximilian Bolingbroke + ... and other contributors + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted +provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of + conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + * Neither the name of Maximilian Bolingbroke nor the names of other contributors may be used to + endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/terminal-0.2.0.0/LICENSE b/docs/dependencies/licences/haskell/terminal-0.2.0.0/LICENSE new file mode 100644 index 0000000000..d45361ccf6 --- /dev/null +++ b/docs/dependencies/licences/haskell/terminal-0.2.0.0/LICENSE @@ -0,0 +1,30 @@ +Copyright Lars Petersen (c) 2018 + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Lars Petersen nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/text-iso8601-0.1/LICENSE b/docs/dependencies/licences/haskell/text-iso8601-0.1/LICENSE new file mode 100644 index 0000000000..fc90c466c7 --- /dev/null +++ b/docs/dependencies/licences/haskell/text-iso8601-0.1/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2023 Oleg Grenrus + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/text-short-0.1.5/LICENSE b/docs/dependencies/licences/haskell/text-short-0.1.5/LICENSE new file mode 100644 index 0000000000..98d4b1ad78 --- /dev/null +++ b/docs/dependencies/licences/haskell/text-short-0.1.5/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2017, Herbert Valerio Riedel + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Herbert Valerio Riedel nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/th-abstraction-0.6.0.0/LICENSE b/docs/dependencies/licences/haskell/th-abstraction-0.6.0.0/LICENSE new file mode 100644 index 0000000000..70f6077c10 --- /dev/null +++ b/docs/dependencies/licences/haskell/th-abstraction-0.6.0.0/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2017-2020 Eric Mertens + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. diff --git a/docs/dependencies/licences/haskell/th-compat-0.1.4/LICENSE b/docs/dependencies/licences/haskell/th-compat-0.1.4/LICENSE new file mode 100644 index 0000000000..879f215afc --- /dev/null +++ b/docs/dependencies/licences/haskell/th-compat-0.1.4/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2020, Ryan Scott + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Ryan Scott nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/these-1.2/LICENSE b/docs/dependencies/licences/haskell/these-1.2/LICENSE new file mode 100644 index 0000000000..9d6d650165 --- /dev/null +++ b/docs/dependencies/licences/haskell/these-1.2/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2012, C. McCann, 2015-2019 Oleg Grenrus + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of C. McCann nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/time-compat-1.9.6.1/LICENSE b/docs/dependencies/licences/haskell/time-compat-1.9.6.1/LICENSE new file mode 100644 index 0000000000..910ef51bb4 --- /dev/null +++ b/docs/dependencies/licences/haskell/time-compat-1.9.6.1/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2019 time contibutors, Oleg Grenrus + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Oleg Grenrus nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/time-manager-0.0.1/LICENSE b/docs/dependencies/licences/haskell/time-manager-0.0.1/LICENSE new file mode 100644 index 0000000000..d9f041796e --- /dev/null +++ b/docs/dependencies/licences/haskell/time-manager-0.0.1/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2012 Michael Snoyman, http://www.yesodweb.com/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/docs/dependencies/licences/haskell/tls-1.9.0/LICENSE b/docs/dependencies/licences/haskell/tls-1.9.0/LICENSE new file mode 100644 index 0000000000..96ec822eb9 --- /dev/null +++ b/docs/dependencies/licences/haskell/tls-1.9.0/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2010-2015 Vincent Hanquez + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/transformers-base-0.4.6/LICENSE b/docs/dependencies/licences/haskell/transformers-base-0.4.6/LICENSE new file mode 100644 index 0000000000..9d51261b52 --- /dev/null +++ b/docs/dependencies/licences/haskell/transformers-base-0.4.6/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2011, Mikhail Vorozhtsov, Bas van Dijk +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +- Neither the names of the copyright owners nor the names of the + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/docs/dependencies/licences/haskell/transformers-compat-0.7.2/LICENSE b/docs/dependencies/licences/haskell/transformers-compat-0.7.2/LICENSE new file mode 100644 index 0000000000..50586d197c --- /dev/null +++ b/docs/dependencies/licences/haskell/transformers-compat-0.7.2/LICENSE @@ -0,0 +1,30 @@ +Copyright 2012-2015 Edward Kmett + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the author nor the names of his contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/type-equality-1/LICENSE b/docs/dependencies/licences/haskell/type-equality-1/LICENSE new file mode 100644 index 0000000000..c30f3e7d31 --- /dev/null +++ b/docs/dependencies/licences/haskell/type-equality-1/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2009 Erik Hesselink, 2019 Oleg Grenrus, Ryan Scott + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of authors nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/typed-process-0.2.11.1/LICENSE b/docs/dependencies/licences/haskell/typed-process-0.2.11.1/LICENSE new file mode 100644 index 0000000000..50bb8ea0cf --- /dev/null +++ b/docs/dependencies/licences/haskell/typed-process-0.2.11.1/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2016 FP Complete, https://www.fpcomplete.com/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/docs/dependencies/licences/haskell/unix-compat-0.7.1/LICENSE b/docs/dependencies/licences/haskell/unix-compat-0.7.1/LICENSE new file mode 100644 index 0000000000..cc3be3e947 --- /dev/null +++ b/docs/dependencies/licences/haskell/unix-compat-0.7.1/LICENSE @@ -0,0 +1,31 @@ +BSD 3-Clause License + +Copyright (c) 2007-2008, Björn Bringert +Copyright (c) 2007-2009, Duncan Coutts +Copyright (c) 2010-2011, Jacob Stanley +Copyright (c) 2011, Bryan O'Sullivan +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +- Neither the names of the copyright owners nor the names of the + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/unix-time-0.4.11/LICENSE b/docs/dependencies/licences/haskell/unix-time-0.4.11/LICENSE new file mode 100644 index 0000000000..5422193088 --- /dev/null +++ b/docs/dependencies/licences/haskell/unix-time-0.4.11/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2009, IIJ Innovation Institute Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/unliftio-0.2.25.0/LICENSE b/docs/dependencies/licences/haskell/unliftio-0.2.25.0/LICENSE new file mode 100644 index 0000000000..1349134e01 --- /dev/null +++ b/docs/dependencies/licences/haskell/unliftio-0.2.25.0/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2017 FP Complete + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/docs/dependencies/licences/haskell/unliftio-core-0.2.1.0/LICENSE b/docs/dependencies/licences/haskell/unliftio-core-0.2.1.0/LICENSE new file mode 100644 index 0000000000..1349134e01 --- /dev/null +++ b/docs/dependencies/licences/haskell/unliftio-core-0.2.1.0/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2017 FP Complete + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/docs/dependencies/licences/haskell/unordered-containers-0.2.19.1/LICENSE b/docs/dependencies/licences/haskell/unordered-containers-0.2.19.1/LICENSE new file mode 100644 index 0000000000..5eb7e1bdda --- /dev/null +++ b/docs/dependencies/licences/haskell/unordered-containers-0.2.19.1/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2010, Johan Tibell + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Johan Tibell nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/uuid-1.3.15/LICENSE b/docs/dependencies/licences/haskell/uuid-1.3.15/LICENSE new file mode 100644 index 0000000000..dc46bca24c --- /dev/null +++ b/docs/dependencies/licences/haskell/uuid-1.3.15/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2008, Antoine Latter + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + * The names of the authors may not be used to endorse or promote +products derived from this software without specific prior written +permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/uuid-types-1.0.5.1/LICENSE b/docs/dependencies/licences/haskell/uuid-types-1.0.5.1/LICENSE new file mode 100644 index 0000000000..dc46bca24c --- /dev/null +++ b/docs/dependencies/licences/haskell/uuid-types-1.0.5.1/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2008, Antoine Latter + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + * The names of the authors may not be used to endorse or promote +products derived from this software without specific prior written +permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/vector-0.13.1.0/LICENSE b/docs/dependencies/licences/haskell/vector-0.13.1.0/LICENSE new file mode 100644 index 0000000000..ef2a5268d6 --- /dev/null +++ b/docs/dependencies/licences/haskell/vector-0.13.1.0/LICENSE @@ -0,0 +1,32 @@ +Copyright (c) 2008-2012, Roman Leshchinskiy + 2020-2022, Alexey Kuleshevich + 2020-2022, Aleksey Khudyakov + 2020-2022, Andrew Lelechenko +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +- Neither name of the University nor the names of its contributors may be +used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF +GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +UNIVERSITY COURT OF THE UNIVERSITY OF GLASGOW OR THE CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. diff --git a/docs/dependencies/licences/haskell/vector-algorithms-0.9.0.1/LICENSE b/docs/dependencies/licences/haskell/vector-algorithms-0.9.0.1/LICENSE new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/dependencies/licences/haskell/vector-stream-0.1.0.0/LICENSE b/docs/dependencies/licences/haskell/vector-stream-0.1.0.0/LICENSE new file mode 100644 index 0000000000..ef2a5268d6 --- /dev/null +++ b/docs/dependencies/licences/haskell/vector-stream-0.1.0.0/LICENSE @@ -0,0 +1,32 @@ +Copyright (c) 2008-2012, Roman Leshchinskiy + 2020-2022, Alexey Kuleshevich + 2020-2022, Aleksey Khudyakov + 2020-2022, Andrew Lelechenko +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +- Neither name of the University nor the names of its contributors may be +used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF +GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +UNIVERSITY COURT OF THE UNIVERSITY OF GLASGOW OR THE CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. diff --git a/docs/dependencies/licences/haskell/websockets-0.12.7.3/LICENCE b/docs/dependencies/licences/haskell/websockets-0.12.7.3/LICENCE new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/dependencies/licences/haskell/witherable-0.4.2/LICENSE b/docs/dependencies/licences/haskell/witherable-0.4.2/LICENSE new file mode 100644 index 0000000000..1994a4c649 --- /dev/null +++ b/docs/dependencies/licences/haskell/witherable-0.4.2/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2014, Fumiaki Kinoshita + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Fumiaki Kinoshita nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/yaml-0.11.11.2/LICENSE b/docs/dependencies/licences/haskell/yaml-0.11.11.2/LICENSE new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/dependencies/licences/haskell/zip-2.0.0/LICENSE.md b/docs/dependencies/licences/haskell/zip-2.0.0/LICENSE.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/dependencies/licences/haskell/zlib-0.6.3.0/LICENSE b/docs/dependencies/licences/haskell/zlib-0.6.3.0/LICENSE new file mode 100644 index 0000000000..e4d25e0e0a --- /dev/null +++ b/docs/dependencies/licences/haskell/zlib-0.6.3.0/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2006-2016, Duncan Coutts +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. This clause is intentionally left blank. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/dependencies/licences/haskell/zstd-0.1.3.0/LICENSE b/docs/dependencies/licences/haskell/zstd-0.1.3.0/LICENSE new file mode 100644 index 0000000000..a793a80289 --- /dev/null +++ b/docs/dependencies/licences/haskell/zstd-0.1.3.0/LICENSE @@ -0,0 +1,30 @@ +BSD License + +For Zstandard software + +Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 7b11d8514a6e96e01810b135f73710072bf18d69 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 25 Apr 2025 11:17:27 +0100 Subject: [PATCH 522/567] core, ui: option to use web port by default for preset servers only (#5847) * core: option to use web port by default for preset servers only * ui * refactor * simplexmq --- .../AdvancedNetworkSettings.swift | 9 +- apps/ios/SimpleXChat/APITypes.swift | 16 ++- apps/ios/SimpleXChat/AppGroup.swift | 16 ++- .../chat/simplex/common/model/SimpleXAPI.kt | 126 ++++++++++-------- .../AdvancedNetworkSettings.kt | 30 +++-- .../commonMain/resources/MR/base/strings.xml | 4 + .../commonMain/resources/MR/ru/strings.xml | 4 + cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat.hs | 5 +- src/Simplex/Chat/Controller.hs | 8 +- src/Simplex/Chat/Library/Commands.hs | 10 +- src/Simplex/Chat/Operators/Presets.hs | 4 +- src/Simplex/Chat/Options.hs | 14 +- 14 files changed, 156 insertions(+), 94 deletions(-) diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift index 55f2e837b8..fa698f8b7c 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift @@ -209,11 +209,16 @@ struct AdvancedNetworkSettings: View { } Section { - Toggle("Use web port", isOn: $netCfg.smpWebPort) + Picker("Use web port", selection: $netCfg.smpWebPortServers) { + ForEach(SMPWebPortServers.allCases, id: \.self) { Text($0.text) } + } + .frame(height: 36) } header: { Text("TCP port for messaging") } footer: { - Text("Use TCP port \(netCfg.smpWebPort ? "443" : "5223") when no port is specified.") + netCfg.smpWebPortServers == .preset + ? Text("Use TCP port 443 for preset servers only.") + : Text("Use TCP port \(netCfg.smpWebPortServers == .all ? "443" : "5223") when no port is specified.") } Section("TCP connection") { diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index a9de0df01b..05fc4ff87b 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -1796,7 +1796,7 @@ public struct NetCfg: Codable, Equatable { public var sessionMode = TransportSessionMode.user public var smpProxyMode: SMPProxyMode = .always public var smpProxyFallback: SMPProxyFallback = .allowProtected - public var smpWebPort = false + public var smpWebPortServers: SMPWebPortServers = .preset public var tcpConnectTimeout: Int // microseconds public var tcpTimeout: Int // microseconds public var tcpTimeoutPerKb: Int // microseconds @@ -1892,6 +1892,20 @@ public enum SMPProxyFallback: String, Codable, SelectableItem { public static let values: [SMPProxyFallback] = [.allow, .allowProtected, .prohibit] } +public enum SMPWebPortServers: String, Codable, CaseIterable { + case all = "all" + case preset = "preset" + case off = "off" + + public var text: LocalizedStringKey { + switch self { + case .all: "All servers" + case .preset: "Preset servers" + case .off: "Off" + } + } +} + public enum OnionHosts: String, Identifiable { case no case prefer diff --git a/apps/ios/SimpleXChat/AppGroup.swift b/apps/ios/SimpleXChat/AppGroup.swift index 45a05a1cd6..75bb537b94 100644 --- a/apps/ios/SimpleXChat/AppGroup.swift +++ b/apps/ios/SimpleXChat/AppGroup.swift @@ -40,7 +40,7 @@ let GROUP_DEFAULT_NETWORK_USE_ONION_HOSTS = "networkUseOnionHosts" let GROUP_DEFAULT_NETWORK_SESSION_MODE = "networkSessionMode" let GROUP_DEFAULT_NETWORK_SMP_PROXY_MODE = "networkSMPProxyMode" let GROUP_DEFAULT_NETWORK_SMP_PROXY_FALLBACK = "networkSMPProxyFallback" -let GROUP_DEFAULT_NETWORK_SMP_WEB_PORT = "networkSMPWebPort" +let GROUP_DEFAULT_NETWORK_SMP_WEB_PORT_SERVERS = "networkSMPWebPortServers" let GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT = "networkTCPConnectTimeout" let GROUP_DEFAULT_NETWORK_TCP_TIMEOUT = "networkTCPTimeout" let GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB = "networkTCPTimeoutPerKb" @@ -72,7 +72,7 @@ public func registerGroupDefaults() { GROUP_DEFAULT_NETWORK_SESSION_MODE: TransportSessionMode.session.rawValue, GROUP_DEFAULT_NETWORK_SMP_PROXY_MODE: SMPProxyMode.unknown.rawValue, GROUP_DEFAULT_NETWORK_SMP_PROXY_FALLBACK: SMPProxyFallback.allowProtected.rawValue, - GROUP_DEFAULT_NETWORK_SMP_WEB_PORT: false, + GROUP_DEFAULT_NETWORK_SMP_WEB_PORT_SERVERS: SMPWebPortServers.preset.rawValue, GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT: NetCfg.defaults.tcpConnectTimeout, GROUP_DEFAULT_NETWORK_TCP_TIMEOUT: NetCfg.defaults.tcpTimeout, GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB: NetCfg.defaults.tcpTimeoutPerKb, @@ -251,6 +251,12 @@ public let networkSMPProxyFallbackGroupDefault = EnumDefault( withDefault: .allowProtected ) +public let networkSMPWebPortServersDefault = EnumDefault( + defaults: groupDefaults, + forKey: GROUP_DEFAULT_NETWORK_SMP_WEB_PORT_SERVERS, + withDefault: .preset +) + public let storeDBPassphraseGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_STORE_DB_PASSPHRASE) public let initialRandomDBPassphraseGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_INITIAL_RANDOM_DB_PASSPHRASE) @@ -338,7 +344,7 @@ public func getNetCfg() -> NetCfg { let sessionMode = networkSessionModeGroupDefault.get() let smpProxyMode = networkSMPProxyModeGroupDefault.get() let smpProxyFallback = networkSMPProxyFallbackGroupDefault.get() - let smpWebPort = groupDefaults.bool(forKey: GROUP_DEFAULT_NETWORK_SMP_WEB_PORT) + let smpWebPortServers = networkSMPWebPortServersDefault.get() let tcpConnectTimeout = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT) let tcpTimeout = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT) let tcpTimeoutPerKb = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB) @@ -362,7 +368,7 @@ public func getNetCfg() -> NetCfg { sessionMode: sessionMode, smpProxyMode: smpProxyMode, smpProxyFallback: smpProxyFallback, - smpWebPort: smpWebPort, + smpWebPortServers: smpWebPortServers, tcpConnectTimeout: tcpConnectTimeout, tcpTimeout: tcpTimeout, tcpTimeoutPerKb: tcpTimeoutPerKb, @@ -381,7 +387,7 @@ public func setNetCfg(_ cfg: NetCfg, networkProxy: NetworkProxy?) { networkSMPProxyFallbackGroupDefault.set(cfg.smpProxyFallback) let socksProxy = networkProxy?.toProxyString() groupDefaults.set(socksProxy, forKey: GROUP_DEFAULT_NETWORK_SOCKS_PROXY) - groupDefaults.set(cfg.smpWebPort, forKey: GROUP_DEFAULT_NETWORK_SMP_WEB_PORT) + networkSMPWebPortServersDefault.set(cfg.smpWebPortServers) groupDefaults.set(cfg.tcpConnectTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT) groupDefaults.set(cfg.tcpTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT) groupDefaults.set(cfg.tcpTimeoutPerKb, forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index a557cf93cf..bfb587820a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -20,6 +20,8 @@ import chat.simplex.common.model.ChatController.getNetCfg import chat.simplex.common.model.ChatController.setNetCfg import chat.simplex.common.model.ChatModel.changingActiveUserMutex import chat.simplex.common.model.MsgContent.MCUnknown +import chat.simplex.common.model.SMPProxyFallback.AllowProtected +import chat.simplex.common.model.SMPProxyMode.Always import dev.icerock.moko.resources.compose.painterResource import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* @@ -86,18 +88,7 @@ class AppPreferences { val backgroundServiceBatteryNoticeShown = mkBoolPreference(SHARED_PREFS_SERVICE_BATTERY_NOTICE_SHOWN, false) val autoRestartWorkerVersion = mkIntPreference(SHARED_PREFS_AUTO_RESTART_WORKER_VERSION, 0) val webrtcPolicyRelay = mkBoolPreference(SHARED_PREFS_WEBRTC_POLICY_RELAY, true) - private val _callOnLockScreen = mkStrPreference(SHARED_PREFS_WEBRTC_CALLS_ON_LOCK_SCREEN, CallOnLockScreen.default.name) - val callOnLockScreen: SharedPreference = SharedPreference( - get = fun(): CallOnLockScreen { - val value = _callOnLockScreen.get() ?: return CallOnLockScreen.default - return try { - CallOnLockScreen.valueOf(value) - } catch (e: Throwable) { - CallOnLockScreen.default - } - }, - set = fun(action: CallOnLockScreen) { _callOnLockScreen.set(action.name) } - ) + val callOnLockScreen: SharedPreference = mkSafeEnumPreference(SHARED_PREFS_WEBRTC_CALLS_ON_LOCK_SCREEN, CallOnLockScreen.default) val performLA = mkBoolPreference(SHARED_PREFS_PERFORM_LA, false) val laMode = mkEnumPreference(SHARED_PREFS_LA_MODE, LAMode.default) { LAMode.values().firstOrNull { it.name == this } } val laLockDelay = mkIntPreference(SHARED_PREFS_LA_LOCK_DELAY, 30) @@ -107,18 +98,7 @@ class AppPreferences { val privacyAcceptImages = mkBoolPreference(SHARED_PREFS_PRIVACY_ACCEPT_IMAGES, true) val privacyLinkPreviews = mkBoolPreference(SHARED_PREFS_PRIVACY_LINK_PREVIEWS, true) val privacyChatListOpenLinks = mkEnumPreference(SHARED_PREFS_PRIVACY_CHAT_LIST_OPEN_LINKS, PrivacyChatListOpenLinksMode.ASK) { PrivacyChatListOpenLinksMode.values().firstOrNull { it.name == this } } - private val _simplexLinkMode = mkStrPreference(SHARED_PREFS_PRIVACY_SIMPLEX_LINK_MODE, SimplexLinkMode.default.name) - val simplexLinkMode: SharedPreference = SharedPreference( - get = fun(): SimplexLinkMode { - val value = _simplexLinkMode.get() ?: return SimplexLinkMode.default - return try { - SimplexLinkMode.valueOf(value) - } catch (e: Throwable) { - SimplexLinkMode.default - } - }, - set = fun(mode: SimplexLinkMode) { _simplexLinkMode.set(mode.name) } - ) + val simplexLinkMode: SharedPreference = mkSafeEnumPreference(SHARED_PREFS_PRIVACY_SIMPLEX_LINK_MODE, SimplexLinkMode.default) val privacyShowChatPreviews = mkBoolPreference(SHARED_PREFS_PRIVACY_SHOW_CHAT_PREVIEWS, true) val privacySaveLastDraft = mkBoolPreference(SHARED_PREFS_PRIVACY_SAVE_LAST_DRAFT, true) val privacyShortLinks = mkBoolPreference(SHARED_PREFS_PRIVACY_SHORT_LINKS, false) @@ -158,23 +138,12 @@ class AppPreferences { }, set = fun(proxy: NetworkProxy) { _networkProxy.set(json.encodeToString(proxy)) } ) - private val _networkSessionMode = mkStrPreference(SHARED_PREFS_NETWORK_SESSION_MODE, TransportSessionMode.default.name) - val networkSessionMode: SharedPreference = SharedPreference( - get = fun(): TransportSessionMode { - val value = _networkSessionMode.get() ?: return TransportSessionMode.default - return try { - TransportSessionMode.valueOf(value) - } catch (e: Throwable) { - TransportSessionMode.default - } - }, - set = fun(mode: TransportSessionMode) { _networkSessionMode.set(mode.name) } - ) - val networkSMPProxyMode = mkStrPreference(SHARED_PREFS_NETWORK_SMP_PROXY_MODE, NetCfg.defaults.smpProxyMode.name) - val networkSMPProxyFallback = mkStrPreference(SHARED_PREFS_NETWORK_SMP_PROXY_FALLBACK, NetCfg.defaults.smpProxyFallback.name) - val networkHostMode = mkStrPreference(SHARED_PREFS_NETWORK_HOST_MODE, HostMode.OnionViaSocks.name) + val networkSessionMode: SharedPreference = mkSafeEnumPreference(SHARED_PREFS_NETWORK_SESSION_MODE, TransportSessionMode.default) + val networkSMPProxyMode: SharedPreference = mkSafeEnumPreference(SHARED_PREFS_NETWORK_SMP_PROXY_MODE, SMPProxyMode.default) + val networkSMPProxyFallback: SharedPreference = mkSafeEnumPreference(SHARED_PREFS_NETWORK_SMP_PROXY_FALLBACK, SMPProxyFallback.default) + val networkHostMode: SharedPreference = mkSafeEnumPreference(SHARED_PREFS_NETWORK_HOST_MODE, HostMode.default) val networkRequiredHostMode = mkBoolPreference(SHARED_PREFS_NETWORK_REQUIRED_HOST_MODE, false) - val networkSMPWebPort = mkBoolPreference(SHARED_PREFS_NETWORK_SMP_WEB_PORT, false) + val networkSMPWebPortServers: SharedPreference = mkSafeEnumPreference(SHARED_PREFS_NETWORK_SMP_WEB_PORT_SERVERS, SMPWebPortServers.default) val networkTCPConnectTimeout = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT, NetCfg.defaults.tcpConnectTimeout, NetCfg.proxyDefaults.tcpConnectTimeout) val networkTCPTimeout = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_TIMEOUT, NetCfg.defaults.tcpTimeout, NetCfg.proxyDefaults.tcpTimeout) val networkTCPTimeoutPerKb = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_TIMEOUT_PER_KB, NetCfg.defaults.tcpTimeoutPerKb, NetCfg.proxyDefaults.tcpTimeoutPerKb) @@ -329,7 +298,19 @@ class AppPreferences { set = fun(value) = settings.putString(prefName, value.toString()) ) - // LALAL + private inline fun > mkSafeEnumPreference(key: String, default: T): SharedPreference = SharedPreference( + get = { + val value = settings.getString(key, "") + if (value == "") return@SharedPreference default + try { + enumValueOf(value) + } catch (e: IllegalArgumentException) { + default + } + }, + set = { value -> settings.putString(key, value.name) } + ) + private fun mkDatePreference(prefName: String, default: Instant?): SharedPreference = SharedPreference( get = { @@ -414,7 +395,7 @@ class AppPreferences { private const val SHARED_PREFS_NETWORK_SMP_PROXY_FALLBACK = "NetworkSMPProxyFallback" private const val SHARED_PREFS_NETWORK_HOST_MODE = "NetworkHostMode" private const val SHARED_PREFS_NETWORK_REQUIRED_HOST_MODE = "NetworkRequiredHostMode" - private const val SHARED_PREFS_NETWORK_SMP_WEB_PORT = "NetworkSMPWebPort" + private const val SHARED_PREFS_NETWORK_SMP_WEB_PORT_SERVERS = "NetworkSMPWebPortServers" private const val SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT = "NetworkTCPConnectTimeout" private const val SHARED_PREFS_NETWORK_TCP_TIMEOUT = "NetworkTCPTimeout" private const val SHARED_PREFS_NETWORK_TCP_TIMEOUT_PER_KB = "networkTCPTimeoutPerKb" @@ -3309,12 +3290,12 @@ object ChatController { } else { null } - val hostMode = HostMode.valueOf(appPrefs.networkHostMode.get()!!) + val hostMode = appPrefs.networkHostMode.get() val requiredHostMode = appPrefs.networkRequiredHostMode.get() val sessionMode = appPrefs.networkSessionMode.get() - val smpProxyMode = SMPProxyMode.valueOf(appPrefs.networkSMPProxyMode.get()!!) - val smpProxyFallback = SMPProxyFallback.valueOf(appPrefs.networkSMPProxyFallback.get()!!) - val smpWebPort = appPrefs.networkSMPWebPort.get() + val smpProxyMode = appPrefs.networkSMPProxyMode.get() + val smpProxyFallback = appPrefs.networkSMPProxyFallback.get() + val smpWebPortServers = appPrefs.networkSMPWebPortServers.get() val tcpConnectTimeout = appPrefs.networkTCPConnectTimeout.get() val tcpTimeout = appPrefs.networkTCPTimeout.get() val tcpTimeoutPerKb = appPrefs.networkTCPTimeoutPerKb.get() @@ -3337,7 +3318,7 @@ object ChatController { sessionMode = sessionMode, smpProxyMode = smpProxyMode, smpProxyFallback = smpProxyFallback, - smpWebPort = smpWebPort, + smpWebPortServers = smpWebPortServers, tcpConnectTimeout = tcpConnectTimeout, tcpTimeout = tcpTimeout, tcpTimeoutPerKb = tcpTimeoutPerKb, @@ -3353,12 +3334,12 @@ object ChatController { * */ fun setNetCfg(cfg: NetCfg) { appPrefs.networkUseSocksProxy.set(cfg.useSocksProxy) - appPrefs.networkHostMode.set(cfg.hostMode.name) + appPrefs.networkHostMode.set(cfg.hostMode) appPrefs.networkRequiredHostMode.set(cfg.requiredHostMode) appPrefs.networkSessionMode.set(cfg.sessionMode) - appPrefs.networkSMPProxyMode.set(cfg.smpProxyMode.name) - appPrefs.networkSMPProxyFallback.set(cfg.smpProxyFallback.name) - appPrefs.networkSMPWebPort.set(cfg.smpWebPort) + appPrefs.networkSMPProxyMode.set(cfg.smpProxyMode) + appPrefs.networkSMPProxyFallback.set(cfg.smpProxyFallback) + appPrefs.networkSMPWebPortServers.set(cfg.smpWebPortServers) appPrefs.networkTCPConnectTimeout.set(cfg.tcpConnectTimeout) appPrefs.networkTCPTimeout.set(cfg.tcpTimeout) appPrefs.networkTCPTimeoutPerKb.set(cfg.tcpTimeoutPerKb) @@ -4460,13 +4441,13 @@ data class ParsedServerAddress ( @Serializable data class NetCfg( val socksProxy: String?, - val socksMode: SocksMode = SocksMode.Always, - val hostMode: HostMode = HostMode.OnionViaSocks, + val socksMode: SocksMode = SocksMode.default, + val hostMode: HostMode = HostMode.default, val requiredHostMode: Boolean = false, val sessionMode: TransportSessionMode = TransportSessionMode.default, - val smpProxyMode: SMPProxyMode = SMPProxyMode.Always, - val smpProxyFallback: SMPProxyFallback = SMPProxyFallback.AllowProtected, - val smpWebPort: Boolean = false, + val smpProxyMode: SMPProxyMode = SMPProxyMode.default, + val smpProxyFallback: SMPProxyFallback = SMPProxyFallback.default, + val smpWebPortServers: SMPWebPortServers = SMPWebPortServers.default, val tcpConnectTimeout: Long, // microseconds val tcpTimeout: Long, // microseconds val tcpTimeoutPerKb: Long, // microseconds @@ -4564,12 +4545,20 @@ enum class HostMode { @SerialName("onionViaSocks") OnionViaSocks, @SerialName("onion") Onion, @SerialName("public") Public; + + companion object { + val default = OnionViaSocks + } } @Serializable enum class SocksMode { @SerialName("always") Always, @SerialName("onion") Onion; + + companion object { + val default = Always + } } @Serializable @@ -4578,6 +4567,10 @@ enum class SMPProxyMode { @SerialName("unknown") Unknown, @SerialName("unprotected") Unprotected, @SerialName("never") Never; + + companion object { + val default = Always + } } @Serializable @@ -4585,6 +4578,27 @@ enum class SMPProxyFallback { @SerialName("allow") Allow, @SerialName("allowProtected") AllowProtected, @SerialName("prohibit") Prohibit; + + companion object { + val default = AllowProtected + } +} + +@Serializable +enum class SMPWebPortServers { + @SerialName("all") All, + @SerialName("preset") Preset, + @SerialName("off") Off; + + val text get(): StringResource = when (this) { + All -> MR.strings.network_smp_web_port_all + Preset -> MR.strings.network_smp_web_port_preset + Off -> MR.strings.network_smp_web_port_off + } + + companion object { + val default = Preset + } } @Serializable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/AdvancedNetworkSettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/AdvancedNetworkSettings.kt index e85d88c5f8..0c38b0c045 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/AdvancedNetworkSettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/AdvancedNetworkSettings.kt @@ -19,6 +19,7 @@ import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.ui.theme.* @@ -45,7 +46,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U val sessionMode = remember { mutableStateOf(currentCfgVal.sessionMode) } val smpProxyMode = remember { mutableStateOf(currentCfgVal.smpProxyMode) } val smpProxyFallback = remember { mutableStateOf(currentCfgVal.smpProxyFallback) } - val smpWebPort = remember { mutableStateOf(currentCfgVal.smpWebPort) } + val smpWebPortServers = remember { mutableStateOf(currentCfgVal.smpWebPortServers) } val networkTCPConnectTimeout = remember { mutableStateOf(currentCfgVal.tcpConnectTimeout) } val networkTCPTimeout = remember { mutableStateOf(currentCfgVal.tcpTimeout) } @@ -84,7 +85,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U sessionMode = sessionMode.value, smpProxyMode = smpProxyMode.value, smpProxyFallback = smpProxyFallback.value, - smpWebPort = smpWebPort.value, + smpWebPortServers = smpWebPortServers.value, tcpConnectTimeout = networkTCPConnectTimeout.value, tcpTimeout = networkTCPTimeout.value, tcpTimeoutPerKb = networkTCPTimeoutPerKb.value, @@ -99,7 +100,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U sessionMode.value = cfg.sessionMode smpProxyMode.value = cfg.smpProxyMode smpProxyFallback.value = cfg.smpProxyFallback - smpWebPort.value = cfg.smpWebPort + smpWebPortServers.value = cfg.smpWebPortServers networkTCPConnectTimeout.value = cfg.tcpConnectTimeout networkTCPTimeout.value = cfg.tcpTimeout networkTCPTimeoutPerKb.value = cfg.tcpTimeoutPerKb @@ -154,7 +155,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U sessionMode = sessionMode, smpProxyMode = smpProxyMode, smpProxyFallback = smpProxyFallback, - smpWebPort, + smpWebPortServers, networkTCPConnectTimeout, networkTCPTimeout, networkTCPTimeoutPerKb, @@ -187,7 +188,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U sessionMode: MutableState, smpProxyMode: MutableState, smpProxyFallback: MutableState, - smpWebPort: MutableState, + smpWebPortServers: MutableState, networkTCPConnectTimeout: MutableState, networkTCPTimeout: MutableState, networkTCPTimeoutPerKb: MutableState, @@ -226,11 +227,16 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U } SectionDividerSpaced() SectionView(stringResource(MR.strings.network_smp_web_port_section_title).uppercase()) { - PreferenceToggle(stringResource(MR.strings.network_smp_web_port_toggle), checked = smpWebPort.value) { - smpWebPort.value = it - } + ExposedDropDownSettingRow( + stringResource(MR.strings.network_smp_web_port_toggle), + SMPWebPortServers.entries.map { it to stringResource(it.text) }, + smpWebPortServers + ) { smpWebPortServers.value = it } } - SectionTextFooter(String.format(stringResource(MR.strings.network_smp_web_port_footer), if (smpWebPort.value) "443" else "5223")) + SectionTextFooter( + if (smpWebPortServers.value == SMPWebPortServers.Preset) stringResource(MR.strings.network_smp_web_port_preset_footer) + else String.format(stringResource(MR.strings.network_smp_web_port_footer), if (smpWebPortServers.value == SMPWebPortServers.All) "443" else "5223") + ) SectionDividerSpaced(maxTopPadding = true) SectionView(stringResource(MR.strings.network_option_tcp_connection).uppercase()) { @@ -320,7 +326,7 @@ private fun SMPProxyModePicker( ) { val density = LocalDensity.current val values = remember { - SMPProxyMode.values().map { + SMPProxyMode.entries.map { when (it) { SMPProxyMode.Always -> ValueTitleDesc(SMPProxyMode.Always, generalGetString(MR.strings.network_smp_proxy_mode_always), escapedHtmlToAnnotatedString(generalGetString(MR.strings.network_smp_proxy_mode_always_description), density)) SMPProxyMode.Unknown -> ValueTitleDesc(SMPProxyMode.Unknown, generalGetString(MR.strings.network_smp_proxy_mode_unknown), escapedHtmlToAnnotatedString(generalGetString(MR.strings.network_smp_proxy_mode_unknown_description), density)) @@ -355,7 +361,7 @@ private fun SMPProxyFallbackPicker( ) { val density = LocalDensity.current val values = remember { - SMPProxyFallback.values().map { + SMPProxyFallback.entries.map { when (it) { SMPProxyFallback.Allow -> ValueTitleDesc(SMPProxyFallback.Allow, generalGetString(MR.strings.network_smp_proxy_fallback_allow), escapedHtmlToAnnotatedString(generalGetString(MR.strings.network_smp_proxy_fallback_allow_description), density)) SMPProxyFallback.AllowProtected -> ValueTitleDesc(SMPProxyFallback.AllowProtected, generalGetString(MR.strings.network_smp_proxy_fallback_allow_protected), escapedHtmlToAnnotatedString(generalGetString(MR.strings.network_smp_proxy_fallback_allow_protected_description), density)) @@ -548,7 +554,7 @@ fun PreviewAdvancedNetworkSettingsLayout() { sessionMode = remember { mutableStateOf(TransportSessionMode.User) }, smpProxyMode = remember { mutableStateOf(SMPProxyMode.Never) }, smpProxyFallback = remember { mutableStateOf(SMPProxyFallback.Allow) }, - smpWebPort = remember { mutableStateOf(false) }, + smpWebPortServers = remember { mutableStateOf(SMPWebPortServers.Preset) }, networkTCPConnectTimeout = remember { mutableStateOf(10_000000) }, networkTCPTimeout = remember { mutableStateOf(10_000000) }, networkTCPTimeoutPerKb = remember { mutableStateOf(10_000) }, diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 90a176658d..1bea4c18d4 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -964,6 +964,10 @@ TCP port for messaging Use web port Use TCP port %1$s when no port is specified. + Use TCP port 443 for preset servers only. + All servers + Preset servers + Off Appearance Customize theme INTERFACE COLORS diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml index dafa22dfa9..97742f82a8 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -2440,6 +2440,10 @@ Пожаловаться: увидят только модераторы группы. Выключить уведомления для всех Использовать TCP-порт %1$s, когда порт не указан. + Использовать TCP-порт 443 только для серверов по умолчанию. + Все серверы + Серверы по умолчанию + Нет Использовать веб-порт Нет Пожаловаться на сообщение: увидят только модераторы группы. diff --git a/cabal.project b/cabal.project index 28403c9fff..4a95ae3cfe 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: ec5a60430d6e2b1b4f33fa1790effbb6060bf7b8 + tag: 08b84deba458407ae97d55debd98b872cb6c4d79 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index e71a13b22a..c945d72656 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."ec5a60430d6e2b1b4f33fa1790effbb6060bf7b8" = "0q238j7w976f9nx7r3gd61yhj557zwcxvrbci5lq7fib0v4ja7aw"; + "https://github.com/simplex-chat/simplexmq.git"."08b84deba458407ae97d55debd98b872cb6c4d79" = "0b4n7d81spl1r7zppr0lc40ls9m1i93g4l3hzg2996pi3bxmafrr"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 02a765bb19..6b554d29c4 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -89,6 +89,7 @@ defaultChatConfig = -- to have a different set of servers on the receiving end and on the sending end. -- To preserve backward compatibility receiving end should update before the sending. shortLinkPresetServers = allPresetServers, + presetDomains = [".simplex.im", ".simplexonflux.com"], tbqSize = 1024, fileChunkSize = 15780, -- do not change xftpDescrPartSize = 14000, @@ -240,12 +241,12 @@ newChatController randomServerCfgs name p opDomains rndSrvs = toJustOrError name $ L.nonEmpty $ agentServerCfgs p opDomains $ concatMap (pServers p) rndSrvs agentServers :: DB.Connection -> ChatConfig -> NonEmpty PresetOperator -> RandomAgentServers -> IO InitialAgentServers - agentServers db ChatConfig {presetServers = PresetServers {ntf, netCfg}} presetOps as = do + agentServers db ChatConfig {presetServers = PresetServers {ntf, netCfg}, presetDomains} presetOps as = do users <- getUsers db ops <- getUpdateServerOperators db presetOps (null users) let opDomains = operatorDomains $ mapMaybe snd ops (smp', xftp') <- unzip <$> mapM (getServers ops opDomains) users - pure InitialAgentServers {smp = M.fromList (optServers smp' smpServers), xftp = M.fromList (optServers xftp' xftpServers), ntf, netCfg} + pure InitialAgentServers {smp = M.fromList (optServers smp' smpServers), xftp = M.fromList (optServers xftp' xftpServers), ntf, netCfg, presetDomains} where optServers :: [(UserId, NonEmpty (ServerCfg p))] -> [ProtoServerWithAuth p] -> [(UserId, NonEmpty (ServerCfg p))] optServers srvs overrides_ = case L.nonEmpty overrides_ of diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 21d5f1041b..bd4d418158 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -50,6 +50,7 @@ import Data.Time.Clock.System (SystemTime (..), systemToUTCTime) import Data.Version (showVersion) import Data.Word (Word16) import Language.Haskell.TH (Exp, Q, runIO) +import Network.Socket (HostName) import Numeric.Natural import qualified Paths_simplex_chat as SC import Simplex.Chat.AppSettings @@ -77,7 +78,7 @@ import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Store.Common (DBStore, withTransaction, withTransactionPriority) import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation, UpMigration) import qualified Simplex.Messaging.Agent.Store.DB as DB -import Simplex.Messaging.Client (HostMode (..), SMPProxyFallback (..), SMPProxyMode (..), SocksMode (..)) +import Simplex.Messaging.Client (HostMode (..), SMPProxyFallback (..), SMPProxyMode (..), SMPWebPortServers (..), SocksMode (..)) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..)) import qualified Simplex.Messaging.Crypto.File as CF @@ -139,6 +140,7 @@ data ChatConfig = ChatConfig confirmMigrations :: MigrationConfirmation, presetServers :: PresetServers, shortLinkPresetServers :: NonEmpty SMPServer, + presetDomains :: [HostName], tbqSize :: Natural, fileChunkSize :: Integer, xftpDescrPartSize :: Int, @@ -1053,7 +1055,7 @@ data SimpleNetCfg = SimpleNetCfg requiredHostMode :: Bool, smpProxyMode_ :: Maybe SMPProxyMode, smpProxyFallback_ :: Maybe SMPProxyFallback, - smpWebPort :: Bool, + smpWebPortServers :: SMPWebPortServers, tcpTimeout_ :: Maybe Int, logTLSErrors :: Bool } @@ -1068,7 +1070,7 @@ defaultSimpleNetCfg = requiredHostMode = False, smpProxyMode_ = Nothing, smpProxyFallback_ = Nothing, - smpWebPort = False, + smpWebPortServers = SWPPreset, tcpTimeout_ = Nothing, logTLSErrors = False } diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 5c544e3397..d10959178c 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -91,7 +91,7 @@ import Simplex.Messaging.Agent.Store.Interface (execSQL) import Simplex.Messaging.Agent.Store.Shared (upMigration) import qualified Simplex.Messaging.Agent.Store.DB as DB import Simplex.Messaging.Agent.Store.Interface (getCurrentMigrations) -import Simplex.Messaging.Client (NetworkConfig (..), SocksMode (SMAlways), textToHostMode) +import Simplex.Messaging.Client (NetworkConfig (..), SMPWebPortServers (..), SocksMode (SMAlways), textToHostMode) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import qualified Simplex.Messaging.Crypto.File as CF @@ -255,11 +255,11 @@ stopChatController ChatController {smpAgent, agentAsync = s, sndFiles, rcvFiles, atomically $ writeTVar files M.empty updateNetworkConfig :: NetworkConfig -> SimpleNetCfg -> NetworkConfig -updateNetworkConfig cfg SimpleNetCfg {socksProxy, socksMode, hostMode, requiredHostMode, smpProxyMode_, smpProxyFallback_, smpWebPort, tcpTimeout_, logTLSErrors} = +updateNetworkConfig cfg SimpleNetCfg {socksProxy, socksMode, hostMode, requiredHostMode, smpProxyMode_, smpProxyFallback_, smpWebPortServers, tcpTimeout_, logTLSErrors} = let cfg1 = maybe cfg (\smpProxyMode -> cfg {smpProxyMode}) smpProxyMode_ cfg2 = maybe cfg1 (\smpProxyFallback -> cfg1 {smpProxyFallback}) smpProxyFallback_ cfg3 = maybe cfg2 (\tcpTimeout -> cfg2 {tcpTimeout, tcpConnectTimeout = (tcpTimeout * 3) `div` 2}) tcpTimeout_ - in cfg3 {socksProxy, socksMode, hostMode, requiredHostMode, smpWebPort, logTLSErrors} + in cfg3 {socksProxy, socksMode, hostMode, requiredHostMode, smpWebPortServers, logTLSErrors} useServers :: Foldable f => RandomAgentServers -> [(Text, ServerOperator)] -> f UserOperatorServers -> (NonEmpty (ServerCfg 'PSMP), NonEmpty (ServerCfg 'PXFTP)) useServers as opDomains uss = @@ -4383,11 +4383,11 @@ chatCommandP = requiredHostMode <- (" required-host-mode" $> True) <|> pure False smpProxyMode_ <- optional $ " smp-proxy=" *> strP smpProxyFallback_ <- optional $ " smp-proxy-fallback=" *> strP - smpWebPort <- (" smp-web-port" $> True) <|> pure False + smpWebPortServers <- (" smp-web-port-servers=" *> strP) <|> (" smp-web-port" $> SWPAll) <|> pure SWPPreset t_ <- optional $ " timeout=" *> A.decimal logTLSErrors <- " log=" *> onOffP <|> pure False let tcpTimeout_ = (1000000 *) <$> t_ - pure $ SimpleNetCfg {socksProxy, socksMode, hostMode, requiredHostMode, smpProxyMode_, smpProxyFallback_, smpWebPort, tcpTimeout_, logTLSErrors} + pure $ SimpleNetCfg {socksProxy, socksMode, hostMode, requiredHostMode, smpProxyMode_, smpProxyFallback_, smpWebPortServers, tcpTimeout_, logTLSErrors} #if !defined(dbPostgres) dbKeyP = nonEmptyKey <$?> strP nonEmptyKey k@(DBEncryptionKey s) = if BA.null s then Left "empty key" else Right k diff --git a/src/Simplex/Chat/Operators/Presets.hs b/src/Simplex/Chat/Operators/Presets.hs index 4aa0903d3c..06c2e19fab 100644 --- a/src/Simplex/Chat/Operators/Presets.hs +++ b/src/Simplex/Chat/Operators/Presets.hs @@ -46,7 +46,7 @@ operatorFlux = -- so that option used for restoring links is updated earlier, for backward/forward compatibility. allPresetServers :: NonEmpty SMPServer allPresetServers = enabledSimplexChatSMPServers <> disabledSimplexChatSMPServers <> fluxSMPServers_ - -- TODO [short links] remove, added for testing + -- added for testing, not preset in the clients <> ["smp://8Af90NX2TTkKEJAF1RCg69P_Odg2Z-6_J6DOKUqK3rQ=@smp7.simplex.im,dbxqutskmmbkbrs7ofi7pmopeyhgi5cxbjbh4ummgmep4r6bz4cbrcid.onion"] simplexChatSMPServers :: [NewUserServer 'PSMP] @@ -87,7 +87,7 @@ disabledSimplexChatSMPServers = ] fluxSMPServers :: [NewUserServer 'PSMP] -fluxSMPServers = map (presetServer' True) $ L.toList fluxSMPServers_ +fluxSMPServers = map (presetServer' True) (L.toList fluxSMPServers_) -- Please note: if any servers are removed from this list, they MUST be added to allPresetServers. -- Otherwise previously created short links won't work. diff --git a/src/Simplex/Chat/Options.hs b/src/Simplex/Chat/Options.hs index de657a9d74..ff2af56b85 100644 --- a/src/Simplex/Chat/Options.hs +++ b/src/Simplex/Chat/Options.hs @@ -29,7 +29,7 @@ import Numeric.Natural (Natural) import Options.Applicative import Simplex.Chat.Controller (ChatLogLevel (..), SimpleNetCfg (..), updateStr, versionNumber, versionString) import Simplex.FileTransfer.Description (mb) -import Simplex.Messaging.Client (HostMode (..), SocksMode (..), textToHostMode) +import Simplex.Messaging.Client (HostMode (..), SMPWebPortServers (..), SocksMode (..), textToHostMode) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (parseAll) import Simplex.Messaging.Protocol (ProtoServerWithAuth, ProtocolTypeI, SMPServerWithAuth, XFTPServerWithAuth) @@ -153,11 +153,17 @@ coreChatOptsP appDir defaultDbName = do <> metavar "SMP_PROXY_FALLBACK_MODE" <> help "Allow downgrade and connect directly: no, [when IP address is] protected (default), yes" ) - smpWebPort <- - switch + smpWebPortServers <- + flag' SWPAll ( long "smp-web-port" <> help "Use port 443 with SMP servers when not specified" ) + <|> option + strParse + ( long "smp-web-port-servers" + <> help "Use port 443 with SMP servers when not specified: all, preset (default), off" + <> value SWPPreset + ) t <- option auto @@ -243,7 +249,7 @@ coreChatOptsP appDir defaultDbName = do requiredHostMode, smpProxyMode_, smpProxyFallback_, - smpWebPort, + smpWebPortServers, tcpTimeout_ = Just $ useTcpTimeout socksProxy t, logTLSErrors }, From 63902633705faf595edae6ccc658de424a5b45f4 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 26 Apr 2025 16:02:29 +0100 Subject: [PATCH 523/567] android, desktop: additional information about database errors in console (#5853) --- .../chat/simplex/common/model/SimpleXAPI.kt | 134 +++++++++++------- 1 file changed, 82 insertions(+), 52 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index bfb587820a..24df07a052 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -6744,64 +6744,79 @@ sealed class StoreError { val string: String get() = when (this) { is DuplicateName -> "duplicateName" - is UserNotFound -> "userNotFound" - is UserNotFoundByName -> "userNotFoundByName" - is UserNotFoundByContactId -> "userNotFoundByContactId" - is UserNotFoundByGroupId -> "userNotFoundByGroupId" - is UserNotFoundByFileId -> "userNotFoundByFileId" - is UserNotFoundByContactRequestId -> "userNotFoundByContactRequestId" - is ContactNotFound -> "contactNotFound" - is ContactNotFoundByName -> "contactNotFoundByName" - is ContactNotFoundByMemberId -> "contactNotFoundByMemberId" - is ContactNotReady -> "contactNotReady" + is UserNotFound -> "userNotFound $userId" + is UserNotFoundByName -> "userNotFoundByName $contactName" + is UserNotFoundByContactId -> "userNotFoundByContactId $contactId" + is UserNotFoundByGroupId -> "userNotFoundByGroupId $groupId" + is UserNotFoundByFileId -> "userNotFoundByFileId $fileId" + is UserNotFoundByContactRequestId -> "userNotFoundByContactRequestId $contactRequestId" + is ContactNotFound -> "contactNotFound $contactId" + is ContactNotFoundByName -> "contactNotFoundByName $contactName" + is ContactNotFoundByMemberId -> "contactNotFoundByMemberId $groupMemberId" + is ContactNotReady -> "contactNotReady $contactName" is DuplicateContactLink -> "duplicateContactLink" is UserContactLinkNotFound -> "userContactLinkNotFound" - is ContactRequestNotFound -> "contactRequestNotFound" - is ContactRequestNotFoundByName -> "contactRequestNotFoundByName" - is GroupNotFound -> "groupNotFound" - is GroupNotFoundByName -> "groupNotFoundByName" - is GroupMemberNameNotFound -> "groupMemberNameNotFound" - is GroupMemberNotFound -> "groupMemberNotFound" - is GroupMemberNotFoundByMemberId -> "groupMemberNotFoundByMemberId" - is MemberContactGroupMemberNotFound -> "memberContactGroupMemberNotFound" + is ContactRequestNotFound -> "contactRequestNotFound $contactRequestId" + is ContactRequestNotFoundByName -> "contactRequestNotFoundByName $contactName" + is GroupNotFound -> "groupNotFound $groupId" + is GroupNotFoundByName -> "groupNotFoundByName $groupName" + is GroupMemberNameNotFound -> "groupMemberNameNotFound $groupId $groupMemberName" + is GroupMemberNotFound -> "groupMemberNotFound $groupMemberId" + is GroupMemberNotFoundByMemberId -> "groupMemberNotFoundByMemberId $memberId" + is MemberContactGroupMemberNotFound -> "memberContactGroupMemberNotFound $contactId" is GroupWithoutUser -> "groupWithoutUser" is DuplicateGroupMember -> "duplicateGroupMember" is GroupAlreadyJoined -> "groupAlreadyJoined" is GroupInvitationNotFound -> "groupInvitationNotFound" - is SndFileNotFound -> "sndFileNotFound" - is SndFileInvalid -> "sndFileInvalid" - is RcvFileNotFound -> "rcvFileNotFound" - is RcvFileDescrNotFound -> "rcvFileDescrNotFound" - is FileNotFound -> "fileNotFound" - is RcvFileInvalid -> "rcvFileInvalid" + is NoteFolderAlreadyExists -> "noteFolderAlreadyExists $noteFolderId" + is NoteFolderNotFound -> "noteFolderNotFound $noteFolderId" + is UserNoteFolderNotFound -> "userNoteFolderNotFound" + is SndFileNotFound -> "sndFileNotFound $fileId" + is SndFileInvalid -> "sndFileInvalid $fileId" + is RcvFileNotFound -> "rcvFileNotFound $fileId" + is RcvFileDescrNotFound -> "rcvFileDescrNotFound $fileId" + is FileNotFound -> "fileNotFound $fileId" + is RcvFileInvalid -> "rcvFileInvalid $fileId" is RcvFileInvalidDescrPart -> "rcvFileInvalidDescrPart" - is SharedMsgIdNotFoundByFileId -> "sharedMsgIdNotFoundByFileId" - is FileIdNotFoundBySharedMsgId -> "fileIdNotFoundBySharedMsgId" - is SndFileNotFoundXFTP -> "sndFileNotFoundXFTP" - is RcvFileNotFoundXFTP -> "rcvFileNotFoundXFTP" - is ExtraFileDescrNotFoundXFTP -> "extraFileDescrNotFoundXFTP" - is ConnectionNotFound -> "connectionNotFound" - is ConnectionNotFoundById -> "connectionNotFoundById" - is ConnectionNotFoundByMemberId -> "connectionNotFoundByMemberId" - is PendingConnectionNotFound -> "pendingConnectionNotFound" + is LocalFileNoTransfer -> "localFileNoTransfer $fileId" + is SharedMsgIdNotFoundByFileId -> "sharedMsgIdNotFoundByFileId $fileId" + is FileIdNotFoundBySharedMsgId -> "fileIdNotFoundBySharedMsgId $sharedMsgId" + is SndFileNotFoundXFTP -> "sndFileNotFoundXFTP $agentSndFileId" + is RcvFileNotFoundXFTP -> "rcvFileNotFoundXFTP $agentRcvFileId" + is ConnectionNotFound -> "connectionNotFound $agentConnId" + is ConnectionNotFoundById -> "connectionNotFoundById $connId" + is ConnectionNotFoundByMemberId -> "connectionNotFoundByMemberId $groupMemberId" + is PendingConnectionNotFound -> "pendingConnectionNotFound $connId" is IntroNotFound -> "introNotFound" is UniqueID -> "uniqueID" - is InternalError -> "internalError" - is NoMsgDelivery -> "noMsgDelivery" - is BadChatItem -> "badChatItem" - is ChatItemNotFound -> "chatItemNotFound" - is ChatItemNotFoundByText -> "chatItemNotFoundByText" - is ChatItemSharedMsgIdNotFound -> "chatItemSharedMsgIdNotFound" - is ChatItemNotFoundByFileId -> "chatItemNotFoundByFileId" - is ChatItemNotFoundByGroupId -> "chatItemNotFoundByGroupId" - is ProfileNotFound -> "profileNotFound" - is DuplicateGroupLink -> "duplicateGroupLink" - is GroupLinkNotFound -> "groupLinkNotFound" - is HostMemberIdNotFound -> "hostMemberIdNotFound" - is ContactNotFoundByFileId -> "contactNotFoundByFileId" - is NoGroupSndStatus -> "noGroupSndStatus" is LargeMsg -> "largeMsg" - is DBException -> "dBException" + is InternalError -> "internalError $message" + is DBException -> "dBException $message" + is DBBusyError -> "dBBusyError $message" + is BadChatItem -> "badChatItem $itemId" + is ChatItemNotFound -> "chatItemNotFound $itemId" + is ChatItemNotFoundByText -> "chatItemNotFoundByText $text" + is ChatItemSharedMsgIdNotFound -> "chatItemSharedMsgIdNotFound $sharedMsgId" + is ChatItemNotFoundByFileId -> "chatItemNotFoundByFileId $fileId" + is ChatItemNotFoundByContactId -> "chatItemNotFoundByContactId $contactId" + is ChatItemNotFoundByGroupId -> "chatItemNotFoundByGroupId $groupId" + is ProfileNotFound -> "profileNotFound $profileId" + is DuplicateGroupLink -> "duplicateGroupLink ${groupInfo.groupId}" + is GroupLinkNotFound -> "groupLinkNotFound ${groupInfo.groupId}" + is HostMemberIdNotFound -> "hostMemberIdNotFound $groupId" + is ContactNotFoundByFileId -> "contactNotFoundByFileId $fileId" + is NoGroupSndStatus -> "noGroupSndStatus $itemId $groupMemberId" + is DuplicateGroupMessage -> "duplicateGroupMessage $groupId $sharedMsgId $authorGroupMemberId $authorGroupMemberId" + is RemoteHostNotFound -> "remoteHostNotFound $remoteHostId" + is RemoteHostUnknown -> "remoteHostUnknown" + is RemoteHostDuplicateCA -> "remoteHostDuplicateCA" + is RemoteCtrlNotFound -> "remoteCtrlNotFound $remoteCtrlId" + is RemoteCtrlDuplicateCA -> "remoteCtrlDuplicateCA" + is ProhibitedDeleteUser -> "prohibitedDeleteUser $userId $contactId" + is OperatorNotFound -> "operatorNotFound $serverOperatorId" + is UsageConditionsNotFound -> "usageConditionsNotFound" + is InvalidQuote -> "invalidQuote" + is InvalidMention -> "invalidMention" } @Serializable @SerialName("duplicateName") object DuplicateName: StoreError() @@ -6829,6 +6844,9 @@ sealed class StoreError { @Serializable @SerialName("duplicateGroupMember") object DuplicateGroupMember: StoreError() @Serializable @SerialName("groupAlreadyJoined") object GroupAlreadyJoined: StoreError() @Serializable @SerialName("groupInvitationNotFound") object GroupInvitationNotFound: StoreError() + @Serializable @SerialName("noteFolderAlreadyExists") class NoteFolderAlreadyExists(val noteFolderId: Long): StoreError() + @Serializable @SerialName("noteFolderNotFound") class NoteFolderNotFound(val noteFolderId: Long): StoreError() + @Serializable @SerialName("userNoteFolderNotFound") object UserNoteFolderNotFound: StoreError() @Serializable @SerialName("sndFileNotFound") class SndFileNotFound(val fileId: Long): StoreError() @Serializable @SerialName("sndFileInvalid") class SndFileInvalid(val fileId: Long): StoreError() @Serializable @SerialName("rcvFileNotFound") class RcvFileNotFound(val fileId: Long): StoreError() @@ -6836,24 +6854,27 @@ sealed class StoreError { @Serializable @SerialName("fileNotFound") class FileNotFound(val fileId: Long): StoreError() @Serializable @SerialName("rcvFileInvalid") class RcvFileInvalid(val fileId: Long): StoreError() @Serializable @SerialName("rcvFileInvalidDescrPart") object RcvFileInvalidDescrPart: StoreError() + @Serializable @SerialName("localFileNoTransfer") class LocalFileNoTransfer(val fileId: Long): StoreError() @Serializable @SerialName("sharedMsgIdNotFoundByFileId") class SharedMsgIdNotFoundByFileId(val fileId: Long): StoreError() @Serializable @SerialName("fileIdNotFoundBySharedMsgId") class FileIdNotFoundBySharedMsgId(val sharedMsgId: String): StoreError() @Serializable @SerialName("sndFileNotFoundXFTP") class SndFileNotFoundXFTP(val agentSndFileId: String): StoreError() @Serializable @SerialName("rcvFileNotFoundXFTP") class RcvFileNotFoundXFTP(val agentRcvFileId: String): StoreError() - @Serializable @SerialName("extraFileDescrNotFoundXFTP") class ExtraFileDescrNotFoundXFTP(val fileId: Long): StoreError() @Serializable @SerialName("connectionNotFound") class ConnectionNotFound(val agentConnId: String): StoreError() @Serializable @SerialName("connectionNotFoundById") class ConnectionNotFoundById(val connId: Long): StoreError() @Serializable @SerialName("connectionNotFoundByMemberId") class ConnectionNotFoundByMemberId(val groupMemberId: Long): StoreError() @Serializable @SerialName("pendingConnectionNotFound") class PendingConnectionNotFound(val connId: Long): StoreError() @Serializable @SerialName("introNotFound") object IntroNotFound: StoreError() @Serializable @SerialName("uniqueID") object UniqueID: StoreError() + @Serializable @SerialName("largeMsg") object LargeMsg: StoreError() @Serializable @SerialName("internalError") class InternalError(val message: String): StoreError() - @Serializable @SerialName("noMsgDelivery") class NoMsgDelivery(val connId: Long, val agentMsgId: String): StoreError() + @Serializable @SerialName("dBException") class DBException(val message: String): StoreError() + @Serializable @SerialName("dBBusyError") class DBBusyError(val message: String): StoreError() @Serializable @SerialName("badChatItem") class BadChatItem(val itemId: Long): StoreError() @Serializable @SerialName("chatItemNotFound") class ChatItemNotFound(val itemId: Long): StoreError() @Serializable @SerialName("chatItemNotFoundByText") class ChatItemNotFoundByText(val text: String): StoreError() @Serializable @SerialName("chatItemSharedMsgIdNotFound") class ChatItemSharedMsgIdNotFound(val sharedMsgId: String): StoreError() @Serializable @SerialName("chatItemNotFoundByFileId") class ChatItemNotFoundByFileId(val fileId: Long): StoreError() + @Serializable @SerialName("chatItemNotFoundByContactId") class ChatItemNotFoundByContactId(val contactId: Long): StoreError() @Serializable @SerialName("chatItemNotFoundByGroupId") class ChatItemNotFoundByGroupId(val groupId: Long): StoreError() @Serializable @SerialName("profileNotFound") class ProfileNotFound(val profileId: Long): StoreError() @Serializable @SerialName("duplicateGroupLink") class DuplicateGroupLink(val groupInfo: GroupInfo): StoreError() @@ -6861,8 +6882,17 @@ sealed class StoreError { @Serializable @SerialName("hostMemberIdNotFound") class HostMemberIdNotFound(val groupId: Long): StoreError() @Serializable @SerialName("contactNotFoundByFileId") class ContactNotFoundByFileId(val fileId: Long): StoreError() @Serializable @SerialName("noGroupSndStatus") class NoGroupSndStatus(val itemId: Long, val groupMemberId: Long): StoreError() - @Serializable @SerialName("largeMsg") object LargeMsg: StoreError() - @Serializable @SerialName("dBException") class DBException(val message: String): StoreError() + @Serializable @SerialName("duplicateGroupMessage") class DuplicateGroupMessage(val groupId: Long, val sharedMsgId: String, val authorGroupMemberId: Long?, val forwardedByGroupMemberId: Long?): StoreError() + @Serializable @SerialName("remoteHostNotFound") class RemoteHostNotFound(val remoteHostId: Long): StoreError() + @Serializable @SerialName("remoteHostUnknown") object RemoteHostUnknown: StoreError() + @Serializable @SerialName("remoteHostDuplicateCA") object RemoteHostDuplicateCA: StoreError() + @Serializable @SerialName("remoteCtrlNotFound") class RemoteCtrlNotFound(val remoteCtrlId: Long): StoreError() + @Serializable @SerialName("remoteCtrlDuplicateCA") class RemoteCtrlDuplicateCA: StoreError() + @Serializable @SerialName("prohibitedDeleteUser") class ProhibitedDeleteUser(val userId: Long, val contactId: Long): StoreError() + @Serializable @SerialName("operatorNotFound") class OperatorNotFound(val serverOperatorId: Long): StoreError() + @Serializable @SerialName("usageConditionsNotFound") object UsageConditionsNotFound: StoreError() + @Serializable @SerialName("invalidQuote") object InvalidQuote: StoreError() + @Serializable @SerialName("invalidMention") object InvalidMention: StoreError() } @Serializable From 7cac164b842c774a399b65f07bd5964b2e6552d5 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 27 Apr 2025 12:38:51 +0100 Subject: [PATCH 524/567] core: use /feed command in broadcast bot (#5854) --- apps/simplex-broadcast-bot/src/Broadcast/Bot.hs | 9 ++++++--- src/Simplex/Chat/Controller.hs | 2 +- src/Simplex/Chat/Library/Commands.hs | 5 ++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs b/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs index 9dc927af9e..15f790e8b1 100644 --- a/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs +++ b/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs @@ -2,6 +2,7 @@ {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} module Broadcast.Bot where @@ -10,6 +11,7 @@ import Control.Concurrent (forkIO) import Control.Concurrent.Async import Control.Concurrent.STM import Control.Monad +import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.Text as T import Broadcast.Options import Simplex.Chat.Bot @@ -47,9 +49,10 @@ broadcastBot BroadcastBotOpts {publishers, welcomeMessage, prohibitedMessage} _u then do sendChatCmd cc ListContacts >>= \case CRContactsList _ cts -> void . forkIO $ do - let cts' = filter broadcastTo cts - forM_ cts' $ \ct' -> sendComposedMessage cc ct' Nothing mc - sendReply $ "Forwarded to " <> tshow (length cts') <> " contact(s)" + sendChatCmd cc (SendMessageBroadcast mc) >>= \case + CRBroadcastSent {successes, failures} -> + sendReply $ "Forwarded to " <> tshow successes <> " contact(s), " <> tshow failures <> " errors" + r -> putStrLn $ "Error broadcasting message: " <> show r r -> putStrLn $ "Error getting contacts list: " <> show r else sendReply "!1 Message is not supported!" | otherwise -> do diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index bd4d418158..043db9bfc9 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -472,7 +472,7 @@ data ChatCommand | SendMemberContactMessage GroupName ContactName Text | SendLiveMessage ChatName Text | SendMessageQuote {contactName :: ContactName, msgDir :: AMsgDirection, quotedMsg :: Text, message :: Text} - | SendMessageBroadcast Text -- UserId (not used in UI) + | SendMessageBroadcast MsgContent -- UserId (not used in UI) | DeleteMessage ChatName Text | DeleteMemberMessage GroupName ContactName Text | EditMessage {chatName :: ChatName, editedMsg :: Text, message :: Text} diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index d10959178c..11ea3a1f93 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -1888,7 +1888,7 @@ processChatCommand' vr = \case withSendRef chatRef $ \sendRef -> do let mc = MCText msg processChatCommand $ APISendMessages sendRef True Nothing [ComposedMessage Nothing Nothing mc mentions] - SendMessageBroadcast msg -> withUser $ \user -> do + SendMessageBroadcast mc -> withUser $ \user -> do contacts <- withFastStore' $ \db -> getUserContacts db vr user withChatLock "sendMessageBroadcast" . procCmd $ do let ctConns_ = L.nonEmpty $ foldr addContactConn [] contacts @@ -1912,7 +1912,6 @@ processChatCommand' vr = \case lift . void $ withStoreBatch' $ \db -> map (createCI db user timestamp) ctSndMsgs pure CRBroadcastSent {user, msgContent = mc, successes = length ctSndMsgs, failures = length errs, timestamp} where - mc = MCText msg addContactConn :: Contact -> [(Contact, Connection)] -> [(Contact, Connection)] addContactConn ct ctConns = case contactSendConn_ ct of Right conn | directOrUsed ct -> (ct, conn) : ctConns @@ -4150,7 +4149,7 @@ chatCommandP = ("\\\\ #" <|> "\\\\#") *> (DeleteMemberMessage <$> displayNameP <* A.space <* char_ '@' <*> displayNameP <* A.space <*> textP), ("! " <|> "!") *> (EditMessage <$> chatNameP <* A.space <*> (quotedMsg <|> pure "") <*> msgTextP), ReactToMessage <$> (("+" $> True) <|> ("-" $> False)) <*> reactionP <* A.space <*> chatNameP' <* A.space <*> textP, - "/feed " *> (SendMessageBroadcast <$> msgTextP), + "/feed " *> (SendMessageBroadcast . MCText <$> msgTextP), ("/chats" <|> "/cs") *> (LastChats <$> (" all" $> Nothing <|> Just <$> (A.space *> A.decimal <|> pure 20))), ("/tail" <|> "/t") *> (LastMessages <$> optional (A.space *> chatNameP) <*> msgCountP <*> pure Nothing), ("/search" <|> "/?") *> (LastMessages <$> optional (A.space *> chatNameP) <*> msgCountP <*> (Just <$> (A.space *> stringP))), From ca49167ec64988aad30f0b9922e9a7bf598f05ad Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 27 Apr 2025 15:55:49 +0100 Subject: [PATCH 525/567] directory service: fix deleting group registration (#5856) --- apps/simplex-directory-service/src/Directory/Service.hs | 5 ----- apps/simplex-directory-service/src/Directory/Store.hs | 2 +- tests/Bots/DirectoryTests.hs | 6 ++++-- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs index c5c41e39be..fc9ac24e71 100644 --- a/apps/simplex-directory-service/src/Directory/Service.hs +++ b/apps/simplex-directory-service/src/Directory/Service.hs @@ -699,11 +699,6 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName DCListUserGroups -> getUserGroupRegs st (contactId' ct) >>= \grs -> do sendReply $ tshow (length grs) <> " registered group(s)" - -- debug how it can be that user has 0 registered groups - when (length grs == 0) $ do - total <- length <$> readTVarIO (groupRegs st) - withSuperUsers $ \ctId -> sendMessage' cc ctId $ - "0 registered groups for " <> localDisplayName' ct <> " (" <> tshow (contactId' ct) <> ") out of " <> tshow total <> " registrations" void . forkIO $ forM_ (reverse grs) $ \gr@GroupReg {userGroupRegId} -> sendGroupInfo ct gr userGroupRegId Nothing DCDeleteGroup gId gName -> diff --git a/apps/simplex-directory-service/src/Directory/Store.hs b/apps/simplex-directory-service/src/Directory/Store.hs index 031d05fd49..628419ea1d 100644 --- a/apps/simplex-directory-service/src/Directory/Store.hs +++ b/apps/simplex-directory-service/src/Directory/Store.hs @@ -212,7 +212,7 @@ delGroupReg st GroupReg {dbGroupId = gId, groupRegStatus} = do logGDelete st gId atomically $ writeTVar groupRegStatus GRSRemoved atomically $ unlistGroup st gId - atomically $ modifyTVar' (groupRegs st) $ filter ((gId ==) . dbGroupId) + atomically $ modifyTVar' (groupRegs st) $ filter ((gId /=) . dbGroupId) setGroupStatus :: DirectoryStore -> GroupReg -> GroupRegStatus -> IO () setGroupStatus st gr grStatus = do diff --git a/tests/Bots/DirectoryTests.hs b/tests/Bots/DirectoryTests.hs index 92d70727e3..0877a48daa 100644 --- a/tests/Bots/DirectoryTests.hs +++ b/tests/Bots/DirectoryTests.hs @@ -299,10 +299,12 @@ testDeleteGroupAdmin ps = superUser #> "@SimpleX-Directory /delete 2:security" superUser <# "SimpleX-Directory> > /delete 2:security" superUser <## " The group security is deleted from the directory" - groupFound bob "privacy" + groupFound cath "privacy" + listUserGroup bob "privacy" "Privacy" groupNotFound bob "security" + sendListCommand cath 0 -- another user can register the group with the same name - registerGroupId superUser bob "security" "Security" 4 1 + registerGroupId superUser bob "security" "Security" 4 2 testSetRole :: HasCallStack => TestParams -> IO () testSetRole ps = From 38b8e0cee69ec1c3b02fb9887feebe3611237687 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 29 Apr 2025 16:27:19 +0000 Subject: [PATCH 526/567] ios: refactor chat state (remove chatItemsChangesListener) (#5858) --- apps/ios/Shared/Model/ChatModel.swift | 14 +- apps/ios/Shared/Model/SimpleXAPI.swift | 2 +- .../Shared/Views/Chat/ChatItemsMerger.swift | 193 +++++++++--------- apps/ios/Shared/Views/Chat/ChatView.swift | 2 +- .../Views/LocalAuth/LocalAuthView.swift | 2 +- 5 files changed, 104 insertions(+), 109 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 00260cc05e..c73cb32c58 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -54,9 +54,7 @@ class ItemsModel: ObservableObject { willSet { publisher.send() } } - // set listener here that will be notified on every add/delete of a chat item let chatState = ActiveChatState() - var chatItemsChangesListener: RecalculatePositions = RecalculatePositions() // Publishes directly to `objectWillChange` publisher, // this will cause reversedChatItems to be rendered without throttling @@ -573,7 +571,7 @@ final class ChatModel: ObservableObject { ci.meta.itemStatus = status } im.reversedChatItems.insert(ci, at: hasLiveDummy ? 1 : 0) - im.chatItemsChangesListener.added((ci.id, ci.isRcvNew), hasLiveDummy ? 1 : 0) + im.chatState.itemAdded((ci.id, ci.isRcvNew), hasLiveDummy ? 1 : 0) im.itemAdded = true ChatItemDummyModel.shared.sendUpdate() return true @@ -621,7 +619,7 @@ final class ChatModel: ObservableObject { if let i = getChatItemIndex(cItem) { withAnimation { let item = im.reversedChatItems.remove(at: i) - im.chatItemsChangesListener.removed([(item.id, i, item.isRcvNew)], im.reversedChatItems.reversed()) + im.chatState.itemsRemoved([(item.id, i, item.isRcvNew)], im.reversedChatItems.reversed()) } } } @@ -709,7 +707,7 @@ final class ChatModel: ObservableObject { let cItem = ChatItem.liveDummy(chatInfo.chatType) withAnimation { im.reversedChatItems.insert(cItem, at: 0) - im.chatItemsChangesListener.added((cItem.id, cItem.isRcvNew), 0) + im.chatState.itemAdded((cItem.id, cItem.isRcvNew), 0) im.itemAdded = true } return cItem @@ -743,7 +741,7 @@ final class ChatModel: ObservableObject { markChatItemRead_(i) i += 1 } - im.chatItemsChangesListener.read(nil, im.reversedChatItems.reversed()) + im.chatState.itemsRead(nil, im.reversedChatItems.reversed()) } } func markChatUnread(_ cInfo: ChatInfo, unreadChat: Bool = true) { @@ -767,7 +765,7 @@ final class ChatModel: ObservableObject { if chatId == cInfo.id { chatItemStatuses = [:] im.reversedChatItems = [] - im.chatItemsChangesListener.cleared() + im.chatState.clear() } } @@ -785,7 +783,7 @@ final class ChatModel: ObservableObject { } i += 1 } - im.chatItemsChangesListener.read(unreadItemIds, im.reversedChatItems.reversed()) + im.chatState.itemsRead(unreadItemIds, im.reversedChatItems.reversed()) } self.unreadCollector.changeUnreadCounter(cInfo.id, by: -itemIds.count, unreadMentions: -mentionsRead) } diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 2de818abb2..22d004f4d9 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -345,7 +345,7 @@ func loadChat(chatId: ChatId, search: String = "", openAroundItemId: ChatItem.ID m.chatItemStatuses = [:] if clearItems { im.reversedChatItems = [] - ItemsModel.shared.chatItemsChangesListener.cleared() + ItemsModel.shared.chatState.clear() } } await apiLoadMessages(chatId, openAroundItemId != nil ? .around(chatItemId: openAroundItemId!, count: loadItemsPerPage) : (search == "" ? .initial(count: loadItemsPerPage) : .last(count: loadItemsPerPage)), im.chatState, search, openAroundItemId, { 0...0 }) diff --git a/apps/ios/Shared/Views/Chat/ChatItemsMerger.swift b/apps/ios/Shared/Views/Chat/ChatItemsMerger.swift index 2e9dac166a..0a55ed48cc 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemsMerger.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemsMerger.swift @@ -321,6 +321,101 @@ class ActiveChatState { unreadAfter = 0 unreadAfterNewestLoaded = 0 } + + func itemsRead(_ itemIds: Set?, _ newItems: [ChatItem]) { + guard let itemIds else { + // special case when the whole chat became read + unreadTotal = 0 + unreadAfter = 0 + return + } + var unreadAfterItemIndex: Int = -1 + // since it's more often that the newest items become read, it's logical to loop from the end of the list to finish it faster + var i = newItems.count - 1 + var ids = itemIds + // intermediate variables to prevent re-setting state value a lot of times without reason + var newUnreadTotal = unreadTotal + var newUnreadAfter = unreadAfter + while i >= 0 { + let item = newItems[i] + if item.id == unreadAfterItemId { + unreadAfterItemIndex = i + } + if ids.contains(item.id) { + // was unread, now this item is read + if (unreadAfterItemIndex == -1) { + newUnreadAfter -= 1 + } + newUnreadTotal -= 1 + ids.remove(item.id) + if ids.isEmpty { + break + } + } + i -= 1 + } + unreadTotal = newUnreadTotal + unreadAfter = newUnreadAfter + } + + func itemAdded(_ item: (Int64, Bool), _ index: Int) { + if item.1 { + unreadAfter += 1 + unreadTotal += 1 + } + } + + func itemsRemoved(_ itemIds: [(Int64, Int, Bool)], _ newItems: [ChatItem]) { + var newSplits: [Int64] = [] + for split in splits { + let index = itemIds.firstIndex(where: { (delId, _, _) in delId == split }) + // deleted the item that was right before the split between items, find newer item so it will act like the split + if let index { + let idx = itemIds[index].1 - itemIds.filter { (_, delIndex, _) in delIndex <= index }.count + let newSplit = newItems.count > idx && idx >= 0 ? newItems[idx].id : nil + // it the whole section is gone and splits overlap, don't add it at all + if let newSplit, !newSplits.contains(newSplit) { + newSplits.append(newSplit) + } + } else { + newSplits.append(split) + } + } + splits = newSplits + + let index = itemIds.firstIndex(where: { (delId, _, _) in delId == unreadAfterItemId }) + // unread after item was removed + if let index { + let idx = itemIds[index].1 - itemIds.filter { (_, delIndex, _) in delIndex <= index }.count + var newUnreadAfterItemId = newItems.count > idx && idx >= 0 ? newItems[idx].id : nil + let newUnreadAfterItemWasNull = newUnreadAfterItemId == nil + if newUnreadAfterItemId == nil { + // everything on top (including unread after item) were deleted, take top item as unread after id + newUnreadAfterItemId = newItems.first?.id + } + if let newUnreadAfterItemId { + unreadAfterItemId = newUnreadAfterItemId + totalAfter -= itemIds.filter { (_, delIndex, _) in delIndex > index }.count + unreadTotal -= itemIds.filter { (_, delIndex, isRcvNew) in delIndex <= index && isRcvNew }.count + unreadAfter -= itemIds.filter { (_, delIndex, isRcvNew) in delIndex > index && isRcvNew }.count + if newUnreadAfterItemWasNull { + // since the unread after item was moved one item after initial position, adjust counters accordingly + if newItems.first?.isRcvNew == true { + unreadTotal += 1 + unreadAfter -= 1 + } + } + } else { + // all items were deleted, 0 items in chatItems + unreadAfterItemId = -1 + totalAfter = 0 + unreadTotal = 0 + unreadAfter = 0 + } + } else { + totalAfter -= itemIds.count + } + } } class BoxedValue: Equatable, Hashable { @@ -359,101 +454,3 @@ func visibleItemIndexesNonReversed(_ listState: EndlessScrollView.Li // visible items mapped to their underlying data structure which is ItemsModel.shared.reversedChatItems.reversed() return range } - -class RecalculatePositions { - private var chatState: ActiveChatState { get { ItemsModel.shared.chatState } } - - func read(_ itemIds: Set?, _ newItems: [ChatItem]) { - guard let itemIds else { - // special case when the whole chat became read - chatState.unreadTotal = 0 - chatState.unreadAfter = 0 - return - } - var unreadAfterItemIndex: Int = -1 - // since it's more often that the newest items become read, it's logical to loop from the end of the list to finish it faster - var i = newItems.count - 1 - var ids = itemIds - // intermediate variables to prevent re-setting state value a lot of times without reason - var newUnreadTotal = chatState.unreadTotal - var newUnreadAfter = chatState.unreadAfter - while i >= 0 { - let item = newItems[i] - if item.id == chatState.unreadAfterItemId { - unreadAfterItemIndex = i - } - if ids.contains(item.id) { - // was unread, now this item is read - if (unreadAfterItemIndex == -1) { - newUnreadAfter -= 1 - } - newUnreadTotal -= 1 - ids.remove(item.id) - if ids.isEmpty { - break - } - } - i -= 1 - } - chatState.unreadTotal = newUnreadTotal - chatState.unreadAfter = newUnreadAfter - } - func added(_ item: (Int64, Bool), _ index: Int) { - if item.1 { - chatState.unreadAfter += 1 - chatState.unreadTotal += 1 - } - } - func removed(_ itemIds: [(Int64, Int, Bool)], _ newItems: [ChatItem]) { - var newSplits: [Int64] = [] - for split in chatState.splits { - let index = itemIds.firstIndex(where: { (delId, _, _) in delId == split }) - // deleted the item that was right before the split between items, find newer item so it will act like the split - if let index { - let idx = itemIds[index].1 - itemIds.filter { (_, delIndex, _) in delIndex <= index }.count - let newSplit = newItems.count > idx && idx >= 0 ? newItems[idx].id : nil - // it the whole section is gone and splits overlap, don't add it at all - if let newSplit, !newSplits.contains(newSplit) { - newSplits.append(newSplit) - } - } else { - newSplits.append(split) - } - } - chatState.splits = newSplits - - let index = itemIds.firstIndex(where: { (delId, _, _) in delId == chatState.unreadAfterItemId }) - // unread after item was removed - if let index { - let idx = itemIds[index].1 - itemIds.filter { (_, delIndex, _) in delIndex <= index }.count - var newUnreadAfterItemId = newItems.count > idx && idx >= 0 ? newItems[idx].id : nil - let newUnreadAfterItemWasNull = newUnreadAfterItemId == nil - if newUnreadAfterItemId == nil { - // everything on top (including unread after item) were deleted, take top item as unread after id - newUnreadAfterItemId = newItems.first?.id - } - if let newUnreadAfterItemId { - chatState.unreadAfterItemId = newUnreadAfterItemId - chatState.totalAfter -= itemIds.filter { (_, delIndex, _) in delIndex > index }.count - chatState.unreadTotal -= itemIds.filter { (_, delIndex, isRcvNew) in delIndex <= index && isRcvNew }.count - chatState.unreadAfter -= itemIds.filter { (_, delIndex, isRcvNew) in delIndex > index && isRcvNew }.count - if newUnreadAfterItemWasNull { - // since the unread after item was moved one item after initial position, adjust counters accordingly - if newItems.first?.isRcvNew == true { - chatState.unreadTotal += 1 - chatState.unreadAfter -= 1 - } - } - } else { - // all items were deleted, 0 items in chatItems - chatState.unreadAfterItemId = -1 - chatState.totalAfter = 0 - chatState.unreadTotal = 0 - chatState.unreadAfter = 0 - } - } else { - chatState.totalAfter -= itemIds.count - } - } - func cleared() { chatState.clear() } -} diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index eae28b76be..1349996683 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -281,7 +281,7 @@ struct ChatView: View { if chatModel.chatId == nil { chatModel.chatItemStatuses = [:] ItemsModel.shared.reversedChatItems = [] - ItemsModel.shared.chatItemsChangesListener.cleared() + ItemsModel.shared.chatState.clear() chatModel.groupMembers = [] chatModel.groupMembersIndexes.removeAll() chatModel.membersLoaded = false diff --git a/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift b/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift index 7c16e22571..16ab26eff7 100644 --- a/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift +++ b/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift @@ -65,7 +65,7 @@ struct LocalAuthView: View { // Clear sensitive data on screen just in case app fails to hide its views while new database is created m.chatId = nil ItemsModel.shared.reversedChatItems = [] - ItemsModel.shared.chatItemsChangesListener.cleared() + ItemsModel.shared.chatState.clear() m.updateChats([]) m.users = [] _ = kcAppPassword.set(password) From e7a4611be9cc04988e9779e631c49d500dcb15b3 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 2 May 2025 12:23:05 +0100 Subject: [PATCH 527/567] ios: deliver notifications even if iOS fails to fire expiration notice, prevent repeat delivery of stale notifications (#5861) * ios: deliver notification when iOS fails to fire expiration notice for NSE * update core api * update ui * sha256map.nix * do not enable background processes in maintenance mode * fix ios * fix parser * ios: fix command * compatible parser for connection ID * log * pass DB queue ID * simplexmq * query plans * fix broadcast bot test --- .../ios/SimpleX NSE/NotificationService.swift | 201 ++++++++++-------- apps/ios/SimpleX SE/ShareModel.swift | 2 +- apps/ios/SimpleXChat/APITypes.swift | 4 +- apps/ios/SimpleXChat/ChatTypes.swift | 40 +++- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat/Controller.hs | 12 +- src/Simplex/Chat/Core.hs | 5 +- src/Simplex/Chat/Library/Commands.hs | 35 ++- .../SQLite/Migrations/agent_query_plans.txt | 4 +- src/Simplex/Chat/View.hs | 4 +- tests/Bots/BroadcastTests.hs | 3 +- 12 files changed, 179 insertions(+), 135 deletions(-) diff --git a/apps/ios/SimpleX NSE/NotificationService.swift b/apps/ios/SimpleX NSE/NotificationService.swift index ba94463d61..c82869a36a 100644 --- a/apps/ios/SimpleX NSE/NotificationService.swift +++ b/apps/ios/SimpleX NSE/NotificationService.swift @@ -143,8 +143,7 @@ class NSEThreads { } struct ExpectedMessage { - var ntfConn: UserNtfConn - var receiveConnId: String? + var ntfConn: NtfConn var expectedMsgId: String? var allowedGetNextAttempts: Int var msgBestAttemptNtf: NSENotificationData? @@ -152,6 +151,14 @@ struct ExpectedMessage { var shouldProcessNtf: Bool var startedProcessingNewMsgs: Bool var semaphore: DispatchSemaphore + + var connMsgReq: ConnMsgReq? { + if let expectedMsg_ = ntfConn.expectedMsg_ { + ConnMsgReq(msgConnId: ntfConn.agentConnId, msgDbQueueId: ntfConn.agentDbQueueId, msgTs: expectedMsg_.msgTs) + } else { + nil + } + } } // Notification service extension creates a new instance of the class and calls didReceive for each notification. @@ -172,20 +179,27 @@ class NotificationService: UNNotificationServiceExtension { override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { logger.debug("DEBUGGING: NotificationService.didReceive") - let ntf = if let ntf_ = request.content.mutableCopy() as? UNMutableNotificationContent { ntf_ } else { UNMutableNotificationContent() } - setServiceBestAttemptNtf(ntf) + let receivedNtf = if let ntf_ = request.content.mutableCopy() as? UNMutableNotificationContent { ntf_ } else { UNMutableNotificationContent() } + setServiceBestAttemptNtf(receivedNtf) self.contentHandler = contentHandler registerGroupDefaults() let appState = appStateGroupDefault.get() logger.debug("NotificationService: app is \(appState.rawValue)") switch appState { case .stopped: +// Use this block to debug notificaitons delivery in CLI, with "ejected" database and stopped chat +// if let nrData = ntfRequestData(request) { +// logger.debug("NotificationService get notification connections: /_ntf conns \(nrData.nonce) \(nrData.encNtfInfo)") +// contentHandler(receivedNtf) +// return; +// } setBadgeCount() - setServiceBestAttemptNtf(createAppStoppedNtf(badgeCount)) - deliverBestAttemptNtf() + contentHandler(createAppStoppedNtf(badgeCount)) case .suspended: - receiveNtfMessages(request, contentHandler) + setExpirationTimer() + receiveNtfMessages(request) case .suspending: + setExpirationTimer() Task { let state: AppState = await withCheckedContinuation { cont in appSubscriber = appStateSubscriber { s in @@ -206,42 +220,55 @@ class NotificationService: UNNotificationServiceExtension { } } logger.debug("NotificationService: app state is now \(state.rawValue)") - if state.inactive { - receiveNtfMessages(request, contentHandler) + if state.inactive && self.contentHandler != nil { + receiveNtfMessages(request) } else { - deliverBestAttemptNtf() + contentHandler(receivedNtf) } } - case .active: contentHandler(UNMutableNotificationContent()) - case .activating: contentHandler(UNMutableNotificationContent()) - case .bgRefresh: contentHandler(UNMutableNotificationContent()) + case .active: contentHandler(receivedNtf) + case .activating: contentHandler(receivedNtf) + case .bgRefresh: contentHandler(receivedNtf) } } - func receiveNtfMessages(_ request: UNNotificationRequest, _ contentHandler: @escaping (UNNotificationContent) -> Void) { + private func setExpirationTimer() -> Void { + DispatchQueue.main.asyncAfter(deadline: .now() + 30) { + self.deliverBestAttemptNtf(urgent: true) + } + } + + private func ntfRequestData(_ request: UNNotificationRequest) -> (nonce: String, encNtfInfo: String)? { + if let ntfData = request.content.userInfo["notificationData"] as? [AnyHashable : Any], + let nonce = ntfData["nonce"] as? String, + let encNtfInfo = ntfData["message"] as? String { + (nonce, encNtfInfo) + } else { + nil + } + } + + func receiveNtfMessages(_ request: UNNotificationRequest) { logger.debug("NotificationService: receiveNtfMessages") if case .documents = dbContainerGroupDefault.get() { deliverBestAttemptNtf() return } - let userInfo = request.content.userInfo - if let ntfData = userInfo["notificationData"] as? [AnyHashable : Any], - let nonce = ntfData["nonce"] as? String, - let encNtfInfo = ntfData["message"] as? String, + if let nrData = ntfRequestData(request), // check it here again appStateGroupDefault.get().inactive { // thread is added to activeThreads tracking set here - if thread started chat it needs to be suspended if let t = threadId { NSEThreads.shared.startThread(t, self) } let dbStatus = startChat() if case .ok = dbStatus, - let ntfConns = apiGetNtfConns(nonce: nonce, encNtfInfo: encNtfInfo) { + let ntfConns = apiGetNtfConns(nonce: nrData.nonce, encNtfInfo: nrData.encNtfInfo) { logger.debug("NotificationService: receiveNtfMessages: apiGetNtfConns ntfConns count = \(ntfConns.count)") - +// logger.debug("NotificationService: receiveNtfMessages: apiGetNtfConns ntfConns \(String(describing: ntfConns.map { $0.connEntity.id }))") for ntfConn in ntfConns { addExpectedMessage(ntfConn: ntfConn) } - let connIdsToGet = expectedMessages.compactMap { (id, _) in + let connMsgReqs = expectedMessages.compactMap { (id, _) in let started = NSEThreads.queue.sync { let canStart = checkCanStart(id) if let t = threadId { logger.debug("NotificationService thread \(t, privacy: .private): receiveNtfMessages: can start: \(canStart)") } @@ -253,7 +280,7 @@ class NotificationService: UNNotificationServiceExtension { return canStart } if started { - return expectedMessages[id]?.receiveConnId + return expectedMessages[id]?.connMsgReq } else { if let t = threadId { logger.debug("NotificationService thread \(t, privacy: .private): receiveNtfMessages: entity \(id, privacy: .private) waiting on semaphore") } expectedMessages[id]?.semaphore.wait() @@ -264,17 +291,17 @@ class NotificationService: UNNotificationServiceExtension { expectedMessages[id]?.startedProcessingNewMsgs = true expectedMessages[id]?.shouldProcessNtf = true } - if let connId = expectedMessages[id]?.receiveConnId { - let _ = getConnNtfMessage(connId: connId) + if let connMsgReq = expectedMessages[id]?.connMsgReq { + let _ = getConnNtfMessage(connMsgReq: connMsgReq) } } return nil } } - if !connIdsToGet.isEmpty { - if let r = apiGetConnNtfMessages(connIds: connIdsToGet) { - logger.debug("NotificationService: receiveNtfMessages: apiGetConnNtfMessages count = \(r.count)") + if !connMsgReqs.isEmpty { + if let r = apiGetConnNtfMessages(connMsgReqs: connMsgReqs) { + logger.debug("NotificationService: receiveNtfMessages: apiGetConnNtfMessages count = \(r.count), expecting messages \(r.count { $0 != nil })") } return } @@ -285,18 +312,16 @@ class NotificationService: UNNotificationServiceExtension { deliverBestAttemptNtf() } - func addExpectedMessage(ntfConn: UserNtfConn) { - if let connEntity = ntfConn.connEntity_, - let receiveEntityId = connEntity.id, ntfConn.expectedMsg_ != nil { - let expectedMsgId = ntfConn.expectedMsg_?.msgId + func addExpectedMessage(ntfConn: NtfConn) { + let expectedMsgId = ntfConn.expectedMsg_?.msgId + if let receiveEntityId = ntfConn.connEntity.id { logger.debug("NotificationService: addExpectedMessage: expectedMsgId = \(expectedMsgId ?? "nil", privacy: .private)") expectedMessages[receiveEntityId] = ExpectedMessage( ntfConn: ntfConn, - receiveConnId: connEntity.conn.agentConnId, expectedMsgId: expectedMsgId, allowedGetNextAttempts: 3, - msgBestAttemptNtf: ntfConn.defaultBestAttemptNtf, - ready: false, + msgBestAttemptNtf: defaultBestAttemptNtf(ntfConn), + ready: ntfConn.expectedMsg_ == nil, // show defaultBestAttemptNtf(ntfConn) if there is no expected message shouldProcessNtf: false, startedProcessingNewMsgs: false, semaphore: DispatchSemaphore(value: 0) @@ -350,10 +375,10 @@ class NotificationService: UNNotificationServiceExtension { NSEThreads.shared.droppedNotifications.append((id, ntf)) if signalReady { entityReady(id) } self.deliverBestAttemptNtf() - } else if (expectedMessages[id]?.allowedGetNextAttempts ?? 0) > 0, let receiveConnId = expectedMessages[id]?.receiveConnId { + } else if (expectedMessages[id]?.allowedGetNextAttempts ?? 0) > 0, let connMsgReq = expectedMessages[id]?.connMsgReq { logger.debug("NotificationService processNtf: msgInfo msgId = \(info.msgId, privacy: .private): unexpected msgInfo, get next message") expectedMessages[id]?.allowedGetNextAttempts -= 1 - if let receivedMsg = getConnNtfMessage(connId: receiveConnId) { + if let receivedMsg = getConnNtfMessage(connMsgReq: connMsgReq) { logger.debug("NotificationService processNtf, on getConnNtfMessage: msgInfo msgId = \(info.msgId, privacy: .private), receivedMsg msgId = \(receivedMsg.msgId, privacy: .private)") } else { logger.debug("NotificationService processNtf, on getConnNtfMessage: msgInfo msgId = \(info.msgId, privacy: .private): no next message, deliver best attempt") @@ -373,13 +398,9 @@ class NotificationService: UNNotificationServiceExtension { setBadgeCount() } let prevBestAttempt = expectedMessages[id]?.msgBestAttemptNtf - if prevBestAttempt?.callInvitation != nil { - if ntf.callInvitation != nil { // replace with newer call - expectedMessages[id]?.msgBestAttemptNtf = ntf - } // otherwise keep call as best attempt - } else { + if prevBestAttempt?.callInvitation == nil || ntf.callInvitation != nil { expectedMessages[id]?.msgBestAttemptNtf = ntf - } + } // otherwise keep call as best attempt } else { NSEThreads.shared.droppedNotifications.append((id, ntf)) if signalReady { entityReady(id) } @@ -406,7 +427,11 @@ class NotificationService: UNNotificationServiceExtension { } private func deliverBestAttemptNtf(urgent: Bool = false) { - if (urgent || !expectingMoreMessages) { + logger.debug("NotificationService.deliverBestAttemptNtf urgent: \(urgent) expectingMoreMessages: \(self.expectingMoreMessages)") + if let handler = contentHandler, urgent || !expectingMoreMessages { + if urgent { + contentHandler = nil + } logger.debug("NotificationService.deliverBestAttemptNtf") // stop processing other messages for (key, _) in expectedMessages { @@ -420,18 +445,18 @@ class NotificationService: UNNotificationServiceExtension { } else { suspend = false } - deliverCallkitOrNotification(urgent: urgent, suspend: suspend) + deliverCallkitOrNotification(urgent: urgent, suspend: suspend, handler: handler) } } - private func deliverCallkitOrNotification(urgent: Bool, suspend: Bool = false) { + private func deliverCallkitOrNotification(urgent: Bool, suspend: Bool = false, handler: @escaping (UNNotificationContent) -> Void) { if useCallKit() && expectedMessages.contains(where: { $0.value.msgBestAttemptNtf?.callInvitation != nil }) { logger.debug("NotificationService.deliverCallkitOrNotification: will suspend, callkit") if urgent { // suspending NSE even though there may be other notifications // to allow the app to process callkit call suspendChat(0) - deliverNotification() + deliverNotification(handler: handler) } else { // suspending NSE with delay and delivering after the suspension // because pushkit notification must be processed without delay @@ -439,7 +464,7 @@ class NotificationService: UNNotificationServiceExtension { DispatchQueue.global().asyncAfter(deadline: .now() + fastNSESuspendSchedule.delay) { suspendChat(fastNSESuspendSchedule.timeout) DispatchQueue.global().asyncAfter(deadline: .now() + Double(fastNSESuspendSchedule.timeout)) { - self.deliverNotification() + self.deliverNotification(handler: handler) } } } @@ -458,12 +483,12 @@ class NotificationService: UNNotificationServiceExtension { } } } - deliverNotification() + deliverNotification(handler: handler) } } - private func deliverNotification() { - if let handler = contentHandler, let ntf = prepareNotification() { + private func deliverNotification(handler: @escaping (UNNotificationContent) -> Void) { + if serviceBestAttemptNtf != nil, let ntf = prepareNotification() { contentHandler = nil serviceBestAttemptNtf = nil switch ntf { @@ -496,7 +521,9 @@ class NotificationService: UNNotificationServiceExtension { let callNtf = callNtfKV.value.msgBestAttemptNtf { return useCallKit() ? .callkit(callInv) : .nse(callNtf.notificationContent(badgeCount)) } else { + logger.debug("NotificationService prepareNotification \(String(describing: self.expectedMessages.map { $0.key }))") let ntfEvents = expectedMessages.compactMap { $0.value.msgBestAttemptNtf?.notificationEvent } + logger.debug("NotificationService prepareNotification \(ntfEvents.count)") if ntfEvents.isEmpty { return .empty } else if let ntfEvent = ntfEvents.count == 1 ? ntfEvents.first : nil { @@ -654,7 +681,7 @@ func doStartChat() -> DBMigrationResult? { let state = NSEChatState.shared.value NSEChatState.shared.set(.starting) if let user = apiGetActiveUser() { - logger.debug("NotificationService active user \(String(describing: user))") + logger.debug("NotificationService active user \(user.displayName)") do { try setNetworkConfig(networkConfig) try apiSetAppFilePaths(filesFolder: getAppFilesDirectory().path, tempFolder: getTempFilesDirectory().path, assetsFolder: getWallpaperDirectory().deletingLastPathComponent().path) @@ -893,7 +920,7 @@ func apiSetEncryptLocalFiles(_ enable: Bool) throws { throw r } -func apiGetNtfConns(nonce: String, encNtfInfo: String) -> [UserNtfConn]? { +func apiGetNtfConns(nonce: String, encNtfInfo: String) -> [NtfConn]? { guard apiGetActiveUser() != nil else { logger.debug("no active user") return nil @@ -901,7 +928,7 @@ func apiGetNtfConns(nonce: String, encNtfInfo: String) -> [UserNtfConn]? { let r = sendSimpleXCmd(.apiGetNtfConns(nonce: nonce, encNtfInfo: encNtfInfo)) if case let .ntfConns(ntfConns) = r { logger.debug("apiGetNtfConns response ntfConns: \(ntfConns.count)") - return ntfConns.compactMap { toUserNtfConn($0) } + return ntfConns } else if case let .chatCmdError(_, error) = r { logger.debug("apiGetNtfMessage error response: \(String.init(describing: error))") } else { @@ -910,30 +937,23 @@ func apiGetNtfConns(nonce: String, encNtfInfo: String) -> [UserNtfConn]? { return nil } -func toUserNtfConn(_ ntfConn: NtfConn) -> UserNtfConn? { - if let user = ntfConn.user_ { - return UserNtfConn(user: user, connEntity_: ntfConn.connEntity_, expectedMsg_: ntfConn.expectedMsg_) - } else { - return nil - } -} - -func apiGetConnNtfMessages(connIds: [String]) -> [NtfMsgInfo?]? { +func apiGetConnNtfMessages(connMsgReqs: [ConnMsgReq]) -> [NtfMsgInfo?]? { guard apiGetActiveUser() != nil else { logger.debug("no active user") return nil } - let r = sendSimpleXCmd(.apiGetConnNtfMessages(connIds: connIds)) + logger.debug("apiGetConnNtfMessages command: \(ChatCommand.apiGetConnNtfMessages(connMsgReqs: connMsgReqs).cmdString)") + let r = sendSimpleXCmd(.apiGetConnNtfMessages(connMsgReqs: connMsgReqs)) if case let .connNtfMessages(receivedMsgs) = r { - logger.debug("apiGetConnNtfMessages response receivedMsgs: \(receivedMsgs.count)") + logger.debug("apiGetConnNtfMessages response receivedMsgs: total \(receivedMsgs.count), expecting messages \(receivedMsgs.count { $0 != nil })") return receivedMsgs } logger.debug("apiGetConnNtfMessages error: \(responseError(r))") return nil } -func getConnNtfMessage(connId: String) -> NtfMsgInfo? { - let r_ = apiGetConnNtfMessages(connIds: [connId]) +func getConnNtfMessage(connMsgReq: ConnMsgReq) -> NtfMsgInfo? { + let r_ = apiGetConnNtfMessages(connMsgReqs: [connMsgReq]) if let r = r_, let receivedMsg = r.count == 1 ? r.first : nil { return receivedMsg } @@ -974,33 +994,28 @@ func setNetworkConfig(_ cfg: NetCfg) throws { throw r } -struct UserNtfConn { - var user: User - var connEntity_: ConnectionEntity? - var expectedMsg_: NtfMsgInfo? - - var defaultBestAttemptNtf: NSENotificationData { - return if !user.showNotifications { - .noNtf - } else if let connEntity = connEntity_ { - switch connEntity { - case let .rcvDirectMsgConnection(_, contact): - contact?.chatSettings.enableNtfs == .all - ? .connectionEvent(user, connEntity) - : .noNtf - case let .rcvGroupMsgConnection(_, groupInfo, _): - groupInfo.chatSettings.enableNtfs == .all - ? .connectionEvent(user, connEntity) - : .noNtf - case .sndFileConnection: .noNtf - case .rcvFileConnection: .noNtf - case let .userContactConnection(_, userContact): - userContact.groupId == nil - ? .connectionEvent(user, connEntity) - : .noNtf - } - } else { - .noNtf +func defaultBestAttemptNtf(_ ntfConn: NtfConn) -> NSENotificationData { + let user = ntfConn.user + let connEntity = ntfConn.connEntity + return if !user.showNotifications { + .noNtf + } else { + switch ntfConn.connEntity { + case let .rcvDirectMsgConnection(_, contact): + contact?.chatSettings.enableNtfs == .all + ? .connectionEvent(user, connEntity) + : .noNtf + case let .rcvGroupMsgConnection(_, groupInfo, _): + groupInfo.chatSettings.enableNtfs == .all + ? .connectionEvent(user, connEntity) + : .noNtf + case .sndFileConnection: .noNtf + case .rcvFileConnection: .noNtf + case let .userContactConnection(_, userContact): + userContact.groupId == nil + ? .connectionEvent(user, connEntity) + : .noNtf } } } + diff --git a/apps/ios/SimpleX SE/ShareModel.swift b/apps/ios/SimpleX SE/ShareModel.swift index 21908026b2..88e174e8bc 100644 --- a/apps/ios/SimpleX SE/ShareModel.swift +++ b/apps/ios/SimpleX SE/ShareModel.swift @@ -179,7 +179,7 @@ class ShareModel: ObservableObject { resetChatCtrl() // Clears retained migration result registerGroupDefaults() haskell_init_se() - let (_, result) = chatMigrateInit(dbKey, confirmMigrations: defaultMigrationConfirmation()) + let (_, result) = chatMigrateInit(dbKey, confirmMigrations: defaultMigrationConfirmation(), backgroundMode: false) if let e = migrationError(result) { return e } try apiSetAppFilePaths( filesFolder: getAppFilesDirectory().path, diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 05fc4ff87b..18d0cbdb3c 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -67,7 +67,7 @@ public enum ChatCommand { case apiCheckToken(token: DeviceToken) case apiDeleteToken(token: DeviceToken) case apiGetNtfConns(nonce: String, encNtfInfo: String) - case apiGetConnNtfMessages(connIds: [String]) + case apiGetConnNtfMessages(connMsgReqs: [ConnMsgReq]) case apiNewGroup(userId: Int64, incognito: Bool, groupProfile: GroupProfile) case apiAddMember(groupId: Int64, contactId: Int64, memberRole: GroupMemberRole) case apiJoinGroup(groupId: Int64) @@ -246,7 +246,7 @@ public enum ChatCommand { case let .apiCheckToken(token): return "/_ntf check \(token.cmdString)" case let .apiDeleteToken(token): return "/_ntf delete \(token.cmdString)" case let .apiGetNtfConns(nonce, encNtfInfo): return "/_ntf conns \(nonce) \(encNtfInfo)" - case let .apiGetConnNtfMessages(connIds): return "/_ntf conn messages \(connIds.joined(separator: ","))" + case let .apiGetConnNtfMessages(connMsgReqs): return "/_ntf conn messages \(connMsgReqs.map { $0.cmdString }.joined(separator: ","))" 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)" diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 0c47442987..fb34ba390c 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2399,14 +2399,14 @@ public enum ConnectionEntity: Decodable, Hashable { public var id: String? { switch self { - case let .rcvDirectMsgConnection(_, contact): - return contact?.id + case let .rcvDirectMsgConnection(conn, contact): + contact?.id ?? conn.id case let .rcvGroupMsgConnection(_, _, groupMember): - return groupMember.id + groupMember.id case let .userContactConnection(_, userContact): - return userContact.id + userContact.id default: - return nil + nil } } @@ -2422,10 +2422,11 @@ public enum ConnectionEntity: Decodable, Hashable { } public struct NtfConn: Decodable, Hashable { - public var user_: User? - public var connEntity_: ConnectionEntity? + public var user: User + public var agentConnId: String + public var agentDbQueueId: Int64 + public var connEntity: ConnectionEntity public var expectedMsg_: NtfMsgInfo? - } public struct NtfMsgInfo: Decodable, Hashable { @@ -2433,6 +2434,29 @@ public struct NtfMsgInfo: Decodable, Hashable { public var msgTs: Date } +let iso8601DateFormatter = { + let f = ISO8601DateFormatter() + f.formatOptions = [.withInternetDateTime] + return f +}() + +// used in apiGetConnNtfMessages +public struct ConnMsgReq { + public var msgConnId: String + public var msgDbQueueId: Int64 + public var msgTs: Date // SystemTime encodes as a number, should be taken from NtfMsgInfo + + public init(msgConnId: String, msgDbQueueId: Int64, msgTs: Date) { + self.msgConnId = msgConnId + self.msgDbQueueId = msgDbQueueId + self.msgTs = msgTs + } + + public var cmdString: String { + "\(msgConnId):\(msgDbQueueId):\(iso8601DateFormatter.string(from: msgTs))" + } +} + public struct NtfMsgAckInfo: Decodable, Hashable { public var msgId: String public var msgTs_: Date? diff --git a/cabal.project b/cabal.project index 4a95ae3cfe..b7c8832d9d 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 08b84deba458407ae97d55debd98b872cb6c4d79 + tag: 3d10c9bf9e4d8196d39162ff8712f6b729b8c247 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index c945d72656..40aa4e7da0 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."08b84deba458407ae97d55debd98b872cb6c4d79" = "0b4n7d81spl1r7zppr0lc40ls9m1i93g4l3hzg2996pi3bxmafrr"; + "https://github.com/simplex-chat/simplexmq.git"."3d10c9bf9e4d8196d39162ff8712f6b729b8c247" = "1nnr6klv240da97qmrzlh8jywpimcnlrxnxnjrm2rd0w0w7gvra1"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 043db9bfc9..4d835b41bb 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -357,7 +357,7 @@ data ChatCommand | APICheckToken DeviceToken | APIDeleteToken DeviceToken | APIGetNtfConns {nonce :: C.CbNonce, encNtfInfo :: ByteString} - | ApiGetConnNtfMessages {connIds :: NonEmpty AgentConnId} + | APIGetConnNtfMessages (NonEmpty ConnMsgReq) | APIAddMember GroupId ContactId GroupMemberRole | APIJoinGroup {groupId :: GroupId, enableNtfs :: MsgFilter} | APIAcceptMember GroupId GroupMemberId GroupMemberRole @@ -1131,12 +1131,18 @@ instance FromJSON ChatTagData where parseJSON invalid = JT.prependFailure "bad ChatTagData, " (JT.typeMismatch "Object" invalid) data NtfConn = NtfConn - { user_ :: Maybe User, - connEntity_ :: Maybe ConnectionEntity, + { user :: User, + agentConnId :: AgentConnId, + agentDbQueueId :: Int64, + connEntity :: ConnectionEntity, + -- Decrypted ntf meta of the expected message (the one notification was sent for). + -- Nothing means it failed to decrypt or to decode, we can still show event for entity expectedMsg_ :: Maybe NtfMsgInfo } deriving (Show) +-- brokerTs is the same msgTs, it is used in ConnMsgReq / APIGetConnNtfMessages +-- to set it as last connection message in case queue is empty data NtfMsgInfo = NtfMsgInfo {msgId :: Text, msgTs :: UTCTime} deriving (Show) diff --git a/src/Simplex/Chat/Core.hs b/src/Simplex/Chat/Core.hs index 0dbee1542e..34fc0423fb 100644 --- a/src/Simplex/Chat/Core.hs +++ b/src/Simplex/Chat/Core.hs @@ -34,7 +34,7 @@ import Text.Read (readMaybe) import UnliftIO.Async simplexChatCore :: ChatConfig -> ChatOpts -> (User -> ChatController -> IO ()) -> IO () -simplexChatCore cfg@ChatConfig {confirmMigrations, testView} opts@ChatOpts {coreOptions = CoreChatOpts {dbOptions, logAgent, yesToUpMigrations}} chat = +simplexChatCore cfg@ChatConfig {confirmMigrations, testView} opts@ChatOpts {coreOptions = CoreChatOpts {dbOptions, logAgent, yesToUpMigrations}, maintenance} chat = case logAgent of Just level -> do setLogLevel level @@ -48,7 +48,8 @@ simplexChatCore cfg@ChatConfig {confirmMigrations, testView} opts@ChatOpts {core exitFailure run db@ChatDatabase {chatStore} = do u_ <- getSelectActiveUser chatStore - cc <- newChatController db u_ cfg opts False + let backgroundMode = not maintenance + cc <- newChatController db u_ cfg opts backgroundMode u <- maybe (createActiveUser cc) pure u_ unless testView $ putStrLn $ "Current user: " <> userStr u runSimplexChat opts u cc chat diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 11ea3a1f93..eeb54c6aef 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -1294,26 +1294,17 @@ processChatCommand' vr = \case ntfInfos <- withAgent $ \a -> getNotificationConns a nonce encNtfInfo (errs, ntfMsgs) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (getMsgConn db) (L.toList ntfInfos)) unless (null errs) $ toView $ CRChatErrors (Just user) errs - pure $ CRNtfConns ntfMsgs + pure $ CRNtfConns $ catMaybes ntfMsgs where - getMsgConn :: DB.Connection -> NotificationInfo -> IO NtfConn - getMsgConn db NotificationInfo {ntfConnId, ntfMsgMeta = nMsgMeta} = do + getMsgConn :: DB.Connection -> NotificationInfo -> IO (Maybe NtfConn) + getMsgConn db NotificationInfo {ntfConnId, ntfDbQueueId, ntfMsgMeta = nMsgMeta} = do let agentConnId = AgentConnId ntfConnId - user_ <- getUserByAConnId db agentConnId - connEntity_ <- - pure user_ $>>= \user -> - eitherToMaybe <$> runExceptT (getConnectionEntity db vr user agentConnId) - pure $ - NtfConn - { user_, - connEntity_, - -- Decrypted ntf meta of the expected message (the one notification was sent for) - expectedMsg_ = expectedMsgInfo <$> nMsgMeta - } - ApiGetConnNtfMessages connIds -> withUser $ \_ -> do - let acIds = L.map (\(AgentConnId acId) -> acId) connIds - msgs <- lift $ withAgent' $ \a -> getConnectionMessages a acIds - let ntfMsgs = L.map (\msg -> receivedMsgInfo <$> msg) msgs + mkNtfConn user connEntity = NtfConn {user, agentConnId, agentDbQueueId = ntfDbQueueId, connEntity, expectedMsg_ = expectedMsgInfo <$> nMsgMeta} + getUserByAConnId db agentConnId + $>>= \user -> fmap (mkNtfConn user) . eitherToMaybe <$> runExceptT (getConnectionEntity db vr user agentConnId) + APIGetConnNtfMessages connMsgs -> withUser $ \_ -> do + msgs <- lift $ withAgent' (`getConnectionMessages` connMsgs) + let ntfMsgs = L.map (receivedMsgInfo <$>) msgs pure $ CRConnNtfMessages ntfMsgs GetUserProtoServers (AProtocolType p) -> withUser $ \user -> withServerProtocol p $ do srvs <- withFastStore (`getUserServers` user) @@ -4005,7 +3996,7 @@ chatCommandP = "/_ntf check " *> (APICheckToken <$> strP), "/_ntf delete " *> (APIDeleteToken <$> strP), "/_ntf conns " *> (APIGetNtfConns <$> strP <* A.space <*> strP), - "/_ntf conn messages " *> (ApiGetConnNtfMessages <$> strP), + "/_ntf conn messages " *> (APIGetConnNtfMessages <$> connMsgsP), "/_add #" *> (APIAddMember <$> A.decimal <* A.space <*> A.decimal <*> memberRole), "/_join #" *> (APIJoinGroup <$> A.decimal <*> pure MFAll), -- needs to be changed to support in UI "/_accept member #" *> (APIAcceptMember <$> A.decimal <* A.space <*> A.decimal <*> memberRole), @@ -4320,6 +4311,12 @@ chatCommandP = cfArgs <- optional $ CFArgs <$> (" key=" *> strP <* A.space) <*> (" nonce=" *> strP) path <- filePath pure $ CryptoFile path cfArgs + connMsgsP = L.fromList <$> connMsgP `A.sepBy1'` A.char ',' + connMsgP = do + AgentConnId msgConnId <- strP <* A.char ':' + msgDbQueueId <- strP <* A.char ':' + ts <- strP + pure ConnMsgReq {msgConnId, msgDbQueueId, msgTs = Just ts} memberRole = A.choice [ " owner" $> GROwner, diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt index e6a567035e..a10d3f3db7 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt @@ -1119,9 +1119,9 @@ Query: UPDATE rcv_messages SET user_ack = ? WHERE conn_id = ? AND internal_id = Plan: SEARCH rcv_messages USING COVERING INDEX idx_rcv_messages_conn_id_internal_id (conn_id=? AND internal_id=?) -Query: UPDATE rcv_queues SET last_broker_ts = ? WHERE conn_id = ? AND rcv_queue_id = ? +Query: UPDATE rcv_queues SET last_broker_ts = ? WHERE conn_id = ? AND rcv_queue_id = ? AND last_broker_ts < ? Plan: -SEARCH rcv_queues USING COVERING INDEX idx_rcv_queue_id (conn_id=? AND rcv_queue_id=?) +SEARCH rcv_queues USING INDEX idx_rcv_queue_id (conn_id=? AND rcv_queue_id=?) Query: UPDATE rcv_queues SET rcv_primary = ? WHERE conn_id = ? Plan: diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index f145262862..6abbf6f03f 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -342,8 +342,8 @@ responseToView hu@(currentRH, user_) cfg@ChatConfig {logLevel, showReactions, sh CRContactConnectionDeleted u PendingContactConnection {pccConnId} -> ttyUser u ["connection :" <> sShow pccConnId <> " deleted"] CRNtfTokenStatus status -> ["device token status: " <> plain (smpEncode status)] CRNtfToken _ status mode srv -> ["device token status: " <> plain (smpEncode status) <> ", notifications mode: " <> plain (strEncode mode) <> ", server: " <> sShow srv] - CRNtfConns {} -> [] - CRConnNtfMessages {} -> [] + CRNtfConns {ntfConns} -> map (\NtfConn {agentConnId, expectedMsg_} -> plain $ show agentConnId <> " " <> show expectedMsg_) ntfConns + CRConnNtfMessages ntfMsgs -> [sShow ntfMsgs] CRNtfMessage {} -> [] CRCurrentRemoteHost rhi_ -> [ maybe diff --git a/tests/Bots/BroadcastTests.hs b/tests/Bots/BroadcastTests.hs index 751a7d1e05..71142c8b60 100644 --- a/tests/Bots/BroadcastTests.hs +++ b/tests/Bots/BroadcastTests.hs @@ -78,10 +78,11 @@ testBroadcastMessages ps = do bob <## "I broadcast messages to all connected users from @alice." cath `connectVia` botLink alice #> "@broadcast_bot hello all!" + alice <# "broadcast_bot> hello all!" -- we broadcast to the sender too, /feed is used by bot bob <# "broadcast_bot> hello all!" cath <# "broadcast_bot> hello all!" alice <# "broadcast_bot> > hello all!" - alice <## " Forwarded to 2 contact(s)" + alice <## " Forwarded to 3 contact(s), 0 errors" where cc `connectVia` botLink = do cc ##> ("/c " <> botLink) From f5c706f2dd206a5a15d7d4336d56afaa771ba27a Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 2 May 2025 12:27:08 +0100 Subject: [PATCH 528/567] ios: remove types used only in the app from the framework (#5866) * ios: remove types used only in the app from the framework * move more types * comment --- apps/ios/Shared/Model/AppAPITypes.swift | 2327 ++++++++++++++++ apps/ios/Shared/Model/SimpleXAPI.swift | 24 +- .../Views/Migration/MigrateFromDevice.swift | 2 +- apps/ios/SimpleX NSE/NSEAPITypes.swift | 173 ++ .../ios/SimpleX NSE/NotificationService.swift | 30 +- apps/ios/SimpleX SE/ShareAPI.swift | 184 +- apps/ios/SimpleX SE/ShareModel.swift | 3 +- apps/ios/SimpleX.xcodeproj/project.pbxproj | 10 +- apps/ios/SimpleXChat/API.swift | 112 +- apps/ios/SimpleXChat/APITypes.swift | 2340 +---------------- apps/ios/SimpleXChat/ChatTypes.swift | 2 +- apps/ios/SimpleXChat/ErrorAlert.swift | 35 +- 12 files changed, 2775 insertions(+), 2467 deletions(-) create mode 100644 apps/ios/Shared/Model/AppAPITypes.swift create mode 100644 apps/ios/SimpleX NSE/NSEAPITypes.swift diff --git a/apps/ios/Shared/Model/AppAPITypes.swift b/apps/ios/Shared/Model/AppAPITypes.swift new file mode 100644 index 0000000000..d7f96284cf --- /dev/null +++ b/apps/ios/Shared/Model/AppAPITypes.swift @@ -0,0 +1,2327 @@ +// +// APITypes.swift +// SimpleX +// +// Created by EP on 01/05/2025. +// Copyright © 2025 SimpleX Chat. All rights reserved. +// + +import SimpleXChat +import SwiftUI + +// some constructors are used in SEChatCommand or NSEChatCommand types as well - they must be syncronised +enum ChatCommand: ChatCmdProtocol { + case showActiveUser + case createActiveUser(profile: Profile?, pastTimestamp: Bool) + case listUsers + case apiSetActiveUser(userId: Int64, viewPwd: String?) + case setAllContactReceipts(enable: Bool) + case apiSetUserContactReceipts(userId: Int64, userMsgReceiptSettings: UserMsgReceiptSettings) + case apiSetUserGroupReceipts(userId: Int64, userMsgReceiptSettings: UserMsgReceiptSettings) + case apiHideUser(userId: Int64, viewPwd: String) + case apiUnhideUser(userId: Int64, viewPwd: String) + case apiMuteUser(userId: Int64) + case apiUnmuteUser(userId: Int64) + case apiDeleteUser(userId: Int64, delSMPQueues: Bool, viewPwd: String?) + case startChat(mainApp: Bool, enableSndFiles: Bool) + case checkChatRunning + case apiStopChat + case apiActivateChat(restoreChat: Bool) + case apiSuspendChat(timeoutMicroseconds: Int) + case apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String) + case apiSetEncryptLocalFiles(enable: Bool) + case apiExportArchive(config: ArchiveConfig) + case apiImportArchive(config: ArchiveConfig) + case apiDeleteStorage + case apiStorageEncryption(config: DBEncryptionConfig) + case testStorageEncryption(key: String) + case apiSaveSettings(settings: AppSettings) + case apiGetSettings(settings: AppSettings) + case apiGetChatTags(userId: Int64) + case apiGetChats(userId: Int64) + case apiGetChat(chatId: ChatId, pagination: ChatPagination, search: String) + case apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64) + case apiSendMessages(type: ChatType, id: Int64, live: Bool, ttl: Int?, composedMessages: [ComposedMessage]) + case apiCreateChatTag(tag: ChatTagData) + case apiSetChatTags(type: ChatType, id: Int64, tagIds: [Int64]) + case apiDeleteChatTag(tagId: Int64) + case apiUpdateChatTag(tagId: Int64, tagData: ChatTagData) + case apiReorderChatTags(tagIds: [Int64]) + case apiCreateChatItems(noteFolderId: Int64, composedMessages: [ComposedMessage]) + case apiReportMessage(groupId: Int64, chatItemId: Int64, reportReason: ReportReason, reportText: String) + case apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, updatedMessage: UpdatedMessage, live: Bool) + case apiDeleteChatItem(type: ChatType, id: Int64, itemIds: [Int64], mode: CIDeleteMode) + case apiDeleteMemberChatItem(groupId: Int64, itemIds: [Int64]) + case apiArchiveReceivedReports(groupId: Int64) + case apiDeleteReceivedReports(groupId: Int64, itemIds: [Int64], mode: CIDeleteMode) + case apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, reaction: MsgReaction) + case apiGetReactionMembers(userId: Int64, groupId: Int64, itemId: Int64, reaction: MsgReaction) + case apiPlanForwardChatItems(toChatType: ChatType, toChatId: Int64, itemIds: [Int64]) + case apiForwardChatItems(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemIds: [Int64], ttl: Int?) + case apiGetNtfToken + case apiRegisterToken(token: DeviceToken, notificationMode: NotificationsMode) + case apiVerifyToken(token: DeviceToken, nonce: String, code: String) + case apiCheckToken(token: DeviceToken) + case apiDeleteToken(token: DeviceToken) + case apiGetNtfConns(nonce: String, encNtfInfo: String) + case apiGetConnNtfMessages(connMsgReqs: [ConnMsgReq]) + case apiNewGroup(userId: Int64, incognito: Bool, groupProfile: GroupProfile) + case apiAddMember(groupId: Int64, contactId: Int64, memberRole: GroupMemberRole) + case apiJoinGroup(groupId: Int64) + case apiMembersRole(groupId: Int64, memberIds: [Int64], memberRole: GroupMemberRole) + case apiBlockMembersForAll(groupId: Int64, memberIds: [Int64], blocked: Bool) + case apiRemoveMembers(groupId: Int64, memberIds: [Int64], withMessages: Bool) + case apiLeaveGroup(groupId: Int64) + case apiListMembers(groupId: Int64) + case apiUpdateGroupProfile(groupId: Int64, groupProfile: GroupProfile) + case apiCreateGroupLink(groupId: Int64, memberRole: GroupMemberRole, short: Bool) + case apiGroupLinkMemberRole(groupId: Int64, memberRole: GroupMemberRole) + case apiDeleteGroupLink(groupId: Int64) + case apiGetGroupLink(groupId: Int64) + case apiCreateMemberContact(groupId: Int64, groupMemberId: Int64) + case apiSendMemberContactInvitation(contactId: Int64, msg: MsgContent) + case apiTestProtoServer(userId: Int64, server: String) + case apiGetServerOperators + case apiSetServerOperators(operators: [ServerOperator]) + case apiGetUserServers(userId: Int64) + case apiSetUserServers(userId: Int64, userServers: [UserOperatorServers]) + case apiValidateServers(userId: Int64, userServers: [UserOperatorServers]) + case apiGetUsageConditions + case apiSetConditionsNotified(conditionsId: Int64) + case apiAcceptConditions(conditionsId: Int64, operatorIds: [Int64]) + case apiSetChatItemTTL(userId: Int64, seconds: Int64) + case apiGetChatItemTTL(userId: Int64) + case apiSetChatTTL(userId: Int64, type: ChatType, id: Int64, seconds: Int64?) + case apiSetNetworkConfig(networkConfig: NetCfg) + case apiGetNetworkConfig + case apiSetNetworkInfo(networkInfo: UserNetworkInfo) + case reconnectAllServers + case reconnectServer(userId: Int64, smpServer: String) + 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 apiContactQueueInfo(contactId: Int64) + case apiGroupMemberQueueInfo(groupId: Int64, groupMemberId: Int64) + case apiSwitchContact(contactId: Int64) + case apiSwitchGroupMember(groupId: Int64, groupMemberId: Int64) + case apiAbortSwitchContact(contactId: Int64) + case apiAbortSwitchGroupMember(groupId: Int64, groupMemberId: Int64) + case apiSyncContactRatchet(contactId: Int64, force: Bool) + case apiSyncGroupMemberRatchet(groupId: Int64, groupMemberId: Int64, force: Bool) + case apiGetContactCode(contactId: Int64) + case apiGetGroupMemberCode(groupId: Int64, groupMemberId: Int64) + case apiVerifyContact(contactId: Int64, connectionCode: String?) + case apiVerifyGroupMember(groupId: Int64, groupMemberId: Int64, connectionCode: String?) + case apiAddContact(userId: Int64, short: Bool, incognito: Bool) + case apiSetConnectionIncognito(connId: Int64, incognito: Bool) + case apiChangeConnectionUser(connId: Int64, userId: Int64) + case apiConnectPlan(userId: Int64, connLink: String) + case apiConnect(userId: Int64, incognito: Bool, connLink: CreatedConnLink) + case apiConnectContactViaAddress(userId: Int64, incognito: Bool, contactId: Int64) + case apiDeleteChat(type: ChatType, id: Int64, chatDeleteMode: ChatDeleteMode) + case apiClearChat(type: ChatType, id: Int64) + case apiListContacts(userId: Int64) + case apiUpdateProfile(userId: Int64, profile: Profile) + case apiSetContactPrefs(contactId: Int64, preferences: Preferences) + case apiSetContactAlias(contactId: Int64, localAlias: String) + case apiSetGroupAlias(groupId: Int64, localAlias: String) + case apiSetConnectionAlias(connId: Int64, localAlias: String) + case apiSetUserUIThemes(userId: Int64, themes: ThemeModeOverrides?) + case apiSetChatUIThemes(chatId: String, themes: ThemeModeOverrides?) + case apiCreateMyAddress(userId: Int64, short: Bool) + case apiDeleteMyAddress(userId: Int64) + case apiShowMyAddress(userId: Int64) + case apiSetProfileAddress(userId: Int64, on: Bool) + case apiAddressAutoAccept(userId: Int64, autoAccept: AutoAccept?) + case apiAcceptContact(incognito: Bool, contactReqId: Int64) + case apiRejectContact(contactReqId: Int64) + // WebRTC calls + case apiSendCallInvitation(contact: Contact, callType: CallType) + case apiRejectCall(contact: Contact) + case apiSendCallOffer(contact: Contact, callOffer: WebRTCCallOffer) + case apiSendCallAnswer(contact: Contact, answer: WebRTCSession) + case apiSendCallExtraInfo(contact: Contact, extraInfo: WebRTCExtraInfo) + case apiEndCall(contact: Contact) + case apiGetCallInvitations + case apiCallStatus(contact: Contact, callStatus: WebRTCCallStatus) + // WebRTC calls / + case apiGetNetworkStatuses + case apiChatRead(type: ChatType, id: Int64) + case apiChatItemsRead(type: ChatType, id: Int64, itemIds: [Int64]) + case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) + case receiveFile(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?, inline: Bool?) + case setFileToReceive(fileId: Int64, userApprovedRelays: Bool, 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) + case apiUploadStandaloneFile(userId: Int64, file: CryptoFile) + case apiDownloadStandaloneFile(userId: Int64, url: String, file: CryptoFile) + case apiStandaloneFileInfo(url: String) + // misc + case showVersion + case getAgentSubsTotal(userId: Int64) + case getAgentServersSummary(userId: Int64) + case resetAgentServersStats + case string(String) + + var cmdString: String { + get { + switch self { + case .showActiveUser: return "/u" + case let .createActiveUser(profile, pastTimestamp): + let user = NewUser(profile: profile, pastTimestamp: pastTimestamp) + return "/_create user \(encodeJSON(user))" + case .listUsers: return "/users" + case let .apiSetActiveUser(userId, viewPwd): return "/_user \(userId)\(maybePwd(viewPwd))" + case let .setAllContactReceipts(enable): return "/set receipts all \(onOff(enable))" + case let .apiSetUserContactReceipts(userId, userMsgReceiptSettings): + let umrs = userMsgReceiptSettings + return "/_set receipts contacts \(userId) \(onOff(umrs.enable)) clear_overrides=\(onOff(umrs.clearOverrides))" + case let .apiSetUserGroupReceipts(userId, userMsgReceiptSettings): + let umrs = userMsgReceiptSettings + return "/_set receipts groups \(userId) \(onOff(umrs.enable)) clear_overrides=\(onOff(umrs.clearOverrides))" + case let .apiHideUser(userId, viewPwd): return "/_hide user \(userId) \(encodeJSON(viewPwd))" + case let .apiUnhideUser(userId, viewPwd): return "/_unhide user \(userId) \(encodeJSON(viewPwd))" + case let .apiMuteUser(userId): return "/_mute user \(userId)" + case let .apiUnmuteUser(userId): return "/_unmute user \(userId)" + case let .apiDeleteUser(userId, delSMPQueues, viewPwd): return "/_delete user \(userId) del_smp=\(onOff(delSMPQueues))\(maybePwd(viewPwd))" + case let .startChat(mainApp, enableSndFiles): return "/_start main=\(onOff(mainApp)) snd_files=\(onOff(enableSndFiles))" + case .checkChatRunning: return "/_check running" + case .apiStopChat: return "/_stop" + case let .apiActivateChat(restore): return "/_app activate restore=\(onOff(restore))" + case let .apiSuspendChat(timeoutMicroseconds): return "/_app suspend \(timeoutMicroseconds)" + case let .apiSetAppFilePaths(filesFolder, tempFolder, assetsFolder): return "/set file paths \(encodeJSON(AppFilePaths(appFilesFolder: filesFolder, appTempFolder: tempFolder, appAssetsFolder: assetsFolder)))" + case let .apiSetEncryptLocalFiles(enable): return "/_files_encrypt \(onOff(enable))" + case let .apiExportArchive(cfg): return "/_db export \(encodeJSON(cfg))" + case let .apiImportArchive(cfg): return "/_db import \(encodeJSON(cfg))" + case .apiDeleteStorage: return "/_db delete" + case let .apiStorageEncryption(cfg): return "/_db encryption \(encodeJSON(cfg))" + case let .testStorageEncryption(key): return "/db test key \(key)" + case let .apiSaveSettings(settings): return "/_save app settings \(encodeJSON(settings))" + case let .apiGetSettings(settings): return "/_get app settings \(encodeJSON(settings))" + case let .apiGetChatTags(userId): return "/_get tags \(userId)" + case let .apiGetChats(userId): return "/_get chats \(userId) pcc=on" + case let .apiGetChat(chatId, pagination, search): return "/_get chat \(chatId) \(pagination.cmdString)" + + (search == "" ? "" : " search=\(search)") + case let .apiGetChatItemInfo(type, id, itemId): return "/_get item info \(ref(type, id)) \(itemId)" + case let .apiSendMessages(type, id, live, ttl, composedMessages): + let msgs = encodeJSON(composedMessages) + let ttlStr = ttl != nil ? "\(ttl!)" : "default" + return "/_send \(ref(type, id)) live=\(onOff(live)) ttl=\(ttlStr) json \(msgs)" + case let .apiCreateChatTag(tag): return "/_create tag \(encodeJSON(tag))" + case let .apiSetChatTags(type, id, tagIds): return "/_tags \(ref(type, id)) \(tagIds.map({ "\($0)" }).joined(separator: ","))" + case let .apiDeleteChatTag(tagId): return "/_delete tag \(tagId)" + case let .apiUpdateChatTag(tagId, tagData): return "/_update tag \(tagId) \(encodeJSON(tagData))" + case let .apiReorderChatTags(tagIds): return "/_reorder tags \(tagIds.map({ "\($0)" }).joined(separator: ","))" + case let .apiCreateChatItems(noteFolderId, composedMessages): + let msgs = encodeJSON(composedMessages) + return "/_create *\(noteFolderId) json \(msgs)" + case let .apiReportMessage(groupId, chatItemId, reportReason, reportText): + return "/_report #\(groupId) \(chatItemId) reason=\(reportReason) \(reportText)" + case let .apiUpdateChatItem(type, id, itemId, um, live): return "/_update item \(ref(type, id)) \(itemId) live=\(onOff(live)) \(um.cmdString)" + case let .apiDeleteChatItem(type, id, itemIds, mode): return "/_delete item \(ref(type, id)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) \(mode.rawValue)" + case let .apiDeleteMemberChatItem(groupId, itemIds): return "/_delete member item #\(groupId) \(itemIds.map({ "\($0)" }).joined(separator: ","))" + case let .apiArchiveReceivedReports(groupId): return "/_archive reports #\(groupId)" + case let .apiDeleteReceivedReports(groupId, itemIds, mode): return "/_delete reports #\(groupId) \(itemIds.map({ "\($0)" }).joined(separator: ",")) \(mode.rawValue)" + case let .apiChatItemReaction(type, id, itemId, add, reaction): return "/_reaction \(ref(type, id)) \(itemId) \(onOff(add)) \(encodeJSON(reaction))" + case let .apiGetReactionMembers(userId, groupId, itemId, reaction): return "/_reaction members \(userId) #\(groupId) \(itemId) \(encodeJSON(reaction))" + case let .apiPlanForwardChatItems(type, id, itemIds): return "/_forward plan \(ref(type, id)) \(itemIds.map({ "\($0)" }).joined(separator: ","))" + case let .apiForwardChatItems(toChatType, toChatId, fromChatType, fromChatId, itemIds, ttl): + let ttlStr = ttl != nil ? "\(ttl!)" : "default" + return "/_forward \(ref(toChatType, toChatId)) \(ref(fromChatType, fromChatId)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) ttl=\(ttlStr)" + case .apiGetNtfToken: return "/_ntf get " + case let .apiRegisterToken(token, notificationMode): return "/_ntf register \(token.cmdString) \(notificationMode.rawValue)" + case let .apiVerifyToken(token, nonce, code): return "/_ntf verify \(token.cmdString) \(nonce) \(code)" + case let .apiCheckToken(token): return "/_ntf check \(token.cmdString)" + case let .apiDeleteToken(token): return "/_ntf delete \(token.cmdString)" + case let .apiGetNtfConns(nonce, encNtfInfo): return "/_ntf conns \(nonce) \(encNtfInfo)" + case let .apiGetConnNtfMessages(connMsgReqs): return "/_ntf conn messages \(connMsgReqs.map { $0.cmdString }.joined(separator: ","))" + 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 .apiMembersRole(groupId, memberIds, memberRole): return "/_member role #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ",")) \(memberRole.rawValue)" + case let .apiBlockMembersForAll(groupId, memberIds, blocked): return "/_block #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ",")) blocked=\(onOff(blocked))" + case let .apiRemoveMembers(groupId, memberIds, withMessages): return "/_remove #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ",")) messages=\(onOff(withMessages))" + case let .apiLeaveGroup(groupId): return "/_leave #\(groupId)" + case let .apiListMembers(groupId): return "/_members #\(groupId)" + case let .apiUpdateGroupProfile(groupId, groupProfile): return "/_group_profile #\(groupId) \(encodeJSON(groupProfile))" + case let .apiCreateGroupLink(groupId, memberRole, short): return "/_create link #\(groupId) \(memberRole) short=\(onOff(short))" + case let .apiGroupLinkMemberRole(groupId, memberRole): return "/_set link role #\(groupId) \(memberRole)" + case let .apiDeleteGroupLink(groupId): return "/_delete link #\(groupId)" + case let .apiGetGroupLink(groupId): return "/_get link #\(groupId)" + case let .apiCreateMemberContact(groupId, groupMemberId): return "/_create member contact #\(groupId) \(groupMemberId)" + case let .apiSendMemberContactInvitation(contactId, mc): return "/_invite member contact @\(contactId) \(mc.cmdString)" + case let .apiTestProtoServer(userId, server): return "/_server test \(userId) \(server)" + case .apiGetServerOperators: return "/_operators" + case let .apiSetServerOperators(operators): return "/_operators \(encodeJSON(operators))" + case let .apiGetUserServers(userId): return "/_servers \(userId)" + case let .apiSetUserServers(userId, userServers): return "/_servers \(userId) \(encodeJSON(userServers))" + case let .apiValidateServers(userId, userServers): return "/_validate_servers \(userId) \(encodeJSON(userServers))" + case .apiGetUsageConditions: return "/_conditions" + case let .apiSetConditionsNotified(conditionsId): return "/_conditions_notified \(conditionsId)" + case let .apiAcceptConditions(conditionsId, operatorIds): return "/_accept_conditions \(conditionsId) \(joinedIds(operatorIds))" + case let .apiSetChatItemTTL(userId, seconds): return "/_ttl \(userId) \(chatItemTTLStr(seconds: seconds))" + case let .apiGetChatItemTTL(userId): return "/_ttl \(userId)" + case let .apiSetChatTTL(userId, type, id, seconds): return "/_ttl \(userId) \(ref(type, id)) \(chatItemTTLStr(seconds: seconds))" + case let .apiSetNetworkConfig(networkConfig): return "/_network \(encodeJSON(networkConfig))" + case .apiGetNetworkConfig: return "/network" + case let .apiSetNetworkInfo(networkInfo): return "/_network info \(encodeJSON(networkInfo))" + case .reconnectAllServers: return "/reconnect" + case let .reconnectServer(userId, smpServer): return "/reconnect \(userId) \(smpServer)" + 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 .apiContactQueueInfo(contactId): return "/_queue info @\(contactId)" + case let .apiGroupMemberQueueInfo(groupId, groupMemberId): return "/_queue info #\(groupId) \(groupMemberId)" + case let .apiSwitchContact(contactId): return "/_switch @\(contactId)" + case let .apiSwitchGroupMember(groupId, groupMemberId): return "/_switch #\(groupId) \(groupMemberId)" + case let .apiAbortSwitchContact(contactId): return "/_abort switch @\(contactId)" + case let .apiAbortSwitchGroupMember(groupId, groupMemberId): return "/_abort switch #\(groupId) \(groupMemberId)" + case let .apiSyncContactRatchet(contactId, force): if force { + return "/_sync @\(contactId) force=on" + } else { + return "/_sync @\(contactId)" + } + case let .apiSyncGroupMemberRatchet(groupId, groupMemberId, force): if force { + return "/_sync #\(groupId) \(groupMemberId) force=on" + } else { + return "/_sync #\(groupId) \(groupMemberId)" + } + case let .apiGetContactCode(contactId): return "/_get code @\(contactId)" + case let .apiGetGroupMemberCode(groupId, groupMemberId): return "/_get code #\(groupId) \(groupMemberId)" + case let .apiVerifyContact(contactId, .some(connectionCode)): return "/_verify code @\(contactId) \(connectionCode)" + case let .apiVerifyContact(contactId, .none): return "/_verify code @\(contactId)" + case let .apiVerifyGroupMember(groupId, groupMemberId, .some(connectionCode)): return "/_verify code #\(groupId) \(groupMemberId) \(connectionCode)" + case let .apiVerifyGroupMember(groupId, groupMemberId, .none): return "/_verify code #\(groupId) \(groupMemberId)" + case let .apiAddContact(userId, short, incognito): return "/_connect \(userId) short=\(onOff(short)) incognito=\(onOff(incognito))" + case let .apiSetConnectionIncognito(connId, incognito): return "/_set incognito :\(connId) \(onOff(incognito))" + case let .apiChangeConnectionUser(connId, userId): return "/_set conn user :\(connId) \(userId)" + case let .apiConnectPlan(userId, connLink): return "/_connect plan \(userId) \(connLink)" + case let .apiConnect(userId, incognito, connLink): return "/_connect \(userId) incognito=\(onOff(incognito)) \(connLink.connFullLink) \(connLink.connShortLink ?? "")" + case let .apiConnectContactViaAddress(userId, incognito, contactId): return "/_connect contact \(userId) incognito=\(onOff(incognito)) \(contactId)" + case let .apiDeleteChat(type, id, chatDeleteMode): return "/_delete \(ref(type, id)) \(chatDeleteMode.cmdString)" + case let .apiClearChat(type, id): return "/_clear chat \(ref(type, id))" + case let .apiListContacts(userId): return "/_contacts \(userId)" + case let .apiUpdateProfile(userId, profile): return "/_profile \(userId) \(encodeJSON(profile))" + case let .apiSetContactPrefs(contactId, preferences): return "/_set prefs @\(contactId) \(encodeJSON(preferences))" + case let .apiSetContactAlias(contactId, localAlias): return "/_set alias @\(contactId) \(localAlias.trimmingCharacters(in: .whitespaces))" + case let .apiSetGroupAlias(groupId, localAlias): return "/_set alias #\(groupId) \(localAlias.trimmingCharacters(in: .whitespaces))" + case let .apiSetConnectionAlias(connId, localAlias): return "/_set alias :\(connId) \(localAlias.trimmingCharacters(in: .whitespaces))" + case let .apiSetUserUIThemes(userId, themes): return "/_set theme user \(userId) \(themes != nil ? encodeJSON(themes) : "")" + case let .apiSetChatUIThemes(chatId, themes): return "/_set theme \(chatId) \(themes != nil ? encodeJSON(themes) : "")" + case let .apiCreateMyAddress(userId, short): return "/_address \(userId) short=\(onOff(short))" + case let .apiDeleteMyAddress(userId): return "/_delete_address \(userId)" + case let .apiShowMyAddress(userId): return "/_show_address \(userId)" + case let .apiSetProfileAddress(userId, on): return "/_profile_address \(userId) \(onOff(on))" + case let .apiAddressAutoAccept(userId, autoAccept): return "/_auto_accept \(userId) \(AutoAccept.cmdString(autoAccept))" + case let .apiAcceptContact(incognito, contactReqId): return "/_accept incognito=\(onOff(incognito)) \(contactReqId)" + case let .apiRejectContact(contactReqId): return "/_reject \(contactReqId)" + case let .apiSendCallInvitation(contact, callType): return "/_call invite @\(contact.apiId) \(encodeJSON(callType))" + case let .apiRejectCall(contact): return "/_call reject @\(contact.apiId)" + case let .apiSendCallOffer(contact, callOffer): return "/_call offer @\(contact.apiId) \(encodeJSON(callOffer))" + case let .apiSendCallAnswer(contact, answer): return "/_call answer @\(contact.apiId) \(encodeJSON(answer))" + case let .apiSendCallExtraInfo(contact, extraInfo): return "/_call extra @\(contact.apiId) \(encodeJSON(extraInfo))" + case let .apiEndCall(contact): return "/_call end @\(contact.apiId)" + case .apiGetCallInvitations: return "/_call get" + case let .apiCallStatus(contact, callStatus): return "/_call status @\(contact.apiId) \(callStatus.rawValue)" + case .apiGetNetworkStatuses: return "/_network_statuses" + case let .apiChatRead(type, id): return "/_read chat \(ref(type, id))" + case let .apiChatItemsRead(type, id, itemIds): return "/_read chat items \(ref(type, id)) \(joinedIds(itemIds))" + case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id)) \(onOff(unreadChat))" + case let .receiveFile(fileId, userApprovedRelays, encrypt, inline): return "/freceive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))\(onOffParam("inline", inline))" + case let .setFileToReceive(fileId, userApprovedRelays, encrypt): return "/_set_file_to_receive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(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 let .apiUploadStandaloneFile(userId, file): return "/_upload \(userId) \(file.filePath)" + case let .apiDownloadStandaloneFile(userId, link, file): return "/_download \(userId) \(link) \(file.filePath)" + case let .apiStandaloneFileInfo(link): return "/_download info \(link)" + case .showVersion: return "/version" + case let .getAgentSubsTotal(userId): return "/get subs total \(userId)" + case let .getAgentServersSummary(userId): return "/get servers summary \(userId)" + case .resetAgentServersStats: return "/reset servers stats" + case let .string(str): return str + } + } + } + + var cmdType: String { + get { + switch self { + case .showActiveUser: return "showActiveUser" + case .createActiveUser: return "createActiveUser" + case .listUsers: return "listUsers" + case .apiSetActiveUser: return "apiSetActiveUser" + case .setAllContactReceipts: return "setAllContactReceipts" + case .apiSetUserContactReceipts: return "apiSetUserContactReceipts" + case .apiSetUserGroupReceipts: return "apiSetUserGroupReceipts" + case .apiHideUser: return "apiHideUser" + case .apiUnhideUser: return "apiUnhideUser" + case .apiMuteUser: return "apiMuteUser" + case .apiUnmuteUser: return "apiUnmuteUser" + case .apiDeleteUser: return "apiDeleteUser" + case .startChat: return "startChat" + case .checkChatRunning: return "checkChatRunning" + case .apiStopChat: return "apiStopChat" + case .apiActivateChat: return "apiActivateChat" + case .apiSuspendChat: return "apiSuspendChat" + case .apiSetAppFilePaths: return "apiSetAppFilePaths" + case .apiSetEncryptLocalFiles: return "apiSetEncryptLocalFiles" + case .apiExportArchive: return "apiExportArchive" + case .apiImportArchive: return "apiImportArchive" + case .apiDeleteStorage: return "apiDeleteStorage" + case .apiStorageEncryption: return "apiStorageEncryption" + case .testStorageEncryption: return "testStorageEncryption" + case .apiSaveSettings: return "apiSaveSettings" + case .apiGetSettings: return "apiGetSettings" + case .apiGetChatTags: return "apiGetChatTags" + case .apiGetChats: return "apiGetChats" + case .apiGetChat: return "apiGetChat" + case .apiGetChatItemInfo: return "apiGetChatItemInfo" + case .apiSendMessages: return "apiSendMessages" + case .apiCreateChatTag: return "apiCreateChatTag" + case .apiSetChatTags: return "apiSetChatTags" + case .apiDeleteChatTag: return "apiDeleteChatTag" + case .apiUpdateChatTag: return "apiUpdateChatTag" + case .apiReorderChatTags: return "apiReorderChatTags" + case .apiCreateChatItems: return "apiCreateChatItems" + case .apiReportMessage: return "apiReportMessage" + case .apiUpdateChatItem: return "apiUpdateChatItem" + case .apiDeleteChatItem: return "apiDeleteChatItem" + case .apiConnectContactViaAddress: return "apiConnectContactViaAddress" + case .apiDeleteMemberChatItem: return "apiDeleteMemberChatItem" + case .apiArchiveReceivedReports: return "apiArchiveReceivedReports" + case .apiDeleteReceivedReports: return "apiDeleteReceivedReports" + case .apiChatItemReaction: return "apiChatItemReaction" + case .apiGetReactionMembers: return "apiGetReactionMembers" + case .apiPlanForwardChatItems: return "apiPlanForwardChatItems" + case .apiForwardChatItems: return "apiForwardChatItems" + case .apiGetNtfToken: return "apiGetNtfToken" + case .apiRegisterToken: return "apiRegisterToken" + case .apiVerifyToken: return "apiVerifyToken" + case .apiCheckToken: return "apiCheckToken" + case .apiDeleteToken: return "apiDeleteToken" + case .apiGetNtfConns: return "apiGetNtfConns" + case .apiGetConnNtfMessages: return "apiGetConnNtfMessages" + case .apiNewGroup: return "apiNewGroup" + case .apiAddMember: return "apiAddMember" + case .apiJoinGroup: return "apiJoinGroup" + case .apiMembersRole: return "apiMembersRole" + case .apiBlockMembersForAll: return "apiBlockMembersForAll" + case .apiRemoveMembers: return "apiRemoveMembers" + case .apiLeaveGroup: return "apiLeaveGroup" + case .apiListMembers: return "apiListMembers" + case .apiUpdateGroupProfile: return "apiUpdateGroupProfile" + case .apiCreateGroupLink: return "apiCreateGroupLink" + case .apiGroupLinkMemberRole: return "apiGroupLinkMemberRole" + case .apiDeleteGroupLink: return "apiDeleteGroupLink" + case .apiGetGroupLink: return "apiGetGroupLink" + case .apiCreateMemberContact: return "apiCreateMemberContact" + case .apiSendMemberContactInvitation: return "apiSendMemberContactInvitation" + case .apiTestProtoServer: return "apiTestProtoServer" + case .apiGetServerOperators: return "apiGetServerOperators" + case .apiSetServerOperators: return "apiSetServerOperators" + case .apiGetUserServers: return "apiGetUserServers" + case .apiSetUserServers: return "apiSetUserServers" + case .apiValidateServers: return "apiValidateServers" + case .apiGetUsageConditions: return "apiGetUsageConditions" + case .apiSetConditionsNotified: return "apiSetConditionsNotified" + case .apiAcceptConditions: return "apiAcceptConditions" + case .apiSetChatItemTTL: return "apiSetChatItemTTL" + case .apiGetChatItemTTL: return "apiGetChatItemTTL" + case .apiSetChatTTL: return "apiSetChatTTL" + case .apiSetNetworkConfig: return "apiSetNetworkConfig" + case .apiGetNetworkConfig: return "apiGetNetworkConfig" + case .apiSetNetworkInfo: return "apiSetNetworkInfo" + case .reconnectAllServers: return "reconnectAllServers" + case .reconnectServer: return "reconnectServer" + case .apiSetChatSettings: return "apiSetChatSettings" + case .apiSetMemberSettings: return "apiSetMemberSettings" + case .apiContactInfo: return "apiContactInfo" + case .apiGroupMemberInfo: return "apiGroupMemberInfo" + case .apiContactQueueInfo: return "apiContactQueueInfo" + case .apiGroupMemberQueueInfo: return "apiGroupMemberQueueInfo" + case .apiSwitchContact: return "apiSwitchContact" + case .apiSwitchGroupMember: return "apiSwitchGroupMember" + case .apiAbortSwitchContact: return "apiAbortSwitchContact" + case .apiAbortSwitchGroupMember: return "apiAbortSwitchGroupMember" + case .apiSyncContactRatchet: return "apiSyncContactRatchet" + case .apiSyncGroupMemberRatchet: return "apiSyncGroupMemberRatchet" + case .apiGetContactCode: return "apiGetContactCode" + case .apiGetGroupMemberCode: return "apiGetGroupMemberCode" + case .apiVerifyContact: return "apiVerifyContact" + case .apiVerifyGroupMember: return "apiVerifyGroupMember" + case .apiAddContact: return "apiAddContact" + case .apiSetConnectionIncognito: return "apiSetConnectionIncognito" + case .apiChangeConnectionUser: return "apiChangeConnectionUser" + case .apiConnectPlan: return "apiConnectPlan" + case .apiConnect: return "apiConnect" + case .apiDeleteChat: return "apiDeleteChat" + case .apiClearChat: return "apiClearChat" + case .apiListContacts: return "apiListContacts" + case .apiUpdateProfile: return "apiUpdateProfile" + case .apiSetContactPrefs: return "apiSetContactPrefs" + case .apiSetContactAlias: return "apiSetContactAlias" + case .apiSetGroupAlias: return "apiSetGroupAlias" + case .apiSetConnectionAlias: return "apiSetConnectionAlias" + case .apiSetUserUIThemes: return "apiSetUserUIThemes" + case .apiSetChatUIThemes: return "apiSetChatUIThemes" + case .apiCreateMyAddress: return "apiCreateMyAddress" + case .apiDeleteMyAddress: return "apiDeleteMyAddress" + case .apiShowMyAddress: return "apiShowMyAddress" + case .apiSetProfileAddress: return "apiSetProfileAddress" + case .apiAddressAutoAccept: return "apiAddressAutoAccept" + case .apiAcceptContact: return "apiAcceptContact" + case .apiRejectContact: return "apiRejectContact" + case .apiSendCallInvitation: return "apiSendCallInvitation" + case .apiRejectCall: return "apiRejectCall" + case .apiSendCallOffer: return "apiSendCallOffer" + case .apiSendCallAnswer: return "apiSendCallAnswer" + case .apiSendCallExtraInfo: return "apiSendCallExtraInfo" + case .apiEndCall: return "apiEndCall" + case .apiGetCallInvitations: return "apiGetCallInvitations" + case .apiCallStatus: return "apiCallStatus" + case .apiGetNetworkStatuses: return "apiGetNetworkStatuses" + case .apiChatRead: return "apiChatRead" + case .apiChatItemsRead: return "apiChatItemsRead" + case .apiChatUnread: return "apiChatUnread" + 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 .apiUploadStandaloneFile: return "apiUploadStandaloneFile" + case .apiDownloadStandaloneFile: return "apiDownloadStandaloneFile" + case .apiStandaloneFileInfo: return "apiStandaloneFileInfo" + case .showVersion: return "showVersion" + case .getAgentSubsTotal: return "getAgentSubsTotal" + case .getAgentServersSummary: return "getAgentServersSummary" + case .resetAgentServersStats: return "resetAgentServersStats" + case .string: return "console command" + } + } + } + + func ref(_ type: ChatType, _ id: Int64) -> String { + "\(type.rawValue)\(id)" + } + + func joinedIds(_ ids: [Int64]) -> String { + ids.map { "\($0)" }.joined(separator: ",") + } + + func chatItemTTLStr(seconds: Int64?) -> String { + if let seconds = seconds { + return String(seconds) + } else { + return "default" + } + } + + var obfuscated: ChatCommand { + switch self { + case let .apiStorageEncryption(cfg): + return .apiStorageEncryption(config: DBEncryptionConfig(currentKey: obfuscate(cfg.currentKey), newKey: obfuscate(cfg.newKey))) + case let .apiSetActiveUser(userId, viewPwd): + return .apiSetActiveUser(userId: userId, viewPwd: obfuscate(viewPwd)) + case let .apiHideUser(userId, viewPwd): + return .apiHideUser(userId: userId, viewPwd: obfuscate(viewPwd)) + case let .apiUnhideUser(userId, viewPwd): + return .apiUnhideUser(userId: userId, viewPwd: obfuscate(viewPwd)) + case let .apiDeleteUser(userId, delSMPQueues, viewPwd): + return .apiDeleteUser(userId: userId, delSMPQueues: delSMPQueues, viewPwd: obfuscate(viewPwd)) + case let .testStorageEncryption(key): + return .testStorageEncryption(key: obfuscate(key)) + default: return self + } + } + + private func obfuscate(_ s: String) -> String { + s == "" ? "" : "***" + } + + private func obfuscate(_ s: String?) -> String? { + if let s = s { + return obfuscate(s) + } + return nil + } + + private func onOffParam(_ param: String, _ b: Bool?) -> String { + if let b = b { + return " \(param)=\(onOff(b))" + } + return "" + } + + private func maybePwd(_ pwd: String?) -> String { + pwd == "" || pwd == nil ? "" : " " + encodeJSON(pwd) + } +} + +enum ChatResponse: Decodable, Error, ChatRespProtocol { + case response(type: String, json: String) + case activeUser(user: User) + case usersList(users: [UserInfo]) + case chatStarted + case chatRunning + case chatStopped + case chatSuspended + case apiChats(user: UserRef, chats: [ChatData]) + case apiChat(user: UserRef, chat: ChatData, navInfo: NavigationInfo?) + case chatTags(user: UserRef, userTags: [ChatTag]) + case chatItemInfo(user: UserRef, chatItem: AChatItem, chatItemInfo: ChatItemInfo) + case serverTestResult(user: UserRef, testServer: String, testFailure: ProtocolTestFailure?) + case serverOperatorConditions(conditions: ServerOperatorConditions) + case userServers(user: UserRef, userServers: [UserOperatorServers]) + case userServersValidation(user: UserRef, serverErrors: [UserServersError]) + case usageConditions(usageConditions: UsageConditions, conditionsText: String, acceptedConditions: UsageConditions?) + case chatItemTTL(user: UserRef, chatItemTTL: Int64?) + case networkConfig(networkConfig: NetCfg) + case contactInfo(user: UserRef, contact: Contact, connectionStats_: ConnectionStats?, customUserProfile: Profile?) + case groupMemberInfo(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats_: ConnectionStats?) + case queueInfo(user: UserRef, rcvMsgInfo: RcvMsgInfo?, queueInfo: ServerQueueInfo) + case contactSwitchStarted(user: UserRef, contact: Contact, connectionStats: ConnectionStats) + case groupMemberSwitchStarted(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats) + case contactSwitchAborted(user: UserRef, contact: Contact, connectionStats: ConnectionStats) + case groupMemberSwitchAborted(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats) + case contactSwitch(user: UserRef, contact: Contact, switchProgress: SwitchProgress) + case groupMemberSwitch(user: UserRef, groupInfo: GroupInfo, member: GroupMember, switchProgress: SwitchProgress) + case contactRatchetSyncStarted(user: UserRef, contact: Contact, connectionStats: ConnectionStats) + case groupMemberRatchetSyncStarted(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats) + case contactRatchetSync(user: UserRef, contact: Contact, ratchetSyncProgress: RatchetSyncProgress) + case groupMemberRatchetSync(user: UserRef, groupInfo: GroupInfo, member: GroupMember, ratchetSyncProgress: RatchetSyncProgress) + case contactVerificationReset(user: UserRef, contact: Contact) + case groupMemberVerificationReset(user: UserRef, groupInfo: GroupInfo, member: GroupMember) + case contactCode(user: UserRef, contact: Contact, connectionCode: String) + case groupMemberCode(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionCode: String) + case connectionVerified(user: UserRef, verified: Bool, expectedCode: String) + case tagsUpdated(user: UserRef, userTags: [ChatTag], chatTags: [Int64]) + case invitation(user: UserRef, connLinkInvitation: CreatedConnLink, connection: PendingContactConnection) + case connectionIncognitoUpdated(user: UserRef, toConnection: PendingContactConnection) + case connectionUserChanged(user: UserRef, fromConnection: PendingContactConnection, toConnection: PendingContactConnection, newUser: UserRef) + case connectionPlan(user: UserRef, connLink: CreatedConnLink, connectionPlan: ConnectionPlan) + case sentConfirmation(user: UserRef, connection: PendingContactConnection) + case sentInvitation(user: UserRef, connection: PendingContactConnection) + case sentInvitationToContact(user: UserRef, contact: Contact, customUserProfile: Profile?) + case contactAlreadyExists(user: UserRef, contact: Contact) + case contactDeleted(user: UserRef, contact: Contact) + case contactDeletedByContact(user: UserRef, contact: Contact) + case chatCleared(user: UserRef, chatInfo: ChatInfo) + case userProfileNoChange(user: User) + case userProfileUpdated(user: User, fromProfile: Profile, toProfile: Profile, updateSummary: UserProfileUpdateSummary) + case userPrivacy(user: User, updatedUser: User) + case contactAliasUpdated(user: UserRef, toContact: Contact) + case groupAliasUpdated(user: UserRef, toGroup: GroupInfo) + case connectionAliasUpdated(user: UserRef, toConnection: PendingContactConnection) + case contactPrefsUpdated(user: User, fromContact: Contact, toContact: Contact) + case userContactLink(user: User, contactLink: UserContactLink) + case userContactLinkUpdated(user: User, contactLink: UserContactLink) + case userContactLinkCreated(user: User, connLinkContact: CreatedConnLink) + case userContactLinkDeleted(user: User) + case contactConnected(user: UserRef, contact: Contact, userCustomProfile: Profile?) + case contactConnecting(user: UserRef, contact: Contact) + case contactSndReady(user: UserRef, contact: Contact) + case receivedContactRequest(user: UserRef, contactRequest: UserContactRequest) + 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) + case networkStatus(networkStatus: NetworkStatus, connections: [String]) + case networkStatuses(user_: UserRef?, networkStatuses: [ConnNetworkStatus]) + case groupSubscribed(user: UserRef, groupInfo: GroupRef) + case memberSubErrors(user: UserRef, memberSubErrors: [MemberSubError]) + case groupEmpty(user: UserRef, groupInfo: GroupInfo) + case userContactLinkSubscribed + case newChatItems(user: UserRef, chatItems: [AChatItem]) + case groupChatItemsDeleted(user: UserRef, groupInfo: GroupInfo, chatItemIDs: Set, byUser: Bool, member_: GroupMember?) + case forwardPlan(user: UserRef, chatItemIds: [Int64], forwardConfirmation: ForwardConfirmation?) + case chatItemsStatusesUpdated(user: UserRef, chatItems: [AChatItem]) + case chatItemUpdated(user: UserRef, chatItem: AChatItem) + case chatItemNotChanged(user: UserRef, chatItem: AChatItem) + case chatItemReaction(user: UserRef, added: Bool, reaction: ACIReaction) + case reactionMembers(user: UserRef, memberReactions: [MemberReaction]) + case chatItemsDeleted(user: UserRef, chatItemDeletions: [ChatItemDeletion], byUser: Bool) + case contactsList(user: UserRef, contacts: [Contact]) + // group events + 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 businessLinkConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember, fromContact: Contact) + case userDeletedMembers(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], withMessages: Bool) + case leftMemberUser(user: UserRef, groupInfo: GroupInfo) + case groupMembers(user: UserRef, group: SimpleXChat.Group) + case receivedGroupInvitation(user: UserRef, groupInfo: GroupInfo, contact: Contact, memberRole: GroupMemberRole) + case groupDeletedUser(user: UserRef, groupInfo: GroupInfo) + case joinedGroupMemberConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember, member: GroupMember) + case memberRole(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, member: GroupMember, fromRole: GroupMemberRole, toRole: GroupMemberRole) + case membersRoleUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], toRole: GroupMemberRole) + case memberBlockedForAll(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, member: GroupMember, blocked: Bool) + case membersBlockedForAllUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], blocked: Bool) + case deletedMemberUser(user: UserRef, groupInfo: GroupInfo, member: GroupMember, withMessages: Bool) + case deletedMember(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, deletedMember: GroupMember, withMessages: Bool) + case leftMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember) + case groupDeleted(user: UserRef, groupInfo: GroupInfo, member: GroupMember) + case contactsMerged(user: UserRef, intoContact: Contact, mergedContact: Contact) + case groupInvitation(user: UserRef, groupInfo: GroupInfo) // unused + case userJoinedGroup(user: UserRef, groupInfo: GroupInfo) + case joinedGroupMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember) + case connectedToGroupMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember, memberContact: Contact?) + case groupRemoved(user: UserRef, groupInfo: GroupInfo) // unused + case groupUpdated(user: UserRef, toGroup: GroupInfo) + case groupLinkCreated(user: UserRef, groupInfo: GroupInfo, connLinkContact: CreatedConnLink, memberRole: GroupMemberRole) + case groupLink(user: UserRef, groupInfo: GroupInfo, connLinkContact: CreatedConnLink, memberRole: GroupMemberRole) + case groupLinkDeleted(user: UserRef, groupInfo: GroupInfo) + case newMemberContact(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember) + case newMemberContactSentInv(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember) + case newMemberContactReceivedInv(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember) + // receiving file events + case rcvFileAccepted(user: UserRef, chatItem: AChatItem) + case rcvFileAcceptedSndCancelled(user: UserRef, rcvFileTransfer: RcvFileTransfer) + case standaloneFileInfo(fileMeta: MigrationFileLinkData?) + case rcvStandaloneFileCreated(user: UserRef, rcvFileTransfer: RcvFileTransfer) + case rcvFileStart(user: UserRef, chatItem: AChatItem) // send by chats + case rcvFileProgressXFTP(user: UserRef, chatItem_: AChatItem?, receivedSize: Int64, totalSize: Int64, rcvFileTransfer: RcvFileTransfer) + case rcvFileComplete(user: UserRef, chatItem: AChatItem) + case rcvStandaloneFileComplete(user: UserRef, targetPath: String, rcvFileTransfer: RcvFileTransfer) + case rcvFileCancelled(user: UserRef, chatItem_: AChatItem?, rcvFileTransfer: RcvFileTransfer) + case rcvFileSndCancelled(user: UserRef, chatItem: AChatItem, rcvFileTransfer: RcvFileTransfer) + case rcvFileError(user: UserRef, chatItem_: AChatItem?, agentError: AgentErrorType, rcvFileTransfer: RcvFileTransfer) + case rcvFileWarning(user: UserRef, chatItem_: AChatItem?, agentError: AgentErrorType, rcvFileTransfer: RcvFileTransfer) + // sending file events + case sndFileStart(user: UserRef, chatItem: AChatItem, sndFileTransfer: SndFileTransfer) + case sndFileComplete(user: UserRef, chatItem: AChatItem, sndFileTransfer: SndFileTransfer) + case sndFileRcvCancelled(user: UserRef, chatItem_: AChatItem?, sndFileTransfer: SndFileTransfer) + case sndFileCancelled(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, sndFileTransfers: [SndFileTransfer]) + case sndStandaloneFileCreated(user: UserRef, fileTransferMeta: FileTransferMeta) // returned by _upload + case sndFileStartXFTP(user: UserRef, chatItem: AChatItem, fileTransferMeta: FileTransferMeta) // not used + case sndFileProgressXFTP(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, sentSize: Int64, totalSize: Int64) + case sndFileRedirectStartXFTP(user: UserRef, fileTransferMeta: FileTransferMeta, redirectMeta: FileTransferMeta) + case sndFileCompleteXFTP(user: UserRef, chatItem: AChatItem, fileTransferMeta: FileTransferMeta) + case sndStandaloneFileComplete(user: UserRef, fileTransferMeta: FileTransferMeta, rcvURIs: [String]) + case sndFileCancelledXFTP(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta) + case sndFileError(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, errorMessage: String) + case sndFileWarning(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, errorMessage: String) + // call events + case callInvitation(callInvitation: RcvCallInvitation) + case callOffer(user: UserRef, contact: Contact, callType: CallType, offer: WebRTCSession, sharedKey: String?, askConfirmation: Bool) + case callAnswer(user: UserRef, contact: Contact, answer: WebRTCSession) + case callExtraInfo(user: UserRef, contact: Contact, extraInfo: WebRTCExtraInfo) + case callEnded(user: UserRef, contact: Contact) + case callInvitations(callInvitations: [RcvCallInvitation]) + case ntfTokenStatus(status: NtfTknStatus) + case ntfToken(token: DeviceToken, status: NtfTknStatus, ntfMode: NotificationsMode, ntfServer: String) + case ntfConns(ntfConns: [NtfConn]) + case connNtfMessages(receivedMsgs: [NtfMsgInfo?]) + case ntfMessage(user: UserRef, connEntity: ConnectionEntity, ntfMessage: NtfMsgAckInfo) + case contactConnectionDeleted(user: UserRef, connection: PendingContactConnection) + case contactDisabled(user: UserRef, contact: Contact) + // 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(rcsState: RemoteCtrlSessionState, rcStopReason: RemoteCtrlStopReason) + // pq + case contactPQEnabled(user: UserRef, contact: Contact, pqEnabled: Bool) + // misc + case versionInfo(versionInfo: CoreVersionInfo, chatMigrations: [UpMigration], agentMigrations: [UpMigration]) + case cmdOk(user_: UserRef?) + case agentSubsTotal(user: UserRef, subsTotal: SMPServerSubs, hasSession: Bool) + case agentServersSummary(user: UserRef, serversSummary: PresentedServersSummary) + case agentSubsSummary(user: UserRef, subsSummary: SMPServerSubs) + case chatCmdError(user_: UserRef?, chatError: ChatError) + case chatError(user_: UserRef?, chatError: ChatError) + case archiveExported(archiveErrors: [ArchiveError]) + case archiveImported(archiveErrors: [ArchiveError]) + case appSettings(appSettings: AppSettings) + + var responseType: String { + get { + switch self { + case let .response(type, _): return "* \(type)" + case .activeUser: return "activeUser" + case .usersList: return "usersList" + case .chatStarted: return "chatStarted" + case .chatRunning: return "chatRunning" + case .chatStopped: return "chatStopped" + case .chatSuspended: return "chatSuspended" + case .apiChats: return "apiChats" + case .apiChat: return "apiChat" + case .chatTags: return "chatTags" + case .chatItemInfo: return "chatItemInfo" + case .serverTestResult: return "serverTestResult" + case .serverOperatorConditions: return "serverOperators" + case .userServers: return "userServers" + case .userServersValidation: return "userServersValidation" + case .usageConditions: return "usageConditions" + case .chatItemTTL: return "chatItemTTL" + case .networkConfig: return "networkConfig" + case .contactInfo: return "contactInfo" + case .groupMemberInfo: return "groupMemberInfo" + case .queueInfo: return "queueInfo" + case .contactSwitchStarted: return "contactSwitchStarted" + case .groupMemberSwitchStarted: return "groupMemberSwitchStarted" + case .contactSwitchAborted: return "contactSwitchAborted" + case .groupMemberSwitchAborted: return "groupMemberSwitchAborted" + case .contactSwitch: return "contactSwitch" + case .groupMemberSwitch: return "groupMemberSwitch" + case .contactRatchetSyncStarted: return "contactRatchetSyncStarted" + case .groupMemberRatchetSyncStarted: return "groupMemberRatchetSyncStarted" + case .contactRatchetSync: return "contactRatchetSync" + case .groupMemberRatchetSync: return "groupMemberRatchetSync" + case .contactVerificationReset: return "contactVerificationReset" + case .groupMemberVerificationReset: return "groupMemberVerificationReset" + case .contactCode: return "contactCode" + case .groupMemberCode: return "groupMemberCode" + case .connectionVerified: return "connectionVerified" + case .tagsUpdated: return "tagsUpdated" + case .invitation: return "invitation" + case .connectionIncognitoUpdated: return "connectionIncognitoUpdated" + case .connectionUserChanged: return "connectionUserChanged" + case .connectionPlan: return "connectionPlan" + case .sentConfirmation: return "sentConfirmation" + case .sentInvitation: return "sentInvitation" + case .sentInvitationToContact: return "sentInvitationToContact" + case .contactAlreadyExists: return "contactAlreadyExists" + case .contactDeleted: return "contactDeleted" + case .contactDeletedByContact: return "contactDeletedByContact" + case .chatCleared: return "chatCleared" + case .userProfileNoChange: return "userProfileNoChange" + case .userProfileUpdated: return "userProfileUpdated" + case .userPrivacy: return "userPrivacy" + case .contactAliasUpdated: return "contactAliasUpdated" + case .groupAliasUpdated: return "groupAliasUpdated" + case .connectionAliasUpdated: return "connectionAliasUpdated" + case .contactPrefsUpdated: return "contactPrefsUpdated" + case .userContactLink: return "userContactLink" + case .userContactLinkUpdated: return "userContactLinkUpdated" + case .userContactLinkCreated: return "userContactLinkCreated" + case .userContactLinkDeleted: return "userContactLinkDeleted" + case .contactConnected: return "contactConnected" + case .contactConnecting: return "contactConnecting" + case .contactSndReady: return "contactSndReady" + case .receivedContactRequest: return "receivedContactRequest" + case .acceptingContactRequest: return "acceptingContactRequest" + case .contactRequestRejected: return "contactRequestRejected" + case .contactUpdated: return "contactUpdated" + case .groupMemberUpdated: return "groupMemberUpdated" + case .networkStatus: return "networkStatus" + case .networkStatuses: return "networkStatuses" + case .groupSubscribed: return "groupSubscribed" + case .memberSubErrors: return "memberSubErrors" + case .groupEmpty: return "groupEmpty" + case .userContactLinkSubscribed: return "userContactLinkSubscribed" + case .newChatItems: return "newChatItems" + case .groupChatItemsDeleted: return "groupChatItemsDeleted" + case .forwardPlan: return "forwardPlan" + case .chatItemsStatusesUpdated: return "chatItemsStatusesUpdated" + case .chatItemUpdated: return "chatItemUpdated" + case .chatItemNotChanged: return "chatItemNotChanged" + case .chatItemReaction: return "chatItemReaction" + case .reactionMembers: return "reactionMembers" + case .chatItemsDeleted: return "chatItemsDeleted" + case .contactsList: return "contactsList" + case .groupCreated: return "groupCreated" + case .sentGroupInvitation: return "sentGroupInvitation" + case .userAcceptedGroupSent: return "userAcceptedGroupSent" + case .groupLinkConnecting: return "groupLinkConnecting" + case .businessLinkConnecting: return "businessLinkConnecting" + case .userDeletedMembers: return "userDeletedMembers" + case .leftMemberUser: return "leftMemberUser" + case .groupMembers: return "groupMembers" + case .receivedGroupInvitation: return "receivedGroupInvitation" + case .groupDeletedUser: return "groupDeletedUser" + case .joinedGroupMemberConnecting: return "joinedGroupMemberConnecting" + case .memberRole: return "memberRole" + case .membersRoleUser: return "membersRoleUser" + case .memberBlockedForAll: return "memberBlockedForAll" + case .membersBlockedForAllUser: return "membersBlockedForAllUser" + case .deletedMemberUser: return "deletedMemberUser" + case .deletedMember: return "deletedMember" + case .leftMember: return "leftMember" + case .groupDeleted: return "groupDeleted" + case .contactsMerged: return "contactsMerged" + case .groupInvitation: return "groupInvitation" + case .userJoinedGroup: return "userJoinedGroup" + case .joinedGroupMember: return "joinedGroupMember" + case .connectedToGroupMember: return "connectedToGroupMember" + case .groupRemoved: return "groupRemoved" + case .groupUpdated: return "groupUpdated" + case .groupLinkCreated: return "groupLinkCreated" + case .groupLink: return "groupLink" + case .groupLinkDeleted: return "groupLinkDeleted" + case .newMemberContact: return "newMemberContact" + case .newMemberContactSentInv: return "newMemberContactSentInv" + case .newMemberContactReceivedInv: return "newMemberContactReceivedInv" + case .rcvFileAccepted: return "rcvFileAccepted" + case .rcvFileAcceptedSndCancelled: return "rcvFileAcceptedSndCancelled" + case .standaloneFileInfo: return "standaloneFileInfo" + case .rcvStandaloneFileCreated: return "rcvStandaloneFileCreated" + case .rcvFileStart: return "rcvFileStart" + case .rcvFileProgressXFTP: return "rcvFileProgressXFTP" + case .rcvFileComplete: return "rcvFileComplete" + case .rcvStandaloneFileComplete: return "rcvStandaloneFileComplete" + case .rcvFileCancelled: return "rcvFileCancelled" + case .rcvFileSndCancelled: return "rcvFileSndCancelled" + case .rcvFileError: return "rcvFileError" + case .rcvFileWarning: return "rcvFileWarning" + case .sndFileStart: return "sndFileStart" + case .sndFileComplete: return "sndFileComplete" + case .sndFileCancelled: return "sndFileCancelled" + case .sndStandaloneFileCreated: return "sndStandaloneFileCreated" + case .sndFileStartXFTP: return "sndFileStartXFTP" + case .sndFileProgressXFTP: return "sndFileProgressXFTP" + case .sndFileRedirectStartXFTP: return "sndFileRedirectStartXFTP" + case .sndFileRcvCancelled: return "sndFileRcvCancelled" + case .sndFileCompleteXFTP: return "sndFileCompleteXFTP" + case .sndStandaloneFileComplete: return "sndStandaloneFileComplete" + case .sndFileCancelledXFTP: return "sndFileCancelledXFTP" + case .sndFileError: return "sndFileError" + case .sndFileWarning: return "sndFileWarning" + case .callInvitation: return "callInvitation" + case .callOffer: return "callOffer" + case .callAnswer: return "callAnswer" + case .callExtraInfo: return "callExtraInfo" + case .callEnded: return "callEnded" + case .callInvitations: return "callInvitations" + case .ntfTokenStatus: return "ntfTokenStatus" + case .ntfToken: return "ntfToken" + case .ntfConns: return "ntfConns" + case .connNtfMessages: return "connNtfMessages" + case .ntfMessage: return "ntfMessage" + case .contactConnectionDeleted: return "contactConnectionDeleted" + case .contactDisabled: return "contactDisabled" + 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 .contactPQEnabled: return "contactPQEnabled" + case .versionInfo: return "versionInfo" + case .cmdOk: return "cmdOk" + case .agentSubsTotal: return "agentSubsTotal" + case .agentServersSummary: return "agentServersSummary" + case .agentSubsSummary: return "agentSubsSummary" + case .chatCmdError: return "chatCmdError" + case .chatError: return "chatError" + case .archiveExported: return "archiveExported" + case .archiveImported: return "archiveImported" + case .appSettings: return "appSettings" + } + } + } + + var details: String { + get { + switch self { + case let .response(_, json): return json + case let .activeUser(user): return String(describing: user) + case let .usersList(users): return String(describing: users) + case .chatStarted: return noDetails + case .chatRunning: return noDetails + case .chatStopped: return noDetails + case .chatSuspended: return noDetails + case let .apiChats(u, chats): return withUser(u, String(describing: chats)) + case let .apiChat(u, chat, navInfo): return withUser(u, "chat: \(String(describing: chat))\nnavInfo: \(String(describing: navInfo))") + case let .chatTags(u, userTags): return withUser(u, "userTags: \(String(describing: userTags))") + case let .chatItemInfo(u, chatItem, chatItemInfo): return withUser(u, "chatItem: \(String(describing: chatItem))\nchatItemInfo: \(String(describing: chatItemInfo))") + case let .serverTestResult(u, server, testFailure): return withUser(u, "server: \(server)\nresult: \(String(describing: testFailure))") + case let .serverOperatorConditions(conditions): return "conditions: \(String(describing: conditions))" + case let .userServers(u, userServers): return withUser(u, "userServers: \(String(describing: userServers))") + case let .userServersValidation(u, serverErrors): return withUser(u, "serverErrors: \(String(describing: serverErrors))") + case let .usageConditions(usageConditions, _, acceptedConditions): return "usageConditions: \(String(describing: usageConditions))\nacceptedConditions: \(String(describing: acceptedConditions))" + 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 .groupMemberInfo(u, groupInfo, member, connectionStats_): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats_: \(String(describing: connectionStats_))") + case let .queueInfo(u, rcvMsgInfo, queueInfo): + let msgInfo = if let info = rcvMsgInfo { encodeJSON(info) } else { "none" } + return withUser(u, "rcvMsgInfo: \(msgInfo)\nqueueInfo: \(encodeJSON(queueInfo))") + 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))") + case let .contactSwitchAborted(u, contact, connectionStats): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))") + case let .groupMemberSwitchAborted(u, groupInfo, member, connectionStats): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats: \(String(describing: connectionStats))") + case let .contactSwitch(u, contact, switchProgress): return withUser(u, "contact: \(String(describing: contact))\nswitchProgress: \(String(describing: switchProgress))") + case let .groupMemberSwitch(u, groupInfo, member, switchProgress): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nswitchProgress: \(String(describing: switchProgress))") + case let .contactRatchetSyncStarted(u, contact, connectionStats): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))") + case let .groupMemberRatchetSyncStarted(u, groupInfo, member, connectionStats): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats: \(String(describing: connectionStats))") + case let .contactRatchetSync(u, contact, ratchetSyncProgress): return withUser(u, "contact: \(String(describing: contact))\nratchetSyncProgress: \(String(describing: ratchetSyncProgress))") + case let .groupMemberRatchetSync(u, groupInfo, member, ratchetSyncProgress): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nratchetSyncProgress: \(String(describing: ratchetSyncProgress))") + case let .contactVerificationReset(u, contact): return withUser(u, "contact: \(String(describing: contact))") + case let .groupMemberVerificationReset(u, groupInfo, member): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))") + case let .contactCode(u, contact, connectionCode): return withUser(u, "contact: \(String(describing: contact))\nconnectionCode: \(connectionCode)") + case let .groupMemberCode(u, groupInfo, member, connectionCode): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionCode: \(connectionCode)") + case let .connectionVerified(u, verified, expectedCode): return withUser(u, "verified: \(verified)\nconnectionCode: \(expectedCode)") + case let .tagsUpdated(u, userTags, chatTags): return withUser(u, "userTags: \(String(describing: userTags))\nchatTags: \(String(describing: chatTags))") + case let .invitation(u, connLinkInvitation, connection): return withUser(u, "connLinkInvitation: \(connLinkInvitation)\nconnection: \(connection)") + case let .connectionIncognitoUpdated(u, toConnection): return withUser(u, String(describing: toConnection)) + case let .connectionUserChanged(u, fromConnection, toConnection, newUser): return withUser(u, "fromConnection: \(String(describing: fromConnection))\ntoConnection: \(String(describing: toConnection))\nnewUserId: \(String(describing: newUser.userId))") + case let .connectionPlan(u, connLink, connectionPlan): return withUser(u, "connLink: \(String(describing: connLink))\nconnectionPlan: \(String(describing: connectionPlan))") + case let .sentConfirmation(u, connection): return withUser(u, String(describing: connection)) + case let .sentInvitation(u, connection): return withUser(u, String(describing: connection)) + case let .sentInvitationToContact(u, contact, _): return withUser(u, String(describing: contact)) + case let .contactAlreadyExists(u, contact): return withUser(u, String(describing: contact)) + case let .contactDeleted(u, contact): return withUser(u, String(describing: contact)) + case let .contactDeletedByContact(u, contact): return withUser(u, String(describing: contact)) + case let .chatCleared(u, chatInfo): return withUser(u, String(describing: chatInfo)) + case .userProfileNoChange: return noDetails + case let .userProfileUpdated(u, _, toProfile, _): return withUser(u, String(describing: toProfile)) + case let .userPrivacy(u, updatedUser): return withUser(u, String(describing: updatedUser)) + case let .contactAliasUpdated(u, toContact): return withUser(u, String(describing: toContact)) + case let .groupAliasUpdated(u, toGroup): return withUser(u, String(describing: toGroup)) + case let .connectionAliasUpdated(u, toConnection): return withUser(u, String(describing: toConnection)) + case let .contactPrefsUpdated(u, fromContact, toContact): return withUser(u, "fromContact: \(String(describing: fromContact))\ntoContact: \(String(describing: toContact))") + case let .userContactLink(u, contactLink): return withUser(u, contactLink.responseDetails) + case let .userContactLinkUpdated(u, contactLink): return withUser(u, contactLink.responseDetails) + case let .userContactLinkCreated(u, connLink): return withUser(u, String(describing: connLink)) + case .userContactLinkDeleted: return noDetails + case let .contactConnected(u, contact, _): return withUser(u, String(describing: contact)) + case let .contactConnecting(u, contact): return withUser(u, String(describing: contact)) + case let .contactSndReady(u, contact): return withUser(u, String(describing: contact)) + case let .receivedContactRequest(u, contactRequest): return withUser(u, String(describing: contactRequest)) + 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 .networkStatus(status, conns): return "networkStatus: \(String(describing: status))\nconnections: \(String(describing: conns))" + case let .networkStatuses(u, statuses): return withUser(u, String(describing: statuses)) + case let .groupSubscribed(u, groupInfo): return withUser(u, String(describing: groupInfo)) + case let .memberSubErrors(u, memberSubErrors): return withUser(u, String(describing: memberSubErrors)) + case let .groupEmpty(u, groupInfo): return withUser(u, String(describing: groupInfo)) + case .userContactLinkSubscribed: return noDetails + case let .newChatItems(u, chatItems): + let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") + return withUser(u, itemsString) + case let .groupChatItemsDeleted(u, gInfo, chatItemIDs, byUser, member_): + return withUser(u, "chatItemIDs: \(String(describing: chatItemIDs))\ngroupInfo: \(String(describing: gInfo))\nbyUser: \(byUser)\nmember_: \(String(describing: member_))") + case let .forwardPlan(u, chatItemIds, forwardConfirmation): return withUser(u, "items: \(chatItemIds) forwardConfirmation: \(String(describing: forwardConfirmation))") + case let .chatItemsStatusesUpdated(u, chatItems): + let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") + return withUser(u, itemsString) + case let .chatItemUpdated(u, chatItem): return withUser(u, String(describing: chatItem)) + case let .chatItemNotChanged(u, chatItem): return withUser(u, String(describing: chatItem)) + case let .chatItemReaction(u, added, reaction): return withUser(u, "added: \(added)\n\(String(describing: reaction))") + case let .reactionMembers(u, reaction): return withUser(u, "memberReactions: \(String(describing: reaction))") + case let .chatItemsDeleted(u, items, byUser): + let itemsString = items.map { item in + "deletedChatItem:\n\(String(describing: item.deletedChatItem))\ntoChatItem:\n\(String(describing: item.toChatItem))" }.joined(separator: "\n") + return withUser(u, itemsString + "\nbyUser: \(byUser)") + case let .contactsList(u, contacts): return withUser(u, String(describing: contacts)) + 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 .businessLinkConnecting(u, groupInfo, hostMember, fromContact): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(String(describing: hostMember))\nfromContact: \(String(describing: fromContact))") + case let .userDeletedMembers(u, groupInfo, members, withMessages): return withUser(u, "groupInfo: \(groupInfo)\nmembers: \(members)\nwithMessages: \(withMessages)") + case let .leftMemberUser(u, groupInfo): return withUser(u, String(describing: groupInfo)) + case let .groupMembers(u, group): return withUser(u, String(describing: group)) + case let .receivedGroupInvitation(u, groupInfo, contact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\ncontact: \(contact)\nmemberRole: \(memberRole)") + case let .groupDeletedUser(u, groupInfo): return withUser(u, String(describing: groupInfo)) + case let .joinedGroupMemberConnecting(u, groupInfo, hostMember, member): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(hostMember)\nmember: \(member)") + case let .memberRole(u, groupInfo, byMember, member, fromRole, toRole): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\nmember: \(member)\nfromRole: \(fromRole)\ntoRole: \(toRole)") + case let .membersRoleUser(u, groupInfo, members, toRole): return withUser(u, "groupInfo: \(groupInfo)\nmembers: \(members)\ntoRole: \(toRole)") + case let .memberBlockedForAll(u, groupInfo, byMember, member, blocked): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\nmember: \(member)\nblocked: \(blocked)") + case let .membersBlockedForAllUser(u, groupInfo, members, blocked): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(members)\nblocked: \(blocked)") + case let .deletedMemberUser(u, groupInfo, member, withMessages): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)\nwithMessages: \(withMessages)") + case let .deletedMember(u, groupInfo, byMember, deletedMember, withMessages): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\ndeletedMember: \(deletedMember)\nwithMessages: \(withMessages)") + case let .leftMember(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") + case let .groupDeleted(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") + case let .contactsMerged(u, intoContact, mergedContact): return withUser(u, "intoContact: \(intoContact)\nmergedContact: \(mergedContact)") + case let .groupInvitation(u, groupInfo): return withUser(u, String(describing: groupInfo)) + case let .userJoinedGroup(u, groupInfo): return withUser(u, String(describing: groupInfo)) + case let .joinedGroupMember(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") + case let .connectedToGroupMember(u, groupInfo, member, memberContact): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)\nmemberContact: \(String(describing: memberContact))") + case let .groupRemoved(u, groupInfo): return withUser(u, String(describing: groupInfo)) + case let .groupUpdated(u, toGroup): return withUser(u, String(describing: toGroup)) + case let .groupLinkCreated(u, groupInfo, connLinkContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnLinkContact: \(connLinkContact)\nmemberRole: \(memberRole)") + case let .groupLink(u, groupInfo, connLinkContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnLinkContact: \(connLinkContact)\nmemberRole: \(memberRole)") + case let .groupLinkDeleted(u, groupInfo): return withUser(u, String(describing: groupInfo)) + case let .newMemberContact(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") + case let .newMemberContactSentInv(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") + case let .newMemberContactReceivedInv(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") + case let .rcvFileAccepted(u, chatItem): return withUser(u, String(describing: chatItem)) + case .rcvFileAcceptedSndCancelled: return noDetails + case let .standaloneFileInfo(fileMeta): return String(describing: fileMeta) + case .rcvStandaloneFileCreated: return noDetails + case let .rcvFileStart(u, chatItem): return withUser(u, String(describing: chatItem)) + case let .rcvFileProgressXFTP(u, chatItem, receivedSize, totalSize, _): return withUser(u, "chatItem: \(String(describing: chatItem))\nreceivedSize: \(receivedSize)\ntotalSize: \(totalSize)") + case let .rcvStandaloneFileComplete(u, targetPath, _): return withUser(u, targetPath) + case let .rcvFileComplete(u, chatItem): return withUser(u, String(describing: chatItem)) + case let .rcvFileCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .rcvFileSndCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .rcvFileError(u, chatItem, agentError, _): return withUser(u, "agentError: \(String(describing: agentError))\nchatItem: \(String(describing: chatItem))") + case let .rcvFileWarning(u, chatItem, agentError, _): return withUser(u, "agentError: \(String(describing: agentError))\nchatItem: \(String(describing: chatItem))") + case let .sndFileStart(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndFileComplete(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndFileCancelled(u, chatItem, _, _): return withUser(u, String(describing: chatItem)) + case .sndStandaloneFileCreated: return noDetails + case let .sndFileStartXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndFileRcvCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndFileProgressXFTP(u, chatItem, _, sentSize, totalSize): return withUser(u, "chatItem: \(String(describing: chatItem))\nsentSize: \(sentSize)\ntotalSize: \(totalSize)") + case let .sndFileRedirectStartXFTP(u, _, redirectMeta): return withUser(u, String(describing: redirectMeta)) + case let .sndFileCompleteXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndStandaloneFileComplete(u, _, rcvURIs): return withUser(u, String(rcvURIs.count)) + case let .sndFileCancelledXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndFileError(u, chatItem, _, err): return withUser(u, "error: \(String(describing: err))\nchatItem: \(String(describing: chatItem))") + case let .sndFileWarning(u, chatItem, _, err): return withUser(u, "error: \(String(describing: err))\nchatItem: \(String(describing: chatItem))") + case let .callInvitation(inv): return String(describing: inv) + case let .callOffer(u, contact, callType, offer, sharedKey, askConfirmation): return withUser(u, "contact: \(contact.id)\ncallType: \(String(describing: callType))\nsharedKey: \(sharedKey ?? "")\naskConfirmation: \(askConfirmation)\noffer: \(String(describing: offer))") + case let .callAnswer(u, contact, answer): return withUser(u, "contact: \(contact.id)\nanswer: \(String(describing: answer))") + case let .callExtraInfo(u, contact, extraInfo): return withUser(u, "contact: \(contact.id)\nextraInfo: \(String(describing: extraInfo))") + case let .callEnded(u, contact): return withUser(u, "contact: \(contact.id)") + case let .callInvitations(invs): return String(describing: invs) + case let .ntfTokenStatus(status): return String(describing: status) + case let .ntfToken(token, status, ntfMode, ntfServer): return "token: \(token)\nstatus: \(status.rawValue)\nntfMode: \(ntfMode.rawValue)\nntfServer: \(ntfServer)" + case let .ntfConns(ntfConns): return String(describing: ntfConns) + case let .connNtfMessages(receivedMsgs): return "receivedMsgs: \(String(describing: receivedMsgs))" + case let .ntfMessage(u, connEntity, ntfMessage): return withUser(u, "connEntity: \(String(describing: connEntity))\nntfMessage: \(String(describing: ntfMessage))") + case let .contactConnectionDeleted(u, connection): return withUser(u, String(describing: connection)) + case let .contactDisabled(u, contact): return withUser(u, String(describing: contact)) + 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 let .remoteCtrlStopped(rcsState, rcStopReason): return "rcsState: \(String(describing: rcsState))\nrcStopReason: \(String(describing: rcStopReason))" + case let .contactPQEnabled(u, contact, pqEnabled): return withUser(u, "contact: \(String(describing: contact))\npqEnabled: \(pqEnabled)") + 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 .agentSubsTotal(u, subsTotal, hasSession): return withUser(u, "subsTotal: \(String(describing: subsTotal))\nhasSession: \(hasSession)") + case let .agentServersSummary(u, serversSummary): return withUser(u, String(describing: serversSummary)) + case let .agentSubsSummary(u, subsSummary): return withUser(u, String(describing: subsSummary)) + case let .chatCmdError(u, chatError): return withUser(u, String(describing: chatError)) + case let .chatError(u, chatError): return withUser(u, String(describing: chatError)) + case let .archiveExported(archiveErrors): return String(describing: archiveErrors) + case let .archiveImported(archiveErrors): return String(describing: archiveErrors) + case let .appSettings(appSettings): return String(describing: appSettings) + } + } + } + + private var noDetails: String { get { "\(responseType): no details" } } + + static func chatResponse(_ s: String) -> ChatResponse { + let d = s.data(using: .utf8)! + // TODO is there a way to do it without copying the data? e.g: + // let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson)) + // let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free) + do { + let r = try callWithLargeStack { + try jsonDecoder.decode(APIResponse.self, from: d) + } + return r.resp + } catch { + logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)") + } + + var type: String? + var json: String? + if let j = try? JSONSerialization.jsonObject(with: d) as? NSDictionary { + if let jResp = j["resp"] as? NSDictionary, jResp.count == 1 || jResp.count == 2 { + type = jResp.allKeys[0] as? String + if jResp.count == 2 && type == "_owsf" { + type = jResp.allKeys[1] as? String + } + if type == "apiChats" { + if let r = parseApiChats(jResp) { + return .apiChats(user: r.user, chats: r.chats) + } + } else if type == "apiChat" { + if let jApiChat = jResp["apiChat"] as? NSDictionary, + let user: UserRef = try? decodeObject(jApiChat["user"] as Any), + let jChat = jApiChat["chat"] as? NSDictionary, + let (chat, navInfo) = try? parseChatData(jChat, jApiChat["navInfo"] as? NSDictionary) { + return .apiChat(user: user, chat: chat, navInfo: navInfo) + } + } else if type == "chatCmdError" { + if let jError = jResp["chatCmdError"] as? NSDictionary { + return .chatCmdError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) + } + } else if type == "chatError" { + if let jError = jResp["chatError"] as? NSDictionary { + return .chatError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) + } + } + } + json = serializeJSON(j, options: .prettyPrinted) + } + return ChatResponse.response(type: type ?? "invalid", json: json ?? s) + } + + var chatError: ChatError? { + switch self { + case let .chatCmdError(_, error): error + case let .chatError(_, error): error + default: nil + } + } + + var chatErrorType: ChatErrorType? { + switch self { + case let .chatCmdError(_, .error(error)): error + case let .chatError(_, .error(error)): error + default: nil + } + } +} + +private let largeStackSize: Int = 2 * 1024 * 1024 + +private func callWithLargeStack(_ f: @escaping () throws -> T) throws -> T { + let semaphore = DispatchSemaphore(value: 0) + var result: Result? + let thread = Thread { + do { + result = .success(try f()) + } catch { + result = .failure(error) + } + semaphore.signal() + } + + thread.stackSize = largeStackSize + thread.qualityOfService = Thread.current.qualityOfService + thread.start() + + semaphore.wait() + + switch result! { + case let .success(r): return r + case let .failure(e): throw e + } +} + +struct NewUser: Encodable { + var profile: Profile? + var pastTimestamp: Bool +} + +enum ChatPagination { + static let INITIAL_COUNT = 75 + static let PRELOAD_COUNT = 100 + static let UNTIL_PRELOAD_COUNT = 50 + + case last(count: Int) + case after(chatItemId: Int64, count: Int) + case before(chatItemId: Int64, count: Int) + case around(chatItemId: Int64, count: Int) + case initial(count: Int) + + var cmdString: String { + switch self { + case let .last(count): return "count=\(count)" + case let .after(chatItemId, count): return "after=\(chatItemId) count=\(count)" + case let .before(chatItemId, count): return "before=\(chatItemId) count=\(count)" + case let .around(chatItemId, count): return "around=\(chatItemId) count=\(count)" + case let .initial(count): return "initial=\(count)" + } + } +} + +enum ConnectionPlan: Decodable, Hashable { + case invitationLink(invitationLinkPlan: InvitationLinkPlan) + case contactAddress(contactAddressPlan: ContactAddressPlan) + case groupLink(groupLinkPlan: GroupLinkPlan) + case error(chatError: ChatError) +} + +enum InvitationLinkPlan: Decodable, Hashable { + case ok + case ownLink + case connecting(contact_: Contact?) + case known(contact: Contact) +} + +enum ContactAddressPlan: Decodable, Hashable { + case ok + case ownLink + case connectingConfirmReconnect + case connectingProhibit(contact: Contact) + case known(contact: Contact) + case contactViaAddress(contact: Contact) +} + +enum GroupLinkPlan: Decodable, Hashable { + case ok + case ownLink(groupInfo: GroupInfo) + case connectingConfirmReconnect + case connectingProhibit(groupInfo_: GroupInfo?) + case known(groupInfo: GroupInfo) +} + +struct ChatTagData: Encodable { + var emoji: String? + var text: String +} + +struct UpdatedMessage: Encodable { + var msgContent: MsgContent + var mentions: [String: Int64] + + var cmdString: String { + "json \(encodeJSON(self))" + } +} + +enum ChatDeleteMode: Codable { + case full(notify: Bool) + case entity(notify: Bool) + case messages + + var cmdString: String { + switch self { + case let .full(notify): "full notify=\(onOff(notify))" + case let .entity(notify): "entity notify=\(onOff(notify))" + case .messages: "messages" + } + } + + var isEntity: Bool { + switch self { + case .entity: return true + default: return false + } + } +} + +enum NetworkStatus: Decodable, Equatable { + case unknown + case connected + case disconnected + case error(connectionError: String) + + var statusString: LocalizedStringKey { + switch self { + case .connected: "connected" + case .error: "error" + default: "connecting" + } + } + + var statusExplanation: LocalizedStringKey { + switch self { + case .connected: "You are connected to the server used to receive messages from this contact." + case let .error(err): "Trying to connect to the server used to receive messages from this contact (error: \(err))." + default: "Trying to connect to the server used to receive messages from this contact." + } + } + + var imageName: String { + switch self { + case .unknown: "circle.dotted" + case .connected: "circle.fill" + case .disconnected: "ellipsis.circle.fill" + case .error: "exclamationmark.circle.fill" + } + } +} + +enum ForwardConfirmation: Decodable, Hashable { + case filesNotAccepted(fileIds: [Int64]) + case filesInProgress(filesCount: Int) + case filesMissing(filesCount: Int) + case filesFailed(filesCount: Int) +} + +struct ConnNetworkStatus: Decodable { + var agentConnId: String + var networkStatus: NetworkStatus +} + +struct UserMsgReceiptSettings: Codable { + var enable: Bool + var clearOverrides: Bool +} + + +struct UserContactLink: Decodable, Hashable { + var connLinkContact: CreatedConnLink + var autoAccept: AutoAccept? + + var responseDetails: String { + "connLinkContact: \(connLinkContact)\nautoAccept: \(AutoAccept.cmdString(autoAccept))" + } +} + +struct AutoAccept: Codable, Hashable { + var businessAddress: Bool + var acceptIncognito: Bool + var autoReply: MsgContent? + + static func cmdString(_ autoAccept: AutoAccept?) -> String { + guard let autoAccept = autoAccept else { return "off" } + var s = "on" + if autoAccept.acceptIncognito { + s += " incognito=on" + } else if autoAccept.businessAddress { + s += " business" + } + guard let msg = autoAccept.autoReply else { return s } + return s + " " + msg.cmdString + } +} + +struct DeviceToken: Decodable { + var pushProvider: PushProvider + var token: String + + var cmdString: String { + "\(pushProvider) \(token)" + } +} + +enum PushEnvironment: String { + case development + case production +} + +enum PushProvider: String, Decodable { + case apns_dev + case apns_prod + + init(env: PushEnvironment) { + switch env { + case .development: self = .apns_dev + case .production: self = .apns_prod + } + } +} + +// This notification mode is for app core, UI uses AppNotificationsMode.off to mean completely disable, +// and .local for periodic background checks +enum NotificationsMode: String, Decodable, SelectableItem { + case off = "OFF" + case periodic = "PERIODIC" + case instant = "INSTANT" + + var label: LocalizedStringKey { + switch self { + case .off: "No push server" + case .periodic: "Periodic" + case .instant: "Instant" + } + } + + var icon: String { + switch self { + case .off: return "arrow.clockwise" + case .periodic: return "timer" + case .instant: return "bolt" + } + } + + var id: String { self.rawValue } + + static var values: [NotificationsMode] = [.instant, .periodic, .off] +} + +enum PrivacyChatListOpenLinksMode: String, CaseIterable, Codable, RawRepresentable, Identifiable { + case yes + case no + case ask + + var id: Self { self } + + var text: LocalizedStringKey { + switch self { + case .yes: return "Yes" + case .no: return "No" + case .ask: return "Ask" + } + } +} + +struct RemoteCtrlInfo: Decodable { + var remoteCtrlId: Int64 + var ctrlDeviceName: String + var sessionState: RemoteCtrlSessionState? + + var deviceViewName: String { + ctrlDeviceName == "" ? "\(remoteCtrlId)" : ctrlDeviceName + } +} + +enum RemoteCtrlSessionState: Decodable { + case starting + case searching + case connecting + case pendingConfirmation(sessionCode: String) + case connected(sessionCode: String) +} + +enum RemoteCtrlStopReason: Decodable { + case discoveryFailed(chatError: ChatError) + case connectionFailed(chatError: ChatError) + case setupFailed(chatError: ChatError) + case disconnected +} + +struct CtrlAppInfo: Decodable { + var appVersionRange: AppVersionRange + var deviceName: String +} + +struct AppVersionRange: Decodable { + var minVersion: String + var maxVersion: String +} + +struct CoreVersionInfo: Decodable { + var version: String + var simplexmqVersion: String + var simplexmqCommit: String +} + +struct ArchiveConfig: Encodable { + var archivePath: String + var disableCompression: Bool? +} + +struct DBEncryptionConfig: Codable { + var currentKey: String + var newKey: String +} + +enum OperatorTag: String, Codable { + case simplex = "simplex" + case flux = "flux" +} + +struct ServerOperatorInfo { + var description: [String] + var website: URL + var selfhost: (text: String, link: URL)? = nil + var logo: String + var largeLogo: String + var logoDarkMode: String + var largeLogoDarkMode: String +} + +let operatorsInfo: Dictionary = [ + .simplex: ServerOperatorInfo( + description: [ + "SimpleX Chat is the first communication network that has no user profile IDs of any kind, not even random numbers or identity keys.", + "SimpleX Chat Ltd develops the communication software for SimpleX network." + ], + website: URL(string: "https://simplex.chat")!, + logo: "decentralized", + largeLogo: "logo", + logoDarkMode: "decentralized-light", + largeLogoDarkMode: "logo-light" + ), + .flux: ServerOperatorInfo( + description: [ + "Flux is the largest decentralized cloud, based on a global network of user-operated nodes.", + "Flux offers a powerful, scalable, and affordable cutting edge technology platform for all.", + "Flux operates servers in SimpleX network to improve its privacy and decentralization." + ], + website: URL(string: "https://runonflux.com")!, + selfhost: (text: "Self-host SimpleX servers on Flux", link: URL(string: "https://home.runonflux.io/apps/marketplace?q=simplex")!), + logo: "flux_logo_symbol", + largeLogo: "flux_logo", + logoDarkMode: "flux_logo_symbol", + largeLogoDarkMode: "flux_logo-light" + ), +] + +struct UsageConditions: Decodable { + var conditionsId: Int64 + var conditionsCommit: String + var notifiedAt: Date? + var createdAt: Date + + static var sampleData = UsageConditions( + conditionsId: 1, + conditionsCommit: "11a44dc1fd461a93079f897048b46998db55da5c", + notifiedAt: nil, + createdAt: Date.now + ) +} + +enum UsageConditionsAction: Decodable { + case review(operators: [ServerOperator], deadline: Date?, showNotice: Bool) + case accepted(operators: [ServerOperator]) + + var showNotice: Bool { + switch self { + case let .review(_, _, showNotice): showNotice + case .accepted: false + } + } +} + +struct ServerOperatorConditions: Decodable { + var serverOperators: [ServerOperator] + var currentConditions: UsageConditions + var conditionsAction: UsageConditionsAction? + + static var empty = ServerOperatorConditions( + serverOperators: [], + currentConditions: UsageConditions(conditionsId: 0, conditionsCommit: "empty", notifiedAt: nil, createdAt: .now), + conditionsAction: nil + ) +} + +enum ConditionsAcceptance: Equatable, Codable, Hashable { + case accepted(acceptedAt: Date?, autoAccepted: Bool) + // If deadline is present, it means there's a grace period to review and accept conditions during which user can continue to use the operator. + // No deadline indicates it's required to accept conditions for the operator to start using it. + case required(deadline: Date?) + + var conditionsAccepted: Bool { + switch self { + case .accepted: true + case .required: false + } + } + + var usageAllowed: Bool { + switch self { + case .accepted: true + case let .required(deadline): deadline != nil + } + } +} + +struct ServerOperator: Identifiable, Equatable, Codable { + var operatorId: Int64 + var operatorTag: OperatorTag? + var tradeName: String + var legalName: String? + var serverDomains: [String] + var conditionsAcceptance: ConditionsAcceptance + var enabled: Bool + var smpRoles: ServerRoles + var xftpRoles: ServerRoles + + var id: Int64 { operatorId } + + static func == (l: ServerOperator, r: ServerOperator) -> Bool { + l.operatorId == r.operatorId && l.operatorTag == r.operatorTag && l.tradeName == r.tradeName && l.legalName == r.legalName && + l.serverDomains == r.serverDomains && l.conditionsAcceptance == r.conditionsAcceptance && l.enabled == r.enabled && + l.smpRoles == r.smpRoles && l.xftpRoles == r.xftpRoles + } + + var legalName_: String { + legalName ?? tradeName + } + + var info: ServerOperatorInfo { + return if let operatorTag = operatorTag { + operatorsInfo[operatorTag] ?? ServerOperator.dummyOperatorInfo + } else { + ServerOperator.dummyOperatorInfo + } + } + + static let dummyOperatorInfo = ServerOperatorInfo( + description: ["Default"], + website: URL(string: "https://simplex.chat")!, + logo: "decentralized", + largeLogo: "logo", + logoDarkMode: "decentralized-light", + largeLogoDarkMode: "logo-light" + ) + + func logo(_ colorScheme: ColorScheme) -> String { + colorScheme == .light ? info.logo : info.logoDarkMode + } + + func largeLogo(_ colorScheme: ColorScheme) -> String { + colorScheme == .light ? info.largeLogo : info.largeLogoDarkMode + } + + static var sampleData1 = ServerOperator( + operatorId: 1, + operatorTag: .simplex, + tradeName: "SimpleX Chat", + legalName: "SimpleX Chat Ltd", + serverDomains: ["simplex.im"], + conditionsAcceptance: .accepted(acceptedAt: nil, autoAccepted: false), + enabled: true, + smpRoles: ServerRoles(storage: true, proxy: true), + xftpRoles: ServerRoles(storage: true, proxy: true) + ) +} + +struct ServerRoles: Equatable, Codable { + var storage: Bool + var proxy: Bool +} + +struct UserOperatorServers: Identifiable, Equatable, Codable { + var `operator`: ServerOperator? + var smpServers: [UserServer] + var xftpServers: [UserServer] + + var id: String { + if let op = self.operator { + "\(op.operatorId)" + } else { + "nil operator" + } + } + + var operator_: ServerOperator { + get { + self.operator ?? ServerOperator( + operatorId: 0, + operatorTag: nil, + tradeName: "", + legalName: "", + serverDomains: [], + conditionsAcceptance: .accepted(acceptedAt: nil, autoAccepted: false), + enabled: false, + smpRoles: ServerRoles(storage: true, proxy: true), + xftpRoles: ServerRoles(storage: true, proxy: true) + ) + } + set { `operator` = newValue } + } + + static var sampleData1 = UserOperatorServers( + operator: ServerOperator.sampleData1, + smpServers: [UserServer.sampleData.preset], + xftpServers: [UserServer.sampleData.xftpPreset] + ) + + static var sampleDataNilOperator = UserOperatorServers( + operator: nil, + smpServers: [UserServer.sampleData.preset], + xftpServers: [UserServer.sampleData.xftpPreset] + ) +} + +enum UserServersError: Decodable { + case noServers(protocol: ServerProtocol, user: UserRef?) + case storageMissing(protocol: ServerProtocol, user: UserRef?) + case proxyMissing(protocol: ServerProtocol, user: UserRef?) + case duplicateServer(protocol: ServerProtocol, duplicateServer: String, duplicateHost: String) + + var globalError: String? { + switch self { + case let .noServers(`protocol`, _): + switch `protocol` { + case .smp: return globalSMPError + case .xftp: return globalXFTPError + } + case let .storageMissing(`protocol`, _): + switch `protocol` { + case .smp: return globalSMPError + case .xftp: return globalXFTPError + } + case let .proxyMissing(`protocol`, _): + switch `protocol` { + case .smp: return globalSMPError + case .xftp: return globalXFTPError + } + default: return nil + } + } + + var globalSMPError: String? { + switch self { + case let .noServers(.smp, user): + let text = NSLocalizedString("No message servers.", comment: "servers error") + if let user = user { + return userStr(user) + " " + text + } else { + return text + } + case let .storageMissing(.smp, user): + let text = NSLocalizedString("No servers to receive messages.", comment: "servers error") + if let user = user { + return userStr(user) + " " + text + } else { + return text + } + case let .proxyMissing(.smp, user): + let text = NSLocalizedString("No servers for private message routing.", comment: "servers error") + if let user = user { + return userStr(user) + " " + text + } else { + return text + } + default: + return nil + } + } + + var globalXFTPError: String? { + switch self { + case let .noServers(.xftp, user): + let text = NSLocalizedString("No media & file servers.", comment: "servers error") + if let user = user { + return userStr(user) + " " + text + } else { + return text + } + case let .storageMissing(.xftp, user): + let text = NSLocalizedString("No servers to send files.", comment: "servers error") + if let user = user { + return userStr(user) + " " + text + } else { + return text + } + case let .proxyMissing(.xftp, user): + let text = NSLocalizedString("No servers to receive files.", comment: "servers error") + if let user = user { + return userStr(user) + " " + text + } else { + return text + } + default: + return nil + } + } + + private func userStr(_ user: UserRef) -> String { + String.localizedStringWithFormat(NSLocalizedString("For chat profile %@:", comment: "servers error"), user.localDisplayName) + } +} + +struct UserServer: Identifiable, Equatable, Codable, Hashable { + var serverId: Int64? + var server: String + var preset: Bool + var tested: Bool? + var enabled: Bool + var deleted: Bool + var createdAt = Date() + + static func == (l: UserServer, r: UserServer) -> Bool { + l.serverId == r.serverId && l.server == r.server && l.preset == r.preset && l.tested == r.tested && + l.enabled == r.enabled && l.deleted == r.deleted + } + + var id: String { "\(server) \(createdAt)" } + + static var empty = UserServer(serverId: nil, server: "", preset: false, tested: nil, enabled: false, deleted: false) + + var isEmpty: Bool { + server.trimmingCharacters(in: .whitespaces) == "" + } + + struct SampleData { + var preset: UserServer + var custom: UserServer + var untested: UserServer + var xftpPreset: UserServer + } + + static var sampleData = SampleData( + preset: UserServer( + serverId: 1, + server: "smp://abcd@smp8.simplex.im", + preset: true, + tested: true, + enabled: true, + deleted: false + ), + custom: UserServer( + serverId: 2, + server: "smp://abcd@smp9.simplex.im", + preset: false, + tested: false, + enabled: false, + deleted: false + ), + untested: UserServer( + serverId: 3, + server: "smp://abcd@smp10.simplex.im", + preset: false, + tested: nil, + enabled: true, + deleted: false + ), + xftpPreset: UserServer( + serverId: 4, + server: "xftp://abcd@xftp8.simplex.im", + preset: true, + tested: true, + enabled: true, + deleted: false + ) + ) + + enum CodingKeys: CodingKey { + case serverId + case server + case preset + case tested + case enabled + case deleted + } +} + +enum ProtocolTestStep: String, Decodable, Equatable { + case connect + case disconnect + case createQueue + case secureQueue + case deleteQueue + case createFile + case uploadFile + case downloadFile + case compareFile + case deleteFile + + var text: String { + switch self { + case .connect: return NSLocalizedString("Connect", comment: "server test step") + case .disconnect: return NSLocalizedString("Disconnect", comment: "server test step") + case .createQueue: return NSLocalizedString("Create queue", comment: "server test step") + case .secureQueue: return NSLocalizedString("Secure queue", comment: "server test step") + case .deleteQueue: return NSLocalizedString("Delete queue", comment: "server test step") + case .createFile: return NSLocalizedString("Create file", comment: "server test step") + case .uploadFile: return NSLocalizedString("Upload file", comment: "server test step") + case .downloadFile: return NSLocalizedString("Download file", comment: "server test step") + case .compareFile: return NSLocalizedString("Compare file", comment: "server test step") + case .deleteFile: return NSLocalizedString("Delete file", comment: "server test step") + } + } +} + +struct ProtocolTestFailure: Decodable, Error, Equatable { + var testStep: ProtocolTestStep + var testError: AgentErrorType + + static func == (l: ProtocolTestFailure, r: ProtocolTestFailure) -> Bool { + l.testStep == r.testStep + } + + var localizedDescription: String { + let err = String.localizedStringWithFormat(NSLocalizedString("Test failed at step %@.", comment: "server test failure"), testStep.text) + switch testError { + case .SMP(_, .AUTH): + return err + " " + NSLocalizedString("Server requires authorization to create queues, check password", comment: "server test error") + case .XFTP(.AUTH): + return err + " " + NSLocalizedString("Server requires authorization to upload, check password", comment: "server test error") + case .BROKER(_, .NETWORK): + return err + " " + NSLocalizedString("Possibly, certificate fingerprint in server address is incorrect", comment: "server test error") + default: + return err + } + } +} + +struct MigrationFileLinkData: Codable { + let networkConfig: NetworkConfig? + + struct NetworkConfig: Codable { + let socksProxy: String? + let networkProxy: NetworkProxy? + let hostMode: HostMode? + let requiredHostMode: Bool? + + func transformToPlatformSupported() -> NetworkConfig { + return if let hostMode, let requiredHostMode { + NetworkConfig( + socksProxy: nil, + networkProxy: nil, + hostMode: hostMode == .onionViaSocks ? .onionHost : hostMode, + requiredHostMode: requiredHostMode + ) + } else { self } + } + } + + func addToLink(link: String) -> String { + "\(link)&data=\(encodeJSON(self).addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)" + } + + static func readFromLink(link: String) -> MigrationFileLinkData? { +// standaloneFileInfo(link) + nil + } +} + +struct AppSettings: Codable, Equatable { + var networkConfig: NetCfg? = nil + var networkProxy: NetworkProxy? = nil + var privacyEncryptLocalFiles: Bool? = nil + var privacyAskToApproveRelays: Bool? = nil + var privacyAcceptImages: Bool? = nil + var privacyLinkPreviews: Bool? = nil + var privacyChatListOpenLinks: PrivacyChatListOpenLinksMode? = nil + var privacyShowChatPreviews: Bool? = nil + var privacySaveLastDraft: Bool? = nil + var privacyProtectScreen: Bool? = nil + var privacyMediaBlurRadius: Int? = nil + var notificationMode: AppSettingsNotificationMode? = nil + var notificationPreviewMode: NotificationPreviewMode? = nil + var webrtcPolicyRelay: Bool? = nil + var webrtcICEServers: [String]? = nil + var confirmRemoteSessions: Bool? = nil + var connectRemoteViaMulticast: Bool? = nil + var connectRemoteViaMulticastAuto: Bool? = nil + var developerTools: Bool? = nil + var confirmDBUpgrades: Bool? = nil + var androidCallOnLockScreen: AppSettingsLockScreenCalls? = nil + var iosCallKitEnabled: Bool? = nil + var iosCallKitCallsInRecents: Bool? = nil + var uiProfileImageCornerRadius: Double? = nil + var uiChatItemRoundness: Double? = nil + var uiChatItemTail: Bool? = nil + var uiColorScheme: String? = nil + var uiDarkColorScheme: String? = nil + var uiCurrentThemeIds: [String: String]? = nil + var uiThemes: [ThemeOverrides]? = nil + var oneHandUI: Bool? = nil + var chatBottomBar: Bool? = nil + + func prepareForExport() -> AppSettings { + var empty = AppSettings() + let def = AppSettings.defaults + if networkConfig != def.networkConfig { empty.networkConfig = networkConfig } + if networkProxy != def.networkProxy { empty.networkProxy = networkProxy } + if privacyEncryptLocalFiles != def.privacyEncryptLocalFiles { empty.privacyEncryptLocalFiles = privacyEncryptLocalFiles } + if privacyAskToApproveRelays != def.privacyAskToApproveRelays { empty.privacyAskToApproveRelays = privacyAskToApproveRelays } + if privacyAcceptImages != def.privacyAcceptImages { empty.privacyAcceptImages = privacyAcceptImages } + if privacyLinkPreviews != def.privacyLinkPreviews { empty.privacyLinkPreviews = privacyLinkPreviews } + if privacyChatListOpenLinks != def.privacyChatListOpenLinks { empty.privacyChatListOpenLinks = privacyChatListOpenLinks } + if privacyShowChatPreviews != def.privacyShowChatPreviews { empty.privacyShowChatPreviews = privacyShowChatPreviews } + if privacySaveLastDraft != def.privacySaveLastDraft { empty.privacySaveLastDraft = privacySaveLastDraft } + if privacyProtectScreen != def.privacyProtectScreen { empty.privacyProtectScreen = privacyProtectScreen } + if privacyMediaBlurRadius != def.privacyMediaBlurRadius { empty.privacyMediaBlurRadius = privacyMediaBlurRadius } + if notificationMode != def.notificationMode { empty.notificationMode = notificationMode } + if notificationPreviewMode != def.notificationPreviewMode { empty.notificationPreviewMode = notificationPreviewMode } + if webrtcPolicyRelay != def.webrtcPolicyRelay { empty.webrtcPolicyRelay = webrtcPolicyRelay } + if webrtcICEServers != def.webrtcICEServers { empty.webrtcICEServers = webrtcICEServers } + if confirmRemoteSessions != def.confirmRemoteSessions { empty.confirmRemoteSessions = confirmRemoteSessions } + if connectRemoteViaMulticast != def.connectRemoteViaMulticast {empty.connectRemoteViaMulticast = connectRemoteViaMulticast } + if connectRemoteViaMulticastAuto != def.connectRemoteViaMulticastAuto { empty.connectRemoteViaMulticastAuto = connectRemoteViaMulticastAuto } + if developerTools != def.developerTools { empty.developerTools = developerTools } + if confirmDBUpgrades != def.confirmDBUpgrades { empty.confirmDBUpgrades = confirmDBUpgrades } + if androidCallOnLockScreen != def.androidCallOnLockScreen { empty.androidCallOnLockScreen = androidCallOnLockScreen } + if iosCallKitEnabled != def.iosCallKitEnabled { empty.iosCallKitEnabled = iosCallKitEnabled } + if iosCallKitCallsInRecents != def.iosCallKitCallsInRecents { empty.iosCallKitCallsInRecents = iosCallKitCallsInRecents } + if uiProfileImageCornerRadius != def.uiProfileImageCornerRadius { empty.uiProfileImageCornerRadius = uiProfileImageCornerRadius } + if uiChatItemRoundness != def.uiChatItemRoundness { empty.uiChatItemRoundness = uiChatItemRoundness } + if uiChatItemTail != def.uiChatItemTail { empty.uiChatItemTail = uiChatItemTail } + if uiColorScheme != def.uiColorScheme { empty.uiColorScheme = uiColorScheme } + if uiDarkColorScheme != def.uiDarkColorScheme { empty.uiDarkColorScheme = uiDarkColorScheme } + if uiCurrentThemeIds != def.uiCurrentThemeIds { empty.uiCurrentThemeIds = uiCurrentThemeIds } + if uiThemes != def.uiThemes { empty.uiThemes = uiThemes } + if oneHandUI != def.oneHandUI { empty.oneHandUI = oneHandUI } + if chatBottomBar != def.chatBottomBar { empty.chatBottomBar = chatBottomBar } + return empty + } + + static var defaults: AppSettings { + AppSettings ( + networkConfig: NetCfg.defaults, + networkProxy: NetworkProxy.def, + privacyEncryptLocalFiles: true, + privacyAskToApproveRelays: true, + privacyAcceptImages: true, + privacyLinkPreviews: true, + privacyChatListOpenLinks: .ask, + privacyShowChatPreviews: true, + privacySaveLastDraft: true, + privacyProtectScreen: false, + privacyMediaBlurRadius: 0, + notificationMode: AppSettingsNotificationMode.instant, + notificationPreviewMode: NotificationPreviewMode.message, + webrtcPolicyRelay: true, + webrtcICEServers: [], + confirmRemoteSessions: false, + connectRemoteViaMulticast: true, + connectRemoteViaMulticastAuto: true, + developerTools: false, + confirmDBUpgrades: false, + androidCallOnLockScreen: AppSettingsLockScreenCalls.show, + iosCallKitEnabled: true, + iosCallKitCallsInRecents: false, + uiProfileImageCornerRadius: 22.5, + uiChatItemRoundness: 0.75, + uiChatItemTail: true, + uiColorScheme: DefaultTheme.SYSTEM_THEME_NAME, + uiDarkColorScheme: DefaultTheme.SIMPLEX.themeName, + uiCurrentThemeIds: nil as [String: String]?, + uiThemes: nil as [ThemeOverrides]?, + oneHandUI: true, + chatBottomBar: true + ) + } +} + +enum AppSettingsNotificationMode: String, Codable { + case off + case periodic + case instant + + func toNotificationsMode() -> NotificationsMode { + switch self { + case .instant: .instant + case .periodic: .periodic + case .off: .off + } + } + + static func from(_ mode: NotificationsMode) -> AppSettingsNotificationMode { + switch mode { + case .instant: .instant + case .periodic: .periodic + case .off: .off + } + } +} + +//enum NotificationPreviewMode: Codable { +// case hidden +// case contact +// case message +//} + +enum AppSettingsLockScreenCalls: String, Codable { + case disable + case show + case accept +} + +struct UserNetworkInfo: Codable, Equatable { + let networkType: UserNetworkType + let online: Bool +} + +enum UserNetworkType: String, Codable { + case none + case cellular + case wifi + case ethernet + case other + + var text: LocalizedStringKey { + switch self { + case .none: "No network connection" + case .cellular: "Cellular" + case .wifi: "WiFi" + case .ethernet: "Wired ethernet" + case .other: "Other" + } + } +} + +struct RcvMsgInfo: Codable { + var msgId: Int64 + var msgDeliveryId: Int64 + var msgDeliveryStatus: String + var agentMsgId: Int64 + var agentMsgMeta: String +} + +struct ServerQueueInfo: Codable { + var server: String + var rcvId: String + var sndId: String + var ntfId: String? + var status: String + var info: QueueInfo +} + +struct QueueInfo: Codable { + var qiSnd: Bool + var qiNtf: Bool + var qiSub: QSub? + var qiSize: Int + var qiMsg: MsgInfo? +} + +struct QSub: Codable { + var qSubThread: QSubThread + var qDelivered: String? +} + +enum QSubThread: String, Codable { + case noSub + case subPending + case subThread + case prohibitSub +} + +struct MsgInfo: Codable { + var msgId: String + var msgTs: Date + var msgType: MsgType +} + +enum MsgType: String, Codable { + case message + case quota +} + +struct PresentedServersSummary: Codable { + var statsStartedAt: Date + var allUsersSMP: SMPServersSummary + var allUsersXFTP: XFTPServersSummary + var currentUserSMP: SMPServersSummary + var currentUserXFTP: XFTPServersSummary +} + +struct SMPServersSummary: Codable { + var smpTotals: SMPTotals + var currentlyUsedSMPServers: [SMPServerSummary] + var previouslyUsedSMPServers: [SMPServerSummary] + var onlyProxiedSMPServers: [SMPServerSummary] +} + +struct SMPTotals: Codable { + var sessions: ServerSessions + var subs: SMPServerSubs + var stats: AgentSMPServerStatsData +} + +struct SMPServerSummary: Codable, Identifiable { + var smpServer: String + var known: Bool? + var sessions: ServerSessions? + var subs: SMPServerSubs? + var stats: AgentSMPServerStatsData? + + var id: String { smpServer } + + var hasSubs: Bool { subs != nil } + + var sessionsOrNew: ServerSessions { sessions ?? ServerSessions.newServerSessions } + + var subsOrNew: SMPServerSubs { subs ?? SMPServerSubs.newSMPServerSubs } +} + +struct ServerSessions: Codable { + var ssConnected: Int + var ssErrors: Int + var ssConnecting: Int + + static var newServerSessions = ServerSessions( + ssConnected: 0, + ssErrors: 0, + ssConnecting: 0 + ) + + var hasSess: Bool { ssConnected > 0 } +} + +struct SMPServerSubs: Codable { + var ssActive: Int + var ssPending: Int + + static var newSMPServerSubs = SMPServerSubs( + ssActive: 0, + ssPending: 0 + ) + + var total: Int { ssActive + ssPending } + + var shareOfActive: Double { + guard total != 0 else { return 0.0 } + return Double(ssActive) / Double(total) + } +} + +struct AgentSMPServerStatsData: Codable { + var _sentDirect: Int + var _sentViaProxy: Int + var _sentProxied: Int + var _sentDirectAttempts: Int + var _sentViaProxyAttempts: Int + var _sentProxiedAttempts: Int + var _sentAuthErrs: Int + var _sentQuotaErrs: Int + var _sentExpiredErrs: Int + var _sentOtherErrs: Int + var _recvMsgs: Int + var _recvDuplicates: Int + var _recvCryptoErrs: Int + var _recvErrs: Int + var _ackMsgs: Int + var _ackAttempts: Int + var _ackNoMsgErrs: Int + var _ackOtherErrs: Int + var _connCreated: Int + var _connSecured: Int + var _connCompleted: Int + var _connDeleted: Int + var _connDelAttempts: Int + var _connDelErrs: Int + var _connSubscribed: Int + var _connSubAttempts: Int + var _connSubIgnored: Int + var _connSubErrs: Int + var _ntfKey: Int + var _ntfKeyAttempts: Int + var _ntfKeyDeleted: Int + var _ntfKeyDeleteAttempts: Int +} + +struct XFTPServersSummary: Codable { + var xftpTotals: XFTPTotals + var currentlyUsedXFTPServers: [XFTPServerSummary] + var previouslyUsedXFTPServers: [XFTPServerSummary] +} + +struct XFTPTotals: Codable { + var sessions: ServerSessions + var stats: AgentXFTPServerStatsData +} + +struct XFTPServerSummary: Codable, Identifiable { + var xftpServer: String + var known: Bool? + var sessions: ServerSessions? + var stats: AgentXFTPServerStatsData? + var rcvInProgress: Bool + var sndInProgress: Bool + var delInProgress: Bool + + var id: String { xftpServer } +} + +struct AgentXFTPServerStatsData: Codable { + var _uploads: Int + var _uploadsSize: Int64 + var _uploadAttempts: Int + var _uploadErrs: Int + var _downloads: Int + var _downloadsSize: Int64 + var _downloadAttempts: Int + var _downloadAuthErrs: Int + var _downloadErrs: Int + var _deletions: Int + var _deleteAttempts: Int + var _deleteErrs: Int +} + +struct AgentNtfServerStatsData: Codable { + var _ntfCreated: Int + var _ntfCreateAttempts: Int + var _ntfChecked: Int + var _ntfCheckAttempts: Int + var _ntfDeleted: Int + var _ntfDelAttempts: Int +} diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 22d004f4d9..4e9c8ce7b6 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -11,7 +11,7 @@ import UIKit import Dispatch import BackgroundTasks import SwiftUI -import SimpleXChat +@preconcurrency import SimpleXChat private var chatController: chat_ctrl? @@ -91,7 +91,7 @@ func chatSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = logger.debug("chatSendCmd \(cmd.cmdType)") } let start = Date.now - let resp = bgTask + let resp: ChatResponse = bgTask ? withBGTask(bgDelay: bgDelay) { sendSimpleXCmd(cmd, ctrl) } : sendSimpleXCmd(cmd, ctrl) if log { @@ -115,7 +115,7 @@ func chatSendCmd(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil func chatRecvMsg(_ ctrl: chat_ctrl? = nil) async -> ChatResponse? { await withCheckedContinuation { cont in _ = withBGTask(bgDelay: msgDelay) { () -> ChatResponse? in - let resp = recvSimpleXMsg(ctrl) + let resp: ChatResponse? = recvSimpleXMsg(ctrl) cont.resume(returning: resp) return resp } @@ -123,7 +123,7 @@ func chatRecvMsg(_ ctrl: chat_ctrl? = nil) async -> ChatResponse? { } func apiGetActiveUser(ctrl: chat_ctrl? = nil) throws -> User? { - let r = chatSendCmdSync(.showActiveUser, ctrl) + let r: ChatResponse = chatSendCmdSync(.showActiveUser, ctrl) switch r { case let .activeUser(user): return user case .chatCmdError(_, .error(.noActiveUser)): return nil @@ -132,7 +132,7 @@ func apiGetActiveUser(ctrl: chat_ctrl? = nil) throws -> User? { } func apiCreateActiveUser(_ p: Profile?, pastTimestamp: Bool = false, ctrl: chat_ctrl? = nil) throws -> User { - let r = chatSendCmdSync(.createActiveUser(profile: p, pastTimestamp: pastTimestamp), ctrl) + let r: ChatResponse = chatSendCmdSync(.createActiveUser(profile: p, pastTimestamp: pastTimestamp), ctrl) if case let .activeUser(user) = r { return user } throw r } @@ -199,19 +199,19 @@ func apiUnmuteUser(_ userId: Int64) async throws -> User { } func setUserPrivacy_(_ cmd: ChatCommand) async throws -> User { - let r = await chatSendCmd(cmd) + let r: ChatResponse = await chatSendCmd(cmd) if case let .userPrivacy(_, updatedUser) = r { return updatedUser } throw r } func apiDeleteUser(_ userId: Int64, _ delSMPQueues: Bool, viewPwd: String?) async throws { - let r = await chatSendCmd(.apiDeleteUser(userId: userId, delSMPQueues: delSMPQueues, viewPwd: viewPwd)) + let r: ChatResponse = await chatSendCmd(.apiDeleteUser(userId: userId, delSMPQueues: delSMPQueues, viewPwd: viewPwd)) if case .cmdOk = r { return } throw r } func apiStartChat(ctrl: chat_ctrl? = nil) throws -> Bool { - let r = chatSendCmdSync(.startChat(mainApp: true, enableSndFiles: true), ctrl) + let r: ChatResponse = chatSendCmdSync(.startChat(mainApp: true, enableSndFiles: true), ctrl) switch r { case .chatStarted: return true case .chatRunning: return false @@ -890,7 +890,7 @@ func apiConnect_(incognito: Bool, connLink: CreatedConnLink) async -> ((ConnReqT logger.error("apiConnect: no current user") return (nil, nil) } - let r = await chatSendCmd(.apiConnect(userId: userId, incognito: incognito, connLink: connLink)) + let r: ChatResponse = await chatSendCmd(.apiConnect(userId: userId, incognito: incognito, connLink: connLink)) let m = ChatModel.shared switch r { case let .sentConfirmation(_, connection): @@ -1281,7 +1281,7 @@ func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool case let .rcvFileAccepted(_, chatItem): await chatItemSimpleUpdate(user, chatItem) default: - if let chatError = chatError(r) { + if let chatError = r.chatErrorType { switch chatError { case let .fileNotApproved(fileId, unknownServers): fileIdsToApprove.append(fileId) @@ -1348,7 +1348,7 @@ func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool ) } default: - if let chatError = chatError(errorResponse) { + if let chatError = errorResponse.chatErrorType { switch chatError { case .fileCancelled, .fileAlreadyReceiving: logger.debug("receiveFiles ignoring FileCancelled or FileAlreadyReceiving error") @@ -1635,7 +1635,7 @@ func apiLeaveGroup(_ groupId: Int64) async throws -> GroupInfo { // use ChatModel's loadGroupMembers from views func apiListMembers(_ groupId: Int64) async -> [GroupMember] { - let r = await chatSendCmd(.apiListMembers(groupId: groupId)) + let r: ChatResponse = await chatSendCmd(.apiListMembers(groupId: groupId)) if case let .groupMembers(_, group) = r { return group.members } return [] } diff --git a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift index 3e8fd131a5..dfe9e37bd6 100644 --- a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift +++ b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift @@ -752,7 +752,7 @@ private class MigrationChatReceiver { func receiveMsgLoop() async { // TODO use function that has timeout - if let msg = await chatRecvMsg(ctrl) { + if let msg: ChatResponse = await chatRecvMsg(ctrl) { Task { await TerminalItems.shared.add(.resp(.now, msg)) } diff --git a/apps/ios/SimpleX NSE/NSEAPITypes.swift b/apps/ios/SimpleX NSE/NSEAPITypes.swift new file mode 100644 index 0000000000..b1ab5e76c2 --- /dev/null +++ b/apps/ios/SimpleX NSE/NSEAPITypes.swift @@ -0,0 +1,173 @@ +// +// APITypes.swift +// SimpleX +// +// Created by EP on 01/05/2025. +// Copyright © 2025 SimpleX Chat. All rights reserved. +// + +import SimpleXChat + +enum NSEChatCommand: ChatCmdProtocol { + case showActiveUser + case startChat(mainApp: Bool, enableSndFiles: Bool) + case apiActivateChat(restoreChat: Bool) + case apiSuspendChat(timeoutMicroseconds: Int) + case apiSetNetworkConfig(networkConfig: NetCfg) + case apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String) + case apiSetEncryptLocalFiles(enable: Bool) + case apiGetNtfConns(nonce: String, encNtfInfo: String) + case apiGetConnNtfMessages(connMsgReqs: [ConnMsgReq]) + case receiveFile(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?, inline: Bool?) + case setFileToReceive(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?) + + var cmdString: String { + switch self { + case .showActiveUser: return "/u" + case let .startChat(mainApp, enableSndFiles): return "/_start main=\(onOff(mainApp)) snd_files=\(onOff(enableSndFiles))" + case let .apiActivateChat(restore): return "/_app activate restore=\(onOff(restore))" + case let .apiSuspendChat(timeoutMicroseconds): return "/_app suspend \(timeoutMicroseconds)" + case let .apiSetNetworkConfig(networkConfig): return "/_network \(encodeJSON(networkConfig))" + case let .apiSetAppFilePaths(filesFolder, tempFolder, assetsFolder): + return "/set file paths \(encodeJSON(AppFilePaths(appFilesFolder: filesFolder, appTempFolder: tempFolder, appAssetsFolder: assetsFolder)))" + case let .apiSetEncryptLocalFiles(enable): return "/_files_encrypt \(onOff(enable))" + case let .apiGetNtfConns(nonce, encNtfInfo): return "/_ntf conns \(nonce) \(encNtfInfo)" + case let .apiGetConnNtfMessages(connMsgReqs): return "/_ntf conn messages \(connMsgReqs.map { $0.cmdString }.joined(separator: ","))" + case let .receiveFile(fileId, userApprovedRelays, encrypt, inline): return "/freceive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))\(onOffParam("inline", inline))" + case let .setFileToReceive(fileId, userApprovedRelays, encrypt): return "/_set_file_to_receive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))" + } + } + + private func onOffParam(_ param: String, _ b: Bool?) -> String { + if let b = b { + " \(param)=\(onOff(b))" + } else { + "" + } + } +} + +enum NSEChatResponse: Decodable, Error, ChatRespProtocol { + case response(type: String, json: String) + case activeUser(user: User) + case chatStarted + case chatRunning + case chatSuspended + case contactConnected(user: UserRef, contact: Contact, userCustomProfile: Profile?) + case receivedContactRequest(user: UserRef, contactRequest: UserContactRequest) + case newChatItems(user: UserRef, chatItems: [AChatItem]) + case rcvFileAccepted(user: UserRef, chatItem: AChatItem) + case rcvFileSndCancelled(user: UserRef, chatItem: AChatItem, rcvFileTransfer: RcvFileTransfer) + case sndFileComplete(user: UserRef, chatItem: AChatItem, sndFileTransfer: SndFileTransfer) + case sndFileRcvCancelled(user: UserRef, chatItem_: AChatItem?, sndFileTransfer: SndFileTransfer) + case callInvitation(callInvitation: RcvCallInvitation) + case ntfConns(ntfConns: [NtfConn]) + case connNtfMessages(receivedMsgs: [NtfMsgInfo?]) + case ntfMessage(user: UserRef, connEntity: ConnectionEntity, ntfMessage: NtfMsgAckInfo) + case cmdOk(user_: UserRef?) + case chatCmdError(user_: UserRef?, chatError: ChatError) + case chatError(user_: UserRef?, chatError: ChatError) + + var responseType: String { + switch self { + case let .response(type, _): "* \(type)" + case .activeUser: "activeUser" + case .chatStarted: "chatStarted" + case .chatRunning: "chatRunning" + case .chatSuspended: "chatSuspended" + case .contactConnected: "contactConnected" + case .receivedContactRequest: "receivedContactRequest" + case .newChatItems: "newChatItems" + case .rcvFileAccepted: "rcvFileAccepted" + case .rcvFileSndCancelled: "rcvFileSndCancelled" + case .sndFileComplete: "sndFileComplete" + case .sndFileRcvCancelled: "sndFileRcvCancelled" + case .callInvitation: "callInvitation" + case .ntfConns: "ntfConns" + case .connNtfMessages: "connNtfMessages" + case .ntfMessage: "ntfMessage" + case .cmdOk: "cmdOk" + case .chatCmdError: "chatCmdError" + case .chatError: "chatError" + } + } + + var details: String { + switch self { + case let .response(_, json): return json + case let .activeUser(user): return String(describing: user) + case .chatStarted: return noDetails + case .chatRunning: return noDetails + case .chatSuspended: return noDetails + case let .contactConnected(u, contact, _): return withUser(u, String(describing: contact)) + case let .receivedContactRequest(u, contactRequest): return withUser(u, String(describing: contactRequest)) + case let .newChatItems(u, chatItems): + let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") + return withUser(u, itemsString) + case let .rcvFileAccepted(u, chatItem): return withUser(u, String(describing: chatItem)) + case let .rcvFileSndCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndFileComplete(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndFileRcvCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .callInvitation(inv): return String(describing: inv) + case let .ntfConns(ntfConns): return String(describing: ntfConns) + case let .connNtfMessages(receivedMsgs): return "receivedMsgs: \(String(describing: receivedMsgs))" + case let .ntfMessage(u, connEntity, ntfMessage): return withUser(u, "connEntity: \(String(describing: connEntity))\nntfMessage: \(String(describing: ntfMessage))") + case .cmdOk: return noDetails + case let .chatCmdError(u, chatError): return withUser(u, String(describing: chatError)) + case let .chatError(u, chatError): return withUser(u, String(describing: chatError)) + } + } + + var noDetails: String { "\(responseType): no details" } + + static func chatResponse(_ s: String) -> NSEChatResponse { + let d = s.data(using: .utf8)! + // TODO is there a way to do it without copying the data? e.g: + // let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson)) + // let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free) + do { + let r = try jsonDecoder.decode(APIResponse.self, from: d) + return r.resp + } catch { + logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)") + } + + var type: String? + var json: String? + if let j = try? JSONSerialization.jsonObject(with: d) as? NSDictionary { + if let jResp = j["resp"] as? NSDictionary, jResp.count == 1 || jResp.count == 2 { + type = jResp.allKeys[0] as? String + if jResp.count == 2 && type == "_owsf" { + type = jResp.allKeys[1] as? String + } + if type == "chatCmdError" { + if let jError = jResp["chatCmdError"] as? NSDictionary { + return .chatCmdError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) + } + } else if type == "chatError" { + if let jError = jResp["chatError"] as? NSDictionary { + return .chatError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) + } + } + } + json = serializeJSON(j, options: .prettyPrinted) + } + return NSEChatResponse.response(type: type ?? "invalid", json: json ?? s) + } + + var chatError: ChatError? { + switch self { + case let .chatCmdError(_, error): error + case let .chatError(_, error): error + default: nil + } + } + + var chatErrorType: ChatErrorType? { + switch self { + case let .chatCmdError(_, .error(error)): error + case let .chatError(_, .error(error)): error + default: nil + } + } +} diff --git a/apps/ios/SimpleX NSE/NotificationService.swift b/apps/ios/SimpleX NSE/NotificationService.swift index c82869a36a..0bfa21781e 100644 --- a/apps/ios/SimpleX NSE/NotificationService.swift +++ b/apps/ios/SimpleX NSE/NotificationService.swift @@ -789,9 +789,9 @@ func receiveMessages() async { } } -func chatRecvMsg() async -> ChatResponse? { +func chatRecvMsg() async -> NSEChatResponse? { await withCheckedContinuation { cont in - let resp = recvSimpleXMsg() + let resp: NSEChatResponse? = recvSimpleXMsg() cont.resume(returning: resp) } } @@ -799,7 +799,7 @@ func chatRecvMsg() async -> ChatResponse? { private let isInChina = SKStorefront().countryCode == "CHN" private func useCallKit() -> Bool { !isInChina && callKitEnabledGroupDefault.get() } -func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotificationData)? { +func receivedMsgNtf(_ res: NSEChatResponse) async -> (String, NSENotificationData)? { logger.debug("NotificationService receivedMsgNtf: \(res.responseType)") switch res { case let .contactConnected(user, contact, _): @@ -868,7 +868,7 @@ func updateNetCfg() { } func apiGetActiveUser() -> User? { - let r = sendSimpleXCmd(.showActiveUser) + let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.showActiveUser) logger.debug("apiGetActiveUser sendSimpleXCmd response: \(r.responseType)") switch r { case let .activeUser(user): return user @@ -885,7 +885,7 @@ func apiGetActiveUser() -> User? { } func apiStartChat() throws -> Bool { - let r = sendSimpleXCmd(.startChat(mainApp: false, enableSndFiles: false)) + let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.startChat(mainApp: false, enableSndFiles: false)) switch r { case .chatStarted: return true case .chatRunning: return false @@ -895,27 +895,27 @@ func apiStartChat() throws -> Bool { func apiActivateChat() -> Bool { chatReopenStore() - let r = sendSimpleXCmd(.apiActivateChat(restoreChat: false)) + let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.apiActivateChat(restoreChat: false)) if case .cmdOk = r { return true } logger.error("NotificationService apiActivateChat error: \(String(describing: r))") return false } func apiSuspendChat(timeoutMicroseconds: Int) -> Bool { - let r = sendSimpleXCmd(.apiSuspendChat(timeoutMicroseconds: timeoutMicroseconds)) + let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.apiSuspendChat(timeoutMicroseconds: timeoutMicroseconds)) if case .cmdOk = r { return true } logger.error("NotificationService apiSuspendChat error: \(String(describing: r))") return false } func apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String) throws { - let r = sendSimpleXCmd(.apiSetAppFilePaths(filesFolder: filesFolder, tempFolder: tempFolder, assetsFolder: assetsFolder)) + let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.apiSetAppFilePaths(filesFolder: filesFolder, tempFolder: tempFolder, assetsFolder: assetsFolder)) if case .cmdOk = r { return } throw r } func apiSetEncryptLocalFiles(_ enable: Bool) throws { - let r = sendSimpleXCmd(.apiSetEncryptLocalFiles(enable: enable)) + let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.apiSetEncryptLocalFiles(enable: enable)) if case .cmdOk = r { return } throw r } @@ -925,7 +925,7 @@ func apiGetNtfConns(nonce: String, encNtfInfo: String) -> [NtfConn]? { logger.debug("no active user") return nil } - let r = sendSimpleXCmd(.apiGetNtfConns(nonce: nonce, encNtfInfo: encNtfInfo)) + let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.apiGetNtfConns(nonce: nonce, encNtfInfo: encNtfInfo)) if case let .ntfConns(ntfConns) = r { logger.debug("apiGetNtfConns response ntfConns: \(ntfConns.count)") return ntfConns @@ -942,8 +942,8 @@ func apiGetConnNtfMessages(connMsgReqs: [ConnMsgReq]) -> [NtfMsgInfo?]? { logger.debug("no active user") return nil } - logger.debug("apiGetConnNtfMessages command: \(ChatCommand.apiGetConnNtfMessages(connMsgReqs: connMsgReqs).cmdString)") - let r = sendSimpleXCmd(.apiGetConnNtfMessages(connMsgReqs: connMsgReqs)) + logger.debug("apiGetConnNtfMessages command: \(NSEChatCommand.apiGetConnNtfMessages(connMsgReqs: connMsgReqs).cmdString)") + let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.apiGetConnNtfMessages(connMsgReqs: connMsgReqs)) if case let .connNtfMessages(receivedMsgs) = r { logger.debug("apiGetConnNtfMessages response receivedMsgs: total \(receivedMsgs.count), expecting messages \(receivedMsgs.count { $0 != nil })") return receivedMsgs @@ -962,7 +962,7 @@ func getConnNtfMessage(connMsgReq: ConnMsgReq) -> NtfMsgInfo? { func apiReceiveFile(fileId: Int64, encrypted: Bool, inline: Bool? = nil) -> AChatItem? { let userApprovedRelays = !privacyAskToApproveRelaysGroupDefault.get() - let r = sendSimpleXCmd(.receiveFile(fileId: fileId, userApprovedRelays: userApprovedRelays, encrypted: encrypted, inline: inline)) + let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.receiveFile(fileId: fileId, userApprovedRelays: userApprovedRelays, encrypted: encrypted, inline: inline)) if case let .rcvFileAccepted(_, chatItem) = r { return chatItem } logger.error("receiveFile error: \(responseError(r))") return nil @@ -970,7 +970,7 @@ func apiReceiveFile(fileId: Int64, encrypted: Bool, inline: Bool? = nil) -> ACha func apiSetFileToReceive(fileId: Int64, encrypted: Bool) { let userApprovedRelays = !privacyAskToApproveRelaysGroupDefault.get() - let r = sendSimpleXCmd(.setFileToReceive(fileId: fileId, userApprovedRelays: userApprovedRelays, encrypted: encrypted)) + let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.setFileToReceive(fileId: fileId, userApprovedRelays: userApprovedRelays, encrypted: encrypted)) if case .cmdOk = r { return } logger.error("setFileToReceive error: \(responseError(r))") } @@ -989,7 +989,7 @@ func autoReceiveFile(_ file: CIFile) -> ChatItem? { } func setNetworkConfig(_ cfg: NetCfg) throws { - let r = sendSimpleXCmd(.apiSetNetworkConfig(networkConfig: cfg)) + let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.apiSetNetworkConfig(networkConfig: cfg)) if case .cmdOk = r { return } throw r } diff --git a/apps/ios/SimpleX SE/ShareAPI.swift b/apps/ios/SimpleX SE/ShareAPI.swift index fcb78c64b1..56f1c2f5f3 100644 --- a/apps/ios/SimpleX SE/ShareAPI.swift +++ b/apps/ios/SimpleX SE/ShareAPI.swift @@ -13,7 +13,7 @@ import SimpleXChat let logger = Logger() func apiGetActiveUser() throws -> User? { - let r = sendSimpleXCmd(.showActiveUser) + let r: SEChatResponse = sendSimpleXCmd(SEChatCommand.showActiveUser) switch r { case let .activeUser(user): return user case .chatCmdError(_, .error(.noActiveUser)): return nil @@ -22,7 +22,7 @@ func apiGetActiveUser() throws -> User? { } func apiStartChat() throws -> Bool { - let r = sendSimpleXCmd(.startChat(mainApp: false, enableSndFiles: true)) + let r: SEChatResponse = sendSimpleXCmd(SEChatCommand.startChat(mainApp: false, enableSndFiles: true)) switch r { case .chatStarted: return true case .chatRunning: return false @@ -31,25 +31,25 @@ func apiStartChat() throws -> Bool { } func apiSetNetworkConfig(_ cfg: NetCfg) throws { - let r = sendSimpleXCmd(.apiSetNetworkConfig(networkConfig: cfg)) + let r: SEChatResponse = sendSimpleXCmd(SEChatCommand.apiSetNetworkConfig(networkConfig: cfg)) if case .cmdOk = r { return } throw r } func apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String) throws { - let r = sendSimpleXCmd(.apiSetAppFilePaths(filesFolder: filesFolder, tempFolder: tempFolder, assetsFolder: assetsFolder)) + let r: SEChatResponse = sendSimpleXCmd(SEChatCommand.apiSetAppFilePaths(filesFolder: filesFolder, tempFolder: tempFolder, assetsFolder: assetsFolder)) if case .cmdOk = r { return } throw r } func apiSetEncryptLocalFiles(_ enable: Bool) throws { - let r = sendSimpleXCmd(.apiSetEncryptLocalFiles(enable: enable)) + let r: SEChatResponse = sendSimpleXCmd(SEChatCommand.apiSetEncryptLocalFiles(enable: enable)) if case .cmdOk = r { return } throw r } func apiGetChats(userId: User.ID) throws -> Array { - let r = sendSimpleXCmd(.apiGetChats(userId: userId)) + let r: SEChatResponse = sendSimpleXCmd(SEChatCommand.apiGetChats(userId: userId)) if case let .apiChats(user: _, chats: chats) = r { return chats } throw r } @@ -58,13 +58,13 @@ func apiSendMessages( chatInfo: ChatInfo, composedMessages: [ComposedMessage] ) throws -> [AChatItem] { - let r = sendSimpleXCmd( + let r: SEChatResponse = sendSimpleXCmd( chatInfo.chatType == .local - ? .apiCreateChatItems( + ? SEChatCommand.apiCreateChatItems( noteFolderId: chatInfo.apiId, composedMessages: composedMessages ) - : .apiSendMessages( + : SEChatCommand.apiSendMessages( type: chatInfo.chatType, id: chatInfo.apiId, live: false, @@ -84,19 +84,20 @@ func apiSendMessages( func apiActivateChat() throws { chatReopenStore() - let r = sendSimpleXCmd(.apiActivateChat(restoreChat: false)) + let r: SEChatResponse = sendSimpleXCmd(SEChatCommand.apiActivateChat(restoreChat: false)) if case .cmdOk = r { return } throw r } func apiSuspendChat(expired: Bool) { - let r = sendSimpleXCmd(.apiSuspendChat(timeoutMicroseconds: expired ? 0 : 3_000000)) + let r: SEChatResponse = sendSimpleXCmd(SEChatCommand.apiSuspendChat(timeoutMicroseconds: expired ? 0 : 3_000000)) // Block until `chatSuspended` received or 3 seconds has passed var suspended = false if case .cmdOk = r, !expired { let startTime = CFAbsoluteTimeGetCurrent() while CFAbsoluteTimeGetCurrent() - startTime < 3 { - switch recvSimpleXMsg(messageTimeout: 3_500000) { + let msg: SEChatResponse? = recvSimpleXMsg(messageTimeout: 3_500000) + switch msg { case .chatSuspended: suspended = false break @@ -105,9 +106,166 @@ func apiSuspendChat(expired: Bool) { } } if !suspended { - _ = sendSimpleXCmd(.apiSuspendChat(timeoutMicroseconds: 0)) + let _r1: SEChatResponse = sendSimpleXCmd(SEChatCommand.apiSuspendChat(timeoutMicroseconds: 0)) } logger.debug("close store") chatCloseStore() SEChatState.shared.set(.inactive) } + +enum SEChatCommand: ChatCmdProtocol { + case showActiveUser + case startChat(mainApp: Bool, enableSndFiles: Bool) + case apiActivateChat(restoreChat: Bool) + case apiSuspendChat(timeoutMicroseconds: Int) + case apiSetNetworkConfig(networkConfig: NetCfg) + case apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String) + case apiSetEncryptLocalFiles(enable: Bool) + case apiGetChats(userId: Int64) + case apiCreateChatItems(noteFolderId: Int64, composedMessages: [ComposedMessage]) + case apiSendMessages(type: ChatType, id: Int64, live: Bool, ttl: Int?, composedMessages: [ComposedMessage]) + + var cmdString: String { + switch self { + case .showActiveUser: return "/u" + case let .startChat(mainApp, enableSndFiles): return "/_start main=\(onOff(mainApp)) snd_files=\(onOff(enableSndFiles))" + case let .apiActivateChat(restore): return "/_app activate restore=\(onOff(restore))" + case let .apiSuspendChat(timeoutMicroseconds): return "/_app suspend \(timeoutMicroseconds)" + case let .apiSetNetworkConfig(networkConfig): return "/_network \(encodeJSON(networkConfig))" + case let .apiSetAppFilePaths(filesFolder, tempFolder, assetsFolder): + return "/set file paths \(encodeJSON(AppFilePaths(appFilesFolder: filesFolder, appTempFolder: tempFolder, appAssetsFolder: assetsFolder)))" + case let .apiSetEncryptLocalFiles(enable): return "/_files_encrypt \(onOff(enable))" + case let .apiGetChats(userId): return "/_get chats \(userId) pcc=on" + case let .apiCreateChatItems(noteFolderId, composedMessages): + let msgs = encodeJSON(composedMessages) + return "/_create *\(noteFolderId) json \(msgs)" + case let .apiSendMessages(type, id, live, ttl, composedMessages): + let msgs = encodeJSON(composedMessages) + let ttlStr = ttl != nil ? "\(ttl!)" : "default" + return "/_send \(ref(type, id)) live=\(onOff(live)) ttl=\(ttlStr) json \(msgs)" + } + } + + func ref(_ type: ChatType, _ id: Int64) -> String { + "\(type.rawValue)\(id)" + } +} + +enum SEChatResponse: Decodable, Error, ChatRespProtocol { + case response(type: String, json: String) + case activeUser(user: User) + case chatStarted + case chatRunning + case chatSuspended + case apiChats(user: UserRef, chats: [ChatData]) + case newChatItems(user: UserRef, chatItems: [AChatItem]) + case sndFileProgressXFTP(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, sentSize: Int64, totalSize: Int64) + case sndFileCompleteXFTP(user: UserRef, chatItem: AChatItem, fileTransferMeta: FileTransferMeta) + case chatItemsStatusesUpdated(user: UserRef, chatItems: [AChatItem]) + case sndFileError(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, errorMessage: String) + case sndFileWarning(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, errorMessage: String) + case cmdOk(user_: UserRef?) + case chatCmdError(user_: UserRef?, chatError: ChatError) + case chatError(user_: UserRef?, chatError: ChatError) + + var responseType: String { + switch self { + case let .response(type, _): "* \(type)" + case .activeUser: "activeUser" + case .chatStarted: "chatStarted" + case .chatRunning: "chatRunning" + case .chatSuspended: "chatSuspended" + case .apiChats: "apiChats" + case .newChatItems: "newChatItems" + case .sndFileProgressXFTP: "sndFileProgressXFTP" + case .sndFileCompleteXFTP: "sndFileCompleteXFTP" + case .chatItemsStatusesUpdated: "chatItemsStatusesUpdated" + case .sndFileError: "sndFileError" + case .sndFileWarning: "sndFileWarning" + case .cmdOk: "cmdOk" + case .chatCmdError: "chatCmdError" + case .chatError: "chatError" + } + } + + var details: String { + switch self { + case let .response(_, json): return json + case let .activeUser(user): return String(describing: user) + case .chatStarted: return noDetails + case .chatRunning: return noDetails + case .chatSuspended: return noDetails + case let .apiChats(u, chats): return withUser(u, String(describing: chats)) + case let .newChatItems(u, chatItems): + let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") + return withUser(u, itemsString) + case let .sndFileProgressXFTP(u, chatItem, _, sentSize, totalSize): return withUser(u, "chatItem: \(String(describing: chatItem))\nsentSize: \(sentSize)\ntotalSize: \(totalSize)") + case let .sndFileCompleteXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .chatItemsStatusesUpdated(u, chatItems): + let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") + return withUser(u, itemsString) + case let .sndFileError(u, chatItem, _, err): return withUser(u, "error: \(String(describing: err))\nchatItem: \(String(describing: chatItem))") + case let .sndFileWarning(u, chatItem, _, err): return withUser(u, "error: \(String(describing: err))\nchatItem: \(String(describing: chatItem))") + case .cmdOk: return noDetails + case let .chatCmdError(u, chatError): return withUser(u, String(describing: chatError)) + case let .chatError(u, chatError): return withUser(u, String(describing: chatError)) + } + } + + var noDetails: String { "\(responseType): no details" } + + static func chatResponse(_ s: String) -> SEChatResponse { + let d = s.data(using: .utf8)! + // TODO is there a way to do it without copying the data? e.g: + // let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson)) + // let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free) + do { + let r = try jsonDecoder.decode(APIResponse.self, from: d) + return r.resp + } catch { + logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)") + } + + var type: String? + var json: String? + if let j = try? JSONSerialization.jsonObject(with: d) as? NSDictionary { + if let jResp = j["resp"] as? NSDictionary, jResp.count == 1 || jResp.count == 2 { + type = jResp.allKeys[0] as? String + if jResp.count == 2 && type == "_owsf" { + type = jResp.allKeys[1] as? String + } + if type == "apiChats" { + if let r = parseApiChats(jResp) { + return .apiChats(user: r.user, chats: r.chats) + } + } else if type == "chatCmdError" { + if let jError = jResp["chatCmdError"] as? NSDictionary { + return .chatCmdError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) + } + } else if type == "chatError" { + if let jError = jResp["chatError"] as? NSDictionary { + return .chatError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) + } + } + } + json = serializeJSON(j, options: .prettyPrinted) + } + return SEChatResponse.response(type: type ?? "invalid", json: json ?? s) + } + + var chatError: ChatError? { + switch self { + case let .chatCmdError(_, error): error + case let .chatError(_, error): error + default: nil + } + } + + var chatErrorType: ChatErrorType? { + switch self { + case let .chatCmdError(_, .error(error)): error + case let .chatError(_, .error(error)): error + default: nil + } + } +} diff --git a/apps/ios/SimpleX SE/ShareModel.swift b/apps/ios/SimpleX SE/ShareModel.swift index 88e174e8bc..a555c14472 100644 --- a/apps/ios/SimpleX SE/ShareModel.swift +++ b/apps/ios/SimpleX SE/ShareModel.swift @@ -303,7 +303,8 @@ class ShareModel: ObservableObject { } } } - switch recvSimpleXMsg(messageTimeout: 1_000_000) { + let r: SEChatResponse? = recvSimpleXMsg(messageTimeout: 1_000_000) + switch r { case let .sndFileProgressXFTP(_, ci, _, sentSize, totalSize): guard isMessage(for: ci) else { continue } networkTimeout = CFAbsoluteTimeGetCurrent() diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 5346e706a2..c7fd19e615 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -242,6 +242,8 @@ E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; E5DCF9982C5906FF007928CC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9962C5906FF007928CC /* InfoPlist.strings */; }; + E5DDBE6E2DC4106800A0EFF0 /* AppAPITypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5DDBE6D2DC4106200A0EFF0 /* AppAPITypes.swift */; }; + E5DDBE702DC4217900A0EFF0 /* NSEAPITypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5DDBE6F2DC4217900A0EFF0 /* NSEAPITypes.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -643,6 +645,8 @@ E5DCF9A62C590731007928CC /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/InfoPlist.strings; sourceTree = ""; }; E5DCF9A72C590732007928CC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoPlist.strings; sourceTree = ""; }; E5DCF9A82C590732007928CC /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = ""; }; + E5DDBE6D2DC4106200A0EFF0 /* AppAPITypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAPITypes.swift; sourceTree = ""; }; + E5DDBE6F2DC4217900A0EFF0 /* NSEAPITypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSEAPITypes.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -795,6 +799,7 @@ 5C764E87279CBC8E000C6508 /* Model */ = { isa = PBXGroup; children = ( + E5DDBE6D2DC4106200A0EFF0 /* AppAPITypes.swift */, 5C764E88279CBCB3000C6508 /* ChatModel.swift */, 5C2E260627A2941F00F70299 /* SimpleXAPI.swift */, 5C35CFC727B2782E00FB6C6D /* BGManager.swift */, @@ -990,6 +995,7 @@ isa = PBXGroup; children = ( 5CDCAD5128186DE400503DA2 /* SimpleX NSE.entitlements */, + E5DDBE6F2DC4217900A0EFF0 /* NSEAPITypes.swift */, 5CDCAD472818589900503DA2 /* NotificationService.swift */, 5CDCAD492818589900503DA2 /* Info.plist */, 5CB0BA862826CB3A00B3292C /* InfoPlist.strings */, @@ -1006,9 +1012,9 @@ 5CDCAD7228188CFF00503DA2 /* ChatTypes.swift */, 5CDCAD7428188D2900503DA2 /* APITypes.swift */, 5C5E5D3C282447AB00B0488A /* CallTypes.swift */, + 5CDCAD7D2818941F00503DA2 /* API.swift */, CE3097FA2C4C0C9F00180898 /* ErrorAlert.swift */, 5C9FD96A27A56D4D0075386C /* JSON.swift */, - 5CDCAD7D2818941F00503DA2 /* API.swift */, 5CDCAD80281A7E2700503DA2 /* Notifications.swift */, 5CBD2859295711D700EC2CF4 /* ImageUtils.swift */, CE2AD9CD2C452A4D00E844E3 /* ChatUtils.swift */, @@ -1534,6 +1540,7 @@ 5CA059EB279559F40002BEB4 /* SimpleXApp.swift in Sources */, 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */, 6448BBB628FA9D56000D2AB9 /* GroupLinkView.swift in Sources */, + E5DDBE6E2DC4106800A0EFF0 /* AppAPITypes.swift in Sources */, 8C9BC2652C240D5200875A27 /* ThemeModeEditor.swift in Sources */, 5CB346E92869E8BA001FD2EF /* PushEnvironment.swift in Sources */, 5C55A91F283AD0E400C4E99E /* CallManager.swift in Sources */, @@ -1606,6 +1613,7 @@ buildActionMask = 2147483647; files = ( 5CDCAD482818589900503DA2 /* NotificationService.swift in Sources */, + E5DDBE702DC4217900A0EFF0 /* NSEAPITypes.swift in Sources */, 5CFE0922282EEAF60002594B /* ZoomableScrollView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/apps/ios/SimpleXChat/API.swift b/apps/ios/SimpleXChat/API.swift index 869dffea31..b10b544a43 100644 --- a/apps/ios/SimpleXChat/API.swift +++ b/apps/ios/SimpleXChat/API.swift @@ -110,19 +110,19 @@ public func resetChatCtrl() { migrationResult = nil } -public func sendSimpleXCmd(_ cmd: ChatCommand, _ ctrl: chat_ctrl? = nil) -> ChatResponse { +public func sendSimpleXCmd(_ cmd: ChatCmdProtocol, _ ctrl: chat_ctrl? = nil) -> CR { var c = cmd.cmdString.cString(using: .utf8)! let cjson = chat_send_cmd(ctrl ?? getChatCtrl(), &c)! - return chatResponse(fromCString(cjson)) + return CR.chatResponse(fromCString(cjson)) } // in microseconds public let MESSAGE_TIMEOUT: Int32 = 15_000_000 -public func recvSimpleXMsg(_ ctrl: chat_ctrl? = nil, messageTimeout: Int32 = MESSAGE_TIMEOUT) -> ChatResponse? { +public func recvSimpleXMsg(_ ctrl: chat_ctrl? = nil, messageTimeout: Int32 = MESSAGE_TIMEOUT) -> CR? { if let cjson = chat_recv_msg_wait(ctrl ?? getChatCtrl(), messageTimeout) { let s = fromCString(cjson) - return s == "" ? nil : chatResponse(s) + return s == "" ? nil : CR.chatResponse(s) } return nil } @@ -177,89 +177,7 @@ public func fromCString(_ c: UnsafeMutablePointer) -> String { return s } -public func chatResponse(_ s: String) -> ChatResponse { - let d = s.data(using: .utf8)! - // TODO is there a way to do it without copying the data? e.g: - // let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson)) - // let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free) - do { - let r = try callWithLargeStack { - try jsonDecoder.decode(APIResponse.self, from: d) - } - return r.resp - } catch { - logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)") - } - - var type: String? - var json: String? - if let j = try? JSONSerialization.jsonObject(with: d) as? NSDictionary { - if let jResp = j["resp"] as? NSDictionary, jResp.count == 1 || jResp.count == 2 { - type = jResp.allKeys[0] as? String - if jResp.count == 2 && type == "_owsf" { - type = jResp.allKeys[1] as? String - } - if type == "apiChats" { - if let jApiChats = jResp["apiChats"] as? NSDictionary, - let user: UserRef = try? decodeObject(jApiChats["user"] as Any), - let jChats = jApiChats["chats"] as? NSArray { - let chats = jChats.map { jChat in - if let chatData = try? parseChatData(jChat) { - return chatData.0 - } - return ChatData.invalidJSON(serializeJSON(jChat, options: .prettyPrinted) ?? "") - } - return .apiChats(user: user, chats: chats) - } - } else if type == "apiChat" { - if let jApiChat = jResp["apiChat"] as? NSDictionary, - let user: UserRef = try? decodeObject(jApiChat["user"] as Any), - let jChat = jApiChat["chat"] as? NSDictionary, - let (chat, navInfo) = try? parseChatData(jChat, jApiChat["navInfo"] as? NSDictionary) { - return .apiChat(user: user, chat: chat, navInfo: navInfo) - } - } else if type == "chatCmdError" { - if let jError = jResp["chatCmdError"] as? NSDictionary { - return .chatCmdError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) - } - } else if type == "chatError" { - if let jError = jResp["chatError"] as? NSDictionary { - return .chatError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) - } - } - } - json = serializeJSON(j, options: .prettyPrinted) - } - return ChatResponse.response(type: type ?? "invalid", json: json ?? s) -} - -private let largeStackSize: Int = 2 * 1024 * 1024 - -private func callWithLargeStack(_ f: @escaping () throws -> T) throws -> T { - let semaphore = DispatchSemaphore(value: 0) - var result: Result? - let thread = Thread { - do { - result = .success(try f()) - } catch { - result = .failure(error) - } - semaphore.signal() - } - - thread.stackSize = largeStackSize - thread.qualityOfService = Thread.current.qualityOfService - thread.start() - - semaphore.wait() - - switch result! { - case let .success(r): return r - case let .failure(e): throw e - } -} - -private func decodeUser_(_ jDict: NSDictionary) -> UserRef? { +public func decodeUser_(_ jDict: NSDictionary) -> UserRef? { if let user_ = jDict["user_"] { try? decodeObject(user_ as Any) } else { @@ -267,7 +185,7 @@ private func decodeUser_(_ jDict: NSDictionary) -> UserRef? { } } -private func errorJson(_ jDict: NSDictionary) -> String? { +public func errorJson(_ jDict: NSDictionary) -> String? { if let chatError = jDict["chatError"] { serializeJSON(chatError) } else { @@ -275,7 +193,7 @@ private func errorJson(_ jDict: NSDictionary) -> String? { } } -func parseChatData(_ jChat: Any, _ jNavInfo: Any? = nil) throws -> (ChatData, NavigationInfo) { +public func parseChatData(_ jChat: Any, _ jNavInfo: Any? = nil) throws -> (ChatData, NavigationInfo) { let jChatDict = jChat as! NSDictionary let chatInfo: ChatInfo = try decodeObject(jChatDict["chatInfo"]!) let chatStats: ChatStats = try decodeObject(jChatDict["chatStats"]!) @@ -294,7 +212,7 @@ func parseChatData(_ jChat: Any, _ jNavInfo: Any? = nil) throws -> (ChatData, Na return (ChatData(chatInfo: chatInfo, chatItems: chatItems, chatStats: chatStats), navInfo) } -func decodeObject(_ obj: Any) throws -> T { +public func decodeObject(_ obj: Any) throws -> T { try jsonDecoder.decode(T.self, from: JSONSerialization.data(withJSONObject: obj)) } @@ -305,7 +223,7 @@ func decodeProperty(_ obj: Any, _ prop: NSString) -> T? { return nil } -func serializeJSON(_ obj: Any, options: JSONSerialization.WritingOptions = []) -> String? { +public func serializeJSON(_ obj: Any, options: JSONSerialization.WritingOptions = []) -> String? { if let d = try? JSONSerialization.data(withJSONObject: obj, options: options) { return String(decoding: d, as: UTF8.self) } @@ -313,14 +231,14 @@ func serializeJSON(_ obj: Any, options: JSONSerialization.WritingOptions = []) - } public func responseError(_ err: Error) -> String { - if let r = err as? ChatResponse { - switch r { - case let .chatCmdError(_, chatError): return chatErrorString(chatError) - case let .chatError(_, chatError): return chatErrorString(chatError) - default: return "\(String(describing: r.responseType)), details: \(String(describing: r.details))" + if let r = err as? ChatRespProtocol { + if let e = r.chatError { + chatErrorString(e) + } else { + "\(String(describing: r.responseType)), details: \(String(describing: r.details))" } } else { - return String(describing: err) + String(describing: err) } } diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 18d0cbdb3c..3cfe67e158 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -13,1164 +13,47 @@ import Network public let jsonDecoder = getJSONDecoder() public let jsonEncoder = getJSONEncoder() -public enum ChatCommand { - case showActiveUser - case createActiveUser(profile: Profile?, pastTimestamp: Bool) - case listUsers - case apiSetActiveUser(userId: Int64, viewPwd: String?) - case setAllContactReceipts(enable: Bool) - case apiSetUserContactReceipts(userId: Int64, userMsgReceiptSettings: UserMsgReceiptSettings) - case apiSetUserGroupReceipts(userId: Int64, userMsgReceiptSettings: UserMsgReceiptSettings) - case apiHideUser(userId: Int64, viewPwd: String) - case apiUnhideUser(userId: Int64, viewPwd: String) - case apiMuteUser(userId: Int64) - case apiUnmuteUser(userId: Int64) - case apiDeleteUser(userId: Int64, delSMPQueues: Bool, viewPwd: String?) - case startChat(mainApp: Bool, enableSndFiles: Bool) - case checkChatRunning - case apiStopChat - case apiActivateChat(restoreChat: Bool) - case apiSuspendChat(timeoutMicroseconds: Int) - case apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String) - case apiSetEncryptLocalFiles(enable: Bool) - case apiExportArchive(config: ArchiveConfig) - case apiImportArchive(config: ArchiveConfig) - case apiDeleteStorage - case apiStorageEncryption(config: DBEncryptionConfig) - case testStorageEncryption(key: String) - case apiSaveSettings(settings: AppSettings) - case apiGetSettings(settings: AppSettings) - case apiGetChatTags(userId: Int64) - case apiGetChats(userId: Int64) - case apiGetChat(chatId: ChatId, pagination: ChatPagination, search: String) - case apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64) - case apiSendMessages(type: ChatType, id: Int64, live: Bool, ttl: Int?, composedMessages: [ComposedMessage]) - case apiCreateChatTag(tag: ChatTagData) - case apiSetChatTags(type: ChatType, id: Int64, tagIds: [Int64]) - case apiDeleteChatTag(tagId: Int64) - case apiUpdateChatTag(tagId: Int64, tagData: ChatTagData) - case apiReorderChatTags(tagIds: [Int64]) - case apiCreateChatItems(noteFolderId: Int64, composedMessages: [ComposedMessage]) - case apiReportMessage(groupId: Int64, chatItemId: Int64, reportReason: ReportReason, reportText: String) - case apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, updatedMessage: UpdatedMessage, live: Bool) - case apiDeleteChatItem(type: ChatType, id: Int64, itemIds: [Int64], mode: CIDeleteMode) - case apiDeleteMemberChatItem(groupId: Int64, itemIds: [Int64]) - case apiArchiveReceivedReports(groupId: Int64) - case apiDeleteReceivedReports(groupId: Int64, itemIds: [Int64], mode: CIDeleteMode) - case apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, reaction: MsgReaction) - case apiGetReactionMembers(userId: Int64, groupId: Int64, itemId: Int64, reaction: MsgReaction) - case apiPlanForwardChatItems(toChatType: ChatType, toChatId: Int64, itemIds: [Int64]) - case apiForwardChatItems(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemIds: [Int64], ttl: Int?) - case apiGetNtfToken - case apiRegisterToken(token: DeviceToken, notificationMode: NotificationsMode) - case apiVerifyToken(token: DeviceToken, nonce: String, code: String) - case apiCheckToken(token: DeviceToken) - case apiDeleteToken(token: DeviceToken) - case apiGetNtfConns(nonce: String, encNtfInfo: String) - case apiGetConnNtfMessages(connMsgReqs: [ConnMsgReq]) - case apiNewGroup(userId: Int64, incognito: Bool, groupProfile: GroupProfile) - case apiAddMember(groupId: Int64, contactId: Int64, memberRole: GroupMemberRole) - case apiJoinGroup(groupId: Int64) - case apiMembersRole(groupId: Int64, memberIds: [Int64], memberRole: GroupMemberRole) - case apiBlockMembersForAll(groupId: Int64, memberIds: [Int64], blocked: Bool) - case apiRemoveMembers(groupId: Int64, memberIds: [Int64], withMessages: Bool) - case apiLeaveGroup(groupId: Int64) - case apiListMembers(groupId: Int64) - case apiUpdateGroupProfile(groupId: Int64, groupProfile: GroupProfile) - case apiCreateGroupLink(groupId: Int64, memberRole: GroupMemberRole, short: Bool) - case apiGroupLinkMemberRole(groupId: Int64, memberRole: GroupMemberRole) - case apiDeleteGroupLink(groupId: Int64) - case apiGetGroupLink(groupId: Int64) - case apiCreateMemberContact(groupId: Int64, groupMemberId: Int64) - case apiSendMemberContactInvitation(contactId: Int64, msg: MsgContent) - case apiTestProtoServer(userId: Int64, server: String) - case apiGetServerOperators - case apiSetServerOperators(operators: [ServerOperator]) - case apiGetUserServers(userId: Int64) - case apiSetUserServers(userId: Int64, userServers: [UserOperatorServers]) - case apiValidateServers(userId: Int64, userServers: [UserOperatorServers]) - case apiGetUsageConditions - case apiSetConditionsNotified(conditionsId: Int64) - case apiAcceptConditions(conditionsId: Int64, operatorIds: [Int64]) - case apiSetChatItemTTL(userId: Int64, seconds: Int64) - case apiGetChatItemTTL(userId: Int64) - case apiSetChatTTL(userId: Int64, type: ChatType, id: Int64, seconds: Int64?) - case apiSetNetworkConfig(networkConfig: NetCfg) - case apiGetNetworkConfig - case apiSetNetworkInfo(networkInfo: UserNetworkInfo) - case reconnectAllServers - case reconnectServer(userId: Int64, smpServer: String) - 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 apiContactQueueInfo(contactId: Int64) - case apiGroupMemberQueueInfo(groupId: Int64, groupMemberId: Int64) - case apiSwitchContact(contactId: Int64) - case apiSwitchGroupMember(groupId: Int64, groupMemberId: Int64) - case apiAbortSwitchContact(contactId: Int64) - case apiAbortSwitchGroupMember(groupId: Int64, groupMemberId: Int64) - case apiSyncContactRatchet(contactId: Int64, force: Bool) - case apiSyncGroupMemberRatchet(groupId: Int64, groupMemberId: Int64, force: Bool) - case apiGetContactCode(contactId: Int64) - case apiGetGroupMemberCode(groupId: Int64, groupMemberId: Int64) - case apiVerifyContact(contactId: Int64, connectionCode: String?) - case apiVerifyGroupMember(groupId: Int64, groupMemberId: Int64, connectionCode: String?) - case apiAddContact(userId: Int64, short: Bool, incognito: Bool) - case apiSetConnectionIncognito(connId: Int64, incognito: Bool) - case apiChangeConnectionUser(connId: Int64, userId: Int64) - case apiConnectPlan(userId: Int64, connLink: String) - case apiConnect(userId: Int64, incognito: Bool, connLink: CreatedConnLink) - case apiConnectContactViaAddress(userId: Int64, incognito: Bool, contactId: Int64) - case apiDeleteChat(type: ChatType, id: Int64, chatDeleteMode: ChatDeleteMode) - case apiClearChat(type: ChatType, id: Int64) - case apiListContacts(userId: Int64) - case apiUpdateProfile(userId: Int64, profile: Profile) - case apiSetContactPrefs(contactId: Int64, preferences: Preferences) - case apiSetContactAlias(contactId: Int64, localAlias: String) - case apiSetGroupAlias(groupId: Int64, localAlias: String) - case apiSetConnectionAlias(connId: Int64, localAlias: String) - case apiSetUserUIThemes(userId: Int64, themes: ThemeModeOverrides?) - case apiSetChatUIThemes(chatId: String, themes: ThemeModeOverrides?) - case apiCreateMyAddress(userId: Int64, short: Bool) - case apiDeleteMyAddress(userId: Int64) - case apiShowMyAddress(userId: Int64) - case apiSetProfileAddress(userId: Int64, on: Bool) - case apiAddressAutoAccept(userId: Int64, autoAccept: AutoAccept?) - case apiAcceptContact(incognito: Bool, contactReqId: Int64) - case apiRejectContact(contactReqId: Int64) - // WebRTC calls - case apiSendCallInvitation(contact: Contact, callType: CallType) - case apiRejectCall(contact: Contact) - case apiSendCallOffer(contact: Contact, callOffer: WebRTCCallOffer) - case apiSendCallAnswer(contact: Contact, answer: WebRTCSession) - case apiSendCallExtraInfo(contact: Contact, extraInfo: WebRTCExtraInfo) - case apiEndCall(contact: Contact) - case apiGetCallInvitations - case apiCallStatus(contact: Contact, callStatus: WebRTCCallStatus) - // WebRTC calls / - case apiGetNetworkStatuses - case apiChatRead(type: ChatType, id: Int64) - case apiChatItemsRead(type: ChatType, id: Int64, itemIds: [Int64]) - case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) - case receiveFile(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?, inline: Bool?) - case setFileToReceive(fileId: Int64, userApprovedRelays: Bool, 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) - case apiUploadStandaloneFile(userId: Int64, file: CryptoFile) - case apiDownloadStandaloneFile(userId: Int64, url: String, file: CryptoFile) - case apiStandaloneFileInfo(url: String) - // misc - case showVersion - case getAgentSubsTotal(userId: Int64) - case getAgentServersSummary(userId: Int64) - case resetAgentServersStats - case string(String) - - public var cmdString: String { - get { - switch self { - case .showActiveUser: return "/u" - case let .createActiveUser(profile, pastTimestamp): - let user = NewUser(profile: profile, pastTimestamp: pastTimestamp) - return "/_create user \(encodeJSON(user))" - case .listUsers: return "/users" - case let .apiSetActiveUser(userId, viewPwd): return "/_user \(userId)\(maybePwd(viewPwd))" - case let .setAllContactReceipts(enable): return "/set receipts all \(onOff(enable))" - case let .apiSetUserContactReceipts(userId, userMsgReceiptSettings): - let umrs = userMsgReceiptSettings - return "/_set receipts contacts \(userId) \(onOff(umrs.enable)) clear_overrides=\(onOff(umrs.clearOverrides))" - case let .apiSetUserGroupReceipts(userId, userMsgReceiptSettings): - let umrs = userMsgReceiptSettings - return "/_set receipts groups \(userId) \(onOff(umrs.enable)) clear_overrides=\(onOff(umrs.clearOverrides))" - case let .apiHideUser(userId, viewPwd): return "/_hide user \(userId) \(encodeJSON(viewPwd))" - case let .apiUnhideUser(userId, viewPwd): return "/_unhide user \(userId) \(encodeJSON(viewPwd))" - case let .apiMuteUser(userId): return "/_mute user \(userId)" - case let .apiUnmuteUser(userId): return "/_unmute user \(userId)" - case let .apiDeleteUser(userId, delSMPQueues, viewPwd): return "/_delete user \(userId) del_smp=\(onOff(delSMPQueues))\(maybePwd(viewPwd))" - case let .startChat(mainApp, enableSndFiles): return "/_start main=\(onOff(mainApp)) snd_files=\(onOff(enableSndFiles))" - case .checkChatRunning: return "/_check running" - case .apiStopChat: return "/_stop" - case let .apiActivateChat(restore): return "/_app activate restore=\(onOff(restore))" - case let .apiSuspendChat(timeoutMicroseconds): return "/_app suspend \(timeoutMicroseconds)" - case let .apiSetAppFilePaths(filesFolder, tempFolder, assetsFolder): return "/set file paths \(encodeJSON(AppFilePaths(appFilesFolder: filesFolder, appTempFolder: tempFolder, appAssetsFolder: assetsFolder)))" - case let .apiSetEncryptLocalFiles(enable): return "/_files_encrypt \(onOff(enable))" - case let .apiExportArchive(cfg): return "/_db export \(encodeJSON(cfg))" - case let .apiImportArchive(cfg): return "/_db import \(encodeJSON(cfg))" - case .apiDeleteStorage: return "/_db delete" - case let .apiStorageEncryption(cfg): return "/_db encryption \(encodeJSON(cfg))" - case let .testStorageEncryption(key): return "/db test key \(key)" - case let .apiSaveSettings(settings): return "/_save app settings \(encodeJSON(settings))" - case let .apiGetSettings(settings): return "/_get app settings \(encodeJSON(settings))" - case let .apiGetChatTags(userId): return "/_get tags \(userId)" - case let .apiGetChats(userId): return "/_get chats \(userId) pcc=on" - case let .apiGetChat(chatId, pagination, search): return "/_get chat \(chatId) \(pagination.cmdString)" + - (search == "" ? "" : " search=\(search)") - case let .apiGetChatItemInfo(type, id, itemId): return "/_get item info \(ref(type, id)) \(itemId)" - case let .apiSendMessages(type, id, live, ttl, composedMessages): - let msgs = encodeJSON(composedMessages) - let ttlStr = ttl != nil ? "\(ttl!)" : "default" - return "/_send \(ref(type, id)) live=\(onOff(live)) ttl=\(ttlStr) json \(msgs)" - case let .apiCreateChatTag(tag): return "/_create tag \(encodeJSON(tag))" - case let .apiSetChatTags(type, id, tagIds): return "/_tags \(ref(type, id)) \(tagIds.map({ "\($0)" }).joined(separator: ","))" - case let .apiDeleteChatTag(tagId): return "/_delete tag \(tagId)" - case let .apiUpdateChatTag(tagId, tagData): return "/_update tag \(tagId) \(encodeJSON(tagData))" - case let .apiReorderChatTags(tagIds): return "/_reorder tags \(tagIds.map({ "\($0)" }).joined(separator: ","))" - case let .apiCreateChatItems(noteFolderId, composedMessages): - let msgs = encodeJSON(composedMessages) - return "/_create *\(noteFolderId) json \(msgs)" - case let .apiReportMessage(groupId, chatItemId, reportReason, reportText): - return "/_report #\(groupId) \(chatItemId) reason=\(reportReason) \(reportText)" - case let .apiUpdateChatItem(type, id, itemId, um, live): return "/_update item \(ref(type, id)) \(itemId) live=\(onOff(live)) \(um.cmdString)" - case let .apiDeleteChatItem(type, id, itemIds, mode): return "/_delete item \(ref(type, id)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) \(mode.rawValue)" - case let .apiDeleteMemberChatItem(groupId, itemIds): return "/_delete member item #\(groupId) \(itemIds.map({ "\($0)" }).joined(separator: ","))" - case let .apiArchiveReceivedReports(groupId): return "/_archive reports #\(groupId)" - case let .apiDeleteReceivedReports(groupId, itemIds, mode): return "/_delete reports #\(groupId) \(itemIds.map({ "\($0)" }).joined(separator: ",")) \(mode.rawValue)" - case let .apiChatItemReaction(type, id, itemId, add, reaction): return "/_reaction \(ref(type, id)) \(itemId) \(onOff(add)) \(encodeJSON(reaction))" - case let .apiGetReactionMembers(userId, groupId, itemId, reaction): return "/_reaction members \(userId) #\(groupId) \(itemId) \(encodeJSON(reaction))" - case let .apiPlanForwardChatItems(type, id, itemIds): return "/_forward plan \(ref(type, id)) \(itemIds.map({ "\($0)" }).joined(separator: ","))" - case let .apiForwardChatItems(toChatType, toChatId, fromChatType, fromChatId, itemIds, ttl): - let ttlStr = ttl != nil ? "\(ttl!)" : "default" - return "/_forward \(ref(toChatType, toChatId)) \(ref(fromChatType, fromChatId)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) ttl=\(ttlStr)" - case .apiGetNtfToken: return "/_ntf get " - case let .apiRegisterToken(token, notificationMode): return "/_ntf register \(token.cmdString) \(notificationMode.rawValue)" - case let .apiVerifyToken(token, nonce, code): return "/_ntf verify \(token.cmdString) \(nonce) \(code)" - case let .apiCheckToken(token): return "/_ntf check \(token.cmdString)" - case let .apiDeleteToken(token): return "/_ntf delete \(token.cmdString)" - case let .apiGetNtfConns(nonce, encNtfInfo): return "/_ntf conns \(nonce) \(encNtfInfo)" - case let .apiGetConnNtfMessages(connMsgReqs): return "/_ntf conn messages \(connMsgReqs.map { $0.cmdString }.joined(separator: ","))" - 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 .apiMembersRole(groupId, memberIds, memberRole): return "/_member role #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ",")) \(memberRole.rawValue)" - case let .apiBlockMembersForAll(groupId, memberIds, blocked): return "/_block #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ",")) blocked=\(onOff(blocked))" - case let .apiRemoveMembers(groupId, memberIds, withMessages): return "/_remove #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ",")) messages=\(onOff(withMessages))" - case let .apiLeaveGroup(groupId): return "/_leave #\(groupId)" - case let .apiListMembers(groupId): return "/_members #\(groupId)" - case let .apiUpdateGroupProfile(groupId, groupProfile): return "/_group_profile #\(groupId) \(encodeJSON(groupProfile))" - case let .apiCreateGroupLink(groupId, memberRole, short): return "/_create link #\(groupId) \(memberRole) short=\(onOff(short))" - case let .apiGroupLinkMemberRole(groupId, memberRole): return "/_set link role #\(groupId) \(memberRole)" - case let .apiDeleteGroupLink(groupId): return "/_delete link #\(groupId)" - case let .apiGetGroupLink(groupId): return "/_get link #\(groupId)" - case let .apiCreateMemberContact(groupId, groupMemberId): return "/_create member contact #\(groupId) \(groupMemberId)" - case let .apiSendMemberContactInvitation(contactId, mc): return "/_invite member contact @\(contactId) \(mc.cmdString)" - case let .apiTestProtoServer(userId, server): return "/_server test \(userId) \(server)" - case .apiGetServerOperators: return "/_operators" - case let .apiSetServerOperators(operators): return "/_operators \(encodeJSON(operators))" - case let .apiGetUserServers(userId): return "/_servers \(userId)" - case let .apiSetUserServers(userId, userServers): return "/_servers \(userId) \(encodeJSON(userServers))" - case let .apiValidateServers(userId, userServers): return "/_validate_servers \(userId) \(encodeJSON(userServers))" - case .apiGetUsageConditions: return "/_conditions" - case let .apiSetConditionsNotified(conditionsId): return "/_conditions_notified \(conditionsId)" - case let .apiAcceptConditions(conditionsId, operatorIds): return "/_accept_conditions \(conditionsId) \(joinedIds(operatorIds))" - case let .apiSetChatItemTTL(userId, seconds): return "/_ttl \(userId) \(chatItemTTLStr(seconds: seconds))" - case let .apiGetChatItemTTL(userId): return "/_ttl \(userId)" - case let .apiSetChatTTL(userId, type, id, seconds): return "/_ttl \(userId) \(ref(type, id)) \(chatItemTTLStr(seconds: seconds))" - case let .apiSetNetworkConfig(networkConfig): return "/_network \(encodeJSON(networkConfig))" - case .apiGetNetworkConfig: return "/network" - case let .apiSetNetworkInfo(networkInfo): return "/_network info \(encodeJSON(networkInfo))" - case .reconnectAllServers: return "/reconnect" - case let .reconnectServer(userId, smpServer): return "/reconnect \(userId) \(smpServer)" - 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 .apiContactQueueInfo(contactId): return "/_queue info @\(contactId)" - case let .apiGroupMemberQueueInfo(groupId, groupMemberId): return "/_queue info #\(groupId) \(groupMemberId)" - case let .apiSwitchContact(contactId): return "/_switch @\(contactId)" - case let .apiSwitchGroupMember(groupId, groupMemberId): return "/_switch #\(groupId) \(groupMemberId)" - case let .apiAbortSwitchContact(contactId): return "/_abort switch @\(contactId)" - case let .apiAbortSwitchGroupMember(groupId, groupMemberId): return "/_abort switch #\(groupId) \(groupMemberId)" - case let .apiSyncContactRatchet(contactId, force): if force { - return "/_sync @\(contactId) force=on" - } else { - return "/_sync @\(contactId)" - } - case let .apiSyncGroupMemberRatchet(groupId, groupMemberId, force): if force { - return "/_sync #\(groupId) \(groupMemberId) force=on" - } else { - return "/_sync #\(groupId) \(groupMemberId)" - } - case let .apiGetContactCode(contactId): return "/_get code @\(contactId)" - case let .apiGetGroupMemberCode(groupId, groupMemberId): return "/_get code #\(groupId) \(groupMemberId)" - case let .apiVerifyContact(contactId, .some(connectionCode)): return "/_verify code @\(contactId) \(connectionCode)" - case let .apiVerifyContact(contactId, .none): return "/_verify code @\(contactId)" - case let .apiVerifyGroupMember(groupId, groupMemberId, .some(connectionCode)): return "/_verify code #\(groupId) \(groupMemberId) \(connectionCode)" - case let .apiVerifyGroupMember(groupId, groupMemberId, .none): return "/_verify code #\(groupId) \(groupMemberId)" - case let .apiAddContact(userId, short, incognito): return "/_connect \(userId) short=\(onOff(short)) incognito=\(onOff(incognito))" - case let .apiSetConnectionIncognito(connId, incognito): return "/_set incognito :\(connId) \(onOff(incognito))" - case let .apiChangeConnectionUser(connId, userId): return "/_set conn user :\(connId) \(userId)" - case let .apiConnectPlan(userId, connLink): return "/_connect plan \(userId) \(connLink)" - case let .apiConnect(userId, incognito, connLink): return "/_connect \(userId) incognito=\(onOff(incognito)) \(connLink.connFullLink) \(connLink.connShortLink ?? "")" - case let .apiConnectContactViaAddress(userId, incognito, contactId): return "/_connect contact \(userId) incognito=\(onOff(incognito)) \(contactId)" - case let .apiDeleteChat(type, id, chatDeleteMode): return "/_delete \(ref(type, id)) \(chatDeleteMode.cmdString)" - case let .apiClearChat(type, id): return "/_clear chat \(ref(type, id))" - case let .apiListContacts(userId): return "/_contacts \(userId)" - case let .apiUpdateProfile(userId, profile): return "/_profile \(userId) \(encodeJSON(profile))" - case let .apiSetContactPrefs(contactId, preferences): return "/_set prefs @\(contactId) \(encodeJSON(preferences))" - case let .apiSetContactAlias(contactId, localAlias): return "/_set alias @\(contactId) \(localAlias.trimmingCharacters(in: .whitespaces))" - case let .apiSetGroupAlias(groupId, localAlias): return "/_set alias #\(groupId) \(localAlias.trimmingCharacters(in: .whitespaces))" - case let .apiSetConnectionAlias(connId, localAlias): return "/_set alias :\(connId) \(localAlias.trimmingCharacters(in: .whitespaces))" - case let .apiSetUserUIThemes(userId, themes): return "/_set theme user \(userId) \(themes != nil ? encodeJSON(themes) : "")" - case let .apiSetChatUIThemes(chatId, themes): return "/_set theme \(chatId) \(themes != nil ? encodeJSON(themes) : "")" - case let .apiCreateMyAddress(userId, short): return "/_address \(userId) short=\(onOff(short))" - case let .apiDeleteMyAddress(userId): return "/_delete_address \(userId)" - case let .apiShowMyAddress(userId): return "/_show_address \(userId)" - case let .apiSetProfileAddress(userId, on): return "/_profile_address \(userId) \(onOff(on))" - case let .apiAddressAutoAccept(userId, autoAccept): return "/_auto_accept \(userId) \(AutoAccept.cmdString(autoAccept))" - case let .apiAcceptContact(incognito, contactReqId): return "/_accept incognito=\(onOff(incognito)) \(contactReqId)" - case let .apiRejectContact(contactReqId): return "/_reject \(contactReqId)" - case let .apiSendCallInvitation(contact, callType): return "/_call invite @\(contact.apiId) \(encodeJSON(callType))" - case let .apiRejectCall(contact): return "/_call reject @\(contact.apiId)" - case let .apiSendCallOffer(contact, callOffer): return "/_call offer @\(contact.apiId) \(encodeJSON(callOffer))" - case let .apiSendCallAnswer(contact, answer): return "/_call answer @\(contact.apiId) \(encodeJSON(answer))" - case let .apiSendCallExtraInfo(contact, extraInfo): return "/_call extra @\(contact.apiId) \(encodeJSON(extraInfo))" - case let .apiEndCall(contact): return "/_call end @\(contact.apiId)" - case .apiGetCallInvitations: return "/_call get" - case let .apiCallStatus(contact, callStatus): return "/_call status @\(contact.apiId) \(callStatus.rawValue)" - case .apiGetNetworkStatuses: return "/_network_statuses" - case let .apiChatRead(type, id): return "/_read chat \(ref(type, id))" - case let .apiChatItemsRead(type, id, itemIds): return "/_read chat items \(ref(type, id)) \(joinedIds(itemIds))" - case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id)) \(onOff(unreadChat))" - case let .receiveFile(fileId, userApprovedRelays, encrypt, inline): return "/freceive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))\(onOffParam("inline", inline))" - case let .setFileToReceive(fileId, userApprovedRelays, encrypt): return "/_set_file_to_receive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(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 let .apiUploadStandaloneFile(userId, file): return "/_upload \(userId) \(file.filePath)" - case let .apiDownloadStandaloneFile(userId, link, file): return "/_download \(userId) \(link) \(file.filePath)" - case let .apiStandaloneFileInfo(link): return "/_download info \(link)" - case .showVersion: return "/version" - case let .getAgentSubsTotal(userId): return "/get subs total \(userId)" - case let .getAgentServersSummary(userId): return "/get servers summary \(userId)" - case .resetAgentServersStats: return "/reset servers stats" - case let .string(str): return str - } - } - } - - public var cmdType: String { - get { - switch self { - case .showActiveUser: return "showActiveUser" - case .createActiveUser: return "createActiveUser" - case .listUsers: return "listUsers" - case .apiSetActiveUser: return "apiSetActiveUser" - case .setAllContactReceipts: return "setAllContactReceipts" - case .apiSetUserContactReceipts: return "apiSetUserContactReceipts" - case .apiSetUserGroupReceipts: return "apiSetUserGroupReceipts" - case .apiHideUser: return "apiHideUser" - case .apiUnhideUser: return "apiUnhideUser" - case .apiMuteUser: return "apiMuteUser" - case .apiUnmuteUser: return "apiUnmuteUser" - case .apiDeleteUser: return "apiDeleteUser" - case .startChat: return "startChat" - case .checkChatRunning: return "checkChatRunning" - case .apiStopChat: return "apiStopChat" - case .apiActivateChat: return "apiActivateChat" - case .apiSuspendChat: return "apiSuspendChat" - case .apiSetAppFilePaths: return "apiSetAppFilePaths" - case .apiSetEncryptLocalFiles: return "apiSetEncryptLocalFiles" - case .apiExportArchive: return "apiExportArchive" - case .apiImportArchive: return "apiImportArchive" - case .apiDeleteStorage: return "apiDeleteStorage" - case .apiStorageEncryption: return "apiStorageEncryption" - case .testStorageEncryption: return "testStorageEncryption" - case .apiSaveSettings: return "apiSaveSettings" - case .apiGetSettings: return "apiGetSettings" - case .apiGetChatTags: return "apiGetChatTags" - case .apiGetChats: return "apiGetChats" - case .apiGetChat: return "apiGetChat" - case .apiGetChatItemInfo: return "apiGetChatItemInfo" - case .apiSendMessages: return "apiSendMessages" - case .apiCreateChatTag: return "apiCreateChatTag" - case .apiSetChatTags: return "apiSetChatTags" - case .apiDeleteChatTag: return "apiDeleteChatTag" - case .apiUpdateChatTag: return "apiUpdateChatTag" - case .apiReorderChatTags: return "apiReorderChatTags" - case .apiCreateChatItems: return "apiCreateChatItems" - case .apiReportMessage: return "apiReportMessage" - case .apiUpdateChatItem: return "apiUpdateChatItem" - case .apiDeleteChatItem: return "apiDeleteChatItem" - case .apiConnectContactViaAddress: return "apiConnectContactViaAddress" - case .apiDeleteMemberChatItem: return "apiDeleteMemberChatItem" - case .apiArchiveReceivedReports: return "apiArchiveReceivedReports" - case .apiDeleteReceivedReports: return "apiDeleteReceivedReports" - case .apiChatItemReaction: return "apiChatItemReaction" - case .apiGetReactionMembers: return "apiGetReactionMembers" - case .apiPlanForwardChatItems: return "apiPlanForwardChatItems" - case .apiForwardChatItems: return "apiForwardChatItems" - case .apiGetNtfToken: return "apiGetNtfToken" - case .apiRegisterToken: return "apiRegisterToken" - case .apiVerifyToken: return "apiVerifyToken" - case .apiCheckToken: return "apiCheckToken" - case .apiDeleteToken: return "apiDeleteToken" - case .apiGetNtfConns: return "apiGetNtfConns" - case .apiGetConnNtfMessages: return "apiGetConnNtfMessages" - case .apiNewGroup: return "apiNewGroup" - case .apiAddMember: return "apiAddMember" - case .apiJoinGroup: return "apiJoinGroup" - case .apiMembersRole: return "apiMembersRole" - case .apiBlockMembersForAll: return "apiBlockMembersForAll" - case .apiRemoveMembers: return "apiRemoveMembers" - case .apiLeaveGroup: return "apiLeaveGroup" - case .apiListMembers: return "apiListMembers" - case .apiUpdateGroupProfile: return "apiUpdateGroupProfile" - case .apiCreateGroupLink: return "apiCreateGroupLink" - case .apiGroupLinkMemberRole: return "apiGroupLinkMemberRole" - case .apiDeleteGroupLink: return "apiDeleteGroupLink" - case .apiGetGroupLink: return "apiGetGroupLink" - case .apiCreateMemberContact: return "apiCreateMemberContact" - case .apiSendMemberContactInvitation: return "apiSendMemberContactInvitation" - case .apiTestProtoServer: return "apiTestProtoServer" - case .apiGetServerOperators: return "apiGetServerOperators" - case .apiSetServerOperators: return "apiSetServerOperators" - case .apiGetUserServers: return "apiGetUserServers" - case .apiSetUserServers: return "apiSetUserServers" - case .apiValidateServers: return "apiValidateServers" - case .apiGetUsageConditions: return "apiGetUsageConditions" - case .apiSetConditionsNotified: return "apiSetConditionsNotified" - case .apiAcceptConditions: return "apiAcceptConditions" - case .apiSetChatItemTTL: return "apiSetChatItemTTL" - case .apiGetChatItemTTL: return "apiGetChatItemTTL" - case .apiSetChatTTL: return "apiSetChatTTL" - case .apiSetNetworkConfig: return "apiSetNetworkConfig" - case .apiGetNetworkConfig: return "apiGetNetworkConfig" - case .apiSetNetworkInfo: return "apiSetNetworkInfo" - case .reconnectAllServers: return "reconnectAllServers" - case .reconnectServer: return "reconnectServer" - case .apiSetChatSettings: return "apiSetChatSettings" - case .apiSetMemberSettings: return "apiSetMemberSettings" - case .apiContactInfo: return "apiContactInfo" - case .apiGroupMemberInfo: return "apiGroupMemberInfo" - case .apiContactQueueInfo: return "apiContactQueueInfo" - case .apiGroupMemberQueueInfo: return "apiGroupMemberQueueInfo" - case .apiSwitchContact: return "apiSwitchContact" - case .apiSwitchGroupMember: return "apiSwitchGroupMember" - case .apiAbortSwitchContact: return "apiAbortSwitchContact" - case .apiAbortSwitchGroupMember: return "apiAbortSwitchGroupMember" - case .apiSyncContactRatchet: return "apiSyncContactRatchet" - case .apiSyncGroupMemberRatchet: return "apiSyncGroupMemberRatchet" - case .apiGetContactCode: return "apiGetContactCode" - case .apiGetGroupMemberCode: return "apiGetGroupMemberCode" - case .apiVerifyContact: return "apiVerifyContact" - case .apiVerifyGroupMember: return "apiVerifyGroupMember" - case .apiAddContact: return "apiAddContact" - case .apiSetConnectionIncognito: return "apiSetConnectionIncognito" - case .apiChangeConnectionUser: return "apiChangeConnectionUser" - case .apiConnectPlan: return "apiConnectPlan" - case .apiConnect: return "apiConnect" - case .apiDeleteChat: return "apiDeleteChat" - case .apiClearChat: return "apiClearChat" - case .apiListContacts: return "apiListContacts" - case .apiUpdateProfile: return "apiUpdateProfile" - case .apiSetContactPrefs: return "apiSetContactPrefs" - case .apiSetContactAlias: return "apiSetContactAlias" - case .apiSetGroupAlias: return "apiSetGroupAlias" - case .apiSetConnectionAlias: return "apiSetConnectionAlias" - case .apiSetUserUIThemes: return "apiSetUserUIThemes" - case .apiSetChatUIThemes: return "apiSetChatUIThemes" - case .apiCreateMyAddress: return "apiCreateMyAddress" - case .apiDeleteMyAddress: return "apiDeleteMyAddress" - case .apiShowMyAddress: return "apiShowMyAddress" - case .apiSetProfileAddress: return "apiSetProfileAddress" - case .apiAddressAutoAccept: return "apiAddressAutoAccept" - case .apiAcceptContact: return "apiAcceptContact" - case .apiRejectContact: return "apiRejectContact" - case .apiSendCallInvitation: return "apiSendCallInvitation" - case .apiRejectCall: return "apiRejectCall" - case .apiSendCallOffer: return "apiSendCallOffer" - case .apiSendCallAnswer: return "apiSendCallAnswer" - case .apiSendCallExtraInfo: return "apiSendCallExtraInfo" - case .apiEndCall: return "apiEndCall" - case .apiGetCallInvitations: return "apiGetCallInvitations" - case .apiCallStatus: return "apiCallStatus" - case .apiGetNetworkStatuses: return "apiGetNetworkStatuses" - case .apiChatRead: return "apiChatRead" - case .apiChatItemsRead: return "apiChatItemsRead" - case .apiChatUnread: return "apiChatUnread" - 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 .apiUploadStandaloneFile: return "apiUploadStandaloneFile" - case .apiDownloadStandaloneFile: return "apiDownloadStandaloneFile" - case .apiStandaloneFileInfo: return "apiStandaloneFileInfo" - case .showVersion: return "showVersion" - case .getAgentSubsTotal: return "getAgentSubsTotal" - case .getAgentServersSummary: return "getAgentServersSummary" - case .resetAgentServersStats: return "resetAgentServersStats" - case .string: return "console command" - } - } - } - - func ref(_ type: ChatType, _ id: Int64) -> String { - "\(type.rawValue)\(id)" - } - - func joinedIds(_ ids: [Int64]) -> String { - ids.map { "\($0)" }.joined(separator: ",") - } - - func chatItemTTLStr(seconds: Int64?) -> String { - if let seconds = seconds { - return String(seconds) - } else { - return "default" - } - } - - public var obfuscated: ChatCommand { - switch self { - case let .apiStorageEncryption(cfg): - return .apiStorageEncryption(config: DBEncryptionConfig(currentKey: obfuscate(cfg.currentKey), newKey: obfuscate(cfg.newKey))) - case let .apiSetActiveUser(userId, viewPwd): - return .apiSetActiveUser(userId: userId, viewPwd: obfuscate(viewPwd)) - case let .apiHideUser(userId, viewPwd): - return .apiHideUser(userId: userId, viewPwd: obfuscate(viewPwd)) - case let .apiUnhideUser(userId, viewPwd): - return .apiUnhideUser(userId: userId, viewPwd: obfuscate(viewPwd)) - case let .apiDeleteUser(userId, delSMPQueues, viewPwd): - return .apiDeleteUser(userId: userId, delSMPQueues: delSMPQueues, viewPwd: obfuscate(viewPwd)) - case let .testStorageEncryption(key): - return .testStorageEncryption(key: obfuscate(key)) - default: return self - } - } - - private func obfuscate(_ s: String) -> String { - s == "" ? "" : "***" - } - - private func obfuscate(_ s: String?) -> String? { - if let s = s { - return obfuscate(s) - } - return nil - } - - private func onOffParam(_ param: String, _ b: Bool?) -> String { - if let b = b { - return " \(param)=\(onOff(b))" - } - return "" - } - - private func maybePwd(_ pwd: String?) -> String { - pwd == "" || pwd == nil ? "" : " " + encodeJSON(pwd) - } +public protocol ChatCmdProtocol { + var cmdString: String { get } } -private func onOff(_ b: Bool) -> String { +public func onOff(_ b: Bool) -> String { b ? "on" : "off" } -public struct APIResponse: Decodable { - var resp: ChatResponse +public struct APIResponse: Decodable { + public var resp: ChatRespProtocol } -public enum ChatResponse: Decodable, Error { - case response(type: String, json: String) - case activeUser(user: User) - case usersList(users: [UserInfo]) - case chatStarted - case chatRunning - case chatStopped - case chatSuspended - case apiChats(user: UserRef, chats: [ChatData]) - case apiChat(user: UserRef, chat: ChatData, navInfo: NavigationInfo?) - case chatTags(user: UserRef, userTags: [ChatTag]) - case chatItemInfo(user: UserRef, chatItem: AChatItem, chatItemInfo: ChatItemInfo) - case serverTestResult(user: UserRef, testServer: String, testFailure: ProtocolTestFailure?) - case serverOperatorConditions(conditions: ServerOperatorConditions) - case userServers(user: UserRef, userServers: [UserOperatorServers]) - case userServersValidation(user: UserRef, serverErrors: [UserServersError]) - case usageConditions(usageConditions: UsageConditions, conditionsText: String, acceptedConditions: UsageConditions?) - case chatItemTTL(user: UserRef, chatItemTTL: Int64?) - case networkConfig(networkConfig: NetCfg) - case contactInfo(user: UserRef, contact: Contact, connectionStats_: ConnectionStats?, customUserProfile: Profile?) - case groupMemberInfo(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats_: ConnectionStats?) - case queueInfo(user: UserRef, rcvMsgInfo: RcvMsgInfo?, queueInfo: ServerQueueInfo) - case contactSwitchStarted(user: UserRef, contact: Contact, connectionStats: ConnectionStats) - case groupMemberSwitchStarted(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats) - case contactSwitchAborted(user: UserRef, contact: Contact, connectionStats: ConnectionStats) - case groupMemberSwitchAborted(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats) - case contactSwitch(user: UserRef, contact: Contact, switchProgress: SwitchProgress) - case groupMemberSwitch(user: UserRef, groupInfo: GroupInfo, member: GroupMember, switchProgress: SwitchProgress) - case contactRatchetSyncStarted(user: UserRef, contact: Contact, connectionStats: ConnectionStats) - case groupMemberRatchetSyncStarted(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats) - case contactRatchetSync(user: UserRef, contact: Contact, ratchetSyncProgress: RatchetSyncProgress) - case groupMemberRatchetSync(user: UserRef, groupInfo: GroupInfo, member: GroupMember, ratchetSyncProgress: RatchetSyncProgress) - case contactVerificationReset(user: UserRef, contact: Contact) - case groupMemberVerificationReset(user: UserRef, groupInfo: GroupInfo, member: GroupMember) - case contactCode(user: UserRef, contact: Contact, connectionCode: String) - case groupMemberCode(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionCode: String) - case connectionVerified(user: UserRef, verified: Bool, expectedCode: String) - case tagsUpdated(user: UserRef, userTags: [ChatTag], chatTags: [Int64]) - case invitation(user: UserRef, connLinkInvitation: CreatedConnLink, connection: PendingContactConnection) - case connectionIncognitoUpdated(user: UserRef, toConnection: PendingContactConnection) - case connectionUserChanged(user: UserRef, fromConnection: PendingContactConnection, toConnection: PendingContactConnection, newUser: UserRef) - case connectionPlan(user: UserRef, connLink: CreatedConnLink, connectionPlan: ConnectionPlan) - case sentConfirmation(user: UserRef, connection: PendingContactConnection) - case sentInvitation(user: UserRef, connection: PendingContactConnection) - case sentInvitationToContact(user: UserRef, contact: Contact, customUserProfile: Profile?) - case contactAlreadyExists(user: UserRef, contact: Contact) - case contactDeleted(user: UserRef, contact: Contact) - case contactDeletedByContact(user: UserRef, contact: Contact) - case chatCleared(user: UserRef, chatInfo: ChatInfo) - case userProfileNoChange(user: User) - case userProfileUpdated(user: User, fromProfile: Profile, toProfile: Profile, updateSummary: UserProfileUpdateSummary) - case userPrivacy(user: User, updatedUser: User) - case contactAliasUpdated(user: UserRef, toContact: Contact) - case groupAliasUpdated(user: UserRef, toGroup: GroupInfo) - case connectionAliasUpdated(user: UserRef, toConnection: PendingContactConnection) - case contactPrefsUpdated(user: User, fromContact: Contact, toContact: Contact) - case userContactLink(user: User, contactLink: UserContactLink) - case userContactLinkUpdated(user: User, contactLink: UserContactLink) - case userContactLinkCreated(user: User, connLinkContact: CreatedConnLink) - case userContactLinkDeleted(user: User) - case contactConnected(user: UserRef, contact: Contact, userCustomProfile: Profile?) - case contactConnecting(user: UserRef, contact: Contact) - case contactSndReady(user: UserRef, contact: Contact) - case receivedContactRequest(user: UserRef, contactRequest: UserContactRequest) - 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) - case networkStatus(networkStatus: NetworkStatus, connections: [String]) - case networkStatuses(user_: UserRef?, networkStatuses: [ConnNetworkStatus]) - case groupSubscribed(user: UserRef, groupInfo: GroupRef) - case memberSubErrors(user: UserRef, memberSubErrors: [MemberSubError]) - case groupEmpty(user: UserRef, groupInfo: GroupInfo) - case userContactLinkSubscribed - case newChatItems(user: UserRef, chatItems: [AChatItem]) - case groupChatItemsDeleted(user: UserRef, groupInfo: GroupInfo, chatItemIDs: Set, byUser: Bool, member_: GroupMember?) - case forwardPlan(user: UserRef, chatItemIds: [Int64], forwardConfirmation: ForwardConfirmation?) - case chatItemsStatusesUpdated(user: UserRef, chatItems: [AChatItem]) - case chatItemUpdated(user: UserRef, chatItem: AChatItem) - case chatItemNotChanged(user: UserRef, chatItem: AChatItem) - case chatItemReaction(user: UserRef, added: Bool, reaction: ACIReaction) - case reactionMembers(user: UserRef, memberReactions: [MemberReaction]) - case chatItemsDeleted(user: UserRef, chatItemDeletions: [ChatItemDeletion], byUser: Bool) - case contactsList(user: UserRef, contacts: [Contact]) - // group events - 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 businessLinkConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember, fromContact: Contact) - case userDeletedMembers(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], withMessages: Bool) - case leftMemberUser(user: UserRef, groupInfo: GroupInfo) - case groupMembers(user: UserRef, group: Group) - case receivedGroupInvitation(user: UserRef, groupInfo: GroupInfo, contact: Contact, memberRole: GroupMemberRole) - case groupDeletedUser(user: UserRef, groupInfo: GroupInfo) - case joinedGroupMemberConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember, member: GroupMember) - case memberRole(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, member: GroupMember, fromRole: GroupMemberRole, toRole: GroupMemberRole) - case membersRoleUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], toRole: GroupMemberRole) - case memberBlockedForAll(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, member: GroupMember, blocked: Bool) - case membersBlockedForAllUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], blocked: Bool) - case deletedMemberUser(user: UserRef, groupInfo: GroupInfo, member: GroupMember, withMessages: Bool) - case deletedMember(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, deletedMember: GroupMember, withMessages: Bool) - case leftMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember) - case groupDeleted(user: UserRef, groupInfo: GroupInfo, member: GroupMember) - case contactsMerged(user: UserRef, intoContact: Contact, mergedContact: Contact) - case groupInvitation(user: UserRef, groupInfo: GroupInfo) // unused - case userJoinedGroup(user: UserRef, groupInfo: GroupInfo) - case joinedGroupMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember) - case connectedToGroupMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember, memberContact: Contact?) - case groupRemoved(user: UserRef, groupInfo: GroupInfo) // unused - case groupUpdated(user: UserRef, toGroup: GroupInfo) - case groupLinkCreated(user: UserRef, groupInfo: GroupInfo, connLinkContact: CreatedConnLink, memberRole: GroupMemberRole) - case groupLink(user: UserRef, groupInfo: GroupInfo, connLinkContact: CreatedConnLink, memberRole: GroupMemberRole) - case groupLinkDeleted(user: UserRef, groupInfo: GroupInfo) - case newMemberContact(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember) - case newMemberContactSentInv(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember) - case newMemberContactReceivedInv(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember) - // receiving file events - case rcvFileAccepted(user: UserRef, chatItem: AChatItem) - case rcvFileAcceptedSndCancelled(user: UserRef, rcvFileTransfer: RcvFileTransfer) - case standaloneFileInfo(fileMeta: MigrationFileLinkData?) - case rcvStandaloneFileCreated(user: UserRef, rcvFileTransfer: RcvFileTransfer) - case rcvFileStart(user: UserRef, chatItem: AChatItem) // send by chats - case rcvFileProgressXFTP(user: UserRef, chatItem_: AChatItem?, receivedSize: Int64, totalSize: Int64, rcvFileTransfer: RcvFileTransfer) - case rcvFileComplete(user: UserRef, chatItem: AChatItem) - case rcvStandaloneFileComplete(user: UserRef, targetPath: String, rcvFileTransfer: RcvFileTransfer) - case rcvFileCancelled(user: UserRef, chatItem_: AChatItem?, rcvFileTransfer: RcvFileTransfer) - case rcvFileSndCancelled(user: UserRef, chatItem: AChatItem, rcvFileTransfer: RcvFileTransfer) - case rcvFileError(user: UserRef, chatItem_: AChatItem?, agentError: AgentErrorType, rcvFileTransfer: RcvFileTransfer) - case rcvFileWarning(user: UserRef, chatItem_: AChatItem?, agentError: AgentErrorType, rcvFileTransfer: RcvFileTransfer) - // sending file events - case sndFileStart(user: UserRef, chatItem: AChatItem, sndFileTransfer: SndFileTransfer) - case sndFileComplete(user: UserRef, chatItem: AChatItem, sndFileTransfer: SndFileTransfer) - case sndFileRcvCancelled(user: UserRef, chatItem_: AChatItem?, sndFileTransfer: SndFileTransfer) - case sndFileCancelled(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, sndFileTransfers: [SndFileTransfer]) - case sndStandaloneFileCreated(user: UserRef, fileTransferMeta: FileTransferMeta) // returned by _upload - case sndFileStartXFTP(user: UserRef, chatItem: AChatItem, fileTransferMeta: FileTransferMeta) // not used - case sndFileProgressXFTP(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, sentSize: Int64, totalSize: Int64) - case sndFileRedirectStartXFTP(user: UserRef, fileTransferMeta: FileTransferMeta, redirectMeta: FileTransferMeta) - case sndFileCompleteXFTP(user: UserRef, chatItem: AChatItem, fileTransferMeta: FileTransferMeta) - case sndStandaloneFileComplete(user: UserRef, fileTransferMeta: FileTransferMeta, rcvURIs: [String]) - case sndFileCancelledXFTP(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta) - case sndFileError(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, errorMessage: String) - case sndFileWarning(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, errorMessage: String) - // call events - case callInvitation(callInvitation: RcvCallInvitation) - case callOffer(user: UserRef, contact: Contact, callType: CallType, offer: WebRTCSession, sharedKey: String?, askConfirmation: Bool) - case callAnswer(user: UserRef, contact: Contact, answer: WebRTCSession) - case callExtraInfo(user: UserRef, contact: Contact, extraInfo: WebRTCExtraInfo) - case callEnded(user: UserRef, contact: Contact) - case callInvitations(callInvitations: [RcvCallInvitation]) - case ntfTokenStatus(status: NtfTknStatus) - case ntfToken(token: DeviceToken, status: NtfTknStatus, ntfMode: NotificationsMode, ntfServer: String) - case ntfConns(ntfConns: [NtfConn]) - case connNtfMessages(receivedMsgs: [NtfMsgInfo?]) - case ntfMessage(user: UserRef, connEntity: ConnectionEntity, ntfMessage: NtfMsgAckInfo) - case contactConnectionDeleted(user: UserRef, connection: PendingContactConnection) - case contactDisabled(user: UserRef, contact: Contact) - // 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(rcsState: RemoteCtrlSessionState, rcStopReason: RemoteCtrlStopReason) - // pq - case contactPQEnabled(user: UserRef, contact: Contact, pqEnabled: Bool) - // misc - case versionInfo(versionInfo: CoreVersionInfo, chatMigrations: [UpMigration], agentMigrations: [UpMigration]) - case cmdOk(user: UserRef?) - case agentSubsTotal(user: UserRef, subsTotal: SMPServerSubs, hasSession: Bool) - case agentServersSummary(user: UserRef, serversSummary: PresentedServersSummary) - case agentSubsSummary(user: UserRef, subsSummary: SMPServerSubs) - case chatCmdError(user_: UserRef?, chatError: ChatError) - case chatError(user_: UserRef?, chatError: ChatError) - case archiveExported(archiveErrors: [ArchiveError]) - case archiveImported(archiveErrors: [ArchiveError]) - case appSettings(appSettings: AppSettings) +public protocol ChatRespProtocol: Decodable, Error { + var responseType: String { get } + var details: String { get } + static func chatResponse(_ s: String) -> Self + var chatError: ChatError? { get } + var chatErrorType: ChatErrorType? { get } +} - public var responseType: String { - get { - switch self { - case let .response(type, _): return "* \(type)" - case .activeUser: return "activeUser" - case .usersList: return "usersList" - case .chatStarted: return "chatStarted" - case .chatRunning: return "chatRunning" - case .chatStopped: return "chatStopped" - case .chatSuspended: return "chatSuspended" - case .apiChats: return "apiChats" - case .apiChat: return "apiChat" - case .chatTags: return "chatTags" - case .chatItemInfo: return "chatItemInfo" - case .serverTestResult: return "serverTestResult" - case .serverOperatorConditions: return "serverOperators" - case .userServers: return "userServers" - case .userServersValidation: return "userServersValidation" - case .usageConditions: return "usageConditions" - case .chatItemTTL: return "chatItemTTL" - case .networkConfig: return "networkConfig" - case .contactInfo: return "contactInfo" - case .groupMemberInfo: return "groupMemberInfo" - case .queueInfo: return "queueInfo" - case .contactSwitchStarted: return "contactSwitchStarted" - case .groupMemberSwitchStarted: return "groupMemberSwitchStarted" - case .contactSwitchAborted: return "contactSwitchAborted" - case .groupMemberSwitchAborted: return "groupMemberSwitchAborted" - case .contactSwitch: return "contactSwitch" - case .groupMemberSwitch: return "groupMemberSwitch" - case .contactRatchetSyncStarted: return "contactRatchetSyncStarted" - case .groupMemberRatchetSyncStarted: return "groupMemberRatchetSyncStarted" - case .contactRatchetSync: return "contactRatchetSync" - case .groupMemberRatchetSync: return "groupMemberRatchetSync" - case .contactVerificationReset: return "contactVerificationReset" - case .groupMemberVerificationReset: return "groupMemberVerificationReset" - case .contactCode: return "contactCode" - case .groupMemberCode: return "groupMemberCode" - case .connectionVerified: return "connectionVerified" - case .tagsUpdated: return "tagsUpdated" - case .invitation: return "invitation" - case .connectionIncognitoUpdated: return "connectionIncognitoUpdated" - case .connectionUserChanged: return "connectionUserChanged" - case .connectionPlan: return "connectionPlan" - case .sentConfirmation: return "sentConfirmation" - case .sentInvitation: return "sentInvitation" - case .sentInvitationToContact: return "sentInvitationToContact" - case .contactAlreadyExists: return "contactAlreadyExists" - case .contactDeleted: return "contactDeleted" - case .contactDeletedByContact: return "contactDeletedByContact" - case .chatCleared: return "chatCleared" - case .userProfileNoChange: return "userProfileNoChange" - case .userProfileUpdated: return "userProfileUpdated" - case .userPrivacy: return "userPrivacy" - case .contactAliasUpdated: return "contactAliasUpdated" - case .groupAliasUpdated: return "groupAliasUpdated" - case .connectionAliasUpdated: return "connectionAliasUpdated" - case .contactPrefsUpdated: return "contactPrefsUpdated" - case .userContactLink: return "userContactLink" - case .userContactLinkUpdated: return "userContactLinkUpdated" - case .userContactLinkCreated: return "userContactLinkCreated" - case .userContactLinkDeleted: return "userContactLinkDeleted" - case .contactConnected: return "contactConnected" - case .contactConnecting: return "contactConnecting" - case .contactSndReady: return "contactSndReady" - case .receivedContactRequest: return "receivedContactRequest" - case .acceptingContactRequest: return "acceptingContactRequest" - case .contactRequestRejected: return "contactRequestRejected" - case .contactUpdated: return "contactUpdated" - case .groupMemberUpdated: return "groupMemberUpdated" - case .networkStatus: return "networkStatus" - case .networkStatuses: return "networkStatuses" - case .groupSubscribed: return "groupSubscribed" - case .memberSubErrors: return "memberSubErrors" - case .groupEmpty: return "groupEmpty" - case .userContactLinkSubscribed: return "userContactLinkSubscribed" - case .newChatItems: return "newChatItems" - case .groupChatItemsDeleted: return "groupChatItemsDeleted" - case .forwardPlan: return "forwardPlan" - case .chatItemsStatusesUpdated: return "chatItemsStatusesUpdated" - case .chatItemUpdated: return "chatItemUpdated" - case .chatItemNotChanged: return "chatItemNotChanged" - case .chatItemReaction: return "chatItemReaction" - case .reactionMembers: return "reactionMembers" - case .chatItemsDeleted: return "chatItemsDeleted" - case .contactsList: return "contactsList" - case .groupCreated: return "groupCreated" - case .sentGroupInvitation: return "sentGroupInvitation" - case .userAcceptedGroupSent: return "userAcceptedGroupSent" - case .groupLinkConnecting: return "groupLinkConnecting" - case .businessLinkConnecting: return "businessLinkConnecting" - case .userDeletedMembers: return "userDeletedMembers" - case .leftMemberUser: return "leftMemberUser" - case .groupMembers: return "groupMembers" - case .receivedGroupInvitation: return "receivedGroupInvitation" - case .groupDeletedUser: return "groupDeletedUser" - case .joinedGroupMemberConnecting: return "joinedGroupMemberConnecting" - case .memberRole: return "memberRole" - case .membersRoleUser: return "membersRoleUser" - case .memberBlockedForAll: return "memberBlockedForAll" - case .membersBlockedForAllUser: return "membersBlockedForAllUser" - case .deletedMemberUser: return "deletedMemberUser" - case .deletedMember: return "deletedMember" - case .leftMember: return "leftMember" - case .groupDeleted: return "groupDeleted" - case .contactsMerged: return "contactsMerged" - case .groupInvitation: return "groupInvitation" - case .userJoinedGroup: return "userJoinedGroup" - case .joinedGroupMember: return "joinedGroupMember" - case .connectedToGroupMember: return "connectedToGroupMember" - case .groupRemoved: return "groupRemoved" - case .groupUpdated: return "groupUpdated" - case .groupLinkCreated: return "groupLinkCreated" - case .groupLink: return "groupLink" - case .groupLinkDeleted: return "groupLinkDeleted" - case .newMemberContact: return "newMemberContact" - case .newMemberContactSentInv: return "newMemberContactSentInv" - case .newMemberContactReceivedInv: return "newMemberContactReceivedInv" - case .rcvFileAccepted: return "rcvFileAccepted" - case .rcvFileAcceptedSndCancelled: return "rcvFileAcceptedSndCancelled" - case .standaloneFileInfo: return "standaloneFileInfo" - case .rcvStandaloneFileCreated: return "rcvStandaloneFileCreated" - case .rcvFileStart: return "rcvFileStart" - case .rcvFileProgressXFTP: return "rcvFileProgressXFTP" - case .rcvFileComplete: return "rcvFileComplete" - case .rcvStandaloneFileComplete: return "rcvStandaloneFileComplete" - case .rcvFileCancelled: return "rcvFileCancelled" - case .rcvFileSndCancelled: return "rcvFileSndCancelled" - case .rcvFileError: return "rcvFileError" - case .rcvFileWarning: return "rcvFileWarning" - case .sndFileStart: return "sndFileStart" - case .sndFileComplete: return "sndFileComplete" - case .sndFileCancelled: return "sndFileCancelled" - case .sndStandaloneFileCreated: return "sndStandaloneFileCreated" - case .sndFileStartXFTP: return "sndFileStartXFTP" - case .sndFileProgressXFTP: return "sndFileProgressXFTP" - case .sndFileRedirectStartXFTP: return "sndFileRedirectStartXFTP" - case .sndFileRcvCancelled: return "sndFileRcvCancelled" - case .sndFileCompleteXFTP: return "sndFileCompleteXFTP" - case .sndStandaloneFileComplete: return "sndStandaloneFileComplete" - case .sndFileCancelledXFTP: return "sndFileCancelledXFTP" - case .sndFileError: return "sndFileError" - case .sndFileWarning: return "sndFileWarning" - case .callInvitation: return "callInvitation" - case .callOffer: return "callOffer" - case .callAnswer: return "callAnswer" - case .callExtraInfo: return "callExtraInfo" - case .callEnded: return "callEnded" - case .callInvitations: return "callInvitations" - case .ntfTokenStatus: return "ntfTokenStatus" - case .ntfToken: return "ntfToken" - case .ntfConns: return "ntfConns" - case .connNtfMessages: return "connNtfMessages" - case .ntfMessage: return "ntfMessage" - case .contactConnectionDeleted: return "contactConnectionDeleted" - case .contactDisabled: return "contactDisabled" - 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 .contactPQEnabled: return "contactPQEnabled" - case .versionInfo: return "versionInfo" - case .cmdOk: return "cmdOk" - case .agentSubsTotal: return "agentSubsTotal" - case .agentServersSummary: return "agentServersSummary" - case .agentSubsSummary: return "agentSubsSummary" - case .chatCmdError: return "chatCmdError" - case .chatError: return "chatError" - case .archiveExported: return "archiveExported" - case .archiveImported: return "archiveImported" - case .appSettings: return "appSettings" +public func parseApiChats(_ jResp: NSDictionary) -> (user: UserRef, chats: [ChatData])? { + if let jApiChats = jResp["apiChats"] as? NSDictionary, + let user: UserRef = try? decodeObject(jApiChats["user"] as Any), + let jChats = jApiChats["chats"] as? NSArray { + let chats = jChats.map { jChat in + if let chatData = try? parseChatData(jChat) { + return chatData.0 } + return ChatData.invalidJSON(serializeJSON(jChat, options: .prettyPrinted) ?? "") } - } - - public var details: String { - get { - switch self { - case let .response(_, json): return json - case let .activeUser(user): return String(describing: user) - case let .usersList(users): return String(describing: users) - case .chatStarted: return noDetails - case .chatRunning: return noDetails - case .chatStopped: return noDetails - case .chatSuspended: return noDetails - case let .apiChats(u, chats): return withUser(u, String(describing: chats)) - case let .apiChat(u, chat, navInfo): return withUser(u, "chat: \(String(describing: chat))\nnavInfo: \(String(describing: navInfo))") - case let .chatTags(u, userTags): return withUser(u, "userTags: \(String(describing: userTags))") - case let .chatItemInfo(u, chatItem, chatItemInfo): return withUser(u, "chatItem: \(String(describing: chatItem))\nchatItemInfo: \(String(describing: chatItemInfo))") - case let .serverTestResult(u, server, testFailure): return withUser(u, "server: \(server)\nresult: \(String(describing: testFailure))") - case let .serverOperatorConditions(conditions): return "conditions: \(String(describing: conditions))" - case let .userServers(u, userServers): return withUser(u, "userServers: \(String(describing: userServers))") - case let .userServersValidation(u, serverErrors): return withUser(u, "serverErrors: \(String(describing: serverErrors))") - case let .usageConditions(usageConditions, _, acceptedConditions): return "usageConditions: \(String(describing: usageConditions))\nacceptedConditions: \(String(describing: acceptedConditions))" - 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 .groupMemberInfo(u, groupInfo, member, connectionStats_): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats_: \(String(describing: connectionStats_))") - case let .queueInfo(u, rcvMsgInfo, queueInfo): - let msgInfo = if let info = rcvMsgInfo { encodeJSON(info) } else { "none" } - return withUser(u, "rcvMsgInfo: \(msgInfo)\nqueueInfo: \(encodeJSON(queueInfo))") - 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))") - case let .contactSwitchAborted(u, contact, connectionStats): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))") - case let .groupMemberSwitchAborted(u, groupInfo, member, connectionStats): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats: \(String(describing: connectionStats))") - case let .contactSwitch(u, contact, switchProgress): return withUser(u, "contact: \(String(describing: contact))\nswitchProgress: \(String(describing: switchProgress))") - case let .groupMemberSwitch(u, groupInfo, member, switchProgress): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nswitchProgress: \(String(describing: switchProgress))") - case let .contactRatchetSyncStarted(u, contact, connectionStats): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))") - case let .groupMemberRatchetSyncStarted(u, groupInfo, member, connectionStats): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats: \(String(describing: connectionStats))") - case let .contactRatchetSync(u, contact, ratchetSyncProgress): return withUser(u, "contact: \(String(describing: contact))\nratchetSyncProgress: \(String(describing: ratchetSyncProgress))") - case let .groupMemberRatchetSync(u, groupInfo, member, ratchetSyncProgress): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nratchetSyncProgress: \(String(describing: ratchetSyncProgress))") - case let .contactVerificationReset(u, contact): return withUser(u, "contact: \(String(describing: contact))") - case let .groupMemberVerificationReset(u, groupInfo, member): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))") - case let .contactCode(u, contact, connectionCode): return withUser(u, "contact: \(String(describing: contact))\nconnectionCode: \(connectionCode)") - case let .groupMemberCode(u, groupInfo, member, connectionCode): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionCode: \(connectionCode)") - case let .connectionVerified(u, verified, expectedCode): return withUser(u, "verified: \(verified)\nconnectionCode: \(expectedCode)") - case let .tagsUpdated(u, userTags, chatTags): return withUser(u, "userTags: \(String(describing: userTags))\nchatTags: \(String(describing: chatTags))") - case let .invitation(u, connLinkInvitation, connection): return withUser(u, "connLinkInvitation: \(connLinkInvitation)\nconnection: \(connection)") - case let .connectionIncognitoUpdated(u, toConnection): return withUser(u, String(describing: toConnection)) - case let .connectionUserChanged(u, fromConnection, toConnection, newUser): return withUser(u, "fromConnection: \(String(describing: fromConnection))\ntoConnection: \(String(describing: toConnection))\nnewUserId: \(String(describing: newUser.userId))") - case let .connectionPlan(u, connLink, connectionPlan): return withUser(u, "connLink: \(String(describing: connLink))\nconnectionPlan: \(String(describing: connectionPlan))") - case let .sentConfirmation(u, connection): return withUser(u, String(describing: connection)) - case let .sentInvitation(u, connection): return withUser(u, String(describing: connection)) - case let .sentInvitationToContact(u, contact, _): return withUser(u, String(describing: contact)) - case let .contactAlreadyExists(u, contact): return withUser(u, String(describing: contact)) - case let .contactDeleted(u, contact): return withUser(u, String(describing: contact)) - case let .contactDeletedByContact(u, contact): return withUser(u, String(describing: contact)) - case let .chatCleared(u, chatInfo): return withUser(u, String(describing: chatInfo)) - case .userProfileNoChange: return noDetails - case let .userProfileUpdated(u, _, toProfile, _): return withUser(u, String(describing: toProfile)) - case let .userPrivacy(u, updatedUser): return withUser(u, String(describing: updatedUser)) - case let .contactAliasUpdated(u, toContact): return withUser(u, String(describing: toContact)) - case let .groupAliasUpdated(u, toGroup): return withUser(u, String(describing: toGroup)) - case let .connectionAliasUpdated(u, toConnection): return withUser(u, String(describing: toConnection)) - case let .contactPrefsUpdated(u, fromContact, toContact): return withUser(u, "fromContact: \(String(describing: fromContact))\ntoContact: \(String(describing: toContact))") - case let .userContactLink(u, contactLink): return withUser(u, contactLink.responseDetails) - case let .userContactLinkUpdated(u, contactLink): return withUser(u, contactLink.responseDetails) - case let .userContactLinkCreated(u, connLink): return withUser(u, String(describing: connLink)) - case .userContactLinkDeleted: return noDetails - case let .contactConnected(u, contact, _): return withUser(u, String(describing: contact)) - case let .contactConnecting(u, contact): return withUser(u, String(describing: contact)) - case let .contactSndReady(u, contact): return withUser(u, String(describing: contact)) - case let .receivedContactRequest(u, contactRequest): return withUser(u, String(describing: contactRequest)) - 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 .networkStatus(status, conns): return "networkStatus: \(String(describing: status))\nconnections: \(String(describing: conns))" - case let .networkStatuses(u, statuses): return withUser(u, String(describing: statuses)) - case let .groupSubscribed(u, groupInfo): return withUser(u, String(describing: groupInfo)) - case let .memberSubErrors(u, memberSubErrors): return withUser(u, String(describing: memberSubErrors)) - case let .groupEmpty(u, groupInfo): return withUser(u, String(describing: groupInfo)) - case .userContactLinkSubscribed: return noDetails - case let .newChatItems(u, chatItems): - let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") - return withUser(u, itemsString) - case let .groupChatItemsDeleted(u, gInfo, chatItemIDs, byUser, member_): - return withUser(u, "chatItemIDs: \(String(describing: chatItemIDs))\ngroupInfo: \(String(describing: gInfo))\nbyUser: \(byUser)\nmember_: \(String(describing: member_))") - case let .forwardPlan(u, chatItemIds, forwardConfirmation): return withUser(u, "items: \(chatItemIds) forwardConfirmation: \(String(describing: forwardConfirmation))") - case let .chatItemsStatusesUpdated(u, chatItems): - let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") - return withUser(u, itemsString) - case let .chatItemUpdated(u, chatItem): return withUser(u, String(describing: chatItem)) - case let .chatItemNotChanged(u, chatItem): return withUser(u, String(describing: chatItem)) - case let .chatItemReaction(u, added, reaction): return withUser(u, "added: \(added)\n\(String(describing: reaction))") - case let .reactionMembers(u, reaction): return withUser(u, "memberReactions: \(String(describing: reaction))") - case let .chatItemsDeleted(u, items, byUser): - let itemsString = items.map { item in - "deletedChatItem:\n\(String(describing: item.deletedChatItem))\ntoChatItem:\n\(String(describing: item.toChatItem))" }.joined(separator: "\n") - return withUser(u, itemsString + "\nbyUser: \(byUser)") - case let .contactsList(u, contacts): return withUser(u, String(describing: contacts)) - 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 .businessLinkConnecting(u, groupInfo, hostMember, fromContact): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(String(describing: hostMember))\nfromContact: \(String(describing: fromContact))") - case let .userDeletedMembers(u, groupInfo, members, withMessages): return withUser(u, "groupInfo: \(groupInfo)\nmembers: \(members)\nwithMessages: \(withMessages)") - case let .leftMemberUser(u, groupInfo): return withUser(u, String(describing: groupInfo)) - case let .groupMembers(u, group): return withUser(u, String(describing: group)) - case let .receivedGroupInvitation(u, groupInfo, contact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\ncontact: \(contact)\nmemberRole: \(memberRole)") - case let .groupDeletedUser(u, groupInfo): return withUser(u, String(describing: groupInfo)) - case let .joinedGroupMemberConnecting(u, groupInfo, hostMember, member): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(hostMember)\nmember: \(member)") - case let .memberRole(u, groupInfo, byMember, member, fromRole, toRole): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\nmember: \(member)\nfromRole: \(fromRole)\ntoRole: \(toRole)") - case let .membersRoleUser(u, groupInfo, members, toRole): return withUser(u, "groupInfo: \(groupInfo)\nmembers: \(members)\ntoRole: \(toRole)") - case let .memberBlockedForAll(u, groupInfo, byMember, member, blocked): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\nmember: \(member)\nblocked: \(blocked)") - case let .membersBlockedForAllUser(u, groupInfo, members, blocked): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(members)\nblocked: \(blocked)") - case let .deletedMemberUser(u, groupInfo, member, withMessages): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)\nwithMessages: \(withMessages)") - case let .deletedMember(u, groupInfo, byMember, deletedMember, withMessages): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\ndeletedMember: \(deletedMember)\nwithMessages: \(withMessages)") - case let .leftMember(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") - case let .groupDeleted(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") - case let .contactsMerged(u, intoContact, mergedContact): return withUser(u, "intoContact: \(intoContact)\nmergedContact: \(mergedContact)") - case let .groupInvitation(u, groupInfo): return withUser(u, String(describing: groupInfo)) - case let .userJoinedGroup(u, groupInfo): return withUser(u, String(describing: groupInfo)) - case let .joinedGroupMember(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") - case let .connectedToGroupMember(u, groupInfo, member, memberContact): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)\nmemberContact: \(String(describing: memberContact))") - case let .groupRemoved(u, groupInfo): return withUser(u, String(describing: groupInfo)) - case let .groupUpdated(u, toGroup): return withUser(u, String(describing: toGroup)) - case let .groupLinkCreated(u, groupInfo, connLinkContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnLinkContact: \(connLinkContact)\nmemberRole: \(memberRole)") - case let .groupLink(u, groupInfo, connLinkContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnLinkContact: \(connLinkContact)\nmemberRole: \(memberRole)") - case let .groupLinkDeleted(u, groupInfo): return withUser(u, String(describing: groupInfo)) - case let .newMemberContact(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") - case let .newMemberContactSentInv(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") - case let .newMemberContactReceivedInv(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") - case let .rcvFileAccepted(u, chatItem): return withUser(u, String(describing: chatItem)) - case .rcvFileAcceptedSndCancelled: return noDetails - case let .standaloneFileInfo(fileMeta): return String(describing: fileMeta) - case .rcvStandaloneFileCreated: return noDetails - case let .rcvFileStart(u, chatItem): return withUser(u, String(describing: chatItem)) - case let .rcvFileProgressXFTP(u, chatItem, receivedSize, totalSize, _): return withUser(u, "chatItem: \(String(describing: chatItem))\nreceivedSize: \(receivedSize)\ntotalSize: \(totalSize)") - case let .rcvStandaloneFileComplete(u, targetPath, _): return withUser(u, targetPath) - case let .rcvFileComplete(u, chatItem): return withUser(u, String(describing: chatItem)) - case let .rcvFileCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .rcvFileSndCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .rcvFileError(u, chatItem, agentError, _): return withUser(u, "agentError: \(String(describing: agentError))\nchatItem: \(String(describing: chatItem))") - case let .rcvFileWarning(u, chatItem, agentError, _): return withUser(u, "agentError: \(String(describing: agentError))\nchatItem: \(String(describing: chatItem))") - case let .sndFileStart(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .sndFileComplete(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .sndFileCancelled(u, chatItem, _, _): return withUser(u, String(describing: chatItem)) - case .sndStandaloneFileCreated: return noDetails - case let .sndFileStartXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .sndFileRcvCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .sndFileProgressXFTP(u, chatItem, _, sentSize, totalSize): return withUser(u, "chatItem: \(String(describing: chatItem))\nsentSize: \(sentSize)\ntotalSize: \(totalSize)") - case let .sndFileRedirectStartXFTP(u, _, redirectMeta): return withUser(u, String(describing: redirectMeta)) - case let .sndFileCompleteXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .sndStandaloneFileComplete(u, _, rcvURIs): return withUser(u, String(rcvURIs.count)) - case let .sndFileCancelledXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .sndFileError(u, chatItem, _, err): return withUser(u, "error: \(String(describing: err))\nchatItem: \(String(describing: chatItem))") - case let .sndFileWarning(u, chatItem, _, err): return withUser(u, "error: \(String(describing: err))\nchatItem: \(String(describing: chatItem))") - case let .callInvitation(inv): return String(describing: inv) - case let .callOffer(u, contact, callType, offer, sharedKey, askConfirmation): return withUser(u, "contact: \(contact.id)\ncallType: \(String(describing: callType))\nsharedKey: \(sharedKey ?? "")\naskConfirmation: \(askConfirmation)\noffer: \(String(describing: offer))") - case let .callAnswer(u, contact, answer): return withUser(u, "contact: \(contact.id)\nanswer: \(String(describing: answer))") - case let .callExtraInfo(u, contact, extraInfo): return withUser(u, "contact: \(contact.id)\nextraInfo: \(String(describing: extraInfo))") - case let .callEnded(u, contact): return withUser(u, "contact: \(contact.id)") - case let .callInvitations(invs): return String(describing: invs) - case let .ntfTokenStatus(status): return String(describing: status) - case let .ntfToken(token, status, ntfMode, ntfServer): return "token: \(token)\nstatus: \(status.rawValue)\nntfMode: \(ntfMode.rawValue)\nntfServer: \(ntfServer)" - case let .ntfConns(ntfConns): return String(describing: ntfConns) - case let .connNtfMessages(receivedMsgs): return "receivedMsgs: \(String(describing: receivedMsgs))" - case let .ntfMessage(u, connEntity, ntfMessage): return withUser(u, "connEntity: \(String(describing: connEntity))\nntfMessage: \(String(describing: ntfMessage))") - case let .contactConnectionDeleted(u, connection): return withUser(u, String(describing: connection)) - case let .contactDisabled(u, contact): return withUser(u, String(describing: contact)) - 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 let .remoteCtrlStopped(rcsState, rcStopReason): return "rcsState: \(String(describing: rcsState))\nrcStopReason: \(String(describing: rcStopReason))" - case let .contactPQEnabled(u, contact, pqEnabled): return withUser(u, "contact: \(String(describing: contact))\npqEnabled: \(pqEnabled)") - 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 .agentSubsTotal(u, subsTotal, hasSession): return withUser(u, "subsTotal: \(String(describing: subsTotal))\nhasSession: \(hasSession)") - case let .agentServersSummary(u, serversSummary): return withUser(u, String(describing: serversSummary)) - case let .agentSubsSummary(u, subsSummary): return withUser(u, String(describing: subsSummary)) - case let .chatCmdError(u, chatError): return withUser(u, String(describing: chatError)) - case let .chatError(u, chatError): return withUser(u, String(describing: chatError)) - case let .archiveExported(archiveErrors): return String(describing: archiveErrors) - case let .archiveImported(archiveErrors): return String(describing: archiveErrors) - case let .appSettings(appSettings): return String(describing: appSettings) - } - } - } - - private var noDetails: String { get { "\(responseType): no details" } } - - private func withUser(_ u: (any UserLike)?, _ s: String) -> String { - if let id = u?.userId { - return "userId: \(id)\n\(s)" - } - return s + return (user, chats) + } else { + return nil } } -public func chatError(_ chatResponse: ChatResponse) -> ChatErrorType? { - switch chatResponse { - case let .chatCmdError(_, .error(error)): return error - case let .chatError(_, .error(error)): return error - default: return nil - } -} - -public enum ChatDeleteMode: Codable { - case full(notify: Bool) - case entity(notify: Bool) - case messages - - var cmdString: String { - switch self { - case let .full(notify): "full notify=\(onOff(notify))" - case let .entity(notify): "entity notify=\(onOff(notify))" - case .messages: "messages" - } - } - - public var isEntity: Bool { - switch self { - case .entity: return true - default: return false - } +public func withUser(_ u: (any UserLike)?, _ s: String) -> String { + if let id = u?.userId { + return "userId: \(id)\n\(s)" } + return s } public struct CreatedConnLink: Decodable, Hashable { @@ -1193,74 +76,6 @@ public func simplexChatLink(_ uri: String) -> String { : uri } -public enum ConnectionPlan: Decodable, Hashable { - case invitationLink(invitationLinkPlan: InvitationLinkPlan) - case contactAddress(contactAddressPlan: ContactAddressPlan) - case groupLink(groupLinkPlan: GroupLinkPlan) - case error(chatError: ChatError) -} - -public enum InvitationLinkPlan: Decodable, Hashable { - case ok - case ownLink - case connecting(contact_: Contact?) - case known(contact: Contact) -} - -public enum ContactAddressPlan: Decodable, Hashable { - case ok - case ownLink - case connectingConfirmReconnect - case connectingProhibit(contact: Contact) - case known(contact: Contact) - case contactViaAddress(contact: Contact) -} - -public enum GroupLinkPlan: Decodable, Hashable { - case ok - case ownLink(groupInfo: GroupInfo) - case connectingConfirmReconnect - case connectingProhibit(groupInfo_: GroupInfo?) - case known(groupInfo: GroupInfo) -} - -struct NewUser: Encodable { - var profile: Profile? - var pastTimestamp: Bool -} - -public enum ChatPagination { - public static let INITIAL_COUNT = 75 - public static let PRELOAD_COUNT = 100 - public static let UNTIL_PRELOAD_COUNT = 50 - - case last(count: Int) - case after(chatItemId: Int64, count: Int) - case before(chatItemId: Int64, count: Int) - case around(chatItemId: Int64, count: Int) - case initial(count: Int) - - var cmdString: String { - switch self { - case let .last(count): return "count=\(count)" - case let .after(chatItemId, count): return "after=\(chatItemId) count=\(count)" - case let .before(chatItemId, count): return "before=\(chatItemId) count=\(count)" - case let .around(chatItemId, count): return "around=\(chatItemId) count=\(count)" - case let .initial(count): return "initial=\(count)" - } - } -} - -public struct ChatTagData: Encodable { - public var emoji: String? - public var text: String - - public init(emoji: String?, text: String) { - self.emoji = emoji - self.text = text - } -} - public struct ComposedMessage: Encodable { public var fileSource: CryptoFile? var quotedItemId: Int64? @@ -1275,477 +90,11 @@ public struct ComposedMessage: Encodable { } } -public struct UpdatedMessage: Encodable { - public var msgContent: MsgContent - public var mentions: [String: Int64] - - public init(msgContent: MsgContent, mentions: [String: Int64] = [:]) { - self.msgContent = msgContent - self.mentions = mentions - } - - var cmdString: String { - "json \(encodeJSON(self))" - } -} - -public struct ArchiveConfig: Encodable { - var archivePath: String - var disableCompression: Bool? - - public init(archivePath: String, disableCompression: Bool? = nil) { - self.archivePath = archivePath - self.disableCompression = disableCompression - } -} - -public struct DBEncryptionConfig: Codable { - public init(currentKey: String, newKey: String) { - self.currentKey = currentKey - self.newKey = newKey - } - - public var currentKey: String - public var newKey: String -} - public enum ServerProtocol: String, Decodable { case smp case xftp } -public enum OperatorTag: String, Codable { - case simplex = "simplex" - case flux = "flux" -} - -public struct ServerOperatorInfo { - public var description: [String] - public var website: URL - public var selfhost: (text: String, link: URL)? = nil - public var logo: String - public var largeLogo: String - public var logoDarkMode: String - public var largeLogoDarkMode: String -} - -public let operatorsInfo: Dictionary = [ - .simplex: ServerOperatorInfo( - description: [ - "SimpleX Chat is the first communication network that has no user profile IDs of any kind, not even random numbers or identity keys.", - "SimpleX Chat Ltd develops the communication software for SimpleX network." - ], - website: URL(string: "https://simplex.chat")!, - logo: "decentralized", - largeLogo: "logo", - logoDarkMode: "decentralized-light", - largeLogoDarkMode: "logo-light" - ), - .flux: ServerOperatorInfo( - description: [ - "Flux is the largest decentralized cloud, based on a global network of user-operated nodes.", - "Flux offers a powerful, scalable, and affordable cutting edge technology platform for all.", - "Flux operates servers in SimpleX network to improve its privacy and decentralization." - ], - website: URL(string: "https://runonflux.com")!, - selfhost: (text: "Self-host SimpleX servers on Flux", link: URL(string: "https://home.runonflux.io/apps/marketplace?q=simplex")!), - logo: "flux_logo_symbol", - largeLogo: "flux_logo", - logoDarkMode: "flux_logo_symbol", - largeLogoDarkMode: "flux_logo-light" - ), -] - -public struct UsageConditions: Decodable { - public var conditionsId: Int64 - public var conditionsCommit: String - public var notifiedAt: Date? - public var createdAt: Date - - public static var sampleData = UsageConditions( - conditionsId: 1, - conditionsCommit: "11a44dc1fd461a93079f897048b46998db55da5c", - notifiedAt: nil, - createdAt: Date.now - ) -} - -public enum UsageConditionsAction: Decodable { - case review(operators: [ServerOperator], deadline: Date?, showNotice: Bool) - case accepted(operators: [ServerOperator]) - - public var showNotice: Bool { - switch self { - case let .review(_, _, showNotice): showNotice - case .accepted: false - } - } -} - -public struct ServerOperatorConditions: Decodable { - public var serverOperators: [ServerOperator] - public var currentConditions: UsageConditions - public var conditionsAction: UsageConditionsAction? - - public static var empty = ServerOperatorConditions( - serverOperators: [], - currentConditions: UsageConditions(conditionsId: 0, conditionsCommit: "empty", notifiedAt: nil, createdAt: .now), - conditionsAction: nil - ) -} - -public enum ConditionsAcceptance: Equatable, Codable, Hashable { - case accepted(acceptedAt: Date?, autoAccepted: Bool) - // If deadline is present, it means there's a grace period to review and accept conditions during which user can continue to use the operator. - // No deadline indicates it's required to accept conditions for the operator to start using it. - case required(deadline: Date?) - - public var conditionsAccepted: Bool { - switch self { - case .accepted: true - case .required: false - } - } - - public var usageAllowed: Bool { - switch self { - case .accepted: true - case let .required(deadline): deadline != nil - } - } -} - -public struct ServerOperator: Identifiable, Equatable, Codable { - public var operatorId: Int64 - public var operatorTag: OperatorTag? - public var tradeName: String - public var legalName: String? - public var serverDomains: [String] - public var conditionsAcceptance: ConditionsAcceptance - public var enabled: Bool - public var smpRoles: ServerRoles - public var xftpRoles: ServerRoles - - public var id: Int64 { operatorId } - - public static func == (l: ServerOperator, r: ServerOperator) -> Bool { - l.operatorId == r.operatorId && l.operatorTag == r.operatorTag && l.tradeName == r.tradeName && l.legalName == r.legalName && - l.serverDomains == r.serverDomains && l.conditionsAcceptance == r.conditionsAcceptance && l.enabled == r.enabled && - l.smpRoles == r.smpRoles && l.xftpRoles == r.xftpRoles - } - - public var legalName_: String { - legalName ?? tradeName - } - - public var info: ServerOperatorInfo { - return if let operatorTag = operatorTag { - operatorsInfo[operatorTag] ?? ServerOperator.dummyOperatorInfo - } else { - ServerOperator.dummyOperatorInfo - } - } - - public static let dummyOperatorInfo = ServerOperatorInfo( - description: ["Default"], - website: URL(string: "https://simplex.chat")!, - logo: "decentralized", - largeLogo: "logo", - logoDarkMode: "decentralized-light", - largeLogoDarkMode: "logo-light" - ) - - public func logo(_ colorScheme: ColorScheme) -> String { - colorScheme == .light ? info.logo : info.logoDarkMode - } - - public func largeLogo(_ colorScheme: ColorScheme) -> String { - colorScheme == .light ? info.largeLogo : info.largeLogoDarkMode - } - - public static var sampleData1 = ServerOperator( - operatorId: 1, - operatorTag: .simplex, - tradeName: "SimpleX Chat", - legalName: "SimpleX Chat Ltd", - serverDomains: ["simplex.im"], - conditionsAcceptance: .accepted(acceptedAt: nil, autoAccepted: false), - enabled: true, - smpRoles: ServerRoles(storage: true, proxy: true), - xftpRoles: ServerRoles(storage: true, proxy: true) - ) -} - -public struct ServerRoles: Equatable, Codable { - public var storage: Bool - public var proxy: Bool -} - -public struct UserOperatorServers: Identifiable, Equatable, Codable { - public var `operator`: ServerOperator? - public var smpServers: [UserServer] - public var xftpServers: [UserServer] - - public var id: String { - if let op = self.operator { - "\(op.operatorId)" - } else { - "nil operator" - } - } - - public var operator_: ServerOperator { - get { - self.operator ?? ServerOperator( - operatorId: 0, - operatorTag: nil, - tradeName: "", - legalName: "", - serverDomains: [], - conditionsAcceptance: .accepted(acceptedAt: nil, autoAccepted: false), - enabled: false, - smpRoles: ServerRoles(storage: true, proxy: true), - xftpRoles: ServerRoles(storage: true, proxy: true) - ) - } - set { `operator` = newValue } - } - - public static var sampleData1 = UserOperatorServers( - operator: ServerOperator.sampleData1, - smpServers: [UserServer.sampleData.preset], - xftpServers: [UserServer.sampleData.xftpPreset] - ) - - public static var sampleDataNilOperator = UserOperatorServers( - operator: nil, - smpServers: [UserServer.sampleData.preset], - xftpServers: [UserServer.sampleData.xftpPreset] - ) -} - -public enum UserServersError: Decodable { - case noServers(protocol: ServerProtocol, user: UserRef?) - case storageMissing(protocol: ServerProtocol, user: UserRef?) - case proxyMissing(protocol: ServerProtocol, user: UserRef?) - case duplicateServer(protocol: ServerProtocol, duplicateServer: String, duplicateHost: String) - - public var globalError: String? { - switch self { - case let .noServers(`protocol`, _): - switch `protocol` { - case .smp: return globalSMPError - case .xftp: return globalXFTPError - } - case let .storageMissing(`protocol`, _): - switch `protocol` { - case .smp: return globalSMPError - case .xftp: return globalXFTPError - } - case let .proxyMissing(`protocol`, _): - switch `protocol` { - case .smp: return globalSMPError - case .xftp: return globalXFTPError - } - default: return nil - } - } - - public var globalSMPError: String? { - switch self { - case let .noServers(.smp, user): - let text = NSLocalizedString("No message servers.", comment: "servers error") - if let user = user { - return userStr(user) + " " + text - } else { - return text - } - case let .storageMissing(.smp, user): - let text = NSLocalizedString("No servers to receive messages.", comment: "servers error") - if let user = user { - return userStr(user) + " " + text - } else { - return text - } - case let .proxyMissing(.smp, user): - let text = NSLocalizedString("No servers for private message routing.", comment: "servers error") - if let user = user { - return userStr(user) + " " + text - } else { - return text - } - default: - return nil - } - } - - public var globalXFTPError: String? { - switch self { - case let .noServers(.xftp, user): - let text = NSLocalizedString("No media & file servers.", comment: "servers error") - if let user = user { - return userStr(user) + " " + text - } else { - return text - } - case let .storageMissing(.xftp, user): - let text = NSLocalizedString("No servers to send files.", comment: "servers error") - if let user = user { - return userStr(user) + " " + text - } else { - return text - } - case let .proxyMissing(.xftp, user): - let text = NSLocalizedString("No servers to receive files.", comment: "servers error") - if let user = user { - return userStr(user) + " " + text - } else { - return text - } - default: - return nil - } - } - - private func userStr(_ user: UserRef) -> String { - String.localizedStringWithFormat(NSLocalizedString("For chat profile %@:", comment: "servers error"), user.localDisplayName) - } -} - -public struct UserServer: Identifiable, Equatable, Codable, Hashable { - public var serverId: Int64? - public var server: String - public var preset: Bool - public var tested: Bool? - public var enabled: Bool - public var deleted: Bool - var createdAt = Date() - - public init(serverId: Int64?, server: String, preset: Bool, tested: Bool?, enabled: Bool, deleted: Bool) { - self.serverId = serverId - self.server = server - self.preset = preset - self.tested = tested - self.enabled = enabled - self.deleted = deleted - } - - public static func == (l: UserServer, r: UserServer) -> Bool { - l.serverId == r.serverId && l.server == r.server && l.preset == r.preset && l.tested == r.tested && - l.enabled == r.enabled && l.deleted == r.deleted - } - - public var id: String { "\(server) \(createdAt)" } - - public static var empty = UserServer(serverId: nil, server: "", preset: false, tested: nil, enabled: false, deleted: false) - - public var isEmpty: Bool { - server.trimmingCharacters(in: .whitespaces) == "" - } - - public struct SampleData { - public var preset: UserServer - public var custom: UserServer - public var untested: UserServer - public var xftpPreset: UserServer - } - - public static var sampleData = SampleData( - preset: UserServer( - serverId: 1, - server: "smp://abcd@smp8.simplex.im", - preset: true, - tested: true, - enabled: true, - deleted: false - ), - custom: UserServer( - serverId: 2, - server: "smp://abcd@smp9.simplex.im", - preset: false, - tested: false, - enabled: false, - deleted: false - ), - untested: UserServer( - serverId: 3, - server: "smp://abcd@smp10.simplex.im", - preset: false, - tested: nil, - enabled: true, - deleted: false - ), - xftpPreset: UserServer( - serverId: 4, - server: "xftp://abcd@xftp8.simplex.im", - preset: true, - tested: true, - enabled: true, - deleted: false - ) - ) - - enum CodingKeys: CodingKey { - case serverId - case server - case preset - case tested - case enabled - case deleted - } -} - -public enum ProtocolTestStep: String, Decodable, Equatable { - case connect - case disconnect - case createQueue - case secureQueue - case deleteQueue - case createFile - case uploadFile - case downloadFile - case compareFile - case deleteFile - - var text: String { - switch self { - case .connect: return NSLocalizedString("Connect", comment: "server test step") - case .disconnect: return NSLocalizedString("Disconnect", comment: "server test step") - case .createQueue: return NSLocalizedString("Create queue", comment: "server test step") - case .secureQueue: return NSLocalizedString("Secure queue", comment: "server test step") - case .deleteQueue: return NSLocalizedString("Delete queue", comment: "server test step") - case .createFile: return NSLocalizedString("Create file", comment: "server test step") - case .uploadFile: return NSLocalizedString("Upload file", comment: "server test step") - case .downloadFile: return NSLocalizedString("Download file", comment: "server test step") - case .compareFile: return NSLocalizedString("Compare file", comment: "server test step") - case .deleteFile: return NSLocalizedString("Delete file", comment: "server test step") - } - } -} - -public struct ProtocolTestFailure: Decodable, Error, Equatable { - public var testStep: ProtocolTestStep - public var testError: AgentErrorType - - public static func == (l: ProtocolTestFailure, r: ProtocolTestFailure) -> Bool { - l.testStep == r.testStep - } - - public var localizedDescription: String { - let err = String.localizedStringWithFormat(NSLocalizedString("Test failed at step %@.", comment: "server test failure"), testStep.text) - switch testError { - case .SMP(_, .AUTH): - return err + " " + NSLocalizedString("Server requires authorization to create queues, check password", comment: "server test error") - case .XFTP(.AUTH): - return err + " " + NSLocalizedString("Server requires authorization to upload, check password", comment: "server test error") - case .BROKER(_, .NETWORK): - return err + " " + NSLocalizedString("Possibly, certificate fingerprint in server address is incorrect", comment: "server test error") - default: - return err - } - } -} - public struct ServerAddress: Decodable { public var serverProtocol: ServerProtocol public var hostnames: [String] @@ -2025,56 +374,6 @@ public enum NetworkProxyAuth: String, Codable { case isolate } -public enum NetworkStatus: Decodable, Equatable { - case unknown - case connected - case disconnected - case error(connectionError: String) - - public var statusString: LocalizedStringKey { - get { - switch self { - case .connected: return "connected" - case .error: return "error" - default: return "connecting" - } - } - } - - public var statusExplanation: LocalizedStringKey { - get { - switch self { - case .connected: return "You are connected to the server used to receive messages from this contact." - case let .error(err): return "Trying to connect to the server used to receive messages from this contact (error: \(err))." - default: return "Trying to connect to the server used to receive messages from this contact." - } - } - } - - public var imageName: String { - get { - switch self { - case .unknown: return "circle.dotted" - case .connected: return "circle.fill" - case .disconnected: return "ellipsis.circle.fill" - case .error: return "exclamationmark.circle.fill" - } - } - } -} - -public enum ForwardConfirmation: Decodable, Hashable { - case filesNotAccepted(fileIds: [Int64]) - case filesInProgress(filesCount: Int) - case filesMissing(filesCount: Int) - case filesFailed(filesCount: Int) -} - -public struct ConnNetworkStatus: Decodable { - public var agentConnId: String - public var networkStatus: NetworkStatus -} - public struct ChatSettings: Codable, Hashable { public var enableNtfs: MsgFilter public var sendRcpts: Bool? @@ -2140,16 +439,6 @@ public enum MsgFilter: String, Codable, Hashable { } } -public struct UserMsgReceiptSettings: Codable { - public var enable: Bool - public var clearOverrides: Bool - - public init(enable: Bool, clearOverrides: Bool) { - self.enable = enable - self.clearOverrides = clearOverrides - } -} - public struct ConnectionStats: Decodable, Hashable { public var connAgentVersion: Int public var rcvQueuesInfo: [RcvQueueInfo] @@ -2217,108 +506,11 @@ public enum RatchetSyncState: String, Decodable { case agreed } -public struct UserContactLink: Decodable, Hashable { - public var connLinkContact: CreatedConnLink - public var autoAccept: AutoAccept? - - public init(connLinkContact: CreatedConnLink, autoAccept: AutoAccept? = nil) { - self.connLinkContact = connLinkContact - self.autoAccept = autoAccept - } - - var responseDetails: String { - "connLinkContact: \(connLinkContact)\nautoAccept: \(AutoAccept.cmdString(autoAccept))" - } -} - -public struct AutoAccept: Codable, Hashable { - public var businessAddress: Bool - public var acceptIncognito: Bool - public var autoReply: MsgContent? - - public init(businessAddress: Bool, acceptIncognito: Bool, autoReply: MsgContent? = nil) { - self.businessAddress = businessAddress - self.acceptIncognito = acceptIncognito - self.autoReply = autoReply - } - - static func cmdString(_ autoAccept: AutoAccept?) -> String { - guard let autoAccept = autoAccept else { return "off" } - var s = "on" - if autoAccept.acceptIncognito { - s += " incognito=on" - } else if autoAccept.businessAddress { - s += " business" - } - guard let msg = autoAccept.autoReply else { return s } - return s + " " + msg.cmdString - } -} - public protocol SelectableItem: Identifiable, Equatable { var label: LocalizedStringKey { get } static var values: [Self] { get } } -public struct DeviceToken: Decodable { - var pushProvider: PushProvider - var token: String - - public init(pushProvider: PushProvider, token: String) { - self.pushProvider = pushProvider - self.token = token - } - - public var cmdString: String { - "\(pushProvider) \(token)" - } -} - -public enum PushEnvironment: String { - case development - case production -} - -public enum PushProvider: String, Decodable { - case apns_dev - case apns_prod - - public init(env: PushEnvironment) { - switch env { - case .development: self = .apns_dev - case .production: self = .apns_prod - } - } -} - -// This notification mode is for app core, UI uses AppNotificationsMode.off to mean completely disable, -// and .local for periodic background checks -public enum NotificationsMode: String, Decodable, SelectableItem { - case off = "OFF" - case periodic = "PERIODIC" - case instant = "INSTANT" - - public var label: LocalizedStringKey { - switch self { - case .off: "No push server" - case .periodic: "Periodic" - case .instant: "Instant" - } - } - - public var icon: String { - switch self { - case .off: return "arrow.clockwise" - case .periodic: return "timer" - case .instant: return "bolt" - } - } - - public var id: String { self.rawValue } - - public static var values: [NotificationsMode] = [.instant, .periodic, .off] -} - public enum NotificationPreviewMode: String, SelectableItem, Codable { case hidden case contact @@ -2337,63 +529,6 @@ public enum NotificationPreviewMode: String, SelectableItem, Codable { public static var values: [NotificationPreviewMode] = [.message, .contact, .hidden] } -public enum PrivacyChatListOpenLinksMode: String, CaseIterable, Codable, RawRepresentable, Identifiable { - case yes - case no - case ask - - public var id: Self { self } - - public var text: LocalizedStringKey { - switch self { - case .yes: return "Yes" - case .no: return "No" - case .ask: return "Ask" - } - } -} - -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 enum RemoteCtrlStopReason: Decodable { - case discoveryFailed(chatError: ChatError) - case connectionFailed(chatError: ChatError) - case setupFailed(chatError: ChatError) - case disconnected -} - -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 - public var simplexmqCommit: String -} - public func decodeJSON(_ json: String) -> T? { if let data = json.data(using: .utf8) { return try? jsonDecoder.decode(T.self, from: data) @@ -2746,425 +881,14 @@ public enum RemoteCtrlError: Decodable, Hashable { case protocolError } -public struct MigrationFileLinkData: Codable { - let networkConfig: NetworkConfig? - - public init(networkConfig: NetworkConfig) { - self.networkConfig = networkConfig - } - - public struct NetworkConfig: Codable { - let socksProxy: String? - let networkProxy: NetworkProxy? - let hostMode: HostMode? - let requiredHostMode: Bool? - - public init(socksProxy: String?, networkProxy: NetworkProxy?, hostMode: HostMode?, requiredHostMode: Bool?) { - self.socksProxy = socksProxy - self.networkProxy = networkProxy - self.hostMode = hostMode - self.requiredHostMode = requiredHostMode - } - - public func transformToPlatformSupported() -> NetworkConfig { - return if let hostMode, let requiredHostMode { - NetworkConfig( - socksProxy: nil, - networkProxy: nil, - hostMode: hostMode == .onionViaSocks ? .onionHost : hostMode, - requiredHostMode: requiredHostMode - ) - } else { self } - } - } - - public func addToLink(link: String) -> String { - "\(link)&data=\(encodeJSON(self).addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)" - } - - public static func readFromLink(link: String) -> MigrationFileLinkData? { -// standaloneFileInfo(link) - nil - } -} - -public struct AppSettings: Codable, Equatable { - public var networkConfig: NetCfg? = nil - public var networkProxy: NetworkProxy? = nil - public var privacyEncryptLocalFiles: Bool? = nil - public var privacyAskToApproveRelays: Bool? = nil - public var privacyAcceptImages: Bool? = nil - public var privacyLinkPreviews: Bool? = nil - public var privacyChatListOpenLinks: PrivacyChatListOpenLinksMode? = nil - public var privacyShowChatPreviews: Bool? = nil - public var privacySaveLastDraft: Bool? = nil - public var privacyProtectScreen: Bool? = nil - public var privacyMediaBlurRadius: Int? = nil - public var notificationMode: AppSettingsNotificationMode? = nil - public var notificationPreviewMode: NotificationPreviewMode? = nil - public var webrtcPolicyRelay: Bool? = nil - public var webrtcICEServers: [String]? = nil - public var confirmRemoteSessions: Bool? = nil - public var connectRemoteViaMulticast: Bool? = nil - public var connectRemoteViaMulticastAuto: Bool? = nil - public var developerTools: Bool? = nil - public var confirmDBUpgrades: Bool? = nil - public var androidCallOnLockScreen: AppSettingsLockScreenCalls? = nil - public var iosCallKitEnabled: Bool? = nil - public var iosCallKitCallsInRecents: Bool? = nil - public var uiProfileImageCornerRadius: Double? = nil - public var uiChatItemRoundness: Double? = nil - public var uiChatItemTail: Bool? = nil - public var uiColorScheme: String? = nil - public var uiDarkColorScheme: String? = nil - public var uiCurrentThemeIds: [String: String]? = nil - public var uiThemes: [ThemeOverrides]? = nil - public var oneHandUI: Bool? = nil - public var chatBottomBar: Bool? = nil - - public func prepareForExport() -> AppSettings { - var empty = AppSettings() - let def = AppSettings.defaults - if networkConfig != def.networkConfig { empty.networkConfig = networkConfig } - if networkProxy != def.networkProxy { empty.networkProxy = networkProxy } - if privacyEncryptLocalFiles != def.privacyEncryptLocalFiles { empty.privacyEncryptLocalFiles = privacyEncryptLocalFiles } - if privacyAskToApproveRelays != def.privacyAskToApproveRelays { empty.privacyAskToApproveRelays = privacyAskToApproveRelays } - if privacyAcceptImages != def.privacyAcceptImages { empty.privacyAcceptImages = privacyAcceptImages } - if privacyLinkPreviews != def.privacyLinkPreviews { empty.privacyLinkPreviews = privacyLinkPreviews } - if privacyChatListOpenLinks != def.privacyChatListOpenLinks { empty.privacyChatListOpenLinks = privacyChatListOpenLinks } - if privacyShowChatPreviews != def.privacyShowChatPreviews { empty.privacyShowChatPreviews = privacyShowChatPreviews } - if privacySaveLastDraft != def.privacySaveLastDraft { empty.privacySaveLastDraft = privacySaveLastDraft } - if privacyProtectScreen != def.privacyProtectScreen { empty.privacyProtectScreen = privacyProtectScreen } - if privacyMediaBlurRadius != def.privacyMediaBlurRadius { empty.privacyMediaBlurRadius = privacyMediaBlurRadius } - if notificationMode != def.notificationMode { empty.notificationMode = notificationMode } - if notificationPreviewMode != def.notificationPreviewMode { empty.notificationPreviewMode = notificationPreviewMode } - if webrtcPolicyRelay != def.webrtcPolicyRelay { empty.webrtcPolicyRelay = webrtcPolicyRelay } - if webrtcICEServers != def.webrtcICEServers { empty.webrtcICEServers = webrtcICEServers } - if confirmRemoteSessions != def.confirmRemoteSessions { empty.confirmRemoteSessions = confirmRemoteSessions } - if connectRemoteViaMulticast != def.connectRemoteViaMulticast {empty.connectRemoteViaMulticast = connectRemoteViaMulticast } - if connectRemoteViaMulticastAuto != def.connectRemoteViaMulticastAuto { empty.connectRemoteViaMulticastAuto = connectRemoteViaMulticastAuto } - if developerTools != def.developerTools { empty.developerTools = developerTools } - if confirmDBUpgrades != def.confirmDBUpgrades { empty.confirmDBUpgrades = confirmDBUpgrades } - if androidCallOnLockScreen != def.androidCallOnLockScreen { empty.androidCallOnLockScreen = androidCallOnLockScreen } - if iosCallKitEnabled != def.iosCallKitEnabled { empty.iosCallKitEnabled = iosCallKitEnabled } - if iosCallKitCallsInRecents != def.iosCallKitCallsInRecents { empty.iosCallKitCallsInRecents = iosCallKitCallsInRecents } - if uiProfileImageCornerRadius != def.uiProfileImageCornerRadius { empty.uiProfileImageCornerRadius = uiProfileImageCornerRadius } - if uiChatItemRoundness != def.uiChatItemRoundness { empty.uiChatItemRoundness = uiChatItemRoundness } - if uiChatItemTail != def.uiChatItemTail { empty.uiChatItemTail = uiChatItemTail } - if uiColorScheme != def.uiColorScheme { empty.uiColorScheme = uiColorScheme } - if uiDarkColorScheme != def.uiDarkColorScheme { empty.uiDarkColorScheme = uiDarkColorScheme } - if uiCurrentThemeIds != def.uiCurrentThemeIds { empty.uiCurrentThemeIds = uiCurrentThemeIds } - if uiThemes != def.uiThemes { empty.uiThemes = uiThemes } - if oneHandUI != def.oneHandUI { empty.oneHandUI = oneHandUI } - if chatBottomBar != def.chatBottomBar { empty.chatBottomBar = chatBottomBar } - return empty - } - - public static var defaults: AppSettings { - AppSettings ( - networkConfig: NetCfg.defaults, - networkProxy: NetworkProxy.def, - privacyEncryptLocalFiles: true, - privacyAskToApproveRelays: true, - privacyAcceptImages: true, - privacyLinkPreviews: true, - privacyChatListOpenLinks: .ask, - privacyShowChatPreviews: true, - privacySaveLastDraft: true, - privacyProtectScreen: false, - privacyMediaBlurRadius: 0, - notificationMode: AppSettingsNotificationMode.instant, - notificationPreviewMode: NotificationPreviewMode.message, - webrtcPolicyRelay: true, - webrtcICEServers: [], - confirmRemoteSessions: false, - connectRemoteViaMulticast: true, - connectRemoteViaMulticastAuto: true, - developerTools: false, - confirmDBUpgrades: false, - androidCallOnLockScreen: AppSettingsLockScreenCalls.show, - iosCallKitEnabled: true, - iosCallKitCallsInRecents: false, - uiProfileImageCornerRadius: 22.5, - uiChatItemRoundness: 0.75, - uiChatItemTail: true, - uiColorScheme: DefaultTheme.SYSTEM_THEME_NAME, - uiDarkColorScheme: DefaultTheme.SIMPLEX.themeName, - uiCurrentThemeIds: nil as [String: String]?, - uiThemes: nil as [ThemeOverrides]?, - oneHandUI: true, - chatBottomBar: true - ) - } -} - -public enum AppSettingsNotificationMode: String, Codable { - case off - case periodic - case instant - - public func toNotificationsMode() -> NotificationsMode { - switch self { - case .instant: .instant - case .periodic: .periodic - case .off: .off - } - } - - public static func from(_ mode: NotificationsMode) -> AppSettingsNotificationMode { - switch mode { - case .instant: .instant - case .periodic: .periodic - case .off: .off - } - } -} - -//public enum NotificationPreviewMode: Codable { -// case hidden -// case contact -// case message -//} - -public enum AppSettingsLockScreenCalls: String, Codable { - case disable - case show - case accept -} - -public struct UserNetworkInfo: Codable, Equatable { - public let networkType: UserNetworkType - public let online: Bool - - public init(networkType: UserNetworkType, online: Bool) { - self.networkType = networkType - self.online = online - } -} - -public enum UserNetworkType: String, Codable { - case none - case cellular - case wifi - case ethernet - case other - - public var text: LocalizedStringKey { - switch self { - case .none: "No network connection" - case .cellular: "Cellular" - case .wifi: "WiFi" - case .ethernet: "Wired ethernet" - case .other: "Other" - } - } -} - -public struct RcvMsgInfo: Codable { - var msgId: Int64 - var msgDeliveryId: Int64 - var msgDeliveryStatus: String - var agentMsgId: Int64 - var agentMsgMeta: String -} - -public struct ServerQueueInfo: Codable { - var server: String - var rcvId: String - var sndId: String - var ntfId: String? - var status: String - var info: QueueInfo -} - -public struct QueueInfo: Codable { - var qiSnd: Bool - var qiNtf: Bool - var qiSub: QSub? - var qiSize: Int - var qiMsg: MsgInfo? -} - -public struct QSub: Codable { - var qSubThread: QSubThread - var qDelivered: String? -} - -public enum QSubThread: String, Codable { - case noSub - case subPending - case subThread - case prohibitSub -} - -public struct MsgInfo: Codable { - var msgId: String - var msgTs: Date - var msgType: MsgType -} - -public enum MsgType: String, Codable { - case message - case quota -} - public struct AppFilePaths: Encodable { public let appFilesFolder: String public let appTempFolder: String public let appAssetsFolder: String -} - -public struct PresentedServersSummary: Codable { - public var statsStartedAt: Date - public var allUsersSMP: SMPServersSummary - public var allUsersXFTP: XFTPServersSummary - public var currentUserSMP: SMPServersSummary - public var currentUserXFTP: XFTPServersSummary -} - -public struct SMPServersSummary: Codable { - public var smpTotals: SMPTotals - public var currentlyUsedSMPServers: [SMPServerSummary] - public var previouslyUsedSMPServers: [SMPServerSummary] - public var onlyProxiedSMPServers: [SMPServerSummary] -} - -public struct SMPTotals: Codable { - public var sessions: ServerSessions - public var subs: SMPServerSubs - public var stats: AgentSMPServerStatsData -} - -public struct SMPServerSummary: Codable, Identifiable { - public var smpServer: String - public var known: Bool? - public var sessions: ServerSessions? - public var subs: SMPServerSubs? - public var stats: AgentSMPServerStatsData? - - public var id: String { smpServer } - - public var hasSubs: Bool { subs != nil } - - public var sessionsOrNew: ServerSessions { sessions ?? ServerSessions.newServerSessions } - - public var subsOrNew: SMPServerSubs { subs ?? SMPServerSubs.newSMPServerSubs } -} - -public struct ServerSessions: Codable { - public var ssConnected: Int - public var ssErrors: Int - public var ssConnecting: Int - - static public var newServerSessions = ServerSessions( - ssConnected: 0, - ssErrors: 0, - ssConnecting: 0 - ) - - public var hasSess: Bool { ssConnected > 0 } -} - -public struct SMPServerSubs: Codable { - public var ssActive: Int - public var ssPending: Int - - public init(ssActive: Int, ssPending: Int) { - self.ssActive = ssActive - self.ssPending = ssPending - } - - static public var newSMPServerSubs = SMPServerSubs( - ssActive: 0, - ssPending: 0 - ) - - public var total: Int { ssActive + ssPending } - - public var shareOfActive: Double { - guard total != 0 else { return 0.0 } - return Double(ssActive) / Double(total) + + public init(appFilesFolder: String, appTempFolder: String, appAssetsFolder: String) { + self.appFilesFolder = appFilesFolder + self.appTempFolder = appTempFolder + self.appAssetsFolder = appAssetsFolder } } - -public struct AgentSMPServerStatsData: Codable { - public var _sentDirect: Int - public var _sentViaProxy: Int - public var _sentProxied: Int - public var _sentDirectAttempts: Int - public var _sentViaProxyAttempts: Int - public var _sentProxiedAttempts: Int - public var _sentAuthErrs: Int - public var _sentQuotaErrs: Int - public var _sentExpiredErrs: Int - public var _sentOtherErrs: Int - public var _recvMsgs: Int - public var _recvDuplicates: Int - public var _recvCryptoErrs: Int - public var _recvErrs: Int - public var _ackMsgs: Int - public var _ackAttempts: Int - public var _ackNoMsgErrs: Int - public var _ackOtherErrs: Int - public var _connCreated: Int - public var _connSecured: Int - public var _connCompleted: Int - public var _connDeleted: Int - public var _connDelAttempts: Int - public var _connDelErrs: Int - public var _connSubscribed: Int - public var _connSubAttempts: Int - public var _connSubIgnored: Int - public var _connSubErrs: Int - public var _ntfKey: Int - public var _ntfKeyAttempts: Int - public var _ntfKeyDeleted: Int - public var _ntfKeyDeleteAttempts: Int -} - -public struct XFTPServersSummary: Codable { - public var xftpTotals: XFTPTotals - public var currentlyUsedXFTPServers: [XFTPServerSummary] - public var previouslyUsedXFTPServers: [XFTPServerSummary] -} - -public struct XFTPTotals: Codable { - public var sessions: ServerSessions - public var stats: AgentXFTPServerStatsData -} - -public struct XFTPServerSummary: Codable, Identifiable { - public var xftpServer: String - public var known: Bool? - public var sessions: ServerSessions? - public var stats: AgentXFTPServerStatsData? - public var rcvInProgress: Bool - public var sndInProgress: Bool - public var delInProgress: Bool - - public var id: String { xftpServer } -} - -public struct AgentXFTPServerStatsData: Codable { - public var _uploads: Int - public var _uploadsSize: Int64 - public var _uploadAttempts: Int - public var _uploadErrs: Int - public var _downloads: Int - public var _downloadsSize: Int64 - public var _downloadAttempts: Int - public var _downloadAuthErrs: Int - public var _downloadErrs: Int - public var _deletions: Int - public var _deleteAttempts: Int - public var _deleteErrs: Int -} - -public struct AgentNtfServerStatsData: Codable { - public var _ntfCreated: Int - public var _ntfCreateAttempts: Int - public var _ntfChecked: Int - public var _ntfCheckAttempts: Int - public var _ntfDeleted: Int - public var _ntfDelAttempts: Int -} diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index fb34ba390c..35c276b2f4 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -3935,7 +3935,7 @@ public enum MsgContent: Equatable, Hashable { } } - var cmdString: String { + public var cmdString: String { "json \(encodeJSON(self))" } diff --git a/apps/ios/SimpleXChat/ErrorAlert.swift b/apps/ios/SimpleXChat/ErrorAlert.swift index d0bf5eeb6e..c99e004d92 100644 --- a/apps/ios/SimpleXChat/ErrorAlert.swift +++ b/apps/ios/SimpleXChat/ErrorAlert.swift @@ -37,7 +37,7 @@ public struct ErrorAlert: Error { } public init(_ error: any Error) { - self = if let chatResponse = error as? ChatResponse { + self = if let chatResponse = error as? ChatRespProtocol { ErrorAlert(chatResponse) } else { ErrorAlert("\(error.localizedDescription)") @@ -48,7 +48,7 @@ public struct ErrorAlert: Error { self = ErrorAlert("\(chatErrorString(chatError))") } - public init(_ chatResponse: ChatResponse) { + public init(_ chatResponse: ChatRespProtocol) { self = if let networkErrorAlert = getNetworkErrorAlert(chatResponse) { networkErrorAlert } else { @@ -94,22 +94,21 @@ extension View { } } -public func getNetworkErrorAlert(_ r: ChatResponse) -> ErrorAlert? { - switch r { - case let .chatCmdError(_, .errorAgent(.BROKER(addr, .TIMEOUT))): - return ErrorAlert(title: "Connection timeout", message: "Please check your network connection with \(serverHostname(addr)) and try again.") - case let .chatCmdError(_, .errorAgent(.BROKER(addr, .NETWORK))): - return ErrorAlert(title: "Connection error", message: "Please check your network connection with \(serverHostname(addr)) and try again.") - case let .chatCmdError(_, .errorAgent(.BROKER(addr, .HOST))): - return ErrorAlert(title: "Connection error", message: "Server address is incompatible with network settings: \(serverHostname(addr)).") - case let .chatCmdError(_, .errorAgent(.BROKER(addr, .TRANSPORT(.version)))): - return ErrorAlert(title: "Connection error", message: "Server version is incompatible with your app: \(serverHostname(addr)).") - case let .chatCmdError(_, .errorAgent(.SMP(serverAddress, .PROXY(proxyErr)))): - return smpProxyErrorAlert(proxyErr, serverAddress) - case let .chatCmdError(_, .errorAgent(.PROXY(proxyServer, relayServer, .protocolError(.PROXY(proxyErr))))): - return proxyDestinationErrorAlert(proxyErr, proxyServer, relayServer) - default: - return nil +public func getNetworkErrorAlert(_ r: ChatRespProtocol) -> ErrorAlert? { + switch r.chatError { + case let .errorAgent(.BROKER(addr, .TIMEOUT)): + ErrorAlert(title: "Connection timeout", message: "Please check your network connection with \(serverHostname(addr)) and try again.") + case let .errorAgent(.BROKER(addr, .NETWORK)): + ErrorAlert(title: "Connection error", message: "Please check your network connection with \(serverHostname(addr)) and try again.") + case let .errorAgent(.BROKER(addr, .HOST)): + ErrorAlert(title: "Connection error", message: "Server address is incompatible with network settings: \(serverHostname(addr)).") + case let .errorAgent(.BROKER(addr, .TRANSPORT(.version))): + ErrorAlert(title: "Connection error", message: "Server version is incompatible with your app: \(serverHostname(addr)).") + case let .errorAgent(.SMP(serverAddress, .PROXY(proxyErr))): + smpProxyErrorAlert(proxyErr, serverAddress) + case let .errorAgent(.PROXY(proxyServer, relayServer, .protocolError(.PROXY(proxyErr)))): + proxyDestinationErrorAlert(proxyErr, proxyServer, relayServer) + default: nil } } From a0d1cca3895ec1b26e532287d4d28ff31cd43e80 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 4 May 2025 22:14:36 +0100 Subject: [PATCH 529/567] core: split response to two types, to improve iOS parsing memory usage (#5867) * core: split response to two types, to improve iOS parsing memory usage * ios: split core events to separate types * comment * limit more events to CLI * fix parser * simplemq --- apps/ios/Shared/Model/AppAPITypes.swift | 497 ++++++++++-------- apps/ios/Shared/Model/SimpleXAPI.swift | 45 +- .../Views/Migration/MigrateFromDevice.swift | 12 +- .../Views/Migration/MigrateToDevice.swift | 10 +- apps/ios/SimpleX NSE/NSEAPITypes.swift | 128 +++-- .../ios/SimpleX NSE/NotificationService.swift | 10 +- apps/ios/SimpleX SE/ShareAPI.swift | 114 +++- apps/ios/SimpleX SE/ShareModel.swift | 4 +- apps/ios/SimpleXChat/API.swift | 4 +- apps/ios/SimpleXChat/APITypes.swift | 8 + .../chat/simplex/common/model/SimpleXAPI.kt | 24 - apps/simplex-bot-advanced/Main.hs | 8 +- .../src/Broadcast/Bot.hs | 41 +- apps/simplex-chat/Server.hs | 39 +- .../src/Directory/Events.hs | 37 +- .../src/Directory/Service.hs | 4 +- cabal.project | 2 +- .../typescript/src/response.ts | 6 - scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat/Bot.hs | 8 +- src/Simplex/Chat/Controller.hs | 316 +++++------ src/Simplex/Chat/Library/Commands.hs | 170 +++--- src/Simplex/Chat/Library/Internal.hs | 117 +++-- src/Simplex/Chat/Library/Subscriber.hs | 345 ++++++------ src/Simplex/Chat/Mobile.hs | 25 +- src/Simplex/Chat/Remote.hs | 22 +- src/Simplex/Chat/Remote/Protocol.hs | 6 +- src/Simplex/Chat/Terminal/Input.hs | 1 - src/Simplex/Chat/Terminal/Main.hs | 13 +- src/Simplex/Chat/Terminal/Output.hs | 48 +- src/Simplex/Chat/View.hs | 460 +++++++++------- 31 files changed, 1394 insertions(+), 1132 deletions(-) diff --git a/apps/ios/Shared/Model/AppAPITypes.swift b/apps/ios/Shared/Model/AppAPITypes.swift index d7f96284cf..37d016e93d 100644 --- a/apps/ios/Shared/Model/AppAPITypes.swift +++ b/apps/ios/Shared/Model/AppAPITypes.swift @@ -587,7 +587,6 @@ enum ChatResponse: Decodable, Error, ChatRespProtocol { case chatStarted case chatRunning case chatStopped - case chatSuspended case apiChats(user: UserRef, chats: [ChatData]) case apiChat(user: UserRef, chat: ChatData, navInfo: NavigationInfo?) case chatTags(user: UserRef, userTags: [ChatTag]) @@ -606,14 +605,8 @@ enum ChatResponse: Decodable, Error, ChatRespProtocol { case groupMemberSwitchStarted(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats) case contactSwitchAborted(user: UserRef, contact: Contact, connectionStats: ConnectionStats) case groupMemberSwitchAborted(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats) - case contactSwitch(user: UserRef, contact: Contact, switchProgress: SwitchProgress) - case groupMemberSwitch(user: UserRef, groupInfo: GroupInfo, member: GroupMember, switchProgress: SwitchProgress) case contactRatchetSyncStarted(user: UserRef, contact: Contact, connectionStats: ConnectionStats) case groupMemberRatchetSyncStarted(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats) - case contactRatchetSync(user: UserRef, contact: Contact, ratchetSyncProgress: RatchetSyncProgress) - case groupMemberRatchetSync(user: UserRef, groupInfo: GroupInfo, member: GroupMember, ratchetSyncProgress: RatchetSyncProgress) - case contactVerificationReset(user: UserRef, contact: Contact) - case groupMemberVerificationReset(user: UserRef, groupInfo: GroupInfo, member: GroupMember) case contactCode(user: UserRef, contact: Contact, connectionCode: String) case groupMemberCode(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionCode: String) case connectionVerified(user: UserRef, verified: Bool, expectedCode: String) @@ -627,7 +620,6 @@ enum ChatResponse: Decodable, Error, ChatRespProtocol { case sentInvitationToContact(user: UserRef, contact: Contact, customUserProfile: Profile?) case contactAlreadyExists(user: UserRef, contact: Contact) case contactDeleted(user: UserRef, contact: Contact) - case contactDeletedByContact(user: UserRef, contact: Contact) case chatCleared(user: UserRef, chatInfo: ChatInfo) case userProfileNoChange(user: User) case userProfileUpdated(user: User, fromProfile: Profile, toProfile: Profile, updateSummary: UserProfileUpdateSummary) @@ -640,113 +632,57 @@ enum ChatResponse: Decodable, Error, ChatRespProtocol { case userContactLinkUpdated(user: User, contactLink: UserContactLink) case userContactLinkCreated(user: User, connLinkContact: CreatedConnLink) case userContactLinkDeleted(user: User) - case contactConnected(user: UserRef, contact: Contact, userCustomProfile: Profile?) - case contactConnecting(user: UserRef, contact: Contact) - case contactSndReady(user: UserRef, contact: Contact) - case receivedContactRequest(user: UserRef, contactRequest: UserContactRequest) 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) - case networkStatus(networkStatus: NetworkStatus, connections: [String]) case networkStatuses(user_: UserRef?, networkStatuses: [ConnNetworkStatus]) - case groupSubscribed(user: UserRef, groupInfo: GroupRef) - case memberSubErrors(user: UserRef, memberSubErrors: [MemberSubError]) - case groupEmpty(user: UserRef, groupInfo: GroupInfo) - case userContactLinkSubscribed case newChatItems(user: UserRef, chatItems: [AChatItem]) case groupChatItemsDeleted(user: UserRef, groupInfo: GroupInfo, chatItemIDs: Set, byUser: Bool, member_: GroupMember?) case forwardPlan(user: UserRef, chatItemIds: [Int64], forwardConfirmation: ForwardConfirmation?) - case chatItemsStatusesUpdated(user: UserRef, chatItems: [AChatItem]) case chatItemUpdated(user: UserRef, chatItem: AChatItem) case chatItemNotChanged(user: UserRef, chatItem: AChatItem) case chatItemReaction(user: UserRef, added: Bool, reaction: ACIReaction) case reactionMembers(user: UserRef, memberReactions: [MemberReaction]) case chatItemsDeleted(user: UserRef, chatItemDeletions: [ChatItemDeletion], byUser: Bool) case contactsList(user: UserRef, contacts: [Contact]) - // group events + // group responses 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 businessLinkConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember, fromContact: Contact) case userDeletedMembers(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], withMessages: Bool) case leftMemberUser(user: UserRef, groupInfo: GroupInfo) case groupMembers(user: UserRef, group: SimpleXChat.Group) - case receivedGroupInvitation(user: UserRef, groupInfo: GroupInfo, contact: Contact, memberRole: GroupMemberRole) case groupDeletedUser(user: UserRef, groupInfo: GroupInfo) - case joinedGroupMemberConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember, member: GroupMember) - case memberRole(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, member: GroupMember, fromRole: GroupMemberRole, toRole: GroupMemberRole) case membersRoleUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], toRole: GroupMemberRole) - case memberBlockedForAll(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, member: GroupMember, blocked: Bool) case membersBlockedForAllUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], blocked: Bool) - case deletedMemberUser(user: UserRef, groupInfo: GroupInfo, member: GroupMember, withMessages: Bool) - case deletedMember(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, deletedMember: GroupMember, withMessages: Bool) - case leftMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember) - case groupDeleted(user: UserRef, groupInfo: GroupInfo, member: GroupMember) - case contactsMerged(user: UserRef, intoContact: Contact, mergedContact: Contact) - case groupInvitation(user: UserRef, groupInfo: GroupInfo) // unused - case userJoinedGroup(user: UserRef, groupInfo: GroupInfo) - case joinedGroupMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember) - case connectedToGroupMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember, memberContact: Contact?) - case groupRemoved(user: UserRef, groupInfo: GroupInfo) // unused case groupUpdated(user: UserRef, toGroup: GroupInfo) case groupLinkCreated(user: UserRef, groupInfo: GroupInfo, connLinkContact: CreatedConnLink, memberRole: GroupMemberRole) case groupLink(user: UserRef, groupInfo: GroupInfo, connLinkContact: CreatedConnLink, memberRole: GroupMemberRole) case groupLinkDeleted(user: UserRef, groupInfo: GroupInfo) case newMemberContact(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember) case newMemberContactSentInv(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember) - case newMemberContactReceivedInv(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember) - // receiving file events + // receiving file responses case rcvFileAccepted(user: UserRef, chatItem: AChatItem) case rcvFileAcceptedSndCancelled(user: UserRef, rcvFileTransfer: RcvFileTransfer) case standaloneFileInfo(fileMeta: MigrationFileLinkData?) case rcvStandaloneFileCreated(user: UserRef, rcvFileTransfer: RcvFileTransfer) - case rcvFileStart(user: UserRef, chatItem: AChatItem) // send by chats - case rcvFileProgressXFTP(user: UserRef, chatItem_: AChatItem?, receivedSize: Int64, totalSize: Int64, rcvFileTransfer: RcvFileTransfer) - case rcvFileComplete(user: UserRef, chatItem: AChatItem) - case rcvStandaloneFileComplete(user: UserRef, targetPath: String, rcvFileTransfer: RcvFileTransfer) case rcvFileCancelled(user: UserRef, chatItem_: AChatItem?, rcvFileTransfer: RcvFileTransfer) - case rcvFileSndCancelled(user: UserRef, chatItem: AChatItem, rcvFileTransfer: RcvFileTransfer) - case rcvFileError(user: UserRef, chatItem_: AChatItem?, agentError: AgentErrorType, rcvFileTransfer: RcvFileTransfer) - case rcvFileWarning(user: UserRef, chatItem_: AChatItem?, agentError: AgentErrorType, rcvFileTransfer: RcvFileTransfer) - // sending file events - case sndFileStart(user: UserRef, chatItem: AChatItem, sndFileTransfer: SndFileTransfer) - case sndFileComplete(user: UserRef, chatItem: AChatItem, sndFileTransfer: SndFileTransfer) - case sndFileRcvCancelled(user: UserRef, chatItem_: AChatItem?, sndFileTransfer: SndFileTransfer) + // sending file responses case sndFileCancelled(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, sndFileTransfers: [SndFileTransfer]) case sndStandaloneFileCreated(user: UserRef, fileTransferMeta: FileTransferMeta) // returned by _upload case sndFileStartXFTP(user: UserRef, chatItem: AChatItem, fileTransferMeta: FileTransferMeta) // not used - case sndFileProgressXFTP(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, sentSize: Int64, totalSize: Int64) - case sndFileRedirectStartXFTP(user: UserRef, fileTransferMeta: FileTransferMeta, redirectMeta: FileTransferMeta) - case sndFileCompleteXFTP(user: UserRef, chatItem: AChatItem, fileTransferMeta: FileTransferMeta) - case sndStandaloneFileComplete(user: UserRef, fileTransferMeta: FileTransferMeta, rcvURIs: [String]) case sndFileCancelledXFTP(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta) - case sndFileError(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, errorMessage: String) - case sndFileWarning(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, errorMessage: String) - // call events - case callInvitation(callInvitation: RcvCallInvitation) - case callOffer(user: UserRef, contact: Contact, callType: CallType, offer: WebRTCSession, sharedKey: String?, askConfirmation: Bool) - case callAnswer(user: UserRef, contact: Contact, answer: WebRTCSession) - case callExtraInfo(user: UserRef, contact: Contact, extraInfo: WebRTCExtraInfo) - case callEnded(user: UserRef, contact: Contact) + // call invitations case callInvitations(callInvitations: [RcvCallInvitation]) + // notifications case ntfTokenStatus(status: NtfTknStatus) case ntfToken(token: DeviceToken, status: NtfTknStatus, ntfMode: NotificationsMode, ntfServer: String) case ntfConns(ntfConns: [NtfConn]) case connNtfMessages(receivedMsgs: [NtfMsgInfo?]) - case ntfMessage(user: UserRef, connEntity: ConnectionEntity, ntfMessage: NtfMsgAckInfo) case contactConnectionDeleted(user: UserRef, connection: PendingContactConnection) - case contactDisabled(user: UserRef, contact: Contact) - // remote desktop responses/events + // remote desktop responses 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(rcsState: RemoteCtrlSessionState, rcStopReason: RemoteCtrlStopReason) - // pq - case contactPQEnabled(user: UserRef, contact: Contact, pqEnabled: Bool) // misc case versionInfo(versionInfo: CoreVersionInfo, chatMigrations: [UpMigration], agentMigrations: [UpMigration]) case cmdOk(user_: UserRef?) @@ -754,7 +690,6 @@ enum ChatResponse: Decodable, Error, ChatRespProtocol { case agentServersSummary(user: UserRef, serversSummary: PresentedServersSummary) case agentSubsSummary(user: UserRef, subsSummary: SMPServerSubs) case chatCmdError(user_: UserRef?, chatError: ChatError) - case chatError(user_: UserRef?, chatError: ChatError) case archiveExported(archiveErrors: [ArchiveError]) case archiveImported(archiveErrors: [ArchiveError]) case appSettings(appSettings: AppSettings) @@ -768,7 +703,6 @@ enum ChatResponse: Decodable, Error, ChatRespProtocol { case .chatStarted: return "chatStarted" case .chatRunning: return "chatRunning" case .chatStopped: return "chatStopped" - case .chatSuspended: return "chatSuspended" case .apiChats: return "apiChats" case .apiChat: return "apiChat" case .chatTags: return "chatTags" @@ -787,14 +721,8 @@ enum ChatResponse: Decodable, Error, ChatRespProtocol { case .groupMemberSwitchStarted: return "groupMemberSwitchStarted" case .contactSwitchAborted: return "contactSwitchAborted" case .groupMemberSwitchAborted: return "groupMemberSwitchAborted" - case .contactSwitch: return "contactSwitch" - case .groupMemberSwitch: return "groupMemberSwitch" case .contactRatchetSyncStarted: return "contactRatchetSyncStarted" case .groupMemberRatchetSyncStarted: return "groupMemberRatchetSyncStarted" - case .contactRatchetSync: return "contactRatchetSync" - case .groupMemberRatchetSync: return "groupMemberRatchetSync" - case .contactVerificationReset: return "contactVerificationReset" - case .groupMemberVerificationReset: return "groupMemberVerificationReset" case .contactCode: return "contactCode" case .groupMemberCode: return "groupMemberCode" case .connectionVerified: return "connectionVerified" @@ -808,7 +736,6 @@ enum ChatResponse: Decodable, Error, ChatRespProtocol { case .sentInvitationToContact: return "sentInvitationToContact" case .contactAlreadyExists: return "contactAlreadyExists" case .contactDeleted: return "contactDeleted" - case .contactDeletedByContact: return "contactDeletedByContact" case .chatCleared: return "chatCleared" case .userProfileNoChange: return "userProfileNoChange" case .userProfileUpdated: return "userProfileUpdated" @@ -821,24 +748,12 @@ enum ChatResponse: Decodable, Error, ChatRespProtocol { case .userContactLinkUpdated: return "userContactLinkUpdated" case .userContactLinkCreated: return "userContactLinkCreated" case .userContactLinkDeleted: return "userContactLinkDeleted" - case .contactConnected: return "contactConnected" - case .contactConnecting: return "contactConnecting" - case .contactSndReady: return "contactSndReady" - case .receivedContactRequest: return "receivedContactRequest" case .acceptingContactRequest: return "acceptingContactRequest" case .contactRequestRejected: return "contactRequestRejected" - case .contactUpdated: return "contactUpdated" - case .groupMemberUpdated: return "groupMemberUpdated" - case .networkStatus: return "networkStatus" case .networkStatuses: return "networkStatuses" - case .groupSubscribed: return "groupSubscribed" - case .memberSubErrors: return "memberSubErrors" - case .groupEmpty: return "groupEmpty" - case .userContactLinkSubscribed: return "userContactLinkSubscribed" case .newChatItems: return "newChatItems" case .groupChatItemsDeleted: return "groupChatItemsDeleted" case .forwardPlan: return "forwardPlan" - case .chatItemsStatusesUpdated: return "chatItemsStatusesUpdated" case .chatItemUpdated: return "chatItemUpdated" case .chatItemNotChanged: return "chatItemNotChanged" case .chatItemReaction: return "chatItemReaction" @@ -848,87 +763,42 @@ enum ChatResponse: Decodable, Error, ChatRespProtocol { case .groupCreated: return "groupCreated" case .sentGroupInvitation: return "sentGroupInvitation" case .userAcceptedGroupSent: return "userAcceptedGroupSent" - case .groupLinkConnecting: return "groupLinkConnecting" - case .businessLinkConnecting: return "businessLinkConnecting" case .userDeletedMembers: return "userDeletedMembers" case .leftMemberUser: return "leftMemberUser" case .groupMembers: return "groupMembers" - case .receivedGroupInvitation: return "receivedGroupInvitation" case .groupDeletedUser: return "groupDeletedUser" - case .joinedGroupMemberConnecting: return "joinedGroupMemberConnecting" - case .memberRole: return "memberRole" case .membersRoleUser: return "membersRoleUser" - case .memberBlockedForAll: return "memberBlockedForAll" case .membersBlockedForAllUser: return "membersBlockedForAllUser" - case .deletedMemberUser: return "deletedMemberUser" - case .deletedMember: return "deletedMember" - case .leftMember: return "leftMember" - case .groupDeleted: return "groupDeleted" - case .contactsMerged: return "contactsMerged" - case .groupInvitation: return "groupInvitation" - case .userJoinedGroup: return "userJoinedGroup" - case .joinedGroupMember: return "joinedGroupMember" - case .connectedToGroupMember: return "connectedToGroupMember" - case .groupRemoved: return "groupRemoved" case .groupUpdated: return "groupUpdated" case .groupLinkCreated: return "groupLinkCreated" case .groupLink: return "groupLink" case .groupLinkDeleted: return "groupLinkDeleted" case .newMemberContact: return "newMemberContact" case .newMemberContactSentInv: return "newMemberContactSentInv" - case .newMemberContactReceivedInv: return "newMemberContactReceivedInv" case .rcvFileAccepted: return "rcvFileAccepted" case .rcvFileAcceptedSndCancelled: return "rcvFileAcceptedSndCancelled" case .standaloneFileInfo: return "standaloneFileInfo" case .rcvStandaloneFileCreated: return "rcvStandaloneFileCreated" - case .rcvFileStart: return "rcvFileStart" - case .rcvFileProgressXFTP: return "rcvFileProgressXFTP" - case .rcvFileComplete: return "rcvFileComplete" - case .rcvStandaloneFileComplete: return "rcvStandaloneFileComplete" case .rcvFileCancelled: return "rcvFileCancelled" - case .rcvFileSndCancelled: return "rcvFileSndCancelled" - case .rcvFileError: return "rcvFileError" - case .rcvFileWarning: return "rcvFileWarning" - case .sndFileStart: return "sndFileStart" - case .sndFileComplete: return "sndFileComplete" case .sndFileCancelled: return "sndFileCancelled" case .sndStandaloneFileCreated: return "sndStandaloneFileCreated" case .sndFileStartXFTP: return "sndFileStartXFTP" - case .sndFileProgressXFTP: return "sndFileProgressXFTP" - case .sndFileRedirectStartXFTP: return "sndFileRedirectStartXFTP" - case .sndFileRcvCancelled: return "sndFileRcvCancelled" - case .sndFileCompleteXFTP: return "sndFileCompleteXFTP" - case .sndStandaloneFileComplete: return "sndStandaloneFileComplete" case .sndFileCancelledXFTP: return "sndFileCancelledXFTP" - case .sndFileError: return "sndFileError" - case .sndFileWarning: return "sndFileWarning" - case .callInvitation: return "callInvitation" - case .callOffer: return "callOffer" - case .callAnswer: return "callAnswer" - case .callExtraInfo: return "callExtraInfo" - case .callEnded: return "callEnded" case .callInvitations: return "callInvitations" case .ntfTokenStatus: return "ntfTokenStatus" case .ntfToken: return "ntfToken" case .ntfConns: return "ntfConns" case .connNtfMessages: return "connNtfMessages" - case .ntfMessage: return "ntfMessage" case .contactConnectionDeleted: return "contactConnectionDeleted" - case .contactDisabled: return "contactDisabled" 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 .contactPQEnabled: return "contactPQEnabled" case .versionInfo: return "versionInfo" case .cmdOk: return "cmdOk" case .agentSubsTotal: return "agentSubsTotal" case .agentServersSummary: return "agentServersSummary" case .agentSubsSummary: return "agentSubsSummary" case .chatCmdError: return "chatCmdError" - case .chatError: return "chatError" case .archiveExported: return "archiveExported" case .archiveImported: return "archiveImported" case .appSettings: return "appSettings" @@ -945,7 +815,6 @@ enum ChatResponse: Decodable, Error, ChatRespProtocol { case .chatStarted: return noDetails case .chatRunning: return noDetails case .chatStopped: return noDetails - case .chatSuspended: return noDetails case let .apiChats(u, chats): return withUser(u, String(describing: chats)) case let .apiChat(u, chat, navInfo): return withUser(u, "chat: \(String(describing: chat))\nnavInfo: \(String(describing: navInfo))") case let .chatTags(u, userTags): return withUser(u, "userTags: \(String(describing: userTags))") @@ -966,14 +835,8 @@ enum ChatResponse: Decodable, Error, ChatRespProtocol { case let .groupMemberSwitchStarted(u, groupInfo, member, connectionStats): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats: \(String(describing: connectionStats))") case let .contactSwitchAborted(u, contact, connectionStats): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))") case let .groupMemberSwitchAborted(u, groupInfo, member, connectionStats): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats: \(String(describing: connectionStats))") - case let .contactSwitch(u, contact, switchProgress): return withUser(u, "contact: \(String(describing: contact))\nswitchProgress: \(String(describing: switchProgress))") - case let .groupMemberSwitch(u, groupInfo, member, switchProgress): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nswitchProgress: \(String(describing: switchProgress))") case let .contactRatchetSyncStarted(u, contact, connectionStats): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))") case let .groupMemberRatchetSyncStarted(u, groupInfo, member, connectionStats): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats: \(String(describing: connectionStats))") - case let .contactRatchetSync(u, contact, ratchetSyncProgress): return withUser(u, "contact: \(String(describing: contact))\nratchetSyncProgress: \(String(describing: ratchetSyncProgress))") - case let .groupMemberRatchetSync(u, groupInfo, member, ratchetSyncProgress): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nratchetSyncProgress: \(String(describing: ratchetSyncProgress))") - case let .contactVerificationReset(u, contact): return withUser(u, "contact: \(String(describing: contact))") - case let .groupMemberVerificationReset(u, groupInfo, member): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))") case let .contactCode(u, contact, connectionCode): return withUser(u, "contact: \(String(describing: contact))\nconnectionCode: \(connectionCode)") case let .groupMemberCode(u, groupInfo, member, connectionCode): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionCode: \(connectionCode)") case let .connectionVerified(u, verified, expectedCode): return withUser(u, "verified: \(verified)\nconnectionCode: \(expectedCode)") @@ -987,7 +850,6 @@ enum ChatResponse: Decodable, Error, ChatRespProtocol { case let .sentInvitationToContact(u, contact, _): return withUser(u, String(describing: contact)) case let .contactAlreadyExists(u, contact): return withUser(u, String(describing: contact)) case let .contactDeleted(u, contact): return withUser(u, String(describing: contact)) - case let .contactDeletedByContact(u, contact): return withUser(u, String(describing: contact)) case let .chatCleared(u, chatInfo): return withUser(u, String(describing: chatInfo)) case .userProfileNoChange: return noDetails case let .userProfileUpdated(u, _, toProfile, _): return withUser(u, String(describing: toProfile)) @@ -1000,29 +862,15 @@ enum ChatResponse: Decodable, Error, ChatRespProtocol { case let .userContactLinkUpdated(u, contactLink): return withUser(u, contactLink.responseDetails) case let .userContactLinkCreated(u, connLink): return withUser(u, String(describing: connLink)) case .userContactLinkDeleted: return noDetails - case let .contactConnected(u, contact, _): return withUser(u, String(describing: contact)) - case let .contactConnecting(u, contact): return withUser(u, String(describing: contact)) - case let .contactSndReady(u, contact): return withUser(u, String(describing: contact)) - case let .receivedContactRequest(u, contactRequest): return withUser(u, String(describing: contactRequest)) 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 .networkStatus(status, conns): return "networkStatus: \(String(describing: status))\nconnections: \(String(describing: conns))" case let .networkStatuses(u, statuses): return withUser(u, String(describing: statuses)) - case let .groupSubscribed(u, groupInfo): return withUser(u, String(describing: groupInfo)) - case let .memberSubErrors(u, memberSubErrors): return withUser(u, String(describing: memberSubErrors)) - case let .groupEmpty(u, groupInfo): return withUser(u, String(describing: groupInfo)) - case .userContactLinkSubscribed: return noDetails case let .newChatItems(u, chatItems): let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") return withUser(u, itemsString) case let .groupChatItemsDeleted(u, gInfo, chatItemIDs, byUser, member_): return withUser(u, "chatItemIDs: \(String(describing: chatItemIDs))\ngroupInfo: \(String(describing: gInfo))\nbyUser: \(byUser)\nmember_: \(String(describing: member_))") case let .forwardPlan(u, chatItemIds, forwardConfirmation): return withUser(u, "items: \(chatItemIds) forwardConfirmation: \(String(describing: forwardConfirmation))") - case let .chatItemsStatusesUpdated(u, chatItems): - let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") - return withUser(u, itemsString) case let .chatItemUpdated(u, chatItem): return withUser(u, String(describing: chatItem)) case let .chatItemNotChanged(u, chatItem): return withUser(u, String(describing: chatItem)) case let .chatItemReaction(u, added, reaction): return withUser(u, "added: \(added)\n\(String(describing: reaction))") @@ -1035,87 +883,42 @@ enum ChatResponse: Decodable, Error, ChatRespProtocol { 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 .businessLinkConnecting(u, groupInfo, hostMember, fromContact): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(String(describing: hostMember))\nfromContact: \(String(describing: fromContact))") case let .userDeletedMembers(u, groupInfo, members, withMessages): return withUser(u, "groupInfo: \(groupInfo)\nmembers: \(members)\nwithMessages: \(withMessages)") case let .leftMemberUser(u, groupInfo): return withUser(u, String(describing: groupInfo)) case let .groupMembers(u, group): return withUser(u, String(describing: group)) - case let .receivedGroupInvitation(u, groupInfo, contact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\ncontact: \(contact)\nmemberRole: \(memberRole)") case let .groupDeletedUser(u, groupInfo): return withUser(u, String(describing: groupInfo)) - case let .joinedGroupMemberConnecting(u, groupInfo, hostMember, member): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(hostMember)\nmember: \(member)") - case let .memberRole(u, groupInfo, byMember, member, fromRole, toRole): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\nmember: \(member)\nfromRole: \(fromRole)\ntoRole: \(toRole)") case let .membersRoleUser(u, groupInfo, members, toRole): return withUser(u, "groupInfo: \(groupInfo)\nmembers: \(members)\ntoRole: \(toRole)") - case let .memberBlockedForAll(u, groupInfo, byMember, member, blocked): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\nmember: \(member)\nblocked: \(blocked)") case let .membersBlockedForAllUser(u, groupInfo, members, blocked): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(members)\nblocked: \(blocked)") - case let .deletedMemberUser(u, groupInfo, member, withMessages): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)\nwithMessages: \(withMessages)") - case let .deletedMember(u, groupInfo, byMember, deletedMember, withMessages): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\ndeletedMember: \(deletedMember)\nwithMessages: \(withMessages)") - case let .leftMember(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") - case let .groupDeleted(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") - case let .contactsMerged(u, intoContact, mergedContact): return withUser(u, "intoContact: \(intoContact)\nmergedContact: \(mergedContact)") - case let .groupInvitation(u, groupInfo): return withUser(u, String(describing: groupInfo)) - case let .userJoinedGroup(u, groupInfo): return withUser(u, String(describing: groupInfo)) - case let .joinedGroupMember(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") - case let .connectedToGroupMember(u, groupInfo, member, memberContact): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)\nmemberContact: \(String(describing: memberContact))") - case let .groupRemoved(u, groupInfo): return withUser(u, String(describing: groupInfo)) case let .groupUpdated(u, toGroup): return withUser(u, String(describing: toGroup)) case let .groupLinkCreated(u, groupInfo, connLinkContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnLinkContact: \(connLinkContact)\nmemberRole: \(memberRole)") case let .groupLink(u, groupInfo, connLinkContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnLinkContact: \(connLinkContact)\nmemberRole: \(memberRole)") case let .groupLinkDeleted(u, groupInfo): return withUser(u, String(describing: groupInfo)) case let .newMemberContact(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") case let .newMemberContactSentInv(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") - case let .newMemberContactReceivedInv(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") case let .rcvFileAccepted(u, chatItem): return withUser(u, String(describing: chatItem)) case .rcvFileAcceptedSndCancelled: return noDetails case let .standaloneFileInfo(fileMeta): return String(describing: fileMeta) case .rcvStandaloneFileCreated: return noDetails - case let .rcvFileStart(u, chatItem): return withUser(u, String(describing: chatItem)) - case let .rcvFileProgressXFTP(u, chatItem, receivedSize, totalSize, _): return withUser(u, "chatItem: \(String(describing: chatItem))\nreceivedSize: \(receivedSize)\ntotalSize: \(totalSize)") - case let .rcvStandaloneFileComplete(u, targetPath, _): return withUser(u, targetPath) - case let .rcvFileComplete(u, chatItem): return withUser(u, String(describing: chatItem)) case let .rcvFileCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .rcvFileSndCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .rcvFileError(u, chatItem, agentError, _): return withUser(u, "agentError: \(String(describing: agentError))\nchatItem: \(String(describing: chatItem))") - case let .rcvFileWarning(u, chatItem, agentError, _): return withUser(u, "agentError: \(String(describing: agentError))\nchatItem: \(String(describing: chatItem))") - case let .sndFileStart(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .sndFileComplete(u, chatItem, _): return withUser(u, String(describing: chatItem)) case let .sndFileCancelled(u, chatItem, _, _): return withUser(u, String(describing: chatItem)) case .sndStandaloneFileCreated: return noDetails case let .sndFileStartXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .sndFileRcvCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .sndFileProgressXFTP(u, chatItem, _, sentSize, totalSize): return withUser(u, "chatItem: \(String(describing: chatItem))\nsentSize: \(sentSize)\ntotalSize: \(totalSize)") - case let .sndFileRedirectStartXFTP(u, _, redirectMeta): return withUser(u, String(describing: redirectMeta)) - case let .sndFileCompleteXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .sndStandaloneFileComplete(u, _, rcvURIs): return withUser(u, String(rcvURIs.count)) case let .sndFileCancelledXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .sndFileError(u, chatItem, _, err): return withUser(u, "error: \(String(describing: err))\nchatItem: \(String(describing: chatItem))") - case let .sndFileWarning(u, chatItem, _, err): return withUser(u, "error: \(String(describing: err))\nchatItem: \(String(describing: chatItem))") - case let .callInvitation(inv): return String(describing: inv) - case let .callOffer(u, contact, callType, offer, sharedKey, askConfirmation): return withUser(u, "contact: \(contact.id)\ncallType: \(String(describing: callType))\nsharedKey: \(sharedKey ?? "")\naskConfirmation: \(askConfirmation)\noffer: \(String(describing: offer))") - case let .callAnswer(u, contact, answer): return withUser(u, "contact: \(contact.id)\nanswer: \(String(describing: answer))") - case let .callExtraInfo(u, contact, extraInfo): return withUser(u, "contact: \(contact.id)\nextraInfo: \(String(describing: extraInfo))") - case let .callEnded(u, contact): return withUser(u, "contact: \(contact.id)") case let .callInvitations(invs): return String(describing: invs) case let .ntfTokenStatus(status): return String(describing: status) case let .ntfToken(token, status, ntfMode, ntfServer): return "token: \(token)\nstatus: \(status.rawValue)\nntfMode: \(ntfMode.rawValue)\nntfServer: \(ntfServer)" case let .ntfConns(ntfConns): return String(describing: ntfConns) case let .connNtfMessages(receivedMsgs): return "receivedMsgs: \(String(describing: receivedMsgs))" - case let .ntfMessage(u, connEntity, ntfMessage): return withUser(u, "connEntity: \(String(describing: connEntity))\nntfMessage: \(String(describing: ntfMessage))") case let .contactConnectionDeleted(u, connection): return withUser(u, String(describing: connection)) - case let .contactDisabled(u, contact): return withUser(u, String(describing: contact)) 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 let .remoteCtrlStopped(rcsState, rcStopReason): return "rcsState: \(String(describing: rcsState))\nrcStopReason: \(String(describing: rcStopReason))" - case let .contactPQEnabled(u, contact, pqEnabled): return withUser(u, "contact: \(String(describing: contact))\npqEnabled: \(pqEnabled)") 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 .agentSubsTotal(u, subsTotal, hasSession): return withUser(u, "subsTotal: \(String(describing: subsTotal))\nhasSession: \(hasSession)") case let .agentServersSummary(u, serversSummary): return withUser(u, String(describing: serversSummary)) case let .agentSubsSummary(u, subsSummary): return withUser(u, String(describing: subsSummary)) case let .chatCmdError(u, chatError): return withUser(u, String(describing: chatError)) - case let .chatError(u, chatError): return withUser(u, String(describing: chatError)) case let .archiveExported(archiveErrors): return String(describing: archiveErrors) case let .archiveImported(archiveErrors): return String(describing: archiveErrors) case let .appSettings(appSettings): return String(describing: appSettings) @@ -1162,10 +965,6 @@ enum ChatResponse: Decodable, Error, ChatRespProtocol { if let jError = jResp["chatCmdError"] as? NSDictionary { return .chatCmdError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) } - } else if type == "chatError" { - if let jError = jResp["chatError"] as? NSDictionary { - return .chatError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) - } } } json = serializeJSON(j, options: .prettyPrinted) @@ -1176,7 +975,6 @@ enum ChatResponse: Decodable, Error, ChatRespProtocol { var chatError: ChatError? { switch self { case let .chatCmdError(_, error): error - case let .chatError(_, error): error default: nil } } @@ -1184,6 +982,289 @@ enum ChatResponse: Decodable, Error, ChatRespProtocol { var chatErrorType: ChatErrorType? { switch self { case let .chatCmdError(_, .error(error)): error + default: nil + } + } +} + +enum ChatEvent: Decodable, ChatEventProtocol { + case event(type: String, json: String) + case chatSuspended + case contactSwitch(user: UserRef, contact: Contact, switchProgress: SwitchProgress) + case groupMemberSwitch(user: UserRef, groupInfo: GroupInfo, member: GroupMember, switchProgress: SwitchProgress) + case contactRatchetSync(user: UserRef, contact: Contact, ratchetSyncProgress: RatchetSyncProgress) + case groupMemberRatchetSync(user: UserRef, groupInfo: GroupInfo, member: GroupMember, ratchetSyncProgress: RatchetSyncProgress) + case contactDeletedByContact(user: UserRef, contact: Contact) + case contactConnected(user: UserRef, contact: Contact, userCustomProfile: Profile?) + case contactConnecting(user: UserRef, contact: Contact) + case contactSndReady(user: UserRef, contact: Contact) + case receivedContactRequest(user: UserRef, contactRequest: UserContactRequest) + case contactUpdated(user: UserRef, toContact: Contact) + case groupMemberUpdated(user: UserRef, groupInfo: GroupInfo, fromMember: GroupMember, toMember: GroupMember) + case contactsMerged(user: UserRef, intoContact: Contact, mergedContact: Contact) + case networkStatus(networkStatus: NetworkStatus, connections: [String]) + case networkStatuses(user_: UserRef?, networkStatuses: [ConnNetworkStatus]) + case newChatItems(user: UserRef, chatItems: [AChatItem]) + case chatItemsStatusesUpdated(user: UserRef, chatItems: [AChatItem]) + case chatItemUpdated(user: UserRef, chatItem: AChatItem) + case chatItemReaction(user: UserRef, added: Bool, reaction: ACIReaction) + case chatItemsDeleted(user: UserRef, chatItemDeletions: [ChatItemDeletion], byUser: Bool) + // group events + case groupChatItemsDeleted(user: UserRef, groupInfo: GroupInfo, chatItemIDs: Set, byUser: Bool, member_: GroupMember?) + case receivedGroupInvitation(user: UserRef, groupInfo: GroupInfo, contact: Contact, memberRole: GroupMemberRole) + case userAcceptedGroupSent(user: UserRef, groupInfo: GroupInfo, hostContact: Contact?) + case groupLinkConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember) + case businessLinkConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember, fromContact: Contact) + case joinedGroupMemberConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember, member: GroupMember) + case memberRole(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, member: GroupMember, fromRole: GroupMemberRole, toRole: GroupMemberRole) + case memberBlockedForAll(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, member: GroupMember, blocked: Bool) + case deletedMemberUser(user: UserRef, groupInfo: GroupInfo, member: GroupMember, withMessages: Bool) + case deletedMember(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, deletedMember: GroupMember, withMessages: Bool) + case leftMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember) + case groupDeleted(user: UserRef, groupInfo: GroupInfo, member: GroupMember) + case userJoinedGroup(user: UserRef, groupInfo: GroupInfo) + case joinedGroupMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember) + case connectedToGroupMember(user: UserRef, groupInfo: GroupInfo, member: GroupMember, memberContact: Contact?) + case groupUpdated(user: UserRef, toGroup: GroupInfo) + case newMemberContactReceivedInv(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember) + // receiving file events + case rcvFileAccepted(user: UserRef, chatItem: AChatItem) + case rcvFileAcceptedSndCancelled(user: UserRef, rcvFileTransfer: RcvFileTransfer) + case rcvFileStart(user: UserRef, chatItem: AChatItem) // send by chats + case rcvFileProgressXFTP(user: UserRef, chatItem_: AChatItem?, receivedSize: Int64, totalSize: Int64, rcvFileTransfer: RcvFileTransfer) + case rcvFileComplete(user: UserRef, chatItem: AChatItem) + case rcvStandaloneFileComplete(user: UserRef, targetPath: String, rcvFileTransfer: RcvFileTransfer) + case rcvFileSndCancelled(user: UserRef, chatItem: AChatItem, rcvFileTransfer: RcvFileTransfer) + case rcvFileError(user: UserRef, chatItem_: AChatItem?, agentError: AgentErrorType, rcvFileTransfer: RcvFileTransfer) + case rcvFileWarning(user: UserRef, chatItem_: AChatItem?, agentError: AgentErrorType, rcvFileTransfer: RcvFileTransfer) + // sending file events + case sndFileStart(user: UserRef, chatItem: AChatItem, sndFileTransfer: SndFileTransfer) + case sndFileComplete(user: UserRef, chatItem: AChatItem, sndFileTransfer: SndFileTransfer) + case sndFileRcvCancelled(user: UserRef, chatItem_: AChatItem?, sndFileTransfer: SndFileTransfer) + case sndFileProgressXFTP(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, sentSize: Int64, totalSize: Int64) + case sndFileRedirectStartXFTP(user: UserRef, fileTransferMeta: FileTransferMeta, redirectMeta: FileTransferMeta) + case sndFileCompleteXFTP(user: UserRef, chatItem: AChatItem, fileTransferMeta: FileTransferMeta) + case sndStandaloneFileComplete(user: UserRef, fileTransferMeta: FileTransferMeta, rcvURIs: [String]) + case sndFileError(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, errorMessage: String) + case sndFileWarning(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, errorMessage: String) + // call events + case callInvitation(callInvitation: RcvCallInvitation) + case callOffer(user: UserRef, contact: Contact, callType: CallType, offer: WebRTCSession, sharedKey: String?, askConfirmation: Bool) + case callAnswer(user: UserRef, contact: Contact, answer: WebRTCSession) + case callExtraInfo(user: UserRef, contact: Contact, extraInfo: WebRTCExtraInfo) + case callEnded(user: UserRef, contact: Contact) + case contactDisabled(user: UserRef, contact: Contact) + // notification marker + case ntfMessage(user: UserRef, connEntity: ConnectionEntity, ntfMessage: NtfMsgAckInfo) + // remote desktop responses + case remoteCtrlFound(remoteCtrl: RemoteCtrlInfo, ctrlAppInfo_: CtrlAppInfo?, appVersion: String, compatible: Bool) + case remoteCtrlSessionCode(remoteCtrl_: RemoteCtrlInfo?, sessionCode: String) + case remoteCtrlConnected(remoteCtrl: RemoteCtrlInfo) + case remoteCtrlStopped(rcsState: RemoteCtrlSessionState, rcStopReason: RemoteCtrlStopReason) + // pq + case contactPQEnabled(user: UserRef, contact: Contact, pqEnabled: Bool) + case chatError(user_: UserRef?, chatError: ChatError) + + var eventType: String { + switch self { + case let .event(type, _): "* \(type)" + case .chatSuspended: "chatSuspended" + case .contactSwitch: "contactSwitch" + case .groupMemberSwitch: "groupMemberSwitch" + case .contactRatchetSync: "contactRatchetSync" + case .groupMemberRatchetSync: "groupMemberRatchetSync" + case .contactDeletedByContact: "contactDeletedByContact" + case .contactConnected: "contactConnected" + case .contactConnecting: "contactConnecting" + case .contactSndReady: "contactSndReady" + case .receivedContactRequest: "receivedContactRequest" + case .contactUpdated: "contactUpdated" + case .groupMemberUpdated: "groupMemberUpdated" + case .contactsMerged: "contactsMerged" + case .networkStatus: "networkStatus" + case .networkStatuses: "networkStatuses" + case .newChatItems: "newChatItems" + case .chatItemsStatusesUpdated: "chatItemsStatusesUpdated" + case .chatItemUpdated: "chatItemUpdated" + case .chatItemReaction: "chatItemReaction" + case .chatItemsDeleted: "chatItemsDeleted" + case .groupChatItemsDeleted: "groupChatItemsDeleted" + case .receivedGroupInvitation: "receivedGroupInvitation" + case .userAcceptedGroupSent: "userAcceptedGroupSent" + case .groupLinkConnecting: "groupLinkConnecting" + case .businessLinkConnecting: "businessLinkConnecting" + case .joinedGroupMemberConnecting: "joinedGroupMemberConnecting" + case .memberRole: "memberRole" + case .memberBlockedForAll: "memberBlockedForAll" + case .deletedMemberUser: "deletedMemberUser" + case .deletedMember: "deletedMember" + case .leftMember: "leftMember" + case .groupDeleted: "groupDeleted" + case .userJoinedGroup: "userJoinedGroup" + case .joinedGroupMember: "joinedGroupMember" + case .connectedToGroupMember: "connectedToGroupMember" + case .groupUpdated: "groupUpdated" + case .newMemberContactReceivedInv: "newMemberContactReceivedInv" + case .rcvFileAccepted: "rcvFileAccepted" + case .rcvFileAcceptedSndCancelled: "rcvFileAcceptedSndCancelled" + case .rcvFileStart: "rcvFileStart" + case .rcvFileProgressXFTP: "rcvFileProgressXFTP" + case .rcvFileComplete: "rcvFileComplete" + case .rcvStandaloneFileComplete: "rcvStandaloneFileComplete" + case .rcvFileSndCancelled: "rcvFileSndCancelled" + case .rcvFileError: "rcvFileError" + case .rcvFileWarning: "rcvFileWarning" + case .sndFileStart: "sndFileStart" + case .sndFileComplete: "sndFileComplete" + case .sndFileRcvCancelled: "sndFileRcvCancelled" + case .sndFileProgressXFTP: "sndFileProgressXFTP" + case .sndFileRedirectStartXFTP: "sndFileRedirectStartXFTP" + case .sndFileCompleteXFTP: "sndFileCompleteXFTP" + case .sndStandaloneFileComplete: "sndStandaloneFileComplete" + case .sndFileError: "sndFileError" + case .sndFileWarning: "sndFileWarning" + case .callInvitation: "callInvitation" + case .callOffer: "callOffer" + case .callAnswer: "callAnswer" + case .callExtraInfo: "callExtraInfo" + case .callEnded: "callEnded" + case .contactDisabled: "contactDisabled" + case .ntfMessage: "ntfMessage" + case .remoteCtrlFound: "remoteCtrlFound" + case .remoteCtrlSessionCode: "remoteCtrlSessionCode" + case .remoteCtrlConnected: "remoteCtrlConnected" + case .remoteCtrlStopped: "remoteCtrlStopped" + case .contactPQEnabled: "contactPQEnabled" + case .chatError: "chatError" + } + } + + var details: String { + switch self { + case let .event(_, json): return json + case .chatSuspended: return noDetails + case let .contactSwitch(u, contact, switchProgress): return withUser(u, "contact: \(String(describing: contact))\nswitchProgress: \(String(describing: switchProgress))") + case let .groupMemberSwitch(u, groupInfo, member, switchProgress): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nswitchProgress: \(String(describing: switchProgress))") + case let .contactRatchetSync(u, contact, ratchetSyncProgress): return withUser(u, "contact: \(String(describing: contact))\nratchetSyncProgress: \(String(describing: ratchetSyncProgress))") + case let .groupMemberRatchetSync(u, groupInfo, member, ratchetSyncProgress): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nratchetSyncProgress: \(String(describing: ratchetSyncProgress))") + case let .contactDeletedByContact(u, contact): return withUser(u, String(describing: contact)) + case let .contactConnected(u, contact, _): return withUser(u, String(describing: contact)) + case let .contactConnecting(u, contact): return withUser(u, String(describing: contact)) + case let .contactSndReady(u, contact): return withUser(u, String(describing: contact)) + case let .receivedContactRequest(u, contactRequest): return withUser(u, String(describing: contactRequest)) + 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 .contactsMerged(u, intoContact, mergedContact): return withUser(u, "intoContact: \(intoContact)\nmergedContact: \(mergedContact)") + case let .networkStatus(status, conns): return "networkStatus: \(String(describing: status))\nconnections: \(String(describing: conns))" + case let .networkStatuses(u, statuses): return withUser(u, String(describing: statuses)) + case let .newChatItems(u, chatItems): + let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") + return withUser(u, itemsString) + case let .chatItemsStatusesUpdated(u, chatItems): + let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") + return withUser(u, itemsString) + case let .chatItemUpdated(u, chatItem): return withUser(u, String(describing: chatItem)) + case let .chatItemReaction(u, added, reaction): return withUser(u, "added: \(added)\n\(String(describing: reaction))") + case let .chatItemsDeleted(u, items, byUser): + let itemsString = items.map { item in + "deletedChatItem:\n\(String(describing: item.deletedChatItem))\ntoChatItem:\n\(String(describing: item.toChatItem))" }.joined(separator: "\n") + return withUser(u, itemsString + "\nbyUser: \(byUser)") + case let .groupChatItemsDeleted(u, gInfo, chatItemIDs, byUser, member_): + return withUser(u, "chatItemIDs: \(String(describing: chatItemIDs))\ngroupInfo: \(String(describing: gInfo))\nbyUser: \(byUser)\nmember_: \(String(describing: member_))") + case let .receivedGroupInvitation(u, groupInfo, contact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\ncontact: \(contact)\nmemberRole: \(memberRole)") + 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 .businessLinkConnecting(u, groupInfo, hostMember, fromContact): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(String(describing: hostMember))\nfromContact: \(String(describing: fromContact))") + case let .joinedGroupMemberConnecting(u, groupInfo, hostMember, member): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(hostMember)\nmember: \(member)") + case let .memberRole(u, groupInfo, byMember, member, fromRole, toRole): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\nmember: \(member)\nfromRole: \(fromRole)\ntoRole: \(toRole)") + case let .memberBlockedForAll(u, groupInfo, byMember, member, blocked): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\nmember: \(member)\nblocked: \(blocked)") + case let .deletedMemberUser(u, groupInfo, member, withMessages): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)\nwithMessages: \(withMessages)") + case let .deletedMember(u, groupInfo, byMember, deletedMember, withMessages): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\ndeletedMember: \(deletedMember)\nwithMessages: \(withMessages)") + case let .leftMember(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") + case let .groupDeleted(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") + case let .userJoinedGroup(u, groupInfo): return withUser(u, String(describing: groupInfo)) + case let .joinedGroupMember(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)") + case let .connectedToGroupMember(u, groupInfo, member, memberContact): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)\nmemberContact: \(String(describing: memberContact))") + case let .groupUpdated(u, toGroup): return withUser(u, String(describing: toGroup)) + case let .newMemberContactReceivedInv(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") + case let .rcvFileAccepted(u, chatItem): return withUser(u, String(describing: chatItem)) + case .rcvFileAcceptedSndCancelled: return noDetails + case let .rcvFileStart(u, chatItem): return withUser(u, String(describing: chatItem)) + case let .rcvFileProgressXFTP(u, chatItem, receivedSize, totalSize, _): return withUser(u, "chatItem: \(String(describing: chatItem))\nreceivedSize: \(receivedSize)\ntotalSize: \(totalSize)") + case let .rcvStandaloneFileComplete(u, targetPath, _): return withUser(u, targetPath) + case let .rcvFileComplete(u, chatItem): return withUser(u, String(describing: chatItem)) + case let .rcvFileSndCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .rcvFileError(u, chatItem, agentError, _): return withUser(u, "agentError: \(String(describing: agentError))\nchatItem: \(String(describing: chatItem))") + case let .rcvFileWarning(u, chatItem, agentError, _): return withUser(u, "agentError: \(String(describing: agentError))\nchatItem: \(String(describing: chatItem))") + case let .sndFileStart(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndFileComplete(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndFileRcvCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndFileProgressXFTP(u, chatItem, _, sentSize, totalSize): return withUser(u, "chatItem: \(String(describing: chatItem))\nsentSize: \(sentSize)\ntotalSize: \(totalSize)") + case let .sndFileRedirectStartXFTP(u, _, redirectMeta): return withUser(u, String(describing: redirectMeta)) + case let .sndFileCompleteXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndStandaloneFileComplete(u, _, rcvURIs): return withUser(u, String(rcvURIs.count)) + case let .sndFileError(u, chatItem, _, err): return withUser(u, "error: \(String(describing: err))\nchatItem: \(String(describing: chatItem))") + case let .sndFileWarning(u, chatItem, _, err): return withUser(u, "error: \(String(describing: err))\nchatItem: \(String(describing: chatItem))") + case let .callInvitation(inv): return String(describing: inv) + case let .callOffer(u, contact, callType, offer, sharedKey, askConfirmation): return withUser(u, "contact: \(contact.id)\ncallType: \(String(describing: callType))\nsharedKey: \(sharedKey ?? "")\naskConfirmation: \(askConfirmation)\noffer: \(String(describing: offer))") + case let .callAnswer(u, contact, answer): return withUser(u, "contact: \(contact.id)\nanswer: \(String(describing: answer))") + case let .callExtraInfo(u, contact, extraInfo): return withUser(u, "contact: \(contact.id)\nextraInfo: \(String(describing: extraInfo))") + case let .callEnded(u, contact): return withUser(u, "contact: \(contact.id)") + case let .contactDisabled(u, contact): return withUser(u, String(describing: contact)) + case let .ntfMessage(u, connEntity, ntfMessage): return withUser(u, "connEntity: \(String(describing: connEntity))\nntfMessage: \(String(describing: ntfMessage))") + case let .remoteCtrlFound(remoteCtrl, ctrlAppInfo_, appVersion, compatible): return "remoteCtrl:\n\(String(describing: remoteCtrl))\nctrlAppInfo_:\n\(String(describing: ctrlAppInfo_))\nappVersion: \(appVersion)\ncompatible: \(compatible)" + case let .remoteCtrlSessionCode(remoteCtrl_, sessionCode): return "remoteCtrl_:\n\(String(describing: remoteCtrl_))\nsessionCode: \(sessionCode)" + case let .remoteCtrlConnected(remoteCtrl): return String(describing: remoteCtrl) + case let .remoteCtrlStopped(rcsState, rcStopReason): return "rcsState: \(String(describing: rcsState))\nrcStopReason: \(String(describing: rcStopReason))" + case let .contactPQEnabled(u, contact, pqEnabled): return withUser(u, "contact: \(String(describing: contact))\npqEnabled: \(pqEnabled)") + case let .chatError(u, chatError): return withUser(u, String(describing: chatError)) + } + } + + private var noDetails: String { "\(eventType): no details" } + + static func chatEvent(_ s: String) -> ChatEvent { + let d = s.data(using: .utf8)! + // TODO is there a way to do it without copying the data? e.g: + // let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson)) + // let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free) + do { + let r = // try callWithLargeStack { + try jsonDecoder.decode(APIResponse.self, from: d) +// } + return r.resp + } catch { + logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)") + } + + var type: String? + var json: String? + if let j = try? JSONSerialization.jsonObject(with: d) as? NSDictionary { + if let jResp = j["resp"] as? NSDictionary, jResp.count == 1 || jResp.count == 2 { + type = jResp.allKeys[0] as? String + if jResp.count == 2 && type == "_owsf" { + type = jResp.allKeys[1] as? String + } + if type == "chatError" { + if let jError = jResp["chatError"] as? NSDictionary { + return .chatError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) + } + } + } + json = serializeJSON(j, options: .prettyPrinted) + } + return ChatEvent.event(type: type ?? "invalid", json: json ?? s) + } + + var chatError: ChatError? { + switch self { + case let .chatError(_, error): error + default: nil + } + } + + var chatErrorType: ChatErrorType? { + switch self { case let .chatError(_, .error(error)): error default: nil } diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 4e9c8ce7b6..a6b9e719c7 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -20,12 +20,14 @@ private let networkStatusesLock = DispatchQueue(label: "chat.simplex.app.network enum TerminalItem: Identifiable { case cmd(Date, ChatCommand) case resp(Date, ChatResponse) + case event(Date, ChatEvent) var id: Date { get { switch self { - case let .cmd(id, _): return id - case let .resp(id, _): return id + case let .cmd(d, _): return d + case let .resp(d, _): return d + case let .event(d, _): return d } } } @@ -35,6 +37,7 @@ enum TerminalItem: Identifiable { switch self { case let .cmd(_, cmd): return "> \(cmd.cmdString.prefix(30))" case let .resp(_, resp): return "< \(resp.responseType)" + case let .event(_, evt): return "< \(evt.eventType)" } } } @@ -44,6 +47,7 @@ enum TerminalItem: Identifiable { switch self { case let .cmd(_, cmd): return cmd.cmdString case let .resp(_, resp): return resp.details + case let .event(_, evt): return evt.details } } } @@ -112,12 +116,12 @@ func chatSendCmd(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil } } -func chatRecvMsg(_ ctrl: chat_ctrl? = nil) async -> ChatResponse? { +func chatRecvMsg(_ ctrl: chat_ctrl? = nil) async -> ChatEvent? { await withCheckedContinuation { cont in - _ = withBGTask(bgDelay: msgDelay) { () -> ChatResponse? in - let resp: ChatResponse? = recvSimpleXMsg(ctrl) - cont.resume(returning: resp) - return resp + _ = withBGTask(bgDelay: msgDelay) { () -> ChatEvent? in + let evt: ChatEvent? = recvSimpleXMsg(ctrl) + cont.resume(returning: evt) + return evt } } } @@ -476,8 +480,11 @@ private func createChatItemsErrorAlert(_ r: ChatResponse) { func apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, updatedMessage: UpdatedMessage, live: Bool = false) async throws -> ChatItem { let r = await chatSendCmd(.apiUpdateChatItem(type: type, id: id, itemId: itemId, updatedMessage: updatedMessage, live: live), bgDelay: msgDelay) - if case let .chatItemUpdated(_, aChatItem) = r { return aChatItem.chatItem } - throw r + switch r { + case let .chatItemUpdated(_, aChatItem): return aChatItem.chatItem + case let .chatItemNotChanged(_, aChatItem): return aChatItem.chatItem + default: throw r + } } func apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, reaction: MsgReaction) async throws -> ChatItem { @@ -1280,6 +1287,10 @@ func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool switch r { case let .rcvFileAccepted(_, chatItem): await chatItemSimpleUpdate(user, chatItem) +// TODO when aChatItem added +// case let .rcvFileAcceptedSndCancelled(user, aChatItem, _): +// await chatItemSimpleUpdate(user, aChatItem) +// Task { cleanupFile(aChatItem) } default: if let chatError = r.chatErrorType { switch chatError { @@ -1925,7 +1936,7 @@ class ChatReceiver { private var receiveMessages = true private var _lastMsgTime = Date.now - var messagesChannel: ((ChatResponse) -> Void)? = nil + var messagesChannel: ((ChatEvent) -> Void)? = nil static let shared = ChatReceiver() @@ -1960,13 +1971,13 @@ class ChatReceiver { } } -func processReceivedMsg(_ res: ChatResponse) async { +func processReceivedMsg(_ res: ChatEvent) async { Task { - await TerminalItems.shared.add(.resp(.now, res)) + await TerminalItems.shared.add(.event(.now, res)) } let m = ChatModel.shared let n = NetworkModel.shared - logger.debug("processReceivedMsg: \(res.responseType)") + logger.debug("processReceivedMsg: \(res.eventType)") switch res { case let .contactDeletedByContact(user, contact): if active(user) && contact.directOrUsed { @@ -2281,6 +2292,10 @@ func processReceivedMsg(_ res: ChatResponse) async { } case let .rcvFileAccepted(user, aChatItem): // usually rcvFileAccepted is a response, but it's also an event for XFTP files auto-accepted from NSE await chatItemSimpleUpdate(user, aChatItem) +// TODO when aChatItem added +// case let .rcvFileAcceptedSndCancelled(user, aChatItem, _): // usually rcvFileAcceptedSndCancelled is a response, but it's also an event for XFTP files auto-accepted from NSE +// await chatItemSimpleUpdate(user, aChatItem) +// Task { cleanupFile(aChatItem) } case let .rcvFileStart(user, aChatItem): await chatItemSimpleUpdate(user, aChatItem) case let .rcvFileComplete(user, aChatItem): @@ -2460,14 +2475,14 @@ func processReceivedMsg(_ res: ChatResponse) async { } } default: - logger.debug("unsupported event: \(res.responseType)") + logger.debug("unsupported event: \(res.eventType)") } func withCall(_ contact: Contact, _ perform: (Call) async -> Void) async { if let call = m.activeCall, call.contact.apiId == contact.apiId { await perform(call) } else { - logger.debug("processReceivedMsg: ignoring \(res.responseType), not in call with the contact \(contact.id)") + logger.debug("processReceivedMsg: ignoring \(res.eventType), not in call with the contact \(contact.id)") } } } diff --git a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift index dfe9e37bd6..c684ad627a 100644 --- a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift +++ b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift @@ -550,7 +550,7 @@ struct MigrateFromDevice: View { alert = .error(title: "Upload failed", error: "Check your internet connection and try again") migrationState = .uploadFailed(totalBytes: totalBytes, archivePath: archivePath) default: - logger.debug("unsupported event: \(msg.responseType)") + logger.debug("unsupported event: \(msg.eventType)") } } } @@ -733,11 +733,11 @@ func chatStoppedView() -> some View { private class MigrationChatReceiver { let ctrl: chat_ctrl let databaseUrl: URL - let processReceivedMsg: (ChatResponse) async -> Void + let processReceivedMsg: (ChatEvent) async -> Void private var receiveLoop: Task? private var receiveMessages = true - init(ctrl: chat_ctrl, databaseUrl: URL, _ processReceivedMsg: @escaping (ChatResponse) async -> Void) { + init(ctrl: chat_ctrl, databaseUrl: URL, _ processReceivedMsg: @escaping (ChatEvent) async -> Void) { self.ctrl = ctrl self.databaseUrl = databaseUrl self.processReceivedMsg = processReceivedMsg @@ -752,11 +752,11 @@ private class MigrationChatReceiver { func receiveMsgLoop() async { // TODO use function that has timeout - if let msg: ChatResponse = await chatRecvMsg(ctrl) { + if let msg: ChatEvent = await chatRecvMsg(ctrl) { Task { - await TerminalItems.shared.add(.resp(.now, msg)) + await TerminalItems.shared.add(.event(.now, msg)) } - logger.debug("processReceivedMsg: \(msg.responseType)") + logger.debug("processReceivedMsg: \(msg.eventType)") await processReceivedMsg(msg) } if self.receiveMessages { diff --git a/apps/ios/Shared/Views/Migration/MigrateToDevice.swift b/apps/ios/Shared/Views/Migration/MigrateToDevice.swift index 1a740874a6..19cefa7f4d 100644 --- a/apps/ios/Shared/Views/Migration/MigrateToDevice.swift +++ b/apps/ios/Shared/Views/Migration/MigrateToDevice.swift @@ -516,7 +516,7 @@ struct MigrateToDevice: View { alert = .error(title: "Download failed", error: "File was deleted or link is invalid") migrationState = .downloadFailed(totalBytes: totalBytes, link: link, archivePath: archivePath) default: - logger.debug("unsupported event: \(msg.responseType)") + logger.debug("unsupported event: \(msg.eventType)") } } } @@ -751,11 +751,11 @@ private func progressView() -> some View { private class MigrationChatReceiver { let ctrl: chat_ctrl let databaseUrl: URL - let processReceivedMsg: (ChatResponse) async -> Void + let processReceivedMsg: (ChatEvent) async -> Void private var receiveLoop: Task? private var receiveMessages = true - init(ctrl: chat_ctrl, databaseUrl: URL, _ processReceivedMsg: @escaping (ChatResponse) async -> Void) { + init(ctrl: chat_ctrl, databaseUrl: URL, _ processReceivedMsg: @escaping (ChatEvent) async -> Void) { self.ctrl = ctrl self.databaseUrl = databaseUrl self.processReceivedMsg = processReceivedMsg @@ -772,9 +772,9 @@ private class MigrationChatReceiver { // TODO use function that has timeout if let msg = await chatRecvMsg(ctrl) { Task { - await TerminalItems.shared.add(.resp(.now, msg)) + await TerminalItems.shared.add(.event(.now, msg)) } - logger.debug("processReceivedMsg: \(msg.responseType)") + logger.debug("processReceivedMsg: \(msg.eventType)") await processReceivedMsg(msg) } if self.receiveMessages { diff --git a/apps/ios/SimpleX NSE/NSEAPITypes.swift b/apps/ios/SimpleX NSE/NSEAPITypes.swift index b1ab5e76c2..7569547e6a 100644 --- a/apps/ios/SimpleX NSE/NSEAPITypes.swift +++ b/apps/ios/SimpleX NSE/NSEAPITypes.swift @@ -52,21 +52,12 @@ enum NSEChatResponse: Decodable, Error, ChatRespProtocol { case activeUser(user: User) case chatStarted case chatRunning - case chatSuspended - case contactConnected(user: UserRef, contact: Contact, userCustomProfile: Profile?) - case receivedContactRequest(user: UserRef, contactRequest: UserContactRequest) - case newChatItems(user: UserRef, chatItems: [AChatItem]) case rcvFileAccepted(user: UserRef, chatItem: AChatItem) - case rcvFileSndCancelled(user: UserRef, chatItem: AChatItem, rcvFileTransfer: RcvFileTransfer) - case sndFileComplete(user: UserRef, chatItem: AChatItem, sndFileTransfer: SndFileTransfer) - case sndFileRcvCancelled(user: UserRef, chatItem_: AChatItem?, sndFileTransfer: SndFileTransfer) - case callInvitation(callInvitation: RcvCallInvitation) case ntfConns(ntfConns: [NtfConn]) case connNtfMessages(receivedMsgs: [NtfMsgInfo?]) case ntfMessage(user: UserRef, connEntity: ConnectionEntity, ntfMessage: NtfMsgAckInfo) case cmdOk(user_: UserRef?) case chatCmdError(user_: UserRef?, chatError: ChatError) - case chatError(user_: UserRef?, chatError: ChatError) var responseType: String { switch self { @@ -74,21 +65,12 @@ enum NSEChatResponse: Decodable, Error, ChatRespProtocol { case .activeUser: "activeUser" case .chatStarted: "chatStarted" case .chatRunning: "chatRunning" - case .chatSuspended: "chatSuspended" - case .contactConnected: "contactConnected" - case .receivedContactRequest: "receivedContactRequest" - case .newChatItems: "newChatItems" case .rcvFileAccepted: "rcvFileAccepted" - case .rcvFileSndCancelled: "rcvFileSndCancelled" - case .sndFileComplete: "sndFileComplete" - case .sndFileRcvCancelled: "sndFileRcvCancelled" - case .callInvitation: "callInvitation" case .ntfConns: "ntfConns" case .connNtfMessages: "connNtfMessages" case .ntfMessage: "ntfMessage" case .cmdOk: "cmdOk" case .chatCmdError: "chatCmdError" - case .chatError: "chatError" } } @@ -98,23 +80,12 @@ enum NSEChatResponse: Decodable, Error, ChatRespProtocol { case let .activeUser(user): return String(describing: user) case .chatStarted: return noDetails case .chatRunning: return noDetails - case .chatSuspended: return noDetails - case let .contactConnected(u, contact, _): return withUser(u, String(describing: contact)) - case let .receivedContactRequest(u, contactRequest): return withUser(u, String(describing: contactRequest)) - case let .newChatItems(u, chatItems): - let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") - return withUser(u, itemsString) case let .rcvFileAccepted(u, chatItem): return withUser(u, String(describing: chatItem)) - case let .rcvFileSndCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .sndFileComplete(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .sndFileRcvCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .callInvitation(inv): return String(describing: inv) case let .ntfConns(ntfConns): return String(describing: ntfConns) case let .connNtfMessages(receivedMsgs): return "receivedMsgs: \(String(describing: receivedMsgs))" case let .ntfMessage(u, connEntity, ntfMessage): return withUser(u, "connEntity: \(String(describing: connEntity))\nntfMessage: \(String(describing: ntfMessage))") case .cmdOk: return noDetails case let .chatCmdError(u, chatError): return withUser(u, String(describing: chatError)) - case let .chatError(u, chatError): return withUser(u, String(describing: chatError)) } } @@ -144,10 +115,6 @@ enum NSEChatResponse: Decodable, Error, ChatRespProtocol { if let jError = jResp["chatCmdError"] as? NSDictionary { return .chatCmdError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) } - } else if type == "chatError" { - if let jError = jResp["chatError"] as? NSDictionary { - return .chatError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) - } } } json = serializeJSON(j, options: .prettyPrinted) @@ -158,7 +125,6 @@ enum NSEChatResponse: Decodable, Error, ChatRespProtocol { var chatError: ChatError? { switch self { case let .chatCmdError(_, error): error - case let .chatError(_, error): error default: nil } } @@ -166,6 +132,100 @@ enum NSEChatResponse: Decodable, Error, ChatRespProtocol { var chatErrorType: ChatErrorType? { switch self { case let .chatCmdError(_, .error(error)): error + default: nil + } + } +} + +enum NSEChatEvent: Decodable, Error, ChatEventProtocol { + case event(type: String, json: String) + case chatSuspended + case contactConnected(user: UserRef, contact: Contact, userCustomProfile: Profile?) + case receivedContactRequest(user: UserRef, contactRequest: UserContactRequest) + case newChatItems(user: UserRef, chatItems: [AChatItem]) + case rcvFileSndCancelled(user: UserRef, chatItem: AChatItem, rcvFileTransfer: RcvFileTransfer) + case sndFileComplete(user: UserRef, chatItem: AChatItem, sndFileTransfer: SndFileTransfer) + case sndFileRcvCancelled(user: UserRef, chatItem_: AChatItem?, sndFileTransfer: SndFileTransfer) + case callInvitation(callInvitation: RcvCallInvitation) + case ntfMessage(user: UserRef, connEntity: ConnectionEntity, ntfMessage: NtfMsgAckInfo) + case chatError(user_: UserRef?, chatError: ChatError) + + var eventType: String { + switch self { + case let .event(type, _): "* \(type)" + case .chatSuspended: "chatSuspended" + case .contactConnected: "contactConnected" + case .receivedContactRequest: "receivedContactRequest" + case .newChatItems: "newChatItems" + case .rcvFileSndCancelled: "rcvFileSndCancelled" + case .sndFileComplete: "sndFileComplete" + case .sndFileRcvCancelled: "sndFileRcvCancelled" + case .callInvitation: "callInvitation" + case .ntfMessage: "ntfMessage" + case .chatError: "chatError" + } + } + + var details: String { + switch self { + case let .event(_, json): return json + case .chatSuspended: return noDetails + case let .contactConnected(u, contact, _): return withUser(u, String(describing: contact)) + case let .receivedContactRequest(u, contactRequest): return withUser(u, String(describing: contactRequest)) + case let .newChatItems(u, chatItems): + let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") + return withUser(u, itemsString) + case let .rcvFileSndCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndFileComplete(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndFileRcvCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .callInvitation(inv): return String(describing: inv) + case let .ntfMessage(u, connEntity, ntfMessage): return withUser(u, "connEntity: \(String(describing: connEntity))\nntfMessage: \(String(describing: ntfMessage))") + case let .chatError(u, chatError): return withUser(u, String(describing: chatError)) + } + } + + var noDetails: String { "\(eventType): no details" } + + static func chatEvent(_ s: String) -> NSEChatEvent { + let d = s.data(using: .utf8)! + // TODO is there a way to do it without copying the data? e.g: + // let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson)) + // let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free) + do { + let r = try jsonDecoder.decode(APIResponse.self, from: d) + return r.resp + } catch { + logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)") + } + + var type: String? + var json: String? + if let j = try? JSONSerialization.jsonObject(with: d) as? NSDictionary { + if let jResp = j["resp"] as? NSDictionary, jResp.count == 1 || jResp.count == 2 { + type = jResp.allKeys[0] as? String + if jResp.count == 2 && type == "_owsf" { + type = jResp.allKeys[1] as? String + } + if type == "chatError" { + if let jError = jResp["chatError"] as? NSDictionary { + return .chatError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) + } + } + } + json = serializeJSON(j, options: .prettyPrinted) + } + return NSEChatEvent.event(type: type ?? "invalid", json: json ?? s) + } + + var chatError: ChatError? { + switch self { + case let .chatError(_, error): error + default: nil + } + } + + var chatErrorType: ChatErrorType? { + switch self { case let .chatError(_, .error(error)): error default: nil } diff --git a/apps/ios/SimpleX NSE/NotificationService.swift b/apps/ios/SimpleX NSE/NotificationService.swift index 0bfa21781e..e8dd21f23c 100644 --- a/apps/ios/SimpleX NSE/NotificationService.swift +++ b/apps/ios/SimpleX NSE/NotificationService.swift @@ -789,9 +789,9 @@ func receiveMessages() async { } } -func chatRecvMsg() async -> NSEChatResponse? { +func chatRecvMsg() async -> NSEChatEvent? { await withCheckedContinuation { cont in - let resp: NSEChatResponse? = recvSimpleXMsg() + let resp: NSEChatEvent? = recvSimpleXMsg() cont.resume(returning: resp) } } @@ -799,8 +799,8 @@ func chatRecvMsg() async -> NSEChatResponse? { private let isInChina = SKStorefront().countryCode == "CHN" private func useCallKit() -> Bool { !isInChina && callKitEnabledGroupDefault.get() } -func receivedMsgNtf(_ res: NSEChatResponse) async -> (String, NSENotificationData)? { - logger.debug("NotificationService receivedMsgNtf: \(res.responseType)") +func receivedMsgNtf(_ res: NSEChatEvent) async -> (String, NSENotificationData)? { + logger.debug("NotificationService receivedMsgNtf: \(res.eventType)") switch res { case let .contactConnected(user, contact, _): return (contact.id, .contactConnected(user, contact)) @@ -849,7 +849,7 @@ func receivedMsgNtf(_ res: NSEChatResponse) async -> (String, NSENotificationDat logger.error("NotificationService receivedMsgNtf error: \(String(describing: err))") return nil default: - logger.debug("NotificationService receivedMsgNtf ignored event: \(res.responseType)") + logger.debug("NotificationService receivedMsgNtf ignored event: \(res.eventType)") return nil } } diff --git a/apps/ios/SimpleX SE/ShareAPI.swift b/apps/ios/SimpleX SE/ShareAPI.swift index 56f1c2f5f3..0f12b002f7 100644 --- a/apps/ios/SimpleX SE/ShareAPI.swift +++ b/apps/ios/SimpleX SE/ShareAPI.swift @@ -96,7 +96,7 @@ func apiSuspendChat(expired: Bool) { if case .cmdOk = r, !expired { let startTime = CFAbsoluteTimeGetCurrent() while CFAbsoluteTimeGetCurrent() - startTime < 3 { - let msg: SEChatResponse? = recvSimpleXMsg(messageTimeout: 3_500000) + let msg: SEChatEvent? = recvSimpleXMsg(messageTimeout: 3_500000) switch msg { case .chatSuspended: suspended = false @@ -156,17 +156,10 @@ enum SEChatResponse: Decodable, Error, ChatRespProtocol { case activeUser(user: User) case chatStarted case chatRunning - case chatSuspended case apiChats(user: UserRef, chats: [ChatData]) case newChatItems(user: UserRef, chatItems: [AChatItem]) - case sndFileProgressXFTP(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, sentSize: Int64, totalSize: Int64) - case sndFileCompleteXFTP(user: UserRef, chatItem: AChatItem, fileTransferMeta: FileTransferMeta) - case chatItemsStatusesUpdated(user: UserRef, chatItems: [AChatItem]) - case sndFileError(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, errorMessage: String) - case sndFileWarning(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, errorMessage: String) case cmdOk(user_: UserRef?) case chatCmdError(user_: UserRef?, chatError: ChatError) - case chatError(user_: UserRef?, chatError: ChatError) var responseType: String { switch self { @@ -174,17 +167,10 @@ enum SEChatResponse: Decodable, Error, ChatRespProtocol { case .activeUser: "activeUser" case .chatStarted: "chatStarted" case .chatRunning: "chatRunning" - case .chatSuspended: "chatSuspended" case .apiChats: "apiChats" case .newChatItems: "newChatItems" - case .sndFileProgressXFTP: "sndFileProgressXFTP" - case .sndFileCompleteXFTP: "sndFileCompleteXFTP" - case .chatItemsStatusesUpdated: "chatItemsStatusesUpdated" - case .sndFileError: "sndFileError" - case .sndFileWarning: "sndFileWarning" case .cmdOk: "cmdOk" case .chatCmdError: "chatCmdError" - case .chatError: "chatError" } } @@ -194,21 +180,12 @@ enum SEChatResponse: Decodable, Error, ChatRespProtocol { case let .activeUser(user): return String(describing: user) case .chatStarted: return noDetails case .chatRunning: return noDetails - case .chatSuspended: return noDetails case let .apiChats(u, chats): return withUser(u, String(describing: chats)) case let .newChatItems(u, chatItems): let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") return withUser(u, itemsString) - case let .sndFileProgressXFTP(u, chatItem, _, sentSize, totalSize): return withUser(u, "chatItem: \(String(describing: chatItem))\nsentSize: \(sentSize)\ntotalSize: \(totalSize)") - case let .sndFileCompleteXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .chatItemsStatusesUpdated(u, chatItems): - let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") - return withUser(u, itemsString) - case let .sndFileError(u, chatItem, _, err): return withUser(u, "error: \(String(describing: err))\nchatItem: \(String(describing: chatItem))") - case let .sndFileWarning(u, chatItem, _, err): return withUser(u, "error: \(String(describing: err))\nchatItem: \(String(describing: chatItem))") case .cmdOk: return noDetails case let .chatCmdError(u, chatError): return withUser(u, String(describing: chatError)) - case let .chatError(u, chatError): return withUser(u, String(describing: chatError)) } } @@ -242,10 +219,6 @@ enum SEChatResponse: Decodable, Error, ChatRespProtocol { if let jError = jResp["chatCmdError"] as? NSDictionary { return .chatCmdError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) } - } else if type == "chatError" { - if let jError = jResp["chatError"] as? NSDictionary { - return .chatError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) - } } } json = serializeJSON(j, options: .prettyPrinted) @@ -256,7 +229,6 @@ enum SEChatResponse: Decodable, Error, ChatRespProtocol { var chatError: ChatError? { switch self { case let .chatCmdError(_, error): error - case let .chatError(_, error): error default: nil } } @@ -264,6 +236,90 @@ enum SEChatResponse: Decodable, Error, ChatRespProtocol { var chatErrorType: ChatErrorType? { switch self { case let .chatCmdError(_, .error(error)): error + default: nil + } + } +} + +enum SEChatEvent: Decodable, Error, ChatEventProtocol { + case event(type: String, json: String) + case chatSuspended + case sndFileProgressXFTP(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, sentSize: Int64, totalSize: Int64) + case sndFileCompleteXFTP(user: UserRef, chatItem: AChatItem, fileTransferMeta: FileTransferMeta) + case chatItemsStatusesUpdated(user: UserRef, chatItems: [AChatItem]) + case sndFileError(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, errorMessage: String) + case sndFileWarning(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, errorMessage: String) + case chatError(user_: UserRef?, chatError: ChatError) + + var eventType: String { + switch self { + case let .event(type, _): "* \(type)" + case .chatSuspended: "chatSuspended" + case .sndFileProgressXFTP: "sndFileProgressXFTP" + case .sndFileCompleteXFTP: "sndFileCompleteXFTP" + case .chatItemsStatusesUpdated: "chatItemsStatusesUpdated" + case .sndFileError: "sndFileError" + case .sndFileWarning: "sndFileWarning" + case .chatError: "chatError" + } + } + + var details: String { + switch self { + case let .event(_, json): return json + case .chatSuspended: return noDetails + case let .sndFileProgressXFTP(u, chatItem, _, sentSize, totalSize): return withUser(u, "chatItem: \(String(describing: chatItem))\nsentSize: \(sentSize)\ntotalSize: \(totalSize)") + case let .sndFileCompleteXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .chatItemsStatusesUpdated(u, chatItems): + let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") + return withUser(u, itemsString) + case let .sndFileError(u, chatItem, _, err): return withUser(u, "error: \(String(describing: err))\nchatItem: \(String(describing: chatItem))") + case let .sndFileWarning(u, chatItem, _, err): return withUser(u, "error: \(String(describing: err))\nchatItem: \(String(describing: chatItem))") + case let .chatError(u, chatError): return withUser(u, String(describing: chatError)) + } + } + + var noDetails: String { "\(eventType): no details" } + + static func chatEvent(_ s: String) -> SEChatEvent { + let d = s.data(using: .utf8)! + // TODO is there a way to do it without copying the data? e.g: + // let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson)) + // let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free) + do { + let r = try jsonDecoder.decode(APIResponse.self, from: d) + return r.resp + } catch { + logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)") + } + + var type: String? + var json: String? + if let j = try? JSONSerialization.jsonObject(with: d) as? NSDictionary { + if let jResp = j["resp"] as? NSDictionary, jResp.count == 1 || jResp.count == 2 { + type = jResp.allKeys[0] as? String + if jResp.count == 2 && type == "_owsf" { + type = jResp.allKeys[1] as? String + } + if type == "chatError" { + if let jError = jResp["chatError"] as? NSDictionary { + return .chatError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) + } + } + } + json = serializeJSON(j, options: .prettyPrinted) + } + return SEChatEvent.event(type: type ?? "invalid", json: json ?? s) + } + var chatError: ChatError? { + switch self { + case let .chatError(_, error): error + default: nil + } + } + + var chatErrorType: ChatErrorType? { + switch self { case let .chatError(_, .error(error)): error default: nil } diff --git a/apps/ios/SimpleX SE/ShareModel.swift b/apps/ios/SimpleX SE/ShareModel.swift index a555c14472..b4d26b6d54 100644 --- a/apps/ios/SimpleX SE/ShareModel.swift +++ b/apps/ios/SimpleX SE/ShareModel.swift @@ -303,7 +303,7 @@ class ShareModel: ObservableObject { } } } - let r: SEChatResponse? = recvSimpleXMsg(messageTimeout: 1_000_000) + let r: SEChatEvent? = recvSimpleXMsg(messageTimeout: 1_000_000) switch r { case let .sndFileProgressXFTP(_, ci, _, sentSize, totalSize): guard isMessage(for: ci) else { continue } @@ -353,8 +353,6 @@ class ShareModel: ObservableObject { return ErrorAlert(title: "File error", message: "\(fileErrorInfo(ci) ?? errorMessage)") case let .chatError(_, chatError): return ErrorAlert(chatError) - case let .chatCmdError(_, chatError): - return ErrorAlert(chatError) default: continue } } diff --git a/apps/ios/SimpleXChat/API.swift b/apps/ios/SimpleXChat/API.swift index b10b544a43..0baf52b26c 100644 --- a/apps/ios/SimpleXChat/API.swift +++ b/apps/ios/SimpleXChat/API.swift @@ -119,10 +119,10 @@ public func sendSimpleXCmd(_ cmd: ChatCmdProtocol, _ ctrl: // in microseconds public let MESSAGE_TIMEOUT: Int32 = 15_000_000 -public func recvSimpleXMsg(_ ctrl: chat_ctrl? = nil, messageTimeout: Int32 = MESSAGE_TIMEOUT) -> CR? { +public func recvSimpleXMsg(_ ctrl: chat_ctrl? = nil, messageTimeout: Int32 = MESSAGE_TIMEOUT) -> CEvt? { if let cjson = chat_recv_msg_wait(ctrl ?? getChatCtrl(), messageTimeout) { let s = fromCString(cjson) - return s == "" ? nil : CR.chatResponse(s) + return s == "" ? nil : CEvt.chatEvent(s) } return nil } diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 3cfe67e158..f635cfb7bb 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -33,6 +33,14 @@ public protocol ChatRespProtocol: Decodable, Error { var chatErrorType: ChatErrorType? { get } } +public protocol ChatEventProtocol: Decodable, Error { + var eventType: String { get } + var details: String { get } + static func chatEvent(_ s: String) -> Self + var chatError: ChatError? { get } + var chatErrorType: ChatErrorType? { get } +} + public func parseApiChats(_ jResp: NSDictionary) -> (user: UserRef, chats: [ChatData])? { if let jApiChats = jResp["apiChats"] as? NSDictionary, let user: UserRef = try? decodeObject(jApiChats["user"] as Any), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 24df07a052..8c1166dccd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -5792,8 +5792,6 @@ sealed class CR { @Serializable @SerialName("groupMemberRatchetSyncStarted") class GroupMemberRatchetSyncStarted(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val connectionStats: ConnectionStats): CR() @Serializable @SerialName("contactRatchetSync") class ContactRatchetSync(val user: UserRef, val contact: Contact, val ratchetSyncProgress: RatchetSyncProgress): CR() @Serializable @SerialName("groupMemberRatchetSync") class GroupMemberRatchetSync(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val ratchetSyncProgress: RatchetSyncProgress): CR() - @Serializable @SerialName("contactVerificationReset") class ContactVerificationReset(val user: UserRef, val contact: Contact): CR() - @Serializable @SerialName("groupMemberVerificationReset") class GroupMemberVerificationReset(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember): CR() @Serializable @SerialName("contactCode") class ContactCode(val user: UserRef, val contact: Contact, val connectionCode: String): CR() @Serializable @SerialName("groupMemberCode") class GroupMemberCode(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val connectionCode: String): CR() @Serializable @SerialName("connectionVerified") class ConnectionVerified(val user: UserRef, val verified: Boolean, val expectedCode: String): CR() @@ -5835,10 +5833,6 @@ sealed class CR { // TODO remove above @Serializable @SerialName("networkStatus") class NetworkStatusResp(val networkStatus: NetworkStatus, val connections: List): CR() @Serializable @SerialName("networkStatuses") class NetworkStatuses(val user_: UserRef?, val networkStatuses: List): CR() - @Serializable @SerialName("groupSubscribed") class GroupSubscribed(val user: UserRef, val group: GroupRef): CR() - @Serializable @SerialName("memberSubErrors") class MemberSubErrors(val user: UserRef, val memberSubErrors: List): CR() - @Serializable @SerialName("groupEmpty") class GroupEmpty(val user: UserRef, val group: GroupInfo): CR() - @Serializable @SerialName("userContactLinkSubscribed") class UserContactLinkSubscribed: CR() @Serializable @SerialName("newChatItems") class NewChatItems(val user: UserRef, val chatItems: List): CR() @Serializable @SerialName("chatItemsStatusesUpdated") class ChatItemsStatusesUpdated(val user: UserRef, val chatItems: List): CR() @Serializable @SerialName("chatItemUpdated") class ChatItemUpdated(val user: UserRef, val chatItem: AChatItem): CR() @@ -5869,11 +5863,9 @@ sealed class CR { @Serializable @SerialName("leftMember") class LeftMember(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember): CR() @Serializable @SerialName("groupDeleted") class GroupDeleted(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember): CR() @Serializable @SerialName("contactsMerged") class ContactsMerged(val user: UserRef, val intoContact: Contact, val mergedContact: Contact): CR() - @Serializable @SerialName("groupInvitation") class GroupInvitation(val user: UserRef, val groupInfo: GroupInfo): CR() // unused @Serializable @SerialName("userJoinedGroup") class UserJoinedGroup(val user: UserRef, val groupInfo: GroupInfo): CR() @Serializable @SerialName("joinedGroupMember") class JoinedGroupMember(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember): CR() @Serializable @SerialName("connectedToGroupMember") class ConnectedToGroupMember(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val memberContact: Contact? = null): CR() - @Serializable @SerialName("groupRemoved") class GroupRemoved(val user: UserRef, val groupInfo: GroupInfo): CR() // unused @Serializable @SerialName("groupUpdated") class GroupUpdated(val user: UserRef, val toGroup: GroupInfo): CR() @Serializable @SerialName("groupLinkCreated") class GroupLinkCreated(val user: UserRef, val groupInfo: GroupInfo, val connLinkContact: CreatedConnLink, val memberRole: GroupMemberRole): CR() @Serializable @SerialName("groupLink") class GroupLink(val user: UserRef, val groupInfo: GroupInfo, val connLinkContact: CreatedConnLink, val memberRole: GroupMemberRole): CR() @@ -5980,8 +5972,6 @@ sealed class CR { is GroupMemberRatchetSyncStarted -> "groupMemberRatchetSyncStarted" is ContactRatchetSync -> "contactRatchetSync" is GroupMemberRatchetSync -> "groupMemberRatchetSync" - is ContactVerificationReset -> "contactVerificationReset" - is GroupMemberVerificationReset -> "groupMemberVerificationReset" is ContactCode -> "contactCode" is GroupMemberCode -> "groupMemberCode" is ConnectionVerified -> "connectionVerified" @@ -6021,10 +6011,6 @@ sealed class CR { is ContactSubSummary -> "contactSubSummary" is NetworkStatusResp -> "networkStatus" is NetworkStatuses -> "networkStatuses" - is GroupSubscribed -> "groupSubscribed" - is MemberSubErrors -> "memberSubErrors" - is GroupEmpty -> "groupEmpty" - is UserContactLinkSubscribed -> "userContactLinkSubscribed" is NewChatItems -> "newChatItems" is ChatItemsStatusesUpdated -> "chatItemsStatusesUpdated" is ChatItemUpdated -> "chatItemUpdated" @@ -6054,11 +6040,9 @@ sealed class CR { is LeftMember -> "leftMember" is GroupDeleted -> "groupDeleted" is ContactsMerged -> "contactsMerged" - is GroupInvitation -> "groupInvitation" is UserJoinedGroup -> "userJoinedGroup" is JoinedGroupMember -> "joinedGroupMember" is ConnectedToGroupMember -> "connectedToGroupMember" - is GroupRemoved -> "groupRemoved" is GroupUpdated -> "groupUpdated" is GroupLinkCreated -> "groupLinkCreated" is GroupLink -> "groupLink" @@ -6158,8 +6142,6 @@ sealed class CR { is GroupMemberRatchetSyncStarted -> withUser(user, "group: ${json.encodeToString(groupInfo)}\nmember: ${json.encodeToString(member)}\nconnectionStats: ${json.encodeToString(connectionStats)}") is ContactRatchetSync -> withUser(user, "contact: ${json.encodeToString(contact)}\nratchetSyncProgress: ${json.encodeToString(ratchetSyncProgress)}") is GroupMemberRatchetSync -> withUser(user, "group: ${json.encodeToString(groupInfo)}\nmember: ${json.encodeToString(member)}\nratchetSyncProgress: ${json.encodeToString(ratchetSyncProgress)}") - is ContactVerificationReset -> withUser(user, "contact: ${json.encodeToString(contact)}") - is GroupMemberVerificationReset -> withUser(user, "group: ${json.encodeToString(groupInfo)}\nmember: ${json.encodeToString(member)}") is ContactCode -> withUser(user, "contact: ${json.encodeToString(contact)}\nconnectionCode: $connectionCode") is GroupMemberCode -> withUser(user, "groupInfo: ${json.encodeToString(groupInfo)}\nmember: ${json.encodeToString(member)}\nconnectionCode: $connectionCode") is ConnectionVerified -> withUser(user, "verified: $verified\nconnectionCode: $expectedCode") @@ -6199,10 +6181,6 @@ sealed class CR { is ContactSubSummary -> withUser(user, json.encodeToString(contactSubscriptions)) is NetworkStatusResp -> "networkStatus $networkStatus\nconnections: $connections" is NetworkStatuses -> withUser(user_, json.encodeToString(networkStatuses)) - is GroupSubscribed -> withUser(user, json.encodeToString(group)) - is MemberSubErrors -> withUser(user, json.encodeToString(memberSubErrors)) - is GroupEmpty -> withUser(user, json.encodeToString(group)) - is UserContactLinkSubscribed -> noDetails() is NewChatItems -> withUser(user, chatItems.joinToString("\n") { json.encodeToString(it) }) is ChatItemsStatusesUpdated -> withUser(user, chatItems.joinToString("\n") { json.encodeToString(it) }) is ChatItemUpdated -> withUser(user, json.encodeToString(chatItem)) @@ -6232,11 +6210,9 @@ sealed class CR { is LeftMember -> withUser(user, "groupInfo: $groupInfo\nmember: $member") is GroupDeleted -> withUser(user, "groupInfo: $groupInfo\nmember: $member") is ContactsMerged -> withUser(user, "intoContact: $intoContact\nmergedContact: $mergedContact") - is GroupInvitation -> withUser(user, json.encodeToString(groupInfo)) is UserJoinedGroup -> withUser(user, json.encodeToString(groupInfo)) is JoinedGroupMember -> withUser(user, "groupInfo: $groupInfo\nmember: $member") is ConnectedToGroupMember -> withUser(user, "groupInfo: $groupInfo\nmember: $member\nmemberContact: $memberContact") - is GroupRemoved -> withUser(user, json.encodeToString(groupInfo)) is GroupUpdated -> withUser(user, json.encodeToString(toGroup)) is GroupLinkCreated -> withUser(user, "groupInfo: $groupInfo\nconnLinkContact: $connLinkContact\nmemberRole: $memberRole") is GroupLink -> withUser(user, "groupInfo: $groupInfo\nconnLinkContact: $connLinkContact\nmemberRole: $memberRole") diff --git a/apps/simplex-bot-advanced/Main.hs b/apps/simplex-bot-advanced/Main.hs index 6c3d8240e4..b6ad9eea96 100644 --- a/apps/simplex-bot-advanced/Main.hs +++ b/apps/simplex-bot-advanced/Main.hs @@ -43,12 +43,12 @@ mySquaringBot :: User -> ChatController -> IO () mySquaringBot _user cc = do initializeBotAddress cc race_ (forever $ void getLine) . forever $ do - (_, _, resp) <- atomically . readTBQueue $ outputQ cc - case resp of - CRContactConnected _ contact _ -> do + (_, evt) <- atomically . readTBQueue $ outputQ cc + case evt of + CEvtContactConnected _ contact _ -> do contactConnected contact sendMessage cc contact welcomeMessage - CRNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content = mc@CIRcvMsgContent {}}) : _} -> do + CEvtNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content = mc@CIRcvMsgContent {}}) : _} -> do let msg = ciContentToText mc number_ = readMaybe (T.unpack msg) :: Maybe Integer sendMessage cc contact $ case number_ of diff --git a/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs b/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs index 15f790e8b1..913f6a732a 100644 --- a/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs +++ b/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs @@ -11,7 +11,6 @@ import Control.Concurrent (forkIO) import Control.Concurrent.Async import Control.Concurrent.STM import Control.Monad -import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.Text as T import Broadcast.Options import Simplex.Chat.Bot @@ -38,39 +37,31 @@ broadcastBot :: BroadcastBotOpts -> User -> ChatController -> IO () broadcastBot BroadcastBotOpts {publishers, welcomeMessage, prohibitedMessage} _user cc = do initializeBotAddress cc race_ (forever $ void getLine) . forever $ do - (_, _, resp) <- atomically . readTBQueue $ outputQ cc - case resp of - CRContactConnected _ ct _ -> do + (_, evt) <- atomically . readTBQueue $ outputQ cc + case evt of + CEvtContactConnected _ ct _ -> do contactConnected ct sendMessage cc ct welcomeMessage - CRNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc}) : _} - | publisher `elem` publishers -> - if allowContent mc - then do - sendChatCmd cc ListContacts >>= \case - CRContactsList _ cts -> void . forkIO $ do - sendChatCmd cc (SendMessageBroadcast mc) >>= \case - CRBroadcastSent {successes, failures} -> - sendReply $ "Forwarded to " <> tshow successes <> " contact(s), " <> tshow failures <> " errors" - r -> putStrLn $ "Error broadcasting message: " <> show r - r -> putStrLn $ "Error getting contacts list: " <> show r - else sendReply "!1 Message is not supported!" - | otherwise -> do - sendReply prohibitedMessage - deleteMessage cc ct $ chatItemId' ci + CEvtNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc}) : _} + | sender `notElem` publishers -> do + sendReply prohibitedMessage + deleteMessage cc ct $ chatItemId' ci + | allowContent mc -> + void $ forkIO $ + sendChatCmd cc (SendMessageBroadcast mc) >>= \case + CRBroadcastSent {successes, failures} -> + sendReply $ "Forwarded to " <> tshow successes <> " contact(s), " <> tshow failures <> " errors" + r -> putStrLn $ "Error broadcasting message: " <> show r + | otherwise -> + sendReply "!1 Message is not supported!" where sendReply = sendComposedMessage cc ct (Just $ chatItemId' ci) . MCText - publisher = KnownContact {contactId = contactId' ct, localDisplayName = localDisplayName' ct} + sender = KnownContact {contactId = contactId' ct, localDisplayName = localDisplayName' ct} allowContent = \case MCText _ -> True MCLink {} -> True MCImage {} -> True _ -> False - broadcastTo Contact {activeConn = Nothing} = False - broadcastTo ct'@Contact {activeConn = Just conn@Connection {connStatus}} = - (connStatus == ConnSndReady || connStatus == ConnReady) - && not (connDisabled conn) - && contactId' ct' /= contactId' ct _ -> pure () where contactConnected ct = putStrLn $ T.unpack (localDisplayName' ct) <> " connected" diff --git a/apps/simplex-chat/Server.hs b/apps/simplex-chat/Server.hs index fddad1cf2c..d087df0bb5 100644 --- a/apps/simplex-chat/Server.hs +++ b/apps/simplex-chat/Server.hs @@ -2,9 +2,11 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TemplateHaskell #-} module Server where @@ -13,6 +15,7 @@ import Control.Monad.Except import Control.Monad.Reader import Data.Aeson (FromJSON, ToJSON) import qualified Data.Aeson as J +import qualified Data.Aeson.TH as JQ import Data.Text (Text) import Data.Text.Encoding (encodeUtf8) import GHC.Generics (Generic) @@ -23,11 +26,25 @@ import Simplex.Chat.Controller import Simplex.Chat.Core import Simplex.Chat.Library.Commands import Simplex.Chat.Options +import Simplex.Messaging.Parsers (defaultJSON) import Simplex.Messaging.Transport.Server (runLocalTCPServer) import Simplex.Messaging.Util (raceAny_) import UnliftIO.Exception import UnliftIO.STM +data ChatSrvRequest = ChatSrvRequest {corrId :: Text, cmd :: Text} + deriving (Generic, FromJSON) + +data ChatSrvResponse r = ChatSrvResponse {corrId :: Maybe Text, resp :: r} + +data AChatSrvResponse = forall r. ToJSON (ChatSrvResponse r) => ACR (ChatSrvResponse r) + +$(pure []) + +instance ToJSON r => ToJSON (ChatSrvResponse r) where + toEncoding = $(JQ.mkToEncoding defaultJSON ''ChatSrvResponse) + toJSON = $(JQ.mkToJSON defaultJSON ''ChatSrvResponse) + simplexChatServer :: ServiceName -> ChatConfig -> ChatOpts -> IO () simplexChatServer chatPort cfg opts = simplexChatCore cfg opts . const $ runChatServer defaultChatServerConfig {chatPort} @@ -44,19 +61,9 @@ defaultChatServerConfig = clientQSize = 1 } -data ChatSrvRequest = ChatSrvRequest {corrId :: Text, cmd :: Text} - deriving (Generic, FromJSON) - -data ChatSrvResponse = ChatSrvResponse {corrId :: Maybe Text, resp :: ChatResponse} - deriving (Generic) - -instance ToJSON ChatSrvResponse where - toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True} - toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True} - data ChatClient = ChatClient { rcvQ :: TBQueue (Text, ChatCommand), - sndQ :: TBQueue ChatSrvResponse + sndQ :: TBQueue AChatSrvResponse } newChatServerClient :: Natural -> STM ChatClient @@ -78,14 +85,14 @@ runChatServer ChatServerConfig {chatPort, clientQSize} cc = do getConnection sock = WS.makePendingConnection sock WS.defaultConnectionOptions >>= WS.acceptRequest send ws ChatClient {sndQ} = forever $ - atomically (readTBQueue sndQ) >>= WS.sendTextData ws . J.encode + atomically (readTBQueue sndQ) >>= \(ACR r) -> WS.sendTextData ws (J.encode r) client ChatClient {rcvQ, sndQ} = forever $ do atomically (readTBQueue rcvQ) >>= processCommand - >>= atomically . writeTBQueue sndQ + >>= atomically . writeTBQueue sndQ . ACR output ChatClient {sndQ} = forever $ do - (_, _, resp) <- atomically . readTBQueue $ outputQ cc - atomically $ writeTBQueue sndQ ChatSrvResponse {corrId = Nothing, resp} + (_, resp) <- atomically . readTBQueue $ outputQ cc + atomically $ writeTBQueue sndQ $ ACR ChatSrvResponse {corrId = Nothing, resp} receive ws ChatClient {rcvQ, sndQ} = forever $ do s <- WS.receiveData ws case J.decodeStrict' s of @@ -96,7 +103,7 @@ runChatServer ChatServerConfig {chatPort, clientQSize} cc = do Left e -> sendError (Just corrId) e Nothing -> sendError Nothing "invalid request" where - sendError corrId e = atomically $ writeTBQueue sndQ ChatSrvResponse {corrId, resp = chatCmdError Nothing e} + sendError corrId e = atomically $ writeTBQueue sndQ $ ACR ChatSrvResponse {corrId, resp = chatCmdError Nothing e} processCommand (corrId, cmd) = runReaderT (runExceptT $ processChatCommand cmd) cc >>= \case Right resp -> response resp diff --git a/apps/simplex-directory-service/src/Directory/Events.hs b/apps/simplex-directory-service/src/Directory/Events.hs index 802221f976..412f87889c 100644 --- a/apps/simplex-directory-service/src/Directory/Events.hs +++ b/apps/simplex-directory-service/src/Directory/Events.hs @@ -63,41 +63,40 @@ data DirectoryEvent | DELogChatResponse Text deriving (Show) -crDirectoryEvent :: ChatResponse -> Maybe DirectoryEvent +crDirectoryEvent :: ChatEvent -> Maybe DirectoryEvent crDirectoryEvent = \case - CRContactConnected {contact} -> Just $ DEContactConnected contact - CRReceivedGroupInvitation {contact, groupInfo, fromMemberRole, memberRole} -> Just $ DEGroupInvitation {contact, groupInfo, fromMemberRole, memberRole} - CRUserJoinedGroup {groupInfo, hostMember} -> (\contactId -> DEServiceJoinedGroup {contactId, groupInfo, hostMember}) <$> memberContactId hostMember - CRGroupUpdated {fromGroup, toGroup, member_} -> (\member -> DEGroupUpdated {member, fromGroup, toGroup}) <$> member_ - CRJoinedGroupMember {groupInfo, member = m} + CEvtContactConnected {contact} -> Just $ DEContactConnected contact + CEvtReceivedGroupInvitation {contact, groupInfo, fromMemberRole, memberRole} -> Just $ DEGroupInvitation {contact, groupInfo, fromMemberRole, memberRole} + CEvtUserJoinedGroup {groupInfo, hostMember} -> (\contactId -> DEServiceJoinedGroup {contactId, groupInfo, hostMember}) <$> memberContactId hostMember + CEvtGroupUpdated {fromGroup, toGroup, member_} -> (\member -> DEGroupUpdated {member, fromGroup, toGroup}) <$> member_ + CEvtJoinedGroupMember {groupInfo, member = m} | pending m -> Just $ DEPendingMember groupInfo m | otherwise -> Nothing - CRNewChatItems {chatItems = AChatItem _ _ (GroupChat g) ci : _} -> case ci of + CEvtNewChatItems {chatItems = AChatItem _ _ (GroupChat g) ci : _} -> case ci of ChatItem {chatDir = CIGroupRcv m, content = CIRcvMsgContent (MCText t)} | pending m -> Just $ DEPendingMemberMsg g m (chatItemId' ci) t _ -> Nothing - CRMemberRole {groupInfo, member, toRole} + CEvtMemberRole {groupInfo, member, toRole} | groupMemberId' member == groupMemberId' (membership groupInfo) -> Just $ DEServiceRoleChanged groupInfo toRole | otherwise -> (\ctId -> DEContactRoleChanged groupInfo ctId toRole) <$> memberContactId member - CRDeletedMember {groupInfo, deletedMember} -> (`DEContactRemovedFromGroup` groupInfo) <$> memberContactId deletedMember - CRLeftMember {groupInfo, member} -> (`DEContactLeftGroup` groupInfo) <$> memberContactId member - CRDeletedMemberUser {groupInfo} -> Just $ DEServiceRemovedFromGroup groupInfo - CRGroupDeleted {groupInfo} -> Just $ DEGroupDeleted groupInfo - CRChatItemUpdated {chatItem = AChatItem _ SMDRcv (DirectChat ct) _} -> Just $ DEItemEditIgnored ct - CRChatItemsDeleted {chatItemDeletions = ((ChatItemDeletion (AChatItem _ SMDRcv (DirectChat ct) _) _) : _), byUser = False} -> Just $ DEItemDeleteIgnored ct - CRNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc, meta = CIMeta {itemLive}}) : _} -> + CEvtDeletedMember {groupInfo, deletedMember} -> (`DEContactRemovedFromGroup` groupInfo) <$> memberContactId deletedMember + CEvtLeftMember {groupInfo, member} -> (`DEContactLeftGroup` groupInfo) <$> memberContactId member + CEvtDeletedMemberUser {groupInfo} -> Just $ DEServiceRemovedFromGroup groupInfo + CEvtGroupDeleted {groupInfo} -> Just $ DEGroupDeleted groupInfo + CEvtChatItemUpdated {chatItem = AChatItem _ SMDRcv (DirectChat ct) _} -> Just $ DEItemEditIgnored ct + CEvtChatItemsDeleted {chatItemDeletions = ((ChatItemDeletion (AChatItem _ SMDRcv (DirectChat ct) _) _) : _), byUser = False} -> Just $ DEItemDeleteIgnored ct + CEvtNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc, meta = CIMeta {itemLive}}) : _} -> Just $ case (mc, itemLive) of (MCText t, Nothing) -> DEContactCommand ct ciId $ fromRight err $ A.parseOnly (directoryCmdP <* A.endOfInput) $ T.dropWhileEnd isSpace t _ -> DEUnsupportedMessage ct ciId where ciId = chatItemId' ci err = ADC SDRUser DCUnknownCommand - CRMessageError {severity, errorMessage} -> Just $ DELogChatResponse $ "message error: " <> severity <> ", " <> errorMessage - CRChatCmdError {chatError} -> Just $ DELogChatResponse $ "chat cmd error: " <> tshow chatError - CRChatError {chatError} -> case chatError of + CEvtMessageError {severity, errorMessage} -> Just $ DELogChatResponse $ "message error: " <> severity <> ", " <> errorMessage + CEvtChatError {chatError} -> case chatError of ChatErrorAgent {agentError = BROKER _ NETWORK} -> Nothing ChatErrorAgent {agentError = BROKER _ TIMEOUT} -> Nothing _ -> Just $ DELogChatResponse $ "chat error: " <> tshow chatError - CRChatErrors {chatErrors} -> Just $ DELogChatResponse $ "chat errors: " <> T.intercalate ", " (map tshow chatErrors) + CEvtChatErrors {chatErrors} -> Just $ DELogChatResponse $ "chat errors: " <> T.intercalate ", " (map tshow chatErrors) _ -> Nothing where pending m = memberStatus m == GSMemPendingApproval diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs index fc9ac24e71..89fb9c30d8 100644 --- a/apps/simplex-directory-service/src/Directory/Service.hs +++ b/apps/simplex-directory-service/src/Directory/Service.hs @@ -153,7 +153,7 @@ directoryService :: DirectoryStore -> DirectoryOpts -> ServiceState -> User -> C directoryService st opts@DirectoryOpts {testing} env user cc = do initializeBotAddress' (not testing) cc race_ (forever $ void getLine) . forever $ do - (_, _, resp) <- atomically . readTBQueue $ outputQ cc + (_, resp) <- atomically . readTBQueue $ outputQ cc directoryServiceEvent st opts env user cc resp acceptMemberHook :: DirectoryOpts -> ServiceState -> GroupInfo -> GroupLinkInfo -> Profile -> IO (Either GroupRejectionReason (GroupAcceptance, GroupMemberRole)) @@ -197,7 +197,7 @@ readBlockedWordsConfig DirectoryOpts {blockedFragmentsFile, blockedWordsFile, na unless testing $ putStrLn $ "Blocked fragments: " <> show (length blockedFragments) <> ", blocked words: " <> show (length blockedWords) <> ", spelling rules: " <> show (M.size spelling) pure BlockedWordsConfig {blockedFragments, blockedWords, extensionRules, spelling} -directoryServiceEvent :: DirectoryStore -> DirectoryOpts -> ServiceState -> User -> ChatController -> ChatResponse -> IO () +directoryServiceEvent :: DirectoryStore -> DirectoryOpts -> ServiceState -> User -> ChatController -> ChatEvent -> IO () directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName, ownersGroup, searchResults} env@ServiceState {searchRequests} user@User {userId} cc event = forM_ (crDirectoryEvent event) $ \case DEContactConnected ct -> deContactConnected ct diff --git a/cabal.project b/cabal.project index b7c8832d9d..a9a4b45f1a 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 3d10c9bf9e4d8196d39162ff8712f6b729b8c247 + tag: a632eea75b677cf2b146ad06ee875307d0321f23 source-repository-package type: git diff --git a/packages/simplex-chat-client/typescript/src/response.ts b/packages/simplex-chat-client/typescript/src/response.ts index 2e92e335df..5f91baa7db 100644 --- a/packages/simplex-chat-client/typescript/src/response.ts +++ b/packages/simplex-chat-client/typescript/src/response.ts @@ -84,7 +84,6 @@ export type ChatResponse = | CRGroupRemoved | CRGroupDeleted | CRGroupUpdated - | CRUserContactLinkSubscribed | CRUserContactLinkSubError | CRContactConnectionDeleted | CRMessageError @@ -182,7 +181,6 @@ type ChatResponseTag = | "groupRemoved" | "groupDeleted" | "groupUpdated" - | "userContactLinkSubscribed" | "userContactLinkSubError" | "newContactConnection" | "contactConnectionDeleted" @@ -721,10 +719,6 @@ export interface CRGroupUpdated extends CR { member_?: GroupMember } -export interface CRUserContactLinkSubscribed extends CR { - type: "userContactLinkSubscribed" -} - export interface CRUserContactLinkSubError extends CR { type: "userContactLinkSubError" chatError: ChatError diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 40aa4e7da0..842348157a 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."3d10c9bf9e4d8196d39162ff8712f6b729b8c247" = "1nnr6klv240da97qmrzlh8jywpimcnlrxnxnjrm2rd0w0w7gvra1"; + "https://github.com/simplex-chat/simplexmq.git"."a632eea75b677cf2b146ad06ee875307d0321f23" = "03vk7214941f5jwmf7sp26lxzh4c1xl89wqmlky379d6gwypbzy6"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat/Bot.hs b/src/Simplex/Chat/Bot.hs index 727d7f9ac5..5acf60556e 100644 --- a/src/Simplex/Chat/Bot.hs +++ b/src/Simplex/Chat/Bot.hs @@ -33,12 +33,12 @@ chatBotRepl :: String -> (Contact -> String -> IO String) -> User -> ChatControl chatBotRepl welcome answer _user cc = do initializeBotAddress cc race_ (forever $ void getLine) . forever $ do - (_, _, resp) <- atomically . readTBQueue $ outputQ cc - case resp of - CRContactConnected _ contact _ -> do + (_, event) <- atomically . readTBQueue $ outputQ cc + case event of + CEvtContactConnected _ contact _ -> do contactConnected contact void $ sendMessage cc contact $ T.pack welcome - CRNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content = mc@CIRcvMsgContent {}}) : _} -> do + CEvtNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content = mc@CIRcvMsgContent {}}) : _} -> do let msg = T.unpack $ ciContentToText mc void $ sendMessage cc contact . T.pack =<< answer contact msg _ -> pure () diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 4d835b41bb..a3b9f34346 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -55,7 +55,6 @@ import Numeric.Natural import qualified Paths_simplex_chat as SC import Simplex.Chat.AppSettings import Simplex.Chat.Call -import Simplex.Chat.Markdown (MarkdownList) import Simplex.Chat.Messages import Simplex.Chat.Messages.CIContent import Simplex.Chat.Operators @@ -86,7 +85,7 @@ import Simplex.Messaging.Crypto.Ratchet (PQEncryption) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Notifications.Protocol (DeviceToken (..), NtfTknStatus) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, parseAll, parseString, sumTypeJSON) -import Simplex.Messaging.Protocol (AProtoServerWithAuth, AProtocolType (..), CorrId, MsgId, NMsgMeta (..), NtfServer, ProtocolType (..), QueueId, SMPMsgMeta (..), SubscriptionMode (..), XFTPServer) +import Simplex.Messaging.Protocol (AProtoServerWithAuth, AProtocolType (..), MsgId, NMsgMeta (..), NtfServer, ProtocolType (..), QueueId, SMPMsgMeta (..), SubscriptionMode (..), XFTPServer) import Simplex.Messaging.TMap (TMap) import Simplex.Messaging.Transport (TLS, simplexMQVersion) import Simplex.Messaging.Transport.Client (SocksProxyWithAuth, TransportHost) @@ -176,7 +175,7 @@ data ChatHooks = ChatHooks preCmdHook :: Maybe (ChatController -> ChatCommand -> IO (Either ChatResponse ChatCommand)), -- eventHook can be used to additionally process or modify events, -- it is called before the event is sent to the user (or to the UI). - eventHook :: Maybe (ChatController -> ChatResponse -> IO ChatResponse), + eventHook :: Maybe (ChatController -> ChatEvent -> IO ChatEvent), -- acceptMember hook can be used to accept or reject member connecting via group link without API calls acceptMember :: Maybe (GroupInfo -> GroupLinkInfo -> Profile -> IO (Either GroupRejectionReason (GroupAcceptance, GroupMemberRole))) } @@ -224,7 +223,7 @@ data ChatController = ChatController random :: TVar ChaChaDRG, eventSeq :: TVar Int, inputQ :: TBQueue String, - outputQ :: TBQueue (Maybe CorrId, Maybe RemoteHostId, ChatResponse), + outputQ :: TBQueue (Maybe RemoteHostId, ChatEvent), connNetworkStatuses :: TMap AgentConnId NetworkStatus, subscriptionMode :: TVar SubscriptionMode, chatLock :: Lock, @@ -548,7 +547,7 @@ data ChatCommand | QuitChat | ShowVersion | DebugLocks - | DebugEvent ChatResponse + | DebugEvent ChatEvent | GetAgentSubsTotal UserId | GetAgentServersSummary UserId | ResetAgentServersStats @@ -608,7 +607,6 @@ data ChatResponse | CRChatStarted | CRChatRunning | CRChatStopped - | CRChatSuspended | CRApiChats {user :: User, chats :: [AChat]} | CRChats {chats :: [AChat]} | CRApiChat {user :: User, chat :: AChat, navInfo :: Maybe NavigationInfo} @@ -616,7 +614,6 @@ data ChatResponse | CRChatItems {user :: User, chatName_ :: Maybe ChatName, chatItems :: [AChatItem]} | CRChatItemInfo {user :: User, chatItem :: AChatItem, chatItemInfo :: ChatItemInfo} | CRChatItemId User (Maybe ChatItemId) - | CRApiParsedMarkdown {formattedText :: Maybe MarkdownList} | CRServerTestResult {user :: User, testServer :: AProtoServerWithAuth, testFailure :: Maybe ProtocolTestFailure} | CRServerOperatorConditions {conditions :: ServerOperatorConditions} | CRUserServers {user :: User, userServers :: [UserOperatorServers]} @@ -632,30 +629,20 @@ data ChatResponse | CRGroupMemberSwitchStarted {user :: User, groupInfo :: GroupInfo, member :: GroupMember, connectionStats :: ConnectionStats} | CRContactSwitchAborted {user :: User, contact :: Contact, connectionStats :: ConnectionStats} | CRGroupMemberSwitchAborted {user :: User, groupInfo :: GroupInfo, member :: GroupMember, connectionStats :: ConnectionStats} - | CRContactSwitch {user :: User, contact :: Contact, switchProgress :: SwitchProgress} - | CRGroupMemberSwitch {user :: User, groupInfo :: GroupInfo, member :: GroupMember, switchProgress :: SwitchProgress} | CRContactRatchetSyncStarted {user :: User, contact :: Contact, connectionStats :: ConnectionStats} | CRGroupMemberRatchetSyncStarted {user :: User, groupInfo :: GroupInfo, member :: GroupMember, connectionStats :: ConnectionStats} - | CRContactRatchetSync {user :: User, contact :: Contact, ratchetSyncProgress :: RatchetSyncProgress} - | CRGroupMemberRatchetSync {user :: User, groupInfo :: GroupInfo, member :: GroupMember, ratchetSyncProgress :: RatchetSyncProgress} - | CRContactVerificationReset {user :: User, contact :: Contact} - | CRGroupMemberVerificationReset {user :: User, groupInfo :: GroupInfo, member :: GroupMember} | CRContactCode {user :: User, contact :: Contact, connectionCode :: Text} | CRGroupMemberCode {user :: User, groupInfo :: GroupInfo, member :: GroupMember, connectionCode :: Text} | CRConnectionVerified {user :: User, verified :: Bool, expectedCode :: Text} | CRTagsUpdated {user :: User, userTags :: [ChatTag], chatTags :: [ChatTagId]} | CRNewChatItems {user :: User, chatItems :: [AChatItem]} - | CRChatItemsStatusesUpdated {user :: User, chatItems :: [AChatItem]} | CRChatItemUpdated {user :: User, chatItem :: AChatItem} | CRChatItemNotChanged {user :: User, chatItem :: AChatItem} | CRChatItemReaction {user :: User, added :: Bool, reaction :: ACIReaction} | CRReactionMembers {user :: User, memberReactions :: [MemberReaction]} | CRChatItemsDeleted {user :: User, chatItemDeletions :: [ChatItemDeletion], byUser :: Bool, timed :: Bool} | CRGroupChatItemsDeleted {user :: User, groupInfo :: GroupInfo, chatItemIDs :: [ChatItemId], byUser :: Bool, member_ :: Maybe GroupMember} - | CRChatItemDeletedNotFound {user :: User, contact :: Contact, sharedMsgId :: SharedMsgId} | CRBroadcastSent {user :: User, msgContent :: MsgContent, successes :: Int, failures :: Int, timestamp :: UTCTime} - | CRMsgIntegrityError {user :: User, msgError :: MsgErrorType} - | CRCmdAccepted {corr :: CorrId} | CRCmdOk {user_ :: Maybe User} | CRChatHelp {helpSection :: HelpSection} | CRWelcome {user :: User} @@ -666,8 +653,6 @@ data ChatResponse | CRUserContactLinkUpdated {user :: User, contactLink :: UserContactLink} | CRContactRequestRejected {user :: User, contactRequest :: UserContactRequest} | CRUserAcceptedGroupSent {user :: User, groupInfo :: GroupInfo, hostContact :: Maybe Contact} - | CRGroupLinkConnecting {user :: User, groupInfo :: GroupInfo, hostMember :: GroupMember} - | CRBusinessLinkConnecting {user :: User, groupInfo :: GroupInfo, hostMember :: GroupMember, fromContact :: Contact} | CRUserDeletedMembers {user :: User, groupInfo :: GroupInfo, members :: [GroupMember], withMessages :: Bool} | CRGroupsList {user :: User, groups :: [(GroupInfo, GroupSummary)]} | CRSentGroupInvitation {user :: User, groupInfo :: GroupInfo, contact :: Contact, member :: GroupMember} @@ -684,134 +669,54 @@ data ChatResponse | CRSentConfirmation {user :: User, connection :: PendingContactConnection} | CRSentInvitation {user :: User, connection :: PendingContactConnection, customUserProfile :: Maybe Profile} | CRSentInvitationToContact {user :: User, contact :: Contact, customUserProfile :: Maybe Profile} - | CRContactUpdated {user :: User, fromContact :: Contact, toContact :: Contact} - | CRGroupMemberUpdated {user :: User, groupInfo :: GroupInfo, fromMember :: GroupMember, toMember :: GroupMember} - | CRContactsMerged {user :: User, intoContact :: Contact, mergedContact :: Contact, updatedContact :: Contact} | CRContactDeleted {user :: User, contact :: Contact} - | CRContactDeletedByContact {user :: User, contact :: Contact} | CRChatCleared {user :: User, chatInfo :: AChatInfo} | CRUserContactLinkCreated {user :: User, connLinkContact :: CreatedLinkContact} | CRUserContactLinkDeleted {user :: User} - | CRReceivedContactRequest {user :: User, contactRequest :: UserContactRequest} | CRAcceptingContactRequest {user :: User, contact :: Contact} - | CRAcceptingBusinessRequest {user :: User, groupInfo :: GroupInfo} | CRContactAlreadyExists {user :: User, contact :: Contact} - | CRContactRequestAlreadyAccepted {user :: User, contact :: Contact} - | CRBusinessRequestAlreadyAccepted {user :: User, groupInfo :: GroupInfo} | CRLeftMemberUser {user :: User, groupInfo :: GroupInfo} | CRGroupDeletedUser {user :: User, groupInfo :: GroupInfo} | CRForwardPlan {user :: User, itemsCount :: Int, chatItemIds :: [ChatItemId], forwardConfirmation :: Maybe ForwardConfirmation} - | CRRcvFileDescrReady {user :: User, chatItem :: AChatItem, rcvFileTransfer :: RcvFileTransfer, rcvFileDescr :: RcvFileDescr} | CRRcvFileAccepted {user :: User, chatItem :: AChatItem} + -- TODO add chatItem :: AChatItem | CRRcvFileAcceptedSndCancelled {user :: User, rcvFileTransfer :: RcvFileTransfer} | CRStandaloneFileInfo {fileMeta :: Maybe J.Value} | CRRcvStandaloneFileCreated {user :: User, rcvFileTransfer :: RcvFileTransfer} -- returned by _download - | CRRcvFileStart {user :: User, chatItem :: AChatItem} -- sent by chats - | CRRcvFileProgressXFTP {user :: User, chatItem_ :: Maybe AChatItem, receivedSize :: Int64, totalSize :: Int64, rcvFileTransfer :: RcvFileTransfer} - | CRRcvFileComplete {user :: User, chatItem :: AChatItem} - | CRRcvStandaloneFileComplete {user :: User, targetPath :: FilePath, rcvFileTransfer :: RcvFileTransfer} | CRRcvFileCancelled {user :: User, chatItem_ :: Maybe AChatItem, rcvFileTransfer :: RcvFileTransfer} - | CRRcvFileSndCancelled {user :: User, chatItem :: AChatItem, rcvFileTransfer :: RcvFileTransfer} - | CRRcvFileError {user :: User, chatItem_ :: Maybe AChatItem, agentError :: AgentErrorType, rcvFileTransfer :: RcvFileTransfer} - | CRRcvFileWarning {user :: User, chatItem_ :: Maybe AChatItem, agentError :: AgentErrorType, rcvFileTransfer :: RcvFileTransfer} - | CRSndFileStart {user :: User, chatItem :: AChatItem, sndFileTransfer :: SndFileTransfer} - | CRSndFileComplete {user :: User, chatItem :: AChatItem, sndFileTransfer :: SndFileTransfer} - | CRSndFileRcvCancelled {user :: User, chatItem_ :: Maybe AChatItem, sndFileTransfer :: SndFileTransfer} | CRSndFileCancelled {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, sndFileTransfers :: [SndFileTransfer]} | CRSndStandaloneFileCreated {user :: User, fileTransferMeta :: FileTransferMeta} -- returned by _upload - | CRSndFileStartXFTP {user :: User, chatItem :: AChatItem, fileTransferMeta :: FileTransferMeta} -- not used - | CRSndFileProgressXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, sentSize :: Int64, totalSize :: Int64} - | CRSndFileRedirectStartXFTP {user :: User, fileTransferMeta :: FileTransferMeta, redirectMeta :: FileTransferMeta} - | CRSndFileCompleteXFTP {user :: User, chatItem :: AChatItem, fileTransferMeta :: FileTransferMeta} - | CRSndStandaloneFileComplete {user :: User, fileTransferMeta :: FileTransferMeta, rcvURIs :: [Text]} - | CRSndFileCancelledXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta} - | CRSndFileError {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, errorMessage :: Text} - | CRSndFileWarning {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, errorMessage :: Text} | CRUserProfileUpdated {user :: User, fromProfile :: Profile, toProfile :: Profile, updateSummary :: UserProfileUpdateSummary} | CRUserProfileImage {user :: User, profile :: Profile} | CRContactAliasUpdated {user :: User, toContact :: Contact} | CRGroupAliasUpdated {user :: User, toGroup :: GroupInfo} | CRConnectionAliasUpdated {user :: User, toConnection :: PendingContactConnection} | CRContactPrefsUpdated {user :: User, fromContact :: Contact, toContact :: Contact} - | CRContactConnecting {user :: User, contact :: Contact} - | CRContactConnected {user :: User, contact :: Contact, userCustomProfile :: Maybe Profile} - | CRContactSndReady {user :: User, contact :: Contact} - | CRContactAnotherClient {user :: User, contact :: Contact} - | CRSubscriptionEnd {user :: User, connectionEntity :: ConnectionEntity} - | CRContactsDisconnected {server :: SMPServer, contactRefs :: [ContactRef]} - | CRContactsSubscribed {server :: SMPServer, contactRefs :: [ContactRef]} - | CRContactSubError {user :: User, contact :: Contact, chatError :: ChatError} - | CRContactSubSummary {user :: User, contactSubscriptions :: [ContactSubStatus]} - | CRUserContactSubSummary {user :: User, userContactSubscriptions :: [UserContactSubStatus]} - | CRNetworkStatus {networkStatus :: NetworkStatus, connections :: [AgentConnId]} | CRNetworkStatuses {user_ :: Maybe User, networkStatuses :: [ConnNetworkStatus]} - | CRHostConnected {protocol :: AProtocolType, transportHost :: TransportHost} - | CRHostDisconnected {protocol :: AProtocolType, transportHost :: TransportHost} - | CRGroupInvitation {user :: User, shortGroupInfo :: ShortGroupInfo} - | CRReceivedGroupInvitation {user :: User, groupInfo :: GroupInfo, contact :: Contact, fromMemberRole :: GroupMemberRole, memberRole :: GroupMemberRole} - | CRUserJoinedGroup {user :: User, groupInfo :: GroupInfo, hostMember :: GroupMember} | CRJoinedGroupMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember} - | CRJoinedGroupMemberConnecting {user :: User, groupInfo :: GroupInfo, hostMember :: GroupMember, member :: GroupMember} - | CRMemberRole {user :: User, groupInfo :: GroupInfo, byMember :: GroupMember, member :: GroupMember, fromRole :: GroupMemberRole, toRole :: GroupMemberRole} | CRMembersRoleUser {user :: User, groupInfo :: GroupInfo, members :: [GroupMember], toRole :: GroupMemberRole} - | CRMemberBlockedForAll {user :: User, groupInfo :: GroupInfo, byMember :: GroupMember, member :: GroupMember, blocked :: Bool} | CRMembersBlockedForAllUser {user :: User, groupInfo :: GroupInfo, members :: [GroupMember], blocked :: Bool} - | CRConnectedToGroupMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember, memberContact :: Maybe Contact} - | CRDeletedMember {user :: User, groupInfo :: GroupInfo, byMember :: GroupMember, deletedMember :: GroupMember, withMessages :: Bool} - | CRDeletedMemberUser {user :: User, groupInfo :: GroupInfo, member :: GroupMember, withMessages :: Bool} - | CRLeftMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember} - | CRUnknownMemberCreated {user :: User, groupInfo :: GroupInfo, forwardedByMember :: GroupMember, member :: GroupMember} - | CRUnknownMemberBlocked {user :: User, groupInfo :: GroupInfo, blockedByMember :: GroupMember, member :: GroupMember} - | CRUnknownMemberAnnounced {user :: User, groupInfo :: GroupInfo, announcingMember :: GroupMember, unknownMember :: GroupMember, announcedMember :: GroupMember} - | CRGroupEmpty {user :: User, shortGroupInfo :: ShortGroupInfo} - | CRGroupDeleted {user :: User, groupInfo :: GroupInfo, member :: GroupMember} | CRGroupUpdated {user :: User, fromGroup :: GroupInfo, toGroup :: GroupInfo, member_ :: Maybe GroupMember} | CRGroupProfile {user :: User, groupInfo :: GroupInfo} | CRGroupDescription {user :: User, groupInfo :: GroupInfo} -- only used in CLI | CRGroupLinkCreated {user :: User, groupInfo :: GroupInfo, connLinkContact :: CreatedLinkContact, memberRole :: GroupMemberRole} | CRGroupLink {user :: User, groupInfo :: GroupInfo, connLinkContact :: CreatedLinkContact, memberRole :: GroupMemberRole} | CRGroupLinkDeleted {user :: User, groupInfo :: GroupInfo} - | CRAcceptingGroupJoinRequestMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember} - | CRNoMemberContactCreating {user :: User, groupInfo :: GroupInfo, member :: GroupMember} -- only used in CLI | CRNewMemberContact {user :: User, contact :: Contact, groupInfo :: GroupInfo, member :: GroupMember} | CRNewMemberContactSentInv {user :: User, contact :: Contact, groupInfo :: GroupInfo, member :: GroupMember} - | CRNewMemberContactReceivedInv {user :: User, contact :: Contact, groupInfo :: GroupInfo, member :: GroupMember} - | CRContactAndMemberAssociated {user :: User, contact :: Contact, groupInfo :: GroupInfo, member :: GroupMember, updatedContact :: Contact} - | CRMemberSubError {user :: User, shortGroupInfo :: ShortGroupInfo, memberToSubscribe :: ShortGroupMember, chatError :: ChatError} - | CRMemberSubSummary {user :: User, memberSubscriptions :: [MemberSubStatus]} - | CRGroupSubscribed {user :: User, shortGroupInfo :: ShortGroupInfo} - | CRPendingSubSummary {user :: User, pendingSubscriptions :: [PendingSubStatus]} - | CRSndFileSubError {user :: User, sndFileTransfer :: SndFileTransfer, chatError :: ChatError} - | CRRcvFileSubError {user :: User, rcvFileTransfer :: RcvFileTransfer, chatError :: ChatError} - | CRCallInvitation {callInvitation :: RcvCallInvitation} - | CRCallOffer {user :: User, contact :: Contact, callType :: CallType, offer :: WebRTCSession, sharedKey :: Maybe C.Key, askConfirmation :: Bool} - | CRCallAnswer {user :: User, contact :: Contact, answer :: WebRTCSession} - | CRCallExtraInfo {user :: User, contact :: Contact, extraInfo :: WebRTCExtraInfo} - | CRCallEnded {user :: User, contact :: Contact} | CRCallInvitations {callInvitations :: [RcvCallInvitation]} - | CRUserContactLinkSubscribed -- TODO delete - | CRUserContactLinkSubError {chatError :: ChatError} -- TODO delete | CRNtfTokenStatus {status :: NtfTknStatus} | CRNtfToken {token :: DeviceToken, status :: NtfTknStatus, ntfMode :: NotificationsMode, ntfServer :: NtfServer} | CRNtfConns {ntfConns :: [NtfConn]} | CRConnNtfMessages {receivedMsgs :: NonEmpty (Maybe NtfMsgInfo)} - | CRNtfMessage {user :: User, connEntity :: ConnectionEntity, ntfMessage :: NtfMsgAckInfo} | CRContactConnectionDeleted {user :: User, connection :: PendingContactConnection} | CRRemoteHostList {remoteHosts :: [RemoteHostInfo]} | CRCurrentRemoteHost {remoteHost_ :: Maybe RemoteHostInfo} | CRRemoteHostStarted {remoteHost_ :: Maybe RemoteHostInfo, invitation :: Text, ctrlPort :: String, localAddrs :: NonEmpty RCCtrlAddress} - | CRRemoteHostSessionCode {remoteHost_ :: Maybe RemoteHostInfo, sessionCode :: Text} - | CRNewRemoteHost {remoteHost :: RemoteHostInfo} - | CRRemoteHostConnected {remoteHost :: RemoteHostInfo} - | CRRemoteHostStopped {remoteHostId_ :: Maybe RemoteHostId, rhsState :: RemoteHostSessionState, rhStopReason :: RemoteHostStopReason} | CRRemoteFileStored {remoteHostId :: RemoteHostId, remoteFileSource :: CryptoFile} | CRRemoteCtrlList {remoteCtrls :: [RemoteCtrlInfo]} - | CRRemoteCtrlFound {remoteCtrl :: RemoteCtrlInfo, ctrlAppInfo_ :: Maybe CtrlAppInfo, appVersion :: AppVersion, compatible :: Bool} | CRRemoteCtrlConnecting {remoteCtrl_ :: Maybe RemoteCtrlInfo, ctrlAppInfo :: CtrlAppInfo, appVersion :: AppVersion} - | CRRemoteCtrlSessionCode {remoteCtrl_ :: Maybe RemoteCtrlInfo, sessionCode :: Text} | CRRemoteCtrlConnected {remoteCtrl :: RemoteCtrlInfo} - | CRRemoteCtrlStopped {rcsState :: RemoteCtrlSessionState, rcStopReason :: RemoteCtrlStopReason} - | CRContactPQEnabled {user :: User, contact :: Contact, pqEnabled :: PQEncryption} | CRSQLResult {rows :: [Text]} #if !defined(dbPostgres) | CRArchiveExported {archiveErrors :: [ArchiveError]} @@ -826,25 +731,134 @@ data ChatResponse | CRAgentSubs {activeSubs :: Map Text Int, pendingSubs :: Map Text Int, removedSubs :: Map Text [String]} | CRAgentSubsDetails {agentSubs :: SubscriptionsInfo} | CRAgentQueuesInfo {agentQueuesInfo :: AgentQueuesInfo} - | CRContactDisabled {user :: User, contact :: Contact} - | CRConnectionDisabled {connectionEntity :: ConnectionEntity} - | CRConnectionInactive {connectionEntity :: ConnectionEntity, inactive :: Bool} - | CRAgentRcvQueuesDeleted {deletedRcvQueues :: NonEmpty DeletedRcvQueue} - | CRAgentConnsDeleted {agentConnIds :: NonEmpty AgentConnId} - | CRAgentUserDeleted {agentUserId :: Int64} - | CRMessageError {user :: User, severity :: Text, errorMessage :: Text} | CRChatCmdError {user_ :: Maybe User, chatError :: ChatError} - | CRChatError {user_ :: Maybe User, chatError :: ChatError} - | CRChatErrors {user_ :: Maybe User, chatErrors :: [ChatError]} | CRAppSettings {appSettings :: AppSettings} - | CRTimedAction {action :: String, durationMilliseconds :: Int64} | CRCustomChatResponse {user_ :: Maybe User, response :: Text} - | CRTerminalEvent TerminalEvent + deriving (Show) + +data ChatEvent + = CEvtChatSuspended + | CEvtContactSwitch {user :: User, contact :: Contact, switchProgress :: SwitchProgress} + | CEvtGroupMemberSwitch {user :: User, groupInfo :: GroupInfo, member :: GroupMember, switchProgress :: SwitchProgress} + | CEvtContactRatchetSync {user :: User, contact :: Contact, ratchetSyncProgress :: RatchetSyncProgress} + | CEvtGroupMemberRatchetSync {user :: User, groupInfo :: GroupInfo, member :: GroupMember, ratchetSyncProgress :: RatchetSyncProgress} + | CEvtNewChatItems {user :: User, chatItems :: [AChatItem]} -- there is the same command response + | CEvtChatItemsStatusesUpdated {user :: User, chatItems :: [AChatItem]} + | CEvtChatItemUpdated {user :: User, chatItem :: AChatItem} -- there is the same command response + | CEvtChatItemNotChanged {user :: User, chatItem :: AChatItem} -- there is the same command response + | CEvtChatItemReaction {user :: User, added :: Bool, reaction :: ACIReaction} -- there is the same command response + | CEvtGroupChatItemsDeleted {user :: User, groupInfo :: GroupInfo, chatItemIDs :: [ChatItemId], byUser :: Bool, member_ :: Maybe GroupMember} -- there is the same command response + | CEvtChatItemsDeleted {user :: User, chatItemDeletions :: [ChatItemDeletion], byUser :: Bool, timed :: Bool} -- there is the same command response + | CEvtChatItemDeletedNotFound {user :: User, contact :: Contact, sharedMsgId :: SharedMsgId} + | CEvtUserAcceptedGroupSent {user :: User, groupInfo :: GroupInfo, hostContact :: Maybe Contact} -- there is the same command response + | CEvtGroupLinkConnecting {user :: User, groupInfo :: GroupInfo, hostMember :: GroupMember} + | CEvtBusinessLinkConnecting {user :: User, groupInfo :: GroupInfo, hostMember :: GroupMember, fromContact :: Contact} + | CEvtSentGroupInvitation {user :: User, groupInfo :: GroupInfo, contact :: Contact, member :: GroupMember} -- there is the same command response + | CEvtContactUpdated {user :: User, fromContact :: Contact, toContact :: Contact} + | CEvtGroupMemberUpdated {user :: User, groupInfo :: GroupInfo, fromMember :: GroupMember, toMember :: GroupMember} + | CEvtContactsMerged {user :: User, intoContact :: Contact, mergedContact :: Contact, updatedContact :: Contact} + | CEvtContactDeletedByContact {user :: User, contact :: Contact} + | CEvtReceivedContactRequest {user :: User, contactRequest :: UserContactRequest} + | CEvtAcceptingContactRequest {user :: User, contact :: Contact} -- there is the same command response + | CEvtAcceptingBusinessRequest {user :: User, groupInfo :: GroupInfo} + | CEvtContactRequestAlreadyAccepted {user :: User, contact :: Contact} + | CEvtBusinessRequestAlreadyAccepted {user :: User, groupInfo :: GroupInfo} + | CEvtRcvFileDescrReady {user :: User, chatItem :: AChatItem, rcvFileTransfer :: RcvFileTransfer, rcvFileDescr :: RcvFileDescr} + | CEvtRcvFileAccepted {user :: User, chatItem :: AChatItem} -- there is the same command response + -- TODO add chatItem :: AChatItem + | CEvtRcvFileAcceptedSndCancelled {user :: User, rcvFileTransfer :: RcvFileTransfer} -- there is the same command response + | CEvtRcvFileStart {user :: User, chatItem :: AChatItem} -- sent by chats + | CEvtRcvFileProgressXFTP {user :: User, chatItem_ :: Maybe AChatItem, receivedSize :: Int64, totalSize :: Int64, rcvFileTransfer :: RcvFileTransfer} + | CEvtRcvFileComplete {user :: User, chatItem :: AChatItem} + | CEvtRcvStandaloneFileComplete {user :: User, targetPath :: FilePath, rcvFileTransfer :: RcvFileTransfer} + | CEvtRcvFileSndCancelled {user :: User, chatItem :: AChatItem, rcvFileTransfer :: RcvFileTransfer} + | CEvtRcvFileError {user :: User, chatItem_ :: Maybe AChatItem, agentError :: AgentErrorType, rcvFileTransfer :: RcvFileTransfer} + | CEvtRcvFileWarning {user :: User, chatItem_ :: Maybe AChatItem, agentError :: AgentErrorType, rcvFileTransfer :: RcvFileTransfer} + | CEvtSndFileStart {user :: User, chatItem :: AChatItem, sndFileTransfer :: SndFileTransfer} + | CEvtSndFileComplete {user :: User, chatItem :: AChatItem, sndFileTransfer :: SndFileTransfer} + | CEvtSndFileRcvCancelled {user :: User, chatItem_ :: Maybe AChatItem, sndFileTransfer :: SndFileTransfer} + | CEvtSndFileStartXFTP {user :: User, chatItem :: AChatItem, fileTransferMeta :: FileTransferMeta} -- not used + | CEvtSndFileProgressXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, sentSize :: Int64, totalSize :: Int64} + | CEvtSndFileRedirectStartXFTP {user :: User, fileTransferMeta :: FileTransferMeta, redirectMeta :: FileTransferMeta} + | CEvtSndFileCompleteXFTP {user :: User, chatItem :: AChatItem, fileTransferMeta :: FileTransferMeta} + | CEvtSndStandaloneFileComplete {user :: User, fileTransferMeta :: FileTransferMeta, rcvURIs :: [Text]} + | CEvtSndFileCancelledXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta} + | CEvtSndFileError {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, errorMessage :: Text} + | CEvtSndFileWarning {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, errorMessage :: Text} + | CEvtContactConnecting {user :: User, contact :: Contact} + | CEvtContactConnected {user :: User, contact :: Contact, userCustomProfile :: Maybe Profile} + | CEvtContactSndReady {user :: User, contact :: Contact} + | CEvtContactAnotherClient {user :: User, contact :: Contact} + | CEvtSubscriptionEnd {user :: User, connectionEntity :: ConnectionEntity} + | CEvtContactsDisconnected {server :: SMPServer, contactRefs :: [ContactRef]} + | CEvtContactsSubscribed {server :: SMPServer, contactRefs :: [ContactRef]} + | CEvtContactSubError {user :: User, contact :: Contact, chatError :: ChatError} + | CEvtContactSubSummary {user :: User, contactSubscriptions :: [ContactSubStatus]} + | CEvtUserContactSubSummary {user :: User, userContactSubscriptions :: [UserContactSubStatus]} + | CEvtNetworkStatus {networkStatus :: NetworkStatus, connections :: [AgentConnId]} + | CEvtNetworkStatuses {user_ :: Maybe User, networkStatuses :: [ConnNetworkStatus]} -- there is the same command response + | CEvtHostConnected {protocol :: AProtocolType, transportHost :: TransportHost} + | CEvtHostDisconnected {protocol :: AProtocolType, transportHost :: TransportHost} + | CEvtReceivedGroupInvitation {user :: User, groupInfo :: GroupInfo, contact :: Contact, fromMemberRole :: GroupMemberRole, memberRole :: GroupMemberRole} + | CEvtUserJoinedGroup {user :: User, groupInfo :: GroupInfo, hostMember :: GroupMember} + | CEvtJoinedGroupMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember} -- there is the same command response + | CEvtJoinedGroupMemberConnecting {user :: User, groupInfo :: GroupInfo, hostMember :: GroupMember, member :: GroupMember} + | CEvtMemberRole {user :: User, groupInfo :: GroupInfo, byMember :: GroupMember, member :: GroupMember, fromRole :: GroupMemberRole, toRole :: GroupMemberRole} + | CEvtMemberBlockedForAll {user :: User, groupInfo :: GroupInfo, byMember :: GroupMember, member :: GroupMember, blocked :: Bool} + | CEvtConnectedToGroupMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember, memberContact :: Maybe Contact} + | CEvtDeletedMember {user :: User, groupInfo :: GroupInfo, byMember :: GroupMember, deletedMember :: GroupMember, withMessages :: Bool} + | CEvtDeletedMemberUser {user :: User, groupInfo :: GroupInfo, member :: GroupMember, withMessages :: Bool} + | CEvtLeftMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember} + | CEvtUnknownMemberCreated {user :: User, groupInfo :: GroupInfo, forwardedByMember :: GroupMember, member :: GroupMember} + | CEvtUnknownMemberBlocked {user :: User, groupInfo :: GroupInfo, blockedByMember :: GroupMember, member :: GroupMember} + | CEvtUnknownMemberAnnounced {user :: User, groupInfo :: GroupInfo, announcingMember :: GroupMember, unknownMember :: GroupMember, announcedMember :: GroupMember} + | CEvtGroupDeleted {user :: User, groupInfo :: GroupInfo, member :: GroupMember} + | CEvtGroupUpdated {user :: User, fromGroup :: GroupInfo, toGroup :: GroupInfo, member_ :: Maybe GroupMember} -- there is the same command response + | CEvtAcceptingGroupJoinRequestMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember} + | CEvtNoMemberContactCreating {user :: User, groupInfo :: GroupInfo, member :: GroupMember} -- only used in CLI + | CEvtNewMemberContactReceivedInv {user :: User, contact :: Contact, groupInfo :: GroupInfo, member :: GroupMember} + | CEvtContactAndMemberAssociated {user :: User, contact :: Contact, groupInfo :: GroupInfo, member :: GroupMember, updatedContact :: Contact} + | CEvtCallInvitation {callInvitation :: RcvCallInvitation} + | CEvtCallOffer {user :: User, contact :: Contact, callType :: CallType, offer :: WebRTCSession, sharedKey :: Maybe C.Key, askConfirmation :: Bool} + | CEvtCallAnswer {user :: User, contact :: Contact, answer :: WebRTCSession} + | CEvtCallExtraInfo {user :: User, contact :: Contact, extraInfo :: WebRTCExtraInfo} + | CEvtCallEnded {user :: User, contact :: Contact} + | CEvtNtfMessage {user :: User, connEntity :: ConnectionEntity, ntfMessage :: NtfMsgAckInfo} + | CEvtRemoteHostSessionCode {remoteHost_ :: Maybe RemoteHostInfo, sessionCode :: Text} + | CEvtNewRemoteHost {remoteHost :: RemoteHostInfo} + | CEvtRemoteHostConnected {remoteHost :: RemoteHostInfo} + | CEvtRemoteHostStopped {remoteHostId_ :: Maybe RemoteHostId, rhsState :: RemoteHostSessionState, rhStopReason :: RemoteHostStopReason} + | CEvtRemoteCtrlFound {remoteCtrl :: RemoteCtrlInfo, ctrlAppInfo_ :: Maybe CtrlAppInfo, appVersion :: AppVersion, compatible :: Bool} + | CEvtRemoteCtrlSessionCode {remoteCtrl_ :: Maybe RemoteCtrlInfo, sessionCode :: Text} + | CEvtRemoteCtrlStopped {rcsState :: RemoteCtrlSessionState, rcStopReason :: RemoteCtrlStopReason} + | CEvtContactPQEnabled {user :: User, contact :: Contact, pqEnabled :: PQEncryption} + | CEvtContactDisabled {user :: User, contact :: Contact} + | CEvtConnectionDisabled {connectionEntity :: ConnectionEntity} + | CEvtConnectionInactive {connectionEntity :: ConnectionEntity, inactive :: Bool} + | CEvtAgentRcvQueuesDeleted {deletedRcvQueues :: NonEmpty DeletedRcvQueue} + | CEvtAgentConnsDeleted {agentConnIds :: NonEmpty AgentConnId} + | CEvtAgentUserDeleted {agentUserId :: Int64} + | CEvtMessageError {user :: User, severity :: Text, errorMessage :: Text} + | CEvtChatError {user_ :: Maybe User, chatError :: ChatError} + | CEvtChatErrors {user_ :: Maybe User, chatErrors :: [ChatError]} + | CEvtTimedAction {action :: String, durationMilliseconds :: Int64} + | CEvtTerminalEvent TerminalEvent deriving (Show) data TerminalEvent = TEGroupLinkRejected {user :: User, groupInfo :: GroupInfo, groupRejectionReason :: GroupRejectionReason} | TERejectingGroupJoinRequestMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember, groupRejectionReason :: GroupRejectionReason} + | TENewMemberContact {user :: User, contact :: Contact, groupInfo :: GroupInfo, member :: GroupMember} + | TEContactVerificationReset {user :: User, contact :: Contact} + | TEGroupMemberVerificationReset {user :: User, groupInfo :: GroupInfo, member :: GroupMember} + | TEGroupEmpty {user :: User, shortGroupInfo :: ShortGroupInfo} + | TEGroupSubscribed {user :: User, shortGroupInfo :: ShortGroupInfo} + | TEGroupInvitation {user :: User, shortGroupInfo :: ShortGroupInfo} + | TEMemberSubError {user :: User, shortGroupInfo :: ShortGroupInfo, memberToSubscribe :: ShortGroupMember, chatError :: ChatError} + | TEMemberSubSummary {user :: User, memberSubscriptions :: [MemberSubStatus]} + | TEPendingSubSummary {user :: User, pendingSubscriptions :: [PendingSubStatus]} + | TESndFileSubError {user :: User, sndFileTransfer :: SndFileTransfer, chatError :: ChatError} + | TERcvFileSubError {user :: User, rcvFileTransfer :: RcvFileTransfer, chatError :: ChatError} deriving (Show) data DeletedRcvQueue = DeletedRcvQueue @@ -856,49 +870,37 @@ data DeletedRcvQueue = DeletedRcvQueue deriving (Show) -- some of these can only be used as command responses -allowRemoteEvent :: ChatResponse -> Bool +allowRemoteEvent :: ChatEvent -> Bool allowRemoteEvent = \case - CRChatStarted -> False - CRChatRunning -> False - CRChatStopped -> False - CRChatSuspended -> False - CRRemoteHostList _ -> False - CRCurrentRemoteHost _ -> False - CRRemoteHostStarted {} -> False - CRRemoteHostSessionCode {} -> False - CRNewRemoteHost _ -> False - CRRemoteHostConnected _ -> False - CRRemoteHostStopped {} -> False - CRRemoteFileStored {} -> False - CRRemoteCtrlList _ -> False - CRRemoteCtrlFound {} -> False - CRRemoteCtrlConnecting {} -> False - CRRemoteCtrlSessionCode {} -> False - CRRemoteCtrlConnected _ -> False - CRRemoteCtrlStopped {} -> False - CRSQLResult _ -> False -#if !defined(dbPostgres) - CRSlowSQLQueries {} -> False -#endif + CEvtChatSuspended -> False + CEvtRemoteHostSessionCode {} -> False + CEvtNewRemoteHost _ -> False + CEvtRemoteHostConnected _ -> False + CEvtRemoteHostStopped {} -> False + CEvtRemoteCtrlFound {} -> False + CEvtRemoteCtrlSessionCode {} -> False + CEvtRemoteCtrlStopped {} -> False _ -> True -logResponseToFile :: ChatResponse -> Bool -logResponseToFile = \case - CRContactsDisconnected {} -> True - CRContactsSubscribed {} -> True - CRContactSubError {} -> True - CRMemberSubError {} -> True - CRSndFileSubError {} -> True - CRRcvFileSubError {} -> True - CRHostConnected {} -> True - CRHostDisconnected {} -> True - CRConnectionDisabled {} -> True - CRAgentRcvQueuesDeleted {} -> True - CRAgentConnsDeleted {} -> True - CRAgentUserDeleted {} -> True - CRChatCmdError {} -> True - CRChatError {} -> True - CRMessageError {} -> True +logEventToFile :: ChatEvent -> Bool +logEventToFile = \case + CEvtContactsDisconnected {} -> True + CEvtContactsSubscribed {} -> True + CEvtContactSubError {} -> True + CEvtHostConnected {} -> True + CEvtHostDisconnected {} -> True + CEvtConnectionDisabled {} -> True + CEvtAgentRcvQueuesDeleted {} -> True + CEvtAgentConnsDeleted {} -> True + CEvtAgentUserDeleted {} -> True + -- CEvtChatCmdError {} -> True -- TODO this should be separately logged to file + CEvtChatError {} -> True + CEvtMessageError {} -> True + CEvtTerminalEvent te -> case te of + TEMemberSubError {} -> True + TESndFileSubError {} -> True + TERcvFileSubError {} -> True + _ -> False _ -> False -- (Maybe GroupMemberId) can later be changed to GroupSndScope = GSSAll | GSSAdmins | GSSMember GroupMemberId @@ -1406,7 +1408,7 @@ data RemoteCtrlSession tls :: TLS, rcsSession :: RCCtrlSession, http2Server :: Async (), - remoteOutputQ :: TBQueue ChatResponse + remoteOutputQ :: TBQueue ChatEvent } data RemoteCtrlSessionState @@ -1512,15 +1514,15 @@ throwChatError :: ChatErrorType -> CM a throwChatError = throwError . ChatError toViewTE :: TerminalEvent -> CM () -toViewTE = toView . CRTerminalEvent +toViewTE = toView . CEvtTerminalEvent {-# INLINE toViewTE #-} -- | Emit local events. -toView :: ChatResponse -> CM () +toView :: ChatEvent -> CM () toView = lift . toView' {-# INLINE toView #-} -toView' :: ChatResponse -> CM' () +toView' :: ChatEvent -> CM' () toView' ev = do cc@ChatController {outputQ = localQ, remoteCtrlSession = session, config = ChatConfig {chatHooks}} <- ask event <- case eventHook chatHooks of @@ -1531,7 +1533,7 @@ toView' ev = do Just (_, RCSessionConnected {remoteOutputQ}) | allowRemoteEvent event -> writeTBQueue remoteOutputQ event -- TODO potentially, it should hold some events while connecting - _ -> writeTBQueue localQ (Nothing, Nothing, event) + _ -> writeTBQueue localQ (Nothing, event) withStore' :: (DB.Connection -> IO a) -> CM a withStore' action = withStore $ liftIO . action @@ -1660,6 +1662,8 @@ $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "TE") ''TerminalEvent) $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CR") ''ChatResponse) +$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CEvt") ''ChatEvent) + $(JQ.deriveFromJSON defaultJSON ''ArchiveConfig) $(JQ.deriveFromJSON defaultJSON ''DBEncryptionConfig) diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index eeb54c6aef..77871ccc1b 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -225,8 +225,8 @@ startReceiveUserFiles :: User -> CM () startReceiveUserFiles user = do filesToReceive <- withStore' (`getRcvFilesToReceive` user) forM_ filesToReceive $ \ft -> - flip catchChatError (toView . CRChatError (Just user)) $ - toView =<< receiveFile' user ft False Nothing Nothing + flip catchChatError (toView . CEvtChatError (Just user)) $ + toView =<< receiveFileEvt' user ft False Nothing Nothing restoreCalls :: CM' () restoreCalls = do @@ -502,7 +502,7 @@ processChatCommand' vr = \case pure $ CRChatTags user tags APIGetChats {userId, pendingConnections, pagination, query} -> withUserId' userId $ \user -> do (errs, previews) <- partitionEithers <$> withFastStore' (\db -> getChatPreviews db vr user pendingConnections pagination query) - unless (null errs) $ toView $ CRChatErrors (Just user) (map ChatErrorStore errs) + unless (null errs) $ toView $ CEvtChatErrors (Just user) (map ChatErrorStore errs) pure $ CRApiChats user previews APIGetChat (ChatRef cType cId) contentFilter pagination search -> withUser $ \user -> case cType of -- TODO optimize queries calculating ChatStats, currently they're disabled @@ -665,9 +665,9 @@ processChatCommand' vr = \case APIDeleteChatItem (ChatRef cType chatId) itemIds mode -> withUser $ \user -> case cType of CTDirect -> withContactLock "deleteChatItem" chatId $ do (ct, items) <- getCommandDirectChatItems user chatId itemIds - case mode of - CIDMInternal -> deleteDirectCIs user ct items True False - CIDMInternalMark -> markDirectCIsDeleted user ct items True =<< liftIO getCurrentTime + deletions <- case mode of + CIDMInternal -> deleteDirectCIs user ct items + CIDMInternalMark -> markDirectCIsDeleted user ct items =<< liftIO getCurrentTime CIDMBroadcast -> do assertDeletable items assertDirectAllowed user MDSnd ct XMsgDel_ @@ -676,13 +676,14 @@ processChatCommand' vr = \case forM_ (L.nonEmpty events) $ \events' -> sendDirectContactMessages user ct events' if featureAllowed SCFFullDelete forUser ct - then deleteDirectCIs user ct items True False - else markDirectCIsDeleted user ct items True =<< liftIO getCurrentTime + then deleteDirectCIs user ct items + else markDirectCIsDeleted user ct items =<< liftIO getCurrentTime + pure $ CRChatItemsDeleted user deletions True False CTGroup -> withGroupLock "deleteChatItem" chatId $ do (gInfo, items) <- getCommandGroupChatItems user chatId itemIds - case mode of - CIDMInternal -> deleteGroupCIs user gInfo items True False Nothing =<< liftIO getCurrentTime - CIDMInternalMark -> markGroupCIsDeleted user gInfo items True Nothing =<< liftIO getCurrentTime + deletions <- case mode of + CIDMInternal -> deleteGroupCIs user gInfo items Nothing =<< liftIO getCurrentTime + CIDMInternalMark -> markGroupCIsDeleted user gInfo items Nothing =<< liftIO getCurrentTime CIDMBroadcast -> do ms <- withFastStore' $ \db -> getGroupMembers db vr user gInfo assertDeletable items @@ -692,6 +693,7 @@ processChatCommand' vr = \case -- TODO [knocking] validate: only current members or only single pending approval member mapM_ (sendGroupMessages user gInfo ms) events delGroupChatItems user gInfo items False + pure $ CRChatItemsDeleted user deletions True False CTLocal -> do (nf, items) <- getCommandLocalChatItems user chatId itemIds deleteLocalCIs user nf items True False @@ -714,7 +716,8 @@ processChatCommand' vr = \case APIDeleteMemberChatItem gId itemIds -> withUser $ \user -> withGroupLock "deleteChatItem" gId $ do (gInfo, items) <- getCommandGroupChatItems user gId itemIds ms <- withFastStore' $ \db -> getGroupMembers db vr user gInfo - delGroupChatItemsForMembers user gInfo ms items + deletions <- delGroupChatItemsForMembers user gInfo ms items + pure $ CRChatItemsDeleted user deletions True False APIArchiveReceivedReports gId -> withUser $ \user -> withFastStore $ \db -> do g <- getGroupInfo db vr user gId deleteTs <- liftIO getCurrentTime @@ -723,12 +726,13 @@ processChatCommand' vr = \case APIDeleteReceivedReports gId itemIds mode -> withUser $ \user -> withGroupLock "deleteReports" gId $ do (gInfo, items) <- getCommandGroupChatItems user gId itemIds unless (all isRcvReport items) $ throwChatError $ CECommandError "some items are not received reports" - case mode of - CIDMInternal -> deleteGroupCIs user gInfo items True False Nothing =<< liftIO getCurrentTime - CIDMInternalMark -> markGroupCIsDeleted user gInfo items True Nothing =<< liftIO getCurrentTime + deletions <- case mode of + CIDMInternal -> deleteGroupCIs user gInfo items Nothing =<< liftIO getCurrentTime + CIDMInternalMark -> markGroupCIsDeleted user gInfo items Nothing =<< liftIO getCurrentTime CIDMBroadcast -> do ms <- withFastStore' $ \db -> getGroupModerators db vr user gInfo delGroupChatItemsForMembers user gInfo ms items + pure $ CRChatItemsDeleted user deletions True False where isRcvReport = \case CChatItem _ ChatItem {content = CIRcvMsgContent (MCReport {})} -> True @@ -1166,7 +1170,7 @@ processChatCommand' vr = \case let call' = Call {contactId, callId, callUUID, chatItemId = chatItemId' ci, callState, callTs = chatItemTs' ci} call_ <- atomically $ TM.lookupInsert contactId call' calls forM_ call_ $ \call -> updateCallItemStatus user ct call WCSDisconnected Nothing - toView $ CRNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct) ci] + toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct) ci] ok user else pure $ chatCmdError (Just user) ("feature not allowed " <> T.unpack (chatFeatureNameText CFCalls)) SendCallInvitation cName callType -> withUser $ \user -> do @@ -1293,7 +1297,7 @@ processChatCommand' vr = \case APIGetNtfConns nonce encNtfInfo -> withUser $ \user -> do ntfInfos <- withAgent $ \a -> getNotificationConns a nonce encNtfInfo (errs, ntfMsgs) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (getMsgConn db) (L.toList ntfInfos)) - unless (null errs) $ toView $ CRChatErrors (Just user) errs + unless (null errs) $ toView $ CEvtChatErrors (Just user) errs pure $ CRNtfConns $ catMaybes ntfMsgs where getMsgConn :: DB.Connection -> NotificationInfo -> IO (Maybe NtfConn) @@ -1404,7 +1408,7 @@ processChatCommand' vr = \case oldTTL = fromMaybe globalTTL oldTTL_ when (newTTL > 0 && (newTTL < oldTTL || oldTTL == 0)) $ do lift $ setExpireCIFlag user False - expireChat user globalTTL `catchChatError` (toView . CRChatError (Just user)) + expireChat user globalTTL `catchChatError` (toView . CEvtChatError (Just user)) lift $ setChatItemsExpiration user globalTTL ttlCount ok user where @@ -1474,7 +1478,7 @@ processChatCommand' vr = \case liftIO $ updateGroupSettings db user chatId chatSettings pure ms forM_ (filter memberActive ms) $ \m -> forM_ (memberConnId m) $ \connId -> - withAgent (\a -> toggleConnectionNtfs a connId $ chatHasNtfs chatSettings) `catchChatError` (toView . CRChatError (Just user)) + withAgent (\a -> toggleConnectionNtfs a connId $ chatHasNtfs chatSettings) `catchChatError` (toView . CEvtChatError (Just user)) ok user _ -> pure $ chatCmdError (Just user) "not supported" APISetMemberSettings gId gMemberId settings -> withUser $ \user -> do @@ -1865,10 +1869,10 @@ processChatCommand' vr = \case Nothing -> do g <- withFastStore $ \db -> getGroupInfo db vr user gId unless (groupFeatureMemberAllowed SGFDirectMessages (membership g) g) $ throwChatError $ CECommandError "direct messages not allowed" - toView $ CRNoMemberContactCreating user g m + toView $ CEvtNoMemberContactCreating user g m processChatCommand (APICreateMemberContact gId mId) >>= \case - cr@(CRNewMemberContact _ Contact {contactId} _ _) -> do - toView cr + CRNewMemberContact _ ct@Contact {contactId} _ _ -> do + toViewTE $ TENewMemberContact user ct g m processChatCommand $ APISendMemberContactInvitation contactId (Just mc) cr -> pure cr Just ctId -> do @@ -2015,7 +2019,7 @@ processChatCommand' vr = \case updateGroupMemberStatus db userId fromMember GSMemInvited updateGroupMemberStatus db userId membership GSMemInvited throwError e - updateCIGroupInvitationStatus user g CIGISAccepted `catchChatError` (toView . CRChatError (Just user)) + updateCIGroupInvitationStatus user g CIGISAccepted `catchChatError` (toView . CEvtChatError (Just user)) pure $ CRUserAcceptedGroupSent user g {membership = membership {memberStatus = GSMemAccepted}} Nothing Nothing -> throwChatError $ CEContactNotActive ct APIAcceptMember groupId gmId role -> withUser $ \user -> do @@ -2042,9 +2046,9 @@ processChatCommand' vr = \case assertUserGroupRole gInfo $ maximum ([GRAdmin, maxRole, newRole] :: [GroupMemberRole]) (errs1, changed1) <- changeRoleInvitedMems user gInfo invitedMems (errs2, changed2, acis) <- changeRoleCurrentMems user g currentMems - unless (null acis) $ toView $ CRNewChatItems user acis + unless (null acis) $ toView $ CEvtNewChatItems user acis let errs = errs1 <> errs2 - unless (null errs) $ toView $ CRChatErrors (Just user) errs + unless (null errs) $ toView $ CEvtChatErrors (Just user) errs pure $ CRMembersRoleUser {user, groupInfo = gInfo, members = changed1 <> changed2, toRole = newRole} -- same order is not guaranteed where selfSelected GroupInfo {membership} = elem (groupMemberId' membership) memberIds @@ -2130,9 +2134,9 @@ processChatCommand' vr = \case cis_ <- saveSndChatItems user (CDGroupSnd gInfo) Nothing itemsData Nothing False when (length cis_ /= length blockMems) $ logError "blockMembers: blockMems and cis_ length mismatch" let acis = map (AChatItem SCTGroup SMDSnd (GroupChat gInfo)) $ rights cis_ - unless (null acis) $ toView $ CRNewChatItems user acis + unless (null acis) $ toView $ CEvtNewChatItems user acis (errs, blocked) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (updateGroupMemberBlocked db user gInfo mrs) blockMems) - unless (null errs) $ toView $ CRChatErrors (Just user) errs + unless (null errs) $ toView $ CEvtChatErrors (Just user) errs -- TODO not batched - requires agent batch api forM_ blocked $ \m -> toggleNtf user m (not blockFlag) pure CRMembersBlockedForAllUser {user, groupInfo = gInfo, members = blocked, blocked = blockFlag} @@ -2156,8 +2160,8 @@ processChatCommand' vr = \case let (errs3, deleted3, acis3) = concatTuples rs acis = acis2 <> acis3 errs = errs1 <> errs2 <> errs3 - unless (null acis) $ toView $ CRNewChatItems user acis - unless (null errs) $ toView $ CRChatErrors (Just user) errs + unless (null acis) $ toView $ CEvtNewChatItems user acis + unless (null errs) $ toView $ CEvtChatErrors (Just user) errs when withMessages $ deleteMessages user gInfo $ currentMems <> pendingMems pure $ CRUserDeletedMembers user gInfo (deleted1 <> deleted2 <> deleted3) withMessages -- same order is not guaranteed where @@ -2218,7 +2222,7 @@ processChatCommand' vr = \case -- TODO [knocking] send to pending approval members (move `memberCurrent` filter from sendGroupMessages_ to call sites) msg <- sendGroupMessage' user gInfo members XGrpLeave ci <- saveSndChatItem user (CDGroupSnd gInfo) msg (CISndGroupEvent SGEUserLeft) - toView $ CRNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci] + toView $ CEvtNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci] -- TODO delete direct connections that were unused deleteGroupLinkIfExists user gInfo -- member records are not deleted to keep history @@ -2324,7 +2328,7 @@ processChatCommand' vr = \case let ct' = ct {contactGrpInvSent = True} forM_ msgContent_ $ \mc -> do ci <- saveSndChatItem user (CDDirectSnd ct') sndMsg (CISndMsgContent mc) - toView $ CRNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct') ci] + toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct') ci] pure $ CRNewMemberContactSentInv user ct' g m _ -> throwChatError CEGroupMemberNotActive CreateGroupLink gName mRole short -> withUser $ \user -> do @@ -2353,7 +2357,7 @@ processChatCommand' vr = \case LastChats count_ -> withUser' $ \user -> do let count = fromMaybe 5000 count_ (errs, previews) <- partitionEithers <$> withFastStore' (\db -> getChatPreviews db vr user False (PTLast count) clqNoFilters) - unless (null errs) $ toView $ CRChatErrors (Just user) (map ChatErrorStore errs) + unless (null errs) $ toView $ CEvtChatErrors (Just user) (map ChatErrorStore errs) pure $ CRChats previews LastMessages (Just chatName) count search -> withUser $ \user -> do chatRef <- getChatRef user chatName @@ -2594,19 +2598,9 @@ processChatCommand' vr = \case -- in a modified CLI app or core - the hook should return Either ChatResponse ChatCommand CustomChatCommand _cmd -> withUser $ \user -> pure $ chatCmdError (Just user) "not supported" where - -- below code would make command responses asynchronous where they can be slow - -- in View.hs `r'` should be defined as `id` in this case - -- procCmd :: m ChatResponse -> m ChatResponse - -- procCmd action = do - -- ChatController {chatLock = l, smpAgent = a, outputQ = q, random = gVar} <- ask - -- corrId <- liftIO $ SMP.CorrId <$> randomBytes gVar 8 - -- void . forkIO $ - -- withAgentLock a . withLock l name $ - -- (atomically . writeTBQueue q) . (Just corrId,) =<< (action `catchChatError` (pure . CRChatError)) - -- pure $ CRCmdAccepted corrId - -- use function below to make commands "synchronous" procCmd :: CM ChatResponse -> CM ChatResponse procCmd = id + {-# INLINE procCmd #-} ok_ = pure $ CRCmdOk Nothing ok = pure . CRCmdOk . Just getChatRef :: User -> ChatName -> CM ChatRef @@ -2768,7 +2762,7 @@ processChatCommand' vr = \case let idsEvts = L.map ctSndEvent changedCts msgReqs_ <- lift $ L.zipWith ctMsgReq changedCts <$> createSndMessages idsEvts (errs, cts) <- partitionEithers . L.toList . L.zipWith (second . const) changedCts <$> deliverMessagesB msgReqs_ - unless (null errs) $ toView $ CRChatErrors (Just user) errs + unless (null errs) $ toView $ CEvtChatErrors (Just user) errs let changedCts' = filter (\ChangedProfileContact {ct, ct'} -> directOrUsed ct' && mergedPreferences ct' /= mergedPreferences ct) cts lift $ createContactsSndFeatureItems user' changedCts' pure @@ -2808,7 +2802,7 @@ processChatCommand' vr = \case mergedProfile' = userProfileToSend user (fromLocalProfile <$> incognitoProfile) (Just ct') False when (mergedProfile' /= mergedProfile) $ withContactLock "updateProfile" (contactId' ct) $ do - void (sendDirectContactMessage user ct' $ XInfo mergedProfile') `catchChatError` (toView . CRChatError (Just user)) + void (sendDirectContactMessage user ct' $ XInfo mergedProfile') `catchChatError` (toView . CEvtChatError (Just user)) lift . when (directOrUsed ct') $ createSndFeatureItems user ct ct' pure $ CRContactPrefsUpdated user ct ct' runUpdateGroupProfile :: User -> Group -> GroupProfile -> CM ChatResponse @@ -2832,7 +2826,7 @@ processChatCommand' vr = \case let cd = CDGroupSnd g' unless (sameGroupProfileInfo p p') $ do ci <- saveSndChatItem user cd msg (CISndGroupEvent $ SGEGroupUpdated p') - toView $ CRNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat g') ci] + toView $ CEvtNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat g') ci] createGroupFeatureChangedItems user cd CISndGroupFeature g g' pure $ CRGroupUpdated user g g' Nothing checkValidName :: GroupName -> CM () @@ -2847,7 +2841,7 @@ processChatCommand' vr = \case when (memberStatus membership == GSMemInvited) $ throwChatError (CEGroupNotJoined g) when (memberRemoved membership) $ throwChatError CEGroupMemberUserRemoved unless (memberActive membership) $ throwChatError CEGroupMemberNotActive - delGroupChatItemsForMembers :: User -> GroupInfo -> [GroupMember] -> [CChatItem 'CTGroup] -> CM ChatResponse + delGroupChatItemsForMembers :: User -> GroupInfo -> [GroupMember] -> [CChatItem 'CTGroup] -> CM [ChatItemDeletion] delGroupChatItemsForMembers user gInfo ms items = do assertDeletable gInfo items assertUserGroupRole gInfo GRAdmin -- TODO GRModerator when most users migrate @@ -2876,16 +2870,16 @@ processChatCommand' vr = \case CIGroupRcv GroupMember {memberId} -> (msgId, memberId) CIGroupSnd -> (msgId, membershipMemId) - delGroupChatItems :: User -> GroupInfo -> [CChatItem 'CTGroup] -> Bool -> CM ChatResponse + delGroupChatItems :: User -> GroupInfo -> [CChatItem 'CTGroup] -> Bool -> CM [ChatItemDeletion] delGroupChatItems user gInfo@GroupInfo {membership} items moderation = do deletedTs <- liftIO getCurrentTime when moderation $ do ciIds <- concat <$> withStore' (\db -> forM items $ \(CChatItem _ ci) -> markMessageReportsDeleted db user gInfo ci membership deletedTs) - unless (null ciIds) $ toView $ CRGroupChatItemsDeleted user gInfo ciIds True (Just membership) + unless (null ciIds) $ toView $ CEvtGroupChatItemsDeleted user gInfo ciIds True (Just membership) let m = if moderation then Just membership else Nothing if groupFeatureMemberAllowed SGFFullDelete membership gInfo - then deleteGroupCIs user gInfo items True False m deletedTs - else markGroupCIsDeleted user gInfo items True m deletedTs + then deleteGroupCIs user gInfo items m deletedTs + else markGroupCIsDeleted user gInfo items m deletedTs updateGroupProfileByName :: GroupName -> (GroupProfile -> GroupProfile) -> CM ChatResponse updateGroupProfileByName gName update = withUser $ \user -> do g@(Group GroupInfo {groupProfile = p} _) <- withStore $ \db -> @@ -2962,7 +2956,7 @@ processChatCommand' vr = \case let content = CISndGroupInvitation (CIGroupInvitation {groupId, groupMemberId, localDisplayName, groupProfile, status = CIGISPending}) memRole timed_ <- contactCITimed ct ci <- saveSndChatItem' user (CDDirectSnd ct) msg content Nothing Nothing Nothing timed_ False - toView $ CRNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct) ci] + toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct) ci] forM_ (timed_ >>= timedDeleteAt') $ startProximateTimedItemThread user (ChatRef CTDirect contactId, chatItemId' ci) drgRandomBytes :: Int -> CM ByteString @@ -3011,7 +3005,7 @@ processChatCommand' vr = \case deleteCIFiles user filesInfo withAgent (\a -> deleteUser a (aUserId user) delSMPQueues) `catchChatError` \case - e@(ChatErrorAgent NO_USER _) -> toView $ CRChatError (Just user) e + e@(ChatErrorAgent NO_USER _) -> toView $ CEvtChatError (Just user) e e -> throwError e withFastStore' (`deleteUserRecord` user) when (activeUser user) $ chatWriteVar currentUser Nothing @@ -3064,7 +3058,7 @@ processChatCommand' vr = \case connectWithPlan :: User -> IncognitoEnabled -> ACreatedConnLink -> ConnectionPlan -> CM ChatResponse connectWithPlan user@User {userId} incognito ccLink plan | connectionPlanProceed plan = do - case plan of CPError e -> toView $ CRChatError (Just user) e; _ -> pure () + case plan of CPError e -> toView $ CEvtChatError (Just user) e; _ -> pure () case plan of CPContactAddress (CAPContactViaAddress Contact {contactId}) -> processChatCommand $ APIConnectContactViaAddress userId incognito contactId @@ -3364,7 +3358,7 @@ processChatCommand' vr = \case case contactOrGroup of CGContact Contact {activeConn} -> forM_ activeConn $ \conn -> withFastStore' $ \db -> createSndFTDescrXFTP db user Nothing conn ft dummyFileDescr - CGGroup _ ms -> forM_ ms $ \m -> saveMemberFD m `catchChatError` (toView . CRChatError (Just user)) + CGGroup _ ms -> forM_ ms $ \m -> saveMemberFD m `catchChatError` (toView . CEvtChatError (Just user)) where -- we are not sending files to pending members, same as with inline files saveMemberFD m@GroupMember {activeConn = Just conn@Connection {connStatus}} = @@ -3388,18 +3382,18 @@ processChatCommand' vr = \case -- no errors ([], _) -> pure () -- at least one item is successfully created - (errs, _ci : _) -> toView $ CRChatErrors (Just user) errs + (errs, _ci : _) -> toView $ CEvtChatErrors (Just user) errs -- single error ([err], []) -> throwError err -- multiple errors (errs@(err : _), []) -> do - toView $ CRChatErrors (Just user) errs + toView $ CEvtChatErrors (Just user) errs throwError err getCommandDirectChatItems :: User -> Int64 -> NonEmpty ChatItemId -> CM (Contact, [CChatItem 'CTDirect]) getCommandDirectChatItems user ctId itemIds = do ct <- withFastStore $ \db -> getContact db vr user ctId (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getDirectCI db) (L.toList itemIds)) - unless (null errs) $ toView $ CRChatErrors (Just user) errs + unless (null errs) $ toView $ CEvtChatErrors (Just user) errs pure (ct, items) where getDirectCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTDirect)) @@ -3408,7 +3402,7 @@ processChatCommand' vr = \case getCommandGroupChatItems user gId itemIds = do gInfo <- withFastStore $ \db -> getGroupInfo db vr user gId (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getGroupCI db gInfo) (L.toList itemIds)) - unless (null errs) $ toView $ CRChatErrors (Just user) errs + unless (null errs) $ toView $ CEvtChatErrors (Just user) errs pure (gInfo, items) where getGroupCI :: DB.Connection -> GroupInfo -> ChatItemId -> IO (Either ChatError (CChatItem 'CTGroup)) @@ -3417,7 +3411,7 @@ processChatCommand' vr = \case getCommandLocalChatItems user nfId itemIds = do nf <- withStore $ \db -> getNoteFolder db user nfId (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getLocalCI db) (L.toList itemIds)) - unless (null errs) $ toView $ CRChatErrors (Just user) errs + unless (null errs) $ toView $ CEvtChatErrors (Just user) errs pure (nf, items) where getLocalCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTLocal)) @@ -3542,7 +3536,7 @@ startExpireCIThread user@User {userId} = do liftIO $ threadDelay' delay interval <- asks $ ciExpirationInterval . config forever $ do - flip catchChatError' (toView' . CRChatError (Just user)) $ do + flip catchChatError' (toView' . CEvtChatError (Just user)) $ do expireFlags <- asks expireCIFlags atomically $ TM.lookup userId expireFlags >>= \b -> unless (b == Just True) retry lift waitChatStartedAndActivated @@ -3574,7 +3568,7 @@ agentSubscriber = do q <- asks $ subQ . smpAgent forever (atomically (readTBQueue q) >>= process) `E.catchAny` \e -> do - toView' $ CRChatError Nothing $ ChatErrorAgent (CRITICAL True $ "Message reception stopped: " <> show e) Nothing + toView' $ CEvtChatError Nothing $ ChatErrorAgent (CRITICAL True $ "Message reception stopped: " <> show e) Nothing E.throwIO e where process :: (ACorrId, AEntityId, AEvt) -> CM' () @@ -3584,7 +3578,7 @@ agentSubscriber = do SAERcvFile -> processAgentMsgRcvFile corrId entId msg SAESndFile -> processAgentMsgSndFile corrId entId msg where - run action = action `catchChatError'` (toView' . CRChatError Nothing) + run action = action `catchChatError'` (toView' . CEvtChatError Nothing) type AgentBatchSubscribe = AgentClient -> [ConnId] -> ExceptT AgentErrorType IO (Map ConnId (Either AgentErrorType ())) @@ -3689,9 +3683,9 @@ subscribeUserConnections vr onlyNeeded agentBatchSubscribe user = do notifyCLI = do let cRs = resultsFor rs cts cErrors = sortOn (\(Contact {localDisplayName = n}, _) -> n) $ filterErrors cRs - toView . CRContactSubSummary user $ map (uncurry ContactSubStatus) cRs - when ce $ mapM_ (toView . uncurry (CRContactSubError user)) cErrors - notifyAPI = toView . CRNetworkStatuses (Just user) . map (uncurry ConnNetworkStatus) + toView . CEvtContactSubSummary user $ map (uncurry ContactSubStatus) cRs + when ce $ mapM_ (toView . uncurry (CEvtContactSubError user)) cErrors + notifyAPI = toView . CEvtNetworkStatuses (Just user) . map (uncurry ConnNetworkStatus) statuses = M.foldrWithKey' addStatus [] cts where addStatus :: ConnId -> Contact -> [(AgentConnId, NetworkStatus)] -> [(AgentConnId, NetworkStatus)] @@ -3708,44 +3702,44 @@ subscribeUserConnections vr onlyNeeded agentBatchSubscribe user = do e -> show e -- TODO possibly below could be replaced with less noisy events for API contactLinkSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId UserContact -> CM () - contactLinkSubsToView rs = toView . CRUserContactSubSummary user . map (uncurry UserContactSubStatus) . resultsFor rs + contactLinkSubsToView rs = toView . CEvtUserContactSubSummary user . map (uncurry UserContactSubStatus) . resultsFor rs groupSubsToView :: Map ConnId (Either AgentErrorType ()) -> [ShortGroup] -> Map ConnId ShortGroupMember -> Bool -> CM () groupSubsToView rs gs ms ce = do mapM_ groupSub $ sortOn (\(ShortGroup ShortGroupInfo {groupName = g} _) -> g) gs - toView . CRMemberSubSummary user $ map (uncurry MemberSubStatus) mRs + toViewTE . TEMemberSubSummary user $ map (uncurry MemberSubStatus) mRs where mRs = resultsFor rs ms groupSub :: ShortGroup -> CM () groupSub (ShortGroup g@ShortGroupInfo {groupId = gId, membershipStatus} members) = do - when ce $ mapM_ (toView . uncurry (CRMemberSubError user g)) mErrors - toView groupEvent + when ce $ mapM_ (toViewTE . uncurry (TEMemberSubError user g)) mErrors + toViewTE groupEvent where mErrors :: [(ShortGroupMember, ChatError)] mErrors = sortOn (\(ShortGroupMember {memberName = n}, _) -> n) . filterErrors $ filter (\(ShortGroupMember {groupId}, _) -> groupId == gId) mRs - groupEvent :: ChatResponse + groupEvent :: TerminalEvent groupEvent - | membershipStatus == GSMemInvited = CRGroupInvitation user g - | null members = CRGroupEmpty user g - | otherwise = CRGroupSubscribed user g + | membershipStatus == GSMemInvited = TEGroupInvitation user g + | null members = TEGroupEmpty user g + | otherwise = TEGroupSubscribed user g sndFileSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId SndFileTransfer -> CM () sndFileSubsToView rs sfts = do let sftRs = resultsFor rs sfts forM_ sftRs $ \(ft@SndFileTransfer {fileId, fileStatus}, err_) -> do - forM_ err_ $ toView . CRSndFileSubError user ft + forM_ err_ $ toViewTE . TESndFileSubError user ft void . forkIO $ do threadDelay 1000000 when (fileStatus == FSConnected) . unlessM (isFileActive fileId sndFiles) . withChatLock "subscribe sendFileChunk" $ sendFileChunk user ft rcvFileSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId RcvFileTransfer -> CM () - rcvFileSubsToView rs = mapM_ (toView . uncurry (CRRcvFileSubError user)) . filterErrors . resultsFor rs + rcvFileSubsToView rs = mapM_ (toViewTE . uncurry (TERcvFileSubError user)) . filterErrors . resultsFor rs pendingConnSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId PendingContactConnection -> CM () - pendingConnSubsToView rs = toView . CRPendingSubSummary user . map (uncurry PendingSubStatus) . resultsFor rs + pendingConnSubsToView rs = toViewTE . TEPendingSubSummary user . map (uncurry PendingSubStatus) . resultsFor rs withStore_ :: (DB.Connection -> User -> IO [a]) -> CM [a] - withStore_ a = withStore' (`a` user) `catchChatError` \e -> toView (CRChatError (Just user) e) $> [] + withStore_ a = withStore' (`a` user) `catchChatError` \e -> toView (CEvtChatError (Just user) e) $> [] filterErrors :: [(a, Maybe ChatError)] -> [(a, ChatError)] filterErrors = mapMaybe (\(a, e_) -> (a,) <$> e_) resultsFor :: Map ConnId (Either AgentErrorType ()) -> Map ConnId a -> [(a, Maybe ChatError)] @@ -3767,28 +3761,28 @@ cleanupManager = do liftIO $ threadDelay' initialDelay stepDelay <- asks (cleanupManagerStepDelay . config) forever $ do - flip catchChatError (toView . CRChatError Nothing) $ do + flip catchChatError (toView . CEvtChatError Nothing) $ do lift waitChatStartedAndActivated users <- withStore' getUsers let (us, us') = partition activeUser users forM_ us $ cleanupUser interval stepDelay forM_ us' $ cleanupUser interval stepDelay - cleanupMessages `catchChatError` (toView . CRChatError Nothing) + cleanupMessages `catchChatError` (toView . CEvtChatError Nothing) -- TODO possibly, also cleanup async commands - cleanupProbes `catchChatError` (toView . CRChatError Nothing) + cleanupProbes `catchChatError` (toView . CEvtChatError Nothing) liftIO $ threadDelay' $ diffToMicroseconds interval where - runWithoutInitialDelay cleanupInterval = flip catchChatError (toView . CRChatError Nothing) $ do + runWithoutInitialDelay cleanupInterval = flip catchChatError (toView . CEvtChatError Nothing) $ do lift waitChatStartedAndActivated users <- withStore' getUsers let (us, us') = partition activeUser users - forM_ us $ \u -> cleanupTimedItems cleanupInterval u `catchChatError` (toView . CRChatError (Just u)) - forM_ us' $ \u -> cleanupTimedItems cleanupInterval u `catchChatError` (toView . CRChatError (Just u)) + forM_ us $ \u -> cleanupTimedItems cleanupInterval u `catchChatError` (toView . CEvtChatError (Just u)) + forM_ us' $ \u -> cleanupTimedItems cleanupInterval u `catchChatError` (toView . CEvtChatError (Just u)) cleanupUser cleanupInterval stepDelay user = do - cleanupTimedItems cleanupInterval user `catchChatError` (toView . CRChatError (Just user)) + cleanupTimedItems cleanupInterval user `catchChatError` (toView . CEvtChatError (Just user)) liftIO $ threadDelay' stepDelay -- TODO remove in future versions: legacy step - contacts are no longer marked as deleted - cleanupDeletedContacts user `catchChatError` (toView . CRChatError (Just user)) + cleanupDeletedContacts user `catchChatError` (toView . CEvtChatError (Just user)) liftIO $ threadDelay' stepDelay cleanupTimedItems cleanupInterval user = do ts <- liftIO getCurrentTime @@ -3800,7 +3794,7 @@ cleanupManager = do contacts <- withStore' $ \db -> getDeletedContacts db vr user forM_ contacts $ \ct -> withStore (\db -> deleteContactWithoutGroups db user ct) - `catchChatError` (toView . CRChatError (Just user)) + `catchChatError` (toView . CEvtChatError (Just user)) cleanupMessages = do ts <- liftIO getCurrentTime let cutoffTs = addUTCTime (-(30 * nominalDay)) ts @@ -3826,7 +3820,7 @@ expireChatItems user@User {userId} globalTTL sync = do loop :: [Int64] -> (Int64 -> CM ()) -> CM () loop [] _ = pure () loop (a : as) process = continue $ do - process a `catchChatError` (toView . CRChatError (Just user)) + process a `catchChatError` (toView . CEvtChatError (Just user)) loop as process continue :: CM () -> CM () continue a = diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs index dca3a7f678..a2c8ae74b2 100644 --- a/src/Simplex/Chat/Library/Internal.hs +++ b/src/Simplex/Chat/Library/Internal.hs @@ -188,7 +188,7 @@ toggleNtf :: User -> GroupMember -> Bool -> CM () toggleNtf user m ntfOn = when (memberActive m) $ forM_ (memberConnId m) $ \connId -> - withAgent (\a -> toggleConnectionNtfs a connId ntfOn) `catchChatError` (toView . CRChatError (Just user)) + withAgent (\a -> toggleConnectionNtfs a connId ntfOn) `catchChatError` (toView . CEvtChatError (Just user)) prepareGroupMsg :: DB.Connection -> User -> GroupInfo -> MsgContent -> Map MemberName MsgMention -> Maybe ChatItemId -> Maybe CIForwardedFrom -> Maybe FileInvitation -> Maybe CITimed -> Bool -> ExceptT StoreError IO (ChatMsgEvent 'Json, Maybe (CIQuote 'CTGroup)) prepareGroupMsg db user g@GroupInfo {membership} mc mentions quotedItemId_ itemForwarded fInv_ timed_ live = case (quotedItemId_, itemForwarded) of @@ -441,25 +441,25 @@ deleteFilesLocally files = withFilesFolder :: (FilePath -> CM ()) -> CM () withFilesFolder action = asks filesFolder >>= readTVarIO >>= mapM_ action -deleteDirectCIs :: User -> Contact -> [CChatItem 'CTDirect] -> Bool -> Bool -> CM ChatResponse -deleteDirectCIs user ct items byUser timed = do +deleteDirectCIs :: User -> Contact -> [CChatItem 'CTDirect] -> CM [ChatItemDeletion] +deleteDirectCIs user ct items = do let ciFilesInfo = mapMaybe (\(CChatItem _ ChatItem {file}) -> mkCIFileInfo <$> file) items deleteCIFiles user ciFilesInfo (errs, deletions) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (deleteItem db) items) - unless (null errs) $ toView $ CRChatErrors (Just user) errs - pure $ CRChatItemsDeleted user deletions byUser timed + unless (null errs) $ toView $ CEvtChatErrors (Just user) errs + pure deletions where deleteItem db (CChatItem md ci) = do deleteDirectChatItem db user ct ci pure $ contactDeletion md ct ci Nothing -deleteGroupCIs :: User -> GroupInfo -> [CChatItem 'CTGroup] -> Bool -> Bool -> Maybe GroupMember -> UTCTime -> CM ChatResponse -deleteGroupCIs user gInfo items byUser timed byGroupMember_ deletedTs = do +deleteGroupCIs :: User -> GroupInfo -> [CChatItem 'CTGroup] -> Maybe GroupMember -> UTCTime -> CM [ChatItemDeletion] +deleteGroupCIs user gInfo items byGroupMember_ deletedTs = do let ciFilesInfo = mapMaybe (\(CChatItem _ ChatItem {file}) -> mkCIFileInfo <$> file) items deleteCIFiles user ciFilesInfo (errs, deletions) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (deleteItem db) items) - unless (null errs) $ toView $ CRChatErrors (Just user) errs - pure $ CRChatItemsDeleted user deletions byUser timed + unless (null errs) $ toView $ CEvtChatErrors (Just user) errs + pure deletions where deleteItem :: DB.Connection -> CChatItem 'CTGroup -> IO ChatItemDeletion deleteItem db (CChatItem md ci) = do @@ -491,7 +491,7 @@ deleteLocalCIs user nf items byUser timed = do let ciFilesInfo = mapMaybe (\(CChatItem _ ChatItem {file}) -> mkCIFileInfo <$> file) items deleteFilesLocally ciFilesInfo (errs, deletions) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (deleteItem db) items) - unless (null errs) $ toView $ CRChatErrors (Just user) errs + unless (null errs) $ toView $ CEvtChatErrors (Just user) errs pure $ CRChatItemsDeleted user deletions byUser timed where deleteItem db (CChatItem md ci) = do @@ -505,25 +505,26 @@ deleteCIFiles user filesInfo = do cancelFilesInProgress user filesInfo deleteFilesLocally filesInfo -markDirectCIsDeleted :: User -> Contact -> [CChatItem 'CTDirect] -> Bool -> UTCTime -> CM ChatResponse -markDirectCIsDeleted user ct items byUser deletedTs = do +markDirectCIsDeleted :: User -> Contact -> [CChatItem 'CTDirect] -> UTCTime -> CM [ChatItemDeletion] +markDirectCIsDeleted user ct items deletedTs = do let ciFilesInfo = mapMaybe (\(CChatItem _ ChatItem {file}) -> mkCIFileInfo <$> file) items cancelFilesInProgress user ciFilesInfo (errs, deletions) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (markDeleted db) items) - unless (null errs) $ toView $ CRChatErrors (Just user) errs - pure $ CRChatItemsDeleted user deletions byUser False + unless (null errs) $ toView $ CEvtChatErrors (Just user) errs + pure deletions where markDeleted db (CChatItem md ci) = do ci' <- markDirectChatItemDeleted db user ct ci deletedTs pure $ contactDeletion md ct ci (Just ci') -markGroupCIsDeleted :: User -> GroupInfo -> [CChatItem 'CTGroup] -> Bool -> Maybe GroupMember -> UTCTime -> CM ChatResponse -markGroupCIsDeleted user gInfo items byUser byGroupMember_ deletedTs = do +markGroupCIsDeleted :: User -> GroupInfo -> [CChatItem 'CTGroup] -> Maybe GroupMember -> UTCTime -> CM [ChatItemDeletion] +markGroupCIsDeleted user gInfo items byGroupMember_ deletedTs = do let ciFilesInfo = mapMaybe (\(CChatItem _ ChatItem {file}) -> mkCIFileInfo <$> file) items cancelFilesInProgress user ciFilesInfo (errs, deletions) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (markDeleted db) items) - unless (null errs) $ toView $ CRChatErrors (Just user) errs - pure $ CRChatItemsDeleted user deletions byUser False + unless (null errs) $ toView $ CEvtChatErrors (Just user) errs + pure deletions + -- pure $ CRChatItemsDeleted user deletions byUser False where markDeleted db (CChatItem md ci) = do ci' <- markGroupChatItemDeleted db user gInfo ci byGroupMember_ deletedTs @@ -569,7 +570,7 @@ updateCallItemStatus user ct@Contact {contactId} Call {chatItemId} receivedStatu updateDirectChatItemView :: User -> Contact -> ChatItemId -> ACIContent -> Bool -> Bool -> Maybe CITimed -> Maybe MessageId -> CM () updateDirectChatItemView user ct chatItemId (ACIContent msgDir ciContent) edited live timed_ msgId_ = do ci' <- withStore $ \db -> updateDirectChatItem db user ct chatItemId ciContent edited live timed_ msgId_ - toView $ CRChatItemUpdated user (AChatItem SCTDirect msgDir (DirectChat ct) ci') + toView $ CEvtChatItemUpdated user (AChatItem SCTDirect msgDir (DirectChat ct) ci') callStatusItemContent :: User -> Contact -> ChatItemId -> WebRTCCallStatus -> CM (Maybe ACIContent) callStatusItemContent user Contact {contactId} chatItemId receivedStatus = do @@ -617,11 +618,25 @@ receiveFile' :: User -> RcvFileTransfer -> Bool -> Maybe Bool -> Maybe FilePath receiveFile' user ft userApprovedRelays rcvInline_ filePath_ = do (CRRcvFileAccepted user <$> acceptFileReceive user ft userApprovedRelays rcvInline_ filePath_) `catchChatError` processError where - processError = \case - -- TODO AChatItem in Cancelled events - ChatErrorAgent (SMP _ SMP.AUTH) _ -> pure $ CRRcvFileAcceptedSndCancelled user ft - ChatErrorAgent (CONN DUPLICATE) _ -> pure $ CRRcvFileAcceptedSndCancelled user ft - e -> throwError e + -- TODO AChatItem in Cancelled events + processError e + | rctFileCancelled e = pure $ CRRcvFileAcceptedSndCancelled user ft + | otherwise = throwError e + +receiveFileEvt' :: User -> RcvFileTransfer -> Bool -> Maybe Bool -> Maybe FilePath -> CM ChatEvent +receiveFileEvt' user ft userApprovedRelays rcvInline_ filePath_ = do + (CEvtRcvFileAccepted user <$> acceptFileReceive user ft userApprovedRelays rcvInline_ filePath_) `catchChatError` processError + where + -- TODO AChatItem in Cancelled events + processError e + | rctFileCancelled e = pure $ CEvtRcvFileAcceptedSndCancelled user ft + | otherwise = throwError e + +rctFileCancelled :: ChatError -> Bool +rctFileCancelled = \case + ChatErrorAgent (SMP _ SMP.AUTH) _ -> True + ChatErrorAgent (CONN DUPLICATE) _ -> True + _ -> False acceptFileReceive :: User -> RcvFileTransfer -> Bool -> Maybe Bool -> Maybe FilePath -> CM AChatItem acceptFileReceive user@User {userId} RcvFileTransfer {fileId, xftpRcvFile, fileInvitation = FileInvitation {fileName = fName, fileConnReq, fileInline, fileSize}, fileStatus, grpMemberId, cryptoArgs} userApprovedRelays rcvInline_ filePath_ = do @@ -728,7 +743,7 @@ receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} aci_ <- resetRcvCIFileStatus user fileId CIFSRcvInvitation forM_ aci_ $ \aci -> do cleanupACIFile aci - toView $ CRChatItemUpdated user aci + toView $ CEvtChatItemUpdated user aci throwChatError $ CEFileNotApproved fileId unknownSrvs cleanupACIFile :: AChatItem -> CM () @@ -782,7 +797,7 @@ startReceivingFile user fileId = do liftIO $ updateRcvFileStatus db fileId FSConnected liftIO $ updateCIFileStatus db user fileId $ CIFSRcvTransfer 0 1 getChatItemByFileId db vr user fileId - toView $ CRRcvFileStart user ci + toView $ CEvtRcvFileStart user ci getRcvFilePath :: FileTransferId -> Maybe FilePath -> String -> Bool -> CM FilePath getRcvFilePath fileId fPath_ fn keepHandle = case fPath_ of @@ -983,7 +998,7 @@ introduceToGroup vr user gInfo@GroupInfo {groupId, membership} m@GroupMember {ac forM_ (L.nonEmpty events) $ \events' -> sendGroupMemberMessages user conn events' groupId else forM_ shuffledIntros $ \intro -> - processIntro intro `catchChatError` (toView . CRChatError (Just user)) + processIntro intro `catchChatError` (toView . CEvtChatError (Just user)) memberIntro :: GroupMember -> ChatMsgEvent 'Json memberIntro reMember = let mInfo = memberInfo reMember @@ -1006,7 +1021,7 @@ introduceToGroup vr user gInfo@GroupInfo {groupId, membership} m@GroupMember {ac (errs, items) <- partitionEithers <$> withStore' (\db -> getGroupHistoryItems db user gInfo m 100) (errs', events) <- partitionEithers <$> mapM (tryChatError . itemForwardEvents) items let errors = map ChatErrorStore errs <> errs' - unless (null errors) $ toView $ CRChatErrors (Just user) errors + unless (null errors) $ toView $ CEvtChatErrors (Just user) errors let events' = maybe (concat events) (\x -> concat events <> [x]) descrEvent_ forM_ (L.nonEmpty events') $ \events'' -> sendGroupMemberMessages user conn events'' groupId @@ -1140,12 +1155,14 @@ deleteTimedItem user (ChatRef cType chatId, itemId) deleteAt = do case cType of CTDirect -> do (ct, ci) <- withStore $ \db -> (,) <$> getContact db vr user chatId <*> getDirectChatItem db user chatId itemId - deleteDirectCIs user ct [ci] True True >>= toView + deletions <- deleteDirectCIs user ct [ci] + toView $ CEvtChatItemsDeleted user deletions True True CTGroup -> do (gInfo, ci) <- withStore $ \db -> (,) <$> getGroupInfo db vr user chatId <*> getGroupChatItem db user chatId itemId deletedTs <- liftIO getCurrentTime - deleteGroupCIs user gInfo [ci] True True Nothing deletedTs >>= toView - _ -> toView . CRChatError (Just user) . ChatError $ CEInternalError "bad deleteTimedItem cType" + deletions <- deleteGroupCIs user gInfo [ci] Nothing deletedTs + toView $ CEvtChatItemsDeleted user deletions True True + _ -> toView . CEvtChatError (Just user) . ChatError $ CEInternalError "bad deleteTimedItem cType" startUpdatedTimedItemThread :: User -> ChatRef -> ChatItem c d -> ChatItem c d -> CM () startUpdatedTimedItemThread user chatRef ci ci' = @@ -1169,7 +1186,7 @@ createContactPQSndItem user ct conn@Connection {pqSndEnabled} pqSndEnabled' = ct' = ct {activeConn = Just conn'} :: Contact when (contactPQEnabled ct /= contactPQEnabled ct') $ do createInternalChatItem user (CDDirectSnd ct') ciContent Nothing - toView $ CRContactPQEnabled user ct' pqSndEnabled' + toView $ CEvtContactPQEnabled user ct' pqSndEnabled' pure (ct', conn') updateContactPQRcv :: User -> Contact -> Connection -> PQEncryption -> CM (Contact, Connection) @@ -1185,7 +1202,7 @@ updateContactPQRcv user ct conn@Connection {connId, pqRcvEnabled} pqRcvEnabled' ct' = ct {activeConn = Just conn'} :: Contact when (contactPQEnabled ct /= contactPQEnabled ct') $ do createInternalChatItem user (CDDirectRcv ct') ciContent Nothing - toView $ CRContactPQEnabled user ct' pqRcvEnabled' + toView $ CEvtContactPQEnabled user ct' pqRcvEnabled' pure (ct', conn') updatePeerChatVRange :: Connection -> VersionRangeChat -> CM Connection @@ -1270,7 +1287,7 @@ sendFileChunk user ft@SndFileTransfer {fileId, fileStatus, agentConnId = AgentCo liftIO $ updateSndFileStatus db ft FSComplete liftIO $ deleteSndFileChunks db ft updateDirectCIFileStatus db vr user fileId CIFSSndComplete - toView $ CRSndFileComplete user ci ft + toView $ CEvtSndFileComplete user ci ft lift $ closeFileHandle fileId sndFiles deleteAgentConnectionAsync user acId @@ -1320,7 +1337,7 @@ appendFileChunk ft@RcvFileTransfer {fileId, fileStatus, cryptoArgs, fileInvitati removeFile fsFilePath `catchChatError` \_ -> pure () renameFile tmpFile fsFilePath Left e -> do - toView $ CRChatError Nothing e + toView $ CEvtChatError Nothing e removeFile tmpFile `catchChatError` \_ -> pure () withStore' (`removeFileCryptoArgs` fileId) where @@ -1345,7 +1362,7 @@ isFileActive fileId files = do cancelRcvFileTransfer :: User -> RcvFileTransfer -> CM (Maybe ConnId) cancelRcvFileTransfer user ft@RcvFileTransfer {fileId, xftpRcvFile, rcvFileInline} = - cancel' `catchChatError` (\e -> toView (CRChatError (Just user) e) $> fileConnId) + cancel' `catchChatError` (\e -> toView (CEvtChatError (Just user) e) $> fileConnId) where cancel' = do lift $ closeFileHandle fileId rcvFiles @@ -1363,13 +1380,13 @@ cancelRcvFileTransfer user ft@RcvFileTransfer {fileId, xftpRcvFile, rcvFileInlin cancelSndFile :: User -> FileTransferMeta -> [SndFileTransfer] -> Bool -> CM [ConnId] cancelSndFile user FileTransferMeta {fileId, xftpSndFile} fts sendCancel = do withStore' (\db -> updateFileCancelled db user fileId CIFSSndCancelled) - `catchChatError` (toView . CRChatError (Just user)) + `catchChatError` (toView . CEvtChatError (Just user)) case xftpSndFile of Nothing -> catMaybes <$> forM fts (\ft -> cancelSndFileTransfer user ft sendCancel) Just xsf -> do forM_ fts (\ft -> cancelSndFileTransfer user ft False) - lift (agentXFTPDeleteSndFileRemote user xsf fileId) `catchChatError` (toView . CRChatError (Just user)) + lift (agentXFTPDeleteSndFileRemote user xsf fileId) `catchChatError` (toView . CEvtChatError (Just user)) pure [] -- TODO v6.0 remove @@ -1377,7 +1394,7 @@ cancelSndFileTransfer :: User -> SndFileTransfer -> Bool -> CM (Maybe ConnId) cancelSndFileTransfer user@User {userId} ft@SndFileTransfer {fileId, connId, agentConnId = AgentConnId acId, fileStatus, fileInline} sendCancel = if fileStatus == FSCancelled || fileStatus == FSComplete then pure Nothing - else cancel' `catchChatError` (\e -> toView (CRChatError (Just user) e) $> fileConnId) + else cancel' `catchChatError` (\e -> toView (CEvtChatError (Just user) e) $> fileConnId) where cancel' = do withStore' $ \db -> do @@ -1498,7 +1515,7 @@ sendGroupMemberMessages user conn events groupId = do when (connDisabled conn) $ throwChatError (CEConnectionDisabled conn) let idsEvts = L.map (GroupId groupId,) events (errs, msgs) <- lift $ partitionEithers . L.toList <$> createSndMessages idsEvts - unless (null errs) $ toView $ CRChatErrors (Just user) errs + unless (null errs) $ toView $ CEvtChatErrors (Just user) errs forM_ (L.nonEmpty msgs) $ \msgs' -> batchSendConnMessages user conn MsgFlags {notification = True} msgs' @@ -1627,7 +1644,7 @@ sendGroupMessages :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> Non sendGroupMessages user gInfo members events = do -- TODO [knocking] when sending to all, send profile update to pending approval members too, then filter for next step? when shouldSendProfileUpdate $ - sendProfileUpdate `catchChatError` (toView . CRChatError (Just user)) + sendProfileUpdate `catchChatError` (toView . CEvtChatError (Just user)) sendGroupMessages_ user gInfo members events where User {profile = p, userMemberProfileUpdatedAt} = user @@ -1786,7 +1803,7 @@ memberSendAction gInfo events members m@GroupMember {memberRole, memberStatus} = sendGroupMemberMessage :: MsgEncodingI e => User -> GroupInfo -> GroupMember -> ChatMsgEvent e -> Maybe Int64 -> CM () -> CM () sendGroupMemberMessage user gInfo@GroupInfo {groupId} m@GroupMember {groupMemberId} chatMsgEvent introId_ postDeliver = do msg <- createSndMessage chatMsgEvent (GroupId groupId) - messageMember msg `catchChatError` (toView . CRChatError (Just user)) + messageMember msg `catchChatError` (toView . CEvtChatError (Just user)) where messageMember :: SndMessage -> CM () messageMember SndMessage {msgId, msgBody} = forM_ (memberSendAction gInfo (chatMsgEvent :| []) [m] m) $ \case @@ -1850,7 +1867,7 @@ saveGroupFwdRcvMsg user groupId forwardingMember refAuthorMember@GroupMember {me if sameMemberId refMemberId am then forM_ (memberConn forwardingMember) $ \fmConn -> void $ sendDirectMemberMessage fmConn (XGrpMemCon amMemberId) groupId - else toView $ CRMessageError user "error" "saveGroupFwdRcvMsg: referenced author member id doesn't match message member id" + else toView $ CEvtMessageError user "error" "saveGroupFwdRcvMsg: referenced author member id doesn't match message member id" throwError e _ -> throwError e @@ -1974,7 +1991,7 @@ deleteAgentConnectionAsync user acId = deleteAgentConnectionAsync' user acId Fal deleteAgentConnectionAsync' :: User -> ConnId -> Bool -> CM () deleteAgentConnectionAsync' user acId waitDelivery = do - withAgent (\a -> deleteConnectionAsync a waitDelivery acId) `catchChatError` (toView . CRChatError (Just user)) + withAgent (\a -> deleteConnectionAsync a waitDelivery acId) `catchChatError` (toView . CEvtChatError (Just user)) deleteAgentConnectionsAsync :: User -> [ConnId] -> CM () deleteAgentConnectionsAsync user acIds = deleteAgentConnectionsAsync' user acIds False @@ -1982,7 +1999,7 @@ deleteAgentConnectionsAsync user acIds = deleteAgentConnectionsAsync' user acIds deleteAgentConnectionsAsync' :: User -> [ConnId] -> Bool -> CM () deleteAgentConnectionsAsync' _ [] _ = pure () deleteAgentConnectionsAsync' user acIds waitDelivery = do - withAgent (\a -> deleteConnectionsAsync a waitDelivery acIds) `catchChatError` (toView . CRChatError (Just user)) + withAgent (\a -> deleteConnectionsAsync a waitDelivery acIds) `catchChatError` (toView . CEvtChatError (Just user)) agentXFTPDeleteRcvFile :: RcvFileId -> FileTransferId -> CM () agentXFTPDeleteRcvFile aFileId fileId = do @@ -2083,8 +2100,8 @@ createContactsFeatureItems :: createContactsFeatureItems user cts chatDir ciFeature ciOffer getPref = do let dirsCIContents = map contactChangedFeatures cts (errs, acis) <- partitionEithers <$> createInternalItemsForChats user Nothing dirsCIContents - unless (null errs) $ toView' $ CRChatErrors (Just user) errs - toView' $ CRNewChatItems user acis + unless (null errs) $ toView' $ CEvtChatErrors (Just user) errs + toView' $ CEvtNewChatItems user acis where contactChangedFeatures :: (Contact, Contact) -> (ChatDirection 'CTDirect d, [CIContent d]) contactChangedFeatures (Contact {mergedPreferences = cups}, ct'@Contact {mergedPreferences = cups'}) = do @@ -2129,7 +2146,7 @@ createGroupFeatureItems user cd ciContent GroupInfo {fullGroupPreferences} = createInternalChatItem :: (ChatTypeI c, MsgDirectionI d) => User -> ChatDirection c d -> CIContent d -> Maybe UTCTime -> CM () createInternalChatItem user cd content itemTs_ = lift (createInternalItemsForChats user itemTs_ [(cd, [content])]) >>= \case - [Right aci] -> toView $ CRNewChatItems user [aci] + [Right aci] -> toView $ CEvtNewChatItems user [aci] [Left e] -> throwError e rs -> throwChatError $ CEInternalError $ "createInternalChatItem: expected 1 result, got " <> show (length rs) @@ -2165,7 +2182,7 @@ createLocalChatItems :: createLocalChatItems user cd itemsData createdAt = do withStore' $ \db -> updateChatTs db user cd createdAt (errs, items) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (createItem db) $ L.toList itemsData) - unless (null errs) $ toView $ CRChatErrors (Just user) errs + unless (null errs) $ toView $ CEvtChatErrors (Just user) errs pure items where createItem :: DB.Connection -> (CIContent 'MDSnd, Maybe (CIFile 'MDSnd), Maybe CIForwardedFrom, (Text, Maybe MarkdownList)) -> IO (ChatItem 'CTLocal 'MDSnd) @@ -2254,5 +2271,5 @@ timeItToView s action = do a <- action t2 <- liftIO getCurrentTime let diff = diffToMilliseconds $ diffUTCTime t2 t1 - toView' $ CRTimedAction s diff + toView' $ CEvtTimedAction s diff pure a diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index 38d67aa150..53aee8938f 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -90,20 +90,20 @@ smallGroupsRcptsMemLimit = 20 processAgentMessage :: ACorrId -> ConnId -> AEvent 'AEConn -> CM () processAgentMessage _ _ (DEL_RCVQS delQs) = - toView $ CRAgentRcvQueuesDeleted $ L.map rcvQ delQs + toView $ CEvtAgentRcvQueuesDeleted $ L.map rcvQ delQs where rcvQ (connId, server, rcvId, err_) = DeletedRcvQueue (AgentConnId connId) server (AgentQueueId rcvId) err_ processAgentMessage _ _ (DEL_CONNS connIds) = - toView $ CRAgentConnsDeleted $ L.map AgentConnId connIds + toView $ CEvtAgentConnsDeleted $ L.map AgentConnId connIds processAgentMessage _ "" (ERR e) = - toView $ CRChatError Nothing $ ChatErrorAgent e Nothing + toView $ CEvtChatError Nothing $ ChatErrorAgent e Nothing processAgentMessage corrId connId msg = do lockEntity <- critical (withStore (`getChatLockEntity` AgentConnId connId)) withEntityLock "processAgentMessage" lockEntity $ do vr <- chatVersionRange -- getUserByAConnId never throws logical errors, only SEDBBusyError can be thrown here critical (withStore' (`getUserByAConnId` AgentConnId connId)) >>= \case - Just user -> processAgentMessageConn vr user corrId connId msg `catchChatError` (toView . CRChatError (Just user)) + Just user -> processAgentMessageConn vr user corrId connId msg `catchChatError` (toView . CEvtChatError (Just user)) _ -> throwChatError $ CENoConnectionUser (AgentConnId connId) -- CRITICAL error will be shown to the user as alert with restart button in Android/desktop apps. @@ -121,22 +121,22 @@ critical a = processAgentMessageNoConn :: AEvent 'AENone -> CM () processAgentMessageNoConn = \case - CONNECT p h -> hostEvent $ CRHostConnected p h - DISCONNECT p h -> hostEvent $ CRHostDisconnected p h - DOWN srv conns -> serverEvent srv conns NSDisconnected CRContactsDisconnected - UP srv conns -> serverEvent srv conns NSConnected CRContactsSubscribed - SUSPENDED -> toView CRChatSuspended - DEL_USER agentUserId -> toView $ CRAgentUserDeleted agentUserId + CONNECT p h -> hostEvent $ CEvtHostConnected p h + DISCONNECT p h -> hostEvent $ CEvtHostDisconnected p h + DOWN srv conns -> serverEvent srv conns NSDisconnected CEvtContactsDisconnected + UP srv conns -> serverEvent srv conns NSConnected CEvtContactsSubscribed + SUSPENDED -> toView CEvtChatSuspended + DEL_USER agentUserId -> toView $ CEvtAgentUserDeleted agentUserId ERRS cErrs -> errsEvent cErrs where - hostEvent :: ChatResponse -> CM () + hostEvent :: ChatEvent -> CM () hostEvent = whenM (asks $ hostEvents . config) . toView serverEvent srv conns nsStatus event = do chatModifyVar connNetworkStatuses $ \m -> foldl' (\m' cId -> M.insert cId nsStatus m') m connIds ifM (asks $ coreApi . config) (notifyAPI connIds) notifyCLI where connIds = map AgentConnId conns - notifyAPI = toView . CRNetworkStatus nsStatus + notifyAPI = toView . CEvtNetworkStatus nsStatus notifyCLI = do cs <- withStore' (`getConnectionsContacts` conns) toView $ event srv cs @@ -144,7 +144,7 @@ processAgentMessageNoConn = \case errsEvent cErrs = do vr <- chatVersionRange errs <- lift $ rights <$> withStoreBatch' (\db -> map (getChatErr vr db) cErrs) - toView $ CRChatErrors Nothing errs + toView $ CEvtChatErrors Nothing errs where getChatErr :: VersionRangeChat -> DB.Connection -> (ConnId, AgentErrorType) -> IO ChatError getChatErr vr db (connId, err) = @@ -156,7 +156,7 @@ processAgentMsgSndFile _corrId aFileId msg = do (cRef_, fileId) <- withStore (`getXFTPSndFileDBIds` AgentSndFileId aFileId) withEntityLock_ cRef_ . withFileLock "processAgentMsgSndFile" fileId $ withStore' (`getUserByASndFileId` AgentSndFileId aFileId) >>= \case - Just user -> process user fileId `catchChatError` (toView . CRChatError (Just user)) + Just user -> process user fileId `catchChatError` (toView . CEvtChatError (Just user)) _ -> do lift $ withAgent' (`xftpDeleteSndFileInternal` aFileId) throwChatError $ CENoSndFileUser $ AgentSndFileId aFileId @@ -176,7 +176,7 @@ processAgentMsgSndFile _corrId aFileId msg = do ci <- withStore $ \db -> do liftIO $ updateCIFileStatus db user fileId status lookupChatItemByFileId db vr user fileId - toView $ CRSndFileProgressXFTP user ci ft sndProgress sndTotal + toView $ CEvtSndFileProgressXFTP user ci ft sndProgress sndTotal SFDONE sndDescr rfds -> do withStore' $ \db -> setSndFTPrivateSndDescr db user fileId (fileDescrText sndDescr) ci <- withStore $ \db -> lookupChatItemByFileId db vr user fileId @@ -188,18 +188,18 @@ processAgentMsgSndFile _corrId aFileId msg = do [] -> sendFileError (FileErrOther "no receiver descriptions") "no receiver descriptions" vr ft rfd : _ -> case [fd | fd@(FD.ValidFileDescription FD.FileDescription {chunks = [_]}) <- rfds] of [] -> case xftpRedirectFor of - Nothing -> xftpSndFileRedirect user fileId rfd >>= toView . CRSndFileRedirectStartXFTP user ft + Nothing -> xftpSndFileRedirect user fileId rfd >>= toView . CEvtSndFileRedirectStartXFTP user ft Just _ -> sendFileError (FileErrOther "chaining redirects") "Prohibit chaining redirects" vr ft rfds' -> do -- we have 1 chunk - use it as URI whether it is redirect or not ft' <- maybe (pure ft) (\fId -> withStore $ \db -> getFileTransferMeta db user fId) xftpRedirectFor - toView $ CRSndStandaloneFileComplete user ft' $ map (decodeLatin1 . strEncode . FD.fileDescriptionURI) rfds' + toView $ CEvtSndStandaloneFileComplete user ft' $ map (decodeLatin1 . strEncode . FD.fileDescriptionURI) rfds' Just (AChatItem _ d cInfo _ci@ChatItem {meta = CIMeta {itemSharedMsgId = msgId_, itemDeleted}}) -> case (msgId_, itemDeleted) of (Just sharedMsgId, Nothing) -> do when (length rfds < length sfts) $ throwChatError $ CEInternalError "not enough XFTP file descriptions to send" -- TODO either update database status or move to SFPROG - toView $ CRSndFileProgressXFTP user ci ft 1 1 + toView $ CEvtSndFileProgressXFTP user ci ft 1 1 case (rfds, sfts, d, cInfo) of (rfd : extraRFDs, sft : _, SMDSnd, DirectChat ct) -> do withStore' $ \db -> createExtraSndFTDescrs db user fileId (map fileDescrText extraRFDs) @@ -208,9 +208,9 @@ processAgentMsgSndFile _corrId aFileId msg = do Just rs -> case L.last rs of Right ([msgDeliveryId], _) -> withStore' $ \db -> updateSndFTDeliveryXFTP db sft msgDeliveryId - Right (deliveryIds, _) -> toView $ CRChatError (Just user) $ ChatError $ CEInternalError $ "SFDONE, sendFileDescriptions: expected 1 delivery id, got " <> show (length deliveryIds) - Left e -> toView $ CRChatError (Just user) e - Nothing -> toView $ CRChatError (Just user) $ ChatError $ CEInternalError "SFDONE, sendFileDescriptions: expected at least 1 result" + Right (deliveryIds, _) -> toView $ CEvtChatError (Just user) $ ChatError $ CEInternalError $ "SFDONE, sendFileDescriptions: expected 1 delivery id, got " <> show (length deliveryIds) + Left e -> toView $ CEvtChatError (Just user) e + Nothing -> toView $ CEvtChatError (Just user) $ ChatError $ CEInternalError "SFDONE, sendFileDescriptions: expected at least 1 result" lift $ withAgent' (`xftpDeleteSndFileInternal` aFileId) (_, _, SMDSnd, GroupChat g@GroupInfo {groupId}) -> do ms <- withStore' $ \db -> getGroupMembers db vr user g @@ -223,7 +223,7 @@ processAgentMsgSndFile _corrId aFileId msg = do liftIO $ updateCIFileStatus db user fileId CIFSSndComplete getChatItemByFileId db vr user fileId lift $ withAgent' (`xftpDeleteSndFileInternal` aFileId) - toView $ CRSndFileCompleteXFTP user ci' ft + toView $ CEvtSndFileCompleteXFTP user ci' ft where memberFTs :: [GroupMember] -> [(Connection, SndFileTransfer)] memberFTs ms = M.elems $ M.intersectionWith (,) (M.fromList mConns') (M.fromList sfts') @@ -244,7 +244,7 @@ processAgentMsgSndFile _corrId aFileId msg = do ci <- withStore $ \db -> do liftIO $ updateCIFileStatus db user fileId (CIFSSndWarning $ agentFileError e) lookupChatItemByFileId db vr user fileId - toView $ CRSndFileWarning user ci ft err + toView $ CEvtSndFileWarning user ci ft err SFERR e -> sendFileError (agentFileError e) (tshow e) vr ft where @@ -259,7 +259,7 @@ processAgentMsgSndFile _corrId aFileId msg = do let (errs, msgReqs) = partitionEithers . L.toList $ L.zipWith (fmap . toMsgReq) connsIdsEvts sndMsgs_ delivered <- mapM deliverMessages (L.nonEmpty msgReqs) let errs' = errs <> maybe [] (lefts . L.toList) delivered - unless (null errs') $ toView $ CRChatErrors (Just user) errs' + unless (null errs') $ toView $ CEvtChatErrors (Just user) errs' pure delivered where connDescrEvents :: Int -> NonEmpty (Connection, (ConnOrGroupId, ChatMsgEvent 'Json)) @@ -278,7 +278,7 @@ processAgentMsgSndFile _corrId aFileId msg = do liftIO $ updateFileCancelled db user fileId (CIFSSndError ferr) lookupChatItemByFileId db vr user fileId lift $ withAgent' (`xftpDeleteSndFileInternal` aFileId) - toView $ CRSndFileError user ci ft err + toView $ CEvtSndFileError user ci ft err agentFileError :: AgentErrorType -> FileError agentFileError = \case @@ -298,7 +298,7 @@ processAgentMsgRcvFile _corrId aFileId msg = do (cRef_, fileId) <- withStore (`getXFTPRcvFileDBIds` AgentRcvFileId aFileId) withEntityLock_ cRef_ . withFileLock "processAgentMsgRcvFile" fileId $ withStore' (`getUserByARcvFileId` AgentRcvFileId aFileId) >>= \case - Just user -> process user fileId `catchChatError` (toView . CRChatError (Just user)) + Just user -> process user fileId `catchChatError` (toView . CEvtChatError (Just user)) _ -> do lift $ withAgent' (`xftpDeleteRcvFile` aFileId) throwChatError $ CENoRcvFileUser $ AgentRcvFileId aFileId @@ -318,7 +318,7 @@ processAgentMsgRcvFile _corrId aFileId msg = do ci <- withStore $ \db -> do liftIO $ updateCIFileStatus db user fileId status lookupChatItemByFileId db vr user fileId - toView $ CRRcvFileProgressXFTP user ci rcvProgress rcvTotal ft + toView $ CEvtRcvFileProgressXFTP user ci rcvProgress rcvTotal ft RFDONE xftpPath -> case liveRcvFileTransferPath ft of Nothing -> throwChatError $ CEInternalError "no target path for received XFTP file" @@ -331,25 +331,25 @@ processAgentMsgRcvFile _corrId aFileId msg = do updateCIFileStatus db user fileId CIFSRcvComplete lookupChatItemByFileId db vr user fileId agentXFTPDeleteRcvFile aFileId fileId - toView $ maybe (CRRcvStandaloneFileComplete user fsTargetPath ft) (CRRcvFileComplete user) ci_ + toView $ maybe (CEvtRcvStandaloneFileComplete user fsTargetPath ft) (CEvtRcvFileComplete user) ci_ RFWARN e -> do ci <- withStore $ \db -> do liftIO $ updateCIFileStatus db user fileId (CIFSRcvWarning $ agentFileError e) lookupChatItemByFileId db vr user fileId - toView $ CRRcvFileWarning user ci e ft + toView $ CEvtRcvFileWarning user ci e ft RFERR e | e == FILE NOT_APPROVED -> do aci_ <- resetRcvCIFileStatus user fileId CIFSRcvAborted forM_ aci_ cleanupACIFile agentXFTPDeleteRcvFile aFileId fileId - forM_ aci_ $ \aci -> toView $ CRChatItemUpdated user aci + forM_ aci_ $ \aci -> toView $ CEvtChatItemUpdated user aci | otherwise -> do aci_ <- withStore $ \db -> do liftIO $ updateFileCancelled db user fileId (CIFSRcvError $ agentFileError e) lookupChatItemByFileId db vr user fileId forM_ aci_ cleanupACIFile agentXFTPDeleteRcvFile aFileId fileId - toView $ CRRcvFileError user aci_ e ft + toView $ CEvtRcvFileError user aci_ e ft processAgentMessageConn :: VersionRangeChat -> User -> ACorrId -> ConnId -> AEvent 'AEConn -> CM () processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = do @@ -360,9 +360,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = entity <- critical $ withStore (\db -> getConnectionEntity db vr user $ AgentConnId agentConnId) >>= updateConnStatus case agentMessage of END -> case entity of - RcvDirectMsgConnection _ (Just ct) -> toView $ CRContactAnotherClient user ct - _ -> toView $ CRSubscriptionEnd user entity - MSGNTF msgId msgTs_ -> toView $ CRNtfMessage user entity $ ntfMsgAckInfo msgId msgTs_ + RcvDirectMsgConnection _ (Just ct) -> toView $ CEvtContactAnotherClient user ct + _ -> toView $ CEvtSubscriptionEnd user entity + MSGNTF msgId msgTs_ -> toView $ CEvtNtfMessage user entity $ ntfMsgAckInfo msgId msgTs_ _ -> case entity of RcvDirectMsgConnection conn contact_ -> processDirectMessage agentMessage entity conn contact_ @@ -438,13 +438,13 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = MWARN _ err -> processConnMWARN connEntity conn err MERR _ err -> do - toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) + toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) processConnMERR connEntity conn err MERRS _ err -> do -- error cannot be AUTH error here - toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) + toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) ERR err -> do - toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) + toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () -- TODO add debugging output _ -> pure () @@ -468,11 +468,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = checkIntegrityCreateItem (CDDirectRcv ct') msgMeta `catchChatError` \_ -> pure () forM_ aChatMsgs $ \case Right (ACMsg _ chatMsg) -> - processEvent ct' conn' tags eInfo chatMsg `catchChatError` \e -> toView $ CRChatError (Just user) e + processEvent ct' conn' tags eInfo chatMsg `catchChatError` \e -> toView $ CEvtChatError (Just user) e Left e -> do atomically $ modifyTVar' tags ("error" :) logInfo $ "contact msg=error " <> eInfo <> " " <> tshow e - toView $ CRChatError (Just user) (ChatError . CEException $ "error parsing chat message: " <> e) + toView $ CEvtChatError (Just user) (ChatError . CEException $ "error parsing chat message: " <> e) checkSendRcpt ct' $ rights aChatMsgs -- not crucial to use ct'' from processEvent where aChatMsgs = parseChatMessages msgBody @@ -543,7 +543,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = incognitoProfile <- forM customUserProfileId $ \pId -> withStore (\db -> getProfileById db userId pId) let profileToSend = userProfileToSend user (fromLocalProfile <$> incognitoProfile) Nothing True allowAgentConnectionAsync user conn'' confId $ XInfo profileToSend - toView $ CRBusinessLinkConnecting user gInfo host ct + toView $ CEvtBusinessLinkConnecting user gInfo host ct _ -> messageError "CONF for existing contact must have x.grp.mem.info or x.info" INFO pqSupport connInfo -> do processINFOpqSupport conn pqSupport @@ -567,7 +567,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- [incognito] print incognito profile used for this contact incognitoProfile <- forM customUserProfileId $ \profileId -> withStore (\db -> getProfileById db userId profileId) lift $ setContactNetworkStatus ct' NSConnected - toView $ CRContactConnected user ct' (fmap fromLocalProfile incognitoProfile) + toView $ CEvtContactConnected user ct' (fmap fromLocalProfile incognitoProfile) when (directOrUsed ct') $ do createInternalChatItem user (CDDirectRcv ct') (CIRcvDirectE2EEInfo $ E2EInfo pqEnc) Nothing createFeatureEnabledItems ct' @@ -601,11 +601,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = cis <- updateDirectItemsStatus' db ct conn msgId (CISSndSent SSPComplete) liftIO $ forM cis $ \ci -> setDirectSndChatItemViaProxy db user ct ci (isJust proxy) let acis = map ctItem cis - unless (null acis) $ toView $ CRChatItemsStatusesUpdated user acis + unless (null acis) $ toView $ CEvtChatItemsStatusesUpdated user acis where ctItem = AChatItem SCTDirect SMDSnd (DirectChat ct) SWITCH qd phase cStats -> do - toView $ CRContactSwitch user ct (SwitchProgress qd phase cStats) + toView $ CEvtContactSwitch user ct (SwitchProgress qd phase cStats) when (phase == SPStarted || phase == SPCompleted) $ case qd of QDRcv -> createInternalChatItem user (CDDirectSnd ct) (CISndConnEvent $ SCESwitchQueue phase Nothing) Nothing QDSnd -> createInternalChatItem user (CDDirectRcv ct) (CIRcvConnEvent $ RCESwitchQueue phase) Nothing @@ -628,12 +628,12 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = . mapM (\(ci, content') -> updateDirectChatItem' db user contactId ci content' False False Nothing Nothing) . mdeUpdatedCI e case ci_ of - Just ci -> toView $ CRChatItemUpdated user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci) + Just ci -> toView $ CEvtChatItemUpdated user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci) _ -> do - toView $ CRContactRatchetSync user ct (RatchetSyncProgress rss cStats) + toView $ CEvtContactRatchetSync user ct (RatchetSyncProgress rss cStats) createInternalChatItem user (CDDirectRcv ct) (CIRcvDecryptionError mde n) Nothing ratchetSyncEventItem ct' = do - toView $ CRContactRatchetSync user ct' (RatchetSyncProgress rss cStats) + toView $ CEvtContactRatchetSync user ct' (RatchetSyncProgress rss cStats) createInternalChatItem user (CDDirectRcv ct') (CIRcvConnEvent $ RCERatchetSync rss) Nothing OK -> -- [async agent commands] continuation on receiving OK @@ -643,7 +643,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> when (directOrUsed ct && sqSecured) $ do lift $ setContactNetworkStatus ct NSConnected - toView $ CRContactSndReady user ct + toView $ CEvtContactSndReady user ct forM_ viaUserContactLink $ \userContactLinkId -> do ucl <- withStore $ \db -> getUserContactLinkById db userId userContactLinkId let (UserContactLink {autoAccept}, _) = ucl @@ -655,14 +655,14 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = processConnMWARN connEntity conn err MERR msgId err -> do updateDirectItemStatus ct conn msgId (CISSndError $ agentSndError err) - toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) + toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) processConnMERR connEntity conn err MERRS msgIds err -> do -- error cannot be AUTH error here updateDirectItemsStatusMsgs ct conn (L.toList msgIds) (CISSndError $ agentSndError err) - toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) + toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) ERR err -> do - toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) + toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () -- TODO add debugging output _ -> pure () @@ -671,7 +671,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = Just AutoAccept {autoReply = Just mc} -> do (msg, _) <- sendDirectContactMessage user ct (XMsgNew $ MCSimple (extMsgContent mc Nothing)) ci <- saveSndChatItem user (CDDirectSnd ct) msg (CISndMsgContent mc) - toView $ CRNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct) ci] + toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct) ci] _ -> pure () processGroupMessage :: AEvent e -> ConnectionEntity -> Connection -> GroupInfo -> GroupMember -> CM () @@ -698,7 +698,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = withStore' $ \db -> setNewContactMemberConnRequest db user m cReq groupLinkId <- withStore' $ \db -> getGroupLinkId db user gInfo sendGrpInvitation ct m groupLinkId - toView $ CRSentGroupInvitation user gInfo ct m + toView $ CEvtSentGroupInvitation user gInfo ct m where sendGrpInvitation :: Contact -> GroupMember -> Maybe GroupLinkId -> CM () sendGrpInvitation ct GroupMember {memberId, memberRole = memRole} groupLinkId = do @@ -776,7 +776,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = withAgent $ \a -> toggleConnectionNtfs a (aConnId conn) $ chatHasNtfs chatSettings case memberCategory m of GCHostMember -> do - toView $ CRUserJoinedGroup user gInfo {membership = membership {memberStatus = status'}} m {memberStatus = status'} + toView $ CEvtUserJoinedGroup user gInfo {membership = membership {memberStatus = status'}} m {memberStatus = status'} let cd = CDGroupRcv gInfo m createInternalChatItem user cd (CIRcvGroupE2EEInfo E2EInfo {pqEnabled = PQEncOff}) Nothing createGroupFeatureItems user cd CIRcvGroupFeature gInfo @@ -787,7 +787,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = expectHistory = groupFeatureAllowed SGFHistory gInfo && m `supportsVersion` groupHistoryIncludeWelcomeVersion GCInviteeMember -> do memberConnectedChatItem gInfo m - toView $ CRJoinedGroupMember user gInfo m {memberStatus = status'} + toView $ CEvtJoinedGroupMember user gInfo m {memberStatus = status'} let Connection {viaUserContactLink} = conn when (isJust viaUserContactLink && isNothing (memberContactId m)) sendXGrpLinkMem when (connChatVersion < batchSend2Version) sendGroupAutoReply @@ -831,12 +831,12 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = checkIntegrityCreateItem (CDGroupRcv gInfo m) msgMeta `catchChatError` \_ -> pure () forM_ aChatMsgs $ \case Right (ACMsg _ chatMsg) -> - processEvent tags eInfo chatMsg `catchChatError` \e -> toView $ CRChatError (Just user) e + processEvent tags eInfo chatMsg `catchChatError` \e -> toView $ CEvtChatError (Just user) e Left e -> do atomically $ modifyTVar' tags ("error" :) logInfo $ "group msg=error " <> eInfo <> " " <> tshow e - toView $ CRChatError (Just user) (ChatError . CEException $ "error parsing chat message: " <> e) - forwardMsgs (rights aChatMsgs) `catchChatError` (toView . CRChatError (Just user)) + toView $ CEvtChatError (Just user) (ChatError . CEException $ "error parsing chat message: " <> e) + forwardMsgs (rights aChatMsgs) `catchChatError` (toView . CEvtChatError (Just user)) checkSendRcpt $ rights aChatMsgs where aChatMsgs = parseChatMessages msgBody @@ -918,7 +918,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = updateGroupItemsStatus gInfo m conn msgId GSSSent (Just $ isJust proxy) when continued $ sendPendingGroupMessages user m conn SWITCH qd phase cStats -> do - toView $ CRGroupMemberSwitch user gInfo m (SwitchProgress qd phase cStats) + toView $ CEvtGroupMemberSwitch user gInfo m (SwitchProgress qd phase cStats) when (phase == SPStarted || phase == SPCompleted) $ case qd of QDRcv -> createInternalChatItem user (CDGroupSnd gInfo) (CISndConnEvent . SCESwitchQueue phase . Just $ groupMemberRef m) Nothing QDSnd -> createInternalChatItem user (CDGroupRcv gInfo m) (CIRcvConnEvent $ RCESwitchQueue phase) Nothing @@ -930,7 +930,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = withStore' $ \db -> setConnectionVerified db user connId Nothing let m' = m {activeConn = Just (conn {connectionCode = Nothing} :: Connection)} :: GroupMember ratchetSyncEventItem m' - toView $ CRGroupMemberVerificationReset user gInfo m' + toViewTE $ TEGroupMemberVerificationReset user gInfo m' createInternalChatItem user (CDGroupRcv gInfo m') (CIRcvConnEvent RCEVerificationCodeReset) Nothing _ -> ratchetSyncEventItem m where @@ -942,12 +942,12 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = . mapM (\(ci, content') -> updateGroupChatItem db user groupId ci content' False False Nothing) . mdeUpdatedCI e case ci_ of - Just ci -> toView $ CRChatItemUpdated user (AChatItem SCTGroup SMDRcv (GroupChat gInfo) ci) + Just ci -> toView $ CEvtChatItemUpdated user (AChatItem SCTGroup SMDRcv (GroupChat gInfo) ci) _ -> do - toView $ CRGroupMemberRatchetSync user gInfo m (RatchetSyncProgress rss cStats) + toView $ CEvtGroupMemberRatchetSync user gInfo m (RatchetSyncProgress rss cStats) createInternalChatItem user (CDGroupRcv gInfo m) (CIRcvDecryptionError mde n) Nothing ratchetSyncEventItem m' = do - toView $ CRGroupMemberRatchetSync user gInfo m' (RatchetSyncProgress rss cStats) + toView $ CEvtGroupMemberRatchetSync user gInfo m' (RatchetSyncProgress rss cStats) createInternalChatItem user (CDGroupRcv gInfo m') (CIRcvConnEvent $ RCERatchetSync rss) Nothing OK -> -- [async agent commands] continuation on receiving OK @@ -965,16 +965,16 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = MERR msgId err -> do withStore' $ \db -> updateGroupItemsErrorStatus db msgId (groupMemberId' m) (GSSError $ agentSndError err) -- group errors are silenced to reduce load on UI event log - -- toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) + -- toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) processConnMERR connEntity conn err MERRS msgIds err -> do let newStatus = GSSError $ agentSndError err -- error cannot be AUTH error here withStore' $ \db -> forM_ msgIds $ \msgId -> updateGroupItemsErrorStatus db msgId (groupMemberId' m) newStatus `catchAll_` pure () - toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) + toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) ERR err -> do - toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) + toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () -- TODO add debugging output _ -> pure () @@ -1000,7 +1000,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = msg <- sendGroupMessage' user gInfo [m] (XMsgNew $ MCSimple (extMsgContent mc Nothing)) ci <- saveSndChatItem user (CDGroupSnd gInfo) msg (CISndMsgContent mc) withStore' $ \db -> createGroupSndStatus db (chatItemId' ci) (groupMemberId' m) GSSNew - toView $ CRNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci] + toView $ CEvtNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci] agentMsgDecryptError :: AgentCryptoError -> (MsgDecryptError, Word32) agentMsgDecryptError = \case @@ -1045,7 +1045,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = ci <- withStore $ \db -> do liftIO $ updateSndFileStatus db ft FSConnected updateDirectCIFileStatus db vr user fileId $ CIFSSndTransfer 0 1 - toView $ CRSndFileStart user ci ft + toView $ CEvtSndFileStart user ci ft sendFileChunk user ft SENT msgId _proxy -> do withStore' $ \db -> updateSndFileChunkSent db ft msgId @@ -1059,7 +1059,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = Just (ChatRef CTDirect _) -> liftIO $ updateFileCancelled db user fileId CIFSSndCancelled _ -> pure () lookupChatItemByFileId db vr user fileId - toView $ CRSndFileRcvCancelled user ci ft + toView $ CEvtSndFileRcvCancelled user ci ft _ -> throwChatError $ CEFileSend fileId err MSG meta _ _ -> withAckMessage' "file msg" agentConnId meta $ pure () @@ -1070,7 +1070,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- [async agent commands] continuation on receiving JOINED when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () ERR err -> do - toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) + toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () -- TODO add debugging output _ -> pure () @@ -1119,10 +1119,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- [async agent commands] continuation on receiving JOINED when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () MERR _ err -> do - toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) + toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) processConnMERR connEntity conn err ERR err -> do - toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) + toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () -- TODO add debugging output _ -> pure () @@ -1133,7 +1133,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = unless (rcvFileCompleteOrCancelled ft) $ do cancelRcvFileTransfer user ft >>= mapM_ (deleteAgentConnectionAsync user) ci <- withStore $ \db -> getChatItemByFileId db vr user fileId - toView $ CRRcvFileSndCancelled user ci ft + toView $ CEvtRcvFileSndCancelled user ci ft FileChunk {chunkNo, chunkBytes = chunk} -> do case integrity of MsgOk -> pure () @@ -1156,7 +1156,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = updateCIFileStatus db user fileId CIFSRcvComplete deleteRcvFileChunks db ft getChatItemByFileId db vr user fileId - toView $ CRRcvFileComplete user ci + toView $ CEvtRcvFileComplete user ci forM_ conn_ $ \conn -> deleteAgentConnectionAsync user (aConnId conn) RcvChunkDuplicate -> withAckMessage' "file msg" agentConnId meta $ pure () RcvChunkError -> badRcvFileChunk ft $ "incorrect chunk number " <> show chunkNo @@ -1171,10 +1171,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- TODO show/log error, other events in contact request _ -> pure () MERR _ err -> do - toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) + toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) processConnMERR connEntity conn err ERR err -> do - toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) + toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () -- TODO add debugging output _ -> pure () @@ -1182,8 +1182,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = profileContactRequest :: InvitationId -> VersionRangeChat -> Profile -> Maybe XContactId -> PQSupport -> CM () profileContactRequest invId chatVRange p@Profile {displayName} xContactId_ reqPQSup = do withStore (\db -> createOrUpdateContactRequest db vr user userContactLinkId invId chatVRange p xContactId_ reqPQSup) >>= \case - CORContact contact -> toView $ CRContactRequestAlreadyAccepted user contact - CORGroup gInfo -> toView $ CRBusinessRequestAlreadyAccepted user gInfo + CORContact contact -> toView $ CEvtContactRequestAlreadyAccepted user contact + CORGroup gInfo -> toView $ CEvtBusinessRequestAlreadyAccepted user gInfo CORRequest cReq -> do ucl <- withStore $ \db -> getUserContactLinkById db userId userContactLinkId let (UserContactLink {connLinkContact = CCLink connReq _, autoAccept}, gLinkInfo_) = ucl @@ -1195,16 +1195,16 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = if isSimplexTeam && v < businessChatsVersion then do ct <- acceptContactRequestAsync user cReq Nothing reqPQSup - toView $ CRAcceptingContactRequest user ct + toView $ CEvtAcceptingContactRequest user ct else do gInfo <- acceptBusinessJoinRequestAsync user cReq - toView $ CRAcceptingBusinessRequest user gInfo + toView $ CEvtAcceptingBusinessRequest user gInfo | otherwise -> case gLinkInfo_ of Nothing -> do -- [incognito] generate profile to send, create connection with incognito profile incognitoProfile <- if acceptIncognito then Just . NewIncognito <$> liftIO generateRandomProfile else pure Nothing ct <- acceptContactRequestAsync user cReq incognitoProfile reqPQSup - toView $ CRAcceptingContactRequest user ct + toView $ CEvtAcceptingContactRequest user ct Just gli@GroupLinkInfo {groupId, memberRole = gLinkMemRole} -> do gInfo <- withStore $ \db -> getGroupInfo db vr user groupId acceptMember_ <- asks $ acceptMember . chatHooks . config @@ -1216,14 +1216,14 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = let profileMode = ExistingIncognito <$> incognitoMembershipProfile gInfo mem <- acceptGroupJoinRequestAsync user gInfo cReq acceptance useRole profileMode createInternalChatItem user (CDGroupRcv gInfo mem) (CIRcvGroupEvent RGEInvitedViaGroupLink) Nothing - toView $ CRAcceptingGroupJoinRequestMember user gInfo mem + toView $ CEvtAcceptingGroupJoinRequestMember user gInfo mem Left rjctReason | v < groupJoinRejectVersion -> messageWarning $ "processUserContactRequest (group " <> groupName' gInfo <> "): joining of " <> displayName <> " is blocked" | otherwise -> do mem <- acceptGroupJoinSendRejectAsync user gInfo cReq rjctReason toViewTE $ TERejectingGroupJoinRequestMember user gInfo mem rjctReason - _ -> toView $ CRReceivedContactRequest user cReq + _ -> toView $ CEvtReceivedContactRequest user cReq -- TODO [knocking] review memberCanSend :: GroupMember -> CM () -> CM () @@ -1238,12 +1238,12 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = authErrCounter' <- withStore' $ \db -> incAuthErrCounter db user conn when (authErrCounter' >= authErrDisableCount) $ case connEntity of RcvDirectMsgConnection ctConn (Just ct) -> do - toView $ CRContactDisabled user ct {activeConn = Just ctConn {authErrCounter = authErrCounter'}} - _ -> toView $ CRConnectionDisabled connEntity + toView $ CEvtContactDisabled user ct {activeConn = Just ctConn {authErrCounter = authErrCounter'}} + _ -> toView $ CEvtConnectionDisabled connEntity SMP _ SMP.QUOTA -> unless (connInactive conn) $ do withStore' $ \db -> setQuotaErrCounter db user conn quotaErrSetOnMERR - toView $ CRConnectionInactive connEntity True + toView $ CEvtConnectionInactive connEntity True _ -> pure () processConnMWARN :: ConnectionEntity -> Connection -> AgentErrorType -> CM () @@ -1253,8 +1253,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = unless (connInactive conn) $ do quotaErrCounter' <- withStore' $ \db -> incQuotaErrCounter db user conn when (quotaErrCounter' >= quotaErrInactiveCount) $ - toView $ - CRConnectionInactive connEntity True + toView $ CEvtConnectionInactive connEntity True _ -> pure () continueSending :: ConnectionEntity -> Connection -> CM Bool @@ -1262,7 +1261,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = if connInactive conn then do withStore' $ \db -> setQuotaErrCounter db user conn 0 - toView $ CRConnectionInactive connEntity False + toView $ CEvtConnectionInactive connEntity False pure True else pure False @@ -1366,7 +1365,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = notifyMemberConnected gInfo m ct_ = do memberConnectedChatItem gInfo m lift $ mapM_ (`setContactNetworkStatus` NSConnected) ct_ - toView $ CRConnectedToGroupMember user gInfo m ct_ + toView $ CEvtConnectedToGroupMember user gInfo m ct_ probeMatchingContactsAndMembers :: Contact -> IncognitoEnabled -> Bool -> CM () probeMatchingContactsAndMembers ct connectedIncognito doProbeContacts = do @@ -1423,10 +1422,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = withStore' $ \db -> createSentProbeHash db userId probeId cgm messageWarning :: Text -> CM () - messageWarning = toView . CRMessageError user "warning" + messageWarning = toView . CEvtMessageError user "warning" messageError :: Text -> CM () - messageError = toView . CRMessageError user "error" + messageError = toView . CEvtMessageError user "error" newContentMessage :: Contact -> MsgContainer -> RcvMessage -> MsgMeta -> CM () newContentMessage ct mc msg@RcvMessage {sharedMsgId_} msgMeta = do @@ -1452,13 +1451,13 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = newChatItem content ciFile_ timed_ live = do ci <- saveRcvChatItem' user (CDDirectRcv ct) Nothing msg sharedMsgId_ brokerTs content ciFile_ timed_ live M.empty reactions <- maybe (pure []) (\sharedMsgId -> withStore' $ \db -> getDirectCIReactions db ct sharedMsgId) sharedMsgId_ - toView $ CRNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci {reactions}] + toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci {reactions}] autoAcceptFile :: Maybe (RcvFileTransfer, CIFile 'MDRcv) -> CM () autoAcceptFile = mapM_ $ \(ft, CIFile {fileSize}) -> do -- ! autoAcceptFileSize is only used in tests ChatConfig {autoAcceptFileSize = sz} <- asks config - when (sz > fileSize) $ receiveFile' user ft False Nothing Nothing >>= toView + when (sz > fileSize) $ receiveFileEvt' user ft False Nothing Nothing >>= toView messageFileDescription :: Contact -> SharedMsgId -> FileDescr -> CM () messageFileDescription ct@Contact {contactId} sharedMsgId fileDescr = do @@ -1482,7 +1481,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = pure (rfd, ft') when fileDescrComplete $ do ci <- withStore $ \db -> getAChatItemBySharedMsgId db user cd sharedMsgId - toView $ CRRcvFileDescrReady user ci ft' rfd + toView $ CEvtRcvFileDescrReady user ci ft' rfd case (fileStatus, xftpRcvFile) of (RFSAccepted _, Just XFTPRcvFile {userApprovedRelays}) -> receiveViaCompleteFD user fileId rfd userApprovedRelays cryptoArgs _ -> pure () @@ -1521,7 +1520,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = ci' <- withStore' $ \db -> do createChatItemVersion db (chatItemId' ci) brokerTs mc updateDirectChatItem' db user contactId ci content True live Nothing Nothing - toView $ CRChatItemUpdated user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci') + toView $ CEvtChatItemUpdated user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci') where brokerTs = metaBrokerTs msgMeta content = CIRcvMsgContent mc @@ -1540,24 +1539,25 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = reactions <- getDirectCIReactions db ct sharedMsgId let edited = itemLive /= Just True updateDirectChatItem' db user contactId ci {reactions} content edited live Nothing $ Just msgId - toView $ CRChatItemUpdated user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci') + toView $ CEvtChatItemUpdated user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci') startUpdatedTimedItemThread user (ChatRef CTDirect contactId) ci ci' - else toView $ CRChatItemNotChanged user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci) + else toView $ CEvtChatItemNotChanged user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci) _ -> messageError "x.msg.update: contact attempted invalid message update" messageDelete :: Contact -> SharedMsgId -> RcvMessage -> MsgMeta -> CM () messageDelete ct@Contact {contactId} sharedMsgId _rcvMessage msgMeta = do - deleteRcvChatItem `catchCINotFound` (toView . CRChatItemDeletedNotFound user ct) + deleteRcvChatItem `catchCINotFound` (toView . CEvtChatItemDeletedNotFound user ct) where brokerTs = metaBrokerTs msgMeta deleteRcvChatItem = do cci@(CChatItem msgDir ci) <- withStore $ \db -> getDirectChatItemBySharedMsgId db user contactId sharedMsgId case msgDir of SMDRcv - | rcvItemDeletable ci brokerTs -> - if featureAllowed SCFFullDelete forContact ct - then deleteDirectCIs user ct [cci] False False >>= toView - else markDirectCIsDeleted user ct [cci] False brokerTs >>= toView + | rcvItemDeletable ci brokerTs -> do + deletions <- if featureAllowed SCFFullDelete forContact ct + then deleteDirectCIs user ct [cci] + else markDirectCIsDeleted user ct [cci] brokerTs + toView $ CEvtChatItemsDeleted user deletions False False | otherwise -> messageError "x.msg.del: contact attempted invalid message delete" SMDSnd -> messageError "x.msg.del: contact attempted invalid message delete" @@ -1575,7 +1575,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = withStore' $ \db -> setDirectReaction db ct sharedMsgId False reaction add msgId brokerTs where updateChatItemReaction = do - cr_ <- withStore $ \db -> do + cEvt_ <- withStore $ \db -> do CChatItem md ci <- getDirectChatItemBySharedMsgId db user (contactId' ct) sharedMsgId if ciReactionAllowed ci then liftIO $ do @@ -1583,9 +1583,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = reactions <- getDirectCIReactions db ct sharedMsgId let ci' = CChatItem md ci {reactions} r = ACIReaction SCTDirect SMDRcv (DirectChat ct) $ CIReaction CIDirectRcv ci' brokerTs reaction - pure $ Just $ CRChatItemReaction user add r + pure $ Just $ CEvtChatItemReaction user add r else pure Nothing - mapM_ toView cr_ + mapM_ toView cEvt_ groupMsgReaction :: GroupInfo -> GroupMember -> SharedMsgId -> MemberId -> MsgReaction -> Bool -> RcvMessage -> UTCTime -> CM () groupMsgReaction g m sharedMsgId itemMemberId reaction add RcvMessage {msgId} brokerTs = do @@ -1596,7 +1596,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = withStore' $ \db -> setGroupReaction db g m itemMemberId sharedMsgId False reaction add msgId brokerTs where updateChatItemReaction = do - cr_ <- withStore $ \db -> do + cEvt_ <- withStore $ \db -> do CChatItem md ci <- getGroupMemberCIBySharedMsgId db user g itemMemberId sharedMsgId if ciReactionAllowed ci then liftIO $ do @@ -1604,9 +1604,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = reactions <- getGroupCIReactions db g itemMemberId sharedMsgId let ci' = CChatItem md ci {reactions} r = ACIReaction SCTGroup SMDRcv (GroupChat g) $ CIReaction (CIGroupRcv m) ci' brokerTs reaction - pure $ Just $ CRChatItemReaction user add r + pure $ Just $ CEvtChatItemReaction user add r else pure Nothing - mapM_ toView cr_ + mapM_ toView cEvt_ reactionAllowed :: Bool -> MsgReaction -> [MsgReaction] -> Bool reactionAllowed add reaction rs = (reaction `elem` rs) /= add && not (add && length rs >= maxMsgReactions) @@ -1656,7 +1656,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = | otherwise = do file_ <- processFileInv ci <- createNonLive file_ - toView =<< markGroupCIsDeleted user gInfo [CChatItem SMDRcv ci] False (Just moderator) moderatedAt + deletions <- markGroupCIsDeleted user gInfo [CChatItem SMDRcv ci] (Just moderator) moderatedAt + toView $ CEvtChatItemsDeleted user deletions False False createNonLive file_ = saveRcvCI (CIRcvMsgContent content, ts) (snd <$> file_) timed' False mentions createContentItem = do @@ -1688,7 +1689,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = createChatItemVersion db (chatItemId' ci) brokerTs mc ci' <- updateGroupChatItem db user groupId ci content True live Nothing blockedMember m ci' $ markGroupChatItemBlocked db user gInfo ci' - toView $ CRChatItemUpdated user (AChatItem SCTGroup SMDRcv (GroupChat gInfo) ci') + toView $ CEvtChatItemUpdated user (AChatItem SCTGroup SMDRcv (GroupChat gInfo) ci') where content = CIRcvMsgContent mc ts@(_, ft_) = msgContentTexts mc @@ -1710,9 +1711,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = ciMentions <- getRcvCIMentions db user gInfo ft_ mentions ci' <- updateGroupChatItem db user groupId ci {reactions} content edited live $ Just msgId updateGroupCIMentions db gInfo ci' ciMentions - toView $ CRChatItemUpdated user (AChatItem SCTGroup SMDRcv (GroupChat gInfo) ci') + toView $ CEvtChatItemUpdated user (AChatItem SCTGroup SMDRcv (GroupChat gInfo) ci') startUpdatedTimedItemThread user (ChatRef CTGroup groupId) ci ci' - else toView $ CRChatItemNotChanged user (AChatItem SCTGroup SMDRcv (GroupChat gInfo) ci) + else toView $ CEvtChatItemNotChanged user (AChatItem SCTGroup SMDRcv (GroupChat gInfo) ci) else messageError "x.msg.update: group member attempted to update a message of another member" _ -> messageError "x.msg.update: group member attempted invalid message update" @@ -1730,13 +1731,13 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- regular deletion Nothing | sameMemberId memberId mem && msgMemberId == memberId && rcvItemDeletable ci brokerTs -> - delete cci Nothing >>= toView + delete cci Nothing | otherwise -> messageError "x.msg.del: member attempted invalid message delete" -- moderation (not limited by time) Just _ | sameMemberId memberId mem && msgMemberId == memberId -> - delete cci (Just m) >>= toView + delete cci (Just m) | otherwise -> moderate mem cci CIGroupSnd -> moderate membership cci @@ -1749,7 +1750,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = moderate mem cci = case sndMemberId_ of Just sndMemberId | sameMemberId sndMemberId mem -> checkRole mem $ do - delete cci (Just m) >>= toView + delete cci (Just m) archiveMessageReports cci m | otherwise -> messageError "x.msg.del: message of another member with incorrect memberId" _ -> messageError "x.msg.del: message of another member without memberId" @@ -1757,14 +1758,16 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = | senderRole < GRModerator || senderRole < memberRole = messageError "x.msg.del: message of another member with insufficient member permissions" | otherwise = a - delete :: CChatItem 'CTGroup -> Maybe GroupMember -> CM ChatResponse - delete cci byGroupMember - | groupFeatureMemberAllowed SGFFullDelete m gInfo = deleteGroupCIs user gInfo [cci] False False byGroupMember brokerTs - | otherwise = markGroupCIsDeleted user gInfo [cci] False byGroupMember brokerTs + delete :: CChatItem 'CTGroup -> Maybe GroupMember -> CM () + delete cci byGroupMember = do + deletions <- if groupFeatureMemberAllowed SGFFullDelete m gInfo + then deleteGroupCIs user gInfo [cci] byGroupMember brokerTs + else markGroupCIsDeleted user gInfo [cci] byGroupMember brokerTs + toView $ CEvtChatItemsDeleted user deletions False False archiveMessageReports :: CChatItem 'CTGroup -> GroupMember -> CM () archiveMessageReports (CChatItem _ ci) byMember = do ciIds <- withStore' $ \db -> markMessageReportsDeleted db user gInfo ci byMember brokerTs - unless (null ciIds) $ toView $ CRGroupChatItemsDeleted user gInfo ciIds False (Just byMember) + unless (null ciIds) $ toView $ CEvtGroupChatItemsDeleted user gInfo ciIds False (Just byMember) -- TODO remove once XFile is discontinued processFileInvitation' :: Contact -> FileInvitation -> RcvMessage -> MsgMeta -> CM () @@ -1777,7 +1780,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = ciFile = Just $ CIFile {fileId, fileName, fileSize, fileSource = Nothing, fileStatus = CIFSRcvInvitation, fileProtocol} content = ciContentNoParse $ CIRcvMsgContent $ MCFile "" ci <- saveRcvChatItem' user (CDDirectRcv ct) Nothing msg sharedMsgId_ brokerTs content ciFile Nothing False M.empty - toView $ CRNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci] + toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci] where brokerTs = metaBrokerTs msgMeta @@ -1815,7 +1818,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = unless (rcvFileCompleteOrCancelled ft) $ do cancelRcvFileTransfer user ft >>= mapM_ (deleteAgentConnectionAsync user) ci <- withStore $ \db -> getChatItemByFileId db vr user fileId - toView $ CRRcvFileSndCancelled user ci ft + toView $ CEvtRcvFileSndCancelled user ci ft xFileAcptInv :: Contact -> SharedMsgId -> Maybe ConnReqInvitation -> String -> CM () xFileAcptInv ct sharedMsgId fileConnReq_ fName = do @@ -1837,7 +1840,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = event <- withStore $ \db -> do ci' <- updateDirectCIFileStatus db vr user fileId $ CIFSSndTransfer 0 1 sft <- createSndDirectInlineFT db ct ft - pure $ CRSndFileStart user ci' sft + pure $ CEvtSndFileStart user ci' sft toView event ifM (allowSendInline fileSize fileInline) @@ -1867,8 +1870,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = case file of Just CIFile {fileProtocol = FPXFTP} -> do ft <- withStore $ \db -> getFileTransferMeta db user fileId - toView $ CRSndFileCompleteXFTP user ci ft - _ -> toView $ CRSndFileComplete user ci sft + toView $ CEvtSndFileCompleteXFTP user ci ft + _ -> toView $ CEvtSndFileComplete user ci sft allowSendInline :: Integer -> Maybe InlineFileMode -> CM Bool allowSendInline fileSize = \case @@ -1909,7 +1912,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = unless (rcvFileCompleteOrCancelled ft) $ do cancelRcvFileTransfer user ft >>= mapM_ (deleteAgentConnectionAsync user) ci <- withStore $ \db -> getChatItemByFileId db vr user fileId - toView $ CRRcvFileSndCancelled user ci ft + toView $ CEvtRcvFileSndCancelled user ci ft else messageError "x.file.cancel: group member attempted to cancel file of another member" -- shouldn't happen now that query includes group member id (SMDSnd, _) -> messageError "x.file.cancel: group member attempted invalid file cancel" @@ -1934,7 +1937,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = event <- withStore $ \db -> do ci' <- updateDirectCIFileStatus db vr user fileId $ CIFSSndTransfer 0 1 sft <- liftIO $ createSndGroupInlineFT db m conn ft - pure $ CRSndFileStart user ci' sft + pure $ CEvtSndFileStart user ci' sft toView event ifM (allowSendInline fileSize fileInline) @@ -1945,7 +1948,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = groupMsgToView :: forall d. MsgDirectionI d => GroupInfo -> ChatItem 'CTGroup d -> CM () groupMsgToView gInfo ci = - toView $ CRNewChatItems user [AChatItem SCTGroup (msgDirection @d) (GroupChat gInfo) ci] + toView $ CEvtNewChatItems user [AChatItem SCTGroup (msgDirection @d) (GroupChat gInfo) ci] processGroupInvitation :: Contact -> GroupInvitation -> RcvMessage -> MsgMeta -> CM () processGroupInvitation ct inv msg msgMeta = do @@ -1967,13 +1970,13 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = createMemberConnectionAsync db user hostId connIds connChatVersion peerChatVRange subMode updateGroupMemberStatusById db userId hostId GSMemAccepted updateGroupMemberStatus db userId membership GSMemAccepted - toView $ CRUserAcceptedGroupSent user gInfo {membership = membership {memberStatus = GSMemAccepted}} (Just ct) + toView $ CEvtUserAcceptedGroupSent user gInfo {membership = membership {memberStatus = GSMemAccepted}} (Just ct) else do let content = CIRcvGroupInvitation (CIGroupInvitation {groupId, groupMemberId, localDisplayName, groupProfile, status = CIGISPending}) memRole ci <- saveRcvChatItemNoParse user (CDDirectRcv ct) msg brokerTs content withStore' $ \db -> setGroupInvitationChatItemId db user groupId (chatItemId' ci) - toView $ CRNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci] - toView $ CRReceivedGroupInvitation {user, groupInfo = gInfo, contact = ct, fromMemberRole = fromRole, memberRole = memRole} + toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci] + toView $ CEvtReceivedGroupInvitation {user, groupInfo = gInfo, contact = ct, fromMemberRole = fromRole, memberRole = memRole} where brokerTs = metaBrokerTs msgMeta sameGroupLinkId :: Maybe GroupLinkId -> Maybe GroupLinkId -> Bool @@ -1999,8 +2002,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = activeConn' <- forM (contactConn ct') $ \conn -> pure conn {connStatus = ConnDeleted} let ct'' = ct' {activeConn = activeConn'} :: Contact ci <- saveRcvChatItemNoParse user (CDDirectRcv ct'') msg brokerTs (CIRcvDirectEvent RDEContactDeleted) - toView $ CRNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct'') ci] - toView $ CRContactDeletedByContact user ct'' + toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct'') ci] + toView $ CEvtContactDeletedByContact user ct'' else do contactConns <- withStore' $ \db -> getContactConnections db vr userId c deleteAgentConnectionsAsync user $ map aConnId contactConns @@ -2020,7 +2023,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = when (directOrUsed c' && createItems) $ do createProfileUpdatedItem c' lift $ createRcvFeatureItems user c c' - toView $ CRContactUpdated user c c' + toView $ CEvtContactUpdated user c c' pure c' | otherwise = pure c @@ -2070,7 +2073,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = updateGroupMemberStatus db userId m GSMemConnected updateGroupMemberAccepted db user membership role let m' = m {memberStatus = GSMemConnected} - toView $ CRUserJoinedGroup user gInfo {membership = membership'} m' + toView $ CEvtUserJoinedGroup user gInfo {membership = membership'} m' let connectedIncognito = memberIncognito membership probeMatchingMemberContact m' connectedIncognito @@ -2082,7 +2085,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = Nothing -> do m' <- withStore $ \db -> updateMemberProfile db user m p' createProfileUpdatedItem m' - toView $ CRGroupMemberUpdated user gInfo m m' + toView $ CEvtGroupMemberUpdated user gInfo m m' pure m' Just mContactId -> do mCt <- withStore $ \db -> getContact db vr user mContactId @@ -2090,8 +2093,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = then do (m', ct') <- withStore $ \db -> updateContactMemberProfile db user m mCt p' createProfileUpdatedItem m' - toView $ CRGroupMemberUpdated user gInfo m m' - toView $ CRContactUpdated user mCt ct' + toView $ CEvtGroupMemberUpdated user gInfo m m' + toView $ CEvtContactUpdated user mCt ct' pure m' else pure m where @@ -2106,7 +2109,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = updateBusinessChatProfile g@GroupInfo {businessChat} = case businessChat of Just bc | isMainBusinessMember bc m -> do g' <- withStore $ \db -> updateGroupProfileFromMember db user g p' - toView $ CRGroupUpdated user g g' (Just m) + toView $ CEvtGroupUpdated user g g' (Just m) _ -> pure () isMainBusinessMember BusinessChatInfo {chatType, businessId, customerId} GroupMember {memberId} = case chatType of BCBusiness -> businessId == memberId @@ -2213,8 +2216,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = withStore' $ \db -> createCall db user call' $ chatItemTs' ci call_ <- atomically (TM.lookupInsert contactId call' calls) forM_ call_ $ \call -> updateCallItemStatus user ct call WCSDisconnected Nothing - toView $ CRCallInvitation RcvCallInvitation {user, contact = ct, callType, sharedKey, callUUID, callTs = chatItemTs' ci} - toView $ CRNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci] + toView $ CEvtCallInvitation RcvCallInvitation {user, contact = ct, callType, sharedKey, callUUID, callTs = chatItemTs' ci} + toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci] else featureRejected CFCalls where brokerTs = metaBrokerTs msgMeta @@ -2222,7 +2225,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = featureRejected f = do let content = ciContentNoParse $ CIRcvChatFeatureRejected f ci <- saveRcvChatItem' user (CDDirectRcv ct) Nothing msg sharedMsgId_ brokerTs content Nothing Nothing False M.empty - toView $ CRNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci] + toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci] -- to party initiating call xCallOffer :: Contact -> CallId -> CallOffer -> RcvMessage -> CM () @@ -2233,7 +2236,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = let sharedKey = C.Key . C.dhBytes' <$> (C.dh' <$> callDhPubKey <*> localDhPrivKey) callState' = CallOfferReceived {localCallType, peerCallType = callType, peerCallSession = rtcSession, sharedKey} askConfirmation = encryptedCall localCallType && not (encryptedCall callType) - toView CRCallOffer {user, contact = ct, callType, offer = rtcSession, sharedKey, askConfirmation} + toView CEvtCallOffer {user, contact = ct, callType, offer = rtcSession, sharedKey, askConfirmation} pure (Just call {callState = callState'}, Just . ACIContent SMDSnd $ CISndCall CISCallAccepted 0) _ -> do msgCallStateError "x.call.offer" call @@ -2246,7 +2249,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = \call -> case callState call of CallOfferSent {localCallType, peerCallType, localCallSession, sharedKey} -> do let callState' = CallNegotiated {localCallType, peerCallType, localCallSession, peerCallSession = rtcSession, sharedKey} - toView $ CRCallAnswer user ct rtcSession + toView $ CEvtCallAnswer user ct rtcSession pure (Just call {callState = callState'}, Just . ACIContent SMDRcv $ CIRcvCall CISCallNegotiated 0) _ -> do msgCallStateError "x.call.answer" call @@ -2260,12 +2263,12 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = CallOfferReceived {localCallType, peerCallType, peerCallSession, sharedKey} -> do -- TODO update the list of ice servers in peerCallSession let callState' = CallOfferReceived {localCallType, peerCallType, peerCallSession, sharedKey} - toView $ CRCallExtraInfo user ct rtcExtraInfo + toView $ CEvtCallExtraInfo user ct rtcExtraInfo pure (Just call {callState = callState'}, Nothing) CallNegotiated {localCallType, peerCallType, localCallSession, peerCallSession, sharedKey} -> do -- TODO update the list of ice servers in peerCallSession let callState' = CallNegotiated {localCallType, peerCallType, localCallSession, peerCallSession, sharedKey} - toView $ CRCallExtraInfo user ct rtcExtraInfo + toView $ CEvtCallExtraInfo user ct rtcExtraInfo pure (Just call {callState = callState'}, Nothing) _ -> do msgCallStateError "x.call.extra" call @@ -2275,7 +2278,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = xCallEnd :: Contact -> CallId -> RcvMessage -> CM () xCallEnd ct callId msg = msgCurrentCall ct callId "x.call.end" msg $ \Call {chatItemId} -> do - toView $ CRCallEnded user ct + toView $ CEvtCallEnded user ct (Nothing,) <$> callStatusItemContent user ct chatItemId WCSDisconnected msgCurrentCall :: Contact -> CallId -> Text -> RcvMessage -> (Call -> CM (Maybe Call, Maybe ACIContent)) -> CM () @@ -2317,7 +2320,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = where merge c1' c2' = do c2'' <- withStore $ \db -> mergeContactRecords db vr user c1' c2' - toView $ CRContactsMerged user c1' c2' c2'' + toView $ CEvtContactsMerged user c1' c2' c2'' when (directOrUsed c2'') $ showSecurityCodeChanged c2'' pure $ Just c2'' where @@ -2356,14 +2359,14 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = associateMemberWithContact c1 m2@GroupMember {groupId} = do withStore' $ \db -> associateMemberWithContactRecord db user c1 m2 g <- withStore $ \db -> getGroupInfo db vr user groupId - toView $ CRContactAndMemberAssociated user c1 g m2 c1 + toView $ CEvtContactAndMemberAssociated user c1 g m2 c1 pure c1 associateContactWithMember :: GroupMember -> Contact -> CM Contact associateContactWithMember m1@GroupMember {groupId} c2 = do c2' <- withStore $ \db -> associateContactWithMemberRecord db vr user m1 c2 g <- withStore $ \db -> getGroupInfo db vr user groupId - toView $ CRContactAndMemberAssociated user c2 g m1 c2' + toView $ CEvtContactAndMemberAssociated user c2 g m1 c2' pure c2' saveConnInfo :: Connection -> ConnInfo -> CM (Connection, Bool) @@ -2373,15 +2376,15 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = case chatMsgEvent of XInfo p -> do ct <- withStore $ \db -> createDirectContact db user conn' p - toView $ CRContactConnecting user ct + toView $ CEvtContactConnecting user ct pure (conn', False) XGrpLinkInv glInv -> do (gInfo, host) <- withStore $ \db -> createGroupInvitedViaLink db vr user conn' glInv - toView $ CRGroupLinkConnecting user gInfo host + toView $ CEvtGroupLinkConnecting user gInfo host pure (conn', True) XGrpLinkReject glRjct@GroupLinkRejection {rejectionReason} -> do (gInfo, host) <- withStore $ \db -> createGroupRejectedViaLink db vr user conn' glRjct - toView $ CRGroupLinkConnecting user gInfo host + toView $ CEvtGroupLinkConnecting user gInfo host toViewTE $ TEGroupLinkRejected user gInfo rejectionReason pure (conn', True) -- TODO show/log error, other events in SMP confirmation @@ -2394,7 +2397,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memId) >>= \case Right unknownMember@GroupMember {memberStatus = GSMemUnknown} -> do updatedMember <- withStore $ \db -> updateUnknownMemberAnnounced db vr user m unknownMember memInfo - toView $ CRUnknownMemberAnnounced user gInfo m unknownMember updatedMember + toView $ CEvtUnknownMemberAnnounced user gInfo m unknownMember updatedMember memberAnnouncedToView updatedMember Right _ -> messageError "x.grp.mem.new error: member already exists" Left _ -> do @@ -2405,7 +2408,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = let event = RGEMemberAdded groupMemberId (fromLocalProfile memberProfile) ci <- saveRcvChatItemNoParse user (CDGroupRcv gInfo m) msg brokerTs (CIRcvGroupEvent event) groupMsgToView gInfo ci - toView $ CRJoinedGroupMemberConnecting user gInfo m announcedMember + toView $ CEvtJoinedGroupMemberConnecting user gInfo m announcedMember xGrpMemIntro :: GroupInfo -> GroupMember -> MemberInfo -> Maybe MemberRestrictions -> CM () xGrpMemIntro gInfo@GroupInfo {chatSettings} m@GroupMember {memberRole, localDisplayName = c} memInfo@(MemberInfo memId _ memChatVRange _) memRestrictions = do @@ -2491,7 +2494,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = withStore' $ \db -> updateGroupMemberRole db user member memRole ci <- saveRcvChatItemNoParse user (CDGroupRcv gInfo m) msg brokerTs (CIRcvGroupEvent gEvent) groupMsgToView gInfo ci - toView CRMemberRole {user, groupInfo = gInfo', byMember = m, member = member {memberRole = memRole}, fromRole, toRole = memRole} + toView CEvtMemberRole {user, groupInfo = gInfo', byMember = m, member = member {memberRole = memRole}, fromRole, toRole = memRole} checkHostRole :: GroupMember -> GroupMemberRole -> CM () checkHostRole GroupMember {memberRole, localDisplayName} memRole = @@ -2519,11 +2522,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = let ciContent = CIRcvGroupEvent $ RGEMemberBlocked bmId (fromLocalProfile bmp) blocked ci <- saveRcvChatItemNoParse user (CDGroupRcv gInfo m) msg brokerTs ciContent groupMsgToView gInfo ci - toView CRMemberBlockedForAll {user, groupInfo = gInfo, byMember = m, member = bm, blocked} + toView CEvtMemberBlockedForAll {user, groupInfo = gInfo, byMember = m, member = bm, blocked} Left (SEGroupMemberNotFoundByMemberId _) -> do bm <- createUnknownMember gInfo memId bm' <- setMemberBlocked bm - toView $ CRUnknownMemberBlocked user gInfo m bm' + toView $ CEvtUnknownMemberBlocked user gInfo m bm' Left e -> throwError $ ChatErrorStore e where setMemberBlocked bm = withStore' $ \db -> updateGroupMemberBlocked db user gInfo restriction bm @@ -2582,7 +2585,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = withStore' $ \db -> updateGroupMemberStatus db userId membership GSMemRemoved when withMessages $ deleteMessages membership SMDSnd deleteMemberItem RGEUserDeleted - toView $ CRDeletedMemberUser user gInfo {membership = membership {memberStatus = GSMemRemoved}} m withMessages + toView $ CEvtDeletedMemberUser user gInfo {membership = membership {memberStatus = GSMemRemoved}} m withMessages else withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memId) >>= \case Left _ -> messageError "x.grp.mem.del with unknown member ID" @@ -2594,7 +2597,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = deleteOrUpdateMemberRecord user member when withMessages $ deleteMessages member SMDRcv deleteMemberItem $ RGEMemberDeleted groupMemberId (fromLocalProfile memberProfile) - toView $ CRDeletedMember user gInfo m member {memberStatus = GSMemRemoved} withMessages + toView $ CEvtDeletedMember user gInfo m member {memberStatus = GSMemRemoved} withMessages where checkRole GroupMember {memberRole} a | senderRole < GRAdmin || senderRole < memberRole = @@ -2615,7 +2618,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = withStore' $ \db -> updateGroupMemberStatus db userId m GSMemLeft ci <- saveRcvChatItemNoParse user (CDGroupRcv gInfo m) msg brokerTs (CIRcvGroupEvent RGEMemberLeft) groupMsgToView gInfo ci - toView $ CRLeftMember user gInfo m {memberStatus = GSMemLeft} + toView $ CEvtLeftMember user gInfo m {memberStatus = GSMemLeft} xGrpDel :: GroupInfo -> GroupMember -> RcvMessage -> UTCTime -> CM () xGrpDel gInfo@GroupInfo {membership} m@GroupMember {memberRole} msg brokerTs = do @@ -2628,7 +2631,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = deleteMembersConnections user ms ci <- saveRcvChatItemNoParse user (CDGroupRcv gInfo m) msg brokerTs (CIRcvGroupEvent RGEGroupDeleted) groupMsgToView gInfo ci - toView $ CRGroupDeleted user gInfo {membership = membership {memberStatus = GSMemGroupDeleted}} m + toView $ CEvtGroupDeleted user gInfo {membership = membership {memberStatus = GSMemGroupDeleted}} m xGrpInfo :: GroupInfo -> GroupMember -> GroupProfile -> RcvMessage -> UTCTime -> CM () xGrpInfo g@GroupInfo {groupProfile = p, businessChat} m@GroupMember {memberRole} p' msg brokerTs @@ -2636,7 +2639,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = | otherwise = case businessChat of Nothing -> unless (p == p') $ do g' <- withStore $ \db -> updateGroupProfile db user g p' - toView $ CRGroupUpdated user g g' (Just m) + toView $ CEvtGroupUpdated user g g' (Just m) let cd = CDGroupRcv g' m unless (sameGroupProfileInfo p p') $ do ci <- saveRcvChatItemNoParse user cd msg brokerTs (CIRcvGroupEvent $ RGEGroupUpdated p') @@ -2653,7 +2656,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = updateGroupPrefs_ g@GroupInfo {groupProfile = p} m ps' = unless (groupPreferences p == Just ps') $ do g' <- withStore' $ \db -> updateGroupPreferences db user g ps' - toView $ CRGroupUpdated user g g' (Just m) + toView $ CEvtGroupUpdated user g g' (Just m) let cd = CDGroupRcv g' m createGroupFeatureChangedItems user cd CIRcvGroupFeature g g' @@ -2696,14 +2699,14 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = joinAgentConnectionAsync user True connReq dm subMode createItems mCt' m' = do createInternalChatItem user (CDGroupRcv g m') (CIRcvGroupEvent RGEMemberCreatedContact) Nothing - toView $ CRNewMemberContactReceivedInv user mCt' g m' + toView $ CEvtNewMemberContactReceivedInv user mCt' g m' forM_ mContent_ $ \mc -> do ci <- saveRcvChatItem user (CDDirectRcv mCt') msg brokerTs (CIRcvMsgContent mc, msgContentTexts mc) - toView $ CRNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat mCt') ci] + toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat mCt') ci] securityCodeChanged :: Contact -> CM () securityCodeChanged ct = do - toView $ CRContactVerificationReset user ct + toViewTE $ TEContactVerificationReset user ct createInternalChatItem user (CDDirectRcv ct) (CIRcvConnEvent RCEVerificationCodeReset) Nothing xGrpMsgForward :: GroupInfo -> GroupMember -> MemberId -> ChatMessage 'Json -> UTCTime -> CM () @@ -2713,7 +2716,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = Right author -> processForwardedMsg author msg Left (SEGroupMemberNotFoundByMemberId _) -> do unknownAuthor <- createUnknownMember gInfo memberId - toView $ CRUnknownMemberCreated user gInfo m unknownAuthor + toView $ CEvtUnknownMemberCreated user gInfo m unknownAuthor processForwardedMsg unknownAuthor msg Left e -> throwError $ ChatErrorStore e where @@ -2763,7 +2766,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = updateDirectItemsStatusMsgs ct conn msgIds newStatus = do cis <- withStore' $ \db -> forM msgIds $ \msgId -> runExceptT $ updateDirectItemsStatus' db ct conn msgId newStatus let acis = map ctItem $ concat $ rights cis - unless (null acis) $ toView $ CRChatItemsStatusesUpdated user acis + unless (null acis) $ toView $ CEvtChatItemsStatusesUpdated user acis where ctItem = AChatItem SCTDirect SMDSnd (DirectChat ct) @@ -2771,7 +2774,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = updateDirectItemStatus ct conn msgId newStatus = do cis <- withStore $ \db -> updateDirectItemsStatus' db ct conn msgId newStatus let acis = map ctItem cis - unless (null acis) $ toView $ CRChatItemsStatusesUpdated user acis + unless (null acis) $ toView $ CEvtChatItemsStatusesUpdated user acis where ctItem = AChatItem SCTDirect SMDSnd (DirectChat ct) @@ -2802,7 +2805,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = items <- withStore' (\db -> getGroupChatItemsByAgentMsgId db user groupId connId msgId) cis <- catMaybes <$> withStore (\db -> mapM (updateItem db) items) let acis = map gItem cis - unless (null acis) $ toView $ CRChatItemsStatusesUpdated user acis + unless (null acis) $ toView $ CEvtChatItemsStatusesUpdated user acis where gItem = AChatItem SCTGroup SMDSnd (GroupChat gInfo) updateItem :: DB.Connection -> CChatItem 'CTGroup -> ExceptT StoreError IO (Maybe (ChatItem 'CTGroup 'MDSnd)) diff --git a/src/Simplex/Chat/Mobile.hs b/src/Simplex/Chat/Mobile.hs index 502dbc98d0..7f9f3eb505 100644 --- a/src/Simplex/Chat/Mobile.hs +++ b/src/Simplex/Chat/Mobile.hs @@ -51,7 +51,7 @@ import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation (..), Migrati import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, sumTypeJSON) -import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), BasicAuth (..), CorrId (..), ProtoServerWithAuth (..), ProtocolServer (..)) +import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), BasicAuth (..), ProtoServerWithAuth (..), ProtocolServer (..)) import Simplex.Messaging.Util (catchAll, liftEitherWith, safeDecodeUtf8) import System.IO (utf8) import System.Timeout (timeout) @@ -72,10 +72,14 @@ data DBMigrationResult $(JQ.deriveToJSON (sumTypeJSON $ dropPrefix "DBM") ''DBMigrationResult) -data APIResponse = APIResponse {corr :: Maybe CorrId, remoteHostId :: Maybe RemoteHostId, resp :: ChatResponse} +data APIResponse = APIResponse {remoteHostId :: Maybe RemoteHostId, resp :: ChatResponse} + +data APIEvent = APIEvent {remoteHostId :: Maybe RemoteHostId, resp :: ChatEvent} $(JQ.deriveToJSON defaultJSON ''APIResponse) +$(JQ.deriveToJSON defaultJSON ''APIEvent) + foreign export ccall "chat_migrate_init" cChatMigrateInit :: CString -> CString -> CString -> Ptr (StablePtr ChatController) -> IO CJSONString foreign export ccall "chat_migrate_init_key" cChatMigrateInitKey :: CString -> CString -> CInt -> CString -> CInt -> Ptr (StablePtr ChatController) -> IO CJSONString @@ -286,21 +290,16 @@ chatSendCmd :: ChatController -> B.ByteString -> IO JSONByteString chatSendCmd cc = chatSendRemoteCmd cc Nothing chatSendRemoteCmd :: ChatController -> Maybe RemoteHostId -> B.ByteString -> IO JSONByteString -chatSendRemoteCmd cc rh s = J.encode . APIResponse Nothing rh <$> runReaderT (execChatCommand rh s) cc +chatSendRemoteCmd cc rh s = J.encode . APIResponse rh <$> runReaderT (execChatCommand rh s) cc chatRecvMsg :: ChatController -> IO JSONByteString chatRecvMsg ChatController {outputQ} = json <$> readChatResponse where - json (corr, remoteHostId, resp) = J.encode APIResponse {corr, remoteHostId, resp} - readChatResponse = do - out@(_, _, cr) <- atomically $ readTBQueue outputQ - if filterEvent cr then pure out else readChatResponse - filterEvent = \case - CRGroupSubscribed {} -> False - CRGroupEmpty {} -> False - CRMemberSubSummary {} -> False - CRPendingSubSummary {} -> False - _ -> True + json (remoteHostId, resp) = J.encode APIEvent {remoteHostId, resp} + readChatResponse = + atomically (readTBQueue outputQ) >>= \case + (_, CEvtTerminalEvent {}) -> readChatResponse + out -> pure out chatRecvMsgWait :: ChatController -> Int -> IO JSONByteString chatRecvMsgWait cc time = fromMaybe "" <$> timeout time (chatRecvMsg cc) diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index d971656a26..bcdd60377f 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -192,7 +192,7 @@ startRemoteHost rh_ rcAddrPrefs_ port_ = do RHSessionConnecting _inv rhs' -> Right ((), RHSessionPendingConfirmation sessionCode tls rhs') _ -> Left $ ChatErrorRemoteHost rhKey RHEBadState let rh_' = (\rh -> (rh :: RemoteHostInfo) {sessionState = Just RHSPendingConfirmation {sessionCode}}) <$> remoteHost_ - toView CRRemoteHostSessionCode {remoteHost_ = rh_', sessionCode} + toView CEvtRemoteHostSessionCode {remoteHost_ = rh_', sessionCode} (RCHostSession {sessionKeys}, rhHello, pairing') <- timeoutThrow (ChatErrorRemoteHost rhKey RHETimeout) 60000000 $ takeRCStep vars' hostInfo@HostAppInfo {deviceName = hostDeviceName} <- liftError (ChatErrorRemoteHost rhKey) $ parseHostAppInfo rhHello @@ -203,7 +203,7 @@ startRemoteHost rh_ rcAddrPrefs_ port_ = do let rhKey' = RHId remoteHostId -- rhKey may be invalid after upserting on RHNew when (rhKey' /= rhKey) $ do atomically $ writeTVar rhKeyVar rhKey' - toView $ CRNewRemoteHost rhi + toView $ CEvtNewRemoteHost rhi -- set up HTTP transport and remote profile protocol disconnected <- toIO $ onDisconnected rhKey' sseq httpClient <- liftError' (httpError remoteHostId) $ attachRevHTTP2Client disconnected tls @@ -213,7 +213,7 @@ startRemoteHost rh_ rcAddrPrefs_ port_ = do RHSessionConfirmed _ RHPendingSession {rchClient} -> Right ((), RHSessionConnected {rchClient, tls, rhClient, pollAction, storePath}) _ -> Left $ ChatErrorRemoteHost rhKey RHEBadState chatWriteVar currentRemoteHost $ Just remoteHostId -- this is required for commands to be passed to remote host - toView $ CRRemoteHostConnected rhi {sessionState = Just RHSConnected {sessionCode}} + toView $ CEvtRemoteHostConnected rhi {sessionState = Just RHSConnected {sessionCode}} upsertRemoteHost :: RCHostPairing -> Maybe RemoteHostInfo -> Maybe RCCtrlAddress -> Text -> SessionSeq -> RemoteHostSessionState -> CM RemoteHostInfo upsertRemoteHost pairing'@RCHostPairing {knownHost = kh_} rhi_ rcAddr_ hostDeviceName sseq state = do KnownHostPairing {hostDhPubKey = hostDhPubKey'} <- maybe (throwError . ChatError $ CEInternalError "KnownHost is known after verification") pure kh_ @@ -235,7 +235,7 @@ startRemoteHost rh_ rcAddrPrefs_ port_ = do oq <- asks outputQ forever $ do r_ <- liftRH rhId $ remoteRecv rhClient 10000000 - forM r_ $ \r -> atomically $ writeTBQueue oq (Nothing, Just rhId, r) + forM r_ $ \r -> atomically $ writeTBQueue oq (Just rhId, r) httpError :: RemoteHostId -> HTTP2ClientError -> ChatError httpError rhId = ChatErrorRemoteHost (RHId rhId) . RHEProtocolError . RPEHTTP2 . tshow @@ -271,7 +271,7 @@ cancelRemoteHostSession handlerInfo_ rhKey = do forM_ deregistered $ \session -> do liftIO $ cancelRemoteHost handlingError session `catchAny` (logError . tshow) forM_ (snd <$> handlerInfo_) $ \rhStopReason -> - toView CRRemoteHostStopped {remoteHostId_, rhsState = rhsSessionState session, rhStopReason} + toView CEvtRemoteHostStopped {remoteHostId_, rhsState = rhsSessionState session, rhStopReason} where handlingError = isJust handlerInfo_ remoteHostId_ = case rhKey of @@ -417,7 +417,7 @@ findKnownRemoteCtrl = do Just rc -> pure rc atomically $ putTMVar foundCtrl (rc, inv) let compatible = isJust $ compatibleAppVersion hostAppVersionRange . appVersionRange =<< ctrlAppInfo_ - toView CRRemoteCtrlFound {remoteCtrl = remoteCtrlInfo rc (Just RCSSearching), ctrlAppInfo_, appVersion = currentAppVersion, compatible} + toView CEvtRemoteCtrlFound {remoteCtrl = remoteCtrlInfo rc (Just RCSSearching), ctrlAppInfo_, appVersion = currentAppVersion, compatible} updateRemoteCtrlSession sseq $ \case RCSessionStarting -> Right RCSessionSearching {action, foundCtrl} _ -> Left $ ChatErrorRemoteCtrl RCEBadState @@ -482,7 +482,7 @@ connectRemoteCtrl verifiedInv@(RCVerifiedInvitation inv@RCInvitation {ca, app}) let remoteCtrlId_ = remoteCtrlId' <$> rc_ in Right RCSessionPendingConfirmation {remoteCtrlId_, ctrlDeviceName = ctrlName, rcsClient, tls, sessionCode, rcsWaitSession, rcsWaitConfirmation} _ -> Left $ ChatErrorRemoteCtrl RCEBadState - toView CRRemoteCtrlSessionCode {remoteCtrl_ = (`remoteCtrlInfo` Just RCSPendingConfirmation {sessionCode}) <$> rc_, sessionCode} + toView CEvtRemoteCtrlSessionCode {remoteCtrl_ = (`remoteCtrlInfo` Just RCSPendingConfirmation {sessionCode}) <$> rc_, sessionCode} checkAppVersion CtrlAppInfo {appVersionRange} = case compatibleAppVersion hostAppVersionRange appVersionRange of Just (AppCompatible v) -> pure v @@ -496,7 +496,7 @@ parseCtrlAppInfo :: JT.Value -> CM CtrlAppInfo parseCtrlAppInfo ctrlAppInfo = do liftEitherWith (const $ ChatErrorRemoteCtrl RCEBadInvitation) $ JT.parseEither J.parseJSON ctrlAppInfo -handleRemoteCommand :: (ByteString -> CM' ChatResponse) -> RemoteCrypto -> TBQueue ChatResponse -> HTTP2Request -> CM' () +handleRemoteCommand :: (ByteString -> CM' ChatResponse) -> RemoteCrypto -> TBQueue ChatEvent -> HTTP2Request -> CM' () handleRemoteCommand execChatCommand encryption remoteOutputQ HTTP2Request {request, reqBody, sendResponse} = do logDebug "handleRemoteCommand" liftIO (tryRemoteError' parseRequest) >>= \case @@ -527,7 +527,7 @@ handleRemoteCommand execChatCommand encryption remoteOutputQ HTTP2Request {reque send resp attach sfKN send flush - Left e -> toView' . CRChatError Nothing . ChatErrorRemoteCtrl $ RCEProtocolError e + Left e -> toView' . CEvtChatError Nothing . ChatErrorRemoteCtrl $ RCEProtocolError e takeRCStep :: RCStepTMVar a -> CM a takeRCStep = liftError' (\e -> ChatErrorAgent {agentError = RCP e, connectionEntity_ = Nothing}) . atomically . takeTMVar @@ -556,7 +556,7 @@ handleSend execChatCommand command = do -- convert errors thrown in execChatCommand into error responses to prevent aborting the protocol wrapper RRChatResponse <$> execChatCommand (encodeUtf8 command) -handleRecv :: Int -> TBQueue ChatResponse -> IO RemoteResponse +handleRecv :: Int -> TBQueue ChatEvent -> IO RemoteResponse handleRecv time events = do logDebug $ "Recv: " <> tshow time RRChatEvent <$> (timeout time . atomically $ readTBQueue events) @@ -675,7 +675,7 @@ cancelActiveRemoteCtrl handlerInfo_ = handleAny (logError . tshow) $ do forM_ session_ $ \session -> do liftIO $ cancelRemoteCtrl handlingError session forM_ (snd <$> handlerInfo_) $ \rcStopReason -> - toView CRRemoteCtrlStopped {rcsState = rcsSessionState session, rcStopReason} + toView CEvtRemoteCtrlStopped {rcsState = rcsSessionState session, rcStopReason} where handlingError = isJust handlerInfo_ diff --git a/src/Simplex/Chat/Remote/Protocol.hs b/src/Simplex/Chat/Remote/Protocol.hs index 00fc56f897..b572780a1f 100644 --- a/src/Simplex/Chat/Remote/Protocol.hs +++ b/src/Simplex/Chat/Remote/Protocol.hs @@ -65,7 +65,7 @@ data RemoteCommand data RemoteResponse = RRChatResponse {chatResponse :: ChatResponse} - | RRChatEvent {chatEvent :: Maybe ChatResponse} -- 'Nothing' on poll timeout + | RRChatEvent {chatEvent :: Maybe ChatEvent} -- 'Nothing' on poll timeout | RRFileStored {filePath :: String} | RRFile {fileSize :: Word32, fileDigest :: FileDigest} -- provides attachment , fileDigest :: FileDigest | RRProtocolError {remoteProcotolError :: RemoteProtocolError} -- The protocol error happened on the server side @@ -115,10 +115,10 @@ remoteSend c cmd = RRChatResponse cr -> pure cr r -> badResponse r -remoteRecv :: RemoteHostClient -> Int -> ExceptT RemoteProtocolError IO (Maybe ChatResponse) +remoteRecv :: RemoteHostClient -> Int -> ExceptT RemoteProtocolError IO (Maybe ChatEvent) remoteRecv c ms = sendRemoteCommand' c Nothing RCRecv {wait = ms} >>= \case - RRChatEvent cr_ -> pure cr_ + RRChatEvent cEvt_ -> pure cEvt_ r -> badResponse r remoteStoreFile :: RemoteHostClient -> FilePath -> FilePath -> ExceptT RemoteProtocolError IO FilePath diff --git a/src/Simplex/Chat/Terminal/Input.hs b/src/Simplex/Chat/Terminal/Input.hs index bf48d1d4f5..06a1e0f314 100644 --- a/src/Simplex/Chat/Terminal/Input.hs +++ b/src/Simplex/Chat/Terminal/Input.hs @@ -81,7 +81,6 @@ runInputLoop ct@ChatTerminal {termState, liveMessageState} cc = forever $ do CRGroupDeletedUser u g -> whenCurrUser cc u $ unsetActiveGroup ct g CRSentGroupInvitation u g _ _ -> whenCurrUser cc u $ setActiveGroup ct g CRChatCmdError _ _ -> when (isMessage cmd) $ echo s - CRChatError _ _ -> when (isMessage cmd) $ echo s CRCmdOk _ -> case cmd of Right APIDeleteUser {} -> setActive ct "" _ -> pure () diff --git a/src/Simplex/Chat/Terminal/Main.hs b/src/Simplex/Chat/Terminal/Main.hs index aa9adb059f..af90340cbc 100644 --- a/src/Simplex/Chat/Terminal/Main.hs +++ b/src/Simplex/Chat/Terminal/Main.hs @@ -10,12 +10,12 @@ import Data.Maybe (fromMaybe) import Data.Time.Clock (getCurrentTime) import Data.Time.LocalTime (getCurrentTimeZone) import Network.Socket -import Simplex.Chat.Controller (ChatConfig (..), ChatController (..), ChatResponse (..), PresetServers (..), SimpleNetCfg (..), currentRemoteHost, versionNumber, versionString) +import Simplex.Chat.Controller (ChatConfig (..), ChatController (..), ChatEvent (..), PresetServers (..), SimpleNetCfg (..), currentRemoteHost, versionNumber, versionString) import Simplex.Chat.Core import Simplex.Chat.Options import Simplex.Chat.Options.DB import Simplex.Chat.Terminal -import Simplex.Chat.View (serializeChatResponse, smpProxyModeStr) +import Simplex.Chat.View (ChatResponseEvent, serializeChatResponse, smpProxyModeStr) import Simplex.Messaging.Client (NetworkConfig (..), SocksMode (..)) import System.Directory (getAppUserDataDirectory) import System.Exit (exitFailure) @@ -43,13 +43,14 @@ simplexChatCLI' cfg opts@ChatOpts {chatCmd, chatCmdLog, chatCmdDelay, chatServer simplexChatTerminal cfg opts t runCommand user cc = do when (chatCmdLog /= CCLNone) . void . forkIO . forever $ do - (_, _, r') <- atomically . readTBQueue $ outputQ cc - case r' of - CRNewChatItems {} -> printResponse r' - _ -> when (chatCmdLog == CCLAll) $ printResponse r' + (_, r) <- atomically . readTBQueue $ outputQ cc + case r of + CEvtNewChatItems {} -> printResponse r + _ -> when (chatCmdLog == CCLAll) $ printResponse r sendChatCmdStr cc chatCmd >>= printResponse threadDelay $ chatCmdDelay * 1000000 where + printResponse :: ChatResponseEvent r => r -> IO () printResponse r = do ts <- getCurrentTime tz <- getCurrentTimeZone diff --git a/src/Simplex/Chat/Terminal/Output.hs b/src/Simplex/Chat/Terminal/Output.hs index 3694b20c67..5134d0efc9 100644 --- a/src/Simplex/Chat/Terminal/Output.hs +++ b/src/Simplex/Chat/Terminal/Output.hs @@ -146,19 +146,19 @@ withTermLock ChatTerminal {termLock} action = do runTerminalOutput :: ChatTerminal -> ChatController -> ChatOpts -> IO () runTerminalOutput ct cc@ChatController {outputQ, showLiveItems, logFilePath} ChatOpts {markRead} = do forever $ do - (_, outputRH, r) <- atomically $ readTBQueue outputQ + (outputRH, r) <- atomically $ readTBQueue outputQ case r of - CRNewChatItems u (ci : _) -> when markRead $ markChatItemRead u ci -- At the moment of writing received items are created one at a time - CRChatItemUpdated u ci -> when markRead $ markChatItemRead u ci - CRRemoteHostConnected {remoteHost = RemoteHostInfo {remoteHostId}} -> getRemoteUser remoteHostId - CRRemoteHostStopped {remoteHostId_} -> mapM_ removeRemoteUser remoteHostId_ + CEvtNewChatItems u (ci : _) -> when markRead $ markChatItemRead u ci -- At the moment of writing received items are created one at a time + CEvtChatItemUpdated u ci -> when markRead $ markChatItemRead u ci + CEvtRemoteHostConnected {remoteHost = RemoteHostInfo {remoteHostId}} -> getRemoteUser remoteHostId + CEvtRemoteHostStopped {remoteHostId_} -> mapM_ removeRemoteUser remoteHostId_ _ -> pure () - let printResp = case logFilePath of - Just path -> if logResponseToFile r then logResponse path else printToTerminal ct + let printEvent = case logFilePath of + Just path -> if logEventToFile r then logResponse path else printToTerminal ct _ -> printToTerminal ct liveItems <- readTVarIO showLiveItems - responseString ct cc liveItems outputRH r >>= printResp - responseNotification ct cc r + responseString ct cc liveItems outputRH r >>= printEvent + chatEventNotification ct cc r where markChatItemRead u (AChatItem _ _ chat ci@ChatItem {chatDir, meta = CIMeta {itemStatus}}) = case (chatDirNtf u chat chatDir (isUserMention ci), itemStatus) of @@ -174,10 +174,10 @@ runTerminalOutput ct cc@ChatController {outputQ, showLiveItems, logFilePath} Cha cr -> logError $ "Unexpected reply while getting remote user: " <> tshow cr removeRemoteUser rhId = atomically $ TM.delete rhId (currentRemoteUsers ct) -responseNotification :: ChatTerminal -> ChatController -> ChatResponse -> IO () -responseNotification t@ChatTerminal {sendNotification} cc = \case +chatEventNotification :: ChatTerminal -> ChatController -> ChatEvent -> IO () +chatEventNotification t@ChatTerminal {sendNotification} cc = \case -- At the moment of writing received items are created one at a time - CRNewChatItems u ((AChatItem _ SMDRcv cInfo ci@ChatItem {chatDir, content = CIRcvMsgContent mc, formattedText}) : _) -> + CEvtNewChatItems u ((AChatItem _ SMDRcv cInfo ci@ChatItem {chatDir, content = CIRcvMsgContent mc, formattedText}) : _) -> when (chatDirNtf u cInfo chatDir $ isUserMention ci) $ do whenCurrUser cc u $ setActiveChat t cInfo case (cInfo, chatDir) of @@ -186,29 +186,29 @@ responseNotification t@ChatTerminal {sendNotification} cc = \case _ -> pure () where text = msgText mc formattedText - CRChatItemUpdated u (AChatItem _ SMDRcv cInfo ci@ChatItem {chatDir, content = CIRcvMsgContent _}) -> + CEvtChatItemUpdated u (AChatItem _ SMDRcv cInfo ci@ChatItem {chatDir, content = CIRcvMsgContent _}) -> whenCurrUser cc u $ when (chatDirNtf u cInfo chatDir $ isUserMention ci) $ setActiveChat t cInfo - CRContactConnected u ct _ -> when (contactNtf u ct False) $ do + CEvtContactConnected u ct _ -> when (contactNtf u ct False) $ do whenCurrUser cc u $ setActiveContact t ct sendNtf (viewContactName ct <> "> ", "connected") - CRContactSndReady u ct -> + CEvtContactSndReady u ct -> whenCurrUser cc u $ setActiveContact t ct - CRContactAnotherClient u ct -> do + CEvtContactAnotherClient u ct -> do whenCurrUser cc u $ unsetActiveContact t ct when (contactNtf u ct False) $ sendNtf (viewContactName ct <> "> ", "connected to another client") - CRContactsDisconnected srv _ -> serverNtf srv "disconnected" - CRContactsSubscribed srv _ -> serverNtf srv "connected" - CRReceivedGroupInvitation u g ct _ _ -> + CEvtContactsDisconnected srv _ -> serverNtf srv "disconnected" + CEvtContactsSubscribed srv _ -> serverNtf srv "connected" + CEvtReceivedGroupInvitation u g ct _ _ -> when (contactNtf u ct False) $ sendNtf ("#" <> viewGroupName g <> " " <> viewContactName ct <> "> ", "invited you to join the group") - CRUserJoinedGroup u g _ -> when (groupNtf u g False) $ do + CEvtUserJoinedGroup u g _ -> when (groupNtf u g False) $ do whenCurrUser cc u $ setActiveGroup t g sendNtf ("#" <> viewGroupName g, "you are connected to group") - CRJoinedGroupMember u g m -> + CEvtJoinedGroupMember u g m -> when (groupNtf u g False) $ sendNtf ("#" <> viewGroupName g, "member " <> viewMemberName m <> " is connected") - CRConnectedToGroupMember u g m _ -> + CEvtConnectedToGroupMember u g m _ -> when (groupNtf u g False) $ sendNtf ("#" <> viewGroupName g, "member " <> viewMemberName m <> " is connected") - CRReceivedContactRequest u UserContactRequest {localDisplayName = n} -> + CEvtReceivedContactRequest u UserContactRequest {localDisplayName = n} -> when (userNtf u) $ sendNtf (viewName n <> ">", "wants to connect to you") _ -> pure () where @@ -274,7 +274,7 @@ whenCurrUser cc u a = do printRespToTerminal :: ChatTerminal -> ChatController -> Bool -> Maybe RemoteHostId -> ChatResponse -> IO () printRespToTerminal ct cc liveItems outputRH r = responseString ct cc liveItems outputRH r >>= printToTerminal ct -responseString :: ChatTerminal -> ChatController -> Bool -> Maybe RemoteHostId -> ChatResponse -> IO [StyledString] +responseString :: ChatResponseEvent r => ChatTerminal -> ChatController -> Bool -> Maybe RemoteHostId -> r -> IO [StyledString] responseString ct cc liveItems outputRH r = do cu <- getCurrentUser ct cc ts <- getCurrentTime diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 6abbf6f03f..b87ba3a081 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -7,6 +7,7 @@ {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} @@ -86,22 +87,27 @@ data WCallCommand $(JQ.deriveToJSON (taggedObjectJSON $ dropPrefix "WCCall") ''WCallCommand) -serializeChatResponse :: (Maybe RemoteHostId, Maybe User) -> CurrentTime -> TimeZone -> Maybe RemoteHostId -> ChatResponse -> String +serializeChatResponse :: ChatResponseEvent r => (Maybe RemoteHostId, Maybe User) -> CurrentTime -> TimeZone -> Maybe RemoteHostId -> r -> String serializeChatResponse user_ ts tz remoteHost_ = unlines . map unStyle . responseToView user_ defaultChatConfig False ts tz remoteHost_ -responseToView :: (Maybe RemoteHostId, Maybe User) -> ChatConfig -> Bool -> CurrentTime -> TimeZone -> Maybe RemoteHostId -> ChatResponse -> [StyledString] -responseToView hu@(currentRH, user_) cfg@ChatConfig {logLevel, showReactions, showReceipts, testView} liveItems ts tz outputRH = \case +class ChatResponseEvent r where + responseToView :: (Maybe RemoteHostId, Maybe User) -> ChatConfig -> Bool -> CurrentTime -> TimeZone -> Maybe RemoteHostId -> r -> [StyledString] + +instance ChatResponseEvent ChatResponse where responseToView = chatResponseToView + +instance ChatResponseEvent ChatEvent where responseToView = chatEventToView + +chatResponseToView :: (Maybe RemoteHostId, Maybe User) -> ChatConfig -> Bool -> CurrentTime -> TimeZone -> Maybe RemoteHostId -> ChatResponse -> [StyledString] +chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, testView} liveItems ts tz outputRH = \case CRActiveUser User {profile, uiThemes} -> viewUserProfile (fromLocalProfile profile) <> viewUITheme uiThemes CRUsersList users -> viewUsersList users CRChatStarted -> ["chat started"] CRChatRunning -> ["chat is running"] CRChatStopped -> ["chat stopped"] - CRChatSuspended -> ["chat suspended"] CRApiChats u chats -> ttyUser u $ if testView then testViewChats chats else [viewJSON chats] CRChats chats -> viewChats ts tz chats CRApiChat u chat _ -> ttyUser u $ if testView then testViewChat chat else [viewJSON chat] CRChatTags u tags -> ttyUser u $ [viewJSON tags] - CRApiParsedMarkdown ft -> [viewJSON ft] CRServerTestResult u srv testFailure -> ttyUser u $ viewServerTestResult srv testFailure CRServerOperatorConditions (ServerOperatorConditions ops _ ca) -> viewServerOperators ops ca CRUserServers u uss -> ttyUser u $ concatMap viewUserServers uss <> (if testView then [] else serversUserHelp) @@ -120,52 +126,23 @@ responseToView hu@(currentRH, user_) cfg@ChatConfig {logLevel, showReactions, sh CRGroupMemberSwitchStarted {} -> ["switch started"] CRContactSwitchAborted {} -> ["switch aborted"] CRGroupMemberSwitchAborted {} -> ["switch aborted"] - CRContactSwitch u ct progress -> ttyUser u $ viewContactSwitch ct progress - CRGroupMemberSwitch u g m progress -> ttyUser u $ viewGroupMemberSwitch g m progress CRContactRatchetSyncStarted {} -> ["connection synchronization started"] CRGroupMemberRatchetSyncStarted {} -> ["connection synchronization started"] - CRContactRatchetSync u ct progress -> ttyUser u $ viewContactRatchetSync ct progress - CRGroupMemberRatchetSync u g m progress -> ttyUser u $ viewGroupMemberRatchetSync g m progress - CRContactVerificationReset u ct -> ttyUser u $ viewContactVerificationReset ct - CRGroupMemberVerificationReset u g m -> ttyUser u $ viewGroupMemberVerificationReset g m CRConnectionVerified u verified code -> ttyUser u [plain $ if verified then "connection verified" else "connection not verified, current code is " <> code] CRContactCode u ct code -> ttyUser u $ viewContactCode ct code testView CRGroupMemberCode u g m code -> ttyUser u $ viewGroupMemberCode g m code testView - CRNewChatItems u chatItems - | length chatItems > 20 -> - if - | all (\aci -> aChatItemDir aci == MDRcv) chatItems -> ttyUser u [sShow (length chatItems) <> " new messages"] - | all (\aci -> aChatItemDir aci == MDSnd) chatItems -> ttyUser u [sShow (length chatItems) <> " messages sent"] - | otherwise -> ttyUser u [sShow (length chatItems) <> " new messages created"] - | otherwise -> - concatMap - (\(AChatItem _ _ chat item) -> ttyUser u $ unmuted u chat item $ viewChatItem chat item False ts tz <> viewItemReactions item) - chatItems + CRNewChatItems u chatItems -> viewChatItems ttyUser unmuted u chatItems ts tz CRChatItems u _ chatItems -> ttyUser u $ concatMap (\(AChatItem _ _ chat item) -> viewChatItem chat item True ts tz <> viewItemReactions item) chatItems CRChatItemInfo u ci ciInfo -> ttyUser u $ viewChatItemInfo ci ciInfo tz CRChatItemId u itemId -> ttyUser u [plain $ maybe "no item" show itemId] - CRChatItemsStatusesUpdated u chatItems - | length chatItems <= 20 -> - concatMap - (\ci -> ttyUser u $ viewChatItemStatusUpdated ci ts tz testView showReceipts) - chatItems - | testView && showReceipts -> - ttyUser u [sShow (length chatItems) <> " message statuses updated"] - | otherwise -> [] CRChatItemUpdated u (AChatItem _ _ chat item) -> ttyUser u $ unmuted u chat item $ viewItemUpdate chat item liveItems ts tz CRChatItemNotChanged u ci -> ttyUser u $ viewItemNotChanged ci CRTagsUpdated u _ _ -> ttyUser u ["chat tags updated"] - CRChatItemsDeleted u deletions byUser timed -> case deletions of - [ChatItemDeletion (AChatItem _ _ chat deletedItem) toItem] -> - ttyUser u $ unmuted u chat deletedItem $ viewItemDelete chat deletedItem toItem byUser timed ts tz testView - deletions' -> ttyUser u [sShow (length deletions') <> " messages deleted"] - CRGroupChatItemsDeleted u g ciIds byUser member_ -> ttyUser u [ttyGroup' g <> ": " <> sShow (length ciIds) <> " messages deleted by " <> if byUser then "user" else "member" <> maybe "" (\m -> " " <> ttyMember m) member_] + CRChatItemsDeleted u deletions byUser timed -> ttyUser u $ viewChatItemsDeleted (unmuted u) deletions byUser timed ts tz testView + CRGroupChatItemsDeleted u g ciIds byUser member_ -> ttyUser u $ viewGroupChatItemsDeleted g ciIds byUser member_ CRChatItemReaction u added (ACIReaction _ _ chat reaction) -> ttyUser u $ unmutedReaction u chat reaction $ viewItemReaction showReactions chat reaction added ts tz CRReactionMembers u memberReactions -> ttyUser u $ viewReactionMembers memberReactions - CRChatItemDeletedNotFound u Contact {localDisplayName = c} _ -> ttyUser u [ttyFrom $ c <> "> [deleted - original message not found]"] CRBroadcastSent u mc s f t -> ttyUser u $ viewSentBroadcast mc s f ts tz t - CRMsgIntegrityError u mErr -> ttyUser u $ viewMsgIntegrityError mErr - CRCmdAccepted _ -> [] CRCmdOk u_ -> ttyUser' u_ ["ok"] CRChatHelp section -> case section of HSMain -> chatHelpInfo @@ -187,18 +164,12 @@ responseToView hu@(currentRH, user_) cfg@ChatConfig {logLevel, showReactions, sh CRGroupCreated u g -> ttyUser u $ viewGroupCreated g testView CRGroupMembers u g -> ttyUser u $ viewGroupMembers g CRGroupsList u gs -> ttyUser u $ viewGroupsList gs - CRSentGroupInvitation u g c _ -> - ttyUser u $ - case contactConn c of - Just Connection {viaGroupLink} - | viaGroupLink -> [ttyContact' c <> " invited to group " <> ttyGroup' g <> " via your group link"] - | otherwise -> ["invitation to join the group " <> ttyGroup' g <> " sent to " <> ttyContact' c] - Nothing -> [] + CRSentGroupInvitation u g c _ -> ttyUser u $ viewSentGroupInvitation g c CRFileTransferStatus u ftStatus -> ttyUser u $ viewFileTransferStatus ftStatus CRFileTransferStatusXFTP u ci -> ttyUser u $ viewFileTransferStatusXFTP ci CRUserProfile u p -> ttyUser u $ viewUserProfile p CRUserProfileNoChange u -> ttyUser u ["user profile did not change"] - CRUserPrivacy u u' -> ttyUserPrefix u $ viewUserPrivacy u u' + CRUserPrivacy u u' -> ttyUserPrefix hu outputRH u $ viewUserPrivacy u u' CRVersionInfo info _ _ -> viewVersionInfo logLevel info CRInvitation u ccLink _ -> ttyUser u $ viewConnReqInvitation ccLink CRConnectionIncognitoUpdated u c -> ttyUser u $ viewConnectionIncognitoUpdated c @@ -208,29 +179,18 @@ responseToView hu@(currentRH, user_) cfg@ChatConfig {logLevel, showReactions, sh CRSentInvitation u _ customUserProfile -> ttyUser u $ viewSentInvitation customUserProfile testView CRSentInvitationToContact u _c customUserProfile -> ttyUser u $ viewSentInvitation customUserProfile testView CRContactDeleted u c -> ttyUser u [ttyContact' c <> ": contact is deleted"] - CRContactDeletedByContact u c -> ttyUser u [ttyFullContact c <> " deleted contact with you"] CRChatCleared u chatInfo -> ttyUser u $ viewChatCleared chatInfo CRAcceptingContactRequest u c -> ttyUser u $ viewAcceptingContactRequest c - CRAcceptingBusinessRequest u g -> ttyUser u $ viewAcceptingBusinessRequest g CRContactAlreadyExists u c -> ttyUser u [ttyFullContact c <> ": contact already exists"] - CRContactRequestAlreadyAccepted u c -> ttyUser u [ttyFullContact c <> ": sent you a duplicate contact request, but you are already connected, no action needed"] - CRBusinessRequestAlreadyAccepted u g -> ttyUser u [ttyFullGroup g <> ": sent you a duplicate connection request, but you are already connected, no action needed"] CRUserContactLinkCreated u ccLink -> ttyUser u $ connReqContact_ "Your new chat address is created!" ccLink CRUserContactLinkDeleted u -> ttyUser u viewUserContactLinkDeleted CRUserAcceptedGroupSent u _g _ -> ttyUser u [] -- [ttyGroup' g <> ": joining the group..."] - CRGroupLinkConnecting u g _ -> ttyUser u [ttyGroup' g <> ": joining the group..."] - CRBusinessLinkConnecting u g _ _ -> ttyUser u [ttyGroup' g <> ": joining the group..."] CRUserDeletedMembers u g members wm -> case members of [m] -> ttyUser u [ttyGroup' g <> ": you removed " <> ttyMember m <> " from the group" <> withMessages wm] mems' -> ttyUser u [ttyGroup' g <> ": you removed " <> sShow (length mems') <> " members from the group" <> withMessages wm] CRLeftMemberUser u g -> ttyUser u $ [ttyGroup' g <> ": you left the group"] <> groupPreserved g - CRUnknownMemberCreated u g fwdM um -> ttyUser u [ttyGroup' g <> ": " <> ttyMember fwdM <> " forwarded a message from an unknown member, creating unknown member record " <> ttyMember um] - CRUnknownMemberBlocked u g byM um -> ttyUser u [ttyGroup' g <> ": " <> ttyMember byM <> " blocked an unknown member, creating unknown member record " <> ttyMember um] - CRUnknownMemberAnnounced u g _ um m -> ttyUser u [ttyGroup' g <> ": unknown member " <> ttyMember um <> " updated to " <> ttyMember m] CRGroupDeletedUser u g -> ttyUser u [ttyGroup' g <> ": you deleted the group"] CRForwardPlan u count itemIds fc -> ttyUser u $ viewForwardPlan count itemIds fc - CRRcvFileDescrReady _ _ _ _ -> [] - CRRcvFileProgressXFTP {} -> [] CRRcvFileAccepted u ci -> ttyUser u $ savingFile' ci CRRcvFileAcceptedSndCancelled u ft -> ttyUser u $ viewRcvFileSndCancelled ft CRSndFileCancelled u _ ftm fts -> ttyUser u $ viewSndFileCancelled ftm fts @@ -241,110 +201,27 @@ responseToView hu@(currentRH, user_) cfg@ChatConfig {logLevel, showReactions, sh CRContactAliasUpdated u c -> ttyUser u $ viewContactAliasUpdated c CRGroupAliasUpdated u g -> ttyUser u $ viewGroupAliasUpdated g CRConnectionAliasUpdated u c -> ttyUser u $ viewConnectionAliasUpdated c - CRContactUpdated {user = u, fromContact = c, toContact = c'} -> ttyUser u $ viewContactUpdated c c' <> viewContactPrefsUpdated u c c' - CRGroupMemberUpdated {} -> [] - CRContactsMerged u intoCt mergedCt ct' -> ttyUser u $ viewContactsMerged intoCt mergedCt ct' - CRReceivedContactRequest u UserContactRequest {localDisplayName = c, profile} -> ttyUser u $ viewReceivedContactRequest c profile CRRcvStandaloneFileCreated u ft -> ttyUser u $ receivingFileStandalone "started" ft - CRRcvFileStart u ci -> ttyUser u $ receivingFile_' hu testView "started" ci - CRRcvFileComplete u ci -> ttyUser u $ receivingFile_' hu testView "completed" ci - CRRcvStandaloneFileComplete u _ ft -> ttyUser u $ receivingFileStandalone "completed" ft - CRRcvFileSndCancelled u _ ft -> ttyUser u $ viewRcvFileSndCancelled ft - CRRcvFileError u (Just ci) e _ -> ttyUser u $ receivingFile_' hu testView "error" ci <> [sShow e] - CRRcvFileError u Nothing e ft -> ttyUser u $ receivingFileStandalone "error" ft <> [sShow e] - CRRcvFileWarning u (Just ci) e _ -> ttyUser u $ receivingFile_' hu testView "warning: " ci <> [sShow e] - CRRcvFileWarning u Nothing e ft -> ttyUser u $ receivingFileStandalone "warning: " ft <> [sShow e] - CRSndFileStart u _ ft -> ttyUser u $ sendingFile_ "started" ft - CRSndFileComplete u _ ft -> ttyUser u $ sendingFile_ "completed" ft CRSndStandaloneFileCreated u ft -> ttyUser u $ uploadingFileStandalone "started" ft - CRSndFileStartXFTP {} -> [] - CRSndFileProgressXFTP {} -> [] - CRSndFileRedirectStartXFTP u ft ftRedirect -> ttyUser u $ standaloneUploadRedirect ft ftRedirect - CRSndStandaloneFileComplete u ft uris -> ttyUser u $ standaloneUploadComplete ft uris - CRSndFileCompleteXFTP u ci _ -> ttyUser u $ uploadingFile "completed" ci - CRSndFileCancelledXFTP {} -> [] - CRSndFileError u Nothing ft e -> ttyUser u $ uploadingFileStandalone "error" ft <> [plain e] - CRSndFileError u (Just ci) _ e -> ttyUser u $ uploadingFile "error" ci <> [plain e] - CRSndFileWarning u Nothing ft e -> ttyUser u $ uploadingFileStandalone "warning: " ft <> [plain e] - CRSndFileWarning u (Just ci) _ e -> ttyUser u $ uploadingFile "warning: " ci <> [plain e] - CRSndFileRcvCancelled u _ ft@SndFileTransfer {recipientDisplayName = c} -> - ttyUser u [ttyContact c <> " cancelled receiving " <> sndFile ft] CRStandaloneFileInfo info_ -> maybe ["no file information in URI"] (\j -> [viewJSON j]) info_ - CRContactConnecting u _ -> ttyUser u [] - CRContactConnected u ct userCustomProfile -> ttyUser u $ viewContactConnected ct userCustomProfile testView - CRContactSndReady u ct -> ttyUser u [ttyFullContact ct <> ": you can send messages to contact"] - CRContactAnotherClient u c -> ttyUser u [ttyContact' c <> ": contact is connected to another client"] - CRSubscriptionEnd u acEntity -> - let Connection {connId} = entityConnection acEntity - in ttyUser u [sShow connId <> ": END"] - CRContactsDisconnected srv cs -> [plain $ "server disconnected " <> showSMPServer srv <> " (" <> contactList cs <> ")"] - CRContactsSubscribed srv cs -> [plain $ "server connected " <> showSMPServer srv <> " (" <> contactList cs <> ")"] - CRContactSubError u c e -> ttyUser u [ttyContact' c <> ": contact error " <> sShow e] - CRContactSubSummary u summary -> - ttyUser u $ [sShow (length subscribed) <> " contacts connected (use " <> highlight' "/cs" <> " for the list)" | not (null subscribed)] <> viewErrorsSummary errors " contact errors" - where - (errors, subscribed) = partition (isJust . contactError) summary - CRUserContactSubSummary u summary -> - ttyUser u $ - map addressSS addresses - <> ([sShow (length groupLinksSubscribed) <> " group links active" | not (null groupLinksSubscribed)] <> viewErrorsSummary groupLinkErrors " group link errors") - where - (addresses, groupLinks) = partition (\UserContactSubStatus {userContact} -> isNothing . userContactGroupId $ userContact) summary - addressSS UserContactSubStatus {userContactError} = maybe ("Your address is active! To show: " <> highlight' "/sa") (\e -> "User address error: " <> sShow e <> ", to delete your address: " <> highlight' "/da") userContactError - (groupLinkErrors, groupLinksSubscribed) = partition (isJust . userContactError) groupLinks - CRNetworkStatus status conns -> if testView then [plain $ show (length conns) <> " connections " <> netStatusStr status] else [] CRNetworkStatuses u statuses -> if testView then ttyUser' u $ viewNetworkStatuses statuses else [] - CRGroupInvitation u g -> ttyUser u [groupInvitationSub g] - CRReceivedGroupInvitation {user = u, groupInfo = g, contact = c, memberRole = r} -> ttyUser u $ viewReceivedGroupInvitation g c r - CRUserJoinedGroup u g _ -> ttyUser u $ viewUserJoinedGroup g CRJoinedGroupMember u g m -> ttyUser u $ viewJoinedGroupMember g m - CRHostConnected p h -> [plain $ "connected to " <> viewHostEvent p h] - CRHostDisconnected p h -> [plain $ "disconnected from " <> viewHostEvent p h] - CRJoinedGroupMemberConnecting u g host m -> ttyUser u [ttyGroup' g <> ": " <> ttyMember host <> " added " <> ttyFullMember m <> " to the group (connecting...)"] - CRConnectedToGroupMember u g m _ -> ttyUser u [ttyGroup' g <> ": " <> connectedMember m <> " is connected"] - CRMemberRole u g by m r r' -> ttyUser u $ viewMemberRoleChanged g by m r r' CRMembersRoleUser u g members r' -> ttyUser u $ viewMemberRoleUserChanged g members r' - CRMemberBlockedForAll u g by m blocked -> ttyUser u $ viewMemberBlockedForAll g by m blocked CRMembersBlockedForAllUser u g members blocked -> ttyUser u $ viewMembersBlockedForAllUser g members blocked - CRDeletedMemberUser u g by wm -> ttyUser u $ [ttyGroup' g <> ": " <> ttyMember by <> " removed you from the group" <> withMessages wm] <> groupPreserved g - CRDeletedMember u g by m wm -> ttyUser u [ttyGroup' g <> ": " <> ttyMember by <> " removed " <> ttyMember m <> " from the group" <> withMessages wm] - CRLeftMember u g m -> ttyUser u [ttyGroup' g <> ": " <> ttyMember m <> " left the group"] - CRGroupEmpty u ShortGroupInfo {groupName = g} -> ttyUser u [ttyGroup g <> ": group is empty"] - CRGroupDeleted u g m -> ttyUser u [ttyGroup' g <> ": " <> ttyMember m <> " deleted the group", "use " <> highlight ("/d #" <> viewGroupName g) <> " to delete the local copy of the group"] CRGroupUpdated u g g' m -> ttyUser u $ viewGroupUpdated g g' m CRGroupProfile u g -> ttyUser u $ viewGroupProfile g CRGroupDescription u g -> ttyUser u $ viewGroupDescription g CRGroupLinkCreated u g ccLink mRole -> ttyUser u $ groupLink_ "Group link is created!" g ccLink mRole CRGroupLink u g ccLink mRole -> ttyUser u $ groupLink_ "Group link:" g ccLink mRole CRGroupLinkDeleted u g -> ttyUser u $ viewGroupLinkDeleted g - CRAcceptingGroupJoinRequestMember _ g m -> [ttyFullMember m <> ": accepting request to join group " <> ttyGroup' g <> "..."] - CRNoMemberContactCreating u g m -> ttyUser u ["member " <> ttyGroup' g <> " " <> ttyMember m <> " does not have direct connection, creating"] CRNewMemberContact u _ g m -> ttyUser u ["contact for member " <> ttyGroup' g <> " " <> ttyMember m <> " is created"] CRNewMemberContactSentInv u _ct g m -> ttyUser u ["sent invitation to connect directly to member " <> ttyGroup' g <> " " <> ttyMember m] - CRNewMemberContactReceivedInv u ct g m -> ttyUser u [ttyGroup' g <> " " <> ttyMember m <> " is creating direct contact " <> ttyContact' ct <> " with you"] - CRContactAndMemberAssociated u ct g m ct' -> ttyUser u $ viewContactAndMemberAssociated ct g m ct' - CRMemberSubError u ShortGroupInfo {groupName = g} ShortGroupMember {memberName = n} e -> ttyUser u [ttyGroup g <> " member " <> ttyContact n <> " error: " <> sShow e] - CRMemberSubSummary u summary -> ttyUser u $ viewErrorsSummary (filter (isJust . memberError) summary) " group member errors" - CRGroupSubscribed u ShortGroupInfo {groupName = g} -> ttyUser u $ viewGroupSubscribed g - CRPendingSubSummary u _ -> ttyUser u [] - CRSndFileSubError u SndFileTransfer {fileId, fileName} e -> - ttyUser u ["sent file " <> sShow fileId <> " (" <> plain fileName <> ") error: " <> sShow e] - CRRcvFileSubError u RcvFileTransfer {fileId, fileInvitation = FileInvitation {fileName}} e -> - ttyUser u ["received file " <> sShow fileId <> " (" <> plain fileName <> ") error: " <> sShow e] - CRCallInvitation RcvCallInvitation {user, contact, callType, sharedKey} -> ttyUser user $ viewCallInvitation contact callType sharedKey - CRCallOffer {user = u, contact, callType, offer, sharedKey} -> ttyUser u $ viewCallOffer contact callType offer sharedKey - CRCallAnswer {user = u, contact, answer} -> ttyUser u $ viewCallAnswer contact answer - CRCallExtraInfo {user = u, contact} -> ttyUser u ["call extra info from " <> ttyContact' contact] - CRCallEnded {user = u, contact} -> ttyUser u ["call with " <> ttyContact' contact <> " ended"] CRCallInvitations _ -> [] - CRUserContactLinkSubscribed -> ["Your address is active! To show: " <> highlight' "/sa"] - CRUserContactLinkSubError e -> ["user address error: " <> sShow e, "to delete your address: " <> highlight' "/da"] CRContactConnectionDeleted u PendingContactConnection {pccConnId} -> ttyUser u ["connection :" <> sShow pccConnId <> " deleted"] CRNtfTokenStatus status -> ["device token status: " <> plain (smpEncode status)] CRNtfToken _ status mode srv -> ["device token status: " <> plain (smpEncode status) <> ", notifications mode: " <> plain (strEncode mode) <> ", server: " <> sShow srv] CRNtfConns {ntfConns} -> map (\NtfConn {agentConnId, expectedMsg_} -> plain $ show agentConnId <> " " <> show expectedMsg_) ntfConns CRConnNtfMessages ntfMsgs -> [sShow ntfMsgs] - CRNtfMessage {} -> [] CRCurrentRemoteHost rhi_ -> [ maybe "Using local profile" @@ -359,40 +236,16 @@ responseToView hu@(currentRH, user_) cfg@ChatConfig {logLevel, showReactions, sh ] where started = " started on " <> B.unpack (strEncode address) <> ":" <> ctrlPort - CRRemoteHostSessionCode {remoteHost_, sessionCode} -> - [ maybe "new remote host connecting" (\RemoteHostInfo {remoteHostId = rhId} -> "remote host " <> sShow rhId <> " connecting") remoteHost_, - "Compare session code with host:", - plain sessionCode - ] - CRNewRemoteHost RemoteHostInfo {remoteHostId = rhId, hostDeviceName} -> ["new remote host " <> sShow rhId <> " added: " <> plain hostDeviceName] - CRRemoteHostConnected RemoteHostInfo {remoteHostId = rhId} -> ["remote host " <> sShow rhId <> " connected"] - CRRemoteHostStopped {remoteHostId_} -> - [ maybe "new remote host" (mappend "remote host " . sShow) remoteHostId_ <> " stopped" - ] CRRemoteFileStored rhId (CryptoFile filePath cfArgs_) -> [plain $ "file " <> filePath <> " stored on remote host " <> show rhId] <> maybe [] ((: []) . cryptoFileArgsStr testView) cfArgs_ CRRemoteCtrlList cs -> viewRemoteCtrls cs - CRRemoteCtrlFound {remoteCtrl = RemoteCtrlInfo {remoteCtrlId, ctrlDeviceName}, ctrlAppInfo_, appVersion, compatible} -> - [ ("remote controller " <> sShow remoteCtrlId <> " found: ") - <> maybe (deviceName <> "not compatible") (\info -> viewRemoteCtrl info appVersion compatible) ctrlAppInfo_ - ] - <> ["use " <> highlight ("/confirm remote ctrl " <> show remoteCtrlId) <> " to connect" | isJust ctrlAppInfo_ && compatible] - where - deviceName = if T.null ctrlDeviceName then "" else plain ctrlDeviceName <> ", " CRRemoteCtrlConnecting {remoteCtrl_, ctrlAppInfo, appVersion} -> [ (maybe "connecting new remote controller" (\RemoteCtrlInfo {remoteCtrlId} -> "connecting remote controller " <> sShow remoteCtrlId) remoteCtrl_ <> ": ") <> viewRemoteCtrl ctrlAppInfo appVersion True ] - CRRemoteCtrlSessionCode {remoteCtrl_, sessionCode} -> - [ maybe "new remote controller connected" (\RemoteCtrlInfo {remoteCtrlId} -> "remote controller " <> sShow remoteCtrlId <> " connected") remoteCtrl_, - "Compare session code with controller and use:", - "/verify remote ctrl " <> plain sessionCode -- TODO maybe pass rcId - ] CRRemoteCtrlConnected RemoteCtrlInfo {remoteCtrlId = rcId, ctrlDeviceName} -> ["remote controller " <> sShow rcId <> " session started with " <> plain ctrlDeviceName] - CRRemoteCtrlStopped {rcStopReason} -> viewRemoteCtrlStopped rcStopReason - CRContactPQEnabled u c (CR.PQEncryption pqOn) -> ttyUser u [ttyContact' c <> ": " <> (if pqOn then "quantum resistant" else "standard") <> " end-to-end encryption enabled"] CRSQLResult rows -> map plain rows #if !defined(dbPostgres) CRArchiveExported archiveErrs -> if null archiveErrs then ["ok"] else ["archive export errors: " <> plain (show archiveErrs)] @@ -433,41 +286,18 @@ responseToView hu@(currentRH, user_) cfg@ChatConfig {logLevel, showReactions, sh [ "agent queues info:", plain . LB.unpack $ J.encode agentQueuesInfo ] - CRContactDisabled u c -> ttyUser u ["[" <> ttyContact' c <> "] connection is disabled, to enable: " <> highlight ("/enable " <> viewContactName c) <> ", to delete: " <> highlight ("/d " <> viewContactName c)] - CRConnectionDisabled entity -> viewConnectionEntityDisabled entity - CRConnectionInactive entity inactive -> viewConnectionEntityInactive entity inactive - CRAgentRcvQueuesDeleted delQs -> ["completed deleting rcv queues: " <> sShow (length delQs) | logLevel <= CLLInfo] - CRAgentConnsDeleted acIds -> ["completed deleting connections: " <> sShow (length acIds) | logLevel <= CLLInfo] - CRAgentUserDeleted auId -> ["completed deleting user" <> if logLevel <= CLLInfo then ", agent user id: " <> sShow auId else ""] - CRMessageError u prefix err -> ttyUser u [plain prefix <> ": " <> plain err | prefix == "error" || logLevel <= CLLWarning] CRChatCmdError u e -> ttyUserPrefix' u $ viewChatError True logLevel testView e - CRChatError u e -> ttyUser' u $ viewChatError False logLevel testView e - CRChatErrors u errs -> ttyUser' u $ concatMap (viewChatError False logLevel testView) errs CRAppSettings as -> ["app settings: " <> viewJSON as] - CRTimedAction _ _ -> [] CRCustomChatResponse u r -> ttyUser' u $ map plain $ T.lines r - CRTerminalEvent te -> case te of - TERejectingGroupJoinRequestMember _ g m reason -> [ttyFullMember m <> ": rejecting request to join group " <> ttyGroup' g <> ", reason: " <> sShow reason] - TEGroupLinkRejected u g reason -> ttyUser u [ttyGroup' g <> ": join rejected, reason: " <> sShow reason] where ttyUser :: User -> [StyledString] -> [StyledString] ttyUser user@User {showNtfs, activeUser, viewPwdHash} ss - | (showNtfs && isNothing viewPwdHash) || activeUser = ttyUserPrefix user ss + | (showNtfs && isNothing viewPwdHash) || activeUser = ttyUserPrefix hu outputRH user ss | otherwise = [] - ttyUserPrefix :: User -> [StyledString] -> [StyledString] - ttyUserPrefix _ [] = [] - ttyUserPrefix User {userId, localDisplayName = u} ss - | null prefix = ss - | otherwise = prependFirst ("[" <> mconcat prefix <> "] ") ss - where - prefix = intersperse ", " $ remotePrefix <> userPrefix - remotePrefix = [maybe "local" (("remote: " <>) . highlight . show) outputRH | outputRH /= currentRH] - userPrefix = ["user: " <> highlight u | Just userId /= currentUserId] - currentUserId = (\User {userId = uId} -> uId) <$> user_ ttyUser' :: Maybe User -> [StyledString] -> [StyledString] ttyUser' = maybe id ttyUser ttyUserPrefix' :: Maybe User -> [StyledString] -> [StyledString] - ttyUserPrefix' = maybe id ttyUserPrefix + ttyUserPrefix' = maybe id $ ttyUserPrefix hu outputRH testViewChats :: [AChat] -> [StyledString] testViewChats chats = [sShow $ map toChatView chats] where @@ -498,10 +328,6 @@ responseToView hu@(currentRH, user_) cfg@ChatConfig {logLevel, showReactions, sh testViewItem (CChatItem _ ci@ChatItem {meta = CIMeta {itemText}}) membership_ = let deleted_ = maybe "" (\t -> " [" <> t <> "]") (chatItemDeletedText ci membership_) in itemText <> deleted_ - viewErrorsSummary :: [a] -> StyledString -> [StyledString] - viewErrorsSummary summary s = [ttyError (T.pack . show $ length summary) <> s <> " (run with -c option to show each error)" | not (null summary)] - contactList :: [ContactRef] -> String - contactList cs = T.unpack . T.intercalate ", " $ map (\ContactRef {localDisplayName = n} -> "@" <> n) cs unmuted :: User -> ChatInfo c -> ChatItem c d -> [StyledString] -> [StyledString] unmuted u chat ci@ChatItem {chatDir} = unmuted' u chat chatDir $ isUserMention ci unmutedReaction :: User -> ChatInfo c -> CIReaction c d -> [StyledString] -> [StyledString] @@ -513,6 +339,203 @@ responseToView hu@(currentRH, user_) cfg@ChatConfig {logLevel, showReactions, sh | otherwise = [] withMessages wm = if wm then " with all messages" else "" +ttyUserPrefix :: (Maybe RemoteHostId, Maybe User) -> Maybe RemoteHostId -> User -> [StyledString] -> [StyledString] +ttyUserPrefix _ _ _ [] = [] +ttyUserPrefix (currentRH, user_) outputRH User {userId, localDisplayName = u} ss + | null prefix = ss + | otherwise = prependFirst ("[" <> mconcat prefix <> "] ") ss + where + prefix = intersperse ", " $ remotePrefix <> userPrefix + remotePrefix = [maybe "local" (("remote: " <>) . highlight . show) outputRH | outputRH /= currentRH] + userPrefix = ["user: " <> highlight u | Just userId /= currentUserId] + currentUserId = (\User {userId = uId} -> uId) <$> user_ + +viewErrorsSummary :: [a] -> StyledString -> [StyledString] +viewErrorsSummary summary s = [ttyError (T.pack . show $ length summary) <> s <> " (run with -c option to show each error)" | not (null summary)] + +contactList :: [ContactRef] -> String +contactList cs = T.unpack . T.intercalate ", " $ map (\ContactRef {localDisplayName = n} -> "@" <> n) cs + +chatEventToView :: (Maybe RemoteHostId, Maybe User) -> ChatConfig -> Bool -> CurrentTime -> TimeZone -> Maybe RemoteHostId -> ChatEvent -> [StyledString] +chatEventToView hu ChatConfig {logLevel, showReactions, showReceipts, testView} liveItems ts tz outputRH = \case + CEvtChatSuspended -> ["chat suspended"] + CEvtContactSwitch u ct progress -> ttyUser u $ viewContactSwitch ct progress + CEvtGroupMemberSwitch u g m progress -> ttyUser u $ viewGroupMemberSwitch g m progress + CEvtContactRatchetSync u ct progress -> ttyUser u $ viewContactRatchetSync ct progress + CEvtGroupMemberRatchetSync u g m progress -> ttyUser u $ viewGroupMemberRatchetSync g m progress + CEvtNewChatItems u chatItems -> viewChatItems ttyUser unmuted u chatItems ts tz + CEvtChatItemsStatusesUpdated u chatItems + | length chatItems <= 20 -> + concatMap + (\ci -> ttyUser u $ viewChatItemStatusUpdated ci ts tz testView showReceipts) + chatItems + | testView && showReceipts -> + ttyUser u [sShow (length chatItems) <> " message statuses updated"] + | otherwise -> [] + CEvtChatItemUpdated u (AChatItem _ _ chat item) -> ttyUser u $ unmuted u chat item $ viewItemUpdate chat item liveItems ts tz + CEvtChatItemNotChanged u ci -> ttyUser u $ viewItemNotChanged ci + CEvtChatItemReaction u added (ACIReaction _ _ chat reaction) -> ttyUser u $ unmutedReaction u chat reaction $ viewItemReaction showReactions chat reaction added ts tz + CEvtChatItemsDeleted u deletions byUser timed -> ttyUser u $ viewChatItemsDeleted (unmuted u) deletions byUser timed ts tz testView + CEvtGroupChatItemsDeleted u g ciIds byUser member_ -> ttyUser u $ viewGroupChatItemsDeleted g ciIds byUser member_ + CEvtChatItemDeletedNotFound u Contact {localDisplayName = c} _ -> ttyUser u [ttyFrom $ c <> "> [deleted - original message not found]"] + CEvtUserAcceptedGroupSent u _g _ -> ttyUser u [] -- [ttyGroup' g <> ": joining the group..."] + CEvtSentGroupInvitation u g c _ -> ttyUser u $ viewSentGroupInvitation g c + CEvtContactDeletedByContact u c -> ttyUser u [ttyFullContact c <> " deleted contact with you"] + CEvtAcceptingContactRequest u c -> ttyUser u $ viewAcceptingContactRequest c + CEvtAcceptingBusinessRequest u g -> ttyUser u $ viewAcceptingBusinessRequest g + CEvtContactRequestAlreadyAccepted u c -> ttyUser u [ttyFullContact c <> ": sent you a duplicate contact request, but you are already connected, no action needed"] + CEvtBusinessRequestAlreadyAccepted u g -> ttyUser u [ttyFullGroup g <> ": sent you a duplicate connection request, but you are already connected, no action needed"] + CEvtGroupLinkConnecting u g _ -> ttyUser u [ttyGroup' g <> ": joining the group..."] + CEvtBusinessLinkConnecting u g _ _ -> ttyUser u [ttyGroup' g <> ": joining the group..."] + CEvtUnknownMemberCreated u g fwdM um -> ttyUser u [ttyGroup' g <> ": " <> ttyMember fwdM <> " forwarded a message from an unknown member, creating unknown member record " <> ttyMember um] + CEvtUnknownMemberBlocked u g byM um -> ttyUser u [ttyGroup' g <> ": " <> ttyMember byM <> " blocked an unknown member, creating unknown member record " <> ttyMember um] + CEvtUnknownMemberAnnounced u g _ um m -> ttyUser u [ttyGroup' g <> ": unknown member " <> ttyMember um <> " updated to " <> ttyMember m] + CEvtRcvFileDescrReady _ _ _ _ -> [] + CEvtRcvFileAccepted u ci -> ttyUser u $ savingFile' ci + CEvtRcvFileAcceptedSndCancelled u ft -> ttyUser u $ viewRcvFileSndCancelled ft + CEvtRcvFileProgressXFTP {} -> [] + CEvtContactUpdated {user = u, fromContact = c, toContact = c'} -> ttyUser u $ viewContactUpdated c c' <> viewContactPrefsUpdated u c c' + CEvtGroupMemberUpdated {} -> [] + CEvtContactsMerged u intoCt mergedCt ct' -> ttyUser u $ viewContactsMerged intoCt mergedCt ct' + CEvtReceivedContactRequest u UserContactRequest {localDisplayName = c, profile} -> ttyUser u $ viewReceivedContactRequest c profile + CEvtRcvFileStart u ci -> ttyUser u $ receivingFile_' hu testView "started" ci + CEvtRcvFileComplete u ci -> ttyUser u $ receivingFile_' hu testView "completed" ci + CEvtRcvStandaloneFileComplete u _ ft -> ttyUser u $ receivingFileStandalone "completed" ft + CEvtRcvFileSndCancelled u _ ft -> ttyUser u $ viewRcvFileSndCancelled ft + CEvtRcvFileError u (Just ci) e _ -> ttyUser u $ receivingFile_' hu testView "error" ci <> [sShow e] + CEvtRcvFileError u Nothing e ft -> ttyUser u $ receivingFileStandalone "error" ft <> [sShow e] + CEvtRcvFileWarning u (Just ci) e _ -> ttyUser u $ receivingFile_' hu testView "warning: " ci <> [sShow e] + CEvtRcvFileWarning u Nothing e ft -> ttyUser u $ receivingFileStandalone "warning: " ft <> [sShow e] + CEvtSndFileStart u _ ft -> ttyUser u $ sendingFile_ "started" ft + CEvtSndFileComplete u _ ft -> ttyUser u $ sendingFile_ "completed" ft + CEvtSndFileStartXFTP {} -> [] + CEvtSndFileProgressXFTP {} -> [] + CEvtSndFileRedirectStartXFTP u ft ftRedirect -> ttyUser u $ standaloneUploadRedirect ft ftRedirect + CEvtSndStandaloneFileComplete u ft uris -> ttyUser u $ standaloneUploadComplete ft uris + CEvtSndFileCompleteXFTP u ci _ -> ttyUser u $ uploadingFile "completed" ci + CEvtSndFileCancelledXFTP {} -> [] + CEvtSndFileError u Nothing ft e -> ttyUser u $ uploadingFileStandalone "error" ft <> [plain e] + CEvtSndFileError u (Just ci) _ e -> ttyUser u $ uploadingFile "error" ci <> [plain e] + CEvtSndFileWarning u Nothing ft e -> ttyUser u $ uploadingFileStandalone "warning: " ft <> [plain e] + CEvtSndFileWarning u (Just ci) _ e -> ttyUser u $ uploadingFile "warning: " ci <> [plain e] + CEvtSndFileRcvCancelled u _ ft@SndFileTransfer {recipientDisplayName = c} -> + ttyUser u [ttyContact c <> " cancelled receiving " <> sndFile ft] + CEvtContactConnecting u _ -> ttyUser u [] + CEvtContactConnected u ct userCustomProfile -> ttyUser u $ viewContactConnected ct userCustomProfile testView + CEvtContactSndReady u ct -> ttyUser u [ttyFullContact ct <> ": you can send messages to contact"] + CEvtContactAnotherClient u c -> ttyUser u [ttyContact' c <> ": contact is connected to another client"] + CEvtSubscriptionEnd u acEntity -> + let Connection {connId} = entityConnection acEntity + in ttyUser u [sShow connId <> ": END"] + CEvtContactsDisconnected srv cs -> [plain $ "server disconnected " <> showSMPServer srv <> " (" <> contactList cs <> ")"] + CEvtContactsSubscribed srv cs -> [plain $ "server connected " <> showSMPServer srv <> " (" <> contactList cs <> ")"] + CEvtContactSubError u c e -> ttyUser u [ttyContact' c <> ": contact error " <> sShow e] + CEvtContactSubSummary u summary -> + ttyUser u $ [sShow (length subscribed) <> " contacts connected (use " <> highlight' "/cs" <> " for the list)" | not (null subscribed)] <> viewErrorsSummary errors " contact errors" + where + (errors, subscribed) = partition (isJust . contactError) summary + CEvtUserContactSubSummary u summary -> + ttyUser u $ + map addressSS addresses + <> ([sShow (length groupLinksSubscribed) <> " group links active" | not (null groupLinksSubscribed)] <> viewErrorsSummary groupLinkErrors " group link errors") + where + (addresses, groupLinks) = partition (\UserContactSubStatus {userContact} -> isNothing . userContactGroupId $ userContact) summary + addressSS UserContactSubStatus {userContactError} = maybe ("Your address is active! To show: " <> highlight' "/sa") (\e -> "User address error: " <> sShow e <> ", to delete your address: " <> highlight' "/da") userContactError + (groupLinkErrors, groupLinksSubscribed) = partition (isJust . userContactError) groupLinks + CEvtNetworkStatus status conns -> if testView then [plain $ show (length conns) <> " connections " <> netStatusStr status] else [] + CEvtNetworkStatuses u statuses -> if testView then ttyUser' u $ viewNetworkStatuses statuses else [] + CEvtReceivedGroupInvitation {user = u, groupInfo = g, contact = c, memberRole = r} -> ttyUser u $ viewReceivedGroupInvitation g c r + CEvtUserJoinedGroup u g _ -> ttyUser u $ viewUserJoinedGroup g + CEvtJoinedGroupMember u g m -> ttyUser u $ viewJoinedGroupMember g m + CEvtHostConnected p h -> [plain $ "connected to " <> viewHostEvent p h] + CEvtHostDisconnected p h -> [plain $ "disconnected from " <> viewHostEvent p h] + CEvtJoinedGroupMemberConnecting u g host m -> ttyUser u [ttyGroup' g <> ": " <> ttyMember host <> " added " <> ttyFullMember m <> " to the group (connecting...)"] + CEvtConnectedToGroupMember u g m _ -> ttyUser u [ttyGroup' g <> ": " <> connectedMember m <> " is connected"] + CEvtMemberRole u g by m r r' -> ttyUser u $ viewMemberRoleChanged g by m r r' + CEvtMemberBlockedForAll u g by m blocked -> ttyUser u $ viewMemberBlockedForAll g by m blocked + CEvtDeletedMemberUser u g by wm -> ttyUser u $ [ttyGroup' g <> ": " <> ttyMember by <> " removed you from the group" <> withMessages wm] <> groupPreserved g + CEvtDeletedMember u g by m wm -> ttyUser u [ttyGroup' g <> ": " <> ttyMember by <> " removed " <> ttyMember m <> " from the group" <> withMessages wm] + CEvtLeftMember u g m -> ttyUser u [ttyGroup' g <> ": " <> ttyMember m <> " left the group"] + CEvtGroupDeleted u g m -> ttyUser u [ttyGroup' g <> ": " <> ttyMember m <> " deleted the group", "use " <> highlight ("/d #" <> viewGroupName g) <> " to delete the local copy of the group"] + CEvtGroupUpdated u g g' m -> ttyUser u $ viewGroupUpdated g g' m + CEvtAcceptingGroupJoinRequestMember _ g m -> [ttyFullMember m <> ": accepting request to join group " <> ttyGroup' g <> "..."] + CEvtNoMemberContactCreating u g m -> ttyUser u ["member " <> ttyGroup' g <> " " <> ttyMember m <> " does not have direct connection, creating"] + CEvtNewMemberContactReceivedInv u ct g m -> ttyUser u [ttyGroup' g <> " " <> ttyMember m <> " is creating direct contact " <> ttyContact' ct <> " with you"] + CEvtContactAndMemberAssociated u ct g m ct' -> ttyUser u $ viewContactAndMemberAssociated ct g m ct' + CEvtCallInvitation RcvCallInvitation {user, contact, callType, sharedKey} -> ttyUser user $ viewCallInvitation contact callType sharedKey + CEvtCallOffer {user = u, contact, callType, offer, sharedKey} -> ttyUser u $ viewCallOffer contact callType offer sharedKey + CEvtCallAnswer {user = u, contact, answer} -> ttyUser u $ viewCallAnswer contact answer + CEvtCallExtraInfo {user = u, contact} -> ttyUser u ["call extra info from " <> ttyContact' contact] + CEvtCallEnded {user = u, contact} -> ttyUser u ["call with " <> ttyContact' contact <> " ended"] + CEvtNtfMessage {} -> [] + CEvtRemoteHostSessionCode {remoteHost_, sessionCode} -> + [ maybe "new remote host connecting" (\RemoteHostInfo {remoteHostId = rhId} -> "remote host " <> sShow rhId <> " connecting") remoteHost_, + "Compare session code with host:", + plain sessionCode + ] + CEvtNewRemoteHost RemoteHostInfo {remoteHostId = rhId, hostDeviceName} -> ["new remote host " <> sShow rhId <> " added: " <> plain hostDeviceName] + CEvtRemoteHostConnected RemoteHostInfo {remoteHostId = rhId} -> ["remote host " <> sShow rhId <> " connected"] + CEvtRemoteHostStopped {remoteHostId_} -> + [ maybe "new remote host" (mappend "remote host " . sShow) remoteHostId_ <> " stopped" + ] + CEvtRemoteCtrlFound {remoteCtrl = RemoteCtrlInfo {remoteCtrlId, ctrlDeviceName}, ctrlAppInfo_, appVersion, compatible} -> + [ ("remote controller " <> sShow remoteCtrlId <> " found: ") + <> maybe (deviceName <> "not compatible") (\info -> viewRemoteCtrl info appVersion compatible) ctrlAppInfo_ + ] + <> ["use " <> highlight ("/confirm remote ctrl " <> show remoteCtrlId) <> " to connect" | isJust ctrlAppInfo_ && compatible] + where + deviceName = if T.null ctrlDeviceName then "" else plain ctrlDeviceName <> ", " + CEvtRemoteCtrlSessionCode {remoteCtrl_, sessionCode} -> + [ maybe "new remote controller connected" (\RemoteCtrlInfo {remoteCtrlId} -> "remote controller " <> sShow remoteCtrlId <> " connected") remoteCtrl_, + "Compare session code with controller and use:", + "/verify remote ctrl " <> plain sessionCode -- TODO maybe pass rcId + ] + CEvtRemoteCtrlStopped {rcStopReason} -> viewRemoteCtrlStopped rcStopReason + CEvtContactPQEnabled u c (CR.PQEncryption pqOn) -> ttyUser u [ttyContact' c <> ": " <> (if pqOn then "quantum resistant" else "standard") <> " end-to-end encryption enabled"] + CEvtContactDisabled u c -> ttyUser u ["[" <> ttyContact' c <> "] connection is disabled, to enable: " <> highlight ("/enable " <> viewContactName c) <> ", to delete: " <> highlight ("/d " <> viewContactName c)] + CEvtConnectionDisabled entity -> viewConnectionEntityDisabled entity + CEvtConnectionInactive entity inactive -> viewConnectionEntityInactive entity inactive + CEvtAgentRcvQueuesDeleted delQs -> ["completed deleting rcv queues: " <> sShow (length delQs) | logLevel <= CLLInfo] + CEvtAgentConnsDeleted acIds -> ["completed deleting connections: " <> sShow (length acIds) | logLevel <= CLLInfo] + CEvtAgentUserDeleted auId -> ["completed deleting user" <> if logLevel <= CLLInfo then ", agent user id: " <> sShow auId else ""] + CEvtMessageError u prefix err -> ttyUser u [plain prefix <> ": " <> plain err | prefix == "error" || logLevel <= CLLWarning] + CEvtChatError u e -> ttyUser' u $ viewChatError False logLevel testView e + CEvtChatErrors u errs -> ttyUser' u $ concatMap (viewChatError False logLevel testView) errs + CEvtTimedAction _ _ -> [] + CEvtTerminalEvent te -> case te of + TERejectingGroupJoinRequestMember _ g m reason -> [ttyFullMember m <> ": rejecting request to join group " <> ttyGroup' g <> ", reason: " <> sShow reason] + TEGroupLinkRejected u g reason -> ttyUser u [ttyGroup' g <> ": join rejected, reason: " <> sShow reason] + TENewMemberContact u _ g m -> ttyUser u ["contact for member " <> ttyGroup' g <> " " <> ttyMember m <> " is created"] + TEContactVerificationReset u ct -> ttyUser u $ viewContactVerificationReset ct + TEGroupMemberVerificationReset u g m -> ttyUser u $ viewGroupMemberVerificationReset g m + TEGroupSubscribed u ShortGroupInfo {groupName = g} -> ttyUser u $ viewGroupSubscribed g + TEGroupInvitation u g -> ttyUser u [groupInvitationSub g] + TEGroupEmpty u ShortGroupInfo {groupName = g} -> ttyUser u [ttyGroup g <> ": group is empty"] + TEMemberSubError u ShortGroupInfo {groupName = g} ShortGroupMember {memberName = n} e -> ttyUser u [ttyGroup g <> " member " <> ttyContact n <> " error: " <> sShow e] + TEMemberSubSummary u summary -> ttyUser u $ viewErrorsSummary (filter (isJust . memberError) summary) " group member errors" + TEPendingSubSummary u _ -> ttyUser u [] + TESndFileSubError u SndFileTransfer {fileId, fileName} e -> + ttyUser u ["sent file " <> sShow fileId <> " (" <> plain fileName <> ") error: " <> sShow e] + TERcvFileSubError u RcvFileTransfer {fileId, fileInvitation = FileInvitation {fileName}} e -> + ttyUser u ["received file " <> sShow fileId <> " (" <> plain fileName <> ") error: " <> sShow e] + where + ttyUser :: User -> [StyledString] -> [StyledString] + ttyUser user@User {showNtfs, activeUser, viewPwdHash} ss + | (showNtfs && isNothing viewPwdHash) || activeUser = ttyUserPrefix hu outputRH user ss + | otherwise = [] + ttyUser' :: Maybe User -> [StyledString] -> [StyledString] + ttyUser' = maybe id ttyUser + withMessages wm = if wm then " with all messages" else "" + unmuted :: User -> ChatInfo c -> ChatItem c d -> [StyledString] -> [StyledString] + unmuted u chat ci@ChatItem {chatDir} = unmuted' u chat chatDir $ isUserMention ci + unmutedReaction :: User -> ChatInfo c -> CIReaction c d -> [StyledString] -> [StyledString] + unmutedReaction u chat CIReaction {chatDir} = unmuted' u chat chatDir False + unmuted' :: User -> ChatInfo c -> CIDirection c d -> Bool -> [StyledString] -> [StyledString] + unmuted' u chat chatDir mention s + | chatDirNtf u chat chatDir mention = s + | testView = map (<> " ") s + | otherwise = [] + userNtf :: User -> Bool userNtf User {showNtfs, activeUser} = showNtfs || activeUser @@ -592,6 +615,23 @@ viewChats ts tz = concatMap chatPreview . reverse GroupChat g -> [" " <> ttyToGroup g] _ -> [] +viewChatItems :: + (User -> [StyledString] -> [StyledString]) -> + (forall c d. User -> ChatInfo c -> ChatItem c d -> [StyledString] -> [StyledString]) -> + User -> + [AChatItem] -> + UTCTime -> + TimeZone -> + [StyledString] +viewChatItems ttyUser unmuted u chatItems ts tz + | length chatItems <= 20 = + concatMap + (\(AChatItem _ _ chat item) -> ttyUser u $ unmuted u chat item $ viewChatItem chat item False ts tz <> viewItemReactions item) + chatItems + | all (\aci -> aChatItemDir aci == MDRcv) chatItems = ttyUser u [sShow (length chatItems) <> " new messages"] + | all (\aci -> aChatItemDir aci == MDSnd) chatItems = ttyUser u [sShow (length chatItems) <> " messages sent"] + | otherwise = ttyUser u [sShow (length chatItems) <> " new messages created"] + viewChatItem :: forall c d. MsgDirectionI d => ChatInfo c -> ChatItem c d -> Bool -> CurrentTime -> TimeZone -> [StyledString] viewChatItem chat ci@ChatItem {chatDir, meta = meta@CIMeta {itemForwarded, forwardedByMember, userMention}, content, quotedItem, file} doShow ts tz = withGroupMsgForwarded . withItemDeleted <$> viewCI @@ -725,14 +765,10 @@ localTs tz ts = do viewChatItemStatusUpdated :: AChatItem -> CurrentTime -> TimeZone -> Bool -> Bool -> [StyledString] viewChatItemStatusUpdated (AChatItem _ _ chat item@ChatItem {meta = CIMeta {itemStatus}}) ts tz testView showReceipts = case itemStatus of - CISSndRcvd rcptStatus SSPPartial -> - if testView && showReceipts - then prependFirst (viewDeliveryReceiptPartial rcptStatus <> " ") $ viewChatItem chat item False ts tz - else [] - CISSndRcvd rcptStatus SSPComplete -> - if testView && showReceipts - then prependFirst (viewDeliveryReceipt rcptStatus <> " ") $ viewChatItem chat item False ts tz - else [] + CISSndRcvd rcptStatus SSPPartial | testView && showReceipts -> + prependFirst (viewDeliveryReceiptPartial rcptStatus <> " ") $ viewChatItem chat item False ts tz + CISSndRcvd rcptStatus SSPComplete | testView && showReceipts -> + prependFirst (viewDeliveryReceipt rcptStatus <> " ") $ viewChatItem chat item False ts tz _ -> [] viewDeliveryReceiptPartial :: MsgReceiptStatus -> StyledString @@ -796,6 +832,23 @@ viewItemNotChanged (AChatItem _ msgDir _ _) = case msgDir of SMDSnd -> ["message didn't change"] SMDRcv -> [] +viewChatItemsDeleted :: + (forall c d. ChatInfo c -> ChatItem c d -> [StyledString] -> [StyledString]) -> + [ChatItemDeletion] -> + Bool -> + Bool -> + UTCTime -> + TimeZone -> + Bool -> + [StyledString] +viewChatItemsDeleted unmuted deletions byUser timed ts tz testView = case deletions of + [ChatItemDeletion (AChatItem _ _ chat deletedItem) toItem] -> + unmuted chat deletedItem $ viewItemDelete chat deletedItem toItem byUser timed ts tz testView + deletions' -> [sShow (length deletions') <> " messages deleted"] + +viewGroupChatItemsDeleted :: GroupInfo -> [ChatItemId] -> Bool -> Maybe GroupMember -> [StyledString] +viewGroupChatItemsDeleted g ciIds byUser member_ = [ttyGroup' g <> ": " <> sShow (length ciIds) <> " messages deleted by " <> if byUser then "user" else "member" <> maybe "" (\m -> " " <> ttyMember m) member_] + viewItemDelete :: ChatInfo c -> ChatItem c d -> Maybe AChatItem -> Bool -> Bool -> CurrentTime -> TimeZone -> Bool -> [StyledString] viewItemDelete chat ci@ChatItem {chatDir, meta, content = deletedContent} toItem byUser timed ts tz testView | timed = [plain ("timed message deleted: " <> T.unpack (ciContentToText deletedContent)) | testView] @@ -1222,6 +1275,13 @@ viewGroupsList gs = map groupSS $ sortOn (ldn_ . fst) gs | localAlias == "" = "" | otherwise = " (alias: " <> plain localAlias <> ")" +viewSentGroupInvitation :: GroupInfo -> Contact -> [StyledString] +viewSentGroupInvitation g c = case contactConn c of + Just Connection {viaGroupLink} + | viaGroupLink -> [ttyContact' c <> " invited to group " <> ttyGroup' g <> " via your group link"] + | otherwise -> ["invitation to join the group " <> ttyGroup' g <> " sent to " <> ttyContact' c] + Nothing -> [] + groupInvitation' :: GroupInfo -> StyledString groupInvitation' g@GroupInfo {localDisplayName = ldn, groupProfile = GroupProfile {fullName}} = highlight ("#" <> viewName ldn) From 24b0f0290b6218df9c84b148712fd1f2a76041b0 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 5 May 2025 11:51:22 +0100 Subject: [PATCH 530/567] core: pass event and response error without dedicated constructor (#5869) * core: pass event and response error without dedicated constructor * ios: WIP * android, desktop: update UI for new API * ios: fix parser * fix showing invalid chats * fix mobile api tests * ios: split ChatResponse to 3 enums, decode API results on the same thread * tweak types * remove throws * rename --- apps/ios/Shared/AppDelegate.swift | 2 +- apps/ios/Shared/Model/AppAPITypes.swift | 636 ++++------ apps/ios/Shared/Model/ChatModel.swift | 13 +- apps/ios/Shared/Model/SimpleXAPI.swift | 817 ++++++------ .../Shared/Views/Call/ActiveCallView.swift | 2 +- .../Chat/ChatItem/CIInvalidJSONView.swift | 7 +- .../Views/ChatList/ChatListNavLink.swift | 6 +- .../Database/DatabaseEncryptionView.swift | 2 +- .../Views/Migration/MigrateFromDevice.swift | 22 +- .../Views/Migration/MigrateToDevice.swift | 18 +- .../Views/Onboarding/CreateProfile.swift | 8 +- .../RemoteAccess/ConnectDesktopView.swift | 12 +- apps/ios/Shared/Views/TerminalView.swift | 42 +- apps/ios/SimpleX NSE/NSEAPITypes.swift | 114 +- .../ios/SimpleX NSE/NotificationService.swift | 85 +- apps/ios/SimpleX SE/ShareAPI.swift | 181 +-- apps/ios/SimpleX SE/ShareModel.swift | 14 +- apps/ios/SimpleXChat/API.swift | 140 +- apps/ios/SimpleXChat/APITypes.swift | 130 +- apps/ios/SimpleXChat/ChatTypes.swift | 8 +- apps/ios/SimpleXChat/CryptoFile.swift | 8 +- apps/ios/SimpleXChat/ErrorAlert.swift | 16 +- .../java/chat/simplex/app/MainActivity.kt | 2 +- .../chat/simplex/common/model/ChatModel.kt | 14 +- .../chat/simplex/common/model/SimpleXAPI.kt | 1130 ++++++++--------- .../chat/simplex/common/views/TerminalView.kt | 2 +- .../simplex/common/views/call/CallView.kt | 10 +- .../simplex/common/views/chat/ChatView.kt | 10 +- .../views/database/DatabaseEncryptionView.kt | 4 +- .../views/migration/MigrateFromDevice.kt | 26 +- .../common/views/migration/MigrateToDevice.kt | 32 +- .../common/views/remote/ConnectDesktopView.kt | 14 +- .../views/usersettings/PrivacySettings.kt | 4 +- apps/simplex-bot-advanced/Main.hs | 4 +- .../src/Broadcast/Bot.hs | 6 +- apps/simplex-chat/Server.hs | 42 +- .../src/Directory/Events.hs | 14 +- .../src/Directory/Service.hs | 48 +- simplex-chat.cabal | 2 + src/Simplex/Chat/Bot.hs | 14 +- src/Simplex/Chat/Controller.hs | 42 +- src/Simplex/Chat/Core.hs | 25 +- src/Simplex/Chat/Library/Commands.hs | 193 ++- src/Simplex/Chat/Library/Internal.hs | 94 +- src/Simplex/Chat/Library/Subscriber.hs | 82 +- src/Simplex/Chat/Mobile.hs | 22 +- src/Simplex/Chat/Remote.hs | 21 +- src/Simplex/Chat/Remote/Protocol.hs | 48 +- src/Simplex/Chat/Terminal/Input.hs | 15 +- src/Simplex/Chat/Terminal/Main.hs | 15 +- src/Simplex/Chat/Terminal/Output.hs | 29 +- src/Simplex/Chat/View.hs | 27 +- tests/JSONFixtures.hs | 32 +- tests/MobileTests.hs | 2 +- 54 files changed, 2131 insertions(+), 2177 deletions(-) diff --git a/apps/ios/Shared/AppDelegate.swift b/apps/ios/Shared/AppDelegate.swift index ad8c661e1c..3f6998c9ec 100644 --- a/apps/ios/Shared/AppDelegate.swift +++ b/apps/ios/Shared/AppDelegate.swift @@ -54,7 +54,7 @@ class AppDelegate: NSObject, UIApplicationDelegate { try await apiVerifyToken(token: token, nonce: nonce, code: verification) m.tokenStatus = .active } catch { - if let cr = error as? ChatResponse, case .chatCmdError(_, .errorAgent(.NTF(.AUTH))) = cr { + if let cr = error as? ChatError, case .errorAgent(.NTF(.AUTH)) = cr { m.tokenStatus = .expired } logger.error("AppDelegate: didReceiveRemoteNotification: apiVerifyToken or apiIntervalNofication error: \(responseError(error))") diff --git a/apps/ios/Shared/Model/AppAPITypes.swift b/apps/ios/Shared/Model/AppAPITypes.swift index 37d016e93d..d5a067a2b8 100644 --- a/apps/ios/Shared/Model/AppAPITypes.swift +++ b/apps/ios/Shared/Model/AppAPITypes.swift @@ -580,8 +580,8 @@ enum ChatCommand: ChatCmdProtocol { } } -enum ChatResponse: Decodable, Error, ChatRespProtocol { - case response(type: String, json: String) +// ChatResponse is split to three enums to reduce stack size used when parsing it, parsing large enums is very inefficient. +enum ChatResponse0: Decodable, ChatAPIResult { case activeUser(user: User) case usersList(users: [UserInfo]) case chatStarted @@ -611,6 +611,95 @@ enum ChatResponse: Decodable, Error, ChatRespProtocol { case groupMemberCode(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionCode: String) case connectionVerified(user: UserRef, verified: Bool, expectedCode: String) case tagsUpdated(user: UserRef, userTags: [ChatTag], chatTags: [Int64]) + + var responseType: String { + switch self { + case .activeUser: "activeUser" + case .usersList: "usersList" + case .chatStarted: "chatStarted" + case .chatRunning: "chatRunning" + case .chatStopped: "chatStopped" + case .apiChats: "apiChats" + case .apiChat: "apiChat" + case .chatTags: "chatTags" + case .chatItemInfo: "chatItemInfo" + case .serverTestResult: "serverTestResult" + case .serverOperatorConditions: "serverOperators" + case .userServers: "userServers" + case .userServersValidation: "userServersValidation" + case .usageConditions: "usageConditions" + case .chatItemTTL: "chatItemTTL" + case .networkConfig: "networkConfig" + case .contactInfo: "contactInfo" + case .groupMemberInfo: "groupMemberInfo" + case .queueInfo: "queueInfo" + case .contactSwitchStarted: "contactSwitchStarted" + case .groupMemberSwitchStarted: "groupMemberSwitchStarted" + case .contactSwitchAborted: "contactSwitchAborted" + case .groupMemberSwitchAborted: "groupMemberSwitchAborted" + case .contactRatchetSyncStarted: "contactRatchetSyncStarted" + case .groupMemberRatchetSyncStarted: "groupMemberRatchetSyncStarted" + case .contactCode: "contactCode" + case .groupMemberCode: "groupMemberCode" + case .connectionVerified: "connectionVerified" + case .tagsUpdated: "tagsUpdated" + } + } + + var details: String { + switch self { + case let .activeUser(user): return String(describing: user) + case let .usersList(users): return String(describing: users) + case .chatStarted: return noDetails + case .chatRunning: return noDetails + case .chatStopped: return noDetails + case let .apiChats(u, chats): return withUser(u, String(describing: chats)) + case let .apiChat(u, chat, navInfo): return withUser(u, "chat: \(String(describing: chat))\nnavInfo: \(String(describing: navInfo))") + case let .chatTags(u, userTags): return withUser(u, "userTags: \(String(describing: userTags))") + case let .chatItemInfo(u, chatItem, chatItemInfo): return withUser(u, "chatItem: \(String(describing: chatItem))\nchatItemInfo: \(String(describing: chatItemInfo))") + case let .serverTestResult(u, server, testFailure): return withUser(u, "server: \(server)\nresult: \(String(describing: testFailure))") + case let .serverOperatorConditions(conditions): return "conditions: \(String(describing: conditions))" + case let .userServers(u, userServers): return withUser(u, "userServers: \(String(describing: userServers))") + case let .userServersValidation(u, serverErrors): return withUser(u, "serverErrors: \(String(describing: serverErrors))") + case let .usageConditions(usageConditions, _, acceptedConditions): return "usageConditions: \(String(describing: usageConditions))\nacceptedConditions: \(String(describing: acceptedConditions))" + 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 .groupMemberInfo(u, groupInfo, member, connectionStats_): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats_: \(String(describing: connectionStats_))") + case let .queueInfo(u, rcvMsgInfo, queueInfo): + let msgInfo = if let info = rcvMsgInfo { encodeJSON(info) } else { "none" } + return withUser(u, "rcvMsgInfo: \(msgInfo)\nqueueInfo: \(encodeJSON(queueInfo))") + 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))") + case let .contactSwitchAborted(u, contact, connectionStats): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))") + case let .groupMemberSwitchAborted(u, groupInfo, member, connectionStats): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats: \(String(describing: connectionStats))") + case let .contactRatchetSyncStarted(u, contact, connectionStats): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))") + case let .groupMemberRatchetSyncStarted(u, groupInfo, member, connectionStats): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats: \(String(describing: connectionStats))") + case let .contactCode(u, contact, connectionCode): return withUser(u, "contact: \(String(describing: contact))\nconnectionCode: \(connectionCode)") + case let .groupMemberCode(u, groupInfo, member, connectionCode): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionCode: \(connectionCode)") + case let .connectionVerified(u, verified, expectedCode): return withUser(u, "verified: \(verified)\nconnectionCode: \(expectedCode)") + case let .tagsUpdated(u, userTags, chatTags): return withUser(u, "userTags: \(String(describing: userTags))\nchatTags: \(String(describing: chatTags))") + } + } + + static func fallbackResult(_ type: String, _ json: NSDictionary) -> ChatResponse0? { + if type == "apiChats" { + if let r = parseApiChats(json) { + return .apiChats(user: r.user, chats: r.chats) + } + } else if type == "apiChat" { + if let jApiChat = json["apiChat"] as? NSDictionary, + let user: UserRef = try? decodeObject(jApiChat["user"] as Any), + let jChat = jApiChat["chat"] as? NSDictionary, + let (chat, navInfo) = try? parseChatData(jChat, jApiChat["navInfo"] as? NSDictionary) { + return .apiChat(user: user, chat: chat, navInfo: navInfo) + } + } + return nil + } +} + +enum ChatResponse1: Decodable, ChatAPIResult { case invitation(user: UserRef, connLinkInvitation: CreatedConnLink, connection: PendingContactConnection) case connectionIncognitoUpdated(user: UserRef, toConnection: PendingContactConnection) case connectionUserChanged(user: UserRef, fromConnection: PendingContactConnection, toConnection: PendingContactConnection, newUser: UserRef) @@ -620,6 +709,8 @@ enum ChatResponse: Decodable, Error, ChatRespProtocol { case sentInvitationToContact(user: UserRef, contact: Contact, customUserProfile: Profile?) case contactAlreadyExists(user: UserRef, contact: Contact) case contactDeleted(user: UserRef, contact: Contact) + case contactConnectionDeleted(user: UserRef, connection: PendingContactConnection) + case groupDeletedUser(user: UserRef, groupInfo: GroupInfo) case chatCleared(user: UserRef, chatInfo: ChatInfo) case userProfileNoChange(user: User) case userProfileUpdated(user: User, fromProfile: Profile, toProfile: Profile, updateSummary: UserProfileUpdateSummary) @@ -644,6 +735,95 @@ enum ChatResponse: Decodable, Error, ChatRespProtocol { case reactionMembers(user: UserRef, memberReactions: [MemberReaction]) case chatItemsDeleted(user: UserRef, chatItemDeletions: [ChatItemDeletion], byUser: Bool) case contactsList(user: UserRef, contacts: [Contact]) + + var responseType: String { + switch self { + case .invitation: "invitation" + case .connectionIncognitoUpdated: "connectionIncognitoUpdated" + case .connectionUserChanged: "connectionUserChanged" + case .connectionPlan: "connectionPlan" + case .sentConfirmation: "sentConfirmation" + case .sentInvitation: "sentInvitation" + case .sentInvitationToContact: "sentInvitationToContact" + case .contactAlreadyExists: "contactAlreadyExists" + case .contactDeleted: "contactDeleted" + case .contactConnectionDeleted: "contactConnectionDeleted" + case .groupDeletedUser: "groupDeletedUser" + case .chatCleared: "chatCleared" + case .userProfileNoChange: "userProfileNoChange" + case .userProfileUpdated: "userProfileUpdated" + case .userPrivacy: "userPrivacy" + case .contactAliasUpdated: "contactAliasUpdated" + case .groupAliasUpdated: "groupAliasUpdated" + case .connectionAliasUpdated: "connectionAliasUpdated" + case .contactPrefsUpdated: "contactPrefsUpdated" + case .userContactLink: "userContactLink" + case .userContactLinkUpdated: "userContactLinkUpdated" + case .userContactLinkCreated: "userContactLinkCreated" + case .userContactLinkDeleted: "userContactLinkDeleted" + case .acceptingContactRequest: "acceptingContactRequest" + case .contactRequestRejected: "contactRequestRejected" + case .networkStatuses: "networkStatuses" + case .newChatItems: "newChatItems" + case .groupChatItemsDeleted: "groupChatItemsDeleted" + case .forwardPlan: "forwardPlan" + case .chatItemUpdated: "chatItemUpdated" + case .chatItemNotChanged: "chatItemNotChanged" + case .chatItemReaction: "chatItemReaction" + case .reactionMembers: "reactionMembers" + case .chatItemsDeleted: "chatItemsDeleted" + case .contactsList: "contactsList" + } + } + + var details: String { + switch self { + case let .contactDeleted(u, contact): return withUser(u, String(describing: contact)) + case let .contactConnectionDeleted(u, connection): return withUser(u, String(describing: connection)) + case let .groupDeletedUser(u, groupInfo): return withUser(u, String(describing: groupInfo)) + case let .chatCleared(u, chatInfo): return withUser(u, String(describing: chatInfo)) + case .userProfileNoChange: return noDetails + case let .userProfileUpdated(u, _, toProfile, _): return withUser(u, String(describing: toProfile)) + case let .userPrivacy(u, updatedUser): return withUser(u, String(describing: updatedUser)) + case let .contactAliasUpdated(u, toContact): return withUser(u, String(describing: toContact)) + case let .groupAliasUpdated(u, toGroup): return withUser(u, String(describing: toGroup)) + case let .connectionAliasUpdated(u, toConnection): return withUser(u, String(describing: toConnection)) + case let .contactPrefsUpdated(u, fromContact, toContact): return withUser(u, "fromContact: \(String(describing: fromContact))\ntoContact: \(String(describing: toContact))") + case let .userContactLink(u, contactLink): return withUser(u, contactLink.responseDetails) + case let .userContactLinkUpdated(u, contactLink): return withUser(u, contactLink.responseDetails) + case let .userContactLinkCreated(u, connLink): return withUser(u, String(describing: connLink)) + case .userContactLinkDeleted: return noDetails + case let .acceptingContactRequest(u, contact): return withUser(u, String(describing: contact)) + case .contactRequestRejected: return noDetails + case let .networkStatuses(u, statuses): return withUser(u, String(describing: statuses)) + case let .newChatItems(u, chatItems): + let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") + return withUser(u, itemsString) + case let .groupChatItemsDeleted(u, gInfo, chatItemIDs, byUser, member_): + return withUser(u, "chatItemIDs: \(String(describing: chatItemIDs))\ngroupInfo: \(String(describing: gInfo))\nbyUser: \(byUser)\nmember_: \(String(describing: member_))") + case let .forwardPlan(u, chatItemIds, forwardConfirmation): return withUser(u, "items: \(chatItemIds) forwardConfirmation: \(String(describing: forwardConfirmation))") + case let .chatItemUpdated(u, chatItem): return withUser(u, String(describing: chatItem)) + case let .chatItemNotChanged(u, chatItem): return withUser(u, String(describing: chatItem)) + case let .chatItemReaction(u, added, reaction): return withUser(u, "added: \(added)\n\(String(describing: reaction))") + case let .reactionMembers(u, reaction): return withUser(u, "memberReactions: \(String(describing: reaction))") + case let .chatItemsDeleted(u, items, byUser): + let itemsString = items.map { item in + "deletedChatItem:\n\(String(describing: item.deletedChatItem))\ntoChatItem:\n\(String(describing: item.toChatItem))" }.joined(separator: "\n") + return withUser(u, itemsString + "\nbyUser: \(byUser)") + case let .contactsList(u, contacts): return withUser(u, String(describing: contacts)) + case let .invitation(u, connLinkInvitation, connection): return withUser(u, "connLinkInvitation: \(connLinkInvitation)\nconnection: \(connection)") + case let .connectionIncognitoUpdated(u, toConnection): return withUser(u, String(describing: toConnection)) + case let .connectionUserChanged(u, fromConnection, toConnection, newUser): return withUser(u, "fromConnection: \(String(describing: fromConnection))\ntoConnection: \(String(describing: toConnection))\nnewUserId: \(String(describing: newUser.userId))") + case let .connectionPlan(u, connLink, connectionPlan): return withUser(u, "connLink: \(String(describing: connLink))\nconnectionPlan: \(String(describing: connectionPlan))") + case let .sentConfirmation(u, connection): return withUser(u, String(describing: connection)) + case let .sentInvitation(u, connection): return withUser(u, String(describing: connection)) + case let .sentInvitationToContact(u, contact, _): return withUser(u, String(describing: contact)) + case let .contactAlreadyExists(u, contact): return withUser(u, String(describing: contact)) + } + } +} + +enum ChatResponse2: Decodable, ChatAPIResult { // group responses case groupCreated(user: UserRef, groupInfo: GroupInfo) case sentGroupInvitation(user: UserRef, groupInfo: GroupInfo, contact: Contact, member: GroupMember) @@ -651,7 +831,6 @@ enum ChatResponse: Decodable, Error, ChatRespProtocol { case userDeletedMembers(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], withMessages: Bool) case leftMemberUser(user: UserRef, groupInfo: GroupInfo) case groupMembers(user: UserRef, group: SimpleXChat.Group) - case groupDeletedUser(user: UserRef, groupInfo: GroupInfo) case membersRoleUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], toRole: GroupMemberRole) case membersBlockedForAllUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], blocked: Bool) case groupUpdated(user: UserRef, toGroup: GroupInfo) @@ -678,7 +857,6 @@ enum ChatResponse: Decodable, Error, ChatRespProtocol { case ntfToken(token: DeviceToken, status: NtfTknStatus, ntfMode: NotificationsMode, ntfServer: String) case ntfConns(ntfConns: [NtfConn]) case connNtfMessages(receivedMsgs: [NtfMsgInfo?]) - case contactConnectionDeleted(user: UserRef, connection: PendingContactConnection) // remote desktop responses case remoteCtrlList(remoteCtrls: [RemoteCtrlInfo]) case remoteCtrlConnecting(remoteCtrl_: RemoteCtrlInfo?, ctrlAppInfo: CtrlAppInfo, appVersion: String) @@ -689,306 +867,100 @@ enum ChatResponse: Decodable, Error, ChatRespProtocol { case agentSubsTotal(user: UserRef, subsTotal: SMPServerSubs, hasSession: Bool) case agentServersSummary(user: UserRef, serversSummary: PresentedServersSummary) case agentSubsSummary(user: UserRef, subsSummary: SMPServerSubs) - case chatCmdError(user_: UserRef?, chatError: ChatError) case archiveExported(archiveErrors: [ArchiveError]) case archiveImported(archiveErrors: [ArchiveError]) case appSettings(appSettings: AppSettings) var responseType: String { - get { - switch self { - case let .response(type, _): return "* \(type)" - case .activeUser: return "activeUser" - case .usersList: return "usersList" - case .chatStarted: return "chatStarted" - case .chatRunning: return "chatRunning" - case .chatStopped: return "chatStopped" - case .apiChats: return "apiChats" - case .apiChat: return "apiChat" - case .chatTags: return "chatTags" - case .chatItemInfo: return "chatItemInfo" - case .serverTestResult: return "serverTestResult" - case .serverOperatorConditions: return "serverOperators" - case .userServers: return "userServers" - case .userServersValidation: return "userServersValidation" - case .usageConditions: return "usageConditions" - case .chatItemTTL: return "chatItemTTL" - case .networkConfig: return "networkConfig" - case .contactInfo: return "contactInfo" - case .groupMemberInfo: return "groupMemberInfo" - case .queueInfo: return "queueInfo" - case .contactSwitchStarted: return "contactSwitchStarted" - case .groupMemberSwitchStarted: return "groupMemberSwitchStarted" - case .contactSwitchAborted: return "contactSwitchAborted" - case .groupMemberSwitchAborted: return "groupMemberSwitchAborted" - case .contactRatchetSyncStarted: return "contactRatchetSyncStarted" - case .groupMemberRatchetSyncStarted: return "groupMemberRatchetSyncStarted" - case .contactCode: return "contactCode" - case .groupMemberCode: return "groupMemberCode" - case .connectionVerified: return "connectionVerified" - case .tagsUpdated: return "tagsUpdated" - case .invitation: return "invitation" - case .connectionIncognitoUpdated: return "connectionIncognitoUpdated" - case .connectionUserChanged: return "connectionUserChanged" - case .connectionPlan: return "connectionPlan" - case .sentConfirmation: return "sentConfirmation" - case .sentInvitation: return "sentInvitation" - case .sentInvitationToContact: return "sentInvitationToContact" - case .contactAlreadyExists: return "contactAlreadyExists" - case .contactDeleted: return "contactDeleted" - case .chatCleared: return "chatCleared" - case .userProfileNoChange: return "userProfileNoChange" - case .userProfileUpdated: return "userProfileUpdated" - case .userPrivacy: return "userPrivacy" - case .contactAliasUpdated: return "contactAliasUpdated" - case .groupAliasUpdated: return "groupAliasUpdated" - case .connectionAliasUpdated: return "connectionAliasUpdated" - case .contactPrefsUpdated: return "contactPrefsUpdated" - case .userContactLink: return "userContactLink" - case .userContactLinkUpdated: return "userContactLinkUpdated" - case .userContactLinkCreated: return "userContactLinkCreated" - case .userContactLinkDeleted: return "userContactLinkDeleted" - case .acceptingContactRequest: return "acceptingContactRequest" - case .contactRequestRejected: return "contactRequestRejected" - case .networkStatuses: return "networkStatuses" - case .newChatItems: return "newChatItems" - case .groupChatItemsDeleted: return "groupChatItemsDeleted" - case .forwardPlan: return "forwardPlan" - case .chatItemUpdated: return "chatItemUpdated" - case .chatItemNotChanged: return "chatItemNotChanged" - case .chatItemReaction: return "chatItemReaction" - case .reactionMembers: return "reactionMembers" - case .chatItemsDeleted: return "chatItemsDeleted" - case .contactsList: return "contactsList" - case .groupCreated: return "groupCreated" - case .sentGroupInvitation: return "sentGroupInvitation" - case .userAcceptedGroupSent: return "userAcceptedGroupSent" - case .userDeletedMembers: return "userDeletedMembers" - case .leftMemberUser: return "leftMemberUser" - case .groupMembers: return "groupMembers" - case .groupDeletedUser: return "groupDeletedUser" - case .membersRoleUser: return "membersRoleUser" - case .membersBlockedForAllUser: return "membersBlockedForAllUser" - case .groupUpdated: return "groupUpdated" - case .groupLinkCreated: return "groupLinkCreated" - case .groupLink: return "groupLink" - case .groupLinkDeleted: return "groupLinkDeleted" - case .newMemberContact: return "newMemberContact" - case .newMemberContactSentInv: return "newMemberContactSentInv" - case .rcvFileAccepted: return "rcvFileAccepted" - case .rcvFileAcceptedSndCancelled: return "rcvFileAcceptedSndCancelled" - case .standaloneFileInfo: return "standaloneFileInfo" - case .rcvStandaloneFileCreated: return "rcvStandaloneFileCreated" - case .rcvFileCancelled: return "rcvFileCancelled" - case .sndFileCancelled: return "sndFileCancelled" - case .sndStandaloneFileCreated: return "sndStandaloneFileCreated" - case .sndFileStartXFTP: return "sndFileStartXFTP" - case .sndFileCancelledXFTP: return "sndFileCancelledXFTP" - case .callInvitations: return "callInvitations" - case .ntfTokenStatus: return "ntfTokenStatus" - case .ntfToken: return "ntfToken" - case .ntfConns: return "ntfConns" - case .connNtfMessages: return "connNtfMessages" - case .contactConnectionDeleted: return "contactConnectionDeleted" - case .remoteCtrlList: return "remoteCtrlList" - case .remoteCtrlConnecting: return "remoteCtrlConnecting" - case .remoteCtrlConnected: return "remoteCtrlConnected" - case .versionInfo: return "versionInfo" - case .cmdOk: return "cmdOk" - case .agentSubsTotal: return "agentSubsTotal" - case .agentServersSummary: return "agentServersSummary" - case .agentSubsSummary: return "agentSubsSummary" - case .chatCmdError: return "chatCmdError" - case .archiveExported: return "archiveExported" - case .archiveImported: return "archiveImported" - case .appSettings: return "appSettings" - } + switch self { + case .groupCreated: "groupCreated" + case .sentGroupInvitation: "sentGroupInvitation" + case .userAcceptedGroupSent: "userAcceptedGroupSent" + case .userDeletedMembers: "userDeletedMembers" + case .leftMemberUser: "leftMemberUser" + case .groupMembers: "groupMembers" + case .membersRoleUser: "membersRoleUser" + case .membersBlockedForAllUser: "membersBlockedForAllUser" + case .groupUpdated: "groupUpdated" + case .groupLinkCreated: "groupLinkCreated" + case .groupLink: "groupLink" + case .groupLinkDeleted: "groupLinkDeleted" + case .newMemberContact: "newMemberContact" + case .newMemberContactSentInv: "newMemberContactSentInv" + case .rcvFileAccepted: "rcvFileAccepted" + case .rcvFileAcceptedSndCancelled: "rcvFileAcceptedSndCancelled" + case .standaloneFileInfo: "standaloneFileInfo" + case .rcvStandaloneFileCreated: "rcvStandaloneFileCreated" + case .rcvFileCancelled: "rcvFileCancelled" + case .sndFileCancelled: "sndFileCancelled" + case .sndStandaloneFileCreated: "sndStandaloneFileCreated" + case .sndFileStartXFTP: "sndFileStartXFTP" + case .sndFileCancelledXFTP: "sndFileCancelledXFTP" + case .callInvitations: "callInvitations" + case .ntfTokenStatus: "ntfTokenStatus" + case .ntfToken: "ntfToken" + case .ntfConns: "ntfConns" + case .connNtfMessages: "connNtfMessages" + case .remoteCtrlList: "remoteCtrlList" + case .remoteCtrlConnecting: "remoteCtrlConnecting" + case .remoteCtrlConnected: "remoteCtrlConnected" + case .versionInfo: "versionInfo" + case .cmdOk: "cmdOk" + case .agentSubsTotal: "agentSubsTotal" + case .agentServersSummary: "agentServersSummary" + case .agentSubsSummary: "agentSubsSummary" + case .archiveExported: "archiveExported" + case .archiveImported: "archiveImported" + case .appSettings: "appSettings" } } var details: String { - get { - switch self { - case let .response(_, json): return json - case let .activeUser(user): return String(describing: user) - case let .usersList(users): return String(describing: users) - case .chatStarted: return noDetails - case .chatRunning: return noDetails - case .chatStopped: return noDetails - case let .apiChats(u, chats): return withUser(u, String(describing: chats)) - case let .apiChat(u, chat, navInfo): return withUser(u, "chat: \(String(describing: chat))\nnavInfo: \(String(describing: navInfo))") - case let .chatTags(u, userTags): return withUser(u, "userTags: \(String(describing: userTags))") - case let .chatItemInfo(u, chatItem, chatItemInfo): return withUser(u, "chatItem: \(String(describing: chatItem))\nchatItemInfo: \(String(describing: chatItemInfo))") - case let .serverTestResult(u, server, testFailure): return withUser(u, "server: \(server)\nresult: \(String(describing: testFailure))") - case let .serverOperatorConditions(conditions): return "conditions: \(String(describing: conditions))" - case let .userServers(u, userServers): return withUser(u, "userServers: \(String(describing: userServers))") - case let .userServersValidation(u, serverErrors): return withUser(u, "serverErrors: \(String(describing: serverErrors))") - case let .usageConditions(usageConditions, _, acceptedConditions): return "usageConditions: \(String(describing: usageConditions))\nacceptedConditions: \(String(describing: acceptedConditions))" - 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 .groupMemberInfo(u, groupInfo, member, connectionStats_): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats_: \(String(describing: connectionStats_))") - case let .queueInfo(u, rcvMsgInfo, queueInfo): - let msgInfo = if let info = rcvMsgInfo { encodeJSON(info) } else { "none" } - return withUser(u, "rcvMsgInfo: \(msgInfo)\nqueueInfo: \(encodeJSON(queueInfo))") - 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))") - case let .contactSwitchAborted(u, contact, connectionStats): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))") - case let .groupMemberSwitchAborted(u, groupInfo, member, connectionStats): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats: \(String(describing: connectionStats))") - case let .contactRatchetSyncStarted(u, contact, connectionStats): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))") - case let .groupMemberRatchetSyncStarted(u, groupInfo, member, connectionStats): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats: \(String(describing: connectionStats))") - case let .contactCode(u, contact, connectionCode): return withUser(u, "contact: \(String(describing: contact))\nconnectionCode: \(connectionCode)") - case let .groupMemberCode(u, groupInfo, member, connectionCode): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionCode: \(connectionCode)") - case let .connectionVerified(u, verified, expectedCode): return withUser(u, "verified: \(verified)\nconnectionCode: \(expectedCode)") - case let .tagsUpdated(u, userTags, chatTags): return withUser(u, "userTags: \(String(describing: userTags))\nchatTags: \(String(describing: chatTags))") - case let .invitation(u, connLinkInvitation, connection): return withUser(u, "connLinkInvitation: \(connLinkInvitation)\nconnection: \(connection)") - case let .connectionIncognitoUpdated(u, toConnection): return withUser(u, String(describing: toConnection)) - case let .connectionUserChanged(u, fromConnection, toConnection, newUser): return withUser(u, "fromConnection: \(String(describing: fromConnection))\ntoConnection: \(String(describing: toConnection))\nnewUserId: \(String(describing: newUser.userId))") - case let .connectionPlan(u, connLink, connectionPlan): return withUser(u, "connLink: \(String(describing: connLink))\nconnectionPlan: \(String(describing: connectionPlan))") - case let .sentConfirmation(u, connection): return withUser(u, String(describing: connection)) - case let .sentInvitation(u, connection): return withUser(u, String(describing: connection)) - case let .sentInvitationToContact(u, contact, _): return withUser(u, String(describing: contact)) - case let .contactAlreadyExists(u, contact): return withUser(u, String(describing: contact)) - case let .contactDeleted(u, contact): return withUser(u, String(describing: contact)) - case let .chatCleared(u, chatInfo): return withUser(u, String(describing: chatInfo)) - case .userProfileNoChange: return noDetails - case let .userProfileUpdated(u, _, toProfile, _): return withUser(u, String(describing: toProfile)) - case let .userPrivacy(u, updatedUser): return withUser(u, String(describing: updatedUser)) - case let .contactAliasUpdated(u, toContact): return withUser(u, String(describing: toContact)) - case let .groupAliasUpdated(u, toGroup): return withUser(u, String(describing: toGroup)) - case let .connectionAliasUpdated(u, toConnection): return withUser(u, String(describing: toConnection)) - case let .contactPrefsUpdated(u, fromContact, toContact): return withUser(u, "fromContact: \(String(describing: fromContact))\ntoContact: \(String(describing: toContact))") - case let .userContactLink(u, contactLink): return withUser(u, contactLink.responseDetails) - case let .userContactLinkUpdated(u, contactLink): return withUser(u, contactLink.responseDetails) - case let .userContactLinkCreated(u, connLink): return withUser(u, String(describing: connLink)) - case .userContactLinkDeleted: return noDetails - case let .acceptingContactRequest(u, contact): return withUser(u, String(describing: contact)) - case .contactRequestRejected: return noDetails - case let .networkStatuses(u, statuses): return withUser(u, String(describing: statuses)) - case let .newChatItems(u, chatItems): - let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") - return withUser(u, itemsString) - case let .groupChatItemsDeleted(u, gInfo, chatItemIDs, byUser, member_): - return withUser(u, "chatItemIDs: \(String(describing: chatItemIDs))\ngroupInfo: \(String(describing: gInfo))\nbyUser: \(byUser)\nmember_: \(String(describing: member_))") - case let .forwardPlan(u, chatItemIds, forwardConfirmation): return withUser(u, "items: \(chatItemIds) forwardConfirmation: \(String(describing: forwardConfirmation))") - case let .chatItemUpdated(u, chatItem): return withUser(u, String(describing: chatItem)) - case let .chatItemNotChanged(u, chatItem): return withUser(u, String(describing: chatItem)) - case let .chatItemReaction(u, added, reaction): return withUser(u, "added: \(added)\n\(String(describing: reaction))") - case let .reactionMembers(u, reaction): return withUser(u, "memberReactions: \(String(describing: reaction))") - case let .chatItemsDeleted(u, items, byUser): - let itemsString = items.map { item in - "deletedChatItem:\n\(String(describing: item.deletedChatItem))\ntoChatItem:\n\(String(describing: item.toChatItem))" }.joined(separator: "\n") - return withUser(u, itemsString + "\nbyUser: \(byUser)") - case let .contactsList(u, contacts): return withUser(u, String(describing: contacts)) - 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 .userDeletedMembers(u, groupInfo, members, withMessages): return withUser(u, "groupInfo: \(groupInfo)\nmembers: \(members)\nwithMessages: \(withMessages)") - case let .leftMemberUser(u, groupInfo): return withUser(u, String(describing: groupInfo)) - case let .groupMembers(u, group): return withUser(u, String(describing: group)) - case let .groupDeletedUser(u, groupInfo): return withUser(u, String(describing: groupInfo)) - case let .membersRoleUser(u, groupInfo, members, toRole): return withUser(u, "groupInfo: \(groupInfo)\nmembers: \(members)\ntoRole: \(toRole)") - case let .membersBlockedForAllUser(u, groupInfo, members, blocked): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(members)\nblocked: \(blocked)") - case let .groupUpdated(u, toGroup): return withUser(u, String(describing: toGroup)) - case let .groupLinkCreated(u, groupInfo, connLinkContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnLinkContact: \(connLinkContact)\nmemberRole: \(memberRole)") - case let .groupLink(u, groupInfo, connLinkContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnLinkContact: \(connLinkContact)\nmemberRole: \(memberRole)") - case let .groupLinkDeleted(u, groupInfo): return withUser(u, String(describing: groupInfo)) - case let .newMemberContact(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") - case let .newMemberContactSentInv(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") - case let .rcvFileAccepted(u, chatItem): return withUser(u, String(describing: chatItem)) - case .rcvFileAcceptedSndCancelled: return noDetails - case let .standaloneFileInfo(fileMeta): return String(describing: fileMeta) - case .rcvStandaloneFileCreated: return noDetails - case let .rcvFileCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .sndFileCancelled(u, chatItem, _, _): return withUser(u, String(describing: chatItem)) - case .sndStandaloneFileCreated: return noDetails - case let .sndFileStartXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .sndFileCancelledXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .callInvitations(invs): return String(describing: invs) - case let .ntfTokenStatus(status): return String(describing: status) - case let .ntfToken(token, status, ntfMode, ntfServer): return "token: \(token)\nstatus: \(status.rawValue)\nntfMode: \(ntfMode.rawValue)\nntfServer: \(ntfServer)" - case let .ntfConns(ntfConns): return String(describing: ntfConns) - case let .connNtfMessages(receivedMsgs): return "receivedMsgs: \(String(describing: receivedMsgs))" - case let .contactConnectionDeleted(u, connection): return withUser(u, String(describing: connection)) - case let .remoteCtrlList(remoteCtrls): return String(describing: remoteCtrls) - case let .remoteCtrlConnecting(remoteCtrl_, ctrlAppInfo, appVersion): return "remoteCtrl_:\n\(String(describing: remoteCtrl_))\nctrlAppInfo:\n\(String(describing: ctrlAppInfo))\nappVersion: \(appVersion)" - case let .remoteCtrlConnected(remoteCtrl): return String(describing: remoteCtrl) - 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 .agentSubsTotal(u, subsTotal, hasSession): return withUser(u, "subsTotal: \(String(describing: subsTotal))\nhasSession: \(hasSession)") - case let .agentServersSummary(u, serversSummary): return withUser(u, String(describing: serversSummary)) - case let .agentSubsSummary(u, subsSummary): return withUser(u, String(describing: subsSummary)) - case let .chatCmdError(u, chatError): return withUser(u, String(describing: chatError)) - case let .archiveExported(archiveErrors): return String(describing: archiveErrors) - case let .archiveImported(archiveErrors): return String(describing: archiveErrors) - case let .appSettings(appSettings): return String(describing: appSettings) - } - } - } - - private var noDetails: String { get { "\(responseType): no details" } } - - static func chatResponse(_ s: String) -> ChatResponse { - let d = s.data(using: .utf8)! - // TODO is there a way to do it without copying the data? e.g: - // let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson)) - // let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free) - do { - let r = try callWithLargeStack { - try jsonDecoder.decode(APIResponse.self, from: d) - } - return r.resp - } catch { - logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)") - } - - var type: String? - var json: String? - if let j = try? JSONSerialization.jsonObject(with: d) as? NSDictionary { - if let jResp = j["resp"] as? NSDictionary, jResp.count == 1 || jResp.count == 2 { - type = jResp.allKeys[0] as? String - if jResp.count == 2 && type == "_owsf" { - type = jResp.allKeys[1] as? String - } - if type == "apiChats" { - if let r = parseApiChats(jResp) { - return .apiChats(user: r.user, chats: r.chats) - } - } else if type == "apiChat" { - if let jApiChat = jResp["apiChat"] as? NSDictionary, - let user: UserRef = try? decodeObject(jApiChat["user"] as Any), - let jChat = jApiChat["chat"] as? NSDictionary, - let (chat, navInfo) = try? parseChatData(jChat, jApiChat["navInfo"] as? NSDictionary) { - return .apiChat(user: user, chat: chat, navInfo: navInfo) - } - } else if type == "chatCmdError" { - if let jError = jResp["chatCmdError"] as? NSDictionary { - return .chatCmdError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) - } - } - } - json = serializeJSON(j, options: .prettyPrinted) - } - return ChatResponse.response(type: type ?? "invalid", json: json ?? s) - } - - var chatError: ChatError? { switch self { - case let .chatCmdError(_, error): error - default: nil - } - } - - var chatErrorType: ChatErrorType? { - switch self { - case let .chatCmdError(_, .error(error)): error - default: nil + 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 .userDeletedMembers(u, groupInfo, members, withMessages): return withUser(u, "groupInfo: \(groupInfo)\nmembers: \(members)\nwithMessages: \(withMessages)") + case let .leftMemberUser(u, groupInfo): return withUser(u, String(describing: groupInfo)) + case let .groupMembers(u, group): return withUser(u, String(describing: group)) + case let .membersRoleUser(u, groupInfo, members, toRole): return withUser(u, "groupInfo: \(groupInfo)\nmembers: \(members)\ntoRole: \(toRole)") + case let .membersBlockedForAllUser(u, groupInfo, members, blocked): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(members)\nblocked: \(blocked)") + case let .groupUpdated(u, toGroup): return withUser(u, String(describing: toGroup)) + case let .groupLinkCreated(u, groupInfo, connLinkContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnLinkContact: \(connLinkContact)\nmemberRole: \(memberRole)") + case let .groupLink(u, groupInfo, connLinkContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnLinkContact: \(connLinkContact)\nmemberRole: \(memberRole)") + case let .groupLinkDeleted(u, groupInfo): return withUser(u, String(describing: groupInfo)) + case let .newMemberContact(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") + case let .newMemberContactSentInv(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") + case let .rcvFileAccepted(u, chatItem): return withUser(u, String(describing: chatItem)) + case .rcvFileAcceptedSndCancelled: return noDetails + case let .standaloneFileInfo(fileMeta): return String(describing: fileMeta) + case .rcvStandaloneFileCreated: return noDetails + case let .rcvFileCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndFileCancelled(u, chatItem, _, _): return withUser(u, String(describing: chatItem)) + case .sndStandaloneFileCreated: return noDetails + case let .sndFileStartXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndFileCancelledXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .callInvitations(invs): return String(describing: invs) + case let .ntfTokenStatus(status): return String(describing: status) + case let .ntfToken(token, status, ntfMode, ntfServer): return "token: \(token)\nstatus: \(status.rawValue)\nntfMode: \(ntfMode.rawValue)\nntfServer: \(ntfServer)" + case let .ntfConns(ntfConns): return String(describing: ntfConns) + case let .connNtfMessages(receivedMsgs): return "receivedMsgs: \(String(describing: receivedMsgs))" + case let .remoteCtrlList(remoteCtrls): return String(describing: remoteCtrls) + case let .remoteCtrlConnecting(remoteCtrl_, ctrlAppInfo, appVersion): return "remoteCtrl_:\n\(String(describing: remoteCtrl_))\nctrlAppInfo:\n\(String(describing: ctrlAppInfo))\nappVersion: \(appVersion)" + case let .remoteCtrlConnected(remoteCtrl): return String(describing: remoteCtrl) + 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 .agentSubsTotal(u, subsTotal, hasSession): return withUser(u, "subsTotal: \(String(describing: subsTotal))\nhasSession: \(hasSession)") + case let .agentServersSummary(u, serversSummary): return withUser(u, String(describing: serversSummary)) + case let .agentSubsSummary(u, subsSummary): return withUser(u, String(describing: subsSummary)) + case let .archiveExported(archiveErrors): return String(describing: archiveErrors) + case let .archiveImported(archiveErrors): return String(describing: archiveErrors) + case let .appSettings(appSettings): return String(describing: appSettings) } } } -enum ChatEvent: Decodable, ChatEventProtocol { - case event(type: String, json: String) +enum ChatEvent: Decodable, ChatAPIResult { case chatSuspended case contactSwitch(user: UserRef, contact: Contact, switchProgress: SwitchProgress) case groupMemberSwitch(user: UserRef, groupInfo: GroupInfo, member: GroupMember, switchProgress: SwitchProgress) @@ -1063,11 +1035,9 @@ enum ChatEvent: Decodable, ChatEventProtocol { case remoteCtrlStopped(rcsState: RemoteCtrlSessionState, rcStopReason: RemoteCtrlStopReason) // pq case contactPQEnabled(user: UserRef, contact: Contact, pqEnabled: Bool) - case chatError(user_: UserRef?, chatError: ChatError) - var eventType: String { + var responseType: String { switch self { - case let .event(type, _): "* \(type)" case .chatSuspended: "chatSuspended" case .contactSwitch: "contactSwitch" case .groupMemberSwitch: "groupMemberSwitch" @@ -1135,13 +1105,11 @@ enum ChatEvent: Decodable, ChatEventProtocol { case .remoteCtrlConnected: "remoteCtrlConnected" case .remoteCtrlStopped: "remoteCtrlStopped" case .contactPQEnabled: "contactPQEnabled" - case .chatError: "chatError" } } var details: String { switch self { - case let .event(_, json): return json case .chatSuspended: return noDetails case let .contactSwitch(u, contact, switchProgress): return withUser(u, "contact: \(String(describing: contact))\nswitchProgress: \(String(describing: switchProgress))") case let .groupMemberSwitch(u, groupInfo, member, switchProgress): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nswitchProgress: \(String(describing: switchProgress))") @@ -1217,84 +1185,8 @@ enum ChatEvent: Decodable, ChatEventProtocol { case let .remoteCtrlConnected(remoteCtrl): return String(describing: remoteCtrl) case let .remoteCtrlStopped(rcsState, rcStopReason): return "rcsState: \(String(describing: rcsState))\nrcStopReason: \(String(describing: rcStopReason))" case let .contactPQEnabled(u, contact, pqEnabled): return withUser(u, "contact: \(String(describing: contact))\npqEnabled: \(pqEnabled)") - case let .chatError(u, chatError): return withUser(u, String(describing: chatError)) } - } - - private var noDetails: String { "\(eventType): no details" } - - static func chatEvent(_ s: String) -> ChatEvent { - let d = s.data(using: .utf8)! - // TODO is there a way to do it without copying the data? e.g: - // let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson)) - // let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free) - do { - let r = // try callWithLargeStack { - try jsonDecoder.decode(APIResponse.self, from: d) -// } - return r.resp - } catch { - logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)") - } - - var type: String? - var json: String? - if let j = try? JSONSerialization.jsonObject(with: d) as? NSDictionary { - if let jResp = j["resp"] as? NSDictionary, jResp.count == 1 || jResp.count == 2 { - type = jResp.allKeys[0] as? String - if jResp.count == 2 && type == "_owsf" { - type = jResp.allKeys[1] as? String - } - if type == "chatError" { - if let jError = jResp["chatError"] as? NSDictionary { - return .chatError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) - } - } - } - json = serializeJSON(j, options: .prettyPrinted) - } - return ChatEvent.event(type: type ?? "invalid", json: json ?? s) - } - - var chatError: ChatError? { - switch self { - case let .chatError(_, error): error - default: nil - } - } - - var chatErrorType: ChatErrorType? { - switch self { - case let .chatError(_, .error(error)): error - default: nil - } - } -} - -private let largeStackSize: Int = 2 * 1024 * 1024 - -private func callWithLargeStack(_ f: @escaping () throws -> T) throws -> T { - let semaphore = DispatchSemaphore(value: 0) - var result: Result? - let thread = Thread { - do { - result = .success(try f()) - } catch { - result = .failure(error) - } - semaphore.signal() - } - - thread.stackSize = largeStackSize - thread.qualityOfService = Thread.current.qualityOfService - thread.start() - - semaphore.wait() - - switch result! { - case let .success(r): return r - case let .failure(e): throw e - } + } } struct NewUser: Encodable { diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index c73cb32c58..63d8b38e3c 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -30,9 +30,18 @@ actor TerminalItems { } } - func addCommand(_ start: Date, _ cmd: ChatCommand, _ resp: ChatResponse) async { + func addCommand(_ start: Date, _ cmd: ChatCommand, _ res: APIResult) async { await add(.cmd(start, cmd)) - await add(.resp(.now, resp)) + await addResult(res) + } + + func addResult(_ res: APIResult) async { + let item: TerminalItem = switch res { + case let .result(r): .res(.now, r) + case let .error(e): .err(.now, e) + case let .invalid(type, json): .bad(.now, type, json) + } + await add(item) } } diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index a6b9e719c7..d92411decd 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -19,36 +19,34 @@ private let networkStatusesLock = DispatchQueue(label: "chat.simplex.app.network enum TerminalItem: Identifiable { case cmd(Date, ChatCommand) - case resp(Date, ChatResponse) - case event(Date, ChatEvent) + case res(Date, ChatAPIResult) + case err(Date, ChatError) + case bad(Date, String, Data?) var id: Date { - get { - switch self { - case let .cmd(d, _): return d - case let .resp(d, _): return d - case let .event(d, _): return d - } + switch self { + case let .cmd(d, _): d + case let .res(d, _): d + case let .err(d, _): d + case let .bad(d, _, _): d } } var label: String { - get { - switch self { - case let .cmd(_, cmd): return "> \(cmd.cmdString.prefix(30))" - case let .resp(_, resp): return "< \(resp.responseType)" - case let .event(_, evt): return "< \(evt.eventType)" - } + switch self { + case let .cmd(_, cmd): "> \(cmd.cmdString.prefix(30))" + case let .res(_, res): "< \(res.responseType)" + case let .err(_, err): "< error \(err.errorType)" + case let .bad(_, type, _): "< * \(type)" } } var details: String { - get { - switch self { - case let .cmd(_, cmd): return cmd.cmdString - case let .resp(_, resp): return resp.details - case let .event(_, evt): return evt.details - } + switch self { + case let .cmd(_, cmd): cmd.cmdString + case let .res(_, res): res.details + case let .err(_, err): String(describing: err) + case let .bad(_, _, json): dataToString(json) } } } @@ -90,18 +88,24 @@ private func withBGTask(bgDelay: Double? = nil, f: @escaping () -> T) -> T { return r } -func chatSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, _ ctrl: chat_ctrl? = nil, log: Bool = true) -> ChatResponse { +@inline(__always) +func chatSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, ctrl: chat_ctrl? = nil, log: Bool = true) throws -> R { + let res: APIResult = chatApiSendCmdSync(cmd, bgTask: bgTask, bgDelay: bgDelay, ctrl: ctrl, log: log) + return try apiResult(res) +} + +func chatApiSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, ctrl: chat_ctrl? = nil, log: Bool = true) -> APIResult { if log { logger.debug("chatSendCmd \(cmd.cmdType)") } let start = Date.now - let resp: ChatResponse = bgTask + let resp: APIResult = bgTask ? withBGTask(bgDelay: bgDelay) { sendSimpleXCmd(cmd, ctrl) } : sendSimpleXCmd(cmd, ctrl) if log { logger.debug("chatSendCmd \(cmd.cmdType): \(resp.responseType)") - if case let .response(_, json) = resp { - logger.debug("chatSendCmd \(cmd.cmdType) response: \(json)") + if case let .invalid(_, json) = resp { + logger.debug("chatSendCmd \(cmd.cmdType) response: \(dataToString(json))") } Task { await TerminalItems.shared.addCommand(start, cmd.obfuscated, resp) @@ -110,16 +114,32 @@ func chatSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = return resp } -func chatSendCmd(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, _ ctrl: chat_ctrl? = nil, log: Bool = true) async -> ChatResponse { +@inline(__always) +func chatSendCmd(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, ctrl: chat_ctrl? = nil, log: Bool = true) async throws -> R { + let res: APIResult = await chatApiSendCmd(cmd, bgTask: bgTask, bgDelay: bgDelay, ctrl: ctrl, log: log) + return try apiResult(res) +} + +@inline(__always) +func chatApiSendCmd(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, ctrl: chat_ctrl? = nil, log: Bool = true) async -> APIResult { await withCheckedContinuation { cont in - cont.resume(returning: chatSendCmdSync(cmd, bgTask: bgTask, bgDelay: bgDelay, ctrl, log: log)) + cont.resume(returning: chatApiSendCmdSync(cmd, bgTask: bgTask, bgDelay: bgDelay, ctrl: ctrl, log: log)) } } -func chatRecvMsg(_ ctrl: chat_ctrl? = nil) async -> ChatEvent? { +@inline(__always) +func apiResult(_ res: APIResult) throws -> R { + switch res { + case let .result(r): return r + case let .error(e): throw e + case let .invalid(type, _): throw ChatError.unexpectedResult(type: type) + } +} + +func chatRecvMsg(_ ctrl: chat_ctrl? = nil) async -> APIResult? { await withCheckedContinuation { cont in - _ = withBGTask(bgDelay: msgDelay) { () -> ChatEvent? in - let evt: ChatEvent? = recvSimpleXMsg(ctrl) + _ = withBGTask(bgDelay: msgDelay) { () -> APIResult? in + let evt: APIResult? = recvSimpleXMsg(ctrl) cont.resume(returning: evt) return evt } @@ -127,18 +147,18 @@ func chatRecvMsg(_ ctrl: chat_ctrl? = nil) async -> ChatEvent? { } func apiGetActiveUser(ctrl: chat_ctrl? = nil) throws -> User? { - let r: ChatResponse = chatSendCmdSync(.showActiveUser, ctrl) + let r: APIResult = chatApiSendCmdSync(.showActiveUser, ctrl: ctrl) switch r { - case let .activeUser(user): return user - case .chatCmdError(_, .error(.noActiveUser)): return nil - default: throw r + case let .result(.activeUser(user)): return user + case .error(.error(.noActiveUser)): return nil + default: throw r.unexpected } } func apiCreateActiveUser(_ p: Profile?, pastTimestamp: Bool = false, ctrl: chat_ctrl? = nil) throws -> User { - let r: ChatResponse = chatSendCmdSync(.createActiveUser(profile: p, pastTimestamp: pastTimestamp), ctrl) + let r: ChatResponse0 = try chatSendCmdSync(.createActiveUser(profile: p, pastTimestamp: pastTimestamp), ctrl: ctrl) if case let .activeUser(user) = r { return user } - throw r + throw r.unexpected } func listUsers() throws -> [UserInfo] { @@ -149,41 +169,35 @@ func listUsersAsync() async throws -> [UserInfo] { return try listUsersResponse(await chatSendCmd(.listUsers)) } -private func listUsersResponse(_ r: ChatResponse) throws -> [UserInfo] { +private func listUsersResponse(_ r: ChatResponse0) throws -> [UserInfo] { if case let .usersList(users) = r { return users.sorted { $0.user.chatViewName.compare($1.user.chatViewName) == .orderedAscending } } - throw r + throw r.unexpected } func apiSetActiveUser(_ userId: Int64, viewPwd: String?) throws -> User { - let r = chatSendCmdSync(.apiSetActiveUser(userId: userId, viewPwd: viewPwd)) + let r: ChatResponse0 = try chatSendCmdSync(.apiSetActiveUser(userId: userId, viewPwd: viewPwd)) if case let .activeUser(user) = r { return user } - throw r + throw r.unexpected } func apiSetActiveUserAsync(_ userId: Int64, viewPwd: String?) async throws -> User { - let r = await chatSendCmd(.apiSetActiveUser(userId: userId, viewPwd: viewPwd)) + let r: ChatResponse0 = try await chatSendCmd(.apiSetActiveUser(userId: userId, viewPwd: viewPwd)) if case let .activeUser(user) = r { return user } - throw r + throw r.unexpected } func apiSetAllContactReceipts(enable: Bool) async throws { - let r = await chatSendCmd(.setAllContactReceipts(enable: enable)) - if case .cmdOk = r { return } - throw r + try await sendCommandOkResp(.setAllContactReceipts(enable: enable)) } func apiSetUserContactReceipts(_ userId: Int64, userMsgReceiptSettings: UserMsgReceiptSettings) async throws { - let r = await chatSendCmd(.apiSetUserContactReceipts(userId: userId, userMsgReceiptSettings: userMsgReceiptSettings)) - if case .cmdOk = r { return } - throw r + try await sendCommandOkResp(.apiSetUserContactReceipts(userId: userId, userMsgReceiptSettings: userMsgReceiptSettings)) } func apiSetUserGroupReceipts(_ userId: Int64, userMsgReceiptSettings: UserMsgReceiptSettings) async throws { - let r = await chatSendCmd(.apiSetUserGroupReceipts(userId: userId, userMsgReceiptSettings: userMsgReceiptSettings)) - if case .cmdOk = r { return } - throw r + try await sendCommandOkResp(.apiSetUserGroupReceipts(userId: userId, userMsgReceiptSettings: userMsgReceiptSettings)) } func apiHideUser(_ userId: Int64, viewPwd: String) async throws -> User { @@ -203,90 +217,88 @@ func apiUnmuteUser(_ userId: Int64) async throws -> User { } func setUserPrivacy_(_ cmd: ChatCommand) async throws -> User { - let r: ChatResponse = await chatSendCmd(cmd) + let r: ChatResponse1 = try await chatSendCmd(cmd) if case let .userPrivacy(_, updatedUser) = r { return updatedUser } - throw r + throw r.unexpected } func apiDeleteUser(_ userId: Int64, _ delSMPQueues: Bool, viewPwd: String?) async throws { - let r: ChatResponse = await chatSendCmd(.apiDeleteUser(userId: userId, delSMPQueues: delSMPQueues, viewPwd: viewPwd)) - if case .cmdOk = r { return } - throw r + try await sendCommandOkResp(.apiDeleteUser(userId: userId, delSMPQueues: delSMPQueues, viewPwd: viewPwd)) } func apiStartChat(ctrl: chat_ctrl? = nil) throws -> Bool { - let r: ChatResponse = chatSendCmdSync(.startChat(mainApp: true, enableSndFiles: true), ctrl) + let r: ChatResponse0 = try chatSendCmdSync(.startChat(mainApp: true, enableSndFiles: true), ctrl: ctrl) switch r { case .chatStarted: return true case .chatRunning: return false - default: throw r + default: throw r.unexpected } } func apiCheckChatRunning() throws -> Bool { - let r = chatSendCmdSync(.checkChatRunning) + let r: ChatResponse0 = try chatSendCmdSync(.checkChatRunning) switch r { case .chatRunning: return true case .chatStopped: return false - default: throw r + default: throw r.unexpected } } func apiStopChat() async throws { - let r = await chatSendCmd(.apiStopChat) + let r: ChatResponse0 = try await chatSendCmd(.apiStopChat) switch r { case .chatStopped: return - default: throw r + default: throw r.unexpected } } func apiActivateChat() { chatReopenStore() - let r = chatSendCmdSync(.apiActivateChat(restoreChat: true)) - if case .cmdOk = r { return } - logger.error("apiActivateChat error: \(String(describing: r))") + do { + try sendCommandOkRespSync(.apiActivateChat(restoreChat: true)) + } catch { + logger.error("apiActivateChat error: \(responseError(error))") + } } func apiSuspendChat(timeoutMicroseconds: Int) { - let r = chatSendCmdSync(.apiSuspendChat(timeoutMicroseconds: timeoutMicroseconds)) - if case .cmdOk = r { return } - logger.error("apiSuspendChat error: \(String(describing: r))") + do { + try sendCommandOkRespSync(.apiSuspendChat(timeoutMicroseconds: timeoutMicroseconds)) + } catch { + logger.error("apiSuspendChat error: \(responseError(error))") + } } func apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String, ctrl: chat_ctrl? = nil) throws { - let r = chatSendCmdSync(.apiSetAppFilePaths(filesFolder: filesFolder, tempFolder: tempFolder, assetsFolder: assetsFolder), ctrl) + let r: ChatResponse2 = try chatSendCmdSync(.apiSetAppFilePaths(filesFolder: filesFolder, tempFolder: tempFolder, assetsFolder: assetsFolder), ctrl: ctrl) if case .cmdOk = r { return } - throw r + throw r.unexpected } func apiSetEncryptLocalFiles(_ enable: Bool) throws { - let r = chatSendCmdSync(.apiSetEncryptLocalFiles(enable: enable)) - if case .cmdOk = r { return } - throw r + try sendCommandOkRespSync(.apiSetEncryptLocalFiles(enable: enable)) } func apiSaveAppSettings(settings: AppSettings) throws { - let r = chatSendCmdSync(.apiSaveSettings(settings: settings)) - if case .cmdOk = r { return } - throw r + try sendCommandOkRespSync(.apiSaveSettings(settings: settings)) } func apiGetAppSettings(settings: AppSettings) throws -> AppSettings { - let r = chatSendCmdSync(.apiGetSettings(settings: settings)) + let r: ChatResponse2 = try chatSendCmdSync(.apiGetSettings(settings: settings)) if case let .appSettings(settings) = r { return settings } - throw r + throw r.unexpected } func apiExportArchive(config: ArchiveConfig) async throws -> [ArchiveError] { - let r = await chatSendCmd(.apiExportArchive(config: config)) + let r: ChatResponse2 = try await chatSendCmd(.apiExportArchive(config: config)) if case let .archiveExported(archiveErrors) = r { return archiveErrors } - throw r + throw r.unexpected } func apiImportArchive(config: ArchiveConfig) async throws -> [ArchiveError] { - let r = await chatSendCmd(.apiImportArchive(config: config)) + let r: ChatResponse2 = try await chatSendCmd(.apiImportArchive(config: config)) if case let .archiveImported(archiveErrors) = r { return archiveErrors } - throw r + throw r.unexpected } func apiDeleteStorage() async throws { @@ -297,8 +309,8 @@ func apiStorageEncryption(currentKey: String = "", newKey: String = "") async th try await sendCommandOkResp(.apiStorageEncryption(config: DBEncryptionConfig(currentKey: currentKey, newKey: newKey))) } -func testStorageEncryption(key: String, _ ctrl: chat_ctrl? = nil) async throws { - try await sendCommandOkResp(.testStorageEncryption(key: key), ctrl) +func testStorageEncryption(key: String, ctrl: chat_ctrl? = nil) async throws { + try await sendCommandOkResp(.testStorageEncryption(key: key), ctrl: ctrl) } func apiGetChats() throws -> [ChatData] { @@ -311,31 +323,31 @@ func apiGetChatsAsync() async throws -> [ChatData] { return try apiChatsResponse(await chatSendCmd(.apiGetChats(userId: userId))) } -private func apiChatsResponse(_ r: ChatResponse) throws -> [ChatData] { +private func apiChatsResponse(_ r: ChatResponse0) throws -> [ChatData] { if case let .apiChats(_, chats) = r { return chats } - throw r + throw r.unexpected } func apiGetChatTags() throws -> [ChatTag] { let userId = try currentUserId("apiGetChatTags") - let r = chatSendCmdSync(.apiGetChatTags(userId: userId)) + let r: ChatResponse0 = try chatSendCmdSync(.apiGetChatTags(userId: userId)) if case let .chatTags(_, tags) = r { return tags } - throw r + throw r.unexpected } func apiGetChatTagsAsync() async throws -> [ChatTag] { let userId = try currentUserId("apiGetChatTags") - let r = await chatSendCmd(.apiGetChatTags(userId: userId)) + let r: ChatResponse0 = try await chatSendCmd(.apiGetChatTags(userId: userId)) if case let .chatTags(_, tags) = r { return tags } - throw r + throw r.unexpected } let loadItemsPerPage = 50 func apiGetChat(chatId: ChatId, pagination: ChatPagination, search: String = "") async throws -> (Chat, NavigationInfo) { - let r = await chatSendCmd(.apiGetChat(chatId: chatId, pagination: pagination, search: search)) + let r: ChatResponse0 = try await chatSendCmd(.apiGetChat(chatId: chatId, pagination: pagination, search: search)) if case let .apiChat(_, chat, navInfo) = r { return (Chat.init(chat), navInfo ?? NavigationInfo()) } - throw r + throw r.unexpected } func loadChat(chat: Chat, search: String = "", clearItems: Bool = true) async { @@ -356,15 +368,15 @@ func loadChat(chatId: ChatId, search: String = "", openAroundItemId: ChatItem.ID } func apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64) async throws -> ChatItemInfo { - let r = await chatSendCmd(.apiGetChatItemInfo(type: type, id: id, itemId: itemId)) + let r: ChatResponse0 = try await chatSendCmd(.apiGetChatItemInfo(type: type, id: id, itemId: itemId)) if case let .chatItemInfo(_, _, chatItemInfo) = r { return chatItemInfo } - throw r + throw r.unexpected } func apiPlanForwardChatItems(type: ChatType, id: Int64, itemIds: [Int64]) async throws -> ([Int64], ForwardConfirmation?) { - let r = await chatSendCmd(.apiPlanForwardChatItems(toChatType: type, toChatId: id, itemIds: itemIds)) + let r: ChatResponse1 = try await chatSendCmd(.apiPlanForwardChatItems(toChatType: type, toChatId: id, itemIds: itemIds)) if case let .forwardPlan(_, chatItemIds, forwardConfimation) = r { return (chatItemIds, forwardConfimation) } - throw r + throw r.unexpected } func apiForwardChatItems(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemIds: [Int64], ttl: Int?) async -> [ChatItem]? { @@ -373,19 +385,19 @@ func apiForwardChatItems(toChatType: ChatType, toChatId: Int64, fromChatType: Ch } func apiCreateChatTag(tag: ChatTagData) async throws -> [ChatTag] { - let r = await chatSendCmd(.apiCreateChatTag(tag: tag)) + let r: ChatResponse0 = try await chatSendCmd(.apiCreateChatTag(tag: tag)) if case let .chatTags(_, userTags) = r { return userTags } - throw r + throw r.unexpected } func apiSetChatTags(type: ChatType, id: Int64, tagIds: [Int64]) async throws -> ([ChatTag], [Int64]) { - let r = await chatSendCmd(.apiSetChatTags(type: type, id: id, tagIds: tagIds)) + let r: ChatResponse0 = try await chatSendCmd(.apiSetChatTags(type: type, id: id, tagIds: tagIds)) if case let .tagsUpdated(_, userTags, chatTags) = r { return (userTags, chatTags) } - throw r + throw r.unexpected } func apiDeleteChatTag(tagId: Int64) async throws { @@ -407,7 +419,7 @@ func apiSendMessages(type: ChatType, id: Int64, live: Bool = false, ttl: Int? = private func processSendMessageCmd(toChatType: ChatType, cmd: ChatCommand) async -> [ChatItem]? { let chatModel = ChatModel.shared - let r: ChatResponse + let r: APIResult if toChatType == .direct { var cItem: ChatItem? = nil let endTask = beginBGTask({ @@ -417,8 +429,8 @@ private func processSendMessageCmd(toChatType: ChatType, cmd: ChatCommand) async } } }) - r = await chatSendCmd(cmd, bgTask: false) - if case let .newChatItems(_, aChatItems) = r { + r = await chatApiSendCmd(cmd, bgTask: false) + if case let .result(.newChatItems(_, aChatItems)) = r { let cItems = aChatItems.map { $0.chatItem } if let cItemLast = cItems.last { cItem = cItemLast @@ -429,40 +441,40 @@ private func processSendMessageCmd(toChatType: ChatType, cmd: ChatCommand) async if let networkErrorAlert = networkErrorAlert(r) { AlertManager.shared.showAlert(networkErrorAlert) } else { - sendMessageErrorAlert(r) + sendMessageErrorAlert(r.unexpected) } endTask() return nil } else { - r = await chatSendCmd(cmd, bgDelay: msgDelay) - if case let .newChatItems(_, aChatItems) = r { + r = await chatApiSendCmd(cmd, bgDelay: msgDelay) + if case let .result(.newChatItems(_, aChatItems)) = r { return aChatItems.map { $0.chatItem } } - sendMessageErrorAlert(r) + sendMessageErrorAlert(r.unexpected) return nil } } func apiCreateChatItems(noteFolderId: Int64, composedMessages: [ComposedMessage]) async -> [ChatItem]? { - let r = await chatSendCmd(.apiCreateChatItems(noteFolderId: noteFolderId, composedMessages: composedMessages)) - if case let .newChatItems(_, aChatItems) = r { return aChatItems.map { $0.chatItem } } - createChatItemsErrorAlert(r) + let r: APIResult = await chatApiSendCmd(.apiCreateChatItems(noteFolderId: noteFolderId, composedMessages: composedMessages)) + if case let .result(.newChatItems(_, aChatItems)) = r { return aChatItems.map { $0.chatItem } } + createChatItemsErrorAlert(r.unexpected) return nil } func apiReportMessage(groupId: Int64, chatItemId: Int64, reportReason: ReportReason, reportText: String) async -> [ChatItem]? { - let r = await chatSendCmd(.apiReportMessage(groupId: groupId, chatItemId: chatItemId, reportReason: reportReason, reportText: reportText)) - if case let .newChatItems(_, aChatItems) = r { return aChatItems.map { $0.chatItem } } + let r: APIResult = await chatApiSendCmd(.apiReportMessage(groupId: groupId, chatItemId: chatItemId, reportReason: reportReason, reportText: reportText)) + if case let .result(.newChatItems(_, aChatItems)) = r { return aChatItems.map { $0.chatItem } } logger.error("apiReportMessage error: \(String(describing: r))") AlertManager.shared.showAlertMsg( title: "Error creating report", - message: "Error: \(responseError(r))" + message: "Error: \(responseError(r.unexpected))" ) return nil } -private func sendMessageErrorAlert(_ r: ChatResponse) { +private func sendMessageErrorAlert(_ r: ChatError) { logger.error("send message error: \(String(describing: r))") AlertManager.shared.showAlertMsg( title: "Error sending message", @@ -470,7 +482,7 @@ private func sendMessageErrorAlert(_ r: ChatResponse) { ) } -private func createChatItemsErrorAlert(_ r: ChatResponse) { +private func createChatItemsErrorAlert(_ r: ChatError) { logger.error("apiCreateChatItems error: \(String(describing: r))") AlertManager.shared.showAlertMsg( title: "Error creating message", @@ -479,56 +491,56 @@ private func createChatItemsErrorAlert(_ r: ChatResponse) { } func apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, updatedMessage: UpdatedMessage, live: Bool = false) async throws -> ChatItem { - let r = await chatSendCmd(.apiUpdateChatItem(type: type, id: id, itemId: itemId, updatedMessage: updatedMessage, live: live), bgDelay: msgDelay) + let r: ChatResponse1 = try await chatSendCmd(.apiUpdateChatItem(type: type, id: id, itemId: itemId, updatedMessage: updatedMessage, live: live), bgDelay: msgDelay) switch r { case let .chatItemUpdated(_, aChatItem): return aChatItem.chatItem case let .chatItemNotChanged(_, aChatItem): return aChatItem.chatItem - default: throw r + default: throw r.unexpected } } func apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, reaction: MsgReaction) async throws -> ChatItem { - let r = await chatSendCmd(.apiChatItemReaction(type: type, id: id, itemId: itemId, add: add, reaction: reaction), bgDelay: msgDelay) + let r: ChatResponse1 = try await chatSendCmd(.apiChatItemReaction(type: type, id: id, itemId: itemId, add: add, reaction: reaction), bgDelay: msgDelay) if case let .chatItemReaction(_, _, reaction) = r { return reaction.chatReaction.chatItem } - throw r + throw r.unexpected } func apiGetReactionMembers(groupId: Int64, itemId: Int64, reaction: MsgReaction) async throws -> [MemberReaction] { let userId = try currentUserId("apiGetReactionMemebers") - let r = await chatSendCmd(.apiGetReactionMembers(userId: userId, groupId: groupId, itemId: itemId, reaction: reaction )) + let r: ChatResponse1 = try await chatSendCmd(.apiGetReactionMembers(userId: userId, groupId: groupId, itemId: itemId, reaction: reaction )) if case let .reactionMembers(_, memberReactions) = r { return memberReactions } - throw r + throw r.unexpected } func apiDeleteChatItems(type: ChatType, id: Int64, itemIds: [Int64], mode: CIDeleteMode) async throws -> [ChatItemDeletion] { - let r = await chatSendCmd(.apiDeleteChatItem(type: type, id: id, itemIds: itemIds, mode: mode), bgDelay: msgDelay) + let r: ChatResponse1 = try await chatSendCmd(.apiDeleteChatItem(type: type, id: id, itemIds: itemIds, mode: mode), bgDelay: msgDelay) if case let .chatItemsDeleted(_, items, _) = r { return items } - throw r + throw r.unexpected } func apiDeleteMemberChatItems(groupId: Int64, itemIds: [Int64]) async throws -> [ChatItemDeletion] { - let r = await chatSendCmd(.apiDeleteMemberChatItem(groupId: groupId, itemIds: itemIds), bgDelay: msgDelay) + let r: ChatResponse1 = try await chatSendCmd(.apiDeleteMemberChatItem(groupId: groupId, itemIds: itemIds), bgDelay: msgDelay) if case let .chatItemsDeleted(_, items, _) = r { return items } - throw r + throw r.unexpected } -func apiArchiveReceivedReports(groupId: Int64) async throws -> ChatResponse { - let r = await chatSendCmd(.apiArchiveReceivedReports(groupId: groupId), bgDelay: msgDelay) +func apiArchiveReceivedReports(groupId: Int64) async throws -> ChatResponse1 { + let r: ChatResponse1 = try await chatSendCmd(.apiArchiveReceivedReports(groupId: groupId), bgDelay: msgDelay) if case .groupChatItemsDeleted = r { return r } - throw r + throw r.unexpected } func apiDeleteReceivedReports(groupId: Int64, itemIds: [Int64], mode: CIDeleteMode) async throws -> [ChatItemDeletion] { - let r = await chatSendCmd(.apiDeleteReceivedReports(groupId: groupId, itemIds: itemIds, mode: mode), bgDelay: msgDelay) + let r: ChatResponse1 = try await chatSendCmd(.apiDeleteReceivedReports(groupId: groupId, itemIds: itemIds, mode: mode), bgDelay: msgDelay) if case let .chatItemsDeleted(_, chatItemDeletions, _) = r { return chatItemDeletions } - throw r + throw r.unexpected } func apiGetNtfToken() -> (DeviceToken?, NtfTknStatus?, NotificationsMode, String?) { - let r = chatSendCmdSync(.apiGetNtfToken) + let r: APIResult = chatApiSendCmdSync(.apiGetNtfToken) switch r { - case let .ntfToken(token, status, ntfMode, ntfServer): return (token, status, ntfMode, ntfServer) - case .chatCmdError(_, .errorAgent(.CMD(.PROHIBITED, _))): return (nil, nil, .off, nil) + case let .result(.ntfToken(token, status, ntfMode, ntfServer)): return (token, status, ntfMode, ntfServer) + case .error(.errorAgent(.CMD(.PROHIBITED, _))): return (nil, nil, .off, nil) default: logger.debug("apiGetNtfToken response: \(String(describing: r))") return (nil, nil, .off, nil) @@ -536,9 +548,9 @@ func apiGetNtfToken() -> (DeviceToken?, NtfTknStatus?, NotificationsMode, String } func apiRegisterToken(token: DeviceToken, notificationMode: NotificationsMode) async throws -> NtfTknStatus { - let r = await chatSendCmd(.apiRegisterToken(token: token, notificationMode: notificationMode)) + let r: ChatResponse2 = try await chatSendCmd(.apiRegisterToken(token: token, notificationMode: notificationMode)) if case let .ntfTokenStatus(status) = r { return status } - throw r + throw r.unexpected } func registerToken(token: DeviceToken) { @@ -601,9 +613,9 @@ func apiVerifyToken(token: DeviceToken, nonce: String, code: String) async throw } func apiCheckToken(token: DeviceToken) async throws -> NtfTknStatus { - let r = await chatSendCmd(.apiCheckToken(token: token)) + let r: ChatResponse2 = try await chatSendCmd(.apiCheckToken(token: token)) if case let .ntfTokenStatus(status) = r { return status } - throw r + throw r.unexpected } func apiDeleteToken(token: DeviceToken) async throws { @@ -612,80 +624,80 @@ func apiDeleteToken(token: DeviceToken) async throws { func testProtoServer(server: String) async throws -> Result<(), ProtocolTestFailure> { let userId = try currentUserId("testProtoServer") - let r = await chatSendCmd(.apiTestProtoServer(userId: userId, server: server)) + let r: ChatResponse0 = try await chatSendCmd(.apiTestProtoServer(userId: userId, server: server)) if case let .serverTestResult(_, _, testFailure) = r { if let t = testFailure { return .failure(t) } return .success(()) } - throw r + throw r.unexpected } func getServerOperators() async throws -> ServerOperatorConditions { - let r = await chatSendCmd(.apiGetServerOperators) + let r: ChatResponse0 = try await chatSendCmd(.apiGetServerOperators) if case let .serverOperatorConditions(conditions) = r { return conditions } logger.error("getServerOperators error: \(String(describing: r))") - throw r + throw r.unexpected } func getServerOperatorsSync() throws -> ServerOperatorConditions { - let r = chatSendCmdSync(.apiGetServerOperators) + let r: ChatResponse0 = try chatSendCmdSync(.apiGetServerOperators) if case let .serverOperatorConditions(conditions) = r { return conditions } logger.error("getServerOperators error: \(String(describing: r))") - throw r + throw r.unexpected } func setServerOperators(operators: [ServerOperator]) async throws -> ServerOperatorConditions { - let r = await chatSendCmd(.apiSetServerOperators(operators: operators)) + let r: ChatResponse0 = try await chatSendCmd(.apiSetServerOperators(operators: operators)) if case let .serverOperatorConditions(conditions) = r { return conditions } logger.error("setServerOperators error: \(String(describing: r))") - throw r + throw r.unexpected } func getUserServers() async throws -> [UserOperatorServers] { let userId = try currentUserId("getUserServers") - let r = await chatSendCmd(.apiGetUserServers(userId: userId)) + let r: ChatResponse0 = try await chatSendCmd(.apiGetUserServers(userId: userId)) if case let .userServers(_, userServers) = r { return userServers } logger.error("getUserServers error: \(String(describing: r))") - throw r + throw r.unexpected } func setUserServers(userServers: [UserOperatorServers]) async throws { let userId = try currentUserId("setUserServers") - let r = await chatSendCmd(.apiSetUserServers(userId: userId, userServers: userServers)) + let r: ChatResponse2 = try await chatSendCmd(.apiSetUserServers(userId: userId, userServers: userServers)) if case .cmdOk = r { return } logger.error("setUserServers error: \(String(describing: r))") - throw r + throw r.unexpected } func validateServers(userServers: [UserOperatorServers]) async throws -> [UserServersError] { let userId = try currentUserId("validateServers") - let r = await chatSendCmd(.apiValidateServers(userId: userId, userServers: userServers)) + let r: ChatResponse0 = try await chatSendCmd(.apiValidateServers(userId: userId, userServers: userServers)) if case let .userServersValidation(_, serverErrors) = r { return serverErrors } logger.error("validateServers error: \(String(describing: r))") - throw r + throw r.unexpected } func getUsageConditions() async throws -> (UsageConditions, String?, UsageConditions?) { - let r = await chatSendCmd(.apiGetUsageConditions) + let r: ChatResponse0 = try await chatSendCmd(.apiGetUsageConditions) if case let .usageConditions(usageConditions, conditionsText, acceptedConditions) = r { return (usageConditions, conditionsText, acceptedConditions) } logger.error("getUsageConditions error: \(String(describing: r))") - throw r + throw r.unexpected } func setConditionsNotified(conditionsId: Int64) async throws { - let r = await chatSendCmd(.apiSetConditionsNotified(conditionsId: conditionsId)) + let r: ChatResponse2 = try await chatSendCmd(.apiSetConditionsNotified(conditionsId: conditionsId)) if case .cmdOk = r { return } logger.error("setConditionsNotified error: \(String(describing: r))") - throw r + throw r.unexpected } func acceptConditions(conditionsId: Int64, operatorIds: [Int64]) async throws -> ServerOperatorConditions { - let r = await chatSendCmd(.apiAcceptConditions(conditionsId: conditionsId, operatorIds: operatorIds)) + let r: ChatResponse0 = try await chatSendCmd(.apiAcceptConditions(conditionsId: conditionsId, operatorIds: operatorIds)) if case let .serverOperatorConditions(conditions) = r { return conditions } logger.error("acceptConditions error: \(String(describing: r))") - throw r + throw r.unexpected } func getChatItemTTL() throws -> ChatItemTTL { @@ -698,7 +710,7 @@ func getChatItemTTLAsync() async throws -> ChatItemTTL { return try chatItemTTLResponse(await chatSendCmd(.apiGetChatItemTTL(userId: userId))) } -private func chatItemTTLResponse(_ r: ChatResponse) throws -> ChatItemTTL { +private func chatItemTTLResponse(_ r: ChatResponse0) throws -> ChatItemTTL { if case let .chatItemTTL(_, chatItemTTL) = r { if let ttl = chatItemTTL { return ChatItemTTL(ttl) @@ -706,7 +718,7 @@ private func chatItemTTLResponse(_ r: ChatResponse) throws -> ChatItemTTL { throw RuntimeError("chatItemTTLResponse: invalid ttl") } } - throw r + throw r.unexpected } func setChatItemTTL(_ chatItemTTL: ChatItemTTL) async throws { @@ -720,21 +732,21 @@ func setChatTTL(chatType: ChatType, id: Int64, _ chatItemTTL: ChatTTL) async thr } func getNetworkConfig() async throws -> NetCfg? { - let r = await chatSendCmd(.apiGetNetworkConfig) + let r: ChatResponse0 = try await chatSendCmd(.apiGetNetworkConfig) if case let .networkConfig(cfg) = r { return cfg } - throw r + throw r.unexpected } func setNetworkConfig(_ cfg: NetCfg, ctrl: chat_ctrl? = nil) throws { - let r = chatSendCmdSync(.apiSetNetworkConfig(networkConfig: cfg), ctrl) + let r: ChatResponse2 = try chatSendCmdSync(.apiSetNetworkConfig(networkConfig: cfg), ctrl: ctrl) if case .cmdOk = r { return } - throw r + throw r.unexpected } func apiSetNetworkInfo(_ networkInfo: UserNetworkInfo) throws { - let r = chatSendCmdSync(.apiSetNetworkInfo(networkInfo: networkInfo)) + let r: ChatResponse2 = try chatSendCmdSync(.apiSetNetworkInfo(networkInfo: networkInfo)) if case .cmdOk = r { return } - throw r + throw r.unexpected } func reconnectAllServers() async throws { @@ -755,93 +767,93 @@ func apiSetMemberSettings(_ groupId: Int64, _ groupMemberId: Int64, _ memberSett } func apiContactInfo(_ contactId: Int64) async throws -> (ConnectionStats?, Profile?) { - let r = await chatSendCmd(.apiContactInfo(contactId: contactId)) + let r: ChatResponse0 = try await chatSendCmd(.apiContactInfo(contactId: contactId)) if case let .contactInfo(_, _, connStats, customUserProfile) = r { return (connStats, customUserProfile) } - throw r + throw r.unexpected } func apiGroupMemberInfoSync(_ groupId: Int64, _ groupMemberId: Int64) throws -> (GroupMember, ConnectionStats?) { - let r = chatSendCmdSync(.apiGroupMemberInfo(groupId: groupId, groupMemberId: groupMemberId)) + let r: ChatResponse0 = try chatSendCmdSync(.apiGroupMemberInfo(groupId: groupId, groupMemberId: groupMemberId)) if case let .groupMemberInfo(_, _, member, connStats_) = r { return (member, connStats_) } - throw r + throw r.unexpected } func apiGroupMemberInfo(_ groupId: Int64, _ groupMemberId: Int64) async throws -> (GroupMember, ConnectionStats?) { - let r = await chatSendCmd(.apiGroupMemberInfo(groupId: groupId, groupMemberId: groupMemberId)) + let r: ChatResponse0 = try await chatSendCmd(.apiGroupMemberInfo(groupId: groupId, groupMemberId: groupMemberId)) if case let .groupMemberInfo(_, _, member, connStats_) = r { return (member, connStats_) } - throw r + throw r.unexpected } func apiContactQueueInfo(_ contactId: Int64) async throws -> (RcvMsgInfo?, ServerQueueInfo) { - let r = await chatSendCmd(.apiContactQueueInfo(contactId: contactId)) + let r: ChatResponse0 = try await chatSendCmd(.apiContactQueueInfo(contactId: contactId)) if case let .queueInfo(_, rcvMsgInfo, queueInfo) = r { return (rcvMsgInfo, queueInfo) } - throw r + throw r.unexpected } func apiGroupMemberQueueInfo(_ groupId: Int64, _ groupMemberId: Int64) async throws -> (RcvMsgInfo?, ServerQueueInfo) { - let r = await chatSendCmd(.apiGroupMemberQueueInfo(groupId: groupId, groupMemberId: groupMemberId)) + let r: ChatResponse0 = try await chatSendCmd(.apiGroupMemberQueueInfo(groupId: groupId, groupMemberId: groupMemberId)) if case let .queueInfo(_, rcvMsgInfo, queueInfo) = r { return (rcvMsgInfo, queueInfo) } - throw r + throw r.unexpected } func apiSwitchContact(contactId: Int64) throws -> ConnectionStats { - let r = chatSendCmdSync(.apiSwitchContact(contactId: contactId)) + let r: ChatResponse0 = try chatSendCmdSync(.apiSwitchContact(contactId: contactId)) if case let .contactSwitchStarted(_, _, connectionStats) = r { return connectionStats } - throw r + throw r.unexpected } func apiSwitchGroupMember(_ groupId: Int64, _ groupMemberId: Int64) throws -> ConnectionStats { - let r = chatSendCmdSync(.apiSwitchGroupMember(groupId: groupId, groupMemberId: groupMemberId)) + let r: ChatResponse0 = try chatSendCmdSync(.apiSwitchGroupMember(groupId: groupId, groupMemberId: groupMemberId)) if case let .groupMemberSwitchStarted(_, _, _, connectionStats) = r { return connectionStats } - throw r + throw r.unexpected } func apiAbortSwitchContact(_ contactId: Int64) throws -> ConnectionStats { - let r = chatSendCmdSync(.apiAbortSwitchContact(contactId: contactId)) + let r: ChatResponse0 = try chatSendCmdSync(.apiAbortSwitchContact(contactId: contactId)) if case let .contactSwitchAborted(_, _, connectionStats) = r { return connectionStats } - throw r + throw r.unexpected } func apiAbortSwitchGroupMember(_ groupId: Int64, _ groupMemberId: Int64) throws -> ConnectionStats { - let r = chatSendCmdSync(.apiAbortSwitchGroupMember(groupId: groupId, groupMemberId: groupMemberId)) + let r: ChatResponse0 = try chatSendCmdSync(.apiAbortSwitchGroupMember(groupId: groupId, groupMemberId: groupMemberId)) if case let .groupMemberSwitchAborted(_, _, _, connectionStats) = r { return connectionStats } - throw r + throw r.unexpected } func apiSyncContactRatchet(_ contactId: Int64, _ force: Bool) throws -> ConnectionStats { - let r = chatSendCmdSync(.apiSyncContactRatchet(contactId: contactId, force: force)) + let r: ChatResponse0 = try chatSendCmdSync(.apiSyncContactRatchet(contactId: contactId, force: force)) if case let .contactRatchetSyncStarted(_, _, connectionStats) = r { return connectionStats } - throw r + throw r.unexpected } func apiSyncGroupMemberRatchet(_ groupId: Int64, _ groupMemberId: Int64, _ force: Bool) throws -> (GroupMember, ConnectionStats) { - let r = chatSendCmdSync(.apiSyncGroupMemberRatchet(groupId: groupId, groupMemberId: groupMemberId, force: force)) + let r: ChatResponse0 = try chatSendCmdSync(.apiSyncGroupMemberRatchet(groupId: groupId, groupMemberId: groupMemberId, force: force)) if case let .groupMemberRatchetSyncStarted(_, _, member, connectionStats) = r { return (member, connectionStats) } - throw r + throw r.unexpected } func apiGetContactCode(_ contactId: Int64) async throws -> (Contact, String) { - let r = await chatSendCmd(.apiGetContactCode(contactId: contactId)) + let r: ChatResponse0 = try await chatSendCmd(.apiGetContactCode(contactId: contactId)) if case let .contactCode(_, contact, connectionCode) = r { return (contact, connectionCode) } - throw r + throw r.unexpected } func apiGetGroupMemberCode(_ groupId: Int64, _ groupMemberId: Int64) async throws -> (GroupMember, String) { - let r = await chatSendCmd(.apiGetGroupMemberCode(groupId: groupId, groupMemberId: groupMemberId)) + let r: ChatResponse0 = try await chatSendCmd(.apiGetGroupMemberCode(groupId: groupId, groupMemberId: groupMemberId)) if case let .groupMemberCode(_, _, member, connectionCode) = r { return (member, connectionCode) } - throw r + throw r.unexpected } func apiVerifyContact(_ contactId: Int64, connectionCode: String?) -> (Bool, String)? { - let r = chatSendCmdSync(.apiVerifyContact(contactId: contactId, connectionCode: connectionCode)) - if case let .connectionVerified(_, verified, expectedCode) = r { return (verified, expectedCode) } + let r: APIResult = chatApiSendCmdSync(.apiVerifyContact(contactId: contactId, connectionCode: connectionCode)) + if case let .result(.connectionVerified(_, verified, expectedCode)) = r { return (verified, expectedCode) } logger.error("apiVerifyContact error: \(String(describing: r))") return nil } func apiVerifyGroupMember(_ groupId: Int64, _ groupMemberId: Int64, connectionCode: String?) -> (Bool, String)? { - let r = chatSendCmdSync(.apiVerifyGroupMember(groupId: groupId, groupMemberId: groupMemberId, connectionCode: connectionCode)) - if case let .connectionVerified(_, verified, expectedCode) = r { return (verified, expectedCode) } + let r: APIResult = chatApiSendCmdSync(.apiVerifyGroupMember(groupId: groupId, groupMemberId: groupMemberId, connectionCode: connectionCode)) + if case let .result(.connectionVerified(_, verified, expectedCode)) = r { return (verified, expectedCode) } logger.error("apiVerifyGroupMember error: \(String(describing: r))") return nil } @@ -852,23 +864,23 @@ func apiAddContact(incognito: Bool) async -> ((CreatedConnLink, PendingContactCo return (nil, nil) } let short = UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_SHORT_LINKS) - let r = await chatSendCmd(.apiAddContact(userId: userId, short: short, incognito: incognito), bgTask: false) - if case let .invitation(_, connLinkInv, connection) = r { return ((connLinkInv, connection), nil) } + let r: APIResult = await chatApiSendCmd(.apiAddContact(userId: userId, short: short, incognito: incognito), bgTask: false) + if case let .result(.invitation(_, connLinkInv, connection)) = r { return ((connLinkInv, connection), nil) } let alert = connectionErrorAlert(r) return (nil, alert) } func apiSetConnectionIncognito(connId: Int64, incognito: Bool) async throws -> PendingContactConnection? { - let r = await chatSendCmd(.apiSetConnectionIncognito(connId: connId, incognito: incognito)) + let r: ChatResponse1 = try await chatSendCmd(.apiSetConnectionIncognito(connId: connId, incognito: incognito)) if case let .connectionIncognitoUpdated(_, toConnection) = r { return toConnection } - throw r + throw r.unexpected } func apiChangeConnectionUser(connId: Int64, userId: Int64) async throws -> PendingContactConnection { - let r = await chatSendCmd(.apiChangeConnectionUser(connId: connId, userId: userId)) + let r: ChatResponse1 = try await chatSendCmd(.apiChangeConnectionUser(connId: connId, userId: userId)) if case let .connectionUserChanged(_, _, toConnection, _) = r {return toConnection} - throw r + throw r.unexpected } func apiConnectPlan(connLink: String) async -> ((CreatedConnLink, ConnectionPlan)?, Alert?) { @@ -876,9 +888,9 @@ func apiConnectPlan(connLink: String) async -> ((CreatedConnLink, ConnectionPlan logger.error("apiConnectPlan: no current user") return (nil, nil) } - let r = await chatSendCmd(.apiConnectPlan(userId: userId, connLink: connLink)) - if case let .connectionPlan(_, connLink, connPlan) = r { return ((connLink, connPlan), nil) } - let alert = apiConnectResponseAlert(r) ?? connectionErrorAlert(r) + let r: APIResult = await chatApiSendCmd(.apiConnectPlan(userId: userId, connLink: connLink)) + if case let .result(.connectionPlan(_, connLink, connPlan)) = r { return ((connLink, connPlan), nil) } + let alert = apiConnectResponseAlert(r.unexpected) ?? connectionErrorAlert(r) return (nil, alert) } @@ -897,14 +909,14 @@ func apiConnect_(incognito: Bool, connLink: CreatedConnLink) async -> ((ConnReqT logger.error("apiConnect: no current user") return (nil, nil) } - let r: ChatResponse = await chatSendCmd(.apiConnect(userId: userId, incognito: incognito, connLink: connLink)) + let r: APIResult = await chatApiSendCmd(.apiConnect(userId: userId, incognito: incognito, connLink: connLink)) let m = ChatModel.shared switch r { - case let .sentConfirmation(_, connection): + case let .result(.sentConfirmation(_, connection)): return ((.invitation, connection), nil) - case let .sentInvitation(_, connection): + case let .result(.sentInvitation(_, connection)): return ((.contact, connection), nil) - case let .contactAlreadyExists(_, contact): + case let .result(.contactAlreadyExists(_, contact)): if let c = m.getContactChat(contact.contactId) { ItemsModel.shared.loadOpenChat(c.id) } @@ -912,28 +924,28 @@ func apiConnect_(incognito: Bool, connLink: CreatedConnLink) async -> ((ConnReqT return (nil, alert) default: () } - let alert = apiConnectResponseAlert(r) ?? connectionErrorAlert(r) + let alert = apiConnectResponseAlert(r.unexpected) ?? connectionErrorAlert(r) return (nil, alert) } -private func apiConnectResponseAlert(_ r: ChatResponse) -> Alert? { +private func apiConnectResponseAlert(_ r: ChatError) -> Alert? { switch r { - case .chatCmdError(_, .error(.invalidConnReq)): + case .error(.invalidConnReq): mkAlert( title: "Invalid connection link", message: "Please check that you used the correct link or ask your contact to send you another one." ) - case .chatCmdError(_, .error(.unsupportedConnReq)): + case .error(.unsupportedConnReq): mkAlert( title: "Unsupported connection link", message: "This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." ) - case .chatCmdError(_, .errorAgent(.SMP(_, .AUTH))): + case .errorAgent(.SMP(_, .AUTH)): mkAlert( title: "Connection error (AUTH)", message: "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." ) - case let .chatCmdError(_, .errorAgent(.SMP(_, .BLOCKED(info)))): + case let .errorAgent(.SMP(_, .BLOCKED(info))): Alert( title: Text("Connection blocked"), message: Text("Connection is blocked by server operator:\n\(info.reason.text)"), @@ -944,12 +956,12 @@ private func apiConnectResponseAlert(_ r: ChatResponse) -> Alert? { } } ) - case .chatCmdError(_, .errorAgent(.SMP(_, .QUOTA))): + case .errorAgent(.SMP(_, .QUOTA)): mkAlert( title: "Undelivered messages", message: "The connection reached the limit of undelivered messages, your contact may be offline." ) - case let .chatCmdError(_, .errorAgent(.INTERNAL(internalErr))): + case let .errorAgent(.INTERNAL(internalErr)): if internalErr == "SEUniqueID" { mkAlert( title: "Already connected?", @@ -969,13 +981,13 @@ func contactAlreadyExistsAlert(_ contact: Contact) -> Alert { ) } -private func connectionErrorAlert(_ r: ChatResponse) -> Alert { +private func connectionErrorAlert(_ r: APIResult) -> Alert { if let networkErrorAlert = networkErrorAlert(r) { return networkErrorAlert } else { return mkAlert( title: "Connection error", - message: "Error: \(responseError(r))" + message: "Error: \(responseError(r.unexpected))" ) } } @@ -985,9 +997,9 @@ func apiConnectContactViaAddress(incognito: Bool, contactId: Int64) async -> (Co 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 r: APIResult = await chatApiSendCmd(.apiConnectContactViaAddress(userId: userId, incognito: incognito, contactId: contactId)) + if case let .result(.sentInvitationToContact(_, contact, _)) = r { return (contact, nil) } + logger.error("apiConnectContactViaAddress error: \(responseError(r.unexpected))") let alert = connectionErrorAlert(r) return (nil, alert) } @@ -996,11 +1008,11 @@ func apiDeleteChat(type: ChatType, id: Int64, chatDeleteMode: ChatDeleteMode = . let chatId = type.rawValue + id.description DispatchQueue.main.async { ChatModel.shared.deletedChats.insert(chatId) } defer { DispatchQueue.main.async { ChatModel.shared.deletedChats.remove(chatId) } } - let r = await chatSendCmd(.apiDeleteChat(type: type, id: id, chatDeleteMode: chatDeleteMode), bgTask: false) + let r: ChatResponse1 = try await chatSendCmd(.apiDeleteChat(type: type, id: id, chatDeleteMode: chatDeleteMode), bgTask: false) if case .direct = type, case .contactDeleted = r { return } if case .contactConnection = type, case .contactConnectionDeleted = r { return } if case .group = type, case .groupDeletedUser = r { return } - throw r + throw r.unexpected } func apiDeleteContact(id: Int64, chatDeleteMode: ChatDeleteMode = .full(notify: true)) async throws -> Contact { @@ -1014,9 +1026,9 @@ func apiDeleteContact(id: Int64, chatDeleteMode: ChatDeleteMode = .full(notify: DispatchQueue.main.async { ChatModel.shared.deletedChats.remove(chatId) } } } - let r = await chatSendCmd(.apiDeleteChat(type: type, id: id, chatDeleteMode: chatDeleteMode), bgTask: false) + let r: ChatResponse1 = try await chatSendCmd(.apiDeleteChat(type: type, id: id, chatDeleteMode: chatDeleteMode), bgTask: false) if case let .contactDeleted(_, contact) = r { return contact } - throw r + throw r.unexpected } func deleteChat(_ chat: Chat, chatDeleteMode: ChatDeleteMode = .full(notify: true)) async { @@ -1067,9 +1079,9 @@ func deleteContactChat(_ chat: Chat, chatDeleteMode: ChatDeleteMode = .full(noti func apiClearChat(type: ChatType, id: Int64) async throws -> ChatInfo { - let r = await chatSendCmd(.apiClearChat(type: type, id: id), bgTask: false) + let r: ChatResponse1 = try await chatSendCmd(.apiClearChat(type: type, id: id), bgTask: false) if case let .chatCleared(_, updatedChatInfo) = r { return updatedChatInfo } - throw r + throw r.unexpected } func clearChat(_ chat: Chat) async { @@ -1084,119 +1096,125 @@ func clearChat(_ chat: Chat) async { func apiListContacts() throws -> [Contact] { let userId = try currentUserId("apiListContacts") - let r = chatSendCmdSync(.apiListContacts(userId: userId)) + let r: ChatResponse1 = try chatSendCmdSync(.apiListContacts(userId: userId)) if case let .contactsList(_, contacts) = r { return contacts } - throw r + throw r.unexpected } func apiUpdateProfile(profile: Profile) async throws -> (Profile, [Contact])? { let userId = try currentUserId("apiUpdateProfile") - let r = await chatSendCmd(.apiUpdateProfile(userId: userId, profile: profile)) + let r: APIResult = await chatApiSendCmd(.apiUpdateProfile(userId: userId, profile: profile)) switch r { - case .userProfileNoChange: return (profile, []) - case let .userProfileUpdated(_, _, toProfile, updateSummary): return (toProfile, updateSummary.changedContacts) - case .chatCmdError(_, .errorStore(.duplicateName)): return nil; - default: throw r + case .result(.userProfileNoChange): return (profile, []) + case let .result(.userProfileUpdated(_, _, toProfile, updateSummary)): return (toProfile, updateSummary.changedContacts) + case .error(.errorStore(.duplicateName)): return nil; + default: throw r.unexpected } } func apiSetProfileAddress(on: Bool) async throws -> User? { let userId = try currentUserId("apiSetProfileAddress") - let r = await chatSendCmd(.apiSetProfileAddress(userId: userId, on: on)) + let r: ChatResponse1 = try await chatSendCmd(.apiSetProfileAddress(userId: userId, on: on)) switch r { case .userProfileNoChange: return nil case let .userProfileUpdated(user, _, _, _): return user - default: throw r + default: throw r.unexpected } } func apiSetContactPrefs(contactId: Int64, preferences: Preferences) async throws -> Contact? { - let r = await chatSendCmd(.apiSetContactPrefs(contactId: contactId, preferences: preferences)) + let r: ChatResponse1 = try await chatSendCmd(.apiSetContactPrefs(contactId: contactId, preferences: preferences)) if case let .contactPrefsUpdated(_, _, toContact) = r { return toContact } - throw r + throw r.unexpected } func apiSetContactAlias(contactId: Int64, localAlias: String) async throws -> Contact? { - let r = await chatSendCmd(.apiSetContactAlias(contactId: contactId, localAlias: localAlias)) + let r: ChatResponse1 = try await chatSendCmd(.apiSetContactAlias(contactId: contactId, localAlias: localAlias)) if case let .contactAliasUpdated(_, toContact) = r { return toContact } - throw r + throw r.unexpected } func apiSetGroupAlias(groupId: Int64, localAlias: String) async throws -> GroupInfo? { - let r = await chatSendCmd(.apiSetGroupAlias(groupId: groupId, localAlias: localAlias)) + let r: ChatResponse1 = try await chatSendCmd(.apiSetGroupAlias(groupId: groupId, localAlias: localAlias)) if case let .groupAliasUpdated(_, toGroup) = r { return toGroup } - throw r + throw r.unexpected } func apiSetConnectionAlias(connId: Int64, localAlias: String) async throws -> PendingContactConnection? { - let r = await chatSendCmd(.apiSetConnectionAlias(connId: connId, localAlias: localAlias)) + let r: ChatResponse1 = try await chatSendCmd(.apiSetConnectionAlias(connId: connId, localAlias: localAlias)) if case let .connectionAliasUpdated(_, toConnection) = r { return toConnection } - throw r + throw r.unexpected } func apiSetUserUIThemes(userId: Int64, themes: ThemeModeOverrides?) async -> Bool { - let r = await chatSendCmd(.apiSetUserUIThemes(userId: userId, themes: themes)) - if case .cmdOk = r { return true } - logger.error("apiSetUserUIThemes bad response: \(String(describing: r))") - return false + do { + try await sendCommandOkResp(.apiSetUserUIThemes(userId: userId, themes: themes)) + return true + } catch { + logger.error("apiSetUserUIThemes bad response: \(responseError(error))") + return false + } } func apiSetChatUIThemes(chatId: ChatId, themes: ThemeModeOverrides?) async -> Bool { - let r = await chatSendCmd(.apiSetChatUIThemes(chatId: chatId, themes: themes)) - if case .cmdOk = r { return true } - logger.error("apiSetChatUIThemes bad response: \(String(describing: r))") - return false + do { + try await sendCommandOkResp(.apiSetChatUIThemes(chatId: chatId, themes: themes)) + return true + } catch { + logger.error("apiSetChatUIThemes bad response: \(responseError(error))") + return false + } } func apiCreateUserAddress(short: Bool) async throws -> CreatedConnLink { let userId = try currentUserId("apiCreateUserAddress") - let r = await chatSendCmd(.apiCreateMyAddress(userId: userId, short: short)) + let r: ChatResponse1 = try await chatSendCmd(.apiCreateMyAddress(userId: userId, short: short)) if case let .userContactLinkCreated(_, connLink) = r { return connLink } - throw r + throw r.unexpected } func apiDeleteUserAddress() async throws -> User? { let userId = try currentUserId("apiDeleteUserAddress") - let r = await chatSendCmd(.apiDeleteMyAddress(userId: userId)) + let r: ChatResponse1 = try await chatSendCmd(.apiDeleteMyAddress(userId: userId)) if case let .userContactLinkDeleted(user) = r { return user } - throw r + throw r.unexpected } func apiGetUserAddress() throws -> UserContactLink? { let userId = try currentUserId("apiGetUserAddress") - return try userAddressResponse(chatSendCmdSync(.apiShowMyAddress(userId: userId))) + return try userAddressResponse(chatApiSendCmdSync(.apiShowMyAddress(userId: userId))) } func apiGetUserAddressAsync() async throws -> UserContactLink? { let userId = try currentUserId("apiGetUserAddressAsync") - return try userAddressResponse(await chatSendCmd(.apiShowMyAddress(userId: userId))) + return try userAddressResponse(await chatApiSendCmd(.apiShowMyAddress(userId: userId))) } -private func userAddressResponse(_ r: ChatResponse) throws -> UserContactLink? { +private func userAddressResponse(_ r: APIResult) throws -> UserContactLink? { switch r { - case let .userContactLink(_, contactLink): return contactLink - case .chatCmdError(_, chatError: .errorStore(storeError: .userContactLinkNotFound)): return nil - default: throw r + case let .result(.userContactLink(_, contactLink)): return contactLink + case .error(.errorStore(storeError: .userContactLinkNotFound)): return nil + default: throw r.unexpected } } func userAddressAutoAccept(_ autoAccept: AutoAccept?) async throws -> UserContactLink? { let userId = try currentUserId("userAddressAutoAccept") - let r = await chatSendCmd(.apiAddressAutoAccept(userId: userId, autoAccept: autoAccept)) + let r: APIResult = await chatApiSendCmd(.apiAddressAutoAccept(userId: userId, autoAccept: autoAccept)) switch r { - case let .userContactLinkUpdated(_, contactLink): return contactLink - case .chatCmdError(_, chatError: .errorStore(storeError: .userContactLinkNotFound)): return nil - default: throw r + case let .result(.userContactLinkUpdated(_, contactLink)): return contactLink + case .error(.errorStore(storeError: .userContactLinkNotFound)): return nil + default: throw r.unexpected } } func apiAcceptContactRequest(incognito: Bool, contactReqId: Int64) async -> Contact? { - let r = await chatSendCmd(.apiAcceptContact(incognito: incognito, contactReqId: contactReqId)) + let r: APIResult = await chatApiSendCmd(.apiAcceptContact(incognito: incognito, contactReqId: contactReqId)) let am = AlertManager.shared - if case let .acceptingContactRequest(_, contact) = r { return contact } - if case .chatCmdError(_, .errorAgent(.SMP(_, .AUTH))) = r { + if case let .result(.acceptingContactRequest(_, contact)) = r { return contact } + if case .error(.errorAgent(.SMP(_, .AUTH))) = r { am.showAlertMsg( title: "Connection error (AUTH)", message: "Sender may have deleted the connection request." @@ -1207,16 +1225,16 @@ func apiAcceptContactRequest(incognito: Bool, contactReqId: Int64) async -> Cont logger.error("apiAcceptContactRequest error: \(String(describing: r))") am.showAlertMsg( title: "Error accepting contact request", - message: "Error: \(responseError(r))" + message: "Error: \(responseError(r.unexpected))" ) } return nil } func apiRejectContactRequest(contactReqId: Int64) async throws { - let r = await chatSendCmd(.apiRejectContact(contactReqId: contactReqId)) + let r: ChatResponse1 = try await chatSendCmd(.apiRejectContact(contactReqId: contactReqId)) if case .contactRequestRejected = r { return } - throw r + throw r.unexpected } func apiChatRead(type: ChatType, id: Int64) async throws { @@ -1232,31 +1250,33 @@ func apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) async throws { } func uploadStandaloneFile(user: any UserLike, file: CryptoFile, ctrl: chat_ctrl? = nil) async -> (FileTransferMeta?, String?) { - let r = await chatSendCmd(.apiUploadStandaloneFile(userId: user.userId, file: file), ctrl) - if case let .sndStandaloneFileCreated(_, fileTransferMeta) = r { + let r: APIResult = await chatApiSendCmd(.apiUploadStandaloneFile(userId: user.userId, file: file), ctrl: ctrl) + if case let .result(.sndStandaloneFileCreated(_, fileTransferMeta)) = r { return (fileTransferMeta, nil) } else { - logger.error("uploadStandaloneFile error: \(String(describing: r))") - return (nil, responseError(r)) + let err = responseError(r.unexpected) + logger.error("uploadStandaloneFile error: \(err)") + return (nil, err) } } func downloadStandaloneFile(user: any UserLike, url: String, file: CryptoFile, ctrl: chat_ctrl? = nil) async -> (RcvFileTransfer?, String?) { - let r = await chatSendCmd(.apiDownloadStandaloneFile(userId: user.userId, url: url, file: file), ctrl) - if case let .rcvStandaloneFileCreated(_, rcvFileTransfer) = r { + let r: APIResult = await chatApiSendCmd(.apiDownloadStandaloneFile(userId: user.userId, url: url, file: file), ctrl: ctrl) + if case let .result(.rcvStandaloneFileCreated(_, rcvFileTransfer)) = r { return (rcvFileTransfer, nil) } else { - logger.error("downloadStandaloneFile error: \(String(describing: r))") - return (nil, responseError(r)) + let err = responseError(r.unexpected) + logger.error("downloadStandaloneFile error: \(err)") + return (nil, err) } } func standaloneFileInfo(url: String, ctrl: chat_ctrl? = nil) async -> MigrationFileLinkData? { - let r = await chatSendCmd(.apiStandaloneFileInfo(url: url), ctrl) - if case let .standaloneFileInfo(fileMeta) = r { + let r: APIResult = await chatApiSendCmd(.apiStandaloneFileInfo(url: url), ctrl: ctrl) + if case let .result(.standaloneFileInfo(fileMeta)) = r { return fileMeta } else { - logger.error("standaloneFileInfo error: \(String(describing: r))") + logger.error("standaloneFileInfo error: \(responseError(r.unexpected))") return nil } } @@ -1271,12 +1291,12 @@ func receiveFile(user: any UserLike, fileId: Int64, userApprovedRelays: Bool = f } func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool = false, auto: Bool = false) async { - var fileIdsToApprove = [Int64]() - var srvsToApprove = Set() - var otherFileErrs = [ChatResponse]() - + var fileIdsToApprove: [Int64] = [] + var srvsToApprove: Set = [] + var otherFileErrs: [APIResult] = [] + for fileId in fileIds { - let r = await chatSendCmd( + let r: APIResult = await chatApiSendCmd( .receiveFile( fileId: fileId, userApprovedRelays: userApprovedRelays || !privacyAskToApproveRelaysGroupDefault.get(), @@ -1285,36 +1305,22 @@ func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool ) ) switch r { - case let .rcvFileAccepted(_, chatItem): + case let .result(.rcvFileAccepted(_, chatItem)): await chatItemSimpleUpdate(user, chatItem) -// TODO when aChatItem added -// case let .rcvFileAcceptedSndCancelled(user, aChatItem, _): -// await chatItemSimpleUpdate(user, aChatItem) -// Task { cleanupFile(aChatItem) } + // TODO when aChatItem added + // case let .rcvFileAcceptedSndCancelled(user, aChatItem, _): + // await chatItemSimpleUpdate(user, aChatItem) + // Task { cleanupFile(aChatItem) } + case let .error(.error(.fileNotApproved(fileId, unknownServers))): + fileIdsToApprove.append(fileId) + srvsToApprove.formUnion(unknownServers) default: - if let chatError = r.chatErrorType { - switch chatError { - case let .fileNotApproved(fileId, unknownServers): - fileIdsToApprove.append(fileId) - srvsToApprove.formUnion(unknownServers) - default: - otherFileErrs.append(r) - } - } + otherFileErrs.append(r) } } - + if !auto { - let otherErrsStr = if otherFileErrs.isEmpty { - "" - } else if otherFileErrs.count == 1 { - "\(otherFileErrs[0])" - } else if otherFileErrs.count == 2 { - "\(otherFileErrs[0])\n\(otherFileErrs[1])" - } else { - "\(otherFileErrs[0])\n\(otherFileErrs[1])\nand \(otherFileErrs.count - 2) other error(s)" - } - + let otherErrsStr = fileErrorStrs(otherFileErrs) // If there are not approved files, alert is shown the same way both in case of singular and plural files reception if !fileIdsToApprove.isEmpty { let srvs = srvsToApprove @@ -1350,7 +1356,7 @@ func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool } else if otherFileErrs.count == 1 { // If there is a single other error, we differentiate on it let errorResponse = otherFileErrs.first! switch errorResponse { - case let .rcvFileAcceptedSndCancelled(_, rcvFileTransfer): + case let .result(.rcvFileAcceptedSndCancelled(_, rcvFileTransfer)): logger.debug("receiveFiles error: sender cancelled file transfer \(rcvFileTransfer.fileId)") await MainActor.run { showAlert( @@ -1358,19 +1364,14 @@ func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool message: NSLocalizedString("Sender cancelled file transfer.", comment: "alert message") ) } + case .error(.error(.fileCancelled)), .error(.error(.fileAlreadyReceiving)): + logger.debug("receiveFiles ignoring FileCancelled or FileAlreadyReceiving error") default: - if let chatError = errorResponse.chatErrorType { - switch chatError { - case .fileCancelled, .fileAlreadyReceiving: - logger.debug("receiveFiles ignoring FileCancelled or FileAlreadyReceiving error") - default: - await MainActor.run { - showAlert( - NSLocalizedString("Error receiving file", comment: "alert title"), - message: responseError(errorResponse) - ) - } - } + await MainActor.run { + showAlert( + NSLocalizedString("Error receiving file", comment: "alert title"), + message: responseError(errorResponse.unexpected) + ) } } } else if otherFileErrs.count > 1 { // If there are multiple other errors, we show general alert @@ -1382,8 +1383,22 @@ func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool } } } + + func fileErrorStrs(_ errs: [APIResult]) -> String { + var errStr = "" + if errs.count >= 1 { + errStr = String(describing: errs[0].unexpected) + } + if errs.count >= 2 { + errStr += "\n\(String(describing: errs[1].unexpected))" + } + if errs.count > 2 { + errStr += "\nand \(errs.count - 2) other error(s)" + } + return errStr + } } - + func cancelFile(user: User, fileId: Int64) async { if let chatItem = await apiCancelFile(fileId: fileId) { await chatItemSimpleUpdate(user, chatItem) @@ -1392,12 +1407,12 @@ func cancelFile(user: User, fileId: Int64) async { } func apiCancelFile(fileId: Int64, ctrl: chat_ctrl? = nil) async -> AChatItem? { - let r = await chatSendCmd(.cancelFile(fileId: fileId), ctrl) + let r: APIResult = await chatApiSendCmd(.cancelFile(fileId: fileId), ctrl: ctrl) switch r { - case let .sndFileCancelled(_, chatItem, _, _) : return chatItem - case let .rcvFileCancelled(_, chatItem, _) : return chatItem + case let .result(.sndFileCancelled(_, chatItem, _, _)) : return chatItem + case let .result(.rcvFileCancelled(_, chatItem, _)) : return chatItem default: - logger.error("apiCancelFile error: \(String(describing: r))") + logger.error("apiCancelFile error: \(responseError(r.unexpected))") return nil } } @@ -1407,9 +1422,9 @@ func setLocalDeviceName(_ displayName: String) throws { } func connectRemoteCtrl(desktopAddress: String) async throws -> (RemoteCtrlInfo?, CtrlAppInfo, String) { - let r = await chatSendCmd(.connectRemoteCtrl(xrcpInvitation: desktopAddress)) + let r: ChatResponse2 = try await chatSendCmd(.connectRemoteCtrl(xrcpInvitation: desktopAddress)) if case let .remoteCtrlConnecting(rc_, ctrlAppInfo, v) = r { return (rc_, ctrlAppInfo, v) } - throw r + throw r.unexpected } func findKnownRemoteCtrl() async throws { @@ -1417,21 +1432,21 @@ func findKnownRemoteCtrl() async throws { } func confirmRemoteCtrl(_ rcId: Int64) async throws -> (RemoteCtrlInfo?, CtrlAppInfo, String) { - let r = await chatSendCmd(.confirmRemoteCtrl(remoteCtrlId: rcId)) + let r: ChatResponse2 = try await chatSendCmd(.confirmRemoteCtrl(remoteCtrlId: rcId)) if case let .remoteCtrlConnecting(rc_, ctrlAppInfo, v) = r { return (rc_, ctrlAppInfo, v) } - throw r + throw r.unexpected } func verifyRemoteCtrlSession(_ sessCode: String) async throws -> RemoteCtrlInfo { - let r = await chatSendCmd(.verifyRemoteCtrlSession(sessionCode: sessCode)) + let r: ChatResponse2 = try await chatSendCmd(.verifyRemoteCtrlSession(sessionCode: sessCode)) if case let .remoteCtrlConnected(rc) = r { return rc } - throw r + throw r.unexpected } func listRemoteCtrls() throws -> [RemoteCtrlInfo] { - let r = chatSendCmdSync(.listRemoteCtrls) + let r: ChatResponse2 = try chatSendCmdSync(.listRemoteCtrls) if case let .remoteCtrlList(rcInfo) = r { return rcInfo } - throw r + throw r.unexpected } func stopRemoteCtrl() async throws { @@ -1442,8 +1457,8 @@ func deleteRemoteCtrl(_ rcId: Int64) async throws { try await sendCommandOkResp(.deleteRemoteCtrl(remoteCtrlId: rcId)) } -func networkErrorAlert(_ r: ChatResponse) -> Alert? { - if let alert = getNetworkErrorAlert(r) { +func networkErrorAlert(_ res: APIResult) -> Alert? { + if case let .error(e) = res, let alert = getNetworkErrorAlert(e) { return mkAlert(title: alert.title, message: alert.message) } else { return nil @@ -1505,15 +1520,15 @@ func apiEndCall(_ contact: Contact) async throws { } func apiGetCallInvitationsSync() throws -> [RcvCallInvitation] { - let r = chatSendCmdSync(.apiGetCallInvitations) + let r: ChatResponse2 = try chatSendCmdSync(.apiGetCallInvitations) if case let .callInvitations(invs) = r { return invs } - throw r + throw r.unexpected } func apiGetCallInvitations() async throws -> [RcvCallInvitation] { - let r = await chatSendCmd(.apiGetCallInvitations) + let r: ChatResponse2 = try await chatSendCmd(.apiGetCallInvitations) if case let .callInvitations(invs) = r { return invs } - throw r + throw r.unexpected } func apiCallStatus(_ contact: Contact, _ status: String) async throws { @@ -1525,9 +1540,9 @@ func apiCallStatus(_ contact: Contact, _ status: String) async throws { } func apiGetNetworkStatuses() throws -> [ConnNetworkStatus] { - let r = chatSendCmdSync(.apiGetNetworkStatuses) + let r: ChatResponse1 = try chatSendCmdSync(.apiGetNetworkStatuses) if case let .networkStatuses(_, statuses) = r { return statuses } - throw r + throw r.unexpected } func markChatRead(_ chat: Chat) async { @@ -1570,29 +1585,29 @@ func apiMarkChatItemsRead(_ cInfo: ChatInfo, _ itemIds: [ChatItem.ID], mentionsR } } -private func sendCommandOkResp(_ cmd: ChatCommand, _ ctrl: chat_ctrl? = nil) async throws { - let r = await chatSendCmd(cmd, ctrl) +private func sendCommandOkResp(_ cmd: ChatCommand, ctrl: chat_ctrl? = nil) async throws { + let r: ChatResponse2 = try await chatSendCmd(cmd, ctrl: ctrl) if case .cmdOk = r { return } - throw r + throw r.unexpected } private func sendCommandOkRespSync(_ cmd: ChatCommand) throws { - let r = chatSendCmdSync(cmd) + let r: ChatResponse2 = try chatSendCmdSync(cmd) if case .cmdOk = r { return } - throw r + throw r.unexpected } func apiNewGroup(incognito: Bool, groupProfile: GroupProfile) throws -> GroupInfo { let userId = try currentUserId("apiNewGroup") - let r = chatSendCmdSync(.apiNewGroup(userId: userId, incognito: incognito, groupProfile: groupProfile)) + let r: ChatResponse2 = try chatSendCmdSync(.apiNewGroup(userId: userId, incognito: incognito, groupProfile: groupProfile)) if case let .groupCreated(_, groupInfo) = r { return groupInfo } - throw r + throw r.unexpected } func apiAddMember(_ groupId: Int64, _ contactId: Int64, _ memberRole: GroupMemberRole) async throws -> GroupMember { - let r = await chatSendCmd(.apiAddMember(groupId: groupId, contactId: contactId, memberRole: memberRole)) + let r: ChatResponse2 = try await chatSendCmd(.apiAddMember(groupId: groupId, contactId: contactId, memberRole: memberRole)) if case let .sentGroupInvitation(_, _, _, member) = r { return member } - throw r + throw r.unexpected } enum JoinGroupResult { @@ -1602,31 +1617,31 @@ enum JoinGroupResult { } func apiJoinGroup(_ groupId: Int64) async throws -> JoinGroupResult { - let r = await chatSendCmd(.apiJoinGroup(groupId: groupId)) + let r: APIResult = await chatApiSendCmd(.apiJoinGroup(groupId: groupId)) switch r { - case let .userAcceptedGroupSent(_, groupInfo, _): return .joined(groupInfo: groupInfo) - case .chatCmdError(_, .errorAgent(.SMP(_, .AUTH))): return .invitationRemoved - case .chatCmdError(_, .errorStore(.groupNotFound)): return .groupNotFound - default: throw r + case let .result(.userAcceptedGroupSent(_, groupInfo, _)): return .joined(groupInfo: groupInfo) + case .error(.errorAgent(.SMP(_, .AUTH))): return .invitationRemoved + case .error(.errorStore(.groupNotFound)): return .groupNotFound + default: throw r.unexpected } } func apiRemoveMembers(_ groupId: Int64, _ memberIds: [Int64], _ withMessages: Bool = false) async throws -> [GroupMember] { - let r = await chatSendCmd(.apiRemoveMembers(groupId: groupId, memberIds: memberIds, withMessages: withMessages), bgTask: false) + let r: ChatResponse2 = try await chatSendCmd(.apiRemoveMembers(groupId: groupId, memberIds: memberIds, withMessages: withMessages), bgTask: false) if case let .userDeletedMembers(_, _, members, withMessages) = r { return members } - throw r + throw r.unexpected } func apiMembersRole(_ groupId: Int64, _ memberIds: [Int64], _ memberRole: GroupMemberRole) async throws -> [GroupMember] { - let r = await chatSendCmd(.apiMembersRole(groupId: groupId, memberIds: memberIds, memberRole: memberRole), bgTask: false) + let r: ChatResponse2 = try await chatSendCmd(.apiMembersRole(groupId: groupId, memberIds: memberIds, memberRole: memberRole), bgTask: false) if case let .membersRoleUser(_, _, members, _) = r { return members } - throw r + throw r.unexpected } func apiBlockMembersForAll(_ groupId: Int64, _ memberIds: [Int64], _ blocked: Bool) async throws -> [GroupMember] { - let r = await chatSendCmd(.apiBlockMembersForAll(groupId: groupId, memberIds: memberIds, blocked: blocked), bgTask: false) + let r: ChatResponse2 = try await chatSendCmd(.apiBlockMembersForAll(groupId: groupId, memberIds: memberIds, blocked: blocked), bgTask: false) if case let .membersBlockedForAllUser(_, _, members, _) = r { return members } - throw r + throw r.unexpected } func leaveGroup(_ groupId: Int64) async { @@ -1639,15 +1654,15 @@ func leaveGroup(_ groupId: Int64) async { } func apiLeaveGroup(_ groupId: Int64) async throws -> GroupInfo { - let r = await chatSendCmd(.apiLeaveGroup(groupId: groupId), bgTask: false) + let r: ChatResponse2 = try await chatSendCmd(.apiLeaveGroup(groupId: groupId), bgTask: false) if case let .leftMemberUser(_, groupInfo) = r { return groupInfo } - throw r + throw r.unexpected } // use ChatModel's loadGroupMembers from views func apiListMembers(_ groupId: Int64) async -> [GroupMember] { - let r: ChatResponse = await chatSendCmd(.apiListMembers(groupId: groupId)) - if case let .groupMembers(_, group) = r { return group.members } + let r: APIResult = await chatApiSendCmd(.apiListMembers(groupId: groupId)) + if case let .result(.groupMembers(_, group)) = r { return group.members } return [] } @@ -1660,73 +1675,73 @@ func filterMembersToAdd(_ ms: [GMember]) -> [Contact] { } func apiUpdateGroup(_ groupId: Int64, _ groupProfile: GroupProfile) async throws -> GroupInfo { - let r = await chatSendCmd(.apiUpdateGroupProfile(groupId: groupId, groupProfile: groupProfile)) + let r: ChatResponse2 = try await chatSendCmd(.apiUpdateGroupProfile(groupId: groupId, groupProfile: groupProfile)) if case let .groupUpdated(_, toGroup) = r { return toGroup } - throw r + throw r.unexpected } func apiCreateGroupLink(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> (CreatedConnLink, GroupMemberRole) { let short = UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_SHORT_LINKS) - let r = await chatSendCmd(.apiCreateGroupLink(groupId: groupId, memberRole: memberRole, short: short)) + let r: ChatResponse2 = try await chatSendCmd(.apiCreateGroupLink(groupId: groupId, memberRole: memberRole, short: short)) if case let .groupLinkCreated(_, _, connLink, memberRole) = r { return (connLink, memberRole) } - throw r + throw r.unexpected } func apiGroupLinkMemberRole(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> (CreatedConnLink, GroupMemberRole) { - let r = await chatSendCmd(.apiGroupLinkMemberRole(groupId: groupId, memberRole: memberRole)) + let r: ChatResponse2 = try await chatSendCmd(.apiGroupLinkMemberRole(groupId: groupId, memberRole: memberRole)) if case let .groupLink(_, _, connLink, memberRole) = r { return (connLink, memberRole) } - throw r + throw r.unexpected } func apiDeleteGroupLink(_ groupId: Int64) async throws { - let r = await chatSendCmd(.apiDeleteGroupLink(groupId: groupId)) + let r: ChatResponse2 = try await chatSendCmd(.apiDeleteGroupLink(groupId: groupId)) if case .groupLinkDeleted = r { return } - throw r + throw r.unexpected } func apiGetGroupLink(_ groupId: Int64) throws -> (CreatedConnLink, GroupMemberRole)? { - let r = chatSendCmdSync(.apiGetGroupLink(groupId: groupId)) + let r: APIResult = chatApiSendCmdSync(.apiGetGroupLink(groupId: groupId)) switch r { - case let .groupLink(_, _, connLink, memberRole): + case let .result(.groupLink(_, _, connLink, memberRole)): return (connLink, memberRole) - case .chatCmdError(_, chatError: .errorStore(storeError: .groupLinkNotFound)): + case .error(.errorStore(storeError: .groupLinkNotFound)): return nil - default: throw r + default: throw r.unexpected } } func apiCreateMemberContact(_ groupId: Int64, _ groupMemberId: Int64) async throws -> Contact { - let r = await chatSendCmd(.apiCreateMemberContact(groupId: groupId, groupMemberId: groupMemberId)) + let r: ChatResponse2 = try await chatSendCmd(.apiCreateMemberContact(groupId: groupId, groupMemberId: groupMemberId)) if case let .newMemberContact(_, contact, _, _) = r { return contact } - throw r + throw r.unexpected } func apiSendMemberContactInvitation(_ contactId: Int64, _ msg: MsgContent) async throws -> Contact { - let r = await chatSendCmd(.apiSendMemberContactInvitation(contactId: contactId, msg: msg), bgDelay: msgDelay) + let r: ChatResponse2 = try await chatSendCmd(.apiSendMemberContactInvitation(contactId: contactId, msg: msg), bgDelay: msgDelay) if case let .newMemberContactSentInv(_, contact, _, _) = r { return contact } - throw r + throw r.unexpected } func apiGetVersion() throws -> CoreVersionInfo { - let r = chatSendCmdSync(.showVersion) + let r: ChatResponse2 = try chatSendCmdSync(.showVersion) if case let .versionInfo(info, _, _) = r { return info } - throw r + throw r.unexpected } func getAgentSubsTotal() async throws -> (SMPServerSubs, Bool) { let userId = try currentUserId("getAgentSubsTotal") - let r = await chatSendCmd(.getAgentSubsTotal(userId: userId), log: false) + let r: ChatResponse2 = try await chatSendCmd(.getAgentSubsTotal(userId: userId), log: false) if case let .agentSubsTotal(_, subsTotal, hasSession) = r { return (subsTotal, hasSession) } logger.error("getAgentSubsTotal error: \(String(describing: r))") - throw r + throw r.unexpected } func getAgentServersSummary() throws -> PresentedServersSummary { let userId = try currentUserId("getAgentServersSummary") - let r = chatSendCmdSync(.getAgentServersSummary(userId: userId), log: false) + let r: ChatResponse2 = try chatSendCmdSync(.getAgentServersSummary(userId: userId), log: false) if case let .agentServersSummary(_, serversSummary) = r { return serversSummary } logger.error("getAgentServersSummary error: \(String(describing: r))") - throw r + throw r.unexpected } func resetAgentServersStats() async throws { @@ -1936,7 +1951,7 @@ class ChatReceiver { private var receiveMessages = true private var _lastMsgTime = Date.now - var messagesChannel: ((ChatEvent) -> Void)? = nil + var messagesChannel: ((APIResult) -> Void)? = nil static let shared = ChatReceiver() @@ -1954,7 +1969,12 @@ class ChatReceiver { while self.receiveMessages { if let msg = await chatRecvMsg() { self._lastMsgTime = .now - await processReceivedMsg(msg) + Task { await TerminalItems.shared.addResult(msg) } + switch msg { + case let .result(evt): await processReceivedMsg(evt) + case let .error(err): logger.debug("chatRecvMsg error: \(responseError(err))") + case let .invalid(type, json): logger.debug("chatRecvMsg event: * \(type) \(dataToString(json))") + } if let messagesChannel { messagesChannel(msg) } @@ -1972,12 +1992,9 @@ class ChatReceiver { } func processReceivedMsg(_ res: ChatEvent) async { - Task { - await TerminalItems.shared.add(.event(.now, res)) - } let m = ChatModel.shared let n = NetworkModel.shared - logger.debug("processReceivedMsg: \(res.eventType)") + logger.debug("processReceivedMsg: \(res.responseType)") switch res { case let .contactDeletedByContact(user, contact): if active(user) && contact.directOrUsed { @@ -2475,14 +2492,14 @@ func processReceivedMsg(_ res: ChatEvent) async { } } default: - logger.debug("unsupported event: \(res.eventType)") + logger.debug("unsupported event: \(res.responseType)") } func withCall(_ contact: Contact, _ perform: (Call) async -> Void) async { if let call = m.activeCall, call.contact.apiId == contact.apiId { await perform(call) } else { - logger.debug("processReceivedMsg: ignoring \(res.eventType), not in call with the contact \(contact.id)") + logger.debug("processReceivedMsg: ignoring \(res.responseType), not in call with the contact \(contact.id)") } } } diff --git a/apps/ios/Shared/Views/Call/ActiveCallView.swift b/apps/ios/Shared/Views/Call/ActiveCallView.swift index 3e88019e95..7c8996a99b 100644 --- a/apps/ios/Shared/Views/Call/ActiveCallView.swift +++ b/apps/ios/Shared/Views/Call/ActiveCallView.swift @@ -243,7 +243,7 @@ struct ActiveCallView: View { ChatReceiver.shared.messagesChannel = nil return } - if case let .chatItemsStatusesUpdated(_, chatItems) = msg, + if case let .result(.chatItemsStatusesUpdated(_, chatItems)) = msg, chatItems.contains(where: { ci in ci.chatInfo.id == call.contact.id && ci.chatItem.content.isSndCall && diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift index 18fd682646..918d8f9449 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift @@ -7,10 +7,11 @@ // import SwiftUI +import SimpleXChat struct CIInvalidJSONView: View { @EnvironmentObject var theme: AppTheme - var json: String + var json: Data? @State private var showJSON = false var body: some View { @@ -25,7 +26,7 @@ struct CIInvalidJSONView: View { .textSelection(.disabled) .onTapGesture { showJSON = true } .appSheet(isPresented: $showJSON) { - invalidJSONView(json) + invalidJSONView(dataToString(json)) } } } @@ -49,6 +50,6 @@ func invalidJSONView(_ json: String) -> some View { struct CIInvalidJSONView_Previews: PreviewProvider { static var previews: some View { - CIInvalidJSONView(json: "{}") + CIInvalidJSONView(json: "{}".data(using: .utf8)!) } } diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index 790af64b3f..f5234ed331 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -579,14 +579,14 @@ struct ChatListNavLink: View { ) } - private func invalidJSONPreview(_ json: String) -> some View { + private func invalidJSONPreview(_ json: Data?) -> some View { Text("invalid chat data") .foregroundColor(.red) .padding(4) .frame(height: dynamicRowHeight) .onTapGesture { showInvalidJSON = true } .appSheet(isPresented: $showInvalidJSON) { - invalidJSONView(json) + invalidJSONView(dataToString(json)) .environment(\EnvironmentValues.refresh as! WritableKeyPath, nil) } } @@ -689,7 +689,7 @@ func joinGroup(_ groupId: Int64, _ onComplete: @escaping () async -> Void) { } func getErrorAlert(_ error: Error, _ title: LocalizedStringKey) -> ErrorAlert { - if let r = error as? ChatResponse, + if let r = error as? ChatError, let alert = getNetworkErrorAlert(r) { return alert } else { diff --git a/apps/ios/Shared/Views/Database/DatabaseEncryptionView.swift b/apps/ios/Shared/Views/Database/DatabaseEncryptionView.swift index 3cd37e4930..441a164f8a 100644 --- a/apps/ios/Shared/Views/Database/DatabaseEncryptionView.swift +++ b/apps/ios/Shared/Views/Database/DatabaseEncryptionView.swift @@ -173,7 +173,7 @@ struct DatabaseEncryptionView: View { } return true } catch let error { - if case .chatCmdError(_, .errorDatabase(.errorExport(.errorNotADatabase))) = error as? ChatResponse { + if case .errorDatabase(.errorExport(.errorNotADatabase)) = error as? ChatError { await operationEnded(.currentPassphraseError) } else { await operationEnded(.error(title: "Error encrypting database", error: "\(responseError(error))")) diff --git a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift index c684ad627a..0af8fa7ad8 100644 --- a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift +++ b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift @@ -520,15 +520,15 @@ struct MigrateFromDevice: View { chatReceiver = MigrationChatReceiver(ctrl: ctrl, databaseUrl: tempDatabaseUrl) { msg in await MainActor.run { switch msg { - case let .sndFileProgressXFTP(_, _, fileTransferMeta, sentSize, totalSize): + case let .result(.sndFileProgressXFTP(_, _, fileTransferMeta, sentSize, totalSize)): if case let .uploadProgress(uploaded, total, _, _, _) = migrationState, uploaded != total { migrationState = .uploadProgress(uploadedBytes: sentSize, totalBytes: totalSize, fileId: fileTransferMeta.fileId, archivePath: archivePath, ctrl: ctrl) } - case .sndFileRedirectStartXFTP: + case .result(.sndFileRedirectStartXFTP): DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { migrationState = .linkCreation } - case let .sndStandaloneFileComplete(_, fileTransferMeta, rcvURIs): + case let .result(.sndStandaloneFileComplete(_, fileTransferMeta, rcvURIs)): let cfg = getNetCfg() let proxy: NetworkProxy? = if cfg.socksProxy == nil { nil @@ -546,11 +546,11 @@ struct MigrateFromDevice: View { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { migrationState = .linkShown(fileId: fileTransferMeta.fileId, link: data.addToLink(link: rcvURIs[0]), archivePath: archivePath, ctrl: ctrl) } - case .sndFileError: + case .result(.sndFileError): alert = .error(title: "Upload failed", error: "Check your internet connection and try again") migrationState = .uploadFailed(totalBytes: totalBytes, archivePath: archivePath) default: - logger.debug("unsupported event: \(msg.eventType)") + logger.debug("unsupported event: \(msg.responseType)") } } } @@ -691,7 +691,7 @@ private struct PassphraseConfirmationView: View { migrationState = .uploadConfirmation } } catch let error { - if case .chatCmdError(_, .errorDatabase(.errorOpen(.errorNotADatabase))) = error as? ChatResponse { + if case .errorDatabase(.errorOpen(.errorNotADatabase)) = error as? ChatError { showErrorOnMigrationIfNeeded(.errorNotADatabase(dbFile: ""), $alert) } else { alert = .error(title: "Error", error: NSLocalizedString("Error verifying passphrase:", comment: "") + " " + String(responseError(error))) @@ -733,11 +733,11 @@ func chatStoppedView() -> some View { private class MigrationChatReceiver { let ctrl: chat_ctrl let databaseUrl: URL - let processReceivedMsg: (ChatEvent) async -> Void + let processReceivedMsg: (APIResult) async -> Void private var receiveLoop: Task? private var receiveMessages = true - init(ctrl: chat_ctrl, databaseUrl: URL, _ processReceivedMsg: @escaping (ChatEvent) async -> Void) { + init(ctrl: chat_ctrl, databaseUrl: URL, _ processReceivedMsg: @escaping (APIResult) async -> Void) { self.ctrl = ctrl self.databaseUrl = databaseUrl self.processReceivedMsg = processReceivedMsg @@ -752,11 +752,11 @@ private class MigrationChatReceiver { func receiveMsgLoop() async { // TODO use function that has timeout - if let msg: ChatEvent = await chatRecvMsg(ctrl) { + if let msg: APIResult = await chatRecvMsg(ctrl) { Task { - await TerminalItems.shared.add(.event(.now, msg)) + await TerminalItems.shared.addResult(msg) } - logger.debug("processReceivedMsg: \(msg.eventType)") + logger.debug("processReceivedMsg: \(msg.responseType)") await processReceivedMsg(msg) } if self.receiveMessages { diff --git a/apps/ios/Shared/Views/Migration/MigrateToDevice.swift b/apps/ios/Shared/Views/Migration/MigrateToDevice.swift index 19cefa7f4d..93fe19cf33 100644 --- a/apps/ios/Shared/Views/Migration/MigrateToDevice.swift +++ b/apps/ios/Shared/Views/Migration/MigrateToDevice.swift @@ -496,10 +496,10 @@ struct MigrateToDevice: View { chatReceiver = MigrationChatReceiver(ctrl: ctrl, databaseUrl: tempDatabaseUrl) { msg in await MainActor.run { switch msg { - case let .rcvFileProgressXFTP(_, _, receivedSize, totalSize, rcvFileTransfer): + case let .result(.rcvFileProgressXFTP(_, _, receivedSize, totalSize, rcvFileTransfer)): migrationState = .downloadProgress(downloadedBytes: receivedSize, totalBytes: totalSize, fileId: rcvFileTransfer.fileId, link: link, archivePath: archivePath, ctrl: ctrl) MigrationToDeviceState.save(.downloadProgress(link: link, archiveName: URL(fileURLWithPath: archivePath).lastPathComponent)) - case .rcvStandaloneFileComplete: + case .result(.rcvStandaloneFileComplete): DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { // User closed the whole screen before new state was saved if migrationState == nil { @@ -509,14 +509,14 @@ struct MigrateToDevice: View { MigrationToDeviceState.save(.archiveImport(archiveName: URL(fileURLWithPath: archivePath).lastPathComponent)) } } - case .rcvFileError: + case .result(.rcvFileError): alert = .error(title: "Download failed", error: "File was deleted or link is invalid") migrationState = .downloadFailed(totalBytes: totalBytes, link: link, archivePath: archivePath) - case .chatError(_, .error(.noRcvFileUser)): + case .error(.error(.noRcvFileUser)): alert = .error(title: "Download failed", error: "File was deleted or link is invalid") migrationState = .downloadFailed(totalBytes: totalBytes, link: link, archivePath: archivePath) default: - logger.debug("unsupported event: \(msg.eventType)") + logger.debug("unsupported event: \(msg.responseType)") } } } @@ -751,11 +751,11 @@ private func progressView() -> some View { private class MigrationChatReceiver { let ctrl: chat_ctrl let databaseUrl: URL - let processReceivedMsg: (ChatEvent) async -> Void + let processReceivedMsg: (APIResult) async -> Void private var receiveLoop: Task? private var receiveMessages = true - init(ctrl: chat_ctrl, databaseUrl: URL, _ processReceivedMsg: @escaping (ChatEvent) async -> Void) { + init(ctrl: chat_ctrl, databaseUrl: URL, _ processReceivedMsg: @escaping (APIResult) async -> Void) { self.ctrl = ctrl self.databaseUrl = databaseUrl self.processReceivedMsg = processReceivedMsg @@ -772,9 +772,9 @@ private class MigrationChatReceiver { // TODO use function that has timeout if let msg = await chatRecvMsg(ctrl) { Task { - await TerminalItems.shared.add(.event(.now, msg)) + await TerminalItems.shared.addResult(msg) } - logger.debug("processReceivedMsg: \(msg.eventType)") + logger.debug("processReceivedMsg: \(msg.responseType)") await processReceivedMsg(msg) } if self.receiveMessages { diff --git a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift index 3199be21fe..c022a2a012 100644 --- a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift +++ b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift @@ -236,15 +236,15 @@ private func showCreateProfileAlert( _ error: Error ) { let m = ChatModel.shared - switch error as? ChatResponse { - case .chatCmdError(_, .errorStore(.duplicateName)), - .chatCmdError(_, .error(.userExists)): + switch error as? ChatError { + case .errorStore(.duplicateName), + .error(.userExists): if m.currentUser == nil { AlertManager.shared.showAlert(duplicateUserAlert) } else { showAlert(.duplicateUserError) } - case .chatCmdError(_, .error(.invalidDisplayName)): + case .error(.invalidDisplayName): if m.currentUser == nil { AlertManager.shared.showAlert(invalidDisplayNameAlert) } else { diff --git a/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift b/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift index 67020e09e7..01b25baed8 100644 --- a/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift +++ b/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift @@ -456,12 +456,12 @@ struct ConnectDesktopView: View { } } 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 + switch e as? ChatError { + case .errorRemoteCtrl(.badInvitation): alert = .badInvitationError + case .error(.commandError): alert = .badInvitationError + case let .errorRemoteCtrl(.badVersion(v)): alert = .badVersionError(version: v) + case .errorAgent(.RCP(.version)): alert = .badVersionError(version: nil) + case .errorAgent(.RCP(.ctrlAuth)): alert = .desktopDisconnectedError default: errorAlert(e) } } diff --git a/apps/ios/Shared/Views/TerminalView.swift b/apps/ios/Shared/Views/TerminalView.swift index 2b58abef65..554219eb69 100644 --- a/apps/ios/Shared/Views/TerminalView.swift +++ b/apps/ios/Shared/Views/TerminalView.swift @@ -145,18 +145,18 @@ struct TerminalView: View { } func consoleSendMessage() { - let cmd = ChatCommand.string(composeState.message) if composeState.message.starts(with: "/sql") && (!prefPerformLA || !developerTools) { - let resp = ChatResponse.chatCmdError(user_: nil, chatError: ChatError.error(errorType: ChatErrorType.commandError(message: "Failed reading: empty"))) + let resp: APIResult = APIResult.error(ChatError.error(errorType: ChatErrorType.commandError(message: "Failed reading: empty"))) Task { - await TerminalItems.shared.addCommand(.now, cmd, resp) + await TerminalItems.shared.addCommand(.now, .string(composeState.message), resp) } } else { + let cmd = composeState.message DispatchQueue.global().async { Task { - composeState.inProgress = true - _ = await chatSendCmd(cmd) - composeState.inProgress = false + await MainActor.run { composeState.inProgress = true } + await sendTerminalCmd(cmd) + await MainActor.run { composeState.inProgress = false } } } } @@ -164,12 +164,38 @@ struct TerminalView: View { } } +func sendTerminalCmd(_ cmd: String) async { + let start: Date = .now + await withCheckedContinuation { (cont: CheckedContinuation) in + let d = sendSimpleXCmdStr(cmd) + Task { + guard let d else { + await TerminalItems.shared.addCommand(start, ChatCommand.string(cmd), APIResult.error(.invalidJSON(json: nil))) + return + } + let r0: APIResult = decodeAPIResult(d) + guard case .invalid = r0 else { + await TerminalItems.shared.addCommand(start, .string(cmd), r0) + return + } + let r1: APIResult = decodeAPIResult(d) + guard case .invalid = r1 else { + await TerminalItems.shared.addCommand(start, .string(cmd), r1) + return + } + let r2: APIResult = decodeAPIResult(d) + await TerminalItems.shared.addCommand(start, .string(cmd), r2) + } + cont.resume(returning: ()) + } +} + struct TerminalView_Previews: PreviewProvider { static var previews: some View { let chatModel = ChatModel() chatModel.terminalItems = [ - .resp(.now, ChatResponse.response(type: "contactSubscribed", json: "{}")), - .resp(.now, ChatResponse.response(type: "newChatItems", json: "{}")) + .err(.now, APIResult.invalid(type: "contactSubscribed", json: "{}".data(using: .utf8)!).unexpected), + .err(.now, APIResult.invalid(type: "newChatItems", json: "{}".data(using: .utf8)!).unexpected) ] return NavigationView { TerminalView() diff --git a/apps/ios/SimpleX NSE/NSEAPITypes.swift b/apps/ios/SimpleX NSE/NSEAPITypes.swift index 7569547e6a..6ab4a779d1 100644 --- a/apps/ios/SimpleX NSE/NSEAPITypes.swift +++ b/apps/ios/SimpleX NSE/NSEAPITypes.swift @@ -47,8 +47,7 @@ enum NSEChatCommand: ChatCmdProtocol { } } -enum NSEChatResponse: Decodable, Error, ChatRespProtocol { - case response(type: String, json: String) +enum NSEChatResponse: Decodable, ChatAPIResult { case activeUser(user: User) case chatStarted case chatRunning @@ -57,11 +56,9 @@ enum NSEChatResponse: Decodable, Error, ChatRespProtocol { case connNtfMessages(receivedMsgs: [NtfMsgInfo?]) case ntfMessage(user: UserRef, connEntity: ConnectionEntity, ntfMessage: NtfMsgAckInfo) case cmdOk(user_: UserRef?) - case chatCmdError(user_: UserRef?, chatError: ChatError) var responseType: String { switch self { - case let .response(type, _): "* \(type)" case .activeUser: "activeUser" case .chatStarted: "chatStarted" case .chatRunning: "chatRunning" @@ -70,13 +67,11 @@ enum NSEChatResponse: Decodable, Error, ChatRespProtocol { case .connNtfMessages: "connNtfMessages" case .ntfMessage: "ntfMessage" case .cmdOk: "cmdOk" - case .chatCmdError: "chatCmdError" } } var details: String { switch self { - case let .response(_, json): return json case let .activeUser(user): return String(describing: user) case .chatStarted: return noDetails case .chatRunning: return noDetails @@ -85,60 +80,11 @@ enum NSEChatResponse: Decodable, Error, ChatRespProtocol { case let .connNtfMessages(receivedMsgs): return "receivedMsgs: \(String(describing: receivedMsgs))" case let .ntfMessage(u, connEntity, ntfMessage): return withUser(u, "connEntity: \(String(describing: connEntity))\nntfMessage: \(String(describing: ntfMessage))") case .cmdOk: return noDetails - case let .chatCmdError(u, chatError): return withUser(u, String(describing: chatError)) - } - } - - var noDetails: String { "\(responseType): no details" } - - static func chatResponse(_ s: String) -> NSEChatResponse { - let d = s.data(using: .utf8)! - // TODO is there a way to do it without copying the data? e.g: - // let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson)) - // let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free) - do { - let r = try jsonDecoder.decode(APIResponse.self, from: d) - return r.resp - } catch { - logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)") - } - - var type: String? - var json: String? - if let j = try? JSONSerialization.jsonObject(with: d) as? NSDictionary { - if let jResp = j["resp"] as? NSDictionary, jResp.count == 1 || jResp.count == 2 { - type = jResp.allKeys[0] as? String - if jResp.count == 2 && type == "_owsf" { - type = jResp.allKeys[1] as? String - } - if type == "chatCmdError" { - if let jError = jResp["chatCmdError"] as? NSDictionary { - return .chatCmdError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) - } - } - } - json = serializeJSON(j, options: .prettyPrinted) - } - return NSEChatResponse.response(type: type ?? "invalid", json: json ?? s) - } - - var chatError: ChatError? { - switch self { - case let .chatCmdError(_, error): error - default: nil - } - } - - var chatErrorType: ChatErrorType? { - switch self { - case let .chatCmdError(_, .error(error)): error - default: nil } } } -enum NSEChatEvent: Decodable, Error, ChatEventProtocol { - case event(type: String, json: String) +enum NSEChatEvent: Decodable, ChatAPIResult { case chatSuspended case contactConnected(user: UserRef, contact: Contact, userCustomProfile: Profile?) case receivedContactRequest(user: UserRef, contactRequest: UserContactRequest) @@ -148,11 +94,9 @@ enum NSEChatEvent: Decodable, Error, ChatEventProtocol { case sndFileRcvCancelled(user: UserRef, chatItem_: AChatItem?, sndFileTransfer: SndFileTransfer) case callInvitation(callInvitation: RcvCallInvitation) case ntfMessage(user: UserRef, connEntity: ConnectionEntity, ntfMessage: NtfMsgAckInfo) - case chatError(user_: UserRef?, chatError: ChatError) - var eventType: String { + var responseType: String { switch self { - case let .event(type, _): "* \(type)" case .chatSuspended: "chatSuspended" case .contactConnected: "contactConnected" case .receivedContactRequest: "receivedContactRequest" @@ -162,13 +106,11 @@ enum NSEChatEvent: Decodable, Error, ChatEventProtocol { case .sndFileRcvCancelled: "sndFileRcvCancelled" case .callInvitation: "callInvitation" case .ntfMessage: "ntfMessage" - case .chatError: "chatError" } } var details: String { switch self { - case let .event(_, json): return json case .chatSuspended: return noDetails case let .contactConnected(u, contact, _): return withUser(u, String(describing: contact)) case let .receivedContactRequest(u, contactRequest): return withUser(u, String(describing: contactRequest)) @@ -180,54 +122,6 @@ enum NSEChatEvent: Decodable, Error, ChatEventProtocol { case let .sndFileRcvCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) case let .callInvitation(inv): return String(describing: inv) case let .ntfMessage(u, connEntity, ntfMessage): return withUser(u, "connEntity: \(String(describing: connEntity))\nntfMessage: \(String(describing: ntfMessage))") - case let .chatError(u, chatError): return withUser(u, String(describing: chatError)) } - } - - var noDetails: String { "\(eventType): no details" } - - static func chatEvent(_ s: String) -> NSEChatEvent { - let d = s.data(using: .utf8)! - // TODO is there a way to do it without copying the data? e.g: - // let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson)) - // let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free) - do { - let r = try jsonDecoder.decode(APIResponse.self, from: d) - return r.resp - } catch { - logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)") - } - - var type: String? - var json: String? - if let j = try? JSONSerialization.jsonObject(with: d) as? NSDictionary { - if let jResp = j["resp"] as? NSDictionary, jResp.count == 1 || jResp.count == 2 { - type = jResp.allKeys[0] as? String - if jResp.count == 2 && type == "_owsf" { - type = jResp.allKeys[1] as? String - } - if type == "chatError" { - if let jError = jResp["chatError"] as? NSDictionary { - return .chatError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) - } - } - } - json = serializeJSON(j, options: .prettyPrinted) - } - return NSEChatEvent.event(type: type ?? "invalid", json: json ?? s) - } - - var chatError: ChatError? { - switch self { - case let .chatError(_, error): error - default: nil - } - } - - var chatErrorType: ChatErrorType? { - switch self { - case let .chatError(_, .error(error)): error - default: nil - } - } + } } diff --git a/apps/ios/SimpleX NSE/NotificationService.swift b/apps/ios/SimpleX NSE/NotificationService.swift index e8dd21f23c..bc783784cb 100644 --- a/apps/ios/SimpleX NSE/NotificationService.swift +++ b/apps/ios/SimpleX NSE/NotificationService.swift @@ -774,12 +774,18 @@ func receiveMessages() async { } func receiveMsg() async { - if let msg = await chatRecvMsg() { + switch await chatRecvMsg() { + case let .result(msg): logger.debug("NotificationService receiveMsg: message") if let (id, ntf) = await receivedMsgNtf(msg) { logger.debug("NotificationService receiveMsg: notification") await NSEThreads.shared.processNotification(id, ntf) } + case let .error(err): + logger.error("NotificationService receivedMsgNtf error: \(String(describing: err))") + case let .invalid(type, _): + logger.error("NotificationService receivedMsgNtf invalid: \(type)") + case .none: () } } @@ -789,9 +795,9 @@ func receiveMessages() async { } } -func chatRecvMsg() async -> NSEChatEvent? { +func chatRecvMsg() async -> APIResult? { await withCheckedContinuation { cont in - let resp: NSEChatEvent? = recvSimpleXMsg() + let resp: APIResult? = recvSimpleXMsg() cont.resume(returning: resp) } } @@ -800,7 +806,7 @@ private let isInChina = SKStorefront().countryCode == "CHN" private func useCallKit() -> Bool { !isInChina && callKitEnabledGroupDefault.get() } func receivedMsgNtf(_ res: NSEChatEvent) async -> (String, NSENotificationData)? { - logger.debug("NotificationService receivedMsgNtf: \(res.eventType)") + logger.debug("NotificationService receivedMsgNtf: \(res.responseType)") switch res { case let .contactConnected(user, contact, _): return (contact.id, .contactConnected(user, contact)) @@ -845,11 +851,8 @@ func receivedMsgNtf(_ res: NSEChatEvent) async -> (String, NSENotificationData)? case .chatSuspended: chatSuspended() return nil - case let .chatError(_, err): - logger.error("NotificationService receivedMsgNtf error: \(String(describing: err))") - return nil default: - logger.debug("NotificationService receivedMsgNtf ignored event: \(res.eventType)") + logger.debug("NotificationService receivedMsgNtf ignored event: \(res.responseType)") return nil } } @@ -868,14 +871,14 @@ func updateNetCfg() { } func apiGetActiveUser() -> User? { - let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.showActiveUser) + let r: APIResult = sendSimpleXCmd(NSEChatCommand.showActiveUser) logger.debug("apiGetActiveUser sendSimpleXCmd response: \(r.responseType)") switch r { - case let .activeUser(user): return user - case .chatCmdError(_, .error(.noActiveUser)): + case let .result(.activeUser(user)): return user + case .error(.error(.noActiveUser)): logger.debug("apiGetActiveUser sendSimpleXCmd no active user") return nil - case let .chatCmdError(_, err): + case let .error(err): logger.debug("apiGetActiveUser sendSimpleXCmd error: \(String(describing: err))") return nil default: @@ -885,39 +888,39 @@ func apiGetActiveUser() -> User? { } func apiStartChat() throws -> Bool { - let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.startChat(mainApp: false, enableSndFiles: false)) + let r: APIResult = sendSimpleXCmd(NSEChatCommand.startChat(mainApp: false, enableSndFiles: false)) switch r { - case .chatStarted: return true - case .chatRunning: return false - default: throw r + case .result(.chatStarted): return true + case .result(.chatRunning): return false + default: throw r.unexpected } } func apiActivateChat() -> Bool { chatReopenStore() - let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.apiActivateChat(restoreChat: false)) - if case .cmdOk = r { return true } + let r: APIResult = sendSimpleXCmd(NSEChatCommand.apiActivateChat(restoreChat: false)) + if case .result(.cmdOk) = r { return true } logger.error("NotificationService apiActivateChat error: \(String(describing: r))") return false } func apiSuspendChat(timeoutMicroseconds: Int) -> Bool { - let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.apiSuspendChat(timeoutMicroseconds: timeoutMicroseconds)) - if case .cmdOk = r { return true } + let r: APIResult = sendSimpleXCmd(NSEChatCommand.apiSuspendChat(timeoutMicroseconds: timeoutMicroseconds)) + if case .result(.cmdOk) = r { return true } logger.error("NotificationService apiSuspendChat error: \(String(describing: r))") return false } func apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String) throws { - let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.apiSetAppFilePaths(filesFolder: filesFolder, tempFolder: tempFolder, assetsFolder: assetsFolder)) - if case .cmdOk = r { return } - throw r + let r: APIResult = sendSimpleXCmd(NSEChatCommand.apiSetAppFilePaths(filesFolder: filesFolder, tempFolder: tempFolder, assetsFolder: assetsFolder)) + if case .result(.cmdOk) = r { return } + throw r.unexpected } func apiSetEncryptLocalFiles(_ enable: Bool) throws { - let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.apiSetEncryptLocalFiles(enable: enable)) - if case .cmdOk = r { return } - throw r + let r: APIResult = sendSimpleXCmd(NSEChatCommand.apiSetEncryptLocalFiles(enable: enable)) + if case .result(.cmdOk) = r { return } + throw r.unexpected } func apiGetNtfConns(nonce: String, encNtfInfo: String) -> [NtfConn]? { @@ -925,11 +928,11 @@ func apiGetNtfConns(nonce: String, encNtfInfo: String) -> [NtfConn]? { logger.debug("no active user") return nil } - let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.apiGetNtfConns(nonce: nonce, encNtfInfo: encNtfInfo)) - if case let .ntfConns(ntfConns) = r { + let r: APIResult = sendSimpleXCmd(NSEChatCommand.apiGetNtfConns(nonce: nonce, encNtfInfo: encNtfInfo)) + if case let .result(.ntfConns(ntfConns)) = r { logger.debug("apiGetNtfConns response ntfConns: \(ntfConns.count)") return ntfConns - } else if case let .chatCmdError(_, error) = r { + } else if case let .error(error) = r { logger.debug("apiGetNtfMessage error response: \(String.init(describing: error))") } else { logger.debug("apiGetNtfMessage ignored response: \(r.responseType) \(String.init(describing: r))") @@ -943,12 +946,12 @@ func apiGetConnNtfMessages(connMsgReqs: [ConnMsgReq]) -> [NtfMsgInfo?]? { return nil } logger.debug("apiGetConnNtfMessages command: \(NSEChatCommand.apiGetConnNtfMessages(connMsgReqs: connMsgReqs).cmdString)") - let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.apiGetConnNtfMessages(connMsgReqs: connMsgReqs)) - if case let .connNtfMessages(receivedMsgs) = r { + let r: APIResult = sendSimpleXCmd(NSEChatCommand.apiGetConnNtfMessages(connMsgReqs: connMsgReqs)) + if case let .result(.connNtfMessages(receivedMsgs)) = r { logger.debug("apiGetConnNtfMessages response receivedMsgs: total \(receivedMsgs.count), expecting messages \(receivedMsgs.count { $0 != nil })") return receivedMsgs } - logger.debug("apiGetConnNtfMessages error: \(responseError(r))") + logger.debug("apiGetConnNtfMessages error: \(responseError(r.unexpected))") return nil } @@ -962,17 +965,17 @@ func getConnNtfMessage(connMsgReq: ConnMsgReq) -> NtfMsgInfo? { func apiReceiveFile(fileId: Int64, encrypted: Bool, inline: Bool? = nil) -> AChatItem? { let userApprovedRelays = !privacyAskToApproveRelaysGroupDefault.get() - let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.receiveFile(fileId: fileId, userApprovedRelays: userApprovedRelays, encrypted: encrypted, inline: inline)) - if case let .rcvFileAccepted(_, chatItem) = r { return chatItem } - logger.error("receiveFile error: \(responseError(r))") + let r: APIResult = sendSimpleXCmd(NSEChatCommand.receiveFile(fileId: fileId, userApprovedRelays: userApprovedRelays, encrypted: encrypted, inline: inline)) + if case let .result(.rcvFileAccepted(_, chatItem)) = r { return chatItem } + logger.error("receiveFile error: \(responseError(r.unexpected))") return nil } func apiSetFileToReceive(fileId: Int64, encrypted: Bool) { let userApprovedRelays = !privacyAskToApproveRelaysGroupDefault.get() - let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.setFileToReceive(fileId: fileId, userApprovedRelays: userApprovedRelays, encrypted: encrypted)) - if case .cmdOk = r { return } - logger.error("setFileToReceive error: \(responseError(r))") + let r: APIResult = sendSimpleXCmd(NSEChatCommand.setFileToReceive(fileId: fileId, userApprovedRelays: userApprovedRelays, encrypted: encrypted)) + if case .result(.cmdOk) = r { return } + logger.error("setFileToReceive error: \(responseError(r.unexpected))") } func autoReceiveFile(_ file: CIFile) -> ChatItem? { @@ -989,9 +992,9 @@ func autoReceiveFile(_ file: CIFile) -> ChatItem? { } func setNetworkConfig(_ cfg: NetCfg) throws { - let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.apiSetNetworkConfig(networkConfig: cfg)) - if case .cmdOk = r { return } - throw r + let r: APIResult = sendSimpleXCmd(NSEChatCommand.apiSetNetworkConfig(networkConfig: cfg)) + if case .result(.cmdOk) = r { return } + throw r.unexpected } func defaultBestAttemptNtf(_ ntfConn: NtfConn) -> NSENotificationData { diff --git a/apps/ios/SimpleX SE/ShareAPI.swift b/apps/ios/SimpleX SE/ShareAPI.swift index 0f12b002f7..3e901c73eb 100644 --- a/apps/ios/SimpleX SE/ShareAPI.swift +++ b/apps/ios/SimpleX SE/ShareAPI.swift @@ -13,52 +13,52 @@ import SimpleXChat let logger = Logger() func apiGetActiveUser() throws -> User? { - let r: SEChatResponse = sendSimpleXCmd(SEChatCommand.showActiveUser) + let r: APIResult = sendSimpleXCmd(SEChatCommand.showActiveUser) switch r { - case let .activeUser(user): return user - case .chatCmdError(_, .error(.noActiveUser)): return nil - default: throw r + case let .result(.activeUser(user)): return user + case .error(.error(.noActiveUser)): return nil + default: throw r.unexpected } } func apiStartChat() throws -> Bool { - let r: SEChatResponse = sendSimpleXCmd(SEChatCommand.startChat(mainApp: false, enableSndFiles: true)) + let r: APIResult = sendSimpleXCmd(SEChatCommand.startChat(mainApp: false, enableSndFiles: true)) switch r { - case .chatStarted: return true - case .chatRunning: return false - default: throw r + case .result(.chatStarted): return true + case .result(.chatRunning): return false + default: throw r.unexpected } } func apiSetNetworkConfig(_ cfg: NetCfg) throws { - let r: SEChatResponse = sendSimpleXCmd(SEChatCommand.apiSetNetworkConfig(networkConfig: cfg)) - if case .cmdOk = r { return } - throw r + let r: APIResult = sendSimpleXCmd(SEChatCommand.apiSetNetworkConfig(networkConfig: cfg)) + if case .result(.cmdOk) = r { return } + throw r.unexpected } func apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String) throws { - let r: SEChatResponse = sendSimpleXCmd(SEChatCommand.apiSetAppFilePaths(filesFolder: filesFolder, tempFolder: tempFolder, assetsFolder: assetsFolder)) - if case .cmdOk = r { return } - throw r + let r: APIResult = sendSimpleXCmd(SEChatCommand.apiSetAppFilePaths(filesFolder: filesFolder, tempFolder: tempFolder, assetsFolder: assetsFolder)) + if case .result(.cmdOk) = r { return } + throw r.unexpected } func apiSetEncryptLocalFiles(_ enable: Bool) throws { - let r: SEChatResponse = sendSimpleXCmd(SEChatCommand.apiSetEncryptLocalFiles(enable: enable)) - if case .cmdOk = r { return } - throw r + let r: APIResult = sendSimpleXCmd(SEChatCommand.apiSetEncryptLocalFiles(enable: enable)) + if case .result(.cmdOk) = r { return } + throw r.unexpected } func apiGetChats(userId: User.ID) throws -> Array { - let r: SEChatResponse = sendSimpleXCmd(SEChatCommand.apiGetChats(userId: userId)) - if case let .apiChats(user: _, chats: chats) = r { return chats } - throw r + let r: APIResult = sendSimpleXCmd(SEChatCommand.apiGetChats(userId: userId)) + if case let .result(.apiChats(user: _, chats: chats)) = r { return chats } + throw r.unexpected } func apiSendMessages( chatInfo: ChatInfo, composedMessages: [ComposedMessage] ) throws -> [AChatItem] { - let r: SEChatResponse = sendSimpleXCmd( + let r: APIResult = sendSimpleXCmd( chatInfo.chatType == .local ? SEChatCommand.apiCreateChatItems( noteFolderId: chatInfo.apiId, @@ -72,33 +72,33 @@ func apiSendMessages( composedMessages: composedMessages ) ) - if case let .newChatItems(_, chatItems) = r { + if case let .result(.newChatItems(_, chatItems)) = r { return chatItems } else { for composedMessage in composedMessages { if let filePath = composedMessage.fileSource?.filePath { removeFile(filePath) } } - throw r + throw r.unexpected } } func apiActivateChat() throws { chatReopenStore() - let r: SEChatResponse = sendSimpleXCmd(SEChatCommand.apiActivateChat(restoreChat: false)) - if case .cmdOk = r { return } - throw r + let r: APIResult = sendSimpleXCmd(SEChatCommand.apiActivateChat(restoreChat: false)) + if case .result(.cmdOk) = r { return } + throw r.unexpected } func apiSuspendChat(expired: Bool) { - let r: SEChatResponse = sendSimpleXCmd(SEChatCommand.apiSuspendChat(timeoutMicroseconds: expired ? 0 : 3_000000)) + let r: APIResult = sendSimpleXCmd(SEChatCommand.apiSuspendChat(timeoutMicroseconds: expired ? 0 : 3_000000)) // Block until `chatSuspended` received or 3 seconds has passed var suspended = false - if case .cmdOk = r, !expired { + if case .result(.cmdOk) = r, !expired { let startTime = CFAbsoluteTimeGetCurrent() while CFAbsoluteTimeGetCurrent() - startTime < 3 { - let msg: SEChatEvent? = recvSimpleXMsg(messageTimeout: 3_500000) + let msg: APIResult? = recvSimpleXMsg(messageTimeout: 3_500000) switch msg { - case .chatSuspended: + case .result(.chatSuspended): suspended = false break default: continue @@ -106,7 +106,7 @@ func apiSuspendChat(expired: Bool) { } } if !suspended { - let _r1: SEChatResponse = sendSimpleXCmd(SEChatCommand.apiSuspendChat(timeoutMicroseconds: 0)) + let _r1: APIResult = sendSimpleXCmd(SEChatCommand.apiSuspendChat(timeoutMicroseconds: 0)) } logger.debug("close store") chatCloseStore() @@ -151,32 +151,27 @@ enum SEChatCommand: ChatCmdProtocol { } } -enum SEChatResponse: Decodable, Error, ChatRespProtocol { - case response(type: String, json: String) +enum SEChatResponse: Decodable, ChatAPIResult { case activeUser(user: User) case chatStarted case chatRunning case apiChats(user: UserRef, chats: [ChatData]) case newChatItems(user: UserRef, chatItems: [AChatItem]) case cmdOk(user_: UserRef?) - case chatCmdError(user_: UserRef?, chatError: ChatError) var responseType: String { switch self { - case let .response(type, _): "* \(type)" case .activeUser: "activeUser" case .chatStarted: "chatStarted" case .chatRunning: "chatRunning" case .apiChats: "apiChats" case .newChatItems: "newChatItems" case .cmdOk: "cmdOk" - case .chatCmdError: "chatCmdError" } } var details: String { switch self { - case let .response(_, json): return json case let .activeUser(user): return String(describing: user) case .chatStarted: return noDetails case .chatRunning: return noDetails @@ -185,88 +180,39 @@ enum SEChatResponse: Decodable, Error, ChatRespProtocol { let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") return withUser(u, itemsString) case .cmdOk: return noDetails - case let .chatCmdError(u, chatError): return withUser(u, String(describing: chatError)) } } - var noDetails: String { "\(responseType): no details" } - - static func chatResponse(_ s: String) -> SEChatResponse { - let d = s.data(using: .utf8)! - // TODO is there a way to do it without copying the data? e.g: - // let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson)) - // let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free) - do { - let r = try jsonDecoder.decode(APIResponse.self, from: d) - return r.resp - } catch { - logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)") - } - - var type: String? - var json: String? - if let j = try? JSONSerialization.jsonObject(with: d) as? NSDictionary { - if let jResp = j["resp"] as? NSDictionary, jResp.count == 1 || jResp.count == 2 { - type = jResp.allKeys[0] as? String - if jResp.count == 2 && type == "_owsf" { - type = jResp.allKeys[1] as? String - } - if type == "apiChats" { - if let r = parseApiChats(jResp) { - return .apiChats(user: r.user, chats: r.chats) - } - } else if type == "chatCmdError" { - if let jError = jResp["chatCmdError"] as? NSDictionary { - return .chatCmdError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) - } - } - } - json = serializeJSON(j, options: .prettyPrinted) - } - return SEChatResponse.response(type: type ?? "invalid", json: json ?? s) - } - - var chatError: ChatError? { - switch self { - case let .chatCmdError(_, error): error - default: nil - } - } - - var chatErrorType: ChatErrorType? { - switch self { - case let .chatCmdError(_, .error(error)): error - default: nil + static func fallbackResult(_ type: String, _ json: NSDictionary) -> SEChatResponse? { + if type == "apiChats", let r = parseApiChats(json) { + .apiChats(user: r.user, chats: r.chats) + } else { + nil } } } -enum SEChatEvent: Decodable, Error, ChatEventProtocol { - case event(type: String, json: String) +enum SEChatEvent: Decodable, ChatAPIResult { case chatSuspended case sndFileProgressXFTP(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, sentSize: Int64, totalSize: Int64) case sndFileCompleteXFTP(user: UserRef, chatItem: AChatItem, fileTransferMeta: FileTransferMeta) case chatItemsStatusesUpdated(user: UserRef, chatItems: [AChatItem]) case sndFileError(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, errorMessage: String) case sndFileWarning(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, errorMessage: String) - case chatError(user_: UserRef?, chatError: ChatError) - var eventType: String { + var responseType: String { switch self { - case let .event(type, _): "* \(type)" case .chatSuspended: "chatSuspended" case .sndFileProgressXFTP: "sndFileProgressXFTP" case .sndFileCompleteXFTP: "sndFileCompleteXFTP" case .chatItemsStatusesUpdated: "chatItemsStatusesUpdated" case .sndFileError: "sndFileError" case .sndFileWarning: "sndFileWarning" - case .chatError: "chatError" } } var details: String { switch self { - case let .event(_, json): return json case .chatSuspended: return noDetails case let .sndFileProgressXFTP(u, chatItem, _, sentSize, totalSize): return withUser(u, "chatItem: \(String(describing: chatItem))\nsentSize: \(sentSize)\ntotalSize: \(totalSize)") case let .sndFileCompleteXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) @@ -275,53 +221,6 @@ enum SEChatEvent: Decodable, Error, ChatEventProtocol { return withUser(u, itemsString) case let .sndFileError(u, chatItem, _, err): return withUser(u, "error: \(String(describing: err))\nchatItem: \(String(describing: chatItem))") case let .sndFileWarning(u, chatItem, _, err): return withUser(u, "error: \(String(describing: err))\nchatItem: \(String(describing: chatItem))") - case let .chatError(u, chatError): return withUser(u, String(describing: chatError)) } - } - - var noDetails: String { "\(eventType): no details" } - - static func chatEvent(_ s: String) -> SEChatEvent { - let d = s.data(using: .utf8)! - // TODO is there a way to do it without copying the data? e.g: - // let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson)) - // let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free) - do { - let r = try jsonDecoder.decode(APIResponse.self, from: d) - return r.resp - } catch { - logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)") - } - - var type: String? - var json: String? - if let j = try? JSONSerialization.jsonObject(with: d) as? NSDictionary { - if let jResp = j["resp"] as? NSDictionary, jResp.count == 1 || jResp.count == 2 { - type = jResp.allKeys[0] as? String - if jResp.count == 2 && type == "_owsf" { - type = jResp.allKeys[1] as? String - } - if type == "chatError" { - if let jError = jResp["chatError"] as? NSDictionary { - return .chatError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) - } - } - } - json = serializeJSON(j, options: .prettyPrinted) - } - return SEChatEvent.event(type: type ?? "invalid", json: json ?? s) - } - var chatError: ChatError? { - switch self { - case let .chatError(_, error): error - default: nil - } - } - - var chatErrorType: ChatErrorType? { - switch self { - case let .chatError(_, .error(error)): error - default: nil - } - } + } } diff --git a/apps/ios/SimpleX SE/ShareModel.swift b/apps/ios/SimpleX SE/ShareModel.swift index b4d26b6d54..12a775f85c 100644 --- a/apps/ios/SimpleX SE/ShareModel.swift +++ b/apps/ios/SimpleX SE/ShareModel.swift @@ -303,9 +303,9 @@ class ShareModel: ObservableObject { } } } - let r: SEChatEvent? = recvSimpleXMsg(messageTimeout: 1_000_000) + let r: APIResult? = recvSimpleXMsg(messageTimeout: 1_000_000) switch r { - case let .sndFileProgressXFTP(_, ci, _, sentSize, totalSize): + case let .result(.sndFileProgressXFTP(_, ci, _, sentSize, totalSize)): guard isMessage(for: ci) else { continue } networkTimeout = CFAbsoluteTimeGetCurrent() await MainActor.run { @@ -314,14 +314,14 @@ class ShareModel: ObservableObject { bottomBar = .loadingBar(progress: progress) } } - case let .sndFileCompleteXFTP(_, ci, _): + case let .result(.sndFileCompleteXFTP(_, ci, _)): guard isMessage(for: ci) else { continue } if isGroupChat { await MainActor.run { bottomBar = .loadingSpinner } } await ch.completeFile() if await !ch.isRunning { break } - case let .chatItemsStatusesUpdated(_, chatItems): + case let .result(.chatItemsStatusesUpdated(_, chatItems)): guard let ci = chatItems.last else { continue } guard isMessage(for: ci) else { continue } if let (title, message) = ci.chatItem.meta.itemStatus.statusInfo { @@ -343,15 +343,15 @@ class ShareModel: ObservableObject { } } } - case let .sndFileError(_, ci, _, errorMessage): + case let .result(.sndFileError(_, ci, _, errorMessage)): guard isMessage(for: ci) else { continue } if let ci { cleanupFile(ci) } return ErrorAlert(title: "File error", message: "\(fileErrorInfo(ci) ?? errorMessage)") - case let .sndFileWarning(_, ci, _, errorMessage): + case let .result(.sndFileWarning(_, ci, _, errorMessage)): guard isMessage(for: ci) else { continue } if let ci { cleanupFile(ci) } return ErrorAlert(title: "File error", message: "\(fileErrorInfo(ci) ?? errorMessage)") - case let .chatError(_, chatError): + case let .error(chatError): return ErrorAlert(chatError) default: continue } diff --git a/apps/ios/SimpleXChat/API.swift b/apps/ios/SimpleXChat/API.swift index 0baf52b26c..0dd3483fd7 100644 --- a/apps/ios/SimpleXChat/API.swift +++ b/apps/ios/SimpleXChat/API.swift @@ -46,7 +46,7 @@ public func chatMigrateInit(_ useKey: String? = nil, confirmMigrations: Migratio var cConfirm = confirm.rawValue.cString(using: .utf8)! // the last parameter of chat_migrate_init is used to return the pointer to chat controller let cjson = chat_migrate_init_key(&cPath, &cKey, 1, &cConfirm, backgroundMode ? 1 : 0, &chatController)! - let dbRes = dbMigrationResult(fromCString(cjson)) + let dbRes = dbMigrationResult(dataFromCString(cjson)) let encrypted = dbKey != "" let keychainErr = dbRes == .ok && useKeychain && encrypted && !kcDatabasePassword.set(dbKey) let result = (encrypted, keychainErr ? .errorKeychain : dbRes) @@ -63,7 +63,7 @@ public func chatInitTemporaryDatabase(url: URL, key: String? = nil, confirmation var cKey = dbKey.cString(using: .utf8)! var cConfirm = confirmation.rawValue.cString(using: .utf8)! let cjson = chat_migrate_init_key(&cPath, &cKey, 1, &cConfirm, 0, &temporaryController)! - return (dbMigrationResult(fromCString(cjson)), temporaryController) + return (dbMigrationResult(dataFromCString(cjson)), temporaryController) } public func chatInitControllerRemovingDatabases() { @@ -110,27 +110,42 @@ public func resetChatCtrl() { migrationResult = nil } -public func sendSimpleXCmd(_ cmd: ChatCmdProtocol, _ ctrl: chat_ctrl? = nil) -> CR { - var c = cmd.cmdString.cString(using: .utf8)! - let cjson = chat_send_cmd(ctrl ?? getChatCtrl(), &c)! - return CR.chatResponse(fromCString(cjson)) +@inline(__always) +public func sendSimpleXCmd(_ cmd: ChatCmdProtocol, _ ctrl: chat_ctrl? = nil) -> APIResult { + if let d = sendSimpleXCmdStr(cmd.cmdString, ctrl) { + decodeAPIResult(d) + } else { + APIResult.error(.invalidJSON(json: nil)) + } +} + +@inline(__always) +public func sendSimpleXCmdStr(_ cmd: String, _ ctrl: chat_ctrl? = nil) -> Data? { + var c = cmd.cString(using: .utf8)! + return if let cjson = chat_send_cmd(ctrl ?? getChatCtrl(), &c) { + dataFromCString(cjson) + } else { + nil + } } // in microseconds public let MESSAGE_TIMEOUT: Int32 = 15_000_000 -public func recvSimpleXMsg(_ ctrl: chat_ctrl? = nil, messageTimeout: Int32 = MESSAGE_TIMEOUT) -> CEvt? { - if let cjson = chat_recv_msg_wait(ctrl ?? getChatCtrl(), messageTimeout) { - let s = fromCString(cjson) - return s == "" ? nil : CEvt.chatEvent(s) +@inline(__always) +public func recvSimpleXMsg(_ ctrl: chat_ctrl? = nil, messageTimeout: Int32 = MESSAGE_TIMEOUT) -> APIResult? { + if let cjson = chat_recv_msg_wait(ctrl ?? getChatCtrl(), messageTimeout), + let d = dataFromCString(cjson) { + decodeAPIResult(d) + } else { + nil } - return nil } public func parseSimpleXMarkdown(_ s: String) -> [FormattedText]? { var c = s.cString(using: .utf8)! if let cjson = chat_parse_markdown(&c) { - if let d = fromCString(cjson).data(using: .utf8) { + if let d = dataFromCString(cjson) { do { let r = try jsonDecoder.decode(ParsedMarkdown.self, from: d) return r.formattedText @@ -154,7 +169,7 @@ struct ParsedMarkdown: Decodable { public func parseServerAddress(_ s: String) -> ServerAddress? { var c = s.cString(using: .utf8)! if let cjson = chat_parse_server(&c) { - if let d = fromCString(cjson).data(using: .utf8) { + if let d = dataFromCString(cjson) { do { let r = try jsonDecoder.decode(ParsedServerAddress.self, from: d) return r.serverAddress @@ -171,12 +186,33 @@ struct ParsedServerAddress: Decodable { var parseError: String } +@inline(__always) public func fromCString(_ c: UnsafeMutablePointer) -> String { let s = String.init(cString: c) free(c) return s } +@inline(__always) +public func dataFromCString(_ c: UnsafeMutablePointer) -> Data? { + let len = strlen(c) + if len > 0 { + return Data(bytesNoCopy: c, count: len, deallocator: .free) + } else { + free(c) + return nil + } +} + +@inline(__always) +public func dataToString(_ d: Data?) -> String { + if let d { + String(data: d, encoding: .utf8) ?? "invalid string" + } else { + "no data" + } +} + public func decodeUser_(_ jDict: NSDictionary) -> UserRef? { if let user_ = jDict["user_"] { try? decodeObject(user_ as Any) @@ -185,7 +221,7 @@ public func decodeUser_(_ jDict: NSDictionary) -> UserRef? { } } -public func errorJson(_ jDict: NSDictionary) -> String? { +public func errorJson(_ jDict: NSDictionary) -> Data? { if let chatError = jDict["chatError"] { serializeJSON(chatError) } else { @@ -197,7 +233,11 @@ public func parseChatData(_ jChat: Any, _ jNavInfo: Any? = nil) throws -> (ChatD let jChatDict = jChat as! NSDictionary let chatInfo: ChatInfo = try decodeObject(jChatDict["chatInfo"]!) let chatStats: ChatStats = try decodeObject(jChatDict["chatStats"]!) - let navInfo: NavigationInfo = jNavInfo == nil ? NavigationInfo() : try decodeObject((jNavInfo as! NSDictionary)["navInfo"]!) + let navInfo: NavigationInfo = if let jNavInfo = jNavInfo as? NSDictionary, let jNav = jNavInfo["navInfo"] { + try decodeObject(jNav) + } else { + NavigationInfo() + } let jChatItems = jChatDict["chatItems"] as! NSArray let chatItems = jChatItems.map { jCI in if let ci: ChatItem = try? decodeObject(jCI) { @@ -206,16 +246,18 @@ public func parseChatData(_ jChat: Any, _ jNavInfo: Any? = nil) throws -> (ChatD return ChatItem.invalidJSON( chatDir: decodeProperty(jCI, "chatDir"), meta: decodeProperty(jCI, "meta"), - json: serializeJSON(jCI, options: .prettyPrinted) ?? "" + json: serializeJSON(jCI, options: .prettyPrinted) ) } return (ChatData(chatInfo: chatInfo, chatItems: chatItems, chatStats: chatStats), navInfo) } +@inline(__always) public func decodeObject(_ obj: Any) throws -> T { try jsonDecoder.decode(T.self, from: JSONSerialization.data(withJSONObject: obj)) } +@inline(__always) func decodeProperty(_ obj: Any, _ prop: NSString) -> T? { if let jProp = (obj as? NSDictionary)?[prop] { return try? decodeObject(jProp) @@ -223,28 +265,52 @@ func decodeProperty(_ obj: Any, _ prop: NSString) -> T? { return nil } -public func serializeJSON(_ obj: Any, options: JSONSerialization.WritingOptions = []) -> String? { - if let d = try? JSONSerialization.data(withJSONObject: obj, options: options) { - return String(decoding: d, as: UTF8.self) +@inline(__always) +func getOWSF(_ obj: NSDictionary, _ prop: NSString) -> (type: String, object: NSDictionary)? { + if let j = obj[prop] as? NSDictionary, j.count == 1 || j.count == 2 { + var type = j.allKeys[0] as? String + if j.count == 2 && type == "_owsf" { + type = j.allKeys[1] as? String + } + if let type { + return (type, j) + } } return nil } +@inline(__always) +public func serializeJSON(_ obj: Any, options: JSONSerialization.WritingOptions = []) -> Data? { + if let d = try? JSONSerialization.data(withJSONObject: obj, options: options) { + dataPrefix(d) + } else { + nil + } +} + +let MAX_JSON_VIEW_LENGTH = 2048 + +@inline(__always) +public func dataPrefix(_ d: Data) -> Data { + d.count > MAX_JSON_VIEW_LENGTH + ? Data(d.prefix(MAX_JSON_VIEW_LENGTH)) + : d +} + public func responseError(_ err: Error) -> String { - if let r = err as? ChatRespProtocol { - if let e = r.chatError { - chatErrorString(e) - } else { - "\(String(describing: r.responseType)), details: \(String(describing: r.details))" - } + if let e = err as? ChatError { + chatErrorString(e) } else { String(describing: err) } } public func chatErrorString(_ err: ChatError) -> String { - if case let .invalidJSON(json) = err { return json } - return String(describing: err) + switch err { + case let .invalidJSON(json): dataToString(json) + case let .unexpectedResult(type): "unexpected result: \(type)" + default: String(describing: err) + } } public enum DBMigrationResult: Decodable, Equatable { @@ -283,15 +349,15 @@ public enum MTRError: Decodable, Equatable { case different(appMigration: String, dbMigration: String) } -func dbMigrationResult(_ s: String) -> DBMigrationResult { - let d = s.data(using: .utf8)! -// TODO is there a way to do it without copying the data? e.g: -// let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson)) -// let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free) - do { - return try jsonDecoder.decode(DBMigrationResult.self, from: d) - } catch let error { - logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)") - return .unknown(json: s) +func dbMigrationResult(_ d: Data?) -> DBMigrationResult { + if let d { + do { + return try jsonDecoder.decode(DBMigrationResult.self, from: d) + } catch let error { + logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)") + return .unknown(json: dataToString(d)) + } + } else { + return .unknown(json: "no data") } } diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index f635cfb7bb..b8d2361ac8 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -17,30 +17,117 @@ public protocol ChatCmdProtocol { var cmdString: String { get } } +@inline(__always) public func onOff(_ b: Bool) -> String { b ? "on" : "off" } -public struct APIResponse: Decodable { - public var resp: ChatRespProtocol +public enum APIResult: Decodable where R: Decodable, R: ChatAPIResult { + case result(R) + case error(ChatError) + case invalid(type: String, json: Data) + + public var responseType: String { + switch self { + case let .result(r): r.responseType + case let .error(e): "error \(e.errorType)" + case let .invalid(type, _): "* \(type)" + } + } + + public var unexpected: ChatError { + switch self { + case let .result(r): .unexpectedResult(type: r.responseType) + case let .error(e): e + case let .invalid(type, _): .unexpectedResult(type: "* \(type)") + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + if container.contains(.result) { + let result = try container.decode(R.self, forKey: .result) + self = .result(result) + } else { + let error = try container.decode(ChatError.self, forKey: .error) + self = .error(error) + } + } + + private enum CodingKeys: String, CodingKey { + case result, error + } } -public protocol ChatRespProtocol: Decodable, Error { +public protocol ChatAPIResult: Decodable { var responseType: String { get } var details: String { get } - static func chatResponse(_ s: String) -> Self - var chatError: ChatError? { get } - var chatErrorType: ChatErrorType? { get } + static func fallbackResult(_ type: String, _ json: NSDictionary) -> Self? } -public protocol ChatEventProtocol: Decodable, Error { - var eventType: String { get } - var details: String { get } - static func chatEvent(_ s: String) -> Self - var chatError: ChatError? { get } - var chatErrorType: ChatErrorType? { get } +extension ChatAPIResult { + public var noDetails: String { "\(self.responseType): no details" } + + @inline(__always) + public static func fallbackResult(_ type: String, _ json: NSDictionary) -> Self? { + nil + } + + @inline(__always) + public var unexpected: ChatError { + .unexpectedResult(type: self.responseType) + } } +public func decodeAPIResult(_ d: Data) -> APIResult { +// print("decodeAPIResult \(String(describing: R.self))") + do { +// return try withStackSizeLimit { try jsonDecoder.decode(APIResult.self, from: d) } + return try jsonDecoder.decode(APIResult.self, from: d) + } catch {} + if let j = try? JSONSerialization.jsonObject(with: d) as? NSDictionary { + if let (_, jErr) = getOWSF(j, "error") { + return APIResult.error(.invalidJSON(json: errorJson(jErr))) as APIResult + } else if let (type, jRes) = getOWSF(j, "result") { + return if let r = R.fallbackResult(type, jRes) { + APIResult.result(r) + } else { + APIResult.invalid(type: type, json: dataPrefix(d)) + } + } + } + return APIResult.invalid(type: "invalid", json: dataPrefix(d)) +} + +// Default stack size for the main thread is 1mb, for secondary threads - 512 kb. +// This function can be used to test what size is used (or to increase available stack size). +// Stack size must be a multiple of system page size (16kb). +//private let stackSizeLimit: Int = 256 * 1024 +// +//private func withStackSizeLimit(_ f: @escaping () throws -> T) throws -> T { +// let semaphore = DispatchSemaphore(value: 0) +// var result: Result? +// let thread = Thread { +// do { +// result = .success(try f()) +// } catch { +// result = .failure(error) +// } +// semaphore.signal() +// } +// +// thread.stackSize = stackSizeLimit +// thread.qualityOfService = Thread.current.qualityOfService +// thread.start() +// +// semaphore.wait() +// +// switch result! { +// case let .success(r): return r +// case let .failure(e): throw e +// } +//} + public func parseApiChats(_ jResp: NSDictionary) -> (user: UserRef, chats: [ChatData])? { if let jApiChats = jResp["apiChats"] as? NSDictionary, let user: UserRef = try? decodeObject(jApiChats["user"] as Any), @@ -49,7 +136,7 @@ public func parseApiChats(_ jResp: NSDictionary) -> (user: UserRef, chats: [Chat if let chatData = try? parseChatData(jChat) { return chatData.0 } - return ChatData.invalidJSON(serializeJSON(jChat, options: .prettyPrinted) ?? "") + return ChatData.invalidJSON(serializeJSON(jChat, options: .prettyPrinted)) } return (user, chats) } else { @@ -553,13 +640,26 @@ private func encodeCJSON(_ value: T) -> [CChar] { encodeJSON(value).cString(using: .utf8)! } -public enum ChatError: Decodable, Hashable { +public enum ChatError: Decodable, Hashable, Error { case error(errorType: ChatErrorType) case errorAgent(agentError: AgentErrorType) case errorStore(storeError: StoreError) case errorDatabase(databaseError: DatabaseError) case errorRemoteCtrl(remoteCtrlError: RemoteCtrlError) - case invalidJSON(json: String) + case invalidJSON(json: Data?) // additional case used to pass errors that failed to parse + case unexpectedResult(type: String) // additional case used to pass unexpected responses + + public var errorType: String { + switch self { + case .error: "chat" + case .errorAgent: "agent" + case .errorStore: "store" + case .errorDatabase: "database" + case .errorRemoteCtrl: "remoteCtrl" + case .invalidJSON: "invalid" + case let .unexpectedResult(type): "! \(type)" + } + } } public enum ChatErrorType: Decodable, Hashable { diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 35c276b2f4..bff1ebb4fd 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -1201,7 +1201,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { case local(noteFolder: NoteFolder) case contactRequest(contactRequest: UserContactRequest) case contactConnection(contactConnection: PendingContactConnection) - case invalidJSON(json: String) + case invalidJSON(json: Data?) private static let invalidChatName = NSLocalizedString("invalid chat", comment: "invalid chat data") @@ -1589,7 +1589,7 @@ public struct ChatData: Decodable, Identifiable, Hashable, ChatLike { self.chatStats = chatStats } - public static func invalidJSON(_ json: String) -> ChatData { + public static func invalidJSON(_ json: Data?) -> ChatData { ChatData( chatInfo: .invalidJSON(json: json), chatItems: [], @@ -2905,7 +2905,7 @@ public struct ChatItem: Identifiable, Decodable, Hashable { return item } - public static func invalidJSON(chatDir: CIDirection?, meta: CIMeta?, json: String) -> ChatItem { + public static func invalidJSON(chatDir: CIDirection?, meta: CIMeta?, json: Data?) -> ChatItem { ChatItem( chatDir: chatDir ?? .directSnd, meta: meta ?? .invalidJSON, @@ -3352,7 +3352,7 @@ public enum CIContent: Decodable, ItemContent, Hashable { case rcvDirectE2EEInfo(e2eeInfo: E2EEInfo) case sndGroupE2EEInfo(e2eeInfo: E2EEInfo) case rcvGroupE2EEInfo(e2eeInfo: E2EEInfo) - case invalidJSON(json: String) + case invalidJSON(json: Data?) public var text: String { get { diff --git a/apps/ios/SimpleXChat/CryptoFile.swift b/apps/ios/SimpleXChat/CryptoFile.swift index 0e539ba97c..dfe833f832 100644 --- a/apps/ios/SimpleXChat/CryptoFile.swift +++ b/apps/ios/SimpleXChat/CryptoFile.swift @@ -18,10 +18,10 @@ public func writeCryptoFile(path: String, data: Data) throws -> CryptoFileArgs { memcpy(ptr, (data as NSData).bytes, data.count) var cPath = path.cString(using: .utf8)! let cjson = chat_write_file(getChatCtrl(), &cPath, ptr, Int32(data.count))! - let d = fromCString(cjson).data(using: .utf8)! + let d = dataFromCString(cjson)! // TODO [unsafe] switch try jsonDecoder.decode(WriteFileResult.self, from: d) { case let .result(cfArgs): return cfArgs - case let .error(err): throw RuntimeError(err) + case let .error(err): throw RuntimeError(err) // TODO [unsafe] } } @@ -51,10 +51,10 @@ public func encryptCryptoFile(fromPath: String, toPath: String) throws -> Crypto var cFromPath = fromPath.cString(using: .utf8)! var cToPath = toPath.cString(using: .utf8)! let cjson = chat_encrypt_file(getChatCtrl(), &cFromPath, &cToPath)! - let d = fromCString(cjson).data(using: .utf8)! + let d = dataFromCString(cjson)! // TODO [unsafe] switch try jsonDecoder.decode(WriteFileResult.self, from: d) { case let .result(cfArgs): return cfArgs - case let .error(err): throw RuntimeError(err) + case let .error(err): throw RuntimeError(err) // TODO [unsafe] } } diff --git a/apps/ios/SimpleXChat/ErrorAlert.swift b/apps/ios/SimpleXChat/ErrorAlert.swift index c99e004d92..a433d2313b 100644 --- a/apps/ios/SimpleXChat/ErrorAlert.swift +++ b/apps/ios/SimpleXChat/ErrorAlert.swift @@ -37,22 +37,18 @@ public struct ErrorAlert: Error { } public init(_ error: any Error) { - self = if let chatResponse = error as? ChatRespProtocol { - ErrorAlert(chatResponse) + self = if let e = error as? ChatError { + ErrorAlert(e) } else { ErrorAlert("\(error.localizedDescription)") } } public init(_ chatError: ChatError) { - self = ErrorAlert("\(chatErrorString(chatError))") - } - - public init(_ chatResponse: ChatRespProtocol) { - self = if let networkErrorAlert = getNetworkErrorAlert(chatResponse) { + self = if let networkErrorAlert = getNetworkErrorAlert(chatError) { networkErrorAlert } else { - ErrorAlert("\(responseError(chatResponse))") + ErrorAlert("\(chatErrorString(chatError))") } } } @@ -94,8 +90,8 @@ extension View { } } -public func getNetworkErrorAlert(_ r: ChatRespProtocol) -> ErrorAlert? { - switch r.chatError { +public func getNetworkErrorAlert(_ e: ChatError) -> ErrorAlert? { + switch e { case let .errorAgent(.BROKER(addr, .TIMEOUT)): ErrorAlert(title: "Connection timeout", message: "Please check your network connection with \(serverHostname(addr)) and try again.") case let .errorAgent(.BROKER(addr, .NETWORK)): diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt index 2d2829f1f2..bacdfe70af 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt @@ -229,5 +229,5 @@ fun isMediaIntent(intent: Intent): Boolean = // val str: String = """ // """.trimIndent() // -// println(json.decodeFromString(str)) +// println(json.decodeFromString(str)) //} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 47d9563ee3..6660cbbb93 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -38,6 +38,7 @@ import java.net.URI import java.time.format.DateTimeFormatter import java.time.format.FormatStyle import java.util.* +import java.util.concurrent.atomic.AtomicLong import kotlin.collections.ArrayList import kotlin.random.Random import kotlin.time.* @@ -1396,19 +1397,21 @@ sealed class ChatInfo: SomeChat, NamedChat { } @Serializable @SerialName("invalidJSON") - class InvalidJSON(val json: String): ChatInfo() { + class InvalidJSON( + val json: String, + override val apiId: Long = -idGenerator.getAndIncrement(), + override val createdAt: Instant = Clock.System.now(), + override val updatedAt: Instant = Clock.System.now() + ): ChatInfo() { override val chatType get() = ChatType.Direct override val localDisplayName get() = invalidChatName - override val id get() = "" - override val apiId get() = 0L + override val id get() = "?$apiId" override val ready get() = false override val chatDeleted get() = false override val sendMsgEnabled get() = false override val incognito get() = false override fun featureEnabled(feature: ChatFeature) = false override val timedMessagesTTL: Int? get() = null - override val createdAt get() = Clock.System.now() - override val updatedAt get() = Clock.System.now() override val displayName get() = invalidChatName override val fullName get() = invalidChatName override val image get() = null @@ -1416,6 +1419,7 @@ sealed class ChatInfo: SomeChat, NamedChat { companion object { private val invalidChatName = generalGetString(MR.strings.invalid_chat) + private val idGenerator = AtomicLong(0) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 8c1166dccd..7025e4caf3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -47,6 +47,9 @@ import kotlinx.datetime.Clock import kotlinx.datetime.Instant import kotlinx.serialization.* import kotlinx.serialization.builtins.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.* import java.util.Date @@ -465,7 +468,7 @@ object ChatController { var ctrl: ChatCtrl? = -1 val appPrefs: AppPreferences by lazy { AppPreferences() } - val messagesChannel: Channel = Channel() + val messagesChannel: Channel = Channel() val chatModel = ChatModel private var receiverStarted = false @@ -478,8 +481,7 @@ object ChatController { val userId = currentUserId("getAgentSubsTotal") val r = sendCmd(rh, CC.GetAgentSubsTotal(userId), log = false) - - if (r is CR.AgentSubsTotal) return r.subsTotal to r.hasSession + if (r is API.Result && r.res is CR.AgentSubsTotal) return r.res.subsTotal to r.res.hasSession Log.e(TAG, "getAgentSubsTotal bad response: ${r.responseType} ${r.details}") return null } @@ -488,8 +490,7 @@ object ChatController { val userId = currentUserId("getAgentServersSummary") val r = sendCmd(rh, CC.GetAgentServersSummary(userId), log = false) - - if (r is CR.AgentServersSummary) return r.serversSummary + if (r is API.Result && r.res is CR.AgentServersSummary) return r.res.serversSummary Log.e(TAG, "getAgentServersSummary bad response: ${r.responseType} ${r.details}") return null } @@ -641,11 +642,11 @@ object ChatController { messagesChannel.trySend(msg) } if (finishedWithoutTimeout == null) { - Log.e(TAG, "Timeout reached while processing received message: " + msg.resp.responseType) + Log.e(TAG, "Timeout reached while processing received message: " + msg.responseType) if (appPreferences.developerTools.get() && appPreferences.showSlowApiCalls.get()) { AlertManager.shared.showAlertMsg( title = generalGetString(MR.strings.possible_slow_function_title), - text = generalGetString(MR.strings.possible_slow_function_desc).format(60, msg.resp.responseType + "\n" + Exception().stackTraceToString()), + text = generalGetString(MR.strings.possible_slow_function_desc).format(60, msg.responseType + "\n" + Exception().stackTraceToString()), shareText = true ) } @@ -661,7 +662,7 @@ object ChatController { } } - suspend fun sendCmd(rhId: Long?, cmd: CC, otherCtrl: ChatCtrl? = null, log: Boolean = true): CR { + suspend fun sendCmd(rhId: Long?, cmd: CC, otherCtrl: ChatCtrl? = null, log: Boolean = true): API { val ctrl = otherCtrl ?: ctrl ?: throw Exception("Controller is not initialized") return withContext(Dispatchers.IO) { @@ -670,37 +671,36 @@ object ChatController { chatModel.addTerminalItem(TerminalItem.cmd(rhId, cmd.obfuscated)) Log.d(TAG, "sendCmd: ${cmd.cmdType}") } - val json = if (rhId == null) chatSendCmd(ctrl, c) else chatSendRemoteCmd(ctrl, rhId.toInt(), c) + val rStr = if (rhId == null) chatSendCmd(ctrl, c) else chatSendRemoteCmd(ctrl, rhId.toInt(), c) // coroutine was cancelled already, no need to process response (helps with apiListMembers - very heavy query in large groups) interruptIfCancelled() - val r = APIResponse.decodeStr(json) + val r = json.decodeFromString(rStr) if (log) { - Log.d(TAG, "sendCmd response type ${r.resp.responseType}") - if (r.resp is CR.Response || r.resp is CR.Invalid) { - Log.d(TAG, "sendCmd response json $json") + Log.d(TAG, "sendCmd response type ${r.responseType}") + if (r is API.Result && (r.res is CR.Response || r.res is CR.Invalid)) { + Log.d(TAG, "sendCmd response json $rStr") } - chatModel.addTerminalItem(TerminalItem.resp(rhId, r.resp)) + chatModel.addTerminalItem(TerminalItem.resp(rhId, r)) } - r.resp + r } } - fun recvMsg(ctrl: ChatCtrl): APIResponse? { - val json = chatRecvMsgWait(ctrl, MESSAGE_TIMEOUT) - return if (json == "") { + fun recvMsg(ctrl: ChatCtrl): API? { + val rStr = chatRecvMsgWait(ctrl, MESSAGE_TIMEOUT) + return if (rStr == "") { null } else { - val apiResp = APIResponse.decodeStr(json) - val r = apiResp.resp + val r = json.decodeFromString(rStr) Log.d(TAG, "chatRecvMsg: ${r.responseType}") - if (r is CR.Response || r is CR.Invalid) Log.d(TAG, "chatRecvMsg json: $json") - apiResp + if (r is API.Result && (r.res is CR.Response || r.res is CR.Invalid)) Log.d(TAG, "chatRecvMsg json: $rStr") + r } } suspend fun apiGetActiveUser(rh: Long?, ctrl: ChatCtrl? = null): User? { val r = sendCmd(rh, CC.ShowActiveUser(), ctrl) - if (r is CR.ActiveUser) return r.user.updateRemoteHostId(rh) + if (r is API.Result && r.res is CR.ActiveUser) return r.res.user.updateRemoteHostId(rh) Log.d(TAG, "apiGetActiveUser: ${r.responseType} ${r.details}") if (rh == null) { chatModel.localUserCreated.value = false @@ -710,14 +710,15 @@ object ChatController { suspend fun apiCreateActiveUser(rh: Long?, p: Profile?, pastTimestamp: Boolean = false, ctrl: ChatCtrl? = null): User? { val r = sendCmd(rh, CC.CreateActiveUser(p, pastTimestamp = pastTimestamp), ctrl) - if (r is CR.ActiveUser) return r.user.updateRemoteHostId(rh) - else if ( - r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore && r.chatError.storeError is StoreError.DuplicateName || - r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorChat && r.chatError.errorType is ChatErrorType.UserExists + if (r is API.Result && r.res is CR.ActiveUser) return r.res.user.updateRemoteHostId(rh) + val e = (r as? API.Error)?.err + if ( + e is ChatError.ChatErrorStore && e.storeError is StoreError.DuplicateName || + e is ChatError.ChatErrorChat && e.errorType is ChatErrorType.UserExists ) { AlertManager.shared.showAlertMsg(generalGetString(MR.strings.failed_to_create_user_duplicate_title), generalGetString(MR.strings.failed_to_create_user_duplicate_desc)) } else if ( - r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorChat && r.chatError.errorType is ChatErrorType.InvalidDisplayName + e is ChatError.ChatErrorChat && e.errorType is ChatErrorType.InvalidDisplayName ) { AlertManager.shared.showAlertMsg(generalGetString(MR.strings.failed_to_create_user_invalid_title), generalGetString(MR.strings.failed_to_create_user_invalid_desc)) } else { @@ -729,8 +730,8 @@ object ChatController { suspend fun listUsers(rh: Long?): List { val r = sendCmd(rh, CC.ListUsers()) - if (r is CR.UsersList) { - val users = if (rh == null) r.users else r.users.map { it.copy(user = it.user.copy(remoteHostId = rh)) } + if (r is API.Result && r.res is CR.UsersList) { + val users = if (rh == null) r.res.users else r.res.users.map { it.copy(user = it.user.copy(remoteHostId = rh)) } return users.sortedBy { it.user.chatViewName } } Log.d(TAG, "listUsers: ${r.responseType} ${r.details}") @@ -739,26 +740,26 @@ object ChatController { suspend fun apiSetActiveUser(rh: Long?, userId: Long, viewPwd: String?): User { val r = sendCmd(rh, CC.ApiSetActiveUser(userId, viewPwd)) - if (r is CR.ActiveUser) return r.user.updateRemoteHostId(rh) + if (r is API.Result && r.res is CR.ActiveUser) return r.res.user.updateRemoteHostId(rh) Log.d(TAG, "apiSetActiveUser: ${r.responseType} ${r.details}") throw Exception("failed to set the user as active ${r.responseType} ${r.details}") } suspend fun apiSetAllContactReceipts(rh: Long?, enable: Boolean) { val r = sendCmd(rh, CC.SetAllContactReceipts(enable)) - if (r is CR.CmdOk) return + if (r.result is CR.CmdOk) return throw Exception("failed to set receipts for all users ${r.responseType} ${r.details}") } suspend fun apiSetUserContactReceipts(u: User, userMsgReceiptSettings: UserMsgReceiptSettings) { val r = sendCmd(u.remoteHostId, CC.ApiSetUserContactReceipts(u.userId, userMsgReceiptSettings)) - if (r is CR.CmdOk) return + if (r.result is CR.CmdOk) return throw Exception("failed to set receipts for user contacts ${r.responseType} ${r.details}") } suspend fun apiSetUserGroupReceipts(u: User, userMsgReceiptSettings: UserMsgReceiptSettings) { val r = sendCmd(u.remoteHostId, CC.ApiSetUserGroupReceipts(u.userId, userMsgReceiptSettings)) - if (r is CR.CmdOk) return + if (r.result is CR.CmdOk) return throw Exception("failed to set receipts for user groups ${r.responseType} ${r.details}") } @@ -776,20 +777,20 @@ object ChatController { private suspend fun setUserPrivacy(rh: Long?, cmd: CC): User { val r = sendCmd(rh, cmd) - if (r is CR.UserPrivacy) return r.updatedUser.updateRemoteHostId(rh) + if (r is API.Result && r.res is CR.UserPrivacy) return r.res.updatedUser.updateRemoteHostId(rh) else throw Exception("Failed to change user privacy: ${r.responseType} ${r.details}") } suspend fun apiDeleteUser(u: User, delSMPQueues: Boolean, viewPwd: String?) { val r = sendCmd(u.remoteHostId, CC.ApiDeleteUser(u.userId, delSMPQueues, viewPwd)) - if (r is CR.CmdOk) return + if (r.result is CR.CmdOk) return Log.d(TAG, "apiDeleteUser: ${r.responseType} ${r.details}") throw Exception("failed to delete the user ${r.responseType} ${r.details}") } suspend fun apiStartChat(ctrl: ChatCtrl? = null): Boolean { val r = sendCmd(null, CC.StartChat(mainApp = true), ctrl) - when (r) { + when (r.result) { is CR.ChatStarted -> return true is CR.ChatRunning -> return false else -> throw Exception("failed starting chat: ${r.responseType} ${r.details}") @@ -798,7 +799,7 @@ object ChatController { private suspend fun apiCheckChatRunning(): Boolean { val r = sendCmd(null, CC.CheckChatRunning()) - when (r) { + when (r.result) { is CR.ChatRunning -> return true is CR.ChatStopped -> return false else -> throw Exception("failed check chat running: ${r.responseType} ${r.details}") @@ -807,15 +808,13 @@ object ChatController { suspend fun apiStopChat(): Boolean { val r = sendCmd(null, CC.ApiStopChat()) - when (r) { - is CR.ChatStopped -> return true - else -> throw Exception("failed stopping chat: ${r.responseType} ${r.details}") - } + if (r.result is CR.ChatStopped) return true + throw Exception("failed stopping chat: ${r.responseType} ${r.details}") } suspend fun apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String, remoteHostsFolder: String, ctrl: ChatCtrl? = null) { val r = sendCmd(null, CC.ApiSetAppFilePaths(filesFolder, tempFolder, assetsFolder, remoteHostsFolder), ctrl) - if (r is CR.CmdOk) return + if (r.result is CR.CmdOk) return throw Exception("failed to set app file paths: ${r.responseType} ${r.details}") } @@ -823,52 +822,52 @@ object ChatController { suspend fun apiSaveAppSettings(settings: AppSettings) { val r = sendCmd(null, CC.ApiSaveSettings(settings)) - if (r is CR.CmdOk) return + if (r.result is CR.CmdOk) return throw Exception("failed to set app settings: ${r.responseType} ${r.details}") } suspend fun apiGetAppSettings(settings: AppSettings): AppSettings { val r = sendCmd(null, CC.ApiGetSettings(settings)) - if (r is CR.AppSettingsR) return r.appSettings + if (r is API.Result && r.res is CR.AppSettingsR) return r.res.appSettings throw Exception("failed to get app settings: ${r.responseType} ${r.details}") } suspend fun apiExportArchive(config: ArchiveConfig): List { val r = sendCmd(null, CC.ApiExportArchive(config)) - if (r is CR.ArchiveExported) return r.archiveErrors + if (r is API.Result && r.res is CR.ArchiveExported) return r.res.archiveErrors throw Exception("failed to export archive: ${r.responseType} ${r.details}") } suspend fun apiImportArchive(config: ArchiveConfig): List { val r = sendCmd(null, CC.ApiImportArchive(config)) - if (r is CR.ArchiveImported) return r.archiveErrors + if (r is API.Result && r.res is CR.ArchiveImported) return r.res.archiveErrors throw Exception("failed to import archive: ${r.responseType} ${r.details}") } suspend fun apiDeleteStorage() { val r = sendCmd(null, CC.ApiDeleteStorage()) - if (r is CR.CmdOk) return + if (r.result is CR.CmdOk) return throw Exception("failed to delete storage: ${r.responseType} ${r.details}") } - suspend fun apiStorageEncryption(currentKey: String = "", newKey: String = ""): CR.ChatCmdError? { + suspend fun apiStorageEncryption(currentKey: String = "", newKey: String = ""): ChatError? { val r = sendCmd(null, CC.ApiStorageEncryption(DBEncryptionConfig(currentKey, newKey))) - if (r is CR.CmdOk) return null - else if (r is CR.ChatCmdError) return r + if (r.result is CR.CmdOk) return null + else if (r is API.Error) return r.err throw Exception("failed to set storage encryption: ${r.responseType} ${r.details}") } - suspend fun testStorageEncryption(key: String, ctrl: ChatCtrl? = null): CR.ChatCmdError? { + suspend fun testStorageEncryption(key: String, ctrl: ChatCtrl? = null): ChatError? { val r = sendCmd(null, CC.TestStorageEncryption(key), ctrl) - if (r is CR.CmdOk) return null - else if (r is CR.ChatCmdError) return r + if (r.result is CR.CmdOk) return null + else if (r is API.Error) return r.err throw Exception("failed to test storage encryption: ${r.responseType} ${r.details}") } suspend fun apiGetChats(rh: Long?): List { val userId = kotlin.runCatching { currentUserId("apiGetChats") }.getOrElse { return emptyList() } val r = sendCmd(rh, CC.ApiGetChats(userId)) - if (r is CR.ApiChats) return if (rh == null) r.chats else r.chats.map { it.copy(remoteHostId = rh) } + if (r is API.Result && r.res is CR.ApiChats) return if (rh == null) r.res.chats else r.res.chats.map { it.copy(remoteHostId = rh) } Log.e(TAG, "failed getting the list of chats: ${r.responseType} ${r.details}") AlertManager.shared.showAlertMsg(generalGetString(MR.strings.failed_to_parse_chats_title), generalGetString(MR.strings.contact_developers)) return emptyList() @@ -877,8 +876,7 @@ object ChatController { private suspend fun apiGetChatTags(rh: Long?): List?{ val userId = currentUserId("apiGetChatTags") val r = sendCmd(rh, CC.ApiGetChatTags(userId)) - - if (r is CR.ChatTags) return r.userTags + if (r is API.Result && r.res is CR.ChatTags) return r.res.userTags Log.e(TAG, "apiGetChatTags bad response: ${r.responseType} ${r.details}") AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_loading_chat_tags), "${r.responseType}: ${r.details}") return null @@ -886,9 +884,10 @@ object ChatController { suspend fun apiGetChat(rh: Long?, type: ChatType, id: Long, contentTag: MsgContentTag? = null, pagination: ChatPagination, search: String = ""): Pair? { val r = sendCmd(rh, CC.ApiGetChat(type, id, contentTag, pagination, search)) - if (r is CR.ApiChat) return if (rh == null) r.chat to r.navInfo else r.chat.copy(remoteHostId = rh) to r.navInfo + if (r is API.Result && r.res is CR.ApiChat) return if (rh == null) r.res.chat to r.res.navInfo else r.res.chat.copy(remoteHostId = rh) to r.res.navInfo Log.e(TAG, "apiGetChat bad response: ${r.responseType} ${r.details}") - if (pagination is ChatPagination.Around && r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore && r.chatError.storeError is StoreError.ChatItemNotFound) { + val e = (r as? API.Error)?.err + if (pagination is ChatPagination.Around && e is ChatError.ChatErrorStore && e.storeError is StoreError.ChatItemNotFound) { showQuotedItemDoesNotExistAlert() } else { AlertManager.shared.showAlertMsg(generalGetString(MR.strings.failed_to_parse_chat_title), generalGetString(MR.strings.contact_developers)) @@ -898,7 +897,7 @@ object ChatController { suspend fun apiCreateChatTag(rh: Long?, tag: ChatTagData): List? { val r = sendCmd(rh, CC.ApiCreateChatTag(tag)) - if (r is CR.ChatTags) return r.userTags + if (r is API.Result && r.res is CR.ChatTags) return r.res.userTags Log.e(TAG, "apiCreateChatTag bad response: ${r.responseType} ${r.details}") AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_creating_chat_tags), "${r.responseType}: ${r.details}") return null @@ -906,7 +905,7 @@ object ChatController { suspend fun apiSetChatTags(rh: Long?, type: ChatType, id: Long, tagIds: List): Pair, List>? { val r = sendCmd(rh, CC.ApiSetChatTags(type, id, tagIds)) - if (r is CR.TagsUpdated) return r.userTags to r.chatTags + if (r is API.Result && r.res is CR.TagsUpdated) return r.res.userTags to r.res.chatTags Log.e(TAG, "apiSetChatTags bad response: ${r.responseType} ${r.details}") AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_updating_chat_tags), "${r.responseType}: ${r.details}") return null @@ -926,8 +925,8 @@ object ChatController { private suspend fun processSendMessageCmd(rh: Long?, cmd: CC): List? { val r = sendCmd(rh, cmd) return when { - r is CR.NewChatItems -> r.chatItems - r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore && r.chatError.storeError is StoreError.LargeMsg && cmd is CC.ApiSendMessages -> { + r is API.Result && r.res is CR.NewChatItems -> r.res.chatItems + r is API.Error && r.err is ChatError.ChatErrorStore && r.err.storeError is StoreError.LargeMsg && cmd is CC.ApiSendMessages -> { val mc = cmd.composedMessages.last().msgContent AlertManager.shared.showAlertMsg( generalGetString(MR.strings.maximum_message_size_title), @@ -939,7 +938,7 @@ object ChatController { ) null } - r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore && r.chatError.storeError is StoreError.LargeMsg && cmd is CC.ApiForwardChatItems -> { + r is API.Error && r.err is ChatError.ChatErrorStore && r.err.storeError is StoreError.LargeMsg && cmd is CC.ApiForwardChatItems -> { AlertManager.shared.showAlertMsg( generalGetString(MR.strings.maximum_message_size_title), generalGetString(MR.strings.maximum_message_size_reached_forwarding) @@ -954,37 +953,27 @@ object ChatController { } } } - suspend fun apiCreateChatItems(rh: Long?, noteFolderId: Long, composedMessages: List): List? { + + suspend fun apiCreateChatItems(rh: Long?, noteFolderId: Long, composedMessages: List): List? { val cmd = CC.ApiCreateChatItems(noteFolderId, composedMessages) val r = sendCmd(rh, cmd) - return when (r) { - is CR.NewChatItems -> r.chatItems - else -> { - apiErrorAlert("apiCreateChatItems", generalGetString(MR.strings.error_creating_message), r) - null - } - } + if (r is API.Result && r.res is CR.NewChatItems) return r.res.chatItems + apiErrorAlert("apiCreateChatItems", generalGetString(MR.strings.error_creating_message), r) + return null } suspend fun apiReportMessage(rh: Long?, groupId: Long, chatItemId: Long, reportReason: ReportReason, reportText: String): List? { val r = sendCmd(rh, CC.ApiReportMessage(groupId, chatItemId, reportReason, reportText)) - return when (r) { - is CR.NewChatItems -> r.chatItems - else -> { - apiErrorAlert("apiReportMessage", generalGetString(MR.strings.error_creating_report), r) - null - } - } + if (r is API.Result && r.res is CR.NewChatItems) r.res.chatItems + apiErrorAlert("apiReportMessage", generalGetString(MR.strings.error_creating_report), r) + return null } suspend fun apiGetChatItemInfo(rh: Long?, type: ChatType, id: Long, itemId: Long): ChatItemInfo? { - return when (val r = sendCmd(rh, CC.ApiGetChatItemInfo(type, id, itemId))) { - is CR.ApiChatItemInfo -> r.chatItemInfo - else -> { - apiErrorAlert("apiGetChatItemInfo", generalGetString(MR.strings.error_loading_details), r) - null - } - } + val r = sendCmd(rh, CC.ApiGetChatItemInfo(type, id, itemId)) + if (r is API.Result && r.res is CR.ApiChatItemInfo) return r.res.chatItemInfo + apiErrorAlert("apiGetChatItemInfo", generalGetString(MR.strings.error_loading_details), r) + return null } suspend fun apiForwardChatItems(rh: Long?, toChatType: ChatType, toChatId: Long, fromChatType: ChatType, fromChatId: Long, itemIds: List, ttl: Int?): List? { @@ -993,21 +982,18 @@ object ChatController { } suspend fun apiPlanForwardChatItems(rh: Long?, fromChatType: ChatType, fromChatId: Long, chatItemIds: List): CR.ForwardPlan? { - return when (val r = sendCmd(rh, CC.ApiPlanForwardChatItems(fromChatType, fromChatId, chatItemIds))) { - is CR.ForwardPlan -> r - else -> { - apiErrorAlert("apiPlanForwardChatItems", generalGetString(MR.strings.error_forwarding_messages), r) - null - } - } + val r = sendCmd(rh, CC.ApiPlanForwardChatItems(fromChatType, fromChatId, chatItemIds)) + if (r is API.Result && r.res is CR.ForwardPlan) return r.res + apiErrorAlert("apiPlanForwardChatItems", generalGetString(MR.strings.error_forwarding_messages), r) + return null } suspend fun apiUpdateChatItem(rh: Long?, type: ChatType, id: Long, itemId: Long, updatedMessage: UpdatedMessage, live: Boolean = false): AChatItem? { val r = sendCmd(rh, CC.ApiUpdateChatItem(type, id, itemId, updatedMessage, live)) when { - r is CR.ChatItemUpdated -> return r.chatItem - r is CR.ChatItemNotChanged -> return r.chatItem - r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore && r.chatError.storeError is StoreError.LargeMsg -> { + r is API.Result && r.res is CR.ChatItemUpdated -> return r.res.chatItem + r is API.Result && r.res is CR.ChatItemNotChanged -> return r.res.chatItem + r is API.Error && r.err is ChatError.ChatErrorStore && r.err.storeError is StoreError.LargeMsg -> { val mc = updatedMessage.msgContent AlertManager.shared.showAlertMsg( generalGetString(MR.strings.maximum_message_size_title), @@ -1027,7 +1013,7 @@ object ChatController { suspend fun apiChatItemReaction(rh: Long?, type: ChatType, id: Long, itemId: Long, add: Boolean, reaction: MsgReaction): ChatItem? { val r = sendCmd(rh, CC.ApiChatItemReaction(type, id, itemId, add, reaction)) - if (r is CR.ChatItemReaction) return r.reaction.chatReaction.chatItem + if (r is API.Result && r.res is CR.ChatItemReaction) return r.res.reaction.chatReaction.chatItem Log.e(TAG, "apiUpdateChatItem bad response: ${r.responseType} ${r.details}") return null } @@ -1035,35 +1021,35 @@ object ChatController { suspend fun apiGetReactionMembers(rh: Long?, groupId: Long, itemId: Long, reaction: MsgReaction): List? { val userId = currentUserId("apiGetReactionMembers") val r = sendCmd(rh, CC.ApiGetReactionMembers(userId, groupId, itemId, reaction)) - if (r is CR.ReactionMembers) return r.memberReactions + if (r is API.Result && r.res is CR.ReactionMembers) return r.res.memberReactions Log.e(TAG, "apiGetReactionMembers bad response: ${r.responseType} ${r.details}") return null } suspend fun apiDeleteChatItems(rh: Long?, type: ChatType, id: Long, itemIds: List, mode: CIDeleteMode): List? { val r = sendCmd(rh, CC.ApiDeleteChatItem(type, id, itemIds, mode)) - if (r is CR.ChatItemsDeleted) return r.chatItemDeletions + if (r is API.Result && r.res is CR.ChatItemsDeleted) return r.res.chatItemDeletions Log.e(TAG, "apiDeleteChatItem bad response: ${r.responseType} ${r.details}") return null } suspend fun apiDeleteMemberChatItems(rh: Long?, groupId: Long, itemIds: List): List? { val r = sendCmd(rh, CC.ApiDeleteMemberChatItem(groupId, itemIds)) - if (r is CR.ChatItemsDeleted) return r.chatItemDeletions + if (r is API.Result && r.res is CR.ChatItemsDeleted) return r.res.chatItemDeletions Log.e(TAG, "apiDeleteMemberChatItem bad response: ${r.responseType} ${r.details}") return null } suspend fun apiArchiveReceivedReports(rh: Long?, groupId: Long): CR.GroupChatItemsDeleted? { val r = sendCmd(rh, CC.ApiArchiveReceivedReports(groupId)) - if (r is CR.GroupChatItemsDeleted) return r + if (r is API.Result && r.res is CR.GroupChatItemsDeleted) return r.res Log.e(TAG, "apiArchiveReceivedReports bad response: ${r.responseType} ${r.details}") return null } suspend fun apiDeleteReceivedReports(rh: Long?, groupId: Long, itemIds: List, mode: CIDeleteMode): List? { val r = sendCmd(rh, CC.ApiDeleteReceivedReports(groupId, itemIds, mode)) - if (r is CR.ChatItemsDeleted) return r.chatItemDeletions + if (r is API.Result && r.res is CR.ChatItemsDeleted) return r.res.chatItemDeletions Log.e(TAG, "apiDeleteReceivedReports bad response: ${r.responseType} ${r.details}") return null } @@ -1071,121 +1057,84 @@ object ChatController { suspend fun testProtoServer(rh: Long?, server: String): ProtocolTestFailure? { val userId = currentUserId("testProtoServer") val r = sendCmd(rh, CC.APITestProtoServer(userId, server)) - return when (r) { - is CR.ServerTestResult -> r.testFailure - else -> { - Log.e(TAG, "testProtoServer bad response: ${r.responseType} ${r.details}") - throw Exception("testProtoServer bad response: ${r.responseType} ${r.details}") - } - } + if (r is API.Result && r.res is CR.ServerTestResult) return r.res.testFailure + Log.e(TAG, "testProtoServer bad response: ${r.responseType} ${r.details}") + throw Exception("testProtoServer bad response: ${r.responseType} ${r.details}") } suspend fun getServerOperators(rh: Long?): ServerOperatorConditionsDetail? { val r = sendCmd(rh, CC.ApiGetServerOperators()) - - return when (r) { - is CR.ServerOperatorConditions -> r.conditions - else -> { - Log.e(TAG, "getServerOperators bad response: ${r.responseType} ${r.details}") - null - } - } + if (r is API.Result && r.res is CR.ServerOperatorConditions) return r.res.conditions + Log.e(TAG, "getServerOperators bad response: ${r.responseType} ${r.details}") + return null } suspend fun setServerOperators(rh: Long?, operators: List): ServerOperatorConditionsDetail? { val r = sendCmd(rh, CC.ApiSetServerOperators(operators)) - return when (r) { - is CR.ServerOperatorConditions -> r.conditions - else -> { - Log.e(TAG, "setServerOperators bad response: ${r.responseType} ${r.details}") - null - } - } + if (r is API.Result && r.res is CR.ServerOperatorConditions) return r.res.conditions + Log.e(TAG, "setServerOperators bad response: ${r.responseType} ${r.details}") + return null } suspend fun getUserServers(rh: Long?): List? { val userId = currentUserId("getUserServers") val r = sendCmd(rh, CC.ApiGetUserServers(userId)) - return when (r) { - is CR.UserServers -> r.userServers - else -> { - Log.e(TAG, "getUserServers bad response: ${r.responseType} ${r.details}") - null - } - } + if (r is API.Result && r.res is CR.UserServers) return r.res.userServers + Log.e(TAG, "getUserServers bad response: ${r.responseType} ${r.details}") + return null } suspend fun setUserServers(rh: Long?, userServers: List): Boolean { val userId = currentUserId("setUserServers") val r = sendCmd(rh, CC.ApiSetUserServers(userId, userServers)) - return when (r) { - is CR.CmdOk -> true - else -> { - AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.failed_to_save_servers), - "${r.responseType}: ${r.details}" - ) - Log.e(TAG, "setUserServers bad response: ${r.responseType} ${r.details}") - false - } - } + if (r.result is CR.CmdOk) return true + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.failed_to_save_servers), + "${r.responseType}: ${r.details}" + ) + Log.e(TAG, "setUserServers bad response: ${r.responseType} ${r.details}") + return false } suspend fun validateServers(rh: Long?, userServers: List): List? { val userId = currentUserId("validateServers") val r = sendCmd(rh, CC.ApiValidateServers(userId, userServers)) - return when (r) { - is CR.UserServersValidation -> r.serverErrors - else -> { - Log.e(TAG, "validateServers bad response: ${r.responseType} ${r.details}") - null - } - } + if (r is API.Result && r.res is CR.UserServersValidation) return r.res.serverErrors + Log.e(TAG, "validateServers bad response: ${r.responseType} ${r.details}") + return null } suspend fun getUsageConditions(rh: Long?): Triple? { val r = sendCmd(rh, CC.ApiGetUsageConditions()) - return when (r) { - is CR.UsageConditions -> Triple(r.usageConditions, r.conditionsText, r.acceptedConditions) - else -> { - Log.e(TAG, "getUsageConditions bad response: ${r.responseType} ${r.details}") - null - } - } + if (r is API.Result && r.res is CR.UsageConditions) return Triple(r.res.usageConditions, r.res.conditionsText, r.res.acceptedConditions) + Log.e(TAG, "getUsageConditions bad response: ${r.responseType} ${r.details}") + return null } suspend fun setConditionsNotified(rh: Long?, conditionsId: Long): Boolean { val r = sendCmd(rh, CC.ApiSetConditionsNotified(conditionsId)) - return when (r) { - is CR.CmdOk -> true - else -> { - Log.e(TAG, "setConditionsNotified bad response: ${r.responseType} ${r.details}") - false - } - } + if (r.result is CR.CmdOk) return true + Log.e(TAG, "setConditionsNotified bad response: ${r.responseType} ${r.details}") + return false } suspend fun acceptConditions(rh: Long?, conditionsId: Long, operatorIds: List): ServerOperatorConditionsDetail? { val r = sendCmd(rh, CC.ApiAcceptConditions(conditionsId, operatorIds)) - return when (r) { - is CR.ServerOperatorConditions -> r.conditions - else -> { - AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.error_accepting_operator_conditions), - "${r.responseType}: ${r.details}" - ) - Log.e(TAG, "acceptConditions bad response: ${r.responseType} ${r.details}") - null - } - } + if (r is API.Result && r.res is CR.ServerOperatorConditions) return r.res.conditions + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.error_accepting_operator_conditions), + "${r.responseType}: ${r.details}" + ) + Log.e(TAG, "acceptConditions bad response: ${r.responseType} ${r.details}") + return null } suspend fun getChatItemTTL(rh: Long?): ChatItemTTL { val userId = currentUserId("getChatItemTTL") val r = sendCmd(rh, CC.APIGetChatItemTTL(userId)) - if (r is CR.ChatItemTTL) { - return if (r.chatItemTTL != null) { - ChatItemTTL.fromSeconds(r.chatItemTTL) + if (r is API.Result && r.res is CR.ChatItemTTL) { + return if (r.res.chatItemTTL != null) { + ChatItemTTL.fromSeconds(r.res.chatItemTTL) } else { ChatItemTTL.None } @@ -1196,37 +1145,32 @@ object ChatController { suspend fun setChatItemTTL(rh: Long?, chatItemTTL: ChatItemTTL) { val userId = currentUserId("setChatItemTTL") val r = sendCmd(rh, CC.APISetChatItemTTL(userId, chatItemTTL.seconds)) - if (r is CR.CmdOk) return + if (r.result is CR.CmdOk) return throw Exception("failed to set chat item TTL: ${r.responseType} ${r.details}") } suspend fun setChatTTL(rh: Long?, chatType: ChatType, id: Long, chatItemTTL: ChatItemTTL?) { val userId = currentUserId("setChatTTL") val r = sendCmd(rh, CC.APISetChatTTL(userId, chatType, id, chatItemTTL?.seconds)) - if (r is CR.CmdOk) return + if (r.result is CR.CmdOk) return throw Exception("failed to set chat TTL: ${r.responseType} ${r.details}") } suspend fun apiSetNetworkConfig(cfg: NetCfg, showAlertOnError: Boolean = true, ctrl: ChatCtrl? = null): Boolean { val r = sendCmd(null, CC.APISetNetworkConfig(cfg), ctrl) - return when (r) { - is CR.CmdOk -> true - else -> { - Log.e(TAG, "apiSetNetworkConfig bad response: ${r.responseType} ${r.details}") - if (showAlertOnError) { - AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.error_setting_network_config), - "${r.responseType}: ${r.details}" - ) - } - false - } + if (r.result is CR.CmdOk) return true + Log.e(TAG, "apiSetNetworkConfig bad response: ${r.responseType} ${r.details}") + if (showAlertOnError) { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.error_setting_network_config), + "${r.responseType}: ${r.details}" + ) } + return false } suspend fun reconnectServer(rh: Long?, server: String): Boolean { val userId = currentUserId("reconnectServer") - return sendCommandOkResp(rh, CC.ReconnectServer(userId, server)) } @@ -1234,13 +1178,9 @@ object ChatController { suspend fun apiSetSettings(rh: Long?, type: ChatType, id: Long, settings: ChatSettings): Boolean { val r = sendCmd(rh, CC.APISetChatSettings(type, id, settings)) - return when (r) { - is CR.CmdOk -> true - else -> { - Log.e(TAG, "apiSetSettings bad response: ${r.responseType} ${r.details}") - false - } - } + if (r.result is CR.CmdOk) return true + Log.e(TAG, "apiSetSettings bad response: ${r.responseType} ${r.details}") + return false } suspend fun apiSetNetworkInfo(networkInfo: UserNetworkInfo): Boolean = @@ -1251,151 +1191,135 @@ object ChatController { suspend fun apiContactInfo(rh: Long?, contactId: Long): Pair? { val r = sendCmd(rh, CC.APIContactInfo(contactId)) - if (r is CR.ContactInfo) return r.connectionStats_ to r.customUserProfile + if (r is API.Result && r.res is CR.ContactInfo) return r.res.connectionStats_ to r.res.customUserProfile Log.e(TAG, "apiContactInfo bad response: ${r.responseType} ${r.details}") return null } suspend fun apiGroupMemberInfo(rh: Long?, groupId: Long, groupMemberId: Long): Pair? { val r = sendCmd(rh, CC.APIGroupMemberInfo(groupId, groupMemberId)) - if (r is CR.GroupMemberInfo) return Pair(r.member, r.connectionStats_) + if (r is API.Result && r.res is CR.GroupMemberInfo) return r.res.member to r.res.connectionStats_ Log.e(TAG, "apiGroupMemberInfo bad response: ${r.responseType} ${r.details}") return null } suspend fun apiContactQueueInfo(rh: Long?, contactId: Long): Pair? { val r = sendCmd(rh, CC.APIContactQueueInfo(contactId)) - if (r is CR.QueueInfoR) return Pair(r.rcvMsgInfo, r.queueInfo) + if (r is API.Result && r.res is CR.QueueInfoR) return r.res.rcvMsgInfo to r.res.queueInfo apiErrorAlert("apiContactQueueInfo", generalGetString(MR.strings.error), r) return null } suspend fun apiGroupMemberQueueInfo(rh: Long?, groupId: Long, groupMemberId: Long): Pair? { val r = sendCmd(rh, CC.APIGroupMemberQueueInfo(groupId, groupMemberId)) - if (r is CR.QueueInfoR) return Pair(r.rcvMsgInfo, r.queueInfo) + if (r is API.Result && r.res is CR.QueueInfoR) return r.res.rcvMsgInfo to r.res.queueInfo apiErrorAlert("apiGroupMemberQueueInfo", generalGetString(MR.strings.error), r) return null } suspend fun apiSwitchContact(rh: Long?, contactId: Long): ConnectionStats? { val r = sendCmd(rh, CC.APISwitchContact(contactId)) - if (r is CR.ContactSwitchStarted) return r.connectionStats + if (r is API.Result && r.res is CR.ContactSwitchStarted) return r.res.connectionStats apiErrorAlert("apiSwitchContact", generalGetString(MR.strings.error_changing_address), r) return null } suspend fun apiSwitchGroupMember(rh: Long?, groupId: Long, groupMemberId: Long): Pair? { val r = sendCmd(rh, CC.APISwitchGroupMember(groupId, groupMemberId)) - if (r is CR.GroupMemberSwitchStarted) return Pair(r.member, r.connectionStats) + if (r is API.Result && r.res is CR.GroupMemberSwitchStarted) return r.res.member to r.res.connectionStats apiErrorAlert("apiSwitchGroupMember", generalGetString(MR.strings.error_changing_address), r) return null } suspend fun apiAbortSwitchContact(rh: Long?, contactId: Long): ConnectionStats? { val r = sendCmd(rh, CC.APIAbortSwitchContact(contactId)) - if (r is CR.ContactSwitchAborted) return r.connectionStats + if (r is API.Result && r.res is CR.ContactSwitchAborted) return r.res.connectionStats apiErrorAlert("apiAbortSwitchContact", generalGetString(MR.strings.error_aborting_address_change), r) return null } suspend fun apiAbortSwitchGroupMember(rh: Long?, groupId: Long, groupMemberId: Long): Pair? { val r = sendCmd(rh, CC.APIAbortSwitchGroupMember(groupId, groupMemberId)) - if (r is CR.GroupMemberSwitchAborted) return Pair(r.member, r.connectionStats) + if (r is API.Result && r.res is CR.GroupMemberSwitchAborted) return r.res.member to r.res.connectionStats apiErrorAlert("apiAbortSwitchGroupMember", generalGetString(MR.strings.error_aborting_address_change), r) return null } suspend fun apiSyncContactRatchet(rh: Long?, contactId: Long, force: Boolean): ConnectionStats? { val r = sendCmd(rh, CC.APISyncContactRatchet(contactId, force)) - if (r is CR.ContactRatchetSyncStarted) return r.connectionStats + if (r is API.Result && r.res is CR.ContactRatchetSyncStarted) return r.res.connectionStats apiErrorAlert("apiSyncContactRatchet", generalGetString(MR.strings.error_synchronizing_connection), r) return null } suspend fun apiSyncGroupMemberRatchet(rh: Long?, groupId: Long, groupMemberId: Long, force: Boolean): Pair? { val r = sendCmd(rh, CC.APISyncGroupMemberRatchet(groupId, groupMemberId, force)) - if (r is CR.GroupMemberRatchetSyncStarted) return Pair(r.member, r.connectionStats) + if (r is API.Result && r.res is CR.GroupMemberRatchetSyncStarted) return r.res.member to r.res.connectionStats apiErrorAlert("apiSyncGroupMemberRatchet", generalGetString(MR.strings.error_synchronizing_connection), r) return null } suspend fun apiGetContactCode(rh: Long?, contactId: Long): Pair? { val r = sendCmd(rh, CC.APIGetContactCode(contactId)) - if (r is CR.ContactCode) return r.contact to r.connectionCode + if (r is API.Result && r.res is CR.ContactCode) return r.res.contact to r.res.connectionCode Log.e(TAG,"failed to get contact code: ${r.responseType} ${r.details}") return null } suspend fun apiGetGroupMemberCode(rh: Long?, groupId: Long, groupMemberId: Long): Pair? { val r = sendCmd(rh, CC.APIGetGroupMemberCode(groupId, groupMemberId)) - if (r is CR.GroupMemberCode) return r.member to r.connectionCode + if (r is API.Result && r.res is CR.GroupMemberCode) return r.res.member to r.res.connectionCode Log.e(TAG,"failed to get group member code: ${r.responseType} ${r.details}") return null } suspend fun apiVerifyContact(rh: Long?, contactId: Long, connectionCode: String?): Pair? { - return when (val r = sendCmd(rh, CC.APIVerifyContact(contactId, connectionCode))) { - is CR.ConnectionVerified -> r.verified to r.expectedCode - else -> null - } + val r = sendCmd(rh, CC.APIVerifyContact(contactId, connectionCode)) + if (r is API.Result && r.res is CR.ConnectionVerified) return r.res.verified to r.res.expectedCode + Log.e(TAG, "apiVerifyContact bad response: ${r.responseType} ${r.details}") + return null } suspend fun apiVerifyGroupMember(rh: Long?, groupId: Long, groupMemberId: Long, connectionCode: String?): Pair? { - return when (val r = sendCmd(rh, CC.APIVerifyGroupMember(groupId, groupMemberId, connectionCode))) { - is CR.ConnectionVerified -> r.verified to r.expectedCode - else -> null - } + val r = sendCmd(rh, CC.APIVerifyGroupMember(groupId, groupMemberId, connectionCode)) + if (r is API.Result && r.res is CR.ConnectionVerified) return r.res.verified to r.res.expectedCode + Log.e(TAG, "apiVerifyGroupMember bad response: ${r.responseType} ${r.details}") + return null } - - suspend fun apiAddContact(rh: Long?, incognito: Boolean): Pair?, (() -> Unit)?> { val userId = try { currentUserId("apiAddContact") } catch (e: Exception) { return null to null } val short = appPrefs.privacyShortLinks.get() val r = sendCmd(rh, CC.APIAddContact(userId, short = short, incognito = incognito)) - return when (r) { - is CR.Invitation -> (r.connLinkInvitation to r.connection) to null - else -> { - if (!(networkErrorAlert(r))) { - return null to { apiErrorAlert("apiAddContact", generalGetString(MR.strings.connection_error), r) } - } - null to null - } + return when { + r is API.Result && r.res is CR.Invitation -> (r.res.connLinkInvitation to r.res.connection) to null + !(networkErrorAlert(r)) -> null to { apiErrorAlert("apiAddContact", generalGetString(MR.strings.connection_error), r) } + else -> null to null } } suspend fun apiSetConnectionIncognito(rh: Long?, connId: Long, incognito: Boolean): PendingContactConnection? { val r = sendCmd(rh, CC.ApiSetConnectionIncognito(connId, incognito)) - - return when (r) { - is CR.ConnectionIncognitoUpdated -> r.toConnection - else -> { - if (!(networkErrorAlert(r))) { - apiErrorAlert("apiSetConnectionIncognito", generalGetString(MR.strings.error_sending_message), r) - } - null - } + if (r is API.Result && r.res is CR.ConnectionIncognitoUpdated) return r.res.toConnection + if (!(networkErrorAlert(r))) { + apiErrorAlert("apiSetConnectionIncognito", generalGetString(MR.strings.error_sending_message), r) } + return null } suspend fun apiChangeConnectionUser(rh: Long?, connId: Long, userId: Long): PendingContactConnection? { val r = sendCmd(rh, CC.ApiChangeConnectionUser(connId, userId)) - - return when (r) { - is CR.ConnectionUserChanged -> r.toConnection - else -> { - if (!(networkErrorAlert(r))) { - apiErrorAlert("apiChangeConnectionUser", generalGetString(MR.strings.error_sending_message), r) - } - null - } + if (r is API.Result && r.res is CR.ConnectionUserChanged) return r.res.toConnection + if (!(networkErrorAlert(r))) { + apiErrorAlert("apiChangeConnectionUser", generalGetString(MR.strings.error_sending_message), r) } + return null } suspend fun apiConnectPlan(rh: Long?, connLink: String): Pair? { val userId = kotlin.runCatching { currentUserId("apiConnectPlan") }.getOrElse { return null } val r = sendCmd(rh, CC.APIConnectPlan(userId, connLink)) - if (r is CR.CRConnectionPlan) return r.connLink to r.connectionPlan + if (r is API.Result && r.res is CR.CRConnectionPlan) return r.res.connLink to r.res.connectionPlan apiConnectResponseAlert(r) return null } @@ -1404,53 +1328,53 @@ object ChatController { val userId = try { currentUserId("apiConnect") } catch (e: Exception) { return null } val r = sendCmd(rh, CC.APIConnect(userId, incognito, connLink)) when { - r is CR.SentConfirmation -> return r.connection - r is CR.SentInvitation -> return r.connection - r is CR.ContactAlreadyExists -> + r is API.Result && r.res is CR.SentConfirmation -> return r.res.connection + r is API.Result && r.res is CR.SentInvitation -> return r.res.connection + r is API.Result && r.res is CR.ContactAlreadyExists -> AlertManager.shared.showAlertMsg( generalGetString(MR.strings.contact_already_exists), - String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), r.contact.displayName) + String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), r.res.contact.displayName) ) else -> apiConnectResponseAlert(r) } return null } - private fun apiConnectResponseAlert(r: CR) { + private fun apiConnectResponseAlert(r: API) { when { - r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorChat - && r.chatError.errorType is ChatErrorType.InvalidConnReq -> { + r is API.Error && r.err is ChatError.ChatErrorChat + && r.err.errorType is ChatErrorType.InvalidConnReq -> { AlertManager.shared.showAlertMsg( generalGetString(MR.strings.invalid_connection_link), generalGetString(MR.strings.please_check_correct_link_and_maybe_ask_for_a_new_one) ) } - r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorChat - && r.chatError.errorType is ChatErrorType.UnsupportedConnReq -> { + r is API.Error && r.err is ChatError.ChatErrorChat + && r.err.errorType is ChatErrorType.UnsupportedConnReq -> { AlertManager.shared.showAlertMsg( generalGetString(MR.strings.unsupported_connection_link), generalGetString(MR.strings.link_requires_newer_app_version_please_upgrade) ) } - r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent - && r.chatError.agentError is AgentErrorType.SMP - && r.chatError.agentError.smpErr is SMPErrorType.AUTH -> { + r is API.Error && r.err is ChatError.ChatErrorAgent + && r.err.agentError is AgentErrorType.SMP + && r.err.agentError.smpErr is SMPErrorType.AUTH -> { AlertManager.shared.showAlertMsg( generalGetString(MR.strings.connection_error_auth), generalGetString(MR.strings.connection_error_auth_desc) ) } - r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent - && r.chatError.agentError is AgentErrorType.SMP - && r.chatError.agentError.smpErr is SMPErrorType.BLOCKED -> { + r is API.Error && r.err is ChatError.ChatErrorAgent + && r.err.agentError is AgentErrorType.SMP + && r.err.agentError.smpErr is SMPErrorType.BLOCKED -> { showContentBlockedAlert( generalGetString(MR.strings.connection_error_blocked), - generalGetString(MR.strings.connection_error_blocked_desc).format(r.chatError.agentError.smpErr.blockInfo.reason.text), + generalGetString(MR.strings.connection_error_blocked_desc).format(r.err.agentError.smpErr.blockInfo.reason.text), ) } - r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent - && r.chatError.agentError is AgentErrorType.SMP - && r.chatError.agentError.smpErr is SMPErrorType.QUOTA -> { + r is API.Error && r.err is ChatError.ChatErrorAgent + && r.err.agentError is AgentErrorType.SMP + && r.err.agentError.smpErr is SMPErrorType.QUOTA -> { AlertManager.shared.showAlertMsg( generalGetString(MR.strings.connection_error_quota), generalGetString(MR.strings.connection_error_quota_desc) @@ -1467,15 +1391,11 @@ object ChatController { suspend fun apiConnectContactViaAddress(rh: Long?, incognito: Boolean, contactId: Long): Contact? { val userId = try { currentUserId("apiConnectContactViaAddress") } catch (e: Exception) { return null } val r = sendCmd(rh, CC.ApiConnectContactViaAddress(userId, incognito, contactId)) - when { - r is CR.SentInvitationToContact -> return r.contact - else -> { - if (!(networkErrorAlert(r))) { - apiErrorAlert("apiConnectContactViaAddress", generalGetString(MR.strings.connection_error), r) - } - return null - } + if (r is API.Result && r.res is CR.SentInvitationToContact) return r.res.contact + if (!(networkErrorAlert(r))) { + apiErrorAlert("apiConnectContactViaAddress", generalGetString(MR.strings.connection_error), r) } + return null } suspend fun deleteChat(chat: Chat, chatDeleteMode: ChatDeleteMode = ChatDeleteMode.Full(notify = true)) { @@ -1490,10 +1410,11 @@ object ChatController { suspend fun apiDeleteChat(rh: Long?, type: ChatType, id: Long, chatDeleteMode: ChatDeleteMode = ChatDeleteMode.Full(notify = true)): Boolean { chatModel.deletedChats.value += rh to type.type + id val r = sendCmd(rh, CC.ApiDeleteChat(type, id, chatDeleteMode)) + val res = r.result val success = when { - r is CR.ContactDeleted && type == ChatType.Direct -> true - r is CR.ContactConnectionDeleted && type == ChatType.ContactConnection -> true - r is CR.GroupDeletedUser && type == ChatType.Group -> true + res is CR.ContactDeleted && type == ChatType.Direct -> true + res is CR.ContactConnectionDeleted && type == ChatType.ContactConnection -> true + res is CR.GroupDeletedUser && type == ChatType.Group -> true else -> { val titleId = when (type) { ChatType.Direct -> MR.strings.error_deleting_contact @@ -1514,13 +1435,12 @@ object ChatController { val type = ChatType.Direct chatModel.deletedChats.value += rh to type.type + id val r = sendCmd(rh, CC.ApiDeleteChat(type, id, chatDeleteMode)) - val contact = when { - r is CR.ContactDeleted -> r.contact - else -> { - val titleId = MR.strings.error_deleting_contact - apiErrorAlert("apiDeleteChat", generalGetString(titleId), r) - null - } + val contact = if (r is API.Result && r.res is CR.ContactDeleted) { + r.res.contact + } else { + val titleId = MR.strings.error_deleting_contact + apiErrorAlert("apiDeleteChat", generalGetString(titleId), r) + null } chatModel.deletedChats.value -= rh to type.type + id return contact @@ -1544,7 +1464,7 @@ object ChatController { suspend fun apiClearChat(rh: Long?, type: ChatType, id: Long): ChatInfo? { val r = sendCmd(rh, CC.ApiClearChat(type, id)) - if (r is CR.ChatCleared) return r.chatInfo + if (r is API.Result && r.res is CR.ChatCleared) return r.res.chatInfo Log.e(TAG, "apiClearChat bad response: ${r.responseType} ${r.details}") return null } @@ -1552,9 +1472,9 @@ object ChatController { suspend fun apiUpdateProfile(rh: Long?, profile: Profile): Pair>? { val userId = kotlin.runCatching { currentUserId("apiUpdateProfile") }.getOrElse { return null } val r = sendCmd(rh, CC.ApiUpdateProfile(userId, profile)) - if (r is CR.UserProfileNoChange) return profile to emptyList() - if (r is CR.UserProfileUpdated) return r.toProfile to r.updateSummary.changedContacts - if (r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore && r.chatError.storeError is StoreError.DuplicateName) { + if (r is API.Result && r.res is CR.UserProfileNoChange) return profile to emptyList() + if (r is API.Result && r.res is CR.UserProfileUpdated) return r.res.toProfile to r.res.updateSummary.changedContacts + if (r is API.Error && r.err is ChatError.ChatErrorStore && r.err.storeError is StoreError.DuplicateName) { AlertManager.shared.showAlertMsg(generalGetString(MR.strings.failed_to_create_user_duplicate_title), generalGetString(MR.strings.failed_to_create_user_duplicate_desc)) } Log.e(TAG, "apiUpdateProfile bad response: ${r.responseType} ${r.details}") @@ -1563,51 +1483,52 @@ object ChatController { suspend fun apiSetProfileAddress(rh: Long?, on: Boolean): User? { val userId = try { currentUserId("apiSetProfileAddress") } catch (e: Exception) { return null } - return when (val r = sendCmd(rh, CC.ApiSetProfileAddress(userId, on))) { - is CR.UserProfileNoChange -> null - is CR.UserProfileUpdated -> r.user.updateRemoteHostId(rh) + val r = sendCmd(rh, CC.ApiSetProfileAddress(userId, on)) + return when { + r is API.Result && r.res is CR.UserProfileNoChange -> null + r is API.Result && r.res is CR.UserProfileUpdated -> r.res.user.updateRemoteHostId(rh) else -> throw Exception("failed to set profile address: ${r.responseType} ${r.details}") } } suspend fun apiSetContactPrefs(rh: Long?, contactId: Long, prefs: ChatPreferences): Contact? { val r = sendCmd(rh, CC.ApiSetContactPrefs(contactId, prefs)) - if (r is CR.ContactPrefsUpdated) return r.toContact + if (r is API.Result && r.res is CR.ContactPrefsUpdated) return r.res.toContact Log.e(TAG, "apiSetContactPrefs bad response: ${r.responseType} ${r.details}") return null } suspend fun apiSetContactAlias(rh: Long?, contactId: Long, localAlias: String): Contact? { val r = sendCmd(rh, CC.ApiSetContactAlias(contactId, localAlias)) - if (r is CR.ContactAliasUpdated) return r.toContact + if (r is API.Result && r.res is CR.ContactAliasUpdated) return r.res.toContact Log.e(TAG, "apiSetContactAlias bad response: ${r.responseType} ${r.details}") return null } suspend fun apiSetGroupAlias(rh: Long?, groupId: Long, localAlias: String): GroupInfo? { val r = sendCmd(rh, CC.ApiSetGroupAlias(groupId, localAlias)) - if (r is CR.GroupAliasUpdated) return r.toGroup + if (r is API.Result && r.res is CR.GroupAliasUpdated) return r.res.toGroup Log.e(TAG, "apiSetGroupAlias bad response: ${r.responseType} ${r.details}") return null } suspend fun apiSetConnectionAlias(rh: Long?, connId: Long, localAlias: String): PendingContactConnection? { val r = sendCmd(rh, CC.ApiSetConnectionAlias(connId, localAlias)) - if (r is CR.ConnectionAliasUpdated) return r.toConnection + if (r is API.Result && r.res is CR.ConnectionAliasUpdated) return r.res.toConnection Log.e(TAG, "apiSetConnectionAlias bad response: ${r.responseType} ${r.details}") return null } suspend fun apiSetUserUIThemes(rh: Long?, userId: Long, themes: ThemeModeOverrides?): Boolean { val r = sendCmd(rh, CC.ApiSetUserUIThemes(userId, themes)) - if (r is CR.CmdOk) return true + if (r.result is CR.CmdOk) return true Log.e(TAG, "apiSetUserUIThemes bad response: ${r.responseType} ${r.details}") return false } suspend fun apiSetChatUIThemes(rh: Long?, chatId: ChatId, themes: ThemeModeOverrides?): Boolean { val r = sendCmd(rh, CC.ApiSetChatUIThemes(chatId, themes)) - if (r is CR.CmdOk) return true + if (r.result is CR.CmdOk) return true Log.e(TAG, "apiSetChatUIThemes bad response: ${r.responseType} ${r.details}") return false } @@ -1615,21 +1536,17 @@ object ChatController { suspend fun apiCreateUserAddress(rh: Long?, short: Boolean): CreatedConnLink? { val userId = kotlin.runCatching { currentUserId("apiCreateUserAddress") }.getOrElse { return null } val r = sendCmd(rh, CC.ApiCreateMyAddress(userId, short)) - return when (r) { - is CR.UserContactLinkCreated -> r.connLinkContact - else -> { - if (!(networkErrorAlert(r))) { - apiErrorAlert("apiCreateUserAddress", generalGetString(MR.strings.error_creating_address), r) - } - null - } + if (r is API.Result && r.res is CR.UserContactLinkCreated) return r.res.connLinkContact + if (!(networkErrorAlert(r))) { + apiErrorAlert("apiCreateUserAddress", generalGetString(MR.strings.error_creating_address), r) } + return null } suspend fun apiDeleteUserAddress(rh: Long?): User? { val userId = try { currentUserId("apiDeleteUserAddress") } catch (e: Exception) { return null } val r = sendCmd(rh, CC.ApiDeleteMyAddress(userId)) - if (r is CR.UserContactLinkDeleted) return r.user.updateRemoteHostId(rh) + if (r is API.Result && r.res is CR.UserContactLinkDeleted) return r.res.user.updateRemoteHostId(rh) Log.e(TAG, "apiDeleteUserAddress bad response: ${r.responseType} ${r.details}") return null } @@ -1637,9 +1554,9 @@ object ChatController { private suspend fun apiGetUserAddress(rh: Long?): UserContactLinkRec? { val userId = kotlin.runCatching { currentUserId("apiGetUserAddress") }.getOrElse { return null } val r = sendCmd(rh, CC.ApiShowMyAddress(userId)) - if (r is CR.UserContactLink) return r.contactLink - if (r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore - && r.chatError.storeError is StoreError.UserContactLinkNotFound + if (r is API.Result && r.res is CR.UserContactLink) return r.res.contactLink + if (r is API.Error && r.err is ChatError.ChatErrorStore + && r.err.storeError is StoreError.UserContactLinkNotFound ) { return null } @@ -1650,9 +1567,9 @@ object ChatController { suspend fun userAddressAutoAccept(rh: Long?, autoAccept: AutoAccept?): UserContactLinkRec? { val userId = kotlin.runCatching { currentUserId("userAddressAutoAccept") }.getOrElse { return null } val r = sendCmd(rh, CC.ApiAddressAutoAccept(userId, autoAccept)) - if (r is CR.UserContactLinkUpdated) return r.contactLink - if (r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore - && r.chatError.storeError is StoreError.UserContactLinkNotFound + if (r is API.Result && r.res is CR.UserContactLinkUpdated) return r.res.contactLink + if (r is API.Error && r.err is ChatError.ChatErrorStore + && r.err.storeError is StoreError.UserContactLinkNotFound ) { return null } @@ -1663,10 +1580,10 @@ object ChatController { suspend fun apiAcceptContactRequest(rh: Long?, incognito: Boolean, contactReqId: Long): Contact? { val r = sendCmd(rh, CC.ApiAcceptContact(incognito, contactReqId)) return when { - r is CR.AcceptingContactRequest -> r.contact - r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent - && r.chatError.agentError is AgentErrorType.SMP - && r.chatError.agentError.smpErr is SMPErrorType.AUTH -> { + r is API.Result && r.res is CR.AcceptingContactRequest -> r.res.contact + r is API.Error && r.err is ChatError.ChatErrorAgent + && r.err.agentError is AgentErrorType.SMP + && r.err.agentError.smpErr is SMPErrorType.AUTH -> { AlertManager.shared.showAlertMsg( generalGetString(MR.strings.connection_error_auth), generalGetString(MR.strings.sender_may_have_deleted_the_connection_request) @@ -1684,89 +1601,89 @@ object ChatController { suspend fun apiRejectContactRequest(rh: Long?, contactReqId: Long): Boolean { val r = sendCmd(rh, CC.ApiRejectContact(contactReqId)) - if (r is CR.ContactRequestRejected) return true + if (r is API.Result && r.res is CR.ContactRequestRejected) return true Log.e(TAG, "apiRejectContactRequest bad response: ${r.responseType} ${r.details}") return false } suspend fun apiGetCallInvitations(rh: Long?): List { val r = sendCmd(rh, CC.ApiGetCallInvitations()) - if (r is CR.CallInvitations) return r.callInvitations + if (r is API.Result && r.res is CR.CallInvitations) return r.res.callInvitations Log.e(TAG, "apiGetCallInvitations bad response: ${r.responseType} ${r.details}") return emptyList() } suspend fun apiSendCallInvitation(rh: Long?, contact: Contact, callType: CallType): Boolean { val r = sendCmd(rh, CC.ApiSendCallInvitation(contact, callType)) - return r is CR.CmdOk + return r.result is CR.CmdOk } suspend fun apiRejectCall(rh: Long?, contact: Contact): Boolean { val r = sendCmd(rh, CC.ApiRejectCall(contact)) - return r is CR.CmdOk + return r.result is CR.CmdOk } suspend fun apiSendCallOffer(rh: Long?, contact: Contact, rtcSession: String, rtcIceCandidates: String, media: CallMediaType, capabilities: CallCapabilities): Boolean { val webRtcSession = WebRTCSession(rtcSession, rtcIceCandidates) val callOffer = WebRTCCallOffer(CallType(media, capabilities), webRtcSession) val r = sendCmd(rh, CC.ApiSendCallOffer(contact, callOffer)) - return r is CR.CmdOk + return r.result is CR.CmdOk } suspend fun apiSendCallAnswer(rh: Long?, contact: Contact, rtcSession: String, rtcIceCandidates: String): Boolean { val answer = WebRTCSession(rtcSession, rtcIceCandidates) val r = sendCmd(rh, CC.ApiSendCallAnswer(contact, answer)) - return r is CR.CmdOk + return r.result is CR.CmdOk } suspend fun apiSendCallExtraInfo(rh: Long?, contact: Contact, rtcIceCandidates: String): Boolean { val extraInfo = WebRTCExtraInfo(rtcIceCandidates) val r = sendCmd(rh, CC.ApiSendCallExtraInfo(contact, extraInfo)) - return r is CR.CmdOk + return r.result is CR.CmdOk } suspend fun apiEndCall(rh: Long?, contact: Contact): Boolean { val r = sendCmd(rh, CC.ApiEndCall(contact)) - return r is CR.CmdOk + return r.result is CR.CmdOk } suspend fun apiCallStatus(rh: Long?, contact: Contact, status: WebRTCCallStatus): Boolean { val r = sendCmd(rh, CC.ApiCallStatus(contact, status)) - return r is CR.CmdOk + return r.result is CR.CmdOk } suspend fun apiGetNetworkStatuses(rh: Long?): List? { val r = sendCmd(rh, CC.ApiGetNetworkStatuses()) - if (r is CR.NetworkStatuses) return r.networkStatuses + if (r is API.Result && r.res is CR.NetworkStatuses) return r.res.networkStatuses Log.e(TAG, "apiGetNetworkStatuses bad response: ${r.responseType} ${r.details}") return null } suspend fun apiChatRead(rh: Long?, type: ChatType, id: Long): Boolean { val r = sendCmd(rh, CC.ApiChatRead(type, id)) - if (r is CR.CmdOk) return true + if (r.result is CR.CmdOk) return true Log.e(TAG, "apiChatRead bad response: ${r.responseType} ${r.details}") return false } suspend fun apiChatItemsRead(rh: Long?, type: ChatType, id: Long, itemIds: List): Boolean { val r = sendCmd(rh, CC.ApiChatItemsRead(type, id, itemIds)) - if (r is CR.CmdOk) return true + if (r.result is CR.CmdOk) return true Log.e(TAG, "apiChatItemsRead bad response: ${r.responseType} ${r.details}") return false } suspend fun apiChatUnread(rh: Long?, type: ChatType, id: Long, unreadChat: Boolean): Boolean { val r = sendCmd(rh, CC.ApiChatUnread(type, id, unreadChat)) - if (r is CR.CmdOk) return true + if (r.result is CR.CmdOk) return true Log.e(TAG, "apiChatUnread bad response: ${r.responseType} ${r.details}") return false } suspend fun uploadStandaloneFile(user: UserLike, file: CryptoFile, ctrl: ChatCtrl? = null): Pair { val r = sendCmd(null, CC.ApiUploadStandaloneFile(user.userId, file), ctrl) - return if (r is CR.SndStandaloneFileCreated) { - r.fileTransferMeta to null + return if (r is API.Result && r.res is CR.SndStandaloneFileCreated) { + r.res.fileTransferMeta to null } else { Log.e(TAG, "uploadStandaloneFile error: $r") null to r.toString() @@ -1775,8 +1692,8 @@ object ChatController { suspend fun downloadStandaloneFile(user: UserLike, url: String, file: CryptoFile, ctrl: ChatCtrl? = null): Pair { val r = sendCmd(null, CC.ApiDownloadStandaloneFile(user.userId, url, file), ctrl) - return if (r is CR.RcvStandaloneFileCreated) { - r.rcvFileTransfer to null + return if (r is API.Result && r.res is CR.RcvStandaloneFileCreated) { + r.res.rcvFileTransfer to null } else { Log.e(TAG, "downloadStandaloneFile error: $r") null to r.toString() @@ -1785,8 +1702,8 @@ object ChatController { suspend fun standaloneFileInfo(url: String, ctrl: ChatCtrl? = null): MigrationFileLinkData? { val r = sendCmd(null, CC.ApiStandaloneFileInfo(url), ctrl) - return if (r is CR.StandaloneFileInfo) { - r.fileMeta + return if (r is API.Result && r.res is CR.StandaloneFileInfo) { + r.res.fileMeta } else { Log.e(TAG, "standaloneFileInfo error: $r") null @@ -1796,7 +1713,7 @@ object ChatController { suspend fun receiveFiles(rhId: Long?, user: UserLike, fileIds: List, userApprovedRelays: Boolean = false, auto: Boolean = false) { val fileIdsToApprove = mutableListOf() val srvsToApprove = mutableSetOf() - val otherFileErrs = mutableListOf() + val otherFileErrs = mutableListOf() for (fileId in fileIds) { val r = sendCmd( @@ -1807,10 +1724,10 @@ object ChatController { inline = null ) ) - if (r is CR.RcvFileAccepted) { - chatItemSimpleUpdate(rhId, user, r.chatItem) + if (r is API.Result && r.res is CR.RcvFileAccepted) { + chatItemSimpleUpdate(rhId, user, r.res.chatItem) } else { - val maybeChatError = chatError(r) + val maybeChatError = apiChatErrorType(r) if (maybeChatError is ChatErrorType.FileNotApproved) { fileIdsToApprove.add(maybeChatError.fileId) srvsToApprove.addAll(maybeChatError.unknownServers.map { serverHostname(it) }) @@ -1838,21 +1755,19 @@ object ChatController { } ) } else if (otherFileErrs.size == 1) { // If there is a single other error, we differentiate on it - when (val errCR = otherFileErrs.first()) { - is CR.RcvFileAcceptedSndCancelled -> { - Log.d(TAG, "receiveFiles error: sender cancelled file transfer") - AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.cannot_receive_file), - generalGetString(MR.strings.sender_cancelled_file_transfer) - ) - } - else -> { - val maybeChatError = chatError(errCR) - if (maybeChatError is ChatErrorType.FileCancelled || maybeChatError is ChatErrorType.FileAlreadyReceiving) { - Log.d(TAG, "receiveFiles ignoring FileCancelled or FileAlreadyReceiving error") - } else { - apiErrorAlert("receiveFiles", generalGetString(MR.strings.error_receiving_file), errCR) - } + val errCR = otherFileErrs.first() + if (errCR is API.Result && errCR.res is CR.RcvFileAcceptedSndCancelled) { + Log.d(TAG, "receiveFiles error: sender cancelled file transfer") + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.cannot_receive_file), + generalGetString(MR.strings.sender_cancelled_file_transfer) + ) + } else { + val maybeChatError = apiChatErrorType(errCR) + if (maybeChatError is ChatErrorType.FileCancelled || maybeChatError is ChatErrorType.FileAlreadyReceiving) { + Log.d(TAG, "receiveFiles ignoring FileCancelled or FileAlreadyReceiving error") + } else { + apiErrorAlert("receiveFiles", generalGetString(MR.strings.error_receiving_file), errCR) } } } else if (otherFileErrs.size > 1) { // If there are multiple other errors, we show general alert @@ -1868,7 +1783,7 @@ object ChatController { private fun showFilesToApproveAlert( srvsToApprove: Set, - otherFileErrs: List, + otherFileErrs: List, approveFiles: (() -> Unit) ) { val srvsToApproveStr = srvsToApprove.sorted().joinToString(separator = ", ") @@ -1931,9 +1846,9 @@ object ChatController { suspend fun apiCancelFile(rh: Long?, fileId: Long, ctrl: ChatCtrl? = null): AChatItem? { val r = sendCmd(rh, CC.CancelFile(fileId), ctrl) - return when (r) { - is CR.SndFileCancelled -> r.chatItem_ - is CR.RcvFileCancelled -> r.chatItem_ + return when { + r is API.Result && r.res is CR.SndFileCancelled -> r.res.chatItem_ + r is API.Result && r.res is CR.RcvFileCancelled -> r.res.chatItem_ else -> { Log.d(TAG, "apiCancelFile bad response: ${r.responseType} ${r.details}") null @@ -1944,33 +1859,29 @@ object ChatController { suspend fun apiNewGroup(rh: Long?, incognito: Boolean, groupProfile: GroupProfile): GroupInfo? { val userId = kotlin.runCatching { currentUserId("apiNewGroup") }.getOrElse { return null } val r = sendCmd(rh, CC.ApiNewGroup(userId, incognito, groupProfile)) - if (r is CR.GroupCreated) return r.groupInfo + if (r is API.Result && r.res is CR.GroupCreated) return r.res.groupInfo Log.e(TAG, "apiNewGroup bad response: ${r.responseType} ${r.details}") return null } suspend fun apiAddMember(rh: Long?, groupId: Long, contactId: Long, memberRole: GroupMemberRole): GroupMember? { val r = sendCmd(rh, CC.ApiAddMember(groupId, contactId, memberRole)) - return when (r) { - is CR.SentGroupInvitation -> r.member - else -> { - if (!(networkErrorAlert(r))) { - apiErrorAlert("apiAddMember", generalGetString(MR.strings.error_adding_members), r) - } - null - } + if (r is API.Result && r.res is CR.SentGroupInvitation) return r.res.member + if (!(networkErrorAlert(r))) { + apiErrorAlert("apiAddMember", generalGetString(MR.strings.error_adding_members), r) } + return null } suspend fun apiJoinGroup(rh: Long?, groupId: Long) { val r = sendCmd(rh, CC.ApiJoinGroup(groupId)) - when (r) { - is CR.UserAcceptedGroupSent -> + when { + r is API.Result && r.res is CR.UserAcceptedGroupSent -> withContext(Dispatchers.Main) { - chatModel.chatsContext.updateGroup(rh, r.groupInfo) + chatModel.chatsContext.updateGroup(rh, r.res.groupInfo) } - is CR.ChatCmdError -> { - val e = r.chatError + r is API.Error -> { + val e = r.err suspend fun deleteGroup() { if (apiDeleteChat(rh, ChatType.Group, groupId)) { withContext(Dispatchers.Main) { chatModel.chatsContext.removeChat(rh, "#$groupId") } } } @@ -1988,58 +1899,53 @@ object ChatController { } } - suspend fun apiRemoveMembers(rh: Long?, groupId: Long, memberIds: List, withMessages: Boolean = false): List? = - when (val r = sendCmd(rh, CC.ApiRemoveMembers(groupId, memberIds, withMessages))) { - is CR.UserDeletedMembers -> r.members - else -> { - if (!(networkErrorAlert(r))) { - apiErrorAlert("apiRemoveMembers", generalGetString(MR.strings.error_removing_member), r) - } - null - } + suspend fun apiRemoveMembers(rh: Long?, groupId: Long, memberIds: List, withMessages: Boolean = false): List? { + val r = sendCmd(rh, CC.ApiRemoveMembers(groupId, memberIds, withMessages)) + if (r is API.Result && r.res is CR.UserDeletedMembers) return r.res.members + if (!(networkErrorAlert(r))) { + apiErrorAlert("apiRemoveMembers", generalGetString(MR.strings.error_removing_member), r) } + return null + } - suspend fun apiMembersRole(rh: Long?, groupId: Long, memberIds: List, memberRole: GroupMemberRole): List = - when (val r = sendCmd(rh, CC.ApiMembersRole(groupId, memberIds, memberRole))) { - is CR.MembersRoleUser -> r.members - else -> { - if (!(networkErrorAlert(r))) { - apiErrorAlert("apiMembersRole", generalGetString(MR.strings.error_changing_role), r) - } - throw Exception("failed to change member role: ${r.responseType} ${r.details}") - } + suspend fun apiMembersRole(rh: Long?, groupId: Long, memberIds: List, memberRole: GroupMemberRole): List { + val r = sendCmd(rh, CC.ApiMembersRole(groupId, memberIds, memberRole)) + if (r is API.Result && r.res is CR.MembersRoleUser) return r.res.members + if (!(networkErrorAlert(r))) { + apiErrorAlert("apiMembersRole", generalGetString(MR.strings.error_changing_role), r) } + throw Exception("failed to change member role: ${r.responseType} ${r.details}") + } - suspend fun apiBlockMembersForAll(rh: Long?, groupId: Long, memberIds: List, blocked: Boolean): List = - when (val r = sendCmd(rh, CC.ApiBlockMembersForAll(groupId, memberIds, blocked))) { - is CR.MembersBlockedForAllUser -> r.members - else -> { - if (!(networkErrorAlert(r))) { - apiErrorAlert("apiBlockMembersForAll", generalGetString(MR.strings.error_blocking_member_for_all), r) - } - throw Exception("failed to block member for all: ${r.responseType} ${r.details}") - } + suspend fun apiBlockMembersForAll(rh: Long?, groupId: Long, memberIds: List, blocked: Boolean): List { + val r = sendCmd(rh, CC.ApiBlockMembersForAll(groupId, memberIds, blocked)) + if (r is API.Result && r.res is CR.MembersBlockedForAllUser) return r.res.members + if (!(networkErrorAlert(r))) { + apiErrorAlert("apiBlockMembersForAll", generalGetString(MR.strings.error_blocking_member_for_all), r) } + throw Exception("failed to block member for all: ${r.responseType} ${r.details}") + } suspend fun apiLeaveGroup(rh: Long?, groupId: Long): GroupInfo? { val r = sendCmd(rh, CC.ApiLeaveGroup(groupId)) - if (r is CR.LeftMemberUser) return r.groupInfo + if (r is API.Result && r.res is CR.LeftMemberUser) return r.res.groupInfo Log.e(TAG, "apiLeaveGroup bad response: ${r.responseType} ${r.details}") return null } suspend fun apiListMembers(rh: Long?, groupId: Long): List { val r = sendCmd(rh, CC.ApiListMembers(groupId)) - if (r is CR.GroupMembers) return r.group.members + if (r is API.Result && r.res is CR.GroupMembers) return r.res.group.members Log.e(TAG, "apiListMembers bad response: ${r.responseType} ${r.details}") return emptyList() } suspend fun apiUpdateGroup(rh: Long?, groupId: Long, groupProfile: GroupProfile): GroupInfo? { - return when (val r = sendCmd(rh, CC.ApiUpdateGroupProfile(groupId, groupProfile))) { - is CR.GroupUpdated -> r.toGroup - is CR.ChatCmdError -> { - AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_saving_group_profile), "$r.chatError") + val r = sendCmd(rh, CC.ApiUpdateGroupProfile(groupId, groupProfile)) + return when { + r is API.Result && r.res is CR.GroupUpdated -> r.res.toGroup + r is API.Error -> { + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_saving_group_profile), "$r.err") null } else -> { @@ -2055,73 +1961,55 @@ object ChatController { suspend fun apiCreateGroupLink(rh: Long?, groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): Pair? { val short = appPrefs.privacyShortLinks.get() - return when (val r = sendCmd(rh, CC.APICreateGroupLink(groupId, memberRole, short))) { - is CR.GroupLinkCreated -> r.connLinkContact to r.memberRole - else -> { - if (!(networkErrorAlert(r))) { - apiErrorAlert("apiCreateGroupLink", generalGetString(MR.strings.error_creating_link_for_group), r) - } - null - } + val r = sendCmd(rh, CC.APICreateGroupLink(groupId, memberRole, short)) + if (r is API.Result && r.res is CR.GroupLinkCreated) return r.res.connLinkContact to r.res.memberRole + if (!(networkErrorAlert(r))) { + apiErrorAlert("apiCreateGroupLink", generalGetString(MR.strings.error_creating_link_for_group), r) } + return null } suspend fun apiGroupLinkMemberRole(rh: Long?, groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): Pair? { - return when (val r = sendCmd(rh, CC.APIGroupLinkMemberRole(groupId, memberRole))) { - is CR.GroupLink -> r.connLinkContact to r.memberRole - else -> { - if (!(networkErrorAlert(r))) { - apiErrorAlert("apiGroupLinkMemberRole", generalGetString(MR.strings.error_updating_link_for_group), r) - } - null - } + val r = sendCmd(rh, CC.APIGroupLinkMemberRole(groupId, memberRole)) + if (r is API.Result && r.res is CR.GroupLink) return r.res.connLinkContact to r.res.memberRole + if (!(networkErrorAlert(r))) { + apiErrorAlert("apiGroupLinkMemberRole", generalGetString(MR.strings.error_updating_link_for_group), r) } + return null } suspend fun apiDeleteGroupLink(rh: Long?, groupId: Long): Boolean { - return when (val r = sendCmd(rh, CC.APIDeleteGroupLink(groupId))) { - is CR.GroupLinkDeleted -> true - else -> { - if (!(networkErrorAlert(r))) { - apiErrorAlert("apiDeleteGroupLink", generalGetString(MR.strings.error_deleting_link_for_group), r) - } - false - } + val r = sendCmd(rh, CC.APIDeleteGroupLink(groupId)) + if (r is API.Result && r.res is CR.GroupLinkDeleted) return true + if (!(networkErrorAlert(r))) { + apiErrorAlert("apiDeleteGroupLink", generalGetString(MR.strings.error_deleting_link_for_group), r) } + return false } suspend fun apiGetGroupLink(rh: Long?, groupId: Long): Pair? { - return when (val r = sendCmd(rh, CC.APIGetGroupLink(groupId))) { - is CR.GroupLink -> r.connLinkContact to r.memberRole - else -> { - Log.e(TAG, "apiGetGroupLink bad response: ${r.responseType} ${r.details}") - null - } - } + val r = sendCmd(rh, CC.APIGetGroupLink(groupId)) + if (r is API.Result && r.res is CR.GroupLink) return r.res.connLinkContact to r.res.memberRole + Log.e(TAG, "apiGetGroupLink bad response: ${r.responseType} ${r.details}") + return null } suspend fun apiCreateMemberContact(rh: Long?, groupId: Long, groupMemberId: Long): Contact? { - return when (val r = sendCmd(rh, CC.APICreateMemberContact(groupId, groupMemberId))) { - is CR.NewMemberContact -> r.contact - else -> { - if (!(networkErrorAlert(r))) { - apiErrorAlert("apiCreateMemberContact", generalGetString(MR.strings.error_creating_member_contact), r) - } - null - } + val r = sendCmd(rh, CC.APICreateMemberContact(groupId, groupMemberId)) + if (r is API.Result && r.res is CR.NewMemberContact) return r.res.contact + if (!(networkErrorAlert(r))) { + apiErrorAlert("apiCreateMemberContact", generalGetString(MR.strings.error_creating_member_contact), r) } + return null } suspend fun apiSendMemberContactInvitation(rh: Long?, contactId: Long, mc: MsgContent): Contact? { - return when (val r = sendCmd(rh, CC.APISendMemberContactInvitation(contactId, mc))) { - is CR.NewMemberContactSentInv -> r.contact - else -> { - if (!(networkErrorAlert(r))) { - apiErrorAlert("apiSendMemberContactInvitation", generalGetString(MR.strings.error_sending_message_contact_invitation), r) - } - null - } + val r = sendCmd(rh, CC.APISendMemberContactInvitation(contactId, mc)) + if (r is API.Result && r.res is CR.NewMemberContactSentInv) return r.res.contact + if (!(networkErrorAlert(r))) { + apiErrorAlert("apiSendMemberContactInvitation", generalGetString(MR.strings.error_sending_message_contact_invitation), r) } + return null } suspend fun allowFeatureToContact(rh: Long?, contact: Contact, feature: ChatFeature, param: Int? = null) { @@ -2138,7 +2026,7 @@ object ChatController { suspend fun listRemoteHosts(): List? { val r = sendCmd(null, CC.ListRemoteHosts()) - if (r is CR.RemoteHostList) return r.remoteHosts + if (r is API.Result && r.res is CR.RemoteHostList) return r.res.remoteHosts apiErrorAlert("listRemoteHosts", generalGetString(MR.strings.error_alert_title), r) return null } @@ -2151,14 +2039,14 @@ object ChatController { suspend fun startRemoteHost(rhId: Long?, multicast: Boolean = true, address: RemoteCtrlAddress?, port: Int?): CR.RemoteHostStarted? { val r = sendCmd(null, CC.StartRemoteHost(rhId, multicast, address, port)) - if (r is CR.RemoteHostStarted) return r + if (r is API.Result && r.res is CR.RemoteHostStarted) return r.res apiErrorAlert("startRemoteHost", generalGetString(MR.strings.error_alert_title), r) return null } suspend fun switchRemoteHost (rhId: Long?): RemoteHostInfo? { val r = sendCmd(null, CC.SwitchRemoteHost(rhId)) - if (r is CR.CurrentRemoteHost) return r.remoteHost_ + if (r is API.Result && r.res is CR.CurrentRemoteHost) return r.res.remoteHost_ apiErrorAlert("switchRemoteHost", generalGetString(MR.strings.error_alert_title), r) return null } @@ -2180,45 +2068,49 @@ object ChatController { suspend fun storeRemoteFile(rhId: Long, storeEncrypted: Boolean?, localPath: String): CryptoFile? { val r = sendCmd(null, CC.StoreRemoteFile(rhId, storeEncrypted, localPath)) - if (r is CR.RemoteFileStored) return r.remoteFileSource + if (r is API.Result && r.res is CR.RemoteFileStored) return r.res.remoteFileSource apiErrorAlert("storeRemoteFile", generalGetString(MR.strings.error_alert_title), r) return null } - suspend fun getRemoteFile(rhId: Long, file: RemoteFile): Boolean = sendCmd(null, CC.GetRemoteFile(rhId, file)) is CR.CmdOk + suspend fun getRemoteFile(rhId: Long, file: RemoteFile): Boolean = sendCmd(null, CC.GetRemoteFile(rhId, file)).result is CR.CmdOk - suspend fun connectRemoteCtrl(desktopAddress: String): Pair { + suspend fun connectRemoteCtrl(desktopAddress: String): Pair { val r = sendCmd(null, CC.ConnectRemoteCtrl(desktopAddress)) - return if (r is CR.RemoteCtrlConnecting) SomeRemoteCtrl(r.remoteCtrl_, r.ctrlAppInfo, r.appVersion) to null - else if (r is CR.ChatCmdError) null to r - else { - apiErrorAlert("connectRemoteCtrl", generalGetString(MR.strings.error_alert_title), r) - null to null + return when { + r is API.Result && r.res is CR.RemoteCtrlConnecting -> SomeRemoteCtrl(r.res.remoteCtrl_, r.res.ctrlAppInfo, r.res.appVersion) to null + r is API.Error -> null to r.err + else -> { + apiErrorAlert("connectRemoteCtrl", generalGetString(MR.strings.error_alert_title), r) + null to null + } } } suspend fun findKnownRemoteCtrl(): Boolean = sendCommandOkResp(null, CC.FindKnownRemoteCtrl()) - suspend fun confirmRemoteCtrl(rcId: Long): Pair { + suspend fun confirmRemoteCtrl(rcId: Long): Pair { val r = sendCmd(null, CC.ConfirmRemoteCtrl(remoteCtrlId = rcId)) - return if (r is CR.RemoteCtrlConnecting) SomeRemoteCtrl(r.remoteCtrl_, r.ctrlAppInfo, r.appVersion) to null - else if (r is CR.ChatCmdError) null to r - else { - apiErrorAlert("confirmRemoteCtrl", generalGetString(MR.strings.error_alert_title), r) - null to null + return when { + r is API.Result && r.res is CR.RemoteCtrlConnecting -> SomeRemoteCtrl(r.res.remoteCtrl_, r.res.ctrlAppInfo, r.res.appVersion) to null + r is API.Error -> null to r.err + else -> { + apiErrorAlert("confirmRemoteCtrl", generalGetString(MR.strings.error_alert_title), r) + null to null + } } } suspend fun verifyRemoteCtrlSession(sessionCode: String): RemoteCtrlInfo? { val r = sendCmd(null, CC.VerifyRemoteCtrlSession(sessionCode)) - if (r is CR.RemoteCtrlConnected) return r.remoteCtrl + if (r is API.Result && r.res is CR.RemoteCtrlConnected) return r.res.remoteCtrl apiErrorAlert("verifyRemoteCtrlSession", generalGetString(MR.strings.error_alert_title), r) return null } suspend fun listRemoteCtrls(): List? { val r = sendCmd(null, CC.ListRemoteCtrls()) - if (r is CR.RemoteCtrlList) return r.remoteCtrls + if (r is API.Result && r.res is CR.RemoteCtrlList) return r.res.remoteCtrls apiErrorAlert("listRemoteCtrls", generalGetString(MR.strings.error_alert_title), r) return null } @@ -2229,72 +2121,71 @@ object ChatController { private suspend fun sendCommandOkResp(rh: Long?, cmd: CC, ctrl: ChatCtrl? = null): Boolean { val r = sendCmd(rh, cmd, ctrl) - val ok = r is CR.CmdOk + val ok = r is API.Result && r.res is CR.CmdOk if (!ok) apiErrorAlert(cmd.cmdType, generalGetString(MR.strings.error_alert_title), r) return ok } suspend fun apiGetVersion(): CoreVersionInfo? { val r = sendCmd(null, CC.ShowVersion()) - return if (r is CR.VersionInfo) { - r.versionInfo - } else { - Log.e(TAG, "apiGetVersion bad response: ${r.responseType} ${r.details}") - null - } + if (r is API.Result && r.res is CR.VersionInfo) return r.res.versionInfo + Log.e(TAG, "apiGetVersion bad response: ${r.responseType} ${r.details}") + return null } - private fun networkErrorAlert(r: CR): Boolean { + private fun networkErrorAlert(r: API): Boolean { + if (r !is API.Error) return false + val e = r.err return when { - r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent - && r.chatError.agentError is AgentErrorType.BROKER - && r.chatError.agentError.brokerErr is BrokerErrorType.TIMEOUT -> { + e is ChatError.ChatErrorAgent + && e.agentError is AgentErrorType.BROKER + && e.agentError.brokerErr is BrokerErrorType.TIMEOUT -> { AlertManager.shared.showAlertMsg( generalGetString(MR.strings.connection_timeout), - String.format(generalGetString(MR.strings.network_error_desc), serverHostname(r.chatError.agentError.brokerAddress)) + String.format(generalGetString(MR.strings.network_error_desc), serverHostname(e.agentError.brokerAddress)) ) true } - r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent - && r.chatError.agentError is AgentErrorType.BROKER - && r.chatError.agentError.brokerErr is BrokerErrorType.NETWORK -> { + e is ChatError.ChatErrorAgent + && e.agentError is AgentErrorType.BROKER + && e.agentError.brokerErr is BrokerErrorType.NETWORK -> { AlertManager.shared.showAlertMsg( generalGetString(MR.strings.connection_error), - String.format(generalGetString(MR.strings.network_error_desc), serverHostname(r.chatError.agentError.brokerAddress)) + String.format(generalGetString(MR.strings.network_error_desc), serverHostname(e.agentError.brokerAddress)) ) true } - r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent - && r.chatError.agentError is AgentErrorType.BROKER - && r.chatError.agentError.brokerErr is BrokerErrorType.HOST -> { + e is ChatError.ChatErrorAgent + && e.agentError is AgentErrorType.BROKER + && e.agentError.brokerErr is BrokerErrorType.HOST -> { AlertManager.shared.showAlertMsg( generalGetString(MR.strings.connection_error), - String.format(generalGetString(MR.strings.network_error_broker_host_desc), serverHostname(r.chatError.agentError.brokerAddress)) + String.format(generalGetString(MR.strings.network_error_broker_host_desc), serverHostname(e.agentError.brokerAddress)) ) true } - r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent - && r.chatError.agentError is AgentErrorType.BROKER - && r.chatError.agentError.brokerErr is BrokerErrorType.TRANSPORT - && r.chatError.agentError.brokerErr.transportErr is SMPTransportError.Version -> { + e is ChatError.ChatErrorAgent + && e.agentError is AgentErrorType.BROKER + && e.agentError.brokerErr is BrokerErrorType.TRANSPORT + && e.agentError.brokerErr.transportErr is SMPTransportError.Version -> { AlertManager.shared.showAlertMsg( generalGetString(MR.strings.connection_error), - String.format(generalGetString(MR.strings.network_error_broker_version_desc), serverHostname(r.chatError.agentError.brokerAddress)) + String.format(generalGetString(MR.strings.network_error_broker_version_desc), serverHostname(e.agentError.brokerAddress)) ) true } - r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent - && r.chatError.agentError is AgentErrorType.SMP - && r.chatError.agentError.smpErr is SMPErrorType.PROXY -> - smpProxyErrorAlert(r.chatError.agentError.smpErr.proxyErr, r.chatError.agentError.serverAddress) - r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent - && r.chatError.agentError is AgentErrorType.PROXY - && r.chatError.agentError.proxyErr is ProxyClientError.ProxyProtocolError - && r.chatError.agentError.proxyErr.protocolErr is SMPErrorType.PROXY -> + e is ChatError.ChatErrorAgent + && e.agentError is AgentErrorType.SMP + && e.agentError.smpErr is SMPErrorType.PROXY -> + smpProxyErrorAlert(e.agentError.smpErr.proxyErr, e.agentError.serverAddress) + e is ChatError.ChatErrorAgent + && e.agentError is AgentErrorType.PROXY + && e.agentError.proxyErr is ProxyClientError.ProxyProtocolError + && e.agentError.proxyErr.protocolErr is SMPErrorType.PROXY -> proxyDestinationErrorAlert( - r.chatError.agentError.proxyErr.protocolErr.proxyErr, - r.chatError.agentError.proxyServer, - r.chatError.agentError.relayServer + e.agentError.proxyErr.protocolErr.proxyErr, + e.agentError.proxyServer, + e.agentError.relayServer ) else -> false } @@ -2385,18 +2276,18 @@ object ChatController { } } - private fun apiErrorAlert(method: String, title: String, r: CR) { + private fun apiErrorAlert(method: String, title: String, r: API) { val errMsg = "${r.responseType}: ${r.details}" Log.e(TAG, "$method bad response: $errMsg") AlertManager.shared.showAlertMsg(title, errMsg) } - private suspend fun processReceivedMsg(apiResp: APIResponse) { + private suspend fun processReceivedMsg(msg: API) { lastMsgReceivedTimestamp = System.currentTimeMillis() - val r = apiResp.resp - val rhId = apiResp.remoteHostId + val rhId = msg.rhId fun active(user: UserLike): Boolean = activeUser(rhId, user) - chatModel.addTerminalItem(TerminalItem.resp(rhId, r)) + chatModel.addTerminalItem(TerminalItem.resp(rhId, msg)) + val r = msg.result when (r) { is CR.ContactDeletedByContact -> { if (active(r.user) && r.contact.directOrUsed) { @@ -3053,16 +2944,17 @@ object ChatController { chatModel.chatsContext.updateContact(rhId, r.contact) } } - is CR.ChatRespError -> when { - r.chatError is ChatError.ChatErrorAgent && r.chatError.agentError is AgentErrorType.CRITICAL -> { - chatModel.processedCriticalError.newError(r.chatError.agentError, r.chatError.agentError.offerRestart) - } - r.chatError is ChatError.ChatErrorAgent && r.chatError.agentError is AgentErrorType.INTERNAL && appPrefs.developerTools.get() && appPrefs.showInternalErrors.get() -> { - chatModel.processedInternalError.newError(r.chatError.agentError, false) - } - } else -> - Log.d(TAG , "unsupported event: ${r.responseType}") + Log.d(TAG , "unsupported event: ${msg.responseType}") + } + val e = (msg as? API.Error)?.err + when { + e is ChatError.ChatErrorAgent && e.agentError is AgentErrorType.CRITICAL -> + chatModel.processedCriticalError.newError(e.agentError, e.agentError.offerRestart) + e is ChatError.ChatErrorAgent && e.agentError is AgentErrorType.INTERNAL && appPrefs.developerTools.get() && appPrefs.showInternalErrors.get() -> + chatModel.processedInternalError.newError(e.agentError, false) + else -> + Log.d(TAG , "unsupported event: ${msg.responseType}") } } @@ -3693,8 +3585,8 @@ sealed class CC { is ApiChatUnread -> "/_unread chat ${chatRef(type, id)} ${onOff(unreadChat)}" is ReceiveFile -> "/freceive $fileId" + - (" approved_relays=${onOff(userApprovedRelays)}") + - (if (encrypt == null) "" else " encrypt=${onOff(encrypt)}") + + " approved_relays=${onOff(userApprovedRelays)}" + + " encrypt=${onOff(encrypt)}" + (if (inline == null) "" else " inline=${onOff(inline)}") is CancelFile -> "/fcancel $fileId" is SetLocalDeviceName -> "/set device name $displayName" @@ -5688,56 +5580,122 @@ val yaml = Yaml(configuration = YamlConfiguration( codePointLimit = 5500000, )) -@Serializable -class APIResponse(val resp: CR, val remoteHostId: Long?, val corr: String? = null) { - companion object { - fun decodeStr(str: String): APIResponse { - return try { - json.decodeFromString(str) - } catch(e: Throwable) { - try { - Log.d(TAG, e.localizedMessage ?: "") - val data = json.parseToJsonElement(str).jsonObject - val resp = data["resp"]!!.jsonObject - val type = resp["type"]?.jsonPrimitive?.contentOrNull ?: "invalid" - val corr = data["corr"]?.toString() - val remoteHostId = data["remoteHostId"]?.jsonPrimitive?.longOrNull - try { - if (type == "apiChats") { - val user: UserRef = json.decodeFromJsonElement(resp["user"]!!.jsonObject) - val chats: List = resp["chats"]!!.jsonArray.map { - parseChatData(it) - } - return APIResponse(CR.ApiChats(user, chats), remoteHostId, corr) - } else if (type == "apiChat") { - val user: UserRef = json.decodeFromJsonElement(resp["user"]!!.jsonObject) - val chat = parseChatData(resp["chat"]!!) - return APIResponse(CR.ApiChat(user, chat), remoteHostId, corr) - } else if (type == "chatCmdError") { - val userObject = resp["user_"]?.jsonObject - val user = runCatching { json.decodeFromJsonElement(userObject!!) }.getOrNull() - return APIResponse(CR.ChatCmdError(user, ChatError.ChatErrorInvalidJSON(json.encodeToString(resp["chatError"]))), remoteHostId, corr) - } else if (type == "chatError") { - val userObject = resp["user_"]?.jsonObject - val user = runCatching { json.decodeFromJsonElement(userObject!!) }.getOrNull() - return APIResponse(CR.ChatRespError(user, ChatError.ChatErrorInvalidJSON(json.encodeToString(resp["chatError"]))), remoteHostId, corr) - } - } catch (e: Exception) { - Log.e(TAG, "Exception while parsing chat(s): " + e.stackTraceToString()) - } catch (e: Throwable) { - Log.e(TAG, "Throwable while parsing chat(s): " + e.stackTraceToString()) - AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error), e.stackTraceToString()) +@Suppress("SERIALIZER_TYPE_INCOMPATIBLE") +@Serializable(with = APISerializer::class) +sealed class API { + @Serializable(with = APISerializer::class) class Result(val remoteHostId: Long?, val res: CR) : API() + @Serializable(with = APISerializer::class) class Error(val remoteHostId: Long?, val err: ChatError) : API() + + val ok: Boolean get() = this is API.Result && this.res is CR.CmdOk + val result: CR? get() = (this as? API.Result)?.res + val rhId: Long? get() = when (this) { + is Result -> remoteHostId + is Error -> remoteHostId + } + + val pair: Pair get() = when (this) { + is Result -> res to null + is Error -> null to err + } + + val responseType: String get() = when (this) { + is Result -> res.responseType + is Error -> "error ${err.resultType}" + } + + val details: String get() = when (this) { + is Result -> res.details + is Error -> "error ${err.string}" + } +} + +object APISerializer : KSerializer { + override val descriptor: SerialDescriptor = buildSerialDescriptor("API", PolymorphicKind.SEALED) { + element("Result", buildClassSerialDescriptor("Result") { + element("remoteHostId") + element("result") + }) + element("Error", buildClassSerialDescriptor("Error") { + element("remoteHostId") + element("error") + }) + } + + override fun deserialize(decoder: Decoder): API { + require(decoder is JsonDecoder) + val j = try { decoder.decodeJsonElement() } catch(e: Exception) { null } catch(e: Throwable) { null } + if (j == null) return API.Error(remoteHostId = null, ChatError.ChatErrorInvalidJSON("")) + if (j !is JsonObject) return API.Error(remoteHostId = null, ChatError.ChatErrorInvalidJSON(json.encodeToString(j))) + val remoteHostId = j["remoteHostId"]?.jsonPrimitive?.longOrNull + val jRes = j["result"] + if (jRes != null) { + val result = try { + decoder.json.decodeFromJsonElement(jRes) + } catch (e: Exception) { + fallbackResult(jRes) + } catch (e: Throwable) { + fallbackResult(jRes) + } + return API.Result(remoteHostId, result) + } + val jErr = j["error"] + if (jErr != null) { + val error = try { + decoder.json.decodeFromJsonElement(jErr) + } catch (e: Exception) { + fallbackChatError(jErr) + } catch (e: Throwable) { + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error), e.stackTraceToString()) + fallbackChatError(jErr) + } + return API.Error(remoteHostId, error) + } + return API.Error(remoteHostId, fallbackChatError(j)) + } + + private fun fallbackResult(jRes: JsonElement): CR { + if (jRes is JsonObject) { + val type = jRes["type"]?.jsonPrimitive?.contentOrNull ?: "invalid" + try { + if (type == "apiChats") { + val user: UserRef = json.decodeFromJsonElement(jRes["user"]!!.jsonObject) + val chats: List = jRes["chats"]!!.jsonArray.map { + parseChatData(it) } - APIResponse(CR.Response(type, json.encodeToString(data)), remoteHostId, corr) - } catch(e: Exception) { - APIResponse(CR.Invalid(str), remoteHostId = null) - } catch(e: Throwable) { - Log.e(TAG, "Throwable2 while parsing chat(s): " + e.stackTraceToString()) - AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error), e.stackTraceToString()) - APIResponse(CR.Invalid(str), remoteHostId = null) + return CR.ApiChats(user, chats) + } else if (type == "apiChat") { + val user: UserRef = json.decodeFromJsonElement(jRes["user"]!!.jsonObject) + val chat = parseChatData(jRes["chat"]!!) + return CR.ApiChat(user, chat) } + } catch (e: Exception) { + Log.e(TAG, "Exception while parsing chat(s): " + e.stackTraceToString()) + } catch (e: Throwable) { + Log.e(TAG, "Throwable while parsing chat(s): " + e.stackTraceToString()) + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error), e.stackTraceToString()) + } + return CR.Response(type, json.encodeToString(jRes)) + } + return CR.Response(type = "invalid", json.encodeToString(jRes)) + } + + private fun fallbackChatError(jErr: JsonElement): ChatError { + return ChatError.ChatErrorInvalidJSON(json.encodeToString(jErr)) + } + + override fun serialize(encoder: Encoder, value: API) { + require(encoder is JsonEncoder) + val json = when (value) { + is API.Result -> buildJsonObject { + value.remoteHostId?.let { put("remoteHostId", it) } + put("result", encoder.json.encodeToJsonElement(value.res)) + } + is API.Error -> buildJsonObject { + value.remoteHostId?.let { put("remoteHostId", it) } + put("error", encoder.json.encodeToJsonElement(value.err)) } } + encoder.encodeJsonElement(json) } } @@ -5931,8 +5889,6 @@ sealed class CR { // misc @Serializable @SerialName("versionInfo") class VersionInfo(val versionInfo: CoreVersionInfo, val chatMigrations: List, val agentMigrations: List): CR() @Serializable @SerialName("cmdOk") class CmdOk(val user: UserRef?): CR() - @Serializable @SerialName("chatCmdError") class ChatCmdError(val user_: UserRef?, val chatError: ChatError): CR() - @Serializable @SerialName("chatError") class ChatRespError(val user_: UserRef?, val chatError: ChatError): CR() @Serializable @SerialName("archiveExported") class ArchiveExported(val archiveErrors: List): CR() @Serializable @SerialName("archiveImported") class ArchiveImported(val archiveErrors: List): CR() @Serializable @SerialName("appSettings") class AppSettingsR(val appSettings: AppSettings): CR() @@ -6103,8 +6059,6 @@ sealed class CR { is AgentSubsTotal -> "agentSubsTotal" is AgentServersSummary -> "agentServersSummary" is CmdOk -> "cmdOk" - is ChatCmdError -> "chatCmdError" - is ChatRespError -> "chatError" is ArchiveExported -> "archiveExported" is ArchiveImported -> "archiveImported" is AppSettingsR -> "appSettings" @@ -6290,8 +6244,6 @@ sealed class CR { "chat migrations: ${json.encodeToString(chatMigrations.map { it.upName })}\n\n" + "agent migrations: ${json.encodeToString(agentMigrations.map { it.upName })}" is CmdOk -> withUser(user, noDetails()) - is ChatCmdError -> withUser(user_, chatError.string) - is ChatRespError -> withUser(user_, chatError.string) is ArchiveExported -> "${archiveErrors.map { it.string } }" is ArchiveImported -> "${archiveErrors.map { it.string } }" is AppSettingsR -> json.encodeToString(appSettings) @@ -6304,13 +6256,9 @@ sealed class CR { private fun withUser(u: UserLike?, s: String): String = if (u != null) "userId: ${u.userId}\n$s" else s } -fun chatError(r: CR): ChatErrorType? { - return ( - if (r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorChat) r.chatError.errorType - else if (r is CR.ChatRespError && r.chatError is ChatError.ChatErrorChat) r.chatError.errorType - else null - ) -} +fun apiChatErrorType(r: API): ChatErrorType? = + if (r is API.Error && r.err is ChatError.ChatErrorChat) r.err.errorType + else null @Serializable sealed class ChatDeleteMode { @@ -6395,7 +6343,7 @@ abstract class TerminalItem { override val details get() = cmd.cmdString } - class Resp(override val id: Long, override val remoteHostId: Long?, val resp: CR): TerminalItem() { + class Resp(override val id: Long, override val remoteHostId: Long?, val resp: API): TerminalItem() { override val label get() = "< ${resp.responseType}" override val details get() = resp.details } @@ -6403,11 +6351,11 @@ abstract class TerminalItem { companion object { val sampleData = listOf( Cmd(0, null, CC.ShowActiveUser()), - Resp(1, null, CR.ActiveUser(User.sampleData)) + Resp(1, null, API.Result(null, CR.ActiveUser(User.sampleData))) ) fun cmd(rhId: Long?, c: CC) = Cmd(System.currentTimeMillis(), rhId, c) - fun resp(rhId: Long?, r: CR) = Resp(System.currentTimeMillis(), rhId, r) + fun resp(rhId: Long?, r: API) = Resp(System.currentTimeMillis(), rhId, r) } } @@ -6551,6 +6499,16 @@ sealed class ChatError { @Serializable @SerialName("errorRemoteHost") class ChatErrorRemoteHost(val remoteHostError: RemoteHostError): ChatError() @Serializable @SerialName("errorRemoteCtrl") class ChatErrorRemoteCtrl(val remoteCtrlError: RemoteCtrlError): ChatError() @Serializable @SerialName("invalidJSON") class ChatErrorInvalidJSON(val json: String): ChatError() + + val resultType: String get() = when (this) { + is ChatErrorChat -> "chat" + is ChatErrorAgent -> "agent" + is ChatErrorStore -> "store" + is ChatErrorDatabase -> "database" + is ChatErrorRemoteHost -> "remoteHost" + is ChatErrorRemoteCtrl -> "remoteCtrl" + is ChatErrorInvalidJSON -> "invalid json" + } } @Serializable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt index 22b013ff60..ca4d4fc0da 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt @@ -48,7 +48,7 @@ private fun sendCommand(chatModel: ChatModel, composeState: MutableState CallState.InvitationSent) break - val msg = apiResp.resp - if (apiResp.remoteHostId == call.remoteHostId && - msg is CR.ChatItemsStatusesUpdated && - msg.chatItems.any { + if (msg.rhId == call.remoteHostId && + msg is API.Result && + msg.res is CR.ChatItemsStatusesUpdated && + msg.res.chatItems.any { it.chatInfo.id == call.contact.id && it.chatItem.content is CIContent.SndCall && it.chatItem.meta.itemStatus is CIStatus.SndRcvd } ) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index ef82b9a35b..dc1c0b71a5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -554,11 +554,11 @@ fun ChatView( ChatItemInfoView(chatRh, cItem, ciInfo, devTools = chatModel.controller.appPrefs.developerTools.get(), chatInfo) LaunchedEffect(cItem.id) { withContext(Dispatchers.Default) { - for (apiResp in controller.messagesChannel) { - val msg = apiResp.resp - if (apiResp.remoteHostId == chatRh && - msg is CR.ChatItemsStatusesUpdated && - msg.chatItems.any { it.chatItem.id == cItem.id } + for (msg in controller.messagesChannel) { + if (msg.rhId == chatRh && + msg is API.Result && + msg.res is CR.ChatItemsStatusesUpdated && + msg.res.chatItems.any { it.chatItem.id == cItem.id } ) { ciInfo = loadChatItemInfo() ?: return@withContext initialCiInfo = ciInfo diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt index c2e1d67d50..1c1c37b7ac 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt @@ -435,7 +435,7 @@ suspend fun encryptDatabase( } val error = m.controller.apiStorageEncryption(currentKey.value, newKey.value) appPrefs.encryptionStartedAt.set(null) - val sqliteError = ((error?.chatError as? ChatError.ChatErrorDatabase)?.databaseError as? DatabaseError.ErrorExport)?.sqliteError + val sqliteError = ((error as? ChatError.ChatErrorDatabase)?.databaseError as? DatabaseError.ErrorExport)?.sqliteError when { sqliteError is SQLiteError.ErrorNotADatabase -> { operationEnded(m, progressIndicator) { @@ -449,7 +449,7 @@ suspend fun encryptDatabase( error != null -> { operationEnded(m, progressIndicator) { AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_encrypting_database), - "failed to set storage encryption: ${error.responseType} ${error.details}" + "failed to set storage encryption: error ${error.string}" ) } false diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt index 8588e0e981..03542ca8af 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt @@ -468,12 +468,12 @@ private suspend fun MutableState.verifyDatabasePassphrase(db val error = controller.testStorageEncryption(dbKey) if (error == null) { state = MigrationFromState.UploadConfirmation - } else if (((error.chatError as? ChatError.ChatErrorDatabase)?.databaseError as? DatabaseError.ErrorOpen)?.sqliteError is SQLiteError.ErrorNotADatabase) { + } else if (((error as? ChatError.ChatErrorDatabase)?.databaseError as? DatabaseError.ErrorOpen)?.sqliteError is SQLiteError.ErrorNotADatabase) { showErrorOnMigrationIfNeeded(DBMigrationResult.ErrorNotADatabase("")) } else { AlertManager.shared.showAlertMsg( title = generalGetString(MR.strings.error), - text = generalGetString(MR.strings.migrate_from_device_error_verifying_passphrase) + " " + error.details + text = generalGetString(MR.strings.migrate_from_device_error_verifying_passphrase) + " " + error.string ) } } @@ -556,11 +556,12 @@ private fun MutableState.startUploading( ) { withBGApi { chatReceiver.value = MigrationFromChatReceiver(ctrl, tempDatabaseFile) { msg -> - when (msg) { + val r = msg.result + when (r) { is CR.SndFileProgressXFTP -> { val s = state if (s is MigrationFromState.UploadProgress && s.uploadedBytes != s.totalBytes) { - state = MigrationFromState.UploadProgress(msg.sentSize, msg.totalSize, msg.fileTransferMeta.fileId, archivePath, ctrl, user) + state = MigrationFromState.UploadProgress(r.sentSize, r.totalSize, r.fileTransferMeta.fileId, archivePath, ctrl, user) } } is CR.SndFileRedirectStartXFTP -> { @@ -578,7 +579,7 @@ private fun MutableState.startUploading( requiredHostMode = cfg.requiredHostMode ) ) - state = MigrationFromState.LinkShown(msg.fileTransferMeta.fileId, data.addToLink(msg.rcvURIs[0]), ctrl) + state = MigrationFromState.LinkShown(r.fileTransferMeta.fileId, data.addToLink(r.rcvURIs[0]), ctrl) } is CR.SndFileError -> { AlertManager.shared.showAlertMsg( @@ -692,7 +693,7 @@ private class MigrationFromChatReceiver( val ctrl: ChatCtrl, val databaseUrl: File, var receiveMessages: Boolean = true, - val processReceivedMsg: suspend (CR) -> Unit + val processReceivedMsg: suspend (API) -> Unit ) { fun start() { Log.d(TAG, "MigrationChatReceiver startReceiver") @@ -701,19 +702,18 @@ private class MigrationFromChatReceiver( try { val msg = ChatController.recvMsg(ctrl) if (msg != null && receiveMessages) { - val r = msg.resp - val rhId = msg.remoteHostId - Log.d(TAG, "processReceivedMsg: ${r.responseType}") - chatModel.addTerminalItem(TerminalItem.resp(rhId, r)) + val rhId = msg.rhId + Log.d(TAG, "processReceivedMsg: ${msg.responseType}") + chatModel.addTerminalItem(TerminalItem.resp(rhId, msg)) val finishedWithoutTimeout = withTimeoutOrNull(60_000L) { - processReceivedMsg(r) + processReceivedMsg(msg) } if (finishedWithoutTimeout == null) { - Log.e(TAG, "Timeout reached while processing received message: " + msg.resp.responseType) + Log.e(TAG, "Timeout reached while processing received message: " + msg.responseType) if (appPreferences.developerTools.get() && appPreferences.showSlowApiCalls.get()) { AlertManager.shared.showAlertMsg( title = generalGetString(MR.strings.possible_slow_function_title), - text = generalGetString(MR.strings.possible_slow_function_desc).format(60, msg.resp.responseType + "\n" + Exception().stackTraceToString()), + text = generalGetString(MR.strings.possible_slow_function_desc).format(60, msg.responseType + "\n" + Exception().stackTraceToString()), shareText = true ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt index 1a28bbf589..d74846f8a3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt @@ -580,12 +580,13 @@ private fun MutableState.startDownloading( ) { withBGApi { chatReceiver.value = MigrationToChatReceiver(ctrl, tempDatabaseFile) { msg -> - when (msg) { - is CR.RcvFileProgressXFTP -> { - state = MigrationToState.DownloadProgress(msg.receivedSize, msg.totalSize, msg.rcvFileTransfer.fileId, link, archivePath, netCfg, networkProxy, ctrl) + val r = msg.result + when { + r is CR.RcvFileProgressXFTP -> { + state = MigrationToState.DownloadProgress(r.receivedSize, r.totalSize, r.rcvFileTransfer.fileId, link, archivePath, netCfg, networkProxy, ctrl) MigrationToDeviceState.save(MigrationToDeviceState.DownloadProgress(link, File(archivePath).name, netCfg, networkProxy)) } - is CR.RcvStandaloneFileComplete -> { + r is CR.RcvStandaloneFileComplete -> { delay(500) // User closed the whole screen before new state was saved if (state == null) { @@ -595,22 +596,22 @@ private fun MutableState.startDownloading( MigrationToDeviceState.save(MigrationToDeviceState.ArchiveImport(File(archivePath).name, netCfg, networkProxy)) } } - is CR.RcvFileError -> { + r is CR.RcvFileError -> { AlertManager.shared.showAlertMsg( generalGetString(MR.strings.migrate_to_device_download_failed), generalGetString(MR.strings.migrate_to_device_file_delete_or_link_invalid) ) state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg, networkProxy) } - is CR.ChatRespError -> { - if (msg.chatError is ChatError.ChatErrorChat && msg.chatError.errorType is ChatErrorType.NoRcvFileUser) { + msg is API.Error -> { + if (msg.err is ChatError.ChatErrorChat && msg.err.errorType is ChatErrorType.NoRcvFileUser) { AlertManager.shared.showAlertMsg( generalGetString(MR.strings.migrate_to_device_download_failed), generalGetString(MR.strings.migrate_to_device_file_delete_or_link_invalid) ) state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg, networkProxy) } else { - Log.d(TAG, "unsupported error: ${msg.responseType}, ${json.encodeToString(msg.chatError)}") + Log.d(TAG, "unsupported error: ${msg.responseType}, ${json.encodeToString(msg.err)}") } } else -> Log.d(TAG, "unsupported event: ${msg.responseType}") @@ -739,7 +740,7 @@ private class MigrationToChatReceiver( val ctrl: ChatCtrl, val databaseUrl: File, var receiveMessages: Boolean = true, - val processReceivedMsg: suspend (CR) -> Unit + val processReceivedMsg: suspend (API) -> Unit ) { fun start() { Log.d(TAG, "MigrationChatReceiver startReceiver") @@ -748,19 +749,18 @@ private class MigrationToChatReceiver( try { val msg = ChatController.recvMsg(ctrl) if (msg != null && receiveMessages) { - val r = msg.resp - val rhId = msg.remoteHostId - Log.d(TAG, "processReceivedMsg: ${r.responseType}") - chatModel.addTerminalItem(TerminalItem.resp(rhId, r)) + val rhId = msg.rhId + Log.d(TAG, "processReceivedMsg: ${msg.responseType}") + chatModel.addTerminalItem(TerminalItem.resp(rhId, msg)) val finishedWithoutTimeout = withTimeoutOrNull(60_000L) { - processReceivedMsg(r) + processReceivedMsg(msg) } if (finishedWithoutTimeout == null) { - Log.e(TAG, "Timeout reached while processing received message: " + msg.resp.responseType) + Log.e(TAG, "Timeout reached while processing received message: " + msg.responseType) if (appPreferences.developerTools.get() && appPreferences.showSlowApiCalls.get()) { AlertManager.shared.showAlertMsg( title = generalGetString(MR.strings.possible_slow_function_title), - text = generalGetString(MR.strings.possible_slow_function_desc).format(60, msg.resp.responseType + "\n" + Exception().stackTraceToString()), + text = generalGetString(MR.strings.possible_slow_function_desc).format(60, msg.responseType + "\n" + Exception().stackTraceToString()), shareText = true ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt index 3b6e176ca3..8bb84060c2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt @@ -492,7 +492,7 @@ private suspend fun connectDesktopAddress(sessionAddress: MutableState, } } -private suspend fun connectDesktop(sessionAddress: MutableState, connect: suspend () -> Pair): Boolean { +private suspend fun connectDesktop(sessionAddress: MutableState, connect: suspend () -> Pair): Boolean { val res = connect() if (res.first != null) { val (rc_, ctrlAppInfo, v) = res.first!! @@ -505,13 +505,13 @@ private suspend fun connectDesktop(sessionAddress: MutableState, connect } else { val e = res.second ?: return false when { - e.chatError is ChatError.ChatErrorRemoteCtrl && e.chatError.remoteCtrlError is RemoteCtrlError.BadInvitation -> showBadInvitationErrorAlert() - e.chatError is ChatError.ChatErrorChat && e.chatError.errorType is ChatErrorType.CommandError -> showBadInvitationErrorAlert() - e.chatError is ChatError.ChatErrorRemoteCtrl && e.chatError.remoteCtrlError is RemoteCtrlError.BadVersion -> showBadVersionAlert(v = e.chatError.remoteCtrlError.appVersion) - e.chatError is ChatError.ChatErrorAgent && e.chatError.agentError is AgentErrorType.RCP && e.chatError.agentError.rcpErr is RCErrorType.VERSION -> showBadVersionAlert(v = null) - e.chatError is ChatError.ChatErrorAgent && e.chatError.agentError is AgentErrorType.RCP && e.chatError.agentError.rcpErr is RCErrorType.CTRL_AUTH -> showDesktopDisconnectedErrorAlert() + e is ChatError.ChatErrorRemoteCtrl && e.remoteCtrlError is RemoteCtrlError.BadInvitation -> showBadInvitationErrorAlert() + e is ChatError.ChatErrorChat && e.errorType is ChatErrorType.CommandError -> showBadInvitationErrorAlert() + e is ChatError.ChatErrorRemoteCtrl && e.remoteCtrlError is RemoteCtrlError.BadVersion -> showBadVersionAlert(v = e.remoteCtrlError.appVersion) + e is ChatError.ChatErrorAgent && e.agentError is AgentErrorType.RCP && e.agentError.rcpErr is RCErrorType.VERSION -> showBadVersionAlert(v = null) + e is ChatError.ChatErrorAgent && e.agentError is AgentErrorType.RCP && e.agentError.rcpErr is RCErrorType.CTRL_AUTH -> showDesktopDisconnectedErrorAlert() else -> { - val errMsg = "${e.responseType}: ${e.details}" + val errMsg = "error: ${e.string}" Log.e(TAG, "bad response: $errMsg") AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error), errMsg) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt index 24978ecf7c..569f4ff5f8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt @@ -291,7 +291,7 @@ private fun DeliveryReceiptsSection( SectionView(stringResource(MR.strings.settings_section_title_delivery_receipts)) { SettingsActionItemWithContent(painterResource(MR.images.ic_person), stringResource(MR.strings.receipts_section_contacts)) { DefaultSwitch( - checked = currentUser.sendRcptsContacts ?: false, + checked = currentUser.sendRcptsContacts, onCheckedChange = { enable -> setOrAskSendReceiptsContacts(enable) } @@ -299,7 +299,7 @@ private fun DeliveryReceiptsSection( } SettingsActionItemWithContent(painterResource(MR.images.ic_group), stringResource(MR.strings.receipts_section_groups)) { DefaultSwitch( - checked = currentUser.sendRcptsSmallGroups ?: false, + checked = currentUser.sendRcptsSmallGroups, onCheckedChange = { enable -> setOrAskSendReceiptsGroups(enable) } diff --git a/apps/simplex-bot-advanced/Main.hs b/apps/simplex-bot-advanced/Main.hs index b6ad9eea96..40e6686065 100644 --- a/apps/simplex-bot-advanced/Main.hs +++ b/apps/simplex-bot-advanced/Main.hs @@ -45,10 +45,10 @@ mySquaringBot _user cc = do race_ (forever $ void getLine) . forever $ do (_, evt) <- atomically . readTBQueue $ outputQ cc case evt of - CEvtContactConnected _ contact _ -> do + Right (CEvtContactConnected _ contact _) -> do contactConnected contact sendMessage cc contact welcomeMessage - CEvtNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content = mc@CIRcvMsgContent {}}) : _} -> do + Right CEvtNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content = mc@CIRcvMsgContent {}}) : _} -> do let msg = ciContentToText mc number_ = readMaybe (T.unpack msg) :: Maybe Integer sendMessage cc contact $ case number_ of diff --git a/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs b/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs index 913f6a732a..86f89f86e8 100644 --- a/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs +++ b/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs @@ -39,17 +39,17 @@ broadcastBot BroadcastBotOpts {publishers, welcomeMessage, prohibitedMessage} _u race_ (forever $ void getLine) . forever $ do (_, evt) <- atomically . readTBQueue $ outputQ cc case evt of - CEvtContactConnected _ ct _ -> do + Right (CEvtContactConnected _ ct _) -> do contactConnected ct sendMessage cc ct welcomeMessage - CEvtNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc}) : _} + Right CEvtNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc}) : _} | sender `notElem` publishers -> do sendReply prohibitedMessage deleteMessage cc ct $ chatItemId' ci | allowContent mc -> void $ forkIO $ sendChatCmd cc (SendMessageBroadcast mc) >>= \case - CRBroadcastSent {successes, failures} -> + Right CRBroadcastSent {successes, failures} -> sendReply $ "Forwarded to " <> tshow successes <> " contact(s), " <> tshow failures <> " errors" r -> putStrLn $ "Error broadcasting message: " <> show r | otherwise -> diff --git a/apps/simplex-chat/Server.hs b/apps/simplex-chat/Server.hs index d087df0bb5..0906d14536 100644 --- a/apps/simplex-chat/Server.hs +++ b/apps/simplex-chat/Server.hs @@ -2,20 +2,23 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE UndecidableInstances #-} module Server where import Control.Monad import Control.Monad.Except import Control.Monad.Reader -import Data.Aeson (FromJSON, ToJSON) +import Data.Aeson (FromJSON, ToJSON (..)) import qualified Data.Aeson as J import qualified Data.Aeson.TH as JQ +import Data.Bifunctor (first) import Data.Text (Text) import Data.Text.Encoding (encodeUtf8) import GHC.Generics (Generic) @@ -26,7 +29,7 @@ import Simplex.Chat.Controller import Simplex.Chat.Core import Simplex.Chat.Library.Commands import Simplex.Chat.Options -import Simplex.Messaging.Parsers (defaultJSON) +import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, taggedObjectJSON) import Simplex.Messaging.Transport.Server (runLocalTCPServer) import Simplex.Messaging.Util (raceAny_) import UnliftIO.Exception @@ -35,13 +38,32 @@ import UnliftIO.STM data ChatSrvRequest = ChatSrvRequest {corrId :: Text, cmd :: Text} deriving (Generic, FromJSON) -data ChatSrvResponse r = ChatSrvResponse {corrId :: Maybe Text, resp :: r} +data ChatSrvResponse r = ChatSrvResponse {corrId :: Maybe Text, resp :: CSRBody r} + +data CSRBody r = CSRBody {csrBody :: Either ChatError r} + +-- backwards compatible encoding, to avoid breaking any chat bots +data ObjChatCmdError = ObjChatCmdError {chatError :: ChatError} + +data ObjChatError = ObjChatError {chatError :: ChatError} + +$(JQ.deriveToJSON (taggedObjectJSON $ dropPrefix "Obj") ''ObjChatCmdError) + +$(JQ.deriveToJSON (taggedObjectJSON $ dropPrefix "Obj") ''ObjChatError) + +instance ToJSON (CSRBody ChatResponse) where + toJSON = toJSON . first ObjChatCmdError . csrBody + toEncoding = toEncoding . first ObjChatCmdError . csrBody + +instance ToJSON (CSRBody ChatEvent) where + toJSON = toJSON . first ObjChatError . csrBody + toEncoding = toEncoding . first ObjChatError . csrBody data AChatSrvResponse = forall r. ToJSON (ChatSrvResponse r) => ACR (ChatSrvResponse r) $(pure []) -instance ToJSON r => ToJSON (ChatSrvResponse r) where +instance ToJSON (CSRBody r) => ToJSON (ChatSrvResponse r) where toEncoding = $(JQ.mkToEncoding defaultJSON ''ChatSrvResponse) toJSON = $(JQ.mkToJSON defaultJSON ''ChatSrvResponse) @@ -91,8 +113,8 @@ runChatServer ChatServerConfig {chatPort, clientQSize} cc = do >>= processCommand >>= atomically . writeTBQueue sndQ . ACR output ChatClient {sndQ} = forever $ do - (_, resp) <- atomically . readTBQueue $ outputQ cc - atomically $ writeTBQueue sndQ $ ACR ChatSrvResponse {corrId = Nothing, resp} + (_, r) <- atomically . readTBQueue $ outputQ cc + atomically $ writeTBQueue sndQ $ ACR ChatSrvResponse {corrId = Nothing, resp = CSRBody r} receive ws ChatClient {rcvQ, sndQ} = forever $ do s <- WS.receiveData ws case J.decodeStrict' s of @@ -103,11 +125,9 @@ runChatServer ChatServerConfig {chatPort, clientQSize} cc = do Left e -> sendError (Just corrId) e Nothing -> sendError Nothing "invalid request" where - sendError corrId e = atomically $ writeTBQueue sndQ $ ACR ChatSrvResponse {corrId, resp = chatCmdError Nothing e} + sendError corrId e = atomically $ writeTBQueue sndQ $ ACR ChatSrvResponse {corrId, resp = CSRBody $ chatCmdError e} processCommand (corrId, cmd) = - runReaderT (runExceptT $ processChatCommand cmd) cc >>= \case - Right resp -> response resp - Left e -> response $ CRChatCmdError Nothing e + response <$> runReaderT (runExceptT $ processChatCommand cmd) cc where - response resp = pure ChatSrvResponse {corrId = Just corrId, resp} + response r = ChatSrvResponse {corrId = Just corrId, resp = CSRBody r} clientDisconnected _ = pure () diff --git a/apps/simplex-directory-service/src/Directory/Events.hs b/apps/simplex-directory-service/src/Directory/Events.hs index 412f87889c..37d2b63d2f 100644 --- a/apps/simplex-directory-service/src/Directory/Events.hs +++ b/apps/simplex-directory-service/src/Directory/Events.hs @@ -63,8 +63,16 @@ data DirectoryEvent | DELogChatResponse Text deriving (Show) -crDirectoryEvent :: ChatEvent -> Maybe DirectoryEvent +crDirectoryEvent :: Either ChatError ChatEvent -> Maybe DirectoryEvent crDirectoryEvent = \case + Right evt -> crDirectoryEvent_ evt + Left e -> case e of + ChatErrorAgent {agentError = BROKER _ NETWORK} -> Nothing + ChatErrorAgent {agentError = BROKER _ TIMEOUT} -> Nothing + _ -> Just $ DELogChatResponse $ "chat error: " <> tshow e + +crDirectoryEvent_ :: ChatEvent -> Maybe DirectoryEvent +crDirectoryEvent_ = \case CEvtContactConnected {contact} -> Just $ DEContactConnected contact CEvtReceivedGroupInvitation {contact, groupInfo, fromMemberRole, memberRole} -> Just $ DEGroupInvitation {contact, groupInfo, fromMemberRole, memberRole} CEvtUserJoinedGroup {groupInfo, hostMember} -> (\contactId -> DEServiceJoinedGroup {contactId, groupInfo, hostMember}) <$> memberContactId hostMember @@ -92,10 +100,6 @@ crDirectoryEvent = \case ciId = chatItemId' ci err = ADC SDRUser DCUnknownCommand CEvtMessageError {severity, errorMessage} -> Just $ DELogChatResponse $ "message error: " <> severity <> ", " <> errorMessage - CEvtChatError {chatError} -> case chatError of - ChatErrorAgent {agentError = BROKER _ NETWORK} -> Nothing - ChatErrorAgent {agentError = BROKER _ TIMEOUT} -> Nothing - _ -> Just $ DELogChatResponse $ "chat error: " <> tshow chatError CEvtChatErrors {chatErrors} -> Just $ DELogChatResponse $ "chat errors: " <> T.intercalate ", " (map tshow chatErrors) _ -> Nothing where diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs index 89fb9c30d8..4b02e0b294 100644 --- a/apps/simplex-directory-service/src/Directory/Service.hs +++ b/apps/simplex-directory-service/src/Directory/Service.hs @@ -60,7 +60,7 @@ import Simplex.Chat.Terminal (terminalChatConfig) import Simplex.Chat.Terminal.Main (simplexChatCLI') import Simplex.Chat.Types import Simplex.Chat.Types.Shared -import Simplex.Chat.View (serializeChatResponse, simplexChatContact, viewContactName, viewGroupName) +import Simplex.Chat.View (serializeChatError, serializeChatResponse, simplexChatContact, viewContactName, viewGroupName) import Simplex.Messaging.Agent.Protocol (AConnectionLink (..), ConnectionLink (..), CreatedConnLink (..)) import Simplex.Messaging.Agent.Store.Common (withTransaction) import Simplex.Messaging.Agent.Protocol (SConnectionMode (..), sameConnReqContact, sameShortLinkContact) @@ -197,7 +197,7 @@ readBlockedWordsConfig DirectoryOpts {blockedFragmentsFile, blockedWordsFile, na unless testing $ putStrLn $ "Blocked fragments: " <> show (length blockedFragments) <> ", blocked words: " <> show (length blockedWords) <> ", spelling rules: " <> show (M.size spelling) pure BlockedWordsConfig {blockedFragments, blockedWords, extensionRules, spelling} -directoryServiceEvent :: DirectoryStore -> DirectoryOpts -> ServiceState -> User -> ChatController -> ChatEvent -> IO () +directoryServiceEvent :: DirectoryStore -> DirectoryOpts -> ServiceState -> User -> ChatController -> Either ChatError ChatEvent -> IO () directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName, ownersGroup, searchResults} env@ServiceState {searchRequests} user@User {userId} cc event = forM_ (crDirectoryEvent event) $ \case DEContactConnected ct -> deContactConnected ct @@ -249,7 +249,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName getGroups_ :: Maybe Text -> IO (Maybe [(GroupInfo, GroupSummary)]) getGroups_ search_ = sendChatCmd cc (APIListGroups userId Nothing $ T.unpack <$> search_) >>= \case - CRGroupsList {groups} -> pure $ Just groups + Right CRGroupsList {groups} -> pure $ Just groups _ -> pure Nothing getDuplicateGroup :: GroupInfo -> IO (Maybe DuplicateGroup) @@ -281,7 +281,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName void $ addGroupReg st ct g GRSProposed r <- sendChatCmd cc $ APIJoinGroup groupId MFNone sendMessage cc ct $ case r of - CRUserAcceptedGroupSent {} -> "Joining the group " <> displayName <> "…" + Right CRUserAcceptedGroupSent {} -> "Joining the group " <> displayName <> "…" _ -> "Error joining group " <> displayName <> ", please re-send the invitation!" deContactConnected :: Contact -> IO () @@ -337,7 +337,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName $>>= \mId -> resp <$> sendChatCmd cc (APIGroupMemberInfo dbGroupId mId) where resp = \case - CRGroupMemberInfo {member} -> Just member + Right CRGroupMemberInfo {member} -> Just member _ -> Nothing deServiceJoinedGroup :: ContactId -> GroupInfo -> GroupMember -> IO () @@ -349,7 +349,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName let GroupInfo {groupId, groupProfile = GroupProfile {displayName}} = g notifyOwner gr $ "Joined the group " <> displayName <> ", creating the link…" sendChatCmd cc (APICreateGroupLink groupId GRMember False) >>= \case - CRGroupLinkCreated {connLinkContact = CCLink gLink _} -> do + Right CRGroupLinkCreated {connLinkContact = CCLink gLink _} -> do setGroupStatus st gr GRSPendingUpdate notifyOwner gr @@ -357,7 +357,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName \Please add it to the group welcome message.\n\ \For example, add:" notifyOwner gr $ "Link to join the group " <> displayName <> ": " <> strEncodeTxt (simplexChatContact gLink) - CRChatCmdError _ (ChatError e) -> case e of + Left (ChatError e) -> case e of CEGroupUserRole {} -> notifyOwner gr "Failed creating group link, as service is no longer an admin." CEGroupMemberUserRemoved -> notifyOwner gr "Failed creating group link, as service is removed from the group." CEGroupNotJoined _ -> notifyOwner gr $ unexpectedError "group not joined" @@ -446,7 +446,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName groupProfileUpdate = profileUpdate <$> sendChatCmd cc (APIGetGroupLink groupId) where profileUpdate = \case - CRGroupLink {connLinkContact = CCLink cr sl_} -> + Right CRGroupLink {connLinkContact = CCLink cr sl_} -> let hadLinkBefore = profileHasGroupLink fromGroup hasLinkNow = profileHasGroupLink toGroup profileHasGroupLink GroupInfo {groupProfile = gp} = @@ -503,7 +503,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName let role = if useMemberFilter image (makeObserver a) then GRObserver else maybe GRMember (\GroupLinkInfo {memberRole} -> memberRole) gli_ gmId = groupMemberId' m sendChatCmd cc (APIAcceptMember groupId gmId role) >>= \case - CRJoinedGroupMember {} -> do + Right CRJoinedGroupMember {} -> do atomically $ TM.delete gmId $ pendingCaptchas env logInfo $ "Member " <> viewName displayName <> " accepted, group " <> tshow groupId <> ":" <> viewGroupName g r -> logError $ "unexpected accept member response: " <> tshow r @@ -528,7 +528,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName let gmId = groupMemberId' m sendComposedMessages cc (SRGroup groupId $ Just gmId) [MCText rjctNotice] sendChatCmd cc (APIRemoveMembers groupId [gmId] False) >>= \case - CRUserDeletedMembers _ _ (_ : _) _ -> do + Right (CRUserDeletedMembers _ _ (_ : _) _) -> do atomically $ TM.delete gmId $ pendingCaptchas env logInfo $ "Member " <> viewName displayName <> " rejected, group " <> tshow groupId <> ":" <> viewGroupName g r -> logError $ "unexpected remove member response: " <> tshow r @@ -891,18 +891,21 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName let groupRef = groupReference' groupId gName withGroupAndReg sendReply groupId gName $ \_ _ -> sendChatCmd cc (APIGetGroupLink groupId) >>= \case - CRGroupLink {connLinkContact = CCLink cReq _, memberRole} -> + Right CRGroupLink {connLinkContact = CCLink cReq _, memberRole} -> sendReply $ T.unlines [ "The link to join the group " <> groupRef <> ":", strEncodeTxt $ simplexChatContact cReq, "New member role: " <> strEncodeTxt memberRole ] - CRChatCmdError _ (ChatErrorStore (SEGroupLinkNotFound _)) -> + Left (ChatErrorStore (SEGroupLinkNotFound _)) -> sendReply $ "The group " <> groupRef <> " has no public link." - r -> do + Right r -> do ts <- getCurrentTime tz <- getCurrentTimeZone - let resp = T.pack $ serializeChatResponse (Nothing, Just user) ts tz Nothing r + let resp = T.pack $ serializeChatResponse (Nothing, Just user) (config cc) ts tz Nothing r + sendReply $ "Unexpected error:\n" <> resp + Left e -> do + let resp = T.pack $ serializeChatError True (config cc) e sendReply $ "Unexpected error:\n" <> resp DCSendToGroupOwner groupId gName msg -> do let groupRef = groupReference' groupId gName @@ -944,11 +947,11 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName inviteToOwnersGroup :: KnownGroup -> GroupReg -> (Either Text () -> IO a) -> IO a inviteToOwnersGroup KnownGroup {groupId = ogId} GroupReg {dbContactId = ctId} cont = sendChatCmd cc (APIListMembers ogId) >>= \case - CRGroupMembers _ (Group _ ms) + Right (CRGroupMembers _ (Group _ ms)) | alreadyMember ms -> cont $ Left "Owner is already a member of owners' group" | otherwise -> do sendChatCmd cc (APIAddMember ogId ctId GRMember) >>= \case - CRSentGroupInvitation {} -> do + Right CRSentGroupInvitation {} -> do printLog cc CLLInfo $ "invited contact ID " <> show ctId <> " to owners' group" cont $ Right () r -> contErr r @@ -969,10 +972,13 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName deSuperUserCommand ct ciId cmd | knownContact ct `elem` superUsers = case cmd of DCExecuteCommand cmdStr -> - sendChatCmdStr cc cmdStr >>= \r -> do - ts <- getCurrentTime - tz <- getCurrentTimeZone - sendReply $ T.pack $ serializeChatResponse (Nothing, Just user) ts tz Nothing r + sendChatCmdStr cc cmdStr >>= \case + Right r -> do + ts <- getCurrentTime + tz <- getCurrentTimeZone + sendReply $ T.pack $ serializeChatResponse (Nothing, Just user) (config cc) ts tz Nothing r + Left e -> + sendReply $ T.pack $ serializeChatError True (config cc) e DCCommandError tag -> sendReply $ "Command error: " <> tshow tag | otherwise = sendReply "You are not allowed to use this command" where @@ -1045,7 +1051,7 @@ setGroupLinkRole :: ChatController -> GroupInfo -> GroupMemberRole -> IO (Maybe setGroupLinkRole cc GroupInfo {groupId} mRole = resp <$> sendChatCmd cc (APIGroupLinkMemberRole groupId mRole) where resp = \case - CRGroupLink _ _ (CCLink gLink _) _ -> Just gLink + Right (CRGroupLink _ _ (CCLink gLink _) _) -> Just gLink _ -> Nothing unexpectedError :: Text -> Text diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 291083368e..6469f24244 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -467,6 +467,8 @@ executable simplex-directory-service , text >=1.2.4.0 && <1.3 test-suite simplex-chat-test + if flag(swift) + cpp-options: -DswiftJSON if flag(client_library) buildable: False type: exitcode-stdio-1.0 diff --git a/src/Simplex/Chat/Bot.hs b/src/Simplex/Chat/Bot.hs index 5acf60556e..73a2970c61 100644 --- a/src/Simplex/Chat/Bot.hs +++ b/src/Simplex/Chat/Bot.hs @@ -35,10 +35,10 @@ chatBotRepl welcome answer _user cc = do race_ (forever $ void getLine) . forever $ do (_, event) <- atomically . readTBQueue $ outputQ cc case event of - CEvtContactConnected _ contact _ -> do + Right (CEvtContactConnected _ contact _) -> do contactConnected contact void $ sendMessage cc contact $ T.pack welcome - CEvtNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content = mc@CIRcvMsgContent {}}) : _} -> do + Right CEvtNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content = mc@CIRcvMsgContent {}}) : _} -> do let msg = T.unpack $ ciContentToText mc void $ sendMessage cc contact . T.pack =<< answer contact msg _ -> pure () @@ -51,12 +51,12 @@ initializeBotAddress = initializeBotAddress' True initializeBotAddress' :: Bool -> ChatController -> IO () initializeBotAddress' logAddress cc = do sendChatCmd cc ShowMyAddress >>= \case - CRUserContactLink _ UserContactLink {connLinkContact} -> showBotAddress connLinkContact - CRChatCmdError _ (ChatErrorStore SEUserContactLinkNotFound) -> do + Right (CRUserContactLink _ UserContactLink {connLinkContact}) -> showBotAddress connLinkContact + Left (ChatErrorStore SEUserContactLinkNotFound) -> do when logAddress $ putStrLn "No bot address, creating..." -- TODO [short links] create short link by default sendChatCmd cc (CreateMyAddress False) >>= \case - CRUserContactLinkCreated _ ccLink -> showBotAddress ccLink + Right (CRUserContactLinkCreated _ ccLink) -> showBotAddress ccLink _ -> putStrLn "can't create bot address" >> exitFailure _ -> putStrLn "unexpected response" >> exitFailure where @@ -84,14 +84,14 @@ sendComposedMessages_ :: ChatController -> SendRef -> NonEmpty (Maybe ChatItemId sendComposedMessages_ cc sendRef qmcs = do let cms = L.map (\(qiId, mc) -> ComposedMessage {fileSource = Nothing, quotedItemId = qiId, msgContent = mc, mentions = M.empty}) qmcs sendChatCmd cc (APISendMessages sendRef False Nothing cms) >>= \case - CRNewChatItems {} -> printLog cc CLLInfo $ "sent " <> show (length cms) <> " messages to " <> show sendRef + Right (CRNewChatItems {}) -> printLog cc CLLInfo $ "sent " <> show (length cms) <> " messages to " <> show sendRef r -> putStrLn $ "unexpected send message response: " <> show r deleteMessage :: ChatController -> Contact -> ChatItemId -> IO () deleteMessage cc ct chatItemId = do let cmd = APIDeleteChatItem (contactRef ct) [chatItemId] CIDMInternal sendChatCmd cc cmd >>= \case - CRChatItemsDeleted {} -> printLog cc CLLInfo $ "deleted message(s) from " <> contactInfo ct + Right (CRChatItemsDeleted {}) -> printLog cc CLLInfo $ "deleted message(s) from " <> contactInfo ct r -> putStrLn $ "unexpected delete message response: " <> show r contactRef :: Contact -> ChatRef diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index a3b9f34346..d1bdeba341 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -172,10 +172,10 @@ data ChatHooks = ChatHooks { -- preCmdHook can be used to process or modify the commands before they are processed. -- This hook should be used to process CustomChatCommand. -- if this hook returns ChatResponse, the command processing will be skipped. - preCmdHook :: Maybe (ChatController -> ChatCommand -> IO (Either ChatResponse ChatCommand)), + preCmdHook :: Maybe (ChatController -> ChatCommand -> IO (Either (Either ChatError ChatResponse) ChatCommand)), -- eventHook can be used to additionally process or modify events, -- it is called before the event is sent to the user (or to the UI). - eventHook :: Maybe (ChatController -> ChatEvent -> IO ChatEvent), + eventHook :: Maybe (ChatController -> Either ChatError ChatEvent -> IO (Either ChatError ChatEvent)), -- acceptMember hook can be used to accept or reject member connecting via group link without API calls acceptMember :: Maybe (GroupInfo -> GroupLinkInfo -> Profile -> IO (Either GroupRejectionReason (GroupAcceptance, GroupMemberRole))) } @@ -223,7 +223,7 @@ data ChatController = ChatController random :: TVar ChaChaDRG, eventSeq :: TVar Int, inputQ :: TBQueue String, - outputQ :: TBQueue (Maybe RemoteHostId, ChatEvent), + outputQ :: TBQueue (Maybe RemoteHostId, Either ChatError ChatEvent), connNetworkStatuses :: TMap AgentConnId NetworkStatus, subscriptionMode :: TVar SubscriptionMode, chatLock :: Lock, @@ -731,7 +731,6 @@ data ChatResponse | CRAgentSubs {activeSubs :: Map Text Int, pendingSubs :: Map Text Int, removedSubs :: Map Text [String]} | CRAgentSubsDetails {agentSubs :: SubscriptionsInfo} | CRAgentQueuesInfo {agentQueuesInfo :: AgentQueuesInfo} - | CRChatCmdError {user_ :: Maybe User, chatError :: ChatError} | CRAppSettings {appSettings :: AppSettings} | CRCustomChatResponse {user_ :: Maybe User, response :: Text} deriving (Show) @@ -839,8 +838,7 @@ data ChatEvent | CEvtAgentConnsDeleted {agentConnIds :: NonEmpty AgentConnId} | CEvtAgentUserDeleted {agentUserId :: Int64} | CEvtMessageError {user :: User, severity :: Text, errorMessage :: Text} - | CEvtChatError {user_ :: Maybe User, chatError :: ChatError} - | CEvtChatErrors {user_ :: Maybe User, chatErrors :: [ChatError]} + | CEvtChatErrors {chatErrors :: [ChatError]} | CEvtTimedAction {action :: String, durationMilliseconds :: Int64} | CEvtTerminalEvent TerminalEvent deriving (Show) @@ -869,7 +867,6 @@ data DeletedRcvQueue = DeletedRcvQueue } deriving (Show) --- some of these can only be used as command responses allowRemoteEvent :: ChatEvent -> Bool allowRemoteEvent = \case CEvtChatSuspended -> False @@ -893,8 +890,7 @@ logEventToFile = \case CEvtAgentRcvQueuesDeleted {} -> True CEvtAgentConnsDeleted {} -> True CEvtAgentUserDeleted {} -> True - -- CEvtChatCmdError {} -> True -- TODO this should be separately logged to file - CEvtChatError {} -> True + -- CRChatCmdError {} -> True -- TODO this should be separately logged to file as command error CEvtMessageError {} -> True CEvtTerminalEvent te -> case te of TEMemberSubError {} -> True @@ -1408,7 +1404,7 @@ data RemoteCtrlSession tls :: TLS, rcsSession :: RCCtrlSession, http2Server :: Async (), - remoteOutputQ :: TBQueue ChatEvent + remoteOutputQ :: TBQueue (Either ChatError ChatEvent) } data RemoteCtrlSessionState @@ -1507,11 +1503,17 @@ mkStoreError :: SomeException -> StoreError mkStoreError = SEInternalError . show {-# INLINE mkStoreError #-} -chatCmdError :: Maybe User -> String -> ChatResponse -chatCmdError user = CRChatCmdError user . ChatError . CECommandError +throwCmdError :: String -> CM a +throwCmdError = throwError . ChatError . CECommandError +{-# INLINE throwCmdError #-} + +chatCmdError :: String -> Either ChatError ChatResponse +chatCmdError = Left . ChatError . CECommandError +{-# INLINE chatCmdError #-} throwChatError :: ChatErrorType -> CM a throwChatError = throwError . ChatError +{-# INLINE throwChatError #-} toViewTE :: TerminalEvent -> CM () toViewTE = toView . CEvtTerminalEvent @@ -1523,7 +1525,19 @@ toView = lift . toView' {-# INLINE toView #-} toView' :: ChatEvent -> CM' () -toView' ev = do +toView' = toView_ . Right +{-# INLINE toView' #-} + +eToView :: ChatError -> CM () +eToView = lift . eToView' +{-# INLINE eToView #-} + +eToView' :: ChatError -> CM' () +eToView' = toView_ . Left +{-# INLINE eToView' #-} + +toView_ :: Either ChatError ChatEvent -> CM' () +toView_ ev = do cc@ChatController {outputQ = localQ, remoteCtrlSession = session, config = ChatConfig {chatHooks}} <- ask event <- case eventHook chatHooks of Just hook -> liftIO $ hook cc ev @@ -1531,7 +1545,7 @@ toView' ev = do atomically $ readTVar session >>= \case Just (_, RCSessionConnected {remoteOutputQ}) - | allowRemoteEvent event -> writeTBQueue remoteOutputQ event + | either (const True) allowRemoteEvent event -> writeTBQueue remoteOutputQ event -- TODO potentially, it should hold some events while connecting _ -> writeTBQueue localQ (Nothing, event) diff --git a/src/Simplex/Chat/Core.hs b/src/Simplex/Chat/Core.hs index 34fc0423fb..865eb6a760 100644 --- a/src/Simplex/Chat/Core.hs +++ b/src/Simplex/Chat/Core.hs @@ -8,6 +8,7 @@ module Simplex.Chat.Core runSimplexChat, sendChatCmdStr, sendChatCmd, + printResponseEvent, ) where @@ -23,9 +24,10 @@ import Simplex.Chat import Simplex.Chat.Controller import Simplex.Chat.Library.Commands import Simplex.Chat.Options (ChatOpts (..), CoreChatOpts (..)) +import Simplex.Chat.Remote.Types (RemoteHostId) import Simplex.Chat.Store.Profiles import Simplex.Chat.Types -import Simplex.Chat.View (serializeChatResponse) +import Simplex.Chat.View (ChatResponseEvent, serializeChatError, serializeChatResponse) import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation (..)) import Simplex.Messaging.Agent.Store.Common (DBStore, withTransaction) import System.Exit (exitFailure) @@ -62,10 +64,10 @@ runSimplexChat ChatOpts {maintenance} u cc chat a2 <- async $ chat u cc waitEither_ a1 a2 -sendChatCmdStr :: ChatController -> String -> IO ChatResponse +sendChatCmdStr :: ChatController -> String -> IO (Either ChatError ChatResponse) sendChatCmdStr cc s = runReaderT (execChatCommand Nothing . encodeUtf8 $ T.pack s) cc -sendChatCmd :: ChatController -> ChatCommand -> IO ChatResponse +sendChatCmd :: ChatController -> ChatCommand -> IO (Either ChatError ChatResponse) sendChatCmd cc cmd = runReaderT (execChatCommand' cmd) cc getSelectActiveUser :: DBStore -> IO (Maybe User) @@ -107,12 +109,17 @@ createActiveUser cc = do displayName <- T.pack <$> getWithPrompt "display name" let profile = Just Profile {displayName, fullName = "", image = Nothing, contactLink = Nothing, preferences = Nothing} execChatCommand' (CreateActiveUser NewUser {profile, pastTimestamp = False}) `runReaderT` cc >>= \case - CRActiveUser user -> pure user - r -> do - ts <- getCurrentTime - tz <- getCurrentTimeZone - putStrLn $ serializeChatResponse (Nothing, Nothing) ts tz Nothing r - loop + Right (CRActiveUser user) -> pure user + r -> printResponseEvent (Nothing, Nothing) (config cc) r >> loop + +printResponseEvent :: ChatResponseEvent r => (Maybe RemoteHostId, Maybe User) -> ChatConfig -> Either ChatError r -> IO () +printResponseEvent hu cfg = \case + Right r -> do + ts <- getCurrentTime + tz <- getCurrentTimeZone + putStrLn $ serializeChatResponse hu cfg ts tz (fst hu) r + Left e -> do + putStrLn $ serializeChatError True cfg e getWithPrompt :: String -> IO String getWithPrompt s = putStr (s <> ": ") >> hFlush stdout >> getLine diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 77871ccc1b..8e7fba8255 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -225,7 +225,7 @@ startReceiveUserFiles :: User -> CM () startReceiveUserFiles user = do filesToReceive <- withStore' (`getRcvFilesToReceive` user) forM_ filesToReceive $ \ft -> - flip catchChatError (toView . CEvtChatError (Just user)) $ + flip catchChatError eToView $ toView =<< receiveFileEvt' user ft False Nothing Nothing restoreCalls :: CM' () @@ -267,32 +267,28 @@ useServers as opDomains uss = xftp' = useServerCfgs SPXFTP as opDomains $ concatMap (servers' SPXFTP) uss in (smp', xftp') -execChatCommand :: Maybe RemoteHostId -> ByteString -> CM' ChatResponse -execChatCommand rh s = do - u <- readTVarIO =<< asks currentUser +execChatCommand :: Maybe RemoteHostId -> ByteString -> CM' (Either ChatError ChatResponse) +execChatCommand rh s = case parseChatCommand s of - Left e -> pure $ chatCmdError u e + Left e -> pure $ chatCmdError e Right cmd -> case rh of Just rhId - | allowRemoteCommand cmd -> execRemoteCommand u rhId cmd s - | otherwise -> pure $ CRChatCmdError u $ ChatErrorRemoteHost (RHId rhId) $ RHELocalCommand + | allowRemoteCommand cmd -> execRemoteCommand rhId cmd s + | otherwise -> pure $ Left $ ChatErrorRemoteHost (RHId rhId) $ RHELocalCommand _ -> do cc@ChatController {config = ChatConfig {chatHooks}} <- ask case preCmdHook chatHooks of - Just hook -> liftIO (hook cc cmd) >>= either pure (execChatCommand_ u) - Nothing -> execChatCommand_ u cmd + Just hook -> liftIO (hook cc cmd) >>= either pure execChatCommand' + Nothing -> execChatCommand' cmd -execChatCommand' :: ChatCommand -> CM' ChatResponse -execChatCommand' cmd = asks currentUser >>= readTVarIO >>= (`execChatCommand_` cmd) +execChatCommand' :: ChatCommand -> CM' (Either ChatError ChatResponse) +execChatCommand' cmd = handleCommandError $ processChatCommand cmd -execChatCommand_ :: Maybe User -> ChatCommand -> CM' ChatResponse -execChatCommand_ u cmd = handleCommandError u $ processChatCommand cmd +execRemoteCommand :: RemoteHostId -> ChatCommand -> ByteString -> CM' (Either ChatError ChatResponse) +execRemoteCommand rhId cmd s = handleCommandError $ getRemoteHostClient rhId >>= \rh -> processRemoteCommand rhId rh cmd s -execRemoteCommand :: Maybe User -> RemoteHostId -> ChatCommand -> ByteString -> CM' ChatResponse -execRemoteCommand u rhId cmd s = handleCommandError u $ getRemoteHostClient rhId >>= \rh -> processRemoteCommand rhId rh cmd s - -handleCommandError :: Maybe User -> CM ChatResponse -> CM' ChatResponse -handleCommandError u a = either (CRChatCmdError u) id <$> (runExceptT a `E.catches` ioErrors) +handleCommandError :: CM ChatResponse -> CM' (Either ChatError ChatResponse) +handleCommandError a = runExceptT a `E.catches` ioErrors where ioErrors = [ E.Handler $ \(e :: ExitCode) -> E.throwIO e, @@ -502,7 +498,7 @@ processChatCommand' vr = \case pure $ CRChatTags user tags APIGetChats {userId, pendingConnections, pagination, query} -> withUserId' userId $ \user -> do (errs, previews) <- partitionEithers <$> withFastStore' (\db -> getChatPreviews db vr user pendingConnections pagination query) - unless (null errs) $ toView $ CEvtChatErrors (Just user) (map ChatErrorStore errs) + unless (null errs) $ toView $ CEvtChatErrors (map ChatErrorStore errs) pure $ CRApiChats user previews APIGetChat (ChatRef cType cId) contentFilter pagination search -> withUser $ \user -> case cType of -- TODO optimize queries calculating ChatStats, currently they're disabled @@ -517,8 +513,8 @@ processChatCommand' vr = \case when (isJust contentFilter) $ throwChatError $ CECommandError "content filter not supported" (localChat, navInfo) <- withFastStore (\db -> getLocalChat db user cId pagination search) pure $ CRApiChat user (AChat SCTLocal localChat) navInfo - CTContactRequest -> pure $ chatCmdError (Just user) "not implemented" - CTContactConnection -> pure $ chatCmdError (Just user) "not supported" + CTContactRequest -> throwCmdError "not implemented" + CTContactConnection -> throwCmdError "not supported" APIGetChatItems pagination search -> withUser $ \user -> do chatItems <- withFastStore $ \db -> getAllChatItems db vr user pagination search pure $ CRChatItems user Nothing chatItems @@ -553,14 +549,14 @@ processChatCommand' vr = \case APICreateChatTag (ChatTagData emoji text) -> withUser $ \user -> withFastStore' $ \db -> do _ <- createChatTag db user emoji text CRChatTags user <$> getUserChatTags db user - APISetChatTags (ChatRef cType chatId) tagIds -> withUser $ \user -> withFastStore' $ \db -> case cType of - CTDirect -> do + APISetChatTags (ChatRef cType chatId) tagIds -> withUser $ \user -> case cType of + CTDirect -> withFastStore' $ \db -> do updateDirectChatTags db chatId (maybe [] L.toList tagIds) CRTagsUpdated user <$> getUserChatTags db user <*> getDirectChatTags db chatId - CTGroup -> do + CTGroup -> withFastStore' $ \db -> do updateGroupChatTags db chatId (maybe [] L.toList tagIds) CRTagsUpdated user <$> getUserChatTags db user <*> getGroupChatTags db chatId - _ -> pure $ chatCmdError (Just user) "not supported" + _ -> throwCmdError "not supported" APIDeleteChatTag tagId -> withUser $ \user -> do withFastStore' $ \db -> deleteChatTag db user tagId ok user @@ -622,7 +618,7 @@ processChatCommand' vr = \case assertUserGroupRole gInfo GRAuthor let (_, ft_) = msgContentTexts mc if prohibitedSimplexLinks gInfo membership ft_ - then pure $ chatCmdError (Just user) ("feature not allowed " <> T.unpack (groupFeatureNameText GFSimplexLinks)) + then throwCmdError ("feature not allowed " <> T.unpack (groupFeatureNameText GFSimplexLinks)) else do cci <- withFastStore $ \db -> getGroupCIWithReactions db user gInfo itemId case cci of @@ -660,8 +656,8 @@ processChatCommand' vr = \case ci' <- updateLocalChatItem' db user noteFolderId ci (CISndMsgContent mc) True pure $ CRChatItemUpdated user (AChatItem SCTLocal SMDSnd (LocalChat nf) ci') _ -> throwChatError CEInvalidChatItemUpdate - CTContactRequest -> pure $ chatCmdError (Just user) "not supported" - CTContactConnection -> pure $ chatCmdError (Just user) "not supported" + CTContactRequest -> throwCmdError "not supported" + CTContactConnection -> throwCmdError "not supported" APIDeleteChatItem (ChatRef cType chatId) itemIds mode -> withUser $ \user -> case cType of CTDirect -> withContactLock "deleteChatItem" chatId $ do (ct, items) <- getCommandDirectChatItems user chatId itemIds @@ -697,8 +693,8 @@ processChatCommand' vr = \case CTLocal -> do (nf, items) <- getCommandLocalChatItems user chatId itemIds deleteLocalCIs user nf items True False - CTContactRequest -> pure $ chatCmdError (Just user) "not supported" - CTContactConnection -> pure $ chatCmdError (Just user) "not supported" + CTContactRequest -> throwCmdError "not supported" + CTContactConnection -> throwCmdError "not supported" where assertDeletable :: forall c. ChatTypeI c => [CChatItem c] -> CM () assertDeletable items = do @@ -781,9 +777,9 @@ processChatCommand' vr = \case r = ACIReaction SCTGroup SMDSnd (GroupChat g) $ CIReaction CIGroupSnd ci' createdAt reaction pure $ CRChatItemReaction user add r _ -> throwChatError $ CECommandError "reaction not possible - no shared item ID" - CTLocal -> pure $ chatCmdError (Just user) "not supported" - CTContactRequest -> pure $ chatCmdError (Just user) "not supported" - CTContactConnection -> pure $ chatCmdError (Just user) "not supported" + CTLocal -> throwCmdError "not supported" + CTContactRequest -> throwCmdError "not supported" + CTContactConnection -> throwCmdError "not supported" where checkReactionAllowed rs = do when ((reaction `elem` rs) == add) $ @@ -799,8 +795,8 @@ processChatCommand' vr = \case CTDirect -> planForward user . snd =<< getCommandDirectChatItems user fromChatId itemIds CTGroup -> planForward user . snd =<< getCommandGroupChatItems user fromChatId itemIds CTLocal -> planForward user . snd =<< getCommandLocalChatItems user fromChatId itemIds - CTContactRequest -> pure $ chatCmdError (Just user) "not supported" - CTContactConnection -> pure $ chatCmdError (Just user) "not supported" + CTContactRequest -> throwCmdError "not supported" + CTContactConnection -> throwCmdError "not supported" where planForward :: User -> [CChatItem c] -> CM ChatResponse planForward user items = do @@ -863,8 +859,8 @@ processChatCommand' vr = \case Just cmrs' -> createNoteFolderContentItems user toChatId cmrs' Nothing -> pure $ CRNewChatItems user [] - CTContactRequest -> pure $ chatCmdError (Just user) "not supported" - CTContactConnection -> pure $ chatCmdError (Just user) "not supported" + CTContactRequest -> throwCmdError "not supported" + CTContactConnection -> throwCmdError "not supported" where prepareForward :: User -> CM [ComposedMessageReq] prepareForward user = case fromCType of @@ -1004,8 +1000,8 @@ processChatCommand' vr = \case user <- withFastStore $ \db -> getUserByNoteFolderId db chatId withFastStore' $ \db -> updateLocalChatItemsRead db user chatId ok user - CTContactRequest -> pure $ chatCmdError Nothing "not supported" - CTContactConnection -> pure $ chatCmdError Nothing "not supported" + CTContactRequest -> throwCmdError "not supported" + CTContactConnection -> throwCmdError "not supported" APIChatItemsRead chatRef@(ChatRef cType chatId) itemIds -> withUser $ \_ -> case cType of CTDirect -> do user <- withFastStore $ \db -> getUserByContactId db chatId @@ -1021,9 +1017,9 @@ processChatCommand' vr = \case setGroupChatItemsDeleteAt db user chatId timedItems =<< getCurrentTime forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt ok user - CTLocal -> pure $ chatCmdError Nothing "not supported" - CTContactRequest -> pure $ chatCmdError Nothing "not supported" - CTContactConnection -> pure $ chatCmdError Nothing "not supported" + CTLocal -> throwCmdError "not supported" + CTContactRequest -> throwCmdError "not supported" + CTContactConnection -> throwCmdError "not supported" APIChatUnread (ChatRef cType chatId) unreadChat -> withUser $ \user -> case cType of CTDirect -> do withFastStore $ \db -> do @@ -1040,7 +1036,7 @@ processChatCommand' vr = \case nf <- getNoteFolder db user chatId liftIO $ updateNoteFolderUnreadChat db user nf unreadChat ok user - _ -> pure $ chatCmdError (Just user) "not supported" + _ -> throwCmdError "not supported" APIDeleteChat cRef@(ChatRef cType chatId) cdm -> withUser $ \user@User {userId} -> case cType of CTDirect -> do ct <- withFastStore $ \db -> getContact db vr user chatId @@ -1074,10 +1070,10 @@ processChatCommand' vr = \case let doSendDel = contactReady ct && contactActive ct && notify when doSendDel $ void (sendDirectContactMessage user ct XDirectDel) `catchChatError` const (pure ()) contactConnIds <- map aConnId <$> withFastStore' (\db -> getContactConnections db vr userId ct) - deleteAgentConnectionsAsync' user contactConnIds doSendDel + deleteAgentConnectionsAsync' contactConnIds doSendDel CTContactConnection -> withConnectionLock "deleteChat contactConnection" chatId . procCmd $ do conn@PendingContactConnection {pccAgentConnId = AgentConnId acId} <- withFastStore $ \db -> getPendingContactConnection db userId chatId - deleteAgentConnectionAsync user acId + deleteAgentConnectionAsync acId withFastStore' $ \db -> deletePendingContactConnection db userId chatId pure $ CRContactConnectionDeleted user conn CTGroup -> do @@ -1100,8 +1096,8 @@ processChatCommand' vr = \case withFastStore' $ \db -> deleteGroupMembers db user gInfo withFastStore' $ \db -> deleteGroup db user gInfo pure $ CRGroupDeletedUser user gInfo - CTLocal -> pure $ chatCmdError (Just user) "not supported" - CTContactRequest -> pure $ chatCmdError (Just user) "not supported" + CTLocal -> throwCmdError "not supported" + CTContactRequest -> throwCmdError "not supported" APIClearChat (ChatRef cType chatId) -> withUser $ \user@User {userId} -> case cType of CTDirect -> do ct <- withFastStore $ \db -> getContact db vr user chatId @@ -1124,8 +1120,8 @@ processChatCommand' vr = \case withFastStore' $ \db -> deleteNoteFolderFiles db userId nf withFastStore' $ \db -> deleteNoteFolderCIs db user nf pure $ CRChatCleared user (AChatInfo SCTLocal $ LocalChat nf) - CTContactConnection -> pure $ chatCmdError (Just user) "not supported" - CTContactRequest -> pure $ chatCmdError (Just user) "not supported" + CTContactConnection -> throwCmdError "not supported" + CTContactRequest -> throwCmdError "not supported" APIAcceptContact incognito connReqId -> withUser $ \_ -> do userContactLinkId <- withFastStore $ \db -> getUserContactLinkIdByCReq db connReqId withUserContactLock "acceptContact" userContactLinkId $ do @@ -1172,7 +1168,7 @@ processChatCommand' vr = \case forM_ call_ $ \call -> updateCallItemStatus user ct call WCSDisconnected Nothing toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct) ci] ok user - else pure $ chatCmdError (Just user) ("feature not allowed " <> T.unpack (chatFeatureNameText CFCalls)) + else throwCmdError ("feature not allowed " <> T.unpack (chatFeatureNameText CFCalls)) SendCallInvitation cName callType -> withUser $ \user -> do contactId <- withFastStore $ \db -> getContactIdByName db user cName processChatCommand $ APISendCallInvitation contactId callType @@ -1286,7 +1282,7 @@ processChatCommand' vr = \case g <- getGroupInfo db vr user chatId liftIO $ setGroupUIThemes db user g uiThemes ok user - _ -> pure $ chatCmdError (Just user) "not supported" + _ -> throwCmdError "not supported" APIGetNtfToken -> withUser' $ \_ -> crNtfToken <$> withAgent getNtfToken APIRegisterToken token mode -> withUser $ \_ -> CRNtfTokenStatus <$> withAgent (\a -> registerNtfToken a token mode) @@ -1294,10 +1290,10 @@ processChatCommand' vr = \case APICheckToken token -> withUser $ \_ -> CRNtfTokenStatus <$> withAgent (`checkNtfToken` token) APIDeleteToken token -> withUser $ \_ -> withAgent (`deleteNtfToken` token) >> ok_ - APIGetNtfConns nonce encNtfInfo -> withUser $ \user -> do + APIGetNtfConns nonce encNtfInfo -> withUser $ \_ -> do ntfInfos <- withAgent $ \a -> getNotificationConns a nonce encNtfInfo (errs, ntfMsgs) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (getMsgConn db) (L.toList ntfInfos)) - unless (null errs) $ toView $ CEvtChatErrors (Just user) errs + unless (null errs) $ toView $ CEvtChatErrors errs pure $ CRNtfConns $ catMaybes ntfMsgs where getMsgConn :: DB.Connection -> NotificationInfo -> IO (Maybe NtfConn) @@ -1408,7 +1404,7 @@ processChatCommand' vr = \case oldTTL = fromMaybe globalTTL oldTTL_ when (newTTL > 0 && (newTTL < oldTTL || oldTTL == 0)) $ do lift $ setExpireCIFlag user False - expireChat user globalTTL `catchChatError` (toView . CEvtChatError (Just user)) + expireChat user globalTTL `catchChatError` eToView lift $ setChatItemsExpiration user globalTTL ttlCount ok user where @@ -1478,15 +1474,15 @@ processChatCommand' vr = \case liftIO $ updateGroupSettings db user chatId chatSettings pure ms forM_ (filter memberActive ms) $ \m -> forM_ (memberConnId m) $ \connId -> - withAgent (\a -> toggleConnectionNtfs a connId $ chatHasNtfs chatSettings) `catchChatError` (toView . CEvtChatError (Just user)) + withAgent (\a -> toggleConnectionNtfs a connId $ chatHasNtfs chatSettings) `catchChatError` eToView ok user - _ -> pure $ chatCmdError (Just user) "not supported" + _ -> throwCmdError "not supported" APISetMemberSettings gId gMemberId settings -> withUser $ \user -> do m <- withFastStore $ \db -> do liftIO $ updateGroupMemberSettings db user gId gMemberId settings getGroupMember db vr user gId gMemberId let ntfOn = showMessages $ memberSettings m - toggleNtf user m ntfOn + toggleNtf m ntfOn ok user APIContactInfo contactId -> withUser $ \user@User {userId} -> do -- [incognito] print user's incognito profile for this contact @@ -1704,7 +1700,7 @@ processChatCommand' vr = \case forM_ customUserProfileId $ \profileId -> deletePCCIncognitoProfile db user profileId createDirectConnection db newUser agConnId ccLink' ConnNew Nothing subMode initialChatVersion PQSupportOn - deleteAgentConnectionAsync user (aConnId' conn) + deleteAgentConnectionAsync (aConnId' conn) pure conn' APIConnectPlan userId cLink -> withUserId userId $ \user -> uncurry (CRConnectionPlan user) <$> connectPlan user cLink @@ -1779,7 +1775,7 @@ processChatCommand' vr = \case APIDeleteMyAddress userId -> withUserId userId $ \user@User {profile = p} -> do conns <- withFastStore $ \db -> getUserAddressConnections db vr user withChatLock "deleteMyAddress" $ do - deleteAgentConnectionsAsync user $ map aConnId conns + deleteAgentConnectionsAsync $ map aConnId conns withFastStore' (`deleteUserAddress` user) let p' = (fromLocalProfile p :: Profile) {contactLink = Nothing} r <- updateProfile_ user p' $ withFastStore' $ \db -> setUserProfileContactLink db user Nothing @@ -2019,7 +2015,7 @@ processChatCommand' vr = \case updateGroupMemberStatus db userId fromMember GSMemInvited updateGroupMemberStatus db userId membership GSMemInvited throwError e - updateCIGroupInvitationStatus user g CIGISAccepted `catchChatError` (toView . CEvtChatError (Just user)) + updateCIGroupInvitationStatus user g CIGISAccepted `catchChatError` eToView pure $ CRUserAcceptedGroupSent user g {membership = membership {memberStatus = GSMemAccepted}} Nothing Nothing -> throwChatError $ CEContactNotActive ct APIAcceptMember groupId gmId role -> withUser $ \user -> do @@ -2048,7 +2044,7 @@ processChatCommand' vr = \case (errs2, changed2, acis) <- changeRoleCurrentMems user g currentMems unless (null acis) $ toView $ CEvtNewChatItems user acis let errs = errs1 <> errs2 - unless (null errs) $ toView $ CEvtChatErrors (Just user) errs + unless (null errs) $ toView $ CEvtChatErrors errs pure $ CRMembersRoleUser {user, groupInfo = gInfo, members = changed1 <> changed2, toRole = newRole} -- same order is not guaranteed where selfSelected GroupInfo {membership} = elem (groupMemberId' membership) memberIds @@ -2136,9 +2132,9 @@ processChatCommand' vr = \case let acis = map (AChatItem SCTGroup SMDSnd (GroupChat gInfo)) $ rights cis_ unless (null acis) $ toView $ CEvtNewChatItems user acis (errs, blocked) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (updateGroupMemberBlocked db user gInfo mrs) blockMems) - unless (null errs) $ toView $ CEvtChatErrors (Just user) errs + unless (null errs) $ toView $ CEvtChatErrors errs -- TODO not batched - requires agent batch api - forM_ blocked $ \m -> toggleNtf user m (not blockFlag) + forM_ blocked $ \m -> toggleNtf m (not blockFlag) pure CRMembersBlockedForAllUser {user, groupInfo = gInfo, members = blocked, blocked = blockFlag} where sndItemData :: GroupMember -> SndMessage -> NewSndChatItemData c @@ -2161,7 +2157,7 @@ processChatCommand' vr = \case acis = acis2 <> acis3 errs = errs1 <> errs2 <> errs3 unless (null acis) $ toView $ CEvtNewChatItems user acis - unless (null errs) $ toView $ CEvtChatErrors (Just user) errs + unless (null errs) $ toView $ CEvtChatErrors errs when withMessages $ deleteMessages user gInfo $ currentMems <> pendingMems pure $ CRUserDeletedMembers user gInfo (deleted1 <> deleted2 <> deleted3) withMessages -- same order is not guaranteed where @@ -2357,7 +2353,7 @@ processChatCommand' vr = \case LastChats count_ -> withUser' $ \user -> do let count = fromMaybe 5000 count_ (errs, previews) <- partitionEithers <$> withFastStore' (\db -> getChatPreviews db vr user False (PTLast count) clqNoFilters) - unless (null errs) $ toView $ CEvtChatErrors (Just user) (map ChatErrorStore errs) + unless (null errs) $ toView $ CEvtChatErrors (map ChatErrorStore errs) pure $ CRChats previews LastMessages (Just chatName) count search -> withUser $ \user -> do chatRef <- getChatRef user chatName @@ -2403,7 +2399,7 @@ processChatCommand' vr = \case processChatCommand $ APISendMessages sendRef False Nothing [composedMessage (Just f) (MCImage "" fixedImagePreview)] ForwardFile chatName fileId -> forwardFile chatName fileId SendFile ForwardImage chatName fileId -> forwardFile chatName fileId SendImage - SendFileDescription _chatName _f -> pure $ chatCmdError Nothing "TODO" + SendFileDescription _chatName _f -> throwCmdError "TODO" -- TODO to use priority transactions we need a parameter that differentiates manual and automatic acceptance ReceiveFile fileId userApprovedRelays encrypted_ rcvInline_ filePath_ -> withUser $ \_ -> withFileLock "receiveFile" fileId . procCmd $ do @@ -2426,7 +2422,7 @@ processChatCommand' vr = \case throwChatError $ CEFileCancel fileId "file transfer is complete" | otherwise -> do fileAgentConnIds <- cancelSndFile user ftm fts True - deleteAgentConnectionsAsync user fileAgentConnIds + deleteAgentConnectionsAsync fileAgentConnIds withFastStore (\db -> liftIO $ lookupChatRefByFileId db user fileId) >>= \case Nothing -> pure () Just (ChatRef CTDirect contactId) -> do @@ -2447,7 +2443,7 @@ processChatCommand' vr = \case | rcvFileComplete fileStatus -> throwChatError $ CEFileCancel fileId "file transfer is complete" | otherwise -> case xftpRcvFile of Nothing -> do - cancelRcvFileTransfer user ftr >>= mapM_ (deleteAgentConnectionAsync user) + cancelRcvFileTransfer user ftr >>= mapM_ deleteAgentConnectionAsync ci <- withFastStore $ \db -> lookupChatItemByFileId db vr user fileId pure $ CRRcvFileCancelled user ci ftr Just XFTPRcvFile {agentRcvFileId} -> do @@ -2595,8 +2591,9 @@ processChatCommand' vr = \case GetAgentSubsDetails -> lift $ CRAgentSubsDetails <$> withAgent' getAgentSubscriptions GetAgentQueuesInfo -> lift $ CRAgentQueuesInfo <$> withAgent' getAgentQueuesInfo -- CustomChatCommand is unsupported, it can be processed in preCmdHook - -- in a modified CLI app or core - the hook should return Either ChatResponse ChatCommand - CustomChatCommand _cmd -> withUser $ \user -> pure $ chatCmdError (Just user) "not supported" + -- in a modified CLI app or core - the hook should return Either (Either ChatError ChatResponse) ChatCommand, + -- where Left means command result, and Right – some other command to be processed by this function. + CustomChatCommand _cmd -> withUser $ \_ -> throwCmdError "not supported" where procCmd :: CM ChatResponse -> CM ChatResponse procCmd = id @@ -2762,7 +2759,7 @@ processChatCommand' vr = \case let idsEvts = L.map ctSndEvent changedCts msgReqs_ <- lift $ L.zipWith ctMsgReq changedCts <$> createSndMessages idsEvts (errs, cts) <- partitionEithers . L.toList . L.zipWith (second . const) changedCts <$> deliverMessagesB msgReqs_ - unless (null errs) $ toView $ CEvtChatErrors (Just user) errs + unless (null errs) $ toView $ CEvtChatErrors errs let changedCts' = filter (\ChangedProfileContact {ct, ct'} -> directOrUsed ct' && mergedPreferences ct' /= mergedPreferences ct) cts lift $ createContactsSndFeatureItems user' changedCts' pure @@ -2802,7 +2799,7 @@ processChatCommand' vr = \case mergedProfile' = userProfileToSend user (fromLocalProfile <$> incognitoProfile) (Just ct') False when (mergedProfile' /= mergedProfile) $ withContactLock "updateProfile" (contactId' ct) $ do - void (sendDirectContactMessage user ct' $ XInfo mergedProfile') `catchChatError` (toView . CEvtChatError (Just user)) + void (sendDirectContactMessage user ct' $ XInfo mergedProfile') `catchChatError` eToView lift . when (directOrUsed ct') $ createSndFeatureItems user ct ct' pure $ CRContactPrefsUpdated user ct ct' runUpdateGroupProfile :: User -> Group -> GroupProfile -> CM ChatResponse @@ -3005,7 +3002,7 @@ processChatCommand' vr = \case deleteCIFiles user filesInfo withAgent (\a -> deleteUser a (aUserId user) delSMPQueues) `catchChatError` \case - e@(ChatErrorAgent NO_USER _) -> toView $ CEvtChatError (Just user) e + e@(ChatErrorAgent NO_USER _) -> eToView e e -> throwError e withFastStore' (`deleteUserRecord` user) when (activeUser user) $ chatWriteVar currentUser Nothing @@ -3058,7 +3055,7 @@ processChatCommand' vr = \case connectWithPlan :: User -> IncognitoEnabled -> ACreatedConnLink -> ConnectionPlan -> CM ChatResponse connectWithPlan user@User {userId} incognito ccLink plan | connectionPlanProceed plan = do - case plan of CPError e -> toView $ CEvtChatError (Just user) e; _ -> pure () + case plan of CPError e -> eToView e; _ -> pure () case plan of CPContactAddress (CAPContactViaAddress Contact {contactId}) -> processChatCommand $ APIConnectContactViaAddress userId incognito contactId @@ -3208,7 +3205,7 @@ processChatCommand' vr = \case let itemsData = prepareSndItemsData (L.toList cmrs) (L.toList ciFiles_) (L.toList quotedItems_) msgs_ when (length itemsData /= length cmrs) $ logError "sendContactContentMessages: cmrs and itemsData length mismatch" r@(_, cis) <- partitionEithers <$> saveSndChatItems user (CDDirectSnd ct) Nothing itemsData timed_ live - processSendErrs user r + processSendErrs r forM_ (timed_ >>= timedDeleteAt') $ \deleteAt -> forM_ cis $ \ci -> startProximateTimedItemThread user (ChatRef CTDirect contactId, chatItemId' ci) deleteAt @@ -3288,7 +3285,7 @@ processChatCommand' vr = \case when (length cis_ /= length cmrs) $ logError "sendGroupContentMessages: cmrs and cis_ length mismatch" createMemberSndStatuses cis_ msgs_ gsr let r@(_, cis) = partitionEithers cis_ - processSendErrs user r + processSendErrs r forM_ (timed_ >>= timedDeleteAt') $ \deleteAt -> forM_ cis $ \ci -> startProximateTimedItemThread user (ChatRef CTGroup groupId, chatItemId' ci) deleteAt @@ -3358,7 +3355,7 @@ processChatCommand' vr = \case case contactOrGroup of CGContact Contact {activeConn} -> forM_ activeConn $ \conn -> withFastStore' $ \db -> createSndFTDescrXFTP db user Nothing conn ft dummyFileDescr - CGGroup _ ms -> forM_ ms $ \m -> saveMemberFD m `catchChatError` (toView . CEvtChatError (Just user)) + CGGroup _ ms -> forM_ ms $ \m -> saveMemberFD m `catchChatError` eToView where -- we are not sending files to pending members, same as with inline files saveMemberFD m@GroupMember {activeConn = Just conn@Connection {connStatus}} = @@ -3377,23 +3374,23 @@ processChatCommand' vr = \case zipWith4 $ \(ComposedMessage {msgContent}, itemForwarded, ts, mm) f q -> \case Right msg -> Right $ NewSndChatItemData msg (CISndMsgContent msgContent) ts mm f q itemForwarded Left e -> Left e -- step over original error - processSendErrs :: User -> ([ChatError], [ChatItem c d]) -> CM () - processSendErrs user = \case + processSendErrs :: ([ChatError], [ChatItem c d]) -> CM () + processSendErrs = \case -- no errors ([], _) -> pure () -- at least one item is successfully created - (errs, _ci : _) -> toView $ CEvtChatErrors (Just user) errs + (errs, _ci : _) -> toView $ CEvtChatErrors errs -- single error ([err], []) -> throwError err -- multiple errors (errs@(err : _), []) -> do - toView $ CEvtChatErrors (Just user) errs + toView $ CEvtChatErrors errs throwError err getCommandDirectChatItems :: User -> Int64 -> NonEmpty ChatItemId -> CM (Contact, [CChatItem 'CTDirect]) getCommandDirectChatItems user ctId itemIds = do ct <- withFastStore $ \db -> getContact db vr user ctId (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getDirectCI db) (L.toList itemIds)) - unless (null errs) $ toView $ CEvtChatErrors (Just user) errs + unless (null errs) $ toView $ CEvtChatErrors errs pure (ct, items) where getDirectCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTDirect)) @@ -3402,7 +3399,7 @@ processChatCommand' vr = \case getCommandGroupChatItems user gId itemIds = do gInfo <- withFastStore $ \db -> getGroupInfo db vr user gId (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getGroupCI db gInfo) (L.toList itemIds)) - unless (null errs) $ toView $ CEvtChatErrors (Just user) errs + unless (null errs) $ toView $ CEvtChatErrors errs pure (gInfo, items) where getGroupCI :: DB.Connection -> GroupInfo -> ChatItemId -> IO (Either ChatError (CChatItem 'CTGroup)) @@ -3411,7 +3408,7 @@ processChatCommand' vr = \case getCommandLocalChatItems user nfId itemIds = do nf <- withStore $ \db -> getNoteFolder db user nfId (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getLocalCI db) (L.toList itemIds)) - unless (null errs) $ toView $ CEvtChatErrors (Just user) errs + unless (null errs) $ toView $ CEvtChatErrors errs pure (nf, items) where getLocalCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTLocal)) @@ -3536,7 +3533,7 @@ startExpireCIThread user@User {userId} = do liftIO $ threadDelay' delay interval <- asks $ ciExpirationInterval . config forever $ do - flip catchChatError' (toView' . CEvtChatError (Just user)) $ do + flip catchChatError' (eToView') $ do expireFlags <- asks expireCIFlags atomically $ TM.lookup userId expireFlags >>= \b -> unless (b == Just True) retry lift waitChatStartedAndActivated @@ -3568,7 +3565,7 @@ agentSubscriber = do q <- asks $ subQ . smpAgent forever (atomically (readTBQueue q) >>= process) `E.catchAny` \e -> do - toView' $ CEvtChatError Nothing $ ChatErrorAgent (CRITICAL True $ "Message reception stopped: " <> show e) Nothing + eToView' $ ChatErrorAgent (CRITICAL True $ "Message reception stopped: " <> show e) Nothing E.throwIO e where process :: (ACorrId, AEntityId, AEvt) -> CM' () @@ -3578,7 +3575,7 @@ agentSubscriber = do SAERcvFile -> processAgentMsgRcvFile corrId entId msg SAESndFile -> processAgentMsgSndFile corrId entId msg where - run action = action `catchChatError'` (toView' . CEvtChatError Nothing) + run action = action `catchChatError'` (eToView') type AgentBatchSubscribe = AgentClient -> [ConnId] -> ExceptT AgentErrorType IO (Map ConnId (Either AgentErrorType ())) @@ -3739,7 +3736,7 @@ subscribeUserConnections vr onlyNeeded agentBatchSubscribe user = do pendingConnSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId PendingContactConnection -> CM () pendingConnSubsToView rs = toViewTE . TEPendingSubSummary user . map (uncurry PendingSubStatus) . resultsFor rs withStore_ :: (DB.Connection -> User -> IO [a]) -> CM [a] - withStore_ a = withStore' (`a` user) `catchChatError` \e -> toView (CEvtChatError (Just user) e) $> [] + withStore_ a = withStore' (`a` user) `catchChatError` \e -> eToView e $> [] filterErrors :: [(a, Maybe ChatError)] -> [(a, ChatError)] filterErrors = mapMaybe (\(a, e_) -> (a,) <$> e_) resultsFor :: Map ConnId (Either AgentErrorType ()) -> Map ConnId a -> [(a, Maybe ChatError)] @@ -3761,28 +3758,28 @@ cleanupManager = do liftIO $ threadDelay' initialDelay stepDelay <- asks (cleanupManagerStepDelay . config) forever $ do - flip catchChatError (toView . CEvtChatError Nothing) $ do + flip catchChatError eToView $ do lift waitChatStartedAndActivated users <- withStore' getUsers let (us, us') = partition activeUser users forM_ us $ cleanupUser interval stepDelay forM_ us' $ cleanupUser interval stepDelay - cleanupMessages `catchChatError` (toView . CEvtChatError Nothing) + cleanupMessages `catchChatError` eToView -- TODO possibly, also cleanup async commands - cleanupProbes `catchChatError` (toView . CEvtChatError Nothing) + cleanupProbes `catchChatError` eToView liftIO $ threadDelay' $ diffToMicroseconds interval where - runWithoutInitialDelay cleanupInterval = flip catchChatError (toView . CEvtChatError Nothing) $ do + runWithoutInitialDelay cleanupInterval = flip catchChatError eToView $ do lift waitChatStartedAndActivated users <- withStore' getUsers let (us, us') = partition activeUser users - forM_ us $ \u -> cleanupTimedItems cleanupInterval u `catchChatError` (toView . CEvtChatError (Just u)) - forM_ us' $ \u -> cleanupTimedItems cleanupInterval u `catchChatError` (toView . CEvtChatError (Just u)) + forM_ us $ \u -> cleanupTimedItems cleanupInterval u `catchChatError` eToView + forM_ us' $ \u -> cleanupTimedItems cleanupInterval u `catchChatError` eToView cleanupUser cleanupInterval stepDelay user = do - cleanupTimedItems cleanupInterval user `catchChatError` (toView . CEvtChatError (Just user)) + cleanupTimedItems cleanupInterval user `catchChatError` eToView liftIO $ threadDelay' stepDelay -- TODO remove in future versions: legacy step - contacts are no longer marked as deleted - cleanupDeletedContacts user `catchChatError` (toView . CEvtChatError (Just user)) + cleanupDeletedContacts user `catchChatError` eToView liftIO $ threadDelay' stepDelay cleanupTimedItems cleanupInterval user = do ts <- liftIO getCurrentTime @@ -3794,7 +3791,7 @@ cleanupManager = do contacts <- withStore' $ \db -> getDeletedContacts db vr user forM_ contacts $ \ct -> withStore (\db -> deleteContactWithoutGroups db user ct) - `catchChatError` (toView . CEvtChatError (Just user)) + `catchChatError` eToView cleanupMessages = do ts <- liftIO getCurrentTime let cutoffTs = addUTCTime (-(30 * nominalDay)) ts @@ -3820,7 +3817,7 @@ expireChatItems user@User {userId} globalTTL sync = do loop :: [Int64] -> (Int64 -> CM ()) -> CM () loop [] _ = pure () loop (a : as) process = continue $ do - process a `catchChatError` (toView . CEvtChatError (Just user)) + process a `catchChatError` eToView loop as process continue :: CM () -> CM () continue a = diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs index a2c8ae74b2..8158df5c94 100644 --- a/src/Simplex/Chat/Library/Internal.hs +++ b/src/Simplex/Chat/Library/Internal.hs @@ -184,11 +184,11 @@ callTimed ct aciContent = aciContentCallStatus (ACIContent _ (CIRcvCall st _)) = Just st aciContentCallStatus _ = Nothing -toggleNtf :: User -> GroupMember -> Bool -> CM () -toggleNtf user m ntfOn = +toggleNtf :: GroupMember -> Bool -> CM () +toggleNtf m ntfOn = when (memberActive m) $ forM_ (memberConnId m) $ \connId -> - withAgent (\a -> toggleConnectionNtfs a connId ntfOn) `catchChatError` (toView . CEvtChatError (Just user)) + withAgent (\a -> toggleConnectionNtfs a connId ntfOn) `catchChatError` eToView prepareGroupMsg :: DB.Connection -> User -> GroupInfo -> MsgContent -> Map MemberName MsgMention -> Maybe ChatItemId -> Maybe CIForwardedFrom -> Maybe FileInvitation -> Maybe CITimed -> Bool -> ExceptT StoreError IO (ChatMsgEvent 'Json, Maybe (CIQuote 'CTGroup)) prepareGroupMsg db user g@GroupInfo {membership} mc mentions quotedItemId_ itemForwarded fInv_ timed_ live = case (quotedItemId_, itemForwarded) of @@ -388,8 +388,8 @@ cancelFilesInProgress user filesInfo = do lift $ agentXFTPDeleteRcvFiles xrfIds let smpSFConnIds = concatMap (\(ft, sfts) -> mapMaybe (smpSndFileConnId ft) sfts) sfs smpRFConnIds = mapMaybe smpRcvFileConnId rfs - deleteAgentConnectionsAsync user smpSFConnIds - deleteAgentConnectionsAsync user smpRFConnIds + deleteAgentConnectionsAsync smpSFConnIds + deleteAgentConnectionsAsync smpRFConnIds where fileEnded CIFileInfo {fileStatus} = case fileStatus of Just (AFS _ status) -> ciFileEnded status @@ -446,7 +446,7 @@ deleteDirectCIs user ct items = do let ciFilesInfo = mapMaybe (\(CChatItem _ ChatItem {file}) -> mkCIFileInfo <$> file) items deleteCIFiles user ciFilesInfo (errs, deletions) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (deleteItem db) items) - unless (null errs) $ toView $ CEvtChatErrors (Just user) errs + unless (null errs) $ toView $ CEvtChatErrors errs pure deletions where deleteItem db (CChatItem md ci) = do @@ -458,7 +458,7 @@ deleteGroupCIs user gInfo items byGroupMember_ deletedTs = do let ciFilesInfo = mapMaybe (\(CChatItem _ ChatItem {file}) -> mkCIFileInfo <$> file) items deleteCIFiles user ciFilesInfo (errs, deletions) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (deleteItem db) items) - unless (null errs) $ toView $ CEvtChatErrors (Just user) errs + unless (null errs) $ toView $ CEvtChatErrors errs pure deletions where deleteItem :: DB.Connection -> CChatItem 'CTGroup -> IO ChatItemDeletion @@ -491,7 +491,7 @@ deleteLocalCIs user nf items byUser timed = do let ciFilesInfo = mapMaybe (\(CChatItem _ ChatItem {file}) -> mkCIFileInfo <$> file) items deleteFilesLocally ciFilesInfo (errs, deletions) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (deleteItem db) items) - unless (null errs) $ toView $ CEvtChatErrors (Just user) errs + unless (null errs) $ toView $ CEvtChatErrors errs pure $ CRChatItemsDeleted user deletions byUser timed where deleteItem db (CChatItem md ci) = do @@ -510,7 +510,7 @@ markDirectCIsDeleted user ct items deletedTs = do let ciFilesInfo = mapMaybe (\(CChatItem _ ChatItem {file}) -> mkCIFileInfo <$> file) items cancelFilesInProgress user ciFilesInfo (errs, deletions) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (markDeleted db) items) - unless (null errs) $ toView $ CEvtChatErrors (Just user) errs + unless (null errs) $ toView $ CEvtChatErrors errs pure deletions where markDeleted db (CChatItem md ci) = do @@ -522,7 +522,7 @@ markGroupCIsDeleted user gInfo items byGroupMember_ deletedTs = do let ciFilesInfo = mapMaybe (\(CChatItem _ ChatItem {file}) -> mkCIFileInfo <$> file) items cancelFilesInProgress user ciFilesInfo (errs, deletions) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (markDeleted db) items) - unless (null errs) $ toView $ CEvtChatErrors (Just user) errs + unless (null errs) $ toView $ CEvtChatErrors errs pure deletions -- pure $ CRChatItemsDeleted user deletions byUser False where @@ -998,7 +998,7 @@ introduceToGroup vr user gInfo@GroupInfo {groupId, membership} m@GroupMember {ac forM_ (L.nonEmpty events) $ \events' -> sendGroupMemberMessages user conn events' groupId else forM_ shuffledIntros $ \intro -> - processIntro intro `catchChatError` (toView . CEvtChatError (Just user)) + processIntro intro `catchChatError` eToView memberIntro :: GroupMember -> ChatMsgEvent 'Json memberIntro reMember = let mInfo = memberInfo reMember @@ -1021,7 +1021,7 @@ introduceToGroup vr user gInfo@GroupInfo {groupId, membership} m@GroupMember {ac (errs, items) <- partitionEithers <$> withStore' (\db -> getGroupHistoryItems db user gInfo m 100) (errs', events) <- partitionEithers <$> mapM (tryChatError . itemForwardEvents) items let errors = map ChatErrorStore errs <> errs' - unless (null errors) $ toView $ CEvtChatErrors (Just user) errors + unless (null errors) $ toView $ CEvtChatErrors errors let events' = maybe (concat events) (\x -> concat events <> [x]) descrEvent_ forM_ (L.nonEmpty events') $ \events'' -> sendGroupMemberMessages user conn events'' groupId @@ -1121,7 +1121,7 @@ deleteGroupLinkIfExists user gInfo = do deleteGroupLink_ :: User -> GroupInfo -> Connection -> CM () deleteGroupLink_ user gInfo conn = do - deleteAgentConnectionAsync user $ aConnId conn + deleteAgentConnectionAsync $ aConnId conn withStore' $ \db -> deleteGroupLink db user gInfo startProximateTimedItemThread :: User -> (ChatRef, ChatItemId) -> UTCTime -> CM () @@ -1162,7 +1162,7 @@ deleteTimedItem user (ChatRef cType chatId, itemId) deleteAt = do deletedTs <- liftIO getCurrentTime deletions <- deleteGroupCIs user gInfo [ci] Nothing deletedTs toView $ CEvtChatItemsDeleted user deletions True True - _ -> toView . CEvtChatError (Just user) . ChatError $ CEInternalError "bad deleteTimedItem cType" + _ -> eToView $ ChatError $ CEInternalError "bad deleteTimedItem cType" startUpdatedTimedItemThread :: User -> ChatRef -> ChatItem c d -> ChatItem c d -> CM () startUpdatedTimedItemThread user chatRef ci ci' = @@ -1289,7 +1289,7 @@ sendFileChunk user ft@SndFileTransfer {fileId, fileStatus, agentConnId = AgentCo updateDirectCIFileStatus db vr user fileId CIFSSndComplete toView $ CEvtSndFileComplete user ci ft lift $ closeFileHandle fileId sndFiles - deleteAgentConnectionAsync user acId + deleteAgentConnectionAsync acId sendFileChunkNo :: SndFileTransfer -> Integer -> CM () sendFileChunkNo ft@SndFileTransfer {agentConnId = AgentConnId acId} chunkNo = do @@ -1337,7 +1337,7 @@ appendFileChunk ft@RcvFileTransfer {fileId, fileStatus, cryptoArgs, fileInvitati removeFile fsFilePath `catchChatError` \_ -> pure () renameFile tmpFile fsFilePath Left e -> do - toView $ CEvtChatError Nothing e + eToView e removeFile tmpFile `catchChatError` \_ -> pure () withStore' (`removeFileCryptoArgs` fileId) where @@ -1362,7 +1362,7 @@ isFileActive fileId files = do cancelRcvFileTransfer :: User -> RcvFileTransfer -> CM (Maybe ConnId) cancelRcvFileTransfer user ft@RcvFileTransfer {fileId, xftpRcvFile, rcvFileInline} = - cancel' `catchChatError` (\e -> toView (CEvtChatError (Just user) e) $> fileConnId) + cancel' `catchChatError` (\e -> eToView e $> fileConnId) where cancel' = do lift $ closeFileHandle fileId rcvFiles @@ -1380,13 +1380,13 @@ cancelRcvFileTransfer user ft@RcvFileTransfer {fileId, xftpRcvFile, rcvFileInlin cancelSndFile :: User -> FileTransferMeta -> [SndFileTransfer] -> Bool -> CM [ConnId] cancelSndFile user FileTransferMeta {fileId, xftpSndFile} fts sendCancel = do withStore' (\db -> updateFileCancelled db user fileId CIFSSndCancelled) - `catchChatError` (toView . CEvtChatError (Just user)) + `catchChatError` eToView case xftpSndFile of Nothing -> catMaybes <$> forM fts (\ft -> cancelSndFileTransfer user ft sendCancel) Just xsf -> do forM_ fts (\ft -> cancelSndFileTransfer user ft False) - lift (agentXFTPDeleteSndFileRemote user xsf fileId) `catchChatError` (toView . CEvtChatError (Just user)) + lift (agentXFTPDeleteSndFileRemote user xsf fileId) `catchChatError` eToView pure [] -- TODO v6.0 remove @@ -1394,7 +1394,7 @@ cancelSndFileTransfer :: User -> SndFileTransfer -> Bool -> CM (Maybe ConnId) cancelSndFileTransfer user@User {userId} ft@SndFileTransfer {fileId, connId, agentConnId = AgentConnId acId, fileStatus, fileInline} sendCancel = if fileStatus == FSCancelled || fileStatus == FSComplete then pure Nothing - else cancel' `catchChatError` (\e -> toView (CEvtChatError (Just user) e) $> fileConnId) + else cancel' `catchChatError` (\e -> eToView e $> fileConnId) where cancel' = do withStore' $ \db -> do @@ -1421,16 +1421,16 @@ deleteMembersConnections user members = deleteMembersConnections' user members F deleteMembersConnections' :: User -> [GroupMember] -> Bool -> CM () deleteMembersConnections' user members waitDelivery = do let memberConns = mapMaybe (\GroupMember {activeConn} -> activeConn) members - deleteAgentConnectionsAsync' user (map aConnId memberConns) waitDelivery + deleteAgentConnectionsAsync' (map aConnId memberConns) waitDelivery lift . void . withStoreBatch' $ \db -> map (\Connection {connId} -> deleteConnectionRecord db user connId) memberConns -deleteMemberConnection :: User -> GroupMember -> CM () -deleteMemberConnection user mem = deleteMemberConnection' user mem False +deleteMemberConnection :: GroupMember -> CM () +deleteMemberConnection mem = deleteMemberConnection' mem False -deleteMemberConnection' :: User -> GroupMember -> Bool -> CM () -deleteMemberConnection' user GroupMember {activeConn} waitDelivery = do +deleteMemberConnection' :: GroupMember -> Bool -> CM () +deleteMemberConnection' GroupMember {activeConn} waitDelivery = do forM_ activeConn $ \conn -> do - deleteAgentConnectionAsync' user (aConnId conn) waitDelivery + deleteAgentConnectionAsync' (aConnId conn) waitDelivery withStore' $ \db -> updateConnectionStatus db conn ConnDeleted deleteOrUpdateMemberRecord :: User -> GroupMember -> CM () @@ -1515,7 +1515,7 @@ sendGroupMemberMessages user conn events groupId = do when (connDisabled conn) $ throwChatError (CEConnectionDisabled conn) let idsEvts = L.map (GroupId groupId,) events (errs, msgs) <- lift $ partitionEithers . L.toList <$> createSndMessages idsEvts - unless (null errs) $ toView $ CEvtChatErrors (Just user) errs + unless (null errs) $ toView $ CEvtChatErrors errs forM_ (L.nonEmpty msgs) $ \msgs' -> batchSendConnMessages user conn MsgFlags {notification = True} msgs' @@ -1644,7 +1644,7 @@ sendGroupMessages :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> Non sendGroupMessages user gInfo members events = do -- TODO [knocking] when sending to all, send profile update to pending approval members too, then filter for next step? when shouldSendProfileUpdate $ - sendProfileUpdate `catchChatError` (toView . CEvtChatError (Just user)) + sendProfileUpdate `catchChatError` eToView sendGroupMessages_ user gInfo members events where User {profile = p, userMemberProfileUpdatedAt} = user @@ -1800,10 +1800,10 @@ memberSendAction gInfo events members m@GroupMember {memberRole, memberStatus} = XGrpMsgForward {} -> True _ -> False -sendGroupMemberMessage :: MsgEncodingI e => User -> GroupInfo -> GroupMember -> ChatMsgEvent e -> Maybe Int64 -> CM () -> CM () -sendGroupMemberMessage user gInfo@GroupInfo {groupId} m@GroupMember {groupMemberId} chatMsgEvent introId_ postDeliver = do +sendGroupMemberMessage :: MsgEncodingI e => GroupInfo -> GroupMember -> ChatMsgEvent e -> Maybe Int64 -> CM () -> CM () +sendGroupMemberMessage gInfo@GroupInfo {groupId} m@GroupMember {groupMemberId} chatMsgEvent introId_ postDeliver = do msg <- createSndMessage chatMsgEvent (GroupId groupId) - messageMember msg `catchChatError` (toView . CEvtChatError (Just user)) + messageMember msg `catchChatError` eToView where messageMember :: SndMessage -> CM () messageMember SndMessage {msgId, msgBody} = forM_ (memberSendAction gInfo (chatMsgEvent :| []) [m] m) $ \case @@ -1986,20 +1986,22 @@ agentAcceptContactAsync user enableNtfs invId msg subMode pqSup chatV = do connId <- withAgent $ \a -> acceptContactAsync a (aCorrId cmdId) enableNtfs invId dm pqSup subMode pure (cmdId, connId) -deleteAgentConnectionAsync :: User -> ConnId -> CM () -deleteAgentConnectionAsync user acId = deleteAgentConnectionAsync' user acId False +deleteAgentConnectionAsync :: ConnId -> CM () +deleteAgentConnectionAsync acId = deleteAgentConnectionAsync' acId False +{-# INLINE deleteAgentConnectionAsync #-} -deleteAgentConnectionAsync' :: User -> ConnId -> Bool -> CM () -deleteAgentConnectionAsync' user acId waitDelivery = do - withAgent (\a -> deleteConnectionAsync a waitDelivery acId) `catchChatError` (toView . CEvtChatError (Just user)) +deleteAgentConnectionAsync' :: ConnId -> Bool -> CM () +deleteAgentConnectionAsync' acId waitDelivery = do + withAgent (\a -> deleteConnectionAsync a waitDelivery acId) `catchChatError` eToView -deleteAgentConnectionsAsync :: User -> [ConnId] -> CM () -deleteAgentConnectionsAsync user acIds = deleteAgentConnectionsAsync' user acIds False +deleteAgentConnectionsAsync :: [ConnId] -> CM () +deleteAgentConnectionsAsync acIds = deleteAgentConnectionsAsync' acIds False +{-# INLINE deleteAgentConnectionsAsync #-} -deleteAgentConnectionsAsync' :: User -> [ConnId] -> Bool -> CM () -deleteAgentConnectionsAsync' _ [] _ = pure () -deleteAgentConnectionsAsync' user acIds waitDelivery = do - withAgent (\a -> deleteConnectionsAsync a waitDelivery acIds) `catchChatError` (toView . CEvtChatError (Just user)) +deleteAgentConnectionsAsync' :: [ConnId] -> Bool -> CM () +deleteAgentConnectionsAsync' [] _ = pure () +deleteAgentConnectionsAsync' acIds waitDelivery = do + withAgent (\a -> deleteConnectionsAsync a waitDelivery acIds) `catchChatError` eToView agentXFTPDeleteRcvFile :: RcvFileId -> FileTransferId -> CM () agentXFTPDeleteRcvFile aFileId fileId = do @@ -2100,7 +2102,7 @@ createContactsFeatureItems :: createContactsFeatureItems user cts chatDir ciFeature ciOffer getPref = do let dirsCIContents = map contactChangedFeatures cts (errs, acis) <- partitionEithers <$> createInternalItemsForChats user Nothing dirsCIContents - unless (null errs) $ toView' $ CEvtChatErrors (Just user) errs + unless (null errs) $ toView' $ CEvtChatErrors errs toView' $ CEvtNewChatItems user acis where contactChangedFeatures :: (Contact, Contact) -> (ChatDirection 'CTDirect d, [CIContent d]) @@ -2182,7 +2184,7 @@ createLocalChatItems :: createLocalChatItems user cd itemsData createdAt = do withStore' $ \db -> updateChatTs db user cd createdAt (errs, items) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (createItem db) $ L.toList itemsData) - unless (null errs) $ toView $ CEvtChatErrors (Just user) errs + unless (null errs) $ toView $ CEvtChatErrors errs pure items where createItem :: DB.Connection -> (CIContent 'MDSnd, Maybe (CIFile 'MDSnd), Maybe CIForwardedFrom, (Text, Maybe MarkdownList)) -> IO (ChatItem 'CTLocal 'MDSnd) @@ -2195,9 +2197,7 @@ withUser' :: (User -> CM ChatResponse) -> CM ChatResponse withUser' action = asks currentUser >>= readTVarIO - >>= maybe (throwChatError CENoActiveUser) run - where - run u = action u `catchChatError` (pure . CRChatCmdError (Just u)) + >>= maybe (throwChatError CENoActiveUser) action withUser :: (User -> CM ChatResponse) -> CM ChatResponse withUser action = withUser' $ \user -> diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index 53aee8938f..d60faef639 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -96,14 +96,14 @@ processAgentMessage _ _ (DEL_RCVQS delQs) = processAgentMessage _ _ (DEL_CONNS connIds) = toView $ CEvtAgentConnsDeleted $ L.map AgentConnId connIds processAgentMessage _ "" (ERR e) = - toView $ CEvtChatError Nothing $ ChatErrorAgent e Nothing + eToView $ ChatErrorAgent e Nothing processAgentMessage corrId connId msg = do lockEntity <- critical (withStore (`getChatLockEntity` AgentConnId connId)) withEntityLock "processAgentMessage" lockEntity $ do vr <- chatVersionRange -- getUserByAConnId never throws logical errors, only SEDBBusyError can be thrown here critical (withStore' (`getUserByAConnId` AgentConnId connId)) >>= \case - Just user -> processAgentMessageConn vr user corrId connId msg `catchChatError` (toView . CEvtChatError (Just user)) + Just user -> processAgentMessageConn vr user corrId connId msg `catchChatError` eToView _ -> throwChatError $ CENoConnectionUser (AgentConnId connId) -- CRITICAL error will be shown to the user as alert with restart button in Android/desktop apps. @@ -144,7 +144,7 @@ processAgentMessageNoConn = \case errsEvent cErrs = do vr <- chatVersionRange errs <- lift $ rights <$> withStoreBatch' (\db -> map (getChatErr vr db) cErrs) - toView $ CEvtChatErrors Nothing errs + toView $ CEvtChatErrors errs where getChatErr :: VersionRangeChat -> DB.Connection -> (ConnId, AgentErrorType) -> IO ChatError getChatErr vr db (connId, err) = @@ -156,7 +156,7 @@ processAgentMsgSndFile _corrId aFileId msg = do (cRef_, fileId) <- withStore (`getXFTPSndFileDBIds` AgentSndFileId aFileId) withEntityLock_ cRef_ . withFileLock "processAgentMsgSndFile" fileId $ withStore' (`getUserByASndFileId` AgentSndFileId aFileId) >>= \case - Just user -> process user fileId `catchChatError` (toView . CEvtChatError (Just user)) + Just user -> process user fileId `catchChatError` eToView _ -> do lift $ withAgent' (`xftpDeleteSndFileInternal` aFileId) throwChatError $ CENoSndFileUser $ AgentSndFileId aFileId @@ -208,9 +208,9 @@ processAgentMsgSndFile _corrId aFileId msg = do Just rs -> case L.last rs of Right ([msgDeliveryId], _) -> withStore' $ \db -> updateSndFTDeliveryXFTP db sft msgDeliveryId - Right (deliveryIds, _) -> toView $ CEvtChatError (Just user) $ ChatError $ CEInternalError $ "SFDONE, sendFileDescriptions: expected 1 delivery id, got " <> show (length deliveryIds) - Left e -> toView $ CEvtChatError (Just user) e - Nothing -> toView $ CEvtChatError (Just user) $ ChatError $ CEInternalError "SFDONE, sendFileDescriptions: expected at least 1 result" + Right (deliveryIds, _) -> eToView $ ChatError $ CEInternalError $ "SFDONE, sendFileDescriptions: expected 1 delivery id, got " <> show (length deliveryIds) + Left e -> eToView e + Nothing -> eToView $ ChatError $ CEInternalError "SFDONE, sendFileDescriptions: expected at least 1 result" lift $ withAgent' (`xftpDeleteSndFileInternal` aFileId) (_, _, SMDSnd, GroupChat g@GroupInfo {groupId}) -> do ms <- withStore' $ \db -> getGroupMembers db vr user g @@ -259,7 +259,7 @@ processAgentMsgSndFile _corrId aFileId msg = do let (errs, msgReqs) = partitionEithers . L.toList $ L.zipWith (fmap . toMsgReq) connsIdsEvts sndMsgs_ delivered <- mapM deliverMessages (L.nonEmpty msgReqs) let errs' = errs <> maybe [] (lefts . L.toList) delivered - unless (null errs') $ toView $ CEvtChatErrors (Just user) errs' + unless (null errs') $ toView $ CEvtChatErrors errs' pure delivered where connDescrEvents :: Int -> NonEmpty (Connection, (ConnOrGroupId, ChatMsgEvent 'Json)) @@ -298,7 +298,7 @@ processAgentMsgRcvFile _corrId aFileId msg = do (cRef_, fileId) <- withStore (`getXFTPRcvFileDBIds` AgentRcvFileId aFileId) withEntityLock_ cRef_ . withFileLock "processAgentMsgRcvFile" fileId $ withStore' (`getUserByARcvFileId` AgentRcvFileId aFileId) >>= \case - Just user -> process user fileId `catchChatError` (toView . CEvtChatError (Just user)) + Just user -> process user fileId `catchChatError` eToView _ -> do lift $ withAgent' (`xftpDeleteRcvFile` aFileId) throwChatError $ CENoRcvFileUser $ AgentRcvFileId aFileId @@ -438,13 +438,13 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = MWARN _ err -> processConnMWARN connEntity conn err MERR _ err -> do - toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) + eToView (ChatErrorAgent err $ Just connEntity) processConnMERR connEntity conn err MERRS _ err -> do -- error cannot be AUTH error here - toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) + eToView (ChatErrorAgent err $ Just connEntity) ERR err -> do - toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) + eToView (ChatErrorAgent err $ Just connEntity) when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () -- TODO add debugging output _ -> pure () @@ -468,11 +468,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = checkIntegrityCreateItem (CDDirectRcv ct') msgMeta `catchChatError` \_ -> pure () forM_ aChatMsgs $ \case Right (ACMsg _ chatMsg) -> - processEvent ct' conn' tags eInfo chatMsg `catchChatError` \e -> toView $ CEvtChatError (Just user) e + processEvent ct' conn' tags eInfo chatMsg `catchChatError` \e -> eToView e Left e -> do atomically $ modifyTVar' tags ("error" :) logInfo $ "contact msg=error " <> eInfo <> " " <> tshow e - toView $ CEvtChatError (Just user) (ChatError . CEException $ "error parsing chat message: " <> e) + eToView (ChatError . CEException $ "error parsing chat message: " <> e) checkSendRcpt ct' $ rights aChatMsgs -- not crucial to use ct'' from processEvent where aChatMsgs = parseChatMessages msgBody @@ -655,14 +655,14 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = processConnMWARN connEntity conn err MERR msgId err -> do updateDirectItemStatus ct conn msgId (CISSndError $ agentSndError err) - toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) + eToView (ChatErrorAgent err $ Just connEntity) processConnMERR connEntity conn err MERRS msgIds err -> do -- error cannot be AUTH error here updateDirectItemsStatusMsgs ct conn (L.toList msgIds) (CISSndError $ agentSndError err) - toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) + eToView (ChatErrorAgent err $ Just connEntity) ERR err -> do - toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) + eToView (ChatErrorAgent err $ Just connEntity) when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () -- TODO add debugging output _ -> pure () @@ -757,7 +757,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = XInfo _ -> -- TODO Keep rejected member to allow them to appeal against rejection. when (memberStatus m == GSMemRejected) $ do - deleteMemberConnection' user m True + deleteMemberConnection' m True withStore' $ \db -> deleteGroupMember db user m XOk -> pure () _ -> messageError "INFO from member must have x.grp.mem.info, x.info or x.ok" @@ -831,12 +831,12 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = checkIntegrityCreateItem (CDGroupRcv gInfo m) msgMeta `catchChatError` \_ -> pure () forM_ aChatMsgs $ \case Right (ACMsg _ chatMsg) -> - processEvent tags eInfo chatMsg `catchChatError` \e -> toView $ CEvtChatError (Just user) e + processEvent tags eInfo chatMsg `catchChatError` \e -> eToView e Left e -> do atomically $ modifyTVar' tags ("error" :) logInfo $ "group msg=error " <> eInfo <> " " <> tshow e - toView $ CEvtChatError (Just user) (ChatError . CEException $ "error parsing chat message: " <> e) - forwardMsgs (rights aChatMsgs) `catchChatError` (toView . CEvtChatError (Just user)) + eToView (ChatError . CEException $ "error parsing chat message: " <> e) + forwardMsgs (rights aChatMsgs) `catchChatError` eToView checkSendRcpt $ rights aChatMsgs where aChatMsgs = parseChatMessages msgBody @@ -965,16 +965,16 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = MERR msgId err -> do withStore' $ \db -> updateGroupItemsErrorStatus db msgId (groupMemberId' m) (GSSError $ agentSndError err) -- group errors are silenced to reduce load on UI event log - -- toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) + -- eToView (ChatErrorAgent err $ Just connEntity) processConnMERR connEntity conn err MERRS msgIds err -> do let newStatus = GSSError $ agentSndError err -- error cannot be AUTH error here withStore' $ \db -> forM_ msgIds $ \msgId -> updateGroupItemsErrorStatus db msgId (groupMemberId' m) newStatus `catchAll_` pure () - toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) + eToView (ChatErrorAgent err $ Just connEntity) ERR err -> do - toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) + eToView (ChatErrorAgent err $ Just connEntity) when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () -- TODO add debugging output _ -> pure () @@ -1051,7 +1051,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = withStore' $ \db -> updateSndFileChunkSent db ft msgId unless (fileStatus == FSCancelled) $ sendFileChunk user ft MERR _ err -> do - cancelSndFileTransfer user ft True >>= mapM_ (deleteAgentConnectionAsync user) + cancelSndFileTransfer user ft True >>= mapM_ deleteAgentConnectionAsync case err of SMP _ SMP.AUTH -> unless (fileStatus == FSCancelled) $ do ci <- withStore $ \db -> do @@ -1070,7 +1070,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- [async agent commands] continuation on receiving JOINED when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () ERR err -> do - toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) + eToView (ChatErrorAgent err $ Just connEntity) when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () -- TODO add debugging output _ -> pure () @@ -1119,10 +1119,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- [async agent commands] continuation on receiving JOINED when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () MERR _ err -> do - toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) + eToView (ChatErrorAgent err $ Just connEntity) processConnMERR connEntity conn err ERR err -> do - toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) + eToView (ChatErrorAgent err $ Just connEntity) when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () -- TODO add debugging output _ -> pure () @@ -1131,7 +1131,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = receiveFileChunk ft@RcvFileTransfer {fileId, chunkSize} conn_ meta@MsgMeta {recipient = (msgId, _), integrity} = \case FileChunkCancel -> unless (rcvFileCompleteOrCancelled ft) $ do - cancelRcvFileTransfer user ft >>= mapM_ (deleteAgentConnectionAsync user) + cancelRcvFileTransfer user ft >>= mapM_ deleteAgentConnectionAsync ci <- withStore $ \db -> getChatItemByFileId db vr user fileId toView $ CEvtRcvFileSndCancelled user ci ft FileChunk {chunkNo, chunkBytes = chunk} -> do @@ -1157,7 +1157,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = deleteRcvFileChunks db ft getChatItemByFileId db vr user fileId toView $ CEvtRcvFileComplete user ci - forM_ conn_ $ \conn -> deleteAgentConnectionAsync user (aConnId conn) + mapM_ (deleteAgentConnectionAsync . aConnId) conn_ RcvChunkDuplicate -> withAckMessage' "file msg" agentConnId meta $ pure () RcvChunkError -> badRcvFileChunk ft $ "incorrect chunk number " <> show chunkNo @@ -1171,10 +1171,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- TODO show/log error, other events in contact request _ -> pure () MERR _ err -> do - toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) + eToView (ChatErrorAgent err $ Just connEntity) processConnMERR connEntity conn err ERR err -> do - toView $ CEvtChatError (Just user) (ChatErrorAgent err $ Just connEntity) + eToView (ChatErrorAgent err $ Just connEntity) when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () -- TODO add debugging output _ -> pure () @@ -1349,7 +1349,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = badRcvFileChunk :: RcvFileTransfer -> String -> CM () badRcvFileChunk ft err = unless (rcvFileCompleteOrCancelled ft) $ do - cancelRcvFileTransfer user ft >>= mapM_ (deleteAgentConnectionAsync user) + cancelRcvFileTransfer user ft >>= mapM_ deleteAgentConnectionAsync throwChatError $ CEFileRcvChunk err memberConnectedChatItem :: GroupInfo -> GroupMember -> CM () @@ -1816,7 +1816,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = fileId <- withStore $ \db -> getFileIdBySharedMsgId db userId contactId sharedMsgId ft <- withStore (\db -> getRcvFileTransfer db user fileId) unless (rcvFileCompleteOrCancelled ft) $ do - cancelRcvFileTransfer user ft >>= mapM_ (deleteAgentConnectionAsync user) + cancelRcvFileTransfer user ft >>= mapM_ deleteAgentConnectionAsync ci <- withStore $ \db -> getChatItemByFileId db vr user fileId toView $ CEvtRcvFileSndCancelled user ci ft @@ -1910,7 +1910,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = then do ft <- withStore (\db -> getRcvFileTransfer db user fileId) unless (rcvFileCompleteOrCancelled ft) $ do - cancelRcvFileTransfer user ft >>= mapM_ (deleteAgentConnectionAsync user) + cancelRcvFileTransfer user ft >>= mapM_ deleteAgentConnectionAsync ci <- withStore $ \db -> getChatItemByFileId db vr user fileId toView $ CEvtRcvFileSndCancelled user ci ft else messageError "x.file.cancel: group member attempted to cancel file of another member" -- shouldn't happen now that query includes group member id @@ -1997,7 +1997,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = then do ct' <- withStore' $ \db -> updateContactStatus db user c CSDeleted contactConns <- withStore' $ \db -> getContactConnections db vr userId ct' - deleteAgentConnectionsAsync user $ map aConnId contactConns + deleteAgentConnectionsAsync $ map aConnId contactConns forM_ contactConns $ \conn -> withStore' $ \db -> updateConnectionStatus db conn ConnDeleted activeConn' <- forM (contactConn ct') $ \conn -> pure conn {connStatus = ConnDeleted} let ct'' = ct' {activeConn = activeConn'} :: Contact @@ -2006,7 +2006,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = toView $ CEvtContactDeletedByContact user ct'' else do contactConns <- withStore' $ \db -> getContactConnections db vr userId c - deleteAgentConnectionsAsync user $ map aConnId contactConns + deleteAgentConnectionsAsync $ map aConnId contactConns withStore $ \db -> deleteContact db user c where brokerTs = metaBrokerTs msgMeta @@ -2447,7 +2447,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = Left _ -> messageError "x.grp.mem.inv error: referenced member does not exist" Right reMember -> do GroupMemberIntro {introId} <- withStore $ \db -> saveIntroInvitation db reMember m introInv - sendGroupMemberMessage user gInfo reMember (XGrpMemFwd (memberInfo m) introInv) (Just introId) $ + sendGroupMemberMessage gInfo reMember (XGrpMemFwd (memberInfo m) introInv) (Just introId) $ withStore' $ \db -> updateIntroStatus db introId GMIntroInvForwarded _ -> messageError "x.grp.mem.inv can be only sent by invitee member" @@ -2518,7 +2518,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = | senderRole < GRModerator || senderRole < memberRole -> messageError "x.grp.mem.restrict with insufficient member permissions" | otherwise -> do bm' <- setMemberBlocked bm - toggleNtf user bm' (not blocked) + toggleNtf bm' (not blocked) let ciContent = CIRcvGroupEvent $ RGEMemberBlocked bmId (fromLocalProfile bmp) blocked ci <- saveRcvChatItemNoParse user (CDGroupRcv gInfo m) msg brokerTs ciContent groupMsgToView gInfo ci @@ -2592,7 +2592,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = Right member@GroupMember {groupMemberId, memberProfile} -> checkRole member $ do -- ? prohibit deleting member if it's the sender - sender should use x.grp.leave - deleteMemberConnection user member + deleteMemberConnection member -- undeleted "member connected" chat item will prevent deletion of member record deleteOrUpdateMemberRecord user member when withMessages $ deleteMessages member SMDRcv @@ -2613,7 +2613,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = xGrpLeave :: GroupInfo -> GroupMember -> RcvMessage -> UTCTime -> CM () xGrpLeave gInfo m msg brokerTs = do - deleteMemberConnection user m + deleteMemberConnection m -- member record is not deleted to allow creation of "member left" chat item withStore' $ \db -> updateGroupMemberStatus db userId m GSMemLeft ci <- saveRcvChatItemNoParse user (CDGroupRcv gInfo m) msg brokerTs (CIRcvGroupEvent RGEMemberLeft) diff --git a/src/Simplex/Chat/Mobile.hs b/src/Simplex/Chat/Mobile.hs index 7f9f3eb505..194fc1bb06 100644 --- a/src/Simplex/Chat/Mobile.hs +++ b/src/Simplex/Chat/Mobile.hs @@ -13,6 +13,7 @@ import Control.Concurrent.STM import Control.Exception (SomeException, catch) import Control.Monad.Except import Control.Monad.Reader +import Data.Aeson (ToJSON (..)) import qualified Data.Aeson as J import qualified Data.Aeson.TH as JQ import Data.Bifunctor (first) @@ -72,13 +73,19 @@ data DBMigrationResult $(JQ.deriveToJSON (sumTypeJSON $ dropPrefix "DBM") ''DBMigrationResult) -data APIResponse = APIResponse {remoteHostId :: Maybe RemoteHostId, resp :: ChatResponse} +data APIResult r + = APIResult {remoteHostId :: Maybe RemoteHostId, result :: r} + | APIError {remoteHostId :: Maybe RemoteHostId, error :: ChatError} -data APIEvent = APIEvent {remoteHostId :: Maybe RemoteHostId, resp :: ChatEvent} +eitherToResult :: Maybe RemoteHostId -> Either ChatError r -> APIResult r +eitherToResult rhId = either (APIError rhId) (APIResult rhId) +{-# INLINE eitherToResult #-} -$(JQ.deriveToJSON defaultJSON ''APIResponse) +$(pure []) -$(JQ.deriveToJSON defaultJSON ''APIEvent) +instance ToJSON r => ToJSON (APIResult r) where + toEncoding = $(JQ.mkToEncoding (defaultJSON {J.sumEncoding = J.UntaggedValue}) ''APIResult) + toJSON = $(JQ.mkToJSON (defaultJSON {J.sumEncoding = J.UntaggedValue}) ''APIResult) foreign export ccall "chat_migrate_init" cChatMigrateInit :: CString -> CString -> CString -> Ptr (StablePtr ChatController) -> IO CJSONString @@ -290,15 +297,14 @@ chatSendCmd :: ChatController -> B.ByteString -> IO JSONByteString chatSendCmd cc = chatSendRemoteCmd cc Nothing chatSendRemoteCmd :: ChatController -> Maybe RemoteHostId -> B.ByteString -> IO JSONByteString -chatSendRemoteCmd cc rh s = J.encode . APIResponse rh <$> runReaderT (execChatCommand rh s) cc +chatSendRemoteCmd cc rh s = J.encode . eitherToResult rh <$> runReaderT (execChatCommand rh s) cc chatRecvMsg :: ChatController -> IO JSONByteString -chatRecvMsg ChatController {outputQ} = json <$> readChatResponse +chatRecvMsg ChatController {outputQ} = J.encode . uncurry eitherToResult <$> readChatResponse where - json (remoteHostId, resp) = J.encode APIEvent {remoteHostId, resp} readChatResponse = atomically (readTBQueue outputQ) >>= \case - (_, CEvtTerminalEvent {}) -> readChatResponse + (_, Right CEvtTerminalEvent {}) -> readChatResponse out -> pure out chatRecvMsgWait :: ChatController -> Int -> IO JSONByteString diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index bcdd60377f..105f872b75 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -370,16 +370,17 @@ processRemoteCommand :: RemoteHostId -> RemoteHostClient -> ChatCommand -> ByteS processRemoteCommand remoteHostId c cmd s = case cmd of SendFile chatName f -> sendFile "/f" chatName f SendImage chatName f -> sendFile "/img" chatName f - _ -> liftRH remoteHostId $ remoteSend c s + _ -> chatRemoteSend s where sendFile cmdName chatName (CryptoFile path cfArgs) = do -- don't encrypt in host if already encrypted locally CryptoFile path' cfArgs' <- storeRemoteFile remoteHostId (cfArgs $> False) path let f = CryptoFile path' (cfArgs <|> cfArgs') -- use local or host encryption - liftRH remoteHostId $ remoteSend c $ B.unwords [cmdName, B.pack (chatNameStr chatName), cryptoFileStr f] + chatRemoteSend $ B.unwords [cmdName, B.pack (chatNameStr chatName), cryptoFileStr f] cryptoFileStr CryptoFile {filePath, cryptoArgs} = maybe "" (\(CFArgs key nonce) -> "key=" <> strEncode key <> " nonce=" <> strEncode nonce <> " ") cryptoArgs <> encodeUtf8 (T.pack filePath) + chatRemoteSend = either throwError pure <=< liftRH remoteHostId . remoteSend c liftRH :: RemoteHostId -> ExceptT RemoteProtocolError IO a -> CM a liftRH rhId = liftError (ChatErrorRemoteHost (RHId rhId) . RHEProtocolError) @@ -496,7 +497,7 @@ parseCtrlAppInfo :: JT.Value -> CM CtrlAppInfo parseCtrlAppInfo ctrlAppInfo = do liftEitherWith (const $ ChatErrorRemoteCtrl RCEBadInvitation) $ JT.parseEither J.parseJSON ctrlAppInfo -handleRemoteCommand :: (ByteString -> CM' ChatResponse) -> RemoteCrypto -> TBQueue ChatEvent -> HTTP2Request -> CM' () +handleRemoteCommand :: (ByteString -> CM' (Either ChatError ChatResponse)) -> RemoteCrypto -> TBQueue (Either ChatError ChatEvent) -> HTTP2Request -> CM' () handleRemoteCommand execChatCommand encryption remoteOutputQ HTTP2Request {request, reqBody, sendResponse} = do logDebug "handleRemoteCommand" liftIO (tryRemoteError' parseRequest) >>= \case @@ -510,7 +511,7 @@ handleRemoteCommand execChatCommand encryption remoteOutputQ HTTP2Request {reque parseRequest = do (rfKN, header, getNext) <- parseDecryptHTTP2Body encryption request reqBody (rfKN,getNext,) <$> liftEitherWith RPEInvalidJSON (J.eitherDecode header) - replyError = reply . RRChatResponse . CRChatCmdError Nothing + replyError = reply . RRChatResponse . RRError processCommand :: User -> C.SbKeyNonce -> GetChunk -> RemoteCommand -> CM () processCommand user rfKN getNext = \case RCSend {command} -> lift $ handleSend execChatCommand command >>= reply @@ -527,7 +528,7 @@ handleRemoteCommand execChatCommand encryption remoteOutputQ HTTP2Request {reque send resp attach sfKN send flush - Left e -> toView' . CEvtChatError Nothing . ChatErrorRemoteCtrl $ RCEProtocolError e + Left e -> eToView' $ ChatErrorRemoteCtrl $ RCEProtocolError e takeRCStep :: RCStepTMVar a -> CM a takeRCStep = liftError' (\e -> ChatErrorAgent {agentError = RCP e, connectionEntity_ = Nothing}) . atomically . takeTMVar @@ -549,17 +550,17 @@ tryRemoteError' :: ExceptT RemoteProtocolError IO a -> IO (Either RemoteProtocol tryRemoteError' = tryAllErrors' (RPEException . tshow) {-# INLINE tryRemoteError' #-} -handleSend :: (ByteString -> CM' ChatResponse) -> Text -> CM' RemoteResponse +handleSend :: (ByteString -> CM' (Either ChatError ChatResponse)) -> Text -> CM' RemoteResponse handleSend execChatCommand command = do logDebug $ "Send: " <> tshow command -- execChatCommand checks for remote-allowed commands -- convert errors thrown in execChatCommand into error responses to prevent aborting the protocol wrapper - RRChatResponse <$> execChatCommand (encodeUtf8 command) + RRChatResponse . eitherToResult <$> execChatCommand (encodeUtf8 command) -handleRecv :: Int -> TBQueue ChatEvent -> IO RemoteResponse +handleRecv :: Int -> TBQueue (Either ChatError ChatEvent) -> IO RemoteResponse handleRecv time events = do logDebug $ "Recv: " <> tshow time - RRChatEvent <$> (timeout time . atomically $ readTBQueue events) + RRChatEvent . fmap eitherToResult <$> (timeout time . atomically $ readTBQueue events) -- TODO this command could remember stored files and return IDs to allow removing files that are not needed. -- Also, there should be some process removing unused files uploaded to remote host (possibly, all unused files). @@ -614,7 +615,7 @@ remoteCtrlInfo RemoteCtrl {remoteCtrlId, ctrlDeviceName} sessionState = RemoteCtrlInfo {remoteCtrlId, ctrlDeviceName, sessionState} -- | Take a look at emoji of tlsunique, commit pairing, and start session server -verifyRemoteCtrlSession :: (ByteString -> CM' ChatResponse) -> Text -> CM RemoteCtrlInfo +verifyRemoteCtrlSession :: (ByteString -> CM' (Either ChatError ChatResponse)) -> Text -> CM RemoteCtrlInfo verifyRemoteCtrlSession execChatCommand sessCode' = do (sseq, client, ctrlName, sessionCode, vars) <- chatReadVar remoteCtrlSession >>= \case diff --git a/src/Simplex/Chat/Remote/Protocol.hs b/src/Simplex/Chat/Remote/Protocol.hs index b572780a1f..207eade665 100644 --- a/src/Simplex/Chat/Remote/Protocol.hs +++ b/src/Simplex/Chat/Remote/Protocol.hs @@ -16,11 +16,11 @@ import Control.Monad.Except import Control.Monad.Reader import Crypto.Hash (SHA512) import qualified Crypto.Hash as CH -import Data.Aeson ((.=)) +import Data.Aeson (FromJSON (..), ToJSON (..), (.=)) import qualified Data.Aeson as J import qualified Data.Aeson.Key as JK import qualified Data.Aeson.KeyMap as JM -import Data.Aeson.TH (deriveJSON) +import qualified Data.Aeson.TH as JQ import qualified Data.Aeson.Types as JT import qualified Data.ByteArray as BA import Data.ByteString (ByteString) @@ -42,7 +42,7 @@ import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..)) import Simplex.Messaging.Crypto.Lazy (LazyByteString) import Simplex.Messaging.Encoding -import Simplex.Messaging.Parsers (dropPrefix, taggedObjectJSON, pattern SingleFieldJSONTag, pattern TaggedObjectJSONData, pattern TaggedObjectJSONTag) +import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, taggedObjectJSON, pattern SingleFieldJSONTag, pattern TaggedObjectJSONData, pattern TaggedObjectJSONTag) import qualified Simplex.Messaging.TMap as TM import Simplex.Messaging.Transport (TSbChainKeys) import Simplex.Messaging.Transport.Buffer (getBuffered) @@ -64,16 +64,40 @@ data RemoteCommand deriving (Show) data RemoteResponse - = RRChatResponse {chatResponse :: ChatResponse} - | RRChatEvent {chatEvent :: Maybe ChatEvent} -- 'Nothing' on poll timeout + = RRChatResponse {chatResponse :: RRResult ChatResponse} + | RRChatEvent {chatEvent :: Maybe (RRResult ChatEvent)} -- 'Nothing' on poll timeout | RRFileStored {filePath :: String} | RRFile {fileSize :: Word32, fileDigest :: FileDigest} -- provides attachment , fileDigest :: FileDigest | RRProtocolError {remoteProcotolError :: RemoteProtocolError} -- The protocol error happened on the server side deriving (Show) +data RRResult r + = RRResult {result :: r} + | RRError {error :: ChatError} + deriving (Show) + +resultToEither :: RRResult r -> Either ChatError r +resultToEither = \case + RRResult r -> Right r + RRError e -> Left e +{-# INLINE resultToEither #-} + +eitherToResult :: Either ChatError r -> RRResult r +eitherToResult = either RRError RRResult +{-# INLINE eitherToResult #-} + +$(pure []) + -- Force platform-independent encoding as the types aren't UI-visible -$(deriveJSON (taggedObjectJSON $ dropPrefix "RC") ''RemoteCommand) -$(deriveJSON (taggedObjectJSON $ dropPrefix "RR") ''RemoteResponse) +instance ToJSON r => ToJSON (RRResult r) where + toEncoding = $(JQ.mkToEncoding (defaultJSON {J.sumEncoding = J.UntaggedValue}) ''RRResult) + toJSON = $(JQ.mkToJSON (defaultJSON {J.sumEncoding = J.UntaggedValue}) ''RRResult) + +instance FromJSON r => FromJSON (RRResult r) where + parseJSON = $(JQ.mkParseJSON (defaultJSON {J.sumEncoding = J.UntaggedValue}) ''RRResult) + +$(JQ.deriveJSON (taggedObjectJSON $ dropPrefix "RC") ''RemoteCommand) +$(JQ.deriveJSON (taggedObjectJSON $ dropPrefix "RR") ''RemoteResponse) -- * Client side / desktop @@ -109,16 +133,16 @@ closeRemoteHostClient RemoteHostClient {httpClient} = closeHTTP2Client httpClien -- ** Commands -remoteSend :: RemoteHostClient -> ByteString -> ExceptT RemoteProtocolError IO ChatResponse +remoteSend :: RemoteHostClient -> ByteString -> ExceptT RemoteProtocolError IO (Either ChatError ChatResponse) remoteSend c cmd = sendRemoteCommand' c Nothing RCSend {command = decodeUtf8 cmd} >>= \case - RRChatResponse cr -> pure cr + RRChatResponse cr -> pure $ resultToEither cr r -> badResponse r -remoteRecv :: RemoteHostClient -> Int -> ExceptT RemoteProtocolError IO (Maybe ChatEvent) +remoteRecv :: RemoteHostClient -> Int -> ExceptT RemoteProtocolError IO (Maybe (Either ChatError ChatEvent)) remoteRecv c ms = sendRemoteCommand' c Nothing RCRecv {wait = ms} >>= \case - RRChatEvent cEvt_ -> pure cEvt_ + RRChatEvent cEvt_ -> pure $ resultToEither <$> cEvt_ r -> badResponse r remoteStoreFile :: RemoteHostClient -> FilePath -> FilePath -> ExceptT RemoteProtocolError IO FilePath @@ -172,7 +196,7 @@ convertJSON :: PlatformEncoding -> PlatformEncoding -> J.Value -> J.Value convertJSON _remote@PEKotlin _local@PEKotlin = id convertJSON PESwift PESwift = id convertJSON PESwift PEKotlin = owsf2tagged -convertJSON PEKotlin PESwift = error "unsupported convertJSON: K/S" -- guarded by handshake +convertJSON PEKotlin PESwift = Prelude.error "unsupported convertJSON: K/S" -- guarded by handshake -- | Convert swift single-field sum encoding into tagged/discriminator-field owsf2tagged :: J.Value -> J.Value diff --git a/src/Simplex/Chat/Terminal/Input.hs b/src/Simplex/Chat/Terminal/Input.hs index 06a1e0f314..7c52f59a50 100644 --- a/src/Simplex/Chat/Terminal/Input.hs +++ b/src/Simplex/Chat/Terminal/Input.hs @@ -64,12 +64,14 @@ runInputLoop ct@ChatTerminal {termState, liveMessageState} cc = forever $ do rh' = if either (const False) allowRemoteCommand cmd then rh else Nothing unless (isMessage cmd) $ echo s r <- runReaderT (execChatCommand rh' bs) cc - processResp s cmd rh r + case r of + Right r' -> processResp cmd rh r' + Left _ -> when (isMessage cmd) $ echo s printRespToTerminal ct cc False rh r - startLiveMessage cmd r + mapM_ (startLiveMessage cmd) r where echo s = printToTerminal ct [plain s] - processResp s cmd rh = \case + processResp cmd rh = \case CRActiveUser u -> case rh of Nothing -> setActive ct "" Just rhId -> updateRemoteUser ct u rhId @@ -80,7 +82,6 @@ runInputLoop ct@ChatTerminal {termState, liveMessageState} cc = forever $ do CRContactDeleted u c -> whenCurrUser cc u $ unsetActiveContact ct c CRGroupDeletedUser u g -> whenCurrUser cc u $ unsetActiveGroup ct g CRSentGroupInvitation u g _ _ -> whenCurrUser cc u $ setActiveGroup ct g - CRChatCmdError _ _ -> when (isMessage cmd) $ echo s CRCmdOk _ -> case cmd of Right APIDeleteUser {} -> setActive ct "" _ -> pure () @@ -132,7 +133,7 @@ runInputLoop ct@ChatTerminal {termState, liveMessageState} cc = forever $ do updateLiveMessage typedMsg lm = case liveMessageToSend typedMsg lm of Just sentMsg -> sendUpdatedLiveMessage cc sentMsg lm True >>= \case - CRChatItemUpdated {} -> setLiveMessage lm {sentMsg, typedMsg} + Right CRChatItemUpdated {} -> setLiveMessage lm {sentMsg, typedMsg} _ -> do -- TODO print error setLiveMessage lm {typedMsg} @@ -146,10 +147,10 @@ runInputLoop ct@ChatTerminal {termState, liveMessageState} cc = forever $ do | otherwise = (s <> reverse (c : w), "") startLiveMessage _ _ = pure () -sendUpdatedLiveMessage :: ChatController -> String -> LiveMessage -> Bool -> IO ChatResponse +sendUpdatedLiveMessage :: ChatController -> String -> LiveMessage -> Bool -> IO (Either ChatError ChatResponse) sendUpdatedLiveMessage cc sentMsg LiveMessage {chatName, chatItemId} live = do let cmd = UpdateLiveMessage chatName chatItemId live $ T.pack sentMsg - either (CRChatCmdError Nothing) id <$> runExceptT (processChatCommand cmd) `runReaderT` cc + runExceptT (processChatCommand cmd) `runReaderT` cc runTerminalInput :: ChatTerminal -> ChatController -> IO () runTerminalInput ct cc = withChatTerm ct $ do diff --git a/src/Simplex/Chat/Terminal/Main.hs b/src/Simplex/Chat/Terminal/Main.hs index af90340cbc..38b0e91a8a 100644 --- a/src/Simplex/Chat/Terminal/Main.hs +++ b/src/Simplex/Chat/Terminal/Main.hs @@ -1,4 +1,5 @@ {-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} module Simplex.Chat.Terminal.Main where @@ -7,15 +8,13 @@ import Control.Concurrent (forkIO, threadDelay) import Control.Concurrent.STM import Control.Monad import Data.Maybe (fromMaybe) -import Data.Time.Clock (getCurrentTime) -import Data.Time.LocalTime (getCurrentTimeZone) import Network.Socket -import Simplex.Chat.Controller (ChatConfig (..), ChatController (..), ChatEvent (..), PresetServers (..), SimpleNetCfg (..), currentRemoteHost, versionNumber, versionString) +import Simplex.Chat.Controller (ChatConfig (..), ChatController (..), ChatError, ChatEvent (..), PresetServers (..), SimpleNetCfg (..), currentRemoteHost, versionNumber, versionString) import Simplex.Chat.Core import Simplex.Chat.Options import Simplex.Chat.Options.DB import Simplex.Chat.Terminal -import Simplex.Chat.View (ChatResponseEvent, serializeChatResponse, smpProxyModeStr) +import Simplex.Chat.View (ChatResponseEvent, smpProxyModeStr) import Simplex.Messaging.Client (NetworkConfig (..), SocksMode (..)) import System.Directory (getAppUserDataDirectory) import System.Exit (exitFailure) @@ -45,17 +44,15 @@ simplexChatCLI' cfg opts@ChatOpts {chatCmd, chatCmdLog, chatCmdDelay, chatServer when (chatCmdLog /= CCLNone) . void . forkIO . forever $ do (_, r) <- atomically . readTBQueue $ outputQ cc case r of - CEvtNewChatItems {} -> printResponse r + Right CEvtNewChatItems {} -> printResponse r _ -> when (chatCmdLog == CCLAll) $ printResponse r sendChatCmdStr cc chatCmd >>= printResponse threadDelay $ chatCmdDelay * 1000000 where - printResponse :: ChatResponseEvent r => r -> IO () + printResponse :: ChatResponseEvent r => Either ChatError r -> IO () printResponse r = do - ts <- getCurrentTime - tz <- getCurrentTimeZone rh <- readTVarIO $ currentRemoteHost cc - putStrLn $ serializeChatResponse (rh, Just user) ts tz rh r + printResponseEvent (rh, Just user) cfg r welcome :: ChatConfig -> ChatOpts -> IO () welcome ChatConfig {presetServers = PresetServers {netCfg}} ChatOpts {coreOptions = CoreChatOpts {dbOptions, simpleNetCfg = SimpleNetCfg {socksProxy, socksMode, smpProxyMode_, smpProxyFallback_}}} = diff --git a/src/Simplex/Chat/Terminal/Output.hs b/src/Simplex/Chat/Terminal/Output.hs index 5134d0efc9..79fc08397c 100644 --- a/src/Simplex/Chat/Terminal/Output.hs +++ b/src/Simplex/Chat/Terminal/Output.hs @@ -7,6 +7,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} module Simplex.Chat.Terminal.Output where @@ -146,19 +147,19 @@ withTermLock ChatTerminal {termLock} action = do runTerminalOutput :: ChatTerminal -> ChatController -> ChatOpts -> IO () runTerminalOutput ct cc@ChatController {outputQ, showLiveItems, logFilePath} ChatOpts {markRead} = do forever $ do - (outputRH, r) <- atomically $ readTBQueue outputQ - case r of + (outputRH, r_) <- atomically $ readTBQueue outputQ + forM_ r_ $ \case CEvtNewChatItems u (ci : _) -> when markRead $ markChatItemRead u ci -- At the moment of writing received items are created one at a time CEvtChatItemUpdated u ci -> when markRead $ markChatItemRead u ci CEvtRemoteHostConnected {remoteHost = RemoteHostInfo {remoteHostId}} -> getRemoteUser remoteHostId CEvtRemoteHostStopped {remoteHostId_} -> mapM_ removeRemoteUser remoteHostId_ _ -> pure () let printEvent = case logFilePath of - Just path -> if logEventToFile r then logResponse path else printToTerminal ct + Just path -> if either (const True) logEventToFile r_ then logResponse path else printToTerminal ct _ -> printToTerminal ct liveItems <- readTVarIO showLiveItems - responseString ct cc liveItems outputRH r >>= printEvent - chatEventNotification ct cc r + responseString ct cc liveItems outputRH r_ >>= printEvent + mapM_ (chatEventNotification ct cc) r_ where markChatItemRead u (AChatItem _ _ chat ci@ChatItem {chatDir, meta = CIMeta {itemStatus}}) = case (chatDirNtf u chat chatDir (isUserMention ci), itemStatus) of @@ -170,7 +171,7 @@ runTerminalOutput ct cc@ChatController {outputQ, showLiveItems, logFilePath} Cha logResponse path s = withFile path AppendMode $ \h -> mapM_ (hPutStrLn h . unStyle) s getRemoteUser rhId = runReaderT (execChatCommand (Just rhId) "/user") cc >>= \case - CRActiveUser {user} -> updateRemoteUser ct user rhId + Right CRActiveUser {user} -> updateRemoteUser ct user rhId cr -> logError $ "Unexpected reply while getting remote user: " <> tshow cr removeRemoteUser rhId = atomically $ TM.delete rhId (currentRemoteUsers ct) @@ -271,15 +272,17 @@ whenCurrUser cc u a = do where sameUser User {userId = uId} = maybe False $ \User {userId} -> userId == uId -printRespToTerminal :: ChatTerminal -> ChatController -> Bool -> Maybe RemoteHostId -> ChatResponse -> IO () +printRespToTerminal :: ChatTerminal -> ChatController -> Bool -> Maybe RemoteHostId -> Either ChatError ChatResponse -> IO () printRespToTerminal ct cc liveItems outputRH r = responseString ct cc liveItems outputRH r >>= printToTerminal ct -responseString :: ChatResponseEvent r => ChatTerminal -> ChatController -> Bool -> Maybe RemoteHostId -> r -> IO [StyledString] -responseString ct cc liveItems outputRH r = do - cu <- getCurrentUser ct cc - ts <- getCurrentTime - tz <- getCurrentTimeZone - pure $ responseToView cu (config cc) liveItems ts tz outputRH r +responseString :: forall r. ChatResponseEvent r => ChatTerminal -> ChatController -> Bool -> Maybe RemoteHostId -> Either ChatError r -> IO [StyledString] +responseString ct cc liveItems outputRH = \case + Right r -> do + cu <- getCurrentUser ct cc + ts <- getCurrentTime + tz <- getCurrentTimeZone + pure $ responseToView cu (config cc) liveItems ts tz outputRH r + Left e -> pure $ chatErrorToView (isCommandResponse @r) (config cc) e updateRemoteUser :: ChatTerminal -> User -> RemoteHostId -> IO () updateRemoteUser ct user rhId = atomically $ TM.insert rhId user (currentRemoteUsers ct) diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index b87ba3a081..c07fcc952d 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-} @@ -38,7 +39,6 @@ import Data.Time.Format (defaultTimeLocale, formatTime) import qualified Data.Version as V import qualified Network.HTTP.Types as Q import Numeric (showFFloat) -import Simplex.Chat (defaultChatConfig) import Simplex.Chat.Call import Simplex.Chat.Controller import Simplex.Chat.Help @@ -87,15 +87,26 @@ data WCallCommand $(JQ.deriveToJSON (taggedObjectJSON $ dropPrefix "WCCall") ''WCallCommand) -serializeChatResponse :: ChatResponseEvent r => (Maybe RemoteHostId, Maybe User) -> CurrentTime -> TimeZone -> Maybe RemoteHostId -> r -> String -serializeChatResponse user_ ts tz remoteHost_ = unlines . map unStyle . responseToView user_ defaultChatConfig False ts tz remoteHost_ +serializeChatError :: Bool -> ChatConfig -> ChatError -> String +serializeChatError isCmd cfg = unlines . map unStyle . chatErrorToView isCmd cfg + +serializeChatResponse :: ChatResponseEvent r => (Maybe RemoteHostId, Maybe User) -> ChatConfig -> CurrentTime -> TimeZone -> Maybe RemoteHostId -> r -> String +serializeChatResponse hu cfg ts tz remoteHost_ = unlines . map unStyle . responseToView hu cfg False ts tz remoteHost_ class ChatResponseEvent r where responseToView :: (Maybe RemoteHostId, Maybe User) -> ChatConfig -> Bool -> CurrentTime -> TimeZone -> Maybe RemoteHostId -> r -> [StyledString] + isCommandResponse :: Bool -instance ChatResponseEvent ChatResponse where responseToView = chatResponseToView +instance ChatResponseEvent ChatResponse where + responseToView = chatResponseToView + isCommandResponse = True -instance ChatResponseEvent ChatEvent where responseToView = chatEventToView +instance ChatResponseEvent ChatEvent where + responseToView = chatEventToView + isCommandResponse = False + +chatErrorToView :: Bool -> ChatConfig -> ChatError -> [StyledString] +chatErrorToView isCmd ChatConfig {logLevel, testView} = viewChatError isCmd logLevel testView chatResponseToView :: (Maybe RemoteHostId, Maybe User) -> ChatConfig -> Bool -> CurrentTime -> TimeZone -> Maybe RemoteHostId -> ChatResponse -> [StyledString] chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, testView} liveItems ts tz outputRH = \case @@ -286,7 +297,6 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, testView} liveIte [ "agent queues info:", plain . LB.unpack $ J.encode agentQueuesInfo ] - CRChatCmdError u e -> ttyUserPrefix' u $ viewChatError True logLevel testView e CRAppSettings as -> ["app settings: " <> viewJSON as] CRCustomChatResponse u r -> ttyUser' u $ map plain $ T.lines r where @@ -296,8 +306,6 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, testView} liveIte | otherwise = [] ttyUser' :: Maybe User -> [StyledString] -> [StyledString] ttyUser' = maybe id ttyUser - ttyUserPrefix' :: Maybe User -> [StyledString] -> [StyledString] - ttyUserPrefix' = maybe id $ ttyUserPrefix hu outputRH testViewChats :: [AChat] -> [StyledString] testViewChats chats = [sShow $ map toChatView chats] where @@ -499,8 +507,7 @@ chatEventToView hu ChatConfig {logLevel, showReactions, showReceipts, testView} CEvtAgentConnsDeleted acIds -> ["completed deleting connections: " <> sShow (length acIds) | logLevel <= CLLInfo] CEvtAgentUserDeleted auId -> ["completed deleting user" <> if logLevel <= CLLInfo then ", agent user id: " <> sShow auId else ""] CEvtMessageError u prefix err -> ttyUser u [plain prefix <> ": " <> plain err | prefix == "error" || logLevel <= CLLWarning] - CEvtChatError u e -> ttyUser' u $ viewChatError False logLevel testView e - CEvtChatErrors u errs -> ttyUser' u $ concatMap (viewChatError False logLevel testView) errs + CEvtChatErrors errs -> concatMap (viewChatError False logLevel testView) errs CEvtTimedAction _ _ -> [] CEvtTerminalEvent te -> case te of TERejectingGroupJoinRequestMember _ g m reason -> [ttyFullMember m <> ": rejecting request to join group " <> ttyGroup' g <> ", reason: " <> sShow reason] diff --git a/tests/JSONFixtures.hs b/tests/JSONFixtures.hs index 32e90cf754..bd74f44022 100644 --- a/tests/JSONFixtures.hs +++ b/tests/JSONFixtures.hs @@ -5,55 +5,55 @@ module JSONFixtures where import qualified Data.ByteString.Lazy.Char8 as LB noActiveUserSwift :: LB.ByteString -noActiveUserSwift = "{\"resp\":{\"_owsf\":true,\"chatCmdError\":{\"chatError\":{\"_owsf\":true,\"error\":{\"errorType\":{\"_owsf\":true,\"noActiveUser\":{}}}}}}}" +noActiveUserSwift = "{\"error\":{\"_owsf\":true,\"error\":{\"errorType\":{\"_owsf\":true,\"noActiveUser\":{}}}}}" noActiveUserTagged :: LB.ByteString -noActiveUserTagged = "{\"resp\":{\"type\":\"chatCmdError\",\"chatError\":{\"type\":\"error\",\"errorType\":{\"type\":\"noActiveUser\"}}}}" +noActiveUserTagged = "{\"error\":{\"type\":\"error\",\"errorType\":{\"type\":\"noActiveUser\"}}}" activeUserExistsSwift :: LB.ByteString -activeUserExistsSwift = "{\"resp\":{\"_owsf\":true,\"chatCmdError\":{\"user_\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true},\"chatError\":{\"_owsf\":true,\"error\":{\"errorType\":{\"_owsf\":true,\"userExists\":{\"contactName\":\"alice\"}}}}}}}" +activeUserExistsSwift = "{\"error\":{\"_owsf\":true,\"error\":{\"errorType\":{\"_owsf\":true,\"userExists\":{\"contactName\":\"alice\"}}}}}" activeUserExistsTagged :: LB.ByteString -activeUserExistsTagged = "{\"resp\":{\"type\":\"chatCmdError\",\"user_\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true},\"chatError\":{\"type\":\"error\",\"errorType\":{\"type\":\"userExists\",\"contactName\":\"alice\"}}}}" +activeUserExistsTagged = "{\"error\":{\"type\":\"error\",\"errorType\":{\"type\":\"userExists\",\"contactName\":\"alice\"}}}" activeUserSwift :: LB.ByteString -activeUserSwift = "{\"resp\":{\"_owsf\":true,\"activeUser\":{\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}}" +activeUserSwift = "{\"result\":{\"_owsf\":true,\"activeUser\":{\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}}" activeUserTagged :: LB.ByteString -activeUserTagged = "{\"resp\":{\"type\":\"activeUser\",\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}" +activeUserTagged = "{\"result\":{\"type\":\"activeUser\",\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}" chatStartedSwift :: LB.ByteString -chatStartedSwift = "{\"resp\":{\"_owsf\":true,\"chatStarted\":{}}}" +chatStartedSwift = "{\"result\":{\"_owsf\":true,\"chatStarted\":{}}}" chatStartedTagged :: LB.ByteString -chatStartedTagged = "{\"resp\":{\"type\":\"chatStarted\"}}" +chatStartedTagged = "{\"result\":{\"type\":\"chatStarted\"}}" networkStatusesSwift :: LB.ByteString -networkStatusesSwift = "{\"resp\":{\"_owsf\":true,\"networkStatuses\":{\"user_\":" <> userJSON <> ",\"networkStatuses\":[]}}}" +networkStatusesSwift = "{\"result\":{\"_owsf\":true,\"networkStatuses\":{\"user_\":" <> userJSON <> ",\"networkStatuses\":[]}}}" networkStatusesTagged :: LB.ByteString -networkStatusesTagged = "{\"resp\":{\"type\":\"networkStatuses\",\"user_\":" <> userJSON <> ",\"networkStatuses\":[]}}" +networkStatusesTagged = "{\"result\":{\"type\":\"networkStatuses\",\"user_\":" <> userJSON <> ",\"networkStatuses\":[]}}" userJSON :: LB.ByteString userJSON = "{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}" memberSubSummarySwift :: LB.ByteString -memberSubSummarySwift = "{\"resp\":{\"_owsf\":true,\"memberSubSummary\":{\"user\":" <> userJSON <> ",\"memberSubscriptions\":[]}}}" +memberSubSummarySwift = "{\"result\":{\"_owsf\":true,\"memberSubSummary\":{\"user\":" <> userJSON <> ",\"memberSubscriptions\":[]}}}" memberSubSummaryTagged :: LB.ByteString -memberSubSummaryTagged = "{\"resp\":{\"type\":\"memberSubSummary\",\"user\":" <> userJSON <> ",\"memberSubscriptions\":[]}}" +memberSubSummaryTagged = "{\"result\":{\"type\":\"memberSubSummary\",\"user\":" <> userJSON <> ",\"memberSubscriptions\":[]}}" userContactSubSummarySwift :: LB.ByteString -userContactSubSummarySwift = "{\"resp\":{\"_owsf\":true,\"userContactSubSummary\":{\"user\":" <> userJSON <> ",\"userContactSubscriptions\":[]}}}" +userContactSubSummarySwift = "{\"result\":{\"_owsf\":true,\"userContactSubSummary\":{\"user\":" <> userJSON <> ",\"userContactSubscriptions\":[]}}}" userContactSubSummaryTagged :: LB.ByteString -userContactSubSummaryTagged = "{\"resp\":{\"type\":\"userContactSubSummary\",\"user\":" <> userJSON <> ",\"userContactSubscriptions\":[]}}" +userContactSubSummaryTagged = "{\"result\":{\"type\":\"userContactSubSummary\",\"user\":" <> userJSON <> ",\"userContactSubscriptions\":[]}}" pendingSubSummarySwift :: LB.ByteString -pendingSubSummarySwift = "{\"resp\":{\"_owsf\":true,\"pendingSubSummary\":{\"user\":" <> userJSON <> ",\"pendingSubscriptions\":[]}}}" +pendingSubSummarySwift = "{\"result\":{\"_owsf\":true,\"pendingSubSummary\":{\"user\":" <> userJSON <> ",\"pendingSubscriptions\":[]}}}" pendingSubSummaryTagged :: LB.ByteString -pendingSubSummaryTagged = "{\"resp\":{\"type\":\"pendingSubSummary\",\"user\":" <> userJSON <> ",\"pendingSubscriptions\":[]}}" +pendingSubSummaryTagged = "{\"result\":{\"type\":\"pendingSubSummary\",\"user\":" <> userJSON <> ",\"pendingSubscriptions\":[]}}" parsedMarkdownSwift :: LB.ByteString parsedMarkdownSwift = "{\"formattedText\":[{\"format\":{\"_owsf\":true,\"bold\":{}},\"text\":\"hello\"}]}" diff --git a/tests/MobileTests.hs b/tests/MobileTests.hs index 41d7280121..11a89bc62e 100644 --- a/tests/MobileTests.hs +++ b/tests/MobileTests.hs @@ -29,7 +29,7 @@ import Foreign.Storable (peek) import GHC.IO.Encoding (setLocaleEncoding, setFileSystemEncoding, setForeignEncoding) import JSONFixtures import Simplex.Chat.Controller (ChatController (..)) -import Simplex.Chat.Mobile +import Simplex.Chat.Mobile hiding (error) import Simplex.Chat.Mobile.File import Simplex.Chat.Mobile.Shared import Simplex.Chat.Mobile.WebRTC From 05de019ecd97d7fef3a97670f00af83702ffc847 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 7 May 2025 07:33:00 +0100 Subject: [PATCH 531/567] ios: deliver notifications instantly when server has no more messages and better concurrency (#5872) * core: return error and message absence when getting notifications * ios: do not wait for notification messages when server says "no" * do not postpone some notification events, comments * refactor * simplexmq (mapM) * simplexmq (release lock) * ios: inline, more aggressive GHC RTC settings for garbage collection * simplexmq * corrections Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> * refactor ntf delivery * ios: 6.3.4 (build 274) * simplexmq (fix updating last ts) * improve notification for multiple messages * simplexmq --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --- apps/ios/Shared/Model/AppAPITypes.swift | 2 +- apps/ios/SimpleX NSE/NSEAPITypes.swift | 2 +- .../ios/SimpleX NSE/NotificationService.swift | 641 +++++++++++------- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +- apps/ios/SimpleXChat/AppGroup.swift | 4 + apps/ios/SimpleXChat/ChatTypes.swift | 28 + apps/ios/SimpleXChat/hs_init.c | 6 +- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat/Controller.hs | 17 +- src/Simplex/Chat/Library/Commands.hs | 2 +- 11 files changed, 483 insertions(+), 263 deletions(-) diff --git a/apps/ios/Shared/Model/AppAPITypes.swift b/apps/ios/Shared/Model/AppAPITypes.swift index d5a067a2b8..fd89026cfe 100644 --- a/apps/ios/Shared/Model/AppAPITypes.swift +++ b/apps/ios/Shared/Model/AppAPITypes.swift @@ -856,7 +856,7 @@ enum ChatResponse2: Decodable, ChatAPIResult { case ntfTokenStatus(status: NtfTknStatus) case ntfToken(token: DeviceToken, status: NtfTknStatus, ntfMode: NotificationsMode, ntfServer: String) case ntfConns(ntfConns: [NtfConn]) - case connNtfMessages(receivedMsgs: [NtfMsgInfo?]) + case connNtfMessages(receivedMsgs: [RcvNtfMsgInfo]) // remote desktop responses case remoteCtrlList(remoteCtrls: [RemoteCtrlInfo]) case remoteCtrlConnecting(remoteCtrl_: RemoteCtrlInfo?, ctrlAppInfo: CtrlAppInfo, appVersion: String) diff --git a/apps/ios/SimpleX NSE/NSEAPITypes.swift b/apps/ios/SimpleX NSE/NSEAPITypes.swift index 6ab4a779d1..35a838fff9 100644 --- a/apps/ios/SimpleX NSE/NSEAPITypes.swift +++ b/apps/ios/SimpleX NSE/NSEAPITypes.swift @@ -53,7 +53,7 @@ enum NSEChatResponse: Decodable, ChatAPIResult { case chatRunning case rcvFileAccepted(user: UserRef, chatItem: AChatItem) case ntfConns(ntfConns: [NtfConn]) - case connNtfMessages(receivedMsgs: [NtfMsgInfo?]) + case connNtfMessages(receivedMsgs: [RcvNtfMsgInfo]) case ntfMessage(user: UserRef, connEntity: ConnectionEntity, ntfMessage: NtfMsgAckInfo) case cmdOk(user_: UserRef?) diff --git a/apps/ios/SimpleX NSE/NotificationService.swift b/apps/ios/SimpleX NSE/NotificationService.swift index bc783784cb..176da2481e 100644 --- a/apps/ios/SimpleX NSE/NotificationService.swift +++ b/apps/ios/SimpleX NSE/NotificationService.swift @@ -22,12 +22,6 @@ let nseSuspendSchedule: SuspendSchedule = (2, 4) let fastNSESuspendSchedule: SuspendSchedule = (1, 1) -enum NSENotification { - case nse(UNMutableNotificationContent) - case callkit(RcvCallInvitation) - case empty -} - public enum NSENotificationData { case connectionEvent(_ user: User, _ connEntity: ConnectionEntity) case contactConnected(_ user: any UserLike, _ contact: Contact) @@ -37,6 +31,7 @@ public enum NSENotificationData { case msgInfo(NtfMsgAckInfo) case noNtf + @inline(__always) var callInvitation: RcvCallInvitation? { switch self { case let .callInvitation(invitation): invitation @@ -56,8 +51,9 @@ public enum NSENotificationData { } } + @inline(__always) var notificationEvent: NSENotificationData? { - return switch self { + switch self { case .connectionEvent: self case .contactConnected: self case .contactRequest: self @@ -68,9 +64,10 @@ public enum NSENotificationData { } } - var newMsgData: (any UserLike, ChatInfo)? { - return switch self { - case let .messageReceived(user, cInfo, _): (user, cInfo) + @inline(__always) + var newMsgNtf: NSENotificationData? { + switch self { + case .messageReceived: self default: nil } } @@ -81,20 +78,25 @@ public enum NSENotificationData { // or when background notification is received. class NSEThreads { static let shared = NSEThreads() - static let queue = DispatchQueue(label: "chat.simplex.app.SimpleX-NSE.notification-threads.lock") + private let queue = DispatchQueue(label: "chat.simplex.app.SimpleX-NSE.notification-threads.lock") private var allThreads: Set = [] - var activeThreads: [(UUID, NotificationService)] = [] - var droppedNotifications: [(ChatId, NSENotificationData)] = [] + private var activeThreads: [(threadId: UUID, nse: NotificationService)] = [] + private var droppedNotifications: [(entityId: ChatId, ntf: NSENotificationData)] = [] + @inline(__always) + private init() {} // only shared instance can be used + + @inline(__always) func newThread() -> UUID { - NSEThreads.queue.sync { + queue.sync { let (_, t) = allThreads.insert(UUID()) return t } } + @inline(__always) func startThread(_ t: UUID, _ service: NotificationService) { - NSEThreads.queue.sync { + queue.sync { if allThreads.contains(t) { activeThreads.append((t, service)) } else { @@ -103,24 +105,111 @@ class NSEThreads { } } + // atomically: + // - checks that passed NSE instance can start processing passed notification entity, + // - adds it to the passed NSE instance, + // - marks as started, if no other NSE instance is processing it. + // Making all these steps atomic prevents a race condition between threads when both will be added and none will be started + @inline(__always) + func startEntity(_ nse: NotificationService, _ ntfEntity: NotificationEntity) -> Bool { + queue.sync { + // checking that none of activeThreads with another NSE instance processes the same entity and is not ready + let canStart = !activeThreads.contains(where: { (tId, otherNSE) in + tId != nse.threadId + && otherNSE.notificationEntities.contains(where: { (id, otherEntity) in + id == ntfEntity.entityId + && otherEntity.expectedMsg != nil + }) + }) + // atomically add entity to passed NSE instance + let id = ntfEntity.entityId + nse.notificationEntities[id] = ntfEntity + if canStart { + // and set as started, so it cannot be chosen to start by another NSE entity in nextThread + nse.notificationEntities[id]?.startedProcessingNewMsgs = true + } + return canStart + } + } + + @inline(__always) + func addDroppedNtf(_ id: ChatId, _ ntf: NSENotificationData) { + queue.sync { droppedNotifications.append((id, ntf)) } + } + + // atomically remove and return first dropped notification for the passed entity + @inline(__always) + func takeDroppedNtf(_ ntfEntity: NotificationEntity) -> (entityId: ChatId, ntf: NSENotificationData)? { + queue.sync { + if droppedNotifications.isEmpty { + nil + } else if let i = droppedNotifications.firstIndex(where: { (id, _) in id == ntfEntity.entityId }) { + droppedNotifications.remove(at: i) + } else { + nil + } + } + } + + // passes notification for processing to NSE instance chosen by rcvEntityThread + @inline(__always) func processNotification(_ id: ChatId, _ ntf: NSENotificationData) async -> Void { - if let (_, nse) = rcvEntityThread(id), - nse.expectedMessages[id]?.shouldProcessNtf ?? false { - nse.processReceivedNtf(id, ntf, signalReady: true) + if let (nse, ntfEntity, expectedMsg) = rcvEntityThread(id, ntf) { + logger.debug("NotificationService processNotification \(id): found nse thread expecting message") + if nse.processReceivedNtf(ntfEntity, expectedMsg, ntf) { + nse.finalizeEntity(id) + } } } - private func rcvEntityThread(_ id: ChatId) -> (UUID, NotificationService)? { - NSEThreads.queue.sync { + // atomically: + // - chooses active NSE instance that is ready to process notifications and expects message for passed entity ID + // - returns all dependencies for processing (notification entity and expected message) + // - adds notification to droppedNotifications if no ready NSE instance is found for the entity + @inline(__always) + private func rcvEntityThread(_ id: ChatId, _ ntf: NSENotificationData) -> (NotificationService, NotificationEntity, NtfMsgInfo)? { + queue.sync { // this selects the earliest thread that: - // 1) has this connection in nse.expectedMessages - // 2) has not completed processing messages for this connection (not ready) - activeThreads.first(where: { (_, nse) in nse.expectedMessages[id]?.ready == false }) + // 1) has this connection entity in nse.notificationEntitites + // 2) has not completed processing messages for this connection entity (not ready) + let r = activeThreads.lazy.compactMap({ (_, nse) in + let ntfEntity = nse.notificationEntities[id] + return if let ntfEntity, let expectedMsg = ntfEntity.expectedMsg, ntfEntity.shouldProcessNtf { + (nse, ntfEntity, expectedMsg) + } else { + nil + } + }).first + if r == nil { droppedNotifications.append((id, ntf)) } + return r } } + // Atomically mark entity in the passed NSE instance as not expecting messages, + // and signal the next NSE instance with this entity to start its processing. + @inline(__always) + func signalNextThread(_ nse: NotificationService, _ id: ChatId) { + queue.sync { + nse.notificationEntities[id]?.expectedMsg = nil + nse.notificationEntities[id]?.shouldProcessNtf = false + let next = activeThreads.first(where: { (_, nseNext) in + if let ntfEntity = nseNext.notificationEntities[id] { + ntfEntity.expectedMsg != nil && !ntfEntity.startedProcessingNewMsgs + } else { + false + } + }) + if let (tNext, nseNext) = next { + if let t = nse.threadId { logger.debug("NotificationService thread \(t): signalNextThread: signal next thread \(tNext) for entity \(id)") } + nseNext.notificationEntities[id]?.startedProcessingNewMsgs = true + nseNext.notificationEntities[id]?.semaphore.signal() + } + } + } + + @inline(__always) func endThread(_ t: UUID) -> Bool { - NSEThreads.queue.sync { + queue.sync { let tActive: UUID? = if let index = activeThreads.firstIndex(where: { $0.0 == t }) { activeThreads.remove(at: index).0 } else { @@ -137,24 +226,49 @@ class NSEThreads { } } + @inline(__always) var noThreads: Bool { allThreads.isEmpty } } -struct ExpectedMessage { +// NotificationEntity is a processing state for notifications from a single connection entity (message queue). +// Each NSE instance within NSE process can have more than one NotificationEntity. +// NotificationEntities of an NSE instance are processed concurrently, as messages arrive in any order. +// NotificationEntities for the same connection across multiple NSE instances (NSEThreads) are processed sequentially, so that the earliest NSE instance receives the earliest messages. +// The reason for this complexity is to process all required messages within allotted 30 seconds, +// accounting for the possibility that multiple notifications may be delivered concurrently. +struct NotificationEntity { var ntfConn: NtfConn - var expectedMsgId: String? - var allowedGetNextAttempts: Int - var msgBestAttemptNtf: NSENotificationData? - var ready: Bool - var shouldProcessNtf: Bool - var startedProcessingNewMsgs: Bool - var semaphore: DispatchSemaphore - + var entityId: ChatId + + // expectedMsg == nil means that entity already has the best attempt to deliver, and no more messages are expected. + // It happens when: + // - the user is muted (set to nil in mkNotificationEntity) + // - apiGetNtfConns returns that there are no new messages (msgId in notification matches previously received), + // - messaging server fails to respond or replies that there are no messages (apiGetConnNtfMessages / getConnNtfMessage), + // - the message is received with the correct ID or timestamp (set to nil in signalNextThread). + var expectedMsg: NtfMsgInfo? + var allowedGetNextAttempts: Int = 3 + var msgBestAttemptNtf: NSENotificationData + + // startedProcessingNewMsgs determines that the entity stared processing events once it processed dropped notifications. + // It remains true when shouldProcessNtf is set to false, to prevent NSE from being chosen as the next for the entity. + // It is atomically set to true by startThead or by nextThread + var startedProcessingNewMsgs: Bool = false + + // shouldProcessNtf determines that NSE should process events for this entity, + // it is atomically set: + // - to true in processDroppedNotifications in case dropped notification is not chosen for delivery, and more messages are needed. + // - to false in nextThread + var shouldProcessNtf: Bool = false + + // this semaphone is used to wait for another NSE instance processing events for the same entity + var semaphore: DispatchSemaphore = DispatchSemaphore(value: 0) + var connMsgReq: ConnMsgReq? { - if let expectedMsg_ = ntfConn.expectedMsg_ { - ConnMsgReq(msgConnId: ntfConn.agentConnId, msgDbQueueId: ntfConn.agentDbQueueId, msgTs: expectedMsg_.msgTs) + if let expectedMsg { + ConnMsgReq(msgConnId: ntfConn.agentConnId, msgDbQueueId: ntfConn.agentDbQueueId, msgTs: expectedMsg.msgTs) } else { nil } @@ -168,12 +282,12 @@ struct ExpectedMessage { class NotificationService: UNNotificationServiceExtension { var contentHandler: ((UNNotificationContent) -> Void)? // served as notification if no message attempts (msgBestAttemptNtf) could be produced - var serviceBestAttemptNtf: NSENotification? + var serviceBestAttemptNtf: UNMutableNotificationContent? var badgeCount: Int = 0 // thread is added to allThreads here - if thread did not start chat, // chat does not need to be suspended but NSE state still needs to be set to "suspended". var threadId: UUID? = NSEThreads.shared.newThread() - var expectedMessages: Dictionary = [:] // key is receiveEntityId + var notificationEntities: Dictionary = [:] // key is entityId var appSubscriber: AppSubscriber? var returnedSuspension = false @@ -199,12 +313,15 @@ class NotificationService: UNNotificationServiceExtension { setExpirationTimer() receiveNtfMessages(request) case .suspending: + // while application is suspending, the current instance will be waiting setExpirationTimer() Task { let state: AppState = await withCheckedContinuation { cont in + // this subscriber uses message delivery via NSFileCoordinator to communicate between the app and NSE appSubscriber = appStateSubscriber { s in if s == .suspended { appSuspension(s) } } + // this is a fallback timeout, in case message from the app does not arrive DispatchQueue.global().asyncAfter(deadline: .now() + Double(appSuspendTimeout) + 1) { logger.debug("NotificationService: appSuspension timeout") appSuspension(appStateGroupDefault.get()) @@ -232,12 +349,18 @@ class NotificationService: UNNotificationServiceExtension { } } + // This timer compensates for the scenarios when serviceExtensionTimeWillExpire does not fire at all. + // It is not clear why in some cases it does not fire, possibly it is a bug, + // or it depends on what the current thread is doing at the moment. + // If notification is not delivered and not cancelled, no further notifications will be processed. + @inline(__always) private func setExpirationTimer() -> Void { DispatchQueue.main.asyncAfter(deadline: .now() + 30) { self.deliverBestAttemptNtf(urgent: true) } } + @inline(__always) private func ntfRequestData(_ request: UNNotificationRequest) -> (nonce: String, encNtfInfo: String)? { if let ntfData = request.content.userInfo["notificationData"] as? [AnyHashable : Any], let nonce = ntfData["nonce"] as? String, @@ -247,7 +370,30 @@ class NotificationService: UNNotificationServiceExtension { nil } } - + + // This function triggers notification message delivery for connection entities referenced in the notification. + // Notification may reference multiple connection entities (message queues) in order to compensate for Apple servers + // only delivering the latest notification, so it allows receiving messages from up to 6 contacts and groups from a + // single notification. This aggregation is handled by a notification server and is delivered via APNS servers in + // e2e encrypted envelope, and the app core prevents duplicate processing by keeping track of the last processed message. + + // The process steps: + // 0. apiGetConnNtfMessages or getConnNtfMessage get messages from the server for passed connection entities. + // We don't know in advance which chat events will be delivered from app core for a given notification, + // it may be a message, but it can also be contact request, various protocol confirmations, calls, etc., + // this function only returns metadata for the expected chat events. + // This metadata is correlated with .ntfMessage core event / .msgInfo notification marker - + // this marker allows determining when some message completed processing. + // 1. receiveMessages: singleton loop receiving events from core. + // 2. receivedMsgNtf: maps core events to notification events. + // 3. NSEThreads.shared.processNotification: chooses which notification service instance in the current process should process notification. + // While most of the time we observe that notifications are delivered sequentially, nothing in the documentation confirms it is sequential, + // and from various sources it follows that each instance executes in its own thread, so concurrency is expected. + // 4. processReceivedNtf: one of the instances of NSE processes notification event, deciding whether to request further messages + // for a given connection entity (via getConnNtfMessage) or that the correct message was received and notification can be delivered (deliverBestAttemptNtf). + // It is based on .msgInfo markers that indicate that message with a given timestamp was processed. + // 5. deliverBestAttemptNtf: is called multiple times, once each connection receives enough messages (based on .msgInfo marker). + // If further messages are expected, this function does nothing (unless it is called with urgent flag from timeout/expiration handlers). func receiveNtfMessages(_ request: UNNotificationRequest) { logger.debug("NotificationService: receiveNtfMessages") if case .documents = dbContainerGroupDefault.get() { @@ -255,95 +401,115 @@ class NotificationService: UNNotificationServiceExtension { return } if let nrData = ntfRequestData(request), - // check it here again + // Check that the app is still inactive before starting the core. appStateGroupDefault.get().inactive { // thread is added to activeThreads tracking set here - if thread started chat it needs to be suspended - if let t = threadId { NSEThreads.shared.startThread(t, self) } + guard let t = threadId else { return } + NSEThreads.shared.startThread(t, self) let dbStatus = startChat() + // If database is opened successfully, get the list of connection entities (group members, contacts) + // that are referenced in the encrypted notification metadata. if case .ok = dbStatus, let ntfConns = apiGetNtfConns(nonce: nrData.nonce, encNtfInfo: nrData.encNtfInfo) { logger.debug("NotificationService: receiveNtfMessages: apiGetNtfConns ntfConns count = \(ntfConns.count)") -// logger.debug("NotificationService: receiveNtfMessages: apiGetNtfConns ntfConns \(String(describing: ntfConns.map { $0.connEntity.id }))") - for ntfConn in ntfConns { - addExpectedMessage(ntfConn: ntfConn) - } + // uncomment localDisplayName in ConnectionEntity + // logger.debug("NotificationService: receiveNtfMessages: apiGetNtfConns ntfConns \(String(describing: ntfConns.map { $0.connEntity.localDisplayName }))") - let connMsgReqs = expectedMessages.compactMap { (id, _) in - let started = NSEThreads.queue.sync { - let canStart = checkCanStart(id) - if let t = threadId { logger.debug("NotificationService thread \(t, privacy: .private): receiveNtfMessages: can start: \(canStart)") } - if canStart { - processDroppedNotifications(id) - expectedMessages[id]?.startedProcessingNewMsgs = true - expectedMessages[id]?.shouldProcessNtf = true - } - return canStart - } - if started { - return expectedMessages[id]?.connMsgReq - } else { - if let t = threadId { logger.debug("NotificationService thread \(t, privacy: .private): receiveNtfMessages: entity \(id, privacy: .private) waiting on semaphore") } - expectedMessages[id]?.semaphore.wait() - if let t = threadId { logger.debug("NotificationService thread \(t, privacy: .private): receiveNtfMessages: entity \(id, privacy: .private) proceeding after semaphore") } - Task { - NSEThreads.queue.sync { - processDroppedNotifications(id) - expectedMessages[id]?.startedProcessingNewMsgs = true - expectedMessages[id]?.shouldProcessNtf = true + // Prepare expected messages - they will be delivered to the reception loop in this chain: + // They are atomically added to the instance notificationEntities inside msgReqs loop, to avoid any race conditions. + let ntfEntities = ntfConns.compactMap(mkNotificationEntity) + + // collect notification message requests for all connection entities + let msgReqs: [(chatId: String, connMsgReq: ConnMsgReq)] = ntfEntities.compactMap { ntfEntity -> (chatId: String, connMsgReq: ConnMsgReq)? in + // No need to request messages for connection entities that are "ready", + // e.g. for muted users or when the message is not expected based on notification. + let id = ntfEntity.entityId + if let expectedMsg = ntfEntity.expectedMsg { + if NSEThreads.shared.startEntity(self, ntfEntity) { // atomically checks and adds ntfEntity to NSE + // process any notifications "postponed" by the previous instance + let completed = processDroppedNotifications(ntfEntity, expectedMsg) + return if !completed, let connMsgReq = notificationEntities[id]?.connMsgReq { + (id, connMsgReq) + } else { + nil } - if let connMsgReq = expectedMessages[id]?.connMsgReq { - let _ = getConnNtfMessage(connMsgReq: connMsgReq) + } else { + // wait for another instance processing the same connection entity + logger.debug("NotificationService thread \(t, privacy: .private): receiveNtfMessages: entity \(id, privacy: .private) waiting on semaphore") + // this semaphore will be released by signalNextThread function, that looks up the instance + // waiting for the connection entity via activeThreads in NSEThreads + notificationEntities[id]?.semaphore.wait() + logger.debug("NotificationService thread \(t, privacy: .private): receiveNtfMessages: entity \(id, privacy: .private) proceeding after semaphore") + Task { + // process any notifications "postponed" by the previous instance + let completed = processDroppedNotifications(ntfEntity, expectedMsg) + // Request messages from the server for this connection entity. + // It triggers event delivery to receiveMessages loop (see above). + if !completed, let connMsgReq = notificationEntities[id]?.connMsgReq, + let rcvMsg = getConnNtfMessage(connMsgReq: connMsgReq), + rcvMsg.noMsg { + // if server returns error or "no message", deliver what we have for this connection entity. + finalizeEntity(id) // also releases any waiting threads for this entity + } } + return nil } + } else { // no expected message + notificationEntities[id] = ntfEntity return nil } } - if !connMsgReqs.isEmpty { - if let r = apiGetConnNtfMessages(connMsgReqs: connMsgReqs) { - logger.debug("NotificationService: receiveNtfMessages: apiGetConnNtfMessages count = \(r.count), expecting messages \(r.count { $0 != nil })") + // Request messages for all connection entities that were not used by other instances. + // It triggers event delivery to receiveMessages loop (see above). + if !msgReqs.isEmpty, + let rcvMsgs = apiGetConnNtfMessages(connMsgReqs: msgReqs.map { $0.connMsgReq }) { + for i in 0 ..< min(msgReqs.count, rcvMsgs.count) { // a sanity check, API always returns the same size + if rcvMsgs[i].noMsg { + // mark entity as ready if there are no message on the server (or on error) + finalizeEntity(msgReqs[i].chatId) + } } - return } } else if let dbStatus = dbStatus { setServiceBestAttemptNtf(createErrorNtf(dbStatus, badgeCount)) } } + // try to deliver the best attempt before exiting deliverBestAttemptNtf() } - func addExpectedMessage(ntfConn: NtfConn) { - let expectedMsgId = ntfConn.expectedMsg_?.msgId - if let receiveEntityId = ntfConn.connEntity.id { - logger.debug("NotificationService: addExpectedMessage: expectedMsgId = \(expectedMsgId ?? "nil", privacy: .private)") - expectedMessages[receiveEntityId] = ExpectedMessage( + @inline(__always) + func mkNotificationEntity(ntfConn: NtfConn) -> NotificationEntity? { + if let rcvEntityId = ntfConn.connEntity.id { + // don't receive messages for muted user profile + let expectedMsg: NtfMsgInfo? = if ntfConn.user.showNotifications { ntfConn.expectedMsg_ } else { nil } + return NotificationEntity( ntfConn: ntfConn, - expectedMsgId: expectedMsgId, - allowedGetNextAttempts: 3, - msgBestAttemptNtf: defaultBestAttemptNtf(ntfConn), - ready: ntfConn.expectedMsg_ == nil, // show defaultBestAttemptNtf(ntfConn) if there is no expected message - shouldProcessNtf: false, - startedProcessingNewMsgs: false, - semaphore: DispatchSemaphore(value: 0) + entityId: rcvEntityId, + expectedMsg: expectedMsg, + msgBestAttemptNtf: defaultBestAttemptNtf(ntfConn) ) } + return nil } - func checkCanStart(_ entityId: String) -> Bool { - return !NSEThreads.shared.activeThreads.contains(where: { - (tId, nse) in tId != threadId && nse.expectedMessages.contains(where: { $0.key == entityId }) - }) - } - - func processDroppedNotifications(_ entityId: String) { - if !NSEThreads.shared.droppedNotifications.isEmpty { - let messagesToProcess = NSEThreads.shared.droppedNotifications.filter { (eId, _) in eId == entityId } - NSEThreads.shared.droppedNotifications.removeAll(where: { (eId, _) in eId == entityId }) - for (index, (_, ntf)) in messagesToProcess.enumerated() { - if let t = threadId { logger.debug("NotificationService thread \(t, privacy: .private): entity \(entityId, privacy: .private): processing dropped notification \(index, privacy: .private)") } - processReceivedNtf(entityId, ntf, signalReady: false) + // Processes notifications received and postponed by the previous NSE instance + func processDroppedNotifications(_ ntfEntity: NotificationEntity, _ expectedMsg: NtfMsgInfo) -> Bool { + var completed = false + while !completed { + if let dropped = NSEThreads.shared.takeDroppedNtf(ntfEntity) { + completed = processReceivedNtf(ntfEntity, expectedMsg, dropped.ntf) + } else { + break } } + if completed { + finalizeEntity(ntfEntity.entityId) + } else { + notificationEntities[ntfEntity.entityId]?.shouldProcessNtf = true + } + return completed } override func serviceExtensionTimeWillExpire() { @@ -351,69 +517,70 @@ class NotificationService: UNNotificationServiceExtension { deliverBestAttemptNtf(urgent: true) } + @inline(__always) var expectingMoreMessages: Bool { - !expectedMessages.allSatisfy { $0.value.ready } + notificationEntities.contains { $0.value.expectedMsg != nil } } - func processReceivedNtf(_ id: ChatId, _ ntf: NSENotificationData, signalReady: Bool) { - guard let expectedMessage = expectedMessages[id] else { - return - } - guard let expectedMsgTs = expectedMessage.ntfConn.expectedMsg_?.msgTs else { - NSEThreads.shared.droppedNotifications.append((id, ntf)) - if signalReady { entityReady(id) } - return - } + // processReceivedNtf returns "completed" - true when no more messages for the passed entity should be processed by the current NSE instance. + // This is used to call finalizeEntity(id) and by processDroppedNotifications to decide if further processing is needed. + func processReceivedNtf(_ ntfEntity: NotificationEntity, _ expectedMsg: NtfMsgInfo, _ ntf: NSENotificationData) -> Bool { + let id = ntfEntity.entityId if case let .msgInfo(info) = ntf { - if info.msgId == expectedMessage.expectedMsgId { + if info.msgId == expectedMsg.msgId { + // The message for this instance is processed, no more expected, deliver. logger.debug("NotificationService processNtf: msgInfo msgId = \(info.msgId, privacy: .private): expected") - expectedMessages[id]?.expectedMsgId = nil - if signalReady { entityReady(id) } - self.deliverBestAttemptNtf() - } else if let msgTs = info.msgTs_, msgTs > expectedMsgTs { + return true + } else if let msgTs = info.msgTs_, msgTs > expectedMsg.msgTs { + // Otherwise check timestamp - if it is after the currently expected timestamp, preserve .msgInfo marker for the next instance. logger.debug("NotificationService processNtf: msgInfo msgId = \(info.msgId, privacy: .private): unexpected msgInfo, let other instance to process it, stopping this one") - NSEThreads.shared.droppedNotifications.append((id, ntf)) - if signalReady { entityReady(id) } - self.deliverBestAttemptNtf() - } else if (expectedMessages[id]?.allowedGetNextAttempts ?? 0) > 0, let connMsgReq = expectedMessages[id]?.connMsgReq { + NSEThreads.shared.addDroppedNtf(id, ntf) + return true + } else if ntfEntity.allowedGetNextAttempts > 0, let connMsgReq = ntfEntity.connMsgReq { + // Otherwise this instance expects more messages, and still has allowed attempts - + // request more messages with getConnNtfMessage. logger.debug("NotificationService processNtf: msgInfo msgId = \(info.msgId, privacy: .private): unexpected msgInfo, get next message") - expectedMessages[id]?.allowedGetNextAttempts -= 1 - if let receivedMsg = getConnNtfMessage(connMsgReq: connMsgReq) { - logger.debug("NotificationService processNtf, on getConnNtfMessage: msgInfo msgId = \(info.msgId, privacy: .private), receivedMsg msgId = \(receivedMsg.msgId, privacy: .private)") + notificationEntities[id]?.allowedGetNextAttempts -= 1 + let receivedMsg = getConnNtfMessage(connMsgReq: connMsgReq) + if case let .info(msg) = receivedMsg, let msg { + // Server delivered message, it will be processed in the loop - see the comments in receiveNtfMessages. + logger.debug("NotificationService processNtf, on getConnNtfMessage: msgInfo msgId = \(info.msgId, privacy: .private), receivedMsg msgId = \(msg.msgId, privacy: .private)") + return false } else { + // Server reported no messages or error, deliver what we have. logger.debug("NotificationService processNtf, on getConnNtfMessage: msgInfo msgId = \(info.msgId, privacy: .private): no next message, deliver best attempt") - NSEThreads.shared.droppedNotifications.append((id, ntf)) - if signalReady { entityReady(id) } - self.deliverBestAttemptNtf() + return true } } else { + // Current instance needs more messages, but ran out of attempts - deliver what we have. logger.debug("NotificationService processNtf: msgInfo msgId = \(info.msgId, privacy: .private): unknown message, let other instance to process it") - NSEThreads.shared.droppedNotifications.append((id, ntf)) - if signalReady { entityReady(id) } - self.deliverBestAttemptNtf() + return true } - } else if expectedMessage.ntfConn.user.showNotifications { + } else if ntfEntity.ntfConn.user.showNotifications { + // This is the notification event for the user with enabled notifications. logger.debug("NotificationService processNtf: setting best attempt") if ntf.notificationEvent != nil { setBadgeCount() } - let prevBestAttempt = expectedMessages[id]?.msgBestAttemptNtf - if prevBestAttempt?.callInvitation == nil || ntf.callInvitation != nil { - expectedMessages[id]?.msgBestAttemptNtf = ntf + // If previous "best attempt" is not a call, or if the current notification is a call, replace best attempt. + // NOTE: we are delaying it until notification marker to make sure we are not delivering stale calls that can't be connected. + // A better logic could be to check whether we have a call in the best attempt while processing .msgInfo marker above. + // If the best attempt is a call, and its marker is received, and the call is recent (e.g., the last 30 seconds), it would deliver at once, + // instead of requesting further messages. + if ntfEntity.msgBestAttemptNtf.callInvitation == nil || ntf.callInvitation != nil { + notificationEntities[id]?.msgBestAttemptNtf = ntf } // otherwise keep call as best attempt + return false } else { - NSEThreads.shared.droppedNotifications.append((id, ntf)) - if signalReady { entityReady(id) } + // We should not get to this branch, as notifications are not delivered for muted users. + return true } } - func entityReady(_ entityId: ChatId) { - if let t = threadId { logger.debug("NotificationService thread \(t, privacy: .private): entityReady: entity \(entityId, privacy: .private)") } - expectedMessages[entityId]?.ready = true - if let (tNext, nse) = NSEThreads.shared.activeThreads.first(where: { (_, nse) in nse.expectedMessages[entityId]?.startedProcessingNewMsgs == false }) { - if let t = threadId { logger.debug("NotificationService thread \(t, privacy: .private): entityReady: signal next thread \(tNext, privacy: .private) for entity \(entityId, privacy: .private)") } - nse.expectedMessages[entityId]?.semaphore.signal() - } + func finalizeEntity(_ entityId: ChatId) { + if let t = threadId { logger.debug("NotificationService thread \(t): entityReady: entity \(entityId)") } + NSEThreads.shared.signalNextThread(self, entityId) + deliverBestAttemptNtf() } func setBadgeCount() { @@ -421,9 +588,10 @@ class NotificationService: UNNotificationServiceExtension { ntfBadgeCountGroupDefault.set(badgeCount) } + @inline(__always) func setServiceBestAttemptNtf(_ ntf: UNMutableNotificationContent) { logger.debug("NotificationService.setServiceBestAttemptNtf") - serviceBestAttemptNtf = .nse(ntf) + serviceBestAttemptNtf = ntf } private func deliverBestAttemptNtf(urgent: Bool = false) { @@ -434,8 +602,8 @@ class NotificationService: UNNotificationServiceExtension { } logger.debug("NotificationService.deliverBestAttemptNtf") // stop processing other messages - for (key, _) in expectedMessages { - expectedMessages[key]?.shouldProcessNtf = false + for (key, _) in notificationEntities { + notificationEntities[key]?.shouldProcessNtf = false } let suspend: Bool @@ -449,22 +617,24 @@ class NotificationService: UNNotificationServiceExtension { } } + @inline(__always) private func deliverCallkitOrNotification(urgent: Bool, suspend: Bool = false, handler: @escaping (UNNotificationContent) -> Void) { - if useCallKit() && expectedMessages.contains(where: { $0.value.msgBestAttemptNtf?.callInvitation != nil }) { + let callInv = notificationEntities.lazy.compactMap({ $0.value.msgBestAttemptNtf.callInvitation }).first + if callInv != nil && useCallKit() { logger.debug("NotificationService.deliverCallkitOrNotification: will suspend, callkit") + // suspending NSE even though there may be other notifications + // to allow the app to process callkit call if urgent { - // suspending NSE even though there may be other notifications - // to allow the app to process callkit call suspendChat(0) - deliverNotification(handler: handler) + deliverNotification(handler, callInv) } else { - // suspending NSE with delay and delivering after the suspension + // when not "urgent", suspending NSE with delay and delivering after the suspension // because pushkit notification must be processed without delay - // to avoid app termination + // to avoid app termination. DispatchQueue.global().asyncAfter(deadline: .now() + fastNSESuspendSchedule.delay) { suspendChat(fastNSESuspendSchedule.timeout) DispatchQueue.global().asyncAfter(deadline: .now() + Double(fastNSESuspendSchedule.timeout)) { - self.deliverNotification(handler: handler) + self.deliverNotification(handler, callInv) } } } @@ -483,68 +653,71 @@ class NotificationService: UNNotificationServiceExtension { } } } - deliverNotification(handler: handler) + deliverNotification(handler, callInv) } } - private func deliverNotification(handler: @escaping (UNNotificationContent) -> Void) { - if serviceBestAttemptNtf != nil, let ntf = prepareNotification() { - contentHandler = nil + private func deliverNotification(_ handler: @escaping (UNNotificationContent) -> Void, _ callInv: RcvCallInvitation?) { + if let serviceNtf = serviceBestAttemptNtf { serviceBestAttemptNtf = nil - switch ntf { - case let .nse(content): - content.badge = badgeCount as NSNumber - handler(content) - case let .callkit(invitation): - logger.debug("NotificationService reportNewIncomingVoIPPushPayload for \(invitation.contact.id)") - CXProvider.reportNewIncomingVoIPPushPayload([ - "displayName": invitation.contact.displayName, - "contactId": invitation.contact.id, - "callUUID": invitation.callUUID ?? "", - "media": invitation.callType.media.rawValue, - "callTs": invitation.callTs.timeIntervalSince1970 - ]) { error in - logger.debug("reportNewIncomingVoIPPushPayload result: \(error)") - handler(error == nil ? UNMutableNotificationContent() : createCallInvitationNtf(invitation, self.badgeCount)) + contentHandler = nil + if let callInv { + if useCallKit() { + logger.debug("NotificationService reportNewIncomingVoIPPushPayload for \(callInv.contact.id)") + CXProvider.reportNewIncomingVoIPPushPayload([ + "displayName": callInv.contact.displayName, + "contactId": callInv.contact.id, + "callUUID": callInv.callUUID ?? "", + "media": callInv.callType.media.rawValue, + "callTs": callInv.callTs.timeIntervalSince1970 + ]) { error in + logger.debug("reportNewIncomingVoIPPushPayload result: \(error)") + handler(error == nil ? UNMutableNotificationContent() : createCallInvitationNtf(callInv, self.badgeCount)) + } + } else { + handler(createCallInvitationNtf(callInv, badgeCount)) } - case .empty: - handler(UNMutableNotificationContent()) // used to mute notifications that did not unsubscribe yet - } - } - } - - private func prepareNotification() -> NSENotification? { - if expectedMessages.isEmpty { - return serviceBestAttemptNtf - } else if let callNtfKV = expectedMessages.first(where: { $0.value.msgBestAttemptNtf?.callInvitation != nil }), - let callInv = callNtfKV.value.msgBestAttemptNtf?.callInvitation, - let callNtf = callNtfKV.value.msgBestAttemptNtf { - return useCallKit() ? .callkit(callInv) : .nse(callNtf.notificationContent(badgeCount)) - } else { - logger.debug("NotificationService prepareNotification \(String(describing: self.expectedMessages.map { $0.key }))") - let ntfEvents = expectedMessages.compactMap { $0.value.msgBestAttemptNtf?.notificationEvent } - logger.debug("NotificationService prepareNotification \(ntfEvents.count)") - if ntfEvents.isEmpty { - return .empty - } else if let ntfEvent = ntfEvents.count == 1 ? ntfEvents.first : nil { - return .nse(ntfEvent.notificationContent(badgeCount)) + } else if notificationEntities.isEmpty { + handler(serviceNtf) } else { - return .nse(createJointNtf(ntfEvents)) + handler(prepareNotification()) } } } - private func createJointNtf(_ ntfEvents: [NSENotificationData]) -> UNMutableNotificationContent { + @inline(__always) + private func prepareNotification() -> UNMutableNotificationContent { + // uncomment localDisplayName in ConnectionEntity + // let conns = self.notificationEntities.compactMap { $0.value.ntfConn.connEntity.localDisplayName } + // logger.debug("NotificationService prepareNotification for \(String(describing: conns))") + let ntfs = notificationEntities.compactMap { $0.value.msgBestAttemptNtf.notificationEvent } + let newMsgNtfs = ntfs.compactMap({ $0.newMsgNtf }) + let useNtfs = if newMsgNtfs.isEmpty { ntfs } else { newMsgNtfs } + return createNtf(useNtfs) + + func createNtf(_ ntfs: [NSENotificationData]) -> UNMutableNotificationContent { + logger.debug("NotificationService prepareNotification: \(ntfs.count) events") + return switch ntfs.count { + case 0: UNMutableNotificationContent() // used to mute notifications that did not unsubscribe yet + case 1: ntfs[0].notificationContent(badgeCount) + default: createJointNtf(ntfs) + } + } + } + + // NOTE: this can be improved when there are two or more connection entity events when no messages were delivered. + // Possibly, it is better to postpone this improvement until message priority is added to prevent notifications in muted groups, + // unless it is a mention, a reply or some other high priority message marked for notification delivery. + @inline(__always) + private func createJointNtf(_ ntfs: [NSENotificationData]) -> UNMutableNotificationContent { let previewMode = ntfPreviewModeGroupDefault.get() - let newMsgsData: [(any UserLike, ChatInfo)] = ntfEvents.compactMap { $0.newMsgData } - if !newMsgsData.isEmpty, let userId = newMsgsData.first?.0.userId { - let newMsgsChats: [ChatInfo] = newMsgsData.map { $0.1 } - let uniqueChatsNames = uniqueNewMsgsChatsNames(newMsgsChats) - var body: String - if previewMode == .hidden { - body = String.localizedStringWithFormat(NSLocalizedString("New messages in %d chats", comment: "notification body"), uniqueChatsNames.count) + logger.debug("NotificationService.createJointNtf ntfs: \(ntfs.count)") + let (userId, chatsNames) = newMsgsChatsNames(ntfs) + if !chatsNames.isEmpty, let userId { + let body = if previewMode == .hidden { + String.localizedStringWithFormat(NSLocalizedString("From %d chat(s)", comment: "notification body"), chatsNames.count) } else { - body = String.localizedStringWithFormat(NSLocalizedString("From: %@", comment: "notification body"), newMsgsChatsNamesStr(uniqueChatsNames)) + String.localizedStringWithFormat(NSLocalizedString("From: %@", comment: "notification body"), newMsgsChatsNamesStr(chatsNames)) } return createNotification( categoryIdentifier: ntfCategoryManyEvents, @@ -557,24 +730,32 @@ class NotificationService: UNNotificationServiceExtension { return createNotification( categoryIdentifier: ntfCategoryManyEvents, title: NSLocalizedString("New events", comment: "notification"), - body: String.localizedStringWithFormat(NSLocalizedString("%d new events", comment: "notification body"), ntfEvents.count), + body: String.localizedStringWithFormat(NSLocalizedString("%d new events", comment: "notification body"), ntfs.count), badgeCount: badgeCount ) } } - private func uniqueNewMsgsChatsNames(_ newMsgsChats: [ChatInfo]) -> [String] { + @inline(__always) + private func newMsgsChatsNames(_ ntfs: [NSENotificationData]) -> (Int64?, [String]) { var seenChatIds = Set() - var uniqueChatsNames: [String] = [] - for chat in newMsgsChats { - if !seenChatIds.contains(chat.id) { - seenChatIds.insert(chat.id) - uniqueChatsNames.append(chat.chatViewName) + var chatsNames: [String] = [] + var userId: Int64? + for ntf in ntfs { + switch ntf { + case let .messageReceived(user, chat, _): + if seenChatIds.isEmpty { userId = user.userId } + if !seenChatIds.contains(chat.id) { + seenChatIds.insert(chat.id) + chatsNames.append(chat.chatViewName) + } + default: () } } - return uniqueChatsNames + return (userId, chatsNames) } + @inline(__always) private func newMsgsChatsNamesStr(_ names: [String]) -> String { return switch names.count { case 1: names[0] @@ -593,9 +774,8 @@ class NSEChatState { static let shared = NSEChatState() private var value_ = NSEState.created - var value: NSEState { - value_ - } + @inline(__always) + var value: NSEState { value_ } func set(_ state: NSEState) { nseStateGroupDefault.set(state) @@ -603,7 +783,7 @@ class NSEChatState { value_ = state } - init() { + private init() { // This is always set to .created state, as in case previous start of NSE crashed in .active state, it is stored correctly. // Otherwise the app will be activating slower set(.created) @@ -651,7 +831,7 @@ func startChat() -> DBMigrationResult? { startLock.wait() defer { startLock.signal() } - + if hasChatCtrl() { return switch NSEChatState.shared.value { case .created: doStartChat() @@ -803,8 +983,11 @@ func chatRecvMsg() async -> APIResult? { } private let isInChina = SKStorefront().countryCode == "CHN" + +@inline(__always) private func useCallKit() -> Bool { !isInChina && callKitEnabledGroupDefault.get() } +@inline(__always) func receivedMsgNtf(_ res: NSEChatEvent) async -> (String, NSENotificationData)? { logger.debug("NotificationService receivedMsgNtf: \(res.responseType)") switch res { @@ -851,12 +1034,10 @@ func receivedMsgNtf(_ res: NSEChatEvent) async -> (String, NSENotificationData)? case .chatSuspended: chatSuspended() return nil - default: - logger.debug("NotificationService receivedMsgNtf ignored event: \(res.responseType)") - return nil } } +@inline(__always) func updateNetCfg() { let newNetConfig = getNetCfg() if newNetConfig != networkConfig { @@ -925,42 +1106,41 @@ func apiSetEncryptLocalFiles(_ enable: Bool) throws { func apiGetNtfConns(nonce: String, encNtfInfo: String) -> [NtfConn]? { guard apiGetActiveUser() != nil else { - logger.debug("no active user") + logger.debug("NotificationService: no active user") return nil } let r: APIResult = sendSimpleXCmd(NSEChatCommand.apiGetNtfConns(nonce: nonce, encNtfInfo: encNtfInfo)) if case let .result(.ntfConns(ntfConns)) = r { - logger.debug("apiGetNtfConns response ntfConns: \(ntfConns.count)") + logger.debug("NotificationService apiGetNtfConns response ntfConns: \(ntfConns.count) conections") return ntfConns } else if case let .error(error) = r { - logger.debug("apiGetNtfMessage error response: \(String.init(describing: error))") + logger.debug("NotificationService apiGetNtfMessage error response: \(String.init(describing: error))") } else { - logger.debug("apiGetNtfMessage ignored response: \(r.responseType) \(String.init(describing: r))") + logger.debug("NotificationService apiGetNtfMessage ignored response: \(r.responseType) \(String.init(describing: r))") } return nil } -func apiGetConnNtfMessages(connMsgReqs: [ConnMsgReq]) -> [NtfMsgInfo?]? { +func apiGetConnNtfMessages(connMsgReqs: [ConnMsgReq]) -> [RcvNtfMsgInfo]? { guard apiGetActiveUser() != nil else { logger.debug("no active user") return nil } - logger.debug("apiGetConnNtfMessages command: \(NSEChatCommand.apiGetConnNtfMessages(connMsgReqs: connMsgReqs).cmdString)") +// logger.debug("NotificationService apiGetConnNtfMessages command: \(NSEChatCommand.apiGetConnNtfMessages(connMsgReqs: connMsgReqs).cmdString)") + logger.debug("NotificationService apiGetConnNtfMessages requests: \(connMsgReqs.count)") let r: APIResult = sendSimpleXCmd(NSEChatCommand.apiGetConnNtfMessages(connMsgReqs: connMsgReqs)) - if case let .result(.connNtfMessages(receivedMsgs)) = r { - logger.debug("apiGetConnNtfMessages response receivedMsgs: total \(receivedMsgs.count), expecting messages \(receivedMsgs.count { $0 != nil })") - return receivedMsgs + if case let .result(.connNtfMessages(msgs)) = r { +// logger.debug("NotificationService apiGetConnNtfMessages responses: \(String(describing: msgs))") + logger.debug("NotificationService apiGetConnNtfMessages responses: total \(msgs.count), expecting messages \(msgs.count { !$0.noMsg }), errors \(msgs.count { $0.isError })") + return msgs } - logger.debug("apiGetConnNtfMessages error: \(responseError(r.unexpected))") + logger.debug("NotificationService apiGetConnNtfMessages error: \(responseError(r.unexpected))") return nil } -func getConnNtfMessage(connMsgReq: ConnMsgReq) -> NtfMsgInfo? { - let r_ = apiGetConnNtfMessages(connMsgReqs: [connMsgReq]) - if let r = r_, let receivedMsg = r.count == 1 ? r.first : nil { - return receivedMsg - } - return nil +func getConnNtfMessage(connMsgReq: ConnMsgReq) -> RcvNtfMsgInfo? { + let r = apiGetConnNtfMessages(connMsgReqs: [connMsgReq]) + return if let r, r.count > 0 { r[0] } else { nil } } func apiReceiveFile(fileId: Int64, encrypted: Bool, inline: Bool? = nil) -> AChatItem? { @@ -1021,4 +1201,3 @@ func defaultBestAttemptNtf(_ ntfConn: NtfConn) -> NSENotificationData { } } } - diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index c7fd19e615..62c6732ab5 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -1971,7 +1971,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 273; + CURRENT_PROJECT_VERSION = 274; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1996,7 +1996,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES_THIN; - MARKETING_VERSION = 6.3.3; + MARKETING_VERSION = 6.3.4; OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000"; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; @@ -2021,7 +2021,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 273; + CURRENT_PROJECT_VERSION = 274; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2046,7 +2046,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.3; + MARKETING_VERSION = 6.3.4; OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000"; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; @@ -2063,11 +2063,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 273; + CURRENT_PROJECT_VERSION = 274; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.3.3; + MARKETING_VERSION = 6.3.4; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2083,11 +2083,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 273; + CURRENT_PROJECT_VERSION = 274; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.3.3; + MARKETING_VERSION = 6.3.4; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2108,7 +2108,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 273; + CURRENT_PROJECT_VERSION = 274; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2123,7 +2123,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.3; + MARKETING_VERSION = 6.3.4; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2145,7 +2145,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 273; + CURRENT_PROJECT_VERSION = 274; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2160,7 +2160,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.3; + MARKETING_VERSION = 6.3.4; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2182,7 +2182,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 273; + CURRENT_PROJECT_VERSION = 274; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2208,7 +2208,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.3; + MARKETING_VERSION = 6.3.4; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2233,7 +2233,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 273; + CURRENT_PROJECT_VERSION = 274; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2259,7 +2259,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.3; + MARKETING_VERSION = 6.3.4; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2284,7 +2284,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 273; + CURRENT_PROJECT_VERSION = 274; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2299,7 +2299,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.3.3; + MARKETING_VERSION = 6.3.4; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2318,7 +2318,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 273; + CURRENT_PROJECT_VERSION = 274; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2333,7 +2333,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.3.3; + MARKETING_VERSION = 6.3.4; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; diff --git a/apps/ios/SimpleXChat/AppGroup.swift b/apps/ios/SimpleXChat/AppGroup.swift index 75bb537b94..29ccab7357 100644 --- a/apps/ios/SimpleXChat/AppGroup.swift +++ b/apps/ios/SimpleXChat/AppGroup.swift @@ -311,12 +311,14 @@ public class EnumDefault where T.RawValue == String { } public class BoolDefault: Default { + @inline(__always) public func get() -> Bool { self.defaults.bool(forKey: self.key) } } public class IntDefault: Default { + @inline(__always) public func get() -> Int { self.defaults.integer(forKey: self.key) } @@ -326,11 +328,13 @@ public class Default { var defaults: UserDefaults var key: String + @inline(__always) public init(defaults: UserDefaults = UserDefaults.standard, forKey: String) { self.defaults = defaults self.key = forKey } + @inline(__always) public func set(_ value: T) { defaults.set(value, forKey: key) defaults.synchronize() diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index bff1ebb4fd..db8f1dd26e 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2409,6 +2409,19 @@ public enum ConnectionEntity: Decodable, Hashable { nil } } + + // public var localDisplayName: String? { + // switch self { + // case let .rcvDirectMsgConnection(conn, contact): + // if let name = contact?.localDisplayName { "@\(name)" } else { conn.id } + // case let .rcvGroupMsgConnection(_, g, m): + // "#\(g.localDisplayName) @\(m.localDisplayName)" + // case let .userContactConnection(_, userContact): + // userContact.id + // default: + // nil + // } + // } public var conn: Connection { switch self { @@ -2434,6 +2447,21 @@ public struct NtfMsgInfo: Decodable, Hashable { public var msgTs: Date } +public enum RcvNtfMsgInfo: Decodable { + case info(ntfMsgInfo: NtfMsgInfo?) + case error(ntfMsgError: AgentErrorType) + + @inline(__always) + public var noMsg: Bool { + if case let .info(msg) = self { msg == nil } else { true } + } + + @inline(__always) + public var isError: Bool { + if case .error = self { true } else { false } + } +} + let iso8601DateFormatter = { let f = ISO8601DateFormatter() f.formatOptions = [.withInternetDateTime] diff --git a/apps/ios/SimpleXChat/hs_init.c b/apps/ios/SimpleXChat/hs_init.c index 4731e7b829..e75173d6cf 100644 --- a/apps/ios/SimpleXChat/hs_init.c +++ b/apps/ios/SimpleXChat/hs_init.c @@ -29,10 +29,10 @@ void haskell_init_nse(void) { char *argv[] = { "simplex", "+RTS", // requires `hs_init_with_rtsopts` - "-A1m", // chunk size for new allocations - "-H1m", // initial heap size + "-A256k", // chunk size for new allocations + "-H512k", // initial heap size "-F0.5", // heap growth triggering GC - "-Fd1", // memory return + "-Fd0.3", // memory return "-c", // compacting garbage collector 0 }; diff --git a/cabal.project b/cabal.project index a9a4b45f1a..687e4788f9 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: a632eea75b677cf2b146ad06ee875307d0321f23 + tag: 7bbd99644ae2f7e49033742c22d7fb8f51500ced source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 842348157a..2fb002d790 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."a632eea75b677cf2b146ad06ee875307d0321f23" = "03vk7214941f5jwmf7sp26lxzh4c1xl89wqmlky379d6gwypbzy6"; + "https://github.com/simplex-chat/simplexmq.git"."7bbd99644ae2f7e49033742c22d7fb8f51500ced" = "05aqkcsra4kjhid75a23s6y6a7ky8rlqwa5yjkfj286bd5rlafxl"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index d1bdeba341..02e2e121b3 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -708,7 +708,7 @@ data ChatResponse | CRNtfTokenStatus {status :: NtfTknStatus} | CRNtfToken {token :: DeviceToken, status :: NtfTknStatus, ntfMode :: NotificationsMode, ntfServer :: NtfServer} | CRNtfConns {ntfConns :: [NtfConn]} - | CRConnNtfMessages {receivedMsgs :: NonEmpty (Maybe NtfMsgInfo)} + | CRConnNtfMessages {receivedMsgs :: NonEmpty RcvNtfMsgInfo} | CRContactConnectionDeleted {user :: User, connection :: PendingContactConnection} | CRRemoteHostList {remoteHosts :: [RemoteHostInfo]} | CRCurrentRemoteHost {remoteHost_ :: Maybe RemoteHostInfo} @@ -1139,13 +1139,20 @@ data NtfConn = NtfConn } deriving (Show) --- brokerTs is the same msgTs, it is used in ConnMsgReq / APIGetConnNtfMessages +-- msgTs is broker message timestamp, it is used in ConnMsgReq / APIGetConnNtfMessages -- to set it as last connection message in case queue is empty data NtfMsgInfo = NtfMsgInfo {msgId :: Text, msgTs :: UTCTime} deriving (Show) -receivedMsgInfo :: SMPMsgMeta -> NtfMsgInfo -receivedMsgInfo SMPMsgMeta {msgId, msgTs} = ntfMsgInfo_ msgId msgTs +data RcvNtfMsgInfo + = RNMInfo {ntfMsgInfo :: Maybe NtfMsgInfo} + | RNMError {ntfMsgError :: AgentErrorType} + deriving (Show) + +receivedMsgInfo :: Either AgentErrorType (Maybe SMPMsgMeta) -> RcvNtfMsgInfo +receivedMsgInfo = \case + Right msgMeta_ -> RNMInfo $ (\SMPMsgMeta {msgId, msgTs} -> ntfMsgInfo_ msgId msgTs) <$> msgMeta_ + Left e -> RNMError e expectedMsgInfo :: NMsgMeta -> NtfMsgInfo expectedMsgInfo NMsgMeta {msgId, msgTs} = ntfMsgInfo_ msgId msgTs @@ -1642,6 +1649,8 @@ $(JQ.deriveJSON defaultJSON ''UserProfileUpdateSummary) $(JQ.deriveJSON defaultJSON ''NtfMsgInfo) +$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "RNM") ''RcvNtfMsgInfo) + $(JQ.deriveJSON defaultJSON ''NtfConn) $(JQ.deriveJSON defaultJSON ''NtfMsgAckInfo) diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 8e7fba8255..f21c9526a5 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -1304,7 +1304,7 @@ processChatCommand' vr = \case $>>= \user -> fmap (mkNtfConn user) . eitherToMaybe <$> runExceptT (getConnectionEntity db vr user agentConnId) APIGetConnNtfMessages connMsgs -> withUser $ \_ -> do msgs <- lift $ withAgent' (`getConnectionMessages` connMsgs) - let ntfMsgs = L.map (receivedMsgInfo <$>) msgs + let ntfMsgs = L.map receivedMsgInfo msgs pure $ CRConnNtfMessages ntfMsgs GetUserProtoServers (AProtocolType p) -> withUser $ \user -> withServerProtocol p $ do srvs <- withFastStore (`getUserServers` user) From 9329bf61447ad3ce9640d82b7b08f05f1db1729f Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 7 May 2025 08:14:11 +0100 Subject: [PATCH 532/567] core: 6.3.4.0 (simplexmq 6.4.0.2) --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- src/Simplex/Chat/Remote.hs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cabal.project b/cabal.project index 687e4788f9..3e6ccab8a5 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 7bbd99644ae2f7e49033742c22d7fb8f51500ced + tag: deaec3cce286e959bd594b9620c307954b510a07 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 2fb002d790..8d17a2ce99 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."7bbd99644ae2f7e49033742c22d7fb8f51500ced" = "05aqkcsra4kjhid75a23s6y6a7ky8rlqwa5yjkfj286bd5rlafxl"; + "https://github.com/simplex-chat/simplexmq.git"."deaec3cce286e959bd594b9620c307954b510a07" = "0b8m4czjiwsi9169plslyk2rjw0f370vv7ha6qm2hpx14bxzz7xm"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 6469f24244..52e40d6c6f 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.3.3.1 +version: 6.3.4.0 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index 105f872b75..021f048d6b 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -75,11 +75,11 @@ remoteFilesFolder = "simplex_v1_files" -- when acting as host minRemoteCtrlVersion :: AppVersion -minRemoteCtrlVersion = AppVersion [6, 3, 3, 1] +minRemoteCtrlVersion = AppVersion [6, 3, 4, 0] -- when acting as controller minRemoteHostVersion :: AppVersion -minRemoteHostVersion = AppVersion [6, 3, 3, 1] +minRemoteHostVersion = AppVersion [6, 3, 4, 0] currentAppVersion :: AppVersion currentAppVersion = AppVersion SC.version From 73fe6827b23276bf02e36e3264549e228b79876f Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Wed, 7 May 2025 09:54:15 +0100 Subject: [PATCH 533/567] ios: update core library --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 62c6732ab5..e96c45b474 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -174,8 +174,8 @@ 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; }; 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; }; - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.3.1-5KR5yzeCZIzIubYi5BDCKe-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.3.1-5KR5yzeCZIzIubYi5BDCKe-ghc9.6.3.a */; }; - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.3.1-5KR5yzeCZIzIubYi5BDCKe.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.3.1-5KR5yzeCZIzIubYi5BDCKe.a */; }; + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.0-47rHZ52LFetD6j9vq8gwHM-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.0-47rHZ52LFetD6j9vq8gwHM-ghc9.6.3.a */; }; + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.0-47rHZ52LFetD6j9vq8gwHM.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.0-47rHZ52LFetD6j9vq8gwHM.a */; }; 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; @@ -533,8 +533,8 @@ 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.3.1-5KR5yzeCZIzIubYi5BDCKe-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.3.1-5KR5yzeCZIzIubYi5BDCKe-ghc9.6.3.a"; sourceTree = ""; }; - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.3.1-5KR5yzeCZIzIubYi5BDCKe.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.3.1-5KR5yzeCZIzIubYi5BDCKe.a"; sourceTree = ""; }; + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.0-47rHZ52LFetD6j9vq8gwHM-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.4.0-47rHZ52LFetD6j9vq8gwHM-ghc9.6.3.a"; sourceTree = ""; }; + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.0-47rHZ52LFetD6j9vq8gwHM.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.4.0-47rHZ52LFetD6j9vq8gwHM.a"; sourceTree = ""; }; 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; @@ -692,8 +692,8 @@ 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */, 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */, 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */, - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.3.1-5KR5yzeCZIzIubYi5BDCKe-ghc9.6.3.a in Frameworks */, - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.3.1-5KR5yzeCZIzIubYi5BDCKe.a in Frameworks */, + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.0-47rHZ52LFetD6j9vq8gwHM-ghc9.6.3.a in Frameworks */, + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.0-47rHZ52LFetD6j9vq8gwHM.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -778,8 +778,8 @@ 64C829992D54AEEE006B9E89 /* libffi.a */, 64C829982D54AEED006B9E89 /* libgmp.a */, 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */, - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.3.1-5KR5yzeCZIzIubYi5BDCKe-ghc9.6.3.a */, - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.3.1-5KR5yzeCZIzIubYi5BDCKe.a */, + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.0-47rHZ52LFetD6j9vq8gwHM-ghc9.6.3.a */, + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.0-47rHZ52LFetD6j9vq8gwHM.a */, ); path = Libraries; sourceTree = ""; From ecb4a36045971c5492b3b76c4dd263589be5acf6 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 7 May 2025 10:34:42 +0100 Subject: [PATCH 534/567] ui: translations (#5874) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (German) Currently translated at 100.0% (2352 of 2352 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (German) Currently translated at 100.0% (2056 of 2056 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (Italian) Currently translated at 100.0% (2056 of 2056 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2056 of 2056 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Czech) Currently translated at 99.9% (2351 of 2352 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Czech) Currently translated at 56.6% (1165 of 2056 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/cs/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2056 of 2056 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (German) Currently translated at 100.0% (2356 of 2356 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2356 of 2356 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2356 of 2356 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Arabic) Currently translated at 100.0% (2356 of 2356 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2356 of 2356 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Italian) Currently translated at 100.0% (2356 of 2356 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Arabic) Currently translated at 100.0% (2356 of 2356 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Arabic) Currently translated at 35.5% (730 of 2056 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ar/ * Translated using Weblate (Catalan) Currently translated at 100.0% (2356 of 2356 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ca/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2356 of 2356 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2356 of 2356 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2056 of 2056 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Vietnamese) Currently translated at 100.0% (2356 of 2356 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2356 of 2356 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Czech) Currently translated at 99.9% (2354 of 2356 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2356 of 2356 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2056 of 2056 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2356 of 2356 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2056 of 2056 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2356 of 2356 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * process localizations --------- Co-authored-by: mlanp Co-authored-by: Random Co-authored-by: No name Co-authored-by: zenobit Co-authored-by: summoner001 Co-authored-by: 大王叫我来巡山 Co-authored-by: Ghost of Sparta Co-authored-by: Muhammad Co-authored-by: jonnysemon Co-authored-by: fran secs Co-authored-by: Bezruchenko Simon Co-authored-by: tuananh-ng <158744840+tuananh-ng@users.noreply.github.com> Co-authored-by: Rafi --- .../ar.xcloc/Localized Contents/ar.xliff | 364 ++++++++++++++++-- .../bg.xcloc/Localized Contents/bg.xliff | 16 +- .../cs.xcloc/Localized Contents/cs.xliff | 20 +- .../de.xcloc/Localized Contents/de.xliff | 22 +- .../en.xcloc/Localized Contents/en.xliff | 20 +- .../es.xcloc/Localized Contents/es.xliff | 22 +- .../fi.xcloc/Localized Contents/fi.xliff | 16 +- .../fr.xcloc/Localized Contents/fr.xliff | 17 +- .../hu.xcloc/Localized Contents/hu.xliff | 30 +- .../it.xcloc/Localized Contents/it.xliff | 22 +- .../ja.xcloc/Localized Contents/ja.xliff | 16 +- .../nl.xcloc/Localized Contents/nl.xliff | 17 +- .../pl.xcloc/Localized Contents/pl.xliff | 17 +- .../ru.xcloc/Localized Contents/ru.xliff | 17 +- .../th.xcloc/Localized Contents/th.xliff | 16 +- .../tr.xcloc/Localized Contents/tr.xliff | 16 +- .../uk.xcloc/Localized Contents/uk.xliff | 17 +- .../Localized Contents/zh-Hans.xliff | 16 +- .../SimpleX NSE/de.lproj/Localizable.strings | 3 - .../SimpleX NSE/es.lproj/Localizable.strings | 3 - .../SimpleX NSE/fr.lproj/Localizable.strings | 3 - .../SimpleX NSE/hu.lproj/Localizable.strings | 3 - .../SimpleX NSE/it.lproj/Localizable.strings | 3 - .../SimpleX NSE/nl.lproj/Localizable.strings | 3 - .../SimpleX NSE/ru.lproj/Localizable.strings | 3 - .../SimpleX NSE/uk.lproj/Localizable.strings | 3 - apps/ios/cs.lproj/Localizable.strings | 12 + apps/ios/de.lproj/Localizable.strings | 15 + apps/ios/es.lproj/Localizable.strings | 15 + apps/ios/hu.lproj/Localizable.strings | 23 +- apps/ios/it.lproj/Localizable.strings | 15 + .../commonMain/resources/MR/ar/strings.xml | 4 + .../commonMain/resources/MR/ca/strings.xml | 4 + .../commonMain/resources/MR/cs/strings.xml | 16 +- .../commonMain/resources/MR/de/strings.xml | 6 +- .../commonMain/resources/MR/es/strings.xml | 4 + .../commonMain/resources/MR/hu/strings.xml | 12 +- .../commonMain/resources/MR/in/strings.xml | 10 + .../commonMain/resources/MR/it/strings.xml | 4 + .../commonMain/resources/MR/uk/strings.xml | 24 +- .../commonMain/resources/MR/vi/strings.xml | 6 +- .../resources/MR/zh-rCN/strings.xml | 4 + 42 files changed, 708 insertions(+), 171 deletions(-) diff --git a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff index 613a5e08ad..278b9ec9b2 100644 --- a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff +++ b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff @@ -814,8 +814,9 @@ جهة الاتصال مخفية: notification - + Contact is connected + تم الاتصال notification @@ -850,8 +851,9 @@ Core built at: %@ No comment provided by engineer. - + Core version: v%@ + الإصدار الأساسي: v%@ No comment provided by engineer. @@ -901,8 +903,9 @@ عبارة المرور الحالية… No comment provided by engineer. - + Currently maximum supported file size is %@. + الحد الأقصى لحجم الملف المدعوم حاليًا هو %@. No comment provided by engineer. @@ -920,9 +923,11 @@ قاعدة البيانات مُعمّاة! No comment provided by engineer. - + Database encryption passphrase will be updated and stored in the keychain. + سيتم تحديث عبارة المرور الخاصة بتشفير قاعدة البيانات وتخزينها في سلسلة المفاتيح. + No comment provided by engineer. @@ -957,8 +962,9 @@ عبارة مرور قاعدة البيانات وتصديرها No comment provided by engineer. - + Database passphrase is different from saved in the keychain. + عبارة المرور الخاصة بقاعدة البيانات مختلفة عن تلك المحفوظة في سلسلة المفاتيح. No comment provided by engineer. @@ -966,9 +972,11 @@ عبارة مرور قاعدة البيانات مطلوبة لفتح الدردشة. No comment provided by engineer. - + Database will be encrypted and the passphrase stored in the keychain. + سيتم تشفير قاعدة البيانات وتخزين عبارة المرور في سلسلة المفاتيح. + No comment provided by engineer. @@ -978,8 +986,9 @@ No comment provided by engineer. - + Database will be migrated when the app restarts + سيتم نقل قاعدة البيانات عند إعادة تشغيل التطبيق No comment provided by engineer. @@ -1079,36 +1088,44 @@ حذف المجموعة؟ No comment provided by engineer. - + Delete invitation + حذف الدعوة No comment provided by engineer. - + Delete link + حذف الرابط No comment provided by engineer. - + Delete link? + حذف الرابط؟ No comment provided by engineer. - + Delete message? + حذف الرسالة؟ No comment provided by engineer. - + Delete messages + حذف الرسائل No comment provided by engineer. - + Delete messages after + حذف الرسائل بعد No comment provided by engineer. - + Delete old database + حذف قاعدة البيانات القديمة No comment provided by engineer. - + Delete old database? + حذف قاعدة البيانات القديمة؟ No comment provided by engineer. @@ -1125,8 +1142,9 @@ حذف قائمة الانتظار server test step - + Delete user profile? + حذف ملف تعريف المستخدم؟ No comment provided by engineer. @@ -1134,8 +1152,9 @@ الوصف No comment provided by engineer. - + Develop + يطور No comment provided by engineer. @@ -1168,28 +1187,34 @@ رسائل مباشرة chat feature - + Direct messages between members are prohibited. + الرسائل المباشرة بين الأعضاء ممنوعة. No comment provided by engineer. - + Disable SimpleX Lock + تعطيل قفل SimpleX authentication reason - + Disappearing messages + الرسائل المختفية chat feature - + Disappearing messages are prohibited in this chat. + يُحظر اختفاء الرسائل في هذه الدردشة. No comment provided by engineer. - + Disappearing messages are prohibited. + الرسائل المختفية ممنوعة. No comment provided by engineer. - + Disconnect + قطع الاتصال server test step @@ -1200,12 +1225,14 @@ Display name: No comment provided by engineer. - + Do NOT use SimpleX for emergency calls. + لا تستخدم SimpleX لإجراء مكالمات الطوارئ. No comment provided by engineer. - + Do it later + افعل ذلك لاحقا No comment provided by engineer. @@ -1258,76 +1285,93 @@ تفعيل الإشعارات دورية؟ No comment provided by engineer. - + Encrypt + التشفير No comment provided by engineer. - + Encrypt database? + تشفير قاعدة البيانات؟ No comment provided by engineer. - + Encrypted database + قاعدة بيانات مشفرة No comment provided by engineer. - + Encrypted message or another event + رسالة مشفرة أو حدث آخر notification - + Encrypted message: database error + رسالة مشفرة: خطأ في قاعدة البيانات notification - + Encrypted message: keychain error + رسالة مشفرة: خطأ في سلسلة المفاتيح notification - + Encrypted message: no passphrase + الرسالة المشفرة: لا توجد عبارة مرور notification - + Encrypted message: unexpected error + رسالة مشفرة: خطأ غير متوقع notification - + Enter correct passphrase. + أدخل عبارة المرور الصحيحة. No comment provided by engineer. - + Enter passphrase… + أدخل عبارة المرور… No comment provided by engineer. - + Enter server manually + أدخل الخادم يدوياً No comment provided by engineer. - + Error + خطأ No comment provided by engineer. - + Error accepting contact request + خطأ في قبول طلب الاتصال No comment provided by engineer. Error accessing database file No comment provided by engineer. - + Error adding member(s) + خطأ في إضافة عضو (أعضاء) No comment provided by engineer. - + Error changing address + خطأ في تغيير العنوان No comment provided by engineer. - + Error changing role + خطأ في تغيير الدور المتغير No comment provided by engineer. - + Error changing setting + خطأ في تغيير الإعدادات No comment provided by engineer. @@ -5477,6 +5521,242 @@ This is your own one-time link! Conditions will be automatically accepted for enabled operators on: %@. سيتم قبول الشروط تلقائيًا للمشغلين الممكّنين على: %@. + + Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 + أنشئ ملفًا شخصيًا جديدًا في [تطبيق سطح المكتب](https://simplex.chat/downloads/). 💻 + + + Error adding server + خطأ في إضافة الخادم + + + Created at: %@ + تم الإنشاء في: %@ + + + Delete %lld messages of members? + حذف %lld الرسائل القديمة للأعضاء؟ + + + Disappearing message + رسالة اختفاء + + + Enabled + ممكّنة + + + Encrypted message: database migration error + رسالة مشفرة: خطأ في ترحيل قاعدة البيانات + + + Delete list? + Delete list? + + + Delivered even when Apple drops them. + يتم تسليمها حتى عندما تسقطها شركة Apple. + + + Destination server address of %@ is incompatible with forwarding server %@ settings. + عنوان خادم الوجهة %@ غير متوافق مع إعدادات خادم التوجيه %@. + + + Destination server version of %@ is incompatible with forwarding server %@. + إصدار خادم الوجهة لـ %@ غير متوافق مع خادم التوجيه %@. + + + Don't create address + لا تنشئ عنوان + + + Done + تم + + + Duration + المدة + + + Encrypt local files + تشفير الملفات المحلية + + + Encryption renegotiation in progress. + إعادة التفاوض على التشفير قيد التنفيذ. + + + Enter Passcode + أدخل رمز المرور + + + Enter passphrase + قم بأدخل عبارة المرور + + + Enter welcome message… + أدخل رسالة ترحيب… + + + Enter your name… + أدخل اسمك… + + + Error changing to incognito! + خطأ في التغيير إلى التصفح المتخفي! + + + Delete %lld messages? + حذف %lld رسائل؟ + + + Error aborting address change + خطأ في إجهاض تغيير العنوان + + + Disappears at + يختفي عند + + + Do not use credentials with proxy. + لا تستخدم بيانات الاعتماد مع البروكسي. + + + Error accepting conditions + خطأ في قبول الشروط + + + Enter password above to show! + أدخل كلمة المرور أعلاه للعرض! + + + Error changing connection profile + خطأ في تغيير ملف تعريف الاتصال + + + Desktop app version %@ is not compatible with this app. + إصدار تطبيق سطح المكتب %@ غير متوافق مع هذا التطبيق. + + + Encrypt stored files & media + تشفير الملفات والوسائط المخزنة + + + Enter this device name… + أدخل اسم الجهاز… + + + Enter welcome message… (optional) + أدخل رسالة ترحيب... (اختياري) + + + Correct name to %@? + الاسم الصحيح ل %@؟ + + + Delete member message? + حذف رسالة العضو؟ + + + Disable automatic message deletion? + تعطيل حذف الرسائل التلقائي؟ + + + Disable delete messages + تعطيل حذف الرسائل + + + Disable for all + تعطيل للجميع + + + Disabled + عاجز + + + Documents: + المستندات: + + + By using SimpleX Chat you agree to: +- send only legal content in public groups. +- respect other users – no spam. + باستخدامك SimpleX Chat، فإنك توافق على: +- إرسال محتوى قانوني فقط في المجموعات العامة. +- احترام المستخدمين الآخرين - ممنوع إرسال رسائل مزعجة. + + + Configure server operators + تكوين مشغلي الخادم + + + Enable Flux in Network & servers settings for better metadata privacy. + تمكين التدفق في إعدادات الشبكة والخوادم لتحسين خصوصية البيانات الوصفية. + + + Discover and join groups + اكتشف المجموعات وانضم إليها + + + Discover via local network + اكتشف عبر الشبكة المحلية + + + Enabled for + ممكّن ل + + + Encrypted message: app is stopped + رسالة مشفرة: تم إيقاف التطبيق + + + Enter group name… + أدخل اسم المجموعة… + + + Do NOT use private routing. + لا تستخدم التوجيه الخاص. + + + Encryption re-negotiation error + خطأ في إعادة تفاوض التشفير + + + Connection with desktop stopped + تم إيقاف الاتصال بسطح المكتب + + + Destination server error: %@ + خطأ خادم الوجهة: %@ + + + Do NOT send messages directly, even if your or destination server does not support private routing. + لا ترسل الرسائل بشكل مباشر، حتى لو كان خادمك أو خادم الوجهة لا يدعم التوجيه الخاص. + + + Direct messages between members are prohibited in this chat. + يُحظر إرسال الرسائل المباشرة بين الأعضاء في هذه الدردشة. + + + Disconnect desktop? + فصل سطح المكتب؟ + + + Disable (keep overrides) + تعطيل (الاحتفاظ بالتجاوزات) + + + Disappears at: %@ + يختفي عند: %@ + + + Do not send history to new members. + لا ترسل التاريخ إلى الأعضاء الجدد. + + + Encryption re-negotiation failed. + فشل إعادة التفاوض على التشفير. + diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 213394aa14..995698cf2e 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -778,6 +778,10 @@ swipe action All reports will be archived for you. No comment provided by engineer. + + All servers + No comment provided by engineer. + All your contacts will remain connected. Всички ваши контакти ще останат свързани. @@ -7832,6 +7836,10 @@ To connect, please ask your contact to create another connection link and check Use TCP port %@ when no port is specified. No comment provided by engineer. + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat Използвай чата @@ -9661,6 +9669,10 @@ last received msg: %2$@ %d new events notification body + + From %d chat(s) + notification body + From: %@ notification body @@ -9673,10 +9685,6 @@ last received msg: %2$@ New messages notification - - New messages in %d chats - notification body - diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index 826ad0171b..bf7bb307e0 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -145,18 +145,22 @@ %d file(s) are still being downloaded. + %d soubor(y) stále stahován(y). forward confirmation reason %d file(s) failed to download. + %d soubor(y) se nepodařilo stáhnout. forward confirmation reason %d file(s) were deleted. + %d soubor(y) smazán(y). forward confirmation reason %d file(s) were not downloaded. + %d soubor(y) nestažen(y). forward confirmation reason @@ -743,6 +747,10 @@ swipe action All reports will be archived for you. No comment provided by engineer. + + All servers + No comment provided by engineer. + All your contacts will remain connected. Všechny vaše kontakty zůstanou připojeny. @@ -7560,6 +7568,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Use TCP port %@ when no port is specified. No comment provided by engineer. + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat Použijte chat @@ -9323,6 +9335,10 @@ last received msg: %2$@ %d new events notification body + + From %d chat(s) + notification body + From: %@ notification body @@ -9335,10 +9351,6 @@ last received msg: %2$@ New messages notification - - New messages in %d chats - notification body - diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index facf6d5a9e..1fb3d61bde 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -796,6 +796,10 @@ swipe action Alle Meldungen werden für Sie archiviert. No comment provided by engineer. + + All servers + No comment provided by engineer. + All your contacts will remain connected. Alle Ihre Kontakte bleiben verbunden. @@ -7201,6 +7205,7 @@ chat item action Short link + Verkürzter Link No comment provided by engineer. @@ -7305,6 +7310,7 @@ chat item action SimpleX channel link + SimpleX-Kanal-Link simplex link type @@ -7899,6 +7905,7 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + Für diesen Link wird eine neuere App-Version benötigt. Bitte aktualisieren Sie die App oder bitten Sie Ihren Kontakt einen kompatiblen Link zu senden. No comment provided by engineer. @@ -8202,6 +8209,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Unsupported connection link + Verbindungs-Link wird nicht unterstützt No comment provided by engineer. @@ -8299,6 +8307,10 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Solange kein Port konfiguriert ist, wird TCP-Port %@ genutzt. No comment provided by engineer. + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat Verwenden Sie Chat @@ -8366,6 +8378,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Use short links (BETA) + Kurze Links verwenden (BETA) No comment provided by engineer. @@ -10191,6 +10204,10 @@ Zuletzt empfangene Nachricht: %2$@ %d neue Ereignisse notification body + + From %d chat(s) + notification body + From: %@ Von: %@ @@ -10206,11 +10223,6 @@ Zuletzt empfangene Nachricht: %2$@ Neue Nachrichten notification - - New messages in %d chats - Neue Nachrichten in %d Chats - notification body - diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 9eb60c2cbc..0082fa574d 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -796,6 +796,11 @@ swipe action All reports will be archived for you. No comment provided by engineer. + + All servers + All servers + No comment provided by engineer. + All your contacts will remain connected. All your contacts will remain connected. @@ -8303,6 +8308,11 @@ To connect, please ask your contact to create another connection link and check Use TCP port %@ when no port is specified. No comment provided by engineer. + + Use TCP port 443 for preset servers only. + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat Use chat @@ -10196,6 +10206,11 @@ last received msg: %2$@ %d new events notification body + + From %d chat(s) + From %d chat(s) + notification body + From: %@ From: %@ @@ -10211,11 +10226,6 @@ last received msg: %2$@ New messages notification - - New messages in %d chats - New messages in %d chats - notification body - diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 5250aa0de7..3f621bee53 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -796,6 +796,10 @@ swipe action Todos los informes serán archivados para ti. No comment provided by engineer. + + All servers + No comment provided by engineer. + All your contacts will remain connected. Todos tus contactos permanecerán conectados. @@ -7201,6 +7205,7 @@ chat item action Short link + Enlace corto No comment provided by engineer. @@ -7305,6 +7310,7 @@ chat item action SimpleX channel link + Enlace de canal SimpleX simplex link type @@ -7899,6 +7905,7 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + Este enlace requiere una versión más reciente de la aplicación. Por favor, actualiza la aplicación o pide a tu contacto un enlace compatible. No comment provided by engineer. @@ -8202,6 +8209,7 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Unsupported connection link + Enlace de conexión no compatible No comment provided by engineer. @@ -8299,6 +8307,10 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Se usa el puerto TCP %@ cuando no se ha especificado otro. No comment provided by engineer. + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat Usar Chat @@ -8366,6 +8378,7 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Use short links (BETA) + Usar enlaces cortos (BETA) No comment provided by engineer. @@ -10191,6 +10204,10 @@ last received msg: %2$@ %d evento(s) nuevo(s) notification body + + From %d chat(s) + notification body + From: %@ De: %@ @@ -10206,11 +10223,6 @@ last received msg: %2$@ Mensajes nuevos notification - - New messages in %d chats - Mensajes nuevos en %d chat(s) - notification body - diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index 8dc4250e20..a6b05ee48a 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -730,6 +730,10 @@ swipe action All reports will be archived for you. No comment provided by engineer. + + All servers + No comment provided by engineer. + All your contacts will remain connected. Kaikki kontaktisi pysyvät yhteydessä. @@ -7537,6 +7541,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Use TCP port %@ when no port is specified. No comment provided by engineer. + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat Käytä chattia @@ -9298,6 +9306,10 @@ last received msg: %2$@ %d new events notification body + + From %d chat(s) + notification body + From: %@ notification body @@ -9310,10 +9322,6 @@ last received msg: %2$@ New messages notification - - New messages in %d chats - notification body - diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 304b9e2084..2928b9f167 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -796,6 +796,10 @@ swipe action Tous les rapports seront archivés pour vous. No comment provided by engineer. + + All servers + No comment provided by engineer. + All your contacts will remain connected. Tous vos contacts resteront connectés. @@ -8227,6 +8231,10 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Use TCP port %@ when no port is specified. No comment provided by engineer. + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat Utiliser le chat @@ -10111,6 +10119,10 @@ dernier message reçu : %2$@ %d nouveaux événements notification body + + From %d chat(s) + notification body + From: %@ De : %@ @@ -10126,11 +10138,6 @@ dernier message reçu : %2$@ Nouveaux messages notification - - New messages in %d chats - Nouveaux messages dans %d chats - notification body - diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index f24ba2b781..cde7807c44 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -796,6 +796,10 @@ swipe action Az összes jelentés archiválva lesz az Ön számára. No comment provided by engineer. + + All servers + No comment provided by engineer. + All your contacts will remain connected. Az összes partnerével kapcsolatban marad. @@ -1093,7 +1097,7 @@ swipe action Attach - Csatolás + Mellékelés No comment provided by engineer. @@ -4055,7 +4059,7 @@ Hiba: %2$@ Hide: - Elrejtés: + Elrejtve: No comment provided by engineer. @@ -5906,7 +5910,7 @@ Hiba: %@ Privacy redefined - Adatvédelem újraértelmezve + Újraértelmezett adatvédelem No comment provided by engineer. @@ -7201,6 +7205,7 @@ chat item action Short link + Rövid hivatkozás No comment provided by engineer. @@ -7245,7 +7250,7 @@ chat item action Show: - Megjelenítés: + Megjelenítve: No comment provided by engineer. @@ -7305,6 +7310,7 @@ chat item action SimpleX channel link + SimpleX-csatornahivatkozás simplex link type @@ -7899,6 +7905,7 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + Ez a hivatkozás újabb alkalmazásverziót igényel. Frissítse az alkalmazást vagy kérjen egy kompatibilis hivatkozást a partnerétől. No comment provided by engineer. @@ -8202,6 +8209,7 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Unsupported connection link + Nem támogatott kapcsolattartási hivatkozás No comment provided by engineer. @@ -8299,6 +8307,10 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso A következő TCP-port használata, amikor nincs port megadva: %@. No comment provided by engineer. + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat SimpleX Chat használata @@ -8366,6 +8378,7 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Use short links (BETA) + Rövid hivatkozások használata (béta) No comment provided by engineer. @@ -10191,6 +10204,10 @@ utoljára fogadott üzenet: %2$@ %d új esemény notification body + + From %d chat(s) + notification body + From: %@ Tőle: %@ @@ -10206,11 +10223,6 @@ utoljára fogadott üzenet: %2$@ Új üzenetek notification - - New messages in %d chats - Új üzenetek %d csevegésben - notification body - diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 353d420e27..31e89b8507 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -796,6 +796,10 @@ swipe action Tutte le segnalazioni verranno archiviate per te. No comment provided by engineer. + + All servers + No comment provided by engineer. + All your contacts will remain connected. Tutti i tuoi contatti resteranno connessi. @@ -7201,6 +7205,7 @@ chat item action Short link + Link breve No comment provided by engineer. @@ -7305,6 +7310,7 @@ chat item action SimpleX channel link + Link del canale SimpleX simplex link type @@ -7899,6 +7905,7 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa. This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link. + Questo link richiede una versione più recente dell'app. Aggiornala o chiedi al tuo contatto di inviare un link compatibile. No comment provided by engineer. @@ -8202,6 +8209,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Unsupported connection link + Link di connessione non supportato No comment provided by engineer. @@ -8299,6 +8307,10 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Usa la porta TCP %@ quando non è specificata alcuna porta. No comment provided by engineer. + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat Usa la chat @@ -8366,6 +8378,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Use short links (BETA) + Usa link brevi (BETA) No comment provided by engineer. @@ -10191,6 +10204,10 @@ ultimo msg ricevuto: %2$@ %d nuovi eventi notification body + + From %d chat(s) + notification body + From: %@ Da: %@ @@ -10206,11 +10223,6 @@ ultimo msg ricevuto: %2$@ Nuovi messaggi notification - - New messages in %d chats - Nuovi messaggi in %d chat - notification body - diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 34856e2e2b..e929df1f35 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -768,6 +768,10 @@ swipe action All reports will be archived for you. No comment provided by engineer. + + All servers + No comment provided by engineer. + All your contacts will remain connected. あなたの連絡先が繋がったまま継続します。 @@ -7607,6 +7611,10 @@ To connect, please ask your contact to create another connection link and check Use TCP port %@ when no port is specified. No comment provided by engineer. + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat チャット @@ -9369,6 +9377,10 @@ last received msg: %2$@ %d new events notification body + + From %d chat(s) + notification body + From: %@ notification body @@ -9381,10 +9393,6 @@ last received msg: %2$@ New messages notification - - New messages in %d chats - notification body - diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 4d9df505db..2b2a79731e 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -796,6 +796,10 @@ swipe action Alle rapporten worden voor u gearchiveerd. No comment provided by engineer. + + All servers + No comment provided by engineer. + All your contacts will remain connected. Al uw contacten blijven verbonden. @@ -8299,6 +8303,10 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Gebruik TCP-poort %@ als er geen poort is opgegeven. No comment provided by engineer. + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat Gebruik chat @@ -10191,6 +10199,10 @@ laatst ontvangen bericht: %2$@ ‐%d nieuwe gebeurtenissen notification body + + From %d chat(s) + notification body + From: %@ Van: %@ @@ -10206,11 +10218,6 @@ laatst ontvangen bericht: %2$@ Nieuwe berichten notification - - New messages in %d chats - Nieuwe berichten in %d chats - notification body - diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 0a548dc227..5e77f836a9 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -796,6 +796,10 @@ swipe action Wszystkie raporty zostaną dla Ciebie zarchiwizowane. No comment provided by engineer. + + All servers + No comment provided by engineer. + All your contacts will remain connected. Wszystkie Twoje kontakty pozostaną połączone. @@ -8104,6 +8108,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Use TCP port %@ when no port is specified. No comment provided by engineer. + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat Użyj czatu @@ -9975,6 +9983,10 @@ ostatnia otrzymana wiadomość: %2$@ %d new events notification body + + From %d chat(s) + notification body + From: %@ notification body @@ -9987,11 +9999,6 @@ ostatnia otrzymana wiadomość: %2$@ New messages notification - - New messages in %d chats - Nowe wiadomości w %d czatach - notification body - diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 61495069c7..d120909994 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -796,6 +796,10 @@ swipe action Все сообщения о нарушениях будут заархивированы для вас. No comment provided by engineer. + + All servers + No comment provided by engineer. + All your contacts will remain connected. Все контакты, которые соединились через этот адрес, сохранятся. @@ -8249,6 +8253,10 @@ To connect, please ask your contact to create another connection link and check Использовать TCP-порт %@, когда порт не указан. No comment provided by engineer. + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat Использовать чат @@ -10135,6 +10143,10 @@ last received msg: %2$@ %d новых сообщений notification body + + From %d chat(s) + notification body + From: %@ От: %@ @@ -10150,11 +10162,6 @@ last received msg: %2$@ Новые сообщения notification - - New messages in %d chats - Новые сообщения в %d разговоре(ах) - notification body - diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index 7431c13969..279fab822a 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -722,6 +722,10 @@ swipe action All reports will be archived for you. No comment provided by engineer. + + All servers + No comment provided by engineer. + All your contacts will remain connected. ผู้ติดต่อทั้งหมดของคุณจะยังคงเชื่อมต่ออยู่. @@ -7509,6 +7513,10 @@ To connect, please ask your contact to create another connection link and check Use TCP port %@ when no port is specified. No comment provided by engineer. + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat ใช้แชท @@ -9265,6 +9273,10 @@ last received msg: %2$@ %d new events notification body + + From %d chat(s) + notification body + From: %@ notification body @@ -9277,10 +9289,6 @@ last received msg: %2$@ New messages notification - - New messages in %d chats - notification body - diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index 935c4885b5..2208f65b89 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -788,6 +788,10 @@ swipe action All reports will be archived for you. No comment provided by engineer. + + All servers + No comment provided by engineer. + All your contacts will remain connected. Konuştuğun kişilerin tümü bağlı kalacaktır. @@ -8119,6 +8123,10 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Use TCP port %@ when no port is specified. No comment provided by engineer. + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat Sohbeti kullan @@ -9989,6 +9997,10 @@ son alınan msj: %2$@ %d new events notification body + + From %d chat(s) + notification body + From: %@ notification body @@ -10001,10 +10013,6 @@ son alınan msj: %2$@ New messages notification - - New messages in %d chats - notification body - diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 9ea65c4b11..1863bf6cd8 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -788,6 +788,10 @@ swipe action All reports will be archived for you. No comment provided by engineer. + + All servers + No comment provided by engineer. + All your contacts will remain connected. Всі ваші контакти залишаться на зв'язку. @@ -8168,6 +8172,10 @@ To connect, please ask your contact to create another connection link and check Use TCP port %@ when no port is specified. No comment provided by engineer. + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat Використовуйте чат @@ -10052,6 +10060,10 @@ last received msg: %2$@ %d нових подій notification body + + From %d chat(s) + notification body + From: %@ Від: %@ @@ -10067,11 +10079,6 @@ last received msg: %2$@ Нові повідомлення notification - - New messages in %d chats - Нові повідомлення в чатах %d - notification body - diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index 30a6567414..4bc7095bd2 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -768,6 +768,10 @@ swipe action All reports will be archived for you. No comment provided by engineer. + + All servers + No comment provided by engineer. + All your contacts will remain connected. 所有联系人会保持连接。 @@ -8010,6 +8014,10 @@ To connect, please ask your contact to create another connection link and check Use TCP port %@ when no port is specified. No comment provided by engineer. + + Use TCP port 443 for preset servers only. + No comment provided by engineer. + Use chat 使用聊天 @@ -9875,6 +9883,10 @@ last received msg: %2$@ %d new events notification body + + From %d chat(s) + notification body + From: %@ notification body @@ -9887,10 +9899,6 @@ last received msg: %2$@ New messages notification - - New messages in %d chats - notification body - diff --git a/apps/ios/SimpleX NSE/de.lproj/Localizable.strings b/apps/ios/SimpleX NSE/de.lproj/Localizable.strings index f9779c6e05..d0b2f8bd1b 100644 --- a/apps/ios/SimpleX NSE/de.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/de.lproj/Localizable.strings @@ -10,6 +10,3 @@ /* notification */ "New messages" = "Neue Nachrichten"; -/* notification body */ -"New messages in %d chats" = "Neue Nachrichten in %d Chats"; - diff --git a/apps/ios/SimpleX NSE/es.lproj/Localizable.strings b/apps/ios/SimpleX NSE/es.lproj/Localizable.strings index fb190400e1..8b43c489b7 100644 --- a/apps/ios/SimpleX NSE/es.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/es.lproj/Localizable.strings @@ -10,6 +10,3 @@ /* notification */ "New messages" = "Mensajes nuevos"; -/* notification body */ -"New messages in %d chats" = "Mensajes nuevos en %d chat(s)"; - diff --git a/apps/ios/SimpleX NSE/fr.lproj/Localizable.strings b/apps/ios/SimpleX NSE/fr.lproj/Localizable.strings index a57961c934..999bb3608f 100644 --- a/apps/ios/SimpleX NSE/fr.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/fr.lproj/Localizable.strings @@ -10,6 +10,3 @@ /* notification */ "New messages" = "Nouveaux messages"; -/* notification body */ -"New messages in %d chats" = "Nouveaux messages dans %d chats"; - diff --git a/apps/ios/SimpleX NSE/hu.lproj/Localizable.strings b/apps/ios/SimpleX NSE/hu.lproj/Localizable.strings index e64c98df9e..69456fd177 100644 --- a/apps/ios/SimpleX NSE/hu.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/hu.lproj/Localizable.strings @@ -10,6 +10,3 @@ /* notification */ "New messages" = "Új üzenetek"; -/* notification body */ -"New messages in %d chats" = "Új üzenetek %d csevegésben"; - diff --git a/apps/ios/SimpleX NSE/it.lproj/Localizable.strings b/apps/ios/SimpleX NSE/it.lproj/Localizable.strings index 31f463eb5b..e22f5aeac3 100644 --- a/apps/ios/SimpleX NSE/it.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/it.lproj/Localizable.strings @@ -10,6 +10,3 @@ /* notification */ "New messages" = "Nuovi messaggi"; -/* notification body */ -"New messages in %d chats" = "Nuovi messaggi in %d chat"; - diff --git a/apps/ios/SimpleX NSE/nl.lproj/Localizable.strings b/apps/ios/SimpleX NSE/nl.lproj/Localizable.strings index 4cf91689b5..12d1e01f1d 100644 --- a/apps/ios/SimpleX NSE/nl.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/nl.lproj/Localizable.strings @@ -10,6 +10,3 @@ /* notification */ "New messages" = "Nieuwe berichten"; -/* notification body */ -"New messages in %d chats" = "Nieuwe berichten in %d chats"; - diff --git a/apps/ios/SimpleX NSE/ru.lproj/Localizable.strings b/apps/ios/SimpleX NSE/ru.lproj/Localizable.strings index 6ba39ccc63..7205b37e7f 100644 --- a/apps/ios/SimpleX NSE/ru.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/ru.lproj/Localizable.strings @@ -10,6 +10,3 @@ /* notification */ "New messages" = "Новые сообщения"; -/* notification body */ -"New messages in %d chats" = "Новые сообщения в %d разговоре(ах)"; - diff --git a/apps/ios/SimpleX NSE/uk.lproj/Localizable.strings b/apps/ios/SimpleX NSE/uk.lproj/Localizable.strings index 69cc53bff1..ceace71e34 100644 --- a/apps/ios/SimpleX NSE/uk.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/uk.lproj/Localizable.strings @@ -10,6 +10,3 @@ /* notification */ "New messages" = "Нові повідомлення"; -/* notification body */ -"New messages in %d chats" = "Нові повідомлення в чатах %d"; - diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index 25fb66aa12..c150ba5564 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -124,6 +124,18 @@ /* time interval */ "%d days" = "%d dní"; +/* forward confirmation reason */ +"%d file(s) are still being downloaded." = "%d soubor(y) stále stahován(y)."; + +/* forward confirmation reason */ +"%d file(s) failed to download." = "%d soubor(y) se nepodařilo stáhnout."; + +/* forward confirmation reason */ +"%d file(s) were deleted." = "%d soubor(y) smazán(y)."; + +/* forward confirmation reason */ +"%d file(s) were not downloaded." = "%d soubor(y) nestažen(y)."; + /* time interval */ "%d hours" = "%d hodin"; diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index ef03de17f0..8b0e18ecce 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -4751,6 +4751,9 @@ chat item action */ /* No comment provided by engineer. */ "Share with contacts" = "Mit Kontakten teilen"; +/* No comment provided by engineer. */ +"Short link" = "Verkürzter Link"; + /* No comment provided by engineer. */ "Show → on messages sent via private routing." = "Bei Nachrichten, die über privates Routing versendet wurden, → anzeigen."; @@ -4793,6 +4796,9 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX address or 1-time link?" = "SimpleX-Adresse oder Einmal-Link?"; +/* simplex link type */ +"SimpleX channel link" = "SimpleX-Kanal-Link"; + /* No comment provided by engineer. */ "SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "SimpleX-Chat und Flux haben vereinbart, die von Flux betriebenen Server in die App aufzunehmen."; @@ -5181,6 +5187,9 @@ report reason */ /* No comment provided by engineer. */ "This is your own SimpleX address!" = "Das ist Ihre eigene SimpleX-Adresse!"; +/* No comment provided by engineer. */ +"This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "Für diesen Link wird eine neuere App-Version benötigt. Bitte aktualisieren Sie die App oder bitten Sie Ihren Kontakt einen kompatiblen Link zu senden."; + /* No comment provided by engineer. */ "This link was used with another mobile device, please create a new link on the desktop." = "Dieser Link wurde schon mit einem anderen Mobiltelefon genutzt. Bitte erstellen sie einen neuen Link in der Desktop-App."; @@ -5373,6 +5382,9 @@ report reason */ /* swipe action */ "Unread" = "Ungelesen"; +/* No comment provided by engineer. */ +"Unsupported connection link" = "Verbindungs-Link wird nicht unterstützt"; + /* No comment provided by engineer. */ "Up to 100 last messages are sent to new members." = "Bis zu 100 der letzten Nachrichten werden an neue Mitglieder gesendet."; @@ -5466,6 +5478,9 @@ report reason */ /* No comment provided by engineer. */ "Use servers" = "Verwende Server"; +/* No comment provided by engineer. */ +"Use short links (BETA)" = "Kurze Links verwenden (BETA)"; + /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Verwenden Sie SimpleX-Chat-Server?"; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index f9fe85dba7..9644521723 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -4751,6 +4751,9 @@ chat item action */ /* No comment provided by engineer. */ "Share with contacts" = "Compartir con contactos"; +/* No comment provided by engineer. */ +"Short link" = "Enlace corto"; + /* No comment provided by engineer. */ "Show → on messages sent via private routing." = "Mostrar → en mensajes con enrutamiento privado."; @@ -4793,6 +4796,9 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX address or 1-time link?" = "¿Dirección SimpleX o enlace de un uso?"; +/* simplex link type */ +"SimpleX channel link" = "Enlace de canal SimpleX"; + /* No comment provided by engineer. */ "SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "Simplex Chat y Flux han acordado incluir en la aplicación servidores operados por Flux."; @@ -5181,6 +5187,9 @@ report reason */ /* No comment provided by engineer. */ "This is your own SimpleX address!" = "¡Esta es tu propia dirección SimpleX!"; +/* No comment provided by engineer. */ +"This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "Este enlace requiere una versión más reciente de la aplicación. Por favor, actualiza la aplicación o pide a tu contacto un enlace compatible."; + /* No comment provided by engineer. */ "This link was used with another mobile device, please create a new link on the desktop." = "Este enlace ha sido usado en otro dispositivo móvil, por favor crea un enlace nuevo en el ordenador."; @@ -5373,6 +5382,9 @@ report reason */ /* swipe action */ "Unread" = "No leído"; +/* No comment provided by engineer. */ +"Unsupported connection link" = "Enlace de conexión no compatible"; + /* No comment provided by engineer. */ "Up to 100 last messages are sent to new members." = "Hasta 100 últimos mensajes son enviados a los miembros nuevos."; @@ -5466,6 +5478,9 @@ report reason */ /* No comment provided by engineer. */ "Use servers" = "Usar servidores"; +/* No comment provided by engineer. */ +"Use short links (BETA)" = "Usar enlaces cortos (BETA)"; + /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "¿Usar servidores SimpleX Chat?"; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index 2d2fbb2ad1..23ff6fc0bf 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -692,7 +692,7 @@ swipe action */ "Ask" = "Mindig kérdezzen rá"; /* No comment provided by engineer. */ -"Attach" = "Csatolás"; +"Attach" = "Mellékelés"; /* No comment provided by engineer. */ "attempts" = "próbálkozások"; @@ -2671,7 +2671,7 @@ snd error text */ "Hide profile" = "Profil elrejtése"; /* No comment provided by engineer. */ -"Hide:" = "Elrejtés:"; +"Hide:" = "Elrejtve:"; /* No comment provided by engineer. */ "History" = "Előzmények"; @@ -3915,7 +3915,7 @@ time to disappear */ "Privacy policy and conditions of use." = "Adatvédelmi szabályzat és felhasználási feltételek."; /* No comment provided by engineer. */ -"Privacy redefined" = "Adatvédelem újraértelmezve"; +"Privacy redefined" = "Újraértelmezett adatvédelem"; /* No comment provided by engineer. */ "Private chats, groups and your contacts are not accessible to server operators." = "A privát csevegések, a csoportok és a partnerek nem érhetők el a szerver üzemeltetői számára."; @@ -4751,6 +4751,9 @@ chat item action */ /* No comment provided by engineer. */ "Share with contacts" = "Megosztás a partnerekkel"; +/* No comment provided by engineer. */ +"Short link" = "Rövid hivatkozás"; + /* No comment provided by engineer. */ "Show → on messages sent via private routing." = "Egy „→” jel megjelenítése a privát útválasztáson keresztül küldött üzeneteknél."; @@ -4776,7 +4779,7 @@ chat item action */ "Show QR code" = "QR-kód megjelenítése"; /* No comment provided by engineer. */ -"Show:" = "Megjelenítés:"; +"Show:" = "Megjelenítve:"; /* No comment provided by engineer. */ "SimpleX" = "SimpleX"; @@ -4793,6 +4796,9 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX address or 1-time link?" = "SimpleX-cím vagy egyszer használható meghívó?"; +/* simplex link type */ +"SimpleX channel link" = "SimpleX-csatornahivatkozás"; + /* No comment provided by engineer. */ "SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "A SimpleX Chat és a Flux megállapodást kötött arról, hogy a Flux által üzemeltetett kiszolgálókat beépítik az alkalmazásba."; @@ -5181,6 +5187,9 @@ report reason */ /* No comment provided by engineer. */ "This is your own SimpleX address!" = "Ez a saját SimpleX-címe!"; +/* No comment provided by engineer. */ +"This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "Ez a hivatkozás újabb alkalmazásverziót igényel. Frissítse az alkalmazást vagy kérjen egy kompatibilis hivatkozást a partnerétől."; + /* No comment provided by engineer. */ "This link was used with another mobile device, please create a new link on the desktop." = "Ezt a hivatkozást egy másik hordozható eszközön már használták, hozzon létre egy új hivatkozást a számítógépén."; @@ -5373,6 +5382,9 @@ report reason */ /* swipe action */ "Unread" = "Olvasatlan"; +/* No comment provided by engineer. */ +"Unsupported connection link" = "Nem támogatott kapcsolattartási hivatkozás"; + /* No comment provided by engineer. */ "Up to 100 last messages are sent to new members." = "Legfeljebb az utolsó 100 üzenet lesz elküldve az új tagok számára."; @@ -5466,6 +5478,9 @@ report reason */ /* No comment provided by engineer. */ "Use servers" = "Kiszolgálók használata"; +/* No comment provided by engineer. */ +"Use short links (BETA)" = "Rövid hivatkozások használata (béta)"; + /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "SimpleX Chat-kiszolgálók használata?"; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index 3988b33531..f68424325f 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -4751,6 +4751,9 @@ chat item action */ /* No comment provided by engineer. */ "Share with contacts" = "Condividi con i contatti"; +/* No comment provided by engineer. */ +"Short link" = "Link breve"; + /* No comment provided by engineer. */ "Show → on messages sent via private routing." = "Mostra → nei messaggi inviati via instradamento privato."; @@ -4793,6 +4796,9 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX address or 1-time link?" = "Indirizzo SimpleX o link una tantum?"; +/* simplex link type */ +"SimpleX channel link" = "Link del canale SimpleX"; + /* No comment provided by engineer. */ "SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "SimpleX Chat e Flux hanno concluso un accordo per includere server gestiti da Flux nell'app."; @@ -5181,6 +5187,9 @@ report reason */ /* No comment provided by engineer. */ "This is your own SimpleX address!" = "Questo è il tuo indirizzo SimpleX!"; +/* No comment provided by engineer. */ +"This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "Questo link richiede una versione più recente dell'app. Aggiornala o chiedi al tuo contatto di inviare un link compatibile."; + /* No comment provided by engineer. */ "This link was used with another mobile device, please create a new link on the desktop." = "Questo link è stato usato con un altro dispositivo mobile, creane uno nuovo sul desktop."; @@ -5373,6 +5382,9 @@ report reason */ /* swipe action */ "Unread" = "Non letto"; +/* No comment provided by engineer. */ +"Unsupported connection link" = "Link di connessione non supportato"; + /* No comment provided by engineer. */ "Up to 100 last messages are sent to new members." = "Vengono inviati ai nuovi membri fino a 100 ultimi messaggi."; @@ -5466,6 +5478,9 @@ report reason */ /* No comment provided by engineer. */ "Use servers" = "Usa i server"; +/* No comment provided by engineer. */ +"Use short links (BETA)" = "Usa link brevi (BETA)"; + /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Usare i server di SimpleX Chat?"; diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index a804d48dfb..4bb2244785 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -2372,4 +2372,8 @@ رابط قصير رابط قناة SimpleX رابط اتصال غير مدعوم + استخدم منفذ TCP 443 للخوادم المُعدة مسبقًا فقط. + إيقاف التشغيل + الخوادم المُعدة مسبقًا + جميع الخوادم diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml index 77c2a6ef65..0e179fadd4 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml @@ -2349,4 +2349,8 @@ Emprar enllaços curts (BETA) Enllaç complet Enllaç curt + Tots els servidors + Apagat + Feu servir el port TCP 443 només per a servidors predefinits. + Servidors predefinits diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml index e262a59214..439b9df18d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml @@ -446,7 +446,7 @@ špatné ID zprávy duplicitní zpráva Přeskočené zprávy - Ochrana osobních údajů a zabezpečení + Soukromí a zabezpečení Vaše soukromí Skrývat aplikaci Odesílat náhledy odkazů @@ -2251,7 +2251,7 @@ Přepnout chat seznam: Tuto akci nelze zrušit - zprávy odeslané a přijaté v tomto chatu dříve než vybraná, budou smazány. Statistiky serverů budou obnoveny - nemůže být vráceno! - Odeslat soukromý report + Odešlete soukromý report Pomozte administrátorům moderovat své skupiny. Rychlejší mazání skupin. Od %s. @@ -2335,7 +2335,7 @@ Zprávy budou smazány pro všechny členy. Aplikace vyžaduje potvrzení stahování z neznámých serverů (s výjimkou .onion nebo při aktivaci SOCKS proxy). Musíte povolit kontaktům volání, abyste jim mohli zavolat. - Nastavit expirace zpráv. + Nastavení expirace zpráv. Zobrazit procenta Nahraný archiv databáze bude ze serverů trvale odstraněn. Pro ochranu před záměnou odkazů, můžete porovnat bezpečnostní kódy. @@ -2370,4 +2370,14 @@ Nastavit operátora serveru Zásady ochrany soukromí a podmínky používání. Soukromé konverzace, skupiny a kontakty nejsou přístupné provozovatelům serverů. + Nepodporovaný odkaz k připojení + Používejte krátké odkazy (BETA) + Tento odkaz vyžaduje novější verzi aplikace. Prosím aktualizujte aplikaci nebo požádejte kontakt o odeslání kompatibilního odkazu. + odkaz SimpleX kanálu + Úplný odkaz + Krátký odkaz + Všechny servery + Vypnut + Přednastavené servery + Použít TCP port 443 jen pro přednastavené servery. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index 089ef06827..6472faa671 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -2453,9 +2453,13 @@ Server-Betreiber konfigurieren Private Chats, Gruppen und Ihre Kontakte sind für Server-Betreiber nicht zugänglich. Verbindungs-Link wird nicht unterstützt - Verkürzte Links verwenden (BETA) + Kurze Links verwenden (BETA) Verkürzter Link Vollständiger Link SimpleX-Kanal-Link Für diesen Link wird eine neuere App-Version benötigt. Bitte aktualisieren Sie die App oder bitten Sie Ihren Kontakt einen kompatiblen Link zu senden. + Alle Server + Aus + Für voreingestellte Server nur TCP-Port 443 verwenden . + Voreingestellte Server diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index 6ba4c15afb..171b367ca0 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -2383,4 +2383,8 @@ Este enlace requiere una versión más reciente de la aplicación. Por favor, actualiza la aplicación o pide a tu contacto un enlace compatible. Enlace de conexión no compatible Usar enlaces cortos (BETA) + Usar puerto TCP 443 solo en servidores predefinidos. + Todos los servidores + Servidores predefinidos + No diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index 6d62d3f5b8..2776310540 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -141,7 +141,7 @@ Engedélyezés Érvénytelen számítógépcím Profil hozzáadása - Csatolás + Mellékelés Alkalmazás jelkód Felkérték a kép fogadására Kamera @@ -658,7 +658,7 @@ Kép A fájlok- és a médiatartalmak küldése le van tiltva. Hogyan működik - Elrejtés: + Elrejtve: Hiba történt a partnerrel történő kapcsolat létrehozásában ICE-kiszolgálók (soronként egy) beolvashatja a QR-kódot a videohívásban, vagy a partnere megoszthat egy meghívási hivatkozást.]]> @@ -1122,7 +1122,7 @@ Biztonsági kód hitelesítése eltávolította Önt SimpleX-cím - Megjelenítés: + Megjelenítve: válasz fogadása… Visszaállítja az adatbázismentést? Üzenetek fogadása… @@ -1175,7 +1175,7 @@ elküldve SOCKS-proxy használata Élő üzenet küldése - Adatvédelem újraértelmezve + Újraértelmezett adatvédelem Hangüzenet… Alkalmazás képernyőjének védelme QR-kód megjelenítése @@ -2350,4 +2350,8 @@ Teljes hivatkozás Ez a hivatkozás újabb alkalmazásverziót igényel. Frissítse az alkalmazást vagy kérjen egy kompatibilis hivatkozást a partnerétől. SimpleX-csatornahivatkozás + Összes kiszolgáló + Kikapcsolva + Előre beállított kiszolgálók + A 443-as TCP-port használata kizárólag az előre beállított kiszolgálokhoz. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml index 95b60a3394..a269149e99 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml @@ -2349,4 +2349,14 @@ Kebijakan privasi dan ketentuan penggunaan. Obrolan pribadi, grup, dan kontak Anda tidak dapat diakses oleh operator server. Frasa sandi di Keystore tidak dapat dibaca, silakan masukkan secara manual. Hal ini mungkin terjadi setelah pembaruan sistem yang tidak kompatibel dengan aplikasi. Jika tidak demikian, silakan hubungi pengembang. + Gunakan port TCP 443 hanya untuk presetel server. + Semua server + Mati + Presetel server + Tautan lengkap + Tautan ini perlu versi aplikasi yang baru. Harap perbarui aplikasi atau minta kontak untuk kirim tautan kompatibel. + Tautan saluran SimpleX + Gunakan tautan singkat (BETA) + Tautan koneksi tidak didukung + Tautan singkat diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index 4373091266..201745f042 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -2387,4 +2387,8 @@ Link del canale SimpleX Link di connessione non supportato Usa link brevi (BETA) + Tutti i server + Off + Server preimpostati + Usa la porta TCP 443 solo per i server preimpostati. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index ad2a7e67be..549cb01b63 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -18,7 +18,7 @@ 1-разове посилання Про SimpleX Chat Додавайте сервери, скануючи QR-коди. - Всі чати і повідомлення будуть видалені - цю дію неможливо скасувати! + Усі чати та повідомлення будуть видалені - цю дію неможливо скасувати! Дозволити дзвінки тільки за умови, що ваш контакт дозволяє їх. Дозволити безповоротне видалення повідомлень, тільки якщо ваш контакт дозволяє вам. (24 години) Дозволити голосові повідомлення\? @@ -50,20 +50,20 @@ Дозволити Розширені налаштування мережі Отримувати доступ до серверів через SOCKS-проксі на порті %d? Проксі має бути запущено до активації цієї опції. - Всі ваші контакти залишаться підключеними. - Всі дані застосунку буде видалено. + Усі ваші контакти залишаться підключеними. + Усі дані застосунку буде видалено. Після перезапуску додатка або зміни ключової фрази буде використано сховище ключів Android для безпечного збереження ключової фрази - це дозволить отримувати сповіщення. Дозвольте вашим контактам надсилати голосові повідомлення. Прийняти інкогніто Додати сервер адміністратор Додати привітання - Всі учасники групи залишаться підключеними. + Усі учасники групи залишаться підключеними. Дозвольте вашим контактам надсилати повідомлення, які зникають. - Всі повідомлення будуть видалені - цю дію неможливо скасувати! Повідомлення будуть видалені ЛИШЕ для вас. + Усі повідомлення будуть видалені - цю дію неможливо скасувати! Повідомлення будуть видалені ЛИШЕ для вас. Версія додатку Додайте адресу до свого профілю, щоб ваші контакти могли поділитися нею з іншими людьми. Оновлення профілю буде відправлено вашим контактам. - Всі ваші контакти залишаться підключеними. Оновлення профілю буде відправлено вашим контактам. + Усі ваші контакти залишаться підключеними. Оновлення профілю буде відправлено вашим контактам. Відповісти на виклик Адреса Додати профіль @@ -717,7 +717,7 @@ Зникає о: %s (поточне) Вилучити учасника - Роль буде змінено на "%s". Всі учасники групи будуть сповіщені. + Роль буде змінено на %s. Усі учасники групи будуть сповіщені. Роль буде змінено на "%s". Учасник отримає нове запрошення. Група Привітальне повідомлення @@ -1428,7 +1428,7 @@ Ви вже подали запит на підключення за цією адресою! надіслати приватне повідомлення Показувати консоль в новому вікні - Всі нові повідомлення від %s будуть приховані! + Усі нові повідомлення від %s будуть приховані! підключив(лась) безпосередньо заблоковано Блокувати учасників групи @@ -1674,7 +1674,7 @@ Перевірте підключення до Інтернету та спробуйте ще раз Переконайтеся, що ви пам\'ятаєте пароль до бази даних для її перенесення. Помилка при перевірці парольної фрази: - Всі ваші контакти, розмови та файли будуть надійно зашифровані та завантажені частинами на налаштовані XFTP-реле. + Усі ваші контакти, розмови та файли будуть надійно зашифровані та завантажені частинами на налаштовані XFTP-реле. Please note: використання однієї і тієї ж бази даних на двох пристроях порушить розшифровку повідомлень з ваших з\'єднань, як захист безпеки.]]> Скасувати міграцію Чат перемістився! @@ -2025,7 +2025,7 @@ Проксірований Надіслати помилки Завершено - Всі профілі + Усі профілі Скинути Вивантажено Видалити %d повідомлень учасників? @@ -2384,4 +2384,8 @@ Коротке посилання Посилання на канал SimpleX Несумісне посилання для підключення + Усі сервери + Ні + Типові сервери + Використовуйте TCP порт 443 лише для попередньо налаштованих серверів. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml index 99931215e7..3cbc54f652 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml @@ -1263,7 +1263,7 @@ Đang chờ xử lý Đang chờ xử lý Không tìm thấy mật khẩu trong Keystore, vui lòng nhập thủ công. Điều này có thể xảy ra nếu bạn khôi phục dữ liệu ứng dụng bằng một công cụ sao lưu. Nếu không phải như vậy, xin vui lòng liên hệ với nhà phát triển. - Thành viên cũ %1$s + Thành viên trước đây %1$s Dán đường dẫn để kết nối! Thông báo định kỳ Dán đường dẫn mà bạn nhận được @@ -2358,4 +2358,8 @@ Sử dụng đường dẫn ngắn (BETA) Toàn bộ đường dẫn Đường dẫn ngắn + Tắt + Các máy chủ cài sẵn + Chỉ sử dụng cổng TCP 443 cho các máy chủ cài sẵn. + Tất cả máy chủ diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index 1958885843..a85bd119c5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -2371,4 +2371,8 @@ 短链接 此链接需要更新的应用版本。请升级应用或请求你的联系人发送相容的链接。 完整链接 + 全部服务器 + 关闭 + 预设服务器 + 仅预设服务器使用 TCP 协议 443 端口。 From f49c51ae16cb521db681f0982e7bbf6210fa2578 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 7 May 2025 11:27:10 +0100 Subject: [PATCH 535/567] website: translations, readme: ZEC address (#5875) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (German) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/de/ * Translated using Weblate (Arabic) Currently translated at 91.4% (235 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ar/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/ * Translated using Weblate (Hungarian) Currently translated at 92.2% (237 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ * Translated using Weblate (Arabic) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ar/ * Translated using Weblate (Arabic) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ar/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ * Translated using Weblate (German) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/de/ * Translated using Weblate (Arabic) Currently translated at 91.4% (235 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ar/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/ * Translated using Weblate (Hungarian) Currently translated at 92.2% (237 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ * Translated using Weblate (Arabic) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ar/ * Translated using Weblate (Arabic) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ar/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ * Translated using Weblate (Arabic) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ar/ * Translated using Weblate (Italian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/it/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ * fix * ZEC address --------- Co-authored-by: mlanp Co-authored-by: Muhammad Co-authored-by: 大王叫我来巡山 Co-authored-by: summoner001 Co-authored-by: jonnysemon Co-authored-by: Random --- README.md | 1 + docs/dependencies/README.md | 6 ++-- website/langs/ar.json | 56 ++++++++++++++++++------------------- website/langs/de.json | 40 +++++++++++++------------- website/langs/hu.json | 40 +++++++++++++------------- website/langs/it.json | 40 +++++++++++++------------- website/langs/zh_Hans.json | 40 +++++++++++++------------- website/web.sh | 1 + 8 files changed, 113 insertions(+), 111 deletions(-) diff --git a/README.md b/README.md index 7e6e5d975d..40d552b84d 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,7 @@ It is possible to donate via: - ETH: 0xD9ee7Db0AD0dc1Dfa7eD53290199ED06beA04692 - USDT (Ethereum): 0xD9ee7Db0AD0dc1Dfa7eD53290199ED06beA04692 - ZEC: t1fwjQW5gpFhDqXNhxqDWyF9j9WeKvVS5Jg +- ZEC shielded: u16rnvkflumf5uw9frngc2lymvmzgdr2mmc9unyu0l44unwfmdcpfm0axujd2w34ct3ye709azxsqge45705lpvvqu264ltzvfay55ygyq - DOGE: D99pV4n9TrPxBPCkQGx4w4SMSa6QjRBxPf - SOL: 7JCf5m3TiHmYKZVr6jCu1KeZVtb9Y1jRMQDU69p5ARnu - please ask if you want to donate any other coins. diff --git a/docs/dependencies/README.md b/docs/dependencies/README.md index 02c0623988..bec369c126 100644 --- a/docs/dependencies/README.md +++ b/docs/dependencies/README.md @@ -1,9 +1,9 @@ # SimpleX Chat and SimpleX servers dependencies -[SQLCipher](https://github.com/sqlcipher/sqlcipher): Extension of [SQLite](https://sqlite.org) with encryption ([BSD-style](./licences/build/sqlcipher/LICENSE.md)) +[SQLCipher](https://github.com/sqlcipher/sqlcipher): Extension of [SQLite](https://sqlite.org) with encryption ([BSD-style](./licences/apps/sqlcipher/LICENSE.md)) -[vlc](https://github.com/videolan/vlc): VLC media player library ([LGPLv2](./licences/build/vlc/COPYING.LIB)) +[vlc](https://github.com/videolan/vlc): VLC media player library ([LGPLv2](./licences/apps/vlc/COPYING.LIB)) -[WebRTC](https://webrtc.googlesource.com/src/): RTC for calls ([BSD-3-clause](./licences/build/webrtc/LICENSE)) +[WebRTC](https://webrtc.googlesource.com/src/): RTC for calls ([BSD-3-clause](./licences/apps/webrtc/LICENSE)) [Haskell dependencies](./HASKELL.md). diff --git a/website/langs/ar.json b/website/langs/ar.json index fd759d5491..0c1fb4515a 100644 --- a/website/langs/ar.json +++ b/website/langs/ar.json @@ -29,7 +29,7 @@ "simplex-explained-tab-2-text": "2. كيف يعمل", "simplex-explained-tab-1-p-2": "كيف يمكن أن تعمل مع قوائم انتظار أحادية الاتجاه وبدون معرّفات ملف تعريف المستخدم؟", "simplex-explained-tab-2-p-1": "لكل اتصال، تستخدم قائمتي انتظار منفصلتين للمُراسلة لإرسال واستلام الرسائل عبر خوادم مختلفة.", - "simplex-explained-tab-2-p-2": "تقوم الخوادم بتمرير الرسائل في اتجاه واحد فقط، دون الحصول على الصورة الكاملة لمُحادثات المستخدم أو اتصالاته.", + "simplex-explained-tab-2-p-2": "تمرّر الخوادم الرسائل في اتجاه واحد فقط، دون الحصول على الصورة الكاملة لمُحادثات المستخدم أو اتصالاته.", "simplex-explained-tab-3-p-1": "تحتوي الخوادم على بيانات اعتماد مجهولة منفصلة لكل قائمة انتظار، ولا تعرف المستخدمين الذين ينتمون إليهم.", "copyright-label": "مشروع مفتوح المصدر © SimpleX 2020-2025", "simplex-chat-protocol": "بروتوكول دردشة SimpleX", @@ -51,13 +51,13 @@ "simplex-private-5-title": "طبقات متعددة من
    حشوة المحتوى", "simplex-private-7-title": "التحقق
    من سلامة الرسالة", "simplex-private-8-title": "خلط الرسائل
    لتقليل من الارتباط", - "simplex-private-10-title": "معرفات زوجية مجهولة مؤقتة", + "simplex-private-10-title": "معرّفات زوجية مجهولة مؤقتة", "simplex-private-card-3-point-1": "يتم استخدام TLS 1.2 / 1.3 مع خوارزميات قوية فقط لاتصالات الخادم والعميل.", "simplex-private-card-3-point-2": "تعمل بصمة الخادم وربط القناة على منع هجمات الوسيط (MITM) وإعادة التشغيل.", "simplex-private-card-3-point-3": "استئناف الاتصال معطل لمنع هجمات الجلسة.", "simplex-private-card-4-point-1": "لحماية عنوان IP الخاص بك، يمكنك الوصول إلى الخوادم عبر تور أو بعض شبكات تراكب النقل الأخرى.", "simplex-private-card-5-point-1": "يستخدم SimpleX حشوة المحتوى لكل طبقة تعمية لإحباط هجمات حجم الرسالة.", - "simplex-private-card-6-point-2": "لمنع ذلك، تقوم تطبيقات SimpleX بتمرير مفاتيح لمرة واحدة خارج النطاق، عند مشاركة عنوان كرابط أو رمز QR.", + "simplex-private-card-6-point-2": "لمنع ذلك، تقوم تطبيقات SimpleX بتمرير مفاتيح لمرة واحدة خارج النطاق، عندما تُشارك عنوان كرابط أو رمز QR.", "simplex-private-card-8-point-1": "تعمل خوادم SimpleX كعقد مختلطة بزمن انتقال منخفض — الرسائل الواردة والصادرة لها ترتيب مختلف.", "simplex-private-card-9-point-1": "كل رسالة انتظار تمرر الرسائل في اتجاه واحد، بعناوين إرسال واستلام مختلفة.", "simplex-private-card-9-point-2": "إنه يقلل من نواقل الهجوم، مقارنة بوسطاء الرسائل التقليديين، والبيانات الوصفية المتاحة.", @@ -74,7 +74,7 @@ "simplex-unique-1-overlay-1-title": "الخصوصية الكاملة لهويتك وملفك الشخصي وجهات الاتصال والبيانات الوصفية", "simplex-unique-2-title": "أنت محمي
    من رسائل الإزعاج وإساءة الاستخدام", "simplex-unique-3-title": "أنت تتحكم في بياناتك", - "simplex-unique-3-overlay-1-title": "ملكية ومراقبة وأمان بياناتك", + "simplex-unique-3-overlay-1-title": "الملكية والتحكم وأمن بياناتك", "simplex-unique-4-title": "أنت تمتلك شبكة SimpleX", "simplex-unique-4-overlay-1-title": "لامركزية بالكامل — يمتلك المستخدمون شبكة SimpleX", "hero-overlay-card-1-p-4": "هذا التصميم يمنع تسريب أي البيانات الوصفية للمستخدمين على مستوى التطبيق. لزيادة تحسين الخصوصية وحماية عنوان IP الخاص بك، يمكنك الاتصال بخوادم المراسلة عبر Tor.", @@ -85,23 +85,23 @@ "privacy-matters-overlay-card-1-p-2": "يعرف تجار التجزئة عبر الإنترنت أن الأشخاص ذوي الدخل المنخفض هم أكثر عرضة لإجراء عمليات شراء عاجلة، لذلك قد يفرضون أسعارًا أعلى أو يزيلون الخصومات.", "simplex-private-6-title": "تبديل
    خارج النطاق", "simplex-private-9-title": "قوائم انتظار
    أحادية الاتجاه", - "privacy-matters-overlay-card-1-p-4": "تحمي منصة SimpleX خصوصية اتصالاتك بشكل أفضل من أي بديل آخر، مما يمنع تمامًا الرسم البياني الاجتماعي الخاص بك من أن يصبح متاحًا لأي شركات أو مؤسسات. حتى عندما يستخدم الأشخاص الخوادم التي توفرها SimpleX Chat، فإننا لا نعرف عدد المستخدمين أو اتصالاتهم.", + "privacy-matters-overlay-card-1-p-4": "تحمي شبكة SimpleX خصوصية اتصالاتك بشكل أفضل من أي بديل آخر، مما يمنع تمامًا الرسم البياني الاجتماعي الخاص بك من أن يصبح متاحًا لأي شركات أو مؤسسات. حتى عندما يستخدم الأشخاص الخوادم التي توفرها SimpleX Chat، فإننا لا نعرف عدد المستخدمين أو اتصالاتهم.", "simplex-private-card-1-point-1": "بروتوكول السقاطة المزدوجة —
    رسائل OTR مع السرية المستمرة واستعادة الاختراق.", "simplex-private-card-1-point-2": "NaCL cryptobox في كل قائمة انتظار لمنع ارتباط حركة مرور البيانات بين قوائم انتظار الرسائل في حالة اختراق TLS.", "simplex-private-card-2-point-1": "طبقة إضافية من تعمية الخادم للتسليم إلى المُستلم، لمنع الارتباط بين حركة مرور بيانات الخادم المُستلمة والمُرسلة في حالة اختراق TLS.", "simplex-private-card-4-point-2": "لاستخدام SimpleX عبر تور، يُرجى تثبيت تطبيق Orbot وتمكّين وكيل SOCKS5 (أو VPN على iOS ).", "simplex-private-card-5-point-2": "يجعل الرسائل ذات الأحجام المختلفة تبدو متشابهة للخوادم ومراقبي الشبكة.", - "simplex-private-card-6-point-1": "العديد من منصات الاتصال عرضة لهجمات الوسيط (MITM) من قبل الخوادم أو موفري الشبكات.", + "simplex-private-card-6-point-1": "العديد من شبكات التواصل عرضة لهجمات الوسيط (MITM) من قِبل الخوادم أو موفري الشبكات.", "simplex-private-card-7-point-1": "لضمان سلامة الرسائل يتم ترقيمها بالتسلسل وتضمين تجزئة الرسالة السابقة.", "simplex-private-card-7-point-2": "إذا أُضيفت أي رسالة أو أُزيلت أو تغيّرت، فسيتم تنبيه المُستلم.", "simplex-private-card-10-point-2": "يسمح بتسليم الرسائل بدون معرّفات ملف تعريف المستخدم، مما يوفر خصوصية للبيانات الوصفية أفضل من البدائل.", "privacy-matters-2-overlay-1-linkText": "تمنحك الخصوصية القوة", "simplex-unique-2-overlay-1-title": "أفضل حماية من رسائل الإزعاج وإساءة الاستخدام", - "hero-overlay-card-1-p-3": "أنت تحدد الخادم (الخوادم) المراد استخدامه لاستلام الرسائل وجهات الاتصال الخاصة بك — الخوادم التي تستخدمها لإرسال الرسائل إليهم. من المرجح أن تستخدم كل مُحادثة خادمين مختلفين.", + "hero-overlay-card-1-p-3": "أنت تحدد الخادم (الخوادم) المراد استخدامه لاستلام الرسائل وجهات اتصالك — الخوادم التي تستخدمها لإرسال الرسائل إليهم. من المرجح أن تستخدم كل مُحادثة خادمين مختلفين.", "hero-overlay-card-1-p-1": "سأل العديد من المستخدمين: إذا لم يكن لدى SimpleX معرّفات مستخدم، فكيف يمكنها معرفة مكان تسليم الرسائل؟ ", - "hero-overlay-card-1-p-2": "لتوصيل الرسائل، بدلاً من معرفات المستخدم التي تستخدمها جميع المنصات الأخرى، يستخدم SimpleX معرّفات مزدوجة مؤقتة مجهولة الهوية لقوائم انتظار الرسائل، مختلفة لكل اتصال من اتصالاتك — لا توجد معرفات مستخدم دائمة.", + "hero-overlay-card-1-p-2": "لتسليم الرسائل، بدلاً من معرّفات المُستخدم المُستخدمة من قِبل جميع الشبكات الأخرى، يستخدم SimpleX معرّفات زوجية مجهولة مؤقتة لقوائم الرسائل، منفصلة لكل اتصال من اتصالاتك — ولا توجد معرّفات طويلة الأجل.", "simplex-network-overlay-card-1-p-1": "بروتوكولات المُراسلة والتطبيقات P2P بها مشاكل مختلفة تجعلها أقل موثوقية من SimpleX وأكثر تعقيدًا في التحليل و عرضة لعدة أنواع من الهجمات.", - "hero-overlay-card-2-p-1": "عندما يكون لدى المستخدمين هويات ثابتة، حتى لو كان هذا مجرد رقم عشوائي، مثل معرف الجلسة، فهناك خطر يتمثل في أن الموفر أو المهاجم يمكنه مراقبة كيفية اتصال المستخدمين وعدد الرسائل التي يرسلونها.", + "hero-overlay-card-2-p-1": "عندما يكون لدى المستخدمين هويات ثابتة، حتى لو كان هذا مجرد رقم عشوائي، مثل معرّف الجلسة، فهناك خطر يتمثل في أن الموفر أو المهاجم يمكنه مراقبة كيفية اتصال المستخدمين وعدد الرسائل التي يرسلونها.", "hero-overlay-card-1-p-6": "اقرأ المزيد في SimpleX whitepaper .", "hero-overlay-card-2-p-3": "حتى مع معظم التطبيقات الخاصة التي تستخدم خدمات Tor v3، إذا تحدثت إلى جهتي اتصال مختلفتين عبر نفس الملف الشخصي، فيمكنهما إثبات أنهما متصلان بنفس الشخص.", "hero-overlay-card-2-p-4": "يحمي SimpleX من هذه الهجمات من خلال عدم وجود أي معرّفات مستخدم في تصميمه. وإذا كنت تستخدم وضع التخفي، فسيكون لديك اسم عرض مختلف لكل جهة اتصال، مع تجنب أي بيانات مشتركة بينهما.", @@ -112,23 +112,23 @@ "simplex-network-overlay-card-1-li-5": "قد تكون جميع شبكات P2P المعروفة عرضة لهجوم Sybil، لأن كل عقدة قابلة للاكتشاف، وتعمل الشبكة ككل. تتطلب الإجراءات المعروفة لتخفيفها إما مكونًا مركزيًا أو إثبات عمل مكلف . لا تحتوي شبكة SimpleX على إمكانية اكتشاف الخادم، فهي مجزأة وتعمل كشبكات فرعية متعددة ومعزولة، مما يجعل الهجمات على مستوى الشبكة مستحيلة.", "privacy-matters-overlay-card-3-p-1": "يجب على الجميع الاهتمام بخصوصية وأمان اتصالاتهم — يمكن للمُحادثات غير الضارة أن تعرضك للخطر، حتى لو لم يكن لديك ما تخفيه.", "privacy-matters-overlay-card-3-p-4": "لا يكفي استخدام برنامج مُراسلة مُعمَّاة بين الطرفين، يجب علينا جميعًا استخدام برامج مُراسلة التي تحمي خصوصية شبكاتنا الشخصية — مع من نحن مرتبطون.", - "simplex-unique-overlay-card-1-p-3": "يحمي هذا التصميم خصوصية الأشخاص الذين تتواصل معهم، ويخفيها عن خوادم منصة SimpleX ومن أي مراقبين. لإخفاء عنوان IP الخاص بك من الخوادم، يمكنك الاتصال بخوادم SimpleX عبر تور.", - "simplex-unique-overlay-card-2-p-1": "نظرًا لعدم وجود معرف لديك على نظام SimpleX، لا يمكن لأي شخص الاتصال بك ما لم تشارك عنوان مستخدم لمرة واحدة أو مؤقتًا، كرمز QR أو رابط.", + "simplex-unique-overlay-card-1-p-3": "يحمي هذا التصميم خصوصية الأشخاص الذين تتواصل معهم، ويخفيها عن خوادم شبكة SimpleX ومن أي مراقبين. لإخفاء عنوان IP الخاص بك من الخوادم، يمكنك الاتصال بخوادم SimpleX عبر تور.", + "simplex-unique-overlay-card-2-p-1": "نظرًا لعدم وجود معرّف لديك على شبكة SimpleX، لا يمكن لأي شخص الاتصال بك ما لم تشارك عنوان مستخدم لمرة واحدة أو مؤقتًا، كرمز QR أو رابط.", "simplex-unique-overlay-card-2-p-2": "حتى مع عنوان المستخدم الاختياري، بينما يمكن استخدامه لإرسال طلبات جهات اتصال مزعجة، يمكنك تغييره أو حذفه بالكامل دون فقد أي من اتصالاتك.", "simplex-unique-overlay-card-3-p-2": "يتم الاحتفاظ بالرسائل المُعمَّاة بين الطرفين مؤقتًا على خوادم ترحيل SimpleX حتى يتم استلامها، ثُمَّ تُحذف نهائيًا.", - "simplex-unique-overlay-card-3-p-4": "لا توجد معرفّات أو نص مُعَمَّى مشترك بين حركة مرور بيانات الخادم المُرسلة والمُستلمة — ؛ إذا كان أي شخص يراقب ذلك، فلن يتمكّن بسهولة من تحديد من يتواصل مع من، حتى لو اختُرق TLS.", - "simplex-unique-card-1-p-1": "يحمي SimpleX خصوصية ملف التعريف الخاص بك، جهات الاتصال والبيانات الوصفية، ويخفيه عن خوادم منصة SimpleX وأي مراقبين.", + "simplex-unique-overlay-card-3-p-4": "لا توجد معرفّات أو نص مُعَمَّى مشترك بين حركة مرور بيانات الخادم المُرسلة والمُستلمة — إذا كان أي شخص يراقب ذلك، فلن يتمكّن بسهولة من تحديد من يتواصل مع من، حتى لو اختُرق TLS.", + "simplex-unique-card-1-p-1": "يحمي SimpleX خصوصية ملف تعريفك، جهات اتصالك والبيانات الوصفية، ويخفيه عن خوادم شبكة SimpleX وأي مراقبين.", "privacy-matters-overlay-card-2-p-1": "منذ وقت ليس ببعيد، لاحظنا أن الانتخابات الرئيسية يتم التلاعب بها بواسطة شركة استشارية ذات سمعة طيبة التي استخدمت الرسوم البيانية الاجتماعية لتشويه نظرتنا للعالم الحقيقي والتلاعب بأصواتنا.", - "privacy-matters-overlay-card-2-p-2": "لكي تكون موضوعيًا وتتخذ قرارات مستقلة، يجب أن تكون متحكمًا في مساحة المعلومات الخاصة بك. هذا ممكن فقط إذا كنت تستخدم منصة اتصالات خاصة لا يمكنها الوصول إلى الرسم البياني الاجتماعي الخاص بك.", - "privacy-matters-overlay-card-2-p-3": "SimpleX هو النظام الأساسي الأول الذي لا يحتوي على أي معرّفات مستخدم صمّم ليكون خاصًا، وبهذه الطريقة تحمي مخطط اتصالاتك بشكل أفضل من أي بديل معروف.", + "privacy-matters-overlay-card-2-p-2": "لكي تكون موضوعيًا وتتخذ قرارات مستقلة، عليك التحكم في مساحة معلوماتك. لا يمكن تحقيق ذلك إلا باستخدام شبكة اتصال خاصة لا يمكنها الوصول إلى حسابك على مواقع التواصل الاجتماعي.", + "privacy-matters-overlay-card-2-p-3": "SimpleX هي أول شبكة لا تحتوي على أي معرّفات مستخدم من حيث التصميم، وبهذه الطريقة تحمي مخطط اتصالاتك بشكل أفضل من أي بديل معروف.", "privacy-matters-overlay-card-3-p-2": "واحدة من أكثر القصص إثارة للصدمة هي تجربة محمدو ولد صلاحي الموصوفة في مذكراته والموضحة في فيلم موريتاني. تم وضعه في معتقل غوانتانامو بدون محاكمة، وتعرض للتعذيب هناك لمدة 15 عامًا بعد مكالمة هاتفية مع قريبه في أفغانستان، للاشتباه في تورطه في هجمات 11 سبتمبر، على الرغم من أنه عاش في ألمانيا طوال السنوات العشر الماضية.", "privacy-matters-overlay-card-3-p-3": "يتم القبض على الأشخاص العاديين بسبب ما يشاركونه عبر الإنترنت، حتى عبر حساباتهم \"المجهولة\"، وحتى في البلدان الديمقراطية.", - "simplex-unique-overlay-card-1-p-1": "على عكس أنظمة المُراسلة الأخرى، لا يحتوي SimpleX على معرّفات مخصصة للمستخدمين. لا يعتمد على أرقام الهواتف أو العناوين المستندة إلى النطاقات (مثل البريد الإلكتروني أو XMPP)، أسماء المستخدمين، المفاتيح العامة أو حتى الأرقام العشوائية لتحديد مستخدميها — لا نعرف عدد الأشخاص الذين يستخدمون خوادم SimpleX الخاصة بنا.", - "simplex-unique-overlay-card-1-p-2": "لتسليم الرسائل، يستخدم SimpleX العناوين المزدوجة المجهولة لقوائم انتظار الرسائل أحادية الاتجاه، منفصلة عن الرسائل المُستلمة والمُرسلة، عادةً عبر خوادم مختلفة. إن استخدام SimpleX يشبه امتلاك بريد إلكتروني أو هاتف “مؤقت” مختلف لكل جهة اتصال، ولا توجد متاعب في إدارتها.", + "simplex-unique-overlay-card-1-p-1": "على عكس شبكات المُراسلة الأخرى، لا يحتوي SimpleX على معرّفات مخصصة للمستخدمين. لا يعتمد على أرقام الهواتف أو العناوين المستندة إلى النطاقات (مثل البريد الإلكتروني أو XMPP)، أسماء المستخدمين، المفاتيح العامة أو حتى الأرقام العشوائية لتحديد مستخدميها — مُشغلي خادم SimpleX لا يعرفون عدد الأشخاص الذين يستخدمون خوادمهم.", + "simplex-unique-overlay-card-1-p-2": "لتسليم الرسائل، يستخدم SimpleX العناوين المزدوجة المجهولة لقوائم انتظار الرسائل أحادية الاتجاه، منفصلة عن الرسائل المُستلمة والمُرسلة، عادةً عبر خوادم مختلفة.", "simplex-unique-overlay-card-3-p-1": "يخزن SimpleX Chat جميع بيانات المستخدم على أجهزة العميل فقط باستخدام تنسيق قاعدة بيانات محمولة مُعمَّاة يمكّن تصديرها ونقلها إلى أي جهاز مدعوم.", "simplex-unique-overlay-card-3-p-3": "على عكس خوادم الشبكات الاتحادية (البريد الإلكتروني أو XMPP أو Matrix)، لا تقوم خوادم SimpleX بتخزين حسابات المستخدمين، فهي تقوم فقط بترحيل الرسائل، مما يحمي خصوصية كلا الطرفين.", - "simplex-unique-overlay-card-4-p-1": "يمكنك استخدام SimpleX مع الخوادم الخاصة بك والاستمرار في التواصل مع الأشخاص الذين يستخدمون الخوادم المهيأة مسبقًا التي نقدمها.", - "simplex-unique-overlay-card-4-p-3": "إذا كنت تفكر في التطوير لمنصة SimpleX، على سبيل المثال، بوت الدردشة لمستخدمي تطبيق SimpleX، أو دمج مكتبة SimpleX Chat في تطبيقات الأجهزة المحمولة، من فضلك تواصل معي لأي نصيحة والدعم.", + "simplex-unique-overlay-card-4-p-1": "يمكنك استخدام SimpleX مع خوادمك والاستمرار في التواصل مع الأشخاص الذين يستخدمون الخوادم المُهيأة مسبقًا في التطبيقات.", + "simplex-unique-overlay-card-4-p-3": "إذا كنت تفكر في التطوير شبكة SimpleX، على سبيل المثال، بوت الدردشة لمستخدمي تطبيق SimpleX، أو دمج مكتبة SimpleX Chat في تطبيقات الأجهزة المحمولة، من فضلك تواصل معي لأي نصيحة والدعم.", "donate-here-to-help-us": "تبرّع هنا لمساعدتنا", "sign-up-to-receive-our-updates": "اشترك للحصول على آخر مستجداتنا", "enter-your-email-address": "أدخل عنوان بريدك الإلكتروني", @@ -152,16 +152,16 @@ "no-decentralized": "لا - لامركزي", "no-federated": "لا - اتِحاديّ", "comparison-section-list-point-2": "العناوين تعتمد على الـDNS", - "comparison-section-list-point-3": "المفتاح العام أو معرف آخر فريد وعام", + "comparison-section-list-point-3": "المفتاح العام أو معرّف آخر فريد وعام", "comparison-section-list-point-7": "شبكات P2P إما لديها سلطة مركزية أو أن الشبكة كلها يمكن عرضة للخطر", "see-here": "اقرأ هنا", "no-secure": "لا - آمن", "comparison-section-list-point-5": "لا يحمي خصوصية البيانات الوصفية للمستخدمين", - "comparison-section-list-point-6": "على الرغم من أن الـP2P موزعة، إلا أنها ليست اتِحاديَّة - يعملون كشبكة واحدة", + "comparison-section-list-point-6": "على الرغم من أن الـP2P موزعة، إلا أنها ليست اتِحاديَّة — يعملون كشبكة واحدة", "comparison-section-list-point-1": "عادة ما يكون مكوناً من رقم الهاتف، أو اسم المستخدم في بعض الأحيان", "comparison-section-list-point-4": "إذا خوادم المشغّل مُخترقة. تحقق من رمز الأمان في Signal وبعض التطبيقات الأخرى للتخفيف منه", "simplex-unique-card-3-p-1": "يخزن SimpleX جميع بيانات المستخدم على الأجهزة العميلة بتنسيق قاعدة بيانات محمولة مُعمَّاة — يمكّن نقلها إلى جهاز آخر.", - "simplex-unique-card-4-p-1": "شبكة SimpleX لا مركزية بالكامل ومستقلة عن أي عملة مُعمَّاة أو أي منصة أخرى، بخلاف الإنترنت.", + "simplex-unique-card-4-p-1": "شبكة SimpleX لا مركزية بالكامل ومستقلة عن أي عملة مُعمَّاة أو أي شبكة أخرى، بخلاف الإنترنت.", "simplex-unique-card-4-p-2": "يمكنك استخدام SimpleX مع خوادمك الخاصة أو مع الخوادم التي نوفرها — ولا يزال الاتصال ممكن بأي مستخدم.", "join": "انضم إلى", "we-invite-you-to-join-the-conversation": "نحن ندعوك للانضمام إلى المُحادثة", @@ -178,7 +178,7 @@ "use-this-command": "استخدم هذا الأمر:", "see-simplex-chat": "انظر SimpleX Chat", "github-repository": "مستودع Github", - "the-instructions--source-code": "التعليمات حول كيفية تنزيله أو تجميعه من التعليمات البرمجية المصدر.", + "the-instructions--source-code": "للتعليمات حول كيفية تنزيله أو تجميعه من التعليمات البرمجية المصدر.", "if-you-already-installed-simplex-chat-for-the-terminal": "إذا قمت بالفعل بتثبيت SimpleX Chat للوحدة الطرفية", "simplex-chat-for-the-terminal": "SimpleX Chat للوحدة الطرفية", "privacy-matters-section-header": "لماذا الخصوصية مهمة", @@ -189,8 +189,8 @@ "simplex-network-3-header": "شبكة SimpleX", "protocol-1-text": "Signal، منصات كبيرة", "protocol-2-text": "XMPP ،Matrix", - "simplex-unique-card-1-p-2": "بخلاف أي نظام مُراسلة آخر، لا يحتوي SimpleX على معرّفات مخصصة للمستخدمين — ولا حتى أرقام عشوائية.", - "simplex-unique-card-2-p-1": "نظرًا لعدم وجود معرف أو عنوان ثابت على منصة SimpleX، لا يمكن لأي شخص الاتصال بك ما لم تشارك عنوان مستخدم لمرة واحدة أو مؤقتًا، كرمز QR أو رابط.", + "simplex-unique-card-1-p-2": "على عكس أي شبكة مُراسلة آخر، لا يحتوي SimpleX على معرّفات مخصّصة للمستخدمين — ولا حتى أرقام عشوائية.", + "simplex-unique-card-2-p-1": "نظرًا لعدم وجود معرّف أو عنوان ثابت على شبكة SimpleX، لا يمكن لأي شخص الاتصال بك ما لم تشارك عنوان مستخدم لمرة واحدة أو مؤقتًا، كرمز QR أو رابط.", "simplex-unique-card-3-p-2": "يتم الاحتفاظ بالرسائل المُعمَّاة بين الطرفين مؤقتًا على خوادم ترحيل SimpleX حتى يتم استلامها، ثُمَّ تُحذف نهائيًا.", "tap-the-connect-button-in-the-app": "اضغط على زر \"اتصال\" في التطبيق", "scan-the-qr-code-with-the-simplex-chat-app": "امسح رمز QR باستخدام تطبيق SimpleX Chat", @@ -208,7 +208,7 @@ "simplex-network-2-desc": "لا تقوم خوادم الترحيل SimpleX بتخزين ملفات تعريف المستخدمين وجهات الاتصال والرسائل التي تم تسليمها، ولا تتصل ببعضها البعض، ولا يوجد دليل خوادم.", "comparison-point-1-text": "يتطلب هوية عالمية", "protocol-3-text": "بروتوكولات P2P", - "simplex-unique-overlay-card-4-p-2": "تستخدم منصة SimpleX بروتوكول مفتوح وتوفر SDK لإنشاء روبوتات دردشة، مما يسمح بتنفيذ الخدمات التي يمكن للمستخدمين التفاعل معها عبر تطبيقات SimpleX Chat — التي تتطلع حقًا لمعرفة خدمات SimpleX التي يمكنك إنشاؤها.", + "simplex-unique-overlay-card-4-p-2": "تستخدم شبكة SimpleX بروتوكول مفتوح وتوفر SDK لإنشاء روبوتات دردشة، مما يسمح بتنفيذ الخدمات التي يمكن للمستخدمين التفاعل معها عبر تطبيقات SimpleX Chat — التي تتطلع حقًا لمعرفة خدمات SimpleX التي ستبنيها.", "guide-dropdown-1": "بداية سريعة", "guide-dropdown-2": "إرسال الرسائل", "guide-dropdown-3": "مجموعات سرية", @@ -227,7 +227,7 @@ "menu": "قائمة", "on-this-page": "على هذه الصفحة", "back-to-top": "عد إلى الأعلى", - "docs-dropdown-1": "منصة SimpleX", + "docs-dropdown-1": "شبكة SimpleX", "guide": "الدليل", "docs-dropdown-2": "الوصول إلى ملفات اندرويد", "docs-dropdown-3": "الوصول إلى قاعدة بيانات الدردشة", @@ -244,7 +244,7 @@ "f-droid-page-f-droid-org-repo-section-text": "مستودعات SimpleX Chat و F-Droid.org مبنية على مفاتيح مختلفة. للتبديل، يُرجى تصدير قاعدة بيانات الدردشة وإعادة تثبيت التطبيق.", "comparison-section-list-point-4a": "مُرحلات SimpleX لا يمكنها أن تتنازل عن تعمية بين الطرفين. تحقق من رمز الأمان للتخفيف من الهجوم على القناة خارج النطاق", "hero-overlay-3-title": "التقييمات الأمنية", - "hero-overlay-card-3-p-2": "قامت Trail of Bits بمراجعة مكونات التشفير والشبكات الخاصة بمنصة SimpleX في نوفمبر 2022. اقرأ المزيد في الإعلان.", + "hero-overlay-card-3-p-2": "قامت Trail of Bits بمراجعة مكونات التشفير والشبكات الخاصة بشبكة SimpleX في نوفمبر 2022. اقرأ المزيد في الإعلان.", "jobs": "انضم للفريق", "hero-overlay-3-textlink": "التقييمات الأمنية", "hero-overlay-card-3-p-1": "Trail of Bits هي شركة رائدة في مجال الاستشارات الأمنية والتكنولوجية، ومن بين عملائها شركات التكنولوجيا الكبرى والوكالات الحكومية ومشاريع blockchain الكبرى.", diff --git a/website/langs/de.json b/website/langs/de.json index f2ec261ab7..3e5ef9fbc4 100644 --- a/website/langs/de.json +++ b/website/langs/de.json @@ -15,7 +15,7 @@ "simplex-explained-tab-1-p-1": "Sie können Kontakte und Gruppen erstellen und haben Zwei-Wege-Kommunikation wie in jedem anderen Messenger.", "simplex-explained-tab-1-p-2": "Wie funktioniert das mit den unidirektionalen Warteschlangen und ohne Profilkennungen?", "simplex-explained-tab-2-p-1": "Für jede Verbindung nutzen Sie zwei separate Nachrichten-Warteschlangen, um die Nachrichten über verschiedene Server zu senden und zu empfangen.", - "simplex-explained-tab-2-p-2": "Die Server leiten Nachrichten immer nur in eine Richtung weiter, ohne den vollständigen Verlauf der Nutzer-Unterhaltung oder seiner Verbindungen zu kennen.", + "simplex-explained-tab-2-p-2": "Die Server leiten Nachrichten immer nur in eine Richtung weiter, ohne den vollständigen Verlauf der Nutzer-Unterhaltungen oder seiner Verbindungen zu kennen.", "simplex-explained-tab-3-p-1": "Die Server nutzen für jede Warteschlange separate, anonyme Anmeldeinformationen und wissen nicht welchem Nutzer diese gehören.", "simplex-explained-tab-3-p-2": "Durch die Verwendung von Tor-Zugangsservern können Nutzer ihre Metadaten-Privatsphäre weiter verbessern und Korellationen von IP-Adressen verhindern.", "smp-protocol": "SMP-Protokoll", @@ -64,7 +64,7 @@ "simplex-private-card-4-point-2": "Um SimpleX per Tor zu nutzen, installieren Sie unter Android bitte die Orbot-App und aktivieren Sie den SOCKS5-Proxy oder unter iOS per VPN.", "simplex-private-card-5-point-1": "SimpleX nutzt Inhalte-Auffüllung für jede Verschlüsselungs-Schicht, um Angriffe auf die Nachrichtengröße zu vereiteln.", "simplex-private-card-5-point-2": "Erzeugt Nachrichten mit unterschiedlichen Größen, die für Server und Netzwerk-Beobachter identisch aussehen.", - "simplex-private-card-6-point-1": "Viele Kommunikations-Plattformen sind für MITM-Angriffe durch Server oder Netzwerk-Anbieter anfällig.", + "simplex-private-card-6-point-1": "Viele Kommunikations-Netzwerke sind für MITM-Angriffe durch Server oder Netzwerk-Anbieter anfällig.", "simplex-private-card-9-point-1": "Jede Nachrichten-Warteschlange leitet Nachrichten mit unterschiedlichen Sende- und Empfängeradressen jeweils nur in einer Richtung weiter.", "simplex-private-card-9-point-2": "Verglichen mit traditionellen Nachrichten-Brokern, werden mögliche Angriffs-Vektoren und vorhandene Metadaten reduziert.", "simplex-private-card-10-point-1": "SimpleX nutzt für jeden Nutzer-Kontakt oder jedes Gruppenmitglied eigene temporäre, anonyme und paarweise Adressen und Berechtigungsnachweise.", @@ -95,29 +95,29 @@ "hero-overlay-card-1-p-6": "Lesen Sie mehr darüber im SimpleX-Whitepaper.", "hero-overlay-card-2-p-2": "Sie können diese Informationen mit bestehenden öffentlichen sozialen Netzwerken korrelieren und damit wahre Identitäten herausfinden.", "hero-overlay-card-2-p-3": "Wenn Sie sich mit zwei unterschiedlichen Kontakten über dasselbe Profil unterhalten, können sie, selbst bei sehr auf Privatsphäre bedachten Apps, die Tor-v3-Dienste nutzen, feststellen, dass diese Kontakte mit derselben Person verbunden sind.", - "hero-overlay-card-1-p-2": "Um Nachrichten auszuliefern, nutzt SimpleX statt Benutzerkennungen wie auf allen anderen Plattformen temporäre, anonyme und paarweise Kennungen für Nachrichten-Warteschlangen, die für jede Ihrer Verbindungen unterschiedlich sind — Es gibt keinerlei Langzeit-Kennungen.", + "hero-overlay-card-1-p-2": "Um Nachrichten auszuliefern, nutzt SimpleX statt Benutzerkennungen wie alle anderen Netzwerke nur temporäre, anonyme und paarweise Kennungen für Nachrichten-Warteschlangen, die für jede Ihrer Verbindungen unterschiedlich sind — es gibt keinerlei Langzeit-Kennungen.", "hero-overlay-card-1-p-4": "Dieses Design verhindert schon auf der Applikations-Ebene Datenlecks für jegliche Benutzer'Metadaten. Sie können sich über Tor mit Nachrichten-Servern verbinden, um Ihre Privatsphäre weiter zu verbessern und die von Ihnen genutzte IP-Adresse zu schützen.", "hero-overlay-card-2-p-1": "Wenn Nutzer dauerhafte Identitäten besitzen, selbst wenn diese eine Zufallsnummer, wie eine Sitzungs-ID, ist, besteht ein Risiko, das Provider oder Angreifer feststellen können, wie Nutzer miteinander verbunden sind und wie viele Nachrichten sie versenden.", "hero-overlay-card-2-p-4": "SimpleX schützt gegen solche Angriffe, weil es vom Design her keinerlei Benutzerkennungen besitzt. Und Sie haben sogar unterschiedliche Anzeigenamen für jeden Kontakt und vermeiden jegliche geteilte Daten zwischen diesen, wenn Sie den Inkognito-Modus nutzen.", "simplex-network-overlay-card-1-p-1": "Peer-to-Peer-Nachrichten-Protokolle und -Applikationen haben verschiedene Probleme, die diese weniger vertrauenswürdig, die Analyse wesentlich komplexer und anfälliger gegen verschiedene Arten von Angriffen, als bei SimpleX machen.", "simplex-network-overlay-card-1-li-2": "Das SimpleX Design hat, im Gegensatz zu den meisten P2P-Netzwerken, keinerlei globalen Benutzerkennungen, auch keine temporären. Es nutzt ausschließlich temporäre paarweise Kennungen, die bessere Anonymität und Metadaten-Schutz bieten.", - "simplex-network-overlay-card-1-li-4": "P2P-Implementierungen können durch Internetanbieter blockiert werden, wie beispielweise BitTorrent). SimpleX ist transportunabhängig – es kann über Standard-Web-Protokolle, wie beispielsweise WebSockets, arbeiten.", + "simplex-network-overlay-card-1-li-4": "——P2P-Implementierungen können durch Internetanbieter blockiert werden, wie beispielweise BitTorrent). SimpleX ist transportunabhängig — es kann über Standard-Web-Protokolle, wie beispielsweise WebSockets, arbeiten.", "simplex-network-overlay-card-1-li-6": "P2P-Netzwerke können anfällig für DRDoS-Angriffe sein, wenn die Clients den Datenverkehr erneut senden und verstärken können, was zu einem netzwerkweiten Denial-of-Service führt. SimpleX-Clients leiten nur Datenverkehr von bekannten Verbindungen weiter und können von einem Angreifer nicht dazu verwendet werden, den Datenverkehr im gesamten Netzwerk zu verstärken.", "privacy-matters-overlay-card-1-p-1": "Viele große Unternehmen nutzen Informationen, mit wem Sie in Verbindung stehen, um Ihr Einkommen zu schätzen, Ihnen Produkte zu verkaufen, die Sie nicht wirklich benötigen und um die Preise zu bestimmen.", "privacy-matters-overlay-card-1-p-2": "Online-Händler wissen, dass Menschen mit geringerem Einkommen eher dringende Einkäufe tätigen, sodass sie möglicherweise höhere Preise verlangen können oder Rabatte streichen.", "privacy-matters-overlay-card-1-p-3": "Einige Finanz- und Versicherungsunternehmen verwenden soziale Graphen, um Zinssätze und Prämien zu ermitteln. Menschen mit niedrigerem Einkommen zahlen so häufig mehr — dies ist als \"Armutsprämie\" bekannt.", - "privacy-matters-overlay-card-2-p-2": "Um objektiv zu sein und unabhängige Entscheidungen treffen zu können, müssen Sie die Kontrolle über Ihren Informationsraum haben. Dies ist nur möglich, wenn Sie eine private Kommunikationsplattform verwenden, die keinen Zugriff auf Ihren sozialen Graphen hat.", - "privacy-matters-overlay-card-2-p-3": "SimpleX ist die erste Plattform, die per Design keinerlei Benutzerkennungen hat und auf diese Weise Ihren Verbindungsgraphen besser schützt als jede andere bekannte Alternative.", + "privacy-matters-overlay-card-2-p-2": "Um objektiv zu sein und unabhängige Entscheidungen treffen zu können, müssen Sie die Kontrolle über Ihren Informationsraum haben. Dies ist nur möglich, wenn Sie ein privates Kommunikations-Netzwerk verwenden, welches keinen Zugriff auf Ihren sozialen Graphen hat.", + "privacy-matters-overlay-card-2-p-3": "SimpleX ist das erste Netzwerk, welches per Design keinerlei Benutzerkennungen hat und auf diese Weise Ihren Verbindungsgraphen besser schützt als jede andere bekannte Alternative.", "privacy-matters-overlay-card-3-p-1": "Jede Person sollte sich um ihre Privatsphäre und die Sicherheit ihrer Kommunikation kümmern — Harmlose Gespräche könnten Sie in Gefahr bringen, selbst wenn Sie nichts zu verbergen haben.", "privacy-matters-overlay-card-3-p-4": "Es reicht nicht aus, einfach einen Ende-zu-Ende-verschlüsselten Messenger zu verwenden. Wir alle sollten den Messenger verwenden, der die Privatsphäre unserer persönlichen Netzwerke schützt, mit welchen wir verbunden sind.", - "simplex-unique-overlay-card-1-p-3": "Dieses Design schützt die Privatsphäre von Ihnen und der Personen, mit denen Sie kommunizieren, und verbirgt die Verbindungen vor den SimpleX-Plattform-Servern und möglichen Beobachtern. Um Ihre IP-Adresse vor den Servern zu verbergen, können Sie sich per Tor mit den SimpleX-Servern verbinden.", - "simplex-unique-overlay-card-2-p-1": "Da Sie auf der SimpleX-Plattform keine Kennungen haben, kann Sie niemand kontaktieren, es sei denn, Sie geben eine einmalige oder vorübergehende Benutzeradresse in Form eines QR-Codes oder eines Links weiter.", + "simplex-unique-overlay-card-1-p-3": "Dieses Design schützt die Privatsphäre von Ihnen und der Nutzer, mit denen Sie kommunizieren, und verbirgt die Verbindungen vor den SimpleX-Netzwerk-Servern und möglichen Beobachtern. Um Ihre IP-Adresse vor den Servern zu verbergen, können Sie sich per Tor mit den SimpleX-Servern verbinden.", + "simplex-unique-overlay-card-2-p-1": "Da Sie im SimpleX-Netzwerk keine Kennungen haben, kann Sie niemand kontaktieren, es sei denn, Sie geben eine einmalige oder vorübergehende Benutzeradresse in Form eines QR-Codes oder eines Links weiter.", "simplex-unique-overlay-card-3-p-2": "Die Ende-zu-Ende-verschlüsselten Nachrichten werden vorübergehend auf SimpleX-Relay-Servern gespeichert, bis sie vom Endgerät empfangen und danach endgültig gelöscht werden.", "simplex-unique-overlay-card-3-p-3": "Im Gegensatz zu föderierten Netzwerkservern (wie z.B. Mail, XMPP oder Matrix) speichern die SimpleX-Server keine Benutzerkonten, sondern leiten Nachrichten nur weiter, so dass die Privatsphäre der beteiligten Parteien geschützt ist.", - "simplex-unique-overlay-card-4-p-1": "Sie können SimpleX mit Ihren eigenen Servern verwenden und trotzdem mit Personen kommunizieren, welche die von uns bereitgestellten und vorkonfigurierten Server verwenden.", - "simplex-unique-card-1-p-1": "SimpleX schützt die Privatsphäre Ihres Profils, Ihrer Kontakte und Metadaten und verbirgt sie vor den SimpleX-Plattform-Servern und allen Beobachtern.", - "simplex-unique-card-1-p-2": "Im Gegensatz zu allen anderen bestehenden Messaging-Plattformen werden den Nutzern von SimpleX keine Kennungen zugewiesen — nicht einmal Zufallszahlen.", - "simplex-unique-card-2-p-1": "Da Sie keine Kennung oder feste Adresse auf der SimpleX-Plattform haben, kann Sie niemand kontaktieren, es sei denn, Sie geben eine einmalige oder vorübergehende Benutzeradresse in Form eines QR-Codes oder Links weiter.", + "simplex-unique-overlay-card-4-p-1": "Sie können SimpleX mit Ihren eigenen Servern verwenden und trotzdem mit Nutzern kommunizieren, welche die vorkonfigurierten Server der App verwenden.", + "simplex-unique-card-1-p-1": "SimpleX schützt die Privatsphäre Ihres Profils, Ihrer Kontakte und Metadaten und verbirgt sie vor den SimpleX-Netzwerk-Servern und weiteren möglichen Beobachtern.", + "simplex-unique-card-1-p-2": "Im Gegensatz zu allen anderen bestehenden Messaging-Netzwerken werden den Nutzern von SimpleX keine Kennungen zugewiesen — nicht einmal Zufallszahlen.", + "simplex-unique-card-2-p-1": "Da Sie keine Kennung oder feste Adresse im SimpleX-Netzwerk haben, kann Sie niemand kontaktieren, es sei denn, Sie geben eine einmalige oder vorübergehende Benutzeradresse in Form eines QR-Codes oder Links weiter.", "simplex-unique-card-3-p-1": "SimpleX speichert Benutzerdaten nur auf den Endgeräten und das in einem portablen, verschlüsselten Datenbankformat — welches auf ein anderes Gerät übertragen werden kann.", "simplex-unique-card-3-p-2": "Die Ende-zu-Ende-verschlüsselten Nachrichten werden vorübergehend auf SimpleX-Relay-Servern gespeichert, bis sie vom Endgerät empfangen und danach endgültig gelöscht werden.", "join": "Nutzen Sie", @@ -142,7 +142,7 @@ "use-this-command": "Benutzen Sie dieses Kommando:", "see-simplex-chat": "Siehe SimpleX-Chat", "github-repository": "GitHub-Repository", - "the-instructions--source-code": "Die Anleitung, wie Sie es herunterladen und aus dem Quellcode kompilieren.", + "the-instructions--source-code": "Für die Anleitungen, wie Sie es herunterladen oder aus dem Quellcode kompilieren.", "if-you-already-installed": "Wenn Sie es schon installiert haben", "simplex-chat-for-the-terminal": "SimpleX Chat für das Terminal", "privacy-matters-section-header": "Warum es auf Privatsphäre ankommt", @@ -171,13 +171,13 @@ "comparison-section-list-point-2": "DNS-basierte Adressen", "comparison-section-list-point-3": "Öffentlicher Schlüssel oder eine andere weltweit eindeutige ID", "comparison-section-list-point-4": "Wenn die Server des Betreibers kompromittiert werden. In Signal und weiteren Apps kann der Sicherheitscode überprüft werden, um dies zu entschärfen", - "comparison-section-list-point-6": "P2P sind zwar verteilt, aber nicht föderiert - sie arbeiten als ein einziges Netzwerk", + "comparison-section-list-point-6": "P2P sind zwar verteilt, aber nicht föderiert — sie arbeiten als ein einziges Netzwerk", "comparison-section-list-point-7": "P2P-Netzwerke haben entweder eine zentrale Verwaltung oder das gesamte Netzwerk kann kompromittiert werden", "see-here": "Siehe hier", - "simplex-unique-card-4-p-1": "Das SimpleX-Netzwerk ist vollständig dezentralisiert und unabhängig von Kryptowährungen oder anderen Plattformen außer dem Internet.", + "simplex-unique-card-4-p-1": "Das SimpleX-Netzwerk ist vollständig dezentralisiert und unabhängig von Kryptowährungen oder anderen Netzwerken außer dem Internet.", "privacy-matters-overlay-card-2-p-1": "Vor nicht allzu langer Zeit beobachteten wir, wie große Wahlen von einem angesehenen Beratungsunternehmen manipuliert wurden, welches unsere sozialen Graphen nutzte, um unsere Sicht auf die reale Welt zu verzerren und unsere Stimmen zu manipulieren.", "privacy-matters-overlay-card-3-p-2": "Eine der schockierendsten Geschichten ist die Erfahrung von Mohamedou Ould Salahi, welche in seinen Memoiren beschrieben und im Film „The Mauritanian“ gezeigt wird. Er kam nach einem Anruf bei seinen Verwandten in Afghanistan und ohne Gerichtsverfahren in das Guantanamo-Lager und wurde dort einige Jahre lang gefoltert, weil er verdächtigt wurde, an den 9/11-Angriffen beteiligt gewesen zu sein, obwohl er die vorhergehenden 10 Jahre in Deutschland gelebt hatte.", - "simplex-unique-overlay-card-1-p-1": "Im Gegensatz zu anderen Nachrichten-Plattformen weist SimpleX den Benutzern keine Kennungen zu. Es verlässt sich nicht auf Telefonnummern, domänenbasierte Adressen (wie E-Mail oder XMPP), Benutzernamen, öffentliche Schlüssel oder sogar Zufallszahlen, um seine Benutzer zu identifizieren — Wir wissen nicht, wie viele Personen unsere SimpleX-Server verwenden.", + "simplex-unique-overlay-card-1-p-1": "Im Gegensatz zu anderen Nachrichten-Netzwerken weist SimpleX den Benutzern keine Kennungen zu. Es verlässt sich nicht auf Telefonnummern, domänenbasierte Adressen (wie E-Mail oder XMPP), Benutzernamen, öffentliche Schlüssel oder Zufallszahlen, um seine Benutzer zu identifizieren — selbst SimpleX-Server-Betreiber wissen nicht, wie viele Personen deren Server verwenden.", "simplex-unique-overlay-card-1-p-2": "Um Nachrichten auszuliefern nutzt SimpleX paarweise anonyme Adressen aus unidirektionalen Nachrichten-Warteschlangen, die für empfangene und gesendete Nachrichten separiert sind und gewöhnlich über verschiedene Server gesendet werden.", "simplex-network-overlay-card-1-li-5": "Alle bekannten P2P-Netzwerke können anfällig für Sybil-Angriffe sein, da jeder Knoten ermittelbar ist und das Netzwerk als Ganzes funktioniert. Bekannte Maßnahmen zur Verhinderung erfordern entweder eine zentralisierte Komponente oder einen teuren Ausführungsnachweis. Das SimpleX-Netzwerk bietet keine Ermittlung der Server, ist fragmentiert und arbeitet mit mehreren isolierten Subnetzwerken, wodurch netzwerkweite Angriffe unmöglich werden.", "simplex-network-overlay-card-1-li-3": "P2P löst nicht das Problem des MITM-Angriffs und die meisten bestehenden Implementierungen nutzen für den initialen Schlüsselaustausch keine Out-of-Band-Nachrichten. Im Gegensatz hierzu nutzt SimpleX für den initialen Schlüsselaustausch Out-of-Band-Nachrichten oder zum Teil schon bestehende sichere und vertrauenswürdige Verbindungen.", @@ -196,14 +196,14 @@ "comparison-section-list-point-5": "Die Privatsphäre-Metadaten des Nutzers werden nicht geschützt", "simplex-network-overlay-card-1-li-1": "P2P-Netzwerke vertrauen auf Varianten von DHT, um Nachrichten zu routen. DHT-Designs müssen zwischen Zustellungsgarantie und Latenz ausgleichen. Verglichen mit P2P bietet SimpleX sowohl eine bessere Zustellungsgarantie als auch eine niedrigere Latenz, weil eine Nachricht redundant und parallel über mehrere Server gesendet werden kann, wobei die durch den Empfänger ausgewählten Server genutzt werden. In P2P-Netzwerken werden Nachrichten sequentiell über O(log N)-Knoten gesendet, wobei die Knoten durch einen Algorithmus ausgewählt werden.", "simplex-unique-overlay-card-3-p-4": "Zwischen dem gesendeten und empfangenen Serververkehr gibt es keine gemeinsamen Kennungen oder Chiffriertexte — sodass ein Beobachter nicht ohne weiteres feststellen kann, wer mit wem kommuniziert, selbst wenn TLS kompromittiert wurde.", - "simplex-unique-overlay-card-4-p-3": "Wenn Sie darüber nachdenken, für die SimpleX-Plattform entwickeln zu wollen, z.B. einen Chatbot für SimpleX-App-Nutzer oder die Integration der SimpleX-Chat-Bibliothek in Ihre mobilen Apps, kontaktieren Sie uns bitte für eine weitere Beratung und Unterstützung.", - "privacy-matters-overlay-card-1-p-4": "Die SimpleX-Plattform schützt die Privatsphäre Ihrer Verbindungen besser als jede Alternative und verhindert vollständig, dass Ihr sozialer Graph für Unternehmen oder Organisationen verfügbar wird. Selbst wenn die Anwender SimpleX-Chat-Server verwenden, kennen wir die Anzahl der Benutzer oder ihre Verbindungen nicht.", + "simplex-unique-overlay-card-4-p-3": "Falls Sie Interesse daran haben, aktiv bei der Entwicklung des SimpleX-Netzwerks mitzuhelfen, z.B. einen Chatbot für SimpleX-App-Nutzer zu entwickeln oder die Integration der SimpleX-Chat-Bibliothek in mobile Apps voranzutreiben, kontaktieren Sie uns bitte für eine weitere Beratung und Unterstützung.", + "privacy-matters-overlay-card-1-p-4": "Das SimpleX-Netzwerk schützt die Privatsphäre Ihrer Verbindungen besser als jede Alternative und verhindert vollständig, dass Ihr sozialer Graph für Unternehmen oder Organisationen verfügbar wird. Selbst wenn Anwender die in der SimpleX-Chat-App vorkonfigurierten Server verwenden, kennen die Server-Betreiber die Anzahl der Benutzer oder deren Verbindungen nicht.", "contact-hero-header": "Sie haben eine Adresse zur Verbindung mit SimpleX Chat erhalten", "invitation-hero-header": "Sie haben einen Einmal-Link zur Verbindung mit SimpleX Chat erhalten", "privacy-matters-overlay-card-3-p-3": "Normale Menschen werden für das, was sie online teilen, sogar unter Nutzung ihrer „anonymen“ Konten, selbst in demokratischen Ländern verhaftet.", "simplex-unique-overlay-card-3-p-1": "SimpleX Chat speichert alle Benutzerdaten ausschließlich auf den Endgeräten in einem portablen und verschlüsselten Datenbankformat, welches exportiert und auf jedes unterstützte Gerät übertragen werden kann.", "simplex-unique-overlay-card-2-p-2": "Auch wenn die optionale Benutzeradresse zum Versenden von Spam-Kontaktanfragen verwendet werden kann, können Sie sie ändern oder ganz löschen, ohne dass Ihre Verbindungen verloren gehen.", - "simplex-unique-overlay-card-4-p-2": "Die SimpleX-Plattform verwendet ein offenes Protokoll und bietet ein SDK zur Erstellung von Chatbots an, welches die Erstellung von Diensten ermöglicht, mit denen Nutzer über SimpleX-Chat interagieren können — Wir sind gespannt, welche SimpleX-Dienste Sie erstellen können.", + "simplex-unique-overlay-card-4-p-2": "Das SimpleX-Netzwerk verwendet ein offenes Protokoll und bietet ein SDK zur Erstellung von Chatbots an. Dies ermöglicht die Erstellung von Diensten, mit denen Nutzer über SimpleX-Chat-Apps interagieren können — wir sind gespannt, welche SimpleX-Dienste Sie entwickeln werden.", "simplex-unique-card-4-p-2": "Sie können SimpleX mit Ihren eigenen Servern oder mit den von uns zur Verfügung gestellten Servern verwenden — und sich trotzdem mit jedem Benutzer verbinden.", "why-simplex-is-unique": "Warum ist SimpleX einmalig", "contact-hero-p-1": "Die öffentlichen Schlüssel und die Adresse der Nachrichtenwarteschlange in diesem Link werden NICHT über das Netzwerk gesendet, wenn Sie diese Seite aufrufen — sie sind in dem Hash-Fragment der Link-URL enthalten.", @@ -218,7 +218,7 @@ "guide-dropdown-8": "App-Einstellungen", "guide-dropdown-9": "Verbindungen herstellen", "guide": "Leitfaden", - "docs-dropdown-1": "SimpleX-Plattform", + "docs-dropdown-1": "SimpleX-Netzwerk", "docs-dropdown-2": "Zugriff auf Android-Dateien", "docs-dropdown-3": "Zugriff auf die Chat-Datenbank", "docs-dropdown-4": "Den SMP-Server hosten", diff --git a/website/langs/hu.json b/website/langs/hu.json index f502291e52..f062899224 100644 --- a/website/langs/hu.json +++ b/website/langs/hu.json @@ -14,7 +14,7 @@ "simplex-explained-tab-1-p-1": "Létrehozhat kapcsolatokat és csoportokat, valamint kétirányú beszélgetéseket folytathat, ugyanúgy mint bármely más üzenetváltó-alkalmazásban.", "simplex-explained-tab-1-p-2": "Hogyan működhet egyirányú üzenet-sorballítással és felhasználói profilazonosítók nélkül?", "simplex-explained-tab-2-p-1": "Minden kapcsolathoz két különböző üzenetküldési sorbaállítást használ a különböző kiszolgálókon keresztül történő üzenetküldéshez és -fogadáshoz.", - "simplex-explained-tab-2-p-2": "A kiszolgálók csak egyirányú üzeneteket továbbítanak, anélkül, hogy teljes képet kapnának a felhasználók beszélgetéseiről vagy kapcsolatairól.", + "simplex-explained-tab-2-p-2": "A kiszolgálók csak egyetlen irányba továbbítják az üzeneteket, anélkül, hogy teljes képet kapnának a felhasználók beszélgetéseiről vagy kapcsolatairól.", "simplex-explained-tab-3-p-1": "A kiszolgálók minden egyes üzenetsorbaállításhoz külön névtelen hitelesítő-adatokkal rendelkeznek, és nem tudják, hogy melyik felhasználóhoz tartoznak.", "simplex-explained-tab-3-p-2": "A felhasználók tovább fokozhatják a metaadatok adatvédelmét, ha a Tor segítségével férnek hozzá a kiszolgálókhoz, így megakadályozva az IP-cím szerinti korrelációt.", "smp-protocol": "SMP-protokoll", @@ -60,7 +60,7 @@ "simplex-private-card-3-point-2": "A kiszolgáló ujjlenyomata és a csatornakötés megakadályozza a MITM- és a visszajátszási támadásokat.", "simplex-private-card-3-point-3": "Az újrakapcsolódás le van tiltva a munkamenet elleni támadások megelőzése érdekében.", "simplex-private-card-4-point-1": "Az IP-címe védelme érdekében a kiszolgálókat a Tor-on vagy más átvitel-átfedő-hálózaton keresztül is elérheti.", - "simplex-private-card-6-point-1": "Számos kommunikációs platform sebezhető a kiszolgálók vagy a hálózat-szolgáltatók MITM-támadásaival szemben.", + "simplex-private-card-6-point-1": "Számos kommunikációs hálózat sebezhető a kiszolgálók vagy a hálózat-szolgáltatók MITM-támadásaival szemben.", "simplex-private-card-6-point-2": "Ennek megakadályozása érdekében a SimpleX-alkalmazások egyszeri kulcsokat adnak át sávon kívül, amikor egy címet hivatkozásként vagy QR-kódként oszt meg.", "simplex-private-card-7-point-1": "Az integritás garantálása érdekében az üzenetek sorszámozással vannak ellátva, és tartalmazzák az előző üzenet hasítóértékét.", "simplex-private-card-7-point-2": "Ha bármilyen üzenetet hozzáadnak, eltávolítanak vagy módosítanak, a címzett értesítést kap róla.", @@ -86,7 +86,7 @@ "simplex-unique-4-title": "Öné a SimpleX-hálózat", "simplex-unique-4-overlay-1-title": "Teljesen decentralizált — a SimpleX-hálózat a felhasználóké", "hero-overlay-card-1-p-1": "Sok felhasználó kérdezte: ha a SimpleXnek nincsenek felhasználói azonosítói, honnan tudja, hogy hová kell eljuttatni az üzeneteket?", - "hero-overlay-card-1-p-2": "Az üzenetek kézbesítéséhez az összes többi platform által használt felhasználói azonosítók helyett a SimpleX az üzenetek sorbaállításához ideiglenes, névtelen, páros azonosítókat használ, külön-külön minden egyes kapcsolathoz — nincsenek hosszú távú azonosítók.", + "hero-overlay-card-1-p-2": "Az üzenetek kézbesítéséhez az összes többi hálózat által használt felhasználói azonosítók helyett a SimpleX az üzenetek sorbaállításához ideiglenes, névtelen, páros azonosítókat használ, külön-külön minden egyes kapcsolathoz — nincsenek hosszú távú azonosítók.", "hero-overlay-card-1-p-4": "Ez a kialakítás megakadályozza a felhasználók metaadatainak kiszivárgását az alkalmazás szintjén. Az adatvédelem további javítása és az IP-cím védelme érdekében az üzenetküldő kiszolgálókhoz Tor hálózaton keresztül is kapcsolódhat.", "hero-overlay-card-1-p-5": "Csak a kliensek tárolják a felhasználói profilokat, kapcsolatokat és csoportokat; az üzenetek küldése 2 rétegű végpontok közötti titkosítással történik.", "hero-overlay-card-1-p-6": "További leírást a SimpleX ismertetőben olvashat.", @@ -104,32 +104,32 @@ "privacy-matters-overlay-card-1-p-1": "Sok nagyvállalat arra használja fel az önnel kapcsolatban álló személyek adatait, hogy megbecsülje az ön jövedelmét, hogy olyan termékeket adjon el önnek, amelyekre valójában nincs is szüksége, és hogy meghatározza az árakat.", "privacy-matters-overlay-card-1-p-2": "Az online kiskereskedők tudják, hogy az alacsonyabb jövedelműek nagyobb valószínűséggel vásárolnak azonnal, ezért magasabb árakat számíthatnak fel, vagy eltörölhetik a kedvezményeket.", "privacy-matters-overlay-card-1-p-3": "Egyes pénzügyi és biztosítótársaságok szociális grafikonokat használnak a kamatlábak és a díjak meghatározásához. Ez gyakran arra készteti az alacsonyabb jövedelmű embereket, hogy többet fizessenek — ez az úgynevezett „szegénységi prémium”.", - "privacy-matters-overlay-card-1-p-4": "A SimpleX-platform minden alternatívánál jobban védi a kapcsolatainak adatait, teljes mértékben megakadályozva, hogy a ismeretségi-hálója bármilyen vállalat vagy szervezet számára elérhetővé váljon. Még ha az emberek a SimpleX Chat által biztosított kiszolgálókat is használják, sem a felhasználók számát, sem a kapcsolataikat nem ismerjük.", + "privacy-matters-overlay-card-1-p-4": "A SimpleX-hálózat minden alternatívánál jobban védi a kapcsolatainak adatait, teljes mértékben megakadályozva, hogy az ismeretségi-hálója bármilyen vállalat vagy szervezet számára elérhetővé váljon. Még ha az emberek a SimpleX Chat által előre beállított kiszolgálókat is használják, sem az alkalmazások, sem a kiszolgálók üzemeltetői nem ismerik, sem felhasználók számát, sem a kapcsolataikat.", "privacy-matters-overlay-card-2-p-1": "Nem is olyan régen megfigyelhettük, hogy a nagy választásokat manipulálta egy neves tanácsadó cég, amely az ismeretségi-háló segítségével eltorzította a valós világról alkotott képünket, és manipulálta a szavazatainkat.", - "privacy-matters-overlay-card-2-p-2": "Ahhoz, hogy objektív legyen és független döntéseket tudjon hozni, az információs terét is kézben kell tartania. Ez csak akkor lehetséges, ha privát kommunikációs platformot használ, amely nem fér hozzá az ismeretségi-hálójához.", - "privacy-matters-overlay-card-2-p-3": "A SimpleX az első olyan platform, amely eleve nem rendelkezik felhasználói azonosítókkal, így jobban védi az ismeretségi-hálóját, mint bármely ismert alternatíva.", + "privacy-matters-overlay-card-2-p-2": "Ahhoz, hogy objektív legyen és független döntéseket tudjon hozni, az információs terét is kézben kell tartania. Ez csak akkor lehetséges, ha privát kommunikációs hálózatot használ, amely nem fér hozzá az ismeretségi-hálójához.", + "privacy-matters-overlay-card-2-p-3": "A SimpleX az első olyan hálózat, amely eleve nem rendelkezik felhasználói azonosítókkal, így jobban védi az ismeretségi-hálóját, mint bármely ismert alternatíva.", "privacy-matters-overlay-card-3-p-1": "Mindenkinek törődnie kell a magánélet és a kommunikáció biztonságával — az ártalmatlan beszélgetések veszélybe sodorhatják, még akkor is, ha nincs semmi rejtegetnivalója.", "privacy-matters-overlay-card-3-p-2": "Az egyik legmegdöbbentőbb a Mohamedou Ould Salahi memoárjában leírt és az „A mauritániai” c. filmben bemutatott történet. Őt bírósági tárgyalás nélkül a guantánamói táborba zárták, és ott kínozták 15 éven át, miután egy afganisztáni rokonát telefonon felhívta, akit azzal gyanúsítottak a hatóságok, hogy köze van a 9/11-es merényletekhez, holott Salahi az előző 10 évben Németországban élt.", "privacy-matters-overlay-card-3-p-3": "Átlagos embereket letartóztatnak azért, amit online megosztanak, még „névtelen” fiókjaikon keresztül is, még demokratikus országokban is.", "privacy-matters-overlay-card-3-p-4": "Nem elég csak egy végpontok között titkosított üzenetváltó-alkalmazást használnunk, mindannyiunknak olyan üzenetváltó-alkalmazásokat kell használnunk, amelyek védik a személyes partnereink magánéletét — akikkel kapcsolatban állunk.", - "simplex-unique-overlay-card-1-p-1": "Más üzenetküldő platformoktól eltérően a SimpleX nem rendel azonosítókat a felhasználókhoz. Nem támaszkodik telefonszámokra, tartomány-alapú címekre (mint az e-mail, XMPP vagy a Matrix), felhasználónevekre, nyilvános kulcsokra vagy akár véletlenszerű számokra a felhasználók azonosításához — nem tudjuk, hogy hányan használják a SimpleX-kiszolgálóinkat.", + "simplex-unique-overlay-card-1-p-1": "Más üzenetküldő hálózatoktól eltérően a SimpleX nem rendel azonosítókat a felhasználókhoz. Nem támaszkodik telefonszámokra, tartomány-alapú címekre (mint az e-mail, XMPP vagy a Matrix), felhasználónevekre, nyilvános kulcsokra vagy akár véletlenszerű számokra a felhasználók azonosításához — a SimpleX-kiszolgálók üzemeltetői nem tudják, hogy hányan használják a kiszolgálóikat.", "simplex-unique-overlay-card-1-p-2": "Az üzenetek kézbesítéséhez a SimpleX az egyirányú üzenet várakoztatást használ páronkénti névtelen címekkel, külön a fogadott és külön az elküldött üzenetek számára, általában különböző kiszolgálókon keresztül.", - "simplex-unique-overlay-card-1-p-3": "Ez a kialakítás megvédi annak titkosságát, hogy kivel kommunikál, elrejtve azt a SimpleX-platform kiszolgálói és a megfigyelők elől. IP-címének a kiszolgálók elől való elrejtéséhez azt teheti meg, hogy Toron keresztül kapcsolódik a SimpleX-kiszolgálókhoz.", - "simplex-unique-overlay-card-2-p-1": "Mivel ön nem rendelkezik azonosítóval a SimpleX-platformon, senki sem tud kapcsolatba lépni önnel, hacsak nem oszt meg egy egyszeri vagy ideiglenes felhasználói címet, például QR-kódot vagy hivatkozást.", + "simplex-unique-overlay-card-1-p-3": "Ez a kialakítás megvédi annak titkosságát, hogy kivel kommunikál, elrejtve azt a SimpleX-hálózat kiszolgálói és a megfigyelők elől. IP-címének a kiszolgálók elől való elrejtéséhez azt teheti meg, hogy Toron keresztül kapcsolódik a SimpleX-kiszolgálókhoz.", + "simplex-unique-overlay-card-2-p-1": "Mivel ön nem rendelkezik azonosítóval a SimpleX-hálózaton, senki sem tud kapcsolatba lépni önnel, hacsak nem oszt meg egy egyszeri vagy ideiglenes felhasználói címet, például QR-kódot vagy hivatkozást.", "simplex-unique-overlay-card-2-p-2": "Még a felhasználói cím használata esetén is, aminek használata nem kötelező – ugyanakkor ez a kéretlen kapcsolatkérelmek küldésére is használható – módosíthatja vagy teljesen törölheti anélkül, hogy elveszítené a meglévő kapcsolatait.", "simplex-unique-overlay-card-3-p-1": "A SimpleX Chat az összes felhasználói adatot kizárólag a klienseken tárolja egy hordozható titkosított adatbázis-formátumban, amely exportálható és átvihető bármely más támogatott eszközre.", "simplex-unique-overlay-card-3-p-2": "A végpontok között titkosított üzenetek átmenetileg a SimpleX továbbítókiszolgálóin tartózkodnak, amíg be nem érkeznek a címzetthez, majd automatikusan véglegesen törlődnek onnan.", "simplex-unique-overlay-card-3-p-3": "A föderált hálózatok kiszolgálóitól (e-mail, XMPP vagy Matrix) eltérően a SimpleX-kiszolgálók nem tárolják a felhasználói fiókokat, csak továbbítják az üzeneteket, így védve mindkét fél magánéletét.", "simplex-unique-overlay-card-3-p-4": "A küldött és a fogadott kiszolgálóforgalom között nincsenek közös azonosítók vagy titkosított szövegek — ha bárki megfigyeli, nem tudja könnyen megállapítani, hogy ki kivel kommunikál, még akkor sem, ha a TLS-t kompromittálják.", - "simplex-unique-overlay-card-4-p-1": "Használhatja a SimpleXet a saját kiszolgálóival, és továbbra is kommunikálhat azokkal, akik az általunk biztosított, előre konfigurált kiszolgálókat használják.", - "simplex-unique-overlay-card-4-p-2": "A SimpleX-platform nyitott protokollt használ és SDK-t biztosít a chatbotok létrehozásához, lehetővé téve olyan szolgáltatások megvalósítását, amelyekkel a felhasználók a SimpleX Chat alkalmazásokon keresztül léphetnek kapcsolatba — mi már nagyon várjuk, hogy milyen SimpleX szolgáltatásokat készítenek a lelkes közreműködők.", - "simplex-unique-overlay-card-4-p-3": "Ha a SimpleX-platformra való fejlesztést fontolgatja, például a SimpleX-alkalmazások felhasználóinak szánt chatbotot, vagy a SimpleX Chat könyvtárbotjának integrálását más mobilalkalmazásba, lépjen velünk kapcsolatba, ha bármilyen tanácsot vagy támogatást szeretne kapni.", - "simplex-unique-card-1-p-1": "A SimpleX védi az ön profiljához tartozó kapcsolatokat és metaadatokat, elrejtve azokat a SimpleX-platform kiszolgálói és a megfigyelők elől.", - "simplex-unique-card-1-p-2": "Minden más létező üzenetküldő platformtól eltérően a SimpleX nem rendelkezik a felhasználókhoz rendelt azonosítókkal — még véletlenszerű számokkal sem.", - "simplex-unique-card-2-p-1": "Mivel a SimpleX-platformon nincs azonosítója vagy állandó címe, senki sem tud kapcsolatba lépni önnel, hacsak nem oszt meg egy egyszeri vagy ideiglenes felhasználói címet, például QR-kódot vagy hivatkozást.", + "simplex-unique-overlay-card-4-p-1": "Használhatja a SimpleXet a saját kiszolgálóival, és továbbra is kommunikálhat azokkal, akik az előre beállított kiszolgálókat használják az alkalmazásban.", + "simplex-unique-overlay-card-4-p-2": "A SimpleX-hálózat nyitott protokollt használ és SDK-t biztosít a chatbotok létrehozásához, lehetővé téve olyan szolgáltatások megvalósítását, amelyekkel a felhasználók a SimpleX Chat alkalmazásokon keresztül léphetnek kapcsolatba — mi már nagyon várjuk, hogy milyen SimpleX szolgáltatásokat készítenek a lelkes közreműködők.", + "simplex-unique-overlay-card-4-p-3": "Ha a SimpleX-hálózatra való fejlesztést fontolgatja, például a SimpleX-alkalmazások felhasználóinak szánt chatbotot, vagy a SimpleX Chat könyvtárbotjának integrálását más mobilalkalmazásba, lépjen velünk kapcsolatba, ha bármilyen tanácsot vagy támogatást szeretne kapni.", + "simplex-unique-card-1-p-1": "A SimpleX megvédi a profilhoz tartozó kapcsolatokat és metaadatokat, elrejtve azokat a SimpleX-hálózat kiszolgálói és a megfigyelők elől.", + "simplex-unique-card-1-p-2": "Minden más létező üzenetküldő hálózattól eltérően a SimpleX nem rendelkezik a felhasználókhoz rendelt azonosítókkal — még véletlenszerű számokkal sem.", + "simplex-unique-card-2-p-1": "Mivel a SimpleX-hálózaton nincs azonosítója vagy állandó címe, senki sem tud kapcsolatba lépni önnel, hacsak nem oszt meg egy egyszeri vagy ideiglenes felhasználói címet, például QR-kódot vagy hivatkozást.", "simplex-unique-card-3-p-1": "A SimpleX Chat az összes felhasználói adatot kizárólag a klienseken tárolja egy hordozható titkosított adatbázis-formátumban —, amely exportálható és átvihető bármely más támogatott eszközre.", "simplex-unique-card-3-p-2": "A végpontok között titkosított üzenetek átmenetileg a SimpleX továbbítókiszolgálóin tartózkodnak, amíg be nem érkeznek a címzetthez, majd automatikusan véglegesen törlődnek onnan.", - "simplex-unique-card-4-p-1": "A SimpleX-hálózat teljesen decentralizált és független bármely kriptopénztől vagy bármely más platformtól, kivéve az internetet.", + "simplex-unique-card-4-p-1": "A SimpleX-hálózat teljesen decentralizált és független bármely kriptopénztől vagy bármely más hálózattól, kivéve az internetet.", "simplex-unique-card-4-p-2": "Használhatja a SimpleXet a saját kiszolgálóival vagy az általunk biztosított kiszolgálókkal, és továbbra is kapcsolódhat bármely felhasználóhoz.", "join": "Csatlakozzon a közösségeinkhez", "we-invite-you-to-join-the-conversation": "Meghívjuk Önt, hogy csatlakozzon a beszélgetésekhez", @@ -165,7 +165,7 @@ "copy-the-command-below-text": "másolja be az alábbi parancsot, és használja a csevegésben:", "privacy-matters-section-header": "Miért számít az adatvédelem", "privacy-matters-section-subheader": "A metaadatok védelmének megőrzése — kivel beszélget — megvédi a következőktől:", - "privacy-matters-section-label": "Győződjön meg arról, hogy az üzenetváltó-alkalmazás amit használ nem fér hozzá az adataidhoz!", + "privacy-matters-section-label": "Győződjön meg arról, hogy az üzenetváltó-alkalmazás amit használ nem fér hozzá az adataihoz!", "simplex-private-section-header": "Mitől lesz a SimpleX privát", "simplex-network-section-header": "SimpleX-hálózat", "simplex-network-section-desc": "A Simplex Chat a P2P- és a föderált hálózatok előnyeinek kombinálásával biztosítja a legjobb adatvédelmet.", @@ -194,7 +194,7 @@ "comparison-section-list-point-4a": "A SimpleX továbbítókiszolgálói nem veszélyeztethetik az e2e titkosítást. Hitelesítse a biztonsági kódot a sávon kívüli csatorna elleni támadások veszélyeinek csökkentésére", "comparison-section-list-point-4": "Ha az üzemeltetett kiszolgálók veszélybe kerülnek. Hitelesítse a biztonsági kódot a Signal vagy más biztonságos üzenetküldő alkalmazás segítségével a támadások veszélyeinek csökkentésére", "comparison-section-list-point-5": "Nem védi a felhasználók metaadatait", - "comparison-section-list-point-6": "Bár a P2P elosztott, de nem föderált – egyetlen hálózatként működnek", + "comparison-section-list-point-6": "Bár a P2P elosztott, de nem föderált — egyetlen hálózatként működnek", "comparison-section-list-point-7": "A P2P-hálózatoknak vagy van egy központi hitelesítője, vagy az egész hálózat kompromittálódhat", "see-here": "tekintse meg itt", "guide-dropdown-1": "Gyors indítás", @@ -206,7 +206,7 @@ "guide-dropdown-7": "Adatvédelem és biztonság", "guide-dropdown-8": "Alkalmazás beállításai", "guide": "Útmutató", - "docs-dropdown-1": "SimpleX-platform", + "docs-dropdown-1": "SimpleX-hálózat", "docs-dropdown-2": "Android fájlok elérése", "docs-dropdown-3": "Hozzáférés a csevegési adatbázishoz", "docs-dropdown-8": "SimpleX jegyzékszolgáltatás", @@ -222,7 +222,7 @@ "please-use-link-in-mobile-app": "Használja a mobilalkalmazásban található hivatkozást", "contact-hero-header": "Kapott egy meghívót a SimpleX Chaten való beszélgetéshez", "invitation-hero-header": "Kapott egy egyszer használható meghívót a SimpleX Chaten való beszélgetéshez", - "simplex-network-overlay-card-1-li-4": "A P2P-megvalósításokat egyes internetszolgáltatók blokkolhatják (mint például a BitTorrent). A SimpleX átvitel-független - a szabványos webes protokollokon, pl. WebSocketsen keresztül is működik.", + "simplex-network-overlay-card-1-li-4": "A P2P-megvalósításokat egyes internetszolgáltatók blokkolhatják (mint például a BitTorrent). A SimpleX átvitel-független — a szabványos webes protokollokon, például WebSocketsen keresztül is működik.", "simplex-private-card-4-point-2": "A SimpleX Tor-on keresztüli használatához telepítse az Orbot alkalmazást és engedélyezze a SOCKS5 proxyt (vagy a VPN-t az iOS-ban).", "simplex-private-card-5-point-1": "A SimpleX minden titkosítási réteghez tartalomkitöltést használ, hogy meghiúsítsa az üzenetméret ellen irányuló támadásokat.", "simplex-private-card-5-point-2": "A kiszolgálók és a hálózatot megfigyelők számára a különböző méretű üzenetek egyformának tűnnek.", diff --git a/website/langs/it.json b/website/langs/it.json index 032706ebab..18a47bfe9f 100644 --- a/website/langs/it.json +++ b/website/langs/it.json @@ -60,23 +60,23 @@ "hero-overlay-card-2-p-2": "Potrebbero quindi correlare queste informazioni con i social network pubblici esistenti e determinare alcune identità reali.", "hero-overlay-card-2-p-4": "SimpleX protegge da questi attacchi non avendo alcun ID utente per design. Inoltre, se usi la modalità di modalità in incognito, il tuo nome mostrato sarà diverso per ogni contatto, evitando dati condivisi tra di loro.", "simplex-network-overlay-card-1-li-2": "Il design di SimpleX, a differenza della maggior parte delle reti P2P, non ha identificatori utente globali di alcun tipo, nemmeno temporanei, e usa solo identificatori temporanei a coppie, garantendo una maggiore protezione dell'anonimato e dei metadati.", - "simplex-network-overlay-card-1-li-4": "Le implementazioni P2P possono essere bloccate da alcuni fornitori di internet (come BitTorrent). SimpleX è indipendente dal trasporto - può funzionare su protocolli web standard, es. WebSocket.", + "simplex-network-overlay-card-1-li-4": "Le implementazioni P2P possono essere bloccate da alcuni fornitori di internet (come BitTorrent). SimpleX è indipendente dal trasporto — può funzionare su protocolli web standard, es. WebSocket.", "hero-overlay-card-2-p-1": "Quando gli utenti hanno identità permanenti, anche se si tratta solo di un numero casuale, come un Session ID, c'è il rischio che il fornitore o un malintenzionato possano osservare come gli utenti sono connessi e quanti messaggi inviano.", "simplex-network-overlay-card-1-li-6": "Le reti P2P possono essere vulnerabili all'attacco DRDoS, quando i client possono ritrasmettere e amplificare il traffico, con conseguente \"denial of service\" a livello di rete. I client SimpleX si limitano a inoltrare il traffico da una connessione nota e non possono essere usati da un aggressore per amplificare il traffico nell'intera rete.", - "privacy-matters-overlay-card-1-p-4": "La piattaforma SimpleX protegge la privacy delle tue connessioni meglio di qualsiasi alternativa, impedendo completamente che il tuo grafico sociale sia disponibile a qualsiasi azienda o organizzazione. Anche quando le persone usano i server forniti da SimpleX Chat, non conosciamo il numero di utenti o le loro connessioni.", - "privacy-matters-overlay-card-2-p-3": "SimpleX è la prima piattaforma che non ha alcun identificatore utente per design, proteggendo così il tuo grafico delle connessioni meglio di qualsiasi alternativa conosciuta.", + "privacy-matters-overlay-card-1-p-4": "La rete di SimpleX protegge la privacy delle tue connessioni meglio di qualsiasi alternativa, impedendo completamente che il tuo grafico sociale sia disponibile a qualsiasi azienda o organizzazione. Anche quando le persone usano i server preconfigurati in SimpleX Chat, gli operatori dei server non conoscono il numero di utenti o le loro connessioni.", + "privacy-matters-overlay-card-2-p-3": "SimpleX è la prima rete che non ha alcun identificatore utente per design, proteggendo così il tuo grafico delle connessioni meglio di qualsiasi alternativa conosciuta.", "privacy-matters-overlay-card-3-p-1": "Tutti dovrebbero preoccuparsi della privacy e della sicurezza delle proprie comunicazioni — conversazioni innocue possono metterti in pericolo, anche se non hai nulla da nascondere.", "privacy-matters-overlay-card-3-p-3": "Le persone comuni vengono arrestate per ciò che condividono online, anche tramite i loro account \"anonimi\", anche nei Paesi democratici.", - "simplex-unique-overlay-card-1-p-3": "Questo design protegge la privacy di chi stai comunicando, nascondendola ai server della piattaforma SimpleX e a qualsiasi osservatore. Per nascondere il tuo indirizzo IP ai server, puoi connetterti ai server SimpleX tramite Tor.", - "simplex-unique-overlay-card-2-p-1": "Poiché non hai alcun identificatore sulla piattaforma SimpleX, nessuno può contattarti a meno che tu non condivida un indirizzo utente una tantum o temporaneo, come un codice QR o un link.", + "simplex-unique-overlay-card-1-p-3": "Questo design protegge la privacy di chi stai comunicando, nascondendola ai server della rete SimpleX e a qualsiasi osservatore. Per nascondere il tuo indirizzo IP ai server, puoi connetterti ai server SimpleX tramite Tor.", + "simplex-unique-overlay-card-2-p-1": "Poiché non hai alcun identificatore sulla rete SimpleX, nessuno può contattarti a meno che tu non condivida un indirizzo utente una tantum o temporaneo, come un codice QR o un link.", "simplex-unique-overlay-card-2-p-2": "Anche l'indirizzo utente opzionale, che può essere usato per inviare richieste di contatto spam, è possibile modificarlo o eliminarlo completamente senza perdere alcuna connessione.", "simplex-unique-overlay-card-3-p-1": "SimpleX Chat conserva tutti i dati utente solo sui dispositivi client usando un formato trasferibile di database crittografato che può essere esportato e trasferito su qualsiasi dispositivo supportato.", "simplex-unique-overlay-card-3-p-2": "I messaggi crittografati end-to-end vengono conservati temporaneamente sui server di inoltro SimpleX fino alla ricezione, quindi vengono eliminati definitivamente.", - "simplex-unique-overlay-card-4-p-1": "Puoi usare SimpleX con i tuoi server personali e continuare a comunicare con le persone che usano i server preconfigurati forniti da noi.", - "simplex-unique-overlay-card-4-p-3": "Se stai pensando di sviluppare per la piattaforma SimpleX, ad esempio, il chat bot per gli utenti dell'app SimpleX o l'integrazione della libreria SimpleX Chat nelle tue app mobili, contattaci per qualsiasi consiglio e supporto.", - "simplex-unique-card-1-p-1": "SimpleX protegge la privacy del tuo profilo, contatti e metadati, nascondendoli ai server della piattaforma SimpleX e ad eventuali osservatori.", - "simplex-unique-card-1-p-2": "A differenza di qualsiasi altra piattaforma di messaggistica esistente, SimpleX non ha identificatori assegnati agli utenti — nemmeno numeri casuali.", - "simplex-unique-card-2-p-1": "Poiché non hai un identificatore o un indirizzo fisso sulla piattaforma SimpleX, nessuno può contattarti a meno che tu non condivida un indirizzo utente una tantum o temporaneo, come codice un QR o un link.", + "simplex-unique-overlay-card-4-p-1": "Puoi usare SimpleX con i tuoi server personali e continuare a comunicare con le persone che usano i server preconfigurati nelle app.", + "simplex-unique-overlay-card-4-p-3": "Se stai pensando di sviluppare per la rete SimpleX, ad esempio, il chat bot per gli utenti dell'app SimpleX o l'integrazione della libreria SimpleX Chat nelle tue app mobili, contattaci per qualsiasi consiglio e supporto.", + "simplex-unique-card-1-p-1": "SimpleX protegge la privacy del tuo profilo, contatti e metadati, nascondendoli ai server della rete SimpleX e ad eventuali osservatori.", + "simplex-unique-card-1-p-2": "A differenza di qualsiasi altra rete di messaggistica esistente, SimpleX non ha identificatori assegnati agli utenti — nemmeno numeri casuali.", + "simplex-unique-card-2-p-1": "Poiché non hai un identificatore o un indirizzo fisso sulla rete SimpleX, nessuno può contattarti a meno che tu non condivida un indirizzo utente una tantum o temporaneo, come codice un QR o un link.", "simplex-unique-card-3-p-1": "SimpleX conserva tutti i dati utente sui dispositivi client in un formato trasferibile di database crittografato — può essere trasferito su un altro dispositivo.", "join": "Unisciti a", "we-invite-you-to-join-the-conversation": "Ti invitiamo a unirti alla conversazione", @@ -102,7 +102,7 @@ "use-this-command": "Usa questo comando:", "see-simplex-chat": "Vedi SimpleX Chat", "github-repository": "Repository GitHub", - "the-instructions--source-code": "le istruzioni su come scaricarlo o compilarlo dal codice sorgente.", + "the-instructions--source-code": "per le istruzioni su come scaricarlo o compilarlo dal codice sorgente.", "if-you-already-installed-simplex-chat-for-the-terminal": "Se hai già installato SimpleX Chat per il terminale", "if-you-already-installed": "Se hai già installato", "privacy-matters-section-subheader": "Preservare la privacy dei tuoi metadati — con chi parli — ti protegge da:", @@ -130,13 +130,13 @@ "no-federated": "No - federato", "comparison-section-list-point-1": "Solitamente si basa su un numero di telefono, in alcuni casi su nomi utente", "comparison-section-list-point-2": "Indirizzi basati su DNS", - "comparison-section-list-point-6": "Sebbene i P2P siano distribuiti, non sono federati - operano come un'unica rete", + "comparison-section-list-point-6": "Sebbene i P2P siano distribuiti, non sono federati — operano come un'unica rete", "comparison-section-list-point-7": "Le reti P2P hanno un'autorità centrale o l'intera rete può essere compromessa", "see-here": "vedi qui", "simplex-network-3-header": "Nella rete di SimpleX", "simplex-private-card-7-point-2": "Se un messaggio viene aggiunto, rimosso o modificato, il destinatario verrà avvisato.", - "hero-overlay-card-1-p-2": "Per consegnare i messaggi, invece degli ID utente utilizzati da tutte le altre piattaforme, SimpleX usa identificatori temporanei anonimi a coppie delle code di messaggi, separati per ciascuna delle tue connessioni — non ci sono identificatori a lungo termine.", - "simplex-private-card-6-point-1": "Molte piattaforme di comunicazione sono vulnerabili agli attacchi MITM da parte di server o fornitori di rete.", + "hero-overlay-card-1-p-2": "Per consegnare i messaggi, invece degli ID utente utilizzati da tutte le altre reti, SimpleX usa identificatori temporanei anonimi a coppie delle code di messaggi, separati per ciascuna delle tue connessioni — non ci sono identificatori a lungo termine.", + "simplex-private-card-6-point-1": "Molte reti di comunicazione sono vulnerabili agli attacchi MITM da parte di server o fornitori di rete.", "simplex-private-card-7-point-1": "Per garantire l'integrità, i messaggi sono numerati in sequenza e includono l'hash del messaggio precedente.", "simplex-private-card-9-point-2": "Riduce i vettori di attacco, rispetto ai broker di messaggi tradizionali, e i metadati disponibili.", "simplex-private-card-10-point-2": "Ciò consente di recapitare messaggi senza identificatori del profilo utente, garantendo una migliore privacy dei metadati rispetto alle alternative.", @@ -161,7 +161,7 @@ "feature-3-title": "Gruppi decentralizzati crittografati E2E — solo gli utenti sanno che esistono", "blog": "Blog", "simplex-explained-tab-2-p-1": "Per ogni connessione usi due code di messaggi distinte per inviare e ricevere i messaggi attraverso server diversi.", - "simplex-explained-tab-2-p-2": "I server passano i messaggi solo in una direzione, senza avere il quadro completo della conversazione dell'utente o delle connessioni.", + "simplex-explained-tab-2-p-2": "I server passano i messaggi solo in una direzione, senza avere il quadro completo delle conversazioni dell'utente o delle connessioni.", "simplex-explained-tab-3-p-2": "Gli utenti possono aumentare ulteriormente la privacy dei metadati usando Tor per accedere ai server, evitando correlazioni per indirizzo IP.", "smp-protocol": "Protocollo SMP", "feature-1-title": "Messaggi crittografati E2E con markdown e modifica", @@ -181,21 +181,21 @@ "privacy-matters-overlay-card-1-p-2": "I rivenditori online sanno che le persone con redditi più bassi sono più propense a fare acquisti urgenti, quindi possono applicare prezzi più alti o rimuovere sconti.", "privacy-matters-overlay-card-1-p-3": "Alcune società finanziarie e assicurative usano grafici sociali per determinare i tassi di interesse e i premi. Spesso ciò fa pagare di più le persone con redditi più bassi — è noto come \"premio di povertà\" .", "privacy-matters-overlay-card-2-p-1": "Non molto tempo fa abbiamo assistito alla manipolazione delle principali elezioni da una rispettabile società di consulenza che ha usato i nostri grafici sociali per distorcere la nostra visione del mondo reale e manipolare i nostri voti.", - "privacy-matters-overlay-card-2-p-2": "Per essere obiettivi e prendere decisioni indipendenti devi avere il controllo del tuo spazio informativo. È possibile solo se utilizzi una piattaforma di comunicazione privata che non ha accesso al tuo grafico sociale.", + "privacy-matters-overlay-card-2-p-2": "Per essere obiettivi e prendere decisioni indipendenti devi avere il controllo del tuo spazio informativo. È possibile solo se utilizzi una rete di comunicazione privata che non ha accesso al tuo grafico sociale.", "privacy-matters-overlay-card-3-p-2": "Una delle storie più scioccanti è l'esperienza di Mohamedou Ould Salahi descritta nel suo libro di memorie e mostrata nel film The Mauritanian. È stato rinchiuso nel campo di Guantánamo, senza processo, e lì è stato torturato per 15 anni dopo una telefonata a un suo parente in Afghanistan, sospettato di essere coinvolto negli attacchi dell'11/9, nonostante avesse vissuto in Germania per i precedenti 10 anni.", "join-us-on-GitHub": "Unisciti a noi su GitHub", "simplex-chat-for-the-terminal": "SimpleX Chat per il terminale", "privacy-matters-overlay-card-3-p-4": "Non è sufficiente usare un messenger crittografato end-to-end, tutti dovremmo usare i messenger che proteggono la privacy delle nostre reti personali — con chi siamo connessi.", "simplex-unique-overlay-card-1-p-2": "Per recapitare i messaggi, SimpleX usa indirizzi anonimi a coppie di code di messaggi unidirezionali, separate per i messaggi ricevuti e inviati, di solito tramite server diversi.", - "simplex-unique-overlay-card-1-p-1": "A differenza di altre piattaforme di messaggistica, SimpleX non ha alcun identificatore assegnato agli utenti. Non si basa su numeri di telefono, indirizzi basati su domini (come email o XMPP), nomi utente, chiavi pubbliche o persino numeri casuali per identificare i suoi utenti — non sappiamo quante persone usano i nostri server SimpleX.", + "simplex-unique-overlay-card-1-p-1": "A differenza di altre reti di messaggistica, SimpleX non ha alcun identificatore assegnato agli utenti. Non si basa su numeri di telefono, indirizzi basati su domini (come email o XMPP), nomi utente, chiavi pubbliche o persino numeri casuali per identificare i suoi utenti — gli operatori dei server non sanno quante persone usano i loro server.", "simplex-unique-overlay-card-3-p-3": "A differenza dei server di reti federate (email, XMPP o Matrix), i server SimpleX non conservano gli account utente, ma trasmettono solo i messaggi, proteggendo la privacy di entrambe le parti.", "simplex-unique-card-3-p-2": "I messaggi crittografati end-to-end vengono conservati temporaneamente sui server di inoltro SimpleX fino alla ricezione, quindi vengono eliminati definitivamente.", "simplex-unique-card-4-p-2": "Puoi usare SimpleX con i tuoi server personali o con i server forniti da noi — e connetterti comunque a qualsiasi utente.", "join-the-REDDIT-community": "Unisciti alla comunità di REDDIT", "sign-up-to-receive-our-updates": "Iscriviti per ricevere i nostri aggiornamenti", "simplex-unique-overlay-card-3-p-4": "Non ci sono identificatori o testi cifrati in comune tra il traffico del server inviato e quello ricevuto — se qualcuno lo osserva, non può determinare facilmente chi comunica con chi, anche se il TLS è compromesso.", - "simplex-unique-overlay-card-4-p-2": "La piattaforma SimpleX usa un protocollo aperto e fornisce un SDK per creare chat bot, consentendo l'implementazione di servizi con cui gli utenti possono interagire tramite le app SimpleX Chat — siamo impazienti di vedere quali servizi SimpleX puoi creare.", - "simplex-unique-card-4-p-1": "La rete SimpleX è completamente decentralizzata e indipendente da qualsiasi criptovaluta o altra piattaforma, ad eccezione di internet.", + "simplex-unique-overlay-card-4-p-2": "La rete di SimpleX usa un protocollo aperto e fornisce un SDK per creare chat bot, consentendo l'implementazione di servizi con cui gli utenti possono interagire tramite le app SimpleX Chat — siamo impazienti di vedere quali servizi SimpleX creerai.", + "simplex-unique-card-4-p-1": "La rete SimpleX è completamente decentralizzata e indipendente da qualsiasi criptovaluta o altra rete, ad eccezione di internet.", "donate-here-to-help-us": "Dona qui per aiutarci", "to-make-a-connection": "Per stabilire una connessione:", "scan-the-qr-code-with-the-simplex-chat-app-description": "Le chiavi pubbliche e l'indirizzo della coda di messaggi in questo link NON vengono inviati in rete quando vedi questa pagina —
    sono contenuti nel frammento hash dell'URL del link.", @@ -218,7 +218,7 @@ "guide-dropdown-7": "Privacy e sicurezza", "guide-dropdown-9": "Creare connessioni", "guide": "Guida", - "docs-dropdown-1": "Piattaforma SimpleX", + "docs-dropdown-1": "Rete di SimpleX", "docs-dropdown-2": "Accesso ai file Android", "docs-dropdown-3": "Accesso al database di chat", "docs-dropdown-4": "Ospita un server SMP", diff --git a/website/langs/zh_Hans.json b/website/langs/zh_Hans.json index c6712e7bbf..aa51a7cff5 100644 --- a/website/langs/zh_Hans.json +++ b/website/langs/zh_Hans.json @@ -24,7 +24,7 @@ "simplex-network-overlay-card-1-li-2": "与大多数 P2P 网络不同,SimpleX 在设计上没有任何类型的全局用户标识符,甚至临时的也没有。SimpleX 仅使用临时的成对标识符,提供更好的匿名性和元数据保护。", "privacy-matters-overlay-card-1-p-1": "许多大型公司会使用您的人际关系来估算您的收入,决定商品的价格,并向您兜售您并不真正需要的产品。", "simplex-unique-overlay-card-3-p-4": "发送和接收的服务器流量之间没有共同的标识符或密文—— 如果有人在观察它,他们也无法轻易确定谁与谁通信,即使 TLS 受到威胁。", - "simplex-unique-card-4-p-1": "SimpleX 网络是完全去中心化的,并且独立于任何加密货币或除互联网以外的任何其他平台。", + "simplex-unique-card-4-p-1": "SimpleX 网络是完全去中心化的,并且独立于任何加密货币或除互联网以外的任何其他网络。", "join": "加入", "get-simplex": "获取 SimpleX desktop app", "hide-info": "隐藏信息", @@ -50,7 +50,7 @@ "simplex-explained-tab-3-text": "3. 服务器能看到什么", "hero-header": "重新定义隐私", "simplex-explained-tab-1-p-2": "它是如何在单向消息队列与没有用户识别符的情况下工作的?", - "simplex-explained-tab-2-p-2": "服务器只会单向传输消息,而无法掌握用户的会话或连接的全貌。", + "simplex-explained-tab-2-p-2": "服务器只会单向传输消息,而无法掌握用户的对话或连接的全貌。", "simplex-explained-tab-3-p-1": "服务器对每个队列都有单独的匿名凭证,并且不知道这些凭证属于哪些用户。", "donate": "捐赠", "simplex-explained-tab-2-p-1": "对于每个连接,您都会使用两个单独的消息队列,通过不同的服务器发送和接收消息。", @@ -104,7 +104,7 @@ "simplex-private-card-10-point-1": "SimpleX 为每个用户的联系人或群组成员均使用临时匿名成对地址和凭据。", "privacy-matters-3-title": "因无辜结社而被起诉", "privacy-matters-2-overlay-1-title": "隐私给您力量", - "hero-overlay-card-1-p-2": "为了传递消息,SimpleX 使用消息队列的临时匿名成对标识符(而非其他平台使用的用户 ID)来区分您的每个连接——没有长期标识符这种东西。", + "hero-overlay-card-1-p-2": "SimpleX 使用消息队列的临时匿名成对标识符(而非其他网络使用的用户 ID)来传递消息,而每个连接的标识符均不尽相同 — 没有长期标识符这种东西。", "hero-overlay-card-1-p-3": "您定义用于接收消息的服务器、您的联系人—— 您用来向他们发送消息的服务器。 每个会话都很可能会使用两个不同的服务器。", "hero-overlay-card-1-p-4": "此设计可防止在应用程序级别泄漏任何用户元数据。 为了进一步改善隐私并保护您的 IP 地址,您可以通过 Tor 连接到消息服务器。", "hero-overlay-card-1-p-5": "只有客户端设备存储用户配置文件、联系人和群组; 消息均使用两层端到端加密发送。", @@ -115,29 +115,29 @@ "hero-overlay-card-2-p-3": "即使是使用 Tor v3 服务的最私密的应用程序,如果您通过同一个人资料与两个不同的联系人交谈,他们也可以证明他们与同一个人有联系。", "hero-overlay-card-2-p-4": "为防止这些攻击,SimpleX 在其设计中不包含任何用户 ID 。 而且,如果您使用隐身模式,您将为每个联系人显示不同的名称,避免他们之间共享任何数据。", "simplex-network-overlay-card-1-li-1": "P2P 网络依赖于 分布式散列表(DHT) 的某些变体来路由消息。 DHT 在设计上必须平衡可达性和延迟。 SimpleX 比 P2P 具有更好的可达性和更低的延迟,因为消息可以通过通讯双方选择的多个服务器并行地冗余传递。若是在 P2P 网络中,消息则需要使用算法选择,并依次通过 O(log N) 个节点。", - "simplex-network-overlay-card-1-li-4": "P2P 实现(如 BitTorrent)可能会被某些互联网提供商阻止。 SimpleX 与传输协议无关——它可以在标准网络协议上工作,例如 WebSockets。", + "simplex-network-overlay-card-1-li-4": "P2P 实现(如 BitTorrent)可能会被某些互联网提供商阻止。 SimpleX 与传输协议无关 — 它可以在标准网络协议上工作,例如 WebSockets。", "simplex-network-overlay-card-1-li-3": "P2P 并未解决中间人攻击(MITM Attack) 问题。大多数现有的 P2P 实现没有使用带外通讯来进行初始密钥的交换,而 SimpleX 使用带外通讯,或者在某些情况下,使用预先存在的安全和可信连接来进行初始密钥交换。", "simplex-network-overlay-card-1-li-5": "所有已知的 P2P 网络都可能受到 Sybil 攻击,因为每个节点都是可发现的,并且网络作为一个整体运行。 已知的缓解措施不是需要一个中心化的组件就是需要昂贵的工作量证明。而 SimpleX 网络没有服务器可发现性,它是碎片化的并且作为多个隔离的子网运行,这样全网络范围的攻击便无从实现。", "simplex-network-overlay-card-1-li-6": "P2P 网络可能受到 DRDoS 攻击 。客户端有能力重新广播和放大流量,从而导致整个网络范围内的服务中断。 SimpleX 客户端仅中继来自已知连接的流量,因此不能被攻击者用来放大整个网络的流量。", "privacy-matters-overlay-card-1-p-2": "在线零售商知道收入较低的人更有可能在紧急情况下购买商品,因此他们可能会收取更高的价格或取消折扣。", "privacy-matters-overlay-card-1-p-3": "一些金融和保险公司使用社交图谱来确定利率和保费。 它通常会让收入较低的人支付更多—— 它被称为“贫困溢价”。", - "privacy-matters-overlay-card-1-p-4": "SimpleX 平台比任何替代方案都能更好地保护您人际关系层面的隐私,防止您的社交图谱被任何公司或组织使用。 即使人们使用 SimpleX Chat 提供的服务器,我们也不知道用户数量或他们的连接数。", + "privacy-matters-overlay-card-1-p-4": "SimpleX 网络比任何替代方案都能更好地保护您人际关系层面的隐私,防止您的社交图谱被任何公司或组织使用。 即使人们使用 SimpleX Chat 应用预配置的服务器,服务器运营方也不知道用户数量或他们的连接数。", "privacy-matters-overlay-card-2-p-1": "不久前,我们观察到几次大选被一家知名咨询公司操纵,该公司使用我们的社交图谱扭曲我们对现实世界的看法并操纵我们的选票。", - "privacy-matters-overlay-card-2-p-2": "为了客观并做出独立的决定,您需要控制您的信息空间。 而这只有当您使用没有能力访问您的社交图谱的,注重隐私的通信平台时,这才有可能。", - "privacy-matters-overlay-card-2-p-3": "SimpleX 是第一个没有设计任何用户标识符的平台,这样能比任何已知的替代方案都更好地保护您的连接图谱。", + "privacy-matters-overlay-card-2-p-2": "为了客观并做出独立的决定,您需要控制您的信息空间。 而这只有当您使用没有能力访问您的社交图谱的,注重隐私的通信网络时,这才有可能。", + "privacy-matters-overlay-card-2-p-3": "SimpleX 是第一个没有设计任何用户标识符的网络,这样能比任何已知的替代方案都更好地保护您的连接图谱。", "privacy-matters-overlay-card-3-p-2": "最令人震惊的故事之一是 Mohamedou Ould Salahi 在他的回忆录中描述并在毛里塔尼亚电影中展示的经历。 他在未经审判的情况下被关进关塔那摩集中营,并在打电话给他在阿富汗的亲戚后在那里遭受了 15 年的折磨,他被怀疑参与了 9/11 袭击,尽管他在过去 10 年住在德国。", "privacy-matters-overlay-card-3-p-1": "每个人都应该关心他们通信的隐私和安全——无害的谈话会让您处于危险之中,即使您没有什么可隐瞒的。", "privacy-matters-overlay-card-3-p-3": "普通人会因为他们在网上分享的内容而被捕,即使是通过他们的“匿名”帐户,即使是在民主国家。", "privacy-matters-overlay-card-3-p-4": "使用端到端加密的即时通讯软件还不够,我们都应该使用保护我们个人网络隐私——即我们与谁有联系的即时通讯软件。", - "simplex-unique-overlay-card-1-p-1": "与其他消息传递平台不同,SimpleX 没有分配给用户的标识符。 它不依赖电话号码、基于域的地址(如电子邮件或 XMPP)、用户名、公钥甚至随机数来识别其用户—— 我们不知道有多少人使用我们的 SimpleX 服务器。", - "simplex-unique-overlay-card-1-p-2": "为了传递消息,SimpleX 使用单向消息队列的成对匿名地址,通常通过不同的服务器将接收和发送的消息分开。 使用 SimpleX 就像为每个联系人使用不同的即时电子邮件或电话,并且无需管理它们。", - "simplex-unique-overlay-card-1-p-3": "这种设计保护了您正在与之通信的人的隐私,将其隐藏在 SimpleX 平台服务器和任何观察者之外。 要对服务器隐藏您的 IP 地址,您可以通过 Tor 连接到 SimpleX 服务器。", - "simplex-unique-overlay-card-4-p-1": "您可以将 SimpleX 与您自己的服务器一起使用,并且仍然可以与使用我们提供的预配置服务器的人进行通信。", + "simplex-unique-overlay-card-1-p-1": "与其他消息网络不同,SimpleX 没有分配给用户的标识符。 它不依赖电话号码、基于域的地址(如电子邮件或 XMPP)、用户名、公钥甚至随机数来识别其用户—— SimpleX 服务器运营方不知道有多少人使用其服务器。", + "simplex-unique-overlay-card-1-p-2": "为了传递消息,SimpleX 使用单向消息队列的成对匿名地址,通常通过不同的服务器将接收和发送的消息分开。", + "simplex-unique-overlay-card-1-p-3": "这种设计保护了您正在与之通信的人的隐私,将其隐藏在 SimpleX 网络的服务器和任何观察者之外。 要对服务器隐藏您的 IP 地址,您可以通过 Tor 连接到 SimpleX 服务器。", + "simplex-unique-overlay-card-4-p-1": "您可以将 SimpleX 与您自己的服务器一起使用,并且仍然可以与使用应用中预配置服务器的人们进行通信。", "simplex-unique-card-3-p-2": "端到端加密的消息在被收到前会暂时保存在 SimpleX 中继服务器上,传送完成后它们会被永久删除。", - "simplex-unique-card-1-p-1": "SimpleX 保护您的个人资料、联系人和元数据的隐私,将其隐藏在 SimpleX 平台服务器和任何观察者之外。", - "simplex-unique-overlay-card-4-p-3": "如果您正在考虑为在SimpleX 平台上开发,例如,为 SimpleX 应用程序用户开发聊天机器人,或将 SimpleX 聊天库集成到您的移动应用程序中,请 联系我们 以获取建议和支持。", + "simplex-unique-card-1-p-1": "SimpleX 保护您的个人资料、联系人和元数据的隐私,不让 SimpleX 网络服务器和任何观察者看到它们。", + "simplex-unique-overlay-card-4-p-3": "如果您正在考虑为在SimpleX 网络上开发,例如,为 SimpleX 应用程序用户开发聊天机器人,或将 SimpleX 聊天库集成到您的移动应用程序中,请 联系我们 以获取建议和支持。", "simplex-unique-card-4-p-2": "您可以搭配自己的服务器来使用 SimpleX 或使用我们提供的服务器 — 并仍然连接到任何用户。", - "simplex-unique-card-2-p-1": "因为您在 SimpleX 平台上没有标识符或固定地址,所以除非您以二维码或链接的形式分享一次性或临时用户地址,没有人可以联系您。", + "simplex-unique-card-2-p-1": "因为您在 SimpleX 网络上没有标识符或固定地址,所以除非您以二维码或链接的形式分享一次性或临时用户地址,没有人可以联系您。", "simplex-unique-card-3-p-1": "SimpleX 以便携式加密数据库格式将所有用户数据存储在客户端设备上—— 它可以转移到另一个设备。", "sign-up-to-receive-our-updates": "注册以接收我们的更新", "we-invite-you-to-join-the-conversation": "我们邀请您加入对话", @@ -164,7 +164,7 @@ "github-repository": "GitHub 仓库", "copy-the-command-below-text": "复制下面的命令并在聊天中使用它:", "privacy-matters-section-header": "为什么隐私很重要", - "the-instructions--source-code": "如何从源代码下载或编译它的说明。", + "the-instructions--source-code": "如何下载或从源代码下载进行编译的说明。", "if-you-already-installed-simplex-chat-for-the-terminal": "如果您已经为终端安装了 SimpleX Chat", "if-you-already-installed": "如果您已经安装", "simplex-chat-for-the-terminal": "用于终端的 SimpleX Chat", @@ -197,16 +197,16 @@ "comparison-section-list-point-4": "如果运营商的服务器受到威胁。 验证 Signal 和其他一些应用程序中的安全代码以缓解该问题", "comparison-section-list-point-1": "通常基于电话号码,在某些情况下基于用户名", "comparison-section-list-point-2": "基于 DNS 的地址", - "comparison-section-list-point-6": "P2P 是分布式的,而非联邦式的 - 它们作为单个网络运行", + "comparison-section-list-point-6": "P2P 是分布式的,但并非联邦式的 — 它们作为单个网络运行", "see-here": "参见此处", "comparison-section-list-point-7": "P2P 网络要么拥有中央权威,要么整个网络可能被攻陷", "simplex-private-card-5-point-1": "SimpleX 为每个加密层进行内容填充来对抗长度扩展攻击。", - "simplex-unique-overlay-card-4-p-2": "SimpleX 平台使用开放协议并提供用于创建聊天机器人的 SDK, 允许用户实现通过 SimpleX Chat 应用程序与之交互的服务—— 我们真的很期待看到您可以依托SimpleX构建哪些服务。", + "simplex-unique-overlay-card-4-p-2": "SimpleX 网络使用开放协议并提供用于创建聊天机器人的 SDK, 允许用户实现通过 SimpleX Chat 应用程序与之交互的服务—— 我们真的很期待看到您会依托SimpleX构建哪些服务。", "simplex-unique-overlay-card-2-p-2": "即使使用可选的用户地址,当它被用于发送垃圾邮件联系请求,您可以更改或完全删除它而不会丢失任何连接。", "simplex-unique-overlay-card-3-p-1": "SimpleX Chat 使用便携式加密数据库格式仅将所有用户数据存储在客户端设备上,该格式可以导出并传输到任何支持的设备。", "donate-here-to-help-us": "在这里捐款来帮助我们", - "simplex-unique-card-1-p-2": "与任何其他现有的消息传递平台不同,SimpleX 没有分配给用户的标识符—— 甚至随机数也没有。", - "simplex-unique-overlay-card-2-p-1": "因为您在 SimpleX 平台上没有标识符,所以除非您以二维码或链接的形式分享一次性或临时用户地址,没有人可以联系您。", + "simplex-unique-card-1-p-2": "与任何其他现有的消息传递网络不同,SimpleX 没有分配给用户的标识符—— 甚至随机数也没有。", + "simplex-unique-overlay-card-2-p-1": "因为您在 SimpleX 网络上没有标识符,所以除非您以二维码或链接的形式分享一次性或临时用户地址,没有人可以联系您。", "simplex-unique-overlay-card-3-p-2": "端到端加密的消息在被收到前会暂时保存在 SimpleX 中继服务器上,传送完成后它们会被永久删除。", "simplex-unique-overlay-card-3-p-3": "与联合网络服务器(电子邮件、XMPP 或 Matrix)不同,SimpleX 服务器不存储用户帐户,它们仅中继消息,保护双方的隐私。", "guide-dropdown-2": "发送消息", @@ -223,7 +223,7 @@ "click-to-see": "点击查看", "menu": "菜单", "guide-dropdown-1": "快速指南", - "docs-dropdown-1": "SimpleX 平台", + "docs-dropdown-1": "SimpleX 网络", "docs-dropdown-5": "托管 XFTP 服务器", "newer-version-of-eng-msg": "本页面有较新的英语版本。", "guide-dropdown-4": "聊天档案", diff --git a/website/web.sh b/website/web.sh index 4feba4be50..897c87743e 100755 --- a/website/web.sh +++ b/website/web.sh @@ -5,6 +5,7 @@ set -e cp -R docs website/src rm -rf website/src/docs/rfcs rm website/src/docs/lang/*/README.md +rm -rf website/src/docs/dependencies cp -R blog website/src cp -R images website/src rm website/src/blog/README.md From 4b42a19ccb035f113cfd8ed318bfb5b97756cd85 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 10 May 2025 14:37:45 +0100 Subject: [PATCH 537/567] ios: fix XCode 16 regressions (tap not working on files, quotes, images, voice messages, etc.), open link previews on tap (#5880) * ios: fix XCode 16 regressions (tap not working on files, quotes, images, voice messages, etc.), open link previews on tap * fix voice recording * fix video, accepting calls from chat, preference toggles in chat * WIP message and meta * handle links in attributed strings * custom attribute for links to prevent race conditions with default tap handler --- apps/ios/Shared/Model/AppAPITypes.swift | 19 - .../Views/Chat/ChatItem/CICallItemView.swift | 20 +- .../ChatItem/CIFeaturePreferenceView.swift | 4 +- .../Views/Chat/ChatItem/CIFileView.swift | 50 +-- .../Chat/ChatItem/CIGroupInvitationView.swift | 4 +- .../Views/Chat/ChatItem/CIImageView.swift | 6 +- .../Chat/ChatItem/CIInvalidJSONView.swift | 4 +- .../Views/Chat/ChatItem/CILinkView.swift | 22 ++ .../ChatItem/CIMemberCreatedContactView.swift | 4 +- .../Views/Chat/ChatItem/CIMetaView.swift | 4 +- .../Chat/ChatItem/CIRcvDecryptionError.swift | 4 +- .../Views/Chat/ChatItem/CIVideoView.swift | 146 ++++---- .../Views/Chat/ChatItem/CIVoiceView.swift | 101 ++---- .../Views/Chat/ChatItem/FramedItemView.swift | 40 +-- .../Chat/ChatItem/FullScreenMediaView.swift | 2 +- .../ChatItem/IntegrityErrorItemView.swift | 2 +- .../Views/Chat/ChatItem/MsgContentView.swift | 340 ++++++++++++------ .../Shared/Views/Chat/ChatItemInfoView.swift | 5 +- .../Chat/ComposeMessage/ContextItemView.swift | 2 +- .../Chat/ComposeMessage/SendMessageView.swift | 17 +- .../Views/Chat/Group/GroupWelcomeView.swift | 4 +- .../Views/ChatList/ChatPreviewView.swift | 23 +- .../Shared/Views/Helpers/ViewModifiers.swift | 1 + .../Views/UserSettings/AppSettings.swift | 2 - .../Views/UserSettings/PrivacySettings.swift | 12 - .../Views/UserSettings/SettingsView.swift | 3 - apps/ios/SimpleXChat/ChatTypes.swift | 30 +- src/Simplex/Chat/AppSettings.hs | 10 - 28 files changed, 461 insertions(+), 420 deletions(-) diff --git a/apps/ios/Shared/Model/AppAPITypes.swift b/apps/ios/Shared/Model/AppAPITypes.swift index fd89026cfe..3bf4cb7b56 100644 --- a/apps/ios/Shared/Model/AppAPITypes.swift +++ b/apps/ios/Shared/Model/AppAPITypes.swift @@ -1413,22 +1413,6 @@ enum NotificationsMode: String, Decodable, SelectableItem { static var values: [NotificationsMode] = [.instant, .periodic, .off] } -enum PrivacyChatListOpenLinksMode: String, CaseIterable, Codable, RawRepresentable, Identifiable { - case yes - case no - case ask - - var id: Self { self } - - var text: LocalizedStringKey { - switch self { - case .yes: return "Yes" - case .no: return "No" - case .ask: return "Ask" - } - } -} - struct RemoteCtrlInfo: Decodable { var remoteCtrlId: Int64 var ctrlDeviceName: String @@ -1941,7 +1925,6 @@ struct AppSettings: Codable, Equatable { var privacyAskToApproveRelays: Bool? = nil var privacyAcceptImages: Bool? = nil var privacyLinkPreviews: Bool? = nil - var privacyChatListOpenLinks: PrivacyChatListOpenLinksMode? = nil var privacyShowChatPreviews: Bool? = nil var privacySaveLastDraft: Bool? = nil var privacyProtectScreen: Bool? = nil @@ -1977,7 +1960,6 @@ struct AppSettings: Codable, Equatable { if privacyAskToApproveRelays != def.privacyAskToApproveRelays { empty.privacyAskToApproveRelays = privacyAskToApproveRelays } if privacyAcceptImages != def.privacyAcceptImages { empty.privacyAcceptImages = privacyAcceptImages } if privacyLinkPreviews != def.privacyLinkPreviews { empty.privacyLinkPreviews = privacyLinkPreviews } - if privacyChatListOpenLinks != def.privacyChatListOpenLinks { empty.privacyChatListOpenLinks = privacyChatListOpenLinks } if privacyShowChatPreviews != def.privacyShowChatPreviews { empty.privacyShowChatPreviews = privacyShowChatPreviews } if privacySaveLastDraft != def.privacySaveLastDraft { empty.privacySaveLastDraft = privacySaveLastDraft } if privacyProtectScreen != def.privacyProtectScreen { empty.privacyProtectScreen = privacyProtectScreen } @@ -2014,7 +1996,6 @@ struct AppSettings: Codable, Equatable { privacyAskToApproveRelays: true, privacyAcceptImages: true, privacyLinkPreviews: true, - privacyChatListOpenLinks: .ask, privacyShowChatPreviews: true, privacySaveLastDraft: true, privacyProtectScreen: false, diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CICallItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CICallItemView.swift index 3b3e1b3899..024aeed96a 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CICallItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CICallItemView.swift @@ -60,16 +60,16 @@ struct CICallItemView: View { @ViewBuilder private func acceptCallButton() -> some View { if case let .direct(contact) = chat.chatInfo { - Button { - if let invitation = m.callInvitations[contact.id] { - CallController.shared.answerCall(invitation: invitation) - logger.debug("acceptCallButton call answered") - } else { - AlertManager.shared.showAlertMsg(title: "Call already ended!") - } - } label: { - Label("Answer call", systemImage: "phone.arrow.down.left") - } + Label("Answer call", systemImage: "phone.arrow.down.left") + .foregroundColor(theme.colors.primary) + .simultaneousGesture(TapGesture().onEnded { + if let invitation = m.callInvitations[contact.id] { + CallController.shared.answerCall(invitation: invitation) + logger.debug("acceptCallButton call answered") + } else { + AlertManager.shared.showAlertMsg(title: "Call already ended!") + } + }) } else { Image(systemName: "phone.arrow.down.left").foregroundColor(theme.colors.secondary) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIFeaturePreferenceView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIFeaturePreferenceView.swift index 2c9c261536..67f7b69e2c 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIFeaturePreferenceView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIFeaturePreferenceView.swift @@ -26,9 +26,9 @@ struct CIFeaturePreferenceView: View { allowed != .no && ct.allowsFeature(feature) && !ct.userAllowsFeature(feature) { let setParam = feature == .timedMessages && ct.mergedPreferences.timedMessages.userPreference.preference.ttl == nil featurePreferenceView(acceptText: setParam ? "Set 1 day" : "Accept") - .onTapGesture { + .simultaneousGesture(TapGesture().onEnded { allowFeatureToContact(ct, feature, param: setParam ? 86400 : nil) - } + }) } else { featurePreferenceView() } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift index bc44afdd7f..b0b404d8b5 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift @@ -19,42 +19,42 @@ struct CIFileView: View { var body: some View { if smallViewSize != nil { fileIndicator() - .onTapGesture(perform: fileAction) + .simultaneousGesture(TapGesture().onEnded(fileAction)) } else { let metaReserve = edited ? " " : " " - Button(action: fileAction) { - HStack(alignment: .bottom, spacing: 6) { - fileIndicator() - .padding(.top, 5) - .padding(.bottom, 3) - if let file = file { - let prettyFileSize = ByteCountFormatter.string(fromByteCount: file.fileSize, countStyle: .binary) - VStack(alignment: .leading, spacing: 2) { - Text(file.fileName) - .lineLimit(1) - .multilineTextAlignment(.leading) - .foregroundColor(theme.colors.onBackground) - Text(prettyFileSize + metaReserve) - .font(.caption) - .lineLimit(1) - .multilineTextAlignment(.leading) - .foregroundColor(theme.colors.secondary) - } - } else { - Text(metaReserve) + HStack(alignment: .bottom, spacing: 6) { + fileIndicator() + .padding(.top, 5) + .padding(.bottom, 3) + if let file = file { + let prettyFileSize = ByteCountFormatter.string(fromByteCount: file.fileSize, countStyle: .binary) + VStack(alignment: .leading, spacing: 2) { + Text(file.fileName) + .lineLimit(1) + .multilineTextAlignment(.leading) + .foregroundColor(theme.colors.onBackground) + Text(prettyFileSize + metaReserve) + .font(.caption) + .lineLimit(1) + .multilineTextAlignment(.leading) + .foregroundColor(theme.colors.secondary) } + } else { + Text(metaReserve) } - .padding(.top, 4) - .padding(.bottom, 6) - .padding(.leading, 10) - .padding(.trailing, 12) } + .padding(.top, 4) + .padding(.bottom, 6) + .padding(.leading, 10) + .padding(.trailing, 12) + .simultaneousGesture(TapGesture().onEnded(fileAction)) .disabled(!itemInteractive) } } + @inline(__always) private var itemInteractive: Bool { if let file = file { switch (file.fileStatus) { diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift index 107208a033..3fcf578875 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift @@ -84,12 +84,12 @@ struct CIGroupInvitationView: View { } if action { - v.onTapGesture { + v.simultaneousGesture(TapGesture().onEnded { inProgress = true joinGroup(groupInvitation.groupId) { await MainActor.run { inProgress = false } } - } + }) .disabled(inProgress) } else { v diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift index 4c8221d887..ba6a2bd200 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift @@ -31,7 +31,7 @@ struct CIImageView: View { .if(!smallView) { view in view.modifier(PrivacyBlur(blurred: $blurred)) } - .onTapGesture { showFullScreenImage = true } + .simultaneousGesture(TapGesture().onEnded { showFullScreenImage = true }) .onChange(of: m.activeCallViewIsCollapsed) { _ in showFullScreenImage = false } @@ -43,7 +43,7 @@ struct CIImageView: View { imageView(preview).modifier(PrivacyBlur(blurred: $blurred)) } } - .onTapGesture { + .simultaneousGesture(TapGesture().onEnded { if let file = file { switch file.fileStatus { case .rcvInvitation, .rcvAborted: @@ -80,7 +80,7 @@ struct CIImageView: View { default: () } } - } + }) } } .onDisappear { diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift index 918d8f9449..5e9fa691de 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift @@ -24,7 +24,7 @@ struct CIInvalidJSONView: View { .padding(.vertical, 6) .background(Color(uiColor: .tertiarySystemGroupedBackground)) .textSelection(.disabled) - .onTapGesture { showJSON = true } + .simultaneousGesture(TapGesture().onEnded { showJSON = true }) .appSheet(isPresented: $showJSON) { invalidJSONView(dataToString(json)) } @@ -33,7 +33,7 @@ struct CIInvalidJSONView: View { func invalidJSONView(_ json: String) -> some View { VStack(alignment: .leading, spacing: 16) { - Button { + Button { // this is used in the sheet, Button works here showShareSheet(items: [json]) } label: { Image(systemName: "square.and.arrow.up") diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift index 692e6bb8a6..4f879db426 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift @@ -38,9 +38,31 @@ struct CILinkView: View { .padding(.horizontal, 12) .frame(maxWidth: .infinity, alignment: .leading) } + .simultaneousGesture(TapGesture().onEnded { + openBrowserAlert(uri: linkPreview.uri) + }) } } +func openBrowserAlert(uri: URL) { + showAlert( + NSLocalizedString("Open in browser?", comment: "alert title"), + message: uri.absoluteString, + actions: {[ + UIAlertAction( + title: NSLocalizedString("Cancel", comment: "alert action"), + style: .default, + handler: { _ in } + ), + UIAlertAction( + title: NSLocalizedString("Open", comment: "alert action"), + style: .default, + handler: { _ in UIApplication.shared.open(uri) } + ) + ]} + ) +} + struct LargeLinkPreview_Previews: PreviewProvider { static var previews: some View { let preview = LinkPreview( diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift index e49e99c77e..2898a318a9 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift @@ -20,11 +20,11 @@ struct CIMemberCreatedContactView: View { case let .groupRcv(groupMember): if let contactId = groupMember.memberContactId { memberCreatedContactView(openText: "Open") - .onTapGesture { + .simultaneousGesture(TapGesture().onEnded { ItemsModel.shared.loadOpenChat("@\(contactId)") { dismissAllSheets(animated: true) } - } + }) } else { memberCreatedContactView() } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift index e58ad0f74e..fc73778239 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift @@ -15,7 +15,7 @@ struct CIMetaView: View { @Environment(\.showTimestamp) var showTimestamp: Bool var chatItem: ChatItem var metaColor: Color - var paleMetaColor = Color(UIColor.tertiaryLabel) + var paleMetaColor = Color(uiColor: .tertiaryLabel) var showStatus = true var showEdited = true var invertedMaterial = false @@ -152,11 +152,13 @@ func ciMetaText( return r.font(.caption) } +@inline(__always) private func statusIconText(_ icon: String, _ color: Color?) -> Text { colored(Text(Image(systemName: icon)), color) } // Applying `foregroundColor(nil)` breaks `.invertedForegroundStyle` modifier +@inline(__always) private func colored(_ t: Text, _ color: Color?) -> Text { if let color { t.foregroundColor(color) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift index 6920ab999b..e26fb62a71 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift @@ -133,7 +133,7 @@ struct CIRcvDecryptionError: View { CIMetaView(chat: chat, chatItem: chatItem, metaColor: theme.colors.secondary) .padding(.horizontal, 12) } - .onTapGesture(perform: { onClick() }) + .simultaneousGesture(TapGesture().onEnded(onClick)) .padding(.vertical, 6) .textSelection(.disabled) } @@ -151,7 +151,7 @@ struct CIRcvDecryptionError: View { CIMetaView(chat: chat, chatItem: chatItem, metaColor: theme.colors.secondary) .padding(.horizontal, 12) } - .onTapGesture(perform: { onClick() }) + .simultaneousGesture(TapGesture().onEnded(onClick)) .padding(.vertical, 6) .textSelection(.disabled) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift index f774299ad3..74bce5e583 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift @@ -47,57 +47,57 @@ struct CIVideoView: View { let file = chatItem.file ZStack(alignment: smallView ? .topLeading : .center) { ZStack(alignment: .topLeading) { - if let file = file, let preview = preview, let decrypted = urlDecrypted, smallView { - smallVideoView(decrypted, file, preview) - } else if let file = file, let preview = preview, let player = player, let decrypted = urlDecrypted { - videoView(player, decrypted, file, preview, duration) - } else if let file = file, let defaultPreview = preview, file.loaded && urlDecrypted == nil, smallView { - smallVideoViewEncrypted(file, defaultPreview) - } else if let file = file, let defaultPreview = preview, file.loaded && urlDecrypted == nil { - videoViewEncrypted(file, defaultPreview, duration) - } else if let preview, let file { - Group { if smallView { smallViewImageView(preview, file) } else { imageView(preview) } } - .onTapGesture { - switch file.fileStatus { - case .rcvInvitation, .rcvAborted: - receiveFileIfValidSize(file: file, receiveFile: receiveFile) - case .rcvAccepted: - switch file.fileProtocol { - case .xftp: - AlertManager.shared.showAlertMsg( - title: "Waiting for video", - message: "Video will be received when your contact completes uploading it." - ) - case .smp: - AlertManager.shared.showAlertMsg( - title: "Waiting for video", - message: "Video will be received when your contact is online, please wait or check later!" - ) - case .local: () - } - case .rcvTransfer: () // ? - case .rcvComplete: () // ? - case .rcvCancelled: () // TODO - default: () - } + if let file, let preview { + if let urlDecrypted { + if smallView { + smallVideoView(urlDecrypted, file, preview) + } else if let player { + videoView(player, urlDecrypted, file, preview, duration) } + } else if file.loaded { + if smallView { + smallVideoViewEncrypted(file, preview) + } else { + videoViewEncrypted(file, preview, duration) + } + } else { + Group { if smallView { smallViewImageView(preview, file) } else { imageView(preview) } } + .simultaneousGesture(TapGesture().onEnded { + switch file.fileStatus { + case .rcvInvitation, .rcvAborted: + receiveFileIfValidSize(file: file, receiveFile: receiveFile) + case .rcvAccepted: + switch file.fileProtocol { + case .xftp: + AlertManager.shared.showAlertMsg( + title: "Waiting for video", + message: "Video will be received when your contact completes uploading it." + ) + case .smp: + AlertManager.shared.showAlertMsg( + title: "Waiting for video", + message: "Video will be received when your contact is online, please wait or check later!" + ) + case .local: () + } + case .rcvTransfer: () // ? + case .rcvComplete: () // ? + case .rcvCancelled: () // TODO + default: () + } + }) + } } if !smallView { durationProgress() } } if !blurred, let file, showDownloadButton(file.fileStatus) { - if !smallView { - Button { - receiveFileIfValidSize(file: file, receiveFile: receiveFile) - } label: { - playPauseIcon("play.fill") - } - } else if !file.showStatusIconInSmallView { + if !smallView || !file.showStatusIconInSmallView { playPauseIcon("play.fill") - .onTapGesture { + .simultaneousGesture(TapGesture().onEnded { receiveFileIfValidSize(file: file, receiveFile: receiveFile) - } + }) } } } @@ -151,27 +151,26 @@ struct CIVideoView: View { ZStack(alignment: .center) { let canBePlayed = !chatItem.chatDir.sent || file.fileStatus == CIFileStatus.sndComplete || (file.fileStatus == .sndStored && file.fileProtocol == .local) imageView(defaultPreview) - .onTapGesture { + .simultaneousGesture(TapGesture().onEnded { decrypt(file: file) { showFullScreenPlayer = urlDecrypted != nil } - } + }) .onChange(of: m.activeCallViewIsCollapsed) { _ in showFullScreenPlayer = false } if !blurred { if !decryptionInProgress { - Button { - decrypt(file: file) { - if urlDecrypted != nil { - videoPlaying = true - player?.play() + playPauseIcon(canBePlayed ? "play.fill" : "play.slash") + .simultaneousGesture(TapGesture().onEnded { + decrypt(file: file) { + if urlDecrypted != nil { + videoPlaying = true + player?.play() + } } - } - } label: { - playPauseIcon(canBePlayed ? "play.fill" : "play.slash") - } - .disabled(!canBePlayed) + }) + .disabled(!canBePlayed) } else { videoDecryptionProgress() } @@ -194,7 +193,7 @@ struct CIVideoView: View { } } .modifier(PrivacyBlur(enabled: !videoPlaying, blurred: $blurred)) - .onTapGesture { + .simultaneousGesture(TapGesture().onEnded { switch player.timeControlStatus { case .playing: player.pause() @@ -205,18 +204,17 @@ struct CIVideoView: View { } default: () } - } + }) .onChange(of: m.activeCallViewIsCollapsed) { _ in showFullScreenPlayer = false } if !videoPlaying && !blurred { - Button { - m.stopPreviousRecPlay = url - player.play() - } label: { - playPauseIcon(canBePlayed ? "play.fill" : "play.slash") - } - .disabled(!canBePlayed) + playPauseIcon(canBePlayed ? "play.fill" : "play.slash") + .simultaneousGesture(TapGesture().onEnded { + m.stopPreviousRecPlay = url + player.play() + }) + .disabled(!canBePlayed) } } fileStatusIcon() @@ -235,7 +233,7 @@ struct CIVideoView: View { return ZStack(alignment: .topLeading) { let canBePlayed = !chatItem.chatDir.sent || file.fileStatus == CIFileStatus.sndComplete || (file.fileStatus == .sndStored && file.fileProtocol == .local) smallViewImageView(preview, file) - .onTapGesture { + .onTapGesture { // this is shown in chat list, where onTapGesture works decrypt(file: file) { showFullScreenPlayer = urlDecrypted != nil } @@ -256,7 +254,7 @@ struct CIVideoView: View { private func smallVideoView(_ url: URL, _ file: CIFile, _ preview: UIImage) -> some View { return ZStack(alignment: .topLeading) { smallViewImageView(preview, file) - .onTapGesture { + .onTapGesture { // this is shown in chat list, where onTapGesture works showFullScreenPlayer = true } .onChange(of: m.activeCallViewIsCollapsed) { _ in @@ -354,14 +352,14 @@ struct CIVideoView: View { case .sndCancelled: fileIcon("xmark", 10, 13) case let .sndError(sndFileError): fileIcon("xmark", 10, 13) - .onTapGesture { + .simultaneousGesture(TapGesture().onEnded { showFileErrorAlert(sndFileError) - } + }) case let .sndWarning(sndFileError): fileIcon("exclamationmark.triangle.fill", 10, 13) - .onTapGesture { + .simultaneousGesture(TapGesture().onEnded { showFileErrorAlert(sndFileError, temporary: true) - } + }) case .rcvInvitation: fileIcon("arrow.down", 10, 13) case .rcvAccepted: fileIcon("ellipsis", 14, 11) case let .rcvTransfer(rcvProgress, rcvTotal): @@ -375,14 +373,14 @@ struct CIVideoView: View { case .rcvCancelled: fileIcon("xmark", 10, 13) case let .rcvError(rcvFileError): fileIcon("xmark", 10, 13) - .onTapGesture { + .simultaneousGesture(TapGesture().onEnded { showFileErrorAlert(rcvFileError) - } + }) case let .rcvWarning(rcvFileError): fileIcon("exclamationmark.triangle.fill", 10, 13) - .onTapGesture { + .simultaneousGesture(TapGesture().onEnded { showFileErrorAlert(rcvFileError, temporary: true) - } + }) case .invalid: fileIcon("questionmark", 10, 13) } } @@ -429,7 +427,7 @@ struct CIVideoView: View { Color.black.edgesIgnoringSafeArea(.all) VideoPlayer(player: fullPlayer) .overlay(alignment: .topLeading, content: { - Button(action: { showFullScreenPlayer = false }, + Button(action: { showFullScreenPlayer = false }, // this is used in full screen player, Button works here label: { Image(systemName: "multiply") .resizable() diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift index e2fec02ba1..715e606a74 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift @@ -168,14 +168,14 @@ struct VoiceMessagePlayer: View { case .sndCancelled: playbackButton() case let .sndError(sndFileError): fileStatusIcon("multiply", 14) - .onTapGesture { + .simultaneousGesture(TapGesture().onEnded { showFileErrorAlert(sndFileError) - } + }) case let .sndWarning(sndFileError): fileStatusIcon("exclamationmark.triangle.fill", 16) - .onTapGesture { + .simultaneousGesture(TapGesture().onEnded { showFileErrorAlert(sndFileError, temporary: true) - } + }) case .rcvInvitation: downloadButton(recordingFile, "play.fill") case .rcvAccepted: loadingIcon() case .rcvTransfer: loadingIcon() @@ -184,14 +184,14 @@ struct VoiceMessagePlayer: View { case .rcvCancelled: playPauseIcon("play.fill", Color(uiColor: .tertiaryLabel)) case let .rcvError(rcvFileError): fileStatusIcon("multiply", 14) - .onTapGesture { + .simultaneousGesture(TapGesture().onEnded { showFileErrorAlert(rcvFileError) - } + }) case let .rcvWarning(rcvFileError): fileStatusIcon("exclamationmark.triangle.fill", 16) - .onTapGesture { + .simultaneousGesture(TapGesture().onEnded { showFileErrorAlert(rcvFileError, temporary: true) - } + }) case .invalid: playPauseIcon("play.fill", Color(uiColor: .tertiaryLabel)) } } else { @@ -255,59 +255,29 @@ struct VoiceMessagePlayer: View { } } - @ViewBuilder private func playbackButton() -> some View { - if sizeMultiplier != 1 { - switch playbackState { - case .noPlayback: - playPauseIcon("play.fill", theme.colors.primary) - .onTapGesture { - if let recordingSource = getLoadedFileSource(recordingFile) { - startPlayback(recordingSource) - } - } - case .playing: - playPauseIcon("pause.fill", theme.colors.primary) - .onTapGesture { - audioPlayer?.pause() - playbackState = .paused - notifyStateChange() - } - case .paused: - playPauseIcon("play.fill", theme.colors.primary) - .onTapGesture { - audioPlayer?.play() - playbackState = .playing - notifyStateChange() - } - } - } else { - switch playbackState { - case .noPlayback: - Button { + private func playbackButton() -> some View { + let icon = switch playbackState { + case .noPlayback: "play.fill" + case .playing: "pause.fill" + case .paused: "play.fill" + } + return playPauseIcon(icon, theme.colors.primary) + .simultaneousGesture(TapGesture().onEnded { _ in + switch playbackState { + case .noPlayback: if let recordingSource = getLoadedFileSource(recordingFile) { startPlayback(recordingSource) } - } label: { - playPauseIcon("play.fill", theme.colors.primary) - } - case .playing: - Button { + case .playing: audioPlayer?.pause() playbackState = .paused notifyStateChange() - } label: { - playPauseIcon("pause.fill", theme.colors.primary) - } - case .paused: - Button { + case .paused: audioPlayer?.play() playbackState = .playing notifyStateChange() - } label: { - playPauseIcon("play.fill", theme.colors.primary) } - } - } + }) } private func playPauseIcon(_ image: String, _ color: Color/* = .accentColor*/) -> some View { @@ -329,28 +299,14 @@ struct VoiceMessagePlayer: View { } private func downloadButton(_ recordingFile: CIFile, _ icon: String) -> some View { - Group { - if sizeMultiplier != 1 { - playPauseIcon(icon, theme.colors.primary) - .onTapGesture { - Task { - if let user = chatModel.currentUser { - await receiveFile(user: user, fileId: recordingFile.fileId) - } - } + playPauseIcon(icon, theme.colors.primary) + .simultaneousGesture(TapGesture().onEnded { + Task { + if let user = chatModel.currentUser { + await receiveFile(user: user, fileId: recordingFile.fileId) } - } else { - Button { - Task { - if let user = chatModel.currentUser { - await receiveFile(user: user, fileId: recordingFile.fileId) - } - } - } label: { - playPauseIcon(icon, theme.colors.primary) } - } - } + }) } func notifyStateChange() { @@ -430,6 +386,7 @@ struct VoiceMessagePlayer: View { } } +@inline(__always) func voiceMessageSizeBasedOnSquareSize(_ squareSize: CGFloat) -> CGFloat { let squareToCircleRatio = 0.935 return squareSize + squareSize * (1 - squareToCircleRatio) @@ -446,10 +403,12 @@ class VoiceItemState { self.playbackTime = playbackTime } + @inline(__always) static func id(_ chat: Chat, _ chatItem: ChatItem) -> String { "\(chat.id) \(chatItem.id)" } + @inline(__always) static func id(_ chatInfo: ChatInfo, _ chatItem: ChatItem) -> String { "\(chatInfo.id) \(chatItem.id)" } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift index e411befbfa..632d4196c2 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift @@ -23,8 +23,6 @@ struct FramedItemView: View { @State private var useWhiteMetaColor: Bool = false @State var showFullScreenImage = false @Binding var allowMenu: Bool - @State private var showSecrets = false - @State private var showQuoteSecrets = false @State private var showFullscreenGallery: Bool = false var body: some View { @@ -57,7 +55,7 @@ struct FramedItemView: View { if let qi = chatItem.quotedItem { ciQuoteView(qi) - .onTapGesture { + .simultaneousGesture(TapGesture().onEnded { if let ci = ItemsModel.shared.reversedChatItems.first(where: { $0.id == qi.itemId }) { withAnimation { scrollToItemId(ci.id) @@ -67,7 +65,7 @@ struct FramedItemView: View { } else { showQuotedItemDoesNotExistAlert() } - } + }) } else if let itemForwarded = chatItem.meta.itemForwarded { framedItemHeader(icon: "arrowshape.turn.up.forward", caption: Text(itemForwarded.text(chat.chatInfo.chatType)).italic(), pad: true) } @@ -94,14 +92,14 @@ struct FramedItemView: View { .onPreferenceChange(DetermineWidth.Key.self) { msgWidth = $0 } if let (title, text) = chatItem.meta.itemStatus.statusInfo { - v.onTapGesture { + v.simultaneousGesture(TapGesture().onEnded { AlertManager.shared.showAlert( Alert( title: Text(title), message: Text(text) ) ) - } + }) } else { v } @@ -159,7 +157,7 @@ struct FramedItemView: View { case let .file(text): ciFileView(chatItem, text) case let .report(text, reason): - ciMsgContentView(chatItem, Text(text.isEmpty ? reason.text : "\(reason.text): ").italic().foregroundColor(.red)) + ciMsgContentView(chatItem, txtPrefix: reason.attrString) case let .link(_, preview): CILinkView(linkPreview: preview) ciMsgContentView(chatItem) @@ -270,14 +268,12 @@ struct FramedItemView: View { .padding(.top, 6) .padding(.horizontal, 12) } - + + @inline(__always) private func ciQuotedMsgTextView(_ qi: CIQuote, lines: Int) -> some View { - toggleSecrets(qi.formattedText, $showQuoteSecrets, - MsgContentView(chat: chat, text: qi.text, formattedText: qi.formattedText, showSecrets: showQuoteSecrets) - .lineLimit(lines) - .font(.subheadline) - .padding(.bottom, 6) - ) + MsgContentView(chat: chat, text: qi.text, formattedText: qi.formattedText, textStyle: .subheadline) + .lineLimit(lines) + .padding(.bottom, 6) } private func ciQuoteIconView(_ image: String) -> some View { @@ -297,21 +293,21 @@ struct FramedItemView: View { } } - @ViewBuilder private func ciMsgContentView(_ ci: ChatItem, _ txtPrefix: Text? = nil) -> some View { + @ViewBuilder private func ciMsgContentView(_ ci: ChatItem, txtPrefix: NSAttributedString? = nil) -> some View { let text = ci.meta.isLive ? ci.content.msgContent?.text ?? ci.text : ci.text let rtl = isRightToLeft(text) let ft = text == "" ? [] : ci.formattedText - let v = toggleSecrets(ft, $showSecrets, MsgContentView( + let v = MsgContentView( chat: chat, text: text, formattedText: ft, + textStyle: .body, meta: ci.meta, mentions: ci.mentions, userMemberId: chat.chatInfo.groupInfo?.membership.memberId, rightToLeft: rtl, - showSecrets: showSecrets, prefix: txtPrefix - )) + ) .multilineTextAlignment(rtl ? .trailing : .leading) .padding(.vertical, 6) .padding(.horizontal, 12) @@ -351,14 +347,6 @@ struct FramedItemView: View { } } -@ViewBuilder func toggleSecrets(_ ft: [FormattedText]?, _ showSecrets: Binding, _ v: V) -> some View { - if let ft = ft, ft.contains(where: { $0.isSecret }) { - v.onTapGesture { showSecrets.wrappedValue.toggle() } - } else { - v - } -} - func isRightToLeft(_ s: String) -> Bool { if let lang = CFStringTokenizerCopyBestStringLanguage(s as CFString, CFRange(location: 0, length: min(s.count, 80))) { return NSLocale.characterDirection(forLanguage: lang as String) == .rightToLeft diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift index c3aad9490d..10e5efa298 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift @@ -126,7 +126,7 @@ struct FullScreenMediaView: View { .scaledToFit() } } - .onTapGesture { showView = false } + .onTapGesture { showView = false } // this is used in full screen view, onTapGesture works } private func videoView( _ player: AVPlayer, _ url: URL) -> some View { diff --git a/apps/ios/Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift index c6d1afa04a..47a30f6cf3 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift @@ -71,7 +71,7 @@ struct CIMsgError: View { .padding(.vertical, 6) .background { chatItemFrameColor(chatItem, theme).modifier(ChatTailPadding()) } .textSelection(.disabled) - .onTapGesture(perform: onTap) + .simultaneousGesture(TapGesture().onEnded(onTap)) } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift index d82b8314a7..d8dbd673f4 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift @@ -11,17 +11,16 @@ import SimpleXChat let uiLinkColor = UIColor(red: 0, green: 0.533, blue: 1, alpha: 1) -private let noTyping = Text(verbatim: " ") - -private let typingIndicators: [Text] = [ - (typing(.black) + typing() + typing()), - (typing(.bold) + typing(.black) + typing()), - (typing() + typing(.bold) + typing(.black)), - (typing() + typing() + typing(.bold)) -] - -private func typing(_ w: Font.Weight = .light) -> Text { - Text(verbatim: ".").fontWeight(w) +private func typing(_ theme: AppTheme, _ descr: UIFontDescriptor, _ ws: [UIFont.Weight]) -> NSMutableAttributedString { + let res = NSMutableAttributedString() + for w in ws { + res.append(NSAttributedString(string: ".", attributes: [ + .font: UIFont.monospacedSystemFont(ofSize: descr.pointSize, weight: w), + .kern: -2 as NSNumber, + .foregroundColor: UIColor(theme.colors.secondary) + ])) + } + return res } struct MsgContentView: View { @@ -30,34 +29,55 @@ struct MsgContentView: View { @EnvironmentObject var theme: AppTheme var text: String var formattedText: [FormattedText]? = nil + var textStyle: UIFont.TextStyle var sender: String? = nil var meta: CIMeta? = nil var mentions: [String: CIMention]? = nil var userMemberId: String? = nil var rightToLeft = false - var showSecrets: Bool - var prefix: Text? = nil + var prefix: NSAttributedString? = nil + @State private var showSecrets: Set = [] @State private var typingIdx = 0 @State private var timer: Timer? + @State private var typingIndicators: [NSAttributedString] = [] + @State private var noTyping = NSAttributedString(string: " ") + @State private var phase: CGFloat = 0 @AppStorage(DEFAULT_SHOW_SENT_VIA_RPOXY) private var showSentViaProxy = false var body: some View { + let v = msgContentView() if meta?.isLive == true { - msgContentView() - .onAppear { switchTyping() } + v.onAppear { + let descr = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body) + noTyping = NSAttributedString(string: " ", attributes: [ + .font: UIFont.monospacedSystemFont(ofSize: descr.pointSize, weight: .regular), + .kern: -2 as NSNumber, + .foregroundColor: UIColor(theme.colors.secondary) + ]) + switchTyping() + } .onDisappear(perform: stopTyping) .onChange(of: meta?.isLive, perform: switchTyping) .onChange(of: meta?.recent, perform: switchTyping) } else { - msgContentView() + v } } private func switchTyping(_: Bool? = nil) { if let meta = meta, meta.isLive && meta.recent { + if typingIndicators.isEmpty { + let descr = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body) + typingIndicators = [ + typing(theme, descr, [.black, .light, .light]), + typing(theme, descr, [.bold, .black, .light]), + typing(theme, descr, [.light, .bold, .black]), + typing(theme, descr, [.light, .light, .bold]) + ] + } timer = timer ?? Timer.scheduledTimer(withTimeInterval: 0.25, repeats: true) { _ in - typingIdx = (typingIdx + 1) % typingIndicators.count + typingIdx = typingIdx + 1 } } else { stopTyping() @@ -67,119 +87,221 @@ struct MsgContentView: View { private func stopTyping() { timer?.invalidate() timer = nil + typingIdx = 0 } - private func msgContentView() -> Text { - var v = messageText(text, formattedText, sender, mentions: mentions, userMemberId: userMemberId, showSecrets: showSecrets, secondaryColor: theme.colors.secondary, prefix: prefix) + @inline(__always) + private func msgContentView() -> some View { + let s = messageText(text, formattedText, textStyle: textStyle, sender: sender, mentions: mentions, userMemberId: userMemberId, showSecrets: showSecrets, secondaryColor: theme.colors.secondary, prefix: prefix) + let t: Text if let mt = meta { if mt.isLive { - v = v + typingIndicator(mt.recent) + s.append(typingIndicator(mt.recent)) } - v = v + reserveSpaceForMeta(mt) + t = Text(AttributedString(s)) + reserveSpaceForMeta(mt) + } else { + t = Text(AttributedString(s)) } - return v + return t.overlay(handleTextLinks(s, showSecrets: $showSecrets)) } - private func typingIndicator(_ recent: Bool) -> Text { - return (recent ? typingIndicators[typingIdx] : noTyping) - .font(.body.monospaced()) - .kerning(-2) - .foregroundColor(theme.colors.secondary) + @inline(__always) + private func typingIndicator(_ recent: Bool) -> NSAttributedString { + recent && !typingIndicators.isEmpty + ? typingIndicators[typingIdx % 4] + : noTyping } + @inline(__always) private func reserveSpaceForMeta(_ mt: CIMeta) -> Text { (rightToLeft ? textNewLine : Text(verbatim: " ")) + ciMetaText(mt, chatTTL: chat.chatInfo.timedMessagesTTL, encrypted: nil, colorMode: .transparent, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) } } -func messageText(_ text: String, _ formattedText: [FormattedText]?, _ sender: String?, icon: String? = nil, preview: Bool = false, mentions: [String: CIMention]?, userMemberId: String?, showSecrets: Bool, secondaryColor: Color, prefix: Text? = nil) -> Text { - let s = text - var res: Text - - if let ft = formattedText, ft.count > 0 && ft.count <= 200 { - res = formatText(ft[0], preview, showSecret: showSecrets, mentions: mentions, userMemberId: userMemberId) - var i = 1 - while i < ft.count { - res = res + formatText(ft[i], preview, showSecret: showSecrets, mentions: mentions, userMemberId: userMemberId) - i = i + 1 - } - } else { - res = Text(s) - } - - if let i = icon { - res = Text(Image(systemName: i)).foregroundColor(secondaryColor) + textSpace + res - } - - if let p = prefix { - res = p + res - } - - if let s = sender { - let t = Text(s) - return (preview ? t : t.fontWeight(.medium)) + Text(verbatim: ": ") + res - } else { - return res - } -} - -private func formatText(_ ft: FormattedText, _ preview: Bool, showSecret: Bool, mentions: [String: CIMention]?, userMemberId: String?) -> Text { - let t = ft.text - if let f = ft.format { - switch (f) { - case .bold: return Text(t).bold() - case .italic: return Text(t).italic() - case .strikeThrough: return Text(t).strikethrough() - case .snippet: return Text(t).font(.body.monospaced()) - case .secret: return - showSecret - ? Text(t) - : Text(AttributedString(t, attributes: AttributeContainer([ - .foregroundColor: UIColor.clear as Any, - .backgroundColor: UIColor.secondarySystemFill as Any - ]))) - case let .colored(color): return Text(t).foregroundColor(color.uiColor) - case .uri: return linkText(t, t, preview, prefix: "") - case let .simplexLink(linkType, simplexUri, smpHosts): - switch privacySimplexLinkModeDefault.get() { - case .description: return linkText(simplexLinkText(linkType, smpHosts), simplexUri, preview, prefix: "") - case .full: return linkText(t, simplexUri, preview, prefix: "") - case .browser: return linkText(t, simplexUri, preview, prefix: "") - } - case let .mention(memberName): - if let m = mentions?[memberName] { - if let ref = m.memberRef { - let name: String = if let alias = ref.localAlias, alias != "" { - "\(alias) (\(ref.displayName))" - } else { - ref.displayName +func handleTextLinks(_ s: NSAttributedString, showSecrets: Binding>? = nil) -> some View { + return GeometryReader { g in + Rectangle() + .fill(Color.clear) + .contentShape(Rectangle()) + .simultaneousGesture(DragGesture(minimumDistance: 0).onEnded { event in + let t = event.translation + if t.width * t.width + t.height * t.height > 100 { return } + let framesetter = CTFramesetterCreateWithAttributedString(s as CFAttributedString) + let path = CGPath(rect: CGRect(origin: .zero, size: g.size), transform: nil) + let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, s.length), path, nil) + let point = CGPoint(x: event.location.x, y: g.size.height - event.location.y) // Flip y for UIKit + var index: CFIndex? + if let lines = CTFrameGetLines(frame) as? [CTLine] { + var origins = [CGPoint](repeating: .zero, count: lines.count) + CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), &origins) + for i in 0 ..< lines.count { + let bounds = CTLineGetBoundsWithOptions(lines[i], .useOpticalBounds) + if bounds.offsetBy(dx: origins[i].x, dy: origins[i].y).contains(point) { + index = CTLineGetStringIndexForPosition(lines[i], point) + break + } } - let tName = mentionText(name) - return m.memberId == userMemberId ? tName.foregroundColor(.accentColor) : tName - } else { - return mentionText(memberName) } + if let index, let (url, browser) = attributedStringLink(s, for: index) { + if browser { + openBrowserAlert(uri: url) + } else { + UIApplication.shared.open(url) + } + } + }) + } + + func attributedStringLink(_ s: NSAttributedString, for index: CFIndex) -> (URL, Bool)? { + var linkURL: URL? + var browser: Bool = false + s.enumerateAttributes(in: NSRange(location: 0, length: s.length)) { attrs, range, stop in + if index >= range.location && index < range.location + range.length { + if let url = attrs[linkAttrKey] as? NSURL { + linkURL = url.absoluteURL + browser = attrs[webLinkAttrKey] != nil + } else if let showSecrets, let i = attrs[secretAttrKey] as? Int { + if showSecrets.wrappedValue.contains(i) { + showSecrets.wrappedValue.remove(i) + } else { + showSecrets.wrappedValue.insert(i) + } + } + stop.pointee = true } - return Text(t) - case .email: return linkText(t, t, preview, prefix: "mailto:") - case .phone: return linkText(t, t.replacingOccurrences(of: " ", with: ""), preview, prefix: "tel:") } - } else { - return Text(t) + return if let linkURL { (linkURL, browser) } else { nil } } } -private func mentionText(_ name: String) -> Text { - Text(verbatim: name.contains(" @") ? "@'\(name)'" : "@\(name)").fontWeight(.semibold) -} +private let linkAttrKey = NSAttributedString.Key("chat.simplex.app.link") -private func linkText(_ s: String, _ link: String, _ preview: Bool, prefix: String, color: Color = Color(uiColor: uiLinkColor), uiColor: UIColor = uiLinkColor) -> Text { - preview - ? Text(s).foregroundColor(color).underline(color: color) - : Text(AttributedString(s, attributes: AttributeContainer([ - .link: NSURL(string: prefix + link) as Any, - .foregroundColor: uiColor as Any - ]))).underline() +private let webLinkAttrKey = NSAttributedString.Key("chat.simplex.app.webLink") + +private let secretAttrKey = NSAttributedString.Key("chat.simplex.app.secret") + +func messageText(_ text: String, _ formattedText: [FormattedText]?, textStyle: UIFont.TextStyle = .body, sender: String?, preview: Bool = false, mentions: [String: CIMention]?, userMemberId: String?, showSecrets: Set?, secondaryColor: Color, prefix: NSAttributedString? = nil) -> NSMutableAttributedString { + let res = NSMutableAttributedString() + let descr = UIFontDescriptor.preferredFontDescriptor(withTextStyle: textStyle) + let font = UIFont.preferredFont(forTextStyle: textStyle) + let plain: [NSAttributedString.Key: Any] = [ + .font: font, + .foregroundColor: UIColor.label + ] + var link: [NSAttributedString.Key: Any]? + + if let sender { + if preview { + res.append(NSAttributedString(string: sender + ": ", attributes: plain)) + } else { + var attrs = plain + attrs[.font] = UIFont(descriptor: descr.addingAttributes([.traits: [UIFontDescriptor.TraitKey.weight: UIFont.Weight.medium]]), size: descr.pointSize) + res.append(NSAttributedString(string: sender, attributes: attrs)) + res.append(NSAttributedString(string: ": ", attributes: plain)) + } + } + + if let prefix { + res.append(prefix) + } + + if let fts = formattedText, fts.count > 0 { + var bold: UIFont? + var italic: UIFont? + var snippet: UIFont? + var mention: UIFont? + var secretIdx: Int = 0 + for ft in fts { + var t = ft.text + var attrs = plain + switch (ft.format) { + case .bold: + bold = bold ?? UIFont(descriptor: descr.addingAttributes([.traits: [UIFontDescriptor.TraitKey.weight: UIFont.Weight.bold]]), size: descr.pointSize) + attrs[.font] = bold + case .italic: + italic = italic ?? UIFont(descriptor: descr.withSymbolicTraits(.traitItalic) ?? descr, size: descr.pointSize) + attrs[.font] = italic + case .strikeThrough: + attrs[.strikethroughStyle] = NSUnderlineStyle.single.rawValue + case .snippet: + snippet = snippet ?? UIFont.monospacedSystemFont(ofSize: descr.pointSize, weight: .regular) + attrs[.font] = snippet + case .secret: + if let showSecrets { + if !showSecrets.contains(secretIdx) { + attrs[.foregroundColor] = UIColor.clear + attrs[.backgroundColor] = UIColor.secondarySystemFill // secretColor + } + attrs[secretAttrKey] = secretIdx + secretIdx += 1 + } else { + attrs[.foregroundColor] = UIColor.clear + attrs[.backgroundColor] = UIColor.secondarySystemFill + } + case let .colored(color): + if let c = color.uiColor { + attrs[.foregroundColor] = UIColor(c) + } + case .uri: + attrs = linkAttrs() + if !preview { + attrs[linkAttrKey] = NSURL(string: ft.text) + attrs[webLinkAttrKey] = true + } + case let .simplexLink(linkType, simplexUri, smpHosts): + attrs = linkAttrs() + if !preview { + attrs[linkAttrKey] = NSURL(string: simplexUri) + } + if case .description = privacySimplexLinkModeDefault.get() { + t = simplexLinkText(linkType, smpHosts) + } + case let .mention(memberName): + if let m = mentions?[memberName] { + mention = mention ?? UIFont(descriptor: descr.addingAttributes([.traits: [UIFontDescriptor.TraitKey.weight: UIFont.Weight.semibold]]), size: descr.pointSize) + attrs[.font] = mention + if let ref = m.memberRef { + let name: String = if let alias = ref.localAlias, alias != "" { + "\(alias) (\(ref.displayName))" + } else { + ref.displayName + } + if m.memberId == userMemberId { + attrs[.foregroundColor] = UIColor.tintColor + } + t = "@'\(name)'" + } else { + t = "@'\(memberName)'" + } + } + case .email: + attrs = linkAttrs() + if !preview { + attrs[linkAttrKey] = NSURL(string: "mailto:" + ft.text) + } + case .phone: + attrs = linkAttrs() + if !preview { + attrs[linkAttrKey] = NSURL(string: "tel:" + t.replacingOccurrences(of: " ", with: "")) + } + case .none: () + } + res.append(NSAttributedString(string: t, attributes: attrs)) + } + } else { + res.append(NSMutableAttributedString(string: text, attributes: plain)) + } + + return res + + func linkAttrs() -> [NSAttributedString.Key: Any] { + link = link ?? [ + .font: font, + .foregroundColor: uiLinkColor, + .underlineStyle: NSUnderlineStyle.single.rawValue + ] + return link! + } } func simplexLinkText(_ linkType: SimplexLinkType, _ smpHosts: [String]) -> String { @@ -193,9 +315,9 @@ struct MsgContentView_Previews: PreviewProvider { chat: Chat.sampleData, text: chatItem.text, formattedText: chatItem.formattedText, + textStyle: .body, sender: chatItem.memberDisplayName, - meta: chatItem.meta, - showSecrets: false + meta: chatItem.meta ) .environmentObject(Chat.sampleData) } diff --git a/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift b/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift index 0fe9f5d4c2..1b840a1547 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift @@ -274,10 +274,11 @@ struct ChatItemInfoView: View { var sender: String? = nil var mentions: [String: CIMention]? var userMemberId: String? - @State private var showSecrets = false + @State private var showSecrets: Set = [] var body: some View { - toggleSecrets(formattedText, $showSecrets, messageText(text, formattedText, sender, mentions: mentions, userMemberId: userMemberId, showSecrets: showSecrets, secondaryColor: theme.colors.secondary)) + let s = messageText(text, formattedText, sender: sender, mentions: mentions, userMemberId: userMemberId, showSecrets: showSecrets, secondaryColor: theme.colors.secondary) + Text(AttributedString(s)).overlay(handleTextLinks(s, showSecrets: $showSecrets)) } } diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift index d5b5e6ccf4..d45cc9abc4 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift @@ -71,7 +71,7 @@ struct ContextItemView: View { } private func contextMsgPreview(_ contextItem: ChatItem) -> Text { - return attachment() + messageText(contextItem.text, contextItem.formattedText, nil, preview: true, mentions: contextItem.mentions, userMemberId: nil, showSecrets: false, secondaryColor: theme.colors.secondary) + return attachment() + Text(AttributedString(messageText(contextItem.text, contextItem.formattedText, sender: nil, preview: true, mentions: contextItem.mentions, userMemberId: nil, showSecrets: nil, secondaryColor: theme.colors.secondary))) func attachment() -> Text { let isFileLoaded = if let fileSource = getLoadedFileSource(contextItem.file) { diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift index 30767d66ec..d7b29a0ecb 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift @@ -263,13 +263,12 @@ struct SendMessageView: View { @State private var pressed: TimeInterval? = nil var body: some View { - Button(action: {}) { - Image(systemName: "mic.fill") - .resizable() - .scaledToFit() - .frame(width: 20, height: 20) - .foregroundColor(theme.colors.primary) - } + Image(systemName: "mic.fill") + .resizable() + .scaledToFit() + .frame(width: 20, height: 20) + .foregroundColor(theme.colors.primary) + .opacity(holdingVMR ? 0.7 : 1) .disabled(disabled) .frame(width: 31, height: 31) .padding([.bottom, .trailing], 4) @@ -279,9 +278,7 @@ struct SendMessageView: View { pressed = ProcessInfo.processInfo.systemUptime startVoiceMessageRecording?() } else { - let now = ProcessInfo.processInfo.systemUptime - if let pressed = pressed, - now - pressed >= 1 { + if let pressed, ProcessInfo.processInfo.systemUptime - pressed >= 1 { finishVoiceMessageRecording?() } holdingVMR = false diff --git a/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift b/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift index 58bfe182cb..4dd2b9e683 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift @@ -58,7 +58,9 @@ struct GroupWelcomeView: View { } private func textPreview() -> some View { - messageText(welcomeText, parseSimpleXMarkdown(welcomeText), nil, mentions: nil, userMemberId: nil, showSecrets: false, secondaryColor: theme.colors.secondary) + let s = messageText(welcomeText, parseSimpleXMarkdown(welcomeText), sender: nil, mentions: nil, userMemberId: nil, showSecrets: nil, secondaryColor: theme.colors.secondary) + return Text(AttributedString(s)) + .overlay(handleTextLinks(s)) .frame(minHeight: 130, alignment: .topLeading) .frame(maxWidth: .infinity, alignment: .leading) } diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift index 7f92862f66..c4b584061d 100644 --- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift @@ -263,7 +263,7 @@ struct ChatPreviewView: View { let msg = draft.message return image("rectangle.and.pencil.and.ellipsis", color: theme.colors.primary) + attachment() - + messageText(msg, parseSimpleXMarkdown(msg), nil, preview: true, mentions: draft.mentions, userMemberId: nil, showSecrets: false, secondaryColor: theme.colors.secondary) + + Text(AttributedString(messageText(msg, parseSimpleXMarkdown(msg), sender: nil, preview: true, mentions: draft.mentions, userMemberId: nil, showSecrets: nil, secondaryColor: theme.colors.secondary))) func image(_ s: String, color: Color = Color(uiColor: .tertiaryLabel)) -> Text { Text(Image(systemName: s)).foregroundColor(color) + textSpace @@ -282,7 +282,7 @@ struct ChatPreviewView: View { func chatItemPreview(_ cItem: ChatItem) -> Text { let itemText = cItem.meta.itemDeleted == nil ? cItem.text : markedDeletedText() let itemFormattedText = cItem.meta.itemDeleted == nil ? cItem.formattedText : nil - return messageText(itemText, itemFormattedText, cItem.memberDisplayName, icon: nil, preview: true, mentions: cItem.mentions, userMemberId: chat.chatInfo.groupInfo?.membership.memberId, showSecrets: false, secondaryColor: theme.colors.secondary, prefix: prefix()) + return Text(AttributedString(messageText(itemText, itemFormattedText, sender: cItem.memberDisplayName, preview: true, mentions: cItem.mentions, userMemberId: chat.chatInfo.groupInfo?.membership.memberId, showSecrets: nil, secondaryColor: theme.colors.secondary, prefix: prefix()))) // same texts are in markedDeletedText in MarkedDeletedItemView, but it returns LocalizedStringKey; // can be refactored into a single function if functions calling these are changed to return same type @@ -309,10 +309,10 @@ struct ChatPreviewView: View { } } - func prefix() -> Text { + func prefix() -> NSAttributedString? { switch cItem.content.msgContent { - case let .report(_, reason): return Text(!itemText.isEmpty ? "\(reason.text): " : reason.text).italic().foregroundColor(Color.red) - default: return Text("") + case let .report(_, reason): reason.attrString + default: nil } } } @@ -348,7 +348,6 @@ struct ChatPreviewView: View { } @ViewBuilder func chatItemContentPreview(_ chat: Chat, _ ci: ChatItem) -> some View { - let linkClicksEnabled = privacyChatListOpenLinksDefault.get() != PrivacyChatListOpenLinksMode.no let mc = ci.content.msgContent switch mc { case let .link(_, preview): @@ -370,17 +369,7 @@ struct ChatPreviewView: View { .cornerRadius(8) } .onTapGesture { - switch privacyChatListOpenLinksDefault.get() { - case .yes: UIApplication.shared.open(preview.uri) - case .no: ItemsModel.shared.loadOpenChat(chat.id) - case .ask: AlertManager.shared.showAlert( - Alert(title: Text("Open web link?"), - message: Text(preview.uri.absoluteString), - primaryButton: .default(Text("Open chat"), action: { ItemsModel.shared.loadOpenChat(chat.id) }), - secondaryButton: .default(Text("Open link"), action: { UIApplication.shared.open(preview.uri) }) - ) - ) - } + openBrowserAlert(uri: preview.uri) } } case let .image(_, image): diff --git a/apps/ios/Shared/Views/Helpers/ViewModifiers.swift b/apps/ios/Shared/Views/Helpers/ViewModifiers.swift index c790b9cff2..3a10cf84d7 100644 --- a/apps/ios/Shared/Views/Helpers/ViewModifiers.swift +++ b/apps/ios/Shared/Views/Helpers/ViewModifiers.swift @@ -9,6 +9,7 @@ import SwiftUI extension View { + @inline(__always) @ViewBuilder func `if`(_ condition: Bool, transform: (Self) -> Content) -> some View { if condition { transform(self) diff --git a/apps/ios/Shared/Views/UserSettings/AppSettings.swift b/apps/ios/Shared/Views/UserSettings/AppSettings.swift index 00532c0a8e..44e0b20958 100644 --- a/apps/ios/Shared/Views/UserSettings/AppSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/AppSettings.swift @@ -38,7 +38,6 @@ extension AppSettings { privacyLinkPreviewsGroupDefault.set(val) def.setValue(val, forKey: DEFAULT_PRIVACY_LINK_PREVIEWS) } - if let val = privacyChatListOpenLinks { privacyChatListOpenLinksDefault.set(val) } if let val = privacyShowChatPreviews { def.setValue(val, forKey: DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS) } if let val = privacySaveLastDraft { def.setValue(val, forKey: DEFAULT_PRIVACY_SAVE_LAST_DRAFT) } if let val = privacyProtectScreen { def.setValue(val, forKey: DEFAULT_PRIVACY_PROTECT_SCREEN) } @@ -78,7 +77,6 @@ extension AppSettings { c.privacyAskToApproveRelays = privacyAskToApproveRelaysGroupDefault.get() c.privacyAcceptImages = privacyAcceptImagesGroupDefault.get() c.privacyLinkPreviews = def.bool(forKey: DEFAULT_PRIVACY_LINK_PREVIEWS) - c.privacyChatListOpenLinks = privacyChatListOpenLinksDefault.get() c.privacyShowChatPreviews = def.bool(forKey: DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS) c.privacySaveLastDraft = def.bool(forKey: DEFAULT_PRIVACY_SAVE_LAST_DRAFT) c.privacyProtectScreen = def.bool(forKey: DEFAULT_PRIVACY_PROTECT_SCREEN) diff --git a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift index 1a17b9d661..eba7f8066a 100644 --- a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift +++ b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift @@ -14,7 +14,6 @@ struct PrivacySettings: View { @EnvironmentObject var theme: AppTheme @AppStorage(DEFAULT_PRIVACY_ACCEPT_IMAGES) private var autoAcceptImages = true @AppStorage(DEFAULT_PRIVACY_LINK_PREVIEWS) private var useLinkPreviews = true - @State private var chatListOpenLinks = privacyChatListOpenLinksDefault.get() @AppStorage(DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS) private var showChatPreviews = true @AppStorage(DEFAULT_PRIVACY_SAVE_LAST_DRAFT) private var saveLastDraft = true @AppStorage(GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES, store: groupDefaults) private var encryptLocalFiles = true @@ -77,17 +76,6 @@ struct PrivacySettings: View { privacyLinkPreviewsGroupDefault.set(linkPreviews) } } - settingsRow("arrow.up.right.circle", color: theme.colors.secondary) { - Picker("Open links from chat list", selection: $chatListOpenLinks) { - ForEach(PrivacyChatListOpenLinksMode.allCases) { mode in - Text(mode.text) - } - } - } - .frame(height: 36) - .onChange(of: chatListOpenLinks) { mode in - privacyChatListOpenLinksDefault.set(mode) - } settingsRow("message", color: theme.colors.secondary) { Toggle("Show last messages", isOn: $showChatPreviews) } diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index 961cad128f..3321b25793 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -29,7 +29,6 @@ let DEFAULT_WEBRTC_ICE_SERVERS = "webrtcICEServers" let DEFAULT_CALL_KIT_CALLS_IN_RECENTS = "callKitCallsInRecents" let DEFAULT_PRIVACY_ACCEPT_IMAGES = "privacyAcceptImages" // unused. Use GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES instead let DEFAULT_PRIVACY_LINK_PREVIEWS = "privacyLinkPreviews" // deprecated, moved to app group -let DEFAULT_PRIVACY_CHAT_LIST_OPEN_LINKS = "privacyChatListOpenLinks" let DEFAULT_PRIVACY_SIMPLEX_LINK_MODE = "privacySimplexLinkMode" let DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS = "privacyShowChatPreviews" let DEFAULT_PRIVACY_SAVE_LAST_DRAFT = "privacySaveLastDraft" @@ -185,8 +184,6 @@ let connectViaLinkTabDefault = EnumDefault(defaults: UserDefa let privacySimplexLinkModeDefault = EnumDefault(defaults: UserDefaults.standard, forKey: DEFAULT_PRIVACY_SIMPLEX_LINK_MODE, withDefault: .description) -let privacyChatListOpenLinksDefault = EnumDefault(defaults: UserDefaults.standard, forKey: DEFAULT_PRIVACY_CHAT_LIST_OPEN_LINKS, withDefault: PrivacyChatListOpenLinksMode.ask) - let privacyLocalAuthModeDefault = EnumDefault(defaults: UserDefaults.standard, forKey: DEFAULT_LA_MODE, withDefault: .system) let privacyDeliveryReceiptsSet = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_PRIVACY_DELIVERY_RECEIPTS_SET) diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index db8f1dd26e..960fdd466d 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -4137,18 +4137,16 @@ public enum FormatColor: String, Decodable, Hashable { case black = "black" case white = "white" - public var uiColor: Color { - get { - switch (self) { - case .red: return .red - case .green: return .green - case .blue: return .blue - case .yellow: return .yellow - case .cyan: return .cyan - case .magenta: return .purple - case .black: return .primary - case .white: return .primary - } + public var uiColor: Color? { + switch (self) { + case .red: .red + case .green: .green + case .blue: .blue + case .yellow: .yellow + case .cyan: .cyan + case .magenta: .purple + case .black: nil + case .white: nil } } } @@ -4173,6 +4171,14 @@ public enum ReportReason: Hashable { case let .unknown(type): return type } } + + public var attrString: NSAttributedString { + let descr = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body) + return NSAttributedString(string: text.isEmpty ? self.text : "\(self.text): ", attributes: [ + .font: UIFont(descriptor: descr.withSymbolicTraits(.traitItalic) ?? descr, size: 0), + .foregroundColor: UIColor(Color.red) + ]) + } } extension ReportReason: Encodable { diff --git a/src/Simplex/Chat/AppSettings.hs b/src/Simplex/Chat/AppSettings.hs index 23b5f2ddad..1efa69fad4 100644 --- a/src/Simplex/Chat/AppSettings.hs +++ b/src/Simplex/Chat/AppSettings.hs @@ -25,8 +25,6 @@ data NotificationPreviewMode = NPMHidden | NPMContact | NPMMessage deriving (Sho data LockScreenCalls = LSCDisable | LSCShow | LSCAccept deriving (Show) -data OpenLinksSetting = OLSYes | OLSNo | OLSAsk deriving (Show) - data AppSettings = AppSettings { appPlatform :: Maybe AppPlatform, networkConfig :: Maybe NetworkConfig, @@ -35,7 +33,6 @@ data AppSettings = AppSettings privacyAskToApproveRelays :: Maybe Bool, privacyAcceptImages :: Maybe Bool, privacyLinkPreviews :: Maybe Bool, - privacyChatListOpenLinks :: Maybe OpenLinksSetting, privacyShowChatPreviews :: Maybe Bool, privacySaveLastDraft :: Maybe Bool, privacyProtectScreen :: Maybe Bool, @@ -86,7 +83,6 @@ defaultAppSettings = privacyAskToApproveRelays = Just True, privacyAcceptImages = Just True, privacyLinkPreviews = Just True, - privacyChatListOpenLinks = Just OLSAsk, privacyShowChatPreviews = Just True, privacySaveLastDraft = Just True, privacyProtectScreen = Just False, @@ -124,7 +120,6 @@ defaultParseAppSettings = privacyAskToApproveRelays = Nothing, privacyAcceptImages = Nothing, privacyLinkPreviews = Nothing, - privacyChatListOpenLinks = Nothing, privacyShowChatPreviews = Nothing, privacySaveLastDraft = Nothing, privacyProtectScreen = Nothing, @@ -162,7 +157,6 @@ combineAppSettings platformDefaults storedSettings = privacyAskToApproveRelays = p privacyAskToApproveRelays, privacyAcceptImages = p privacyAcceptImages, privacyLinkPreviews = p privacyLinkPreviews, - privacyChatListOpenLinks = p privacyChatListOpenLinks, privacyShowChatPreviews = p privacyShowChatPreviews, privacySaveLastDraft = p privacySaveLastDraft, privacyProtectScreen = p privacyProtectScreen, @@ -203,8 +197,6 @@ $(JQ.deriveJSON (enumJSON $ dropPrefix "LSC") ''LockScreenCalls) $(JQ.deriveJSON (enumJSON $ dropPrefix "NPA") ''NetworkProxyAuth) -$(JQ.deriveJSON (enumJSON $ dropPrefix "OLS") ''OpenLinksSetting) - $(JQ.deriveJSON defaultJSON ''NetworkProxy) $(JQ.deriveToJSON defaultJSON ''AppSettings) @@ -218,7 +210,6 @@ instance FromJSON AppSettings where privacyAskToApproveRelays <- p "privacyAskToApproveRelays" privacyAcceptImages <- p "privacyAcceptImages" privacyLinkPreviews <- p "privacyLinkPreviews" - privacyChatListOpenLinks <- p "privacyChatListOpenLinks" privacyShowChatPreviews <- p "privacyShowChatPreviews" privacySaveLastDraft <- p "privacySaveLastDraft" privacyProtectScreen <- p "privacyProtectScreen" @@ -253,7 +244,6 @@ instance FromJSON AppSettings where privacyAskToApproveRelays, privacyAcceptImages, privacyLinkPreviews, - privacyChatListOpenLinks, privacyShowChatPreviews, privacySaveLastDraft, privacyProtectScreen, From 5b7f3fdd78653385029b61a7871d5ede137ecc06 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 10 May 2025 16:46:54 +0100 Subject: [PATCH 538/567] ios: export localizations --- .../bg.xcloc/Localized Contents/bg.xliff | 24 +++------------ .../cs.xcloc/Localized Contents/cs.xliff | 24 +++------------ .../de.xcloc/Localized Contents/de.xliff | 29 +++--------------- .../en.xcloc/Localized Contents/en.xliff | 30 ++++--------------- .../es.xcloc/Localized Contents/es.xliff | 29 +++--------------- .../fi.xcloc/Localized Contents/fi.xliff | 24 +++------------ .../fr.xcloc/Localized Contents/fr.xliff | 25 +++------------- .../hu.xcloc/Localized Contents/hu.xliff | 29 +++--------------- .../it.xcloc/Localized Contents/it.xliff | 29 +++--------------- .../ja.xcloc/Localized Contents/ja.xliff | 24 +++------------ .../nl.xcloc/Localized Contents/nl.xliff | 29 +++--------------- .../pl.xcloc/Localized Contents/pl.xliff | 25 +++------------- .../ru.xcloc/Localized Contents/ru.xliff | 29 +++--------------- .../th.xcloc/Localized Contents/th.xliff | 24 +++------------ .../tr.xcloc/Localized Contents/tr.xliff | 24 +++------------ .../uk.xcloc/Localized Contents/uk.xliff | 24 +++------------ .../Localized Contents/zh-Hans.xliff | 24 +++------------ 17 files changed, 69 insertions(+), 377 deletions(-) diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 995698cf2e..926eb7e20f 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -1063,10 +1063,6 @@ swipe action Архивиране на база данни No comment provided by engineer.
    - - Ask - No comment provided by engineer. - Attach Прикачи @@ -5264,7 +5260,7 @@ Requires compatible VPN. Open Отвори - No comment provided by engineer. + alert action Open Settings @@ -5294,23 +5290,15 @@ Requires compatible VPN. Отвори група No comment provided by engineer. - - Open link - No comment provided by engineer. - - - Open links from chat list - No comment provided by engineer. + + Open in browser? + alert title Open migration to another device Отвори миграцията към друго устройство authentication reason - - Open web link? - No comment provided by engineer. - Opening app… Приложението се отваря… @@ -8185,10 +8173,6 @@ To connect, please ask your contact to create another connection link and check XFTP server No comment provided by engineer. - - Yes - No comment provided by engineer. - You **must not** use the same database on two devices. **Не трябва** да използвате една и съща база данни на две устройства. diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index bf7bb307e0..32403dba44 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -1016,10 +1016,6 @@ swipe action Archiving database No comment provided by engineer. - - Ask - No comment provided by engineer. - Attach Připojit @@ -5068,7 +5064,7 @@ Vyžaduje povolení sítě VPN. Open Otevřít - No comment provided by engineer. + alert action Open Settings @@ -5097,22 +5093,14 @@ Vyžaduje povolení sítě VPN. Open group No comment provided by engineer. - - Open link - No comment provided by engineer. - - - Open links from chat list - No comment provided by engineer. + + Open in browser? + alert title Open migration to another device authentication reason - - Open web link? - No comment provided by engineer. - Opening app… No comment provided by engineer. @@ -7897,10 +7885,6 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu XFTP server No comment provided by engineer. - - Yes - No comment provided by engineer. - You **must not** use the same database on two devices. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 1fb3d61bde..b4751e1014 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -1090,11 +1090,6 @@ swipe action Datenbank wird archiviert No comment provided by engineer. - - Ask - Fragen - No comment provided by engineer. - Attach Anhängen @@ -5545,7 +5540,7 @@ Dies erfordert die Aktivierung eines VPNs. Open Öffnen - No comment provided by engineer. + alert action Open Settings @@ -5577,26 +5572,15 @@ Dies erfordert die Aktivierung eines VPNs. Gruppe öffnen No comment provided by engineer. - - Open link - Web-Link öffnen - No comment provided by engineer. - - - Open links from chat list - Web-Links aus der Chat-Liste öffnen - No comment provided by engineer. + + Open in browser? + alert title Open migration to another device Migration auf ein anderes Gerät öffnen authentication reason - - Open web link? - Web-Link öffnen? - No comment provided by engineer. - Opening app… App wird geöffnet… @@ -8676,11 +8660,6 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s XFTP-Server No comment provided by engineer. - - Yes - Ja - No comment provided by engineer. - You **must not** use the same database on two devices. Sie dürfen die selbe Datenbank **nicht** auf zwei Geräten nutzen. diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 0082fa574d..a4a0c07410 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -1091,11 +1091,6 @@ swipe action Archiving database No comment provided by engineer. - - Ask - Ask - No comment provided by engineer. - Attach Attach @@ -5546,7 +5541,7 @@ Requires compatible VPN. Open Open - No comment provided by engineer. + alert action Open Settings @@ -5578,26 +5573,16 @@ Requires compatible VPN. Open group No comment provided by engineer. - - Open link - Open link - No comment provided by engineer. - - - Open links from chat list - Open links from chat list - No comment provided by engineer. + + Open in browser? + Open in browser? + alert title Open migration to another device Open migration to another device authentication reason - - Open web link? - Open web link? - No comment provided by engineer. - Opening app… Opening app… @@ -8678,11 +8663,6 @@ To connect, please ask your contact to create another connection link and check XFTP server No comment provided by engineer. - - Yes - Yes - No comment provided by engineer. - You **must not** use the same database on two devices. You **must not** use the same database on two devices. diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 3f621bee53..849cff5ebc 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -1090,11 +1090,6 @@ swipe action Archivando base de datos No comment provided by engineer. - - Ask - Preguntar - No comment provided by engineer. - Attach Adjuntar @@ -5545,7 +5540,7 @@ Requiere activación de la VPN. Open Abrir - No comment provided by engineer. + alert action Open Settings @@ -5577,26 +5572,15 @@ Requiere activación de la VPN. Grupo abierto No comment provided by engineer. - - Open link - Abrir enlace - No comment provided by engineer. - - - Open links from chat list - Abrir enlaces desde listado de chats - No comment provided by engineer. + + Open in browser? + alert title Open migration to another device Abrir menú migración a otro dispositivo authentication reason - - Open web link? - ¿Abrir enlace web? - No comment provided by engineer. - Opening app… Iniciando aplicación… @@ -8676,11 +8660,6 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Servidor XFTP No comment provided by engineer. - - Yes - Si - No comment provided by engineer. - You **must not** use the same database on two devices. **No debes** usar la misma base de datos en dos dispositivos. diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index a6b05ee48a..7e493d5f73 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -998,10 +998,6 @@ swipe action Archiving database No comment provided by engineer. - - Ask - No comment provided by engineer. - Attach Liitä @@ -5044,7 +5040,7 @@ Edellyttää VPN:n sallimista. Open - No comment provided by engineer. + alert action Open Settings @@ -5073,22 +5069,14 @@ Edellyttää VPN:n sallimista. Open group No comment provided by engineer. - - Open link - No comment provided by engineer. - - - Open links from chat list - No comment provided by engineer. + + Open in browser? + alert title Open migration to another device authentication reason - - Open web link? - No comment provided by engineer. - Opening app… No comment provided by engineer. @@ -7870,10 +7858,6 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja XFTP server No comment provided by engineer. - - Yes - No comment provided by engineer. - You **must not** use the same database on two devices. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 2928b9f167..80d5ae5f55 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -1089,11 +1089,6 @@ swipe action Archivage de la base de données No comment provided by engineer. - - Ask - Demander - No comment provided by engineer. - Attach Attacher @@ -5510,7 +5505,7 @@ Nécessite l'activation d'un VPN. Open Ouvrir - No comment provided by engineer. + alert action Open Settings @@ -5542,23 +5537,15 @@ Nécessite l'activation d'un VPN. Ouvrir le groupe No comment provided by engineer. - - Open link - No comment provided by engineer. - - - Open links from chat list - No comment provided by engineer. + + Open in browser? + alert title Open migration to another device Ouvrir le transfert vers un autre appareil authentication reason - - Open web link? - No comment provided by engineer. - Opening app… Ouverture de l'app… @@ -8598,10 +8585,6 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Serveur XFTP No comment provided by engineer. - - Yes - No comment provided by engineer. - You **must not** use the same database on two devices. Vous **ne devez pas** utiliser la même base de données sur deux appareils. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index cde7807c44..3d3d8c6383 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -1090,11 +1090,6 @@ swipe action Adatbázis archiválása No comment provided by engineer. - - Ask - Mindig kérdezzen rá - No comment provided by engineer. - Attach Mellékelés @@ -5545,7 +5540,7 @@ VPN engedélyezése szükséges. Open Megnyitás - No comment provided by engineer. + alert action Open Settings @@ -5577,26 +5572,15 @@ VPN engedélyezése szükséges. Csoport megnyitása No comment provided by engineer. - - Open link - Hivatkozás megnyitása - No comment provided by engineer. - - - Open links from chat list - Hivatkozás megnyitása a csevegési listából - No comment provided by engineer. + + Open in browser? + alert title Open migration to another device Átköltöztetés indítása egy másik eszközre authentication reason - - Open web link? - Megnyitja a webhivatkozást? - No comment provided by engineer. - Opening app… Az alkalmazás megnyitása… @@ -8676,11 +8660,6 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso XFTP-kiszolgáló No comment provided by engineer. - - Yes - Igen - No comment provided by engineer. - You **must not** use the same database on two devices. **Nem szabad** ugyanazt az adatbázist használni egyszerre két eszközön. diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 31e89b8507..c672096082 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -1090,11 +1090,6 @@ swipe action Archiviazione del database No comment provided by engineer. - - Ask - Chiedi - No comment provided by engineer. - Attach Allega @@ -5545,7 +5540,7 @@ Richiede l'attivazione della VPN. Open Apri - No comment provided by engineer. + alert action Open Settings @@ -5577,26 +5572,15 @@ Richiede l'attivazione della VPN. Apri gruppo No comment provided by engineer. - - Open link - Apri link - No comment provided by engineer. - - - Open links from chat list - Apri i link dall'elenco delle chat - No comment provided by engineer. + + Open in browser? + alert title Open migration to another device Apri migrazione ad un altro dispositivo authentication reason - - Open web link? - Aprire il link? - No comment provided by engineer. - Opening app… Apertura dell'app… @@ -8676,11 +8660,6 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Server XFTP No comment provided by engineer. - - Yes - - No comment provided by engineer. - You **must not** use the same database on two devices. **Non devi** usare lo stesso database su due dispositivi. diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index e929df1f35..4dd0d467a1 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -1047,10 +1047,6 @@ swipe action Archiving database No comment provided by engineer. - - Ask - No comment provided by engineer. - Attach 添付する @@ -5121,7 +5117,7 @@ VPN を有効にする必要があります。 Open 開く - No comment provided by engineer. + alert action Open Settings @@ -5150,22 +5146,14 @@ VPN を有効にする必要があります。 Open group No comment provided by engineer. - - Open link - No comment provided by engineer. - - - Open links from chat list - No comment provided by engineer. + + Open in browser? + alert title Open migration to another device authentication reason - - Open web link? - No comment provided by engineer. - Opening app… No comment provided by engineer. @@ -7940,10 +7928,6 @@ To connect, please ask your contact to create another connection link and check XFTP server No comment provided by engineer. - - Yes - No comment provided by engineer. - You **must not** use the same database on two devices. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 2b2a79731e..43d568f747 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -1090,11 +1090,6 @@ swipe action Database archiveren No comment provided by engineer. - - Ask - Vragen - No comment provided by engineer. - Attach Bijvoegen @@ -5545,7 +5540,7 @@ Vereist het inschakelen van VPN. Open Open - No comment provided by engineer. + alert action Open Settings @@ -5577,26 +5572,15 @@ Vereist het inschakelen van VPN. Open groep No comment provided by engineer. - - Open link - Link openen - No comment provided by engineer. - - - Open links from chat list - Open links van chatlijst - No comment provided by engineer. + + Open in browser? + alert title Open migration to another device Open de migratie naar een ander apparaat authentication reason - - Open web link? - Weblink openen? - No comment provided by engineer. - Opening app… App openen… @@ -8671,11 +8655,6 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak XFTP server No comment provided by engineer. - - Yes - Ja - No comment provided by engineer. - You **must not** use the same database on two devices. U **mag** niet dezelfde database op twee apparaten gebruiken. diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 5e77f836a9..f8a73a220d 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -1090,11 +1090,6 @@ swipe action Archiwizowanie bazy danych No comment provided by engineer. - - Ask - Zapytaj - No comment provided by engineer. - Attach Dołącz @@ -5424,7 +5419,7 @@ Wymaga włączenia VPN. Open Otwórz - No comment provided by engineer. + alert action Open Settings @@ -5454,23 +5449,15 @@ Wymaga włączenia VPN. Grupa otwarta No comment provided by engineer. - - Open link - No comment provided by engineer. - - - Open links from chat list - No comment provided by engineer. + + Open in browser? + alert title Open migration to another device Otwórz migrację na innym urządzeniu authentication reason - - Open web link? - No comment provided by engineer. - Opening app… Otwieranie aplikacji… @@ -8469,10 +8456,6 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Serwer XFTP No comment provided by engineer. - - Yes - No comment provided by engineer. - You **must not** use the same database on two devices. **Nie możesz** używać tej samej bazy na dwóch urządzeniach. diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index d120909994..2b4a426130 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -1090,11 +1090,6 @@ swipe action Подготовка архива No comment provided by engineer. - - Ask - Спросить - No comment provided by engineer. - Attach Прикрепить @@ -5511,7 +5506,7 @@ Requires compatible VPN. Open Открыть - No comment provided by engineer. + alert action Open Settings @@ -5543,26 +5538,15 @@ Requires compatible VPN. Открыть группу No comment provided by engineer. - - Open link - Открыть ссылку - No comment provided by engineer. - - - Open links from chat list - Открыть ссылку из списка чатов - No comment provided by engineer. + + Open in browser? + alert title Open migration to another device Открытие миграции на другое устройство authentication reason - - Open web link? - Открыть веб-ссылку? - No comment provided by engineer. - Opening app… Приложение отрывается… @@ -8621,11 +8605,6 @@ To connect, please ask your contact to create another connection link and check XFTP сервер No comment provided by engineer. - - Yes - Да - No comment provided by engineer. - You **must not** use the same database on two devices. Вы **не должны** использовать одну и ту же базу данных на двух устройствах. diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index 279fab822a..0398c37c8c 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -990,10 +990,6 @@ swipe action Archiving database No comment provided by engineer. - - Ask - No comment provided by engineer. - Attach แนบ @@ -5023,7 +5019,7 @@ Requires compatible VPN. Open - No comment provided by engineer. + alert action Open Settings @@ -5052,22 +5048,14 @@ Requires compatible VPN. Open group No comment provided by engineer. - - Open link - No comment provided by engineer. - - - Open links from chat list - No comment provided by engineer. + + Open in browser? + alert title Open migration to another device authentication reason - - Open web link? - No comment provided by engineer. - Opening app… No comment provided by engineer. @@ -7840,10 +7828,6 @@ To connect, please ask your contact to create another connection link and check XFTP server No comment provided by engineer. - - Yes - No comment provided by engineer. - You **must not** use the same database on two devices. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index 2208f65b89..b1f65a1791 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -1073,10 +1073,6 @@ swipe action Veritabanı arşivleniyor No comment provided by engineer. - - Ask - No comment provided by engineer. - Attach Ekle @@ -5436,7 +5432,7 @@ VPN'nin etkinleştirilmesi gerekir. Open - No comment provided by engineer. + alert action Open Settings @@ -5466,23 +5462,15 @@ VPN'nin etkinleştirilmesi gerekir. Grubu aç No comment provided by engineer. - - Open link - No comment provided by engineer. - - - Open links from chat list - No comment provided by engineer. + + Open in browser? + alert title Open migration to another device Başka bir cihaza açık geçiş authentication reason - - Open web link? - No comment provided by engineer. - Opening app… Uygulama açılıyor… @@ -8484,10 +8472,6 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste XFTP sunucusu No comment provided by engineer. - - Yes - No comment provided by engineer. - You **must not** use the same database on two devices. Aynı veritabanını iki cihazda **kullanmamalısınız**. diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 1863bf6cd8..784505ee62 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -1073,10 +1073,6 @@ swipe action Архівування бази даних No comment provided by engineer. - - Ask - No comment provided by engineer. - Attach Прикріпити @@ -5451,7 +5447,7 @@ Requires compatible VPN. Open Відкрито - No comment provided by engineer. + alert action Open Settings @@ -5483,23 +5479,15 @@ Requires compatible VPN. Відкрита група No comment provided by engineer. - - Open link - No comment provided by engineer. - - - Open links from chat list - No comment provided by engineer. + + Open in browser? + alert title Open migration to another device Відкрита міграція на інший пристрій authentication reason - - Open web link? - No comment provided by engineer. - Opening app… Відкриваємо програму… @@ -8539,10 +8527,6 @@ To connect, please ask your contact to create another connection link and check XFTP-сервер No comment provided by engineer. - - Yes - No comment provided by engineer. - You **must not** use the same database on two devices. Ви **не повинні використовувати** одну і ту ж базу даних на двох пристроях. diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index 4bc7095bd2..a1ca7d430a 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -1052,10 +1052,6 @@ swipe action 正在存档数据库 No comment provided by engineer. - - Ask - No comment provided by engineer. - Attach 附件 @@ -5347,7 +5343,7 @@ Requires compatible VPN. Open 打开 - No comment provided by engineer. + alert action Open Settings @@ -5377,23 +5373,15 @@ Requires compatible VPN. 打开群 No comment provided by engineer. - - Open link - No comment provided by engineer. - - - Open links from chat list - No comment provided by engineer. + + Open in browser? + alert title Open migration to another device 打开迁移到另一台设备 authentication reason - - Open web link? - No comment provided by engineer. - Opening app… 正在打开应用程序… @@ -8374,10 +8362,6 @@ To connect, please ask your contact to create another connection link and check XFTP 服务器 No comment provided by engineer. - - Yes - No comment provided by engineer. - You **must not** use the same database on two devices. 您 **不得** 在两台设备上使用相同的数据库。 From d33869603590a24498f7d16296d947335ecf807c Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 10 May 2025 17:23:53 +0100 Subject: [PATCH 539/567] ios: 6.3.4 (build 275) --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index e96c45b474..af1eebd8b3 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -1971,7 +1971,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 274; + CURRENT_PROJECT_VERSION = 275; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2021,7 +2021,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 274; + CURRENT_PROJECT_VERSION = 275; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2063,7 +2063,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 274; + CURRENT_PROJECT_VERSION = 275; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2083,7 +2083,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 274; + CURRENT_PROJECT_VERSION = 275; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2108,7 +2108,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 274; + CURRENT_PROJECT_VERSION = 275; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2145,7 +2145,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 274; + CURRENT_PROJECT_VERSION = 275; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2182,7 +2182,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 274; + CURRENT_PROJECT_VERSION = 275; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2233,7 +2233,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 274; + CURRENT_PROJECT_VERSION = 275; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2284,7 +2284,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 274; + CURRENT_PROJECT_VERSION = 275; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2318,7 +2318,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 274; + CURRENT_PROJECT_VERSION = 275; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; From 8d54acef9220d7ae32ea76de01dbbff3c148bae0 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 11 May 2025 14:15:14 +0100 Subject: [PATCH 540/567] ios: only handle taps on messages with links or secrets, use image for secret markdown (#5885) * ios: use image for secret markdown * remove unnecessary ViewBuilders --- .../vertical_logo.imageset/Contents.json | 23 ++ .../vertical_logo_x1.png | Bin 0 -> 1234 bytes .../vertical_logo_x2.png | Bin 0 -> 1617 bytes .../vertical_logo_x3.png | Bin 0 -> 2010 bytes apps/ios/Shared/ContentView.swift | 4 +- .../Shared/Views/Call/ActiveCallView.swift | 6 +- .../Views/Chat/ChatItem/CICallItemView.swift | 2 +- .../Views/Chat/ChatItem/CILinkView.swift | 2 +- .../Chat/ChatItem/CIRcvDecryptionError.swift | 2 +- .../Views/Chat/ChatItem/FramedItemView.swift | 7 +- .../Views/Chat/ChatItem/MsgContentView.swift | 61 +++- .../Views/Chat/ChatItemForwardingView.swift | 2 +- .../Shared/Views/Chat/ChatItemInfoView.swift | 41 +-- apps/ios/Shared/Views/Chat/ChatItemView.swift | 9 + apps/ios/Shared/Views/Chat/ChatView.swift | 7 +- .../Chat/ComposeMessage/ContextItemView.swift | 6 +- .../Chat/Group/AddGroupMembersView.swift | 4 +- .../Views/Chat/Group/GroupChatInfoView.swift | 17 +- .../Views/Chat/Group/GroupWelcomeView.swift | 6 +- .../Views/ChatList/ChatListNavLink.swift | 4 +- .../Shared/Views/ChatList/ChatListView.swift | 6 +- .../Views/ChatList/ChatPreviewView.swift | 28 +- .../Views/ChatList/ServersSummaryView.swift | 8 +- .../Shared/Views/ChatList/TagListView.swift | 2 +- .../Views/Contacts/ContactListNavLink.swift | 4 +- .../Views/Database/DatabaseErrorView.swift | 2 +- .../Views/LocalAuth/PasscodeEntry.swift | 2 +- .../Views/NewChat/NewChatMenuButton.swift | 4 +- .../Shared/Views/NewChat/NewChatView.swift | 2 +- .../Onboarding/ChooseServerOperators.swift | 4 +- .../NetworkAndServers/OperatorView.swift | 8 +- .../ProtocolServersView.swift | 4 +- .../Views/UserSettings/SettingsView.swift | 262 +++++++++--------- .../Views/UserSettings/UserProfile.swift | 1 - .../Views/UserSettings/UserProfilesView.swift | 4 +- .../bg.xcloc/Localized Contents/bg.xliff | 4 +- .../cs.xcloc/Localized Contents/cs.xliff | 4 +- .../de.xcloc/Localized Contents/de.xliff | 4 +- .../en.xcloc/Localized Contents/en.xliff | 6 +- .../es.xcloc/Localized Contents/es.xliff | 4 +- .../fi.xcloc/Localized Contents/fi.xliff | 4 +- .../fr.xcloc/Localized Contents/fr.xliff | 4 +- .../hu.xcloc/Localized Contents/hu.xliff | 4 +- .../it.xcloc/Localized Contents/it.xliff | 4 +- .../ja.xcloc/Localized Contents/ja.xliff | 4 +- .../nl.xcloc/Localized Contents/nl.xliff | 4 +- .../pl.xcloc/Localized Contents/pl.xliff | 4 +- .../ru.xcloc/Localized Contents/ru.xliff | 4 +- .../th.xcloc/Localized Contents/th.xliff | 4 +- .../tr.xcloc/Localized Contents/tr.xliff | 4 +- .../uk.xcloc/Localized Contents/uk.xliff | 4 +- .../Localized Contents/zh-Hans.xliff | 4 +- apps/ios/SimpleX SE/ShareView.swift | 2 +- 53 files changed, 352 insertions(+), 264 deletions(-) create mode 100644 apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/Contents.json create mode 100644 apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/vertical_logo_x1.png create mode 100644 apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/vertical_logo_x2.png create mode 100644 apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/vertical_logo_x3.png diff --git a/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/Contents.json b/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/Contents.json new file mode 100644 index 0000000000..cb29f09fe1 --- /dev/null +++ b/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "vertical_logo_x1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "vertical_logo_x2.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "vertical_logo_x3.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/vertical_logo_x1.png b/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/vertical_logo_x1.png new file mode 100644 index 0000000000000000000000000000000000000000..f916e43ea99bbda5ae2bba007dc73ac5a77c8ea9 GIT binary patch literal 1234 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqEa{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBDAAG{;hE;^%b*2hb1<+n z3NbJPS&Tr)z$nE4G7ZRL@M4sPvx68lplX;H7}_%#SfFa6fHVkr05M1pgl1mAh%j*h z6I?aN0%imoq%CcqSRjz%EbxddW?Q&?xfOIj~R9FF-xv3?I3Kh9IdBs*0wn|_XRzNmLSYJs2tfVB{Rw=?aK*2e`C{@8s z&p^*W$&O1wLBXadCCw_x#SN+*$g@?-C@Cqh($_C9FV`zK*2^zS*Eh7ZwA42+(l;{F z1**_3uFNY*tkBIXR)!b?Gsh*hIJqdZpd>RtPXT0ZVp4u-iLH_n$Rap^xU(cP4PjGW zG1OZ?59)(t^bPe4^s#A6t;oco4I~562KE=kIvbE-R*^xe#rZj)0j?1ok@jPI{ed6ij7$PAl+HdcDm_guZ{q+zHC5s>IKHUmCc6RUB zq1n;Rw@y#?Pe5G*->#Pr7U`|C^33e=aOAz!5F$1ClFIYf&5!Th-TnC9L7x5lj`X|N z{ASYc^4PO_UcmGnJ_n_QKMML5&JgcLY(2J1Hq5vaU>p5IWx2^6@tu+_ zcWPrcZ>~EK#+{T`*e4-;-N0M=?eiX$azFjxd6TA`nd|b&PibHUB)wIukE-|oopnYXt(t9jmHI>-IvAhdr_44 Zw_a>YvSZQGceg<~)zj6_Wt~$(69AmVcuD{O literal 0 HcmV?d00001 diff --git a/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/vertical_logo_x2.png b/apps/ios/Shared/Assets.xcassets/vertical_logo.imageset/vertical_logo_x2.png new file mode 100644 index 0000000000000000000000000000000000000000..bb35878f0c4aec928c3e16b4d10603dfc2a14748 GIT binary patch literal 1617 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 L)z4*}Q$iB}rr7 some View { + func allViews() -> some View { ZStack { let showCallArea = chatModel.activeCall != nil && chatModel.activeCall?.callState != .waitCapabilities && chatModel.activeCall?.callState != .invitationAccepted // contentView() has to be in a single branch, so that enabling authentication doesn't trigger re-rendering and close settings. @@ -209,7 +209,7 @@ struct ContentView: View { } } - @ViewBuilder private func activeCallInteractiveArea(_ call: Call) -> some View { + private func activeCallInteractiveArea(_ call: Call) -> some View { HStack { Text(call.contact.displayName).font(.body).foregroundColor(.white) Spacer() diff --git a/apps/ios/Shared/Views/Call/ActiveCallView.swift b/apps/ios/Shared/Views/Call/ActiveCallView.swift index 7c8996a99b..ab7a47b944 100644 --- a/apps/ios/Shared/Views/Call/ActiveCallView.swift +++ b/apps/ios/Shared/Views/Call/ActiveCallView.swift @@ -467,7 +467,7 @@ struct ActiveCallOverlay: View { .disabled(call.initialCallType == .audio && client.activeCall?.peerHasOldVersion == true) } - @ViewBuilder private func flipCameraButton() -> some View { + private func flipCameraButton() -> some View { controlButton(call, "arrow.triangle.2.circlepath", padding: 12) { Task { if await WebRTCClient.isAuthorized(for: .video) { @@ -477,11 +477,11 @@ struct ActiveCallOverlay: View { } } - @ViewBuilder private func controlButton(_ call: Call, _ imageName: String, padding: CGFloat, _ perform: @escaping () -> Void) -> some View { + private func controlButton(_ call: Call, _ imageName: String, padding: CGFloat, _ perform: @escaping () -> Void) -> some View { callButton(imageName, call.peerMediaSources.hasVideo ? Color.black.opacity(0.2) : Color.white.opacity(0.2), padding: padding, perform) } - @ViewBuilder private func audioDevicePickerButton() -> some View { + private func audioDevicePickerButton() -> some View { AudioDevicePicker() .opacity(0.8) .scaleEffect(2) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CICallItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CICallItemView.swift index 024aeed96a..0283e9c07e 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CICallItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CICallItemView.swift @@ -50,7 +50,7 @@ struct CICallItemView: View { Image(systemName: "phone.connection").foregroundColor(.green) } - @ViewBuilder private func endedCallIcon(_ sent: Bool) -> some View { + private func endedCallIcon(_ sent: Bool) -> some View { HStack { Image(systemName: "phone.down") Text(durationText(duration)).foregroundColor(theme.colors.secondary) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift index 4f879db426..273c9de408 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift @@ -46,7 +46,7 @@ struct CILinkView: View { func openBrowserAlert(uri: URL) { showAlert( - NSLocalizedString("Open in browser?", comment: "alert title"), + NSLocalizedString("Open link?", comment: "alert title"), message: uri.absoluteString, actions: {[ UIAlertAction( diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift index e26fb62a71..4e5713c263 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift @@ -68,7 +68,7 @@ struct CIRcvDecryptionError: View { } } - @ViewBuilder private func viewBody() -> some View { + private func viewBody() -> some View { Group { if case let .direct(contact) = chat.chatInfo, let contactStats = contact.activeConn?.connectionStats { diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift index 632d4196c2..b27d266d8a 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift @@ -87,7 +87,7 @@ struct FramedItemView: View { .overlay(DetermineWidth()) .accessibilityLabel("") } - } + } .background { chatItemFrameColorMaybeImageOrVideo(chatItem, theme).modifier(ChatTailPadding()) } .onPreferenceChange(DetermineWidth.Key.self) { msgWidth = $0 } @@ -201,6 +201,7 @@ struct FramedItemView: View { } @ViewBuilder private func ciQuoteView(_ qi: CIQuote) -> some View { + let backgroundColor = chatItemFrameContextColor(chatItem, theme) let v = ZStack(alignment: .topTrailing) { switch (qi.content) { case let .image(_, image): @@ -242,7 +243,8 @@ struct FramedItemView: View { // if enable this always, size of the framed voice message item will be incorrect after end of playback .overlay { if case .voice = chatItem.content.msgContent {} else { DetermineWidth() } } .frame(minWidth: msgWidth, alignment: .leading) - .background(chatItemFrameContextColor(chatItem, theme)) + .background(backgroundColor) + .environment(\.containerBackground, UIColor(backgroundColor)) if let mediaWidth = maxMediaWidth(), mediaWidth < maxWidth { v.frame(maxWidth: mediaWidth, alignment: .leading) } else { @@ -308,6 +310,7 @@ struct FramedItemView: View { rightToLeft: rtl, prefix: txtPrefix ) + .environment(\.containerBackground, UIColor(chatItemFrameColor(ci, theme))) .multilineTextAlignment(rtl ? .trailing : .leading) .padding(.vertical, 6) .padding(.horizontal, 12) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift index d8dbd673f4..aab4177cbf 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift @@ -26,6 +26,7 @@ private func typing(_ theme: AppTheme, _ descr: UIFontDescriptor, _ ws: [UIFont. struct MsgContentView: View { @ObservedObject var chat: Chat @Environment(\.showTimestamp) var showTimestamp: Bool + @Environment(\.containerBackground) var containerBackground: UIColor @EnvironmentObject var theme: AppTheme var text: String var formattedText: [FormattedText]? = nil @@ -92,7 +93,8 @@ struct MsgContentView: View { @inline(__always) private func msgContentView() -> some View { - let s = messageText(text, formattedText, textStyle: textStyle, sender: sender, mentions: mentions, userMemberId: userMemberId, showSecrets: showSecrets, secondaryColor: theme.colors.secondary, prefix: prefix) + let r = messageText(text, formattedText, textStyle: textStyle, sender: sender, mentions: mentions, userMemberId: userMemberId, showSecrets: showSecrets, backgroundColor: containerBackground, prefix: prefix) + let s = r.string let t: Text if let mt = meta { if mt.isLive { @@ -102,7 +104,7 @@ struct MsgContentView: View { } else { t = Text(AttributedString(s)) } - return t.overlay(handleTextLinks(s, showSecrets: $showSecrets)) + return msgTextResultView(r, t, showSecrets: $showSecrets) } @inline(__always) @@ -118,7 +120,13 @@ struct MsgContentView: View { } } -func handleTextLinks(_ s: NSAttributedString, showSecrets: Binding>? = nil) -> some View { +func msgTextResultView(_ r: MsgTextResult, _ t: Text, showSecrets: Binding>? = nil) -> some View { + t.if(r.hasSecrets, transform: hiddenSecretsView) + .if(r.handleTaps) { $0.overlay(handleTextTaps(r.string, showSecrets: showSecrets)) } +} + +@inline(__always) +private func handleTextTaps(_ s: NSAttributedString, showSecrets: Binding>? = nil) -> some View { return GeometryReader { g in Rectangle() .fill(Color.clear) @@ -174,13 +182,43 @@ func handleTextLinks(_ s: NSAttributedString, showSecrets: Binding>? = } } +func hiddenSecretsView(_ v: V) -> some View { + v.overlay( + GeometryReader { g in + let size = (g.size.width + g.size.height) / 1.4142 + Image("vertical_logo") + .resizable(resizingMode: .tile) + .frame(width: size, height: size) + .rotationEffect(.degrees(45), anchor: .center) + .position(x: g.size.width / 2, y: g.size.height / 2) + .clipped() + .saturation(0.65) + .opacity(0.35) + } + .mask(v) + ) +} + private let linkAttrKey = NSAttributedString.Key("chat.simplex.app.link") private let webLinkAttrKey = NSAttributedString.Key("chat.simplex.app.webLink") private let secretAttrKey = NSAttributedString.Key("chat.simplex.app.secret") -func messageText(_ text: String, _ formattedText: [FormattedText]?, textStyle: UIFont.TextStyle = .body, sender: String?, preview: Bool = false, mentions: [String: CIMention]?, userMemberId: String?, showSecrets: Set?, secondaryColor: Color, prefix: NSAttributedString? = nil) -> NSMutableAttributedString { +typealias MsgTextResult = (string: NSMutableAttributedString, hasSecrets: Bool, handleTaps: Bool) + +func messageText( + _ text: String, + _ formattedText: [FormattedText]?, + textStyle: UIFont.TextStyle = .body, + sender: String?, + preview: Bool = false, + mentions: [String: CIMention]?, + userMemberId: String?, + showSecrets: Set?, + backgroundColor: UIColor, + prefix: NSAttributedString? = nil +) -> MsgTextResult { let res = NSMutableAttributedString() let descr = UIFontDescriptor.preferredFontDescriptor(withTextStyle: textStyle) let font = UIFont.preferredFont(forTextStyle: textStyle) @@ -188,7 +226,10 @@ func messageText(_ text: String, _ formattedText: [FormattedText]?, textStyle: U .font: font, .foregroundColor: UIColor.label ] + let secretColor = backgroundColor.withAlphaComponent(1) var link: [NSAttributedString.Key: Any]? + var hasSecrets = false + var handleTaps = false if let sender { if preview { @@ -230,14 +271,16 @@ func messageText(_ text: String, _ formattedText: [FormattedText]?, textStyle: U if let showSecrets { if !showSecrets.contains(secretIdx) { attrs[.foregroundColor] = UIColor.clear - attrs[.backgroundColor] = UIColor.secondarySystemFill // secretColor + attrs[.backgroundColor] = secretColor } attrs[secretAttrKey] = secretIdx secretIdx += 1 + handleTaps = true } else { attrs[.foregroundColor] = UIColor.clear - attrs[.backgroundColor] = UIColor.secondarySystemFill + attrs[.backgroundColor] = secretColor } + hasSecrets = true case let .colored(color): if let c = color.uiColor { attrs[.foregroundColor] = UIColor(c) @@ -247,11 +290,13 @@ func messageText(_ text: String, _ formattedText: [FormattedText]?, textStyle: U if !preview { attrs[linkAttrKey] = NSURL(string: ft.text) attrs[webLinkAttrKey] = true + handleTaps = true } case let .simplexLink(linkType, simplexUri, smpHosts): attrs = linkAttrs() if !preview { attrs[linkAttrKey] = NSURL(string: simplexUri) + handleTaps = true } if case .description = privacySimplexLinkModeDefault.get() { t = simplexLinkText(linkType, smpHosts) @@ -278,11 +323,13 @@ func messageText(_ text: String, _ formattedText: [FormattedText]?, textStyle: U attrs = linkAttrs() if !preview { attrs[linkAttrKey] = NSURL(string: "mailto:" + ft.text) + handleTaps = true } case .phone: attrs = linkAttrs() if !preview { attrs[linkAttrKey] = NSURL(string: "tel:" + t.replacingOccurrences(of: " ", with: "")) + handleTaps = true } case .none: () } @@ -292,7 +339,7 @@ func messageText(_ text: String, _ formattedText: [FormattedText]?, textStyle: U res.append(NSMutableAttributedString(string: text, attributes: plain)) } - return res + return (string: res, hasSecrets: hasSecrets, handleTaps: handleTaps) func linkAttrs() -> [NSAttributedString.Key: Any] { link = link ?? [ diff --git a/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift b/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift index 587957cd5d..dfc620c402 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift @@ -41,7 +41,7 @@ struct ChatItemForwardingView: View { .alert(item: $alert) { $0.alert } } - @ViewBuilder private func forwardListView() -> some View { + private func forwardListView() -> some View { VStack(alignment: .leading) { if !chatsToForwardTo.isEmpty { List { diff --git a/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift b/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift index 1b840a1547..cd75d1b0cd 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift @@ -131,9 +131,9 @@ struct ChatItemInfoView: View { } } - @ViewBuilder private func details() -> some View { + private func details() -> some View { let meta = ci.meta - VStack(alignment: .leading, spacing: 16) { + return VStack(alignment: .leading, spacing: 16) { Text(title) .font(.largeTitle) .bold() @@ -197,7 +197,7 @@ struct ChatItemInfoView: View { } } - @ViewBuilder private func historyTab() -> some View { + private func historyTab() -> some View { GeometryReader { g in let maxWidth = (g.size.width - 32) * 0.84 ScrollView { @@ -227,12 +227,13 @@ struct ChatItemInfoView: View { } } - @ViewBuilder private func itemVersionView(_ itemVersion: ChatItemVersion, _ maxWidth: CGFloat, current: Bool) -> some View { - VStack(alignment: .leading, spacing: 4) { - textBubble(itemVersion.msgContent.text, itemVersion.formattedText, nil) + private func itemVersionView(_ itemVersion: ChatItemVersion, _ maxWidth: CGFloat, current: Bool) -> some View { + let backgroundColor = chatItemFrameColor(ci, theme) + return VStack(alignment: .leading, spacing: 4) { + textBubble(itemVersion.msgContent.text, itemVersion.formattedText, nil, backgroundColor: UIColor(backgroundColor)) .padding(.horizontal, 12) .padding(.vertical, 6) - .background(chatItemFrameColor(ci, theme)) + .background(backgroundColor) .modifier(ChatItemClipped()) .contextMenu { if itemVersion.msgContent.text != "" { @@ -257,9 +258,9 @@ struct ChatItemInfoView: View { .frame(maxWidth: maxWidth, alignment: .leading) } - @ViewBuilder private func textBubble(_ text: String, _ formattedText: [FormattedText]?, _ sender: String? = nil) -> some View { + @ViewBuilder private func textBubble(_ text: String, _ formattedText: [FormattedText]?, _ sender: String? = nil, backgroundColor: UIColor) -> some View { if text != "" { - TextBubble(text: text, formattedText: formattedText, sender: sender, mentions: ci.mentions, userMemberId: userMemberId) + TextBubble(text: text, formattedText: formattedText, sender: sender, mentions: ci.mentions, userMemberId: userMemberId, backgroundColor: backgroundColor) } else { Text("no text") .italic() @@ -274,15 +275,16 @@ struct ChatItemInfoView: View { var sender: String? = nil var mentions: [String: CIMention]? var userMemberId: String? + var backgroundColor: UIColor @State private var showSecrets: Set = [] var body: some View { - let s = messageText(text, formattedText, sender: sender, mentions: mentions, userMemberId: userMemberId, showSecrets: showSecrets, secondaryColor: theme.colors.secondary) - Text(AttributedString(s)).overlay(handleTextLinks(s, showSecrets: $showSecrets)) + let r = messageText(text, formattedText, sender: sender, mentions: mentions, userMemberId: userMemberId, showSecrets: showSecrets, backgroundColor: backgroundColor) + return msgTextResultView(r, Text(AttributedString(r.string)), showSecrets: $showSecrets) } } - @ViewBuilder private func quoteTab(_ qi: CIQuote) -> some View { + private func quoteTab(_ qi: CIQuote) -> some View { GeometryReader { g in let maxWidth = (g.size.width - 32) * 0.84 ScrollView { @@ -300,9 +302,10 @@ struct ChatItemInfoView: View { } } - @ViewBuilder private func quotedMsgView(_ qi: CIQuote, _ maxWidth: CGFloat) -> some View { - VStack(alignment: .leading, spacing: 4) { - textBubble(qi.text, qi.formattedText, qi.getSender(nil)) + private func quotedMsgView(_ qi: CIQuote, _ maxWidth: CGFloat) -> some View { + let backgroundColor = quotedMsgFrameColor(qi, theme) + return VStack(alignment: .leading, spacing: 4) { + textBubble(qi.text, qi.formattedText, qi.getSender(nil), backgroundColor: UIColor(backgroundColor)) .padding(.horizontal, 12) .padding(.vertical, 6) .background(quotedMsgFrameColor(qi, theme)) @@ -335,7 +338,7 @@ struct ChatItemInfoView: View { : theme.appColors.receivedMessage } - @ViewBuilder private func forwardedFromTab(_ forwardedFromItem: AChatItem) -> some View { + private func forwardedFromTab(_ forwardedFromItem: AChatItem) -> some View { ScrollView { VStack(alignment: .leading, spacing: 16) { details() @@ -373,7 +376,7 @@ struct ChatItemInfoView: View { } } - @ViewBuilder private func forwardedFromSender(_ forwardedFromItem: AChatItem) -> some View { + private func forwardedFromSender(_ forwardedFromItem: AChatItem) -> some View { HStack { ChatInfoImage(chat: Chat(chatInfo: forwardedFromItem.chatInfo), size: 48) .padding(.trailing, 6) @@ -404,7 +407,7 @@ struct ChatItemInfoView: View { } } - @ViewBuilder private func deliveryTab(_ memberDeliveryStatuses: [MemberDeliveryStatus]) -> some View { + private func deliveryTab(_ memberDeliveryStatuses: [MemberDeliveryStatus]) -> some View { ScrollView { VStack(alignment: .leading, spacing: 16) { details() @@ -419,7 +422,7 @@ struct ChatItemInfoView: View { .frame(maxHeight: .infinity, alignment: .top) } - @ViewBuilder private func memberDeliveryStatusesView(_ memberDeliveryStatuses: [MemberDeliveryStatus]) -> some View { + private func memberDeliveryStatusesView(_ memberDeliveryStatuses: [MemberDeliveryStatus]) -> some View { LazyVStack(alignment: .leading, spacing: 12) { let mss = membersStatuses(memberDeliveryStatuses) if !mss.isEmpty { diff --git a/apps/ios/Shared/Views/Chat/ChatItemView.swift b/apps/ios/Shared/Views/Chat/ChatItemView.swift index 5d09e153d5..f5558bcd93 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemView.swift @@ -18,6 +18,10 @@ extension EnvironmentValues { static let defaultValue: Bool = true } + struct ContainerBackground: EnvironmentKey { + static let defaultValue: UIColor = .clear + } + var showTimestamp: Bool { get { self[ShowTimestamp.self] } set { self[ShowTimestamp.self] = newValue } @@ -27,6 +31,11 @@ extension EnvironmentValues { get { self[Revealed.self] } set { self[Revealed.self] = newValue } } + + var containerBackground: UIColor { + get { self[ContainerBackground.self] } + set { self[ContainerBackground.self] = newValue } + } } struct ChatItemView: View { diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 1349996683..fc80eb6dec 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -71,10 +71,9 @@ struct ChatView: View { } } - @ViewBuilder private var viewBody: some View { let cInfo = chat.chatInfo - ZStack { + return ZStack { let wallpaperImage = theme.wallpaper.type.image let wallpaperType = theme.wallpaper.type let backgroundColor = theme.wallpaper.background ?? wallpaperType.defaultBackgroundColor(theme.base, theme.colors.background) @@ -1528,9 +1527,9 @@ struct ChatView: View { } } - @ViewBuilder func chatItemWithMenu(_ ci: ChatItem, _ range: ClosedRange?, _ maxWidth: CGFloat, _ itemSeparation: ItemSeparation) -> some View { + func chatItemWithMenu(_ ci: ChatItem, _ range: ClosedRange?, _ maxWidth: CGFloat, _ itemSeparation: ItemSeparation) -> some View { let alignment: Alignment = ci.chatDir.sent ? .trailing : .leading - VStack(alignment: alignment.horizontal, spacing: 3) { + return VStack(alignment: alignment.horizontal, spacing: 3) { HStack { if ci.chatDir.sent { goToItemButton(true) diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift index d45cc9abc4..845442c75f 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift @@ -70,8 +70,10 @@ struct ContextItemView: View { .lineLimit(lines) } - private func contextMsgPreview(_ contextItem: ChatItem) -> Text { - return attachment() + Text(AttributedString(messageText(contextItem.text, contextItem.formattedText, sender: nil, preview: true, mentions: contextItem.mentions, userMemberId: nil, showSecrets: nil, secondaryColor: theme.colors.secondary))) + private func contextMsgPreview(_ contextItem: ChatItem) -> some View { + let r = messageText(contextItem.text, contextItem.formattedText, sender: nil, preview: true, mentions: contextItem.mentions, userMemberId: nil, showSecrets: nil, backgroundColor: UIColor(background)) + let t = attachment() + Text(AttributedString(r.string)) + return t.if(r.hasSecrets, transform: hiddenSecretsView) func attachment() -> Text { let isFileLoaded = if let fileSource = getLoadedFileSource(contextItem.file) { diff --git a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift index 66fe67a29e..7cd543af10 100644 --- a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift +++ b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift @@ -145,9 +145,9 @@ struct AddGroupMembersViewCommon: View { return dummy }() - @ViewBuilder private func inviteMembersButton() -> some View { + private func inviteMembersButton() -> some View { let label: LocalizedStringKey = groupInfo.businessChat == nil ? "Invite to group" : "Invite to chat" - Button { + return Button { inviteMembers() } label: { HStack { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index 9fa07bc391..96a4981be0 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -292,9 +292,9 @@ struct GroupChatInfoView: View { .disabled(!groupInfo.ready || chat.chatItems.isEmpty) } - @ViewBuilder private func addMembersActionButton(width: CGFloat) -> some View { - if chat.chatInfo.incognito { - ZStack { + private func addMembersActionButton(width: CGFloat) -> some View { + ZStack { + if chat.chatInfo.incognito { InfoViewButton(image: "link.badge.plus", title: "invite", width: width) { groupLinkNavLinkActive = true } @@ -306,10 +306,7 @@ struct GroupChatInfoView: View { } .frame(width: 1, height: 1) .hidden() - } - .disabled(!groupInfo.ready) - } else { - ZStack { + } else { InfoViewButton(image: "person.fill.badge.plus", title: "invite", width: width) { addMembersNavLinkActive = true } @@ -322,8 +319,8 @@ struct GroupChatInfoView: View { .frame(width: 1, height: 1) .hidden() } - .disabled(!groupInfo.ready) } + .disabled(!groupInfo.ready) } private func muteButton(width: CGFloat, nextNtfMode: MsgFilter) -> some View { @@ -569,9 +566,9 @@ struct GroupChatInfoView: View { } } - @ViewBuilder private func leaveGroupButton() -> some View { + private func leaveGroupButton() -> some View { let label: LocalizedStringKey = groupInfo.businessChat == nil ? "Leave group" : "Leave chat" - Button(role: .destructive) { + return Button(role: .destructive) { alert = .leaveGroupAlert } label: { Label(label, systemImage: "rectangle.portrait.and.arrow.right") diff --git a/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift b/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift index 4dd2b9e683..97bff70efb 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift @@ -18,6 +18,7 @@ struct GroupWelcomeView: View { @State private var editMode = true @FocusState private var keyboardVisible: Bool @State private var showSaveDialog = false + @State private var showSecrets: Set = [] let maxByteCount = 1200 @@ -58,9 +59,8 @@ struct GroupWelcomeView: View { } private func textPreview() -> some View { - let s = messageText(welcomeText, parseSimpleXMarkdown(welcomeText), sender: nil, mentions: nil, userMemberId: nil, showSecrets: nil, secondaryColor: theme.colors.secondary) - return Text(AttributedString(s)) - .overlay(handleTextLinks(s)) + let r = messageText(welcomeText, parseSimpleXMarkdown(welcomeText), sender: nil, mentions: nil, userMemberId: nil, showSecrets: showSecrets, backgroundColor: UIColor(theme.colors.background)) + return msgTextResultView(r, Text(AttributedString(r.string)), showSecrets: $showSecrets) .frame(minHeight: 130, alignment: .topLeading) .frame(maxWidth: .infinity, alignment: .leading) } diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index f5234ed331..f9cf5e98e4 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -90,7 +90,7 @@ struct ChatListNavLink: View { .actionSheet(item: $actionSheet) { $0.actionSheet } } - @ViewBuilder private func contactNavLink(_ contact: Contact) -> some View { + private func contactNavLink(_ contact: Contact) -> some View { Group { if contact.activeConn == nil && contact.profile.contactLink != nil && contact.active { ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) @@ -243,7 +243,7 @@ struct ChatListNavLink: View { } } - @ViewBuilder private func noteFolderNavLink(_ noteFolder: NoteFolder) -> some View { + private func noteFolderNavLink(_ noteFolder: NoteFolder) -> some View { NavLinkPlain( chatId: chat.chatInfo.id, selection: $chatModel.chatId, diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 93c18f28cc..5c491b6303 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -335,9 +335,9 @@ struct ChatListView: View { } } - @ViewBuilder private var chatList: some View { + private var chatList: some View { let cs = filteredChats() - ZStack { + return ZStack { ScrollViewReader { scrollProxy in List { if !chatModel.chats.isEmpty { @@ -804,7 +804,7 @@ struct TagsView: View { } } - @ViewBuilder private func expandedPresetTagsFiltersView() -> some View { + private func expandedPresetTagsFiltersView() -> some View { ForEach(PresetTag.allCases, id: \.id) { tag in if (chatTagsModel.presetTags[tag] ?? 0) > 0 { expandedTagFilterView(tag) diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift index c4b584061d..b8c8233e6e 100644 --- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift @@ -187,13 +187,14 @@ struct ChatPreviewView: View { .kerning(-2) } - private func chatPreviewLayout(_ text: Text?, draft: Bool = false, _ hasFilePreview: Bool = false) -> some View { + private func chatPreviewLayout(_ text: Text?, draft: Bool = false, hasFilePreview: Bool = false, hasSecrets: Bool) -> some View { ZStack(alignment: .topTrailing) { let s = chat.chatStats let mentionWidth: CGFloat = if s.unreadMentions > 0 && s.unreadCount > 1 { dynamicSize(userFont).unreadCorner } else { 0 } let t = text .lineLimit(userFont <= .xxxLarge ? 2 : 1) .multilineTextAlignment(.leading) + .if(hasSecrets, transform: hiddenSecretsView) .frame(maxWidth: .infinity, alignment: .topLeading) .padding(.leading, hasFilePreview ? 0 : 8) .padding(.trailing, mentionWidth + (hasFilePreview ? 38 : 36)) @@ -259,11 +260,13 @@ struct ChatPreviewView: View { } } - private func messageDraft(_ draft: ComposeState) -> Text { + private func messageDraft(_ draft: ComposeState) -> (Text, Bool) { let msg = draft.message - return image("rectangle.and.pencil.and.ellipsis", color: theme.colors.primary) - + attachment() - + Text(AttributedString(messageText(msg, parseSimpleXMarkdown(msg), sender: nil, preview: true, mentions: draft.mentions, userMemberId: nil, showSecrets: nil, secondaryColor: theme.colors.secondary))) + let r = messageText(msg, parseSimpleXMarkdown(msg), sender: nil, preview: true, mentions: draft.mentions, userMemberId: nil, showSecrets: nil, backgroundColor: UIColor(theme.colors.background)) + return (image("rectangle.and.pencil.and.ellipsis", color: theme.colors.primary) + + attachment() + + Text(AttributedString(r.string)), + r.hasSecrets) func image(_ s: String, color: Color = Color(uiColor: .tertiaryLabel)) -> Text { Text(Image(systemName: s)).foregroundColor(color) + textSpace @@ -279,10 +282,11 @@ struct ChatPreviewView: View { } } - func chatItemPreview(_ cItem: ChatItem) -> Text { + func chatItemPreview(_ cItem: ChatItem) -> (Text, Bool) { let itemText = cItem.meta.itemDeleted == nil ? cItem.text : markedDeletedText() let itemFormattedText = cItem.meta.itemDeleted == nil ? cItem.formattedText : nil - return Text(AttributedString(messageText(itemText, itemFormattedText, sender: cItem.memberDisplayName, preview: true, mentions: cItem.mentions, userMemberId: chat.chatInfo.groupInfo?.membership.memberId, showSecrets: nil, secondaryColor: theme.colors.secondary, prefix: prefix()))) + let r = messageText(itemText, itemFormattedText, sender: cItem.memberDisplayName, preview: true, mentions: cItem.mentions, userMemberId: chat.chatInfo.groupInfo?.membership.memberId, showSecrets: nil, backgroundColor: UIColor(theme.colors.background), prefix: prefix()) + return (Text(AttributedString(r.string)), r.hasSecrets) // same texts are in markedDeletedText in MarkedDeletedItemView, but it returns LocalizedStringKey; // can be refactored into a single function if functions calling these are changed to return same type @@ -319,9 +323,11 @@ struct ChatPreviewView: View { @ViewBuilder private func chatMessagePreview(_ cItem: ChatItem?, _ hasFilePreview: Bool = false) -> some View { if chatModel.draftChatId == chat.id, let draft = chatModel.draft { - chatPreviewLayout(messageDraft(draft), draft: true, hasFilePreview) + let (t, hasSecrets) = messageDraft(draft) + chatPreviewLayout(t, draft: true, hasFilePreview: hasFilePreview, hasSecrets: hasSecrets) } else if let cItem = cItem { - chatPreviewLayout(itemStatusMark(cItem) + chatItemPreview(cItem), hasFilePreview) + let (t, hasSecrets) = chatItemPreview(cItem) + chatPreviewLayout(itemStatusMark(cItem) + t, hasFilePreview: hasFilePreview, hasSecrets: hasSecrets) } else { switch (chat.chatInfo) { case let .direct(contact): @@ -399,7 +405,7 @@ struct ChatPreviewView: View { : chatPreviewInfoText("you are invited to group") } - @ViewBuilder private func chatPreviewInfoText(_ text: LocalizedStringKey) -> some View { + private func chatPreviewInfoText(_ text: LocalizedStringKey) -> some View { Text(text) .frame(maxWidth: .infinity, minHeight: 44, maxHeight: 44, alignment: .topLeading) .padding([.leading, .trailing], 8) @@ -479,7 +485,7 @@ struct ChatPreviewView: View { } } -@ViewBuilder func groupReportsIcon(size: CGFloat) -> some View { +func groupReportsIcon(size: CGFloat) -> some View { Image(systemName: "flag") .resizable() .scaledToFit() diff --git a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift index 4dbdc81620..8b0a8af888 100644 --- a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift +++ b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift @@ -245,7 +245,7 @@ struct ServersSummaryView: View { } } - @ViewBuilder private func smpServersListView( + private func smpServersListView( _ servers: [SMPServerSummary], _ statsStartedAt: Date, _ header: LocalizedStringKey? = nil, @@ -256,7 +256,7 @@ struct ServersSummaryView: View { ? serverAddress($0.smpServer) < serverAddress($1.smpServer) : $0.hasSubs && !$1.hasSubs } - Section { + return Section { ForEach(sortedServers) { server in smpServerView(server, statsStartedAt) } @@ -318,14 +318,14 @@ struct ServersSummaryView: View { return onionHosts == .require ? .indigo : .accentColor } - @ViewBuilder private func xftpServersListView( + private func xftpServersListView( _ servers: [XFTPServerSummary], _ statsStartedAt: Date, _ header: LocalizedStringKey? = nil, _ footer: LocalizedStringKey? = nil ) -> some View { let sortedServers = servers.sorted { serverAddress($0.xftpServer) < serverAddress($1.xftpServer) } - Section { + return Section { ForEach(sortedServers) { server in xftpServerView(server, statsStartedAt) } diff --git a/apps/ios/Shared/Views/ChatList/TagListView.swift b/apps/ios/Shared/Views/ChatList/TagListView.swift index 74ed9534e0..2063fe15de 100644 --- a/apps/ios/Shared/Views/ChatList/TagListView.swift +++ b/apps/ios/Shared/Views/ChatList/TagListView.swift @@ -138,7 +138,7 @@ struct TagListView: View { } } - @ViewBuilder private func radioButton(selected: Bool) -> some View { + private func radioButton(selected: Bool) -> some View { Image(systemName: selected ? "checkmark.circle.fill" : "circle") .imageScale(.large) .foregroundStyle(selected ? Color.accentColor : Color(.tertiaryLabel)) diff --git a/apps/ios/Shared/Views/Contacts/ContactListNavLink.swift b/apps/ios/Shared/Views/Contacts/ContactListNavLink.swift index fe840006cd..456c46d318 100644 --- a/apps/ios/Shared/Views/Contacts/ContactListNavLink.swift +++ b/apps/ios/Shared/Views/Contacts/ContactListNavLink.swift @@ -140,9 +140,9 @@ struct ContactListNavLink: View { } } - @ViewBuilder private func previewTitle(_ contact: Contact, titleColor: Color) -> some View { + private func previewTitle(_ contact: Contact, titleColor: Color) -> some View { let t = Text(chat.chatInfo.chatViewName).foregroundColor(titleColor) - ( + return ( contact.verified == true ? verifiedIcon + t : t diff --git a/apps/ios/Shared/Views/Database/DatabaseErrorView.swift b/apps/ios/Shared/Views/Database/DatabaseErrorView.swift index 1ded0acc90..02a1b87826 100644 --- a/apps/ios/Shared/Views/Database/DatabaseErrorView.swift +++ b/apps/ios/Shared/Views/Database/DatabaseErrorView.swift @@ -28,7 +28,7 @@ struct DatabaseErrorView: View { } } - @ViewBuilder private func databaseErrorView() -> some View { + private func databaseErrorView() -> some View { VStack(alignment: .center, spacing: 20) { switch status { case let .errorNotADatabase(dbFile): diff --git a/apps/ios/Shared/Views/LocalAuth/PasscodeEntry.swift b/apps/ios/Shared/Views/LocalAuth/PasscodeEntry.swift index 609943bcb6..4a6f8e7549 100644 --- a/apps/ios/Shared/Views/LocalAuth/PasscodeEntry.swift +++ b/apps/ios/Shared/Views/LocalAuth/PasscodeEntry.swift @@ -28,7 +28,7 @@ struct PasscodeEntry: View { } } - @ViewBuilder private func passwordView() -> some View { + private func passwordView() -> some View { Text( password == "" ? " " diff --git a/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift b/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift index 39656c1534..e5263813fa 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift @@ -85,7 +85,7 @@ struct NewChatSheet: View { } } - @ViewBuilder private func viewBody(_ showArchive: Bool) -> some View { + private func viewBody(_ showArchive: Bool) -> some View { List { HStack { ContactsListSearchBar( @@ -258,7 +258,7 @@ struct ContactsList: View { } } - @ViewBuilder private func noResultSection(text: String) -> some View { + private func noResultSection(text: String) -> some View { Section { Text(text) .foregroundColor(theme.colors.secondary) diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift index 2524b5e682..110eda7882 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatView.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -506,7 +506,7 @@ private struct ActiveProfilePicker: View { } } - @ViewBuilder private func profilerPickerUserOption(_ user: User) -> some View { + private func profilerPickerUserOption(_ user: User) -> some View { Button { if selectedProfile == user && incognitoEnabled { incognitoEnabled = false diff --git a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift index 45ef186671..17e1735472 100644 --- a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift +++ b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift @@ -304,11 +304,11 @@ struct ChooseServerOperators: View { } } - @ViewBuilder private func operatorCheckView(_ serverOperator: ServerOperator) -> some View { + private func operatorCheckView(_ serverOperator: ServerOperator) -> some View { let checked = selectedOperatorIds.contains(serverOperator.operatorId) let icon = checked ? "checkmark.circle.fill" : "circle" let iconColor = checked ? theme.colors.primary : Color(uiColor: .tertiaryLabel).asAnotherColorFromSecondary(theme) - HStack(spacing: 10) { + return HStack(spacing: 10) { Image(serverOperator.largeLogo(colorScheme)) .resizable() .scaledToFit() diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift index 24da6a94a8..afbccc109c 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift @@ -38,9 +38,9 @@ struct OperatorView: View { .allowsHitTesting(!testing) } - @ViewBuilder private func operatorView() -> some View { + private func operatorView() -> some View { let duplicateHosts = findDuplicateHosts(serverErrors) - VStack { + return VStack { List { Section { infoViewLink() @@ -500,14 +500,14 @@ struct SingleOperatorUsageConditionsView: View { } } - @ViewBuilder private func acceptConditionsButton() -> some View { + private func acceptConditionsButton() -> some View { let operatorIds = ChatModel.shared.conditions.serverOperators .filter { $0.operatorId == userServers[operatorIndex].operator_.operatorId || // Opened operator ($0.enabled && !$0.conditionsAcceptance.conditionsAccepted) // Other enabled operators with conditions not accepted } .map { $0.operatorId } - Button { + return Button { acceptForOperators(operatorIds, operatorIndex) } label: { Text("Accept conditions") diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift index ed3c5c773c..b9737914ec 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift @@ -38,9 +38,9 @@ struct YourServersView: View { .allowsHitTesting(!testing) } - @ViewBuilder private func yourServersView() -> some View { + private func yourServersView() -> some View { let duplicateHosts = findDuplicateHosts(serverErrors) - List { + return List { if !userServers[operatorIndex].smpServers.filter({ !$0.deleted }).isEmpty { Section { ForEach($userServers[operatorIndex].smpServers) { srv in diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index 3321b25793..e06b1c4dd3 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -280,159 +280,159 @@ struct SettingsView: View { } } - @ViewBuilder func settingsView() -> some View { - let user = chatModel.currentUser - List { - Section(header: Text("Settings").foregroundColor(theme.colors.secondary)) { - NavigationLink { - NotificationsView() - .navigationTitle("Notifications") - .modifier(ThemedBackground(grouped: true)) - } label: { - HStack { - notificationsIcon() - Text("Notifications") - } - } - .disabled(chatModel.chatRunning != true) - - NavigationLink { - NetworkAndServers() - .navigationTitle("Network & servers") - .modifier(ThemedBackground(grouped: true)) - } label: { - settingsRow("externaldrive.connected.to.line.below", color: theme.colors.secondary) { Text("Network & servers") } - } - .disabled(chatModel.chatRunning != true) - - NavigationLink { - CallSettings() - .navigationTitle("Your calls") - .modifier(ThemedBackground(grouped: true)) - } label: { - settingsRow("video", color: theme.colors.secondary) { Text("Audio & video calls") } - } - .disabled(chatModel.chatRunning != true) - - NavigationLink { - PrivacySettings() - .navigationTitle("Your privacy") - .modifier(ThemedBackground(grouped: true)) - } label: { - settingsRow("lock", color: theme.colors.secondary) { Text("Privacy & security") } - } - .disabled(chatModel.chatRunning != true) - - if UIApplication.shared.supportsAlternateIcons { - NavigationLink { - AppearanceSettings() - .navigationTitle("Appearance") - .modifier(ThemedBackground(grouped: true)) - } label: { - settingsRow("sun.max", color: theme.colors.secondary) { Text("Appearance") } - } - .disabled(chatModel.chatRunning != true) + func settingsView() -> some View { + List { + let user = chatModel.currentUser + Section(header: Text("Settings").foregroundColor(theme.colors.secondary)) { + NavigationLink { + NotificationsView() + .navigationTitle("Notifications") + .modifier(ThemedBackground(grouped: true)) + } label: { + HStack { + notificationsIcon() + Text("Notifications") } } + .disabled(chatModel.chatRunning != true) - Section(header: Text("Chat database").foregroundColor(theme.colors.secondary)) { - chatDatabaseRow() - NavigationLink { - MigrateFromDevice(showProgressOnSettings: $showProgress) - .toolbar { - // Redaction broken for `.navigationTitle` - using a toolbar item instead. - ToolbarItem(placement: .principal) { - Text("Migrate device").font(.headline) - } - } - .modifier(ThemedBackground(grouped: true)) - .navigationBarTitleDisplayMode(.large) - } label: { - settingsRow("tray.and.arrow.up", color: theme.colors.secondary) { Text("Migrate to another device") } - } + NavigationLink { + NetworkAndServers() + .navigationTitle("Network & servers") + .modifier(ThemedBackground(grouped: true)) + } label: { + settingsRow("externaldrive.connected.to.line.below", color: theme.colors.secondary) { Text("Network & servers") } } - - Section(header: Text("Help").foregroundColor(theme.colors.secondary)) { - if let user = user { - NavigationLink { - ChatHelp(dismissSettingsSheet: dismiss) - .navigationTitle("Welcome \(user.displayName)!") - .modifier(ThemedBackground()) - .frame(maxHeight: .infinity, alignment: .top) - } label: { - settingsRow("questionmark", color: theme.colors.secondary) { Text("How to use it") } - } - } + .disabled(chatModel.chatRunning != true) + + NavigationLink { + CallSettings() + .navigationTitle("Your calls") + .modifier(ThemedBackground(grouped: true)) + } label: { + settingsRow("video", color: theme.colors.secondary) { Text("Audio & video calls") } + } + .disabled(chatModel.chatRunning != true) + + NavigationLink { + PrivacySettings() + .navigationTitle("Your privacy") + .modifier(ThemedBackground(grouped: true)) + } label: { + settingsRow("lock", color: theme.colors.secondary) { Text("Privacy & security") } + } + .disabled(chatModel.chatRunning != true) + + if UIApplication.shared.supportsAlternateIcons { NavigationLink { - WhatsNewView(viaSettings: true, updatedConditions: false) - .modifier(ThemedBackground()) - .navigationBarTitleDisplayMode(.inline) + AppearanceSettings() + .navigationTitle("Appearance") + .modifier(ThemedBackground(grouped: true)) } label: { - settingsRow("plus", color: theme.colors.secondary) { Text("What's new") } + settingsRow("sun.max", color: theme.colors.secondary) { Text("Appearance") } } + .disabled(chatModel.chatRunning != true) + } + } + + Section(header: Text("Chat database").foregroundColor(theme.colors.secondary)) { + chatDatabaseRow() + NavigationLink { + MigrateFromDevice(showProgressOnSettings: $showProgress) + .toolbar { + // Redaction broken for `.navigationTitle` - using a toolbar item instead. + ToolbarItem(placement: .principal) { + Text("Migrate device").font(.headline) + } + } + .modifier(ThemedBackground(grouped: true)) + .navigationBarTitleDisplayMode(.large) + } label: { + settingsRow("tray.and.arrow.up", color: theme.colors.secondary) { Text("Migrate to another device") } + } + } + + Section(header: Text("Help").foregroundColor(theme.colors.secondary)) { + if let user = user { NavigationLink { - SimpleXInfo(onboarding: false) - .navigationBarTitle("", displayMode: .inline) + ChatHelp(dismissSettingsSheet: dismiss) + .navigationTitle("Welcome \(user.displayName)!") .modifier(ThemedBackground()) .frame(maxHeight: .infinity, alignment: .top) } label: { - settingsRow("info", color: theme.colors.secondary) { Text("About SimpleX Chat") } + settingsRow("questionmark", color: theme.colors.secondary) { Text("How to use it") } } - settingsRow("number", color: theme.colors.secondary) { - Button("Send questions and ideas") { - dismiss() - DispatchQueue.main.async { - UIApplication.shared.open(simplexTeamURL) - } + } + NavigationLink { + WhatsNewView(viaSettings: true, updatedConditions: false) + .modifier(ThemedBackground()) + .navigationBarTitleDisplayMode(.inline) + } label: { + settingsRow("plus", color: theme.colors.secondary) { Text("What's new") } + } + NavigationLink { + SimpleXInfo(onboarding: false) + .navigationBarTitle("", displayMode: .inline) + .modifier(ThemedBackground()) + .frame(maxHeight: .infinity, alignment: .top) + } label: { + settingsRow("info", color: theme.colors.secondary) { Text("About SimpleX Chat") } + } + settingsRow("number", color: theme.colors.secondary) { + Button("Send questions and ideas") { + dismiss() + DispatchQueue.main.async { + UIApplication.shared.open(simplexTeamURL) } } - .disabled(chatModel.chatRunning != true) - settingsRow("envelope", color: theme.colors.secondary) { Text("[Send us email](mailto:chat@simplex.chat)") } } + .disabled(chatModel.chatRunning != true) + settingsRow("envelope", color: theme.colors.secondary) { Text("[Send us email](mailto:chat@simplex.chat)") } + } - Section(header: Text("Support SimpleX Chat").foregroundColor(theme.colors.secondary)) { - settingsRow("keyboard", color: theme.colors.secondary) { Text("[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)") } - settingsRow("star", color: theme.colors.secondary) { - Button("Rate the app") { - if let scene = sceneDelegate.windowScene { - SKStoreReviewController.requestReview(in: scene) - } + Section(header: Text("Support SimpleX Chat").foregroundColor(theme.colors.secondary)) { + settingsRow("keyboard", color: theme.colors.secondary) { Text("[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)") } + settingsRow("star", color: theme.colors.secondary) { + Button("Rate the app") { + if let scene = sceneDelegate.windowScene { + SKStoreReviewController.requestReview(in: scene) } } - ZStack(alignment: .leading) { - Image(colorScheme == .dark ? "github_light" : "github") - .resizable() - .frame(width: 24, height: 24) - .opacity(0.5) - .colorMultiply(theme.colors.secondary) - Text("[Star on GitHub](https://github.com/simplex-chat/simplex-chat)") - .padding(.leading, indent) - } } + ZStack(alignment: .leading) { + Image(colorScheme == .dark ? "github_light" : "github") + .resizable() + .frame(width: 24, height: 24) + .opacity(0.5) + .colorMultiply(theme.colors.secondary) + Text("[Star on GitHub](https://github.com/simplex-chat/simplex-chat)") + .padding(.leading, indent) + } + } - Section(header: Text("Develop").foregroundColor(theme.colors.secondary)) { - NavigationLink { - DeveloperView() - .navigationTitle("Developer tools") - .modifier(ThemedBackground(grouped: true)) - } label: { - settingsRow("chevron.left.forwardslash.chevron.right", color: theme.colors.secondary) { Text("Developer tools") } - } - NavigationLink { - VersionView() - .navigationBarTitle("App version") - .modifier(ThemedBackground()) - } label: { - Text("v\(appVersion ?? "?") (\(appBuild ?? "?"))") - } + Section(header: Text("Develop").foregroundColor(theme.colors.secondary)) { + NavigationLink { + DeveloperView() + .navigationTitle("Developer tools") + .modifier(ThemedBackground(grouped: true)) + } label: { + settingsRow("chevron.left.forwardslash.chevron.right", color: theme.colors.secondary) { Text("Developer tools") } + } + NavigationLink { + VersionView() + .navigationBarTitle("App version") + .modifier(ThemedBackground()) + } label: { + Text("v\(appVersion ?? "?") (\(appBuild ?? "?"))") } } - .navigationTitle("Your settings") - .modifier(ThemedBackground(grouped: true)) - .onDisappear { - chatModel.showingTerminal = false - chatModel.terminalItems = [] - } + } + .navigationTitle("Your settings") + .modifier(ThemedBackground(grouped: true)) + .onDisappear { + chatModel.showingTerminal = false + chatModel.terminalItems = [] + } } private func chatDatabaseRow() -> some View { diff --git a/apps/ios/Shared/Views/UserSettings/UserProfile.swift b/apps/ios/Shared/Views/UserSettings/UserProfile.swift index 8a70efbe82..9aa42930bf 100644 --- a/apps/ios/Shared/Views/UserSettings/UserProfile.swift +++ b/apps/ios/Shared/Views/UserSettings/UserProfile.swift @@ -133,7 +133,6 @@ struct UserProfile: View { .alert(item: $alert) { a in userProfileAlert(a, $profile.displayName) } } - @ViewBuilder private func overlayButton( _ systemName: String, edge: Edge.Set, diff --git a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift index 781ea4bc34..887023b670 100644 --- a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift @@ -221,11 +221,11 @@ struct UserProfilesView: View { !user.hidden ? nil : trimmedSearchTextOrPassword } - @ViewBuilder private func profileActionView(_ action: UserProfileAction) -> some View { + private func profileActionView(_ action: UserProfileAction) -> some View { let passwordValid = actionPassword == actionPassword.trimmingCharacters(in: .whitespaces) let passwordField = PassphraseField(key: $actionPassword, placeholder: "Profile password", valid: passwordValid) let actionEnabled: (User) -> Bool = { user in actionPassword != "" && passwordValid && correctPassword(user, actionPassword) } - List { + return List { switch action { case let .deleteUser(user, delSMPQueues): actionHeader("Delete profile", user) diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 926eb7e20f..56553b3283 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -5290,8 +5290,8 @@ Requires compatible VPN. Отвори група No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index 32403dba44..21bf0aef60 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -5093,8 +5093,8 @@ Vyžaduje povolení sítě VPN. Open group No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index b4751e1014..6e834157df 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -5572,8 +5572,8 @@ Dies erfordert die Aktivierung eines VPNs. Gruppe öffnen No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index a4a0c07410..641af86c2a 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -5573,9 +5573,9 @@ Requires compatible VPN. Open group No comment provided by engineer. - - Open in browser? - Open in browser? + + Open link? + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 849cff5ebc..f5226df190 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -5572,8 +5572,8 @@ Requiere activación de la VPN. Grupo abierto No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index 7e493d5f73..5281fbc701 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -5069,8 +5069,8 @@ Edellyttää VPN:n sallimista. Open group No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 80d5ae5f55..1e5da0b0ed 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -5537,8 +5537,8 @@ Nécessite l'activation d'un VPN. Ouvrir le groupe No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 3d3d8c6383..c8a29ede41 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -5572,8 +5572,8 @@ VPN engedélyezése szükséges. Csoport megnyitása No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index c672096082..1bfccb3b06 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -5572,8 +5572,8 @@ Richiede l'attivazione della VPN. Apri gruppo No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 4dd0d467a1..26f415dd13 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -5146,8 +5146,8 @@ VPN を有効にする必要があります。 Open group No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 43d568f747..681502e255 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -5572,8 +5572,8 @@ Vereist het inschakelen van VPN. Open groep No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index f8a73a220d..01bc0b8508 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -5449,8 +5449,8 @@ Wymaga włączenia VPN. Grupa otwarta No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 2b4a426130..a7b63e38ba 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -5538,8 +5538,8 @@ Requires compatible VPN. Открыть группу No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index 0398c37c8c..be68dc9780 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -5048,8 +5048,8 @@ Requires compatible VPN. Open group No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index b1f65a1791..6eb1daf84b 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -5462,8 +5462,8 @@ VPN'nin etkinleştirilmesi gerekir. Grubu aç No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 784505ee62..7c8c6f4254 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -5479,8 +5479,8 @@ Requires compatible VPN. Відкрита група No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index a1ca7d430a..31a333085f 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -5373,8 +5373,8 @@ Requires compatible VPN. 打开群 No comment provided by engineer. - - Open in browser? + + Open link? alert title diff --git a/apps/ios/SimpleX SE/ShareView.swift b/apps/ios/SimpleX SE/ShareView.swift index f2b9de9f72..07180ffa1b 100644 --- a/apps/ios/SimpleX SE/ShareView.swift +++ b/apps/ios/SimpleX SE/ShareView.swift @@ -160,7 +160,7 @@ struct ShareView: View { } } - @ViewBuilder private func linkPreview(_ linkPreview: LinkPreview) -> some View { + private func linkPreview(_ linkPreview: LinkPreview) -> some View { previewArea { HStack(alignment: .center, spacing: 8) { if let uiImage = imageFromBase64(linkPreview.image) { From e1aa32952ef94228302e391ed34344a2743ef8a1 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 11 May 2025 15:42:09 +0100 Subject: [PATCH 541/567] ios: unblur media on tap, open/play on the second tap; handle link preview errors (#5886) * ios: unblur media on tap, open/play on the second tap (Xcode 16 regression) * disable link preview spinner on link loading error --- .../Views/Chat/ChatItem/CIImageView.swift | 4 +++- .../Views/Chat/ChatItem/CILinkView.swift | 16 ++++++------- .../Views/Chat/ChatItem/CIVideoView.swift | 24 ++++++++++--------- .../Chat/ComposeMessage/ComposeLinkView.swift | 2 +- .../Chat/ComposeMessage/ComposeView.swift | 9 ++++--- .../Shared/Views/Helpers/ViewModifiers.swift | 4 ++-- 6 files changed, 33 insertions(+), 26 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift index ba6a2bd200..d30369339d 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift @@ -31,7 +31,9 @@ struct CIImageView: View { .if(!smallView) { view in view.modifier(PrivacyBlur(blurred: $blurred)) } - .simultaneousGesture(TapGesture().onEnded { showFullScreenImage = true }) + .if(!blurred) { v in + v.simultaneousGesture(TapGesture().onEnded { showFullScreenImage = true }) + } .onChange(of: m.activeCallViewIsCollapsed) { _ in showFullScreenImage = false } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift index 273c9de408..f9dbaede63 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift @@ -21,15 +21,15 @@ struct CILinkView: View { .resizable() .scaledToFit() .modifier(PrivacyBlur(blurred: $blurred)) + .if(!blurred) { v in + v.simultaneousGesture(TapGesture().onEnded { + openBrowserAlert(uri: linkPreview.uri) + }) + } } VStack(alignment: .leading, spacing: 6) { Text(linkPreview.title) .lineLimit(3) -// if linkPreview.description != "" { -// Text(linkPreview.description) -// .font(.subheadline) -// .lineLimit(12) -// } Text(linkPreview.uri.absoluteString) .font(.caption) .lineLimit(1) @@ -37,10 +37,10 @@ struct CILinkView: View { } .padding(.horizontal, 12) .frame(maxWidth: .infinity, alignment: .leading) + .simultaneousGesture(TapGesture().onEnded { + openBrowserAlert(uri: linkPreview.uri) + }) } - .simultaneousGesture(TapGesture().onEnded { - openBrowserAlert(uri: linkPreview.uri) - }) } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift index 74bce5e583..eacbe9360a 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift @@ -193,18 +193,20 @@ struct CIVideoView: View { } } .modifier(PrivacyBlur(enabled: !videoPlaying, blurred: $blurred)) - .simultaneousGesture(TapGesture().onEnded { - switch player.timeControlStatus { - case .playing: - player.pause() - videoPlaying = false - case .paused: - if canBePlayed { - showFullScreenPlayer = true + .if(!blurred) { v in + v.simultaneousGesture(TapGesture().onEnded { + switch player.timeControlStatus { + case .playing: + player.pause() + videoPlaying = false + case .paused: + if canBePlayed { + showFullScreenPlayer = true + } + default: () } - default: () - } - }) + }) + } .onChange(of: m.activeCallViewIsCollapsed) { _ in showFullScreenPlayer = false } diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift index 6c44aeea83..e629a984df 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift @@ -18,7 +18,7 @@ struct ComposeLinkView: View { var body: some View { HStack(alignment: .center, spacing: 8) { - if let linkPreview = linkPreview { + if let linkPreview { linkPreviewView(linkPreview) } else { ProgressView() diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index 6ded9cae72..3e9c340266 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -1254,11 +1254,14 @@ struct ComposeView: View { if pendingLinkUrl == url { composeState = composeState.copy(preview: .linkPreview(linkPreview: nil)) getLinkPreview(url: url) { linkPreview in - if let linkPreview = linkPreview, - pendingLinkUrl == url { + if let linkPreview, pendingLinkUrl == url { composeState = composeState.copy(preview: .linkPreview(linkPreview: linkPreview)) - pendingLinkUrl = nil + } else { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + composeState = composeState.copy(preview: .noPreview) + } } + pendingLinkUrl = nil } } } diff --git a/apps/ios/Shared/Views/Helpers/ViewModifiers.swift b/apps/ios/Shared/Views/Helpers/ViewModifiers.swift index 3a10cf84d7..85ef85c611 100644 --- a/apps/ios/Shared/Views/Helpers/ViewModifiers.swift +++ b/apps/ios/Shared/Views/Helpers/ViewModifiers.swift @@ -37,9 +37,9 @@ struct PrivacyBlur: ViewModifier { .overlay { if (blurred && enabled) { Color.clear.contentShape(Rectangle()) - .onTapGesture { + .simultaneousGesture(TapGesture().onEnded { blurred = false - } + }) } } .onReceive(NotificationCenter.default.publisher(for: .chatViewWillBeginScrolling)) { _ in From 2a43a02af3570bedce68b15b7dd63632d26613cf Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 12 May 2025 11:22:35 +0100 Subject: [PATCH 542/567] core, ui: support trailing punctuation for mentions, URIs (also support domains), and email addresses (#5888) * core: improve markdown parser for mentions, URIs, and email addresses * ui --- .../Views/Chat/ChatItem/MsgContentView.swift | 15 ++++-- .../Views/Chat/Group/GroupMentions.swift | 4 +- .../chat/simplex/common/model/ChatModel.kt | 2 +- .../common/views/chat/group/GroupMentions.kt | 10 +++- .../src/Directory/Events.hs | 4 -- .../src/Directory/Service.hs | 2 +- src/Simplex/Chat/Markdown.hs | 52 +++++++++++++------ .../SQLite/Migrations/agent_query_plans.txt | 2 +- tests/Bots/DirectoryTests.hs | 4 +- tests/MarkdownTests.hs | 28 ++++++++-- 10 files changed, 89 insertions(+), 34 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift index aab4177cbf..e04584dfff 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift @@ -288,7 +288,11 @@ func messageText( case .uri: attrs = linkAttrs() if !preview { - attrs[linkAttrKey] = NSURL(string: ft.text) + let s = t.lowercased() + let link = s.hasPrefix("http://") || s.hasPrefix("https://") + ? t + : "https://" + t + attrs[linkAttrKey] = NSURL(string: link) attrs[webLinkAttrKey] = true handleTaps = true } @@ -314,9 +318,9 @@ func messageText( if m.memberId == userMemberId { attrs[.foregroundColor] = UIColor.tintColor } - t = "@'\(name)'" + t = mentionText(name) } else { - t = "@'\(memberName)'" + t = mentionText(memberName) } } case .email: @@ -351,6 +355,11 @@ func messageText( } } +@inline(__always) +private func mentionText(_ name: String) -> String { + name.contains(" @") ? "@'\(name)'" : "@\(name)" +} + func simplexLinkText(_ linkType: SimplexLinkType, _ smpHosts: [String]) -> String { linkType.description + " " + "(via \(smpHosts.first ?? "?"))" } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift b/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift index 168f0490c3..9bb4a0cc35 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift @@ -196,7 +196,9 @@ struct GroupMentionsView: View { newName = composeState.mentionMemberName(member.wrapped.memberProfile.displayName) } mentions[newName] = CIMention(groupMember: member.wrapped) - var msgMention = "@" + (newName.contains(" ") ? "'\(newName)'" : newName) + var msgMention = newName.contains(" ") || newName.last?.isPunctuation == true + ? "@'\(newName)'" + : "@\(newName)" var newPos = r.location + msgMention.count let newMsgLength = composeState.message.count + msgMention.count - r.length print(newPos) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 6660cbbb93..6ee609020a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -3902,7 +3902,7 @@ enum class MsgContentTag { class FormattedText(val text: String, val format: Format? = null) { // TODO make it dependent on simplexLinkMode preference fun link(mode: SimplexLinkMode): String? = when (format) { - is Format.Uri -> text + is Format.Uri -> if (text.startsWith("http://", ignoreCase = true) || text.startsWith("https://", ignoreCase = true)) text else "https://$text" is Format.SimplexLink -> if (mode == SimplexLinkMode.BROWSER) text else format.simplexUri is Format.Email -> "mailto:$text" is Format.Phone -> "tel:$text" diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMentions.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMentions.kt index 1a63375432..91f4f5173c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMentions.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMentions.kt @@ -23,6 +23,12 @@ import chat.simplex.common.views.chatlist.setGroupMembers import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import kotlinx.coroutines.launch +import kotlin.text.CharCategory.* + +val punctuation = setOf( + DASH_PUNCTUATION, START_PUNCTUATION, END_PUNCTUATION, + CONNECTOR_PUNCTUATION, OTHER_PUNCTUATION +) private val PICKER_ROW_SIZE = MEMBER_ROW_AVATAR_SIZE + (MEMBER_ROW_VERTICAL_PADDING * 2f) private val MAX_PICKER_HEIGHT = (PICKER_ROW_SIZE * 4) + (MEMBER_ROW_AVATAR_SIZE + MEMBER_ROW_VERTICAL_PADDING - 4.dp) @@ -126,7 +132,9 @@ fun GroupMentions( } val newName = existingMention?.key ?: composeState.value.mentionMemberName(member.memberProfile.displayName) mentions[newName] = CIMention(member) - var msgMention = "@" + if (newName.contains(" ")) "'$newName'" else newName + var msgMention = if (newName.contains(" ") || (newName.lastOrNull()?.category in punctuation)) + "@'$newName'" + else "@$newName" var newPos = range.start + msgMention.length val newMsgLength = composeState.value.message.text.length + msgMention.length - range.length if (newPos == newMsgLength) { diff --git a/apps/simplex-directory-service/src/Directory/Events.hs b/apps/simplex-directory-service/src/Directory/Events.hs index 37d2b63d2f..faaccbd2bf 100644 --- a/apps/simplex-directory-service/src/Directory/Events.hs +++ b/apps/simplex-directory-service/src/Directory/Events.hs @@ -16,7 +16,6 @@ module Directory.Events SDirectoryRole (..), crDirectoryEvent, directoryCmdTag, - viewName, ) where @@ -291,9 +290,6 @@ directoryCmdP = -- wordP = spacesP *> A.takeTill isSpace spacesP = A.takeWhile1 isSpace -viewName :: Text -> Text -viewName n = if T.any isSpace n then "'" <> n <> "'" else n - directoryCmdTag :: DirectoryCmd r -> Text directoryCmdTag = \case DCHelp _ -> "help" diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs index 4b02e0b294..4517ee9c5b 100644 --- a/apps/simplex-directory-service/src/Directory/Service.hs +++ b/apps/simplex-directory-service/src/Directory/Service.hs @@ -48,7 +48,7 @@ import Simplex.Chat.Bot import Simplex.Chat.Bot.KnownContacts import Simplex.Chat.Controller import Simplex.Chat.Core -import Simplex.Chat.Markdown (FormattedText (..), Format (..), parseMaybeMarkdownList) +import Simplex.Chat.Markdown (FormattedText (..), Format (..), parseMaybeMarkdownList, viewName) import Simplex.Chat.Messages import Simplex.Chat.Options import Simplex.Chat.Protocol (MsgContent (..)) diff --git a/src/Simplex/Chat/Markdown.hs b/src/Simplex/Chat/Markdown.hs index e5de9c408c..9811556fe0 100644 --- a/src/Simplex/Chat/Markdown.hs +++ b/src/Simplex/Chat/Markdown.hs @@ -4,6 +4,7 @@ {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TupleSections #-} {-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} {-# HLINT ignore "Use newtype instead of data" #-} @@ -16,7 +17,7 @@ import qualified Data.Aeson as J import qualified Data.Aeson.TH as JQ import Data.Attoparsec.Text (Parser) import qualified Data.Attoparsec.Text as A -import Data.Char (isDigit, isPunctuation, isSpace) +import Data.Char (isAlpha, isAscii, isDigit, isPunctuation, isSpace) import Data.Either (fromRight) import Data.Functor (($>)) import Data.List (foldl', intercalate) @@ -204,17 +205,18 @@ markdownP = mconcat <$> A.many' fragmentP else pure $ markdown (colored clr) s mentionP = do c <- A.char '@' *> A.peekChar' - name <- displayNameTextP + (name, punct) <- displayNameTextP_ let sName = if c == '\'' then '\'' `T.cons` name `T.snoc` '\'' else name - pure $ markdown (Mention name) ('@' `T.cons` sName) + mention = markdown (Mention name) ('@' `T.cons` sName) + pure $ if T.null punct then mention else mention :|: unmarked punct colorP = A.anyChar >>= \case - 'r' -> "ed" $> Red <|> pure Red - 'g' -> "reen" $> Green <|> pure Green - 'b' -> "lue" $> Blue <|> pure Blue - 'y' -> "ellow" $> Yellow <|> pure Yellow - 'c' -> "yan" $> Cyan <|> pure Cyan - 'm' -> "agenta" $> Magenta <|> pure Magenta + 'r' -> optional "ed" $> Red + 'g' -> optional "reen" $> Green + 'b' -> optional "lue" $> Blue + 'y' -> optional "ellow" $> Yellow + 'c' -> optional "yan" $> Cyan + 'm' -> optional "agenta" $> Magenta '1' -> pure Red '2' -> pure Green '3' -> pure Blue @@ -236,12 +238,14 @@ markdownP = mconcat <$> A.many' fragmentP wordMD :: Text -> Markdown wordMD s | T.null s = unmarked s - | isUri s = - let t = T.takeWhileEnd isPunctuation' s - uri = uriMarkdown $ T.dropWhileEnd isPunctuation' s - in if T.null t then uri else uri :|: unmarked t - | isEmail s = markdown Email s + | isUri s' = res $ uriMarkdown s' + | isDomain s' = res $ markdown Uri s' + | isEmail s' = res $ markdown Email s' | otherwise = unmarked s + where + punct = T.takeWhileEnd isPunctuation' s + s' = T.dropWhileEnd isPunctuation' s + res md' = if T.null punct then md' else md' :|: unmarked punct isPunctuation' = \case '/' -> False ')' -> False @@ -250,6 +254,16 @@ markdownP = mconcat <$> A.many' fragmentP Right cLink -> markdown (simplexUriFormat cLink) s _ -> markdown Uri s isUri s = T.length s >= 10 && any (`T.isPrefixOf` s) ["http://", "https://", "simplex:/"] + -- matches what is likely to be a domain, not all valid domain names + isDomain s = case T.splitOn "." s of + [name, tld] -> isDomain_ name tld + [sub, name, tld] -> T.length sub >= 3 && T.length sub <= 8 && isDomain_ name tld + _ -> False + where + isDomain_ name tld = + (let n = T.length name in n >= 1 && n <= 24) + && (let n = T.length tld in n >= 2 && n <= 8) + && (let p c = isAscii c && isAlpha c in T.all p name && T.all p tld) isEmail s = T.any (== '@') s && Email.isValid (encodeUtf8 s) noFormat = pure . unmarked simplexUriFormat :: AConnectionLink -> Format @@ -307,16 +321,22 @@ markdownText (FormattedText f_ t) = case f_ of White -> Nothing displayNameTextP :: Parser Text -displayNameTextP = quoted '\'' <|> takeNameTill (== ' ') +displayNameTextP = displayNameTextP_ >>= \(t, sfx) -> if T.null sfx then pure t else fail "Name ends with punctuation" +{-# INLINE displayNameTextP #-} + +displayNameTextP_ :: Parser (Text, Text) +displayNameTextP_ = (,"") <$> quoted '\'' <|> splitPunctuation <$> takeNameTill isSpace where takeNameTill p = A.peekChar' >>= \c -> if refChar c then A.takeTill p else fail "invalid first character in display name" + splitPunctuation s = (T.dropWhileEnd isPunctuation s, T.takeWhileEnd isPunctuation s) quoted c = A.char c *> takeNameTill (== c) <* A.char c refChar c = c > ' ' && c /= '#' && c /= '@' && c /= '\'' +-- quotes names that contain spaces or end on punctuation viewName :: Text -> Text -viewName s = if T.any isSpace s then "'" <> s <> "'" else s +viewName s = if T.any isSpace s || maybe False (isPunctuation . snd) (T.unsnoc s) then "'" <> s <> "'" else s $(JQ.deriveJSON (enumJSON $ dropPrefix "XL") ''SimplexLinkType) diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt index a10d3f3db7..13215dcb75 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt @@ -1119,7 +1119,7 @@ Query: UPDATE rcv_messages SET user_ack = ? WHERE conn_id = ? AND internal_id = Plan: SEARCH rcv_messages USING COVERING INDEX idx_rcv_messages_conn_id_internal_id (conn_id=? AND internal_id=?) -Query: UPDATE rcv_queues SET last_broker_ts = ? WHERE conn_id = ? AND rcv_queue_id = ? AND last_broker_ts < ? +Query: UPDATE rcv_queues SET last_broker_ts = ? WHERE conn_id = ? AND rcv_queue_id = ? AND (last_broker_ts IS NULL OR last_broker_ts < ?) Plan: SEARCH rcv_queues USING INDEX idx_rcv_queue_id (conn_id=? AND rcv_queue_id=?) diff --git a/tests/Bots/DirectoryTests.hs b/tests/Bots/DirectoryTests.hs index 0877a48daa..f8a5aa8b80 100644 --- a/tests/Bots/DirectoryTests.hs +++ b/tests/Bots/DirectoryTests.hs @@ -14,7 +14,6 @@ import Control.Exception (finally) import Control.Monad (forM_, when) import qualified Data.Text as T import Directory.Captcha -import qualified Directory.Events as DE import Directory.Options import Directory.Service import Directory.Store @@ -22,6 +21,7 @@ import GHC.IO.Handle (hClose) import Simplex.Chat.Bot.KnownContacts import Simplex.Chat.Controller (ChatConfig (..), ChatHooks (..), defaultChatHooks) import Simplex.Chat.Core +import qualified Simplex.Chat.Markdown as MD import Simplex.Chat.Options (CoreChatOpts (..)) import Simplex.Chat.Options.DB import Simplex.Chat.Types (Profile (..)) @@ -111,7 +111,7 @@ serviceDbPrefix :: FilePath serviceDbPrefix = "directory_service" viewName :: String -> String -viewName = T.unpack . DE.viewName . T.pack +viewName = T.unpack . MD.viewName . T.pack testDirectoryService :: HasCallStack => TestParams -> IO () testDirectoryService ps = diff --git a/tests/MarkdownTests.hs b/tests/MarkdownTests.hs index fc872f05b1..05d5362cf1 100644 --- a/tests/MarkdownTests.hs +++ b/tests/MarkdownTests.hs @@ -192,10 +192,23 @@ textWithUri = describe "text with Uri" do "https://github.com/simplex-chat/ - SimpleX on GitHub" <==> uri "https://github.com/simplex-chat/" <> " - SimpleX on GitHub" -- "SimpleX on GitHub (https://github.com/simplex-chat/)" <==> "SimpleX on GitHub (" <> uri "https://github.com/simplex-chat/" <> ")" "https://en.m.wikipedia.org/wiki/Servo_(software)" <==> uri "https://en.m.wikipedia.org/wiki/Servo_(software)" + "example.com" <==> uri "example.com" + "example.com." <==> uri "example.com" <> "." + "example.com..." <==> uri "example.com" <> "..." + "www.example.com" <==> uri "www.example.com" + "example.academy" <==> uri "example.academy" + "this is example.com" <==> "this is " <> uri "example.com" + "x.com" <==> uri "x.com" it "ignored as markdown" do "_https://simplex.chat" <==> "_https://simplex.chat" "this is _https://simplex.chat" <==> "this is _https://simplex.chat" "this is https://" <==> "this is https://" + "example.c" <==> "example.c" + "www.www.example.com" <==> "www.www.example.com" + "www.example1.com" <==> "www.example1.com" + "www." <==> "www." + ".com" <==> ".com" + "example.academytoolong" <==> "example.academytoolong" it "SimpleX links" do let inv = "/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D" ("https://simplex.chat" <> inv) <==> simplexLink XLInvitation ("simplex:" <> inv) ["smp.simplex.im"] ("https://simplex.chat" <> inv) @@ -220,12 +233,14 @@ textWithEmail = describe "text with Email" do "test chat.chat+123@simplex.chat" <==> "test " <> email "chat.chat+123@simplex.chat" "chat@simplex.chat test" <==> email "chat@simplex.chat" <> " test" "test1 chat@simplex.chat test2" <==> "test1 " <> email "chat@simplex.chat" <> " test2" - it "ignored as markdown" do + "test chat@simplex.chat." <==> "test " <> email "chat@simplex.chat" <> "." + "test chat@simplex.chat..." <==> "test " <> email "chat@simplex.chat" <> "..." + it "ignored as email markdown" do "chat @simplex.chat" <==> "chat " <> mention "simplex.chat" "@simplex.chat" "this is chat @simplex.chat" <==> "this is chat " <> mention "simplex.chat" "@simplex.chat" - "this is chat@ simplex.chat" <==> "this is chat@ simplex.chat" - "this is chat @ simplex.chat" <==> "this is chat @ simplex.chat" - "*this* is chat @ simplex.chat" <==> bold "this" <> " is chat @ simplex.chat" + "this is chat@ simplex.chat" <==> "this is chat@ " <> uri "simplex.chat" + "this is chat @ simplex.chat" <==> "this is chat @ " <> uri "simplex.chat" + "*this* is chat @ simplex.chat" <==> bold "this" <> " is chat @ " <> uri "simplex.chat" phone :: Text -> Markdown phone = Markdown $ Just Phone @@ -258,8 +273,13 @@ textWithMentions = describe "text with mentions" do "@alice" <==> mention "alice" "@alice" "hello @alice" <==> "hello " <> mention "alice" "@alice" "hello @alice !" <==> "hello " <> mention "alice" "@alice" <> " !" + "hello @alice!" <==> "hello " <> mention "alice" "@alice" <> "!" + "hello @alice..." <==> "hello " <> mention "alice" "@alice" <> "..." + "hello @alice@example.com" <==> "hello " <> mention "alice@example.com" "@alice@example.com" + "hello @'alice @ example.com'" <==> "hello " <> mention "alice @ example.com" "@'alice @ example.com'" "@'alice jones'" <==> mention "alice jones" "@'alice jones'" "hello @'alice jones'!" <==> "hello " <> mention "alice jones" "@'alice jones'" <> "!" + "hello @'a.j.'!" <==> "hello " <> mention "a.j." "@'a.j.'" <> "!" it "ignored as markdown" $ do "hello @'alice jones!" <==> "hello @'alice jones!" "hello @bob @'alice jones!" <==> "hello " <> mention "bob" "@bob" <> " @'alice jones!" From bb2e7baaa8b560a7525d2c43fc1b0524f6f5c6a8 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 12 May 2025 12:59:03 +0100 Subject: [PATCH 543/567] ios: fix taps on reactions, member profile images, selecting items, icons to navigate to found and forwarded items (Xcode 16 regressions) (#5890) --- apps/ios/Shared/Views/Chat/ChatView.swift | 27 ++++++++++------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index fc80eb6dec..9e648ef98c 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -1276,10 +1276,10 @@ struct ChatView: View { if let selected = selectedChatItems, chatItem.canBeDeletedForSelf { Color.clear .contentShape(Rectangle()) - .onTapGesture { + .simultaneousGesture(TapGesture().onEnded { let checked = selected.contains(chatItem.id) selectUnselectChatItem(select: !checked, chatItem) - } + }) } } if let date = itemSeparation.date { @@ -1468,7 +1468,7 @@ struct ChatView: View { } HStack(alignment: .top, spacing: 10) { MemberProfileImage(member, size: memberImageSize, backgroundColor: theme.colors.background) - .onTapGesture { + .simultaneousGesture(TapGesture().onEnded { if let mem = m.getGroupMember(member.groupMemberId) { selectedMember = mem } else { @@ -1477,7 +1477,7 @@ struct ChatView: View { m.groupMembersIndexes[member.groupMemberId] = m.groupMembers.count - 1 selectedMember = mem } - } + }) chatItemWithMenu(ci, range, maxWidth, itemSeparation) .onPreferenceChange(DetermineWidth.Key.self) { msgWidth = $0 } } @@ -1621,9 +1621,9 @@ struct ChatView: View { .padding(.horizontal, 6) .padding(.vertical, 4) .if(chat.chatInfo.featureEnabled(.reactions) && (ci.allowAddReaction || r.userReacted)) { v in - v.onTapGesture { + v.simultaneousGesture(TapGesture().onEnded { setReaction(ci, add: !r.userReacted, reaction: r.reaction) - } + }) } switch chat.chatInfo { case let .group(groupInfo): @@ -2213,15 +2213,12 @@ struct ChatView: View { } func goToItemInnerButton(_ alignStart: Bool, _ image: String, touchInProgress: Bool, _ onClick: @escaping () -> Void) -> some View { - Button { - onClick() - } label: { - Image(systemName: image) - .resizable() - .frame(width: 13, height: 13) - .padding([alignStart ? .trailing : .leading], 10) - .tint(theme.colors.secondary.opacity(touchInProgress ? 1.0 : 0.4)) - } + Image(systemName: image) + .resizable() + .frame(width: 13, height: 13) + .padding([alignStart ? .trailing : .leading], 10) + .tint(theme.colors.secondary.opacity(touchInProgress ? 1.0 : 0.4)) + .simultaneousGesture(TapGesture().onEnded(onClick)) } @ViewBuilder From 9e60ce7a600184f5397336505f1d231ec1150d95 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 12 May 2025 13:33:49 +0100 Subject: [PATCH 544/567] ui: translations (#5891) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (Italian) Currently translated at 100.0% (2356 of 2356 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (2058 of 2058 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2356 of 2356 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2356 of 2356 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 85.6% (1762 of 2058 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hans/ * Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 73.6% (1735 of 2356 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hant/ * Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 56.5% (1164 of 2058 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hant/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2356 of 2356 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2058 of 2058 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2356 of 2356 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2058 of 2058 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2058 of 2058 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (German) Currently translated at 100.0% (2356 of 2356 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (German) Currently translated at 100.0% (2054 of 2054 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2356 of 2356 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 94.8% (1949 of 2054 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hans/ * export/import localizations --------- Co-authored-by: Random Co-authored-by: dns Co-authored-by: 大王叫我来巡山 Co-authored-by: No name Co-authored-by: summoner001 Co-authored-by: Ghost of Sparta Co-authored-by: mlanp --- .../de.xcloc/Localized Contents/de.xliff | 5 +- .../es.xcloc/Localized Contents/es.xliff | 5 +- .../hu.xcloc/Localized Contents/hu.xliff | 17 +- .../it.xcloc/Localized Contents/it.xliff | 3 + .../Localized Contents/zh-Hans.xliff | 234 ++++++- .../Localized Contents/zh-Hant.xliff | 410 ++++++++++- .../SimpleX NSE/de.lproj/Localizable.strings | 3 + .../SimpleX NSE/es.lproj/Localizable.strings | 3 + .../SimpleX NSE/hu.lproj/Localizable.strings | 3 + .../SimpleX NSE/it.lproj/Localizable.strings | 3 + apps/ios/bg.lproj/Localizable.strings | 2 +- apps/ios/cs.lproj/Localizable.strings | 2 +- apps/ios/de.lproj/Localizable.strings | 25 +- apps/ios/es.lproj/Localizable.strings | 25 +- apps/ios/fr.lproj/Localizable.strings | 5 +- apps/ios/hu.lproj/Localizable.strings | 37 +- apps/ios/it.lproj/Localizable.strings | 23 +- apps/ios/ja.lproj/Localizable.strings | 2 +- apps/ios/nl.lproj/Localizable.strings | 17 +- apps/ios/pl.lproj/Localizable.strings | 5 +- apps/ios/ru.lproj/Localizable.strings | 17 +- apps/ios/tr.lproj/Localizable.strings | 2 +- apps/ios/uk.lproj/Localizable.strings | 2 +- apps/ios/zh-Hans.lproj/Localizable.strings | 659 +++++++++++++++++- .../commonMain/resources/MR/de/strings.xml | 4 +- .../commonMain/resources/MR/es/strings.xml | 6 +- .../commonMain/resources/MR/hu/strings.xml | 14 +- .../commonMain/resources/MR/it/strings.xml | 2 +- .../resources/MR/zh-rCN/strings.xml | 34 +- .../resources/MR/zh-rTW/strings.xml | 4 +- 30 files changed, 1370 insertions(+), 203 deletions(-) diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 6e834157df..a113d35bbd 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -798,6 +798,7 @@ swipe action All servers + Alle Server No comment provided by engineer. @@ -2194,7 +2195,7 @@ Das ist Ihr eigener Einmal-Link! Current profile - Aktueller Profil + Aktuelles Profil No comment provided by engineer. @@ -8293,6 +8294,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Use TCP port 443 for preset servers only. + TCP-Port 443 nur für voreingestellte Server verwenden. No comment provided by engineer. @@ -10185,6 +10187,7 @@ Zuletzt empfangene Nachricht: %2$@ From %d chat(s) + Von %d Chat(s) notification body diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index f5226df190..73f88e1cab 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -798,6 +798,7 @@ swipe action All servers + Todos los servidores No comment provided by engineer. @@ -8086,7 +8087,7 @@ Se te pedirá que completes la autenticación antes de activar esta función.
    Unblock member for all? - ¿Desbloquear miembro para todos? + ¿Desbloquear el miembro para todos? No comment provided by engineer. @@ -8293,6 +8294,7 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Use TCP port 443 for preset servers only. + Usar puerto TCP 443 solo en servidores predefinidos. No comment provided by engineer. @@ -10185,6 +10187,7 @@ last received msg: %2$@ From %d chat(s) + De %d chat(s) notification body diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index c8a29ede41..9be5879eb6 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -768,7 +768,7 @@ swipe action All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. - Az összes üzenet és fájl **végpontok közötti titkosítással**, a közvetlen üzenetek továbbá kvantumálló titkosítással is rendelkeznek. + Az összes üzenet és fájl **végpontok közötti titkosítással**, a közvetlen üzenetek továbbá kvantumbiztos titkosítással is rendelkeznek. No comment provided by engineer. @@ -798,6 +798,7 @@ swipe action All servers + Összes kiszolgáló No comment provided by engineer. @@ -4941,12 +4942,12 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. - Az üzeneteket, fájlokat és hívásokat **végpontok közötti titkosítással**, sérülés utáni titkosság-védelemmel és -helyreállítással, továbbá visszautasítással védi. + Az üzenetek, a fájlok és a hívások **végpontok közötti titkosítással**, sérülés utáni titkosságvédelemmel és -helyreállítással, továbbá letagadhatósággal vannak védve. No comment provided by engineer. Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. - Az üzenetek, fájlok és hívások **végpontok közötti kvantumálló titkosítással** sérülés utáni titkosságvédelemmel, visszautasítással és feltörés utáni helyreállítással vannak védve. + Az üzenetek, a fájlok és a hívások **végpontok közötti kvantumbiztos titkosítással**, sérülés utáni titkosságvédelemmel és -helyreállítással, továbbá letagadhatósággal vannak védve. No comment provided by engineer. @@ -6076,7 +6077,7 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. Quantum resistant encryption - Kvantumálló titkosítás + Kvantumbiztos titkosítás No comment provided by engineer. @@ -7854,7 +7855,7 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. This chat is protected by quantum resistant end-to-end encryption. - Ez a csevegés végpontok közötti kvantumálló titkosítással védett. + Ez a csevegés végpontok közötti kvantumbiztos titkosítással védett. E2EE info chat item @@ -8293,6 +8294,7 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Use TCP port 443 for preset servers only. + A 443-as TCP-port használata kizárólag az előre beállított kiszolgálokhoz. No comment provided by engineer. @@ -8437,7 +8439,7 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Via secure quantum resistant protocol. - Biztonságos kvantumálló-protokollon keresztül. + Biztonságos kvantumbiztos protokollon keresztül. No comment provided by engineer. @@ -9800,7 +9802,7 @@ time to disappear quantum resistant e2e encryption - végpontok közötti kvantumálló titkosítás + végpontok közötti kvantumbiztos titkosítás chat item text @@ -10185,6 +10187,7 @@ utoljára fogadott üzenet: %2$@ From %d chat(s) + %d csevegésből notification body diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 1bfccb3b06..0c0c12004e 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -798,6 +798,7 @@ swipe action All servers + Tutti i server No comment provided by engineer. @@ -8293,6 +8294,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Use TCP port 443 for preset servers only. + Usa la porta TCP 443 solo per i server preimpostati. No comment provided by engineer. @@ -10185,6 +10187,7 @@ ultimo msg ricevuto: %2$@ From %d chat(s) + Da %d chat notification body diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index 31a333085f..03e053326a 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -122,6 +122,7 @@ %1$@, %2$@ + %1$@, %2$@ format for date separator in chat @@ -146,18 +147,22 @@ %d file(s) are still being downloaded. + 仍在下载 %d 个文件。 forward confirmation reason %d file(s) failed to download. + %d 个文件下载失败。 forward confirmation reason %d file(s) were deleted. + 已刪除 %d 个文件。 forward confirmation reason %d file(s) were not downloaded. + 未能下载 %d 个文件。 forward confirmation reason @@ -167,6 +172,7 @@ %d messages not forwarded + 未转发 %d 条消息 alert title @@ -186,11 +192,12 @@ %d seconds(s) + %d 秒 delete after time %d skipped message(s) - %d 跳过消息 + 跳过的 %d 条消息 integrity error chat item @@ -355,6 +362,7 @@ **Scan / Paste link**: to connect via a link you received. + **扫描/粘贴链接**:用您收到的链接连接。 No comment provided by engineer. @@ -458,14 +466,17 @@ time interval 1 year + 1 年 delete after time 1-time link + 一次性链接 No comment provided by engineer. 1-time link can be used *with one contact only* - share in person or via any messenger. + 一次性链接*只能给一名联系人*使用。当面或使用聊天应用分享链接。 No comment provided by engineer. @@ -539,6 +550,7 @@ time interval About operators + 关于运营方 No comment provided by engineer. @@ -555,6 +567,7 @@ swipe action Accept conditions + 接受条款 No comment provided by engineer. @@ -575,6 +588,7 @@ swipe action Accepted conditions + 已接受的条款 No comment provided by engineer. @@ -589,6 +603,7 @@ swipe action Active + 活跃 token status text @@ -603,10 +618,12 @@ swipe action Add friends + 添加好友 No comment provided by engineer. Add list + 添加列表 No comment provided by engineer. @@ -626,6 +643,7 @@ swipe action Add team members + 添加团队成员 No comment provided by engineer. @@ -635,6 +653,7 @@ swipe action Add to list + 添加到列表 No comment provided by engineer. @@ -644,14 +663,17 @@ swipe action Add your team members to the conversations. + 将你的团队成员加入对话。 No comment provided by engineer. Added media & file servers + 已添加媒体和文件服务器 No comment provided by engineer. Added message servers + 已添加消息服务器 No comment provided by engineer. @@ -681,10 +703,12 @@ swipe action Address or 1-time link? + 地址还是一次性链接? No comment provided by engineer. Address settings + 地址设置 No comment provided by engineer. @@ -709,6 +733,7 @@ swipe action All + 全部 No comment provided by engineer. @@ -723,6 +748,7 @@ swipe action All chats will be removed from the list %@, and the list deleted. + 列表 %@ 和其中全部聊天将被删除。 alert message @@ -742,6 +768,7 @@ swipe action All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages. + 所有消息和文件均通过**端到端加密**发送;私信以量子安全方式发送。 No comment provided by engineer. @@ -766,10 +793,12 @@ swipe action All reports will be archived for you. + 将为你存档所有举报。 No comment provided by engineer. All servers + 全部服务器 No comment provided by engineer. @@ -849,6 +878,7 @@ swipe action Allow to report messsages to moderators. + 允许向 moderators 举报消息。 No comment provided by engineer. @@ -933,6 +963,7 @@ swipe action Another reason + 另一个理由 report reason @@ -962,6 +993,7 @@ swipe action App group: + 应用组: No comment provided by engineer. @@ -981,6 +1013,7 @@ swipe action App session + 应用会话 No comment provided by engineer. @@ -1010,14 +1043,17 @@ swipe action Archive + 存档 No comment provided by engineer. Archive %lld reports? + 存档 %lld 个举报? No comment provided by engineer. Archive all reports? + 存档所有举报? No comment provided by engineer. @@ -1032,14 +1068,17 @@ swipe action Archive report + 存档举报 No comment provided by engineer. Archive report? + 存档举报? No comment provided by engineer. Archive reports + 存档举报 swipe action @@ -1114,6 +1153,7 @@ swipe action Auto-accept settings + 自动接受设置 alert title @@ -1143,6 +1183,7 @@ swipe action Better calls + 更佳的通话 No comment provided by engineer. @@ -1152,10 +1193,12 @@ swipe action Better groups performance + 更好的群性能 No comment provided by engineer. Better message dates. + 更好的消息日期。 No comment provided by engineer. @@ -1170,18 +1213,22 @@ swipe action Better notifications + 更佳的通知 No comment provided by engineer. Better privacy and security + 更好的隐私和安全 No comment provided by engineer. Better security ✅ + 更佳的安全性✅ No comment provided by engineer. Better user experience + 更佳的使用体验 No comment provided by engineer. @@ -1266,14 +1313,17 @@ swipe action Business address + 企业地址 No comment provided by engineer. Business chats + 企业聊天 No comment provided by engineer. Businesses + 企业 No comment provided by engineer. @@ -1378,10 +1428,12 @@ alert button Change automatic message deletion? + 更改消息自动删除设置? alert title Change chat profiles + 更改聊天资料 authentication reason @@ -1432,14 +1484,17 @@ set passcode view Chat + 聊天 No comment provided by engineer. Chat already exists + 聊天已存在 No comment provided by engineer. Chat already exists! + 聊天已存在! No comment provided by engineer. @@ -1504,6 +1559,7 @@ set passcode view Chat preferences were changed. + 聊天偏好设置已修改。 alert message @@ -1518,10 +1574,12 @@ set passcode view Chat will be deleted for all members - this cannot be undone! + 将为所有成员删除聊天 - 此操作无法撤销! No comment provided by engineer. Chat will be deleted for you - this cannot be undone! + 将为你删除聊天 - 此操作无法撤销! No comment provided by engineer. @@ -1531,10 +1589,12 @@ set passcode view Check messages every 20 min. + 每 20 分钟检查消息。 No comment provided by engineer. Check messages when allowed. + 在被允许时检查消息。 No comment provided by engineer. @@ -1594,10 +1654,12 @@ set passcode view Clear group? + 清除群? No comment provided by engineer. Clear or delete group? + 清除还是删除群? No comment provided by engineer. @@ -1622,6 +1684,7 @@ set passcode view Community guidelines violation + 违反社区指导方针 report reason @@ -1641,14 +1704,17 @@ set passcode view Conditions accepted on: %@. + 已于 %@ 接受条款。 No comment provided by engineer. Conditions are accepted for the operator(s): **%@**. + 已接受运营方 **%@** 的条款。 No comment provided by engineer. Conditions are already accepted for these operator(s): **%@**. + 已经接受下列运营方的条款:**%@**。 No comment provided by engineer. @@ -1658,14 +1724,17 @@ set passcode view Conditions will be accepted for the operator(s): **%@**. + 将接受下列运营方的条款:**%@**。 No comment provided by engineer. Conditions will be accepted on: %@. + 将于 %@ 接受条款。 No comment provided by engineer. Conditions will be automatically accepted for enabled operators on: %@. + 将在 %@ 自动接受启用的运营方的条款。 No comment provided by engineer. @@ -1730,6 +1799,7 @@ set passcode view Confirmed + 已确定 token status text @@ -1853,6 +1923,7 @@ This is your own one-time link! Connection blocked + 连接被阻止 No comment provided by engineer. @@ -1868,10 +1939,12 @@ This is your own one-time link! Connection is blocked by server operator: %@ + 连接被运营方 %@ 阻止 No comment provided by engineer. Connection not ready. + 连接未就绪。 No comment provided by engineer. @@ -1886,10 +1959,12 @@ This is your own one-time link! Connection requires encryption renegotiation. + 连接需要加密重协商。 No comment provided by engineer. Connection security + 连接安全性 No comment provided by engineer. @@ -1969,6 +2044,7 @@ This is your own one-time link! Content violates conditions of use + 内容违反使用条款 blocking reason @@ -1998,6 +2074,7 @@ This is your own one-time link! Corner + 拐角 No comment provided by engineer. @@ -2012,6 +2089,7 @@ This is your own one-time link! Create 1-time link + 创建一次性链接 No comment provided by engineer. @@ -2046,6 +2124,7 @@ This is your own one-time link! Create list + 创建列表 No comment provided by engineer. @@ -2105,6 +2184,7 @@ This is your own one-time link! Current conditions text couldn't be loaded, you can review conditions via this link: + 无法加载当前条款文本,你可以通过此链接审阅条款: No comment provided by engineer. @@ -2129,6 +2209,7 @@ This is your own one-time link! Customizable message shape. + 可自定义消息形状。 No comment provided by engineer. @@ -2302,10 +2383,12 @@ swipe action Delete chat + 删除聊天 No comment provided by engineer. Delete chat messages from your device. + 从你的设备删除聊天消息。 No comment provided by engineer. @@ -2320,6 +2403,7 @@ swipe action Delete chat? + 删除聊天? No comment provided by engineer. @@ -2399,6 +2483,7 @@ swipe action Delete list? + 删除列表? alert title @@ -2433,6 +2518,7 @@ swipe action Delete or moderate up to 200 messages. + 允许自行删除或管理员移除最多200条消息。 No comment provided by engineer. @@ -2452,6 +2538,7 @@ swipe action Delete report + 删除举报 No comment provided by engineer. @@ -2491,6 +2578,7 @@ swipe action Delivered even when Apple drops them. + 已送达,即使苹果已将其删除。 No comment provided by engineer. @@ -2575,7 +2663,7 @@ swipe action Device authentication is disabled. Turning off SimpleX Lock. - 设备验证被禁用。关闭 SimpleX 锁定。 + 设备验证已禁用。 SimpleX 已解锁。 No comment provided by engineer. @@ -2595,11 +2683,12 @@ swipe action Direct messages between members are prohibited in this chat. + 此群禁止成员间私信。 No comment provided by engineer. Direct messages between members are prohibited. - 此群中禁止成员之间私信。 + 此群禁止成员间私信。 No comment provided by engineer. @@ -2614,10 +2703,12 @@ swipe action Disable automatic message deletion? + 禁用消息自动销毁? alert title Disable delete messages + 停用消息删除 alert button @@ -2707,10 +2798,12 @@ swipe action Do not use credentials with proxy. + 代理不使用身份验证凭据。 No comment provided by engineer. Documents: + 文档: No comment provided by engineer. @@ -2725,6 +2818,7 @@ swipe action Don't miss important messages. + 不错过重要消息。 No comment provided by engineer. @@ -2734,6 +2828,7 @@ swipe action Done + 完成 No comment provided by engineer. @@ -2764,6 +2859,7 @@ chat item action Download files + 下载文件 alert action @@ -2798,6 +2894,7 @@ chat item action E2E encrypted notifications. + 端到端加密的通知。 No comment provided by engineer. @@ -2822,6 +2919,7 @@ chat item action Enable Flux in Network & servers settings for better metadata privacy. + 在“网络&服务器”设置中启用 Flux,更好地保护元数据隐私。 No comment provided by engineer. @@ -2966,6 +3064,7 @@ chat item action Encryption renegotiation in progress. + 正进行加密重协商。 No comment provided by engineer. @@ -3035,6 +3134,7 @@ chat item action Error accepting conditions + 接受条款出错 alert title @@ -3049,6 +3149,7 @@ chat item action Error adding server + 添加服务器出错 alert title @@ -3058,6 +3159,7 @@ chat item action Error changing connection profile + 更改连接资料出错 No comment provided by engineer. @@ -3072,6 +3174,7 @@ chat item action Error changing to incognito! + 切换至隐身聊天出错! No comment provided by engineer. @@ -3100,6 +3203,7 @@ chat item action Error creating list + 创建列表出错 alert title @@ -3119,6 +3223,7 @@ chat item action Error creating report + 创建举报出错 No comment provided by engineer. @@ -3203,10 +3308,12 @@ chat item action Error loading servers + 加载服务器出错 alert title Error migrating settings + 迁移设置出错 No comment provided by engineer. @@ -3231,6 +3338,7 @@ chat item action Error registering for notifications + 注册消息推送出错 alert title @@ -3240,6 +3348,7 @@ chat item action Error reordering lists + 重排列表出错 alert title @@ -3254,6 +3363,7 @@ chat item action Error saving chat list + 保存聊天列表出错 alert title @@ -3273,6 +3383,7 @@ chat item action Error saving servers + 保存服务器出错 alert title @@ -3322,6 +3433,7 @@ chat item action Error switching profile + 切换配置文件出错 No comment provided by engineer. @@ -3336,6 +3448,7 @@ chat item action Error testing server connection + 检验服务器连接出错 No comment provided by engineer. @@ -3350,6 +3463,7 @@ chat item action Error updating server + 更新服务器出错 alert title @@ -3401,6 +3515,7 @@ snd error text Errors in servers configuration. + 服务器配置有错误。 servers error @@ -3420,6 +3535,7 @@ snd error text Expired + 已过期 token status text @@ -3464,6 +3580,7 @@ snd error text Faster deletion of groups. + 更快地删除群。 No comment provided by engineer. @@ -3473,6 +3590,7 @@ snd error text Faster sending messages. + 更快发送消息。 No comment provided by engineer. @@ -3482,6 +3600,7 @@ snd error text Favorites + 收藏 No comment provided by engineer. @@ -3492,11 +3611,15 @@ snd error text File errors: %@ + 文件错误: +%@ alert message File is blocked by server operator: %@. + 文件被服务器运营方阻止: +%@。 file error text @@ -3626,10 +3749,12 @@ snd error text For all moderators + 所有 moderators No comment provided by engineer. For chat profile %@: + 为聊天资料 %@: servers error @@ -3639,18 +3764,22 @@ snd error text For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server. + 比如,如果你通过 SimpleX 服务器收到消息,应用会通过 Flux 服务器传送它们。 No comment provided by engineer. For me + 仅自己 No comment provided by engineer. For private routing + 用于私密路由 No comment provided by engineer. For social media + 用于社交媒体 No comment provided by engineer. @@ -3660,6 +3789,7 @@ snd error text Forward %d message(s)? + 转发 %d 条消息? alert title @@ -3669,14 +3799,17 @@ snd error text Forward messages + 已转发的消息 alert action Forward messages without files? + 仅转发消息不转发文件? alert message Forward up to 20 messages at once. + 一次转发最多20条消息。 No comment provided by engineer. @@ -3691,6 +3824,7 @@ snd error text Forwarding %lld messages + 正在转发 %lld 条消息 No comment provided by engineer. @@ -3764,6 +3898,7 @@ Error: %2$@ Get notified when mentioned. + 被提及时收到通知。 No comment provided by engineer. @@ -3873,6 +4008,7 @@ Error: %2$@ Groups + No comment provided by engineer. @@ -3882,6 +4018,7 @@ Error: %2$@ Help admins moderating their groups. + 帮助管理员管理群组。 No comment provided by engineer. @@ -3936,14 +4073,17 @@ Error: %2$@ How it affects privacy + 它如何影响隐私 No comment provided by engineer. How it helps privacy + 它如何帮助隐私 No comment provided by engineer. How it works + 工作原理 alert button @@ -3973,6 +4113,7 @@ Error: %2$@ IP address + IP 地址 No comment provided by engineer. @@ -4053,6 +4194,8 @@ Error: %2$@ Improved delivery, reduced traffic usage. More improvements are coming soon! + 改善传送,降低流量使用。 +更多改进即将推出! No comment provided by engineer. @@ -4087,10 +4230,12 @@ More improvements are coming soon! Inappropriate content + 不当内容 report reason Inappropriate profile + 不当个人资料 report reason @@ -4187,22 +4332,27 @@ More improvements are coming soon! Invalid + 无效 token status text Invalid (bad token) + Token 无效 token status text Invalid (expired) + 无效(已过期) token status text Invalid (unregistered) + 无效(未注册) token status text Invalid (wrong topic) + 无效(话题有误) token status text @@ -4267,6 +4417,7 @@ More improvements are coming soon! Invite to chat + 邀请加入聊天 No comment provided by engineer. @@ -4429,10 +4580,12 @@ This is your link for group %@! Leave chat + 离开聊天 No comment provided by engineer. Leave chat? + 离开聊天? No comment provided by engineer. @@ -4477,14 +4630,17 @@ This is your link for group %@! List + 列表 swipe action List name and emoji should be different for all lists. + 所有列表的名称和表情符号都应不同。 No comment provided by engineer. List name... + 列表名… No comment provided by engineer. @@ -4579,10 +4735,12 @@ This is your link for group %@! Member reports + 成员举报 chat feature Member role will be changed to "%@". All chat members will be notified. + 将变更成员角色为“%@”。所有成员都会收到通知。 No comment provided by engineer. @@ -4597,6 +4755,7 @@ This is your link for group %@! Member will be removed from chat - this cannot be undone! + 将从聊天中删除成员 - 此操作无法撤销! No comment provided by engineer. @@ -4616,6 +4775,7 @@ This is your link for group %@! Members can report messsages to moderators. + 成员可以向 moderators 举报消息。 No comment provided by engineer. @@ -4645,6 +4805,7 @@ This is your link for group %@! Mention members 👋 + 提及成员👋 No comment provided by engineer. @@ -4714,6 +4875,7 @@ This is your link for group %@! Message shape + 消息形状 No comment provided by engineer. @@ -4758,6 +4920,7 @@ This is your link for group %@! Messages in this chat will never be deleted. + 此聊天中的消息永远不会被删除。 alert message @@ -4772,6 +4935,7 @@ This is your link for group %@! Messages were deleted after you selected them. + 在你选中消息后这些消息已被删除。 alert message @@ -4861,6 +5025,7 @@ This is your link for group %@! More + 更多 swipe action @@ -4875,6 +5040,7 @@ This is your link for group %@! More reliable notifications + 更可靠的通知 No comment provided by engineer. @@ -4894,6 +5060,7 @@ This is your link for group %@! Mute all + 全部静音 notification label action @@ -4918,6 +5085,7 @@ This is your link for group %@! Network decentralization + 网络去中心化 No comment provided by engineer. @@ -4932,6 +5100,7 @@ This is your link for group %@! Network operator + 网络运营方 No comment provided by engineer. @@ -4946,6 +5115,7 @@ This is your link for group %@! New + token status text @@ -4955,10 +5125,12 @@ This is your link for group %@! New SOCKS credentials will be used every time you start the app. + 每次启动应用都会使用新的 SOCKS 凭据。 No comment provided by engineer. New SOCKS credentials will be used for each server. + 每个服务器都会使用新的 SOCKS 凭据。 No comment provided by engineer. @@ -4993,6 +5165,7 @@ This is your link for group %@! New events + 新事件 notification @@ -5022,6 +5195,7 @@ This is your link for group %@! New server + 新服务器 No comment provided by engineer. @@ -5036,14 +5210,17 @@ This is your link for group %@! No chats + 无聊天 No comment provided by engineer. No chats found + 找不到聊天 No comment provided by engineer. No chats in list %@ + 列表 %@ 中无聊天 No comment provided by engineer. @@ -5093,14 +5270,17 @@ This is your link for group %@! No media & file servers. + 无媒体和文件服务器。 servers error No message + 无消息 No comment provided by engineer. No message servers. + 无消息服务器。 servers error @@ -5110,10 +5290,12 @@ This is your link for group %@! No permission to record speech + 无录音权限 No comment provided by engineer. No permission to record video + 无录像权限 No comment provided by engineer. @@ -5133,26 +5315,32 @@ This is your link for group %@! No servers for private message routing. + 无私密消息路由服务器。 servers error No servers to receive files. + 无文件接收服务器。 servers error No servers to receive messages. + 无消息接收服务器。 servers error No servers to send files. + 无文件发送服务器。 servers error No token! + 无 token! alert title No unread chats + 没有未读聊天 No comment provided by engineer. @@ -5167,6 +5355,7 @@ This is your link for group %@! Notes + 附注 No comment provided by engineer. @@ -5176,6 +5365,7 @@ This is your link for group %@! Nothing to forward! + 无可转发! alert title @@ -5190,14 +5380,17 @@ This is your link for group %@! Notifications error + 通知错误 alert title Notifications privacy + 通知隐私 No comment provided by engineer. Notifications status + 通知状态 alert title @@ -5255,6 +5448,7 @@ Requires compatible VPN. Only chat owners can change preferences. + 仅聊天所有人可更改首选项。 No comment provided by engineer. @@ -5284,10 +5478,12 @@ Requires compatible VPN. Only sender and moderators see it + 仅发送人和moderators能看到 No comment provided by engineer. Only you and moderators see it + 只有你和moderators能看到 No comment provided by engineer. @@ -5352,6 +5548,7 @@ Requires compatible VPN. Open changes + 打开更改 No comment provided by engineer. @@ -5366,6 +5563,7 @@ Requires compatible VPN. Open conditions + 打开条款 No comment provided by engineer. @@ -5389,14 +5587,17 @@ Requires compatible VPN. Operator + 运营方 No comment provided by engineer. Operator server + 运营方服务器 alert title Or import archive file + 或者导入或者导入压缩文件 No comment provided by engineer. @@ -5421,10 +5622,12 @@ Requires compatible VPN. Or to share privately + 或者私下分享 No comment provided by engineer. Organize chats into lists + 将聊天组织到列表 No comment provided by engineer. @@ -5474,6 +5677,7 @@ Requires compatible VPN. Password + 密码 No comment provided by engineer. @@ -6272,21 +6476,22 @@ swipe action Review conditions + 审阅条款 No comment provided by engineer. Revoke - 撤销 + 吊销 No comment provided by engineer. Revoke file - 撤销文件 + 吊销文件 cancel file action Revoke file? - 撤销文件? + 吊销文件? No comment provided by engineer. @@ -6296,7 +6501,7 @@ swipe action Run chat - 运行聊天程序 + 运行聊天 No comment provided by engineer. @@ -6356,6 +6561,7 @@ chat item action Save list + 保存列表 No comment provided by engineer. @@ -6395,6 +6601,7 @@ chat item action Save your profile? + 保存您的个人资料? alert title @@ -6419,6 +6626,7 @@ chat item action Saving %lld messages + 正在保存 %lld 条消息 No comment provided by engineer. @@ -6999,6 +7207,7 @@ chat item action SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. + SimpleX Chat 与 Flux 达成了协议,将由 Flux 控制的服务器纳入 SimpleX 应用。 No comment provided by engineer. @@ -7033,14 +7242,17 @@ chat item action SimpleX address and 1-time links are safe to share via any messenger. + 可以通过任何消息应用安全分享 SimpleX 地址和一次性链接。 No comment provided by engineer. SimpleX address or 1-time link? + SimpleX 地址或一次性链接? No comment provided by engineer. SimpleX channel link + SimpleX 频道链接 simplex link type @@ -7050,12 +7262,12 @@ chat item action SimpleX encrypted message or connection event - SimpleX 加密消息或连接项目 + SimpleX 加密的消息或连接事件 notification SimpleX group link - SimpleX 群组链接 + SimpleX 群链接 simplex link type @@ -7080,6 +7292,7 @@ chat item action SimpleX protocols reviewed by Trail of Bits. + SimpleX 协议由 Trail of Bits 审阅。 No comment provided by engineer. @@ -7114,6 +7327,7 @@ chat item action Some app settings were not migrated. + 部分应用设置未被迁移。 No comment provided by engineer. @@ -9143,7 +9357,7 @@ pref value duplicates - 复本 + 副本 No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff index 93b9725131..8a771369e6 100644 --- a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff @@ -109,7 +109,7 @@ %d skipped message(s) - %d錯過了訊息 + 錯過的 %d 則訊息 integrity error chat item @@ -124,17 +124,17 @@ %lld contact(s) selected - %lld 已選擇聯絡人(s) + 已選擇 %lld 個聯絡人 No comment provided by engineer. %lld file(s) with total size of %@ - %lld 檔案(s) 的總共大小為%@ + %lld 個檔案,總共大小 %@ No comment provided by engineer. %lld members - %lld 成員 + %lld 個成員 No comment provided by engineer. @@ -224,7 +224,7 @@ **Warning**: Instant push notifications require passphrase saved in Keychain. - **警告**:即時推送訊息通知需要數據庫的密碼儲存在資料庫中。 + **警告**:即時推送訊息通知需要將數據庫的密碼儲存在資料庫中。 No comment provided by engineer. @@ -2704,12 +2704,12 @@ We will be adding server redundancy to prevent lost messages. Send link previews - 傳送可以預覽的連結 + 傳送連結預覽 No comment provided by engineer. Send live message - 傳送實況的訊息 + 傳送實時訊息 No comment provided by engineer. @@ -2724,7 +2724,7 @@ We will be adding server redundancy to prevent lost messages. Send questions and ideas - 傳送問題和想法給開發者 + 給開發者提問題和想法 No comment provided by engineer. @@ -2774,7 +2774,7 @@ We will be adding server redundancy to prevent lost messages. Set 1 day - 設定為1天 + 設定為 1 天 No comment provided by engineer. @@ -3022,7 +3022,7 @@ We will be adding server redundancy to prevent lost messages. The connection you accepted will be cancelled! - 你所接受的連接將被取消! + 你接受的連接將被取消! No comment provided by engineer. @@ -3061,7 +3061,7 @@ We will be adding server redundancy to prevent lost messages. The sender will NOT be notified - 發送者不會接收到通知 + 發送者不會收到通知 No comment provided by engineer. @@ -3071,12 +3071,12 @@ We will be adding server redundancy to prevent lost messages. This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain. - 這操作不能還原 - 所有已經接收和傳送的檔案和媒體檔案將刪除。低解析度圖片將保留。 + 這操作不能還原 - 將刪除所有已經接收和傳送的檔案和媒體。將保留低解析度圖片。 No comment provided by engineer. This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes. - 這操作無法撤銷 - 早於所選擇的時間發送和接收的訊息將被刪除。這可能需要幾分鐘的時間。 + 這操作無法撤銷 - 早於所選時間的收發訊息將被刪除。可能需要幾分鐘。 No comment provided by engineer. @@ -3263,7 +3263,7 @@ To connect, please ask your contact to create another connection link and check Use for new connections - 用於新的連接 + 用於新的連線 No comment provided by engineer. @@ -3283,7 +3283,7 @@ To connect, please ask your contact to create another connection link and check Verify connection security - 驗證連接安全性 + 驗證連線安全性 No comment provided by engineer. @@ -4163,7 +4163,7 @@ SimpleX 伺服器並不會看到你的個人檔案。 via contact address link - 透過聯絡人的邀請連結連接 + 透過聯絡人的邀請連結連線 chat list item description @@ -4173,7 +4173,7 @@ SimpleX 伺服器並不會看到你的個人檔案。 via one-time link - 透過一次性連結連接 + 透過一次性連結連線 chat list item description @@ -4702,7 +4702,7 @@ Available in v5.1 %u messages failed to decrypt. - %u 訊息解密失敗。 + %u 則訊息解密失敗。 No comment provided by engineer. @@ -5152,7 +5152,7 @@ Available in v5.1 Tap to activate profile. - 點擊以激活配置檔案。 + 點擊以激活設定檔。 No comment provided by engineer. @@ -6034,7 +6034,7 @@ It can happen because of some bug or when the connection is compromised. %lld messages marked deleted - %lld 條訊息已刪除 + %lld 則訊息已標記為刪除 Already connecting! @@ -6046,7 +6046,7 @@ It can happen because of some bug or when the connection is compromised. (new) - (新) + (新) %@, %@ and %lld other members connected @@ -6112,6 +6112,374 @@ It can happen because of some bug or when the connection is compromised.Background 後台 + + SimpleX links not allowed + 不允許 SimpleX 連結 + + + Voice messages not allowed + 不允許語音訊息 + + + The text you pasted is not a SimpleX link. + 您貼在這裡的連結不是 SimpleX 連結。 + + + %d file(s) were deleted. + 已刪除 %d 個檔案。 + + + Reset to app theme + 重設至應用程式主題 + + + Retry + 重試 + + + The uploaded database archive will be permanently removed from the servers. + 上傳的資料庫存檔將從伺服器永久移除。 + + + Shape profile images + 塑造個人資料圖片 + + + **Scan / Paste link**: to connect via a link you received. + **掃描/貼上連結**:以透過您收到的連結連線。 + + + Reports + 舉報 + + + Use SOCKS proxy + 使用 SOCKS 代理 + + + Reset all statistics + 重設所有統計數據 + + + SOCKS proxy + SOCKS 代理 + + + Send message to enable calls. + 發送訊息以啟用通話功能。 + + + Send direct message to connect + 直接發送訊息以連結 + + + Scale + 顯示比例 + + + Sent via proxy + 通過代理發送 + + + Servers info + 伺服器訊息 + + + Set message expiration in chats. + 設定聊天中訊息期限。 + + + Share SimpleX address on social media. + 在社交媒體上分享 SimpleX 聯絡地址。 + + + Storage + 存儲 + + + Starting from %@. + 開始於 %@。 + + + The second tick we missed! ✅ + 我們錯過的第二個勾選! ✅ + + + Themes + 主題 + + + %d file(s) failed to download. + %d 個檔案下載失敗。 + + + Session code + 會話代碼 + + + Servers statistics will be reset - this cannot be undone! + 伺服器統計資料將被重設 - 此操作無法撤銷! + + + **Create 1-time link**: to create and share a new invitation link. + **建立一次性連結**:建立並分享新邀請連結。 + + + Set default theme + 設定缺省主題 + + + %lld group events + %lld 個群組事件 + + + Reset all statistics? + 重設所有統計數據? + + + %@ server + %@ 伺服器 + + + %d file(s) were not downloaded. + %d 個檔案未下載。 + + + %d messages not forwarded + %d 則訊息未轉發 + + + Test notifications + 测试通知 + + + (this device v%@) + (此設備 v%@) + + + Settings were changed. + 設定已更改。 + + + This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted. + 這操作不能撤銷 - 此聊天中早於所選訊息的收發訊息將被刪除。 + + + Subscription errors + 訂閱錯誤 + + + Report + 舉報 + + + Send messages directly when IP address is protected and your or destination server does not support private routing. + 當 IP 位址受保護且您或目的地伺服器不支援私人路由時,直接傳送訊息。 + + + Reset to user theme + 重設為使用者主題 + + + Use short links (BETA) + 使用短連結(Beta) + + + Up to 100 last messages are sent to new members. + 最多 100 則最後的訊息會傳送至新成員。 + + + %d seconds(s) + %d 秒 + + + %d file(s) are still being downloaded. + 仍在下載 %d 個檔案。 + + + %lld messages blocked by admin + %lld 則訊息被管理員封鎖 + + + Report: %@ + 舉報:%@ + + + Review conditions + 檢視使用條款 + + + Search or paste SimpleX link + 搜尋或貼上 SimpleX 連結 + + + Sent directly + 已直接發送 + + + SimpleX links are prohibited. + 這群組禁止 SimpleX 連結。 + + + Uploaded files + 已上傳的檔案 + + + Use %@ + 使用 %@ + + + Upload errors + 上傳錯誤 + + + Use servers + 使用伺服器 + + + security code changed + 安全碼已變更 + + + These settings are for your current profile **%@**. + 這些設定是針對您目前的設定檔 **%@**。 + + + They can be overridden in contact and group settings. + 您可在連絡人和群組設定中覆寫它們。 + + + %1$@, %2$@ + %1$@, %2$@ + + + Verify connections + 驗證連線 + + + Verify connection + 驗證連線 + + + Verify passphrase + 驗證密碼 + + + Verify code with desktop + 使用桌上電腦驗證代碼 + + + Save list + 儲存列表 + + + Saving %lld messages + 正在儲存 %lld 則訊息 + + + search + 搜尋 + + + requested to connect + 已請求連結 + + + saved + 已儲存 + + + video + 視訊 + + + Tap to Connect + 點擊以連結 + + + Unsupported connection link + 未受支持的連線連結 + + + Saved from + 儲存自 + + + Saved + 已儲存 + + + Scan / Paste link + 掃描/貼上連結 + + + SimpleX + SimpleX + + + Use the app while in the call. + 在通話時使用此應用程式。 + + + v%@ + v%@ + + + Save your profile? + 儲存設定檔? + + + Use for messages + 用於訊息 + + + Uploading archive + 正在上傳檔案庫 + + + Unlink + 從桌上電腦解除連結 + + + %lld messages blocked + 已封鎖 %d 則訊息 + + + The same conditions will apply to operator **%@**. + 相同條件也適用於 **%@** 操作員。 + + + These conditions will also apply for: **%@**. + 這些條件也適用於:**%@**。 + + + Upload failed + 上傳失敗 + + + Use the app with one hand. + 單手使用此應用程式。 + + + Safely receive files + 安全地接收檔案 + + + Saved message + 已儲存的訊息 + + + Use from desktop + 在桌上電腦上使用 + + + Via secure quantum resistant protocol. + 使用量子安全的協定。 + + + Uploaded + 已上傳 + diff --git a/apps/ios/SimpleX NSE/de.lproj/Localizable.strings b/apps/ios/SimpleX NSE/de.lproj/Localizable.strings index d0b2f8bd1b..ec502c53c6 100644 --- a/apps/ios/SimpleX NSE/de.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/de.lproj/Localizable.strings @@ -1,6 +1,9 @@ /* notification body */ "%d new events" = "%d neue Ereignisse"; +/* notification body */ +"From %d chat(s)" = "Von %d Chat(s)"; + /* notification body */ "From: %@" = "Von: %@"; diff --git a/apps/ios/SimpleX NSE/es.lproj/Localizable.strings b/apps/ios/SimpleX NSE/es.lproj/Localizable.strings index 8b43c489b7..685eb3d93d 100644 --- a/apps/ios/SimpleX NSE/es.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/es.lproj/Localizable.strings @@ -1,6 +1,9 @@ /* notification body */ "%d new events" = "%d evento(s) nuevo(s)"; +/* notification body */ +"From %d chat(s)" = "De %d chat(s)"; + /* notification body */ "From: %@" = "De: %@"; diff --git a/apps/ios/SimpleX NSE/hu.lproj/Localizable.strings b/apps/ios/SimpleX NSE/hu.lproj/Localizable.strings index 69456fd177..a6330b93db 100644 --- a/apps/ios/SimpleX NSE/hu.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/hu.lproj/Localizable.strings @@ -1,6 +1,9 @@ /* notification body */ "%d new events" = "%d új esemény"; +/* notification body */ +"From %d chat(s)" = "%d csevegésből"; + /* notification body */ "From: %@" = "Tőle: %@"; diff --git a/apps/ios/SimpleX NSE/it.lproj/Localizable.strings b/apps/ios/SimpleX NSE/it.lproj/Localizable.strings index e22f5aeac3..a6c1ec215b 100644 --- a/apps/ios/SimpleX NSE/it.lproj/Localizable.strings +++ b/apps/ios/SimpleX NSE/it.lproj/Localizable.strings @@ -1,6 +1,9 @@ /* notification body */ "%d new events" = "%d nuovi eventi"; +/* notification body */ +"From %d chat(s)" = "Da %d chat"; + /* notification body */ "From: %@" = "Da: %@"; diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index 432bc75894..f241158185 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -2825,7 +2825,7 @@ time to disappear */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Само вашият контакт може да изпраща гласови съобщения."; -/* No comment provided by engineer. */ +/* alert action */ "Open" = "Отвори"; /* No comment provided by engineer. */ diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index c150ba5564..003ac23325 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -2217,7 +2217,7 @@ time to disappear */ /* 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. */ +/* alert action */ "Open" = "Otevřít"; /* No comment provided by engineer. */ diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 8b0e18ecce..0eab764216 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -505,6 +505,9 @@ swipe action */ /* No comment provided by engineer. */ "All reports will be archived for you." = "Alle Meldungen werden für Sie archiviert."; +/* No comment provided by engineer. */ +"All servers" = "Alle Server"; + /* No comment provided by engineer. */ "All your contacts will remain connected." = "Alle Ihre Kontakte bleiben verbunden."; @@ -688,9 +691,6 @@ swipe action */ /* No comment provided by engineer. */ "Archiving database" = "Datenbank wird archiviert"; -/* No comment provided by engineer. */ -"Ask" = "Fragen"; - /* No comment provided by engineer. */ "Attach" = "Anhängen"; @@ -1460,7 +1460,7 @@ set passcode view */ "Current passphrase…" = "Aktuelles Passwort…"; /* No comment provided by engineer. */ -"Current profile" = "Aktueller Profil"; +"Current profile" = "Aktuelles Profil"; /* No comment provided by engineer. */ "Currently maximum supported file size is %@." = "Die derzeit maximal unterstützte Dateigröße beträgt %@."; @@ -3677,7 +3677,7 @@ time to disappear */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Nur Ihr Kontakt kann Sprachnachrichten versenden."; -/* No comment provided by engineer. */ +/* alert action */ "Open" = "Öffnen"; /* No comment provided by engineer. */ @@ -3695,21 +3695,12 @@ time to disappear */ /* No comment provided by engineer. */ "Open group" = "Gruppe öffnen"; -/* No comment provided by engineer. */ -"Open link" = "Web-Link öffnen"; - -/* No comment provided by engineer. */ -"Open links from chat list" = "Web-Links aus der Chat-Liste öffnen"; - /* authentication reason */ "Open migration to another device" = "Migration auf ein anderes Gerät öffnen"; /* No comment provided by engineer. */ "Open Settings" = "Geräte-Einstellungen öffnen"; -/* No comment provided by engineer. */ -"Open web link?" = "Web-Link öffnen?"; - /* No comment provided by engineer. */ "Opening app…" = "App wird geöffnet…"; @@ -5490,6 +5481,9 @@ report reason */ /* No comment provided by engineer. */ "Use TCP port %@ when no port is specified." = "Solange kein Port konfiguriert ist, wird TCP-Port %@ genutzt."; +/* No comment provided by engineer. */ +"Use TCP port 443 for preset servers only." = "TCP-Port 443 nur für voreingestellte Server verwenden."; + /* No comment provided by engineer. */ "Use the app while in the call." = "Die App kann während eines Anrufs genutzt werden."; @@ -5709,9 +5703,6 @@ report reason */ /* pref value */ "yes" = "Ja"; -/* No comment provided by engineer. */ -"Yes" = "Ja"; - /* No comment provided by engineer. */ "you" = "Profil"; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 9644521723..e797b73b98 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -505,6 +505,9 @@ swipe action */ /* No comment provided by engineer. */ "All reports will be archived for you." = "Todos los informes serán archivados para ti."; +/* No comment provided by engineer. */ +"All servers" = "Todos los servidores"; + /* No comment provided by engineer. */ "All your contacts will remain connected." = "Todos tus contactos permanecerán conectados."; @@ -688,9 +691,6 @@ swipe action */ /* No comment provided by engineer. */ "Archiving database" = "Archivando base de datos"; -/* No comment provided by engineer. */ -"Ask" = "Preguntar"; - /* No comment provided by engineer. */ "Attach" = "Adjuntar"; @@ -3677,7 +3677,7 @@ time to disappear */ /* 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. */ +/* alert action */ "Open" = "Abrir"; /* No comment provided by engineer. */ @@ -3695,21 +3695,12 @@ time to disappear */ /* No comment provided by engineer. */ "Open group" = "Grupo abierto"; -/* No comment provided by engineer. */ -"Open link" = "Abrir enlace"; - -/* No comment provided by engineer. */ -"Open links from chat list" = "Abrir enlaces desde listado de chats"; - /* authentication reason */ "Open migration to another device" = "Abrir menú migración a otro dispositivo"; /* No comment provided by engineer. */ "Open Settings" = "Abrir Configuración"; -/* No comment provided by engineer. */ -"Open web link?" = "¿Abrir enlace web?"; - /* No comment provided by engineer. */ "Opening app…" = "Iniciando aplicación…"; @@ -5305,7 +5296,7 @@ report reason */ "Unblock member" = "Desbloquear miembro"; /* No comment provided by engineer. */ -"Unblock member for all?" = "¿Desbloquear miembro para todos?"; +"Unblock member for all?" = "¿Desbloquear el miembro para todos?"; /* No comment provided by engineer. */ "Unblock member?" = "¿Desbloquear miembro?"; @@ -5490,6 +5481,9 @@ report reason */ /* No comment provided by engineer. */ "Use TCP port %@ when no port is specified." = "Se usa el puerto TCP %@ cuando no se ha especificado otro."; +/* No comment provided by engineer. */ +"Use TCP port 443 for preset servers only." = "Usar puerto TCP 443 solo en servidores predefinidos."; + /* No comment provided by engineer. */ "Use the app while in the call." = "Usar la aplicación durante la llamada."; @@ -5709,9 +5703,6 @@ report reason */ /* pref value */ "yes" = "sí"; -/* No comment provided by engineer. */ -"Yes" = "Si"; - /* No comment provided by engineer. */ "you" = "tu"; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index a0145a7f47..1c16f8847d 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -682,9 +682,6 @@ swipe action */ /* No comment provided by engineer. */ "Archiving database" = "Archivage de la base de données"; -/* No comment provided by engineer. */ -"Ask" = "Demander"; - /* No comment provided by engineer. */ "Attach" = "Attacher"; @@ -3566,7 +3563,7 @@ time to disappear */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Seul votre contact peut envoyer des messages vocaux."; -/* No comment provided by engineer. */ +/* alert action */ "Open" = "Ouvrir"; /* No comment provided by engineer. */ diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index 23ff6fc0bf..c190cccaba 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -488,7 +488,7 @@ swipe action */ "all members" = "összes tag"; /* No comment provided by engineer. */ -"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Az összes üzenet és fájl **végpontok közötti titkosítással**, a közvetlen üzenetek továbbá kvantumálló titkosítással is rendelkeznek."; +"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Az összes üzenet és fájl **végpontok közötti titkosítással**, a közvetlen üzenetek továbbá kvantumbiztos titkosítással is rendelkeznek."; /* No comment provided by engineer. */ "All messages will be deleted - this cannot be undone!" = "Az összes üzenet törölve lesz – ez a művelet nem vonható vissza!"; @@ -505,6 +505,9 @@ swipe action */ /* No comment provided by engineer. */ "All reports will be archived for you." = "Az összes jelentés archiválva lesz az Ön számára."; +/* No comment provided by engineer. */ +"All servers" = "Összes kiszolgáló"; + /* No comment provided by engineer. */ "All your contacts will remain connected." = "Az összes partnerével kapcsolatban marad."; @@ -688,9 +691,6 @@ swipe action */ /* No comment provided by engineer. */ "Archiving database" = "Adatbázis archiválása"; -/* No comment provided by engineer. */ -"Ask" = "Mindig kérdezzen rá"; - /* No comment provided by engineer. */ "Attach" = "Mellékelés"; @@ -3274,10 +3274,10 @@ snd error text */ "Messages were deleted after you selected them." = "Az üzeneteket törölték miután kijelölte őket."; /* No comment provided by engineer. */ -"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Az üzeneteket, fájlokat és hívásokat **végpontok közötti titkosítással**, sérülés utáni titkosság-védelemmel és -helyreállítással, továbbá visszautasítással védi."; +"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Az üzenetek, a fájlok és a hívások **végpontok közötti titkosítással**, sérülés utáni titkosságvédelemmel és -helyreállítással, továbbá letagadhatósággal vannak védve."; /* No comment provided by engineer. */ -"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Az üzenetek, fájlok és hívások **végpontok közötti kvantumálló titkosítással** sérülés utáni titkosságvédelemmel, visszautasítással és feltörés utáni helyreállítással vannak védve."; +"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Az üzenetek, a fájlok és a hívások **végpontok közötti kvantumbiztos titkosítással**, sérülés utáni titkosságvédelemmel és -helyreállítással, továbbá letagadhatósággal vannak védve."; /* No comment provided by engineer. */ "Migrate device" = "Eszköz átköltöztetése"; @@ -3677,7 +3677,7 @@ time to disappear */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Csak a partnere tud hangüzeneteket küldeni."; -/* No comment provided by engineer. */ +/* alert action */ "Open" = "Megnyitás"; /* No comment provided by engineer. */ @@ -3695,21 +3695,12 @@ time to disappear */ /* No comment provided by engineer. */ "Open group" = "Csoport megnyitása"; -/* No comment provided by engineer. */ -"Open link" = "Hivatkozás megnyitása"; - -/* No comment provided by engineer. */ -"Open links from chat list" = "Hivatkozás megnyitása a csevegési listából"; - /* authentication reason */ "Open migration to another device" = "Átköltöztetés indítása egy másik eszközre"; /* No comment provided by engineer. */ "Open Settings" = "Beállítások megnyitása"; -/* No comment provided by engineer. */ -"Open web link?" = "Megnyitja a webhivatkozást?"; - /* No comment provided by engineer. */ "Opening app…" = "Az alkalmazás megnyitása…"; @@ -4023,10 +4014,10 @@ time to disappear */ "Push server" = "Push-kiszolgáló"; /* chat item text */ -"quantum resistant e2e encryption" = "végpontok közötti kvantumálló titkosítás"; +"quantum resistant e2e encryption" = "végpontok közötti kvantumbiztos titkosítás"; /* No comment provided by engineer. */ -"Quantum resistant encryption" = "Kvantumálló titkosítás"; +"Quantum resistant encryption" = "Kvantumbiztos titkosítás"; /* No comment provided by engineer. */ "Rate the app" = "Értékelje az alkalmazást"; @@ -5164,7 +5155,7 @@ report reason */ "This chat is protected by end-to-end encryption." = "Ez a csevegés végpontok közötti titkosítással védett."; /* E2EE info chat item */ -"This chat is protected by quantum resistant end-to-end encryption." = "Ez a csevegés végpontok közötti kvantumálló titkosítással védett."; +"This chat is protected by quantum resistant end-to-end encryption." = "Ez a csevegés végpontok közötti kvantumbiztos titkosítással védett."; /* notification title */ "this contact" = "ez a partner"; @@ -5490,6 +5481,9 @@ report reason */ /* No comment provided by engineer. */ "Use TCP port %@ when no port is specified." = "A következő TCP-port használata, amikor nincs port megadva: %@."; +/* No comment provided by engineer. */ +"Use TCP port 443 for preset servers only." = "A 443-as TCP-port használata kizárólag az előre beállított kiszolgálokhoz."; + /* No comment provided by engineer. */ "Use the app while in the call." = "Használja az alkalmazást hívás közben."; @@ -5551,7 +5545,7 @@ report reason */ "via relay" = "egy továbbítókiszolgálón keresztül"; /* No comment provided by engineer. */ -"Via secure quantum resistant protocol." = "Biztonságos kvantumálló-protokollon keresztül."; +"Via secure quantum resistant protocol." = "Biztonságos kvantumbiztos protokollon keresztül."; /* No comment provided by engineer. */ "video" = "videó"; @@ -5709,9 +5703,6 @@ report reason */ /* pref value */ "yes" = "igen"; -/* No comment provided by engineer. */ -"Yes" = "Igen"; - /* No comment provided by engineer. */ "you" = "Ön"; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index f68424325f..f67a492cc4 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -505,6 +505,9 @@ swipe action */ /* No comment provided by engineer. */ "All reports will be archived for you." = "Tutte le segnalazioni verranno archiviate per te."; +/* No comment provided by engineer. */ +"All servers" = "Tutti i server"; + /* No comment provided by engineer. */ "All your contacts will remain connected." = "Tutti i tuoi contatti resteranno connessi."; @@ -688,9 +691,6 @@ swipe action */ /* No comment provided by engineer. */ "Archiving database" = "Archiviazione del database"; -/* No comment provided by engineer. */ -"Ask" = "Chiedi"; - /* No comment provided by engineer. */ "Attach" = "Allega"; @@ -3677,7 +3677,7 @@ time to disappear */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Solo il tuo contatto può inviare messaggi vocali."; -/* No comment provided by engineer. */ +/* alert action */ "Open" = "Apri"; /* No comment provided by engineer. */ @@ -3695,21 +3695,12 @@ time to disappear */ /* No comment provided by engineer. */ "Open group" = "Apri gruppo"; -/* No comment provided by engineer. */ -"Open link" = "Apri link"; - -/* No comment provided by engineer. */ -"Open links from chat list" = "Apri i link dall'elenco delle chat"; - /* authentication reason */ "Open migration to another device" = "Apri migrazione ad un altro dispositivo"; /* No comment provided by engineer. */ "Open Settings" = "Apri le impostazioni"; -/* No comment provided by engineer. */ -"Open web link?" = "Aprire il link?"; - /* No comment provided by engineer. */ "Opening app…" = "Apertura dell'app…"; @@ -5490,6 +5481,9 @@ report reason */ /* No comment provided by engineer. */ "Use TCP port %@ when no port is specified." = "Usa la porta TCP %@ quando non è specificata alcuna porta."; +/* No comment provided by engineer. */ +"Use TCP port 443 for preset servers only." = "Usa la porta TCP 443 solo per i server preimpostati."; + /* No comment provided by engineer. */ "Use the app while in the call." = "Usa l'app mentre sei in chiamata."; @@ -5709,9 +5703,6 @@ report reason */ /* pref value */ "yes" = "sì"; -/* No comment provided by engineer. */ -"Yes" = "Sì"; - /* No comment provided by engineer. */ "you" = "tu"; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index 89934d67ce..9d0cccf591 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -2367,7 +2367,7 @@ time to disappear */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "音声メッセージを送れるのはあなたの連絡相手だけです。"; -/* No comment provided by engineer. */ +/* alert action */ "Open" = "開く"; /* No comment provided by engineer. */ diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index cd026361e0..d2cfcba0de 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -688,9 +688,6 @@ swipe action */ /* No comment provided by engineer. */ "Archiving database" = "Database archiveren"; -/* No comment provided by engineer. */ -"Ask" = "Vragen"; - /* No comment provided by engineer. */ "Attach" = "Bijvoegen"; @@ -3677,7 +3674,7 @@ time to disappear */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Alleen uw contact kan spraak berichten verzenden."; -/* No comment provided by engineer. */ +/* alert action */ "Open" = "Open"; /* No comment provided by engineer. */ @@ -3695,21 +3692,12 @@ time to disappear */ /* No comment provided by engineer. */ "Open group" = "Open groep"; -/* No comment provided by engineer. */ -"Open link" = "Link openen"; - -/* No comment provided by engineer. */ -"Open links from chat list" = "Open links van chatlijst"; - /* authentication reason */ "Open migration to another device" = "Open de migratie naar een ander apparaat"; /* No comment provided by engineer. */ "Open Settings" = "Open instellingen"; -/* No comment provided by engineer. */ -"Open web link?" = "Weblink openen?"; - /* No comment provided by engineer. */ "Opening app…" = "App openen…"; @@ -5694,9 +5682,6 @@ report reason */ /* pref value */ "yes" = "Ja"; -/* No comment provided by engineer. */ -"Yes" = "Ja"; - /* No comment provided by engineer. */ "you" = "jij"; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index 82730db52a..867f3beff4 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -682,9 +682,6 @@ swipe action */ /* No comment provided by engineer. */ "Archiving database" = "Archiwizowanie bazy danych"; -/* No comment provided by engineer. */ -"Ask" = "Zapytaj"; - /* No comment provided by engineer. */ "Attach" = "Dołącz"; @@ -3320,7 +3317,7 @@ time to disappear */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Tylko Twój kontakt może wysyłać wiadomości głosowe."; -/* No comment provided by engineer. */ +/* alert action */ "Open" = "Otwórz"; /* No comment provided by engineer. */ diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 45a2d6db4f..b819d013b9 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -685,9 +685,6 @@ swipe action */ /* No comment provided by engineer. */ "Archiving database" = "Подготовка архива"; -/* No comment provided by engineer. */ -"Ask" = "Спросить"; - /* No comment provided by engineer. */ "Attach" = "Прикрепить"; @@ -3575,7 +3572,7 @@ time to disappear */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Только Ваш контакт может отправлять голосовые сообщения."; -/* No comment provided by engineer. */ +/* alert action */ "Open" = "Открыть"; /* No comment provided by engineer. */ @@ -3593,21 +3590,12 @@ time to disappear */ /* No comment provided by engineer. */ "Open group" = "Открыть группу"; -/* No comment provided by engineer. */ -"Open link" = "Открыть ссылку"; - -/* No comment provided by engineer. */ -"Open links from chat list" = "Открыть ссылку из списка чатов"; - /* authentication reason */ "Open migration to another device" = "Открытие миграции на другое устройство"; /* No comment provided by engineer. */ "Open Settings" = "Открыть Настройки"; -/* No comment provided by engineer. */ -"Open web link?" = "Открыть веб-ссылку?"; - /* No comment provided by engineer. */ "Opening app…" = "Приложение отрывается…"; @@ -5544,9 +5532,6 @@ report reason */ /* pref value */ "yes" = "да"; -/* No comment provided by engineer. */ -"Yes" = "Да"; - /* No comment provided by engineer. */ "you" = "Вы"; diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index 718d547c67..ab0703333e 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -3353,7 +3353,7 @@ time to disappear */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Sadece karşıdaki kişi sesli mesajlar gönderebilir."; -/* No comment provided by engineer. */ +/* alert action */ "Open" = "Aç"; /* No comment provided by engineer. */ diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index f54ecec21d..8e2b514ed4 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -3401,7 +3401,7 @@ time to disappear */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Тільки ваш контакт може надсилати голосові повідомлення."; -/* No comment provided by engineer. */ +/* alert action */ "Open" = "Відкрито"; /* No comment provided by engineer. */ diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index b363d25a4c..6ceeeb22d0 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -61,6 +61,9 @@ /* No comment provided by engineer. */ "**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**推荐**:设备令牌和通知会发送至 SimpleX Chat 通知服务器,但是消息内容、大小或者发送人不会。"; +/* No comment provided by engineer. */ +"**Scan / Paste link**: to connect via a link you received." = "**扫描/粘贴链接**:用您收到的链接连接。"; + /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**警告**:及时推送通知需要保存在钥匙串的密码。"; @@ -133,6 +136,9 @@ /* notification title */ "%@ wants to connect!" = "%@ 要连接!"; +/* format for date separator in chat */ +"%@, %@" = "%1$@, %2$@"; + /* No comment provided by engineer. */ "%@, %@ and %lld members" = "%@, %@ 和 %lld 成员"; @@ -145,9 +151,24 @@ /* time interval */ "%d days" = "%d 天"; +/* forward confirmation reason */ +"%d file(s) are still being downloaded." = "仍在下载 %d 个文件。"; + +/* forward confirmation reason */ +"%d file(s) failed to download." = "%d 个文件下载失败。"; + +/* forward confirmation reason */ +"%d file(s) were deleted." = "已刪除 %d 个文件。"; + +/* forward confirmation reason */ +"%d file(s) were not downloaded." = "未能下载 %d 个文件。"; + /* time interval */ "%d hours" = "%d 小时"; +/* alert title */ +"%d messages not forwarded" = "未转发 %d 条消息"; + /* time interval */ "%d min" = "%d 分钟"; @@ -157,8 +178,11 @@ /* time interval */ "%d sec" = "%d 秒"; +/* delete after time */ +"%d seconds(s)" = "%d 秒"; + /* integrity error chat item */ -"%d skipped message(s)" = "%d 跳过消息"; +"%d skipped message(s)" = "跳过的 %d 条消息"; /* time interval */ "%d weeks" = "%d 星期"; @@ -262,6 +286,15 @@ time interval */ time interval */ "1 week" = "1周"; +/* delete after time */ +"1 year" = "1 年"; + +/* No comment provided by engineer. */ +"1-time link" = "一次性链接"; + +/* No comment provided by engineer. */ +"1-time link can be used *with one contact only* - share in person or via any messenger." = "一次性链接*只能给一名联系人*使用。当面或使用聊天应用分享链接。"; + /* No comment provided by engineer. */ "5 minutes" = "5分钟"; @@ -295,6 +328,9 @@ time interval */ /* No comment provided by engineer. */ "Abort changing address?" = "中止地址更改?"; +/* No comment provided by engineer. */ +"About operators" = "关于运营方"; + /* No comment provided by engineer. */ "About SimpleX Chat" = "关于SimpleX Chat"; @@ -309,6 +345,9 @@ accept incoming call via notification swipe action */ "Accept" = "接受"; +/* No comment provided by engineer. */ +"Accept conditions" = "接受条款"; + /* No comment provided by engineer. */ "Accept connection request?" = "接受联系人?"; @@ -322,18 +361,30 @@ swipe action */ /* call status */ "accepted call" = "已接受通话"; +/* No comment provided by engineer. */ +"Accepted conditions" = "已接受的条款"; + /* No comment provided by engineer. */ "Acknowledged" = "确认"; /* No comment provided by engineer. */ "Acknowledgement errors" = "确认错误"; +/* token status text */ +"Active" = "活跃"; + /* No comment provided by engineer. */ "Active connections" = "活动连接"; /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "将地址添加到您的个人资料,以便您的联系人可以与其他人共享。个人资料更新将发送给您的联系人。"; +/* No comment provided by engineer. */ +"Add friends" = "添加好友"; + +/* No comment provided by engineer. */ +"Add list" = "添加列表"; + /* No comment provided by engineer. */ "Add profile" = "添加个人资料"; @@ -343,12 +394,27 @@ swipe action */ /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "扫描二维码来添加服务器。"; +/* No comment provided by engineer. */ +"Add team members" = "添加团队成员"; + /* No comment provided by engineer. */ "Add to another device" = "添加另一设备"; +/* No comment provided by engineer. */ +"Add to list" = "添加到列表"; + /* No comment provided by engineer. */ "Add welcome message" = "添加欢迎信息"; +/* No comment provided by engineer. */ +"Add your team members to the conversations." = "将你的团队成员加入对话。"; + +/* No comment provided by engineer. */ +"Added media & file servers" = "已添加媒体和文件服务器"; + +/* No comment provided by engineer. */ +"Added message servers" = "已添加消息服务器"; + /* No comment provided by engineer. */ "Additional accent" = "附加重音"; @@ -364,6 +430,12 @@ swipe action */ /* No comment provided by engineer. */ "Address change will be aborted. Old receiving address will be used." = "将中止地址更改。将使用旧接收地址。"; +/* No comment provided by engineer. */ +"Address or 1-time link?" = "地址还是一次性链接?"; + +/* No comment provided by engineer. */ +"Address settings" = "地址设置"; + /* member role */ "admin" = "管理员"; @@ -388,12 +460,18 @@ swipe action */ /* chat item text */ "agreeing encryption…" = "同意加密…"; +/* No comment provided by engineer. */ +"All" = "全部"; + /* No comment provided by engineer. */ "All app data is deleted." = "已删除所有应用程序数据。"; /* No comment provided by engineer. */ "All chats and messages will be deleted - this cannot be undone!" = "所有聊天记录和消息将被删除——这一行为无法撤销!"; +/* alert message */ +"All chats will be removed from the list %@, and the list deleted." = "列表 %@ 和其中全部聊天将被删除。"; + /* No comment provided by engineer. */ "All data is erased when it is entered." = "所有数据在输入后将被删除。"; @@ -406,6 +484,9 @@ swipe action */ /* feature role */ "all members" = "所有成员"; +/* No comment provided by engineer. */ +"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "所有消息和文件均通过**端到端加密**发送;私信以量子安全方式发送。"; + /* No comment provided by engineer. */ "All messages will be deleted - this cannot be undone!" = "所有消息都将被删除 - 这无法被撤销!"; @@ -418,6 +499,12 @@ swipe action */ /* profile dropdown */ "All profiles" = "所有配置文件"; +/* No comment provided by engineer. */ +"All reports will be archived for you." = "将为你存档所有举报。"; + +/* No comment provided by engineer. */ +"All servers" = "全部服务器"; + /* No comment provided by engineer. */ "All your contacts will remain connected." = "所有联系人会保持连接。"; @@ -463,6 +550,9 @@ swipe action */ /* No comment provided by engineer. */ "Allow to irreversibly delete sent messages. (24 hours)" = "允许不可撤回地删除已发送消息"; +/* No comment provided by engineer. */ +"Allow to report messsages to moderators." = "允许向 moderators 举报消息。"; + /* No comment provided by engineer. */ "Allow to send files and media." = "允许发送文件和媒体。"; @@ -517,6 +607,9 @@ swipe action */ /* No comment provided by engineer. */ "and %lld other events" = "和 %lld 其他事件"; +/* report reason */ +"Another reason" = "另一个理由"; + /* No comment provided by engineer. */ "Answer call" = "接听来电"; @@ -532,6 +625,9 @@ swipe action */ /* No comment provided by engineer. */ "App encrypts new local files (except videos)." = "应用程序为新的本地文件(视频除外)加密。"; +/* No comment provided by engineer. */ +"App group:" = "应用组:"; + /* No comment provided by engineer. */ "App icon" = "应用程序图标"; @@ -541,6 +637,9 @@ swipe action */ /* No comment provided by engineer. */ "App passcode is replaced with self-destruct passcode." = "应用程序密码被替换为自毁密码。"; +/* No comment provided by engineer. */ +"App session" = "应用会话"; + /* No comment provided by engineer. */ "App version" = "应用程序版本"; @@ -556,12 +655,30 @@ swipe action */ /* No comment provided by engineer. */ "Apply to" = "应用于"; +/* No comment provided by engineer. */ +"Archive" = "存档"; + +/* No comment provided by engineer. */ +"Archive %lld reports?" = "存档 %lld 个举报?"; + +/* No comment provided by engineer. */ +"Archive all reports?" = "存档所有举报?"; + /* No comment provided by engineer. */ "Archive and upload" = "存档和上传"; /* No comment provided by engineer. */ "Archive contacts to chat later." = "存档联系人以便稍后聊天."; +/* No comment provided by engineer. */ +"Archive report" = "存档举报"; + +/* No comment provided by engineer. */ +"Archive report?" = "存档举报?"; + +/* swipe action */ +"Archive reports" = "存档举报"; + /* No comment provided by engineer. */ "Archived contacts" = "已存档的联系人"; @@ -613,6 +730,9 @@ swipe action */ /* No comment provided by engineer. */ "Auto-accept images" = "自动接受图片"; +/* alert title */ +"Auto-accept settings" = "自动接受设置"; + /* No comment provided by engineer. */ "Back" = "返回"; @@ -634,15 +754,36 @@ swipe action */ /* No comment provided by engineer. */ "Bad message ID" = "错误消息 ID"; +/* No comment provided by engineer. */ +"Better calls" = "更佳的通话"; + /* No comment provided by engineer. */ "Better groups" = "更佳的群组"; +/* No comment provided by engineer. */ +"Better groups performance" = "更好的群性能"; + +/* No comment provided by engineer. */ +"Better message dates." = "更好的消息日期。"; + /* No comment provided by engineer. */ "Better messages" = "更好的消息"; /* No comment provided by engineer. */ "Better networking" = "更好的网络"; +/* No comment provided by engineer. */ +"Better notifications" = "更佳的通知"; + +/* No comment provided by engineer. */ +"Better privacy and security" = "更好的隐私和安全"; + +/* No comment provided by engineer. */ +"Better security ✅" = "更佳的安全性✅"; + +/* No comment provided by engineer. */ +"Better user experience" = "更佳的使用体验"; + /* No comment provided by engineer. */ "Black" = "黑色"; @@ -704,6 +845,15 @@ marked deleted chat item preview text */ /* 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)!" = "保加利亚语、芬兰语、泰语和乌克兰语——感谢用户和[Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; +/* No comment provided by engineer. */ +"Business address" = "企业地址"; + +/* No comment provided by engineer. */ +"Business chats" = "企业聊天"; + +/* No comment provided by engineer. */ +"Businesses" = "企业"; + /* 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)." = "通过聊天资料(默认)或者[通过连接](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)。"; @@ -777,6 +927,12 @@ alert button */ /* No comment provided by engineer. */ "Change" = "更改"; +/* alert title */ +"Change automatic message deletion?" = "更改消息自动删除设置?"; + +/* authentication reason */ +"Change chat profiles" = "更改聊天资料"; + /* No comment provided by engineer. */ "Change database passphrase?" = "更改数据库密码?"; @@ -820,6 +976,15 @@ set passcode view */ /* chat item text */ "changing address…" = "更改地址…"; +/* No comment provided by engineer. */ +"Chat" = "聊天"; + +/* No comment provided by engineer. */ +"Chat already exists" = "聊天已存在"; + +/* No comment provided by engineer. */ +"Chat already exists!" = "聊天已存在!"; + /* No comment provided by engineer. */ "Chat colors" = "聊天颜色"; @@ -856,15 +1021,30 @@ set passcode view */ /* No comment provided by engineer. */ "Chat preferences" = "聊天偏好设置"; +/* alert message */ +"Chat preferences were changed." = "聊天偏好设置已修改。"; + /* No comment provided by engineer. */ "Chat profile" = "用户资料"; /* No comment provided by engineer. */ "Chat theme" = "聊天主题"; +/* No comment provided by engineer. */ +"Chat will be deleted for all members - this cannot be undone!" = "将为所有成员删除聊天 - 此操作无法撤销!"; + +/* No comment provided by engineer. */ +"Chat will be deleted for you - this cannot be undone!" = "将为你删除聊天 - 此操作无法撤销!"; + /* No comment provided by engineer. */ "Chats" = "聊天"; +/* No comment provided by engineer. */ +"Check messages every 20 min." = "每 20 分钟检查消息。"; + +/* No comment provided by engineer. */ +"Check messages when allowed." = "在被允许时检查消息。"; + /* alert title */ "Check server address and try again." = "检查服务器地址并再试一次。"; @@ -898,6 +1078,12 @@ set passcode view */ /* No comment provided by engineer. */ "Clear conversation?" = "清除对话吗?"; +/* No comment provided by engineer. */ +"Clear group?" = "清除群?"; + +/* No comment provided by engineer. */ +"Clear or delete group?" = "清除还是删除群?"; + /* No comment provided by engineer. */ "Clear private notes?" = "清除私密笔记?"; @@ -913,6 +1099,9 @@ set passcode view */ /* No comment provided by engineer. */ "colored" = "彩色"; +/* report reason */ +"Community guidelines violation" = "违反社区指导方针"; + /* server test step */ "Compare file" = "对比文件"; @@ -925,9 +1114,27 @@ set passcode view */ /* No comment provided by engineer. */ "Completed" = "已完成"; +/* No comment provided by engineer. */ +"Conditions accepted on: %@." = "已于 %@ 接受条款。"; + +/* No comment provided by engineer. */ +"Conditions are accepted for the operator(s): **%@**." = "已接受运营方 **%@** 的条款。"; + +/* No comment provided by engineer. */ +"Conditions are already accepted for these operator(s): **%@**." = "已经接受下列运营方的条款:**%@**。"; + /* No comment provided by engineer. */ "Conditions of use" = "使用条款"; +/* No comment provided by engineer. */ +"Conditions will be accepted for the operator(s): **%@**." = "将接受下列运营方的条款:**%@**。"; + +/* No comment provided by engineer. */ +"Conditions will be accepted on: %@." = "将于 %@ 接受条款。"; + +/* No comment provided by engineer. */ +"Conditions will be automatically accepted for enabled operators on: %@." = "将在 %@ 自动接受启用的运营方的条款。"; + /* No comment provided by engineer. */ "Configure ICE servers" = "配置 ICE 服务器"; @@ -964,6 +1171,9 @@ set passcode view */ /* No comment provided by engineer. */ "Confirm upload" = "确认上传"; +/* token status text */ +"Confirmed" = "已确定"; + /* server test step */ "Connect" = "连接"; @@ -1063,6 +1273,9 @@ set passcode view */ /* No comment provided by engineer. */ "Connection and servers status." = "连接和服务器状态。"; +/* No comment provided by engineer. */ +"Connection blocked" = "连接被阻止"; + /* No comment provided by engineer. */ "Connection error" = "连接错误"; @@ -1072,12 +1285,24 @@ set passcode view */ /* chat list item title (it should not be shown */ "connection established" = "连接已建立"; +/* No comment provided by engineer. */ +"Connection is blocked by server operator:\n%@" = "连接被运营方 %@ 阻止"; + +/* No comment provided by engineer. */ +"Connection not ready." = "连接未就绪。"; + /* No comment provided by engineer. */ "Connection notifications" = "连接通知"; /* No comment provided by engineer. */ "Connection request sent!" = "已发送连接请求!"; +/* No comment provided by engineer. */ +"Connection requires encryption renegotiation." = "连接需要加密重协商。"; + +/* No comment provided by engineer. */ +"Connection security" = "连接安全性"; + /* No comment provided by engineer. */ "Connection terminated" = "连接被终止"; @@ -1135,6 +1360,9 @@ set passcode view */ /* No comment provided by engineer. */ "Contacts can mark messages for deletion; you will be able to view them." = "联系人可以将信息标记为删除;您将可以查看这些信息。"; +/* blocking reason */ +"Content violates conditions of use" = "内容违反使用条款"; + /* No comment provided by engineer. */ "Continue" = "继续"; @@ -1150,12 +1378,18 @@ set passcode view */ /* No comment provided by engineer. */ "Core version: v%@" = "核心版本: v%@"; +/* No comment provided by engineer. */ +"Corner" = "拐角"; + /* No comment provided by engineer. */ "Correct name to %@?" = "将名称更正为 %@?"; /* No comment provided by engineer. */ "Create" = "创建"; +/* No comment provided by engineer. */ +"Create 1-time link" = "创建一次性链接"; + /* No comment provided by engineer. */ "Create a group using a random profile." = "使用随机身份创建群组."; @@ -1171,6 +1405,9 @@ set passcode view */ /* No comment provided by engineer. */ "Create link" = "创建链接"; +/* No comment provided by engineer. */ +"Create list" = "创建列表"; + /* No comment provided by engineer. */ "Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" = "在[桌面应用程序](https://simplex.chat/downloads/)中创建新的个人资料。 💻"; @@ -1207,6 +1444,9 @@ set passcode view */ /* No comment provided by engineer. */ "creator" = "创建者"; +/* No comment provided by engineer. */ +"Current conditions text couldn't be loaded, you can review conditions via this link:" = "无法加载当前条款文本,你可以通过此链接审阅条款:"; + /* No comment provided by engineer. */ "Current Passcode" = "当前密码"; @@ -1225,6 +1465,9 @@ set passcode view */ /* No comment provided by engineer. */ "Custom time" = "自定义时间"; +/* No comment provided by engineer. */ +"Customizable message shape." = "可自定义消息形状。"; + /* No comment provided by engineer. */ "Customize theme" = "自定义主题"; @@ -1341,12 +1584,21 @@ swipe action */ /* No comment provided by engineer. */ "Delete and notify contact" = "删除并通知联系人"; +/* No comment provided by engineer. */ +"Delete chat" = "删除聊天"; + +/* No comment provided by engineer. */ +"Delete chat messages from your device." = "从你的设备删除聊天消息。"; + /* No comment provided by engineer. */ "Delete chat profile" = "删除聊天资料"; /* No comment provided by engineer. */ "Delete chat profile?" = "删除聊天资料?"; +/* No comment provided by engineer. */ +"Delete chat?" = "删除聊天?"; + /* No comment provided by engineer. */ "Delete connection" = "删除连接"; @@ -1392,6 +1644,9 @@ swipe action */ /* No comment provided by engineer. */ "Delete link?" = "删除链接?"; +/* alert title */ +"Delete list?" = "删除列表?"; + /* No comment provided by engineer. */ "Delete member message?" = "删除成员消息?"; @@ -1410,6 +1665,9 @@ swipe action */ /* No comment provided by engineer. */ "Delete old database?" = "删除旧数据库吗?"; +/* No comment provided by engineer. */ +"Delete or moderate up to 200 messages." = "允许自行删除或管理员移除最多200条消息。"; + /* No comment provided by engineer. */ "Delete pending connection?" = "删除待定连接?"; @@ -1419,6 +1677,9 @@ swipe action */ /* server test step */ "Delete queue" = "删除队列"; +/* No comment provided by engineer. */ +"Delete report" = "删除举报"; + /* No comment provided by engineer. */ "Delete up to 20 messages at once." = "一次最多删除 20 条信息。"; @@ -1449,6 +1710,9 @@ swipe action */ /* No comment provided by engineer. */ "Deletion errors" = "删除错误"; +/* No comment provided by engineer. */ +"Delivered even when Apple drops them." = "已送达,即使苹果已将其删除。"; + /* No comment provided by engineer. */ "Delivery" = "传送"; @@ -1498,7 +1762,7 @@ swipe action */ "Device" = "设备"; /* No comment provided by engineer. */ -"Device authentication is disabled. Turning off SimpleX Lock." = "设备验证被禁用。关闭 SimpleX 锁定。"; +"Device authentication is disabled. Turning off SimpleX Lock." = "设备验证已禁用。 SimpleX 已解锁。"; /* No comment provided by engineer. */ "Device authentication is not enabled. You can turn on SimpleX Lock via Settings, once you enable device authentication." = "没有启用设备验证。一旦启用设备验证,您可以通过设置打开 SimpleX 锁定。"; @@ -1516,11 +1780,20 @@ swipe action */ "Direct messages" = "私信"; /* No comment provided by engineer. */ -"Direct messages between members are prohibited." = "此群中禁止成员之间私信。"; +"Direct messages between members are prohibited in this chat." = "此群禁止成员间私信。"; + +/* No comment provided by engineer. */ +"Direct messages between members are prohibited." = "此群禁止成员间私信。"; /* No comment provided by engineer. */ "Disable (keep overrides)" = "禁用(保留覆盖)"; +/* alert title */ +"Disable automatic message deletion?" = "禁用消息自动销毁?"; + +/* alert button */ +"Disable delete messages" = "停用消息删除"; + /* No comment provided by engineer. */ "Disable for all" = "全部禁用"; @@ -1572,21 +1845,33 @@ swipe action */ /* No comment provided by engineer. */ "Do NOT send messages directly, even if your or destination server does not support private routing." = "请勿直接发送消息,即使您的服务器或目标服务器不支持私有路由。"; +/* No comment provided by engineer. */ +"Do not use credentials with proxy." = "代理不使用身份验证凭据。"; + /* No comment provided by engineer. */ "Do NOT use private routing." = "不要使用私有路由。"; /* No comment provided by engineer. */ "Do NOT use SimpleX for emergency calls." = "请勿使用 SimpleX 进行紧急通话。"; +/* No comment provided by engineer. */ +"Documents:" = "文档:"; + /* No comment provided by engineer. */ "Don't create address" = "不创建地址"; /* No comment provided by engineer. */ "Don't enable" = "不要启用"; +/* No comment provided by engineer. */ +"Don't miss important messages." = "不错过重要消息。"; + /* No comment provided by engineer. */ "Don't show again" = "不再显示"; +/* No comment provided by engineer. */ +"Done" = "完成"; + /* No comment provided by engineer. */ "Downgrade and open chat" = "降级并打开聊天"; @@ -1603,6 +1888,9 @@ chat item action */ /* server test step */ "Download file" = "下载文件"; +/* alert action */ +"Download files" = "下载文件"; + /* No comment provided by engineer. */ "Downloaded" = "已下载"; @@ -1622,7 +1910,7 @@ chat item action */ "duplicate message" = "重复的消息"; /* No comment provided by engineer. */ -"duplicates" = "复本"; +"duplicates" = "副本"; /* No comment provided by engineer. */ "Duration" = "时长"; @@ -1630,6 +1918,9 @@ chat item action */ /* No comment provided by engineer. */ "e2e encrypted" = "端到端加密"; +/* No comment provided by engineer. */ +"E2E encrypted notifications." = "端到端加密的通知。"; + /* chat item action */ "Edit" = "编辑"; @@ -1648,6 +1939,9 @@ chat item action */ /* No comment provided by engineer. */ "Enable camera access" = "启用相机访问"; +/* No comment provided by engineer. */ +"Enable Flux in Network & servers settings for better metadata privacy." = "在“网络&服务器”设置中启用 Flux,更好地保护元数据隐私。"; + /* No comment provided by engineer. */ "Enable for all" = "全部启用"; @@ -1759,6 +2053,9 @@ chat item action */ /* chat item text */ "encryption re-negotiation required for %@" = "需要为 %@ 重新进行加密协商"; +/* No comment provided by engineer. */ +"Encryption renegotiation in progress." = "正进行加密重协商。"; + /* No comment provided by engineer. */ "ended" = "已结束"; @@ -1807,21 +2104,33 @@ chat item action */ /* No comment provided by engineer. */ "Error aborting address change" = "中止地址更改错误"; +/* alert title */ +"Error accepting conditions" = "接受条款出错"; + /* No comment provided by engineer. */ "Error accepting contact request" = "接受联系人请求错误"; /* No comment provided by engineer. */ "Error adding member(s)" = "添加成员错误"; +/* alert title */ +"Error adding server" = "添加服务器出错"; + /* No comment provided by engineer. */ "Error changing address" = "更改地址错误"; +/* No comment provided by engineer. */ +"Error changing connection profile" = "更改连接资料出错"; + /* No comment provided by engineer. */ "Error changing role" = "更改角色错误"; /* No comment provided by engineer. */ "Error changing setting" = "更改设置错误"; +/* No comment provided by engineer. */ +"Error changing to incognito!" = "切换至隐身聊天出错!"; + /* No comment provided by engineer. */ "Error connecting to forwarding server %@. Please try later." = "连接到转发服务器 %@ 时出错。请稍后尝试。"; @@ -1834,6 +2143,9 @@ chat item action */ /* No comment provided by engineer. */ "Error creating group link" = "创建群组链接错误"; +/* alert title */ +"Error creating list" = "创建列表出错"; + /* No comment provided by engineer. */ "Error creating member contact" = "创建成员联系人时出错"; @@ -1843,6 +2155,9 @@ chat item action */ /* No comment provided by engineer. */ "Error creating profile!" = "创建资料错误!"; +/* No comment provided by engineer. */ +"Error creating report" = "创建举报出错"; + /* No comment provided by engineer. */ "Error decrypting file" = "解密文件时出错"; @@ -1891,6 +2206,12 @@ chat item action */ /* No comment provided by engineer. */ "Error joining group" = "加入群组错误"; +/* alert title */ +"Error loading servers" = "加载服务器出错"; + +/* No comment provided by engineer. */ +"Error migrating settings" = "迁移设置出错"; + /* No comment provided by engineer. */ "Error opening chat" = "打开聊天时出错"; @@ -1903,12 +2224,21 @@ chat item action */ /* No comment provided by engineer. */ "Error reconnecting servers" = "重新连接服务器时出错"; +/* alert title */ +"Error registering for notifications" = "注册消息推送出错"; + /* No comment provided by engineer. */ "Error removing member" = "删除成员错误"; +/* alert title */ +"Error reordering lists" = "重排列表出错"; + /* No comment provided by engineer. */ "Error resetting statistics" = "重置统计信息时出错"; +/* alert title */ +"Error saving chat list" = "保存聊天列表出错"; + /* No comment provided by engineer. */ "Error saving group profile" = "保存群组资料错误"; @@ -1921,6 +2251,9 @@ chat item action */ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "保存密码到钥匙串错误"; +/* alert title */ +"Error saving servers" = "保存服务器出错"; + /* when migrating */ "Error saving settings" = "保存设置出错"; @@ -1948,18 +2281,27 @@ chat item action */ /* No comment provided by engineer. */ "Error stopping chat" = "停止聊天错误"; +/* No comment provided by engineer. */ +"Error switching profile" = "切换配置文件出错"; + /* alertTitle */ "Error switching profile!" = "切换资料错误!"; /* No comment provided by engineer. */ "Error synchronizing connection" = "同步连接错误"; +/* No comment provided by engineer. */ +"Error testing server connection" = "检验服务器连接出错"; + /* No comment provided by engineer. */ "Error updating group link" = "更新群组链接错误"; /* No comment provided by engineer. */ "Error updating message" = "更新消息错误"; +/* alert title */ +"Error updating server" = "更新服务器出错"; + /* No comment provided by engineer. */ "Error updating settings" = "更新设置错误"; @@ -1989,6 +2331,9 @@ snd error text */ /* No comment provided by engineer. */ "Errors" = "错误"; +/* servers error */ +"Errors in servers configuration." = "服务器配置有错误。"; + /* No comment provided by engineer. */ "Even when disabled in the conversation." = "即使在对话中被禁用。"; @@ -2001,6 +2346,9 @@ snd error text */ /* No comment provided by engineer. */ "expired" = "过期"; +/* token status text */ +"Expired" = "已过期"; + /* No comment provided by engineer. */ "Export database" = "导出数据库"; @@ -2025,15 +2373,30 @@ snd error text */ /* No comment provided by engineer. */ "Fast and no wait until the sender is online!" = "快速且无需等待发件人在线!"; +/* No comment provided by engineer. */ +"Faster deletion of groups." = "更快地删除群。"; + /* No comment provided by engineer. */ "Faster joining and more reliable messages." = "加入速度更快、信息更可靠。"; +/* No comment provided by engineer. */ +"Faster sending messages." = "更快发送消息。"; + /* swipe action */ "Favorite" = "最喜欢"; +/* No comment provided by engineer. */ +"Favorites" = "收藏"; + /* file error alert title */ "File error" = "文件错误"; +/* alert message */ +"File errors:\n%@" = "文件错误:\n%@"; + +/* file error text */ +"File is blocked by server operator:\n%@." = "文件被服务器运营方阻止:\n%@。"; + /* file error text */ "File not found - most likely file was deleted or cancelled." = "找不到文件 - 很可能文件已被删除或取消。"; @@ -2109,15 +2472,45 @@ snd error text */ /* No comment provided by engineer. */ "Fix not supported by group member" = "修复群组成员不支持的问题"; +/* No comment provided by engineer. */ +"For all moderators" = "所有 moderators"; + +/* servers error */ +"For chat profile %@:" = "为聊天资料 %@:"; + /* No comment provided by engineer. */ "For console" = "用于控制台"; +/* No comment provided by engineer. */ +"For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server." = "比如,如果你通过 SimpleX 服务器收到消息,应用会通过 Flux 服务器传送它们。"; + +/* No comment provided by engineer. */ +"For me" = "仅自己"; + +/* No comment provided by engineer. */ +"For private routing" = "用于私密路由"; + +/* No comment provided by engineer. */ +"For social media" = "用于社交媒体"; + /* chat item action */ "Forward" = "转发"; +/* alert title */ +"Forward %d message(s)?" = "转发 %d 条消息?"; + /* No comment provided by engineer. */ "Forward and save messages" = "转发并保存消息"; +/* alert action */ +"Forward messages" = "已转发的消息"; + +/* alert message */ +"Forward messages without files?" = "仅转发消息不转发文件?"; + +/* No comment provided by engineer. */ +"Forward up to 20 messages at once." = "一次转发最多20条消息。"; + /* No comment provided by engineer. */ "forwarded" = "已转发"; @@ -2127,6 +2520,9 @@ snd error text */ /* No comment provided by engineer. */ "Forwarded from" = "转发自"; +/* No comment provided by engineer. */ +"Forwarding %lld messages" = "正在转发 %lld 条消息"; + /* No comment provided by engineer. */ "Forwarding server %@ failed to connect to destination server %@. Please try later." = "转发服务器 %@ 无法连接到目标服务器 %@。请稍后尝试。"; @@ -2163,6 +2559,9 @@ snd error text */ /* No comment provided by engineer. */ "Further reduced battery usage" = "进一步减少电池使用"; +/* No comment provided by engineer. */ +"Get notified when mentioned." = "被提及时收到通知。"; + /* No comment provided by engineer. */ "GIFs and stickers" = "GIF 和贴纸"; @@ -2235,9 +2634,15 @@ snd error text */ /* No comment provided by engineer. */ "Group will be deleted for you - this cannot be undone!" = "将为您删除群组——此操作无法撤消!"; +/* No comment provided by engineer. */ +"Groups" = "群"; + /* No comment provided by engineer. */ "Help" = "帮助"; +/* No comment provided by engineer. */ +"Help admins moderating their groups." = "帮助管理员管理群组。"; + /* No comment provided by engineer. */ "Hidden" = "隐藏"; @@ -2268,6 +2673,15 @@ snd error text */ /* time unit */ "hours" = "小时"; +/* No comment provided by engineer. */ +"How it affects privacy" = "它如何影响隐私"; + +/* No comment provided by engineer. */ +"How it helps privacy" = "它如何帮助隐私"; + +/* alert button */ +"How it works" = "工作原理"; + /* No comment provided by engineer. */ "How SimpleX works" = "SimpleX的工作原理"; @@ -2331,6 +2745,9 @@ snd error text */ /* No comment provided by engineer. */ "Importing archive" = "正在导入存档"; +/* No comment provided by engineer. */ +"Improved delivery, reduced traffic usage.\nMore improvements are coming soon!" = "改善传送,降低流量使用。\n更多改进即将推出!"; + /* No comment provided by engineer. */ "Improved message delivery" = "改进了消息传递"; @@ -2352,6 +2769,12 @@ snd error text */ /* No comment provided by engineer. */ "inactive" = "无效"; +/* report reason */ +"Inappropriate content" = "不当内容"; + +/* report reason */ +"Inappropriate profile" = "不当个人资料"; + /* No comment provided by engineer. */ "Incognito" = "隐身聊天"; @@ -2418,6 +2841,21 @@ snd error text */ /* No comment provided by engineer. */ "Interface colors" = "界面颜色"; +/* token status text */ +"Invalid" = "无效"; + +/* token status text */ +"Invalid (bad token)" = "Token 无效"; + +/* token status text */ +"Invalid (expired)" = "无效(已过期)"; + +/* token status text */ +"Invalid (unregistered)" = "无效(未注册)"; + +/* token status text */ +"Invalid (wrong topic)" = "无效(话题有误)"; + /* invalid chat data */ "invalid chat" = "无效聊天"; @@ -2469,6 +2907,9 @@ snd error text */ /* No comment provided by engineer. */ "Invite members" = "邀请成员"; +/* No comment provided by engineer. */ +"Invite to chat" = "邀请加入聊天"; + /* No comment provided by engineer. */ "Invite to group" = "邀请加入群组"; @@ -2490,6 +2931,9 @@ snd error text */ /* No comment provided by engineer. */ "iOS Keychain will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving push notifications." = "在您重启应用或改变密码后,iOS钥匙串将被用来安全地存储密码——它将允许接收推送通知。"; +/* No comment provided by engineer. */ +"IP address" = "IP 地址"; + /* No comment provided by engineer. */ "Irreversible message deletion" = "不可撤回消息移除"; @@ -2580,6 +3024,12 @@ snd error text */ /* swipe action */ "Leave" = "离开"; +/* No comment provided by engineer. */ +"Leave chat" = "离开聊天"; + +/* No comment provided by engineer. */ +"Leave chat?" = "离开聊天?"; + /* No comment provided by engineer. */ "Leave group" = "离开群组"; @@ -2607,6 +3057,15 @@ snd error text */ /* No comment provided by engineer. */ "Linked desktops" = "已链接桌面"; +/* swipe action */ +"List" = "列表"; + +/* No comment provided by engineer. */ +"List name and emoji should be different for all lists." = "所有列表的名称和表情符号都应不同。"; + +/* No comment provided by engineer. */ +"List name..." = "列表名…"; + /* No comment provided by engineer. */ "LIVE" = "实时"; @@ -2676,12 +3135,21 @@ snd error text */ /* item status text */ "Member inactive" = "成员不活跃"; +/* chat feature */ +"Member reports" = "成员举报"; + +/* No comment provided by engineer. */ +"Member role will be changed to \"%@\". All chat members will be notified." = "将变更成员角色为“%@”。所有成员都会收到通知。"; + /* No comment provided by engineer. */ "Member role will be changed to \"%@\". All group members will be notified." = "成员角色将更改为 \"%@\"。所有群成员将收到通知。"; /* No comment provided by engineer. */ "Member role will be changed to \"%@\". The member will receive a new invitation." = "成员角色将更改为 \"%@\"。该成员将收到一份新的邀请。"; +/* No comment provided by engineer. */ +"Member will be removed from chat - this cannot be undone!" = "将从聊天中删除成员 - 此操作无法撤销!"; + /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "成员将被移出群组——此操作无法撤消!"; @@ -2691,6 +3159,9 @@ snd error text */ /* No comment provided by engineer. */ "Members can irreversibly delete sent messages. (24 hours)" = "群组成员可以不可撤回地删除已发送的消息"; +/* No comment provided by engineer. */ +"Members can report messsages to moderators." = "成员可以向 moderators 举报消息。"; + /* No comment provided by engineer. */ "Members can send direct messages." = "群组成员可以私信。"; @@ -2706,6 +3177,9 @@ snd error text */ /* No comment provided by engineer. */ "Members can send voice messages." = "群组成员可以发送语音消息。"; +/* No comment provided by engineer. */ +"Mention members 👋" = "提及成员👋"; + /* No comment provided by engineer. */ "Menus" = "菜单"; @@ -2751,6 +3225,9 @@ snd error text */ /* No comment provided by engineer. */ "Message servers" = "消息服务器"; +/* No comment provided by engineer. */ +"Message shape" = "消息形状"; + /* No comment provided by engineer. */ "Message source remains private." = "消息来源保持私密。"; @@ -2775,12 +3252,18 @@ snd error text */ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "将显示来自 %@ 的消息!"; +/* alert message */ +"Messages in this chat will never be deleted." = "此聊天中的消息永远不会被删除。"; + /* No comment provided by engineer. */ "Messages received" = "收到的消息"; /* No comment provided by engineer. */ "Messages sent" = "已发送的消息"; +/* alert message */ +"Messages were deleted after you selected them." = "在你选中消息后这些消息已被删除。"; + /* No comment provided by engineer. */ "Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "消息、文件和通话受到 **端到端加密** 的保护,具有完全正向保密、否认和闯入恢复。"; @@ -2847,12 +3330,18 @@ snd error text */ /* time unit */ "months" = "月"; +/* swipe action */ +"More" = "更多"; + /* No comment provided by engineer. */ "More improvements are coming soon!" = "更多改进即将推出!"; /* No comment provided by engineer. */ "More reliable network connection." = "更可靠的网络连接。"; +/* No comment provided by engineer. */ +"More reliable notifications" = "更可靠的通知"; + /* item status description */ "Most likely this connection is deleted." = "此连接很可能已被删除。"; @@ -2862,6 +3351,9 @@ snd error text */ /* notification label action */ "Mute" = "静音"; +/* notification label action */ +"Mute all" = "全部静音"; + /* No comment provided by engineer. */ "Muted when inactive!" = "不活动时静音!"; @@ -2874,12 +3366,18 @@ snd error text */ /* No comment provided by engineer. */ "Network connection" = "网络连接"; +/* No comment provided by engineer. */ +"Network decentralization" = "网络去中心化"; + /* snd error text */ "Network issues - message expired after many attempts to send it." = "网络问题 - 消息在多次尝试发送后过期。"; /* No comment provided by engineer. */ "Network management" = "网络管理"; +/* No comment provided by engineer. */ +"Network operator" = "网络运营方"; + /* No comment provided by engineer. */ "Network settings" = "网络设置"; @@ -2889,6 +3387,9 @@ snd error text */ /* delete after time */ "never" = "从不"; +/* token status text */ +"New" = "新"; + /* No comment provided by engineer. */ "New chat" = "新聊天"; @@ -2907,6 +3408,9 @@ snd error text */ /* No comment provided by engineer. */ "New display name" = "新显示名"; +/* notification */ +"New events" = "新事件"; + /* No comment provided by engineer. */ "New in %@" = "%@ 的新内容"; @@ -2928,6 +3432,15 @@ snd error text */ /* No comment provided by engineer. */ "New passphrase…" = "新密码……"; +/* No comment provided by engineer. */ +"New server" = "新服务器"; + +/* No comment provided by engineer. */ +"New SOCKS credentials will be used every time you start the app." = "每次启动应用都会使用新的 SOCKS 凭据。"; + +/* No comment provided by engineer. */ +"New SOCKS credentials will be used for each server." = "每个服务器都会使用新的 SOCKS 凭据。"; + /* pref value */ "no" = "否"; @@ -2937,6 +3450,15 @@ snd error text */ /* Authentication unavailable */ "No app password" = "没有应用程序密码"; +/* No comment provided by engineer. */ +"No chats" = "无聊天"; + +/* No comment provided by engineer. */ +"No chats found" = "找不到聊天"; + +/* No comment provided by engineer. */ +"No chats in list %@" = "列表 %@ 中无聊天"; + /* No comment provided by engineer. */ "No contacts selected" = "未选择联系人"; @@ -2967,9 +3489,24 @@ snd error text */ /* No comment provided by engineer. */ "No info, try to reload" = "无信息,尝试重新加载"; +/* servers error */ +"No media & file servers." = "无媒体和文件服务器。"; + +/* No comment provided by engineer. */ +"No message" = "无消息"; + +/* servers error */ +"No message servers." = "无消息服务器。"; + /* No comment provided by engineer. */ "No network connection" = "无网络连接"; +/* No comment provided by engineer. */ +"No permission to record speech" = "无录音权限"; + +/* No comment provided by engineer. */ +"No permission to record video" = "无录像权限"; + /* No comment provided by engineer. */ "No permission to record voice message" = "没有录制语音消息的权限"; @@ -2979,24 +3516,57 @@ snd error text */ /* No comment provided by engineer. */ "No received or sent files" = "未收到或发送文件"; +/* servers error */ +"No servers for private message routing." = "无私密消息路由服务器。"; + +/* servers error */ +"No servers to receive files." = "无文件接收服务器。"; + +/* servers error */ +"No servers to receive messages." = "无消息接收服务器。"; + +/* servers error */ +"No servers to send files." = "无文件发送服务器。"; + /* copied message info in history */ "no text" = "无文本"; +/* alert title */ +"No token!" = "无 token!"; + +/* No comment provided by engineer. */ +"No unread chats" = "没有未读聊天"; + /* No comment provided by engineer. */ "No user identifiers." = "没有用户标识符。"; /* No comment provided by engineer. */ "Not compatible!" = "不兼容!"; +/* No comment provided by engineer. */ +"Notes" = "附注"; + /* No comment provided by engineer. */ "Nothing selected" = "未选中任何内容"; +/* alert title */ +"Nothing to forward!" = "无可转发!"; + /* No comment provided by engineer. */ "Notifications" = "通知"; /* No comment provided by engineer. */ "Notifications are disabled!" = "通知被禁用!"; +/* alert title */ +"Notifications error" = "通知错误"; + +/* No comment provided by engineer. */ +"Notifications privacy" = "通知隐私"; + +/* alert title */ +"Notifications status" = "通知状态"; + /* No comment provided by engineer. */ "Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "现在管理员可以:\n- 删除成员的消息。\n- 禁用成员(“观察员”角色)"; @@ -3041,6 +3611,9 @@ time to disappear */ /* No comment provided by engineer. */ "Onion hosts will not be used." = "将不会使用 Onion 主机。"; +/* No comment provided by engineer. */ +"Only chat owners can change preferences." = "仅聊天所有人可更改首选项。"; + /* No comment provided by engineer. */ "Only client devices store user profiles, contacts, groups, and messages." = "只有客户端设备存储用户资料、联系人、群组和**双层端到端加密**发送的消息。"; @@ -3056,6 +3629,12 @@ time to disappear */ /* No comment provided by engineer. */ "Only group owners can enable voice messages." = "只有群主可以启用语音信息。"; +/* No comment provided by engineer. */ +"Only sender and moderators see it" = "仅发送人和moderators能看到"; + +/* No comment provided by engineer. */ +"Only you and moderators see it" = "只有你和moderators能看到"; + /* No comment provided by engineer. */ "Only you can add message reactions." = "只有您可以添加消息回应。"; @@ -3086,15 +3665,21 @@ time to disappear */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "只有您的联系人可以发送语音消息。"; -/* No comment provided by engineer. */ +/* alert action */ "Open" = "打开"; +/* No comment provided by engineer. */ +"Open changes" = "打开更改"; + /* No comment provided by engineer. */ "Open chat" = "打开聊天"; /* authentication reason */ "Open chat console" = "打开聊天控制台"; +/* No comment provided by engineer. */ +"Open conditions" = "打开条款"; + /* No comment provided by engineer. */ "Open group" = "打开群"; @@ -3107,6 +3692,15 @@ time to disappear */ /* No comment provided by engineer. */ "Opening app…" = "正在打开应用程序…"; +/* No comment provided by engineer. */ +"Operator" = "运营方"; + +/* alert title */ +"Operator server" = "运营方服务器"; + +/* No comment provided by engineer. */ +"Or import archive file" = "或者导入或者导入压缩文件"; + /* No comment provided by engineer. */ "Or paste archive link" = "或粘贴存档链接"; @@ -3119,6 +3713,12 @@ time to disappear */ /* No comment provided by engineer. */ "Or show this code" = "或者显示此码"; +/* No comment provided by engineer. */ +"Or to share privately" = "或者私下分享"; + +/* No comment provided by engineer. */ +"Organize chats into lists" = "将聊天组织到列表"; + /* No comment provided by engineer. */ "other" = "其他"; @@ -3149,6 +3749,9 @@ time to disappear */ /* No comment provided by engineer. */ "Passcode set!" = "密码已设置!"; +/* No comment provided by engineer. */ +"Password" = "密码"; + /* No comment provided by engineer. */ "Password to show" = "显示密码"; @@ -3604,19 +4207,22 @@ swipe action */ "Reveal" = "揭示"; /* No comment provided by engineer. */ -"Revoke" = "撤销"; - -/* cancel file action */ -"Revoke file" = "撤销文件"; +"Review conditions" = "审阅条款"; /* No comment provided by engineer. */ -"Revoke file?" = "撤销文件?"; +"Revoke" = "吊销"; + +/* cancel file action */ +"Revoke file" = "吊销文件"; + +/* No comment provided by engineer. */ +"Revoke file?" = "吊销文件?"; /* No comment provided by engineer. */ "Role" = "角色"; /* No comment provided by engineer. */ -"Run chat" = "运行聊天程序"; +"Run chat" = "运行聊天"; /* No comment provided by engineer. */ "Safely receive files" = "安全接收文件"; @@ -3646,6 +4252,9 @@ chat item action */ /* No comment provided by engineer. */ "Save group profile" = "保存群组资料"; +/* No comment provided by engineer. */ +"Save list" = "保存列表"; + /* No comment provided by engineer. */ "Save passphrase and open chat" = "保存密码并打开聊天"; @@ -3667,6 +4276,9 @@ chat item action */ /* No comment provided by engineer. */ "Save welcome message?" = "保存欢迎信息?"; +/* alert title */ +"Save your profile?" = "保存您的个人资料?"; + /* No comment provided by engineer. */ "saved" = "已保存"; @@ -3685,6 +4297,9 @@ chat item action */ /* No comment provided by engineer. */ "Saved WebRTC ICE servers will be removed" = "已保存的WebRTC ICE服务器将被删除"; +/* No comment provided by engineer. */ +"Saving %lld messages" = "正在保存 %lld 条消息"; + /* No comment provided by engineer. */ "Scale" = "规模"; @@ -4025,6 +4640,18 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX Address" = "SimpleX 地址"; +/* No comment provided by engineer. */ +"SimpleX address and 1-time links are safe to share via any messenger." = "可以通过任何消息应用安全分享 SimpleX 地址和一次性链接。"; + +/* No comment provided by engineer. */ +"SimpleX address or 1-time link?" = "SimpleX 地址或一次性链接?"; + +/* simplex link type */ +"SimpleX channel link" = "SimpleX 频道链接"; + +/* No comment provided by engineer. */ +"SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "SimpleX Chat 与 Flux 达成了协议,将由 Flux 控制的服务器纳入 SimpleX 应用。"; + /* No comment provided by engineer. */ "SimpleX Chat security was audited by Trail of Bits." = "SimpleX Chat 的安全性 由 Trail of Bits 审核。"; @@ -4032,10 +4659,10 @@ chat item action */ "SimpleX contact address" = "SimpleX 联系地址"; /* notification */ -"SimpleX encrypted message or connection event" = "SimpleX 加密消息或连接项目"; +"SimpleX encrypted message or connection event" = "SimpleX 加密的消息或连接事件"; /* simplex link type */ -"SimpleX group link" = "SimpleX 群组链接"; +"SimpleX group link" = "SimpleX 群链接"; /* chat feature */ "SimpleX links" = "SimpleX 链接"; @@ -4061,6 +4688,9 @@ chat item action */ /* simplex link type */ "SimpleX one-time invitation" = "SimpleX 一次性邀请"; +/* No comment provided by engineer. */ +"SimpleX protocols reviewed by Trail of Bits." = "SimpleX 协议由 Trail of Bits 审阅。"; + /* No comment provided by engineer. */ "Simplified incognito mode" = "简化的隐身模式"; @@ -4082,6 +4712,9 @@ chat item action */ /* blur media */ "Soft" = "软"; +/* No comment provided by engineer. */ +"Some app settings were not migrated." = "部分应用设置未被迁移。"; + /* No comment provided by engineer. */ "Some file(s) were not exported:" = "某些文件未导出:"; diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index 6472faa671..6646720c5c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -1145,7 +1145,7 @@ Die ID der nächsten Nachricht ist falsch (kleiner oder gleich der vorherigen). \nDies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompromittiert wurde. %1$d Nachrichten konnten nicht entschlüsselt werden. - Der Hash der vorherigen Nachricht unterscheidet sich.\" + Der Hash der vorherigen Nachricht unterscheidet sich. Sie können die SimpleX-Sperre über die Einstellungen aktivieren. SOCKS-Proxy-Einstellungen System-Authentifizierung @@ -2460,6 +2460,6 @@ Für diesen Link wird eine neuere App-Version benötigt. Bitte aktualisieren Sie die App oder bitten Sie Ihren Kontakt einen kompatiblen Link zu senden. Alle Server Aus - Für voreingestellte Server nur TCP-Port 443 verwenden . + TCP-Port 443 nur für voreingestellte Server verwenden. Voreingestellte Server diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index 171b367ca0..5667c42d2d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -1068,7 +1068,7 @@ Hash de mensaje incorrecto ID de mensaje incorrecto Puede ocurrir si tu contacto o tu usáis una copia de seguridad antigua de la base de datos. - El hash del mensaje anterior es diferente.\" + El hash del mensaje anterior es diferente. %1$d mensaje(s) no ha(n) podido ser descifrado(s). Detener archivo El archivo será eliminado de los servidores. @@ -1552,7 +1552,7 @@ ¿Bloqear miembro para todos? Creado: %s Bloquear para todos - ¿Desbloquear miembro para todos? + ¿Desbloquear el miembro para todos? Desbloquear para todos bloqueado bloqueado por administrador @@ -2360,7 +2360,7 @@ rechazado ¿Expulsar miembros? ¡Los mensajes de estos miembros serán mostrados! - ¿Desbloquear miembros para todos? + ¿Desbloquear los miembros para todos? ¡Todos los mensajes nuevos de estos miembros estarán ocultos! ¿Bloquear miembros para todos? Los miembros serán expulsados del chat. ¡No puede deshacerse! diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index 2776310540..c8897c4063 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -1349,7 +1349,7 @@ Akkor lesz kapcsolódva, ha a meghívási kérése el lesz fogadva, várjon, vagy ellenőrizze később! A hangüzenetek küldése le van tiltva. Alkalmazás akkumulátor-használata / Korlátlan módot az alkalmazás beállításaiban.]]> - Biztonságos kvantumálló-protokollon keresztül. + Biztonságos kvantumbiztos protokollon keresztül. - legfeljebb 5 perc hosszúságú hangüzenetek.\n- egyéni üzenet-eltűnési időkorlát.\n- előzmények szerkesztése. Társítás számítógéppel menüt a hordozható eszköz alkalmazásban és olvassa be a QR-kódot.]]>
    %s ekkor: %s @@ -1652,7 +1652,7 @@ Átköltöztetés egy másik eszközre Figyelmeztetés: az archívum törölve lesz.]]> Átköltöztetés egy másik eszközről - Kvantumálló titkosítás + Kvantumbiztos titkosítás Megpróbálhatja még egyszer. Átköltöztetés befejezve Átköltöztetés egy másik eszközre QR-kód használatával. @@ -1660,12 +1660,12 @@ Megjegyzés: ha két eszközön is ugyanazt az adatbázist használja, akkor biztonsági védelemként megszakítja a partnereitől érkező üzenetek visszafejtését.]]> Megpróbálhatja még egyszer. Érvénytelen hivatkozás - végpontok közötti kvantumálló titkosítás + végpontok közötti kvantumbiztos titkosítás Ez a csevegés végpontok közötti titkosítással védett. Átköltöztetési párbeszédablak megnyitása - Ez a csevegés végpontok közötti kvantumálló titkosítással védett. - végpontok közötti titkosítással, sérülés utáni titkosság-védelemmel és -helyreállítással, továbbá visszautasítással védi.]]> - végpontok közötti kvantumálló titkosítással sérülés utáni titkosságvédelemmel, visszautasítással és feltörés utáni helyreállítással vannak védve.]]> + Ez a csevegés végpontok közötti kvantumbiztos titkosítással védett. + végpontok közötti titkosítással, sérülés utáni titkosságvédelemmel és -helyreállítással, továbbá letagadhatósággal vannak védve.]]> + végpontok közötti kvantumbiztos titkosítással, sérülés utáni titkosságvédelemmel és -helyreállítással, továbbá letagadhatósággal vannak védve.]]> Hiba történt az értesítés megjelenítésekor, lépjen kapcsolatba a fejlesztőkkel. Keresse meg ezt az engedélyt az Android beállításaiban, és adja meg kézzel. Engedélyezés a beállításokban @@ -2172,7 +2172,7 @@ A küldéshez másolhatja és csökkentheti az üzenet méretét. Adja hozzá a munkatársait a beszélgetésekhez. Üzleti cím - végpontok közötti titkosítással, a közvetlen üzenetek továbbá kvantumálló titkosítással is rendelkeznek.]]> + végpontok közötti titkosítással, a közvetlen üzenetek továbbá kvantumbiztos titkosítással is rendelkeznek.]]> Hogyan segíti az adatvédelmet Nincs háttérszolgáltatás Értesítések és akkumulátor diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index 201745f042..6c086835ea 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -1069,7 +1069,7 @@ \nPuò accadere a causa di qualche bug o quando la connessione è compromessa.
    %1$d messaggi non decifrati. Hash del messaggio errato - L\'hash del messaggio precedente è diverso.\" + L\'hash del messaggio precedente è diverso. Si prega di segnalarlo agli sviluppatori. L\'invio del file verrà interrotto. Ferma file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index a85bd119c5..28a5f6f50d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -1059,7 +1059,7 @@ 当你或你的连接使用旧数据库备份时,可能会发生这种情况。 解密错误 请向开发者报告。 - 上一条消息的散列不同。\" + 上条消息的哈希值不同。 下一条消息的 ID 不正确(小于或等于上一条)。 \n它可能是由于某些错误或连接被破坏才发生。 停止文件 @@ -1068,10 +1068,10 @@ 停止接收文件? 即将停止接收文件。 停止 - 撤销文件 - 撤销文件? + 吊销文件 + 吊销文件? 文件将从服务器中删除。 - 撤销 + 吊销 音频/视频通话 " \n在 v5.1 版本中可用" @@ -1330,7 +1330,7 @@ 更改密码或重启应用后,密码将以明文形式保存在设置中。 粘贴你收到的链接以与你的联系人联系… 送达回执 - 没有选择聊天 + 没有选中的聊天 可以加密 重新协商加密 禁用(保留组覆盖) @@ -2061,7 +2061,7 @@ 用户名 分享配置文件 转发消息出错 - 在你选中消息后这些消息被删除。 + 在你选中消息后这些消息已被删除。 %1$d 个文件错误:\n%2$s 其他 %1$d 个文件错误。 %1$d 个文件未被下载。 @@ -2070,29 +2070,29 @@ 没什么可转发的! 仍有 %1$d 个文件在下载中。 %1$d 个文件下载失败。 - %1$d 个文件被删除了。 + 删除了 %1$d 个文件。 下载 %1$s 条消息未被转发 转发消息… 转发 %1$s 条消息 保存 %1$s 条消息 已静音 - 管理形状 + 消息形状 拐角 尾部 初始化 WebView 出错。确保你安装了 WebView 且其支持的架构为 arm64。\n错误:%s 应用会话 - 每次启动应用都会使用新的 SOCKS5 凭据。 + 每次启动应用都会使用新的 SOCKS 凭据。 服务器 打开 Safari 设置/网站/麦克风,接着在 localhost 选择“允许”。 要进行通话,请允许使用设备麦克风。结束通话并尝试再次呼叫。 单击地址栏附近的“信息”按钮允许使用麦克风。 - 每个服务器都会使用新的 SOCKS5 凭据。 + 每个服务器都会使用新的 SOCKS 凭据。 更好的消息日期。 更佳的安全性✅ 更佳的使用体验 可自定义消息形状。 - 一次性转发最多20条消息。 + 一次转发最多20条消息。 Trail of Bits 审核了 SimpleX 协议。 通话期间切换音频和视频。 对一次性邀请切换聊天配置文件。 @@ -2160,11 +2160,11 @@ 应用通过在每个对话中使用不同运营方保护你的隐私。 接受条款 模糊 - 地址或一次性链接? + 地址还是一次性链接? 已添加消息服务器 已添加媒体和文件服务器 地址设置 - 已接受条款 + 已接受的条款 应用工具栏 仅用于一名联系人 - 面对面或通过任何消息应用分享.]]> %s.]]> @@ -2192,7 +2192,7 @@ 你可以在“网络和服务器”设置中配置运营方。 接受运营方条款的日期:%s 远程移动设备 - 或者导入压缩文件 + 或者导入存档文件 小米设备:请在系统设置中开启“自动启动”让通知正常工作。]]> 消息太大! 你可以复制并减小消息大小来发送它。 @@ -2282,8 +2282,8 @@ 另一个理由 已存档的举报 违反社区指导方针 - 不恰当的内容 - 不恰当的个人资料 + 不当内容 + 不当个人资料 仅发送人和moderators能看到 只有你和moderators能看到 垃圾信息 @@ -2337,7 +2337,7 @@ 更好的隐私和安全 不错过重要消息。 更快地删除群。 - 更快死发送消息。 + 更快发送消息。 被提及时收到通知。 帮助管理员管理群组。 将聊天组织到列表 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml index 3038dd3d6c..8ae414de00 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml @@ -1495,7 +1495,7 @@ %s 的版本。請檢察兩台裝置安裝的是否版本相同]]> 更可靠的網路連接 發現和加入群組 - 裝置 + 設備 新行動裝置 保存設定出錯 導出的檔案不存在 @@ -1628,7 +1628,7 @@ 找到桌面 自動連接 與PC版的連接不穩定 - 桌面 + 桌上電腦 已安裝的PC版本不支援。請確認兩台裝置所安裝的版本相同 PC版邀請碼錯誤 通過連結連接? From c0b9a0e0944d79aba366b13fbb73bed8454742ca Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Mon, 12 May 2025 14:40:36 +0000 Subject: [PATCH 545/567] android, desktop: narrow condition for showing reported count toolbar (to avoid showing it to regular members who received reports due to a bug in older version) (#5894) --- .../kotlin/chat/simplex/common/views/chat/ChatView.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index dc1c0b71a5..b3fdcf79c0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -833,7 +833,7 @@ fun ChatLayout( } val reportsCount = reportsCount(chatInfo?.id) if (oneHandUI.value && chatBottomBar.value) { - if (chatsCtx.contentTag == null && reportsCount > 0) { + if (chatInfo is ChatInfo.Group && chatInfo.groupInfo.canModerate && chatsCtx.contentTag == null && reportsCount > 0) { ReportedCountToolbar(reportsCount, withStatusBar = true, showGroupReports) } else { StatusBarBackground() @@ -865,7 +865,7 @@ fun ChatLayout( SelectedItemsCounterToolbar(selectedChatItems, !oneHandUI.value || !chatBottomBar.value) } } - if (chatsCtx.contentTag == null && reportsCount > 0 && (!oneHandUI.value || !chatBottomBar.value)) { + if (chatInfo is ChatInfo.Group && chatInfo.groupInfo.canModerate && chatsCtx.contentTag == null && reportsCount > 0 && (!oneHandUI.value || !chatBottomBar.value)) { ReportedCountToolbar(reportsCount, withStatusBar = false, showGroupReports) } } From c822fa53f60a976dfed971959c6d1a1429848d57 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 12 May 2025 14:02:39 +0100 Subject: [PATCH 546/567] ios: 6.3.4 (build 276) --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index af1eebd8b3..8a035c70a9 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -1971,7 +1971,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 275; + CURRENT_PROJECT_VERSION = 276; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2021,7 +2021,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 275; + CURRENT_PROJECT_VERSION = 276; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2063,7 +2063,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 275; + CURRENT_PROJECT_VERSION = 276; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2083,7 +2083,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 275; + CURRENT_PROJECT_VERSION = 276; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2108,7 +2108,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 275; + CURRENT_PROJECT_VERSION = 276; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2145,7 +2145,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 275; + CURRENT_PROJECT_VERSION = 276; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2182,7 +2182,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 275; + CURRENT_PROJECT_VERSION = 276; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2233,7 +2233,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 275; + CURRENT_PROJECT_VERSION = 276; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2284,7 +2284,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 275; + CURRENT_PROJECT_VERSION = 276; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2318,7 +2318,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 275; + CURRENT_PROJECT_VERSION = 276; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; From a5a9d4f7d5d647631a2c75bc3b0aee94258c5134 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 12 May 2025 16:27:42 +0100 Subject: [PATCH 547/567] website: translations (#5892) * Translated using Weblate (Ukrainian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/uk/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/ * Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 78.5% (202 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hant/ * Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 78.5% (202 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hant/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/uk/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/ * Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 78.5% (202 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hant/ * Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 78.5% (202 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hant/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ --------- Co-authored-by: Bezruchenko Simon Co-authored-by: dns Co-authored-by: 4 Bi 5aYzVk 93FCVjWLWxh44XH3984teVSfjwFYmUGUrbvnHwGirk9 Co-authored-by: summoner001 --- website/langs/hu.json | 4 +- website/langs/uk.json | 40 ++++---- website/langs/zh_Hans.json | 2 +- website/langs/zh_Hant.json | 190 +++++++++++++++++++++++++++++++++++-- 4 files changed, 206 insertions(+), 30 deletions(-) diff --git a/website/langs/hu.json b/website/langs/hu.json index f062899224..8acd7d9a60 100644 --- a/website/langs/hu.json +++ b/website/langs/hu.json @@ -24,7 +24,7 @@ "simplex-chat-protocol": "A SimpleX Chat-protokoll", "terminal-cli": "Terminál CLI", "terms-and-privacy-policy": "Adatvédelmi irányelvek", - "hero-header": "Újradefiniált adatvédelem", + "hero-header": "Újraértelmezett adatvédelem", "hero-subheader": "Az első üzenetváltó-alkalmazás
    felhasználói azonosítók nélkül", "hero-p-1": "Más alkalmazások felhasználói azonosítókkal rendelkeznek: Signal, Matrix, Session, Briar, Jami, Cwtch, stb.
    A SimpleX azonban nem, még véletlenszerű számokkal sem.
    Ez radikálisan javítja az adatvédelmet.", "hero-overlay-1-textlink": "Miért ártanak a felhasználói azonosítók az adatvédelemnek?", @@ -138,7 +138,7 @@ "donate-here-to-help-us": "Adományozzon és segítsen nekünk", "sign-up-to-receive-our-updates": "Regisztráljon a hírleveleinkre, hogy ne maradjon le semmiről", "enter-your-email-address": "Adja meg az e-mail-címét", - "get-simplex": "A SimpleX számítógép-alkalmazásának letöltése", + "get-simplex": "A SimpleX számítógépes alkalmazásának letöltése", "why-simplex-is-unique": "A SimpleX mitől egyedülálló", "learn-more": "Tudjon meg többet", "more-info": "További információ", diff --git a/website/langs/uk.json b/website/langs/uk.json index 71b3254e63..1c1780edb3 100644 --- a/website/langs/uk.json +++ b/website/langs/uk.json @@ -14,19 +14,19 @@ "simplex-network-overlay-card-1-li-6": "P2P-мережі можуть бути вразливими до атаки DRDoS, коли клієнти можуть ребродкастити та збільшувати трафік, що призводить до відмови в обслуговуванні на рівні всієї мережі. Клієнти SimpleX лише пересилають трафік від відомого підключення і не можуть бути використані зловмисником для збільшення трафіку в усій мережі.", "privacy-matters-overlay-card-1-p-2": "Інтернет-роздрібники знають, що люди з низьким доходом частіше роблять термінові покупки, тому вони можуть встановлювати вищі ціни або скасовувати знижки.", "privacy-matters-overlay-card-1-p-3": "Деякі фінансові та страхові компанії використовують соціальні графи для визначення ставок та страхових премій. Це часто змушує людей з низькими доходами платити більше — це відомо як 'поширений преміум'.", - "privacy-matters-overlay-card-1-p-4": "Платформа SimpleX захищає конфіденційність ваших з'єднань краще, ніж будь-яка альтернатива, повністю запобігаючи доступу вашого соціального графа будь-яким компаніям чи організаціям. Навіть коли люди використовують сервери, надані SimpleX Chat, ми не знаємо кількість користувачів чи їх зв'язки.", + "privacy-matters-overlay-card-1-p-4": "Мережа SimpleX краще захищає конфіденційність ваших з'єднань, ніж будь-яка інша альтернатива, повністю запобігаючи тому, щоб ваш соціальний граф став доступним для будь-яких компаній чи організацій. Навіть коли люди використовують сервери, попередньо налаштовані в додатках SimpleX Chat, оператори серверів не знають кількості користувачів або їхніх з'єднань.", "privacy-matters-overlay-card-2-p-1": "Не так давно ми спостерігали, як великі вибори маніпулювалися поважною консалтинговою компанією, яка використовувала наші соціальні графи для спотворення нашого уявлення про реальний світ та маніпулювання нашими голосами.", - "privacy-matters-overlay-card-2-p-2": "Щоб бути об'єктивним та приймати незалежні рішення, вам потрібно контролювати ваш інформаційний простір. Це можливо лише за умови використання приватної платформи для спілкування, яка не має доступу до вашого соціального графа.", - "privacy-matters-overlay-card-2-p-3": "SimpleX - це перша платформа, яка не має жодних ідентифікаторів користувачів за своїм дизайном, таким чином, захищаючи ваш графік з'єднань краще, ніж будь-яка відома альтернатива.", + "privacy-matters-overlay-card-2-p-2": "Щоб бути об'єктивним та приймати незалежні рішення, вам потрібно контролювати ваш інформаційний простір. Це можливо лише за умови використання приватної мережі для спілкування, яка не має доступу до вашого соціального графа.", + "privacy-matters-overlay-card-2-p-3": "SimpleX - це перша мережа, яка не має жодних ідентифікаторів користувачів за своїм дизайном, таким чином, захищаючи ваш графік з'єднань краще, ніж будь-яка відома альтернатива.", "privacy-matters-overlay-card-3-p-1": "Кожен повинен турбуватися про конфіденційність та безпеку свого спілкування — безпечні розмови можуть поставити вас під загрозу, навіть якщо у вас немає чого приховувати.", "privacy-matters-overlay-card-3-p-2": "Однією з найшокуючих історій є досвід Мохамеду Ульд Слахі, описаний в його мемуарах і показаний у фільмі \"Мавританець\". Його посадили в табір Гуантанамо без суду і мукили там 15 років після телефонного дзвінка йому родичеві в Афганістані, під підозрою в причетності до атак 11 вересня, навіть не дивлячись на те, що він проживав у Німеччині протягом попередніх 10 років.", "privacy-matters-overlay-card-3-p-4": "Недостатньо використовувати зашифрований від кінця до кінця месенджер; ми всі повинні використовувати месенджери, які захищають конфіденційність наших особистих мереж — з ким ми з'єднані.", - "simplex-unique-overlay-card-1-p-1": "На відміну від інших платформ обміну повідомленнями, у SimpleX не присвоюються ідентифікатори користувачів. Він не покладається на номери телефонів, адреси на основі домену (наприклад, електронна пошта або XMPP), імена користувачів, публічні ключі або навіть випадкові номери для ідентифікації своїх користувачів — ми не знаємо, скільки людей використовує наші сервери SimpleX.", + "simplex-unique-overlay-card-1-p-1": "На відміну від інших мереж для обміну повідомленнями, у SimpleX немає ідентифікаторів, призначених користувачам. Він не покладається на номери телефонів, адреси на основі доменів (як електронна пошта або XMPP), імена користувачів, публічні ключі або навіть випадкові числа для ідентифікації своїх користувачів — оператори серверів SimpleX не знають, скільки людей використовують їхні сервери.", "simplex-unique-overlay-card-3-p-3": "На відміну від федеративних мереж серверів (електронна пошта, XMPP або Matrix), сервери SimpleX не зберігають облікові записи користувачів, вони лише ретранслюють повідомлення, захищаючи приватність обох сторін.", "simplex-unique-card-3-p-2": "Зашифровані повідомлення від кінця до кінця тимчасово зберігаються на ретрансляційних серверах SimpleX до їх отримання, після чого вони назавжди видаляються.", - "simplex-unique-card-4-p-1": "Мережа SimpleX є повністю децентралізованою та незалежною від будь-якої криптовалюти чи іншої платформи, крім Інтернету.", + "simplex-unique-card-4-p-1": "Мережа SimpleX повністю децентралізована та незалежна від будь-якої криптовалюти або будь-якої іншої мережі, окрім Інтернету.", "simplex-network-overlay-card-1-li-3": "P2P не вирішує проблему атаки MITM, і більшість існуючих реалізацій не використовують поза каналом повідомлень для початкового обміну ключами. SimpleX використовує поза каналом повідомлень або, у деяких випадках, передбачені і безпечні з'єднання для початкового обміну ключами.", - "simplex-network-overlay-card-1-li-4": "Реалізації P2P можуть бути блоковані деякими інтернет-провайдерами (наприклад, BitTorrent). SimpleX є транспортно-агностичним - він може працювати через стандартні веб-протоколи, наприклад, WebSockets.", + "simplex-network-overlay-card-1-li-4": "Реалізації P2P можуть бути заблоковані деякими інтернет-провайдерами (наприклад, BitTorrent). SimpleX є незалежним від транспорту — він може працювати через стандартні веб-протоколи, наприклад, WebSockets.", "simplex-unique-card-4-p-2": "Ви можете використовувати SimpleX з власними серверами або з серверами, які ми надаємо — і все одно підключатися до будь-якого користувача.", "contact-hero-p-1": "Публічні ключі та адреса черги повідомлень в цьому посиланні НЕ відправляються по мережі під час перегляду цієї сторінки — вони містяться в хеш-фрагменті URL-посилання.", "scan-qr-code-from-mobile-app": "Сканувати QR-код з мобільного додатка", @@ -46,7 +46,7 @@ "guide-dropdown-8": "Налаштування додатка", "guide-dropdown-9": "Створення підключень", "guide": "Посібник", - "docs-dropdown-1": "Платформа SimpleX", + "docs-dropdown-1": "SimpleX мережа", "docs-dropdown-2": "Доступ до файлів Android", "docs-dropdown-3": "Доступ до бази даних чату", "docs-dropdown-4": "Хостинг сервера SMP", @@ -71,7 +71,7 @@ "simplex-explained-tab-1-p-1": "Ви можете створювати контакти та групи і вести двосторонні розмови, як у будь-якому іншому месенджері.", "simplex-explained-tab-1-p-2": "Як це може працювати з однобічними чергами та без ідентифікаторів профілю користувача?", "simplex-explained-tab-2-p-1": "Для кожного з'єднання ви використовуєте дві окремі черги обміну повідомленнями для відправки та отримання повідомлень через різні сервери.", - "simplex-explained-tab-2-p-2": "Сервери передають повідомлення тільки в одному напрямку, не маючи повної картини розмови або підключень користувача.", + "simplex-explained-tab-2-p-2": "Сервери передають повідомлення тільки в одному напрямку, не маючи повної картини розмов або підключень користувача.", "simplex-explained-tab-3-p-1": "Сервери мають окремі анонімні облікові дані для кожної черги і не знають, які користувачі до них відносяться.", "simplex-explained-tab-3-p-2": "Користувачі можуть додатково підвищити конфіденційність метаданих, використовуючи Tor для доступу до серверів, що запобігає кореляції за IP-адресою.", "chat-bot-example": "Приклад чат-бота", @@ -116,7 +116,7 @@ "simplex-private-card-4-point-2": "Для використання SimpleX через Tor, будь ласка, встановіть додаток Orbot та активуйте SOCKS5-проксі (або VPN на iOS).", "simplex-private-card-5-point-1": "SimpleX використовує наповнення вмісту для кожного шару шифрування для ускладнення атак за розміром повідомлень.", "simplex-private-card-5-point-2": "Це забезпечує, що повідомлення різних розмірів виглядають однаково для серверів та спостерігачів мережі.", - "simplex-private-card-6-point-1": "Багато комунікаційних платформ вразливі до MITM-атак серверів чи постачальників мережі.", + "simplex-private-card-6-point-1": "Багато комунікаційних мереж вразливі до MITM-атак серверів чи постачальників мережі.", "simplex-private-card-6-point-2": "Щоб запобігти цьому, програми SimpleX передають одноразові ключі позаканально, коли ви ділитесь адресою як посиланням або QR-кодом.", "simplex-private-card-7-point-2": "Якщо будь-яке повідомлення додається, вилучається чи змінюється, отримувач буде проінформований.", "simplex-private-card-8-point-1": "Сервери SimpleX виступають як вузли низької затримки для змішування — вхідні та вихідні повідомлення мають різний порядок.", @@ -140,7 +140,7 @@ "simplex-unique-4-title": "Ви власник мережі SimpleX", "simplex-unique-4-overlay-1-title": "Повністю децентралізована — користувачі володіють мережею SimpleX", "hero-overlay-card-1-p-1": "Багато користувачів запитували: якщо у SimpleX немає ідентифікаторів користувачів, як він може знати, куди відправити повідомлення?", - "hero-overlay-card-1-p-2": "Для доставки повідомлень, замість ідентифікаторів користувачів, які використовують усі інші платформи, SimpleX використовує тимчасові анонімні парні ідентифікатори черг повідомлень, окремі для кожного вашого підключення — тут немає довгострокових ідентифікаторів.", + "hero-overlay-card-1-p-2": "Для доставки повідомлень, замість ідентифікаторів користувачів, які використовують усі інші мережі, SimpleX використовує тимчасові анонімні парні ідентифікатори черг повідомлень, окремі для кожного вашого підключення — тут немає довгострокових ідентифікаторів.", "hero-overlay-card-1-p-3": "Ви визначаєте, який(кі) сервер(и) використовувати для отримання повідомлень, ваші контакти — сервери, які ви використовуєте для відправки повідомлень їм. Кожна розмова, ймовірно, використовує два різних сервери.", "hero-overlay-card-1-p-4": "Цей дизайн запобігає витоку метаданих будь-яких користувачів на рівні додатка. Для подальшого покращення конфіденційності та захисту вашої IP-адреси ви можете підключитися до серверів обміну повідомленнями через Tor.", "hero-overlay-card-1-p-6": "Докладніше читайте у білетному запису SimpleX.", @@ -153,18 +153,18 @@ "privacy-matters-overlay-card-1-p-1": "Багато великих компаній використовують інформацію про те, з ким ви з'єднані, щоб оцінити ваш дохід, продавати вам продукти, які вам дійсно не потрібні, і визначати ціни.", "privacy-matters-overlay-card-3-p-3": "Звичайних людей арештовують за те, що вони публікують онлайн, навіть через свої 'анонімні' облікові записи, навіть у демократичних країнах.", "simplex-unique-overlay-card-1-p-2": "Для доставки повідомлень SimpleX використовує парні анонімні адреси однобічних черг повідомлень, окремо для отриманих та відправлених повідомлень, зазвичай через різні сервери.", - "simplex-unique-overlay-card-1-p-3": "Цей дизайн захищає конфіденційність осіб, з якими ви спілкуєтеся, приховуючи це від серверів платформи SimpleX та будь-яких спостерігачів. Щоб сховати свою IP-адресу від серверів, ви можете підключитися до серверів SimpleX через Tor.", - "simplex-unique-overlay-card-2-p-1": "Оскільки у вас немає ідентифікатора на платформі SimpleX, ніхто не може з вами зв'язатися, якщо ви не поділитеся одноразовою або тимчасовою адресою користувача, у вигляді QR-коду або посилання.", + "simplex-unique-overlay-card-1-p-3": "Цей дизайн захищає конфіденційність того, з ким ви спілкуєтесь, приховуючи це від серверів мережі SimpleX та від будь-яких спостерігачів. Щоб приховати вашу IP-адресу від серверів, ви можете підключатися до серверів SimpleX через Tor.", + "simplex-unique-overlay-card-2-p-1": "Оскільки у вас немає ідентифікатора в мережі SimpleX, ніхто не може зв'язатися з вами, якщо ви не поділитеся одноразовою або тимчасовою адресою користувача, як QR-кодом або посиланням.", "simplex-unique-overlay-card-2-p-2": "Навіть з необов'язковою адресою користувача, яка може бути використана для відправки спамових запитань на зв'язок, ви можете змінити або повністю видалити її, не втрачаючи жодного з ваших з'єднань.", "simplex-unique-overlay-card-3-p-1": "SimpleX Chat зберігає всі дані користувачів лише на пристроях клієнтів за допомогою переносного зашифрованого формату бази даних, який можна експортувати і передавати на будь-який підтримуваний пристрій.", "simplex-unique-overlay-card-3-p-2": "Зашифровані повідомлення від кінця до кінця тимчасово зберігаються на ретрансляційних серверах SimpleX до їх отримання, після чого вони назавжди видаляються.", "simplex-unique-overlay-card-3-p-4": "Між надісланим і отриманим серверним трафіком немає спільних ідентифікаторів чи шифрованого тексту — якщо хтось його спостерігає, він не може легко визначити, хто спілкується з ким, навіть якщо TLS скомпрометовано.", - "simplex-unique-overlay-card-4-p-1": "Ви можете використовувати SimpleX з власними серверами і все одно спілкуватися з людьми, які використовують надані нам сервери заздалегідь налаштовані.", - "simplex-unique-overlay-card-4-p-2": "Платформа SimpleX використовує відкритий протокол та надає SDK для створення чат-ботів, що дозволяє впроваджувати сервіси, з якими користувачі можуть взаємодіяти через додатки SimpleX Chat — ми дійсно чекаємо, які сервіси SimpleX ви зможете побудувати.", - "simplex-unique-overlay-card-4-p-3": "Якщо ви розглядаєте можливість розробки для платформи SimpleX, наприклад, чат-бота для користувачів додатку SimpleX або інтеграції бібліотеки SimpleX Chat у свої мобільні додатки, будь ласка, зв'яжіться з нами для отримання порад та підтримки.", - "simplex-unique-card-1-p-1": "SimpleX захищає конфіденційність вашого профілю, контактів та метаданих, приховуючи їх від серверів платформи SimpleX та будь-яких спостерігачів.", - "simplex-unique-card-1-p-2": "На відміну від будь-якої іншої існуючої платформи обміну повідомленнями, SimpleX не має ідентифікаторів, призначених користувачам — навіть випадкових чисел.", - "simplex-unique-card-2-p-1": "Оскільки у вас немає ідентифікатора або фіксованої адреси на платформі SimpleX, ніхто не може з вами зв'язатися, якщо ви не поділитесь одноразовою або тимчасовою адресою користувача, як QR-код або посиланням.", + "simplex-unique-overlay-card-4-p-1": "Ви можете використовувати SimpleX зі своїми власними серверами і при цьому спілкуватися з людьми, які використовують сервери, попередньо налаштовані в додатках.", + "simplex-unique-overlay-card-4-p-2": "Мережа SimpleX використовує відкритий протокол і надає SDK для створення чат-ботів, що дозволяє реалізувати сервіси, з якими користувачі можуть взаємодіяти через додатки SimpleX Chat — ми справді чекаємо, щоб побачити, які сервіси SimpleX ви створите.", + "simplex-unique-overlay-card-4-p-3": "Якщо ви плануєте розробляти для мережі SimpleX, наприклад, чат-бота для користувачів додатка SimpleX або інтеграцію бібліотеки SimpleX Chat у ваші мобільні додатки, будь ласка, зв'яжіться з нами для отримання порад та підтримки.", + "simplex-unique-card-1-p-1": "SimpleX захищає конфіденційність вашого профілю, контактів та метаданих, приховуючи їх від серверів мережі SimpleX та будь-яких спостерігачів.", + "simplex-unique-card-1-p-2": "На відміну від будь-якої іншої існуючої мережі для обміну повідомленнями, у SimpleX немає ідентифікаторів, призначених користувачам — не навіть випадкових чисел.", + "simplex-unique-card-2-p-1": "Оскільки у вас немає ідентифікатора або фіксованої адреси в мережі SimpleX, ніхто не може зв'язатися з вами, якщо ви не поділитеся одноразовою або тимчасовою адресою користувача, як QR-кодом або посиланням.", "simplex-unique-card-3-p-1": "SimpleX зберігає всі дані користувачів на пристроях клієнтів у переносному зашифрованому форматі бази даних — його можна передавати на інший пристрій.", "join": "Приєднатися", "we-invite-you-to-join-the-conversation": "Ми запрошуємо вас приєднатися до розмови", @@ -191,7 +191,7 @@ "installing-simplex-chat-to-terminal": "Встановлення SimpleX Chat для терміналу", "use-this-command": "Використовуйте цю команду:", "see-simplex-chat": "Дивіться SimpleX Chat", - "the-instructions--source-code": "інструкції з того, як його завантажити чи скомпілювати з вихідного коду.", + "the-instructions--source-code": "для інструкції з того, як його завантажити чи скомпілювати з вихідного коду.", "if-you-already-installed-simplex-chat-for-the-terminal": "Якщо ви вже встановили SimpleX Chat для терміналу", "if-you-already-installed": "Якщо ви вже встановили", "simplex-chat-for-the-terminal": "SimpleX Chat для терміналу", @@ -227,7 +227,7 @@ "no-decentralized": "Ні - децентралізовано", "comparison-section-list-point-4": "Якщо сервери оператора порушені. Перевірте безпековий код в Signal та деяких інших додатках для зменшення ризику", "comparison-section-list-point-5": "Не захищає конфіденційність метаданих користувачів", - "comparison-section-list-point-6": "Хоча P2P є розподіленими, вони не є федеративними - вони працюють як єдина мережа", + "comparison-section-list-point-6": "Хоча P2P є розподіленими, вони не є федеративними — вони працюють як одна мережа", "guide-dropdown-1": "Швидкий старт", "guide-dropdown-2": "Відправлення повідомлень", "guide-dropdown-3": "Таємні групи", diff --git a/website/langs/zh_Hans.json b/website/langs/zh_Hans.json index aa51a7cff5..c0b843ca9e 100644 --- a/website/langs/zh_Hans.json +++ b/website/langs/zh_Hans.json @@ -49,7 +49,7 @@ "simplex-explained": "SimpleX 简述", "simplex-explained-tab-3-text": "3. 服务器能看到什么", "hero-header": "重新定义隐私", - "simplex-explained-tab-1-p-2": "它是如何在单向消息队列与没有用户识别符的情况下工作的?", + "simplex-explained-tab-1-p-2": "它是如何利用单向消息队列并不利用用户识别符工作的?", "simplex-explained-tab-2-p-2": "服务器只会单向传输消息,而无法掌握用户的对话或连接的全貌。", "simplex-explained-tab-3-p-1": "服务器对每个队列都有单独的匿名凭证,并且不知道这些凭证属于哪些用户。", "donate": "捐赠", diff --git a/website/langs/zh_Hant.json b/website/langs/zh_Hant.json index d95c6259c7..324360cb19 100644 --- a/website/langs/zh_Hant.json +++ b/website/langs/zh_Hant.json @@ -1,13 +1,13 @@ { - "home": "家", + "home": "开始", "developers": "開發人員", "reference": "參考", - "blog": "博客", + "blog": "部落格", "features": "特徵", "why-simplex": "為什麼選擇SimpleX", "simplex-privacy": "SimpleX 隱私", "simplex-network": "SimpleX 網路", - "simplex-explained": "Simplex 解釋", + "simplex-explained": "SimpleX 解釋", "simplex-explained-tab-1-text": "1. 用戶體驗", "simplex-explained-tab-2-text": "2. 它是如何工作的", "simplex-explained-tab-3-text": "3. 伺服器可以看到什麼", @@ -16,15 +16,191 @@ "hero-subheader": "第一個沒有 User ID 的 Messenger", "simplex-explained-tab-3-p-2": "用戶可以通過使用 Tor 訪問伺服器來進一步提高元數據隱私,防止按 IP 位址進行序列化。", "smp-protocol": "SMP 協定", - "simplex-explained-tab-2-p-2": "伺服器僅以一種方式傳遞消息,而無法全面瞭解使用者的對話或連接。", + "simplex-explained-tab-2-p-2": "伺服器僅單向傳遞消息,無法全面瞭解使用者的對話記錄或連接。", "simplex-explained-tab-2-p-1": "對於每個連接,您可以使用兩個單獨的消息佇列通過不同的伺服器發送和接收消息。", "chat-protocol": "聊天協定", - "copyright-label": "© 2020-2024 單工 |開源專案", - "donate": "捐", + "copyright-label": "© 2020-2025 SimpleX |開源專案", + "donate": "捐助", "simplex-explained-tab-1-p-1": "您可以創建聯繫人和群組,並進行雙向對話,就像在任何其他 Messenger 中一樣。", "simplex-explained-tab-1-p-2": "它如何在沒有使用者配置檔標識符的情況下使用單向佇列?", "simplex-explained-tab-3-p-1": "伺服器對每個佇列都有單獨的匿名憑證,並且不知道它們屬於哪些使用者。", "chat-bot-example": "聊天機器人示例", "simplex-chat-protocol": "SimpleX Chat 協定", - "terms-and-privacy-policy": "隱私策略" + "terms-and-privacy-policy": "隱私策略", + "hero-overlay-card-3-p-3": "Trail of Bits 在 2024 年 7 月檢閱了 SimpleX 網路通訊協定的加密設計。閱讀更多。", + "hero-overlay-3-textlink": "安全評估", + "hero-overlay-card-3-p-2": "Trail of Bits 在 2022 年 11 月檢閱了 SimpleX 網路加密和網路元件。閱讀更多。", + "hero-overlay-1-textlink": "用戶ID為何有害私隱?", + "hero-overlay-2-textlink": "SimpleX如何工作?", + "simplex-private-card-5-point-2": "它讓不同長度的訊息在伺服器和網路觀察者看來相同。", + "simplex-private-card-10-point-1": "對用戶與群組成員,SimpleX使用臨時、匿名的地址對與憑證對。", + "privacy-matters-1-title": "廣告投放與價格歧視", + "privacy-matters-1-overlay-1-linkText": "私隱為您省錢", + "privacy-matters-2-title": "操縱選舉", + "privacy-matters-2-overlay-1-title": "私隱給您權力", + "privacy-matters-2-overlay-1-linkText": "私隱給您權力", + "privacy-matters-3-overlay-1-title": "私隱保護您的自由", + "privacy-matters-3-overlay-1-linkText": "私隱保護您的自由", + "simplex-unique-4-title": "您擁有SimpleX網路", + "simplex-unique-4-overlay-1-title": "完全去中心化—用戶擁有 SimpleX 網路", + "hero-overlay-card-1-p-4": "此設計可防止在應用程式層級洩漏任何使用者' 元資料。為了進一步改善隱私並保護您的 IP 位址,您可以透過 Tor 連接到訊息伺服器。", + "hero-overlay-card-1-p-5": "只有用戶端會儲存使用者設定檔、聯絡人和群組;訊息傳送採用雙層端對端加密。", + "hero-overlay-card-2-p-2": "然後他們可以將這些資訊與現有的公開社交網路進行關聯,並確定一些真實身份。", + "simplex-unique-card-1-p-1": "SimpleX 保護您的個人資料、聯絡人和元資料的隱私,使其對 SimpleX 網路伺服器和任何觀察者隱藏。", + "scan-qr-code-from-mobile-app": "用手機應用程式掃描二維碼", + "scan-the-qr-code-with-the-simplex-chat-app": "使用SimpleX Chat應用程式掃描二維碼", + "protocol-3-text": "點對點協議", + "hero-p-1": "其他聊天應用,如Signal、Matrix、Session、Briar、Jami、Cwtch等,使用用戶ID;SimpleX不這樣做。它甚至不使用隨機數。這種做法極大地保護用戶私隱。", + "hero-2-header-desc": "此視訊教您以一次性QR碼、親身或以視訊連結的方式與聯繫人連線。您也可以通過分享邀請連結與人連線。", + "feature-2-title": "端到端加密的圖像、視訊、檔案", + "simplex-private-card-9-point-2": "與傳統的訊息代理相比,它可減小攻擊媒介及可見的詮釋資料量。", + "simplex-private-10-title": "臨時匿名標識符對", + "simplex-private-card-1-point-1": "雙棘輪協定—帶有前向保密、入侵恢復特質的不留記錄即時通訊協定。", + "privacy-matters-3-title": "因無辜關聯而被起訴", + "signing-key-fingerprint": "簽名密鑰指紋(SHA-256)", + "hero-overlay-card-1-p-3": "您決定用哪個/哪些伺服器接收訊息。您的聯絡人用此伺服器向您傳送訊息。往往每個對話都使用兩台不同的伺服器。", + "docs-dropdown-2": "訪問Android檔案", + "simplex-unique-card-2-p-1": "由於您在 SimpleX 網路上沒有任何標識符或固定地址,因此除非您分享一次性或臨時地址 (如 二維碼或連結),任何人都無法與您聯絡。", + "see-here": "見此", + "privacy-matters-overlay-card-3-p-4": "僅使用端對端加密的通訊工具是不夠的,我們都應使用能保護個人網路隱私的通訊工具。", + "hero-overlay-card-1-p-1": "很多用戶問: 如果 SimpleX 沒有用戶標識符,它何以知道向何處傳送訊息?", + "simplex-unique-overlay-card-4-p-2": "SimpleX網路使用開源協議並提供開發包以編寫聊天機器人,允許使用者透過SimpleX Chat應用程式與服務互動—我們非常期待看到您的SimpleX服務之作!", + "hero-overlay-card-1-p-2": "為傳送訊息,SimpleX 使用消息佇列中的臨時匿名標識符對,每連線各異,而不像其他網路一樣使用用戶ID— SimpleX 沒有長期標識符。", + "privacy-matters-overlay-card-3-p-2": "最令人震驚的故事之一是 Mohamedou Ould Salahi 的經歷,描述於他的回憶錄中,並在《The Mauritanian movie》中呈現。他在未經審訊的情況下被送進關塔納摩集中營,並在那裏遭受了長達 15 年的酷刑,原因是他被懷疑參與了 9/11 襲擊事件,而此前 10 年他一直住在德國。", + "simplex-private-2-title": "伺服器附加加密層", + "hero-overlay-card-2-p-1": "如使用者擁有持久性身份,即使只是一個隨機數,例如 Session ID,也會有服務提供者或攻擊者可以觀察使用者如何連線以及傳送了多少條訊息的風險。", + "simplex-unique-overlay-card-4-p-3": "如您正考慮針對 SimpleX 網路進行開發,例如針對 SimpleX 使用者的聊天機器人,或將 SimpleX 聊天函式庫整合至您的手機應用程式,請與我們聯絡以獲取支持與建議。", + "simplex-network-overlay-card-1-li-1": "點對點網路依賴 DHT 的某些變體來路由訊息。DHT 設計必須平衡傳送保證和延遲。與點對點相比,SimpleX 具有更好的傳送保證和更低的延遲,因為訊息可以使用收件者選擇的伺服器,經由多個伺服器並行冗餘地傳送。在點對點網路中,訊息依次經由O(log N)個由演算法選擇的節點。", + "simplex-private-card-3-point-3": "連線恢復被停用,以防止會話攻擊。", + "simplex-private-card-2-point-1": "傳送至收件者的伺服器加密附加層,以防止 TLS 遭到攻擊時,接收和傳送的伺服器流量之間的關聯。", + "simplex-private-card-5-point-1": "SimpleX 為每個加密層使用內容填充,以挫敗通過監控訊息長度的攻擊。", + "comparison-section-list-point-4a": "SimpleX中繼不可能威脅端到端加密。由其他方式驗證安全碼以杜絕攻擊之可能", + "docs-dropdown-6": "WebRTC伺服器", + "feature-3-title": "端到端加密的、去中心化的群組—存在只有用戶自己知道", + "feature-7-title": "可攜、加密存儲—將設定檔移至另一設備", + "back-to-top": "回到頂部", + "click-to-see": "點擊查看", + "menu": "功能表", + "on-this-page": "此頁中", + "docs-dropdown-14": "在商業中使用SimpleX", + "glossary": "詞彙表", + "simplex-chat-repo": "SimpleX Chat儲存庫", + "newer-version-of-eng-msg": "本頁面有更新的英文版本。", + "f-droid-page-simplex-chat-repo-section-text": "掃描二維碼或使用此URL以添加SimpleX儲存庫至您的F-Droid客戶端:", + "jobs": "加入團隊", + "please-use-link-in-mobile-app": "請使用手機應用程式中的連結", + "stable-versions-built-by-f-droid-org": "由F-Droid.org編譯的穩定版", + "please-enable-javascript": "啟用JavaScript以查看二維碼。", + "docs-dropdown-8": "SimpleX通訊錄服務", + "hero-2-header": "創建私密連線", + "hero-overlay-1-title": "SimpleX如何工作?", + "hero-overlay-2-title": "用戶ID為何有害私隱?", + "hero-overlay-3-title": "安全評估", + "feature-1-title": "支持Markdown與編輯的、端到端加密的訊息", + "feature-4-title": "端到端加密的語音訊息", + "feature-5-title": "自刪除訊息", + "feature-6-title": "端到端加密的語音、視訊通話", + "simplex-network-overlay-1-title": "與點對點訊息傳輸協定的比較", + "simplex-private-1-title": "雙層端到端加密", + "simplex-private-3-title": "經安全鑑權的TLS 傳送", + "simplex-private-4-title": "可選的經由Tor訪問", + "simplex-private-5-title": "多層內容填充", + "feature-8-title": "隱身模式—SimpleX獨有", + "simplex-private-6-title": "頻帶外密鑰交換", + "simplex-private-7-title": "訊息完整性驗證", + "simplex-private-8-title": "訊息混雜以降低關聯性", + "simplex-private-card-1-point-2": "如TLS安全受威脅,每個隊列中的NaCL cryptobox可防止關聯訊息隊列間的通訊。", + "simplex-private-9-title": "單向訊息隊列", + "simplex-private-card-10-point-2": "SimpleX使不用用戶設定檔傳輸訊息成為可能,與其他軟體相比提供更強的詮釋資料私隱性。", + "privacy-matters-1-overlay-1-title": "私隱為您省錢", + "simplex-unique-1-title": "您擁有完整的私隱權", + "simplex-unique-1-overlay-1-title": "您的身份、個人資料、聯絡人與詮釋資料完全隱密", + "simplex-unique-2-title": "SimpleX保護您免受垃圾訊息、濫用之害", + "simplex-private-card-4-point-1": "為保護您的 IP 位址,您可透過 Tor 或其他傳輸覆蓋網路訪問伺服器。", + "simplex-unique-2-overlay-1-title": "防止垃圾郵件和濫用的最佳保護", + "simplex-unique-3-title": "您掌控您的數據", + "simplex-unique-3-overlay-1-title": "您的數據的擁有權、控制與安全", + "hero-overlay-card-2-p-4": "SimpleX 通過在設計中不使用任何使用者 ID以防止這些攻擊。而且,如果您使用匿名聊天模式,每個聯絡人都會有不同的顯示名稱,避免他們之間有任何共用資料。", + "hero-overlay-card-2-p-3": "即使是使用 Tor v3 服務的最隱私的應用程式,如果您透過相同的設定檔與兩個不同的人交談,他們也可以證明他們連線的是同一個人。", + "hero-overlay-card-1-p-6": "請參閱 SimpleX 白皮書以閱讀更多資訊。", + "hero-overlay-card-3-p-1": "Trail of Bits 是一家領先的安全和技術顧問公司,客戶包括大型科技公司、政府機構和主要的區塊鏈專案。", + "simplex-network-overlay-card-1-p-1": "點對點通訊協定和應用程式有多種問題,使得它們不如 SimpleX 可靠、分析起來更複雜,而且易受幾種類型的攻擊。", + "simplex-network-overlay-card-1-li-2": "SimpleX 的設計與大多數點對點網路不同,沒有任何類型的全局使用者標識符,即使是臨時標識符。SimpleX 只使用臨時的標識符對,提供更好的匿名性和元資料保護。", + "privacy-matters-overlay-card-1-p-1": "許多大公司利用與您有聯繫的人的資訊來估計您的收入,向您銷售您並不真正需要的產品,以及定價。", + "privacy-matters-overlay-card-1-p-2": "線上零售商知道收入較低的人更有可能進行緊急購物,因此他們可能收取較高的價格或取消折扣。", + "enter-your-email-address": "輸入您的電郵地址", + "get-simplex": "下載SimpleX 桌面版應用程式", + "join-us-on-GitHub": "在GitHub上加入我們", + "donate-here-to-help-us": "於此捐助以幫助我們", + "sign-up-to-receive-our-updates": "登記以收取更新", + "join": "加入", + "simplex-unique-card-3-p-1": "SimpleX 以 可攜式加密數據庫格式 儲存用戶端裝置上的所有使用者資料;這些資料可傳輸至其他設備。", + "simplex-unique-card-3-p-2": "端對端加密的訊息暫時保留在 SimpleX 中繼伺服器上,收到訊息後會被永久刪除。", + "simplex-unique-card-4-p-2": "您可以使用您自己的伺服器運行SimpleX,或使用我們提供的伺服器並仍連線到任何使用者。", + "simplex-unique-overlay-card-3-p-4": "傳送與接收的伺服器流量之間沒有共同的標識符或密文—如果有人觀察,即使 TLS 遭到破壞,也無法輕易確定誰與誰通訊。", + "join-the-REDDIT-community": "加入Reddit社群", + "privacy-matters-overlay-card-3-p-1": "人人都應關心自己通訊的隱私和安全;無害的對話可能會讓您陷入危險,即使您沒有什麼要隱瞞的。", + "privacy-matters-overlay-card-3-p-3": "普通人會因為他們在網路上分享的內容而被捕,即使是透過他們的「匿名」帳號,即使是在民主國家。", + "privacy-matters-overlay-card-2-p-1": "不久前我們觀察到大選被一家聲譽良好的顧問公司操縱,該公司利用我們的社交圖來扭曲我們對真實世界的看法,並操縱我們的選票。", + "comparison-section-list-point-2": "基於DNS的位址", + "comparison-section-list-point-1": "通常基於電話號碼,有時用戶名", + "simplex-private-card-6-point-1": "許多通訊網路容易受到伺服器或網路供應商的中間人攻擊。", + "privacy-matters-overlay-card-1-p-4": "SimpleX 網路比任何其他方式都能更好地保護您的連線隱私,完全防止您的社交圖表被任何公司或組織獲得。即使使用 SimpleX Chat 應用程式中預設的伺服器,伺服器操作員也不會知道使用者的數量或他們的連線。", + "privacy-matters-overlay-card-2-p-2": "要做到客觀和獨立決策,您需要控制自己的資訊空間。只有當使用無法存取您的社交圖表的私人通訊網路時,您才可能做到這一點。", + "simplex-unique-overlay-card-1-p-1": "與其他通訊網路不同,SimpleX 不為使用者指定標識符。它不依賴電話號碼、網域地址 (如電子郵件或 XMPP)、用戶名、公鑰或甚至隨機數來識別使用者;SimpleX 伺服器操作員不知道有多少人使用他們的伺服器。", + "privacy-matters-overlay-card-2-p-3": "SimpleX 是第一個在設計上不使用任何使用者標識符的網路,因此比任何已知的其他方案都能更好地保護您的社交圖。", + "simplex-unique-overlay-card-4-p-1": "您可以使用您自己的伺服器運行SimpleX,並仍與使用應用程式中預設伺服器的人進行通訊。", + "simplex-unique-card-1-p-2": "與任何其他現有的訊息網路不同,SimpleX 不指定使用者的標識符—甚至沒有隨機數。", + "why-simplex-is-unique": "SimpleX為何與眾不同", + "learn-more": "查看更多", + "more-info": "更多訊息", + "hide-info": "隱藏訊息", + "contact-hero-header": "您收到了SimpleX聯絡地址", + "simplex-unique-card-4-p-1": "SimpleX 網路是完全去中心化的,獨立於任何加密貨幣或任何其他網路(除Internet)。", + "invitation-hero-header": "您收到了SimpleX一次性連結", + "contact-hero-subheader": "使用手提電話或平板電腦中的SimpleX應用程式掃描二維碼。", + "connect-in-app": "在應用中連結", + "contact-hero-p-2": "尚未下載SimpleX?", + "contact-hero-p-1": "當您檢視此頁面時,此連結中的公鑰和訊息佇列位址不透過網路傳送;它們包含在連結 URL 的雜湊片段中。", + "contact-hero-p-3": "使用以下連結下載應用程式。", + "to-make-a-connection": "建立連結:", + "install-simplex-app": "安裝 SimpleX 應用程式", + "use-this-command": "使用此命令:", + "scan-the-qr-code-with-the-simplex-chat-app-description": "當您檢視此頁面時,此連結中的公鑰和訊息佇列位址不會透過網路傳送 —它們包含在連結 URL 的雜湊片段中。", + "installing-simplex-chat-to-terminal": "安裝SimpleX至終端", + "open-simplex-app": "開啟SimpleX應用程式", + "if-you-already-installed": "如已安裝", + "simplex-chat-for-the-terminal": "用於終端的 SimpleX Chat", + "if-you-already-installed-simplex-chat-for-the-terminal": "如您已經安裝了用於終端的 SimpleX Chat", + "tap-to-close": "輕觸以關閉", + "simplex-network-1-desc": "所有訊息都經由伺服器傳送,既能提供更好的元資料隱私和可靠的異步訊息傳送,又能避免許多", + "simplex-network-section-desc": "SinpleX Chat 結合點對點和互聯網路的優點,提供最佳的隱私性。", + "copy-the-command-below-text": "複製下面的命令並用於聊天:", + "privacy-matters-section-header": "隱私為何 重要", + "privacy-matters-section-label": "確保您的通訊應用程式無法竊取您的資料!", + "simplex-network-3-header": "SimpleX網路", + "simplex-network-1-overlay-linktext": "點對點網路的弊病", + "simplex-network-2-desc": "SimpleX 中繼伺服器不儲存使用者個人資料、聯絡人和傳送的訊息,也不連線彼此,也沒有伺服器目錄。", + "simplex-network-2-header": "與互聯網路不同", + "comparison-section-header": "與其他協議之比較", + "protocol-2-text": "XMPP、Matrix", + "comparison-point-1-text": "需要全局身份", + "simplex-network-3-desc": "伺服器提供單向佇列以連接使用者,但伺服器無法看到網路連線圖 —只有使用者能看到。", + "protocol-1-text": "Signal,大平台", + "comparison-point-2-text": "中間人攻擊之可能", + "comparison-point-3-text": "對DNS的依賴", + "comparison-point-4-text": "單一或集中式網路", + "comparison-section-list-point-3": "公鑰或其他某種全局獨一的標識符", + "comparison-section-list-point-5": "不保護使用者的元數據隱私", + "comparison-section-list-point-7": "點對點網路必須有中央權威,否則整個網路都可能受到攻擊", + "comparison-section-list-point-4": "如果運營商的伺服器受到攻擊,用其他應用程式(如 Signal)驗證 SimpleX 安全碼,以減輕風險", + "guide-dropdown-8": "應用程式設定", + "guide-dropdown-6": "音視訊通話", + "docs-dropdown-1": "SimpleX 網路", + "guide": "指南", + "docs-dropdown-9": "下載", + "docs-dropdown-11": "常見問題", + "docs-dropdown-12": "安全性", + "docs-dropdown-7": "翻譯SimpleX", + "f-droid-page-f-droid-org-repo-section-text": "SimpleX Chat 和F-Droid.org 儲存庫使用不同的金鑰為安裝包簽名。若要切換,請匯出聊天資料庫並重新安裝應用程式。" } From 348961576ba25cdf1d8db47c0e60ab4f0e26688e Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 12 May 2025 16:36:18 +0100 Subject: [PATCH 548/567] website: translations (#5896) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ Co-authored-by: summoner001 --- website/langs/hu.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/langs/hu.json b/website/langs/hu.json index 8acd7d9a60..9dfd3d9f65 100644 --- a/website/langs/hu.json +++ b/website/langs/hu.json @@ -139,7 +139,7 @@ "sign-up-to-receive-our-updates": "Regisztráljon a hírleveleinkre, hogy ne maradjon le semmiről", "enter-your-email-address": "Adja meg az e-mail-címét", "get-simplex": "A SimpleX számítógépes alkalmazásának letöltése", - "why-simplex-is-unique": "A SimpleX mitől egyedülálló", + "why-simplex-is-unique": "Mitől egyedülálló a SimpleX", "learn-more": "Tudjon meg többet", "more-info": "További információ", "hide-info": "Információ elrejtése", From 1f8609a31f91cf74a67efb73ee7c05836722de3f Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Mon, 12 May 2025 15:57:20 +0000 Subject: [PATCH 549/567] core: make member admission forwards compatible (#5893) * core: make member admission forwards compatible * cabal file * schema * plans * inserts * plans --- simplex-chat.cabal | 2 ++ src/Simplex/Chat/Library/Commands.hs | 2 +- src/Simplex/Chat/Library/Internal.hs | 2 +- src/Simplex/Chat/Store/Connections.hs | 2 +- src/Simplex/Chat/Store/Groups.hs | 36 +++++++++---------- src/Simplex/Chat/Store/Postgres/Migrations.hs | 4 ++- .../Migrations/M20250402_short_links.hs | 2 ++ .../Migrations/M20250512_member_admission.hs | 21 +++++++++++ src/Simplex/Chat/Store/SQLite/Migrations.hs | 4 ++- .../Migrations/M20250512_member_admission.hs | 18 ++++++++++ .../SQLite/Migrations/chat_query_plans.txt | 16 ++++----- .../Store/SQLite/Migrations/chat_schema.sql | 3 +- src/Simplex/Chat/Store/Shared.hs | 8 ++--- src/Simplex/Chat/Types.hs | 28 +++++++++++++-- src/Simplex/Chat/View.hs | 9 +++-- tests/ProtocolTests.hs | 2 +- 16 files changed, 117 insertions(+), 42 deletions(-) create mode 100644 src/Simplex/Chat/Store/Postgres/Migrations/M20250512_member_admission.hs create mode 100644 src/Simplex/Chat/Store/SQLite/Migrations/M20250512_member_admission.hs diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 52e40d6c6f..0ae425be48 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -104,6 +104,7 @@ library Simplex.Chat.Store.Postgres.Migrations Simplex.Chat.Store.Postgres.Migrations.M20241220_initial Simplex.Chat.Store.Postgres.Migrations.M20250402_short_links + Simplex.Chat.Store.Postgres.Migrations.M20250512_member_admission else exposed-modules: Simplex.Chat.Archive @@ -234,6 +235,7 @@ library Simplex.Chat.Store.SQLite.Migrations.M20250129_delete_unused_contacts Simplex.Chat.Store.SQLite.Migrations.M20250130_indexes Simplex.Chat.Store.SQLite.Migrations.M20250402_short_links + Simplex.Chat.Store.SQLite.Migrations.M20250512_member_admission other-modules: Paths_simplex_chat hs-source-dirs: diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index f21c9526a5..d3b945af4f 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -4290,7 +4290,7 @@ chatCommandP = { directMessages = Just DirectMessagesGroupPreference {enable = FEOn, role = Nothing}, history = Just HistoryGroupPreference {enable = FEOn} } - pure GroupProfile {displayName = gName, fullName, description = Nothing, image = Nothing, groupPreferences} + pure GroupProfile {displayName = gName, fullName, description = Nothing, image = Nothing, groupPreferences, memberAdmission = Nothing} fullNameP = A.space *> textP <|> pure "" textP = safeDecodeUtf8 <$> A.takeByteString pwdP = jsonP <|> (UserPwd . safeDecodeUtf8 <$> A.takeTill (== ' ')) diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs index 8158df5c94..bbefbcfde0 100644 --- a/src/Simplex/Chat/Library/Internal.hs +++ b/src/Simplex/Chat/Library/Internal.hs @@ -972,7 +972,7 @@ acceptBusinessJoinRequestAsync where businessGroupProfile :: Profile -> GroupPreferences -> GroupProfile businessGroupProfile Profile {displayName, fullName, image} groupPreferences = - GroupProfile {displayName, fullName, description = Nothing, image, groupPreferences = Just groupPreferences} + GroupProfile {displayName, fullName, description = Nothing, image, groupPreferences = Just groupPreferences, memberAdmission = Nothing} profileToSendOnAccept :: User -> Maybe IncognitoProfile -> Bool -> Profile profileToSendOnAccept user ip = userProfileToSend user (getIncognitoProfile <$> ip) Nothing diff --git a/src/Simplex/Chat/Store/Connections.hs b/src/Simplex/Chat/Store/Connections.hs index 0a4f5392c0..5c177969b9 100644 --- a/src/Simplex/Chat/Store/Connections.hs +++ b/src/Simplex/Chat/Store/Connections.hs @@ -136,7 +136,7 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do SELECT -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, - g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, + g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, -- GroupInfo {membership} mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 0c49338a2e..fc23c9ef44 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -277,7 +277,7 @@ getGroupAndMember db User {userId, userContactId} groupMemberId vr = do SELECT -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, - g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, + g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, -- GroupInfo {membership} mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, @@ -318,7 +318,7 @@ getGroupAndMember db User {userId, userContactId} groupMemberId vr = do -- | creates completely new group with a single member - the current user createNewGroup :: DB.Connection -> VersionRangeChat -> TVar ChaChaDRG -> User -> GroupProfile -> Maybe Profile -> ExceptT StoreError IO GroupInfo createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = ExceptT $ do - let GroupProfile {displayName, fullName, description, image, groupPreferences} = groupProfile + let GroupProfile {displayName, fullName, description, image, groupPreferences, memberAdmission} = groupProfile fullGroupPreferences = mergeGroupPreferences groupPreferences currentTs <- getCurrentTime customUserProfileId <- mapM (createIncognitoProfile_ db userId currentTs) incognitoProfile @@ -326,8 +326,8 @@ createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = Exc groupId <- liftIO $ do DB.execute db - "INSERT INTO group_profiles (display_name, full_name, description, image, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)" - (displayName, fullName, description, image, userId, groupPreferences, currentTs, currentTs) + "INSERT INTO group_profiles (display_name, full_name, description, image, user_id, preferences, member_admission, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)" + (displayName, fullName, description, image, userId, groupPreferences, memberAdmission, currentTs, currentTs) profileId <- insertedRowId db DB.execute db @@ -387,7 +387,7 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ DB.query db "SELECT group_id FROM groups WHERE inv_queue_info = ? AND user_id = ? LIMIT 1" (connRequest, userId) createGroupInvitation_ :: ExceptT StoreError IO (GroupInfo, GroupMemberId) createGroupInvitation_ = do - let GroupProfile {displayName, fullName, description, image, groupPreferences} = groupProfile + let GroupProfile {displayName, fullName, description, image, groupPreferences, memberAdmission} = groupProfile fullGroupPreferences = mergeGroupPreferences groupPreferences ExceptT $ withLocalDisplayName db userId displayName $ \localDisplayName -> runExceptT $ do @@ -395,8 +395,8 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ groupId <- liftIO $ do DB.execute db - "INSERT INTO group_profiles (display_name, full_name, description, image, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)" - (displayName, fullName, description, image, userId, groupPreferences, currentTs, currentTs) + "INSERT INTO group_profiles (display_name, full_name, description, image, user_id, preferences, member_admission, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)" + (displayName, fullName, description, image, userId, groupPreferences, memberAdmission, currentTs, currentTs) profileId <- insertedRowId db DB.execute db @@ -554,13 +554,13 @@ createGroupViaLink' (,) <$> getGroupInfo db vr user groupId <*> getGroupMemberById db vr user hostMemberId where insertGroup_ currentTs = ExceptT $ do - let GroupProfile {displayName, fullName, description, image, groupPreferences} = groupProfile + let GroupProfile {displayName, fullName, description, image, groupPreferences, memberAdmission} = groupProfile withLocalDisplayName db userId displayName $ \localDisplayName -> runExceptT $ do liftIO $ do DB.execute db - "INSERT INTO group_profiles (display_name, full_name, description, image, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)" - (displayName, fullName, description, image, userId, groupPreferences, currentTs, currentTs) + "INSERT INTO group_profiles (display_name, full_name, description, image, user_id, preferences, member_admission, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)" + (displayName, fullName, description, image, userId, groupPreferences, memberAdmission, currentTs, currentTs) profileId <- insertedRowId db DB.execute db @@ -763,7 +763,7 @@ getUserGroupDetails db vr User {userId, userContactId} _contactId_ search_ = do [sql| SELECT g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, - g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, + g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, @@ -1544,7 +1544,7 @@ getViaGroupMember db vr User {userId, userContactId} Contact {contactId} = do SELECT -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, - g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, + g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, -- GroupInfo {membership} mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, @@ -1601,7 +1601,7 @@ getViaGroupContact db vr user@User {userId} GroupMember {groupMemberId} = do maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getContact db vr user) contactId_ updateGroupProfile :: DB.Connection -> User -> GroupInfo -> GroupProfile -> ExceptT StoreError IO GroupInfo -updateGroupProfile db user@User {userId} g@GroupInfo {groupId, localDisplayName, groupProfile = GroupProfile {displayName}} p'@GroupProfile {displayName = newName, fullName, description, image, groupPreferences} +updateGroupProfile db user@User {userId} g@GroupInfo {groupId, localDisplayName, groupProfile = GroupProfile {displayName}} p'@GroupProfile {displayName = newName, fullName, description, image, groupPreferences, memberAdmission} | displayName == newName = liftIO $ do currentTs <- getCurrentTime updateGroupProfile_ currentTs @@ -1619,14 +1619,14 @@ updateGroupProfile db user@User {userId} g@GroupInfo {groupId, localDisplayName, db [sql| UPDATE group_profiles - SET display_name = ?, full_name = ?, description = ?, image = ?, preferences = ?, updated_at = ? + SET display_name = ?, full_name = ?, description = ?, image = ?, preferences = ?, member_admission = ?, updated_at = ? WHERE group_profile_id IN ( SELECT group_profile_id FROM groups WHERE user_id = ? AND group_id = ? ) |] - (newName, fullName, description, image, groupPreferences, currentTs, userId, groupId) + (newName, fullName, description, image, groupPreferences, memberAdmission, currentTs, userId, groupId) updateGroup_ ldn currentTs = do DB.execute db @@ -1664,14 +1664,14 @@ updateGroupProfileFromMember db user g@GroupInfo {groupId} Profile {displayName DB.query db [sql| - SELECT gp.display_name, gp.full_name, gp.description, gp.image, gp.preferences + SELECT gp.display_name, gp.full_name, gp.description, gp.image, gp.preferences, gp.member_admission FROM group_profiles gp JOIN groups g ON gp.group_profile_id = g.group_profile_id WHERE g.group_id = ? |] (Only groupId) - toGroupProfile (displayName, fullName, description, image, groupPreferences) = - GroupProfile {displayName, fullName, description, image, groupPreferences} + toGroupProfile (displayName, fullName, description, image, groupPreferences, memberAdmission) = + GroupProfile {displayName, fullName, description, image, groupPreferences, memberAdmission} getGroupInfo :: DB.Connection -> VersionRangeChat -> User -> Int64 -> ExceptT StoreError IO GroupInfo getGroupInfo db vr User {userId, userContactId} groupId = ExceptT $ do diff --git a/src/Simplex/Chat/Store/Postgres/Migrations.hs b/src/Simplex/Chat/Store/Postgres/Migrations.hs index dc7202edc8..c392c17db1 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations.hs +++ b/src/Simplex/Chat/Store/Postgres/Migrations.hs @@ -6,12 +6,14 @@ import Data.List (sortOn) import Data.Text (Text) import Simplex.Chat.Store.Postgres.Migrations.M20241220_initial import Simplex.Chat.Store.Postgres.Migrations.M20250402_short_links +import Simplex.Chat.Store.Postgres.Migrations.M20250512_member_admission import Simplex.Messaging.Agent.Store.Shared (Migration (..)) schemaMigrations :: [(String, Text, Maybe Text)] schemaMigrations = [ ("20241220_initial", m20241220_initial, Nothing), - ("20250402_short_links", m20250402_short_links, Just down_m20250402_short_links) + ("20250402_short_links", m20250402_short_links, Just down_m20250402_short_links), + ("20250512_member_admission", m20250512_member_admission, Just down_m20250512_member_admission) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/M20250402_short_links.hs b/src/Simplex/Chat/Store/Postgres/Migrations/M20250402_short_links.hs index de4f699377..4b3b7e9640 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations/M20250402_short_links.hs +++ b/src/Simplex/Chat/Store/Postgres/Migrations/M20250402_short_links.hs @@ -12,6 +12,7 @@ m20250402_short_links = [r| ALTER TABLE user_contact_links ADD COLUMN short_link_contact BYTEA; ALTER TABLE connections ADD COLUMN short_link_inv BYTEA; +ALTER TABLE connections ADD COLUMN via_short_link_contact BYTEA; |] down_m20250402_short_links :: Text @@ -20,4 +21,5 @@ down_m20250402_short_links = [r| ALTER TABLE user_contact_links DROP COLUMN short_link_contact; ALTER TABLE connections DROP COLUMN short_link_inv; +ALTER TABLE connections DROP COLUMN via_short_link_contact; |] diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/M20250512_member_admission.hs b/src/Simplex/Chat/Store/Postgres/Migrations/M20250512_member_admission.hs new file mode 100644 index 0000000000..eb0d73a523 --- /dev/null +++ b/src/Simplex/Chat/Store/Postgres/Migrations/M20250512_member_admission.hs @@ -0,0 +1,21 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Store.Postgres.Migrations.M20250512_member_admission where + +import Data.Text (Text) +import qualified Data.Text as T +import Text.RawString.QQ (r) + +m20250512_member_admission :: Text +m20250512_member_admission = + T.pack + [r| +ALTER TABLE group_profiles ADD COLUMN member_admission TEXT; +|] + +down_m20250512_member_admission :: Text +down_m20250512_member_admission = + T.pack + [r| +ALTER TABLE group_profiles DROP COLUMN member_admission; +|] diff --git a/src/Simplex/Chat/Store/SQLite/Migrations.hs b/src/Simplex/Chat/Store/SQLite/Migrations.hs index 81253c5b87..183d699f01 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations.hs @@ -129,6 +129,7 @@ import Simplex.Chat.Store.SQLite.Migrations.M20250126_mentions import Simplex.Chat.Store.SQLite.Migrations.M20250129_delete_unused_contacts import Simplex.Chat.Store.SQLite.Migrations.M20250130_indexes import Simplex.Chat.Store.SQLite.Migrations.M20250402_short_links +import Simplex.Chat.Store.SQLite.Migrations.M20250512_member_admission import Simplex.Messaging.Agent.Store.Shared (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -257,7 +258,8 @@ schemaMigrations = ("20250126_mentions", m20250126_mentions, Just down_m20250126_mentions), ("20250129_delete_unused_contacts", m20250129_delete_unused_contacts, Just down_m20250129_delete_unused_contacts), ("20250130_indexes", m20250130_indexes, Just down_m20250130_indexes), - ("20250402_short_links", m20250402_short_links, Just down_m20250402_short_links) + ("20250402_short_links", m20250402_short_links, Just down_m20250402_short_links), + ("20250512_member_admission", m20250512_member_admission, Just down_m20250512_member_admission) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20250512_member_admission.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20250512_member_admission.hs new file mode 100644 index 0000000000..e1f45beef1 --- /dev/null +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20250512_member_admission.hs @@ -0,0 +1,18 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Store.SQLite.Migrations.M20250512_member_admission where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20250512_member_admission :: Query +m20250512_member_admission = + [sql| +ALTER TABLE group_profiles ADD COLUMN member_admission TEXT; +|] + +down_m20250512_member_admission :: Query +down_m20250512_member_admission = + [sql| +ALTER TABLE group_profiles DROP COLUMN member_admission; +|] diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt index e3eff0f6f1..88c6c33b41 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt @@ -35,7 +35,7 @@ Query: SELECT -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, - g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, + g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, -- GroupInfo {membership} mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, @@ -544,7 +544,7 @@ SEARCH m USING INTEGER PRIMARY KEY (rowid=?) SEARCH ct USING INTEGER PRIMARY KEY (rowid=?) Query: - SELECT gp.display_name, gp.full_name, gp.description, gp.image, gp.preferences + SELECT gp.display_name, gp.full_name, gp.description, gp.image, gp.preferences, gp.member_admission FROM group_profiles gp JOIN groups g ON gp.group_profile_id = g.group_profile_id WHERE g.group_id = ? @@ -805,7 +805,7 @@ Query: SELECT -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, - g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, + g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, -- GroupInfo {membership} mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, @@ -850,7 +850,7 @@ SEARCH cc USING COVERING INDEX idx_connections_group_member (user_id=? AND group Query: SELECT g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, - g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, + g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, @@ -1235,7 +1235,7 @@ SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?) Query: UPDATE group_profiles - SET display_name = ?, full_name = ?, description = ?, image = ?, preferences = ?, updated_at = ? + SET display_name = ?, full_name = ?, description = ?, image = ?, preferences = ?, member_admission = ?, updated_at = ? WHERE group_profile_id IN ( SELECT group_profile_id FROM groups @@ -4458,7 +4458,7 @@ Query: SELECT -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, - g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, + g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, -- GroupMember - membership mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, @@ -4480,7 +4480,7 @@ Query: SELECT -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, - g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, + g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, -- GroupMember - membership mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, @@ -5387,7 +5387,7 @@ Plan: Query: INSERT INTO files (user_id, group_id, file_name, file_size, chunk_size, file_inline, ci_file_status, protocol, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?) Plan: -Query: INSERT INTO group_profiles (display_name, full_name, description, image, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?) +Query: INSERT INTO group_profiles (display_name, full_name, description, image, user_id, preferences, member_admission, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?) Plan: Query: INSERT INTO group_profiles (display_name, full_name, image, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?) diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql index 33b800f4ef..6fbed97d27 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql @@ -108,7 +108,8 @@ CREATE TABLE group_profiles( image TEXT, user_id INTEGER DEFAULT NULL REFERENCES users ON DELETE CASCADE, preferences TEXT, - description TEXT NULL + description TEXT NULL, + member_admission TEXT ); CREATE TABLE groups( group_id INTEGER PRIMARY KEY, -- local group ID diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index c681180759..b32fd07bb5 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -579,16 +579,16 @@ safeDeleteLDN db User {userId} localDisplayName = do type BusinessChatInfoRow = (Maybe BusinessChatType, Maybe MemberId, Maybe MemberId) -type GroupInfoRow = (Int64, GroupName, GroupName, Text, Text, Maybe Text, Maybe ImageData, Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. BusinessChatInfoRow :. (Maybe UIThemeEntityOverrides, Maybe CustomData, Maybe Int64) :. GroupMemberRow +type GroupInfoRow = (Int64, GroupName, GroupName, Text, Text, Maybe Text, Maybe ImageData, Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe GroupPreferences, Maybe GroupMemberAdmission) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. BusinessChatInfoRow :. (Maybe UIThemeEntityOverrides, Maybe CustomData, Maybe Int64) :. GroupMemberRow type GroupMemberRow = (Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnLinkContact, LocalAlias, Maybe Preferences) :. (UTCTime, UTCTime) toGroupInfo :: VersionRangeChat -> Int64 -> [ChatTagId] -> GroupInfoRow -> GroupInfo -toGroupInfo vr userContactId chatTags ((groupId, localDisplayName, displayName, fullName, localAlias, description, image, enableNtfs_, sendRcpts, BI favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. businessRow :. (uiThemes, customData, chatItemTTL) :. userMemberRow) = +toGroupInfo vr userContactId chatTags ((groupId, localDisplayName, displayName, fullName, localAlias, description, image, enableNtfs_, sendRcpts, BI favorite, groupPreferences, memberAdmission) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. businessRow :. (uiThemes, customData, chatItemTTL) :. userMemberRow) = let membership = (toGroupMember userContactId userMemberRow) {memberChatVRange = vr} chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite} fullGroupPreferences = mergeGroupPreferences groupPreferences - groupProfile = GroupProfile {displayName, fullName, description, image, groupPreferences} + groupProfile = GroupProfile {displayName, fullName, description, image, groupPreferences, memberAdmission} businessChat = toBusinessChatInfo businessRow in GroupInfo {groupId, localDisplayName, groupProfile, localAlias, businessChat, fullGroupPreferences, membership, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, chatTags, chatItemTTL, uiThemes, customData} @@ -612,7 +612,7 @@ groupInfoQuery = SELECT -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, - g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, + g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, -- GroupMember - membership mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index 9d875f5bf4..0063f711c7 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -57,7 +57,7 @@ import Simplex.Messaging.Crypto.File (CryptoFileArgs (..)) import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport, pattern PQEncOff) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, sumTypeJSON) -import Simplex.Messaging.Util (safeDecodeUtf8, (<$?>)) +import Simplex.Messaging.Util (decodeJSON, encodeJSON, safeDecodeUtf8, (<$?>)) import Simplex.Messaging.Version import Simplex.Messaging.Version.Internal #if defined(dbPostgres) @@ -616,10 +616,24 @@ data GroupProfile = GroupProfile fullName :: Text, description :: Maybe Text, image :: Maybe ImageData, - groupPreferences :: Maybe GroupPreferences + groupPreferences :: Maybe GroupPreferences, + memberAdmission :: Maybe GroupMemberAdmission } deriving (Eq, Show) +data GroupMemberAdmission = GroupMemberAdmission + { -- names :: Maybe MemberCriteria, + -- captcha :: Maybe MemberCriteria, + review :: Maybe MemberCriteria + } + deriving (Eq, Show) + +data MemberCriteria = MCAll + deriving (Eq, Show) + +emptyGroupMemberAdmission :: GroupMemberAdmission +emptyGroupMemberAdmission = GroupMemberAdmission Nothing + newtype ImageData = ImageData Text deriving (Eq, Show) @@ -1816,6 +1830,16 @@ $(JQ.deriveJSON defaultJSON ''LocalProfile) $(JQ.deriveJSON defaultJSON ''UserContactRequest) +$(JQ.deriveJSON (enumJSON $ dropPrefix "MC") {J.tagSingleConstructors = True} ''MemberCriteria) + +$(JQ.deriveJSON defaultJSON ''GroupMemberAdmission) + +instance ToField GroupMemberAdmission where + toField = toField . encodeJSON + +instance FromField GroupMemberAdmission where + fromField = fromTextField_ decodeJSON + $(JQ.deriveJSON defaultJSON ''GroupProfile) $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "IB") ''InvitedBy) diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index c07fcc952d..42d1132961 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -1680,10 +1680,10 @@ countactUserPrefText cup = case cup of viewGroupUpdated :: GroupInfo -> GroupInfo -> Maybe GroupMember -> [StyledString] viewGroupUpdated - GroupInfo {localDisplayName = n, groupProfile = GroupProfile {fullName, description, image, groupPreferences = gps}} - g'@GroupInfo {localDisplayName = n', groupProfile = GroupProfile {fullName = fullName', description = description', image = image', groupPreferences = gps'}} + GroupInfo {localDisplayName = n, groupProfile = GroupProfile {fullName, description, image, groupPreferences = gps, memberAdmission = ma}} + g'@GroupInfo {localDisplayName = n', groupProfile = GroupProfile {fullName = fullName', description = description', image = image', groupPreferences = gps', memberAdmission = ma'}} m = do - let update = groupProfileUpdated <> groupPrefsUpdated + let update = groupProfileUpdated <> groupPrefsUpdated <> memberAdmissionUpdated if null update then [] else memberUpdated <> update @@ -1704,6 +1704,9 @@ viewGroupUpdated | otherwise = Just . plain $ groupPreferenceText (pref gps') where pref = getGroupPreference f . mergeGroupPreferences + memberAdmissionUpdated + | ma == ma' = [] + | otherwise = ["changed member admission rules"] viewGroupProfile :: GroupInfo -> [StyledString] viewGroupProfile g@GroupInfo {groupProfile = GroupProfile {description, image, groupPreferences = gps}} = diff --git a/tests/ProtocolTests.hs b/tests/ProtocolTests.hs index 50d2c1eef0..1d37a52459 100644 --- a/tests/ProtocolTests.hs +++ b/tests/ProtocolTests.hs @@ -107,7 +107,7 @@ testProfile :: Profile testProfile = Profile {displayName = "alice", fullName = "Alice", image = Just (ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII="), contactLink = Nothing, preferences = testChatPreferences} testGroupProfile :: GroupProfile -testGroupProfile = GroupProfile {displayName = "team", fullName = "Team", description = Nothing, image = Nothing, groupPreferences = testGroupPreferences} +testGroupProfile = GroupProfile {displayName = "team", fullName = "Team", description = Nothing, image = Nothing, groupPreferences = testGroupPreferences, memberAdmission = Nothing} decodeChatMessageTest :: Spec decodeChatMessageTest = describe "Chat message encoding/decoding" $ do From 9098e22d4be39b4b7882379c93acb0946a174502 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 12 May 2025 16:58:49 +0100 Subject: [PATCH 550/567] core: 6.3.4.1 --- simplex-chat.cabal | 2 +- src/Simplex/Chat/Remote.hs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 0ae425be48..a9dca273e3 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.3.4.0 +version: 6.3.4.1 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index 021f048d6b..d41957bbac 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -75,11 +75,11 @@ remoteFilesFolder = "simplex_v1_files" -- when acting as host minRemoteCtrlVersion :: AppVersion -minRemoteCtrlVersion = AppVersion [6, 3, 4, 0] +minRemoteCtrlVersion = AppVersion [6, 3, 4, 1] -- when acting as controller minRemoteHostVersion :: AppVersion -minRemoteHostVersion = AppVersion [6, 3, 4, 0] +minRemoteHostVersion = AppVersion [6, 3, 4, 1] currentAppVersion :: AppVersion currentAppVersion = AppVersion SC.version From 9b4908c3702740ee36ea49fba30f41dbd402af15 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 12 May 2025 19:34:06 +0100 Subject: [PATCH 551/567] 6.3.4: ios 277, android 287, desktop 100 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 36 +++++++++++----------- apps/multiplatform/gradle.properties | 8 ++--- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 8a035c70a9..5ebc7f9b4b 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -174,8 +174,8 @@ 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; }; 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; }; - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.0-47rHZ52LFetD6j9vq8gwHM-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.0-47rHZ52LFetD6j9vq8gwHM-ghc9.6.3.a */; }; - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.0-47rHZ52LFetD6j9vq8gwHM.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.0-47rHZ52LFetD6j9vq8gwHM.a */; }; + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO-ghc9.6.3.a */; }; + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO.a */; }; 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; @@ -533,8 +533,8 @@ 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.0-47rHZ52LFetD6j9vq8gwHM-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.4.0-47rHZ52LFetD6j9vq8gwHM-ghc9.6.3.a"; sourceTree = ""; }; - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.0-47rHZ52LFetD6j9vq8gwHM.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.4.0-47rHZ52LFetD6j9vq8gwHM.a"; sourceTree = ""; }; + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO-ghc9.6.3.a"; sourceTree = ""; }; + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO.a"; sourceTree = ""; }; 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; @@ -692,8 +692,8 @@ 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */, 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */, 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */, - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.0-47rHZ52LFetD6j9vq8gwHM-ghc9.6.3.a in Frameworks */, - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.0-47rHZ52LFetD6j9vq8gwHM.a in Frameworks */, + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO-ghc9.6.3.a in Frameworks */, + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -778,8 +778,8 @@ 64C829992D54AEEE006B9E89 /* libffi.a */, 64C829982D54AEED006B9E89 /* libgmp.a */, 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */, - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.0-47rHZ52LFetD6j9vq8gwHM-ghc9.6.3.a */, - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.0-47rHZ52LFetD6j9vq8gwHM.a */, + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO-ghc9.6.3.a */, + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO.a */, ); path = Libraries; sourceTree = ""; @@ -1971,7 +1971,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 276; + CURRENT_PROJECT_VERSION = 277; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2021,7 +2021,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 276; + CURRENT_PROJECT_VERSION = 277; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2063,7 +2063,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 276; + CURRENT_PROJECT_VERSION = 277; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2083,7 +2083,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 276; + CURRENT_PROJECT_VERSION = 277; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2108,7 +2108,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 276; + CURRENT_PROJECT_VERSION = 277; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2145,7 +2145,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 276; + CURRENT_PROJECT_VERSION = 277; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2182,7 +2182,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 276; + CURRENT_PROJECT_VERSION = 277; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2233,7 +2233,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 276; + CURRENT_PROJECT_VERSION = 277; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2284,7 +2284,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 276; + CURRENT_PROJECT_VERSION = 277; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2318,7 +2318,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 276; + CURRENT_PROJECT_VERSION = 277; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 2dc5be4210..9258529ecb 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -24,11 +24,11 @@ android.nonTransitiveRClass=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.3.3 -android.version_code=285 +android.version_name=6.3.4 +android.version_code=287 -desktop.version_name=6.3.3 -desktop.version_code=99 +desktop.version_name=6.3.4 +desktop.version_code=100 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From dc35e5f765acd4614f670ddb207c231af37b52f5 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 13 May 2025 18:19:22 +0100 Subject: [PATCH 552/567] android, desktop: fix sending reports --- .../commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 7025e4caf3..979d79c839 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -964,7 +964,7 @@ object ChatController { suspend fun apiReportMessage(rh: Long?, groupId: Long, chatItemId: Long, reportReason: ReportReason, reportText: String): List? { val r = sendCmd(rh, CC.ApiReportMessage(groupId, chatItemId, reportReason, reportText)) - if (r is API.Result && r.res is CR.NewChatItems) r.res.chatItems + if (r is API.Result && r.res is CR.NewChatItems) return r.res.chatItems apiErrorAlert("apiReportMessage", generalGetString(MR.strings.error_creating_report), r) return null } From 8af3cb935eeaae2f644ddc9e61ddb314d464eea4 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 13 May 2025 18:53:37 +0100 Subject: [PATCH 553/567] 6.3.4: android 288, desktop 101 --- apps/multiplatform/gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 9258529ecb..18add58bcf 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -25,10 +25,10 @@ kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 android.version_name=6.3.4 -android.version_code=287 +android.version_code=288 desktop.version_name=6.3.4 -desktop.version_code=100 +desktop.version_code=101 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From a36a6d44db8d8b91ba9ff5a43e00a13578056147 Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Wed, 14 May 2025 09:55:03 +0000 Subject: [PATCH 554/567] flatpak: update metainfo (#5899) * flatpak: update metainfo * flatpak: rewrite metainfo --- .../flatpak/chat.simplex.simplex.metainfo.xml | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/scripts/flatpak/chat.simplex.simplex.metainfo.xml b/scripts/flatpak/chat.simplex.simplex.metainfo.xml index 82987e211a..b15e382207 100644 --- a/scripts/flatpak/chat.simplex.simplex.metainfo.xml +++ b/scripts/flatpak/chat.simplex.simplex.metainfo.xml @@ -38,6 +38,32 @@ + + https://simplex.chat/blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.html + +

    New in v6.3.1-4:

    +
      +
    • fixes mentions with trailing punctuation (e.g., hello @name!).
    • +
    • recognizes domain names as links (e.g., simplex.chat).
    • +
    • forward compatibility with "knocking" (a feature for group admins to review and to chat with the new members prior to admitting them to groups, it will be released in 6.4)
    • +
    • support for connecting via short connection links.
    • +
    • fix related to backward/forward compatibility of the app in some rare cases.
    • +
    • scrolling/navigation improvements.
    • +
    • faster onboarding (conditions and operators are combined to one screen).
    • +
    +

    New in v6.3.0:

    +
      +
    • Mention members and get notified when mentioned.
    • +
    • Send private reports to moderators.
    • +
    • Delete, block and change role for multiple members at once
    • +
    • Faster sending messages and faster deletion.
    • +
    • Organize chats into lists to keep track of what's important.
    • +
    • Jump to found and forwarded messages.
    • +
    • Private media file names.
    • +
    • Message expiration in chats.
    • +
    +
    +
    https://simplex.chat/blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.html From 5dd89fe12760f531bd8e51b06cb4cbaeb76ae960 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 15 May 2025 14:25:46 +0100 Subject: [PATCH 555/567] ios: fix swipe on iOS 15, fix onboarding layout on iOS 15 and small screens (#5913) * ios: fix onboarding layout issues on iOS 15 and small screens * fix swipe on iOS 15 --- .../Views/ChatList/ChatListNavLink.swift | 36 ++++++++++++++----- .../Shared/Views/ChatList/ChatListView.swift | 8 +---- .../Onboarding/ChooseServerOperators.swift | 10 ++++-- .../Views/Onboarding/CreateProfile.swift | 35 ++++++++++-------- .../Onboarding/SetNotificationsMode.swift | 8 ++++- .../Shared/Views/Onboarding/SimpleXInfo.swift | 16 ++++++--- .../ar.xcloc/Localized Contents/ar.xliff | 4 +-- .../bg.xcloc/Localized Contents/bg.xliff | 15 +++----- .../bn.xcloc/Localized Contents/bn.xliff | 4 +-- .../cs.xcloc/Localized Contents/cs.xliff | 16 ++++----- .../de.xcloc/Localized Contents/de.xliff | 15 +++----- .../el.xcloc/Localized Contents/el.xliff | 4 +-- .../en.xcloc/Localized Contents/en.xliff | 15 +++----- .../es.xcloc/Localized Contents/es.xliff | 15 +++----- .../fi.xcloc/Localized Contents/fi.xliff | 16 ++++----- .../fr.xcloc/Localized Contents/fr.xliff | 15 +++----- .../he.xcloc/Localized Contents/he.xliff | 4 +-- .../hr.xcloc/Localized Contents/hr.xliff | 4 +-- .../hu.xcloc/Localized Contents/hu.xliff | 15 +++----- .../it.xcloc/Localized Contents/it.xliff | 15 +++----- .../ja.xcloc/Localized Contents/ja.xliff | 16 ++++----- .../ko.xcloc/Localized Contents/ko.xliff | 4 +-- .../lt.xcloc/Localized Contents/lt.xliff | 4 +-- .../nl.xcloc/Localized Contents/nl.xliff | 15 +++----- .../pl.xcloc/Localized Contents/pl.xliff | 15 +++----- .../Localized Contents/pt-BR.xliff | 4 +-- .../pt.xcloc/Localized Contents/pt.xliff | 4 +-- .../ru.xcloc/Localized Contents/ru.xliff | 15 +++----- .../th.xcloc/Localized Contents/th.xliff | 15 +++----- .../tr.xcloc/Localized Contents/tr.xliff | 15 +++----- .../uk.xcloc/Localized Contents/uk.xliff | 15 +++----- .../Localized Contents/zh-Hans.xliff | 15 +++----- .../Localized Contents/zh-Hant.xliff | 4 +-- apps/ios/bg.lproj/Localizable.strings | 2 +- apps/ios/cs.lproj/Localizable.strings | 5 ++- apps/ios/de.lproj/Localizable.strings | 2 +- apps/ios/es.lproj/Localizable.strings | 2 +- apps/ios/fi.lproj/Localizable.strings | 5 ++- apps/ios/fr.lproj/Localizable.strings | 2 +- apps/ios/hu.lproj/Localizable.strings | 2 +- apps/ios/it.lproj/Localizable.strings | 2 +- apps/ios/ja.lproj/Localizable.strings | 5 ++- apps/ios/nl.lproj/Localizable.strings | 2 +- apps/ios/pl.lproj/Localizable.strings | 2 +- apps/ios/ru.lproj/Localizable.strings | 2 +- apps/ios/th.lproj/Localizable.strings | 2 +- apps/ios/tr.lproj/Localizable.strings | 2 +- apps/ios/uk.lproj/Localizable.strings | 2 +- apps/ios/zh-Hans.lproj/Localizable.strings | 2 +- 49 files changed, 208 insertions(+), 244 deletions(-) diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index f9cf5e98e4..81d78fbadd 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -94,7 +94,7 @@ struct ChatListNavLink: View { Group { if contact.activeConn == nil && contact.profile.contactLink != nil && contact.active { ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) - .frame(height: dynamicRowHeight) + .frameCompat(height: dynamicRowHeight) .swipeActions(edge: .trailing, allowsFullSwipe: true) { Button { deleteContactDialog( @@ -121,6 +121,7 @@ struct ChatListNavLink: View { selection: $chatModel.chatId, label: { ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) } ) + .frameCompat(height: dynamicRowHeight) .swipeActions(edge: .leading, allowsFullSwipe: true) { markReadButton() toggleFavoriteButton() @@ -145,7 +146,6 @@ struct ChatListNavLink: View { } .tint(.red) } - .frame(height: dynamicRowHeight) } } .alert(item: $alert) { $0.alert } @@ -163,7 +163,7 @@ struct ChatListNavLink: View { switch (groupInfo.membership.memberStatus) { case .memInvited: ChatPreviewView(chat: chat, progressByTimeout: $progressByTimeout) - .frame(height: dynamicRowHeight) + .frameCompat(height: dynamicRowHeight) .swipeActions(edge: .trailing, allowsFullSwipe: true) { joinGroupButton() if groupInfo.canDelete { @@ -183,7 +183,7 @@ struct ChatListNavLink: View { .disabled(inProgress) case .memAccepted: ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) - .frame(height: dynamicRowHeight) + .frameCompat(height: dynamicRowHeight) .onTapGesture { AlertManager.shared.showAlert(groupInvitationAcceptedAlert()) } @@ -203,7 +203,7 @@ struct ChatListNavLink: View { label: { ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) }, disabled: !groupInfo.ready ) - .frame(height: dynamicRowHeight) + .frameCompat(height: dynamicRowHeight) .swipeActions(edge: .leading, allowsFullSwipe: true) { markReadButton() toggleFavoriteButton() @@ -250,7 +250,7 @@ struct ChatListNavLink: View { label: { ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) }, disabled: !noteFolder.ready ) - .frame(height: dynamicRowHeight) + .frameCompat(height: dynamicRowHeight) .swipeActions(edge: .leading, allowsFullSwipe: true) { markReadButton() } @@ -433,6 +433,7 @@ struct ChatListNavLink: View { private func contactRequestNavLink(_ contactRequest: UserContactRequest) -> some View { ContactRequestView(contactRequest: contactRequest, chat: chat) + .frameCompat(height: dynamicRowHeight) .swipeActions(edge: .trailing, allowsFullSwipe: true) { Button { Task { await acceptContactRequest(incognito: false, contactRequest: contactRequest) } @@ -451,7 +452,6 @@ struct ChatListNavLink: View { } .tint(.red) } - .frame(height: dynamicRowHeight) .contentShape(Rectangle()) .onTapGesture { showContactRequestDialog = true } .confirmationDialog("Accept connection request?", isPresented: $showContactRequestDialog, titleVisibility: .visible) { @@ -463,6 +463,7 @@ struct ChatListNavLink: View { private func contactConnectionNavLink(_ contactConnection: PendingContactConnection) -> some View { ContactConnectionView(chat: chat) + .frameCompat(height: dynamicRowHeight) .swipeActions(edge: .trailing, allowsFullSwipe: true) { Button { AlertManager.shared.showAlert(deleteContactConnectionAlert(contactConnection) { a in @@ -480,7 +481,6 @@ struct ChatListNavLink: View { } .tint(theme.colors.primary) } - .frame(height: dynamicRowHeight) .appSheet(isPresented: $showContactConnectionInfo) { Group { if case let .contactConnection(contactConnection) = chat.chatInfo { @@ -583,7 +583,7 @@ struct ChatListNavLink: View { Text("invalid chat data") .foregroundColor(.red) .padding(4) - .frame(height: dynamicRowHeight) + .frameCompat(height: dynamicRowHeight) .onTapGesture { showInvalidJSON = true } .appSheet(isPresented: $showInvalidJSON) { invalidJSONView(dataToString(json)) @@ -603,6 +603,24 @@ struct ChatListNavLink: View { } } +extension View { + @inline(__always) + @ViewBuilder fileprivate func frameCompat(height: CGFloat) -> some View { + if #available(iOS 16, *) { + self.frame(height: height) + } else { + VStack(spacing: 0) { + Divider() + .padding(.leading, 16) + self + .frame(height: height) + .padding(.horizontal, 8) + .padding(.vertical, 8) + } + } + } +} + func rejectContactRequestAlert(_ contactRequest: UserContactRequest) -> Alert { Alert( title: Text("Reject contact request"), diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 5c491b6303..f34f930c6f 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -367,13 +367,7 @@ struct ChatListView: View { .offset(x: -8) } else { ForEach(cs, id: \.viewId) { chat in - VStack(spacing: .zero) { - Divider() - .padding(.leading, 16) - ChatListNavLink(chat: chat, parentSheet: $sheet) - .padding(.horizontal, 8) - .padding(.vertical, 6) - } + ChatListNavLink(chat: chat, parentSheet: $sheet) .scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center) .listRowSeparator(.hidden) .listRowInsets(EdgeInsets()) diff --git a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift index 17e1735472..656cef4a04 100644 --- a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift +++ b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift @@ -67,7 +67,7 @@ struct OnboardingConditionsView: View { var body: some View { GeometryReader { g in - ScrollView { + let v = ScrollView { VStack(alignment: .leading, spacing: 20) { Text("Conditions of use") .font(.largeTitle) @@ -107,6 +107,7 @@ struct OnboardingConditionsView: View { .frame(minHeight: 40) } } + .padding(25) .frame(minHeight: g.size.height) } .onAppear { @@ -127,9 +128,14 @@ struct OnboardingConditionsView: View { } } .frame(maxHeight: .infinity, alignment: .top) + if #available(iOS 16.4, *) { + v.scrollBounceBehavior(.basedOnSize) + } else { + v + } } .frame(maxHeight: .infinity, alignment: .top) - .padding(25) + .navigationBarHidden(true) // necessary on iOS 15 } private func continueToNextStep() { diff --git a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift index c022a2a012..ae72cb1be5 100644 --- a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift +++ b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift @@ -62,8 +62,7 @@ struct CreateProfile: View { .frame(height: 20) } footer: { VStack(alignment: .leading, spacing: 8) { - Text("Your profile, contacts and delivered messages are stored on your device.") - Text("The profile is only shared with your contacts.") + Text("Your profile is stored on your device and only shared with your contacts.") } .foregroundColor(theme.colors.secondary) .frame(maxWidth: .infinity, alignment: .leading) @@ -118,25 +117,22 @@ struct CreateFirstProfile: View { @State private var nextStepNavLinkActive = false var body: some View { - VStack(alignment: .leading, spacing: 20) { - VStack(alignment: .center, spacing: 20) { - Text("Create your profile") + let v = VStack(alignment: .leading, spacing: 16) { + VStack(alignment: .center, spacing: 16) { + Text("Create profile") .font(.largeTitle) .bold() .multilineTextAlignment(.center) - - Text("Your profile, contacts and delivered messages are stored on your device.") - .font(.callout) - .foregroundColor(theme.colors.secondary) - .multilineTextAlignment(.center) - - Text("The profile is only shared with your contacts.") + + Text("Your profile is stored on your device and only shared with your contacts.") .font(.callout) .foregroundColor(theme.colors.secondary) .multilineTextAlignment(.center) } + .fixedSize(horizontal: false, vertical: true) .frame(maxWidth: .infinity) // Ensures it takes up the full width .padding(.horizontal, 10) + .onTapGesture { focusDisplayName = false } HStack { let name = displayName.trimmingCharacters(in: .whitespaces) @@ -174,12 +170,23 @@ struct CreateFirstProfile: View { } } .onAppear() { - focusDisplayName = true + if #available(iOS 16, *) { + focusDisplayName = true + } else { + // it does not work before animation completes on iOS 15 + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + focusDisplayName = true + } + } } .padding(.horizontal, 25) - .padding(.top, 10) .padding(.bottom, 25) .frame(maxWidth: .infinity, alignment: .leading) + if #available(iOS 16, *) { + return v.padding(.top, 10) + } else { + return v.padding(.top, 75).ignoresSafeArea(.all, edges: .top) + } } func createProfileButton() -> some View { diff --git a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift index 97e1f49382..31865e7af9 100644 --- a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift +++ b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift @@ -17,7 +17,7 @@ struct SetNotificationsMode: View { var body: some View { GeometryReader { g in - ScrollView { + let v = ScrollView { VStack(alignment: .center, spacing: 20) { Text("Push notifications") .font(.largeTitle) @@ -57,11 +57,17 @@ struct SetNotificationsMode: View { .padding(25) .frame(minHeight: g.size.height) } + if #available(iOS 16.4, *) { + v.scrollBounceBehavior(.basedOnSize) + } else { + v + } } .frame(maxHeight: .infinity) .sheet(isPresented: $showInfo) { NotificationsInfoView() } + .navigationBarHidden(true) // necessary on iOS 15 } private func setNotificationsMode(_ token: DeviceToken, _ mode: NotificationsMode) { diff --git a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift index e55cc4037a..9f41a37b1d 100644 --- a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift +++ b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift @@ -18,7 +18,7 @@ struct SimpleXInfo: View { var body: some View { GeometryReader { g in - ScrollView { + let v = ScrollView { VStack(alignment: .leading) { VStack(alignment: .center, spacing: 10) { Image(colorScheme == .light ? "logo" : "logo-light") @@ -36,7 +36,7 @@ struct SimpleXInfo: View { .font(.headline) } } - + Spacer() VStack(alignment: .leading) { @@ -66,6 +66,9 @@ struct SimpleXInfo: View { } } } + .padding(.horizontal, 25) + .padding(.top, 75) + .padding(.bottom, 25) .frame(minHeight: g.size.height) } .sheet(isPresented: Binding( @@ -88,14 +91,17 @@ struct SimpleXInfo: View { createProfileNavLinkActive: $createProfileNavLinkActive ) } + if #available(iOS 16.4, *) { + v.scrollBounceBehavior(.basedOnSize) + } else { + v + } } .onAppear() { setLastVersionDefault() } .frame(maxHeight: .infinity) - .padding(.horizontal, 25) - .padding(.top, 75) - .padding(.bottom, 25) + .navigationBarHidden(true) // necessary on iOS 15 } private func onboardingInfoRow(_ image: String, _ title: LocalizedStringKey, _ text: LocalizedStringKey, width: CGFloat) -> some View { diff --git a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff index 278b9ec9b2..e965e5a1a5 100644 --- a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff +++ b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff @@ -2826,8 +2826,8 @@ We will be adding server redundancy to prevent lost messages. The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 56553b3283..776199ac1f 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -7329,11 +7329,6 @@ It can happen because of some bug or when the connection is compromised.Старата база данни не бе премахната по време на миграцията, тя може да бъде изтрита. No comment provided by engineer. - - The profile is only shared with your contacts. - Профилът се споделя само с вашите контакти. - No comment provided by engineer. - The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -8577,6 +8572,11 @@ Repeat connection request? Вашият профил **%@** ще бъде споделен. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + Профилът се споделя само с вашите контакти. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Вашият профил се съхранява на вашето устройство и се споделя само с вашите контакти. SimpleX сървърите не могат да видят вашия профил. @@ -8586,11 +8586,6 @@ Repeat connection request? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message - - Your profile, contacts and delivered messages are stored on your device. - Вашият профил, контакти и доставени съобщения се съхраняват на вашето устройство. - No comment provided by engineer. - Your random profile Вашият автоматично генериран профил diff --git a/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff b/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff index 7002f790df..bf7753675e 100644 --- a/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff +++ b/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff @@ -3422,8 +3422,8 @@ It can happen because of some bug or when the connection is compromised.The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index 21bf0aef60..0400839cb0 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -1942,6 +1942,7 @@ This is your own one-time link! Create profile + Vytvořte si profil No comment provided by engineer. @@ -7080,11 +7081,6 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Stará databáze nebyla během přenášení odstraněna, lze ji smazat. No comment provided by engineer. - - The profile is only shared with your contacts. - Profil je sdílen pouze s vašimi kontakty. - No comment provided by engineer. - The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -8271,6 +8267,11 @@ Repeat connection request? Váš profil **%@** bude sdílen. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + Profil je sdílen pouze s vašimi kontakty. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Váš profil je uložen ve vašem zařízení a sdílen pouze s vašimi kontakty. Servery SimpleX nevidí váš profil. @@ -8280,11 +8281,6 @@ Repeat connection request? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message - - Your profile, contacts and delivered messages are stored on your device. - Váš profil, kontakty a doručené zprávy jsou uloženy ve vašem zařízení. - No comment provided by engineer. - Your random profile Váš náhodný profil diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index a113d35bbd..06fd7c5a1d 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -7763,11 +7763,6 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Die alte Datenbank wurde während der Migration nicht entfernt. Sie kann gelöscht werden. No comment provided by engineer. - - The profile is only shared with your contacts. - Das Profil wird nur mit Ihren Kontakten geteilt. - No comment provided by engineer. - The same conditions will apply to operator **%@**. Dieselben Nutzungsbedingungen gelten auch für den Betreiber **%@**. @@ -9081,6 +9076,11 @@ Verbindungsanfrage wiederholen? Ihr Profil **%@** wird geteilt. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + Das Profil wird nur mit Ihren Kontakten geteilt. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Ihr Profil wird auf Ihrem Gerät gespeichert und nur mit Ihren Kontakten geteilt. SimpleX-Server können Ihr Profil nicht einsehen. @@ -9091,11 +9091,6 @@ Verbindungsanfrage wiederholen? Ihr Profil wurde geändert. Wenn Sie es speichern, wird das aktualisierte Profil an alle Ihre Kontakte gesendet. alert message - - Your profile, contacts and delivered messages are stored on your device. - Ihr Profil, Ihre Kontakte und zugestellten Nachrichten werden auf Ihrem Gerät gespeichert. - No comment provided by engineer. - Your random profile Ihr Zufallsprofil diff --git a/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff b/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff index b601d1fa74..fc1846942c 100644 --- a/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff +++ b/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff @@ -3043,8 +3043,8 @@ It can happen because of some bug or when the connection is compromised.The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 641af86c2a..fd71e0dee6 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -7764,11 +7764,6 @@ It can happen because of some bug or when the connection is compromised.The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. - The profile is only shared with your contacts. - No comment provided by engineer. - The same conditions will apply to operator **%@**. The same conditions will apply to operator **%@**. @@ -9082,6 +9077,11 @@ Repeat connection request? Your profile **%@** will be shared. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + Your profile is stored on your device and only shared with your contacts. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. @@ -9092,11 +9092,6 @@ Repeat connection request? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message - - Your profile, contacts and delivered messages are stored on your device. - Your profile, contacts and delivered messages are stored on your device. - No comment provided by engineer. - Your random profile Your random profile diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 73f88e1cab..d39fb61249 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -7763,11 +7763,6 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. La base de datos antigua no se eliminó durante la migración, puede eliminarse. No comment provided by engineer. - - The profile is only shared with your contacts. - El perfil sólo se comparte con tus contactos. - No comment provided by engineer. - The same conditions will apply to operator **%@**. Las mismas condiciones se aplicarán al operador **%@**. @@ -9081,6 +9076,11 @@ Repeat connection request? El perfil **%@** será compartido. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + El perfil sólo se comparte con tus contactos. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Tu perfil es almacenado en tu dispositivo y solamente se comparte con tus contactos. Los servidores SimpleX no pueden ver tu perfil. @@ -9091,11 +9091,6 @@ Repeat connection request? Tu perfil ha sido modificado. Si lo guardas la actualización será enviada a todos tus contactos. alert message - - Your profile, contacts and delivered messages are stored on your device. - Tu perfil, contactos y mensajes se almacenan en tu dispositivo. - No comment provided by engineer. - Your random profile Tu perfil aleatorio diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index 5281fbc701..a54666bb10 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -1923,6 +1923,7 @@ This is your own one-time link! Create profile + Luo profiilisi No comment provided by engineer. @@ -7054,11 +7055,6 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.
    Vanhaa tietokantaa ei poistettu siirron aikana, se voidaan kuitenkin poistaa. No comment provided by engineer. - - The profile is only shared with your contacts. - Profiili jaetaan vain kontaktiesi kanssa. - No comment provided by engineer. - The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -8244,6 +8240,11 @@ Repeat connection request? Profiilisi **%@** jaetaan. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + Profiili jaetaan vain kontaktiesi kanssa. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Profiilisi tallennetaan laitteeseesi ja jaetaan vain yhteystietojesi kanssa. SimpleX-palvelimet eivät näe profiiliasi. @@ -8253,11 +8254,6 @@ Repeat connection request? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message - - Your profile, contacts and delivered messages are stored on your device. - Profiilisi, kontaktisi ja toimitetut viestit tallennetaan laitteellesi. - No comment provided by engineer. - Your random profile Satunnainen profiilisi diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 1e5da0b0ed..59bde0650e 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -7696,11 +7696,6 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. L'ancienne base de données n'a pas été supprimée lors de la migration, elle peut être supprimée. No comment provided by engineer. - - The profile is only shared with your contacts. - Le profil n'est partagé qu'avec vos contacts. - No comment provided by engineer. - The same conditions will apply to operator **%@**. Les mêmes conditions s'appliquent à l'opérateur **%@**. @@ -9003,6 +8998,11 @@ Répéter la demande de connexion ? Votre profil **%@** sera partagé. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + Le profil n'est partagé qu'avec vos contacts. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Votre profil est stocké sur votre appareil et est seulement partagé avec vos contacts. Les serveurs SimpleX ne peuvent pas voir votre profil. @@ -9013,11 +9013,6 @@ Répéter la demande de connexion ? Votre profil a été modifié. Si vous l'enregistrez, le profil mis à jour sera envoyé à tous vos contacts. alert message - - Your profile, contacts and delivered messages are stored on your device. - Votre profil, vos contacts et les messages reçus sont stockés sur votre appareil. - No comment provided by engineer. - Your random profile Votre profil aléatoire diff --git a/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff b/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff index 08f46bb056..f76d7eba1e 100644 --- a/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff +++ b/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff @@ -3569,8 +3569,8 @@ It can happen because of some bug or when the connection is compromised.The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff b/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff index bdb3083f5a..6ad4d159c7 100644 --- a/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff +++ b/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff @@ -2619,8 +2619,8 @@ We will be adding server redundancy to prevent lost messages. The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 9be5879eb6..78bee138e4 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -7763,11 +7763,6 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. A régi adatbázis nem lett eltávolítva az átköltöztetéskor, ezért törölhető. No comment provided by engineer. - - The profile is only shared with your contacts. - A profilja csak a partnereivel van megosztva. - No comment provided by engineer. - The same conditions will apply to operator **%@**. Ugyanezek a feltételek lesznek elfogadva a következő üzemeltető számára is: **%@**. @@ -9081,6 +9076,11 @@ Megismétli a meghívási kérést? A(z) **%@** nevű profilja meg lesz osztva. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + A profilja csak a partnereivel van megosztva. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. A profilja az eszközén van tárolva és csak a partnereivel van megosztva. A SimpleX-kiszolgálók nem láthatják a profilját. @@ -9091,11 +9091,6 @@ Megismétli a meghívási kérést? A profilja módosult. Ha elmenti, a profilfrissítés el lesz küldve a partnerei számára. alert message - - Your profile, contacts and delivered messages are stored on your device. - A profilja, a partnerei és az elküldött üzenetei a saját eszközén vannak tárolva. - No comment provided by engineer. - Your random profile Véletlenszerű profil diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 0c0c12004e..cf5f61918f 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -7763,11 +7763,6 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Il database vecchio non è stato rimosso durante la migrazione, può essere eliminato. No comment provided by engineer. - - The profile is only shared with your contacts. - Il profilo è condiviso solo con i tuoi contatti. - No comment provided by engineer. - The same conditions will apply to operator **%@**. Le stesse condizioni si applicheranno all'operatore **%@**. @@ -9081,6 +9076,11 @@ Ripetere la richiesta di connessione? Verrà condiviso il tuo profilo **%@**. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + Il profilo è condiviso solo con i tuoi contatti. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Il tuo profilo è memorizzato sul tuo dispositivo e condiviso solo con i tuoi contatti. I server di SimpleX non possono vedere il tuo profilo. @@ -9091,11 +9091,6 @@ Ripetere la richiesta di connessione? Il tuo profilo è stato cambiato. Se lo salvi, il profilo aggiornato verrà inviato a tutti i tuoi contatti. alert message - - Your profile, contacts and delivered messages are stored on your device. - Il tuo profilo, i contatti e i messaggi recapitati sono memorizzati sul tuo dispositivo. - No comment provided by engineer. - Your random profile Il tuo profilo casuale diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 26f415dd13..27134216a7 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -1990,6 +1990,7 @@ This is your own one-time link! Create profile + プロフィールを作成する No comment provided by engineer. @@ -7125,11 +7126,6 @@ It can happen because of some bug or when the connection is compromised.古いデータベースは移行時に削除されなかったので、削除することができます。 No comment provided by engineer. - - The profile is only shared with your contacts. - プロフィールは連絡先にしか共有されません。 - No comment provided by engineer. - The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -8315,6 +8311,11 @@ Repeat connection request? あなたのプロファイル **%@** が共有されます。 No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + プロフィールは連絡先にしか共有されません。 + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. プロフィールはデバイスに保存され、連絡先とのみ共有されます。 SimpleX サーバーはあなたのプロファイルを参照できません。 @@ -8324,11 +8325,6 @@ Repeat connection request? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message - - Your profile, contacts and delivered messages are stored on your device. - あなたのプロフィール、連絡先、送信したメッセージがご自分の端末に保存されます。 - No comment provided by engineer. - Your random profile あなたのランダム・プロフィール diff --git a/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff b/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff index e35732f046..019f63cbc0 100644 --- a/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff +++ b/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff @@ -2867,8 +2867,8 @@ We will be adding server redundancy to prevent lost messages. The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff b/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff index 54a713478f..0f795170c6 100644 --- a/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff +++ b/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff @@ -2631,8 +2631,8 @@ We will be adding server redundancy to prevent lost messages. The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 681502e255..4008c57ac0 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -7760,11 +7760,6 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. De oude database is niet verwijderd tijdens de migratie, deze kan worden verwijderd. No comment provided by engineer. - - The profile is only shared with your contacts. - Het profiel wordt alleen gedeeld met uw contacten. - No comment provided by engineer. - The same conditions will apply to operator **%@**. Dezelfde voorwaarden gelden voor operator **%@**. @@ -9074,6 +9069,11 @@ Verbindingsverzoek herhalen? Uw profiel **%@** wordt gedeeld. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + Het profiel wordt alleen gedeeld met uw contacten. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Uw profiel wordt op uw apparaat opgeslagen en alleen gedeeld met uw contacten. SimpleX servers kunnen uw profiel niet zien. @@ -9084,11 +9084,6 @@ Verbindingsverzoek herhalen? Je profiel is gewijzigd. Als je het opslaat, wordt het bijgewerkte profiel naar al je contacten verzonden. alert message - - Your profile, contacts and delivered messages are stored on your device. - Uw profiel, contacten en afgeleverde berichten worden op uw apparaat opgeslagen. - No comment provided by engineer. - Your random profile Je willekeurige profiel diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 01bc0b8508..175c8b4112 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -7583,11 +7583,6 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Stara baza danych nie została usunięta podczas migracji, można ją usunąć. No comment provided by engineer. - - The profile is only shared with your contacts. - Profil jest udostępniany tylko Twoim kontaktom. - No comment provided by engineer. - The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -8870,6 +8865,11 @@ Powtórzyć prośbę połączenia? Twój profil **%@** zostanie udostępniony. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + Profil jest udostępniany tylko Twoim kontaktom. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Twój profil jest przechowywany na urządzeniu i udostępniany tylko Twoim kontaktom. Serwery SimpleX nie mogą zobaczyć Twojego profilu. @@ -8880,11 +8880,6 @@ Powtórzyć prośbę połączenia? Twój profil został zmieniony. Jeśli go zapiszesz, zaktualizowany profil zostanie wysłany do wszystkich kontaktów. alert message - - Your profile, contacts and delivered messages are stored on your device. - Twój profil, kontakty i dostarczone wiadomości są przechowywane na Twoim urządzeniu. - No comment provided by engineer. - Your random profile Twój losowy profil diff --git a/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff b/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff index 93ba6f357b..bbb6c7d22a 100644 --- a/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff +++ b/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff @@ -3002,8 +3002,8 @@ We will be adding server redundancy to prevent lost messages. The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. O perfil é compartilhado apenas com seus contatos. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff b/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff index de1787bdad..bc8bf79da1 100644 --- a/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff +++ b/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff @@ -3146,8 +3146,8 @@ It can happen because of some bug or when the connection is compromised.The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index a7b63e38ba..419fa75375 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -7715,11 +7715,6 @@ It can happen because of some bug or when the connection is compromised.Предыдущая версия данных чата не удалена при перемещении, её можно удалить. No comment provided by engineer. - - The profile is only shared with your contacts. - Профиль отправляется только Вашим контактам. - No comment provided by engineer. - The same conditions will apply to operator **%@**. Те же самые условия будут приняты для оператора **%@**. @@ -9021,6 +9016,11 @@ Repeat connection request? Будет отправлен Ваш профиль **%@**. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + Ваш профиль храниться на Вашем устройстве и отправляется только контактам. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Ваш профиль хранится на Вашем устройстве и отправляется только Вашим контактам. SimpleX серверы не могут получить доступ к Вашему профилю. @@ -9031,11 +9031,6 @@ Repeat connection request? Ваш профиль был изменен. Если вы сохраните его, обновленный профиль будет отправлен всем вашим контактам. alert message - - Your profile, contacts and delivered messages are stored on your device. - Ваш профиль, контакты и доставленные сообщения хранятся на Вашем устройстве. - No comment provided by engineer. - Your random profile Случайный профиль diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index be68dc9780..671dd87d7d 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -7028,11 +7028,6 @@ It can happen because of some bug or when the connection is compromised.ฐานข้อมูลเก่าไม่ได้ถูกลบในระหว่างการย้ายข้อมูล แต่สามารถลบได้ No comment provided by engineer. - - The profile is only shared with your contacts. - โปรไฟล์นี้แชร์กับผู้ติดต่อของคุณเท่านั้น - No comment provided by engineer. - The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -8212,6 +8207,11 @@ Repeat connection request? Your profile **%@** will be shared. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + โปรไฟล์นี้แชร์กับผู้ติดต่อของคุณเท่านั้น + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. โปรไฟล์ของคุณจะถูกจัดเก็บไว้ในอุปกรณ์ของคุณและแชร์กับผู้ติดต่อของคุณเท่านั้น เซิร์ฟเวอร์ SimpleX ไม่สามารถดูโปรไฟล์ของคุณได้ @@ -8221,11 +8221,6 @@ Repeat connection request? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message - - Your profile, contacts and delivered messages are stored on your device. - โปรไฟล์ รายชื่อผู้ติดต่อ และข้อความที่ส่งของคุณจะถูกจัดเก็บไว้ในอุปกรณ์ของคุณ - No comment provided by engineer. - Your random profile โปรไฟล์แบบสุ่มของคุณ diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index 6eb1daf84b..bbee40c2b9 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -7599,11 +7599,6 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Eski veritabanı geçiş sırasında kaldırılmadı, silinebilir. No comment provided by engineer. - - The profile is only shared with your contacts. - Profil sadece kişilerinle paylaşılacak. - No comment provided by engineer. - The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -8886,6 +8881,11 @@ Bağlantı isteği tekrarlansın mı? Profiliniz **%@** paylaşılacaktır. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + Profil sadece kişilerinle paylaşılacak. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Profiliniz cihazınızda saklanır ve sadece kişilerinizle paylaşılır. SimpleX sunucuları profilinizi göremez. @@ -8896,11 +8896,6 @@ Bağlantı isteği tekrarlansın mı? Profiliniz değiştirildi. Kaydederseniz, güncellenmiş profil tüm kişilerinize gönderilecektir. alert message - - Your profile, contacts and delivered messages are stored on your device. - Profiliniz, kişileriniz ve gönderilmiş mesajlar cihazınızda saklanır. - No comment provided by engineer. - Your random profile Rasgele profiliniz diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 7c8c6f4254..c0375e3b02 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -7638,11 +7638,6 @@ It can happen because of some bug or when the connection is compromised.Стара база даних не була видалена під час міграції, її можна видалити. No comment provided by engineer. - - The profile is only shared with your contacts. - Профіль доступний лише вашим контактам. - No comment provided by engineer. - The same conditions will apply to operator **%@**. Такі ж умови діятимуть і для оператора **%@**. @@ -8945,6 +8940,11 @@ Repeat connection request? Ваш профіль **%@** буде опублікований. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + Профіль доступний лише вашим контактам. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Ваш профіль зберігається на вашому пристрої і доступний лише вашим контактам. Сервери SimpleX не бачать ваш профіль. @@ -8955,11 +8955,6 @@ Repeat connection request? Ваш профіль було змінено. Якщо ви збережете його, оновлений профіль буде надіслано всім вашим контактам. alert message - - Your profile, contacts and delivered messages are stored on your device. - Ваш профіль, контакти та доставлені повідомлення зберігаються на вашому пристрої. - No comment provided by engineer. - Your random profile Ваш випадковий профіль diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index 03e053326a..d5411f86e3 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -7708,11 +7708,6 @@ It can happen because of some bug or when the connection is compromised.旧数据库在迁移过程中没有被移除,可以删除。 No comment provided by engineer. - - The profile is only shared with your contacts. - 该资料仅与您的联系人共享。 - No comment provided by engineer. - The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -8987,6 +8982,11 @@ Repeat connection request? 您的个人资料 **%@** 将被共享。 No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + 该资料仅与您的联系人共享。 + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. 您的资料存储在您的设备上并仅与您的联系人共享。 SimpleX 服务器无法看到您的资料。 @@ -8996,11 +8996,6 @@ Repeat connection request? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message - - Your profile, contacts and delivered messages are stored on your device. - 您的资料、联系人和发送的消息存储在您的设备上。 - No comment provided by engineer. - Your random profile 您的随机资料 diff --git a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff index 8a771369e6..3ea46ee364 100644 --- a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff @@ -3054,8 +3054,8 @@ We will be adding server redundancy to prevent lost messages. 舊的數據庫在遷移過程中沒有被移除,可以刪除。 No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. 你的個人檔案只會和你的聯絡人分享。 No comment provided by engineer. diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index f241158185..e4bc8f2150 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -3777,7 +3777,7 @@ chat item action */ "The old database was not removed during the migration, it can be deleted." = "Старата база данни не бе премахната по време на миграцията, тя може да бъде изтрита."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Профилът се споделя само с вашите контакти."; +"Your profile is stored on your device and only shared with your contacts." = "Профилът се споделя само с вашите контакти."; /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Втората отметка, която пропуснахме! ✅"; diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index 003ac23325..08a94615a3 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -822,6 +822,9 @@ set passcode view */ /* 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 profile" = "Vytvořte si profil"; + /* server test step */ "Create queue" = "Vytvořit frontu"; @@ -2986,7 +2989,7 @@ chat item action */ "The old database was not removed during the migration, it can be deleted." = "Stará databáze nebyla během přenášení odstraněna, lze ji smazat."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Profil je sdílen pouze s vašimi kontakty."; +"Your profile is stored on your device and only shared with your contacts." = "Profil je sdílen pouze s vašimi kontakty."; /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Druhé zaškrtnutí jsme přehlédli! ✅"; diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 0eab764216..8da7835c43 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -5101,7 +5101,7 @@ report reason */ "The old database was not removed during the migration, it can be deleted." = "Die alte Datenbank wurde während der Migration nicht entfernt. Sie kann gelöscht werden."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Das Profil wird nur mit Ihren Kontakten geteilt."; +"Your profile is stored on your device and only shared with your contacts." = "Das Profil wird nur mit Ihren Kontakten geteilt."; /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Dieselben Nutzungsbedingungen gelten auch für den Betreiber **%@**."; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index e797b73b98..28ba0f0642 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -5101,7 +5101,7 @@ report reason */ "The old database was not removed during the migration, it can be deleted." = "La base de datos antigua no se eliminó durante la migración, puede eliminarse."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "El perfil sólo se comparte con tus contactos."; +"Your profile is stored on your device and only shared with your contacts." = "El perfil sólo se comparte con tus contactos."; /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Las mismas condiciones se aplicarán al operador **%@**."; diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index c4031adf9a..4891c7fb26 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -768,6 +768,9 @@ set passcode view */ /* No comment provided by engineer. */ "Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" = "Luo uusi profiili [työpöytäsovelluksessa](https://simplex.chat/downloads/). 💻"; +/* No comment provided by engineer. */ +"Create profile" = "Luo profiilisi"; + /* server test step */ "Create queue" = "Luo jono"; @@ -2908,7 +2911,7 @@ chat item action */ "The old database was not removed during the migration, it can be deleted." = "Vanhaa tietokantaa ei poistettu siirron aikana, se voidaan kuitenkin poistaa."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Profiili jaetaan vain kontaktiesi kanssa."; +"Your profile is stored on your device and only shared with your contacts." = "Profiili jaetaan vain kontaktiesi kanssa."; /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Toinen kuittaus, joka uupui! ✅"; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index 1c16f8847d..4dd75039dc 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -4884,7 +4884,7 @@ chat item action */ "The old database was not removed during the migration, it can be deleted." = "L'ancienne base de données n'a pas été supprimée lors de la migration, elle peut être supprimée."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Le profil n'est partagé qu'avec vos contacts."; +"Your profile is stored on your device and only shared with your contacts." = "Le profil n'est partagé qu'avec vos contacts."; /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Les mêmes conditions s'appliquent à l'opérateur **%@**."; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index c190cccaba..5a9b6b4e38 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -5101,7 +5101,7 @@ report reason */ "The old database was not removed during the migration, it can be deleted." = "A régi adatbázis nem lett eltávolítva az átköltöztetéskor, ezért törölhető."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "A profilja csak a partnereivel van megosztva."; +"Your profile is stored on your device and only shared with your contacts." = "A profilja csak a partnereivel van megosztva."; /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Ugyanezek a feltételek lesznek elfogadva a következő üzemeltető számára is: **%@**."; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index f67a492cc4..b914a06079 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -5101,7 +5101,7 @@ report reason */ "The old database was not removed during the migration, it can be deleted." = "Il database vecchio non è stato rimosso durante la migrazione, può essere eliminato."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Il profilo è condiviso solo con i tuoi contatti."; +"Your profile is stored on your device and only shared with your contacts." = "Il profilo è condiviso solo con i tuoi contatti."; /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Le stesse condizioni si applicheranno all'operatore **%@**."; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index 9d0cccf591..d214f88e1c 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -957,6 +957,9 @@ set passcode view */ /* No comment provided by engineer. */ "Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" = "[デスクトップアプリ](https://simplex.chat/downloads/)で新しいプロファイルを作成します。 💻"; +/* No comment provided by engineer. */ +"Create profile" = "プロフィールを作成する"; + /* server test step */ "Create queue" = "キューの作成"; @@ -3109,7 +3112,7 @@ chat item action */ "The old database was not removed during the migration, it can be deleted." = "古いデータベースは移行時に削除されなかったので、削除することができます。"; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "プロフィールは連絡先にしか共有されません。"; +"Your profile is stored on your device and only shared with your contacts." = "プロフィールは連絡先にしか共有されません。"; /* No comment provided by engineer. */ "The second tick we missed! ✅" = "長らくお待たせしました! ✅"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index d2cfcba0de..232de56641 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -5092,7 +5092,7 @@ report reason */ "The old database was not removed during the migration, it can be deleted." = "De oude database is niet verwijderd tijdens de migratie, deze kan worden verwijderd."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Het profiel wordt alleen gedeeld met uw contacten."; +"Your profile is stored on your device and only shared with your contacts." = "Het profiel wordt alleen gedeeld met uw contacten."; /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Dezelfde voorwaarden gelden voor operator **%@**."; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index 867f3beff4..31a9b87662 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -4557,7 +4557,7 @@ chat item action */ "The old database was not removed during the migration, it can be deleted." = "Stara baza danych nie została usunięta podczas migracji, można ją usunąć."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Profil jest udostępniany tylko Twoim kontaktom."; +"Your profile is stored on your device and only shared with your contacts." = "Profil jest udostępniany tylko Twoim kontaktom."; /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Drugi tik, który przegapiliśmy! ✅"; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index b819d013b9..cb837836ff 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -4957,7 +4957,7 @@ report reason */ "The old database was not removed during the migration, it can be deleted." = "Предыдущая версия данных чата не удалена при перемещении, её можно удалить."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Профиль отправляется только Вашим контактам."; +"Your profile is stored on your device and only shared with your contacts." = "Ваш профиль храниться на Вашем устройстве и отправляется только контактам."; /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Те же самые условия будут приняты для оператора **%@**."; diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index 6b3381922a..57c0466eb9 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -2830,7 +2830,7 @@ chat item action */ "The old database was not removed during the migration, it can be deleted." = "ฐานข้อมูลเก่าไม่ได้ถูกลบในระหว่างการย้ายข้อมูล แต่สามารถลบได้"; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "โปรไฟล์นี้แชร์กับผู้ติดต่อของคุณเท่านั้น"; +"Your profile is stored on your device and only shared with your contacts." = "โปรไฟล์นี้แชร์กับผู้ติดต่อของคุณเท่านั้น"; /* No comment provided by engineer. */ "The second tick we missed! ✅" = "ขีดที่สองที่เราพลาด! ✅"; diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index ab0703333e..e3bb11d1cc 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -4602,7 +4602,7 @@ chat item action */ "The old database was not removed during the migration, it can be deleted." = "Eski veritabanı geçiş sırasında kaldırılmadı, silinebilir."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Profil sadece kişilerinle paylaşılacak."; +"Your profile is stored on your device and only shared with your contacts." = "Profil sadece kişilerinle paylaşılacak."; /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Özlediğimiz ikinci tik! ✅"; diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index 8e2b514ed4..734b8dda82 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -4722,7 +4722,7 @@ chat item action */ "The old database was not removed during the migration, it can be deleted." = "Стара база даних не була видалена під час міграції, її можна видалити."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Профіль доступний лише вашим контактам."; +"Your profile is stored on your device and only shared with your contacts." = "Профіль доступний лише вашим контактам."; /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Такі ж умови діятимуть і для оператора **%@**."; diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index 6ceeeb22d0..e3f9669d9f 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -4923,7 +4923,7 @@ chat item action */ "The old database was not removed during the migration, it can be deleted." = "旧数据库在迁移过程中没有被移除,可以删除。"; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "该资料仅与您的联系人共享。"; +"Your profile is stored on your device and only shared with your contacts." = "该资料仅与您的联系人共享。"; /* No comment provided by engineer. */ "The second tick we missed! ✅" = "我们错过的第二个\"√\"!✅"; From 26e5742354f2d9dd87b5c0cf3a4cf8227eec4813 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 15 May 2025 14:58:40 +0100 Subject: [PATCH 556/567] ios: fix swipe in members list for iOS 15 (#5914) * ios: fix swipe in members list for iOS 15 * refactor --- .../Views/Chat/Group/GroupChatInfoView.swift | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index 96a4981be0..15749b0761 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -144,17 +144,9 @@ struct GroupChatInfoView: View { let filteredMembers = s == "" ? members : members.filter { $0.wrapped.localAliasAndFullName.localizedLowercase.contains(s) } - MemberRowView(groupInfo: groupInfo, groupMember: GMember(groupInfo.membership), user: true, alert: $alert) + MemberRowView(chat: chat, groupInfo: groupInfo, groupMember: GMember(groupInfo.membership), user: true, alert: $alert) ForEach(filteredMembers) { member in - ZStack { - NavigationLink { - memberInfoView(member) - } label: { - EmptyView() - } - .opacity(0) - MemberRowView(groupInfo: groupInfo, groupMember: member, alert: $alert) - } + MemberRowView(chat: chat, groupInfo: groupInfo, groupMember: member, alert: $alert) } } @@ -358,6 +350,7 @@ struct GroupChatInfoView: View { } private struct MemberRowView: View { + var chat: Chat var groupInfo: GroupInfo @ObservedObject var groupMember: GMember @EnvironmentObject var theme: AppTheme @@ -366,7 +359,7 @@ struct GroupChatInfoView: View { var body: some View { let member = groupMember.wrapped - let v = HStack{ + let v1 = HStack{ MemberProfileImage(member, size: 38) .padding(.trailing, 2) // TODO server connection status @@ -383,6 +376,20 @@ struct GroupChatInfoView: View { memberInfo(member) } + let v = ZStack { + if user { + v1 + } else { + NavigationLink { + memberInfoView() + } label: { + EmptyView() + } + .opacity(0) + v1 + } + } + if user { v } else if groupInfo.membership.memberRole >= .admin { @@ -407,6 +414,11 @@ struct GroupChatInfoView: View { } } + private func memberInfoView() -> some View { + GroupMemberInfoView(groupInfo: groupInfo, chat: chat, groupMember: groupMember) + .navigationBarHidden(false) + } + private func memberConnStatus(_ member: GroupMember) -> LocalizedStringKey { if member.activeConn?.connDisabled ?? false { return "disabled" @@ -485,11 +497,6 @@ struct GroupChatInfoView: View { .foregroundColor(theme.colors.secondary) } } - - private func memberInfoView(_ groupMember: GMember) -> some View { - GroupMemberInfoView(groupInfo: groupInfo, chat: chat, groupMember: groupMember) - .navigationBarHidden(false) - } private func groupLinkButton() -> some View { NavigationLink { From 7b362ff655959a790815931e4db44ced52a9212c Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 19 May 2025 15:50:33 +0100 Subject: [PATCH 557/567] ui: label in compose when user cannot send messages (#5922) * ui: label in compose when user cannot send messages * gray buttons when user cannot send messages * improve * kotlin * fix order * fix alert --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --- apps/ios/Shared/Model/ChatModel.swift | 21 ------- apps/ios/Shared/Views/Chat/ChatView.swift | 12 +++- .../Chat/ComposeMessage/ComposeView.swift | 19 +++--- .../Chat/ComposeMessage/SendMessageView.swift | 8 ++- apps/ios/SimpleXChat/ChatTypes.swift | 50 ++++++++++++--- .../platform/PlatformTextField.android.kt | 13 ++-- .../chat/simplex/common/model/ChatModel.kt | 62 ++++++++++++------- .../common/platform/PlatformTextField.kt | 2 +- .../chat/simplex/common/views/TerminalView.kt | 3 +- .../simplex/common/views/chat/ChatView.kt | 2 +- .../simplex/common/views/chat/ComposeView.kt | 11 ++-- .../simplex/common/views/chat/SendMsgView.kt | 62 +++++++++---------- .../commonMain/resources/MR/base/strings.xml | 15 ++++- .../platform/PlatformTextField.desktop.kt | 12 ++-- 14 files changed, 164 insertions(+), 128 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 63d8b38e3c..9b9fda0397 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -1152,27 +1152,6 @@ final class Chat: ObservableObject, Identifiable, ChatLike { ) } - var userCanSend: Bool { - switch chatInfo { - case .direct: return true - case let .group(groupInfo): - let m = groupInfo.membership - return m.memberActive && m.memberRole >= .member - case .local: - return true - default: return false - } - } - - var userIsObserver: Bool { - switch chatInfo { - case let .group(groupInfo): - let m = groupInfo.membership - return m.memberActive && m.memberRole == .observer - default: return false - } - } - var unreadTag: Bool { switch chatInfo.chatSettings?.enableNtfs { case .all: chatStats.unreadChat || chatStats.unreadCount > 0 diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 9e648ef98c..c136ebc01b 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -98,14 +98,24 @@ struct ChatView: View { } connectingText() if selectedChatItems == nil { + let reason = chat.chatInfo.userCantSendReason ComposeView( chat: chat, composeState: $composeState, keyboardVisible: $keyboardVisible, keyboardHiddenDate: $keyboardHiddenDate, - selectedRange: $selectedRange + selectedRange: $selectedRange, + disabledText: reason?.composeLabel ) .disabled(!cInfo.sendMsgEnabled) + .if(!cInfo.sendMsgEnabled) { v in + v.disabled(true).onTapGesture { + AlertManager.shared.showAlertMsg( + title: "You can't send messages!", + message: reason?.alertMessage + ) + } + } } else { SelectedItemsBottomToolbar( chatItems: ItemsModel.shared.reversedChatItems, diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index 3e9c340266..8993de886f 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -327,6 +327,7 @@ struct ComposeView: View { @Binding var keyboardVisible: Bool @Binding var keyboardHiddenDate: Date @Binding var selectedRange: NSRange + var disabledText: LocalizedStringKey? = nil @State var linkUrl: URL? = nil @State var hasSimplexLink: Bool = false @@ -391,7 +392,7 @@ struct ComposeView: View { Image(systemName: "paperclip") .resizable() } - .disabled(composeState.attachmentDisabled || !chat.userCanSend || (chat.chatInfo.contact?.nextSendGrpInv ?? false)) + .disabled(composeState.attachmentDisabled || !chat.chatInfo.sendMsgEnabled || (chat.chatInfo.contact?.nextSendGrpInv ?? false)) .frame(width: 25, height: 25) .padding(.bottom, 16) .padding(.leading, 12) @@ -441,19 +442,13 @@ struct ComposeView: View { : theme.colors.primary ) .padding(.trailing, 12) - .disabled(!chat.userCanSend) + .disabled(!chat.chatInfo.sendMsgEnabled) - if chat.userIsObserver { - Text("you are observer") + if let disabledText { + Text(disabledText) .italic() .foregroundColor(theme.colors.secondary) .padding(.horizontal, 12) - .onTapGesture { - AlertManager.shared.showAlertMsg( - title: "You can't send messages!", - message: "Please contact group admin." - ) - } } } } @@ -479,8 +474,8 @@ struct ComposeView: View { hasSimplexLink = false } } - .onChange(of: chat.userCanSend) { canSend in - if !canSend { + .onChange(of: chat.chatInfo.sendMsgEnabled) { sendEnabled in + if !sendEnabled { cancelCurrentVoiceRecording() clearCurrentDraft() clearState() diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift index d7b29a0ecb..e7b02c9aea 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift @@ -15,6 +15,7 @@ struct SendMessageView: View { @Binding var composeState: ComposeState @Binding var selectedRange: NSRange @EnvironmentObject var theme: AppTheme + @Environment(\.isEnabled) var isEnabled var sendMessage: (Int?) -> Void var sendLiveMessage: (() async -> Void)? = nil var updateLiveMessage: (() async -> Void)? = nil @@ -255,6 +256,7 @@ struct SendMessageView: View { } private struct RecordVoiceMessageButton: View { + @Environment(\.isEnabled) var isEnabled @EnvironmentObject var theme: AppTheme var startVoiceMessageRecording: (() -> Void)? var finishVoiceMessageRecording: (() -> Void)? @@ -263,11 +265,11 @@ struct SendMessageView: View { @State private var pressed: TimeInterval? = nil var body: some View { - Image(systemName: "mic.fill") + Image(systemName: isEnabled ? "mic.fill" : "mic") .resizable() .scaledToFit() .frame(width: 20, height: 20) - .foregroundColor(theme.colors.primary) + .foregroundColor(isEnabled ? theme.colors.primary : theme.colors.secondary) .opacity(holdingVMR ? 0.7 : 1) .disabled(disabled) .frame(width: 31, height: 31) @@ -352,7 +354,7 @@ struct SendMessageView: View { Image(systemName: "bolt.fill") .resizable() .scaledToFit() - .foregroundColor(theme.colors.primary) + .foregroundColor(isEnabled ? theme.colors.primary : theme.colors.secondary) .frame(width: 20, height: 20) } .frame(width: 29, height: 29) diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 960fdd466d..88246465e1 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -1333,6 +1333,19 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { } } + public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { + get { + switch self { + case let .direct(contact): return contact.userCantSendReason + case let .group(groupInfo): return groupInfo.userCantSendReason + case let .local(noteFolder): return noteFolder.userCantSendReason + case let .contactRequest(contactRequest): return contactRequest.userCantSendReason + case let .contactConnection(contactConnection): return contactConnection.userCantSendReason + case .invalidJSON: return ("can't send messages", nil) + } + } + } + public var sendMsgEnabled: Bool { get { switch self { @@ -1642,15 +1655,16 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable { public var ready: Bool { get { activeConn?.connStatus == .ready } } public var sndReady: Bool { get { ready || activeConn?.connStatus == .sndReady } } public var active: Bool { get { contactStatus == .active } } - public var sendMsgEnabled: Bool { get { - ( - sndReady - && active - && !(activeConn?.connectionStats?.ratchetSyncSendProhibited ?? false) - && !(activeConn?.connDisabled ?? true) - ) - || nextSendGrpInv - } } + public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { + // TODO [short links] this will have additional statuses for pending contact requests before they are accepted + if nextSendGrpInv { return nil } + if !active { return ("contact deleted", nil) } + if !sndReady { return ("contact not ready", nil) } + if activeConn?.connectionStats?.ratchetSyncSendProhibited ?? false { return ("not synchronized", nil) } + if activeConn?.connDisabled ?? true { return ("contact disabled", nil) } + return nil + } + public var sendMsgEnabled: Bool { userCantSendReason == nil } public var nextSendGrpInv: Bool { get { contactGroupMemberId != nil && !contactGrpInvSent } } public var displayName: String { localAlias == "" ? profile.displayName : localAlias } public var fullName: String { get { profile.fullName } } @@ -1829,6 +1843,7 @@ public struct UserContactRequest: Decodable, NamedChat, Hashable { public var id: ChatId { get { "<@\(contactRequestId)" } } public var apiId: Int64 { get { contactRequestId } } var ready: Bool { get { true } } + public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { ("can't send messages", nil) } public var sendMsgEnabled: Bool { get { false } } public var displayName: String { get { profile.displayName } } public var fullName: String { get { profile.fullName } } @@ -1861,6 +1876,7 @@ public struct PendingContactConnection: Decodable, NamedChat, Hashable { public var id: ChatId { get { ":\(pccConnId)" } } public var apiId: Int64 { get { pccConnId } } var ready: Bool { get { false } } + public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { ("can't send messages", nil) } public var sendMsgEnabled: Bool { get { false } } var localDisplayName: String { get { String.localizedStringWithFormat(NSLocalizedString("connection:%@", comment: "connection information"), pccConnId) } @@ -1990,7 +2006,20 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable { public var id: ChatId { get { "#\(groupId)" } } public var apiId: Int64 { get { groupId } } public var ready: Bool { get { true } } - public var sendMsgEnabled: Bool { get { membership.memberActive } } + public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { + return if membership.memberActive { + membership.memberRole == .observer ? ("you are observer", "Please contact group admin.") : nil + } else { + switch membership.memberStatus { + case .memRejected: ("request to join rejected", nil) + case .memGroupDeleted: ("group is deleted", nil) + case .memRemoved: ("removed from group", nil) + case .memLeft: ("you left", nil) + default: ("can't send messages", nil) + } + } + } + public var sendMsgEnabled: Bool { userCantSendReason == nil } public var displayName: String { localAlias == "" ? groupProfile.displayName : localAlias } public var fullName: String { get { groupProfile.fullName } } public var image: String? { get { groupProfile.image } } @@ -2357,6 +2386,7 @@ public struct NoteFolder: Identifiable, Decodable, NamedChat, Hashable { public var id: ChatId { get { "*\(noteFolderId)" } } public var apiId: Int64 { get { noteFolderId } } public var ready: Bool { get { true } } + public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { nil } public var sendMsgEnabled: Bool { get { true } } public var displayName: String { get { ChatInfo.privateNotesChatName } } public var fullName: String { get { "" } } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt index 54e437afb1..4f48ccca52 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt @@ -42,7 +42,6 @@ import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.filter import java.lang.reflect.Field import java.net.URI @@ -51,10 +50,10 @@ import java.net.URI actual fun PlatformTextField( composeState: MutableState, sendMsgEnabled: Boolean, + disabledText: String?, sendMsgButtonDisabled: Boolean, textStyle: MutableState, showDeleteTextButton: MutableState, - userIsObserver: Boolean, placeholder: String, showVoiceButton: Boolean, onMessageChange: (ComposeMessage) -> Unit, @@ -197,16 +196,16 @@ actual fun PlatformTextField( showDeleteTextButton.value = it.lineCount >= 4 && !cs.inProgress } if (composeState.value.preview is ComposePreview.VoicePreview) { - ComposeOverlay(MR.strings.voice_message_send_text, textStyle, padding) - } else if (userIsObserver) { - ComposeOverlay(MR.strings.you_are_observer, textStyle, padding) + ComposeOverlay(generalGetString(MR.strings.voice_message_send_text), textStyle, padding) + } else if (disabledText != null) { + ComposeOverlay(disabledText, textStyle, padding) } } @Composable -private fun ComposeOverlay(textId: StringResource, textStyle: MutableState, padding: PaddingValues) { +private fun ComposeOverlay(text: String, textStyle: MutableState, padding: PaddingValues) { Text( - generalGetString(textId), + text, Modifier.padding(padding), color = MaterialTheme.colors.secondary, style = textStyle.value.copy(fontStyle = FontStyle.Italic) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 6ee609020a..61c20587bf 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -1204,6 +1204,7 @@ interface SomeChat { val apiId: Long val ready: Boolean val chatDeleted: Boolean + val userCantSendReason: Pair? val sendMsgEnabled: Boolean val incognito: Boolean fun featureEnabled(feature: ChatFeature): Boolean @@ -1228,14 +1229,6 @@ data class Chat( else -> false } - val userIsObserver: Boolean get() = when(chatInfo) { - is ChatInfo.Group -> { - val m = chatInfo.groupInfo.membership - m.memberActive && m.memberRole == GroupMemberRole.Observer - } - else -> false - } - val unreadTag: Boolean get() = when (chatInfo.chatSettings?.enableNtfs) { All -> chatStats.unreadChat || chatStats.unreadCount > 0 Mentions -> chatStats.unreadChat || chatStats.unreadMentions > 0 @@ -1282,6 +1275,7 @@ sealed class ChatInfo: SomeChat, NamedChat { override val apiId get() = contact.apiId override val ready get() = contact.ready override val chatDeleted get() = contact.chatDeleted + override val userCantSendReason get() = contact.userCantSendReason override val sendMsgEnabled get() = contact.sendMsgEnabled override val incognito get() = contact.incognito override fun featureEnabled(feature: ChatFeature) = contact.featureEnabled(feature) @@ -1307,6 +1301,7 @@ sealed class ChatInfo: SomeChat, NamedChat { override val apiId get() = groupInfo.apiId override val ready get() = groupInfo.ready override val chatDeleted get() = groupInfo.chatDeleted + override val userCantSendReason get() = groupInfo.userCantSendReason override val sendMsgEnabled get() = groupInfo.sendMsgEnabled override val incognito get() = groupInfo.incognito override fun featureEnabled(feature: ChatFeature) = groupInfo.featureEnabled(feature) @@ -1331,6 +1326,7 @@ sealed class ChatInfo: SomeChat, NamedChat { override val apiId get() = noteFolder.apiId override val ready get() = noteFolder.ready override val chatDeleted get() = noteFolder.chatDeleted + override val userCantSendReason get() = noteFolder.userCantSendReason override val sendMsgEnabled get() = noteFolder.sendMsgEnabled override val incognito get() = noteFolder.incognito override fun featureEnabled(feature: ChatFeature) = noteFolder.featureEnabled(feature) @@ -1355,6 +1351,7 @@ sealed class ChatInfo: SomeChat, NamedChat { override val apiId get() = contactRequest.apiId override val ready get() = contactRequest.ready override val chatDeleted get() = contactRequest.chatDeleted + override val userCantSendReason get() = contactRequest.userCantSendReason override val sendMsgEnabled get() = contactRequest.sendMsgEnabled override val incognito get() = contactRequest.incognito override fun featureEnabled(feature: ChatFeature) = contactRequest.featureEnabled(feature) @@ -1379,6 +1376,7 @@ sealed class ChatInfo: SomeChat, NamedChat { override val apiId get() = contactConnection.apiId override val ready get() = contactConnection.ready override val chatDeleted get() = contactConnection.chatDeleted + override val userCantSendReason get() = contactConnection.userCantSendReason override val sendMsgEnabled get() = contactConnection.sendMsgEnabled override val incognito get() = contactConnection.incognito override fun featureEnabled(feature: ChatFeature) = contactConnection.featureEnabled(feature) @@ -1408,6 +1406,7 @@ sealed class ChatInfo: SomeChat, NamedChat { override val id get() = "?$apiId" override val ready get() = false override val chatDeleted get() = false + override val userCantSendReason get() = generalGetString(MR.strings.cant_send_message_generic) to null override val sendMsgEnabled get() = false override val incognito get() = false override fun featureEnabled(feature: ChatFeature) = false @@ -1450,14 +1449,6 @@ sealed class ChatInfo: SomeChat, NamedChat { is InvalidJSON -> updatedAt } - val userCanSend: Boolean - get() = when (this) { - is ChatInfo.Direct -> true - is ChatInfo.Group -> groupInfo.membership.memberRole >= GroupMemberRole.Member - is ChatInfo.Local -> true - else -> false - } - val chatTags: List? get() = when (this) { is Direct -> contact.chatTags @@ -1528,13 +1519,17 @@ data class Contact( override val ready get() = activeConn?.connStatus == ConnStatus.Ready val sndReady get() = ready || activeConn?.connStatus == ConnStatus.SndReady val active get() = contactStatus == ContactStatus.Active - override val sendMsgEnabled get() = ( - sndReady - && active - && !(activeConn?.connectionStats?.ratchetSyncSendProhibited ?: false) - && !(activeConn?.connDisabled ?: true) - ) - || nextSendGrpInv + override val userCantSendReason: Pair? + get() { + // TODO [short links] this will have additional statuses for pending contact requests before they are accepted + if (nextSendGrpInv) return null + if (!active) return generalGetString(MR.strings.cant_send_message_contact_deleted) to null + if (!sndReady) return generalGetString(MR.strings.cant_send_message_contact_not_ready) to null + if (activeConn?.connectionStats?.ratchetSyncSendProhibited == true) return generalGetString(MR.strings.cant_send_message_contact_not_synchronized) to null + if (activeConn?.connDisabled == true) return generalGetString(MR.strings.cant_send_message_contact_disabled) to null + return null + } + override val sendMsgEnabled get() = userCantSendReason == null val nextSendGrpInv get() = contactGroupMemberId != null && !contactGrpInvSent override val incognito get() = contactConnIncognito override fun featureEnabled(feature: ChatFeature) = when (feature) { @@ -1768,7 +1763,23 @@ data class GroupInfo ( override val apiId get() = groupId override val ready get() = membership.memberActive override val chatDeleted get() = false - override val sendMsgEnabled get() = membership.memberActive + override val userCantSendReason: Pair? get() = + if (membership.memberActive) { + if (membership.memberRole == GroupMemberRole.Observer) { + generalGetString(MR.strings.observer_cant_send_message_title) to generalGetString(MR.strings.observer_cant_send_message_desc) + } else { + null + } + } else { + when (membership.memberStatus) { + GroupMemberStatus.MemRejected -> generalGetString(MR.strings.cant_send_message_rejected) to null + GroupMemberStatus.MemGroupDeleted -> generalGetString(MR.strings.cant_send_message_group_deleted) to null + GroupMemberStatus.MemRemoved -> generalGetString(MR.strings.cant_send_message_mem_removed) to null + GroupMemberStatus.MemLeft -> generalGetString(MR.strings.cant_send_message_you_left) to null + else -> generalGetString(MR.strings.cant_send_message_generic) to null + } + } + override val sendMsgEnabled get() = userCantSendReason == null override val incognito get() = membership.memberIncognito override fun featureEnabled(feature: ChatFeature) = when (feature) { ChatFeature.TimedMessages -> fullGroupPreferences.timedMessages.on @@ -2144,6 +2155,7 @@ class NoteFolder( override val apiId get() = noteFolderId override val chatDeleted get() = false override val ready get() = true + override val userCantSendReason: Pair? = null override val sendMsgEnabled get() = true override val incognito get() = false override fun featureEnabled(feature: ChatFeature) = feature == ChatFeature.Voice @@ -2180,6 +2192,7 @@ class UserContactRequest ( override val apiId get() = contactRequestId override val chatDeleted get() = false override val ready get() = true + override val userCantSendReason = generalGetString(MR.strings.cant_send_message_generic) to null override val sendMsgEnabled get() = false override val incognito get() = false override fun featureEnabled(feature: ChatFeature) = false @@ -2219,6 +2232,7 @@ class PendingContactConnection( override val apiId get() = pccConnId override val chatDeleted get() = false override val ready get() = false + override val userCantSendReason = generalGetString(MR.strings.cant_send_message_generic) to null override val sendMsgEnabled get() = false override val incognito get() = customUserProfileId != null override fun featureEnabled(feature: ChatFeature) = false diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/PlatformTextField.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/PlatformTextField.kt index 1daf5a7ba7..6b301b9df4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/PlatformTextField.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/PlatformTextField.kt @@ -12,10 +12,10 @@ import java.net.URI expect fun PlatformTextField( composeState: MutableState, sendMsgEnabled: Boolean, + disabledText: String?, sendMsgButtonDisabled: Boolean, textStyle: MutableState, showDeleteTextButton: MutableState, - userIsObserver: Boolean, placeholder: String, showVoiceButton: Boolean, onMessageChange: (ComposeMessage) -> Unit, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt index ca4d4fc0da..37aa7fc1d1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt @@ -99,12 +99,11 @@ fun TerminalLayout( isDirectChat = false, liveMessageAlertShown = SharedPreference(get = { false }, set = {}), sendMsgEnabled = true, + userCantSendReason = null, sendButtonEnabled = true, nextSendGrpInv = false, needToAllowVoiceToContact = false, allowedVoiceByPrefs = false, - userIsObserver = false, - userCanSend = true, allowVoiceToContact = {}, placeholder = "", sendMessage = { sendCommand() }, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index b3fdcf79c0..6d7cdcdebe 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -723,7 +723,7 @@ fun ChatLayout( Modifier .fillMaxWidth() .desktopOnExternalDrag( - enabled = remember(attachmentDisabled.value, chatInfo.value?.userCanSend) { mutableStateOf(!attachmentDisabled.value && chatInfo.value?.userCanSend == true) }.value, + enabled = remember(attachmentDisabled.value, chatInfo.value?.sendMsgEnabled) { mutableStateOf(!attachmentDisabled.value && chatInfo.value?.sendMsgEnabled == true) }.value, onFiles = { paths -> composeState.onFilesAttached(paths.map { it.toURI() }) }, onImage = { file -> CoroutineScope(Dispatchers.IO).launch { composeState.processPickedMedia(listOf(file.toURI()), null) } }, onText = { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index de9fc26905..894bcf3b37 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -999,9 +999,8 @@ fun ComposeView( chatModel.sharedContent.value = null } - val userCanSend = rememberUpdatedState(chat.chatInfo.userCanSend) val sendMsgEnabled = rememberUpdatedState(chat.chatInfo.sendMsgEnabled) - val userIsObserver = rememberUpdatedState(chat.userIsObserver) + val userCantSendReason = rememberUpdatedState(chat.chatInfo.userCantSendReason) val nextSendGrpInv = rememberUpdatedState(chat.nextSendGrpInv) Column { @@ -1056,7 +1055,6 @@ fun ComposeView( val attachmentEnabled = !composeState.value.attachmentDisabled && sendMsgEnabled.value - && userCanSend.value && !isGroupAndProhibitedFiles && !nextSendGrpInv.value IconButton( @@ -1102,8 +1100,8 @@ fun ComposeView( } } - LaunchedEffect(rememberUpdatedState(chat.chatInfo.userCanSend).value) { - if (!chat.chatInfo.userCanSend) { + LaunchedEffect(rememberUpdatedState(chat.chatInfo.sendMsgEnabled).value) { + if (!chat.chatInfo.sendMsgEnabled) { clearCurrentDraft() clearState() } @@ -1159,13 +1157,12 @@ fun ComposeView( chat.chatInfo is ChatInfo.Direct, liveMessageAlertShown = chatModel.controller.appPrefs.liveMessageAlertShown, sendMsgEnabled = sendMsgEnabled.value, + userCantSendReason = userCantSendReason.value, sendButtonEnabled = sendMsgEnabled.value && !(simplexLinkProhibited || fileProhibited || voiceProhibited), nextSendGrpInv = nextSendGrpInv.value, needToAllowVoiceToContact, allowedVoiceByPrefs, allowVoiceToContact = ::allowVoiceToContact, - userIsObserver = userIsObserver.value, - userCanSend = userCanSend.value, sendButtonColor = sendButtonColor, timedMessageAllowed = timedMessageAllowed, customDisappearingMessageTimePref = chatModel.controller.appPrefs.customDisappearingMessageTime, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt index 5524eff655..5710f09ed5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt @@ -15,9 +15,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.graphics.* import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.semantics.Role -import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.* import chat.simplex.common.model.* @@ -41,12 +39,11 @@ fun SendMsgView( isDirectChat: Boolean, liveMessageAlertShown: SharedPreference, sendMsgEnabled: Boolean, + userCantSendReason: Pair?, sendButtonEnabled: Boolean, nextSendGrpInv: Boolean, needToAllowVoiceToContact: Boolean, allowedVoiceByPrefs: Boolean, - userIsObserver: Boolean, - userCanSend: Boolean, sendButtonColor: Color = MaterialTheme.colors.primary, allowVoiceToContact: () -> Unit, timedMessageAllowed: Boolean = false, @@ -82,14 +79,14 @@ fun SendMsgView( (!allowedVoiceByPrefs && cs.preview is ComposePreview.VoicePreview) || cs.endLiveDisabled || !sendButtonEnabled - val clicksOnTextFieldDisabled = !sendMsgEnabled || cs.preview is ComposePreview.VoicePreview || !userCanSend || cs.inProgress + val clicksOnTextFieldDisabled = !sendMsgEnabled || cs.preview is ComposePreview.VoicePreview || cs.inProgress PlatformTextField( composeState, sendMsgEnabled, + disabledText = userCantSendReason?.first, sendMsgButtonDisabled, textStyle, showDeleteTextButton, - userIsObserver, if (clicksOnTextFieldDisabled) "" else placeholder, showVoiceButton, onMessageChange, @@ -102,16 +99,23 @@ fun SendMsgView( } } if (clicksOnTextFieldDisabled) { - Box( - Modifier - .matchParentSize() - .clickable(enabled = !userCanSend, indication = null, interactionSource = remember { MutableInteractionSource() }, onClick = { - AlertManager.shared.showAlertMsg( - title = generalGetString(MR.strings.observer_cant_send_message_title), - text = generalGetString(MR.strings.observer_cant_send_message_desc) - ) - }) - ) + if (userCantSendReason != null) { + Box( + Modifier + .matchParentSize() + .clickable(indication = null, interactionSource = remember { MutableInteractionSource() }, onClick = { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.cant_send_message_alert_title), + text = userCantSendReason.second + ) + }) + ) + } else { + Box( + Modifier + .matchParentSize() + ) + } } if (showDeleteTextButton.value) { DeleteTextButton(composeState) @@ -135,11 +139,11 @@ fun SendMsgView( Row(verticalAlignment = Alignment.CenterVertically) { val stopRecOnNextClick = remember { mutableStateOf(false) } when { - needToAllowVoiceToContact || !allowedVoiceByPrefs || !userCanSend -> { - DisallowedVoiceButton(userCanSend) { + needToAllowVoiceToContact || !allowedVoiceByPrefs -> { + DisallowedVoiceButton { if (needToAllowVoiceToContact) { showNeedToAllowVoiceAlert(allowVoiceToContact) - } else if (!allowedVoiceByPrefs) { + } else { showDisabledVoiceAlert(isDirectChat) } } @@ -155,7 +159,7 @@ fun SendMsgView( && cs.contextItem is ComposeContextItem.NoContextItem ) { Spacer(Modifier.width(12.dp)) - StartLiveMessageButton(userCanSend) { + StartLiveMessageButton { if (composeState.value.preview is ComposePreview.NoPreview) { startLiveMessage(scope, sendLiveMessage, updateLiveMessage, sendButtonSize, sendButtonAlpha, composeState, liveMessageAlertShown) } @@ -343,8 +347,8 @@ private fun RecordVoiceView(recState: MutableState, stopRecOnNex } @Composable -private fun DisallowedVoiceButton(enabled: Boolean, onClick: () -> Unit) { - IconButton(onClick, Modifier.size(36.dp), enabled = enabled) { +private fun DisallowedVoiceButton(onClick: () -> Unit) { + IconButton(onClick, Modifier.size(36.dp)) { Icon( painterResource(MR.images.ic_keyboard_voice), stringResource(MR.strings.icon_descr_record_voice_message), @@ -460,14 +464,13 @@ private fun SendMsgButton( } @Composable -private fun StartLiveMessageButton(enabled: Boolean, onClick: () -> Unit) { +private fun StartLiveMessageButton(onClick: () -> Unit) { val interactionSource = remember { MutableInteractionSource() } val ripple = remember { ripple(bounded = false, radius = 24.dp) } Box( modifier = Modifier.requiredSize(36.dp) .clickable( onClick = onClick, - enabled = enabled, role = Role.Button, interactionSource = interactionSource, indication = ripple @@ -477,7 +480,7 @@ private fun StartLiveMessageButton(enabled: Boolean, onClick: () -> Unit) { Icon( BoltFilled, stringResource(MR.strings.icon_descr_send_message), - tint = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, + tint = MaterialTheme.colors.primary, modifier = Modifier .size(36.dp) .padding(4.dp) @@ -576,12 +579,11 @@ fun PreviewSendMsgView() { isDirectChat = true, liveMessageAlertShown = SharedPreference(get = { true }, set = { }), sendMsgEnabled = true, + userCantSendReason = null, sendButtonEnabled = true, nextSendGrpInv = false, needToAllowVoiceToContact = false, allowedVoiceByPrefs = true, - userIsObserver = false, - userCanSend = true, allowVoiceToContact = {}, timedMessageAllowed = false, placeholder = "", @@ -612,12 +614,11 @@ fun PreviewSendMsgViewEditing() { isDirectChat = true, liveMessageAlertShown = SharedPreference(get = { true }, set = { }), sendMsgEnabled = true, + userCantSendReason = null, sendButtonEnabled = true, nextSendGrpInv = false, needToAllowVoiceToContact = false, allowedVoiceByPrefs = true, - userIsObserver = false, - userCanSend = true, allowVoiceToContact = {}, timedMessageAllowed = false, placeholder = "", @@ -648,12 +649,11 @@ fun PreviewSendMsgViewInProgress() { isDirectChat = true, liveMessageAlertShown = SharedPreference(get = { true }, set = { }), sendMsgEnabled = true, + userCantSendReason = null, sendButtonEnabled = true, nextSendGrpInv = false, needToAllowVoiceToContact = false, allowedVoiceByPrefs = true, - userIsObserver = false, - userCanSend = true, allowVoiceToContact = {}, timedMessageAllowed = false, placeholder = "", diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 1bea4c18d4..6726009a5f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -487,8 +487,6 @@ The image cannot be decoded. Please, try a different image or contact developers. The video cannot be decoded. Please, try a different video or contact developers. you are observer - You can\'t send messages! - Please contact group admin. Files and media prohibited! Only group owners can enable files and media. Send direct message to connect @@ -508,6 +506,19 @@ Report content: only group moderators will see it. Report other: only group moderators will see it. + You can\'t send messages! + contact not ready + contact deleted + not synchronized + contact disabled + you are observer + Please contact group admin. + request to join rejected + group is deleted + removed from group + you left + can\'t send messages + Image Waiting for image diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt index d0d4fb5e92..41964b7d18 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt @@ -44,10 +44,10 @@ import kotlin.text.substring actual fun PlatformTextField( composeState: MutableState, sendMsgEnabled: Boolean, + disabledText: String?, sendMsgButtonDisabled: Boolean, textStyle: MutableState, showDeleteTextButton: MutableState, - userIsObserver: Boolean, placeholder: String, showVoiceButton: Boolean, onMessageChange: (ComposeMessage) -> Unit, @@ -203,16 +203,16 @@ actual fun PlatformTextField( ) showDeleteTextButton.value = cs.message.text.split("\n").size >= 4 && !cs.inProgress if (composeState.value.preview is ComposePreview.VoicePreview) { - ComposeOverlay(MR.strings.voice_message_send_text, textStyle, padding) - } else if (userIsObserver) { - ComposeOverlay(MR.strings.you_are_observer, textStyle, padding) + ComposeOverlay(generalGetString(MR.strings.voice_message_send_text), textStyle, padding) + } else if (disabledText != null) { + ComposeOverlay(disabledText, textStyle, padding) } } @Composable -private fun ComposeOverlay(textId: StringResource, textStyle: MutableState, padding: PaddingValues) { +private fun ComposeOverlay(text: String, textStyle: MutableState, padding: PaddingValues) { Text( - generalGetString(textId), + text, Modifier.padding(padding), color = MaterialTheme.colors.secondary, style = textStyle.value.copy(fontStyle = FontStyle.Italic) From cf0639bf2851ac1aa266ef5908530a83a4505daf Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 5 Jun 2025 21:05:16 +0100 Subject: [PATCH 558/567] website: add Whonix to reviews (#5966) --- README.md | 20 ++++++++++---------- images/privacy-guides.jpg | Bin 11225 -> 11430 bytes images/whonix-logo.jpg | Bin 0 -> 8183 bytes website/src/_includes/hero.html | 8 ++++++-- website/src/img/whonix-dark.png | Bin 0 -> 36012 bytes website/src/img/whonix-light.png | Bin 0 -> 29922 bytes 6 files changed, 16 insertions(+), 12 deletions(-) create mode 100644 images/whonix-logo.jpg create mode 100644 website/src/img/whonix-dark.png create mode 100644 website/src/img/whonix-light.png diff --git a/README.md b/README.md index 40d552b84d..554c6068d9 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ # SimpleX - the first messaging platform that has no user identifiers of any kind - 100% private by design! -[](http://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html)     [](https://www.privacyguides.org/en/real-time-communication/#simplex-chat)     [](https://www.kuketz-blog.de/simplex-eindruecke-vom-messenger-ohne-identifier/) +[](http://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html)     [](https://www.privacyguides.org/en/real-time-communication/#simplex-chat)     [](https://www.whonix.org/wiki/Chat#Recommendation)     [](https://www.kuketz-blog.de/simplex-eindruecke-vom-messenger-ohne-identifier/) ## Welcome to SimpleX Chat! @@ -110,6 +110,15 @@ After you connect, you can [verify connection security code](./blog/20230103-sim Read about the app features and settings in the new [User guide](./docs/guide/README.md). +## Contribute + +We would love to have you join the development! You can help us with: + +- [share the color theme](./docs/THEMES.md) you use in Android app! +- writing a tutorial or recipes about hosting servers, chat bot automations, etc. +- contributing to SimpleX Chat knowledge-base. +- developing features - please connect to us via chat so we can help you get started. + ## Help translating SimpleX Chat Thanks to our users and [Weblate](https://hosted.weblate.org/engage/simplex-chat/), SimpleX Chat apps, website and documents are translated to many other languages. @@ -141,15 +150,6 @@ Join our translators to help SimpleX grow! Languages in progress: Arabic, Japanese, Korean, Portuguese and [others](https://hosted.weblate.org/projects/simplex-chat/#languages). We will be adding more languages as some of the already added are completed – please suggest new languages, review the [translation guide](./docs/TRANSLATIONS.md) and get in touch with us! -## Contribute - -We would love to have you join the development! You can help us with: - -- [share the color theme](./docs/THEMES.md) you use in Android app! -- writing a tutorial or recipes about hosting servers, chat bot automations, etc. -- contributing to SimpleX Chat knowledge-base. -- developing features - please connect to us via chat so we can help you get started. - ## Please support us with your donations Huge thank you to everybody who donated to SimpleX Chat! diff --git a/images/privacy-guides.jpg b/images/privacy-guides.jpg index f15a8862f7aaf91449031a0c11e8b669bb11f11f..5876d10c02a5f1bf13a746fec056c7a94d957cef 100644 GIT binary patch literal 11430 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 + + Whonix + + Heise Online @@ -43,7 +47,7 @@ Opt Out Podcast
    - +
    @@ -66,7 +70,7 @@ - + {#

    PLAY

    diff --git a/website/src/img/whonix-dark.png b/website/src/img/whonix-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..f361f4d1f6c1429ca368654146e59b8f5041b0e2 GIT binary patch literal 36012 zcmZ^}1ymiuvNnplySux)1`qD;?zVA<0KwfgxNC6t;10nF?hYI0kt6q>ch>!9t(mT= zuCJuKXS%0)B9s)Q5aICPKtMncWu(PbKtMpZKWP^jsLyK@BAE4O0bwaBFA4%u9|!+x z4EcFaVk)g74+7#%1p*TI4Fu%*lNERb0^-I30&-#m0>YOL0)p+7-L5R~SrKopC1W8k z4?_1z!+=16LV`zKpA?7&DE|M@2B0+mD<5tR0`V^yi_h}! zA^my&#r(4b&j;_?@!pT-sZS-?6;YrBGgz@z^?K|wOJaX>)8 zC9T!809x{Le5Q`}OvYx8Cgx0@_D+9MAOfCzpQODxz?j6--p;|5&r^`>9};|@^j|hJ z8Oc9H0Jef;TJlOHVva86BwS1^Oe|zVa3mxo0xo72d@ACS|AK$s36fa>08V_&%pM*d zOdjk^jxLtWth~Iu%q(ooY;2655{#~14gh0MMh92&e=7O69&vM5Qx|I|fVHCo$zQ$3 zCXQ|ZK{B$xhW_XHr=I|8i~qIc;QFtyJ_BU_Tf@xC#KQc4!OT6a{~xfwHUEVDBd&j% z6Zor)Pt4KY$;I5&_0ugOR)K#Q`2X?#*S&wrtC+hw+PVFWLfyd{AjI}B$bYf_8>RKX z^+MeL#{4JqzX96+7vMjc{|!*Gas)VjcB+fDsf+`_+~qSUfbm~9|HsAux5R(zQgpF4 z|Lnwnp#R+_|H=Cg`M)j8r)2GEZl@(~ZEx=Ik9OqXVG&^dKWqLQDduSB=%VUmY-;|u z@&5t&57vK6|HF><|Jq??<@irK|55WVqyY2Zk@g=W@1Mi^AKcGzEd(dP{6Ei#5L`O< z*)a%+2#AchsG29}Sq^NVzC;S}bh8DxnR5YHn)-tF<;!of1_=$+Sy2fLR020A{1J14 zp>r|N5jTJ&VVbkjursdlV1hF)AvT0^$Txc!3KVIQmt4S{4{+@Q__Ey5GH>ne_4e#` z6tBAh-0(To$l-Id;dN6`&vB7E`U3Y21BMnwW!p<4Xw9)l@Md+nd&!l6`qHv$=SMgZ z-;Wi-rkQZ9rsur0WfGGV{Q0)j0eKf zm~@-ZT1e5u$k4aLY-Z5ow ziq&}DXtG^qoSd9=B(=@(of7vr`zUOz1%^;1YMGlXz)^Q3>tJ##h|0=S7Dn>I{JIcI zVqcSo!d;#;aI{!_t1hp$P8`IaG-I>4KUt(#5PrK$Aw_2jD2By#da~v#C;7@b1SbW2 zy`9z!H*g2OY<+CD9p?Jn6rsC66su~=X)YUa8WQwa>rLvZw$im#863`pbm?`x z^0)Y7_cq!5^544p<0TdpNL%3U?^$7OFaefFMk9ARL+GkdI-ZgJq!VbrR< zyDzx=jPQtpN;d4A_;9lhE$4VCz+Qc`TvBfQ!@Qqi+vaVUyW6=RTKKU~5VTPkgWDGm z8S)F10MUv3O)E#L|Kfspp*O{R=20hs=wlF<5`v1t-086%wo49Vtzcj4?6Cqz>diV1 zVOfgy*#_J%B2&6JXGNg_M%UgwSKTYmZ>>P5jmf46dZ%DQww*w{=@6e@gv-v`DT%{u z*D3Ugt#*ScPsPjUi1}Ruhr|nbxhY%r*flAK#l?;R7Nc&y=L^(GX9KgY<0F163r#z& z7(_s?dE51@H)o4 z^$!gzb_Lbd_LDdJH$*9KydgX0`ubBVbbD+vcNfpYL@ zcy`$pEMV4r;Z4!Z?NL!4%^zZo!=<%1Y2?UnP$0CzF~=TmZAjJ-N!)Dmc+QVe$|4S^-!Eza}gs#yl~i6 zZ&1sV=>gN^2rp+-XnwhUWBgptu|cq!28XdxG8IZoVt%OPaC?Yq8E8HC$hN%PMloBK zjbwV#8Gb=i03xS!+ui1K+2x__=GK&6yS%tUxR%$}r=}8nSH#r@* z(t^3Foc0RcrT4N1Om9pD<>+s;X?iR;G@YS6Qs|HrGY5y$c>!(=7AZE zPU)LYdeiIOY0iXUiQ(!@lLou72{gX6@eG5!c9Rad(^G5|H1h?<*@}gD+{J!-2Cxb4FwsS=%upN3LmuH>Bcu63D(B<`b48pCH}sDo z#0;XIJBX4Bm~oLm+&6^QJdEj=@OT*^ZV3Vw!{YZ^y-OB#`QhXBEY_7xtDDZs!ckSj zA-=;EmJPVnIyTGFl!_-IK9NT%wyM*Lqu?F*i<@B7Q#9dbY!cWsbE4nXS*F?Z*cq9- zn(6Cd>JB5*?~9Wjj?=vA^UBVbUf{2f3?^NPwv1`wW%00L-wOMzEx}c+-baMrudBq# zx}|wMfM;%9zcgd;wXJRpS{Sresu0Q;L#r@5;itvk=yEqy&WsT7hOq3K>2F!&{2))^ zwZaUw%e`H1m6XML>Q(dajOTvO!u z1ii@NXT{~{GJ)!$InB~GU&C^L`N6IFvSUkL;!53&LpQI`dBT$~5RLUb1cG_n z(mpC5gzwlnd$Sk{k3A(xx487vSJAyANwevS&XX`^JT_5SA{^DHVXn`0*egBAc(?>N zZs_o(zE~Z0gNGJ{ZQB`*C7S5(^e=t0NWh!ac_7q`o^wi(HDBUq`SUs0b|bT=b8_#X zmZeL&?MW_8i$O0*Xle_|gkQv4xZ2;@cu;2}JC4fe7i}&&)sBf0Xl@2|Ns9Xs3w&1c z>jgGX@bN*sN#K_LirLO#&p1cdah^QIG78AovG*I%#H^YVklj;LrM&0ZzT+G@^pA!BtG3}Yzz zA9^4_zqZ;crC9`)iW&p}ig6K`UIYii=V(~+L1e*EhT;A$16kn84&fJEg{5xvElfs# z9@|H0zU`;U`L9S&KIT zd(YR(11B#d%+~V8GR0LF?#|yJjTLuR`-_p54mSlt9aNM_>TH(h=hrYv4xC&_hQ_z$ z7i)c0>MX?4oh+Qwa&R;R)ie1-WmM{_lR%OF_?=iH|cRkGQx3Y)T^9yi)pN^Abh6JlswP3yzE4H^iUu8CR#@TjXDgWHPl|DuNu9>5Y zWYjF>R=}BlrS7^&pXkhF`s{AUeoVOLvMDCKSmjh?b3C5?Tf3I49;=z9KA&DOu4+nk zqX^9D9q}v|2uM55S~a12e~v2IJ?Q!FZSTXbP}Q(Be)s;JVp^&F4fYzPT}BFY{IR`# z)&1D4&{F_4qK#dE{~*%HVa}fsdJ6Jk``|Fsio*DZ3BGqJGVjbyGWicvEXiMiaVrJL z>_VUzNaR~{=B}6M6mv8%333B1i_=lNcw%z7@@B8-k*-3JT+3WBL zc0!>bAFOjQP-p^S4vEUWY+^{#zrW`k5MH-tBz)!YDcKeH`UI;~y^Ht35Imr;%|J^@ z#vBh}CljwwN_=mve062muWOCLQ&dRD=#NAHcl`?nxj5{MGSd`KWq<{e|Go|DR<^F-fV1hMa%snX;C%hdRyN>ezfBJa z%ggNXWpg}l&+BQvHTAlaCr_5ydEQ==H;4aY{+M?rWTlGcy6eK{@)zOjNE+T@#!H55 zY^H5TKVpj;dGqZ?3nBmAOi$m-;0K1 zky*if9#X8+P>Eh;@u~GiX9?zPHmh^^Qp<9P3!s|h8zkx2{M_8qvULvrK)>*q#G6X` z4dGFX>5b)bzeuFF$^k$&nqdIklw;dOlgWMZo{!1LFJzzh--OvnUy8Gkn=5kBPI{ZK zw)mVw9pvuss*=PNe(N58_=6>8wR%I5(v#!iKfa6gcjaD1-DWh` z9`d{MAX(5K6E^0wbWZZbeIOOSGCY#$%gfC>yP{^&kHC-&Xe9k=z|N{*XL9R|)z>B; z*BgF<7Ot*Kx`$U|qL+3;vsCMX^>E-BFXE*?xtUOcJ_}>^N#`=8Go5isUs-rsZKDXNl+NP@XtT6FIg`(nJKFeJa!VJ9mxgT zZO`r11xJ;iqLE|3=)3$Q#D39h4VRn=Yv~Wm=k@|Bodo+2V9x>I0Z~Udp<1=AbK#wjSX&TLujWlKDd-*_z~sov--Q_ zOA;{zZ{PbLfs^aV;?k%PsVY<(YL{}>Ys$!R%m;K7;|AXJx(%d-xw#v}qIsV_W({@q zj12WR_4)a6?cS*nPkD35PL0*VxBL>JQxiXtKlqiU@{}P%HdqE5wq$-=Y_}!)q>Ihw z%^koq|Btku*Zq(mceU3hbOa-q<=^n2a>bZJj?| z@l10z(ecxdcOMMrq-IbhVbeY4n&2wG-8?dzdr6Nw@Dxw#nUkV8qaIczL&pJ12|p(= zW(mI?E7o3~oE(#sYl*%6JUgo}%#_j>uyuRATt zD0-StjEvf8U%5^{C};g@@D7jCN8b@zgLjipwYA}sAhXH}!F4<5r0sj8EK5}pT^t(^ ztFgDnEk)G>xx)yNal+}#22j4DI_61`)u?_eI0?}>hcIj4%*f`)&+WSrWjTF5cm9;k z0R>I9^lukq^tBT*bX{i+kk5l+7$HBVvIuCA1-{($ta%}|n^ z8I@^hpLv0q?*gOJPEE&udP4!vIVGgmMcOQfm|p3%)ne5zIsvTqt3O*FwSlM46cmHX zkt(0lRYZ-n@?)$&O3kBVfywj8m(?jBheMLT{m9N+p_5^2~qZ&fQP_2q55`rrWkn(7A92 z4a0$e(m0z~JSLkn(X}<6=_#>rXQmj=DJ-*XRM%&EM1>D&)Qy`5D?H&GD!ci1 z;Su0EmQ1S&Go6{~T@%&#v_4eAyfKP(hd@op%JFk;r=n9l`?e8|Q#*xhX>ES3-WFJM z`k9{t$`1iEE(6ENwq{a7!2Xmq6R05=6I5axS@tSS;0a%?r1>V`Zai)~Q*Fs`v-Cv<&?-NsEWO(axX~szK3tjSW+U-3yWQgj$Q#pf zM*N8!!}O=aHLeY z&?nwIm`ex(`M49luc9;D=lFHMq+hzhR0pyI_&6zjqd;&hc(1>N**oG9QeP z>i!ZETQ5Ilmd=%RYRlxw z{=D4tj$aMXfVy5b*m6;ff&m;s{mndqe@7Jx!@wbj^ex%@sf8{M@ODVFC+4;15B78(Ao()~ZK27{gR-zW^ zM+}FvYO`)C`tG1JBqC+hDptaFg(7%<8TGLP@n~wf`|XN`JtWYi*V^agP+K%CUVx@5 z`uuITB*LJYZ|bV6^ggh^gFv@1)-fu0JIu5fjP0r+DEtXEI%pZC@8;CpUj<}72bvK) zY*FW$M?_@nC{X^}0HIm|FOnU|6W_vdN@pbg8p*nt2Zj;|l}#xLjVfj-6RYXdeZQ|g zQm|t-tKF3F$E)5iPe$$7--@3v6`bpjLr&v2%Aq`i-A`~cZ$Na~kO>NDy6!4F3ZqKbHuRJSaH^wx%RSG^_d%ft5f z6DQ%v?JZx5uTW3R0>T_F4;$6Y!onU47%@_`H-gWsF~Nyo7`&g^o3)r0cP}sIBmoCS zlqMuNfd&ecF6mjt{OPS%y14TBGc8Y3-?re@Jofpn`4?V3bg|U=m16!B58Mnac8tQM z{8IeXl}vWjLEJPyu?+jiat5gWKZ|_D#?H>ZGFn*ib#qFOeA}8Y!Mp)A+XLEX-uPEf zPwzBPlyDHsbx-%jlC$cG`(~AC1AiFx!e6kSiH+e1WD!qck{;5fr<%=>jyH?fE5z_7 zpK^3bj%D_XV{K`|SSe_B=xdlJRkcgl%zDXBXaPs{e+F~_oXL&f_+C|epHQT#Z3Cyf z0`2OhXr?#x&%{mG*J)%E%rV3&%idyDD=R_(gX83hR`q5>Oa&nZLLH%wai||c8p7jD zce{FiD6Q<$S4u!1o-dFbk4@gL9_iV4;oh49WuLu7jtBB(x5|)uBw5%aOfrE-_X0j^(QY&nhrs&f9^*J;GYt3`=eRJ z)Hu(dGr*ks;C-w;5hcqDe;^7xx~Hpx3>FQ%=EgCHoR}$vuYcy9;#}+Bf0Cqpoity( zlB?0Fp>RW$2%Arvw&e7yOX6$u`gngkoff#AC@oLRTy8;XJ<$}CJexNyaGb%vNs|Is zR?r&S)5RQWZZQE1H>QGwq>}Y2|8VC;5kL;HyQjUa64J-abPPh&X3bqlQWWy?_w##` zO&ZsM-xxQbev&prk;AW zg#rNmloOx-DO!e`c zKq%*|9f5RE>*gumhlsTALp;R&Fxt+&=~>9YA8~xJ_5RSZ?7v4&+}8TJAGCk%`C6sL zz?BF3BRW0$NK3{}N9Vv(D|b~6zcp^)B!JCv)9@9n)!jeO+iSvMKWKP0jsU?%vS`;+ z_=}^oJ`ojz*6vX0_s6O8mMw>u%A<$#HOHFDN*~z54tFvroeDA|1mZ#J?=*==$HyxV zk(!-5`vLE_c|OBp6gw5@(?OS()-;;8Ot55QU51?80+Iv%vouX%VYmDbqg|LmnYKL5@b_PY8&R9MaNfx5kPzmXQ zM8mUiT*)Aje~?%5_Q+*y1obpUjNhVK--VzOJZ&^^1si!wPs~Ov@TDvm%d``AG(gE< zE)iI3HGr+Sh3c+!qL_BG3#b~Js$3^Wi0@Dl#~6!7O^PXf6~nU{k2{lL+lqeXT~0gD z3uj8&YFVB!9!+aIV*Gk$u-s21Pb^y%YSlFm;%dJ3w_j^4gG~MKQL@bQ zJ}HkXRj_}@lBf_Gaq;esp%op=psCt**AEvpdbl1T4jAG+%x=`4NU?3@Ix}LVqYJ@= zbDb6hW(WFU-(P>VHbgSE$y?ZbE@wT`y*e4(t%q6G+nKhvvTCiUJbl-f9&V}2dx+Zh z1R+Qfz7H+zgEPYN!?xQ#xaZY>i4HGO z$iX9+V%2jhwtXpH?zy-RxPJ%sd_av^GW+9Al%Xa=Qs-;wM3x{-pl5N}dkao~VBhr& zkZBHv3w8k44Ybw0=-%+c8pa2ZQ>vUoK~kyR-2o1HTz;#D+Z*WSgC6l??4>a4U*W#& z8$4!A`&uB`^t<(kR2D@_LsbD(RaI~4jG95DG_?s*_7D_Y4kXafyjR+m@d zhC%+w)$D!^V%5o>no@AnaoI1QfYd5}jg*Xqa;WvcuRQ-Q3{oKZt8qhCdPTAAVb#{r zC>}TksvO_>?T^{DC%(E^!l>4aX))G3V}k|<27-UE*lccV=h$hVz17Vl~9 zky)RT6n{x!GYivL6{(tZ<#MXvEyLmVEK%ZpK~`Jfka}2DmpBB3us8g3>yi$ zMNehY_wEVkOfyP5>vb+C3n@G!h4dat{PW})>A-rRd-(tg3p|`#xf?AkGPJL(j}8Lj zJg$aHM9Y1IEl%bYh*bblQ+iIc0tCscKCBnAz|hviG<_3q&IIr${D!PcbDEzsEOWiM zw|Zg&P#$=TmE&8bI2)~`3MbB8@KAqo8$3)|-P0a=H^q#2`KhZ*x9P|g!^Ke2fejk{ zbOPILp!~(uFkG9LKrX#z`T8@LZ<-W2H|@`q4nyvr1v^R?9tDRA=hISr;3Iq2DyC+7 zE@E6pSI<*ylB`FTID?5E?96V(F$E`-)Jw|`T5?@sPZ?E>T^nMdDhTlUq#&T`66tRa zsP}lufSmPqx6|;WO9!J>F`umcU_R^Xt5eNCvd%qp3i>B&dv>5ohow&6wtCK28nLZ= zj`2Pg3Seh-NJaLvV62Zk?5v;fdjcmP0~UwX2<`ES5ebZCHf_3fKHnPa9?W{JSI~p? z`)2{@)x+ly)Qgdj4%1C&=fx+3B9H6a#i^tOxrX7?MR-C~6{@ur?iLLm=9la51sW!(!ZM zHkydurz+{8PW#M%ov?#-%_s{wtmQQc59<>^GK7WdSVqze8d!+LhTVb^D>^_x=UeYj zmj-hyXOAp!)w}CYA-iS*DyPPS*R3vG*`FAu5pThfC{FF($N}o#ezLdFM=RGxAcJ`V zVr_ZsTd=TO%gQZ){BimkR@hF!vEWjyi$Sm!Z?`t|8`2~C1kZ8Wfc?AynDMO2#BuR+ zx07H#`fQl9+-_>K4y7qtLyMjt+~12b&z%~zU5Xl7EM}ia zEvW6lO30Co#Weh_8@K;{QGPc*wA|vM0NWFKZ7ql6m|`2#YX@dH0YJwiEa;ZHYfs!v zERrZV8NQN-dqeqK#afw~Sf|_bI#@T}o04{1UOWdwuXxmiQ4ud+PgQkSy}G*kR!PyY z*JoN>y1&er@ZOkb#i1G8xGQ-S@{XZB`&TJv_~@I)Pt{AKK;N3zUMW=-^DmXe(TjQ$ zIs8D=$Ii|z4+LZ(hmwkl$f5WpSjDU@m+jMmy0`o8jTa#axVL%pOqk6ff-xQ8Kd{FO zxb{r9f6}}gi=l!PfS?d51!|ttks~CeMp)C&8qv_0#C-g@SO6AHUPI?sr2O=w!qEDC zutuNP7)RgTT$1WpGWjbHU8wB>zfzk!EiOQ({aUl1+FaSR@8T$|2kT}-i-3`iRJ+N= zSL*Tg7icZ&E8agUlF!&^bjQoA*HyM?!Lz?RJCsIBdx62=CECNQduFB=4k!}8Je zM6&4=&DW~R%d$gywPXiQ%lnydukPebPtkKaewfi%v0*zBX8*-otF zws^`Mw+a%~#|&S^g>Ve z>tO17Y16pnA(yRBPGf<^nAUGYM?71{>|0!aX-H0{gG;Ff10SF3B^I5>*6vRR+Zy&7 z;ooEpf8~0~pog4N0@OcAf%*4-XkEausbxw$u|nHv-mkl_*|X!EZ*saGOiafI3@!dr zLr(3!NjEA5h^H~Yp9sC)%_|zpYOf|7K$P-RJc>ag??#cPsIgS8@hGxKvvmHH5zP?K zZZlw2$xQ-~r(R1EGLIfRjcVmy5~Y109^+Uv zrqoaz59t-zSMd0*%J%Q=DlZR`RaGD@tco32sQBops2(2F`ISke*BJH){cwLh;O=RQ zwg1wSA?Wi^8jrFVW|y5LSD}IB20$V$_HVXcu((7 z7A#eRt%wln6dX-*W6?Hio5*g2&f{V?mgwptYab2QRgHd!P)(iO&>e8AnJhoe;250N zb_~?D(0O9fU$K}~xHf}B6<;4zCdek^vZ7MUofCZz9AuB_?nYIuEjLOp zg*n6{-C$3Xe)kwIUfkr2FB*}>!wJaL09SY3s+5;5d?~iE z?}s~|e`FaP&9~%@#w5o-L;FdAKY!f-&n-S=?=+R!P{>f^O+vvm$nhm4oXFdX)ZVU{EHy(~6g+k^gZ)sg4>`R& zH_EN=l`D#QxYB_*yU5%^4&4fV>EoE(dL>ejXB*YO1v7i`SFPu4lmS2(Q_BKvake~R zg^j=e5n|N(ol!O?O69PdR8g{RziM8Tudgc{8WF|#O-D?0>AW`lE3V{-fFoE+UAi7x z3BD93LzCkdG<%L6N^)fiW!ByPl%F=H<*~}_)E@tI;qXYLOR4p-=E)hTMK6`R&YOJU zT{FZWqF$_J+=`5(Tl43I24W_5mL;+Gy09(C+-o;?8Ex<8t++=2u;*8N)@x3JWfoaF z-&=XW?6@d7eISM0+NRdBqBmC{eAfAn&@Xfm!sHnpufW10_2vsTpQTeuVO?wIwmvf2 zzt(B)8n|qXvl5qP;C)PWfZZf7U1OKSpMfeM>qH^O&xa`P??*GIO`zT8c_J>8x@`;} z^;Yao#A8$v)mEGbvgxA0O(Vyfe`_RzUad8Ool=|b2u3yD zShUjLl@DhTixu`A!=@I@6^p!DgskQYmw7_nnKO@`YLfC8OGc++pXQRqsYsvcA|hQg zA=*p5NV_j3G1)#3UQWk9rMD6y7N<3bJWSixK-+U@7nND`xi8zP+Z25tzf|s4edb2i zzpp%+ntZ;FhZ602KThE0_h~$b|5!UZtAGq~Qn5k>@i>c!qwm(sW&ruLODZikQC?op z>O*n%YI}!9XP{~Uxm*m5RceT}umiE#2j76`2D^8N#s!)sy{fZ1qqV@qi8I##b z>CJV;`Yw+;&-aGzUi6VLHLM6SiUMtkjyMALc->fTHxZ${)e=Ew=*Dvu3V|GU4(F5v zqFtw-udE>s`|Hdi62Vwt?ojBB@;)LKj+``ZN?_#?)$6xuvr{>{47L{6kehvOsMAuQ zGhY*~8^fy46MQ%##QLqn&y5JlL@1(>emVtKqgyBeWQ*+ZdQ7!`lY&ei!~+lbEf?wP zZ^qcL@6zKF)2qBXlaRJGtxoDrwQ`SXxovxCCN(_eQFYE(_Cq^mDc3E|=h^k<>nuLy zdDY%{kG&XYUkJKat_*^^lp@BcRNaQRNH0prwYJBuk7KktA2n}iN;^}dVzG>H{p~g? z%gg=uN2&83^Ma*bRJ$PVW%e0_+}t=Xla28fN7)|GljsV;6JT*j*U)Nj->8e;MP%WL zer%h1<4CB&Ji#NN$&b=wbWfO*nibHbx=R1%h7+24*bg1)cN9*-VfkF&y|m_yCR1{e z&lL;Rfu%%-+esm?jO_a-H((oxc%~mFW;u4ZB?Ko7ssZ@QbnLVqXs1QKI-=&%ZR|Ai zcuQj$tvne95eeR*BL3jxSaCuFU7)p8W@HqF>;6gvfY`^O&}t3L>c7`TudSH^He38L zbr|3@WM|dDU1q_FBWo*IG494$F}trW(ND$>zcHLsvoHISE}RAHho zh{z+cOO&~PwB9O zRb({@Ypa}w!&=!CF}>lM;Zef}bSpX8@yPI9dywvSmxPLyO!%3!LnsK5q8eyys&yDuoT{w~xSeJn?(lzCeu976`VNokF;cgCUImQXaV4*9FT z7yudOWl-WTVDug`5BL1)X1kOF3BE*TT%FErsV)7?`P=?64ZG)YNRjrV-;#ys>x_3p zzzt{-gP?6Pmj)Ftj>89k#Of68@J|%QYK3GBO||f9C!R~6&xc11r!IvdNLhhQTOb7n z*{iB~q0vh~i4;30_JJ0D#+e5%ZK1xvRM|_dQ#pGmWHGxB?x-PUlIED`J}<<3_-=?l zB|}+qaA;4{9Iq_i|3sY5A`_6-ydqioaUey-e!37mi7+_J6RmQhH^Jz_T;_hB&3s$q zjAKGzfx5f>@_pH~`R%=QYL(b;dE%?)c(8iKJ%>Ko5UZFBS~W* zBU%wJ1fk2{f17^uxu{UDR4A#DZ8Sbri+F@c-1Z@JStw)}4s ze~Ae{Wv(>^Bt=je7qi%Cx2w<(Z7w!4ak@R7M&Z;S63U*K*)cYRIhn)|}i&_D8JlDqTw+Zeq<6rfsvUu{{-hJ% zMiFhD{F7A9WY+RASV#EmSfTj9sL8Fyc$_`sTg5gA_&p^0T2bSHqpvdB-Bv*Jb-4nnt+N2ljIy6%+Zc z=LP5|(w@)U>;vXrUx|fxC^B{APUmgqr#H__Tt_T+`(bS@o6@JO@}A=2x)2bD(A=VbQKH-O>jcYE(5PXK7=z8%l z3{e<9uJ#}dZdLsa2`F`35+hNg+mF&|rrNNGo9^b$$wSK2FLh98&Mc44U1Buw_)r4a zKI0OyCxhs@WIDysUXRkh!HBTGg#N-p8p+xy)qJ4F_8r)&SL#izJem1SGl z3PU>Cz?P#}v1|9REVxJI_P znj4QVZy{UrFTa{k;ff4k zTtsGf$cH!>^PdJSS7iNbj5-d}jbClzv`_h&J4smrq^OS!Y+BXr7t!of@r0n)q_c%R zLTRA7{Q0vc+)LBGiSK@E^4XY^B2xCkFB8v}Ou^HWJKCMU!WhLeo8`a@$*)*sddQKO z7(ysfjH`=Ni4O3IA5dv^Es^cAkPvL%|E|2Cgu759&e6wataHz><6$^Wve{5(f<^Rk zC&1(2*m}{EJVruFujt`(=};)A`ddrQXe6f9@*7ejwh3tuEhU~%-}}sOJ&K%^_~e2h zUOGn-?E()= z>oP^fsXYJ5Y3@=NmthFLjL@>Bjl~2Tv@7)FR17jt3QoZiogEZpHwA)&bWdot!m3bl zA9!;au8er=c(hCO;%ZmDjjGpF6JA$*B@CDAWmujPBSs&AY|a+DO+a-f%|1aGmau zx%yB0Y0i62%_oxcjt{+?7$F5WnpOY0w>Z17wh)i%Wn50hRMzM6Rk)zZsT4|6R$uYH zRGBeaY<`%L@d9EGV*qiX;ApOv0&;@OMD8j zE3q$uV1lcBJb$VLeo-}Bk*!vAL#AyO$MT^O#xZJ^|Bz9w`OP8W8tTV8aplYHCn8B( zkiy(Kc-NauM}tv_ zTRAB|ftMDG;1=qpAsST14Wrd@Nm$J}ODrSEePxx`Lz+aZf-$DmhzQ=j!kE4dteVoc zX7)NI@Z?BIW2*LnMmH%uoVf~$NfZ8#e6h!P8P0xNwvy+>xJ*%pM@QoAMf0L22DA|k`#~~-ai<6?$7&`0DJv^ z_PqSi8FoPdRaMf*V)1ATn(jJJY|GGm$}{9yS>nE+q3_CxO;IYvhT# z7c{(;#i`D0js=rhf6rY}lc;!d-FjWm0?W4xZO7=I%Xjoo-&QU6SfmH`zJm+%9v3BD zpsGSUD1)oG`3E&-d@QweH-$yELMv+1)A}G%>=v@oC1UE|5WioAyEWG|k;ZI^=z(XC z`)LMQkE`v&QfE6Q20}}6g~`&8a5%?mrn_t(CkYlnIx|E!>J9{lx(xhbjd)eZl^7{& z#&ZfB93lGLM`XvHVGz%97FdSWi%MrVoZzhvm^|_*?03ObH-^=WY$U{YE00i5nD+%) za)dBCDO;p6fJhTuzH8pU{!WpaWB)9GLmUm~Kwq-&93sQIOkX}{0G?|MfOKQ_zy7w; z=2P2C$(lsO}y6b zHqvp?^YKo(FfcUH*NrkCf9Na>UT%eYn@Im~DSXA=TaMm@L5vF+HB=Yp#i<*om2vf@ z35lvU+m;d&KcYuW=l|R+yCi#*xvlWeuL`-&7ar~&@CmGDga~WsRspi{E+){ZhFWZ> zo;sjR7{zwrJif`vr^x^Ee;PLw`gL@rEbOv^kCA9nhbi+N`55e;BYc`U%$WV!LVldE ze9Zc~z$!fmQ!tZ)&`r6p7A|Oi%Uh ziX%xN@t}CivW(maV#4b4F4P4$o>QNAu?lWTlQnM7Nb}0{`@_w?=~q*u~+WnjAv30zaQ-43tgG|eIqMQ?-_J{{ud~}PAEd*9AxbcGb@8ke_0{pZZJxj z8+k&f`Hb>p8(ZFL_q{W`W{0<|E2xfX%w*eO)$i1`9@nT1s{P>3rr=@$7NceDzJ6Xj zA@&wB}d%R{|~u9M!$C-@QfQhk0FSrid1h^j+4m+ zbuzbg>(=*ECr@f=nfWiA;Pn(p_Noo5-}BpY_lYN-xU#Xa;gwsrZvQynMeMTofdhzR zD*@A2b9}#23wHp>qwdk>nPVD1;A39a9XRx*;&C!byKwU}o3&mcl1ip74$|`P5gt-K3dtcWT%ew& zvs~^+E;>U&Vx`{whG<~?0RtY11~yO2mMuGuMpQ$ZmT{Jvb`TF7Y?`QTAH%PU*I+ar z1et7i;j4}=#)&77WBKJInCs_xm#R0WW52koqZ@ds)65_v4z(9UF3g+S zO0J6TOOk9jN@vXWb%n8Q237Krb*ypD7D&5y>vraWbKoo-trRaN%a$ny5is0>tF z5;E#(x`ALmm`s~w1}0rI!-idK5C8x`07*naRKzcR7p2vQd97LqmVcw4pONT##Ij7? zQd+i7qF#Rd-h1yo8DlJ8lBX;a@!c|A^>JLX1B{pF$aXOINK4DCPhqj?Cj_6!fuYc0 zkQNCBh4%ee`Vk+1-cbaNRr~hr`xbe}%Tc+N)7d&%-&p_Nr=ED~?-I!bw&JYDZM=0= zl}sildshB=GwQeb^ zRtKgz=4X|@B56(+DXp5KUdDyH4=}(ME?tw zenx$P{Ko67^oz;KFTLtjuiC0UPxWdnKnGVW8yQ0s ztKsVH0J4aW1>gu_)hP2Y)#L)4!VNdSN+xW^2x|}6CS!x~93h$hPh%oP5X(=?L!zC| zYPrSo4^-kon8A1Ejz*o=9vo+sciVw65DFM@z~n}W_MClo=&U}?T-H@!kdqWa8EOZNE7xJXMqIhQcde%f67nt~Q%U!Q~ z7o(EoEIyno__$KqYawQNepP-B>nHvJYe%o;IXjj*2@|#&H%{+)5Y5bb;wwKT^(Vv| z)iEe!j>Ed3-i;dP`4j*_EHACZweP)_^;mmv^7B@1t*2_P=xghSpT>A@st+;R^q!mk z_9kij_LfT3AHSYHwV*VB{a-Pi?FP!nL1-t z(`-?S*!f@0yNVHOn$$J9&Q^{YVvq2YX$DIgBcL!bpMphS`?jxRTT(ZOX~{Bq2vi-F zo!i=a;D6cT@)6#pY93RaBtB8>_LMCB;3GaFl*TXbuAiw!e%S=pcB{dL6K5_F3No zsTfmb%aDLm1W-ncC7N(`Xf*qF?%a8sAFt$w!$9FIU<~PKpwQc@8l@Zaj=;Ff}`-`UCN$Oggj={h0VLfWt*?FhhwHgSeM+&9;i zOP=y#Cto`qqZv7HD=<~|Z(`;qka%HOk`__vO1g!2!GKvE&4ZqaXr`44W66~B9zhx*ggkM;JN~s^8c!B3n~K|c6A-| z&XblxRM@s{Td*K&QWAx7Q+Ox zJy=*UtR+5}p+-114ElPls+0>FRN-ypUmKQ=e8>9r>%TK{(BZ#AU`rPU3a1f@_B*6S z6N@M2A!no3v~$`DJ3Q2)zt}u50D91mQI5Haq7yGAAoS8auH0D4st}{_Yw5ycbghA(nrT31J&=AC2G@oJ>j42%dS8`Zwph?NK>`ARY@Fex!#ZQaqFFb5?N zMk-zEk4a@PgweLorW-ApOe3Lv=$oWk9Zx9dYOa=XY`mrx;~fpAZaUI8{=KN9#Y7ni zSp8~Yn~_#(Yugcw7hN-t%8yr&>IDlwF=(mYQNybn^6F-rSoKuL>)a(qqA43Y7**0- z&!Gdsf-AL7+Ss{VZremW- zaCD^HY~$LqXU|Q9u{d)tsqCKBNCJDqn>TNM7%5DvHdK*I0#3ZcT)@~+Ezf_?d)_0< zBzQebZEMqF6|zU9wV3N@gyNa_KMVec3T2s9AGhM7bk#zf!Y@TR`)9qwDX{E_IMRp$r6E^sae zwT5;^U?(wkHp{uy4+IL^LJV6?ka~4jjp4DNf~Lht5*A|?k5fg7uy;7uRgi$C#EihXBl9+KhEh$moXsX%oTRU@(shB$5WRsO9)7NEs+Yex>a<@6SRa2TQ59UmC zA(vyXx%QijVF!5->$3C0TBJ2V`qi^lsVgnMDPDxd|A{3RD?99Cs?^}-6v4-hM(0AP z3Hqg!$5kf-K_r{a26eJLMK(l!(KJjL>>-ky>|VD0u@31&2qAs6E%mMAD$6owSp9sI z-5&R2a`IO3CtS&cgka>=k7J-h#AKwb#H@q4s6mM+!HBXyYzYsxqQTgjues)$xnKS2 zSEcKhpCz!L8D+47bnQH`ayN!_EF3z9Z5)KWku+Li_Km=`o#YtcC{25Y@!Yv{mH(${ zkm^u^&RL)=UIXT{%TY-LLsoh`ROk;W5!}Eu3qH@&U!;xB=QwLBQ_;H`;~R_IUdu2Y zr>apSFzQgiY{hVONmA6@C{iTMTJxk<1FC0RZN=DBr2G=5C*5c3OoUTfbcSy}H&6j& zKMj-pplaz(x|};4i6>2*Gk{}Q*AhGL>Fza|w%yF_cA0|F$pR=roi(S$G@NjvS6x5b z)HTjB71b%Ps;+??VaW^KvyLZJ>(=b4i}J_S4ndaC@dP90lNhe@29FgywC!9lUxMYM zN#nGfY|N@3pa)4}qS7+p|0<+@^!}cI{`r>Z7|iOJ=v>fWja2v(o&2PNPrI>O7rAd@ zVdrN!Q}+`fCD`XnP;B4L5YETS!n+(#3!K#c`Yo{eeQ6x^^fFdG8x*O%l4Z1 zg2gakRMC_|un;LwIoo%blTJ8Fq#O>lcbr5jZj#A}mrQ7mIPZ8c*}UeC;ci_Abn=lg zd$(*fQ4V6LvqWh~gD28c+f0_-8rhy; zn$vb%Onr6>4HH}lfs``2_THSyF>sq!$VT@WUxH-Ex)7j;w+xMF+ zi@`ekIuZ7B4%?l9pZDT!%%l?ariZhJy>6}67V~9LVOc$>an~QKoAhF$wN_Pc_eTT5 znwkZp=S98E<=DJkaAWI){7B)gBnnuD#z8!v?P%|KQ&n}=iHqHH_* z6_yfz15UK~(xqQf=2;T3JOhUs^iMp$BX}><-8M{A%*PAfAjW2;%;x6i(>2eAldaAX zaa5qe4%PEU`N7DZ;a2JZ!ZtYOfrGusV&VfG<9`SkjV|y#%h5oUhKmIJf84c4Rt0R+ z@zZ!--^lNCo!6iYTbMY7+x6umQ%_)nFOnVn#D3IZ5T+{$7UdIre;Md)f85#f)I$_i z#8g!!O%wC1P19yM^;4&KiRwz@rYcRloAQo-$zSHLshsXS@|)Y8wfFtO8OaiKP|(8- ztrsji$D4QZsitnhab9i9e6PN7s!237ddd2!CKZntrhwVAob5|H-QDeG-{xnW?mb)G z&Rtu~{@v|P*Y-{3;FdMcj;9_s2im)WXWVmLmNqZr{b7JKxZRHzWEg)G3eb?~3ak*A*|}@y zhZa^Z{4e}$-#ALt3HuB_5{9gmF4h-)TnUZErKVxwA&0}0q_yz)*>BZjZ2{z#s|Y6U6=j|Dr) zUU!?h>$7il(mOWw!{Nc!g^DTl=G04G=FM7qwrQNP(3?JQu6Np1Z^)nWsw+*;{$0+# zo!iXr_3NEITc2?b?%Zk)>_tNE?LiVn=wd#%qN>)L($tJ4Or+(;Ca-4d9Ipyfn3X)o z8>V`x%4#o(eQBwQ`*OWbwx`qS*}dEB-LuPW-M-P;w`r4e;K?V=`ak^2+27kb5WmBo zqp2#gtNfy5?n|33n;+5TY<=V&=bRh*8tyx|2+uqca09FJv}*g0Tw?I*pl!#(g0<<0Cc|L|dCmydtf1>~Zf`V+wXFo2mYB!I5j|8tzGu_DPFL#=XaA1f zPWz5$oE_Vqa<;8o<7~a>=g$5f*)uuhqK(r`&9vj~Aqx&mm(em?b+@)9d3FI7$f4b1 zxijX1$d}W^O6Ur+ea%|a*WSv)t~z5ULL~(VtOlWeen7@}aZXBVp6g_HZ(_nYUdP;z zbkZRM7Og^8G6ez&^NA;(xbB57eBmVsJ8G2b6Kypt8SvO6DoxqA&#bVaz{lQEmCy3E1LLNRKfrM@#nyV) zU3aausjGJj-4Ua}{Q2`8Bw(diUl?nNpVT7!qGx6mv*u4#hpZ%MWj`2QManFF(+Bce zi}mCme$kI>q+yz=opl_Gq?o^jnde}79jyn=1OI)yqbmq_&XsTR8ke2qwX`huYMQ5E z`Lde%tFx6>5@w>*@~qn2TMqj{_;$`pNs^oeWj8#$3ke^8!-R7jd*r7?UPSF zxe0?F?ZC5Jp78JQ_;@bbvC>%#c%ZXz@So>t?!PoIB0VmA`jJ0ws=DIb(rooITr{ zGGn@_X8X~UrfFV``CzxM$*V{t7ziXd2svje(Bf(CI;+7%j^lihy8FmqF6)_I{JT)D zXYvdh60ESUonkt(72v@@!`65ypBZUr6|{Yq7J#7&akMJC;1Js(iT@JbLJEwF0t|?4 zO=DWFWNqen5TM;l^BW`mgWvzb_dhFGf$i$?zzyOK%YYI8ZGVG-{Z-5Z%kV&{Qe==; za2G*6m|}`Ov@>|#%EoIaSd7K|uT?)QOc0^Sd%%D6b4(WGZ|I7FvEdC650`;>Ukc=N}2%%UCP*;NbLAwYZDF&F8oO}ub zpj5Hil4IEsq9H0_wggQ%?Obox^0Q6VHE+%5I`%tV9j&Ibb&u1zcc1AvaKQ9-w42`U zPSexdiBx+Kb#n%IFg4$(M-KI~5@`3*PEYGDuY%RrHAvBoZ#~VcsHyWRDl1JzWy(Y< z>pZ8T)+8&EoZ40Cxv`{)aZj*nITDN0T`+zHXW4QVtJJ)$Fixu zgEDW+cT8oX;me@=?qvIXTW=Qpdzj9Z0nS41tqMM9mef|-6mO`rl?oO*3c>6x7&sUF z?1%6tq`=rHP~d&mrhEm`uVsEfIHGmR@AG4Xvn7@wV+DFId~QB;p*a&jHQ;y(#M zsU|0fOMF-c48x+cu7`xeAW-D`rcJH%XYC7Ch0= z4>fhY7wf<^V8FIBfB`>F+*cGEe^5BV&w4hf)+N_ zg#;o>L}6IDO%t~2Vp3Kd`>SbNRbWZM3MLjWD7LvN8iqJ*e!=ruJBfUJ_~1Jq2aU_M za$Byjm8@(MLIG&_d#q7_`mHIr4AYeJ8tNN9kkaS^P^)g!mR$N$V^iv-nf+)~{5UVZ5OmyFS-Ql=7Baw4jOBGRPONmhD*nz4J9BS|c9 zal~Q{6}88luu`*@yp$GK!Rf9m$RSUvj-;CpsygolxfloxIP(2QLGnx(+ZE6SP~mS# zfzl{o`*S4D&m@v|O^_Jip$b_V%rV8Hoe765hCoi-v2DjE6q&YO)NcR=YBNYQrcxnq$4||ulC<0q~EaPPGu+9fCt~qQp z09G<*uc!t0+0TAf3%jsUXx+Ff25@Yz4FlE^T=C@o9k3vWeIfB~Vc?*zyGuQfBl zBnAu&b+J^vVpL+b8ZOy80SoWeqyJez7{K#mWlP;`!;!jIhu%}~fr&~QRd1esx3X>} z4X&824nl|~D!ja#v{Ej{WMFV=4hjRqRUkdeg$eex?s9i++vyzG_>{AI z`v#|N{|?i2aEG+xCXuMbSjRL|zvwt`&dFzZQ)eym3bVTa!WAlT;a7Po_~>1iZA#qb z{BMP57H7MCYX3I>vo+lqnCS9D6+VT(Aq9$|0QFDncd{JEdR{|A!&Ssl1ssi66$84I zFqRKBG&WrShdcjp8+bj=bYw&;v`YatIL>I9vMui`U-`ce0^ZmC*6pc9>|c z53^)cS<9FTRY<>ucinA0kq?gw7hGarh430o$Jvt9&VdN6>sLEF)~|JTZQ0~>@7QiS zx(_Ihlf)oKeLV&@W-c%d^X7UDa~EM!^0<5~RZYq)EN0cRou}nZ_L-e{!s|GDL% zh1pxFTprjiE`;Y7Xvw|~)6v=LIq@1NpXuCBjDP|27`l)GW2S)RJ8U8Sykh!1XUss# z6hZAJ3;&2NJ9XJtdA^ACQHAPXh6F}rz+zZ9sa)GW|M}0aCcTTfM4Yimr$eD>I{}{o zqkpqHb{Xdg?8pukXCyyM5G2UW1d<6y;cWfh_{KNxx%~3Ww~7J#1!>bB3E04Iz_H=M zZ6W^sxUyhpDt>wwJxbOLfRh-wr6*Qa9#GoB3Uob(?ub&rHhfatA_h#MxY+|cO(ctn zYWBKH>8kRQG@OIUN=CNt_)D29K~_JKe#NJxV%FTRRY-fCX3cMZ>D>0gzm4=FRgd6; z`f2Y=&p+2a_2Sne1gFf?YaVvGc06OUk(8M}cfOgo^pyOQ53Y3Yz5QQF0f^=vaLq=ixC&{s(k6) zW=b?rAcezEj5-I;1p*A1?Z4^v?R*K^L_p6GvKxL1DR77a)HAF7+qiM#UtxdYg?uT= z9PuHDWqA^x%+@v3UC7kto9VB=Q?Av}BQjtgbupJbt8?##AQzLkn%=k^LtTQ>g5 z{QkTDWCw25(_1)HA!qsy9x$D|HxqJT^8q_Mh7^{ZnuOtwx|uEJV8<40K|krtZl3C8 zSW-npn_|)&Ll}$4^y$;T@R5&vObF&n^Al6bGtr7wWSAC}1qLJ@ zlxK;t7DwecR(sv*f1G#@ed{ot0!PyX?Jtgp)3vwjN_>Oh!&cEnZXG0j4AZcU?|=XMzwo)weeSI{-gx7<8E`S93(Mi}yYIf$7>UpUE*f?F zEqWw;NBpL?n@5A|*45S7mBg1_cG)bXOnN!%3O$1VQpC-M2fu&;Tl|8(v`Clz%Ug?p zHB=A_54AG~pB5Yoc7nk|;8oHc=4)ZIfGn8*Ev8ILN-K<16a|7o>1v1lxKw^GjTy%% zQfd}vz>z@+qoG|4%C2RrQ9!ltjMdw3)Uwkm40#;LvS zeHhy?Cf?NG<=lkR2?y=TN1PldEY)$&uUxbe)h~0o1vgUD;TufUm2{0uHOw@9til#2 zMFu9XSjgf#Tb8keuY5`Rl~fN?w#>JXpF-Xw;zl`2k?mGn?AG;&m+2B%o#Z7qFgFFC z@HeEuFck0wkIe6W`Mcv-cy*m0ENF|v#5%Ix;Ap2D5yal21!DBw*}Lz)dt5k<+S!Xi zh&Y-{MM?`+c+e8Z?JNxDX6FeKE1>5gWs12=A2(7jqTmCa8hQjNH@AQPek+~wZX5sJ zy?gDQ712*gm8NjyMfZ{%MnZJ7p>;^_bN$Eh-fUjMag`6KbOgtXNf>`11^&c=yY@Y| z^)V#9UcXjSJk%i-l9h6)R)sqZOsZ!XmELu>Zh=v04wz#K6L3+S&h#DC2{>2X?BiI4 zz0aI+&2?T9fglS$aXutjGLh@+bmlBQG2e^{$OD_#nYxzaP+en_+14f>K%p7*Cl`T} zPd)Ag)4JsubLLB5>DA6Z(d(wQX|1+pz(m7Th`nID+@*3aOz5POb5AlfhTL5&r*4bMF9E{=)XHV{TIm&)M= zo55YHGeyC%!j}YK^N)nSl=$`;>3x=;mMsxEk_&=W0+l59rVI@eZV|BO!v%o&X664Y*m=^x_nY*M&%c|osv*4_= zZMoJ?orc6(38M^}o|4lA3q(Ea#uBEAFbypW&4EpYB@N48{g>=u!oUXyLOAqG+E?T- zAV@aOHgCA)XTp*B+^5;n)NN8zXPVj>Ev9yQi>VoKRW`SnTz9wG^Yjzuq(v__m%Zoj z^I1$;N@TZ-wo#X7B;^Z#9;Yt~%QFs0$O1g54*H8Uxl>UO5Owpx3GKPn924<+MTRK!4) zih1MdG;b2aiPJM4m~c=VQeL`*xI1T z7C~g_4rrhWn=$Exn2?1mm94T?rRwhYe)Idk_xp-UQp>${Z&d{ENlty+IqzBSIp=@g z_q^vl{FpX~hTJ z9RqQ?1%?a27BMu>22=uMnT8Z+>AMUHSF_*!2f?Ku|6jo)|NJd$FXI79EVv(_bjIo%q&3;$10e(c3NGQk>7Tk9hj&3xeyOSe5^e zrylix{m@tooPb~2jVnJU(+`=)7mL40nbm}XKGK3dG{8?Jsox;3DF!S4_}#v#3D z^CJbt4a?n_ndi73CgyQ*g|skjMG_E6Y%8!8NC~VAyx2UEXE#p*a6>hVgAT^K_W-67 z-Zj^I@%}sgS6&W*>!@-~pV;C~{j+O=IrD$7c+%W6+1fJ^eXtZ5sU}EbeR>?$H(U=H zXqkSpTk!GE73Y2U&x(gT+Py=&_IQ~C`^gvFGLwUbNt4}#lTHjKo^WE&&^Fb}VDnkq zl;UvVB@F~LyD1P|&ypxz1@;sY;g7g@btv?;U6ycJ;(OYbwSTwE{QEg{Ri=VdS?0b%K~6=Wg#$bLmPS>0DKlns8H3+EG)wSu#~WxA|S35eWRkH z0UarSM6Zsx0?_@Bct?a9^g%zULlNs;bLY-I5Uy9(F$g_`Zvu^T*DgI18Z4O7Xa~DIAc3AqJ3rjT|8W3y5{kH3k5)wMcQ&W5&2s zKKK{G+ZTMWuy4;+uWLUb*_E^~@i7gqwQX|HG<90gIC(O|MT0BC;p#}j66Z_Vqanpw zqDB-F280WMsn9;&GSy9*I3t)eSKU>{I+QMf>1mv}#Gw#*FOw!Lq!M;&pbTzEr0l`{ zmPF1ualhJvq=^LN*~>>+18uHHC6zEtCX{>wf+T}v2T%wF%sCg~FiV2 z;)-*8kHaD-FdCHroROH=(SNxlo0Y~+aZ^v87|fV!hCDz@a8`0`Qe|hb%fL#<(JnT7 z(ukdROk$3J*$jXR!uZ?7zE{%@>PN@OvztAW|RWk98vf|iC`Uo!+NM+hOPnOPj1hy zJzqozNQ_vmwjHT)`!I|5G3Ngr76w1Uwx&tEi~2RU)jP&;iJcwWbH4?(pK;&KJ1!-; zQaLG9JllX#rOwC}SGoda1fta^zpeCDUn_%A~zyGUeqn?C&*}8Hp}pUpu71khNt0nf7Ab>OfaM^51N3l@4m) zFb2;ThbvEW=aKAai`47ho*DH>7qGQ8;2dmkR?mo;1;$xTFjN-am)3?0oGVPT-@eu* zH?AtOM$TTO^mc0fk+9}NWUtxpP$%>ztbq``a!)?_qCaO*@qidR8W3aYvasO)n& z8}h0!uA~CPbrArsf( zq9sI0K}i5iPXvSoS^_bNakF^94W%W$2buvgTXY4#LTm*r?PMeC%3!G4CasL7CXlOS zeh0c5{oXh{+k{`mfV=L`k%oX(-8q*ncwjK%XuMq!scRwu6>;?7Fzd;z8}qN2?sh%T z{zNGif;3yTeD^)bYlCx5#)ST|T7UPZs{w11Fdu*4(Ac1MBi)z&+i3+wXB@S0l_kl4 zJ9qxMs}&W!o&THcNota4`*wX>J$F$9FzAc`mCr*t>xQiNCK?-|1(n`7x-lf@D(X=RrPrlx zMxg?kG`T59iOWS4Zrad#R@tl^Ubf&PeaCNIf&V-^=+bVC*j)o#?+s&)}haf07nB1Yd}e;kJ54j%Y=NW7+PR9HY72Krca zTok5Z&*A-`mr9V2bZoy<5g@Ze14z8=ga*w;l^@Y?j(~MgN?l!D(OT=E;YR7B)jfHt z8{inF3Li!?VP`FV#}!iByrvTp`K_yq!7Iz$7_4M@MHHprcn!GVBn86a9$6|i5j3Cp zre{`Rb;U~*ZHL|qcL~?)!AY5R)ggl{DpObqrLD|i*w;arcziN3IhoT|qpUb#XxSRDM9#{uCJnrg%9jby zd`@<`{8M*|Y(C%=B-}sZIli&Z{i-tRZs;0_wjIryHS3n4Lst|+coawZs(tqC**{fG z>?&k+W%te~3*Gy0d~YoO$oNgv z5Gh>slO>xdFe_Rmr6*jR3hF1=sJ&9+K5NpQFTVJqDUb1Nc^tJI)DtSF2J|~=W{!9@ zc2m%UEtf=d;ow7GWt;A6t{8&@&gMdKw8YI4JKGv+D0bGf_SXu~5JhXBE!xI5UyeYr z_bPD4pi~T1uD$Kvvb(?KZ&~(;-x7ytf`cH)gtV0-5A1@czH1!Y6u4xMTk^Hf$8Ni9 zL2T_K|Ki8dye@%n2-XTK-`t7lCjWL!jR#6=(N z?^XZL<+iOX`c2alUOu%G!Xv_2K)3GRgf$RuI|{aL-TEm2>uids0lAu`yuP($V<<{j z_wq6&hB4g1W0EY!|ER6je5_Sk%E+XtlfHJ}efP}-ge8g|5?_Z5(6@9EzyWwKVE96x zsxs}^mv*K16wW{l5$?g|%a@x-JW{yohnB)R)*Ch+ZL(1~fo`UWGfcj`uu!iV}<#`Q-D90n%=l!7yY7acve`%o!B3q`{m9%u)bu+U|LV zolFDFde$D?QtwyS6EME+v{Eba^V1JM;6L__FZ$p4^q=~xp7?Pr*R#*XQK%Y&V%1pY z)nn=EaV%E_xr{roeue+!?Ki|;`1y~F*sCx7D{Hw~b}qpc8-2ZweCba1xzbETrRkgj zVKabh>7Hk}^$gMmxXB`>zdm)x}Jw`cEOk_MaL0@=yrb*;$nj}_|)4k zl~8vB*MP=3P7%sobkRjE7<9TR3|SEd4NTqIUmWRdanq(vcZcC3pf#`(7Yuj2m2YaV z(YHZJmjB5H@F2*eq&x1y3on#bw4C+H(ry2L4Ym9KbnM#*;LbYhtaY&SThRud6#`kU zD)HDv2Y3^v8-t_Wbk$W??Hdfl1a3H~jQ8*Z{RXRBC+Y>y15h9F*EU$e0h=hF)HDM$ z)_lZMt|u^@8c_SD6URU6(g$D4x1JcwJ^xtXzipmZJnJ&&<+=gH1Y2$6fxyfMz+$qK zj7S$_Ss3o%$~(!M`5Sm>LN~H42}yHr4^>MPgQ_+;2`g1{C!KztyZe7%L$JIoo@f;SKNCpzY60WjhLlG6m3NHHFP|i zWiD~P056uU;B-sMVkK>7_5+hosaTf8(QxhnH4MB<9PV(PKfO(a(96m*6)652!s&Z> zR^8OwE|pMsL)L)sCU@6ecYPV`n5KJtS)$q@DTnLG;L^|M&!4}J;ay6t2E1G~+-r|+ z2IleWq^-4DeXA*ML0{!7XkB~Vx^*Asc}bY=fc>K)01gYHf`J|B6#`%q)J&qL1%X~U z&Oi(<(!|e17z)Ic$6~l%e1XbRP9h^2hQ_j7^dnQBBbip6c2zZ?&Prd&Va&z-4~fkw72J6al-0i9XseO(A7lln*EB62bni(+E!qjtf4+{%X#K_Sat+X~#Q^1=dg`gSW4z+?VO>;&KSR`? zwL<{*PK9GkJ-S^Q7{diGMnwGucC*YPSk1AEa#Bpsu~X(&=s&@ICVZz3c#Inka9Y6- z{r*<010zycc}%JLc@cpepvNG3T%CRhiAoAPcI+_aF_aQ&Dh@fWrF-)xZXK9D`xQFW za|od|bI*1E%g3F+e+$z;&M3;}Y@L^N)ezJW?*d@5GKFN!)`Bwvx|}#q`(_GG^jRR- z%x(bS0A%Y#cj5J4D_(iq-NkqR;idg{+^CvQJ@sUF>9v0oT=(cR#Zx|Tg$_mpeEaPG zg)(RKq8vtgtU;%3y%&QU$`jtem%OlF86zA5V>}av0^6o?g^ZVtjdj7Vmb&!6e&2ab zGm3z(lD-DN8Lv7B>xH|ztJlE5HDI`L=ILiH#H6oqM_l32AUSaC!M|wyjIq^dE1JlU zD`=$X6-5IG>9)2`n>n!eK&1Rt^KqASR5tAme(SDXyS^HxKj1`TzziOXV0G1d0FW)E}X=Tq($7l zcI{fTB2~sQewN4c&O7fl*0r{&47kzGx6-uwg98@#%NS{!wvR_Xl$W}ztOlZiqn+{N zvSMly9taE-Zn04;?c0KWo_XHDWof>#elL*JD zN-3}qF}J`U3^guR+-=GfzzQy@d&JI3GM5C%Q>2>$h*JP>5{^8nJmE9hEcwbIK5y~T z8-DFN?)bEGW7;^T=|Dpe6eRWfR9I!!jw`Fadg$N98VJwKl|hV%^Q>7lgwSK=h_~<9 zexC?O%EJ95BB&BL<}qD-v%Uk4BhijE1J29*?3_7sZdW?Ee#iKV0hkP{5V1vz7Igt4 z5%)O~g(zja|G$2n$HS^0chJ2#COnB9=rrhOD7RY99%%-IiE`-=8k-Zb!VLqjM5jqxCqxP;3X;J zb~6CH0M*h$of)pzMByfKRvJtQz+bNHQRZGA`8DzLu(t5*8=iH0zWoUo#2Ry0z)E0b zdK2ZoB&>3Azx@s<;rIHbUISJG+IFNHPSU%EZAWKvE2)XvPfZ--x+cXQlF;YSuwDT+ z`Bz+Vh2Hh+2(SL@X3w5|H#Qi=1PPD_46m@mbO!WoZEf$N>{pX+F{+Ec^eP76FtW+G zP-xPOShI-H?u?cm695C!a8}c|Tm#)(zNJ7mP=HZ*Z*`6c|HUw^(XP8uDB_zw2@N82 zu);lDuPt`(@-bD(9@!58sI&sR0Yp zb@yhf0mfz9cJ#?lezI-q)TuXyWvdzZhV*RwWIVk)d>#_hBobHHo!fR^7seT>i{*vG z8Wt|Tb@4Q9!GcjdB7a9bRFDfRbZ2Mhb7334|7&-6x9_z~^RsK7F6_SJvo5!N9aH`B zhfG?Nf$^MARuJ2RS(DTrq`>=lfmzcR$V1iEUZ0 zA)avAo}H9$K7KpiX7ya_u3iJ>)PMoq!kZUVZ*c3SAG`D$ihaWkHyly^BOWRs zZ=mt)v(K)A3qC|V8RRffu7E~0K@B*|Rltf8iWIT7Z{NNK@Qy?wqn2-pA(R)=Uc*eN z-@_xKywY0djd1h7JXN1KVVG@`39YUVjh>lJZqq<2mcZ zmH>)j*Mnh-qr8fLB@!j?V& z{DSg<(*(#s*`Dfu-e@(T-&2Et`q6i3mOEVc-Us}~)<#*L+_m_cMDcxBdu~`IIo7b~@MbU3NDJXXB zWX(5NN?ISr_uy5&k>C&@TX%298o+3i73;LqPSds{HC(lyVd2dn^`ptJ+qrw^-MS8< ze5KDvIIRF*S_-uPd-(?(`++J>pAqccC7VYyhPj*Ij1q6Ve8DS^``OK zcg%V)n%P=k79B*|Mb0z!8eO`x!|nLlo!-W8enQ}z^O~pm#Y62ez#Or9Z~?za9em%= z&GPtUK057a-7D)pfYFT)k&9@OTFVTHrl-Sf&N$aaIL7aO58VTk0X5+?T330UUU@a3B@H@ob{%}_861S!=}5?3TD0mN%WwvJDDFu42Lk`#W6EGym*u z_`-X=eLwwnA=Waj;59TQg2Q_lgkqP`t*#A=tntAHsq(6z9_l#PfY_>XnURsosuyiL znlW=m&9@!($+VU8@S($ZhwqFV?(==fTh)~`lNT;rcn}L(H-?i}^l<?7d}a3@Zl_$7(uNjoPoy}cYxY7uyB-n@AQtaTD`?l|ZOP(~Vvt>V#SUK!@Q()27o zGoD}dDYw1r^IY>=CJNf{9b@9~D#pmSC;>S45eHydKnk6ek$gyNJ$g5+YabdTT5o;Q z8CE_ODWcK$I&5M?M~w6e;75y}in-WNgYJEc;(p^>z2=i#_e)E1>t9+DpM2hB-qiV* z2IJm(HpV+xA4dBanUrf_ZdpK6I;PW%I81>>G;Qm(9wpNrF~rgQc#>R~a)P*7awk@U z@B&wfnF}n8!4*SKTavJM>56eqQ_3b?uxE$c|C{H%9glt2_3YaMv3SvIZjBeTcI~?# zLF%(GEBlD0aY3z8G9aoC_@(q9^PJHkvuYZl!i`TDTzm`hRlJADszmD7;4aRyJ>%Yc% zQ+NGZYpHb(d~FgfIdy<&h8&LI(WlC;VFwot>r|vslg<^3#UeU2&uf@x4oT=uqq0Jm zAKGA#;2c(1c#P{(znK4$>ukT)AJf(tYZ&YHEq^$Nk4>C=wm)^jN8Ff`&k4LKCpdrH zSVY5P5bLm3oK4Z0GAt5t?y&4dGp1@?xb(v@8tO(1$Qp2&WSJdl;4;HajyOOo-w|+Zuen37yx_GjkqvqS)*#+GJzhAx*AEWw{}0~%4J4k>N1XcCNOQfVVw48C=-v{q z!55MIedaTt*$Y-H#sZg4P%IAoUle#e5slvW<+Nz z0l^H7;&!nC9bmAdC+)g>x?FD828SgpulJROOX34RP#Uke{myR@5`D*0y%&D9Bozu0TN4;u%bI?qZU+YV? z$}3|-0lMR{c$OMYUd2Hv9(gQ~M==vpEYhTmEY_?(3-rbDnQTVC)u4_oHXh@v`_P9z zbRC@Dgb)t|I?87)GGygdCJOmNV&|@%-_w01%2x`1v=jJJXVP?h3=3J0Oqw+5Qu1#4 z54_`(O?QuuR?)NQC<1LLpgtR$Tblp&=9_O`Qq^DtQDgg;EnD{G*4Ea4WEM8kA}hE6 z--G+jUwrY!J5(U@Fx)f)&Jq3qmC-aB`Bq-}QN0ZtHkjou@;%aAhW$1y5qyt{#!?L= zq*;qjprV*l%cTC|D3xsqUkytPL|lu9*y_PUhYqb)-HcRT*}A5Et#ZT7YbkblfAt%y z&IN9g`Ci3!VUX^;x|r_#uU?{gM#H!kJ4xxl@<$7J8>k5EPd(>-USignExP$Nhf{nunI;yipr?RvRxGK z>8H2Z$3HP}}?mXSvr`&O=8xt4Z4x6C9f4yHWfhnyrtb=J|* zu?Xu=-xkKrW33eN%a3+`m!tU3Tqi=7t-QPH=Q4s!glf_($hwu;I-bcD|qFxB0!CRSj#n1eEJb`!>KOS#X-JZzt8hL&yUaX5>3rs<2XNvu?DQPq+}EM zC~X89cJdKx!$qCqkOy#)oCT@FJB!KA)qGs$im|1D?yra%0l&zJBNy;S?;?t$bydUk z)Q?BPsh{EWJ5y=ZJM!xPw&#`c(8S}_m-bi+mDXoC=2){mmRfDz==Z1r1{{%@K4Z{` z<~>SOMKyz&S*kdQEGezB-^?BELO8fuv*C38hL`3Yr51{dLbO|CTVo(wiiU>+cr&5Y2(v5V3ba!_zyX>-T z_w3C1{t$NOoZt8R&75(#u896mK=*(D|F2!iGhexeUB3Em;0MO1V*U0dqDnI_O{i{u*Oal!)Vo$xbL^@QeBLExsJ1W)RiC<}z4yDK zq%w1(kjd14nwZ`R6*BA|GW&XAIq98GhNcH|kmY;*eBbeuZ&DmGp0%;kyPTx=4_J}? zyPuff*<|Yd6iG~-8S;*%z^N%nq;*@_cQS>q%0W6W)O`n2nSDs=gSOJUmMR`cVqbLC zy<4fu86@{*cip>`%AG`lqdQCQPAYyFNq*Myok^jakmy_Qy(7tMfsHCW5%EqW-Owzg zd$s?&kXpqc;W3k2!6!c~{O9D2Cvj<67OM^ODBE(fGm@h3U)sN7+ShHXg?Z|NFE!Hp&!d1XTi({3#8 zR>Yv0HGRzk+YfGV0vb;}mx6?5-}|deL7gV%yA8ScsDhgtbCr?DQjpxN>$4j8XkcA4 z_<+CY|Cb>ZF3-WDI_HlR^5MFdGSZy5uTNo}#uab?Ir*a6|1$I|;$z8wy~)$rqc8RC z*(@YH=hmbuMop`GCNFTF{(l+Uk%Nc4oIA$qGat!Fee{wBX6?#*Cvx*?d3QACI)B6< z7vono_Ry+YB=ZBuxt}rC`8y7|nXsa+QIj(Ki5$IJ(9Mj#>Tj=*tFL~mtka;%JmE?9 z$L?i{O@5BteLB9d_B2H132rdEkqPX7T}BSuF7@)!n7sEQS0l^2jWIUeYe7zDZmg-( zl&Z(_MIPWTrs(X~$nnTgAzIQf;ntgR|VegiMS_LA>*kNfR26+&$>+=B4ruD+*)M zq#zB*w2ePcOSpA0bh(2fY292}`zd&nKj|&px%iHbN5Q<41y^n)sckwlfSlyw&KYMQVHn#WZ8Ozos*rN2deav*Ykc!Njj&E2nVW;?elrcr4qz#H>xGsPb9D7&+}72~i}6k&N; zpe9q|8IN*4afpESqrQVlRzFTEe5N`Q4^SebZ39GY3i93XJ2?K~J|SjUrLRqZ(^0U& zu5*`C&w3Z_M&Y#Pn?os?VBgNK?q_~(D&z?Sg9v%BSB}*m?7kg+Z&{yWpD!qG@p%!AY zV=ix*JN)Ah-mg$9qFAwF#Y>c_+OYkXWB)vKKiNi6YBw#VRy=pU0k0y|1xh3ocTaz0U}2G14NP=e0#CgFXzC_d|u%9iC0g*Rt|+@gGzL4+K&vRk+wROww~^qCAwFuIP0(jkj8v%nonu`5Vo&XqBhO`u*W zxZ}nILU6sec2ejDU(#B*L(xq|3N0u57Lq{ao(-4A(UIO&JCPRf<$Q=cl-f}QSTFPs z1r|*WRwq+|M?y3cXqn2D3E&3BVwjtU{g(tko?geg8x@NF-uZsbQ zta^|DyihQ#FCk!fUMTS-#LrpzH%aWPS{8P}-$2wWB0Z8nnQ| z93Jx5OyGMP7qo^R28!SJ6&9me6;xD7Zl>ai5{xg@NM06|CV{?F7nP zr!KiGxI-BvvvaNt&>Vh%wez9c33N-QO`c_PhvIt>nSEFnsIGLKv5fW_ifNWi+TlXr z4yAE2Gl?Gq-RGa6!@`0O_HW`dW*9H0; z$FYQqBuxd(-=|PxTU!v^rOZS|XXrU#Sjg0+P;CWFLlSARE(-;BDFLS$xm6N)SosM{ z$qh9YFnkt8N#^W8aGO%|H5>9s;G#5*CHz)v0bQ$GbR>M|1@2P@BAeHW@Bu13VQI5T zYXPXbPbT8thX!z;qF=$tNZ_P2o25J9+6xr;C6xl}-s1@eFH2*~cA}LL+^h6OCSTV9es;}bXqirn z0r2d3(ZJq?sasol!2e1Al|?*d;%RBFG*4wGv#n+WV5mO#v8=NA4Q;&P|E2%Rnx-+a z-o%yAi|pVq4F^D9X7srX^;jP+Xy5_=H~m-E7g?110avB@3@wth8~`b}{nC3DHOMhL z29(gl|4;vw&0ykHHNHUi9Cr44XgdI4DB9zXD=ErkJ>D{;iYNRp^&gqxA103bai&Zp zJJ<4SJ^-N4*J${POE0tRkrRJ==fn;XX86DAKeB)`O#BL*^*O>$bXhIvzlPw7ZHCNT zbLhhDN6%hFJ%4=n^0CeHzVB9}kXeHNvHl|~_Lv1*Z{EN>c2XN@MQ@Z0p8f@kmnv7T zbcygFZ<7T7d;LdNBawxKn%wClGn4bNru6^Ye`IYkS-2g>oknTQ;LvxUd@i%F&yzbP zpRqIT-6tcF#os0F1fFN-Pu)9DW-u`ZxNF+U&KBdlPIP~Ax&=%wpzM1vh85&mF!(x2V&Zee#v6TCdf|eTR;oG<)8{-6>{V*ERmQyAaK-jbzFxx7H4+6RLBAcf`vXBn8&{ZtLT0Bv($l^jr6mW_Nd+OimwN!B#U* zC(1~*<>{6oRlS@#l1^_hdwTo$`TBU94SJpAc4VdANBaOSFcZd zcI()xVU4my^ZOYk$3|D6{rns0NW2`g_q*!m4o((H?^CF3-S(dkpZM$F>$mU!_vo>c zCypLDuxs=31yjELs#E=PVZM5IA(Me^$rMt$@y8?PuGx3?&hxmW)Qs#Lt4)?|)|{-2 zl!VyF*G_Ezb7J4NRSJ1Yj>sB;Eq}e5iCmOl?O17Xn&<<|H19KQ-O*by$yqYmIXflx z&aw4VziL{-U+4Z~7(<>#8-6)^+r_9!)l;zU8}5-}^8{MpdUjJv6I(Id91!_|fj$Uhv*; zjbiLLd6nrl=kW6^RDbH7O(W`u=-hT>wPL_nq{GZ3(K*OwPSk-3O+$6!_q%7l{@+-d z9V02Y;h24q7GyX1!Y}O$>fCdr)K;6h^p|VyXCd3Em*%t#kp!<&*`7`!DY*HJ^U273 z=Jok410`1reP9s%CSvsdffRet;}uA_uOuAN{p59Y|6pvrdp5o!@ou%L6u?6X$O-v6fQoP5L z8sB3`_;e7Ci-Bs9!uuUeL1ME`ejO$`Dl*doR4E3jO>M%8vD%cIIY@5erq3i-Iy{1zC)f$bi&Oh=L*7Ne%vW5RMi5|4wP>d==NbKQyrTQ3g} zlN=A3MzN{*k{leshDXCC(MWU3mX;n4AGC<)$BSx|43_!u;yrkKt4#WCjlQChSarcr1{VlS)i}f|OhSZRg=2$Rs;VhrCs1Y_KK~ zX^)x|?%+YWd;Ey5Nk_F9KHMt&f)tC{hL^(N{ugF>N7C z9suGoSSg?Gr>)4rdwqSxPA1<0N^PvJ@SmO{7gK&K?%0JK=0|KDDtz|w;bM61I$P*l zsv|`A7gh1^(Gqf%u;hB;k@K-K#s-_yk&l_H%ZZ!JNk<3!tI4nTB^&bcVq1eF2kn!& zflEz9)QsbTr7z^Z>{cQ1$ZW2JRA$~Qewt$ zkE!|2Oyub6AN?IUkEQ&GYe2*g$i>6KdGI+*-{WG0REVYu=h4zGo3PC$@kp$pK;Xc; z$kl{#{sJcR){!~ntrDr^yaeQ{Wle+w2UVX;2Cg)d;P)>V(z`-l=hBzbkdQ7GvX1(a zG6D(z3B)6^wB3wN_gRp$mes{X%SGc`bAxYHmF&PgDyk0OFK;4$Pu6zeq|us;gr+3a zjpxD^pFH)nk|%#b$V4PNvJ55C62h|D3CQh2_A2p99CG+*HL-H3_+MFBL`1d7vcr{%%RIqPR1 zuTQTbpKmmg#L1mWe3!vzPm^SY{wUaA1))DhA*Z80(K~GLQ93u0S`rX&oC{0Mc?&pA z-xd=Yg>KV@<0&cfkg&r(01+dyFz1M2B$`VV6Tiq<>y2ug^mM zKWr#Co=sG_ypPEle-7-YK&4F<6u=j495v{j$qU@$$Jlh_;HAR$(J!a(Pl<@qk)$ov zkr8}}u$6iNv2@Ly`O|wSgjXtyjA!E`_o$j2@#mOtH;+&5q8Q$->8SZ`uGU;7^g-&>p6q4^bp@e$+c=Q0gWx@JKL2 z1+H*ltB>7`>&cUQ(U9*NZ8(#R-wE68AtKicyrt;hStyio?L@?bQRS}HWX@j#``IC6 zYBGxD>vj$rd~M-Hc5jB-Wzd(<0_;DMJe)#9&3NMWdlBJ%kgzErBBy-bplf^@Me|ZA z@o+w>+_{>J=?^h4Z)LYyQ8X`=bkKtK^8!zVGNW67^#5&Epm{oZn~fxVW+U!#1QFr) z>9TYalAWgic=UUL;`z6iXgHpz+`XDPe}{u!d61f3LIGXk>6pO>3B0iMVJ7SbebKc* z!6Ns`yA({qTqHi@10vo}r0Zn`Az7vefZ&;FD4-c%3Wn2($_G@*b)Ce&Yq6&Cg2bEc#~al`wB7?`3%kybn-^-xJZ(M%U>8A(;sPr8ZenNbgn<3a6v; zA=PBcUm>Mou6)KNqnO?(?U+G@2fWDnni2gUNdCiepy)XAvf5Bk{4sg!jWkR|y8n=b zYy=TTIbdkKWYiOP20T?T5V{1LEUZu0$}jiS2P?3lr~R!-o>LTnVdL!LYalx>$q z9S)_SMLK!c^OIm$PuKtu5&28#@(6`By{9-h9F?^`K6HJ$1=S4sBh1T1@y#|A*5u9( zTE3&auzthFN7>|EDWXuxr{pcBU<8t%P@9CH^K>QG6OycM{a>NLUMeaI4o79BDyLO9 zfSMtHgM(ggsd^Me_EbT~3_7K70$S_^ruo&q&9p1{+U1u3P$f26jL zP;j4Aam=9lb57vRVhntmLtRF9P`sOky2Lu{Re8mM<9-wjM!J6lXAG|Q;=<9t$m48O z4l7SY2=_i>J@@VxLS0nY?eU&R8MV+lDd)=)Z zh$>IPLb@{BIcHKk@nFACM^;A@<*dqM?dMU?fM&)Mc&efh;jaLO-mxgY@l72w2s_6K zyi-DTr5;n4*bL?Lgg1FPX3!&p6F6RVUs$PIZdL)zBX5I&HIH39z@G^ybeFDweVjDe zy~*37bp z6F6B?SJ~&(&DE=->MQD^%d*xeg$wr!v)A29mhCgAO->)$HhUqP>l#iQJQYfr*Mo?h zPR+akb*F&Nq1k3z?B_hf?LLkf6uZg^oS{>b#}=er{t48nTS6TUW=vYb2OP?rel1;> zLYz02-h>&){3micX_RpV>TUrYLo?wC%$efhJWBq+L4&WdIq|Hrnkq&SceSWWpr1_L zLdF74abmfiwZJoUt<*bj@ZSKkq%V`Bv?$XG)Extkp_%XmrVrCQjc|?GLG$0i37o4} z(IBKfXCSDxb|Q8Az1S)r&54V_td)8}*Kly+WOOF0O*Ce8!bF)CsGA0G3eAKkFuuF< zD6Wo!22JBQ@vNeHJa-azJXAILu2Oe5jIB=DoQSK=T8nhD(waGQaFZudHEyG#PNEDe zQ1=Zug=QH(Y3MY09_pyU;2b{SD7AR+r7h|BuHZ(g z#Njxm3f|<#As@Cp_Ruv2oICd;2r`b(QO3b2vjUjs!cA2H&Y&6ah;5FClL(ImI%>fO zIdL^yCDmWiwqpH~!fsQyR?kwML~cAQ$yUKzWLdg8d2Ia&>Yj*-Iv8bEz`QNoQvuGP z8Sn(Ae=RwQQd>D{&?bowwt*`74oQ!Bzbo0eiMq3aEPW+&BeM-#)#J%}S;5JJdws~M zaUT_RFv_R`>B}t@;0&5w7}eBCgum+@HS-ca;OS6R_#C9|S3^4LGR9Tc!0WvCgR0TMKrsokn8{s5+P~1_2%8&S9`AikHUX%8yB9#3> z265>v81g;BkxfRX#vs{?C1(##A;vsMR>ppmQK87R+y&2{6yOM&o=0%GuXD)yg~JBl z+qi&-{M9fTNgpeztk^^1@N0%jKIh2I0xaoP(bXTEJ{R-R@@WbxYCp=TP&k&z^+5An z9tv;-P0u6Q!zHH>ZZQ9Wfev>b`C{VaM<9(R4(9lZ{_MFP-Yzjs-H;R9uIa# zAy50WRN^^VHw!v{aK1!H;5Jm&EkYGJ4Hp4TTOO*w33R|Ccs$%Ggo7M6uT^|VZmHZ@ zr2Ira^>xf9?p7gY4C}b^hs03RG_p1soj}(M(xLkyM;ZH3CWR_I7U2SDM&(I?6KHxK z!Cy^Ip=}-x8@w0E1zc@bD&PcVnLR=MrJoZQUxS&@D_l7hz))W#dki>%GTPG7WUL2M@cPihS; z&LR$fWad*FS1euGG4CYnSxKkR`4A%X6J=#=N0}6=s@#&u63l6y6}ZGBxWvN=gsTmX z8+@9TyP5E4>aWa z7nQXgWl*R`S4B7jX6375R?|`uyb|FA%53MrLH!sm+$pYvZZQ%*&%lQNN#dTDWTe<5 z&O9v2OqXn`Y`vUBJ3T0nez2)4%Ain>WV;wc5ipD2C~$?WOkz7H z&=Fq;4oY0-0iMsVK+hb)#!HOA=Qi4~A0xw%wna&sI2WMeW9L` zYf4p7RXkP|nAP&Cz#f`acr>5$=Ve7l4*F*E!1B5MjBAkY>&1Z88n20)!-j4dcUI{c z@jF4*O1+Z^XA|&ghPtBkg?jEq5~(-AtbeD#9-5AB+ zbqFvUe1W)KW+sBpap!Uo|?ejg7o!k~!&VQXY{DB4Y zP7WRMWx}+btjXX!+U3Dkt0aZ&MrjN6+Hax27MhN%jHR!$2PZmsp@(_!@;$rwAEs-y z2UCW%#4Xb?5Pp|Kk)@f)ca^G~51mN&i!kNC3sn`RE!1m1n(r0Z!oD(iDcsq!)$G_o z`(zH_Y@IzCB$5?d57-_-+zCGh8l-S2t34ByUQzY3f)gpFK2tq16>?iXIt$W~m61m| zdv4@+@XSkj@TiPECL`Gk42(5RqwPrq179PL$1>3|i>j0UP9(VxOCiTlRZ-ePz3CTG zU<=JAyjR-E6Ib2AgQ`zCfIr$H^gLNlDgoo+cWFy)q&{gem$sN#_ztOFEIE;I4oiJ3 z3VBYbH~DZj3Opw(gHt5ujhnLzrQ3xV(EUYL zKX4}P_F$*LIaF1Yu265~!_z1RHLD(%ckbYLM-K{~=74ozo}~Fm_08hI{73du|OEvl?Tf&Dfa zv~4%3u45LLa3BN49#M6(fKw^47Bl(JqN-v~sJH!eb_(p7RZlBAcP<1ud|vA~ko9q1 zKFX%*bbbONp3rtJzv{*!pa0aUuYL+un@monoNmnY%TdT4gY`D~cyx-vo~#T`lbkw{ zr5rw}9Yr7R@y(rKITD@K3kdYwOWUi;YBO%;)Y(AweQu*_3^CW=J-L;%k<$IbFRL!5tY&o zNbHhL8je(3?Lxn0Q%VU)eNcV7uBCiWSTk6cCiJNZI$hj#_y+iAVUe697KXqVILR z+?fACqSt!TQ1%6Ff9X{Ag^g1K0gl`oK1fgNlgvJDojjB86iuO4EQAkT)7dZUk}@@KWq3E;PlK3r_bMTHkc zy{Zp1`W~U}n2*ZlA+Ps&s;xo{RdYe~QcwNdte^qxD+M&1{XphTY!xOM2IH%l-37pu&I=(Oh|1&KUruZqfIs>{}|oBVb{yX53sVp)7WA&Qw)7*wij zag8S_jhq57&_W)Ld&^Ly`DO_qGvG97I89w6k>6_#>heBJ)e60^*-o@Jfm(H~?(yU| z7W&&%6{RWEHxdR4Y?)2?izGfd`x@#&y;aX}#P*SMAZR~*w~M{OYd1~SVL+#}n6@n@ zH5pcO>{@d@0zy_L4wtj+RJKRtYDsTLpA2XcXYN{5;G255w z>SZPBK@l-anOg|dE)B@w#@&Ld)6GX!Wdr#Ry&>G=Ei@^!3xN6uWO8GflK?6{ATOsc z0Ql^uX~0S;sNZV^HzLcZuIL@5Y|#|ziz=`| z%R>qEMCNV}Gcf40ksCWqP6GJWN*x{#2B;ZFQc8OuRlkBX9HFKe$n)dAYBFx5YBC6z zb!TN@vdU9lB-Bz}?;KUd?wg z`$MN5&%IoGQC3Ys7m2cbC}1hmLx4?b5-+k^sLf+DD(oSlzN!KbdFXO}QAwQ@3XB^6 zP>r23_{x?qGH)IG(DKl|q5@*uQ3jYEAoC%) zzVkpDD+}2q9XaqlY1Uq<(f`f4yM@&wEh0+p2V#{zGYFUtI>U+CDtqshQgWMCeK8Id zZOtajN8*vSFh8(eE|L!!t(*rCc22}@mjrrSq*I2IRTFrYbE%Eg<8_dzq&i|1H^>7R zpT>zLDjR{SN=v>ouY^nWqLK82H!$w8gAdkjP6X(gE?%|~KyZ-e5tv0?YhN{J~dqo5zs<#oCAIS&&(wX4BQM}F;q{w3j zX_tc3($&JdWqQ@rP9$ohQKT-_2lfl!Mx1OLivo#b`EW5n zO~!S|SWWW%6zye~^9jVU@<5`;c0NpZDnRk8VwKtvXfFATG90RsushtdexerRI-;JI z6sL<}Kx3zDF5C!KQ_=hCQr?%Ld_#83dmc({DiGfSkyH8bt8)Rq$`+~(UPO5xAni9v z9rcpAhqKiZc%G<3-r{t~i-?LbT)106O`Wool5?Mb12|S7hEvhY#)Ute4E{SsDy|Vw zJ%hA^Uh3#C^Y5UyO3J<)035B7`9xg|cECn( zA-0N&KDRMevwlNGcNzZTaG1ApVYk`Y07G&F>Dzk);qxiOiK=Ly&D0TZsFO^=24fMP z`-obucfh(?c@S4!J<@NeGTxW(&iAl;>bRey0gJe>*Td-$d{B&ZYe)HKHpSJw0#2*T-zJ-0R&+SVabdrg(*fEf3DH+}Y+AKf zlwAl^foUUKlK=uPP>@td6*c2&N^9bXRgC39LM`=Fj#X6#YL>5pxuVI$M1J%v&gs`>En1D^2%{IAY=5evD@ zgXFsEnTaaneZQfs>Ym!~^zXaLQi= zhSfw(0!PgEFb`7dtHyJ$y8JC_Ae$Z)cQk6e=D~44XGDRM!n4tvy7v<)TcCnpkm);N z0MOY&L39Nb_@AZ8@}Wa!+RlU22C6CYKvfy2FR$D3P(}+!qudJ~obY!>z=vsq^SUum zpYIxF`@9tMKE&1$9{{M8NP*=;6_kEXQ*?QUOj<1zjZ&H7`}0CL)X^yV&}rwjT5$d_ zl4n>=+1)~lm3YF|Djfh6dq@FJRzb52nojyVWbnIC^h2dR;;+H7KXeX8zFST^s2M3V zk=23zVU)$!RIFtNTay3){LYZD%R{*VNb_%!V>U}Deo-60MfJL;j`=tk0hgV3(gay# zrs+7)IF&H#M~Z!iZ1?$(aRUhtiYWIhO>$pw%qEJ2$3Au0tFN!C+4Q7@gW-GHc?X3r z3e4Toz`?LPgyA@)^sCs)X#F3UK|)$n#XNV@l=QxXHcBMIZmTLSz8Mvus!n;Kw1y6b z_rFd&=#?ofwvoU?(`L$68x#t@#8zD8H+)S34p*$eEt+l=aL|T|M74O9JmBY2pq~Cc zui`o$vVHDgc=PmsaRwD_ci=-cb1!3712Z`Y?y4AHYHT zN+iBTrS0&I*dnX`=Fe2GduoHxu`vDP)YH!tltV$BDE^4BjAjZ=N48h%-caxs39oA? zH3mt}Y~!f)76@sPy6n}5vn7(qU2oHTzzd!6b1*hK^`OKJAxY~3ob=dDnA}GhuYHV7 zhBta0BEi;MA>H3JJuTs=^%4lvE~fZomPn$^DxUh7w^gu}O%IDX7RL3?J?NJ$BAdN= z0h0*Bsmhdi!dTxo>HZ=C=PBfUh^B+yj@l;z5p+>iS;U>$5=oRU>Om3XFkwWQhP# zVpSDrmg<;?EFB#S!&)aFw1^jvB_@89eooj)KSjnO+i{iO@`;s%$TG@Qi6zPUiNp4> zK-5lFn`tXR?6Q@FxEKxj0=V1MGfGm_rHWaEUbii8)J9F z-cmo6gzS#Ww8|nWuBrymAzNjcR-pIy_gCokY>_}e5@4#T0&`J)(LFWUwFJuD6OHLQ zj=*ff;%g~U_&#GteBV;)83{N+nE^=hgPD(UOV{DD?Edl3g*lJc}CQZwA8o*doRTe7H`1|{- zD(tdsi2(Y<0LPG|?A)MN*zMtB5_&`^HJTR5fOEC`n#W5{a38nII`S+IopQgUvq>`|&}Xh9YOP;#{T3JEn`PNXri zD5QM|yshz?uIDA~Hvk#G7nX|(F*F?aQ=p5Lq@O_pD0Ew8?t7G~Y5+jV(Q==#E3=8q z2^2al3jcU>C*&es$@T3fEoSYgZ!Yp(qam`i0>hBv>@HeBi6?6F5|4^x4xrBpka9GC z$U~M6t|!nvO$_3j0eAI((`D^yH~*8Yt zj%mDvt(Oam*E%uy%fufTNEd!}O@&-a3uwUa?PuIX(g}Ypphl9~&W zEs`9C1ptt8Os%}irctgay2&CiQ_rIUH|W}8ve$TIJhM%%I%d;wG1zW}A5kRt0WF|K zrcw@oUTvl=k>nT*Ny#zA$~7jqqCnA0{JUQUcx+fl*V#ZjnYOYPS2b5vU(*m<$!;}X zQa)Mzb4Sl!UUjq(N5cvI2z+gmAh=dOG@=;+`CkmQ_a%Iimzp-dP%Gu_05&% zej0GB-3B1V8w{GjCstNK{RV7F93nF*W|S*hEV-bV|3=e!IKyMp6&;3xgB#|Ua&7-RR)VZLvOXA*<5MG(Lk%a^8^c!>2AonAP zxEx|9-5QEAT5AJeuq0F!pr66EC6XBSeUGBs^|uR3&;jIqdN<(qSh}X$$M*`Pt_R_I+#bW@T0f15bwxsR;t zp>`R9tltmIgU5a%R_W~1AekU*PmSPnq8|Tt_f;+jf9qULYDaS~suu8kDp@Z|*+W{w z+%dmANQ;O#6KJ2VR)XTHY6PEIMS#oPpROjJtC9P&^<4YVLRC^dd-$DTZjC+9%-h*<$^|Db^<#e`3sYvFgLSEZv(U9?heP&U#Os5fi zXmRrV3a%xgN09sDfbYIXslq|_XqCZS-#k@%MFS49i*7AN-+)HYCQDWM2Ql5nIl61x+)YJQG|?O5aBQ$2I5Nr&g*~>GGyq z#$0UWJehYBvER!c`EL?*FI+3An+`ssrA63pmBxodS7+ zUx~OEW{=X*1nuz93aZ63#Eo!?8|YF}FGdJ*M({203{_V{^HwU7xkEmA8$?7xO?%W& zB4{FL1!bdH@(4eY?Ne70?+qx1kE?P`T1?gR;(2S4&fIb-Z>=(D$oa?~ohD3&=xIacTvQHOl;G6k7m2=*-ff(HCCZ)IQ7v)-s7^tP$V zt5g-(v9N84zaOd@Z}c zWFj6F%~O9Q_eq47Q0$?q((y=DVRnnaD9j%NMXhGesw^E`L@GT-0X<)YTSXtzl-VW^ z&C;2BSlB+F%QU1k%+o}A4tQ$`erKoxb3qXUfRb&8ZHol@zK5b>H7emf*AV0HD5O*M zyzN+=Qa)Ry{1{8qy+O zOkx}J3MLRWIuDbO`PF*+L_8y6PF_qq$g%d+6krZXkZ;p@7PNcltiMZ$|1K2N-GRL7 zkVVsi-09b{_cPcx;1m&iJn|55nw(cvwS_?xVZKl0@FLl?-slqYMYgC|2J@=aGnzK( zbLDrEIr%d?>DCZ&CoB(P_sKaQq%E}0RFiA+D@_4ckiwTxSnpTll=nfJj{4@R*hA)0 z8`%lQ6OmXu4;5m`S*F((D#S3v6!}IyN5@Xi+P-iFk;cp7f=fO41T$#5Ss+)nl9+p5 z%3eKfG*~~)L*rC(hJdz^@2aZO@y=|`wwIT*My?>$o}s{|cIDJZIV44u%2hWDa~A^b zRVRrCoRWubR%%ikX$+>VN<}6{yq-t^{cHfBmXpU3E+FQmD70t8`BdR0NlA5bH4>TM zWU^P0M?`EjKB4O4K-2D_V?Hh*ZIZ+WC+WH5f1IT3j=7P3 zXKyCh+xI9D*Yf9X8adlNw1!?*2hR&F(YbyE?nl9mX}~2|Koa)PjmJLr`rA!fM#Sr? zxsq0rGZD0gT8V11!)*bW40_o#3|v3@SwshS1aPUhjijG*lm9w%nJw%FqlvI|%a!K= zYAl^Jhk`d)5^jmaa16luYKg1To4bA#zlOq_J&;S)<49U2<)T6ibI}#-*DZ$z9Gk0r zSE+eXPIEB+qpEE11^^O^PF2o6D(>>3n~VZ{uRM=}&XcsyBNrcJFn2Y?el-(_SZByh z$rscd_tP9;oKmrfp8){LIV2mPIl0aOFPD#+FGL9c^x%;0FOn{W=Au6`zs+pFuzN&Y z2+mENBx)8(+C!&ohPWlpnn@C~-6#f{hh~GThsR13;*>5Nf&nBwkI2Pz_U3^7y$=x) zQz18a@D5rQ~!qZ_Ks*^}jVy(9sH?sG&0bep~*l#AJS;NqFv7lUcx}gq*6zEXGxlWn4V!z7#3EQ;s{~cPTpH`Ia%r{2Wly zb1xAYt=?8TikQa{ngonhneTBkW)`XvD~DWXpOm~$YDQkuAS(#;!#};l>1DSOSuS(*(aJgn*AZTau|L^ z5#Qp+7nn@ZBIyl-kom!&@GK&5#G8W75%afRt1zy~6yr>XnPQnq6iv#xM1oW<>15H} z!Y&;RUkO=OU(R&45VX_uhB?e-G*haVjfkbXHxz$D%wW(e;B%H`LzpOBWD@0*lv7zG z08-3L%T-JDt{gu9pqSq(&6l!~1f385k7)~YPfIFQJAsH({%@$6NK0xX?V|Kg+EcJa zBx)r|$-@W_C6dI<8oEa;-NBWkM~0x`A`@494ioe=;y-~GnLFXDRDoMWM3#9&r)*m8 zhHDq5w=%^zTTE4f=PaUuoQv_Km=~mzMvmSs;>uC*42pVEd#=D7f|6?gr}T5?{?sXD z-bzGf>o*KWTDO_C3%D31MH|9U73cwrM8lcA=c>gzSB_COVZ-AExYE-~kEQE>8l*8d z92B2K1b+L5S;R~L4P*E3wyCNB?b#xUrjyFowS6SB1GsQhe26sPUb7{MKKUmdPb$aI zgCqXa%gS12E9JUdiTGO&0EW%9WOvds)_SfaTL=dp0cs>kqWPql8EmDIqnGo$a2OXM z&6ZEW^ap+TqBc+bPZ2c#KjV<~$g;{+izDK+KL7-rq2)zcEu%Z>pLwDYUG|8Ht-w`N zz=fk(JW)5o0BXIY4>$R8MEZ@O9VP(%GUm?sD;Ii&h{&=4Q2Yrk$9*-8t-e$fo`aQr zWmII_l4j!W?(XjH?hb`R0foCu;ZnG}yG!9t;!r^0?(SN+4!39C^zH69uY2-O=2~Y( zd=YU%_Sq+PWEQ6CIQ+~BVR-Fn-*yXFc+Tgx90dNzYXaF-vi#&AGT^dsoMDm9d`(zf z6Aw_9Ej}hHGkbJFKML2cSLB96|MW`+l_uX+py z3%7mBH=t zu5w3%`c^lPSww>mEPQGM2|DU!0rbb+!HHNw^D7NR)*G$JjPV97pg2jUXQ9Rj_=eJ+ zaC_uGdTrGkKckhu>wi#EzxabSPor|3P|Y@o2YQZIbCiknB>br2M0Sc#xfD6|->#34 zk3_h_sVGCxkFLs@$f?2mZ1k74hOj4^1o1IQ#DlP(ac*_k?izmsuXxRY#wWVJjimb;8`an8Q^|>>1g9zmigv1nedMwl9Nfym97ItEh zC^1lJ{ZL)RA2TIbv}{#B99nRKri`~Dd6xz4wtkeTupm8hj>HPZDs%`DRTS2zr7@>+ z0D-c1@h~Hp_bn=;fM8osMT1LAA{&a&t{wqAHM&46?gT5@N#l$c>dH&TfL#tNjxPV? zN{EJ%O$3bPy;@%JXF+QR?FqkhZKSLL-C2G93(ly4**Hd29FKnwE|1Dyx4#W*h1f%C z+}`b!Xr4?#M3h@Fgv5NdhKkk{JuU(x-larB@-mE*;T|m#s*Yn{r-CqLpllfIXyvbr zABU194iYM^h8lCCad7KQ$MxGk@ka&m5}>fZ?E(l9_rapjVzXlBXZZESe?wT3PVq{_ zvq#-8DXXt*V7S6WE2!U?kD3*JXVn)HHt4Ut0`ki4h;`6^?!V=ypejk2mMh|2Z}g$} zeVQqyB4P#FDL>2%s6qCKobXnlnqyQJ4fc2|AeiJqiq8dGHh5U~10K>Ae5HgoA#~ar zUBgvKLqcE71T8s>zRd=HJm0#Q#ojp8!~fK$b*sscyE$_XG!mS=IA3$;4M`1}4z}W= zq@paD7^~e7QjbG+O}L(oH;^D~$s_I>2g@kKK^|-#5jl>}SO=z2TAduBkRTXCLw#Ki zqY3@k%BCQ}q*p^sh~QWd%_de9%hO+rtaWOh6nnwo#4?SRa8*7dd{|cSQ>JpS+KRGb zz0+@wsnHLGFnADd1g85Szd5e*^pcFo=QWk$p>|jZ+0n{AWIs*ihMe+AlP+3ppz(=Q zROuM)%H3+^Q7zI=Mz>Nr1;N;9Hww0C66i=JFJSNzOjcPK`ZaA5)4QUqLJr;2N}!z9 zif)8Q{YoArHJY(Jn1O2rs=N6HfE4Ng@WqG{AUp|Z>$@(93I)X=Dj%_s`=X{kziEh} zZcAEwdU^RND4eBU;uB1(RWPAgI~%WmHeAkO;2mE$SXb3o7AW=C5C!&_Crg^E!YXlI zMB*x%vOe;79W~F{3R)|s5n()xwe7=tmO0s_V#IUg=x&bj0#ewa)wScSSu!J-3F>|o>r@*wxHlxKnE807=+%rg5j2SzI1tQiKsic z*t@E3Kn{*bKtY!yHv8ySl5ERFi;N0lGH%g>c{ZU*d=)HfFV&!5 zz-`1_2iKNRB-SH|=T#1|Y=GWV^#;}F{OoCHUe%5FDSbj-%mKDg9x5}a69juZj=v8?B9L{SB6VfX9$4f+NTwhZOVQzK|iC2eIQ?;MD>SWMUBc z8L2x1=XAcYH&0(Q!+51e*Zq-xR#!HeGFI%g64jj!D{z=Gv}M*IPC04BeFM(~O)DJ+ z34qWz3(|p;-qh$2`wVhivP4WnDU2zyM5I=(AMCOE1$FAB?f1(&%+QUv&>Q6?QN(rm z4ykhn!jfP4-25I1TW!>RqlzwXG^K#PihJ-B2$T_h(8}N2sEws9%hrYOUSN%Kq7Xlc zU>BjAu;WCc&!bAegD~Qi2*tVR5-D4zx(IId%NDtgpazXMpTKdG!Mpdd<3EyXgN!Fm zgMe$Q2jB&-z>nk&3H;&L53v;X8=jQHCi)Y2J&Md5yUYA}_lveCMPHa>yD48%iG2j~ra)7$H&_xoGFV<7Rd824A`2(lsnip55RK}N@ZJ;{2Vow{XIpQQ5#8)P%9}5#h};li2JSZ$Lq}M zp;{e|N=WaZ-qb%|R$hzd6p_JpA8gM1MBT0I6cXxD9utSSh5YCNVU_mZmgiGeR(~LbO zkXxNRY9vpHQcOvVkeD2#04h(lj`pm~I*&Dgvp+LYKz7Rhzkt+;fp373R$M5yu5v)D zi;FCYeS8drMe+#Tm~a8SwWs-MZ^SSmNE*lriihGzJ>E+H5jL~~n?Gw!xOa0%ket*J zN|9hO4zuNRB9=!jzXEfh`!$DlK>BNg+hPLH+Nn7s>UTbX3zYf~t`u8m2Xw0&{3yv~ zoDiP!IS!LUczOwCc~*x;T>7NVT4Z$+UP?`S@Vp(ODDUq^z;mvH9ovDhJc4XdhhKYDu==#HBfn3bKDu**@M6)F(J57 zKlKO}b@kTAlC@(d}SiP1Q(XSV)%$ z(!R3JMqCa|Ow8?sGSzHtDPK|v(gr$`AXm`RdSpJ4*uB7&bL@7@oxY$+v03Y9An6IT z7f(Ob3W>r>OdvV#v|1Qachs8Rq z*NMz66@kAZQ2XK4NN1HUt)&R=fR*3vb_P+ojjs zE%|t#`~WP*N;=|mpH1T4He7)!VHOQPFb@-Am{mA)F2+1tX(99Ttt(nkX(O)g>utIP zqZR7!cVYP3Fig0^%`o|ajYFLuhVm;)A&&1akr0*84mj8fT721JVIESnV3JzOfm}sV zjCPxNttM^_^hxLq7%pB8Y98$bmhs-+Hr`mHFcY}MnBQP!U>@n{x1gR1V9MKS_5=nN6JyhY6EPJHE-yT7Lf}SGK*| zRFK>V5+`DjF95w8Yc#q_a1T7|KvPGp*@%3yCe%tFnN?s=@Q0go@OjE|jDuBUt*w+q zo<3jYEi{h@yFd84sohpJ>zs6t^Qk4ci7gvX^H82=!13&hn`p-}O0%vRUtf9uZy7aF zAjiNXpOZ2nEIVe;5Q3g5DP(tor|Wp?&lx7}djbnXVvTjnCZvM(Gw4;jINLBasT19G z`7P2J{v4>-F=P-h`ZZ}QC4~tO1BQ6)&qnClC`=t};j77OH_^sS0^?O%l}{it*Za^L z;U3L{9yHI#u$+jzpf%tw#1ELZ@k(Q0>n&9eA;#hmaOoh+hD!V|>DCeFp-W@-m_lEI z#zqdc`K7HPOq-RqL(i$gG2fgN&3u8lKK->_Ik-5&(;?;ee<5uf!}-X6&ZN*qnwt$6 zlirpa={Es!@u=B?lw2r*2z8*XXLp{MZQ~$dXS;elim+k&1d6$;W1S~glzL7T3I!!< zVzJB`4>1CDs(r_DILtx_#Keu+|ZVGghDF&h}5qmpPNg0d}9z)z>mHC>=!-i z4Jle-+uKfLO*$&MA^Rhz^LfT;mX*L+Fw7G}O-2pOd036JgQQ^HM#|%!HAf$0Y_*58<%OM~f6??S!%nkhZa6Xr}8sXNJ(~KH9zSm8B=M=3weIO`) z*6qT?eybc4vlXe$gFh~9%kL?EDKzQem_LG1Nw^SQ5dnMx0a!W=kqL?tZ=5z9wkxwU zI`ZOUgMFm|cWS-e?~vDQ0?Tz}At)Qczcu|N4Ep({Ci|TMk=W*zOoEhjc$S~&m)fM{ z!9el+?Ut458Lrc{rM45l)^$Ip4(WgiU+e-l^wr96>8kMx{?RWck79Z8J6@LHom%Rj;yGu|i1BKsm3ni=7TT{}zL@^K4*7Q*=w(Iyt%HYT26ojrT+J$x# zB@|p7iNsumjPED)L9@U&&`PiJ;f`}INO=Oy6zvh(Y8_uUD(zjfK&**k<`OtYrG-(R1)8#d@7*i^js zk2aR~x)bu*&Dw?3p+EMjZ9ZSR9HrVDHlOOOS8O-A2|mmdeT98pMcQz<@>;jralP-n z+P{2jv3;I@u^0SWXqSAw(vf|Ar1h~<@!|7OZH>5L6m7SEouNzQ{fAHJRmu9q@B2=U znwxg1Zn_QQhTCgQ7>@d$I8p9{r34Yo#!q@rbZeHcgCSqwbejkGR;k<>E=z;lIq(AM z+_9TZZWGDzz|xtRKG?dn&P54PJO3Gh zFnES0mCYYiPCKXA3;Dd?&tkFEq9v-$&dkoLswEfzzre2)~SSuc6)iG zHvR4wVy_Y}&bZ5Lv0MDLH^Ik%pU2zq`=33f6v5%Q%Dac#&Af4C zR>Rz3cF)-lp%b;+p3_nw(Wz99QUwx=U)x5D-?4y>pZgu)z2n1VadU2WO6bY(byCsL zeDj>>_Kl_exn1LVclpzr7lNfr3aChl_Zw$Qdp(oyK!fosrsZaUkih%I5eJ9;Zx@N< z9=~D$e~}LY{Lt{(I;x3XI7^Fo+G>Hk?KCClQ6ip;OmsW>(0{lvUeIO-7qQcLZO~ev za!GdoKOp%#uR0pOdhFqtX^T1T`;v0r^O=8+@;8z?>t%aC2k>m$eDm7%7uPtKNmUe-Df4)_JGvZL4 zzzcZ=5nIYabvw4#@g0Cm-w^aYAd~1H{7~k^egtt~4_Oc!w7h{ZP+m1^aGIbZ zqKuGKeZlZK;+n9JPIo=(M&zC?e{0W7yk;6wPkyzOMa(HWL+JnI$bYxazjMy_w~&lm zljJ8ShK9>@&FK*rczvVxzPc~=2CD@MOTYf>;P}rxO@K#6Oxo&WHWT*<~I zunyb;2B^dkU94CC4*NGR`7aUo54TKX;^Lgf0+|++7fOGG3%7$KiP)vQN;STSd0lVZ z)PvHvd&p3hqeJisT4FM83Y>rgTTLwfAI0?lL13g}=JZb~(f}cs=Z_t2V5$#-SbrU+ zwVTX!0+QLFvRsfTZ|sZTvjXA{h{Kd zM`I6Cs?-Nb1=<~IC`AJjC0nC-$GQhygPdjIl3|K<{l7~Se=kNg@9A*^)SF(h@V^f4 zi)vPXa!I*?$Nr>Di>+y|f3MAvy}=bt)lJC#m&x!y#vO&n@?oDb`i-Oc5o@U#2@BAG za8I%m#`Dgk8qh8rL`aNdB`HrZeU;6KW1v_b@zj#;t_vQ5*VcXCmHDsD^0yL-vgfv= zVFcbt_pWw=rAYm_SpoO{J$0TCg_f7YooMp^M_fl!^8F`I;=WBRsBz>v7BS%PW63WT zw?6pH_SA{Ivpoh%zQq{3th5@WQ;5Tn*VGNKXVSj#^l$%drTtr>&v#UJh8pp+b~&Ox zkon^$5plq@?$viHcxxbn4%LT3vWIz*eZ7UbWTv$wCW`itn)E+eKTK0nV(&kDE7(!* zDP{U;FW(^dJV=$E9Kp`%40I;M#7_RL693oL$A5Km=7LuP5P!HfYff=P8tV|zR^OvV zVK#sts~h>2|A@}bjnMy>;svsQ3b_hC=a7GRT@veM{=&=psyU~Bh#Kb;^8a2_{O5T8 zM{VZ;XZmMLsDD!e4FGum81wxcD^JY1mzVA)#Y$8E>uC=e#a;&I&u?Md5V|pwsTTBr z>JDh|VUp)oyVs1T&M}PYcajt+80jewF|(-&kL&|MXzEWEU_f%~2v7afw8|Yee%X)i zWlN5{gIA2sXe@Ccd3xIt_a;N4$rC%+kzR;GuuJ%m z=aX-T5=qj_TAUNS`uaB|uHB1b=<}UUTwPh0w-~?nIh29JeouZy2wr5Z6TdOr%?w1f zFsdt(L53xLOWjW)YObjG3) z1wqYoSbZt1O66^S0!9HBn7Pih0xBo+3(E=09wJ|Xor-{#u?^~g&F*iv5Ryh=q?*!! z_%oe9(Ql%Qjku6t#4odDunlZr%3!BnEl`)a9e+t??2afDT}yKL8z#YZkasM@Dz0EM zm+zlTJhl5ly%U<6fRwhtxGI$#FzmAQyyH{u;dFFKczji(4p!@2Gb0~YO!h&N0AOPl zfx`gr@EYinnn=QzUw6&-9n}Afo*q0N=Y0{ce7X5%=Mqd^2LPaVxQzIB+gzLqdCOug z?&kX9&;_N`7l8J(x)ErF4;6h`1V(swbv#y5J3|;OoNYHwk-LbWoRf=(f9r$p=w~u; zu=33YMsu$7!RW?6-wisjM_g;~!e5gfG)rFNDsxh;xD3iIY0u$bQW!qZhPB`{B4+d5Of=c&nxG}@Wk2}ouocoz}+7Nsyf`#yFDPf;>T z2DgH+Qpw!#6i;7OhvX-7~T8DH8?n30Pk^9O*|ISCC`Sqe3VF=8)-a5`*w zeK>m241rdc9}34z{fRGpz+(U4!d61k_pk%5gHojCy!VQ5>S;W9J@wOg0OQI|^n2HhTT4!I?6;Z_>GM4aS{tq}K?_54R2Tpq; zB1P>kef3yjVzQHWxeXC-lSiV|0OpD#E$ngP49GV;DvYN!ZQY#b{i0O2FDHDmaRvN$ z#2C9kH(#y!&2L6(q&Kf{f+UvMaaUbdkfN#e3)tfNjV>qjQALI?4>H zvcB)g*TQUOvXVg!frh#2heO*N@6C%<>Yv9zz-??&1F*ocgSF#gwGDXBWFlYJV|p9w zviwyH9nI}Ga&`*-yxiU#5(Q(;WKwY(;@T}D9hPx!Kfy(0<-LprL%ADx_X{Dl3lym7 z>;0j`lL2RPNgb)IU_7xdxdM{C3*}@v~?sV7TtTVsHTnlqk`Pl(PZ)qJ(?O2 zQS5Q@PE{&+=CP)3F9NfDXl2z`_%L_&F+iz<+1J%XPMn!JRP?@41*sJLS{RALhH_ky zRBXkqFR7}NIC4FW=-3BNCT%#(F+J*|9%nAOlnuzY6a^dMz2RhOQNbbeOoF6=5vd=C zO63;%C8!QB!mY`WX=t$}XuLxm=_-qVY=Fp0OfoZBdiYUSy&0R1X4!v|A*)xTc^&50 zVxT{D!&5h$)L{_l!Oif&l`O-!FIiH_wK*6cqyqAPEJh+(ncperZ@O0A79N~4FOq*P zP~0aTg1<&R?@anIH8=8jcZNBKk4YHuN5ZO=;@IPe>{?AZ_pQFhi_+j!Hf$>ZM9Z{rtt(j{JhFk962c*zDZVx(sXv1&47i#gU; z7&;<8>P65f_11er>1l+s9`TEcpp5rV{t((xf*3>XKU7D+Bpi^>46+H|Cn&1S-N4*s z!u%6)Jv?nC#QGWxU_HSth>Y~hg?&!z9n!PoM^K)tmMHa+)U2A;lO@i*9j6BjT8F&VYZN|8}o_IPJFlQri*xYCP zB+#f8-VNN+o0m%T4jbYmTtD#TL`zfTr!VK!j$A`!VDZ3=VKpX94S&GxlE5KCb{-s;&?^!zoILLl zc4et*U}Z>OJq!H-WsycHnzk4K2R3E<+_5MQQW@3c9FnO|P(z4noc>5v74h-%l2U-G(ocyBMt`z4_~wzy&PZID%QeoMUTnGnxXF7`T5F*bq;!jlBs*^BghC z;Qb1Yrl}e%b(jv>hLTt^=){E5aAU3}5E5WnGQM5af%!VM&h4Lh%#oRf9J+L{AU+_R za{s=GCi|ffa~)G%Nefv@8X3Ug78ztERsLoY2msdcsAlH{+9r4Kn4$wP$cJDg$-UEo z+zJA8U7(YPdyi61?Hvp+aHiHht)w|rzu&{I?ceXQ4S&MqG0mFVO@q!d^Z#!6>U;^= zxG*C8!z}QaU6w=a#2)~FA;9_hq`f5oGlkdtY9uQ|zz}2l(;TE#(K5J5P(4|;qF|tz z8r}RuoNwRM2VU2=IciW*MM-kh_=JL!G_-dgs9TL1q6cFY_$4^T^$m$~|4<2(I-?0} zL`2a9N<^}Z<-}2ZHu@EFX=S&wRcD*Lbh<{9FLQ1u{++BMmPJdh4h zXSuGdSI6xa#@Q9nOS2}}?bFs~k*x3iZ@Y2}xJ9~>3k0=kj4@AH-fG1)VsQIthCl<8 zlRv8!f+rxf9Hv_vsrOlt4A~f(7gCAk(kGo>4a-41t<#rbvK7UQxwycthw7|0oiJqP z6a;}XIujF@OSOIF0S*`D15;^4%3NBwQ)7a~o(#ycwf-n}VP;!b2hCeSZ$*;?YRR)x zOo_j;mk4QhvnxB*FnN&+vo+;WE~-8>h}9w(r*N`4p1}raw>zdOgNrvwm7Ltbp}o*O z0?~j3l(m=#81Xh`huYbD^bzTYL2fb1|L^Z5DK8?G3MZ?ce@aWs}Wp(k|GU zcg}zG>A}QqKvbdmnN__w34IUZRH>XKdTze|1SR8>pJ)k#fE9$Gots^aN}FL~bS-iK zhj!%3u1b@F`o2+I!*3JP)hdcWUah4q8UBEho<61mXO<*r+Rd7a%_;O^#Q4R5O&>3h z!NYGaGidbD=TUn7V$L1r zZdNcvVZG8@%rq(z_w$$x~ohj*2BCKE?%PYJFz3V9DJhWroLlbN7|G4o80w z*Zrg%AY|`Ye)WePMYc!Scr>_u{Gw4Z#)OC*bD)=Ew(^JBF46yugZ*!%ur6bZ$#AS%x4b5riWpYHg zcVUWDOZIeoXC`?t^0s-UhyA7KUI=k76O{97PW}`y5x}&Rl0E6sIfds&;H@JPocqF`K)gERQj_XAZ@u9~}{Yz^(^lwMasLmGYcF@OU)NLdrPUvOqB0 z1rik4+f&(gmZA!HGiuh{zchNS!u`_8`l_AOQnhrx5Pr3s_Ngo=zDi|^syZx@QaAKh zP!(o-XslF_a0n%g)S3kMC-ds6z~B}3V~UdL`U<_I$X|c#1=sw3^qQ%0I`#``3`XTb z(-kOLWMGyZI^RBy^%SxTslfjv-hliwT_r+Wo8~2m!fysfZlSmljkHUA$2W&t4Dmzz zK(j*!eSAvGOYV=@T_uMiYgenF)3kFUd`RF2vGEt}`@* z(4o#uR^d4-yB0vg*hGay2F_+FZod9C&{c+m8LR_rKf^JVh>?o>c{NIhMH_@m^hI;; z<H%dZ9Le*8_v7Y}S!urjtRyw^t%N$?wj;m;nv zV%)EnW7)m~Slb!|Zm`26{-DaHQY0ST4=VU!4U{w~#ooAy9_ap`mIFcO0lglj4(sZg zotKaiEu#spaf}v2<(ObMEzG%>0zGL$LsjVkmFtElDd_LVzczNR7~sBir755XYp~Cc zcge0VgS_e^`@WV2@Q-(WZ7JE@0W~iE(*JTDjwndKmXq`y;TfPo<7+hc15_S)NgS8& zyQyCLkfjHM2H3AkS60N5pu#E{S>DGwkh_&W36J4drFQ?8ak+H}&?EO#K)l~bkwSIw zs|$+LI%n5fRNaT2SWV_hgNZ>7%oDm`lulwpa`o`LPo+#C?c$g9AIBj(kbmI)g9Y!M zS4|0B15nGEK9vLze~-0NS4V%;+#CUCv1q?gcI+BBb}{Fws-1`!<;j^V zmsX9Az7J!`^nmgx4`7)X0Vf?uG41Wz+QXqO5D_u+WRfQ+a?u}S73Ls&-wDbcO|4c? z&(T)lOSxbxR4zG$;#Cq3|Fzl`g2?Ql0M zeT^=(tdfZ|Ph~6YodFsrEpcDYNniY-(r}3XDku59(GO zO|c8VA+$!hD`KBXUiw2DE%9nrN(1`vW8!-~GJR5D)JQqsoBM85Kgc1B0=Nt?%s>pO zSI2NltG0o^TZ*{s@3$a|h!0|O6I8m3-W{VF=!B^9%pepD(Dh-cL6i0$O~%%jy(r$t zY@c^*_*0h(rjBf5X|-bUh8mR1;sqelP{f^!F7On$(Rf(Bs*f{PpGsCLm=5_pT7}&A za8)p|PN2D8F3!26KR1yk*g9QUjE7ULhGL$>UVVu};TInN%2fd5$#*&mofQE~KkCL3 zid0SK*oiH)QzuS!Df8E4zD9t73+79U=WA$VftOPJ8#a{>iDzA;zUQX(kn50W%LX-K zsQ{N0NQMmi8M?M06eqjQT$rm4t4H1tYHjgt2yP*{VWm?8Z>%;~BLVLLYHC4ycCH|j zs;)N(4~Pz4DJ}V|A3+ZOnHGYshL1DSw-1_M>MaM`iP@rjW0nC)YlAHs$Uj#X6ALRz z;9;oN_7%CRu}+q{+hSgdvvTtVZTN#8}-}!m$1m`S|mHb=<_~WHcipe1*!AG@hbL>xHTbbCeyJs17pcULqM7( z3kQnGS*3O5$t9w;r;)mh)sm}k^LAvS@Atis@Xy|TAuSp07!V71Z1Ket;g zxG(t9d7HQA;NZ3*kV4j8pM0mR&P7pX0m8S|UC@{Z2K9ogR=fKnc@%5a+! ziXmtaZktFMd5k0yRhcDI%zF8rIGtr|TE`Q#WFpLSrou+8+jEV5L(m zyR^mAe%&=^uINiiO_$bRC@IVMo{pl3#dkr2a!plTd~-spe_M$7{Yawv9v24FnEh20 z6hgc&S7Cig?G`52k+?o0=!!G$aeIF}<%hcm Date: Fri, 6 Jun 2025 11:58:44 +0100 Subject: [PATCH 559/567] core: 6.3.4.2 (simplexmq 6.4.0.3) --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- tests/ChatClient.hs | 3 ++- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cabal.project b/cabal.project index 3e6ccab8a5..44305353f2 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: deaec3cce286e959bd594b9620c307954b510a07 + tag: f44ea0a6d8eec8abf4af177ebeb91629f7d89165 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 8d17a2ce99..0c29b47a36 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."deaec3cce286e959bd594b9620c307954b510a07" = "0b8m4czjiwsi9169plslyk2rjw0f370vv7ha6qm2hpx14bxzz7xm"; + "https://github.com/simplex-chat/simplexmq.git"."f44ea0a6d8eec8abf4af177ebeb91629f7d89165" = "1biq1kq33v7hnacbhllry9n5c6dmh9dyqnz8hc5abgsv1z38qb1a"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index a9dca273e3..f3691ab1a6 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.3.4.1 +version: 6.3.4.2 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index 2d0bde5058..e3bab5a0ec 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -17,6 +17,7 @@ import Control.Concurrent (forkIOWithUnmask, killThread, threadDelay) import Control.Concurrent.Async import Control.Concurrent.STM import Control.Exception (bracket, bracket_) +import Control.Logger.Simple import Control.Monad import Control.Monad.Except import Control.Monad.Reader @@ -519,7 +520,7 @@ smpServerCfg = allowSMPProxy = True, serverClientConcurrency = 16, information = Nothing, - startOptions = StartOptions {maintenance = False, compactLog = False, skipWarnings = False, confirmMigrations = MCYesUp} + startOptions = StartOptions {maintenance = False, logLevel = LogError, compactLog = False, skipWarnings = False, confirmMigrations = MCYesUp} } persistentServerStoreCfg :: FilePath -> AServerStoreCfg From 50dfda6c0908a9c95391c85bcac70bfbc5237354 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 8 Jun 2025 18:27:42 +0100 Subject: [PATCH 560/567] core: fix deletion queries for PostgreSQL client (#5969) * core: fix deletion queries for PostgreSQL client * disable test in posrgres * plan --- src/Simplex/Chat/Store/Messages.hs | 10 ++++++++-- .../Chat/Store/SQLite/Migrations/chat_query_plans.txt | 2 +- tests/ChatTests/Groups.hs | 7 +++++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index bdbb3fe67d..752a4a2c6d 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -171,7 +171,7 @@ import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import Simplex.Messaging.Util (eitherToMaybe) import UnliftIO.STM #if defined(dbPostgres) -import Database.PostgreSQL.Simple (FromRow, Only (..), Query, ToRow, (:.) (..)) +import Database.PostgreSQL.Simple (FromRow, In (..), Only (..), Query, ToRow, (:.) (..)) import Database.PostgreSQL.Simple.SqlQQ (sql) #else import Database.SQLite.Simple (FromRow, Only (..), Query, ToRow, (:.) (..)) @@ -2370,8 +2370,14 @@ updateGroupChatItemModerated db User {userId} GroupInfo {groupId} ci m@GroupMemb updateMemberCIsModerated :: MsgDirectionI d => DB.Connection -> User -> GroupInfo -> GroupMember -> GroupMember -> SMsgDirection d -> UTCTime -> IO () updateMemberCIsModerated db User {userId} GroupInfo {groupId, membership} member byGroupMember md deletedTs = do itemIds <- updateCIs =<< getCurrentTime +#if defined(dbPostgres) + let inItemIds = Only $ In (map fromOnly itemIds) + DB.execute db "DELETE FROM messages WHERE message_id IN (SELECT message_id FROM chat_item_messages WHERE chat_item_id IN ?)" inItemIds + DB.execute db "DELETE FROM chat_item_versions WHERE chat_item_id IN ?" inItemIds +#else DB.executeMany db deleteChatItemMessagesQuery itemIds DB.executeMany db "DELETE FROM chat_item_versions WHERE chat_item_id = ?" itemIds +#endif where memId = groupMemberId' member updateQuery = @@ -2887,7 +2893,7 @@ getGroupCIMentions db ciId = SELECT r.display_name, r.member_id, m.group_member_id, m.member_role, p.display_name, p.local_alias FROM chat_item_mentions r LEFT JOIN group_members m ON r.group_id = m.group_id AND r.member_id = m.member_id - LEFT JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) + LEFT JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) WHERE r.chat_item_id = ? |] (Only ciId) diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt index 88c6c33b41..1f16e0fdff 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt @@ -3186,7 +3186,7 @@ Query: SELECT r.display_name, r.member_id, m.group_member_id, m.member_role, p.display_name, p.local_alias FROM chat_item_mentions r LEFT JOIN group_members m ON r.group_id = m.group_id AND r.member_id = m.member_id - LEFT JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) + LEFT JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) WHERE r.chat_item_id = ? Plan: diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index 73a8735f63..431f19c77f 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -84,7 +84,10 @@ chatGroupTests = do describe "batch send messages" $ do it "send multiple messages api" testSendMulti it "send multiple timed messages" testSendMultiTimed +#if !defined(dbPostgres) + -- TODO [postgres] this test hangs with PostgreSQL it "send multiple messages (many chat batches)" testSendMultiManyBatches +#endif xit'' "shared message body is reused" testSharedMessageBody xit'' "shared batch body is reused" testSharedBatchBody describe "async group connections" $ do @@ -1821,7 +1824,7 @@ testDeleteMemberWithMessages = do cath <## "alice updated group #team:" cath <## "updated group preferences:" - cath <## "Full deletion: on" + cath <## "Full deletion: on" ] threadDelay 750000 bob #> "#team hello" @@ -6496,7 +6499,7 @@ testForwardQuoteMention = bob <## " hello @alice @cath", do cath <# "#team alice!> -> forwarded" - cath <## " hello @alice @cath" + cath <## " hello @alice @cath" ] -- forward mentions alice `send` "@bob <- #team hello" From 6fdd50efb941a8ffce26179397f5a69704dc36f5 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sun, 8 Jun 2025 18:28:26 +0100 Subject: [PATCH 561/567] core: 6.3.5.0 --- simplex-chat.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simplex-chat.cabal b/simplex-chat.cabal index f3691ab1a6..7d66292ef5 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.3.4.2 +version: 6.3.5.0 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From 5f6595dda931563afd736e1eaa182d0e1359e09d Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 9 Jun 2025 09:32:32 +0100 Subject: [PATCH 562/567] 6.3.5: ios 280, android 292, desktop 104 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 56 +++++++++++----------- apps/multiplatform/gradle.properties | 8 ++-- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 5ebc7f9b4b..0a3a42d774 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -174,8 +174,8 @@ 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; }; 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; }; - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO-ghc9.6.3.a */; }; - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO.a */; }; + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0-ghc9.6.3.a */; }; + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0.a */; }; 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; @@ -533,8 +533,8 @@ 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO-ghc9.6.3.a"; sourceTree = ""; }; - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO.a"; sourceTree = ""; }; + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0-ghc9.6.3.a"; sourceTree = ""; }; + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0.a"; sourceTree = ""; }; 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; @@ -692,8 +692,8 @@ 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */, 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */, 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */, - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO-ghc9.6.3.a in Frameworks */, - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO.a in Frameworks */, + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0-ghc9.6.3.a in Frameworks */, + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -778,8 +778,8 @@ 64C829992D54AEEE006B9E89 /* libffi.a */, 64C829982D54AEED006B9E89 /* libgmp.a */, 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */, - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO-ghc9.6.3.a */, - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO.a */, + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0-ghc9.6.3.a */, + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0.a */, ); path = Libraries; sourceTree = ""; @@ -1971,7 +1971,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 277; + CURRENT_PROJECT_VERSION = 280; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1996,7 +1996,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES_THIN; - MARKETING_VERSION = 6.3.4; + MARKETING_VERSION = 6.3.5; OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000"; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; @@ -2021,7 +2021,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 277; + CURRENT_PROJECT_VERSION = 280; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2046,7 +2046,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.4; + MARKETING_VERSION = 6.3.5; OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000"; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; @@ -2063,11 +2063,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 277; + CURRENT_PROJECT_VERSION = 280; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.3.4; + MARKETING_VERSION = 6.3.5; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2083,11 +2083,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 277; + CURRENT_PROJECT_VERSION = 280; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.3.4; + MARKETING_VERSION = 6.3.5; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2108,7 +2108,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 277; + CURRENT_PROJECT_VERSION = 280; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2123,7 +2123,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.4; + MARKETING_VERSION = 6.3.5; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2145,7 +2145,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 277; + CURRENT_PROJECT_VERSION = 280; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2160,7 +2160,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.4; + MARKETING_VERSION = 6.3.5; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2182,7 +2182,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 277; + CURRENT_PROJECT_VERSION = 280; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2208,7 +2208,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.4; + MARKETING_VERSION = 6.3.5; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2233,7 +2233,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 277; + CURRENT_PROJECT_VERSION = 280; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2259,7 +2259,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.4; + MARKETING_VERSION = 6.3.5; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2284,7 +2284,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 277; + CURRENT_PROJECT_VERSION = 280; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2299,7 +2299,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.3.4; + MARKETING_VERSION = 6.3.5; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2318,7 +2318,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 277; + CURRENT_PROJECT_VERSION = 280; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2333,7 +2333,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.3.4; + MARKETING_VERSION = 6.3.5; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 18add58bcf..bf87553b56 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -24,11 +24,11 @@ android.nonTransitiveRClass=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.3.4 -android.version_code=288 +android.version_name=6.3.5 +android.version_code=292 -desktop.version_name=6.3.4 -desktop.version_code=101 +desktop.version_name=6.3.5 +desktop.version_code=104 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From 07abe24e18275695959688aadf9ba40dfaa021cb Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 14 Jun 2025 14:17:34 +0100 Subject: [PATCH 563/567] core: make decoding for short link data forward compatible (#5989) * core: make decoding for short link data forward compatible * simplexmq --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cabal.project b/cabal.project index 44305353f2..ccdf35d776 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: f44ea0a6d8eec8abf4af177ebeb91629f7d89165 + tag: 6ac7101f4f9ad477d967537a647b06d3b6dff547 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 0c29b47a36..1454f17a5b 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."f44ea0a6d8eec8abf4af177ebeb91629f7d89165" = "1biq1kq33v7hnacbhllry9n5c6dmh9dyqnz8hc5abgsv1z38qb1a"; + "https://github.com/simplex-chat/simplexmq.git"."6ac7101f4f9ad477d967537a647b06d3b6dff547" = "0vh6jsn1sh8v39zldar0g04snijyc0fq2h678rbbmk1pprcnwrr7"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; From a593557c2116310f66d7cfb1390e3c4a974d701a Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 14 Jun 2025 14:46:08 +0100 Subject: [PATCH 564/567] core: 6.3.6.0 (simplexmq 6.4.0.3.1) --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cabal.project b/cabal.project index ccdf35d776..f406b9820e 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 6ac7101f4f9ad477d967537a647b06d3b6dff547 + tag: 3d62a383d5dcae6529d6d866233857182bcb4d47 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 1454f17a5b..84f9d0db34 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."6ac7101f4f9ad477d967537a647b06d3b6dff547" = "0vh6jsn1sh8v39zldar0g04snijyc0fq2h678rbbmk1pprcnwrr7"; + "https://github.com/simplex-chat/simplexmq.git"."3d62a383d5dcae6529d6d866233857182bcb4d47" = "133xm8jkim7agd6drwm3lbx1z7v8nf4l3asrm46ag3n2q201yfxc"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 7d66292ef5..96b2f941c7 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.3.5.0 +version: 6.3.6.0 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From 442d9afc4b3c1b6c8a165bbd2ab51bb1000f0087 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 14 Jun 2025 19:26:46 +0100 Subject: [PATCH 565/567] android: remove Contribute link from Android bundle --- apps/multiplatform/common/build.gradle.kts | 1 + .../chat/simplex/common/views/usersettings/SettingsView.kt | 5 ++++- apps/multiplatform/gradle.properties | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts index 345a75b1e7..e2927e4aaf 100644 --- a/apps/multiplatform/common/build.gradle.kts +++ b/apps/multiplatform/common/build.gradle.kts @@ -155,6 +155,7 @@ buildConfig { buildConfigField("String", "DESKTOP_VERSION_NAME", "\"${extra["desktop.version_name"]}\"") buildConfigField("int", "DESKTOP_VERSION_CODE", "${extra["desktop.version_code"]}") buildConfigField("String", "DATABASE_BACKEND", "\"${extra["database.backend"]}\"") + buildConfigField("Boolean", "ANDROID_BUNDLE", "${extra["android.bundle"]}") } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt index 5bd45ccaab..7ea656e1e4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt @@ -21,6 +21,7 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* +import chat.simplex.common.BuildConfigCommon import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* @@ -127,7 +128,9 @@ fun SettingsLayout( SectionDividerSpaced() SectionView(stringResource(MR.strings.settings_section_title_support)) { - ContributeItem(uriHandler) + if (!BuildConfigCommon.ANDROID_BUNDLE) { + ContributeItem(uriHandler) + } RateAppItem(uriHandler) StarOnGithubItem(uriHandler) } diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index bf87553b56..de82f8272f 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -27,6 +27,8 @@ kotlin.jvm.target=11 android.version_name=6.3.5 android.version_code=292 +android.bundle=false + desktop.version_name=6.3.5 desktop.version_code=104 From c08189108e1616c820606a3050f87bdebfd07a1e Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 14 Jun 2025 20:12:19 +0100 Subject: [PATCH 566/567] 6.3.6: ios 282, android 295, desktop 106 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 56 +++++++++++----------- apps/multiplatform/gradle.properties | 8 ++-- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 0a3a42d774..9326ae9abe 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -174,8 +174,8 @@ 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; }; 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; }; - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0-ghc9.6.3.a */; }; - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0.a */; }; + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a */; }; + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a */; }; 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; @@ -533,8 +533,8 @@ 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0-ghc9.6.3.a"; sourceTree = ""; }; - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0.a"; sourceTree = ""; }; + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a"; sourceTree = ""; }; + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a"; sourceTree = ""; }; 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; @@ -692,8 +692,8 @@ 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */, 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */, 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */, - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0-ghc9.6.3.a in Frameworks */, - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0.a in Frameworks */, + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a in Frameworks */, + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -778,8 +778,8 @@ 64C829992D54AEEE006B9E89 /* libffi.a */, 64C829982D54AEED006B9E89 /* libgmp.a */, 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */, - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0-ghc9.6.3.a */, - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0.a */, + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a */, + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a */, ); path = Libraries; sourceTree = ""; @@ -1971,7 +1971,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 280; + CURRENT_PROJECT_VERSION = 282; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1996,7 +1996,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES_THIN; - MARKETING_VERSION = 6.3.5; + MARKETING_VERSION = 6.3.6; OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000"; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; @@ -2021,7 +2021,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 280; + CURRENT_PROJECT_VERSION = 282; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2046,7 +2046,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.5; + MARKETING_VERSION = 6.3.6; OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000"; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; @@ -2063,11 +2063,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 280; + CURRENT_PROJECT_VERSION = 282; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.3.5; + MARKETING_VERSION = 6.3.6; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2083,11 +2083,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 280; + CURRENT_PROJECT_VERSION = 282; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.3.5; + MARKETING_VERSION = 6.3.6; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2108,7 +2108,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 280; + CURRENT_PROJECT_VERSION = 282; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2123,7 +2123,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.5; + MARKETING_VERSION = 6.3.6; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2145,7 +2145,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 280; + CURRENT_PROJECT_VERSION = 282; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2160,7 +2160,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.5; + MARKETING_VERSION = 6.3.6; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2182,7 +2182,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 280; + CURRENT_PROJECT_VERSION = 282; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2208,7 +2208,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.5; + MARKETING_VERSION = 6.3.6; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2233,7 +2233,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 280; + CURRENT_PROJECT_VERSION = 282; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2259,7 +2259,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.5; + MARKETING_VERSION = 6.3.6; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2284,7 +2284,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 280; + CURRENT_PROJECT_VERSION = 282; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2299,7 +2299,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.3.5; + MARKETING_VERSION = 6.3.6; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2318,7 +2318,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 280; + CURRENT_PROJECT_VERSION = 282; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2333,7 +2333,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.3.5; + MARKETING_VERSION = 6.3.6; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index de82f8272f..aa4c7a7470 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -24,13 +24,13 @@ android.nonTransitiveRClass=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.3.5 -android.version_code=292 +android.version_name=6.3.6 +android.version_code=295 android.bundle=false -desktop.version_name=6.3.5 -desktop.version_code=104 +desktop.version_name=6.3.6 +desktop.version_code=106 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From 3d22b738d8da3f89c5f8f91130ca0e2d62646a1a Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Mon, 16 Jun 2025 21:38:02 +0000 Subject: [PATCH 567/567] core: fix change connection user (#5992) * core: fix change connection user * plans --- src/Simplex/Chat/Library/Commands.hs | 17 +-------- src/Simplex/Chat/Store/Direct.hs | 14 ------- .../SQLite/Migrations/agent_query_plans.txt | 4 -- .../SQLite/Migrations/chat_query_plans.txt | 8 ---- src/Simplex/Chat/View.hs | 7 ++-- tests/ChatTests/Profiles.hs | 38 +++++++++++++++---- 6 files changed, 34 insertions(+), 54 deletions(-) diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index d3b945af4f..8c475b111b 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -1671,25 +1671,10 @@ processChatCommand' vr = \case case (pccConnStatus, connLinkInv) of (ConnNew, Just (CCLink cReqInv _)) -> do newUser <- privateGetUser newUserId - conn' <- ifM (canKeepLink cReqInv newUser) (updateConnRecord user conn newUser) (recreateConn user conn newUser) + conn' <- recreateConn user conn newUser pure $ CRConnectionUserChanged user conn conn' newUser _ -> throwChatError CEConnectionUserChangeProhibited where - canKeepLink :: ConnReqInvitation -> User -> CM Bool - canKeepLink (CRInvitationUri crData _) newUser = do - let ConnReqUriData {crSmpQueues = q :| _} = crData - SMPQueueUri {queueAddress = SMPQueueAddress {smpServer}} = q - newUserServers <- - map protoServer' . L.filter (\ServerCfg {enabled} -> enabled) - <$> getKnownAgentServers SPSMP newUser - pure $ smpServer `elem` newUserServers - updateConnRecord user@User {userId} conn@PendingContactConnection {customUserProfileId} newUser = do - withAgent $ \a -> changeConnectionUser a (aUserId user) (aConnId' conn) (aUserId newUser) - withFastStore' $ \db -> do - conn' <- updatePCCUser db userId conn newUserId - forM_ customUserProfileId $ \profileId -> - deletePCCIncognitoProfile db user profileId - pure conn' recreateConn user conn@PendingContactConnection {customUserProfileId, connLinkInv} newUser = do subMode <- chatReadVar subscriptionMode let userData = shortLinkUserData $ isJust $ connShortLink =<< connLinkInv diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs index 4de832a8b1..9318f62f76 100644 --- a/src/Simplex/Chat/Store/Direct.hs +++ b/src/Simplex/Chat/Store/Direct.hs @@ -66,7 +66,6 @@ module Simplex.Chat.Store.Direct updateContactAccepted, getUserByContactRequestId, getPendingContactConnections, - updatePCCUser, getContactConnections, getConnectionById, getConnectionsContacts, @@ -440,19 +439,6 @@ updatePCCIncognito db User {userId} conn customUserProfileId = do (customUserProfileId, updatedAt, userId, pccConnId conn) pure (conn :: PendingContactConnection) {customUserProfileId, updatedAt} -updatePCCUser :: DB.Connection -> UserId -> PendingContactConnection -> UserId -> IO PendingContactConnection -updatePCCUser db userId conn newUserId = do - updatedAt <- getCurrentTime - DB.execute - db - [sql| - UPDATE connections - SET user_id = ?, custom_user_profile_id = NULL, updated_at = ? - WHERE user_id = ? AND connection_id = ? - |] - (newUserId, updatedAt, userId, pccConnId conn) - pure (conn :: PendingContactConnection) {customUserProfileId = Nothing, updatedAt} - deletePCCIncognitoProfile :: DB.Connection -> User -> ProfileId -> IO () deletePCCIncognitoProfile db User {userId} profileId = DB.execute diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt index 13215dcb75..a85ba4a4cb 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt @@ -1071,10 +1071,6 @@ Query: UPDATE connections SET smp_agent_version = ? WHERE conn_id = ? Plan: SEARCH connections USING PRIMARY KEY (conn_id=?) -Query: UPDATE connections SET user_id = ? WHERE conn_id = ? and user_id = ? -Plan: -SEARCH connections USING PRIMARY KEY (conn_id=?) - Query: UPDATE messages SET msg_body = x'' WHERE conn_id = ? AND internal_id = ? Plan: SEARCH messages USING PRIMARY KEY (conn_id=? AND internal_id=?) diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt index 1f16e0fdff..e9ade30f93 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt @@ -4215,14 +4215,6 @@ Query: Plan: SEARCH connections USING INTEGER PRIMARY KEY (rowid=?) -Query: - UPDATE connections - SET user_id = ?, custom_user_profile_id = NULL, updated_at = ? - WHERE user_id = ? AND connection_id = ? - -Plan: -SEARCH connections USING INTEGER PRIMARY KEY (rowid=?) - Query: UPDATE contact_profiles SET contact_link = ?, updated_at = ? diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 42d1132961..4ba5acbb43 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -1747,10 +1747,9 @@ viewConnectionIncognitoUpdated PendingContactConnection {pccConnId, customUserPr | otherwise = ["connection " <> sShow pccConnId <> " changed to non incognito"] viewConnectionUserChanged :: User -> PendingContactConnection -> User -> PendingContactConnection -> [StyledString] -viewConnectionUserChanged User {localDisplayName = n} PendingContactConnection {pccConnId, connLinkInv} User {localDisplayName = n'} PendingContactConnection {connLinkInv = connLinkInv'} = - case (connLinkInv, connLinkInv') of - (Just ccLink, Just ccLink') - | ccLink /= ccLink' -> [userChangedStr <> ", new link:"] <> newLink ccLink' +viewConnectionUserChanged User {localDisplayName = n} PendingContactConnection {pccConnId} User {localDisplayName = n'} PendingContactConnection {connLinkInv = connLinkInv'} = + case connLinkInv' of + Just ccLink' -> [userChangedStr <> ", new link:"] <> newLink ccLink' _ -> [userChangedStr] where userChangedStr = "connection " <> sShow pccConnId <> " changed from user " <> plain n <> " to user " <> plain n' diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index 433615e62a..adff745200 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -1827,7 +1827,7 @@ testChangePCCUser = testChat2 aliceProfile bobProfile $ \alice bob -> do -- Create a new invite alice ##> "/connect" - inv <- getInvitation alice + _ <- getInvitation alice -- Create new user and go back to original user alice ##> "/create user alisa" showActiveUser alice "alisa" @@ -1837,12 +1837,18 @@ testChangePCCUser = testChat2 aliceProfile bobProfile $ showActiveUser alice "alice (Alice)" -- Change connection to newly created user alice ##> "/_set conn user :1 2" - alice <## "connection 1 changed from user alice to user alisa" + alice <## "connection 1 changed from user alice to user alisa, new link:" + alice <## "" + _ <- getTermLine alice + alice <## "" alice ##> "/user alisa" showActiveUser alice "alisa" -- Change connection back to other user alice ##> "/_set conn user :1 3" - alice <## "connection 1 changed from user alisa to user alisa2" + alice <## "connection 1 changed from user alisa to user alisa2, new link:" + alice <## "" + inv <- getTermLine alice + alice <## "" alice ##> "/user alisa2" showActiveUser alice "alisa2" -- Connect @@ -1851,13 +1857,14 @@ testChangePCCUser = testChat2 aliceProfile bobProfile $ concurrently_ (alice <## "bob (Bob): contact is connected") (bob <## "alisa2: contact is connected") + alice <##> bob testChangePCCUserFromIncognito :: HasCallStack => TestParams -> IO () testChangePCCUserFromIncognito = testChat2 aliceProfile bobProfile $ \alice bob -> do -- Create a new invite and set as incognito alice ##> "/connect" - inv <- getInvitation alice + _ <- getInvitation alice alice ##> "/_set incognito :1 on" alice <## "connection 1 changed to incognito" -- Create new user and go back to original user @@ -1867,13 +1874,19 @@ testChangePCCUserFromIncognito = testChat2 aliceProfile bobProfile $ showActiveUser alice "alice (Alice)" -- Change connection to newly created user alice ##> "/_set conn user :1 2" - alice <## "connection 1 changed from user alice to user alisa" + alice <## "connection 1 changed from user alice to user alisa, new link:" + alice <## "" + _ <- getTermLine alice + alice <## "" alice `hasContactProfiles` ["alice"] alice ##> "/user alisa" showActiveUser alice "alisa" -- Change connection back to initial user alice ##> "/_set conn user :1 1" - alice <## "connection 1 changed from user alisa to user alice" + alice <## "connection 1 changed from user alisa to user alice, new link:" + alice <## "" + inv <- getTermLine alice + alice <## "" alice ##> "/user alice" showActiveUser alice "alice (Alice)" -- Connect @@ -1882,13 +1895,14 @@ testChangePCCUserFromIncognito = testChat2 aliceProfile bobProfile $ concurrently_ (alice <## "bob (Bob): contact is connected") (bob <## "alice (Alice): contact is connected") + alice <##> bob testChangePCCUserAndThenIncognito :: HasCallStack => TestParams -> IO () testChangePCCUserAndThenIncognito = testChat2 aliceProfile bobProfile $ \alice bob -> do -- Create a new invite and set as incognito alice ##> "/connect" - inv <- getInvitation alice + _ <- getInvitation alice -- Create new user and go back to original user alice ##> "/create user alisa" showActiveUser alice "alisa" @@ -1896,7 +1910,10 @@ testChangePCCUserAndThenIncognito = testChat2 aliceProfile bobProfile $ showActiveUser alice "alice (Alice)" -- Change connection to newly created user alice ##> "/_set conn user :1 2" - alice <## "connection 1 changed from user alice to user alisa" + alice <## "connection 1 changed from user alice to user alisa, new link:" + alice <## "" + inv <- getTermLine alice + alice <## "" alice ##> "/user alisa" showActiveUser alice "alisa" -- Change connection to incognito and make sure it's attached to the newly created user profile @@ -1911,6 +1928,10 @@ testChangePCCUserAndThenIncognito = testChat2 aliceProfile bobProfile $ alice <## ("bob (Bob): contact is connected, your incognito profile for this contact is " <> alisaIncognito) alice <## ("use /i bob to print out this incognito profile again") ] + alice ?#> "@bob hi" + bob <# (alisaIncognito <> "> hi") + bob #> ("@" <> alisaIncognito <> " hey") + alice ?<# "bob> hey" testChangePCCUserDiffSrv :: HasCallStack => TestParams -> IO () testChangePCCUserDiffSrv ps = do @@ -1951,6 +1972,7 @@ testChangePCCUserDiffSrv ps = do concurrently_ (alice <## "bob (Bob): contact is connected") (bob <## "alisa: contact is connected") + alice <##> bob where serverCfg' = smpServerCfg